/*
 *  drivers/s390/s390io.c
 *   S/390 common I/O routines
 *   $Revision: 1.247.4.4 $
 *
 *  S390 version
 *    Copyright (C) 1999, 2000 IBM Deutschland Entwicklung GmbH,
 *                             IBM Corporation
 *    Author(s): Ingo Adlung (adlung@de.ibm.com)
 *               Cornelia Huck (cohuck@de.ibm.com) 
 *    ChangeLog: 01/07/2001 Blacklist cleanup (djbarrow@de.ibm.com,barrow_dj@yahoo.com)
 *               01/04/2001 Holger Smolinski (smolinsk@de.ibm.com)
 *                          Fixed lost interrupts and do_adapter_IO
 *               xx/xx/xxxx nnn          multiple changes not reflected
 *               03/12/2001 Ingo Adlung  blacklist= - changed to cio_ignore=  
 *               03/14/2001 Ingo Adlung  disable interrupts before start_IO
 *                                        in Path Group processing 
 *                                       decrease retry2 on busy while 
 *                                        disabling sync_isc; reset isc_cnt
 *                                        on io error during sync_isc enablement
 *               05/09/2001 Cornelia Huck added exploitation of debug feature
 *               05/16/2001 Cornelia Huck added /proc/deviceinfo/<devno>/
 *               05/22/2001 Cornelia Huck added /proc/cio_ignore
 *                                        un-ignore blacklisted devices by piping 
 *                                        to /proc/cio_ignore
 *               xx/xx/xxxx some bugfixes & cleanups
 *               08/02/2001 Cornelia Huck not already known devices can be blacklisted
 *                                        by piping to /proc/cio_ignore
 *               09/xx/2001 couple more fixes
 *               10/15/2001 Cornelia Huck xsch - internal only for now
 *               10/29/2001 Cornelia Huck Blacklisting reworked again
 *               10/29/2001 Cornelia Huck improved utilization of debug feature
 *               10/29/2001 Cornelia Huck more work on cancel_IO - use the flag
 *                                        DOIO_CANCEL_ON_TIMEOUT in do_IO to get
 *                                        io cancelled
 *               11/15/2001 Cornelia Huck proper behaviour with procfs off
 *               12/10/2001 Cornelia Huck added private_data + functions to 
 *                                        ioinfo_t
 *               11-12/2001 Cornelia Huck various cleanups
 *               01/09/2002 Cornelia Huck PGID fixes
 *                                        process css machine checks 
 *               01/10/2002 Cornelia Huck added /proc/chpids
 *               04/10/2002 Cornelia Huck fixed reaction on css machine checks
 *               04/23/2002 Cornelia Huck fixed console isc (un)setting
 *               06/06/2002 Cornelia Huck added detection of locked devices
 */

#include <linux/module.h>
#include <linux/config.h>
#include <linux/errno.h>
#include <linux/kernel_stat.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/smp.h>
#include <linux/threads.h>
#include <linux/smp_lock.h>
#include <linux/init.h>
#include <linux/bootmem.h>
#include <linux/ctype.h>
#ifdef CONFIG_PROC_FS
#include <linux/proc_fs.h>
#endif
#include <asm/system.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/bitops.h>
#include <asm/smp.h>
#include <asm/pgtable.h>
#include <asm/delay.h>
#include <asm/processor.h>
#include <asm/lowcore.h>
#include <asm/idals.h>
#include <asm/uaccess.h>
#include <asm/cpcmd.h>

#include <asm/s390io.h>
#include <asm/s390dyn.h>
#include <asm/s390mach.h>
#include <asm/debug.h>
#include <asm/queue.h>

#ifndef TRUE
#define TRUE  1
#define FALSE 0
#endif

#define SANITY_CHECK(irq) do { \
if (irq > highest_subchannel || irq < 0) \
		return (-ENODEV); \
	if (ioinfo[irq] == INVALID_STORAGE_AREA) \
		return (-ENODEV); \
        if (ioinfo[irq]->st) \
                return -ENODEV; \
	} while(0)

#define CIO_TRACE_EVENT(imp, txt) do { \
	if (cio_debug_initialized) \
		debug_text_event(cio_debug_trace_id, \
				 imp, \
				 txt); \
        }while (0)

#define CIO_MSG_EVENT(imp, args...) do { \
        if (cio_debug_initialized) \
                debug_sprintf_event(cio_debug_msg_id, \
                                    imp, \
                                    ##args); \
        } while (0)

#define CIO_CRW_EVENT(imp, args...) do { \
        if (cio_debug_initialized) \
                debug_sprintf_event(cio_debug_crw_id, \
                                    imp, \
                                    ##args); \
        } while (0)

#define CIO_HEX_EVENT(imp, args...) do { \
	if (cio_debug_initialized) \
                debug_event(cio_debug_trace_id, imp, ##args); \
        } while (0)

#undef  CONFIG_DEBUG_IO
#define CONFIG_DEBUG_CRW
#define CONFIG_DEBUG_CHSC

unsigned int highest_subchannel;
ioinfo_t *ioinfo_head = NULL;
ioinfo_t *ioinfo_tail = NULL;
ioinfo_t *ioinfo[__MAX_SUBCHANNELS] = {
	[0 ... (__MAX_SUBCHANNELS - 1)] = INVALID_STORAGE_AREA
};

#ifdef CONFIG_CHSC
__u64 chpids[4] = {0,0,0,0};
__u64 chpids_logical[4] = {-1,-1,-1,-1};
__u64 chpids_known[4] = {0,0,0,0};
#endif /* CONFIG_CHSC */

static atomic_t sync_isc = ATOMIC_INIT (-1);
static int sync_isc_cnt = 0;	/* synchronous irq processing lock */

static spinlock_t adapter_lock = SPIN_LOCK_UNLOCKED;	/* adapter interrupt lock */
static int cons_dev = -1;	/* identify console device */
static int init_IRQ_complete = 0;
static int cio_show_msg = 0;
static schib_t *p_init_schib = NULL;
static irb_t *p_init_irb = NULL;
static __u64 irq_IPL_TOD;
static adapter_int_handler_t adapter_handler = NULL;
static pgid_t * global_pgid;

/* for use of debug feature */
debug_info_t *cio_debug_msg_id = NULL;
debug_info_t *cio_debug_trace_id = NULL;
debug_info_t *cio_debug_crw_id = NULL;
int cio_debug_initialized = 0;

#ifdef CONFIG_CHSC
int cio_chsc_desc_avail = 0;
int cio_chsc_err_msg = 0;
#endif

static void init_IRQ_handler (int irq, void *dev_id, struct pt_regs *regs);
static void s390_process_subchannels (void);
static void s390_device_recognition_all (void);
static void s390_device_recognition_irq (int irq);
#ifdef CONFIG_PROC_FS
static void s390_redo_validation (void);
#endif
static int s390_validate_subchannel (int irq, int enable);
static int s390_SenseID (int irq, senseid_t * sid, __u8 lpm);
static int s390_SetPGID (int irq, __u8 lpm);
static int s390_SensePGID (int irq, __u8 lpm, pgid_t * pgid);
static int s390_process_IRQ (unsigned int irq);
static int enable_subchannel (unsigned int irq);
static int disable_subchannel (unsigned int irq);
int cancel_IO (int irq);
int s390_start_IO (int irq, ccw1_t * cpa, unsigned long user_intparm,
		   __u8 lpm, unsigned long flag);

#ifdef CONFIG_PROC_FS
static int chan_proc_init (void);
#endif

static inline void do_adapter_IO (__u32 intparm);

static void s390_schedule_path_verification(unsigned long irq);
int s390_DevicePathVerification (int irq, __u8 domask);
int s390_register_adapter_interrupt (adapter_int_handler_t handler);
int s390_unregister_adapter_interrupt (adapter_int_handler_t handler);

extern int do_none (unsigned int irq, int cpu, struct pt_regs *regs);
extern int enable_none (unsigned int irq);
extern int disable_none (unsigned int irq);

asmlinkage void do_IRQ (struct pt_regs regs);

#ifdef CONFIG_CHSC
static chsc_area_t *chsc_area_ssd = NULL;
static chsc_area_t *chsc_area_sei = NULL;
static spinlock_t chsc_lock_ssd = SPIN_LOCK_UNLOCKED;
static spinlock_t chsc_lock_sei = SPIN_LOCK_UNLOCKED;
static int chsc_get_sch_descriptions( void );
int s390_vary_chpid( __u8 chpid, int on );
#endif

#ifdef CONFIG_PROC_FS
#define MAX_CIO_PROCFS_ENTRIES 0x300
/* magic number; we want to have some room to spare */

int cio_procfs_device_create (int devno);
int cio_procfs_device_remove (int devno);
int cio_procfs_device_purge (void);
#endif

int cio_notoper_msg = 1;

#ifdef CONFIG_PROC_FS
int cio_proc_devinfo = 0;	/* switch off the /proc/deviceinfo/ stuff by default
				   until problems are dealt with */
#endif

unsigned long s390_irq_count[NR_CPUS];	/* trace how many irqs have occured per cpu... */
int cio_count_irqs = 1;		/* toggle use here... */

int cio_sid_with_pgid = 0;     /* if we need a PGID for SenseID, switch this on */

/* 
 * "Blacklisting" of certain devices:
 * Device numbers given in the commandline as cio_ignore=... won't be known to Linux
 * These can be single devices or ranges of devices
 * 
 * 10/23/01 reworked to get rid of lists
 */

static u32 bl_dev[2048];

static spinlock_t blacklist_lock = SPIN_LOCK_UNLOCKED;
static int highest_ignored = 0;
static int nr_ignored = 0;

/* 
 * Function: blacklist_range_add
 * Blacklist the devices from-to
 */

static inline void
blacklist_range_add (int from, int to, int locked)
{

	unsigned long flags;
	int i;

	if ((to && (from > to))
	    || (to<0) || (to > 0xffff)
	    || (from<0) || (from > 0xffff))
		return;

	if (!locked)
		spin_lock_irqsave (&blacklist_lock, flags);

	if (!to)
		to = from;
	for (i = from; i <= to; i++) {
		if (!test_and_set_bit (i, &bl_dev))
			nr_ignored++;
	}

	if (to >= highest_ignored)
		highest_ignored = to;

	if (!locked)
		spin_unlock_irqrestore (&blacklist_lock, flags);
}

/* 
 * Function: blacklist_range_remove
 * Removes a range from the blacklist chain
 */

static inline void
blacklist_range_remove (int from, int to)
{
	long flags;
	int i;

	if ((to && (from > to))
	    || (to<0) || (to > 0xffff)
	    || (from<0) || (from > 0xffff))
		return;

	spin_lock_irqsave (&blacklist_lock, flags);

	for (i = from; i <= to; i++) {
		if (test_and_clear_bit (i, &bl_dev))
			nr_ignored--;
	}

	if (to == highest_ignored)
		for (highest_ignored = from; (highest_ignored > 0)
		     && (!test_bit (highest_ignored, &bl_dev));
		     highest_ignored--) ;

	spin_unlock_irqrestore (&blacklist_lock, flags);
}

/* Parsing the commandline for blacklist parameters */

/* 
 * Variable to hold the blacklisted devices given by the parameter line
 * cio_ignore=...
 */
char *blacklist[256] = { NULL, };

/*
 * Get the cio_ignore=... items from the parameter line
 */

static void
blacklist_split_parm_string (char *str)
{
	char *tmp = str;
	int count = 0;
	do {
		char *end;
		int len;
		end = strchr (tmp, ',');
		if (end == NULL) {
			len = strlen (tmp) + 1;
		} else {
			len = (long) end - (long) tmp + 1;
			*end = '\0';
			end++;
		}
		blacklist[count] = alloc_bootmem (len * sizeof (char));
		if (blacklist == NULL) {
			printk (KERN_WARNING
				"can't store cio_ignore= parameter no %d\n",
				count + 1);
			break;
		}
		memset (blacklist[count], 0, len * sizeof (char));
		memcpy (blacklist[count], tmp, len * sizeof (char));
		count++;
		tmp = end;
	} while (tmp != NULL && *tmp != '\0');
}

/*
 * The blacklist parameters as one concatenated string
 */

static char blacklist_parm_string[1024] __initdata = { 0, };

/* 
 * function: blacklist_strtoul
 * Strip leading '0x' and interpret the values as Hex
 */
static inline int
blacklist_strtoul (char *str, char **stra)
{
	if (*str == '0') {
		str++;		/* strip leading zero */
		if (*str == 'x')
			str++;	/* strip leading x */
	}
	return simple_strtoul (str, stra, 16);	/* interpret anything as hex */
}

/*
 * Function: blacklist_parse
 * Parse the parameters given to cio_ignore=... 
 * Add the blacklisted devices to the blacklist chain
 */

static inline void
blacklist_parse (char **str)
{
	char *temp;
	int from, to;

	while (*str) {
		temp = *str;
		from = 0;
		to = 0;

		from = blacklist_strtoul (temp, &temp);
		if (*temp == '-') {
			temp++;
			to = blacklist_strtoul (temp, &temp);
		}
		blacklist_range_add (from, to, 0);
#ifdef CONFIG_DEBUG_IO
		printk (KERN_INFO "Blacklisted range from %X to %X\n", from,
			to);
#endif
		str++;
	}
}

/*
 * Initialisation of blacklist 
 */

void __init
blacklist_init (void)
{
#ifdef CONFIG_DEBUG_IO
	printk (KERN_DEBUG "Reading blacklist...\n");
#endif
	CIO_MSG_EVENT(6, "Reading blacklist\n");

	blacklist_split_parm_string (blacklist_parm_string);
	blacklist_parse (blacklist);
}

/*
 * Get all the blacklist parameters from parameter line
 */

void __init
blacklist_setup (char *str, int *ints)
{
	int len = strlen (blacklist_parm_string);
	if (len != 0) {
		strcat (blacklist_parm_string, ",");
	}
	strcat (blacklist_parm_string, str);
}

int __init
blacklist_call_setup (char *str)
{
	int dummy;
#ifdef CONFIG_DEBUG_IO
	printk (KERN_DEBUG "Reading blacklist parameters...\n");
#endif
	CIO_MSG_EVENT(6, "Reading blacklist parameters\n");

	blacklist_setup (str, &dummy);

	/* Blacklist ranges must be ready when device recognition starts */
	blacklist_init ();

	return 1;
}

__setup ("cio_ignore=", blacklist_call_setup);

/* Checking if devices are blacklisted */

/*
 * Function: is_blacklisted
 * Returns 1 if the given devicenumber can be found in the blacklist, otherwise 0.
 */

static inline int
is_blacklisted (int devno)
{
	long flags;
	int retval = 0;

	spin_lock_irqsave (&blacklist_lock, flags);

	if (test_bit (devno, &bl_dev))
		retval = 1;

	spin_unlock_irqrestore (&blacklist_lock, flags);
	return retval;
}

/*
 * Function: blacklist_free_all_ranges
 * set all blacklisted devices free...
 */

void
blacklist_free_all_ranges (void)
{
	unsigned long flags;
	int i;

	spin_lock_irqsave (&blacklist_lock, flags);

	for (i = 0; i <= highest_ignored; i++)
		clear_bit (i, &bl_dev);
	highest_ignored = 0;
	nr_ignored = 0;

	spin_unlock_irqrestore (&blacklist_lock, flags);
}

#ifdef CONFIG_PROC_FS
/*
 * Function: blacklist_parse_proc_parameters
 * parse the stuff which is piped to /proc/cio_ignore
 */
void
blacklist_parse_proc_parameters (char *buf)
{
	int i;
	int from = 0;
	int to = 0;
	long flags;
	int err = 0;

	if (strstr (buf, "free ")) {
		for (i = 0; i < 5; i++) {
			buf++;
		}
		if (strstr (buf, "all")) {
			blacklist_free_all_ranges ();
			s390_redo_validation ();
		} else {
			while (*buf != 0 && *buf != '\n') {
				if (!isxdigit(*buf)) {
					printk(KERN_WARNING "%s: error parsing "
					       "\"%s\"\n", __FUNCTION__, buf);
					return;
				}
				
				from = blacklist_strtoul (buf, &buf);
				to = (*buf == '-') ?
					blacklist_strtoul (buf+1, &buf) : from;
				
				blacklist_range_remove (from, to);
			
				if (*buf == ',')
					buf++;
			}
			s390_redo_validation();
		}
	} else if (strstr (buf, "add ")) {
		for (i = 0; i < 4; i++) {
			buf++;
		}
		while (*buf != 0 && *buf != '\n') {
			if (!isxdigit(*buf)) {
				printk(KERN_WARNING "%s: error parsing "
				       "\"%s\"\n", __FUNCTION__, buf);
				return;
			}
			
			from = blacklist_strtoul (buf, &buf);
			to = (*buf == '-') ?
				blacklist_strtoul (buf+1, &buf) : from;
			
			spin_lock_irqsave (&blacklist_lock, flags);
			
			/*
			 * Don't allow for already known devices to be
			 * blacklisted
			 * The criterion is a bit dumb, devices which once were
			 * there but are already gone are also caught...
			 */
			
			err = 0;
			for (i = 0; i <= highest_subchannel; i++) {
				if (ioinfo[i] != INVALID_STORAGE_AREA) {
					if (!ioinfo[i]->st) 
						if ((ioinfo[i]->schib.pmcw.dev >= from)
						    && (ioinfo[i]->schib.pmcw.dev <=
							to)) {
							printk (KERN_WARNING
								"cio_ignore: Won't blacklist "
								"already known devices, "
								"skipping range %x to %x\n",
								from, to);
							err = 1;
							break;
						}
				}
			}
			
			if (!err)
				blacklist_range_add (from, to, 1);
			
			spin_unlock_irqrestore (&blacklist_lock, flags);
			if (*buf == ',')
				buf++;
		}

	} else {
		printk (KERN_WARNING
			"cio_ignore: Parse error; "
			"try using 'free all|<devno-range>,<devno-range>,...'\n");
		printk (KERN_WARNING
			"or 'add <devno-range>,<devno-range>,...'\n");
	}
}
#endif
/* End of blacklist handling */

void s390_displayhex (char *str, void *ptr, s32 cnt);

void
s390_displayhex (char *str, void *ptr, s32 cnt)
{
	s32 cnt1, cnt2, maxcnt2;
	u32 *currptr = (__u32 *) ptr;

	printk ("\n%s\n", str);

	for (cnt1 = 0; cnt1 < cnt; cnt1 += 16) {
		printk ("%08lX ", (unsigned long) currptr);
		maxcnt2 = cnt - cnt1;
		if (maxcnt2 > 16)
			maxcnt2 = 16;
		for (cnt2 = 0; cnt2 < maxcnt2; cnt2 += 4)
			printk ("%08X ", *currptr++);
		printk ("\n");
	}
}

static int __init
cio_setup (char *parm)
{
	if (!strcmp (parm, "yes")) {
		cio_show_msg = 1;
	} else if (!strcmp (parm, "no")) {
		cio_show_msg = 0;
	} else {
		printk (KERN_ERR "cio_setup : invalid cio_msg parameter '%s'",
			parm);

	}

	return 1;
}

__setup ("cio_msg=", cio_setup);

static int __init
cio_notoper_setup (char *parm)
{
	if (!strcmp (parm, "yes")) {
		cio_notoper_msg = 1;
	} else if (!strcmp (parm, "no")) {
		cio_notoper_msg = 0;
	} else {
		printk (KERN_ERR
			"cio_notoper_setup: "
			"invalid cio_notoper_msg parameter '%s'", parm);
	}

	return 1;
}

__setup ("cio_notoper_msg=", cio_notoper_setup);

#ifdef CONFIG_PROC_FS
static int __init
cio_proc_devinfo_setup (char *parm)
{
	if (!strcmp (parm, "yes")) {
		cio_proc_devinfo = 1;
	} else if (!strcmp (parm, "no")) {
		cio_proc_devinfo = 0;
	} else {
		printk (KERN_ERR
			"cio_proc_devinfo_setup: invalid parameter '%s'\n",
			parm);
	}

	return 1;
}

__setup ("cio_proc_devinfo=", cio_proc_devinfo_setup);
#endif

static int __init
cio_pgid_setup (char *parm)
{
	if (!strcmp (parm, "yes")) {
		cio_sid_with_pgid = 1;
	} else if (!strcmp (parm, "no")) {
		cio_sid_with_pgid = 0;
	} else {
		printk (KERN_ERR 
			"cio_pgid_setup : invalid cio_msg parameter '%s'",
			parm);

	}

	return 1;
}

__setup ("cio_sid_with_pgid=", cio_pgid_setup);

/*
 * register for adapter interrupts
 *
 * With HiperSockets the zSeries architecture provides for
 *  means of adapter interrups, pseudo I/O interrupts that are
 *  not tied to an I/O subchannel, but to an adapter. However,
 *  it doesn't disclose the info how to enable/disable them, but
 *  to recognize them only. Perhaps we should consider them
 *  being shared interrupts, and thus build a linked list
 *  of adapter handlers ... to be evaluated ...
 */
int
s390_register_adapter_interrupt (adapter_int_handler_t handler)
{
	int ret = 0;
	char dbf_txt[15];

	CIO_TRACE_EVENT (4, "rgaint");

	spin_lock (&adapter_lock);

	if (handler == NULL)
		ret = -EINVAL;
	else if (adapter_handler)
		ret = -EBUSY;
	else
		adapter_handler = handler;

	spin_unlock (&adapter_lock);

	sprintf (dbf_txt, "ret:%d", ret);
	CIO_TRACE_EVENT (4, dbf_txt);

	return (ret);
}

int
s390_unregister_adapter_interrupt (adapter_int_handler_t handler)
{
	int ret = 0;
	char dbf_txt[15];

	CIO_TRACE_EVENT (4, "urgaint");

	spin_lock (&adapter_lock);

	if (handler == NULL)
		ret = -EINVAL;
	else if (handler != adapter_handler)
		ret = -EINVAL;
	else
		adapter_handler = NULL;

	spin_unlock (&adapter_lock);

	sprintf (dbf_txt, "ret:%d", ret);
	CIO_TRACE_EVENT (4, dbf_txt);

	return (ret);
}

static inline void
do_adapter_IO (__u32 intparm)
{
	CIO_TRACE_EVENT (4, "doaio");

	spin_lock (&adapter_lock);

	if (adapter_handler)
		(*adapter_handler) (intparm);

	spin_unlock (&adapter_lock);

	return;
}

void s390_free_irq (unsigned int irq, void *dev_id);

/*
 * Note : internal use of irqflags SA_PROBE for NOT path grouping 
 *
 */
int
s390_request_irq_special (int irq,
			  io_handler_func_t io_handler,
			  not_oper_handler_func_t not_oper_handler,
			  unsigned long irqflags,
			  const char *devname, void *dev_id)
{
	int retval = 0;
	unsigned long flags;
	char dbf_txt[15];
	int retry;

	if (irq >= __MAX_SUBCHANNELS)
		return -EINVAL;

	if (!io_handler || !dev_id)
		return -EINVAL;

	if (ioinfo[irq] == INVALID_STORAGE_AREA)
		return -ENODEV;
	
	if (ioinfo[irq]->st)
		return -ENODEV;

	sprintf (dbf_txt, "reqsp%x", irq);
	CIO_TRACE_EVENT (4, dbf_txt);

	/*
	 * The following block of code has to be executed atomically
	 */
	s390irq_spin_lock_irqsave (irq, flags);

	if (ioinfo[irq]->ui.flags.unfriendly &&
	    !(irqflags & SA_FORCE)) {
		retval = -EUSERS;

	} else if (!ioinfo[irq]->ui.flags.ready) {
		retry = 5;

		ioinfo[irq]->irq_desc.handler = io_handler;
		ioinfo[irq]->irq_desc.name = devname;
		ioinfo[irq]->irq_desc.dev_id = dev_id;
		ioinfo[irq]->ui.flags.ready = 1;

		do {
			retval = enable_subchannel (irq);
			if (retval) {
				ioinfo[irq]->ui.flags.ready = 0;
				break;
			}

			stsch (irq, &ioinfo[irq]->schib);
			if (ioinfo[irq]->schib.pmcw.ena)
				retry = 0;
			else
				retry--;

		} while (retry);
	} else {
		/*
		 *  interrupt already owned, and shared interrupts
		 *   aren't supported on S/390.
		 */
		retval = -EBUSY;

	}

	s390irq_spin_unlock_irqrestore (irq, flags);

	if (retval == 0) {
		if (irqflags & SA_DOPATHGROUP) {
			ioinfo[irq]->ui.flags.pgid_supp = 1;
			ioinfo[irq]->ui.flags.notacccap = 1;
		}
		if ((irqflags & SA_DOPATHGROUP) &&
		    (!ioinfo[irq]->ui.flags.pgid ||
		     irqflags & SA_PROBE)) {
			pgid_t pgid;
			int i, mask;
			/* 
			 * Do an initial SensePGID to find out if device
			 * is locked by someone else.
			 */
			memcpy(&pgid, global_pgid, sizeof(pgid_t));
			
			retval = -EAGAIN;
			for (i=0; i<8 && retval==-EAGAIN; i++) {

				mask = (0x80 >> i) & ioinfo[irq]->opm;
				
				if (!mask)
					continue;
				
				retval = s390_SensePGID(irq, mask, &pgid);
				
				if (retval == -EOPNOTSUPP) 
					/* Doesn't prevent us from proceeding */
					retval = 0;
			}
			
		}
		if (!(irqflags & SA_PROBE) &&
		    (irqflags & SA_DOPATHGROUP) &&
		    (!ioinfo[irq]->ui.flags.unfriendly)) 
			s390_DevicePathVerification (irq, 0);

		if (ioinfo[irq]->ui.flags.unfriendly &&
		    !(irqflags & SA_FORCE)) {
			/* 
			 * We found out during path verification that the
			 * device is locked by someone else and we have to
			 * let the device driver know.
			 */
			retval = -EUSERS;
			free_irq(irq, dev_id);
		} else {
			ioinfo[irq]->ui.flags.newreq = 1;
			ioinfo[irq]->nopfunc = not_oper_handler;
		}
	}

	if (cio_debug_initialized)
		debug_int_event (cio_debug_trace_id, 4, retval);

	return retval;
}

int
s390_request_irq (unsigned int irq,
		  void (*handler) (int, void *, struct pt_regs *),
		  unsigned long irqflags, const char *devname, void *dev_id)
{
	int ret;

	ret = s390_request_irq_special (irq,
					(io_handler_func_t) handler,
					NULL, irqflags, devname, dev_id);

	if (ret == 0) {
		ioinfo[irq]->ui.flags.newreq = 0;

	}
	return (ret);
}

void
s390_free_irq (unsigned int irq, void *dev_id)
{
	unsigned long flags;
	int ret;

	char dbf_txt[15];

	if (irq >= __MAX_SUBCHANNELS || ioinfo[irq] == INVALID_STORAGE_AREA)
		return;

	if (ioinfo[irq]->st)
		return;

	sprintf (dbf_txt, "free%x", irq);
	CIO_TRACE_EVENT (2, dbf_txt);

	s390irq_spin_lock_irqsave (irq, flags);

#ifdef  CONFIG_KERNEL_DEBUG
	if (irq != cons_dev)
		printk (KERN_DEBUG "Trying to free IRQ%d\n", irq);
#endif
	CIO_MSG_EVENT(2, "Trying to free IRQ %d\n", irq);

	/*
	 * disable the device and reset all IRQ info if
	 *  the IRQ is actually owned by the handler ...
	 */
	if (ioinfo[irq]->ui.flags.ready) {
		if (dev_id == ioinfo[irq]->irq_desc.dev_id) {
			/* start deregister */
			ioinfo[irq]->ui.flags.unready = 1;

			ret = disable_subchannel (irq);

			if (ret == -EBUSY) {

				/*
				 * kill it !
				 * We try to terminate the I/O by halt_IO first,
				 * then clear_IO.
				 * Because the device may be gone (machine 
				 * check handling), we can't use sync I/O.
				 */

				halt_IO (irq, 0xC8C1D3E3, 0);
				s390irq_spin_unlock_irqrestore (irq, flags);
				udelay (200000);	/* 200 ms */
				s390irq_spin_lock_irqsave (irq, flags);

				ret = disable_subchannel (irq);

				if (ret == -EBUSY) {

					clear_IO (irq, 0x40C3D3D9, 0);
					s390irq_spin_unlock_irqrestore (irq,
									flags);
					udelay (1000000);	/* 1000 ms */
					s390irq_spin_lock_irqsave (irq, flags);

					/* give it a very last try ... */
					disable_subchannel (irq);

					if (ioinfo[irq]->ui.flags.busy) {
						printk (KERN_CRIT
							"free_irq(%04X) "
							"- device %04X busy, retry "
							"count exceeded\n", irq,
							ioinfo[irq]->devstat.
							devno);
						CIO_MSG_EVENT( 0,
							       "free_irq(%04X) - "
							       "device %04X busy, "
							       "retry count exceeded\n",
							       irq,
							       ioinfo[irq]->
							       devstat.devno);
						
					}
				}
			}

			ioinfo[irq]->ui.flags.ready = 0;
			ioinfo[irq]->ui.flags.unready = 0;	/* deregister ended */

			ioinfo[irq]->nopfunc = NULL;

			s390irq_spin_unlock_irqrestore (irq, flags);
		} else {
			s390irq_spin_unlock_irqrestore (irq, flags);

			printk (KERN_ERR "free_irq(%04X) : error, "
				"dev_id does not match !\n", irq);
			CIO_MSG_EVENT( 0,
				       "free_irq(%04X) : error, "
				       "dev_id does not match !\n",
				       irq);

		}
	} else {
		s390irq_spin_unlock_irqrestore (irq, flags);

		printk (KERN_ERR "free_irq(%04X) : error, "
			"no action block ... !\n", irq);
		CIO_MSG_EVENT(0,
			      "free_irq(%04X) : error, "
			      "no action block ... !\n", irq);

	}
}

/*
 * Enable IRQ by modifying the subchannel
 */
static int
enable_subchannel (unsigned int irq)
{
	int ret = 0;
	int ccode;
	int retry = 5;
	char dbf_txt[15];

	SANITY_CHECK (irq);

	sprintf (dbf_txt, "ensch%x", irq);
	CIO_TRACE_EVENT (2, dbf_txt);

	/*
	 * If a previous disable request is pending we reset it. However, this
	 *  status implies that the device may (still) be not-operational.
	 */
	if (ioinfo[irq]->ui.flags.d_disable) {
		ioinfo[irq]->ui.flags.d_disable = 0;
		ret = 0;
	} else {
		ccode = stsch (irq, &(ioinfo[irq]->schib));

		if (ccode) {
			ret = -ENODEV;
		} else {
			ioinfo[irq]->schib.pmcw.ena = 1;

			if (irq == cons_dev) {
				ioinfo[irq]->schib.pmcw.isc = 7;
			} else {
				ioinfo[irq]->schib.pmcw.isc = 3;

			}

			do {
				ccode = msch (irq, &(ioinfo[irq]->schib));

				switch (ccode) {
				case 0:	/* ok */
					ret = 0;
					retry = 0;
					break;

				case 1:	/* status pending */

					ioinfo[irq]->ui.flags.s_pend = 1;
					s390_process_IRQ (irq);
					ioinfo[irq]->ui.flags.s_pend = 0;

					ret = -EIO;
					/* 
					 * might be overwritten on re-driving 
					 * the msch()       
					 */
					retry--;
					break;

				case 2:	/* busy */
					udelay (100);	/* allow for recovery */
					ret = -EBUSY;
					retry--;
					break;

				case 3:	/* not oper */
					ioinfo[irq]->ui.flags.oper = 0;
					retry = 0;
					ret = -ENODEV;
					break;
				}

			} while (retry);

		}
	}

	sprintf (dbf_txt, "ret:%d", ret);
	CIO_TRACE_EVENT (2, dbf_txt);

	return (ret);
}

/*
 * Disable IRQ by modifying the subchannel
 */
static int
disable_subchannel (unsigned int irq)
{
	int cc;			/* condition code */
	int ret = 0;		/* function return value */
	int retry = 5;
	char dbf_txt[15];

	SANITY_CHECK (irq);

	sprintf (dbf_txt, "dissch%x", irq);
	CIO_TRACE_EVENT (2, dbf_txt);

	if (ioinfo[irq]->ui.flags.busy) {
		/*
		 * the disable function must not be called while there are
		 *  requests pending for completion !
		 */
		ret = -EBUSY;
	} else {

		/*
		 * If device isn't operational we have to perform delayed
		 *  disabling when the next interrupt occurs - unless the
		 *  irq is re-requested prior to the interrupt to occur.
		 */
		cc = stsch (irq, &(ioinfo[irq]->schib));

		if (cc == 3) {
			ioinfo[irq]->ui.flags.oper = 0;
			ioinfo[irq]->ui.flags.d_disable = 1;

			ret = 0;
		} else {	/* cc == 0 */

			ioinfo[irq]->schib.pmcw.ena = 0;

			do {
				cc = msch (irq, &(ioinfo[irq]->schib));

				switch (cc) {
				case 0:	/* ok */
					retry = 0;
					ret = 0;
					break;

				case 1:	/* status pending */
					ioinfo[irq]->ui.flags.s_pend = 1;
					s390_process_IRQ (irq);
					ioinfo[irq]->ui.flags.s_pend = 0;

					ret = -EIO;
					/* 
					 * might be overwritten on re-driving 
					 * the msch() call       
					 */
					retry--;
					break;

				case 2:	/* busy; this should not happen! */
					printk (KERN_CRIT
						"disable_subchannel(%04X) "
						"- unexpected busy condition for "
						"device %04X received !\n", irq,
						ioinfo[irq]->devstat.devno);
					CIO_MSG_EVENT(0,
						      "disable_subchannel(%04X) "
						      "- unexpected busy condition "
						      "for device %04X received !\n",
						      irq,
						      ioinfo[irq]->devstat.
						      devno);
					retry = 0;
					ret = -EBUSY;
					break;

				case 3:	/* not oper */
					/*
					 * should hardly occur ?!
					 */
					ioinfo[irq]->ui.flags.oper = 0;
					ioinfo[irq]->ui.flags.d_disable = 1;
					retry = 0;

					ret = 0;
					/* 
					 * if the device has gone, we don't need 
					 * to disable it anymore !          
					 */
					break;

				}

			} while (retry);

		}
	}

	sprintf (dbf_txt, "ret:%d", ret);
	CIO_TRACE_EVENT (2, dbf_txt);

	return (ret);
}

