/*
 * 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>
 * Bugreports.to..: <Linux390@de.ibm.com>
 * (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999-2001
 *
 * $Revision: 1.298.2.11 $
 *
 * History of changes (starts July 2000)
 * 11/09/00 complete redesign after code review
 * 02/01/01 added dynamic registration of ioctls
 *          fixed bug in registration of new majors
 *          fixed handling of request during dasd_end_request
 *          fixed handling of plugged queues
 *          fixed partition handling and HDIO_GETGEO
 *          fixed traditional naming scheme for devices beyond 702
 *          fixed some race conditions related to modules
 *          added devfs suupport
 * 03/06/01 refined dynamic attach/detach for leaving devices which are online.
 * 03/09/01 refined dynamic modifiaction of devices
 * 03/12/01 moved policy in dasd_format to dasdfmt (renamed BIODASDFORMAT)
 * 03/19/01 added BIODASDINFO-ioctl
 *          removed 2.2 compatibility
 * 04/27/01 fixed PL030119COT (dasd_disciplines does not work)
 * 04/30/01 fixed PL030146HSM (module locking with dynamic ioctls)
 *          fixed PL030130SBA (handling of invalid ranges)
 * 05/02/01 fixed PL030145SBA (killing dasdmt)
 *          fixed PL030149SBA (status of 'accepted' devices)
 *          fixed PL030146SBA (BUG in ibm.c after adding device)
 *          added BIODASDPRRD ioctl interface
 * 05/11/01 fixed  PL030164MVE (trap in probeonly mode)
 * 05/15/01 fixed devfs support for unformatted devices
 * 06/26/01 hopefully fixed PL030172SBA,PL030234SBA
 * 07/09/01 fixed PL030324MSH (wrong statistics output)
 * 07/16/01 merged in new fixes for handling low-mem situations
 * 01/22/01 fixed PL030579KBE (wrong statistics)
 * 08/07/03 fixed LTC BZ 3847 Erroneous message when formatting DASD
 */

#include <linux/config.h>
#include <linux/version.h>
#include <linux/kmod.h>
#include <linux/init.h>
#include <linux/blkdev.h>
#include <linux/stddef.h>
#include <linux/kernel.h>
#include <linux/tqueue.h>
#include <linux/timer.h>
#include <linux/slab.h>
#include <linux/genhd.h>
#include <linux/hdreg.h>
#include <linux/interrupt.h>
#include <linux/ctype.h>
#ifdef CONFIG_PROC_FS
#include <linux/proc_fs.h>
#endif				/* CONFIG_PROC_FS */
#include <linux/spinlock.h>
#include <linux/devfs_fs_kernel.h>
#include <linux/blkpg.h>
#include <linux/wait.h>

#include <asm/ccwcache.h>
#include <asm/debug.h>

#include <asm/atomic.h>
#include <asm/delay.h>
#include <asm/io.h>
#include <asm/semaphore.h>
#include <asm/ebcdic.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/s390_ext.h>
#include <asm/s390dyn.h>
#include <asm/idals.h>

#include "dasd_int.h"

#ifdef CONFIG_DASD_ECKD
#include "dasd_eckd.h"
#endif				/*  CONFIG_DASD_ECKD */
#ifdef CONFIG_DASD_FBA
#include "dasd_fba.h"
#endif				/*  CONFIG_DASD_FBA */
#ifdef CONFIG_DASD_DIAG
#include "dasd_diag.h"
#endif				/*  CONFIG_DASD_DIAG */

/********************************************************************************
 * SECTION: exported variables of dasd.c 
 ********************************************************************************/
debug_info_t *dasd_debug_area;

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_PARM (dasd_disciplines, "1-" __MODULE_STRING (8) "s");
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,12))
MODULE_LICENSE ("GPL");
#endif
EXPORT_SYMBOL (dasd_chanq_enq_head);
EXPORT_SYMBOL (dasd_debug_area);
EXPORT_SYMBOL (dasd_chanq_enq);
EXPORT_SYMBOL (dasd_chanq_deq);
EXPORT_SYMBOL (dasd_discipline_add);
EXPORT_SYMBOL (dasd_discipline_del);
EXPORT_SYMBOL (dasd_start_IO);
EXPORT_SYMBOL (dasd_term_IO);
EXPORT_SYMBOL (dasd_schedule_bh);
EXPORT_SYMBOL (dasd_schedule_bh_timed);
EXPORT_SYMBOL (dasd_int_handler);
EXPORT_SYMBOL (dasd_oper_handler);
EXPORT_SYMBOL (dasd_alloc_request);
EXPORT_SYMBOL (dasd_free_request);
EXPORT_SYMBOL (dasd_ioctl_no_register);
EXPORT_SYMBOL (dasd_ioctl_no_unregister);
EXPORT_SYMBOL (dasd_default_erp_action);
EXPORT_SYMBOL (dasd_default_erp_postaction);
EXPORT_SYMBOL (dasd_sleep_on_req);
EXPORT_SYMBOL (dasd_set_normalized_cda);
EXPORT_SYMBOL (dasd_device_from_kdev);


/********************************************************************************
 * SECTION: Constant definitions to be used within this file 
 ********************************************************************************/

#define PRINTK_HEADER DASD_NAME":"
#define DASD_PROFILE            /* fill profile information - used for */
                                /* statistics and perfomance           */

#ifndef CONFIG_PROC_FS          /* DASD_PROFILE doesn't make sense     */
#undef DASD_PROFILE             /* without procfs                      */
#endif /* not CONFIG_PROC_FS */

#define DASD_CHANQ_MAX_SIZE 4

/********************************************************************************
 * SECTION: prototypes for static functions of dasd.c 
 ********************************************************************************/

static request_fn_proc do_dasd_request;
static int dasd_set_device_level (unsigned int, dasd_discipline_t *, int);
static request_queue_t *dasd_get_queue (kdev_t kdev);
static void cleanup_dasd (void);
static void dasd_plug_device (dasd_device_t * device);
static int dasd_fillgeo (int kdev, struct hd_geometry *geo);
static void dasd_enable_ranges (dasd_range_t *, dasd_discipline_t *, int); 
static void dasd_disable_ranges (dasd_range_t *, dasd_discipline_t *, int, int); 
static void dasd_enable_single_device ( unsigned long);
static inline int dasd_state_init_to_ready(dasd_device_t*);
static inline void dasd_setup_partitions ( dasd_device_t *);
static inline void dasd_destroy_partitions ( dasd_device_t *);
static inline int dasd_setup_blkdev(dasd_device_t*);
static void dasd_deactivate_queue (dasd_device_t *);
static inline int dasd_disable_blkdev(dasd_device_t*);
static void dasd_flush_chanq ( dasd_device_t * device, int destroy ); 
static void dasd_flush_request_queues ( dasd_device_t * device, int destroy );
static struct block_device_operations dasd_device_operations;
static inline dasd_device_t ** dasd_device_from_devno (int);
static void dasd_process_queues (dasd_device_t * device);
static int  dasd_sleep_on_immediate (ccw_req_t *cqr);
static int  dasd_devno_from_devindex (int devindex);
static int  dasd_devindex_from_devno (int devno);

/********************************************************************************
 * SECTION: static variables of dasd.c 
 ********************************************************************************/

static devfs_handle_t dasd_devfs_handle;
static wait_queue_head_t dasd_init_waitq;
static atomic_t dasd_init_pending = ATOMIC_INIT (0);

#ifdef CONFIG_DASD_DYNAMIC

/********************************************************************************
 * SECTION: managing dynamic configuration of dasd_driver 
 ********************************************************************************/

static struct list_head dasd_devreg_head = LIST_HEAD_INIT (dasd_devreg_head);

/*
 * function: dasd_create_devreg
 * creates a dasd_devreg_t related to a devno
 */
static inline dasd_devreg_t *
dasd_create_devreg (int devno)
{
	dasd_devreg_t *r = kmalloc (sizeof (dasd_devreg_t), GFP_KERNEL);
	if (r != NULL) {
		memset (r, 0, sizeof (dasd_devreg_t));
		r->devreg.ci.devno = devno;
		r->devreg.flag = DEVREG_TYPE_DEVNO;
		r->devreg.oper_func = dasd_oper_handler;
	}
	return r;
}

/*
 * function: dasd_destroy_devreg
 * destroys the dasd_devreg_t given as argument
 */
static inline void
dasd_destroy_devreg (dasd_devreg_t * devreg)
{
	kfree (devreg);
}

#endif				/* CONFIG_DASD_DYNAMIC */

/********************************************************************************
 * SECTION: managing setup of dasd_driver 
 ********************************************************************************/

/* default setting is probeonly, autodetect */
static int dasd_probeonly =  0;	/* is true, when probeonly mode is active */
static int dasd_autodetect = 0;	/* is true, when autodetection is active */

static dasd_range_t dasd_range_head =
    { list:LIST_HEAD_INIT (dasd_range_head.list) };
static spinlock_t range_lock     = SPIN_LOCK_UNLOCKED;

/*
 * function: dasd_create_range
 * creates a dasd_range_t according to the arguments
 * FIXME: no check is performed for reoccurrence of a devno
 */
static inline dasd_range_t *
dasd_create_range (int from, int to, int features)
{
	dasd_range_t *range = NULL;

	range = (dasd_range_t *) kmalloc (sizeof (dasd_range_t), GFP_KERNEL);
	if (range == NULL)
		return NULL;
	memset (range, 0, sizeof (dasd_range_t));
	range->from = from;
        range->to = to;
        range->features = features;
	return range;
}

/*
 * function dasd_destroy_range
 * destroy a range allocated wit dasd_crate_range
 * CAUTION: must not be callen in arunning sysztem, because it destroys
 * the mapping of DASDs
 */
static inline void
dasd_destroy_range (dasd_range_t * range)
{
	kfree (range);
}

/*
 * function: dasd_append_range
 * appends the range given as argument to the list anchored at dasd_range_head.
 */
static inline void
dasd_append_range (dasd_range_t * range)
{
	long flags;

	spin_lock_irqsave (&range_lock, flags);
	list_add_tail (&range->list, &dasd_range_head.list);
	spin_unlock_irqrestore (&range_lock, flags);
}

/*
 * function dasd_dechain_range
 * removes a range from the chain of ranges
 * CAUTION: must not be called in a running system because it destroys
 * the mapping of devices
 */
static inline void
dasd_dechain_range (dasd_range_t * range)
{
	unsigned long flags;

	spin_lock_irqsave (&range_lock, flags);
	list_del (&range->list);
	spin_unlock_irqrestore (&range_lock, flags);
}

/*
 * function: dasd_add_range
 * creates a dasd_range_t according to the arguments and
 * appends it to the list of ranges.
 * If a device in the range is already in an other range, we split the 
 * range and add the subranges (no duplicate devices).
 * additionally a devreg_t is created and added to the list of devregs
 */
static inline void
dasd_add_range (int from, int to, int features)
{
	dasd_range_t *range;
        int start, end, index, i;

	if (from > to) {
                MESSAGE (KERN_DEBUG, 
                         "Adding device range %04x-%04x: "
                         "range invalid, ignoring.",
                         from, to);
		return;
	}

        /* loop over the given range, remove the already contained devices */
        /* and add the remaining subranges                                 */
	for (start = index = from, end = -EINVAL; index <= to; index++) {
                
                if (dasd_devindex_from_devno(index) >= 0) {
                        /* current device is already in range */
                        MESSAGE (KERN_DEBUG,
                                 "dasd_add_range %04x-%04x: "
                                 "device %04x is already in range",
                                 from, to, index);

                        if (start == index) 
                                start++;        /* first already in range */
                        else
                                end = index -1; /* current already in range */
                } else {
                        if (index == to) 
                                end = to; /* end of original range reached */
                }

                range = NULL;
                if (end != -EINVAL) {
                        MESSAGE (KERN_DEBUG,
                                 "dasd_add_range %04x-%04x: "
                                 "add (sub)range %04x-%04x",
                                 from, to, start, end);
                        
                        range = dasd_create_range (start, end, features); 
                        end = -EINVAL;
                        start = index + 1; 
                }

                if (range) {
                        dasd_append_range (range);
#ifdef CONFIG_DASD_DYNAMIC
                        /* allocate and chain devreg infos for the devnos... */
                        for (i = range->from; i <= range->to; i++) {
                                dasd_devreg_t *reg = dasd_create_devreg (i);
                                s390_device_register (&reg->devreg);
                                list_add (&reg->list, &dasd_devreg_head);
                        }
#endif				/* CONFIG_DASD_DYNAMIC */
                }
        }
	return;
}

/*
 * function: dasd_remove_range
 * removes a range and the corresponding devregs from all of the chains
 * CAUTION: must not be called in a running system because it destroys
 * the mapping of devices!
 */
static inline void
dasd_remove_range (dasd_range_t * range)
{
#ifdef CONFIG_DASD_DYNAMIC
	/* deallocate and dechain devreg infos for the devnos... */
	{
		int i;
		for (i = range->from; i <= range->to; i++) {
			struct list_head *l;
			dasd_devreg_t *reg = NULL;
			list_for_each (l, &dasd_devreg_head) {
				reg = list_entry (l, dasd_devreg_t, list);
				if (reg->devreg.flag == DEVREG_TYPE_DEVNO &&
				    reg->devreg.ci.devno == i &&
				    reg->devreg.oper_func == dasd_oper_handler)
					break;
			}
			if (l == &dasd_devreg_head)
				BUG ();
                        list_del(&reg->list);
			s390_device_unregister (&reg->devreg);
			dasd_destroy_devreg (reg);
		}
	}
#endif				/* CONFIG_DASD_DYNAMIC */
	dasd_dechain_range (range);
	dasd_destroy_range (range);
}

/* 
 * function: dasd_devindex_from_devno
 * finds the logical number of the devno supplied as argument in the list
 * of dasd ranges and returns it or ENODEV when not found
 */
static int
dasd_devindex_from_devno (int devno)
{
	dasd_range_t *temp;
	int devindex = 0;
	unsigned long flags;
	struct list_head *l;

	spin_lock_irqsave (&range_lock, flags);
	list_for_each (l, &dasd_range_head.list) {
		temp = list_entry (l, dasd_range_t, list);
		if (devno >= temp->from && devno <= temp->to) {
			spin_unlock_irqrestore (&range_lock, flags);
			return devindex + devno - temp->from;
		}
		devindex += temp->to - temp->from + 1;
	}
	spin_unlock_irqrestore (&range_lock, flags);
	return -ENODEV;
}

/*
 * function: dasd_devno_from_devindex
 */
static int
dasd_devno_from_devindex (int devindex)
{
	dasd_range_t *temp;
	unsigned long flags;
	struct list_head *l;

	spin_lock_irqsave (&range_lock, flags);
	list_for_each (l, &dasd_range_head.list) {
		temp = list_entry (l, dasd_range_t, list);
                if ( devindex < temp->to - temp->from + 1) {
			spin_unlock_irqrestore (&range_lock, flags);
			return temp->from + devindex;
		}
		devindex -= temp->to - temp->from + 1;
	}
	spin_unlock_irqrestore (&range_lock, flags);
	return -ENODEV;
}

/********************************************************************************
 * SECTION: parsing the dasd= parameter of the parmline/insmod cmdline 
 ********************************************************************************/

/*
 * 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.
 * a maximum of 256 ranges can be supplied, as the parmline is limited to
 * <1024 Byte anyway.
 */
char *dasd[256];
char *dasd_disciplines[8];

#ifndef MODULE
/*
 * function: dasd_split_parm_string
 * splits the parmline given to the kernel into comma separated strings
 * which are filled into the 'dasd[]' array, to be parsed later on
 */
static void
dasd_split_parm_string (char *str)
{
	char *tmp = str;
	int count = 0;
	while (tmp != NULL && *tmp != '\0') {
		char *end;
		int len;
		end = strchr (tmp, ',');
		if (end == NULL) {
			len = strlen (tmp) + 1;
		} else {
			len = (long) end - (long) tmp + 1;
			*end = '\0';
			end++;
		}
		dasd[count] = kmalloc (len * sizeof (char), 
                                       GFP_ATOMIC);
		if (dasd[count] == NULL) {

			MESSAGE (KERN_WARNING,
                                 "can't store dasd= parameter no"
                                 " %d",
                                 count + 1);
			break;
		}
		memset (dasd[count], 0, len * sizeof (char));
		memcpy (dasd[count], tmp, len * sizeof (char));
		count++;
		tmp = end;
	};
}

/*
 * dasd_parm_string holds a concatenated version of all 'dasd=' parameters
 * supplied in the parmline, which is later to be split by
 * dasd_split_parm_string
 * FIXME: why first concatenate then split ?
 */
static char dasd_parm_string[1024] __initdata = { 0, };

/*
 * function: dasd_setup
 * is invoked for any single 'dasd=' parameter supplied in the parmline
 * it merges all the arguments into dasd_parm_string
 */
void __init
dasd_setup (char *str, int *ints)
{
	int len = strlen (dasd_parm_string);
	if (len != 0) {
		strcat (dasd_parm_string, ",");
	}
	strcat (dasd_parm_string, str);
}

/*
 * function: dasd_call_setup
 * is the 2.4 version of dasd_setup and
 * is invoked for any single 'dasd=' parameter supplied in the parmline
 */
int __init
dasd_call_setup (char *str)
{
	int dummy;
	dasd_setup (str, &dummy);
	return 1;
}

int __init
dasd_disciplines_setup (char *str)
{
	return 1;
}

__setup ("dasd=", dasd_call_setup);
__setup ("dasd_disciplines=", dasd_disciplines_setup);

#endif				/* MODULE */

/*
 * function: dasd_strtoul
 * provides a wrapper to simple_strtoul to strip leading '0x' and
 * interpret any argument to dasd=[range,...] as hexadecimal
 */
static inline int
dasd_strtoul (char *str, char **stra, int* features)
{
        char *temp=str;
        char *buffer;
        int val,i,start;

        buffer=(char*)kmalloc((strlen(str)+1)*sizeof(char),GFP_ATOMIC);

        if (buffer==NULL) {

                MESSAGE_LOG (KERN_WARNING,
                             "can't parse dasd= parameter %s due "
                             "to low memory",
                             str);
        }

        /* remove leading '0x' */
        if (*temp == '0') {
                temp++;         /* strip leading zero */
                if (*temp == 'x')
                        temp++; /* strip leading x */
        }

        /* copy device no to buffer and convert to decimal */
        for (i=0; temp[i]!='\0' && temp[i]!='(' && 
                  temp[i]!='-'  && temp[i]!=' '; i++){
                if (isxdigit(temp[i])) {
                        buffer[i]=temp[i];
                } else {
                        return -EINVAL;
                }
        }

        buffer[i]='\0';

        val = simple_strtoul (buffer, &buffer, 16);

        /* check for features - e.g. (ro) ; the '\0', ')' and '-' stops check */
        *features = DASD_FEATURE_DEFAULT;

        if (temp[i]=='(') {

                while (temp[i]!='\0' && temp[i]!=')'&&temp[i]!='-') { 
                        start=++i;      
        
                        /* move next feature to buffer */
                        for (;temp[i]!='\0'&&temp[i]!=':'&&temp[i]!=')'&&temp[i]!='-';i++)
                                buffer[i-start]=temp[i];
                        buffer[i-start]='\0';

                        if (strlen(buffer)) { 
                                if (!strcmp(buffer,"ro")) { /* handle 'ro' feature */
                                        (*features) |= DASD_FEATURE_READONLY;
                                        break;
                                }

                                MESSAGE_LOG (KERN_WARNING,
                                             "unsupported feature: %s, "
                                             "ignoring setting",
                                             buffer);
                        }
                }
        }

        *stra = temp+i;
        if ((val > 0xFFFF) || (val < 0))
                return -EINVAL;
        return val;
}

/*
 * function: dasd_parse
 * examines the strings given in the string array str and
 * creates and adds the ranges to the apropriate lists
 */
static int
dasd_parse (char **str)
{
	char *temp;
	int from, to;
        int features;
        int rc = 0;

	while (*str) {
		temp = *str;
		from = 0;
		to = 0;
		if (strcmp ("autodetect", *str) == 0) {
			dasd_autodetect = 1;

			MESSAGE (KERN_INFO, "%s",
                                 "turning to autodetection mode");

			break;
		} else if (strcmp ("probeonly", *str) == 0) {
			dasd_probeonly = 1;

			MESSAGE (KERN_INFO, "%s",
                                 "turning to probeonly mode");

			break;
		} else {
			/* turn off autodetect mode, if any range is present */
			dasd_autodetect = 0;
			from = dasd_strtoul (temp, &temp, &features);
                        to = from;
			if (*temp == '-') {
				temp++;
				to = dasd_strtoul (temp, &temp, &features);
			}
                        if (from == -EINVAL ||
                            to   == -EINVAL    ) {
                                rc = -EINVAL;
                                break;
                        } else {
                                dasd_add_range (from, to ,features);
                        }
                }
		str++;
	}

        return rc;
}

