patch-2.3.99-pre8 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.3.99-pre7/linux/drivers/s390/misc/chandev.c linux/drivers/s390/misc/chandev.c
@@ -0,0 +1,759 @@
+/*
+ *  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)
+ */
+
+#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>
+#include <linux/init.h>
+#include <linux/unistd.h>
+#include <asm/chandev.h>
+#include <linux/proc_fs.h>
+#include <linux/vmalloc.h>
+
+static chandev_model_info *chandev_models_head=NULL;
+static chandev *chandev_head=NULL;
+static chandev_noauto_range *chandev_noauto_head=NULL;
+static chandev_force *chandev_force_head=NULL;
+static chandev_probelist *chandev_probelist_head=NULL;
+static int use_devno_names=FALSE;
+static int chandev_conf_read=FALSE;
+
+static void *chandev_alloc_listmember(list **listhead,size_t size)
+{
+	void *newmember=kmalloc(GFP_KERNEL,size);
+	if(newmember)
+		add_to_list(listhead,newmember);
+	return(newmember);
+}
+
+void chandev_free_listmember(list **listhead,list *member)
+{
+	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);
+}
+
+void chandev_free_all(list **listhead)
+{
+	while(*listhead)
+		chandev_free_listmember(listhead,*listhead);
+}
+
+void chandev_add_model(chandev_type chan_type,u16 cu_type,u8 cu_model,u8 max_port_no)
+{
+	chandev_model_info *newmodel;
+
+	if((newmodel=chandev_alloc_listmember(
+		(list **)&chandev_models_head,sizeof(chandev_model_info))))
+	{
+		newmodel->chan_type=chan_type;
+		newmodel->cu_type=cu_type;
+		newmodel->cu_model=cu_model;
+		newmodel->max_port_no=max_port_no;
+	}
+}
+
+
+void chandev_remove(chandev *member)
+{
+	chandev_free_listmember((list **)&chandev_head,(list *)member);
+}
+
+
+void chandev_remove_all(void)
+{
+	chandev_free_all((list **)&chandev_head);
+}
+
+void chandev_remove_model(chandev_model_info *model)
+{
+	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);
+}
+
+void chandev_remove_all_models(void)
+{
+	while(chandev_models_head)
+		chandev_remove_model(chandev_models_head);
+}
+
+void chandev_del_model(u16 cu_type,u8 cu_model)
+{
+	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);			
+}
+
+static void chandev_init_default_models(void)
+{
+	/* 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);
+
+	/* 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_add(dev_info_t  *newdevinfo,chandev_model_info *newmodelinfo)
+{
+	chandev *new_chandev;
+
+	if((new_chandev=chandev_alloc_listmember(
+		(list **)&chandev_head,sizeof(chandev))))
+	{
+		new_chandev->model_info=newmodelinfo;
+		new_chandev->devno=newdevinfo->devno;
+		new_chandev->irq=newdevinfo->irq;
+	}
+}
+
+
+void chandev_collect_devices(void)
+{
+	int curr_irq,loopcnt=0,err;
+	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;
+		}
+		if((err=get_dev_info_by_irq(curr_irq,&curr_devinfo)))
+		{
+			printk("chandev_collect_devices get_dev_info_by_irq reported err=%X on irq %d\n"
+			       "should not happen\n",err,curr_irq);
+			continue;
+		}
+		for(curr_model=chandev_models_head;curr_model!=NULL;
+		    curr_model=curr_model->next)
+		{
+			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);
+		}
+	}
+}
+
+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)
+
+{
+	chandev_force *new_chandev_force;
+
+	if((new_chandev_force=chandev_alloc_listmember(
+		(list **)&chandev_force_head,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_no=port_no;
+		new_chandev_force->do_ip_checksumming=do_ip_checksumming;
+		new_chandev_force->use_hw_stats=use_hw_stats;
+	}
+}
+
+void chandev_del_force(u16 read_devno)
+{
+	chandev_force *curr_force;
+	for(curr_force=chandev_force_head;curr_force!=NULL;
+		    curr_force=curr_force->next)
+	{
+		if(curr_force->read_devno==read_devno)
+			chandev_free_listmember((list **)&chandev_force_head,
+						(list *)curr_force);
+	}
+}
+
+void chandev_pack_args(char *str)
+{
+	char *newstr=str;
+	while(*str)
+	{
+		if(isspace(*str))
+			str++;
+		else
+			*newstr++=*str++;
+	}
+	*newstr=0;
+}
+
+typedef enum
+{ 
+	isnull=0,
+	isstr=1,
+	isnum=2,
+	iscomma=4,
+} chandev_strval;
+
+chandev_strval chandev_strcmp(char *teststr,char **str,long *endlong)
+{
+	char *cur=*str;
+	chandev_strval  retval=isnull;
+
+	int len=strlen(teststr);
+	if(strncmp(teststr,*str,len)==0)
+	{
+		*str+=len;
+		retval=isstr;
+		*endlong=simple_strtol(cur,str,0);
+		if(cur!=*str)
+			retval|=isnum;
+		if(**str==',')
+			retval|=iscomma;
+	}
+	return(retval);
+}
+
+static char *argstrs[]=
+{
+	"noauto",
+	"lcs",
+	"ctc",
+	"escon",
+	"del_force",
+	"use_devno_names"
+	"dont_use_devno_names",
+	"add_model"
+	"del_model"
+	"del_all_models"
+};
+
+typedef enum
+{
+	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,
+	last_stridx,
+} chandev_str_enum;
+
+void chandev_add_noauto(u16 lo_devno,u16 hi_devno)
+{
+	chandev_noauto_range *new_range;
+
+	if((new_range=chandev_alloc_listmember(
+		(list **)&chandev_noauto_head,sizeof(chandev_noauto_range))))
+	{
+		new_range->lo_devno=lo_devno;
+		new_range->hi_devno=hi_devno;
+	}
+}
+
+static char chandev_keydescript[]=
+"chan_type key bitfield\nctc=0x1,escon=0x2,lcs=0x4,lcs=0x4,osad=0x8,claw=0x16\n";
+
+static void chandev_print_args(void)
+{
+	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");
+}
+
+
+static int chandev_setup(char *str)
+{
+	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];
+	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)
+	{
+		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]);
+			
+			}
+			break;
+		case (ctc_stridx*stridx_mult)|isnum|iscomma:
+		case (escon_stridx*stridx_mult)|isnum|iscomma:
+		case (lcs_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;
+			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)
+			{
+				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)
+				goto BadArgs;
+			chandev_del_model(ints[1],ints[2]);
+			break;
+		case del_all_models_stridx*stridx_mult:
+			chandev_remove_all_models();
+			break;
+		default:
+			goto BadArgs;
+		}		
+	}
+	return(1);
+ BadArgs:
+	chandev_print_args();
+	return(0);
+}
+
+__setup("chandev=",chandev_setup);
+
+int chandev_doprobe(chandev_force *force,chandev *read_chandev,
+chandev *write_chandev)
+{
+	chandev_probelist *probe;
+	chandev_model_info *model_info;
+	chandev_probeinfo probeinfo;
+	int  retval=-1,hint=-1;
+
+	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)
+{
+	chandev *read_chandev,*write_chandev,*curr_chandev;
+	chandev_force *curr_force;
+	chandev_noauto_range *curr_noauto;
+
+	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)
+	{
+		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_remove_all();
+}
+
+int chandev_do_setup(char *buff,int size)
+{
+	int curr,startline=0,comment=FALSE,newline=FALSE,oldnewline=TRUE;
+	int rc=1;
+
+	buff[size]=0;
+	for(curr=0;curr<=size;curr++)
+	{
+		if(buff[curr]=='#')
+		{
+			comment=TRUE;
+			newline=FALSE;
+		}
+		else if(buff[curr]==10||buff[curr]==13||buff[curr]==0)
+		{
+			buff[curr]=0;
+			comment=FALSE;
+			newline=TRUE;
+		}
+		if(comment==FALSE&&curr>startline
+		   &&((oldnewline==TRUE&&newline==FALSE)||curr==size))
+		{
+			if((rc=chandev_setup(&buff[startline]))==0)
+				break;
+			startline=curr+1;
+		}
+	        oldnewline=newline;
+	}
+	return(rc);
+}
+void chandev_read_conf(void)
+{
+#define CHANDEV_FILE "/etc/chandev.conf"
+	struct stat statbuf;
+	char        *buff;
+	int         curr,left,len,fd;
+
+	chandev_conf_read=TRUE;
+	set_fs(KERNEL_DS);
+	if(stat(CHANDEV_FILE,&statbuf)==0)
+	{
+		set_fs(USER_DS);
+		buff=vmalloc(statbuf.st_size+1);
+		if(buff)
+		{
+			set_fs(KERNEL_DS);
+			if((fd=open(CHANDEV_FILE,O_RDONLY,0))!=-1)
+			{
+				curr=0;
+				left=statbuf.st_size;
+				while((len=read(fd,&buff[curr],left))>0)
+				{
+					curr+=len;
+					left-=len;
+				}
+				close(fd);
+			}
+			set_fs(USER_DS);
+			chandev_do_setup(buff,statbuf.st_size);
+			vfree(buff);
+		}
+	}
+	set_fs(USER_DS);
+}
+
+void chandev_register_and_probe(chandev_probefunc probefunc,chandev_type chan_type)
+{
+	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);                \
+spoffset+=splen;                             \
+if(spoffset>offset) {                        \
+       spbuff+=splen;                        \
+       currlen+=splen;                       \
+}                                            \
+if(currlen>=length)                          \
+       goto exitchan;
+
+
+
+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;
+	off_t  spoffset=0;
+	chandev_model_info *curr_model;
+	chandev_noauto_range *curr_noauto;
+	chandev_force *curr_force;
+
+
+	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);
+	}
+	*eof=TRUE;
+ chan_exit:
+	if(currlen>length) {
+		/* rewind to previous printf so that we are correctly
+		 * aligned if we get called to print another page.
+                 */
+		currlen-=splen;
+	}
+	return(currlen);
+}
+
+
+static int chandev_write_proc(struct file *file, const char *buffer,
+			   unsigned long count, void *data)
+{
+	int         rc;
+	char        *buff;
+
+	buff=vmalloc(count+1);
+	if(buff)
+	{
+		rc = copy_from_user(buff,buffer,count);
+		if (rc)
+			goto chandev_write_exit;
+		chandev_do_setup(buff,count);
+		rc=count;
+	chandev_write_exit:
+		vfree(buff);
+		return rc;
+	}
+	else
+		return -ENOMEM;
+	return(0);
+}
+
+static void __init chandev_create_proc(void)
+{
+	struct proc_dir_entry *dir_entry=
+		create_proc_entry("chandev",0644,
+				  &proc_root);
+	if(dir_entry)
+	{
+		dir_entry->read_proc=&chandev_read_proc;
+		dir_entry->write_proc=&chandev_write_proc;
+	}
+}
+
+
+#endif
+static int __init chandev_init(void)
+{
+	chandev_init_default_models();
+#if CONFIG_PROC_FS
+	chandev_create_proc();
+#endif
+	return(0);
+}
+__initcall(chandev_init);
+
+
+
+
+
+
+

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