void
s390_init_IRQ (void)
{
	unsigned long flags;	/* PSW flags */
	long cr6 __attribute__ ((aligned (8)));
	cpuid_t cpuid;

	asm volatile ("STCK %0":"=m" (irq_IPL_TOD));

	p_init_schib = alloc_bootmem_low (sizeof (schib_t));
	p_init_irb = alloc_bootmem_low (sizeof (irb_t));

	/*
	 * As we don't know about the calling environment
	 *  we assure running disabled. Before leaving the
	 *  function we resestablish the old environment.
	 *
	 * Note : as we don't need a system wide lock, therefore
	 *        we shouldn't use cli(), but __cli() as this
	 *        affects the current CPU only.
	 */
	__save_flags (flags);
	__cli ();

	/*
	 * disable all interrupts
	 */
	cr6 = 0;
	__ctl_load (cr6, 6, 6);

	s390_process_subchannels ();

	if (cio_count_irqs) {
		int i;
		for (i = 0; i < NR_CPUS; i++)
			s390_irq_count[i] = 0;
	}

	
	/*
	 * Let's build our path group ID here.
	 */
	
	global_pgid = (pgid_t *)alloc_bootmem(sizeof(pgid_t));

	cpuid = *(cpuid_t*) __LC_CPUID;
	
	if (MACHINE_NEW_STIDP)
		global_pgid->cpu_addr = 0x8000;
	else {
#ifdef CONFIG_SMP
		global_pgid->cpu_addr = hard_smp_processor_id();
#else
		global_pgid->cpu_addr = 0;
#endif
	}
	global_pgid->cpu_id = cpuid.ident;
	global_pgid->cpu_model = ((cpuid_t *) __LC_CPUID)->machine;
	global_pgid->tod_high = *(__u32 *) & irq_IPL_TOD;


	/*
	 * enable default I/O-interrupt sublass 3
	 */
	cr6 = 0x10000000;
	__ctl_load (cr6, 6, 6);

	s390_device_recognition_all ();

	init_IRQ_complete = 1;

	__restore_flags (flags);

	return;
}

/*
 * dummy handler, used during init_IRQ() processing for compatibility only
 */
void
init_IRQ_handler (int irq, void *dev_id, struct pt_regs *regs)
{
	/* this is a dummy handler only ... */
}

int
s390_start_IO (int irq,		/* IRQ */
	       ccw1_t * cpa,	/* logical channel prog addr */
	       unsigned long user_intparm,	/* interruption parameter */
	       __u8 lpm,	/* logical path mask */
	       unsigned long flag)
{				/* flags */
	int ccode;
	int ret = 0;
	char dbf_txt[15];

	SANITY_CHECK (irq);

	/*
	 * The flag usage is mutal exclusive ...
	 */
	if ((flag & DOIO_EARLY_NOTIFICATION)
	    && (flag & DOIO_REPORT_ALL)) {
		return (-EINVAL);

	}

	sprintf (dbf_txt, "stIO%x", irq);
	CIO_TRACE_EVENT (4, dbf_txt);

	/*
	 * setup ORB
	 */
	ioinfo[irq]->orb.intparm = (__u32) (long) &ioinfo[irq]->u_intparm;
	ioinfo[irq]->orb.fmt = 1;

	ioinfo[irq]->orb.pfch = !(flag & DOIO_DENY_PREFETCH);
	ioinfo[irq]->orb.spnd = (flag & DOIO_ALLOW_SUSPEND ? TRUE : FALSE);
	ioinfo[irq]->orb.ssic = ((flag & DOIO_ALLOW_SUSPEND)
				 && (flag & DOIO_SUPPRESS_INTER));

	if (flag & DOIO_VALID_LPM) {
		ioinfo[irq]->orb.lpm = lpm;
	} else {
		ioinfo[irq]->orb.lpm = ioinfo[irq]->opm;

	}

#ifdef CONFIG_ARCH_S390X
	/* 
	 * for 64 bit we always support 64 bit IDAWs with 4k page size only
	 */
	ioinfo[irq]->orb.c64 = 1;
	ioinfo[irq]->orb.i2k = 0;
#endif

	ioinfo[irq]->orb.cpa = (__u32) virt_to_phys (cpa);

	/*
	 * If sync processing was requested we lock the sync ISC, modify the
	 *  device to present interrupts for this ISC only and switch the
	 *  CPU to handle this ISC + the console ISC exclusively.
	 */
	if (flag & DOIO_WAIT_FOR_INTERRUPT) {
		ret = enable_cpu_sync_isc (irq);

		if (ret) {
			return (ret);
		}

	}

	if (flag & DOIO_DONT_CALL_INTHDLR) {
		ioinfo[irq]->ui.flags.repnone = 1;

	}

	/*
	 * Issue "Start subchannel" and process condition code
	 */
	if (flag & DOIO_USE_DIAG98) {
		ioinfo[irq]->orb.key = get_storage_key() >> 4;
		ioinfo[irq]->orb.cpa =
			(__u32) pfix_get_addr((void *)ioinfo[irq]->orb.cpa);
		ccode = diag98 (irq, &(ioinfo[irq]->orb));
	} else {
		ccode = ssch (irq, &(ioinfo[irq]->orb));
	}

	sprintf (dbf_txt, "ccode:%d", ccode);
	CIO_TRACE_EVENT (4, dbf_txt);

	switch (ccode) {
	case 0:

		if (!ioinfo[irq]->ui.flags.w4sense) {
			/*
			 * init the device driver specific devstat irb area
			 *
			 * Note : donīt clear saved irb info in case of sense !
			 */
			memset (&((devstat_t *) ioinfo[irq]->irq_desc.dev_id)->
				ii.irb, '\0', sizeof (irb_t));
		}

		memset (&ioinfo[irq]->devstat.ii.irb, '\0', sizeof (irb_t));

		/*
		 * initialize device status information
		 */
		ioinfo[irq]->ui.flags.busy = 1;
		ioinfo[irq]->ui.flags.doio = 1;

		ioinfo[irq]->u_intparm = user_intparm;
		ioinfo[irq]->devstat.cstat = 0;
		ioinfo[irq]->devstat.dstat = 0;
		ioinfo[irq]->devstat.lpum = 0;
		ioinfo[irq]->devstat.flag = DEVSTAT_START_FUNCTION;
		ioinfo[irq]->devstat.scnt = 0;

		ioinfo[irq]->ui.flags.fast = 0;
		ioinfo[irq]->ui.flags.repall = 0;

		/*
		 * Check for either early (FAST) notification requests
		 *  or if we are to return all interrupt info.
		 * Default is to call IRQ handler at secondary status only
		 */
		if (flag & DOIO_EARLY_NOTIFICATION) {
			ioinfo[irq]->ui.flags.fast = 1;
		} else if (flag & DOIO_REPORT_ALL) {
			ioinfo[irq]->ui.flags.repall = 1;

		}

		/*
		 * If synchronous I/O processing is requested, we have
		 *  to wait for the corresponding interrupt to occur by
		 *  polling the interrupt condition. However, as multiple
		 *  interrupts may be outstanding, we must not just wait
		 *  for the first interrupt, but must poll until ours
		 *  pops up.
		 */
		if (flag & DOIO_WAIT_FOR_INTERRUPT) {
			unsigned long psw_mask;
			int ccode;
			uint64_t time_start;
			uint64_t time_curr;

			int ready = 0;
			int io_sub = -1;
			int do_retry = 1;

			/*
			 * We shouldn't perform a TPI loop, waiting for an
			 *  interrupt to occur, but should load a WAIT PSW
			 *  instead. Otherwise we may keep the channel subsystem
			 *  busy, not able to present the interrupt. When our
			 *  sync. interrupt arrived we reset the I/O old PSW to
			 *  its original value.
			 */

			ccode = iac ();

			switch (ccode) {
			case 0:	/* primary-space */
				psw_mask = _IO_PSW_MASK
				    | _PSW_PRIM_SPACE_MODE | _PSW_IO_WAIT;
				break;
			case 1:	/* secondary-space */
				psw_mask = _IO_PSW_MASK
				    | _PSW_SEC_SPACE_MODE | _PSW_IO_WAIT;
				break;
			case 2:	/* access-register */
				psw_mask = _IO_PSW_MASK
				    | _PSW_ACC_REG_MODE | _PSW_IO_WAIT;
				break;
			case 3:	/* home-space */
				psw_mask = _IO_PSW_MASK
				    | _PSW_HOME_SPACE_MODE | _PSW_IO_WAIT;
				break;
			default:
				panic ("start_IO() : unexpected "
				       "address-space-control %d\n", ccode);
				break;
			}

			/*
			 * Martin didn't like modifying the new PSW, now we take
			 *  a fast exit in do_IRQ() instead
			 */
			*(__u32 *) __LC_SYNC_IO_WORD = 1;

			asm volatile ("STCK %0":"=m" (time_start));

			time_start = time_start >> 32;

			do {
				if (flag & DOIO_TIMEOUT) {
					tpi_info_t tpi_info = { 0, };

					do {
						if (tpi (&tpi_info) == 1) {
							io_sub = tpi_info.irq;
							break;
						} else {
							udelay (100);	/* usecs */
							asm volatile
							 ("STCK %0":"=m"
							  (time_curr));

							if (((time_curr >> 32) -
							     time_start) >= 3)
								do_retry = 0;

						}

					} while (do_retry);
				} else {
					__load_psw_mask (psw_mask);

					io_sub =
					    (__u32) *
					    (__u16 *) __LC_SUBCHANNEL_NR;

				}

				if (do_retry)
					ready = s390_process_IRQ (io_sub);

				/*
				 * surrender when retry count's exceeded ...
				 */
			} while (!((io_sub == irq)
				   && (ready == 1))
				 && do_retry);

			*(__u32 *) __LC_SYNC_IO_WORD = 0;

			if (!do_retry)
				ret = -ETIMEDOUT;

		}

		break;

	case 1:		/* status pending */
		
		/* 
		 * Don't do an inline processing of pending interrupt conditions
		 * while doing async. I/O. The interrupt will pop up when we are
		 * enabled again and the I/O can be retried.
		 */
		if (!ioinfo[irq]->ui.flags.syncio) {
			ret = -EBUSY;
			break;
		}

		ioinfo[irq]->devstat.flag = DEVSTAT_START_FUNCTION
		    | DEVSTAT_STATUS_PENDING;

		/*
		 * initialize the device driver specific devstat irb area
		 */
		memset (&((devstat_t *) ioinfo[irq]->irq_desc.dev_id)->ii.irb,
			'\0', sizeof (irb_t));

		/*
		 * Let the common interrupt handler process the pending status.
		 *  However, we must avoid calling the user action handler, as
		 *  it won't be prepared to handle a pending status during
		 *  do_IO() processing inline. This also implies that process_IRQ
		 *  must terminate synchronously - especially if device sensing
		 *  is required.
		 */
		ioinfo[irq]->ui.flags.s_pend = 1;
		ioinfo[irq]->ui.flags.busy = 1;
		ioinfo[irq]->ui.flags.doio = 1;

		s390_process_IRQ (irq);

		ioinfo[irq]->ui.flags.s_pend = 0;
		ioinfo[irq]->ui.flags.busy = 0;
		ioinfo[irq]->ui.flags.doio = 0;

		ioinfo[irq]->ui.flags.repall = 0;
		ioinfo[irq]->ui.flags.w4final = 0;

		ioinfo[irq]->devstat.flag |= DEVSTAT_FINAL_STATUS;

		/*
		 * In multipath mode a condition code 3 implies the last path
		 *  has gone, except we have previously restricted the I/O to
		 *  a particular path. A condition code 1 (0 won't occur)
		 *  results in return code EIO as well as 3 with another path
		 *  than the one used (i.e. path available mask is non-zero).
		 */
		if (ioinfo[irq]->devstat.ii.irb.scsw.cc == 3) {

			if (ioinfo[irq]->opm == 0) {
				ret = -ENODEV;
				ioinfo[irq]->ui.flags.oper = 0;
			} else {
				ret = -EIO;

			}

			ioinfo[irq]->devstat.flag |= DEVSTAT_NOT_OPER;

#ifdef CONFIG_DEBUG_IO
			{
				char buffer[80];

				stsch (irq, &(ioinfo[irq]->schib));

				sprintf (buffer,
					 "s390_start_IO(%04X) - irb for "
					 "device %04X, after status pending\n",
					 irq, ioinfo[irq]->devstat.devno);

				s390_displayhex (buffer,
						 &(ioinfo[irq]->devstat.ii.irb),
						 sizeof (irb_t));

				sprintf (buffer,
					 "s390_start_IO(%04X) - schib for "
					 "device %04X, after status pending\n",
					 irq, ioinfo[irq]->devstat.devno);

				s390_displayhex (buffer,
						 &(ioinfo[irq]->schib),
						 sizeof (schib_t));

				if (ioinfo[irq]->devstat.
				    flag & DEVSTAT_FLAG_SENSE_AVAIL) {
					sprintf (buffer,
						 "s390_start_IO(%04X) "
						 "- sense data for device %04X,"
						 " after status pending\n",
						 irq,
						 ioinfo[irq]->devstat.devno);

					s390_displayhex (buffer,
							 ioinfo[irq]->irq_desc.
							 dev_id->ii.sense.data,
							 ioinfo[irq]->irq_desc.
							 dev_id->rescnt);

				}
			}
#endif
			if (cio_debug_initialized) {
				stsch (irq, &(ioinfo[irq]->schib));
				
				sprintf(dbf_txt, "sp%x", irq);
				CIO_TRACE_EVENT(2, dbf_txt);
				CIO_TRACE_EVENT(2, "irb:");
				CIO_HEX_EVENT(2, &(ioinfo[irq]->devstat.ii.irb),
					      sizeof (irb_t));
				CIO_TRACE_EVENT(2, "schib:");
				CIO_HEX_EVENT(2, &(ioinfo[irq]->schib),
					      sizeof (schib_t));

				if (ioinfo[irq]->devstat.
				    flag & DEVSTAT_FLAG_SENSE_AVAIL) {
					CIO_TRACE_EVENT(2, "sense:");
					CIO_HEX_EVENT(2, ioinfo[irq]->irq_desc.
						      dev_id->ii.sense.data,
						      ioinfo[irq]->irq_desc.
						      dev_id->rescnt);

				}
			}
		} else {
			ret = -EIO;
			ioinfo[irq]->devstat.flag &= ~DEVSTAT_NOT_OPER;
			ioinfo[irq]->ui.flags.oper = 1;

		}

		break;

	case 2:		/* busy */

		ret = -EBUSY;
		break;

	default:		/* device/path not operational */

		if (flag & DOIO_VALID_LPM) {
			ioinfo[irq]->opm &= ~lpm;
		} else {
			ioinfo[irq]->opm = 0;

		}

		if (ioinfo[irq]->opm == 0) {
			ioinfo[irq]->ui.flags.oper = 0;
			ioinfo[irq]->devstat.flag |= DEVSTAT_NOT_OPER;

		}

		ret = -ENODEV;

		memcpy (ioinfo[irq]->irq_desc.dev_id,
			&(ioinfo[irq]->devstat), sizeof (devstat_t));

#ifdef CONFIG_DEBUG_IO

		stsch (irq, &(ioinfo[irq]->schib));

		sprintf (buffer, "s390_start_IO(%04X) - schib for "
			 "device %04X, after 'not oper' status\n",
			 irq, ioinfo[irq]->devstat.devno);

		s390_displayhex (buffer,
				 &(ioinfo[irq]->schib), sizeof (schib_t));
#endif
		if (cio_debug_initialized) {
			stsch (irq, &(ioinfo[irq]->schib));
			sprintf(dbf_txt, "no%x", irq);
			CIO_TRACE_EVENT(2, dbf_txt);
			CIO_HEX_EVENT(2, &(ioinfo[irq]->schib),
				      sizeof (schib_t));
		}

		break;

	}

	if (flag & DOIO_WAIT_FOR_INTERRUPT) {
		disable_cpu_sync_isc (irq);

	}

	if (flag & DOIO_DONT_CALL_INTHDLR) {
		ioinfo[irq]->ui.flags.repnone = 0;

	}

	return (ret);
}

int
do_IO (int irq,			/* IRQ */
       ccw1_t * cpa,		/* channel program address */
       unsigned long user_intparm,	/* interruption parameter */
       __u8 lpm,		/* logical path mask */
       unsigned long flag)
{				/* flags : see above */
	int ret = 0;
	char dbf_txt[15];

	SANITY_CHECK (irq);

	/* handler registered ? or free_irq() in process already ? */
	if (!ioinfo[irq]->ui.flags.ready || ioinfo[irq]->ui.flags.unready) {
		return (-ENODEV);

	}

	sprintf (dbf_txt, "doIO%x", irq);
	CIO_TRACE_EVENT (4, dbf_txt);

	if (ioinfo[irq]->ui.flags.noio)
		return -EBUSY;

	/*
	 * Note: We ignore the device operational status - if not operational,
	 *        the SSCH will lead to an -ENODEV condition ...
	 */
	if (!ioinfo[irq]->ui.flags.busy) {	/* last I/O completed ? */
		ret = s390_start_IO (irq, cpa, user_intparm, lpm, flag);
	} else {
		ret = -EBUSY;

	}

	return (ret);

}

/*
 * resume suspended I/O operation
 */
int
resume_IO (int irq)
{
	int ret = 0;
	char dbf_txt[15];

	SANITY_CHECK (irq);

	sprintf (dbf_txt, "resIO%x", irq);
	CIO_TRACE_EVENT (4, dbf_txt);

	/*
	 * We allow for 'resume' requests only for active I/O operations
	 */
	if (ioinfo[irq]->ui.flags.busy) {
		int ccode;

		ccode = rsch (irq);

		sprintf (dbf_txt, "ccode:%d", ccode);
		CIO_TRACE_EVENT (4, dbf_txt);

		switch (ccode) {
		case 0:
			break;

		case 1:
			ret = -EBUSY;
			break;

		case 2:
			ret = -EINVAL;
			break;

		case 3:
			/*
			 * useless to wait for request completion
			 *  as device is no longer operational !
			 */
			ioinfo[irq]->ui.flags.oper = 0;
			ioinfo[irq]->ui.flags.busy = 0;
			ret = -ENODEV;
			break;

		}

	} else {
		ret = -ENOTCONN;

	}

	return (ret);
}

/*
 * Note: The "intparm" parameter is not used by the halt_IO() function
 *       itself, as no ORB is built for the HSCH instruction. However,
 *       it allows the device interrupt handler to associate the upcoming
 *       interrupt with the halt_IO() request.
 */
int
halt_IO (int irq, unsigned long user_intparm, unsigned long flag)
{				/* possible DOIO_WAIT_FOR_INTERRUPT */
	int ret;
	int ccode;
	char dbf_txt[15];

	SANITY_CHECK (irq);

	if (ioinfo[irq]->ui.flags.noio)
		return -EBUSY;

	/*
	 * we only allow for halt_IO if the device has an I/O handler associated
	 */
	if (!ioinfo[irq]->ui.flags.ready) {
		return -ENODEV;
	}
	/*
	 * we ignore the halt_io() request if ending_status was received but
	 *  a SENSE operation is waiting for completion.
	 */
	if (ioinfo[irq]->ui.flags.w4sense) {
		return 0;
	}
	sprintf (dbf_txt, "haltIO%x", irq);
	CIO_TRACE_EVENT (2, dbf_txt);

	/*
	 * If sync processing was requested we lock the sync ISC,
	 *  modify the device to present interrupts for this ISC only
	 *  and switch the CPU to handle this ISC + the console ISC
	 *  exclusively.
	 */
	if (flag & DOIO_WAIT_FOR_INTERRUPT) {
		ret = enable_cpu_sync_isc (irq);

		if (ret)
			return (ret);
	}

	/*
	 * Issue "Halt subchannel" and process condition code
	 */
	ccode = hsch (irq);

	sprintf (dbf_txt, "ccode:%d", ccode);
	CIO_TRACE_EVENT (2, dbf_txt);

	switch (ccode) {
	case 0:

		ioinfo[irq]->ui.flags.haltio = 1;

		if (!ioinfo[irq]->ui.flags.doio) {
			ioinfo[irq]->ui.flags.busy = 1;
			ioinfo[irq]->u_intparm = user_intparm;
			ioinfo[irq]->devstat.cstat = 0;
			ioinfo[irq]->devstat.dstat = 0;
			ioinfo[irq]->devstat.lpum = 0;
			ioinfo[irq]->devstat.flag = DEVSTAT_HALT_FUNCTION;
			ioinfo[irq]->devstat.scnt = 0;

		} else {
			ioinfo[irq]->devstat.flag |= DEVSTAT_HALT_FUNCTION;

		}

		/*
		 * If synchronous I/O processing is requested, we have
		 *  to wait for the corresponding interrupt to occur by
		 *  polling the interrupt condition. However, as multiple
		 *  interrupts may be outstanding, we must not just wait
		 *  for the first interrupt, but must poll until ours
		 *  pops up.
		 */
		if (flag & DOIO_WAIT_FOR_INTERRUPT) {
			int io_sub;
			__u32 io_parm;
			unsigned long psw_mask;
			int ccode;

			int ready = 0;

			/*
			 * We shouldn't perform a TPI loop, waiting for
			 *  an interrupt to occur, but should load a
			 *  WAIT PSW instead. Otherwise we may keep the
			 *  channel subsystem busy, not able to present
			 *  the interrupt. When our sync. interrupt
			 *  arrived we reset the I/O old PSW to its
			 *  original value.
			 */

			ccode = iac ();

			switch (ccode) {
			case 0:	/* primary-space */
				psw_mask = _IO_PSW_MASK
				    | _PSW_PRIM_SPACE_MODE | _PSW_IO_WAIT;
				break;
			case 1:	/* secondary-space */
				psw_mask = _IO_PSW_MASK
				    | _PSW_SEC_SPACE_MODE | _PSW_IO_WAIT;
				break;
			case 2:	/* access-register */
				psw_mask = _IO_PSW_MASK
				    | _PSW_ACC_REG_MODE | _PSW_IO_WAIT;
				break;
			case 3:	/* home-space */
				psw_mask = _IO_PSW_MASK
				    | _PSW_HOME_SPACE_MODE | _PSW_IO_WAIT;
				break;
			default:
				panic ("halt_IO() : unexpected "
				       "address-space-control %d\n", ccode);
				break;
			}

			/*
			 * Martin didn't like modifying the new PSW, now we take
			 *  a fast exit in do_IRQ() instead
			 */
			*(__u32 *) __LC_SYNC_IO_WORD = 1;

			do {
				__load_psw_mask (psw_mask);

				io_parm = *(__u32 *) __LC_IO_INT_PARM;
				io_sub = (__u32) * (__u16 *) __LC_SUBCHANNEL_NR;

				ready = s390_process_IRQ (io_sub);

			} while (!((io_sub == irq) && (ready == 1)));

			*(__u32 *) __LC_SYNC_IO_WORD = 0;

		}

		ret = 0;
		break;

	case 1:		/* status pending */

		/* 
		 * Don't do an inline processing of pending interrupt conditions
		 * while doing async. I/O. The interrupt will pop up when we are
		 * enabled again and the I/O can be retried.
		 */
		if (!ioinfo[irq]->ui.flags.syncio) {
			ret = -EBUSY;
			break;
		}
		
		ioinfo[irq]->devstat.flag |= DEVSTAT_STATUS_PENDING;

		/*
		 * initialize the device driver specific devstat irb area
		 */
		memset (&ioinfo[irq]->irq_desc.dev_id->ii.irb,
			'\0', sizeof (irb_t));

		/*
		 * Let the common interrupt handler process the pending
		 *  status. However, we must avoid calling the user
		 *  action handler, as it won't be prepared to handle
		 *  a pending status during do_IO() processing inline.
		 *  This also implies that s390_process_IRQ must
		 *  terminate synchronously - especially if device
		 *  sensing is required.
		 */
		ioinfo[irq]->ui.flags.s_pend = 1;
		ioinfo[irq]->ui.flags.busy = 1;
		ioinfo[irq]->ui.flags.doio = 1;

		s390_process_IRQ (irq);

		ioinfo[irq]->ui.flags.s_pend = 0;
		ioinfo[irq]->ui.flags.busy = 0;
		ioinfo[irq]->ui.flags.doio = 0;
		ioinfo[irq]->ui.flags.repall = 0;
		ioinfo[irq]->ui.flags.w4final = 0;

		ioinfo[irq]->devstat.flag |= DEVSTAT_FINAL_STATUS;

		/*
		 * In multipath mode a condition code 3 implies the last
		 *  path has gone, except we have previously restricted
		 *  the I/O to a particular path. A condition code 1
		 *  (0 won't occur) results in return code EIO as well
		 *  as 3 with another path than the one used (i.e. path 
		 *  available mask is non-zero).
		 */
		if (ioinfo[irq]->devstat.ii.irb.scsw.cc == 3) {
			ret = -ENODEV;
			ioinfo[irq]->devstat.flag |= DEVSTAT_NOT_OPER;
			ioinfo[irq]->ui.flags.oper = 0;
		} else {
			ret = -EIO;
			ioinfo[irq]->devstat.flag &= ~DEVSTAT_NOT_OPER;
			ioinfo[irq]->ui.flags.oper = 1;

		}

		break;

	case 2:		/* busy */

		ret = -EBUSY;
		break;

	default:		/* device not operational */

		ret = -ENODEV;
		break;

	}

	if (flag & DOIO_WAIT_FOR_INTERRUPT) {
		disable_cpu_sync_isc (irq);

	}

	return (ret);
}

/*
 * Note: The "intparm" parameter is not used by the clear_IO() function
 *       itself, as no ORB is built for the CSCH instruction. However,
 *       it allows the device interrupt handler to associate the upcoming
 *       interrupt with the clear_IO() request.
 */
int
clear_IO (int irq, unsigned long user_intparm, unsigned long flag)
{				/* possible DOIO_WAIT_FOR_INTERRUPT */
	int ret = 0;
	int ccode;
	char dbf_txt[15];

	SANITY_CHECK (irq);

	if (ioinfo[irq] == INVALID_STORAGE_AREA)
		return (-ENODEV);

	if (ioinfo[irq]->ui.flags.noio)
		return -EBUSY;
	/*
	 * we only allow for clear_IO if the device has an I/O handler associated
	 */
	if (!ioinfo[irq]->ui.flags.ready)
		return -ENODEV;
	/*
	 * we ignore the clear_io() request if ending_status was received but
	 *  a SENSE operation is waiting for completion.
	 */
	if (ioinfo[irq]->ui.flags.w4sense)
		return 0;

	sprintf (dbf_txt, "clearIO%x", irq);
	CIO_TRACE_EVENT (2, dbf_txt);

	/*
	 * If sync processing was requested we lock the sync ISC,
	 *  modify the device to present interrupts for this ISC only
	 *  and switch the CPU to handle this ISC + the console ISC
	 *  exclusively.
	 */
	if (flag & DOIO_WAIT_FOR_INTERRUPT) {
		ret = enable_cpu_sync_isc (irq);

		if (ret)
			return (ret);
	}

	/*
	 * Issue "Clear subchannel" and process condition code
	 */
	ccode = csch (irq);

	sprintf (dbf_txt, "ccode:%d", ccode);
	CIO_TRACE_EVENT (2, dbf_txt);

	switch (ccode) {
	case 0:

		ioinfo[irq]->ui.flags.haltio = 1;

		if (!ioinfo[irq]->ui.flags.doio) {
			ioinfo[irq]->ui.flags.busy = 1;
			ioinfo[irq]->u_intparm = user_intparm;
			ioinfo[irq]->devstat.cstat = 0;
			ioinfo[irq]->devstat.dstat = 0;
			ioinfo[irq]->devstat.lpum = 0;
			ioinfo[irq]->devstat.flag = DEVSTAT_CLEAR_FUNCTION;
			ioinfo[irq]->devstat.scnt = 0;

		} else {
			ioinfo[irq]->devstat.flag |= DEVSTAT_CLEAR_FUNCTION;

		}

		/*
		 * If synchronous I/O processing is requested, we have
		 *  to wait for the corresponding interrupt to occur by
		 *  polling the interrupt condition. However, as multiple
		 *  interrupts may be outstanding, we must not just wait
		 *  for the first interrupt, but must poll until ours
		 *  pops up.
		 */
		if (flag & DOIO_WAIT_FOR_INTERRUPT) {
			int io_sub;
			__u32 io_parm;
			unsigned long psw_mask;
			int ccode;

			int ready = 0;

			/*
			 * We shouldn't perform a TPI loop, waiting for
			 *  an interrupt to occur, but should load a
			 *  WAIT PSW instead. Otherwise we may keep the
			 *  channel subsystem busy, not able to present
			 *  the interrupt. When our sync. interrupt
			 *  arrived we reset the I/O old PSW to its
			 *  original value.
			 */

			ccode = iac ();

			switch (ccode) {
			case 0:	/* primary-space */
				psw_mask = _IO_PSW_MASK
				    | _PSW_PRIM_SPACE_MODE | _PSW_IO_WAIT;
				break;
			case 1:	/* secondary-space */
				psw_mask = _IO_PSW_MASK
				    | _PSW_SEC_SPACE_MODE | _PSW_IO_WAIT;
				break;
			case 2:	/* access-register */
				psw_mask = _IO_PSW_MASK
				    | _PSW_ACC_REG_MODE | _PSW_IO_WAIT;
				break;
			case 3:	/* home-space */
				psw_mask = _IO_PSW_MASK
				    | _PSW_HOME_SPACE_MODE | _PSW_IO_WAIT;
				break;
			default:
				panic ("clear_IO() : unexpected "
				       "address-space-control %d\n", ccode);
				break;
			}

			/*
			 * Martin didn't like modifying the new PSW, now we take
			 *  a fast exit in do_IRQ() instead
			 */
			*(__u32 *) __LC_SYNC_IO_WORD = 1;

			do {
				__load_psw_mask (psw_mask);

				io_parm = *(__u32 *) __LC_IO_INT_PARM;
				io_sub = (__u32) * (__u16 *) __LC_SUBCHANNEL_NR;

				ready = s390_process_IRQ (io_sub);

			} while (!((io_sub == irq) && (ready == 1)));

			*(__u32 *) __LC_SYNC_IO_WORD = 0;

		}

		ret = 0;
		break;

	case 1:		/* no status pending for csh */
		BUG ();
		break;

	case 2:		/* no busy for csh */
		BUG ();
		break;

	default:		/* device not operational */

		ret = -ENODEV;
		break;

	}

	if (flag & DOIO_WAIT_FOR_INTERRUPT) {
		disable_cpu_sync_isc (irq);

	}

	return (ret);
}

/*
 * Function: cancel_IO
 * Issues a "Cancel Subchannel" on the specified subchannel
 * Note: We don't need any fancy intparms and flags here
 *       since xsch is executed synchronously.
 * Only for common I/O internal use as for now.
 */
int
cancel_IO (int irq)
{

	int ccode;
	char dbf_txt[15];
	int ret = 0;

	SANITY_CHECK (irq);

	sprintf (dbf_txt, "cancelIO%x", irq);
	CIO_TRACE_EVENT (2, dbf_txt);

	ccode = xsch (irq);

	sprintf (dbf_txt, "ccode:%d", ccode);
	CIO_TRACE_EVENT (2, dbf_txt);

	switch (ccode) {

	case 0:		/* success */
		ret = 0;
		break;

	case 1:		/* status pending */

		ret = -EBUSY;
		break;

	case 2:		/* not applicable */
		ret = -EINVAL;
		break;

	default:		/* not oper */
		ret = -ENODEV;
	}

	return ret;
}

/*
 * do_IRQ() handles all normal I/O device IRQ's (the special
 *          SMP cross-CPU interrupts have their own specific
 *          handlers).
 *
 */