/********************************************************************************
 * SECTION: Dealing with devices registered to multiple major numbers 
 ********************************************************************************/

static spinlock_t dasd_major_lock = SPIN_LOCK_UNLOCKED;

static struct list_head dasd_major_info = LIST_HEAD_INIT(dasd_major_info);
static major_info_t dasd_major_static = {
        gendisk:{INIT_GENDISK(94, DASD_NAME, DASD_PARTN_BITS, DASD_PER_MAJOR)},
        flags: DASD_MAJOR_INFO_IS_STATIC
};

static major_info_t *
get_new_major_info (void)
{
	major_info_t *major_info = NULL;

	major_info = kmalloc (sizeof (major_info_t), GFP_KERNEL);
	if (major_info) {
		static major_info_t temp_major_info = {
			gendisk:{
				 INIT_GENDISK (0, DASD_NAME, DASD_PARTN_BITS,
					       DASD_PER_MAJOR)}
		};
		memcpy (major_info, &temp_major_info, sizeof (major_info_t));
	}
	return major_info;
}

/*
 * register major number
 * is called with the 'static' major_info during init of the driver or 'NULL' to
 * allocate an additional dynamic major.
 */
static int
dasd_register_major (major_info_t * major_info)
{
	int rc = 0;
	int major;
	unsigned long flags;

        /* allocate dynamic major */
	if (major_info == NULL) {
		major_info = get_new_major_info ();
		if (!major_info) {

			MESSAGE (KERN_WARNING, "%s",
				"Cannot get memory to allocate another "
                                 "major number");

			return -ENOMEM;
		}
	}

	major = major_info->gendisk.major;

        /* init devfs array */
	major_info->gendisk.de_arr = (devfs_handle_t *)
	    kmalloc (DASD_PER_MAJOR * sizeof (devfs_handle_t), GFP_KERNEL);
	if(major_info->gendisk.de_arr == NULL)
		goto out_gd_de_arr;

	memset (major_info->gendisk.de_arr, 0,
		DASD_PER_MAJOR * sizeof (devfs_handle_t));

        /* init flags */
	major_info->gendisk.flags = (char *)
	    kmalloc (DASD_PER_MAJOR * sizeof (char), GFP_KERNEL);
	if(major_info->gendisk.flags == NULL)
		goto out_gd_flags;

	memset (major_info->gendisk.flags, 0, DASD_PER_MAJOR * sizeof (char));

	/* init label array */
	major_info->gendisk.label_arr = (devfs_handle_t *)
		kmalloc (DASD_PER_MAJOR * sizeof (devfs_handle_t), GFP_KERNEL);
	if(major_info->gendisk.label_arr == NULL)
		goto out_gd_label_arr;

	memset (major_info->gendisk.label_arr, 0,
		DASD_PER_MAJOR * sizeof(devfs_handle_t));

        /* register blockdevice */
	rc = devfs_register_blkdev (major, DASD_NAME, &dasd_device_operations);
	if (rc < 0) {
                
		MESSAGE (KERN_WARNING,
                         "Cannot register to major no %d, rc = %d",
                         major, 
                         rc);

		goto out_reg_blkdev; 
	} else {
		major_info->flags |= DASD_MAJOR_INFO_REGISTERED;
	}

	/* Insert the new major info into dasd_major_info if needed (dynamic major) */
	if (!(major_info->flags & DASD_MAJOR_INFO_IS_STATIC)) {
		spin_lock_irqsave (&dasd_major_lock, flags);
		list_add_tail (&major_info->list, &dasd_major_info);
		spin_unlock_irqrestore (&dasd_major_lock, flags);
	}

	if (major == 0) {
		major = rc;
		rc = 0;
	}

        /* init array of devices */
	major_info->dasd_device =
	    (dasd_device_t **) kmalloc (DASD_PER_MAJOR *
					sizeof (dasd_device_t *), GFP_ATOMIC);
	if (!major_info->dasd_device)
		goto out_devices;
	memset (major_info->dasd_device, 0,
		DASD_PER_MAJOR * sizeof (dasd_device_t *));

        /* init blk_size */
	blk_size[major] =
	    (int *) kmalloc ((1 << MINORBITS) * sizeof (int), GFP_ATOMIC);
	if (!blk_size[major])
		goto out_blk_size;
	memset (blk_size[major], 0, (1 << MINORBITS) * sizeof (int));

        /* init blksize_size */
	blksize_size[major] =
	    (int *) kmalloc ((1 << MINORBITS) * sizeof (int), GFP_ATOMIC);
	if (!blksize_size[major])
		goto out_blksize_size;
	memset (blksize_size[major], 0, (1 << MINORBITS) * sizeof (int));

        /* init_hardsect_size */
	hardsect_size[major] =
	    (int *) kmalloc ((1 << MINORBITS) * sizeof (int), GFP_ATOMIC);
	if (!hardsect_size[major])
		goto out_hardsect_size;
	memset (hardsect_size[major], 0, (1 << MINORBITS) * sizeof (int));

        /* init max_sectors */
	max_sectors[major] =
	    (int *) kmalloc ((1 << MINORBITS) * sizeof (int), GFP_ATOMIC);
	if (!max_sectors[major])
		goto out_max_sectors;
	memset (max_sectors[major], 0, (1 << MINORBITS) * sizeof (int));

	/* finally do the gendisk stuff */
	major_info->gendisk.part = kmalloc ((1 << MINORBITS) *
					    sizeof (struct hd_struct),
					    GFP_ATOMIC);
	if (!major_info->gendisk.part)
		goto out_gendisk;
	memset (major_info->gendisk.part, 0, (1 << MINORBITS) *
		sizeof (struct hd_struct));

	INIT_BLK_DEV (major, do_dasd_request, dasd_get_queue, NULL);

	major_info->gendisk.sizes = blk_size[major];
	major_info->gendisk.major = major;
	add_gendisk (&major_info->gendisk);
	return major;

        /* error handling - free the prior allocated memory */  
      out_gendisk:
	kfree (max_sectors[major]);
	max_sectors[major] = NULL;

      out_max_sectors:
	kfree (hardsect_size[major]);
	hardsect_size[major] = NULL;

      out_hardsect_size:
	kfree (blksize_size[major]);
	blksize_size[major] = NULL;

      out_blksize_size:
	kfree (blk_size[major]);
	blk_size[major] = NULL;

      out_blk_size:
	kfree (major_info->dasd_device);

      out_devices:
	/* Delete the new major info from dasd_major_info list if needed (dynamic) +*/
	if (!(major_info->flags & DASD_MAJOR_INFO_IS_STATIC)) {
		spin_lock_irqsave (&dasd_major_lock, flags);
		list_del (&major_info->list);
		spin_unlock_irqrestore (&dasd_major_lock, flags);
	}

        /* unregister blockdevice */
	rc = devfs_unregister_blkdev (major, DASD_NAME);
	if (rc < 0) {

		MESSAGE (KERN_WARNING,
                         "Unable to unregister from major no %d, rc = %d", 
                         major,
                         rc);
	} else {
		major_info->flags &= ~DASD_MAJOR_INFO_REGISTERED;
	}

out_reg_blkdev:
	kfree (major_info->gendisk.label_arr);

out_gd_label_arr:
	kfree (major_info->gendisk.flags);

out_gd_flags:
	kfree (major_info->gendisk.de_arr);

out_gd_de_arr:
	/* Delete the new major info from dasd_major_info if needed */
	if (!(major_info->flags & DASD_MAJOR_INFO_IS_STATIC)) {
		kfree (major_info);
	}

	return -ENOMEM;
}

static int
dasd_unregister_major (major_info_t * major_info)
{
	int rc = 0;
	int major;
	unsigned long flags;

	if (major_info == NULL) {
		return -EINVAL;
	}
	major = major_info->gendisk.major;
	INIT_BLK_DEV (major, NULL, NULL, NULL);

	del_gendisk (&major_info->gendisk);

	kfree (major_info->dasd_device);
	kfree (major_info->gendisk.part);

	kfree (blk_size[major]);
	kfree (blksize_size[major]);
	kfree (hardsect_size[major]);
	kfree (max_sectors[major]);

	blk_size[major]      = NULL;
	blksize_size[major]  = NULL;
	hardsect_size[major] = NULL;
	max_sectors[major]   = NULL;

	rc = devfs_unregister_blkdev (major, DASD_NAME);
	if (rc < 0) {

		MESSAGE (KERN_WARNING,
                         "Cannot unregister from major no %d, rc = %d",
                         major,
                         rc);

		return rc;
	} else {
		major_info->flags &= ~DASD_MAJOR_INFO_REGISTERED;
	}

	kfree (major_info->gendisk.label_arr);
	kfree (major_info->gendisk.flags);
	kfree (major_info->gendisk.de_arr);

	/* Delete the new major info from dasd_major_info if needed */
	if (!(major_info->flags & DASD_MAJOR_INFO_IS_STATIC)) {
		spin_lock_irqsave (&dasd_major_lock, flags);
		list_del (&major_info->list);
		spin_unlock_irqrestore (&dasd_major_lock, flags);
		kfree (major_info);
	}
	return rc;
}

/*
 * function: dasd_device_from_kdev
 * finds the device structure corresponding to the kdev supplied as argument
 * in the major_info structures and returns it or NULL when not found
 */
dasd_device_t *
dasd_device_from_kdev (kdev_t kdev)
{
	major_info_t *major_info;
        dasd_device_t *device;
	struct list_head *l;
	unsigned long flags;

        device = NULL;
	spin_lock_irqsave (&dasd_major_lock, flags);
	list_for_each (l, &dasd_major_info) {
		major_info = list_entry (l, major_info_t, list);
		if (major_info->gendisk.major == MAJOR (kdev)) {
                        device = major_info->dasd_device[MINOR (kdev) >>
                                                         DASD_PARTN_BITS];
			break;
                }
	}
	spin_unlock_irqrestore (&dasd_major_lock, flags);
        return device;
}

/*
 * function: dasd_device_from_devno
 * finds the address of the device structure corresponding to the devno
 * supplied as argument in the major_info structures and returns
 * it or NULL when not found
 */
static inline dasd_device_t **
dasd_device_from_devno (int devno)
{
	major_info_t *major_info;
        dasd_device_t **device;
	struct list_head *l;
	int devindex;
	unsigned long flags;

	spin_lock_irqsave (&dasd_major_lock, flags);
        devindex = dasd_devindex_from_devno (devno);
        if (devindex < 0) {
                spin_unlock_irqrestore (&dasd_major_lock, flags);
                return NULL;
        }

        device = NULL;
	list_for_each (l, &dasd_major_info) {
		major_info = list_entry (l, major_info_t, list);
		if (devindex < DASD_PER_MAJOR) {
                        device = &major_info->dasd_device[devindex];
                        break;
		}
		devindex -= DASD_PER_MAJOR;
	}
	spin_unlock_irqrestore (&dasd_major_lock, flags);
	return device;
}

/*
 * function: dasd_features_from_devno
 * finds the device range corresponding to the devno
 * supplied as argument in the major_info structures and returns
 * the features set for it
 */

static int
dasd_features_from_devno (int devno)
{
	dasd_range_t *temp;
	int devindex = 0;
	unsigned long flags;
	struct list_head *l;

	spin_lock_irqsave (&range_lock, flags);
	list_for_each (l, &dasd_range_head.list) {
		temp = list_entry (l, dasd_range_t, list);
		if (devno >= temp->from && devno <= temp->to) {
			spin_unlock_irqrestore (&range_lock, flags);
			return temp->features;
		}
		devindex += temp->to - temp->from + 1;
	}
	spin_unlock_irqrestore (&range_lock, flags);
	return -ENODEV;
}

/*
 * function: dasd_check_bp_block
 * checks the blocksize and returns 0 if valid.
 */

static int
dasd_check_bp_block (dasd_device_t *device)
{
        int rc;

        switch (device->sizes.bp_block) {
        case 512:
        case 1024:
        case 2048:
        case 4096:
                rc = 0;
                break;
        default:
                rc = -EMEDIUMTYPE;
        }
        
        return rc;
}

/********************************************************************************
 * SECTION: managing dasd disciplines 
 ********************************************************************************/

/* anchor and spinlock for list of disciplines */
static struct list_head dasd_disc_head = LIST_HEAD_INIT(dasd_disc_head);
static spinlock_t discipline_lock = SPIN_LOCK_UNLOCKED;

/*
 * function dasd_discipline_enq
 * chains the discpline given as argument to the tail of disiplines.
 * Exception: DIAG is always queued to the head, to ensure that CMS RESERVED
 * minidisks are invariably accessed using DIAG.
 */
static inline void
dasd_discipline_enq (dasd_discipline_t *discipline)
{
        if (strncmp (discipline->name, "DIAG", 4) == 0) {
                list_add (&discipline->list, &dasd_disc_head);
        } else {
                list_add_tail (&discipline->list, &dasd_disc_head);
        }
}

/*
 * function dasd_discipline_deq
 * removes the discipline given as argument from the list of disciplines
 */
static inline void
dasd_discipline_deq (dasd_discipline_t * discipline)
{
        if (&discipline->list) {
                list_del (&discipline->list);
        }
}

void
dasd_discipline_add (dasd_discipline_t * discipline)
{
        unsigned long flags;
        MOD_INC_USE_COUNT;
	spin_lock_irqsave (&discipline_lock,flags);
        dasd_discipline_enq (discipline);
	spin_unlock_irqrestore (&discipline_lock,flags);

        dasd_enable_ranges (&dasd_range_head, 
                            discipline, 
                            DASD_STATE_ONLINE);
}

void dasd_discipline_del (dasd_discipline_t * discipline)
{
        unsigned long flags;

        dasd_disable_ranges(&dasd_range_head,
                            discipline, 
                            DASD_STATE_DEL, 
                            1);

	spin_lock_irqsave (&discipline_lock,flags);
        dasd_discipline_deq (discipline);
	spin_unlock_irqrestore (&discipline_lock,flags);
        MOD_DEC_USE_COUNT;
}

/*
 * function dasd_find_disc
 * checks the list of disciplines for the first one able to access the device
 */
static inline dasd_discipline_t *
dasd_find_disc (dasd_device_t * device, dasd_discipline_t *discipline)
{
        dasd_discipline_t *t;
        struct list_head *l = discipline ? 
                              &discipline->list : dasd_disc_head.next;

        do {
                t = list_entry(l,dasd_discipline_t,list);

                if ( ( t->id_check == NULL ||
                       t->id_check (&device->devinfo) == 0 ) &&
                     ( t->check_characteristics == NULL ||
                       t->check_characteristics (device) == 0 ) )
                        break;
                l = l->next;
                if ( discipline || 
                     l == &dasd_disc_head ) {
                        t = NULL;
                        break;
                }
         } while ( 1 );

	return t;
}

/********************************************************************************
 * SECTION: profiling stuff 
 ********************************************************************************/

#ifdef CONFIG_PROC_FS

static dasd_profile_info_t dasd_global_profile;
#endif  /* CONFIG_PROC_FS */

#ifdef DASD_PROFILE

#define DASD_PROFILE_ON  1
#define DASD_PROFILE_OFF 0

static unsigned int dasd_profile_level = DASD_PROFILE_OFF;

/*
 * macro: dasd_profile_add_counter
 * increments counter in global and local profiling structures
 * according to the value
 */
#define dasd_profile_add_counter( value, counter, device ) \
{ \
        int ind; \
        long help; \
	for (ind = 0, help = value >> 2; \
             ind < 31 && help; \
             help = help >> 1, ind++) {} \
	dasd_global_profile.counter[ind]++; \
        device->profile.counter[ind]++; \
}

/*
 * function dasd_profile_add
 * adds the profiling information from the cqr given as argument to the
 * global and device specific profiling information
 */
