/***********************************************************************
 *  drivers/s390/char/tape.c
 *    tape device driver for S/390 and zSeries tapes.
 *
 *  S390 and zSeries version
 *    Copyright (C) 2001 IBM Corporation
 *    Author(s): Carsten Otte <cotte@de.ibm.com>
 *               Michael Holzheu <holzheu@de.ibm.com>
 *               Tuan Ngo-Anh <ngoanh@de.ibm.com>
 *
 ***********************************************************************
 */

#include "tapedefs.h" // kernel 2.2 compatibility defines

#include <linux/stddef.h>    // defines NULL
#include <linux/proc_fs.h>   // for /proc/tapedevices
#include <linux/init.h>      // for kernel parameters
#include <linux/kmod.h>      // for requesting modules
#include <linux/spinlock.h>  // for locks
#include <asm/types.h>       // for variable types
#ifdef CONFIG_S390_TAPE_DYNAMIC
#include <asm/s390dyn.h>
#endif
#include "tape.h"
#ifdef CONFIG_S390_TAPE_3590
#include "tape3590.h"
#endif
#ifdef CONFIG_S390_TAPE_3490
#include "tape3490.h"
#endif
#ifdef CONFIG_S390_TAPE_3480
#include "tape3480.h"
#endif
#ifdef CONFIG_S390_TAPE_BLOCK
#include "tapeblock.h"
#endif
#ifdef CONFIG_S390_TAPE_CHAR
#include "tapechar.h"
#endif
#ifdef CONFIG_PROC_FS
#include <linux/vmalloc.h>
#endif
#define PRINTK_HEADER "T390:"
#define TAPE_MAX_DEVREGS        (256 / TAPE_MINORS_PER_DEV)

#define TAPE_NO_IO          0
#define TAPE_DO_IO          1

#define TAPE_CIO_PRIVATE_DATA

#ifdef CONFIG_KMOD
#define tape_request_module(a) request_module(a)
#else
#define tape_request_module(a)
#endif

/*******************************************************************
 * Internal Prototypes
 *******************************************************************/

static void tape_do_irq (int irq, void *int_parm, struct pt_regs *regs);
static inline int tape_halt_io(tape_dev_t* td);
static void tape_wait(tape_ccw_req_t* treq);
#ifdef CONFIG_S390_TAPE_DYNAMIC
/* functions for dyn. dev. attach/detach */
static int tape_oper_handler ( int irq, struct _devreg *dreg);
static void tape_noper_handler ( int irq, int status );
#endif
static inline void tape_disable_device(tape_dev_t* td);
static inline int tape_enable_device(tape_dev_t* td);


/*******************************************************************
 * GLOBALS
 *******************************************************************/

static devreg_t          *tape_devreg[TAPE_MAX_DEVREGS];
static int               tape_devregct=0;
static int               tape_autoprobe = 1;
static tape_discipline_t *tape_first_disc = NULL;
tape_dev_t               *tape_first_dev = NULL;
tape_frontend_t          *tape_first_front = NULL;
char                     *tape[256] = { NULL, }; 

/*
 * Lock hirarchy:
 * tape_discipline_lock > tape_dev_lock > td->lock
 */

rwlock_t   tape_dev_lock=RW_LOCK_UNLOCKED;
static rwlock_t   tape_discipline_lock=RW_LOCK_UNLOCKED;

#ifdef TAPE_DEBUG
debug_info_t *tape_dbf_area = NULL;
#endif

const char* tape_med_st_verbose[MS_SIZE]={
	"UNKNOWN ",
	"LOADED  ",
	"UNLOADED"
};

const char* tape_state_verbose[TS_SIZE]={
	"UNUSED",
	"IN_USE", 
	"INIT  ",
	"NOT_OP"
};

const char* tape_op_verbose[TO_SIZE] = {
	"BLK",
	"BSB",
	"BSF",
	"DSE",
	"EGA",
	"FSB",
	"FSF",
	"LDI",
	"LBL",
	"MSE",
	"NOP",
	"RBA",
	"RBI",
	"RBU",
	"RBL",
	"RDC",
	"RFO",
	"RSD",
	"REW",
	"RUN",
	"SEN",
	"SID",
	"SNP",
	"SPG",
	"SWI",
	"SMR",
	"SYN",
	"TIO",
	"UNA",
	"WRI",
	"WTM",
	"MSN",
	"LOA",
	"RCF", /* 3590 */
	"RAT", /* 3590 */
	"NOT"
};

/*******************************************************************
 * DEVFS Functions
 *******************************************************************/

#ifdef CONFIG_DEVFS_FS
devfs_handle_t tape_devfs_root_entry;

/*
 * Create devfs root entry (devno in hex) for device td
 */

static inline devfs_handle_t
tape_mkdevfsroot (tape_dev_t* td) 
{
    char devno [5];
    sprintf (devno,"%04x",td->devinfo.devno);
    return (td->devfs_dir=devfs_mk_dir (tape_devfs_root_entry, devno, td));
}

/*
 * Remove devfs root entry for device td
 */

static inline void
tape_rmdevfsroot (tape_dev_t* td)
{
    if (td->devfs_dir)
            devfs_unregister (td->devfs_dir);
}

#endif

/*******************************************************************
 * PROCFS Functions
 *******************************************************************/

#ifdef CONFIG_PROC_FS
/* functions used in tape_proc_file_ops */
static ssize_t tape_proc_devices_read (struct file *file, char *user_buf, size_t user_len, loff_t * offset);
static int tape_proc_devices_open (struct inode *inode, struct file *file);
static int tape_proc_devices_release (struct inode *inode, struct file *file);

/* our proc tapedevices entry */
static struct proc_dir_entry *tape_proc_devices;

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

static struct file_operations tape_proc_devices_file_ops =
{
	read:tape_proc_devices_read,	/* read */
	open:tape_proc_devices_open,	/* open */
	release:tape_proc_devices_release,	/* close */
};

/* 
 * Initialize procfs stuff on startup
 */

static inline void
tape_proc_init (void) {
	tape_proc_devices = create_proc_entry ("tapedevices",
						S_IFREG | S_IRUGO | S_IWUSR,
						&proc_root);
	if (tape_proc_devices == NULL) 
	        goto error;
	tape_proc_devices->proc_fops = &tape_proc_devices_file_ops;
	tape_proc_devices->proc_iops = &tape_proc_devices_inode_ops;
	return;
 error:
	PRINT_WARN ("tape: Cannot register procfs entry tapedevices\n");
	return;
}

/*
 * Open function for /proc/tapedevices
 */