asmlinkage void
do_IRQ (struct pt_regs regs)
{
	/*
	 * Get interrupt info from lowcore
	 */
	volatile tpi_info_t *tpi_info = (tpi_info_t *) (__LC_SUBCHANNEL_ID);
	int cpu = smp_processor_id ();

	/*
	 * take fast exit if CPU is in sync. I/O state
	 *
	 * Note: we have to turn off the WAIT bit and re-disable
	 *       interrupts prior to return as this was the initial
	 *       entry condition to synchronous I/O.
	 */
	if (*(__u32 *) __LC_SYNC_IO_WORD) {
		regs.psw.mask &= ~(_PSW_WAIT_MASK_BIT | _PSW_IO_MASK_BIT);
		return;
	}
	/* endif */
#ifdef CONFIG_FAST_IRQ
	do {
#endif				/* CONFIG_FAST_IRQ */

		/*
		 * Non I/O-subchannel thin interrupts are processed differently
		 */
		if (tpi_info->adapter_IO == 1 &&
		    tpi_info->int_type == IO_INTERRUPT_TYPE) {
			irq_enter (cpu, -1);
			do_adapter_IO (tpi_info->intparm);
			irq_exit (cpu, -1);
		} else {
			unsigned int irq = tpi_info->irq;

			/*
			 * fix me !!!
			 *
			 * instead of boxing the device, we need to schedule device
			 * recognition, the interrupt stays pending. We need to
			 * dynamically allocate an ioinfo structure, etc..
			 */
			if (ioinfo[irq] == INVALID_STORAGE_AREA) {
				return;	/* this keeps the device boxed ... */
			}

			if (ioinfo[irq]->st) {
				/* How can that be? */
				printk(KERN_WARNING "Received interrupt on "
				       "non-IO subchannel %x!\n", irq);
				return;
			}

			irq_enter (cpu, irq);
			s390irq_spin_lock (irq);
			s390_process_IRQ (irq);
			s390irq_spin_unlock (irq);
			irq_exit (cpu, irq);
		}

#ifdef CONFIG_FAST_IRQ

		/*
		 * Are more interrupts pending?
		 * If so, the tpi instruction will update the lowcore 
		 * to hold the info for the next interrupt.
		 */
	} while (tpi (NULL) != 0);

#endif				/* CONFIG_FAST_IRQ */

	return;
}

/*
 * s390_process_IRQ() handles status pending situations and interrupts
 *
 * Called by : do_IRQ()             - for "real" interrupts
 *             s390_start_IO, halt_IO()
 *                                  - status pending cond. after SSCH, or HSCH
 *             disable_subchannel() - status pending conditions (after MSCH)
 *
 * Returns: 0 - no ending status received, no further action taken
 *          1 - interrupt handler was called with ending status
 */
int
s390_process_IRQ (unsigned int irq)
{
	int ccode;		/* cond code from tsch() operation */
	int irb_cc;		/* cond code from irb */
	int sdevstat;		/* struct devstat size to copy */
	unsigned int fctl;	/* function control */
	unsigned int stctl;	/* status   control */
	unsigned int actl;	/* activity control */

	int issense = 0;
	int ending_status = 0;
	int allow4handler = 1;
	int chnchk = 0;
	devstat_t *dp;
	devstat_t *udp;

	char dbf_txt[15];
	char buffer[256];

	if (cio_count_irqs) {
		int cpu = smp_processor_id ();
		s390_irq_count[cpu]++;
	}

	CIO_TRACE_EVENT (3, "procIRQ");
	sprintf (dbf_txt, "%x", irq);
	CIO_TRACE_EVENT (3, dbf_txt);

	if (ioinfo[irq] == INVALID_STORAGE_AREA) {
		/* we can't properly process the interrupt ... */
#ifdef CONFIG_DEBUG_IO
		printk (KERN_CRIT "s390_process_IRQ(%04X) - got interrupt "
			"for non-initialized subchannel!\n", irq);
#endif /* CONFIG_DEBUG_IO */
		CIO_MSG_EVENT (0,
			       "s390_process_IRQ(%04X) - got interrupt "
			       "for non-initialized subchannel!\n",
			       irq);
		tsch (irq, p_init_irb);
		return (1);

	}

	if (ioinfo[irq]->st) {
		/* can't be */
		BUG();
		return 1;
	}

	dp = &ioinfo[irq]->devstat;
	udp = ioinfo[irq]->irq_desc.dev_id;
	
	/*
	 * It might be possible that a device was not-oper. at the time
	 *  of free_irq() processing. This means the handler is no longer
	 *  available when the device possibly becomes ready again. In
	 *  this case we perform delayed disable_subchannel() processing.
	 */
	if (!ioinfo[irq]->ui.flags.ready) {
		if (!ioinfo[irq]->ui.flags.d_disable) {
#ifdef CONFIG_DEBUG_IO
			printk (KERN_CRIT "s390_process_IRQ(%04X) "
				"- no interrupt handler registered "
				"for device %04X !\n",
				irq, ioinfo[irq]->devstat.devno);
#endif				/* CONFIG_DEBUG_IO */
			CIO_MSG_EVENT(0,
				      "s390_process_IRQ(%04X) "
				      "- no interrupt handler "
				      "registered for device "
				      "%04X !\n",
				      irq,
				      ioinfo[irq]->devstat.devno);
		}
	}

	/*
	 * retrieve the i/o interrupt information (irb),
	 *  update the device specific status information
	 *  and possibly call the interrupt handler.
	 *
	 * Note 1: At this time we don't process the resulting
	 *         condition code (ccode) from tsch(), although
	 *         we probably should.
	 *
	 * Note 2: Here we will have to check for channel
	 *         check conditions and call a channel check
	 *         handler.
	 *
	 * Note 3: If a start function was issued, the interruption
	 *         parameter relates to it. If a halt function was
	 *         issued for an idle device, the intparm must not
	 *         be taken from lowcore, but from the devstat area.
	 */
	ccode = tsch (irq, &(dp->ii.irb));

	sprintf (dbf_txt, "ccode:%d", ccode);
	CIO_TRACE_EVENT (3, dbf_txt);

	if (ccode == 1) {
#ifdef CONFIG_DEBUG_IO
		printk (KERN_INFO "s390_process_IRQ(%04X) - no status "
			 "pending...\n", irq);
#endif /* CONFIG_DEBUG_IO */
		CIO_MSG_EVENT(2,
			      "s390_process_IRQ(%04X) - no status pending\n",
			      irq);
	} else if (ccode == 3) {
#ifdef CONFIG_DEBUG_IO
		printk (KERN_WARNING "s390_process_IRQ(%04X) - subchannel "
			"is not operational!\n",
			irq);
#endif /* CONFIG_DEBUG_IO */
		CIO_MSG_EVENT(0,
			      "s390_process_IRQ(%04X) - subchannel "
			      "is not operational!\n",
			      irq);
	}

	/*
	 * We must only accumulate the status if the device is busy already
	 */
	if (ioinfo[irq]->ui.flags.busy) {
		dp->dstat |= dp->ii.irb.scsw.dstat;
		dp->cstat |= dp->ii.irb.scsw.cstat;
		dp->intparm = ioinfo[irq]->u_intparm;

	} else {
		dp->dstat = dp->ii.irb.scsw.dstat;
		dp->cstat = dp->ii.irb.scsw.cstat;

		dp->flag = 0;	/* reset status flags */
		dp->intparm = 0;

	}

	dp->lpum = dp->ii.irb.esw.esw1.lpum;

	/*
	 * reset device-busy bit if no longer set in irb
	 */
	if ((dp->dstat & DEV_STAT_BUSY)
	    && ((dp->ii.irb.scsw.dstat & DEV_STAT_BUSY) == 0)) {
		dp->dstat &= ~DEV_STAT_BUSY;

	}

	/*
	   * Save residual count and CCW information in case primary and
	   *  secondary status are presented with different interrupts.
	 */
	if (dp->ii.irb.scsw.stctl
	    & (SCSW_STCTL_PRIM_STATUS | SCSW_STCTL_INTER_STATUS)) {

		/*
		 * If the subchannel status shows status pending
		 * and we received a check condition, the count
		 * information is not meaningful.
		 */

		if (!((dp->ii.irb.scsw.stctl & SCSW_STCTL_STATUS_PEND)
		      && (dp->ii.irb.scsw.cstat
			  & (SCHN_STAT_CHN_DATA_CHK
			     | SCHN_STAT_CHN_CTRL_CHK
			     | SCHN_STAT_INTF_CTRL_CHK
			     | SCHN_STAT_PROG_CHECK
			     | SCHN_STAT_PROT_CHECK
			     | SCHN_STAT_CHAIN_CHECK)))) {

			dp->rescnt = dp->ii.irb.scsw.count;
		} else {
			dp->rescnt = SENSE_MAX_COUNT;
		}

		dp->cpa = dp->ii.irb.scsw.cpa;
		
	}
	irb_cc = dp->ii.irb.scsw.cc;

	/*
	 * check for any kind of channel or interface control check but don't
	 * issue the message for the console device
	 */
	if ((dp->ii.irb.scsw.cstat
	     & (SCHN_STAT_CHN_DATA_CHK
		| SCHN_STAT_CHN_CTRL_CHK | SCHN_STAT_INTF_CTRL_CHK))) {
		if (irq != cons_dev)
			printk (KERN_WARNING
				"Channel-Check or Interface-Control-Check "
				"received\n"
				" ... device %04X on subchannel %04X, dev_stat "
				": %02X sch_stat : %02X\n",
				ioinfo[irq]->devstat.devno, irq, dp->dstat,
				dp->cstat);
		CIO_MSG_EVENT(0,
			      "Channel-Check or "
			      "Interface-Control-Check received\n");
		CIO_MSG_EVENT(0,
			      "... device %04X on subchannel %04X,"
			      " dev_stat: %02X sch_stat: %02X\n",
			      ioinfo[irq]->devstat.devno, irq,
			      dp->dstat, dp->cstat);
	

		chnchk = 1;

	}

	if (dp->ii.irb.scsw.ectl == 0) {
		issense = 0;
	} else if ((dp->ii.irb.scsw.stctl == SCSW_STCTL_STATUS_PEND)
		   && (dp->ii.irb.scsw.eswf == 0)) {
		issense = 0;
	} else if ((dp->ii.irb.scsw.stctl ==
		    (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_INTER_STATUS))
		   && ((dp->ii.irb.scsw.actl & SCSW_ACTL_SUSPENDED) == 0)) {
		issense = 0;
	} else {
		issense = dp->ii.irb.esw.esw0.erw.cons;

	}

	if (issense) {
		dp->scnt = dp->ii.irb.esw.esw0.erw.scnt;
		dp->flag |= DEVSTAT_FLAG_SENSE_AVAIL;

		sdevstat = sizeof (devstat_t);

#ifdef CONFIG_DEBUG_IO
		if (irq != cons_dev)
			printk (KERN_DEBUG "s390_process_IRQ( %04X ) : "
				"concurrent sense bytes avail %d\n",
				irq, dp->scnt);
#endif
		CIO_MSG_EVENT(4,
			      "s390_process_IRQ( %04X ): "
			      "concurrent sense bytes avail %d\n",
			      irq, dp->scnt);
	} else {
		/* don't copy the sense data area ! */
		sdevstat = sizeof (devstat_t) - SENSE_MAX_COUNT;

	}

	switch (irb_cc) {
	case 1:		/* status pending */

		dp->flag |= DEVSTAT_STATUS_PENDING;

	case 0:		/* normal i/o interruption */

		fctl = dp->ii.irb.scsw.fctl;
		stctl = dp->ii.irb.scsw.stctl;
		actl = dp->ii.irb.scsw.actl;

		if (chnchk) {
			sprintf (buffer, "s390_process_IRQ(%04X) - irb for "
				 "device %04X after channel check "
				 "or interface control check\n",
				 irq, dp->devno);

			s390_displayhex (buffer, &(dp->ii.irb), sizeof (irb_t));
			sprintf(dbf_txt, "chk%x", irq);
			CIO_TRACE_EVENT(0, dbf_txt);
			CIO_HEX_EVENT(0, &(dp->ii.irb), sizeof (irb_t));
		}

		ioinfo[irq]->stctl |= stctl;

		ending_status = (stctl & SCSW_STCTL_SEC_STATUS)
		    || (stctl ==
			(SCSW_STCTL_ALERT_STATUS | SCSW_STCTL_STATUS_PEND))
		    || (stctl == SCSW_STCTL_STATUS_PEND);

		/*
		 * Check for unsolicited interrupts - for debug purposes only
		 *
		 * We only consider an interrupt as unsolicited, if the device was not
		 *  actively in use (busy) and an interrupt other than an ALERT status
		 *  was received.
		 *
		 * Note: We must not issue a message to the console, if the
		 *       unsolicited interrupt applies to the console device
		 *       itself !
		 */
		if (!(stctl & SCSW_STCTL_ALERT_STATUS)
		    && (ioinfo[irq]->ui.flags.busy == 0)) {

#ifdef CONFIG_DEBUG_IO
			if (irq != cons_dev)
				printk (KERN_INFO
					"Unsolicited interrupt received for "
					"device %04X on subchannel %04X\n"
					" ... device status : %02X "
					"subchannel status : %02X\n",
					dp->devno, irq, dp->dstat, dp->cstat);

			sprintf (buffer, "s390_process_IRQ(%04X) - irb for "
				 "device %04X, ending_status %d\n",
				 irq, dp->devno, ending_status);

			s390_displayhex (buffer, &(dp->ii.irb), sizeof (irb_t));
#endif
			CIO_MSG_EVENT(2,
				      "Unsolicited interrupt "
				      "received for device %04X "
				      "on subchannel %04X\n"
				      " ... device status : %02X "
				      "subchannel status : %02X\n",
				      dp->devno,
				      irq, dp->dstat, dp->cstat);
			sprintf(dbf_txt, "uint%x", irq);
			CIO_TRACE_EVENT(2, dbf_txt);
			CIO_HEX_EVENT(2, &(dp->ii.irb), sizeof (irb_t));
		}

		/*
		 * take fast exit if no handler is available
		 */
		if (!ioinfo[irq]->ui.flags.ready)
			return (ending_status);

		/*
		 * Check whether we must issue a SENSE CCW ourselves if there is no
		 *  concurrent sense facility installed for the subchannel.
		 *
		 * Note: We should check for ioinfo[irq]->ui.flags.consns but VM
		 *       violates the ESA/390 architecture and doesn't present an
		 *       operand exception for virtual devices without concurrent
		 *       sense facility available/supported when enabling the
		 *       concurrent sense facility.
		 */
		if (((dp->ii.irb.scsw.dstat & DEV_STAT_UNIT_CHECK)
		     && (!issense))
		    || (ioinfo[irq]->ui.flags.delsense && ending_status)) {
			int ret_io;
			ccw1_t *s_ccw = &ioinfo[irq]->senseccw;
			unsigned long s_flag = 0;

			if (ending_status) {
				/* there is a chance that the command
				 * that gave us the unit check actually
				 * was a basic sense, so we must not
				 * overwrite *udp in that case
				 */
				if (ioinfo[irq]->ui.flags.w4sense &&
					(dp->ii.irb.scsw.dstat & DEV_STAT_UNIT_CHECK)) {
					CIO_MSG_EVENT(4,"double unit check irq %04x, dstat %02x," 
							"flags %8x\n", irq, dp->ii.irb.scsw.dstat,
							 ioinfo[irq]->ui.info, ending_status);
				} else {
				/*
				 * We copy the current status information into the device driver
				 *  status area. Then we can use the local devstat area for device
				 *  sensing. When finally calling the IRQ handler we must not overlay
				 *  the original device status but copy the sense data only.
				 */
					memcpy (udp, dp, sizeof (devstat_t));
				}

				s_ccw->cmd_code = CCW_CMD_BASIC_SENSE;
				s_ccw->cda =
				    (__u32) virt_to_phys (ioinfo[irq]->
							  sense_data);
				s_ccw->count = SENSE_MAX_COUNT;
				s_ccw->flags = CCW_FLAG_SLI;

				/*
				 * If free_irq() or a sync do_IO/s390_start_IO() is in
				 *  process we have to sense synchronously
				 */
				if (ioinfo[irq]->ui.flags.unready
				    || ioinfo[irq]->ui.flags.syncio)
					s_flag = DOIO_WAIT_FOR_INTERRUPT
						|  DOIO_TIMEOUT
						|  DOIO_VALID_LPM;

				else
					s_flag = DOIO_VALID_LPM;

				/*
				 * Reset status info
				 *
				 * It does not matter whether this is a sync. or async.
				 *  SENSE request, but we have to assure we don't call
				 *  the irq handler now, but keep the irq in busy state.
				 *  In sync. mode s390_process_IRQ() is called recursively,
				 *  while in async. mode we re-enter do_IRQ() with the
				 *  next interrupt.
				 *
				 * Note : this may be a delayed sense request !
				 */
				allow4handler = 0;

				ioinfo[irq]->ui.flags.fast = 0;
				ioinfo[irq]->ui.flags.repall = 0;
				ioinfo[irq]->ui.flags.w4final = 0;
				ioinfo[irq]->ui.flags.delsense = 0;

				dp->cstat = 0;
				dp->dstat = 0;
				dp->rescnt = SENSE_MAX_COUNT;

				ioinfo[irq]->ui.flags.w4sense = 1;

				ret_io = s390_start_IO (irq, s_ccw, 0xE2C5D5E2,	/* = SENSe */
							0xff,
							s_flag);
				switch (ret_io) {
				case 0: /* OK */
					break;
				case -ENODEV:
					/*
					 * The device is no longer operational.
					 * We won't get any sense data.
					 */
					ioinfo[irq]->ui.flags.w4sense = 0;
					ioinfo[irq]->ui.flags.oper = 0;
					allow4handler = 1; /* to notify the driver */
					break;
				case -EBUSY:
					/*
					 * The channel subsystem is either busy, or we have
					 * a status pending. Retry later.
					 */
					ioinfo[irq]->ui.flags.w4sense = 0;
					ioinfo[irq]->ui.flags.delsense = 1;
					break;
				default:
					printk(KERN_ERR"irq %04X: Unexpected rc %d "
					       "for BASIC SENSE!\n", irq, ret_io);
					ioinfo[irq]->ui.flags.w4sense = 0;
					allow4handler = 1;
				}
			} else {
				/*
				 * we received an Unit Check but we have no final
				 *  status yet, therefore we must delay the SENSE
				 *  processing. However, we must not report this
				 *  intermediate status to the device interrupt
				 *  handler.
				 */
				ioinfo[irq]->ui.flags.fast = 0;
				ioinfo[irq]->ui.flags.repall = 0;

				ioinfo[irq]->ui.flags.delsense = 1;
				allow4handler = 0;

			}

		}

		/*
		 * we allow for the device action handler if .
		 *  - we received ending status
		 *  - the action handler requested to see all interrupts
		 *  - we received an intermediate status
		 *  - fast notification was requested (primary status)
		 *  - unsollicited interrupts
		 *
		 */
		if (allow4handler) {
			allow4handler = ending_status
			    || (ioinfo[irq]->ui.flags.repall)
			    || (stctl & SCSW_STCTL_INTER_STATUS)
			    || ((ioinfo[irq]->ui.flags.fast)
				&& (stctl & SCSW_STCTL_PRIM_STATUS))
			    || (ioinfo[irq]->ui.flags.oper == 0);

		}

		/*
		 * We used to copy the device status information right before
		 *  calling the device action handler. However, in status
		 *  pending situations during do_IO() or halt_IO(), as well as
		 *  enable_subchannel/disable_subchannel processing we must
		 *  synchronously return the status information and must not
		 *  call the device action handler.
		 *
		 */
		if (allow4handler) {
			/*
			 * if we were waiting for sense data we copy the sense
			 *  bytes only as the original status information was
			 *  saved prior to sense already.
			 */
			if (ioinfo[irq]->ui.flags.w4sense) {
				int sense_count =
				    SENSE_MAX_COUNT -
				    ioinfo[irq]->devstat.rescnt;

#ifdef CONFIG_DEBUG_IO
				if (irq != cons_dev)
					printk (KERN_DEBUG
						"s390_process_IRQ( %04X ) : "
						"BASIC SENSE bytes avail %d\n",
						irq, sense_count);
#endif
				CIO_MSG_EVENT(4,
					      "s390_process_IRQ( %04X ): "
					      "BASIC SENSE bytes avail %d\n",
					      irq, sense_count);
				ioinfo[irq]->ui.flags.w4sense = 0;
				udp->flag |= DEVSTAT_FLAG_SENSE_AVAIL;
				udp->scnt = sense_count;

				if (sense_count > 0) {
					memcpy (udp->ii.sense.data,
						ioinfo[irq]->sense_data,
						sense_count);
				} else if (sense_count == 0) {
					udp->flag &= ~DEVSTAT_FLAG_SENSE_AVAIL;
				} else {
					panic
					    ("s390_process_IRQ(%04x) encountered "
					     "negative sense count\n", irq);

				}
			} else {
				memcpy (udp, dp, sdevstat);

			}

		}

		/*
		 * for status pending situations other than deferred interrupt
		 *  conditions detected by s390_process_IRQ() itself we must not
		 *  call the handler. This will synchronously be reported back
		 *  to the caller instead, e.g. when detected during do_IO().
		 */
		if (ioinfo[irq]->ui.flags.s_pend
		    || ioinfo[irq]->ui.flags.unready
		    || ioinfo[irq]->ui.flags.repnone) {
			if (ending_status) {

				ioinfo[irq]->ui.flags.busy = 0;
				ioinfo[irq]->ui.flags.doio = 0;
				ioinfo[irq]->ui.flags.haltio = 0;
				ioinfo[irq]->ui.flags.fast = 0;
				ioinfo[irq]->ui.flags.repall = 0;
				ioinfo[irq]->ui.flags.w4final = 0;

				dp->flag |= DEVSTAT_FINAL_STATUS;
				udp->flag |= DEVSTAT_FINAL_STATUS;

			}

			allow4handler = 0;

		}

		/*
		 * Call device action handler if applicable
		 */
		if (allow4handler) {

			/*
			 *  We only reset the busy condition when we are sure that no further
			 *   interrupt is pending for the current I/O request (ending_status).
			 */
			if (ending_status || !ioinfo[irq]->ui.flags.oper) {
				ioinfo[irq]->ui.flags.oper = 1;	/* dev IS oper */

				ioinfo[irq]->ui.flags.busy = 0;
				ioinfo[irq]->ui.flags.doio = 0;
				ioinfo[irq]->ui.flags.haltio = 0;
				ioinfo[irq]->ui.flags.fast = 0;
				ioinfo[irq]->ui.flags.repall = 0;
				ioinfo[irq]->ui.flags.w4final = 0;

				dp->flag |= DEVSTAT_FINAL_STATUS;
				udp->flag |= DEVSTAT_FINAL_STATUS;

				if (!ioinfo[irq]->ui.flags.killio)
					ioinfo[irq]->irq_desc.handler (irq, udp, NULL);

				/*
				 * reset intparm after final status or we will badly present unsolicited
				 *  interrupts with a intparm value possibly no longer valid.
				 */
				dp->intparm = 0;

			} else {
				ioinfo[irq]->ui.flags.w4final = 1;

				/*
				 * Eventually reset subchannel PCI status and
				 *  set the PCI or SUSPENDED flag in the user
				 *  device status block if appropriate.
				 */
				if (dp->cstat & SCHN_STAT_PCI) {
					udp->flag |= DEVSTAT_PCI;
					dp->cstat &= ~SCHN_STAT_PCI;
				}

				if (actl & SCSW_ACTL_SUSPENDED) {
					udp->flag |= DEVSTAT_SUSPENDED;

				}
				
				ioinfo[irq]->irq_desc.handler (irq, udp, NULL);

			}

		}

		break;

	case 3:		/* device/path not operational */

		ioinfo[irq]->ui.flags.busy = 0;
		ioinfo[irq]->ui.flags.doio = 0;
		ioinfo[irq]->ui.flags.haltio = 0;

		dp->cstat = 0;
		dp->dstat = 0;

 		if ((dp->ii.irb.scsw.fctl != 0) &&
 		    ((dp->ii.irb.scsw.stctl & SCSW_STCTL_STATUS_PEND) != 0) &&
 		    (((dp->ii.irb.scsw.stctl & SCSW_STCTL_INTER_STATUS) == 0) ||
 		     ((dp->ii.irb.scsw.actl & SCSW_ACTL_SUSPENDED) != 0)))
 			if (dp->ii.irb.scsw.pno) {
 				stsch(irq, &ioinfo[irq]->schib);
 				ioinfo[irq]->opm &=
 					~ioinfo[irq]->schib.pmcw.pnom;
 			}

		if (ioinfo[irq]->opm == 0) {
			ioinfo[irq]->ui.flags.oper = 0;

		}

		ioinfo[irq]->devstat.flag |= DEVSTAT_NOT_OPER;
		ioinfo[irq]->devstat.flag |= DEVSTAT_FINAL_STATUS;


               /*
                * When we find a device "not oper" we save the status
                *  information into the device status area and call the
                *  device specific interrupt handler.
                *
                * Note: currently we don't have any way to reenable
                *       the device unless an unsolicited interrupt
                *       is presented. We don't check for spurious
                *       interrupts on "not oper" conditions.
                */

		
		ioinfo[irq]->ui.flags.fast = 0;
		ioinfo[irq]->ui.flags.repall = 0;
		ioinfo[irq]->ui.flags.w4final = 0;

		/*
		 * take fast exit if no handler is available
		 */
		if (!ioinfo[irq]->ui.flags.ready)
			return (ending_status);

		/*
		 * Special case: We got a deferred cc 3 on a basic sense.
		 * We have to notify the device driver of the former unit
		 * check, but must not confuse it by calling it with the status
		 * for the failed basic sense.
		 */
		if (ioinfo[irq]->ui.flags.w4sense)
			ioinfo[irq]->ui.flags.w4sense = 0;
		else
			memcpy (udp, &(ioinfo[irq]->devstat), sdevstat);

		ioinfo[irq]->devstat.intparm = 0;

		if (!ioinfo[irq]->ui.flags.s_pend
		    && !ioinfo[irq]->ui.flags.repnone
		    && !ioinfo[irq]->ui.flags.killio) {

			ioinfo[irq]->irq_desc.handler (irq, udp, NULL);
		}

		ending_status = 1;

		break;

	}

	if (ending_status && 
	    ioinfo[irq]->ui.flags.noio &&
	    !ioinfo[irq]->ui.flags.syncio &&
	    !ioinfo[irq]->ui.flags.w4sense) {
		if(ioinfo[irq]->ui.flags.ready) {
			s390_schedule_path_verification(irq);
		} else {
			ioinfo[irq]->ui.flags.killio = 0;
			ioinfo[irq]->ui.flags.noio = 0;
		}
	}

	return (ending_status);
}

/*
 * Set the special i/o-interruption sublass 7 for the
 *  device specified by parameter irq. There can only
 *  be a single device been operated on this special
 *  isc. This function is aimed being able to check
 *  on special device interrupts in disabled state,
 *  without having to delay I/O processing (by queueing)
 *  for non-console devices.
 *
 * Setting of this isc is done by set_cons_dev(). 
 *  wait_cons_dev() allows
 *  to actively wait on an interrupt for this device in
 *  disabed state. When the interrupt condition is
 *  encountered, wait_cons_dev(9 calls do_IRQ() to have
 *  the console device driver processing the interrupt.
 */
int
set_cons_dev (int irq)
{
	int ccode;
	int rc = 0;
	char dbf_txt[15];

	SANITY_CHECK (irq);

	if (cons_dev != -1)
		return -EBUSY;

	sprintf (dbf_txt, "scons%x", irq);
	CIO_TRACE_EVENT (4, dbf_txt);

	/*
	 * modify the indicated console device to operate
	 *  on special console interrupt sublass 7
	 */
	ccode = stsch (irq, &(ioinfo[irq]->schib));

	if (ccode) {
		rc = -ENODEV;
		ioinfo[irq]->devstat.flag |= DEVSTAT_NOT_OPER;
	} else {
		ioinfo[irq]->schib.pmcw.isc = 7;

		ccode = msch (irq, &(ioinfo[irq]->schib));

		if (ccode) {
			rc = -EIO;
		} else {
			cons_dev = irq;

			/*
			 * enable console I/O-interrupt sublass 7
			 */
			ctl_set_bit (6, 24);

		}
	}

	return (rc);
}

int
wait_cons_dev (int irq)
{
	int rc = 0;
	long save_cr6;
	char dbf_txt[15];

	if (irq != cons_dev)
		return -EINVAL;

	sprintf (dbf_txt, "wcons%x", irq);
	CIO_TRACE_EVENT (4, dbf_txt);

	/*
	 * before entering the spinlock we may already have
	 *  processed the interrupt on a different CPU ...
	 */
	if (ioinfo[irq]->ui.flags.busy == 1) {
		long cr6 __attribute__ ((aligned (8)));

		/*
		 * disable all, but isc 7 (console device)
		 */
		__ctl_store (cr6, 6, 6);
		save_cr6 = cr6;
		cr6 &= 0x01FFFFFF;
		__ctl_load (cr6, 6, 6);

		do {
			tpi_info_t tpi_info = { 0, };
			if (tpi (&tpi_info) == 1) {
				s390_process_IRQ (tpi_info.irq);
			} else {
				s390irq_spin_unlock (irq);
				udelay (100);
				s390irq_spin_lock (irq);
			}
			eieio ();
		} while (ioinfo[irq]->ui.flags.busy == 1);

		/*
		 * restore previous isc value
		 */
		cr6 = save_cr6;
		__ctl_load (cr6, 6, 6);

	}

	return (rc);
}

int
enable_cpu_sync_isc (int irq)
{
	int ccode;
	long cr6 __attribute__ ((aligned (8)));

	int retry = 3;
	int rc = 0;
	char dbf_txt[15];

	sprintf (dbf_txt, "enisc%x", irq);
	CIO_TRACE_EVENT (4, dbf_txt);

	/* This one spins until it can get the sync_isc lock for irq# irq */

	if ((irq <= highest_subchannel) && 
	    (ioinfo[irq] != INVALID_STORAGE_AREA) &&
	    (!ioinfo[irq]->st)) {
		if (atomic_read (&sync_isc) != irq)
			atomic_compare_and_swap_spin (-1, irq, &sync_isc);

		sync_isc_cnt++;

		if (sync_isc_cnt > 255) {	/* fixme : magic number */
			panic ("Too many recursive calls to enable_sync_isc");

		}
		/*
		 * we only run the STSCH/MSCH path for the first enablement
		 */
		else if (sync_isc_cnt == 1) {

			ccode = stsch (irq, &(ioinfo[irq]->schib));

			if (!ccode) {
				ioinfo[irq]->schib.pmcw.isc = 5;

				do {
					ccode = msch (irq,
						      &(ioinfo[irq]->schib));

					switch (ccode) {
					case 0:
						/*
						 * enable special isc
						 */
						__ctl_store (cr6, 6, 6);
						/* enable sync isc 5 */
						cr6 |= 0x04000000;
						/* disable standard isc 3 */
						cr6 &= 0xEFFFFFFF;
						/* disable console isc 7 */
						cr6 &= 0xFEFFFFFF;
						ioinfo[irq]->ui.flags.syncio = 1;
						__ctl_load (cr6, 6, 6);
						rc = 0;
						retry = 0;
						break;

					case 1:
						/*
						 * process pending status
						 */
						ioinfo[irq]->ui.flags.s_pend =
						    1;
						s390_process_IRQ (irq);
						ioinfo[irq]->ui.flags.s_pend =
						    0;

						rc = -EIO;	/* might be overwritten... */
						retry--;
						break;

					case 2:	/* busy */
						retry = 0;
						rc = -EBUSY;
						break;

					case 3:	/* not oper */
						retry = 0;
						rc = -ENODEV;
						break;

					}

				} while (retry);

			} else {
				rc = -ENODEV;	/* device is not-operational */

			}
		}

		if (rc) {	/* can only happen if stsch/msch fails */
			sync_isc_cnt = 0;
			atomic_set (&sync_isc, -1);
		} else if (sync_isc_cnt == 1) {
			int ccode;
			
			ccode = stsch(irq, &ioinfo[irq]->schib);
			if (!ccode && ioinfo[irq]->schib.pmcw.isc != 5) {
				ioinfo[irq]->ui.flags.syncio = 0;
				sync_isc_cnt = 0;
				atomic_set (&sync_isc, -1);
			}
		}
	} else {
#ifdef CONFIG_SYNC_ISC_PARANOIA
		panic ("enable_sync_isc: called with invalid %x\n", irq);
#endif

		rc = -EINVAL;

	}

	return (rc);
}

int
disable_cpu_sync_isc (int irq)
{
	int rc = 0;
	int retry1 = 5;
	int retry2 = 5;
	int clear_pend = 0;

	int ccode;
	long cr6 __attribute__ ((aligned (8)));

	char dbf_txt[15];

	sprintf (dbf_txt, "disisc%x", irq);
	CIO_TRACE_EVENT (4, dbf_txt);

	if ((irq <= highest_subchannel) && 
	    (ioinfo[irq] != INVALID_STORAGE_AREA) && 
	    (!ioinfo[irq]->st)) {
		/*
		 * We disable if we're the top user only, as we may
		 *  run recursively ... 
		 * We must not decrease the count immediately; during
		 *  msch() processing we may face another pending
		 *  status we have to process recursively (sync).
		 */

#ifdef CONFIG_SYNC_ISC_PARANOIA
		if (atomic_read (&sync_isc) != irq)
			panic
			    ("disable_sync_isc: called for %x while %x locked\n",
			     irq, atomic_read (&sync_isc));
#endif

		if (sync_isc_cnt == 1) {
			ccode = stsch (irq, &(ioinfo[irq]->schib));

			ioinfo[irq]->schib.pmcw.isc = 3;

			do {
				retry2 = 5;
				do {
					ccode =
					    msch (irq, &(ioinfo[irq]->schib));

					switch (ccode) {
					case 0:
						/*
						 * disable special interrupt subclass in CPU
						 */
						__ctl_store (cr6, 6, 6);
						/* disable sync isc 5 */
						cr6 &= 0xFBFFFFFF;
						/* enable standard isc 3 */
						cr6 |= 0x10000000;
						/* enable console isc 7 */
						cr6 |= 0x01000000;
						__ctl_load (cr6, 6, 6);

						retry2 = 0;
						break;

					case 1:	/* status pending */
						ioinfo[irq]->ui.flags.s_pend =
						    1;
						s390_process_IRQ (irq);
						ioinfo[irq]->ui.flags.s_pend =
						    0;

						retry2--;
						break;

					case 2:	/* busy */
						retry2--;
						udelay (100);	/* give it time */
						break;

					default:	/* not oper */
						retry2 = 0;
						break;
					}

				} while (retry2);

				retry1--;

				/* try stopping it ... */
				if ((ccode) && !clear_pend) {
					clear_IO (irq, 0x00004711, 0);
					clear_pend = 1;

				}

				udelay (100);

			} while (retry1 && ccode);

			ioinfo[irq]->ui.flags.syncio = 0;

			sync_isc_cnt = 0;
			atomic_set (&sync_isc, -1);
		
		} else {
			sync_isc_cnt--;

		}
	} else {
#ifdef CONFIG_SYNC_ISC_PARANOIA
		if (atomic_read (&sync_isc) != -1)
			panic
			    ("disable_sync_isc: called with invalid %x while %x locked\n",
			     irq, atomic_read (&sync_isc));
#endif

		rc = -EINVAL;

	}

	return (rc);
}