void
dasd_profile_add (ccw_req_t * cqr)
{
	long strtime, irqtime, endtime, tottime; /* in microsecnds*/
	long tottimeps, sectors;
	dasd_device_t *device = cqr->device;

	if (!cqr->req)		/* safeguard against abnormal cqrs */
		return;

        if ((!cqr->buildclk) ||
            (!cqr->startclk) ||
            (!cqr->stopclk ) ||
            (!cqr->endclk  ) ||
            (!(sectors = ((struct request *) (cqr->req))->nr_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 (dasd_profile_info_t));
	};
	if (!device->profile.dasd_io_reqs) {
		memset (&device->profile, 0, sizeof (dasd_profile_info_t));
	};

	dasd_global_profile.dasd_io_reqs++;
	device->profile.dasd_io_reqs++;
	dasd_global_profile.dasd_io_sects+=sectors;
	device->profile.dasd_io_sects+=sectors;
	dasd_profile_add_counter (sectors, dasd_io_secs, device);
	dasd_profile_add_counter (tottime, dasd_io_times, device);
	dasd_profile_add_counter (tottimeps, dasd_io_timps, device);
	dasd_profile_add_counter (strtime, dasd_io_time1, device);
	dasd_profile_add_counter (irqtime, dasd_io_time2, device);
	dasd_profile_add_counter (irqtime / sectors, dasd_io_time2ps, device);
	dasd_profile_add_counter (endtime, dasd_io_time3, device);
}
#endif /* DASD_PROFILE */

/********************************************************************************
 * SECTION: All the gendisk stuff 
 ********************************************************************************/


/********************************************************************************
 * SECTION: Managing wrappers for ccwcache 
 ********************************************************************************/

/*
 * function dasd_alloc_request
 * tries to return space for a channel program of length cplength with
 * additional data of size datasize.
 * If the ccwcache cannot fulfill the request it tries the lowmem requests
 * before giving up finally.
 * FIXME: initialization of ccw_req_t should be done by function of ccwcache
 */
ccw_req_t *
dasd_alloc_request (char *magic, int cplength, int datasize, 
                    dasd_device_t *device)
{
	ccw_req_t *cqr;
        unsigned long size_needed;
	unsigned long data_offset, ccw_offset;
        dasd_lowmem_t *lowmem;
 
        if ((cqr = ccw_alloc_request (magic, cplength, datasize)) != NULL) {
                return cqr;
        }

	/* Sanity checks */
	if (magic == NULL || datasize > PAGE_SIZE ||
            cplength == 0 || (cplength * sizeof(ccw1_t)) > PAGE_SIZE)
		BUG();

        /* use lowmem page only for ERP or */
        /* if there are less than 2 requests on queue */
        if (device->queue.head != NULL &&
            device->queue.head->next != NULL &&
            device->queue.head->status != CQR_STATUS_ERROR) {
                return NULL;
        }

	/* We try to keep things together in memory */
	size_needed = (sizeof (ccw_req_t) + 7) & (~7L);
	data_offset = ccw_offset = 0;
	if (size_needed + datasize <= PAGE_SIZE) {
		/* Keep data with the request */
		data_offset = size_needed;
		size_needed += (datasize + 7) & (~7L);
	}
	if (size_needed + cplength*sizeof(ccw1_t) <= PAGE_SIZE) {
		/* Keep CCWs with request */
		ccw_offset = size_needed;
		size_needed += (cplength*sizeof(ccw1_t)) & (~7L);
	}
        
        /* take page from lowmem_pool for request */
        list_for_each_entry (lowmem, &device->lowmem_pool, list) {
                list_del (&lowmem->list);
                cqr = (ccw_req_t *) lowmem;
                memset (cqr, 0, PAGE_SIZE);
                cqr->flags |= CQR_FLAGS_LM_CQR;
                break;
        }
	if (cqr == NULL)
		return NULL;

	/* take page from lowmem_pool for the extra data */
	if (data_offset == 0) {

                list_for_each_entry (lowmem, &device->lowmem_pool, list) {
                        list_del (&lowmem->list);
                        cqr->data = (void *) lowmem;
                        memset (cqr->data, 0, PAGE_SIZE);
                        break;
                }
		if (cqr->data == NULL) {
			printk(KERN_DEBUG PRINTK_HEADER 
			       "Couldn't allocate data area\n");
                        
                        lowmem = (dasd_lowmem_t *) cqr;
                        list_add (&lowmem->list, &device->lowmem_pool);
			return NULL;
		}
	} else {
		/* Extra data already allocated with the request */
		cqr->data = (void *) ((addr_t) cqr + data_offset);
        }

	/* take page from lowmem_pool for the channel program */
	if (ccw_offset == 0) {

                list_for_each_entry (lowmem, &device->lowmem_pool, list) {
                        list_del (&lowmem->list);
                        cqr->cpaddr = (ccw1_t *) lowmem;
                        memset (cqr->cpaddr, 0, PAGE_SIZE);
                        break;
                }

		if (cqr->cpaddr == NULL) {
			printk (KERN_DEBUG PRINTK_HEADER
				"Couldn't allocate channel program area\n");
			if (data_offset == 0) {
                                lowmem = (dasd_lowmem_t *) cqr->data;
                                list_add (&lowmem->list, &device->lowmem_pool);
                        }
                        lowmem = (dasd_lowmem_t *) cqr;
                        list_add (&lowmem->list, &device->lowmem_pool);
			return NULL;
		}
	} else {
		/* Channel program already allocated with the request */
		cqr->cpaddr = (ccw1_t *) ((addr_t) cqr + ccw_offset);
        }

        /* use the remaining memory of the cqr page for IDALs */
        cqr->lowmem_idal_ptr = (void *) ((addr_t) cqr + size_needed);

	strncpy ((char *)(&cqr->magic), magic, 4);

	ASCEBC((char *)(&cqr->magic), 4);
	cqr->cplength = cplength;
	cqr->datasize = datasize;

	return cqr;
}

/*
 * function dasd_free_request
 * returns a ccw_req_t to the appropriate cache or emergeny request line
 */
void
dasd_free_request (ccw_req_t *cqr, dasd_device_t* device)
{
        unsigned long size_needed;
        dasd_lowmem_t *lowmem;

#ifdef CONFIG_ARCH_S390X
        ccw1_t* ccw;
        /* clear any idals used for chain (might be in lowmen cqr page, */
        /* in seperate lowmen page or kmalloced */
        ccw=cqr->cpaddr-1;
        do {
                ccw++;
                if ((cqr->flags & CQR_FLAGS_LM_CQR) &&
                    (ccw->cda >= (unsigned long) cqr) &&
                    (ccw->cda < (unsigned long) cqr + PAGE_SIZE)) {
                    /* IDAL is on the car lowmem page */
                        continue;
                }

                if ((cqr->flags & CQR_FLAGS_LM_IDAL) &&
                    (ccw->cda >= (unsigned long) cqr->lowmem_idal) &&
                    (ccw->cda < (unsigned long) cqr->lowmem_idal + PAGE_SIZE)) {
                        /* IDAL is on seperate lowmem page */
                        continue;
                }

                /* IDAL was build by set_normalized_cda */
                clear_normalized_cda (ccw);

        } while ((ccw->flags & CCW_FLAG_CC) || 
                 (ccw->flags & CCW_FLAG_DC)   );
#endif
        /* give idal lowmem page back to lowmem_pool */
        if (cqr->flags & CQR_FLAGS_LM_IDAL) {
                lowmem = (dasd_lowmem_t *) cqr->lowmem_idal;
                list_add (&lowmem->list, &device->lowmem_pool);
                cqr->flags &= ~CQR_FLAGS_LM_IDAL;
        }
                
        /* give cqr lowmem pages back to lowmem_pool */
        if (cqr->flags & CQR_FLAGS_LM_CQR) { 
                
                /* make the same decisions as in dasd_alloc_request */
                size_needed = (sizeof (ccw_req_t) + 7) & (~7L);
                if (size_needed + cqr->datasize <= PAGE_SIZE) {
                        /* We kept the data with the request */
                        size_needed += (cqr->datasize + 7) & (~7L);
                } else {
                        lowmem = (dasd_lowmem_t *) cqr->data;
                        list_add (&lowmem->list, &device->lowmem_pool);
                }
                
                if (size_needed + cqr->cplength * sizeof(ccw1_t) > PAGE_SIZE) {
                        /* We didn't keep the CCWs with request */
                        lowmem = (dasd_lowmem_t *) cqr->cpaddr;
                        list_add (&lowmem->list, &device->lowmem_pool);
                }
                lowmem = (dasd_lowmem_t *) cqr;
                list_add (&lowmem->list, &device->lowmem_pool);
	} else {
                ccw_free_request (cqr);
        }
}

/*
 * function dasd_set_normalized_cda
 * calls set_normalized_cda to build IDALs.
 * If this did not work because of low memory, we try to use memory from the 
 * lowmem pool.
 */
int
dasd_set_normalized_cda (ccw1_t *cp, unsigned long address, 
                         ccw_req_t *cqr, dasd_device_t *device)
{
#ifdef CONFIG_ARCH_S390X
        int rc;
	int nridaws;
        dasd_lowmem_t *lowmem;
        int count = cp->count;
        
        /* use lowmem idal page if already assinged */
        if (!(cqr->flags & CQR_FLAGS_LM_IDAL)) {
                rc = set_normalized_cda (cp, (void *)address);
                if (rc !=-ENOMEM) {
                        return rc;
                }
        }
        
        /* get number of idal words needed */
        nridaws = ((address & (IDA_BLOCK_SIZE-1)) + count + 
		   (IDA_BLOCK_SIZE-1)) >> IDA_SIZE_LOG;

        /* check if we need an additional IDALs page */
	if (!(cqr->flags & CQR_FLAGS_LM_IDAL)) {
                /* we got no lowmem cqr page OR */
                /* there is no space left for IDALs */
                if ((!(cqr->flags & CQR_FLAGS_LM_CQR)) ||
                    ((cqr->lowmem_idal_ptr + nridaws * sizeof(unsigned long)) > 
                     ((void *) cqr + PAGE_SIZE))) {
                        
                        /* use lowmem page only for ERP or */
                        /* if there are less than 2 requests on queue */
                        if (device->queue.head != NULL &&
                            device->queue.head->next != NULL &&
                            device->queue.head->status != CQR_STATUS_ERROR) {
                                return -ENOMEM;
                        }
                       
                        list_for_each_entry (lowmem, &device->lowmem_pool, 
                                             list) {
                                list_del (&lowmem->list);
                                cqr->lowmem_idal = (void *)lowmem;
                                cqr->lowmem_idal_ptr  = (void *) lowmem;
                                memset (cqr->lowmem_idal, 0, PAGE_SIZE);
                                cqr->flags |= CQR_FLAGS_LM_IDAL;
                                break;
                        }
                }
                
        }
        
        /* now we (should) have an valid lowmem_idal_ptr and enough space for */
        /* the IDALs - fill the idals table */
	cp->flags |= CCW_FLAG_IDA;
	cp->cda = (__u32)(unsigned long)cqr->lowmem_idal_ptr;
        do {
		*((long*)cqr->lowmem_idal_ptr) = address;
		address  = (address & -(IDA_BLOCK_SIZE)) + (IDA_BLOCK_SIZE);
                cqr->lowmem_idal_ptr += sizeof(unsigned long);
		nridaws --;
        } while ( nridaws > 0 );
#else 
        cp->cda = address;
#endif
	return 0;
}


/********************************************************************************
 * SECTION: (de)queueing of requests to channel program queues 
 ********************************************************************************/

/*
 * function dasd_chanq_enq
 * appends the cqr given as argument to the queue
 * has to be called with the queue lock (namely the s390_irq_lock) acquired
 */
inline void
dasd_chanq_enq (dasd_chanq_t * q, ccw_req_t * cqr)
{
	if (q->head != NULL) {
		q->tail->next = cqr;
	} else
		q->head = cqr;
	cqr->next = NULL;
	q->tail = cqr;
	check_then_set (&cqr->status, 
                        CQR_STATUS_FILLED, 
                        CQR_STATUS_QUEUED);

       
#ifdef DASD_PROFILE
        if (dasd_profile_level == DASD_PROFILE_ON) {

                /* save profile information for non erp cqr */
                if (cqr->refers == NULL) {
                        unsigned int  counter = 0;
                        ccw_req_t     *ptr;
                        dasd_device_t *device = cqr->device;
                        
                        /* count the length of the chanq for statistics */
                        for (ptr = q->head; 
                             ptr->next != NULL && counter <=31; 
                             ptr = ptr->next) {
                                counter++;
                        }                
                        
                        dasd_global_profile.dasd_io_nr_req[counter]++;
                        device->profile.dasd_io_nr_req[counter]++;
                }

        } /* end if DASD_PROFILE_ON */
#endif 
}

/*
 * function dasd_chanq_enq_head
 * chains the cqr given as argument to the queue head
 * has to be called with the queue lock (namely the s390_irq_lock) acquired
 */
inline void
dasd_chanq_enq_head (dasd_chanq_t * q, ccw_req_t * cqr)
{
	cqr->next = q->head;
	q->head = cqr;
	if (q->tail == NULL)
		q->tail = cqr;

	check_then_set (&cqr->status, 
                        CQR_STATUS_FILLED, 
                        CQR_STATUS_QUEUED);
}

/*
 * function dasd_chanq_deq
 * dechains the cqr given as argument from the queue
 * has to be called with the queue lock (namely the s390_irq_lock) acquired
 */
inline void
dasd_chanq_deq (dasd_chanq_t * q, ccw_req_t * cqr)
{
	ccw_req_t *prev;

	if (cqr == NULL)
		BUG ();

	if (cqr == q->head) {
		q->head = cqr->next;
		if (q->head == NULL)
			q->tail = NULL;

	} else {
		prev = q->head;
		while (prev && prev->next != cqr)
			prev = prev->next;
		if (prev == NULL)
			return; /* request not in chanq */
		prev->next = cqr->next;
		if (prev->next == NULL)
			q->tail = prev;
	}
	cqr->next = NULL;
}

/********************************************************************************
 * SECTION: Managing the device queues etc. 
 ********************************************************************************/

/*
 * DASD_RESREL_TIMEOUT
 *
 * A timer is used to suspend the current reserve/release request 
 * if it doesn't return within a certain time.
 */
void
dasd_resrel_timeout (unsigned long cqr_ptr)
{
        dasd_device_t *device = ((ccw_req_t *) cqr_ptr)->device;
        ccw_req_t     *cqr;
	unsigned long flags;

	s390irq_spin_lock_irqsave (device->devinfo.irq, 
                                   flags);
        cqr = device->queue.head;

        switch (cqr->status) {
        case CQR_STATUS_FILLED:
        case CQR_STATUS_QUEUED:
                /* request was not started - just set to failed */
                cqr->status = CQR_STATUS_FAILED;
                break;

        case CQR_STATUS_IN_IO:
        case CQR_STATUS_ERROR:        
                if (device->discipline->term_IO (cqr) != 0);
                        cqr->status = CQR_STATUS_FAILED;
                break;

        default:
                ; /* DONE and FAILED are ok */
        }

        dasd_schedule_bh (device);

        s390irq_spin_unlock_irqrestore (device->devinfo.irq, 
                                        flags);

} /* end dasd_resrel_timeout */

/*
 * Call unconditional reserve to break the reserve of an other system.
 * Timeout the request if it doesn't succseed within a certain time.
 */
static int 
dasd_steal_lock (dasd_device_t *device)
{
        ccw_req_t *cqr;
        int rc = 0;

        if (!device->discipline->steal_lock) 
                rc = -EINVAL;
        
        cqr = device->discipline->steal_lock (device);
        
        if (cqr) {
                struct timer_list res_timer;
                
                init_timer(&res_timer);
                res_timer.function = dasd_resrel_timeout; 
                res_timer.data     = (unsigned long) cqr;
                res_timer.expires  = jiffies + 4 * HZ; 
                add_timer(&res_timer);
                
                rc = dasd_sleep_on_immediate (cqr);
                
                del_timer_sync(&res_timer); 
                dasd_free_request (cqr, 
                                   device);
        } else {
                rc = -ENOMEM;
        }

        return rc;

} /* end dasd_steal_lock */

/*
 * DASD_TERM_IO
 *
 * Attempts to terminate the current IO and set it to failed if termination
 * was successful.
 * Returns an appropriate return code.
 */
int
dasd_term_IO (ccw_req_t * cqr)
{
	int rc = 0;
	dasd_device_t *device = cqr->device;
	int irq;
        int retries = 0;

	if (!cqr) {
		BUG ();
	}
	irq = device->devinfo.irq;
	if (strncmp ((char *) &cqr->magic, 
                     device->discipline->ebcname, 4)) {

		DEV_MESSAGE (KERN_WARNING, device,
                             " ccw_req_t 0x%08x magic doesn't match"
                             " discipline 0x%08x",
                             cqr->magic,
                             *(unsigned int *) device->discipline->name);

		return -EINVAL;
	}
        
        while ((retries < 5                    ) &&
               (cqr->status == CQR_STATUS_IN_IO)   ) {

                rc = clear_IO (irq, 
                               (long)cqr, 
                               cqr->options);
                
                switch (rc) {
                case 0:         /* termination successful */
                        check_then_set (&cqr->status,
                                        CQR_STATUS_IN_IO, 
                                        CQR_STATUS_FAILED);
                        
                        cqr->stopclk = get_clock ();

                        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 -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;
                }

                dasd_schedule_bh (device);
                retries ++;
        }
	return rc;
}

/*
 * function dasd_start_IO
 * attempts to start the IO and returns an appropriate return code
 */
int
dasd_start_IO (ccw_req_t * cqr)
{
	int rc = 0;
	dasd_device_t *device = cqr->device;
	int irq;
	unsigned long long now;

	if (!cqr) {
		BUG ();
	}
	irq = device->devinfo.irq;
	if (strncmp ((char *) &cqr->magic, 
                     device->discipline->ebcname, 4)) {

		DEV_MESSAGE (KERN_ERR, device,
                             " ccw_req_t 0x%08x magic doesn't match"
                             " discipline 0x%08x",
                             cqr->magic,
                             *(unsigned int *) device->discipline->name);

		return -EINVAL;
	}

	now = get_clock ();

        cqr->startclk = now;
        if (!device->stopped)
                rc = do_IO (irq, cqr->cpaddr, (long) cqr, cqr->lpm, cqr->options);
        else
                rc = -EBUSY;

	switch (rc) {
	case 0:
                if (cqr->options & DOIO_WAIT_FOR_INTERRUPT) {
                        /* request already finished (synchronous IO) */
                        check_then_set (&cqr->status,
                                        CQR_STATUS_QUEUED, 
                                        CQR_STATUS_DONE);

                        cqr->stopclk = now;
                        dasd_schedule_bh (device);
                        
                } else {
                        check_then_set (&cqr->status,
                                        CQR_STATUS_QUEUED, 
                                        CQR_STATUS_IN_IO);
                }
		break;
	case -EBUSY:
		DBF_DEV_EVENT (DBF_ERR, device, "%s",
                               "device busy, retry later");

                if (!timer_pending(&device->timer)) {
                        init_timer (&device->timer);
                        device->timer.function = dasd_schedule_bh_timed; 
                        device->timer.data     = (unsigned long) device;
                        device->timer.expires  = jiffies + (HZ >> 4);
                        add_timer (&device->timer);
                } else {
                        mod_timer(&device->timer, jiffies + (HZ >> 4));
                }
		break;
	case -ETIMEDOUT: 
		DBF_DEV_EVENT (DBF_ERR, device, "%s",
                               "request timeout - terminated");
	case -ENODEV:
	case -EIO:
		check_then_set (&cqr->status,
				CQR_STATUS_QUEUED, 
                                CQR_STATUS_FAILED);

                cqr->stopclk = now;
                dasd_schedule_bh (device);
		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;
}

/*
 * function dasd_sleep_on_req
 * attempts to start the IO and waits for completion
 */
int
dasd_sleep_on_req (ccw_req_t * cqr)
{
	unsigned long flags;
	dasd_device_t *device = (dasd_device_t *) cqr->device;

        if (signal_pending(current)) {
                return -ERESTARTSYS;
        }
	s390irq_spin_lock_irqsave (device->devinfo.irq, 
                                   flags);
        
	dasd_chanq_enq (&device->queue, 
                        cqr);

	/* let the bh start the request to keep them in order */
	dasd_schedule_bh (device);

        s390irq_spin_unlock_irqrestore (device->devinfo.irq, 
                                        flags);

        wait_event (device->wait_q,
                    cqr->flags & CQR_FLAGS_FINALIZED); 

        if (cqr->status == CQR_STATUS_FAILED) {
                return -EIO;
        }

	return 0;

} /* end dasd_sleep_on_req */

/*
 * function dasd_sleep_on_immediate
 * same as dasd_sleep_on_req, but attempts to start the IO immediately 
 * (killing the actual running IO). 
 */
static int
dasd_sleep_on_immediate (ccw_req_t *cqr)
{
	unsigned long flags;
	dasd_device_t *device = (dasd_device_t *) cqr->device;

        if (signal_pending(current))
                return -ERESTARTSYS;

	s390irq_spin_lock_irqsave (device->devinfo.irq, 
                                   flags);
        
        /* terminate currently running IO */
        if (device->queue.head->status == CQR_STATUS_IN_IO) {

                device->discipline->term_IO (device->queue.head);

                device->queue.head->status = CQR_STATUS_QUEUED;
        }

	dasd_chanq_enq_head (&device->queue, 
                             cqr);

	/* let the bh start the request to keep them in order */
	dasd_schedule_bh (device);

        s390irq_spin_unlock_irqrestore (device->devinfo.irq, 
                                        flags);
        
        wait_event (device->wait_q,
                    cqr->flags & CQR_FLAGS_FINALIZED); 
        
        if (cqr->status == CQR_STATUS_FAILED) {
                return -EIO;
        }

	return 0;

} /* end dasd_sleep_on_immediate */

/*
 * function dasd_end_request
 * posts the buffer_cache about a finalized request
 * FIXME: for requests splitted to serveral cqrs
 */
static inline void
dasd_end_request (struct request *req, int uptodate)
{
	while (end_that_request_first (req, uptodate, DASD_NAME)) {
	}
#ifndef DEVICE_NO_RANDOM
	add_blkdev_randomness (MAJOR (req->rq_dev));
#endif
	end_that_request_last (req);
	return;
}

/*
 * function dasd_get_queue
 * returns the queue corresponding to a device behind a kdev
 */
static request_queue_t *
dasd_get_queue (kdev_t kdev)
{
	dasd_device_t *device = dasd_device_from_kdev (kdev);

	if (!device) {
		return NULL;
	}

	return device->request_queue;
}

/*
 * function dasd_check_expire_time
 * check the request given as argument for expiration
 * and returns 0 if not yet expired, EIO else
 */
static inline int
dasd_check_expire_time (ccw_req_t * cqr)
{
	unsigned long long now;
	int rc = 0;

	now = get_clock ();

	if (cqr->expires && 
            cqr->expires + cqr->startclk < now) {

		DBF_DEV_EVENT (DBF_WARNING, ((dasd_device_t *) cqr->device),
                               "IO timeout 0x%08lx%08lx usecs in req %p",
                               (long) (cqr->expires >> 44),
                               (long) (cqr->expires >> 12), 
                               cqr);

		cqr->expires <<= 1;
                rc = -EIO;
	}
	return rc;
}

/*
 * function dasd_finalize_request
 * implemets the actions to perform, when a request is finally finished
 * namely in status CQR_STATUS_DONE || CQR_STATUS_FAILED
 */
static inline void
dasd_finalize_request (ccw_req_t * cqr)
{
	dasd_device_t *device = cqr->device;

	cqr->endclk = get_clock ();

	if (cqr->req) {

#ifdef DASD_PROFILE
                if (dasd_profile_level == DASD_PROFILE_ON) {
                        dasd_profile_add (cqr);
                }
#endif

		dasd_end_request (cqr->req, (cqr->status == CQR_STATUS_DONE));
		/* free request if nobody is waiting on it */
		dasd_free_request (cqr, cqr->device);
	} else {
                if (cqr == device->init_cqr && /* bring late devices online */
                    device->level <= DASD_STATE_ONLINE ) { 
                        if (!timer_pending(&device->late_timer)) {
                                init_timer(&device->late_timer);
                                device->late_timer.function = dasd_enable_single_device; 
                                device->late_timer.data     = (unsigned long) device;
                                device->late_timer.expires  = jiffies;
                                add_timer(&device->late_timer);
                        } else {
                                mod_timer(&device->late_timer, jiffies);
                        }
                } else {
                        /* notify sleep_on_xxx about finished cqr */
                        cqr->flags |= CQR_FLAGS_FINALIZED;
                }

		/* notify sleeping task about finished postprocessing */
		wake_up (&device->wait_q);
                
	}
	return;
}

/*
 * function dasd_process_queues
 * transfers the requests on the queue given as argument to the chanq
 * if possible, the request ist started on a fastpath
 */
static void
dasd_process_queues (dasd_device_t * device)
{
	unsigned long flags;
	struct request *req;
	request_queue_t *queue = device->request_queue;
	dasd_chanq_t *qp = &device->queue;
	int irq = device->devinfo.irq;
	ccw_req_t *final_requests = NULL;
	int chanq_max_size = DASD_CHANQ_MAX_SIZE;
	ccw_req_t *cqr = NULL, *temp;
	dasd_erp_postaction_fn_t erp_postaction;

	s390irq_spin_lock_irqsave (irq, flags);

	/* First we dechain the requests, processed with completed status */
	while (qp->head &&
	       ((qp->head->status == CQR_STATUS_DONE  ) ||
		(qp->head->status == CQR_STATUS_FAILED) ||
		(qp->head->status == CQR_STATUS_ERROR )   )) {

		dasd_erp_action_fn_t erp_action;
		ccw_req_t            *erp_cqr = NULL;

		/*  preprocess requests with CQR_STATUS_ERROR */
		if (qp->head->status == CQR_STATUS_ERROR) {

                        qp->head->retries--; 

			if ((qp->head->dstat == NULL                                         ) ||
			    ((qp->head->dstat->flag & DEVSTAT_FLAG_SENSE_AVAIL) == 0         ) ||
			    (device->discipline->erp_action == NULL                          ) ||
			    ((erp_action = device->discipline->erp_action (qp->head)) == NULL)   ) {
                                
				erp_cqr = dasd_default_erp_action (qp->head);

			} else { /* call discipline ERP action */

                                erp_cqr = erp_action (qp->head);
                        }
                        continue;

		} else if (qp->head->refers) {	/* we deal with a finished ERP */

			if (qp->head->status == CQR_STATUS_DONE) {

                                DBF_DEV_EVENT (DBF_NOTICE, device, "%s",
                                               "ERP successful");
			} else {

                                DEV_MESSAGE (KERN_WARNING, device, "%s",
                                             "ERP unsuccessful");
			}

			if ((device->discipline->erp_postaction == NULL                              )||
			    ((erp_postaction = device->discipline->erp_postaction (qp->head)) == NULL)  ) {

                                dasd_default_erp_postaction (qp->head);

			} else {  /* call ERP postaction of discipline */

                                erp_postaction (qp->head);
                        }

			continue;
		}

		/* dechain request now */
		if (final_requests == NULL)
			final_requests = qp->head;

		cqr      = qp->head;
		qp->head = qp->head->next;

		if (qp->head == NULL)
			qp->tail = NULL;

	} /* end while over completed requests */

	if (cqr)
		cqr->next = NULL;         /* terminate final_requests queue */

	/* Now clean the requests with final status */
	while (final_requests) { 
		temp = final_requests;
		final_requests = temp->next;
		dasd_finalize_request (temp);
	}

	/* Now we try to fetch requests from the request queue */
	for (temp = qp->head; temp != NULL; temp = temp->next) {
		if (temp->status == CQR_STATUS_QUEUED)
			chanq_max_size--;
        }

	while ((atomic_read(&device->plugged) == 0) &&
               (queue) &&
               (!queue->plugged) &&
	       (!list_empty (&queue->queue_head)) &&
	       (req = dasd_next_request (queue)) &&
               (qp->head == NULL || chanq_max_size > 0)) {
		/* queue empty or certain critera fulfilled -> transfer */
                cqr = NULL;
                if (is_read_only(device->kdev) && req->cmd == WRITE) {

                        DBF_EVENT (DBF_ERR,
                                   "(%04x) Rejecting write request %p",
                                   device->devinfo.devno,
                                   req);
                        dasd_dequeue_request (queue,req);
                        dasd_end_request (req, 0);
                        continue;
                } 
                cqr = device->discipline->build_cp_from_req (device, req);
                        
                if (cqr == NULL || IS_ERR(cqr)) {
                        if (cqr == ERR_PTR(-ENOMEM)) {
                                break;
                        }

                        MESSAGE (KERN_EMERG,
                                 "(%04x) CCW creation failed "
                                 "on request %p",
                                 device->devinfo.devno, req);
                        dasd_dequeue_request (queue,req);
                        dasd_end_request (req, 0);
                        continue;
                }
                dasd_dequeue_request (queue, req);
                dasd_chanq_enq (qp, cqr);
                chanq_max_size--;

	}

	/* we process the requests with non-final status */
	if (qp->head) {
		switch (qp->head->status) {
		case CQR_STATUS_QUEUED:
			/* try to start the first I/O that can be started */
			if (device->discipline->start_IO == NULL)
				BUG ();
                        device->discipline->start_IO(qp->head);
			break;
		case CQR_STATUS_IN_IO:
			/* Check, if to invoke the missing interrupt handler */
			if (dasd_check_expire_time (qp->head)) {
				/* to be filled with MIH */
			}
			break;
		default:
                        MESSAGE (KERN_EMERG,
                                 "invalid cqr (%p) detected with status %02x ",
                                 qp->head,
                                 qp->head->status);
			BUG ();
		}
	}
	s390irq_spin_unlock_irqrestore (irq, flags);

} /* end dasd_process_queues */

/*
 * function dasd_run_bh
 * acquires the locks needed and then runs the bh
 */
static void
dasd_run_bh (dasd_device_t * device)
{
	long flags;
	spin_lock_irqsave (&io_request_lock, flags);
	atomic_set (&device->bh_scheduled, 0);
	dasd_process_queues (device);
	spin_unlock_irqrestore (&io_request_lock, flags);
}

/*
 * function dasd_schedule_bh_timed
 * retriggers the dasd_schedule_bh function (called by timer queue)
 */
void 
dasd_schedule_bh_timed (unsigned long device_ptr)
{
        dasd_device_t *device = (dasd_device_t *) device_ptr;

        dasd_schedule_bh (device);
}

/*
 * function dasd_schedule_bh
 * schedules the request_fn to run with next run_bh cycle
 */
void
dasd_schedule_bh (dasd_device_t * device)
{
	/* Protect against rescheduling, when already running */
	if (atomic_compare_and_swap (0, 1, &device->bh_scheduled)) {
		return;
	}

	INIT_LIST_HEAD (&device->bh_tq.list);
	device->bh_tq.sync = 0;
	device->bh_tq.routine = (void *) (void *) dasd_run_bh;
	device->bh_tq.data = device;

	queue_task (&device->bh_tq, &tq_immediate);
	mark_bh (IMMEDIATE_BH);
	return;
}

/*
 * function do_dasd_request
 * is called from ll_rw_blk.c and provides the caller of
 * dasd_process_queues
 */
static void
do_dasd_request (request_queue_t * queue)
{
        dasd_device_t *device = (dasd_device_t *)queue->queuedata;
	dasd_process_queues (device);
}

/*
 * function dasd_handle_state_change_pending
 *
 * handles the state change pending interrupt.
 */
void
dasd_handle_state_change_pending (devstat_t * stat)
{

	dasd_device_t **device_addr, *device;
	ccw_req_t *cqr;

	device_addr = dasd_device_from_devno (stat->devno);

	if (!device_addr) 
                return;

        device = *device_addr;
	if (!device) 
                return;
        
        /* restart all 'running' IO on queue */
        cqr = device->queue.head;
        while (cqr) {
                if (cqr->status == CQR_STATUS_IN_IO) {
                        cqr->status = CQR_STATUS_QUEUED;
                }
                cqr = cqr->next;
        }

        DEV_MESSAGE (KERN_DEBUG, device, "%s",
                     "device request queue restarted by "
                     "state change pending interrupt");
        
        del_timer_sync (&(device->blocking_timer));
        device->stopped &= ~DASD_STOPPED_PENDING;
        dasd_schedule_bh (device);

} /* end dasd_handle_state_change_pending */

/*
 * function dasd_int_handler
 * is the DASD driver's default interrupt handler for SSCH-IO
 */
void
dasd_int_handler (int irq, void *ds, struct pt_regs *regs)
{
	int ip;
	ccw_req_t *cqr;
	dasd_device_t *device;
	dasd_era_t era;
	devstat_t *stat = (devstat_t *)ds;

        if (stat == NULL) {
                BUG();
	}

        DBF_EVENT (DBF_DEBUG,
                   "Int: IRQ %02x, CS/DS %04x, flag %08x, devno %04x, ip %08x",
                   irq,
                   ((stat->cstat<<8)|stat->dstat),
                   stat->flag,
                   stat->devno,
                   stat->intparm);

        /* first of all check for state change pending interrupt */
        if ((stat->dstat & DEV_STAT_ATTENTION ) && 
            (stat->dstat & DEV_STAT_DEV_END   ) &&
            (stat->dstat & DEV_STAT_UNIT_EXCEP)   ) {

                DBF_EVENT (DBF_NOTICE,
                           "State change Interrupt: %04x",
                           stat->devno);

                dasd_handle_state_change_pending (stat);
                return;
        }

	ip = stat->intparm;
	if (!ip) {		/* no intparm: unsolicited interrupt */

		MESSAGE (KERN_DEBUG,
                         "unsolicited interrupt: irq 0x%x devno %04x",
                         irq,
                         stat->devno);
		return;
	}

	if (ip & 0x80000001) {  /* check for invalid 'cqr' address */

		MESSAGE (KERN_DEBUG,
                         "spurious interrupt: irq 0x%x devno %04x, parm %08x",
                         irq,
                         stat->devno,ip);
		return;
	}

	cqr = (ccw_req_t *)(long)ip;

        /* check status - the request might have been killed because of dyn dettach */
	if (cqr->status != CQR_STATUS_IN_IO) {

		MESSAGE (KERN_DEBUG,
                         "invalid status: irq 0x%x devno %04x, status %02x",
                         irq,
                         stat->devno,
                         cqr->status);
		return;
	}

        /* some consistency checks */
	device = (dasd_device_t *) cqr->device;
	if (device == NULL || 
            device != ds-offsetof(dasd_device_t,dev_status)) {
                BUG();
	}
	if (device->devinfo.irq != irq) {
                BUG();
	}
	if (strncmp (device->discipline->ebcname, (char *) &cqr->magic, 4)) {
                BUG();
	}

	/* first of all lets try to find out the appropriate era_action */
        if (stat->flag & DEVSTAT_HALT_FUNCTION) {
                era = dasd_era_fatal;
                
	} else if (stat->flag  & DEVSTAT_FINAL_STATUS &&
                   stat->dstat == (DEV_STAT_CHN_END | DEV_STAT_DEV_END) &&
                   stat->cstat == 0) {
		/* received 'ok' for running IO */
		era = dasd_era_none;
                
        } else if (stat->flag & DEVSTAT_FLAG_SENSE_AVAIL) {
                /* got sense data */
		if (cqr->dstat == NULL)
			cqr->dstat = kmalloc (sizeof (devstat_t), GFP_ATOMIC);
		if (cqr->dstat) {
			memcpy (cqr->dstat, stat, sizeof (devstat_t));
		} else {
			MESSAGE (KERN_DEBUG, "%s",
                                 "no memory for dstat...ignoring");
		}
#ifdef ERP_DEBUG
		if (device->discipline            &&
                    device->discipline->dump_sense  ) {
                        
                        device->discipline->dump_sense (device, 
                                                        cqr);
		}
#endif
                if (device->discipline->examine_error == NULL) {
                        era = dasd_era_recover;
                } else {
                        era = device->discipline->examine_error (cqr, stat);
                }

        } else  if (stat->flag & DEVSTAT_NOT_OPER) {
                /* path became offline or similar                          */
                /* => retry to see if there are any other pathes available */
                DEV_MESSAGE (KERN_DEBUG, device, "%s",
                             "Device or a path became not operational while in IO");
		era = dasd_era_recover;

        } else if (stat->dstat & ~(DEV_STAT_CHN_END | DEV_STAT_DEV_END) ||
                   stat->cstat & ~(SCHN_STAT_PCI | SCHN_STAT_INCORR_LEN) ) {
                /* received device state apart from (channel end & device end)  */
                /* OR any kind of channel check (e.g. IFCC, DATA_CHECK or ..... */
                /* we got no sense data, therefore we just retry                */
                DEV_MESSAGE (KERN_DEBUG, device,
                             "Status without sense (IFCC,...) CS/DS %04x flag %08x",
			     ((stat->cstat<<8)|stat->dstat),
			     stat->flag);
		era = dasd_era_recover;

        } else {
                /* any other kind of interrupt  - just retry */
                DEV_MESSAGE (KERN_DEBUG, device,
                             "Got unclassified interrupt CS/DS %04x flag %08x",
                             ((stat->cstat<<8)|stat->dstat),
                             stat->flag);
                era = dasd_era_recover;
        }
        
	/* process channel measurement facility configuration while 
	   the channel is idle */
	cmf_device_callback(&device->cdev);

        switch (era) {
        case dasd_era_none:
		check_then_set(&cqr->status, 
			       CQR_STATUS_IN_IO, 
			       CQR_STATUS_DONE);
		cqr->stopclk = get_clock ();
		/* start the next queued request if possible -> fast_io */
		if (cqr->next &&
		    cqr->next->status == CQR_STATUS_QUEUED) {
			if (device->discipline->start_IO (cqr->next) != 0) {
				MESSAGE (KERN_WARNING, "%s",
					 "Interrupt fastpath failed!");
			} 
		}
		break;
        case dasd_era_fatal:
		check_then_set (&cqr->status, 
				CQR_STATUS_IN_IO,
				CQR_STATUS_FAILED);
		cqr->stopclk = get_clock ();
		break;
        case dasd_era_recover:
		check_then_set (&cqr->status, 
				CQR_STATUS_IN_IO,
				CQR_STATUS_ERROR);
		break;
        default:
		BUG ();
        }
	
	/* handle special device initialization request */
        if ( cqr == device->init_cqr &&
             ( cqr->status == CQR_STATUS_DONE ||
               cqr->status == CQR_STATUS_FAILED )){
                dasd_state_init_to_ready(device);
                if ( atomic_read(&dasd_init_pending) == 0)
                        wake_up (&dasd_init_waitq);
        }
	dasd_schedule_bh (device);

} /* end dasd_int_handler */

/********************************************************************************
 * SECTION: Some stuff related to error recovery 
 ********************************************************************************/

/*
 * DEFAULT_ERP_ACTION
 *
 * DESCRIPTION
 *   just retries the current cqr
 *
 * PARAMETER
 *   cqr                failed CQR
 *
 * RETURN VALUES
 *   cqr                modified CQR
 */
ccw_req_t *
dasd_default_erp_action (ccw_req_t * cqr)
{

        dasd_device_t *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);
 
                check_then_set (&cqr->status,
                                CQR_STATUS_ERROR,
                                CQR_STATUS_QUEUED);
        } else {
                DEV_MESSAGE (KERN_WARNING, device, "%s",
			     "default ERP called (NO retry left)");
		
		check_then_set (&cqr->status,
				CQR_STATUS_ERROR,
				CQR_STATUS_FAILED);
		
		cqr->stopclk = get_clock ();
        }
        return cqr;
} /* end dasd_default_erp_action */

