patch-2.4.2 linux/drivers/s390/misc/chandev.c

Next file: linux/drivers/s390/net/Makefile
Previous file: linux/drivers/s390/misc/Makefile
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.4.1/linux/drivers/s390/misc/chandev.c linux/drivers/s390/misc/chandev.c
@@ -1,17 +1,15 @@
 /*
  *  drivers/s390/misc/chandev.c
- *    common channel device layer
  *
- *  S390 version
  *    Copyright (C) 2000 IBM Deutschland Entwicklung GmbH, IBM Corporation
  *    Author(s): Denis Joseph Barrow (djbarrow@de.ibm.com,barrow_dj@yahoo.com)
+ * 
+ *  Generic channel device initialisation support. 
  */
-
 #define __KERNEL_SYSCALLS__
 #include <linux/config.h>
 #include <linux/types.h>
 #include <linux/ctype.h>
-#include <asm/queue.h>
 #include <asm/uaccess.h>
 #include <linux/slab.h>
 #include <asm/irq.h>
@@ -20,260 +18,1847 @@
 #include <asm/chandev.h>
 #include <linux/proc_fs.h>
 #include <linux/vmalloc.h>
+#include <asm/s390dyn.h>
+#include <asm/queue.h>
+
+typedef struct chandev_model_info chandev_model_info;
+struct chandev_model_info
+{
+	struct chandev_model_info *next;
+	chandev_type chan_type;
+	s32 cu_type;      /* control unit type  -1 = don't care */
+	s16 cu_model;     /* control unit model -1 = don't care */
+	s32 dev_type;     /* device type -1 = don't care */
+	s16 dev_model;    /* device model -1 = don't care */
+	u8  max_port_no;
+	int      auto_msck_recovery;
+	devreg_t drinfo;
+};
+
+typedef struct chandev chandev;
+struct chandev
+{
+	struct chandev *next;
+	chandev_model_info *model_info;
+	u16 cu_type;      /* control unit type */
+	u8  cu_model;     /* control unit model */
+	u16 dev_type;     /* device type */
+	u8  dev_model;    /* device model */
+	u16 devno;
+	unsigned int irq;
+	int owned;
+};
+
+typedef struct chandev_noauto_range chandev_noauto_range;
+struct chandev_noauto_range
+{
+	struct chandev_noauto_range *next;
+	u16     lo_devno;
+	u16     hi_devno;
+};
+
+typedef struct chandev_force chandev_force;
+struct chandev_force
+{
+	struct chandev_force *next;
+	chandev_type chan_type;
+	s32     devif_num; /* -1 don't care e.g. tr0 implies 0 */
+        u16     read_devno;
+	u16     write_devno;
+        s16     port_protocol_no; /* where available e.g. lcs,-1 don't care */
+	u8      checksum_received_ip_pkts;
+	u8      use_hw_stats; /* where available e.g. lcs */
+};
+
+typedef struct chandev_probelist chandev_probelist;
+struct chandev_probelist
+{
+	struct chandev_probelist *next;
+	chandev_probefunc       probefunc;
+	chandev_shutdownfunc    shutdownfunc;
+	chandev_reoperfunc      reoperfunc;
+	chandev_type            chan_type;
+	int                     devices_found;
+};
+
+
+
+#define default_msck_bits ((1<<(not_oper-1))|(1<<(no_path-1))|(1<<(revalidate-1))|(1<<(gone-1)))
+
+
+static char *msck_status_strs[]=
+{
+	"good",
+	"not_operational",
+	"no_path",
+	"revalidate",
+	"device_gone"
+};
+
+typedef struct chandev_msck_range chandev_msck_range;
+struct chandev_msck_range
+{
+	struct chandev_msck_range *next;
+	u16     lo_devno;
+	u16     hi_devno;
+	int      auto_msck_recovery;
+};
+
+static chandev_msck_range *chandev_msck_range_head=NULL;
+
+typedef struct chandev_irqinfo chandev_irqinfo;
+struct chandev_irqinfo
+{
+	chandev_irqinfo      *next;
+	chandev_msck_status  msck_status;
+	u16                  devno;
+	unsigned int         irq;
+	void                 (*handler)(int, void *, struct pt_regs *);
+	unsigned long        irqflags;
+	void                 *dev_id;
+	char                 devname[0];
+};
+
+
+chandev_irqinfo *chandev_irqinfo_head=NULL;
+
+typedef struct chandev_parms chandev_parms;
+struct chandev_parms
+{
+	chandev_parms      *next;
+	chandev_type       chan_type;
+	char               parmstr[0];
+};
+
+chandev_parms *chandev_parms_head=NULL;
+
+
+typedef struct chandev_activelist chandev_activelist;
+struct chandev_activelist
+{
+	struct chandev_activelist *next;
+	chandev_irqinfo         *read_irqinfo;
+	chandev_irqinfo         *write_irqinfo;
+	u16                     cu_type;      /* control unit type */
+	u8                      cu_model;     /* control unit model */
+	u16                     dev_type;     /* device type */
+	u8                      dev_model;    /* device model */
+	chandev_probefunc       probefunc;
+	chandev_shutdownfunc    shutdownfunc;
+	chandev_reoperfunc      reoperfunc;
+	chandev_unregfunc       unreg_dev;
+	chandev_type            chan_type;
+	u8                      port_no;
+	chandev_category        category;
+	int                     saved_busy_flag;
+	
+
+	void                    *dev_ptr;
+	char                    devname[0];
+};
+
+
 
 static chandev_model_info *chandev_models_head=NULL;
-static chandev *chandev_head=NULL;
+/* The only reason chandev_head is a queue is so that net devices */
+/* will be by default named in the order of their irqs */
+static qheader chandev_head={NULL,NULL};
 static chandev_noauto_range *chandev_noauto_head=NULL;
 static chandev_force *chandev_force_head=NULL;
 static chandev_probelist *chandev_probelist_head=NULL;
+static chandev_activelist *chandev_activelist_head=NULL;
+#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
 static int use_devno_names=FALSE;
+#endif
 static int chandev_conf_read=FALSE;
+static int chandev_initialised=FALSE;
 
-static void *chandev_alloc_listmember(list **listhead,size_t size)
+
+static unsigned long chandev_last_machine_check;
+
+
+static struct tq_struct chandev_msck_task_tq;
+static atomic_t chandev_msck_thread_lock;
+static atomic_t chandev_new_msck;
+static unsigned long chandev_last_startmsck_list_update;
+
+typedef struct chandev_startmsck_list chandev_startmsck_list;
+struct chandev_startmsck_list
+{
+	chandev_startmsck_list    *next;
+	chandev_msck_status       pre_recovery_action_status;
+	chandev_msck_status       post_recovery_action_status;
+	char                      devname[0];
+};
+
+
+static chandev_startmsck_list *startlist_head=NULL;
+static chandev_startmsck_list *mscklist_head=NULL;
+
+
+
+
+static void chandev_read_conf(void);
+
+#if LINUX_VERSION_CODE >=KERNEL_VERSION(2,3,0)
+typedef struct net_device  net_device;
+#else
+typedef struct device  net_device;
+
+static inline void init_waitqueue_head(wait_queue_head_t *q)
 {
-	void *newmember=kmalloc(size, GFP_KERNEL);
-	if(newmember)
-		add_to_list(listhead,newmember);
-	return(newmember);
+	*q=NULL;
 }
+#endif
 
-void chandev_free_listmember(list **listhead,list *member)
+#if LINUX_VERSION_CODE<KERNEL_VERSION(2,3,45)
+static __inline__ void netif_stop_queue(net_device *dev)
 {
-	if(remove_from_list(listhead,member))
-		kfree(member);
-	else
-		printk(KERN_CRIT"chandev_free_listmember detected nonexistant"
-		       "listmember listhead=%p member %p\n",listhead,member);
+	dev->tbusy=1;
 }
 
-void chandev_free_all(list **listhead)
+static __inline__ void netif_start_queue(net_device *dev)
 {
-	while(*listhead)
-		chandev_free_listmember(listhead,*listhead);
+	dev->tbusy=0;
 }
+#endif
+
+
+
+#define CHANDEV_INVALID_LOCK_OWNER            -1
+static long                 chandev_lock_owner;
+static int                  chandev_lock_cnt; 
+static spinlock_t           chandev_spinlock;
+
+typedef struct chandev_not_oper_struct chandev_not_oper_struct;
+
+struct  chandev_not_oper_struct
+{
+	chandev_not_oper_struct *next;
+	int irq;
+	int status;
+};
+
+
+/* May as well try to keep machine checks in the order they happen so
+ * we use qheader for chandev_not_oper_head instead of list.
+ */
+static qheader chandev_not_oper_head={NULL,NULL};
+static spinlock_t           chandev_not_oper_spinlock;
+static char           exec_script[]="/bin/chandev";
+
+#define chandev_interrupt_check() \
+if(in_interrupt())                \
+     printk(KERN_WARNING __FUNCTION__ " called under interrupt this shouldn't happen\n")
+
+
+#define for_each(variable,head) \
+for((variable)=(head);(variable)!=NULL;(variable)=(variable)->next)
+
+
+#define CHANDEV_USE_KERNEL_THREADS (LINUX_VERSION_CODE<KERNEL_VERSION(2,4,0))
+
+#if CHANDEV_USE_KERNEL_THREADS
+static void chandev_start_msck_thread(void *unused);
+#if LINUX_VERSION_CODE<KERNEL_VERSION(2,3,0)
+#define chandev_daemonize(name,mask,use_init_fs) s390_daemonize(name,mask)
+#else
+#define chandev_daemonize(args...) s390_daemonize(args)
+#endif
+typedef int chandev_task_retval;
+#define chandev_task_return(val) return(val)       
+#else
+#define chandev_daemonize(noargs...)
+#define chandev_task_retval void
+#define chandev_task_return(val) return
+#endif
+
 
-void chandev_add_model(chandev_type chan_type,u16 cu_type,u8 cu_model,u8 max_port_no)
+static void chandev_lock(void)
 {
-	chandev_model_info *newmodel;
+	chandev_interrupt_check();
+	eieio();
+	if(chandev_lock_owner!=(long)current)
+	{
+		spin_lock(&chandev_spinlock);
+		chandev_lock_cnt=1;
+		chandev_lock_owner=(long)current;
+	}
+	else
+		chandev_lock_cnt++;
+	if(chandev_lock_cnt<0||chandev_lock_cnt>100)
+			panic("odd lock_cnt in lcs %d lcs_chan_lock",chandev_lock_cnt);
+}
 
-	if((newmodel=chandev_alloc_listmember(
-		(list **)&chandev_models_head,sizeof(chandev_model_info))))
+
+static void chandev_unlock(void)
+{
+	if(chandev_lock_owner!=(long)current)
+		panic("chandev_unlock: current=%lx"
+		      " chandev_lock_owner=%lx chandev_lock_cnt=%d\n",
+		      (long)current,
+		      chandev_lock_owner,
+		      chandev_lock_cnt);
+	if(--chandev_lock_cnt==0)
 	{
-		newmodel->chan_type=chan_type;
-		newmodel->cu_type=cu_type;
-		newmodel->cu_model=cu_model;
-		newmodel->max_port_no=max_port_no;
+		chandev_lock_owner=CHANDEV_INVALID_LOCK_OWNER;
+		spin_unlock(&chandev_spinlock);
 	}
+	if(chandev_lock_cnt<0)
+		panic("odd lock_cnt in lcs %d lcs_chan_unlock",chandev_lock_cnt);
 }
 
-
-void chandev_remove(chandev *member)
+int chandev_full_unlock(void)
 {
-	chandev_free_listmember((list **)&chandev_head,(list *)member);
+	int ret_lock_cnt=chandev_lock_cnt;
+	chandev_lock_cnt=0;
+	chandev_lock_owner=CHANDEV_INVALID_LOCK_OWNER;
+	spin_unlock(&chandev_spinlock);
+	return(ret_lock_cnt);
 }
 
 
-void chandev_remove_all(void)
+
+
+void *chandev_alloc(size_t size)
 {
-	chandev_free_all((list **)&chandev_head);
+	void *mem=kmalloc(size,GFP_ATOMIC);
+	if(mem)
+		memset(mem,0,size);
+	return(mem);
 }
 
-void chandev_remove_model(chandev_model_info *model)
+static void chandev_add_to_list(list **listhead,void *member)
 {
-	chandev *curr_chandev;
-	for(curr_chandev=chandev_head;curr_chandev!=NULL;
-		    curr_chandev=curr_chandev->next)
-		if(curr_chandev->model_info==model)
-			chandev_remove(curr_chandev);
-	chandev_free_listmember((list **)&chandev_models_head,(list *)model);
+	chandev_lock();
+	add_to_list(listhead,member);
+	chandev_unlock();
 }
 
-void chandev_remove_all_models(void)
+static void chandev_queuemember(qheader *qhead,void *member)
 {
-	while(chandev_models_head)
-		chandev_remove_model(chandev_models_head);
+	chandev_lock();
+	enqueue_tail(qhead,(queue *)member);
+	chandev_unlock();
 }
 
-void chandev_del_model(u16 cu_type,u8 cu_model)
+static int chandev_remove_from_list(list **listhead,list *member)
 {
-	chandev_model_info *curr_model;
-	for(curr_model=chandev_models_head;curr_model!=NULL;
-		    curr_model=curr_model->next)
-		if(curr_model->cu_type==cu_type&&curr_model->cu_model==cu_model)
-			chandev_remove_model(curr_model);			
+	int retval;
+
+	chandev_lock();
+	retval=remove_from_list(listhead,member);
+	chandev_unlock();
+	return(retval);
 }
 