int diag210 (diag210_t *addr)
{
        int ccode;

        __asm__ __volatile__(
#ifdef CONFIG_ARCH_S390X
                "   sam31\n"
                "   diag  %1,0,0x210\n"
                "   sam64\n"
#else
                "   diag  %1,0,0x210\n"
#endif
                "   ipm   %0\n"
                "   srl   %0,28"
                : "=d" (ccode) 
		: "a" (addr)
                : "cc" );
        return ccode;
}

/*
 * Input :
 *   devno - device number
 *   ps    - pointer to sense ID data area
 * Output : none
 */
void
VM_virtual_device_info (__u16 devno, senseid_t * ps)
{
	diag210_t *p_diag_data;
	int ccode;

	int error = 0;

	CIO_TRACE_EVENT (4, "VMvdinf");

	if (init_IRQ_complete) {
		p_diag_data = kmalloc (sizeof (diag210_t), GFP_DMA | GFP_ATOMIC);
	} else {
		p_diag_data = alloc_bootmem_low (sizeof (diag210_t));

	}
	if (!p_diag_data)
		return;

	p_diag_data->vrdcdvno = devno;
	p_diag_data->vrdclen = sizeof (diag210_t);
	ccode = diag210 ((diag210_t *) virt_to_phys (p_diag_data));
	ps->reserved = 0xff;

	switch (p_diag_data->vrdcvcla) {
	case 0x80:

		switch (p_diag_data->vrdcvtyp) {
		case 00:

			ps->cu_type = 0x3215;

			break;

		default:

			error = 1;

			break;

		}

		break;

	case 0x40:

		switch (p_diag_data->vrdcvtyp) {
		case 0xC0:

			ps->cu_type = 0x5080;

			break;

		case 0x80:

			ps->cu_type = 0x2250;

			break;

		case 0x04:

			ps->cu_type = 0x3277;

			break;

		case 0x01:

			ps->cu_type = 0x3278;

			break;

		default:

			error = 1;

			break;

		}

		break;

	case 0x20:

		switch (p_diag_data->vrdcvtyp) {
		case 0x84:

			ps->cu_type = 0x3505;

			break;

		case 0x82:

			ps->cu_type = 0x2540;

			break;

		case 0x81:

			ps->cu_type = 0x2501;

			break;

		default:

			error = 1;

			break;

		}

		break;

	case 0x10:

		switch (p_diag_data->vrdcvtyp) {
		case 0x84:

			ps->cu_type = 0x3525;

			break;

		case 0x82:

			ps->cu_type = 0x2540;

			break;

		case 0x4F:
		case 0x4E:
		case 0x48:

			ps->cu_type = 0x3820;

			break;

		case 0x4D:
		case 0x49:
		case 0x45:

			ps->cu_type = 0x3800;

			break;

		case 0x4B:

			ps->cu_type = 0x4248;

			break;

		case 0x4A:

			ps->cu_type = 0x4245;

			break;

		case 0x47:

			ps->cu_type = 0x3262;

			break;

		case 0x43:

			ps->cu_type = 0x3203;

			break;

		case 0x42:

			ps->cu_type = 0x3211;

			break;

		case 0x41:

			ps->cu_type = 0x1403;

			break;

		default:

			error = 1;

			break;

		}

		break;

	case 0x08:

		switch (p_diag_data->vrdcvtyp) {
		case 0x82:

			ps->cu_type = 0x3422;

			break;

		case 0x81:

			ps->cu_type = 0x3490;

			break;

		case 0x10:

			ps->cu_type = 0x3420;

			break;

		case 0x02:

			ps->cu_type = 0x3430;

			break;

		case 0x01:

			ps->cu_type = 0x3480;

			break;

		case 0x42:

			ps->cu_type = 0x3424;

			break;

		case 0x44:

			ps->cu_type = 0x9348;

			break;

		default:

			error = 1;

			break;

		}

		break;

	case 02:		/* special device class ... */

		switch (p_diag_data->vrdcvtyp) {
		case 0x20:	/* OSA */

			ps->cu_type = 0x3088;
			ps->cu_model = 0x60;

			break;

		default:

			error = 1;
			break;

		}

		break;

	default:

		error = 1;

		break;

	}

	if (init_IRQ_complete) {
		kfree (p_diag_data);
	} else {
		free_bootmem ((unsigned long) p_diag_data, sizeof (diag210_t));

	}

	if (error) {
		printk (KERN_ERR "DIAG X'210' for "
			"device %04X returned "
			"(cc = %d): vdev class : %02X, "
			"vdev type : %04X \n"
			" ...  rdev class : %02X, rdev type : %04X, "
			"rdev model: %02X\n",
			devno,
			ccode,
			p_diag_data->vrdcvcla,
			p_diag_data->vrdcvtyp,
			p_diag_data->vrdcrccl,
			p_diag_data->vrdccrty, p_diag_data->vrdccrmd);
		CIO_MSG_EVENT(0,
			      "DIAG X'210' for "
			      "device %04X returned "
			      "(cc = %d): vdev class : %02X, "
			      "vdev type : %04X \n ...  "
			      "rdev class : %02X, rdev type : %04X, "
			      "rdev model: %02X\n",
			      devno,
			      ccode,
			      p_diag_data->vrdcvcla,
			      p_diag_data->vrdcvtyp,
			      p_diag_data->vrdcrccl,
			      p_diag_data->vrdccrty,
			      p_diag_data->vrdccrmd);
		
	}
}

/*
 * This routine returns the characteristics for the device
 *  specified. Some old devices might not provide the necessary
 *  command code information during SenseID processing. In this
 *  case the function returns -EINVAL. Otherwise the function
 *  allocates a decice specific data buffer and provides the
 *  device characteristics together with the buffer size. Its
 *  the callers responability to release the kernel memory if
 *  not longer needed. In case of persistent I/O problems -EBUSY
 *  is returned.
 *
 *  The function may be called enabled or disabled. However, the
 *   caller must have locked the irq it is requesting data for.
 *
 * Note : It would have been nice to collect this information
 *         during init_IRQ() processing but this is not possible
 *
 *         a) without statically pre-allocation fixed size buffers
 *            as virtual memory management isn't available yet.
 *
 *         b) without unnecessarily increase system startup by
 *            evaluating devices eventually not used at all.
 */
int
read_dev_chars (int irq, void **buffer, int length)
{
	unsigned long flags;
	ccw1_t *rdc_ccw;
	devstat_t devstat;
	char *rdc_buf;
	int devflag = 0;

	int ret = 0;
	int emulated = 0;
	int retry = 5;

	char dbf_txt[15];

	if (!buffer || !length) {
		return (-EINVAL);

	}

	SANITY_CHECK (irq);

	if (ioinfo[irq]->ui.flags.oper == 0) {
		return (-ENODEV);

	}

 	if (ioinfo[irq]->ui.flags.unfriendly) {
 		/* don't even try it */
 		return -EUSERS;
 	}

	sprintf (dbf_txt, "rddevch%x", irq);
	CIO_TRACE_EVENT (4, dbf_txt);

	/*
	 * Before playing around with irq locks we should assure
	 *   running disabled on (just) our CPU. Sync. I/O requests
	 *   also require to run disabled.
	 *
	 * Note : as no global lock is required, we must not use
	 *        cli(), but __cli() instead.   
	 */
	__save_flags (flags);
	__cli ();

	rdc_ccw = &ioinfo[irq]->senseccw;

	if (!ioinfo[irq]->ui.flags.ready) {
		ret = request_irq (irq,
				   init_IRQ_handler, SA_PROBE, "RDC", &devstat);

		if (!ret) {
			emulated = 1;

		}

	}

	if (!ret) {
		if (!*buffer) {
			rdc_buf = kmalloc (length, GFP_KERNEL);
		} else {
			rdc_buf = *buffer;

		}

		if (!rdc_buf) {
			ret = -ENOMEM;
		} else {
			do {
				rdc_ccw->cmd_code = CCW_CMD_RDC;
				rdc_ccw->count = length;
				rdc_ccw->flags = CCW_FLAG_SLI;
				ret =
				    set_normalized_cda (rdc_ccw, rdc_buf);
				if (!ret) {

					memset (ioinfo[irq]->irq_desc.dev_id,
						'\0', sizeof (devstat_t));

					ret = s390_start_IO (irq, rdc_ccw, 0x00524443,	/* RDC */
							     0,	/* n/a */
							     DOIO_WAIT_FOR_INTERRUPT
							     |
							     DOIO_DONT_CALL_INTHDLR);
					retry--;
					devflag =
					    ioinfo[irq]->irq_desc.dev_id->flag;

					clear_normalized_cda (rdc_ccw);
				} else {
					udelay (100);	/* wait for recovery */
					retry--;
				}

			} while ((retry)
				 && (ret
				     || (devflag & DEVSTAT_STATUS_PENDING)));

		}

		if (!retry) {
			ret = (ret == -ENOMEM) ? -ENOMEM : -EBUSY;

		}

		__restore_flags (flags);

		/*
		 * on success we update the user input parms
		 */
		if (!ret) {
			*buffer = rdc_buf;

		}

		if (emulated) {
			free_irq (irq, &devstat);

		}

	} else {
		__restore_flags (flags);
	}

	return (ret);
}

/*
 *  Read Configuration data
 */
int
read_conf_data (int irq, void **buffer, int *length, __u8 lpm)
{
	unsigned long flags;
	int ciw_cnt;

	int found = 0;		/* RCD CIW found */
	int ret = 0;		/* return code */

	char dbf_txt[15];

	SANITY_CHECK (irq);

	if (!buffer || !length) {
		return (-EINVAL);
	} else if (ioinfo[irq]->ui.flags.oper == 0) {
		return (-ENODEV);
	} else if (ioinfo[irq]->ui.flags.esid == 0) {
		*buffer = NULL;
		*length = 0;
		return (-EOPNOTSUPP);

	}

 	if (ioinfo[irq]->ui.flags.unfriendly) {
 		/* don't even try it */
 		return -EUSERS;
 	}

	sprintf (dbf_txt, "rdconf%x", irq);
	CIO_TRACE_EVENT (4, dbf_txt);

	/*
	 * scan for RCD command in extended SenseID data
	 */

	for (ciw_cnt = 0; (found == 0) && (ciw_cnt < MAX_CIWS); ciw_cnt++) {
		if (ioinfo[irq]->senseid.ciw[ciw_cnt].ct == CIW_TYPE_RCD) {
			/*
			 * paranoia check ...
			 */
			if (ioinfo[irq]->senseid.ciw[ciw_cnt].cmd != 0
			    && ioinfo[irq]->senseid.ciw[ciw_cnt].count != 0) {
				found = 1;

			}

			break;

		}
	}

	if (found) {
		devstat_t devstat;	/* inline device status area */
		devstat_t *pdevstat;
		int ioflags;

		ccw1_t *rcd_ccw = &ioinfo[irq]->senseccw;
		char *rcd_buf = NULL;
		int emulated = 0;	/* no i/O handler installed */
		int retry = 5;	/* retry count */

		__save_flags (flags);
		__cli ();

		if (!ioinfo[irq]->ui.flags.ready) {
			pdevstat = &devstat;
			ret = request_irq (irq,
					   init_IRQ_handler,
					   SA_PROBE, "RCD", pdevstat);

			if (!ret) {
				emulated = 1;

			}	/* endif */
		} else {
			pdevstat = ioinfo[irq]->irq_desc.dev_id;

		}		/* endif */

		if (!ret) {
			if (init_IRQ_complete) {
				rcd_buf =
				    kmalloc (ioinfo[irq]->senseid.ciw[ciw_cnt].
					     count, GFP_DMA | GFP_ATOMIC);
			} else {
				rcd_buf =
				    alloc_bootmem_low (ioinfo[irq]->senseid.
						       ciw[ciw_cnt].count);

			}

			if (rcd_buf == NULL) {
				ret = -ENOMEM;

			}
			if (!ret) {
				memset (rcd_buf,
					'\0',
					ioinfo[irq]->senseid.ciw[ciw_cnt].
					count);

				do {
					rcd_ccw->cmd_code =
					    ioinfo[irq]->senseid.ciw[ciw_cnt].
					    cmd;
					rcd_ccw->cda =
					    (__u32) virt_to_phys (rcd_buf);
					rcd_ccw->count =
					    ioinfo[irq]->senseid.ciw[ciw_cnt].
					    count;
					rcd_ccw->flags = CCW_FLAG_SLI;

					memset (pdevstat, '\0',
						sizeof (devstat_t));

					if (lpm) {
						ioflags =
						    DOIO_WAIT_FOR_INTERRUPT |
						    DOIO_VALID_LPM |
						    DOIO_DONT_CALL_INTHDLR;
					} else {
						ioflags =
						    DOIO_WAIT_FOR_INTERRUPT |
						    DOIO_DONT_CALL_INTHDLR;

					}

					ret = s390_start_IO (irq, rcd_ccw, 0x00524344,	/* == RCD */
							     lpm, ioflags);
					switch (ret) {
					case 0:
					case -EIO:

						if (!
						    (pdevstat->
						     flag &
						     (DEVSTAT_STATUS_PENDING |
						      DEVSTAT_NOT_OPER |
						      DEVSTAT_FLAG_SENSE_AVAIL)))
						{
							retry = 0;	/* we got it ... */
						} else {
							retry--;	/* try again ... */

						}

						break;

					default:	/* -EBUSY, -ENODEV, ??? */
						retry = 0;

					}

				} while (retry);
			}
		}

		__restore_flags (flags);

		/*
		 * on success we update the user input parms
		 */
		if (ret == 0) {
			*length = ioinfo[irq]->senseid.ciw[ciw_cnt].count;
			*buffer = rcd_buf;
		} else {
			if (rcd_buf != NULL) {
				if (init_IRQ_complete) {
					kfree (rcd_buf);
				} else {
					free_bootmem ((unsigned long) rcd_buf,
						      ioinfo[irq]->senseid.
						      ciw[ciw_cnt].count);

				}

			}

			*buffer = NULL;
			*length = 0;

		}

		if (emulated)
			free_irq (irq, pdevstat);
	} else {
		*buffer = NULL;
		*length = 0;
		ret = -EOPNOTSUPP;

	}

	return (ret);

}

int
get_dev_info (int irq, s390_dev_info_t * pdi)
{
	return (get_dev_info_by_irq (irq, pdi));
}

static int __inline__
get_next_available_irq (ioinfo_t * pi)
{
	int ret_val = -ENODEV;

	while (pi != NULL) {
		if ((!pi->st) 
		    && (pi->ui.flags.oper)
		    && (!pi->ui.flags.unfriendly)) {
			ret_val = pi->irq;
			break;
		} else {
			pi = pi->next;
		}
	}

	return ret_val;
}

int
get_irq_first (void)
{
	int ret_irq;

	if (ioinfo_head) {
		if ((ioinfo_head->ui.flags.oper) && 
		    (!ioinfo_head->ui.flags.unfriendly) &&
		    (!ioinfo_head->st)) {
			ret_irq = ioinfo_head->irq;
		} else if (ioinfo_head->next) {
			ret_irq = get_next_available_irq (ioinfo_head->next);

		} else {
			ret_irq = -ENODEV;

		}
	} else {
		ret_irq = -ENODEV;

	}

	return ret_irq;
}

int
get_irq_next (int irq)
{
	int ret_irq;

	if (ioinfo[irq] != INVALID_STORAGE_AREA) {
		if (ioinfo[irq]->next) {
			if ((ioinfo[irq]->next->ui.flags.oper) &&
			    (!ioinfo[irq]->next->ui.flags.unfriendly) &&
			    (!ioinfo[irq]->next->st)) {
				ret_irq = ioinfo[irq]->next->irq;
			} else {
				ret_irq =
				    get_next_available_irq (ioinfo[irq]->next);

			}
		} else {
			ret_irq = -ENODEV;

		}
	} else {
		ret_irq = -EINVAL;

	}

	return ret_irq;
}

int
get_dev_info_by_irq (int irq, s390_dev_info_t * pdi)
{

	SANITY_CHECK (irq);

	if (pdi == NULL)
		return -EINVAL;

	pdi->devno = ioinfo[irq]->schib.pmcw.dev;
	pdi->irq = irq;

	if (ioinfo[irq]->ui.flags.oper && !ioinfo[irq]->ui.flags.unknown) {
		pdi->status = 0;
		memcpy (&(pdi->sid_data),
			&ioinfo[irq]->senseid, sizeof (senseid_t));
 
 	} else if (ioinfo[irq]->ui.flags.unfriendly) {
 		pdi->status = DEVSTAT_UNFRIENDLY_DEV;
 		memset (&(pdi->sid_data), '\0', sizeof (senseid_t));
 		pdi->sid_data.cu_type = 0xFFFF;

	} else if (ioinfo[irq]->ui.flags.unknown) {
		pdi->status = DEVSTAT_UNKNOWN_DEV;
		memset (&(pdi->sid_data), '\0', sizeof (senseid_t));
		pdi->sid_data.cu_type = 0xFFFF;

	} else {
		pdi->status = DEVSTAT_NOT_OPER;
		memset (&(pdi->sid_data), '\0', sizeof (senseid_t));
		pdi->sid_data.cu_type = 0xFFFF;

	}

	if (ioinfo[irq]->ui.flags.ready)
		pdi->status |= DEVSTAT_DEVICE_OWNED;

	return 0;
}

int
get_dev_info_by_devno (__u16 devno, s390_dev_info_t * pdi)
{
	int i;
	int rc = -ENODEV;

	if (devno > 0x0000ffff)
		return -ENODEV;
	if (pdi == NULL)
		return -EINVAL;

	for (i = 0; i <= highest_subchannel; i++) {

		if ((ioinfo[i] != INVALID_STORAGE_AREA) &&
		    (!ioinfo[i]->st) &&
		    (ioinfo[i]->schib.pmcw.dev == devno)) {

			pdi->irq = i;
			pdi->devno = devno;

			if (ioinfo[i]->ui.flags.oper
			    && !ioinfo[i]->ui.flags.unknown) {
				pdi->status = 0;
				memcpy (&(pdi->sid_data),
					&ioinfo[i]->senseid,
					sizeof (senseid_t));
 
 			} else if (ioinfo[i]->ui.flags.unfriendly) {
 				pdi->status = DEVSTAT_UNFRIENDLY_DEV;
 				memset (&(pdi->sid_data), '\0', 
 					sizeof (senseid_t));
 				pdi->sid_data.cu_type = 0xFFFF;
 

			} else if (ioinfo[i]->ui.flags.unknown) {
				pdi->status = DEVSTAT_UNKNOWN_DEV;

				memset (&(pdi->sid_data),
					'\0', sizeof (senseid_t));

				pdi->sid_data.cu_type = 0xFFFF;
			} else {
				pdi->status = DEVSTAT_NOT_OPER;

				memset (&(pdi->sid_data),
					'\0', sizeof (senseid_t));

				pdi->sid_data.cu_type = 0xFFFF;

			}

			if (ioinfo[i]->ui.flags.ready)
				pdi->status |= DEVSTAT_DEVICE_OWNED;

			if (!ioinfo[i]->ui.flags.unfriendly)
				rc = 0;	/* found */
			else
				rc = -EUSERS;
			break;

		}
	}

	return (rc);

}

int
get_irq_by_devno (__u16 devno)
{
	int i;
	int rc = -1;

	if (devno <= 0x0000ffff) {
		for (i = 0; i <= highest_subchannel; i++) {
			if ((ioinfo[i] != INVALID_STORAGE_AREA)
			    && (!ioinfo[i]->st)
			    && (ioinfo[i]->schib.pmcw.dev == devno)
			    && (ioinfo[i]->schib.pmcw.dnv == 1)) {
				rc = i;
				break;
			}
		}
	}

	return (rc);
}

unsigned int
get_devno_by_irq (int irq)
{

	if ((irq > highest_subchannel)
	    || (irq < 0)
	    || (ioinfo[irq] == INVALID_STORAGE_AREA)) {
		return -1;

	}

	if (ioinfo[irq]->st) 
		return -1;

	/*
	 * we don't need to check for the device be operational
	 *  as the initial STSCH will always present the device
	 *  number defined by the IOCDS regardless of the device
	 *  existing or not. However, there could be subchannels
	 *  defined who's device number isn't valid ...
	 */
	if (ioinfo[irq]->schib.pmcw.dnv)
		return (ioinfo[irq]->schib.pmcw.dev);
	else
		return -1;
}

/*
 * s390_device_recognition_irq
 *
 * Used for individual device recognition. Issues the device
 *  independant SenseID command to obtain info the device type.
 *
 */
void
s390_device_recognition_irq (int irq)
{
	int ret;
	char dbf_txt[15];

	sprintf (dbf_txt, "devrec%x", irq);
	CIO_TRACE_EVENT (4, dbf_txt);

	/*
	 * We issue the SenseID command on I/O subchannels we think are
	 *  operational only.
	 */
	if ((ioinfo[irq] != INVALID_STORAGE_AREA)
	    && (!ioinfo[irq]->st)
	    && (ioinfo[irq]->schib.pmcw.st == 0)
	    && (ioinfo[irq]->ui.flags.oper == 1)) {
		int irq_ret;
		devstat_t devstat;

		if (ioinfo[irq]->ui.flags.pgid_supp)
			irq_ret = request_irq (irq,
					       init_IRQ_handler,
					       SA_PROBE | SA_DOPATHGROUP,
					       "INIT", &devstat);
		else
			irq_ret = request_irq (irq,
					       init_IRQ_handler,
					       SA_PROBE, "INIT", &devstat);

		if (!irq_ret) {
			ret = enable_cpu_sync_isc (irq);

			if (!ret) {
				ioinfo[irq]->ui.flags.unknown = 0;
				
				memset (&ioinfo[irq]->senseid, '\0',
					sizeof (senseid_t));
				
				if (cio_sid_with_pgid) {
					
					ret = s390_DevicePathVerification(irq,0);
					
					if (ret == -EOPNOTSUPP) 
						/* 
						 * Doesn't prevent us from proceeding
						 */
						ret = 0;
				}
				
				/*
				 * we'll fallthrough here if we don't want
				 * to do SPID before SID
				 */
				if (!ret) {
					ret = s390_SenseID (irq, &ioinfo[irq]->senseid, 0xff);
					if (ret == -ETIMEDOUT) {
						/* SenseID timed out.
						 * We consider this device to be
						 * boxed for now.
						 */
						ioinfo[irq]->ui.flags.unfriendly = 1;
					}

#if 0				/* FIXME */
				/*
				 * We initially check the configuration data for
				 *  those devices with more than a single path
				 */
				if (ioinfo[irq]->schib.pmcw.pim != 0x80) {
					char *prcd;
					int lrcd;

					ret =
					    read_conf_data (irq,
							    (void **) &prcd,
							    &lrcd, 0);

					if (!ret)	// on success only ...
					{
						char buffer[80];
#ifdef CONFIG_DEBUG_IO
						sprintf (buffer,
							 "RCD for device(%04X)/"
							 "subchannel(%04X) returns :\n",
							 ioinfo[irq]->schib.
							 pmcw.dev, irq);

						s390_displayhex (buffer, prcd,
								 lrcd);
#endif
						CIO_TRACE_EVENT(2, "rcddata:");
						CIO_HEX_EVENT(2, prcd, lrcd);

						if (init_IRQ_complete) {
							kfree (prcd);
						} else {
							free_bootmem ((unsigned
								       long)
								      prcd,
								      lrcd);

						}
					}
				}
#endif
				}
				disable_cpu_sync_isc (irq);

			}

			free_irq (irq, &devstat);

		}			
	}
}

/*
 * s390_device_recognition_all
 *
 * Used for system wide device recognition.
 *
 */
void
s390_device_recognition_all (void)
{
	int irq = 0;		/* let's start with subchannel 0 ... */

	do {
		s390_device_recognition_irq (irq);

		irq++;

	} while (irq <= highest_subchannel);

}

/*
 * Function: s390_redo_validation
 * Look for no longer blacklisted devices
 * FIXME: there must be a better way to do this...
 */

void
s390_redo_validation (void)
{
	int irq = 0;
	int ret;

	CIO_TRACE_EVENT (0, "redoval");

	do {
		if (ioinfo[irq] == INVALID_STORAGE_AREA) {
			ret = s390_validate_subchannel (irq, 0);
			if (!ret) {
				s390_device_recognition_irq (irq);
				if (ioinfo[irq]->ui.flags.oper) {
					devreg_t *pdevreg;

					pdevreg =
					    s390_search_devreg (ioinfo[irq]);
					if (pdevreg != NULL) {
						if (pdevreg->oper_func != NULL)
							pdevreg->oper_func (irq,
									    pdevreg);

					}
				}
#ifdef CONFIG_PROC_FS
				if (cio_proc_devinfo)
					if (irq < MAX_CIO_PROCFS_ENTRIES) {
						cio_procfs_device_create (ioinfo
									  [irq]->
									  devno);
					}
#endif
			}
		}
		irq++;
	} while (irq <= highest_subchannel);
}


/*
 * s390_trigger_resense
 *
 * try to re-sense the device on subchannel irq
 * only to be called without interrupt handler
 */
int
s390_trigger_resense(int irq)
{

	SANITY_CHECK(irq);

	if (ioinfo[irq]->ui.flags.ready) {
		printk (KERN_WARNING "s390_trigger_resense(%04X): "
			"Device is in use!\n", irq);
		return -EBUSY;
	}

	/* 
	 * This function is called by dasd if it just executed a "steal lock".
	 * Therefore, re-initialize the 'unfriendly' flag to 0.
	 * We run into timeouts if the device is still boxed...
	 */
	ioinfo[irq]->ui.flags.unfriendly = 0;

	s390_device_recognition_irq(irq);

	return 0;
}

/*
 * s390_search_devices
 *
 * Determines all subchannels available to the system.
 *
 */
void
s390_process_subchannels (void)
{
	int ret;
	int irq = 0;		/* Evaluate all subchannels starting with 0 ... */

	do {
		ret = s390_validate_subchannel (irq, 0);

		if (ret != -ENXIO)
			irq++;

	} while ((ret != -ENXIO) && (irq < __MAX_SUBCHANNELS));

	highest_subchannel = (--irq);

	printk (KERN_INFO "Highest subchannel number detected (hex) : %04X\n",
		highest_subchannel);
	CIO_MSG_EVENT(0,
		      "Highest subchannel number detected "
		      "(hex) : %04X\n", highest_subchannel);
}

/*
 * s390_validate_subchannel()
 *
 * Process the subchannel for the requested irq. Returns 1 for valid
 *  subchannels, otherwise 0.
 */
int
s390_validate_subchannel (int irq, int enable)
{

	int retry;		/* retry count for status pending conditions */
	int ccode;		/* condition code for stsch() only */
	int ccode2;		/* condition code for other I/O routines */
	schib_t *p_schib;
	int ret;
#ifdef CONFIG_CHSC
	int      chp = 0;
	int      mask;
#endif /* CONFIG_CHSC */

	char dbf_txt[15];

	sprintf (dbf_txt, "valsch%x", irq);
	CIO_TRACE_EVENT (4, dbf_txt);

	/*
	 * The first subchannel that is not-operational (ccode==3)
	 *  indicates that there aren't any more devices available.
	 */
	if ((init_IRQ_complete)
	    && (ioinfo[irq] != INVALID_STORAGE_AREA)) {
		p_schib = &ioinfo[irq]->schib;
	} else {
		p_schib = p_init_schib;

	}

	/*
	 * If we knew the device before we assume the worst case ...    
	 */
	if (ioinfo[irq] != INVALID_STORAGE_AREA) {
		ioinfo[irq]->ui.flags.oper = 0;
		ioinfo[irq]->ui.flags.dval = 0;

	}

	ccode = stsch (irq, p_schib);

	if (ccode) {
		return -ENXIO;
	}
	/*
	 * ... just being curious we check for non I/O subchannels
	 */
	if (p_schib->pmcw.st) {
		if (cio_show_msg) {
			printk (KERN_INFO "Subchannel %04X reports "
				"non-I/O subchannel type %04X\n",
				irq, p_schib->pmcw.st);
		}
		CIO_MSG_EVENT(0,
			      "Subchannel %04X reports "
			      "non-I/O subchannel type %04X\n",
			      irq, p_schib->pmcw.st);

		if (ioinfo[irq] != INVALID_STORAGE_AREA)
			ioinfo[irq]->ui.flags.oper = 0;

	}

	if ((!p_schib->pmcw.dnv) && (!p_schib->pmcw.st)) {
		return -ENODEV;
	}
	if (!p_schib->pmcw.st) {
		if (is_blacklisted (p_schib->pmcw.dev)) {
			/* 
			 * This device must not be known to Linux. So we simply say that 
			 * there is no device and return ENODEV.
			 */
#ifdef CONFIG_DEBUG_IO
			printk (KERN_DEBUG
				"Blacklisted device detected at devno %04X\n",
				p_schib->pmcw.dev);
#endif
			CIO_MSG_EVENT(0,
				      "Blacklisted device detected at devno %04X\n",
				      p_schib->pmcw.dev);
			return -ENODEV;
		}
	}
	
	if (ioinfo[irq] == INVALID_STORAGE_AREA) {
		if (!init_IRQ_complete) {
			ioinfo[irq] = (ioinfo_t *)
			    alloc_bootmem_low (sizeof (ioinfo_t));
		} else {
			ioinfo[irq] = (ioinfo_t *)
			    kmalloc (sizeof (ioinfo_t), GFP_DMA | GFP_ATOMIC);

		}
		if (!ioinfo[irq])
			return -ENOMEM;
			

		memset (ioinfo[irq], '\0', sizeof (ioinfo_t));
		memcpy (&ioinfo[irq]->schib, p_init_schib, sizeof (schib_t));

		/*
		 * We have to insert the new ioinfo element
		 *  into the linked list, either at its head,
		 *  its tail or insert it.
		 */
		if (ioinfo_head == NULL) {	/* first element */
			ioinfo_head = ioinfo[irq];
			ioinfo_tail = ioinfo[irq];
		} else if (irq < ioinfo_head->irq) {	/* new head */
			ioinfo[irq]->next = ioinfo_head;
			ioinfo_head->prev = ioinfo[irq];
			ioinfo_head = ioinfo[irq];
		} else if (irq > ioinfo_tail->irq) {	/* new tail */
			ioinfo_tail->next = ioinfo[irq];
			ioinfo[irq]->prev = ioinfo_tail;
			ioinfo_tail = ioinfo[irq];
		} else {	/* insert element */

			ioinfo_t *pi = ioinfo_head;

			for (pi = ioinfo_head; pi != NULL; pi = pi->next) {

				if (irq < pi->next->irq) {
					ioinfo[irq]->next = pi->next;
					ioinfo[irq]->prev = pi;
					pi->next->prev = ioinfo[irq];
					pi->next = ioinfo[irq];
					break;

				}
			}
		}
	}

	/* initialize some values ... */
	ioinfo[irq]->irq = irq;
	ioinfo[irq]->st = ioinfo[irq]->schib.pmcw.st;
	if (ioinfo[irq]->st)
		return -ENODEV;

	ioinfo[irq]->opm = ioinfo[irq]->schib.pmcw.pim
	    & ioinfo[irq]->schib.pmcw.pam & ioinfo[irq]->schib.pmcw.pom;

#ifdef CONFIG_CHSC
	if (ioinfo[irq]->opm) {
		for (chp=0;chp<=7;chp++) {
			mask = 0x80 >> chp;
			if (ioinfo[irq]->opm & mask) {
				if (!test_bit
				    (ioinfo[irq]->schib.pmcw.chpid[chp], 
				     &chpids_logical)) {
					/* disable using this path */
					ioinfo[irq]->opm &= ~mask;
				}
			} else {
				/* This chpid is not available to us */
				clear_bit(ioinfo[irq]->schib.pmcw.chpid[chp],
					  &chpids);
			}
		}
	}
#endif /* CONFIG_CHSC */

	if (cio_show_msg) {
		printk (KERN_INFO
			"Detected device %04X "
			"on subchannel %04X"
			" - PIM = %02X, PAM = %02X, POM = %02X\n",
			ioinfo[irq]->schib.pmcw.dev,
			irq,
			ioinfo[irq]->schib.pmcw.pim,
			ioinfo[irq]->schib.pmcw.pam,
			ioinfo[irq]->schib.pmcw.pom);

	}
	CIO_MSG_EVENT(0,
		      "Detected device %04X "
		      "on subchannel %04X"
		      " - PIM = %02X, "
		      "PAM = %02X, POM = %02X\n",
		      ioinfo[irq]->schib.pmcw.dev, 
		      irq,
		      ioinfo[irq]->schib.pmcw.pim,
		      ioinfo[irq]->schib.pmcw.pam, 
		      ioinfo[irq]->schib.pmcw.pom);

	/*
	 * initialize ioinfo structure
	 */
	if (!ioinfo[irq]->ui.flags.ready) {
		ioinfo[irq]->nopfunc = NULL;
		ioinfo[irq]->ui.flags.busy = 0;
		ioinfo[irq]->ui.flags.dval = 1;
		ioinfo[irq]->devstat.intparm = 0;

	}
	ioinfo[irq]->devstat.devno = ioinfo[irq]->schib.pmcw.dev;
	ioinfo[irq]->devno = ioinfo[irq]->schib.pmcw.dev;

	/*
	 * We should have at least one CHPID ...
	 */
	if (ioinfo[irq]->opm) {
		/*
		 * We now have to initially ...
		 *  ... set "interruption sublass"
		 *  ... enable "concurrent sense"
		 *  ... enable "multipath mode" if more than one
		 *        CHPID is available. This is done regardless
		 *        whether multiple paths are available for us.
		 *
		 * Note : we don't enable the device here, this is temporarily
		 *        done during device sensing below.
		 */
		ioinfo[irq]->schib.pmcw.isc = 3;	/* could be smth. else */
		ioinfo[irq]->schib.pmcw.csense = 1;	/* concurrent sense */
		ioinfo[irq]->schib.pmcw.ena = enable;
		ioinfo[irq]->schib.pmcw.intparm = ioinfo[irq]->schib.pmcw.dev;

		if ((ioinfo[irq]->opm != 0x80)
		    && (ioinfo[irq]->opm != 0x40)
		    && (ioinfo[irq]->opm != 0x20)
		    && (ioinfo[irq]->opm != 0x10)
		    && (ioinfo[irq]->opm != 0x08)
		    && (ioinfo[irq]->opm != 0x04)
		    && (ioinfo[irq]->opm != 0x02)
		    && (ioinfo[irq]->opm != 0x01)) {
			ioinfo[irq]->schib.pmcw.mp = 1;	/* multipath mode */

		}

		retry = 5;

		do {
			ccode2 = msch_err (irq, &ioinfo[irq]->schib);

			switch (ccode2) {
			case 0:
				/*
				 * successful completion
				 *
				 * concurrent sense facility available
				 */
				ioinfo[irq]->ui.flags.oper = 1;
				ioinfo[irq]->ui.flags.consns = 1;
				ret = 0;
				break;

			case 1:
				/*
				 * status pending
				 *
				 * How can we have a pending status 
				 * as the device is disabled for 
				 * interrupts ?
				 * Anyway, process it ...
				 */
				ioinfo[irq]->ui.flags.s_pend = 1;
				s390_process_IRQ (irq);
				ioinfo[irq]->ui.flags.s_pend = 0;
				retry--;
				ret = -EIO;
				break;

			case 2:
				/*
				 * busy
				 *
				 * we mark it not-oper as we can't 
				 * properly operate it !
				 */
				ioinfo[irq]->ui.flags.oper = 0;
				udelay (100);	/* allow for recovery */
				retry--;
				ret = -EBUSY;
				break;

			case 3:	/* not operational */
				ioinfo[irq]->ui.flags.oper = 0;
				retry = 0;
				ret = -ENODEV;
				break;

			default:
#define PGMCHK_OPERAND_EXC      0x15

				if ((ccode2 & PGMCHK_OPERAND_EXC)
				    == PGMCHK_OPERAND_EXC) {
					/*
					 * re-issue the modify subchannel without trying to
					 *  enable the concurrent sense facility
					 */
					ioinfo[irq]->schib.pmcw.csense = 0;

					ccode2 =
					    msch_err (irq, &ioinfo[irq]->schib);

					if (ccode2 != 0) {
						printk (KERN_ERR
							" ... msch() (2) failed"
							" with CC = %X\n",
							ccode2);
						CIO_MSG_EVENT(0,
							      "msch() (2) failed"
							      " with CC=%X\n",
							      ccode2);
						ioinfo[irq]->ui.flags.oper = 0;
						ret = -EIO;
					} else {
						ioinfo[irq]->ui.flags.oper = 1;
						ioinfo[irq]->ui.
						    flags.consns = 0;
						ret = 0;

					}

				} else {
					printk (KERN_ERR
						" ... msch() (1) failed with "
						"CC = %X\n", ccode2);
					CIO_MSG_EVENT(0,
						      "msch() (1) failed with "
						      "CC = %X\n", ccode2);
					ioinfo[irq]->ui.flags.oper = 0;
					ret = -EIO;

				}

				retry = 0;
				break;

			}

		} while (ccode2 && retry);

		if ((ccode2 != 0) && (ccode2 != 3)
		    && (!retry)) {
			printk (KERN_ERR
				" ... msch() retry count for "
				"subchannel %04X exceeded, CC = %d\n",
				irq, ccode2);
			CIO_MSG_EVENT(0,
				      " ... msch() retry count for "
				      "subchannel %04X exceeded, CC = %d\n",
				      irq, ccode2);

		}
	} else {
		/* no path available ... */
		ioinfo[irq]->ui.flags.oper = 0;
		ret = -ENODEV;

	}

	return (ret);
}