/*
 * DEFAULT_ERP_POSTACTION
 *
 * DESCRIPTION
 *   Frees all ERPs of the current ERP Chain and set the status
 *   of the original CQR either to CQR_STATUS_DONE if ERP was successful
 *   or to CQR_STATUS_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
 */
ccw_req_t *
dasd_default_erp_postaction (ccw_req_t *erp)
{

	ccw_req_t     *cqr      = NULL, 
                      *free_erp = NULL;
	dasd_device_t *device   = erp->device;
	int           success;

	if (erp->refers   == NULL || 
            erp->function == NULL   ) {

		BUG ();
	}

	if (erp->status == CQR_STATUS_DONE)
		success = 1;
	else
		success = 0;

	/* free all ERPs - but NOT the original cqr */
	while (erp->refers != NULL) {

		free_erp = erp;
		erp      = erp->refers;

		/* remove the request from the device queue */
		dasd_chanq_deq (&device->queue,
                                free_erp);

		/* free the finished erp request */
		dasd_free_request (free_erp, free_erp->device);
	}

	/* save ptr to original cqr */
	cqr = erp;

	/* set corresponding status for original cqr */
	if (success) {
                cqr->status = CQR_STATUS_DONE;
	} else {
                cqr->status  = CQR_STATUS_FAILED;
                cqr->stopclk = get_clock ();
	}

	return cqr;

} /* end default_erp_postaction */

/********************************************************************************
 * SECTION: The helpers of the struct file_operations 
 ********************************************************************************/

/*
 * function dasd_format
 * 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 (dasd_device_t * device, format_data_t * fdata)
{
	int rc = 0;
	int openct = atomic_read (&device->open_count);
        ccw_req_t *req;

	if (openct > 1) {

		DEV_MESSAGE (KERN_WARNING, device, "%s",
                             "dasd_format: device is open! "
                             "expect errors.");
	}

	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);

	while ((!rc) && (fdata->start_unit <= fdata->stop_unit)) {

		if (device->discipline->format_device == NULL)
			break;

		req = device->discipline->format_device (device, fdata);
		if (req == NULL) {
			rc = -ENOMEM;
			break;
		}

                rc = dasd_sleep_on_req (req);
                dasd_free_request (req, device); /* request is no longer used */

                if ( rc ) {
                        if (rc != -ERESTARTSYS )
                               DEV_MESSAGE (KERN_WARNING, device,
                                            " Formatting of unit %d failed"
                                            " with rc = %d",
                                            fdata->start_unit, rc);
                       break;
                }
		fdata->start_unit++;
	}
	return rc;
}				/* end dasd_format */

static struct list_head dasd_ioctls = LIST_HEAD_INIT (dasd_ioctls);

static dasd_ioctl_list_t *
dasd_find_ioctl (int no)
{
	struct list_head *curr;
	list_for_each (curr, &dasd_ioctls) {
		if (list_entry (curr, dasd_ioctl_list_t, list)->no == no) {
			return list_entry (curr, dasd_ioctl_list_t, list);
		}
	}
	return NULL;
}

int
dasd_ioctl_no_register (struct module *owner, int no, dasd_ioctl_fn_t handler)
{
	dasd_ioctl_list_t *new;
	if (dasd_find_ioctl (no))
		return -EBUSY;
	new = kmalloc (sizeof (dasd_ioctl_list_t), GFP_KERNEL);
	if (new == NULL)
		return -ENOMEM;
	new->owner = owner;
	new->no = no;
	new->handler = handler;
	list_add (&new->list, &dasd_ioctls);
	MOD_INC_USE_COUNT;
	return 0;
}

int
dasd_ioctl_no_unregister (struct module *owner, int no, dasd_ioctl_fn_t handler)
{
	dasd_ioctl_list_t *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);
	MOD_DEC_USE_COUNT;
	return 0;
}

/*
 * handle the re-read partition table IOCTL (BLKRRPART)
 */ 
static int
dasd_revalidate (dasd_device_t * device)
{
        int rc = 0;
	int i;
	kdev_t kdev = device->kdev;
	int openct = atomic_read (&device->open_count);
	int start = MINOR (kdev);
	if (openct != 1) {

		DEV_MESSAGE (KERN_WARNING, device, "%s",
                             "BLKRRPART: device is open! expect errors.");
	}
	for (i = (1 << DASD_PARTN_BITS) - 1; i >= 0; i--) {
                int major = device->major_info->gendisk.major;
		invalidate_device(MKDEV (major, start+i), 1);
	}
        dasd_destroy_partitions(device);
        dasd_setup_partitions(device);
        return rc;

}

/*
 * function do_dasd_ioctl
 * Implementation of the DASD API.
 * Changes to the API should be binary compatible to privous versions
 * of the user-space applications by means of any already existing tool 
 * (e.g. dasdfmt) must work with the new kernel API.
 */