static int
tape_proc_devices_open (struct inode *inode, struct file *file)
{
	tape_dev_t* td;
	tape_procinfo_t* procinfo;
	char* data = NULL;
	int size=0,check_size = -1;
	int pos=0;
	int rc=0;
	long lockflags,lockflags2;
	tape_ccw_req_t *treq;

	MOD_INC_USE_COUNT;

	procinfo = kmalloc (sizeof(tape_procinfo_t),GFP_KERNEL);
	if (!procinfo){
		rc = -ENOMEM;
		MOD_DEC_USE_COUNT;
		goto out_no_lock;
	}

	/* Find out mem size for output, ensure that after releasing lock   */
	/* (vmalloc must not be called with interrupts disabled) no devices */
	/* have been added/removed                                          */

	do{
		size = 100; // Headline
		read_lock_irqsave(&tape_dev_lock,lockflags);

		for (td=tape_first_dev;td!=NULL;td=td->next)
			size+=100; // FIXME: Guess better!

		if(size == check_size)
                        break;

		read_unlock_irqrestore(&tape_dev_lock,lockflags);

		if(data)
			vfree(data);
		data=vmalloc(size);
		if (!data) {
                    kfree (procinfo);
                    rc = -ENOMEM;
                    goto out_no_lock;
		}
		check_size = size;
	}while (1);

	// We have the tape_dev lock now

#ifdef CONFIG_S390_TAPE_CHAR
	pos+=sprintf(data+pos,"TapeNo\tDevNo\tCuType\tCuModel\tDevType\tDevMod\tBlkSize\tState\tOp\tMedState\n");
#else
	pos+=sprintf(data+pos,"TapeNo\tDevNo\tCuType\tCuModel\tDevType\tDevMod\tState\tOp\tMedState\n");
#endif

	for (td=tape_first_dev;td!=NULL;td=td->next) {
		s390irq_spin_lock_irqsave (td->devinfo.irq,lockflags2);
		treq = tape_get_active_ccw_req(td);
		pos+=sprintf(data+pos,"%d\t",td->first_minor/TAPE_MINORS_PER_DEV);
		pos+=sprintf(data+pos,"%04X\t",td->devinfo.devno);
		pos+=sprintf(data+pos,"%04X\t",td->devinfo.sid_data.cu_type);
		pos+=sprintf(data+pos,"%02X\t",td->devinfo.sid_data.cu_model);
		pos+=sprintf(data+pos,"%04X\t",td->devinfo.sid_data.dev_type);
		pos+=sprintf(data+pos,"%02X\t",td->devinfo.sid_data.dev_model);
#ifdef CONFIG_S390_TAPE_CHAR
		if(td->char_data.block_size == 0)
			pos+=sprintf(data+pos,"auto\t");
		else
			pos+=sprintf(data+pos,"%i\t",td->char_data.block_size);
#endif
		pos+=sprintf(data+pos,"%s\t",((tape_state_get(td) >= 0) &&
			(tape_state_get(td) < TS_SIZE)) ?
			tape_state_verbose[tape_state_get (td)] : "UNKNOWN");
		pos+=sprintf(data+pos,"%s\t",(treq != NULL) ? 
			tape_op_verbose[treq->op] : "---");
		pos+=sprintf(data+pos,"%s\n",tape_med_st_verbose[td->medium_state]);
		s390irq_spin_unlock_irqrestore (td->devinfo.irq,lockflags2);
	}

	procinfo->data=data;
	procinfo->len=pos;
        if (pos>size) BUG(); // we've overwritten some memory
	file->private_data= (void*) procinfo;
	read_unlock_irqrestore(&tape_dev_lock,lockflags);
out_no_lock:
	return rc;
}

/*
 * Read function for /proc/tapedevices
 */

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

	if (*offset >= p_info->len) {
		goto out; /* EOF */
	} else {
		len =  user_len<(p_info->len - *offset)?user_len:(p_info->len - *offset);
		if (copy_to_user (user_buf, &(p_info->data[*offset]), len))
			return -EFAULT;
		(*offset) += len;
	}
out:
	return len;
}

/*
 * Close function for /proc/tapedevices
 */

static int
tape_proc_devices_release (struct inode *inode, struct file *file)
{
	int rc = 0;
	tape_procinfo_t *p_info = (tape_procinfo_t *) file->private_data;
        vfree(p_info->data);
        kfree (p_info);
        MOD_DEC_USE_COUNT;
	return rc;
}

/*
 * Cleanup all stuff registered to the procfs
 */
static inline void
tape_proc_cleanup (void)
{
        if (tape_proc_devices != NULL)
		remove_proc_entry ("tapedevices", &proc_root);
}

#endif /* CONFIG_PROC_FS */

/*******************************************************************
 * Wait/Wakeup Functions
 *******************************************************************/

static void tape_wake_up_remove(tape_ccw_req_t* treq){
	tape_remove_ccw_req(treq->tape_dev,treq);
	tape_free_ccw_req(treq);
}
 
static void tape_wake_up(tape_ccw_req_t* treq){
        treq->wakeup = NULL;
        wake_up(&treq->wq);
}
 
static void tape_wake_up_interruptible(tape_ccw_req_t* treq){
        treq->wakeup = NULL;
        wake_up_interruptible(&treq->wq);
}
 
#ifdef CONFIG_S390_TAPE_BLOCK
static void tape_schedule_tapeblock(tape_ccw_req_t* treq){
        treq->wakeup = NULL;
        tapeblock_schedule_exec_io((tape_dev_t*)(treq->tape_dev));;
}
#endif
 
static void tape_wait_event(tape_ccw_req_t* treq){
        wait_event (treq->wq,(treq->wakeup == NULL));
}
 
static void tape_wait_event_interruptible(tape_ccw_req_t* treq){
        wait_event_interruptible(treq->wq,(treq->wakeup == NULL));
	if (signal_pending (current)) {
		treq->rc = tape_halt_io(treq->tape_dev);
		if(treq->rc == -ERESTARTSYS)
		        PRINT_INFO("IO stopped on irq %d\n",treq->tape_dev->devinfo.irq); /* FIXME: only put into dbf */
                else if(treq->rc == 0)
		        PRINT_INFO("could not stop IO,irq was faster on irq %d\n",treq->tape_dev->devinfo.irq); /* FIXME: only put into dbf */
                else
			PRINT_WARN("IO error while stopping IO on irq %d\n",treq->tape_dev->devinfo.irq);
	}
}

static void tape_wait_event_interruptible_nohaltio(tape_ccw_req_t* treq){
	wait_event_interruptible(treq->wq,(treq->wakeup == NULL));
}

/*******************************************************************
 * DYNAMIC ATTACH/DETACH Functions
 *******************************************************************/

static inline void
tape_init_devregs(void)
{
        long lockflags;
	write_lock_irqsave(&tape_dev_lock,lockflags);
	memset(tape_devreg,0,sizeof(devreg_t*) * TAPE_MAX_DEVREGS);
	write_unlock_irqrestore(&tape_dev_lock,lockflags);
}

/*
 * Alloc a devreg for a devno
 */

static inline devreg_t *
tape_create_devno_devreg (int devno)
{
        devreg_t *devreg = kmalloc (sizeof (devreg_t), GFP_KERNEL);
        if (devreg != NULL) {
                memset (devreg, 0, sizeof (devreg_t));
                devreg->ci.devno = devno;
                devreg->flag = DEVREG_TYPE_DEVNO;
                devreg->oper_func = tape_oper_handler;
        }
        return devreg;
}

/*
 * Alloc a devreg for a cu-type 
 */
 
static inline devreg_t *
tape_create_cu_devreg (int cu_type)
{
        devreg_t *devreg = kmalloc (sizeof (devreg_t), GFP_KERNEL);
        if (devreg != NULL) {
                memset (devreg, 0, sizeof (devreg_t));
                devreg->ci.hc.ctype = cu_type;
                devreg->flag = DEVREG_MATCH_CU_TYPE | DEVREG_TYPE_DEVCHARS;
                devreg->oper_func = tape_oper_handler;
        }
        return devreg;
}

/*
 * Create devregs for device numbers from "from" to "to"
 */
 