/*
 * s390_SenseID
 *
 * Try to obtain the 'control unit'/'device type' information
 *  associated with the subchannel.
 *
 * The function is primarily meant to be called without irq
 *  action handler in place. However, it also allows for
 *  use with an action handler in place. If there is already
 *  an action handler registered assure it can handle the
 *  s390_SenseID() related device interrupts - interruption
 *  parameter used is 0x00E2C9C4 ( SID ).
 */
int
s390_SenseID (int irq, senseid_t * sid, __u8 lpm)
{
	ccw1_t *sense_ccw;	/* ccw area for SenseID command */
	senseid_t isid;		/* internal sid */
	devstat_t devstat;	/* required by request_irq() */
	__u8 pathmask;		/* calulate path mask */
	__u8 domask;		/* path mask to use */
	int inlreq;		/* inline request_irq() */
	int irq_ret;		/* return code */
	devstat_t *pdevstat;	/* ptr to devstat in use */
	int retry;		/* retry count */
	int io_retry;		/* retry indicator */

	senseid_t *psid = sid;	/* start with the external buffer */
	int sbuffer = 0;	/* switch SID data buffer */

	char dbf_txt[15];
	int i;
	int failure = 0;	/* nothing went wrong yet */

	SANITY_CHECK (irq);

	if (ioinfo[irq]->ui.flags.oper == 0) {
		return (-ENODEV);

	}

 	if (ioinfo[irq]->ui.flags.unfriendly) {
 		/* don't even try it */
 		return -EUSERS;
 	}

	sprintf (dbf_txt, "snsID%x", irq);
	CIO_TRACE_EVENT (4, dbf_txt);

	inlreq = 0;		/* to make the compiler quiet... */

	if (!ioinfo[irq]->ui.flags.ready) {

		pdevstat = &devstat;

		/*
		 * Perform SENSE ID command processing. We have to request device
		 *  ownership and provide a dummy I/O handler. We issue sync. I/O
		 *  requests and evaluate the devstat area on return therefore
		 *  we don't need a real I/O handler in place.
		 */
		irq_ret =
		    request_irq (irq, init_IRQ_handler, SA_PROBE, "SID",
				 &devstat);

		if (irq_ret == 0)
			inlreq = 1;
	} else {
		inlreq = 0;
		irq_ret = 0;
		pdevstat = ioinfo[irq]->irq_desc.dev_id;

	}

	if (irq_ret) {
		return irq_ret;
	}

	s390irq_spin_lock (irq);

	if (init_IRQ_complete) {
		sense_ccw = kmalloc (2 * sizeof (ccw1_t), GFP_DMA | GFP_ATOMIC);
	} else {
		sense_ccw = alloc_bootmem_low (2 * sizeof (ccw1_t));

	}
	if (!sense_ccw) {
		s390irq_spin_unlock (irq);
		if (inlreq)
			free_irq (irq, &devstat);
		return -ENOMEM;
	}

	/* more than one path installed ? */
	if (ioinfo[irq]->schib.pmcw.pim != 0x80) {
		sense_ccw[0].cmd_code = CCW_CMD_SUSPEND_RECONN;
		sense_ccw[0].cda = 0;
		sense_ccw[0].count = 0;
		sense_ccw[0].flags = CCW_FLAG_SLI | CCW_FLAG_CC;

		sense_ccw[1].cmd_code = CCW_CMD_SENSE_ID;
		sense_ccw[1].cda = (__u32) virt_to_phys (sid);
		sense_ccw[1].count = sizeof (senseid_t);
		sense_ccw[1].flags = CCW_FLAG_SLI;
	} else {
		sense_ccw[0].cmd_code = CCW_CMD_SENSE_ID;
		sense_ccw[0].cda = (__u32) virt_to_phys (sid);
		sense_ccw[0].count = sizeof (senseid_t);
		sense_ccw[0].flags = CCW_FLAG_SLI;

	}

	for (i = 0; (i < 8); i++) {
		pathmask = 0x80 >> i;

		domask = ioinfo[irq]->opm & pathmask;

		if (lpm)
			domask &= lpm;

		if (!domask)
			continue;

		failure = 0;

		memset(psid, 0, sizeof(senseid_t));
		psid->cu_type = 0xFFFF;	/* initialize fields ... */

		retry = 5;	/* retry count    */
		io_retry = 1;	/* enable retries */

		/*
		 * We now issue a SenseID request. In case of BUSY,
		 *  STATUS PENDING or non-CMD_REJECT error conditions
		 *  we run simple retries.
		 */
		do {
			memset (pdevstat, '\0', sizeof (devstat_t));

			irq_ret = s390_start_IO (irq, sense_ccw, 0x00E2C9C4,	/* == SID */
						 domask,
						 DOIO_WAIT_FOR_INTERRUPT
						 | DOIO_TIMEOUT
						 | DOIO_VALID_LPM
						 | DOIO_DONT_CALL_INTHDLR);

			if ((psid->cu_type != 0xFFFF)
			    && (psid->reserved == 0xFF)) {
				if (!sbuffer) {	/* switch buffers */
					/*
					 * we report back the
					 *  first hit only
					 */
					psid = &isid;

					if (ioinfo[irq]->schib.pmcw.pim != 0x80) {
						sense_ccw[1].cda = (__u32)
						    virt_to_phys (psid);
					} else {
						sense_ccw[0].cda = (__u32)
						    virt_to_phys (psid);

					}

					/*
					 * if just the very first
					 *  was requested to be
					 *  sensed disable further
					 *  scans.
					 */
					if (!lpm)
						lpm = domask;

					sbuffer = 1;

				}

				if (pdevstat->rescnt < (sizeof (senseid_t) - 8)) {
					ioinfo[irq]->ui.flags.esid = 1;

				}

				io_retry = 0;

				break;
			}

			failure = 1;

			if (pdevstat->flag & DEVSTAT_STATUS_PENDING) {
#ifdef CONFIG_DEBUG_IO
				printk (KERN_DEBUG
					"SenseID : device %04X on "
					"Subchannel %04X "
					"reports pending status, "
					"retry : %d\n",
					ioinfo[irq]->schib.pmcw.dev, irq,
					retry);
#endif
				CIO_MSG_EVENT(2,
					      "SenseID : device %04X on "
					      "Subchannel %04X "
					      "reports pending status, "
					      "retry : %d\n",
					      ioinfo
					      [irq]->schib.pmcw.dev, irq, retry);
			}

			else if (pdevstat->flag & DEVSTAT_FLAG_SENSE_AVAIL) {
				/*
				 * if the device doesn't support the SenseID
				 *  command further retries wouldn't help ...
				 */
				if (pdevstat->ii.sense.data[0]
				    & (SNS0_CMD_REJECT | SNS0_INTERVENTION_REQ)) {
#ifdef CONFIG_DEBUG_IO
					printk (KERN_ERR
						"SenseID : device %04X on "
						"Subchannel %04X "
						"reports cmd reject or "
						"intervention required\n",
						ioinfo[irq]->schib.pmcw.dev,
						irq);
#endif
					CIO_MSG_EVENT(2,
						      "SenseID : device %04X on "
						      "Subchannel %04X "
						      "reports cmd reject or "
						      "intervention required\n",
						      ioinfo[irq]->schib.pmcw.dev, 
						      irq);
					io_retry = 0;
				} else {
#ifdef CONFIG_DEBUG_IO
					printk
					    (KERN_WARNING
					     "SenseID : UC on "
					     "dev %04X, "
					     "retry %d, "
					     "lpum %02X, "
					     "cnt %02d, "
					     "sns :"
					     " %02X%02X%02X%02X "
					     "%02X%02X%02X%02X ...\n",
					     ioinfo[irq]->schib.pmcw.dev,
					     retry,
					     pdevstat->lpum,
					     pdevstat->scnt,
					     pdevstat->ii.sense.data[0],
					     pdevstat->ii.sense.data[1],
					     pdevstat->ii.sense.data[2],
					     pdevstat->ii.sense.data[3],
					     pdevstat->ii.sense.data[4],
					     pdevstat->ii.sense.data[5],
					     pdevstat->ii.sense.data[6],
					     pdevstat->ii.sense.data[7]);
#endif
					CIO_MSG_EVENT(2,
						      "SenseID : UC on "
						      "dev %04X, "
						      "retry %d, "
						      "lpum %02X, "
						      "cnt %02d, "
						      "sns :"
						      " %02X%02X%02X%02X "
						      "%02X%02X%02X%02X ...\n",
						      ioinfo[irq]->
						      schib.pmcw.dev,
						      retry,
						      pdevstat->lpum,
						      pdevstat->scnt,
						      pdevstat->
						      ii.sense.data[0],
						      pdevstat->
						      ii.sense.data[1],
						      pdevstat->
						      ii.sense.data[2],
						      pdevstat->
						      ii.sense.data[3],
						      pdevstat->
						      ii.sense.data[4],
						      pdevstat->
						      ii.sense.data[5],
						      pdevstat->
						      ii.sense.data[6],
						      pdevstat->
						      ii.sense.data[7]);

				}

			} else if ((pdevstat->flag & DEVSTAT_NOT_OPER)
				   || (irq_ret == -ENODEV)) {
#ifdef CONFIG_DEBUG_IO
				printk (KERN_ERR
					"SenseID : path %02X for "
					"device %04X on "
					"subchannel %04X "
					"is 'not operational'\n",
					domask,
					ioinfo[irq]->schib.pmcw.dev, irq);
#endif
				CIO_MSG_EVENT(2,
					      "SenseID : path %02X for "
					      "device %04X on "
					      "subchannel %04X "
					      "is 'not operational'\n",
					      domask,
					      ioinfo[irq]->schib.pmcw.dev, irq);
				
				io_retry = 0;
				ioinfo[irq]->opm &= ~domask;

			} else {
#ifdef CONFIG_DEBUG_IO
				printk (KERN_INFO
					"SenseID : start_IO() for "
					"device %04X on "
					"subchannel %04X "
					"returns %d, retry %d, "
					"status %04X\n",
					ioinfo[irq]->schib.pmcw.dev,
					irq, irq_ret, retry, pdevstat->flag);
#endif
				CIO_MSG_EVENT(2,
					     "SenseID : start_IO() for "
					     "device %04X on "
					     "subchannel %04X "
					     "returns %d, retry %d, "
					     "status %04X\n",
					     ioinfo[irq]->schib.pmcw.dev, irq,
					     irq_ret, retry, pdevstat->flag);

				if (irq_ret == -ETIMEDOUT) {
					int xret;

					/*
					 * Seems we need to cancel the first ssch sometimes...
					 * On the next try, the ssch will usually be fine.
					 */

					xret = cancel_IO (irq);

					if (!xret)
						CIO_MSG_EVENT(2,
							      "SenseID: sch canceled "
							      "successfully for irq %x\n",
							      irq);
				}

			}

			if (io_retry) {
				retry--;

				if (retry == 0) {
					io_retry = 0;

				}
			}

			if ((failure) && (io_retry)) {
				/* reset fields... */

				failure = 0;

				memset(psid, 0, sizeof(senseid_t));
				psid->cu_type = 0xFFFF;
			}

		} while ((io_retry));

	}

	if (init_IRQ_complete) {
		kfree (sense_ccw);
	} else {
		free_bootmem ((unsigned long) sense_ccw, 2 * sizeof (ccw1_t));

	}

	s390irq_spin_unlock (irq);

	/*
	 * If we installed the irq action handler we have to
	 *  release it too.
	 */
	if (inlreq)
		free_irq (irq, pdevstat);

	/*
	 * if running under VM check there ... perhaps we should do
	 *  only if we suffered a command reject, but it doesn't harm
	 */
	if ((sid->cu_type == 0xFFFF)
	    && (MACHINE_IS_VM)) {
		VM_virtual_device_info (ioinfo[irq]->schib.pmcw.dev, sid);
	}

	if (sid->cu_type == 0xFFFF) {
		/*
		 * SenseID CU-type of 0xffff indicates that no device
		 *  information could be retrieved (pre-init value).
		 *
		 * If we can't couldn't identify the device type we
		 *  consider the device "not operational".
		 */
#ifdef CONFIG_DEBUG_IO
		printk (KERN_WARNING
			"SenseID : unknown device %04X on subchannel %04X\n",
			ioinfo[irq]->schib.pmcw.dev, irq);
#endif
		CIO_MSG_EVENT(2,
			      "SenseID : unknown device %04X on subchannel %04X\n",
			      ioinfo[irq]->schib.pmcw.dev, irq);
		ioinfo[irq]->ui.flags.unknown = 1;

	}

	/*
	 * Issue device info message if unit was operational .
	 */
	if (!ioinfo[irq]->ui.flags.unknown) {
		if (sid->dev_type != 0) {
			if (cio_show_msg)
				printk (KERN_INFO
					"SenseID : device %04X reports: "
					"CU  Type/Mod = %04X/%02X,"
					" Dev Type/Mod = %04X/%02X\n",
					ioinfo[irq]->schib.pmcw.dev,
					sid->cu_type, sid->cu_model,
					sid->dev_type, sid->dev_model);
			CIO_MSG_EVENT(2,
				      "SenseID : device %04X reports: "
				      "CU  Type/Mod = %04X/%02X,"
				      " Dev Type/Mod = %04X/%02X\n",
				      ioinfo[irq]->schib.
				      pmcw.dev,
				      sid->cu_type,
				      sid->cu_model,
				      sid->dev_type,
				      sid->dev_model);
		} else {
			if (cio_show_msg)
				printk (KERN_INFO
					"SenseID : device %04X reports:"
					" Dev Type/Mod = %04X/%02X\n",
					ioinfo[irq]->schib.pmcw.dev,
					sid->cu_type, sid->cu_model);
			CIO_MSG_EVENT(2,
				      "SenseID : device %04X reports:"
				      " Dev Type/Mod = %04X/%02X\n",
				      ioinfo[irq]->schib.
				      pmcw.dev,
				      sid->cu_type,
				      sid->cu_model);
		}

	}

	if (!ioinfo[irq]->ui.flags.unknown)
		irq_ret = 0;
	else if (irq_ret != -ETIMEDOUT)
		irq_ret = -ENODEV;

	return (irq_ret);
}

static int __inline__
s390_SetMultiPath (int irq)
{
	int cc;

	cc = stsch (irq, &ioinfo[irq]->schib);

	if (!cc) {
		ioinfo[irq]->schib.pmcw.mp = 1;	/* multipath mode */

		cc = msch (irq, &ioinfo[irq]->schib);

	}

	return (cc);
}

static int
s390_do_path_verification(int irq, __u8 usermask)
{
	__u8 domask;
	int i;
	pgid_t pgid;
	__u8 dev_path;
	int first = 1;
	int ret = 0;
	char dbf_txt[15];

	sprintf(dbf_txt, "dopv%x", irq);
	CIO_TRACE_EVENT(2, dbf_txt);

	dev_path = usermask ? usermask : ioinfo[irq]->opm;

	if (ioinfo[irq]->ui.flags.pgid == 0) {
		memcpy (&ioinfo[irq]->pgid, global_pgid, sizeof (pgid_t));
		ioinfo[irq]->ui.flags.pgid = 1;
	}

	for (i = 0; i < 8 && !ret; i++) {

		domask = dev_path & (0x80>>i);

		if (!domask)
			continue;

		if (!test_bit(ioinfo[irq]->schib.pmcw.chpid[i],
			      &chpids_logical))
			/* Chpid is logically offline, don't do io */
			continue;

		ret = s390_SetPGID (irq, domask);

		/*
		 * For the *first* path we are prepared for recovery
		 *
		 *  - If we fail setting the PGID we assume its
		 *     using  a different PGID already (VM) we
		 *     try to sense.
		 */
		if (ret == -EOPNOTSUPP && first) {
			*(int *) &pgid = 0;
			
			ret = s390_SensePGID (irq, domask, &pgid);
			first = 0;

			if (ret == 0) {
				/*
				 * Check whether we retrieved
				 *  a reasonable PGID ...
				 */
				if (pgid.inf.ps.state1 == SNID_STATE1_GROUPED)
					memcpy (&ioinfo[irq]->pgid,
						&pgid, sizeof (pgid_t));
				else /* ungrouped or garbage ... */
					ret = -EOPNOTSUPP;
				
			} else {
				ioinfo[irq]->ui.flags.pgid_supp = 0;

#ifdef CONFIG_DEBUG_IO
				printk (KERN_WARNING
					"PathVerification(%04X) - Device %04X "
					"doesn't support path grouping\n",
					irq, ioinfo[irq]->schib.pmcw.dev);
#endif
				CIO_MSG_EVENT(2, "PathVerification(%04X) "
					      "- Device %04X doesn't "
					      " support path grouping\n",
					      irq,
					      ioinfo[irq]->schib.pmcw.dev);
					
			}
		} else if (ret == -EIO) {
#ifdef CONFIG_DEBUG_IO
			printk (KERN_ERR "PathVerification(%04X) - I/O error "
				"on device %04X\n", irq,
				ioinfo[irq]->schib.pmcw.dev);
#endif

			CIO_MSG_EVENT(2, "PathVerification(%04X) - I/O error "
				      "on device %04X\n", irq,
				      ioinfo[irq]->schib.pmcw.dev);
				
			ioinfo[irq]->ui.flags.pgid_supp = 0;

		} else if (ret == -ETIMEDOUT) {
#ifdef CONFIG_DEBUG_IO
			printk (KERN_ERR "PathVerification(%04X) - I/O timed "
				"out on device %04X\n", irq,
				ioinfo[irq]->schib.pmcw.dev);
#endif
			CIO_MSG_EVENT(2, "PathVerification(%04X) - I/O timed "
				      "out on device %04X\n", irq,
				      ioinfo[irq]->schib.pmcw.dev);
				
			ioinfo[irq]->ui.flags.pgid_supp = 0;

		} else if (ret == -EAGAIN) {
			
			ret = 0;
		} else if (ret == -EUSERS) {
			
#ifdef CONFIG_DEBUG_IO
			printk (KERN_ERR "PathVerification(%04X) "
				"- Device is locked by someone else!\n",
				irq);
#endif
			CIO_MSG_EVENT(2, "PathVerification(%04X) "
				      "- Device is locked by someone else!\n",
				      irq);
		} else if (ret == -ENODEV) {
#ifdef CONFIG_DEBUG_IO
			printk (KERN_ERR "PathVerification(%04X) "
				"- Device %04X is no longer there?!?\n",
				irq, ioinfo[irq]->schib.pmcw.dev);
#endif
			CIO_MSG_EVENT(2, "PathVerification(%04X) "
				      "- Device %04X is no longer there?!?\n",
				      irq, ioinfo[irq]->schib.pmcw.dev);

		} else if (ret == -EBUSY) {
			/* 
			 * The device is busy. Schedule the path verification 
			 * bottom half and we'll hopefully get in next time.
			 */
			if (!ioinfo[irq]->ui.flags.noio) {
				s390_schedule_path_verification(irq);
			}
			return -EINPROGRESS;
		} else if (ret) {
#ifdef CONFIG_DEBUG_IO
			printk (KERN_ERR "PathVerification(%04X) "
				"- Unexpected error %d on device %04X\n",
				irq, ret, ioinfo[irq]->schib.pmcw.dev);
#endif
			CIO_MSG_EVENT(2, "PathVerification(%04X) - "
				      "Unexpected error %d on device %04X\n",
				      irq, ret, ioinfo[irq]->schib.pmcw.dev);
				
			ioinfo[irq]->ui.flags.pgid_supp = 0;
		}
	}
	if (stsch(irq, &ioinfo[irq]->schib) != 0)
		/* FIXME: tell driver device is dead. */
		return -ENODEV;

	/*
	 * stsch() doesn't always yield the correct pim, pam, and pom
	 * values, if no device selection has been performed yet.
	 * However, after complete path verification they are up to date.
	 */
	ioinfo[irq]->opm = ioinfo[irq]->schib.pmcw.pim &
		ioinfo[irq]->schib.pmcw.pam &
		ioinfo[irq]->schib.pmcw.pom;

#ifdef CONFIG_CHSC	
	if (ioinfo[irq]->opm) {
		for (i=0;i<=7;i++) {
			int mask = 0x80 >> i;
			if ((ioinfo[irq]->opm & mask) &&
			    (!test_bit(ioinfo[irq]->schib.pmcw.chpid[i],
				       &chpids_logical)))
				/* disable using this path */
				ioinfo[irq]->opm &= ~mask;
		}
	}
#endif /* CONFIG_CHSC */	

	ioinfo[irq]->ui.flags.noio = 0;

	/* Eventually wake up the device driver. */
	if (ioinfo[irq]->opm != 0) {
		devreg_t *pdevreg;
		pdevreg = s390_search_devreg(ioinfo[irq]);

		if (pdevreg && pdevreg->oper_func)
			pdevreg->oper_func(irq, pdevreg);
	}
	return ret;

}

/*
 * Device Path Verification
 *
 * Path verification is accomplished by checking which paths (CHPIDs) are
 *  available. Further, a path group ID is set, if possible in multipath
 *  mode, otherwise in single path mode.
 *
 * Note : This function must not be called during normal device recognition,
 *         but during device driver initiated request_irq() processing only.
 */
int
s390_DevicePathVerification (int irq, __u8 usermask)
{
	int ccode;
#ifdef CONFIG_CHSC
	int chp;
	int mask;
	int old_opm = 0;
#endif /* CONFIG_CHSC */

	int ret = 0;

	char dbf_txt[15];
	devreg_t *pdevreg;

	sprintf (dbf_txt, "dpver%x", irq);
	CIO_TRACE_EVENT (4, dbf_txt);

	if (ioinfo[irq]->st) 
		return -ENODEV;

#ifdef CONFIG_CHSC
	old_opm = ioinfo[irq]->opm;
#endif /* CONFIG_CHSC */
	ccode = stsch (irq, &(ioinfo[irq]->schib));

	if (ccode)
		return -ENODEV;

	if (ioinfo[irq]->schib.pmcw.pim == 0x80) {
		/*
		 * no error, just not required for single path only devices
		 */
		ioinfo[irq]->ui.flags.pgid_supp = 0;
		ret = 0;
		ioinfo[irq]->ui.flags.noio = 0;

#ifdef CONFIG_CHSC
		/*
		 * disable if chpid is logically offline
		 */
		if (!test_bit(ioinfo[irq]->schib.pmcw.chpid[0], 
			      &chpids_logical)) {

			ioinfo[irq]->opm = 0;
			ioinfo[irq]->ui.flags.oper = 0;
			printk(KERN_WARNING 
			       "No logical path for sch %d...\n",
			       irq);

			if (ioinfo[irq]->nopfunc) {
				if (ioinfo[irq]->ui.flags.notacccap)
					ioinfo[irq]->nopfunc(irq,
							     DEVSTAT_NOT_ACC);
				else {
					not_oper_handler_func_t nopfunc =
						ioinfo[irq]->nopfunc;
#ifdef CONFIG_PROC_FS
					/* remove procfs entry */
					if (cio_proc_devinfo)
						cio_procfs_device_remove
							(ioinfo[irq]->devno);
#endif
					free_irq(irq,
						 ioinfo[irq]->irq_desc.dev_id);
					nopfunc(irq, DEVSTAT_DEVICE_GONE);
				}
			}
			return -ENODEV;
		}
		if (!old_opm) {

			ioinfo[irq]->opm = ioinfo[irq]->schib.pmcw.pim
				& ioinfo[irq]->schib.pmcw.pam
				& ioinfo[irq]->schib.pmcw.pom;
		
			if (ioinfo[irq]->opm) {

				ioinfo[irq]->ui.flags.oper = 1;
				pdevreg = s390_search_devreg(ioinfo[irq]);
			
				if (pdevreg && pdevreg->oper_func)
					pdevreg->oper_func(irq, pdevreg);
				ret = 0;
			} else {
				ret = -ENODEV;
			}
		}
#endif /* CONFIG_CHSC */
		return ret;
	}

	ioinfo[irq]->opm = ioinfo[irq]->schib.pmcw.pim
	    & ioinfo[irq]->schib.pmcw.pam & ioinfo[irq]->schib.pmcw.pom;

#ifdef CONFIG_CHSC
	if (ioinfo[irq]->opm) {
		for (chp=0;chp<=7;chp++) {
			mask = 0x80 >> chp;
			if ((ioinfo[irq]->opm & mask)
			    &&(!test_bit(ioinfo[irq]->schib.pmcw.chpid[chp],
					 &chpids_logical)))
				/* disable using this path */
				ioinfo[irq]->opm &= ~mask;
		}
	}
	
#endif /* CONFIG_CHSC */

	if (ioinfo[irq]->ui.flags.pgid_supp == 0) {		

		if (ioinfo[irq]->opm == 0)
			return -ENODEV;
			
		ioinfo[irq]->ui.flags.oper = 1;
		ioinfo[irq]->ui.flags.noio = 0;

		pdevreg = s390_search_devreg(ioinfo[irq]);
		
		if (pdevreg && pdevreg->oper_func)
			pdevreg->oper_func(irq, pdevreg);

		return 0;
	}

	if (ioinfo[irq]->ui.flags.ready)
		return s390_do_path_verification (irq, usermask);
	return 0;

}

void
s390_kick_path_verification (unsigned long irq)
{
	long cr6 __attribute__ ((aligned (8)));

	atomic_set (&ioinfo[irq]->pver_pending, 0);
	/* Do not enter path verification if sync_isc is enabled. */
	__ctl_store (cr6, 6, 6);
	if (cr6 & 0x04000000) {
		s390_schedule_path_verification (irq);
		return;
	}
	ioinfo[irq]->ui.flags.killio = 0;
	s390_DevicePathVerification(irq, 0xff);

}

static void
s390_schedule_path_verification(unsigned long irq)
{
	/* Protect against rescheduling, when already running */
	if (atomic_compare_and_swap (0, 1, &ioinfo[irq]->pver_pending)) {
		return;
	}

	/* 
	 * Call path verification.
	 * Note this is always called from inside the i/o layer, so we don't 
	 * need to care about the usermask.
	 */
	INIT_LIST_HEAD (&ioinfo[irq]->pver_bh.list);
	ioinfo[irq]->pver_bh.sync = 0;
	ioinfo[irq]->pver_bh.routine = (void*) (void*) s390_kick_path_verification;
	ioinfo[irq]->pver_bh.data = (void*) irq;
	queue_task (&ioinfo[irq]->pver_bh, &tq_immediate);
	mark_bh (IMMEDIATE_BH);
}

/*
 * s390_SetPGID
 *
 * Set Path Group ID
 *
 */