-static void chandev_init_default_models(void)
+static int chandev_remove_from_queue(qheader *qhead,queue *member)
 {
-	/* P390/Planter 3172 emulation assume maximum 16 to be safe. */
-	chandev_add_model(lcs,0x3088,0x1,15);	
-
-	/* 3172/2216 Paralell the 2216 allows 16 ports per card the */
-	/* the original 3172 only allows 4 we will assume the max of 16 */
-	chandev_add_model(lcs|ctc,0x3088,0x8,15);
+	int retval;
+	
+	chandev_lock();
+	retval=remove_from_queue(qhead,member);
+	chandev_unlock();
+	return(retval);
+}
 
-	/* 3172/2216 Escon serial the 2216 allows 16 ports per card the */
-	/* the original 3172 only allows 4 we will assume the max of 16 */
-	chandev_add_model(lcs|escon,0x3088,0x1F,15);
 
-	/* Only 2 ports allowed on OSA2 cards model 0x60 */
-	chandev_add_model(lcs,0x3088,0x60,1);
 
-	/* Osa-D we currently aren't too emotionally involved with this */
-	chandev_add_model(osad,0x3088,0x62,0);
+void chandev_free_listmember(list **listhead,list *member)
+{
+	if(member)
+	{
+		if(chandev_remove_from_list(listhead,member))
+			kfree(member);
+		else
+			printk(KERN_CRIT"chandev_free_listmember detected nonexistant"
+			       "listmember listhead=%p member %p\n",listhead,member);
+	}
 }
 
-void chandev_add(dev_info_t  *newdevinfo,chandev_model_info *newmodelinfo)
+void chandev_free_queuemember(qheader *qhead,queue *member)
 {
-	chandev *new_chandev;
-
-	if((new_chandev=chandev_alloc_listmember(
-		(list **)&chandev_head,sizeof(chandev))))
+	if(member)
 	{
-		new_chandev->model_info=newmodelinfo;
-		new_chandev->devno=newdevinfo->devno;
-		new_chandev->irq=newdevinfo->irq;
+		if(chandev_remove_from_queue(qhead,member))
+			kfree(member);
+		else
+			printk(KERN_CRIT"chandev_free_listmember detected nonexistant"
+			       "listmember qhead=%p member %p\n",qhead,member);
 	}
 }
 
 
-void chandev_collect_devices(void)
+
+void chandev_free_all_list(list **listhead)
 {
-	int curr_irq,loopcnt=0,err;
-	dev_info_t   curr_devinfo;
-	chandev_model_info *curr_model;
-     
+	list *head;
 
-	for(curr_irq=get_irq_first();curr_irq>=0; curr_irq=get_irq_next(curr_irq))
-	{
-		/* check read chandev
-		 * we had to do the cu_model check also because ctc devices
-		 * have the same cutype & after asking some people
-		 * the model numbers are given out pseudo randomly so
-		 * we can't just take a range of them also the dev_type & models are 0
-		 */
-		loopcnt++;
-		if(loopcnt>0x10000)
-		{
-			printk(KERN_ERR"chandev_collect_devices detected infinite loop bug in get_irq_next\n");
-			break;
-		}
-		if((err=get_dev_info_by_irq(curr_irq,&curr_devinfo)))
+	while((head=remove_listhead(listhead)))
+		kfree(head);
+}
+
+void chandev_free_all_queue(qheader *qhead)
+{
+	while(qhead->head)
+		chandev_free_queuemember(qhead,qhead->head);
+}
+
+
+struct files_struct *chandev_new_files_struct(void)
+{
+        struct files_struct *newf = kmem_cache_alloc(files_cachep, SLAB_KERNEL);
+	if (!newf) 
+		return(NULL);
+	memset(newf,0,sizeof(struct files_struct));
+	atomic_set(&newf->count, 1);
+	newf->file_lock	    = RW_LOCK_UNLOCKED;
+	newf->next_fd	    = 0;
+	newf->max_fds	    = NR_OPEN_DEFAULT;
+	newf->max_fdset	    = __FD_SETSIZE;
+	newf->close_on_exec = &newf->close_on_exec_init;
+	newf->open_fds	    = &newf->open_fds_init;
+	newf->fd	    = &newf->fd_array[0];
+	return(newf);
+}
+/*
+ * Mostly robbed from kmod.c
+ */
+
+static inline void
+use_init_fs_context(void)
+{
+	struct fs_struct *our_fs, *init_fs;
+	struct dentry *root, *pwd;
+	struct vfsmount *rootmnt, *pwdmnt;
+
+	/*
+	 * Make modprobe's fs context be a copy of init's.
+	 *
+	 * We cannot use the user's fs context, because it
+	 * may have a different root than init.
+	 * Since init was created with CLONE_FS, we can grab
+	 * its fs context from "init_task".
+	 *
+	 * The fs context has to be a copy. If it is shared
+	 * with init, then any chdir() call in modprobe will
+	 * also affect init and the other threads sharing
+	 * init_task's fs context.
+	 *
+	 * We created the exec_modprobe thread without CLONE_FS,
+	 * so we can update the fields in our fs context freely.
+	 */
+
+	init_fs = init_task.fs;
+	read_lock(&init_fs->lock);
+	rootmnt = mntget(init_fs->rootmnt);
+	root = dget(init_fs->root);
+	pwdmnt = mntget(init_fs->pwdmnt);
+	pwd = dget(init_fs->pwd);
+	read_unlock(&init_fs->lock);
+
+	/* FIXME - unsafe ->fs access */
+	our_fs = current->fs;
+	our_fs->umask = init_fs->umask;
+	set_fs_root(our_fs, rootmnt, root);
+	set_fs_pwd(our_fs, pwdmnt, pwd);
+	write_lock(&our_fs->lock);
+	if (our_fs->altroot) {
+		struct vfsmount *mnt = our_fs->altrootmnt;
+		struct dentry *dentry = our_fs->altroot;
+		our_fs->altrootmnt = NULL;
+		our_fs->altroot = NULL;
+		write_unlock(&our_fs->lock);
+		dput(dentry);
+		mntput(mnt);
+	} else 
+		write_unlock(&our_fs->lock);
+	dput(root);
+	mntput(rootmnt);
+	dput(pwd);
+	mntput(pwdmnt);
+}
+
+
+static int exec_usermodehelper(char *program_path, char *argv[], char *envp[])
+{
+	int err;
+	wait_queue_head_t    wait;
+
+
+	current->session = 1;
+	current->pgrp = 1;
+
+	/* We copy this off init & can't go until this is set up */
+	init_waitqueue_head(&wait);
+	while(init_task.fs->root==NULL)
+	{
+		sleep_on_timeout(&wait,HZ);
+	}
+	use_init_fs_context();
+
+	/* Prevent parent user process from sending signals to child.
+	   Otherwise, if the modprobe program does not exist, it might
+	   be possible to get a user defined signal handler to execute
+	   as the super user right after the execve fails if you time
+	   the signal just right.
+	*/
+	spin_lock_irq(&current->sigmask_lock);
+	flush_signals(current);
+	flush_signal_handlers(current);
+	spin_unlock_irq(&current->sigmask_lock);
+	 /* current->files sometimes was null this means we need */
+	 /* to build our own */
+	 exit_files(current);
+	 if((current->files=chandev_new_files_struct())==NULL)
+	 {
+		 printk("chandev_new_files_struct allocation failed\n");
+		 return(0);
+	 }
+	 
+	/* Drop the "current user" thing */
+	{
+		struct user_struct *user = current->user;
+		current->user = INIT_USER;
+		atomic_inc(&INIT_USER->__count);
+		atomic_inc(&INIT_USER->processes);
+		atomic_dec(&user->processes);
+		free_uid(user);
+	}
+
+	/* Take all effective privileges.. */
+	current->uid = current->euid = current->fsuid = 0;
+	cap_set_full(current->cap_effective);
+	/* Allow execve & open args to be in kernel space. */
+	set_fs(KERNEL_DS);
+	/* We need stdin out & err for scripts */
+	if (open("/dev/console", O_RDWR, 0)< 0)
+		printk("chandev exec_usermode_helper unable to open an initial console.\n");
+	(void) dup(0);
+	(void) dup(0);
+
+	
+        /* Go, go, go... */
+        err=execve(program_path, argv, envp);
+	return err;
+}
+
+static int exec_start_script(void *unused)
+{
+	
+	char **argv,*tempname;
+	int retval=-ENOMEM;
+	int loopcnt,argc;
+	size_t allocsize;
+	chandev_startmsck_list *member;
+	wait_queue_head_t      wait;
+	static char * envp[] = { "HOME=/", "TERM=linux", "PATH=/sbin:/usr/sbin:/bin:/usr/bin", NULL };
+	
+	init_waitqueue_head(&wait);
+	s390_daemonize("chandev_script",0,FALSE);
+	for(loopcnt=0;loopcnt<10&&(jiffies-chandev_last_startmsck_list_update)<HZ;loopcnt++)
+	{
+		sleep_on_timeout(&wait,HZ);
+	}
+	chandev_lock();
+	argc=1;
+	if(startlist_head)
+		argc++;
+	for_each(member,startlist_head)
+		argc++;
+	if(mscklist_head)
+		argc+=3;
+	for_each(member,mscklist_head)
+		argc++;
+
+	allocsize=(argc+1)*sizeof(char *);
+        /* Warning possible stack overflow */
+	/* We can't kmalloc the parameters here as execve will */
+	/* not return if successful */
+	argv=alloca(allocsize);
+	if(argv)
+	{
+		memset(argv,0,allocsize);
+		argc=0;
+		argv[argc++]=exec_script;
+		if(startlist_head)
+			argv[argc++]="start";
+		for_each(member,startlist_head)
 		{
-			printk("chandev_collect_devices get_dev_info_by_irq reported err=%X on irq %d\n"
-			       "should not happen\n",err,curr_irq);
-			continue;
+			tempname=alloca(strlen(member->devname)+1);
+			if(tempname)
+			{
+				strcpy(tempname,member->devname);
+				argv[argc++]=tempname;
+			}
+			else
+				goto Fail;
 		}
-		for(curr_model=chandev_models_head;curr_model!=NULL;
-		    curr_model=curr_model->next)
+		if(mscklist_head)
+			argv[argc++]="machine_check";
+		for_each(member,mscklist_head)
 		{
-			if((curr_model->cu_type==curr_devinfo.sid_data.cu_type)&&
-			   (curr_model->cu_model==curr_devinfo.sid_data.cu_model)
-			   &&((curr_devinfo.status&DEVSTAT_DEVICE_OWNED)==0))
-				chandev_add(&curr_devinfo,curr_model);
+			tempname=alloca(strlen(member->devname)+1);
+			if(tempname)
+			{
+				strcpy(tempname,member->devname);
+				argv[argc++]=tempname;
+			}
+			else
+				goto Fail;
+			argv[loopcnt++]=msck_status_strs[member->pre_recovery_action_status];
+			argv[loopcnt++]=msck_status_strs[member->post_recovery_action_status];
 		}
+		chandev_free_all_list((list **)&startlist_head);
+		chandev_free_all_list((list **)&mscklist_head);
+		chandev_unlock();
+		/* We are basically execve'ing here there normally is no */
+		/* return */
+		retval=exec_usermodehelper(exec_script, argv, envp);
+		goto Fail2;
 	}
+ Fail:
+	
+	chandev_unlock();
+ Fail2:
+	/* We don't really need to report /bin/chandev not existing */
+	if(retval!=-ENOENT)
+	   printk("exec_start_script failed retval=%d\n",retval);
+	return(0);
 }
 
-void chandev_add_force(chandev_type chan_type,s32 devif_num,u16 read_devno,
-u16 write_devno,s16 port_no,u8 do_ip_checksumming,u8 use_hw_stats)
 
+void *chandev_allocstr(const char *str,size_t offset)
 {
-	chandev_force *new_chandev_force;
+	char *member;
 
-	if((new_chandev_force=chandev_alloc_listmember(
-		(list **)&chandev_force_head,sizeof(chandev_force))))
+	if((member=chandev_alloc(offset+strlen(str)+1)))
 	{
-		new_chandev_force->chan_type=chan_type;
-		new_chandev_force->devif_num=devif_num;
-		new_chandev_force->read_devno=read_devno;
-		new_chandev_force->write_devno=write_devno;
-		new_chandev_force->port_no=port_no;
-		new_chandev_force->do_ip_checksumming=do_ip_checksumming;
-		new_chandev_force->use_hw_stats=use_hw_stats;
+		strcpy(&member[offset],str);
 	}
+	return((void *)member);
 }
 
-void chandev_del_force(u16 read_devno)
+
+static int chandev_add_to_startmsck_list(chandev_startmsck_list **listhead,char *devname,
+chandev_msck_status pre_recovery_action_status,chandev_msck_status post_recovery_action_status)
 {
-	chandev_force *curr_force;
-	for(curr_force=chandev_force_head;curr_force!=NULL;
-		    curr_force=curr_force->next)
+	int retval;
+	chandev_startmsck_list *member;
+	int pid;
+	
+	chandev_lock();
+	/* remove operations still outstanding for this device */
+	for_each(member,startlist_head)
+		if(strcmp(member->devname,devname)==0)
+			chandev_remove_from_list((list **)&startlist_head,(list *)member);
+	for_each(member,mscklist_head)
+		if(strcmp(member->devname,devname)==0)
+			chandev_remove_from_list((list **)&mscklist_head,(list *)member);
+	
+
+	if((member=chandev_allocstr(devname,offsetof(chandev_startmsck_list,devname))))
 	{
-		if(curr_force->read_devno==read_devno)
-			chandev_free_listmember((list **)&chandev_force_head,
-						(list *)curr_force);
+		member->pre_recovery_action_status=pre_recovery_action_status;
+		member->post_recovery_action_status=post_recovery_action_status;
+		add_to_list((list **)listhead,(list *)member);
+		chandev_last_startmsck_list_update=jiffies;
+		chandev_unlock();
+		/* We do CLONE_FILES so we can exit_files to get rid of it */
+                /* cheaply & allocate a new one we need current->files &  */
+                /* some tasks have current->files==NULL */
+		pid = kernel_thread(exec_start_script,NULL,CLONE_FILES|SIGCHLD);
+		if(pid<0)
+		{
+			printk("error making kernel thread for exec_start_script\n");
+			retval=pid;
+		}
+		else
+			return(0);
+
+	}
+	else
+	{
+		printk("chandev_add_to_startmscklist memory allocation failed devname=%s\n",devname);
+		retval=-ENOMEM;
 	}
+	chandev_unlock();
+	return(retval);
 }
 
-void chandev_pack_args(char *str)
+
+
+
+
+int chandev_oper_func(int irq,devreg_t *dreg)
 {
-	char *newstr=str;
-	while(*str)
+	chandev_last_machine_check=jiffies;
+	if(atomic_dec_and_test(&chandev_msck_thread_lock))
 	{
-		if(isspace(*str))
-			str++;
-		else
-			*newstr++=*str++;
+#if CHANDEV_USE_KERNEL_THREADS
+		queue_task(&chandev_msck_task_tq,&tq_scheduler);
+#else
+		schedule_task(&chandev_msck_task_tq);
+#endif
 	}
-	*newstr=0;
+	atomic_set(&chandev_new_msck,TRUE);
+	return(0);
 }
 
-typedef enum
-{ 
-	isnull=0,
-	isstr=1,
-	isnum=2,
-	iscomma=4,
-} chandev_strval;
-
-chandev_strval chandev_strcmp(char *teststr,char **str,long *endlong)
+static void chandev_not_oper_handler(int irq,int status )
 {
-	char *cur=*str;
-	chandev_strval  retval=isnull;
+	chandev_not_oper_struct *new_not_oper;
 
-	int len=strlen(teststr);
-	if(strncmp(teststr,*str,len)==0)
+	chandev_last_machine_check=jiffies;
+	if((new_not_oper=kmalloc(sizeof(chandev_not_oper_struct),GFP_ATOMIC)))
 	{
-		*str+=len;
-		retval=isstr;
-		*endlong=simple_strtol(cur,str,0);
-		if(cur!=*str)
-			retval|=isnum;
-		if(**str==',')
-			retval|=iscomma;
+		new_not_oper->irq=irq;
+		new_not_oper->status=status;
+		spin_lock(&chandev_not_oper_spinlock);
+		enqueue_tail(&chandev_not_oper_head,(queue *)new_not_oper);
+		spin_unlock(&chandev_not_oper_spinlock);
+		if(atomic_dec_and_test(&chandev_msck_thread_lock))
+		{
+#if CHANDEV_USE_KERNEL_THREADS
+			queue_task(&chandev_msck_task_tq,&tq_scheduler);
+#else
+			schedule_task(&chandev_msck_task_tq);
+#endif
+		}
 	}
-	return(retval);
+	else
+		printk("chandev_not_oper_handler failed to allocate memory & "
+		       "lost a not operational interrupt %d %x",
+		       irq,status);
 }
 
-static char *argstrs[]=
+chandev_irqinfo *chandev_get_irqinfo_by_irq(int irq)
 {
-	"noauto",
-	"lcs",
-	"ctc",
-	"escon",
-	"del_force",
-	"use_devno_names"
-	"dont_use_devno_names",
-	"add_model"
-	"del_model"
-	"del_all_models"
-};
+	chandev_irqinfo *curr_irqinfo;
+	for_each(curr_irqinfo,chandev_irqinfo_head)
+		if(irq==curr_irqinfo->irq)
+			return(curr_irqinfo);
+	return(NULL);
+}
 