static inline void
tape_create_devregs_range(int from, int to)
{
	int i;
	long lockflags;
 
	write_lock_irqsave(&tape_dev_lock,lockflags);
	for (i=from;i<=to;i++) {
		// register for attch/detach of a devno
		if(tape_devregct >= TAPE_MAX_DEVREGS){
			PRINT_WARN ("Could not create devregs for devno range %04x - %04x.\n",i,to);
			PRINT_WARN ("These devices cannot be used. Use autoprobe\n");
			PRINT_WARN ("or specify device ranges more precisely!\n");
			break;
		}
		tape_devreg[tape_devregct]=tape_create_devno_devreg(i);
		if (tape_devreg[tape_devregct]!=NULL) {
                        s390_device_register (tape_devreg[tape_devregct++]);
		} else {
                	PRINT_WARN ("Could not create devreg for devno %04x, dyn. attach for this devno deactivated.\n",i);
		}
	}
	write_unlock_irqrestore(&tape_dev_lock,lockflags);
        return;
}

/*
 * Create a devreg for a discipline
 */

static inline void
tape_create_devreg_for_disc(tape_discipline_t *disc)
{
	int devreg_nr;
	long lockflags;
 
	write_lock_irqsave(&tape_dev_lock,lockflags);
	for(devreg_nr = 0; devreg_nr < TAPE_MAX_DEVREGS; devreg_nr++){
		if(tape_devreg[devreg_nr] == NULL)
			break;
	}
	if(devreg_nr == TAPE_MAX_DEVREGS){
		PRINT_WARN ("Could not create devreg for discipline (%x), dyn. attach for this discipline deactivated.\n",disc->cu_type);
		goto out_unlock;
	}
	tape_devreg[devreg_nr] = tape_create_cu_devreg(disc->cu_type);
	if (tape_devreg[devreg_nr] != NULL) {
		s390_device_register(tape_devreg[devreg_nr]);
	} else {
		PRINT_WARN("Could not alloc devreg: Out of memory\n");
		PRINT_WARN("Dynamic attach/detach will not work!\n");
	}
out_unlock:
	write_unlock_irqrestore(&tape_dev_lock,lockflags);
	return;
}

/*
 * Free all devregs
 */ 

static inline void
tape_delete_all_devregs(void)
{
	int i;
	long lockflags;

	write_lock_irqsave(&tape_dev_lock,lockflags);

	for(i = 0; i < TAPE_MAX_DEVREGS; i++){
		if(tape_devreg[i]){
			s390_device_unregister(tape_devreg[i]);
			kfree(tape_devreg[i]);
		}
	}
	memset(tape_devreg,0,sizeof(devreg_t*) * TAPE_MAX_DEVREGS);
	write_unlock_irqrestore(&tape_dev_lock,lockflags);
}

/*
 * Free Devregs for a discipline
 */

static inline void
tape_delete_devreg_for_disc(tape_discipline_t* disc)
{
	int i;
	long lockflags;
 
	write_lock_irqsave(&tape_dev_lock,lockflags);
 
	for(i = 0; i < TAPE_MAX_DEVREGS; i++){
		if(tape_devreg[i]){
			if(tape_devreg[i]->ci.hc.ctype == disc->cu_type){
				s390_device_unregister(tape_devreg[i]);
				kfree(tape_devreg[i]);
				tape_devreg[i] = NULL;
			}
		}
	}
 
	write_unlock_irqrestore(&tape_dev_lock,lockflags);
}

/*******************************************************************
 * Module/Kernel Parameter Handling
 *******************************************************************/

#ifndef MODULE
static char tape_parm_string[1024] __initdata = { 0, };

/*
 * Get Kernel parameters (str) seperated by ","
 * and store them into the tape[] array
 */

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

/*
 * This function is called for each "tape=" Kernel parameter
 * at Kernel initialization. We need tape_parm_setup because of
 * 2.2 compatibility (At least I assume this :-))
 */
 
void __init
tape_parm_setup (char *str, int *ints)
{
	int len = strlen (tape_parm_string);
	if (len != 0) {
		strcat (tape_parm_string, ",");
	}
	strcat (tape_parm_string, str);
}

int __init
tape_parm_call_setup (char *str)
{
	int dummy;
	tape_parm_setup (str, &dummy);
	return 1;
}

__setup("tape=", tape_parm_call_setup);
#endif   /* not defined MODULE */


/*
 * Convert string to int
 */

static inline int
tape_parm_strtoul (char *str, char **stra)
{
	char *temp = str;
	int val;
	if (*temp == '0') {
		temp++;         /* strip leading zero */
		if (*temp == 'x')
			temp++; /* strip leading x */
	}
	val = simple_strtoul (temp, &temp, 16); /* interpret anything as hex */
	*stra = temp;
	return val;
}

/*
 * Parse Kernel/Module Parameters and create devregs for dynamic attach/detach
 */ 

static inline void
tape_parm_parse (char **str)
{
	char *temp;
	int from, to;
 
	if (*str==NULL) {
		/* no params present -> leave */
		return;
	}
	while (*str) {
		temp = *str;
		from = 0;
		to = 0;
		from = tape_parm_strtoul (temp, &temp);
		to = from;
		if (*temp == '-') {
			temp++;
			to = tape_parm_strtoul (temp, &temp);
		}
		tape_create_devregs_range(from,to);
		str++;
	}
}

/*******************************************************************
 * Tape device (td) functions for create, free, enq, deq, enable,
 * disable, get and put
 *******************************************************************/

/*
 * Enable Device
 */

static inline int 
tape_enable_device(tape_dev_t* td)
{
#ifdef CONFIG_DEVFS_FS
	tape_frontend_t* frontend;
#endif
	int rc = 0;

	if(td->discipline->setup_device(td) != 0){
		rc = -ENOMEM;
		goto out;
	}

	/* Register IRQ */

#ifdef TAPE_CIO_PRIVATE_DATA
	#ifdef CONFIG_S390_TAPE_DYNAMIC
		rc = s390_request_irq_special (td->devinfo.irq, tape_do_irq,
			tape_noper_handler,0, TAPE_MAGIC, &(td->devstat));
	#else
		rc = s390_request_irq (td->devinfo.irq, tape_do_irq, 0, TAPE_MAGIC, &(td->devstat));
	#endif
#else
	#ifdef CONFIG_S390_TAPE_DYNAMIC
		rc = s390_request_irq_special (td->devinfo.irq, tape_do_irq,
			tape_noper_handler,0, (char*)td, &(td->devstat));
	#else
		rc = s390_request_irq (td->devinfo.irq, tape_do_irq, 0, (char*)td, &(td->devstat));
	#endif
#endif /* TAPE_CIO_PRIVATE_DATA */
	if (rc){
		PRINT_WARN ("Cannot register irq %d, rc=%d\n", td->devinfo.irq, rc);
		td->discipline->cleanup_device(td);
		goto out;
	}
	/* Create devfs entries */
#ifdef CONFIG_DEVFS_FS
	if (tape_mkdevfsroot(td)==NULL){
		PRINT_WARN ("Cannot create a devfs directory for device %04x\n",td->devinfo.devno);
		goto out_undo;
	}
		
	for (frontend=tape_first_front;frontend!=NULL;frontend=frontend->next){
		if(frontend->mkdevfstree(td) == NULL){
			goto out_undo;
		}
	}
#endif
#ifdef TAPE_CIO_PRIVATE_DATA
	s390_set_private_data(td->devinfo.irq,td);
#endif
out:
	return rc;
out_undo:
        tape_disable_device(td);
        return -ENOMEM;        
}

