diff options
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; + + /* 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); |