-typedef enum
+chandev *chandev_get_by_irq(int irq)
 {
-	stridx_mult=16,
-	first_stridx=0,
-	noauto_stridx=first_stridx,
-	lcs_stridx,
-	ctc_stridx,
-	escon_stridx,
-	del_force_stridx,
-	use_devno_names_stridx,
-	dont_use_devno_names_stridx,
-	add_model_stridx,
-	del_model_stridx,
-	del_all_models_stridx,
+	chandev *curr_chandev;
+
+	for_each(curr_chandev,(chandev *)chandev_head.head)
+		if(curr_chandev->irq==irq)
+		{
+			return(curr_chandev);
+		}
+	return(NULL);
+}
+
+chandev_activelist *chandev_get_activelist_by_irq(int irq)
+{
+	chandev_activelist *curr_device;
+
+	for_each(curr_device,chandev_activelist_head)
+	{
+			if(curr_device->read_irqinfo->irq==irq||
+			   curr_device->write_irqinfo->irq==irq)
+				return(curr_device);
+	}
+	return(NULL);
+}
+
+
+
+int chandev_request_irq(unsigned int   irq,
+                      void           (*handler)(int, void *, struct pt_regs *),
+                      unsigned long  irqflags,
+                      const char    *devname,
+                      void          *dev_id)
+{
+	chandev_irqinfo *new_irqinfo;
+	chandev_activelist *curr_device;
+	s390_dev_info_t         devinfo;
+	int          retval;
+	
+
+	chandev_lock();
+	if((curr_device=chandev_get_activelist_by_irq(irq)))
+	{
+		printk("chandev_request_irq failed devname=%s irq=%d "
+		       "it already belongs to %s shutdown this device first.\n",
+		       devname,irq,curr_device->devname);
+		chandev_unlock();
+		return(-EPERM);
+	}
+	/* remove any orphan irqinfo left lying around. */
+        if((new_irqinfo=chandev_get_irqinfo_by_irq(irq)))
+		chandev_remove_from_list((list **)chandev_irqinfo_head,
+					 (list *)new_irqinfo);
+	chandev_unlock();
+	if((new_irqinfo=chandev_allocstr(devname,offsetof(chandev_irqinfo,devname))))
+	{
+		
+		if((retval=get_dev_info_by_irq(irq,&devinfo))||
+		   (retval=s390_request_irq_special(irq,handler,
+						chandev_not_oper_handler,
+						irqflags,devname,dev_id)))
+			kfree(new_irqinfo);
+		else
+		{
+			new_irqinfo->irq=irq;
+			new_irqinfo->handler=handler;
+			new_irqinfo->dev_id=dev_id;
+			new_irqinfo->devno=devinfo.devno;
+			chandev_add_to_list((list **)&chandev_irqinfo_head,new_irqinfo);
+		}
+	}
+	else
+	{
+		printk("chandev_request_irq memory allocation failed devname=%s irq=%d\n",devname,irq);
+		retval=-ENOMEM;
+	}
+	return(retval);
+}
+
+
+
+void chandev_sprint_type_model(char *buff,s32 type,s16 model)
+{
+	if(type==-1)
+		strcpy(buff,"    *    ");
+	else
+		sprintf(buff," 0x%04x  ",(int)type);
+	buff+=strlen(buff);
+	if(model==-1)
+		strcpy(buff,"    *   ");
+	else
+		sprintf(buff," 0x%02x  ",(int)model);
+}
+
+void chandev_sprint_devinfo(char *buff,s32 cu_type,s16 cu_model,s32 dev_type,s16 dev_model)
+{
+	chandev_sprint_type_model(buff,cu_type,cu_model);
+	chandev_sprint_type_model(&buff[strlen(buff)],dev_type,dev_model);
+}
+
+void chandev_remove_parms(chandev_type chan_type,int exact_match)
+{
+	chandev_parms      *curr_parms;
+
+	chandev_lock();
+	for_each(curr_parms,chandev_parms_head)
+	{
+		if((chan_type&(curr_parms->chan_type)&&!exact_match)||
+		    (chan_type==(curr_parms->chan_type)&&exact_match))
+		    chandev_free_listmember((list **)&chandev_parms_head,(list *)curr_parms);
+	}
+	chandev_unlock();
+}
+
+void chandev_add_parms(chandev_type chan_type,char *parmstr)
+{
+	chandev_parms      *new_parms;
+	
+	if((new_parms=chandev_allocstr(parmstr,offsetof(chandev_parms,parmstr))))
+	{
+		chandev_remove_parms(chan_type,TRUE);
+		new_parms->chan_type=chan_type;
+		chandev_add_to_list((list **)&chandev_parms_head,(void *)new_parms);
+	}
+	else
+		printk("chandev_add_parmstr memory request failed\n");
+}
+
+
+void chandev_add_model(chandev_type chan_type,s32 cu_type,s16 cu_model,
+		       s32 dev_type,s16 dev_model,u8 max_port_no,int auto_msck_recovery)
+{
+	chandev_model_info *newmodel;
+	int                err;
+	char buff[40];
+
+	if((newmodel=chandev_alloc(sizeof(chandev_model_info))))
+	{
+		devreg_t *drinfo=&newmodel->drinfo;
+		newmodel->chan_type=chan_type;
+		newmodel->cu_type=cu_type;
+		newmodel->cu_model=cu_model;
+		newmodel->dev_type=dev_type;
+		newmodel->dev_model=dev_model;
+		newmodel->max_port_no=max_port_no;
+		newmodel->auto_msck_recovery=auto_msck_recovery;
+		
+		if(cu_type==-1&&dev_type==-1)
+		{
+			chandev_sprint_devinfo(buff,newmodel->cu_type,newmodel->cu_model,
+					       newmodel->dev_type,newmodel->dev_model);
+			printk(KERN_INFO"can't call s390_device_register for this device chan_type/chan_model/dev_type/dev_model %s\n",buff);
+			kfree(newmodel);
+			return;
+		}
+		/* We ignore errors as they are likely to
+		   occur owing to incompatibilities with
+		   Ingos layer 
+		*/
+		drinfo->flag=DEVREG_TYPE_DEVCHARS;
+		if(dev_model==-1)
+			drinfo->flag|=(dev_type==-1 ? DEVREG_NO_DEV_INFO:DEVREG_MATCH_DEV_TYPE);
+		if(cu_model==-1)
+			drinfo->flag|=(cu_type==-1 ? DEVREG_NO_CU_INFO:DEVREG_MATCH_CU_TYPE);
+		else if(dev_model!=-1&&cu_type!=-1)
+			drinfo->flag|=DEVREG_EXACT_MATCH;
+		drinfo->ci.hc.ctype=cu_type;
+		drinfo->ci.hc.cmode=cu_model;
+		drinfo->ci.hc.dtype=dev_type;
+		drinfo->ci.hc.dmode=dev_model;
+		drinfo->oper_func=chandev_oper_func;
+		if((err=s390_device_register(&newmodel->drinfo)))
+		{
+			chandev_sprint_devinfo(buff,newmodel->cu_type,newmodel->cu_model,
+					       newmodel->dev_type,newmodel->dev_model);
+			printk("s390_device_register failed in chandev_add_model"
+			       " this is nothing to worry about chan_type/chan_model/dev_type/dev_model %s\n",buff);
+			drinfo->oper_func=NULL;
+		}
+		chandev_add_to_list((list **)&chandev_models_head,newmodel);
+	}
+}
+
+
+void chandev_remove(chandev *member)
+{
+	chandev_free_queuemember(&chandev_head,(queue *)member);
+}
+
+
+void chandev_remove_all(void)
+{
+	chandev_free_all_queue(&chandev_head);
+}
+
+void chandev_remove_model(chandev_model_info *model)
+{
+	chandev *curr_chandev;
+
+	chandev_lock();
+	for_each(curr_chandev,(chandev *)chandev_head.head)
+		if(curr_chandev->model_info==model)
+			chandev_remove(curr_chandev);
+	if(model->drinfo.oper_func)
+		s390_device_unregister(&model->drinfo);
+	chandev_free_listmember((list **)&chandev_models_head,(list *)model);
+	chandev_unlock();
+}
+
+void chandev_remove_all_models(void)
+{
+	chandev_lock();
+	while(chandev_models_head)
+		chandev_remove_model(chandev_models_head);
+	chandev_unlock();
+}
+
+void chandev_del_model(s32 cu_type,s16 cu_model,s32 dev_type,s16 dev_model)
+{
+	chandev_model_info *curr_model;
+	
+	chandev_lock();
+	for_each(curr_model,chandev_models_head)
+		if((curr_model->cu_type==cu_type||cu_type==-1)&&
+		   (curr_model->cu_model==cu_model||cu_model==-1)&&
+		   (curr_model->dev_type==dev_type||dev_type==-1)&&
+		   (curr_model->dev_model==dev_model||dev_model==-1))
+			chandev_remove_model(curr_model);			
+	chandev_unlock();
+}
+
+static void chandev_init_default_models(void)
+{
+	/* P390/Planter 3172 emulation assume maximum 16 to be safe. */
+	chandev_add_model(lcs,0x3088,0x1,-1,-1,15,default_msck_bits);	
+
+	/* 3172/2216 Paralell the 2216 allows 16 ports per card the */
+	/* the original 3172 only allows 4 we will assume the max of 16 */
+	chandev_add_model(lcs|ctc,0x3088,0x8,-1,-1,15,default_msck_bits);
+
+	/* 3172/2216 Escon serial the 2216 allows 16 ports per card the */
+	/* the original 3172 only allows 4 we will assume the max of 16 */
+	chandev_add_model(lcs|escon,0x3088,0x1F,-1,-1,15,default_msck_bits);
+
+	/* Only 2 ports allowed on OSA2 cards model 0x60 */
+	chandev_add_model(lcs,0x3088,0x60,-1,-1,1,default_msck_bits);
+	/* qeth has relative adapter concept so we give it 16 */
+	chandev_add_model(qeth,0x1731,0x1,0x1732,0x1,15,default_msck_bits);
+	/* Osa-D we currently aren't too emotionally involved with this */
+	chandev_add_model(osad,0x3088,0x62,-1,-1,0,default_msck_bits);
+}
+
+
+void chandev_del_noauto(u16 devno)
+{
+	chandev_noauto_range *curr_noauto;
+	chandev_lock();
+	for_each(curr_noauto,chandev_noauto_head)
+		if(curr_noauto->lo_devno<=devno&&curr_noauto->hi_devno>=devno)
+			chandev_free_listmember((list **)&chandev_noauto_head,(list *)curr_noauto); 
+	chandev_unlock();
+}
+
+void chandev_del_msck(u16 devno)
+{
+	chandev_msck_range *curr_msck_range;
+	chandev_lock();
+	for_each(curr_msck_range,chandev_msck_range_head)
+		if(curr_msck_range->lo_devno<=devno&&curr_msck_range->hi_devno>=devno)
+			chandev_free_listmember((list **)&chandev_msck_range_head,(list *)curr_msck_range); 
+	chandev_unlock();
+}
+
+
+void chandev_add(s390_dev_info_t  *newdevinfo,chandev_model_info *newmodelinfo)
+{
+	chandev *new_chandev=NULL;
+
+	if((new_chandev=chandev_alloc(sizeof(chandev))))
+	{
+		new_chandev->model_info=newmodelinfo;
+		new_chandev->cu_type=newdevinfo->sid_data.cu_type; /* control unit type */
+		new_chandev->cu_model=newdevinfo->sid_data.cu_model; /* control unit model */
+		new_chandev->dev_type=newdevinfo->sid_data.dev_type; /* device type */
+		new_chandev->dev_model=newdevinfo->sid_data.dev_model; /* device model */
+		new_chandev->devno=newdevinfo->devno;
+		new_chandev->irq=newdevinfo->irq;
+		new_chandev->owned=(newdevinfo->status&DEVSTAT_DEVICE_OWNED ? TRUE:FALSE);
+		chandev_queuemember(&chandev_head,new_chandev);
+	}
+}
+
+void chandev_unregister_probe(chandev_probefunc probefunc)
+{
+	chandev_probelist *curr_probe;
+
+	chandev_lock();
+	for_each(curr_probe,chandev_probelist_head)
+		if(curr_probe->probefunc==probefunc)
+			chandev_free_listmember((list **)&chandev_probelist_head,
+						(list *)curr_probe);
+	chandev_unlock();
+}
+
+
+void chandev_reset(void)
+{
+	chandev_lock();
+	chandev_remove_all_models();
+	chandev_free_all_list((list **)&chandev_noauto_head);
+	chandev_free_all_list((list **)&chandev_msck_range_head);
+	chandev_free_all_list((list **)&chandev_force_head);
+	chandev_remove_parms(-1,FALSE);
+#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
+	use_devno_names=FALSE;
+#endif
+	chandev_conf_read=FALSE;
+	chandev_unlock();
+}
+
+
+chandev_model_info *chandev_is_chandev(int irq,s390_dev_info_t *devinfo)
+{
+	chandev_model_info *curr_model=NULL;
+	int err;
+	if((err=get_dev_info_by_irq(irq,devinfo)))
+	{
+		printk("chandev_is_chandev get_dev_info_by_irq reported err=%X on irq %d\n"
+		       "should not happen\n",err,irq);
+			return(NULL);
+	}
+	chandev_lock();
+	for_each(curr_model,chandev_models_head)
+	{
+		if(((curr_model->cu_type==devinfo->sid_data.cu_type)||(curr_model->cu_type==-1))&&
+		   ((curr_model->cu_model==devinfo->sid_data.cu_model)||(curr_model->cu_model==-1))&&
+		   ((curr_model->dev_type==devinfo->sid_data.dev_type)||(curr_model->dev_type==-1))&&
+		   ((curr_model->dev_model==devinfo->sid_data.dev_model)||(curr_model->dev_model==-1)))
+			break;
+	}
+	chandev_unlock();
+	return(curr_model);
+}
+
+void chandev_collect_devices(void)
+{
+	int curr_irq,loopcnt=0;
+	s390_dev_info_t   curr_devinfo;
+	chandev_model_info *curr_model;
+     
+
+	for(curr_irq=get_irq_first();curr_irq>=0; curr_irq=get_irq_next(curr_irq))
+	{
+		/* check read chandev
+		 * we had to do the cu_model check also because ctc devices
+		 * have the same cutype & after asking some people
+		 * the model numbers are given out pseudo randomly so
+		 * we can't just take a range of them also the dev_type & models are 0
+		 */
+		loopcnt++;
+		if(loopcnt>0x10000)
+		{
+			printk(KERN_ERR"chandev_collect_devices detected infinite loop bug in get_irq_next\n");
+			break;
+		}
+		chandev_lock();
+		if((curr_model=chandev_is_chandev(curr_irq,&curr_devinfo)))
+			chandev_add(&curr_devinfo,curr_model);
+		chandev_unlock();
+	}
+}
+
+void chandev_add_force(chandev_type chan_type,s32 devif_num,u16 read_devno,
+u16 write_devno,s16 port_protocol_no,u8 checksum_received_ip_pkts,u8 use_hw_stats)
+
+{
+	chandev_force *new_chandev_force;
+
+	if((new_chandev_force=chandev_alloc(sizeof(chandev_force))))
+	{
+		new_chandev_force->chan_type=chan_type;
+		new_chandev_force->devif_num=devif_num;
+		new_chandev_force->read_devno=read_devno;
+		new_chandev_force->write_devno=write_devno;
+		new_chandev_force->port_protocol_no=port_protocol_no;
+		new_chandev_force->checksum_received_ip_pkts=checksum_received_ip_pkts;
+		new_chandev_force->use_hw_stats=use_hw_stats;
+		chandev_add_to_list((list **)&chandev_force_head,new_chandev_force);
+	}
+}
+
+void chandev_del_force(u16 read_devno)
+{
+	chandev_force *curr_force;
+	
+	chandev_lock();
+	for_each(curr_force,chandev_force_head)
+	{
+		if(curr_force->read_devno==read_devno)
+			chandev_free_listmember((list **)&chandev_force_head,
+						(list *)curr_force);
+	}
+	chandev_unlock();
+}
+
+
+void chandev_shutdown(chandev_activelist *curr_device)
+{
+	chandev_lock();
+
+	if(curr_device->category==network_device)
+	{
+		/* unregister_netdev calls the dev->close so we shouldn't do this */
+		/* this otherwise we crash */
+		if(curr_device->unreg_dev)
+			curr_device->unreg_dev(curr_device->dev_ptr); 
+	}
+	curr_device->shutdownfunc(curr_device->dev_ptr);
+	kfree(curr_device->dev_ptr);
+	chandev_free_listmember((list **)&chandev_irqinfo_head,(list *)curr_device->read_irqinfo);
+	chandev_free_listmember((list **)&chandev_irqinfo_head,(list *)curr_device->write_irqinfo);
+	chandev_free_listmember((list **)&chandev_activelist_head,
+				(list *)curr_device);
+	chandev_unlock();
+}
+
+void chandev_shutdown_all(void)
+{
+	while(chandev_activelist_head)
+		chandev_shutdown(chandev_activelist_head);
+}
+void chandev_shutdown_by_name(char *devname)
+{
+	chandev_activelist *curr_device;
+
+	chandev_lock();
+	for_each(curr_device,chandev_activelist_head)
+		if(strcmp(devname,curr_device->devname)==0)
+		{
+			chandev_shutdown(curr_device);
+			break;
+		}
+	chandev_unlock();
+}
+
+static chandev_activelist *chandev_active(u16 devno)
+{
+	chandev_activelist *curr_device;
+
+	for_each(curr_device,chandev_activelist_head)
+		if(curr_device->read_irqinfo->devno==devno||
+		   curr_device->write_irqinfo->devno==devno)
+		{
+			return(curr_device);
+		}
+	return(NULL);
+}
+
+void chandev_shutdown_by_devno(u16 devno)
+{
+	chandev_activelist *curr_device;
+
+	chandev_lock();
+	curr_device=chandev_active(devno);
+	if(curr_device)
+		chandev_shutdown(curr_device);
+	chandev_unlock();
+}
+
+
+int chandev_pack_args(char *str)
+{
+	char *newstr=str,*next;
+	int strcnt=1;
+
+	while(*str)
+	{
+		next=str+1;
+		/*remove dead spaces */
+		if(isspace(*str)&&isspace(*next))
+		{
+			str++;
+			continue;
+		}
+		if(isspace(*str)||((*str)=='-'))
+		{
+			*str=',';
+			goto pack_dn;
+		}
+		if(((*str)==';')&&(*next))
+		{
+			strcnt++;
+			*str=0;
+		}
+	pack_dn:
+		*newstr++=*str++;
+		
+	}
+	*newstr=0;
+	return(strcnt);
+}
+
+typedef enum
+{ 
+	isnull=0,
+	isstr=1,
+	isnum=2,
+	iscomma=4,
+} chandev_strval;
+
+chandev_strval chandev_strcmp(char *teststr,char **str,long *endlong)
+{
+	char *cur;
+	chandev_strval  retval=isnull;
+
+	int len=strlen(teststr);
+	if(strncmp(teststr,*str,len)==0)
+	{
+		*str+=len;
+		retval=isstr;
+		cur=*str;
+		*endlong=simple_strtol(cur,str,0);
+		if(cur!=*str)
+			retval|=isnum;
+		if(**str==',')
+		{
+			retval|=iscomma;
+			*str+=1;
+		}
+		else if(**str!=0)
+			retval=isnull;
+	}
+	return(retval);
+}
+
+
+int chandev_initdevice(chandev_probeinfo *probeinfo,void *dev_ptr,u8 port_no,char *devname,chandev_category category,chandev_unregfunc unreg_dev)
+{
+	chandev_activelist *newdevice;
+
+	chandev_interrupt_check();
+	if(probeinfo->newdevice!=NULL)
+	{
+		printk("probeinfo->newdevice!=NULL in chandev_initdevice for %s",devname);
+		return(-EPERM);
+	}
+	
+
+	if((newdevice=chandev_allocstr(devname,offsetof(chandev_activelist,devname))))
+	{
+		probeinfo->newdevice=newdevice;
+		chandev_lock();
+		newdevice->read_irqinfo=chandev_get_irqinfo_by_irq(probeinfo->read_irq);
+		newdevice->write_irqinfo=chandev_get_irqinfo_by_irq(probeinfo->write_irq);
+		chandev_unlock();
+		if(newdevice->read_irqinfo==NULL||newdevice->write_irqinfo==NULL)
+		{
+			printk("chandev_initdevice, it appears that chandev_request_irq was not "
+			       "called for devname=%s read_irq=%d write_irq=%d\n",devname,probeinfo->read_irq,probeinfo->write_irq);
+			kfree(newdevice);
+			return(-EPERM);
+		}
+		newdevice->cu_type=probeinfo->cu_type;
+		newdevice->cu_model=probeinfo->cu_model;
+		newdevice->dev_type=probeinfo->dev_type;
+		newdevice->dev_model=probeinfo->dev_model;
+		newdevice->chan_type=probeinfo->chan_type;		
+		newdevice->dev_ptr=dev_ptr;
+		newdevice->port_no=port_no;
+		newdevice->category=category;
+		newdevice->unreg_dev=unreg_dev;
+		return(0);
+	}
+	return(-ENOMEM);
+}
+
+#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
+struct net_device *chandev_initnetdevice(chandev_probeinfo *probeinfo,u8 port_no,
+struct net_device *dev, int sizeof_priv, char *basename, 
+struct net_device *(*init_netdevfunc)(struct net_device *dev, int sizeof_priv),
+void (*unreg_netdevfunc)(struct net_device *dev))
+#else
+struct device *chandev_initnetdevice(chandev_probeinfo *probeinfo,u8 port_no,
+struct device *dev, int sizeof_priv, char *basename,
+struct device *(*init_netdevfunc)(struct device *dev, int sizeof_priv),
+void (*unreg_netdevfunc)(struct device *dev))
+#endif
+{
+#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
+	struct net_device *retdevice=NULL;
+	int new_device = 0;
+#else
+	struct device *retdevice=NULL;
+#endif
+
+	if (!init_netdevfunc) 
+	{
+		printk("init_netdevfunc=NULL in chandev_initnetdevice, it should not be valid.\n");
+		return NULL;
+	}
+	if (!unreg_netdevfunc) 
+	{
+		printk("unreg_netdevfunc=NULL in chandev_initnetdevice, it should not be valid.\n");
+		return NULL;
+	}
+
+	chandev_interrupt_check();
+
+#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
+        /* Allocate a device if one is not provided. */
+        if (dev == NULL) 
+	{
+		/* ensure 32-byte alignment of the private area */
+		int alloc_size = sizeof (*dev) + sizeof_priv + 31;
+
+		dev = (struct net_device *) kmalloc (alloc_size, GFP_KERNEL);
+		if (dev == NULL) 
+		{
+			printk(KERN_ERR "chandev_initnetdevice: Unable to allocate device memory.\n");
+			return NULL;
+		}
+
+		memset(dev, 0, alloc_size);
+
+		if (sizeof_priv)
+			dev->priv = (void *) (((long)(dev + 1) + 31) & ~31);
+
+		if (probeinfo->devif_num != -1) 
+			sprintf(dev->name,"%s%d",basename,(int)probeinfo->devif_num);
+		else if (use_devno_names) 
+			sprintf(dev->name,"%s0x%04x",basename,(int)probeinfo->read_devno);
+
+		new_device = 1;
+	}
+#endif
+
+	retdevice=init_netdevfunc(dev,sizeof_priv);
+
+#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
+	/* Register device if necessary */
+	if (retdevice && new_device)
+		register_netdev(retdevice);
+#endif
+
+	if (retdevice) 
+	{
+		if (chandev_initdevice(probeinfo,retdevice,port_no,retdevice->name,
+				      network_device,(chandev_unregfunc)unreg_netdevfunc)) 
+		{
+			unreg_netdevfunc(retdevice);
+			retdevice = NULL;
+		}
+	}
+
+#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
+	/* We allocated it, so we should free it on error */
+	if (!retdevice && new_device) 
+		kfree(dev);
+#endif
+
+	return retdevice;
+}
+
+
+
+
+int chandev_doprobe(chandev_force *force,chandev *read_chandev,
+chandev *write_chandev)
+{
+	chandev_probelist *probe;
+	chandev_model_info *model_info;
+	chandev_probeinfo probeinfo;
+	int               rc=-1,hint=-1;
+	chandev_activelist *newdevice;
+	chandev_probefunc  probefunc;
+	int                saved_lock_cnt;
+	chandev_parms      *curr_parms;
+
+	memset(&probeinfo,0,sizeof(probeinfo));
+	model_info=read_chandev->model_info;
+	if(read_chandev->model_info!=write_chandev->model_info||
+	   (force&&((force->chan_type&model_info->chan_type)==0))||
+	   (!force&&((read_chandev->cu_type!=write_chandev->cu_type)||
+		    (read_chandev->cu_model!=write_chandev->cu_model)||
+		    (read_chandev->dev_type!=write_chandev->dev_type)||
+		   (read_chandev->dev_model!=write_chandev->dev_model))))
+		return(-1); /* inconsistent */
+	for_each(probe,chandev_probelist_head)
+	{
+		probeinfo.chan_type=(probe->chan_type&model_info->chan_type);
+		if(probeinfo.chan_type)
+		{
+#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
+			if(use_devno_names)
+				probeinfo.devif_num=read_chandev->devno;
+			else
+#endif
+				probeinfo.devif_num=-1;
+			probeinfo.read_irq=read_chandev->irq;
+			probeinfo.write_irq=write_chandev->irq;
+			probeinfo.read_devno=read_chandev->devno;
+			probeinfo.write_devno=write_chandev->devno;
+			probeinfo.max_port_no=model_info->max_port_no;
+			probeinfo.cu_type=read_chandev->cu_type;
+			probeinfo.cu_model=read_chandev->cu_model;
+			probeinfo.dev_type=read_chandev->dev_type;
+			probeinfo.dev_model=read_chandev->dev_model;
+			for_each(curr_parms,chandev_parms_head)
+			{
+				if(probe->chan_type==curr_parms->chan_type)
+				{
+					probeinfo.parmstr=curr_parms->parmstr;
+					break;
+				}
+			}
+			if(force)
+			{
+				probeinfo.port_protocol_no=force->port_protocol_no;
+				if(force->devif_num!=-1)
+					probeinfo.devif_num=force->devif_num;
+				probeinfo.checksum_received_ip_pkts=force->checksum_received_ip_pkts;
+				probeinfo.use_hw_stats=force->use_hw_stats;
+				
+			}
+			else
+			{
+				probeinfo.port_protocol_no=-1;
+				probeinfo.checksum_received_ip_pkts=FALSE;
+				probeinfo.use_hw_stats=FALSE;
+				if(probe->chan_type&lcs)
+				{
+					if((probeinfo.read_devno&1)||
+					   ((probeinfo.read_devno|1)!=
+					    (probeinfo.write_devno)))
+						return(-1);
+					hint=(read_chandev->devno&0xFF)>>1;
+					if(hint>model_info->max_port_no)
+					{
+				/* The card is possibly emulated e.g P/390 */
+				/* or possibly configured to use a shared */
+				/* port configured by osa-sf. */
+						hint=0;
+					}
+				}
+			}
+			probeinfo.hint_port_no=hint;
+			probefunc=probe->probefunc;
+			saved_lock_cnt=chandev_full_unlock();
+			/* We have to leave the lock go here */
+			/* as probefunctions can call schedule & */
+                        /* reenter to do a kernel thread & we may deadlock */
+			rc=probefunc(&probeinfo);
+			chandev_lock();
+			chandev_lock_cnt=saved_lock_cnt;
+			if(rc==0)
+			{
+				newdevice=probeinfo.newdevice;
+				if(newdevice)
+				{
+					newdevice->probefunc=probe->probefunc;
+					newdevice->shutdownfunc=probe->shutdownfunc;
+					newdevice->reoperfunc=probe->reoperfunc;
+					probe->devices_found++;
+					chandev_add_to_list((list **)&chandev_activelist_head,
+							    newdevice);
+					chandev_add_to_startmsck_list(&startlist_head,
+								      newdevice->devname,good,good);
+					
+				}
+				else
+				{
+					printk("chandev_initdevice either failed or wasn't called for device read_irq=0x%04x\n",probeinfo.read_irq);
+				}
+				break;
+			}
+		}
+	}
+	return(rc);
+}
+
+
+int chandev_request_irq_from_irqinfo(chandev_irqinfo *irqinfo,chandev *this_chandev)
+{
+	int retval=s390_request_irq_special(irqinfo->irq,
+				   irqinfo->handler,
+				   chandev_not_oper_handler,
+				   irqinfo->irqflags,
+				   irqinfo->devname,
+				   irqinfo->dev_id);
+	if(retval==0)
+		this_chandev->owned=TRUE;
+	return(retval);
+}
+
+void chandev_irqallocerr(chandev_irqinfo *irqinfo,int err)
+{
+	printk("chandev_probe failed to realloc irq=%d for %s err=%d\n",irqinfo->irq,irqinfo->devname,err);
+}
+
+void chandev_probe(void)
+{
+	chandev *read_chandev,*write_chandev,*curr_chandev;
+	chandev_force *curr_force;
+	chandev_noauto_range *curr_noauto;
+	chandev_activelist *curr_device;
+	chandev_irqinfo *curr_irqinfo;
+	s390_dev_info_t curr_devinfo;
+	int  err;
+	int auto_msck_recovery;
+	chandev_msck_status prevstatus;
+	chandev_msck_range *curr_msck_range;
+
+
+	chandev_interrupt_check();
+	chandev_collect_devices();
+	chandev_lock();
+	for_each(curr_irqinfo,chandev_irqinfo_head)
+	{
+		if((curr_device=chandev_get_activelist_by_irq(curr_irqinfo->irq)))
+		{
+			prevstatus=curr_irqinfo->msck_status;
+			if(curr_irqinfo->msck_status!=good)
+			{
+				curr_chandev=chandev_get_by_irq(curr_irqinfo->irq);
+				if(curr_chandev)
+				{
+					auto_msck_recovery=curr_chandev->model_info->
+						auto_msck_recovery;
+				}
+				else
+					goto remove;
+				for_each(curr_msck_range,chandev_msck_range_head)
+				{
+					if(curr_msck_range->lo_devno<=
+					   curr_irqinfo->devno&&
+					   curr_msck_range->hi_devno>=
+					   curr_irqinfo->devno)
+					{
+						auto_msck_recovery=
+							curr_msck_range->
+							auto_msck_recovery;
+						break;
+					}
+				}
+				if((1<<(curr_irqinfo->msck_status-1))&auto_msck_recovery)
+				{
+					if(curr_irqinfo->msck_status==revalidate)
+					{
+						if((get_dev_info_by_irq(curr_irqinfo->irq,&curr_devinfo)==0))
+						{
+							curr_irqinfo->devno=curr_devinfo.devno;
+							curr_irqinfo->msck_status=good;
+							goto remove;
+						}
+					}
+					else
+					{
+						if((curr_chandev=chandev_get_by_irq(curr_irqinfo->irq)))
+						{
+							/* Has the device reappeared */
+							if(curr_chandev->cu_type==curr_device->cu_type&&
+							   curr_chandev->cu_model==curr_device->cu_model&&
+							   curr_chandev->dev_type==curr_device->dev_type&&
+							   curr_chandev->dev_model==curr_device->dev_model&&
+							   curr_chandev->devno==curr_irqinfo->devno)
+							{
+								if((err=chandev_request_irq_from_irqinfo(curr_irqinfo,curr_chandev))==0)
+									curr_irqinfo->msck_status=good;
+								else
+									chandev_irqallocerr(curr_irqinfo,err);
+							}
+					
+						}
+					}
+				}
+			}
+			if(curr_irqinfo->msck_status==good&&prevstatus!=good)
+			{
+				if(curr_device->reoperfunc)
+					curr_device->reoperfunc(curr_device->dev_ptr,
+								(curr_device->read_irqinfo==curr_irqinfo),
+								prevstatus);
+				if(curr_device->category==network_device&&
+				   curr_device->write_irqinfo==curr_irqinfo)
+				{
+					net_device *dev=(net_device *)curr_device->dev_ptr;
+					if(dev->flags&IFF_UP)
+						netif_start_queue(dev);
+				}
+				chandev_add_to_startmsck_list(&mscklist_head,curr_device->devname,
+							      prevstatus,curr_irqinfo->msck_status);
+			}
+		}
+		/* This is required because the device can go & come back */
+                /* even before we realize it is gone owing to the waits in our kernel threads */
+		/* & the device will be marked as not owned but its status will be good */
+                /* & an attempt to accidently reprobe it may be done. */ 
+		remove:
+		chandev_remove(chandev_get_by_irq(curr_irqinfo->irq));
+		
+	}
+	/* extra sanity */
+	for_each(curr_chandev,(chandev *)chandev_head.head)
+		if(curr_chandev->owned)
+			chandev_remove(curr_chandev);
+	for_each(curr_force,chandev_force_head)
+	{
+		for_each(read_chandev,(chandev *)chandev_head.head)
+			if(read_chandev->devno==curr_force->read_devno&&
+				!chandev_active(curr_force->read_devno))
+			{
+				for_each(write_chandev,(chandev *)chandev_head.head)
+					if(write_chandev->devno==
+					   curr_force->write_devno&&
+					   !chandev_active(curr_force->write_devno))
+					{
+						if(chandev_doprobe(curr_force,
+								read_chandev,
+								write_chandev)==0)
+						{
+							chandev_remove(read_chandev);
+							chandev_remove(write_chandev);
+							goto chandev_probe_skip;
+						}
+					}
+			}
+	chandev_probe_skip:
+	}
+	for_each(curr_chandev,(chandev *)chandev_head.head)
+	{
+		for_each(curr_noauto,chandev_noauto_head)
+		{
+			if(curr_chandev->devno>=curr_noauto->lo_devno&&
+			   curr_chandev->devno<=curr_noauto->hi_devno)
+			{
+				chandev_remove(curr_chandev);
+				break;
+			}
+		}
+	}
+	for_each(curr_chandev,(chandev *)chandev_head.head)
+	{
+		if(curr_chandev->next&&curr_chandev->model_info==
+		   curr_chandev->next->model_info)
+		{
+			
+			chandev_doprobe(NULL,curr_chandev,curr_chandev->next);
+			curr_chandev=curr_chandev->next;
+		}
+	}
+	chandev_unlock();
+	chandev_remove_all();
+}
+
+static void chandev_not_oper_func(int irq,int status)
+{
+	chandev_irqinfo *curr_irqinfo;
+	chandev_activelist *curr_device;
+	
+	chandev_lock();
+	for_each(curr_irqinfo,chandev_irqinfo_head)
+		if(curr_irqinfo->irq==irq)
+		{
+			switch(status)
+			{
+				/* Currently defined but not used in kernel */
+				/* Despite being in specs */
+			case DEVSTAT_NOT_OPER:
+				curr_irqinfo->msck_status=not_oper;
+				break;
+#ifdef DEVSTAT_NO_PATH
+				/* Kernel hasn't this defined currently. */
+				/* Despite being in specs */
+			case DEVSTAT_NO_PATH:
+				curr_irqinfo->msck_status=no_path;
+				break;
+#endif
+			case DEVSTAT_REVALIDATE:
+				curr_irqinfo->msck_status=revalidate;
+				break;
+			case DEVSTAT_DEVICE_GONE:
+				curr_irqinfo->msck_status=gone;
+				break;
+			}
+			for_each(curr_device,chandev_activelist_head)
+			{
+				if(curr_device->write_irqinfo==curr_irqinfo)
+				{
+					if(curr_device->category==network_device)
+					{
+						net_device *dev=(net_device *)curr_device->dev_ptr;
+						if(dev->flags&IFF_UP)
+							netif_stop_queue(dev);
+					}
+				}
+				break;
+			}
+			break;
+		}
+	chandev_unlock();
+}
+
+
+static chandev_task_retval chandev_msck_task(void *unused)
+{
+	int loopcnt,not_oper_probe_required=FALSE;
+	wait_queue_head_t    wait;
+	chandev_not_oper_struct *new_not_oper;
+
+	chandev_daemonize("chandev_msck_kernel_thread",0,TRUE);
+	/* This loop exists because machine checks tend to come in groups & we have
+           to wait for the other devnos to appear also */
+	init_waitqueue_head(&wait);
+	for(loopcnt=0;loopcnt<10||(jiffies-chandev_last_machine_check)<HZ;loopcnt++)
+	{
+		sleep_on_timeout(&wait,HZ);
+	}
+	atomic_set(&chandev_msck_thread_lock,1);
+	while(!atomic_compare_and_swap(TRUE,FALSE,&chandev_new_msck));
+	{
+		chandev_probe();
+	}
+	while(TRUE)
+	{
+		
+		unsigned long        flags; 
+		spin_lock_irqsave(&chandev_not_oper_spinlock,flags);
+		new_not_oper=(chandev_not_oper_struct *)dequeue_head(&chandev_not_oper_head);
+		spin_unlock_irqrestore(&chandev_not_oper_spinlock,flags);
+		if(new_not_oper)
+		{
+			chandev_not_oper_func(new_not_oper->irq,new_not_oper->status);
+			not_oper_probe_required=TRUE;
+			kfree(new_not_oper);
+		}
+		else
+			break;
+	}
+	if(not_oper_probe_required)
+		chandev_probe();
+	chandev_task_return(0);
+}
+
+
+
+#if CHANDEV_USE_KERNEL_THREADS
+static void chandev_start_msck_thread(void *unused)
+{
+	/* tq_scheduler sometimes leaves interrupts disabled from do bottom half */
+	__sti();
+	kernel_thread((int (*)(void *))chandev_msck_task,
+		      (void*)NULL,0);
+}
+#endif
+
+
+
+static char *argstrs[]=
+{
+	"noauto",
+	"del_noauto",
+	"ctc",
+	"escon",
+	"lcs",
+	"osad",
+	"qeth",
+	"claw",
+	"add_parms",
+	"del_parms",
+	"del_force",
+#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
+	"use_devno_names",
+	"dont_use_devno_names",
+#endif
+	"add_model",
+	"del_model",
+	"auto_msck",
+	"del_auto_msck",
+	"del_all_models",
+	"reset_conf_clean",
+	"reset_conf",
+	"shutdown",
+	"reprobe",
+	"unregister_probe",
+	"read_conf",
+	"dont_read_conf",
+};
+
+typedef enum
+{
+	stridx_mult=256,
+	first_stridx=0,
+	noauto_stridx=first_stridx,
+	del_noauto_stridx,
+	ctc_stridx,
+	escon_stridx,
+	lcs_stridx,
+	osad_stridx,
+        qeth_stridx,
+	claw_stridx,
+	add_parms_stridx,
+	del_parms_stridx,
+	del_force_stridx,
+#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
+	use_devno_names_stridx,
+	dont_use_devno_names_stridx,
+#endif
+	add_model_stridx,
+	del_model_stridx,
+	auto_msck_stridx,
+	del_auto_msck_stridx,
+	del_all_models_stridx,
+	reset_conf_clean_stridx,
+	reset_conf_stridx,
+	shutdown_stridx,
+	reprobe_stridx,
+	unregister_probe_stridx,
+	read_conf_stridx,
+	dont_read_conf_stridx,
 	last_stridx,
 } chandev_str_enum;
 