/*
 * Disable Device
 */

static inline void
tape_disable_device(tape_dev_t* td)
{
#ifdef CONFIG_DEVFS_FS
	tape_frontend_t* frontend;
#endif
	td->discipline->cleanup_device(td);
#ifdef TAPE_CIO_PRIVATE_DATA 
	s390_set_private_data(td->devinfo.irq,NULL);
#else
	ioinfo[td->devinfo.irq]->irq_desc.name = NULL;
#endif

	free_irq (td->devinfo.irq, &(td->devstat)); 
#ifdef CONFIG_DEVFS_FS
        for (frontend=tape_first_front;frontend!=NULL;frontend=frontend->next)
                frontend->rmdevfstree(td);
        tape_rmdevfsroot(td);
#endif
	tape_state_set(td,TS_NOT_OPER);
}

/*
 * Append Tape device (td) to our tape info list
 * Must be called with hold tape_dev_lock
 */
 
static inline void
tape_enq_device(tape_dev_t* td)
{
	tape_dev_t *temptd = NULL;
 
	if (tape_first_dev == NULL) {
		tape_first_dev = td;
	} else {
		temptd = tape_first_dev;
		while (temptd->next != NULL)
			temptd = temptd->next;
		temptd->next = td;
	}
}
 
/*
 * Remove Tape device (td) from our tape info list
 * Must be called with hold tape_dev_lock
 */
 
static inline void
tape_deq_device(tape_dev_t* td)
{
	tape_dev_t* lasttd;
 
	if (td==tape_first_dev) {
		tape_first_dev=td->next;
	} else {
		lasttd=tape_first_dev;
		while (lasttd->next!=td) lasttd=lasttd->next;
		lasttd->next=td->next;
	}
}


/*
 * Get Free minor number
 * Must be called with held tape_dev_lock
 */

static inline int
tape_get_new_minor(int devno)
{
	int i,tape_num = -1;
	tape_dev_t* newtape;

	if(!tape_autoprobe){
		/* we have static device ranges, so fingure out the */
		/* tape_num of the attached tape                    */
		for (i=0;i<tape_devregct;i++){
			if (tape_devreg[i]->ci.devno==devno) {
				tape_num=TAPE_MINORS_PER_DEV*i;
				goto out;
			}
		}
	} else {
		/* we are running in autoprobe mode, find a free */
		/* tape_num */
		i = 0;
		newtape=tape_first_dev;
		while (newtape!=NULL) {
			if (newtape->first_minor==i) {
				/* tape num in use. try next one */
				i+=TAPE_MINORS_PER_DEV;
				newtape=tape_first_dev;
			} else {
				/* tape num not used by newtape. look at next */
				/* tape info */
				newtape=newtape->next;
			}
		}
		if(i>255) /* No more minor available */
			tape_num = -1;
		else
			tape_num = i;
	}
out:
	return tape_num;
}


/*
 * Create device: Alloc, enable and enq device
 */

static inline tape_dev_t*
tape_create_device(int irq, int devno, tape_discipline_t* disc)
{
	int rc = 0;
	int tape_num;
	tape_dev_t *td;
	long lockflags;

	write_lock_irqsave(&tape_dev_lock,lockflags);

	td = kmalloc (sizeof (tape_dev_t), GFP_ATOMIC);
	if (td == NULL) {
		tape_sprintf_exception (tape_dbf_area,2,"ti:no mem \n");
		PRINT_INFO ("tape: can't allocate memory for "
			"tape info structure\n");
		goto error;
	}
	memset(td,0,sizeof(tape_dev_t));
        tape_num = tape_get_new_minor(devno);
        if(tape_num == -1){
                PRINT_WARN("tape: could not get minor for tape %x\n",devno);
                goto error;
        }
	rc = get_dev_info_by_irq (irq, &(td->devinfo));
	if (rc == -ENODEV) {    /* end of device list */
		goto error;
	}
	td->discipline = disc;
	atomic_set(&(td->use_count),1);
	td->first_minor = tape_num;
	td->medium_state = MS_UNKNOWN;
	td->next = NULL;
	td->discdata = td->treq = NULL;
	tape_state_set (td, TS_INIT);
	td->discdata=NULL;
	td->last_op = TO_NOTHING;

	if(td->discipline->setup_device(td) != 0)
		goto error;
	if(tape_enable_device(td) !=0)	
		goto error;
	tape_enq_device(td);
	PRINT_INFO ("using devno %04x with discipline %04x on irq %d as tape device %d\n",devno,disc->cu_type,irq,tape_num/2);
	write_unlock_irqrestore(&tape_dev_lock,lockflags);
	return td;
error:
	tape_sprintf_event (tape_dbf_area,3,"tsetup err: %x\n",rc);
	if(td)
		kfree (td);
	write_unlock_irqrestore(&tape_dev_lock,lockflags);
	return NULL;
}

/*
 * Free Device storage
 */

static void
tape_free_device(tape_dev_t* td)
{
        if (TAPE_BUSY(td)) 
	        BUG(); /* one should _not_ free the device when a request is pending */
	tape_sprintf_event (tape_dbf_area,6,"free irq: %x\n",td->devinfo.irq);
        kfree(td);
}

/*
 * Decrement use count of tape structure
 * if use count == 0 tape structure is freed
 */
 
inline void
tape_put_device(tape_dev_t* td)
{
	if (td==NULL)
		BUG();
	if(atomic_dec_and_test(&(td->use_count)))
		tape_free_device(td);
}

/*
 * Find the tape_dev_t structure associated with member
 * and increase use count:
 *
 * member:    TAPE_MEMB_IRQ, TAPE_MEMB_MINOR
 */

tape_dev_t *
__tape_get_device_by_member(unsigned long value, int member)
{
	tape_dev_t *td = NULL;
	long lockflags;
 
	read_lock_irqsave(&tape_dev_lock,lockflags);
	td = tape_first_dev;
	while(td != NULL) {
		switch(member){
			case TAPE_MEMB_IRQ:
				if(td->devinfo.irq == value)
					goto out;
				break;
			case TAPE_MEMB_MINOR:
				if((value >= td->first_minor)  && 
				    (value < (td->first_minor + TAPE_MINORS_PER_DEV)) )
					goto out;
				break;
			case TAPE_MEMB_QUEUE:
				if(((unsigned long)(&td->blk_data.request_queue)) == value)
					goto out;
				break;
			default:
				BUG();
		}
		td = td->next;
	}
out:
	if(td) // found!
		atomic_inc(&(td->use_count));
	read_unlock_irqrestore(&tape_dev_lock,lockflags);
	return td;
}

/*
 * Scan all irqs an create tape devices for all matching cu types
 */

