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/Makefile b/drivers/s390/cio/Makefile
new file mode 100644
index 0000000..c490c2a
--- /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 0000000..3720e77
--- /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 0000000..7d6be3f
--- /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 0000000..4a06c7d
--- /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 0000000..fb42caf
--- /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 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);
diff --git a/drivers/s390/cio/chsc.c b/drivers/s390/cio/chsc.c
new file mode 100644
index 0000000..b35fe12
--- /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;
+	}
+}
+
+void
+chpid_is_actually_online(int chp)
+{
+	int state;
+
+	state = get_chp_status(chp);
+	if (state < 0) {
+		need_rescan = 1;
+		queue_work(slow_path_wq, &slow_path_work);
+	} else
+		WARN_ON(!state);
+}
+
+/* FIXME: this is _always_ called for every subchannel. shouldn't we
+ *	  process more than one at a time? */
+static int
+chsc_get_sch_desc_irq(struct subchannel *sch, void *page)
+{
+	int ccode, j;
+
+	struct {
+		struct chsc_header request;
+		u16 reserved1;
+		u16 f_sch;	  /* first subchannel */
+		u16 reserved2;
+		u16 l_sch;	  /* last subchannel */
+		u32 reserved3;
+		struct chsc_header response;
+		u32 reserved4;
+		u8 sch_valid : 1;
+		u8 dev_valid : 1;
+		u8 st	     : 3; /* subchannel type */
+		u8 zeroes    : 3;
+		u8  unit_addr;	  /* unit address */
+		u16 devno;	  /* device number */
+		u8 path_mask;
+		u8 fla_valid_mask;
+		u16 sch;	  /* subchannel */
+		u8 chpid[8];	  /* chpids 0-7 */
+		u16 fla[8];	  /* full link addresses 0-7 */
+	} *ssd_area;
+
+	ssd_area = page;
+
+	ssd_area->request = (struct chsc_header) {
+		.length = 0x0010,
+		.code   = 0x0004,
+	};
+
+	ssd_area->f_sch = sch->irq;
+	ssd_area->l_sch = sch->irq;
+
+	ccode = chsc(ssd_area);
+	if (ccode > 0) {
+		pr_debug("chsc returned with ccode = %d\n", ccode);
+		return (ccode == 3) ? -ENODEV : -EBUSY;
+	}
+
+	switch (ssd_area->response.code) {
+	case 0x0001: /* everything ok */
+		break;
+	case 0x0002:
+		CIO_CRW_EVENT(2, "Invalid command!\n");
+		return -EINVAL;
+	case 0x0003:
+		CIO_CRW_EVENT(2, "Error in chsc request block!\n");
+		return -EINVAL;
+	case 0x0004:
+		CIO_CRW_EVENT(2, "Model does not provide ssd\n");
+		return -EOPNOTSUPP;
+	default:
+		CIO_CRW_EVENT(2, "Unknown CHSC response %d\n",
+			      ssd_area->response.code);
+		return -EIO;
+	}
+
+	/*
+	 * ssd_area->st stores the type of the detected
+	 * subchannel, with the following definitions:
+	 *
+	 * 0: I/O subchannel:	  All fields have meaning
+	 * 1: CHSC subchannel:	  Only sch_val, st and sch
+	 *			  have meaning
+	 * 2: Message subchannel: All fields except unit_addr
+	 *			  have meaning
+	 * 3: ADM subchannel:	  Only sch_val, st and sch
+	 *			  have meaning
+	 *
+	 * Other types are currently undefined.
+	 */
+	if (ssd_area->st > 3) { /* uhm, that looks strange... */
+		CIO_CRW_EVENT(0, "Strange subchannel type %d"
+			      " for sch %04x\n", ssd_area->st, sch->irq);
+		/*
+		 * There may have been a new subchannel type defined in the
+		 * time since this code was written; since we don't know which
+		 * fields have meaning and what to do with it we just jump out
+		 */
+		return 0;
+	} else {
+		const char *type[4] = {"I/O", "chsc", "message", "ADM"};
+		CIO_CRW_EVENT(6, "ssd: sch %04x is %s subchannel\n",
+			      sch->irq, type[ssd_area->st]);
+
+		sch->ssd_info.valid = 1;
+		sch->ssd_info.type = ssd_area->st;
+	}
+
+	if (ssd_area->st == 0 || ssd_area->st == 2) {
+		for (j = 0; j < 8; j++) {
+			if (!((0x80 >> j) & ssd_area->path_mask &
+			      ssd_area->fla_valid_mask))
+				continue;
+			sch->ssd_info.chpid[j] = ssd_area->chpid[j];
+			sch->ssd_info.fla[j]   = ssd_area->fla[j];
+		}
+	}
+	return 0;
+}
+
+int
+css_get_ssd_info(struct subchannel *sch)
+{
+	int ret;
+	void *page;
+
+	page = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
+	if (!page)
+		return -ENOMEM;
+	spin_lock_irq(&sch->lock);
+	ret = chsc_get_sch_desc_irq(sch, page);
+	if (ret) {
+		static int cio_chsc_err_msg;
+		
+		if (!cio_chsc_err_msg) {
+			printk(KERN_ERR
+			       "chsc_get_sch_descriptions:"
+			       " Error %d while doing chsc; "
+			       "processing some machine checks may "
+			       "not work\n", ret);
+			cio_chsc_err_msg = 1;
+		}
+	}
+	spin_unlock_irq(&sch->lock);
+	free_page((unsigned long)page);
+	if (!ret) {
+		int j, chpid;
+		/* Allocate channel path structures, if needed. */
+		for (j = 0; j < 8; j++) {
+			chpid = sch->ssd_info.chpid[j];
+			if (chpid && (get_chp_status(chpid) < 0))
+			    new_channel_path(chpid);
+		}
+	}
+	return ret;
+}
+
+static int
+s390_subchannel_remove_chpid(struct device *dev, void *data)
+{
+	int j;
+	int mask;
+	struct subchannel *sch;
+	__u8 *chpid;
+	struct schib schib;
+
+	sch = to_subchannel(dev);
+	chpid = data;
+	for (j = 0; j < 8; j++)
+		if (sch->schib.pmcw.chpid[j] == *chpid)
+			break;
+	if (j >= 8)
+		return 0;
+
+	mask = 0x80 >> j;
+	spin_lock(&sch->lock);
+
+	stsch(sch->irq, &schib);
+	if (!schib.pmcw.dnv)
+		goto out_unreg;
+	memcpy(&sch->schib, &schib, sizeof(struct schib));
+	/* Check for single path devices. */
+	if (sch->schib.pmcw.pim == 0x80)
+		goto out_unreg;
+	if (sch->vpm == mask)
+		goto out_unreg;
+
+	if ((sch->schib.scsw.actl & (SCSW_ACTL_CLEAR_PEND |
+				     SCSW_ACTL_HALT_PEND |
+				     SCSW_ACTL_START_PEND |
+				     SCSW_ACTL_RESUME_PEND)) &&
+	    (sch->schib.pmcw.lpum == mask)) {
+		int cc = cio_cancel(sch);
+		
+		if (cc == -ENODEV)
+			goto out_unreg;
+
+		if (cc == -EINVAL) {
+			cc = cio_clear(sch);
+			if (cc == -ENODEV)
+				goto out_unreg;
+			/* Call handler. */
+			if (sch->driver && sch->driver->termination)
+				sch->driver->termination(&sch->dev);
+			goto out_unlock;
+		}
+	} else if ((sch->schib.scsw.actl & SCSW_ACTL_DEVACT) &&
+		   (sch->schib.scsw.actl & SCSW_ACTL_SCHACT) &&
+		   (sch->schib.pmcw.lpum == mask)) {
+		int cc;
+
+		cc = cio_clear(sch);
+		if (cc == -ENODEV)
+			goto out_unreg;
+		/* Call handler. */
+		if (sch->driver && sch->driver->termination)
+			sch->driver->termination(&sch->dev);
+		goto out_unlock;
+	}
+
+	/* trigger path verification. */
+	if (sch->driver && sch->driver->verify)
+		sch->driver->verify(&sch->dev);
+out_unlock:
+	spin_unlock(&sch->lock);
+	return 0;
+out_unreg:
+	spin_unlock(&sch->lock);
+	sch->lpm = 0;
+	if (css_enqueue_subchannel_slow(sch->irq)) {
+		css_clear_subchannel_slow_list();
+		need_rescan = 1;
+	}
+	return 0;
+}
+
+static inline void
+s390_set_chpid_offline( __u8 chpid)
+{
+	char dbf_txt[15];
+
+	sprintf(dbf_txt, "chpr%x", chpid);
+	CIO_TRACE_EVENT(2, dbf_txt);
+
+	if (get_chp_status(chpid) <= 0)
+		return;
+
+	bus_for_each_dev(&css_bus_type, NULL, &chpid,
+			 s390_subchannel_remove_chpid);
+
+	if (need_rescan || css_slow_subchannels_exist())
+		queue_work(slow_path_wq, &slow_path_work);
+}
+
+static int
+s390_process_res_acc_sch(u8 chpid, __u16 fla, u32 fla_mask,
+			 struct subchannel *sch)
+{
+	int found;
+	int chp;
+	int ccode;
+	
+	found = 0;
+	for (chp = 0; chp <= 7; chp++)
+		/*
+		 * check if chpid is in information updated by ssd
+		 */
+		if (sch->ssd_info.valid &&
+		    sch->ssd_info.chpid[chp] == chpid &&
+		    (sch->ssd_info.fla[chp] & fla_mask) == fla) {
+			found = 1;
+			break;
+		}
+	
+	if (found == 0)
+		return 0;
+
+	/*
+	 * Do a stsch to update our subchannel structure with the
+	 * new path information and eventually check for logically
+	 * offline chpids.
+	 */
+	ccode = stsch(sch->irq, &sch->schib);
+	if (ccode > 0)
+		return 0;
+
+	return 0x80 >> chp;
+}
+
+static int
+s390_process_res_acc (u8 chpid, __u16 fla, u32 fla_mask)
+{
+	struct subchannel *sch;
+	int irq, rc;
+	char dbf_txt[15];
+
+	sprintf(dbf_txt, "accpr%x", chpid);
+	CIO_TRACE_EVENT( 2, dbf_txt);
+	if (fla != 0) {
+		sprintf(dbf_txt, "fla%x", fla);
+		CIO_TRACE_EVENT( 2, dbf_txt);
+	}
+
+	/*
+	 * I/O resources may have become accessible.
+	 * Scan through all subchannels that may be concerned and
+	 * do a validation on those.
+	 * The more information we have (info), the less scanning
+	 * will we have to do.
+	 */
+
+	if (!get_chp_status(chpid))
+		return 0; /* no need to do the rest */
+
+	rc = 0;
+	for (irq = 0; irq < __MAX_SUBCHANNELS; irq++) {
+		int chp_mask, old_lpm;
+
+		sch = get_subchannel_by_schid(irq);
+		if (!sch) {
+			struct schib schib;
+			int ret;
+			/*
+			 * We don't know the device yet, but since a path
+			 * may be available now to the device we'll have
+			 * to do recognition again.
+			 * Since we don't have any idea about which chpid
+			 * that beast may be on we'll have to do a stsch
+			 * on all devices, grr...
+			 */
+			if (stsch(irq, &schib)) {
+				/* We're through */
+				if (need_rescan)
+					rc = -EAGAIN;
+				break;
+			}
+			if (need_rescan) {
+				rc = -EAGAIN;
+				continue;
+			}
+			/* Put it on the slow path. */
+			ret = css_enqueue_subchannel_slow(irq);
+			if (ret) {
+				css_clear_subchannel_slow_list();
+				need_rescan = 1;
+			}
+			rc = -EAGAIN;
+			continue;
+		}
+	
+		spin_lock_irq(&sch->lock);
+
+		chp_mask = s390_process_res_acc_sch(chpid, fla, fla_mask, sch);
+
+		if (chp_mask == 0) {
+
+			spin_unlock_irq(&sch->lock);
+
+			if (fla_mask != 0)
+				break;
+			else
+				continue;
+		}
+		old_lpm = sch->lpm;
+		sch->lpm = ((sch->schib.pmcw.pim &
+			     sch->schib.pmcw.pam &
+			     sch->schib.pmcw.pom)
+			    | chp_mask) & sch->opm;
+		if (!old_lpm && sch->lpm)
+			device_trigger_reprobe(sch);
+		else if (sch->driver && sch->driver->verify)
+			sch->driver->verify(&sch->dev);
+
+		spin_unlock_irq(&sch->lock);
+		put_device(&sch->dev);
+		if (fla_mask != 0)
+			break;
+	}
+	return rc;
+}
+
+static int
+__get_chpid_from_lir(void *data)
+{
+	struct lir {
+		u8  iq;
+		u8  ic;
+		u16 sci;
+		/* incident-node descriptor */
+		u32 indesc[28];
+		/* attached-node descriptor */
+		u32 andesc[28];
+		/* incident-specific information */
+		u32 isinfo[28];
+	} *lir;
+
+	lir = (struct lir*) data;
+	if (!(lir->iq&0x80))
+		/* NULL link incident record */
+		return -EINVAL;
+	if (!(lir->indesc[0]&0xc0000000))
+		/* node descriptor not valid */
+		return -EINVAL;
+	if (!(lir->indesc[0]&0x10000000))
+		/* don't handle device-type nodes - FIXME */
+		return -EINVAL;
+	/* Byte 3 contains the chpid. Could also be CTCA, but we don't care */
+
+	return (u16) (lir->indesc[0]&0x000000ff);
+}
+
+int
+chsc_process_crw(void)
+{
+	int chpid, ret;
+	struct {
+		struct chsc_header request;
+		u32 reserved1;
+		u32 reserved2;
+		u32 reserved3;
+		struct chsc_header response;
+		u32 reserved4;
+		u8  flags;
+		u8  vf;		/* validity flags */
+		u8  rs;		/* reporting source */
+		u8  cc;		/* content code */
+		u16 fla;	/* full link address */
+		u16 rsid;	/* reporting source id */
+		u32 reserved5;
+		u32 reserved6;
+		u32 ccdf[96];	/* content-code dependent field */
+		/* ccdf has to be big enough for a link-incident record */
+	} *sei_area;
+
+	if (!sei_page)
+		return 0;
+	/*
+	 * build the chsc request block for store event information
+	 * and do the call
+	 * This function is only called by the machine check handler thread,
+	 * so we don't need locking for the sei_page.
+	 */
+	sei_area = sei_page;
+
+	CIO_TRACE_EVENT( 2, "prcss");
+	ret = 0;
+	do {
+		int ccode, status;
+		memset(sei_area, 0, sizeof(*sei_area));
+
+		sei_area->request = (struct chsc_header) {
+			.length = 0x0010,
+			.code   = 0x000e,
+		};
+
+		ccode = chsc(sei_area);
+		if (ccode > 0)
+			return 0;
+
+		switch (sei_area->response.code) {
+			/* for debug purposes, check for problems */
+		case 0x0001:
+			CIO_CRW_EVENT(4, "chsc_process_crw: event information "
+					"successfully stored\n");
+			break; /* everything ok */
+		case 0x0002:
+			CIO_CRW_EVENT(2,
+				      "chsc_process_crw: invalid command!\n");
+			return 0;
+		case 0x0003:
+			CIO_CRW_EVENT(2, "chsc_process_crw: error in chsc "
+				      "request block!\n");
+			return 0;
+		case 0x0005:
+			CIO_CRW_EVENT(2, "chsc_process_crw: no event "
+				      "information stored\n");
+			return 0;
+		default:
+			CIO_CRW_EVENT(2, "chsc_process_crw: chsc response %d\n",
+				      sei_area->response.code);
+			return 0;
+		}
+
+		/* Check if we might have lost some information. */
+		if (sei_area->flags & 0x40)
+			CIO_CRW_EVENT(2, "chsc_process_crw: Event information "
+				       "has been lost due to overflow!\n");
+
+		if (sei_area->rs != 4) {
+			CIO_CRW_EVENT(2, "chsc_process_crw: reporting source "
+				      "(%04X) isn't a chpid!\n",
+				      sei_area->rsid);
+			continue;
+		}
+
+		/* which kind of information was stored? */
+		switch (sei_area->cc) {
+		case 1: /* link incident*/
+			CIO_CRW_EVENT(4, "chsc_process_crw: "
+				      "channel subsystem reports link incident,"
+				      " reporting source is chpid %x\n",
+				      sei_area->rsid);
+			chpid = __get_chpid_from_lir(sei_area->ccdf);
+			if (chpid < 0)
+				CIO_CRW_EVENT(4, "%s: Invalid LIR, skipping\n",
+					      __FUNCTION__);
+			else
+				s390_set_chpid_offline(chpid);
+			break;
+			
+		case 2: /* i/o resource accessibiliy */
+			CIO_CRW_EVENT(4, "chsc_process_crw: "
+				      "channel subsystem reports some I/O "
+				      "devices may have become accessible\n");
+			pr_debug("Data received after sei: \n");
+			pr_debug("Validity flags: %x\n", sei_area->vf);
+			
+			/* allocate a new channel path structure, if needed */
+			status = get_chp_status(sei_area->rsid);
+			if (status < 0)
+				new_channel_path(sei_area->rsid);
+			else if (!status)
+				return 0;
+			if ((sei_area->vf & 0x80) == 0) {
+				pr_debug("chpid: %x\n", sei_area->rsid);
+				ret = s390_process_res_acc(sei_area->rsid,
+							   0, 0);
+			} else if ((sei_area->vf & 0xc0) == 0x80) {
+				pr_debug("chpid: %x link addr: %x\n",
+					 sei_area->rsid, sei_area->fla);
+				ret = s390_process_res_acc(sei_area->rsid,
+							   sei_area->fla,
+							   0xff00);
+			} else if ((sei_area->vf & 0xc0) == 0xc0) {
+				pr_debug("chpid: %x full link addr: %x\n",
+					 sei_area->rsid, sei_area->fla);
+				ret = s390_process_res_acc(sei_area->rsid,
+							   sei_area->fla,
+							   0xffff);
+			}
+			pr_debug("\n");
+			
+			break;
+			
+		default: /* other stuff */
+			CIO_CRW_EVENT(4, "chsc_process_crw: event %d\n",
+				      sei_area->cc);
+			break;
+		}
+	} while (sei_area->flags & 0x80);
+	return ret;
+}
+
+static int
+chp_add(int chpid)
+{
+	struct subchannel *sch;
+	int irq, ret, rc;
+	char dbf_txt[15];
+
+	if (!get_chp_status(chpid))
+		return 0; /* no need to do the rest */
+	
+	sprintf(dbf_txt, "cadd%x", chpid);
+	CIO_TRACE_EVENT(2, dbf_txt);
+
+	rc = 0;
+	for (irq = 0; irq < __MAX_SUBCHANNELS; irq++) {
+		int i;
+
+		sch = get_subchannel_by_schid(irq);
+		if (!sch) {
+			struct schib schib;
+
+			if (stsch(irq, &schib)) {
+				/* We're through */
+				if (need_rescan)
+					rc = -EAGAIN;
+				break;
+			}
+			if (need_rescan) {
+				rc = -EAGAIN;
+				continue;
+			}
+			/* Put it on the slow path. */
+			ret = css_enqueue_subchannel_slow(irq);
+			if (ret) {
+				css_clear_subchannel_slow_list();
+				need_rescan = 1;
+			}
+			rc = -EAGAIN;
+			continue;
+		}
+	
+		spin_lock(&sch->lock);
+		for (i=0; i<8; i++)
+			if (sch->schib.pmcw.chpid[i] == chpid) {
+				if (stsch(sch->irq, &sch->schib) != 0) {
+					/* Endgame. */
+					spin_unlock(&sch->lock);
+					return rc;
+				}
+				break;
+			}
+		if (i==8) {
+			spin_unlock(&sch->lock);
+			return rc;
+		}
+		sch->lpm = ((sch->schib.pmcw.pim &
+			     sch->schib.pmcw.pam &
+			     sch->schib.pmcw.pom)
+			    | 0x80 >> i) & sch->opm;
+
+		if (sch->driver && sch->driver->verify)
+			sch->driver->verify(&sch->dev);
+
+		spin_unlock(&sch->lock);
+		put_device(&sch->dev);
+	}
+	return rc;
+}
+
+/* 
+ * Handling of crw machine checks with channel path source.
+ */
+int
+chp_process_crw(int chpid, int on)
+{
+	if (on == 0) {
+		/* Path has gone. We use the link incident routine.*/
+		s390_set_chpid_offline(chpid);
+		return 0; /* De-register is async anyway. */
+	}
+	/*
+	 * Path has come. Allocate a new channel path structure,
+	 * if needed.
+	 */
+	if (get_chp_status(chpid) < 0)
+		new_channel_path(chpid);
+	/* Avoid the extra overhead in process_rec_acc. */
+	return chp_add(chpid);
+}
+
+static inline int
+__check_for_io_and_kill(struct subchannel *sch, int index)
+{
+	int cc;
+
+	if (!device_is_online(sch))
+		/* cio could be doing I/O. */
+		return 0;
+	cc = stsch(sch->irq, &sch->schib);
+	if (cc)
+		return 0;
+	if (sch->schib.scsw.actl && sch->schib.pmcw.lpum == (0x80 >> index)) {
+		device_set_waiting(sch);
+		return 1;
+	}
+	return 0;
+}
+
+static inline void
+__s390_subchannel_vary_chpid(struct subchannel *sch, __u8 chpid, int on)
+{
+	int chp, old_lpm;
+	unsigned long flags;
+
+	if (!sch->ssd_info.valid)
+		return;
+	
+	spin_lock_irqsave(&sch->lock, flags);
+	old_lpm = sch->lpm;
+	for (chp = 0; chp < 8; chp++) {
+		if (sch->ssd_info.chpid[chp] != chpid)
+			continue;
+
+		if (on) {
+			sch->opm |= (0x80 >> chp);
+			sch->lpm |= (0x80 >> chp);
+			if (!old_lpm)
+				device_trigger_reprobe(sch);
+			else if (sch->driver && sch->driver->verify)
+				sch->driver->verify(&sch->dev);
+		} else {
+			sch->opm &= ~(0x80 >> chp);
+			sch->lpm &= ~(0x80 >> chp);
+			/*
+			 * Give running I/O a grace period in which it
+			 * can successfully terminate, even using the
+			 * just varied off path. Then kill it.
+			 */
+			if (!__check_for_io_and_kill(sch, chp) && !sch->lpm) {
+				if (css_enqueue_subchannel_slow(sch->irq)) {
+					css_clear_subchannel_slow_list();
+					need_rescan = 1;
+				}
+			} else if (sch->driver && sch->driver->verify)
+				sch->driver->verify(&sch->dev);
+		}
+		break;
+	}
+	spin_unlock_irqrestore(&sch->lock, flags);
+}
+
+static int
+s390_subchannel_vary_chpid_off(struct device *dev, void *data)
+{
+	struct subchannel *sch;
+	__u8 *chpid;
+
+	sch = to_subchannel(dev);
+	chpid = data;
+
+	__s390_subchannel_vary_chpid(sch, *chpid, 0);
+	return 0;
+}
+
+static int
+s390_subchannel_vary_chpid_on(struct device *dev, void *data)
+{
+	struct subchannel *sch;
+	__u8 *chpid;
+
+	sch = to_subchannel(dev);
+	chpid = data;
+
+	__s390_subchannel_vary_chpid(sch, *chpid, 1);
+	return 0;
+}
+
+/*
+ * Function: s390_vary_chpid
+ * Varies the specified chpid online or offline
+ */
+static int
+s390_vary_chpid( __u8 chpid, int on)
+{
+	char dbf_text[15];
+	int status, irq, ret;
+	struct subchannel *sch;
+
+	sprintf(dbf_text, on?"varyon%x":"varyoff%x", chpid);
+	CIO_TRACE_EVENT( 2, dbf_text);
+
+	status = get_chp_status(chpid);
+	if (status < 0) {
+		printk(KERN_ERR "Can't vary unknown chpid %02X\n", chpid);
+		return -EINVAL;
+	}
+
+	if (!on && !status) {
+		printk(KERN_ERR "chpid %x is already offline\n", chpid);
+		return -EINVAL;
+	}
+
+	set_chp_logically_online(chpid, on);
+
+	/*
+	 * Redo PathVerification on the devices the chpid connects to
+	 */
+
+	bus_for_each_dev(&css_bus_type, NULL, &chpid, on ?
+			 s390_subchannel_vary_chpid_on :
+			 s390_subchannel_vary_chpid_off);
+	if (!on)
+		goto out;
+	/* Scan for new devices on varied on path. */
+	for (irq = 0; irq < __MAX_SUBCHANNELS; irq++) {
+		struct schib schib;
+
+		if (need_rescan)
+			break;
+		sch = get_subchannel_by_schid(irq);
+		if (sch) {
+			put_device(&sch->dev);
+			continue;
+		}
+		if (stsch(irq, &schib))
+			/* We're through */
+			break;
+		/* Put it on the slow path. */
+		ret = css_enqueue_subchannel_slow(irq);
+		if (ret) {
+			css_clear_subchannel_slow_list();
+			need_rescan = 1;
+		}
+	}
+out:
+	if (need_rescan || css_slow_subchannels_exist())
+		queue_work(slow_path_wq, &slow_path_work);
+	return 0;
+}
+
+/*
+ * Files for the channel path entries.
+ */
+static ssize_t
+chp_status_show(struct device *dev, char *buf)
+{
+	struct channel_path *chp = container_of(dev, struct channel_path, dev);
+
+	if (!chp)
+		return 0;
+	return (get_chp_status(chp->id) ? sprintf(buf, "online\n") :
+		sprintf(buf, "offline\n"));
+}
+
+static ssize_t
+chp_status_write(struct device *dev, const char *buf, size_t count)
+{
+	struct channel_path *cp = container_of(dev, struct channel_path, dev);
+	char cmd[10];
+	int num_args;
+	int error;
+
+	num_args = sscanf(buf, "%5s", cmd);
+	if (!num_args)
+		return count;
+
+	if (!strnicmp(cmd, "on", 2))
+		error = s390_vary_chpid(cp->id, 1);
+	else if (!strnicmp(cmd, "off", 3))
+		error = s390_vary_chpid(cp->id, 0);
+	else
+		error = -EINVAL;
+
+	return error < 0 ? error : count;
+
+}
+
+static DEVICE_ATTR(status, 0644, chp_status_show, chp_status_write);
+
+static ssize_t
+chp_type_show(struct device *dev, char *buf)
+{
+	struct channel_path *chp = container_of(dev, struct channel_path, dev);
+
+	if (!chp)
+		return 0;
+	return sprintf(buf, "%x\n", chp->desc.desc);
+}
+
+static DEVICE_ATTR(type, 0444, chp_type_show, NULL);
+
+static struct attribute * chp_attrs[] = {
+	&dev_attr_status.attr,
+	&dev_attr_type.attr,
+	NULL,
+};
+
+static struct attribute_group chp_attr_group = {
+	.attrs = chp_attrs,
+};
+
+static void
+chp_release(struct device *dev)
+{
+	struct channel_path *cp;
+	
+	cp = container_of(dev, struct channel_path, dev);
+	kfree(cp);
+}
+
+static int
+chsc_determine_channel_path_description(int chpid,
+					struct channel_path_desc *desc)
+{
+	int ccode, ret;
+
+	struct {
+		struct chsc_header request;
+		u32 : 24;
+		u32 first_chpid : 8;
+		u32 : 24;
+		u32 last_chpid : 8;
+		u32 zeroes1;
+		struct chsc_header response;
+		u32 zeroes2;
+		struct channel_path_desc desc;
+	} *scpd_area;
+
+	scpd_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
+	if (!scpd_area)
+		return -ENOMEM;
+
+	scpd_area->request = (struct chsc_header) {
+		.length = 0x0010,
+		.code   = 0x0002,
+	};
+
+	scpd_area->first_chpid = chpid;
+	scpd_area->last_chpid = chpid;
+
+	ccode = chsc(scpd_area);
+	if (ccode > 0) {
+		ret = (ccode == 3) ? -ENODEV : -EBUSY;
+		goto out;
+	}
+
+	switch (scpd_area->response.code) {
+	case 0x0001: /* Success. */
+		memcpy(desc, &scpd_area->desc,
+		       sizeof(struct channel_path_desc));
+		ret = 0;
+		break;
+	case 0x0003: /* Invalid block. */
+	case 0x0007: /* Invalid format. */
+	case 0x0008: /* Other invalid block. */
+		CIO_CRW_EVENT(2, "Error in chsc request block!\n");
+		ret = -EINVAL;
+		break;
+	case 0x0004: /* Command not provided in model. */
+		CIO_CRW_EVENT(2, "Model does not provide scpd\n");
+		ret = -EOPNOTSUPP;
+		break;
+	default:
+		CIO_CRW_EVENT(2, "Unknown CHSC response %d\n",
+			      scpd_area->response.code);
+		ret = -EIO;
+	}
+out:
+	free_page((unsigned long)scpd_area);
+	return ret;
+}
+
+/*
+ * Entries for chpids on the system bus.
+ * This replaces /proc/chpids.
+ */
+static int
+new_channel_path(int chpid)
+{
+	struct channel_path *chp;
+	int ret;
+
+	chp = kmalloc(sizeof(struct channel_path), GFP_KERNEL);
+	if (!chp)
+		return -ENOMEM;
+	memset(chp, 0, sizeof(struct channel_path));
+
+	/* fill in status, etc. */
+	chp->id = chpid;
+	chp->state = 1;
+	chp->dev = (struct device) {
+		.parent  = &css_bus_device,
+		.release = chp_release,
+	};
+	snprintf(chp->dev.bus_id, BUS_ID_SIZE, "chp0.%x", chpid);
+
+	/* Obtain channel path description and fill it in. */
+	ret = chsc_determine_channel_path_description(chpid, &chp->desc);
+	if (ret)
+		goto out_free;
+
+	/* make it known to the system */
+	ret = device_register(&chp->dev);
+	if (ret) {
+		printk(KERN_WARNING "%s: could not register %02x\n",
+		       __func__, chpid);
+		goto out_free;
+	}
+	ret = sysfs_create_group(&chp->dev.kobj, &chp_attr_group);
+	if (ret) {
+		device_unregister(&chp->dev);
+		goto out_free;
+	} else
+		chps[chpid] = chp;
+	return ret;
+out_free:
+	kfree(chp);
+	return ret;
+}
+
+void *
+chsc_get_chp_desc(struct subchannel *sch, int chp_no)
+{
+	struct channel_path *chp;
+	struct channel_path_desc *desc;
+
+	chp = chps[sch->schib.pmcw.chpid[chp_no]];
+	if (!chp)
+		return NULL;
+	desc = kmalloc(sizeof(struct channel_path_desc), GFP_KERNEL);
+	if (!desc)
+		return NULL;
+	memcpy(desc, &chp->desc, sizeof(struct channel_path_desc));
+	return desc;
+}
+
+
+static int __init
+chsc_alloc_sei_area(void)
+{
+	sei_page = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
+	if (!sei_page)
+		printk(KERN_WARNING"Can't allocate page for processing of " \
+		       "chsc machine checks!\n");
+	return (sei_page ? 0 : -ENOMEM);
+}
+
+subsys_initcall(chsc_alloc_sei_area);
+
+struct css_general_char css_general_characteristics;
+struct css_chsc_char css_chsc_characteristics;
+
+int __init
+chsc_determine_css_characteristics(void)
+{
+	int result;
+	struct {
+		struct chsc_header request;
+		u32 reserved1;
+		u32 reserved2;
+		u32 reserved3;
+		struct chsc_header response;
+		u32 reserved4;
+		u32 general_char[510];
+		u32 chsc_char[518];
+	} *scsc_area;
+
+	scsc_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
+	if (!scsc_area) {
+	        printk(KERN_WARNING"cio: Was not able to determine available" \
+		       "CHSCs due to no memory.\n");
+		return -ENOMEM;
+	}
+
+	scsc_area->request = (struct chsc_header) {
+		.length = 0x0010,
+		.code   = 0x0010,
+	};
+
+	result = chsc(scsc_area);
+	if (result) {
+		printk(KERN_WARNING"cio: Was not able to determine " \
+		       "available CHSCs, cc=%i.\n", result);
+		result = -EIO;
+		goto exit;
+	}
+
+	if (scsc_area->response.code != 1) {
+		printk(KERN_WARNING"cio: Was not able to determine " \
+		       "available CHSCs.\n");
+		result = -EIO;
+		goto exit;
+	}
+	memcpy(&css_general_characteristics, scsc_area->general_char,
+	       sizeof(css_general_characteristics));
+	memcpy(&css_chsc_characteristics, scsc_area->chsc_char,
+	       sizeof(css_chsc_characteristics));
+exit:
+	free_page ((unsigned long) scsc_area);
+	return result;
+}
+
+EXPORT_SYMBOL_GPL(css_general_characteristics);
+EXPORT_SYMBOL_GPL(css_chsc_characteristics);
diff --git a/drivers/s390/cio/chsc.h b/drivers/s390/cio/chsc.h
new file mode 100644
index 0000000..be20da4
--- /dev/null
+++ b/drivers/s390/cio/chsc.h
@@ -0,0 +1,66 @@
+#ifndef S390_CHSC_H
+#define S390_CHSC_H
+
+#define NR_CHPIDS 256
+
+#define CHSC_SEI_ACC_CHPID        1
+#define CHSC_SEI_ACC_LINKADDR     2
+#define CHSC_SEI_ACC_FULLLINKADDR 3
+
+struct chsc_header {
+	u16 length;
+	u16 code;
+};
+
+struct channel_path_desc {
+	u8 flags;
+	u8 lsn;
+	u8 desc;
+	u8 chpid;
+	u8 swla;
+	u8 zeroes;
+	u8 chla;
+	u8 chpp;
+};
+
+struct channel_path {
+	int id;
+	int state;
+	struct channel_path_desc desc;
+	struct device dev;
+};
+
+extern void s390_process_css( void );
+extern void chsc_validate_chpids(struct subchannel *);
+extern void chpid_is_actually_online(int);
+
+struct css_general_char {
+	u64 : 41;
+	u32 aif : 1;     /* bit 41 */
+	u32 : 3;
+	u32 mcss : 1;    /* bit 45 */
+	u32 : 2;
+	u32 ext_mb : 1;  /* bit 48 */
+	u32 : 7;
+	u32 aif_tdd : 1; /* bit 56 */
+	u32 : 10;
+	u32 aif_osa : 1; /* bit 67 */
+	u32 : 28;
+}__attribute__((packed));
+
+struct css_chsc_char {
+	u64 res;
+	u64 : 43;
+	u32 scssc : 1;  /* bit 107 */
+	u32 scsscf : 1; /* bit 108 */
+	u32 : 19;
+}__attribute__((packed));
+
+extern struct css_general_char css_general_characteristics;
+extern struct css_chsc_char css_chsc_characteristics;
+
+extern int chsc_determine_css_characteristics(void);
+extern int css_characteristics_avail;
+
+extern void *chsc_get_chp_desc(struct subchannel*, int);
+#endif
diff --git a/drivers/s390/cio/cio.c b/drivers/s390/cio/cio.c
new file mode 100644
index 0000000..99ce5a5
--- /dev/null
+++ b/drivers/s390/cio/cio.c
@@ -0,0 +1,860 @@
+/*
+ *  drivers/s390/cio/cio.c
+ *   S/390 common I/O routines -- low level i/o calls
+ *   $Revision: 1.131 $
+ *
+ *    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)
+ *		 Martin Schwidefsky (schwidefsky@de.ibm.com)
+ */
+
+#include <linux/module.h>
+#include <linux/config.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/kernel_stat.h>
+#include <linux/interrupt.h>
+
+#include <asm/cio.h>
+#include <asm/delay.h>
+#include <asm/irq.h>
+
+#include "airq.h"
+#include "cio.h"
+#include "css.h"
+#include "chsc.h"
+#include "ioasm.h"
+#include "blacklist.h"
+#include "cio_debug.h"
+
+debug_info_t *cio_debug_msg_id;
+debug_info_t *cio_debug_trace_id;
+debug_info_t *cio_debug_crw_id;
+
+int cio_show_msg;
+
+static int __init
+cio_setup (char *parm)
+{
+	if (!strcmp (parm, "yes"))
+		cio_show_msg = 1;
+	else if (!strcmp (parm, "no"))
+		cio_show_msg = 0;
+	else
+		printk (KERN_ERR "cio_setup : invalid cio_msg parameter '%s'",
+			parm);
+	return 1;
+}
+
+__setup ("cio_msg=", cio_setup);
+
+/*
+ * Function: cio_debug_init
+ * Initializes three debug logs (under /proc/s390dbf) for common I/O:
+ * - cio_msg logs the messages which are printk'ed when CONFIG_DEBUG_IO is on
+ * - cio_trace logs the calling of different functions
+ * - cio_crw logs the messages which are printk'ed when CONFIG_DEBUG_CRW is on
+ * debug levels depend on CONFIG_DEBUG_IO resp. CONFIG_DEBUG_CRW
+ */
+static int __init
+cio_debug_init (void)
+{
+	cio_debug_msg_id = debug_register ("cio_msg", 4, 4, 16*sizeof (long));
+	if (!cio_debug_msg_id)
+		goto out_unregister;
+	debug_register_view (cio_debug_msg_id, &debug_sprintf_view);
+	debug_set_level (cio_debug_msg_id, 2);
+	cio_debug_trace_id = debug_register ("cio_trace", 4, 4, 8);
+	if (!cio_debug_trace_id)
+		goto out_unregister;
+	debug_register_view (cio_debug_trace_id, &debug_hex_ascii_view);
+	debug_set_level (cio_debug_trace_id, 2);
+	cio_debug_crw_id = debug_register ("cio_crw", 2, 4, 16*sizeof (long));
+	if (!cio_debug_crw_id)
+		goto out_unregister;
+	debug_register_view (cio_debug_crw_id, &debug_sprintf_view);
+	debug_set_level (cio_debug_crw_id, 2);
+	pr_debug("debugging initialized\n");
+	return 0;
+
+out_unregister:
+	if (cio_debug_msg_id)
+		debug_unregister (cio_debug_msg_id);
+	if (cio_debug_trace_id)
+		debug_unregister (cio_debug_trace_id);
+	if (cio_debug_crw_id)
+		debug_unregister (cio_debug_crw_id);
+	pr_debug("could not initialize debugging\n");
+	return -1;
+}
+
+arch_initcall (cio_debug_init);
+
+int
+cio_set_options (struct subchannel *sch, int flags)
+{
+       sch->options.suspend = (flags & DOIO_ALLOW_SUSPEND) != 0;
+       sch->options.prefetch = (flags & DOIO_DENY_PREFETCH) != 0;
+       sch->options.inter = (flags & DOIO_SUPPRESS_INTER) != 0;
+       return 0;
+}
+
+/* FIXME: who wants to use this? */
+int
+cio_get_options (struct subchannel *sch)
+{
+       int flags;
+
+       flags = 0;
+       if (sch->options.suspend)
+		flags |= DOIO_ALLOW_SUSPEND;
+       if (sch->options.prefetch)
+		flags |= DOIO_DENY_PREFETCH;
+       if (sch->options.inter)
+		flags |= DOIO_SUPPRESS_INTER;
+       return flags;
+}
+
+/*
+ * Use tpi to get a pending interrupt, call the interrupt handler and
+ * return a pointer to the subchannel structure.
+ */
+static inline int
+cio_tpi(void)
+{
+	struct tpi_info *tpi_info;
+	struct subchannel *sch;
+	struct irb *irb;
+
+	tpi_info = (struct tpi_info *) __LC_SUBCHANNEL_ID;
+	if (tpi (NULL) != 1)
+		return 0;
+	irb = (struct irb *) __LC_IRB;
+	/* Store interrupt response block to lowcore. */
+	if (tsch (tpi_info->irq, irb) != 0)
+		/* Not status pending or not operational. */
+		return 1;
+	sch = (struct subchannel *)(unsigned long)tpi_info->intparm;
+	if (!sch)
+		return 1;
+	local_bh_disable();
+	irq_enter ();
+	spin_lock(&sch->lock);
+	memcpy (&sch->schib.scsw, &irb->scsw, sizeof (struct scsw));
+	if (sch->driver && sch->driver->irq)
+		sch->driver->irq(&sch->dev);
+	spin_unlock(&sch->lock);
+	irq_exit ();
+	__local_bh_enable();
+	return 1;
+}
+
+static inline int
+cio_start_handle_notoper(struct subchannel *sch, __u8 lpm)
+{
+	char dbf_text[15];
+
+	if (lpm != 0)
+		sch->lpm &= ~lpm;
+	else
+		sch->lpm = 0;
+
+	stsch (sch->irq, &sch->schib);
+
+	CIO_MSG_EVENT(0, "cio_start: 'not oper' status for "
+		      "subchannel %04x!\n", sch->irq);
+	sprintf(dbf_text, "no%s", sch->dev.bus_id);
+	CIO_TRACE_EVENT(0, dbf_text);
+	CIO_HEX_EVENT(0, &sch->schib, sizeof (struct schib));
+
+	return (sch->lpm ? -EACCES : -ENODEV);
+}
+
+int
+cio_start_key (struct subchannel *sch,	/* subchannel structure */
+	       struct ccw1 * cpa,	/* logical channel prog addr */
+	       __u8 lpm,		/* logical path mask */
+	       __u8 key)                /* storage key */
+{
+	char dbf_txt[15];
+	int ccode;
+
+	CIO_TRACE_EVENT (4, "stIO");
+	CIO_TRACE_EVENT (4, sch->dev.bus_id);
+
+	/* sch is always under 2G. */
+	sch->orb.intparm = (__u32)(unsigned long)sch;
+	sch->orb.fmt = 1;
+
+	sch->orb.pfch = sch->options.prefetch == 0;
+	sch->orb.spnd = sch->options.suspend;
+	sch->orb.ssic = sch->options.suspend && sch->options.inter;
+	sch->orb.lpm = (lpm != 0) ? (lpm & sch->opm) : sch->lpm;
+#ifdef CONFIG_ARCH_S390X
+	/*
+	 * for 64 bit we always support 64 bit IDAWs with 4k page size only
+	 */
+	sch->orb.c64 = 1;
+	sch->orb.i2k = 0;
+#endif
+	sch->orb.key = key >> 4;
+	/* issue "Start Subchannel" */
+	sch->orb.cpa = (__u32) __pa (cpa);
+	ccode = ssch (sch->irq, &sch->orb);
+
+	/* process condition code */
+	sprintf (dbf_txt, "ccode:%d", ccode);
+	CIO_TRACE_EVENT (4, dbf_txt);
+
+	switch (ccode) {
+	case 0:
+		/*
+		 * initialize device status information
+		 */
+		sch->schib.scsw.actl |= SCSW_ACTL_START_PEND;
+		return 0;
+	case 1:		/* status pending */
+	case 2:		/* busy */
+		return -EBUSY;
+	default:		/* device/path not operational */
+		return cio_start_handle_notoper(sch, lpm);
+	}
+}
+
+int
+cio_start (struct subchannel *sch, struct ccw1 *cpa, __u8 lpm)
+{
+	return cio_start_key(sch, cpa, lpm, default_storage_key);
+}
+
+/*
+ * resume suspended I/O operation
+ */
+int
+cio_resume (struct subchannel *sch)
+{
+	char dbf_txt[15];
+	int ccode;
+
+	CIO_TRACE_EVENT (4, "resIO");
+	CIO_TRACE_EVENT (4, sch->dev.bus_id);
+
+	ccode = rsch (sch->irq);
+
+	sprintf (dbf_txt, "ccode:%d", ccode);
+	CIO_TRACE_EVENT (4, dbf_txt);
+
+	switch (ccode) {
+	case 0:
+		sch->schib.scsw.actl |= SCSW_ACTL_RESUME_PEND;
+		return 0;
+	case 1:
+		return -EBUSY;
+	case 2:
+		return -EINVAL;
+	default:
+		/*
+		 * useless to wait for request completion
+		 *  as device is no longer operational !
+		 */
+		return -ENODEV;
+	}
+}
+
+/*
+ * halt I/O operation
+ */
+int
+cio_halt(struct subchannel *sch)
+{
+	char dbf_txt[15];
+	int ccode;
+
+	if (!sch)
+		return -ENODEV;
+
+	CIO_TRACE_EVENT (2, "haltIO");
+	CIO_TRACE_EVENT (2, sch->dev.bus_id);
+
+	/*
+	 * Issue "Halt subchannel" and process condition code
+	 */
+	ccode = hsch (sch->irq);
+
+	sprintf (dbf_txt, "ccode:%d", ccode);
+	CIO_TRACE_EVENT (2, dbf_txt);
+
+	switch (ccode) {
+	case 0:
+		sch->schib.scsw.actl |= SCSW_ACTL_HALT_PEND;
+		return 0;
+	case 1:		/* status pending */
+	case 2:		/* busy */
+		return -EBUSY;
+	default:		/* device not operational */
+		return -ENODEV;
+	}
+}
+
+/*
+ * Clear I/O operation
+ */
+int
+cio_clear(struct subchannel *sch)
+{
+	char dbf_txt[15];
+	int ccode;
+
+	if (!sch)
+		return -ENODEV;
+
+	CIO_TRACE_EVENT (2, "clearIO");
+	CIO_TRACE_EVENT (2, sch->dev.bus_id);
+
+	/*
+	 * Issue "Clear subchannel" and process condition code
+	 */
+	ccode = csch (sch->irq);
+
+	sprintf (dbf_txt, "ccode:%d", ccode);
+	CIO_TRACE_EVENT (2, dbf_txt);
+
+	switch (ccode) {
+	case 0:
+		sch->schib.scsw.actl |= SCSW_ACTL_CLEAR_PEND;
+		return 0;
+	default:		/* device not operational */
+		return -ENODEV;
+	}
+}
+
+/*
+ * Function: cio_cancel
+ * Issues a "Cancel Subchannel" on the specified subchannel
+ * Note: We don't need any fancy intparms and flags here
+ *	 since xsch is executed synchronously.
+ * Only for common I/O internal use as for now.
+ */
+int
+cio_cancel (struct subchannel *sch)
+{
+	char dbf_txt[15];
+	int ccode;
+
+	if (!sch)
+		return -ENODEV;
+
+	CIO_TRACE_EVENT (2, "cancelIO");
+	CIO_TRACE_EVENT (2, sch->dev.bus_id);
+
+	ccode = xsch (sch->irq);
+
+	sprintf (dbf_txt, "ccode:%d", ccode);
+	CIO_TRACE_EVENT (2, dbf_txt);
+
+	switch (ccode) {
+	case 0:		/* success */
+		/* Update information in scsw. */
+		stsch (sch->irq, &sch->schib);
+		return 0;
+	case 1:		/* status pending */
+		return -EBUSY;
+	case 2:		/* not applicable */
+		return -EINVAL;
+	default:	/* not oper */
+		return -ENODEV;
+	}
+}
+
+/*
+ * Function: cio_modify
+ * Issues a "Modify Subchannel" on the specified subchannel
+ */
+int
+cio_modify (struct subchannel *sch)
+{
+	int ccode, retry, ret;
+
+	ret = 0;
+	for (retry = 0; retry < 5; retry++) {
+		ccode = msch_err (sch->irq, &sch->schib);
+		if (ccode < 0)	/* -EIO if msch gets a program check. */
+			return ccode;
+		switch (ccode) {
+		case 0: /* successfull */
+			return 0;
+		case 1:	/* status pending */
+			return -EBUSY;
+		case 2:	/* busy */
+			udelay (100);	/* allow for recovery */
+			ret = -EBUSY;
+			break;
+		case 3:	/* not operational */
+			return -ENODEV;
+		}
+	}
+	return ret;
+}
+
+/*
+ * Enable subchannel.
+ */
+int
+cio_enable_subchannel (struct subchannel *sch, unsigned int isc)
+{
+	char dbf_txt[15];
+	int ccode;
+	int retry;
+	int ret;
+
+	CIO_TRACE_EVENT (2, "ensch");
+	CIO_TRACE_EVENT (2, sch->dev.bus_id);
+
+	ccode = stsch (sch->irq, &sch->schib);
+	if (ccode)
+		return -ENODEV;
+
+	for (retry = 5, ret = 0; retry > 0; retry--) {
+		sch->schib.pmcw.ena = 1;
+		sch->schib.pmcw.isc = isc;
+		sch->schib.pmcw.intparm = (__u32)(unsigned long)sch;
+		ret = cio_modify(sch);
+		if (ret == -ENODEV)
+			break;
+		if (ret == -EIO)
+			/*
+			 * Got a program check in cio_modify. Try without
+			 * the concurrent sense bit the next time.
+			 */
+			sch->schib.pmcw.csense = 0;
+		if (ret == 0) {
+			stsch (sch->irq, &sch->schib);
+			if (sch->schib.pmcw.ena)
+				break;
+		}
+		if (ret == -EBUSY) {
+			struct irb irb;
+			if (tsch(sch->irq, &irb) != 0)
+				break;
+		}
+	}
+	sprintf (dbf_txt, "ret:%d", ret);
+	CIO_TRACE_EVENT (2, dbf_txt);
+	return ret;
+}
+
+/*
+ * Disable subchannel.
+ */
+int
+cio_disable_subchannel (struct subchannel *sch)
+{
+	char dbf_txt[15];
+	int ccode;
+	int retry;
+	int ret;
+
+	CIO_TRACE_EVENT (2, "dissch");
+	CIO_TRACE_EVENT (2, sch->dev.bus_id);
+
+	ccode = stsch (sch->irq, &sch->schib);
+	if (ccode == 3)		/* Not operational. */
+		return -ENODEV;
+
+	if (sch->schib.scsw.actl != 0)
+		/*
+		 * the disable function must not be called while there are
+		 *  requests pending for completion !
+		 */
+		return -EBUSY;
+
+	for (retry = 5, ret = 0; retry > 0; retry--) {
+		sch->schib.pmcw.ena = 0;
+		ret = cio_modify(sch);
+		if (ret == -ENODEV)
+			break;
+		if (ret == -EBUSY)
+			/*
+			 * The subchannel is busy or status pending.
+			 * We'll disable when the next interrupt was delivered
+			 * via the state machine.
+			 */
+			break;
+		if (ret == 0) {
+			stsch (sch->irq, &sch->schib);
+			if (!sch->schib.pmcw.ena)
+				break;
+		}
+	}
+	sprintf (dbf_txt, "ret:%d", ret);
+	CIO_TRACE_EVENT (2, dbf_txt);
+	return ret;
+}
+
+/*
+ * cio_validate_subchannel()
+ *
+ * Find out subchannel type and initialize struct subchannel.
+ * Return codes:
+ *   SUBCHANNEL_TYPE_IO for a normal io subchannel
+ *   SUBCHANNEL_TYPE_CHSC for a chsc subchannel
+ *   SUBCHANNEL_TYPE_MESSAGE for a messaging subchannel
+ *   SUBCHANNEL_TYPE_ADM for a adm(?) subchannel
+ *   -ENXIO for non-defined subchannels
+ *   -ENODEV for subchannels with invalid device number or blacklisted devices
+ */
+int
+cio_validate_subchannel (struct subchannel *sch, unsigned int irq)
+{
+	char dbf_txt[15];
+	int ccode;
+
+	sprintf (dbf_txt, "valsch%x", irq);
+	CIO_TRACE_EVENT (4, dbf_txt);
+
+	/* Nuke all fields. */
+	memset(sch, 0, sizeof(struct subchannel));
+
+	spin_lock_init(&sch->lock);
+
+	/* Set a name for the subchannel */
+	snprintf (sch->dev.bus_id, BUS_ID_SIZE, "0.0.%04x", irq);
+
+	/*
+	 * The first subchannel that is not-operational (ccode==3)
+	 *  indicates that there aren't any more devices available.
+	 */
+	sch->irq = irq;
+	ccode = stsch (irq, &sch->schib);
+	if (ccode)
+		return -ENXIO;
+
+	/* Copy subchannel type from path management control word. */
+	sch->st = sch->schib.pmcw.st;
+
+	/*
+	 * ... just being curious we check for non I/O subchannels
+	 */
+	if (sch->st != 0) {
+		CIO_DEBUG(KERN_INFO, 0,
+			  "Subchannel %04X reports "
+			  "non-I/O subchannel type %04X\n",
+			  sch->irq, sch->st);
+		/* We stop here for non-io subchannels. */
+		return sch->st;
+	}
+
+	/* Initialization for io subchannels. */
+	if (!sch->schib.pmcw.dnv)
+		/* io subchannel but device number is invalid. */
+		return -ENODEV;
+
+	/* Devno is valid. */
+	if (is_blacklisted (sch->schib.pmcw.dev)) {
+		/*
+		 * This device must not be known to Linux. So we simply
+		 * say that there is no device and return ENODEV.
+		 */
+		CIO_MSG_EVENT(0, "Blacklisted device detected "
+			      "at devno %04X\n", sch->schib.pmcw.dev);
+		return -ENODEV;
+	}
+	sch->opm = 0xff;
+	chsc_validate_chpids(sch);
+	sch->lpm = sch->schib.pmcw.pim &
+		sch->schib.pmcw.pam &
+		sch->schib.pmcw.pom &
+		sch->opm;
+
+	CIO_DEBUG(KERN_INFO, 0,
+		  "Detected device %04X on subchannel %04X"
+		  " - PIM = %02X, PAM = %02X, POM = %02X\n",
+		  sch->schib.pmcw.dev, sch->irq, sch->schib.pmcw.pim,
+		  sch->schib.pmcw.pam, sch->schib.pmcw.pom);
+
+	/*
+	 * We now have to initially ...
+	 *  ... set "interruption subclass"
+	 *  ... enable "concurrent sense"
+	 *  ... enable "multipath mode" if more than one
+	 *	  CHPID is available. This is done regardless
+	 *	  whether multiple paths are available for us.
+	 */
+	sch->schib.pmcw.isc = 3;	/* could be smth. else */
+	sch->schib.pmcw.csense = 1;	/* concurrent sense */
+	sch->schib.pmcw.ena = 0;
+	if ((sch->lpm & (sch->lpm - 1)) != 0)
+		sch->schib.pmcw.mp = 1;	/* multipath mode */
+	return 0;
+}
+
+/*
+ * do_IRQ() handles all normal I/O device IRQ's (the special
+ *	    SMP cross-CPU interrupts have their own specific
+ *	    handlers).
+ *
+ */
+void
+do_IRQ (struct pt_regs *regs)
+{
+	struct tpi_info *tpi_info;
+	struct subchannel *sch;
+	struct irb *irb;
+
+	irq_enter ();
+	asm volatile ("mc 0,0");
+	if (S390_lowcore.int_clock >= S390_lowcore.jiffy_timer)
+		/**
+		 * Make sure that the i/o interrupt did not "overtake"
+		 * the last HZ timer interrupt.
+		 */
+		account_ticks(regs);
+	/*
+	 * Get interrupt information from lowcore
+	 */
+	tpi_info = (struct tpi_info *) __LC_SUBCHANNEL_ID;
+	irb = (struct irb *) __LC_IRB;
+	do {
+		kstat_cpu(smp_processor_id()).irqs[IO_INTERRUPT]++;
+		/*
+		 * Non I/O-subchannel thin interrupts are processed differently
+		 */
+		if (tpi_info->adapter_IO == 1 &&
+		    tpi_info->int_type == IO_INTERRUPT_TYPE) {
+			do_adapter_IO();
+			continue;
+		}
+		sch = (struct subchannel *)(unsigned long)tpi_info->intparm;
+		if (sch)
+			spin_lock(&sch->lock);
+		/* Store interrupt response block to lowcore. */
+		if (tsch (tpi_info->irq, irb) == 0 && sch) {
+			/* Keep subchannel information word up to date. */
+			memcpy (&sch->schib.scsw, &irb->scsw,
+				sizeof (irb->scsw));
+			/* Call interrupt handler if there is one. */
+			if (sch->driver && sch->driver->irq)
+				sch->driver->irq(&sch->dev);
+		}
+		if (sch)
+			spin_unlock(&sch->lock);
+		/*
+		 * Are more interrupts pending?
+		 * If so, the tpi instruction will update the lowcore
+		 * to hold the info for the next interrupt.
+		 * We don't do this for VM because a tpi drops the cpu
+		 * out of the sie which costs more cycles than it saves.
+		 */
+	} while (!MACHINE_IS_VM && tpi (NULL) != 0);
+	irq_exit ();
+}
+
+#ifdef CONFIG_CCW_CONSOLE
+static struct subchannel console_subchannel;
+static int console_subchannel_in_use;
+
+/*
+ * busy wait for the next interrupt on the console
+ */
+void
+wait_cons_dev (void)
+{
+	unsigned long cr6      __attribute__ ((aligned (8)));
+	unsigned long save_cr6 __attribute__ ((aligned (8)));
+
+	/* 
+	 * before entering the spinlock we may already have
+	 * processed the interrupt on a different CPU...
+	 */
+	if (!console_subchannel_in_use)
+		return;
+
+	/* disable all but isc 7 (console device) */
+	__ctl_store (save_cr6, 6, 6);
+	cr6 = 0x01000000;
+	__ctl_load (cr6, 6, 6);
+
+	do {
+		spin_unlock(&console_subchannel.lock);
+		if (!cio_tpi())
+			cpu_relax();
+		spin_lock(&console_subchannel.lock);
+	} while (console_subchannel.schib.scsw.actl != 0);
+	/*
+	 * restore previous isc value
+	 */
+	__ctl_load (save_cr6, 6, 6);
+}
+
+static int
+cio_console_irq(void)
+{
+	int irq;
+	
+	if (console_irq != -1) {
+		/* VM provided us with the irq number of the console. */
+		if (stsch(console_irq, &console_subchannel.schib) != 0 ||
+		    !console_subchannel.schib.pmcw.dnv)
+			return -1;
+		console_devno = console_subchannel.schib.pmcw.dev;
+	} else if (console_devno != -1) {
+		/* At least the console device number is known. */
+		for (irq = 0; irq < __MAX_SUBCHANNELS; irq++) {
+			if (stsch(irq, &console_subchannel.schib) != 0)
+				break;
+			if (console_subchannel.schib.pmcw.dnv &&
+			    console_subchannel.schib.pmcw.dev ==
+			    console_devno) {
+				console_irq = irq;
+				break;
+			}
+		}
+		if (console_irq == -1)
+			return -1;
+	} else {
+		/* unlike in 2.4, we cannot autoprobe here, since
+		 * the channel subsystem is not fully initialized.
+		 * With some luck, the HWC console can take over */
+		printk(KERN_WARNING "No ccw console found!\n");
+		return -1;
+	}
+	return console_irq;
+}
+
+struct subchannel *
+cio_probe_console(void)
+{
+	int irq, ret;
+
+	if (xchg(&console_subchannel_in_use, 1) != 0)
+		return ERR_PTR(-EBUSY);
+	irq = cio_console_irq();
+	if (irq == -1) {
+		console_subchannel_in_use = 0;
+		return ERR_PTR(-ENODEV);
+	}
+	memset(&console_subchannel, 0, sizeof(struct subchannel));
+	ret = cio_validate_subchannel(&console_subchannel, irq);
+	if (ret) {
+		console_subchannel_in_use = 0;
+		return ERR_PTR(-ENODEV);
+	}
+
+	/*
+	 * enable console I/O-interrupt subclass 7
+	 */
+	ctl_set_bit(6, 24);
+	console_subchannel.schib.pmcw.isc = 7;
+	console_subchannel.schib.pmcw.intparm =
+		(__u32)(unsigned long)&console_subchannel;
+	ret = cio_modify(&console_subchannel);
+	if (ret) {
+		console_subchannel_in_use = 0;
+		return ERR_PTR(ret);
+	}
+	return &console_subchannel;
+}
+
+void
+cio_release_console(void)
+{
+	console_subchannel.schib.pmcw.intparm = 0;
+	cio_modify(&console_subchannel);
+	ctl_clear_bit(6, 24);
+	console_subchannel_in_use = 0;
+}
+
+/* Bah... hack to catch console special sausages. */
+int
+cio_is_console(int irq)
+{
+	if (!console_subchannel_in_use)
+		return 0;
+	return (irq == console_subchannel.irq);
+}
+
+struct subchannel *
+cio_get_console_subchannel(void)
+{
+	if (!console_subchannel_in_use)
+		return 0;
+	return &console_subchannel;
+}
+
+#endif
+static inline int
+__disable_subchannel_easy(unsigned int schid, struct schib *schib)
+{
+	int retry, cc;
+
+	cc = 0;
+	for (retry=0;retry<3;retry++) {
+		schib->pmcw.ena = 0;
+		cc = msch(schid, schib);
+		if (cc)
+			return (cc==3?-ENODEV:-EBUSY);
+		stsch(schid, schib);
+		if (!schib->pmcw.ena)
+			return 0;
+	}
+	return -EBUSY; /* uhm... */
+}
+
+static inline int
+__clear_subchannel_easy(unsigned int schid)
+{
+	int retry;
+
+	if (csch(schid))
+		return -ENODEV;
+	for (retry=0;retry<20;retry++) {
+		struct tpi_info ti;
+
+		if (tpi(&ti)) {
+			tsch(schid, (struct irb *)__LC_IRB);
+			return 0;
+		}
+		udelay(100);
+	}
+	return -EBUSY;
+}
+
+extern void do_reipl(unsigned long devno);
+
+/* Clear all subchannels. */
+void
+clear_all_subchannels(void)
+{
+	unsigned int schid;
+
+	local_irq_disable();
+	for (schid=0;schid<=highest_subchannel;schid++) {
+		struct schib schib;
+		if (stsch(schid, &schib))
+			break; /* break out of the loop */
+		if (!schib.pmcw.ena)
+			continue;
+		switch(__disable_subchannel_easy(schid, &schib)) {
+		case 0:
+		case -ENODEV:
+			break;
+		default: /* -EBUSY */
+			if (__clear_subchannel_easy(schid))
+				break; /* give up... jump out of switch */
+			stsch(schid, &schib);
+			__disable_subchannel_easy(schid, &schib);
+		}
+	}
+}
+
+/* Make sure all subchannels are quiet before we re-ipl an lpar. */
+void
+reipl(unsigned long devno)
+{
+	clear_all_subchannels();
+	do_reipl(devno);
+}
diff --git a/drivers/s390/cio/cio.h b/drivers/s390/cio/cio.h
new file mode 100644
index 0000000..c50a9da
--- /dev/null
+++ b/drivers/s390/cio/cio.h
@@ -0,0 +1,143 @@
+#ifndef S390_CIO_H
+#define S390_CIO_H
+
+/*
+ * where we put the ssd info
+ */
+struct ssd_info {
+	__u8  valid:1;
+	__u8  type:7;		/* subchannel type */
+	__u8  chpid[8];		/* chpids */
+	__u16 fla[8];		/* full link addresses */
+} __attribute__ ((packed));
+
+/*
+ * path management control word
+ */
+struct pmcw {
+	__u32 intparm;		/* interruption parameter */
+	__u32 qf   : 1;		/* qdio facility */
+	__u32 res0 : 1;		/* reserved zeros */
+	__u32 isc  : 3;		/* interruption sublass */
+	__u32 res5 : 3;		/* reserved zeros */
+	__u32 ena  : 1;		/* enabled */
+	__u32 lm   : 2;		/* limit mode */
+	__u32 mme  : 2;		/* measurement-mode enable */
+	__u32 mp   : 1;		/* multipath mode */
+	__u32 tf   : 1;		/* timing facility */
+	__u32 dnv  : 1;		/* device number valid */
+	__u32 dev  : 16;	/* device number */
+	__u8  lpm;		/* logical path mask */
+	__u8  pnom;		/* path not operational mask */
+	__u8  lpum;		/* last path used mask */
+	__u8  pim;		/* path installed mask */
+	__u16 mbi;		/* measurement-block index */
+	__u8  pom;		/* path operational mask */
+	__u8  pam;		/* path available mask */
+	__u8  chpid[8];		/* CHPID 0-7 (if available) */
+	__u32 unused1 : 8;	/* reserved zeros */
+	__u32 st      : 3;	/* subchannel type */
+	__u32 unused2 : 18;	/* reserved zeros */
+	__u32 mbfc    : 1;      /* measurement block format control */
+	__u32 xmwme   : 1;      /* extended measurement word mode enable */
+	__u32 csense  : 1;	/* concurrent sense; can be enabled ...*/
+				/*  ... per MSCH, however, if facility */
+				/*  ... is not installed, this results */
+				/*  ... in an operand exception.       */
+} __attribute__ ((packed));
+
+/*
+ * subchannel information block
+ */
+struct schib {
+	struct pmcw pmcw;	 /* path management control word */
+	struct scsw scsw;	 /* subchannel status word */
+	__u64 mba;               /* measurement block address */
+	__u8 mda[4];		 /* model dependent area */
+} __attribute__ ((packed,aligned(4)));
+
+/*
+ * operation request block
+ */
+struct orb {
+	__u32 intparm;		/* interruption parameter */
+	__u32 key  : 4; 	/* flags, like key, suspend control, etc. */
+	__u32 spnd : 1; 	/* suspend control */
+	__u32 res1 : 1; 	/* reserved */
+	__u32 mod  : 1; 	/* modification control */
+	__u32 sync : 1; 	/* synchronize control */
+	__u32 fmt  : 1; 	/* format control */
+	__u32 pfch : 1; 	/* prefetch control */
+	__u32 isic : 1; 	/* initial-status-interruption control */
+	__u32 alcc : 1; 	/* address-limit-checking control */
+	__u32 ssic : 1; 	/* suppress-suspended-interr. control */
+	__u32 res2 : 1; 	/* reserved */
+	__u32 c64  : 1; 	/* IDAW/QDIO 64 bit control  */
+	__u32 i2k  : 1; 	/* IDAW 2/4kB block size control */
+	__u32 lpm  : 8; 	/* logical path mask */
+	__u32 ils  : 1; 	/* incorrect length */
+	__u32 zero : 6; 	/* reserved zeros */
+	__u32 orbx : 1; 	/* ORB extension control */
+	__u32 cpa;		/* channel program address */
+}  __attribute__ ((packed,aligned(4)));
+
+/* subchannel data structure used by I/O subroutines */
+struct subchannel {
+	unsigned int irq;	/* aka. subchannel number */
+	spinlock_t lock;	/* subchannel lock */
+
+	enum {
+		SUBCHANNEL_TYPE_IO = 0,
+		SUBCHANNEL_TYPE_CHSC = 1,
+		SUBCHANNEL_TYPE_MESSAGE = 2,
+		SUBCHANNEL_TYPE_ADM = 3,
+	} st;			/* subchannel type */
+
+	struct {
+		unsigned int suspend:1; /* allow suspend */
+		unsigned int prefetch:1;/* deny prefetch */
+		unsigned int inter:1;   /* suppress intermediate interrupts */
+	} __attribute__ ((packed)) options;
+
+	__u8 vpm;		/* verified path mask */
+	__u8 lpm;		/* logical path mask */
+	__u8 opm;               /* operational path mask */
+	struct schib schib;	/* subchannel information block */
+	struct orb orb;		/* operation request block */
+	struct ccw1 sense_ccw;	/* static ccw for sense command */
+	struct ssd_info ssd_info;	/* subchannel description */
+	struct device dev;	/* entry in device tree */
+	struct css_driver *driver;
+} __attribute__ ((aligned(8)));
+
+#define IO_INTERRUPT_TYPE	   0 /* I/O interrupt type */
+
+#define to_subchannel(n) container_of(n, struct subchannel, dev)
+
+extern int cio_validate_subchannel (struct subchannel *, unsigned int);
+extern int cio_enable_subchannel (struct subchannel *, unsigned int);
+extern int cio_disable_subchannel (struct subchannel *);
+extern int cio_cancel (struct subchannel *);
+extern int cio_clear (struct subchannel *);
+extern int cio_resume (struct subchannel *);
+extern int cio_halt (struct subchannel *);
+extern int cio_start (struct subchannel *, struct ccw1 *, __u8);
+extern int cio_start_key (struct subchannel *, struct ccw1 *, __u8, __u8);
+extern int cio_cancel (struct subchannel *);
+extern int cio_set_options (struct subchannel *, int);
+extern int cio_get_options (struct subchannel *);
+extern int cio_modify (struct subchannel *);
+/* Use with care. */
+#ifdef CONFIG_CCW_CONSOLE
+extern struct subchannel *cio_probe_console(void);
+extern void cio_release_console(void);
+extern int cio_is_console(int irq);
+extern struct subchannel *cio_get_console_subchannel(void);
+#else
+#define cio_is_console(irq) 0
+#define cio_get_console_subchannel() NULL
+#endif
+
+extern int cio_show_msg;
+
+#endif
diff --git a/drivers/s390/cio/cio_debug.h b/drivers/s390/cio/cio_debug.h
new file mode 100644
index 0000000..6af8b27
--- /dev/null
+++ b/drivers/s390/cio/cio_debug.h
@@ -0,0 +1,32 @@
+#ifndef CIO_DEBUG_H
+#define CIO_DEBUG_H
+
+#include <asm/debug.h>
+
+#define CIO_TRACE_EVENT(imp, txt) do { \
+		debug_text_event(cio_debug_trace_id, imp, txt); \
+	} while (0)
+
+#define CIO_MSG_EVENT(imp, args...) do { \
+		debug_sprintf_event(cio_debug_msg_id, imp , ##args); \
+	} while (0)
+
+#define CIO_CRW_EVENT(imp, args...) do { \
+		debug_sprintf_event(cio_debug_crw_id, imp , ##args); \
+	} while (0)
+
+#define CIO_HEX_EVENT(imp, args...) do { \
+                debug_event(cio_debug_trace_id, imp, ##args); \
+        } while (0)
+
+#define CIO_DEBUG(printk_level,event_level,msg...) ({ \
+	if (cio_show_msg) printk(printk_level msg); \
+	CIO_MSG_EVENT (event_level, msg); \
+})
+
+/* for use of debug feature */
+extern debug_info_t *cio_debug_msg_id;
+extern debug_info_t *cio_debug_trace_id;
+extern debug_info_t *cio_debug_crw_id;
+
+#endif
diff --git a/drivers/s390/cio/cmf.c b/drivers/s390/cio/cmf.c
new file mode 100644
index 0000000..49def26
--- /dev/null
+++ b/drivers/s390/cio/cmf.c
@@ -0,0 +1,1042 @@
+/*
+ * linux/drivers/s390/cio/cmf.c ($Revision: 1.16 $)
+ *
+ * Linux on zSeries Channel Measurement Facility support
+ *
+ * Copyright 2000,2003 IBM Corporation
+ *
+ * Author: Arnd Bergmann <arndb@de.ibm.com>
+ *
+ * original idea from Natarajan Krishnaswami <nkrishna@us.ibm.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/bootmem.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+
+#include <asm/ccwdev.h>
+#include <asm/cio.h>
+#include <asm/cmb.h>
+
+#include "cio.h"
+#include "css.h"
+#include "device.h"
+#include "ioasm.h"
+#include "chsc.h"
+
+/* parameter to enable cmf during boot, possible uses are:
+ *  "s390cmf" -- enable cmf and allocate 2 MB of ram so measuring can be
+ *               used on any subchannel
+ *  "s390cmf=<num>" -- enable cmf and allocate enough memory to measure
+ *                     <num> subchannel, where <num> is an integer
+ *                     between 1 and 65535, default is 1024
+ */
+#define ARGSTRING "s390cmf"
+
+/* indices for READCMB */
+enum cmb_index {
+ /* basic and exended format: */
+	cmb_ssch_rsch_count,
+	cmb_sample_count,
+	cmb_device_connect_time,
+	cmb_function_pending_time,
+	cmb_device_disconnect_time,
+	cmb_control_unit_queuing_time,
+	cmb_device_active_only_time,
+ /* extended format only: */
+	cmb_device_busy_time,
+	cmb_initial_command_response_time,
+};
+
+/**
+ * enum cmb_format - types of supported measurement block formats
+ *
+ * @CMF_BASIC:      traditional channel measurement blocks supported
+ * 		    by all machines that we run on
+ * @CMF_EXTENDED:   improved format that was introduced with the z990
+ * 		    machine
+ * @CMF_AUTODETECT: default: use extended format when running on a z990
+ *                  or later machine, otherwise fall back to basic format
+ **/
+enum cmb_format {
+	CMF_BASIC,
+	CMF_EXTENDED,
+	CMF_AUTODETECT = -1,
+};
+/**
+ * format - actual format for all measurement blocks
+ *
+ * The format module parameter can be set to a value of 0 (zero)
+ * or 1, indicating basic or extended format as described for
+ * enum cmb_format.
+ */
+static int format = CMF_AUTODETECT;
+module_param(format, bool, 0444);
+
+/**
+ * struct cmb_operations - functions to use depending on cmb_format
+ *
+ * all these functions operate on a struct cmf_device. There is only
+ * one instance of struct cmb_operations because all cmf_device
+ * objects are guaranteed to be of the same type.
+ *
+ * @alloc:	allocate memory for a channel measurement block,
+ *		either with the help of a special pool or with kmalloc
+ * @free:	free memory allocated with @alloc
+ * @set:	enable or disable measurement
+ * @readall:	read a measurement block in a common format
+ * @reset:	clear the data in the associated measurement block and
+ *		reset its time stamp
+ */
+struct cmb_operations {
+	int (*alloc)  (struct ccw_device*);
+	void(*free)   (struct ccw_device*);
+	int (*set)    (struct ccw_device*, u32);
+	u64 (*read)   (struct ccw_device*, int);
+	int (*readall)(struct ccw_device*, struct cmbdata *);
+	void (*reset) (struct ccw_device*);
+
+	struct attribute_group *attr_group;
+};
+static struct cmb_operations *cmbops;
+
+/* our user interface is designed in terms of nanoseconds,
+ * while the hardware measures total times in its own
+ * unit.*/
+static inline u64 time_to_nsec(u32 value)
+{
+	return ((u64)value) * 128000ull;
+}
+
+/*
+ * Users are usually interested in average times,
+ * not accumulated time.
+ * This also helps us with atomicity problems
+ * when reading sinlge values.
+ */
+static inline u64 time_to_avg_nsec(u32 value, u32 count)
+{
+	u64 ret;
+
+	/* no samples yet, avoid division by 0 */
+	if (count == 0)
+		return 0;
+
+	/* value comes in units of 128 µsec */
+	ret = time_to_nsec(value);
+	do_div(ret, count);
+
+	return ret;
+}
+
+/* activate or deactivate the channel monitor. When area is NULL,
+ * the monitor is deactivated. The channel monitor needs to
+ * be active in order to measure subchannels, which also need
+ * to be enabled. */
+static inline void
+cmf_activate(void *area, unsigned int onoff)
+{
+	register void * __gpr2 asm("2");
+	register long __gpr1 asm("1");
+
+	__gpr2 = area;
+	__gpr1 = onoff ? 2 : 0;
+	/* activate channel measurement */
+	asm("schm" : : "d" (__gpr2), "d" (__gpr1) );
+}
+
+static int
+set_schib(struct ccw_device *cdev, u32 mme, int mbfc, unsigned long address)
+{
+	int ret;
+	int retry;
+	struct subchannel *sch;
+	struct schib *schib;
+
+	sch = to_subchannel(cdev->dev.parent);
+	schib = &sch->schib;
+	/* msch can silently fail, so do it again if necessary */
+	for (retry = 0; retry < 3; retry++) {
+		/* prepare schib */
+		stsch(sch->irq, schib);
+		schib->pmcw.mme  = mme;
+		schib->pmcw.mbfc = mbfc;
+		/* address can be either a block address or a block index */
+		if (mbfc)
+			schib->mba = address;
+		else
+			schib->pmcw.mbi = address;
+
+		/* try to submit it */
+		switch(ret = msch_err(sch->irq, schib)) {
+			case 0:
+				break;
+			case 1:
+			case 2: /* in I/O or status pending */
+				ret = -EBUSY;
+				break;
+			case 3: /* subchannel is no longer valid */
+				ret = -ENODEV;
+				break;
+			default: /* msch caught an exception */
+				ret = -EINVAL;
+				break;
+		}
+		stsch(sch->irq, schib); /* restore the schib */
+
+		if (ret)
+			break;
+
+		/* check if it worked */
+		if (schib->pmcw.mme  == mme &&
+		    schib->pmcw.mbfc == mbfc &&
+		    (mbfc ? (schib->mba == address)
+			  : (schib->pmcw.mbi == address)))
+			return 0;
+
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+struct set_schib_struct {
+	u32 mme;
+	int mbfc;
+	unsigned long address;
+	wait_queue_head_t wait;
+	int ret;
+};
+
+static int set_schib_wait(struct ccw_device *cdev, u32 mme,
+				int mbfc, unsigned long address)
+{
+	struct set_schib_struct s = {
+		.mme = mme,
+		.mbfc = mbfc,
+		.address = address,
+		.wait = __WAIT_QUEUE_HEAD_INITIALIZER(s.wait),
+	};
+
+	spin_lock_irq(cdev->ccwlock);
+	s.ret = set_schib(cdev, mme, mbfc, address);
+	if (s.ret != -EBUSY) {
+		goto out_nowait;
+	}
+
+	if (cdev->private->state != DEV_STATE_ONLINE) {
+		s.ret = -EBUSY;
+		/* if the device is not online, don't even try again */
+		goto out_nowait;
+	}
+	cdev->private->state = DEV_STATE_CMFCHANGE;
+	cdev->private->cmb_wait = &s;
+	s.ret = 1;
+
+	spin_unlock_irq(cdev->ccwlock);
+	if (wait_event_interruptible(s.wait, s.ret != 1)) {
+		spin_lock_irq(cdev->ccwlock);
+		if (s.ret == 1) {
+			s.ret = -ERESTARTSYS;
+			cdev->private->cmb_wait = 0;
+			if (cdev->private->state == DEV_STATE_CMFCHANGE)
+				cdev->private->state = DEV_STATE_ONLINE;
+		}
+		spin_unlock_irq(cdev->ccwlock);
+	}
+	return s.ret;
+
+out_nowait:
+	spin_unlock_irq(cdev->ccwlock);
+	return s.ret;
+}
+
+void retry_set_schib(struct ccw_device *cdev)
+{
+	struct set_schib_struct *s;
+
+	s = cdev->private->cmb_wait;
+	cdev->private->cmb_wait = 0;
+	if (!s) {
+		WARN_ON(1);
+		return;
+	}
+	s->ret = set_schib(cdev, s->mme, s->mbfc, s->address);
+	wake_up(&s->wait);
+}
+
+/**
+ * struct cmb_area - container for global cmb data
+ *
+ * @mem:	pointer to CMBs (only in basic measurement mode)
+ * @list:	contains a linked list of all subchannels
+ * @lock:	protect concurrent access to @mem and @list
+ */
+struct cmb_area {
+	struct cmb *mem;
+	struct list_head list;
+	int num_channels;
+	spinlock_t lock;
+};
+
+static struct cmb_area cmb_area = {
+	.lock = SPIN_LOCK_UNLOCKED,
+	.list = LIST_HEAD_INIT(cmb_area.list),
+	.num_channels  = 1024,
+};
+
+
+/* ****** old style CMB handling ********/
+
+/** int maxchannels
+ *
+ * Basic channel measurement blocks are allocated in one contiguous
+ * block of memory, which can not be moved as long as any channel
+ * is active. Therefore, a maximum number of subchannels needs to
+ * be defined somewhere. This is a module parameter, defaulting to
+ * a resonable value of 1024, or 32 kb of memory.
+ * Current kernels don't allow kmalloc with more than 128kb, so the
+ * maximum is 4096
+ */
+
+module_param_named(maxchannels, cmb_area.num_channels, uint, 0444);
+
+/**
+ * struct cmb - basic channel measurement block
+ *
+ * cmb as used by the hardware the fields are described in z/Architecture
+ * Principles of Operation, chapter 17.
+ * The area to be a contiguous array and may not be reallocated or freed.
+ * Only one cmb area can be present in the system.
+ */
+struct cmb {
+	u16 ssch_rsch_count;
+	u16 sample_count;
+	u32 device_connect_time;
+	u32 function_pending_time;
+	u32 device_disconnect_time;
+	u32 control_unit_queuing_time;
+	u32 device_active_only_time;
+	u32 reserved[2];
+};
+
+/* insert a single device into the cmb_area list
+ * called with cmb_area.lock held from alloc_cmb
+ */
+static inline int
+alloc_cmb_single (struct ccw_device *cdev)
+{
+	struct cmb *cmb;
+	struct ccw_device_private *node;
+	int ret;
+
+	spin_lock_irq(cdev->ccwlock);
+	if (!list_empty(&cdev->private->cmb_list)) {
+		ret = -EBUSY;
+		goto out;
+	}
+
+	/* find first unused cmb in cmb_area.mem.
+	 * this is a little tricky: cmb_area.list
+	 * remains sorted by ->cmb pointers */
+	cmb = cmb_area.mem;
+	list_for_each_entry(node, &cmb_area.list, cmb_list) {
+		if ((struct cmb*)node->cmb > cmb)
+			break;
+		cmb++;
+	}
+	if (cmb - cmb_area.mem >= cmb_area.num_channels) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	/* insert new cmb */
+	list_add_tail(&cdev->private->cmb_list, &node->cmb_list);
+	cdev->private->cmb = cmb;
+	ret = 0;
+out:
+	spin_unlock_irq(cdev->ccwlock);
+	return ret;
+}
+
+static int
+alloc_cmb (struct ccw_device *cdev)
+{
+	int ret;
+	struct cmb *mem;
+	ssize_t size;
+
+	spin_lock(&cmb_area.lock);
+
+	if (!cmb_area.mem) {
+		/* there is no user yet, so we need a new area */
+		size = sizeof(struct cmb) * cmb_area.num_channels;
+		WARN_ON(!list_empty(&cmb_area.list));
+
+		spin_unlock(&cmb_area.lock);
+		mem = (void*)__get_free_pages(GFP_KERNEL | GFP_DMA,
+				 get_order(size));
+		spin_lock(&cmb_area.lock);
+
+		if (cmb_area.mem) {
+			/* ok, another thread was faster */
+			free_pages((unsigned long)mem, get_order(size));
+		} else if (!mem) {
+			/* no luck */
+			ret = -ENOMEM;
+			goto out;
+		} else {
+			/* everything ok */
+			memset(mem, 0, size);
+			cmb_area.mem = mem;
+			cmf_activate(cmb_area.mem, 1);
+		}
+	}
+
+	/* do the actual allocation */
+	ret = alloc_cmb_single(cdev);
+out:
+	spin_unlock(&cmb_area.lock);
+
+	return ret;
+}
+
+static void
+free_cmb(struct ccw_device *cdev)
+{
+	struct ccw_device_private *priv;
+
+	priv = cdev->private;
+
+	spin_lock(&cmb_area.lock);
+	spin_lock_irq(cdev->ccwlock);
+
+	if (list_empty(&priv->cmb_list)) {
+		/* already freed */
+		goto out;
+	}
+
+	priv->cmb = NULL;
+	list_del_init(&priv->cmb_list);
+
+	if (list_empty(&cmb_area.list)) {
+		ssize_t size;
+		size = sizeof(struct cmb) * cmb_area.num_channels;
+		cmf_activate(NULL, 0);
+		free_pages((unsigned long)cmb_area.mem, get_order(size));
+		cmb_area.mem = NULL;
+	}
+out:
+	spin_unlock_irq(cdev->ccwlock);
+	spin_unlock(&cmb_area.lock);
+}
+
+static int
+set_cmb(struct ccw_device *cdev, u32 mme)
+{
+	u16 offset;
+
+	if (!cdev->private->cmb)
+		return -EINVAL;
+
+	offset = mme ? (struct cmb *)cdev->private->cmb - cmb_area.mem : 0;
+
+	return set_schib_wait(cdev, mme, 0, offset);
+}
+
+static u64
+read_cmb (struct ccw_device *cdev, int index)
+{
+	/* yes, we have to put it on the stack
+	 * because the cmb must only be accessed
+	 * atomically, e.g. with mvc */
+	struct cmb cmb;
+	unsigned long flags;
+	u32 val;
+
+	spin_lock_irqsave(cdev->ccwlock, flags);
+	if (!cdev->private->cmb) {
+		spin_unlock_irqrestore(cdev->ccwlock, flags);
+		return 0;
+	}
+
+	cmb = *(struct cmb*)cdev->private->cmb;
+	spin_unlock_irqrestore(cdev->ccwlock, flags);
+
+	switch (index) {
+	case cmb_ssch_rsch_count:
+		return cmb.ssch_rsch_count;
+	case cmb_sample_count:
+		return cmb.sample_count;
+	case cmb_device_connect_time:
+		val = cmb.device_connect_time;
+		break;
+	case cmb_function_pending_time:
+		val = cmb.function_pending_time;
+		break;
+	case cmb_device_disconnect_time:
+		val = cmb.device_disconnect_time;
+		break;
+	case cmb_control_unit_queuing_time:
+		val = cmb.control_unit_queuing_time;
+		break;
+	case cmb_device_active_only_time:
+		val = cmb.device_active_only_time;
+		break;
+	default:
+		return 0;
+	}
+	return time_to_avg_nsec(val, cmb.sample_count);
+}
+
+static int
+readall_cmb (struct ccw_device *cdev, struct cmbdata *data)
+{
+	/* yes, we have to put it on the stack
+	 * because the cmb must only be accessed
+	 * atomically, e.g. with mvc */
+	struct cmb cmb;
+	unsigned long flags;
+	u64 time;
+
+	spin_lock_irqsave(cdev->ccwlock, flags);
+	if (!cdev->private->cmb) {
+		spin_unlock_irqrestore(cdev->ccwlock, flags);
+		return -ENODEV;
+	}
+
+	cmb = *(struct cmb*)cdev->private->cmb;
+	time = get_clock() - cdev->private->cmb_start_time;
+	spin_unlock_irqrestore(cdev->ccwlock, flags);
+
+	memset(data, 0, sizeof(struct cmbdata));
+
+	/* we only know values before device_busy_time */
+	data->size = offsetof(struct cmbdata, device_busy_time);
+
+	/* convert to nanoseconds */
+	data->elapsed_time = (time * 1000) >> 12;
+
+	/* copy data to new structure */
+	data->ssch_rsch_count = cmb.ssch_rsch_count;
+	data->sample_count = cmb.sample_count;
+
+	/* time fields are converted to nanoseconds while copying */
+	data->device_connect_time = time_to_nsec(cmb.device_connect_time);
+	data->function_pending_time = time_to_nsec(cmb.function_pending_time);
+	data->device_disconnect_time = time_to_nsec(cmb.device_disconnect_time);
+	data->control_unit_queuing_time
+		= time_to_nsec(cmb.control_unit_queuing_time);
+	data->device_active_only_time
+		= time_to_nsec(cmb.device_active_only_time);
+
+	return 0;
+}
+
+static void
+reset_cmb(struct ccw_device *cdev)
+{
+	struct cmb *cmb;
+	spin_lock_irq(cdev->ccwlock);
+	cmb = cdev->private->cmb;
+	if (cmb)
+		memset (cmb, 0, sizeof (*cmb));
+	cdev->private->cmb_start_time = get_clock();
+	spin_unlock_irq(cdev->ccwlock);
+}
+
+static struct attribute_group cmf_attr_group;
+
+static struct cmb_operations cmbops_basic = {
+	.alloc	= alloc_cmb,
+	.free	= free_cmb,
+	.set	= set_cmb,
+	.read	= read_cmb,
+	.readall    = readall_cmb,
+	.reset	    = reset_cmb,
+	.attr_group = &cmf_attr_group,
+};
+
+/* ******** extended cmb handling ********/
+
+/**
+ * struct cmbe - extended channel measurement block
+ *
+ * cmb as used by the hardware, may be in any 64 bit physical location,
+ * the fields are described in z/Architecture Principles of Operation,
+ * third edition, chapter 17.
+ */
+struct cmbe {
+	u32 ssch_rsch_count;
+	u32 sample_count;
+	u32 device_connect_time;
+	u32 function_pending_time;
+	u32 device_disconnect_time;
+	u32 control_unit_queuing_time;
+	u32 device_active_only_time;
+	u32 device_busy_time;
+	u32 initial_command_response_time;
+	u32 reserved[7];
+};
+
+/* kmalloc only guarantees 8 byte alignment, but we need cmbe
+ * pointers to be naturally aligned. Make sure to allocate
+ * enough space for two cmbes */
+static inline struct cmbe* cmbe_align(struct cmbe *c)
+{
+	unsigned long addr;
+	addr = ((unsigned long)c + sizeof (struct cmbe) - sizeof(long)) &
+				 ~(sizeof (struct cmbe) - sizeof(long));
+	return (struct cmbe*)addr;
+}
+
+static int
+alloc_cmbe (struct ccw_device *cdev)
+{
+	struct cmbe *cmbe;
+	cmbe = kmalloc (sizeof (*cmbe) * 2, GFP_KERNEL);
+	if (!cmbe)
+		return -ENOMEM;
+
+	spin_lock_irq(cdev->ccwlock);
+	if (cdev->private->cmb) {
+		kfree(cmbe);
+		spin_unlock_irq(cdev->ccwlock);
+		return -EBUSY;
+	}
+
+	cdev->private->cmb = cmbe;
+	spin_unlock_irq(cdev->ccwlock);
+
+	/* activate global measurement if this is the first channel */
+	spin_lock(&cmb_area.lock);
+	if (list_empty(&cmb_area.list))
+		cmf_activate(NULL, 1);
+	list_add_tail(&cdev->private->cmb_list, &cmb_area.list);
+	spin_unlock(&cmb_area.lock);
+
+	return 0;
+}
+
+static void
+free_cmbe (struct ccw_device *cdev)
+{
+	spin_lock_irq(cdev->ccwlock);
+	if (cdev->private->cmb)
+		kfree(cdev->private->cmb);
+	cdev->private->cmb = NULL;
+	spin_unlock_irq(cdev->ccwlock);
+
+	/* deactivate global measurement if this is the last channel */
+	spin_lock(&cmb_area.lock);
+	list_del_init(&cdev->private->cmb_list);
+	if (list_empty(&cmb_area.list))
+		cmf_activate(NULL, 0);
+	spin_unlock(&cmb_area.lock);
+}
+
+static int
+set_cmbe(struct ccw_device *cdev, u32 mme)
+{
+	unsigned long mba;
+
+	if (!cdev->private->cmb)
+		return -EINVAL;
+	mba = mme ? (unsigned long) cmbe_align(cdev->private->cmb) : 0;
+
+	return set_schib_wait(cdev, mme, 1, mba);
+}
+
+
+u64
+read_cmbe (struct ccw_device *cdev, int index)
+{
+	/* yes, we have to put it on the stack
+	 * because the cmb must only be accessed
+	 * atomically, e.g. with mvc */
+	struct cmbe cmb;
+	unsigned long flags;
+	u32 val;
+
+	spin_lock_irqsave(cdev->ccwlock, flags);
+	if (!cdev->private->cmb) {
+		spin_unlock_irqrestore(cdev->ccwlock, flags);
+		return 0;
+	}
+
+	cmb = *cmbe_align(cdev->private->cmb);
+	spin_unlock_irqrestore(cdev->ccwlock, flags);
+
+	switch (index) {
+	case cmb_ssch_rsch_count:
+		return cmb.ssch_rsch_count;
+	case cmb_sample_count:
+		return cmb.sample_count;
+	case cmb_device_connect_time:
+		val = cmb.device_connect_time;
+		break;
+	case cmb_function_pending_time:
+		val = cmb.function_pending_time;
+		break;
+	case cmb_device_disconnect_time:
+		val = cmb.device_disconnect_time;
+		break;
+	case cmb_control_unit_queuing_time:
+		val = cmb.control_unit_queuing_time;
+		break;
+	case cmb_device_active_only_time:
+		val = cmb.device_active_only_time;
+		break;
+	case cmb_device_busy_time:
+		val = cmb.device_busy_time;
+		break;
+	case cmb_initial_command_response_time:
+		val = cmb.initial_command_response_time;
+		break;
+	default:
+		return 0;
+	}
+	return time_to_avg_nsec(val, cmb.sample_count);
+}
+
+static int
+readall_cmbe (struct ccw_device *cdev, struct cmbdata *data)
+{
+	/* yes, we have to put it on the stack
+	 * because the cmb must only be accessed
+	 * atomically, e.g. with mvc */
+	struct cmbe cmb;
+	unsigned long flags;
+	u64 time;
+
+	spin_lock_irqsave(cdev->ccwlock, flags);
+	if (!cdev->private->cmb) {
+		spin_unlock_irqrestore(cdev->ccwlock, flags);
+		return -ENODEV;
+	}
+
+	cmb = *cmbe_align(cdev->private->cmb);
+	time = get_clock() - cdev->private->cmb_start_time;
+	spin_unlock_irqrestore(cdev->ccwlock, flags);
+
+	memset (data, 0, sizeof(struct cmbdata));
+
+	/* we only know values before device_busy_time */
+	data->size = offsetof(struct cmbdata, device_busy_time);
+
+	/* conver to nanoseconds */
+	data->elapsed_time = (time * 1000) >> 12;
+
+	/* copy data to new structure */
+	data->ssch_rsch_count = cmb.ssch_rsch_count;
+	data->sample_count = cmb.sample_count;
+
+	/* time fields are converted to nanoseconds while copying */
+	data->device_connect_time = time_to_nsec(cmb.device_connect_time);
+	data->function_pending_time = time_to_nsec(cmb.function_pending_time);
+	data->device_disconnect_time = time_to_nsec(cmb.device_disconnect_time);
+	data->control_unit_queuing_time
+		= time_to_nsec(cmb.control_unit_queuing_time);
+	data->device_active_only_time
+		= time_to_nsec(cmb.device_active_only_time);
+	data->device_busy_time = time_to_nsec(cmb.device_busy_time);
+	data->initial_command_response_time
+		= time_to_nsec(cmb.initial_command_response_time);
+
+	return 0;
+}
+
+static void
+reset_cmbe(struct ccw_device *cdev)
+{
+	struct cmbe *cmb;
+	spin_lock_irq(cdev->ccwlock);
+	cmb = cmbe_align(cdev->private->cmb);
+	if (cmb)
+		memset (cmb, 0, sizeof (*cmb));
+	cdev->private->cmb_start_time = get_clock();
+	spin_unlock_irq(cdev->ccwlock);
+}
+
+static struct attribute_group cmf_attr_group_ext;
+
+static struct cmb_operations cmbops_extended = {
+	.alloc	    = alloc_cmbe,
+	.free	    = free_cmbe,
+	.set	    = set_cmbe,
+	.read	    = read_cmbe,
+	.readall    = readall_cmbe,
+	.reset	    = reset_cmbe,
+	.attr_group = &cmf_attr_group_ext,
+};
+
+
+static ssize_t
+cmb_show_attr(struct device *dev, char *buf, enum cmb_index idx)
+{
+	return sprintf(buf, "%lld\n",
+		(unsigned long long) cmf_read(to_ccwdev(dev), idx));
+}
+
+static ssize_t
+cmb_show_avg_sample_interval(struct device *dev, char *buf)
+{
+	struct ccw_device *cdev;
+	long interval;
+	unsigned long count;
+
+	cdev = to_ccwdev(dev);
+	interval  = get_clock() - cdev->private->cmb_start_time;
+	count = cmf_read(cdev, cmb_sample_count);
+	if (count)
+		interval /= count;
+	else
+		interval = -1;
+	return sprintf(buf, "%ld\n", interval);
+}
+
+static ssize_t
+cmb_show_avg_utilization(struct device *dev, char *buf)
+{
+	struct cmbdata data;
+	u64 utilization;
+	unsigned long t, u;
+	int ret;
+
+	ret = cmf_readall(to_ccwdev(dev), &data);
+	if (ret)
+		return ret;
+
+	utilization = data.device_connect_time +
+		      data.function_pending_time +
+		      data.device_disconnect_time;
+
+	/* shift to avoid long long division */
+	while (-1ul < (data.elapsed_time | utilization)) {
+		utilization >>= 8;
+		data.elapsed_time >>= 8;
+	}
+
+	/* calculate value in 0.1 percent units */
+	t = (unsigned long) data.elapsed_time / 1000;
+	u = (unsigned long) utilization / t;
+
+	return sprintf(buf, "%02ld.%01ld%%\n", u/ 10, u - (u/ 10) * 10);
+}
+
+#define cmf_attr(name) \
+static ssize_t show_ ## name (struct device * dev, char * buf) \
+{ return cmb_show_attr((dev), buf, cmb_ ## name); } \
+static DEVICE_ATTR(name, 0444, show_ ## name, NULL);
+
+#define cmf_attr_avg(name) \
+static ssize_t show_avg_ ## name (struct device * dev, char * buf) \
+{ return cmb_show_attr((dev), buf, cmb_ ## name); } \
+static DEVICE_ATTR(avg_ ## name, 0444, show_avg_ ## name, NULL);
+
+cmf_attr(ssch_rsch_count);
+cmf_attr(sample_count);
+cmf_attr_avg(device_connect_time);
+cmf_attr_avg(function_pending_time);
+cmf_attr_avg(device_disconnect_time);
+cmf_attr_avg(control_unit_queuing_time);
+cmf_attr_avg(device_active_only_time);
+cmf_attr_avg(device_busy_time);
+cmf_attr_avg(initial_command_response_time);
+
+static DEVICE_ATTR(avg_sample_interval, 0444, cmb_show_avg_sample_interval, NULL);
+static DEVICE_ATTR(avg_utilization, 0444, cmb_show_avg_utilization, NULL);
+
+static struct attribute *cmf_attributes[] = {
+	&dev_attr_avg_sample_interval.attr,
+	&dev_attr_avg_utilization.attr,
+	&dev_attr_ssch_rsch_count.attr,
+	&dev_attr_sample_count.attr,
+	&dev_attr_avg_device_connect_time.attr,
+	&dev_attr_avg_function_pending_time.attr,
+	&dev_attr_avg_device_disconnect_time.attr,
+	&dev_attr_avg_control_unit_queuing_time.attr,
+	&dev_attr_avg_device_active_only_time.attr,
+	0,
+};
+
+static struct attribute_group cmf_attr_group = {
+	.name  = "cmf",
+	.attrs = cmf_attributes,
+};
+
+static struct attribute *cmf_attributes_ext[] = {
+	&dev_attr_avg_sample_interval.attr,
+	&dev_attr_avg_utilization.attr,
+	&dev_attr_ssch_rsch_count.attr,
+	&dev_attr_sample_count.attr,
+	&dev_attr_avg_device_connect_time.attr,
+	&dev_attr_avg_function_pending_time.attr,
+	&dev_attr_avg_device_disconnect_time.attr,
+	&dev_attr_avg_control_unit_queuing_time.attr,
+	&dev_attr_avg_device_active_only_time.attr,
+	&dev_attr_avg_device_busy_time.attr,
+	&dev_attr_avg_initial_command_response_time.attr,
+	0,
+};
+
+static struct attribute_group cmf_attr_group_ext = {
+	.name  = "cmf",
+	.attrs = cmf_attributes_ext,
+};
+
+static ssize_t cmb_enable_show(struct device *dev, char *buf)
+{
+	return sprintf(buf, "%d\n", to_ccwdev(dev)->private->cmb ? 1 : 0);
+}
+
+static ssize_t cmb_enable_store(struct device *dev, const char *buf, size_t c)
+{
+	struct ccw_device *cdev;
+	int ret;
+
+	cdev = to_ccwdev(dev);
+
+	switch (buf[0]) {
+	case '0':
+		ret = disable_cmf(cdev);
+		if (ret)
+			printk(KERN_INFO "disable_cmf failed (%d)\n", ret);
+		break;
+	case '1':
+		ret = enable_cmf(cdev);
+		if (ret && ret != -EBUSY)
+			printk(KERN_INFO "enable_cmf failed (%d)\n", ret);
+		break;
+	}
+
+	return c;
+}
+
+DEVICE_ATTR(cmb_enable, 0644, cmb_enable_show, cmb_enable_store);
+
+/* enable_cmf/disable_cmf: module interface for cmf (de)activation */
+int
+enable_cmf(struct ccw_device *cdev)
+{
+	int ret;
+
+	ret = cmbops->alloc(cdev);
+	cmbops->reset(cdev);
+	if (ret)
+		return ret;
+	ret = cmbops->set(cdev, 2);
+	if (ret) {
+		cmbops->free(cdev);
+		return ret;
+	}
+	ret = sysfs_create_group(&cdev->dev.kobj, cmbops->attr_group);
+	if (!ret)
+		return 0;
+	cmbops->set(cdev, 0);  //FIXME: this can fail
+	cmbops->free(cdev);
+	return ret;
+}
+
+int
+disable_cmf(struct ccw_device *cdev)
+{
+	int ret;
+
+	ret = cmbops->set(cdev, 0);
+	if (ret)
+		return ret;
+	cmbops->free(cdev);
+	sysfs_remove_group(&cdev->dev.kobj, cmbops->attr_group);
+	return ret;
+}
+
+u64
+cmf_read(struct ccw_device *cdev, int index)
+{
+	return cmbops->read(cdev, index);
+}
+
+int
+cmf_readall(struct ccw_device *cdev, struct cmbdata *data)
+{
+	return cmbops->readall(cdev, data);
+}
+
+static int __init
+init_cmf(void)
+{
+	char *format_string;
+	char *detect_string = "parameter";
+
+	/* We cannot really autoprobe this. If the user did not give a parameter,
+	   see if we are running on z990 or up, otherwise fall back to basic mode. */
+
+	if (format == CMF_AUTODETECT) {
+		if (!css_characteristics_avail ||
+		    !css_general_characteristics.ext_mb) {
+			format = CMF_BASIC;
+		} else {
+			format = CMF_EXTENDED;
+		}
+		detect_string = "autodetected";
+	} else {
+		detect_string = "parameter";
+	}
+
+	switch (format) {
+	case CMF_BASIC:
+		format_string = "basic";
+		cmbops = &cmbops_basic;
+		if (cmb_area.num_channels > 4096 || cmb_area.num_channels < 1) {
+			printk(KERN_ERR "Basic channel measurement facility"
+					" can only use 1 to 4096 devices\n"
+			       KERN_ERR "when the cmf driver is built"
+					" as a loadable module\n");
+			return 1;
+		}
+		break;
+	case CMF_EXTENDED:
+ 		format_string = "extended";
+		cmbops = &cmbops_extended;
+		break;
+	default:
+		printk(KERN_ERR "Invalid format %d for channel "
+			"measurement facility\n", format);
+		return 1;
+	}
+
+	printk(KERN_INFO "Channel measurement facility using %s format (%s)\n",
+		format_string, detect_string);
+	return 0;
+}
+
+module_init(init_cmf);
+
+
+MODULE_AUTHOR("Arnd Bergmann <arndb@de.ibm.com>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("channel measurement facility base driver\n"
+		   "Copyright 2003 IBM Corporation\n");
+
+EXPORT_SYMBOL_GPL(enable_cmf);
+EXPORT_SYMBOL_GPL(disable_cmf);
+EXPORT_SYMBOL_GPL(cmf_read);
+EXPORT_SYMBOL_GPL(cmf_readall);
diff --git a/drivers/s390/cio/css.c b/drivers/s390/cio/css.c
new file mode 100644
index 0000000..87bd70e
--- /dev/null
+++ b/drivers/s390/cio/css.c
@@ -0,0 +1,575 @@
+/*
+ *  drivers/s390/cio/css.c
+ *  driver for channel subsystem
+ *   $Revision: 1.85 $
+ *
+ *    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/init.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/list.h>
+
+#include "css.h"
+#include "cio.h"
+#include "cio_debug.h"
+#include "ioasm.h"
+#include "chsc.h"
+
+unsigned int highest_subchannel;
+int need_rescan = 0;
+int css_init_done = 0;
+
+struct pgid global_pgid;
+int css_characteristics_avail = 0;
+
+struct device css_bus_device = {
+	.bus_id = "css0",
+};
+
+static struct subchannel *
+css_alloc_subchannel(int irq)
+{
+	struct subchannel *sch;
+	int ret;
+
+	sch = kmalloc (sizeof (*sch), GFP_KERNEL | GFP_DMA);
+	if (sch == NULL)
+		return ERR_PTR(-ENOMEM);
+	ret = cio_validate_subchannel (sch, irq);
+	if (ret < 0) {
+		kfree(sch);
+		return ERR_PTR(ret);
+	}
+	if (irq > highest_subchannel)
+		highest_subchannel = irq;
+
+	if (sch->st != SUBCHANNEL_TYPE_IO) {
+		/* For now we ignore all non-io subchannels. */
+		kfree(sch);
+		return ERR_PTR(-EINVAL);
+	}
+
+	/* 
+	 * Set intparm to subchannel address.
+	 * This is fine even on 64bit since the subchannel is always located
+	 * under 2G.
+	 */
+	sch->schib.pmcw.intparm = (__u32)(unsigned long)sch;
+	ret = cio_modify(sch);
+	if (ret) {
+		kfree(sch);
+		return ERR_PTR(ret);
+	}
+	return sch;
+}
+
+static void
+css_free_subchannel(struct subchannel *sch)
+{
+	if (sch) {
+		/* Reset intparm to zeroes. */
+		sch->schib.pmcw.intparm = 0;
+		cio_modify(sch);
+		kfree(sch);
+	}
+	
+}
+
+static void
+css_subchannel_release(struct device *dev)
+{
+	struct subchannel *sch;
+
+	sch = to_subchannel(dev);
+	if (!cio_is_console(sch->irq))
+		kfree(sch);
+}
+
+extern int css_get_ssd_info(struct subchannel *sch);
+
+static int
+css_register_subchannel(struct subchannel *sch)
+{
+	int ret;
+
+	/* Initialize the subchannel structure */
+	sch->dev.parent = &css_bus_device;
+	sch->dev.bus = &css_bus_type;
+	sch->dev.release = &css_subchannel_release;
+	
+	/* make it known to the system */
+	ret = device_register(&sch->dev);
+	if (ret)
+		printk (KERN_WARNING "%s: could not register %s\n",
+			__func__, sch->dev.bus_id);
+	else
+		css_get_ssd_info(sch);
+	return ret;
+}
+
+int
+css_probe_device(int irq)
+{
+	int ret;
+	struct subchannel *sch;
+
+	sch = css_alloc_subchannel(irq);
+	if (IS_ERR(sch))
+		return PTR_ERR(sch);
+	ret = css_register_subchannel(sch);
+	if (ret)
+		css_free_subchannel(sch);
+	return ret;
+}
+
+struct subchannel *
+get_subchannel_by_schid(int irq)
+{
+	struct subchannel *sch;
+	struct list_head *entry;
+	struct device *dev;
+
+	if (!get_bus(&css_bus_type))
+		return NULL;
+	down_read(&css_bus_type.subsys.rwsem);
+	sch = NULL;
+	list_for_each(entry, &css_bus_type.devices.list) {
+		dev = get_device(container_of(entry,
+					      struct device, bus_list));
+		if (!dev)
+			continue;
+		sch = to_subchannel(dev);
+		if (sch->irq == irq)
+			break;
+		put_device(dev);
+		sch = NULL;
+	}
+	up_read(&css_bus_type.subsys.rwsem);
+	put_bus(&css_bus_type);
+
+	return sch;
+}
+
+static inline int
+css_get_subchannel_status(struct subchannel *sch, int schid)
+{
+	struct schib schib;
+	int cc;
+
+	cc = stsch(schid, &schib);
+	if (cc)
+		return CIO_GONE;
+	if (!schib.pmcw.dnv)
+		return CIO_GONE;
+	if (sch && sch->schib.pmcw.dnv &&
+	    (schib.pmcw.dev != sch->schib.pmcw.dev))
+		return CIO_REVALIDATE;
+	if (sch && !sch->lpm)
+		return CIO_NO_PATH;
+	return CIO_OPER;
+}
+	
+static int
+css_evaluate_subchannel(int irq, int slow)
+{
+	int event, ret, disc;
+	struct subchannel *sch;
+	unsigned long flags;
+
+	sch = get_subchannel_by_schid(irq);
+	disc = sch ? device_is_disconnected(sch) : 0;
+	if (disc && slow) {
+		if (sch)
+			put_device(&sch->dev);
+		return 0; /* Already processed. */
+	}
+	/*
+	 * We've got a machine check, so running I/O won't get an interrupt.
+	 * Kill any pending timers.
+	 */
+	if (sch)
+		device_kill_pending_timer(sch);
+	if (!disc && !slow) {
+		if (sch)
+			put_device(&sch->dev);
+		return -EAGAIN; /* Will be done on the slow path. */
+	}
+	event = css_get_subchannel_status(sch, irq);
+	CIO_MSG_EVENT(4, "Evaluating schid %04x, event %d, %s, %s path.\n",
+		      irq, event, sch?(disc?"disconnected":"normal"):"unknown",
+		      slow?"slow":"fast");
+	switch (event) {
+	case CIO_NO_PATH:
+	case CIO_GONE:
+		if (!sch) {
+			/* Never used this subchannel. Ignore. */
+			ret = 0;
+			break;
+		}
+		if (disc && (event == CIO_NO_PATH)) {
+			/*
+			 * Uargh, hack again. Because we don't get a machine
+			 * check on configure on, our path bookkeeping can
+			 * be out of date here (it's fine while we only do
+			 * logical varying or get chsc machine checks). We
+			 * need to force reprobing or we might miss devices
+			 * coming operational again. It won't do harm in real
+			 * no path situations.
+			 */
+			spin_lock_irqsave(&sch->lock, flags);
+			device_trigger_reprobe(sch);
+			spin_unlock_irqrestore(&sch->lock, flags);
+			ret = 0;
+			break;
+		}
+		if (sch->driver && sch->driver->notify &&
+		    sch->driver->notify(&sch->dev, event)) {
+			cio_disable_subchannel(sch);
+			device_set_disconnected(sch);
+			ret = 0;
+			break;
+		}
+		/*
+		 * Unregister subchannel.
+		 * The device will be killed automatically.
+		 */
+		cio_disable_subchannel(sch);
+		device_unregister(&sch->dev);
+		/* Reset intparm to zeroes. */
+		sch->schib.pmcw.intparm = 0;
+		cio_modify(sch);
+		put_device(&sch->dev);
+		ret = 0;
+		break;
+	case CIO_REVALIDATE:
+		/* 
+		 * Revalidation machine check. Sick.
+		 * We don't notify the driver since we have to throw the device
+		 * away in any case.
+		 */
+		if (!disc) {
+			device_unregister(&sch->dev);
+			/* Reset intparm to zeroes. */
+			sch->schib.pmcw.intparm = 0;
+			cio_modify(sch);
+			put_device(&sch->dev);
+			ret = css_probe_device(irq);
+		} else {
+			/*
+			 * We can't immediately deregister the disconnected
+			 * device since it might block.
+			 */
+			spin_lock_irqsave(&sch->lock, flags);
+			device_trigger_reprobe(sch);
+			spin_unlock_irqrestore(&sch->lock, flags);
+			ret = 0;
+		}
+		break;
+	case CIO_OPER:
+		if (disc) {
+			spin_lock_irqsave(&sch->lock, flags);
+			/* Get device operational again. */
+			device_trigger_reprobe(sch);
+			spin_unlock_irqrestore(&sch->lock, flags);
+		}
+		ret = sch ? 0 : css_probe_device(irq);
+		break;
+	default:
+		BUG();
+		ret = 0;
+	}
+	return ret;
+}
+
+static void
+css_rescan_devices(void)
+{
+	int irq, ret;
+
+	for (irq = 0; irq < __MAX_SUBCHANNELS; irq++) {
+		ret = css_evaluate_subchannel(irq, 1);
+		/* No more memory. It doesn't make sense to continue. No
+		 * panic because this can happen in midflight and just
+		 * because we can't use a new device is no reason to crash
+		 * the system. */
+		if (ret == -ENOMEM)
+			break;
+		/* -ENXIO indicates that there are no more subchannels. */
+		if (ret == -ENXIO)
+			break;
+	}
+}
+
+struct slow_subchannel {
+	struct list_head slow_list;
+	unsigned long schid;
+};
+
+static LIST_HEAD(slow_subchannels_head);
+static DEFINE_SPINLOCK(slow_subchannel_lock);
+
+static void
+css_trigger_slow_path(void)
+{
+	CIO_TRACE_EVENT(4, "slowpath");
+
+	if (need_rescan) {
+		need_rescan = 0;
+		css_rescan_devices();
+		return;
+	}
+
+	spin_lock_irq(&slow_subchannel_lock);
+	while (!list_empty(&slow_subchannels_head)) {
+		struct slow_subchannel *slow_sch =
+			list_entry(slow_subchannels_head.next,
+				   struct slow_subchannel, slow_list);
+
+		list_del_init(slow_subchannels_head.next);
+		spin_unlock_irq(&slow_subchannel_lock);
+		css_evaluate_subchannel(slow_sch->schid, 1);
+		spin_lock_irq(&slow_subchannel_lock);
+		kfree(slow_sch);
+	}
+	spin_unlock_irq(&slow_subchannel_lock);
+}
+
+typedef void (*workfunc)(void *);
+DECLARE_WORK(slow_path_work, (workfunc)css_trigger_slow_path, NULL);
+struct workqueue_struct *slow_path_wq;
+
+/*
+ * Rescan for new devices. FIXME: This is slow.
+ * This function is called when we have lost CRWs due to overflows and we have
+ * to do subchannel housekeeping.
+ */
+void
+css_reiterate_subchannels(void)
+{
+	css_clear_subchannel_slow_list();
+	need_rescan = 1;
+}
+
+/*
+ * Called from the machine check handler for subchannel report words.
+ */
+int
+css_process_crw(int irq)
+{
+	int ret;
+
+	CIO_CRW_EVENT(2, "source is subchannel %04X\n", irq);
+
+	if (need_rescan)
+		/* We need to iterate all subchannels anyway. */
+		return -EAGAIN;
+	/* 
+	 * Since we are always presented with IPI in the CRW, we have to
+	 * use stsch() to find out if the subchannel in question has come
+	 * or gone.
+	 */
+	ret = css_evaluate_subchannel(irq, 0);
+	if (ret == -EAGAIN) {
+		if (css_enqueue_subchannel_slow(irq)) {
+			css_clear_subchannel_slow_list();
+			need_rescan = 1;
+		}
+	}
+	return ret;
+}
+
+static void __init
+css_generate_pgid(void)
+{
+	/* Let's build our path group ID here. */
+	if (css_characteristics_avail && css_general_characteristics.mcss)
+		global_pgid.cpu_addr = 0x8000;
+	else {
+#ifdef CONFIG_SMP
+		global_pgid.cpu_addr = hard_smp_processor_id();
+#else
+		global_pgid.cpu_addr = 0;
+#endif
+	}
+	global_pgid.cpu_id = ((cpuid_t *) __LC_CPUID)->ident;
+	global_pgid.cpu_model = ((cpuid_t *) __LC_CPUID)->machine;
+	global_pgid.tod_high = (__u32) (get_clock() >> 32);
+}
+
+/*
+ * Now that the driver core is running, we can setup our channel subsystem.
+ * The struct subchannel's are created during probing (except for the
+ * static console subchannel).
+ */
+static int __init
+init_channel_subsystem (void)
+{
+	int ret, irq;
+
+	if (chsc_determine_css_characteristics() == 0)
+		css_characteristics_avail = 1;
+
+	css_generate_pgid();
+
+	if ((ret = bus_register(&css_bus_type)))
+		goto out;
+	if ((ret = device_register (&css_bus_device)))
+		goto out_bus;
+
+	css_init_done = 1;
+
+	ctl_set_bit(6, 28);
+
+	for (irq = 0; irq < __MAX_SUBCHANNELS; irq++) {
+		struct subchannel *sch;
+
+		if (cio_is_console(irq))
+			sch = cio_get_console_subchannel();
+		else {
+			sch = css_alloc_subchannel(irq);
+			if (IS_ERR(sch))
+				ret = PTR_ERR(sch);
+			else
+				ret = 0;
+			if (ret == -ENOMEM)
+				panic("Out of memory in "
+				      "init_channel_subsystem\n");
+			/* -ENXIO: no more subchannels. */
+			if (ret == -ENXIO)
+				break;
+			if (ret)
+				continue;
+		}
+		/*
+		 * We register ALL valid subchannels in ioinfo, even those
+		 * that have been present before init_channel_subsystem.
+		 * These subchannels can't have been registered yet (kmalloc
+		 * not working) so we do it now. This is true e.g. for the
+		 * console subchannel.
+		 */
+		css_register_subchannel(sch);
+	}
+	return 0;
+
+out_bus:
+	bus_unregister(&css_bus_type);
+out:
+	return ret;
+}
+
+/*
+ * find a driver for a subchannel. They identify by the subchannel
+ * type with the exception that the console subchannel driver has its own
+ * subchannel type although the device is an i/o subchannel
+ */
+static int
+css_bus_match (struct device *dev, struct device_driver *drv)
+{
+	struct subchannel *sch = container_of (dev, struct subchannel, dev);
+	struct css_driver *driver = container_of (drv, struct css_driver, drv);
+
+	if (sch->st == driver->subchannel_type)
+		return 1;
+
+	return 0;
+}
+
+struct bus_type css_bus_type = {
+	.name  = "css",
+	.match = &css_bus_match,
+};
+
+subsys_initcall(init_channel_subsystem);
+
+/*
+ * Register root devices for some drivers. The release function must not be
+ * in the device drivers, so we do it here.
+ */
+static void
+s390_root_dev_release(struct device *dev)
+{
+	kfree(dev);
+}
+
+struct device *
+s390_root_dev_register(const char *name)
+{
+	struct device *dev;
+	int ret;
+
+	if (!strlen(name))
+		return ERR_PTR(-EINVAL);
+	dev = kmalloc(sizeof(struct device), GFP_KERNEL);
+	if (!dev)
+		return ERR_PTR(-ENOMEM);
+	memset(dev, 0, sizeof(struct device));
+	strncpy(dev->bus_id, name, min(strlen(name), (size_t)BUS_ID_SIZE));
+	dev->release = s390_root_dev_release;
+	ret = device_register(dev);
+	if (ret) {
+		kfree(dev);
+		return ERR_PTR(ret);
+	}
+	return dev;
+}
+
+void
+s390_root_dev_unregister(struct device *dev)
+{
+	if (dev)
+		device_unregister(dev);
+}
+
+int
+css_enqueue_subchannel_slow(unsigned long schid)
+{
+	struct slow_subchannel *new_slow_sch;
+	unsigned long flags;
+
+	new_slow_sch = kmalloc(sizeof(struct slow_subchannel), GFP_ATOMIC);
+	if (!new_slow_sch)
+		return -ENOMEM;
+	memset(new_slow_sch, 0, sizeof(struct slow_subchannel));
+	new_slow_sch->schid = schid;
+	spin_lock_irqsave(&slow_subchannel_lock, flags);
+	list_add_tail(&new_slow_sch->slow_list, &slow_subchannels_head);
+	spin_unlock_irqrestore(&slow_subchannel_lock, flags);
+	return 0;
+}
+
+void
+css_clear_subchannel_slow_list(void)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&slow_subchannel_lock, flags);
+	while (!list_empty(&slow_subchannels_head)) {
+		struct slow_subchannel *slow_sch =
+			list_entry(slow_subchannels_head.next,
+				   struct slow_subchannel, slow_list);
+
+		list_del_init(slow_subchannels_head.next);
+		kfree(slow_sch);
+	}
+	spin_unlock_irqrestore(&slow_subchannel_lock, flags);
+}
+
+
+
+int
+css_slow_subchannels_exist(void)
+{
+	return (!list_empty(&slow_subchannels_head));
+}
+
+MODULE_LICENSE("GPL");
+EXPORT_SYMBOL(css_bus_type);
+EXPORT_SYMBOL(s390_root_dev_register);
+EXPORT_SYMBOL(s390_root_dev_unregister);
+EXPORT_SYMBOL_GPL(css_characteristics_avail);
diff --git a/drivers/s390/cio/css.h b/drivers/s390/cio/css.h
new file mode 100644
index 0000000..2004a6c
--- /dev/null
+++ b/drivers/s390/cio/css.h
@@ -0,0 +1,155 @@
+#ifndef _CSS_H
+#define _CSS_H
+
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+
+#include <asm/cio.h>
+
+/*
+ * path grouping stuff
+ */
+#define SPID_FUNC_SINGLE_PATH	   0x00
+#define SPID_FUNC_MULTI_PATH	   0x80
+#define SPID_FUNC_ESTABLISH	   0x00
+#define SPID_FUNC_RESIGN	   0x40
+#define SPID_FUNC_DISBAND	   0x20
+
+#define SNID_STATE1_RESET	   0
+#define SNID_STATE1_UNGROUPED	   2
+#define SNID_STATE1_GROUPED	   3
+
+#define SNID_STATE2_NOT_RESVD	   0
+#define SNID_STATE2_RESVD_ELSE	   2
+#define SNID_STATE2_RESVD_SELF	   3
+
+#define SNID_STATE3_MULTI_PATH	   1
+#define SNID_STATE3_SINGLE_PATH	   0
+
+struct path_state {
+	__u8  state1 : 2;	/* path state value 1 */
+	__u8  state2 : 2;	/* path state value 2 */
+	__u8  state3 : 1;	/* path state value 3 */
+	__u8  resvd  : 3;	/* reserved */
+} __attribute__ ((packed));
+
+struct pgid {
+	union {
+		__u8 fc;   	/* SPID function code */
+		struct path_state ps;	/* SNID path state */
+	} inf;
+	__u32 cpu_addr	: 16;	/* CPU address */
+	__u32 cpu_id	: 24;	/* CPU identification */
+	__u32 cpu_model : 16;	/* CPU model */
+	__u32 tod_high;		/* high word TOD clock */
+} __attribute__ ((packed));
+
+extern struct pgid global_pgid;
+
+#define MAX_CIWS 8
+
+/*
+ * sense-id response buffer layout
+ */
+struct senseid {
+	/* common part */
+	__u8  reserved;     	/* always 0x'FF' */
+	__u16 cu_type;	     	/* control unit type */
+	__u8  cu_model;     	/* control unit model */
+	__u16 dev_type;     	/* device type */
+	__u8  dev_model;    	/* device model */
+	__u8  unused;	     	/* padding byte */
+	/* extended part */
+	struct ciw ciw[MAX_CIWS];	/* variable # of CIWs */
+}  __attribute__ ((packed,aligned(4)));
+
+struct ccw_device_private {
+	int state;		/* device state */
+	atomic_t onoff;
+	unsigned long registered;
+	__u16 devno;		/* device number */
+	__u16 irq;		/* subchannel number */
+	__u8 imask;		/* lpm mask for SNID/SID/SPGID */
+	int iretry;		/* retry counter SNID/SID/SPGID */
+	struct {
+		unsigned int fast:1;	/* post with "channel end" */
+		unsigned int repall:1;	/* report every interrupt status */
+		unsigned int pgroup:1;  /* do path grouping */
+		unsigned int force:1;   /* allow forced online */
+	} __attribute__ ((packed)) options;
+	struct {
+		unsigned int pgid_single:1; /* use single path for Set PGID */
+		unsigned int esid:1;        /* Ext. SenseID supported by HW */
+		unsigned int dosense:1;	    /* delayed SENSE required */
+		unsigned int doverify:1;    /* delayed path verification */
+		unsigned int donotify:1;    /* call notify function */
+		unsigned int recog_done:1;  /* dev. recog. complete */
+		unsigned int fake_irb:1;    /* deliver faked irb */
+	} __attribute__((packed)) flags;
+	unsigned long intparm;	/* user interruption parameter */
+	struct qdio_irq *qdio_data;
+	struct irb irb;		/* device status */
+	struct senseid senseid;	/* SenseID info */
+	struct pgid pgid;	/* path group ID */
+	struct ccw1 iccws[2];	/* ccws for SNID/SID/SPGID commands */
+	struct work_struct kick_work;
+	wait_queue_head_t wait_q;
+	struct timer_list timer;
+	void *cmb;			/* measurement information */
+	struct list_head cmb_list;	/* list of measured devices */
+	u64 cmb_start_time;		/* clock value of cmb reset */
+	void *cmb_wait;			/* deferred cmb enable/disable */
+};
+
+/*
+ * A css driver handles all subchannels of one type.
+ * Currently, we only care about I/O subchannels (type 0), these
+ * have a ccw_device connected to them.
+ */
+struct css_driver {
+	unsigned int subchannel_type;
+	struct device_driver drv;
+	void (*irq)(struct device *);
+	int (*notify)(struct device *, int);
+	void (*verify)(struct device *);
+	void (*termination)(struct device *);
+};
+
+/*
+ * all css_drivers have the css_bus_type
+ */
+extern struct bus_type css_bus_type;
+extern struct css_driver io_subchannel_driver;
+
+int css_probe_device(int irq);
+extern struct subchannel * get_subchannel_by_schid(int irq);
+extern unsigned int highest_subchannel;
+extern int css_init_done;
+
+#define __MAX_SUBCHANNELS 65536
+
+extern struct bus_type css_bus_type;
+extern struct device css_bus_device;
+
+/* Some helper functions for disconnected state. */
+int device_is_disconnected(struct subchannel *);
+void device_set_disconnected(struct subchannel *);
+void device_trigger_reprobe(struct subchannel *);
+
+/* Helper functions for vary on/off. */
+int device_is_online(struct subchannel *);
+void device_set_waiting(struct subchannel *);
+
+/* Machine check helper function. */
+void device_kill_pending_timer(struct subchannel *);
+
+/* Helper functions to build lists for the slow path. */
+int css_enqueue_subchannel_slow(unsigned long schid);
+void css_walk_subchannel_slow_list(void (*fn)(unsigned long));
+void css_clear_subchannel_slow_list(void);
+int css_slow_subchannels_exist(void);
+extern int need_rescan;
+
+extern struct workqueue_struct *slow_path_wq;
+extern struct work_struct slow_path_work;
+#endif
diff --git a/drivers/s390/cio/device.c b/drivers/s390/cio/device.c
new file mode 100644
index 0000000..df03255
--- /dev/null
+++ b/drivers/s390/cio/device.c
@@ -0,0 +1,1135 @@
+/*
+ *  drivers/s390/cio/device.c
+ *  bus driver for ccw devices
+ *   $Revision: 1.131 $
+ *
+ *    Copyright (C) 2002 IBM Deutschland Entwicklung GmbH,
+ *			 IBM Corporation
+ *    Author(s): Arnd Bergmann (arndb@de.ibm.com)
+ *		 Cornelia Huck (cohuck@de.ibm.com)
+ *		 Martin Schwidefsky (schwidefsky@de.ibm.com)
+ */
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/list.h>
+#include <linux/device.h>
+#include <linux/workqueue.h>
+
+#include <asm/ccwdev.h>
+#include <asm/cio.h>
+
+#include "cio.h"
+#include "css.h"
+#include "device.h"
+#include "ioasm.h"
+
+/******************* bus type handling ***********************/
+
+/* The Linux driver model distinguishes between a bus type and
+ * the bus itself. Of course we only have one channel
+ * subsystem driver and one channel system per machine, but
+ * we still use the abstraction. T.R. says it's a good idea. */
+static int
+ccw_bus_match (struct device * dev, struct device_driver * drv)
+{
+	struct ccw_device *cdev = to_ccwdev(dev);
+	struct ccw_driver *cdrv = to_ccwdrv(drv);
+	const struct ccw_device_id *ids = cdrv->ids, *found;
+
+	if (!ids)
+		return 0;
+
+	found = ccw_device_id_match(ids, &cdev->id);
+	if (!found)
+		return 0;
+
+	cdev->id.driver_info = found->driver_info;
+
+	return 1;
+}
+
+/*
+ * Hotplugging interface for ccw devices.
+ * Heavily modeled on pci and usb hotplug.
+ */
+static int
+ccw_hotplug (struct device *dev, char **envp, int num_envp,
+	     char *buffer, int buffer_size)
+{
+	struct ccw_device *cdev = to_ccwdev(dev);
+	int i = 0;
+	int length = 0;
+
+	if (!cdev)
+		return -ENODEV;
+
+	/* what we want to pass to /sbin/hotplug */
+
+	envp[i++] = buffer;
+	length += scnprintf(buffer, buffer_size - length, "CU_TYPE=%04X",
+			   cdev->id.cu_type);
+	if ((buffer_size - length <= 0) || (i >= num_envp))
+		return -ENOMEM;
+	++length;
+	buffer += length;
+
+	envp[i++] = buffer;
+	length += scnprintf(buffer, buffer_size - length, "CU_MODEL=%02X",
+			   cdev->id.cu_model);
+	if ((buffer_size - length <= 0) || (i >= num_envp))
+		return -ENOMEM;
+	++length;
+	buffer += length;
+
+	/* The next two can be zero, that's ok for us */
+	envp[i++] = buffer;
+	length += scnprintf(buffer, buffer_size - length, "DEV_TYPE=%04X",
+			   cdev->id.dev_type);
+	if ((buffer_size - length <= 0) || (i >= num_envp))
+		return -ENOMEM;
+	++length;
+	buffer += length;
+
+	envp[i++] = buffer;
+	length += scnprintf(buffer, buffer_size - length, "DEV_MODEL=%02X",
+			   cdev->id.dev_model);
+	if ((buffer_size - length <= 0) || (i >= num_envp))
+		return -ENOMEM;
+
+	envp[i] = 0;
+
+	return 0;
+}
+
+struct bus_type ccw_bus_type = {
+	.name  = "ccw",
+	.match = &ccw_bus_match,
+	.hotplug = &ccw_hotplug,
+};
+
+static int io_subchannel_probe (struct device *);
+static int io_subchannel_remove (struct device *);
+void io_subchannel_irq (struct device *);
+static int io_subchannel_notify(struct device *, int);
+static void io_subchannel_verify(struct device *);
+static void io_subchannel_ioterm(struct device *);
+static void io_subchannel_shutdown(struct device *);
+
+struct css_driver io_subchannel_driver = {
+	.subchannel_type = SUBCHANNEL_TYPE_IO,
+	.drv = {
+		.name = "io_subchannel",
+		.bus  = &css_bus_type,
+		.probe = &io_subchannel_probe,
+		.remove = &io_subchannel_remove,
+		.shutdown = &io_subchannel_shutdown,
+	},
+	.irq = io_subchannel_irq,
+	.notify = io_subchannel_notify,
+	.verify = io_subchannel_verify,
+	.termination = io_subchannel_ioterm,
+};
+
+struct workqueue_struct *ccw_device_work;
+struct workqueue_struct *ccw_device_notify_work;
+static wait_queue_head_t ccw_device_init_wq;
+static atomic_t ccw_device_init_count;
+
+static int __init
+init_ccw_bus_type (void)
+{
+	int ret;
+
+	init_waitqueue_head(&ccw_device_init_wq);
+	atomic_set(&ccw_device_init_count, 0);
+
+	ccw_device_work = create_singlethread_workqueue("cio");
+	if (!ccw_device_work)
+		return -ENOMEM; /* FIXME: better errno ? */
+	ccw_device_notify_work = create_singlethread_workqueue("cio_notify");
+	if (!ccw_device_notify_work) {
+		ret = -ENOMEM; /* FIXME: better errno ? */
+		goto out_err;
+	}
+	slow_path_wq = create_singlethread_workqueue("kslowcrw");
+	if (!slow_path_wq) {
+		ret = -ENOMEM; /* FIXME: better errno ? */
+		goto out_err;
+	}
+	if ((ret = bus_register (&ccw_bus_type)))
+		goto out_err;
+
+	if ((ret = driver_register(&io_subchannel_driver.drv)))
+		goto out_err;
+
+	wait_event(ccw_device_init_wq,
+		   atomic_read(&ccw_device_init_count) == 0);
+	flush_workqueue(ccw_device_work);
+	return 0;
+out_err:
+	if (ccw_device_work)
+		destroy_workqueue(ccw_device_work);
+	if (ccw_device_notify_work)
+		destroy_workqueue(ccw_device_notify_work);
+	if (slow_path_wq)
+		destroy_workqueue(slow_path_wq);
+	return ret;
+}
+
+static void __exit
+cleanup_ccw_bus_type (void)
+{
+	driver_unregister(&io_subchannel_driver.drv);
+	bus_unregister(&ccw_bus_type);
+	destroy_workqueue(ccw_device_notify_work);
+	destroy_workqueue(ccw_device_work);
+}
+
+subsys_initcall(init_ccw_bus_type);
+module_exit(cleanup_ccw_bus_type);
+
+/************************ device handling **************************/
+
+/*
+ * A ccw_device has some interfaces in sysfs in addition to the
+ * standard ones.
+ * The following entries are designed to export the information which
+ * resided in 2.4 in /proc/subchannels. Subchannel and device number
+ * are obvious, so they don't have an entry :)
+ * TODO: Split chpids and pimpampom up? Where is "in use" in the tree?
+ */
+static ssize_t
+chpids_show (struct device * dev, char * buf)
+{
+	struct subchannel *sch = to_subchannel(dev);
+	struct ssd_info *ssd = &sch->ssd_info;
+	ssize_t ret = 0;
+	int chp;
+
+	for (chp = 0; chp < 8; chp++)
+		ret += sprintf (buf+ret, "%02x ", ssd->chpid[chp]);
+
+	ret += sprintf (buf+ret, "\n");
+	return min((ssize_t)PAGE_SIZE, ret);
+}
+
+static ssize_t
+pimpampom_show (struct device * dev, char * buf)
+{
+	struct subchannel *sch = to_subchannel(dev);
+	struct pmcw *pmcw = &sch->schib.pmcw;
+
+	return sprintf (buf, "%02x %02x %02x\n",
+			pmcw->pim, pmcw->pam, pmcw->pom);
+}
+
+static ssize_t
+devtype_show (struct device *dev, char *buf)
+{
+	struct ccw_device *cdev = to_ccwdev(dev);
+	struct ccw_device_id *id = &(cdev->id);
+
+	if (id->dev_type != 0)
+		return sprintf(buf, "%04x/%02x\n",
+				id->dev_type, id->dev_model);
+	else
+		return sprintf(buf, "n/a\n");
+}
+
+static ssize_t
+cutype_show (struct device *dev, char *buf)
+{
+	struct ccw_device *cdev = to_ccwdev(dev);
+	struct ccw_device_id *id = &(cdev->id);
+
+	return sprintf(buf, "%04x/%02x\n",
+		       id->cu_type, id->cu_model);
+}
+
+static ssize_t
+online_show (struct device *dev, char *buf)
+{
+	struct ccw_device *cdev = to_ccwdev(dev);
+
+	return sprintf(buf, cdev->online ? "1\n" : "0\n");
+}
+
+static void
+ccw_device_remove_disconnected(struct ccw_device *cdev)
+{
+	struct subchannel *sch;
+	/*
+	 * Forced offline in disconnected state means
+	 * 'throw away device'.
+	 */
+	sch = to_subchannel(cdev->dev.parent);
+	device_unregister(&sch->dev);
+	/* Reset intparm to zeroes. */
+	sch->schib.pmcw.intparm = 0;
+	cio_modify(sch);
+	put_device(&sch->dev);
+}
+
+int
+ccw_device_set_offline(struct ccw_device *cdev)
+{
+	int ret;
+
+	if (!cdev)
+		return -ENODEV;
+	if (!cdev->online || !cdev->drv)
+		return -EINVAL;
+
+	if (cdev->drv->set_offline) {
+		ret = cdev->drv->set_offline(cdev);
+		if (ret != 0)
+			return ret;
+	}
+	cdev->online = 0;
+	spin_lock_irq(cdev->ccwlock);
+	ret = ccw_device_offline(cdev);
+	if (ret == -ENODEV) {
+		if (cdev->private->state != DEV_STATE_NOT_OPER) {
+			cdev->private->state = DEV_STATE_OFFLINE;
+			dev_fsm_event(cdev, DEV_EVENT_NOTOPER);
+		}
+		spin_unlock_irq(cdev->ccwlock);
+		return ret;
+	}
+	spin_unlock_irq(cdev->ccwlock);
+	if (ret == 0)
+		wait_event(cdev->private->wait_q, dev_fsm_final_state(cdev));
+	else {
+		pr_debug("ccw_device_offline returned %d, device %s\n",
+			 ret, cdev->dev.bus_id);
+		cdev->online = 1;
+	}
+ 	return ret;
+}
+
+int
+ccw_device_set_online(struct ccw_device *cdev)
+{
+	int ret;
+
+	if (!cdev)
+		return -ENODEV;
+	if (cdev->online || !cdev->drv)
+		return -EINVAL;
+
+	spin_lock_irq(cdev->ccwlock);
+	ret = ccw_device_online(cdev);
+	spin_unlock_irq(cdev->ccwlock);
+	if (ret == 0)
+		wait_event(cdev->private->wait_q, dev_fsm_final_state(cdev));
+	else {
+		pr_debug("ccw_device_online returned %d, device %s\n",
+			 ret, cdev->dev.bus_id);
+		return ret;
+	}
+	if (cdev->private->state != DEV_STATE_ONLINE)
+		return -ENODEV;
+	if (!cdev->drv->set_online || cdev->drv->set_online(cdev) == 0) {
+		cdev->online = 1;
+		return 0;
+	}
+	spin_lock_irq(cdev->ccwlock);
+	ret = ccw_device_offline(cdev);
+	spin_unlock_irq(cdev->ccwlock);
+	if (ret == 0)
+		wait_event(cdev->private->wait_q, dev_fsm_final_state(cdev));
+	else 
+		pr_debug("ccw_device_offline returned %d, device %s\n",
+			 ret, cdev->dev.bus_id);
+	return (ret = 0) ? -ENODEV : ret;
+}
+
+static ssize_t
+online_store (struct device *dev, const char *buf, size_t count)
+{
+	struct ccw_device *cdev = to_ccwdev(dev);
+	int i, force, ret;
+	char *tmp;
+
+	if (atomic_compare_and_swap(0, 1, &cdev->private->onoff))
+		return -EAGAIN;
+
+	if (cdev->drv && !try_module_get(cdev->drv->owner)) {
+		atomic_set(&cdev->private->onoff, 0);
+		return -EINVAL;
+	}
+	if (!strncmp(buf, "force\n", count)) {
+		force = 1;
+		i = 1;
+	} else {
+		force = 0;
+		i = simple_strtoul(buf, &tmp, 16);
+	}
+	if (i == 1) {
+		/* Do device recognition, if needed. */
+		if (cdev->id.cu_type == 0) {
+			ret = ccw_device_recognition(cdev);
+			if (ret) {
+				printk(KERN_WARNING"Couldn't start recognition "
+				       "for device %s (ret=%d)\n",
+				       cdev->dev.bus_id, ret);
+				goto out;
+			}
+			wait_event(cdev->private->wait_q,
+				   cdev->private->flags.recog_done);
+		}
+		if (cdev->drv && cdev->drv->set_online)
+			ccw_device_set_online(cdev);
+	} else if (i == 0) {
+		if (cdev->private->state == DEV_STATE_DISCONNECTED)
+			ccw_device_remove_disconnected(cdev);
+		else if (cdev->drv && cdev->drv->set_offline)
+			ccw_device_set_offline(cdev);
+	}
+	if (force && cdev->private->state == DEV_STATE_BOXED) {
+		ret = ccw_device_stlck(cdev);
+		if (ret) {
+			printk(KERN_WARNING"ccw_device_stlck for device %s "
+			       "returned %d!\n", cdev->dev.bus_id, ret);
+			goto out;
+		}
+		/* Do device recognition, if needed. */
+		if (cdev->id.cu_type == 0) {
+			cdev->private->state = DEV_STATE_NOT_OPER;
+			ret = ccw_device_recognition(cdev);
+			if (ret) {
+				printk(KERN_WARNING"Couldn't start recognition "
+				       "for device %s (ret=%d)\n",
+				       cdev->dev.bus_id, ret);
+				goto out;
+			}
+			wait_event(cdev->private->wait_q,
+				   cdev->private->flags.recog_done);
+		}
+		if (cdev->drv && cdev->drv->set_online)
+			ccw_device_set_online(cdev);
+	}
+	out:
+	if (cdev->drv)
+		module_put(cdev->drv->owner);
+	atomic_set(&cdev->private->onoff, 0);
+	return count;
+}
+
+static ssize_t
+available_show (struct device *dev, char *buf)
+{
+	struct ccw_device *cdev = to_ccwdev(dev);
+	struct subchannel *sch;
+
+	switch (cdev->private->state) {
+	case DEV_STATE_BOXED:
+		return sprintf(buf, "boxed\n");
+	case DEV_STATE_DISCONNECTED:
+	case DEV_STATE_DISCONNECTED_SENSE_ID:
+	case DEV_STATE_NOT_OPER:
+		sch = to_subchannel(dev->parent);
+		if (!sch->lpm)
+			return sprintf(buf, "no path\n");
+		else
+			return sprintf(buf, "no device\n");
+	default:
+		/* All other states considered fine. */
+		return sprintf(buf, "good\n");
+	}
+}
+
+static DEVICE_ATTR(chpids, 0444, chpids_show, NULL);
+static DEVICE_ATTR(pimpampom, 0444, pimpampom_show, NULL);
+static DEVICE_ATTR(devtype, 0444, devtype_show, NULL);
+static DEVICE_ATTR(cutype, 0444, cutype_show, NULL);
+static DEVICE_ATTR(online, 0644, online_show, online_store);
+extern struct device_attribute dev_attr_cmb_enable;
+static DEVICE_ATTR(availability, 0444, available_show, NULL);
+
+static struct attribute * subch_attrs[] = {
+	&dev_attr_chpids.attr,
+	&dev_attr_pimpampom.attr,
+	NULL,
+};
+
+static struct attribute_group subch_attr_group = {
+	.attrs = subch_attrs,
+};
+
+static inline int
+subchannel_add_files (struct device *dev)
+{
+	return sysfs_create_group(&dev->kobj, &subch_attr_group);
+}
+
+static struct attribute * ccwdev_attrs[] = {
+	&dev_attr_devtype.attr,
+	&dev_attr_cutype.attr,
+	&dev_attr_online.attr,
+	&dev_attr_cmb_enable.attr,
+	&dev_attr_availability.attr,
+	NULL,
+};
+
+static struct attribute_group ccwdev_attr_group = {
+	.attrs = ccwdev_attrs,
+};
+
+static inline int
+device_add_files (struct device *dev)
+{
+	return sysfs_create_group(&dev->kobj, &ccwdev_attr_group);
+}
+
+static inline void
+device_remove_files(struct device *dev)
+{
+	sysfs_remove_group(&dev->kobj, &ccwdev_attr_group);
+}
+
+/* this is a simple abstraction for device_register that sets the
+ * correct bus type and adds the bus specific files */
+int
+ccw_device_register(struct ccw_device *cdev)
+{
+	struct device *dev = &cdev->dev;
+	int ret;
+
+	dev->bus = &ccw_bus_type;
+
+	if ((ret = device_add(dev)))
+		return ret;
+
+	set_bit(1, &cdev->private->registered);
+	if ((ret = device_add_files(dev))) {
+		if (test_and_clear_bit(1, &cdev->private->registered))
+			device_del(dev);
+	}
+	return ret;
+}
+
+static struct ccw_device *
+get_disc_ccwdev_by_devno(unsigned int devno, struct ccw_device *sibling)
+{
+	struct ccw_device *cdev;
+	struct list_head *entry;
+	struct device *dev;
+
+	if (!get_bus(&ccw_bus_type))
+		return NULL;
+	down_read(&ccw_bus_type.subsys.rwsem);
+	cdev = NULL;
+	list_for_each(entry, &ccw_bus_type.devices.list) {
+		dev = get_device(container_of(entry,
+					      struct device, bus_list));
+		if (!dev)
+			continue;
+		cdev = to_ccwdev(dev);
+		if ((cdev->private->state == DEV_STATE_DISCONNECTED) &&
+		    (cdev->private->devno == devno) &&
+		    (cdev != sibling)) {
+			cdev->private->state = DEV_STATE_NOT_OPER;
+			break;
+		}
+		put_device(dev);
+		cdev = NULL;
+	}
+	up_read(&ccw_bus_type.subsys.rwsem);
+	put_bus(&ccw_bus_type);
+
+	return cdev;
+}
+
+static void
+ccw_device_add_changed(void *data)
+{
+
+	struct ccw_device *cdev;
+
+	cdev = (struct ccw_device *)data;
+	if (device_add(&cdev->dev)) {
+		put_device(&cdev->dev);
+		return;
+	}
+	set_bit(1, &cdev->private->registered);
+	if (device_add_files(&cdev->dev)) {
+		if (test_and_clear_bit(1, &cdev->private->registered))
+			device_unregister(&cdev->dev);
+	}
+}
+
+extern int css_get_ssd_info(struct subchannel *sch);
+
+void
+ccw_device_do_unreg_rereg(void *data)
+{
+	struct ccw_device *cdev;
+	struct subchannel *sch;
+	int need_rename;
+
+	cdev = (struct ccw_device *)data;
+	sch = to_subchannel(cdev->dev.parent);
+	if (cdev->private->devno != sch->schib.pmcw.dev) {
+		/*
+		 * The device number has changed. This is usually only when
+		 * a device has been detached under VM and then re-appeared
+		 * on another subchannel because of a different attachment
+		 * order than before. Ideally, we should should just switch
+		 * subchannels, but unfortunately, this is not possible with
+		 * the current implementation.
+		 * Instead, we search for the old subchannel for this device
+		 * number and deregister so there are no collisions with the
+		 * newly registered ccw_device.
+		 * FIXME: Find another solution so the block layer doesn't
+		 *        get possibly sick...
+		 */
+		struct ccw_device *other_cdev;
+
+		need_rename = 1;
+		other_cdev = get_disc_ccwdev_by_devno(sch->schib.pmcw.dev,
+						      cdev);
+		if (other_cdev) {
+			struct subchannel *other_sch;
+
+			other_sch = to_subchannel(other_cdev->dev.parent);
+			if (get_device(&other_sch->dev)) {
+				stsch(other_sch->irq, &other_sch->schib);
+				if (other_sch->schib.pmcw.dnv) {
+					other_sch->schib.pmcw.intparm = 0;
+					cio_modify(other_sch);
+				}
+				device_unregister(&other_sch->dev);
+			}
+		}
+		/* Update ssd info here. */
+		css_get_ssd_info(sch);
+		cdev->private->devno = sch->schib.pmcw.dev;
+	} else
+		need_rename = 0;
+	device_remove_files(&cdev->dev);
+	if (test_and_clear_bit(1, &cdev->private->registered))
+		device_del(&cdev->dev);
+	if (need_rename)
+		snprintf (cdev->dev.bus_id, BUS_ID_SIZE, "0.0.%04x",
+			  sch->schib.pmcw.dev);
+	PREPARE_WORK(&cdev->private->kick_work,
+		     ccw_device_add_changed, (void *)cdev);
+	queue_work(ccw_device_work, &cdev->private->kick_work);
+}
+
+static void
+ccw_device_release(struct device *dev)
+{
+	struct ccw_device *cdev;
+
+	cdev = to_ccwdev(dev);
+	kfree(cdev->private);
+	kfree(cdev);
+}
+
+/*
+ * Register recognized device.
+ */
+static void
+io_subchannel_register(void *data)
+{
+	struct ccw_device *cdev;
+	struct subchannel *sch;
+	int ret;
+	unsigned long flags;
+
+	cdev = (struct ccw_device *) data;
+	sch = to_subchannel(cdev->dev.parent);
+
+	if (!list_empty(&sch->dev.children)) {
+		bus_rescan_devices(&ccw_bus_type);
+		goto out;
+	}
+	/* make it known to the system */
+	ret = ccw_device_register(cdev);
+	if (ret) {
+		printk (KERN_WARNING "%s: could not register %s\n",
+			__func__, cdev->dev.bus_id);
+		put_device(&cdev->dev);
+		spin_lock_irqsave(&sch->lock, flags);
+		sch->dev.driver_data = NULL;
+		spin_unlock_irqrestore(&sch->lock, flags);
+		kfree (cdev->private);
+		kfree (cdev);
+		put_device(&sch->dev);
+		if (atomic_dec_and_test(&ccw_device_init_count))
+			wake_up(&ccw_device_init_wq);
+		return;
+	}
+
+	ret = subchannel_add_files(cdev->dev.parent);
+	if (ret)
+		printk(KERN_WARNING "%s: could not add attributes to %s\n",
+		       __func__, sch->dev.bus_id);
+	put_device(&cdev->dev);
+out:
+	cdev->private->flags.recog_done = 1;
+	put_device(&sch->dev);
+	wake_up(&cdev->private->wait_q);
+	if (atomic_dec_and_test(&ccw_device_init_count))
+		wake_up(&ccw_device_init_wq);
+}
+
+void
+ccw_device_call_sch_unregister(void *data)
+{
+	struct ccw_device *cdev = data;
+	struct subchannel *sch;
+
+	sch = to_subchannel(cdev->dev.parent);
+	device_unregister(&sch->dev);
+	/* Reset intparm to zeroes. */
+	sch->schib.pmcw.intparm = 0;
+	cio_modify(sch);
+	put_device(&cdev->dev);
+	put_device(&sch->dev);
+}
+
+/*
+ * subchannel recognition done. Called from the state machine.
+ */
+void
+io_subchannel_recog_done(struct ccw_device *cdev)
+{
+	struct subchannel *sch;
+
+	if (css_init_done == 0) {
+		cdev->private->flags.recog_done = 1;
+		return;
+	}
+	switch (cdev->private->state) {
+	case DEV_STATE_NOT_OPER:
+		cdev->private->flags.recog_done = 1;
+		/* Remove device found not operational. */
+		if (!get_device(&cdev->dev))
+			break;
+		sch = to_subchannel(cdev->dev.parent);
+		PREPARE_WORK(&cdev->private->kick_work,
+			     ccw_device_call_sch_unregister, (void *) cdev);
+		queue_work(slow_path_wq, &cdev->private->kick_work);
+		if (atomic_dec_and_test(&ccw_device_init_count))
+			wake_up(&ccw_device_init_wq);
+		break;
+	case DEV_STATE_BOXED:
+		/* Device did not respond in time. */
+	case DEV_STATE_OFFLINE:
+		/* 
+		 * We can't register the device in interrupt context so
+		 * we schedule a work item.
+		 */
+		if (!get_device(&cdev->dev))
+			break;
+		PREPARE_WORK(&cdev->private->kick_work,
+			     io_subchannel_register, (void *) cdev);
+		queue_work(slow_path_wq, &cdev->private->kick_work);
+		break;
+	}
+}
+
+static int
+io_subchannel_recog(struct ccw_device *cdev, struct subchannel *sch)
+{
+	int rc;
+	struct ccw_device_private *priv;
+
+	sch->dev.driver_data = cdev;
+	sch->driver = &io_subchannel_driver;
+	cdev->ccwlock = &sch->lock;
+	/* Init private data. */
+	priv = cdev->private;
+	priv->devno = sch->schib.pmcw.dev;
+	priv->irq = sch->irq;
+	priv->state = DEV_STATE_NOT_OPER;
+	INIT_LIST_HEAD(&priv->cmb_list);
+	init_waitqueue_head(&priv->wait_q);
+	init_timer(&priv->timer);
+
+	/* Set an initial name for the device. */
+	snprintf (cdev->dev.bus_id, BUS_ID_SIZE, "0.0.%04x",
+		  sch->schib.pmcw.dev);
+
+	/* Increase counter of devices currently in recognition. */
+	atomic_inc(&ccw_device_init_count);
+
+	/* Start async. device sensing. */
+	spin_lock_irq(&sch->lock);
+	rc = ccw_device_recognition(cdev);
+	spin_unlock_irq(&sch->lock);
+	if (rc) {
+		if (atomic_dec_and_test(&ccw_device_init_count))
+			wake_up(&ccw_device_init_wq);
+	}
+	return rc;
+}
+
+static int
+io_subchannel_probe (struct device *pdev)
+{
+	struct subchannel *sch;
+	struct ccw_device *cdev;
+	int rc;
+	unsigned long flags;
+
+	sch = to_subchannel(pdev);
+	if (sch->dev.driver_data) {
+		/*
+		 * This subchannel already has an associated ccw_device.
+		 * Register it and exit. This happens for all early
+		 * device, e.g. the console.
+		 */
+		cdev = sch->dev.driver_data;
+		device_initialize(&cdev->dev);
+		ccw_device_register(cdev);
+		subchannel_add_files(&sch->dev);
+		/*
+		 * Check if the device is already online. If it is
+		 * the reference count needs to be corrected
+		 * (see ccw_device_online and css_init_done for the
+		 * ugly details).
+		 */
+		if (cdev->private->state != DEV_STATE_NOT_OPER &&
+		    cdev->private->state != DEV_STATE_OFFLINE &&
+		    cdev->private->state != DEV_STATE_BOXED)
+			get_device(&cdev->dev);
+		return 0;
+	}
+	cdev  = kmalloc (sizeof(*cdev), GFP_KERNEL);
+	if (!cdev)
+		return -ENOMEM;
+	memset(cdev, 0, sizeof(struct ccw_device));
+	cdev->private = kmalloc(sizeof(struct ccw_device_private), 
+				GFP_KERNEL | GFP_DMA);
+	if (!cdev->private) {
+		kfree(cdev);
+		return -ENOMEM;
+	}
+	memset(cdev->private, 0, sizeof(struct ccw_device_private));
+	atomic_set(&cdev->private->onoff, 0);
+	cdev->dev = (struct device) {
+		.parent = pdev,
+		.release = ccw_device_release,
+	};
+	INIT_LIST_HEAD(&cdev->private->kick_work.entry);
+	/* Do first half of device_register. */
+	device_initialize(&cdev->dev);
+
+	if (!get_device(&sch->dev)) {
+		if (cdev->dev.release)
+			cdev->dev.release(&cdev->dev);
+		return -ENODEV;
+	}
+
+	rc = io_subchannel_recog(cdev, to_subchannel(pdev));
+	if (rc) {
+		spin_lock_irqsave(&sch->lock, flags);
+		sch->dev.driver_data = NULL;
+		spin_unlock_irqrestore(&sch->lock, flags);
+		if (cdev->dev.release)
+			cdev->dev.release(&cdev->dev);
+	}
+
+	return rc;
+}
+
+static void
+ccw_device_unregister(void *data)
+{
+	struct ccw_device *cdev;
+
+	cdev = (struct ccw_device *)data;
+	if (test_and_clear_bit(1, &cdev->private->registered))
+		device_unregister(&cdev->dev);
+	put_device(&cdev->dev);
+}
+
+static int
+io_subchannel_remove (struct device *dev)
+{
+	struct ccw_device *cdev;
+	unsigned long flags;
+
+	if (!dev->driver_data)
+		return 0;
+	cdev = dev->driver_data;
+	/* Set ccw device to not operational and drop reference. */
+	spin_lock_irqsave(cdev->ccwlock, flags);
+	dev->driver_data = NULL;
+	cdev->private->state = DEV_STATE_NOT_OPER;
+	spin_unlock_irqrestore(cdev->ccwlock, flags);
+	/*
+	 * Put unregistration on workqueue to avoid livelocks on the css bus
+	 * semaphore.
+	 */
+	if (get_device(&cdev->dev)) {
+		PREPARE_WORK(&cdev->private->kick_work,
+			     ccw_device_unregister, (void *) cdev);
+		queue_work(ccw_device_work, &cdev->private->kick_work);
+	}
+	return 0;
+}
+
+static int
+io_subchannel_notify(struct device *dev, int event)
+{
+	struct ccw_device *cdev;
+
+	cdev = dev->driver_data;
+	if (!cdev)
+		return 0;
+	if (!cdev->drv)
+		return 0;
+	if (!cdev->online)
+		return 0;
+	return cdev->drv->notify ? cdev->drv->notify(cdev, event) : 0;
+}
+
+static void
+io_subchannel_verify(struct device *dev)
+{
+	struct ccw_device *cdev;
+
+	cdev = dev->driver_data;
+	if (cdev)
+		dev_fsm_event(cdev, DEV_EVENT_VERIFY);
+}
+
+static void
+io_subchannel_ioterm(struct device *dev)
+{
+	struct ccw_device *cdev;
+
+	cdev = dev->driver_data;
+	if (!cdev)
+		return;
+	cdev->private->state = DEV_STATE_CLEAR_VERIFY;
+	if (cdev->handler)
+		cdev->handler(cdev, cdev->private->intparm,
+			      ERR_PTR(-EIO));
+}
+
+static void
+io_subchannel_shutdown(struct device *dev)
+{
+	struct subchannel *sch;
+	struct ccw_device *cdev;
+	int ret;
+
+	sch = to_subchannel(dev);
+	cdev = dev->driver_data;
+
+	if (cio_is_console(sch->irq))
+		return;
+	if (!sch->schib.pmcw.ena)
+		/* Nothing to do. */
+		return;
+	ret = cio_disable_subchannel(sch);
+	if (ret != -EBUSY)
+		/* Subchannel is disabled, we're done. */
+		return;
+	cdev->private->state = DEV_STATE_QUIESCE;
+	if (cdev->handler)
+		cdev->handler(cdev, cdev->private->intparm,
+			      ERR_PTR(-EIO));
+	ret = ccw_device_cancel_halt_clear(cdev);
+	if (ret == -EBUSY) {
+		ccw_device_set_timeout(cdev, HZ/10);
+		wait_event(cdev->private->wait_q, dev_fsm_final_state(cdev));
+	}
+	cio_disable_subchannel(sch);
+}
+
+#ifdef CONFIG_CCW_CONSOLE
+static struct ccw_device console_cdev;
+static struct ccw_device_private console_private;
+static int console_cdev_in_use;
+
+static int
+ccw_device_console_enable (struct ccw_device *cdev, struct subchannel *sch)
+{
+	int rc;
+
+	/* Initialize the ccw_device structure. */
+	cdev->dev = (struct device) {
+		.parent = &sch->dev,
+	};
+	/* Initialize the subchannel structure */
+	sch->dev.parent = &css_bus_device;
+	sch->dev.bus = &css_bus_type;
+
+	rc = io_subchannel_recog(cdev, sch);
+	if (rc)
+		return rc;
+
+	/* Now wait for the async. recognition to come to an end. */
+	spin_lock_irq(cdev->ccwlock);
+	while (!dev_fsm_final_state(cdev))
+		wait_cons_dev();
+	rc = -EIO;
+	if (cdev->private->state != DEV_STATE_OFFLINE)
+		goto out_unlock;
+	ccw_device_online(cdev);
+	while (!dev_fsm_final_state(cdev))
+		wait_cons_dev();
+	if (cdev->private->state != DEV_STATE_ONLINE)
+		goto out_unlock;
+	rc = 0;
+out_unlock:
+	spin_unlock_irq(cdev->ccwlock);
+	return 0;
+}
+
+struct ccw_device *
+ccw_device_probe_console(void)
+{
+	struct subchannel *sch;
+	int ret;
+
+	if (xchg(&console_cdev_in_use, 1) != 0)
+		return NULL;
+	sch = cio_probe_console();
+	if (IS_ERR(sch)) {
+		console_cdev_in_use = 0;
+		return (void *) sch;
+	}
+	memset(&console_cdev, 0, sizeof(struct ccw_device));
+	memset(&console_private, 0, sizeof(struct ccw_device_private));
+	console_cdev.private = &console_private;
+	ret = ccw_device_console_enable(&console_cdev, sch);
+	if (ret) {
+		cio_release_console();
+		console_cdev_in_use = 0;
+		return ERR_PTR(ret);
+	}
+	console_cdev.online = 1;
+	return &console_cdev;
+}
+#endif
+
+/*
+ * get ccw_device matching the busid, but only if owned by cdrv
+ */
+struct ccw_device *
+get_ccwdev_by_busid(struct ccw_driver *cdrv, const char *bus_id)
+{
+	struct device *d, *dev;
+	struct device_driver *drv;
+
+	drv = get_driver(&cdrv->driver);
+	if (!drv)
+		return 0;
+
+	down_read(&drv->bus->subsys.rwsem);
+
+	dev = NULL;
+	list_for_each_entry(d, &drv->devices, driver_list) {
+		dev = get_device(d);
+
+		if (dev && !strncmp(bus_id, dev->bus_id, BUS_ID_SIZE))
+			break;
+		else if (dev) {
+			put_device(dev);
+			dev = NULL;
+		}
+	}
+	up_read(&drv->bus->subsys.rwsem);
+	put_driver(drv);
+
+	return dev ? to_ccwdev(dev) : 0;
+}
+
+/************************** device driver handling ************************/
+
+/* This is the implementation of the ccw_driver class. The probe, remove
+ * and release methods are initially very similar to the device_driver
+ * implementations, with the difference that they have ccw_device
+ * arguments.
+ *
+ * A ccw driver also contains the information that is needed for
+ * device matching.
+ */
+static int
+ccw_device_probe (struct device *dev)
+{
+	struct ccw_device *cdev = to_ccwdev(dev);
+	struct ccw_driver *cdrv = to_ccwdrv(dev->driver);
+	int ret;
+
+	cdev->drv = cdrv; /* to let the driver call _set_online */
+
+	ret = cdrv->probe ? cdrv->probe(cdev) : -ENODEV;
+
+	if (ret) {
+		cdev->drv = 0;
+		return ret;
+	}
+
+	return 0;
+}
+
+static int
+ccw_device_remove (struct device *dev)
+{
+	struct ccw_device *cdev = to_ccwdev(dev);
+	struct ccw_driver *cdrv = cdev->drv;
+	int ret;
+
+	pr_debug("removing device %s\n", cdev->dev.bus_id);
+	if (cdrv->remove)
+		cdrv->remove(cdev);
+	if (cdev->online) {
+		cdev->online = 0;
+		spin_lock_irq(cdev->ccwlock);
+		ret = ccw_device_offline(cdev);
+		spin_unlock_irq(cdev->ccwlock);
+		if (ret == 0)
+			wait_event(cdev->private->wait_q,
+				   dev_fsm_final_state(cdev));
+		else
+			//FIXME: we can't fail!
+			pr_debug("ccw_device_offline returned %d, device %s\n",
+				 ret, cdev->dev.bus_id);
+	}
+	ccw_device_set_timeout(cdev, 0);
+	cdev->drv = 0;
+	return 0;
+}
+
+int
+ccw_driver_register (struct ccw_driver *cdriver)
+{
+	struct device_driver *drv = &cdriver->driver;
+
+	drv->bus = &ccw_bus_type;
+	drv->name = cdriver->name;
+	drv->probe = ccw_device_probe;
+	drv->remove = ccw_device_remove;
+
+	return driver_register(drv);
+}
+
+void
+ccw_driver_unregister (struct ccw_driver *cdriver)
+{
+	driver_unregister(&cdriver->driver);
+}
+
+MODULE_LICENSE("GPL");
+EXPORT_SYMBOL(ccw_device_set_online);
+EXPORT_SYMBOL(ccw_device_set_offline);
+EXPORT_SYMBOL(ccw_driver_register);
+EXPORT_SYMBOL(ccw_driver_unregister);
+EXPORT_SYMBOL(get_ccwdev_by_busid);
+EXPORT_SYMBOL(ccw_bus_type);
+EXPORT_SYMBOL(ccw_device_work);
+EXPORT_SYMBOL(ccw_device_notify_work);
diff --git a/drivers/s390/cio/device.h b/drivers/s390/cio/device.h
new file mode 100644
index 0000000..a3aa056
--- /dev/null
+++ b/drivers/s390/cio/device.h
@@ -0,0 +1,115 @@
+#ifndef S390_DEVICE_H
+#define S390_DEVICE_H
+
+/*
+ * states of the device statemachine
+ */
+enum dev_state {
+	DEV_STATE_NOT_OPER,
+	DEV_STATE_SENSE_PGID,
+	DEV_STATE_SENSE_ID,
+	DEV_STATE_OFFLINE,
+	DEV_STATE_VERIFY,
+	DEV_STATE_ONLINE,
+	DEV_STATE_W4SENSE,
+	DEV_STATE_DISBAND_PGID,
+	DEV_STATE_BOXED,
+	/* states to wait for i/o completion before doing something */
+	DEV_STATE_CLEAR_VERIFY,
+	DEV_STATE_TIMEOUT_KILL,
+	DEV_STATE_WAIT4IO,
+	DEV_STATE_QUIESCE,
+	/* special states for devices gone not operational */
+	DEV_STATE_DISCONNECTED,
+	DEV_STATE_DISCONNECTED_SENSE_ID,
+	DEV_STATE_CMFCHANGE,
+	/* last element! */
+	NR_DEV_STATES
+};
+
+/*
+ * asynchronous events of the device statemachine
+ */
+enum dev_event {
+	DEV_EVENT_NOTOPER,
+	DEV_EVENT_INTERRUPT,
+	DEV_EVENT_TIMEOUT,
+	DEV_EVENT_VERIFY,
+	/* last element! */
+	NR_DEV_EVENTS
+};
+
+struct ccw_device;
+
+/*
+ * action called through jumptable
+ */
+typedef void (fsm_func_t)(struct ccw_device *, enum dev_event);
+extern fsm_func_t *dev_jumptable[NR_DEV_STATES][NR_DEV_EVENTS];
+
+static inline void
+dev_fsm_event(struct ccw_device *cdev, enum dev_event dev_event)
+{
+	dev_jumptable[cdev->private->state][dev_event](cdev, dev_event);
+}
+
+/*
+ * Delivers 1 if the device state is final.
+ */
+static inline int
+dev_fsm_final_state(struct ccw_device *cdev)
+{
+	return (cdev->private->state == DEV_STATE_NOT_OPER ||
+		cdev->private->state == DEV_STATE_OFFLINE ||
+		cdev->private->state == DEV_STATE_ONLINE ||
+		cdev->private->state == DEV_STATE_BOXED);
+}
+
+extern struct workqueue_struct *ccw_device_work;
+extern struct workqueue_struct *ccw_device_notify_work;
+
+void io_subchannel_recog_done(struct ccw_device *cdev);
+
+int ccw_device_cancel_halt_clear(struct ccw_device *);
+
+int ccw_device_register(struct ccw_device *);
+void ccw_device_do_unreg_rereg(void *);
+void ccw_device_call_sch_unregister(void *);
+
+int ccw_device_recognition(struct ccw_device *);
+int ccw_device_online(struct ccw_device *);
+int ccw_device_offline(struct ccw_device *);
+
+/* Function prototypes for device status and basic sense stuff. */
+void ccw_device_accumulate_irb(struct ccw_device *, struct irb *);
+void ccw_device_accumulate_basic_sense(struct ccw_device *, struct irb *);
+int ccw_device_accumulate_and_sense(struct ccw_device *, struct irb *);
+int ccw_device_do_sense(struct ccw_device *, struct irb *);
+
+/* Function prototypes for sense id stuff. */
+void ccw_device_sense_id_start(struct ccw_device *);
+void ccw_device_sense_id_irq(struct ccw_device *, enum dev_event);
+void ccw_device_sense_id_done(struct ccw_device *, int);
+
+/* Function prototypes for path grouping stuff. */
+void ccw_device_sense_pgid_start(struct ccw_device *);
+void ccw_device_sense_pgid_irq(struct ccw_device *, enum dev_event);
+void ccw_device_sense_pgid_done(struct ccw_device *, int);
+
+void ccw_device_verify_start(struct ccw_device *);
+void ccw_device_verify_irq(struct ccw_device *, enum dev_event);
+void ccw_device_verify_done(struct ccw_device *, int);
+
+void ccw_device_disband_start(struct ccw_device *);
+void ccw_device_disband_irq(struct ccw_device *, enum dev_event);
+void ccw_device_disband_done(struct ccw_device *, int);
+
+int ccw_device_call_handler(struct ccw_device *);
+
+int ccw_device_stlck(struct ccw_device *);
+
+/* qdio needs this. */
+void ccw_device_set_timeout(struct ccw_device *, int);
+
+void retry_set_schib(struct ccw_device *cdev);
+#endif
diff --git a/drivers/s390/cio/device_fsm.c b/drivers/s390/cio/device_fsm.c
new file mode 100644
index 0000000..9b7f6f5
--- /dev/null
+++ b/drivers/s390/cio/device_fsm.c
@@ -0,0 +1,1250 @@
+/*
+ * drivers/s390/cio/device_fsm.c
+ * finite state machine for device handling
+ *
+ *    Copyright (C) 2002 IBM Deutschland Entwicklung GmbH,
+ *			 IBM Corporation
+ *    Author(s): Cornelia Huck(cohuck@de.ibm.com)
+ *		 Martin Schwidefsky (schwidefsky@de.ibm.com)
+ */
+
+#include <linux/module.h>
+#include <linux/config.h>
+#include <linux/init.h>
+
+#include <asm/ccwdev.h>
+#include <asm/qdio.h>
+
+#include "cio.h"
+#include "cio_debug.h"
+#include "css.h"
+#include "device.h"
+#include "chsc.h"
+#include "ioasm.h"
+#include "qdio.h"
+
+int
+device_is_online(struct subchannel *sch)
+{
+	struct ccw_device *cdev;
+
+	if (!sch->dev.driver_data)
+		return 0;
+	cdev = sch->dev.driver_data;
+	return (cdev->private->state == DEV_STATE_ONLINE);
+}
+
+int
+device_is_disconnected(struct subchannel *sch)
+{
+	struct ccw_device *cdev;
+
+	if (!sch->dev.driver_data)
+		return 0;
+	cdev = sch->dev.driver_data;
+	return (cdev->private->state == DEV_STATE_DISCONNECTED ||
+		cdev->private->state == DEV_STATE_DISCONNECTED_SENSE_ID);
+}
+
+void
+device_set_disconnected(struct subchannel *sch)
+{
+	struct ccw_device *cdev;
+
+	if (!sch->dev.driver_data)
+		return;
+	cdev = sch->dev.driver_data;
+	ccw_device_set_timeout(cdev, 0);
+	cdev->private->flags.fake_irb = 0;
+	cdev->private->state = DEV_STATE_DISCONNECTED;
+}
+
+void
+device_set_waiting(struct subchannel *sch)
+{
+	struct ccw_device *cdev;
+
+	if (!sch->dev.driver_data)
+		return;
+	cdev = sch->dev.driver_data;
+	ccw_device_set_timeout(cdev, 10*HZ);
+	cdev->private->state = DEV_STATE_WAIT4IO;
+}
+
+/*
+ * Timeout function. It just triggers a DEV_EVENT_TIMEOUT.
+ */
+static void
+ccw_device_timeout(unsigned long data)
+{
+	struct ccw_device *cdev;
+
+	cdev = (struct ccw_device *) data;
+	spin_lock_irq(cdev->ccwlock);
+	dev_fsm_event(cdev, DEV_EVENT_TIMEOUT);
+	spin_unlock_irq(cdev->ccwlock);
+}
+
+/*
+ * Set timeout
+ */
+void
+ccw_device_set_timeout(struct ccw_device *cdev, int expires)
+{
+	if (expires == 0) {
+		del_timer(&cdev->private->timer);
+		return;
+	}
+	if (timer_pending(&cdev->private->timer)) {
+		if (mod_timer(&cdev->private->timer, jiffies + expires))
+			return;
+	}
+	cdev->private->timer.function = ccw_device_timeout;
+	cdev->private->timer.data = (unsigned long) cdev;
+	cdev->private->timer.expires = jiffies + expires;
+	add_timer(&cdev->private->timer);
+}
+
+/* Kill any pending timers after machine check. */
+void
+device_kill_pending_timer(struct subchannel *sch)
+{
+	struct ccw_device *cdev;
+
+	if (!sch->dev.driver_data)
+		return;
+	cdev = sch->dev.driver_data;
+	ccw_device_set_timeout(cdev, 0);
+}
+
+/*
+ * Cancel running i/o. This is called repeatedly since halt/clear are
+ * asynchronous operations. We do one try with cio_cancel, two tries
+ * with cio_halt, 255 tries with cio_clear. If everythings fails panic.
+ * Returns 0 if device now idle, -ENODEV for device not operational and
+ * -EBUSY if an interrupt is expected (either from halt/clear or from a
+ * status pending).
+ */
+int
+ccw_device_cancel_halt_clear(struct ccw_device *cdev)
+{
+	struct subchannel *sch;
+	int ret;
+
+	sch = to_subchannel(cdev->dev.parent);
+	ret = stsch(sch->irq, &sch->schib);
+	if (ret || !sch->schib.pmcw.dnv)
+		return -ENODEV; 
+	if (!sch->schib.pmcw.ena || sch->schib.scsw.actl == 0)
+		/* Not operational or no activity -> done. */
+		return 0;
+	/* Stage 1: cancel io. */
+	if (!(sch->schib.scsw.actl & SCSW_ACTL_HALT_PEND) &&
+	    !(sch->schib.scsw.actl & SCSW_ACTL_CLEAR_PEND)) {
+		ret = cio_cancel(sch);
+		if (ret != -EINVAL)
+			return ret;
+		/* cancel io unsuccessful. From now on it is asynchronous. */
+		cdev->private->iretry = 3;	/* 3 halt retries. */
+	}
+	if (!(sch->schib.scsw.actl & SCSW_ACTL_CLEAR_PEND)) {
+		/* Stage 2: halt io. */
+		if (cdev->private->iretry) {
+			cdev->private->iretry--;
+			ret = cio_halt(sch);
+			return (ret == 0) ? -EBUSY : ret;
+		}
+		/* halt io unsuccessful. */
+		cdev->private->iretry = 255;	/* 255 clear retries. */
+	}
+	/* Stage 3: clear io. */
+	if (cdev->private->iretry) {
+		cdev->private->iretry--;
+		ret = cio_clear (sch);
+		return (ret == 0) ? -EBUSY : ret;
+	}
+	panic("Can't stop i/o on subchannel.\n");
+}
+
+static int
+ccw_device_handle_oper(struct ccw_device *cdev)
+{
+	struct subchannel *sch;
+
+	sch = to_subchannel(cdev->dev.parent);
+	cdev->private->flags.recog_done = 1;
+	/*
+	 * Check if cu type and device type still match. If
+	 * not, it is certainly another device and we have to
+	 * de- and re-register. Also check here for non-matching devno.
+	 */
+	if (cdev->id.cu_type != cdev->private->senseid.cu_type ||
+	    cdev->id.cu_model != cdev->private->senseid.cu_model ||
+	    cdev->id.dev_type != cdev->private->senseid.dev_type ||
+	    cdev->id.dev_model != cdev->private->senseid.dev_model ||
+	    cdev->private->devno != sch->schib.pmcw.dev) {
+		PREPARE_WORK(&cdev->private->kick_work,
+			     ccw_device_do_unreg_rereg, (void *)cdev);
+		queue_work(ccw_device_work, &cdev->private->kick_work);
+		return 0;
+	}
+	cdev->private->flags.donotify = 1;
+	return 1;
+}
+
+/*
+ * The machine won't give us any notification by machine check if a chpid has
+ * been varied online on the SE so we have to find out by magic (i. e. driving
+ * the channel subsystem to device selection and updating our path masks).
+ */
+static inline void
+__recover_lost_chpids(struct subchannel *sch, int old_lpm)
+{
+	int mask, i;
+
+	for (i = 0; i<8; i++) {
+		mask = 0x80 >> i;
+		if (!(sch->lpm & mask))
+			continue;
+		if (old_lpm & mask)
+			continue;
+		chpid_is_actually_online(sch->schib.pmcw.chpid[i]);
+	}
+}
+
+/*
+ * Stop device recognition.
+ */
+static void
+ccw_device_recog_done(struct ccw_device *cdev, int state)
+{
+	struct subchannel *sch;
+	int notify, old_lpm, same_dev;
+
+	sch = to_subchannel(cdev->dev.parent);
+
+	ccw_device_set_timeout(cdev, 0);
+	cio_disable_subchannel(sch);
+	/*
+	 * Now that we tried recognition, we have performed device selection
+	 * through ssch() and the path information is up to date.
+	 */
+	old_lpm = sch->lpm;
+	stsch(sch->irq, &sch->schib);
+	sch->lpm = sch->schib.pmcw.pim &
+		sch->schib.pmcw.pam &
+		sch->schib.pmcw.pom &
+		sch->opm;
+	if (cdev->private->state == DEV_STATE_DISCONNECTED_SENSE_ID)
+		/* Force reprobe on all chpids. */
+		old_lpm = 0;
+	if (sch->lpm != old_lpm)
+		__recover_lost_chpids(sch, old_lpm);
+	if (cdev->private->state == DEV_STATE_DISCONNECTED_SENSE_ID) {
+		if (state == DEV_STATE_NOT_OPER) {
+			cdev->private->flags.recog_done = 1;
+			cdev->private->state = DEV_STATE_DISCONNECTED;
+			return;
+		}
+		/* Boxed devices don't need extra treatment. */
+	}
+	notify = 0;
+	same_dev = 0; /* Keep the compiler quiet... */
+	switch (state) {
+	case DEV_STATE_NOT_OPER:
+		CIO_DEBUG(KERN_WARNING, 2,
+			  "SenseID : unknown device %04x on subchannel %04x\n",
+			  cdev->private->devno, sch->irq);
+		break;
+	case DEV_STATE_OFFLINE:
+		if (cdev->private->state == DEV_STATE_DISCONNECTED_SENSE_ID) {
+			same_dev = ccw_device_handle_oper(cdev);
+			notify = 1;
+		}
+		/* fill out sense information */
+		cdev->id = (struct ccw_device_id) {
+			.cu_type   = cdev->private->senseid.cu_type,
+			.cu_model  = cdev->private->senseid.cu_model,
+			.dev_type  = cdev->private->senseid.dev_type,
+			.dev_model = cdev->private->senseid.dev_model,
+		};
+		if (notify) {
+			cdev->private->state = DEV_STATE_OFFLINE;
+			if (same_dev) {
+				/* Get device online again. */
+				ccw_device_online(cdev);
+				wake_up(&cdev->private->wait_q);
+			}
+			return;
+		}
+		/* Issue device info message. */
+		CIO_DEBUG(KERN_INFO, 2, "SenseID : device %04x reports: "
+			  "CU  Type/Mod = %04X/%02X, Dev Type/Mod = "
+			  "%04X/%02X\n", cdev->private->devno,
+			  cdev->id.cu_type, cdev->id.cu_model,
+			  cdev->id.dev_type, cdev->id.dev_model);
+		break;
+	case DEV_STATE_BOXED:
+		CIO_DEBUG(KERN_WARNING, 2,
+			  "SenseID : boxed device %04x on subchannel %04x\n",
+			  cdev->private->devno, sch->irq);
+		break;
+	}
+	cdev->private->state = state;
+	io_subchannel_recog_done(cdev);
+	if (state != DEV_STATE_NOT_OPER)
+		wake_up(&cdev->private->wait_q);
+}
+
+/*
+ * Function called from device_id.c after sense id has completed.
+ */
+void
+ccw_device_sense_id_done(struct ccw_device *cdev, int err)
+{
+	switch (err) {
+	case 0:
+		ccw_device_recog_done(cdev, DEV_STATE_OFFLINE);
+		break;
+	case -ETIME:		/* Sense id stopped by timeout. */
+		ccw_device_recog_done(cdev, DEV_STATE_BOXED);
+		break;
+	default:
+		ccw_device_recog_done(cdev, DEV_STATE_NOT_OPER);
+		break;
+	}
+}
+
+static void
+ccw_device_oper_notify(void *data)
+{
+	struct ccw_device *cdev;
+	struct subchannel *sch;
+	int ret;
+
+	cdev = (struct ccw_device *)data;
+	sch = to_subchannel(cdev->dev.parent);
+	ret = (sch->driver && sch->driver->notify) ?
+		sch->driver->notify(&sch->dev, CIO_OPER) : 0;
+	if (!ret)
+		/* Driver doesn't want device back. */
+		ccw_device_do_unreg_rereg((void *)cdev);
+	else
+		wake_up(&cdev->private->wait_q);
+}
+
+/*
+ * Finished with online/offline processing.
+ */
+static void
+ccw_device_done(struct ccw_device *cdev, int state)
+{
+	struct subchannel *sch;
+
+	sch = to_subchannel(cdev->dev.parent);
+
+	if (state != DEV_STATE_ONLINE)
+		cio_disable_subchannel(sch);
+
+	/* Reset device status. */
+	memset(&cdev->private->irb, 0, sizeof(struct irb));
+
+	cdev->private->state = state;
+
+
+	if (state == DEV_STATE_BOXED)
+		CIO_DEBUG(KERN_WARNING, 2,
+			  "Boxed device %04x on subchannel %04x\n",
+			  cdev->private->devno, sch->irq);
+
+	if (cdev->private->flags.donotify) {
+		cdev->private->flags.donotify = 0;
+		PREPARE_WORK(&cdev->private->kick_work, ccw_device_oper_notify,
+			     (void *)cdev);
+		queue_work(ccw_device_notify_work, &cdev->private->kick_work);
+	}
+	wake_up(&cdev->private->wait_q);
+
+	if (css_init_done && state != DEV_STATE_ONLINE)
+		put_device (&cdev->dev);
+}
+
+/*
+ * Function called from device_pgid.c after sense path ground has completed.
+ */
+void
+ccw_device_sense_pgid_done(struct ccw_device *cdev, int err)
+{
+	struct subchannel *sch;
+
+	sch = to_subchannel(cdev->dev.parent);
+	switch (err) {
+	case 0:
+		/* Start Path Group verification. */
+		sch->vpm = 0;	/* Start with no path groups set. */
+		cdev->private->state = DEV_STATE_VERIFY;
+		ccw_device_verify_start(cdev);
+		break;
+	case -ETIME:		/* Sense path group id stopped by timeout. */
+	case -EUSERS:		/* device is reserved for someone else. */
+		ccw_device_done(cdev, DEV_STATE_BOXED);
+		break;
+	case -EOPNOTSUPP: /* path grouping not supported, just set online. */
+		cdev->private->options.pgroup = 0;
+		ccw_device_done(cdev, DEV_STATE_ONLINE);
+		break;
+	default:
+		ccw_device_done(cdev, DEV_STATE_NOT_OPER);
+		break;
+	}
+}
+
+/*
+ * Start device recognition.
+ */
+int
+ccw_device_recognition(struct ccw_device *cdev)
+{
+	struct subchannel *sch;
+	int ret;
+
+	if ((cdev->private->state != DEV_STATE_NOT_OPER) &&
+	    (cdev->private->state != DEV_STATE_BOXED))
+		return -EINVAL;
+	sch = to_subchannel(cdev->dev.parent);
+	ret = cio_enable_subchannel(sch, sch->schib.pmcw.isc);
+	if (ret != 0)
+		/* Couldn't enable the subchannel for i/o. Sick device. */
+		return ret;
+
+	/* After 60s the device recognition is considered to have failed. */
+	ccw_device_set_timeout(cdev, 60*HZ);
+
+	/*
+	 * We used to start here with a sense pgid to find out whether a device
+	 * is locked by someone else. Unfortunately, the sense pgid command
+	 * code has other meanings on devices predating the path grouping
+	 * algorithm, so we start with sense id and box the device after an
+	 * timeout (or if sense pgid during path verification detects the device
+	 * is locked, as may happen on newer devices).
+	 */
+	cdev->private->flags.recog_done = 0;
+	cdev->private->state = DEV_STATE_SENSE_ID;
+	ccw_device_sense_id_start(cdev);
+	return 0;
+}
+
+/*
+ * Handle timeout in device recognition.
+ */
+static void
+ccw_device_recog_timeout(struct ccw_device *cdev, enum dev_event dev_event)
+{
+	int ret;
+
+	ret = ccw_device_cancel_halt_clear(cdev);
+	switch (ret) {
+	case 0:
+		ccw_device_recog_done(cdev, DEV_STATE_BOXED);
+		break;
+	case -ENODEV:
+		ccw_device_recog_done(cdev, DEV_STATE_NOT_OPER);
+		break;
+	default:
+		ccw_device_set_timeout(cdev, 3*HZ);
+	}
+}
+
+
+static void
+ccw_device_nopath_notify(void *data)
+{
+	struct ccw_device *cdev;
+	struct subchannel *sch;
+	int ret;
+
+	cdev = (struct ccw_device *)data;
+	sch = to_subchannel(cdev->dev.parent);
+	/* Extra sanity. */
+	if (sch->lpm)
+		return;
+	ret = (sch->driver && sch->driver->notify) ?
+		sch->driver->notify(&sch->dev, CIO_NO_PATH) : 0;
+	if (!ret) {
+		if (get_device(&sch->dev)) {
+			/* Driver doesn't want to keep device. */
+			cio_disable_subchannel(sch);
+			if (get_device(&cdev->dev)) {
+				PREPARE_WORK(&cdev->private->kick_work,
+					     ccw_device_call_sch_unregister,
+					     (void *)cdev);
+				queue_work(ccw_device_work,
+					   &cdev->private->kick_work);
+			} else
+				put_device(&sch->dev);
+		}
+	} else {
+		cio_disable_subchannel(sch);
+		ccw_device_set_timeout(cdev, 0);
+		cdev->private->flags.fake_irb = 0;
+		cdev->private->state = DEV_STATE_DISCONNECTED;
+		wake_up(&cdev->private->wait_q);
+	}
+}
+
+void
+ccw_device_verify_done(struct ccw_device *cdev, int err)
+{
+	cdev->private->flags.doverify = 0;
+	switch (err) {
+	case -EOPNOTSUPP: /* path grouping not supported, just set online. */
+		cdev->private->options.pgroup = 0;
+	case 0:
+		ccw_device_done(cdev, DEV_STATE_ONLINE);
+		/* Deliver fake irb to device driver, if needed. */
+		if (cdev->private->flags.fake_irb) {
+			memset(&cdev->private->irb, 0, sizeof(struct irb));
+			cdev->private->irb.scsw = (struct scsw) {
+				.cc = 1,
+				.fctl = SCSW_FCTL_START_FUNC,
+				.actl = SCSW_ACTL_START_PEND,
+				.stctl = SCSW_STCTL_STATUS_PEND,
+			};
+			cdev->private->flags.fake_irb = 0;
+			if (cdev->handler)
+				cdev->handler(cdev, cdev->private->intparm,
+					      &cdev->private->irb);
+			memset(&cdev->private->irb, 0, sizeof(struct irb));
+		}
+		break;
+	case -ETIME:
+		ccw_device_done(cdev, DEV_STATE_BOXED);
+		break;
+	default:
+		PREPARE_WORK(&cdev->private->kick_work,
+			     ccw_device_nopath_notify, (void *)cdev);
+		queue_work(ccw_device_notify_work, &cdev->private->kick_work);
+		ccw_device_done(cdev, DEV_STATE_NOT_OPER);
+		break;
+	}
+}
+
+/*
+ * Get device online.
+ */
+int
+ccw_device_online(struct ccw_device *cdev)
+{
+	struct subchannel *sch;
+	int ret;
+
+	if ((cdev->private->state != DEV_STATE_OFFLINE) &&
+	    (cdev->private->state != DEV_STATE_BOXED))
+		return -EINVAL;
+	sch = to_subchannel(cdev->dev.parent);
+	if (css_init_done && !get_device(&cdev->dev))
+		return -ENODEV;
+	ret = cio_enable_subchannel(sch, sch->schib.pmcw.isc);
+	if (ret != 0) {
+		/* Couldn't enable the subchannel for i/o. Sick device. */
+		if (ret == -ENODEV)
+			dev_fsm_event(cdev, DEV_EVENT_NOTOPER);
+		return ret;
+	}
+	/* Do we want to do path grouping? */
+	if (!cdev->private->options.pgroup) {
+		/* No, set state online immediately. */
+		ccw_device_done(cdev, DEV_STATE_ONLINE);
+		return 0;
+	}
+	/* Do a SensePGID first. */
+	cdev->private->state = DEV_STATE_SENSE_PGID;
+	ccw_device_sense_pgid_start(cdev);
+	return 0;
+}
+
+void
+ccw_device_disband_done(struct ccw_device *cdev, int err)
+{
+	switch (err) {
+	case 0:
+		ccw_device_done(cdev, DEV_STATE_OFFLINE);
+		break;
+	case -ETIME:
+		ccw_device_done(cdev, DEV_STATE_BOXED);
+		break;
+	default:
+		ccw_device_done(cdev, DEV_STATE_NOT_OPER);
+		break;
+	}
+}
+
+/*
+ * Shutdown device.
+ */
+int
+ccw_device_offline(struct ccw_device *cdev)
+{
+	struct subchannel *sch;
+
+	sch = to_subchannel(cdev->dev.parent);
+	if (stsch(sch->irq, &sch->schib) || !sch->schib.pmcw.dnv)
+		return -ENODEV;
+	if (cdev->private->state != DEV_STATE_ONLINE) {
+		if (sch->schib.scsw.actl != 0)
+			return -EBUSY;
+		return -EINVAL;
+	}
+	if (sch->schib.scsw.actl != 0)
+		return -EBUSY;
+	/* Are we doing path grouping? */
+	if (!cdev->private->options.pgroup) {
+		/* No, set state offline immediately. */
+		ccw_device_done(cdev, DEV_STATE_OFFLINE);
+		return 0;
+	}
+	/* Start Set Path Group commands. */
+	cdev->private->state = DEV_STATE_DISBAND_PGID;
+	ccw_device_disband_start(cdev);
+	return 0;
+}
+
+/*
+ * Handle timeout in device online/offline process.
+ */
+static void
+ccw_device_onoff_timeout(struct ccw_device *cdev, enum dev_event dev_event)
+{
+	int ret;
+
+	ret = ccw_device_cancel_halt_clear(cdev);
+	switch (ret) {
+	case 0:
+		ccw_device_done(cdev, DEV_STATE_BOXED);
+		break;
+	case -ENODEV:
+		ccw_device_done(cdev, DEV_STATE_NOT_OPER);
+		break;
+	default:
+		ccw_device_set_timeout(cdev, 3*HZ);
+	}
+}
+
+/*
+ * Handle not oper event in device recognition.
+ */
+static void
+ccw_device_recog_notoper(struct ccw_device *cdev, enum dev_event dev_event)
+{
+	ccw_device_recog_done(cdev, DEV_STATE_NOT_OPER);
+}
+
+/*
+ * Handle not operational event while offline.
+ */
+static void
+ccw_device_offline_notoper(struct ccw_device *cdev, enum dev_event dev_event)
+{
+	struct subchannel *sch;
+
+	cdev->private->state = DEV_STATE_NOT_OPER;
+	sch = to_subchannel(cdev->dev.parent);
+	if (get_device(&cdev->dev)) {
+		PREPARE_WORK(&cdev->private->kick_work,
+			     ccw_device_call_sch_unregister, (void *)cdev);
+		queue_work(ccw_device_work, &cdev->private->kick_work);
+	}
+	wake_up(&cdev->private->wait_q);
+}
+
+/*
+ * Handle not operational event while online.
+ */
+static void
+ccw_device_online_notoper(struct ccw_device *cdev, enum dev_event dev_event)
+{
+	struct subchannel *sch;
+
+	sch = to_subchannel(cdev->dev.parent);
+	if (sch->driver->notify &&
+	    sch->driver->notify(&sch->dev, sch->lpm ? CIO_GONE : CIO_NO_PATH)) {
+			ccw_device_set_timeout(cdev, 0);
+			cdev->private->flags.fake_irb = 0;
+			cdev->private->state = DEV_STATE_DISCONNECTED;
+			wake_up(&cdev->private->wait_q);
+			return;
+	}
+	cdev->private->state = DEV_STATE_NOT_OPER;
+	cio_disable_subchannel(sch);
+	if (sch->schib.scsw.actl != 0) {
+		// FIXME: not-oper indication to device driver ?
+		ccw_device_call_handler(cdev);
+	}
+	if (get_device(&cdev->dev)) {
+		PREPARE_WORK(&cdev->private->kick_work,
+			     ccw_device_call_sch_unregister, (void *)cdev);
+		queue_work(ccw_device_work, &cdev->private->kick_work);
+	}
+	wake_up(&cdev->private->wait_q);
+}
+
+/*
+ * Handle path verification event.
+ */
+static void
+ccw_device_online_verify(struct ccw_device *cdev, enum dev_event dev_event)
+{
+	struct subchannel *sch;
+
+	if (!cdev->private->options.pgroup)
+		return;
+	if (cdev->private->state == DEV_STATE_W4SENSE) {
+		cdev->private->flags.doverify = 1;
+		return;
+	}
+	sch = to_subchannel(cdev->dev.parent);
+	/*
+	 * Since we might not just be coming from an interrupt from the
+	 * subchannel we have to update the schib.
+	 */
+	stsch(sch->irq, &sch->schib);
+
+	if (sch->schib.scsw.actl != 0 ||
+	    (cdev->private->irb.scsw.stctl & SCSW_STCTL_STATUS_PEND)) {
+		/*
+		 * No final status yet or final status not yet delivered
+		 * to the device driver. Can't do path verfication now,
+		 * delay until final status was delivered.
+		 */
+		cdev->private->flags.doverify = 1;
+		return;
+	}
+	/* Device is idle, we can do the path verification. */
+	cdev->private->state = DEV_STATE_VERIFY;
+	ccw_device_verify_start(cdev);
+}
+
+/*
+ * Got an interrupt for a normal io (state online).
+ */
+static void
+ccw_device_irq(struct ccw_device *cdev, enum dev_event dev_event)
+{
+	struct irb *irb;
+
+	irb = (struct irb *) __LC_IRB;
+	/* Check for unsolicited interrupt. */
+	if ((irb->scsw.stctl ==
+	    		(SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS))
+	    && (!irb->scsw.cc)) {
+		if ((irb->scsw.dstat & DEV_STAT_UNIT_CHECK) &&
+		    !irb->esw.esw0.erw.cons) {
+			/* Unit check but no sense data. Need basic sense. */
+			if (ccw_device_do_sense(cdev, irb) != 0)
+				goto call_handler_unsol;
+			memcpy(irb, &cdev->private->irb, sizeof(struct irb));
+			cdev->private->state = DEV_STATE_W4SENSE;
+			cdev->private->intparm = 0;
+			return;
+		}
+call_handler_unsol:
+		if (cdev->handler)
+			cdev->handler (cdev, 0, irb);
+		return;
+	}
+	/* Accumulate status and find out if a basic sense is needed. */
+	ccw_device_accumulate_irb(cdev, irb);
+	if (cdev->private->flags.dosense) {
+		if (ccw_device_do_sense(cdev, irb) == 0) {
+			cdev->private->state = DEV_STATE_W4SENSE;
+		}
+		return;
+	}
+	/* Call the handler. */
+	if (ccw_device_call_handler(cdev) && cdev->private->flags.doverify)
+		/* Start delayed path verification. */
+		ccw_device_online_verify(cdev, 0);
+}
+
+/*
+ * Got an timeout in online state.
+ */
+static void
+ccw_device_online_timeout(struct ccw_device *cdev, enum dev_event dev_event)
+{
+	int ret;
+
+	ccw_device_set_timeout(cdev, 0);
+	ret = ccw_device_cancel_halt_clear(cdev);
+	if (ret == -EBUSY) {
+		ccw_device_set_timeout(cdev, 3*HZ);
+		cdev->private->state = DEV_STATE_TIMEOUT_KILL;
+		return;
+	}
+	if (ret == -ENODEV) {
+		struct subchannel *sch;
+
+		sch = to_subchannel(cdev->dev.parent);
+		if (!sch->lpm) {
+			PREPARE_WORK(&cdev->private->kick_work,
+				     ccw_device_nopath_notify, (void *)cdev);
+			queue_work(ccw_device_notify_work,
+				   &cdev->private->kick_work);
+		} else
+			dev_fsm_event(cdev, DEV_EVENT_NOTOPER);
+	} else if (cdev->handler)
+		cdev->handler(cdev, cdev->private->intparm,
+			      ERR_PTR(-ETIMEDOUT));
+}
+
+/*
+ * Got an interrupt for a basic sense.
+ */
+void
+ccw_device_w4sense(struct ccw_device *cdev, enum dev_event dev_event)
+{
+	struct irb *irb;
+
+	irb = (struct irb *) __LC_IRB;
+	/* Check for unsolicited interrupt. */
+	if (irb->scsw.stctl ==
+	    		(SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) {
+		if (irb->scsw.cc == 1)
+			/* Basic sense hasn't started. Try again. */
+			ccw_device_do_sense(cdev, irb);
+		else {
+			printk("Huh? %s(%s): unsolicited interrupt...\n",
+			       __FUNCTION__, cdev->dev.bus_id);
+			if (cdev->handler)
+				cdev->handler (cdev, 0, irb);
+		}
+		return;
+	}
+	/* Add basic sense info to irb. */
+	ccw_device_accumulate_basic_sense(cdev, irb);
+	if (cdev->private->flags.dosense) {
+		/* Another basic sense is needed. */
+		ccw_device_do_sense(cdev, irb);
+		return;
+	}
+	cdev->private->state = DEV_STATE_ONLINE;
+	/* Call the handler. */
+	if (ccw_device_call_handler(cdev) && cdev->private->flags.doverify)
+		/* Start delayed path verification. */
+		ccw_device_online_verify(cdev, 0);
+}
+
+static void
+ccw_device_clear_verify(struct ccw_device *cdev, enum dev_event dev_event)
+{
+	struct irb *irb;
+
+	irb = (struct irb *) __LC_IRB;
+	/* Accumulate status. We don't do basic sense. */
+	ccw_device_accumulate_irb(cdev, irb);
+	/* Try to start delayed device verification. */
+	ccw_device_online_verify(cdev, 0);
+	/* Note: Don't call handler for cio initiated clear! */
+}
+
+static void
+ccw_device_killing_irq(struct ccw_device *cdev, enum dev_event dev_event)
+{
+	struct subchannel *sch;
+
+	sch = to_subchannel(cdev->dev.parent);
+	ccw_device_set_timeout(cdev, 0);
+	/* OK, i/o is dead now. Call interrupt handler. */
+	cdev->private->state = DEV_STATE_ONLINE;
+	if (cdev->handler)
+		cdev->handler(cdev, cdev->private->intparm,
+			      ERR_PTR(-ETIMEDOUT));
+	if (!sch->lpm) {
+		PREPARE_WORK(&cdev->private->kick_work,
+			     ccw_device_nopath_notify, (void *)cdev);
+		queue_work(ccw_device_notify_work, &cdev->private->kick_work);
+	} else if (cdev->private->flags.doverify)
+		/* Start delayed path verification. */
+		ccw_device_online_verify(cdev, 0);
+}
+
+static void
+ccw_device_killing_timeout(struct ccw_device *cdev, enum dev_event dev_event)
+{
+	int ret;
+
+	ret = ccw_device_cancel_halt_clear(cdev);
+	if (ret == -EBUSY) {
+		ccw_device_set_timeout(cdev, 3*HZ);
+		return;
+	}
+	if (ret == -ENODEV) {
+		struct subchannel *sch;
+
+		sch = to_subchannel(cdev->dev.parent);
+		if (!sch->lpm) {
+			PREPARE_WORK(&cdev->private->kick_work,
+				     ccw_device_nopath_notify, (void *)cdev);
+			queue_work(ccw_device_notify_work,
+				   &cdev->private->kick_work);
+		} else
+			dev_fsm_event(cdev, DEV_EVENT_NOTOPER);
+		return;
+	}
+	//FIXME: Can we get here?
+	cdev->private->state = DEV_STATE_ONLINE;
+	if (cdev->handler)
+		cdev->handler(cdev, cdev->private->intparm,
+			      ERR_PTR(-ETIMEDOUT));
+}
+
+static void
+ccw_device_wait4io_irq(struct ccw_device *cdev, enum dev_event dev_event)
+{
+	struct irb *irb;
+	struct subchannel *sch;
+
+	irb = (struct irb *) __LC_IRB;
+	/*
+	 * Accumulate status and find out if a basic sense is needed.
+	 * This is fine since we have already adapted the lpm.
+	 */
+	ccw_device_accumulate_irb(cdev, irb);
+	if (cdev->private->flags.dosense) {
+		if (ccw_device_do_sense(cdev, irb) == 0) {
+			cdev->private->state = DEV_STATE_W4SENSE;
+		}
+		return;
+	}
+
+	/* Iff device is idle, reset timeout. */
+	sch = to_subchannel(cdev->dev.parent);
+	if (!stsch(sch->irq, &sch->schib))
+		if (sch->schib.scsw.actl == 0)
+			ccw_device_set_timeout(cdev, 0);
+	/* Call the handler. */
+	ccw_device_call_handler(cdev);
+	if (!sch->lpm) {
+		PREPARE_WORK(&cdev->private->kick_work,
+			     ccw_device_nopath_notify, (void *)cdev);
+		queue_work(ccw_device_notify_work, &cdev->private->kick_work);
+	} else if (cdev->private->flags.doverify)
+		ccw_device_online_verify(cdev, 0);
+}
+
+static void
+ccw_device_wait4io_timeout(struct ccw_device *cdev, enum dev_event dev_event)
+{
+	int ret;
+	struct subchannel *sch;
+
+	sch = to_subchannel(cdev->dev.parent);
+	ccw_device_set_timeout(cdev, 0);
+	ret = ccw_device_cancel_halt_clear(cdev);
+	if (ret == -EBUSY) {
+		ccw_device_set_timeout(cdev, 3*HZ);
+		cdev->private->state = DEV_STATE_TIMEOUT_KILL;
+		return;
+	}
+	if (ret == -ENODEV) {
+		if (!sch->lpm) {
+			PREPARE_WORK(&cdev->private->kick_work,
+				     ccw_device_nopath_notify, (void *)cdev);
+			queue_work(ccw_device_notify_work,
+				   &cdev->private->kick_work);
+		} else
+			dev_fsm_event(cdev, DEV_EVENT_NOTOPER);
+		return;
+	}
+	if (cdev->handler)
+		cdev->handler(cdev, cdev->private->intparm,
+			      ERR_PTR(-ETIMEDOUT));
+	if (!sch->lpm) {
+		PREPARE_WORK(&cdev->private->kick_work,
+			     ccw_device_nopath_notify, (void *)cdev);
+		queue_work(ccw_device_notify_work, &cdev->private->kick_work);
+	} else if (cdev->private->flags.doverify)
+		/* Start delayed path verification. */
+		ccw_device_online_verify(cdev, 0);
+}
+
+static void
+ccw_device_wait4io_verify(struct ccw_device *cdev, enum dev_event dev_event)
+{
+	/* When the I/O has terminated, we have to start verification. */
+	if (cdev->private->options.pgroup)
+		cdev->private->flags.doverify = 1;
+}
+
+static void
+ccw_device_stlck_done(struct ccw_device *cdev, enum dev_event dev_event)
+{
+	struct irb *irb;
+
+	switch (dev_event) {
+	case DEV_EVENT_INTERRUPT:
+		irb = (struct irb *) __LC_IRB;
+		/* Check for unsolicited interrupt. */
+		if ((irb->scsw.stctl ==
+		     (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) &&
+		    (!irb->scsw.cc))
+			/* FIXME: we should restart stlck here, but this
+			 * is extremely unlikely ... */
+			goto out_wakeup;
+
+		ccw_device_accumulate_irb(cdev, irb);
+		/* We don't care about basic sense etc. */
+		break;
+	default: /* timeout */
+		break;
+	}
+out_wakeup:
+	wake_up(&cdev->private->wait_q);
+}
+
+static void
+ccw_device_start_id(struct ccw_device *cdev, enum dev_event dev_event)
+{
+	struct subchannel *sch;
+
+	sch = to_subchannel(cdev->dev.parent);
+	if (cio_enable_subchannel(sch, sch->schib.pmcw.isc) != 0)
+		/* Couldn't enable the subchannel for i/o. Sick device. */
+		return;
+
+	/* After 60s the device recognition is considered to have failed. */
+	ccw_device_set_timeout(cdev, 60*HZ);
+
+	cdev->private->state = DEV_STATE_DISCONNECTED_SENSE_ID;
+	ccw_device_sense_id_start(cdev);
+}
+
+void
+device_trigger_reprobe(struct subchannel *sch)
+{
+	struct ccw_device *cdev;
+
+	if (!sch->dev.driver_data)
+		return;
+	cdev = sch->dev.driver_data;
+	if (cdev->private->state != DEV_STATE_DISCONNECTED)
+		return;
+
+	/* Update some values. */
+	if (stsch(sch->irq, &sch->schib))
+		return;
+
+	/*
+	 * The pim, pam, pom values may not be accurate, but they are the best
+	 * we have before performing device selection :/
+	 */
+	sch->lpm = sch->schib.pmcw.pim &
+		sch->schib.pmcw.pam &
+		sch->schib.pmcw.pom &
+		sch->opm;
+	/* Re-set some bits in the pmcw that were lost. */
+	sch->schib.pmcw.isc = 3;
+	sch->schib.pmcw.csense = 1;
+	sch->schib.pmcw.ena = 0;
+	if ((sch->lpm & (sch->lpm - 1)) != 0)
+		sch->schib.pmcw.mp = 1;
+	sch->schib.pmcw.intparm = (__u32)(unsigned long)sch;
+	/* We should also udate ssd info, but this has to wait. */
+	ccw_device_start_id(cdev, 0);
+}
+
+static void
+ccw_device_offline_irq(struct ccw_device *cdev, enum dev_event dev_event)
+{
+	struct subchannel *sch;
+
+	sch = to_subchannel(cdev->dev.parent);
+	/*
+	 * An interrupt in state offline means a previous disable was not
+	 * successful. Try again.
+	 */
+	cio_disable_subchannel(sch);
+}
+
+static void
+ccw_device_change_cmfstate(struct ccw_device *cdev, enum dev_event dev_event)
+{
+	retry_set_schib(cdev);
+	cdev->private->state = DEV_STATE_ONLINE;
+	dev_fsm_event(cdev, dev_event);
+}
+
+
+static void
+ccw_device_quiesce_done(struct ccw_device *cdev, enum dev_event dev_event)
+{
+	ccw_device_set_timeout(cdev, 0);
+	if (dev_event == DEV_EVENT_NOTOPER)
+		cdev->private->state = DEV_STATE_NOT_OPER;
+	else
+		cdev->private->state = DEV_STATE_OFFLINE;
+	wake_up(&cdev->private->wait_q);
+}
+
+static void
+ccw_device_quiesce_timeout(struct ccw_device *cdev, enum dev_event dev_event)
+{
+	int ret;
+
+	ret = ccw_device_cancel_halt_clear(cdev);
+	switch (ret) {
+	case 0:
+		cdev->private->state = DEV_STATE_OFFLINE;
+		wake_up(&cdev->private->wait_q);
+		break;
+	case -ENODEV:
+		cdev->private->state = DEV_STATE_NOT_OPER;
+		wake_up(&cdev->private->wait_q);
+		break;
+	default:
+		ccw_device_set_timeout(cdev, HZ/10);
+	}
+}
+
+/*
+ * No operation action. This is used e.g. to ignore a timeout event in
+ * state offline.
+ */
+static void
+ccw_device_nop(struct ccw_device *cdev, enum dev_event dev_event)
+{
+}
+
+/*
+ * Bug operation action. 
+ */
+static void
+ccw_device_bug(struct ccw_device *cdev, enum dev_event dev_event)
+{
+	printk(KERN_EMERG "dev_jumptable[%i][%i] == NULL\n",
+	       cdev->private->state, dev_event);
+	BUG();
+}
+
+/*
+ * device statemachine
+ */
+fsm_func_t *dev_jumptable[NR_DEV_STATES][NR_DEV_EVENTS] = {
+	[DEV_STATE_NOT_OPER] = {
+		[DEV_EVENT_NOTOPER]	= ccw_device_nop,
+		[DEV_EVENT_INTERRUPT]	= ccw_device_bug,
+		[DEV_EVENT_TIMEOUT]	= ccw_device_nop,
+		[DEV_EVENT_VERIFY]	= ccw_device_nop,
+	},
+	[DEV_STATE_SENSE_PGID] = {
+		[DEV_EVENT_NOTOPER]	= ccw_device_online_notoper,
+		[DEV_EVENT_INTERRUPT]	= ccw_device_sense_pgid_irq,
+		[DEV_EVENT_TIMEOUT]	= ccw_device_onoff_timeout,
+		[DEV_EVENT_VERIFY]	= ccw_device_nop,
+	},
+	[DEV_STATE_SENSE_ID] = {
+		[DEV_EVENT_NOTOPER]	= ccw_device_recog_notoper,
+		[DEV_EVENT_INTERRUPT]	= ccw_device_sense_id_irq,
+		[DEV_EVENT_TIMEOUT]	= ccw_device_recog_timeout,
+		[DEV_EVENT_VERIFY]	= ccw_device_nop,
+	},
+	[DEV_STATE_OFFLINE] = {
+		[DEV_EVENT_NOTOPER]	= ccw_device_offline_notoper,
+		[DEV_EVENT_INTERRUPT]	= ccw_device_offline_irq,
+		[DEV_EVENT_TIMEOUT]	= ccw_device_nop,
+		[DEV_EVENT_VERIFY]	= ccw_device_nop,
+	},
+	[DEV_STATE_VERIFY] = {
+		[DEV_EVENT_NOTOPER]	= ccw_device_online_notoper,
+		[DEV_EVENT_INTERRUPT]	= ccw_device_verify_irq,
+		[DEV_EVENT_TIMEOUT]	= ccw_device_onoff_timeout,
+		[DEV_EVENT_VERIFY]	= ccw_device_nop,
+	},
+	[DEV_STATE_ONLINE] = {
+		[DEV_EVENT_NOTOPER]	= ccw_device_online_notoper,
+		[DEV_EVENT_INTERRUPT]	= ccw_device_irq,
+		[DEV_EVENT_TIMEOUT]	= ccw_device_online_timeout,
+		[DEV_EVENT_VERIFY]	= ccw_device_online_verify,
+	},
+	[DEV_STATE_W4SENSE] = {
+		[DEV_EVENT_NOTOPER]	= ccw_device_online_notoper,
+		[DEV_EVENT_INTERRUPT]	= ccw_device_w4sense,
+		[DEV_EVENT_TIMEOUT]	= ccw_device_nop,
+		[DEV_EVENT_VERIFY]	= ccw_device_online_verify,
+	},
+	[DEV_STATE_DISBAND_PGID] = {
+		[DEV_EVENT_NOTOPER]	= ccw_device_online_notoper,
+		[DEV_EVENT_INTERRUPT]	= ccw_device_disband_irq,
+		[DEV_EVENT_TIMEOUT]	= ccw_device_onoff_timeout,
+		[DEV_EVENT_VERIFY]	= ccw_device_nop,
+	},
+	[DEV_STATE_BOXED] = {
+		[DEV_EVENT_NOTOPER]	= ccw_device_offline_notoper,
+		[DEV_EVENT_INTERRUPT]	= ccw_device_stlck_done,
+		[DEV_EVENT_TIMEOUT]	= ccw_device_stlck_done,
+		[DEV_EVENT_VERIFY]	= ccw_device_nop,
+	},
+	/* states to wait for i/o completion before doing something */
+	[DEV_STATE_CLEAR_VERIFY] = {
+		[DEV_EVENT_NOTOPER]     = ccw_device_online_notoper,
+		[DEV_EVENT_INTERRUPT]   = ccw_device_clear_verify,
+		[DEV_EVENT_TIMEOUT]	= ccw_device_nop,
+		[DEV_EVENT_VERIFY]	= ccw_device_nop,
+	},
+	[DEV_STATE_TIMEOUT_KILL] = {
+		[DEV_EVENT_NOTOPER]	= ccw_device_online_notoper,
+		[DEV_EVENT_INTERRUPT]	= ccw_device_killing_irq,
+		[DEV_EVENT_TIMEOUT]	= ccw_device_killing_timeout,
+		[DEV_EVENT_VERIFY]	= ccw_device_nop, //FIXME
+	},
+	[DEV_STATE_WAIT4IO] = {
+		[DEV_EVENT_NOTOPER]	= ccw_device_online_notoper,
+		[DEV_EVENT_INTERRUPT]	= ccw_device_wait4io_irq,
+		[DEV_EVENT_TIMEOUT]	= ccw_device_wait4io_timeout,
+		[DEV_EVENT_VERIFY]	= ccw_device_wait4io_verify,
+	},
+	[DEV_STATE_QUIESCE] = {
+		[DEV_EVENT_NOTOPER]	= ccw_device_quiesce_done,
+		[DEV_EVENT_INTERRUPT]	= ccw_device_quiesce_done,
+		[DEV_EVENT_TIMEOUT]	= ccw_device_quiesce_timeout,
+		[DEV_EVENT_VERIFY]	= ccw_device_nop,
+	},
+	/* special states for devices gone not operational */
+	[DEV_STATE_DISCONNECTED] = {
+		[DEV_EVENT_NOTOPER]	= ccw_device_nop,
+		[DEV_EVENT_INTERRUPT]	= ccw_device_start_id,
+		[DEV_EVENT_TIMEOUT]	= ccw_device_bug,
+		[DEV_EVENT_VERIFY]	= ccw_device_nop,
+	},
+	[DEV_STATE_DISCONNECTED_SENSE_ID] = {
+		[DEV_EVENT_NOTOPER]	= ccw_device_recog_notoper,
+		[DEV_EVENT_INTERRUPT]	= ccw_device_sense_id_irq,
+		[DEV_EVENT_TIMEOUT]	= ccw_device_recog_timeout,
+		[DEV_EVENT_VERIFY]	= ccw_device_nop,
+	},
+	[DEV_STATE_CMFCHANGE] = {
+		[DEV_EVENT_NOTOPER]	= ccw_device_change_cmfstate,
+		[DEV_EVENT_INTERRUPT]	= ccw_device_change_cmfstate,
+		[DEV_EVENT_TIMEOUT]	= ccw_device_change_cmfstate,
+		[DEV_EVENT_VERIFY]	= ccw_device_change_cmfstate,
+	},
+};
+
+/*
+ * io_subchannel_irq is called for "real" interrupts or for status
+ * pending conditions on msch.
+ */
+void
+io_subchannel_irq (struct device *pdev)
+{
+	struct ccw_device *cdev;
+
+	cdev = to_subchannel(pdev)->dev.driver_data;
+
+	CIO_TRACE_EVENT (3, "IRQ");
+	CIO_TRACE_EVENT (3, pdev->bus_id);
+	if (cdev)
+		dev_fsm_event(cdev, DEV_EVENT_INTERRUPT);
+}
+
+EXPORT_SYMBOL_GPL(ccw_device_set_timeout);
diff --git a/drivers/s390/cio/device_id.c b/drivers/s390/cio/device_id.c
new file mode 100644
index 0000000..0e68fb5
--- /dev/null
+++ b/drivers/s390/cio/device_id.c
@@ -0,0 +1,355 @@
+/*
+ * drivers/s390/cio/device_id.c
+ *
+ *    Copyright (C) 2002 IBM Deutschland Entwicklung GmbH,
+ *			 IBM Corporation
+ *    Author(s): Cornelia Huck(cohuck@de.ibm.com)
+ *		 Martin Schwidefsky (schwidefsky@de.ibm.com)
+ *
+ * Sense ID functions.
+ */
+
+#include <linux/module.h>
+#include <linux/config.h>
+#include <linux/init.h>
+
+#include <asm/ccwdev.h>
+#include <asm/delay.h>
+#include <asm/cio.h>
+#include <asm/lowcore.h>
+
+#include "cio.h"
+#include "cio_debug.h"
+#include "css.h"
+#include "device.h"
+#include "ioasm.h"
+
+/*
+ * diag210 is used under VM to get information about a virtual device
+ */
+#ifdef CONFIG_ARCH_S390X
+int
+diag210(struct diag210 * addr)
+{
+	/*
+	 * diag 210 needs its data below the 2GB border, so we
+	 * use a static data area to be sure
+	 */
+	static struct diag210 diag210_tmp;
+	static DEFINE_SPINLOCK(diag210_lock);
+	unsigned long flags;
+	int ccode;
+
+	spin_lock_irqsave(&diag210_lock, flags);
+	diag210_tmp = *addr;
+
+	asm volatile (
+		"   lhi	  %0,-1\n"
+		"   sam31\n"
+		"   diag  %1,0,0x210\n"
+		"0: ipm	  %0\n"
+		"   srl	  %0,28\n"
+		"1: sam64\n"
+		".section __ex_table,\"a\"\n"
+		"    .align 8\n"
+		"    .quad 0b,1b\n"
+		".previous"
+		: "=&d" (ccode) : "a" (__pa(&diag210_tmp)) : "cc", "memory" );
+
+	*addr = diag210_tmp;
+	spin_unlock_irqrestore(&diag210_lock, flags);
+
+	return ccode;
+}
+#else
+int
+diag210(struct diag210 * addr)
+{
+	int ccode;
+
+	asm volatile (
+		"   lhi	  %0,-1\n"
+		"   diag  %1,0,0x210\n"
+		"0: ipm	  %0\n"
+		"   srl	  %0,28\n"
+		"1:\n"
+		".section __ex_table,\"a\"\n"
+		"    .align 4\n"
+		"    .long 0b,1b\n"
+		".previous"
+		: "=&d" (ccode) : "a" (__pa(addr)) : "cc", "memory" );
+
+	return ccode;
+}
+#endif
+
+/*
+ * Input :
+ *   devno - device number
+ *   ps	   - pointer to sense ID data area
+ * Output : none
+ */
+static void
+VM_virtual_device_info (__u16 devno, struct senseid *ps)
+{
+	static struct {
+		int vrdcvcla, vrdcvtyp, cu_type;
+	} vm_devices[] = {
+		{ 0x08, 0x01, 0x3480 },
+		{ 0x08, 0x02, 0x3430 },
+		{ 0x08, 0x10, 0x3420 },
+		{ 0x08, 0x42, 0x3424 },
+		{ 0x08, 0x44, 0x9348 },
+		{ 0x08, 0x81, 0x3490 },
+		{ 0x08, 0x82, 0x3422 },
+		{ 0x10, 0x41, 0x1403 },
+		{ 0x10, 0x42, 0x3211 },
+		{ 0x10, 0x43, 0x3203 },
+		{ 0x10, 0x45, 0x3800 },
+		{ 0x10, 0x47, 0x3262 },
+		{ 0x10, 0x48, 0x3820 },
+		{ 0x10, 0x49, 0x3800 },
+		{ 0x10, 0x4a, 0x4245 },
+		{ 0x10, 0x4b, 0x4248 },
+		{ 0x10, 0x4d, 0x3800 },
+		{ 0x10, 0x4e, 0x3820 },
+		{ 0x10, 0x4f, 0x3820 },
+		{ 0x10, 0x82, 0x2540 },
+		{ 0x10, 0x84, 0x3525 },
+		{ 0x20, 0x81, 0x2501 },
+		{ 0x20, 0x82, 0x2540 },
+		{ 0x20, 0x84, 0x3505 },
+		{ 0x40, 0x01, 0x3278 },
+		{ 0x40, 0x04, 0x3277 },
+		{ 0x40, 0x80, 0x2250 },
+		{ 0x40, 0xc0, 0x5080 },
+		{ 0x80, 0x00, 0x3215 },
+	};
+	struct diag210 diag_data;
+	int ccode, i;
+
+	CIO_TRACE_EVENT (4, "VMvdinf");
+
+	diag_data = (struct diag210) {
+		.vrdcdvno = devno,
+		.vrdclen = sizeof (diag_data),
+	};
+
+	ccode = diag210 (&diag_data);
+	ps->reserved = 0xff;
+
+	/* Special case for bloody osa devices. */
+	if (diag_data.vrdcvcla == 0x02 &&
+	    diag_data.vrdcvtyp == 0x20) {
+		ps->cu_type = 0x3088;
+		ps->cu_model = 0x60;
+		return;
+	}
+	for (i = 0; i < sizeof(vm_devices) / sizeof(vm_devices[0]); i++)
+		if (diag_data.vrdcvcla == vm_devices[i].vrdcvcla &&
+		    diag_data.vrdcvtyp == vm_devices[i].vrdcvtyp) {
+			ps->cu_type = vm_devices[i].cu_type;
+			return;
+		}
+	CIO_MSG_EVENT(0, "DIAG X'210' for device %04X returned (cc = %d):"
+		      "vdev class : %02X, vdev type : %04X \n ...  "
+		      "rdev class : %02X, rdev type : %04X, "
+		      "rdev model: %02X\n",
+		      devno, ccode,
+		      diag_data.vrdcvcla, diag_data.vrdcvtyp,
+		      diag_data.vrdcrccl, diag_data.vrdccrty,
+		      diag_data.vrdccrmd);
+}
+
+/*
+ * Start Sense ID helper function.
+ * Try to obtain the 'control unit'/'device type' information
+ *  associated with the subchannel.
+ */
+static int
+__ccw_device_sense_id_start(struct ccw_device *cdev)
+{
+	struct subchannel *sch;
+	struct ccw1 *ccw;
+	int ret;
+
+	sch = to_subchannel(cdev->dev.parent);
+	/* Setup sense channel program. */
+	ccw = cdev->private->iccws;
+	if (sch->schib.pmcw.pim != 0x80) {
+		/* more than one path installed. */
+		ccw->cmd_code = CCW_CMD_SUSPEND_RECONN;
+		ccw->cda = 0;
+		ccw->count = 0;
+		ccw->flags = CCW_FLAG_SLI | CCW_FLAG_CC;
+		ccw++;
+	}
+	ccw->cmd_code = CCW_CMD_SENSE_ID;
+	ccw->cda = (__u32) __pa (&cdev->private->senseid);
+	ccw->count = sizeof (struct senseid);
+	ccw->flags = CCW_FLAG_SLI;
+
+	/* Reset device status. */
+	memset(&cdev->private->irb, 0, sizeof(struct irb));
+
+	/* Try on every path. */
+	ret = -ENODEV;
+	while (cdev->private->imask != 0) {
+		if ((sch->opm & cdev->private->imask) != 0 &&
+		    cdev->private->iretry > 0) {
+			cdev->private->iretry--;
+			ret = cio_start (sch, cdev->private->iccws,
+					 cdev->private->imask);
+			/* ret is 0, -EBUSY, -EACCES or -ENODEV */
+			if (ret != -EACCES)
+				return ret;
+		}
+		cdev->private->imask >>= 1;
+		cdev->private->iretry = 5;
+	}
+	return ret;
+}
+
+void
+ccw_device_sense_id_start(struct ccw_device *cdev)
+{
+	int ret;
+
+	memset (&cdev->private->senseid, 0, sizeof (struct senseid));
+	cdev->private->senseid.cu_type = 0xFFFF;
+	cdev->private->imask = 0x80;
+	cdev->private->iretry = 5;
+	ret = __ccw_device_sense_id_start(cdev);
+	if (ret && ret != -EBUSY)
+		ccw_device_sense_id_done(cdev, ret);
+}
+
+/*
+ * Called from interrupt context to check if a valid answer
+ * to Sense ID was received.
+ */
+static int
+ccw_device_check_sense_id(struct ccw_device *cdev)
+{
+	struct subchannel *sch;
+	struct irb *irb;
+
+	sch = to_subchannel(cdev->dev.parent);
+	irb = &cdev->private->irb;
+	/* Did we get a proper answer ? */
+	if (cdev->private->senseid.cu_type != 0xFFFF && 
+	    cdev->private->senseid.reserved == 0xFF) {
+		if (irb->scsw.count < sizeof (struct senseid) - 8)
+			cdev->private->flags.esid = 1;
+		return 0; /* Success */
+	}
+	/* Check the error cases. */
+	if (irb->scsw.fctl & (SCSW_FCTL_HALT_FUNC | SCSW_FCTL_CLEAR_FUNC))
+		return -ETIME;
+	if (irb->esw.esw0.erw.cons && (irb->ecw[0] & SNS0_CMD_REJECT)) {
+		/*
+		 * if the device doesn't support the SenseID
+		 *  command further retries wouldn't help ...
+		 * NB: We don't check here for intervention required like we
+		 *     did before, because tape devices with no tape inserted
+		 *     may present this status *in conjunction with* the
+		 *     sense id information. So, for intervention required,
+		 *     we use the "whack it until it talks" strategy...
+		 */
+		CIO_MSG_EVENT(2, "SenseID : device %04x on Subchannel %04x "
+			      "reports cmd reject\n",
+			      cdev->private->devno, sch->irq);
+		return -EOPNOTSUPP;
+	}
+	if (irb->esw.esw0.erw.cons) {
+		CIO_MSG_EVENT(2, "SenseID : UC on dev %04x, "
+			      "lpum %02X, cnt %02d, sns :"
+			      " %02X%02X%02X%02X %02X%02X%02X%02X ...\n",
+			      cdev->private->devno,
+			      irb->esw.esw0.sublog.lpum,
+			      irb->esw.esw0.erw.scnt,
+			      irb->ecw[0], irb->ecw[1],
+			      irb->ecw[2], irb->ecw[3],
+			      irb->ecw[4], irb->ecw[5],
+			      irb->ecw[6], irb->ecw[7]);
+		return -EAGAIN;
+	}
+	if (irb->scsw.cc == 3) {
+		if ((sch->orb.lpm &
+		     sch->schib.pmcw.pim & sch->schib.pmcw.pam) != 0)
+			CIO_MSG_EVENT(2, "SenseID : path %02X for device %04x on"
+				      " subchannel %04x is 'not operational'\n",
+				      sch->orb.lpm, cdev->private->devno,
+				      sch->irq);
+		return -EACCES;
+	}
+	/* Hmm, whatever happened, try again. */
+	CIO_MSG_EVENT(2, "SenseID : start_IO() for device %04x on "
+		      "subchannel %04x returns status %02X%02X\n",
+		      cdev->private->devno, sch->irq,
+		      irb->scsw.dstat, irb->scsw.cstat);
+	return -EAGAIN;
+}
+
+/*
+ * Got interrupt for Sense ID.
+ */
+void
+ccw_device_sense_id_irq(struct ccw_device *cdev, enum dev_event dev_event)
+{
+	struct subchannel *sch;
+	struct irb *irb;
+	int ret;
+
+	sch = to_subchannel(cdev->dev.parent);
+	irb = (struct irb *) __LC_IRB;
+	/* Retry sense id, if needed. */
+	if (irb->scsw.stctl ==
+	    (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) {
+		if ((irb->scsw.cc == 1) || !irb->scsw.actl) {
+			ret = __ccw_device_sense_id_start(cdev);
+			if (ret && ret != -EBUSY)
+				ccw_device_sense_id_done(cdev, ret);
+		}
+		return;
+	}
+	if (ccw_device_accumulate_and_sense(cdev, irb) != 0)
+		return;
+	ret = ccw_device_check_sense_id(cdev);
+	memset(&cdev->private->irb, 0, sizeof(struct irb));
+	switch (ret) {
+	/* 0, -ETIME, -EOPNOTSUPP, -EAGAIN or -EACCES */
+	case 0:			/* Sense id succeeded. */
+	case -ETIME:		/* Sense id stopped by timeout. */
+		ccw_device_sense_id_done(cdev, ret);
+		break;
+	case -EACCES:		/* channel is not operational. */
+		sch->lpm &= ~cdev->private->imask;
+		cdev->private->imask >>= 1;
+		cdev->private->iretry = 5;
+		/* fall through. */
+	case -EAGAIN:		/* try again. */
+		ret = __ccw_device_sense_id_start(cdev);
+		if (ret == 0 || ret == -EBUSY)
+			break;
+		/* fall through. */
+	default:		/* Sense ID failed. Try asking VM. */
+		if (MACHINE_IS_VM) {
+			VM_virtual_device_info (cdev->private->devno,
+						&cdev->private->senseid);
+			if (cdev->private->senseid.cu_type != 0xFFFF) {
+				/* Got the device information from VM. */
+				ccw_device_sense_id_done(cdev, 0);
+				return;
+			}
+		}
+		/*
+		 * If we can't couldn't identify the device type we
+		 *  consider the device "not operational".
+		 */
+		ccw_device_sense_id_done(cdev, -ENODEV);
+		break;
+	}
+}
+
+EXPORT_SYMBOL(diag210);
diff --git a/drivers/s390/cio/device_ops.c b/drivers/s390/cio/device_ops.c
new file mode 100644
index 0000000..11e260e
--- /dev/null
+++ b/drivers/s390/cio/device_ops.c
@@ -0,0 +1,603 @@
+/*
+ *  drivers/s390/cio/device_ops.c
+ *
+ *   $Revision: 1.55 $
+ *
+ *    Copyright (C) 2002 IBM Deutschland Entwicklung GmbH,
+ *			 IBM Corporation
+ *    Author(s): Martin Schwidefsky (schwidefsky@de.ibm.com)
+ *               Cornelia Huck (cohuck@de.ibm.com)
+ */
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/list.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+
+#include <asm/ccwdev.h>
+#include <asm/idals.h>
+#include <asm/qdio.h>
+
+#include "cio.h"
+#include "cio_debug.h"
+#include "css.h"
+#include "chsc.h"
+#include "device.h"
+#include "qdio.h"
+
+int
+ccw_device_set_options(struct ccw_device *cdev, unsigned long flags)
+{
+       /*
+	* The flag usage is mutal exclusive ...
+	*/
+	if ((flags & CCWDEV_EARLY_NOTIFICATION) &&
+	    (flags & CCWDEV_REPORT_ALL))
+		return -EINVAL;
+	cdev->private->options.fast = (flags & CCWDEV_EARLY_NOTIFICATION) != 0;
+	cdev->private->options.repall = (flags & CCWDEV_REPORT_ALL) != 0;
+	cdev->private->options.pgroup = (flags & CCWDEV_DO_PATHGROUP) != 0;
+	cdev->private->options.force = (flags & CCWDEV_ALLOW_FORCE) != 0;
+	return 0;
+}
+
+int
+ccw_device_clear(struct ccw_device *cdev, unsigned long intparm)
+{
+	struct subchannel *sch;
+	int ret;
+
+	if (!cdev)
+		return -ENODEV;
+	if (cdev->private->state == DEV_STATE_NOT_OPER)
+		return -ENODEV;
+	if (cdev->private->state != DEV_STATE_ONLINE &&
+	    cdev->private->state != DEV_STATE_WAIT4IO &&
+	    cdev->private->state != DEV_STATE_W4SENSE)
+		return -EINVAL;
+	sch = to_subchannel(cdev->dev.parent);
+	if (!sch)
+		return -ENODEV;
+	ret = cio_clear(sch);
+	if (ret == 0)
+		cdev->private->intparm = intparm;
+	return ret;
+}
+
+int
+ccw_device_start_key(struct ccw_device *cdev, struct ccw1 *cpa,
+		     unsigned long intparm, __u8 lpm, __u8 key,
+		     unsigned long flags)
+{
+	struct subchannel *sch;
+	int ret;
+
+	if (!cdev)
+		return -ENODEV;
+	sch = to_subchannel(cdev->dev.parent);
+	if (!sch)
+		return -ENODEV;
+	if (cdev->private->state == DEV_STATE_NOT_OPER)
+		return -ENODEV;
+	if (cdev->private->state == DEV_STATE_VERIFY) {
+		/* Remember to fake irb when finished. */
+		if (!cdev->private->flags.fake_irb) {
+			cdev->private->flags.fake_irb = 1;
+			cdev->private->intparm = intparm;
+			return 0;
+		} else
+			/* There's already a fake I/O around. */
+			return -EBUSY;
+	}
+	if (cdev->private->state != DEV_STATE_ONLINE ||
+	    ((sch->schib.scsw.stctl & SCSW_STCTL_PRIM_STATUS) &&
+	     !(sch->schib.scsw.stctl & SCSW_STCTL_SEC_STATUS)) ||
+	    cdev->private->flags.doverify)
+		return -EBUSY;
+	ret = cio_set_options (sch, flags);
+	if (ret)
+		return ret;
+	ret = cio_start_key (sch, cpa, lpm, key);
+	if (ret == 0)
+		cdev->private->intparm = intparm;
+	return ret;
+}
+
+
+int
+ccw_device_start_timeout_key(struct ccw_device *cdev, struct ccw1 *cpa,
+			     unsigned long intparm, __u8 lpm, __u8 key,
+			     unsigned long flags, int expires)
+{
+	int ret;
+
+	if (!cdev)
+		return -ENODEV;
+	ccw_device_set_timeout(cdev, expires);
+	ret = ccw_device_start_key(cdev, cpa, intparm, lpm, key, flags);
+	if (ret != 0)
+		ccw_device_set_timeout(cdev, 0);
+	return ret;
+}
+
+int
+ccw_device_start(struct ccw_device *cdev, struct ccw1 *cpa,
+		 unsigned long intparm, __u8 lpm, unsigned long flags)
+{
+	return ccw_device_start_key(cdev, cpa, intparm, lpm,
+				    default_storage_key, flags);
+}
+
+int
+ccw_device_start_timeout(struct ccw_device *cdev, struct ccw1 *cpa,
+			 unsigned long intparm, __u8 lpm, unsigned long flags,
+			 int expires)
+{
+	return ccw_device_start_timeout_key(cdev, cpa, intparm, lpm,
+					    default_storage_key, flags,
+					    expires);
+}
+
+
+int
+ccw_device_halt(struct ccw_device *cdev, unsigned long intparm)
+{
+	struct subchannel *sch;
+	int ret;
+
+	if (!cdev)
+		return -ENODEV;
+	if (cdev->private->state == DEV_STATE_NOT_OPER)
+		return -ENODEV;
+	if (cdev->private->state != DEV_STATE_ONLINE &&
+	    cdev->private->state != DEV_STATE_WAIT4IO &&
+	    cdev->private->state != DEV_STATE_W4SENSE)
+		return -EINVAL;
+	sch = to_subchannel(cdev->dev.parent);
+	if (!sch)
+		return -ENODEV;
+	ret = cio_halt(sch);
+	if (ret == 0)
+		cdev->private->intparm = intparm;
+	return ret;
+}
+
+int
+ccw_device_resume(struct ccw_device *cdev)
+{
+	struct subchannel *sch;
+
+	if (!cdev)
+		return -ENODEV;
+	sch = to_subchannel(cdev->dev.parent);
+	if (!sch)
+		return -ENODEV;
+	if (cdev->private->state == DEV_STATE_NOT_OPER)
+		return -ENODEV;
+	if (cdev->private->state != DEV_STATE_ONLINE ||
+	    !(sch->schib.scsw.actl & SCSW_ACTL_SUSPENDED))
+		return -EINVAL;
+	return cio_resume(sch);
+}
+
+/*
+ * Pass interrupt to device driver.
+ */
+int
+ccw_device_call_handler(struct ccw_device *cdev)
+{
+	struct subchannel *sch;
+	unsigned int stctl;
+	int ending_status;
+
+	sch = to_subchannel(cdev->dev.parent);
+
+	/*
+	 * we allow for the device action handler if .
+	 *  - we received ending status
+	 *  - the action handler requested to see all interrupts
+	 *  - we received an intermediate status
+	 *  - fast notification was requested (primary status)
+	 *  - unsolicited interrupts
+	 */
+	stctl = cdev->private->irb.scsw.stctl;
+	ending_status = (stctl & SCSW_STCTL_SEC_STATUS) ||
+		(stctl == (SCSW_STCTL_ALERT_STATUS | SCSW_STCTL_STATUS_PEND)) ||
+		(stctl == SCSW_STCTL_STATUS_PEND);
+	if (!ending_status &&
+	    !cdev->private->options.repall &&
+	    !(stctl & SCSW_STCTL_INTER_STATUS) &&
+	    !(cdev->private->options.fast &&
+	      (stctl & SCSW_STCTL_PRIM_STATUS)))
+		return 0;
+
+	/*
+	 * Now we are ready to call the device driver interrupt handler.
+	 */
+	if (cdev->handler)
+		cdev->handler(cdev, cdev->private->intparm,
+			      &cdev->private->irb);
+
+	/*
+	 * Clear the old and now useless interrupt response block.
+	 */
+	memset(&cdev->private->irb, 0, sizeof(struct irb));
+
+	return 1;
+}
+
+/*
+ * Search for CIW command in extended sense data.
+ */
+struct ciw *
+ccw_device_get_ciw(struct ccw_device *cdev, __u32 ct)
+{
+	int ciw_cnt;
+
+	if (cdev->private->flags.esid == 0)
+		return NULL;
+	for (ciw_cnt = 0; ciw_cnt < MAX_CIWS; ciw_cnt++)
+		if (cdev->private->senseid.ciw[ciw_cnt].ct == ct)
+			return cdev->private->senseid.ciw + ciw_cnt;
+	return NULL;
+}
+
+__u8
+ccw_device_get_path_mask(struct ccw_device *cdev)
+{
+	struct subchannel *sch;
+
+	sch = to_subchannel(cdev->dev.parent);
+	if (!sch)
+		return 0;
+	else
+		return sch->vpm;
+}
+
+static void
+ccw_device_wake_up(struct ccw_device *cdev, unsigned long ip, struct irb *irb)
+{
+	if (!ip)
+		/* unsolicited interrupt */
+		return;
+
+	/* Abuse intparm for error reporting. */
+	if (IS_ERR(irb))
+		cdev->private->intparm = -EIO;
+	else if ((irb->scsw.dstat !=
+		  (DEV_STAT_CHN_END|DEV_STAT_DEV_END)) ||
+		 (irb->scsw.cstat != 0)) {
+		/*
+		 * We didn't get channel end / device end. Check if path
+		 * verification has been started; we can retry after it has
+		 * finished. We also retry unit checks except for command reject
+		 * or intervention required.
+		 */
+		 if (cdev->private->flags.doverify ||
+			 cdev->private->state == DEV_STATE_VERIFY)
+			 cdev->private->intparm = -EAGAIN;
+		 if ((irb->scsw.dstat & DEV_STAT_UNIT_CHECK) &&
+		     !(irb->ecw[0] &
+		       (SNS0_CMD_REJECT | SNS0_INTERVENTION_REQ)))
+			 cdev->private->intparm = -EAGAIN;
+		 else
+			 cdev->private->intparm = -EIO;
+			 
+	} else
+		cdev->private->intparm = 0;
+	wake_up(&cdev->private->wait_q);
+}
+
+static inline int
+__ccw_device_retry_loop(struct ccw_device *cdev, struct ccw1 *ccw, long magic, __u8 lpm)
+{
+	int ret;
+	struct subchannel *sch;
+
+	sch = to_subchannel(cdev->dev.parent);
+	do {
+		ret = cio_start (sch, ccw, lpm);
+		if ((ret == -EBUSY) || (ret == -EACCES)) {
+			/* Try again later. */
+			spin_unlock_irq(&sch->lock);
+			msleep(10);
+			spin_lock_irq(&sch->lock);
+			continue;
+		}
+		if (ret != 0)
+			/* Non-retryable error. */
+			break;
+		/* Wait for end of request. */
+		cdev->private->intparm = magic;
+		spin_unlock_irq(&sch->lock);
+		wait_event(cdev->private->wait_q,
+			   (cdev->private->intparm == -EIO) ||
+			   (cdev->private->intparm == -EAGAIN) ||
+			   (cdev->private->intparm == 0));
+		spin_lock_irq(&sch->lock);
+		/* Check at least for channel end / device end */
+		if (cdev->private->intparm == -EIO) {
+			/* Non-retryable error. */
+			ret = -EIO;
+			break;
+		}
+		if (cdev->private->intparm == 0)
+			/* Success. */
+			break;
+		/* Try again later. */
+		spin_unlock_irq(&sch->lock);
+		msleep(10);
+		spin_lock_irq(&sch->lock);
+	} while (1);
+
+	return ret;
+}
+
+/**
+ * read_dev_chars() - read device characteristics
+ * @param cdev   target ccw device
+ * @param buffer pointer to buffer for rdc data
+ * @param length size of rdc data
+ * @returns 0 for success, negative error value on failure
+ *
+ * Context:
+ *   called for online device, lock not held
+ **/
+int
+read_dev_chars (struct ccw_device *cdev, void **buffer, int length)
+{
+	void (*handler)(struct ccw_device *, unsigned long, struct irb *);
+	struct subchannel *sch;
+	int ret;
+	struct ccw1 *rdc_ccw;
+
+	if (!cdev)
+		return -ENODEV;
+	if (!buffer || !length)
+		return -EINVAL;
+	sch = to_subchannel(cdev->dev.parent);
+
+	CIO_TRACE_EVENT (4, "rddevch");
+	CIO_TRACE_EVENT (4, sch->dev.bus_id);
+
+	rdc_ccw = kmalloc(sizeof(struct ccw1), GFP_KERNEL | GFP_DMA);
+	if (!rdc_ccw)
+		return -ENOMEM;
+	memset(rdc_ccw, 0, sizeof(struct ccw1));
+	rdc_ccw->cmd_code = CCW_CMD_RDC;
+	rdc_ccw->count = length;
+	rdc_ccw->flags = CCW_FLAG_SLI;
+	ret = set_normalized_cda (rdc_ccw, (*buffer));
+	if (ret != 0) {
+		kfree(rdc_ccw);
+		return ret;
+	}
+
+	spin_lock_irq(&sch->lock);
+	/* Save interrupt handler. */
+	handler = cdev->handler;
+	/* Temporarily install own handler. */
+	cdev->handler = ccw_device_wake_up;
+	if (cdev->private->state != DEV_STATE_ONLINE)
+		ret = -ENODEV;
+	else if (((sch->schib.scsw.stctl & SCSW_STCTL_PRIM_STATUS) &&
+		  !(sch->schib.scsw.stctl & SCSW_STCTL_SEC_STATUS)) ||
+		 cdev->private->flags.doverify)
+		ret = -EBUSY;
+	else
+		/* 0x00D9C4C3 == ebcdic "RDC" */
+		ret = __ccw_device_retry_loop(cdev, rdc_ccw, 0x00D9C4C3, 0);
+
+	/* Restore interrupt handler. */
+	cdev->handler = handler;
+	spin_unlock_irq(&sch->lock);
+
+	clear_normalized_cda (rdc_ccw);
+	kfree(rdc_ccw);
+
+	return ret;
+}
+
+/*
+ *  Read Configuration data using path mask
+ */
+int
+read_conf_data_lpm (struct ccw_device *cdev, void **buffer, int *length, __u8 lpm)
+{
+	void (*handler)(struct ccw_device *, unsigned long, struct irb *);
+	struct subchannel *sch;
+	struct ciw *ciw;
+	char *rcd_buf;
+	int ret;
+	struct ccw1 *rcd_ccw;
+
+	if (!cdev)
+		return -ENODEV;
+	if (!buffer || !length)
+		return -EINVAL;
+	sch = to_subchannel(cdev->dev.parent);
+
+	CIO_TRACE_EVENT (4, "rdconf");
+	CIO_TRACE_EVENT (4, sch->dev.bus_id);
+
+	/*
+	 * scan for RCD command in extended SenseID data
+	 */
+	ciw = ccw_device_get_ciw(cdev, CIW_TYPE_RCD);
+	if (!ciw || ciw->cmd == 0)
+		return -EOPNOTSUPP;
+
+	rcd_ccw = kmalloc(sizeof(struct ccw1), GFP_KERNEL | GFP_DMA);
+	if (!rcd_ccw)
+		return -ENOMEM;
+	memset(rcd_ccw, 0, sizeof(struct ccw1));
+	rcd_buf = kmalloc(ciw->count, GFP_KERNEL | GFP_DMA);
+ 	if (!rcd_buf) {
+		kfree(rcd_ccw);
+		return -ENOMEM;
+	}
+ 	memset (rcd_buf, 0, ciw->count);
+	rcd_ccw->cmd_code = ciw->cmd;
+	rcd_ccw->cda = (__u32) __pa (rcd_buf);
+	rcd_ccw->count = ciw->count;
+	rcd_ccw->flags = CCW_FLAG_SLI;
+
+	spin_lock_irq(&sch->lock);
+	/* Save interrupt handler. */
+	handler = cdev->handler;
+	/* Temporarily install own handler. */
+	cdev->handler = ccw_device_wake_up;
+	if (cdev->private->state != DEV_STATE_ONLINE)
+		ret = -ENODEV;
+	else if (((sch->schib.scsw.stctl & SCSW_STCTL_PRIM_STATUS) &&
+		  !(sch->schib.scsw.stctl & SCSW_STCTL_SEC_STATUS)) ||
+		 cdev->private->flags.doverify)
+		ret = -EBUSY;
+	else
+		/* 0x00D9C3C4 == ebcdic "RCD" */
+		ret = __ccw_device_retry_loop(cdev, rcd_ccw, 0x00D9C3C4, lpm);
+
+	/* Restore interrupt handler. */
+	cdev->handler = handler;
+	spin_unlock_irq(&sch->lock);
+
+ 	/*
+ 	 * on success we update the user input parms
+ 	 */
+ 	if (ret) {
+ 		kfree (rcd_buf);
+ 		*buffer = NULL;
+ 		*length = 0;
+ 	} else {
+		*length = ciw->count;
+		*buffer = rcd_buf;
+	}
+	kfree(rcd_ccw);
+
+	return ret;
+}
+
+/*
+ *  Read Configuration data
+ */
+int
+read_conf_data (struct ccw_device *cdev, void **buffer, int *length)
+{
+	return read_conf_data_lpm (cdev, buffer, length, 0);
+}
+
+/*
+ * Try to break the lock on a boxed device.
+ */
+int
+ccw_device_stlck(struct ccw_device *cdev)
+{
+	void *buf, *buf2;
+	unsigned long flags;
+	struct subchannel *sch;
+	int ret;
+
+	if (!cdev)
+		return -ENODEV;
+
+	if (cdev->drv && !cdev->private->options.force)
+		return -EINVAL;
+
+	sch = to_subchannel(cdev->dev.parent);
+	
+	CIO_TRACE_EVENT(2, "stl lock");
+	CIO_TRACE_EVENT(2, cdev->dev.bus_id);
+
+	buf = kmalloc(32*sizeof(char), GFP_DMA|GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+	buf2 = kmalloc(32*sizeof(char), GFP_DMA|GFP_KERNEL);
+	if (!buf2) {
+		kfree(buf);
+		return -ENOMEM;
+	}
+	spin_lock_irqsave(&sch->lock, flags);
+	ret = cio_enable_subchannel(sch, 3);
+	if (ret)
+		goto out_unlock;
+	/*
+	 * Setup ccw. We chain an unconditional reserve and a release so we
+	 * only break the lock.
+	 */
+	cdev->private->iccws[0].cmd_code = CCW_CMD_STLCK;
+	cdev->private->iccws[0].cda = (__u32) __pa(buf);
+	cdev->private->iccws[0].count = 32;
+	cdev->private->iccws[0].flags = CCW_FLAG_CC;
+	cdev->private->iccws[1].cmd_code = CCW_CMD_RELEASE;
+	cdev->private->iccws[1].cda = (__u32) __pa(buf2);
+	cdev->private->iccws[1].count = 32;
+	cdev->private->iccws[1].flags = 0;
+	ret = cio_start(sch, cdev->private->iccws, 0);
+	if (ret) {
+		cio_disable_subchannel(sch); //FIXME: return code?
+		goto out_unlock;
+	}
+	cdev->private->irb.scsw.actl |= SCSW_ACTL_START_PEND;
+	spin_unlock_irqrestore(&sch->lock, flags);
+	wait_event(cdev->private->wait_q, cdev->private->irb.scsw.actl == 0);
+	spin_lock_irqsave(&sch->lock, flags);
+	cio_disable_subchannel(sch); //FIXME: return code?
+	if ((cdev->private->irb.scsw.dstat !=
+	     (DEV_STAT_CHN_END|DEV_STAT_DEV_END)) ||
+	    (cdev->private->irb.scsw.cstat != 0))
+		ret = -EIO;
+	/* Clear irb. */
+	memset(&cdev->private->irb, 0, sizeof(struct irb));
+out_unlock:
+	if (buf)
+		kfree(buf);
+	if (buf2)
+		kfree(buf2);
+	spin_unlock_irqrestore(&sch->lock, flags);
+	return ret;
+}
+
+void *
+ccw_device_get_chp_desc(struct ccw_device *cdev, int chp_no)
+{
+	struct subchannel *sch;
+
+	sch = to_subchannel(cdev->dev.parent);
+	return chsc_get_chp_desc(sch, chp_no);
+}
+
+// FIXME: these have to go:
+
+int
+_ccw_device_get_subchannel_number(struct ccw_device *cdev)
+{
+	return cdev->private->irq;
+}
+
+int
+_ccw_device_get_device_number(struct ccw_device *cdev)
+{
+	return cdev->private->devno;
+}
+
+
+MODULE_LICENSE("GPL");
+EXPORT_SYMBOL(ccw_device_set_options);
+EXPORT_SYMBOL(ccw_device_clear);
+EXPORT_SYMBOL(ccw_device_halt);
+EXPORT_SYMBOL(ccw_device_resume);
+EXPORT_SYMBOL(ccw_device_start_timeout);
+EXPORT_SYMBOL(ccw_device_start);
+EXPORT_SYMBOL(ccw_device_start_timeout_key);
+EXPORT_SYMBOL(ccw_device_start_key);
+EXPORT_SYMBOL(ccw_device_get_ciw);
+EXPORT_SYMBOL(ccw_device_get_path_mask);
+EXPORT_SYMBOL(read_conf_data);
+EXPORT_SYMBOL(read_dev_chars);
+EXPORT_SYMBOL(_ccw_device_get_subchannel_number);
+EXPORT_SYMBOL(_ccw_device_get_device_number);
+EXPORT_SYMBOL_GPL(ccw_device_get_chp_desc);
+EXPORT_SYMBOL_GPL(read_conf_data_lpm);
diff --git a/drivers/s390/cio/device_pgid.c b/drivers/s390/cio/device_pgid.c
new file mode 100644
index 0000000..0adac8a
--- /dev/null
+++ b/drivers/s390/cio/device_pgid.c
@@ -0,0 +1,448 @@
+/*
+ * drivers/s390/cio/device_pgid.c
+ *
+ *    Copyright (C) 2002 IBM Deutschland Entwicklung GmbH,
+ *			 IBM Corporation
+ *    Author(s): Cornelia Huck(cohuck@de.ibm.com)
+ *		 Martin Schwidefsky (schwidefsky@de.ibm.com)
+ *
+ * Path Group ID functions.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/init.h>
+
+#include <asm/ccwdev.h>
+#include <asm/cio.h>
+#include <asm/delay.h>
+#include <asm/lowcore.h>
+
+#include "cio.h"
+#include "cio_debug.h"
+#include "css.h"
+#include "device.h"
+
+/*
+ * Start Sense Path Group ID helper function. Used in ccw_device_recog
+ * and ccw_device_sense_pgid.
+ */
+static int
+__ccw_device_sense_pgid_start(struct ccw_device *cdev)
+{
+	struct subchannel *sch;
+	struct ccw1 *ccw;
+	int ret;
+
+	sch = to_subchannel(cdev->dev.parent);
+	/* Setup sense path group id channel program. */
+	ccw = cdev->private->iccws;
+	ccw->cmd_code = CCW_CMD_SENSE_PGID;
+	ccw->cda = (__u32) __pa (&cdev->private->pgid);
+	ccw->count = sizeof (struct pgid);
+	ccw->flags = CCW_FLAG_SLI;
+
+	/* Reset device status. */
+	memset(&cdev->private->irb, 0, sizeof(struct irb));
+	/* Try on every path. */
+	ret = -ENODEV;
+	while (cdev->private->imask != 0) {
+		/* Try every path multiple times. */
+		if (cdev->private->iretry > 0) {
+			cdev->private->iretry--;
+			ret = cio_start (sch, cdev->private->iccws, 
+					 cdev->private->imask);
+			/* ret is 0, -EBUSY, -EACCES or -ENODEV */
+			if (ret != -EACCES)
+				return ret;
+			CIO_MSG_EVENT(2, "SNID - Device %04x on Subchannel "
+				      "%04x, lpm %02X, became 'not "
+				      "operational'\n",
+				      cdev->private->devno, sch->irq,
+				      cdev->private->imask);
+
+		}
+		cdev->private->imask >>= 1;
+		cdev->private->iretry = 5;
+	}
+	return ret;
+}
+
+void
+ccw_device_sense_pgid_start(struct ccw_device *cdev)
+{
+	int ret;
+
+	cdev->private->state = DEV_STATE_SENSE_PGID;
+	cdev->private->imask = 0x80;
+	cdev->private->iretry = 5;
+	memset (&cdev->private->pgid, 0, sizeof (struct pgid));
+	ret = __ccw_device_sense_pgid_start(cdev);
+	if (ret && ret != -EBUSY)
+		ccw_device_sense_pgid_done(cdev, ret);
+}
+
+/*
+ * Called from interrupt context to check if a valid answer
+ * to Sense Path Group ID was received.
+ */
+static int
+__ccw_device_check_sense_pgid(struct ccw_device *cdev)
+{
+	struct subchannel *sch;
+	struct irb *irb;
+
+	sch = to_subchannel(cdev->dev.parent);
+	irb = &cdev->private->irb;
+	if (irb->scsw.fctl & (SCSW_FCTL_HALT_FUNC | SCSW_FCTL_CLEAR_FUNC))
+		return -ETIME;
+	if (irb->esw.esw0.erw.cons &&
+	    (irb->ecw[0]&(SNS0_CMD_REJECT|SNS0_INTERVENTION_REQ))) {
+		/*
+		 * If the device doesn't support the Sense Path Group ID
+		 *  command further retries wouldn't help ...
+		 */
+		return -EOPNOTSUPP;
+	}
+	if (irb->esw.esw0.erw.cons) {
+		CIO_MSG_EVENT(2, "SNID - device %04x, unit check, "
+			      "lpum %02X, cnt %02d, sns : "
+			      "%02X%02X%02X%02X %02X%02X%02X%02X ...\n",
+			      cdev->private->devno,
+			      irb->esw.esw0.sublog.lpum,
+			      irb->esw.esw0.erw.scnt,
+			      irb->ecw[0], irb->ecw[1],
+			      irb->ecw[2], irb->ecw[3],
+			      irb->ecw[4], irb->ecw[5],
+			      irb->ecw[6], irb->ecw[7]);
+		return -EAGAIN;
+	}
+	if (irb->scsw.cc == 3) {
+		CIO_MSG_EVENT(2, "SNID - Device %04x on Subchannel "
+			      "%04x, lpm %02X, became 'not operational'\n",
+			      cdev->private->devno, sch->irq, sch->orb.lpm);
+		return -EACCES;
+	}
+	if (cdev->private->pgid.inf.ps.state2 == SNID_STATE2_RESVD_ELSE) {
+		CIO_MSG_EVENT(2, "SNID - Device %04x on Subchannel %04x "
+			      "is reserved by someone else\n",
+			      cdev->private->devno, sch->irq);
+		return -EUSERS;
+	}
+	return 0;
+}
+
+/*
+ * Got interrupt for Sense Path Group ID.
+ */
+void
+ccw_device_sense_pgid_irq(struct ccw_device *cdev, enum dev_event dev_event)
+{
+	struct subchannel *sch;
+	struct irb *irb;
+	int ret;
+
+	irb = (struct irb *) __LC_IRB;
+	/* Retry sense pgid for cc=1. */
+	if (irb->scsw.stctl ==
+	    (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) {
+		if (irb->scsw.cc == 1) {
+			ret = __ccw_device_sense_pgid_start(cdev);
+			if (ret && ret != -EBUSY)
+				ccw_device_sense_pgid_done(cdev, ret);
+		}
+		return;
+	}
+	if (ccw_device_accumulate_and_sense(cdev, irb) != 0)
+		return;
+	sch = to_subchannel(cdev->dev.parent);
+	ret = __ccw_device_check_sense_pgid(cdev);
+	memset(&cdev->private->irb, 0, sizeof(struct irb));
+	switch (ret) {
+	/* 0, -ETIME, -EOPNOTSUPP, -EAGAIN, -EACCES or -EUSERS */
+	case 0:			/* Sense Path Group ID successful. */
+		if (cdev->private->pgid.inf.ps.state1 == SNID_STATE1_RESET)
+			memcpy(&cdev->private->pgid, &global_pgid,
+			       sizeof(struct pgid));
+		ccw_device_sense_pgid_done(cdev, 0);
+		break;
+	case -EOPNOTSUPP:	/* Sense Path Group ID not supported */
+		ccw_device_sense_pgid_done(cdev, -EOPNOTSUPP);
+		break;
+	case -ETIME:		/* Sense path group id stopped by timeout. */
+		ccw_device_sense_pgid_done(cdev, -ETIME);
+		break;
+	case -EACCES:		/* channel is not operational. */
+		sch->lpm &= ~cdev->private->imask;
+		cdev->private->imask >>= 1;
+		cdev->private->iretry = 5;
+		/* Fall through. */
+	case -EAGAIN:		/* Try again. */
+		ret = __ccw_device_sense_pgid_start(cdev);
+		if (ret != 0 && ret != -EBUSY)
+			ccw_device_sense_pgid_done(cdev, -ENODEV);
+		break;
+	case -EUSERS:		/* device is reserved for someone else. */
+		ccw_device_sense_pgid_done(cdev, -EUSERS);
+		break;
+	}
+}
+
+/*
+ * Path Group ID helper function.
+ */
+static int
+__ccw_device_do_pgid(struct ccw_device *cdev, __u8 func)
+{
+	struct subchannel *sch;
+	struct ccw1 *ccw;
+	int ret;
+
+	sch = to_subchannel(cdev->dev.parent);
+
+	/* Setup sense path group id channel program. */
+	cdev->private->pgid.inf.fc = func;
+	ccw = cdev->private->iccws;
+	if (!cdev->private->flags.pgid_single) {
+		cdev->private->pgid.inf.fc |= SPID_FUNC_MULTI_PATH;
+		ccw->cmd_code = CCW_CMD_SUSPEND_RECONN;
+		ccw->cda = 0;
+		ccw->count = 0;
+		ccw->flags = CCW_FLAG_SLI | CCW_FLAG_CC;
+		ccw++;
+	} else
+		cdev->private->pgid.inf.fc |= SPID_FUNC_SINGLE_PATH;
+
+	ccw->cmd_code = CCW_CMD_SET_PGID;
+	ccw->cda = (__u32) __pa (&cdev->private->pgid);
+	ccw->count = sizeof (struct pgid);
+	ccw->flags = CCW_FLAG_SLI;
+
+	/* Reset device status. */
+	memset(&cdev->private->irb, 0, sizeof(struct irb));
+
+	/* Try multiple times. */
+	ret = -ENODEV;
+	if (cdev->private->iretry > 0) {
+		cdev->private->iretry--;
+		ret = cio_start (sch, cdev->private->iccws,
+				 cdev->private->imask);
+		/* ret is 0, -EBUSY, -EACCES or -ENODEV */
+		if ((ret != -EACCES) && (ret != -ENODEV))
+			return ret;
+	}
+	/* PGID command failed on this path. Switch it off. */
+	sch->lpm &= ~cdev->private->imask;
+	sch->vpm &= ~cdev->private->imask;
+	CIO_MSG_EVENT(2, "SPID - Device %04x on Subchannel "
+		      "%04x, lpm %02X, became 'not operational'\n",
+		      cdev->private->devno, sch->irq, cdev->private->imask);
+	return ret;
+}
+
+/*
+ * Called from interrupt context to check if a valid answer
+ * to Set Path Group ID was received.
+ */
+static int
+__ccw_device_check_pgid(struct ccw_device *cdev)
+{
+	struct subchannel *sch;
+	struct irb *irb;
+
+	sch = to_subchannel(cdev->dev.parent);
+	irb = &cdev->private->irb;
+	if (irb->scsw.fctl & (SCSW_FCTL_HALT_FUNC | SCSW_FCTL_CLEAR_FUNC))
+		return -ETIME;
+	if (irb->esw.esw0.erw.cons) {
+		if (irb->ecw[0] & SNS0_CMD_REJECT)
+			return -EOPNOTSUPP;
+		/* Hmm, whatever happened, try again. */
+		CIO_MSG_EVENT(2, "SPID - device %04x, unit check, cnt %02d, "
+			      "sns : %02X%02X%02X%02X %02X%02X%02X%02X ...\n",
+			      cdev->private->devno, irb->esw.esw0.erw.scnt,
+			      irb->ecw[0], irb->ecw[1],
+			      irb->ecw[2], irb->ecw[3],
+			      irb->ecw[4], irb->ecw[5],
+			      irb->ecw[6], irb->ecw[7]);
+		return -EAGAIN;
+	}
+	if (irb->scsw.cc == 3) {
+		CIO_MSG_EVENT(2, "SPID - Device %04x on Subchannel "
+			      "%04x, lpm %02X, became 'not operational'\n",
+			      cdev->private->devno, sch->irq,
+			      cdev->private->imask);
+		return -EACCES;
+	}
+	return 0;
+}
+
+static void
+__ccw_device_verify_start(struct ccw_device *cdev)
+{
+	struct subchannel *sch;
+	__u8 imask, func;
+	int ret;
+
+	sch = to_subchannel(cdev->dev.parent);
+	while (sch->vpm != sch->lpm) {
+		/* Find first unequal bit in vpm vs. lpm */
+		for (imask = 0x80; imask != 0; imask >>= 1)
+			if ((sch->vpm & imask) != (sch->lpm & imask))
+				break;
+		cdev->private->imask = imask;
+		func = (sch->vpm & imask) ?
+			SPID_FUNC_RESIGN : SPID_FUNC_ESTABLISH;
+		ret = __ccw_device_do_pgid(cdev, func);
+		if (ret == 0 || ret == -EBUSY)
+			return;
+		cdev->private->iretry = 5;
+	}
+	ccw_device_verify_done(cdev, (sch->lpm != 0) ? 0 : -ENODEV);
+}
+		
+/*
+ * Got interrupt for Set Path Group ID.
+ */
+void
+ccw_device_verify_irq(struct ccw_device *cdev, enum dev_event dev_event)
+{
+	struct subchannel *sch;
+	struct irb *irb;
+	int ret;
+
+	irb = (struct irb *) __LC_IRB;
+	/* Retry set pgid for cc=1. */
+	if (irb->scsw.stctl ==
+	    (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) {
+		if (irb->scsw.cc == 1)
+			__ccw_device_verify_start(cdev);
+		return;
+	}
+	if (ccw_device_accumulate_and_sense(cdev, irb) != 0)
+		return;
+	sch = to_subchannel(cdev->dev.parent);
+	ret = __ccw_device_check_pgid(cdev);
+	memset(&cdev->private->irb, 0, sizeof(struct irb));
+	switch (ret) {
+	/* 0, -ETIME, -EAGAIN, -EOPNOTSUPP or -EACCES */
+	case 0:
+		/* Establish or Resign Path Group done. Update vpm. */
+		if ((sch->lpm & cdev->private->imask) != 0)
+			sch->vpm |= cdev->private->imask;
+		else
+			sch->vpm &= ~cdev->private->imask;
+		cdev->private->iretry = 5;
+		__ccw_device_verify_start(cdev);
+		break;
+	case -EOPNOTSUPP:
+		/*
+		 * One of those strange devices which claim to be able
+		 * to do multipathing but not for Set Path Group ID.
+		 */
+		if (cdev->private->flags.pgid_single) {
+			ccw_device_verify_done(cdev, -EOPNOTSUPP);
+			break;
+		}
+		cdev->private->flags.pgid_single = 1;
+		/* fall through. */
+	case -EAGAIN:		/* Try again. */
+		__ccw_device_verify_start(cdev);
+		break;
+	case -ETIME:		/* Set path group id stopped by timeout. */
+		ccw_device_verify_done(cdev, -ETIME);
+		break;
+	case -EACCES:		/* channel is not operational. */
+		sch->lpm &= ~cdev->private->imask;
+		sch->vpm &= ~cdev->private->imask;
+		cdev->private->iretry = 5;
+		__ccw_device_verify_start(cdev);
+		break;
+	}
+}
+
+void
+ccw_device_verify_start(struct ccw_device *cdev)
+{
+	cdev->private->flags.pgid_single = 0;
+	cdev->private->iretry = 5;
+	__ccw_device_verify_start(cdev);
+}
+
+static void
+__ccw_device_disband_start(struct ccw_device *cdev)
+{
+	struct subchannel *sch;
+	int ret;
+
+	sch = to_subchannel(cdev->dev.parent);
+	while (cdev->private->imask != 0) {
+		if (sch->lpm & cdev->private->imask) {
+			ret = __ccw_device_do_pgid(cdev, SPID_FUNC_DISBAND);
+			if (ret == 0)
+				return;
+		}
+		cdev->private->iretry = 5;
+		cdev->private->imask >>= 1;
+	}
+	ccw_device_verify_done(cdev, (sch->lpm != 0) ? 0 : -ENODEV);
+}
+
+/*
+ * Got interrupt for Unset Path Group ID.
+ */
+void
+ccw_device_disband_irq(struct ccw_device *cdev, enum dev_event dev_event)
+{
+	struct subchannel *sch;
+	struct irb *irb;
+	int ret;
+
+	irb = (struct irb *) __LC_IRB;
+	/* Retry set pgid for cc=1. */
+	if (irb->scsw.stctl ==
+	    (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) {
+		if (irb->scsw.cc == 1)
+			__ccw_device_disband_start(cdev);
+		return;
+	}
+	if (ccw_device_accumulate_and_sense(cdev, irb) != 0)
+		return;
+	sch = to_subchannel(cdev->dev.parent);
+	ret = __ccw_device_check_pgid(cdev);
+	memset(&cdev->private->irb, 0, sizeof(struct irb));
+	switch (ret) {
+	/* 0, -ETIME, -EAGAIN, -EOPNOTSUPP or -EACCES */
+	case 0:			/* disband successful. */
+		sch->vpm = 0;
+		ccw_device_disband_done(cdev, ret);
+		break;
+	case -EOPNOTSUPP:
+		/*
+		 * One of those strange devices which claim to be able
+		 * to do multipathing but not for Unset Path Group ID.
+		 */
+		cdev->private->flags.pgid_single = 1;
+		/* fall through. */
+	case -EAGAIN:		/* Try again. */
+		__ccw_device_disband_start(cdev);
+		break;
+	case -ETIME:		/* Set path group id stopped by timeout. */
+		ccw_device_disband_done(cdev, -ETIME);
+		break;
+	case -EACCES:		/* channel is not operational. */
+		cdev->private->imask >>= 1;
+		cdev->private->iretry = 5;
+		__ccw_device_disband_start(cdev);
+		break;
+	}
+}
+
+void
+ccw_device_disband_start(struct ccw_device *cdev)
+{
+	cdev->private->flags.pgid_single = 0;
+	cdev->private->iretry = 5;
+	cdev->private->imask = 0x80;
+	__ccw_device_disband_start(cdev);
+}
diff --git a/drivers/s390/cio/device_status.c b/drivers/s390/cio/device_status.c
new file mode 100644
index 0000000..4ab2e0d
--- /dev/null
+++ b/drivers/s390/cio/device_status.c
@@ -0,0 +1,385 @@
+/*
+ * drivers/s390/cio/device_status.c
+ *
+ *    Copyright (C) 2002 IBM Deutschland Entwicklung GmbH,
+ *			 IBM Corporation
+ *    Author(s): Cornelia Huck(cohuck@de.ibm.com)
+ *		 Martin Schwidefsky (schwidefsky@de.ibm.com)
+ *
+ * Status accumulation and basic sense functions.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/init.h>
+
+#include <asm/ccwdev.h>
+#include <asm/cio.h>
+
+#include "cio.h"
+#include "cio_debug.h"
+#include "css.h"
+#include "device.h"
+#include "ioasm.h"
+
+/*
+ * Check for any kind of channel or interface control check but don't
+ * issue the message for the console device
+ */
+static inline void
+ccw_device_msg_control_check(struct ccw_device *cdev, struct irb *irb)
+{
+	if (!(irb->scsw.cstat & (SCHN_STAT_CHN_DATA_CHK |
+				 SCHN_STAT_CHN_CTRL_CHK |
+				 SCHN_STAT_INTF_CTRL_CHK)))
+		return;
+		
+	CIO_MSG_EVENT(0, "Channel-Check or Interface-Control-Check "
+		      "received"
+		      " ... device %04X on subchannel %04X, dev_stat "
+		      ": %02X sch_stat : %02X\n",
+		      cdev->private->devno, cdev->private->irq,
+		      cdev->private->irb.scsw.dstat,
+		      cdev->private->irb.scsw.cstat);
+
+	if (irb->scsw.cc != 3) {
+		char dbf_text[15];
+
+		sprintf(dbf_text, "chk%x", cdev->private->irq);
+		CIO_TRACE_EVENT(0, dbf_text);
+		CIO_HEX_EVENT(0, &cdev->private->irb, sizeof (struct irb));
+	}
+}
+
+/*
+ * Some paths became not operational (pno bit in scsw is set).
+ */
+static void
+ccw_device_path_notoper(struct ccw_device *cdev)
+{
+	struct subchannel *sch;
+
+	sch = to_subchannel(cdev->dev.parent);
+	stsch (sch->irq, &sch->schib);
+
+	CIO_MSG_EVENT(0, "%s(%04x) - path(s) %02x are "
+		      "not operational \n", __FUNCTION__, sch->irq,
+		      sch->schib.pmcw.pnom);
+
+	sch->lpm &= ~sch->schib.pmcw.pnom;
+	if (cdev->private->options.pgroup)
+		cdev->private->flags.doverify = 1;
+}
+
+/*
+ * Copy valid bits from the extended control word to device irb.
+ */
+static inline void
+ccw_device_accumulate_ecw(struct ccw_device *cdev, struct irb *irb)
+{
+	/*
+	 * Copy extended control bit if it is valid... yes there
+	 * are condition that have to be met for the extended control
+	 * bit to have meaning. Sick.
+	 */
+	cdev->private->irb.scsw.ectl = 0;
+	if ((irb->scsw.stctl & SCSW_STCTL_ALERT_STATUS) &&
+	    !(irb->scsw.stctl & SCSW_STCTL_INTER_STATUS))
+		cdev->private->irb.scsw.ectl = irb->scsw.ectl;
+	/* Check if extended control word is valid. */
+	if (!cdev->private->irb.scsw.ectl)
+		return;
+	/* Copy concurrent sense / model dependent information. */
+	memcpy (&cdev->private->irb.ecw, irb->ecw, sizeof (irb->ecw));
+}
+
+/*
+ * Check if extended status word is valid.
+ */
+static inline int
+ccw_device_accumulate_esw_valid(struct irb *irb)
+{
+	if (!irb->scsw.eswf && irb->scsw.stctl == SCSW_STCTL_STATUS_PEND)
+		return 0;
+	if (irb->scsw.stctl == 
+	    		(SCSW_STCTL_INTER_STATUS|SCSW_STCTL_STATUS_PEND) &&
+	    !(irb->scsw.actl & SCSW_ACTL_SUSPENDED))
+		return 0;
+	return 1;
+}
+
+/*
+ * Copy valid bits from the extended status word to device irb.
+ */
+static inline void
+ccw_device_accumulate_esw(struct ccw_device *cdev, struct irb *irb)
+{
+	struct irb *cdev_irb;
+	struct sublog *cdev_sublog, *sublog;
+
+	if (!ccw_device_accumulate_esw_valid(irb))
+		return;
+
+	cdev_irb = &cdev->private->irb;
+
+	/* Copy last path used mask. */
+	cdev_irb->esw.esw1.lpum = irb->esw.esw1.lpum;
+
+	/* Copy subchannel logout information if esw is of format 0. */
+	if (irb->scsw.eswf) {
+		cdev_sublog = &cdev_irb->esw.esw0.sublog;
+		sublog = &irb->esw.esw0.sublog;
+		/* Copy extended status flags. */
+		cdev_sublog->esf = sublog->esf;
+		/*
+		 * Copy fields that have a meaning for channel data check
+		 * channel control check and interface control check.
+		 */
+		if (irb->scsw.cstat & (SCHN_STAT_CHN_DATA_CHK |
+				       SCHN_STAT_CHN_CTRL_CHK |
+				       SCHN_STAT_INTF_CTRL_CHK)) {
+			/* Copy ancillary report bit. */
+			cdev_sublog->arep = sublog->arep;
+			/* Copy field-validity-flags. */
+			cdev_sublog->fvf = sublog->fvf;
+			/* Copy storage access code. */
+			cdev_sublog->sacc = sublog->sacc;
+			/* Copy termination code. */
+			cdev_sublog->termc = sublog->termc;
+			/* Copy sequence code. */
+			cdev_sublog->seqc = sublog->seqc;
+		}
+		/* Copy device status check. */
+		cdev_sublog->devsc = sublog->devsc;
+		/* Copy secondary error. */
+		cdev_sublog->serr = sublog->serr;
+		/* Copy i/o-error alert. */
+		cdev_sublog->ioerr = sublog->ioerr;
+		/* Copy channel path timeout bit. */
+		if (irb->scsw.cstat & SCHN_STAT_INTF_CTRL_CHK)
+			cdev_irb->esw.esw0.erw.cpt = irb->esw.esw0.erw.cpt;
+		/* Copy failing storage address validity flag. */
+		cdev_irb->esw.esw0.erw.fsavf = irb->esw.esw0.erw.fsavf;
+		if (cdev_irb->esw.esw0.erw.fsavf) {
+			/* ... and copy the failing storage address. */
+			memcpy(cdev_irb->esw.esw0.faddr, irb->esw.esw0.faddr,
+			       sizeof (irb->esw.esw0.faddr));
+			/* ... and copy the failing storage address format. */
+			cdev_irb->esw.esw0.erw.fsaf = irb->esw.esw0.erw.fsaf;
+		}
+		/* Copy secondary ccw address validity bit. */
+		cdev_irb->esw.esw0.erw.scavf = irb->esw.esw0.erw.scavf;
+		if (irb->esw.esw0.erw.scavf)
+			/* ... and copy the secondary ccw address. */
+			cdev_irb->esw.esw0.saddr = irb->esw.esw0.saddr;
+		
+	}
+	/* FIXME: DCTI for format 2? */
+
+	/* Copy authorization bit. */
+	cdev_irb->esw.esw0.erw.auth = irb->esw.esw0.erw.auth;
+	/* Copy path verification required flag. */
+	cdev_irb->esw.esw0.erw.pvrf = irb->esw.esw0.erw.pvrf;
+	if (irb->esw.esw0.erw.pvrf && cdev->private->options.pgroup)
+		cdev->private->flags.doverify = 1;
+	/* Copy concurrent sense bit. */
+	cdev_irb->esw.esw0.erw.cons = irb->esw.esw0.erw.cons;
+	if (irb->esw.esw0.erw.cons)
+		cdev_irb->esw.esw0.erw.scnt = irb->esw.esw0.erw.scnt;
+}
+
+/*
+ * Accumulate status from irb to devstat.
+ */
+void
+ccw_device_accumulate_irb(struct ccw_device *cdev, struct irb *irb)
+{
+	struct irb *cdev_irb;
+
+	/*
+	 * Check if the status pending bit is set in stctl.
+	 * If not, the remaining bit have no meaning and we must ignore them.
+	 * The esw is not meaningful as well...
+	 */
+	if (!(irb->scsw.stctl & SCSW_STCTL_STATUS_PEND))
+		return;
+
+	/* Check for channel checks and interface control checks. */
+	ccw_device_msg_control_check(cdev, irb);
+
+	/* Check for path not operational. */
+	if (irb->scsw.pno && irb->scsw.fctl != 0 &&
+	    (!(irb->scsw.stctl & SCSW_STCTL_INTER_STATUS) ||
+	     (irb->scsw.actl & SCSW_ACTL_SUSPENDED)))
+		ccw_device_path_notoper(cdev);
+
+	/*
+	 * Don't accumulate unsolicited interrupts.
+	 */
+	if ((irb->scsw.stctl ==
+	     (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) &&
+	    (!irb->scsw.cc))
+		return;
+
+	cdev_irb = &cdev->private->irb;
+
+	/* Copy bits which are valid only for the start function. */
+	if (irb->scsw.fctl & SCSW_FCTL_START_FUNC) {
+		/* Copy key. */
+		cdev_irb->scsw.key = irb->scsw.key;
+		/* Copy suspend control bit. */
+		cdev_irb->scsw.sctl = irb->scsw.sctl;
+		/* Accumulate deferred condition code. */
+		cdev_irb->scsw.cc |= irb->scsw.cc;
+		/* Copy ccw format bit. */
+		cdev_irb->scsw.fmt = irb->scsw.fmt;
+		/* Copy prefetch bit. */
+		cdev_irb->scsw.pfch = irb->scsw.pfch;
+		/* Copy initial-status-interruption-control. */
+		cdev_irb->scsw.isic = irb->scsw.isic;
+		/* Copy address limit checking control. */
+		cdev_irb->scsw.alcc = irb->scsw.alcc;
+		/* Copy suppress suspend bit. */
+		cdev_irb->scsw.ssi = irb->scsw.ssi;
+	}
+
+	/* Take care of the extended control bit and extended control word. */
+	ccw_device_accumulate_ecw(cdev, irb);
+	    
+	/* Accumulate function control. */
+	cdev_irb->scsw.fctl |= irb->scsw.fctl;
+	/* Copy activity control. */
+	cdev_irb->scsw.actl= irb->scsw.actl;
+	/* Accumulate status control. */
+	cdev_irb->scsw.stctl |= irb->scsw.stctl;
+	/*
+	 * Copy ccw address if it is valid. This is a bit simplified
+	 * but should be close enough for all practical purposes.
+	 */
+	if ((irb->scsw.stctl & SCSW_STCTL_PRIM_STATUS) ||
+	    ((irb->scsw.stctl == 
+	      (SCSW_STCTL_INTER_STATUS|SCSW_STCTL_STATUS_PEND)) &&
+	     (irb->scsw.actl & SCSW_ACTL_DEVACT) &&
+	     (irb->scsw.actl & SCSW_ACTL_SCHACT)) ||
+	    (irb->scsw.actl & SCSW_ACTL_SUSPENDED))
+		cdev_irb->scsw.cpa = irb->scsw.cpa;
+	/* Accumulate device status, but not the device busy flag. */
+	cdev_irb->scsw.dstat &= ~DEV_STAT_BUSY;
+	cdev_irb->scsw.dstat |= irb->scsw.dstat;
+	/* Accumulate subchannel status. */
+	cdev_irb->scsw.cstat |= irb->scsw.cstat;
+	/* Copy residual count if it is valid. */
+	if ((irb->scsw.stctl & SCSW_STCTL_PRIM_STATUS) &&
+	    (irb->scsw.cstat & ~(SCHN_STAT_PCI | SCHN_STAT_INCORR_LEN)) == 0)
+		cdev_irb->scsw.count = irb->scsw.count;
+
+	/* Take care of bits in the extended status word. */
+	ccw_device_accumulate_esw(cdev, irb);
+
+	/*
+	 * Check whether we must issue a SENSE CCW ourselves if there is no
+	 * concurrent sense facility installed for the subchannel.
+	 * No sense is required if no delayed sense is pending
+	 * and we did not get a unit check without sense information.
+	 *
+	 * Note: We should check for ioinfo[irq]->flags.consns but VM
+	 *	 violates the ESA/390 architecture and doesn't present an
+	 *	 operand exception for virtual devices without concurrent
+	 *	 sense facility available/supported when enabling the
+	 *	 concurrent sense facility.
+	 */
+	if ((cdev_irb->scsw.dstat & DEV_STAT_UNIT_CHECK) &&
+	    !(cdev_irb->esw.esw0.erw.cons))
+		cdev->private->flags.dosense = 1;
+}
+
+/*
+ * Do a basic sense.
+ */
+int
+ccw_device_do_sense(struct ccw_device *cdev, struct irb *irb)
+{
+	struct subchannel *sch;
+
+	sch = to_subchannel(cdev->dev.parent);
+
+	/* A sense is required, can we do it now ? */
+	if ((irb->scsw.actl  & (SCSW_ACTL_DEVACT | SCSW_ACTL_SCHACT)) != 0)
+		/*
+		 * we received an Unit Check but we have no final
+		 *  status yet, therefore we must delay the SENSE
+		 *  processing. We must not report this intermediate
+		 *  status to the device interrupt handler.
+		 */
+		return -EBUSY;
+
+	/*
+	 * We have ending status but no sense information. Do a basic sense.
+	 */
+	sch = to_subchannel(cdev->dev.parent);
+	sch->sense_ccw.cmd_code = CCW_CMD_BASIC_SENSE;
+	sch->sense_ccw.cda = (__u32) __pa(cdev->private->irb.ecw);
+	sch->sense_ccw.count = SENSE_MAX_COUNT;
+	sch->sense_ccw.flags = CCW_FLAG_SLI;
+
+	return cio_start (sch, &sch->sense_ccw, 0xff);
+}
+
+/*
+ * Add information from basic sense to devstat.
+ */
+void
+ccw_device_accumulate_basic_sense(struct ccw_device *cdev, struct irb *irb)
+{
+	/*
+	 * Check if the status pending bit is set in stctl.
+	 * If not, the remaining bit have no meaning and we must ignore them.
+	 * The esw is not meaningful as well...
+	 */
+	if (!(irb->scsw.stctl & SCSW_STCTL_STATUS_PEND))
+		return;
+
+	/* Check for channel checks and interface control checks. */
+	ccw_device_msg_control_check(cdev, irb);
+
+	/* Check for path not operational. */
+	if (irb->scsw.pno && irb->scsw.fctl != 0 &&
+	    (!(irb->scsw.stctl & SCSW_STCTL_INTER_STATUS) ||
+	     (irb->scsw.actl & SCSW_ACTL_SUSPENDED)))
+		ccw_device_path_notoper(cdev);
+
+	if (!(irb->scsw.dstat & DEV_STAT_UNIT_CHECK) &&
+	    (irb->scsw.dstat & DEV_STAT_CHN_END)) {
+		cdev->private->irb.esw.esw0.erw.cons = 1;
+		cdev->private->flags.dosense = 0;
+	}
+	/* Check if path verification is required. */
+	if (ccw_device_accumulate_esw_valid(irb) &&
+	    irb->esw.esw0.erw.pvrf && cdev->private->options.pgroup) 
+		cdev->private->flags.doverify = 1;
+}
+
+/*
+ * This function accumulates the status into the private devstat and
+ * starts a basic sense if one is needed.
+ */
+int
+ccw_device_accumulate_and_sense(struct ccw_device *cdev, struct irb *irb)
+{
+	ccw_device_accumulate_irb(cdev, irb);
+	if ((irb->scsw.actl  & (SCSW_ACTL_DEVACT | SCSW_ACTL_SCHACT)) != 0)
+		return -EBUSY;
+	/* Check for basic sense. */
+	if (cdev->private->flags.dosense &&
+	    !(irb->scsw.dstat & DEV_STAT_UNIT_CHECK)) {
+		cdev->private->irb.esw.esw0.erw.cons = 1;
+		cdev->private->flags.dosense = 0;
+		return 0;
+	}
+	if (cdev->private->flags.dosense) {
+		ccw_device_do_sense(cdev, irb);
+		return -EBUSY;
+	}
+	return 0;
+}
+
diff --git a/drivers/s390/cio/ioasm.h b/drivers/s390/cio/ioasm.h
new file mode 100644
index 0000000..c874607
--- /dev/null
+++ b/drivers/s390/cio/ioasm.h
@@ -0,0 +1,228 @@
+#ifndef S390_CIO_IOASM_H
+#define S390_CIO_IOASM_H
+
+/*
+ * TPI info structure
+ */
+struct tpi_info {
+	__u32 reserved1	 : 16;	 /* reserved 0x00000001 */
+	__u32 irq	 : 16;	 /* aka. subchannel number */
+	__u32 intparm;		 /* interruption parameter */
+	__u32 adapter_IO : 1;
+	__u32 reserved2	 : 1;
+	__u32 isc	 : 3;
+	__u32 reserved3	 : 12;
+	__u32 int_type	 : 3;
+	__u32 reserved4	 : 12;
+} __attribute__ ((packed));
+
+
+/*
+ * Some S390 specific IO instructions as inline
+ */
+
+extern __inline__ int stsch(int irq, volatile struct schib *addr)
+{
+	int ccode;
+
+	__asm__ __volatile__(
+		"   lr	  1,%1\n"
+		"   stsch 0(%2)\n"
+		"   ipm	  %0\n"
+		"   srl	  %0,28"
+		: "=d" (ccode)
+		: "d" (irq | 0x10000), "a" (addr)
+		: "cc", "1" );
+	return ccode;
+}
+
+extern __inline__ int msch(int irq, volatile struct schib *addr)
+{
+	int ccode;
+
+	__asm__ __volatile__(
+		"   lr	  1,%1\n"
+		"   msch  0(%2)\n"
+		"   ipm	  %0\n"
+		"   srl	  %0,28"
+		: "=d" (ccode)
+		: "d" (irq | 0x10000L), "a" (addr)
+		: "cc", "1" );
+	return ccode;
+}
+
+extern __inline__ int msch_err(int irq, volatile struct schib *addr)
+{
+	int ccode;
+
+	__asm__ __volatile__(
+		"    lhi  %0,%3\n"
+		"    lr	  1,%1\n"
+		"    msch 0(%2)\n"
+		"0:  ipm  %0\n"
+		"    srl  %0,28\n"
+		"1:\n"
+#ifdef CONFIG_ARCH_S390X
+		".section __ex_table,\"a\"\n"
+		"   .align 8\n"
+		"   .quad 0b,1b\n"
+		".previous"
+#else
+		".section __ex_table,\"a\"\n"
+		"   .align 4\n"
+		"   .long 0b,1b\n"
+		".previous"
+#endif
+		: "=&d" (ccode)
+		: "d" (irq | 0x10000L), "a" (addr), "K" (-EIO)
+		: "cc", "1" );
+	return ccode;
+}
+
+extern __inline__ int tsch(int irq, volatile struct irb *addr)
+{
+	int ccode;
+
+	__asm__ __volatile__(
+		"   lr	  1,%1\n"
+		"   tsch  0(%2)\n"
+		"   ipm	  %0\n"
+		"   srl	  %0,28"
+		: "=d" (ccode)
+		: "d" (irq | 0x10000L), "a" (addr)
+		: "cc", "1" );
+	return ccode;
+}
+
+extern __inline__ int tpi( volatile struct tpi_info *addr)
+{
+	int ccode;
+
+	__asm__ __volatile__(
+		"   tpi	  0(%1)\n"
+		"   ipm	  %0\n"
+		"   srl	  %0,28"
+		: "=d" (ccode)
+		: "a" (addr)
+		: "cc", "1" );
+	return ccode;
+}
+
+extern __inline__ int ssch(int irq, volatile struct orb *addr)
+{
+	int ccode;
+
+	__asm__ __volatile__(
+		"   lr	  1,%1\n"
+		"   ssch  0(%2)\n"
+		"   ipm	  %0\n"
+		"   srl	  %0,28"
+		: "=d" (ccode)
+		: "d" (irq | 0x10000L), "a" (addr)
+		: "cc", "1" );
+	return ccode;
+}
+
+extern __inline__ int rsch(int irq)
+{
+	int ccode;
+
+	__asm__ __volatile__(
+		"   lr	  1,%1\n"
+		"   rsch\n"
+		"   ipm	  %0\n"
+		"   srl	  %0,28"
+		: "=d" (ccode)
+		: "d" (irq | 0x10000L)
+		: "cc", "1" );
+	return ccode;
+}
+
+extern __inline__ int csch(int irq)
+{
+	int ccode;
+
+	__asm__ __volatile__(
+		"   lr	  1,%1\n"
+		"   csch\n"
+		"   ipm	  %0\n"
+		"   srl	  %0,28"
+		: "=d" (ccode)
+		: "d" (irq | 0x10000L)
+		: "cc", "1" );
+	return ccode;
+}
+
+extern __inline__ int hsch(int irq)
+{
+	int ccode;
+
+	__asm__ __volatile__(
+		"   lr	  1,%1\n"
+		"   hsch\n"
+		"   ipm	  %0\n"
+		"   srl	  %0,28"
+		: "=d" (ccode)
+		: "d" (irq | 0x10000L)
+		: "cc", "1" );
+	return ccode;
+}
+
+extern __inline__ int xsch(int irq)
+{
+	int ccode;
+
+	__asm__ __volatile__(
+		"   lr	  1,%1\n"
+		"   .insn rre,0xb2760000,%1,0\n"
+		"   ipm	  %0\n"
+		"   srl	  %0,28"
+		: "=d" (ccode)
+		: "d" (irq | 0x10000L)
+		: "cc", "1" );
+	return ccode;
+}
+
+extern __inline__ int chsc(void *chsc_area)
+{
+	int cc;
+
+	__asm__ __volatile__ (
+		".insn	rre,0xb25f0000,%1,0	\n\t"
+		"ipm	%0	\n\t"
+		"srl	%0,28	\n\t"
+		: "=d" (cc)
+		: "d" (chsc_area)
+		: "cc" );
+
+	return cc;
+}
+
+extern __inline__ int iac( void)
+{
+	int ccode;
+
+	__asm__ __volatile__(
+		"   iac	  1\n"
+		"   ipm	  %0\n"
+		"   srl	  %0,28"
+		: "=d" (ccode) : : "cc", "1" );
+	return ccode;
+}
+
+extern __inline__ int rchp(int chpid)
+{
+	int ccode;
+
+	__asm__ __volatile__(
+		"   lr	  1,%1\n"
+		"   rchp\n"
+		"   ipm	  %0\n"
+		"   srl	  %0,28"
+		: "=d" (ccode)
+		: "d" (chpid)
+		: "cc", "1" );
+	return ccode;
+}
+
+#endif
diff --git a/drivers/s390/cio/qdio.c b/drivers/s390/cio/qdio.c
new file mode 100644
index 0000000..bbe9f45
--- /dev/null
+++ b/drivers/s390/cio/qdio.c
@@ -0,0 +1,3468 @@
+/*
+ *
+ * linux/drivers/s390/cio/qdio.c
+ *
+ * Linux for S/390 QDIO base support, Hipersocket base support
+ * version 2
+ *
+ * Copyright 2000,2002 IBM Corporation
+ * Author(s):             Utz Bacher <utz.bacher@de.ibm.com>
+ * 2.6 cio integration by Cornelia Huck <cohuck@de.ibm.com>
+ *
+ * Restriction: only 63 iqdio subchannels would have its own indicator,
+ * after that, subsequent subchannels share one indicator
+ *
+ *
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/init.h>
+
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/proc_fs.h>
+#include <linux/timer.h>
+
+#include <asm/ccwdev.h>
+#include <asm/io.h>
+#include <asm/atomic.h>
+#include <asm/semaphore.h>
+#include <asm/timex.h>
+
+#include <asm/debug.h>
+#include <asm/qdio.h>
+
+#include "cio.h"
+#include "css.h"
+#include "device.h"
+#include "airq.h"
+#include "qdio.h"
+#include "ioasm.h"
+#include "chsc.h"
+
+#define VERSION_QDIO_C "$Revision: 1.98 $"
+
+/****************** MODULE PARAMETER VARIABLES ********************/
+MODULE_AUTHOR("Utz Bacher <utz.bacher@de.ibm.com>");
+MODULE_DESCRIPTION("QDIO base support version 2, " \
+		   "Copyright 2000 IBM Corporation");
+MODULE_LICENSE("GPL");
+
+/******************** HERE WE GO ***********************************/
+
+static const char version[] = "QDIO base support version 2 ("
+	VERSION_QDIO_C "/" VERSION_QDIO_H  "/" VERSION_CIO_QDIO_H ")";
+
+#ifdef QDIO_PERFORMANCE_STATS
+static int proc_perf_file_registration;
+static unsigned long i_p_c, i_p_nc, o_p_c, o_p_nc, ii_p_c, ii_p_nc;
+static struct qdio_perf_stats perf_stats;
+#endif /* QDIO_PERFORMANCE_STATS */
+
+static int hydra_thinints;
+static int omit_svs;
+
+static int indicator_used[INDICATORS_PER_CACHELINE];
+static __u32 * volatile indicators;
+static __u32 volatile spare_indicator;
+static atomic_t spare_indicator_usecount;
+
+static debug_info_t *qdio_dbf_setup;
+static debug_info_t *qdio_dbf_sbal;
+static debug_info_t *qdio_dbf_trace;
+static debug_info_t *qdio_dbf_sense;
+#ifdef CONFIG_QDIO_DEBUG
+static debug_info_t *qdio_dbf_slsb_out;
+static debug_info_t *qdio_dbf_slsb_in;
+#endif /* CONFIG_QDIO_DEBUG */
+
+/* iQDIO stuff: */
+static volatile struct qdio_q *tiq_list=NULL; /* volatile as it could change
+						 during a while loop */
+static DEFINE_SPINLOCK(ttiq_list_lock);
+static int register_thinint_result;
+static void tiqdio_tl(unsigned long);
+static DECLARE_TASKLET(tiqdio_tasklet,tiqdio_tl,0);
+
+/* not a macro, as one of the arguments is atomic_read */
+static inline int 
+qdio_min(int a,int b)
+{
+	if (a<b)
+		return a;
+	else
+		return b;
+}
+
+/***************** SCRUBBER HELPER ROUTINES **********************/
+
+static inline volatile __u64 
+qdio_get_micros(void)
+{
+        return (get_clock() >> 10); /* time>>12 is microseconds */
+}
+
+/* 
+ * unfortunately, we can't just xchg the values; in do_QDIO we want to reserve
+ * the q in any case, so that we'll not be interrupted when we are in
+ * qdio_mark_tiq... shouldn't have a really bad impact, as reserving almost
+ * ever works (last famous words) 
+ */
+static inline int 
+qdio_reserve_q(struct qdio_q *q)
+{
+	return atomic_add_return(1,&q->use_count) - 1;
+}
+
+static inline void 
+qdio_release_q(struct qdio_q *q)
+{
+	atomic_dec(&q->use_count);
+}
+
+static volatile inline void 
+qdio_set_slsb(volatile char *slsb, unsigned char value)
+{
+	xchg((char*)slsb,value);
+}
+
+static inline int 
+qdio_siga_sync(struct qdio_q *q, unsigned int gpr2,
+	       unsigned int gpr3)
+{
+	int cc;
+
+	QDIO_DBF_TEXT4(0,trace,"sigasync");
+	QDIO_DBF_HEX4(0,trace,&q,sizeof(void*));
+
+#ifdef QDIO_PERFORMANCE_STATS
+	perf_stats.siga_syncs++;
+#endif /* QDIO_PERFORMANCE_STATS */
+
+	cc = do_siga_sync(q->irq, gpr2, gpr3);
+	if (cc)
+		QDIO_DBF_HEX3(0,trace,&cc,sizeof(int*));
+
+	return cc;
+}
+
+static inline int
+qdio_siga_sync_q(struct qdio_q *q)
+{
+	if (q->is_input_q)
+		return qdio_siga_sync(q, 0, q->mask);
+	return qdio_siga_sync(q, q->mask, 0);
+}
+
+/* 
+ * returns QDIO_SIGA_ERROR_ACCESS_EXCEPTION as cc, when SIGA returns
+ * an access exception 
+ */
+static inline int 
+qdio_siga_output(struct qdio_q *q)
+{
+	int cc;
+	__u32 busy_bit;
+	__u64 start_time=0;
+
+#ifdef QDIO_PERFORMANCE_STATS
+	perf_stats.siga_outs++;
+#endif /* QDIO_PERFORMANCE_STATS */
+
+	QDIO_DBF_TEXT4(0,trace,"sigaout");
+	QDIO_DBF_HEX4(0,trace,&q,sizeof(void*));
+
+	for (;;) {
+		cc = do_siga_output(q->irq, q->mask, &busy_bit);
+//QDIO_PRINT_ERR("cc=%x, busy=%x\n",cc,busy_bit);
+		if ((cc==2) && (busy_bit) && (q->is_iqdio_q)) {
+			if (!start_time) 
+				start_time=NOW;
+			if ((NOW-start_time)>QDIO_BUSY_BIT_PATIENCE)
+				break;
+		} else
+			break;
+	}
+	
+	if ((cc==2) && (busy_bit)) 
+		cc |= QDIO_SIGA_ERROR_B_BIT_SET;
+
+	if (cc)
+		QDIO_DBF_HEX3(0,trace,&cc,sizeof(int*));
+
+	return cc;
+}
+
+static inline int 
+qdio_siga_input(struct qdio_q *q)
+{
+	int cc;
+
+	QDIO_DBF_TEXT4(0,trace,"sigain");
+	QDIO_DBF_HEX4(0,trace,&q,sizeof(void*));
+
+#ifdef QDIO_PERFORMANCE_STATS
+	perf_stats.siga_ins++;
+#endif /* QDIO_PERFORMANCE_STATS */
+
+	cc = do_siga_input(q->irq, q->mask);
+	
+	if (cc)
+		QDIO_DBF_HEX3(0,trace,&cc,sizeof(int*));
+
+	return cc;
+}
+
+/* locked by the locks in qdio_activate and qdio_cleanup */
+static __u32 * volatile 
+qdio_get_indicator(void)
+{
+	int i;
+
+	for (i=1;i<INDICATORS_PER_CACHELINE;i++)
+		if (!indicator_used[i]) {
+			indicator_used[i]=1;
+			return indicators+i;
+		}
+	atomic_inc(&spare_indicator_usecount);
+	return (__u32 * volatile) &spare_indicator;
+}
+
+/* locked by the locks in qdio_activate and qdio_cleanup */
+static void 
+qdio_put_indicator(__u32 *addr)
+{
+	int i;
+
+	if ( (addr) && (addr!=&spare_indicator) ) {
+		i=addr-indicators;
+		indicator_used[i]=0;
+	}
+	if (addr == &spare_indicator)
+		atomic_dec(&spare_indicator_usecount);
+}
+
+static inline volatile void 
+tiqdio_clear_summary_bit(__u32 *location)
+{
+	QDIO_DBF_TEXT5(0,trace,"clrsummb");
+	QDIO_DBF_HEX5(0,trace,&location,sizeof(void*));
+
+	xchg(location,0);
+}
+
+static inline volatile void
+tiqdio_set_summary_bit(__u32 *location)
+{
+	QDIO_DBF_TEXT5(0,trace,"setsummb");
+	QDIO_DBF_HEX5(0,trace,&location,sizeof(void*));
+
+	xchg(location,-1);
+}
+
+static inline void 
+tiqdio_sched_tl(void)
+{
+	tasklet_hi_schedule(&tiqdio_tasklet);
+}
+
+static inline void
+qdio_mark_tiq(struct qdio_q *q)
+{
+	unsigned long flags;
+
+	QDIO_DBF_TEXT4(0,trace,"mark iq");
+	QDIO_DBF_HEX4(0,trace,&q,sizeof(void*));
+
+	spin_lock_irqsave(&ttiq_list_lock,flags);
+	if (unlikely(atomic_read(&q->is_in_shutdown)))
+		goto out_unlock;
+
+	if (!q->is_input_q)
+		goto out_unlock;
+
+	if ((q->list_prev) || (q->list_next)) 
+		goto out_unlock;
+
+	if (!tiq_list) {
+		tiq_list=q;
+		q->list_prev=q;
+		q->list_next=q;
+	} else {
+		q->list_next=tiq_list;
+		q->list_prev=tiq_list->list_prev;
+		tiq_list->list_prev->list_next=q;
+		tiq_list->list_prev=q;
+	}
+	spin_unlock_irqrestore(&ttiq_list_lock,flags);
+
+	tiqdio_set_summary_bit((__u32*)q->dev_st_chg_ind);
+	tiqdio_sched_tl();
+	return;
+out_unlock:
+	spin_unlock_irqrestore(&ttiq_list_lock,flags);
+	return;
+}
+
+static inline void
+qdio_mark_q(struct qdio_q *q)
+{
+	QDIO_DBF_TEXT4(0,trace,"mark q");
+	QDIO_DBF_HEX4(0,trace,&q,sizeof(void*));
+
+	if (unlikely(atomic_read(&q->is_in_shutdown)))
+		return;
+
+	tasklet_schedule(&q->tasklet);
+}
+
+static inline int
+qdio_stop_polling(struct qdio_q *q)
+{
+#ifdef QDIO_USE_PROCESSING_STATE
+	int gsf;
+
+	if (!atomic_swap(&q->polling,0)) 
+		return 1;
+
+	QDIO_DBF_TEXT4(0,trace,"stoppoll");
+	QDIO_DBF_HEX4(0,trace,&q,sizeof(void*));
+
+	/* show the card that we are not polling anymore */
+	if (!q->is_input_q)
+		return 1;
+
+	gsf=GET_SAVED_FRONTIER(q);
+	set_slsb(&q->slsb.acc.val[(gsf+QDIO_MAX_BUFFERS_PER_Q-1)&
+				  (QDIO_MAX_BUFFERS_PER_Q-1)],
+		 SLSB_P_INPUT_NOT_INIT);
+	/* 
+	 * we don't issue this SYNC_MEMORY, as we trust Rick T and
+	 * moreover will not use the PROCESSING state under VM, so
+	 * q->polling was 0 anyway
+	 */
+	/*SYNC_MEMORY;*/
+	if (q->slsb.acc.val[gsf]!=SLSB_P_INPUT_PRIMED)
+		return 1;
+	/* 
+	 * set our summary bit again, as otherwise there is a
+	 * small window we can miss between resetting it and
+	 * checking for PRIMED state 
+	 */
+	if (q->is_thinint_q)
+		tiqdio_set_summary_bit((__u32*)q->dev_st_chg_ind);
+	return 0;
+
+#else /* QDIO_USE_PROCESSING_STATE */
+	return 1;
+#endif /* QDIO_USE_PROCESSING_STATE */
+}
+
+/* 
+ * see the comment in do_QDIO and before qdio_reserve_q about the
+ * sophisticated locking outside of unmark_q, so that we don't need to
+ * disable the interrupts :-) 
+*/
+static inline void
+qdio_unmark_q(struct qdio_q *q)
+{
+	unsigned long flags;
+
+	QDIO_DBF_TEXT4(0,trace,"unmark q");
+	QDIO_DBF_HEX4(0,trace,&q,sizeof(void*));
+
+	if ((!q->list_prev)||(!q->list_next))
+		return;
+
+	if ((q->is_thinint_q)&&(q->is_input_q)) {
+		/* iQDIO */
+		spin_lock_irqsave(&ttiq_list_lock,flags);
+		/* in case cleanup has done this already and simultanously
+		 * qdio_unmark_q is called from the interrupt handler, we've
+		 * got to check this in this specific case again */
+		if ((!q->list_prev)||(!q->list_next))
+			goto out;
+		if (q->list_next==q) {
+			/* q was the only interesting q */
+			tiq_list=NULL;
+			q->list_next=NULL;
+			q->list_prev=NULL;
+		} else {
+			q->list_next->list_prev=q->list_prev;
+			q->list_prev->list_next=q->list_next;
+			tiq_list=q->list_next;
+			q->list_next=NULL;
+			q->list_prev=NULL;
+		}
+out:
+		spin_unlock_irqrestore(&ttiq_list_lock,flags);
+	}
+}
+
+static inline unsigned long 
+tiqdio_clear_global_summary(void)
+{
+	unsigned long time;
+
+	QDIO_DBF_TEXT5(0,trace,"clrglobl");
+	
+	time = do_clear_global_summary();
+
+	QDIO_DBF_HEX5(0,trace,&time,sizeof(unsigned long));
+
+	return time;
+}
+
+
+/************************* OUTBOUND ROUTINES *******************************/
+
+inline static int
+qdio_get_outbound_buffer_frontier(struct qdio_q *q)
+{
+	int f,f_mod_no;
+	volatile char *slsb;
+	int first_not_to_check;
+	char dbf_text[15];
+
+	QDIO_DBF_TEXT4(0,trace,"getobfro");
+	QDIO_DBF_HEX4(0,trace,&q,sizeof(void*));
+
+	slsb=&q->slsb.acc.val[0];
+	f_mod_no=f=q->first_to_check;
+	/* 
+	 * f points to already processed elements, so f+no_used is correct...
+	 * ... but: we don't check 128 buffers, as otherwise
+	 * qdio_has_outbound_q_moved would return 0 
+	 */
+	first_not_to_check=f+qdio_min(atomic_read(&q->number_of_buffers_used),
+				      (QDIO_MAX_BUFFERS_PER_Q-1));
+
+	if ((!q->is_iqdio_q)&&(!q->hydra_gives_outbound_pcis))
+		SYNC_MEMORY;
+
+check_next:
+	if (f==first_not_to_check) 
+		goto out;
+
+	switch(slsb[f_mod_no]) {
+
+        /* the adapter has not fetched the output yet */
+	case SLSB_CU_OUTPUT_PRIMED:
+		QDIO_DBF_TEXT5(0,trace,"outpprim");
+		break;
+
+	/* the adapter got it */
+	case SLSB_P_OUTPUT_EMPTY:
+		atomic_dec(&q->number_of_buffers_used);
+		f++;
+		f_mod_no=f&(QDIO_MAX_BUFFERS_PER_Q-1);
+		QDIO_DBF_TEXT5(0,trace,"outpempt");
+		goto check_next;
+
+	case SLSB_P_OUTPUT_ERROR:
+		QDIO_DBF_TEXT3(0,trace,"outperr");
+		sprintf(dbf_text,"%x-%x-%x",f_mod_no,
+			q->sbal[f_mod_no]->element[14].sbalf.value,
+			q->sbal[f_mod_no]->element[15].sbalf.value);
+		QDIO_DBF_TEXT3(1,trace,dbf_text);
+		QDIO_DBF_HEX2(1,sbal,q->sbal[f_mod_no],256);
+
+		/* kind of process the buffer */
+		set_slsb(&q->slsb.acc.val[f_mod_no], SLSB_P_OUTPUT_NOT_INIT);
+
+		/* 
+		 * we increment the frontier, as this buffer
+		 * was processed obviously 
+		 */
+		atomic_dec(&q->number_of_buffers_used);
+		f_mod_no=(f_mod_no+1)&(QDIO_MAX_BUFFERS_PER_Q-1);
+
+		if (q->qdio_error)
+			q->error_status_flags|=
+				QDIO_STATUS_MORE_THAN_ONE_QDIO_ERROR;
+		q->qdio_error=SLSB_P_OUTPUT_ERROR;
+		q->error_status_flags|=QDIO_STATUS_LOOK_FOR_ERROR;
+
+		break;
+
+	/* no new buffers */
+	default:
+		QDIO_DBF_TEXT5(0,trace,"outpni");
+	}
+out:
+	return (q->first_to_check=f_mod_no);
+}
+
+/* all buffers are processed */
+inline static int
+qdio_is_outbound_q_done(struct qdio_q *q)
+{
+	int no_used;
+#ifdef CONFIG_QDIO_DEBUG
+	char dbf_text[15];
+#endif
+
+	no_used=atomic_read(&q->number_of_buffers_used);
+
+#ifdef CONFIG_QDIO_DEBUG
+	if (no_used) {
+		sprintf(dbf_text,"oqisnt%02x",no_used);
+		QDIO_DBF_TEXT4(0,trace,dbf_text);
+	} else {
+		QDIO_DBF_TEXT4(0,trace,"oqisdone");
+	}
+	QDIO_DBF_HEX4(0,trace,&q,sizeof(void*));
+#endif /* CONFIG_QDIO_DEBUG */
+	return (no_used==0);
+}
+
+inline static int
+qdio_has_outbound_q_moved(struct qdio_q *q)
+{
+	int i;
+
+	i=qdio_get_outbound_buffer_frontier(q);
+
+	if ( (i!=GET_SAVED_FRONTIER(q)) ||
+	     (q->error_status_flags&QDIO_STATUS_LOOK_FOR_ERROR) ) {
+		SAVE_FRONTIER(q,i);
+		QDIO_DBF_TEXT4(0,trace,"oqhasmvd");
+		QDIO_DBF_HEX4(0,trace,&q,sizeof(void*));
+		return 1;
+	} else {
+		QDIO_DBF_TEXT4(0,trace,"oqhsntmv");
+		QDIO_DBF_HEX4(0,trace,&q,sizeof(void*));
+		return 0;
+	}
+}
+
+inline static void
+qdio_kick_outbound_q(struct qdio_q *q)
+{
+	int result;
+#ifdef CONFIG_QDIO_DEBUG
+	char dbf_text[15];
+
+	QDIO_DBF_TEXT4(0,trace,"kickoutq");
+	QDIO_DBF_HEX4(0,trace,&q,sizeof(void*));
+#endif /* CONFIG_QDIO_DEBUG */
+
+	if (!q->siga_out)
+		return;
+
+	/* here's the story with cc=2 and busy bit set (thanks, Rick):
+	 * VM's CP could present us cc=2 and busy bit set on SIGA-write
+	 * during reconfiguration of their Guest LAN (only in HIPERS mode,
+	 * QDIO mode is asynchronous -- cc=2 and busy bit there will take
+	 * the queues down immediately; and not being under VM we have a
+	 * problem on cc=2 and busy bit set right away).
+	 *
+	 * Therefore qdio_siga_output will try for a short time constantly,
+	 * if such a condition occurs. If it doesn't change, it will
+	 * increase the busy_siga_counter and save the timestamp, and
+	 * schedule the queue for later processing (via mark_q, using the
+	 * queue tasklet). __qdio_outbound_processing will check out the
+	 * counter. If non-zero, it will call qdio_kick_outbound_q as often
+	 * as the value of the counter. This will attempt further SIGA
+	 * instructions. For each successful SIGA, the counter is
+	 * decreased, for failing SIGAs the counter remains the same, after
+	 * all.
+	 * After some time of no movement, qdio_kick_outbound_q will
+	 * finally fail and reflect corresponding error codes to call
+	 * the upper layer module and have it take the queues down.
+	 *
+	 * Note that this is a change from the original HiperSockets design
+	 * (saying cc=2 and busy bit means take the queues down), but in
+	 * these days Guest LAN didn't exist... excessive cc=2 with busy bit
+	 * conditions will still take the queues down, but the threshold is
+	 * higher due to the Guest LAN environment.
+	 */
+
+
+	result=qdio_siga_output(q);
+
+		switch (result) {
+		case 0:
+			/* went smooth this time, reset timestamp */
+#ifdef CONFIG_QDIO_DEBUG
+			QDIO_DBF_TEXT3(0,trace,"cc2reslv");
+			sprintf(dbf_text,"%4x%2x%2x",q->irq,q->q_no,
+				atomic_read(&q->busy_siga_counter));
+			QDIO_DBF_TEXT3(0,trace,dbf_text);
+#endif /* CONFIG_QDIO_DEBUG */
+			q->timing.busy_start=0;
+			break;
+		case (2|QDIO_SIGA_ERROR_B_BIT_SET):
+			/* cc=2 and busy bit: */
+			atomic_inc(&q->busy_siga_counter);
+
+			/* if the last siga was successful, save
+			 * timestamp here */
+			if (!q->timing.busy_start)
+				q->timing.busy_start=NOW;
+
+			/* if we're in time, don't touch error_status_flags
+			 * and siga_error */
+			if (NOW-q->timing.busy_start<QDIO_BUSY_BIT_GIVE_UP) {
+				qdio_mark_q(q);
+				break;
+			}
+			QDIO_DBF_TEXT2(0,trace,"cc2REPRT");
+#ifdef CONFIG_QDIO_DEBUG
+			sprintf(dbf_text,"%4x%2x%2x",q->irq,q->q_no,
+				atomic_read(&q->busy_siga_counter));
+			QDIO_DBF_TEXT3(0,trace,dbf_text);
+#endif /* CONFIG_QDIO_DEBUG */
+			/* else fallthrough and report error */
+		default:
+			/* for plain cc=1, 2 or 3: */
+			if (q->siga_error)
+				q->error_status_flags|=
+					QDIO_STATUS_MORE_THAN_ONE_SIGA_ERROR;
+			q->error_status_flags|=
+				QDIO_STATUS_LOOK_FOR_ERROR;
+			q->siga_error=result;
+		}
+}
+
+inline static void
+qdio_kick_outbound_handler(struct qdio_q *q)
+{
+	int start, end, real_end, count;
+#ifdef CONFIG_QDIO_DEBUG
+	char dbf_text[15];
+#endif
+
+	start = q->first_element_to_kick;
+	/* last_move_ftc was just updated */
+	real_end = GET_SAVED_FRONTIER(q);
+	end = (real_end+QDIO_MAX_BUFFERS_PER_Q-1)&
+		(QDIO_MAX_BUFFERS_PER_Q-1);
+	count = (end+QDIO_MAX_BUFFERS_PER_Q+1-start)&
+		(QDIO_MAX_BUFFERS_PER_Q-1);
+
+#ifdef CONFIG_QDIO_DEBUG
+	QDIO_DBF_TEXT4(0,trace,"kickouth");
+	QDIO_DBF_HEX4(0,trace,&q,sizeof(void*));
+
+	sprintf(dbf_text,"s=%2xc=%2x",start,count);
+	QDIO_DBF_TEXT4(0,trace,dbf_text);
+#endif /* CONFIG_QDIO_DEBUG */
+
+	if (q->state==QDIO_IRQ_STATE_ACTIVE)
+		q->handler(q->cdev,QDIO_STATUS_OUTBOUND_INT|
+			   q->error_status_flags,
+			   q->qdio_error,q->siga_error,q->q_no,start,count,
+			   q->int_parm);
+
+	/* for the next time: */
+	q->first_element_to_kick=real_end;
+	q->qdio_error=0;
+	q->siga_error=0;
+	q->error_status_flags=0;
+}
+
+static inline void
+__qdio_outbound_processing(struct qdio_q *q)
+{
+	int siga_attempts;
+
+	QDIO_DBF_TEXT4(0,trace,"qoutproc");
+	QDIO_DBF_HEX4(0,trace,&q,sizeof(void*));
+
+	if (unlikely(qdio_reserve_q(q))) {
+		qdio_release_q(q);
+#ifdef QDIO_PERFORMANCE_STATS
+		o_p_c++;
+#endif /* QDIO_PERFORMANCE_STATS */
+		/* as we're sissies, we'll check next time */
+		if (likely(!atomic_read(&q->is_in_shutdown))) {
+			qdio_mark_q(q);
+			QDIO_DBF_TEXT4(0,trace,"busy,agn");
+		}
+		return;
+	}
+#ifdef QDIO_PERFORMANCE_STATS
+	o_p_nc++;
+	perf_stats.tl_runs++;
+#endif /* QDIO_PERFORMANCE_STATS */
+
+	/* see comment in qdio_kick_outbound_q */
+	siga_attempts=atomic_read(&q->busy_siga_counter);
+	while (siga_attempts) {
+		atomic_dec(&q->busy_siga_counter);
+		qdio_kick_outbound_q(q);
+		siga_attempts--;
+	}
+
+	if (qdio_has_outbound_q_moved(q))
+		qdio_kick_outbound_handler(q);
+
+	if (q->is_iqdio_q) {
+		/* 
+		 * for asynchronous queues, we better check, if the fill
+		 * level is too high. for synchronous queues, the fill
+		 * level will never be that high. 
+		 */
+		if (atomic_read(&q->number_of_buffers_used)>
+		    IQDIO_FILL_LEVEL_TO_POLL)
+			qdio_mark_q(q);
+
+	} else if (!q->hydra_gives_outbound_pcis)
+		if (!qdio_is_outbound_q_done(q))
+			qdio_mark_q(q);
+
+	qdio_release_q(q);
+}
+
+static void
+qdio_outbound_processing(struct qdio_q *q)
+{
+	__qdio_outbound_processing(q);
+}
+
+/************************* INBOUND ROUTINES *******************************/
+
+
+inline static int
+qdio_get_inbound_buffer_frontier(struct qdio_q *q)
+{
+	int f,f_mod_no;
+	volatile char *slsb;
+	int first_not_to_check;
+#ifdef CONFIG_QDIO_DEBUG
+	char dbf_text[15];
+#endif /* CONFIG_QDIO_DEBUG */
+#ifdef QDIO_USE_PROCESSING_STATE
+	int last_position=-1;
+#endif /* QDIO_USE_PROCESSING_STATE */
+
+	QDIO_DBF_TEXT4(0,trace,"getibfro");
+	QDIO_DBF_HEX4(0,trace,&q,sizeof(void*));
+
+	slsb=&q->slsb.acc.val[0];
+	f_mod_no=f=q->first_to_check;
+	/* 
+	 * we don't check 128 buffers, as otherwise qdio_has_inbound_q_moved
+	 * would return 0 
+	 */
+	first_not_to_check=f+qdio_min(atomic_read(&q->number_of_buffers_used),
+				      (QDIO_MAX_BUFFERS_PER_Q-1));
+
+	/* 
+	 * we don't use this one, as a PCI or we after a thin interrupt
+	 * will sync the queues
+	 */
+	/* SYNC_MEMORY;*/
+
+check_next:
+	f_mod_no=f&(QDIO_MAX_BUFFERS_PER_Q-1);
+	if (f==first_not_to_check) 
+		goto out;
+	switch (slsb[f_mod_no]) {
+
+	/* CU_EMPTY means frontier is reached */
+	case SLSB_CU_INPUT_EMPTY:
+		QDIO_DBF_TEXT5(0,trace,"inptempt");
+		break;
+
+	/* P_PRIMED means set slsb to P_PROCESSING and move on */
+	case SLSB_P_INPUT_PRIMED:
+		QDIO_DBF_TEXT5(0,trace,"inptprim");
+
+#ifdef QDIO_USE_PROCESSING_STATE
+		/* 
+		 * as soon as running under VM, polling the input queues will
+		 * kill VM in terms of CP overhead 
+		 */
+		if (q->siga_sync) {
+			set_slsb(&slsb[f_mod_no],SLSB_P_INPUT_NOT_INIT);
+		} else {
+			/* set the previous buffer to NOT_INIT. The current
+			 * buffer will be set to PROCESSING at the end of
+			 * this function to avoid further interrupts. */
+			if (last_position>=0)
+				set_slsb(&slsb[last_position],
+					 SLSB_P_INPUT_NOT_INIT);
+			atomic_set(&q->polling,1);
+			last_position=f_mod_no;
+		}
+#else /* QDIO_USE_PROCESSING_STATE */
+		set_slsb(&slsb[f_mod_no],SLSB_P_INPUT_NOT_INIT);
+#endif /* QDIO_USE_PROCESSING_STATE */
+		/* 
+		 * not needed, as the inbound queue will be synced on the next
+		 * siga-r, resp. tiqdio_is_inbound_q_done will do the siga-s
+		 */
+		/*SYNC_MEMORY;*/
+		f++;
+		atomic_dec(&q->number_of_buffers_used);
+		goto check_next;
+
+	case SLSB_P_INPUT_NOT_INIT:
+	case SLSB_P_INPUT_PROCESSING:
+		QDIO_DBF_TEXT5(0,trace,"inpnipro");
+		break;
+
+	/* P_ERROR means frontier is reached, break and report error */
+	case SLSB_P_INPUT_ERROR:
+#ifdef CONFIG_QDIO_DEBUG
+		sprintf(dbf_text,"inperr%2x",f_mod_no);
+		QDIO_DBF_TEXT3(1,trace,dbf_text);
+#endif /* CONFIG_QDIO_DEBUG */
+		QDIO_DBF_HEX2(1,sbal,q->sbal[f_mod_no],256);
+
+		/* kind of process the buffer */
+		set_slsb(&slsb[f_mod_no],SLSB_P_INPUT_NOT_INIT);
+
+		if (q->qdio_error)
+			q->error_status_flags|=
+				QDIO_STATUS_MORE_THAN_ONE_QDIO_ERROR;
+		q->qdio_error=SLSB_P_INPUT_ERROR;
+		q->error_status_flags|=QDIO_STATUS_LOOK_FOR_ERROR;
+
+		/* we increment the frontier, as this buffer
+		 * was processed obviously */
+		f_mod_no=(f_mod_no+1)&(QDIO_MAX_BUFFERS_PER_Q-1);
+		atomic_dec(&q->number_of_buffers_used);
+
+#ifdef QDIO_USE_PROCESSING_STATE
+		last_position=-1;
+#endif /* QDIO_USE_PROCESSING_STATE */
+
+		break;
+
+	/* everything else means frontier not changed (HALTED or so) */
+	default: 
+		break;
+	}
+out:
+	q->first_to_check=f_mod_no;
+
+#ifdef QDIO_USE_PROCESSING_STATE
+	if (last_position>=0)
+		set_slsb(&slsb[last_position],SLSB_P_INPUT_PROCESSING);
+#endif /* QDIO_USE_PROCESSING_STATE */
+
+	QDIO_DBF_HEX4(0,trace,&q->first_to_check,sizeof(int));
+
+	return q->first_to_check;
+}
+
+inline static int
+qdio_has_inbound_q_moved(struct qdio_q *q)
+{
+	int i;
+
+#ifdef QDIO_PERFORMANCE_STATS
+	static int old_pcis=0;
+	static int old_thinints=0;
+
+	if ((old_pcis==perf_stats.pcis)&&(old_thinints==perf_stats.thinints))
+		perf_stats.start_time_inbound=NOW;
+	else
+		old_pcis=perf_stats.pcis;
+#endif /* QDIO_PERFORMANCE_STATS */
+
+	i=qdio_get_inbound_buffer_frontier(q);
+	if ( (i!=GET_SAVED_FRONTIER(q)) ||
+	     (q->error_status_flags&QDIO_STATUS_LOOK_FOR_ERROR) ) {
+		SAVE_FRONTIER(q,i);
+		if ((!q->siga_sync)&&(!q->hydra_gives_outbound_pcis))
+			SAVE_TIMESTAMP(q);
+
+		QDIO_DBF_TEXT4(0,trace,"inhasmvd");
+		QDIO_DBF_HEX4(0,trace,&q,sizeof(void*));
+		return 1;
+	} else {
+		QDIO_DBF_TEXT4(0,trace,"inhsntmv");
+		QDIO_DBF_HEX4(0,trace,&q,sizeof(void*));
+		return 0;
+	}
+}
+
+/* means, no more buffers to be filled */
+inline static int
+tiqdio_is_inbound_q_done(struct qdio_q *q)
+{
+	int no_used;
+#ifdef CONFIG_QDIO_DEBUG
+	char dbf_text[15];
+#endif
+
+	no_used=atomic_read(&q->number_of_buffers_used);
+
+	/* propagate the change from 82 to 80 through VM */
+	SYNC_MEMORY;
+
+#ifdef CONFIG_QDIO_DEBUG
+	if (no_used) {
+		sprintf(dbf_text,"iqisnt%02x",no_used);
+		QDIO_DBF_TEXT4(0,trace,dbf_text);
+	} else {
+		QDIO_DBF_TEXT4(0,trace,"iniqisdo");
+	}
+	QDIO_DBF_HEX4(0,trace,&q,sizeof(void*));
+#endif /* CONFIG_QDIO_DEBUG */
+
+	if (!no_used)
+		return 1;
+
+	if (!q->siga_sync)
+		/* we'll check for more primed buffers in qeth_stop_polling */
+		return 0;
+
+	if (q->slsb.acc.val[q->first_to_check]!=SLSB_P_INPUT_PRIMED)
+		/* 
+		 * nothing more to do, if next buffer is not PRIMED.
+		 * note that we did a SYNC_MEMORY before, that there
+		 * has been a sychnronization.
+		 * we will return 0 below, as there is nothing to do
+		 * (stop_polling not necessary, as we have not been
+		 * using the PROCESSING state 
+		 */
+		return 0;
+
+	/* 
+	 * ok, the next input buffer is primed. that means, that device state 
+	 * change indicator and adapter local summary are set, so we will find
+	 * it next time.
+	 * we will return 0 below, as there is nothing to do, except scheduling
+	 * ourselves for the next time. 
+	 */
+	tiqdio_set_summary_bit((__u32*)q->dev_st_chg_ind);
+	tiqdio_sched_tl();
+	return 0;
+}
+
+inline static int
+qdio_is_inbound_q_done(struct qdio_q *q)
+{
+	int no_used;
+#ifdef CONFIG_QDIO_DEBUG
+	char dbf_text[15];
+#endif
+
+	no_used=atomic_read(&q->number_of_buffers_used);
+
+	/* 
+	 * we need that one for synchronization with the adapter, as it
+	 * does a kind of PCI avoidance 
+	 */
+	SYNC_MEMORY;
+
+	if (!no_used) {
+		QDIO_DBF_TEXT4(0,trace,"inqisdnA");
+		QDIO_DBF_HEX4(0,trace,&q,sizeof(void*));
+		QDIO_DBF_TEXT4(0,trace,dbf_text);
+		return 1;
+	}
+
+	if (q->slsb.acc.val[q->first_to_check]==SLSB_P_INPUT_PRIMED) {
+		/* we got something to do */
+		QDIO_DBF_TEXT4(0,trace,"inqisntA");
+		QDIO_DBF_HEX4(0,trace,&q,sizeof(void*));
+		return 0;
+	}
+
+	/* on VM, we don't poll, so the q is always done here */
+	if (q->siga_sync)
+		return 1;
+	if (q->hydra_gives_outbound_pcis)
+		return 1;
+
+	/* 
+	 * at this point we know, that inbound first_to_check
+	 * has (probably) not moved (see qdio_inbound_processing) 
+	 */
+	if (NOW>GET_SAVED_TIMESTAMP(q)+q->timing.threshold) {
+#ifdef CONFIG_QDIO_DEBUG
+		QDIO_DBF_TEXT4(0,trace,"inqisdon");
+		QDIO_DBF_HEX4(0,trace,&q,sizeof(void*));
+		sprintf(dbf_text,"pf%02xcn%02x",q->first_to_check,no_used);
+		QDIO_DBF_TEXT4(0,trace,dbf_text);
+#endif /* CONFIG_QDIO_DEBUG */
+		return 1;
+	} else {
+#ifdef CONFIG_QDIO_DEBUG
+		QDIO_DBF_TEXT4(0,trace,"inqisntd");
+		QDIO_DBF_HEX4(0,trace,&q,sizeof(void*));
+		sprintf(dbf_text,"pf%02xcn%02x",q->first_to_check,no_used);
+		QDIO_DBF_TEXT4(0,trace,dbf_text);
+#endif /* CONFIG_QDIO_DEBUG */
+		return 0;
+	}
+}
+
+inline static void
+qdio_kick_inbound_handler(struct qdio_q *q)
+{
+	int count, start, end, real_end, i;
+#ifdef CONFIG_QDIO_DEBUG
+	char dbf_text[15];
+#endif
+
+	QDIO_DBF_TEXT4(0,trace,"kickinh");
+	QDIO_DBF_HEX4(0,trace,&q,sizeof(void*));
+
+  	start=q->first_element_to_kick;
+ 	real_end=q->first_to_check;
+ 	end=(real_end+QDIO_MAX_BUFFERS_PER_Q-1)&(QDIO_MAX_BUFFERS_PER_Q-1);
+ 
+ 	i=start;
+	count=0;
+ 	while (1) {
+ 		count++;
+ 		if (i==end)
+			break;
+ 		i=(i+1)&(QDIO_MAX_BUFFERS_PER_Q-1);
+ 	}
+
+#ifdef CONFIG_QDIO_DEBUG
+	sprintf(dbf_text,"s=%2xc=%2x",start,count);
+	QDIO_DBF_TEXT4(0,trace,dbf_text);
+#endif /* CONFIG_QDIO_DEBUG */
+
+	if (likely(q->state==QDIO_IRQ_STATE_ACTIVE))
+		q->handler(q->cdev,
+			   QDIO_STATUS_INBOUND_INT|q->error_status_flags,
+			   q->qdio_error,q->siga_error,q->q_no,start,count,
+			   q->int_parm);
+
+	/* for the next time: */
+	q->first_element_to_kick=real_end;
+	q->qdio_error=0;
+	q->siga_error=0;
+	q->error_status_flags=0;
+
+#ifdef QDIO_PERFORMANCE_STATS
+	perf_stats.inbound_time+=NOW-perf_stats.start_time_inbound;
+	perf_stats.inbound_cnt++;
+#endif /* QDIO_PERFORMANCE_STATS */
+}
+
+static inline void
+__tiqdio_inbound_processing(struct qdio_q *q, int spare_ind_was_set)
+{
+	struct qdio_irq *irq_ptr;
+	struct qdio_q *oq;
+	int i;
+
+	QDIO_DBF_TEXT4(0,trace,"iqinproc");
+	QDIO_DBF_HEX4(0,trace,&q,sizeof(void*));
+
+	/* 
+	 * we first want to reserve the q, so that we know, that we don't
+	 * interrupt ourselves and call qdio_unmark_q, as is_in_shutdown might
+	 * be set 
+	 */
+	if (unlikely(qdio_reserve_q(q))) {
+		qdio_release_q(q);
+#ifdef QDIO_PERFORMANCE_STATS
+		ii_p_c++;
+#endif /* QDIO_PERFORMANCE_STATS */
+		/* 
+		 * as we might just be about to stop polling, we make
+		 * sure that we check again at least once more 
+		 */
+		tiqdio_sched_tl();
+		return;
+	}
+#ifdef QDIO_PERFORMANCE_STATS
+	ii_p_nc++;
+#endif /* QDIO_PERFORMANCE_STATS */
+	if (unlikely(atomic_read(&q->is_in_shutdown))) {
+		qdio_unmark_q(q);
+		goto out;
+	}
+
+	/* 
+	 * we reset spare_ind_was_set, when the queue does not use the
+	 * spare indicator
+	 */
+	if (spare_ind_was_set)
+		spare_ind_was_set = (q->dev_st_chg_ind == &spare_indicator);
+
+	if (!(*(q->dev_st_chg_ind)) && !spare_ind_was_set)
+		goto out;
+	/*
+	 * q->dev_st_chg_ind is the indicator, be it shared or not.
+	 * only clear it, if indicator is non-shared
+	 */
+	if (!spare_ind_was_set)
+		tiqdio_clear_summary_bit((__u32*)q->dev_st_chg_ind);
+
+	if (q->hydra_gives_outbound_pcis) {
+		if (!q->siga_sync_done_on_thinints) {
+			SYNC_MEMORY_ALL;
+		} else if ((!q->siga_sync_done_on_outb_tis)&&
+			 (q->hydra_gives_outbound_pcis)) {
+			SYNC_MEMORY_ALL_OUTB;
+		}
+	} else {
+		SYNC_MEMORY;
+	}
+	/*
+	 * maybe we have to do work on our outbound queues... at least
+	 * we have to check the outbound-int-capable thinint-capable
+	 * queues
+	 */
+	if (q->hydra_gives_outbound_pcis) {
+		irq_ptr = (struct qdio_irq*)q->irq_ptr;
+		for (i=0;i<irq_ptr->no_output_qs;i++) {
+			oq = irq_ptr->output_qs[i];
+#ifdef QDIO_PERFORMANCE_STATS
+			perf_stats.tl_runs--;
+#endif /* QDIO_PERFORMANCE_STATS */
+			if (!qdio_is_outbound_q_done(oq))
+				__qdio_outbound_processing(oq);
+		}
+	}
+
+	if (!qdio_has_inbound_q_moved(q))
+		goto out;
+
+	qdio_kick_inbound_handler(q);
+	if (tiqdio_is_inbound_q_done(q))
+		if (!qdio_stop_polling(q)) {
+			/* 
+			 * we set the flags to get into the stuff next time,
+			 * see also comment in qdio_stop_polling 
+			 */
+			tiqdio_set_summary_bit((__u32*)q->dev_st_chg_ind);
+			tiqdio_sched_tl();
+		}
+out:
+	qdio_release_q(q);
+}
+
+static void
+tiqdio_inbound_processing(struct qdio_q *q)
+{
+	__tiqdio_inbound_processing(q, atomic_read(&spare_indicator_usecount));
+}
+
+static inline void
+__qdio_inbound_processing(struct qdio_q *q)
+{
+	int q_laps=0;
+
+	QDIO_DBF_TEXT4(0,trace,"qinproc");
+	QDIO_DBF_HEX4(0,trace,&q,sizeof(void*));
+
+	if (unlikely(qdio_reserve_q(q))) {
+		qdio_release_q(q);
+#ifdef QDIO_PERFORMANCE_STATS
+		i_p_c++;
+#endif /* QDIO_PERFORMANCE_STATS */
+		/* as we're sissies, we'll check next time */
+		if (likely(!atomic_read(&q->is_in_shutdown))) {
+			qdio_mark_q(q);
+			QDIO_DBF_TEXT4(0,trace,"busy,agn");
+		}
+		return;
+	}
+#ifdef QDIO_PERFORMANCE_STATS
+	i_p_nc++;
+	perf_stats.tl_runs++;
+#endif /* QDIO_PERFORMANCE_STATS */
+
+again:
+	if (qdio_has_inbound_q_moved(q)) {
+		qdio_kick_inbound_handler(q);
+		if (!qdio_stop_polling(q)) {
+			q_laps++;
+			if (q_laps<QDIO_Q_LAPS) 
+				goto again;
+		}
+		qdio_mark_q(q);
+	} else {
+		if (!qdio_is_inbound_q_done(q)) 
+                        /* means poll time is not yet over */
+			qdio_mark_q(q);
+	}
+
+	qdio_release_q(q);
+}
+
+static void
+qdio_inbound_processing(struct qdio_q *q)
+{
+	__qdio_inbound_processing(q);
+}
+
+/************************* MAIN ROUTINES *******************************/
+
+#ifdef QDIO_USE_PROCESSING_STATE
+static inline int
+tiqdio_reset_processing_state(struct qdio_q *q, int q_laps)
+{
+	if (!q) {
+		tiqdio_sched_tl();
+		return 0;
+	}
+
+	/* 
+	 * under VM, we have not used the PROCESSING state, so no
+	 * need to stop polling 
+	 */
+	if (q->siga_sync)
+		return 2;
+
+	if (unlikely(qdio_reserve_q(q))) {
+		qdio_release_q(q);
+#ifdef QDIO_PERFORMANCE_STATS
+		ii_p_c++;
+#endif /* QDIO_PERFORMANCE_STATS */
+		/* 
+		 * as we might just be about to stop polling, we make
+		 * sure that we check again at least once more 
+		 */
+		
+		/* 
+		 * sanity -- we'd get here without setting the
+		 * dev st chg ind 
+		 */
+		tiqdio_set_summary_bit((__u32*)q->dev_st_chg_ind);
+		tiqdio_sched_tl();
+		return 0;
+	}
+	if (qdio_stop_polling(q)) {
+		qdio_release_q(q);
+		return 2;
+	}		
+	if (q_laps<QDIO_Q_LAPS-1) {
+		qdio_release_q(q);
+		return 3;
+	}
+	/* 
+	 * we set the flags to get into the stuff
+	 * next time, see also comment in qdio_stop_polling 
+	 */
+	tiqdio_set_summary_bit((__u32*)q->dev_st_chg_ind);
+	tiqdio_sched_tl();
+	qdio_release_q(q);
+	return 1;
+	
+}
+#endif /* QDIO_USE_PROCESSING_STATE */
+
+static inline void
+tiqdio_inbound_checks(void)
+{
+	struct qdio_q *q;
+	int spare_ind_was_set=0;
+#ifdef QDIO_USE_PROCESSING_STATE
+	int q_laps=0;
+#endif /* QDIO_USE_PROCESSING_STATE */
+
+	QDIO_DBF_TEXT4(0,trace,"iqdinbck");
+	QDIO_DBF_TEXT5(0,trace,"iqlocsum");
+
+#ifdef QDIO_USE_PROCESSING_STATE
+again:
+#endif /* QDIO_USE_PROCESSING_STATE */
+
+	/* when the spare indicator is used and set, save that and clear it */
+	if ((atomic_read(&spare_indicator_usecount)) && spare_indicator) {
+		spare_ind_was_set = 1;
+		tiqdio_clear_summary_bit((__u32*)&spare_indicator);
+	}
+
+	q=(struct qdio_q*)tiq_list;
+	do {
+		if (!q)
+			break;
+		__tiqdio_inbound_processing(q, spare_ind_was_set);
+		q=(struct qdio_q*)q->list_next;
+	} while (q!=(struct qdio_q*)tiq_list);
+
+#ifdef QDIO_USE_PROCESSING_STATE
+	q=(struct qdio_q*)tiq_list;
+	do {
+		int ret;
+
+		ret = tiqdio_reset_processing_state(q, q_laps);
+		switch (ret) {
+		case 0:
+			return;
+		case 1:
+			q_laps++;
+		case 2:
+			q = (struct qdio_q*)q->list_next;
+			break;
+		default:
+			q_laps++;
+			goto again;
+		}
+	} while (q!=(struct qdio_q*)tiq_list);
+#endif /* QDIO_USE_PROCESSING_STATE */
+}
+
+static void
+tiqdio_tl(unsigned long data)
+{
+	QDIO_DBF_TEXT4(0,trace,"iqdio_tl");
+
+#ifdef QDIO_PERFORMANCE_STATS
+	perf_stats.tl_runs++;
+#endif /* QDIO_PERFORMANCE_STATS */
+
+	tiqdio_inbound_checks();
+}
+
+/********************* GENERAL HELPER_ROUTINES ***********************/
+
+static void
+qdio_release_irq_memory(struct qdio_irq *irq_ptr)
+{
+	int i;
+
+	for (i=0;i<QDIO_MAX_QUEUES_PER_IRQ;i++) {
+		if (!irq_ptr->input_qs[i])
+			goto next;
+
+		if (irq_ptr->input_qs[i]->slib)
+			kfree(irq_ptr->input_qs[i]->slib);
+		kfree(irq_ptr->input_qs[i]);
+
+next:
+		if (!irq_ptr->output_qs[i])
+			continue;
+
+		if (irq_ptr->output_qs[i]->slib)
+			kfree(irq_ptr->output_qs[i]->slib);
+		kfree(irq_ptr->output_qs[i]);
+
+	}
+	kfree(irq_ptr->qdr);
+	kfree(irq_ptr);
+}
+
+static void
+qdio_set_impl_params(struct qdio_irq *irq_ptr,
+		     unsigned int qib_param_field_format,
+		     /* pointer to 128 bytes or NULL, if no param field */
+		     unsigned char *qib_param_field,
+		     /* pointer to no_queues*128 words of data or NULL */
+		     unsigned int no_input_qs,
+		     unsigned int no_output_qs,
+		     unsigned long *input_slib_elements,
+		     unsigned long *output_slib_elements)
+{
+	int i,j;
+
+	if (!irq_ptr)
+		return;
+
+	irq_ptr->qib.pfmt=qib_param_field_format;
+	if (qib_param_field)
+		memcpy(irq_ptr->qib.parm,qib_param_field,
+		       QDIO_MAX_BUFFERS_PER_Q);
+
+	if (input_slib_elements)
+		for (i=0;i<no_input_qs;i++) {
+			for (j=0;j<QDIO_MAX_BUFFERS_PER_Q;j++)
+				irq_ptr->input_qs[i]->slib->slibe[j].parms=
+					input_slib_elements[
+						i*QDIO_MAX_BUFFERS_PER_Q+j];
+		}
+	if (output_slib_elements)
+		for (i=0;i<no_output_qs;i++) {
+			for (j=0;j<QDIO_MAX_BUFFERS_PER_Q;j++)
+				irq_ptr->output_qs[i]->slib->slibe[j].parms=
+					output_slib_elements[
+						i*QDIO_MAX_BUFFERS_PER_Q+j];
+		}
+}
+
+static int
+qdio_alloc_qs(struct qdio_irq *irq_ptr,
+	      int no_input_qs, int no_output_qs)
+{
+	int i;
+	struct qdio_q *q;
+	int result=-ENOMEM;
+
+	for (i=0;i<no_input_qs;i++) {
+		q=kmalloc(sizeof(struct qdio_q),GFP_KERNEL);
+
+		if (!q) {
+			QDIO_PRINT_ERR("kmalloc of q failed!\n");
+			goto out;
+		}
+
+		memset(q,0,sizeof(struct qdio_q));
+
+		q->slib=kmalloc(PAGE_SIZE,GFP_KERNEL);
+		if (!q->slib) {
+			QDIO_PRINT_ERR("kmalloc of slib failed!\n");
+			goto out;
+		}
+
+		irq_ptr->input_qs[i]=q;
+	}
+
+	for (i=0;i<no_output_qs;i++) {
+		q=kmalloc(sizeof(struct qdio_q),GFP_KERNEL);
+
+		if (!q) {
+			goto out;
+		}
+
+		memset(q,0,sizeof(struct qdio_q));
+
+		q->slib=kmalloc(PAGE_SIZE,GFP_KERNEL);
+		if (!q->slib) {
+			QDIO_PRINT_ERR("kmalloc of slib failed!\n");
+			goto out;
+		}
+
+		irq_ptr->output_qs[i]=q;
+	}
+
+	result=0;
+out:
+	return result;
+}
+
+static void
+qdio_fill_qs(struct qdio_irq *irq_ptr, struct ccw_device *cdev,
+       	     int no_input_qs, int no_output_qs,
+	     qdio_handler_t *input_handler,
+	     qdio_handler_t *output_handler,
+	     unsigned long int_parm,int q_format,
+	     unsigned long flags,
+	     void **inbound_sbals_array,
+	     void **outbound_sbals_array)
+{
+	struct qdio_q *q;
+	int i,j;
+	char dbf_text[20]; /* see qdio_initialize */
+	void *ptr;
+	int available;
+
+	sprintf(dbf_text,"qfqs%4x",cdev->private->irq);
+	QDIO_DBF_TEXT0(0,setup,dbf_text);
+	for (i=0;i<no_input_qs;i++) {
+		q=irq_ptr->input_qs[i];
+
+		memset(q,0,((char*)&q->slib)-((char*)q));
+		sprintf(dbf_text,"in-q%4x",i);
+		QDIO_DBF_TEXT0(0,setup,dbf_text);
+		QDIO_DBF_HEX0(0,setup,&q,sizeof(void*));
+
+		memset(q->slib,0,PAGE_SIZE);
+		q->sl=(struct sl*)(((char*)q->slib)+PAGE_SIZE/2);
+
+		available=0;
+
+		for (j=0;j<QDIO_MAX_BUFFERS_PER_Q;j++)
+			q->sbal[j]=*(inbound_sbals_array++);
+
+                q->queue_type=q_format;
+		q->int_parm=int_parm;
+		q->irq=irq_ptr->irq;
+		q->irq_ptr = irq_ptr;
+		q->cdev = cdev;
+		q->mask=1<<(31-i);
+		q->q_no=i;
+		q->is_input_q=1;
+		q->first_to_check=0;
+		q->last_move_ftc=0;
+		q->handler=input_handler;
+		q->dev_st_chg_ind=irq_ptr->dev_st_chg_ind;
+
+		q->tasklet.data=(unsigned long)q;
+		/* q->is_thinint_q isn't valid at this time, but
+		 * irq_ptr->is_thinint_irq is */
+		q->tasklet.func=(void(*)(unsigned long))
+			((irq_ptr->is_thinint_irq)?&tiqdio_inbound_processing:
+			 &qdio_inbound_processing);
+
+		/* actually this is not used for inbound queues. yet. */
+		atomic_set(&q->busy_siga_counter,0);
+		q->timing.busy_start=0;
+
+/*		for (j=0;j<QDIO_STATS_NUMBER;j++)
+			q->timing.last_transfer_times[j]=(qdio_get_micros()/
+							  QDIO_STATS_NUMBER)*j;
+		q->timing.last_transfer_index=QDIO_STATS_NUMBER-1;
+*/
+
+		/* fill in slib */
+		if (i>0) irq_ptr->input_qs[i-1]->slib->nsliba=
+				 (unsigned long)(q->slib);
+		q->slib->sla=(unsigned long)(q->sl);
+		q->slib->slsba=(unsigned long)(&q->slsb.acc.val[0]);
+
+		/* fill in sl */
+		for (j=0;j<QDIO_MAX_BUFFERS_PER_Q;j++)
+			q->sl->element[j].sbal=(unsigned long)(q->sbal[j]);
+
+		QDIO_DBF_TEXT2(0,setup,"sl-sb-b0");
+		ptr=(void*)q->sl;
+		QDIO_DBF_HEX2(0,setup,&ptr,sizeof(void*));
+		ptr=(void*)&q->slsb;
+		QDIO_DBF_HEX2(0,setup,&ptr,sizeof(void*));
+		ptr=(void*)q->sbal[0];
+		QDIO_DBF_HEX2(0,setup,&ptr,sizeof(void*));
+
+		/* fill in slsb */
+		for (j=0;j<QDIO_MAX_BUFFERS_PER_Q;j++) {
+			set_slsb(&q->slsb.acc.val[j],
+		   		 SLSB_P_INPUT_NOT_INIT);
+/*			q->sbal[j]->element[1].sbalf.i1.key=QDIO_STORAGE_KEY;*/
+		}
+	}
+
+	for (i=0;i<no_output_qs;i++) {
+		q=irq_ptr->output_qs[i];
+		memset(q,0,((char*)&q->slib)-((char*)q));
+
+		sprintf(dbf_text,"outq%4x",i);
+		QDIO_DBF_TEXT0(0,setup,dbf_text);
+		QDIO_DBF_HEX0(0,setup,&q,sizeof(void*));
+
+		memset(q->slib,0,PAGE_SIZE);
+		q->sl=(struct sl*)(((char*)q->slib)+PAGE_SIZE/2);
+
+		available=0;
+		
+		for (j=0;j<QDIO_MAX_BUFFERS_PER_Q;j++)
+			q->sbal[j]=*(outbound_sbals_array++);
+
+                q->queue_type=q_format;
+		q->int_parm=int_parm;
+		q->is_input_q=0;
+		q->irq=irq_ptr->irq;
+		q->cdev = cdev;
+		q->irq_ptr = irq_ptr;
+		q->mask=1<<(31-i);
+		q->q_no=i;
+		q->first_to_check=0;
+		q->last_move_ftc=0;
+		q->handler=output_handler;
+
+		q->tasklet.data=(unsigned long)q;
+		q->tasklet.func=(void(*)(unsigned long))
+			&qdio_outbound_processing;
+
+		atomic_set(&q->busy_siga_counter,0);
+		q->timing.busy_start=0;
+
+		/* fill in slib */
+		if (i>0) irq_ptr->output_qs[i-1]->slib->nsliba=
+				 (unsigned long)(q->slib);
+		q->slib->sla=(unsigned long)(q->sl);
+		q->slib->slsba=(unsigned long)(&q->slsb.acc.val[0]);
+
+		/* fill in sl */
+		for (j=0;j<QDIO_MAX_BUFFERS_PER_Q;j++)
+			q->sl->element[j].sbal=(unsigned long)(q->sbal[j]);
+
+		QDIO_DBF_TEXT2(0,setup,"sl-sb-b0");
+		ptr=(void*)q->sl;
+		QDIO_DBF_HEX2(0,setup,&ptr,sizeof(void*));
+		ptr=(void*)&q->slsb;
+		QDIO_DBF_HEX2(0,setup,&ptr,sizeof(void*));
+		ptr=(void*)q->sbal[0];
+		QDIO_DBF_HEX2(0,setup,&ptr,sizeof(void*));
+
+		/* fill in slsb */
+		for (j=0;j<QDIO_MAX_BUFFERS_PER_Q;j++) {
+			set_slsb(&q->slsb.acc.val[j],
+		   		 SLSB_P_OUTPUT_NOT_INIT);
+/*			q->sbal[j]->element[1].sbalf.i1.key=QDIO_STORAGE_KEY;*/
+		}
+	}
+}
+
+static void
+qdio_fill_thresholds(struct qdio_irq *irq_ptr,
+		     unsigned int no_input_qs,
+		     unsigned int no_output_qs,
+		     unsigned int min_input_threshold,
+		     unsigned int max_input_threshold,
+		     unsigned int min_output_threshold,
+		     unsigned int max_output_threshold)
+{
+	int i;
+	struct qdio_q *q;
+
+	for (i=0;i<no_input_qs;i++) {
+		q=irq_ptr->input_qs[i];
+		q->timing.threshold=max_input_threshold;
+/*		for (j=0;j<QDIO_STATS_CLASSES;j++) {
+			q->threshold_classes[j].threshold=
+				min_input_threshold+
+				(max_input_threshold-min_input_threshold)/
+				QDIO_STATS_CLASSES;
+		}
+		qdio_use_thresholds(q,QDIO_STATS_CLASSES/2);*/
+	}
+	for (i=0;i<no_output_qs;i++) {
+		q=irq_ptr->output_qs[i];
+		q->timing.threshold=max_output_threshold;
+/*		for (j=0;j<QDIO_STATS_CLASSES;j++) {
+			q->threshold_classes[j].threshold=
+				min_output_threshold+
+				(max_output_threshold-min_output_threshold)/
+				QDIO_STATS_CLASSES;
+		}
+		qdio_use_thresholds(q,QDIO_STATS_CLASSES/2);*/
+	}
+}
+
+static int
+tiqdio_thinint_handler(void)
+{
+	QDIO_DBF_TEXT4(0,trace,"thin_int");
+
+#ifdef QDIO_PERFORMANCE_STATS
+	perf_stats.thinints++;
+	perf_stats.start_time_inbound=NOW;
+#endif /* QDIO_PERFORMANCE_STATS */
+
+	/* SVS only when needed:
+	 * issue SVS to benefit from iqdio interrupt avoidance
+	 * (SVS clears AISOI)*/
+	if (!omit_svs)
+		tiqdio_clear_global_summary();
+
+	tiqdio_inbound_checks();
+	return 0;
+}
+
+static void
+qdio_set_state(struct qdio_irq *irq_ptr, enum qdio_irq_states state)
+{
+	int i;
+#ifdef CONFIG_QDIO_DEBUG
+	char dbf_text[15];
+
+	QDIO_DBF_TEXT5(0,trace,"newstate");
+	sprintf(dbf_text,"%4x%4x",irq_ptr->irq,state);
+	QDIO_DBF_TEXT5(0,trace,dbf_text);
+#endif /* CONFIG_QDIO_DEBUG */
+
+	irq_ptr->state=state;
+	for (i=0;i<irq_ptr->no_input_qs;i++)
+		irq_ptr->input_qs[i]->state=state;
+	for (i=0;i<irq_ptr->no_output_qs;i++)
+		irq_ptr->output_qs[i]->state=state;
+	mb();
+}
+
+static inline void
+qdio_irq_check_sense(int irq, struct irb *irb)
+{
+	char dbf_text[15];
+
+	if (irb->esw.esw0.erw.cons) {
+		sprintf(dbf_text,"sens%4x",irq);
+		QDIO_DBF_TEXT2(1,trace,dbf_text);
+		QDIO_DBF_HEX0(0,sense,irb,QDIO_DBF_SENSE_LEN);
+
+		QDIO_PRINT_WARN("sense data available on qdio channel.\n");
+		HEXDUMP16(WARN,"irb: ",irb);
+		HEXDUMP16(WARN,"sense data: ",irb->ecw);
+	}
+		
+}
+
+static inline void
+qdio_handle_pci(struct qdio_irq *irq_ptr)
+{
+	int i;
+	struct qdio_q *q;
+
+#ifdef QDIO_PERFORMANCE_STATS
+	perf_stats.pcis++;
+	perf_stats.start_time_inbound=NOW;
+#endif /* QDIO_PERFORMANCE_STATS */
+	for (i=0;i<irq_ptr->no_input_qs;i++) {
+		q=irq_ptr->input_qs[i];
+		if (q->is_input_q&QDIO_FLAG_NO_INPUT_INTERRUPT_CONTEXT)
+			qdio_mark_q(q);
+		else {
+#ifdef QDIO_PERFORMANCE_STATS
+			perf_stats.tl_runs--;
+#endif /* QDIO_PERFORMANCE_STATS */
+			__qdio_inbound_processing(q);
+		}
+	}
+	if (!irq_ptr->hydra_gives_outbound_pcis)
+		return;
+	for (i=0;i<irq_ptr->no_output_qs;i++) {
+		q=irq_ptr->output_qs[i];
+#ifdef QDIO_PERFORMANCE_STATS
+		perf_stats.tl_runs--;
+#endif /* QDIO_PERFORMANCE_STATS */
+		if (qdio_is_outbound_q_done(q))
+			continue;
+		if (!irq_ptr->sync_done_on_outb_pcis)
+			SYNC_MEMORY;
+		__qdio_outbound_processing(q);
+	}
+}
+
+static void qdio_establish_handle_irq(struct ccw_device*, int, int);
+
+static inline void
+qdio_handle_activate_check(struct ccw_device *cdev, unsigned long intparm,
+			   int cstat, int dstat)
+{
+	struct qdio_irq *irq_ptr;
+	struct qdio_q *q;
+	char dbf_text[15];
+
+	irq_ptr = cdev->private->qdio_data;
+
+	QDIO_DBF_TEXT2(1, trace, "ick2");
+	sprintf(dbf_text,"%s", cdev->dev.bus_id);
+	QDIO_DBF_TEXT2(1,trace,dbf_text);
+	QDIO_DBF_HEX2(0,trace,&intparm,sizeof(int));
+	QDIO_DBF_HEX2(0,trace,&dstat,sizeof(int));
+	QDIO_DBF_HEX2(0,trace,&cstat,sizeof(int));
+	QDIO_PRINT_ERR("received check condition on activate " \
+		       "queues on device %s (cs=x%x, ds=x%x).\n",
+		       cdev->dev.bus_id, cstat, dstat);
+	if (irq_ptr->no_input_qs) {
+		q=irq_ptr->input_qs[0];
+	} else if (irq_ptr->no_output_qs) {
+		q=irq_ptr->output_qs[0];
+	} else {
+		QDIO_PRINT_ERR("oops... no queue registered for device %s!?\n",
+			       cdev->dev.bus_id);
+		goto omit_handler_call;
+	}
+	q->handler(q->cdev,QDIO_STATUS_ACTIVATE_CHECK_CONDITION|
+		   QDIO_STATUS_LOOK_FOR_ERROR,
+		   0,0,0,-1,-1,q->int_parm);
+omit_handler_call:
+	qdio_set_state(irq_ptr,QDIO_IRQ_STATE_STOPPED);
+
+}
+
+static void
+qdio_call_shutdown(void *data)
+{
+	struct ccw_device *cdev;
+
+	cdev = (struct ccw_device *)data;
+	qdio_shutdown(cdev, QDIO_FLAG_CLEANUP_USING_CLEAR);
+	put_device(&cdev->dev);
+}
+
+static void
+qdio_timeout_handler(struct ccw_device *cdev)
+{
+	struct qdio_irq *irq_ptr;
+	char dbf_text[15];
+
+	QDIO_DBF_TEXT2(0, trace, "qtoh");
+	sprintf(dbf_text, "%s", cdev->dev.bus_id);
+	QDIO_DBF_TEXT2(0, trace, dbf_text);
+
+	irq_ptr = cdev->private->qdio_data;
+	sprintf(dbf_text, "state:%d", irq_ptr->state);
+	QDIO_DBF_TEXT2(0, trace, dbf_text);
+
+	switch (irq_ptr->state) {
+	case QDIO_IRQ_STATE_INACTIVE:
+		QDIO_PRINT_ERR("establish queues on irq %04x: timed out\n",
+			       irq_ptr->irq);
+		QDIO_DBF_TEXT2(1,setup,"eq:timeo");
+		qdio_set_state(irq_ptr, QDIO_IRQ_STATE_ERR);
+		break;
+	case QDIO_IRQ_STATE_CLEANUP:
+		QDIO_PRINT_INFO("Did not get interrupt on cleanup, irq=0x%x.\n",
+				irq_ptr->irq);
+		qdio_set_state(irq_ptr, QDIO_IRQ_STATE_ERR);
+		break;
+	case QDIO_IRQ_STATE_ESTABLISHED:
+	case QDIO_IRQ_STATE_ACTIVE:
+		/* I/O has been terminated by common I/O layer. */
+		QDIO_PRINT_INFO("Queues on irq %04x killed by cio.\n",
+				irq_ptr->irq);
+		QDIO_DBF_TEXT2(1, trace, "cio:term");
+		qdio_set_state(irq_ptr, QDIO_IRQ_STATE_STOPPED);
+		if (get_device(&cdev->dev)) {
+			/* Can't call shutdown from interrupt context. */
+			PREPARE_WORK(&cdev->private->kick_work,
+				     qdio_call_shutdown, (void *)cdev);
+			queue_work(ccw_device_work, &cdev->private->kick_work);
+		}
+		break;
+	default:
+		BUG();
+	}
+	ccw_device_set_timeout(cdev, 0);
+	wake_up(&cdev->private->wait_q);
+}
+
+static void
+qdio_handler(struct ccw_device *cdev, unsigned long intparm, struct irb *irb)
+{
+	struct qdio_irq *irq_ptr;
+	int cstat,dstat;
+	char dbf_text[15];
+
+#ifdef CONFIG_QDIO_DEBUG
+	QDIO_DBF_TEXT4(0, trace, "qint");
+	sprintf(dbf_text, "%s", cdev->dev.bus_id);
+	QDIO_DBF_TEXT4(0, trace, dbf_text);
+#endif /* CONFIG_QDIO_DEBUG */
+	
+	if (!intparm) {
+		QDIO_PRINT_ERR("got unsolicited interrupt in qdio " \
+				  "handler, device %s\n", cdev->dev.bus_id);
+		return;
+	}
+
+	irq_ptr = cdev->private->qdio_data;
+	if (!irq_ptr) {
+		QDIO_DBF_TEXT2(1, trace, "uint");
+		sprintf(dbf_text,"%s", cdev->dev.bus_id);
+		QDIO_DBF_TEXT2(1,trace,dbf_text);
+		QDIO_PRINT_ERR("received interrupt on unused device %s!\n",
+			       cdev->dev.bus_id);
+		return;
+	}
+
+	if (IS_ERR(irb)) {
+		/* Currently running i/o is in error. */
+		switch (PTR_ERR(irb)) {
+		case -EIO:
+			QDIO_PRINT_ERR("i/o error on device %s\n",
+				       cdev->dev.bus_id);
+			return;
+		case -ETIMEDOUT:
+			qdio_timeout_handler(cdev);
+			return;
+		default:
+			QDIO_PRINT_ERR("unknown error state %ld on device %s\n",
+				       PTR_ERR(irb), cdev->dev.bus_id);
+			return;
+		}
+	}
+
+	qdio_irq_check_sense(irq_ptr->irq, irb);
+
+#ifdef CONFIG_QDIO_DEBUG
+	sprintf(dbf_text, "state:%d", irq_ptr->state);
+	QDIO_DBF_TEXT4(0, trace, dbf_text);
+#endif /* CONFIG_QDIO_DEBUG */
+
+        cstat = irb->scsw.cstat;
+        dstat = irb->scsw.dstat;
+
+	switch (irq_ptr->state) {
+	case QDIO_IRQ_STATE_INACTIVE:
+		qdio_establish_handle_irq(cdev, cstat, dstat);
+		break;
+
+	case QDIO_IRQ_STATE_CLEANUP:
+		qdio_set_state(irq_ptr, QDIO_IRQ_STATE_INACTIVE);
+		break;
+
+	case QDIO_IRQ_STATE_ESTABLISHED:
+	case QDIO_IRQ_STATE_ACTIVE:
+		if (cstat & SCHN_STAT_PCI) {
+			qdio_handle_pci(irq_ptr);
+			break;
+		}
+
+		if ((cstat&~SCHN_STAT_PCI)||dstat) {
+			qdio_handle_activate_check(cdev, intparm, cstat, dstat);
+			break;
+		}
+	default:
+		QDIO_PRINT_ERR("got interrupt for queues in state %d on " \
+			       "device %s?!\n",
+			       irq_ptr->state, cdev->dev.bus_id);
+	}
+	wake_up(&cdev->private->wait_q);
+
+}
+
+int
+qdio_synchronize(struct ccw_device *cdev, unsigned int flags,
+		 unsigned int queue_number)
+{
+	int cc;
+	struct qdio_q *q;
+	struct qdio_irq *irq_ptr;
+	void *ptr;
+#ifdef CONFIG_QDIO_DEBUG
+	char dbf_text[15]="SyncXXXX";
+#endif
+
+	irq_ptr = cdev->private->qdio_data;
+	if (!irq_ptr)
+		return -ENODEV;
+
+#ifdef CONFIG_QDIO_DEBUG
+	*((int*)(&dbf_text[4])) = irq_ptr->irq;
+	QDIO_DBF_HEX4(0,trace,dbf_text,QDIO_DBF_TRACE_LEN);
+	*((int*)(&dbf_text[0]))=flags;
+	*((int*)(&dbf_text[4]))=queue_number;
+	QDIO_DBF_HEX4(0,trace,dbf_text,QDIO_DBF_TRACE_LEN);
+#endif /* CONFIG_QDIO_DEBUG */
+
+	if (flags&QDIO_FLAG_SYNC_INPUT) {
+		q=irq_ptr->input_qs[queue_number];
+		if (!q)
+			return -EINVAL;
+		cc = do_siga_sync(q->irq, 0, q->mask);
+	} else if (flags&QDIO_FLAG_SYNC_OUTPUT) {
+		q=irq_ptr->output_qs[queue_number];
+		if (!q)
+			return -EINVAL;
+		cc = do_siga_sync(q->irq, q->mask, 0);
+	} else 
+		return -EINVAL;
+
+	ptr=&cc;
+	if (cc)
+		QDIO_DBF_HEX3(0,trace,&ptr,sizeof(int));
+
+	return cc;
+}
+
+static unsigned char
+qdio_check_siga_needs(int sch)
+{
+	int result;
+	unsigned char qdioac;
+
+	struct {
+		struct chsc_header request;
+		u16 reserved1;
+		u16 first_sch;
+		u16 reserved2;
+		u16 last_sch;
+		u32 reserved3;
+		struct chsc_header response;
+		u32 reserved4;
+		u8  flags;
+		u8  reserved5;
+		u16 sch;
+		u8  qfmt;
+		u8  reserved6;
+		u8  qdioac;
+		u8  sch_class;
+		u8  reserved7;
+		u8  icnt;
+		u8  reserved8;
+		u8  ocnt;
+	} *ssqd_area;
+
+	ssqd_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
+	if (!ssqd_area) {
+	        QDIO_PRINT_WARN("Could not get memory for chsc. Using all " \
+				"SIGAs for sch x%x.\n", sch);
+		return CHSC_FLAG_SIGA_INPUT_NECESSARY ||
+			CHSC_FLAG_SIGA_OUTPUT_NECESSARY ||
+			CHSC_FLAG_SIGA_SYNC_NECESSARY; /* all flags set */
+	}
+	ssqd_area->request = (struct chsc_header) {
+		.length = 0x0010,
+		.code   = 0x0024,
+	};
+
+	ssqd_area->first_sch = sch;
+	ssqd_area->last_sch = sch;
+
+	result=chsc(ssqd_area);
+
+	if (result) {
+		QDIO_PRINT_WARN("CHSC returned cc %i. Using all " \
+				"SIGAs for sch x%x.\n",
+				result,sch);
+		qdioac = CHSC_FLAG_SIGA_INPUT_NECESSARY ||
+			CHSC_FLAG_SIGA_OUTPUT_NECESSARY ||
+			CHSC_FLAG_SIGA_SYNC_NECESSARY; /* all flags set */
+		goto out;
+	}
+
+	if (ssqd_area->response.code != QDIO_CHSC_RESPONSE_CODE_OK) {
+		QDIO_PRINT_WARN("response upon checking SIGA needs " \
+				"is 0x%x. Using all SIGAs for sch x%x.\n",
+				ssqd_area->response.code, sch);
+		qdioac = CHSC_FLAG_SIGA_INPUT_NECESSARY ||
+			CHSC_FLAG_SIGA_OUTPUT_NECESSARY ||
+			CHSC_FLAG_SIGA_SYNC_NECESSARY; /* all flags set */
+		goto out;
+	}
+	if (!(ssqd_area->flags & CHSC_FLAG_QDIO_CAPABILITY) ||
+	    !(ssqd_area->flags & CHSC_FLAG_VALIDITY) ||
+	    (ssqd_area->sch != sch)) {
+		QDIO_PRINT_WARN("huh? problems checking out sch x%x... " \
+				"using all SIGAs.\n",sch);
+		qdioac = CHSC_FLAG_SIGA_INPUT_NECESSARY |
+			CHSC_FLAG_SIGA_OUTPUT_NECESSARY |
+			CHSC_FLAG_SIGA_SYNC_NECESSARY; /* worst case */
+		goto out;
+	}
+
+	qdioac = ssqd_area->qdioac;
+out:
+	free_page ((unsigned long) ssqd_area);
+	return qdioac;
+}
+
+static unsigned int
+tiqdio_check_chsc_availability(void)
+{
+	char dbf_text[15];
+
+	if (!css_characteristics_avail)
+		return -EIO;
+
+	/* Check for bit 41. */
+	if (!css_general_characteristics.aif) {
+		QDIO_PRINT_WARN("Adapter interruption facility not " \
+				"installed.\n");
+		return -ENOENT;
+	}
+
+	/* Check for bits 107 and 108. */
+	if (!css_chsc_characteristics.scssc ||
+	    !css_chsc_characteristics.scsscf) {
+		QDIO_PRINT_WARN("Set Chan Subsys. Char. & Fast-CHSCs " \
+				"not available.\n");
+		return -ENOENT;
+	}
+
+	/* Check for OSA/FCP thin interrupts (bit 67). */
+	hydra_thinints = css_general_characteristics.aif_osa;
+	sprintf(dbf_text,"hydrati%1x", hydra_thinints);
+	QDIO_DBF_TEXT0(0,setup,dbf_text);
+
+	/* Check for aif time delay disablement fac (bit 56). If installed,
+	 * omit svs even under lpar (good point by rick again) */
+	omit_svs = css_general_characteristics.aif_tdd;
+	sprintf(dbf_text,"omitsvs%1x", omit_svs);
+	QDIO_DBF_TEXT0(0,setup,dbf_text);
+	return 0;
+}
+
+
+static unsigned int
+tiqdio_set_subchannel_ind(struct qdio_irq *irq_ptr, int reset_to_zero)
+{
+	unsigned long real_addr_local_summary_bit;
+	unsigned long real_addr_dev_st_chg_ind;
+	void *ptr;
+	char dbf_text[15];
+
+	unsigned int resp_code;
+	int result;
+
+	struct {
+		struct chsc_header request;
+		u16 operation_code;
+		u16 reserved1;
+		u32 reserved2;
+		u32 reserved3;
+		u64 summary_indicator_addr;
+		u64 subchannel_indicator_addr;
+		u32 ks:4;
+		u32 kc:4;
+		u32 reserved4:21;
+		u32 isc:3;
+		u32 word_with_d_bit;
+		/* set to 0x10000000 to enable
+		 * time delay disablement facility */
+		u32 reserved5;
+		u32 subsystem_id;
+		u32 reserved6[1004];
+		struct chsc_header response;
+		u32 reserved7;
+	} *scssc_area;
+
+	if (!irq_ptr->is_thinint_irq)
+		return -ENODEV;
+
+	if (reset_to_zero) {
+		real_addr_local_summary_bit=0;
+		real_addr_dev_st_chg_ind=0;
+	} else {
+		real_addr_local_summary_bit=
+			virt_to_phys((volatile void *)indicators);
+		real_addr_dev_st_chg_ind=
+			virt_to_phys((volatile void *)irq_ptr->dev_st_chg_ind);
+	}
+
+	scssc_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
+	if (!scssc_area) {
+		QDIO_PRINT_WARN("No memory for setting indicators on " \
+				"subchannel x%x.\n", irq_ptr->irq);
+		return -ENOMEM;
+	}
+	scssc_area->request = (struct chsc_header) {
+		.length = 0x0fe0,
+		.code   = 0x0021,
+	};
+	scssc_area->operation_code = 0;
+
+        scssc_area->summary_indicator_addr = real_addr_local_summary_bit;
+	scssc_area->subchannel_indicator_addr = real_addr_dev_st_chg_ind;
+	scssc_area->ks = QDIO_STORAGE_KEY;
+	scssc_area->kc = QDIO_STORAGE_KEY;
+	scssc_area->isc = TIQDIO_THININT_ISC;
+	scssc_area->subsystem_id = (1<<16) + irq_ptr->irq;
+	/* enables the time delay disablement facility. Don't care
+	 * whether it is really there (i.e. we haven't checked for
+	 * it) */
+	if (css_general_characteristics.aif_tdd)
+		scssc_area->word_with_d_bit = 0x10000000;
+	else
+		QDIO_PRINT_WARN("Time delay disablement facility " \
+				"not available\n");
+
+
+
+	result = chsc(scssc_area);
+	if (result) {
+		QDIO_PRINT_WARN("could not set indicators on irq x%x, " \
+				"cc=%i.\n",irq_ptr->irq,result);
+		result = -EIO;
+		goto out;
+	}
+
+	resp_code = scssc_area->response.code;
+	if (resp_code!=QDIO_CHSC_RESPONSE_CODE_OK) {
+		QDIO_PRINT_WARN("response upon setting indicators " \
+				"is 0x%x.\n",resp_code);
+		sprintf(dbf_text,"sidR%4x",resp_code);
+		QDIO_DBF_TEXT1(0,trace,dbf_text);
+		QDIO_DBF_TEXT1(0,setup,dbf_text);
+		ptr=&scssc_area->response;
+		QDIO_DBF_HEX2(1,setup,&ptr,QDIO_DBF_SETUP_LEN);
+		result = -EIO;
+		goto out;
+	}
+
+	QDIO_DBF_TEXT2(0,setup,"setscind");
+	QDIO_DBF_HEX2(0,setup,&real_addr_local_summary_bit,
+		      sizeof(unsigned long));
+	QDIO_DBF_HEX2(0,setup,&real_addr_dev_st_chg_ind,sizeof(unsigned long));
+	result = 0;
+out:
+	free_page ((unsigned long) scssc_area);
+	return result;
+
+}
+
+static unsigned int
+tiqdio_set_delay_target(struct qdio_irq *irq_ptr, unsigned long delay_target)
+{
+	unsigned int resp_code;
+	int result;
+	void *ptr;
+	char dbf_text[15];
+
+	struct {
+		struct chsc_header request;
+		u16 operation_code;
+		u16 reserved1;
+		u32 reserved2;
+		u32 reserved3;
+		u32 reserved4[2];
+		u32 delay_target;
+		u32 reserved5[1009];
+		struct chsc_header response;
+		u32 reserved6;
+	} *scsscf_area;
+
+	if (!irq_ptr->is_thinint_irq)
+		return -ENODEV;
+
+	scsscf_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
+	if (!scsscf_area) {
+		QDIO_PRINT_WARN("No memory for setting delay target on " \
+				"subchannel x%x.\n", irq_ptr->irq);
+		return -ENOMEM;
+	}
+	scsscf_area->request = (struct chsc_header) {
+		.length = 0x0fe0,
+		.code   = 0x1027,
+	};
+
+	scsscf_area->delay_target = delay_target<<16;
+
+	result=chsc(scsscf_area);
+	if (result) {
+		QDIO_PRINT_WARN("could not set delay target on irq x%x, " \
+				"cc=%i. Continuing.\n",irq_ptr->irq,result);
+		result = -EIO;
+		goto out;
+	}
+
+	resp_code = scsscf_area->response.code;
+	if (resp_code!=QDIO_CHSC_RESPONSE_CODE_OK) {
+		QDIO_PRINT_WARN("response upon setting delay target " \
+				"is 0x%x. Continuing.\n",resp_code);
+		sprintf(dbf_text,"sdtR%4x",resp_code);
+		QDIO_DBF_TEXT1(0,trace,dbf_text);
+		QDIO_DBF_TEXT1(0,setup,dbf_text);
+		ptr=&scsscf_area->response;
+		QDIO_DBF_HEX2(1,trace,&ptr,QDIO_DBF_TRACE_LEN);
+	}
+	QDIO_DBF_TEXT2(0,trace,"delytrgt");
+	QDIO_DBF_HEX2(0,trace,&delay_target,sizeof(unsigned long));
+	result = 0; /* not critical */
+out:
+	free_page ((unsigned long) scsscf_area);
+	return result;
+}
+
+int
+qdio_cleanup(struct ccw_device *cdev, int how)
+{
+	struct qdio_irq *irq_ptr;
+	char dbf_text[15];
+	int rc;
+
+	irq_ptr = cdev->private->qdio_data;
+	if (!irq_ptr)
+		return -ENODEV;
+
+	sprintf(dbf_text,"qcln%4x",irq_ptr->irq);
+	QDIO_DBF_TEXT1(0,trace,dbf_text);
+	QDIO_DBF_TEXT0(0,setup,dbf_text);
+
+	rc = qdio_shutdown(cdev, how);
+	if ((rc == 0) || (rc == -EINPROGRESS))
+		rc = qdio_free(cdev);
+	return rc;
+}
+
+int
+qdio_shutdown(struct ccw_device *cdev, int how)
+{
+	struct qdio_irq *irq_ptr;
+	int i;
+	int result = 0;
+	int rc;
+	unsigned long flags;
+	int timeout;
+	char dbf_text[15];
+
+	irq_ptr = cdev->private->qdio_data;
+	if (!irq_ptr)
+		return -ENODEV;
+
+	down(&irq_ptr->setting_up_sema);
+
+	sprintf(dbf_text,"qsqs%4x",irq_ptr->irq);
+	QDIO_DBF_TEXT1(0,trace,dbf_text);
+	QDIO_DBF_TEXT0(0,setup,dbf_text);
+
+	/* mark all qs as uninteresting */
+	for (i=0;i<irq_ptr->no_input_qs;i++)
+		atomic_set(&irq_ptr->input_qs[i]->is_in_shutdown,1);
+
+	for (i=0;i<irq_ptr->no_output_qs;i++)
+		atomic_set(&irq_ptr->output_qs[i]->is_in_shutdown,1);
+
+	tasklet_kill(&tiqdio_tasklet);
+
+	for (i=0;i<irq_ptr->no_input_qs;i++) {
+		qdio_unmark_q(irq_ptr->input_qs[i]);
+		tasklet_kill(&irq_ptr->input_qs[i]->tasklet);
+		wait_event_interruptible_timeout(cdev->private->wait_q,
+						 !atomic_read(&irq_ptr->
+							      input_qs[i]->
+							      use_count),
+						 QDIO_NO_USE_COUNT_TIMEOUT);
+		if (atomic_read(&irq_ptr->input_qs[i]->use_count))
+			result=-EINPROGRESS;
+	}
+
+	for (i=0;i<irq_ptr->no_output_qs;i++) {
+		tasklet_kill(&irq_ptr->output_qs[i]->tasklet);
+		wait_event_interruptible_timeout(cdev->private->wait_q,
+						 !atomic_read(&irq_ptr->
+							      output_qs[i]->
+							      use_count),
+						 QDIO_NO_USE_COUNT_TIMEOUT);
+		if (atomic_read(&irq_ptr->output_qs[i]->use_count))
+			result=-EINPROGRESS;
+	}
+
+	/* cleanup subchannel */
+	spin_lock_irqsave(get_ccwdev_lock(cdev),flags);
+	if (how&QDIO_FLAG_CLEANUP_USING_CLEAR) {
+		rc = ccw_device_clear(cdev, QDIO_DOING_CLEANUP);
+		timeout=QDIO_CLEANUP_CLEAR_TIMEOUT;
+	} else if (how&QDIO_FLAG_CLEANUP_USING_HALT) {
+		rc = ccw_device_halt(cdev, QDIO_DOING_CLEANUP);
+		timeout=QDIO_CLEANUP_HALT_TIMEOUT;
+	} else { /* default behaviour */
+		rc = ccw_device_halt(cdev, QDIO_DOING_CLEANUP);
+		timeout=QDIO_CLEANUP_HALT_TIMEOUT;
+	}
+	if (rc == -ENODEV) {
+		/* No need to wait for device no longer present. */
+		qdio_set_state(irq_ptr, QDIO_IRQ_STATE_INACTIVE);
+		spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags);
+	} else if (((void *)cdev->handler != (void *)qdio_handler) && rc == 0) {
+		/*
+		 * Whoever put another handler there, has to cope with the
+		 * interrupt theirself. Might happen if qdio_shutdown was
+		 * called on already shutdown queues, but this shouldn't have
+		 * bad side effects.
+		 */
+		qdio_set_state(irq_ptr, QDIO_IRQ_STATE_INACTIVE);
+		spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags);
+	} else if (rc == 0) {
+		qdio_set_state(irq_ptr, QDIO_IRQ_STATE_CLEANUP);
+		ccw_device_set_timeout(cdev, timeout);
+		spin_unlock_irqrestore(get_ccwdev_lock(cdev),flags);
+
+		wait_event(cdev->private->wait_q,
+			   irq_ptr->state == QDIO_IRQ_STATE_INACTIVE ||
+			   irq_ptr->state == QDIO_IRQ_STATE_ERR);
+	} else {
+		QDIO_PRINT_INFO("ccw_device_{halt,clear} returned %d for "
+				"device %s\n", result, cdev->dev.bus_id);
+		spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags);
+		result = rc;
+		goto out;
+	}
+	if (irq_ptr->is_thinint_irq) {
+		qdio_put_indicator((__u32*)irq_ptr->dev_st_chg_ind);
+		tiqdio_set_subchannel_ind(irq_ptr,1); 
+                /* reset adapter interrupt indicators */
+	}
+
+ 	/* exchange int handlers, if necessary */
+ 	if ((void*)cdev->handler == (void*)qdio_handler)
+ 		cdev->handler=irq_ptr->original_int_handler;
+
+	/* Ignore errors. */
+	qdio_set_state(irq_ptr, QDIO_IRQ_STATE_INACTIVE);
+	ccw_device_set_timeout(cdev, 0);
+out:
+	up(&irq_ptr->setting_up_sema);
+	return result;
+}
+
+int
+qdio_free(struct ccw_device *cdev)
+{
+	struct qdio_irq *irq_ptr;
+	char dbf_text[15];
+
+	irq_ptr = cdev->private->qdio_data;
+	if (!irq_ptr)
+		return -ENODEV;
+
+	down(&irq_ptr->setting_up_sema);
+
+	sprintf(dbf_text,"qfqs%4x",irq_ptr->irq);
+	QDIO_DBF_TEXT1(0,trace,dbf_text);
+	QDIO_DBF_TEXT0(0,setup,dbf_text);
+
+	cdev->private->qdio_data = 0;
+
+	up(&irq_ptr->setting_up_sema);
+
+	qdio_release_irq_memory(irq_ptr);
+	module_put(THIS_MODULE);
+	return 0;
+}
+
+static inline void
+qdio_allocate_do_dbf(struct qdio_initialize *init_data)
+{
+	char dbf_text[20]; /* if a printf printed out more than 8 chars */
+
+	sprintf(dbf_text,"qfmt:%x",init_data->q_format);
+	QDIO_DBF_TEXT0(0,setup,dbf_text);
+	QDIO_DBF_HEX0(0,setup,init_data->adapter_name,8);
+	sprintf(dbf_text,"qpff%4x",init_data->qib_param_field_format);
+	QDIO_DBF_TEXT0(0,setup,dbf_text);
+	QDIO_DBF_HEX0(0,setup,&init_data->qib_param_field,sizeof(char*));
+	QDIO_DBF_HEX0(0,setup,&init_data->input_slib_elements,sizeof(long*));
+	QDIO_DBF_HEX0(0,setup,&init_data->output_slib_elements,sizeof(long*));
+	sprintf(dbf_text,"miit%4x",init_data->min_input_threshold);
+	QDIO_DBF_TEXT0(0,setup,dbf_text);
+	sprintf(dbf_text,"mait%4x",init_data->max_input_threshold);
+	QDIO_DBF_TEXT0(0,setup,dbf_text);
+	sprintf(dbf_text,"miot%4x",init_data->min_output_threshold);
+	QDIO_DBF_TEXT0(0,setup,dbf_text);
+	sprintf(dbf_text,"maot%4x",init_data->max_output_threshold);
+	QDIO_DBF_TEXT0(0,setup,dbf_text);
+	sprintf(dbf_text,"niq:%4x",init_data->no_input_qs);
+	QDIO_DBF_TEXT0(0,setup,dbf_text);
+	sprintf(dbf_text,"noq:%4x",init_data->no_output_qs);
+	QDIO_DBF_TEXT0(0,setup,dbf_text);
+	QDIO_DBF_HEX0(0,setup,&init_data->input_handler,sizeof(void*));
+	QDIO_DBF_HEX0(0,setup,&init_data->output_handler,sizeof(void*));
+	QDIO_DBF_HEX0(0,setup,&init_data->int_parm,sizeof(long));
+	QDIO_DBF_HEX0(0,setup,&init_data->flags,sizeof(long));
+	QDIO_DBF_HEX0(0,setup,&init_data->input_sbal_addr_array,sizeof(void*));
+	QDIO_DBF_HEX0(0,setup,&init_data->output_sbal_addr_array,sizeof(void*));
+}
+
+static inline void
+qdio_allocate_fill_input_desc(struct qdio_irq *irq_ptr, int i, int iqfmt)
+{
+	irq_ptr->input_qs[i]->is_iqdio_q = iqfmt;
+	irq_ptr->input_qs[i]->is_thinint_q = irq_ptr->is_thinint_irq;
+
+	irq_ptr->qdr->qdf0[i].sliba=(unsigned long)(irq_ptr->input_qs[i]->slib);
+
+	irq_ptr->qdr->qdf0[i].sla=(unsigned long)(irq_ptr->input_qs[i]->sl);
+
+	irq_ptr->qdr->qdf0[i].slsba=
+		(unsigned long)(&irq_ptr->input_qs[i]->slsb.acc.val[0]);
+
+	irq_ptr->qdr->qdf0[i].akey=QDIO_STORAGE_KEY;
+	irq_ptr->qdr->qdf0[i].bkey=QDIO_STORAGE_KEY;
+	irq_ptr->qdr->qdf0[i].ckey=QDIO_STORAGE_KEY;
+	irq_ptr->qdr->qdf0[i].dkey=QDIO_STORAGE_KEY;
+}
+
+static inline void
+qdio_allocate_fill_output_desc(struct qdio_irq *irq_ptr, int i,
+			       int j, int iqfmt)
+{
+	irq_ptr->output_qs[i]->is_iqdio_q = iqfmt;
+	irq_ptr->output_qs[i]->is_thinint_q = irq_ptr->is_thinint_irq;
+
+	irq_ptr->qdr->qdf0[i+j].sliba=(unsigned long)(irq_ptr->output_qs[i]->slib);
+
+	irq_ptr->qdr->qdf0[i+j].sla=(unsigned long)(irq_ptr->output_qs[i]->sl);
+
+	irq_ptr->qdr->qdf0[i+j].slsba=
+		(unsigned long)(&irq_ptr->output_qs[i]->slsb.acc.val[0]);
+
+	irq_ptr->qdr->qdf0[i+j].akey=QDIO_STORAGE_KEY;
+	irq_ptr->qdr->qdf0[i+j].bkey=QDIO_STORAGE_KEY;
+	irq_ptr->qdr->qdf0[i+j].ckey=QDIO_STORAGE_KEY;
+	irq_ptr->qdr->qdf0[i+j].dkey=QDIO_STORAGE_KEY;
+}
+
+
+static inline void
+qdio_initialize_set_siga_flags_input(struct qdio_irq *irq_ptr)
+{
+	int i;
+
+	for (i=0;i<irq_ptr->no_input_qs;i++) {
+		irq_ptr->input_qs[i]->siga_sync=
+			irq_ptr->qdioac&CHSC_FLAG_SIGA_SYNC_NECESSARY;
+		irq_ptr->input_qs[i]->siga_in=
+			irq_ptr->qdioac&CHSC_FLAG_SIGA_INPUT_NECESSARY;
+		irq_ptr->input_qs[i]->siga_out=
+			irq_ptr->qdioac&CHSC_FLAG_SIGA_OUTPUT_NECESSARY;
+		irq_ptr->input_qs[i]->siga_sync_done_on_thinints=
+			irq_ptr->qdioac&CHSC_FLAG_SIGA_SYNC_DONE_ON_THININTS;
+		irq_ptr->input_qs[i]->hydra_gives_outbound_pcis=
+			irq_ptr->hydra_gives_outbound_pcis;
+		irq_ptr->input_qs[i]->siga_sync_done_on_outb_tis=
+			((irq_ptr->qdioac&
+			  (CHSC_FLAG_SIGA_SYNC_DONE_ON_OUTB_PCIS|
+			   CHSC_FLAG_SIGA_SYNC_DONE_ON_THININTS))==
+			 (CHSC_FLAG_SIGA_SYNC_DONE_ON_OUTB_PCIS|
+			  CHSC_FLAG_SIGA_SYNC_DONE_ON_THININTS));
+
+	}
+}
+
+static inline void
+qdio_initialize_set_siga_flags_output(struct qdio_irq *irq_ptr)
+{
+	int i;
+
+	for (i=0;i<irq_ptr->no_output_qs;i++) {
+		irq_ptr->output_qs[i]->siga_sync=
+			irq_ptr->qdioac&CHSC_FLAG_SIGA_SYNC_NECESSARY;
+		irq_ptr->output_qs[i]->siga_in=
+			irq_ptr->qdioac&CHSC_FLAG_SIGA_INPUT_NECESSARY;
+		irq_ptr->output_qs[i]->siga_out=
+			irq_ptr->qdioac&CHSC_FLAG_SIGA_OUTPUT_NECESSARY;
+		irq_ptr->output_qs[i]->siga_sync_done_on_thinints=
+			irq_ptr->qdioac&CHSC_FLAG_SIGA_SYNC_DONE_ON_THININTS;
+		irq_ptr->output_qs[i]->hydra_gives_outbound_pcis=
+			irq_ptr->hydra_gives_outbound_pcis;
+		irq_ptr->output_qs[i]->siga_sync_done_on_outb_tis=
+			((irq_ptr->qdioac&
+			  (CHSC_FLAG_SIGA_SYNC_DONE_ON_OUTB_PCIS|
+			   CHSC_FLAG_SIGA_SYNC_DONE_ON_THININTS))==
+			 (CHSC_FLAG_SIGA_SYNC_DONE_ON_OUTB_PCIS|
+			  CHSC_FLAG_SIGA_SYNC_DONE_ON_THININTS));
+
+	}
+}
+
+static inline int
+qdio_establish_irq_check_for_errors(struct ccw_device *cdev, int cstat,
+				    int dstat)
+{
+	char dbf_text[15];
+	struct qdio_irq *irq_ptr;
+
+	irq_ptr = cdev->private->qdio_data;
+
+	if (cstat || (dstat & ~(DEV_STAT_CHN_END|DEV_STAT_DEV_END))) {
+		sprintf(dbf_text,"ick1%4x",irq_ptr->irq);
+		QDIO_DBF_TEXT2(1,trace,dbf_text);
+		QDIO_DBF_HEX2(0,trace,&dstat,sizeof(int));
+		QDIO_DBF_HEX2(0,trace,&cstat,sizeof(int));
+		QDIO_PRINT_ERR("received check condition on establish " \
+			       "queues on irq 0x%x (cs=x%x, ds=x%x).\n",
+			       irq_ptr->irq,cstat,dstat);
+		qdio_set_state(irq_ptr,QDIO_IRQ_STATE_ERR);
+	}
+	
+	if (!(dstat & DEV_STAT_DEV_END)) {
+		QDIO_DBF_TEXT2(1,setup,"eq:no de");
+		QDIO_DBF_HEX2(0,setup,&dstat, sizeof(dstat));
+		QDIO_DBF_HEX2(0,setup,&cstat, sizeof(cstat));
+		QDIO_PRINT_ERR("establish queues on irq %04x: didn't get "
+			       "device end: dstat=%02x, cstat=%02x\n",
+			       irq_ptr->irq, dstat, cstat);
+		qdio_set_state(irq_ptr, QDIO_IRQ_STATE_ERR);
+		return 1;
+	}
+
+	if (dstat & ~(DEV_STAT_CHN_END|DEV_STAT_DEV_END)) {
+		QDIO_DBF_TEXT2(1,setup,"eq:badio");
+		QDIO_DBF_HEX2(0,setup,&dstat, sizeof(dstat));
+		QDIO_DBF_HEX2(0,setup,&cstat, sizeof(cstat));
+		QDIO_PRINT_ERR("establish queues on irq %04x: got "
+			       "the following devstat: dstat=%02x, "
+			       "cstat=%02x\n",
+			       irq_ptr->irq, dstat, cstat);
+		qdio_set_state(irq_ptr, QDIO_IRQ_STATE_ERR);
+		return 1;
+	}
+	return 0;
+}
+
+static void
+qdio_establish_handle_irq(struct ccw_device *cdev, int cstat, int dstat)
+{
+	struct qdio_irq *irq_ptr;
+	char dbf_text[15];
+
+	irq_ptr = cdev->private->qdio_data;
+
+	sprintf(dbf_text,"qehi%4x",cdev->private->irq);
+	QDIO_DBF_TEXT0(0,setup,dbf_text);
+	QDIO_DBF_TEXT0(0,trace,dbf_text);
+
+	if (qdio_establish_irq_check_for_errors(cdev, cstat, dstat)) {
+		ccw_device_set_timeout(cdev, 0);
+		return;
+	}
+
+	qdio_set_state(irq_ptr,QDIO_IRQ_STATE_ESTABLISHED);
+	ccw_device_set_timeout(cdev, 0);
+}
+
+int
+qdio_initialize(struct qdio_initialize *init_data)
+{
+	int rc;
+	char dbf_text[15];
+
+	sprintf(dbf_text,"qini%4x",init_data->cdev->private->irq);
+	QDIO_DBF_TEXT0(0,setup,dbf_text);
+	QDIO_DBF_TEXT0(0,trace,dbf_text);
+
+	rc = qdio_allocate(init_data);
+	if (rc == 0) {
+		rc = qdio_establish(init_data);
+		if (rc != 0)
+			qdio_free(init_data->cdev);
+	}
+
+	return rc;
+}
+
+
+int
+qdio_allocate(struct qdio_initialize *init_data)
+{
+	struct qdio_irq *irq_ptr;
+	char dbf_text[15];
+
+	sprintf(dbf_text,"qalc%4x",init_data->cdev->private->irq);
+	QDIO_DBF_TEXT0(0,setup,dbf_text);
+	QDIO_DBF_TEXT0(0,trace,dbf_text);
+	if ( (init_data->no_input_qs>QDIO_MAX_QUEUES_PER_IRQ) ||
+	     (init_data->no_output_qs>QDIO_MAX_QUEUES_PER_IRQ) ||
+	     ((init_data->no_input_qs) && (!init_data->input_handler)) ||
+	     ((init_data->no_output_qs) && (!init_data->output_handler)) )
+		return -EINVAL;
+
+	if (!init_data->input_sbal_addr_array)
+		return -EINVAL;
+
+	if (!init_data->output_sbal_addr_array)
+		return -EINVAL;
+
+	qdio_allocate_do_dbf(init_data);
+
+	/* create irq */
+	irq_ptr=kmalloc(sizeof(struct qdio_irq), GFP_KERNEL | GFP_DMA);
+
+	QDIO_DBF_TEXT0(0,setup,"irq_ptr:");
+	QDIO_DBF_HEX0(0,setup,&irq_ptr,sizeof(void*));
+
+	if (!irq_ptr) {
+		QDIO_PRINT_ERR("kmalloc of irq_ptr failed!\n");
+		return -ENOMEM;
+	}
+
+	memset(irq_ptr,0,sizeof(struct qdio_irq));
+
+	init_MUTEX(&irq_ptr->setting_up_sema);
+
+	/* QDR must be in DMA area since CCW data address is only 32 bit */
+	irq_ptr->qdr=kmalloc(sizeof(struct qdr), GFP_KERNEL | GFP_DMA);
+  	if (!(irq_ptr->qdr)) {
+   		kfree(irq_ptr);
+    		QDIO_PRINT_ERR("kmalloc of irq_ptr->qdr failed!\n");
+		return -ENOMEM;
+       	}
+	QDIO_DBF_TEXT0(0,setup,"qdr:");
+	QDIO_DBF_HEX0(0,setup,&irq_ptr->qdr,sizeof(void*));
+
+	if (qdio_alloc_qs(irq_ptr,
+       			  init_data->no_input_qs,
+			  init_data->no_output_qs)) {
+		qdio_release_irq_memory(irq_ptr);
+		return -ENOMEM;
+	}
+
+	init_data->cdev->private->qdio_data = irq_ptr;
+
+	qdio_set_state(irq_ptr,QDIO_IRQ_STATE_INACTIVE);
+
+	return 0;
+}
+
+int qdio_fill_irq(struct qdio_initialize *init_data)
+{
+	int i;
+	char dbf_text[15];
+	struct ciw *ciw;
+	int is_iqdio;
+	struct qdio_irq *irq_ptr;
+
+	irq_ptr = init_data->cdev->private->qdio_data;
+
+	memset(irq_ptr,0,((char*)&irq_ptr->qdr)-((char*)irq_ptr));
+
+        /* wipes qib.ac, required by ar7063 */
+	memset(irq_ptr->qdr,0,sizeof(struct qdr));
+
+	irq_ptr->int_parm=init_data->int_parm;
+
+	irq_ptr->irq = init_data->cdev->private->irq;
+	irq_ptr->no_input_qs=init_data->no_input_qs;
+	irq_ptr->no_output_qs=init_data->no_output_qs;
+
+	if (init_data->q_format==QDIO_IQDIO_QFMT) {
+		irq_ptr->is_iqdio_irq=1;
+		irq_ptr->is_thinint_irq=1;
+	} else {
+		irq_ptr->is_iqdio_irq=0;
+		irq_ptr->is_thinint_irq=hydra_thinints;
+	}
+	sprintf(dbf_text,"is_i_t%1x%1x",
+		irq_ptr->is_iqdio_irq,irq_ptr->is_thinint_irq);
+	QDIO_DBF_TEXT2(0,setup,dbf_text);
+
+	if (irq_ptr->is_thinint_irq) {
+		irq_ptr->dev_st_chg_ind=qdio_get_indicator();
+		QDIO_DBF_HEX1(0,setup,&irq_ptr->dev_st_chg_ind,sizeof(void*));
+		if (!irq_ptr->dev_st_chg_ind) {
+			QDIO_PRINT_WARN("no indicator location available " \
+					"for irq 0x%x\n",irq_ptr->irq);
+			qdio_release_irq_memory(irq_ptr);
+			return -ENOBUFS;
+		}
+	}
+
+	/* defaults */
+	irq_ptr->equeue.cmd=DEFAULT_ESTABLISH_QS_CMD;
+	irq_ptr->equeue.count=DEFAULT_ESTABLISH_QS_COUNT;
+	irq_ptr->aqueue.cmd=DEFAULT_ACTIVATE_QS_CMD;
+	irq_ptr->aqueue.count=DEFAULT_ACTIVATE_QS_COUNT;
+
+	qdio_fill_qs(irq_ptr, init_data->cdev,
+		     init_data->no_input_qs,
+		     init_data->no_output_qs,
+		     init_data->input_handler,
+		     init_data->output_handler,init_data->int_parm,
+		     init_data->q_format,init_data->flags,
+	   	     init_data->input_sbal_addr_array,
+   		     init_data->output_sbal_addr_array);
+
+	if (!try_module_get(THIS_MODULE)) {
+		QDIO_PRINT_CRIT("try_module_get() failed!\n");
+		qdio_release_irq_memory(irq_ptr);
+		return -EINVAL;
+	}
+
+	qdio_fill_thresholds(irq_ptr,init_data->no_input_qs,
+			     init_data->no_output_qs,
+			     init_data->min_input_threshold,
+			     init_data->max_input_threshold,
+			     init_data->min_output_threshold,
+			     init_data->max_output_threshold);
+
+	/* fill in qdr */
+	irq_ptr->qdr->qfmt=init_data->q_format;
+	irq_ptr->qdr->iqdcnt=init_data->no_input_qs;
+	irq_ptr->qdr->oqdcnt=init_data->no_output_qs;
+	irq_ptr->qdr->iqdsz=sizeof(struct qdesfmt0)/4; /* size in words */
+	irq_ptr->qdr->oqdsz=sizeof(struct qdesfmt0)/4;
+
+	irq_ptr->qdr->qiba=(unsigned long)&irq_ptr->qib;
+	irq_ptr->qdr->qkey=QDIO_STORAGE_KEY;
+
+	/* fill in qib */
+	irq_ptr->qib.qfmt=init_data->q_format;
+	if (init_data->no_input_qs)
+		irq_ptr->qib.isliba=(unsigned long)(irq_ptr->input_qs[0]->slib);
+	if (init_data->no_output_qs)
+		irq_ptr->qib.osliba=(unsigned long)(irq_ptr->output_qs[0]->slib);
+	memcpy(irq_ptr->qib.ebcnam,init_data->adapter_name,8);
+
+	qdio_set_impl_params(irq_ptr,init_data->qib_param_field_format,
+			     init_data->qib_param_field,
+			     init_data->no_input_qs,
+			     init_data->no_output_qs,
+			     init_data->input_slib_elements,
+			     init_data->output_slib_elements);
+
+	/* first input descriptors, then output descriptors */
+	is_iqdio = (init_data->q_format == QDIO_IQDIO_QFMT) ? 1 : 0;
+	for (i=0;i<init_data->no_input_qs;i++)
+		qdio_allocate_fill_input_desc(irq_ptr, i, is_iqdio);
+
+	for (i=0;i<init_data->no_output_qs;i++)
+		qdio_allocate_fill_output_desc(irq_ptr, i,
+					       init_data->no_input_qs,
+					       is_iqdio);
+
+	/* qdr, qib, sls, slsbs, slibs, sbales filled. */
+
+	/* get qdio commands */
+	ciw = ccw_device_get_ciw(init_data->cdev, CIW_TYPE_EQUEUE);
+	if (!ciw) {
+		QDIO_DBF_TEXT2(1,setup,"no eq");
+		QDIO_PRINT_INFO("No equeue CIW found for QDIO commands. "
+				"Trying to use default.\n");
+	} else
+		irq_ptr->equeue = *ciw;
+	ciw = ccw_device_get_ciw(init_data->cdev, CIW_TYPE_AQUEUE);
+	if (!ciw) {
+		QDIO_DBF_TEXT2(1,setup,"no aq");
+		QDIO_PRINT_INFO("No aqueue CIW found for QDIO commands. "
+				"Trying to use default.\n");
+	} else
+		irq_ptr->aqueue = *ciw;
+
+	/* Set new interrupt handler. */
+	irq_ptr->original_int_handler = init_data->cdev->handler;
+	init_data->cdev->handler = qdio_handler;
+
+	return 0;
+}
+
+int
+qdio_establish(struct qdio_initialize *init_data)
+{
+	struct qdio_irq *irq_ptr;
+	unsigned long saveflags;
+	int result, result2;
+	struct ccw_device *cdev;
+	char dbf_text[20];
+
+	cdev=init_data->cdev;
+	irq_ptr = cdev->private->qdio_data;
+	if (!irq_ptr)
+		return -EINVAL;
+
+	if (cdev->private->state != DEV_STATE_ONLINE)
+		return -EINVAL;
+	
+	down(&irq_ptr->setting_up_sema);
+
+	qdio_fill_irq(init_data);
+
+	/* the thinint CHSC stuff */
+	if (irq_ptr->is_thinint_irq) {
+
+		result = tiqdio_set_subchannel_ind(irq_ptr,0);
+		if (result) {
+			up(&irq_ptr->setting_up_sema);
+			qdio_shutdown(cdev, QDIO_FLAG_CLEANUP_USING_CLEAR);
+			return result;
+		}
+		tiqdio_set_delay_target(irq_ptr,TIQDIO_DELAY_TARGET);
+	}
+
+	sprintf(dbf_text,"qest%4x",cdev->private->irq);
+	QDIO_DBF_TEXT0(0,setup,dbf_text);
+	QDIO_DBF_TEXT0(0,trace,dbf_text);
+
+	/* establish q */
+	irq_ptr->ccw.cmd_code=irq_ptr->equeue.cmd;
+	irq_ptr->ccw.flags=CCW_FLAG_SLI;
+	irq_ptr->ccw.count=irq_ptr->equeue.count;
+	irq_ptr->ccw.cda=QDIO_GET_ADDR(irq_ptr->qdr);
+
+	spin_lock_irqsave(get_ccwdev_lock(cdev),saveflags);
+
+	ccw_device_set_options(cdev, 0);
+	result=ccw_device_start_timeout(cdev,&irq_ptr->ccw,
+					QDIO_DOING_ESTABLISH,0, 0,
+					QDIO_ESTABLISH_TIMEOUT);
+	if (result) {
+		result2=ccw_device_start_timeout(cdev,&irq_ptr->ccw,
+						 QDIO_DOING_ESTABLISH,0,0,
+						 QDIO_ESTABLISH_TIMEOUT);
+		sprintf(dbf_text,"eq:io%4x",result);
+		QDIO_DBF_TEXT2(1,setup,dbf_text);
+		if (result2) {
+			sprintf(dbf_text,"eq:io%4x",result);
+			QDIO_DBF_TEXT2(1,setup,dbf_text);
+		}
+		QDIO_PRINT_WARN("establish queues on irq %04x: do_IO " \
+                           "returned %i, next try returned %i\n",
+                           irq_ptr->irq,result,result2);
+		result=result2;
+		if (result)
+			ccw_device_set_timeout(cdev, 0);
+	}
+
+	spin_unlock_irqrestore(get_ccwdev_lock(cdev),saveflags);
+
+	if (result) {
+		up(&irq_ptr->setting_up_sema);
+		qdio_shutdown(cdev,QDIO_FLAG_CLEANUP_USING_CLEAR);
+		return result;
+	}
+	
+	wait_event_interruptible_timeout(cdev->private->wait_q,
+		 irq_ptr->state == QDIO_IRQ_STATE_ESTABLISHED ||
+		 irq_ptr->state == QDIO_IRQ_STATE_ERR,
+		 QDIO_ESTABLISH_TIMEOUT);
+
+	if (irq_ptr->state == QDIO_IRQ_STATE_ESTABLISHED)
+		result = 0;
+	else {
+		up(&irq_ptr->setting_up_sema);
+		qdio_shutdown(cdev, QDIO_FLAG_CLEANUP_USING_CLEAR);
+		return -EIO;
+	}
+
+	irq_ptr->qdioac=qdio_check_siga_needs(irq_ptr->irq);
+	/* if this gets set once, we're running under VM and can omit SVSes */
+	if (irq_ptr->qdioac&CHSC_FLAG_SIGA_SYNC_NECESSARY)
+		omit_svs=1;
+
+	sprintf(dbf_text,"qdioac%2x",irq_ptr->qdioac);
+	QDIO_DBF_TEXT2(0,setup,dbf_text);
+
+	sprintf(dbf_text,"qib ac%2x",irq_ptr->qib.ac);
+	QDIO_DBF_TEXT2(0,setup,dbf_text);
+
+	irq_ptr->hydra_gives_outbound_pcis=
+		irq_ptr->qib.ac&QIB_AC_OUTBOUND_PCI_SUPPORTED;
+	irq_ptr->sync_done_on_outb_pcis=
+		irq_ptr->qdioac&CHSC_FLAG_SIGA_SYNC_DONE_ON_OUTB_PCIS;
+
+	qdio_initialize_set_siga_flags_input(irq_ptr);
+	qdio_initialize_set_siga_flags_output(irq_ptr);
+
+	up(&irq_ptr->setting_up_sema);
+
+	return result;
+	
+}
+
+int
+qdio_activate(struct ccw_device *cdev, int flags)
+{
+	struct qdio_irq *irq_ptr;
+	int i,result=0,result2;
+	unsigned long saveflags;
+	char dbf_text[20]; /* see qdio_initialize */
+
+	irq_ptr = cdev->private->qdio_data;
+	if (!irq_ptr)
+		return -ENODEV;
+
+	if (cdev->private->state != DEV_STATE_ONLINE)
+		return -EINVAL;
+
+	down(&irq_ptr->setting_up_sema);
+	if (irq_ptr->state==QDIO_IRQ_STATE_INACTIVE) {
+		result=-EBUSY;
+		goto out;
+	}
+
+	sprintf(dbf_text,"qact%4x", irq_ptr->irq);
+	QDIO_DBF_TEXT2(0,setup,dbf_text);
+	QDIO_DBF_TEXT2(0,trace,dbf_text);
+
+	/* activate q */
+	irq_ptr->ccw.cmd_code=irq_ptr->aqueue.cmd;
+	irq_ptr->ccw.flags=CCW_FLAG_SLI;
+	irq_ptr->ccw.count=irq_ptr->aqueue.count;
+	irq_ptr->ccw.cda=QDIO_GET_ADDR(0);
+
+	spin_lock_irqsave(get_ccwdev_lock(cdev),saveflags);
+
+	ccw_device_set_timeout(cdev, 0);
+	ccw_device_set_options(cdev, CCWDEV_REPORT_ALL);
+	result=ccw_device_start(cdev,&irq_ptr->ccw,QDIO_DOING_ACTIVATE,
+				0, DOIO_DENY_PREFETCH);
+	if (result) {
+		result2=ccw_device_start(cdev,&irq_ptr->ccw,
+					 QDIO_DOING_ACTIVATE,0,0);
+		sprintf(dbf_text,"aq:io%4x",result);
+		QDIO_DBF_TEXT2(1,setup,dbf_text);
+		if (result2) {
+			sprintf(dbf_text,"aq:io%4x",result);
+			QDIO_DBF_TEXT2(1,setup,dbf_text);
+		}
+		QDIO_PRINT_WARN("activate queues on irq %04x: do_IO " \
+                           "returned %i, next try returned %i\n",
+                           irq_ptr->irq,result,result2);
+		result=result2;
+	}
+
+	spin_unlock_irqrestore(get_ccwdev_lock(cdev),saveflags);
+	if (result)
+		goto out;
+
+	for (i=0;i<irq_ptr->no_input_qs;i++) {
+		if (irq_ptr->is_thinint_irq) {
+			/* 
+			 * that way we know, that, if we will get interrupted
+			 * by tiqdio_inbound_processing, qdio_unmark_q will
+			 * not be called 
+			 */
+			qdio_reserve_q(irq_ptr->input_qs[i]);
+			qdio_mark_tiq(irq_ptr->input_qs[i]);
+			qdio_release_q(irq_ptr->input_qs[i]);
+		}
+	}
+
+	if (flags&QDIO_FLAG_NO_INPUT_INTERRUPT_CONTEXT) {
+		for (i=0;i<irq_ptr->no_input_qs;i++) {
+			irq_ptr->input_qs[i]->is_input_q|=
+				QDIO_FLAG_NO_INPUT_INTERRUPT_CONTEXT;
+		}
+	}
+
+	wait_event_interruptible_timeout(cdev->private->wait_q,
+					 ((irq_ptr->state ==
+					  QDIO_IRQ_STATE_STOPPED) ||
+					  (irq_ptr->state ==
+					   QDIO_IRQ_STATE_ERR)),
+					 QDIO_ACTIVATE_TIMEOUT);
+
+	switch (irq_ptr->state) {
+	case QDIO_IRQ_STATE_STOPPED:
+	case QDIO_IRQ_STATE_ERR:
+		up(&irq_ptr->setting_up_sema);
+		qdio_shutdown(cdev, QDIO_FLAG_CLEANUP_USING_CLEAR);
+		down(&irq_ptr->setting_up_sema);
+		result = -EIO;
+		break;
+	default:
+		qdio_set_state(irq_ptr, QDIO_IRQ_STATE_ACTIVE);
+		result = 0;
+	}
+ out:
+	up(&irq_ptr->setting_up_sema);
+
+	return result;
+}
+
+/* buffers filled forwards again to make Rick happy */
+static inline void
+qdio_do_qdio_fill_input(struct qdio_q *q, unsigned int qidx,
+			unsigned int count, struct qdio_buffer *buffers)
+{
+	for (;;) {
+		set_slsb(&q->slsb.acc.val[qidx],SLSB_CU_INPUT_EMPTY);
+		count--;
+		if (!count) break;
+		qidx=(qidx+1)&(QDIO_MAX_BUFFERS_PER_Q-1);
+	}
+
+	/* not necessary, as the queues are synced during the SIGA read */
+	/*SYNC_MEMORY;*/
+}
+
+static inline void
+qdio_do_qdio_fill_output(struct qdio_q *q, unsigned int qidx,
+			 unsigned int count, struct qdio_buffer *buffers)
+{
+	for (;;) {
+		set_slsb(&q->slsb.acc.val[qidx],SLSB_CU_OUTPUT_PRIMED);
+		count--;
+		if (!count) break;
+		qidx=(qidx+1)&(QDIO_MAX_BUFFERS_PER_Q-1);
+	}
+
+	/* SIGA write will sync the queues */
+	/*SYNC_MEMORY;*/
+}
+
+static inline void
+do_qdio_handle_inbound(struct qdio_q *q, unsigned int callflags,
+		       unsigned int qidx, unsigned int count,
+		       struct qdio_buffer *buffers)
+{
+	int used_elements;
+
+        /* This is the inbound handling of queues */
+	used_elements=atomic_add_return(count, &q->number_of_buffers_used) - count;
+	
+	qdio_do_qdio_fill_input(q,qidx,count,buffers);
+	
+	if ((used_elements+count==QDIO_MAX_BUFFERS_PER_Q)&&
+	    (callflags&QDIO_FLAG_UNDER_INTERRUPT))
+		atomic_swap(&q->polling,0);
+	
+	if (used_elements) 
+		return;
+	if (callflags&QDIO_FLAG_DONT_SIGA)
+		return;
+	if (q->siga_in) {
+		int result;
+		
+		result=qdio_siga_input(q);
+		if (result) {
+			if (q->siga_error)
+				q->error_status_flags|=
+					QDIO_STATUS_MORE_THAN_ONE_SIGA_ERROR;
+			q->error_status_flags|=QDIO_STATUS_LOOK_FOR_ERROR;
+			q->siga_error=result;
+		}
+	}
+		
+	qdio_mark_q(q);
+}
+
+static inline void
+do_qdio_handle_outbound(struct qdio_q *q, unsigned int callflags,
+			unsigned int qidx, unsigned int count,
+			struct qdio_buffer *buffers)
+{
+	int used_elements;
+
+	/* This is the outbound handling of queues */
+#ifdef QDIO_PERFORMANCE_STATS
+	perf_stats.start_time_outbound=NOW;
+#endif /* QDIO_PERFORMANCE_STATS */
+
+	qdio_do_qdio_fill_output(q,qidx,count,buffers);
+
+	used_elements=atomic_add_return(count, &q->number_of_buffers_used) - count;
+
+	if (callflags&QDIO_FLAG_DONT_SIGA) {
+#ifdef QDIO_PERFORMANCE_STATS
+		perf_stats.outbound_time+=NOW-perf_stats.start_time_outbound;
+		perf_stats.outbound_cnt++;
+#endif /* QDIO_PERFORMANCE_STATS */
+		return;
+	}
+	if (q->is_iqdio_q) {
+		/* one siga for every sbal */
+		while (count--)
+			qdio_kick_outbound_q(q);
+			
+		__qdio_outbound_processing(q);
+	} else {
+		/* under VM, we do a SIGA sync unconditionally */
+		SYNC_MEMORY;
+		else {
+			/* 
+			 * w/o shadow queues (else branch of
+			 * SYNC_MEMORY :-/ ), we try to
+			 * fast-requeue buffers 
+			 */
+			if (q->slsb.acc.val[(qidx+QDIO_MAX_BUFFERS_PER_Q-1)
+					    &(QDIO_MAX_BUFFERS_PER_Q-1)]!=
+			    SLSB_CU_OUTPUT_PRIMED) {
+				qdio_kick_outbound_q(q);
+			} else {
+				QDIO_DBF_TEXT3(0,trace, "fast-req");
+#ifdef QDIO_PERFORMANCE_STATS
+				perf_stats.fast_reqs++;
+#endif /* QDIO_PERFORMANCE_STATS */
+			}
+		}
+		/* 
+		 * only marking the q could take too long,
+		 * the upper layer module could do a lot of
+		 * traffic in that time 
+		 */
+		__qdio_outbound_processing(q);
+	}
+
+#ifdef QDIO_PERFORMANCE_STATS
+	perf_stats.outbound_time+=NOW-perf_stats.start_time_outbound;
+	perf_stats.outbound_cnt++;
+#endif /* QDIO_PERFORMANCE_STATS */
+}
+
+/* count must be 1 in iqdio */
+int
+do_QDIO(struct ccw_device *cdev,unsigned int callflags,
+	unsigned int queue_number, unsigned int qidx,
+	unsigned int count,struct qdio_buffer *buffers)
+{
+	struct qdio_irq *irq_ptr;
+#ifdef CONFIG_QDIO_DEBUG
+	char dbf_text[20];
+
+	sprintf(dbf_text,"doQD%04x",cdev->private->irq);
+ 	QDIO_DBF_TEXT3(0,trace,dbf_text);
+#endif /* CONFIG_QDIO_DEBUG */
+
+	if ( (qidx>QDIO_MAX_BUFFERS_PER_Q) ||
+	     (count>QDIO_MAX_BUFFERS_PER_Q) ||
+	     (queue_number>QDIO_MAX_QUEUES_PER_IRQ) )
+		return -EINVAL;
+
+	if (count==0)
+		return 0;
+
+	irq_ptr = cdev->private->qdio_data;
+	if (!irq_ptr)
+		return -ENODEV;
+
+#ifdef CONFIG_QDIO_DEBUG
+	if (callflags&QDIO_FLAG_SYNC_INPUT)
+		QDIO_DBF_HEX3(0,trace,&irq_ptr->input_qs[queue_number],
+			      sizeof(void*));
+	else
+		QDIO_DBF_HEX3(0,trace,&irq_ptr->output_qs[queue_number],
+			      sizeof(void*));
+	sprintf(dbf_text,"flag%04x",callflags);
+	QDIO_DBF_TEXT3(0,trace,dbf_text);
+	sprintf(dbf_text,"qi%02xct%02x",qidx,count);
+	QDIO_DBF_TEXT3(0,trace,dbf_text);
+#endif /* CONFIG_QDIO_DEBUG */
+
+	if (irq_ptr->state!=QDIO_IRQ_STATE_ACTIVE)
+		return -EBUSY;
+
+	if (callflags&QDIO_FLAG_SYNC_INPUT)
+		do_qdio_handle_inbound(irq_ptr->input_qs[queue_number],
+				       callflags, qidx, count, buffers);
+	else if (callflags&QDIO_FLAG_SYNC_OUTPUT)
+		do_qdio_handle_outbound(irq_ptr->output_qs[queue_number],
+					callflags, qidx, count, buffers);
+	else {
+		QDIO_DBF_TEXT3(1,trace,"doQD:inv");
+		return -EINVAL;
+	}
+	return 0;
+}
+
+#ifdef QDIO_PERFORMANCE_STATS
+static int
+qdio_perf_procfile_read(char *buffer, char **buffer_location, off_t offset,
+			int buffer_length, int *eof, void *data)
+{
+        int c=0;
+
+        /* we are always called with buffer_length=4k, so we all
+           deliver on the first read */
+        if (offset>0)
+		return 0;
+
+#define _OUTP_IT(x...) c+=sprintf(buffer+c,x)
+	_OUTP_IT("i_p_nc/c=%lu/%lu\n",i_p_nc,i_p_c);
+	_OUTP_IT("ii_p_nc/c=%lu/%lu\n",ii_p_nc,ii_p_c);
+	_OUTP_IT("o_p_nc/c=%lu/%lu\n",o_p_nc,o_p_c);
+	_OUTP_IT("Number of tasklet runs (total)                  : %u\n",
+		 perf_stats.tl_runs);
+	_OUTP_IT("\n");
+	_OUTP_IT("Number of SIGA sync's issued                    : %u\n",
+		 perf_stats.siga_syncs);
+	_OUTP_IT("Number of SIGA in's issued                      : %u\n",
+		 perf_stats.siga_ins);
+	_OUTP_IT("Number of SIGA out's issued                     : %u\n",
+		 perf_stats.siga_outs);
+	_OUTP_IT("Number of PCIs caught                           : %u\n",
+		 perf_stats.pcis);
+	_OUTP_IT("Number of adapter interrupts caught             : %u\n",
+		 perf_stats.thinints);
+	_OUTP_IT("Number of fast requeues (outg. SBALs w/o SIGA)  : %u\n",
+		 perf_stats.fast_reqs);
+	_OUTP_IT("\n");
+	_OUTP_IT("Total time of all inbound actions (us) incl. UL : %u\n",
+		 perf_stats.inbound_time);
+	_OUTP_IT("Number of inbound transfers                     : %u\n",
+		 perf_stats.inbound_cnt);
+	_OUTP_IT("Total time of all outbound do_QDIOs (us)        : %u\n",
+		 perf_stats.outbound_time);
+	_OUTP_IT("Number of do_QDIOs outbound                     : %u\n",
+		 perf_stats.outbound_cnt);
+	_OUTP_IT("\n");
+
+        return c;
+}
+
+static struct proc_dir_entry *qdio_perf_proc_file;
+#endif /* QDIO_PERFORMANCE_STATS */
+
+static void
+qdio_add_procfs_entry(void)
+{
+#ifdef QDIO_PERFORMANCE_STATS
+        proc_perf_file_registration=0;
+	qdio_perf_proc_file=create_proc_entry(QDIO_PERF,
+					      S_IFREG|0444,&proc_root);
+	if (qdio_perf_proc_file) {
+		qdio_perf_proc_file->read_proc=&qdio_perf_procfile_read;
+	} else proc_perf_file_registration=-1;
+
+        if (proc_perf_file_registration)
+                QDIO_PRINT_WARN("was not able to register perf. " \
+				"proc-file (%i).\n",
+				proc_perf_file_registration);
+#endif /* QDIO_PERFORMANCE_STATS */
+}
+
+static void
+qdio_remove_procfs_entry(void)
+{
+#ifdef QDIO_PERFORMANCE_STATS
+	perf_stats.tl_runs=0;
+
+        if (!proc_perf_file_registration) /* means if it went ok earlier */
+		remove_proc_entry(QDIO_PERF,&proc_root);
+#endif /* QDIO_PERFORMANCE_STATS */
+}
+
+static void
+tiqdio_register_thinints(void)
+{
+	char dbf_text[20];
+	register_thinint_result=
+		s390_register_adapter_interrupt(&tiqdio_thinint_handler);
+	if (register_thinint_result) {
+		sprintf(dbf_text,"regthn%x",(register_thinint_result&0xff));
+		QDIO_DBF_TEXT0(0,setup,dbf_text);
+		QDIO_PRINT_ERR("failed to register adapter handler " \
+			       "(rc=%i).\nAdapter interrupts might " \
+			       "not work. Continuing.\n",
+			       register_thinint_result);
+	}
+}
+
+static void
+tiqdio_unregister_thinints(void)
+{
+	if (!register_thinint_result)
+		s390_unregister_adapter_interrupt(&tiqdio_thinint_handler);
+}
+
+static int
+qdio_get_qdio_memory(void)
+{
+	int i;
+	indicator_used[0]=1;
+
+	for (i=1;i<INDICATORS_PER_CACHELINE;i++)
+		indicator_used[i]=0;
+	indicators=(__u32*)kmalloc(sizeof(__u32)*(INDICATORS_PER_CACHELINE),
+				   GFP_KERNEL);
+       	if (!indicators) return -ENOMEM;
+	memset(indicators,0,sizeof(__u32)*(INDICATORS_PER_CACHELINE));
+	return 0;
+}
+
+static void
+qdio_release_qdio_memory(void)
+{
+	if (indicators)
+		kfree(indicators);
+}
+
+static void
+qdio_unregister_dbf_views(void)
+{
+	if (qdio_dbf_setup)
+		debug_unregister(qdio_dbf_setup);
+	if (qdio_dbf_sbal)
+		debug_unregister(qdio_dbf_sbal);
+	if (qdio_dbf_sense)
+		debug_unregister(qdio_dbf_sense);
+	if (qdio_dbf_trace)
+		debug_unregister(qdio_dbf_trace);
+#ifdef CONFIG_QDIO_DEBUG
+        if (qdio_dbf_slsb_out)
+                debug_unregister(qdio_dbf_slsb_out);
+        if (qdio_dbf_slsb_in)
+                debug_unregister(qdio_dbf_slsb_in);
+#endif /* CONFIG_QDIO_DEBUG */
+}
+
+static int
+qdio_register_dbf_views(void)
+{
+	qdio_dbf_setup=debug_register(QDIO_DBF_SETUP_NAME,
+				      QDIO_DBF_SETUP_INDEX,
+				      QDIO_DBF_SETUP_NR_AREAS,
+				      QDIO_DBF_SETUP_LEN);
+	if (!qdio_dbf_setup)
+		goto oom;
+	debug_register_view(qdio_dbf_setup,&debug_hex_ascii_view);
+	debug_set_level(qdio_dbf_setup,QDIO_DBF_SETUP_LEVEL);
+
+	qdio_dbf_sbal=debug_register(QDIO_DBF_SBAL_NAME,
+				     QDIO_DBF_SBAL_INDEX,
+				     QDIO_DBF_SBAL_NR_AREAS,
+				     QDIO_DBF_SBAL_LEN);
+	if (!qdio_dbf_sbal)
+		goto oom;
+
+	debug_register_view(qdio_dbf_sbal,&debug_hex_ascii_view);
+	debug_set_level(qdio_dbf_sbal,QDIO_DBF_SBAL_LEVEL);
+
+	qdio_dbf_sense=debug_register(QDIO_DBF_SENSE_NAME,
+				      QDIO_DBF_SENSE_INDEX,
+				      QDIO_DBF_SENSE_NR_AREAS,
+				      QDIO_DBF_SENSE_LEN);
+	if (!qdio_dbf_sense)
+		goto oom;
+
+	debug_register_view(qdio_dbf_sense,&debug_hex_ascii_view);
+	debug_set_level(qdio_dbf_sense,QDIO_DBF_SENSE_LEVEL);
+
+	qdio_dbf_trace=debug_register(QDIO_DBF_TRACE_NAME,
+				      QDIO_DBF_TRACE_INDEX,
+				      QDIO_DBF_TRACE_NR_AREAS,
+				      QDIO_DBF_TRACE_LEN);
+	if (!qdio_dbf_trace)
+		goto oom;
+
+	debug_register_view(qdio_dbf_trace,&debug_hex_ascii_view);
+	debug_set_level(qdio_dbf_trace,QDIO_DBF_TRACE_LEVEL);
+
+#ifdef CONFIG_QDIO_DEBUG
+        qdio_dbf_slsb_out=debug_register(QDIO_DBF_SLSB_OUT_NAME,
+                                         QDIO_DBF_SLSB_OUT_INDEX,
+                                         QDIO_DBF_SLSB_OUT_NR_AREAS,
+                                         QDIO_DBF_SLSB_OUT_LEN);
+        if (!qdio_dbf_slsb_out)
+		goto oom;
+        debug_register_view(qdio_dbf_slsb_out,&debug_hex_ascii_view);
+        debug_set_level(qdio_dbf_slsb_out,QDIO_DBF_SLSB_OUT_LEVEL);
+
+        qdio_dbf_slsb_in=debug_register(QDIO_DBF_SLSB_IN_NAME,
+                                        QDIO_DBF_SLSB_IN_INDEX,
+                                        QDIO_DBF_SLSB_IN_NR_AREAS,
+                                        QDIO_DBF_SLSB_IN_LEN);
+        if (!qdio_dbf_slsb_in)
+		goto oom;
+        debug_register_view(qdio_dbf_slsb_in,&debug_hex_ascii_view);
+        debug_set_level(qdio_dbf_slsb_in,QDIO_DBF_SLSB_IN_LEVEL);
+#endif /* CONFIG_QDIO_DEBUG */
+	return 0;
+oom:
+	QDIO_PRINT_ERR("not enough memory for dbf.\n");
+	qdio_unregister_dbf_views();
+	return -ENOMEM;
+}
+
+static int __init
+init_QDIO(void)
+{
+	int res;
+#ifdef QDIO_PERFORMANCE_STATS
+	void *ptr;
+#endif /* QDIO_PERFORMANCE_STATS */
+
+	printk("qdio: loading %s\n",version);
+
+	res=qdio_get_qdio_memory();
+	if (res)
+		return res;
+
+	res = qdio_register_dbf_views();
+	if (res)
+		return res;
+
+	QDIO_DBF_TEXT0(0,setup,"initQDIO");
+
+#ifdef QDIO_PERFORMANCE_STATS
+       	memset((void*)&perf_stats,0,sizeof(perf_stats));
+	QDIO_DBF_TEXT0(0,setup,"perfstat");
+	ptr=&perf_stats;
+	QDIO_DBF_HEX0(0,setup,&ptr,sizeof(void*));
+#endif /* QDIO_PERFORMANCE_STATS */
+
+	qdio_add_procfs_entry();
+
+	if (tiqdio_check_chsc_availability())
+		QDIO_PRINT_ERR("Not all CHSCs supported. Continuing.\n");
+
+	tiqdio_register_thinints();
+
+	return 0;
+ }
+
+static void __exit
+cleanup_QDIO(void)
+{
+	tiqdio_unregister_thinints();
+	qdio_remove_procfs_entry();
+	qdio_release_qdio_memory();
+	qdio_unregister_dbf_views();
+
+  	printk("qdio: %s: module removed\n",version);
+}
+
+module_init(init_QDIO);
+module_exit(cleanup_QDIO);
+
+EXPORT_SYMBOL(qdio_allocate);
+EXPORT_SYMBOL(qdio_establish);
+EXPORT_SYMBOL(qdio_initialize);
+EXPORT_SYMBOL(qdio_activate);
+EXPORT_SYMBOL(do_QDIO);
+EXPORT_SYMBOL(qdio_shutdown);
+EXPORT_SYMBOL(qdio_free);
+EXPORT_SYMBOL(qdio_cleanup);
+EXPORT_SYMBOL(qdio_synchronize);
diff --git a/drivers/s390/cio/qdio.h b/drivers/s390/cio/qdio.h
new file mode 100644
index 0000000..9ad14db
--- /dev/null
+++ b/drivers/s390/cio/qdio.h
@@ -0,0 +1,648 @@
+#ifndef _CIO_QDIO_H
+#define _CIO_QDIO_H
+
+#define VERSION_CIO_QDIO_H "$Revision: 1.26 $"
+
+#ifdef CONFIG_QDIO_DEBUG
+#define QDIO_VERBOSE_LEVEL 9
+#else /* CONFIG_QDIO_DEBUG */
+#define QDIO_VERBOSE_LEVEL 5
+#endif /* CONFIG_QDIO_DEBUG */
+
+#define QDIO_USE_PROCESSING_STATE
+
+#ifdef CONFIG_QDIO_PERF_STATS
+#define QDIO_PERFORMANCE_STATS
+#endif /* CONFIG_QDIO_PERF_STATS */
+
+#define QDIO_MINIMAL_BH_RELIEF_TIME 16
+#define QDIO_TIMER_POLL_VALUE 1
+#define IQDIO_TIMER_POLL_VALUE 1
+
+/*
+ * unfortunately this can't be (QDIO_MAX_BUFFERS_PER_Q*4/3) or so -- as
+ * we never know, whether we'll get initiative again, e.g. to give the
+ * transmit skb's back to the stack, however the stack may be waiting for
+ * them... therefore we define 4 as threshold to start polling (which
+ * will stop as soon as the asynchronous queue catches up)
+ * btw, this only applies to the asynchronous HiperSockets queue
+ */
+#define IQDIO_FILL_LEVEL_TO_POLL 4
+
+#define TIQDIO_THININT_ISC 3
+#define TIQDIO_DELAY_TARGET 0
+#define QDIO_BUSY_BIT_PATIENCE 100 /* in microsecs */
+#define QDIO_BUSY_BIT_GIVE_UP 10000000 /* 10 seconds */
+#define IQDIO_GLOBAL_LAPS 2 /* GLOBAL_LAPS are not used as we */
+#define IQDIO_GLOBAL_LAPS_INT 1 /* don't global summary */
+#define IQDIO_LOCAL_LAPS 4
+#define IQDIO_LOCAL_LAPS_INT 1
+#define IQDIO_GLOBAL_SUMMARY_CC_MASK 2
+/*#define IQDIO_IQDC_INT_PARM 0x1234*/
+
+#define QDIO_Q_LAPS 5
+
+#define QDIO_STORAGE_KEY 0
+
+#define L2_CACHELINE_SIZE 256
+#define INDICATORS_PER_CACHELINE (L2_CACHELINE_SIZE/sizeof(__u32))
+
+#define QDIO_PERF "qdio_perf"
+
+/* must be a power of 2 */
+/*#define QDIO_STATS_NUMBER 4
+
+#define QDIO_STATS_CLASSES 2
+#define QDIO_STATS_COUNT_NEEDED 2*/
+
+#define QDIO_NO_USE_COUNT_TIMEOUT (1*HZ) /* wait for 1 sec on each q before
+					    exiting without having use_count
+					    of the queue to 0 */
+
+#define QDIO_ESTABLISH_TIMEOUT (1*HZ)
+#define QDIO_ACTIVATE_TIMEOUT ((5*HZ)>>10)
+#define QDIO_CLEANUP_CLEAR_TIMEOUT (20*HZ)
+#define QDIO_CLEANUP_HALT_TIMEOUT (10*HZ)
+
+enum qdio_irq_states {
+	QDIO_IRQ_STATE_INACTIVE,
+	QDIO_IRQ_STATE_ESTABLISHED,
+	QDIO_IRQ_STATE_ACTIVE,
+	QDIO_IRQ_STATE_STOPPED,
+	QDIO_IRQ_STATE_CLEANUP,
+	QDIO_IRQ_STATE_ERR,
+	NR_QDIO_IRQ_STATES,
+};
+
+/* used as intparm in do_IO: */
+#define QDIO_DOING_SENSEID 0
+#define QDIO_DOING_ESTABLISH 1
+#define QDIO_DOING_ACTIVATE 2
+#define QDIO_DOING_CLEANUP 3
+
+/************************* DEBUG FACILITY STUFF *********************/
+
+#define QDIO_DBF_HEX(ex,name,level,addr,len) \
+	do { \
+	if (ex) \
+		debug_exception(qdio_dbf_##name,level,(void*)(addr),len); \
+	else \
+		debug_event(qdio_dbf_##name,level,(void*)(addr),len); \
+	} while (0)
+#define QDIO_DBF_TEXT(ex,name,level,text) \
+	do { \
+	if (ex) \
+		debug_text_exception(qdio_dbf_##name,level,text); \
+	else \
+		debug_text_event(qdio_dbf_##name,level,text); \
+	} while (0)
+
+
+#define QDIO_DBF_HEX0(ex,name,addr,len) QDIO_DBF_HEX(ex,name,0,addr,len)
+#define QDIO_DBF_HEX1(ex,name,addr,len) QDIO_DBF_HEX(ex,name,1,addr,len)
+#define QDIO_DBF_HEX2(ex,name,addr,len) QDIO_DBF_HEX(ex,name,2,addr,len)
+#ifdef CONFIG_QDIO_DEBUG
+#define QDIO_DBF_HEX3(ex,name,addr,len) QDIO_DBF_HEX(ex,name,3,addr,len)
+#define QDIO_DBF_HEX4(ex,name,addr,len) QDIO_DBF_HEX(ex,name,4,addr,len)
+#define QDIO_DBF_HEX5(ex,name,addr,len) QDIO_DBF_HEX(ex,name,5,addr,len)
+#define QDIO_DBF_HEX6(ex,name,addr,len) QDIO_DBF_HEX(ex,name,6,addr,len)
+#else /* CONFIG_QDIO_DEBUG */
+#define QDIO_DBF_HEX3(ex,name,addr,len) do {} while (0)
+#define QDIO_DBF_HEX4(ex,name,addr,len) do {} while (0)
+#define QDIO_DBF_HEX5(ex,name,addr,len) do {} while (0)
+#define QDIO_DBF_HEX6(ex,name,addr,len) do {} while (0)
+#endif /* CONFIG_QDIO_DEBUG */
+
+#define QDIO_DBF_TEXT0(ex,name,text) QDIO_DBF_TEXT(ex,name,0,text)
+#define QDIO_DBF_TEXT1(ex,name,text) QDIO_DBF_TEXT(ex,name,1,text)
+#define QDIO_DBF_TEXT2(ex,name,text) QDIO_DBF_TEXT(ex,name,2,text)
+#ifdef CONFIG_QDIO_DEBUG
+#define QDIO_DBF_TEXT3(ex,name,text) QDIO_DBF_TEXT(ex,name,3,text)
+#define QDIO_DBF_TEXT4(ex,name,text) QDIO_DBF_TEXT(ex,name,4,text)
+#define QDIO_DBF_TEXT5(ex,name,text) QDIO_DBF_TEXT(ex,name,5,text)
+#define QDIO_DBF_TEXT6(ex,name,text) QDIO_DBF_TEXT(ex,name,6,text)
+#else /* CONFIG_QDIO_DEBUG */
+#define QDIO_DBF_TEXT3(ex,name,text) do {} while (0)
+#define QDIO_DBF_TEXT4(ex,name,text) do {} while (0)
+#define QDIO_DBF_TEXT5(ex,name,text) do {} while (0)
+#define QDIO_DBF_TEXT6(ex,name,text) do {} while (0)
+#endif /* CONFIG_QDIO_DEBUG */
+
+#define QDIO_DBF_SETUP_NAME "qdio_setup"
+#define QDIO_DBF_SETUP_LEN 8
+#define QDIO_DBF_SETUP_INDEX 2
+#define QDIO_DBF_SETUP_NR_AREAS 1
+#ifdef CONFIG_QDIO_DEBUG
+#define QDIO_DBF_SETUP_LEVEL 6
+#else /* CONFIG_QDIO_DEBUG */
+#define QDIO_DBF_SETUP_LEVEL 2
+#endif /* CONFIG_QDIO_DEBUG */
+
+#define QDIO_DBF_SBAL_NAME "qdio_labs" /* sbal */
+#define QDIO_DBF_SBAL_LEN 256
+#define QDIO_DBF_SBAL_INDEX 2
+#define QDIO_DBF_SBAL_NR_AREAS 2
+#ifdef CONFIG_QDIO_DEBUG
+#define QDIO_DBF_SBAL_LEVEL 6
+#else /* CONFIG_QDIO_DEBUG */
+#define QDIO_DBF_SBAL_LEVEL 2
+#endif /* CONFIG_QDIO_DEBUG */
+
+#define QDIO_DBF_TRACE_NAME "qdio_trace"
+#define QDIO_DBF_TRACE_LEN 8
+#define QDIO_DBF_TRACE_NR_AREAS 2
+#ifdef CONFIG_QDIO_DEBUG
+#define QDIO_DBF_TRACE_INDEX 4
+#define QDIO_DBF_TRACE_LEVEL 4 /* -------- could be even more verbose here */
+#else /* CONFIG_QDIO_DEBUG */
+#define QDIO_DBF_TRACE_INDEX 2
+#define QDIO_DBF_TRACE_LEVEL 2
+#endif /* CONFIG_QDIO_DEBUG */
+
+#define QDIO_DBF_SENSE_NAME "qdio_sense"
+#define QDIO_DBF_SENSE_LEN 64
+#define QDIO_DBF_SENSE_INDEX 1
+#define QDIO_DBF_SENSE_NR_AREAS 1
+#ifdef CONFIG_QDIO_DEBUG
+#define QDIO_DBF_SENSE_LEVEL 6
+#else /* CONFIG_QDIO_DEBUG */
+#define QDIO_DBF_SENSE_LEVEL 2
+#endif /* CONFIG_QDIO_DEBUG */
+
+#ifdef CONFIG_QDIO_DEBUG
+#define QDIO_TRACE_QTYPE QDIO_ZFCP_QFMT
+
+#define QDIO_DBF_SLSB_OUT_NAME "qdio_slsb_out"
+#define QDIO_DBF_SLSB_OUT_LEN QDIO_MAX_BUFFERS_PER_Q
+#define QDIO_DBF_SLSB_OUT_INDEX 8
+#define QDIO_DBF_SLSB_OUT_NR_AREAS 1
+#define QDIO_DBF_SLSB_OUT_LEVEL 6
+
+#define QDIO_DBF_SLSB_IN_NAME "qdio_slsb_in"
+#define QDIO_DBF_SLSB_IN_LEN QDIO_MAX_BUFFERS_PER_Q
+#define QDIO_DBF_SLSB_IN_INDEX 8
+#define QDIO_DBF_SLSB_IN_NR_AREAS 1
+#define QDIO_DBF_SLSB_IN_LEVEL 6
+#endif /* CONFIG_QDIO_DEBUG */
+
+#define QDIO_PRINTK_HEADER QDIO_NAME ": "
+
+#if QDIO_VERBOSE_LEVEL>8
+#define QDIO_PRINT_STUPID(x...) printk( KERN_DEBUG QDIO_PRINTK_HEADER x)
+#else
+#define QDIO_PRINT_STUPID(x...)
+#endif
+
+#if QDIO_VERBOSE_LEVEL>7
+#define QDIO_PRINT_ALL(x...) printk( QDIO_PRINTK_HEADER x)
+#else
+#define QDIO_PRINT_ALL(x...)
+#endif
+
+#if QDIO_VERBOSE_LEVEL>6
+#define QDIO_PRINT_INFO(x...) printk( QDIO_PRINTK_HEADER x)
+#else
+#define QDIO_PRINT_INFO(x...)
+#endif
+
+#if QDIO_VERBOSE_LEVEL>5
+#define QDIO_PRINT_WARN(x...) printk( QDIO_PRINTK_HEADER x)
+#else
+#define QDIO_PRINT_WARN(x...)
+#endif
+
+#if QDIO_VERBOSE_LEVEL>4
+#define QDIO_PRINT_ERR(x...) printk( QDIO_PRINTK_HEADER x)
+#else
+#define QDIO_PRINT_ERR(x...)
+#endif
+
+#if QDIO_VERBOSE_LEVEL>3
+#define QDIO_PRINT_CRIT(x...) printk( QDIO_PRINTK_HEADER x)
+#else
+#define QDIO_PRINT_CRIT(x...)
+#endif
+
+#if QDIO_VERBOSE_LEVEL>2
+#define QDIO_PRINT_ALERT(x...) printk( QDIO_PRINTK_HEADER x)
+#else
+#define QDIO_PRINT_ALERT(x...)
+#endif
+
+#if QDIO_VERBOSE_LEVEL>1
+#define QDIO_PRINT_EMERG(x...) printk( QDIO_PRINTK_HEADER x)
+#else
+#define QDIO_PRINT_EMERG(x...)
+#endif
+
+#define HEXDUMP16(importance,header,ptr) \
+QDIO_PRINT_##importance(header "%02x %02x %02x %02x  " \
+			"%02x %02x %02x %02x  %02x %02x %02x %02x  " \
+			"%02x %02x %02x %02x\n",*(((char*)ptr)), \
+			*(((char*)ptr)+1),*(((char*)ptr)+2), \
+			*(((char*)ptr)+3),*(((char*)ptr)+4), \
+			*(((char*)ptr)+5),*(((char*)ptr)+6), \
+			*(((char*)ptr)+7),*(((char*)ptr)+8), \
+			*(((char*)ptr)+9),*(((char*)ptr)+10), \
+			*(((char*)ptr)+11),*(((char*)ptr)+12), \
+			*(((char*)ptr)+13),*(((char*)ptr)+14), \
+			*(((char*)ptr)+15)); \
+QDIO_PRINT_##importance(header "%02x %02x %02x %02x  %02x %02x %02x %02x  " \
+			"%02x %02x %02x %02x  %02x %02x %02x %02x\n", \
+			*(((char*)ptr)+16),*(((char*)ptr)+17), \
+			*(((char*)ptr)+18),*(((char*)ptr)+19), \
+			*(((char*)ptr)+20),*(((char*)ptr)+21), \
+			*(((char*)ptr)+22),*(((char*)ptr)+23), \
+			*(((char*)ptr)+24),*(((char*)ptr)+25), \
+			*(((char*)ptr)+26),*(((char*)ptr)+27), \
+			*(((char*)ptr)+28),*(((char*)ptr)+29), \
+			*(((char*)ptr)+30),*(((char*)ptr)+31));
+
+/****************** END OF DEBUG FACILITY STUFF *********************/
+
+/*
+ * Some instructions as assembly
+ */
+extern __inline__ int 
+do_siga_sync(unsigned int irq, unsigned int mask1, unsigned int mask2)
+{
+	int cc;
+
+#ifndef CONFIG_ARCH_S390X
+	asm volatile (
+		"lhi	0,2	\n\t"
+		"lr	1,%1	\n\t"
+		"lr	2,%2	\n\t"
+		"lr	3,%3	\n\t"
+		"siga   0	\n\t"
+		"ipm	%0	\n\t"
+		"srl	%0,28	\n\t"
+		: "=d" (cc)
+		: "d" (0x10000|irq), "d" (mask1), "d" (mask2)
+		: "cc", "0", "1", "2", "3"
+		);
+#else /* CONFIG_ARCH_S390X */
+	asm volatile (
+		"lghi	0,2	\n\t"
+		"llgfr	1,%1	\n\t"
+		"llgfr	2,%2	\n\t"
+		"llgfr	3,%3	\n\t"
+		"siga   0	\n\t"
+		"ipm	%0	\n\t"
+		"srl	%0,28	\n\t"
+		: "=d" (cc)
+		: "d" (0x10000|irq), "d" (mask1), "d" (mask2)
+		: "cc", "0", "1", "2", "3"
+		);
+#endif /* CONFIG_ARCH_S390X */
+	return cc;
+}
+
+extern __inline__ int
+do_siga_input(unsigned int irq, unsigned int mask)
+{
+	int cc;
+
+#ifndef CONFIG_ARCH_S390X
+	asm volatile (
+		"lhi	0,1	\n\t"
+		"lr	1,%1	\n\t"
+		"lr	2,%2	\n\t"
+		"siga   0	\n\t"
+		"ipm	%0	\n\t"
+		"srl	%0,28	\n\t"
+		: "=d" (cc)
+		: "d" (0x10000|irq), "d" (mask)
+		: "cc", "0", "1", "2", "memory"
+		);
+#else /* CONFIG_ARCH_S390X */
+	asm volatile (
+		"lghi	0,1	\n\t"
+		"llgfr	1,%1	\n\t"
+		"llgfr	2,%2	\n\t"
+		"siga   0	\n\t"
+		"ipm	%0	\n\t"
+		"srl	%0,28	\n\t"
+		: "=d" (cc)
+		: "d" (0x10000|irq), "d" (mask)
+		: "cc", "0", "1", "2", "memory"
+		);
+#endif /* CONFIG_ARCH_S390X */
+	
+	return cc;
+}
+
+extern __inline__ int
+do_siga_output(unsigned long irq, unsigned long mask, __u32 *bb)
+{
+	int cc;
+	__u32 busy_bit;
+
+#ifndef CONFIG_ARCH_S390X
+	asm volatile (
+		"lhi	0,0	\n\t"
+		"lr	1,%2	\n\t"
+		"lr	2,%3	\n\t"
+		"siga	0	\n\t"
+		"0:"
+		"ipm	%0	\n\t"
+		"srl	%0,28	\n\t"
+		"srl	0,31	\n\t"
+		"lr	%1,0	\n\t"
+		"1:	\n\t"
+		".section .fixup,\"ax\"\n\t"
+		"2:	\n\t"
+		"lhi	%0,%4	\n\t"
+		"bras	1,3f	\n\t"
+		".long 1b	\n\t"
+		"3:	\n\t"
+		"l	1,0(1)	\n\t"
+		"br	1	\n\t"
+		".previous	\n\t"
+		".section __ex_table,\"a\"\n\t"
+		".align 4	\n\t"
+		".long	0b,2b	\n\t"
+		".previous	\n\t"
+		: "=d" (cc), "=d" (busy_bit)
+		: "d" (0x10000|irq), "d" (mask),
+		"i" (QDIO_SIGA_ERROR_ACCESS_EXCEPTION)
+		: "cc", "0", "1", "2", "memory"
+		);
+#else /* CONFIG_ARCH_S390X */
+	asm volatile (
+		"lghi	0,0	\n\t"
+		"llgfr	1,%2	\n\t"
+		"llgfr	2,%3	\n\t"
+		"siga	0	\n\t"
+		"0:"
+		"ipm	%0	\n\t"
+		"srl	%0,28	\n\t"
+		"srl	0,31	\n\t"
+		"llgfr	%1,0	\n\t"
+		"1:	\n\t"
+		".section .fixup,\"ax\"\n\t"
+		"lghi	%0,%4	\n\t"
+		"jg	1b	\n\t"
+		".previous\n\t"
+		".section __ex_table,\"a\"\n\t"
+		".align 8	\n\t"
+		".quad	0b,1b	\n\t"
+		".previous	\n\t"
+		: "=d" (cc), "=d" (busy_bit)
+		: "d" (0x10000|irq), "d" (mask),
+		"i" (QDIO_SIGA_ERROR_ACCESS_EXCEPTION)
+		: "cc", "0", "1", "2", "memory"
+		);
+#endif /* CONFIG_ARCH_S390X */
+	
+	(*bb) = busy_bit;
+	return cc;
+}
+
+extern __inline__ unsigned long
+do_clear_global_summary(void)
+{
+
+	unsigned long time;
+
+#ifndef CONFIG_ARCH_S390X
+	asm volatile (
+		"lhi	1,3	\n\t"
+		".insn	rre,0xb2650000,2,0	\n\t"
+		"lr	%0,3	\n\t"
+		: "=d" (time) : : "cc", "1", "2", "3"
+		);
+#else /* CONFIG_ARCH_S390X */
+	asm volatile (
+		"lghi	1,3	\n\t"
+		".insn	rre,0xb2650000,2,0	\n\t"
+		"lgr	%0,3	\n\t"
+		: "=d" (time) : : "cc", "1", "2", "3"
+		);
+#endif /* CONFIG_ARCH_S390X */
+	
+	return time;
+}
+	
+/*
+ * QDIO device commands returned by extended Sense-ID
+ */
+#define DEFAULT_ESTABLISH_QS_CMD 0x1b
+#define DEFAULT_ESTABLISH_QS_COUNT 0x1000
+#define DEFAULT_ACTIVATE_QS_CMD 0x1f
+#define DEFAULT_ACTIVATE_QS_COUNT 0
+
+/*
+ * additional CIWs returned by extended Sense-ID
+ */
+#define CIW_TYPE_EQUEUE 0x3       /* establish QDIO queues */
+#define CIW_TYPE_AQUEUE 0x4       /* activate QDIO queues */
+
+#define QDIO_CHSC_RESPONSE_CODE_OK 1
+/* flags for st qdio sch data */
+#define CHSC_FLAG_QDIO_CAPABILITY 0x80
+#define CHSC_FLAG_VALIDITY 0x40
+
+#define CHSC_FLAG_SIGA_INPUT_NECESSARY 0x40
+#define CHSC_FLAG_SIGA_OUTPUT_NECESSARY 0x20
+#define CHSC_FLAG_SIGA_SYNC_NECESSARY 0x10
+#define CHSC_FLAG_SIGA_SYNC_DONE_ON_THININTS 0x08
+#define CHSC_FLAG_SIGA_SYNC_DONE_ON_OUTB_PCIS 0x04
+
+#ifdef QDIO_PERFORMANCE_STATS
+struct qdio_perf_stats {
+	unsigned int tl_runs;
+
+	unsigned int siga_outs;
+	unsigned int siga_ins;
+	unsigned int siga_syncs;
+	unsigned int pcis;
+	unsigned int thinints;
+	unsigned int fast_reqs;
+
+	__u64 start_time_outbound;
+	unsigned int outbound_cnt;
+	unsigned int outbound_time;
+	__u64 start_time_inbound;
+	unsigned int inbound_cnt;
+	unsigned int inbound_time;
+};
+#endif /* QDIO_PERFORMANCE_STATS */
+
+#define atomic_swap(a,b) xchg((int*)a.counter,b)
+
+/* unlikely as the later the better */
+#define SYNC_MEMORY if (unlikely(q->siga_sync)) qdio_siga_sync_q(q)
+#define SYNC_MEMORY_ALL if (unlikely(q->siga_sync)) \
+	qdio_siga_sync(q,~0U,~0U)
+#define SYNC_MEMORY_ALL_OUTB if (unlikely(q->siga_sync)) \
+	qdio_siga_sync(q,~0U,0)
+
+#define NOW qdio_get_micros()
+#define SAVE_TIMESTAMP(q) q->timing.last_transfer_time=NOW
+#define GET_SAVED_TIMESTAMP(q) (q->timing.last_transfer_time)
+#define SAVE_FRONTIER(q,val) q->last_move_ftc=val
+#define GET_SAVED_FRONTIER(q) (q->last_move_ftc)
+
+#define MY_MODULE_STRING(x) #x
+
+#ifdef CONFIG_ARCH_S390X
+#define QDIO_GET_ADDR(x) ((__u32)(unsigned long)x)
+#else /* CONFIG_ARCH_S390X */
+#define QDIO_GET_ADDR(x) ((__u32)(long)x)
+#endif /* CONFIG_ARCH_S390X */
+
+#ifdef CONFIG_QDIO_DEBUG
+#define set_slsb(x,y) \
+  if(q->queue_type==QDIO_TRACE_QTYPE) { \
+        if(q->is_input_q) { \
+            QDIO_DBF_HEX2(0,slsb_in,&q->slsb,QDIO_MAX_BUFFERS_PER_Q); \
+        } else { \
+            QDIO_DBF_HEX2(0,slsb_out,&q->slsb,QDIO_MAX_BUFFERS_PER_Q); \
+        } \
+  } \
+  qdio_set_slsb(x,y); \
+  if(q->queue_type==QDIO_TRACE_QTYPE) { \
+        if(q->is_input_q) { \
+            QDIO_DBF_HEX2(0,slsb_in,&q->slsb,QDIO_MAX_BUFFERS_PER_Q); \
+        } else { \
+            QDIO_DBF_HEX2(0,slsb_out,&q->slsb,QDIO_MAX_BUFFERS_PER_Q); \
+        } \
+  }
+#else /* CONFIG_QDIO_DEBUG */
+#define set_slsb(x,y) qdio_set_slsb(x,y)
+#endif /* CONFIG_QDIO_DEBUG */
+
+struct qdio_q {
+	volatile struct slsb slsb;
+
+	char unused[QDIO_MAX_BUFFERS_PER_Q];
+
+	__u32 * volatile dev_st_chg_ind;
+
+	int is_input_q;
+	int irq;
+	struct ccw_device *cdev;
+
+	unsigned int is_iqdio_q;
+	unsigned int is_thinint_q;
+
+	/* bit 0 means queue 0, bit 1 means queue 1, ... */
+	unsigned int mask;
+	unsigned int q_no;
+
+	qdio_handler_t (*handler);
+
+	/* points to the next buffer to be checked for having
+	 * been processed by the card (outbound)
+	 * or to the next buffer the program should check for (inbound) */
+	volatile int first_to_check;
+	/* and the last time it was: */
+	volatile int last_move_ftc;
+
+	atomic_t number_of_buffers_used;
+	atomic_t polling;
+
+	unsigned int siga_in;
+	unsigned int siga_out;
+	unsigned int siga_sync;
+	unsigned int siga_sync_done_on_thinints;
+	unsigned int siga_sync_done_on_outb_tis;
+	unsigned int hydra_gives_outbound_pcis;
+
+	/* used to save beginning position when calling dd_handlers */
+	int first_element_to_kick;
+
+	atomic_t use_count;
+	atomic_t is_in_shutdown;
+
+	void *irq_ptr;
+
+#ifdef QDIO_USE_TIMERS_FOR_POLLING
+	struct timer_list timer;
+	atomic_t timer_already_set;
+	spinlock_t timer_lock;
+#else /* QDIO_USE_TIMERS_FOR_POLLING */
+	struct tasklet_struct tasklet;
+#endif /* QDIO_USE_TIMERS_FOR_POLLING */
+
+	enum qdio_irq_states state;
+
+	/* used to store the error condition during a data transfer */
+	unsigned int qdio_error;
+	unsigned int siga_error;
+	unsigned int error_status_flags;
+
+	/* list of interesting queues */
+	volatile struct qdio_q *list_next;
+	volatile struct qdio_q *list_prev;
+
+	struct sl *sl;
+	volatile struct sbal *sbal[QDIO_MAX_BUFFERS_PER_Q];
+
+	struct qdio_buffer *qdio_buffers[QDIO_MAX_BUFFERS_PER_Q];
+
+	unsigned long int_parm;
+
+	/*struct {
+		int in_bh_check_limit;
+		int threshold;
+	} threshold_classes[QDIO_STATS_CLASSES];*/
+
+	struct {
+		/* inbound: the time to stop polling
+		   outbound: the time to kick peer */
+		int threshold; /* the real value */
+
+		/* outbound: last time of do_QDIO
+		   inbound: last time of noticing incoming data */
+		/*__u64 last_transfer_times[QDIO_STATS_NUMBER];
+		int last_transfer_index; */
+
+		__u64 last_transfer_time;
+		__u64 busy_start;
+	} timing;
+	atomic_t busy_siga_counter;
+        unsigned int queue_type;
+
+	/* leave this member at the end. won't be cleared in qdio_fill_qs */
+	struct slib *slib; /* a page is allocated under this pointer,
+			      sl points into this page, offset PAGE_SIZE/2
+			      (after slib) */
+} __attribute__ ((aligned(256)));
+
+struct qdio_irq {
+	__u32 * volatile dev_st_chg_ind;
+
+	unsigned long int_parm;
+	int irq;
+
+	unsigned int is_iqdio_irq;
+	unsigned int is_thinint_irq;
+	unsigned int hydra_gives_outbound_pcis;
+	unsigned int sync_done_on_outb_pcis;
+
+	enum qdio_irq_states state;
+
+	unsigned int no_input_qs;
+	unsigned int no_output_qs;
+
+	unsigned char qdioac;
+
+	struct ccw1 ccw;
+
+	struct ciw equeue;
+	struct ciw aqueue;
+
+	struct qib qib;
+	
+ 	void (*original_int_handler) (struct ccw_device *,
+ 				      unsigned long, struct irb *);
+
+	/* leave these four members together at the end. won't be cleared in qdio_fill_irq */
+	struct qdr *qdr;
+	struct qdio_q *input_qs[QDIO_MAX_QUEUES_PER_IRQ];
+	struct qdio_q *output_qs[QDIO_MAX_QUEUES_PER_IRQ];
+	struct semaphore setting_up_sema;
+};
+#endif