static int
do_dasd_ioctl (struct inode *inp, /* unsigned */ int no, unsigned long data)
{
	int rc = 0;
	dasd_device_t *device = dasd_device_from_kdev (inp->i_rdev);
	major_info_t *major_info;

	if (!device) {

		MESSAGE (KERN_WARNING,
                         "No device registered as device (%d:%d)",
                         MAJOR (inp->i_rdev), 
                         MINOR (inp->i_rdev));

		return -EINVAL;
	}
	if ((_IOC_DIR (no) != _IOC_NONE) && (data == 0)) {
		PRINT_DEBUG ("empty data ptr");
		return -EINVAL;
	}
	major_info = device->major_info;

	DBF_DEV_EVENT (DBF_DEBUG, device,
                 "ioctl 0x%08x %s'0x%x'%d(%d) with data %8lx",
                 no,
                 (_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"),
                 _IOC_TYPE (no),
                 _IOC_NR (no),
                 _IOC_SIZE (no),
                 data);

	switch (no) {
        case DASDAPIVER: {      /* retrun dasd API version */
                int ver = DASD_API_VERSION;
                rc = put_user(ver, (int *) data);
                break;
        }
	case BLKGETSIZE: {	/* Return device size in # of sectors */
                long blocks = major_info->gendisk.sizes 
                        [MINOR (inp->i_rdev)] << 1;
                rc = put_user(blocks, (long *) data);
                break;
        }
	case BLKGETSIZE64:{ 
                u64 blocks = major_info->gendisk.sizes 
                        [MINOR (inp->i_rdev)];
                rc = put_user(blocks << 10, (u64 *) data);
                break;
        }
	case BLKRRPART: {       /* reread partition table */
                if (!capable (CAP_SYS_ADMIN)) {
                        rc = -EACCES;
                        break;
                }
                rc = dasd_revalidate (device);
                break;
        }
	case HDIO_GETGEO: {     /* return disk geometry */
                struct hd_geometry geo = { 0, };
                rc = dasd_fillgeo (inp->i_rdev, &geo);
                if (rc)
                        break;

                rc = copy_to_user ((struct hd_geometry *) data, &geo,
                                   sizeof (struct hd_geometry));
                if (rc)
                        rc = -EFAULT;
                break;
        }
	case BIODASDDISABLE: {  /* disable device */
                if (!capable (CAP_SYS_ADMIN)) {
                        rc = -EACCES;
                        break;
                }

                if ( device->level > DASD_STATE_ACCEPT) {
                        dasd_deactivate_queue(device);
                        if ( device->request_queue)
                                dasd_flush_request_queues(device,0);
                        dasd_flush_chanq(device,0);
                        dasd_disable_blkdev(device);
                        dasd_set_device_level (device->devinfo.devno, 
                                               device->discipline, 
                                               DASD_STATE_ACCEPT);
                }

                break;
        }
	case BIODASDENABLE: {   /* enable device */
                dasd_range_t range = { 
                        from: device->devinfo.devno,
                        to: device->devinfo.devno 
                };
                if (!capable (CAP_SYS_ADMIN)) {
                        rc = -EACCES;
                        break;
                }
                dasd_enable_ranges (&range, device->discipline, 0);
                break;
        }
	case BIODASDFMT: {      /* format device */
                /* fdata == NULL is no longer a valid arg to dasd_format ! */
                int partn = MINOR (inp->i_rdev) &
                        ((1 << major_info->gendisk.minor_shift) - 1);
                format_data_t fdata;
                
                if (!capable (CAP_SYS_ADMIN)) {
                        rc = -EACCES;
                        break;
                }
                if (dasd_features_from_devno(device->devinfo.devno)&DASD_FEATURE_READONLY) {
                        rc = -EROFS;
                        break;
                }
                if (!data) {
                        rc = -EINVAL;
                        break;
                }
                rc = copy_from_user (&fdata, (void *) data,
                                     sizeof (format_data_t));
                if (rc) {
                        rc = -EFAULT;
                        break;
                }
                if (partn != 0) {

                        DEV_MESSAGE (KERN_WARNING, device, "%s",
                                     "Cannot low-level format a partition");

                        return -EINVAL;
                }
                rc = dasd_format (device, &fdata);
                break;
        }
	case BIODASDGATTR: {      /* Get Attributes (cache operations) */

                attrib_data_t attrib;
                
                if (!capable (CAP_SYS_ADMIN)) {
                        rc = -EACCES;
                        break;
                }

                if (!data) {
                        rc = -EINVAL;
                        break;
                }
                        
                if (!device->discipline->get_attrib) {
                        rc = -EINVAL;
                        break;
                }
                        
                device->discipline->get_attrib (device,
                                                &attrib);
                
                rc = copy_to_user ((void *) data, &attrib,
                                   sizeof (attrib_data_t));

                if (rc) {
                        rc = -EFAULT;
                }

                break;
        }
	case BIODASDSATTR: {      /* Set Attributes (cache operations) */

                attrib_data_t attrib;
                
                if (!capable (CAP_SYS_ADMIN)) {
                        rc = -EACCES;
                        break;
                }

                if (!data) {
                        rc = -EINVAL;
                        break;
                }
                        
                if (!device->discipline->set_attrib) {
                        rc = -EINVAL;
                        break;
                }
                        
                rc = copy_from_user (&attrib, (void *) data,
                                     sizeof (attrib_data_t));
                if (rc) {
                        rc = -EFAULT;
                        break;
                }

                rc = device->discipline->set_attrib (device,
                                                     &attrib);
                break;
        }
	case BIODASDPRRST: {    /* reset device profile information */
                if (!capable (CAP_SYS_ADMIN)) {
                        rc = -EACCES;
                        break;
                }
                memset (&device->profile, 0,
                        sizeof (dasd_profile_info_t));
                break;
        }
	case BIODASDPRRD: {     /* return device profile information */
                rc = copy_to_user((long *)data,
                                  (long *)&device->profile,
                                  sizeof(dasd_profile_info_t));
                if (rc)
                        rc = -EFAULT;
                break;
        }
	case BIODASDRSRV: {     /* reserve device */
                ccw_req_t *cqr;
                if (!capable (CAP_SYS_ADMIN)) {
                        rc = -EACCES;
                        break;
                }

                if (!device->discipline->reserve) {
                        rc = -EINVAL;
                        break;
                }
                
                cqr = device->discipline->reserve (device);

                if (cqr) {
                        struct timer_list res_timer;
                        
                        init_timer (&res_timer);
                        res_timer.function = dasd_resrel_timeout; 
                        res_timer.data     = (unsigned long) cqr;
                        res_timer.expires  = jiffies + 2 * HZ; 
                        add_timer (&res_timer);
                        
                        rc = dasd_sleep_on_immediate (cqr);

                        del_timer_sync (&res_timer); 
                        dasd_free_request (cqr, 
                                           device);
                } else {
                        rc = -ENOMEM;
                }
                break;
        }
	case BIODASDRLSE: {     /* release device */
                ccw_req_t *cqr;
                if (!capable (CAP_SYS_ADMIN)) {
                        rc = -EACCES;
                        break;
                }

                if (!device->discipline->release) {
                        rc = -EINVAL;
                        break;
                }

                cqr = device->discipline->release (device);
                
                if (cqr) {
                        struct timer_list rel_timer;
                        
                        init_timer (&rel_timer);
                        rel_timer.function = dasd_resrel_timeout; 
                        rel_timer.data     = (unsigned long) cqr;
                        rel_timer.expires  = jiffies + 2 * HZ; 
                        add_timer (&rel_timer);
                        
                        rc = dasd_sleep_on_immediate (cqr);

                        del_timer_sync (&rel_timer); /* in case of interrupt */
                        dasd_free_request (cqr, 
                                           device);
                } else {
                        rc = -ENOMEM;
                }
                break;
        }
	case BIODASDSLCK: {     /* steal lock - unconditional reserve device */
                if (!capable (CAP_SYS_ADMIN)) {
                        rc = -EACCES;
                        break;
                }
                        
                rc = dasd_steal_lock (device);
                break;
        }
	case BIODASDINFO:       /* return dasd information */
	case BIODASDINFO2: {    /* return dasd information2 (incl. format and features) */
                dasd_information2_t dasd_info;
                
                unsigned long flags;

                if (!device->discipline->fill_info) {
                        rc = -EINVAL;
                        break;
                }

                rc = device->discipline->fill_info (device, 
                                                    &dasd_info);

                dasd_info.label_block = device->sizes.pt_block;
                dasd_info.devno = device->devinfo.devno;
                dasd_info.schid = device->devinfo.irq;
                dasd_info.cu_type = device->devinfo.sid_data.cu_type;
                dasd_info.cu_model = device->devinfo.sid_data.cu_model;
                dasd_info.dev_type = device->devinfo.sid_data.dev_type;
                dasd_info.dev_model = device->devinfo.sid_data.dev_model;
                dasd_info.open_count =
                        atomic_read (&device->open_count);
                dasd_info.status = device->level;
                
                /* check if device is really formatted - LDL / CDL was returned by 'fill_info' */
                if ((device->level < DASD_STATE_READY) ||
                    (dasd_check_bp_block (device)    )   ) {
                        dasd_info.format = DASD_FORMAT_NONE;
                }
                
                dasd_info.features = 
                        dasd_features_from_devno (device->devinfo.devno);
                
                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            ) &&
                    (device->request_queue->request_fn)   ) {
                        struct list_head *l;
                        ccw_req_t *cqr = device->queue.head;
                        spin_lock_irqsave (&io_request_lock, flags);
                        list_for_each (l,
                                       &device->request_queue->
                                       queue_head) {
                                dasd_info.req_queue_len++;
                        }
                        spin_unlock_irqrestore (&io_request_lock,
                                                flags);
                        s390irq_spin_lock_irqsave (device->devinfo.irq,
                                                   flags);
                        while (cqr) {
                                cqr = cqr->next;
                                dasd_info.chanq_len++;
                        }
                        s390irq_spin_unlock_irqrestore (device->devinfo.
                                                        irq, flags);
                }

                rc = copy_to_user ((long *) data, (long *) &dasd_info,
                                   ((no == (unsigned int) BIODASDINFO2) ? 
                                    sizeof (dasd_information2_t) : 
                                    sizeof (dasd_information_t)));

                if (rc)
                        rc = -EFAULT;
                break;
        }
        case BIODASDPSRD: { /* Performance Statistics Read */
                
                ccw_req_t              *cqr;
                dasd_rssd_perf_stats_t *stats;

                if ((!device->discipline->read_stats) ||
                    (!device->discipline->ret_stats )   ) {
                        rc = -EINVAL;
                        break;
                }

                cqr = device->discipline->read_stats (device); 
                
                if (cqr) {
                        
                        if ((rc = dasd_sleep_on_req (cqr)) == 0) {
                                
                                if ((stats = device->discipline->ret_stats (cqr)) != NULL) {
                                        
                                        rc = copy_to_user ((long *) data,
                                                           (long *) stats,
                                                           sizeof (dasd_rssd_perf_stats_t));
                                } else {
                                        
                                        rc = -EFAULT;
                             }
                        }
                        
                        dasd_free_request (cqr, 
                                           device);
                        
                } else {
                        rc = -ENOMEM;
                }
                break;
        }
#if 0 /* needed for XFS */
	case BLKBSZSET: {
		int bsz;
		rc = copy_from_user ((long *)&bsz,(long *)data,sizeof(int));
		if ( rc ) {
			rc = -EFAULT;
		} else {
			if ( bsz >= device->sizes.bp_block )
				rc = blk_ioctl (inp->i_rdev, no, data);
			else
				rc = -EINVAL; 
		}
		break;
        }
#endif /* 0 */
	case BLKROSET: {
                int intval;
                dasd_range_t *temp;
                int devindex = 0;
                unsigned long flags;
                struct list_head *l;
                int major=MAJOR(device->kdev);
                int minor;

                if (!capable(CAP_SYS_ADMIN))
                        return -EACCES;
                if (inp->i_rdev != device->kdev)
                        // ro setting is not allowed for partitions
                        return -EINVAL; 
                if (get_user(intval, (int *)(data)))
                        return -EFAULT;
                spin_lock_irqsave (&range_lock, flags);
                list_for_each (l, &dasd_range_head.list) {
                        temp = list_entry (l, dasd_range_t, list);
                        if (device->devinfo.devno >= temp->from && device->devinfo.devno <= temp->to) {
                                spin_unlock_irqrestore (&range_lock, flags);
                                if (intval)
                                        temp->features |= DASD_FEATURE_READONLY;
                                else
                                        temp->features &= ~DASD_FEATURE_READONLY;
                                goto continue_blkroset;
                        }
                        devindex += temp->to - temp->from + 1;
                }
                spin_unlock_irqrestore (&range_lock, flags);
                return(-ENODEV);
continue_blkroset:
                for (minor = MINOR(device->kdev); minor < MINOR(device->kdev) + (1 << DASD_PARTN_BITS); minor++)
                        set_device_ro(MKDEV(major,minor), intval);
                return 0;
        }
	case BLKBSZGET:
	case BLKSSZGET:
	case BLKROGET:
	case BLKRASET:
	case BLKRAGET:
	case BLKFLSBUF:
	case BLKPG:
	case BLKELVGET:
	case BLKELVSET:
		return blk_ioctl (inp->i_rdev, no, data);
		break;
	default: {

                dasd_ioctl_list_t *old = dasd_find_ioctl (no);
                if (old) {
                        if ( old->owner )
                                __MOD_INC_USE_COUNT(old->owner);
                        rc = old->handler (inp, no, data);
                        if ( old->owner )
                                __MOD_DEC_USE_COUNT(old->owner);
                } else {

                        DBF_DEV_EVENT (DBF_INFO, device,
                                       "unknown ioctl 0x%08x=%s'0x%x'%d(%d) data %8lx",
                                       no,
                                       (_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"),
                                       _IOC_TYPE (no),
                                       _IOC_NR (no), 
                                       _IOC_SIZE (no),
                                       data);

                        rc = -ENOTTY;
                }
                break;
		}
	}
	return rc;
}

/********************************************************************************
 * SECTION: The members of the struct file_operations 
 ********************************************************************************/

static int
dasd_ioctl (struct inode *inp, struct file *filp,
	    unsigned int no, unsigned long data)
{
	int rc = 0;
	if ((!inp) || !(inp->i_rdev)) {
		return -EINVAL;
	}
	rc = do_dasd_ioctl (inp, no, data);
	return rc;
}

static int
dasd_open (struct inode *inp, struct file *filp)
{
	int rc = 0;
        unsigned long flags;
	dasd_device_t *device;

	if ((!inp) || !(inp->i_rdev)) {
		rc = -EINVAL;
                goto fail;
	}
	if (dasd_probeonly) {

		MESSAGE (KERN_INFO,
                         "No access to device (%d:%d) due to probeonly mode",
                         MAJOR (inp->i_rdev), 
                         MINOR (inp->i_rdev));

		rc = -EPERM;
                goto fail;
	}
        spin_lock_irqsave(&discipline_lock,flags);
	device = dasd_device_from_kdev (inp->i_rdev);
	if (!device) {

		MESSAGE (KERN_WARNING,
                         "No device registered as (%d:%d)",
                         MAJOR (inp->i_rdev), 
                         MINOR (inp->i_rdev));

		rc = -ENODEV;
                goto unlock;
	}
	if (device->level <= DASD_STATE_ACCEPT ) {

		DBF_DEV_EVENT (DBF_ERR, device, " %s", 
                               " Cannot open unrecognized device");

		rc = -ENODEV;
                goto unlock;
	}
	if (atomic_inc_return (&device->open_count) == 1 ) {
                if ( device->discipline->owner )
                        __MOD_INC_USE_COUNT(device->discipline->owner);
        }
 unlock:
        spin_unlock_irqrestore(&discipline_lock,flags);
 fail:
	return rc;
}

/*
 * DASD_RELEASE
 *
 * DESCRIPTION
 */
static int
dasd_release (struct inode *inp, struct file *filp)
{
	int rc = 0;
        int count;
	dasd_device_t *device;

	if ((!inp) || !(inp->i_rdev)) {
		rc = -EINVAL;
                goto out;
	}
	device = dasd_device_from_kdev (inp->i_rdev);
	if (!device) {

		MESSAGE (KERN_WARNING,
                         "No device registered as %d:%d",
                         MAJOR (inp->i_rdev), 
                         MINOR (inp->i_rdev));

		rc = -EINVAL;
                goto out;
	}

	if (device->level < DASD_STATE_ACCEPT ) {

		DBF_DEV_EVENT (DBF_ERR, device, " %s",
                               " Cannot release unrecognized device");

		rc = -ENODEV;
                goto out;
	}
        count = atomic_dec_return (&device->open_count);
        if ( count == 0) {
                invalidate_buffers (inp->i_rdev);
                if ( device->discipline->owner )
                        __MOD_DEC_USE_COUNT(device->discipline->owner);
	} else if ( count == -1 ) { /* paranoia only */
                atomic_set (&device->open_count,0);

                MESSAGE (KERN_WARNING, "%s",
                         "release called with open count==0");
        }
 out:
	return rc;
}

static struct
block_device_operations dasd_device_operations =
{
	owner:THIS_MODULE,
	open:dasd_open,
	release:dasd_release,
	ioctl:dasd_ioctl,
};

/********************************************************************************
 * SECTION: Management of device list 
 ********************************************************************************/
int
dasd_fillgeo(int kdev,struct hd_geometry *geo)
{
	dasd_device_t *device = dasd_device_from_kdev (kdev);

	if (!device)
                return -EINVAL;

        if (!device->discipline->fill_geometry)
		return -EINVAL;

	device->discipline->fill_geometry (device, geo);
	geo->start = device->major_info->gendisk.part[MINOR(kdev)].start_sect 
		     >> device->sizes.s2b_shift;;
        return 0;
} 


/* This one is needed for naming 18000+ possible dasd devices */
int
dasd_device_name (char *str, int index, int partition, struct gendisk *hd)
{
        major_info_t *major_info;
        struct list_head *l;
	char first, second, third;
	int len;

        if (hd == NULL)
                return -EINVAL;

        major_info = NULL;
        list_for_each (l, &dasd_major_info) {
                major_info = list_entry (l, major_info_t, list);
                if (&major_info->gendisk == hd)
                        break;
                index += DASD_PER_MAJOR;
        }
        if (major_info == NULL || &major_info->gendisk != hd) { 
                /* list empty or hd not found in list */
                return -EINVAL;
        }

        len = 0;
	third = index % 26;
	second = ((index - 26) / 26) % 26;
	first = (((index - 702) / 26) / 26) % 26;

	len = sprintf (str, "dasd");
	if (index > 701) {
		len += sprintf (str + len, "%c", first + 'a');
	}
	if (index > 25) {
		len += sprintf (str + len, "%c", second + 'a');
	}
	len += sprintf (str + len, "%c", third + 'a');
	if (partition) {
		if (partition > 9) {
			return -EINVAL;
		} else {
			len += sprintf (str + len, "%d", partition);
		}
	}
	str[len] = '\0';
	return 0;
}

static void
dasd_plug_device (dasd_device_t * device)
{
	atomic_set(&device->plugged,1);	
}

static void
dasd_unplug_device (dasd_device_t * device)
{
	atomic_set(&device->plugged,0);	
        dasd_schedule_bh(device);
}

static void
dasd_flush_chanq ( dasd_device_t * device, int destroy ) 
{
        ccw_req_t *cqr;
        unsigned long flags;
        if ( destroy ) {
                s390irq_spin_lock_irqsave (device->devinfo.irq, flags);
                cqr = device->queue.head;
                while ( cqr != NULL ) {
                        if ( cqr->status == CQR_STATUS_IN_IO )
                                device->discipline->term_IO (cqr);
                        if ( cqr->status != CQR_STATUS_DONE &&
                             cqr->status != CQR_STATUS_FAILED ) {

                                cqr->status = CQR_STATUS_FAILED;

                                cqr->stopclk = get_clock ();

                        }
                        dasd_schedule_bh(device);
                        cqr = cqr->next;
                }
                s390irq_spin_unlock_irqrestore (device->devinfo.irq, flags);
        }
        wait_event( device->wait_q, device->queue.head == NULL );
}

static void
dasd_flush_request_queues ( dasd_device_t * device, int destroy )
{
        int i;
        int major = MAJOR(device->kdev);
        int minor = MINOR(device->kdev);
        for ( i = 0; i < (1 << DASD_PARTN_BITS); i ++) {
                if ( destroy )
                        destroy_buffers(MKDEV(major,minor+i)); 
                else
                        invalidate_buffers(MKDEV(major,minor+i)); 
        } 
}

