diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/s390/block |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/s390/block')
-rw-r--r-- | drivers/s390/block/Kconfig | 68 | ||||
-rw-r--r-- | drivers/s390/block/Makefile | 17 | ||||
-rw-r--r-- | drivers/s390/block/dasd.c | 2065 | ||||
-rw-r--r-- | drivers/s390/block/dasd_3370_erp.c | 104 | ||||
-rw-r--r-- | drivers/s390/block/dasd_3990_erp.c | 2742 | ||||
-rw-r--r-- | drivers/s390/block/dasd_9336_erp.c | 61 | ||||
-rw-r--r-- | drivers/s390/block/dasd_9343_erp.c | 22 | ||||
-rw-r--r-- | drivers/s390/block/dasd_cmb.c | 145 | ||||
-rw-r--r-- | drivers/s390/block/dasd_devmap.c | 772 | ||||
-rw-r--r-- | drivers/s390/block/dasd_diag.c | 541 | ||||
-rw-r--r-- | drivers/s390/block/dasd_diag.h | 66 | ||||
-rw-r--r-- | drivers/s390/block/dasd_eckd.c | 1722 | ||||
-rw-r--r-- | drivers/s390/block/dasd_eckd.h | 346 | ||||
-rw-r--r-- | drivers/s390/block/dasd_erp.c | 254 | ||||
-rw-r--r-- | drivers/s390/block/dasd_fba.c | 607 | ||||
-rw-r--r-- | drivers/s390/block/dasd_fba.h | 73 | ||||
-rw-r--r-- | drivers/s390/block/dasd_genhd.c | 185 | ||||
-rw-r--r-- | drivers/s390/block/dasd_int.h | 576 | ||||
-rw-r--r-- | drivers/s390/block/dasd_ioctl.c | 554 | ||||
-rw-r--r-- | drivers/s390/block/dasd_proc.c | 319 | ||||
-rw-r--r-- | drivers/s390/block/dcssblk.c | 775 | ||||
-rw-r--r-- | drivers/s390/block/xpram.c | 539 |
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 |