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/block/Kconfig b/drivers/s390/block/Kconfig
new file mode 100644
index 0000000..dc1c89d
--- /dev/null
+++ b/drivers/s390/block/Kconfig
@@ -0,0 +1,68 @@
+if ARCH_S390
+
+comment "S/390 block device drivers"
+	depends on ARCH_S390
+
+config BLK_DEV_XPRAM
+	tristate "XPRAM disk support"
+	depends on ARCH_S390
+	help
+	  Select this option if you want to use your expanded storage on S/390
+	  or zSeries as a disk.  This is useful as a _fast_ swap device if you
+	  want to access more than 2G of memory when running in 31 bit mode.
+	  This option is also available as a module which will be called
+	  xpram.  If unsure, say "N".
+
+config DCSSBLK
+	tristate "DCSSBLK support"
+	help
+	  Support for dcss block device
+
+config DASD
+	tristate "Support for DASD devices"
+	depends on CCW
+	help
+	  Enable this option if you want to access DASDs directly utilizing
+	  S/390s channel subsystem commands. This is necessary for running
+	  natively on a single image or an LPAR.
+
+config DASD_PROFILE
+	bool "Profiling support for dasd devices"
+	depends on DASD
+	help
+	  Enable this option if you want to see profiling information
+          in /proc/dasd/statistics.
+
+config DASD_ECKD
+	tristate "Support for ECKD Disks"
+	depends on DASD
+	help
+	  ECKD devices are the most commonly used devices. You should enable
+	  this option unless you are very sure to have no ECKD device.
+
+config DASD_FBA
+	tristate "Support for FBA  Disks"
+	depends on DASD
+	help
+	  Select this option to be able to access FBA devices. It is safe to
+	  say "Y".
+
+config DASD_DIAG
+	tristate "Support for DIAG access to Disks"
+	depends on DASD && ARCH_S390X = 'n'
+	help
+	  Select this option if you want to use Diagnose250 command to access
+	  Disks under VM.  If you are not running under VM or unsure what it is,
+	  say "N".
+
+config DASD_CMB
+	tristate "Compatibility interface for DASD channel measurement blocks"
+	depends on DASD
+	help
+	  This driver provides an additional interface to the channel measurement
+	  facility, which is normally accessed though sysfs, with a set of
+	  ioctl functions specific to the dasd driver.
+	  This is only needed if you want to use applications written for
+	  linux-2.4 dasd channel measurement facility interface.
+
+endif
diff --git a/drivers/s390/block/Makefile b/drivers/s390/block/Makefile
new file mode 100644
index 0000000..58c6780
--- /dev/null
+++ b/drivers/s390/block/Makefile
@@ -0,0 +1,17 @@
+#
+# S/390 block devices
+#
+
+dasd_eckd_mod-objs := dasd_eckd.o dasd_3990_erp.o dasd_9343_erp.o
+dasd_fba_mod-objs  := dasd_fba.o dasd_3370_erp.o dasd_9336_erp.o
+dasd_diag_mod-objs := dasd_diag.o
+dasd_mod-objs      := dasd.o dasd_ioctl.o dasd_proc.o dasd_devmap.o \
+			dasd_genhd.o dasd_erp.o
+
+obj-$(CONFIG_DASD) += dasd_mod.o
+obj-$(CONFIG_DASD_DIAG) += dasd_diag_mod.o
+obj-$(CONFIG_DASD_ECKD) += dasd_eckd_mod.o
+obj-$(CONFIG_DASD_FBA)  += dasd_fba_mod.o
+obj-$(CONFIG_DASD_CMB)  += dasd_cmb.o
+obj-$(CONFIG_BLK_DEV_XPRAM) += xpram.o
+obj-$(CONFIG_DCSSBLK) += dcssblk.o
diff --git a/drivers/s390/block/dasd.c b/drivers/s390/block/dasd.c
new file mode 100644
index 0000000..b755bac
--- /dev/null
+++ b/drivers/s390/block/dasd.c
@@ -0,0 +1,2065 @@
+/*
+ * File...........: linux/drivers/s390/block/dasd.c
+ * Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
+ *		    Horst Hummel <Horst.Hummel@de.ibm.com>
+ *		    Carsten Otte <Cotte@de.ibm.com>
+ *		    Martin Schwidefsky <schwidefsky@de.ibm.com>
+ * Bugreports.to..: <Linux390@de.ibm.com>
+ * (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999-2001
+ *
+ * $Revision: 1.158 $
+ */
+
+#include <linux/config.h>
+#include <linux/kmod.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/ctype.h>
+#include <linux/major.h>
+#include <linux/slab.h>
+#include <linux/buffer_head.h>
+
+#include <asm/ccwdev.h>
+#include <asm/ebcdic.h>
+#include <asm/idals.h>
+#include <asm/todclk.h>
+
+/* This is ugly... */
+#define PRINTK_HEADER "dasd:"
+
+#include "dasd_int.h"
+/*
+ * SECTION: Constant definitions to be used within this file
+ */
+#define DASD_CHANQ_MAX_SIZE 4
+
+/*
+ * SECTION: exported variables of dasd.c
+ */
+debug_info_t *dasd_debug_area;
+struct dasd_discipline *dasd_diag_discipline_pointer;
+
+MODULE_AUTHOR("Holger Smolinski <Holger.Smolinski@de.ibm.com>");
+MODULE_DESCRIPTION("Linux on S/390 DASD device driver,"
+		   " Copyright 2000 IBM Corporation");
+MODULE_SUPPORTED_DEVICE("dasd");
+MODULE_PARM(dasd, "1-" __MODULE_STRING(256) "s");
+MODULE_LICENSE("GPL");
+
+/*
+ * SECTION: prototypes for static functions of dasd.c
+ */
+static int  dasd_alloc_queue(struct dasd_device * device);
+static void dasd_setup_queue(struct dasd_device * device);
+static void dasd_free_queue(struct dasd_device * device);
+static void dasd_flush_request_queue(struct dasd_device *);
+static void dasd_int_handler(struct ccw_device *, unsigned long, struct irb *);
+static void dasd_flush_ccw_queue(struct dasd_device *, int);
+static void dasd_tasklet(struct dasd_device *);
+static void do_kick_device(void *data);
+
+/*
+ * SECTION: Operations on the device structure.
+ */
+static wait_queue_head_t dasd_init_waitq;
+
+/*
+ * Allocate memory for a new device structure.
+ */
+struct dasd_device *
+dasd_alloc_device(void)
+{
+	struct dasd_device *device;
+
+	device = kmalloc(sizeof (struct dasd_device), GFP_ATOMIC);
+	if (device == NULL)
+		return ERR_PTR(-ENOMEM);
+	memset(device, 0, sizeof (struct dasd_device));
+	/* open_count = 0 means device online but not in use */
+	atomic_set(&device->open_count, -1);
+
+	/* Get two pages for normal block device operations. */
+	device->ccw_mem = (void *) __get_free_pages(GFP_ATOMIC | GFP_DMA, 1);
+	if (device->ccw_mem == NULL) {
+		kfree(device);
+		return ERR_PTR(-ENOMEM);
+	}
+	/* Get one page for error recovery. */
+	device->erp_mem = (void *) get_zeroed_page(GFP_ATOMIC | GFP_DMA);
+	if (device->erp_mem == NULL) {
+		free_pages((unsigned long) device->ccw_mem, 1);
+		kfree(device);
+		return ERR_PTR(-ENOMEM);
+	}
+
+	dasd_init_chunklist(&device->ccw_chunks, device->ccw_mem, PAGE_SIZE*2);
+	dasd_init_chunklist(&device->erp_chunks, device->erp_mem, PAGE_SIZE);
+	spin_lock_init(&device->mem_lock);
+	spin_lock_init(&device->request_queue_lock);
+	atomic_set (&device->tasklet_scheduled, 0);
+	tasklet_init(&device->tasklet, 
+		     (void (*)(unsigned long)) dasd_tasklet,
+		     (unsigned long) device);
+	INIT_LIST_HEAD(&device->ccw_queue);
+	init_timer(&device->timer);
+	INIT_WORK(&device->kick_work, do_kick_device, device);
+	device->state = DASD_STATE_NEW;
+	device->target = DASD_STATE_NEW;
+
+	return device;
+}
+
+/*
+ * Free memory of a device structure.
+ */
+void
+dasd_free_device(struct dasd_device *device)
+{
+	if (device->private)
+		kfree(device->private);
+	free_page((unsigned long) device->erp_mem);
+	free_pages((unsigned long) device->ccw_mem, 1);
+	kfree(device);
+}
+
+/*
+ * Make a new device known to the system.
+ */
+static inline int
+dasd_state_new_to_known(struct dasd_device *device)
+{
+	int rc;
+
+	/*
+	 * As long as the device is not in state DASD_STATE_NEW we want to 
+	 * keep the reference count > 0.
+	 */
+	dasd_get_device(device);
+
+	rc = dasd_alloc_queue(device);
+	if (rc) {
+		dasd_put_device(device);
+		return rc;
+	}
+
+	device->state = DASD_STATE_KNOWN;
+	return 0;
+}
+
+/*
+ * Let the system forget about a device.
+ */
+static inline void
+dasd_state_known_to_new(struct dasd_device * device)
+{
+	/* Forget the discipline information. */
+	device->discipline = NULL;
+	device->state = DASD_STATE_NEW;
+
+	dasd_free_queue(device);
+
+	/* Give up reference we took in dasd_state_new_to_known. */
+	dasd_put_device(device);
+}
+
+/*
+ * Request the irq line for the device.
+ */
+static inline int
+dasd_state_known_to_basic(struct dasd_device * device)
+{
+	int rc;
+
+	/* Allocate and register gendisk structure. */
+	rc = dasd_gendisk_alloc(device);
+	if (rc)
+		return rc;
+
+	/* register 'device' debug area, used for all DBF_DEV_XXX calls */
+	device->debug_area = debug_register(device->cdev->dev.bus_id, 0, 2,
+					    8 * sizeof (long));
+	debug_register_view(device->debug_area, &debug_sprintf_view);
+	debug_set_level(device->debug_area, DBF_EMERG);
+	DBF_DEV_EVENT(DBF_EMERG, device, "%s", "debug area created");
+
+	device->state = DASD_STATE_BASIC;
+	return 0;
+}
+
+/*
+ * Release the irq line for the device. Terminate any running i/o.
+ */
+static inline void
+dasd_state_basic_to_known(struct dasd_device * device)
+{
+	dasd_gendisk_free(device);
+	dasd_flush_ccw_queue(device, 1);
+	DBF_DEV_EVENT(DBF_EMERG, device, "%p debug area deleted", device);
+	if (device->debug_area != NULL) {
+		debug_unregister(device->debug_area);
+		device->debug_area = NULL;
+	}
+	device->state = DASD_STATE_KNOWN;
+}
+
+/*
+ * Do the initial analysis. The do_analysis function may return
+ * -EAGAIN in which case the device keeps the state DASD_STATE_BASIC
+ * until the discipline decides to continue the startup sequence
+ * by calling the function dasd_change_state. The eckd disciplines
+ * uses this to start a ccw that detects the format. The completion
+ * interrupt for this detection ccw uses the kernel event daemon to
+ * trigger the call to dasd_change_state. All this is done in the
+ * discipline code, see dasd_eckd.c.
+ * After the analysis ccw is done (do_analysis returned 0 or error)
+ * the block device is setup. Either a fake disk is added to allow
+ * formatting or a proper device request queue is created.
+ */
+static inline int
+dasd_state_basic_to_ready(struct dasd_device * device)
+{
+	int rc;
+
+	rc = 0;
+	if (device->discipline->do_analysis != NULL)
+		rc = device->discipline->do_analysis(device);
+	if (rc)
+		return rc;
+	dasd_setup_queue(device);
+	device->state = DASD_STATE_READY;
+	if (dasd_scan_partitions(device) != 0)
+		device->state = DASD_STATE_BASIC;
+	return 0;
+}
+
+/*
+ * Remove device from block device layer. Destroy dirty buffers.
+ * Forget format information. Check if the target level is basic
+ * and if it is create fake disk for formatting.
+ */
+static inline void
+dasd_state_ready_to_basic(struct dasd_device * device)
+{
+	dasd_flush_ccw_queue(device, 0);
+	dasd_destroy_partitions(device);
+	dasd_flush_request_queue(device);
+	device->blocks = 0;
+	device->bp_block = 0;
+	device->s2b_shift = 0;
+	device->state = DASD_STATE_BASIC;
+}
+
+/*
+ * Make the device online and schedule the bottom half to start
+ * the requeueing of requests from the linux request queue to the
+ * ccw queue.
+ */
+static inline int
+dasd_state_ready_to_online(struct dasd_device * device)
+{
+	device->state = DASD_STATE_ONLINE;
+	dasd_schedule_bh(device);
+	return 0;
+}
+
+/*
+ * Stop the requeueing of requests again.
+ */
+static inline void
+dasd_state_online_to_ready(struct dasd_device * device)
+{
+	device->state = DASD_STATE_READY;
+}
+
+/*
+ * Device startup state changes.
+ */
+static inline int
+dasd_increase_state(struct dasd_device *device)
+{
+	int rc;
+
+	rc = 0;
+	if (device->state == DASD_STATE_NEW &&
+	    device->target >= DASD_STATE_KNOWN)
+		rc = dasd_state_new_to_known(device);
+
+	if (!rc &&
+	    device->state == DASD_STATE_KNOWN &&
+	    device->target >= DASD_STATE_BASIC)
+		rc = dasd_state_known_to_basic(device);
+
+	if (!rc &&
+	    device->state == DASD_STATE_BASIC &&
+	    device->target >= DASD_STATE_READY)
+		rc = dasd_state_basic_to_ready(device);
+
+	if (!rc &&
+	    device->state == DASD_STATE_READY &&
+	    device->target >= DASD_STATE_ONLINE)
+		rc = dasd_state_ready_to_online(device);
+
+	return rc;
+}
+
+/*
+ * Device shutdown state changes.
+ */
+static inline int
+dasd_decrease_state(struct dasd_device *device)
+{
+	if (device->state == DASD_STATE_ONLINE &&
+	    device->target <= DASD_STATE_READY)
+		dasd_state_online_to_ready(device);
+	
+	if (device->state == DASD_STATE_READY &&
+	    device->target <= DASD_STATE_BASIC)
+		dasd_state_ready_to_basic(device);
+	
+	if (device->state == DASD_STATE_BASIC && 
+	    device->target <= DASD_STATE_KNOWN)
+		dasd_state_basic_to_known(device);
+	
+	if (device->state == DASD_STATE_KNOWN &&
+	    device->target <= DASD_STATE_NEW)
+		dasd_state_known_to_new(device);
+
+	return 0;
+}
+
+/*
+ * This is the main startup/shutdown routine.
+ */
+static void
+dasd_change_state(struct dasd_device *device)
+{
+        int rc;
+
+	if (device->state == device->target)
+		/* Already where we want to go today... */
+		return;
+	if (device->state < device->target)
+		rc = dasd_increase_state(device);
+	else
+		rc = dasd_decrease_state(device);
+        if (rc && rc != -EAGAIN)
+                device->target = device->state;
+
+	if (device->state == device->target)
+		wake_up(&dasd_init_waitq);
+}
+
+/*
+ * Kick starter for devices that did not complete the startup/shutdown
+ * procedure or were sleeping because of a pending state.
+ * dasd_kick_device will schedule a call do do_kick_device to the kernel
+ * event daemon.
+ */
+static void
+do_kick_device(void *data)
+{
+	struct dasd_device *device;
+
+	device = (struct dasd_device *) data;
+	dasd_change_state(device);
+	dasd_schedule_bh(device);
+	dasd_put_device(device);
+}
+
+void
+dasd_kick_device(struct dasd_device *device)
+{
+	dasd_get_device(device);
+	/* queue call to dasd_kick_device to the kernel event daemon. */
+	schedule_work(&device->kick_work);
+}
+
+/*
+ * Set the target state for a device and starts the state change.
+ */
+void
+dasd_set_target_state(struct dasd_device *device, int target)
+{
+	/* If we are in probeonly mode stop at DASD_STATE_READY. */
+	if (dasd_probeonly && target > DASD_STATE_READY)
+		target = DASD_STATE_READY;
+	if (device->target != target) {
+                if (device->state == target)
+			wake_up(&dasd_init_waitq);
+		device->target = target;
+	}
+	if (device->state != device->target)
+		dasd_change_state(device);
+}
+
+/*
+ * Enable devices with device numbers in [from..to].
+ */
+static inline int
+_wait_for_device(struct dasd_device *device)
+{
+	return (device->state == device->target);
+}
+
+void
+dasd_enable_device(struct dasd_device *device)
+{
+	dasd_set_target_state(device, DASD_STATE_ONLINE);
+	if (device->state <= DASD_STATE_KNOWN)
+		/* No discipline for device found. */
+		dasd_set_target_state(device, DASD_STATE_NEW);
+	/* Now wait for the devices to come up. */
+	wait_event(dasd_init_waitq, _wait_for_device(device));
+}
+
+/*
+ * SECTION: device operation (interrupt handler, start i/o, term i/o ...)
+ */
+#ifdef CONFIG_DASD_PROFILE
+
+struct dasd_profile_info_t dasd_global_profile;
+unsigned int dasd_profile_level = DASD_PROFILE_OFF;
+
+/*
+ * Increments counter in global and local profiling structures.
+ */
+#define dasd_profile_counter(value, counter, device) \
+{ \
+	int index; \
+	for (index = 0; index < 31 && value >> (2+index); index++); \
+	dasd_global_profile.counter[index]++; \
+	device->profile.counter[index]++; \
+}
+
+/*
+ * Add profiling information for cqr before execution.
+ */
+static inline void
+dasd_profile_start(struct dasd_device *device, struct dasd_ccw_req * cqr,
+		   struct request *req)
+{
+	struct list_head *l;
+	unsigned int counter;
+
+	if (dasd_profile_level != DASD_PROFILE_ON)
+		return;
+
+	/* count the length of the chanq for statistics */
+	counter = 0;
+	list_for_each(l, &device->ccw_queue)
+		if (++counter >= 31)
+			break;
+	dasd_global_profile.dasd_io_nr_req[counter]++;
+	device->profile.dasd_io_nr_req[counter]++;
+}
+
+/*
+ * Add profiling information for cqr after execution.
+ */
+static inline void
+dasd_profile_end(struct dasd_device *device, struct dasd_ccw_req * cqr,
+		 struct request *req)
+{
+	long strtime, irqtime, endtime, tottime;	/* in microseconds */
+	long tottimeps, sectors;
+
+	if (dasd_profile_level != DASD_PROFILE_ON)
+		return;
+
+	sectors = req->nr_sectors;
+	if (!cqr->buildclk || !cqr->startclk ||
+	    !cqr->stopclk || !cqr->endclk ||
+	    !sectors)
+		return;
+
+	strtime = ((cqr->startclk - cqr->buildclk) >> 12);
+	irqtime = ((cqr->stopclk - cqr->startclk) >> 12);
+	endtime = ((cqr->endclk - cqr->stopclk) >> 12);
+	tottime = ((cqr->endclk - cqr->buildclk) >> 12);
+	tottimeps = tottime / sectors;
+
+	if (!dasd_global_profile.dasd_io_reqs)
+		memset(&dasd_global_profile, 0,
+		       sizeof (struct dasd_profile_info_t));
+	dasd_global_profile.dasd_io_reqs++;
+	dasd_global_profile.dasd_io_sects += sectors;
+
+	if (!device->profile.dasd_io_reqs)
+		memset(&device->profile, 0,
+		       sizeof (struct dasd_profile_info_t));
+	device->profile.dasd_io_reqs++;
+	device->profile.dasd_io_sects += sectors;
+
+	dasd_profile_counter(sectors, dasd_io_secs, device);
+	dasd_profile_counter(tottime, dasd_io_times, device);
+	dasd_profile_counter(tottimeps, dasd_io_timps, device);
+	dasd_profile_counter(strtime, dasd_io_time1, device);
+	dasd_profile_counter(irqtime, dasd_io_time2, device);
+	dasd_profile_counter(irqtime / sectors, dasd_io_time2ps, device);
+	dasd_profile_counter(endtime, dasd_io_time3, device);
+}
+#else
+#define dasd_profile_start(device, cqr, req) do {} while (0)
+#define dasd_profile_end(device, cqr, req) do {} while (0)
+#endif				/* CONFIG_DASD_PROFILE */
+
+/*
+ * Allocate memory for a channel program with 'cplength' channel
+ * command words and 'datasize' additional space. There are two
+ * variantes: 1) dasd_kmalloc_request uses kmalloc to get the needed
+ * memory and 2) dasd_smalloc_request uses the static ccw memory
+ * that gets allocated for each device.
+ */
+struct dasd_ccw_req *
+dasd_kmalloc_request(char *magic, int cplength, int datasize,
+		   struct dasd_device * device)
+{
+	struct dasd_ccw_req *cqr;
+
+	/* Sanity checks */
+	if ( magic == NULL || datasize > PAGE_SIZE ||
+	     (cplength*sizeof(struct ccw1)) > PAGE_SIZE)
+		BUG();
+
+	cqr = kmalloc(sizeof(struct dasd_ccw_req), GFP_ATOMIC);
+	if (cqr == NULL)
+		return ERR_PTR(-ENOMEM);
+	memset(cqr, 0, sizeof(struct dasd_ccw_req));
+	cqr->cpaddr = NULL;
+	if (cplength > 0) {
+		cqr->cpaddr = kmalloc(cplength*sizeof(struct ccw1),
+				      GFP_ATOMIC | GFP_DMA);
+		if (cqr->cpaddr == NULL) {
+			kfree(cqr);
+			return ERR_PTR(-ENOMEM);
+		}
+		memset(cqr->cpaddr, 0, cplength*sizeof(struct ccw1));
+	}
+	cqr->data = NULL;
+	if (datasize > 0) {
+		cqr->data = kmalloc(datasize, GFP_ATOMIC | GFP_DMA);
+		if (cqr->data == NULL) {
+			if (cqr->cpaddr != NULL)
+				kfree(cqr->cpaddr);
+			kfree(cqr);
+			return ERR_PTR(-ENOMEM);
+		}
+		memset(cqr->data, 0, datasize);
+	}
+	strncpy((char *) &cqr->magic, magic, 4);
+	ASCEBC((char *) &cqr->magic, 4);
+	set_bit(DASD_CQR_FLAGS_USE_ERP, &cqr->flags);
+	dasd_get_device(device);
+	return cqr;
+}
+
+struct dasd_ccw_req *
+dasd_smalloc_request(char *magic, int cplength, int datasize,
+		   struct dasd_device * device)
+{
+	unsigned long flags;
+	struct dasd_ccw_req *cqr;
+	char *data;
+	int size;
+
+	/* Sanity checks */
+	if ( magic == NULL || datasize > PAGE_SIZE ||
+	     (cplength*sizeof(struct ccw1)) > PAGE_SIZE)
+		BUG();
+
+	size = (sizeof(struct dasd_ccw_req) + 7L) & -8L;
+	if (cplength > 0)
+		size += cplength * sizeof(struct ccw1);
+	if (datasize > 0)
+		size += datasize;
+	spin_lock_irqsave(&device->mem_lock, flags);
+	cqr = (struct dasd_ccw_req *)
+		dasd_alloc_chunk(&device->ccw_chunks, size);
+	spin_unlock_irqrestore(&device->mem_lock, flags);
+	if (cqr == NULL)
+		return ERR_PTR(-ENOMEM);
+	memset(cqr, 0, sizeof(struct dasd_ccw_req));
+	data = (char *) cqr + ((sizeof(struct dasd_ccw_req) + 7L) & -8L);
+	cqr->cpaddr = NULL;
+	if (cplength > 0) {
+		cqr->cpaddr = (struct ccw1 *) data;
+		data += cplength*sizeof(struct ccw1);
+		memset(cqr->cpaddr, 0, cplength*sizeof(struct ccw1));
+	}
+	cqr->data = NULL;
+	if (datasize > 0) {
+		cqr->data = data;
+ 		memset(cqr->data, 0, datasize);
+	}
+	strncpy((char *) &cqr->magic, magic, 4);
+	ASCEBC((char *) &cqr->magic, 4);
+	set_bit(DASD_CQR_FLAGS_USE_ERP, &cqr->flags);
+	dasd_get_device(device);
+	return cqr;
+}
+
+/*
+ * Free memory of a channel program. This function needs to free all the
+ * idal lists that might have been created by dasd_set_cda and the
+ * struct dasd_ccw_req itself.
+ */
+void
+dasd_kfree_request(struct dasd_ccw_req * cqr, struct dasd_device * device)
+{
+#ifdef CONFIG_ARCH_S390X
+	struct ccw1 *ccw;
+
+	/* Clear any idals used for the request. */
+	ccw = cqr->cpaddr;
+	do {
+		clear_normalized_cda(ccw);
+	} while (ccw++->flags & (CCW_FLAG_CC | CCW_FLAG_DC));
+#endif
+	if (cqr->cpaddr != NULL)
+		kfree(cqr->cpaddr);
+	if (cqr->data != NULL)
+		kfree(cqr->data);
+	kfree(cqr);
+	dasd_put_device(device);
+}
+
+void
+dasd_sfree_request(struct dasd_ccw_req * cqr, struct dasd_device * device)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&device->mem_lock, flags);
+	dasd_free_chunk(&device->ccw_chunks, cqr);
+	spin_unlock_irqrestore(&device->mem_lock, flags);
+	dasd_put_device(device);
+}
+
+/*
+ * Check discipline magic in cqr.
+ */
+static inline int
+dasd_check_cqr(struct dasd_ccw_req *cqr)
+{
+	struct dasd_device *device;
+
+	if (cqr == NULL)
+		return -EINVAL;
+	device = cqr->device;
+	if (strncmp((char *) &cqr->magic, device->discipline->ebcname, 4)) {
+		DEV_MESSAGE(KERN_WARNING, device,
+			    " dasd_ccw_req 0x%08x magic doesn't match"
+			    " discipline 0x%08x",
+			    cqr->magic,
+			    *(unsigned int *) device->discipline->name);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/*
+ * Terminate the current i/o and set the request to clear_pending.
+ * Timer keeps device runnig.
+ * ccw_device_clear can fail if the i/o subsystem
+ * is in a bad mood.
+ */
+int
+dasd_term_IO(struct dasd_ccw_req * cqr)
+{
+	struct dasd_device *device;
+	int retries, rc;
+
+	/* Check the cqr */
+	rc = dasd_check_cqr(cqr);
+	if (rc)
+		return rc;
+	retries = 0;
+	device = (struct dasd_device *) cqr->device;
+	while ((retries < 5) && (cqr->status == DASD_CQR_IN_IO)) {
+		rc = ccw_device_clear(device->cdev, (long) cqr);
+		switch (rc) {
+		case 0:	/* termination successful */
+		        if (cqr->retries > 0) {
+				cqr->retries--;
+				cqr->status = DASD_CQR_CLEAR;
+			} else
+				cqr->status = DASD_CQR_FAILED;
+			cqr->stopclk = get_clock();
+			DBF_DEV_EVENT(DBF_DEBUG, device,
+				      "terminate cqr %p successful",
+				      cqr);
+			break;
+		case -ENODEV:
+			DBF_DEV_EVENT(DBF_ERR, device, "%s",
+				      "device gone, retry");
+			break;
+		case -EIO:
+			DBF_DEV_EVENT(DBF_ERR, device, "%s",
+				      "I/O error, retry");
+			break;
+		case -EINVAL:
+		case -EBUSY:
+			DBF_DEV_EVENT(DBF_ERR, device, "%s",
+				      "device busy, retry later");
+			break;
+		default:
+			DEV_MESSAGE(KERN_ERR, device,
+				    "line %d unknown RC=%d, please "
+				    "report to linux390@de.ibm.com",
+				    __LINE__, rc);
+			BUG();
+			break;
+		}
+		retries++;
+	}
+	dasd_schedule_bh(device);
+	return rc;
+}
+
+/*
+ * Start the i/o. This start_IO can fail if the channel is really busy.
+ * In that case set up a timer to start the request later.
+ */
+int
+dasd_start_IO(struct dasd_ccw_req * cqr)
+{
+	struct dasd_device *device;
+	int rc;
+
+	/* Check the cqr */
+	rc = dasd_check_cqr(cqr);
+	if (rc)
+		return rc;
+	device = (struct dasd_device *) cqr->device;
+	if (cqr->retries < 0) {
+		DEV_MESSAGE(KERN_DEBUG, device,
+			    "start_IO: request %p (%02x/%i) - no retry left.",
+			    cqr, cqr->status, cqr->retries);
+		cqr->status = DASD_CQR_FAILED;
+		return -EIO;
+	}
+	cqr->startclk = get_clock();
+	cqr->starttime = jiffies;
+	cqr->retries--;
+	rc = ccw_device_start(device->cdev, cqr->cpaddr, (long) cqr,
+			      cqr->lpm, 0);
+	switch (rc) {
+	case 0:
+		cqr->status = DASD_CQR_IN_IO;
+		DBF_DEV_EVENT(DBF_DEBUG, device,
+			      "start_IO: request %p started successful",
+			      cqr);
+		break;
+	case -EBUSY:
+		DBF_DEV_EVENT(DBF_ERR, device, "%s",
+			      "start_IO: device busy, retry later");
+		break;
+	case -ETIMEDOUT:
+		DBF_DEV_EVENT(DBF_ERR, device, "%s",
+			      "start_IO: request timeout, retry later");
+		break;
+	case -EACCES:
+		/* -EACCES indicates that the request used only a
+		 * subset of the available pathes and all these
+		 * pathes are gone.
+		 * Do a retry with all available pathes.
+		 */
+		cqr->lpm = LPM_ANYPATH;
+		DBF_DEV_EVENT(DBF_ERR, device, "%s",
+			      "start_IO: selected pathes gone,"
+			      " retry on all pathes");
+		break;
+	case -ENODEV:
+	case -EIO:
+		DBF_DEV_EVENT(DBF_ERR, device, "%s",
+			      "start_IO: device gone, retry");
+		break;
+	default:
+		DEV_MESSAGE(KERN_ERR, device,
+			    "line %d unknown RC=%d, please report"
+			    " to linux390@de.ibm.com", __LINE__, rc);
+		BUG();
+		break;
+	}
+	return rc;
+}
+
+/*
+ * Timeout function for dasd devices. This is used for different purposes
+ *  1) missing interrupt handler for normal operation
+ *  2) delayed start of request where start_IO failed with -EBUSY
+ *  3) timeout for missing state change interrupts
+ * The head of the ccw queue will have status DASD_CQR_IN_IO for 1),
+ * DASD_CQR_QUEUED for 2) and 3).
+ */
+static void
+dasd_timeout_device(unsigned long ptr)
+{
+	unsigned long flags;
+	struct dasd_device *device;
+
+	device = (struct dasd_device *) ptr;
+	spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags);
+	/* re-activate request queue */
+        device->stopped &= ~DASD_STOPPED_PENDING;
+	spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags);
+	dasd_schedule_bh(device);
+}
+
+/*
+ * Setup timeout for a device in jiffies.
+ */
+void
+dasd_set_timer(struct dasd_device *device, int expires)
+{
+	if (expires == 0) {
+		if (timer_pending(&device->timer))
+			del_timer(&device->timer);
+		return;
+	}
+	if (timer_pending(&device->timer)) {
+		if (mod_timer(&device->timer, jiffies + expires))
+			return;
+	}
+	device->timer.function = dasd_timeout_device;
+	device->timer.data = (unsigned long) device;
+	device->timer.expires = jiffies + expires;
+	add_timer(&device->timer);
+}
+
+/*
+ * Clear timeout for a device.
+ */
+void
+dasd_clear_timer(struct dasd_device *device)
+{
+	if (timer_pending(&device->timer))
+		del_timer(&device->timer);
+}
+
+static void
+dasd_handle_killed_request(struct ccw_device *cdev, unsigned long intparm)
+{
+	struct dasd_ccw_req *cqr;
+	struct dasd_device *device;
+
+	cqr = (struct dasd_ccw_req *) intparm;
+	if (cqr->status != DASD_CQR_IN_IO) {
+		MESSAGE(KERN_DEBUG,
+			"invalid status in handle_killed_request: "
+			"bus_id %s, status %02x",
+			cdev->dev.bus_id, cqr->status);
+		return;
+	}
+
+	device = (struct dasd_device *) cqr->device;
+	if (device == NULL ||
+	    device != dasd_device_from_cdev(cdev) ||
+	    strncmp(device->discipline->ebcname, (char *) &cqr->magic, 4)) {
+		MESSAGE(KERN_DEBUG, "invalid device in request: bus_id %s",
+			cdev->dev.bus_id);
+		return;
+	}
+
+	/* Schedule request to be retried. */
+	cqr->status = DASD_CQR_QUEUED;
+
+	dasd_clear_timer(device);
+	dasd_schedule_bh(device);
+	dasd_put_device(device);
+}
+
+static void
+dasd_handle_state_change_pending(struct dasd_device *device)
+{
+	struct dasd_ccw_req *cqr;
+	struct list_head *l, *n;
+
+	device->stopped &= ~DASD_STOPPED_PENDING;
+
+        /* restart all 'running' IO on queue */
+	list_for_each_safe(l, n, &device->ccw_queue) {
+		cqr = list_entry(l, struct dasd_ccw_req, list);
+                if (cqr->status == DASD_CQR_IN_IO) {
+                        cqr->status = DASD_CQR_QUEUED;
+		}
+        }
+	dasd_clear_timer(device);
+	dasd_schedule_bh(device);
+}
+
+/*
+ * Interrupt handler for "normal" ssch-io based dasd devices.
+ */
+void
+dasd_int_handler(struct ccw_device *cdev, unsigned long intparm,
+		 struct irb *irb)
+{
+	struct dasd_ccw_req *cqr, *next;
+	struct dasd_device *device;
+	unsigned long long now;
+	int expires;
+	dasd_era_t era;
+	char mask;
+
+	if (IS_ERR(irb)) {
+		switch (PTR_ERR(irb)) {
+		case -EIO:
+			dasd_handle_killed_request(cdev, intparm);
+			break;
+		case -ETIMEDOUT:
+			printk(KERN_WARNING"%s(%s): request timed out\n",
+			       __FUNCTION__, cdev->dev.bus_id);
+			//FIXME - dasd uses own timeout interface...
+			break;
+		default:
+			printk(KERN_WARNING"%s(%s): unknown error %ld\n",
+			       __FUNCTION__, cdev->dev.bus_id, PTR_ERR(irb));
+		}
+		return;
+	}
+
+	now = get_clock();
+
+	DBF_EVENT(DBF_ERR, "Interrupt: bus_id %s CS/DS %04x ip %08x",
+		  cdev->dev.bus_id, ((irb->scsw.cstat<<8)|irb->scsw.dstat),
+		  (unsigned int) intparm);
+
+	/* first of all check for state change pending interrupt */
+	mask = DEV_STAT_ATTENTION | DEV_STAT_DEV_END | DEV_STAT_UNIT_EXCEP;
+	if ((irb->scsw.dstat & mask) == mask) {
+		device = dasd_device_from_cdev(cdev);
+		if (!IS_ERR(device)) {
+			dasd_handle_state_change_pending(device);
+			dasd_put_device(device);
+		}
+		return;
+	}
+
+	cqr = (struct dasd_ccw_req *) intparm;
+
+	/* check for unsolicited interrupts */
+	if (cqr == NULL) {
+		MESSAGE(KERN_DEBUG,
+			"unsolicited interrupt received: bus_id %s",
+			cdev->dev.bus_id);
+		return;
+	}
+
+	device = (struct dasd_device *) cqr->device;
+	if (device == NULL ||
+	    strncmp(device->discipline->ebcname, (char *) &cqr->magic, 4)) {
+		MESSAGE(KERN_DEBUG, "invalid device in request: bus_id %s",
+			cdev->dev.bus_id);
+		return;
+	}
+
+	/* Check for clear pending */
+	if (cqr->status == DASD_CQR_CLEAR &&
+	    irb->scsw.fctl & SCSW_FCTL_CLEAR_FUNC) {
+		cqr->status = DASD_CQR_QUEUED;
+		dasd_clear_timer(device);
+		dasd_schedule_bh(device);
+		return;
+	}
+
+ 	/* check status - the request might have been killed by dyn detach */
+	if (cqr->status != DASD_CQR_IN_IO) {
+		MESSAGE(KERN_DEBUG,
+			"invalid status: bus_id %s, status %02x",
+			cdev->dev.bus_id, cqr->status);
+		return;
+	}
+	DBF_DEV_EVENT(DBF_DEBUG, device, "Int: CS/DS 0x%04x for cqr %p",
+		      ((irb->scsw.cstat << 8) | irb->scsw.dstat), cqr);
+
+ 	/* Find out the appropriate era_action. */
+	if (irb->scsw.fctl & SCSW_FCTL_HALT_FUNC) 
+		era = dasd_era_fatal;
+	else if (irb->scsw.dstat == (DEV_STAT_CHN_END | DEV_STAT_DEV_END) &&
+		 irb->scsw.cstat == 0 &&
+		 !irb->esw.esw0.erw.cons)
+		era = dasd_era_none;
+	else if (!test_bit(DASD_CQR_FLAGS_USE_ERP, &cqr->flags))
+ 	        era = dasd_era_fatal; /* don't recover this request */
+	else if (irb->esw.esw0.erw.cons)
+		era = device->discipline->examine_error(cqr, irb);
+	else 
+		era = dasd_era_recover;
+
+	DBF_DEV_EVENT(DBF_DEBUG, device, "era_code %d", era);
+	expires = 0;
+	if (era == dasd_era_none) {
+		cqr->status = DASD_CQR_DONE;
+		cqr->stopclk = now;
+		/* Start first request on queue if possible -> fast_io. */
+		if (cqr->list.next != &device->ccw_queue) {
+			next = list_entry(cqr->list.next,
+					  struct dasd_ccw_req, list);
+			if ((next->status == DASD_CQR_QUEUED) &&
+			    (!device->stopped)) {
+				if (device->discipline->start_IO(next) == 0)
+					expires = next->expires;
+				else
+					DEV_MESSAGE(KERN_DEBUG, device, "%s",
+						    "Interrupt fastpath "
+						    "failed!");
+			}
+		}
+	} else {		/* error */
+		memcpy(&cqr->irb, irb, sizeof (struct irb));
+#ifdef ERP_DEBUG
+		/* dump sense data */
+		dasd_log_sense(cqr, irb);
+#endif
+		switch (era) {
+		case dasd_era_fatal:
+			cqr->status = DASD_CQR_FAILED;
+			cqr->stopclk = now;
+			break;
+		case dasd_era_recover:
+			cqr->status = DASD_CQR_ERROR;
+			break;
+		default:
+			BUG();
+		}
+	}
+	if (expires != 0)
+		dasd_set_timer(device, expires);
+	else
+		dasd_clear_timer(device);
+	dasd_schedule_bh(device);
+}
+
+/*
+ * posts the buffer_cache about a finalized request
+ */
+static inline void
+dasd_end_request(struct request *req, int uptodate)
+{
+	if (end_that_request_first(req, uptodate, req->hard_nr_sectors))
+		BUG();
+	add_disk_randomness(req->rq_disk);
+	end_that_request_last(req);
+}
+
+/*
+ * Process finished error recovery ccw.
+ */
+static inline void
+__dasd_process_erp(struct dasd_device *device, struct dasd_ccw_req *cqr)
+{
+	dasd_erp_fn_t erp_fn;
+
+	if (cqr->status == DASD_CQR_DONE)
+		DBF_DEV_EVENT(DBF_NOTICE, device, "%s", "ERP successful");
+	else
+		DEV_MESSAGE(KERN_ERR, device, "%s", "ERP unsuccessful");
+	erp_fn = device->discipline->erp_postaction(cqr);
+	erp_fn(cqr);
+}
+
+/*
+ * Process ccw request queue.
+ */
+static inline void
+__dasd_process_ccw_queue(struct dasd_device * device,
+			 struct list_head *final_queue)
+{
+	struct list_head *l, *n;
+	struct dasd_ccw_req *cqr;
+	dasd_erp_fn_t erp_fn;
+
+restart:
+	/* Process request with final status. */
+	list_for_each_safe(l, n, &device->ccw_queue) {
+		cqr = list_entry(l, struct dasd_ccw_req, list);
+		/* Stop list processing at the first non-final request. */
+		if (cqr->status != DASD_CQR_DONE &&
+		    cqr->status != DASD_CQR_FAILED &&
+		    cqr->status != DASD_CQR_ERROR)
+			break;
+		/*  Process requests with DASD_CQR_ERROR */
+		if (cqr->status == DASD_CQR_ERROR) {
+			if (cqr->irb.scsw.fctl & SCSW_FCTL_HALT_FUNC) {
+				cqr->status = DASD_CQR_FAILED;
+				cqr->stopclk = get_clock();
+			} else {
+				if (cqr->irb.esw.esw0.erw.cons) {
+					erp_fn = device->discipline->
+						erp_action(cqr);
+					erp_fn(cqr);
+				} else
+					dasd_default_erp_action(cqr);
+			}
+			goto restart;
+		}
+		/* Process finished ERP request. */
+		if (cqr->refers) {
+			__dasd_process_erp(device, cqr);
+			goto restart;
+		}
+
+		/* Rechain finished requests to final queue */
+		cqr->endclk = get_clock();
+		list_move_tail(&cqr->list, final_queue);
+	}
+}
+
+static void
+dasd_end_request_cb(struct dasd_ccw_req * cqr, void *data)
+{
+	struct request *req;
+	struct dasd_device *device;
+	int status;
+
+	req = (struct request *) data;
+	device = cqr->device;
+	dasd_profile_end(device, cqr, req);
+	status = cqr->device->discipline->free_cp(cqr,req);
+	spin_lock_irq(&device->request_queue_lock);
+	dasd_end_request(req, status);
+	spin_unlock_irq(&device->request_queue_lock);
+}
+
+
+/*
+ * Fetch requests from the block device queue.
+ */
+static inline void
+__dasd_process_blk_queue(struct dasd_device * device)
+{
+	request_queue_t *queue;
+	struct request *req;
+	struct dasd_ccw_req *cqr;
+	int nr_queued;
+
+	queue = device->request_queue;
+	/* No queue ? Then there is nothing to do. */
+	if (queue == NULL)
+		return;
+
+	/*
+	 * We requeue request from the block device queue to the ccw
+	 * queue only in two states. In state DASD_STATE_READY the
+	 * partition detection is done and we need to requeue requests
+	 * for that. State DASD_STATE_ONLINE is normal block device
+	 * operation.
+	 */
+	if (device->state != DASD_STATE_READY &&
+	    device->state != DASD_STATE_ONLINE)
+		return;
+	nr_queued = 0;
+	/* Now we try to fetch requests from the request queue */
+	list_for_each_entry(cqr, &device->ccw_queue, list)
+		if (cqr->status == DASD_CQR_QUEUED)
+			nr_queued++;
+	while (!blk_queue_plugged(queue) &&
+	       elv_next_request(queue) &&
+		nr_queued < DASD_CHANQ_MAX_SIZE) {
+		req = elv_next_request(queue);
+		if (test_bit(DASD_FLAG_RO, &device->flags) &&
+		    rq_data_dir(req) == WRITE) {
+			DBF_DEV_EVENT(DBF_ERR, device,
+				      "Rejecting write request %p",
+				      req);
+			blkdev_dequeue_request(req);
+			dasd_end_request(req, 0);
+			continue;
+		}
+		if (device->stopped & DASD_STOPPED_DC_EIO) {
+			blkdev_dequeue_request(req);
+			dasd_end_request(req, 0);
+			continue;
+		}
+		cqr = device->discipline->build_cp(device, req);
+		if (IS_ERR(cqr)) {
+			if (PTR_ERR(cqr) == -ENOMEM)
+				break;	/* terminate request queue loop */
+			DBF_DEV_EVENT(DBF_ERR, device,
+				      "CCW creation failed (rc=%ld) "
+				      "on request %p",
+				      PTR_ERR(cqr), req);
+			blkdev_dequeue_request(req);
+			dasd_end_request(req, 0);
+			continue;
+		}
+		cqr->callback = dasd_end_request_cb;
+		cqr->callback_data = (void *) req;
+		cqr->status = DASD_CQR_QUEUED;
+		blkdev_dequeue_request(req);
+		list_add_tail(&cqr->list, &device->ccw_queue);
+		dasd_profile_start(device, cqr, req);
+		nr_queued++;
+	}
+}
+
+/*
+ * Take a look at the first request on the ccw queue and check
+ * if it reached its expire time. If so, terminate the IO.
+ */
+static inline void
+__dasd_check_expire(struct dasd_device * device)
+{
+	struct dasd_ccw_req *cqr;
+
+	if (list_empty(&device->ccw_queue))
+		return;
+	cqr = list_entry(device->ccw_queue.next, struct dasd_ccw_req, list);
+	if (cqr->status == DASD_CQR_IN_IO && cqr->expires != 0) {
+		if (time_after_eq(jiffies, cqr->expires + cqr->starttime)) {
+			if (device->discipline->term_IO(cqr) != 0)
+				/* Hmpf, try again in 1/10 sec */
+				dasd_set_timer(device, 10);
+		}
+	}
+}
+
+/*
+ * Take a look at the first request on the ccw queue and check
+ * if it needs to be started.
+ */
+static inline void
+__dasd_start_head(struct dasd_device * device)
+{
+	struct dasd_ccw_req *cqr;
+	int rc;
+
+	if (list_empty(&device->ccw_queue))
+		return;
+	cqr = list_entry(device->ccw_queue.next, struct dasd_ccw_req, list);
+	if ((cqr->status == DASD_CQR_QUEUED) &&
+	    (!device->stopped)) {
+		/* try to start the first I/O that can be started */
+		rc = device->discipline->start_IO(cqr);
+		if (rc == 0)
+			dasd_set_timer(device, cqr->expires);
+		else if (rc == -EACCES) {
+			dasd_schedule_bh(device);
+		} else
+			/* Hmpf, try again in 1/2 sec */
+			dasd_set_timer(device, 50);
+	}
+}
+
+/*
+ * Remove requests from the ccw queue. 
+ */
+static void
+dasd_flush_ccw_queue(struct dasd_device * device, int all)
+{
+	struct list_head flush_queue;
+	struct list_head *l, *n;
+	struct dasd_ccw_req *cqr;
+
+	INIT_LIST_HEAD(&flush_queue);
+	spin_lock_irq(get_ccwdev_lock(device->cdev));
+	list_for_each_safe(l, n, &device->ccw_queue) {
+		cqr = list_entry(l, struct dasd_ccw_req, list);
+		/* Flush all request or only block device requests? */
+		if (all == 0 && cqr->callback == dasd_end_request_cb)
+			continue;
+		if (cqr->status == DASD_CQR_IN_IO)
+			device->discipline->term_IO(cqr);
+		if (cqr->status != DASD_CQR_DONE ||
+		    cqr->status != DASD_CQR_FAILED) {
+			cqr->status = DASD_CQR_FAILED;
+			cqr->stopclk = get_clock();
+		}
+		/* Process finished ERP request. */
+		if (cqr->refers) {
+			__dasd_process_erp(device, cqr);
+			continue;
+		}
+		/* Rechain request on device request queue */
+		cqr->endclk = get_clock();
+		list_move_tail(&cqr->list, &flush_queue);
+	}
+	spin_unlock_irq(get_ccwdev_lock(device->cdev));
+	/* Now call the callback function of flushed requests */
+	list_for_each_safe(l, n, &flush_queue) {
+		cqr = list_entry(l, struct dasd_ccw_req, list);
+		if (cqr->callback != NULL)
+			(cqr->callback)(cqr, cqr->callback_data);
+	}
+}
+
+/*
+ * Acquire the device lock and process queues for the device.
+ */
+static void
+dasd_tasklet(struct dasd_device * device)
+{
+	struct list_head final_queue;
+	struct list_head *l, *n;
+	struct dasd_ccw_req *cqr;
+
+	atomic_set (&device->tasklet_scheduled, 0);
+	INIT_LIST_HEAD(&final_queue);
+	spin_lock_irq(get_ccwdev_lock(device->cdev));
+	/* Check expire time of first request on the ccw queue. */
+	__dasd_check_expire(device);
+	/* Finish off requests on ccw queue */
+	__dasd_process_ccw_queue(device, &final_queue);
+	spin_unlock_irq(get_ccwdev_lock(device->cdev));
+	/* Now call the callback function of requests with final status */
+	list_for_each_safe(l, n, &final_queue) {
+		cqr = list_entry(l, struct dasd_ccw_req, list);
+		list_del(&cqr->list);
+		if (cqr->callback != NULL)
+			(cqr->callback)(cqr, cqr->callback_data);
+	}
+	spin_lock_irq(&device->request_queue_lock);
+	spin_lock(get_ccwdev_lock(device->cdev));
+	/* Get new request from the block device request queue */
+	__dasd_process_blk_queue(device);
+	/* Now check if the head of the ccw queue needs to be started. */
+	__dasd_start_head(device);
+	spin_unlock(get_ccwdev_lock(device->cdev));
+	spin_unlock_irq(&device->request_queue_lock);
+	dasd_put_device(device);
+}
+
+/*
+ * Schedules a call to dasd_tasklet over the device tasklet.
+ */
+void
+dasd_schedule_bh(struct dasd_device * device)
+{
+	/* Protect against rescheduling. */
+	if (atomic_compare_and_swap (0, 1, &device->tasklet_scheduled))
+		return;
+	dasd_get_device(device);
+	tasklet_hi_schedule(&device->tasklet);
+}
+
+/*
+ * Queue a request to the head of the ccw_queue. Start the I/O if
+ * possible.
+ */
+void
+dasd_add_request_head(struct dasd_ccw_req *req)
+{
+	struct dasd_device *device;
+	unsigned long flags;
+
+	device = req->device;
+	spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags);
+	req->status = DASD_CQR_QUEUED;
+	req->device = device;
+	list_add(&req->list, &device->ccw_queue);
+	/* let the bh start the request to keep them in order */
+	dasd_schedule_bh(device);
+	spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags);
+}
+
+/*
+ * Queue a request to the tail of the ccw_queue. Start the I/O if
+ * possible.
+ */
+void
+dasd_add_request_tail(struct dasd_ccw_req *req)
+{
+	struct dasd_device *device;
+	unsigned long flags;
+
+	device = req->device;
+	spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags);
+	req->status = DASD_CQR_QUEUED;
+	req->device = device;
+	list_add_tail(&req->list, &device->ccw_queue);
+	/* let the bh start the request to keep them in order */
+	dasd_schedule_bh(device);
+	spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags);
+}
+
+/*
+ * Wakeup callback.
+ */
+static void
+dasd_wakeup_cb(struct dasd_ccw_req *cqr, void *data)
+{
+	wake_up((wait_queue_head_t *) data);
+}
+
+static inline int
+_wait_for_wakeup(struct dasd_ccw_req *cqr)
+{
+	struct dasd_device *device;
+	int rc;
+
+	device = cqr->device;
+	spin_lock_irq(get_ccwdev_lock(device->cdev));
+	rc = cqr->status == DASD_CQR_DONE || cqr->status == DASD_CQR_FAILED;
+	spin_unlock_irq(get_ccwdev_lock(device->cdev));
+	return rc;
+}
+
+/*
+ * Attempts to start a special ccw queue and waits for its completion.
+ */
+int
+dasd_sleep_on(struct dasd_ccw_req * cqr)
+{
+	wait_queue_head_t wait_q;
+	struct dasd_device *device;
+	int rc;
+	
+	device = cqr->device;
+	spin_lock_irq(get_ccwdev_lock(device->cdev));
+	
+	init_waitqueue_head (&wait_q);
+	cqr->callback = dasd_wakeup_cb;
+	cqr->callback_data = (void *) &wait_q;
+	cqr->status = DASD_CQR_QUEUED;
+	list_add_tail(&cqr->list, &device->ccw_queue);
+	
+	/* let the bh start the request to keep them in order */
+	dasd_schedule_bh(device);
+	
+	spin_unlock_irq(get_ccwdev_lock(device->cdev));
+
+	wait_event(wait_q, _wait_for_wakeup(cqr));
+	
+	/* Request status is either done or failed. */
+	rc = (cqr->status == DASD_CQR_FAILED) ? -EIO : 0;
+	return rc;
+}
+
+/*
+ * Attempts to start a special ccw queue and wait interruptible
+ * for its completion.
+ */
+int
+dasd_sleep_on_interruptible(struct dasd_ccw_req * cqr)
+{
+	wait_queue_head_t wait_q;
+	struct dasd_device *device;
+	int rc, finished;
+
+	device = cqr->device;
+	spin_lock_irq(get_ccwdev_lock(device->cdev));
+
+	init_waitqueue_head (&wait_q);
+	cqr->callback = dasd_wakeup_cb;
+	cqr->callback_data = (void *) &wait_q;
+	cqr->status = DASD_CQR_QUEUED;
+	list_add_tail(&cqr->list, &device->ccw_queue);
+
+	/* let the bh start the request to keep them in order */
+	dasd_schedule_bh(device);
+	spin_unlock_irq(get_ccwdev_lock(device->cdev));
+
+	finished = 0;
+	while (!finished) {
+		rc = wait_event_interruptible(wait_q, _wait_for_wakeup(cqr));
+		if (rc != -ERESTARTSYS) {
+			/* Request status is either done or failed. */
+			rc = (cqr->status == DASD_CQR_FAILED) ? -EIO : 0;
+			break;
+		}
+		spin_lock_irq(get_ccwdev_lock(device->cdev));
+		if (cqr->status == DASD_CQR_IN_IO &&
+		    device->discipline->term_IO(cqr) == 0) {
+			list_del(&cqr->list);
+			finished = 1;
+		}
+		spin_unlock_irq(get_ccwdev_lock(device->cdev));
+	}
+	return rc;
+}
+
+/*
+ * Whoa nelly now it gets really hairy. For some functions (e.g. steal lock
+ * for eckd devices) the currently running request has to be terminated
+ * and be put back to status queued, before the special request is added
+ * to the head of the queue. Then the special request is waited on normally.
+ */
+static inline int
+_dasd_term_running_cqr(struct dasd_device *device)
+{
+	struct dasd_ccw_req *cqr;
+	int rc;
+
+	if (list_empty(&device->ccw_queue))
+		return 0;
+	cqr = list_entry(device->ccw_queue.next, struct dasd_ccw_req, list);
+	rc = device->discipline->term_IO(cqr);
+	if (rc == 0) {
+		/* termination successful */
+		cqr->status = DASD_CQR_QUEUED;
+		cqr->startclk = cqr->stopclk = 0;
+		cqr->starttime = 0;
+	}
+	return rc;
+}
+
+int
+dasd_sleep_on_immediatly(struct dasd_ccw_req * cqr)
+{
+	wait_queue_head_t wait_q;
+	struct dasd_device *device;
+	int rc;
+	
+	device = cqr->device;
+	spin_lock_irq(get_ccwdev_lock(device->cdev));
+	rc = _dasd_term_running_cqr(device);
+	if (rc) {
+		spin_unlock_irq(get_ccwdev_lock(device->cdev));
+		return rc;
+	}
+	
+	init_waitqueue_head (&wait_q);
+	cqr->callback = dasd_wakeup_cb;
+	cqr->callback_data = (void *) &wait_q;
+	cqr->status = DASD_CQR_QUEUED;
+	list_add(&cqr->list, &device->ccw_queue);
+	
+	/* let the bh start the request to keep them in order */
+	dasd_schedule_bh(device);
+	
+	spin_unlock_irq(get_ccwdev_lock(device->cdev));
+
+	wait_event(wait_q, _wait_for_wakeup(cqr));
+	
+	/* Request status is either done or failed. */
+	rc = (cqr->status == DASD_CQR_FAILED) ? -EIO : 0;
+	return rc;
+}
+
+/*
+ * Cancels a request that was started with dasd_sleep_on_req.
+ * This is useful to timeout requests. The request will be
+ * terminated if it is currently in i/o.
+ * Returns 1 if the request has been terminated.
+ */
+int
+dasd_cancel_req(struct dasd_ccw_req *cqr)
+{
+	struct dasd_device *device = cqr->device;
+	unsigned long flags;
+	int rc;
+
+	rc = 0;
+	spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags);
+	switch (cqr->status) {
+	case DASD_CQR_QUEUED:
+		/* request was not started - just set to failed */
+		cqr->status = DASD_CQR_FAILED;
+		break;
+	case DASD_CQR_IN_IO:
+		/* request in IO - terminate IO and release again */
+		if (device->discipline->term_IO(cqr) != 0)
+			/* what to do if unable to terminate ??????
+			   e.g. not _IN_IO */
+			cqr->status = DASD_CQR_FAILED;
+		cqr->stopclk = get_clock();
+		rc = 1;
+		break;
+	case DASD_CQR_DONE:
+	case DASD_CQR_FAILED:
+		/* already finished - do nothing */
+		break;
+	default:
+		DEV_MESSAGE(KERN_ALERT, device,
+			    "invalid status %02x in request",
+			    cqr->status);
+		BUG();
+
+	}
+	spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags);
+	dasd_schedule_bh(device);
+	return rc;
+}
+
+/*
+ * SECTION: Block device operations (request queue, partitions, open, release).
+ */
+
+/*
+ * Dasd request queue function. Called from ll_rw_blk.c
+ */
+static void
+do_dasd_request(request_queue_t * queue)
+{
+	struct dasd_device *device;
+
+	device = (struct dasd_device *) queue->queuedata;
+	spin_lock(get_ccwdev_lock(device->cdev));
+	/* Get new request from the block device request queue */
+	__dasd_process_blk_queue(device);
+	/* Now check if the head of the ccw queue needs to be started. */
+	__dasd_start_head(device);
+	spin_unlock(get_ccwdev_lock(device->cdev));
+}
+
+/*
+ * Allocate and initialize request queue and default I/O scheduler.
+ */
+static int
+dasd_alloc_queue(struct dasd_device * device)
+{
+	int rc;
+
+	device->request_queue = blk_init_queue(do_dasd_request,
+					       &device->request_queue_lock);
+	if (device->request_queue == NULL)
+		return -ENOMEM;
+
+	device->request_queue->queuedata = device;
+
+	elevator_exit(device->request_queue->elevator);
+	rc = elevator_init(device->request_queue, "deadline");
+	if (rc) {
+		blk_cleanup_queue(device->request_queue);
+		return rc;
+	}
+	return 0;
+}
+
+/*
+ * Allocate and initialize request queue.
+ */
+static void
+dasd_setup_queue(struct dasd_device * device)
+{
+	int max;
+
+	blk_queue_hardsect_size(device->request_queue, device->bp_block);
+	max = device->discipline->max_blocks << device->s2b_shift;
+	blk_queue_max_sectors(device->request_queue, max);
+	blk_queue_max_phys_segments(device->request_queue, -1L);
+	blk_queue_max_hw_segments(device->request_queue, -1L);
+	blk_queue_max_segment_size(device->request_queue, -1L);
+	blk_queue_segment_boundary(device->request_queue, -1L);
+}
+
+/*
+ * Deactivate and free request queue.
+ */
+static void
+dasd_free_queue(struct dasd_device * device)
+{
+	if (device->request_queue) {
+		blk_cleanup_queue(device->request_queue);
+		device->request_queue = NULL;
+	}
+}
+
+/*
+ * Flush request on the request queue.
+ */
+static void
+dasd_flush_request_queue(struct dasd_device * device)
+{
+	struct request *req;
+
+	if (!device->request_queue)
+		return;
+	
+	spin_lock_irq(&device->request_queue_lock);
+	while (!list_empty(&device->request_queue->queue_head)) {
+		req = elv_next_request(device->request_queue);
+		if (req == NULL)
+			break;
+		dasd_end_request(req, 0);
+		blkdev_dequeue_request(req);
+	}
+	spin_unlock_irq(&device->request_queue_lock);
+}
+
+static int
+dasd_open(struct inode *inp, struct file *filp)
+{
+	struct gendisk *disk = inp->i_bdev->bd_disk;
+	struct dasd_device *device = disk->private_data;
+	int rc;
+
+        atomic_inc(&device->open_count);
+	if (test_bit(DASD_FLAG_OFFLINE, &device->flags)) {
+		rc = -ENODEV;
+		goto unlock;
+	}
+
+	if (!try_module_get(device->discipline->owner)) {
+		rc = -EINVAL;
+		goto unlock;
+	}
+
+	if (dasd_probeonly) {
+		DEV_MESSAGE(KERN_INFO, device, "%s",
+			    "No access to device due to probeonly mode");
+		rc = -EPERM;
+		goto out;
+	}
+
+	if (device->state < DASD_STATE_BASIC) {
+		DBF_DEV_EVENT(DBF_ERR, device, " %s",
+			      " Cannot open unrecognized device");
+		rc = -ENODEV;
+		goto out;
+	}
+
+	return 0;
+
+out:
+	module_put(device->discipline->owner);
+unlock:
+	atomic_dec(&device->open_count);
+	return rc;
+}
+
+static int
+dasd_release(struct inode *inp, struct file *filp)
+{
+	struct gendisk *disk = inp->i_bdev->bd_disk;
+	struct dasd_device *device = disk->private_data;
+
+	atomic_dec(&device->open_count);
+	module_put(device->discipline->owner);
+	return 0;
+}
+
+struct block_device_operations
+dasd_device_operations = {
+	.owner		= THIS_MODULE,
+	.open		= dasd_open,
+	.release	= dasd_release,
+	.ioctl		= dasd_ioctl,
+};
+
+
+static void
+dasd_exit(void)
+{
+#ifdef CONFIG_PROC_FS
+	dasd_proc_exit();
+#endif
+	dasd_ioctl_exit();
+	dasd_gendisk_exit();
+	dasd_devmap_exit();
+	devfs_remove("dasd");
+	if (dasd_debug_area != NULL) {
+		debug_unregister(dasd_debug_area);
+		dasd_debug_area = NULL;
+	}
+}
+
+/*
+ * SECTION: common functions for ccw_driver use
+ */
+
+/* initial attempt at a probe function. this can be simplified once
+ * the other detection code is gone */
+int
+dasd_generic_probe (struct ccw_device *cdev,
+		    struct dasd_discipline *discipline)
+{
+	int ret;
+
+	ret = dasd_add_sysfs_files(cdev);
+	if (ret) {
+		printk(KERN_WARNING
+		       "dasd_generic_probe: could not add sysfs entries "
+		       "for %s\n", cdev->dev.bus_id);
+	}
+
+	cdev->handler = &dasd_int_handler;
+
+	return ret;
+}
+
+/* this will one day be called from a global not_oper handler.
+ * It is also used by driver_unregister during module unload */
+void
+dasd_generic_remove (struct ccw_device *cdev)
+{
+	struct dasd_device *device;
+
+	dasd_remove_sysfs_files(cdev);
+	device = dasd_device_from_cdev(cdev);
+	if (IS_ERR(device))
+		return;
+	if (test_and_set_bit(DASD_FLAG_OFFLINE, &device->flags)) {
+		/* Already doing offline processing */
+		dasd_put_device(device);
+		return;
+	}
+	/*
+	 * This device is removed unconditionally. Set offline
+	 * flag to prevent dasd_open from opening it while it is
+	 * no quite down yet.
+	 */
+	dasd_set_target_state(device, DASD_STATE_NEW);
+	/* dasd_delete_device destroys the device reference. */
+	dasd_delete_device(device);
+}
+
+/* activate a device. This is called from dasd_{eckd,fba}_probe() when either
+ * the device is detected for the first time and is supposed to be used
+ * or the user has started activation through sysfs */
+int
+dasd_generic_set_online (struct ccw_device *cdev,
+			 struct dasd_discipline *discipline)
+
+{
+	struct dasd_device *device;
+	int rc;
+
+	device = dasd_create_device(cdev);
+	if (IS_ERR(device))
+		return PTR_ERR(device);
+
+	if (test_bit(DASD_FLAG_USE_DIAG, &device->flags)) {
+	  	if (!dasd_diag_discipline_pointer) {
+		        printk (KERN_WARNING
+				"dasd_generic couldn't online device %s "
+				"- discipline DIAG not available\n",
+				cdev->dev.bus_id);
+			dasd_delete_device(device);
+			return -ENODEV;
+		}
+		discipline = dasd_diag_discipline_pointer;
+	}
+	device->discipline = discipline;
+
+	rc = discipline->check_device(device);
+	if (rc) {
+		printk (KERN_WARNING
+			"dasd_generic couldn't online device %s "
+			"with discipline %s rc=%i\n",
+			cdev->dev.bus_id, discipline->name, rc);
+		dasd_delete_device(device);
+		return rc;
+	}
+
+	dasd_set_target_state(device, DASD_STATE_ONLINE);
+	if (device->state <= DASD_STATE_KNOWN) {
+		printk (KERN_WARNING
+			"dasd_generic discipline not found for %s\n",
+			cdev->dev.bus_id);
+		rc = -ENODEV;
+		dasd_set_target_state(device, DASD_STATE_NEW);
+		dasd_delete_device(device);
+	} else
+		pr_debug("dasd_generic device %s found\n",
+				cdev->dev.bus_id);
+
+	/* FIXME: we have to wait for the root device but we don't want
+	 * to wait for each single device but for all at once. */
+	wait_event(dasd_init_waitq, _wait_for_device(device));
+
+	dasd_put_device(device);
+
+	return rc;
+}
+
+int
+dasd_generic_set_offline (struct ccw_device *cdev)
+{
+	struct dasd_device *device;
+	int max_count;
+
+	device = dasd_device_from_cdev(cdev);
+	if (IS_ERR(device))
+		return PTR_ERR(device);
+	if (test_and_set_bit(DASD_FLAG_OFFLINE, &device->flags)) {
+		/* Already doing offline processing */
+		dasd_put_device(device);
+		return 0;
+	}
+	/*
+	 * We must make sure that this device is currently not in use.
+	 * The open_count is increased for every opener, that includes
+	 * the blkdev_get in dasd_scan_partitions. We are only interested
+	 * in the other openers.
+	 */
+	max_count = device->bdev ? 0 : -1;
+	if (atomic_read(&device->open_count) > max_count) {
+		printk (KERN_WARNING "Can't offline dasd device with open"
+			" count = %i.\n",
+			atomic_read(&device->open_count));
+		clear_bit(DASD_FLAG_OFFLINE, &device->flags);
+		dasd_put_device(device);
+		return -EBUSY;
+	}
+	dasd_set_target_state(device, DASD_STATE_NEW);
+	/* dasd_delete_device destroys the device reference. */
+	dasd_delete_device(device);
+
+	return 0;
+}
+
+int
+dasd_generic_notify(struct ccw_device *cdev, int event)
+{
+	struct dasd_device *device;
+	struct dasd_ccw_req *cqr;
+	unsigned long flags;
+	int ret;
+
+	device = dasd_device_from_cdev(cdev);
+	if (IS_ERR(device))
+		return 0;
+	spin_lock_irqsave(get_ccwdev_lock(cdev), flags);
+	ret = 0;
+	switch (event) {
+	case CIO_GONE:
+	case CIO_NO_PATH:
+		if (device->state < DASD_STATE_BASIC)
+			break;
+		/* Device is active. We want to keep it. */
+		if (test_bit(DASD_FLAG_DSC_ERROR, &device->flags)) {
+			list_for_each_entry(cqr, &device->ccw_queue, list)
+				if (cqr->status == DASD_CQR_IN_IO)
+					cqr->status = DASD_CQR_FAILED;
+			device->stopped |= DASD_STOPPED_DC_EIO;
+			dasd_schedule_bh(device);
+		} else {
+			list_for_each_entry(cqr, &device->ccw_queue, list)
+				if (cqr->status == DASD_CQR_IN_IO) {
+					cqr->status = DASD_CQR_QUEUED;
+					cqr->retries++;
+				}
+			device->stopped |= DASD_STOPPED_DC_WAIT;
+			dasd_set_timer(device, 0);
+		}
+		ret = 1;
+		break;
+	case CIO_OPER:
+		/* FIXME: add a sanity check. */
+		device->stopped &= ~(DASD_STOPPED_DC_WAIT|DASD_STOPPED_DC_EIO);
+		dasd_schedule_bh(device);
+		ret = 1;
+		break;
+	}
+	spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags);
+	dasd_put_device(device);
+	return ret;
+}
+
+/*
+ * Automatically online either all dasd devices (dasd_autodetect) or
+ * all devices specified with dasd= parameters.
+ */
+void
+dasd_generic_auto_online (struct ccw_driver *dasd_discipline_driver)
+{
+	struct device_driver *drv;
+	struct device *d, *dev;
+	struct ccw_device *cdev;
+
+	drv = get_driver(&dasd_discipline_driver->driver);
+	down_read(&drv->bus->subsys.rwsem);
+	dev = NULL;
+	list_for_each_entry(d, &drv->devices, driver_list) {
+		dev = get_device(d);
+		if (!dev)
+			continue;
+		cdev = to_ccwdev(dev);
+		if (dasd_autodetect || dasd_busid_known(cdev->dev.bus_id) == 0)
+			ccw_device_set_online(cdev);
+		put_device(dev);
+	}
+	up_read(&drv->bus->subsys.rwsem);
+	put_driver(drv);
+}
+
+static int __init
+dasd_init(void)
+{
+	int rc;
+
+	init_waitqueue_head(&dasd_init_waitq);
+
+	/* register 'common' DASD debug area, used for all DBF_XXX calls */
+	dasd_debug_area = debug_register("dasd", 0, 2, 8 * sizeof (long));
+	if (dasd_debug_area == NULL) {
+		rc = -ENOMEM;
+		goto failed;
+	}
+	debug_register_view(dasd_debug_area, &debug_sprintf_view);
+	debug_set_level(dasd_debug_area, DBF_EMERG);
+
+	DBF_EVENT(DBF_EMERG, "%s", "debug area created");
+
+	dasd_diag_discipline_pointer = NULL;
+
+	rc = devfs_mk_dir("dasd");
+	if (rc)
+		goto failed;
+	rc = dasd_devmap_init();
+	if (rc)
+		goto failed;
+	rc = dasd_gendisk_init();
+	if (rc)
+		goto failed;
+	rc = dasd_parse();
+	if (rc)
+		goto failed;
+	rc = dasd_ioctl_init();
+	if (rc)
+		goto failed;
+#ifdef CONFIG_PROC_FS
+	rc = dasd_proc_init();
+	if (rc)
+		goto failed;
+#endif
+
+	return 0;
+failed:
+	MESSAGE(KERN_INFO, "%s", "initialization not performed due to errors");
+	dasd_exit();
+	return rc;
+}
+
+module_init(dasd_init);
+module_exit(dasd_exit);
+
+EXPORT_SYMBOL(dasd_debug_area);
+EXPORT_SYMBOL(dasd_diag_discipline_pointer);
+
+EXPORT_SYMBOL(dasd_add_request_head);
+EXPORT_SYMBOL(dasd_add_request_tail);
+EXPORT_SYMBOL(dasd_cancel_req);
+EXPORT_SYMBOL(dasd_clear_timer);
+EXPORT_SYMBOL(dasd_enable_device);
+EXPORT_SYMBOL(dasd_int_handler);
+EXPORT_SYMBOL(dasd_kfree_request);
+EXPORT_SYMBOL(dasd_kick_device);
+EXPORT_SYMBOL(dasd_kmalloc_request);
+EXPORT_SYMBOL(dasd_schedule_bh);
+EXPORT_SYMBOL(dasd_set_target_state);
+EXPORT_SYMBOL(dasd_set_timer);
+EXPORT_SYMBOL(dasd_sfree_request);
+EXPORT_SYMBOL(dasd_sleep_on);
+EXPORT_SYMBOL(dasd_sleep_on_immediatly);
+EXPORT_SYMBOL(dasd_sleep_on_interruptible);
+EXPORT_SYMBOL(dasd_smalloc_request);
+EXPORT_SYMBOL(dasd_start_IO);
+EXPORT_SYMBOL(dasd_term_IO);
+
+EXPORT_SYMBOL_GPL(dasd_generic_probe);
+EXPORT_SYMBOL_GPL(dasd_generic_remove);
+EXPORT_SYMBOL_GPL(dasd_generic_notify);
+EXPORT_SYMBOL_GPL(dasd_generic_set_online);
+EXPORT_SYMBOL_GPL(dasd_generic_set_offline);
+EXPORT_SYMBOL_GPL(dasd_generic_auto_online);
+
+/*
+ * Overrides for Emacs so that we follow Linus's tabbing style.
+ * Emacs will notice this stuff at the end of the file and automatically
+ * adjust the settings for this buffer only.  This must remain at the end
+ * of the file.
+ * ---------------------------------------------------------------------------
+ * Local variables:
+ * c-indent-level: 4
+ * c-brace-imaginary-offset: 0
+ * c-brace-offset: -4
+ * c-argdecl-indent: 4
+ * c-label-offset: -4
+ * c-continued-statement-offset: 4
+ * c-continued-brace-offset: 0
+ * indent-tabs-mode: 1
+ * tab-width: 8
+ * End:
+ */
diff --git a/drivers/s390/block/dasd_3370_erp.c b/drivers/s390/block/dasd_3370_erp.c
new file mode 100644
index 0000000..84565c8
--- /dev/null
+++ b/drivers/s390/block/dasd_3370_erp.c
@@ -0,0 +1,104 @@
+/* 
+ * File...........: linux/drivers/s390/block/dasd_3370_erp.c
+ * Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
+ * Bugreports.to..: <Linux390@de.ibm.com>
+ * (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 2000
+ *
+ * $Revision: 1.9 $
+ */
+
+#define PRINTK_HEADER "dasd_erp(3370)"
+
+#include "dasd_int.h"
+
+
+/*
+ * DASD_3370_ERP_EXAMINE 
+ *
+ * DESCRIPTION
+ *   Checks only for fatal/no/recover error. 
+ *   A detailed examination of the sense data is done later outside
+ *   the interrupt handler.
+ *
+ *   The logic is based on the 'IBM 3880 Storage Control Reference' manual
+ *   'Chapter 7. 3370 Sense Data'.
+ *
+ * RETURN VALUES
+ *   dasd_era_none	no error 
+ *   dasd_era_fatal	for all fatal (unrecoverable errors)
+ *   dasd_era_recover	for all others.
+ */
+dasd_era_t
+dasd_3370_erp_examine(struct dasd_ccw_req * cqr, struct irb * irb)
+{
+	char *sense = irb->ecw;
+
+	/* check for successful execution first */
+	if (irb->scsw.cstat == 0x00 &&
+	    irb->scsw.dstat == (DEV_STAT_CHN_END | DEV_STAT_DEV_END))
+		return dasd_era_none;
+	if (sense[0] & 0x80) {	/* CMD reject */
+		return dasd_era_fatal;
+	}
+	if (sense[0] & 0x40) {	/* Drive offline */
+		return dasd_era_recover;
+	}
+	if (sense[0] & 0x20) {	/* Bus out parity */
+		return dasd_era_recover;
+	}
+	if (sense[0] & 0x10) {	/* equipment check */
+		if (sense[1] & 0x80) {
+			return dasd_era_fatal;
+		}
+		return dasd_era_recover;
+	}
+	if (sense[0] & 0x08) {	/* data check */
+		if (sense[1] & 0x80) {
+			return dasd_era_fatal;
+		}
+		return dasd_era_recover;
+	}
+	if (sense[0] & 0x04) {	/* overrun */
+		if (sense[1] & 0x80) {
+			return dasd_era_fatal;
+		}
+		return dasd_era_recover;
+	}
+	if (sense[1] & 0x40) {	/* invalid blocksize */
+		return dasd_era_fatal;
+	}
+	if (sense[1] & 0x04) {	/* file protected */
+		return dasd_era_recover;
+	}
+	if (sense[1] & 0x01) {	/* operation incomplete */
+		return dasd_era_recover;
+	}
+	if (sense[2] & 0x80) {	/* check data erroor */
+		return dasd_era_recover;
+	}
+	if (sense[2] & 0x10) {	/* Env. data present */
+		return dasd_era_recover;
+	}
+	/* examine the 24 byte sense data */
+	return dasd_era_recover;
+
+}				/* END dasd_3370_erp_examine */
+
+/*
+ * Overrides for Emacs so that we follow Linus's tabbing style.
+ * Emacs will notice this stuff at the end of the file and automatically
+ * adjust the settings for this buffer only.  This must remain at the end
+ * of the file.
+ * ---------------------------------------------------------------------------
+ * Local variables:
+ * c-indent-level: 4 
+ * c-brace-imaginary-offset: 0
+ * c-brace-offset: -4
+ * c-argdecl-indent: 4
+ * c-label-offset: -4
+ * c-continued-statement-offset: 4
+ * c-continued-brace-offset: 0
+ * indent-tabs-mode: 1
+ * tab-width: 8
+ * End:
+ */
diff --git a/drivers/s390/block/dasd_3990_erp.c b/drivers/s390/block/dasd_3990_erp.c
new file mode 100644
index 0000000..c143ecb
--- /dev/null
+++ b/drivers/s390/block/dasd_3990_erp.c
@@ -0,0 +1,2742 @@
+/* 
+ * File...........: linux/drivers/s390/block/dasd_3990_erp.c
+ * Author(s)......: Horst  Hummel    <Horst.Hummel@de.ibm.com> 
+ *		    Holger Smolinski <Holger.Smolinski@de.ibm.com>
+ * Bugreports.to..: <Linux390@de.ibm.com>
+ * (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 2000, 2001
+ *
+ * $Revision: 1.36 $
+ */
+
+#include <linux/timer.h>
+#include <linux/slab.h>
+#include <asm/idals.h>
+#include <asm/todclk.h>
+
+#define PRINTK_HEADER "dasd_erp(3990): "
+
+#include "dasd_int.h"
+#include "dasd_eckd.h"
+
+
+struct DCTL_data {
+	unsigned char subcommand;  /* e.g Inhibit Write, Enable Write,... */
+	unsigned char modifier;	   /* Subcommand modifier */
+	unsigned short res;	   /* reserved */
+} __attribute__ ((packed));
+
+/*
+ ***************************************************************************** 
+ * SECTION ERP EXAMINATION
+ ***************************************************************************** 
+ */
+
+/*
+ * DASD_3990_ERP_EXAMINE_24 
+ *
+ * DESCRIPTION
+ *   Checks only for fatal (unrecoverable) error. 
+ *   A detailed examination of the sense data is done later outside
+ *   the interrupt handler.
+ *
+ *   Each bit configuration leading to an action code 2 (Exit with
+ *   programming error or unusual condition indication)
+ *   are handled as fatal error´s.
+ * 
+ *   All other configurations are handled as recoverable errors.
+ *
+ * RETURN VALUES
+ *   dasd_era_fatal	for all fatal (unrecoverable errors)
+ *   dasd_era_recover	for all others.
+ */
+static dasd_era_t
+dasd_3990_erp_examine_24(struct dasd_ccw_req * cqr, char *sense)
+{
+
+	struct dasd_device *device = cqr->device;
+
+	/* check for 'Command Reject' */
+	if ((sense[0] & SNS0_CMD_REJECT) &&
+	    (!(sense[2] & SNS2_ENV_DATA_PRESENT))) {
+
+		DEV_MESSAGE(KERN_ERR, device, "%s",
+			    "EXAMINE 24: Command Reject detected - "
+			    "fatal error");
+
+		return dasd_era_fatal;
+	}
+
+	/* check for 'Invalid Track Format' */
+	if ((sense[1] & SNS1_INV_TRACK_FORMAT) &&
+	    (!(sense[2] & SNS2_ENV_DATA_PRESENT))) {
+
+		DEV_MESSAGE(KERN_ERR, device, "%s",
+			    "EXAMINE 24: Invalid Track Format detected "
+			    "- fatal error");
+
+		return dasd_era_fatal;
+	}
+
+	/* check for 'No Record Found' */
+	if (sense[1] & SNS1_NO_REC_FOUND) {
+
+                /* FIXME: fatal error ?!? */
+		DEV_MESSAGE(KERN_ERR, device,
+			    "EXAMINE 24: No Record Found detected %s",
+                            device->state <= DASD_STATE_BASIC ?
+			    " " : "- fatal error");
+
+		return dasd_era_fatal;
+	}
+
+	/* return recoverable for all others */
+	return dasd_era_recover;
+}				/* END dasd_3990_erp_examine_24 */
+
+/*
+ * DASD_3990_ERP_EXAMINE_32 
+ *
+ * DESCRIPTION
+ *   Checks only for fatal/no/recoverable error. 
+ *   A detailed examination of the sense data is done later outside
+ *   the interrupt handler.
+ *
+ * RETURN VALUES
+ *   dasd_era_none	no error 
+ *   dasd_era_fatal	for all fatal (unrecoverable errors)
+ *   dasd_era_recover	for recoverable others.
+ */
+static dasd_era_t
+dasd_3990_erp_examine_32(struct dasd_ccw_req * cqr, char *sense)
+{
+
+	struct dasd_device *device = cqr->device;
+
+	switch (sense[25]) {
+	case 0x00:
+		return dasd_era_none;
+
+	case 0x01:
+		DEV_MESSAGE(KERN_ERR, device, "%s", "EXAMINE 32: fatal error");
+
+		return dasd_era_fatal;
+
+	default:
+
+		return dasd_era_recover;
+	}
+
+}				/* end dasd_3990_erp_examine_32 */
+
+/*
+ * DASD_3990_ERP_EXAMINE 
+ *
+ * DESCRIPTION
+ *   Checks only for fatal/no/recover error. 
+ *   A detailed examination of the sense data is done later outside
+ *   the interrupt handler.
+ *
+ *   The logic is based on the 'IBM 3990 Storage Control  Reference' manual
+ *   'Chapter 7. Error Recovery Procedures'.
+ *
+ * RETURN VALUES
+ *   dasd_era_none	no error 
+ *   dasd_era_fatal	for all fatal (unrecoverable errors)
+ *   dasd_era_recover	for all others.
+ */
+dasd_era_t
+dasd_3990_erp_examine(struct dasd_ccw_req * cqr, struct irb * irb)
+{
+
+	char *sense = irb->ecw;
+	dasd_era_t era = dasd_era_recover;
+	struct dasd_device *device = cqr->device;
+
+	/* check for successful execution first */
+	if (irb->scsw.cstat == 0x00 &&
+	    irb->scsw.dstat == (DEV_STAT_CHN_END | DEV_STAT_DEV_END))
+		return dasd_era_none;
+
+	/* distinguish between 24 and 32 byte sense data */
+	if (sense[27] & DASD_SENSE_BIT_0) {
+
+		era = dasd_3990_erp_examine_24(cqr, sense);
+
+	} else {
+
+		era = dasd_3990_erp_examine_32(cqr, sense);
+
+	}
+
+	/* log the erp chain if fatal error occurred */
+	if ((era == dasd_era_fatal) && (device->state >= DASD_STATE_READY)) {
+		dasd_log_sense(cqr, irb);
+		dasd_log_ccw(cqr, 0, irb->scsw.cpa);
+	}
+
+	return era;
+
+}				/* END dasd_3990_erp_examine */
+
+/*
+ ***************************************************************************** 
+ * SECTION ERP HANDLING
+ ***************************************************************************** 
+ */
+/*
+ ***************************************************************************** 
+ * 24 and 32 byte sense ERP functions
+ ***************************************************************************** 
+ */
+
+/*
+ * DASD_3990_ERP_CLEANUP 
+ *
+ * DESCRIPTION
+ *   Removes the already build but not necessary ERP request and sets
+ *   the status of the original cqr / erp to the given (final) status
+ *
+ *  PARAMETER
+ *   erp		request to be blocked
+ *   final_status	either DASD_CQR_DONE or DASD_CQR_FAILED 
+ *
+ * RETURN VALUES
+ *   cqr		original cqr		   
+ */
+static struct dasd_ccw_req *
+dasd_3990_erp_cleanup(struct dasd_ccw_req * erp, char final_status)
+{
+	struct dasd_ccw_req *cqr = erp->refers;
+
+	dasd_free_erp_request(erp, erp->device);
+	cqr->status = final_status;
+	return cqr;
+
+}				/* end dasd_3990_erp_cleanup */
+
+/*
+ * DASD_3990_ERP_BLOCK_QUEUE 
+ *
+ * DESCRIPTION
+ *   Block the given device request queue to prevent from further
+ *   processing until the started timer has expired or an related
+ *   interrupt was received.
+ */
+static void
+dasd_3990_erp_block_queue(struct dasd_ccw_req * erp, int expires)
+{
+
+	struct dasd_device *device = erp->device;
+
+	DEV_MESSAGE(KERN_INFO, device,
+		    "blocking request queue for %is", expires/HZ);
+
+	device->stopped |= DASD_STOPPED_PENDING;
+	erp->status = DASD_CQR_QUEUED;
+
+	dasd_set_timer(device, expires);
+}
+
+/*
+ * DASD_3990_ERP_INT_REQ 
+ *
+ * DESCRIPTION
+ *   Handles 'Intervention Required' error.
+ *   This means either device offline or not installed.
+ *
+ * PARAMETER
+ *   erp		current erp
+ * RETURN VALUES
+ *   erp		modified erp
+ */
+static struct dasd_ccw_req *
+dasd_3990_erp_int_req(struct dasd_ccw_req * erp)
+{
+
+	struct dasd_device *device = erp->device;
+
+	/* first time set initial retry counter and erp_function */
+	/* and retry once without blocking queue		 */
+	/* (this enables easier enqueing of the cqr)		 */
+	if (erp->function != dasd_3990_erp_int_req) {
+
+		erp->retries = 256;
+		erp->function = dasd_3990_erp_int_req;
+
+	} else {
+
+		/* issue a message and wait for 'device ready' interrupt */
+		DEV_MESSAGE(KERN_ERR, device, "%s",
+			    "is offline or not installed - "
+			    "INTERVENTION REQUIRED!!");
+
+		dasd_3990_erp_block_queue(erp, 60*HZ);
+	}
+
+	return erp;
+
+}				/* end dasd_3990_erp_int_req */
+
+/*
+ * DASD_3990_ERP_ALTERNATE_PATH 
+ *
+ * DESCRIPTION
+ *   Repeat the operation on a different channel path.
+ *   If all alternate paths have been tried, the request is posted with a
+ *   permanent error.
+ *
+ *  PARAMETER
+ *   erp		pointer to the current ERP
+ *
+ * RETURN VALUES
+ *   erp		modified pointer to the ERP
+ */
+static void
+dasd_3990_erp_alternate_path(struct dasd_ccw_req * erp)
+{
+	struct dasd_device *device = erp->device;
+	__u8 opm;
+
+	/* try alternate valid path */
+	opm = ccw_device_get_path_mask(device->cdev);
+	//FIXME: start with get_opm ?
+	if (erp->lpm == 0)
+		erp->lpm = LPM_ANYPATH & ~(erp->irb.esw.esw0.sublog.lpum);
+	else
+		erp->lpm &= ~(erp->irb.esw.esw0.sublog.lpum);
+
+	if ((erp->lpm & opm) != 0x00) {
+
+		DEV_MESSAGE(KERN_DEBUG, device,
+			    "try alternate lpm=%x (lpum=%x / opm=%x)",
+			    erp->lpm, erp->irb.esw.esw0.sublog.lpum, opm);
+
+		/* reset status to queued to handle the request again... */
+		if (erp->status > DASD_CQR_QUEUED)
+			erp->status = DASD_CQR_QUEUED;
+		erp->retries = 1;
+	} else {
+		DEV_MESSAGE(KERN_ERR, device,
+			    "No alternate channel path left (lpum=%x / "
+			    "opm=%x) -> permanent error",
+			    erp->irb.esw.esw0.sublog.lpum, opm);
+
+		/* post request with permanent error */
+		if (erp->status > DASD_CQR_QUEUED)
+			erp->status = DASD_CQR_FAILED;
+	}
+}				/* end dasd_3990_erp_alternate_path */
+
+/*
+ * DASD_3990_ERP_DCTL
+ *
+ * DESCRIPTION
+ *   Setup cqr to do the Diagnostic Control (DCTL) command with an 
+ *   Inhibit Write subcommand (0x20) and the given modifier.
+ *
+ *  PARAMETER
+ *   erp		pointer to the current (failed) ERP
+ *   modifier		subcommand modifier
+ *   
+ * RETURN VALUES
+ *   dctl_cqr		pointer to NEW dctl_cqr 
+ *
+ */
+static struct dasd_ccw_req *
+dasd_3990_erp_DCTL(struct dasd_ccw_req * erp, char modifier)
+{
+
+	struct dasd_device *device = erp->device;
+	struct DCTL_data *DCTL_data;
+	struct ccw1 *ccw;
+	struct dasd_ccw_req *dctl_cqr;
+
+	dctl_cqr = dasd_alloc_erp_request((char *) &erp->magic, 1,
+					  sizeof (struct DCTL_data),
+					  erp->device);
+	if (IS_ERR(dctl_cqr)) {
+		DEV_MESSAGE(KERN_ERR, device, "%s",
+			    "Unable to allocate DCTL-CQR");
+		erp->status = DASD_CQR_FAILED;
+		return erp;
+	}
+
+	DCTL_data = dctl_cqr->data;
+
+	DCTL_data->subcommand = 0x02;	/* Inhibit Write */
+	DCTL_data->modifier = modifier;
+
+	ccw = dctl_cqr->cpaddr;
+	memset(ccw, 0, sizeof (struct ccw1));
+	ccw->cmd_code = CCW_CMD_DCTL;
+	ccw->count = 4;
+	ccw->cda = (__u32)(addr_t) DCTL_data;
+	dctl_cqr->function = dasd_3990_erp_DCTL;
+	dctl_cqr->refers = erp;
+	dctl_cqr->device = erp->device;
+	dctl_cqr->magic = erp->magic;
+	dctl_cqr->expires = 5 * 60 * HZ;
+	dctl_cqr->retries = 2;
+
+	dctl_cqr->buildclk = get_clock();
+
+	dctl_cqr->status = DASD_CQR_FILLED;
+
+	return dctl_cqr;
+
+}				/* end dasd_3990_erp_DCTL */
+
+/*
+ * DASD_3990_ERP_ACTION_1 
+ *
+ * DESCRIPTION
+ *   Setup ERP to do the ERP action 1 (see Reference manual).
+ *   Repeat the operation on a different channel path.
+ *   If all alternate paths have been tried, the request is posted with a
+ *   permanent error.
+ *   Note: duplex handling is not implemented (yet).
+ *
+ *  PARAMETER
+ *   erp		pointer to the current ERP
+ *
+ * RETURN VALUES
+ *   erp		pointer to the ERP
+ *
+ */
+static struct dasd_ccw_req *
+dasd_3990_erp_action_1(struct dasd_ccw_req * erp)
+{
+
+	erp->function = dasd_3990_erp_action_1;
+
+	dasd_3990_erp_alternate_path(erp);
+
+	return erp;
+
+}				/* end dasd_3990_erp_action_1 */
+
+/*
+ * DASD_3990_ERP_ACTION_4 
+ *
+ * DESCRIPTION
+ *   Setup ERP to do the ERP action 4 (see Reference manual).
+ *   Set the current request to PENDING to block the CQR queue for that device
+ *   until the state change interrupt appears.
+ *   Use a timer (20 seconds) to retry the cqr if the interrupt is still
+ *   missing.
+ *
+ *  PARAMETER
+ *   sense		sense data of the actual error
+ *   erp		pointer to the current ERP
+ *
+ * RETURN VALUES
+ *   erp		pointer to the ERP
+ *
+ */
+static struct dasd_ccw_req *
+dasd_3990_erp_action_4(struct dasd_ccw_req * erp, char *sense)
+{
+
+	struct dasd_device *device = erp->device;
+
+	/* first time set initial retry counter and erp_function    */
+	/* and retry once without waiting for state change pending  */
+	/* interrupt (this enables easier enqueing of the cqr)	    */
+	if (erp->function != dasd_3990_erp_action_4) {
+
+		DEV_MESSAGE(KERN_INFO, device, "%s",
+			    "dasd_3990_erp_action_4: first time retry");
+
+		erp->retries = 256;
+		erp->function = dasd_3990_erp_action_4;
+
+	} else {
+
+		if (sense[25] == 0x1D) {	/* state change pending */
+
+			DEV_MESSAGE(KERN_INFO, device, 
+				    "waiting for state change pending "
+				    "interrupt, %d retries left",
+				    erp->retries);
+			
+			dasd_3990_erp_block_queue(erp, 30*HZ);
+
+                } else if (sense[25] == 0x1E) {	/* busy */
+			DEV_MESSAGE(KERN_INFO, device,
+				    "busy - redriving request later, "
+				    "%d retries left",
+				    erp->retries);
+                        dasd_3990_erp_block_queue(erp, HZ);
+		} else {
+
+			/* no state change pending - retry */
+			DEV_MESSAGE (KERN_INFO, device, 
+				     "redriving request immediately, "
+				     "%d retries left", 
+				     erp->retries);
+			erp->status = DASD_CQR_QUEUED;
+		}
+	}
+
+	return erp;
+
+}				/* end dasd_3990_erp_action_4 */
+
+/*
+ ***************************************************************************** 
+ * 24 byte sense ERP functions (only)
+ ***************************************************************************** 
+ */
+
+/*
+ * DASD_3990_ERP_ACTION_5 
+ *
+ * DESCRIPTION
+ *   Setup ERP to do the ERP action 5 (see Reference manual).
+ *   NOTE: Further handling is done in xxx_further_erp after the retries.
+ *
+ *  PARAMETER
+ *   erp		pointer to the current ERP
+ *
+ * RETURN VALUES
+ *   erp		pointer to the ERP
+ *
+ */
+static struct dasd_ccw_req *
+dasd_3990_erp_action_5(struct dasd_ccw_req * erp)
+{
+
+	/* first of all retry */
+	erp->retries = 10;
+	erp->function = dasd_3990_erp_action_5;
+
+	return erp;
+
+}				/* end dasd_3990_erp_action_5 */
+
+/*
+ * DASD_3990_HANDLE_ENV_DATA
+ *
+ * DESCRIPTION
+ *   Handles 24 byte 'Environmental data present'.
+ *   Does a analysis of the sense data (message Format)
+ *   and prints the error messages.
+ *
+ * PARAMETER
+ *   sense		current sense data
+ *   
+ * RETURN VALUES
+ *   void
+ */
+static void
+dasd_3990_handle_env_data(struct dasd_ccw_req * erp, char *sense)
+{
+
+	struct dasd_device *device = erp->device;
+	char msg_format = (sense[7] & 0xF0);
+	char msg_no = (sense[7] & 0x0F);
+
+	switch (msg_format) {
+	case 0x00:		/* Format 0 - Program or System Checks */
+
+		if (sense[1] & 0x10) {	/* check message to operator bit */
+
+			switch (msg_no) {
+			case 0x00:	/* No Message */
+				break;
+			case 0x01:
+				DEV_MESSAGE(KERN_WARNING, device, "%s",
+					    "FORMAT 0 - Invalid Command");
+				break;
+			case 0x02:
+				DEV_MESSAGE(KERN_WARNING, device, "%s",
+					    "FORMAT 0 - Invalid Command "
+					    "Sequence");
+				break;
+			case 0x03:
+				DEV_MESSAGE(KERN_WARNING, device, "%s",
+					    "FORMAT 0 - CCW Count less than "
+					    "required");
+				break;
+			case 0x04:
+				DEV_MESSAGE(KERN_WARNING, device, "%s",
+					    "FORMAT 0 - Invalid Parameter");
+				break;
+			case 0x05:
+				DEV_MESSAGE(KERN_WARNING, device, "%s",
+					    "FORMAT 0 - Diagnostic of Sepecial"
+					    " Command Violates File Mask");
+				break;
+			case 0x07:
+				DEV_MESSAGE(KERN_WARNING, device, "%s",
+					    "FORMAT 0 - Channel Returned with "
+					    "Incorrect retry CCW");
+				break;
+			case 0x08:
+				DEV_MESSAGE(KERN_WARNING, device, "%s",
+					    "FORMAT 0 - Reset Notification");
+				break;
+			case 0x09:
+				DEV_MESSAGE(KERN_WARNING, device, "%s",
+					    "FORMAT 0 - Storage Path Restart");
+				break;
+			case 0x0A:
+				DEV_MESSAGE(KERN_WARNING, device,
+					    "FORMAT 0 - Channel requested "
+					    "... %02x", sense[8]);
+				break;
+			case 0x0B:
+				DEV_MESSAGE(KERN_WARNING, device, "%s",
+					    "FORMAT 0 - Invalid Defective/"
+					    "Alternate Track Pointer");
+				break;
+			case 0x0C:
+				DEV_MESSAGE(KERN_WARNING, device, "%s",
+					    "FORMAT 0 - DPS Installation "
+					    "Check");
+				break;
+			case 0x0E:
+				DEV_MESSAGE(KERN_WARNING, device, "%s",
+					    "FORMAT 0 - Command Invalid on "
+					    "Secondary Address");
+				break;
+			case 0x0F:
+				DEV_MESSAGE(KERN_WARNING, device,
+					    "FORMAT 0 - Status Not As "
+					    "Required: reason %02x", sense[8]);
+				break;
+			default:
+				DEV_MESSAGE(KERN_WARNING, device, "%s",
+					    "FORMAT 0 - Reseved");
+			}
+		} else {
+			switch (msg_no) {
+			case 0x00:	/* No Message */
+				break;
+			case 0x01:
+				DEV_MESSAGE(KERN_WARNING, device, "%s",
+					    "FORMAT 0 - Device Error Source");
+				break;
+			case 0x02:
+				DEV_MESSAGE(KERN_WARNING, device, "%s",
+					    "FORMAT 0 - Reserved");
+				break;
+			case 0x03:
+				DEV_MESSAGE(KERN_WARNING, device,
+					    "FORMAT 0 - Device Fenced - "
+					    "device = %02x", sense[4]);
+				break;
+			case 0x04:
+				DEV_MESSAGE(KERN_WARNING, device, "%s",
+					    "FORMAT 0 - Data Pinned for "
+					    "Device");
+				break;
+			default:
+				DEV_MESSAGE(KERN_WARNING, device, "%s",
+					    "FORMAT 0 - Reserved");
+			}
+		}
+		break;
+
+	case 0x10:		/* Format 1 - Device Equipment Checks */
+		switch (msg_no) {
+		case 0x00:	/* No Message */
+			break;
+		case 0x01:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 1 - Device Status 1 not as "
+				    "expected");
+			break;
+		case 0x03:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 1 - Index missing");
+			break;
+		case 0x04:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 1 - Interruption cannot be reset");
+			break;
+		case 0x05:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 1 - Device did not respond to "
+				    "selection");
+			break;
+		case 0x06:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 1 - Device check-2 error or Set "
+				    "Sector is not complete");
+			break;
+		case 0x07:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 1 - Head address does not "
+				    "compare");
+			break;
+		case 0x08:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 1 - Device status 1 not valid");
+			break;
+		case 0x09:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 1 - Device not ready");
+			break;
+		case 0x0A:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 1 - Track physical address did "
+				    "not compare");
+			break;
+		case 0x0B:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 1 - Missing device address bit");
+			break;
+		case 0x0C:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 1 - Drive motor switch is off");
+			break;
+		case 0x0D:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 1 - Seek incomplete");
+			break;
+		case 0x0E:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 1 - Cylinder address did not "
+				    "compare");
+			break;
+		case 0x0F:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 1 - Offset active cannot be "
+				    "reset");
+			break;
+		default:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 1 - Reserved");
+		}
+		break;
+
+	case 0x20:		/* Format 2 - 3990 Equipment Checks */
+		switch (msg_no) {
+		case 0x08:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 2 - 3990 check-2 error");
+			break;
+		case 0x0E:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 2 - Support facility errors");
+			break;
+		case 0x0F:
+			DEV_MESSAGE(KERN_WARNING, device,
+				    "FORMAT 2 - Microcode detected error %02x",
+				    sense[8]);
+			break;
+		default:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 2 - Reserved");
+		}
+		break;
+
+	case 0x30:		/* Format 3 - 3990 Control Checks */
+		switch (msg_no) {
+		case 0x0F:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 3 - Allegiance terminated");
+			break;
+		default:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 3 - Reserved");
+		}
+		break;
+
+	case 0x40:		/* Format 4 - Data Checks */
+		switch (msg_no) {
+		case 0x00:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 4 - Home address area error");
+			break;
+		case 0x01:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 4 - Count area error");
+			break;
+		case 0x02:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 4 - Key area error");
+			break;
+		case 0x03:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 4 - Data area error");
+			break;
+		case 0x04:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 4 - No sync byte in home address "
+				    "area");
+			break;
+		case 0x05:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 4 - No sync byte in count address "
+				    "area");
+			break;
+		case 0x06:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 4 - No sync byte in key area");
+			break;
+		case 0x07:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 4 - No sync byte in data area");
+			break;
+		case 0x08:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 4 - Home address area error; "
+				    "offset active");
+			break;
+		case 0x09:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 4 - Count area error; offset "
+				    "active");
+			break;
+		case 0x0A:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 4 - Key area error; offset "
+				    "active");
+			break;
+		case 0x0B:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 4 - Data area error; "
+				    "offset active");
+			break;
+		case 0x0C:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 4 - No sync byte in home "
+				    "address area; offset active");
+			break;
+		case 0x0D:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 4 - No syn byte in count "
+				    "address area; offset active");
+			break;
+		case 0x0E:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 4 - No sync byte in key area; "
+				    "offset active");
+			break;
+		case 0x0F:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 4 - No syn byte in data area; "
+				    "offset active");
+			break;
+		default:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 4 - Reserved");
+		}
+		break;
+
+	case 0x50:  /* Format 5 - Data Check with displacement information */
+		switch (msg_no) {
+		case 0x00:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 5 - Data Check in the "
+				    "home address area");
+			break;
+		case 0x01:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 5 - Data Check in the count area");
+			break;
+		case 0x02:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 5 - Data Check in the key area");
+			break;
+		case 0x03:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 5 - Data Check in the data area");
+			break;
+		case 0x08:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 5 - Data Check in the "
+				    "home address area; offset active");
+			break;
+		case 0x09:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 5 - Data Check in the count area; "
+				    "offset active");
+			break;
+		case 0x0A:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 5 - Data Check in the key area; "
+				    "offset active");
+			break;
+		case 0x0B:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 5 - Data Check in the data area; "
+				    "offset active");
+			break;
+		default:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 5 - Reserved");
+		}
+		break;
+
+	case 0x60:  /* Format 6 - Usage Statistics/Overrun Errors */
+		switch (msg_no) {
+		case 0x00:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 6 - Overrun on channel A");
+			break;
+		case 0x01:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 6 - Overrun on channel B");
+			break;
+		case 0x02:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 6 - Overrun on channel C");
+			break;
+		case 0x03:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 6 - Overrun on channel D");
+			break;
+		case 0x04:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 6 - Overrun on channel E");
+			break;
+		case 0x05:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 6 - Overrun on channel F");
+			break;
+		case 0x06:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 6 - Overrun on channel G");
+			break;
+		case 0x07:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 6 - Overrun on channel H");
+			break;
+		default:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 6 - Reserved");
+		}
+		break;
+
+	case 0x70:  /* Format 7 - Device Connection Control Checks */
+		switch (msg_no) {
+		case 0x00:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 7 - RCC initiated by a connection "
+				    "check alert");
+			break;
+		case 0x01:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 7 - RCC 1 sequence not "
+				    "successful");
+			break;
+		case 0x02:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 7 - RCC 1 and RCC 2 sequences not "
+				    "successful");
+			break;
+		case 0x03:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 7 - Invalid tag-in during "
+				    "selection sequence");
+			break;
+		case 0x04:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 7 - extra RCC required");
+			break;
+		case 0x05:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 7 - Invalid DCC selection "
+				    "response or timeout");
+			break;
+		case 0x06:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 7 - Missing end operation; device "
+				    "transfer complete");
+			break;
+		case 0x07:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 7 - Missing end operation; device "
+				    "transfer incomplete");
+			break;
+		case 0x08:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 7 - Invalid tag-in for an "
+				    "immediate command sequence");
+			break;
+		case 0x09:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 7 - Invalid tag-in for an "
+				    "extended command sequence");
+			break;
+		case 0x0A:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 7 - 3990 microcode time out when "
+				    "stopping selection");
+			break;
+		case 0x0B:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 7 - No response to selection "
+				    "after a poll interruption");
+			break;
+		case 0x0C:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 7 - Permanent path error (DASD "
+				    "controller not available)");
+			break;
+		case 0x0D:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 7 - DASD controller not available"
+				    " on disconnected command chain");
+			break;
+		default:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 7 - Reserved");
+		}
+		break;
+
+	case 0x80:  /* Format 8 - Additional Device Equipment Checks */
+		switch (msg_no) {
+		case 0x00:	/* No Message */
+		case 0x01:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 8 - Error correction code "
+				    "hardware fault");
+			break;
+		case 0x03:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 8 - Unexpected end operation "
+				    "response code");
+			break;
+		case 0x04:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 8 - End operation with transfer "
+				    "count not zero");
+			break;
+		case 0x05:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 8 - End operation with transfer "
+				    "count zero");
+			break;
+		case 0x06:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 8 - DPS checks after a system "
+				    "reset or selective reset");
+			break;
+		case 0x07:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 8 - DPS cannot be filled");
+			break;
+		case 0x08:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 8 - Short busy time-out during "
+				    "device selection");
+			break;
+		case 0x09:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 8 - DASD controller failed to "
+				    "set or reset the long busy latch");
+			break;
+		case 0x0A:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 8 - No interruption from device "
+				    "during a command chain");
+			break;
+		default:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 8 - Reserved");
+		}
+		break;
+
+	case 0x90:  /* Format 9 - Device Read, Write, and Seek Checks */
+		switch (msg_no) {
+		case 0x00:
+			break;	/* No Message */
+		case 0x06:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 9 - Device check-2 error");
+			break;
+		case 0x07:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 9 - Head address did not compare");
+			break;
+		case 0x0A:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 9 - Track physical address did "
+				    "not compare while oriented");
+			break;
+		case 0x0E:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 9 - Cylinder address did not "
+				    "compare");
+			break;
+		default:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT 9 - Reserved");
+		}
+		break;
+
+	case 0xF0:		/* Format F - Cache Storage Checks */
+		switch (msg_no) {
+		case 0x00:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT F - Operation Terminated");
+			break;
+		case 0x01:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT F - Subsystem Processing Error");
+			break;
+		case 0x02:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT F - Cache or nonvolatile storage "
+				    "equipment failure");
+			break;
+		case 0x04:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT F - Caching terminated");
+			break;
+		case 0x06:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT F - Cache fast write access not "
+				    "authorized");
+			break;
+		case 0x07:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT F - Track format incorrect");
+			break;
+		case 0x09:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT F - Caching reinitiated");
+			break;
+		case 0x0A:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT F - Nonvolatile storage "
+				    "terminated");
+			break;
+		case 0x0B:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT F - Volume is suspended duplex");
+			break;
+		case 0x0C:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT F - Subsystem status connot be "
+				    "determined");
+			break;
+		case 0x0D:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT F - Caching status reset to "
+				    "default");
+			break;
+		case 0x0E:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT F - DASD Fast Write inhibited");
+			break;
+		default:
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "FORMAT D - Reserved");
+		}
+		break;
+
+	default:	/* unknown message format - should not happen */
+	        DEV_MESSAGE (KERN_WARNING, device,
+                             "unknown message format %02x",
+                             msg_format);
+		break;
+	}			/* end switch message format */
+
+}				/* end dasd_3990_handle_env_data */
+
+/*
+ * DASD_3990_ERP_COM_REJ
+ *
+ * DESCRIPTION
+ *   Handles 24 byte 'Command Reject' error.
+ *
+ * PARAMETER
+ *   erp		current erp_head
+ *   sense		current sense data
+ * 
+ * RETURN VALUES
+ *   erp		'new' erp_head - pointer to new ERP 
+ */
+static struct dasd_ccw_req *
+dasd_3990_erp_com_rej(struct dasd_ccw_req * erp, char *sense)
+{
+
+	struct dasd_device *device = erp->device;
+
+	erp->function = dasd_3990_erp_com_rej;
+
+	/* env data present (ACTION 10 - retry should work) */
+	if (sense[2] & SNS2_ENV_DATA_PRESENT) {
+
+		DEV_MESSAGE(KERN_DEBUG, device, "%s",
+			    "Command Reject - environmental data present");
+
+		dasd_3990_handle_env_data(erp, sense);
+
+		erp->retries = 5;
+
+	} else {
+		/* fatal error -  set status to FAILED */
+		DEV_MESSAGE(KERN_ERR, device, "%s",
+			    "Command Reject - Fatal error");
+
+		erp = dasd_3990_erp_cleanup(erp, DASD_CQR_FAILED);
+	}
+
+	return erp;
+
+}				/* end dasd_3990_erp_com_rej */
+
+/*
+ * DASD_3990_ERP_BUS_OUT 
+ *
+ * DESCRIPTION
+ *   Handles 24 byte 'Bus Out Parity Check' error.
+ *
+ * PARAMETER
+ *   erp		current erp_head
+ * RETURN VALUES
+ *   erp		new erp_head - pointer to new ERP
+ */
+static struct dasd_ccw_req *
+dasd_3990_erp_bus_out(struct dasd_ccw_req * erp)
+{
+
+	struct dasd_device *device = erp->device;
+
+	/* first time set initial retry counter and erp_function */
+	/* and retry once without blocking queue		 */
+	/* (this enables easier enqueing of the cqr)		 */
+	if (erp->function != dasd_3990_erp_bus_out) {
+		erp->retries = 256;
+		erp->function = dasd_3990_erp_bus_out;
+
+	} else {
+
+		/* issue a message and wait for 'device ready' interrupt */
+		DEV_MESSAGE(KERN_DEBUG, device, "%s",
+			    "bus out parity error or BOPC requested by "
+			    "channel");
+
+		dasd_3990_erp_block_queue(erp, 60*HZ);
+
+	}
+
+	return erp;
+
+}				/* end dasd_3990_erp_bus_out */
+
+/*
+ * DASD_3990_ERP_EQUIP_CHECK
+ *
+ * DESCRIPTION
+ *   Handles 24 byte 'Equipment Check' error.
+ *
+ * PARAMETER
+ *   erp		current erp_head
+ * RETURN VALUES
+ *   erp		new erp_head - pointer to new ERP
+ */
+static struct dasd_ccw_req *
+dasd_3990_erp_equip_check(struct dasd_ccw_req * erp, char *sense)
+{
+
+	struct dasd_device *device = erp->device;
+
+	erp->function = dasd_3990_erp_equip_check;
+
+	if (sense[1] & SNS1_WRITE_INHIBITED) {
+
+		DEV_MESSAGE(KERN_DEBUG, device, "%s",
+			    "Write inhibited path encountered");
+
+		/* vary path offline */
+		DEV_MESSAGE(KERN_ERR, device, "%s",
+			    "Path should be varied off-line. "
+			    "This is not implemented yet \n - please report "
+			    "to linux390@de.ibm.com");
+
+		erp = dasd_3990_erp_action_1(erp);
+
+	} else if (sense[2] & SNS2_ENV_DATA_PRESENT) {
+
+		DEV_MESSAGE(KERN_DEBUG, device, "%s",
+			    "Equipment Check - " "environmental data present");
+
+		dasd_3990_handle_env_data(erp, sense);
+
+		erp = dasd_3990_erp_action_4(erp, sense);
+
+	} else if (sense[1] & SNS1_PERM_ERR) {
+
+		DEV_MESSAGE(KERN_DEBUG, device, "%s",
+			    "Equipment Check - retry exhausted or "
+			    "undesirable");
+
+		erp = dasd_3990_erp_action_1(erp);
+
+	} else {
+		/* all other equipment checks - Action 5 */
+		/* rest is done when retries == 0 */
+		DEV_MESSAGE(KERN_DEBUG, device, "%s",
+			    "Equipment check or processing error");
+
+		erp = dasd_3990_erp_action_5(erp);
+	}
+
+	return erp;
+
+}				/* end dasd_3990_erp_equip_check */
+
+/*
+ * DASD_3990_ERP_DATA_CHECK
+ *
+ * DESCRIPTION
+ *   Handles 24 byte 'Data Check' error.
+ *
+ * PARAMETER
+ *   erp		current erp_head
+ * RETURN VALUES
+ *   erp		new erp_head - pointer to new ERP
+ */
+static struct dasd_ccw_req *
+dasd_3990_erp_data_check(struct dasd_ccw_req * erp, char *sense)
+{
+
+	struct dasd_device *device = erp->device;
+
+	erp->function = dasd_3990_erp_data_check;
+
+	if (sense[2] & SNS2_CORRECTABLE) {	/* correctable data check */
+
+		/* issue message that the data has been corrected */
+		DEV_MESSAGE(KERN_EMERG, device, "%s",
+			    "Data recovered during retry with PCI "
+			    "fetch mode active");
+
+		/* not possible to handle this situation in Linux */
+		panic("No way to inform application about the possibly "
+		      "incorrect data");
+
+	} else if (sense[2] & SNS2_ENV_DATA_PRESENT) {
+
+		DEV_MESSAGE(KERN_DEBUG, device, "%s",
+			    "Uncorrectable data check recovered secondary "
+			    "addr of duplex pair");
+
+		erp = dasd_3990_erp_action_4(erp, sense);
+
+	} else if (sense[1] & SNS1_PERM_ERR) {
+
+		DEV_MESSAGE(KERN_DEBUG, device, "%s",
+			    "Uncorrectable data check with internal "
+			    "retry exhausted");
+
+		erp = dasd_3990_erp_action_1(erp);
+
+	} else {
+		/* all other data checks */
+		DEV_MESSAGE(KERN_DEBUG, device, "%s",
+			    "Uncorrectable data check with retry count "
+			    "exhausted...");
+
+		erp = dasd_3990_erp_action_5(erp);
+	}
+
+	return erp;
+
+}				/* end dasd_3990_erp_data_check */
+
+/*
+ * DASD_3990_ERP_OVERRUN
+ *
+ * DESCRIPTION
+ *   Handles 24 byte 'Overrun' error.
+ *
+ * PARAMETER
+ *   erp		current erp_head
+ * RETURN VALUES
+ *   erp		new erp_head - pointer to new ERP
+ */
+static struct dasd_ccw_req *
+dasd_3990_erp_overrun(struct dasd_ccw_req * erp, char *sense)
+{
+
+	struct dasd_device *device = erp->device;
+
+	erp->function = dasd_3990_erp_overrun;
+
+	DEV_MESSAGE(KERN_DEBUG, device, "%s",
+		    "Overrun - service overrun or overrun"
+		    " error requested by channel");
+
+	erp = dasd_3990_erp_action_5(erp);
+
+	return erp;
+
+}				/* end dasd_3990_erp_overrun */
+
+/*
+ * DASD_3990_ERP_INV_FORMAT
+ *
+ * DESCRIPTION
+ *   Handles 24 byte 'Invalid Track Format' error.
+ *
+ * PARAMETER
+ *   erp		current erp_head
+ * RETURN VALUES
+ *   erp		new erp_head - pointer to new ERP
+ */
+static struct dasd_ccw_req *
+dasd_3990_erp_inv_format(struct dasd_ccw_req * erp, char *sense)
+{
+
+	struct dasd_device *device = erp->device;
+
+	erp->function = dasd_3990_erp_inv_format;
+
+	if (sense[2] & SNS2_ENV_DATA_PRESENT) {
+
+		DEV_MESSAGE(KERN_DEBUG, device, "%s",
+			    "Track format error when destaging or "
+			    "staging data");
+
+		dasd_3990_handle_env_data(erp, sense);
+
+		erp = dasd_3990_erp_action_4(erp, sense);
+
+	} else {
+		DEV_MESSAGE(KERN_ERR, device, "%s",
+			    "Invalid Track Format - Fatal error should have "
+			    "been handled within the interrupt handler");
+
+		erp = dasd_3990_erp_cleanup(erp, DASD_CQR_FAILED);
+	}
+
+	return erp;
+
+}				/* end dasd_3990_erp_inv_format */
+
+/*
+ * DASD_3990_ERP_EOC
+ *
+ * DESCRIPTION
+ *   Handles 24 byte 'End-of-Cylinder' error.
+ *
+ * PARAMETER
+ *   erp		already added default erp
+ * RETURN VALUES
+ *   erp		pointer to original (failed) cqr.
+ */
+static struct dasd_ccw_req *
+dasd_3990_erp_EOC(struct dasd_ccw_req * default_erp, char *sense)
+{
+
+	struct dasd_device *device = default_erp->device;
+
+	DEV_MESSAGE(KERN_ERR, device, "%s",
+		    "End-of-Cylinder - must never happen");
+
+	/* implement action 7 - BUG */
+	return dasd_3990_erp_cleanup(default_erp, DASD_CQR_FAILED);
+
+}				/* end dasd_3990_erp_EOC */
+
+/*
+ * DASD_3990_ERP_ENV_DATA
+ *
+ * DESCRIPTION
+ *   Handles 24 byte 'Environmental-Data Present' error.
+ *
+ * PARAMETER
+ *   erp		current erp_head
+ * RETURN VALUES
+ *   erp		new erp_head - pointer to new ERP
+ */
+static struct dasd_ccw_req *
+dasd_3990_erp_env_data(struct dasd_ccw_req * erp, char *sense)
+{
+
+	struct dasd_device *device = erp->device;
+
+	erp->function = dasd_3990_erp_env_data;
+
+	DEV_MESSAGE(KERN_DEBUG, device, "%s", "Environmental data present");
+
+	dasd_3990_handle_env_data(erp, sense);
+
+	/* don't retry on disabled interface */
+	if (sense[7] != 0x0F) {
+
+		erp = dasd_3990_erp_action_4(erp, sense);
+	} else {
+
+		erp = dasd_3990_erp_cleanup(erp, DASD_CQR_IN_IO);
+	}
+
+	return erp;
+
+}				/* end dasd_3990_erp_env_data */
+
+/*
+ * DASD_3990_ERP_NO_REC
+ *
+ * DESCRIPTION
+ *   Handles 24 byte 'No Record Found' error.
+ *
+ * PARAMETER
+ *   erp		already added default ERP
+ *		
+ * RETURN VALUES
+ *   erp		new erp_head - pointer to new ERP
+ */
+static struct dasd_ccw_req *
+dasd_3990_erp_no_rec(struct dasd_ccw_req * default_erp, char *sense)
+{
+
+	struct dasd_device *device = default_erp->device;
+
+	DEV_MESSAGE(KERN_ERR, device, "%s",
+		    "No Record Found - Fatal error should "
+		    "have been handled within the interrupt handler");
+
+	return dasd_3990_erp_cleanup(default_erp, DASD_CQR_FAILED);
+
+}				/* end dasd_3990_erp_no_rec */
+
+/*
+ * DASD_3990_ERP_FILE_PROT
+ *
+ * DESCRIPTION
+ *   Handles 24 byte 'File Protected' error.
+ *   Note: Seek related recovery is not implemented because
+ *	   wee don't use the seek command yet.
+ *
+ * PARAMETER
+ *   erp		current erp_head
+ * RETURN VALUES
+ *   erp		new erp_head - pointer to new ERP
+ */
+static struct dasd_ccw_req *
+dasd_3990_erp_file_prot(struct dasd_ccw_req * erp)
+{
+
+	struct dasd_device *device = erp->device;
+
+	DEV_MESSAGE(KERN_ERR, device, "%s", "File Protected");
+
+	return dasd_3990_erp_cleanup(erp, DASD_CQR_FAILED);
+
+}				/* end dasd_3990_erp_file_prot */
+
+/*
+ * DASD_3990_ERP_INSPECT_24 
+ *
+ * DESCRIPTION
+ *   Does a detailed inspection of the 24 byte sense data
+ *   and sets up a related error recovery action.  
+ *
+ * PARAMETER
+ *   sense		sense data of the actual error
+ *   erp		pointer to the currently created default ERP
+ *
+ * RETURN VALUES
+ *   erp		pointer to the (addtitional) ERP
+ */
+static struct dasd_ccw_req *
+dasd_3990_erp_inspect_24(struct dasd_ccw_req * erp, char *sense)
+{
+
+	struct dasd_ccw_req *erp_filled = NULL;
+
+	/* Check sense for ....	   */
+	/* 'Command Reject'	   */
+	if ((erp_filled == NULL) && (sense[0] & SNS0_CMD_REJECT)) {
+		erp_filled = dasd_3990_erp_com_rej(erp, sense);
+	}
+	/* 'Intervention Required' */
+	if ((erp_filled == NULL) && (sense[0] & SNS0_INTERVENTION_REQ)) {
+		erp_filled = dasd_3990_erp_int_req(erp);
+	}
+	/* 'Bus Out Parity Check'  */
+	if ((erp_filled == NULL) && (sense[0] & SNS0_BUS_OUT_CHECK)) {
+		erp_filled = dasd_3990_erp_bus_out(erp);
+	}
+	/* 'Equipment Check'	   */
+	if ((erp_filled == NULL) && (sense[0] & SNS0_EQUIPMENT_CHECK)) {
+		erp_filled = dasd_3990_erp_equip_check(erp, sense);
+	}
+	/* 'Data Check'		   */
+	if ((erp_filled == NULL) && (sense[0] & SNS0_DATA_CHECK)) {
+		erp_filled = dasd_3990_erp_data_check(erp, sense);
+	}
+	/* 'Overrun'		   */
+	if ((erp_filled == NULL) && (sense[0] & SNS0_OVERRUN)) {
+		erp_filled = dasd_3990_erp_overrun(erp, sense);
+	}
+	/* 'Invalid Track Format'  */
+	if ((erp_filled == NULL) && (sense[1] & SNS1_INV_TRACK_FORMAT)) {
+		erp_filled = dasd_3990_erp_inv_format(erp, sense);
+	}
+	/* 'End-of-Cylinder'	   */
+	if ((erp_filled == NULL) && (sense[1] & SNS1_EOC)) {
+		erp_filled = dasd_3990_erp_EOC(erp, sense);
+	}
+	/* 'Environmental Data'	   */
+	if ((erp_filled == NULL) && (sense[2] & SNS2_ENV_DATA_PRESENT)) {
+		erp_filled = dasd_3990_erp_env_data(erp, sense);
+	}
+	/* 'No Record Found'	   */
+	if ((erp_filled == NULL) && (sense[1] & SNS1_NO_REC_FOUND)) {
+		erp_filled = dasd_3990_erp_no_rec(erp, sense);
+	}
+	/* 'File Protected'	   */
+	if ((erp_filled == NULL) && (sense[1] & SNS1_FILE_PROTECTED)) {
+		erp_filled = dasd_3990_erp_file_prot(erp);
+	}
+	/* other (unknown) error - do default ERP */
+	if (erp_filled == NULL) {
+
+		erp_filled = erp;
+	}
+
+	return erp_filled;
+
+}				/* END dasd_3990_erp_inspect_24 */
+
+/*
+ ***************************************************************************** 
+ * 32 byte sense ERP functions (only)
+ ***************************************************************************** 
+ */
+
+/*
+ * DASD_3990_ERPACTION_10_32 
+ *
+ * DESCRIPTION
+ *   Handles 32 byte 'Action 10' of Single Program Action Codes.
+ *   Just retry and if retry doesn't work, return with error.
+ *
+ * PARAMETER
+ *   erp		current erp_head
+ *   sense		current sense data 
+ * RETURN VALUES
+ *   erp		modified erp_head
+ */
+static struct dasd_ccw_req *
+dasd_3990_erp_action_10_32(struct dasd_ccw_req * erp, char *sense)
+{
+
+	struct dasd_device *device = erp->device;
+
+	erp->retries = 256;
+	erp->function = dasd_3990_erp_action_10_32;
+
+	DEV_MESSAGE(KERN_DEBUG, device, "%s", "Perform logging requested");
+
+	return erp;
+
+}				/* end dasd_3990_erp_action_10_32 */
+
+/*
+ * DASD_3990_ERP_ACTION_1B_32
+ *
+ * DESCRIPTION
+ *   Handles 32 byte 'Action 1B' of Single Program Action Codes.
+ *   A write operation could not be finished because of an unexpected 
+ *   condition.
+ *   The already created 'default erp' is used to get the link to 
+ *   the erp chain, but it can not be used for this recovery 
+ *   action because it contains no DE/LO data space.
+ *
+ * PARAMETER
+ *   default_erp	already added default erp.
+ *   sense		current sense data 
+ *
+ * RETURN VALUES
+ *   erp		new erp or 
+ *			default_erp in case of imprecise ending or error
+ */
+static struct dasd_ccw_req *
+dasd_3990_erp_action_1B_32(struct dasd_ccw_req * default_erp, char *sense)
+{
+
+	struct dasd_device *device = default_erp->device;
+	__u32 cpa = 0;
+	struct dasd_ccw_req *cqr;
+	struct dasd_ccw_req *erp;
+	struct DE_eckd_data *DE_data;
+	char *LO_data;		/* LO_eckd_data_t */
+	struct ccw1 *ccw;
+
+	DEV_MESSAGE(KERN_DEBUG, device, "%s",
+		    "Write not finished because of unexpected condition");
+
+	default_erp->function = dasd_3990_erp_action_1B_32;
+
+	/* determine the original cqr */
+	cqr = default_erp;
+
+	while (cqr->refers != NULL) {
+		cqr = cqr->refers;
+	}
+
+	/* for imprecise ending just do default erp */
+	if (sense[1] & 0x01) {
+
+		DEV_MESSAGE(KERN_DEBUG, device, "%s",
+			    "Imprecise ending is set - just retry");
+
+		return default_erp;
+	}
+
+	/* determine the address of the CCW to be restarted */
+	/* Imprecise ending is not set -> addr from IRB-SCSW */
+	cpa = default_erp->refers->irb.scsw.cpa;
+
+	if (cpa == 0) {
+
+		DEV_MESSAGE(KERN_DEBUG, device, "%s",
+			    "Unable to determine address of the CCW "
+			    "to be restarted");
+
+		return dasd_3990_erp_cleanup(default_erp, DASD_CQR_FAILED);
+	}
+
+	/* Build new ERP request including DE/LO */
+	erp = dasd_alloc_erp_request((char *) &cqr->magic,
+				     2 + 1,/* DE/LO + TIC */
+				     sizeof (struct DE_eckd_data) +
+				     sizeof (struct LO_eckd_data), device);
+
+	if (IS_ERR(erp)) {
+		DEV_MESSAGE(KERN_ERR, device, "%s", "Unable to allocate ERP");
+		return dasd_3990_erp_cleanup(default_erp, DASD_CQR_FAILED);
+	}
+
+	/* use original DE */
+	DE_data = erp->data;
+	memcpy(DE_data, cqr->data, sizeof (struct DE_eckd_data));
+
+	/* create LO */
+	LO_data = erp->data + sizeof (struct DE_eckd_data);
+
+	if ((sense[3] == 0x01) && (LO_data[1] & 0x01)) {
+
+		DEV_MESSAGE(KERN_ERR, device, "%s",
+			    "BUG - this should not happen");
+
+		return dasd_3990_erp_cleanup(default_erp, DASD_CQR_FAILED);
+	}
+
+	if ((sense[7] & 0x3F) == 0x01) {
+		/* operation code is WRITE DATA -> data area orientation */
+		LO_data[0] = 0x81;
+
+	} else if ((sense[7] & 0x3F) == 0x03) {
+		/* operation code is FORMAT WRITE -> index orientation */
+		LO_data[0] = 0xC3;
+
+	} else {
+		LO_data[0] = sense[7];	/* operation */
+	}
+
+	LO_data[1] = sense[8];	/* auxiliary */
+	LO_data[2] = sense[9];
+	LO_data[3] = sense[3];	/* count */
+	LO_data[4] = sense[29];	/* seek_addr.cyl */
+	LO_data[5] = sense[30];	/* seek_addr.cyl 2nd byte */
+	LO_data[7] = sense[31];	/* seek_addr.head 2nd byte */
+
+	memcpy(&(LO_data[8]), &(sense[11]), 8);
+
+	/* create DE ccw */
+	ccw = erp->cpaddr;
+	memset(ccw, 0, sizeof (struct ccw1));
+	ccw->cmd_code = DASD_ECKD_CCW_DEFINE_EXTENT;
+	ccw->flags = CCW_FLAG_CC;
+	ccw->count = 16;
+	ccw->cda = (__u32)(addr_t) DE_data;
+
+	/* create LO ccw */
+	ccw++;
+	memset(ccw, 0, sizeof (struct ccw1));
+	ccw->cmd_code = DASD_ECKD_CCW_LOCATE_RECORD;
+	ccw->flags = CCW_FLAG_CC;
+	ccw->count = 16;
+	ccw->cda = (__u32)(addr_t) LO_data;
+
+	/* TIC to the failed ccw */
+	ccw++;
+	ccw->cmd_code = CCW_CMD_TIC;
+	ccw->cda = cpa;
+
+	/* fill erp related fields */
+	erp->function = dasd_3990_erp_action_1B_32;
+	erp->refers = default_erp->refers;
+	erp->device = device;
+	erp->magic = default_erp->magic;
+	erp->expires = 0;
+	erp->retries = 256;
+	erp->buildclk = get_clock();
+	erp->status = DASD_CQR_FILLED;
+
+	/* remove the default erp */
+	dasd_free_erp_request(default_erp, device);
+
+	return erp;
+
+}				/* end dasd_3990_erp_action_1B_32 */
+
+/*
+ * DASD_3990_UPDATE_1B
+ *
+ * DESCRIPTION
+ *   Handles the update to the 32 byte 'Action 1B' of Single Program 
+ *   Action Codes in case the first action was not successful.
+ *   The already created 'previous_erp' is the currently not successful
+ *   ERP. 
+ *
+ * PARAMETER
+ *   previous_erp	already created previous erp.
+ *   sense		current sense data 
+ * RETURN VALUES
+ *   erp		modified erp 
+ */
+static struct dasd_ccw_req *
+dasd_3990_update_1B(struct dasd_ccw_req * previous_erp, char *sense)
+{
+
+	struct dasd_device *device = previous_erp->device;
+	__u32 cpa = 0;
+	struct dasd_ccw_req *cqr;
+	struct dasd_ccw_req *erp;
+	char *LO_data;		/* struct LO_eckd_data */
+	struct ccw1 *ccw;
+
+	DEV_MESSAGE(KERN_DEBUG, device, "%s",
+		    "Write not finished because of unexpected condition"
+		    " - follow on");
+
+	/* determine the original cqr */
+	cqr = previous_erp;
+
+	while (cqr->refers != NULL) {
+		cqr = cqr->refers;
+	}
+
+	/* for imprecise ending just do default erp */
+	if (sense[1] & 0x01) {
+
+		DEV_MESSAGE(KERN_DEBUG, device, "%s",
+			    "Imprecise ending is set - just retry");
+
+		previous_erp->status = DASD_CQR_QUEUED;
+
+		return previous_erp;
+	}
+
+	/* determine the address of the CCW to be restarted */
+	/* Imprecise ending is not set -> addr from IRB-SCSW */
+	cpa = previous_erp->irb.scsw.cpa;
+
+	if (cpa == 0) {
+
+		DEV_MESSAGE(KERN_DEBUG, device, "%s",
+			    "Unable to determine address of the CCW "
+			    "to be restarted");
+
+		previous_erp->status = DASD_CQR_FAILED;
+
+		return previous_erp;
+	}
+
+	erp = previous_erp;
+
+	/* update the LO with the new returned sense data  */
+	LO_data = erp->data + sizeof (struct DE_eckd_data);
+
+	if ((sense[3] == 0x01) && (LO_data[1] & 0x01)) {
+
+		DEV_MESSAGE(KERN_ERR, device, "%s",
+			    "BUG - this should not happen");
+
+		previous_erp->status = DASD_CQR_FAILED;
+
+		return previous_erp;
+	}
+
+	if ((sense[7] & 0x3F) == 0x01) {
+		/* operation code is WRITE DATA -> data area orientation */
+		LO_data[0] = 0x81;
+
+	} else if ((sense[7] & 0x3F) == 0x03) {
+		/* operation code is FORMAT WRITE -> index orientation */
+		LO_data[0] = 0xC3;
+
+	} else {
+		LO_data[0] = sense[7];	/* operation */
+	}
+
+	LO_data[1] = sense[8];	/* auxiliary */
+	LO_data[2] = sense[9];
+	LO_data[3] = sense[3];	/* count */
+	LO_data[4] = sense[29];	/* seek_addr.cyl */
+	LO_data[5] = sense[30];	/* seek_addr.cyl 2nd byte */
+	LO_data[7] = sense[31];	/* seek_addr.head 2nd byte */
+
+	memcpy(&(LO_data[8]), &(sense[11]), 8);
+
+	/* TIC to the failed ccw */
+	ccw = erp->cpaddr;	/* addr of DE ccw */
+	ccw++;			/* addr of LE ccw */
+	ccw++;			/* addr of TIC ccw */
+	ccw->cda = cpa;
+
+	erp->status = DASD_CQR_QUEUED;
+
+	return erp;
+
+}				/* end dasd_3990_update_1B */
+
+/*
+ * DASD_3990_ERP_COMPOUND_RETRY 
+ *
+ * DESCRIPTION
+ *   Handles the compound ERP action retry code.
+ *   NOTE: At least one retry is done even if zero is specified
+ *	   by the sense data. This makes enqueueing of the request
+ *	   easier.
+ *
+ * PARAMETER
+ *   sense		sense data of the actual error
+ *   erp		pointer to the currently created ERP
+ *
+ * RETURN VALUES
+ *   erp		modified ERP pointer
+ *
+ */
+static void
+dasd_3990_erp_compound_retry(struct dasd_ccw_req * erp, char *sense)
+{
+
+	switch (sense[25] & 0x03) {
+	case 0x00:		/* no not retry */
+		erp->retries = 1;
+		break;
+
+	case 0x01:		/* retry 2 times */
+		erp->retries = 2;
+		break;
+
+	case 0x02:		/* retry 10 times */
+		erp->retries = 10;
+		break;
+
+	case 0x03:		/* retry 256 times */
+		erp->retries = 256;
+		break;
+
+	default:
+		BUG();
+	}
+
+	erp->function = dasd_3990_erp_compound_retry;
+
+}				/* end dasd_3990_erp_compound_retry */
+
+/*
+ * DASD_3990_ERP_COMPOUND_PATH 
+ *
+ * DESCRIPTION
+ *   Handles the compound ERP action for retry on alternate
+ *   channel path.
+ *
+ * PARAMETER
+ *   sense		sense data of the actual error
+ *   erp		pointer to the currently created ERP
+ *
+ * RETURN VALUES
+ *   erp		modified ERP pointer
+ *
+ */
+static void
+dasd_3990_erp_compound_path(struct dasd_ccw_req * erp, char *sense)
+{
+
+	if (sense[25] & DASD_SENSE_BIT_3) {
+		dasd_3990_erp_alternate_path(erp);
+
+		if (erp->status == DASD_CQR_FAILED) {
+			/* reset the lpm and the status to be able to 
+			 * try further actions. */
+
+			erp->lpm = 0;
+
+			erp->status = DASD_CQR_ERROR;
+
+		}
+	}
+
+	erp->function = dasd_3990_erp_compound_path;
+
+}				/* end dasd_3990_erp_compound_path */
+
+/*
+ * DASD_3990_ERP_COMPOUND_CODE 
+ *
+ * DESCRIPTION
+ *   Handles the compound ERP action for retry code.
+ *
+ * PARAMETER
+ *   sense		sense data of the actual error
+ *   erp		pointer to the currently created ERP
+ *
+ * RETURN VALUES
+ *   erp		NEW ERP pointer
+ *
+ */
+static struct dasd_ccw_req *
+dasd_3990_erp_compound_code(struct dasd_ccw_req * erp, char *sense)
+{
+
+	if (sense[25] & DASD_SENSE_BIT_2) {
+
+		switch (sense[28]) {
+		case 0x17:
+			/* issue a Diagnostic Control command with an 
+			 * Inhibit Write subcommand and controler modifier */
+			erp = dasd_3990_erp_DCTL(erp, 0x20);
+			break;
+			
+		case 0x25:
+			/* wait for 5 seconds and retry again */
+			erp->retries = 1;
+			
+			dasd_3990_erp_block_queue (erp, 5*HZ);
+			break;
+			
+		default:
+			/* should not happen - continue */
+			break;
+		}
+	}
+
+	erp->function = dasd_3990_erp_compound_code;
+
+	return erp;
+
+}				/* end dasd_3990_erp_compound_code */
+
+/*
+ * DASD_3990_ERP_COMPOUND_CONFIG 
+ *
+ * DESCRIPTION
+ *   Handles the compound ERP action for configruation
+ *   dependent error.
+ *   Note: duplex handling is not implemented (yet).
+ *
+ * PARAMETER
+ *   sense		sense data of the actual error
+ *   erp		pointer to the currently created ERP
+ *
+ * RETURN VALUES
+ *   erp		modified ERP pointer
+ *
+ */
+static void
+dasd_3990_erp_compound_config(struct dasd_ccw_req * erp, char *sense)
+{
+
+	if ((sense[25] & DASD_SENSE_BIT_1) && (sense[26] & DASD_SENSE_BIT_2)) {
+
+		/* set to suspended duplex state then restart */
+		struct dasd_device *device = erp->device;
+
+		DEV_MESSAGE(KERN_ERR, device, "%s",
+			    "Set device to suspended duplex state should be "
+			    "done!\n"
+			    "This is not implemented yet (for compound ERP)"
+			    " - please report to linux390@de.ibm.com");
+
+	}
+
+	erp->function = dasd_3990_erp_compound_config;
+
+}				/* end dasd_3990_erp_compound_config */
+
+/*
+ * DASD_3990_ERP_COMPOUND 
+ *
+ * DESCRIPTION
+ *   Does the further compound program action if 
+ *   compound retry was not successful.
+ *
+ * PARAMETER
+ *   sense		sense data of the actual error
+ *   erp		pointer to the current (failed) ERP
+ *
+ * RETURN VALUES
+ *   erp		(additional) ERP pointer
+ *
+ */
+static struct dasd_ccw_req *
+dasd_3990_erp_compound(struct dasd_ccw_req * erp, char *sense)
+{
+
+	if ((erp->function == dasd_3990_erp_compound_retry) &&
+	    (erp->status == DASD_CQR_ERROR)) {
+
+		dasd_3990_erp_compound_path(erp, sense);
+	}
+
+	if ((erp->function == dasd_3990_erp_compound_path) &&
+	    (erp->status == DASD_CQR_ERROR)) {
+
+		erp = dasd_3990_erp_compound_code(erp, sense);
+	}
+
+	if ((erp->function == dasd_3990_erp_compound_code) &&
+	    (erp->status == DASD_CQR_ERROR)) {
+
+		dasd_3990_erp_compound_config(erp, sense);
+	}
+
+	/* if no compound action ERP specified, the request failed */
+	if (erp->status == DASD_CQR_ERROR) {
+
+		erp->status = DASD_CQR_FAILED;
+	}
+
+	return erp;
+
+}				/* end dasd_3990_erp_compound */
+
+/*
+ * DASD_3990_ERP_INSPECT_32 
+ *
+ * DESCRIPTION
+ *   Does a detailed inspection of the 32 byte sense data
+ *   and sets up a related error recovery action.  
+ *
+ * PARAMETER
+ *   sense		sense data of the actual error
+ *   erp		pointer to the currently created default ERP
+ *
+ * RETURN VALUES
+ *   erp_filled		pointer to the ERP
+ *
+ */
+static struct dasd_ccw_req *
+dasd_3990_erp_inspect_32(struct dasd_ccw_req * erp, char *sense)
+{
+
+	struct dasd_device *device = erp->device;
+
+	erp->function = dasd_3990_erp_inspect_32;
+
+	if (sense[25] & DASD_SENSE_BIT_0) {
+
+		/* compound program action codes (byte25 bit 0 == '1') */
+		dasd_3990_erp_compound_retry(erp, sense);
+
+	} else {
+
+		/* single program action codes (byte25 bit 0 == '0') */
+		switch (sense[25]) {
+
+		case 0x00:	/* success - use default ERP for retries */
+		        DEV_MESSAGE(KERN_DEBUG, device, "%s",
+				    "ERP called for successful request"
+				    " - just retry");
+			break;
+
+		case 0x01:	/* fatal error */
+			DEV_MESSAGE(KERN_ERR, device, "%s",
+				    "Fatal error should have been "
+				    "handled within the interrupt handler");
+
+			erp = dasd_3990_erp_cleanup(erp, DASD_CQR_FAILED);
+			break;
+
+		case 0x02:	/* intervention required */
+		case 0x03:	/* intervention required during dual copy */
+			erp = dasd_3990_erp_int_req(erp);
+			break;
+
+		case 0x0F:  /* length mismatch during update write command */
+			DEV_MESSAGE(KERN_ERR, device, "%s",
+				    "update write command error - should not "
+				    "happen;\n"
+				    "Please send this message together with "
+				    "the above sense data to linux390@de."
+				    "ibm.com");
+
+			erp = dasd_3990_erp_cleanup(erp, DASD_CQR_FAILED);
+			break;
+
+		case 0x10:  /* logging required for other channel program */
+			erp = dasd_3990_erp_action_10_32(erp, sense);
+			break;
+
+		case 0x15:	/* next track outside defined extend */
+			DEV_MESSAGE(KERN_ERR, device, "%s",
+				    "next track outside defined extend - "
+				    "should not happen;\n"
+				    "Please send this message together with "
+				    "the above sense data to linux390@de."
+				    "ibm.com");
+
+			erp = dasd_3990_erp_cleanup(erp, DASD_CQR_FAILED);
+			break;
+
+		case 0x1B:	/* unexpected condition during write */
+
+			erp = dasd_3990_erp_action_1B_32(erp, sense);
+			break;
+
+		case 0x1C:	/* invalid data */
+			DEV_MESSAGE(KERN_EMERG, device, "%s",
+				    "Data recovered during retry with PCI "
+				    "fetch mode active");
+
+			/* not possible to handle this situation in Linux */
+			panic
+			    ("Invalid data - No way to inform application "
+			     "about the possibly incorrect data");
+			break;
+
+		case 0x1D:	/* state-change pending */
+			DEV_MESSAGE(KERN_DEBUG, device, "%s",
+				    "A State change pending condition exists "
+				    "for the subsystem or device");
+
+			erp = dasd_3990_erp_action_4(erp, sense);
+			break;
+
+		case 0x1E:	/* busy */
+                        DEV_MESSAGE(KERN_DEBUG, device, "%s",
+				    "Busy condition exists "
+				    "for the subsystem or device");
+                        erp = dasd_3990_erp_action_4(erp, sense);
+			break;
+
+		default:	/* all others errors - default erp  */
+			break;
+		}
+	}
+
+	return erp;
+
+}				/* end dasd_3990_erp_inspect_32 */
+
+/*
+ ***************************************************************************** 
+ * main ERP control fuctions (24 and 32 byte sense)
+ ***************************************************************************** 
+ */
+
+/*
+ * DASD_3990_ERP_INSPECT
+ *
+ * DESCRIPTION
+ *   Does a detailed inspection for sense data by calling either
+ *   the 24-byte or the 32-byte inspection routine.
+ *
+ * PARAMETER
+ *   erp		pointer to the currently created default ERP
+ * RETURN VALUES
+ *   erp_new		contens was possibly modified 
+ */
+static struct dasd_ccw_req *
+dasd_3990_erp_inspect(struct dasd_ccw_req * erp)
+{
+
+	struct dasd_ccw_req *erp_new = NULL;
+	/* sense data are located in the refers record of the */
+	/* already set up new ERP !			      */
+	char *sense = erp->refers->irb.ecw;
+
+	/* distinguish between 24 and 32 byte sense data */
+	if (sense[27] & DASD_SENSE_BIT_0) {
+
+		/* inspect the 24 byte sense data */
+		erp_new = dasd_3990_erp_inspect_24(erp, sense);
+
+	} else {
+
+		/* inspect the 32 byte sense data */
+		erp_new = dasd_3990_erp_inspect_32(erp, sense);
+
+	}	/* end distinguish between 24 and 32 byte sense data */
+
+	return erp_new;
+}
+
+/*
+ * DASD_3990_ERP_ADD_ERP
+ * 
+ * DESCRIPTION
+ *   This funtion adds an additional request block (ERP) to the head of
+ *   the given cqr (or erp).
+ *   This erp is initialized as an default erp (retry TIC)
+ *
+ * PARAMETER
+ *   cqr		head of the current ERP-chain (or single cqr if 
+ *			first error)
+ * RETURN VALUES
+ *   erp		pointer to new ERP-chain head
+ */
+static struct dasd_ccw_req *
+dasd_3990_erp_add_erp(struct dasd_ccw_req * cqr)
+{
+
+	struct dasd_device *device = cqr->device;
+	struct ccw1 *ccw;
+
+	/* allocate additional request block */
+	struct dasd_ccw_req *erp;
+
+	erp = dasd_alloc_erp_request((char *) &cqr->magic, 2, 0, cqr->device);
+	if (IS_ERR(erp)) {
+                if (cqr->retries <= 0) {
+		        DEV_MESSAGE(KERN_ERR, device, "%s",
+				    "Unable to allocate ERP request");
+			cqr->status = DASD_CQR_FAILED;
+                        cqr->stopclk = get_clock ();
+		} else {
+                        DEV_MESSAGE (KERN_ERR, device,
+                                     "Unable to allocate ERP request "
+				     "(%i retries left)",
+                                     cqr->retries);
+			dasd_set_timer(device, (HZ << 3));
+                }
+		return cqr;
+	}
+
+	/* initialize request with default TIC to current ERP/CQR */
+	ccw = erp->cpaddr;
+	ccw->cmd_code = CCW_CMD_NOOP;
+	ccw->flags = CCW_FLAG_CC;
+	ccw++;
+	ccw->cmd_code = CCW_CMD_TIC;
+	ccw->cda      = (long)(cqr->cpaddr);
+	erp->function = dasd_3990_erp_add_erp;
+	erp->refers   = cqr;
+	erp->device   = cqr->device;
+	erp->magic    = cqr->magic;
+	erp->expires  = 0;
+	erp->retries  = 256;
+	erp->buildclk = get_clock();
+
+	erp->status = DASD_CQR_FILLED;
+
+	return erp;
+}
+
+/*
+ * DASD_3990_ERP_ADDITIONAL_ERP 
+ * 
+ * DESCRIPTION
+ *   An additional ERP is needed to handle the current error.
+ *   Add ERP to the head of the ERP-chain containing the ERP processing
+ *   determined based on the sense data.
+ *
+ * PARAMETER
+ *   cqr		head of the current ERP-chain (or single cqr if 
+ *			first error)
+ *
+ * RETURN VALUES
+ *   erp		pointer to new ERP-chain head
+ */
+static struct dasd_ccw_req *
+dasd_3990_erp_additional_erp(struct dasd_ccw_req * cqr)
+{
+
+	struct dasd_ccw_req *erp = NULL;
+
+	/* add erp and initialize with default TIC */
+	erp = dasd_3990_erp_add_erp(cqr);
+
+	/* inspect sense, determine specific ERP if possible */
+	if (erp != cqr) {
+
+		erp = dasd_3990_erp_inspect(erp);
+	}
+
+	return erp;
+
+}				/* end dasd_3990_erp_additional_erp */
+
+/*
+ * DASD_3990_ERP_ERROR_MATCH
+ *
+ * DESCRIPTION
+ *   Check if the device status of the given cqr is the same.
+ *   This means that the failed CCW and the relevant sense data
+ *   must match.
+ *   I don't distinguish between 24 and 32 byte sense because in case of
+ *   24 byte sense byte 25 and 27 is set as well.
+ *
+ * PARAMETER
+ *   cqr1		first cqr, which will be compared with the 
+ *   cqr2		second cqr.
+ *
+ * RETURN VALUES
+ *   match		'boolean' for match found
+ *			returns 1 if match found, otherwise 0.
+ */
+static int
+dasd_3990_erp_error_match(struct dasd_ccw_req *cqr1, struct dasd_ccw_req *cqr2)
+{
+
+	/* check failed CCW */
+	if (cqr1->irb.scsw.cpa != cqr2->irb.scsw.cpa) {
+		//	return 0;	/* CCW doesn't match */
+	}
+
+	/* check sense data; byte 0-2,25,27 */
+	if (!((memcmp (cqr1->irb.ecw, cqr2->irb.ecw, 3) == 0) &&
+	      (cqr1->irb.ecw[27] == cqr2->irb.ecw[27]) &&
+	      (cqr1->irb.ecw[25] == cqr2->irb.ecw[25]))) {
+
+		return 0;	/* sense doesn't match */
+	}
+
+	return 1;		/* match */
+
+}				/* end dasd_3990_erp_error_match */
+
+/*
+ * DASD_3990_ERP_IN_ERP
+ *
+ * DESCRIPTION
+ *   check if the current error already happened before.
+ *   quick exit if current cqr is not an ERP (cqr->refers=NULL)
+ *
+ * PARAMETER
+ *   cqr		failed cqr (either original cqr or already an erp)
+ *
+ * RETURN VALUES
+ *   erp		erp-pointer to the already defined error 
+ *			recovery procedure OR
+ *			NULL if a 'new' error occurred.
+ */
+static struct dasd_ccw_req *
+dasd_3990_erp_in_erp(struct dasd_ccw_req *cqr)
+{
+
+	struct dasd_ccw_req *erp_head = cqr,	/* save erp chain head */
+	*erp_match = NULL;	/* save erp chain head */
+	int match = 0;		/* 'boolean' for matching error found */
+
+	if (cqr->refers == NULL) {	/* return if not in erp */
+		return NULL;
+	}
+
+	/* check the erp/cqr chain for current error */
+	do {
+		match = dasd_3990_erp_error_match(erp_head, cqr->refers);
+		erp_match = cqr;	/* save possible matching erp  */
+		cqr = cqr->refers;	/* check next erp/cqr in queue */
+
+	} while ((cqr->refers != NULL) && (!match));
+
+	if (!match) {
+		return NULL;	/* no match was found */
+	}
+
+	return erp_match;	/* return address of matching erp */
+
+}				/* END dasd_3990_erp_in_erp */
+
+/*
+ * DASD_3990_ERP_FURTHER_ERP (24 & 32 byte sense)
+ *
+ * DESCRIPTION
+ *   No retry is left for the current ERP. Check what has to be done 
+ *   with the ERP.
+ *     - do further defined ERP action or
+ *     - wait for interrupt or	
+ *     - exit with permanent error
+ *
+ * PARAMETER
+ *   erp		ERP which is in progress with no retry left
+ *
+ * RETURN VALUES
+ *   erp		modified/additional ERP
+ */
+static struct dasd_ccw_req *
+dasd_3990_erp_further_erp(struct dasd_ccw_req *erp)
+{
+
+	struct dasd_device *device = erp->device;
+	char *sense = erp->irb.ecw;
+
+	/* check for 24 byte sense ERP */
+	if ((erp->function == dasd_3990_erp_bus_out) ||
+	    (erp->function == dasd_3990_erp_action_1) ||
+	    (erp->function == dasd_3990_erp_action_4)) {
+
+		erp = dasd_3990_erp_action_1(erp);
+
+	} else if (erp->function == dasd_3990_erp_action_5) {
+
+		/* retries have not been successful */
+		/* prepare erp for retry on different channel path */
+		erp = dasd_3990_erp_action_1(erp);
+
+		if (!(sense[2] & DASD_SENSE_BIT_0)) {
+
+			/* issue a Diagnostic Control command with an 
+			 * Inhibit Write subcommand */
+
+			switch (sense[25]) {
+			case 0x17:
+			case 0x57:{	/* controller */
+					erp = dasd_3990_erp_DCTL(erp, 0x20);
+					break;
+				}
+			case 0x18:
+			case 0x58:{	/* channel path */
+					erp = dasd_3990_erp_DCTL(erp, 0x40);
+					break;
+				}
+			case 0x19:
+			case 0x59:{	/* storage director */
+					erp = dasd_3990_erp_DCTL(erp, 0x80);
+					break;
+				}
+			default:
+				DEV_MESSAGE(KERN_DEBUG, device,
+					    "invalid subcommand modifier 0x%x "
+					    "for Diagnostic Control Command",
+					    sense[25]);
+			}
+		}
+
+		/* check for 32 byte sense ERP */
+	} else if ((erp->function == dasd_3990_erp_compound_retry) ||
+		   (erp->function == dasd_3990_erp_compound_path) ||
+		   (erp->function == dasd_3990_erp_compound_code) ||
+		   (erp->function == dasd_3990_erp_compound_config)) {
+
+		erp = dasd_3990_erp_compound(erp, sense);
+
+	} else {
+		/* No retry left and no additional special handling */
+		/*necessary */
+		DEV_MESSAGE(KERN_ERR, device,
+			    "no retries left for erp %p - "
+			    "set status to FAILED", erp);
+
+		erp->status = DASD_CQR_FAILED;
+	}
+
+	return erp;
+
+}				/* end dasd_3990_erp_further_erp */
+
+/*
+ * DASD_3990_ERP_HANDLE_MATCH_ERP 
+ *
+ * DESCRIPTION
+ *   An error occurred again and an ERP has been detected which is already
+ *   used to handle this error (e.g. retries). 
+ *   All prior ERP's are asumed to be successful and therefore removed
+ *   from queue.
+ *   If retry counter of matching erp is already 0, it is checked if further 
+ *   action is needed (besides retry) or if the ERP has failed.
+ *
+ * PARAMETER
+ *   erp_head		first ERP in ERP-chain
+ *   erp		ERP that handles the actual error.
+ *			(matching erp)
+ *
+ * RETURN VALUES
+ *   erp		modified/additional ERP
+ */
+static struct dasd_ccw_req *
+dasd_3990_erp_handle_match_erp(struct dasd_ccw_req *erp_head,
+			       struct dasd_ccw_req *erp)
+{
+
+	struct dasd_device *device = erp_head->device;
+	struct dasd_ccw_req *erp_done = erp_head;	/* finished req */
+	struct dasd_ccw_req *erp_free = NULL;	/* req to be freed */
+
+	/* loop over successful ERPs and remove them from chanq */
+	while (erp_done != erp) {
+
+		if (erp_done == NULL)	/* end of chain reached */
+			panic(PRINTK_HEADER "Programming error in ERP! The "
+			      "original request was lost\n");
+
+		/* remove the request from the device queue */
+		list_del(&erp_done->list);
+
+		erp_free = erp_done;
+		erp_done = erp_done->refers;
+
+		/* free the finished erp request */
+		dasd_free_erp_request(erp_free, erp_free->device);
+
+	}			/* end while */
+
+	if (erp->retries > 0) {
+
+		char *sense = erp->refers->irb.ecw;
+
+		/* check for special retries */
+		if (erp->function == dasd_3990_erp_action_4) {
+
+			erp = dasd_3990_erp_action_4(erp, sense);
+
+		} else if (erp->function == dasd_3990_erp_action_1B_32) {
+
+			erp = dasd_3990_update_1B(erp, sense);
+
+		} else if (erp->function == dasd_3990_erp_int_req) {
+
+			erp = dasd_3990_erp_int_req(erp);
+
+		} else {
+			/* simple retry	  */
+			DEV_MESSAGE(KERN_DEBUG, device,
+				    "%i retries left for erp %p",
+				    erp->retries, erp);
+
+			/* handle the request again... */
+			erp->status = DASD_CQR_QUEUED;
+		}
+
+	} else {
+		/* no retry left - check for further necessary action	 */
+		/* if no further actions, handle rest as permanent error */
+		erp = dasd_3990_erp_further_erp(erp);
+	}
+
+	return erp;
+
+}				/* end dasd_3990_erp_handle_match_erp */
+
+/*
+ * DASD_3990_ERP_ACTION
+ *
+ * DESCRIPTION
+ *   controll routine for 3990 erp actions.
+ *   Has to be called with the queue lock (namely the s390_irq_lock) acquired.
+ *
+ * PARAMETER
+ *   cqr		failed cqr (either original cqr or already an erp)
+ *
+ * RETURN VALUES
+ *   erp		erp-pointer to the head of the ERP action chain.
+ *			This means:
+ *			 - either a ptr to an additional ERP cqr or
+ *			 - the original given cqr (which's status might 
+ *			   be modified)
+ */
+struct dasd_ccw_req *
+dasd_3990_erp_action(struct dasd_ccw_req * cqr)
+{
+
+	struct dasd_ccw_req *erp = NULL;
+	struct dasd_device *device = cqr->device;
+	__u32 cpa = cqr->irb.scsw.cpa;
+
+#ifdef ERP_DEBUG
+	/* print current erp_chain */
+	DEV_MESSAGE(KERN_ERR, device, "%s",
+		    "ERP chain at BEGINNING of ERP-ACTION");
+	{
+		struct dasd_ccw_req *temp_erp = NULL;
+
+		for (temp_erp = cqr;
+		     temp_erp != NULL; temp_erp = temp_erp->refers) {
+
+			DEV_MESSAGE(KERN_ERR, device,
+				    "   erp %p (%02x) refers to %p",
+				    temp_erp, temp_erp->status,
+				    temp_erp->refers);
+		}
+	}
+#endif				/* ERP_DEBUG */
+
+	/* double-check if current erp/cqr was successfull */
+	if ((cqr->irb.scsw.cstat == 0x00) &&
+	    (cqr->irb.scsw.dstat == (DEV_STAT_CHN_END|DEV_STAT_DEV_END))) {
+
+		DEV_MESSAGE(KERN_DEBUG, device,
+			    "ERP called for successful request %p"
+			    " - NO ERP necessary", cqr);
+
+		cqr->status = DASD_CQR_DONE;
+
+		return cqr;
+	}
+	/* check if sense data are available */
+	if (!cqr->irb.ecw) {
+		DEV_MESSAGE(KERN_DEBUG, device,
+			    "ERP called witout sense data avail ..."
+			    "request %p - NO ERP possible", cqr);
+
+		cqr->status = DASD_CQR_FAILED;
+
+		return cqr;
+
+	}
+
+	/* check if error happened before */
+	erp = dasd_3990_erp_in_erp(cqr);
+
+	if (erp == NULL) {
+		/* no matching erp found - set up erp */
+		erp = dasd_3990_erp_additional_erp(cqr);
+	} else {
+		/* matching erp found - set all leading erp's to DONE */
+		erp = dasd_3990_erp_handle_match_erp(cqr, erp);
+	}
+
+#ifdef ERP_DEBUG
+	/* print current erp_chain */
+	DEV_MESSAGE(KERN_ERR, device, "%s", "ERP chain at END of ERP-ACTION");
+	{
+		struct dasd_ccw_req *temp_erp = NULL;
+		for (temp_erp = erp;
+		     temp_erp != NULL; temp_erp = temp_erp->refers) {
+
+			DEV_MESSAGE(KERN_ERR, device,
+				    "   erp %p (%02x) refers to %p",
+				    temp_erp, temp_erp->status,
+				    temp_erp->refers);
+		}
+	}
+#endif				/* ERP_DEBUG */
+
+	if (erp->status == DASD_CQR_FAILED)
+		dasd_log_ccw(erp, 1, cpa);
+
+	/* enqueue added ERP request */
+	if (erp->status == DASD_CQR_FILLED) {
+		erp->status = DASD_CQR_QUEUED;
+		list_add(&erp->list, &device->ccw_queue);
+	}
+
+	return erp;
+
+}				/* end dasd_3990_erp_action */
+
+/*
+ * Overrides for Emacs so that we follow Linus's tabbing style.
+ * Emacs will notice this stuff at the end of the file and automatically
+ * adjust the settings for this buffer only.  This must remain at the end
+ * of the file.
+ * ---------------------------------------------------------------------------
+ * Local variables:
+ * c-indent-level: 4 
+ * c-brace-imaginary-offset: 0
+ * c-brace-offset: -4
+ * c-argdecl-indent: 4
+ * c-label-offset: -4
+ * c-continued-statement-offset: 4
+ * c-continued-brace-offset: 0
+ * indent-tabs-mode: 1
+ * tab-width: 8
+ * End:
+ */
diff --git a/drivers/s390/block/dasd_9336_erp.c b/drivers/s390/block/dasd_9336_erp.c
new file mode 100644
index 0000000..01e8717
--- /dev/null
+++ b/drivers/s390/block/dasd_9336_erp.c
@@ -0,0 +1,61 @@
+/* 
+ * File...........: linux/drivers/s390/block/dasd_9336_erp.c
+ * Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
+ * Bugreports.to..: <Linux390@de.ibm.com>
+ * (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 2000
+ *
+ * $Revision: 1.8 $
+ */
+
+#define PRINTK_HEADER "dasd_erp(9336)"
+
+#include "dasd_int.h"
+
+
+/*
+ * DASD_9336_ERP_EXAMINE 
+ *
+ * DESCRIPTION
+ *   Checks only for fatal/no/recover error. 
+ *   A detailed examination of the sense data is done later outside
+ *   the interrupt handler.
+ *
+ *   The logic is based on the 'IBM 3880 Storage Control Reference' manual
+ *   'Chapter 7. 9336 Sense Data'.
+ *
+ * RETURN VALUES
+ *   dasd_era_none	no error 
+ *   dasd_era_fatal	for all fatal (unrecoverable errors)
+ *   dasd_era_recover	for all others.
+ */
+dasd_era_t
+dasd_9336_erp_examine(struct dasd_ccw_req * cqr, struct irb * irb)
+{
+	/* check for successful execution first */
+	if (irb->scsw.cstat == 0x00 &&
+	    irb->scsw.dstat == (DEV_STAT_CHN_END | DEV_STAT_DEV_END))
+		return dasd_era_none;
+
+	/* examine the 24 byte sense data */
+	return dasd_era_recover;
+
+}				/* END dasd_9336_erp_examine */
+
+/*
+ * Overrides for Emacs so that we follow Linus's tabbing style.
+ * Emacs will notice this stuff at the end of the file and automatically
+ * adjust the settings for this buffer only.  This must remain at the end
+ * of the file.
+ * ---------------------------------------------------------------------------
+ * Local variables:
+ * c-indent-level: 4 
+ * c-brace-imaginary-offset: 0
+ * c-brace-offset: -4
+ * c-argdecl-indent: 4
+ * c-label-offset: -4
+ * c-continued-statement-offset: 4
+ * c-continued-brace-offset: 0
+ * indent-tabs-mode: 1
+ * tab-width: 8
+ * End:
+ */
diff --git a/drivers/s390/block/dasd_9343_erp.c b/drivers/s390/block/dasd_9343_erp.c
new file mode 100644
index 0000000..2a23b74
--- /dev/null
+++ b/drivers/s390/block/dasd_9343_erp.c
@@ -0,0 +1,22 @@
+/* 
+ * File...........: linux/drivers/s390/block/dasd_9345_erp.c
+ * Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
+ * Bugreports.to..: <Linux390@de.ibm.com>
+ * (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 2000
+ *
+ * $Revision: 1.13 $
+ */
+
+#define PRINTK_HEADER "dasd_erp(9343)"
+
+#include "dasd_int.h"
+
+dasd_era_t
+dasd_9343_erp_examine(struct dasd_ccw_req * cqr, struct irb * irb)
+{
+	if (irb->scsw.cstat == 0x00 &&
+	    irb->scsw.dstat == (DEV_STAT_CHN_END | DEV_STAT_DEV_END))
+		return dasd_era_none;
+
+	return dasd_era_recover;
+}
diff --git a/drivers/s390/block/dasd_cmb.c b/drivers/s390/block/dasd_cmb.c
new file mode 100644
index 0000000..ed1ab47
--- /dev/null
+++ b/drivers/s390/block/dasd_cmb.c
@@ -0,0 +1,145 @@
+/*
+ * linux/drivers/s390/block/dasd_cmb.c ($Revision: 1.6 $)
+ *
+ * Linux on zSeries Channel Measurement Facility support
+ *  (dasd device driver interface)
+ *
+ * Copyright 2000,2003 IBM Corporation
+ *
+ * Author: Arnd Bergmann <arndb@de.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/init.h>
+#include <linux/ioctl32.h>
+#include <linux/module.h>
+#include <asm/ccwdev.h>
+#include <asm/cmb.h>
+
+#include "dasd_int.h"
+
+static int
+dasd_ioctl_cmf_enable(struct block_device *bdev, int no, long args)
+{
+	struct dasd_device *device;
+
+	device = bdev->bd_disk->private_data;
+	if (!device)
+		return -EINVAL;
+
+	return enable_cmf(device->cdev);
+}
+
+static int
+dasd_ioctl_cmf_disable(struct block_device *bdev, int no, long args)
+{
+	struct dasd_device *device;
+
+	device = bdev->bd_disk->private_data;
+	if (!device)
+		return -EINVAL;
+
+	return disable_cmf(device->cdev);
+}
+
+static int
+dasd_ioctl_readall_cmb(struct block_device *bdev, int no, long args)
+{
+	struct dasd_device *device;
+	struct cmbdata __user *udata;
+	struct cmbdata data;
+	size_t size;
+	int ret;
+
+	device = bdev->bd_disk->private_data;
+	if (!device)
+		return -EINVAL;
+	udata = (void __user *) args;
+	size = _IOC_SIZE(no);
+
+	if (!access_ok(VERIFY_WRITE, udata, size))
+		return -EFAULT;
+	ret = cmf_readall(device->cdev, &data);
+	if (ret)
+		return ret;
+	if (copy_to_user(udata, &data, min(size, sizeof(*udata))))
+		return -EFAULT;
+	return 0;
+}
+
+/* module initialization below here. dasd already provides a mechanism
+ * to dynamically register ioctl functions, so we simply use this. */
+static inline int
+ioctl_reg(unsigned int no, dasd_ioctl_fn_t handler)
+{
+	int ret;
+	ret = dasd_ioctl_no_register(THIS_MODULE, no, handler);
+#ifdef CONFIG_COMPAT
+	if (ret)
+		return ret;
+
+	ret = register_ioctl32_conversion(no, NULL);
+	if (ret)
+		dasd_ioctl_no_unregister(THIS_MODULE, no, handler);
+#endif
+	return ret;
+}
+
+static inline void
+ioctl_unreg(unsigned int no, dasd_ioctl_fn_t handler)
+{
+	dasd_ioctl_no_unregister(THIS_MODULE, no, handler);
+#ifdef CONFIG_COMPAT
+	unregister_ioctl32_conversion(no);
+#endif
+
+}
+
+static void
+dasd_cmf_exit(void)
+{
+	ioctl_unreg(BIODASDCMFENABLE,  dasd_ioctl_cmf_enable);
+	ioctl_unreg(BIODASDCMFDISABLE, dasd_ioctl_cmf_disable);
+	ioctl_unreg(BIODASDREADALLCMB, dasd_ioctl_readall_cmb);
+}
+
+static int __init
+dasd_cmf_init(void)
+{
+	int ret;
+	ret = ioctl_reg (BIODASDCMFENABLE, dasd_ioctl_cmf_enable);
+	if (ret)
+		goto err;
+	ret = ioctl_reg (BIODASDCMFDISABLE, dasd_ioctl_cmf_disable);
+	if (ret)
+		goto err;
+	ret = ioctl_reg (BIODASDREADALLCMB, dasd_ioctl_readall_cmb);
+	if (ret)
+		goto err;
+
+	return 0;
+err:
+	dasd_cmf_exit();
+
+	return ret;
+}
+
+module_init(dasd_cmf_init);
+module_exit(dasd_cmf_exit);
+
+MODULE_AUTHOR("Arnd Bergmann <arndb@de.ibm.com>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("channel measurement facility interface for dasd\n"
+		   "Copyright 2003 IBM Corporation\n");
diff --git a/drivers/s390/block/dasd_devmap.c b/drivers/s390/block/dasd_devmap.c
new file mode 100644
index 0000000..ad1841a
--- /dev/null
+++ b/drivers/s390/block/dasd_devmap.c
@@ -0,0 +1,772 @@
+/*
+ * File...........: linux/drivers/s390/block/dasd_devmap.c
+ * Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
+ *		    Horst Hummel <Horst.Hummel@de.ibm.com>
+ *		    Carsten Otte <Cotte@de.ibm.com>
+ *		    Martin Schwidefsky <schwidefsky@de.ibm.com>
+ * Bugreports.to..: <Linux390@de.ibm.com>
+ * (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999-2001
+ *
+ * Device mapping and dasd= parameter parsing functions. All devmap
+ * functions may not be called from interrupt context. In particular
+ * dasd_get_device is a no-no from interrupt context.
+ *
+ * $Revision: 1.37 $
+ */
+
+#include <linux/config.h>
+#include <linux/ctype.h>
+#include <linux/init.h>
+
+#include <asm/debug.h>
+#include <asm/uaccess.h>
+
+/* This is ugly... */
+#define PRINTK_HEADER "dasd_devmap:"
+
+#include "dasd_int.h"
+
+kmem_cache_t *dasd_page_cache;
+EXPORT_SYMBOL(dasd_page_cache);
+
+/*
+ * dasd_devmap_t is used to store the features and the relation
+ * between device number and device index. To find a dasd_devmap_t
+ * that corresponds to a device number of a device index each
+ * dasd_devmap_t is added to two linked lists, one to search by
+ * the device number and one to search by the device index. As
+ * soon as big minor numbers are available the device index list
+ * can be removed since the device number will then be identical
+ * to the device index.
+ */
+struct dasd_devmap {
+	struct list_head list;
+	char bus_id[BUS_ID_SIZE];
+        unsigned int devindex;
+        unsigned short features;
+	struct dasd_device *device;
+};
+
+/*
+ * Parameter parsing functions for dasd= parameter. The syntax is:
+ *   <devno>		: (0x)?[0-9a-fA-F]+
+ *   <busid>		: [0-0a-f]\.[0-9a-f]\.(0x)?[0-9a-fA-F]+
+ *   <feature>		: ro
+ *   <feature_list>	: \(<feature>(:<feature>)*\)
+ *   <devno-range>	: <devno>(-<devno>)?<feature_list>?
+ *   <busid-range>	: <busid>(-<busid>)?<feature_list>?
+ *   <devices>		: <devno-range>|<busid-range>
+ *   <dasd_module>	: dasd_diag_mod|dasd_eckd_mod|dasd_fba_mod
+ *
+ *   <dasd>		: autodetect|probeonly|<devices>(,<devices>)*
+ */
+
+int dasd_probeonly =  0;	/* is true, when probeonly mode is active */
+int dasd_autodetect = 0;	/* is true, when autodetection is active */
+
+/*
+ * char *dasd[] is intended to hold the ranges supplied by the dasd= statement
+ * it is named 'dasd' to directly be filled by insmod with the comma separated
+ * strings when running as a module.
+ */
+static char *dasd[256];
+/*
+ * Single spinlock to protect devmap structures and lists.
+ */
+static DEFINE_SPINLOCK(dasd_devmap_lock);
+
+/*
+ * Hash lists for devmap structures.
+ */
+static struct list_head dasd_hashlists[256];
+int dasd_max_devindex;
+
+static struct dasd_devmap *dasd_add_busid(char *, int);
+
+static inline int
+dasd_hash_busid(char *bus_id)
+{
+	int hash, i;
+
+	hash = 0;
+	for (i = 0; (i < BUS_ID_SIZE) && *bus_id; i++, bus_id++)
+		hash += *bus_id;
+	return hash & 0xff;
+}
+
+#ifndef MODULE
+/*
+ * The parameter parsing functions for builtin-drivers are called
+ * before kmalloc works. Store the pointers to the parameters strings
+ * into dasd[] for later processing.
+ */
+static int __init
+dasd_call_setup(char *str)
+{
+	static int count = 0;
+
+	if (count < 256)
+		dasd[count++] = str;
+	return 1;
+}
+
+__setup ("dasd=", dasd_call_setup);
+#endif	/* #ifndef MODULE */
+
+/*
+ * Read a device busid/devno from a string.
+ */
+static inline int
+dasd_busid(char **str, int *id0, int *id1, int *devno)
+{
+	int val, old_style;
+ 
+	/* 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 */
+		return -EINVAL;
+	val = simple_strtoul(*str, str, 16);
+	if (old_style || (*str)[0] != '.') {
+		*id0 = *id1 = 0;
+		if (val < 0 || val > 0xffff)
+			return -EINVAL;
+		*devno = val;
+		return 0;
+	}
+	/* New style x.y.z busid */
+	if (val < 0 || val > 0xff)
+		return -EINVAL;
+	*id0 = val;
+	(*str)++;
+	if (!isxdigit((*str)[0]))	/* We require at least one hex digit */
+		return -EINVAL;
+	val = simple_strtoul(*str, str, 16);
+	if (val < 0 || val > 0xff || (*str)++[0] != '.')
+		return -EINVAL;
+	*id1 = val;
+	if (!isxdigit((*str)[0]))	/* We require at least one hex digit */
+		return -EINVAL;
+	val = simple_strtoul(*str, str, 16);
+	if (val < 0 || val > 0xffff)
+		return -EINVAL;
+	*devno = val;
+	return 0;
+}
+
+/*
+ * Read colon separated list of dasd features. Currently there is
+ * only one: "ro" for read-only devices. The default feature set
+ * is empty (value 0).
+ */
+static inline int
+dasd_feature_list(char *str, char **endp)
+{
+	int features, len, rc;
+
+	rc = 0;
+	if (*str != '(') {
+		*endp = str;
+		return DASD_FEATURE_DEFAULT;
+	}
+	str++;
+	features = 0;
+
+	while (1) {
+		for (len = 0; 
+		     str[len] && str[len] != ':' && str[len] != ')'; len++);
+		if (len == 2 && !strncmp(str, "ro", 2))
+			features |= DASD_FEATURE_READONLY;
+		else if (len == 4 && !strncmp(str, "diag", 4))
+			features |= DASD_FEATURE_USEDIAG;
+		else {
+			MESSAGE(KERN_WARNING,
+				"unsupported feature: %*s, "
+				"ignoring setting", len, str);
+			rc = -EINVAL;
+		}
+		str += len;
+		if (*str != ':')
+			break;
+		str++;
+	}
+	if (*str != ')') {
+		MESSAGE(KERN_WARNING, "%s",
+			"missing ')' in dasd parameter string\n");
+		rc = -EINVAL;
+	} else
+		str++;
+	*endp = str;
+	if (rc != 0)
+		return rc;
+	return features;
+}
+
+/*
+ * Try to match the first element on the comma separated parse string
+ * with one of the known keywords. If a keyword is found, take the approprate
+ * action and return a pointer to the residual string. If the first element
+ * could not be matched to any keyword then return an error code.
+ */
+static char *
+dasd_parse_keyword( char *parsestring ) {
+
+	char *nextcomma, *residual_str;
+	int length;
+
+	nextcomma = strchr(parsestring,',');
+	if (nextcomma) {
+		length = nextcomma - parsestring;
+		residual_str = nextcomma + 1;
+	} else {
+		length = strlen(parsestring);
+		residual_str = parsestring + length;
+        }
+	if (strncmp ("autodetect", parsestring, length) == 0) {
+		dasd_autodetect = 1;
+		MESSAGE (KERN_INFO, "%s",
+			 "turning to autodetection mode");
+                return residual_str;
+        }
+        if (strncmp ("probeonly", parsestring, length) == 0) {
+		dasd_probeonly = 1;
+		MESSAGE(KERN_INFO, "%s",
+			"turning to probeonly mode");
+                return residual_str;
+        }
+        if (strncmp ("fixedbuffers", parsestring, length) == 0) {
+		if (dasd_page_cache)
+			return residual_str;
+		dasd_page_cache =
+			kmem_cache_create("dasd_page_cache", PAGE_SIZE, 0,
+					  SLAB_CACHE_DMA, NULL, NULL );
+		if (!dasd_page_cache)
+			MESSAGE(KERN_WARNING, "%s", "Failed to create slab, "
+				"fixed buffer mode disabled.");
+		else
+			MESSAGE (KERN_INFO, "%s",
+				 "turning on fixed buffer mode");
+                return residual_str;
+        }
+	return ERR_PTR(-EINVAL);
+}
+
+/*
+ * Try to interprete the first element on the comma separated parse string
+ * as a device number or a range of devices. If the interpretation is
+ * successfull, create the matching dasd_devmap entries and return a pointer
+ * to the residual string.
+ * If interpretation fails or in case of an error, return an error code.
+ */
+static char *
+dasd_parse_range( char *parsestring ) {
+
+	struct dasd_devmap *devmap;
+	int from, from_id0, from_id1;
+	int to, to_id0, to_id1;
+	int features, rc;
+	char bus_id[BUS_ID_SIZE+1], *str;
+
+	str = parsestring;
+	rc = dasd_busid(&str, &from_id0, &from_id1, &from);
+	if (rc == 0) {
+		to = from;
+		to_id0 = from_id0;
+		to_id1 = from_id1;
+		if (*str == '-') {
+			str++;
+			rc = dasd_busid(&str, &to_id0, &to_id1, &to);
+		}
+	}
+	if (rc == 0 &&
+	    (from_id0 != to_id0 || from_id1 != to_id1 || from > to))
+		rc = -EINVAL;
+	if (rc) {
+		MESSAGE(KERN_ERR, "Invalid device range %s", parsestring);
+		return ERR_PTR(rc);
+	}
+	features = dasd_feature_list(str, &str);
+	if (features < 0)
+		return ERR_PTR(-EINVAL);
+	while (from <= to) {
+		sprintf(bus_id, "%01x.%01x.%04x",
+			from_id0, from_id1, from++);
+		devmap = dasd_add_busid(bus_id, features);
+		if (IS_ERR(devmap))
+			return (char *)devmap;
+	}
+	if (*str == ',')
+		return str + 1;
+	if (*str == '\0')
+		return str;
+	MESSAGE(KERN_WARNING,
+		"junk at end of dasd parameter string: %s\n", str);
+	return ERR_PTR(-EINVAL);
+}
+
+static inline char *
+dasd_parse_next_element( char *parsestring ) {
+	char * residual_str;
+	residual_str = dasd_parse_keyword(parsestring);
+	if (!IS_ERR(residual_str))
+		return residual_str;
+	residual_str = dasd_parse_range(parsestring);
+	return residual_str;
+}
+
+/*
+ * Parse parameters stored in dasd[]
+ * The 'dasd=...' parameter allows to specify a comma separated list of
+ * keywords and device ranges. When the dasd driver is build into the kernel,
+ * the complete list will be stored as one element of the dasd[] array.
+ * When the dasd driver is build as a module, then the list is broken into
+ * it's elements and each dasd[] entry contains one element.
+ */
+int
+dasd_parse(void)
+{
+	int rc, i;
+	char *parsestring;
+
+	rc = 0;
+	for (i = 0; i < 256; i++) {
+		if (dasd[i] == NULL)
+			break;
+		parsestring = dasd[i];
+		/* loop over the comma separated list in the parsestring */
+		while (*parsestring) {
+			parsestring = dasd_parse_next_element(parsestring);
+			if(IS_ERR(parsestring)) {
+				rc = PTR_ERR(parsestring);
+				break;
+			}
+		}
+		if (rc) {
+			DBF_EVENT(DBF_ALERT, "%s", "invalid range found");
+			break;
+		}
+	}
+	return rc;
+}
+
+/*
+ * Add a devmap for the device specified by busid. It is possible that
+ * the devmap already exists (dasd= parameter). The order of the devices
+ * added through this function will define the kdevs for the individual
+ * devices. 
+ */
+static struct dasd_devmap *
+dasd_add_busid(char *bus_id, int features)
+{
+	struct dasd_devmap *devmap, *new, *tmp;
+	int hash;
+
+	new = (struct dasd_devmap *)
+		kmalloc(sizeof(struct dasd_devmap), GFP_KERNEL);
+	if (!new)
+		return ERR_PTR(-ENOMEM);
+	spin_lock(&dasd_devmap_lock);
+	devmap = 0;
+	hash = dasd_hash_busid(bus_id);
+	list_for_each_entry(tmp, &dasd_hashlists[hash], list)
+		if (strncmp(tmp->bus_id, bus_id, BUS_ID_SIZE) == 0) {
+			devmap = tmp;
+			break;
+		}
+	if (!devmap) {
+		/* This bus_id is new. */
+		new->devindex = dasd_max_devindex++;
+		strncpy(new->bus_id, bus_id, BUS_ID_SIZE);
+		new->features = features;
+		new->device = 0;
+		list_add(&new->list, &dasd_hashlists[hash]);
+		devmap = new;
+		new = 0;
+	}
+	spin_unlock(&dasd_devmap_lock);
+	if (new)
+		kfree(new);
+	return devmap;
+}
+
+/*
+ * Find devmap for device with given bus_id.
+ */
+static struct dasd_devmap *
+dasd_find_busid(char *bus_id)
+{
+	struct dasd_devmap *devmap, *tmp;
+	int hash;
+
+	spin_lock(&dasd_devmap_lock);
+	devmap = ERR_PTR(-ENODEV);
+	hash = dasd_hash_busid(bus_id);
+	list_for_each_entry(tmp, &dasd_hashlists[hash], list) {
+		if (strncmp(tmp->bus_id, bus_id, BUS_ID_SIZE) == 0) {
+			devmap = tmp;
+			break;
+		}
+	}
+	spin_unlock(&dasd_devmap_lock);
+	return devmap;
+}
+
+/*
+ * Check if busid has been added to the list of dasd ranges.
+ */
+int
+dasd_busid_known(char *bus_id)
+{
+	return IS_ERR(dasd_find_busid(bus_id)) ? -ENOENT : 0;
+}
+
+/*
+ * Forget all about the device numbers added so far.
+ * This may only be called at module unload or system shutdown.
+ */
+static void
+dasd_forget_ranges(void)
+{
+	struct dasd_devmap *devmap, *n;
+	int i;
+
+	spin_lock(&dasd_devmap_lock);
+	for (i = 0; i < 256; i++) {
+		list_for_each_entry_safe(devmap, n, &dasd_hashlists[i], list) {
+			if (devmap->device != NULL)
+				BUG();
+			list_del(&devmap->list);
+			kfree(devmap);
+		}
+	}
+	spin_unlock(&dasd_devmap_lock);
+}
+
+/*
+ * Find the device struct by its device index.
+ */
+struct dasd_device *
+dasd_device_from_devindex(int devindex)
+{
+	struct dasd_devmap *devmap, *tmp;
+	struct dasd_device *device;
+	int i;
+
+	spin_lock(&dasd_devmap_lock);
+	devmap = 0;
+	for (i = 0; (i < 256) && !devmap; i++)
+		list_for_each_entry(tmp, &dasd_hashlists[i], list)
+			if (tmp->devindex == devindex) {
+				/* Found the devmap for the device. */
+				devmap = tmp;
+				break;
+			}
+	if (devmap && devmap->device) {
+		device = devmap->device;
+		dasd_get_device(device);
+	} else
+		device = ERR_PTR(-ENODEV);
+	spin_unlock(&dasd_devmap_lock);
+	return device;
+}
+
+/*
+ * Return devmap for cdev. If no devmap exists yet, create one and
+ * connect it to the cdev.
+ */
+static struct dasd_devmap *
+dasd_devmap_from_cdev(struct ccw_device *cdev)
+{
+	struct dasd_devmap *devmap;
+
+	devmap = dasd_find_busid(cdev->dev.bus_id);
+	if (IS_ERR(devmap))
+		devmap = dasd_add_busid(cdev->dev.bus_id,
+					DASD_FEATURE_DEFAULT);
+	return devmap;
+}
+
+/*
+ * Create a dasd device structure for cdev.
+ */
+struct dasd_device *
+dasd_create_device(struct ccw_device *cdev)
+{
+	struct dasd_devmap *devmap;
+	struct dasd_device *device;
+	int rc;
+
+	devmap = dasd_devmap_from_cdev(cdev);
+	if (IS_ERR(devmap))
+		return (void *) devmap;
+	cdev->dev.driver_data = devmap;
+
+	device = dasd_alloc_device();
+	if (IS_ERR(device))
+		return device;
+	atomic_set(&device->ref_count, 2);
+
+	spin_lock(&dasd_devmap_lock);
+	if (!devmap->device) {
+		devmap->device = device;
+		device->devindex = devmap->devindex;
+		if (devmap->features & DASD_FEATURE_READONLY)
+			set_bit(DASD_FLAG_RO, &device->flags);
+		else
+			clear_bit(DASD_FLAG_RO, &device->flags);
+		if (devmap->features & DASD_FEATURE_USEDIAG)
+			set_bit(DASD_FLAG_USE_DIAG, &device->flags);
+		else
+			clear_bit(DASD_FLAG_USE_DIAG, &device->flags);
+		get_device(&cdev->dev);
+		device->cdev = cdev;
+		rc = 0;
+	} else
+		/* Someone else was faster. */
+		rc = -EBUSY;
+	spin_unlock(&dasd_devmap_lock);
+
+	if (rc) {
+		dasd_free_device(device);
+		return ERR_PTR(rc);
+	}
+	return device;
+}
+
+/*
+ * Wait queue for dasd_delete_device waits.
+ */
+static DECLARE_WAIT_QUEUE_HEAD(dasd_delete_wq);
+
+/*
+ * Remove a dasd device structure. The passed referenced
+ * is destroyed.
+ */
+void
+dasd_delete_device(struct dasd_device *device)
+{
+	struct ccw_device *cdev;
+	struct dasd_devmap *devmap;
+
+	/* First remove device pointer from devmap. */
+	devmap = dasd_find_busid(device->cdev->dev.bus_id);
+	if (IS_ERR(devmap))
+		BUG();
+	spin_lock(&dasd_devmap_lock);
+	if (devmap->device != device) {
+		spin_unlock(&dasd_devmap_lock);
+		dasd_put_device(device);
+		return;
+	}
+	devmap->device = NULL;
+	spin_unlock(&dasd_devmap_lock);
+
+	/* Drop ref_count by 2, one for the devmap reference and
+	 * one for the passed reference. */
+	atomic_sub(2, &device->ref_count);
+
+	/* Wait for reference counter to drop to zero. */
+	wait_event(dasd_delete_wq, atomic_read(&device->ref_count) == 0);
+
+	/* Disconnect dasd_device structure from ccw_device structure. */
+	cdev = device->cdev;
+	device->cdev = NULL;
+
+	/* Disconnect dasd_devmap structure from ccw_device structure. */
+	cdev->dev.driver_data = NULL;
+
+	/* Put ccw_device structure. */
+	put_device(&cdev->dev);
+
+	/* Now the device structure can be freed. */
+	dasd_free_device(device);
+}
+
+/*
+ * Reference counter dropped to zero. Wake up waiter
+ * in dasd_delete_device.
+ */
+void
+dasd_put_device_wake(struct dasd_device *device)
+{
+	wake_up(&dasd_delete_wq);
+}
+
+/*
+ * Return dasd_device structure associated with cdev.
+ */
+struct dasd_device *
+dasd_device_from_cdev(struct ccw_device *cdev)
+{
+	struct dasd_devmap *devmap;
+	struct dasd_device *device;
+
+	device = ERR_PTR(-ENODEV);
+	spin_lock(&dasd_devmap_lock);
+	devmap = cdev->dev.driver_data;
+	if (devmap && devmap->device) {
+		device = devmap->device;
+		dasd_get_device(device);
+	}
+	spin_unlock(&dasd_devmap_lock);
+	return device;
+}
+
+/*
+ * SECTION: files in sysfs
+ */
+
+/*
+ * readonly controls the readonly status of a dasd
+ */
+static ssize_t
+dasd_ro_show(struct device *dev, char *buf)
+{
+	struct dasd_devmap *devmap;
+	int ro_flag;
+
+	devmap = dasd_find_busid(dev->bus_id);
+	if (!IS_ERR(devmap))
+		ro_flag = (devmap->features & DASD_FEATURE_READONLY) != 0;
+	else
+		ro_flag = (DASD_FEATURE_DEFAULT & DASD_FEATURE_READONLY) != 0;
+	return snprintf(buf, PAGE_SIZE, ro_flag ? "1\n" : "0\n");
+}
+
+static ssize_t
+dasd_ro_store(struct device *dev, const char *buf, size_t count)
+{
+	struct dasd_devmap *devmap;
+	int ro_flag;
+
+	devmap = dasd_devmap_from_cdev(to_ccwdev(dev));
+	if (IS_ERR(devmap))
+		return PTR_ERR(devmap);
+	ro_flag = buf[0] == '1';
+	spin_lock(&dasd_devmap_lock);
+	if (ro_flag)
+		devmap->features |= DASD_FEATURE_READONLY;
+	else
+		devmap->features &= ~DASD_FEATURE_READONLY;
+	if (devmap->device) {
+		if (devmap->device->gdp)
+			set_disk_ro(devmap->device->gdp, ro_flag);
+		if (ro_flag)
+			set_bit(DASD_FLAG_RO, &devmap->device->flags);
+		else
+			clear_bit(DASD_FLAG_RO, &devmap->device->flags);
+	}
+	spin_unlock(&dasd_devmap_lock);
+	return count;
+}
+
+static DEVICE_ATTR(readonly, 0644, dasd_ro_show, dasd_ro_store);
+
+/*
+ * use_diag controls whether the driver should use diag rather than ssch
+ * to talk to the device
+ */
+static ssize_t 
+dasd_use_diag_show(struct device *dev, char *buf)
+{
+	struct dasd_devmap *devmap;
+	int use_diag;
+
+	devmap = dasd_find_busid(dev->bus_id);
+	if (!IS_ERR(devmap))
+		use_diag = (devmap->features & DASD_FEATURE_USEDIAG) != 0;
+	else
+		use_diag = (DASD_FEATURE_DEFAULT & DASD_FEATURE_USEDIAG) != 0;
+	return sprintf(buf, use_diag ? "1\n" : "0\n");
+}
+
+static ssize_t
+dasd_use_diag_store(struct device *dev, const char *buf, size_t count)
+{
+	struct dasd_devmap *devmap;
+	ssize_t rc;
+	int use_diag;
+
+	devmap = dasd_devmap_from_cdev(to_ccwdev(dev));
+	if (IS_ERR(devmap))
+		return PTR_ERR(devmap);
+	use_diag = buf[0] == '1';
+	spin_lock(&dasd_devmap_lock);
+	/* Changing diag discipline flag is only allowed in offline state. */
+	rc = count;
+	if (!devmap->device) {
+		if (use_diag)
+			devmap->features |= DASD_FEATURE_USEDIAG;
+		else
+			devmap->features &= ~DASD_FEATURE_USEDIAG;
+	} else
+		rc = -EPERM;
+	spin_unlock(&dasd_devmap_lock);
+	return rc;
+}
+
+static
+DEVICE_ATTR(use_diag, 0644, dasd_use_diag_show, dasd_use_diag_store);
+
+static ssize_t
+dasd_discipline_show(struct device *dev, char *buf)
+{
+	struct dasd_devmap *devmap;
+	char *dname;
+
+	spin_lock(&dasd_devmap_lock);
+	dname = "none";
+	devmap = dev->driver_data;
+	if (devmap && devmap->device && devmap->device->discipline)
+		dname = devmap->device->discipline->name;
+	spin_unlock(&dasd_devmap_lock);
+	return snprintf(buf, PAGE_SIZE, "%s\n", dname);
+}
+
+static DEVICE_ATTR(discipline, 0444, dasd_discipline_show, NULL);
+
+static struct attribute * dasd_attrs[] = {
+	&dev_attr_readonly.attr,
+	&dev_attr_discipline.attr,
+	&dev_attr_use_diag.attr,
+	NULL,
+};
+
+static struct attribute_group dasd_attr_group = {
+	.attrs = dasd_attrs,
+};
+
+int
+dasd_add_sysfs_files(struct ccw_device *cdev)
+{
+	return sysfs_create_group(&cdev->dev.kobj, &dasd_attr_group);
+}
+
+void
+dasd_remove_sysfs_files(struct ccw_device *cdev)
+{
+	sysfs_remove_group(&cdev->dev.kobj, &dasd_attr_group);
+}
+
+
+int
+dasd_devmap_init(void)
+{
+	int i;
+
+	/* Initialize devmap structures. */
+	dasd_max_devindex = 0;
+	for (i = 0; i < 256; i++)
+		INIT_LIST_HEAD(&dasd_hashlists[i]);
+	return 0;
+
+}
+
+void
+dasd_devmap_exit(void)
+{
+	dasd_forget_ranges();
+}
diff --git a/drivers/s390/block/dasd_diag.c b/drivers/s390/block/dasd_diag.c
new file mode 100644
index 0000000..1276998
--- /dev/null
+++ b/drivers/s390/block/dasd_diag.c
@@ -0,0 +1,541 @@
+/* 
+ * File...........: linux/drivers/s390/block/dasd_diag.c
+ * Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
+ * Based on.......: linux/drivers/s390/block/mdisk.c
+ * ...............: by Hartmunt Penner <hpenner@de.ibm.com>
+ * Bugreports.to..: <Linux390@de.ibm.com>
+ * (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999,2000
+ *
+ * $Revision: 1.42 $
+ */
+
+#include <linux/config.h>
+#include <linux/stddef.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/hdreg.h>	/* HDIO_GETGEO			    */
+#include <linux/bio.h>
+#include <linux/module.h>
+#include <linux/init.h>
+
+#include <asm/dasd.h>
+#include <asm/debug.h>
+#include <asm/ebcdic.h>
+#include <asm/io.h>
+#include <asm/s390_ext.h>
+#include <asm/todclk.h>
+
+#include "dasd_int.h"
+#include "dasd_diag.h"
+
+#ifdef PRINTK_HEADER
+#undef PRINTK_HEADER
+#endif				/* PRINTK_HEADER */
+#define PRINTK_HEADER "dasd(diag):"
+
+MODULE_LICENSE("GPL");
+
+struct dasd_discipline dasd_diag_discipline;
+
+struct dasd_diag_private {
+	struct dasd_diag_characteristics rdc_data;
+	struct dasd_diag_rw_io iob;
+	struct dasd_diag_init_io iib;
+	unsigned int pt_block;
+};
+
+struct dasd_diag_req {
+	int block_count;
+	struct dasd_diag_bio bio[0];
+};
+
+static __inline__ int
+dia250(void *iob, int cmd)
+{
+	int rc;
+
+	__asm__ __volatile__("    lhi   %0,3\n"
+			     "	  lr	0,%2\n"
+			     "	  diag	0,%1,0x250\n"
+			     "0:  ipm	%0\n"
+			     "	  srl	%0,28\n"
+			     "	  or	%0,1\n"
+			     "1:\n"
+#ifndef CONFIG_ARCH_S390X
+			     ".section __ex_table,\"a\"\n"
+			     "	  .align 4\n"
+			     "	  .long 0b,1b\n"
+			     ".previous\n"
+#else
+			     ".section __ex_table,\"a\"\n"
+			     "	  .align 8\n"
+			     "	  .quad  0b,1b\n"
+			     ".previous\n"
+#endif
+			     : "=&d" (rc)
+			     : "d" (cmd), "d" ((void *) __pa(iob))
+			     : "0", "1", "cc");
+	return rc;
+}
+
+static __inline__ int
+mdsk_init_io(struct dasd_device * device, int blocksize, int offset, int size)
+{
+	struct dasd_diag_private *private;
+	struct dasd_diag_init_io *iib;
+	int rc;
+
+	private = (struct dasd_diag_private *) device->private;
+	iib = &private->iib;
+	memset(iib, 0, sizeof (struct dasd_diag_init_io));
+
+	iib->dev_nr = _ccw_device_get_device_number(device->cdev);
+	iib->block_size = blocksize;
+	iib->offset = offset;
+	iib->start_block = 0;
+	iib->end_block = size;
+
+	rc = dia250(iib, INIT_BIO);
+
+	return rc & 3;
+}
+
+static __inline__ int
+mdsk_term_io(struct dasd_device * device)
+{
+	struct dasd_diag_private *private;
+	struct dasd_diag_init_io *iib;
+	int rc;
+
+	private = (struct dasd_diag_private *) device->private;
+	iib = &private->iib;
+	memset(iib, 0, sizeof (struct dasd_diag_init_io));
+	iib->dev_nr = _ccw_device_get_device_number(device->cdev);
+	rc = dia250(iib, TERM_BIO);
+	return rc & 3;
+}
+
+static int
+dasd_start_diag(struct dasd_ccw_req * cqr)
+{
+	struct dasd_device *device;
+	struct dasd_diag_private *private;
+	struct dasd_diag_req *dreq;
+	int rc;
+
+	device = cqr->device;
+	private = (struct dasd_diag_private *) device->private;
+	dreq = (struct dasd_diag_req *) cqr->data;
+
+	private->iob.dev_nr = _ccw_device_get_device_number(device->cdev);
+	private->iob.key = 0;
+	private->iob.flags = 2;	/* do asynchronous io */
+	private->iob.block_count = dreq->block_count;
+	private->iob.interrupt_params = (u32)(addr_t) cqr;
+	private->iob.bio_list = __pa(dreq->bio);
+
+	cqr->startclk = get_clock();
+
+	rc = dia250(&private->iob, RW_BIO);
+	if (rc > 8) {
+		DEV_MESSAGE(KERN_WARNING, device, "dia250 returned CC %d", rc);
+		cqr->status = DASD_CQR_ERROR;
+	} else if (rc == 0) {
+		cqr->status = DASD_CQR_DONE;
+		dasd_schedule_bh(device);
+	} else {
+		cqr->status = DASD_CQR_IN_IO;
+		rc = 0;
+	}
+	return rc;
+}
+
+static void
+dasd_ext_handler(struct pt_regs *regs, __u16 code)
+{
+	struct dasd_ccw_req *cqr, *next;
+	struct dasd_device *device;
+	unsigned long long expires;
+	unsigned long flags;
+	char status;
+	int ip;
+
+	/*
+	 * Get the external interruption subcode. VM stores
+	 * this in the 'cpu address' field associated with
+	 * the external interrupt. For diag 250 the subcode
+	 * needs to be 3.
+	 */
+	if ((S390_lowcore.cpu_addr & 0xff00) != 0x0300)
+		return;
+	status = *((char *) &S390_lowcore.ext_params + 5);
+	ip = S390_lowcore.ext_params;
+
+	if (!ip) {		/* no intparm: unsolicited interrupt */
+		MESSAGE(KERN_DEBUG, "%s", "caught unsolicited interrupt");
+		return;
+	}
+	cqr = (struct dasd_ccw_req *)(addr_t) ip;
+	device = (struct dasd_device *) cqr->device;
+	if (strncmp(device->discipline->ebcname, (char *) &cqr->magic, 4)) {
+		DEV_MESSAGE(KERN_WARNING, device,
+			    " magic number of dasd_ccw_req 0x%08X doesn't"
+			    " match discipline 0x%08X",
+			    cqr->magic, *(int *) (&device->discipline->name));
+		return;
+	}
+
+	/* get irq lock to modify request queue */
+	spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags);
+
+	cqr->stopclk = get_clock();
+
+	expires = 0;
+	if (status == 0) {
+		cqr->status = DASD_CQR_DONE;
+		/* Start first request on queue if possible -> fast_io. */
+		if (!list_empty(&device->ccw_queue)) {
+			next = list_entry(device->ccw_queue.next,
+					  struct dasd_ccw_req, list);
+			if (next->status == DASD_CQR_QUEUED) {
+				if (dasd_start_diag(next) == 0)
+					expires = next->expires;
+				else
+					DEV_MESSAGE(KERN_WARNING, device, "%s",
+						    "Interrupt fastpath "
+						    "failed!");
+			}
+		}
+	} else 
+		cqr->status = DASD_CQR_FAILED;
+
+	if (expires != 0)
+		dasd_set_timer(device, expires);
+	else
+		dasd_clear_timer(device);
+	dasd_schedule_bh(device);
+
+	spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags);
+}
+
+static int
+dasd_diag_check_device(struct dasd_device *device)
+{
+	struct dasd_diag_private *private;
+	struct dasd_diag_characteristics *rdc_data;
+	struct dasd_diag_bio bio;
+	long *label;
+	int sb, bsize;
+	int rc;
+
+	private = (struct dasd_diag_private *) device->private;
+	if (private == NULL) {
+		private = kmalloc(sizeof(struct dasd_diag_private),GFP_KERNEL);
+		if (private == NULL) {
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				"memory allocation failed for private data");
+			return -ENOMEM;
+		}
+		device->private = (void *) private;
+	}
+	/* Read Device Characteristics */
+	rdc_data = (void *) &(private->rdc_data);
+	rdc_data->dev_nr = _ccw_device_get_device_number(device->cdev);
+	rdc_data->rdc_len = sizeof (struct dasd_diag_characteristics);
+
+	rc = diag210((struct diag210 *) rdc_data);
+	if (rc)
+		return -ENOTSUPP;
+
+	/* Figure out position of label block */
+	switch (private->rdc_data.vdev_class) {
+	case DEV_CLASS_FBA:
+		private->pt_block = 1;
+		break;
+	case DEV_CLASS_ECKD:
+		private->pt_block = 2;
+		break;
+	default:
+		return -ENOTSUPP;
+	}
+
+	DBF_DEV_EVENT(DBF_INFO, device,
+		      "%04X: %04X on real %04X/%02X",
+		      rdc_data->dev_nr,
+		      rdc_data->vdev_type,
+		      rdc_data->rdev_type, rdc_data->rdev_model);
+
+	/* terminate all outstanding operations */
+	mdsk_term_io(device);
+
+	/* figure out blocksize of device */
+	label = (long *) get_zeroed_page(GFP_KERNEL);
+	if (label == NULL)  {
+		DEV_MESSAGE(KERN_WARNING, device, "%s",
+			    "No memory to allocate initialization request");
+		return -ENOMEM;
+	}
+	/* try all sizes - needed for ECKD devices */
+	for (bsize = 512; bsize <= PAGE_SIZE; bsize <<= 1) {
+		mdsk_init_io(device, bsize, 0, 64);
+		memset(&bio, 0, sizeof (struct dasd_diag_bio));
+		bio.type = MDSK_READ_REQ;
+		bio.block_number = private->pt_block + 1;
+		bio.buffer = __pa(label);
+		memset(&private->iob, 0, sizeof (struct dasd_diag_rw_io));
+		private->iob.dev_nr = rdc_data->dev_nr;
+		private->iob.key = 0;
+		private->iob.flags = 0;	/* do synchronous io */
+		private->iob.block_count = 1;
+		private->iob.interrupt_params = 0;
+		private->iob.bio_list = __pa(&bio);
+		if (dia250(&private->iob, RW_BIO) == 0)
+			break;
+		mdsk_term_io(device);
+	}
+	if (bsize <= PAGE_SIZE && label[0] == 0xc3d4e2f1) {
+		/* get formatted blocksize from label block */
+		bsize = (int) label[3];
+		device->blocks = label[7];
+		device->bp_block = bsize;
+		device->s2b_shift = 0;	/* bits to shift 512 to get a block */
+		for (sb = 512; sb < bsize; sb = sb << 1)
+			device->s2b_shift++;
+		
+		DEV_MESSAGE(KERN_INFO, device,
+			    "capacity (%dkB blks): %ldkB",
+			    (device->bp_block >> 10),
+			    (device->blocks << device->s2b_shift) >> 1);
+		rc = 0;
+	} else {
+		if (bsize > PAGE_SIZE)
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "DIAG access failed");
+		else
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "volume is not CMS formatted");
+		rc = -EMEDIUMTYPE;
+	}
+	free_page((long) label);
+	return rc;
+}
+
+static int
+dasd_diag_fill_geometry(struct dasd_device *device, struct hd_geometry *geo)
+{
+	if (dasd_check_blocksize(device->bp_block) != 0)
+		return -EINVAL;
+	geo->cylinders = (device->blocks << device->s2b_shift) >> 10;
+	geo->heads = 16;
+	geo->sectors = 128 >> device->s2b_shift;
+	return 0;
+}
+
+static dasd_era_t
+dasd_diag_examine_error(struct dasd_ccw_req * cqr, struct irb * stat)
+{
+	return dasd_era_fatal;
+}
+
+static dasd_erp_fn_t
+dasd_diag_erp_action(struct dasd_ccw_req * cqr)
+{
+	return dasd_default_erp_action;
+}
+
+static dasd_erp_fn_t
+dasd_diag_erp_postaction(struct dasd_ccw_req * cqr)
+{
+	return dasd_default_erp_postaction;
+}
+
+static struct dasd_ccw_req *
+dasd_diag_build_cp(struct dasd_device * device, struct request *req)
+{
+	struct dasd_ccw_req *cqr;
+	struct dasd_diag_req *dreq;
+	struct dasd_diag_bio *dbio;
+	struct bio *bio;
+	struct bio_vec *bv;
+	char *dst;
+	int count, datasize;
+	sector_t recid, first_rec, last_rec;
+	unsigned blksize, off;
+	unsigned char rw_cmd;
+	int i;
+
+	if (rq_data_dir(req) == READ)
+		rw_cmd = MDSK_READ_REQ;
+	else if (rq_data_dir(req) == WRITE)
+		rw_cmd = MDSK_WRITE_REQ;
+	else
+		return ERR_PTR(-EINVAL);
+	blksize = device->bp_block;
+	/* Calculate record id of first and last block. */
+	first_rec = req->sector >> device->s2b_shift;
+	last_rec = (req->sector + req->nr_sectors - 1) >> device->s2b_shift;
+	/* Check struct bio and count the number of blocks for the request. */
+	count = 0;
+	rq_for_each_bio(bio, req) {
+		bio_for_each_segment(bv, bio, i) {
+			if (bv->bv_len & (blksize - 1))
+				/* Fba can only do full blocks. */
+				return ERR_PTR(-EINVAL);
+			count += bv->bv_len >> (device->s2b_shift + 9);
+		}
+	}
+	/* Paranoia. */
+	if (count != last_rec - first_rec + 1)
+		return ERR_PTR(-EINVAL);
+	/* Build the request */
+	datasize = sizeof(struct dasd_diag_req) +
+		count*sizeof(struct dasd_diag_bio);
+	cqr = dasd_smalloc_request(dasd_diag_discipline.name, 0,
+				   datasize, device);
+	if (IS_ERR(cqr))
+		return cqr;
+	
+	dreq = (struct dasd_diag_req *) cqr->data;
+	dreq->block_count = count;
+	dbio = dreq->bio;
+	recid = first_rec;
+	rq_for_each_bio(bio, req) {
+		bio_for_each_segment(bv, bio, i) {
+			dst = page_address(bv->bv_page) + bv->bv_offset;
+			for (off = 0; off < bv->bv_len; off += blksize) {
+				memset(dbio, 0, sizeof (struct dasd_diag_bio));
+				dbio->type = rw_cmd;
+				dbio->block_number = recid + 1;
+				dbio->buffer = __pa(dst);
+				dbio++;
+				dst += blksize;
+				recid++;
+			}
+		}
+	}
+	cqr->buildclk = get_clock();
+	cqr->device = device;
+	cqr->expires = 50 * HZ;	/* 50 seconds */
+	cqr->status = DASD_CQR_FILLED;
+	return cqr;
+}
+
+static int
+dasd_diag_free_cp(struct dasd_ccw_req *cqr, struct request *req)
+{
+	int status;
+
+	status = cqr->status == DASD_CQR_DONE;
+	dasd_sfree_request(cqr, cqr->device);
+	return status;
+}
+
+static int
+dasd_diag_fill_info(struct dasd_device * device,
+		    struct dasd_information2_t * info)
+{
+	struct dasd_diag_private *private;
+
+	private = (struct dasd_diag_private *) device->private;
+	info->label_block = private->pt_block;
+	info->FBA_layout = 1;
+	info->format = DASD_FORMAT_LDL;
+	info->characteristics_size = sizeof (struct dasd_diag_characteristics);
+	memcpy(info->characteristics,
+	       &((struct dasd_diag_private *) device->private)->rdc_data,
+	       sizeof (struct dasd_diag_characteristics));
+	info->confdata_size = 0;
+	return 0;
+}
+
+static void
+dasd_diag_dump_sense(struct dasd_device *device, struct dasd_ccw_req * req,
+		     struct irb *stat)
+{
+	DEV_MESSAGE(KERN_ERR, device, "%s",
+		    "dump sense not available for DIAG data");
+}
+
+/*
+ * max_blocks is dependent on the amount of storage that is available
+ * in the static io buffer for each device. Currently each device has
+ * 8192 bytes (=2 pages). dasd diag is only relevant for 31 bit.
+ * The struct dasd_ccw_req has 96 bytes, the struct dasd_diag_req has
+ * 8 bytes and the struct dasd_diag_bio for each block has 16 bytes. 
+ * That makes:
+ * (8192 - 96 - 8) / 16 = 505.5 blocks at maximum.
+ * We want to fit two into the available memory so that we can immediately
+ * start the next request if one finishes off. That makes 252.75 blocks
+ * for one request. Give a little safety and the result is 240.
+ */
+struct dasd_discipline dasd_diag_discipline = {
+	.owner = THIS_MODULE,
+	.name = "DIAG",
+	.ebcname = "DIAG",
+	.max_blocks = 240,
+	.check_device = dasd_diag_check_device,
+	.fill_geometry = dasd_diag_fill_geometry,
+	.start_IO = dasd_start_diag,
+	.examine_error = dasd_diag_examine_error,
+	.erp_action = dasd_diag_erp_action,
+	.erp_postaction = dasd_diag_erp_postaction,
+	.build_cp = dasd_diag_build_cp,
+	.free_cp = dasd_diag_free_cp,
+	.dump_sense = dasd_diag_dump_sense,
+	.fill_info = dasd_diag_fill_info,
+};
+
+static int __init
+dasd_diag_init(void)
+{
+	if (!MACHINE_IS_VM) {
+		MESSAGE_LOG(KERN_INFO,
+			    "Machine is not VM: %s "
+			    "discipline not initializing",
+			    dasd_diag_discipline.name);
+		return -EINVAL;
+	}
+	ASCEBC(dasd_diag_discipline.ebcname, 4);
+
+	ctl_set_bit(0, 9);
+	register_external_interrupt(0x2603, dasd_ext_handler);
+	dasd_diag_discipline_pointer = &dasd_diag_discipline;
+	return 0;
+}
+
+static void __exit
+dasd_diag_cleanup(void)
+{
+	if (!MACHINE_IS_VM) {
+		MESSAGE_LOG(KERN_INFO,
+			    "Machine is not VM: %s "
+			    "discipline not cleaned",
+			    dasd_diag_discipline.name);
+		return;
+	}
+	unregister_external_interrupt(0x2603, dasd_ext_handler);
+	ctl_clear_bit(0, 9);
+	dasd_diag_discipline_pointer = NULL;
+}
+
+module_init(dasd_diag_init);
+module_exit(dasd_diag_cleanup);
+
+/*
+ * Overrides for Emacs so that we follow Linus's tabbing style.
+ * Emacs will notice this stuff at the end of the file and automatically
+ * adjust the settings for this buffer only.  This must remain at the end
+ * of the file.
+ * ---------------------------------------------------------------------------
+ * Local variables:
+ * c-indent-level: 4 
+ * c-brace-imaginary-offset: 0
+ * c-brace-offset: -4
+ * c-argdecl-indent: 4
+ * c-label-offset: -4
+ * c-continued-statement-offset: 4
+ * c-continued-brace-offset: 0
+ * indent-tabs-mode: 1
+ * tab-width: 8
+ * End:
+ */
diff --git a/drivers/s390/block/dasd_diag.h b/drivers/s390/block/dasd_diag.h
new file mode 100644
index 0000000..a0c38e3
--- /dev/null
+++ b/drivers/s390/block/dasd_diag.h
@@ -0,0 +1,66 @@
+/* 
+ * File...........: linux/drivers/s390/block/dasd_diag.h
+ * Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
+ * Based on.......: linux/drivers/s390/block/mdisk.h
+ * ...............: by Hartmunt Penner <hpenner@de.ibm.com>
+ * Bugreports.to..: <Linux390@de.ibm.com>
+ * (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999,2000
+ *
+ * $Revision: 1.6 $
+ */
+
+#define MDSK_WRITE_REQ 0x01
+#define MDSK_READ_REQ  0x02
+
+#define INIT_BIO	0x00
+#define RW_BIO		0x01
+#define TERM_BIO	0x02
+
+#define DEV_CLASS_FBA	0x01
+#define DEV_CLASS_ECKD	0x04
+
+struct dasd_diag_characteristics {
+	u16 dev_nr;
+	u16 rdc_len;
+	u8 vdev_class;
+	u8 vdev_type;
+	u8 vdev_status;
+	u8 vdev_flags;
+	u8 rdev_class;
+	u8 rdev_type;
+	u8 rdev_model;
+	u8 rdev_features;
+} __attribute__ ((packed, aligned(4)));
+
+struct dasd_diag_bio {
+	u8 type;
+	u8 status;
+	u16 spare1;
+	u32 block_number;
+	u32 alet;
+	u32 buffer;
+} __attribute__ ((packed, aligned(8)));
+
+struct dasd_diag_init_io {
+	u16 dev_nr;
+	u16 spare1[11];
+	u32 block_size;
+	u32 offset;
+	u32 start_block;
+	u32 end_block;
+	u32 spare2[6];
+} __attribute__ ((packed, aligned(8)));
+
+struct dasd_diag_rw_io {
+	u16 dev_nr;
+	u16 spare1[11];
+	u8 key;
+	u8 flags;
+	u16 spare2;
+	u32 block_count;
+	u32 alet;
+	u32 bio_list;
+	u32 interrupt_params;
+	u32 spare3[5];
+} __attribute__ ((packed, aligned(8)));
+
diff --git a/drivers/s390/block/dasd_eckd.c b/drivers/s390/block/dasd_eckd.c
new file mode 100644
index 0000000..838aedf
--- /dev/null
+++ b/drivers/s390/block/dasd_eckd.c
@@ -0,0 +1,1722 @@
+/* 
+ * File...........: linux/drivers/s390/block/dasd_eckd.c
+ * Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
+ *		    Horst Hummel <Horst.Hummel@de.ibm.com> 
+ *		    Carsten Otte <Cotte@de.ibm.com>
+ *		    Martin Schwidefsky <schwidefsky@de.ibm.com>
+ * Bugreports.to..: <Linux390@de.ibm.com>
+ * (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999,2000
+ *
+ * $Revision: 1.69 $
+ */
+
+#include <linux/config.h>
+#include <linux/stddef.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/hdreg.h>	/* HDIO_GETGEO			    */
+#include <linux/bio.h>
+#include <linux/module.h>
+#include <linux/init.h>
+
+#include <asm/debug.h>
+#include <asm/idals.h>
+#include <asm/ebcdic.h>
+#include <asm/io.h>
+#include <asm/todclk.h>
+#include <asm/uaccess.h>
+#include <asm/ccwdev.h>
+
+#include "dasd_int.h"
+#include "dasd_eckd.h"
+
+#ifdef PRINTK_HEADER
+#undef PRINTK_HEADER
+#endif				/* PRINTK_HEADER */
+#define PRINTK_HEADER "dasd(eckd):"
+
+#define ECKD_C0(i) (i->home_bytes)
+#define ECKD_F(i) (i->formula)
+#define ECKD_F1(i) (ECKD_F(i)==0x01?(i->factors.f_0x01.f1):\
+		    (i->factors.f_0x02.f1))
+#define ECKD_F2(i) (ECKD_F(i)==0x01?(i->factors.f_0x01.f2):\
+		    (i->factors.f_0x02.f2))
+#define ECKD_F3(i) (ECKD_F(i)==0x01?(i->factors.f_0x01.f3):\
+		    (i->factors.f_0x02.f3))
+#define ECKD_F4(i) (ECKD_F(i)==0x02?(i->factors.f_0x02.f4):0)
+#define ECKD_F5(i) (ECKD_F(i)==0x02?(i->factors.f_0x02.f5):0)
+#define ECKD_F6(i) (i->factor6)
+#define ECKD_F7(i) (i->factor7)
+#define ECKD_F8(i) (i->factor8)
+
+MODULE_LICENSE("GPL");
+
+static struct dasd_discipline dasd_eckd_discipline;
+
+struct dasd_eckd_private {
+	struct dasd_eckd_characteristics rdc_data;
+	struct dasd_eckd_confdata conf_data;
+	struct dasd_eckd_path path_data;
+	struct eckd_count count_area[5];
+	int init_cqr_status;
+	int uses_cdl;
+	struct attrib_data_t attrib;	/* e.g. cache operations */
+};
+
+/* The ccw bus type uses this table to find devices that it sends to
+ * dasd_eckd_probe */
+static struct ccw_device_id dasd_eckd_ids[] = {
+	{ CCW_DEVICE_DEVTYPE (0x3990, 0, 0x3390, 0), driver_info: 0x1},
+	{ CCW_DEVICE_DEVTYPE (0x2105, 0, 0x3390, 0), driver_info: 0x2},
+	{ CCW_DEVICE_DEVTYPE (0x3880, 0, 0x3390, 0), driver_info: 0x3},
+	{ CCW_DEVICE_DEVTYPE (0x3990, 0, 0x3380, 0), driver_info: 0x4},
+	{ CCW_DEVICE_DEVTYPE (0x2105, 0, 0x3380, 0), driver_info: 0x5},
+	{ CCW_DEVICE_DEVTYPE (0x9343, 0, 0x9345, 0), driver_info: 0x6},
+	{ CCW_DEVICE_DEVTYPE (0x2107, 0, 0x3390, 0), driver_info: 0x7},
+	{ CCW_DEVICE_DEVTYPE (0x2107, 0, 0x3380, 0), driver_info: 0x8},
+	{ CCW_DEVICE_DEVTYPE (0x1750, 0, 0x3390, 0), driver_info: 0x9},
+	{ CCW_DEVICE_DEVTYPE (0x1750, 0, 0x3380, 0), driver_info: 0xa},
+	{ /* end of list */ },
+};
+
+MODULE_DEVICE_TABLE(ccw, dasd_eckd_ids);
+
+static struct ccw_driver dasd_eckd_driver; /* see below */
+
+/* initial attempt at a probe function. this can be simplified once
+ * the other detection code is gone */
+static int
+dasd_eckd_probe (struct ccw_device *cdev)
+{
+	int ret;
+
+	ret = dasd_generic_probe (cdev, &dasd_eckd_discipline);
+	if (ret)
+		return ret;
+	ccw_device_set_options(cdev, CCWDEV_DO_PATHGROUP | CCWDEV_ALLOW_FORCE);
+	return 0;
+}
+
+static int
+dasd_eckd_set_online(struct ccw_device *cdev)
+{
+	return dasd_generic_set_online (cdev, &dasd_eckd_discipline);
+}
+
+static struct ccw_driver dasd_eckd_driver = {
+	.name        = "dasd-eckd",
+	.owner       = THIS_MODULE,
+	.ids         = dasd_eckd_ids,
+	.probe       = dasd_eckd_probe,
+	.remove      = dasd_generic_remove,
+	.set_offline = dasd_generic_set_offline,
+	.set_online  = dasd_eckd_set_online,
+	.notify      = dasd_generic_notify,
+};
+
+static const int sizes_trk0[] = { 28, 148, 84 };
+#define LABEL_SIZE 140
+
+static inline unsigned int
+round_up_multiple(unsigned int no, unsigned int mult)
+{
+	int rem = no % mult;
+	return (rem ? no - rem + mult : no);
+}
+
+static inline unsigned int
+ceil_quot(unsigned int d1, unsigned int d2)
+{
+	return (d1 + (d2 - 1)) / d2;
+}
+
+static inline int
+bytes_per_record(struct dasd_eckd_characteristics *rdc, int kl, int dl)
+{
+	unsigned int fl1, fl2, int1, int2;
+	int bpr;
+
+	switch (rdc->formula) {
+	case 0x01:
+		fl1 = round_up_multiple(ECKD_F2(rdc) + dl, ECKD_F1(rdc));
+		fl2 = round_up_multiple(kl ? ECKD_F2(rdc) + kl : 0,
+					ECKD_F1(rdc));
+		bpr = fl1 + fl2;
+		break;
+	case 0x02:
+		int1 = ceil_quot(dl + ECKD_F6(rdc), ECKD_F5(rdc) << 1);
+		int2 = ceil_quot(kl + ECKD_F6(rdc), ECKD_F5(rdc) << 1);
+		fl1 = round_up_multiple(ECKD_F1(rdc) * ECKD_F2(rdc) + dl +
+					ECKD_F6(rdc) + ECKD_F4(rdc) * int1,
+					ECKD_F1(rdc));
+		fl2 = round_up_multiple(ECKD_F1(rdc) * ECKD_F3(rdc) + kl +
+					ECKD_F6(rdc) + ECKD_F4(rdc) * int2,
+					ECKD_F1(rdc));
+		bpr = fl1 + fl2;
+		break;
+	default:
+		bpr = 0;
+		break;
+	}
+	return bpr;
+}
+
+static inline unsigned int
+bytes_per_track(struct dasd_eckd_characteristics *rdc)
+{
+	return *(unsigned int *) (rdc->byte_per_track) >> 8;
+}
+
+static inline unsigned int
+recs_per_track(struct dasd_eckd_characteristics * rdc,
+	       unsigned int kl, unsigned int dl)
+{
+	int dn, kn;
+
+	switch (rdc->dev_type) {
+	case 0x3380:
+		if (kl)
+			return 1499 / (15 + 7 + ceil_quot(kl + 12, 32) +
+				       ceil_quot(dl + 12, 32));
+		else
+			return 1499 / (15 + ceil_quot(dl + 12, 32));
+	case 0x3390:
+		dn = ceil_quot(dl + 6, 232) + 1;
+		if (kl) {
+			kn = ceil_quot(kl + 6, 232) + 1;
+			return 1729 / (10 + 9 + ceil_quot(kl + 6 * kn, 34) +
+				       9 + ceil_quot(dl + 6 * dn, 34));
+		} else
+			return 1729 / (10 + 9 + ceil_quot(dl + 6 * dn, 34));
+	case 0x9345:
+		dn = ceil_quot(dl + 6, 232) + 1;
+		if (kl) {
+			kn = ceil_quot(kl + 6, 232) + 1;
+			return 1420 / (18 + 7 + ceil_quot(kl + 6 * kn, 34) +
+				       ceil_quot(dl + 6 * dn, 34));
+		} else
+			return 1420 / (18 + 7 + ceil_quot(dl + 6 * dn, 34));
+	}
+	return 0;
+}
+
+static inline void
+check_XRC (struct ccw1         *de_ccw,
+           struct DE_eckd_data *data,
+           struct dasd_device  *device)
+{
+        struct dasd_eckd_private *private;
+
+        private = (struct dasd_eckd_private *) device->private;
+
+        /* switch on System Time Stamp - needed for XRC Support */
+        if (private->rdc_data.facilities.XRC_supported) {
+                
+                data->ga_extended |= 0x08; /* switch on 'Time Stamp Valid'   */
+                data->ga_extended |= 0x02; /* switch on 'Extended Parameter' */
+                
+                data->ep_sys_time = get_clock ();
+                
+                de_ccw->count = sizeof (struct DE_eckd_data);
+                de_ccw->flags |= CCW_FLAG_SLI;  
+        }
+
+        return;
+
+} /* end check_XRC */
+
+static inline void
+define_extent(struct ccw1 * ccw, struct DE_eckd_data * data, int trk,
+	      int totrk, int cmd, struct dasd_device * device)
+{
+	struct dasd_eckd_private *private;
+	struct ch_t geo, beg, end;
+
+	private = (struct dasd_eckd_private *) device->private;
+
+	ccw->cmd_code = DASD_ECKD_CCW_DEFINE_EXTENT;
+	ccw->flags = 0;
+	ccw->count = 16;
+	ccw->cda = (__u32) __pa(data);
+
+	memset(data, 0, sizeof (struct DE_eckd_data));
+	switch (cmd) {
+	case DASD_ECKD_CCW_READ_HOME_ADDRESS:
+	case DASD_ECKD_CCW_READ_RECORD_ZERO:
+	case DASD_ECKD_CCW_READ:
+	case DASD_ECKD_CCW_READ_MT:
+	case DASD_ECKD_CCW_READ_CKD:
+	case DASD_ECKD_CCW_READ_CKD_MT:
+	case DASD_ECKD_CCW_READ_KD:
+	case DASD_ECKD_CCW_READ_KD_MT:
+	case DASD_ECKD_CCW_READ_COUNT:
+		data->mask.perm = 0x1;
+		data->attributes.operation = private->attrib.operation;
+		break;
+	case DASD_ECKD_CCW_WRITE:
+	case DASD_ECKD_CCW_WRITE_MT:
+	case DASD_ECKD_CCW_WRITE_KD:
+	case DASD_ECKD_CCW_WRITE_KD_MT:
+		data->mask.perm = 0x02;
+		data->attributes.operation = private->attrib.operation;
+                check_XRC (ccw, data, device);
+		break;
+	case DASD_ECKD_CCW_WRITE_CKD:
+	case DASD_ECKD_CCW_WRITE_CKD_MT:
+		data->attributes.operation = DASD_BYPASS_CACHE;
+                check_XRC (ccw, data, device);
+		break;
+	case DASD_ECKD_CCW_ERASE:
+	case DASD_ECKD_CCW_WRITE_HOME_ADDRESS:
+	case DASD_ECKD_CCW_WRITE_RECORD_ZERO:
+		data->mask.perm = 0x3;
+		data->mask.auth = 0x1;
+		data->attributes.operation = DASD_BYPASS_CACHE;
+                check_XRC (ccw, data, device);
+		break;
+	default:
+		DEV_MESSAGE(KERN_ERR, device, "unknown opcode 0x%x", cmd);
+		break;
+	}
+
+	data->attributes.mode = 0x3;	/* ECKD */
+
+	if ((private->rdc_data.cu_type == 0x2105 ||
+	     private->rdc_data.cu_type == 0x2107 ||
+	     private->rdc_data.cu_type == 0x1750)
+	    && !(private->uses_cdl && trk < 2))
+		data->ga_extended |= 0x40; /* Regular Data Format Mode */
+
+	geo.cyl = private->rdc_data.no_cyl;
+	geo.head = private->rdc_data.trk_per_cyl;
+	beg.cyl = trk / geo.head;
+	beg.head = trk % geo.head;
+	end.cyl = totrk / geo.head;
+	end.head = totrk % geo.head;
+
+	/* check for sequential prestage - enhance cylinder range */
+	if (data->attributes.operation == DASD_SEQ_PRESTAGE ||
+	    data->attributes.operation == DASD_SEQ_ACCESS) {
+		
+		if (end.cyl + private->attrib.nr_cyl < geo.cyl) 
+			end.cyl += private->attrib.nr_cyl;
+		else
+			end.cyl = (geo.cyl - 1);
+	}
+
+	data->beg_ext.cyl = beg.cyl;
+	data->beg_ext.head = beg.head;
+	data->end_ext.cyl = end.cyl;
+	data->end_ext.head = end.head;
+}
+
+static inline void
+locate_record(struct ccw1 *ccw, struct LO_eckd_data *data, int trk,
+	      int rec_on_trk, int no_rec, int cmd,
+	      struct dasd_device * device, int reclen)
+{
+	struct dasd_eckd_private *private;
+	int sector;
+	int dn, d;
+				
+	private = (struct dasd_eckd_private *) device->private;
+
+	DBF_DEV_EVENT(DBF_INFO, device,
+		  "Locate: trk %d, rec %d, no_rec %d, cmd %d, reclen %d",
+		  trk, rec_on_trk, no_rec, cmd, reclen);
+
+	ccw->cmd_code = DASD_ECKD_CCW_LOCATE_RECORD;
+	ccw->flags = 0;
+	ccw->count = 16;
+	ccw->cda = (__u32) __pa(data);
+
+	memset(data, 0, sizeof (struct LO_eckd_data));
+	sector = 0;
+	if (rec_on_trk) {
+		switch (private->rdc_data.dev_type) {
+		case 0x3390:
+			dn = ceil_quot(reclen + 6, 232);
+			d = 9 + ceil_quot(reclen + 6 * (dn + 1), 34);
+			sector = (49 + (rec_on_trk - 1) * (10 + d)) / 8;
+			break;
+		case 0x3380:
+			d = 7 + ceil_quot(reclen + 12, 32);
+			sector = (39 + (rec_on_trk - 1) * (8 + d)) / 7;
+			break;
+		}
+	}
+	data->sector = sector;
+	data->count = no_rec;
+	switch (cmd) {
+	case DASD_ECKD_CCW_WRITE_HOME_ADDRESS:
+		data->operation.orientation = 0x3;
+		data->operation.operation = 0x03;
+		break;
+	case DASD_ECKD_CCW_READ_HOME_ADDRESS:
+		data->operation.orientation = 0x3;
+		data->operation.operation = 0x16;
+		break;
+	case DASD_ECKD_CCW_WRITE_RECORD_ZERO:
+		data->operation.orientation = 0x1;
+		data->operation.operation = 0x03;
+		data->count++;
+		break;
+	case DASD_ECKD_CCW_READ_RECORD_ZERO:
+		data->operation.orientation = 0x3;
+		data->operation.operation = 0x16;
+		data->count++;
+		break;
+	case DASD_ECKD_CCW_WRITE:
+	case DASD_ECKD_CCW_WRITE_MT:
+	case DASD_ECKD_CCW_WRITE_KD:
+	case DASD_ECKD_CCW_WRITE_KD_MT:
+		data->auxiliary.last_bytes_used = 0x1;
+		data->length = reclen;
+		data->operation.operation = 0x01;
+		break;
+	case DASD_ECKD_CCW_WRITE_CKD:
+	case DASD_ECKD_CCW_WRITE_CKD_MT:
+		data->auxiliary.last_bytes_used = 0x1;
+		data->length = reclen;
+		data->operation.operation = 0x03;
+		break;
+	case DASD_ECKD_CCW_READ:
+	case DASD_ECKD_CCW_READ_MT:
+	case DASD_ECKD_CCW_READ_KD:
+	case DASD_ECKD_CCW_READ_KD_MT:
+		data->auxiliary.last_bytes_used = 0x1;
+		data->length = reclen;
+		data->operation.operation = 0x06;
+		break;
+	case DASD_ECKD_CCW_READ_CKD:
+	case DASD_ECKD_CCW_READ_CKD_MT:
+		data->auxiliary.last_bytes_used = 0x1;
+		data->length = reclen;
+		data->operation.operation = 0x16;
+		break;
+	case DASD_ECKD_CCW_READ_COUNT:
+		data->operation.operation = 0x06;
+		break;
+	case DASD_ECKD_CCW_ERASE:
+		data->length = reclen;
+		data->auxiliary.last_bytes_used = 0x1;
+		data->operation.operation = 0x0b;
+		break;
+	default:
+		DEV_MESSAGE(KERN_ERR, device, "unknown opcode 0x%x", cmd);
+	}
+	data->seek_addr.cyl = data->search_arg.cyl =
+		trk / private->rdc_data.trk_per_cyl;
+	data->seek_addr.head = data->search_arg.head =
+		trk % private->rdc_data.trk_per_cyl;
+	data->search_arg.record = rec_on_trk;
+}
+
+/*
+ * Returns 1 if the block is one of the special blocks that needs
+ * to get read/written with the KD variant of the command.
+ * That is DASD_ECKD_READ_KD_MT instead of DASD_ECKD_READ_MT and
+ * DASD_ECKD_WRITE_KD_MT instead of DASD_ECKD_WRITE_MT.
+ * Luckily the KD variants differ only by one bit (0x08) from the
+ * normal variant. So don't wonder about code like:
+ * if (dasd_eckd_cdl_special(blk_per_trk, recid))
+ *         ccw->cmd_code |= 0x8;
+ */
+static inline int
+dasd_eckd_cdl_special(int blk_per_trk, int recid)
+{
+	if (recid < 3)
+		return 1;
+	if (recid < blk_per_trk)
+		return 0;
+	if (recid < 2 * blk_per_trk)
+		return 1;
+	return 0;
+}
+
+/*
+ * Returns the record size for the special blocks of the cdl format.
+ * Only returns something useful if dasd_eckd_cdl_special is true
+ * for the recid.
+ */
+static inline int
+dasd_eckd_cdl_reclen(int recid)
+{
+	if (recid < 3)
+		return sizes_trk0[recid];
+	return LABEL_SIZE;
+}
+
+static int
+dasd_eckd_read_conf(struct dasd_device *device)
+{
+	void *conf_data;
+	int conf_len, conf_data_saved;
+	int rc;
+	__u8 lpm;
+	struct dasd_eckd_private *private;
+	struct dasd_eckd_path *path_data;
+
+	private = (struct dasd_eckd_private *) device->private;
+	path_data = (struct dasd_eckd_path *) &private->path_data;
+	path_data->opm = ccw_device_get_path_mask(device->cdev);
+	lpm = 0x80;
+	conf_data_saved = 0;
+
+	/* get configuration data per operational path */
+	for (lpm = 0x80; lpm; lpm>>= 1) {
+		if (lpm & path_data->opm){
+			rc = read_conf_data_lpm(device->cdev, &conf_data,
+						&conf_len, lpm);
+			if (rc && rc != -EOPNOTSUPP) {	/* -EOPNOTSUPP is ok */
+				MESSAGE(KERN_WARNING,
+					"Read configuration data returned "
+					"error %d", rc);
+				return rc;
+			}
+			if (conf_data == NULL) {
+				MESSAGE(KERN_WARNING, "%s", "No configuration "
+					"data retrieved");
+				continue;	/* no errror */
+			}
+			if (conf_len != sizeof (struct dasd_eckd_confdata)) {
+				MESSAGE(KERN_WARNING,
+					"sizes of configuration data mismatch"
+					"%d (read) vs %ld (expected)",
+					conf_len,
+					sizeof (struct dasd_eckd_confdata));
+				kfree(conf_data);
+				continue;	/* no errror */
+			}
+			/* save first valid configuration data */
+			if (!conf_data_saved){
+				memcpy(&private->conf_data, conf_data,
+				       sizeof (struct dasd_eckd_confdata));
+				conf_data_saved++;
+			}
+			switch (((char *)conf_data)[242] & 0x07){
+			case 0x02:
+				path_data->npm |= lpm;
+				break;
+			case 0x03:
+				path_data->ppm |= lpm;
+				break;
+			}
+			kfree(conf_data);
+		}
+	}
+	return 0;
+}
+
+
+static int
+dasd_eckd_check_characteristics(struct dasd_device *device)
+{
+	struct dasd_eckd_private *private;
+	void *rdc_data;
+	int rc;
+
+	private = (struct dasd_eckd_private *) device->private;
+	if (private == NULL) {
+		private = kmalloc(sizeof(struct dasd_eckd_private),
+				  GFP_KERNEL | GFP_DMA);
+		if (private == NULL) {
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "memory allocation failed for private "
+				    "data");
+			return -ENOMEM;
+		}
+		memset(private, 0, sizeof(struct dasd_eckd_private));
+		device->private = (void *) private;
+	}
+	/* Invalidate status of initial analysis. */
+	private->init_cqr_status = -1;
+	/* Set default cache operations. */
+	private->attrib.operation = DASD_NORMAL_CACHE;
+	private->attrib.nr_cyl = 0;
+
+	/* Read Device Characteristics */
+	rdc_data = (void *) &(private->rdc_data);
+	rc = read_dev_chars(device->cdev, &rdc_data, 64);
+	if (rc) {
+		DEV_MESSAGE(KERN_WARNING, device,
+			    "Read device characteristics returned error %d",
+			    rc);
+		return rc;
+	}
+
+	DEV_MESSAGE(KERN_INFO, device,
+		    "%04X/%02X(CU:%04X/%02X) Cyl:%d Head:%d Sec:%d",
+		    private->rdc_data.dev_type,
+		    private->rdc_data.dev_model,
+		    private->rdc_data.cu_type,
+		    private->rdc_data.cu_model.model,
+		    private->rdc_data.no_cyl,
+		    private->rdc_data.trk_per_cyl,
+		    private->rdc_data.sec_per_trk);
+
+	/* Read Configuration Data */
+	rc = dasd_eckd_read_conf (device);
+	return rc;
+
+}
+
+static struct dasd_ccw_req *
+dasd_eckd_analysis_ccw(struct dasd_device *device)
+{
+	struct dasd_eckd_private *private;
+	struct eckd_count *count_data;
+	struct LO_eckd_data *LO_data;
+	struct dasd_ccw_req *cqr;
+	struct ccw1 *ccw;
+	int cplength, datasize;
+	int i;
+
+	private = (struct dasd_eckd_private *) device->private;
+
+	cplength = 8;
+	datasize = sizeof(struct DE_eckd_data) + 2*sizeof(struct LO_eckd_data);
+	cqr = dasd_smalloc_request(dasd_eckd_discipline.name,
+				   cplength, datasize, device);
+	if (IS_ERR(cqr))
+		return cqr;
+	ccw = cqr->cpaddr;
+	/* Define extent for the first 3 tracks. */
+	define_extent(ccw++, cqr->data, 0, 2,
+		      DASD_ECKD_CCW_READ_COUNT, device);
+	LO_data = cqr->data + sizeof (struct DE_eckd_data);
+	/* Locate record for the first 4 records on track 0. */
+	ccw[-1].flags |= CCW_FLAG_CC;
+	locate_record(ccw++, LO_data++, 0, 0, 4,
+		      DASD_ECKD_CCW_READ_COUNT, device, 0);
+
+	count_data = private->count_area;
+	for (i = 0; i < 4; i++) {
+		ccw[-1].flags |= CCW_FLAG_CC;
+		ccw->cmd_code = DASD_ECKD_CCW_READ_COUNT;
+		ccw->flags = 0;
+		ccw->count = 8;
+		ccw->cda = (__u32)(addr_t) count_data;
+		ccw++;
+		count_data++;
+	}
+
+	/* Locate record for the first record on track 2. */
+	ccw[-1].flags |= CCW_FLAG_CC;
+	locate_record(ccw++, LO_data++, 2, 0, 1,
+		      DASD_ECKD_CCW_READ_COUNT, device, 0);
+	/* Read count ccw. */
+	ccw[-1].flags |= CCW_FLAG_CC;
+	ccw->cmd_code = DASD_ECKD_CCW_READ_COUNT;
+	ccw->flags = 0;
+	ccw->count = 8;
+	ccw->cda = (__u32)(addr_t) count_data;
+
+	cqr->device = device;
+	cqr->retries = 0;
+	cqr->buildclk = get_clock();
+	cqr->status = DASD_CQR_FILLED;
+	return cqr;
+}
+
+/*
+ * This is the callback function for the init_analysis cqr. It saves
+ * the status of the initial analysis ccw before it frees it and kicks
+ * the device to continue the startup sequence. This will call
+ * dasd_eckd_do_analysis again (if the devices has not been marked
+ * for deletion in the meantime).
+ */
+static void
+dasd_eckd_analysis_callback(struct dasd_ccw_req *init_cqr, void *data)
+{
+	struct dasd_eckd_private *private;
+	struct dasd_device *device;
+
+	device = init_cqr->device;
+	private = (struct dasd_eckd_private *) device->private;
+	private->init_cqr_status = init_cqr->status;
+	dasd_sfree_request(init_cqr, device);
+	dasd_kick_device(device);
+}
+
+static int
+dasd_eckd_start_analysis(struct dasd_device *device)
+{
+	struct dasd_eckd_private *private;
+	struct dasd_ccw_req *init_cqr;
+
+	private = (struct dasd_eckd_private *) device->private;
+	init_cqr = dasd_eckd_analysis_ccw(device);
+	if (IS_ERR(init_cqr))
+		return PTR_ERR(init_cqr);
+	init_cqr->callback = dasd_eckd_analysis_callback;
+	init_cqr->callback_data = NULL;
+	init_cqr->expires = 5*HZ;
+	dasd_add_request_head(init_cqr);
+	return -EAGAIN;
+}
+
+static int
+dasd_eckd_end_analysis(struct dasd_device *device)
+{
+	struct dasd_eckd_private *private;
+	struct eckd_count *count_area;
+	unsigned int sb, blk_per_trk;
+	int status, i;
+
+	private = (struct dasd_eckd_private *) device->private;
+	status = private->init_cqr_status;
+	private->init_cqr_status = -1;
+	if (status != DASD_CQR_DONE) {
+		DEV_MESSAGE(KERN_WARNING, device, "%s",
+			    "volume analysis returned unformatted disk");
+		return -EMEDIUMTYPE;
+	}
+
+	private->uses_cdl = 1;
+	/* Calculate number of blocks/records per track. */
+	blk_per_trk = recs_per_track(&private->rdc_data, 0, device->bp_block);
+	/* Check Track 0 for Compatible Disk Layout */
+	count_area = NULL;
+	for (i = 0; i < 3; i++) {
+		if (private->count_area[i].kl != 4 ||
+		    private->count_area[i].dl != dasd_eckd_cdl_reclen(i) - 4) {
+			private->uses_cdl = 0;
+			break;
+		}
+	}
+	if (i == 3)
+		count_area = &private->count_area[4];
+
+	if (private->uses_cdl == 0) {
+		for (i = 0; i < 5; i++) {
+			if ((private->count_area[i].kl != 0) ||
+			    (private->count_area[i].dl !=
+			     private->count_area[0].dl))
+				break;
+		}
+		if (i == 5)
+			count_area = &private->count_area[0];
+	} else {
+		if (private->count_area[3].record == 1)
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "Trk 0: no records after VTOC!");
+	}
+	if (count_area != NULL && count_area->kl == 0) {
+		/* we found notthing violating our disk layout */
+		if (dasd_check_blocksize(count_area->dl) == 0)
+			device->bp_block = count_area->dl;
+	}
+	if (device->bp_block == 0) {
+		DEV_MESSAGE(KERN_WARNING, device, "%s",
+			    "Volume has incompatible disk layout");
+		return -EMEDIUMTYPE;
+	}
+	device->s2b_shift = 0;	/* bits to shift 512 to get a block */
+	for (sb = 512; sb < device->bp_block; sb = sb << 1)
+		device->s2b_shift++;
+
+	blk_per_trk = recs_per_track(&private->rdc_data, 0, device->bp_block);
+	device->blocks = (private->rdc_data.no_cyl *
+			  private->rdc_data.trk_per_cyl *
+			  blk_per_trk);
+
+	DEV_MESSAGE(KERN_INFO, device,
+		    "(%dkB blks): %dkB at %dkB/trk %s",
+		    (device->bp_block >> 10),
+		    ((private->rdc_data.no_cyl *
+		      private->rdc_data.trk_per_cyl *
+		      blk_per_trk * (device->bp_block >> 9)) >> 1),
+		    ((blk_per_trk * device->bp_block) >> 10), 
+		    private->uses_cdl ?
+		    "compatible disk layout" : "linux disk layout");
+
+	return 0;
+}
+
+static int
+dasd_eckd_do_analysis(struct dasd_device *device)
+{
+	struct dasd_eckd_private *private;
+
+	private = (struct dasd_eckd_private *) device->private;
+	if (private->init_cqr_status < 0)
+		return dasd_eckd_start_analysis(device);
+	else
+		return dasd_eckd_end_analysis(device);
+}
+
+static int
+dasd_eckd_fill_geometry(struct dasd_device *device, struct hd_geometry *geo)
+{
+	struct dasd_eckd_private *private;
+
+	private = (struct dasd_eckd_private *) device->private;
+	if (dasd_check_blocksize(device->bp_block) == 0) {
+		geo->sectors = recs_per_track(&private->rdc_data,
+					      0, device->bp_block);
+	}
+	geo->cylinders = private->rdc_data.no_cyl;
+	geo->heads = private->rdc_data.trk_per_cyl;
+	return 0;
+}
+
+static struct dasd_ccw_req *
+dasd_eckd_format_device(struct dasd_device * device,
+			struct format_data_t * fdata)
+{
+	struct dasd_eckd_private *private;
+	struct dasd_ccw_req *fcp;
+	struct eckd_count *ect;
+	struct ccw1 *ccw;
+	void *data;
+	int rpt, cyl, head;
+	int cplength, datasize;
+	int i;
+
+	private = (struct dasd_eckd_private *) device->private;
+	rpt = recs_per_track(&private->rdc_data, 0, fdata->blksize);
+	cyl = fdata->start_unit / private->rdc_data.trk_per_cyl;
+	head = fdata->start_unit % private->rdc_data.trk_per_cyl;
+
+	/* Sanity checks. */
+	if (fdata->start_unit >=
+	    (private->rdc_data.no_cyl * private->rdc_data.trk_per_cyl)) {
+		DEV_MESSAGE(KERN_INFO, device, "Track no %d too big!",
+			    fdata->start_unit);
+		return ERR_PTR(-EINVAL);
+	}
+	if (fdata->start_unit > fdata->stop_unit) {
+		DEV_MESSAGE(KERN_INFO, device, "Track %d reached! ending.",
+			    fdata->start_unit);
+		return ERR_PTR(-EINVAL);
+	}
+	if (dasd_check_blocksize(fdata->blksize) != 0) {
+		DEV_MESSAGE(KERN_WARNING, device,
+			    "Invalid blocksize %d...terminating!",
+			    fdata->blksize);
+		return ERR_PTR(-EINVAL);
+	}
+
+	/*
+	 * fdata->intensity is a bit string that tells us what to do:
+	 *   Bit 0: write record zero
+	 *   Bit 1: write home address, currently not supported
+	 *   Bit 2: invalidate tracks
+	 *   Bit 3: use OS/390 compatible disk layout (cdl)
+	 * Only some bit combinations do make sense.
+	 */
+	switch (fdata->intensity) {
+	case 0x00:	/* Normal format */
+	case 0x08:	/* Normal format, use cdl. */
+		cplength = 2 + rpt;
+		datasize = sizeof(struct DE_eckd_data) +
+			sizeof(struct LO_eckd_data) +
+			rpt * sizeof(struct eckd_count);
+		break;
+	case 0x01:	/* Write record zero and format track. */
+	case 0x09:	/* Write record zero and format track, use cdl. */
+		cplength = 3 + rpt;
+		datasize = sizeof(struct DE_eckd_data) +
+			sizeof(struct LO_eckd_data) +
+			sizeof(struct eckd_count) +
+			rpt * sizeof(struct eckd_count);
+		break;
+	case 0x04:	/* Invalidate track. */
+	case 0x0c:	/* Invalidate track, use cdl. */
+		cplength = 3;
+		datasize = sizeof(struct DE_eckd_data) +
+			sizeof(struct LO_eckd_data) +
+			sizeof(struct eckd_count);
+		break;
+	default:
+		DEV_MESSAGE(KERN_WARNING, device, "Invalid flags 0x%x.",
+			    fdata->intensity);
+		return ERR_PTR(-EINVAL);
+	}
+	/* Allocate the format ccw request. */
+	fcp = dasd_smalloc_request(dasd_eckd_discipline.name,
+				   cplength, datasize, device);
+	if (IS_ERR(fcp))
+		return fcp;
+
+	data = fcp->data;
+	ccw = fcp->cpaddr;
+
+	switch (fdata->intensity & ~0x08) {
+	case 0x00: /* Normal format. */
+		define_extent(ccw++, (struct DE_eckd_data *) data,
+			      fdata->start_unit, fdata->start_unit,
+			      DASD_ECKD_CCW_WRITE_CKD, device);
+		data += sizeof(struct DE_eckd_data);
+		ccw[-1].flags |= CCW_FLAG_CC;
+		locate_record(ccw++, (struct LO_eckd_data *) data,
+			      fdata->start_unit, 0, rpt,
+			      DASD_ECKD_CCW_WRITE_CKD, device,
+			      fdata->blksize);
+		data += sizeof(struct LO_eckd_data);
+		break;
+	case 0x01: /* Write record zero + format track. */
+		define_extent(ccw++, (struct DE_eckd_data *) data,
+			      fdata->start_unit, fdata->start_unit,
+			      DASD_ECKD_CCW_WRITE_RECORD_ZERO,
+			      device);
+		data += sizeof(struct DE_eckd_data);
+		ccw[-1].flags |= CCW_FLAG_CC;
+		locate_record(ccw++, (struct LO_eckd_data *) data,
+			      fdata->start_unit, 0, rpt + 1,
+			      DASD_ECKD_CCW_WRITE_RECORD_ZERO, device,
+			      device->bp_block);
+		data += sizeof(struct LO_eckd_data);
+		break;
+	case 0x04: /* Invalidate track. */
+		define_extent(ccw++, (struct DE_eckd_data *) data,
+			      fdata->start_unit, fdata->start_unit,
+			      DASD_ECKD_CCW_WRITE_CKD, device);
+		data += sizeof(struct DE_eckd_data);
+		ccw[-1].flags |= CCW_FLAG_CC;
+		locate_record(ccw++, (struct LO_eckd_data *) data,
+			      fdata->start_unit, 0, 1,
+			      DASD_ECKD_CCW_WRITE_CKD, device, 8);
+		data += sizeof(struct LO_eckd_data);
+		break;
+	}
+	if (fdata->intensity & 0x01) {	/* write record zero */
+		ect = (struct eckd_count *) data;
+		data += sizeof(struct eckd_count);
+		ect->cyl = cyl;
+		ect->head = head;
+		ect->record = 0;
+		ect->kl = 0;
+		ect->dl = 8;
+		ccw[-1].flags |= CCW_FLAG_CC;
+		ccw->cmd_code = DASD_ECKD_CCW_WRITE_RECORD_ZERO;
+		ccw->flags = CCW_FLAG_SLI;
+		ccw->count = 8;
+		ccw->cda = (__u32)(addr_t) ect;
+		ccw++;
+	}
+	if ((fdata->intensity & ~0x08) & 0x04) {	/* erase track */
+		ect = (struct eckd_count *) data;
+		data += sizeof(struct eckd_count);
+		ect->cyl = cyl;
+		ect->head = head;
+		ect->record = 1;
+		ect->kl = 0;
+		ect->dl = 0;
+		ccw[-1].flags |= CCW_FLAG_CC;
+		ccw->cmd_code = DASD_ECKD_CCW_WRITE_CKD;
+		ccw->flags = CCW_FLAG_SLI;
+		ccw->count = 8;
+		ccw->cda = (__u32)(addr_t) ect;
+	} else {		/* write remaining records */
+		for (i = 0; i < rpt; i++) {
+			ect = (struct eckd_count *) data;
+			data += sizeof(struct eckd_count);
+			ect->cyl = cyl;
+			ect->head = head;
+			ect->record = i + 1;
+			ect->kl = 0;
+			ect->dl = fdata->blksize;
+			/* Check for special tracks 0-1 when formatting CDL */
+			if ((fdata->intensity & 0x08) &&
+			    fdata->start_unit == 0) {
+				if (i < 3) {
+					ect->kl = 4;
+					ect->dl = sizes_trk0[i] - 4;
+				} 
+			}
+			if ((fdata->intensity & 0x08) &&
+			    fdata->start_unit == 1) {
+				ect->kl = 44;
+				ect->dl = LABEL_SIZE - 44;
+			}
+			ccw[-1].flags |= CCW_FLAG_CC;
+			ccw->cmd_code = DASD_ECKD_CCW_WRITE_CKD;
+			ccw->flags = CCW_FLAG_SLI;
+			ccw->count = 8;
+			ccw->cda = (__u32)(addr_t) ect;
+			ccw++;
+		}
+	}
+	fcp->device = device;
+	fcp->retries = 2;	/* set retry counter to enable ERP */
+	fcp->buildclk = get_clock();
+	fcp->status = DASD_CQR_FILLED;
+	return fcp;
+}
+
+static dasd_era_t
+dasd_eckd_examine_error(struct dasd_ccw_req * cqr, struct irb * irb)
+{
+	struct dasd_device *device = (struct dasd_device *) cqr->device;
+	struct ccw_device *cdev = device->cdev;
+
+	if (irb->scsw.cstat == 0x00 &&
+	    irb->scsw.dstat == (DEV_STAT_CHN_END | DEV_STAT_DEV_END))
+		return dasd_era_none;
+
+	switch (cdev->id.cu_type) {
+	case 0x3990:
+	case 0x2105:
+	case 0x2107:
+	case 0x1750:
+		return dasd_3990_erp_examine(cqr, irb);
+	case 0x9343:
+		return dasd_9343_erp_examine(cqr, irb);
+	case 0x3880:
+	default:
+		DEV_MESSAGE(KERN_WARNING, device, "%s",
+			    "default (unknown CU type) - RECOVERABLE return");
+		return dasd_era_recover;
+	}
+}
+
+static dasd_erp_fn_t
+dasd_eckd_erp_action(struct dasd_ccw_req * cqr)
+{
+	struct dasd_device *device = (struct dasd_device *) cqr->device;
+	struct ccw_device *cdev = device->cdev;
+
+	switch (cdev->id.cu_type) {
+	case 0x3990:
+	case 0x2105:
+	case 0x2107:
+	case 0x1750:
+		return dasd_3990_erp_action;
+	case 0x9343:
+	case 0x3880:
+	default:
+		return dasd_default_erp_action;
+	}
+}
+
+static dasd_erp_fn_t
+dasd_eckd_erp_postaction(struct dasd_ccw_req * cqr)
+{
+	return dasd_default_erp_postaction;
+}
+
+static struct dasd_ccw_req *
+dasd_eckd_build_cp(struct dasd_device * device, struct request *req)
+{
+	struct dasd_eckd_private *private;
+	unsigned long *idaws;
+	struct LO_eckd_data *LO_data;
+	struct dasd_ccw_req *cqr;
+	struct ccw1 *ccw;
+	struct bio *bio;
+	struct bio_vec *bv;
+	char *dst;
+	unsigned int blksize, blk_per_trk, off;
+	int count, cidaw, cplength, datasize;
+	sector_t recid, first_rec, last_rec;
+	sector_t first_trk, last_trk;
+	unsigned int first_offs, last_offs;
+	unsigned char cmd, rcmd;
+	int i;
+
+	private = (struct dasd_eckd_private *) device->private;
+	if (rq_data_dir(req) == READ)
+		cmd = DASD_ECKD_CCW_READ_MT;
+	else if (rq_data_dir(req) == WRITE)
+		cmd = DASD_ECKD_CCW_WRITE_MT;
+	else
+		return ERR_PTR(-EINVAL);
+	/* Calculate number of blocks/records per track. */
+	blksize = device->bp_block;
+	blk_per_trk = recs_per_track(&private->rdc_data, 0, blksize);
+	/* Calculate record id of first and last block. */
+	first_rec = first_trk = req->sector >> device->s2b_shift;
+	first_offs = sector_div(first_trk, blk_per_trk);
+	last_rec = last_trk =
+		(req->sector + req->nr_sectors - 1) >> device->s2b_shift;
+	last_offs = sector_div(last_trk, blk_per_trk);
+	/* Check struct bio and count the number of blocks for the request. */
+	count = 0;
+	cidaw = 0;
+	rq_for_each_bio(bio, req) {
+		bio_for_each_segment(bv, bio, i) {
+			if (bv->bv_len & (blksize - 1))
+				/* Eckd can only do full blocks. */
+				return ERR_PTR(-EINVAL);
+			count += bv->bv_len >> (device->s2b_shift + 9);
+#if defined(CONFIG_ARCH_S390X)
+			if (idal_is_needed (page_address(bv->bv_page),
+					    bv->bv_len))
+				cidaw += bv->bv_len >> (device->s2b_shift + 9);
+#endif
+		}
+	}
+	/* Paranoia. */
+	if (count != last_rec - first_rec + 1)
+		return ERR_PTR(-EINVAL);
+	/* 1x define extent + 1x locate record + number of blocks */
+	cplength = 2 + count;
+	/* 1x define extent + 1x locate record + cidaws*sizeof(long) */
+	datasize = sizeof(struct DE_eckd_data) + sizeof(struct LO_eckd_data) +
+		cidaw * sizeof(unsigned long);
+	/* Find out the number of additional locate record ccws for cdl. */
+	if (private->uses_cdl && first_rec < 2*blk_per_trk) {
+		if (last_rec >= 2*blk_per_trk)
+			count = 2*blk_per_trk - first_rec;
+		cplength += count;
+		datasize += count*sizeof(struct LO_eckd_data);
+	}
+	/* Allocate the ccw request. */
+	cqr = dasd_smalloc_request(dasd_eckd_discipline.name,
+				   cplength, datasize, device);
+	if (IS_ERR(cqr))
+		return cqr;
+	ccw = cqr->cpaddr;
+	/* First ccw is define extent. */
+	define_extent(ccw++, cqr->data, first_trk, last_trk, cmd, device);
+	/* Build locate_record+read/write/ccws. */
+	idaws = (unsigned long *) (cqr->data + sizeof(struct DE_eckd_data));
+	LO_data = (struct LO_eckd_data *) (idaws + cidaw);
+	recid = first_rec;
+	if (private->uses_cdl == 0 || recid > 2*blk_per_trk) {
+		/* Only standard blocks so there is just one locate record. */
+		ccw[-1].flags |= CCW_FLAG_CC;
+		locate_record(ccw++, LO_data++, first_trk, first_offs + 1,
+			      last_rec - recid + 1, cmd, device, blksize);
+	}
+	rq_for_each_bio(bio, req) bio_for_each_segment(bv, bio, i) {
+		dst = page_address(bv->bv_page) + bv->bv_offset;
+		if (dasd_page_cache) {
+			char *copy = kmem_cache_alloc(dasd_page_cache,
+						      SLAB_DMA | __GFP_NOWARN);
+			if (copy && rq_data_dir(req) == WRITE)
+				memcpy(copy + bv->bv_offset, dst, bv->bv_len);
+			if (copy)
+				dst = copy + bv->bv_offset;
+		}
+		for (off = 0; off < bv->bv_len; off += blksize) {
+			sector_t trkid = recid;
+			unsigned int recoffs = sector_div(trkid, blk_per_trk);
+			rcmd = cmd;
+			count = blksize;
+			/* Locate record for cdl special block ? */
+			if (private->uses_cdl && recid < 2*blk_per_trk) {
+				if (dasd_eckd_cdl_special(blk_per_trk, recid)){
+					rcmd |= 0x8;
+					count = dasd_eckd_cdl_reclen(recid);
+					if (count < blksize)
+						memset(dst + count, 0xe5,
+						       blksize - count);
+				}
+				ccw[-1].flags |= CCW_FLAG_CC;
+				locate_record(ccw++, LO_data++,
+					      trkid, recoffs + 1,
+					      1, rcmd, device, count);
+			}
+			/* Locate record for standard blocks ? */
+			if (private->uses_cdl && recid == 2*blk_per_trk) {
+				ccw[-1].flags |= CCW_FLAG_CC;
+				locate_record(ccw++, LO_data++,
+					      trkid, recoffs + 1,
+					      last_rec - recid + 1,
+					      cmd, device, count);
+			}
+			/* Read/write ccw. */
+			ccw[-1].flags |= CCW_FLAG_CC;
+			ccw->cmd_code = rcmd;
+			ccw->count = count;
+			if (idal_is_needed(dst, blksize)) {
+				ccw->cda = (__u32)(addr_t) idaws;
+				ccw->flags = CCW_FLAG_IDA;
+				idaws = idal_create_words(idaws, dst, blksize);
+			} else {
+				ccw->cda = (__u32)(addr_t) dst;
+				ccw->flags = 0;
+			}
+			ccw++;
+			dst += blksize;
+			recid++;
+		}
+	}
+	cqr->device = device;
+	cqr->expires = 5 * 60 * HZ;	/* 5 minutes */
+	cqr->lpm = private->path_data.ppm;
+	cqr->retries = 256;
+	cqr->buildclk = get_clock();
+	cqr->status = DASD_CQR_FILLED;
+	return cqr;
+}
+
+static int
+dasd_eckd_free_cp(struct dasd_ccw_req *cqr, struct request *req)
+{
+	struct dasd_eckd_private *private;
+	struct ccw1 *ccw;
+	struct bio *bio;
+	struct bio_vec *bv;
+	char *dst, *cda;
+	unsigned int blksize, blk_per_trk, off;
+	sector_t recid;
+	int i, status;
+
+	if (!dasd_page_cache)
+		goto out;
+	private = (struct dasd_eckd_private *) cqr->device->private;
+	blksize = cqr->device->bp_block;
+	blk_per_trk = recs_per_track(&private->rdc_data, 0, blksize);
+	recid = req->sector >> cqr->device->s2b_shift;
+	ccw = cqr->cpaddr;
+	/* Skip over define extent & locate record. */
+	ccw++;
+	if (private->uses_cdl == 0 || recid > 2*blk_per_trk)
+		ccw++;
+	rq_for_each_bio(bio, req) bio_for_each_segment(bv, bio, i) {
+		dst = page_address(bv->bv_page) + bv->bv_offset;
+		for (off = 0; off < bv->bv_len; off += blksize) {
+			/* Skip locate record. */
+			if (private->uses_cdl && recid <= 2*blk_per_trk)
+				ccw++;
+			if (dst) {
+				if (ccw->flags & CCW_FLAG_IDA)
+					cda = *((char **)((addr_t) ccw->cda));
+				else
+					cda = (char *)((addr_t) ccw->cda);
+				if (dst != cda) {
+					if (rq_data_dir(req) == READ)
+						memcpy(dst, cda, bv->bv_len);
+					kmem_cache_free(dasd_page_cache,
+					    (void *)((addr_t)cda & PAGE_MASK));
+				}
+				dst = NULL;
+			}
+			ccw++;
+			recid++;
+		}
+	}
+out:
+	status = cqr->status == DASD_CQR_DONE;
+	dasd_sfree_request(cqr, cqr->device);
+	return status;
+}
+
+static int
+dasd_eckd_fill_info(struct dasd_device * device,
+		    struct dasd_information2_t * info)
+{
+	struct dasd_eckd_private *private;
+
+	private = (struct dasd_eckd_private *) device->private;
+	info->label_block = 2;
+	info->FBA_layout = private->uses_cdl ? 0 : 1;
+	info->format = private->uses_cdl ? DASD_FORMAT_CDL : DASD_FORMAT_LDL;
+	info->characteristics_size = sizeof(struct dasd_eckd_characteristics);
+	memcpy(info->characteristics, &private->rdc_data,
+	       sizeof(struct dasd_eckd_characteristics));
+	info->confdata_size = sizeof (struct dasd_eckd_confdata);
+	memcpy(info->configuration_data, &private->conf_data,
+	       sizeof (struct dasd_eckd_confdata));
+	return 0;
+}
+
+/*
+ * SECTION: ioctl functions for eckd devices.
+ */
+
+/*
+ * Release device ioctl.
+ * Buils a channel programm to releases a prior reserved 
+ * (see dasd_eckd_reserve) device.
+ */
+static int
+dasd_eckd_release(struct block_device *bdev, int no, long args)
+{
+	struct dasd_device *device;
+	struct dasd_ccw_req *cqr;
+	int rc;
+
+	if (!capable(CAP_SYS_ADMIN))
+		return -EACCES;
+
+	device = bdev->bd_disk->private_data;
+	if (device == NULL)
+		return -ENODEV;
+
+	cqr = dasd_smalloc_request(dasd_eckd_discipline.name,
+				   1, 32, device);
+	if (IS_ERR(cqr)) {
+		DEV_MESSAGE(KERN_WARNING, device, "%s",
+			    "Could not allocate initialization request");
+		return PTR_ERR(cqr);
+	}
+	cqr->cpaddr->cmd_code = DASD_ECKD_CCW_RELEASE;
+        cqr->cpaddr->flags |= CCW_FLAG_SLI;
+        cqr->cpaddr->count = 32;
+	cqr->cpaddr->cda = (__u32)(addr_t) cqr->data;
+	cqr->device = device;
+	clear_bit(DASD_CQR_FLAGS_USE_ERP, &cqr->flags);
+	cqr->retries = 0;
+	cqr->expires = 2 * HZ;
+	cqr->buildclk = get_clock();
+	cqr->status = DASD_CQR_FILLED;
+
+	rc = dasd_sleep_on_immediatly(cqr);
+
+	dasd_sfree_request(cqr, cqr->device);
+	return rc;
+}
+
+/*
+ * Reserve device ioctl.
+ * Options are set to 'synchronous wait for interrupt' and
+ * 'timeout the request'. This leads to a terminate IO if 
+ * the interrupt is outstanding for a certain time. 
+ */
+static int
+dasd_eckd_reserve(struct block_device *bdev, int no, long args)
+{
+	struct dasd_device *device;
+	struct dasd_ccw_req *cqr;
+	int rc;
+
+	if (!capable(CAP_SYS_ADMIN))
+		return -EACCES;
+
+	device = bdev->bd_disk->private_data;
+	if (device == NULL)
+		return -ENODEV;
+
+	cqr = dasd_smalloc_request(dasd_eckd_discipline.name,
+				   1, 32, device);
+	if (IS_ERR(cqr)) {
+		DEV_MESSAGE(KERN_WARNING, device, "%s",
+			    "Could not allocate initialization request");
+		return PTR_ERR(cqr);
+	}
+	cqr->cpaddr->cmd_code = DASD_ECKD_CCW_RESERVE;
+        cqr->cpaddr->flags |= CCW_FLAG_SLI;
+        cqr->cpaddr->count = 32;
+	cqr->cpaddr->cda = (__u32)(addr_t) cqr->data;
+	cqr->device = device;
+	clear_bit(DASD_CQR_FLAGS_USE_ERP, &cqr->flags);
+	cqr->retries = 0;
+	cqr->expires = 2 * HZ;
+	cqr->buildclk = get_clock();
+	cqr->status = DASD_CQR_FILLED;
+
+	rc = dasd_sleep_on_immediatly(cqr);
+
+	dasd_sfree_request(cqr, cqr->device);
+	return rc;
+}
+
+/*
+ * Steal lock ioctl - unconditional reserve device.
+ * Buils a channel programm to break a device's reservation. 
+ * (unconditional reserve)
+ */
+static int
+dasd_eckd_steal_lock(struct block_device *bdev, int no, long args)
+{
+	struct dasd_device *device;
+	struct dasd_ccw_req *cqr;
+	int rc;
+
+	if (!capable(CAP_SYS_ADMIN))
+		return -EACCES;
+
+	device = bdev->bd_disk->private_data;
+	if (device == NULL)
+		return -ENODEV;
+
+	cqr = dasd_smalloc_request(dasd_eckd_discipline.name,
+				   1, 32, device);
+	if (IS_ERR(cqr)) {
+		DEV_MESSAGE(KERN_WARNING, device, "%s",
+			    "Could not allocate initialization request");
+		return PTR_ERR(cqr);
+	}
+	cqr->cpaddr->cmd_code = DASD_ECKD_CCW_SLCK;
+        cqr->cpaddr->flags |= CCW_FLAG_SLI;
+        cqr->cpaddr->count = 32;
+	cqr->cpaddr->cda = (__u32)(addr_t) cqr->data;
+	cqr->device = device;
+	clear_bit(DASD_CQR_FLAGS_USE_ERP, &cqr->flags);
+	cqr->retries = 0;
+	cqr->expires = 2 * HZ;
+	cqr->buildclk = get_clock();
+	cqr->status = DASD_CQR_FILLED;
+
+	rc = dasd_sleep_on_immediatly(cqr);
+
+	dasd_sfree_request(cqr, cqr->device);
+	return rc;
+}
+
+/*
+ * Read performance statistics
+ */
+static int
+dasd_eckd_performance(struct block_device *bdev, int no, long args)
+{
+	struct dasd_device *device;
+	struct dasd_psf_prssd_data *prssdp;
+	struct dasd_rssd_perf_stats_t *stats;
+	struct dasd_ccw_req *cqr;
+	struct ccw1 *ccw;
+	int rc;
+
+	device = bdev->bd_disk->private_data;
+	if (device == NULL)
+		return -ENODEV;
+
+	cqr = dasd_smalloc_request(dasd_eckd_discipline.name,
+				   1 /* PSF */  + 1 /* RSSD */ ,
+				   (sizeof (struct dasd_psf_prssd_data) +
+				    sizeof (struct dasd_rssd_perf_stats_t)),
+				   device);
+	if (IS_ERR(cqr)) {
+		DEV_MESSAGE(KERN_WARNING, device, "%s",
+			    "Could not allocate initialization request");
+		return PTR_ERR(cqr);
+	}
+	cqr->device = device;
+	cqr->retries = 0;
+	cqr->expires = 10 * HZ;
+
+	/* Prepare for Read Subsystem Data */
+	prssdp = (struct dasd_psf_prssd_data *) cqr->data;
+	memset(prssdp, 0, sizeof (struct dasd_psf_prssd_data));
+	prssdp->order = PSF_ORDER_PRSSD;
+	prssdp->suborder = 0x01;	/* Perfomance Statistics */
+	prssdp->varies[1] = 0x01;	/* Perf Statistics for the Subsystem */
+
+	ccw = cqr->cpaddr;
+	ccw->cmd_code = DASD_ECKD_CCW_PSF;
+	ccw->count = sizeof (struct dasd_psf_prssd_data);
+	ccw->flags |= CCW_FLAG_CC;
+	ccw->cda = (__u32)(addr_t) prssdp;
+
+	/* Read Subsystem Data - Performance Statistics */
+	stats = (struct dasd_rssd_perf_stats_t *) (prssdp + 1);
+	memset(stats, 0, sizeof (struct dasd_rssd_perf_stats_t));
+
+	ccw++;
+	ccw->cmd_code = DASD_ECKD_CCW_RSSD;
+	ccw->count = sizeof (struct dasd_rssd_perf_stats_t);
+	ccw->cda = (__u32)(addr_t) stats;
+
+	cqr->buildclk = get_clock();
+	cqr->status = DASD_CQR_FILLED;
+	rc = dasd_sleep_on(cqr);
+	if (rc == 0) {
+		/* Prepare for Read Subsystem Data */
+		prssdp = (struct dasd_psf_prssd_data *) cqr->data;
+		stats = (struct dasd_rssd_perf_stats_t *) (prssdp + 1);
+		rc = copy_to_user((long __user *) args, (long *) stats,
+				  sizeof(struct dasd_rssd_perf_stats_t));
+	}
+	dasd_sfree_request(cqr, cqr->device);
+	return rc;
+}
+
+/*
+ * Get attributes (cache operations)
+ * Returnes the cache attributes used in Define Extend (DE).
+ */
+static int
+dasd_eckd_get_attrib (struct block_device *bdev, int no, long args)
+{
+	struct dasd_device *device;
+        struct dasd_eckd_private *private;
+        struct attrib_data_t attrib;
+	int rc;
+
+        if (!capable(CAP_SYS_ADMIN))
+                return -EACCES;
+        if (!args)
+                return -EINVAL;
+
+        device = bdev->bd_disk->private_data;
+        if (device == NULL)
+                return -ENODEV;
+
+        private = (struct dasd_eckd_private *) device->private;
+        attrib = private->attrib;
+
+        rc = copy_to_user((long __user *) args, (long *) &attrib,
+			  sizeof (struct attrib_data_t));
+
+	return rc;
+}
+
+/*
+ * Set attributes (cache operations)
+ * Stores the attributes for cache operation to be used in Define Extend (DE).
+ */
+static int
+dasd_eckd_set_attrib(struct block_device *bdev, int no, long args)
+{
+	struct dasd_device *device;
+	struct dasd_eckd_private *private;
+	struct attrib_data_t attrib;
+
+	if (!capable(CAP_SYS_ADMIN))
+		return -EACCES;
+	if (!args)
+		return -EINVAL;
+
+	device = bdev->bd_disk->private_data;
+	if (device == NULL)
+		return -ENODEV;
+
+	if (copy_from_user(&attrib, (void __user *) args,
+			   sizeof (struct attrib_data_t))) {
+		return -EFAULT;
+	}
+	private = (struct dasd_eckd_private *) device->private;
+	private->attrib = attrib;
+
+	DEV_MESSAGE(KERN_INFO, device,
+		    "cache operation mode set to %x (%i cylinder prestage)",
+		    private->attrib.operation, private->attrib.nr_cyl);
+	return 0;
+}
+
+/*
+ * Print sense data and related channel program.
+ * Parts are printed because printk buffer is only 1024 bytes.
+ */
+static void
+dasd_eckd_dump_sense(struct dasd_device *device, struct dasd_ccw_req * req,
+		     struct irb *irb)
+{
+	char *page;
+	struct ccw1 *act, *end, *last;
+	int len, sl, sct, count;
+
+	page = (char *) get_zeroed_page(GFP_ATOMIC);
+	if (page == NULL) {
+		DEV_MESSAGE(KERN_ERR, device, " %s",
+			    "No memory to dump sense data");
+		return;
+	}
+	len = sprintf(page, KERN_ERR PRINTK_HEADER
+		      " I/O status report for device %s:\n",
+		      device->cdev->dev.bus_id);
+	len += sprintf(page + len, KERN_ERR PRINTK_HEADER
+		       " in req: %p CS: 0x%02X DS: 0x%02X\n", req,
+		       irb->scsw.cstat, irb->scsw.dstat);
+	len += sprintf(page + len, KERN_ERR PRINTK_HEADER
+		       " device %s: Failing CCW: %p\n",
+		       device->cdev->dev.bus_id,
+		       (void *) (addr_t) irb->scsw.cpa);
+	if (irb->esw.esw0.erw.cons) {
+		for (sl = 0; sl < 4; sl++) {
+			len += sprintf(page + len, KERN_ERR PRINTK_HEADER
+				       " Sense(hex) %2d-%2d:",
+				       (8 * sl), ((8 * sl) + 7));
+
+			for (sct = 0; sct < 8; sct++) {
+				len += sprintf(page + len, " %02x",
+					       irb->ecw[8 * sl + sct]);
+			}
+			len += sprintf(page + len, "\n");
+		}
+
+		if (irb->ecw[27] & DASD_SENSE_BIT_0) {
+			/* 24 Byte Sense Data */
+			len += sprintf(page + len, KERN_ERR PRINTK_HEADER
+				       " 24 Byte: %x MSG %x, "
+				       "%s MSGb to SYSOP\n",
+				       irb->ecw[7] >> 4, irb->ecw[7] & 0x0f,
+				       irb->ecw[1] & 0x10 ? "" : "no");
+		} else {
+			/* 32 Byte Sense Data */
+			len += sprintf(page + len, KERN_ERR PRINTK_HEADER
+				       " 32 Byte: Format: %x "
+				       "Exception class %x\n",
+				       irb->ecw[6] & 0x0f, irb->ecw[22] >> 4);
+		}
+	} else {
+	        len += sprintf(page + len, KERN_ERR PRINTK_HEADER
+			       " SORRY - NO VALID SENSE AVAILABLE\n");
+	}
+	MESSAGE_LOG(KERN_ERR, "%s",
+		    page + sizeof(KERN_ERR PRINTK_HEADER));
+
+	/* dump the Channel Program */
+	/* print first CCWs (maximum 8) */
+	act = req->cpaddr;
+        for (last = act; last->flags & (CCW_FLAG_CC | CCW_FLAG_DC); last++);
+	end = min(act + 8, last);
+	len = sprintf(page, KERN_ERR PRINTK_HEADER
+		      " Related CP in req: %p\n", req);
+	while (act <= end) {
+		len += sprintf(page + len, KERN_ERR PRINTK_HEADER
+			       " CCW %p: %08X %08X DAT:",
+			       act, ((int *) act)[0], ((int *) act)[1]);
+		for (count = 0; count < 32 && count < act->count;
+		     count += sizeof(int))
+			len += sprintf(page + len, " %08X",
+				       ((int *) (addr_t) act->cda)
+				       [(count>>2)]);
+		len += sprintf(page + len, "\n");
+		act++;
+	}
+	MESSAGE_LOG(KERN_ERR, "%s",
+		    page + sizeof(KERN_ERR PRINTK_HEADER));
+
+	/* print failing CCW area */
+	len = 0;
+	if (act <  ((struct ccw1 *)(addr_t) irb->scsw.cpa) - 2) {
+		act = ((struct ccw1 *)(addr_t) irb->scsw.cpa) - 2;
+		len += sprintf(page + len, KERN_ERR PRINTK_HEADER "......\n");
+	}
+	end = min((struct ccw1 *)(addr_t) irb->scsw.cpa + 2, last);
+	while (act <= end) {
+		len += sprintf(page + len, KERN_ERR PRINTK_HEADER
+			       " CCW %p: %08X %08X DAT:",
+			       act, ((int *) act)[0], ((int *) act)[1]);
+		for (count = 0; count < 32 && count < act->count;
+		     count += sizeof(int))
+			len += sprintf(page + len, " %08X",
+				       ((int *) (addr_t) act->cda)
+				       [(count>>2)]);
+		len += sprintf(page + len, "\n");
+		act++;
+	}
+
+	/* print last CCWs */
+	if (act <  last - 2) {
+		act = last - 2;
+		len += sprintf(page + len, KERN_ERR PRINTK_HEADER "......\n");
+	}
+	while (act <= last) {
+		len += sprintf(page + len, KERN_ERR PRINTK_HEADER
+			       " CCW %p: %08X %08X DAT:",
+			       act, ((int *) act)[0], ((int *) act)[1]);
+		for (count = 0; count < 32 && count < act->count;
+		     count += sizeof(int))
+			len += sprintf(page + len, " %08X",
+				       ((int *) (addr_t) act->cda)
+				       [(count>>2)]);
+		len += sprintf(page + len, "\n");
+		act++;
+	}
+	if (len > 0)
+		MESSAGE_LOG(KERN_ERR, "%s",
+			    page + sizeof(KERN_ERR PRINTK_HEADER));
+	free_page((unsigned long) page);
+}
+
+/*
+ * max_blocks is dependent on the amount of storage that is available
+ * in the static io buffer for each device. Currently each device has
+ * 8192 bytes (=2 pages). For 64 bit one dasd_mchunkt_t structure has
+ * 24 bytes, the struct dasd_ccw_req has 136 bytes and each block can use
+ * up to 16 bytes (8 for the ccw and 8 for the idal pointer). In
+ * addition we have one define extent ccw + 16 bytes of data and one
+ * locate record ccw + 16 bytes of data. That makes:
+ * (8192 - 24 - 136 - 8 - 16 - 8 - 16) / 16 = 499 blocks at maximum.
+ * We want to fit two into the available memory so that we can immediately
+ * start the next request if one finishes off. That makes 249.5 blocks
+ * for one request. Give a little safety and the result is 240.
+ */
+static struct dasd_discipline dasd_eckd_discipline = {
+	.owner = THIS_MODULE,
+	.name = "ECKD",
+	.ebcname = "ECKD",
+	.max_blocks = 240,
+	.check_device = dasd_eckd_check_characteristics,
+	.do_analysis = dasd_eckd_do_analysis,
+	.fill_geometry = dasd_eckd_fill_geometry,
+	.start_IO = dasd_start_IO,
+	.term_IO = dasd_term_IO,
+	.format_device = dasd_eckd_format_device,
+	.examine_error = dasd_eckd_examine_error,
+	.erp_action = dasd_eckd_erp_action,
+	.erp_postaction = dasd_eckd_erp_postaction,
+	.build_cp = dasd_eckd_build_cp,
+	.free_cp = dasd_eckd_free_cp,
+	.dump_sense = dasd_eckd_dump_sense,
+	.fill_info = dasd_eckd_fill_info,
+};
+
+static int __init
+dasd_eckd_init(void)
+{
+	int ret;
+
+	dasd_ioctl_no_register(THIS_MODULE, BIODASDGATTR,
+			       dasd_eckd_get_attrib);
+	dasd_ioctl_no_register(THIS_MODULE, BIODASDSATTR,
+			       dasd_eckd_set_attrib);
+	dasd_ioctl_no_register(THIS_MODULE, BIODASDPSRD,
+			       dasd_eckd_performance);
+	dasd_ioctl_no_register(THIS_MODULE, BIODASDRLSE,
+			       dasd_eckd_release);
+	dasd_ioctl_no_register(THIS_MODULE, BIODASDRSRV,
+			       dasd_eckd_reserve);
+	dasd_ioctl_no_register(THIS_MODULE, BIODASDSLCK,
+			       dasd_eckd_steal_lock);
+
+	ASCEBC(dasd_eckd_discipline.ebcname, 4);
+
+	ret = ccw_driver_register(&dasd_eckd_driver);
+	if (ret) {
+		dasd_ioctl_no_unregister(THIS_MODULE, BIODASDGATTR,
+					 dasd_eckd_get_attrib);
+		dasd_ioctl_no_unregister(THIS_MODULE, BIODASDSATTR,
+					 dasd_eckd_set_attrib);
+		dasd_ioctl_no_unregister(THIS_MODULE, BIODASDPSRD,
+					 dasd_eckd_performance);
+		dasd_ioctl_no_unregister(THIS_MODULE, BIODASDRLSE,
+					 dasd_eckd_release);
+		dasd_ioctl_no_unregister(THIS_MODULE, BIODASDRSRV,
+					 dasd_eckd_reserve);
+		dasd_ioctl_no_unregister(THIS_MODULE, BIODASDSLCK,
+					 dasd_eckd_steal_lock);
+		return ret;
+	}
+
+	dasd_generic_auto_online(&dasd_eckd_driver);
+	return 0;
+}
+
+static void __exit
+dasd_eckd_cleanup(void)
+{
+	ccw_driver_unregister(&dasd_eckd_driver);
+
+	dasd_ioctl_no_unregister(THIS_MODULE, BIODASDGATTR,
+				 dasd_eckd_get_attrib);
+	dasd_ioctl_no_unregister(THIS_MODULE, BIODASDSATTR,
+				 dasd_eckd_set_attrib);
+	dasd_ioctl_no_unregister(THIS_MODULE, BIODASDPSRD,
+				 dasd_eckd_performance);
+	dasd_ioctl_no_unregister(THIS_MODULE, BIODASDRLSE,
+				 dasd_eckd_release);
+	dasd_ioctl_no_unregister(THIS_MODULE, BIODASDRSRV,
+				 dasd_eckd_reserve);
+	dasd_ioctl_no_unregister(THIS_MODULE, BIODASDSLCK,
+				 dasd_eckd_steal_lock);
+}
+
+module_init(dasd_eckd_init);
+module_exit(dasd_eckd_cleanup);
+
+/*
+ * Overrides for Emacs so that we follow Linus's tabbing style.
+ * Emacs will notice this stuff at the end of the file and automatically
+ * adjust the settings for this buffer only.  This must remain at the end
+ * of the file.
+ * ---------------------------------------------------------------------------
+ * Local variables:
+ * c-indent-level: 4 
+ * c-brace-imaginary-offset: 0
+ * c-brace-offset: -4
+ * c-argdecl-indent: 4
+ * c-label-offset: -4
+ * c-continued-statement-offset: 4
+ * c-continued-brace-offset: 0
+ * indent-tabs-mode: 1
+ * tab-width: 8
+ * End:
+ */
diff --git a/drivers/s390/block/dasd_eckd.h b/drivers/s390/block/dasd_eckd.h
new file mode 100644
index 0000000..b6888c6
--- /dev/null
+++ b/drivers/s390/block/dasd_eckd.h
@@ -0,0 +1,346 @@
+/* 
+ * File...........: linux/drivers/s390/block/dasd_eckd.h
+ * Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
+ *                  Horst Hummel <Horst.Hummel@de.ibm.com> 
+ * Bugreports.to..: <Linux390@de.ibm.com>
+ * (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999,2000
+ *
+ * $Revision: 1.10 $
+ */
+
+#ifndef DASD_ECKD_H
+#define DASD_ECKD_H
+
+/*****************************************************************************
+ * SECTION: CCW Definitions
+ ****************************************************************************/
+#define DASD_ECKD_CCW_WRITE		 0x05
+#define DASD_ECKD_CCW_READ		 0x06
+#define DASD_ECKD_CCW_WRITE_HOME_ADDRESS 0x09
+#define DASD_ECKD_CCW_READ_HOME_ADDRESS	 0x0a
+#define DASD_ECKD_CCW_WRITE_KD		 0x0d
+#define DASD_ECKD_CCW_READ_KD		 0x0e
+#define DASD_ECKD_CCW_ERASE		 0x11
+#define DASD_ECKD_CCW_READ_COUNT	 0x12
+#define DASD_ECKD_CCW_SLCK		 0x14
+#define DASD_ECKD_CCW_WRITE_RECORD_ZERO	 0x15
+#define DASD_ECKD_CCW_READ_RECORD_ZERO	 0x16
+#define DASD_ECKD_CCW_WRITE_CKD		 0x1d
+#define DASD_ECKD_CCW_READ_CKD		 0x1e
+#define DASD_ECKD_CCW_PSF		 0x27
+#define DASD_ECKD_CCW_RSSD		 0x3e
+#define DASD_ECKD_CCW_LOCATE_RECORD	 0x47
+#define DASD_ECKD_CCW_DEFINE_EXTENT	 0x63
+#define DASD_ECKD_CCW_WRITE_MT		 0x85
+#define DASD_ECKD_CCW_READ_MT		 0x86
+#define DASD_ECKD_CCW_WRITE_KD_MT	 0x8d
+#define DASD_ECKD_CCW_READ_KD_MT	 0x8e
+#define DASD_ECKD_CCW_RELEASE		 0x94
+#define DASD_ECKD_CCW_READ_CKD_MT	 0x9e
+#define DASD_ECKD_CCW_WRITE_CKD_MT	 0x9d
+#define DASD_ECKD_CCW_RESERVE		 0xB4
+
+/*
+ *Perform Subsystem Function / Sub-Orders
+ */
+#define PSF_ORDER_PRSSD			 0x18
+
+/*****************************************************************************
+ * SECTION: Type Definitions
+ ****************************************************************************/
+
+struct eckd_count {
+	__u16 cyl;
+	__u16 head;
+	__u8 record;
+	__u8 kl;
+	__u16 dl;
+} __attribute__ ((packed));
+
+struct ch_t {
+	__u16 cyl;
+	__u16 head;
+} __attribute__ ((packed));
+
+struct chs_t {
+	__u16 cyl;
+	__u16 head;
+	__u32 sector;
+} __attribute__ ((packed));
+
+struct chr_t {
+	__u16 cyl;
+	__u16 head;
+	__u8 record;
+} __attribute__ ((packed));
+
+struct geom_t {
+	__u16 cyl;
+	__u16 head;
+	__u32 sector;
+} __attribute__ ((packed));
+
+struct eckd_home {
+	__u8 skip_control[14];
+	__u16 cell_number;
+	__u8 physical_addr[3];
+	__u8 flag;
+	struct ch_t track_addr;
+	__u8 reserved;
+	__u8 key_length;
+	__u8 reserved2[2];
+} __attribute__ ((packed));
+
+struct DE_eckd_data {
+	struct {
+		unsigned char perm:2;	/* Permissions on this extent */
+		unsigned char reserved:1;
+		unsigned char seek:2;	/* Seek control */
+		unsigned char auth:2;	/* Access authorization */
+		unsigned char pci:1;	/* PCI Fetch mode */
+	} __attribute__ ((packed)) mask;
+	struct {
+		unsigned char mode:2;	/* Architecture mode */
+		unsigned char ckd:1;	/* CKD Conversion */
+		unsigned char operation:3;	/* Operation mode */
+		unsigned char cfw:1;	/* Cache fast write */
+		unsigned char dfw:1;	/* DASD fast write */
+	} __attribute__ ((packed)) attributes;
+	__u16 blk_size;		/* Blocksize */
+	__u16 fast_write_id;
+	__u8 ga_additional;	/* Global Attributes Additional */
+	__u8 ga_extended;	/* Global Attributes Extended	*/
+	struct ch_t beg_ext;
+	struct ch_t end_ext;
+	unsigned long long ep_sys_time; /* Ext Parameter - System Time Stamp */
+	__u8 ep_format;        /* Extended Parameter format byte       */
+	__u8 ep_prio;          /* Extended Parameter priority I/O byte */
+	__u8 ep_reserved[6];   /* Extended Parameter Reserved          */
+} __attribute__ ((packed));
+
+struct LO_eckd_data {
+	struct {
+		unsigned char orientation:2;
+		unsigned char operation:6;
+	} __attribute__ ((packed)) operation;
+	struct {
+		unsigned char last_bytes_used:1;
+		unsigned char reserved:6;
+		unsigned char read_count_suffix:1;
+	} __attribute__ ((packed)) auxiliary;
+	__u8 unused;
+	__u8 count;
+	struct ch_t seek_addr;
+	struct chr_t search_arg;
+	__u8 sector;
+	__u16 length;
+} __attribute__ ((packed));
+
+struct dasd_eckd_characteristics {
+	__u16 cu_type;
+	struct {
+		unsigned char support:2;
+		unsigned char async:1;
+		unsigned char reserved:1;
+		unsigned char cache_info:1;
+		unsigned char model:3;
+	} __attribute__ ((packed)) cu_model;
+	__u16 dev_type;
+	__u8 dev_model;
+	struct {
+		unsigned char mult_burst:1;
+		unsigned char RT_in_LR:1;
+		unsigned char reserved1:1;
+		unsigned char RD_IN_LR:1;
+		unsigned char reserved2:4;
+		unsigned char reserved3:8;
+		unsigned char defect_wr:1;
+		unsigned char XRC_supported:1; 
+		unsigned char reserved4:1;
+		unsigned char striping:1;
+		unsigned char reserved5:4;
+		unsigned char cfw:1;
+		unsigned char reserved6:2;
+		unsigned char cache:1;
+		unsigned char dual_copy:1;
+		unsigned char dfw:1;
+		unsigned char reset_alleg:1;
+		unsigned char sense_down:1;
+	} __attribute__ ((packed)) facilities;
+	__u8 dev_class;
+	__u8 unit_type;
+	__u16 no_cyl;
+	__u16 trk_per_cyl;
+	__u8 sec_per_trk;
+	__u8 byte_per_track[3];
+	__u16 home_bytes;
+	__u8 formula;
+	union {
+		struct {
+			__u8 f1;
+			__u16 f2;
+			__u16 f3;
+		} __attribute__ ((packed)) f_0x01;
+		struct {
+			__u8 f1;
+			__u8 f2;
+			__u8 f3;
+			__u8 f4;
+			__u8 f5;
+		} __attribute__ ((packed)) f_0x02;
+	} __attribute__ ((packed)) factors;
+	__u16 first_alt_trk;
+	__u16 no_alt_trk;
+	__u16 first_dia_trk;
+	__u16 no_dia_trk;
+	__u16 first_sup_trk;
+	__u16 no_sup_trk;
+	__u8 MDR_ID;
+	__u8 OBR_ID;
+	__u8 director;
+	__u8 rd_trk_set;
+	__u16 max_rec_zero;
+	__u8 reserved1;
+	__u8 RWANY_in_LR;
+	__u8 factor6;
+	__u8 factor7;
+	__u8 factor8;
+	__u8 reserved2[3];
+	__u8 reserved3[10];
+} __attribute__ ((packed));
+
+struct dasd_eckd_confdata {
+	struct {
+		struct {
+			unsigned char identifier:2;
+			unsigned char token_id:1;
+			unsigned char sno_valid:1;
+			unsigned char subst_sno:1;
+			unsigned char recNED:1;
+			unsigned char emuNED:1;
+			unsigned char reserved:1;
+		} __attribute__ ((packed)) flags;
+		__u8 descriptor;
+		__u8 dev_class;
+		__u8 reserved;
+		unsigned char dev_type[6];
+		unsigned char dev_model[3];
+		unsigned char HDA_manufacturer[3];
+		unsigned char HDA_location[2];
+		unsigned char HDA_seqno[12];
+		__u16 ID;
+	} __attribute__ ((packed)) ned1;
+	struct {
+		struct {
+			unsigned char identifier:2;
+			unsigned char token_id:1;
+			unsigned char sno_valid:1;
+			unsigned char subst_sno:1;
+			unsigned char recNED:1;
+			unsigned char emuNED:1;
+			unsigned char reserved:1;
+		} __attribute__ ((packed)) flags;
+		__u8 descriptor;
+		__u8 reserved[2];
+		unsigned char dev_type[6];
+		unsigned char dev_model[3];
+		unsigned char DASD_manufacturer[3];
+		unsigned char DASD_location[2];
+		unsigned char DASD_seqno[12];
+		__u16 ID;
+	} __attribute__ ((packed)) ned2;
+	struct {
+		struct {
+			unsigned char identifier:2;
+			unsigned char token_id:1;
+			unsigned char sno_valid:1;
+			unsigned char subst_sno:1;
+			unsigned char recNED:1;
+			unsigned char emuNED:1;
+			unsigned char reserved:1;
+		} __attribute__ ((packed)) flags;
+		__u8 descriptor;
+		__u8 reserved[2];
+		unsigned char cont_type[6];
+		unsigned char cont_model[3];
+		unsigned char cont_manufacturer[3];
+		unsigned char cont_location[2];
+		unsigned char cont_seqno[12];
+		__u16 ID;
+	} __attribute__ ((packed)) ned3;
+	struct {
+		struct {
+			unsigned char identifier:2;
+			unsigned char token_id:1;
+			unsigned char sno_valid:1;
+			unsigned char subst_sno:1;
+			unsigned char recNED:1;
+			unsigned char emuNED:1;
+			unsigned char reserved:1;
+		} __attribute__ ((packed)) flags;
+		__u8 descriptor;
+		__u8 reserved[2];
+		unsigned char cont_type[6];
+		unsigned char empty[3];
+		unsigned char cont_manufacturer[3];
+		unsigned char cont_location[2];
+		unsigned char cont_seqno[12];
+		__u16 ID;
+	} __attribute__ ((packed)) ned4;
+	unsigned char ned5[32];
+	unsigned char ned6[32];
+	unsigned char ned7[32];
+	struct {
+		struct {
+			unsigned char identifier:2;
+			unsigned char reserved:6;
+		} __attribute__ ((packed)) flags;
+		__u8 selector;
+		__u16 interfaceID;
+		__u32 reserved;
+		__u16 subsystemID;
+		struct {
+			unsigned char sp0:1;
+			unsigned char sp1:1;
+			unsigned char reserved:5;
+			unsigned char scluster:1;
+		} __attribute__ ((packed)) spathID;
+		__u8 unit_address;
+		__u8 dev_ID;
+		__u8 dev_address;
+		__u8 adapterID;
+		__u16 link_address;
+		struct {
+			unsigned char parallel:1;
+			unsigned char escon:1;
+			unsigned char reserved:1;
+			unsigned char ficon:1;
+			unsigned char reserved2:4;
+		} __attribute__ ((packed)) protocol_type;
+		struct {
+			unsigned char PID_in_236:1;
+			unsigned char reserved:7;
+		} __attribute__ ((packed)) format_flags;
+		__u8 log_dev_address;
+		unsigned char reserved2[12];
+	} __attribute__ ((packed)) neq;
+} __attribute__ ((packed));
+
+struct dasd_eckd_path {
+	__u8 opm;
+	__u8 ppm;
+	__u8 npm;
+};
+
+/*
+ * Perform Subsystem Function - Prepare for Read Subsystem Data	 
+ */
+struct dasd_psf_prssd_data {
+	unsigned char order;
+	unsigned char flags;
+	unsigned char reserved[4];
+	unsigned char suborder;
+	unsigned char varies[9];
+} __attribute__ ((packed));
+
+#endif				/* DASD_ECKD_H */
diff --git a/drivers/s390/block/dasd_erp.c b/drivers/s390/block/dasd_erp.c
new file mode 100644
index 0000000..7cb98d2
--- /dev/null
+++ b/drivers/s390/block/dasd_erp.c
@@ -0,0 +1,254 @@
+/*
+ * File...........: linux/drivers/s390/block/dasd.c
+ * Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
+ *		    Horst Hummel <Horst.Hummel@de.ibm.com>
+ *		    Carsten Otte <Cotte@de.ibm.com>
+ *		    Martin Schwidefsky <schwidefsky@de.ibm.com>
+ * Bugreports.to..: <Linux390@de.ibm.com>
+ * (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999-2001
+ *
+ * $Revision: 1.14 $
+ */
+
+#include <linux/config.h>
+#include <linux/ctype.h>
+#include <linux/init.h>
+
+#include <asm/debug.h>
+#include <asm/ebcdic.h>
+#include <asm/uaccess.h>
+
+/* This is ugly... */
+#define PRINTK_HEADER "dasd_erp:"
+
+#include "dasd_int.h"
+
+struct dasd_ccw_req *
+dasd_alloc_erp_request(char *magic, int cplength, int datasize,
+		       struct dasd_device * device)
+{
+	unsigned long flags;
+	struct dasd_ccw_req *cqr;
+	char *data;
+	int size;
+
+	/* Sanity checks */
+	if ( magic == NULL || datasize > PAGE_SIZE ||
+	     (cplength*sizeof(struct ccw1)) > PAGE_SIZE)
+		BUG();
+
+	size = (sizeof(struct dasd_ccw_req) + 7L) & -8L;
+	if (cplength > 0)
+		size += cplength * sizeof(struct ccw1);
+	if (datasize > 0)
+		size += datasize;
+	spin_lock_irqsave(&device->mem_lock, flags);
+	cqr = (struct dasd_ccw_req *)
+		dasd_alloc_chunk(&device->erp_chunks, size);
+	spin_unlock_irqrestore(&device->mem_lock, flags);
+	if (cqr == NULL)
+		return ERR_PTR(-ENOMEM);
+	memset(cqr, 0, sizeof(struct dasd_ccw_req));
+	data = (char *) cqr + ((sizeof(struct dasd_ccw_req) + 7L) & -8L);
+	cqr->cpaddr = NULL;
+	if (cplength > 0) {
+		cqr->cpaddr = (struct ccw1 *) data;
+		data += cplength*sizeof(struct ccw1);
+		memset(cqr->cpaddr, 0, cplength*sizeof(struct ccw1));
+	}
+	cqr->data = NULL;
+	if (datasize > 0) {
+		cqr->data = data;
+ 		memset(cqr->data, 0, datasize);
+	}
+	strncpy((char *) &cqr->magic, magic, 4);
+	ASCEBC((char *) &cqr->magic, 4);
+	set_bit(DASD_CQR_FLAGS_USE_ERP, &cqr->flags);
+	dasd_get_device(device);
+	return cqr;
+}
+
+void
+dasd_free_erp_request(struct dasd_ccw_req * cqr, struct dasd_device * device)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&device->mem_lock, flags);
+	dasd_free_chunk(&device->erp_chunks, cqr);
+	spin_unlock_irqrestore(&device->mem_lock, flags);
+	atomic_dec(&device->ref_count);
+}
+
+
+/*
+ * dasd_default_erp_action just retries the current cqr
+ */
+struct dasd_ccw_req *
+dasd_default_erp_action(struct dasd_ccw_req * cqr)
+{
+	struct dasd_device *device;
+
+	device = cqr->device;
+
+        /* just retry - there is nothing to save ... I got no sense data.... */
+        if (cqr->retries > 0) {
+                DEV_MESSAGE (KERN_DEBUG, device, 
+                             "default ERP called (%i retries left)",
+                             cqr->retries);
+		cqr->lpm    = LPM_ANYPATH;
+		cqr->status = DASD_CQR_QUEUED;
+        } else {
+                DEV_MESSAGE (KERN_WARNING, device, "%s",
+			     "default ERP called (NO retry left)");
+		cqr->status = DASD_CQR_FAILED;
+		cqr->stopclk = get_clock ();
+        }
+        return cqr;
+}				/* end dasd_default_erp_action */
+
+/*
+ * DESCRIPTION
+ *   Frees all ERPs of the current ERP Chain and set the status
+ *   of the original CQR either to DASD_CQR_DONE if ERP was successful
+ *   or to DASD_CQR_FAILED if ERP was NOT successful.
+ *   NOTE: This function is only called if no discipline postaction
+ *	   is available
+ *
+ * PARAMETER
+ *   erp		current erp_head
+ *
+ * RETURN VALUES
+ *   cqr		pointer to the original CQR
+ */
+struct dasd_ccw_req *
+dasd_default_erp_postaction(struct dasd_ccw_req * cqr)
+{
+	struct dasd_device *device;
+	int success;
+
+	if (cqr->refers == NULL || cqr->function == NULL)
+		BUG();
+
+	device = cqr->device;
+	success = cqr->status == DASD_CQR_DONE;
+
+	/* free all ERPs - but NOT the original cqr */
+	while (cqr->refers != NULL) {
+		struct dasd_ccw_req *refers;
+
+		refers = cqr->refers;
+		/* remove the request from the device queue */
+		list_del(&cqr->list);
+		/* free the finished erp request */
+		dasd_free_erp_request(cqr, device);
+		cqr = refers;
+	}
+
+	/* set corresponding status to original cqr */
+	if (success)
+		cqr->status = DASD_CQR_DONE;
+	else {
+		cqr->status = DASD_CQR_FAILED;
+		cqr->stopclk = get_clock();
+	}
+
+	return cqr;
+
+}				/* end default_erp_postaction */
+
+/*
+ * Print the hex dump of the memory used by a request. This includes
+ * all error recovery ccws that have been chained in from of the 
+ * real request.
+ */
+static inline void
+hex_dump_memory(struct dasd_device *device, void *data, int len)
+{
+	int *pint;
+
+	pint = (int *) data;
+	while (len > 0) {
+		DEV_MESSAGE(KERN_ERR, device, "%p: %08x %08x %08x %08x",
+			    pint, pint[0], pint[1], pint[2], pint[3]);
+		pint += 4;
+		len -= 16;
+	}
+}
+
+void
+dasd_log_sense(struct dasd_ccw_req *cqr, struct irb *irb)
+{
+	struct dasd_device *device;
+
+	device = cqr->device;
+	/* dump sense data */
+	if (device->discipline && device->discipline->dump_sense)
+		device->discipline->dump_sense(device, cqr, irb);
+}
+
+void
+dasd_log_ccw(struct dasd_ccw_req * cqr, int caller, __u32 cpa)
+{
+	struct dasd_device *device;
+	struct dasd_ccw_req *lcqr;
+	struct ccw1 *ccw;
+	int cplength;
+
+	device = cqr->device;
+	/* log the channel program */
+	for (lcqr = cqr; lcqr != NULL; lcqr = lcqr->refers) {
+		DEV_MESSAGE(KERN_ERR, device,
+			    "(%s) ERP chain report for req: %p",
+			    caller == 0 ? "EXAMINE" : "ACTION", lcqr);
+		hex_dump_memory(device, lcqr, sizeof(struct dasd_ccw_req));
+
+		cplength = 1;
+		ccw = lcqr->cpaddr;
+		while (ccw++->flags & (CCW_FLAG_DC | CCW_FLAG_CC))
+			cplength++;
+
+		if (cplength > 40) {	/* log only parts of the CP */
+			DEV_MESSAGE(KERN_ERR, device, "%s",
+				    "Start of channel program:");
+			hex_dump_memory(device, lcqr->cpaddr,
+					40*sizeof(struct ccw1));
+
+			DEV_MESSAGE(KERN_ERR, device, "%s",
+				    "End of channel program:");
+			hex_dump_memory(device, lcqr->cpaddr + cplength - 10,
+					10*sizeof(struct ccw1));
+		} else {	/* log the whole CP */
+			DEV_MESSAGE(KERN_ERR, device, "%s",
+				    "Channel program (complete):");
+			hex_dump_memory(device, lcqr->cpaddr,
+					cplength*sizeof(struct ccw1));
+		}
+
+		if (lcqr != cqr)
+			continue;
+
+		/*
+		 * Log bytes arround failed CCW but only if we did
+		 * not log the whole CP of the CCW is outside the
+		 * logged CP. 
+		 */
+		if (cplength > 40 ||
+		    ((addr_t) cpa < (addr_t) lcqr->cpaddr &&
+		     (addr_t) cpa > (addr_t) (lcqr->cpaddr + cplength + 4))) {
+			
+			DEV_MESSAGE(KERN_ERR, device,
+				    "Failed CCW (%p) (area):",
+				    (void *) (long) cpa);
+			hex_dump_memory(device, cqr->cpaddr - 10,
+					20*sizeof(struct ccw1));
+		}
+	}
+
+}				/* end log_erp_chain */
+
+EXPORT_SYMBOL(dasd_default_erp_action);
+EXPORT_SYMBOL(dasd_default_erp_postaction);
+EXPORT_SYMBOL(dasd_alloc_erp_request);
+EXPORT_SYMBOL(dasd_free_erp_request);
+EXPORT_SYMBOL(dasd_log_sense);
+EXPORT_SYMBOL(dasd_log_ccw);
diff --git a/drivers/s390/block/dasd_fba.c b/drivers/s390/block/dasd_fba.c
new file mode 100644
index 0000000..7963ae3
--- /dev/null
+++ b/drivers/s390/block/dasd_fba.c
@@ -0,0 +1,607 @@
+/* 
+ * File...........: linux/drivers/s390/block/dasd_fba.c
+ * Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
+ * Bugreports.to..: <Linux390@de.ibm.com>
+ * (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999,2000
+ *
+ * $Revision: 1.39 $
+ */
+
+#include <linux/config.h>
+#include <linux/stddef.h>
+#include <linux/kernel.h>
+#include <asm/debug.h>
+
+#include <linux/slab.h>
+#include <linux/hdreg.h>	/* HDIO_GETGEO			    */
+#include <linux/bio.h>
+#include <linux/module.h>
+#include <linux/init.h>
+
+#include <asm/idals.h>
+#include <asm/ebcdic.h>
+#include <asm/io.h>
+#include <asm/todclk.h>
+#include <asm/ccwdev.h>
+
+#include "dasd_int.h"
+#include "dasd_fba.h"
+
+#ifdef PRINTK_HEADER
+#undef PRINTK_HEADER
+#endif				/* PRINTK_HEADER */
+#define PRINTK_HEADER "dasd(fba):"
+
+#define DASD_FBA_CCW_WRITE 0x41
+#define DASD_FBA_CCW_READ 0x42
+#define DASD_FBA_CCW_LOCATE 0x43
+#define DASD_FBA_CCW_DEFINE_EXTENT 0x63
+
+MODULE_LICENSE("GPL");
+
+static struct dasd_discipline dasd_fba_discipline;
+
+struct dasd_fba_private {
+	struct dasd_fba_characteristics rdc_data;
+};
+
+static struct ccw_device_id dasd_fba_ids[] = {
+	{ CCW_DEVICE_DEVTYPE (0x6310, 0, 0x9336, 0), driver_info: 0x1},
+	{ CCW_DEVICE_DEVTYPE (0x3880, 0, 0x3370, 0), driver_info: 0x2},
+	{ /* end of list */ },
+};
+
+MODULE_DEVICE_TABLE(ccw, dasd_fba_ids);
+
+static struct ccw_driver dasd_fba_driver; /* see below */
+static int
+dasd_fba_probe(struct ccw_device *cdev)
+{
+	int ret;
+
+	ret = dasd_generic_probe (cdev, &dasd_fba_discipline);
+	if (ret)
+		return ret;
+	ccw_device_set_options(cdev, CCWDEV_DO_PATHGROUP);
+	return 0;
+}
+
+static int
+dasd_fba_set_online(struct ccw_device *cdev)
+{
+	return dasd_generic_set_online (cdev, &dasd_fba_discipline);
+}
+
+static struct ccw_driver dasd_fba_driver = {
+	.name        = "dasd-fba",
+	.owner       = THIS_MODULE,
+	.ids         = dasd_fba_ids,
+	.probe       = dasd_fba_probe,
+	.remove      = dasd_generic_remove,
+	.set_offline = dasd_generic_set_offline,
+	.set_online  = dasd_fba_set_online,
+	.notify      = dasd_generic_notify,
+};
+
+static inline void
+define_extent(struct ccw1 * ccw, struct DE_fba_data *data, int rw,
+	      int blksize, int beg, int nr)
+{
+	ccw->cmd_code = DASD_FBA_CCW_DEFINE_EXTENT;
+	ccw->flags = 0;
+	ccw->count = 16;
+	ccw->cda = (__u32) __pa(data);
+	memset(data, 0, sizeof (struct DE_fba_data));
+	if (rw == WRITE)
+		(data->mask).perm = 0x0;
+	else if (rw == READ)
+		(data->mask).perm = 0x1;
+	else
+		data->mask.perm = 0x2;
+	data->blk_size = blksize;
+	data->ext_loc = beg;
+	data->ext_end = nr - 1;
+}
+
+static inline void
+locate_record(struct ccw1 * ccw, struct LO_fba_data *data, int rw,
+	      int block_nr, int block_ct)
+{
+	ccw->cmd_code = DASD_FBA_CCW_LOCATE;
+	ccw->flags = 0;
+	ccw->count = 8;
+	ccw->cda = (__u32) __pa(data);
+	memset(data, 0, sizeof (struct LO_fba_data));
+	if (rw == WRITE)
+		data->operation.cmd = 0x5;
+	else if (rw == READ)
+		data->operation.cmd = 0x6;
+	else
+		data->operation.cmd = 0x8;
+	data->blk_nr = block_nr;
+	data->blk_ct = block_ct;
+}
+
+static int
+dasd_fba_check_characteristics(struct dasd_device *device)
+{
+	struct dasd_fba_private *private;
+	struct ccw_device *cdev = device->cdev;	
+	void *rdc_data;
+	int rc;
+
+	private = (struct dasd_fba_private *) device->private;
+	if (private == NULL) {
+		private = kmalloc(sizeof(struct dasd_fba_private), GFP_KERNEL);
+		if (private == NULL) {
+			DEV_MESSAGE(KERN_WARNING, device, "%s",
+				    "memory allocation failed for private "
+				    "data");
+			return -ENOMEM;
+		}
+		device->private = (void *) private;
+	}
+	/* Read Device Characteristics */
+	rdc_data = (void *) &(private->rdc_data);
+	rc = read_dev_chars(device->cdev, &rdc_data, 32);
+	if (rc) {
+		DEV_MESSAGE(KERN_WARNING, device,
+			    "Read device characteristics returned error %d",
+			    rc);
+		return rc;
+	}
+
+	DEV_MESSAGE(KERN_INFO, device,
+		    "%04X/%02X(CU:%04X/%02X) %dMB at(%d B/blk)",
+		    cdev->id.dev_type,
+		    cdev->id.dev_model,
+		    cdev->id.cu_type,
+		    cdev->id.cu_model,
+		    ((private->rdc_data.blk_bdsa *
+		      (private->rdc_data.blk_size >> 9)) >> 11),
+		    private->rdc_data.blk_size);
+	return 0;
+}
+
+static int
+dasd_fba_do_analysis(struct dasd_device *device)
+{
+	struct dasd_fba_private *private;
+	int sb, rc;
+
+	private = (struct dasd_fba_private *) device->private;
+	rc = dasd_check_blocksize(private->rdc_data.blk_size);
+	if (rc) {
+		DEV_MESSAGE(KERN_INFO, device, "unknown blocksize %d",
+			    private->rdc_data.blk_size);
+		return rc;
+	}
+	device->blocks = private->rdc_data.blk_bdsa;
+	device->bp_block = private->rdc_data.blk_size;
+	device->s2b_shift = 0;	/* bits to shift 512 to get a block */
+	for (sb = 512; sb < private->rdc_data.blk_size; sb = sb << 1)
+		device->s2b_shift++;
+	return 0;
+}
+
+static int
+dasd_fba_fill_geometry(struct dasd_device *device, struct hd_geometry *geo)
+{
+	if (dasd_check_blocksize(device->bp_block) != 0)
+		return -EINVAL;
+	geo->cylinders = (device->blocks << device->s2b_shift) >> 10;
+	geo->heads = 16;
+	geo->sectors = 128 >> device->s2b_shift;
+	return 0;
+}
+
+static dasd_era_t
+dasd_fba_examine_error(struct dasd_ccw_req * cqr, struct irb * irb)
+{
+	struct dasd_device *device;
+	struct ccw_device *cdev;
+
+	device = (struct dasd_device *) cqr->device;
+	if (irb->scsw.cstat == 0x00 &&
+	    irb->scsw.dstat == (DEV_STAT_CHN_END | DEV_STAT_DEV_END))
+		return dasd_era_none;
+	
+	cdev = device->cdev;
+	switch (cdev->id.dev_type) {
+	case 0x3370:
+		return dasd_3370_erp_examine(cqr, irb);
+	case 0x9336:
+		return dasd_9336_erp_examine(cqr, irb);
+	default:
+		return dasd_era_recover;
+	}
+}
+
+static dasd_erp_fn_t
+dasd_fba_erp_action(struct dasd_ccw_req * cqr)
+{
+	return dasd_default_erp_action;
+}
+
+static dasd_erp_fn_t
+dasd_fba_erp_postaction(struct dasd_ccw_req * cqr)
+{
+	if (cqr->function == dasd_default_erp_action)
+		return dasd_default_erp_postaction;
+
+	DEV_MESSAGE(KERN_WARNING, cqr->device, "unknown ERP action %p",
+		    cqr->function);
+	return NULL;
+}
+
+static struct dasd_ccw_req *
+dasd_fba_build_cp(struct dasd_device * device, struct request *req)
+{
+	struct dasd_fba_private *private;
+	unsigned long *idaws;
+	struct LO_fba_data *LO_data;
+	struct dasd_ccw_req *cqr;
+	struct ccw1 *ccw;
+	struct bio *bio;
+	struct bio_vec *bv;
+	char *dst;
+	int count, cidaw, cplength, datasize;
+	sector_t recid, first_rec, last_rec;
+	unsigned int blksize, off;
+	unsigned char cmd;
+	int i;
+
+	private = (struct dasd_fba_private *) device->private;
+	if (rq_data_dir(req) == READ) {
+		cmd = DASD_FBA_CCW_READ;
+	} else if (rq_data_dir(req) == WRITE) {
+		cmd = DASD_FBA_CCW_WRITE;
+	} else
+		return ERR_PTR(-EINVAL);
+	blksize = device->bp_block;
+	/* Calculate record id of first and last block. */
+	first_rec = req->sector >> device->s2b_shift;
+	last_rec = (req->sector + req->nr_sectors - 1) >> device->s2b_shift;
+	/* Check struct bio and count the number of blocks for the request. */
+	count = 0;
+	cidaw = 0;
+	rq_for_each_bio(bio, req) {
+		bio_for_each_segment(bv, bio, i) {
+			if (bv->bv_len & (blksize - 1))
+				/* Fba can only do full blocks. */
+				return ERR_PTR(-EINVAL);
+			count += bv->bv_len >> (device->s2b_shift + 9);
+#if defined(CONFIG_ARCH_S390X)
+			if (idal_is_needed (page_address(bv->bv_page),
+					    bv->bv_len))
+				cidaw += bv->bv_len / blksize;
+#endif
+		}
+	}
+	/* Paranoia. */
+	if (count != last_rec - first_rec + 1)
+		return ERR_PTR(-EINVAL);
+	/* 1x define extent + 1x locate record + number of blocks */
+	cplength = 2 + count;
+	/* 1x define extent + 1x locate record */
+	datasize = sizeof(struct DE_fba_data) + sizeof(struct LO_fba_data) +
+		cidaw * sizeof(unsigned long);
+	/*
+	 * Find out number of additional locate record ccws if the device
+	 * can't do data chaining.
+	 */
+	if (private->rdc_data.mode.bits.data_chain == 0) {
+		cplength += count - 1;
+		datasize += (count - 1)*sizeof(struct LO_fba_data);
+	}
+	/* Allocate the ccw request. */
+	cqr = dasd_smalloc_request(dasd_fba_discipline.name,
+				   cplength, datasize, device);
+	if (IS_ERR(cqr))
+		return cqr;
+	ccw = cqr->cpaddr;
+	/* First ccw is define extent. */
+	define_extent(ccw++, cqr->data, rq_data_dir(req),
+		      device->bp_block, req->sector, req->nr_sectors);
+	/* Build locate_record + read/write ccws. */
+	idaws = (unsigned long *) (cqr->data + sizeof(struct DE_fba_data));
+	LO_data = (struct LO_fba_data *) (idaws + cidaw);
+	/* Locate record for all blocks for smart devices. */
+	if (private->rdc_data.mode.bits.data_chain != 0) {
+		ccw[-1].flags |= CCW_FLAG_CC;
+		locate_record(ccw++, LO_data++, rq_data_dir(req), 0, count);
+	}
+	recid = first_rec;
+	rq_for_each_bio(bio, req) bio_for_each_segment(bv, bio, i) {
+		dst = page_address(bv->bv_page) + bv->bv_offset;
+		if (dasd_page_cache) {
+			char *copy = kmem_cache_alloc(dasd_page_cache,
+						      SLAB_DMA | __GFP_NOWARN);
+			if (copy && rq_data_dir(req) == WRITE)
+				memcpy(copy + bv->bv_offset, dst, bv->bv_len);
+			if (copy)
+				dst = copy + bv->bv_offset;
+		}
+		for (off = 0; off < bv->bv_len; off += blksize) {
+			/* Locate record for stupid devices. */
+			if (private->rdc_data.mode.bits.data_chain == 0) {
+				ccw[-1].flags |= CCW_FLAG_CC;
+				locate_record(ccw, LO_data++,
+					      rq_data_dir(req),
+					      recid - first_rec, 1);
+				ccw->flags = CCW_FLAG_CC;
+				ccw++;
+			} else {
+				if (recid > first_rec)
+					ccw[-1].flags |= CCW_FLAG_DC;
+				else
+					ccw[-1].flags |= CCW_FLAG_CC;
+			}
+			ccw->cmd_code = cmd;
+			ccw->count = device->bp_block;
+			if (idal_is_needed(dst, blksize)) {
+				ccw->cda = (__u32)(addr_t) idaws;
+				ccw->flags = CCW_FLAG_IDA;
+				idaws = idal_create_words(idaws, dst, blksize);
+			} else {
+				ccw->cda = (__u32)(addr_t) dst;
+				ccw->flags = 0;
+			}
+			ccw++;
+			dst += blksize;
+			recid++;
+		}
+	}
+	cqr->device = device;
+	cqr->expires = 5 * 60 * HZ;	/* 5 minutes */
+	cqr->status = DASD_CQR_FILLED;
+	return cqr;
+}
+
+static int
+dasd_fba_free_cp(struct dasd_ccw_req *cqr, struct request *req)
+{
+	struct dasd_fba_private *private;
+	struct ccw1 *ccw;
+	struct bio *bio;
+	struct bio_vec *bv;
+	char *dst, *cda;
+	unsigned int blksize, off;
+	int i, status;
+
+	if (!dasd_page_cache)
+		goto out;
+	private = (struct dasd_fba_private *) cqr->device->private;
+	blksize = cqr->device->bp_block;
+	ccw = cqr->cpaddr;
+	/* Skip over define extent & locate record. */
+	ccw++;
+	if (private->rdc_data.mode.bits.data_chain != 0)
+		ccw++;
+	rq_for_each_bio(bio, req) bio_for_each_segment(bv, bio, i) {
+		dst = page_address(bv->bv_page) + bv->bv_offset;
+		for (off = 0; off < bv->bv_len; off += blksize) {
+			/* Skip locate record. */
+			if (private->rdc_data.mode.bits.data_chain == 0)
+				ccw++;
+			if (dst) {
+				if (ccw->flags & CCW_FLAG_IDA)
+					cda = *((char **)((addr_t) ccw->cda));
+				else
+					cda = (char *)((addr_t) ccw->cda);
+				if (dst != cda) {
+					if (rq_data_dir(req) == READ)
+						memcpy(dst, cda, bv->bv_len);
+					kmem_cache_free(dasd_page_cache,
+					    (void *)((addr_t)cda & PAGE_MASK));
+				}
+				dst = NULL;
+			}
+			ccw++;
+		}
+	}
+out:
+	status = cqr->status == DASD_CQR_DONE;
+	dasd_sfree_request(cqr, cqr->device);
+	return status;
+}
+
+static int
+dasd_fba_fill_info(struct dasd_device * device,
+		   struct dasd_information2_t * info)
+{
+	info->label_block = 1;
+	info->FBA_layout = 1;
+	info->format = DASD_FORMAT_LDL;
+	info->characteristics_size = sizeof(struct dasd_fba_characteristics);
+	memcpy(info->characteristics,
+	       &((struct dasd_fba_private *) device->private)->rdc_data,
+	       sizeof (struct dasd_fba_characteristics));
+	info->confdata_size = 0;
+	return 0;
+}
+
+static void
+dasd_fba_dump_sense(struct dasd_device *device, struct dasd_ccw_req * req,
+		    struct irb *irb)
+{
+	char *page;
+	struct ccw1 *act, *end, *last;
+	int len, sl, sct, count;
+
+	page = (char *) get_zeroed_page(GFP_ATOMIC);
+	if (page == NULL) {
+		DEV_MESSAGE(KERN_ERR, device, " %s",
+			    "No memory to dump sense data");
+		return;
+	}
+	len = sprintf(page, KERN_ERR PRINTK_HEADER
+		      " I/O status report for device %s:\n",
+		      device->cdev->dev.bus_id);
+	len += sprintf(page + len, KERN_ERR PRINTK_HEADER
+		       " in req: %p CS: 0x%02X DS: 0x%02X\n", req,
+		       irb->scsw.cstat, irb->scsw.dstat);
+	len += sprintf(page + len, KERN_ERR PRINTK_HEADER
+		       " device %s: Failing CCW: %p\n",
+		       device->cdev->dev.bus_id,
+		       (void *) (addr_t) irb->scsw.cpa);
+	if (irb->esw.esw0.erw.cons) {
+		for (sl = 0; sl < 4; sl++) {
+			len += sprintf(page + len, KERN_ERR PRINTK_HEADER
+				       " Sense(hex) %2d-%2d:",
+				       (8 * sl), ((8 * sl) + 7));
+
+			for (sct = 0; sct < 8; sct++) {
+				len += sprintf(page + len, " %02x",
+					       irb->ecw[8 * sl + sct]);
+			}
+			len += sprintf(page + len, "\n");
+		}
+	} else {
+	        len += sprintf(page + len, KERN_ERR PRINTK_HEADER
+			       " SORRY - NO VALID SENSE AVAILABLE\n");
+	}
+	MESSAGE_LOG(KERN_ERR, "%s",
+		    page + sizeof(KERN_ERR PRINTK_HEADER));
+
+	/* dump the Channel Program */
+	/* print first CCWs (maximum 8) */
+	act = req->cpaddr;
+        for (last = act; last->flags & (CCW_FLAG_CC | CCW_FLAG_DC); last++);
+	end = min(act + 8, last);
+	len = sprintf(page, KERN_ERR PRINTK_HEADER
+		      " Related CP in req: %p\n", req);
+	while (act <= end) {
+		len += sprintf(page + len, KERN_ERR PRINTK_HEADER
+			       " CCW %p: %08X %08X DAT:",
+			       act, ((int *) act)[0], ((int *) act)[1]);
+		for (count = 0; count < 32 && count < act->count;
+		     count += sizeof(int))
+			len += sprintf(page + len, " %08X",
+				       ((int *) (addr_t) act->cda)
+				       [(count>>2)]);
+		len += sprintf(page + len, "\n");
+		act++;
+	}
+	MESSAGE_LOG(KERN_ERR, "%s",
+		    page + sizeof(KERN_ERR PRINTK_HEADER));
+
+
+	/* print failing CCW area */
+	len = 0;
+	if (act <  ((struct ccw1 *)(addr_t) irb->scsw.cpa) - 2) {
+		act = ((struct ccw1 *)(addr_t) irb->scsw.cpa) - 2;
+		len += sprintf(page + len, KERN_ERR PRINTK_HEADER "......\n");
+	}
+	end = min((struct ccw1 *)(addr_t) irb->scsw.cpa + 2, last);
+	while (act <= end) {
+		len += sprintf(page + len, KERN_ERR PRINTK_HEADER
+			       " CCW %p: %08X %08X DAT:",
+			       act, ((int *) act)[0], ((int *) act)[1]);
+		for (count = 0; count < 32 && count < act->count;
+		     count += sizeof(int))
+			len += sprintf(page + len, " %08X",
+				       ((int *) (addr_t) act->cda)
+				       [(count>>2)]);
+		len += sprintf(page + len, "\n");
+		act++;
+	}
+
+	/* print last CCWs */
+	if (act <  last - 2) {
+		act = last - 2;
+		len += sprintf(page + len, KERN_ERR PRINTK_HEADER "......\n");
+	}
+	while (act <= last) {
+		len += sprintf(page + len, KERN_ERR PRINTK_HEADER
+			       " CCW %p: %08X %08X DAT:",
+			       act, ((int *) act)[0], ((int *) act)[1]);
+		for (count = 0; count < 32 && count < act->count;
+		     count += sizeof(int))
+			len += sprintf(page + len, " %08X",
+				       ((int *) (addr_t) act->cda)
+				       [(count>>2)]);
+		len += sprintf(page + len, "\n");
+		act++;
+	}
+	if (len > 0)
+		MESSAGE_LOG(KERN_ERR, "%s",
+			    page + sizeof(KERN_ERR PRINTK_HEADER));
+	free_page((unsigned long) page);
+}
+
+/*
+ * max_blocks is dependent on the amount of storage that is available
+ * in the static io buffer for each device. Currently each device has
+ * 8192 bytes (=2 pages). For 64 bit one dasd_mchunkt_t structure has
+ * 24 bytes, the struct dasd_ccw_req has 136 bytes and each block can use
+ * up to 16 bytes (8 for the ccw and 8 for the idal pointer). In
+ * addition we have one define extent ccw + 16 bytes of data and a 
+ * locate record ccw for each block (stupid devices!) + 16 bytes of data.
+ * That makes:
+ * (8192 - 24 - 136 - 8 - 16) / 40 = 200.2 blocks at maximum.
+ * We want to fit two into the available memory so that we can immediately
+ * start the next request if one finishes off. That makes 100.1 blocks
+ * for one request. Give a little safety and the result is 96.
+ */
+static struct dasd_discipline dasd_fba_discipline = {
+	.owner = THIS_MODULE,
+	.name = "FBA ",
+	.ebcname = "FBA ",
+	.max_blocks = 96,
+	.check_device = dasd_fba_check_characteristics,
+	.do_analysis = dasd_fba_do_analysis,
+	.fill_geometry = dasd_fba_fill_geometry,
+	.start_IO = dasd_start_IO,
+	.term_IO = dasd_term_IO,
+	.examine_error = dasd_fba_examine_error,
+	.erp_action = dasd_fba_erp_action,
+	.erp_postaction = dasd_fba_erp_postaction,
+	.build_cp = dasd_fba_build_cp,
+	.free_cp = dasd_fba_free_cp,
+	.dump_sense = dasd_fba_dump_sense,
+	.fill_info = dasd_fba_fill_info,
+};
+
+static int __init
+dasd_fba_init(void)
+{
+	int ret;
+
+	ASCEBC(dasd_fba_discipline.ebcname, 4);
+
+	ret = ccw_driver_register(&dasd_fba_driver);
+	if (ret)
+		return ret;
+
+	dasd_generic_auto_online(&dasd_fba_driver);
+	return 0;
+}
+
+static void __exit
+dasd_fba_cleanup(void)
+{
+	ccw_driver_unregister(&dasd_fba_driver);
+}
+
+module_init(dasd_fba_init);
+module_exit(dasd_fba_cleanup);
+
+/*
+ * Overrides for Emacs so that we follow Linus's tabbing style.
+ * Emacs will notice this stuff at the end of the file and automatically
+ * adjust the settings for this buffer only.  This must remain at the end
+ * of the file.
+ * ---------------------------------------------------------------------------
+ * Local variables:
+ * c-indent-level: 4 
+ * c-brace-imaginary-offset: 0
+ * c-brace-offset: -4
+ * c-argdecl-indent: 4
+ * c-label-offset: -4
+ * c-continued-statement-offset: 4
+ * c-continued-brace-offset: 0
+ * indent-tabs-mode: 1
+ * tab-width: 8
+ * End:
+ */
diff --git a/drivers/s390/block/dasd_fba.h b/drivers/s390/block/dasd_fba.h
new file mode 100644
index 0000000..624f040
--- /dev/null
+++ b/drivers/s390/block/dasd_fba.h
@@ -0,0 +1,73 @@
+/* 
+ * File...........: linux/drivers/s390/block/dasd_fba.h
+ * Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
+ * Bugreports.to..: <Linux390@de.ibm.com>
+ * (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999,2000
+ *
+ * $Revision: 1.6 $
+ */
+
+#ifndef DASD_FBA_H
+#define DASD_FBA_H
+
+struct DE_fba_data {
+	struct {
+		unsigned char perm:2;	/* Permissions on this extent */
+		unsigned char zero:2;	/* Must be zero */
+		unsigned char da:1;	/* usually zero */
+		unsigned char diag:1;	/* allow diagnose */
+		unsigned char zero2:2;	/* zero */
+	} __attribute__ ((packed)) mask;
+	__u8 zero;		/* Must be zero */
+	__u16 blk_size;		/* Blocksize */
+	__u32 ext_loc;		/* Extent locator */
+	__u32 ext_beg;		/* logical number of block 0 in extent */
+	__u32 ext_end;		/* logocal number of last block in extent */
+} __attribute__ ((packed));
+
+struct LO_fba_data {
+	struct {
+		unsigned char zero:4;
+		unsigned char cmd:4;
+	} __attribute__ ((packed)) operation;
+	__u8 auxiliary;
+	__u16 blk_ct;
+	__u32 blk_nr;
+} __attribute__ ((packed));
+
+struct dasd_fba_characteristics {
+	union {
+		__u8 c;
+		struct {
+			unsigned char reserved:1;
+			unsigned char overrunnable:1;
+			unsigned char burst_byte:1;
+			unsigned char data_chain:1;
+			unsigned char zeros:4;
+		} __attribute__ ((packed)) bits;
+	} __attribute__ ((packed)) mode;
+	union {
+		__u8 c;
+		struct {
+			unsigned char zero0:1;
+			unsigned char removable:1;
+			unsigned char shared:1;
+			unsigned char zero1:1;
+			unsigned char mam:1;
+			unsigned char zeros:3;
+		} __attribute__ ((packed)) bits;
+	} __attribute__ ((packed)) features;
+	__u8 dev_class;
+	__u8 unit_type;
+	__u16 blk_size;
+	__u32 blk_per_cycl;
+	__u32 blk_per_bound;
+	__u32 blk_bdsa;
+	__u32 reserved0;
+	__u16 reserved1;
+	__u16 blk_ce;
+	__u32 reserved2;
+	__u16 reserved3;
+} __attribute__ ((packed));
+
+#endif				/* DASD_FBA_H */
diff --git a/drivers/s390/block/dasd_genhd.c b/drivers/s390/block/dasd_genhd.c
new file mode 100644
index 0000000..1d52db4
--- /dev/null
+++ b/drivers/s390/block/dasd_genhd.c
@@ -0,0 +1,185 @@
+/*
+ * File...........: linux/drivers/s390/block/dasd_genhd.c
+ * Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
+ *		    Horst Hummel <Horst.Hummel@de.ibm.com>
+ *		    Carsten Otte <Cotte@de.ibm.com>
+ *		    Martin Schwidefsky <schwidefsky@de.ibm.com>
+ * Bugreports.to..: <Linux390@de.ibm.com>
+ * (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999-2001
+ *
+ * gendisk related functions for the dasd driver.
+ *
+ * $Revision: 1.48 $
+ */
+
+#include <linux/config.h>
+#include <linux/interrupt.h>
+#include <linux/fs.h>
+#include <linux/blkpg.h>
+
+#include <asm/uaccess.h>
+
+/* This is ugly... */
+#define PRINTK_HEADER "dasd_gendisk:"
+
+#include "dasd_int.h"
+
+/*
+ * Allocate and register gendisk structure for device.
+ */
+int
+dasd_gendisk_alloc(struct dasd_device *device)
+{
+	struct gendisk *gdp;
+	int len;
+
+	/* Make sure the minor for this device exists. */
+	if (device->devindex >= DASD_PER_MAJOR)
+		return -EBUSY;
+
+	gdp = alloc_disk(1 << DASD_PARTN_BITS);
+	if (!gdp)
+		return -ENOMEM;
+
+	/* Initialize gendisk structure. */
+	gdp->major = DASD_MAJOR;
+	gdp->first_minor = device->devindex << DASD_PARTN_BITS;
+	gdp->fops = &dasd_device_operations;
+	gdp->driverfs_dev = &device->cdev->dev;
+
+	/*
+	 * Set device name.
+	 *   dasda - dasdz : 26 devices
+	 *   dasdaa - dasdzz : 676 devices, added up = 702
+	 *   dasdaaa - dasdzzz : 17576 devices, added up = 18278
+	 *   dasdaaaa - dasdzzzz : 456976 devices, added up = 475252
+	 */
+	len = sprintf(gdp->disk_name, "dasd");
+	if (device->devindex > 25) {
+	        if (device->devindex > 701) {
+		        if (device->devindex > 18277)
+			        len += sprintf(gdp->disk_name + len, "%c",
+					       'a'+(((device->devindex-18278)
+						     /17576)%26));
+			len += sprintf(gdp->disk_name + len, "%c",
+				       'a'+(((device->devindex-702)/676)%26));
+		}
+		len += sprintf(gdp->disk_name + len, "%c",
+			       'a'+(((device->devindex-26)/26)%26));
+	}
+	len += sprintf(gdp->disk_name + len, "%c", 'a'+(device->devindex%26));
+
+ 	sprintf(gdp->devfs_name, "dasd/%s", device->cdev->dev.bus_id);
+
+	if (test_bit(DASD_FLAG_RO, &device->flags))
+		set_disk_ro(gdp, 1);
+	gdp->private_data = device;
+	gdp->queue = device->request_queue;
+	device->gdp = gdp;
+	set_capacity(device->gdp, 0);
+	add_disk(device->gdp);
+	return 0;
+}
+
+/*
+ * Unregister and free gendisk structure for device.
+ */
+void
+dasd_gendisk_free(struct dasd_device *device)
+{
+	del_gendisk(device->gdp);
+	device->gdp->queue = 0;
+	put_disk(device->gdp);
+	device->gdp = 0;
+}
+
+/*
+ * Trigger a partition detection.
+ */
+int
+dasd_scan_partitions(struct dasd_device * device)
+{
+	struct block_device *bdev;
+
+	/* Make the disk known. */
+	set_capacity(device->gdp, device->blocks << device->s2b_shift);
+	bdev = bdget_disk(device->gdp, 0);
+	if (!bdev || blkdev_get(bdev, FMODE_READ, 1) < 0)
+		return -ENODEV;
+	/*
+	 * See fs/partition/check.c:register_disk,rescan_partitions
+	 * Can't call rescan_partitions directly. Use ioctl.
+	 */
+	ioctl_by_bdev(bdev, BLKRRPART, 0);
+	/*
+	 * Since the matching blkdev_put call to the blkdev_get in
+	 * this function is not called before dasd_destroy_partitions
+	 * the offline open_count limit needs to be increased from
+	 * 0 to 1. This is done by setting device->bdev (see
+	 * dasd_generic_set_offline). As long as the partition
+	 * detection is running no offline should be allowed. That
+	 * is why the assignment to device->bdev is done AFTER
+	 * the BLKRRPART ioctl.
+	 */
+	device->bdev = bdev;
+	return 0;
+}
+
+/*
+ * Remove all inodes in the system for a device, delete the
+ * partitions and make device unusable by setting its size to zero.
+ */
+void
+dasd_destroy_partitions(struct dasd_device * device)
+{
+	/* The two structs have 168/176 byte on 31/64 bit. */
+	struct blkpg_partition bpart;
+	struct blkpg_ioctl_arg barg;
+	struct block_device *bdev;
+
+	/*
+	 * Get the bdev pointer from the device structure and clear
+	 * device->bdev to lower the offline open_count limit again.
+	 */
+	bdev = device->bdev;
+	device->bdev = 0;
+
+	/*
+	 * See fs/partition/check.c:delete_partition
+	 * Can't call delete_partitions directly. Use ioctl.
+	 * The ioctl also does locking and invalidation.
+	 */
+	memset(&bpart, 0, sizeof(struct blkpg_partition));
+	memset(&barg, 0, sizeof(struct blkpg_ioctl_arg));
+	barg.data = &bpart;
+	barg.op = BLKPG_DEL_PARTITION;
+	for (bpart.pno = device->gdp->minors - 1; bpart.pno > 0; bpart.pno--)
+		ioctl_by_bdev(bdev, BLKPG, (unsigned long) &barg);
+
+	invalidate_partition(device->gdp, 0);
+	/* Matching blkdev_put to the blkdev_get in dasd_scan_partitions. */
+	blkdev_put(bdev);
+	set_capacity(device->gdp, 0);
+}
+
+int
+dasd_gendisk_init(void)
+{
+	int rc;
+
+	/* Register to static dasd major 94 */
+	rc = register_blkdev(DASD_MAJOR, "dasd");
+	if (rc != 0) {
+		MESSAGE(KERN_WARNING,
+			"Couldn't register successfully to "
+			"major no %d", DASD_MAJOR);
+		return rc;
+	}
+	return 0;
+}
+
+void
+dasd_gendisk_exit(void)
+{
+	unregister_blkdev(DASD_MAJOR, "dasd");
+}
diff --git a/drivers/s390/block/dasd_int.h b/drivers/s390/block/dasd_int.h
new file mode 100644
index 0000000..4586e0e
--- /dev/null
+++ b/drivers/s390/block/dasd_int.h
@@ -0,0 +1,576 @@
+/* 
+ * File...........: linux/drivers/s390/block/dasd_int.h
+ * Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
+ *                  Horst Hummel <Horst.Hummel@de.ibm.com> 
+ *		    Martin Schwidefsky <schwidefsky@de.ibm.com>
+ * Bugreports.to..: <Linux390@de.ibm.com>
+ * (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999,2000
+ *
+ * $Revision: 1.63 $
+ */
+
+#ifndef DASD_INT_H
+#define DASD_INT_H
+
+#ifdef __KERNEL__
+
+/* erp debugging in dasd.c and dasd_3990_erp.c */
+#define ERP_DEBUG
+
+
+/* we keep old device allocation scheme; IOW, minors are still in 0..255 */
+#define DASD_PER_MAJOR (1U << (MINORBITS - DASD_PARTN_BITS))
+#define DASD_PARTN_MASK ((1 << DASD_PARTN_BITS) - 1)
+
+/*
+ * States a dasd device can have:
+ *   new: the dasd_device structure is allocated.
+ *   known: the discipline for the device is identified.
+ *   basic: the device can do basic i/o.
+ *   accept: the device is analysed (format is known).
+ *   ready: partition detection is done and the device is can do block io.
+ *   online: the device accepts requests from the block device queue.
+ *
+ * Things to do for startup state transitions:
+ *   new -> known: find discipline for the device and create devfs entries.
+ *   known -> basic: request irq line for the device.
+ *   basic -> ready: do the initial analysis, e.g. format detection,
+ *                   do block device setup and detect partitions.
+ *   ready -> online: schedule the device tasklet.
+ * Things to do for shutdown state transitions:
+ *   online -> ready: just set the new device state.
+ *   ready -> basic: flush requests from the block device layer, clear
+ *                   partition information and reset format information.
+ *   basic -> known: terminate all requests and free irq.
+ *   known -> new: remove devfs entries and forget discipline.
+ */
+
+#define DASD_STATE_NEW	  0
+#define DASD_STATE_KNOWN  1
+#define DASD_STATE_BASIC  2
+#define DASD_STATE_READY  3
+#define DASD_STATE_ONLINE 4
+
+#include <linux/module.h>
+#include <linux/wait.h>
+#include <linux/blkdev.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/genhd.h>
+#include <linux/hdreg.h>
+#include <linux/interrupt.h>
+#include <asm/ccwdev.h>
+#include <linux/workqueue.h>
+#include <asm/debug.h>
+#include <asm/dasd.h>
+#include <asm/idals.h>
+
+/*
+ * SECTION: Type definitions
+ */
+struct dasd_device;
+
+typedef int (*dasd_ioctl_fn_t) (struct block_device *bdev, int no, long args);
+
+struct dasd_ioctl {
+	struct list_head list;
+	struct module *owner;
+	int no;
+	dasd_ioctl_fn_t handler;
+};
+
+typedef enum {
+	dasd_era_fatal = -1,	/* no chance to recover		     */
+	dasd_era_none = 0,	/* don't recover, everything alright */
+	dasd_era_msg = 1,	/* don't recover, just report...     */
+	dasd_era_recover = 2	/* recovery action recommended	     */
+} dasd_era_t;
+
+/* BIT DEFINITIONS FOR SENSE DATA */
+#define DASD_SENSE_BIT_0 0x80
+#define DASD_SENSE_BIT_1 0x40
+#define DASD_SENSE_BIT_2 0x20
+#define DASD_SENSE_BIT_3 0x10
+
+/*
+ * SECTION: MACROs for klogd and s390 debug feature (dbf)
+ */
+#define DBF_DEV_EVENT(d_level, d_device, d_str, d_data...) \
+do { \
+	debug_sprintf_event(d_device->debug_area, \
+			    d_level, \
+			    d_str "\n", \
+			    d_data); \
+} while(0)
+
+#define DBF_DEV_EXC(d_level, d_device, d_str, d_data...) \
+do { \
+	debug_sprintf_exception(d_device->debug_area, \
+				d_level, \
+				d_str "\n", \
+				d_data); \
+} while(0)
+
+#define DBF_EVENT(d_level, d_str, d_data...)\
+do { \
+	debug_sprintf_event(dasd_debug_area, \
+			    d_level,\
+			    d_str "\n", \
+			    d_data); \
+} while(0)
+
+#define DBF_EXC(d_level, d_str, d_data...)\
+do { \
+	debug_sprintf_exception(dasd_debug_area, \
+				d_level,\
+				d_str "\n", \
+				d_data); \
+} while(0)
+
+/* definition of dbf debug levels */
+#define	DBF_EMERG	0	/* system is unusable			*/
+#define	DBF_ALERT	1	/* action must be taken immediately	*/
+#define	DBF_CRIT	2	/* critical conditions			*/
+#define	DBF_ERR		3	/* error conditions			*/
+#define	DBF_WARNING	4	/* warning conditions			*/
+#define	DBF_NOTICE	5	/* normal but significant condition	*/
+#define	DBF_INFO	6	/* informational			*/
+#define	DBF_DEBUG	6	/* debug-level messages			*/
+
+/* messages to be written via klogd and dbf */
+#define DEV_MESSAGE(d_loglevel,d_device,d_string,d_args...)\
+do { \
+	printk(d_loglevel PRINTK_HEADER " %s: " d_string "\n", \
+	       d_device->cdev->dev.bus_id, d_args); \
+	DBF_DEV_EVENT(DBF_ALERT, d_device, d_string, d_args); \
+} while(0)
+
+#define MESSAGE(d_loglevel,d_string,d_args...)\
+do { \
+	printk(d_loglevel PRINTK_HEADER " " d_string "\n", d_args); \
+	DBF_EVENT(DBF_ALERT, d_string, d_args); \
+} while(0)
+
+/* messages to be written via klogd only */
+#define DEV_MESSAGE_LOG(d_loglevel,d_device,d_string,d_args...)\
+do { \
+	printk(d_loglevel PRINTK_HEADER " %s: " d_string "\n", \
+	       d_device->cdev->dev.bus_id, d_args); \
+} while(0)
+
+#define MESSAGE_LOG(d_loglevel,d_string,d_args...)\
+do { \
+	printk(d_loglevel PRINTK_HEADER " " d_string "\n", d_args); \
+} while(0)
+
+struct dasd_ccw_req {
+	unsigned int magic;		/* Eye catcher */
+        struct list_head list;		/* list_head for request queueing. */
+
+	/* Where to execute what... */
+	struct dasd_device *device;	/* device the request is for */
+	struct ccw1 *cpaddr;		/* address of channel program */
+	char status;	        	/* status of this request */
+	short retries;			/* A retry counter */
+	unsigned long flags;        	/* flags of this request */
+
+	/* ... and how */
+	unsigned long starttime;	/* jiffies time of request start */
+	int expires;			/* expiration period in jiffies */
+	char lpm;               	/* logical path mask */
+	void *data;			/* pointer to data area */
+
+	/* these are important for recovering erroneous requests          */
+	struct irb irb;			/* device status in case of an error */
+	struct dasd_ccw_req *refers;	/* ERP-chain queueing. */
+	void *function; 		/* originating ERP action */
+
+	/* these are for statistics only */
+	unsigned long long buildclk;	/* TOD-clock of request generation */
+	unsigned long long startclk;	/* TOD-clock of request start */
+	unsigned long long stopclk;	/* TOD-clock of request interrupt */
+	unsigned long long endclk;	/* TOD-clock of request termination */
+
+        /* Callback that is called after reaching final status. */
+        void (*callback)(struct dasd_ccw_req *, void *data);
+        void *callback_data;
+};
+
+/* 
+ * dasd_ccw_req -> status can be:
+ */
+#define DASD_CQR_FILLED   0x00	/* request is ready to be processed */
+#define DASD_CQR_QUEUED   0x01	/* request is queued to be processed */
+#define DASD_CQR_IN_IO    0x02	/* request is currently in IO */
+#define DASD_CQR_DONE     0x03	/* request is completed successfully */
+#define DASD_CQR_ERROR    0x04	/* request is completed with error */
+#define DASD_CQR_FAILED   0x05	/* request is finally failed */
+#define DASD_CQR_CLEAR    0x06	/* request is clear pending */
+
+/* per dasd_ccw_req flags */
+#define DASD_CQR_FLAGS_USE_ERP   0	/* use ERP for this request */
+
+/* Signature for error recovery functions. */
+typedef struct dasd_ccw_req *(*dasd_erp_fn_t) (struct dasd_ccw_req *);
+
+/*
+ * the struct dasd_discipline is
+ * sth like a table of virtual functions, if you think of dasd_eckd
+ * inheriting dasd...
+ * no, currently we are not planning to reimplement the driver in C++
+ */
+struct dasd_discipline {
+	struct module *owner;
+	char ebcname[8];	/* a name used for tagging and printks */
+	char name[8];		/* a name used for tagging and printks */
+	int max_blocks;		/* maximum number of blocks to be chained */
+
+	struct list_head list;	/* used for list of disciplines */
+
+        /*
+         * Device recognition functions. check_device is used to verify
+         * the sense data and the information returned by read device
+         * characteristics. It returns 0 if the discipline can be used
+         * for the device in question.
+         * do_analysis is used in the step from device state "basic" to
+         * state "accept". It returns 0 if the device can be made ready,
+         * it returns -EMEDIUMTYPE if the device can't be made ready or
+         * -EAGAIN if do_analysis started a ccw that needs to complete
+         * before the analysis may be repeated.
+         */
+        int (*check_device)(struct dasd_device *);
+	int (*do_analysis) (struct dasd_device *);
+
+        /*
+         * Device operation functions. build_cp creates a ccw chain for
+         * a block device request, start_io starts the request and
+         * term_IO cancels it (e.g. in case of a timeout). format_device
+         * returns a ccw chain to be used to format the device.
+         */
+	struct dasd_ccw_req *(*build_cp) (struct dasd_device *,
+					  struct request *);
+	int (*start_IO) (struct dasd_ccw_req *);
+	int (*term_IO) (struct dasd_ccw_req *);
+	struct dasd_ccw_req *(*format_device) (struct dasd_device *,
+					       struct format_data_t *);
+	int (*free_cp) (struct dasd_ccw_req *, struct request *);
+        /*
+         * Error recovery functions. examine_error() returns a value that
+         * indicates what to do for an error condition. If examine_error()
+         * returns 'dasd_era_recover' erp_action() is called to create a 
+         * special error recovery ccw. erp_postaction() is called after
+         * an error recovery ccw has finished its execution. dump_sense
+         * is called for every error condition to print the sense data
+         * to the console.
+         */
+	dasd_era_t(*examine_error) (struct dasd_ccw_req *, struct irb *);
+	dasd_erp_fn_t(*erp_action) (struct dasd_ccw_req *);
+	dasd_erp_fn_t(*erp_postaction) (struct dasd_ccw_req *);
+	void (*dump_sense) (struct dasd_device *, struct dasd_ccw_req *,
+			    struct irb *);
+
+        /* i/o control functions. */
+	int (*fill_geometry) (struct dasd_device *, struct hd_geometry *);
+	int (*fill_info) (struct dasd_device *, struct dasd_information2_t *);
+};
+
+extern struct dasd_discipline *dasd_diag_discipline_pointer;
+
+struct dasd_device {
+	/* Block device stuff. */
+	struct gendisk *gdp;
+	request_queue_t *request_queue;
+	spinlock_t request_queue_lock;
+	struct block_device *bdev;
+        unsigned int devindex;
+	unsigned long blocks;		/* size of volume in blocks */
+	unsigned int bp_block;		/* bytes per block */
+	unsigned int s2b_shift;		/* log2 (bp_block/512) */
+	unsigned long flags;		/* per device flags */
+
+	/* Device discipline stuff. */
+	struct dasd_discipline *discipline;
+	char *private;
+
+	/* Device state and target state. */
+	int state, target;
+	int stopped;		/* device (ccw_device_start) was stopped */
+
+	/* Open and reference count. */
+        atomic_t ref_count;
+	atomic_t open_count;
+
+	/* ccw queue and memory for static ccw/erp buffers. */
+	struct list_head ccw_queue;
+	spinlock_t mem_lock;
+	void *ccw_mem;
+	void *erp_mem;
+	struct list_head ccw_chunks;
+	struct list_head erp_chunks;
+
+	atomic_t tasklet_scheduled;
+        struct tasklet_struct tasklet;
+	struct work_struct kick_work;
+	struct timer_list timer;
+
+	debug_info_t *debug_area;
+
+	struct ccw_device *cdev;
+
+#ifdef CONFIG_DASD_PROFILE
+	struct dasd_profile_info_t profile;
+#endif
+};
+
+/* reasons why device (ccw_device_start) was stopped */
+#define DASD_STOPPED_NOT_ACC 1         /* not accessible */
+#define DASD_STOPPED_QUIESCE 2         /* Quiesced */
+#define DASD_STOPPED_PENDING 4         /* long busy */
+#define DASD_STOPPED_DC_WAIT 8         /* disconnected, wait */
+#define DASD_STOPPED_DC_EIO  16        /* disconnected, return -EIO */
+
+/* per device flags */
+#define DASD_FLAG_RO		0	/* device is read-only */
+#define DASD_FLAG_USE_DIAG	1	/* use diag disciplnie */
+#define DASD_FLAG_DSC_ERROR	2	/* return -EIO when disconnected */
+#define DASD_FLAG_OFFLINE	3	/* device is in offline processing */
+
+void dasd_put_device_wake(struct dasd_device *);
+
+/*
+ * Reference count inliners
+ */
+static inline void
+dasd_get_device(struct dasd_device *device)
+{
+	atomic_inc(&device->ref_count);
+}
+
+static inline void
+dasd_put_device(struct dasd_device *device)
+{
+	if (atomic_dec_return(&device->ref_count) == 0)
+		dasd_put_device_wake(device);
+}
+
+/*
+ * The static memory in ccw_mem and erp_mem is managed by a sorted
+ * list of free memory chunks.
+ */
+struct dasd_mchunk
+{
+	struct list_head list;
+	unsigned long size;
+} __attribute__ ((aligned(8)));
+
+static inline void
+dasd_init_chunklist(struct list_head *chunk_list, void *mem,
+		    unsigned long size)
+{
+	struct dasd_mchunk *chunk;
+
+	INIT_LIST_HEAD(chunk_list);
+	chunk = (struct dasd_mchunk *) mem;
+	chunk->size = size - sizeof(struct dasd_mchunk);
+	list_add(&chunk->list, chunk_list);
+}
+
+static inline void *
+dasd_alloc_chunk(struct list_head *chunk_list, unsigned long size)
+{
+	struct dasd_mchunk *chunk, *tmp;
+
+	size = (size + 7L) & -8L;
+	list_for_each_entry(chunk, chunk_list, list) {
+		if (chunk->size < size)
+			continue;
+		if (chunk->size > size + sizeof(struct dasd_mchunk)) {
+			char *endaddr = (char *) (chunk + 1) + chunk->size;
+			tmp = (struct dasd_mchunk *) (endaddr - size) - 1;
+			tmp->size = size;
+			chunk->size -= size + sizeof(struct dasd_mchunk);
+			chunk = tmp;
+		} else
+			list_del(&chunk->list);
+		return (void *) (chunk + 1);
+	}
+	return NULL;
+}
+
+static inline void
+dasd_free_chunk(struct list_head *chunk_list, void *mem)
+{
+	struct dasd_mchunk *chunk, *tmp;
+	struct list_head *p, *left;
+
+	chunk = (struct dasd_mchunk *)
+		((char *) mem - sizeof(struct dasd_mchunk));
+	/* Find out the left neighbour in chunk_list. */
+	left = chunk_list;
+	list_for_each(p, chunk_list) {
+		if (list_entry(p, struct dasd_mchunk, list) > chunk)
+			break;
+		left = p;
+	}
+	/* Try to merge with right neighbour = next element from left. */
+	if (left->next != chunk_list) {
+		tmp = list_entry(left->next, struct dasd_mchunk, list);
+		if ((char *) (chunk + 1) + chunk->size == (char *) tmp) {
+			list_del(&tmp->list);
+			chunk->size += tmp->size + sizeof(struct dasd_mchunk);
+		}
+	}
+	/* Try to merge with left neighbour. */
+	if (left != chunk_list) {
+		tmp = list_entry(left, struct dasd_mchunk, list);
+		if ((char *) (tmp + 1) + tmp->size == (char *) chunk) {
+			tmp->size += chunk->size + sizeof(struct dasd_mchunk);
+			return;
+		}
+	}
+	__list_add(&chunk->list, left, left->next);
+}
+
+/*
+ * Check if bsize is in { 512, 1024, 2048, 4096 }
+ */
+static inline int
+dasd_check_blocksize(int bsize)
+{
+	if (bsize < 512 || bsize > 4096 || (bsize & (bsize - 1)) != 0)
+		return -EMEDIUMTYPE;
+	return 0;
+}
+
+/* externals in dasd.c */
+#define DASD_PROFILE_ON	 1
+#define DASD_PROFILE_OFF 0
+
+extern debug_info_t *dasd_debug_area;
+extern struct dasd_profile_info_t dasd_global_profile;
+extern unsigned int dasd_profile_level;
+extern struct block_device_operations dasd_device_operations;
+
+extern kmem_cache_t *dasd_page_cache;
+
+struct dasd_ccw_req *
+dasd_kmalloc_request(char *, int, int, struct dasd_device *);
+struct dasd_ccw_req *
+dasd_smalloc_request(char *, int, int, struct dasd_device *);
+void dasd_kfree_request(struct dasd_ccw_req *, struct dasd_device *);
+void dasd_sfree_request(struct dasd_ccw_req *, struct dasd_device *);
+
+static inline int
+dasd_kmalloc_set_cda(struct ccw1 *ccw, void *cda, struct dasd_device *device)
+{
+	return set_normalized_cda(ccw, cda);
+}
+
+struct dasd_device *dasd_alloc_device(void);
+void dasd_free_device(struct dasd_device *);
+
+void dasd_enable_device(struct dasd_device *);
+void dasd_set_target_state(struct dasd_device *, int);
+void dasd_kick_device(struct dasd_device *);
+
+void dasd_add_request_head(struct dasd_ccw_req *);
+void dasd_add_request_tail(struct dasd_ccw_req *);
+int  dasd_start_IO(struct dasd_ccw_req *);
+int  dasd_term_IO(struct dasd_ccw_req *);
+void dasd_schedule_bh(struct dasd_device *);
+int  dasd_sleep_on(struct dasd_ccw_req *);
+int  dasd_sleep_on_immediatly(struct dasd_ccw_req *);
+int  dasd_sleep_on_interruptible(struct dasd_ccw_req *);
+void dasd_set_timer(struct dasd_device *, int);
+void dasd_clear_timer(struct dasd_device *);
+int  dasd_cancel_req(struct dasd_ccw_req *);
+int dasd_generic_probe (struct ccw_device *, struct dasd_discipline *);
+void dasd_generic_remove (struct ccw_device *cdev);
+int dasd_generic_set_online(struct ccw_device *, struct dasd_discipline *);
+int dasd_generic_set_offline (struct ccw_device *cdev);
+int dasd_generic_notify(struct ccw_device *, int);
+void dasd_generic_auto_online (struct ccw_driver *);
+
+/* externals in dasd_devmap.c */
+extern int dasd_max_devindex;
+extern int dasd_probeonly;
+extern int dasd_autodetect;
+
+int dasd_devmap_init(void);
+void dasd_devmap_exit(void);
+
+struct dasd_device *dasd_create_device(struct ccw_device *);
+void dasd_delete_device(struct dasd_device *);
+
+int dasd_add_sysfs_files(struct ccw_device *);
+void dasd_remove_sysfs_files(struct ccw_device *);
+
+struct dasd_device *dasd_device_from_cdev(struct ccw_device *);
+struct dasd_device *dasd_device_from_devindex(int);
+
+int dasd_parse(void);
+int dasd_busid_known(char *);
+
+/* externals in dasd_gendisk.c */
+int  dasd_gendisk_init(void);
+void dasd_gendisk_exit(void);
+int dasd_gendisk_alloc(struct dasd_device *);
+void dasd_gendisk_free(struct dasd_device *);
+int dasd_scan_partitions(struct dasd_device *);
+void dasd_destroy_partitions(struct dasd_device *);
+
+/* externals in dasd_ioctl.c */
+int  dasd_ioctl_init(void);
+void dasd_ioctl_exit(void);
+int  dasd_ioctl_no_register(struct module *, int, dasd_ioctl_fn_t);
+int  dasd_ioctl_no_unregister(struct module *, int, dasd_ioctl_fn_t);
+int  dasd_ioctl(struct inode *, struct file *, unsigned int, unsigned long);
+
+/* externals in dasd_proc.c */
+int dasd_proc_init(void);
+void dasd_proc_exit(void);
+
+/* externals in dasd_erp.c */
+struct dasd_ccw_req *dasd_default_erp_action(struct dasd_ccw_req *);
+struct dasd_ccw_req *dasd_default_erp_postaction(struct dasd_ccw_req *);
+struct dasd_ccw_req *dasd_alloc_erp_request(char *, int, int,
+					    struct dasd_device *);
+void dasd_free_erp_request(struct dasd_ccw_req *, struct dasd_device *);
+void dasd_log_sense(struct dasd_ccw_req *, struct irb *);
+void dasd_log_ccw(struct dasd_ccw_req *, int, __u32);
+
+/* externals in dasd_3370_erp.c */
+dasd_era_t dasd_3370_erp_examine(struct dasd_ccw_req *, struct irb *);
+
+/* externals in dasd_3990_erp.c */
+dasd_era_t dasd_3990_erp_examine(struct dasd_ccw_req *, struct irb *);
+struct dasd_ccw_req *dasd_3990_erp_action(struct dasd_ccw_req *);
+
+/* externals in dasd_9336_erp.c */
+dasd_era_t dasd_9336_erp_examine(struct dasd_ccw_req *, struct irb *);
+
+/* externals in dasd_9336_erp.c */
+dasd_era_t dasd_9343_erp_examine(struct dasd_ccw_req *, struct irb *);
+struct dasd_ccw_req *dasd_9343_erp_action(struct dasd_ccw_req *);
+
+#endif				/* __KERNEL__ */
+
+#endif				/* DASD_H */
+
+/*
+ * Overrides for Emacs so that we follow Linus's tabbing style.
+ * Emacs will notice this stuff at the end of the file and automatically
+ * adjust the settings for this buffer only.  This must remain at the end
+ * of the file.
+ * ---------------------------------------------------------------------------
+ * Local variables:
+ * c-indent-level: 4 
+ * c-brace-imaginary-offset: 0
+ * c-brace-offset: -4
+ * c-argdecl-indent: 4
+ * c-label-offset: -4
+ * c-continued-statement-offset: 4
+ * c-continued-brace-offset: 0
+ * indent-tabs-mode: 1
+ * tab-width: 8
+ * End:
+ */
diff --git a/drivers/s390/block/dasd_ioctl.c b/drivers/s390/block/dasd_ioctl.c
new file mode 100644
index 0000000..f1892ba
--- /dev/null
+++ b/drivers/s390/block/dasd_ioctl.c
@@ -0,0 +1,554 @@
+/*
+ * File...........: linux/drivers/s390/block/dasd_ioctl.c
+ * Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
+ *		    Horst Hummel <Horst.Hummel@de.ibm.com>
+ *		    Carsten Otte <Cotte@de.ibm.com>
+ *		    Martin Schwidefsky <schwidefsky@de.ibm.com>
+ * Bugreports.to..: <Linux390@de.ibm.com>
+ * (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999-2001
+ *
+ * i/o controls for the dasd driver.
+ */
+#include <linux/config.h>
+#include <linux/interrupt.h>
+#include <linux/major.h>
+#include <linux/fs.h>
+#include <linux/blkpg.h>
+
+#include <asm/ccwdev.h>
+#include <asm/uaccess.h>
+
+/* This is ugly... */
+#define PRINTK_HEADER "dasd_ioctl:"
+
+#include "dasd_int.h"
+
+/*
+ * SECTION: ioctl functions.
+ */
+static struct list_head dasd_ioctl_list = LIST_HEAD_INIT(dasd_ioctl_list);
+
+/*
+ * Find the ioctl with number no.
+ */
+static struct dasd_ioctl *
+dasd_find_ioctl(int no)
+{
+	struct dasd_ioctl *ioctl;
+
+	list_for_each_entry (ioctl, &dasd_ioctl_list, list)
+		if (ioctl->no == no)
+			return ioctl;
+	return NULL;
+}
+
+/*
+ * Register ioctl with number no.
+ */
+int
+dasd_ioctl_no_register(struct module *owner, int no, dasd_ioctl_fn_t handler)
+{
+	struct dasd_ioctl *new;
+	if (dasd_find_ioctl(no))
+		return -EBUSY;
+	new = kmalloc(sizeof (struct dasd_ioctl), GFP_KERNEL);
+	if (new == NULL)
+		return -ENOMEM;
+	new->owner = owner;
+	new->no = no;
+	new->handler = handler;
+	list_add(&new->list, &dasd_ioctl_list);
+	return 0;
+}
+
+/*
+ * Deregister ioctl with number no.
+ */
+int
+dasd_ioctl_no_unregister(struct module *owner, int no, dasd_ioctl_fn_t handler)
+{
+	struct dasd_ioctl *old = dasd_find_ioctl(no);
+	if (old == NULL)
+		return -ENOENT;
+	if (old->no != no || old->handler != handler || owner != old->owner)
+		return -EINVAL;
+	list_del(&old->list);
+	kfree(old);
+	return 0;
+}
+
+int
+dasd_ioctl(struct inode *inp, struct file *filp,
+	   unsigned int no, unsigned long data)
+{
+	struct block_device *bdev = inp->i_bdev;
+	struct dasd_device *device = bdev->bd_disk->private_data;
+	struct dasd_ioctl *ioctl;
+	const char *dir;
+	int rc;
+
+	if ((_IOC_DIR(no) != _IOC_NONE) && (data == 0)) {
+		PRINT_DEBUG("empty data ptr");
+		return -EINVAL;
+	}
+	dir = _IOC_DIR (no) == _IOC_NONE ? "0" :
+		_IOC_DIR (no) == _IOC_READ ? "r" :
+		_IOC_DIR (no) == _IOC_WRITE ? "w" : 
+		_IOC_DIR (no) == (_IOC_READ | _IOC_WRITE) ? "rw" : "u";
+	DBF_DEV_EVENT(DBF_DEBUG, device,
+		      "ioctl 0x%08x %s'0x%x'%d(%d) with data %8lx", no,
+		      dir, _IOC_TYPE(no), _IOC_NR(no), _IOC_SIZE(no), data);
+	/* Search for ioctl no in the ioctl list. */
+	list_for_each_entry(ioctl, &dasd_ioctl_list, list) {
+		if (ioctl->no == no) {
+			/* Found a matching ioctl. Call it. */
+			if (!try_module_get(ioctl->owner))
+				continue;
+			rc = ioctl->handler(bdev, no, data);
+			module_put(ioctl->owner);
+			return rc;
+		}
+	}
+	/* No ioctl with number no. */
+	DBF_DEV_EVENT(DBF_INFO, device,
+		      "unknown ioctl 0x%08x=%s'0x%x'%d(%d) data %8lx", no,
+		      dir, _IOC_TYPE(no), _IOC_NR(no), _IOC_SIZE(no), data);
+	return -EINVAL;
+}
+
+static int
+dasd_ioctl_api_version(struct block_device *bdev, int no, long args)
+{
+	int ver = DASD_API_VERSION;
+	return put_user(ver, (int __user *) args);
+}
+
+/*
+ * Enable device.
+ * used by dasdfmt after BIODASDDISABLE to retrigger blocksize detection
+ */
+static int
+dasd_ioctl_enable(struct block_device *bdev, int no, long args)
+{
+	struct dasd_device *device;
+
+	if (!capable(CAP_SYS_ADMIN))
+		return -EACCES;
+	device = bdev->bd_disk->private_data;
+	if (device == NULL)
+		return -ENODEV;
+	dasd_enable_device(device);
+	/* Formatting the dasd device can change the capacity. */
+	down(&bdev->bd_sem);
+	i_size_write(bdev->bd_inode, (loff_t)get_capacity(device->gdp) << 9);
+	up(&bdev->bd_sem);
+	return 0;
+}
+
+/*
+ * Disable device.
+ * Used by dasdfmt. Disable I/O operations but allow ioctls.
+ */
+static int
+dasd_ioctl_disable(struct block_device *bdev, int no, long args)
+{
+	struct dasd_device *device;
+
+	if (!capable(CAP_SYS_ADMIN))
+		return -EACCES;
+	device = bdev->bd_disk->private_data;
+	if (device == NULL)
+		return -ENODEV;
+	/*
+	 * Man this is sick. We don't do a real disable but only downgrade
+	 * the device to DASD_STATE_BASIC. The reason is that dasdfmt uses
+	 * BIODASDDISABLE to disable accesses to the device via the block
+	 * device layer but it still wants to do i/o on the device by
+	 * using the BIODASDFMT ioctl. Therefore the correct state for the
+	 * device is DASD_STATE_BASIC that allows to do basic i/o.
+	 */
+	dasd_set_target_state(device, DASD_STATE_BASIC);
+	/*
+	 * Set i_size to zero, since read, write, etc. check against this
+	 * value.
+	 */
+	down(&bdev->bd_sem);
+	i_size_write(bdev->bd_inode, 0);
+	up(&bdev->bd_sem);
+	return 0;
+}
+
+/*
+ * Quiesce device.
+ */
+static int
+dasd_ioctl_quiesce(struct block_device *bdev, int no, long args)
+{
+	struct dasd_device *device;
+	unsigned long flags;
+	
+	if (!capable (CAP_SYS_ADMIN))
+		return -EACCES;
+	
+	device = bdev->bd_disk->private_data;
+	if (device == NULL)
+		return -ENODEV;
+	
+	DEV_MESSAGE (KERN_DEBUG, device, "%s",
+		     "Quiesce IO on device");
+	spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags);
+	device->stopped |= DASD_STOPPED_QUIESCE;
+	spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags);
+	return 0;
+}
+
+
+/*
+ * Quiesce device.
+ */
+static int
+dasd_ioctl_resume(struct block_device *bdev, int no, long args)
+{
+	struct dasd_device *device;
+	unsigned long flags;
+	
+	if (!capable (CAP_SYS_ADMIN)) 
+		return -EACCES;
+
+	device = bdev->bd_disk->private_data;
+	if (device == NULL)
+		return -ENODEV;
+
+	DEV_MESSAGE (KERN_DEBUG, device, "%s",
+		     "resume IO on device");
+	
+	spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags);
+	device->stopped &= ~DASD_STOPPED_QUIESCE;
+	spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags);
+
+	dasd_schedule_bh (device);
+	return 0;
+}
+
+/*
+ * performs formatting of _device_ according to _fdata_
+ * Note: The discipline's format_function is assumed to deliver formatting
+ * commands to format a single unit of the device. In terms of the ECKD
+ * devices this means CCWs are generated to format a single track.
+ */
+static int
+dasd_format(struct dasd_device * device, struct format_data_t * fdata)
+{
+	struct dasd_ccw_req *cqr;
+	int rc;
+
+	if (device->discipline->format_device == NULL)
+		return -EPERM;
+
+	if (device->state != DASD_STATE_BASIC) {
+		DEV_MESSAGE(KERN_WARNING, device, "%s",
+			    "dasd_format: device is not disabled! ");
+		return -EBUSY;
+	}
+
+	DBF_DEV_EVENT(DBF_NOTICE, device,
+		      "formatting units %d to %d (%d B blocks) flags %d",
+		      fdata->start_unit,
+		      fdata->stop_unit, fdata->blksize, fdata->intensity);
+
+	/* Since dasdfmt keeps the device open after it was disabled,
+	 * there still exists an inode for this device.
+	 * We must update i_blkbits, otherwise we might get errors when
+	 * enabling the device later.
+	 */
+	if (fdata->start_unit == 0) {
+		struct block_device *bdev = bdget_disk(device->gdp, 0);
+		bdev->bd_inode->i_blkbits = blksize_bits(fdata->blksize);
+		bdput(bdev);
+	}
+
+	while (fdata->start_unit <= fdata->stop_unit) {
+		cqr = device->discipline->format_device(device, fdata);
+		if (IS_ERR(cqr))
+			return PTR_ERR(cqr);
+		rc = dasd_sleep_on_interruptible(cqr);
+		dasd_sfree_request(cqr, cqr->device);
+		if (rc) {
+			if (rc != -ERESTARTSYS)
+				DEV_MESSAGE(KERN_ERR, device,
+					    " Formatting of unit %d failed "
+					    "with rc = %d",
+					    fdata->start_unit, rc);
+			return rc;
+		}
+		fdata->start_unit++;
+	}
+	return 0;
+}
+
+/*
+ * Format device.
+ */
+static int
+dasd_ioctl_format(struct block_device *bdev, int no, long args)
+{
+	struct dasd_device *device;
+	struct format_data_t fdata;
+
+	if (!capable(CAP_SYS_ADMIN))
+		return -EACCES;
+	if (!args)
+		return -EINVAL;
+	/* fdata == NULL is no longer a valid arg to dasd_format ! */
+	device = bdev->bd_disk->private_data;
+
+	if (device == NULL)
+		return -ENODEV;
+	if (test_bit(DASD_FLAG_RO, &device->flags))
+		return -EROFS;
+	if (copy_from_user(&fdata, (void __user *) args,
+			   sizeof (struct format_data_t)))
+		return -EFAULT;
+	if (bdev != bdev->bd_contains) {
+		DEV_MESSAGE(KERN_WARNING, device, "%s",
+			    "Cannot low-level format a partition");
+		return -EINVAL;
+	}
+	return dasd_format(device, &fdata);
+}
+
+#ifdef CONFIG_DASD_PROFILE
+/*
+ * Reset device profile information
+ */
+static int
+dasd_ioctl_reset_profile(struct block_device *bdev, int no, long args)
+{
+	struct dasd_device *device;
+
+	if (!capable(CAP_SYS_ADMIN))
+		return -EACCES;
+
+	device = bdev->bd_disk->private_data;
+	if (device == NULL)
+		return -ENODEV;
+
+	memset(&device->profile, 0, sizeof (struct dasd_profile_info_t));
+	return 0;
+}
+
+/*
+ * Return device profile information
+ */
+static int
+dasd_ioctl_read_profile(struct block_device *bdev, int no, long args)
+{
+	struct dasd_device *device;
+
+	device = bdev->bd_disk->private_data;
+	if (device == NULL)
+		return -ENODEV;
+
+	if (copy_to_user((long __user *) args, (long *) &device->profile,
+			 sizeof (struct dasd_profile_info_t)))
+		return -EFAULT;
+	return 0;
+}
+#else
+static int
+dasd_ioctl_reset_profile(struct block_device *bdev, int no, long args)
+{
+	return -ENOSYS;
+}
+
+static int
+dasd_ioctl_read_profile(struct block_device *bdev, int no, long args)
+{
+	return -ENOSYS;
+}
+#endif
+
+/*
+ * Return dasd information. Used for BIODASDINFO and BIODASDINFO2.
+ */
+static int
+dasd_ioctl_information(struct block_device *bdev, int no, long args)
+{
+	struct dasd_device *device;
+	struct dasd_information2_t *dasd_info;
+	unsigned long flags;
+	int rc;
+	struct ccw_device *cdev;
+
+	device = bdev->bd_disk->private_data;
+	if (device == NULL)
+		return -ENODEV;
+
+	if (!device->discipline->fill_info)
+		return -EINVAL;
+
+	dasd_info = kmalloc(sizeof(struct dasd_information2_t), GFP_KERNEL);
+	if (dasd_info == NULL)
+		return -ENOMEM;
+
+	rc = device->discipline->fill_info(device, dasd_info);
+	if (rc) {
+		kfree(dasd_info);
+		return rc;
+	}
+
+	cdev = device->cdev;
+
+	dasd_info->devno = _ccw_device_get_device_number(device->cdev);
+	dasd_info->schid = _ccw_device_get_subchannel_number(device->cdev);
+	dasd_info->cu_type = cdev->id.cu_type;
+	dasd_info->cu_model = cdev->id.cu_model;
+	dasd_info->dev_type = cdev->id.dev_type;
+	dasd_info->dev_model = cdev->id.dev_model;
+	dasd_info->open_count = atomic_read(&device->open_count);
+	dasd_info->status = device->state;
+	
+	/*
+	 * check if device is really formatted
+	 * LDL / CDL was returned by 'fill_info'
+	 */
+	if ((device->state < DASD_STATE_READY) ||
+	    (dasd_check_blocksize(device->bp_block)))
+		dasd_info->format = DASD_FORMAT_NONE;
+	
+	dasd_info->features |= test_bit(DASD_FLAG_RO, &device->flags) ?
+		DASD_FEATURE_READONLY : DASD_FEATURE_DEFAULT;
+
+	if (device->discipline)
+		memcpy(dasd_info->type, device->discipline->name, 4);
+	else
+		memcpy(dasd_info->type, "none", 4);
+	dasd_info->req_queue_len = 0;
+	dasd_info->chanq_len = 0;
+	if (device->request_queue->request_fn) {
+		struct list_head *l;
+#ifdef DASD_EXTENDED_PROFILING
+		{
+			struct list_head *l;
+			spin_lock_irqsave(&device->lock, flags);
+			list_for_each(l, &device->request_queue->queue_head)
+				dasd_info->req_queue_len++;
+			spin_unlock_irqrestore(&device->lock, flags);
+		}
+#endif				/* DASD_EXTENDED_PROFILING */
+		spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags);
+		list_for_each(l, &device->ccw_queue)
+			dasd_info->chanq_len++;
+		spin_unlock_irqrestore(get_ccwdev_lock(device->cdev),
+				       flags);
+	}
+
+	rc = 0;
+	if (copy_to_user((long __user *) args, (long *) dasd_info,
+			 ((no == (unsigned int) BIODASDINFO2) ?
+			  sizeof (struct dasd_information2_t) :
+			  sizeof (struct dasd_information_t))))
+		rc = -EFAULT;
+	kfree(dasd_info);
+	return rc;
+}
+
+/*
+ * Set read only
+ */
+static int
+dasd_ioctl_set_ro(struct block_device *bdev, int no, long args)
+{
+	struct dasd_device *device;
+	int intval;
+
+	if (!capable(CAP_SYS_ADMIN))
+		return -EACCES;
+	if (bdev != bdev->bd_contains)
+		// ro setting is not allowed for partitions
+		return -EINVAL;
+	if (get_user(intval, (int __user *) args))
+		return -EFAULT;
+	device =  bdev->bd_disk->private_data;
+	if (device == NULL)
+		return -ENODEV;
+	set_disk_ro(bdev->bd_disk, intval);
+	if (intval)
+		set_bit(DASD_FLAG_RO, &device->flags);
+	else
+		clear_bit(DASD_FLAG_RO, &device->flags);
+	return 0;
+}
+
+/*
+ * Return disk geometry.
+ */
+static int
+dasd_ioctl_getgeo(struct block_device *bdev, int no, long args)
+{
+	struct hd_geometry geo = { 0, };
+	struct dasd_device *device;
+
+	device =  bdev->bd_disk->private_data;
+	if (device == NULL)
+		return -ENODEV;
+
+	if (device == NULL || device->discipline == NULL ||
+	    device->discipline->fill_geometry == NULL)
+		return -EINVAL;
+
+	geo = (struct hd_geometry) {};
+	device->discipline->fill_geometry(device, &geo);
+	geo.start = get_start_sect(bdev) >> device->s2b_shift;
+	if (copy_to_user((struct hd_geometry __user *) args, &geo,
+			 sizeof (struct hd_geometry)))
+		return -EFAULT;
+
+	return 0;
+}
+
+/*
+ * List of static ioctls.
+ */
+static struct { int no; dasd_ioctl_fn_t fn; } dasd_ioctls[] =
+{
+	{ BIODASDDISABLE, dasd_ioctl_disable },
+	{ BIODASDENABLE, dasd_ioctl_enable },
+	{ BIODASDQUIESCE, dasd_ioctl_quiesce },
+	{ BIODASDRESUME, dasd_ioctl_resume },
+	{ BIODASDFMT, dasd_ioctl_format },
+	{ BIODASDINFO, dasd_ioctl_information },
+	{ BIODASDINFO2, dasd_ioctl_information },
+	{ BIODASDPRRD, dasd_ioctl_read_profile },
+	{ BIODASDPRRST, dasd_ioctl_reset_profile },
+	{ BLKROSET, dasd_ioctl_set_ro },
+	{ DASDAPIVER, dasd_ioctl_api_version },
+	{ HDIO_GETGEO, dasd_ioctl_getgeo },
+	{ -1, NULL }
+};
+
+int
+dasd_ioctl_init(void)
+{
+	int i;
+
+	for (i = 0; dasd_ioctls[i].no != -1; i++)
+		dasd_ioctl_no_register(NULL, dasd_ioctls[i].no,
+				       dasd_ioctls[i].fn);
+	return 0;
+
+}
+
+void
+dasd_ioctl_exit(void)
+{
+	int i;
+
+	for (i = 0; dasd_ioctls[i].no != -1; i++)
+		dasd_ioctl_no_unregister(NULL, dasd_ioctls[i].no,
+					 dasd_ioctls[i].fn);
+
+}
+
+EXPORT_SYMBOL(dasd_ioctl_no_register);
+EXPORT_SYMBOL(dasd_ioctl_no_unregister);
diff --git a/drivers/s390/block/dasd_proc.c b/drivers/s390/block/dasd_proc.c
new file mode 100644
index 0000000..353d411
--- /dev/null
+++ b/drivers/s390/block/dasd_proc.c
@@ -0,0 +1,319 @@
+/*
+ * File...........: linux/drivers/s390/block/dasd_proc.c
+ * Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
+ *		    Horst Hummel <Horst.Hummel@de.ibm.com>
+ *		    Carsten Otte <Cotte@de.ibm.com>
+ *		    Martin Schwidefsky <schwidefsky@de.ibm.com>
+ * Bugreports.to..: <Linux390@de.ibm.com>
+ * (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999-2002
+ *
+ * /proc interface for the dasd driver.
+ *
+ * $Revision: 1.30 $
+ */
+
+#include <linux/config.h>
+#include <linux/ctype.h>
+#include <linux/seq_file.h>
+#include <linux/vmalloc.h>
+
+#include <asm/debug.h>
+#include <asm/uaccess.h>
+
+/* This is ugly... */
+#define PRINTK_HEADER "dasd_proc:"
+
+#include "dasd_int.h"
+
+static struct proc_dir_entry *dasd_proc_root_entry = NULL;
+static struct proc_dir_entry *dasd_devices_entry = NULL;
+static struct proc_dir_entry *dasd_statistics_entry = NULL;
+
+static inline char *
+dasd_get_user_string(const char __user *user_buf, size_t user_len)
+{
+	char *buffer;
+
+	buffer = kmalloc(user_len + 1, GFP_KERNEL);
+	if (buffer == NULL)
+		return ERR_PTR(-ENOMEM);
+	if (copy_from_user(buffer, user_buf, user_len) != 0) {
+		kfree(buffer);
+		return ERR_PTR(-EFAULT);
+	}
+	/* got the string, now strip linefeed. */
+	if (buffer[user_len - 1] == '\n')
+		buffer[user_len - 1] = 0;
+	else
+		buffer[user_len] = 0;
+	return buffer;
+}
+
+static int
+dasd_devices_show(struct seq_file *m, void *v)
+{
+	struct dasd_device *device;
+	char *substr;
+
+	device = dasd_device_from_devindex((unsigned long) v - 1);
+	if (IS_ERR(device))
+		return 0;
+	/* Print device number. */
+	seq_printf(m, "%s", device->cdev->dev.bus_id);
+	/* Print discipline string. */
+	if (device != NULL && device->discipline != NULL)
+		seq_printf(m, "(%s)", device->discipline->name);
+	else
+		seq_printf(m, "(none)");
+	/* Print kdev. */
+	if (device->gdp)
+		seq_printf(m, " at (%3d:%6d)",
+			   device->gdp->major, device->gdp->first_minor);
+	else
+		seq_printf(m, "  at (???:??????)");
+	/* Print device name. */
+	if (device->gdp)
+		seq_printf(m, " is %-8s", device->gdp->disk_name);
+	else
+		seq_printf(m, " is ????????");
+	/* Print devices features. */
+	substr = test_bit(DASD_FLAG_RO, &device->flags) ? "(ro)" : " ";
+	seq_printf(m, "%4s: ", substr);
+	/* Print device status information. */
+	switch ((device != NULL) ? device->state : -1) {
+	case -1:
+		seq_printf(m, "unknown");
+		break;
+	case DASD_STATE_NEW:
+		seq_printf(m, "new");
+		break;
+	case DASD_STATE_KNOWN:
+		seq_printf(m, "detected");
+		break;
+	case DASD_STATE_BASIC:
+		seq_printf(m, "basic");
+		break;
+	case DASD_STATE_READY:
+	case DASD_STATE_ONLINE:
+		seq_printf(m, "active ");
+		if (dasd_check_blocksize(device->bp_block))
+			seq_printf(m, "n/f	 ");
+		else
+			seq_printf(m,
+				   "at blocksize: %d, %ld blocks, %ld MB",
+				   device->bp_block, device->blocks,
+				   ((device->bp_block >> 9) *
+				    device->blocks) >> 11);
+		break;
+	default:
+		seq_printf(m, "no stat");
+		break;
+	}
+	dasd_put_device(device);
+	if (dasd_probeonly)
+		seq_printf(m, "(probeonly)");
+	seq_printf(m, "\n");
+	return 0;
+}
+
+static void *dasd_devices_start(struct seq_file *m, loff_t *pos)
+{
+	if (*pos >= dasd_max_devindex)
+		return NULL;
+	return (void *)((unsigned long) *pos + 1);
+}
+
+static void *dasd_devices_next(struct seq_file *m, void *v, loff_t *pos)
+{
+	++*pos;
+	return dasd_devices_start(m, pos);
+}
+
+static void dasd_devices_stop(struct seq_file *m, void *v)
+{
+}
+
+static struct seq_operations dasd_devices_seq_ops = {
+	.start		= dasd_devices_start,
+	.next		= dasd_devices_next,
+	.stop		= dasd_devices_stop,
+	.show		= dasd_devices_show,
+};
+
+static int dasd_devices_open(struct inode *inode, struct file *file)
+{
+	return seq_open(file, &dasd_devices_seq_ops);
+}
+
+static struct file_operations dasd_devices_file_ops = {
+	.open		= dasd_devices_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= seq_release,
+};
+
+static inline int
+dasd_calc_metrics(char *page, char **start, off_t off,
+		  int count, int *eof, int len)
+{
+	len = (len > off) ? len - off : 0;
+	if (len > count)
+		len = count;
+	if (len < count)
+		*eof = 1;
+	*start = page + off;
+	return len;
+}
+
+static inline char *
+dasd_statistics_array(char *str, int *array, int shift)
+{
+	int i;
+
+	for (i = 0; i < 32; i++) {
+		str += sprintf(str, "%7d ", array[i] >> shift);
+		if (i == 15)
+			str += sprintf(str, "\n");
+	}
+	str += sprintf(str,"\n");
+	return str;
+}
+
+static int
+dasd_statistics_read(char *page, char **start, off_t off,
+		     int count, int *eof, void *data)
+{
+	unsigned long len;
+#ifdef CONFIG_DASD_PROFILE
+	struct dasd_profile_info_t *prof;
+	char *str;
+	int shift;
+
+	/* check for active profiling */
+	if (dasd_profile_level == DASD_PROFILE_OFF) {
+		len = sprintf(page, "Statistics are off - they might be "
+				    "switched on using 'echo set on > "
+				    "/proc/dasd/statistics'\n");
+		return dasd_calc_metrics(page, start, off, count, eof, len);
+	}
+
+	prof = &dasd_global_profile;
+	/* prevent couter 'overflow' on output */
+	for (shift = 0; (prof->dasd_io_reqs >> shift) > 9999999; shift++);
+
+	str = page;
+	str += sprintf(str, "%d dasd I/O requests\n", prof->dasd_io_reqs);
+	str += sprintf(str, "with %d sectors(512B each)\n",
+		       prof->dasd_io_sects);
+	str += sprintf(str,
+		       "   __<4	   ___8	   __16	   __32	   __64	   _128	"
+		       "   _256	   _512	   __1k	   __2k	   __4k	   __8k	"
+		       "   _16k	   _32k	   _64k	   128k\n");
+	str += sprintf(str,
+		       "   _256	   _512	   __1M	   __2M	   __4M	   __8M	"
+		       "   _16M	   _32M	   _64M	   128M	   256M	   512M	"
+		       "   __1G	   __2G	   __4G " "   _>4G\n");
+
+	str += sprintf(str, "Histogram of sizes (512B secs)\n");
+	str = dasd_statistics_array(str, prof->dasd_io_secs, shift);
+	str += sprintf(str, "Histogram of I/O times (microseconds)\n");
+	str = dasd_statistics_array(str, prof->dasd_io_times, shift);
+	str += sprintf(str, "Histogram of I/O times per sector\n");
+	str = dasd_statistics_array(str, prof->dasd_io_timps, shift);
+	str += sprintf(str, "Histogram of I/O time till ssch\n");
+	str = dasd_statistics_array(str, prof->dasd_io_time1, shift);
+	str += sprintf(str, "Histogram of I/O time between ssch and irq\n");
+	str = dasd_statistics_array(str, prof->dasd_io_time2, shift);
+	str += sprintf(str, "Histogram of I/O time between ssch "
+			    "and irq per sector\n");
+	str = dasd_statistics_array(str, prof->dasd_io_time2ps, shift);
+	str += sprintf(str, "Histogram of I/O time between irq and end\n");
+	str = dasd_statistics_array(str, prof->dasd_io_time3, shift);
+	str += sprintf(str, "# of req in chanq at enqueuing (1..32) \n");
+	str = dasd_statistics_array(str, prof->dasd_io_nr_req, shift);
+	len = str - page;
+#else
+	len = sprintf(page, "Statistics are not activated in this kernel\n");
+#endif
+	return dasd_calc_metrics(page, start, off, count, eof, len);
+}
+
+static int
+dasd_statistics_write(struct file *file, const char __user *user_buf,
+		      unsigned long user_len, void *data)
+{
+#ifdef CONFIG_DASD_PROFILE
+	char *buffer, *str;
+
+	if (user_len > 65536)
+		user_len = 65536;
+	buffer = dasd_get_user_string(user_buf, user_len);
+	if (IS_ERR(buffer))
+		return PTR_ERR(buffer);
+	MESSAGE_LOG(KERN_INFO, "/proc/dasd/statictics: '%s'", buffer);
+
+	/* check for valid verbs */
+	for (str = buffer; isspace(*str); str++);
+	if (strncmp(str, "set", 3) == 0 && isspace(str[3])) {
+		/* 'set xxx' was given */
+		for (str = str + 4; isspace(*str); str++);
+		if (strcmp(str, "on") == 0) {
+			/* switch on statistics profiling */
+			dasd_profile_level = DASD_PROFILE_ON;
+			MESSAGE(KERN_INFO, "%s", "Statistics switched on");
+		} else if (strcmp(str, "off") == 0) {
+			/* switch off and reset statistics profiling */
+			memset(&dasd_global_profile,
+			       0, sizeof (struct dasd_profile_info_t));
+			dasd_profile_level = DASD_PROFILE_OFF;
+			MESSAGE(KERN_INFO, "%s", "Statistics switched off");
+		} else
+			goto out_error;
+	} else if (strncmp(str, "reset", 5) == 0) {
+		/* reset the statistics */
+		memset(&dasd_global_profile, 0,
+		       sizeof (struct dasd_profile_info_t));
+		MESSAGE(KERN_INFO, "%s", "Statistics reset");
+	} else
+		goto out_error;
+	kfree(buffer);
+	return user_len;
+out_error:
+	MESSAGE(KERN_WARNING, "%s",
+		"/proc/dasd/statistics: only 'set on', 'set off' "
+		"and 'reset' are supported verbs");
+	kfree(buffer);
+	return -EINVAL;
+#else
+	MESSAGE(KERN_WARNING, "%s",
+		"/proc/dasd/statistics: is not activated in this kernel");
+	return user_len;
+#endif				/* CONFIG_DASD_PROFILE */
+}
+
+int
+dasd_proc_init(void)
+{
+	dasd_proc_root_entry = proc_mkdir("dasd", &proc_root);
+	dasd_proc_root_entry->owner = THIS_MODULE;
+	dasd_devices_entry = create_proc_entry("devices",
+					       S_IFREG | S_IRUGO | S_IWUSR,
+					       dasd_proc_root_entry);
+	dasd_devices_entry->proc_fops = &dasd_devices_file_ops;
+	dasd_devices_entry->owner = THIS_MODULE;
+	dasd_statistics_entry = create_proc_entry("statistics",
+						  S_IFREG | S_IRUGO | S_IWUSR,
+						  dasd_proc_root_entry);
+	dasd_statistics_entry->read_proc = dasd_statistics_read;
+	dasd_statistics_entry->write_proc = dasd_statistics_write;
+	dasd_statistics_entry->owner = THIS_MODULE;
+	return 0;
+}
+
+void
+dasd_proc_exit(void)
+{
+	remove_proc_entry("devices", dasd_proc_root_entry);
+	remove_proc_entry("statistics", dasd_proc_root_entry);
+	remove_proc_entry("dasd", &proc_root);
+}
diff --git a/drivers/s390/block/dcssblk.c b/drivers/s390/block/dcssblk.c
new file mode 100644
index 0000000..a66b17b6
--- /dev/null
+++ b/drivers/s390/block/dcssblk.c
@@ -0,0 +1,775 @@
+/*
+ * dcssblk.c -- the S/390 block driver for dcss memory
+ *
+ * Authors: Carsten Otte, Stefan Weinhuber, Gerald Schaefer
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/ctype.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/blkdev.h>
+#include <asm/extmem.h>
+#include <asm/io.h>
+#include <linux/completion.h>
+#include <linux/interrupt.h>
+#include <asm/ccwdev.h> 	// for s390_root_dev_(un)register()
+
+//#define DCSSBLK_DEBUG		/* Debug messages on/off */
+#define DCSSBLK_NAME "dcssblk"
+#define DCSSBLK_MINORS_PER_DISK 1
+#define DCSSBLK_PARM_LEN 400
+
+#ifdef DCSSBLK_DEBUG
+#define PRINT_DEBUG(x...) printk(KERN_DEBUG DCSSBLK_NAME " debug: " x)
+#else
+#define PRINT_DEBUG(x...) do {} while (0)
+#endif
+#define PRINT_INFO(x...)  printk(KERN_INFO DCSSBLK_NAME " info: " x)
+#define PRINT_WARN(x...)  printk(KERN_WARNING DCSSBLK_NAME " warning: " x)
+#define PRINT_ERR(x...)	  printk(KERN_ERR DCSSBLK_NAME " error: " x)
+
+
+static int dcssblk_open(struct inode *inode, struct file *filp);
+static int dcssblk_release(struct inode *inode, struct file *filp);
+static int dcssblk_make_request(struct request_queue *q, struct bio *bio);
+
+static char dcssblk_segments[DCSSBLK_PARM_LEN] = "\0";
+
+static int dcssblk_major;
+static struct block_device_operations dcssblk_devops = {
+	.owner   = THIS_MODULE,
+	.open    = dcssblk_open,
+	.release = dcssblk_release,
+};
+
+static ssize_t dcssblk_add_store(struct device * dev, const char * buf,
+				  size_t count);
+static ssize_t dcssblk_remove_store(struct device * dev, const char * buf,
+				  size_t count);
+static ssize_t dcssblk_save_store(struct device * dev, const char * buf,
+				  size_t count);
+static ssize_t dcssblk_save_show(struct device *dev, char *buf);
+static ssize_t dcssblk_shared_store(struct device * dev, const char * buf,
+				  size_t count);
+static ssize_t dcssblk_shared_show(struct device *dev, char *buf);
+
+static DEVICE_ATTR(add, S_IWUSR, NULL, dcssblk_add_store);
+static DEVICE_ATTR(remove, S_IWUSR, NULL, dcssblk_remove_store);
+static DEVICE_ATTR(save, S_IWUSR | S_IRUGO, dcssblk_save_show,
+		   dcssblk_save_store);
+static DEVICE_ATTR(shared, S_IWUSR | S_IRUGO, dcssblk_shared_show,
+		   dcssblk_shared_store);
+
+static struct device *dcssblk_root_dev;
+
+struct dcssblk_dev_info {
+	struct list_head lh;
+	struct device dev;
+	char segment_name[BUS_ID_SIZE];
+	atomic_t use_count;
+	struct gendisk *gd;
+	unsigned long start;
+	unsigned long end;
+	int segment_type;
+	unsigned char save_pending;
+	unsigned char is_shared;
+	struct request_queue *dcssblk_queue;
+};
+
+static struct list_head dcssblk_devices = LIST_HEAD_INIT(dcssblk_devices);
+static struct rw_semaphore dcssblk_devices_sem;
+
+/*
+ * release function for segment device.
+ */
+static void
+dcssblk_release_segment(struct device *dev)
+{
+	PRINT_DEBUG("segment release fn called for %s\n", dev->bus_id);
+	kfree(container_of(dev, struct dcssblk_dev_info, dev));
+	module_put(THIS_MODULE);
+}
+
+/*
+ * get a minor number. needs to be called with
+ * down_write(&dcssblk_devices_sem) and the
+ * device needs to be enqueued before the semaphore is
+ * freed.
+ */
+static inline int
+dcssblk_assign_free_minor(struct dcssblk_dev_info *dev_info)
+{
+	int minor, found;
+	struct dcssblk_dev_info *entry;
+
+	if (dev_info == NULL)
+		return -EINVAL;
+	for (minor = 0; minor < (1<<MINORBITS); minor++) {
+		found = 0;
+		// test if minor available
+		list_for_each_entry(entry, &dcssblk_devices, lh)
+			if (minor == entry->gd->first_minor)
+				found++;
+		if (!found) break; // got unused minor
+	}
+	if (found)
+		return -EBUSY;
+	dev_info->gd->first_minor = minor;
+	return 0;
+}
+
+/*
+ * get the struct dcssblk_dev_info from dcssblk_devices
+ * for the given name.
+ * down_read(&dcssblk_devices_sem) must be held.
+ */
+static struct dcssblk_dev_info *
+dcssblk_get_device_by_name(char *name)
+{
+	struct dcssblk_dev_info *entry;
+
+	list_for_each_entry(entry, &dcssblk_devices, lh) {
+		if (!strcmp(name, entry->segment_name)) {
+			return entry;
+		}
+	}
+	return NULL;
+}
+
+/*
+ * print appropriate error message for segment_load()/segment_type()
+ * return code
+ */
+static void
+dcssblk_segment_warn(int rc, char* seg_name)
+{
+	switch (rc) {
+	case -ENOENT:
+		PRINT_WARN("cannot load/query segment %s, does not exist\n",
+			   seg_name);
+		break;
+	case -ENOSYS:
+		PRINT_WARN("cannot load/query segment %s, not running on VM\n",
+			   seg_name);
+		break;
+	case -EIO:
+		PRINT_WARN("cannot load/query segment %s, hardware error\n",
+			   seg_name);
+		break;
+	case -ENOTSUPP:
+		PRINT_WARN("cannot load/query segment %s, is a multi-part "
+			   "segment\n", seg_name);
+		break;
+	case -ENOSPC:
+		PRINT_WARN("cannot load/query segment %s, overlaps with "
+			   "storage\n", seg_name);
+		break;
+	case -EBUSY:
+		PRINT_WARN("cannot load/query segment %s, overlaps with "
+			   "already loaded dcss\n", seg_name);
+		break;
+	case -EPERM:
+		PRINT_WARN("cannot load/query segment %s, already loaded in "
+			   "incompatible mode\n", seg_name);
+		break;
+	case -ENOMEM:
+		PRINT_WARN("cannot load/query segment %s, out of memory\n",
+			   seg_name);
+		break;
+	case -ERANGE:
+		PRINT_WARN("cannot load/query segment %s, exceeds kernel "
+			   "mapping range\n", seg_name);
+		break;
+	default:
+		PRINT_WARN("cannot load/query segment %s, return value %i\n",
+			   seg_name, rc);
+		break;
+	}
+}
+
+/*
+ * device attribute for switching shared/nonshared (exclusive)
+ * operation (show + store)
+ */
+static ssize_t
+dcssblk_shared_show(struct device *dev, char *buf)
+{
+	struct dcssblk_dev_info *dev_info;
+
+	dev_info = container_of(dev, struct dcssblk_dev_info, dev);
+	return sprintf(buf, dev_info->is_shared ? "1\n" : "0\n");
+}
+
+static ssize_t
+dcssblk_shared_store(struct device *dev, const char *inbuf, size_t count)
+{
+	struct dcssblk_dev_info *dev_info;
+	int rc;
+
+	if ((count > 1) && (inbuf[1] != '\n') && (inbuf[1] != '\0')) {
+		PRINT_WARN("Invalid value, must be 0 or 1\n");
+		return -EINVAL;
+	}
+	down_write(&dcssblk_devices_sem);
+	dev_info = container_of(dev, struct dcssblk_dev_info, dev);
+	if (atomic_read(&dev_info->use_count)) {
+		PRINT_ERR("share: segment %s is busy!\n",
+			  dev_info->segment_name);
+		rc = -EBUSY;
+		goto out;
+	}
+	if (inbuf[0] == '1') {
+		// reload segment in shared mode
+		rc = segment_modify_shared(dev_info->segment_name,
+					   SEGMENT_SHARED);
+		if (rc < 0) {
+			BUG_ON(rc == -EINVAL);
+			if (rc == -EIO || rc == -ENOENT)
+				goto removeseg;
+		} else {
+			dev_info->is_shared = 1;
+			switch (dev_info->segment_type) {
+				case SEG_TYPE_SR:
+				case SEG_TYPE_ER:
+				case SEG_TYPE_SC:
+					set_disk_ro(dev_info->gd,1);
+			}
+		}
+	} else if (inbuf[0] == '0') {
+		// reload segment in exclusive mode
+		if (dev_info->segment_type == SEG_TYPE_SC) {
+			PRINT_ERR("Segment type SC (%s) cannot be loaded in "
+				  "non-shared mode\n", dev_info->segment_name);
+			rc = -EINVAL;
+			goto out;
+		}
+		rc = segment_modify_shared(dev_info->segment_name,
+					   SEGMENT_EXCLUSIVE);
+		if (rc < 0) {
+			BUG_ON(rc == -EINVAL);
+			if (rc == -EIO || rc == -ENOENT)
+				goto removeseg;
+		} else {
+			dev_info->is_shared = 0;
+			set_disk_ro(dev_info->gd, 0);
+		}
+	} else {
+		PRINT_WARN("Invalid value, must be 0 or 1\n");
+		rc = -EINVAL;
+		goto out;
+	}
+	rc = count;
+	goto out;
+
+removeseg:
+	PRINT_ERR("Could not reload segment %s, removing it now!\n",
+			dev_info->segment_name);
+	list_del(&dev_info->lh);
+
+	del_gendisk(dev_info->gd);
+	blk_put_queue(dev_info->dcssblk_queue);
+	dev_info->gd->queue = NULL;
+	put_disk(dev_info->gd);
+	device_unregister(dev);
+	put_device(dev);
+out:
+	up_write(&dcssblk_devices_sem);
+	return rc;
+}
+
+/*
+ * device attribute for save operation on current copy
+ * of the segment. If the segment is busy, saving will
+ * become pending until it gets released, which can be
+ * undone by storing a non-true value to this entry.
+ * (show + store)
+ */
+static ssize_t
+dcssblk_save_show(struct device *dev, char *buf)
+{
+	struct dcssblk_dev_info *dev_info;
+
+	dev_info = container_of(dev, struct dcssblk_dev_info, dev);
+	return sprintf(buf, dev_info->save_pending ? "1\n" : "0\n");
+}
+
+static ssize_t
+dcssblk_save_store(struct device *dev, const char *inbuf, size_t count)
+{
+	struct dcssblk_dev_info *dev_info;
+
+	if ((count > 1) && (inbuf[1] != '\n') && (inbuf[1] != '\0')) {
+		PRINT_WARN("Invalid value, must be 0 or 1\n");
+		return -EINVAL;
+	}
+	dev_info = container_of(dev, struct dcssblk_dev_info, dev);
+
+	down_write(&dcssblk_devices_sem);
+	if (inbuf[0] == '1') {
+		if (atomic_read(&dev_info->use_count) == 0) {
+			// device is idle => we save immediately
+			PRINT_INFO("Saving segment %s\n",
+				   dev_info->segment_name);
+			segment_save(dev_info->segment_name);
+		}  else {
+			// device is busy => we save it when it becomes
+			// idle in dcssblk_release
+			PRINT_INFO("Segment %s is currently busy, it will "
+				   "be saved when it becomes idle...\n",
+				   dev_info->segment_name);
+			dev_info->save_pending = 1;
+		}
+	} else if (inbuf[0] == '0') {
+		if (dev_info->save_pending) {
+			// device is busy & the user wants to undo his save
+			// request
+			dev_info->save_pending = 0;
+			PRINT_INFO("Pending save for segment %s deactivated\n",
+					dev_info->segment_name);
+		}
+	} else {
+		up_write(&dcssblk_devices_sem);
+		PRINT_WARN("Invalid value, must be 0 or 1\n");
+		return -EINVAL;
+	}
+	up_write(&dcssblk_devices_sem);
+	return count;
+}
+
+/*
+ * device attribute for adding devices
+ */
+static ssize_t
+dcssblk_add_store(struct device *dev, const char *buf, size_t count)
+{
+	int rc, i;
+	struct dcssblk_dev_info *dev_info;
+	char *local_buf;
+	unsigned long seg_byte_size;
+
+	dev_info = NULL;
+	if (dev != dcssblk_root_dev) {
+		rc = -EINVAL;
+		goto out_nobuf;
+	}
+	local_buf = kmalloc(count + 1, GFP_KERNEL);
+	if (local_buf == NULL) {
+		rc = -ENOMEM;
+		goto out_nobuf;
+	}
+	/*
+	 * parse input
+	 */
+	for (i = 0; ((buf[i] != '\0') && (buf[i] != '\n') && i < count); i++) {
+		local_buf[i] = toupper(buf[i]);
+	}
+	local_buf[i] = '\0';
+	if ((i == 0) || (i > 8)) {
+		rc = -ENAMETOOLONG;
+		goto out;
+	}
+	/*
+	 * already loaded?
+	 */
+	down_read(&dcssblk_devices_sem);
+	dev_info = dcssblk_get_device_by_name(local_buf);
+	up_read(&dcssblk_devices_sem);
+	if (dev_info != NULL) {
+		PRINT_WARN("Segment %s already loaded!\n", local_buf);
+		rc = -EEXIST;
+		goto out;
+	}
+	/*
+	 * get a struct dcssblk_dev_info
+	 */
+	dev_info = kmalloc(sizeof(struct dcssblk_dev_info), GFP_KERNEL);
+	if (dev_info == NULL) {
+		rc = -ENOMEM;
+		goto out;
+	}
+	memset(dev_info, 0, sizeof(struct dcssblk_dev_info));
+
+	strcpy(dev_info->segment_name, local_buf);
+	strlcpy(dev_info->dev.bus_id, local_buf, BUS_ID_SIZE);
+	dev_info->dev.release = dcssblk_release_segment;
+	INIT_LIST_HEAD(&dev_info->lh);
+
+	dev_info->gd = alloc_disk(DCSSBLK_MINORS_PER_DISK);
+	if (dev_info->gd == NULL) {
+		rc = -ENOMEM;
+		goto free_dev_info;
+	}
+	dev_info->gd->major = dcssblk_major;
+	dev_info->gd->fops = &dcssblk_devops;
+	dev_info->dcssblk_queue = blk_alloc_queue(GFP_KERNEL);
+	dev_info->gd->queue = dev_info->dcssblk_queue;
+	dev_info->gd->private_data = dev_info;
+	dev_info->gd->driverfs_dev = &dev_info->dev;
+	/*
+	 * load the segment
+	 */
+	rc = segment_load(local_buf, SEGMENT_SHARED,
+				&dev_info->start, &dev_info->end);
+	if (rc < 0) {
+		dcssblk_segment_warn(rc, dev_info->segment_name);
+		goto dealloc_gendisk;
+	}
+	seg_byte_size = (dev_info->end - dev_info->start + 1);
+	set_capacity(dev_info->gd, seg_byte_size >> 9); // size in sectors
+	PRINT_INFO("Loaded segment %s, size = %lu Byte, "
+		   "capacity = %lu (512 Byte) sectors\n", local_buf,
+		   seg_byte_size, seg_byte_size >> 9);
+
+	dev_info->segment_type = rc;
+	dev_info->save_pending = 0;
+	dev_info->is_shared = 1;
+	dev_info->dev.parent = dcssblk_root_dev;
+
+	/*
+	 * get minor, add to list
+	 */
+	down_write(&dcssblk_devices_sem);
+	rc = dcssblk_assign_free_minor(dev_info);
+	if (rc) {
+		up_write(&dcssblk_devices_sem);
+		PRINT_ERR("No free minor number available! "
+			  "Unloading segment...\n");
+		goto unload_seg;
+	}
+	sprintf(dev_info->gd->disk_name, "dcssblk%d",
+		dev_info->gd->first_minor);
+	list_add_tail(&dev_info->lh, &dcssblk_devices);
+
+	if (!try_module_get(THIS_MODULE)) {
+		rc = -ENODEV;
+		goto list_del;
+	}
+	/*
+	 * register the device
+	 */
+	rc = device_register(&dev_info->dev);
+	if (rc) {
+		PRINT_ERR("Segment %s could not be registered RC=%d\n",
+				local_buf, rc);
+		module_put(THIS_MODULE);
+		goto list_del;
+	}
+	get_device(&dev_info->dev);
+	rc = device_create_file(&dev_info->dev, &dev_attr_shared);
+	if (rc)
+		goto unregister_dev;
+	rc = device_create_file(&dev_info->dev, &dev_attr_save);
+	if (rc)
+		goto unregister_dev;
+
+	add_disk(dev_info->gd);
+
+	blk_queue_make_request(dev_info->dcssblk_queue, dcssblk_make_request);
+	blk_queue_hardsect_size(dev_info->dcssblk_queue, 4096);
+
+	switch (dev_info->segment_type) {
+		case SEG_TYPE_SR:
+		case SEG_TYPE_ER:
+		case SEG_TYPE_SC:
+			set_disk_ro(dev_info->gd,1);
+			break;
+		default:
+			set_disk_ro(dev_info->gd,0);
+			break;
+	}
+	PRINT_DEBUG("Segment %s loaded successfully\n", local_buf);
+	up_write(&dcssblk_devices_sem);
+	rc = count;
+	goto out;
+
+unregister_dev:
+	PRINT_ERR("device_create_file() failed!\n");
+	list_del(&dev_info->lh);
+	blk_put_queue(dev_info->dcssblk_queue);
+	dev_info->gd->queue = NULL;
+	put_disk(dev_info->gd);
+	device_unregister(&dev_info->dev);
+	segment_unload(dev_info->segment_name);
+	put_device(&dev_info->dev);
+	up_write(&dcssblk_devices_sem);
+	goto out;
+list_del:
+	list_del(&dev_info->lh);
+	up_write(&dcssblk_devices_sem);
+unload_seg:
+	segment_unload(local_buf);
+dealloc_gendisk:
+	blk_put_queue(dev_info->dcssblk_queue);
+	dev_info->gd->queue = NULL;
+	put_disk(dev_info->gd);
+free_dev_info:
+	kfree(dev_info);
+out:
+	kfree(local_buf);
+out_nobuf:
+	return rc;
+}
+
+/*
+ * device attribute for removing devices
+ */
+static ssize_t
+dcssblk_remove_store(struct device *dev, const char *buf, size_t count)
+{
+	struct dcssblk_dev_info *dev_info;
+	int rc, i;
+	char *local_buf;
+
+	if (dev != dcssblk_root_dev) {
+		return -EINVAL;
+	}
+	local_buf = kmalloc(count + 1, GFP_KERNEL);
+	if (local_buf == NULL) {
+		return -ENOMEM;
+	}
+	/*
+	 * parse input
+	 */
+	for (i = 0; ((*(buf+i)!='\0') && (*(buf+i)!='\n') && i < count); i++) {
+		local_buf[i] = toupper(buf[i]);
+	}
+	local_buf[i] = '\0';
+	if ((i == 0) || (i > 8)) {
+		rc = -ENAMETOOLONG;
+		goto out_buf;
+	}
+
+	down_write(&dcssblk_devices_sem);
+	dev_info = dcssblk_get_device_by_name(local_buf);
+	if (dev_info == NULL) {
+		up_write(&dcssblk_devices_sem);
+		PRINT_WARN("Segment %s is not loaded!\n", local_buf);
+		rc = -ENODEV;
+		goto out_buf;
+	}
+	if (atomic_read(&dev_info->use_count) != 0) {
+		up_write(&dcssblk_devices_sem);
+		PRINT_WARN("Segment %s is in use!\n", local_buf);
+		rc = -EBUSY;
+		goto out_buf;
+	}
+	list_del(&dev_info->lh);
+
+	del_gendisk(dev_info->gd);
+	blk_put_queue(dev_info->dcssblk_queue);
+	dev_info->gd->queue = NULL;
+	put_disk(dev_info->gd);
+	device_unregister(&dev_info->dev);
+	segment_unload(dev_info->segment_name);
+	PRINT_DEBUG("Segment %s unloaded successfully\n",
+			dev_info->segment_name);
+	put_device(&dev_info->dev);
+	up_write(&dcssblk_devices_sem);
+
+	rc = count;
+out_buf:
+	kfree(local_buf);
+	return rc;
+}
+
+static int
+dcssblk_open(struct inode *inode, struct file *filp)
+{
+	struct dcssblk_dev_info *dev_info;
+	int rc;
+
+	dev_info = inode->i_bdev->bd_disk->private_data;
+	if (NULL == dev_info) {
+		rc = -ENODEV;
+		goto out;
+	}
+	atomic_inc(&dev_info->use_count);
+	inode->i_bdev->bd_block_size = 4096;
+	rc = 0;
+out:
+	return rc;
+}
+
+static int
+dcssblk_release(struct inode *inode, struct file *filp)
+{
+	struct dcssblk_dev_info *dev_info;
+	int rc;
+
+	dev_info = inode->i_bdev->bd_disk->private_data;
+	if (NULL == dev_info) {
+		rc = -ENODEV;
+		goto out;
+	}
+	down_write(&dcssblk_devices_sem);
+	if (atomic_dec_and_test(&dev_info->use_count)
+	    && (dev_info->save_pending)) {
+		PRINT_INFO("Segment %s became idle and is being saved now\n",
+			    dev_info->segment_name);
+		segment_save(dev_info->segment_name);
+		dev_info->save_pending = 0;
+	}
+	up_write(&dcssblk_devices_sem);
+	rc = 0;
+out:
+	return rc;
+}
+
+static int
+dcssblk_make_request(request_queue_t *q, struct bio *bio)
+{
+	struct dcssblk_dev_info *dev_info;
+	struct bio_vec *bvec;
+	unsigned long index;
+	unsigned long page_addr;
+	unsigned long source_addr;
+	unsigned long bytes_done;
+	int i;
+
+	bytes_done = 0;
+	dev_info = bio->bi_bdev->bd_disk->private_data;
+	if (dev_info == NULL)
+		goto fail;
+	if ((bio->bi_sector & 7) != 0 || (bio->bi_size & 4095) != 0)
+		/* Request is not page-aligned. */
+		goto fail;
+	if (((bio->bi_size >> 9) + bio->bi_sector)
+			> get_capacity(bio->bi_bdev->bd_disk)) {
+		/* Request beyond end of DCSS segment. */
+		goto fail;
+	}
+	index = (bio->bi_sector >> 3);
+	bio_for_each_segment(bvec, bio, i) {
+		page_addr = (unsigned long)
+			page_address(bvec->bv_page) + bvec->bv_offset;
+		source_addr = dev_info->start + (index<<12) + bytes_done;
+		if (unlikely(page_addr & 4095) != 0 || (bvec->bv_len & 4095) != 0)
+			// More paranoia.
+			goto fail;
+		if (bio_data_dir(bio) == READ) {
+			memcpy((void*)page_addr, (void*)source_addr,
+				bvec->bv_len);
+		} else {
+			memcpy((void*)source_addr, (void*)page_addr,
+				bvec->bv_len);
+		}
+		bytes_done += bvec->bv_len;
+	}
+	bio_endio(bio, bytes_done, 0);
+	return 0;
+fail:
+	bio_io_error(bio, bytes_done);
+	return 0;
+}
+
+static void
+dcssblk_check_params(void)
+{
+	int rc, i, j, k;
+	char buf[9];
+	struct dcssblk_dev_info *dev_info;
+
+	for (i = 0; (i < DCSSBLK_PARM_LEN) && (dcssblk_segments[i] != '\0');
+	     i++) {
+		for (j = i; (dcssblk_segments[j] != ',')  &&
+			    (dcssblk_segments[j] != '\0') &&
+			    (dcssblk_segments[j] != '(')  &&
+			    (j - i) < 8; j++)
+		{
+			buf[j-i] = dcssblk_segments[j];
+		}
+		buf[j-i] = '\0';
+		rc = dcssblk_add_store(dcssblk_root_dev, buf, j-i);
+		if ((rc >= 0) && (dcssblk_segments[j] == '(')) {
+			for (k = 0; buf[k] != '\0'; k++)
+				buf[k] = toupper(buf[k]);
+			if (!strncmp(&dcssblk_segments[j], "(local)", 7)) {
+				down_read(&dcssblk_devices_sem);
+				dev_info = dcssblk_get_device_by_name(buf);
+				up_read(&dcssblk_devices_sem);
+				if (dev_info)
+					dcssblk_shared_store(&dev_info->dev,
+							     "0\n", 2);
+			}
+		}
+		while ((dcssblk_segments[j] != ',') &&
+		       (dcssblk_segments[j] != '\0'))
+		{
+			j++;
+		}
+		if (dcssblk_segments[j] == '\0')
+			break;
+		i = j;
+	}
+}
+
+/*
+ * The init/exit functions.
+ */
+static void __exit
+dcssblk_exit(void)
+{
+	int rc;
+
+	PRINT_DEBUG("DCSSBLOCK EXIT...\n");
+	s390_root_dev_unregister(dcssblk_root_dev);
+	rc = unregister_blkdev(dcssblk_major, DCSSBLK_NAME);
+	if (rc) {
+		PRINT_ERR("unregister_blkdev() failed!\n");
+	}
+	PRINT_DEBUG("...finished!\n");
+}
+
+static int __init
+dcssblk_init(void)
+{
+	int rc;
+
+	PRINT_DEBUG("DCSSBLOCK INIT...\n");
+	dcssblk_root_dev = s390_root_dev_register("dcssblk");
+	if (IS_ERR(dcssblk_root_dev)) {
+		PRINT_ERR("device_register() failed!\n");
+		return PTR_ERR(dcssblk_root_dev);
+	}
+	rc = device_create_file(dcssblk_root_dev, &dev_attr_add);
+	if (rc) {
+		PRINT_ERR("device_create_file(add) failed!\n");
+		s390_root_dev_unregister(dcssblk_root_dev);
+		return rc;
+	}
+	rc = device_create_file(dcssblk_root_dev, &dev_attr_remove);
+	if (rc) {
+		PRINT_ERR("device_create_file(remove) failed!\n");
+		s390_root_dev_unregister(dcssblk_root_dev);
+		return rc;
+	}
+	rc = register_blkdev(0, DCSSBLK_NAME);
+	if (rc < 0) {
+		PRINT_ERR("Can't get dynamic major!\n");
+		s390_root_dev_unregister(dcssblk_root_dev);
+		return rc;
+	}
+	dcssblk_major = rc;
+	init_rwsem(&dcssblk_devices_sem);
+
+	dcssblk_check_params();
+
+	PRINT_DEBUG("...finished!\n");
+	return 0;
+}
+
+module_init(dcssblk_init);
+module_exit(dcssblk_exit);
+
+module_param_string(segments, dcssblk_segments, DCSSBLK_PARM_LEN, 0444);
+MODULE_PARM_DESC(segments, "Name of DCSS segment(s) to be loaded, "
+		 "comma-separated list, each name max. 8 chars.\n"
+		 "Adding \"(local)\" to segment name equals echoing 0 to "
+		 "/sys/devices/dcssblk/<segment name>/shared after loading "
+		 "the segment - \n"
+		 "e.g. segments=\"mydcss1,mydcss2,mydcss3(local)\"");
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/s390/block/xpram.c b/drivers/s390/block/xpram.c
new file mode 100644
index 0000000..d428c90
--- /dev/null
+++ b/drivers/s390/block/xpram.c
@@ -0,0 +1,539 @@
+/*
+ * Xpram.c -- the S/390 expanded memory RAM-disk
+ *           
+ * significant parts of this code are based on
+ * the sbull device driver presented in
+ * A. Rubini: Linux Device Drivers
+ *
+ * Author of XPRAM specific coding: Reinhard Buendgen
+ *                                  buendgen@de.ibm.com
+ * Rewrite for 2.5: Martin Schwidefsky <schwidefsky@de.ibm.com>
+ *
+ * External interfaces:
+ *   Interfaces to linux kernel
+ *        xpram_setup: read kernel parameters
+ *   Device specific file operations
+ *        xpram_iotcl
+ *        xpram_open
+ *
+ * "ad-hoc" partitioning:
+ *    the expanded memory can be partitioned among several devices 
+ *    (with different minors). The partitioning set up can be
+ *    set by kernel or module parameters (int devs & int sizes[])
+ *
+ * Potential future improvements:
+ *   generic hard disk support to replace ad-hoc partitioning
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/ctype.h>  /* isdigit, isxdigit */
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/blkdev.h>
+#include <linux/blkpg.h>
+#include <linux/hdreg.h>  /* HDIO_GETGEO */
+#include <linux/sysdev.h>
+#include <linux/bio.h>
+#include <linux/devfs_fs_kernel.h>
+#include <asm/uaccess.h>
+
+#define XPRAM_NAME	"xpram"
+#define XPRAM_DEVS	1	/* one partition */
+#define XPRAM_MAX_DEVS	32	/* maximal number of devices (partitions) */
+
+#define PRINT_DEBUG(x...)	printk(KERN_DEBUG XPRAM_NAME " debug:" x)
+#define PRINT_INFO(x...)	printk(KERN_INFO XPRAM_NAME " info:" x)
+#define PRINT_WARN(x...)	printk(KERN_WARNING XPRAM_NAME " warning:" x)
+#define PRINT_ERR(x...)		printk(KERN_ERR XPRAM_NAME " error:" x)
+
+
+static struct sysdev_class xpram_sysclass = {
+	set_kset_name("xpram"),
+};
+
+static struct sys_device xpram_sys_device = {
+	.id	= 0,
+	.cls	= &xpram_sysclass,
+}; 
+
+typedef struct {
+	unsigned int	size;		/* size of xpram segment in pages */
+	unsigned int	offset;		/* start page of xpram segment */
+} xpram_device_t;
+
+static xpram_device_t xpram_devices[XPRAM_MAX_DEVS];
+static unsigned int xpram_sizes[XPRAM_MAX_DEVS];
+static struct gendisk *xpram_disks[XPRAM_MAX_DEVS];
+static unsigned int xpram_pages;
+static int xpram_devs;
+
+/*
+ * Parameter parsing functions.
+ */
+static int devs = XPRAM_DEVS;
+static unsigned int sizes[XPRAM_MAX_DEVS];
+
+module_param(devs, int, 0);
+module_param_array(sizes, int, NULL, 0);
+
+MODULE_PARM_DESC(devs, "number of devices (\"partitions\"), " \
+		 "the default is " __MODULE_STRING(XPRAM_DEVS) "\n");
+MODULE_PARM_DESC(sizes, "list of device (partition) sizes " \
+		 "the defaults are 0s \n" \
+		 "All devices with size 0 equally partition the "
+		 "remaining space on the expanded strorage not "
+		 "claimed by explicit sizes\n");
+MODULE_LICENSE("GPL");
+
+#ifndef MODULE
+/*
+ * Parses the kernel parameters given in the kernel parameter line.
+ * The expected format is
+ *           <number_of_partitions>[","<partition_size>]*
+ * where
+ *           devices is a positive integer that initializes xpram_devs
+ *           each size is a non-negative integer possibly followed by a
+ *           magnitude (k,K,m,M,g,G), the list of sizes initialises
+ *           xpram_sizes
+ *
+ * Arguments
+ *           str: substring of kernel parameter line that contains xprams
+ *                kernel parameters.
+ *
+ * Result    0 on success, -EINVAL else -- only for Version > 2.3
+ *
+ * Side effects
+ *           the global variabls devs is set to the value of
+ *           <number_of_partitions> and sizes[i] is set to the i-th
+ *           partition size (if provided). A parsing error of a value
+ *           results in this value being set to -EINVAL.
+ */
+static int __init xpram_setup (char *str)
+{
+	char *cp;
+	int i;
+
+	devs = simple_strtoul(str, &cp, 10);
+	if (cp <= str || devs > XPRAM_MAX_DEVS)
+		return 0;
+	for (i = 0; (i < devs) && (*cp++ == ','); i++) {
+		sizes[i] = simple_strtoul(cp, &cp, 10);
+		if (*cp == 'g' || *cp == 'G') {
+			sizes[i] <<= 20;
+			cp++;
+		} else if (*cp == 'm' || *cp == 'M') {
+			sizes[i] <<= 10;
+			cp++;
+		} else if (*cp == 'k' || *cp == 'K')
+			cp++;
+		while (isspace(*cp)) cp++;
+	}
+	if (*cp == ',' && i >= devs)
+		PRINT_WARN("partition sizes list has too many entries.\n");
+	else if (*cp != 0)
+		PRINT_WARN("ignored '%s' at end of parameter string.\n", cp);
+	return 1;
+}
+
+__setup("xpram_parts=", xpram_setup);
+#endif
+
+/*
+ * Copy expanded memory page (4kB) into main memory                  
+ * Arguments                                                         
+ *           page_addr:    address of target page                    
+ *           xpage_index:  index of expandeded memory page           
+ * Return value                                                      
+ *           0:            if operation succeeds
+ *           -EIO:         if pgin failed
+ *           -ENXIO:       if xpram has vanished
+ */
+static int xpram_page_in (unsigned long page_addr, unsigned int xpage_index)
+{
+	int cc;
+
+	__asm__ __volatile__ (
+		"   lhi   %0,2\n"  /* return unused cc 2 if pgin traps */
+		"   .insn rre,0xb22e0000,%1,%2\n"  /* pgin %1,%2 */
+                "0: ipm   %0\n"
+		"   srl   %0,28\n"
+		"1:\n"
+#ifndef CONFIG_ARCH_S390X
+		".section __ex_table,\"a\"\n"
+		"   .align 4\n"
+		"   .long  0b,1b\n"
+		".previous"
+#else
+                ".section __ex_table,\"a\"\n"
+                "   .align 8\n"
+                "   .quad 0b,1b\n"
+                ".previous"
+#endif
+		: "=&d" (cc) 
+		: "a" (__pa(page_addr)), "a" (xpage_index) 
+		: "cc" );
+	if (cc == 3)
+		return -ENXIO;
+	if (cc == 2) {
+		PRINT_ERR("expanded storage lost!\n");
+		return -ENXIO;
+	}
+	if (cc == 1) {
+		PRINT_ERR("page in failed for page index %u.\n",
+			  xpage_index);
+		return -EIO;
+	}
+	return 0;
+}
+
+/*
+ * Copy a 4kB page of main memory to an expanded memory page          
+ * Arguments                                                          
+ *           page_addr:    address of source page                     
+ *           xpage_index:  index of expandeded memory page            
+ * Return value                                                       
+ *           0:            if operation succeeds
+ *           -EIO:         if pgout failed
+ *           -ENXIO:       if xpram has vanished
+ */
+static long xpram_page_out (unsigned long page_addr, unsigned int xpage_index)
+{
+	int cc;
+
+	__asm__ __volatile__ (
+		"   lhi   %0,2\n"  /* return unused cc 2 if pgout traps */
+		"   .insn rre,0xb22f0000,%1,%2\n"  /* pgout %1,%2 */
+                "0: ipm   %0\n"
+		"   srl   %0,28\n"
+		"1:\n"
+#ifndef CONFIG_ARCH_S390X
+		".section __ex_table,\"a\"\n"
+		"   .align 4\n"
+		"   .long  0b,1b\n"
+		".previous"
+#else
+                ".section __ex_table,\"a\"\n"
+                "   .align 8\n"
+                "   .quad 0b,1b\n"
+                ".previous"
+#endif
+		: "=&d" (cc) 
+		: "a" (__pa(page_addr)), "a" (xpage_index) 
+		: "cc" );
+	if (cc == 3)
+		return -ENXIO;
+	if (cc == 2) {
+		PRINT_ERR("expanded storage lost!\n");
+		return -ENXIO;
+	}
+	if (cc == 1) {
+		PRINT_ERR("page out failed for page index %u.\n",
+			  xpage_index);
+		return -EIO;
+	}
+	return 0;
+}
+
+/*
+ * Check if xpram is available.
+ */
+static int __init xpram_present(void)
+{
+	unsigned long mem_page;
+	int rc;
+
+	mem_page = (unsigned long) __get_free_page(GFP_KERNEL);
+	if (!mem_page)
+		return -ENOMEM;
+	rc = xpram_page_in(mem_page, 0);
+	free_page(mem_page);
+	return rc ? -ENXIO : 0;
+}
+
+/*
+ * Return index of the last available xpram page.
+ */
+static unsigned long __init xpram_highest_page_index(void)
+{
+	unsigned int page_index, add_bit;
+	unsigned long mem_page;
+
+	mem_page = (unsigned long) __get_free_page(GFP_KERNEL);
+	if (!mem_page)
+		return 0;
+
+	page_index = 0;
+	add_bit = 1ULL << (sizeof(unsigned int)*8 - 1);
+	while (add_bit > 0) {
+		if (xpram_page_in(mem_page, page_index | add_bit) == 0)
+			page_index |= add_bit;
+		add_bit >>= 1;
+	}
+
+	free_page (mem_page);
+
+	return page_index;
+}
+
+/*
+ * Block device make request function.
+ */
+static int xpram_make_request(request_queue_t *q, struct bio *bio)
+{
+	xpram_device_t *xdev = bio->bi_bdev->bd_disk->private_data;
+	struct bio_vec *bvec;
+	unsigned int index;
+	unsigned long page_addr;
+	unsigned long bytes;
+	int i;
+
+	if ((bio->bi_sector & 7) != 0 || (bio->bi_size & 4095) != 0)
+		/* Request is not page-aligned. */
+		goto fail;
+	if ((bio->bi_size >> 12) > xdev->size)
+		/* Request size is no page-aligned. */
+		goto fail;
+	if ((bio->bi_sector >> 3) > 0xffffffffU - xdev->offset)
+		goto fail;
+	index = (bio->bi_sector >> 3) + xdev->offset;
+	bio_for_each_segment(bvec, bio, i) {
+		page_addr = (unsigned long)
+			kmap(bvec->bv_page) + bvec->bv_offset;
+		bytes = bvec->bv_len;
+		if ((page_addr & 4095) != 0 || (bytes & 4095) != 0)
+			/* More paranoia. */
+			goto fail;
+		while (bytes > 0) {
+			if (bio_data_dir(bio) == READ) {
+				if (xpram_page_in(page_addr, index) != 0)
+					goto fail;
+			} else {
+				if (xpram_page_out(page_addr, index) != 0)
+					goto fail;
+			}
+			page_addr += 4096;
+			bytes -= 4096;
+			index++;
+		}
+	}
+	set_bit(BIO_UPTODATE, &bio->bi_flags);
+	bytes = bio->bi_size;
+	bio->bi_size = 0;
+	bio->bi_end_io(bio, bytes, 0);
+	return 0;
+fail:
+	bio_io_error(bio, bio->bi_size);
+	return 0;
+}
+
+static int xpram_ioctl (struct inode *inode, struct file *filp,
+		 unsigned int cmd, unsigned long arg)
+{
+	struct hd_geometry __user *geo;
+	unsigned long size;
+	if (cmd != HDIO_GETGEO)
+		return -EINVAL;
+	/*
+	 * get geometry: we have to fake one...  trim the size to a
+	 * multiple of 64 (32k): tell we have 16 sectors, 4 heads,
+	 * whatever cylinders. Tell also that data starts at sector. 4.
+	 */
+	geo = (struct hd_geometry __user *) arg;
+	size = (xpram_pages * 8) & ~0x3f;
+	put_user(size >> 6, &geo->cylinders);
+	put_user(4, &geo->heads);
+	put_user(16, &geo->sectors);
+	put_user(4, &geo->start);
+	return 0;
+}
+
+static struct block_device_operations xpram_devops =
+{
+	.owner	= THIS_MODULE,
+	.ioctl	= xpram_ioctl,
+};
+
+/*
+ * Setup xpram_sizes array.
+ */
+static int __init xpram_setup_sizes(unsigned long pages)
+{
+	unsigned long mem_needed;
+	unsigned long mem_auto;
+	int mem_auto_no;
+	int i;
+
+	/* Check number of devices. */
+	if (devs <= 0 || devs > XPRAM_MAX_DEVS) {
+		PRINT_ERR("invalid number %d of devices\n",devs);
+		return -EINVAL;
+	}
+	xpram_devs = devs;
+
+	/*
+	 * Copy sizes array to xpram_sizes and align partition
+	 * sizes to page boundary.
+	 */
+	mem_needed = 0;
+	mem_auto_no = 0;
+	for (i = 0; i < xpram_devs; i++) {
+		xpram_sizes[i] = (sizes[i] + 3) & -4UL;
+		if (xpram_sizes[i])
+			mem_needed += xpram_sizes[i];
+		else
+			mem_auto_no++;
+	}
+	
+	PRINT_INFO("  number of devices (partitions): %d \n", xpram_devs);
+	for (i = 0; i < xpram_devs; i++) {
+		if (xpram_sizes[i])
+			PRINT_INFO("  size of partition %d: %u kB\n",
+				   i, xpram_sizes[i]);
+		else
+			PRINT_INFO("  size of partition %d to be set "
+				   "automatically\n",i);
+	}
+	PRINT_DEBUG("  memory needed (for sized partitions): %lu kB\n",
+		    mem_needed);
+	PRINT_DEBUG("  partitions to be sized automatically: %d\n",
+		    mem_auto_no);
+
+	if (mem_needed > pages * 4) {
+		PRINT_ERR("Not enough expanded memory available\n");
+		return -EINVAL;
+	}
+
+	/*
+	 * partitioning:
+	 * xpram_sizes[i] != 0; partition i has size xpram_sizes[i] kB
+	 * else:             ; all partitions with zero xpram_sizes[i]
+	 *                     partition equally the remaining space
+	 */
+	if (mem_auto_no) {
+		mem_auto = ((pages - mem_needed / 4) / mem_auto_no) * 4;
+		PRINT_INFO("  automatically determined "
+			   "partition size: %lu kB\n", mem_auto);
+		for (i = 0; i < xpram_devs; i++)
+			if (xpram_sizes[i] == 0)
+				xpram_sizes[i] = mem_auto;
+	}
+	return 0;
+}
+
+static struct request_queue *xpram_queue;
+
+static int __init xpram_setup_blkdev(void)
+{
+	unsigned long offset;
+	int i, rc = -ENOMEM;
+
+	for (i = 0; i < xpram_devs; i++) {
+		struct gendisk *disk = alloc_disk(1);
+		if (!disk)
+			goto out;
+		xpram_disks[i] = disk;
+	}
+
+	/*
+	 * Register xpram major.
+	 */
+	rc = register_blkdev(XPRAM_MAJOR, XPRAM_NAME);
+	if (rc < 0)
+		goto out;
+
+	devfs_mk_dir("slram");
+
+	/*
+	 * Assign the other needed values: make request function, sizes and
+	 * hardsect size. All the minor devices feature the same value.
+	 */
+	xpram_queue = blk_alloc_queue(GFP_KERNEL);
+	if (!xpram_queue) {
+		rc = -ENOMEM;
+		goto out_unreg;
+	}
+	blk_queue_make_request(xpram_queue, xpram_make_request);
+	blk_queue_hardsect_size(xpram_queue, 4096);
+
+	/*
+	 * Setup device structures.
+	 */
+	offset = 0;
+	for (i = 0; i < xpram_devs; i++) {
+		struct gendisk *disk = xpram_disks[i];
+
+		xpram_devices[i].size = xpram_sizes[i] / 4;
+		xpram_devices[i].offset = offset;
+		offset += xpram_devices[i].size;
+		disk->major = XPRAM_MAJOR;
+		disk->first_minor = i;
+		disk->fops = &xpram_devops;
+		disk->private_data = &xpram_devices[i];
+		disk->queue = xpram_queue;
+		sprintf(disk->disk_name, "slram%d", i);
+		sprintf(disk->devfs_name, "slram/%d", i);
+		set_capacity(disk, xpram_sizes[i] << 1);
+		add_disk(disk);
+	}
+
+	return 0;
+out_unreg:
+	devfs_remove("slram");
+	unregister_blkdev(XPRAM_MAJOR, XPRAM_NAME);
+out:
+	while (i--)
+		put_disk(xpram_disks[i]);
+	return rc;
+}
+
+/*
+ * Finally, the init/exit functions.
+ */
+static void __exit xpram_exit(void)
+{
+	int i;
+	for (i = 0; i < xpram_devs; i++) {
+		del_gendisk(xpram_disks[i]);
+		put_disk(xpram_disks[i]);
+	}
+	unregister_blkdev(XPRAM_MAJOR, XPRAM_NAME);
+	devfs_remove("slram");
+	blk_cleanup_queue(xpram_queue);
+	sysdev_unregister(&xpram_sys_device);
+	sysdev_class_unregister(&xpram_sysclass);
+}
+
+static int __init xpram_init(void)
+{
+	int rc;
+
+	/* Find out size of expanded memory. */
+	if (xpram_present() != 0) {
+		PRINT_WARN("No expanded memory available\n");
+		return -ENODEV;
+	}
+	xpram_pages = xpram_highest_page_index();
+	PRINT_INFO("  %u pages expanded memory found (%lu KB).\n",
+		   xpram_pages, (unsigned long) xpram_pages*4);
+	rc = xpram_setup_sizes(xpram_pages);
+	if (rc)
+		return rc;
+	rc = sysdev_class_register(&xpram_sysclass);
+	if (rc)
+		return rc;
+
+	rc = sysdev_register(&xpram_sys_device);
+	if (rc) {
+		sysdev_class_unregister(&xpram_sysclass);
+		return rc;
+	}
+	rc = xpram_setup_blkdev();
+	if (rc)
+		sysdev_unregister(&xpram_sys_device);
+	return rc;
+}
+
+module_init(xpram_init);
+module_exit(xpram_exit);