static inline void dasd_do_hotplug_event (dasd_device_t* device, int eventid) {
#ifdef CONFIG_HOTPLUG
        int i;
        char *argv[3], *envp[8];
        char devno[20],major[20],minor[20],devname[26],action[20];

        /* setup command line arguments */
        i=0;
        argv[i++] = hotplug_path;
        argv[i++] = "dasd";
        argv[i++] = 0;

        /* minimal environment */
        i=0;
        envp[i++] = "HOME=/";
        envp[i++] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";
        
        /* device information and event*/
        sprintf (devno, "DEVNO=%04x", device->devinfo.devno);
        sprintf (major, "MAJOR=%d", MAJOR(device->kdev));
        sprintf (minor, "MINOR=%d", MINOR(device->kdev));
        sprintf (devname, "DASDNAME=%s",device->name);
        switch (eventid) {
        case DASD_HOTPLUG_EVENT_ADD:
                sprintf (action,"ACTION=add");
                break;
        case DASD_HOTPLUG_EVENT_REMOVE:
                sprintf (action,"ACTION=remove");
                break;
        case DASD_HOTPLUG_EVENT_PARTCHK:
                sprintf (action,"ACTION=partchk");
                break;
        case DASD_HOTPLUG_EVENT_PARTREMOVE:
                sprintf (action,"ACTION=partremove");
                break;
        default:
                BUG();
        }
        envp[i++] = devno;
        envp[i++] = major;
        envp[i++] = minor;
        envp[i++] = devname;
        envp[i++] = action;
        envp[i++] = 0;
        
        call_usermodehelper (argv [0], argv, envp);
#endif
}


static int
dasd_disable_volume ( dasd_device_t * device, int force ) 
{
        int rc = 0;
        int target  = DASD_STATE_KNOWN;
        int count = atomic_read (&device->open_count);
        
	if ( count ) {

		DEV_MESSAGE (KERN_EMERG, device, "%s",
                             "device has vanished although it was open!");
        }
        if ( force ) {
                dasd_deactivate_queue(device);
                dasd_flush_chanq(device,force);
                dasd_flush_request_queues(device,force);
                dasd_disable_blkdev(device);
                target = DASD_STATE_DEL;
        }

        /* unregister partitions ('ungrok_partitions') */
        devfs_register_partitions(&device->major_info->gendisk,
                                  MINOR(device->kdev),1);
        dasd_do_hotplug_event (device, DASD_HOTPLUG_EVENT_PARTREMOVE);
        
        DBF_DEV_EVENT (DBF_ERR, device, 
                       "disabling device, target state: %d",
                       target);

        dasd_set_device_level (device->devinfo.devno, 
                               device->discipline, 
                               target);
        return rc;
}

static void
dasd_disable_ranges (dasd_range_t *range, 
                     dasd_discipline_t *discipline,
                     int all, int force ) 
{
        dasd_device_t **dptr;
        dasd_device_t *device;
        dasd_range_t *rrange;
        int j;

        if (range == &dasd_range_head) {
                rrange = list_entry (range->list.next, 
                                     dasd_range_t, list);
        } else {
                rrange = range;
        }
        do {
                for (j = rrange->from; j <= rrange->to; j++) {
                        dptr = dasd_device_from_devno(j);
                        if (dptr == NULL) {
                            continue;
                        }
                        device = *dptr;
                        if (device == NULL ||
                            (discipline != NULL &&
                             device -> discipline != discipline))
                                continue;
                        
                        dasd_disable_volume(device, force);
                }

                if (rrange->list.next == NULL)
                        break;
                rrange = list_entry (rrange->list.next, dasd_range_t, list);
        } while ( all && rrange && rrange != range );
        
}

static void 
dasd_enable_single_device ( unsigned long arg ) {
        dasd_device_t * device =(dasd_device_t *) arg;
        int devno = device->devinfo.devno;
        dasd_range_t range = { from: devno, to:devno };
        dasd_enable_ranges (&range,NULL,0);
}

static void
dasd_enable_ranges (dasd_range_t *range, 
                    dasd_discipline_t *discipline, 
                    int all) 
{
        int retries = 0;
	int j;
        int do_again;
        kdev_t tempdev;
	dasd_range_t *rrange;

	if (range == NULL)
		return;
        
        do {
                do_again = 0;
                if (range == &dasd_range_head) {
                        rrange = list_entry (range->list.next, 
                                             dasd_range_t, list);
                } else {
                        rrange = range;
                }
                do {
                        for (j = rrange->from; j <= rrange->to; j++) {
                                if ( dasd_devindex_from_devno(j) < 0 )
                                        continue;
                                if (-EAGAIN == dasd_set_device_level 
                                    (j, discipline, DASD_STATE_ONLINE))
                                        do_again = 1;
                        }
                        rrange = list_entry (rrange->list.next, dasd_range_t, list);
                } while ( all && rrange && rrange != range );

                if ((atomic_read (&dasd_init_pending) == 0) &&
                    (!do_again)) /* we are done, exit loop */
                        break;

                if ( retries == 0 ) {

                        MESSAGE (KERN_INFO, "%s",
                                 "waiting for responses...");

                } else if ( retries < 5 ) {

                        DBF_EVENT (DBF_NOTICE, "%s",
                                   "waiting a little bit longer...");

                } else {

                        MESSAGE (KERN_INFO, "%s",
                                 "giving up, enable late devices manually!");
                        break;
                }

                /* prevent scheduling if called by bh (timer) */
                if (!in_interrupt()) {
                        interruptible_sleep_on_timeout (&dasd_init_waitq, 
                                                        (1 * HZ)         );
                }

                retries ++;
        } while (1);
        /* now setup block devices */

        /* Now do block device and partition setup */
        if (range == &dasd_range_head) {
                rrange = list_entry (range->list.next, 
                                     dasd_range_t, list);
        } else {
                rrange = range;
        }
        do {
                for (j = rrange->from; j <= rrange->to; j++) {
                        dasd_device_t **dptr;
                        dasd_device_t *device;
                        if ( dasd_devindex_from_devno(j) < 0 )
                                continue;
                        dptr = dasd_device_from_devno(j);
                        device = *dptr;
                        if (device == NULL )
                                continue;
                        if ( ((discipline == NULL && device->discipline != NULL) ||
                              (device->discipline == discipline )) &&
                             device->level == DASD_STATE_ONLINE &&
                             device->request_queue == NULL ) {
                                if (dasd_features_from_devno(j)&DASD_FEATURE_READONLY) {
                                        for (tempdev=device->kdev;
                                             tempdev<(device->kdev +(1 << DASD_PARTN_BITS));
                                             tempdev++)
                                                set_device_ro (tempdev, 1);

                                        DEV_MESSAGE (KERN_WARNING, device, "%s",
                                                     "setting read-only mode ");
                                }
                                dasd_setup_blkdev(device);
                                dasd_setup_partitions(device);
                        }
                }
                rrange = list_entry (rrange->list.next, dasd_range_t, list);
        } while ( all && rrange && rrange != range );
}

#ifdef CONFIG_DASD_DYNAMIC
/*
 * DASD_NOT_OPER_HANDLER
 *
 * DESCRIPTION
 *   handles leaving devices
 */
static void
dasd_not_oper_handler (int irq, int status)
{
	dasd_device_t *device;
	major_info_t *major_info;
        ccw_req_t* cqr;
	struct list_head *l;
        unsigned long flags;
	int i, devno;

	/* find out devno of leaving device: CIO has already deleted this information ! */
        devno = -ENODEV;
	device = NULL;
	list_for_each (l, &dasd_major_info) {
		major_info = list_entry (l, major_info_t, list);
		for (i = 0; i < DASD_PER_MAJOR; i++) {
			device = major_info->dasd_device[i];
			if (device && device->devinfo.irq == irq) {
				devno = device->devinfo.devno;
				break;
			}
		}
		if (devno != -ENODEV)
			break;
	}

	if (devno < 0) {

		MESSAGE (KERN_WARNING,
                         "not_oper_handler called on irq 0x%04x no devno!", 
                         irq);
		return;
	}
	switch (status) {
	case DEVSTAT_DEVICE_GONE:
	case DEVSTAT_REVALIDATE: //FIXME
		DEV_MESSAGE (KERN_DEBUG, device, "%s", 
			"device is gone, disabling it permanently\n");
        	dasd_disable_volume(device, 1);
		break;
	case DEVSTAT_NOT_ACC:
	case DEVSTAT_NOT_ACC_ERR:
		DEV_MESSAGE (KERN_DEBUG, device, "%s",
			"device is not accessible, disabling it temporary\n");                
                s390irq_spin_lock_irqsave (device->devinfo.irq, 
                                           flags);
                device->stopped |= DASD_STOPPED_NOT_ACC;

		if (status == DEVSTAT_NOT_ACC_ERR) {
			cqr = device->queue.head;
			while (cqr) {
				if (cqr->status == CQR_STATUS_QUEUED)
					break;
				if (cqr->status == CQR_STATUS_IN_IO)
					cqr->status = CQR_STATUS_QUEUED;
				cqr = cqr->next;
			}
		}
                s390irq_spin_unlock_irqrestore(device->devinfo.irq, 
                                               flags);

		break;
	default:
		panic ("dasd not operational handler was called with illegal status\n");
	}
}

/*
 * DASD_OPER_HANDLER
 *
 * DESCRIPTION
 *   called by the machine check handler to make an device operational
 */
int
dasd_oper_handler (int irq, devreg_t * devreg)
{
	int devno;
	int rc = 0;
	major_info_t *major_info;
        dasd_range_t range;
        dasd_device_t *device;
	struct list_head *l;
	unsigned long flags;
        int i;


	devno = get_devno_by_irq (irq);
	if (devno == -ENODEV) {
		rc = -ENODEV;
                goto out;
	}

	/* find out devno of device */
	device = NULL;
	list_for_each (l, &dasd_major_info) {
		major_info = list_entry (l, major_info_t, list);
		for (i = 0; i < DASD_PER_MAJOR; i++) {
			device = major_info->dasd_device[i];
			if (device && device->devinfo.irq == irq)
				break;
			else
                                device = NULL;
		}
		if (device)
			break;
	}

        if (device &&
            device->level >= DASD_STATE_NEW) {
                s390irq_spin_lock_irqsave (device->devinfo.irq, 
                                           flags);
		DEV_MESSAGE (KERN_DEBUG, device, "%s",
                             "device is accessible again, reenabling it\n");
                device->stopped &= ~DASD_STOPPED_NOT_ACC;
                
                s390irq_spin_unlock_irqrestore(device->devinfo.irq, 
                                               flags);
                dasd_schedule_bh(device);
        } else {

                if (dasd_autodetect) {
                        dasd_add_range (devno, devno, DASD_FEATURE_DEFAULT);
                }
                range.from = devno;
                range.to = devno;
                dasd_enable_ranges (&range, NULL, 0);
        }
 out:
	return rc;
}
#endif				/* CONFIG_DASD_DYNAMIC */

static inline dasd_device_t **
dasd_find_device_addr ( int devno ) 
{
        dasd_device_t **device_addr;

	DBF_EVENT (DBF_INFO, 
                   "devno %04x", 
                   devno);

	if ( dasd_devindex_from_devno (devno) < 0 ) {

                DBF_EXC (DBF_ALERT, 
                         "no dasd: devno %04x",
                         devno);

		return NULL;
	}
        /* allocate major numbers on demand  for new devices */
	while ((device_addr = dasd_device_from_devno (devno)) == NULL) {
                int rc;

		if ((rc = dasd_register_major (NULL)) <= 0) {

                        DBF_EXC (DBF_ALERT, "%s",
                                 "out of major numbers!");
                        break;
		}
	}
        return device_addr;
}

static inline int
dasd_state_del_to_new (dasd_device_t **addr, int devno) 
{
        int i;
        dasd_device_t* device;
        dasd_lowmem_t *lowmem;
        int rc;


        /* allocate device descriptor on demand for new device */
	if (*addr != NULL) { 
                BUG ();
        }

        device = kmalloc (sizeof (dasd_device_t), GFP_ATOMIC);
        if (device == NULL) {
                return -ENOMEM;
        }
        
        memset (device, 0, sizeof (dasd_device_t));
        dasd_plug_device (device);
        INIT_LIST_HEAD (&device->lowmem_pool);

	cmf_device_init(&device->cdev, devno);
       
        /* allocate pages for lowmem pool */
        for (i = 0; i < DASD_LOWMEM_PAGES; i++) {
                
                lowmem = (void *) get_free_page (GFP_ATOMIC|GFP_DMA);
                if (lowmem == NULL) {
                        break;
                }

                list_add (&lowmem->list, &device->lowmem_pool);
        } 

        if (i < DASD_LOWMEM_PAGES) {
                /* didn't get the needed lowmem pages */
                list_for_each_entry (lowmem, &device->lowmem_pool, list) {
                        MESSAGE (KERN_DEBUG,
                                 "<devno: %04x> not enough memory - "
                                 "Free page again :%p",
                                 devno, lowmem);
                        free_page ((unsigned long) lowmem);
                }
                kfree (device);
                rc = -ENOMEM;
        } else {
                *addr = device;
                rc = 0;
        }
        return rc;
}

static inline int
dasd_state_new_to_del (dasd_device_t **addr, int devno)
{
        dasd_lowmem_t *lowmem;

        dasd_device_t *device = *addr;

        /* free private area */
        if (device && device->private) {
                kfree(device->private);
        }

        /* free lowmem_pool */
        list_for_each_entry (lowmem, &device->lowmem_pool, list) {
                free_page ((unsigned long) lowmem);
        }

        /* free device */
        kfree(device);
        *addr = NULL; 
        return 0;
}

static inline int
dasd_state_new_to_known (dasd_device_t **dptr, 
                         int devno, 
                         dasd_discipline_t *discipline) 
{
        int rc = 0;
	umode_t devfs_perm  = S_IFBLK | S_IRUSR | S_IWUSR;
        struct list_head *l;
        major_info_t *major_info, *tmp;
        int i;
        dasd_device_t *device = *dptr;
        devfs_handle_t dir;
        char buffer[5];
        
        major_info = NULL;
	list_for_each (l, &dasd_major_info) {
                tmp = list_entry (l, major_info_t, list);
		for (i = 0; i < DASD_PER_MAJOR; i++) {
			if (tmp->dasd_device[i] == device) {
				device->kdev = MKDEV (tmp->gendisk.major,
                                                      i << DASD_PARTN_BITS);
                                major_info = tmp;
				break;
			}
		}
		if (major_info != NULL) /* we found one */
			break;
	}
        if ( major_info == NULL )
                BUG();

        device->major_info = major_info;
        dasd_device_name (device->name,
                          (((long)dptr -
                            (long)device->major_info->dasd_device) /
                           sizeof (dasd_device_t *)),
                          0, &device->major_info->gendisk);
        init_waitqueue_head (&device->wait_q);
        
        rc = get_dev_info_by_devno (devno, &device->devinfo);
        if ( rc ) {
                /* returns -EUSERS if boxed !!*/
                if (rc == -EUSERS) {
                        device->level = DASD_STATE_BOXED;
                }
                goto out;
        }

	DBF_EVENT (DBF_NOTICE,
                   "got devinfo CU-type %04x and dev-type %04x", 
                   device->devinfo.sid_data.cu_type,
                   device->devinfo.sid_data.dev_type);
        

        if ( devno != device->devinfo.devno )
                BUG();

        device->discipline = dasd_find_disc (device, 
                                             discipline);
        if ( device->discipline == NULL ) {
                rc = -ENODEV;
                goto out;
        }
        sprintf (buffer, "%04x", 
                 device->devinfo.devno);
        dir = devfs_mk_dir (dasd_devfs_handle, buffer, device);
        device->major_info->gendisk.de_arr[MINOR(device->kdev)
                                          >> DASD_PARTN_BITS] = dir;
	if (dasd_features_from_devno(device->devinfo.devno)&DASD_FEATURE_READONLY) {
	        devfs_perm &= ~(S_IWUSR);
	}
        device->devfs_entry = devfs_register (dir,"device",DEVFS_FL_DEFAULT,
                                              MAJOR(device->kdev),
                                              MINOR(device->kdev),
                                              devfs_perm,
                                              &dasd_device_operations,NULL);
        dasd_do_hotplug_event (device, DASD_HOTPLUG_EVENT_ADD);
        device->level = DASD_STATE_KNOWN;
 out:
        return rc;
}

static inline int
dasd_state_known_to_new (dasd_device_t *device ) 
{
        int rc = 0;
        /* don't reset to zeros because of persistent data durich detach/attach! */
        devfs_unregister(device->devfs_entry);
        devfs_unregister(device->major_info->gendisk.de_arr[MINOR(device->kdev) >> DASD_PARTN_BITS]);
        dasd_do_hotplug_event (device, DASD_HOTPLUG_EVENT_REMOVE);
        return rc;
}

static inline int
dasd_state_known_to_accept (dasd_device_t *device) 
{
        int rc = 0;

        /* register 'device' debug area, used for all DBF_DEV_XXX calls*/
        device->debug_area = debug_register (device->name, 
                                             0,     /* size of debug area */
                                             2,     /* number of areas */
                                             8 * sizeof (long));

        debug_register_view (device->debug_area, 
                             &debug_sprintf_view);

        debug_set_level (device->debug_area, 
                         DBF_ERR);

        DBF_DEV_EVENT (DBF_EMERG, device, "%s",
                       "debug area created");
        
        if (device->discipline->int_handler) {
                rc = s390_request_irq_special (device->devinfo.irq,
                                               device->discipline->int_handler,
                                               dasd_not_oper_handler,
                                               SA_DOPATHGROUP, DASD_NAME,
                                               &device->dev_status);
                if ( rc ) {

                        MESSAGE (KERN_DEBUG, "%s",
                                 "No request IRQ");

			if (rc == -EUSERS) {
				/* Device is reserved by someone else. */
				device->level = DASD_STATE_BOXED;
			}

                        goto out;
                }
        }
        device->level = DASD_STATE_ACCEPT;
 out:
        return rc;
}

static inline int
dasd_state_accept_to_known (dasd_device_t *device ) 
{
        if ( device->discipline == NULL )
                goto out;
        if (device->discipline->int_handler) {
                free_irq (device->devinfo.irq, &device->dev_status);
        }

        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->discipline = NULL;
        device->level = DASD_STATE_KNOWN;
 out:
        return 0;
}

static inline int
dasd_state_accept_to_init (dasd_device_t *device) 
{
        int rc = 0;
        unsigned long flags;

        if ( device->discipline->init_analysis ) {
                device->init_cqr=device->discipline->init_analysis (device);
                if ( device->init_cqr != NULL ) {
                        if ( device->discipline->start_IO == NULL )
                                BUG();
                        atomic_inc (&dasd_init_pending);
                        s390irq_spin_lock_irqsave (device->devinfo.irq, 
                                                   flags);
                        rc = device->discipline->start_IO (device->init_cqr);
                        if ( ! rc )
                        	device->level = DASD_STATE_INIT;
                        s390irq_spin_unlock_irqrestore(device->devinfo.irq, 
                                                       flags);
                } else {
                        rc = -ENOMEM;
                }
        } else {
                rc = dasd_state_init_to_ready ( device ); 
        }

        return rc;
}

static inline int
dasd_state_init_to_ready (dasd_device_t *device ) 
{
        int rc = 0;    
        if (device->discipline->do_analysis != NULL)
                if ( device->discipline->do_analysis (device) == 0 ) 
                        rc = dasd_check_bp_block (device);
        
        if ( device->init_cqr ) {
                /* This pointer is no longer needed, BUT dont't free the       */ 
                /* memory, because this is done in bh for finished request!!!! */
                atomic_dec(&dasd_init_pending);
                device->init_cqr = NULL; 
        }
        device->level = DASD_STATE_READY;
        return rc;
}

static inline int
dasd_state_ready_to_accept (dasd_device_t *device ) 
{
        int rc = 0;
        unsigned long flags;

        s390irq_spin_lock_irqsave (device->devinfo.irq, flags);
        if ( device->init_cqr != NULL &&  atomic_read(&dasd_init_pending) != 0 ) {
                if ( device->discipline->term_IO == NULL )
                        BUG();
                device->discipline->term_IO (device->init_cqr);
                atomic_dec (&dasd_init_pending);
                dasd_free_request (device->init_cqr, device);
                device->init_cqr = NULL;
        }
        s390irq_spin_unlock_irqrestore(device->devinfo.irq, flags);
        memset(&device->sizes,0,sizeof(dasd_sizes_t));
        device->level = DASD_STATE_ACCEPT;
        return rc;
}

static inline int
dasd_state_ready_to_online (dasd_device_t *device ) 
{
        int rc = 0;
        if (!(rc = dasd_check_bp_block (device))) {
                dasd_unplug_device (device);
                device->level = DASD_STATE_ONLINE;
        }
        return rc;
}