@@ -281,303 +1866,423 @@
 {
 	chandev_noauto_range *new_range;
 
-	if((new_range=chandev_alloc_listmember(
-		(list **)&chandev_noauto_head,sizeof(chandev_noauto_range))))
+	if((new_range=chandev_alloc(sizeof(chandev_noauto_range))))
+	{
+		new_range->lo_devno=lo_devno;
+		new_range->hi_devno=hi_devno;
+		chandev_add_to_list((list **)&chandev_noauto_head,new_range);
+	}
+}
+
+
+void chandev_add_msck_range(u16 lo_devno,u16 hi_devno,int auto_msck_recovery)
+{
+	chandev_msck_range *new_range;
+
+	if((new_range=chandev_alloc(sizeof(chandev_msck_range))))
 	{
 		new_range->lo_devno=lo_devno;
 		new_range->hi_devno=hi_devno;
+		new_range->auto_msck_recovery=auto_msck_recovery;
+		chandev_add_to_list((list **)&chandev_msck_range_head,new_range);
 	}
 }
 
+
+
 static char chandev_keydescript[]=
-"chan_type key bitfield\nctc=0x1,escon=0x2,lcs=0x4,lcs=0x4,osad=0x8,claw=0x16\n";
+"\nchan_type key bitfield ctc=0x1,escon=0x2,lcs=0x4,osad=0x8,qeth=0x10,claw=0x20\n";
+
+
+#if  CONFIG_ARCH_S390X
+/* We need this as we sometimes use this to evaluate pointers */
+typedef long chandev_int; 
+#else
+typedef int chandev_int;
+#endif
+
 