static inline void
tape_create_devs_for_disc(tape_discipline_t* disc)
{
	int irq,i;
	tape_dev_t* td = NULL;
	s390_dev_info_t dinfo;

	for (irq = get_irq_first(); irq!=-ENODEV; irq=get_irq_next(irq)) {
		if(get_dev_info_by_irq (irq, &dinfo) == -ENODEV)
			continue;
		if(disc->cu_type != dinfo.sid_data.cu_type)
			/* Wrong type - try next one */
			continue;

		tape_sprintf_event (tape_dbf_area,3,"det irq:  %x\n",irq);
		tape_sprintf_event (tape_dbf_area,3,"cu     :  %x\n",disc->cu_type);


		if(!tape_autoprobe) {
		        for( i=0; i<tape_devregct; i++ ) {
		                if(tape_devreg[i]->ci.devno == dinfo.devno) {
			                td = tape_create_device(irq, dinfo.devno, disc);
					if(!td) {
					        PRINT_WARN( "Could not initialize tape 0x%x\n",dinfo.devno);
						continue;
					}
					if(disc->init_device)
					        disc->init_device(td); /* XXX */
					tape_state_set (td, TS_UNUSED);
					break;
				}
			}
		}
		else {
		        td = tape_create_device(irq,dinfo.devno,disc);
		  
			if(!td){
			        PRINT_WARN( "Could not initialize tape 0x%x\n",dinfo.devno);
			        continue;
			}
			if(disc->init_device)
			        disc->init_device(td); /* XXX */
			tape_state_set (td, TS_UNUSED); 
		}
	}
}

/*
 * Go through our tape info list and disable, deq and free all devices with 
 * matching cu type
 */

static inline void
tape_delete_devs_for_disc(tape_discipline_t* disc)
{
	tape_dev_t *td;
	long lockflags;

	write_lock_irqsave(&tape_dev_lock,lockflags);
	td = tape_first_dev;
	while (td !=NULL){
		if(td->discipline == disc){
			tape_deq_device(td);
			write_unlock_irqrestore(&tape_dev_lock,lockflags);
			tape_disable_device(td);
			tape_put_device(td);
			write_lock_irqsave(&tape_dev_lock,lockflags);
			td = tape_first_dev;
		} else {
			td=td->next;
		}
	}
	write_unlock_irqrestore(&tape_dev_lock,lockflags);
}
		
/*******************************************************************
 * TAPE Request and IO Functions
 *******************************************************************/

/*
 * Allocate a new tape ccw request
 */

inline tape_ccw_req_t *
tape_alloc_ccw_req (int cplength, int datasize,int idal_buf_size, tape_op_t operation)
{
	tape_ccw_req_t* treq;
	int kmalloc_flags;
	if(in_interrupt())
		kmalloc_flags = GFP_ATOMIC;
	else
		kmalloc_flags = GFP_KERNEL;

	treq = (tape_ccw_req_t*) kmalloc(sizeof(tape_ccw_req_t),kmalloc_flags);
	if(!treq)
		goto error;
	memset(treq,0,sizeof(tape_ccw_req_t));
	treq->kernbuf_size = datasize;
	treq->userbuf_size = datasize;
	treq->op           = operation;
	treq->cplength     = cplength;
	init_waitqueue_head (&treq->wq); 

	// alloc small kernel buffer

	if(datasize > PAGE_SIZE)
		BUG();
	if(datasize > 0){
		// the kernbuf must be below 2GB --> GFP_DMA
		treq->kernbuf = kmalloc(datasize,kmalloc_flags | GFP_DMA); 
		if(!treq->kernbuf)
			goto error;
		memset(treq->kernbuf, 0, datasize);
	}

	// alloc idal kernel buffer

	if(idal_buf_size > 0){
		treq->idal_buf = idalbuf_alloc(idal_buf_size);
		if(!treq->idal_buf)
			goto error;
	}
		
	if(cplength < 0)
		BUG();

	// the channel program must be below 2GB --> GFP_DMA
	treq->cpaddr = kmalloc(cplength * sizeof(ccw1_t), kmalloc_flags | GFP_DMA);
	if(!treq->cpaddr)
		goto error;
	memset(treq->cpaddr, 0, cplength*sizeof(ccw1_t));
	return treq;
error:
	tape_sprintf_exception (tape_dbf_area,1,"cqra nomem\n");
	if(treq)
		tape_free_ccw_req(treq);
	return NULL;
}

/*
 * Free tape ccw request
 */

void
tape_free_ccw_req (tape_ccw_req_t * treq)
{
	if(!treq)
                BUG();
	if(treq->cpaddr)
		kfree(treq->cpaddr);
	if(treq->kernbuf)
		kfree(treq->kernbuf);
	if(treq->idal_buf)
		idalbuf_free(treq->idal_buf);
	kfree(treq);
}

/*
 * Add request to request queue (At the moment queue length = 1)
 */

static inline int
tape_add_ccw_req(tape_dev_t* td,tape_ccw_req_t* treq)
{
	if(td->treq)
		return -1;
	td->treq = treq;
	treq->tape_dev = td;
	return 0;
}

/*
 * Remove request from request queue (At the moment queue length = 1)
 */

int
tape_remove_ccw_req(tape_dev_t* td,tape_ccw_req_t* treq)
{
	if(treq != td->treq)
		BUG();
	td->last_op = treq->op;
        td->treq=NULL;
	treq->tape_dev=NULL;
        return 0;
}

/*
 * Get the active ccw request
 */

tape_ccw_req_t*
tape_get_active_ccw_req(tape_dev_t* td)
{
	return td->treq;
}

/*
 * Stop the active ccw request
 */

static inline int
tape_halt_io(tape_dev_t* td)
{
	int retries = 0;
	int irq = td->devinfo.irq;
	int rc = 0;
	long lockflags;
	tape_ccw_req_t* treq;

	s390irq_spin_lock_irqsave (td->devinfo.irq, lockflags);
	treq = tape_get_active_ccw_req(td);
	/* check if interrupt has already been processed */
	if(!treq)
		goto out;
	if(treq->wakeup == NULL)
		goto out;
	while (retries < 5){
		if ( retries < 2 )
			rc = halt_IO(irq, (long)treq, treq->options);
		else
			rc = clear_IO(irq, (long)treq, treq->options);
		switch (rc) {
		case 0:         /* termination successful */
			rc = -ERESTARTSYS;
			goto out;
		case -ENODEV:
		        PRINT_INFO ("device gone, retry\n"); /* FIXME: s390dbf only */
			break;
		case -EIO:
			PRINT_INFO ("I/O error, retry\n"); /* FIXME: s390dbf only */
			break;
		case -EBUSY:
			PRINT_INFO ( "device busy, retry later\n"); /* FIXME: s390dbf only */
			break;
		default:
			PRINT_ERR ( "line %d unknown RC=%d, please report"
			   	    " to linux390@de.ibm.com\n", __LINE__, rc);
			BUG ();
		}
		retries ++;
	}
out:
	s390irq_spin_unlock_irqrestore(td->devinfo.irq, lockflags);
	return rc;
}


/*
 * The tape IO function:
 * tape_do_io MUST be called with locked td lock!
 */
 