static inline int
dasd_state_online_to_ready (dasd_device_t *device ) 
{
        int rc = 0;
        dasd_plug_device (device);
        device->level = DASD_STATE_READY;
        return rc;
}

static inline int
dasd_setup_blkdev (dasd_device_t *device ) 
{
        int rc = 0;
        int i;
        int major = MAJOR(device->kdev);
        int minor = MINOR(device->kdev);
        request_queue_t *request_queue;

        for (i = 0; i < (1 << DASD_PARTN_BITS); i++) {
                if (i == 0)
                        device->major_info->gendisk.sizes[minor] =
                                (device->sizes.blocks << device->
                                 sizes.s2b_shift) >> 1;
                else
                        device->major_info->gendisk.sizes[minor + i] = 0;
                hardsect_size[major][minor + i] = device->sizes.bp_block;
                blksize_size[major][minor + i] = device->sizes.bp_block;
                max_sectors[major][minor + i] =
                        device->discipline->max_blocks << 
                        device->sizes.s2b_shift;
		device->major_info->gendisk.part[minor+i].start_sect = 0;
		device->major_info->gendisk.part[minor+i].nr_sects = 0;
        }

        request_queue = kmalloc(sizeof(request_queue_t),GFP_KERNEL);
        if (request_queue) {
                request_queue->queuedata = device;
                blk_init_queue (request_queue, do_dasd_request);
                blk_queue_headactive (request_queue, 1);
                elevator_init (&(request_queue->elevator),ELEVATOR_NOOP);
        }
        device->request_queue = request_queue;
        
        return rc;
}

static void
dasd_deactivate_queue (dasd_device_t *device)
{
        int i;
        int minor = MINOR(device->kdev);

        for (i = 0; i < (1 << DASD_PARTN_BITS); i++) {
                device->major_info->gendisk.sizes[minor + i] = 0;
        }
}

static inline int
dasd_disable_blkdev (dasd_device_t *device ) 
{
        int i;
        int major = MAJOR(device->kdev);
        int minor = MINOR(device->kdev);
	request_queue_t *q   = device->request_queue;
	struct request  *req;
	long             flags;

	spin_lock_irqsave(&io_request_lock, flags);
	while (q                           &&
               !list_empty(&q->queue_head) &&
               (req = dasd_next_request(q)) != NULL) {

		dasd_end_request(req, 0);
		dasd_dequeue_request(q, req);
	}
	spin_unlock_irqrestore(&io_request_lock, flags);

        for (i = 0; i < (1 << DASD_PARTN_BITS); i++) {
                destroy_buffers(MKDEV(major,minor+i));
                device->major_info->gendisk.sizes[minor + i] = 0;
                hardsect_size[major][minor + i] = 0;
                blksize_size[major][minor + i] = 0;
                max_sectors[major][minor + i] = 0;
        }
        if (device->request_queue) {
            blk_cleanup_queue (device->request_queue);
            kfree(device->request_queue);
            device->request_queue = NULL;
        }
        return 0;
}


/*
 * function dasd_setup_partitions
 * calls the function in genhd, which is appropriate to setup a partitioned disk
 */
static inline void
dasd_setup_partitions ( dasd_device_t * device ) 
{
	register_disk (&device->major_info->gendisk,
                       device->kdev,
		       1 << DASD_PARTN_BITS,
		       &dasd_device_operations,
		       (device->sizes.blocks << device->sizes.s2b_shift));
        dasd_do_hotplug_event (device, DASD_HOTPLUG_EVENT_PARTCHK);
}

static inline void
dasd_destroy_partitions ( dasd_device_t * device ) 
{
        int i;
        int minor = MINOR(device->kdev);
        
        for (i = 0; i < (1 << DASD_PARTN_BITS); i++) {
                device->major_info->gendisk.part[minor+i].start_sect = 0;
                device->major_info->gendisk.part[minor+i].nr_sects   = 0;
        }
        devfs_register_partitions(&device->major_info->gendisk,
                                  MINOR(device->kdev),1);
        dasd_do_hotplug_event (device, DASD_HOTPLUG_EVENT_PARTREMOVE);
}

/*
 * function dasd_set_device_level
 */
static int
dasd_set_device_level (unsigned int devno, 
                       dasd_discipline_t * discipline,
                       int to_state)
{
	int rc = 0;
        dasd_device_t **device_addr;
        dasd_device_t *device;
        int from_state;

        device_addr = dasd_find_device_addr ( devno );
        if ( device_addr == NULL ) {
                rc = -ENODEV;
                goto out;
        }
        device = *device_addr;

        if ( device == NULL ) {
                from_state = DASD_STATE_DEL;
                if ( to_state == DASD_STATE_DEL )
                        goto out;
        } else {
                from_state = device->level;
        }

        DBF_EVENT (DBF_INFO,
                   "devno %04x; from %i to %i",
                   devno,
                   from_state,
                   to_state);

        if ( from_state == to_state )
                goto out;

        if ( to_state < from_state )
                goto shutdown;

        /* First check for bringup */
        if ( from_state <= DASD_STATE_DEL &&
             to_state >= DASD_STATE_NEW ) { 
                rc = dasd_state_del_to_new(device_addr, devno);
                if ( rc ) {
                        goto bringup_fail;
                }
                device = *device_addr;
        }

        /* reprobe boxed devices */
        if (device->level == DASD_STATE_BOXED) {
                rc = s390_trigger_resense (device->devinfo.irq);
                if ( rc ) {
                        goto bringup_fail;
                }
        }

        if ( device->level <= DASD_STATE_BOXED &&
             to_state >= DASD_STATE_KNOWN ) { 
                rc = dasd_state_new_to_known( device_addr, devno, discipline );
                if ( rc ) {
                        goto bringup_fail;
                }
        }

        if ( device->level <= DASD_STATE_KNOWN &&
             to_state >= DASD_STATE_ACCEPT ) { 
                rc = dasd_state_known_to_accept(device);
                if ( rc ) {
                        goto bringup_fail;
                }
        }
        if ( dasd_probeonly ) {
            goto out;
        }

        if ( device->level <= DASD_STATE_ACCEPT &&
             to_state >= DASD_STATE_INIT ) { 
                rc = dasd_state_accept_to_init(device);
                if ( rc ) {
                        goto bringup_fail;
                }
        }
        if ( device->level <= DASD_STATE_INIT &&
             to_state >= DASD_STATE_READY ) { 
                rc = -EAGAIN;
                goto out;
        }

        if ( device->level <= DASD_STATE_READY &&
             to_state >= DASD_STATE_ONLINE ) { 
                rc = dasd_state_ready_to_online(device);
                if ( rc ) {
                        goto bringup_fail;
                }
        }
        goto out;
 bringup_fail:   /* revert changes */

        DBF_DEV_EVENT (DBF_ERR, device,
                       "failed to set device from state %d to %d at "
                       "level %d rc %d. Reverting...",
                       from_state,
                       to_state,
                       device->level,
                       rc);

        if (device->level <= DASD_STATE_NEW) {
                /* Revert - device can not be accessed */
                to_state = from_state;
                from_state = device->level;
        }
        
        /* now do a shutdown */
 shutdown: 
        if ( device->level >= DASD_STATE_ONLINE &&
             to_state <= DASD_STATE_READY ) 
                if (dasd_state_online_to_ready(device))
                        BUG();

        if ( device->level >= DASD_STATE_READY &&
             to_state <= DASD_STATE_ACCEPT ) 
                if ( dasd_state_ready_to_accept(device))
                        BUG();

        if ( device->level >= DASD_STATE_ACCEPT &&
             to_state <= DASD_STATE_KNOWN ) 
                if ( dasd_state_accept_to_known(device))
                        BUG();

        if ( device->level >= DASD_STATE_KNOWN &&
             to_state <= DASD_STATE_NEW ) 
                if ( dasd_state_known_to_new(device))
                        BUG();

        if ( device->level >= DASD_STATE_NEW &&
             to_state <= DASD_STATE_DEL) 
                if (dasd_state_new_to_del(device_addr, devno))
                        BUG();
        goto out;
 out:
        return rc;
}

/********************************************************************************
 * SECTION: Procfs stuff 
 ********************************************************************************/
#ifdef CONFIG_PROC_FS

typedef struct {
	char *data;
	int len;
} tempinfo_t;

void
dasd_fill_inode (struct inode *inode, int fill)
{
	if (fill)
		MOD_INC_USE_COUNT;
	else
		MOD_DEC_USE_COUNT;
}

static struct proc_dir_entry *dasd_proc_root_entry = NULL;
static struct proc_dir_entry *dasd_devices_entry;
static struct proc_dir_entry *dasd_statistics_entry;

static int
dasd_devices_open (struct inode *inode, struct file *file)
{
	int rc = 0;
	int size = 1;
	int len = 0;
	major_info_t *temp = NULL;
	struct list_head *l;
	tempinfo_t *info;
	int i;
        unsigned long flags;
        int index = 0;

        MOD_INC_USE_COUNT;

        spin_lock_irqsave(&discipline_lock,
                          flags);

	info = (tempinfo_t *) vmalloc (sizeof (tempinfo_t));

	if (info == NULL) {

                MESSAGE (KERN_WARNING, "%s",
                         "No memory available for data (tempinfo)");

                spin_unlock_irqrestore(&discipline_lock,
                                       flags);

                MOD_DEC_USE_COUNT;

                return -ENOMEM;

	} else {
		file->private_data = (void *) info;
	}
	list_for_each (l, &dasd_major_info) {
                size += 128 * 1 << (MINORBITS - DASD_PARTN_BITS);
	}
	info->data = (char *) vmalloc (size);	

	if (size && info->data == NULL) {

		MESSAGE (KERN_WARNING, "%s",
                         "No memory available for data (info->data)");

		vfree (info);

                spin_unlock_irqrestore(&discipline_lock,
                                       flags);

                MOD_DEC_USE_COUNT;

		return -ENOMEM;
	}

	DBF_EVENT (DBF_NOTICE,
                   "procfs-area: %p, size 0x%x allocated",
                   info->data, 
                   size);

	list_for_each (l, &dasd_major_info) {
		temp = list_entry (l, major_info_t, list);
		for (i = 0; i < 1 << (MINORBITS - DASD_PARTN_BITS); i++) {
			dasd_device_t *device;
                        int devno = dasd_devno_from_devindex(index+i);
                        int features;

                        if ( devno == -ENODEV )
                                continue;

                        features = dasd_features_from_devno(devno);
                        if (features < DASD_FEATURE_DEFAULT)
                                features = DASD_FEATURE_DEFAULT;

                        device = temp->dasd_device[i];
			if (device) {

				len += sprintf (info->data + len,
						"%04x(%s) at (%3d:%3d) is %-7s%4s: ",
						device->devinfo.devno,
						device->discipline ?
						device->
						discipline->name : "none",
						temp->gendisk.major,
						i << DASD_PARTN_BITS,
						device->name,
                                                (features & DASD_FEATURE_READONLY) ? 
                                                "(ro)" : " ");
                                
				switch (device->level) {
				case DASD_STATE_NEW:
                                        len +=
					    sprintf (info->data + len,
						     "new");
                                        break;
				case DASD_STATE_KNOWN:
					len +=
					    sprintf (info->data + len,
						     "detected");
					break;
				case DASD_STATE_BOXED:
					len +=
					    sprintf (info->data + len,
						     "boxed");
					break;
				case DASD_STATE_ACCEPT:
                                        len += sprintf (info->data + len,
                                                        "accepted");
					break;
				case DASD_STATE_INIT:
					len += sprintf (info->data + len,
                                                        "busy   ");
					break;
				case DASD_STATE_READY:
					len += sprintf (info->data + len,
							"ready  ");
					break;
				case DASD_STATE_ONLINE:
                                        len += sprintf (info->data + len,
                                                        "active ");

                                        if (dasd_check_bp_block (device))
                                                len +=
                                                    sprintf (info->data + len,
                                                             "n/f    ");
                                        else
                                                len +=
                                                    sprintf (info->data + len,
                                                             "at blocksize: %d, %ld blocks, %ld MB",
                                                             device->sizes.bp_block,
                                                             device->sizes.blocks,
                                                             ((device->
                                                               sizes.bp_block >> 9) *
                                                              device->sizes.
                                                              blocks) >> 11);
					break;
				default:
					len +=
                                            sprintf (info->data + len,
						     "no stat");
					break;
				}
			} else {
                                char buffer[7];
                                dasd_device_name (buffer, i, 0, &temp->gendisk);
                                if ( devno < 0  ) {
                                        len += sprintf (info->data + len,
                                                        "none");
                                } else {
                                        len += sprintf (info->data + len,
                                                        "%04x",devno);
                                }
                                len += sprintf (info->data + len,
                                                "(none) at (%3d:%3d) is %-7s%4s: unknown",
						temp->gendisk.major,
						i << DASD_PARTN_BITS,
						buffer,
                                                (features & DASD_FEATURE_READONLY) ? 
                                                "(ro)" : " ");
                        }
                        if ( dasd_probeonly )
                            len += sprintf(info->data + len,"(probeonly)");
                        len += sprintf(info->data + len,"\n");
		}
                index += 1 << (MINORBITS - DASD_PARTN_BITS);
	}
	info->len = len;

        spin_unlock_irqrestore(&discipline_lock,
                               flags);
	return rc;
}

#define MIN(a,b) ((a)<(b)?(a):(b))

static ssize_t
dasd_generic_read (struct file *file, char *user_buf, size_t user_len,
		   loff_t * offset)
{
	loff_t len;
	tempinfo_t *p_info = (tempinfo_t *) file->private_data;

	if (*offset >= p_info->len) {
		return 0;	/* EOF */
	} else {
		len = MIN (user_len, (p_info->len - *offset));
		if (copy_to_user (user_buf, &(p_info->data[*offset]), len))
			return -EFAULT;
		(*offset) += len;
		return len;	/* number of bytes "read" */
	}
}

/* 
 * scan for device range in given string (e.g. 0x0150-0x0155).
 * devnos are always hex and leading 0x are ignored.
 */
static char *
dasd_parse_range (char *buffer, dasd_range_t *range)
{
        char *str;

        /* remove optional 'device ' and 'range=' and search for nexet digit */
        for (str = buffer + 4; isspace(*str); str++);

	if (strncmp (str, "device ", 7) == 0) 
		for (str = str + 7; isspace(*str); str++);
        
	if (strncmp (str, "range=", 6) == 0) 
                for (str = str + 6; isspace(*str); str++);
        
        range->to = range->from = dasd_strtoul (str, 
                                                &str, 
                                                &(range->features));
        
        if (*str == '-') {
		str++;
		range->to = dasd_strtoul (str, 
                                          &str, 
                                          &(range->features));
	}
        
        /* remove blanks after device range */
	for (; isspace(*str); str++);

        if (range->from < 0 || range->to < 0) {
                MESSAGE_LOG (KERN_WARNING,
                             "/proc/dasd/devices: range parse error in '%s'", 
                             buffer);
                return ERR_PTR (-EINVAL);
        }

        return str;

} /* end dasd_parse_range */

/* 
 * Enable / Disable the given devices  
 */
static void
dasd_proc_set (char *buffer)
{
	dasd_range_t range;
        char *str;

        str = dasd_parse_range (buffer, 
                                &range);
        
        /* Negative numbers in str/from/to indicate errors */
        if (IS_ERR (str) || (range.from < 0) || (range.to < 0)
            || (range.from > 0xFFFF) || (range.to > 0xFFFF)) 
                return;

        if (strncmp (str, "on", 2) == 0) {
                dasd_enable_ranges (&range, NULL, 0);

        } else if (strncmp (str, "off", 3) == 0) {
                dasd_disable_ranges (&range, NULL, 0, 1);
                
        } else {
                MESSAGE_LOG (KERN_WARNING,
                             "/proc/dasd/devices: "
                             "only 'on' and 'off' are alowed in 'set' "
                             "command ('%s'/'%s')",
                             buffer,
                             str);
        }

        return;

} /* end dasd_proc_set */

/* 
 * Add the given devices
 */
static void
dasd_proc_add (char *buffer)
{
	dasd_range_t range;
        char *str;
        
        str = dasd_parse_range (buffer, 
                                &range);
        
        /* Negative numbers in str/from/to indicate errors */
        if (IS_ERR (str) || (range.from < 0) || (range.to < 0)
            || (range.from > 0xFFFF) || (range.to > 0xFFFF))
                return;

        dasd_add_range (range.from, range.to, range.features);
        dasd_enable_ranges (&range, NULL, 0);

        return;

} /* end dasd_proc_add */

/* 
 * Break the lock of a given 'boxed' dasd.
 * If the dasd in not in status 'boxed' just return.  
 */
static int
dasd_break_boxed (dasd_range_t  *range,
                  dasd_device_t *device)
{
        int rc = 0;
        dasd_discipline_t *discipline;
        struct list_head  *lh = dasd_disc_head.next;
                
        /* check devixe status */
        if (device->level != DASD_STATE_BOXED) {
                MESSAGE (KERN_WARNING,
                         "/proc/dasd/devices: the given device (%04X) "
                         "is not 'boxed')",
                         device->devinfo.devno);
                rc = -EINVAL;
                goto out; 
        }

        /* force eckd discipline */
        do {
                discipline = list_entry(lh,
                                        dasd_discipline_t,
                                        list);
                
                if (strncmp (discipline->name, range->discipline, 4) == 0)
                        break;  /* discipline found */
                
                lh = lh->next;    /* check next discipline in list */
                if (lh == &dasd_disc_head) {
                        discipline = NULL;
                        break;
                }
        } while ( 1 );
        device->discipline = discipline;

        if (device->discipline == NULL) {
                MESSAGE (KERN_WARNING, "%s",
                         "/proc/dasd/devices: discipline not found "
                         "in discipline list");
                rc = -EINVAL;
                goto out;
        }

        /* register the int handler to enable IO */
        rc = s390_request_irq_special (device->devinfo.irq,
                                       device->discipline->int_handler,
                                       dasd_not_oper_handler,
                                       SA_DOPATHGROUP | SA_FORCE, 
                                       DASD_NAME,
                                       &device->dev_status);
        if ( rc ) 
                goto out;

	rc = dasd_steal_lock (device);

        /* unregister the int handler to enable re-sensing */
        free_irq (device->devinfo.irq, 
                  &device->dev_status);
        
        device->discipline = NULL;
        device->level      = DASD_STATE_NEW;

 out:
        return rc;

} /* end dasd_break_boxed */

/* 
 * Handle the procfs call 'brk <devno> <discipline>.
 */
static void
dasd_proc_brk (char *buffer)
{
	char *str;
	dasd_range_t range;
        dasd_device_t *device;
        int rc = 0;

        str = dasd_parse_range (buffer,
                                 &range);

        if (IS_ERR (str))
                return;

        if (range.from != range.to) {
                MESSAGE (KERN_WARNING, "%s",
                         "/proc/dasd/devices: 'brk <devno> <discipline> "
                         "is only allowed for a single device (no ranges)");
                return;
        }

        /* check for discipline = 'eckd' */
        if (strncmp(str, "eckd", 4) != 0) {
                MESSAGE_LOG (KERN_WARNING,
                             "/proc/dasd/devices: 'brk <devno> <discipline> "
                             "is only allowed for 'eckd' (%s)",
                             str);
                return;
        }
        
        memcpy (range.discipline, "ECKD", 4);

	device = *(dasd_device_from_devno (range.from));
        if (device == NULL) {
                MESSAGE (KERN_WARNING,
                         "/proc/dasd/devices: no device found for devno (%04X)",
                         range.from);
                return;
        }
        
        rc = dasd_break_boxed (&range, 
                               device);
        if (rc == 0) {
                /* trigger CIO to resense the device */
                s390_trigger_resense (device->devinfo.irq);
                
                // get the device online now
                dasd_enable_ranges (&range, 
                                    NULL, 
                                    0);
        }
        
} /* end dasd_proc_brk */

