aboutsummaryrefslogtreecommitdiff
path: root/drivers/s390/block
diff options
context:
space:
mode:
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;
+
+ /* 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 00000000000..84565c8f584
--- /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 00000000000..c143ecb53d9
--- /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 00000000000..01e87170a3a
--- /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 00000000000..2a23b74faf3
--- /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 00000000000..ed1ab474c0c
--- /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 00000000000..ad1841a96c8
--- /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 00000000000..127699830fa
--- /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 00000000000..a0c38e30397
--- /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 00000000000..838aedf78a5
--- /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 00000000000..b6888c68b22
--- /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 00000000000..7cb98d25f34
--- /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 00000000000..7963ae343ee
--- /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 00000000000..624f0402ee2
--- /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 00000000000..1d52db406b2
--- /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 00000000000..4586e0ecc52
--- /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 00000000000..f1892baa3b1
--- /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 00000000000..353d41118c6
--- /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 00000000000..a66b17b6529
--- /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 00000000000..d428c909b8a
--- /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);