static inline int
__tape_do_io(tape_dev_t * td,tape_ccw_req_t *treq,tape_wait_t type,int io)
{
	int rc = 0;
 
        if ((td==NULL) || (treq==NULL)) 
                BUG();

	if(tape_state_get(td) == TS_NOT_OPER){
		rc = -ENODEV;
		goto out;
	}
	if(TAPE_BUSY(td)){
		tape_sprintf_event (tape_dbf_area,1,"tape: IRQ - Tape busy\n");	
		rc = -EBUSY;
		goto out;
	}
	tape_add_ccw_req(td,treq);
	switch(type){
		case TAPE_REMOVE_REQ_ON_WAKEUP:
			treq->wakeup = tape_wake_up_remove;
			break;
		case TAPE_NO_WAIT: 
			// needed for retry
			break; 
		case TAPE_WAIT:
			treq->wakeup = tape_wake_up;
			treq->wait   = tape_wait_event;
			break;
		case TAPE_WAIT_INTERRUPTIBLE:
			treq->wakeup = tape_wake_up_interruptible;
			treq->wait   = tape_wait_event_interruptible;
			break;
		case TAPE_WAIT_INTERRUPTIBLE_NOHALTIO:
			// neded for dummy load 
			treq->wakeup = tape_wake_up_interruptible;
			treq->wait   = tape_wait_event_interruptible_nohaltio;
			break;
#ifdef CONFIG_S390_TAPE_BLOCK
		case TAPE_SCHED_BLOCK:
			treq->wakeup = tape_schedule_tapeblock;
			treq->wait   = NULL;
			break;
#endif
		default:
                        BUG();
	}
 
	if(io)
		rc = do_IO(td->devinfo.irq,treq->cpaddr,
		(unsigned long)treq, 0x00, treq->options);
 
	if(rc) {
		tape_sprintf_event (tape_dbf_area,1,"tape: DOIO failed with er = %i\n",rc);
		treq->wakeup = NULL;
		treq->wait   = NULL; /*XXX ??*/
		tape_remove_ccw_req(td,treq);
	}
out:
	return rc;
}

/*
 * Do the io request and wait for it
 */

int
tape_do_io_and_wait(tape_dev_t * td,tape_ccw_req_t *treq,tape_wait_t type)
{
	int rc = 0;
	long lockflags;
        if (td==NULL) 
                BUG();
	s390irq_spin_lock_irqsave (td->devinfo.irq, lockflags);
	rc = __tape_do_io(td,treq,type,TAPE_DO_IO);
	s390irq_spin_unlock_irqrestore (td->devinfo.irq, lockflags);
	if(rc == 0)
		tape_wait(treq);
	return rc;
}

/*
 * Needed for MTLOAD: Just create a wait request without doing io
 */

int
tape_do_wait_req(tape_dev_t * td,tape_ccw_req_t *treq,tape_wait_t type)
{
	int rc = 0;
	long lockflags;
        if (td==NULL)
                BUG();
	s390irq_spin_lock_irqsave (td->devinfo.irq, lockflags);
	rc = __tape_do_io(td,treq,type,TAPE_NO_IO);
	s390irq_spin_unlock_irqrestore (td->devinfo.irq, lockflags);
	tape_wait(treq);
	return rc;
}

/*
 * Just do the io
 */

int
tape_do_io(tape_dev_t * td,tape_ccw_req_t *treq,tape_wait_t type)
{
	int rc = 0;
	long lockflags;
	s390irq_spin_lock_irqsave (td->devinfo.irq, lockflags);
	rc = __tape_do_io(td,treq,type,TAPE_DO_IO);
	s390irq_spin_unlock_irqrestore (td->devinfo.irq, lockflags);
	return rc;
}

/*
 * Just do the io, but we are already locked here (in irq)
 */

int
tape_do_io_irq(tape_dev_t * td,tape_ccw_req_t *treq,tape_wait_t type)
{
        return __tape_do_io(td,treq,type,TAPE_DO_IO);
}

/*
 * Wait for an io request
 */

static void
tape_wait(tape_ccw_req_t* treq)
{
	tape_dev_t* td;
	if(treq==NULL)
                BUG();
	td = treq->tape_dev;
        if (td==NULL)
                BUG();       
	if(treq->wait==NULL)
                BUG();
        treq->wait(treq);
	tape_remove_ccw_req(td,treq);
}

/*
 * Tape interrupt routine, called from Ingo's I/O layer
 */

static void
tape_do_irq (int irq, void *int_parm, struct pt_regs *regs)
{
	tape_dev_t *td;
#ifdef TAPE_CIO_PRIVATE_DATA
	td = (tape_dev_t*)s390_get_private_data(irq);
#else
	td = (tape_dev_t*)ioinfo[irq]->irq_desc.name;
#endif
	if(!td) {
		PRINT_ERR ("tape: could not get device structure for irq %d in interrupt\n",irq);
		goto out;
	}
	if(td->devstat.dstat != 0x0c){
		tape_sprintf_event (tape_dbf_area,3,"-- Tape Interrupthandler --\n");
		tape_dump_sense_dbf(td);
	}
	if (tape_state_get(td) == TS_NOT_OPER) {
		tape_sprintf_event (tape_dbf_area,6,"tape:device is not operational\n");
		goto out;
	}
	td->discipline->irq(td);
out:
	return;
}

/*
 * Oper Handler is called from Ingo's I/O layer when a new tape device is 
 * attached. We create a new devinfo for the new device, enable and enq it.
 */

static int 
tape_oper_handler ( int irq, struct _devreg *dreg) {
	tape_dev_t* td=tape_first_dev;
	int rc=0;
	s390_dev_info_t dinfo;
	tape_discipline_t* disc;
	long lockflags;

	td = tape_get_device_by_irq(irq);
	if (td!=NULL) {
		// irq is (still) used by tape. tell ingo to try again later
		PRINT_ERR ("tape:Oper handler for irq %d called, but irq is still (internally) used.\n",irq);
		rc = -EAGAIN;
		goto out;
	}

	rc = get_dev_info_by_irq (irq, &dinfo);
	if (rc == -ENODEV) {
                PRINT_ERR ("tape: Cannot get dev info for irq %d in the oper handler.\n",irq);
                rc = -EAGAIN;
                goto out;
	}

	read_lock_irqsave(&tape_discipline_lock,lockflags);

	disc = tape_first_disc;
	while ((disc != NULL) && (disc->cu_type != dinfo.sid_data.cu_type))
		disc = (tape_discipline_t *) (disc->next);

	if (disc == NULL){
		PRINT_WARN ("tape: No matching discipline for cu_type %x found in the oper handler, ignoring device %04x.\n",dinfo.sid_data.cu_type,dinfo.devno);
		rc = -ENODEV;
		goto out_unlock;
	}

	/* Allocate tape structure  */
	td = tape_create_device(irq,dinfo.devno,disc);

	if (td == NULL) {
		PRINT_WARN( "Could not initialize tape 0x%x\n",dinfo.devno);
		rc = -ENOBUFS;
		goto out_unlock;
	} else {
		if(disc->init_device)
			disc->init_device(td);
	}
	tape_state_set (td, TS_UNUSED); 
out_unlock:
        read_unlock_irqrestore(&tape_discipline_lock,lockflags);
out:
	return rc;
}

/*
 * Not Oper Handler is called from Ingo's IO layer, when a tape device
 * is detached. We deq, disable and free the tape info for this device
 */

static void
tape_noper_handler ( int irq, int status ) {
	tape_dev_t *td;
	long lockflags;
	tape_ccw_req_t *treq;

	td = tape_get_device_by_irq(irq);
	if (td==NULL) {
                PRINT_ERR("tape: not operational handler called for irq %x, but tape does not hold this irq.\n",irq);
                goto error;
        }

	write_lock_irqsave(&tape_dev_lock,lockflags);
	tape_deq_device(td);
	write_unlock_irqrestore(&tape_dev_lock,lockflags);

	tape_disable_device(td);

	s390irq_spin_lock_irqsave (td->devinfo.irq, lockflags);
	treq = tape_get_active_ccw_req(td);
	if (treq) {
		// device is in use!
                treq->rc= -ENODEV;
		if(treq->wakeup)
			treq->wakeup (treq);
		PRINT_WARN ("Tape #%d is detached while it was busy.\n",td->first_minor/TAPE_MINORS_PER_DEV);
	} else {
		// device is unused!
		PRINT_WARN ("Tape #%d is detached now.\n",td->first_minor/TAPE_MINORS_PER_DEV);
	}

	s390irq_spin_unlock_irqrestore (td->devinfo.irq, lockflags);	

	/* decrement use count twice ! */	
	tape_put_device(td);
	tape_put_device(td);
error:
	return;
}

