aboutsummaryrefslogtreecommitdiff
path: root/drivers/s390/block
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
commit1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch)
tree0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/s390/block
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history, even though we have it. We can create a separate "historical" git archive of that later if we want to, and in the meantime it's about 3.2GB when imported into git - space that would just make the early git days unnecessarily complicated, when we don't have a lot of good infrastructure for it. Let it rip!
Diffstat (limited to 'drivers/s390/block')
-rw-r--r--drivers/s390/block/Kconfig68
-rw-r--r--drivers/s390/block/Makefile17
-rw-r--r--drivers/s390/block/dasd.c2065
-rw-r--r--drivers/s390/block/dasd_3370_erp.c104
-rw-r--r--drivers/s390/block/dasd_3990_erp.c2742
-rw-r--r--drivers/s390/block/dasd_9336_erp.c61
-rw-r--r--drivers/s390/block/dasd_9343_erp.c22
-rw-r--r--drivers/s390/block/dasd_cmb.c145
-rw-r--r--drivers/s390/block/dasd_devmap.c772
-rw-r--r--drivers/s390/block/dasd_diag.c541
-rw-r--r--drivers/s390/block/dasd_diag.h66
-rw-r--r--drivers/s390/block/dasd_eckd.c1722
-rw-r--r--drivers/s390/block/dasd_eckd.h346
-rw-r--r--drivers/s390/block/dasd_erp.c254
-rw-r--r--drivers/s390/block/dasd_fba.c607
-rw-r--r--drivers/s390/block/dasd_fba.h73
-rw-r--r--drivers/s390/block/dasd_genhd.c185
-rw-r--r--drivers/s390/block/dasd_int.h576
-rw-r--r--drivers/s390/block/dasd_ioctl.c554
-rw-r--r--drivers/s390/block/dasd_proc.c319
-rw-r--r--drivers/s390/block/dcssblk.c775
-rw-r--r--drivers/s390/block/xpram.c539
22 files changed, 12553 insertions, 0 deletions
diff --git a/drivers/s390/block/Kconfig b/drivers/s390/block/Kconfig
new file mode 100644
index 00000000000..dc1c89dbdb8
--- /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 00000000000..58c6780134f
--- /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 00000000000..b755bac6ccb
--- /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