-static void chandev_print_args(void)
+#if (LINUX_VERSION_CODE<KERNEL_VERSION(2,3,0)) || (CONFIG_ARCH_S390X)
+/*
+ * Read an int from an option string; if available accept a subsequent
+ * comma as well.
+ *
+ * Return values:
+ * 0 : no int in string
+ * 1 : int found, no subsequent comma
+ * 2 : int found including a subsequent comma
+ */
+static chandev_int chandev_get_option(char **str,chandev_int *pint)
 {
-	printk("valid chandev arguments are" 
-                "<> indicate optional parameters | indicate a choice.\n");
-	printk("noauto,<lo_devno>,<hi_devno>\n"
-               "don't probe a range of device numbers for channel devices\n");
-	printk("lcs|ctc|escon<devif_num>,read_devno,write_devno,<port_no>,"
-	       "<do_ip_checksumming>,<use_hw_stats>\n");
-	printk("e.g. ctc0,0x7c00,0x7c01,-1,0,0\n");
-	printk("     tells the channel layer to force ctc0 if detected to use\n"
-	       "     cuu's 7c00 & 7c01 port ( rel adapter no ) is invalid for\n"
-	       "     ctc's so use -1 don't do checksumming on received ip\n"
-               "     packets & as ctc doesn't have hardware stats ignore this\n"
-               "     parameter\n\n");
-	printk("del_force read_devno\n"
-       "delete a forced channel device from force list.\n");
-	printk("use_devno_names, tells the channel layer to assign device\n"
-               "names based on the read channel cuu number\n"
-	       "e.g. a token ring read channel 0x7c00 would have an interface"
-	       "called tr0x7c00 this avoids name collisions on devices.");
-	printk("add_model chan_type cu_model max_port no\n"
-	       "tells the channel layer to probe for the device described\n");
-	printk("%s use max_port_no of 0 for devices where this field "
-       "is invalid.\n",chandev_keydescript);
-	printk("del_model cu_type cu_model\n");
-	printk("del_all_models\n");
+    char *cur = *str;
+
+    if (!cur || !(*cur)) return 0;
+    *pint = simple_strtol(cur,str,0);
+    if (cur==*str) return 0;
+    if (**str==',') {
+        (*str)++;
+        return 2;
+    }
+
+    return 1;
 }
 