/*
 * Write sense data to dbf
 */

void tape_dump_sense_dbf(tape_dev_t* td)
{
	devstat_t *stat = &td->devstat;
	const char* op;
	if(TAPE_BUSY(td))
		op = tape_op_verbose[tape_get_active_ccw_req(td)->op];
	else
		op = "---";
	tape_sprintf_event (tape_dbf_area,3,"DSTAT : %02x   CSTAT: %02x\n",stat->dstat,stat->cstat);
	tape_sprintf_event (tape_dbf_area,3,"DEVICE: %04x OP   : %s\n",td->devinfo.devno,op);
	tape_sprintf_event (tape_dbf_area,3,"%08x %08x\n",*((unsigned int*)&stat->ii.sense.data[0]),*((unsigned int*)&stat->ii.sense.data[4]));
	tape_sprintf_event (tape_dbf_area,3,"%08x %08x\n",*((unsigned int*)&stat->ii.sense.data[8]),*((unsigned int*)&stat->ii.sense.data[12]));
	tape_sprintf_event (tape_dbf_area,3,"%08x %08x\n",*((unsigned int*)&stat->ii.sense.data[16]),*((unsigned int*)&stat->ii.sense.data[20]));
	tape_sprintf_event (tape_dbf_area,3,"%08x %08x\n",*((unsigned int*)&stat->ii.sense.data[24]),*((unsigned int*)&stat->ii.sense.data[28]));
}

/*
 * Write sense data to console/dbf
 */

void
tape_dump_sense (tape_dev_t* td)
{
	devstat_t *stat = &td->devstat;
#if 1 /* XXX */
	PRINT_INFO ("-------------------------------------------------\n");
	PRINT_INFO ("DSTAT : %02x  CSTAT: %02x  CPA: %04x\n",stat->dstat,stat->cstat,stat->cpa);
	PRINT_INFO ("DEVICE: %04x\n",td->devinfo.devno);
	if(TAPE_BUSY(td))
		PRINT_INFO("OP    : %s\n",tape_op_verbose[tape_get_active_ccw_req(td)->op]);
		
	PRINT_INFO ("Sense data: %02X%02X%02X%02X %02X%02X%02X%02X "
		    " %02X%02X%02X%02X %02X%02X%02X%02X \n",
		    stat->ii.sense.data[0], stat->ii.sense.data[1],
		    stat->ii.sense.data[2], stat->ii.sense.data[3],
		    stat->ii.sense.data[4], stat->ii.sense.data[5],
		    stat->ii.sense.data[6], stat->ii.sense.data[7],
		    stat->ii.sense.data[8], stat->ii.sense.data[9],
		    stat->ii.sense.data[10], stat->ii.sense.data[11],
		    stat->ii.sense.data[12], stat->ii.sense.data[13],
		    stat->ii.sense.data[14], stat->ii.sense.data[15]);
	PRINT_INFO ("Sense data: %02X%02X%02X%02X %02X%02X%02X%02X "
		    " %02X%02X%02X%02X %02X%02X%02X%02X \n",
		    stat->ii.sense.data[16], stat->ii.sense.data[17],
		    stat->ii.sense.data[18], stat->ii.sense.data[19],
		    stat->ii.sense.data[20], stat->ii.sense.data[21],
		    stat->ii.sense.data[22], stat->ii.sense.data[23],
		    stat->ii.sense.data[24], stat->ii.sense.data[25],
		    stat->ii.sense.data[26], stat->ii.sense.data[27],
		    stat->ii.sense.data[28], stat->ii.sense.data[29],
		    stat->ii.sense.data[30], stat->ii.sense.data[31]);
	PRINT_INFO ("--------------------------------------------------\n");
#endif
}

/*******************************************************************
 * TAPE Discipline functions
 *******************************************************************/

/*
 * Register backend discipline
 */

int
tape_register_discipline(tape_discipline_t *disc)
{
        tape_discipline_t *disc_ptr;
	long lockflags;

        disc->next = NULL;

	write_lock_irqsave(&tape_discipline_lock,lockflags);

        if (tape_first_disc == NULL) {
            tape_first_disc = disc;
        } else {
                for(disc_ptr = tape_first_disc; disc_ptr->next!=NULL; disc_ptr=(tape_discipline_t*)disc_ptr->next);
                disc_ptr->next = disc;
        }

	tape_create_devs_for_disc(disc);
	if(tape_autoprobe)
		tape_create_devreg_for_disc(disc);

	write_unlock_irqrestore(&tape_discipline_lock,lockflags);
        return 0;
}

/*
 * Unregister backend discipline
 */

void 
tape_unregister_discipline(tape_discipline_t* disc)
{
	tape_discipline_t* lastdisc;
	long lockflags;

	write_lock_irqsave(&tape_discipline_lock,lockflags);

	tape_delete_devs_for_disc(disc);
	if(tape_autoprobe)
		tape_delete_devreg_for_disc(disc);

	if (disc==tape_first_disc) {
		tape_first_disc=disc->next;
        } else {
		lastdisc=tape_first_disc;
		while (lastdisc->next!=disc) lastdisc=lastdisc->next;
		lastdisc->next=disc->next;
	}
	
	disc->shutdown();

	write_unlock_irqrestore(&tape_discipline_lock,lockflags);
}

/*
 * Cleanup all registered disciplines
 */

static inline void
tape_cleanup_disciplines (void) 
{
        long lockflags;
        tape_discipline_t* disc;
	/* Unregister all backend disciplines - this also deletes the devices */

	write_lock_irqsave(&tape_discipline_lock,lockflags);
        disc=tape_first_disc;
        while (disc != NULL) {
		write_unlock_irqrestore(&tape_discipline_lock,lockflags);
		tape_unregister_discipline(disc);
		kfree(disc);
		write_lock_irqsave(&tape_discipline_lock,lockflags);
		disc=tape_first_disc; 
        }
	write_unlock_irqrestore(&tape_discipline_lock,lockflags);
}


/*******************************************************************
 * TAPE Init Functions
 *******************************************************************/

/*
 *      tape_print_banner will be called on initialisation and print a nice banner
 */

static inline void 
tape_print_banner (void) 
{
        char *opt_char,*opt_block;

        /* print banner */        
        PRINT_WARN ("IBM zSeries Tape Device Driver (v%d.%02d).\n",TAPE_VERSION_MAJOR,TAPE_VERSION_MINOR);
        PRINT_WARN ("(C) IBM Deutschland Entwicklung GmbH, 2000 - 2001\n");
        opt_char=opt_block="not present";
#ifdef CONFIG_S390_TAPE_CHAR
        opt_char="built in";
#endif
#ifdef CONFIG_S390_TAPE_BLOCK
        opt_block="built in";
#endif
        /* print feature info */
        PRINT_WARN ("character device frontend   : %s\n",opt_char);
        PRINT_WARN ("block device frontend       : %s\n",opt_block);
}