int
s390_SetPGID (int irq, __u8 lpm)
{
	ccw1_t *spid_ccw;	/* ccw area for SPID command */
	devstat_t devstat;	/* required by request_irq() */
	devstat_t *pdevstat = &devstat;
	unsigned long flags;
	char dbf_txt[15];

	int irq_ret = 0;	/* return code */
	int retry = 5;		/* retry count */
	int inlreq = 0;		/* inline request_irq() */
	int mpath = 1;		/* try multi-path first */

	SANITY_CHECK (irq);

	if (ioinfo[irq]->ui.flags.oper == 0) {
		return (-ENODEV);

	}

 	if (ioinfo[irq]->ui.flags.unfriendly) {
 		/* don't even try it */
 		return -EUSERS;
 	}

	sprintf (dbf_txt, "SPID%x", irq);
	CIO_TRACE_EVENT (4, dbf_txt);

	if (!ioinfo[irq]->ui.flags.ready) {
		/*
		 * Perform SetPGID command processing. We have to request device
		 *  ownership and provide a dummy I/O handler. We issue sync. I/O
		 *  requests and evaluate the devstat area on return therefore
		 *  we don't need a real I/O handler in place.
		 */
		irq_ret = request_irq (irq,
				       init_IRQ_handler,
				       SA_PROBE, "SPID", pdevstat);

		if (irq_ret == 0)
			inlreq = 1;
	} else {
		pdevstat = ioinfo[irq]->irq_desc.dev_id;

	}

	if (irq_ret) {
		return irq_ret;
	}

	s390irq_spin_lock_irqsave (irq, flags);

	if (init_IRQ_complete) {
		spid_ccw = kmalloc (2 * sizeof (ccw1_t), GFP_DMA | GFP_ATOMIC);
	} else {
		spid_ccw = alloc_bootmem_low (2 * sizeof (ccw1_t));
	}
	if (!spid_ccw) {
		s390irq_spin_unlock_irqrestore(irq, flags);
		if (inlreq)
			free_irq(irq, pdevstat);
		return -ENOMEM;
	}

	spid_ccw[0].cmd_code = CCW_CMD_SUSPEND_RECONN;
	spid_ccw[0].cda = 0;
	spid_ccw[0].count = 0;
	spid_ccw[0].flags = CCW_FLAG_SLI | CCW_FLAG_CC;

	spid_ccw[1].cmd_code = CCW_CMD_SET_PGID;
	spid_ccw[1].cda = (__u32) virt_to_phys (&ioinfo[irq]->pgid);
	spid_ccw[1].count = sizeof (pgid_t);
	spid_ccw[1].flags = CCW_FLAG_SLI;

	ioinfo[irq]->pgid.inf.fc = SPID_FUNC_MULTI_PATH | SPID_FUNC_ESTABLISH;

	/*
	 * We now issue a SetPGID request. In case of BUSY
	 *  or STATUS PENDING conditions we retry 5 times.
	 */
	do {
		memset (pdevstat, '\0', sizeof (devstat_t));

		irq_ret = s390_start_IO (irq, spid_ccw, 0xE2D7C9C4,	/* == SPID */
					 lpm,	/* n/a */
					 DOIO_WAIT_FOR_INTERRUPT
					 | DOIO_VALID_LPM
					 | DOIO_DONT_CALL_INTHDLR
					 | DOIO_TIMEOUT);

		if (!irq_ret) {
			if (pdevstat->flag & DEVSTAT_STATUS_PENDING) {
#ifdef CONFIG_DEBUG_IO
				printk (KERN_DEBUG "SPID - Device %04X "
					"on Subchannel %04X "
					"reports pending status, "
					"lpm = %x, "
					"retry : %d\n",
					ioinfo[irq]->schib.pmcw.dev,
					irq, lpm, retry);
#endif
				CIO_MSG_EVENT(2,
					      "SPID - Device %04X "
					      "on Subchannel %04X "
					      "reports pending status, "
					      "lpm = %x, "
					      "retry : %d\n",
					      ioinfo[irq]->schib.pmcw.
					      dev, irq, lpm, retry);
				retry--;
				irq_ret = -EIO;
			}

			if (pdevstat->flag == (DEVSTAT_START_FUNCTION
					       | DEVSTAT_FINAL_STATUS)) {
				retry = 0;	/* successfully set ... */
				irq_ret = 0;
			} else if (pdevstat->flag & DEVSTAT_FLAG_SENSE_AVAIL) {
				/*
				 * If the device doesn't support the
				 *  Sense Path Group ID command
				 *  further retries wouldn't help ...
				 */
				if (pdevstat->ii.sense.
				    data[0] & SNS0_CMD_REJECT) {
					if (mpath) {
						/*
						 * We now try single path mode.
						 * Note we must not issue the suspend
						 * multipath reconnect, or we will get
						 * a command reject by tapes.
						 */

						spid_ccw[0].cmd_code =
						    CCW_CMD_SET_PGID;
						spid_ccw[0].cda = (__u32)
						    virt_to_phys (&ioinfo[irq]->pgid);
						spid_ccw[0].count =
						    sizeof (pgid_t);
						spid_ccw[0].flags =
						    CCW_FLAG_SLI;

						ioinfo[irq]->pgid.inf.fc =
						    SPID_FUNC_SINGLE_PATH
						    | SPID_FUNC_ESTABLISH;
						mpath = 0;
						retry--;
						irq_ret = -EIO;
					} else {
						irq_ret = -EOPNOTSUPP;
						retry = 0;

					}
				} else {
#ifdef CONFIG_DEBUG_IO
					printk (KERN_WARNING
						"SPID - device %04X,"
						" unit check,"
						" retry %d, cnt %02d,"
						" lpm %x, sns :"
						" %02X%02X%02X%02X %02X%02X%02X%02X ...\n",
						ioinfo[irq]->schib.pmcw.
						dev, retry,
						pdevstat->scnt,
						lpm, 
						pdevstat->ii.sense.
						data[0],
						pdevstat->ii.sense.
						data[1],
						pdevstat->ii.sense.
						data[2],
						pdevstat->ii.sense.
						data[3],
						pdevstat->ii.sense.
						data[4],
						pdevstat->ii.sense.
						data[5],
						pdevstat->ii.sense.
						data[6],
						pdevstat->ii.sense.data[7]);
#endif

					CIO_MSG_EVENT(2,
						     "SPID - device %04X,"
						     " unit check,"
						     " retry %d, cnt %02d,"
						     " lpm %x, sns :"
						     " %02X%02X%02X%02X %02X%02X%02X%02X ...\n",
						     ioinfo[irq]->schib.
						     pmcw.dev, retry,
						     pdevstat->scnt,
						     lpm, 
						     pdevstat->ii.sense.
						     data[0],
						     pdevstat->ii.sense.
						     data[1],
						     pdevstat->ii.sense.
						     data[2],
						     pdevstat->ii.sense.
						     data[3],
						     pdevstat->ii.sense.
						     data[4],
						     pdevstat->ii.sense.
						     data[5],
						     pdevstat->ii.sense.
						     data[6],
						     pdevstat->ii.sense.
						     data[7]);

					retry--;
					irq_ret = -EIO;

				}

			} else if (pdevstat->flag & DEVSTAT_NOT_OPER) {
				/* don't issue warnings during startup unless requested */
				if (init_IRQ_complete || cio_notoper_msg) {

					printk (KERN_INFO
						"SPID - Device %04X "
						"on Subchannel %04X, "
						"lpm %02X, "
						"became 'not operational'\n",
						ioinfo[irq]->schib.pmcw.
						dev, irq,
						lpm);
					CIO_MSG_EVENT(2,
						     "SPID - Device %04X "
						     "on Subchannel %04X, "
						      "lpm %02X, "
						     "became 'not operational'\n",
						     ioinfo[irq]->schib.
						     pmcw.dev, irq,
						     lpm);
				}

				retry = 0;
				ioinfo[irq]->opm &= ~lpm;
				irq_ret = -EAGAIN;

			}

		} else if (irq_ret == -ETIMEDOUT) {
			/* 
			 * SetPGID timed out, so we cancel it before
			 * we retry
			 */
			int xret;

			xret = cancel_IO(irq);

			if (!xret) 
				CIO_MSG_EVENT(2,
					      "SetPGID: sch canceled "
					      "successfully for irq %x\n",
					      irq);
			retry--;

		} else if (irq_ret == -EBUSY) {
#ifdef CONFIG_DEBUG_IO
			printk(KERN_WARNING
			       "SPID - device %x, irq %x is busy!\n",
			       ioinfo[irq]->schib.pmcw.dev, irq);
#endif /* CONFIG_DEBUG_IO */
			CIO_MSG_EVENT(2,
				      "SPID - device %x, irq %x is busy!\n",
				      ioinfo[irq]->schib.pmcw.dev, irq);
			retry = 0;

		} else if (irq_ret != -ENODEV) {
			retry--;
			irq_ret = -EIO;
		} else if (!pdevstat->flag & DEVSTAT_NOT_OPER) {
			retry = 0;
			irq_ret = -ENODEV;
		} else {
			/* don't issue warnings during startup unless requested */
			if (init_IRQ_complete || cio_notoper_msg) {
				
				printk (KERN_INFO
					"SPID - Device %04X "
					"on Subchannel %04X, "
					"lpm %02X, "
					"became 'not operational'\n",
					ioinfo[irq]->schib.pmcw.
					dev, irq,
					lpm);
				CIO_MSG_EVENT(2,
					      "SPID - Device %04X "
					      "on Subchannel %04X, "
					      "lpm %02X, "
					      "became 'not operational'\n",
					      ioinfo[irq]->schib.
					      pmcw.dev, irq,
					      lpm);
			}

			retry = 0;
			ioinfo[irq]->opm &= ~lpm;

			if (ioinfo[irq]->opm != 0)
				irq_ret = -EAGAIN;
			else
				irq_ret = -ENODEV;

		}

	} while (retry > 0);

	if (init_IRQ_complete) {
		kfree (spid_ccw);
	} else {
		free_bootmem ((unsigned long) spid_ccw, 2 * sizeof (ccw1_t));

	}

	s390irq_spin_unlock_irqrestore (irq, flags);

	/*
	 * If we installed the irq action handler we have to
	 *  release it too.
	 */
	if (inlreq)
		free_irq (irq, pdevstat);

	return (irq_ret);
}

/*
 * s390_SensePGID
 *
 * Sense Path Group ID
 *
 */
int
s390_SensePGID (int irq, __u8 lpm, pgid_t * pgid)
{
	ccw1_t *snid_ccw;	/* ccw area for SNID command */
	devstat_t devstat;	/* required by request_irq() */
	devstat_t *pdevstat = &devstat;
	char dbf_txt[15];
	pgid_t * tmp_pgid;

	int irq_ret = 0;	/* return code */
	int retry = 5;		/* retry count */
	int inlreq = 0;		/* inline request_irq() */
	unsigned long flags;

	SANITY_CHECK (irq);

	if (ioinfo[irq]->ui.flags.oper == 0) {
		return (-ENODEV);

	}

	sprintf (dbf_txt, "SNID%x", irq);
	CIO_TRACE_EVENT (4, dbf_txt);

	if (!ioinfo[irq]->ui.flags.ready) {
		/*
		 * Perform SENSE PGID command processing. We have to request device
		 *  ownership and provide a dummy I/O handler. We issue sync. I/O
		 *  requests and evaluate the devstat area on return therefore
		 *  we don't need a real I/O handler in place.
		 */
		irq_ret = request_irq (irq,
				       init_IRQ_handler,
				       SA_PROBE, "SNID", pdevstat);

		if (irq_ret == 0)
			inlreq = 1;

	} else {
		pdevstat = ioinfo[irq]->irq_desc.dev_id;

	}

	if (irq_ret) {
		return irq_ret;
	}

	s390irq_spin_lock_irqsave (irq, flags);

	ioinfo[irq]->ui.flags.unfriendly = 0; /* assume it's friendly... */

	if (init_IRQ_complete) {
		snid_ccw = kmalloc (sizeof (ccw1_t), GFP_DMA | GFP_ATOMIC);
		tmp_pgid = kmalloc (sizeof (pgid_t), GFP_DMA | GFP_ATOMIC);
	} else {
		snid_ccw = alloc_bootmem_low (sizeof (ccw1_t));
		tmp_pgid = alloc_bootmem_low (sizeof (pgid_t));
	}

	if (!snid_ccw || !tmp_pgid) {
		if (snid_ccw) {
			if (init_IRQ_complete)
				kfree(snid_ccw);
			else
				free_bootmem((unsigned long) snid_ccw, sizeof(ccw1_t));
		}
		if (tmp_pgid) {
			if (init_IRQ_complete)
				kfree(tmp_pgid);
			else
				free_bootmem((unsigned long) tmp_pgid, sizeof(pgid_t));
		}
		s390irq_spin_unlock_irqrestore(irq, flags);
		if (inlreq) 
			free_irq (irq, pdevstat);
		return -ENOMEM;
	}

	snid_ccw->cmd_code = CCW_CMD_SENSE_PGID;
	snid_ccw->cda = (__u32) virt_to_phys (tmp_pgid);
	snid_ccw->count = sizeof (pgid_t);
	snid_ccw->flags = CCW_FLAG_SLI;

	/*
	 * We now issue a SensePGID request. In case of BUSY
	 *  or STATUS PENDING conditions we retry 5 times.
	 */
	do {
		memset (pdevstat, '\0', sizeof (devstat_t));

		irq_ret = s390_start_IO (irq, snid_ccw, 0xE2D5C9C4,	/* == SNID */
					 lpm,	/* n/a */
					 DOIO_WAIT_FOR_INTERRUPT
 					 | DOIO_TIMEOUT
					 | DOIO_VALID_LPM
					 | DOIO_DONT_CALL_INTHDLR);

		if (irq_ret == 0) {
			if (pdevstat->flag & DEVSTAT_FLAG_SENSE_AVAIL) {
				/*
				 * If the device doesn't support the
				 *  Sense Path Group ID command
				 *  further retries wouldn't help ...
				 */
				if (pdevstat->ii.sense.data[0] 
				    & (SNS0_CMD_REJECT | SNS0_INTERVENTION_REQ)) {
					retry = 0;
					irq_ret = -EOPNOTSUPP;
				} else {
#ifdef CONFIG_DEBUG_IO
					printk (KERN_WARNING
						"SNID - device %04X,"
						" unit check,"
						" flag %04X, "
						" retry %d, cnt %02d,"
						" sns :"
						" %02X%02X%02X%02X %02X%02X%02X%02X ...\n",
						ioinfo[irq]->schib.pmcw.
						dev, pdevstat->flag,
						retry, pdevstat->scnt,
						pdevstat->ii.sense.
						data[0],
						pdevstat->ii.sense.
						data[1],
						pdevstat->ii.sense.
						data[2],
						pdevstat->ii.sense.
						data[3],
						pdevstat->ii.sense.
						data[4],
						pdevstat->ii.sense.
						data[5],
						pdevstat->ii.sense.
						data[6],
						pdevstat->ii.sense.data[7]);

#endif
					CIO_MSG_EVENT(2,
						     "SNID - device %04X,"
						     " unit check,"
						     " flag %04X, "
						     " retry %d, cnt %02d,"
						     " sns :"
						     " %02X%02X%02X%02X %02X%02X%02X%02X ...\n",
						     ioinfo[irq]->schib.
						     pmcw.dev,
						     pdevstat->flag,
						     retry,
						     pdevstat->scnt,
						     pdevstat->ii.sense.
						     data[0],
						     pdevstat->ii.sense.
						     data[1],
						     pdevstat->ii.sense.
						     data[2],
						     pdevstat->ii.sense.
						     data[3],
						     pdevstat->ii.sense.
						     data[4],
						     pdevstat->ii.sense.
						     data[5],
						     pdevstat->ii.sense.
						     data[6],
						     pdevstat->ii.sense.
						     data[7]);
					retry--;
					irq_ret = -EIO;

				}
			} else if (pdevstat->flag & DEVSTAT_NOT_OPER) {
				/* don't issue warnings during startup unless requested */
				if (init_IRQ_complete || cio_notoper_msg) {
					printk (KERN_INFO
						"SNID - Device %04X "
						"on Subchannel %04X, "
						"lpm %02X, "
						"became 'not operational'\n",
						ioinfo[irq]->schib.pmcw.
						dev, irq,
						lpm);
					CIO_MSG_EVENT(2,
						     "SNID - Device %04X "
						     "on Subchannel %04X, "
						     "lpm %02X, "
						     "became 'not operational'\n",
						     ioinfo[irq]->schib.
						     pmcw.dev, irq,
						     lpm);
				}

				retry = 0;
				ioinfo[irq]->opm &= ~lpm;
				irq_ret = -EAGAIN;

			} else {
				retry = 0;	/* success ... */
				irq_ret = 0;
 				/*
 				 * Check if device is locked by someone else
 				 * -- we'll fail other commands if that is
 				 * the case
 				 */
 				if (tmp_pgid->inf.ps.state2 ==
 				    SNID_STATE2_RESVD_ELSE) {
 					printk (KERN_WARNING 
 						"SNID - Device %04X "
 						"on Subchannel %04X "
 						"is reserved by "
 						"someone else\n",
 						ioinfo[irq]->schib.pmcw.dev,
 						irq);
 					CIO_MSG_EVENT(2,
 						      "SNID - Device %04X "
 						      "on Subchannel %04X "
 						      "is reserved by "
 						      "someone else\n",
 						      ioinfo[irq]->schib.
 						      pmcw.dev,
 						      irq);
 				
 					ioinfo[irq]->ui.flags.unfriendly = 1;
 				} else {
 					/*
 					 * device is friendly to us :)
 					 */
 					ioinfo[irq]->ui.flags.unfriendly = 0;
 				}
				memcpy(pgid, tmp_pgid, sizeof(pgid_t));
			}

 		} else if (irq_ret == -ETIMEDOUT) {
#ifdef CONFIG_DEBUG_IO
 			printk(KERN_INFO "SNID - Operation timed out "
 			       "on Device %04X, Subchannel %04X... "
 			       "cancelling IO\n",
 			       ioinfo[irq]->schib.pmcw.dev,
 			       irq);
#endif /* CONFIG_DEBUG_IO */
 			CIO_MSG_EVENT(2,
 				      "SNID - Operation timed out "
 				      "on Device %04X, Subchannel %04X... "
 				      "cancelling IO\n",
 				      ioinfo[irq]->schib.pmcw.dev,
 				      irq);
 			cancel_IO(irq);
 			retry--;

		} else if (irq_ret != -ENODEV) {	/* -EIO, or -EBUSY */

			if (pdevstat->flag & DEVSTAT_STATUS_PENDING) {
#ifdef CONFIG_DEBUG_IO
				printk (KERN_INFO "SNID - Device %04X "
					"on Subchannel %04X "
					"reports pending status, "
					"retry : %d\n",
					ioinfo[irq]->schib.pmcw.dev,
					irq, retry);
#endif
				CIO_MSG_EVENT(2,
					     "SNID - Device %04X "
					     "on Subchannel %04X "
					     "reports pending status, "
					     "retry : %d\n",
					     ioinfo[irq]->schib.pmcw.
					     dev, irq, retry);
			}

			printk (KERN_WARNING "SNID - device %04X,"
				" start_io() reports rc : %d, retrying ...\n",
				ioinfo[irq]->schib.pmcw.dev, irq_ret);
			CIO_MSG_EVENT(2,
				      "SNID - device %04X,"
				      " start_io() reports rc : %d,"
				      " retrying ...\n",
				      ioinfo[irq]->schib.pmcw.dev, irq_ret);
			retry--;
			irq_ret = -EIO;
		} else if (!pdevstat->flag & DEVSTAT_NOT_OPER) {
			retry = 0;
			irq_ret = -ENODEV;
		} else {
			/* don't issue warnings during startup unless requested */
			if (init_IRQ_complete || cio_notoper_msg) {
				
				printk (KERN_INFO
					"SNID - Device %04X "
					"on Subchannel %04X, "
					"lpm %02X, "
					"became 'not operational'\n",
					ioinfo[irq]->schib.pmcw.
					dev, irq,
					lpm);
				CIO_MSG_EVENT(2,
					      "SNID - Device %04X "
					      "on Subchannel %04X, "
					      "lpm %02X, "
					      "became 'not operational'\n",
					      ioinfo[irq]->schib.
					      pmcw.dev, irq,
					      lpm);
			}

			retry = 0;
			ioinfo[irq]->opm &= ~lpm;

			if (ioinfo[irq]->opm != 0)
				irq_ret = -EAGAIN;
			else
				irq_ret = -ENODEV;

		}

	} while (retry > 0);

	if (init_IRQ_complete) {
		kfree (snid_ccw);
		kfree (tmp_pgid);
	} else {
		free_bootmem ((unsigned long) snid_ccw, sizeof (ccw1_t));
		free_bootmem ((unsigned long) tmp_pgid, sizeof (pgid_t));

	}

	s390irq_spin_unlock_irqrestore (irq, flags);

	/*
	 * If we installed the irq action handler we have to
	 *  release it too.
	 */
	if (inlreq)
		free_irq (irq, pdevstat);

	return (irq_ret);
}

void
s390_process_subchannel_source (int irq)
{
	int dev_oper = 0;
	int dev_no = -1;
	int lock = 0;
	int is_owned = 0;

	/*
	 * If the device isn't known yet
	 *   we can't lock it ...
	 */
	if (ioinfo[irq] != INVALID_STORAGE_AREA) {
		s390irq_spin_lock (irq);
		lock = 1;

		if (!ioinfo[irq]->st) {
			dev_oper = ioinfo[irq]->ui.flags.oper;
			
			if (ioinfo[irq]->ui.flags.dval)
				dev_no = ioinfo[irq]->devno;
			
			is_owned = ioinfo[irq]->ui.flags.ready;
		}

	}
#ifdef CONFIG_DEBUG_CRW
	printk (KERN_DEBUG
		"do_crw_pending : subchannel validation - start ...\n");
#endif
	CIO_CRW_EVENT(4, "subchannel validation - start\n");
	s390_validate_subchannel (irq, is_owned);

	if (irq > highest_subchannel)
		highest_subchannel = irq;

#ifdef CONFIG_DEBUG_CRW
	printk (KERN_DEBUG "do_crw_pending : subchannel validation - done\n");
#endif
	CIO_CRW_EVENT(4, "subchannel validation - done\n");
	/*
	 * After the validate processing
	 *   the ioinfo control block
	 *   should be allocated ...
	 */
	if (lock) {
		s390irq_spin_unlock (irq);
	}

	if (ioinfo[irq] != INVALID_STORAGE_AREA) {
#ifdef CONFIG_DEBUG_CRW
		printk (KERN_DEBUG "do_crw_pending : ioinfo at "
#ifdef CONFIG_ARCH_S390X
			"%08lX\n", (unsigned long) ioinfo[irq]
#else				/* CONFIG_ARCH_S390X */
			"%08X\n", (unsigned) ioinfo[irq]
#endif				/* CONFIG_ARCH_S390X */
			);
#endif
#ifdef CONFIG_ARCH_S390X
		CIO_CRW_EVENT(4, "ioinfo at %08lX\n", 
			      (unsigned long)ioinfo[irq]);
#else				/* CONFIG_ARCH_S390X */
		CIO_CRW_EVENT(4, "ioinfo at %08X\n", 
			      (unsigned)ioinfo[irq]);
#endif				/* CONFIG_ARCH_S390X */

		if (ioinfo[irq]->st)
			return;

		if (ioinfo[irq]->ui.flags.oper == 0) {
			not_oper_handler_func_t nopfunc = ioinfo[irq]->nopfunc;
#ifdef CONFIG_PROC_FS
			/* remove procfs entry */
			if (cio_proc_devinfo)
				cio_procfs_device_remove (dev_no);
#endif
			/*
			 * If the device has gone
			 *  call not oper handler               
			 */
			if ((dev_oper == 1)
			    && (nopfunc != NULL)) {

				free_irq (irq, ioinfo[irq]->irq_desc.dev_id);
				nopfunc (irq, DEVSTAT_DEVICE_GONE);

			}
		} else {
#ifdef CONFIG_DEBUG_CRW
			printk (KERN_DEBUG
				"do_crw_pending : device "
				"recognition - start ...\n");
#endif
			CIO_CRW_EVENT( 4,
				       "device recognition - start\n");
			s390_device_recognition_irq (irq);

#ifdef CONFIG_DEBUG_CRW
			printk (KERN_DEBUG
				"do_crw_pending : device "
				"recognition - done\n");
#endif
			CIO_CRW_EVENT( 4,
				       "device recognition - done\n");
			/*
			 * the device became operational
			 */
			if (dev_oper == 0) {
				devreg_t *pdevreg;

				pdevreg = s390_search_devreg (ioinfo[irq]);

				if (pdevreg && pdevreg->oper_func)
					pdevreg->oper_func(irq, pdevreg);

#ifdef CONFIG_PROC_FS
				/* add new procfs entry */
				if (cio_proc_devinfo)
					if (highest_subchannel <
					    MAX_CIO_PROCFS_ENTRIES) {
						cio_procfs_device_create
						    (ioinfo[irq]->devno);
					}
#endif
			}
			/*
			 * ... it is and was operational, but
			 *      the devno may have changed
			 */
			else if ((ioinfo[irq]->devno != dev_no)
				 && (ioinfo[irq]->nopfunc != NULL)) {
#ifdef CONFIG_PROC_FS
				int devno_old = ioinfo[irq]->devno;
#endif
				ioinfo[irq]->nopfunc (irq, DEVSTAT_REVALIDATE);
#ifdef CONFIG_PROC_FS
				/* remove old entry, add new */
				if (cio_proc_devinfo) {
					cio_procfs_device_remove (devno_old);
					cio_procfs_device_create
					    (ioinfo[irq]->devno);
				}
#endif
			}
		}
#ifdef CONFIG_PROC_FS
		/* get rid of dead procfs entries */
		if (cio_proc_devinfo)
			cio_procfs_device_purge ();
#endif
	}
}

#ifdef CONFIG_CHSC
static int 
chsc_get_sch_desc_irq(int irq)
{
	int j = 0;
	int ccode;

	spin_lock(&chsc_lock_ssd);
		
	if (!chsc_area_ssd)
		chsc_area_ssd = kmalloc(sizeof(chsc_area_t),GFP_KERNEL);
	
	if (!chsc_area_ssd) {
		printk( KERN_CRIT "No memory to determine sch descriptions...\n");
		spin_unlock(&chsc_lock_ssd);
		return -ENOMEM;
	}
	
	memset(chsc_area_ssd, 0, sizeof(chsc_area_t));
	
	chsc_area_ssd->request_block.command_code1=0x0010;
	chsc_area_ssd->request_block.command_code2=0x0004;
	chsc_area_ssd->request_block.request_block_data.ssd_req.f_sch=irq;
	chsc_area_ssd->request_block.request_block_data.ssd_req.l_sch=irq;
	
	ccode = chsc(chsc_area_ssd);
#ifdef CONFIG_DEBUG_CHSC
	if (ccode)
		printk( KERN_DEBUG "chsc returned with ccode = %d\n",ccode);
#endif /* CONFIG_DEBUG_CHSC */
	if (!ccode) {
		if (chsc_area_ssd->response_block.response_code == 0x0003) {
#ifdef CONFIG_DEBUG_CHSC
			printk( KERN_WARNING "Error in chsc request block!\n");
#endif /* CONFIG_DEBUG_CHSC */
			CIO_CRW_EVENT( 2, "Error in chsc request block!\n");
			spin_unlock(&chsc_lock_ssd);
			return -EINVAL;
			
		} else if (chsc_area_ssd->response_block.response_code == 0x0004) {
#ifdef CONFIG_DEBUG_CHSC
			printk( KERN_WARNING "Model does not provide ssd\n");
#endif /* CONFIG_DEBUG_CHSC */
			CIO_CRW_EVENT( 2, "Model does not provide ssd\n");
			spin_unlock(&chsc_lock_ssd);
			return -EOPNOTSUPP;

		} else if (chsc_area_ssd->response_block.response_code == 0x0002) {
#ifdef CONFIG_DEBUG_CHSC
			printk( KERN_WARNING "chsc: Invalid command!\n");
#endif /* CONFIG_DEBUG_CHSC */
			CIO_CRW_EVENT( 2,
				       "chsc: Invalid command!\n");
			return -EINVAL;

		} else if (chsc_area_ssd->response_block.response_code == 0x0001) {
			/* everything ok */
					
			switch (chsc_area_ssd->response_block.response_block_data.ssd_res.st) {
				
			case 0:  /* I/O subchannel */
				
				/* 
				 * All fields have meaning
				 */
#ifdef CONFIG_DEBUG_CHSC
				if (cio_show_msg) 
					printk( KERN_DEBUG 
						"ssd: sch %x is I/O subchannel\n",
						irq);
#endif /* CONFIG_DEBUG_CHSC */
				CIO_CRW_EVENT( 6,
					       "ssd: sch %x is I/O subchannel\n",
					       irq);

				if (ioinfo[irq] == INVALID_STORAGE_AREA)
					/* FIXME: we should do device rec. here... */
					break;

				ioinfo[irq]->ssd_info.valid = 1;
				ioinfo[irq]->ssd_info.type = 0;
				for (j=0;j<8;j++) {
					if ((0x80 >> j) & 
					    chsc_area_ssd->response_block.
					    response_block_data.ssd_res.path_mask & 
					    chsc_area_ssd->response_block.
					    response_block_data.ssd_res.fla_valid_mask) {

						if (chsc_area_ssd->response_block.
						    response_block_data.ssd_res.chpid[j]) 

							if (!test_and_set_bit
							    (chsc_area_ssd->response_block.
							     response_block_data.
							     ssd_res.chpid[j],
							     &chpids_known)) 

								if (test_bit
								    (chsc_area_ssd->response_block.
								     response_block_data.
								     ssd_res.chpid[j],
								     &chpids_logical))

									set_bit(chsc_area_ssd->response_block.
										response_block_data.
										ssd_res.chpid[j],
										&chpids);

						ioinfo[irq]->ssd_info.chpid[j] = 
							chsc_area_ssd->response_block.
							response_block_data.ssd_res.chpid[j];
						ioinfo[irq]->ssd_info.fla[j] = 
							chsc_area_ssd->response_block.
							response_block_data.ssd_res.fla[j];
					}
				}
				break;
				
			case 1:  /* CHSC subchannel */
				
				/*
				 * Only sch_val, st and sch have meaning
				 */
#ifdef CONFIG_DEBUG_CHSC
				if (cio_show_msg)
					printk( KERN_DEBUG 
						"ssd: sch %x is chsc subchannel\n",
						irq);
#endif /* CONFIG_DEBUG_CHSC */
				CIO_CRW_EVENT( 6,
					       "ssd: sch %x is chsc subchannel\n",
					       irq);

				if (ioinfo[irq] == INVALID_STORAGE_AREA)
					/* FIXME: we should do device rec. here... */
					break;

				ioinfo[irq]->ssd_info.valid = 1;
				ioinfo[irq]->ssd_info.type = 1;
				break;
				
			case 2: /* Message subchannel */
				
				/*
				 * All fields except unit_addr have meaning
				 */
#ifdef CONFIG_DEBUG_CHSC
				if (cio_show_msg)
					printk( KERN_DEBUG 
						"ssd: sch %x is message subchannel\n",
						irq);
#endif
				CIO_CRW_EVENT( 6,
					       "ssd: sch %x is message subchannel\n",
					       irq);

				if (ioinfo[irq] == INVALID_STORAGE_AREA)
					/* FIXME: we should do device rec. here... */
					break;

				ioinfo[irq]->ssd_info.valid = 1;
				ioinfo[irq]->ssd_info.type = 2;
				for (j=0;j<8;j++) {
					if ((0x80 >> j) & 
					    chsc_area_ssd->response_block.
					    response_block_data.ssd_res.path_mask & 
					    chsc_area_ssd->response_block.
					    response_block_data.ssd_res.fla_valid_mask) {
						if (chsc_area_ssd->response_block.
						    response_block_data.ssd_res.chpid[j])

							if (!test_and_set_bit
							    (chsc_area_ssd->response_block.
							     response_block_data.
							     ssd_res.chpid[j],
							     &chpids_known)) 

								if (test_bit
								    (chsc_area_ssd->response_block.
								     response_block_data.
								     ssd_res.chpid[j],
								     &chpids_logical))

									set_bit(chsc_area_ssd->response_block.
										response_block_data.
										ssd_res.chpid[j],
										&chpids);

						ioinfo[irq]->ssd_info.chpid[j] = 
							chsc_area_ssd->response_block.
							response_block_data.ssd_res.chpid[j];
						ioinfo[irq]->ssd_info.fla[j] = 
							chsc_area_ssd->response_block.
							response_block_data.ssd_res.fla[j];
					}
				}
				break;
				
			case 3: /* ADM subchannel */
				
				/*
				 * Only sch_val, st and sch have meaning
				 */
#ifdef CONFIG_DEBUG_CHSC
				if (cio_show_msg) 
					printk( KERN_DEBUG 
						"ssd: sch %x is ADM subchannel\n",
						irq);
#endif /* CONFIG_DEBUG_CHSC */
				CIO_CRW_EVENT( 6,
					       "ssd: sch %x is ADM subchannel\n",
					       irq);

				if (ioinfo[irq] == INVALID_STORAGE_AREA)
					/* FIXME: we should do device rec. here... */
					break;

				ioinfo[irq]->ssd_info.valid = 1;
				ioinfo[irq]->ssd_info.type = 3;
				break;
				
			default: /* uhm, that looks strange... */
#ifdef CONFIG_DEBUG_CHSC
				if (cio_show_msg) 
					printk( KERN_DEBUG 
						"Strange subchannel type %d for sch %x\n", 
						chsc_area_ssd->response_block.
						response_block_data.ssd_res.st,
						irq);
#endif /* CONFIG_DEBUG_CHSC */
				CIO_CRW_EVENT( 0, 
					       "Strange subchannel type %d for "
					       "sch %x\n", 
					       chsc_area_ssd->response_block.
					       response_block_data.ssd_res.st,
					       irq);
			}
			spin_unlock(&chsc_lock_ssd);
			return 0;
		}
	} else {
		spin_unlock(&chsc_lock_ssd);
		if (ccode == 3)
			return -ENODEV;
		return -EBUSY;
	}
	return -EIO;
}


static int 
chsc_get_sch_descriptions( void )
{

	int irq = 0;
	int err = 0;

	CIO_TRACE_EVENT( 4, "gsdesc");

	/*
	 * get information about chpids and link addresses 
	 * by executing the chsc command 'store subchannel description'
	 */

	if (init_IRQ_complete) {
		
		for (irq=0; irq<=highest_subchannel; irq++) {

			/*
			 * retrieve information for each sch
			 */
			err = chsc_get_sch_desc_irq(irq);
			if (err) {
				if (!cio_chsc_err_msg) {
					printk( KERN_ERR
						"chsc_get_sch_descriptions:"
						" Error %d while doing chsc; "
						"processing "
						"some machine checks may "
						"not work\n", 
						err);
					cio_chsc_err_msg=1;
				}
				return err;
			}
		}
		cio_chsc_desc_avail = 1;
		return 0;
	} else {
		/* Paranoia... */
		
		printk( KERN_ERR 
			"Error: chsc_get_sch_descriptions called before "
		       "initialization complete\n");
		return -EINVAL;
	}
	
}