+static char *chandev_get_options(char *str, int nints, chandev_int *ints)
+{
+	int res,i=1;
+
+    while (i<nints) {
+        res = chandev_get_option(&str, ints+i);
+        if (res==0) break;
+        i++;
+        if (res==1) break;
+    }
+	ints[0] = i-1;
+	return(str);
+}
+#else
+#define chandev_get_option get_option
+#define chandev_get_options get_options
+#endif
 
-static int chandev_setup(char *str)
+static int chandev_setup(char *instr,char *errstr,int lineno)
 {
 	chandev_strval   val=isnull;
 	chandev_str_enum stridx;
 	long             endlong;
 	chandev_type     chan_type;
-#define CHANDEV_MAX_EXTRA_INTS 5
-	int ints[CHANDEV_MAX_EXTRA_INTS+1];
+	char             *str,*currstr,*interpretstr=NULL;
+	int              cnt,strcnt;
+#define CHANDEV_MAX_EXTRA_INTS 8
+	chandev_int ints[CHANDEV_MAX_EXTRA_INTS+1];
 	memset(ints,0,sizeof(ints));
-	chandev_pack_args(str);
-	for(stridx=first_stridx;stridx<last_stridx;stridx++)
-		if((val=chandev_strcmp(argstrs[stridx],&str,&endlong)))
-			break;
-	if(val)
+	currstr=alloca(strlen(instr)+1);
+	strcpy(currstr,instr);
+	strcnt=chandev_pack_args(currstr);
+	for(cnt=1;cnt<=strcnt;cnt++)
 	{
-		if(val&iscomma)
-			get_options(str,CHANDEV_MAX_EXTRA_INTS,ints);
-		else
-			ints[0]=0;
-		val=(((chandev_strval)stridx)*stridx_mult)+(val&~isstr);
-		switch(val)
-		{
-		case noauto_stridx*stridx_mult:
-		case (noauto_stridx*stridx_mult)|iscomma:
-			switch(ints[0])
-			{
-			case 0: 
-				chandev_free_all((list **)&chandev_noauto_head);
-				chandev_add_noauto(0,0xffff);
-				break;
-			case 1:
-				ints[2]=ints[1];
-			case 2:
-				chandev_add_noauto(ints[1],ints[2]);
-			
+		interpretstr=currstr;
+		for(stridx=first_stridx;stridx<last_stridx;stridx++)
+		{
+			str=currstr;
+			if((val=chandev_strcmp(argstrs[stridx],&str,&endlong)))
+				break;
+		}
+		currstr=str;
+		if(val)
+		{
+			if(val&iscomma)
+			{
+				if(stridx==add_parms_stridx&&(val==(isstr|iscomma)))
+				{
+					str=currstr;
+					if(chandev_get_option(&str,&ints[0])==2)
+					{
+						chandev_add_parms(ints[0],str);
+						currstr=str+strlen(str)+1;
+						continue;
+					}
+					else
+						goto BadArgs;
+				}
+				else
+					currstr=chandev_get_options(str,CHANDEV_MAX_EXTRA_INTS,ints)+1;
 			}
-			break;
-		case (ctc_stridx*stridx_mult)|isnum|iscomma:
-		case (escon_stridx*stridx_mult)|isnum|iscomma:
-		case (lcs_stridx*stridx_mult)|isnum|iscomma:
+			else
+			{
+				ints[0]=0;
+				currstr++;
+			}
+			val=(((chandev_strval)stridx)*stridx_mult)+(val&~isstr);
 			switch(val)
 			{
-			case (ctc_stridx*stridx_mult)|isnum|iscomma:
-				chan_type=ctc;
+			case noauto_stridx*stridx_mult:
+			case (noauto_stridx*stridx_mult)|iscomma:
+				switch(ints[0])
+				{
+				case 0: 
+					chandev_free_all_list((list **)&chandev_noauto_head);
+					chandev_add_noauto(0,0xffff);
+					break;
+				case 1:
+					ints[2]=ints[1];
+				case 2:
+					chandev_add_noauto(ints[1],ints[2]);
+					break;
+				default:
+					goto BadArgs;
+				}
 				break;
-			case (escon_stridx*stridx_mult)|isnum|iscomma:
-				chan_type=escon;
+			case (auto_msck_stridx*stridx_mult)|iscomma:
+				switch(ints[0])
+				{
+				case 1:
+					chandev_free_all_list((list **)&chandev_msck_range_head);
+					chandev_add_msck_range(0,0xffff,ints[1]);
+					break;
+				case 2:
+					chandev_add_msck_range(ints[1],ints[1],ints[2]);
+					break;
+				case 3:
+					chandev_add_msck_range(ints[1],ints[2],ints[3]);
+					break;
+				default:
+					goto BadArgs;
+					
+				}
+			case del_auto_msck_stridx*stridx_mult:
+			case (del_auto_msck_stridx*stridx_mult)|iscomma:
+				switch(ints[0])
+				{
+				case 0:
+					chandev_free_all_list((list **)&chandev_msck_range_head);
+					break;
+				case 1:
+					chandev_del_msck(ints[1]);
+				default:
+					goto BadArgs;
+				}
+			case del_noauto_stridx*stridx_mult:
+				chandev_free_all_list((list **)&chandev_noauto_head);
 				break;
+			case (del_noauto_stridx*stridx_mult)|iscomma:
+				if(ints[0]==1)
+					chandev_del_noauto(ints[1]);
+				else
+					goto BadArgs;
+				break;
+			case (ctc_stridx*stridx_mult)|isnum|iscomma:
+			case (escon_stridx*stridx_mult)|isnum|iscomma:
 			case (lcs_stridx*stridx_mult)|isnum|iscomma:
-				chan_type=lcs;
+			case (osad_stridx*stridx_mult)|isnum|iscomma:
+			case (qeth_stridx*stridx_mult)|isnum|iscomma:
+			case (claw_stridx*stridx_mult)|isnum|iscomma:
+				switch(val)
+				{
+				case (ctc_stridx*stridx_mult)|isnum|iscomma:
+					chan_type=ctc;
+					break;
+				case (escon_stridx*stridx_mult)|isnum|iscomma:
+					chan_type=escon;
+					break;
+				case (lcs_stridx*stridx_mult)|isnum|iscomma:
+					chan_type=lcs;
+					break;
+				case (osad_stridx*stridx_mult)|isnum|iscomma:
+					chan_type=osad;
+					break;
+				case (qeth_stridx*stridx_mult)|isnum|iscomma:
+					chan_type=qeth;
+					break;
+				case (claw_stridx*stridx_mult)|isnum|iscomma:
+					chan_type=claw;
+					break;
+				default:
+					goto BadArgs;
+				}
+				chandev_add_force(chan_type,endlong,ints[1],ints[2],
+						  ints[3],ints[4],ints[5]);
 				break;
-			default:
-				goto BadArgs;
-			}
-			chandev_add_force(chan_type,endlong,ints[1],ints[2],
-					  ints[3],ints[4],ints[5]);
-			break;
-		case (del_force_stridx*stridx_mult)|iscomma:
-			if(ints[0]!=1)
-				goto BadArgs;
-			chandev_del_force(ints[1]);
-			break;
-		case (use_devno_names_stridx*stridx_mult):
-			use_devno_names=1;
-			break;
-		case (dont_use_devno_names_stridx*stridx_mult):
-			use_devno_names=0;
-	        case (add_model_stridx*stridx_mult)|iscomma:
-			if(ints[0]<3)
-				goto BadArgs;
-			if(ints[0]==3)
-			{
+			case (del_parms_stridx*stridx_mult):
+				ints[1]=-1;
+			case (del_parms_stridx*stridx_mult)|iscomma:
+				if(ints[0]==1)
+					ints[2]=FALSE;
+				if(ints[0]>2)
+					goto BadArgs;
+				chandev_remove_parms(ints[1],ints[2]);
+				break;
+			case (del_force_stridx*stridx_mult)|iscomma:
+				if(ints[0]!=1)
+					goto BadArgs;
+				chandev_del_force(ints[1]);
+				break;
+#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
+			case (use_devno_names_stridx*stridx_mult):
+				use_devno_names=1;
+				break;
+			case (dont_use_devno_names_stridx*stridx_mult):
+				use_devno_names=0;
+#endif
+			case (add_model_stridx*stridx_mult)|iscomma:
+				if(ints[0]<3)
+					goto BadArgs;
+				if(ints[0]==3)
+					ints[4]=-1;
+				if(ints[0]<=4)
+					ints[5]=-1;
+				if(ints[0]<=5)
+					ints[6]=-1;
+				if(ints[0]<=6)
+					ints[7]=default_msck_bits;
+				ints[0]=7;
+				chandev_add_model(ints[1],ints[2],ints[3],
+						  ints[4],ints[5],ints[6],ints[7]);
+				break;
+			case (del_model_stridx*stridx_mult)|iscomma:
+				if(ints[0]<2||ints[0]>4)
+					goto BadArgs;
+				if(ints[0]<3)
+					ints[3]=-2;
+				if(ints[0]<4)
+					ints[4]=-2;
 				ints[0]=4;
-				ints[4]=-1;
-			}
-			chandev_add_model(ints[1],ints[2],ints[3],ints[4]);
-		break;
-		case (del_model_stridx*stridx_mult)|iscomma:
-			if(ints[0]!=2)
+				chandev_del_model(ints[1],ints[2],ints[3],ints[4]);
+				break;
+			case del_all_models_stridx*stridx_mult:
+				chandev_remove_all_models();
+				break;
+			case reset_conf_stridx*stridx_mult:
+				chandev_reset();
+				chandev_init_default_models();
+				break;
+			case reset_conf_clean_stridx*stridx_mult:
+				chandev_reset();
+				break;
+			case shutdown_stridx*stridx_mult:
+				chandev_shutdown_all();
+				break;
+			case (shutdown_stridx*stridx_mult)|iscomma:
+				switch(ints[0])
+				{
+				case 0:
+					if(strlen(str))
+						chandev_shutdown_by_name(str);
+					else
+						goto BadArgs;
+					break;
+				case 1:
+					chandev_shutdown_by_devno(ints[1]);
+					break;
+				default:
+					goto BadArgs;
+				}
+				break;
+			case reprobe_stridx*stridx_mult:
+				chandev_probe();
+				break;
+			case unregister_probe_stridx*stridx_mult:
+				chandev_free_all_list((list **)&chandev_probelist_head);
+				break;
+			case (unregister_probe_stridx*stridx_mult)|iscomma:
+				if(ints[0]!=1)
+					goto BadArgs;
+				chandev_unregister_probe((chandev_probefunc)ints[1]);
+				break;
+			case read_conf_stridx*stridx_mult:
+				chandev_read_conf();
+				break;
+			case dont_read_conf_stridx*stridx_mult:
+				chandev_conf_read=TRUE;
+				break;
+			default:
 				goto BadArgs;
-			chandev_del_model(ints[1],ints[2]);
-			break;
-		case del_all_models_stridx*stridx_mult:
-			chandev_remove_all_models();
-			break;
-		default:
+			}		
+		}
+		else
 			goto BadArgs;
-		}		
 	}
 	return(1);
  BadArgs:
-	chandev_print_args();
+	printk("chandev_setup bad argument %s",instr);
+	if(errstr)
+	{
+                printk("%s %d interpreted as %s",errstr,lineno,interpretstr);
+		if(strcnt>1)
+			printk(" before semicolon no %d",cnt);
+	}
+	printk(".\n Type man chandev for more info.\n\n");
 	return(0);
 }