/*
 *      tape_init will register the driver for each tape.
 */

int
tape_init (void)
{
        static int initialized=0;

        if (initialized) // Only init the devices once
            return 0;
        initialized=1;

	tape_init_devregs();
#ifdef TAPE_DEBUG
        tape_dbf_area = debug_register ( "tape", 2, 2, 3*sizeof(long));
        debug_register_view(tape_dbf_area,&debug_sprintf_view);
        tape_sprintf_event (tape_dbf_area,3,"begin init\n");
#endif /* TAPE_DEBUG */
	tape_print_banner();
#ifndef MODULE
        tape_split_parm_string(tape_parm_string);
#endif
#ifdef CONFIG_DEVFS_FS
        tape_devfs_root_entry=devfs_mk_dir (NULL, "tape", NULL);
#endif /* CONFIG_DEVFS_FS */

        tape_sprintf_event (tape_dbf_area,3,"dev detect\n");
	/* Allocate the tape structures */
        if (*tape!=NULL) { /* if parameters are present */
		PRINT_INFO ("Using ranges supplied in parameters, disabling autoprobe mode.\n");
		tape_parm_parse (tape);
		tape_autoprobe = 0;
	} else {
		PRINT_INFO ("No parameters supplied, enabling autoprobe mode for all supported devices.\n");
		tape_autoprobe = 1;
	}
#ifdef CONFIG_PROC_FS
	tape_proc_init();
#endif /* CONFIG_PROC_FS */
#ifdef  CONFIG_S390_TAPE_3480 
	tape_register_discipline(tape3480_init());
#else
#ifndef CONFIG_S390_TAPE
	tape_request_module("tape_3480_mod");
#endif
#endif /* CONFIG_S390_TAPE_3480 */

#ifdef CONFIG_S390_TAPE_3490
        tape_register_discipline(tape3490_init());
#else
#ifndef CONFIG_S390_TAPE
        tape_request_module("tape_3490_mod");
#endif
#endif /* CONFIG_S390_TAPE_3490 */

#ifdef CONFIG_S390_TAPE_3590
        tape_register_discipline(tape3590_init());
#else
#ifndef CONFIG_S390_TAPE
        tape_request_module("tape_3590_mod");
#endif
#endif /* CONFIG_S390_TAPE_3590 */
	return 0;
}

/*******************************************************************
 * TAPE Module functions
 *******************************************************************/

#ifdef MODULE
MODULE_AUTHOR("(C) 2001 IBM Deutschland Entwicklung GmbH by Carsten Otte and Michael Holzheu (cotte@de.ibm.com,holzheu@de.ibm.com)");
MODULE_DESCRIPTION("Linux on zSeries channel attached tape device driver");
MODULE_PARM (tape, "1-" __MODULE_STRING (256) "s");

/*
 * The famous init_module
 */

int
init_module (void)
{
#ifdef CONFIG_S390_TAPE_CHAR
	tapechar_init ();
#endif
#ifdef CONFIG_S390_TAPE_BLOCK
	tapeblock_init ();
#endif
        return 0;
}

/*
 * Cleanup the frontends
 */
static inline void
tape_cleanup_frontends(void) 
{
        tape_frontend_t* frontend, *tempfe;

	/* Now get rid of the frontends */
#ifdef CONFIG_S390_TAPE_CHAR
	tapechar_uninit();
#endif
#ifdef CONFIG_S390_TAPE_BLOCK
        tapeblock_uninit();
#endif
        frontend=tape_first_front;
	while (frontend != NULL) {
		tempfe = frontend;
		frontend = frontend->next;
		kfree (tempfe);
	}
}


/*
 * Cleanup module
 */

void
cleanup_module (void)
{
        tape_sprintf_event (tape_dbf_area,6,"cleaup mod");
	tape_cleanup_disciplines();
#ifdef CONFIG_DEVFS_FS
	devfs_unregister (tape_devfs_root_entry); /* devfs checks for NULL */
#endif CONFIG_DEVFS_FS
#ifdef CONFIG_PROC_FS
	tape_proc_cleanup();
#endif 
	tape_cleanup_frontends();
	/* Deallocate devregs in case of not autoprobe */
	if(!tape_autoprobe)
		tape_delete_all_devregs();
#ifdef TAPE_DEBUG
        debug_unregister (tape_dbf_area);
#endif /* TAPE_DEBUG */
}
#endif				/* MODULE */


/*******************************************************************
 * TAPE Tapestate functions
 *******************************************************************/

inline void
tape_state_set (tape_dev_t * td, tape_state_t newstate)
{
    if (td->tape_state == TS_NOT_OPER) {
        tape_sprintf_event(tape_dbf_area,3,"ts_set err: not oper\n");
    } else {
        tape_sprintf_event(tape_dbf_area,4,"ts. dev:  %x\n",td->first_minor);
        tape_sprintf_event(tape_dbf_area,4,"old ts:   %s\n",(((tape_state_get (td) < TO_SIZE) && (tape_state_get (td) >=0 )) ?  tape_state_verbose[tape_state_get (td)] : "UNKNOWN TS"));
        tape_sprintf_event(tape_dbf_area,4,"%s\n", 
			(((tape_state_get (td) < TO_SIZE) &&
			(tape_state_get (td) >=0 )) ?
			tape_state_verbose[tape_state_get (td)] :
			"UNKNOWN TS"));
        tape_sprintf_event (tape_dbf_area,4,"new ts:   \n");
        tape_sprintf_event (tape_dbf_area,4,"%s\n",(((newstate < TO_SIZE) &&
                                              (newstate >= 0)) ?
                                             tape_state_verbose[newstate] :
                                             "UNKNOWN TS"));
	td->tape_state = newstate;
    }
}

inline tape_state_t
tape_state_get (tape_dev_t * td)
{
	return (td->tape_state);
}

inline void
tape_med_state_set(tape_dev_t * td, tape_medium_state_t newstate)
{
	if(td->medium_state == newstate)
		goto out;
	switch(newstate){
		case MS_UNLOADED:
			PRINT_INFO("(%x): Tape is unloaded\n",td->devstat.devno);
			break;
		case MS_LOADED:
			PRINT_INFO("(%x): Tape has been mounted\n",td->devstat.devno);
			break;
		default:
			// print nothing
			break;
	}
	td->medium_state = newstate;
out:
	return;
}
	

/*******************************************************************
 * TAPE Exported Functions 
 *******************************************************************/

EXPORT_SYMBOL(tape_register_discipline);
EXPORT_SYMBOL(tape_unregister_discipline);
EXPORT_SYMBOL(tape_dbf_area);
EXPORT_SYMBOL(tape_alloc_ccw_req);
EXPORT_SYMBOL(tape_free_ccw_req); 
EXPORT_SYMBOL(tape_state_set);
EXPORT_SYMBOL(tape_state_get);
EXPORT_SYMBOL(tape_med_state_set);
EXPORT_SYMBOL(tape_state_verbose);
EXPORT_SYMBOL(tape_op_verbose);
EXPORT_SYMBOL(tape_dump_sense);
EXPORT_SYMBOL(tape_dump_sense_dbf);
EXPORT_SYMBOL(tape_do_io);
EXPORT_SYMBOL(tape_do_io_irq);
EXPORT_SYMBOL(tape_do_io_and_wait);
EXPORT_SYMBOL(tape_remove_ccw_req);
EXPORT_SYMBOL(tape_get_active_ccw_req);