static ssize_t
dasd_devices_write (struct file *file, const char *user_buf,
		    size_t user_len, loff_t * offset)
{
	char *buffer;

	if (user_len > PAGE_SIZE)
		return -EINVAL;

	buffer = vmalloc (user_len+1);
	if (buffer == NULL)
		return -ENOMEM;

	if (copy_from_user (buffer, user_buf, user_len)) {
		vfree (buffer);
		return -EFAULT;
	}

        /* replace LF with '\0' */
        if (buffer[user_len -1] == '\n') {
                buffer[user_len -1] = '\0';
        } else {
                buffer[user_len] = '\0';
        }

	MESSAGE_LOG (KERN_INFO,
                     "/proc/dasd/devices: '%s'", 
                     buffer);
        
	if (strncmp (buffer, "set ", 4) == 0) {
                /* handle 'set <devno> on/off' */
                dasd_proc_set (buffer);

        } else if (strncmp (buffer, "add ", 4) == 0) {
                /* handle 'add <devno>' */
                dasd_proc_add (buffer);

        } else if (strncmp (buffer, "brk ", 4) == 0) {
                /* handle 'brk <devno> <discipline>' */
                dasd_proc_brk (buffer);

        } else {
		MESSAGE (KERN_WARNING, "%s",
                         "/proc/dasd/devices: only 'set' ,'add' and "
                         "'brk' are supported verbs");
		vfree (buffer);
		return -EINVAL;
	}

	vfree (buffer);
	return user_len;
}

static int
dasd_devices_close (struct inode *inode, struct file *file)
{
	int rc = 0;
	tempinfo_t *p_info = (tempinfo_t *) file->private_data;
	if (p_info) {
		if (p_info->data)
			vfree (p_info->data);
		vfree (p_info);
	}
	MOD_DEC_USE_COUNT;
	return rc;
}

static struct file_operations dasd_devices_file_ops = {
	read:dasd_generic_read,	        /* read */
	write:dasd_devices_write,	/* write */
	open:dasd_devices_open,	        /* open */
	release:dasd_devices_close,	/* close */
};

static struct inode_operations dasd_devices_inode_ops = {
};

static int
dasd_statistics_open (struct inode *inode, struct file *file)
{
	int rc = 0;
	int len = 0;
	tempinfo_t *info;
	int shift, i, help = 0;

        MOD_INC_USE_COUNT;
	info = (tempinfo_t *) vmalloc (sizeof (tempinfo_t));

	if (info == NULL) {
 
		MESSAGE (KERN_WARNING, "%s",
                         "No memory available for data (tempinfo)");

                MOD_DEC_USE_COUNT;
		return -ENOMEM;
	} else {
		file->private_data = (void *) info;
	}
        /* FIXME! determine space needed in a better way */
	info->data = (char *) vmalloc (PAGE_SIZE);	

	if (info->data == NULL) {

		MESSAGE (KERN_WARNING, "%s",
                         "No memory available for data (info->data)");

		vfree (info);
		file->private_data = NULL;
                MOD_DEC_USE_COUNT;
		return -ENOMEM;
	}
        
        /* check for active profiling */
        if (dasd_profile_level == DASD_PROFILE_OFF) {

                info->len = sprintf (info->data, 
                                     "Statistics are off - they might be "
                                     "switched on using 'echo set on > "
                                     "/proc/dasd/statistics'\n");
                return rc;
        } 

        /* prevent couter 'ouverflow' on output */
	for (shift = 0, help = dasd_global_profile.dasd_io_reqs;
	     help > 9999999; help = help >> 1, shift++) ;

	len = sprintf (info->data, "%d dasd I/O requests\n",
                       dasd_global_profile.dasd_io_reqs);
	len += sprintf (info->data + len, "with %d sectors(512B each)\n",
                        dasd_global_profile.dasd_io_sects);

	len += sprintf (info->data + len,
                        "   __<4    ___8    __16    __32    __64 "
                        "   _128    _256    _512    __1k    __2k "
                        "   __4k    __8k    _16k    _32k    _64k "
                        "   128k\n");

	len += sprintf (info->data + len,
                        "   _256    _512    __1M    __2M    __4M "
                        "   __8M    _16M    _32M    _64M    128M "
                        "   256M    512M    __1G    __2G    __4G "
                        "   _>4G\n");

	len += sprintf (info->data + len, "Histogram of sizes (512B secs)\n");
	for (i = 0; i < 16; i++) {
		len += sprintf (info->data + len, "%7d ",
                                dasd_global_profile.dasd_io_secs[i] >> shift);
	}
	len += sprintf (info->data + len, "\n");

	for (; i < 32; i++) {
		len += sprintf (info->data + len, "%7d ",
                                dasd_global_profile.dasd_io_secs[i] >> shift);
	}
	len += sprintf (info->data + len, "\n");

	len += sprintf (info->data + len, 
                        "Histogram of I/O times (microseconds)\n");

	for (i = 0; i < 16; i++) {
		len += sprintf (info->data + len, "%7d ",
                                dasd_global_profile.dasd_io_times[i] >> shift);
	}
	len += sprintf (info->data + len, "\n");
	for (; i < 32; i++) {
		len += sprintf (info->data + len, "%7d ",
                                dasd_global_profile.dasd_io_times[i] >> shift);
	}
	len += sprintf (info->data + len, "\n");

	len += sprintf (info->data + len, "Histogram of I/O times per sector\n");
	for (i = 0; i < 16; i++) {
		len += sprintf (info->data + len, "%7d ",
                                dasd_global_profile.dasd_io_timps[i] >> shift);
	}
	len += sprintf (info->data + len, "\n");
	for (; i < 32; i++) {
		len += sprintf (info->data + len, "%7d ",
                                dasd_global_profile.dasd_io_timps[i] >> shift);
	}
	len += sprintf (info->data + len, "\n");

	len += sprintf (info->data + len, "Histogram of I/O time till ssch\n");
	for (i = 0; i < 16; i++) {
		len += sprintf (info->data + len, "%7d ",
                                dasd_global_profile.dasd_io_time1[i] >> shift);
	}
	len += sprintf (info->data + len, "\n");
	for (; i < 32; i++) {
		len += sprintf (info->data + len, "%7d ",
                                dasd_global_profile.dasd_io_time1[i] >> shift);
	}
	len += sprintf (info->data + len, "\n");

	len += sprintf (info->data + len,
                        "Histogram of I/O time between ssch and irq\n");
	for (i = 0; i < 16; i++) {
		len += sprintf (info->data + len, "%7d ",
                                dasd_global_profile.dasd_io_time2[i] >> shift);
	}
	len += sprintf (info->data + len, "\n");
	for (; i < 32; i++) {
		len += sprintf (info->data + len, "%7d ",
                                dasd_global_profile.dasd_io_time2[i] >> shift);
	}
	len += sprintf (info->data + len, "\n");

	len += sprintf (info->data + len,
                        "Histogram of I/O time between ssch and irq per "
                        "sector\n");

	for (i = 0; i < 16; i++) {
		len += sprintf (info->data + len, "%7d ",
                                dasd_global_profile.dasd_io_time2ps[i] >> shift);
	}
	len += sprintf (info->data + len, "\n");
	for (; i < 32; i++) {
		len += sprintf (info->data + len, "%7d ",
                                dasd_global_profile.dasd_io_time2ps[i] >> shift);
	}
	len += sprintf (info->data + len, "\n");

	len += sprintf (info->data + len,
                        "Histogram of I/O time between irq and end\n");
	for (i = 0; i < 16; i++) {
		len +=
		    sprintf (info->data + len, "%7d ",
			     dasd_global_profile.dasd_io_time3[i] >> shift);
	}
	len += sprintf (info->data + len, "\n");
	for (; i < 32; i++) {
		len += sprintf (info->data + len, "%7d ",
                                dasd_global_profile.dasd_io_time3[i] >> shift);
	}
	len += sprintf (info->data + len, "\n");

	len += sprintf (info->data + len,
                        "# of req in chanq at enqueuing (1..32) \n");
	for (i = 0; i < 16; i++) {
		len += sprintf (info->data + len, "%7d ",
                                dasd_global_profile.dasd_io_nr_req[i] >> shift);
	}
	len += sprintf (info->data + len, "\n");
	for (; i < 32; i++) {
		len += sprintf (info->data + len, "%7d ",
                                dasd_global_profile.dasd_io_nr_req[i] >> shift);
	}
	len += sprintf (info->data + len, "\n");

	info->len = len;
	return rc;
}

static ssize_t
dasd_statistics_write (struct file *file, const char *user_buf,
		       size_t user_len, loff_t * offset)
{
	char *buffer;
	
	if(user_len > 65536)
		user_len = 65536;
		
	buffer = vmalloc (user_len);

	if (buffer == NULL)
		return -ENOMEM;

	if (copy_from_user (buffer, user_buf, user_len)) {
		vfree (buffer);
		return -EFAULT;
	}

	buffer[user_len] = 0;
        
	MESSAGE (KERN_INFO,
                 "/proc/dasd/statictics: '%s'",
                 buffer);

#ifdef DASD_PROFILE
         /* check for valid verbs */
	if (strncmp (buffer, "reset", 5) && 
            strncmp (buffer, "set ",  4)   ) {

		MESSAGE (KERN_WARNING, "%s",
                         "/proc/dasd/statistics: only 'set' and "
                         "'reset' are supported verbs");

		vfree (buffer);
        	return -EINVAL;
	}

	if (!strncmp (buffer, "reset", 5)) {
                
                /* reset the statistics */
		memset (&dasd_global_profile, 
                        0, 
                        sizeof (dasd_profile_info_t));
                
                MESSAGE (KERN_INFO, "%s",
                         "Statictics reset");

	} else {

                /* 'set xxx' was given */
                int offset = 4;

                while (buffer[offset] && !isalnum (buffer[offset]))
                        offset++;

                if (!strncmp (buffer + offset, "on", 2)) {
                        
                        /* switch on statistics profiling */
                        dasd_profile_level = DASD_PROFILE_ON;
                        
                        MESSAGE (KERN_INFO, "%s",
                                 "Statictics switched on");
                        
                } else if (!strncmp (buffer + offset, "off", 3)) {
                        
                        /* switch off and reset statistics profiling */
                        memset (&dasd_global_profile, 
                                0, 
                                sizeof (dasd_profile_info_t));
                        
                        dasd_profile_level = DASD_PROFILE_OFF;
                        
                        MESSAGE (KERN_INFO, "%s",
                                 "Statictics switched off");

                } else {

                        MESSAGE (KERN_WARNING, "%s",
                                 "/proc/dasd/statistics: only 'set on' and "
                                 "'set off' are supported verbs");
                } 
        } 
#else
        MESSAGE (KERN_WARNING, "%s",
                 "/proc/dasd/statistics: is not activated in this "
                 "kernel");
        

#endif /* DASD_PROFILE */
        vfree (buffer);
	return user_len;
}

static struct file_operations dasd_statistics_file_ops = {
	read:dasd_generic_read,	        /* read */
	write:dasd_statistics_write,	/* write */
	open:dasd_statistics_open,	/* open */
	release:dasd_devices_close,	/* close */
};

static struct inode_operations dasd_statistics_inode_ops = {
};

int
dasd_proc_init (void)
{
	int rc = 0;
	dasd_proc_root_entry = proc_mkdir ("dasd", &proc_root);
	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->proc_iops = &dasd_devices_inode_ops;
	dasd_statistics_entry = create_proc_entry ("statistics",
						   S_IFREG | S_IRUGO | S_IWUSR,
						   dasd_proc_root_entry);
	dasd_statistics_entry->proc_fops = &dasd_statistics_file_ops;
	dasd_statistics_entry->proc_iops = &dasd_statistics_inode_ops;
	return rc;
}

void
dasd_proc_cleanup (void)
{
	remove_proc_entry ("devices", dasd_proc_root_entry);
	remove_proc_entry ("statistics", dasd_proc_root_entry);
	remove_proc_entry ("dasd", &proc_root);
}

#endif /* CONFIG_PROC_FS */

/********************************************************************************
 * SECTION: Initializing the driver 
 ********************************************************************************/
int
dasd_request_module ( void *name ) {
	int rc = -ERESTARTSYS;
    	strcpy(current->comm, name);
   	daemonize();
   	while ( current->fs->root == NULL ) { /* wait for root-FS */
        	DECLARE_WAIT_QUEUE_HEAD(wait);
        	sleep_on_timeout(&wait,HZ); /* wait in steps of one second */
	} 
	while ( (rc=request_module(name)) != 0 ) {
        	DECLARE_WAIT_QUEUE_HEAD(wait);

		MESSAGE_LOG (KERN_INFO,
                             "request_module returned %d for %s",
                             rc,
                             (char*)name);

        	sleep_on_timeout(&wait,5* HZ); /* wait in steps of 5 seconds */
    	}
    	return rc;
}

int __init
dasd_init (void)
{
	int rc = 0;
	int irq;
	major_info_t *major_info = NULL;
	struct list_head *l;

	MESSAGE (KERN_INFO, "%s",
                 "initializing...");

	init_waitqueue_head (&dasd_init_waitq);

        /* register 'common' DASD debug area, used faor all DBF_XXX calls*/
	dasd_debug_area = debug_register (DASD_NAME, 
                                          0,     /* size of debug area */
                                          2,     /* number of areas */
                                          8 * sizeof (long));

	debug_register_view (dasd_debug_area, 
                             &debug_sprintf_view);

	if (dasd_debug_area == NULL) {
		goto failed;
	}

        debug_set_level (dasd_debug_area, 
                         DBF_ERR);

        DBF_EVENT (DBF_EMERG, "%s", 
                   "debug area created");

	dasd_devfs_handle = devfs_mk_dir (NULL, 
                                          DASD_NAME, 
                                          NULL);

	if (dasd_devfs_handle < 0) {

		DBF_EVENT (DBF_ALERT, "%s", 
                           "no devfs");
		goto failed;
	}

        list_add_tail(&dasd_major_static.list, &dasd_major_info);
	list_for_each (l, &dasd_major_info) {
		major_info = list_entry (l, major_info_t, list);
		if ((rc = dasd_register_major (major_info)) > 0) {

			MESSAGE (KERN_INFO,
                                 "Registered successfully to major no %u",
                                 major_info->gendisk.major);
		} else {

			MESSAGE (KERN_WARNING,
                                 "Couldn't register successfully to "
                                 "major no %d",
                                 major_info->gendisk.major);

			/* revert registration of major infos */
			goto failed;
		}
	}
#ifndef MODULE
	dasd_split_parm_string (dasd_parm_string);
#endif				/* ! MODULE */
	rc = dasd_parse (dasd);
	if (rc) {

		DBF_EVENT (DBF_ALERT, "%s",
                           "invalid range found");
		goto failed;
	}

#ifdef CONFIG_PROC_FS
	rc = dasd_proc_init ();
	if (rc) {
                
		DBF_EVENT (DBF_ALERT, "%s", 
                           "no proc-FS");
		goto failed;
	}
#endif /* CONFIG_PROC_FS */

	genhd_dasd_name = dasd_device_name;
        genhd_dasd_ioctl = dasd_ioctl;

	if (dasd_autodetect) {	/* update device range to all devices */
		for (irq = get_irq_first (); irq != -ENODEV;
		     irq = get_irq_next (irq)) {
			int devno = get_devno_by_irq (irq);
			int index = dasd_devindex_from_devno (devno);
			if (index < 0) {	/* not included in ranges */

				DBF_EVENT (DBF_CRIT,
                                           "add %04x to range",
                                           devno);

				dasd_add_range (devno, devno, 
                                                DASD_FEATURE_DEFAULT);
			}
		}
	}

	if (MACHINE_IS_VM) {
#ifdef CONFIG_DASD_DIAG
		rc = dasd_diag_init ();
		if (rc == 0) {

			MESSAGE (KERN_INFO, "%s",
                                 "Registered DIAG discipline successfully");

		} else {

			DBF_EVENT (DBF_ALERT, "%s",
                                   "Register DIAG discipline failed");

			goto failed;
		}
#endif /* CONFIG_DASD_DIAG */
#if defined(CONFIG_DASD_DIAG_MODULE) && defined(CONFIG_DASD_AUTO_DIAG)
                kernel_thread(dasd_request_module,"dasd_diag_mod",SIGCHLD);
#endif /* CONFIG_DASD_AUTO_DIAG */
	}
#ifdef CONFIG_DASD_ECKD
	rc = dasd_eckd_init ();
	if (rc == 0) {

		MESSAGE (KERN_INFO, "%s",
                         "Registered ECKD discipline successfully");
	} else {

		DBF_EVENT (DBF_ALERT, "%s",
                           "Register ECKD discipline failed");

		goto failed;
	}
#endif /* CONFIG_DASD_ECKD */
#if defined(CONFIG_DASD_ECKD_MODULE) && defined(CONFIG_DASD_AUTO_ECKD)
        kernel_thread(dasd_request_module,"dasd_eckd_mod",SIGCHLD);
#endif /* CONFIG_DASD_AUTO_ECKD */
#ifdef CONFIG_DASD_FBA
	rc = dasd_fba_init ();
	if (rc == 0) {
                
		MESSAGE (KERN_INFO, "%s",
			"Registered FBA discipline successfully");
	} else {
                
		DBF_EVENT (DBF_ALERT, "%s",
                           "Register FBA discipline failed");

		goto failed;
	}
#endif /* CONFIG_DASD_FBA */
#if defined(CONFIG_DASD_FBA_MODULE) && defined(CONFIG_DASD_AUTO_FBA)
        kernel_thread(dasd_request_module,"dasd_fba_mod",SIGCHLD);
#endif /* CONFIG_DASD_AUTO_FBA */
        {
                char **disc=dasd_disciplines;
                while (*disc) {
                        kernel_thread(dasd_request_module,*disc,SIGCHLD);
                        disc++;
                }
        }

	rc = 0;
	goto out;
      failed:

	MESSAGE (KERN_INFO, "%s",
                 "initialization not performed due to errors");

	cleanup_dasd ();
      out:
        
	MESSAGE (KERN_INFO, "%s",
                 "initialization finished");

	return rc;
}

static void
cleanup_dasd (void)
{
	int i,rc=0;
	major_info_t *major_info = NULL;
	struct list_head *l,*n;
	dasd_range_t *range;

	MESSAGE (KERN_INFO, "%s",
                "shutting down");

	dasd_disable_ranges (&dasd_range_head, 
                             NULL, 1, 1);

#ifdef CONFIG_DASD_DIAG
        if (MACHINE_IS_VM) {
                dasd_diag_cleanup ();

                MESSAGE (KERN_INFO, "%s",
                         "De-Registered DIAG discipline successfully");
	}
#endif /* CONFIG_DASD_DIAG */

#ifdef CONFIG_DASD_FBA
	dasd_fba_cleanup ();

	MESSAGE (KERN_INFO, "%s",
		"De-Registered FBA discipline successfully");
#endif /* CONFIG_DASD_FBA */

#ifdef CONFIG_DASD_ECKD
	dasd_eckd_cleanup ();

	MESSAGE (KERN_INFO, "%s",
                 "De-Registered ECKD discipline successfully");
#endif /* CONFIG_DASD_ECKD */

        genhd_dasd_name = NULL;
        genhd_dasd_ioctl = NULL;

#ifdef CONFIG_PROC_FS
	dasd_proc_cleanup ();
#endif /* CONFIG_PROC_FS */
        
	list_for_each_safe (l, n, &dasd_major_info) {
		major_info = list_entry (l, major_info_t, list);
		for (i = 0; i < DASD_PER_MAJOR; i++) {
			kfree (major_info->dasd_device[i]);
		}
		if ((major_info->flags & DASD_MAJOR_INFO_REGISTERED) &&
		    (rc = dasd_unregister_major (major_info)) == 0) {

			MESSAGE (KERN_INFO, 
				"Unregistered successfully from major no %u",
				major_info->gendisk.major);
		} else {

			MESSAGE (KERN_WARNING,
                                 "Couldn't unregister successfully from major "
                                 "no %d rc = %d",
                                 major_info->gendisk.major, 
                                 rc);
  		}
  	}
	list_for_each_safe (l, n, &dasd_range_head.list) {
		range = list_entry (l, dasd_range_t, list);
                dasd_remove_range(range);
        }

#ifndef MODULE
        for( i = 0; i < 256; i++ )
                if ( dasd[i] ) {
                        kfree(dasd[i]);
                        dasd[i] = NULL;
                }
#endif /* MODULE */
        if (dasd_devfs_handle) 
                devfs_unregister(dasd_devfs_handle);

        if (dasd_debug_area != NULL ) {
                debug_unregister(dasd_debug_area);
                dasd_debug_area = NULL;
        }

	MESSAGE (KERN_INFO, "%s",
                 "shutdown completed");
}

#ifdef MODULE
int
init_module (void)
{
	int rc = 0;
	rc = dasd_init ();
	return rc;
}

void
cleanup_module (void)
{
	cleanup_dasd ();
	return;
}
#endif

/*
 * 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: nil
 * tab-width: 8
 * End:
 */