-
-__setup("chandev=",chandev_setup);
-
-int chandev_doprobe(chandev_force *force,chandev *read_chandev,
-chandev *write_chandev)
+#define CHANDEV_KEYWORD "chandev="
+static int chandev_setup_bootargs(char *str,int paramno)
 {
-	chandev_probelist *probe;
-	chandev_model_info *model_info;
-	chandev_probeinfo probeinfo;
-	int  retval=-1,hint=-1;
+	int len;
+
+	char *copystr;
+	for(len=0;str[len]!=0&&!isspace(str[len]);len++);
+	copystr=alloca(len+1);
+	strncpy(copystr,str,len);
+	copystr[len]=0;
+	if(chandev_setup(copystr,"at "CHANDEV_KEYWORD" bootparam no",paramno)==0)
+		return(0);
+	return(len);
 
-	model_info=read_chandev->model_info;
-	if(read_chandev->model_info!=write_chandev->model_info||
-	   (force&&((force->chan_type&model_info->chan_type)==0)))
-		return(-1); /* inconsistent */
-	for(probe=chandev_probelist_head;
-	    probe!=NULL;
-	    probe=probe->next)
-	{
-		if(probe->chan_type&model_info->chan_type)
-		{
-			if(use_devno_names)
-				probeinfo.devif_num=read_chandev->devno;
-			else
-				probeinfo.devif_num=-1;
-			probeinfo.read_irq=read_chandev->irq;
-			probeinfo.write_irq=write_chandev->irq;
-			
-			probeinfo.max_port_no=model_info->max_port_no;
-			if(force)
-			{
-				probeinfo.forced_port_no=force->port_no;
-				if(force->devif_num!=-1)
-					probeinfo.devif_num=force->devif_num;
-				probeinfo.do_ip_checksumming=force->do_ip_checksumming;
-				probeinfo.use_hw_stats=force->use_hw_stats;
-				
-			}
-			else
-			{
-				probeinfo.forced_port_no=-1;
-				probeinfo.do_ip_checksumming=FALSE;
-				probeinfo.use_hw_stats=FALSE;
-				if(probe->chan_type&lcs)
-				{
-					hint=(read_chandev->devno&0xFF)>>1;
-					if(hint>model_info->max_port_no)
-					{
-				/* The card is possibly emulated e.g P/390 */
-				/* or possibly configured to use a shared */
-				/* port configured by osa-sf. */
-						hint=0;
-					}
-				}
-			}
-			probeinfo.hint_port_no=hint;
-			retval=probe->probefunc(&probeinfo);
-			if(retval==0)
-				break;
-		}
-	}
-	return(retval);
 }
 
-void chandev_probe(void)
+/*
+  We can't parse using a __setup function as kmalloc isn't available
+  at this time.
+ */
+static void __init chandev_parse_args(void)
 {
-	chandev *read_chandev,*write_chandev,*curr_chandev;
-	chandev_force *curr_force;
-	chandev_noauto_range *curr_noauto;
+#define CHANDEV_KEYWORD "chandev="
+	extern char saved_command_line[];
+	int cnt,len,paramno=1;
 
-	chandev_collect_devices();
-	for(curr_force=chandev_force_head;curr_force!=NULL;
-		    curr_force=curr_force->next)
-	{
-		for(read_chandev=chandev_head;
-		    read_chandev!=NULL;
-		    read_chandev=read_chandev->next)
-			if(read_chandev->devno==curr_force->read_devno)
-			{
-				for(write_chandev=chandev_head;
-				    write_chandev!=NULL;
-				    write_chandev=write_chandev->next)
-					if(write_chandev->devno==
-					   curr_force->write_devno)
-					{
-						if(chandev_doprobe(curr_force,
-								read_chandev,
-								write_chandev)==0)
-						{
-							chandev_remove(read_chandev);
-							chandev_remove(write_chandev);
-							goto chandev_probe_skip;
-						}
-					}
-			}
-	chandev_probe_skip:
-	}
-	for(curr_chandev=chandev_head;
-		    curr_chandev!=NULL;
-		    curr_chandev=curr_chandev->next)
-	{
-		for(curr_noauto=chandev_noauto_head;curr_noauto!=NULL;
-		    curr_noauto=curr_noauto->next)
-		{
-			if(curr_chandev->devno>=curr_noauto->lo_devno&&
-			   curr_chandev->devno<=curr_noauto->hi_devno)
-			{
-				chandev_remove(curr_chandev);
-				break;
-			}
-		}
-	}
-	for(curr_chandev=chandev_head;curr_chandev!=NULL;
-	    curr_chandev=curr_chandev->next)
+	len=strlen(saved_command_line)-sizeof(CHANDEV_KEYWORD);
+	for(cnt=0;cnt<len;cnt++)
 	{
-		if(curr_chandev->next&&curr_chandev->model_info==
-		   curr_chandev->next->model_info)
+		if(strncmp(&saved_command_line[cnt],CHANDEV_KEYWORD,
+			   sizeof(CHANDEV_KEYWORD)-1)==0)
 		{
-			
-			chandev_doprobe(NULL,curr_chandev,curr_chandev->next);
-			curr_chandev=curr_chandev->next;
+			cnt+=(sizeof(CHANDEV_KEYWORD)-1);	
+			cnt+=chandev_setup_bootargs(&saved_command_line[cnt],paramno);
+			paramno++;
 		}
 	}
-	chandev_remove_all();
 }
 
 int chandev_do_setup(char *buff,int size)
 {
-	int curr,startline=0,comment=FALSE,newline=FALSE,oldnewline=TRUE;
-	int rc=1;
+	int curr,comment=FALSE,newline=FALSE,oldnewline=TRUE;
+	char *startline=NULL,*endbuff=&buff[size];
+
+	int lineno=0;
 
-	buff[size]=0;
-	for(curr=0;curr<=size;curr++)
+	*endbuff=0;
+	for(;buff<=endbuff;curr++,buff++)
 	{
-		if(buff[curr]=='#')
-		{
-			comment=TRUE;
-			newline=FALSE;
-		}
-		else if(buff[curr]==10||buff[curr]==13||buff[curr]==0)
+		if(*buff==0xa||*buff==0xc||*buff==0)
 		{
-			buff[curr]=0;
-			comment=FALSE;
+			if(*buff==0xa||*buff==0)
+				lineno++;
+			*buff=0;
 			newline=TRUE;
 		}
-		if(comment==FALSE&&curr>startline
-		   &&((oldnewline==TRUE&&newline==FALSE)||curr==size))
+		else
+		{ 
+			newline=FALSE;
+			if(*buff=='#')
+				comment=TRUE;
+		}
+		if(comment==TRUE)
+			*buff=0;
+		if(startline==NULL&&isalpha(*buff))
+			startline=buff;
+		if(startline&&(buff>startline)&&(oldnewline==FALSE)&&(newline==TRUE))
 		{
-			if((rc=chandev_setup(&buff[startline]))==0)
-				break;
-			startline=curr+1;
+			if((chandev_setup(startline," on line no",lineno))==0)
+				return(-EINVAL);
+			startline=NULL;
 		}
+		if(newline)
+			comment=FALSE;
 	        oldnewline=newline;
 	}
-	return(rc);
+	return(0);
 }
-void chandev_read_conf(void)
+
+
+static void chandev_read_conf(void)
 {
 #define CHANDEV_FILE "/etc/chandev.conf"
 	struct stat statbuf;
 	char        *buff;
 	int         curr,left,len,fd;
 
+	if(in_interrupt()||current->fs->root==NULL)
+		return;
 	chandev_conf_read=TRUE;
 	set_fs(KERNEL_DS);
 	if(stat(CHANDEV_FILE,&statbuf)==0)
@@ -606,34 +2311,12 @@
 	set_fs(USER_DS);
 }
 
-void chandev_register_and_probe(chandev_probefunc probefunc,chandev_type chan_type)
+static void chandev_read_conf_if_necessary(void)
 {
-	chandev_probelist *new_probe;
 	if(!chandev_conf_read)
 		chandev_read_conf();
-	if((new_probe=chandev_alloc_listmember((list **)&
-		chandev_probelist_head,sizeof(chandev_probelist))))
-	{
-		new_probe->probefunc=probefunc;
-		new_probe->chan_type=chan_type;
-		chandev_probe();
-	}
-}
-
-void chandev_unregister(chandev_probefunc probefunc)
-{
-	 chandev_probelist *curr_probe=NULL;
-		
-	for(curr_probe=chandev_probelist_head;curr_probe!=NULL;
-		    curr_probe=curr_probe->next)
-	{
-		if(curr_probe->probefunc==probefunc)
-			chandev_free_listmember((list **)&chandev_probelist_head,
-						(list *)curr_probe);
-	}
 }
 