__initcall(chsc_get_sch_descriptions);

static int
__check_for_io_and_kill(int irq, __u8 mask, int fatal)
{
	schib_t *schib = &ioinfo[irq]->schib;
	int ret = 0;

	if (schib->scsw.actl & SCSW_ACTL_DEVACT) {
		if ((ioinfo[irq]->opm != mask) ||
		     (fatal == 0)) {
			ret = CIO_PATHGONE_WAIT4INT;
		}
		if ((schib->scsw.actl & SCSW_ACTL_SCHACT) &&
		    (schib->pmcw.lpum == mask) &&
		    (fatal != 0)) {
			int cc;
			/* Kill the IO. It won't complete. */
			ioinfo[irq]->ui.flags.noio = 0;
			ioinfo[irq]->ui.flags.killio = 1;
			cc = clear_IO(irq, 0xD2C9D3D3, 0);
			if (cc != 0) {
				/* Eek, can't kill io. */
				CIO_CRW_EVENT(0, 
					      "Can't kill io on "
					      "sch %x, clear_IO "
					      "returned %d!\n",
					      irq, cc);
				ioinfo[irq]->ui.flags.killio = 0;
				s390irq_spin_unlock(irq);
				if ((cc == -ENODEV) && 
				    (ioinfo[irq]->nopfunc)) {
					ioinfo[irq]->ui.flags.oper = 0;
					ioinfo[irq]->nopfunc(irq,
							     DEVSTAT_DEVICE_GONE);
				}
				ret = CIO_PATHGONE_DEVGONE;
			} else {
				ret |= CIO_PATHGONE_WAIT4INT;
			}
			ioinfo[irq]->ui.flags.noio = 1;
			ret |= CIO_PATHGONE_IOERR;
		}

	} else if (schib->scsw.actl & (SCSW_ACTL_CLEAR_PEND |
				SCSW_ACTL_HALT_PEND |
				SCSW_ACTL_START_PEND |
				SCSW_ACTL_RESUME_PEND)) {
		if ((schib->pmcw.lpum != mask) ||
		    (fatal == 0)) {
			ret = CIO_PATHGONE_WAIT4INT;
		} else {
			int cc;
			/* Cancel the i/o. */
			cc = cancel_IO(irq);
			switch (cc) {
			case 0: 
				/* i/o cancelled; we can do path verif. */
				ret = CIO_PATHGONE_IOERR;
				break;
			case -EBUSY:
				/* Status pending, we'll get an interrupt */
				ret = CIO_PATHGONE_WAIT4INT;
				break;
			case -EINVAL:
				/*
				 * There is either not only the start function
				 * specified or we are subchannel active.
				 * Do a clear sch.
				 */
				ioinfo[irq]->ui.flags.noio = 0;
				ioinfo[irq]->ui.flags.killio = 1;
				cc = clear_IO(irq, 0xD2C9D3D3, 0);
				if (cc != 0) {
				/* Eek, can't kill io. */
					CIO_CRW_EVENT(0, 
						      "Can't kill io on "
						      "sch %x, clear_IO "
						      "returned %d!\n",
						      irq, cc);
					ioinfo[irq]->ui.flags.killio = 0;
					s390irq_spin_unlock(irq);
					if ((cc == -ENODEV) && 
					    (ioinfo[irq]->nopfunc)) {
						ioinfo[irq]->nopfunc(irq,
								     DEVSTAT_DEVICE_GONE);
						ioinfo[irq]->ui.flags.oper = 0;
					}
					ret = CIO_PATHGONE_DEVGONE;
				} else {
					ret = CIO_PATHGONE_WAIT4INT
						| CIO_PATHGONE_IOERR;
					ioinfo[irq]->ui.flags.noio = 1;
				}
				break;
			default: /* -ENODEV */
				s390irq_spin_unlock(irq);
				if (ioinfo[irq]->nopfunc) {
						ioinfo[irq]->ui.flags.oper = 0;
						ioinfo[irq]->nopfunc(irq,
								     DEVSTAT_DEVICE_GONE);
				}
				ret = CIO_PATHGONE_DEVGONE;
			}
		}
	}
	return ret;
}

void 
s390_do_chpid_processing( __u8 chpid)
{

	int irq;
	int j;
	char dbf_txt[15];

	sprintf(dbf_txt, "chpr%x", chpid);
	CIO_TRACE_EVENT( 2, dbf_txt);

	/* 
	 * TODO: the chpid may be not the chpid with the link incident,
	 * but the chpid the report came in through. How to handle???
	 */
	clear_bit(chpid, &chpids);
	if (!test_and_clear_bit(chpid, &chpids_known)) {
#ifdef CONFIG_DEBUG_CHSC
		pr_debug(KERN_DEBUG"Got link incident for unknown chpid %x\n",
		       chpid);
#endif /* CONFIG_DEBUG_CHSC */
		return;  /* we didn't know the chpid anyway */
	}

	for (irq=0;irq<=highest_subchannel;irq++) {
		schib_t *schib;
		
		if (ioinfo[irq] == INVALID_STORAGE_AREA) 
			continue;  /* we don't know the device anyway */
		if (ioinfo[irq]->st)
			continue; /* non-io subchannel */
		schib = &ioinfo[irq]->schib;
		for (j=0; j<8;j++) {
			int mask = 0x80 >> j;
			int out = 0;
			int err = 0;
			
			if (schib->pmcw.chpid[j] != chpid)
				continue;
			
			if (stsch(irq, schib) != 0) {
				ioinfo[irq]->ui.flags.oper = 0;
				if (ioinfo[irq]->nopfunc)
					ioinfo[irq]->nopfunc(irq, DEVSTAT_DEVICE_GONE);
				break;
			}
			
			s390irq_spin_lock(irq);
			
			ioinfo[irq]->ui.flags.noio = 1;
			
			/* Do we still expect an interrupt for outstanding io? */
			if (ioinfo[irq]->ui.flags.busy) {
				int rck = __check_for_io_and_kill(irq, mask, 1);
				if (rck & CIO_PATHGONE_WAIT4INT)
					out=1;
				if (rck & CIO_PATHGONE_IOERR)
					err=1;
				if (rck & CIO_PATHGONE_DEVGONE)
					break;
			}
			
			s390irq_spin_unlock(irq);
			
			/* 
			 * Tell the device driver not to disturb us. 
			 * If the driver is not capable of handling
			 * DEVSTAT_NOT_ACC, it doesn't want path grouping anyway.
			 */
			if (ioinfo[irq]->ui.flags.ready &&
			    schib->pmcw.pim != 0x80 &&
			    ioinfo[irq]->nopfunc &&
			    ioinfo[irq]->ui.flags.notacccap) {
				if (err)
					ioinfo[irq]->nopfunc(irq, DEVSTAT_NOT_ACC_ERR);
				else
					ioinfo[irq]->nopfunc(irq, DEVSTAT_NOT_ACC);
			}

			ioinfo[irq]->opm &= ~mask;
			
			if (out)
				break;

			/* 
			 * Always schedule the path verification, even if opm=0.
			 * Reason: We can't rely on stsch() to return latest&greatest
			 * values, if a device selections hasn't been performed, and
			 * we might miss a path we didn't get a mchk for.
			 */
			if (ioinfo[irq]->ui.flags.ready)
				s390_schedule_path_verification(irq);
			else {
				ioinfo[irq]->ui.flags.noio = 0;
				ioinfo[irq]->ui.flags.killio = 0;
			}
			break;
		}
	}	
}


void 
s390_do_res_acc_processing( __u8 chpid, __u16 fla, int info)
{

	char dbf_txt[15];
	int irq = 0;
	__u32 fla_mask = 0xffff;
	int chp;
	int mask;

	sprintf(dbf_txt, "accpr%x", chpid);
	CIO_TRACE_EVENT( 2, dbf_txt);
	if (info != CHSC_SEI_ACC_CHPID) {
		sprintf(dbf_txt, "fla%x", fla);
		CIO_TRACE_EVENT( 2, dbf_txt);
	}
	sprintf(dbf_txt, "info:%d", info);
	CIO_TRACE_EVENT( 2, dbf_txt);
	
	/*
	 * I/O resources may have become accessible.
	 * Scan through all subchannels that may be concerned and
	 * do a validation on those.
	 * The more information we have (info), the less scanning
	 * will we have to do.
	 */

	if (!cio_chsc_desc_avail) 
		chsc_get_sch_descriptions();

	if (!cio_chsc_desc_avail) {
		/*
		 * Something went wrong...
		 */
#ifdef CONFIG_DEBUG_CRW
		printk( KERN_WARNING 
			"Error: Could not retrieve subchannel descriptions, "
		       "will not process css machine check...\n");
#endif /* CONFIG_DEBUG_CRW */
		CIO_CRW_EVENT( 0,
			       "Error: Could not retrieve subchannel descriptions, "
			       "will not process css machine check...\n");
		return;
	} 

	if (!test_bit(chpid, &chpids_logical)) {
#ifdef CONFIG_DEBUG_CHSC
		printk(KERN_DEBUG"chpid %x is logically offline, "
		       "skipping res acc processing\n", chpid);
#endif /* CONFIG_DEBUG_CHSC */
		return; /* no need to do the rest */
	}

	switch (info) {
	case CHSC_SEI_ACC_CHPID: /*
				  * worst case, we only know about the chpid
				  * the devices are attached to
				  */
#ifdef CONFIG_DEBUG_CHSC
		printk( KERN_DEBUG "Looking at chpid %x...\n", chpid);
#endif /* CONFIG_DEBUG_CHSC */
		
		for (irq=0; irq<__MAX_SUBCHANNELS; irq++) {
			
			if((ioinfo[irq] != INVALID_STORAGE_AREA) 
			   && (ioinfo[irq]->st != 0))
				continue;
				
			if (ioinfo[irq] == INVALID_STORAGE_AREA) {
				/*
				 * We don't know the device yet, but since a path
				 * may be available now to the device we'll have
				 * to do recognition again.
				 * Since we don't have any idea about which chpid
				 * that beast may be on we'll have to do a stsch
				 * on all devices, grr...
				 */
				int valret = 0;
				
				valret = s390_validate_subchannel(irq,0);
				if (valret == -ENXIO) {
					/* We're through */
					return;
				}
				if (irq > highest_subchannel)
					highest_subchannel = irq;
				if (valret == 0)
					s390_device_recognition_irq(irq);
				continue;
			}

			for (chp=0;chp<=7;chp++) {
				mask = 0x80 >> chp;
				
				/*
				 * check if chpid is in information
				 * updated by ssd
				 */
				if ((!ioinfo[irq]->ssd_info.valid) ||
				    (ioinfo[irq]->ssd_info.chpid[chp] != chpid))
					continue;

				/* Tell the device driver not to disturb us. */
				if (ioinfo[irq]->ui.flags.ready &&
				    ioinfo[irq]->schib.pmcw.pim != 0x80 &&
				    ioinfo[irq]->nopfunc &&
				    ioinfo[irq]->ui.flags.notacccap)
					ioinfo[irq]->nopfunc(irq, DEVSTAT_NOT_ACC);

				ioinfo[irq]->ui.flags.noio = 1;

				/* Do we still expect an interrupt for outstanding io? */
				if (ioinfo[irq]->ui.flags.busy)
					/* Wait for interrupt. */
					break;

				if (ioinfo[irq]->ui.flags.ready) {
					s390_schedule_path_verification(irq);
				} else
					ioinfo[irq]->ui.flags.noio = 0;

				break;
			}
		}
		break;
		
	case CHSC_SEI_ACC_LINKADDR: /*
				     * better, we know the link determined by
				     * the link address and the chpid
				     */
		fla_mask = 0xff00;
		/* fallthrough */

	case CHSC_SEI_ACC_FULLLINKADDR: /*
					 * best case, we know the CU image
					 * by chpid and full link address
					 */

#ifdef CONFIG_DEBUG_CHSC
		printk( KERN_DEBUG "Looking at chpid %x, link addr %x...\n", 
			chpid, fla);
#endif /* CONFIG_DEBUG_CHSC */
		
		for (irq=0; irq<__MAX_SUBCHANNELS; irq++) {
			int j;
			/*
			 * Walk through all subchannels and
			 * look if our chpid and our (masked) link 
			 * address are in somewhere
			 * Do a stsch for the found subchannels and
			 * perform path grouping  
			 */
			if (ioinfo[irq] == INVALID_STORAGE_AREA) {
				/* The full program again (see above), grr... */
				int valret = 0;
				
				valret = s390_validate_subchannel(irq,0);
				if (valret == -ENXIO) {
					/* We're done */
					return;
				}
				if (irq > highest_subchannel)
					highest_subchannel = irq;
				if (valret == 0)
					s390_device_recognition_irq(irq);
				continue;
			}
			if (ioinfo[irq]->st != 0)
				continue;
				
			/* Update our ssd_info */
			if (chsc_get_sch_desc_irq(irq)) 
				break;
			
			for (j=0;j<8;j++) {
				if ((ioinfo[irq]->ssd_info.chpid[j] != chpid) ||
				    ((ioinfo[irq]->ssd_info.fla[j]&fla_mask) != fla))
					continue;
					
				/* Tell the device driver not to disturb us. */
				if (ioinfo[irq]->ui.flags.ready &&
				    ioinfo[irq]->schib.pmcw.pim != 0x80 &&
				    ioinfo[irq]->nopfunc &&
				    ioinfo[irq]->ui.flags.notacccap)
					ioinfo[irq]->nopfunc(irq, DEVSTAT_NOT_ACC);

				ioinfo[irq]->ui.flags.noio = 1;

				/* Do we still expect an interrupt for outstanding io? */
				if (ioinfo[irq]->ui.flags.busy)
					/* Wait for interrupt. */
					break;

				if (ioinfo[irq]->ui.flags.ready) {
					s390_schedule_path_verification(irq);
				} else
					ioinfo[irq]->ui.flags.noio = 0;
				
				break;
			}
			break;
			
		}
		break;

	default: BUG();
	}
}

static int
__get_chpid_from_lir(void *data)
{
	struct lir {
		u8  iq;
		u8  ic;
		u16 sci;
		/* incident-node descriptor */
		u32 indesc[28];
		/* attached-node descriptor */
		u32 andesc[28];
		/* incident-specific information */
		u32 isinfo[28];
	} *lir;

	lir = (struct lir*) data;
	if (!(lir->iq&0x80))
		/* NULL link incident record */
		return -EINVAL;
	if (!(lir->indesc[0]&0xc0000000))
		/* node descriptor not valid */
		return -EINVAL;
	if (!(lir->indesc[0]&0x10000000))
		/* don't handle device-type nodes - FIXME */
		return -EINVAL;
	/* Byte 3 contains the chpid. Could also be CTCA, but we don't care */

	return (u16) (lir->indesc[0]&0x000000ff);
}

void 
s390_process_css( void ) 
{

	int ccode, do_sei, chpid;

	CIO_TRACE_EVENT( 2, "prcss");

	spin_lock(&chsc_lock_sei);

	if (!chsc_area_sei) {
		if (init_IRQ_complete) 
			chsc_area_sei = kmalloc(sizeof(chsc_area_t),GFP_KERNEL);
		else
			chsc_area_sei = alloc_bootmem(sizeof(chsc_area_t));
	}
	
	if (!chsc_area_sei) {
		printk( KERN_CRIT 
			"No memory to store event information...\n");
		spin_unlock(&chsc_lock_sei);
		return;
	}

	do_sei = 1;

	while (do_sei) {
		
		do_sei = 0;

		/* 
		 * build the chsc request block for store event information 
		 * and do the call 
		 */
		memset(chsc_area_sei,0,sizeof(chsc_area_t));
		chsc_area_sei->request_block.command_code1=0x0010;
		chsc_area_sei->request_block.command_code2=0x000E;
		
		ccode = chsc(chsc_area_sei);
		
		
		if (ccode)
			break;

		/* for debug purposes, check for problems */
		if (chsc_area_sei->response_block.response_code == 0x0003) {
#ifdef CONFIG_DEBUG_CHSC
			printk( KERN_WARNING 
				"s390_process_css: error in chsc request block!\n");
#endif /* CONFIG_DEBUG_CHSC */
			CIO_CRW_EVENT( 2, 
				       "s390_process_css: "
				       "error in chsc request block!\n");
			break;
		}
		if (chsc_area_sei->response_block.response_code == 0x0005) {
#ifdef CONFIG_DEBUG_CHSC
			printk( KERN_WARNING 
				"s390_process_css: no event information stored\n");
#endif /* CONFIG_DEBUG_CHSC */
			CIO_CRW_EVENT( 2, 
				       "s390_process_css: "
				       "no event information stored\n");
			break;
		}
		if (chsc_area_sei->response_block.response_code == 0x0002) {
#ifdef CONFIG_DEBUG_CHSC
			printk( KERN_WARNING
				"s390_process_css: invalid command!\n");
#endif /* CONFIG_DEBUG_CHSC */
			CIO_CRW_EVENT( 2,
				       "s390_process_css: "
				       "invalid command!\n");
			break;
		}
		if (chsc_area_sei->response_block.response_code != 0x0001) {
#ifdef CONFIG_DEBUG_CHSC
			printk( KERN_WARNING
				"s390_process_css: unknown response code %d\n",
				chsc_area_sei->response_block.response_code);
#endif /* CONFIG_DEBUG_CHSC */
			CIO_CRW_EVENT( 2,
				       "s390_process_css: unknown response "
				       "code %d\n",
				       chsc_area_sei->response_block.response_code);
			break;
		}
		/* everything ok */
#ifdef CONFIG_DEBUG_CHSC
		printk( KERN_DEBUG 
			"s390_process_css: "
			"event information successfully stored\n");
#endif /* CONFIG_DEBUG_CHSC */
		CIO_CRW_EVENT( 4, 
			       "s390_process_css: "
			       "event information successfully stored\n");
					
		/* Check if there is more event information pending. */
		if (chsc_area_sei->response_block.response_block_data.
		    sei_res.flags & 0x80) {
#ifdef CONFIG_DEBUG_CHSC
			printk(KERN_INFO"s390_process_css: further event "
			       "information pending...\n");
#endif /* CONFIG_DEBUG_CHSC */
			CIO_CRW_EVENT( 2, "further event information pending\n");

			do_sei = 1;
		}

		/* Check if we might have lost some information. */
		if (chsc_area_sei->response_block.response_block_data.
		    sei_res.flags & 0x40) {
#ifdef CONFIG_DEBUG_CHSC
			printk(KERN_ERR"s390_process_css: Event information has "
			       "been lost due to overflow!\n");
#endif /* CONFIG_DEBUG_CHSC */
			CIO_CRW_EVENT( 2, "Event information has "
				       "been lost due to overflow!\n");
		}
		
		if (chsc_area_sei->response_block.
		    response_block_data.sei_res.rs != 4) {
#ifdef CONFIG_DEBUG_CHSC
			printk( KERN_ERR
				"s390_process_css: "
				"reporting source (%04X) isn't a chpid!\n",
				chsc_area_sei->response_block.
				response_block_data.sei_res.rsid);
#endif /* CONFIG_DEBUG_CHSC */
			CIO_CRW_EVENT( 2,
				       "s390_process_css: "
				       "reporting source (%04X) isn't a chpid!\n",
				       chsc_area_sei->response_block.
				       response_block_data.sei_res.rsid);
			continue;
		}

		/* which kind of information was stored? */
		switch (chsc_area_sei->response_block.
			response_block_data.sei_res.cc) {
		case 1: /* link incident*/
#ifdef CONFIG_DEBUG_CHSC
			printk( KERN_DEBUG 
				"s390_process_css: "
				"channel subsystem reports link incident,"
				" source is chpid %x\n", 
				chsc_area_sei->response_block.
				response_block_data.sei_res.rsid);
#endif /* CONFIG_DEBUG_CHSC */
			CIO_CRW_EVENT( 4,
				       "s390_process_css: "
				       "channel subsystem reports "
				       "link incident, "
				       "source is chpid %x\n", 
				       chsc_area_sei->response_block.
				       response_block_data.sei_res.rsid);
			
			chpid = __get_chpid_from_lir(chsc_area_sei->response_block.
						     response_block_data.sei_res.
						     ccdf);
			if (chpid >= 0)
				s390_do_chpid_processing(chpid);
			break;
			
		case 2: /* i/o resource accessibiliy */
#ifdef CONFIG_DEBUG_CHSC
			printk( KERN_DEBUG 
				"s390_process_css: channel subsystem "
				"reports some I/O devices "
				"may have become accessible\n");
#endif /* CONFIG_DEBUG_CHSC */
			CIO_CRW_EVENT( 4,
				       "s390_process_css: "
				       "channel subsystem reports "
				       "some I/O devices "
				       "may have become accessible\n");
#ifdef CONFIG_DEBUG_CHSC
			printk( KERN_DEBUG 
				"Data received after sei: \n");
			printk( KERN_DEBUG 
				"Validity flags: %x\n", 
				chsc_area_sei->response_block.
				response_block_data.sei_res.vf);
#endif /* CONFIG_DEBUG_CHSC */
			if ((chsc_area_sei->response_block.
			     response_block_data.sei_res.vf&0x80)
			    == 0) {
#ifdef CONFIG_DEBUG_CHSC
				printk( KERN_DEBUG "chpid: %x\n",
					chsc_area_sei->response_block.
					response_block_data.sei_res.rsid);
#endif /* CONFIG_DEBUG_CHSC */
				s390_do_res_acc_processing
					(chsc_area_sei->response_block.
					 response_block_data.sei_res.rsid,
					 0,
					 CHSC_SEI_ACC_CHPID);
			} else if ((chsc_area_sei->response_block.
				    response_block_data.sei_res.vf&0xc0)
				   == 0x80) {
#ifdef CONFIG_DEBUG_CHSC
				printk( KERN_DEBUG 
					"chpid: %x  link addr: %x\n",
					chsc_area_sei->response_block.
					response_block_data.sei_res.rsid,
					chsc_area_sei->response_block.
					response_block_data.sei_res.fla);
#endif /* CONFIG_DEBUG_CHSC */
				s390_do_res_acc_processing
					(chsc_area_sei->response_block.
					 response_block_data.sei_res.rsid, 
					 chsc_area_sei->response_block.
					 response_block_data.sei_res.fla,
					 CHSC_SEI_ACC_LINKADDR);
			} else if ((chsc_area_sei->response_block.
				    response_block_data.sei_res.vf & 0xc0)
				   == 0xc0) {
#ifdef CONFIG_DEBUG_CHSC
				printk( KERN_DEBUG 
					"chpid: %x  "
					"full link addr: %x\n",
					chsc_area_sei->response_block.
					response_block_data.sei_res.rsid,
					chsc_area_sei->response_block.
					response_block_data.sei_res.fla);
#endif /* CONFIG_DEBUG_CHSC */
				s390_do_res_acc_processing
					(chsc_area_sei->response_block.
					 response_block_data.sei_res.rsid, 
					 chsc_area_sei->response_block.
					 response_block_data.sei_res.fla,
					 CHSC_SEI_ACC_FULLLINKADDR);
			}
#ifdef CONFIG_DEBUG_CHSC
			printk( KERN_DEBUG "\n");
#endif /* CONFIG_DEBUG_CHSC */
			
			break;
			
		default: /* other stuff */
#ifdef CONFIG_DEBUG_CHSC
			printk( KERN_DEBUG 
				"s390_process_css: event %d\n",
				chsc_area_sei->response_block.
				response_block_data.sei_res.cc);
#endif /* CONFIG_DEBUG_CHSC */
			CIO_CRW_EVENT( 4, 
				       "s390_process_css: event %d\n",
				       chsc_area_sei->response_block.
				       response_block_data.sei_res.cc);
			
			break;
			
		}
	}
		
	spin_unlock(&chsc_lock_sei);
}
#endif

static void
__process_chp_gone(int irq, int chpid)
{
	schib_t *schib = &ioinfo[irq]->schib;
	int i;
	
	for (i=0;i<8;i++) {
		int mask = 0x80>>i;
		int out = 0;
		int err = 0;
		
		if (schib->pmcw.chpid[i] != chpid)
			continue;
		
		if (stsch(irq, schib) != 0) {
			ioinfo[irq]->ui.flags.oper = 0;
			if (ioinfo[irq]->nopfunc)
				ioinfo[irq]->nopfunc(irq, DEVSTAT_DEVICE_GONE);
			break;
		}
		
		s390irq_spin_lock(irq);
		
		ioinfo[irq]->ui.flags.noio = 1;
		
		/* Do we still expect an interrupt for outstanding io? */
		if (ioinfo[irq]->ui.flags.busy) {
			int rck = __check_for_io_and_kill(irq, mask, 1);
			if (rck & CIO_PATHGONE_WAIT4INT)
				out=1;
			if (rck & CIO_PATHGONE_IOERR)
				err=1;
			if (rck & CIO_PATHGONE_DEVGONE)
				break;
		}
		
		s390irq_spin_unlock(irq);

		/* Tell the device driver not to disturb us. */
		if (ioinfo[irq]->ui.flags.ready &&
		    schib->pmcw.pim != 0x80 &&
		    ioinfo[irq]->nopfunc &&
		    ioinfo[irq]->ui.flags.notacccap) {
			if (err)
				ioinfo[irq]->nopfunc(irq, DEVSTAT_NOT_ACC_ERR);
			else
				ioinfo[irq]->nopfunc(irq, DEVSTAT_NOT_ACC);
		}
		
		if (out)
			break;
		
		if (ioinfo[irq]->ui.flags.ready) {
			s390_schedule_path_verification(irq);
		} else {
			ioinfo[irq]->ui.flags.noio = 0;
			ioinfo[irq]->ui.flags.killio = 0;
		}
		break;
	}

}

static void
__process_chp_come(int irq, int chpid)
{
	schib_t *schib = &ioinfo[irq]->schib;
	int i;

	for (i=0;i<8;i++) {

		if (schib->pmcw.chpid[i] != chpid)
			continue;
		
		/* Tell the device driver not to disturb us. */
		if (ioinfo[irq]->ui.flags.ready &&
		    schib->pmcw.pim != 0x80 &&
		    ioinfo[irq]->nopfunc &&
		    ioinfo[irq]->ui.flags.notacccap)
			ioinfo[irq]->nopfunc(irq, DEVSTAT_NOT_ACC);
		
		ioinfo[irq]->ui.flags.noio = 1;
		
		/* Do we still expect an interrupt for outstanding io? */
		if (ioinfo[irq]->ui.flags.busy)
			/* Wait for interrupt. */
			break;
		
		if (ioinfo[irq]->ui.flags.ready)
			s390_schedule_path_verification(irq);
		else
			ioinfo[irq]->ui.flags.noio = 0;
		
		break;
	}
}

static void
s390_process_chp_source(int chpid, int onoff)
{
	int irq;
	int ret;
	char dbf_txt[15];

	sprintf(dbf_txt, "prchp%x", chpid);
	CIO_TRACE_EVENT(2, dbf_txt);

#ifdef CONFIG_CHSC
	if (onoff == 0) {
		clear_bit(chpid, &chpids);
	} else {
		set_bit(chpid, &chpids);
		set_bit(chpid, &chpids_known);
	}
#endif /* CONFIG_CHSC */

	if (onoff == 0) {
		for (irq=0;irq<=highest_subchannel;irq++) {

			if ((ioinfo[irq] == INVALID_STORAGE_AREA)
			    || (ioinfo[irq]->st != 0))
				continue;

			__process_chp_gone(irq, chpid);
		}
		return;
	}

	for (irq=0;irq<__MAX_SUBCHANNELS;irq++) {
		
		if (ioinfo[irq] == INVALID_STORAGE_AREA) {
			ret = s390_validate_subchannel(irq,0);
			if (ret == 0) {
				if (irq > highest_subchannel)
					highest_subchannel = irq;
#ifdef CONFIG_DEBUG_CRW
				printk(KERN_DEBUG"process_chp_source: Found "
				       "device on irq %x\n", irq);
#endif /* CONFIG_DEBUG_CRW */
				CIO_CRW_EVENT(4, "Found device on irq %x\n",
					      irq);
				s390_device_recognition_irq(irq);
			}
		} else if (ioinfo[irq]->st == 0) {
			ret = stsch(irq, &ioinfo[irq]->schib);
			if (ret != 0)
				ret = -ENXIO;
		} else
			continue;

		if (ret == -ENXIO)
			/* We're through. */
			return;

		if (ret != 0)
			continue;
		
		__process_chp_come(irq, chpid);
	}

}

/*
 * s390_do_crw_pending
 *
 * Called by the machine check handler to process CRW pending
 *  conditions. It may be a single CRW, or CRWs may be chained.
 *
 * Note : we currently process CRWs for subchannel source only
 */
void
s390_do_crw_pending (crwe_t * pcrwe)
{
	int irq;
	int chpid;
	
#ifdef CONFIG_DEBUG_CRW
	printk (KERN_DEBUG "do_crw_pending : starting ...\n");
#endif
	CIO_CRW_EVENT( 2, "do_crw_pending: starting\n");
	while (pcrwe != NULL) {

		switch (pcrwe->crw.rsc) {
		case CRW_RSC_SCH:
			
			irq = pcrwe->crw.rsid;
			
#ifdef CONFIG_DEBUG_CRW
			printk (KERN_NOTICE "do_crw_pending : source is "
				"subchannel %04X\n", irq);
#endif
			CIO_CRW_EVENT(2, "source is subchannel %04X\n",
				      irq);
			s390_process_subchannel_source (irq);

			break;

		case CRW_RSC_MONITOR:

#ifdef CONFIG_DEBUG_CRW
			printk (KERN_NOTICE "do_crw_pending : source is "
				"monitoring facility\n");
#endif
			CIO_CRW_EVENT(2, "source is monitoring facility\n");
			break;

		case CRW_RSC_CPATH:

			chpid = pcrwe->crw.rsid;

#ifdef CONFIG_DEBUG_CRW
			printk (KERN_NOTICE "do_crw_pending : source is "
				"channel path %02X\n", chpid);
#endif
			CIO_CRW_EVENT(2, "source is channel path %02X\n",
				      chpid);
			switch (pcrwe->crw.erc) {
			case CRW_ERC_IPARM: /* Path has come. */
				s390_process_chp_source(chpid, 1);
				break;
			case CRW_ERC_PERRI: /* Path has gone. */
				s390_process_chp_source(chpid, 0);
				break;
			default:
#ifdef CONFIG_DEBUG_CRW
				printk(KERN_WARNING"do_crw_pending: don't "
				       "know how to handle erc=%x\n",
				       pcrwe->crw.erc);
#endif /* CONFIG_DEBUG_CRW */
				CIO_CRW_EVENT(0, "don't know how to handle "
					      "erc=%x\n", pcrwe->crw.erc);
			}
			break;

		case CRW_RSC_CONFIG:

#ifdef CONFIG_DEBUG_CRW
			printk (KERN_NOTICE "do_crw_pending : source is "
				"configuration-alert facility\n");
#endif
			CIO_CRW_EVENT(2, "source is configuration-alert facility\n");
			break;

		case CRW_RSC_CSS:

#ifdef CONFIG_DEBUG_CRW
			printk (KERN_NOTICE "do_crw_pending : source is "
				"channel subsystem\n");
#endif
			CIO_CRW_EVENT(2, "source is channel subsystem\n");
#ifdef CONFIG_CHSC
			s390_process_css();
#endif
			break;

		default:

#ifdef CONFIG_DEBUG_CRW
			printk (KERN_NOTICE
				"do_crw_pending : unknown source\n");
#endif
			CIO_CRW_EVENT( 2, "unknown source\n");
			break;

		}

		pcrwe = pcrwe->crwe_next;

	}

#ifdef CONFIG_DEBUG_CRW
	printk (KERN_DEBUG "do_crw_pending : done\n");
#endif
	CIO_CRW_EVENT(2, "do_crw_pending: done\n");
	return;
}

/* added by Holger Smolinski for reipl support in reipl.S */
extern void do_reipl (int);
void
reipl (int sch)
{
	int i;
	s390_dev_info_t dev_info;

	for (i = 0; i <= highest_subchannel; i++) {
		if (get_dev_info_by_irq (i, &dev_info) == 0
		    && (dev_info.status & DEVSTAT_DEVICE_OWNED)) {
			free_irq (i, ioinfo[i]->irq_desc.dev_id);
		}
	}
	if (MACHINE_IS_VM)
		cpcmd ("IPL", NULL, 0);
	else
		do_reipl (0x10000 | sch);
}

/*
 * Function: cio_debug_init
 * Initializes three debug logs (under /proc/s390dbf) for common I/O:
 * - cio_msg logs the messages which are printk'ed when CONFIG_DEBUG_IO is on
 * - cio_trace logs the calling of different functions
 * - cio_crw logs the messages which are printk'ed when CONFIG_DEBUG_CRW is on
 * debug levels depend on CONFIG_DEBUG_IO resp. CONFIG_DEBUG_CRW
 */