-
 #ifdef CONFIG_PROC_FS
 #define chandev_printf(exitchan,args...)     \
 splen=sprintf(spbuff,##args);                \
@@ -645,53 +2328,222 @@
 if(currlen>=length)                          \
        goto exitchan;
 
-
+void sprintf_msck(char *buff,int auto_msck_recovery)
+{
+	chandev_msck_status idx;
+	int first_time=TRUE;
+	buff[0]=0;
+	for(idx=first_msck;idx<last_msck;idx++)
+	{
+		if((1<<(idx-1))&auto_msck_recovery)
+		{
+			buff+=sprintf(buff,"%s%s",(first_time ? "":","),
+				      msck_status_strs[idx]);
+			first_time=FALSE;
+		}
+	}
+}
 
 static int chandev_read_proc(char *page, char **start, off_t offset,
 			  int length, int *eof, void *data)
 {
 	char *spbuff=*start=page;
-	int    currlen=0,splen;
+	int    currlen=0,splen=0;
 	off_t  spoffset=0;
 	chandev_model_info *curr_model;
 	chandev_noauto_range *curr_noauto;
 	chandev_force *curr_force;
+	chandev_activelist *curr_device;
+	chandev_probelist  *curr_probe;
+	chandev_msck_range *curr_msck_range;
+	s390_dev_info_t   curr_devinfo;
+	int pass,chandevs_detected,curr_irq,loopcnt;
+	chandev_irqinfo *read_irqinfo,*write_irqinfo;
+	char buff[40],buff2[80];    
+
+	chandev_lock();
+	chandev_read_conf_if_necessary();
+	chandev_printf(chan_exit,"\n%s\n"
+		       "*'s for cu/dev type/models indicate don't cares\n",chandev_keydescript);
+#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
+	chandev_printf(chan_exit,"\nuse_devno_names: %s\n\n",use_devno_names ? "on":"off");
+#endif
+	if(chandev_models_head)
+	{
+		chandev_printf(chan_exit,"Channels enabled for detection\n");      
+		chandev_printf(chan_exit,"  chan     cu      cu     dev   dev    max     auto recovery\n");
+		chandev_printf(chan_exit,"  type    type    model  type  model  port_no.      type    \n");
+		chandev_printf(chan_exit,"============================================================\n");
+		for_each(curr_model,chandev_models_head)
+		{
+			
+			
+			chandev_sprint_devinfo(buff,curr_model->cu_type,
+					       curr_model->cu_model,
+					       curr_model->dev_type,
+					       curr_model->dev_model);
+			sprintf_msck(buff2,curr_model->auto_msck_recovery);
+			chandev_printf(chan_exit,"  0x%02x  %s%3d %s\n",
+				       curr_model->chan_type,buff,
+				       (int)curr_model->max_port_no,buff2);         
+		}
+	}
+        
+	if(chandev_noauto_head)
+	{
+		chandev_printf(chan_exit,"\nNo auto devno ranges\n");
+		chandev_printf(chan_exit,"   From        To   \n");
+		chandev_printf(chan_exit,"====================\n");
+		for_each(curr_noauto,chandev_noauto_head)
+		{
+			chandev_printf(chan_exit,"  0x%04x     0x%04x\n",
+				       curr_noauto->lo_devno,
+				       curr_noauto->hi_devno);
+		}
+	}
+	if(chandev_msck_range_head)
+	{
+		
+		chandev_printf(chan_exit,"\nAutomatic machine check recovery devno ranges\n");
+		chandev_printf(chan_exit,"   From        To   automatic recovery type\n");
+		chandev_printf(chan_exit,"===========================================\n");
+		for_each(curr_msck_range,chandev_msck_range_head)
+		{
+			sprintf_msck(buff2,curr_msck_range->auto_msck_recovery);
+			chandev_printf(chan_exit,"  0x%04x     0x%04x %s\n",
+				       curr_msck_range->lo_devno,
+				       curr_msck_range->hi_devno,buff2)
+		}
+	}
+	if(chandev_force_head)
+	{
+		chandev_printf(chan_exit,"\nForced devices\n");
+		chandev_printf(chan_exit,"  chan defif read   write     port         ip    hw\n");
+		chandev_printf(chan_exit,"  type  num  devno  devno  protocol no.  chksum stats\n");
+		chandev_printf(chan_exit,"======================================================\n");
+		for_each(curr_force,chandev_force_head)
+		{
+			chandev_printf(chan_exit,"  0x%02x  %3d  0x%04x 0x%04x       %3d       %1d    %1d\n",
+				       curr_force->chan_type,curr_force->devif_num,
+				       curr_force->read_devno,curr_force->write_devno,
+				       curr_force->port_protocol_no,curr_force->checksum_received_ip_pkts,
+				       curr_force->use_hw_stats);
+		}
+	}
+	if(chandev_probelist_head)
+	{
+#if CONFIG_ARCH_S390X
+		chandev_printf(chan_exit,"\nRegistered probe functions\n"
+			       		 "probefunc            shutdownfunc         reoperfunc         chan  devices\n"
+                                         "                                                             type   found\n"
+			                 "==========================================================================\n");
+#else
+		chandev_printf(chan_exit,"\nRegistered probe functions\n"
+			                 "probefunc   shutdownfunc  reoperfunc chan  devices\n"
+                                         "                                     type   found\n"
+			                 "==================================================\n");
+#endif
+		for_each(curr_probe,chandev_probelist_head)
+		{
+			chandev_printf(chan_exit,"0x%p   0x%p   0x%p   0x%02x      %d\n",
+				       curr_probe->probefunc,
+				       curr_probe->shutdownfunc,
+				       curr_probe->reoperfunc,
+				       curr_probe->chan_type,
+				       curr_probe->devices_found);
+		}
+	}
+	if(chandev_activelist_head)
+	{
+#if CONFIG_ARCH_S390X
+		chandev_printf(chan_exit,
+			       "\nInitialised Devices\n"
+			       " read   write  read   write chan port  dev             dev        read msck   write msck\n"
+			       " irq    irq    devno  devno type no.   ptr             name        status      status   \n"
+			       "========================================================================================\n");
+#else
+		chandev_printf(chan_exit,
+			       "\nInitialised Devices\n"
+			       " read   write  read   write chan port  dev     dev        read msck   write msck\n"
+			       " irq    irq    devno  devno type no.   ptr     name        status      status   \n"
+			       "================================================================================\n");
+#endif
+		/* We print this list backwards for cosmetic reasons */
+		for(curr_device=chandev_activelist_head;
+		    curr_device->next!=NULL;curr_device=curr_device->next);
+		while(curr_device)
+		{
+			read_irqinfo=curr_device->read_irqinfo;
+			write_irqinfo=curr_device->write_irqinfo;
+			chandev_printf(chan_exit,
+				       "0x%04x 0x%04x 0x%04x 0x%04x 0x%02x %2d 0x%p %-10s   %-12s %-12s\n",
+				       curr_device->read_irqinfo->irq,curr_device->write_irqinfo->irq,
+				       (int)read_irqinfo->devno,
+				       (int)write_irqinfo->devno,
+				       curr_device->chan_type,(int)curr_device->port_no,
+				       curr_device->dev_ptr,curr_device->devname,
+				       msck_status_strs[read_irqinfo->msck_status],
+				       msck_status_strs[write_irqinfo->msck_status]);
+			get_prev((list *)chandev_activelist_head,
+				 (list *)curr_device,
+				 (list **)&curr_device);
+		}
+	}
+	chandevs_detected=FALSE;
+	for(pass=FALSE;pass<=TRUE;pass++)
+	{
+		if(pass&&chandevs_detected)
+		{
+			chandev_printf(chan_exit,"\nchannels detected\n");
+			chandev_printf(chan_exit,"              chan    cu    cu   dev    dev   in   chandev\n");
+			chandev_printf(chan_exit,"  irq  devno  type   type  model type  model  use    reg.\n");
+			chandev_printf(chan_exit,"==========================================================\n");
+		}
+		for(curr_irq=get_irq_first(),loopcnt=0;curr_irq>=0; curr_irq=get_irq_next(curr_irq),loopcnt++)
+		{
+			if(loopcnt>0x10000)
+			{
+				printk(KERN_ERR"chandev_read_proc detected infinite loop bug in get_irq_next\n");
+				goto chan_error;
+			}
+			if((curr_model=chandev_is_chandev(curr_irq,&curr_devinfo)))
+			{
+				chandevs_detected=TRUE;
+				if(pass)
+				{
+					chandev_printf(chan_exit,"0x%04x 0x%04x 0x%02x  0x%04x 0x%02x  0x%04x 0x%02x  %-5s %-5s\n",
+						       curr_irq,curr_devinfo.devno,
+						       curr_model->chan_type,
+						       (int)curr_devinfo.sid_data.cu_type,
+						       (int)curr_devinfo.sid_data.cu_model,
+						       (int)curr_devinfo.sid_data.dev_type,
+						       (int)curr_devinfo.sid_data.dev_model,
+						       (curr_devinfo.status&DEVSTAT_DEVICE_OWNED) ? "yes":"no",
+						       (chandev_get_irqinfo_by_irq(curr_irq) ? "yes":"no"));
+						       
+						       
+				}
+					
+			}
 
+		}
+	}
+	if(chandev_parms_head)
+	{
+		chandev_parms      *curr_parms;
 
-	chandev_printf(chan_exit,"Channels enabled for detection\n");      
-	chandev_printf(chan_exit,"chan_type   cu_type       cu_model    max_port_no\n");
-	chandev_printf(chan_exit,"=================================================\n");
-	for(curr_model=chandev_models_head;curr_model!=NULL;
-	    curr_model=curr_model->next)
-	{
-		chandev_printf(chan_exit,"0x%02x       0x%04x         0x%02x        %d\n",
-		       curr_model->chan_type,(int)curr_model->cu_type,
-		       (int)curr_model->cu_model,(int)curr_model->max_port_no);         
-	}
-	
-        chandev_printf(chan_exit,"%s",chandev_keydescript);
-	chandev_printf(chan_exit,"No auto devno ranges\n");
-	chandev_printf(chan_exit,"   From        To   \n");
-	chandev_printf(chan_exit,"====================\n");
-	for(curr_noauto=chandev_noauto_head;curr_noauto!=NULL;
-		    curr_noauto=curr_noauto->next)
-	{
-		chandev_printf(chan_exit,"0x%4x       0x%4x\n",
-			       curr_noauto->lo_devno,
-			       curr_noauto->hi_devno);
-	}
-	chandev_printf(chan_exit,"\nForced devices\n");
-	chandev_printf(chan_exit,"chan_type defif_num read_devno write_devno port_no ip_cksum hw_stats\n");
-	chandev_printf(chan_exit,"====================================================================\n");
-	for(curr_force=chandev_force_head;curr_force!=NULL;
-		    curr_force=curr_force->next)
-	{
-	chandev_printf(chan_exit,"0x%2x   %d  0x%4x 0x%4x  %4d    %1d   %1d\n",
-		       curr_force->chan_type,curr_force->devif_num,
-		       curr_force->read_devno,curr_force->write_devno,
-		       curr_force->port_no,curr_force->do_ip_checksumming,
-		       curr_force->use_hw_stats);
+		chandev_printf(chan_exit,"\n driver specific parameters\n");
+		chandev_printf(chan_exit,"chan    driver\n");
+		chandev_printf(chan_exit,"type  parameters\n");
+		chandev_printf(chan_exit,"=============================================================================\n");
+		for_each(curr_parms,chandev_parms_head)
+		{
+			chandev_printf(chan_exit,"0x%02x    %s\n",
+				       curr_parms->chan_type,
+				       curr_parms->parmstr);
+		}
 	}
+ chan_error:
 	*eof=TRUE;
  chan_exit:
 	if(currlen>length) {
@@ -700,6 +2552,7 @@
                  */
 		currlen-=splen;
 	}
+	chandev_unlock();
 	return(currlen);
 }
 
@@ -709,7 +2562,8 @@
 {
 	int         rc;
 	char        *buff;
-
+	
+	chandev_read_conf_if_necessary();
 	buff=vmalloc(count+1);
 	if(buff)
 	{
@@ -741,19 +2595,93 @@
 
 
 #endif
-static int __init chandev_init(void)
+#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
+static  
+#endif
+int __init chandev_init(void)
 {
-	chandev_init_default_models();
+	if(!chandev_initialised)
+	{
+		chandev_parse_args();
+		chandev_init_default_models();
 #if CONFIG_PROC_FS
-	chandev_create_proc();
+		chandev_create_proc();
+#endif
+		chandev_msck_task_tq.routine=
+#if CHANDEV_USE_KERNEL_THREADS
+		chandev_start_msck_thread;
+#else
+		chandev_msck_task;
 #endif
+#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
+		INIT_LIST_HEAD(&chandev_msck_task_tq.list);
+		chandev_msck_task_tq.sync=0;
+#endif
+		chandev_msck_task_tq.data=NULL;
+		chandev_last_startmsck_list_update=chandev_last_machine_check=jiffies-HZ;
+		atomic_set(&chandev_msck_thread_lock,1);
+		chandev_lock_owner=CHANDEV_INVALID_LOCK_OWNER;
+		chandev_lock_cnt=0;
+		spin_lock_init(&chandev_spinlock);
+		spin_lock_init(&chandev_not_oper_spinlock);
+		chandev_initialised=TRUE;
+		atomic_set(&chandev_new_msck,FALSE);
+	}
 	return(0);
 }
+#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
 __initcall(chandev_init);
+#endif
 
+int chandev_register_and_probe(chandev_probefunc probefunc,
+			       chandev_shutdownfunc shutdownfunc,
+			       chandev_reoperfunc reoperfunc,
+			       chandev_type chan_type)
+{
+	chandev_probelist *new_probe;
+	/* Avoid chicked & egg situations where we may be called before we */
+	/* are initialised. */
 
+	chandev_interrupt_check();
+	if(!chandev_initialised)
+		chandev_init();
+	chandev_read_conf_if_necessary();
+	if((new_probe=chandev_alloc(sizeof(chandev_probelist))))
+	{
+		new_probe->probefunc=probefunc;
+		new_probe->shutdownfunc=shutdownfunc;
+		new_probe->reoperfunc=reoperfunc;
+		new_probe->chan_type=chan_type;
+		new_probe->devices_found=0;
+		chandev_add_to_list((list **)&chandev_probelist_head,new_probe);
+		chandev_probe();
+	}
+	return(new_probe ? new_probe->devices_found:0);
+}
 
-
-
-
+void chandev_unregister(chandev_probefunc probefunc,int call_shutdown)
+{
+	chandev_probelist *curr_probe=NULL;
+	chandev_activelist *curr_device;
+	
+	chandev_interrupt_check();
+	chandev_lock();
+	for_each(curr_probe,chandev_probelist_head)
+	{
+		if(curr_probe->probefunc==probefunc)
+		{
+			for_each(curr_device,chandev_activelist_head)
+				if(curr_device->probefunc==probefunc)
+				{
+					if(call_shutdown)
+					{
+						chandev_shutdown(curr_device);
+					}
+				}
+			chandev_free_listmember((list **)&chandev_probelist_head,
+						(list *)curr_probe);
+		}
+	}
+	chandev_unlock();
+}
 

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)