int
cio_debug_init (void)
{
	int ret = 0;

	cio_debug_msg_id = debug_register ("cio_msg", 4, 4, 16 * sizeof (long));
	if (cio_debug_msg_id != NULL) {
		debug_register_view (cio_debug_msg_id, &debug_sprintf_view);
		debug_set_level (cio_debug_msg_id, 6);
	} else {
		ret = -1;
	}
	cio_debug_trace_id = debug_register ("cio_trace", 4, 4, 8);
	if (cio_debug_trace_id != NULL) {
		debug_register_view (cio_debug_trace_id, &debug_hex_ascii_view);
		debug_set_level (cio_debug_trace_id, 6);
	} else {
		ret = -1;
	}
	cio_debug_crw_id = debug_register ("cio_crw", 2, 4, 16 * sizeof (long));
	if (cio_debug_crw_id != NULL) {
		debug_register_view (cio_debug_crw_id, &debug_sprintf_view);
		debug_set_level (cio_debug_crw_id, 6);
	} else {
		ret = -1;
	}
	if (ret)
		return ret;
	cio_debug_initialized = 1;
	return 0;
}

__initcall (cio_debug_init);

#ifdef CONFIG_PROC_FS
#ifdef CONFIG_CHSC
/*
 * Function: cio_parse_chpids_proc_parameters
 * parse the stuff piped to /proc/chpids
 */
void 
cio_parse_chpids_proc_parameters(char* buf)
{
	int i;
	int cp;
	int ret;

	if (strstr(buf, "on ")) {
		for (i=0; i<3; i++) {
			buf++;
		}
		cp = blacklist_strtoul(buf, &buf);

		chsc_get_sch_descriptions();
		if (!cio_chsc_desc_avail) {
			printk(KERN_ERR "Could not get chpid status, "
			       "vary on/off not available\n");
			return;
		}
 
		if (!test_bit(cp, &chpids)) {
			ret = s390_vary_chpid(cp, 1);
			if (ret == -EINVAL) {
#ifdef CONFIG_DEBUG_CHSC
				printk(KERN_ERR "/proc/chpids: "
				       "Invalid chpid specified\n");
#else /* CONFIG_DEBUG_CHSC */
				printk(KERN_DEBUG "/proc/chpids: "
				       "Invalid chpid specified\n");
#endif /* CONFIG_DEBUG_CHSC */
			} else if (ret == 0) {
				printk(KERN_INFO "/proc/chpids: "
				       "Varied chpid %x logically online\n",
				       cp);
			}
		} else {
			printk(KERN_ERR "/proc/chpids: chpid %x is "
			       "already online\n",
			       cp);
		}
	} else if (strstr(buf, "off ")) {
		for (i=0; i<4; i++) {
			buf++;
		}
		cp = blacklist_strtoul(buf, &buf);

		chsc_get_sch_descriptions();
		if (!cio_chsc_desc_avail) {
			printk(KERN_ERR "Could not get chpid status, "
			       "vary on/off not available\n");
			return;
		}
		
		if (test_bit(cp, &chpids)) {
			ret = s390_vary_chpid(cp, 0);
			if (ret == -EINVAL) {
#ifdef CONFIG_DEBUG_CHSC
				printk(KERN_ERR "/proc/chpids: "
				       "Invalid chpid specified\n");
#else /* CONFIG_DEBUG_CHSC */
				printk(KERN_DEBUG "/proc/chpids: "
				       "Invalid chpid specified\n");
#endif /* CONFIG_DEBUG_CHSC */
			} else if (ret == 0) {
				printk(KERN_INFO "/proc/chpids: "
				       "Varied chpid %x logically offline\n",
				       cp);
			}
		} else { 
			printk(KERN_ERR "/proc/chpids: "
			       "chpid %x is already offline\n",
			       cp);
		}
	} else {
		printk(KERN_ERR "/proc/chpids: Parse error; "
		       "try using '{on,off} <chpid>'\n");
	}
}

static void
__vary_chpid_offline(int irq, int chpid)
{
	schib_t *schib = &ioinfo[irq]->schib;
	int i;
	
	for (i=0;i<8;i++) {
		int mask = 0x80>>i;
		int out = 0;
		unsigned long flags;
		
		if (ioinfo[irq]->ssd_info.chpid[i] != chpid)
			continue;
		
		s390irq_spin_lock_irqsave(irq, flags);
		
		ioinfo[irq]->ui.flags.noio = 1;
		
		/* Hmm, the path is not really gone... */
		if (ioinfo[irq]->ui.flags.busy) {
			if (__check_for_io_and_kill(irq, mask, 0) != 0)
				out=1;
		}
		
		s390irq_spin_unlock_irqrestore(irq, flags);

		/* Tell the device driver not to disturb us. */
		if (ioinfo[irq]->ui.flags.ready &&
		    schib->pmcw.pim != 0x80 &&
		    ioinfo[irq]->nopfunc &&
		    ioinfo[irq]->ui.flags.notacccap)
			ioinfo[irq]->nopfunc(irq, DEVSTAT_NOT_ACC);
		
		if (out)
			break;
		
		if (ioinfo[irq]->ui.flags.ready)
			s390_schedule_path_verification(irq);
		else
			ioinfo[irq]->ui.flags.noio = 0;

		break;
	}

}

static void
__vary_chpid_online(int irq, int chpid)
{
	schib_t *schib = &ioinfo[irq]->schib;
	int i;

	for (i=0;i<8;i++) {

		if (schib->pmcw.chpid[i] != chpid)
			continue;
		
		/* Tell the device driver not to disturb us. */
		if (ioinfo[irq]->ui.flags.ready &&
		    schib->pmcw.pim != 0x80 &&
		    ioinfo[irq]->nopfunc &&
		    ioinfo[irq]->ui.flags.notacccap)
			ioinfo[irq]->nopfunc(irq, DEVSTAT_NOT_ACC);
		
		ioinfo[irq]->ui.flags.noio = 1;
		
		/* Do we still expect an interrupt for outstanding io? */
		if (ioinfo[irq]->ui.flags.busy)
			/* Wait for interrupt. */
			break;
		
		s390_schedule_path_verification(irq);
		
		break;
	}
}


/*
 * Function: s390_vary_chpid
 * Varies the specified chpid online or offline
 */
int 
s390_vary_chpid( __u8 chpid, int on) 
{
	char dbf_text[15];
	int irq;

	if ((chpid <=0) || (chpid >= NR_CHPIDS))
		return -EINVAL;

	sprintf(dbf_text, on?"varyon%x":"varyoff%x", chpid);
	CIO_TRACE_EVENT( 2, dbf_text);

	if (!test_bit(chpid, &chpids_known)) {
		printk(KERN_ERR "Can't vary unknown chpid %02X\n", chpid);
		return -EINVAL;
	}

	if (on && test_bit(chpid, &chpids_logical)) {
		printk(KERN_ERR "chpid %02X already logically online\n", 
		       chpid);
		return -EINVAL;
	}

	if (!on && !test_bit(chpid, &chpids_logical)) {
		printk(KERN_ERR "chpid %02X already logically offline\n", 
		       chpid);
		return -EINVAL;
	}

	if (on) {
		set_bit(chpid, &chpids_logical);
		set_bit(chpid, &chpids);

	} else {
		clear_bit(chpid, &chpids_logical);
		clear_bit(chpid, &chpids);
	}

	/*
	 * Redo PathVerification on the devices the chpid connects to 
	 */
	
	for (irq=0;irq<=highest_subchannel;irq++) {

		if (ioinfo[irq] == INVALID_STORAGE_AREA)
			continue;

		if (ioinfo[irq]->st)
			continue;
		
		if (!ioinfo[irq]->ssd_info.valid)
			continue;

		if (on)
			__vary_chpid_online(irq, chpid);
		else
			__vary_chpid_offline(irq, chpid);

	}

	return 0;
}
#endif /* CONFIG_CHSC */

/* 
 * Display info on subchannels in /proc/subchannels. 
 * Adapted from procfs stuff in dasd.c by Cornelia Huck, 02/28/01.      
 */

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

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

static struct proc_dir_entry *chan_subch_entry;

static int
chan_subch_open (struct inode *inode, struct file *file)
{
	int rc = 0;
	int size = 1;
	int len = 0;
	int i = 0;
	int j = 0;
	tempinfo_t *info;

	info = (tempinfo_t *) vmalloc (sizeof (tempinfo_t));
	if (info == NULL) {
		printk (KERN_WARNING "No memory available for data\n");
		return -ENOMEM;
	} else {
		file->private_data = (void *) info;
	}

	size += (highest_subchannel + 1) * 128;
	info->data = (char *) vmalloc (size);

	if (size && info->data == NULL) {
		printk (KERN_WARNING "No memory available for data\n");
		vfree (info);
		return -ENOMEM;
	}

	len += sprintf (info->data + len,
			"Device sch.  Dev Type/Model CU  in use  PIM PAM POM CHPIDs\n");
	len += sprintf (info->data + len,
			"---------------------------------------------------------------------\n");

	for (i = 0; i <= highest_subchannel; i++) {
		if (!((ioinfo[i] == NULL) || (ioinfo[i] == INVALID_STORAGE_AREA)
		      || (ioinfo[i]->st )|| !(ioinfo[i]->ui.flags.oper))) {
			len +=
			    sprintf (info->data + len, "%04X   %04X  ",
				     ioinfo[i]->schib.pmcw.dev, i);
			if (ioinfo[i]->senseid.dev_type != 0) {
				len += sprintf (info->data + len,
						"%04X/%02X   %04X/%02X",
						ioinfo[i]->senseid.dev_type,
						ioinfo[i]->senseid.dev_model,
						ioinfo[i]->senseid.cu_type,
						ioinfo[i]->senseid.cu_model);
			} else {
				len += sprintf (info->data + len,
						"          %04X/%02X",
						ioinfo[i]->senseid.cu_type,
						ioinfo[i]->senseid.cu_model);
			}
			if (ioinfo[i]->ui.flags.ready) {
				len += sprintf (info->data + len, "  yes ");
			} else {
				len += sprintf (info->data + len, "      ");
			}
			len += sprintf (info->data + len,
					"    %02X  %02X  %02X  ",
					ioinfo[i]->schib.pmcw.pim,
					ioinfo[i]->schib.pmcw.pam,
					ioinfo[i]->schib.pmcw.pom);
			for (j = 0; j < 8; j++) {
				len += sprintf (info->data + len,
						"%02X",
						ioinfo[i]->schib.pmcw.chpid[j]);
				if (j == 3) {
					len += sprintf (info->data + len, " ");
				}
			}
			len += sprintf (info->data + len, "\n");
		}
	}
	info->len = len;

	return rc;
}

static int
chan_subch_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);
	}

	return rc;
}

static ssize_t
chan_subch_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;
	loff_t pos = *offset;

	if (pos < 0 || pos >= p_info->len) {
		return 0;
	} else {
		len = MIN (user_len, (p_info->len - pos));
		if (copy_to_user (user_buf, &(p_info->data[pos]), len))
			return -EFAULT;
		*offset = pos + len;
		return len;
	}
}

static struct file_operations chan_subch_file_ops = {
	read:chan_subch_read, open:chan_subch_open, release:chan_subch_close,
};

static int
chan_proc_init (void)
{
	chan_subch_entry =
	    create_proc_entry ("subchannels", S_IFREG | S_IRUGO, &proc_root);
	chan_subch_entry->proc_fops = &chan_subch_file_ops;

	return 1;
}

__initcall (chan_proc_init);

void
chan_proc_cleanup (void)
{
	remove_proc_entry ("subchannels", &proc_root);
}

/* 
 * Display device specific information under /proc/deviceinfo/<devno>
 */ static struct proc_dir_entry *cio_procfs_deviceinfo_root = NULL;

/* 
 * cio_procfs_device_list holds all devno-specific procfs directories
 */

typedef struct {
	int devno;
	struct proc_dir_entry *cio_device_entry;
	struct proc_dir_entry *cio_sensedata_entry;
	struct proc_dir_entry *cio_in_use_entry;
	struct proc_dir_entry *cio_chpid_entry;
} cio_procfs_entry_t;

typedef struct _cio_procfs_device {
	struct _cio_procfs_device *next;
	cio_procfs_entry_t *entry;
} cio_procfs_device_t;

cio_procfs_device_t *cio_procfs_device_list = NULL;

/*
 * File operations
 */

static int
cio_device_entry_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);
	}

	return rc;
}

static ssize_t
cio_device_entry_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;
	loff_t pos = *offset;

	if (pos < 0 || pos >= p_info->len) {
		return 0;
	} else {
		len = MIN (user_len, (p_info->len - pos));
		if (copy_to_user (user_buf, &(p_info->data[pos]), len))
			return -EFAULT;
		*offset = pos + len;
		return len;
	}
}

static int
cio_sensedata_entry_open (struct inode *inode, struct file *file)
{
	int rc = 0;
	int size = 1;
	int len = 0;
	tempinfo_t *info;
	int irq;
	int devno;
	char *devno_str;

	info = (tempinfo_t *) vmalloc (sizeof (tempinfo_t));
	if (info == NULL) {
		printk (KERN_WARNING "No memory available for data\n");
		rc = -ENOMEM;
	} else {
		file->private_data = (void *) info;
		size += 2 * 32;
		info->data = (char *) vmalloc (size);
		if (size && info->data == NULL) {
			printk (KERN_WARNING "No memory available for data\n");
			vfree (info);
			rc = -ENOMEM;
		} else {
			devno_str = kmalloc (6 * sizeof (char), GFP_KERNEL);
			memset (devno_str, 0, 6 * sizeof (char));
			memcpy (devno_str,
				file->f_dentry->d_parent->d_name.name,
				strlen (file->f_dentry->d_parent->d_name.name) +
				1);
			devno = simple_strtoul (devno_str, &devno_str, 16);
			irq = get_irq_by_devno (devno);
			if (irq != -1) {
				len +=
				    sprintf (info->data + len,
					     "Dev Type/Mod: ");
				if (ioinfo[irq]->senseid.dev_type == 0) {
					len +=
					    sprintf (info->data + len,
						     "%04X/%02X\n",
						     ioinfo[irq]->senseid.
						     cu_type,
						     ioinfo[irq]->senseid.
						     cu_model);
				} else {
					len +=
					    sprintf (info->data + len,
						     "%04X/%02X\n",
						     ioinfo[irq]->senseid.
						     dev_type,
						     ioinfo[irq]->senseid.
						     dev_model);
					len +=
					    sprintf (info->data + len,
						     "CU Type/Mod:  %04X/%02X\n",
						     ioinfo[irq]->senseid.
						     cu_type,
						     ioinfo[irq]->senseid.
						     cu_model);
				}
			}
			info->len = len;
		}
	}

	return rc;
}

static int
cio_in_use_entry_open (struct inode *inode, struct file *file)
{
	int rc = 0;
	int size = 1;
	int len = 0;
	tempinfo_t *info;
	int irq;
	int devno;
	char *devno_str;

	info = (tempinfo_t *) vmalloc (sizeof (tempinfo_t));
	if (info == NULL) {
		printk (KERN_WARNING "No memory available for data\n");
		rc = -ENOMEM;
	} else {
		file->private_data = (void *) info;
		size += 8;
		info->data = (char *) vmalloc (size);
		if (size && info->data == NULL) {
			printk (KERN_WARNING "No memory available for data\n");
			vfree (info);
			rc = -ENOMEM;
		} else {
			devno_str = kmalloc (6 * sizeof (char), GFP_KERNEL);
			memset (devno_str, 0, 6 * sizeof (char));
			memcpy (devno_str,
				file->f_dentry->d_parent->d_name.name,
				strlen (file->f_dentry->d_parent->d_name.name) +
				1);
			devno = simple_strtoul (devno_str, &devno_str, 16);
			irq = get_irq_by_devno (devno);
			if (irq != -1) {
				len +=
				    sprintf (info->data + len, "%s\n",
					     ioinfo[irq]->ui.flags.
					     ready ? "yes" : "no");
			}
			info->len = len;
		}
	}

	return rc;
}

static int
cio_chpid_entry_open (struct inode *inode, struct file *file)
{
	int rc = 0;
	int size = 1;
	int len = 0;
	tempinfo_t *info;
	int irq;
	int devno;
	int i;
	char *devno_str;

	info = (tempinfo_t *) vmalloc (sizeof (tempinfo_t));
	if (info == NULL) {
		printk (KERN_WARNING "No memory available for data\n");
		rc = -ENOMEM;
	} else {
		file->private_data = (void *) info;
		size += 8 * 16;
		info->data = (char *) vmalloc (size);
		if (size && info->data == NULL) {
			printk (KERN_WARNING "No memory available for data\n");
			vfree (info);
			rc = -ENOMEM;
		} else {
			devno_str = kmalloc (6 * sizeof (char), GFP_KERNEL);
			memset (devno_str, 0, 6 * sizeof (char));
			memcpy (devno_str,
				file->f_dentry->d_parent->d_name.name,
				strlen (file->f_dentry->d_parent->d_name.name) +
				1);
			devno = simple_strtoul (devno_str, &devno_str, 16);
			irq = get_irq_by_devno (devno);
			if (irq != -1) {
				for (i = 0; i < 8; i++) {
					len +=
					    sprintf (info->data + len,
						     "CHPID[%d]: ", i);
					len +=
					    sprintf (info->data + len, "%02X\n",
						     ioinfo[irq]->schib.pmcw.
						     chpid[i]);
				}
			}
			info->len = len;
		}
	}

	return rc;
}

static struct file_operations cio_sensedata_entry_file_ops = {
	read:cio_device_entry_read, open:cio_sensedata_entry_open,
	release:cio_device_entry_close,
};

static struct file_operations cio_in_use_entry_file_ops = {
	read:cio_device_entry_read, open:cio_in_use_entry_open,
	release:cio_device_entry_close,
};

static struct file_operations cio_chpid_entry_file_ops = {
	read:cio_device_entry_read, open:cio_chpid_entry_open,
	release:cio_device_entry_close,
};

/*
 * Function: cio_procfs_device_create
 * create procfs entry for given device number
 * and insert it into list
 */
int
cio_procfs_device_create (int devno)
{
	cio_procfs_entry_t *entry;
	cio_procfs_device_t *tmp;
	cio_procfs_device_t *where;
	char buf[8];
	int i;
	int rc = 0;

	/* create the directory entry */
	entry =
	    (cio_procfs_entry_t *) kmalloc (sizeof (cio_procfs_entry_t),
					    GFP_KERNEL);
	if (entry) {
		entry->devno = devno;
		sprintf (buf, "%x", devno);
		entry->cio_device_entry =
		    proc_mkdir (buf, cio_procfs_deviceinfo_root);

		if (entry->cio_device_entry) {
			tmp = (cio_procfs_device_t *)
			    kmalloc (sizeof (cio_procfs_device_t), GFP_KERNEL);
			if (tmp) {
				tmp->entry = entry;

				if (cio_procfs_device_list == NULL) {
					cio_procfs_device_list = tmp;
					tmp->next = NULL;
				} else {
					where = cio_procfs_device_list;
					i = where->entry->devno;
					while ((devno > i)
					       && (where->next != NULL)) {
						where = where->next;
						i = where->entry->devno;
					}
					if (where->next == NULL) {
						where->next = tmp;
						tmp->next = NULL;
					} else {
						tmp->next = where->next;
						where->next = tmp;
					}
				}
				/* create the different entries */
				entry->cio_sensedata_entry =
				    create_proc_entry ("sensedata",
						       S_IFREG | S_IRUGO,
						       entry->cio_device_entry);
				entry->cio_sensedata_entry->proc_fops =
				    &cio_sensedata_entry_file_ops;
				entry->cio_in_use_entry =
				    create_proc_entry ("in_use",
						       S_IFREG | S_IRUGO,
						       entry->cio_device_entry);
				entry->cio_in_use_entry->proc_fops =
				    &cio_in_use_entry_file_ops;
				entry->cio_chpid_entry =
				    create_proc_entry ("chpids",
						       S_IFREG | S_IRUGO,
						       entry->cio_device_entry);
				entry->cio_chpid_entry->proc_fops =
				    &cio_chpid_entry_file_ops;
			} else {
				printk (KERN_WARNING
					"Error, could not allocate procfs structure!\n");
				remove_proc_entry (buf,
						   cio_procfs_deviceinfo_root);
				kfree (entry);
				rc = -ENOMEM;
			}
		} else {
			printk (KERN_WARNING
				"Error, could not allocate procfs structure!\n");
			kfree (entry);
			rc = -ENOMEM;
		}

	} else {
		printk (KERN_WARNING
			"Error, could not allocate procfs structure!\n");
		rc = -ENOMEM;
	}
	return rc;
}

/*
 * Function: cio_procfs_device_remove
 * remove procfs entry for given device number
 */
int
cio_procfs_device_remove (int devno)
{
	int rc = 0;
	cio_procfs_device_t *tmp;
	cio_procfs_device_t *prev = NULL;

	tmp = cio_procfs_device_list;
	while (tmp) {
		if (tmp->entry->devno == devno)
			break;
		prev = tmp;
		tmp = tmp->next;
	}
	if (tmp) {
		char buf[8];

		remove_proc_entry ("sensedata", tmp->entry->cio_device_entry);
		remove_proc_entry ("in_use", tmp->entry->cio_device_entry);
		remove_proc_entry ("chpid", tmp->entry->cio_device_entry);
		sprintf (buf, "%x", devno);
		remove_proc_entry (buf, cio_procfs_deviceinfo_root);

		if (tmp == cio_procfs_device_list) {
			cio_procfs_device_list = tmp->next;
		} else {
			prev->next = tmp->next;
		}
		kfree (tmp->entry);
		kfree (tmp);
	} else {
		rc = -ENODEV;
	}

	return rc;
}

/*
 * Function: cio_procfs_purge
 * purge /proc/deviceinfo of entries for gone devices
 */

int
cio_procfs_device_purge (void)
{
	int i;

	for (i = 0; i <= highest_subchannel; i++) {
		if (ioinfo[i] != INVALID_STORAGE_AREA) {
			if (!ioinfo[i]->ui.flags.oper)
				cio_procfs_device_remove (ioinfo[i]->devno);
		}
	}
	return 0;
}

/*
 * Function: cio_procfs_create
 * create /proc/deviceinfo/ and subdirs for the devices
 */
static int
cio_procfs_create (void)
{
	int irq;

	if (cio_proc_devinfo) {

		cio_procfs_deviceinfo_root =
		    proc_mkdir ("deviceinfo", &proc_root);

		if (highest_subchannel >= MAX_CIO_PROCFS_ENTRIES) {
			printk (KERN_ALERT
				"Warning: Not enough inodes for creating all "
				"entries under /proc/deviceinfo/. "
				"Not every device will get an entry.\n");
		}

		for (irq = 0; irq <= highest_subchannel; irq++) {
			if (irq >= MAX_CIO_PROCFS_ENTRIES)
				break;
			if (ioinfo[irq] != INVALID_STORAGE_AREA) {
				if (ioinfo[irq]->ui.flags.oper)
					if (cio_procfs_device_create
					    (ioinfo[irq]->devno) == -ENOMEM) {
						printk (KERN_CRIT
							"Out of memory while creating "
							"entries in /proc/deviceinfo/, "
							"not all devices might show up\n");
						break;
					}
			}
		}

	}

	return 1;
}

__initcall (cio_procfs_create);

/*
 * Entry /proc/cio_ignore to display blacklisted ranges of devices.
 * un-ignore devices by piping to /proc/cio_ignore:
 * free all frees all blacklisted devices, free <range>,<range>,...
 * frees specified ranges of devnos
 * add <range>,<range>,... will add a range of devices to blacklist -
 * but only for devices not already known
 */

static struct proc_dir_entry *cio_ignore_proc_entry;
static int
cio_ignore_proc_open (struct inode *inode, struct file *file)
{
	int rc = 0;
	int size = 1;
	int len = 0;
	tempinfo_t *info;
	long flags;
	int i, j;

	info = (tempinfo_t *) vmalloc (sizeof (tempinfo_t));
	if (info == NULL) {
		printk (KERN_WARNING "No memory available for data\n");
		rc = -ENOMEM;
	} else {
		file->private_data = (void *) info;
		size += nr_ignored * 6;
		info->data = (char *) vmalloc (size);
		if (size && info->data == NULL) {
			printk (KERN_WARNING "No memory available for data\n");
			vfree (info);
			rc = -ENOMEM;
		} else {
			spin_lock_irqsave (&blacklist_lock, flags);
			for (i = 0; i <= highest_ignored; i++)
				if (test_bit (i, &bl_dev)) {
					len +=
					    sprintf (info->data + len, "%04x ",
						     i);
					for (j = i; (j <= highest_ignored)
					     && (test_bit (j, &bl_dev)); j++) ;
					j--;
					if (i != j)
						len +=
						    sprintf (info->data + len,
							     "- %04x", j);
					len += sprintf (info->data + len, "\n");
					i = j;
				}
			spin_unlock_irqrestore (&blacklist_lock, flags);
			info->len = len;
		}
	}
	return rc;
}

static int
cio_ignore_proc_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);
	}

	return rc;
}

static ssize_t
cio_ignore_proc_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;
	loff_t pos = *offset;

	if (pos < 0 || pos >= p_info->len) {
		return 0;
	} else {
		len = MIN (user_len, (p_info->len - *offset));
		if (copy_to_user (user_buf, &(p_info->data[*offset]), len))
			return -EFAULT;
		(*offset) = pos + len;
		return len;
	}
}

static ssize_t
cio_ignore_proc_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 + 1);

	if (buffer == NULL)
		return -ENOMEM;
	if (copy_from_user (buffer, user_buf, user_len)) {
		vfree (buffer);
		return -EFAULT;
	}
	buffer[user_len] = '\0';
#ifdef CONFIG_DEBUG_IO
	printk (KERN_DEBUG "/proc/cio_ignore: '%s'\n", buffer);
#endif /* CONFIG_DEBUG_IO */

	blacklist_parse_proc_parameters (buffer);

	vfree (buffer);
	return user_len;
}

static struct file_operations cio_ignore_proc_file_ops = {
	read:cio_ignore_proc_read, open:cio_ignore_proc_open,
	write:cio_ignore_proc_write, release:cio_ignore_proc_close,
};

static int
cio_ignore_proc_init (void)
{
	cio_ignore_proc_entry =
	    create_proc_entry ("cio_ignore", S_IFREG | S_IRUGO | S_IWUSR,
			       &proc_root);
	cio_ignore_proc_entry->proc_fops = &cio_ignore_proc_file_ops;

	return 1;
}

__initcall (cio_ignore_proc_init);

/*
 * Entry /proc/irq_count
 * display how many irqs have occured per cpu...
 */

static struct proc_dir_entry *cio_irq_proc_entry;

static int
cio_irq_proc_open (struct inode *inode, struct file *file)
{
	int rc = 0;
	int size = 1;
	int len = 0;
	tempinfo_t *info;
	int i;

	info = (tempinfo_t *) vmalloc (sizeof (tempinfo_t));
	if (info == NULL) {
		printk (KERN_WARNING "No memory available for data\n");
		rc = -ENOMEM;
	} else {
		file->private_data = (void *) info;
		size += NR_CPUS * 16;
		info->data = (char *) vmalloc (size);
		if (size && info->data == NULL) {
			printk (KERN_WARNING "No memory available for data\n");
			vfree (info);
			rc = -ENOMEM;
		} else {
			for (i = 0; i < NR_CPUS; i++) {
				if (s390_irq_count[i] != 0)
					len +=
					    sprintf (info->data + len, "%lx\n",
						     s390_irq_count[i]);
			}
			info->len = len;
		}
	}
	return rc;
}

static int
cio_irq_proc_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);
	}

	return rc;
}

static ssize_t
cio_irq_proc_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;
	loff_t pos = *offset;

	if (pos < 0 || pos >= p_info->len) {
		return 0;
	} else {
		len = MIN (user_len, (p_info->len - *offset));
		if (copy_to_user (user_buf, &(p_info->data[*offset]), len))
			return -EFAULT;
		(*offset) = pos + len;
		return len;
	}
}

static struct file_operations cio_irq_proc_file_ops = {
	read:cio_irq_proc_read, open:cio_irq_proc_open,
	release:cio_irq_proc_close,
};

static int
cio_irq_proc_init (void)
{

	int i;

	if (cio_count_irqs) {
		for (i = 0; i < NR_CPUS; i++)
			s390_irq_count[i] = 0;
		cio_irq_proc_entry =
		    create_proc_entry ("irq_count", S_IFREG | S_IRUGO,
				       &proc_root);
		cio_irq_proc_entry->proc_fops = &cio_irq_proc_file_ops;
	}

	return 1;
}

__initcall (cio_irq_proc_init);


#ifdef CONFIG_CHSC
/*
 * /proc/chpids to display available chpids
 * vary chpids on/off by piping to it
 */

static struct proc_dir_entry *cio_chpids_proc_entry;

static int 
cio_chpids_proc_open(struct inode *inode, struct file *file)
{
	int rc = 0;
	int size = 1;
	int len = 0;
	tempinfo_t *info;
	int i;

	if (!cio_chsc_desc_avail) {
		/* 
		 * We have not yet retrieved the link addresses,
		 * so we do it now.
		 */
		chsc_get_sch_descriptions();
	}


	info = (tempinfo_t *) vmalloc(sizeof(tempinfo_t));
	if (info == NULL) {
		printk( KERN_WARNING "No memory available for data\n");
		rc = -ENOMEM;
	} else {
		file->private_data = (void *) info;
		size += NR_CHPIDS * 16;
		info->data = (char *) vmalloc(size);
		if ( size && info->data == NULL) {
			printk( KERN_WARNING "No memory available for data\n");
			vfree (info);
			rc = -ENOMEM;
		} else {
			/* update our stuff */
			chsc_get_sch_descriptions();
			if (!cio_chsc_desc_avail) {
				len += sprintf(info->data+len, "no info available\n");
				goto cont;
			}

			for (i=0;i<NR_CHPIDS;i++) {
				if (test_bit(i, &chpids_known)) {
					if (!test_bit(i, &chpids))
						len += sprintf(info->data+len,
							       "%02X n/a\n",
							       i);
					else if (test_bit(i, &chpids_logical))
						len += sprintf(info->data+len, 
							       "%02X online\n", 
							       i);
					else 
						len += sprintf(info->data+len, 
							       "%02X logically "
							       "offline\n", 
							       i);
				}

			}
		cont:
			info->len = len;
		}
	}
	return rc;
}

static int 
cio_chpids_proc_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 );
     }
     
     return rc;
}

static ssize_t 
cio_chpids_proc_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;
     loff_t pos = *offset;
     
     if (pos < 0 || pos >= p_info->len) {
	  return 0;
     } else {
	  len = MIN(user_len, (p_info->len - pos));
	  if (copy_to_user( user_buf, &(p_info->data[pos]), len))
	       return -EFAULT; 
	  *offset = pos + len;
	  return len;
     }
}

static ssize_t 
cio_chpids_proc_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 + 1);

	if (buffer == NULL)
		return -ENOMEM;
	if (copy_from_user (buffer, user_buf, user_len)) {
		vfree (buffer);
		return -EFAULT;
	}
	buffer[user_len]='\0';
#ifdef CIO_DEBUG_IO
	printk("/proc/chpids: '%s'\n", buffer);
#endif /* CIO_DEBUG_IO */

	cio_parse_chpids_proc_parameters(buffer);

	vfree (buffer);
	return user_len;
}

static struct file_operations cio_chpids_proc_file_ops =
{
	read:cio_chpids_proc_read,
	open:cio_chpids_proc_open,
	write:cio_chpids_proc_write,
	release:cio_chpids_proc_close,
};

static int 
cio_chpids_proc_init(void)
{

	cio_chpids_proc_entry = create_proc_entry("chpids", S_IFREG|S_IRUGO|S_IWUSR, &proc_root);
	cio_chpids_proc_entry->proc_fops = &cio_chpids_proc_file_ops;
	
	return 1;


}

__initcall(cio_chpids_proc_init);
#endif
/* end of procfs stuff */
#endif

schib_t *
s390_get_schib (int irq)
{
	if ((irq > highest_subchannel) || (irq < 0))
		return NULL;
	if (ioinfo[irq] == INVALID_STORAGE_AREA)
		return NULL;
	if (ioinfo[irq]->st)
		return NULL;
	return &ioinfo[irq]->schib;

}

int
s390_set_private_data(int irq, void *data)
{
	SANITY_CHECK(irq);
	
	ioinfo[irq]->private_data = data;
		
	return 0;
}

void *
s390_get_private_data(int irq)
{
	if ((irq > highest_subchannel) || (irq < 0))
		return NULL;
	if (ioinfo[irq] == INVALID_STORAGE_AREA)
		return NULL;
	if (ioinfo[irq]->st)
		return NULL;
	return ioinfo[irq]->private_data;
}

EXPORT_SYMBOL (halt_IO);
EXPORT_SYMBOL (clear_IO);
EXPORT_SYMBOL (do_IO);
EXPORT_SYMBOL (resume_IO);
EXPORT_SYMBOL (ioinfo);
EXPORT_SYMBOL (diag210);
EXPORT_SYMBOL (get_dev_info_by_irq);
EXPORT_SYMBOL (get_dev_info_by_devno);
EXPORT_SYMBOL (get_irq_by_devno);
EXPORT_SYMBOL (get_devno_by_irq);
EXPORT_SYMBOL (get_irq_first);
EXPORT_SYMBOL (get_irq_next);
EXPORT_SYMBOL (read_conf_data);
EXPORT_SYMBOL (read_dev_chars);
EXPORT_SYMBOL (s390_request_irq_special);
EXPORT_SYMBOL (s390_get_schib);
EXPORT_SYMBOL (s390_register_adapter_interrupt);
EXPORT_SYMBOL (s390_unregister_adapter_interrupt);
EXPORT_SYMBOL (s390_set_private_data);
EXPORT_SYMBOL (s390_get_private_data);
EXPORT_SYMBOL (s390_trigger_resense);
