patch-2.2.19 linux/drivers/isdn/avmb1/capi.c

Next file: linux/drivers/isdn/avmb1/capicmd.h
Previous file: linux/drivers/isdn/avmb1/c4.c
Back to the patch index
Back to the overall index

diff -u --new-file --recursive --exclude-from /usr/src/exclude v2.2.18/drivers/isdn/avmb1/capi.c linux/drivers/isdn/avmb1/capi.c
@@ -1,11 +1,120 @@
 /*
- * $Id: capi.c,v 1.23 2000/02/26 01:00:53 keil Exp $
+ * $Id: capi.c,v 1.44.6.5 2001/02/13 11:43:29 kai Exp $
  *
  * CAPI 2.0 Interface for Linux
  *
  * Copyright 1996 by Carsten Paeth (calle@calle.in-berlin.de)
  *
  * $Log: capi.c,v $
+ * Revision 1.44.6.5  2001/02/13 11:43:29  kai
+ * more compatility changes for 2.2.19
+ *
+ * Revision 1.44.6.4  2001/02/10 14:41:20  kai
+ * Changes from kernel tree
+ *
+ * Revision 1.44.6.3  2000/12/17 22:45:08  kai
+ * That's hopefully it for test13-4
+ *
+ * Revision 1.44.6.2  2000/12/14 23:04:12  kai
+ * Makefile changes and the like for 2.4.0-test13-pre1
+ * No compatiblity code for older kernels yet, but note the branch
+ *
+ * Revision 1.45  2000/12/02 19:47:29  kai
+ * Change the Makefiles to new style.
+ * There may be problems there that I missed, so this shouldn't go into
+ * an offical kernel any time soon.
+ * However, if I didn't commit it, we wouldn't find the bugs...
+ *
+ * Revision 1.44  2000/11/25 17:00:59  kai
+ * compatibility cleanup - final part for the time being
+ *
+ * Revision 1.43  2000/11/23 20:45:14  kai
+ * fixed module_init/exit stuff
+ * Note: compiled-in kernel doesn't work pre 2.2.18 anymore.
+ *
+ * Revision 1.42  2000/11/19 17:03:55  kai
+ * compatibility cleanup - part 5
+ *
+ * Revision 1.41  2000/11/01 14:05:02  calle
+ * - use module_init/module_exit from linux/init.h.
+ * - all static struct variables are initialized with "membername:" now.
+ * - avm_cs.c, let it work with newer pcmcia-cs.
+ *
+ * Revision 1.40  2000/10/24 15:15:04  calle
+ * Workaround: pppd calls restoretty before reseting the ldisc and
+ *   ldisc "ppp_sync" didn't support this. So we call n_tty_ioctl
+ *   in the driver ioctl function. (remember: driver ioctl function is
+ *   only called if ldisc ioctl function did not handle the call)
+ *
+ * Revision 1.39  2000/07/24 13:42:50  calle
+ * - lock_kernel/unlock_kernel for _release functions. (from 2.4)
+ *
+ * Revision 1.38  2000/07/24 08:49:09  calle
+ * - Bugfix: capiminor_del_all_ack completely wrong :-(
+ *
+ * Revision 1.37  2000/07/20 10:22:27  calle
+ * - Made procfs function cleaner and removed variable "begin".
+ *
+ * Revision 1.36  2000/06/29 13:59:35  calle
+ * - call to devfs_register was wrong
+ *
+ * Revision 1.35  2000/06/19 15:11:24  keil
+ * avoid use of freed structs
+ * changes from 2.4.0-ac21
+ *
+ * Revision 1.34  2000/06/18 16:09:54  keil
+ * more changes for 2.4
+ *
+ * Revision 1.33  2000/05/18 16:35:43  calle
+ * Uaaahh. Bad memory leak fixed.
+ *
+ * Revision 1.32  2000/04/21 12:38:42  calle
+ * Bugfix: error in proc_ functions, begin-off => off-begin
+ *
+ * Revision 1.31  2000/04/03 13:29:24  calle
+ * make Tim Waugh happy (module unload races in 2.3.99-pre3).
+ * no real problem there, but now it is much cleaner ...
+ *
+ * Revision 1.30  2000/03/19 12:31:36  calle
+ * PPP over CAPI raw driver disabled for now, ppp_generic has been changed.
+ *
+ * Revision 1.29  2000/03/13 17:48:13  calle
+ * removed unused variable.
+ *
+ * Revision 1.28  2000/03/08 17:06:33  calle
+ * - changes for devfs and 2.3.49
+ * - capifs now configurable (no need with devfs)
+ * - New Middleware ioctl CAPI_NCCI_GETUNIT
+ * - Middleware again tested with 2.2.14 and 2.3.49 (with and without devfs)
+ *
+ * Revision 1.27  2000/03/06 18:00:23  calle
+ * - Middleware extention now working with 2.3.49 (capifs).
+ * - Fixed typos in debug section of capi.c
+ * - Bugfix: Makefile corrected for b1pcmcia.c
+ *
+ * Revision 1.26  2000/03/03 16:48:38  calle
+ * - Added CAPI2.0 Middleware support (CONFIG_ISDN_CAPI)
+ *   It is now possible to create a connection with a CAPI2.0 applikation
+ *   and than to handle the data connection from /dev/capi/ (capifs) and also
+ *   using async or sync PPP on this connection.
+ *   The two major device number 190 and 191 are not confirmed yet,
+ *   but I want to save the code in cvs, before I go on.
+ *
+ * Revision 1.25  2000/03/03 16:37:11  kai
+ * incorporated some cosmetic changes from the official kernel tree back
+ * into CVS
+ *
+ * Revision 1.24  2000/03/03 15:50:42  calle
+ * - kernel CAPI:
+ *   - Changed parameter "param" in capi_signal from __u32 to void *.
+ *   - rewrote notifier handling in kcapi.c
+ *   - new notifier NCCI_UP and NCCI_DOWN
+ * - User CAPI:
+ *   - /dev/capi20 is now a cloning device.
+ *   - middleware extentions prepared.
+ * - capidrv.c
+ *   - locking of list operations and module count updates.
+ *
  * Revision 1.23  2000/02/26 01:00:53  keil
  * changes from 2.3.47
  *
@@ -114,96 +223,724 @@
  *
  */
 
+#include <linux/config.h>
 #include <linux/module.h>
 #include <linux/errno.h>
 #include <linux/kernel.h>
 #include <linux/major.h>
 #include <linux/sched.h>
-#include <linux/malloc.h>
+#include <linux/slab.h>
 #include <linux/fcntl.h>
 #include <linux/fs.h>
 #include <linux/signal.h>
 #include <linux/mm.h>
+#include <linux/smp_lock.h>
 #include <linux/timer.h>
 #include <linux/wait.h>
+#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE
+#include <linux/tty.h>
+#ifdef CONFIG_PPP
+#include <linux/netdevice.h>
+#include <linux/ppp_defs.h>
+#include <linux/if_ppp.h>
+#undef CAPI_PPP_ON_RAW_DEVICE
+#endif /* CONFIG_PPP */
+#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */
 #include <linux/skbuff.h>
 #include <linux/proc_fs.h>
 #include <linux/poll.h>
 #include <linux/capi.h>
 #include <linux/kernelcapi.h>
-
+#include <linux/init.h>
 #include "capiutil.h"
 #include "capicmd.h"
-#include "capidev.h"
+#if defined(CONFIG_ISDN_CAPI_CAPIFS) || defined(CONFIG_ISDN_CAPI_CAPIFS_MODULE)
+#include "capifs.h"
+#endif
+
+static char *revision = "$Revision: 1.44.6.5 $";
 
 MODULE_AUTHOR("Carsten Paeth (calle@calle.in-berlin.de)");
 
+#undef _DEBUG_REFCOUNT		/* alloc/free and open/close debug */
+#undef _DEBUG_TTYFUNCS		/* call to tty_driver */
+#undef _DEBUG_DATAFLOW		/* data flow */
+
 /* -------- driver information -------------------------------------- */
 
 int capi_major = 68;		/* allocated */
+#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE
+int capi_rawmajor = 190;
+int capi_ttymajor = 191;
+#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */
 
 MODULE_PARM(capi_major, "i");
+#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE
+MODULE_PARM(capi_rawmajor, "i");
+MODULE_PARM(capi_ttymajor, "i");
+#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */
+
+/* -------- defines ------------------------------------------------- */
+
+#define CAPINC_MAX_RECVQUEUE	10
+#define CAPINC_MAX_SENDQUEUE	10
+#define CAPI_MAX_BLKSIZE	2048
+
+/* -------- data structures ----------------------------------------- */
+
+struct capidev;
+struct capincci;
+#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE
+struct capiminor;
+
+struct capiminor {
+	struct capiminor *next;
+	struct capincci  *nccip;
+	unsigned int      minor;
+
+	__u16		 applid;
+	__u32		 ncci;
+	__u16		 datahandle;
+	__u16		 msgid;
+
+	struct file      *file;
+	struct tty_struct *tty;
+	int                ttyinstop;
+	int                ttyoutstop;
+	struct sk_buff    *ttyskb;
+	atomic_t           ttyopencount;
+
+	struct sk_buff_head inqueue;
+	int                 inbytes;
+	struct sk_buff_head outqueue;
+	int                 outbytes;
+
+	/* for raw device */
+	struct sk_buff_head recvqueue;
+	wait_queue_head_t recvwait;
+	wait_queue_head_t sendwait;
+	
+	/* transmit path */
+	struct datahandle_queue {
+		    struct datahandle_queue *next;
+		    __u16                    datahandle;
+	} *ackqueue;
+	int nack;
+
+};
+#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */
+
+struct capincci {
+	struct capincci *next;
+	__u32		 ncci;
+	struct capidev	*cdev;
+#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE
+	struct capiminor *minorp;
+#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */
+};
+
+struct capidev {
+	struct capidev *next;
+	struct file    *file;
+	__u16		applid;
+	__u16		errcode;
+	unsigned int    minor;
+	unsigned        userflags;
+
+	struct sk_buff_head recvqueue;
+	wait_queue_head_t recvwait;
+
+	/* Statistic */
+	unsigned long	nrecvctlpkt;
+	unsigned long	nrecvdatapkt;
+	unsigned long	nsentctlpkt;
+	unsigned long	nsentdatapkt;
+	
+	struct capincci *nccis;
+};
 
 /* -------- global variables ---------------------------------------- */
 
-static struct capidev capidevs[CAPI_MAXMINOR + 1];
-struct capi_interface *capifuncs;
+static struct capi_interface *capifuncs = 0;
+static struct capidev *capidev_openlist = 0;
+#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE
+static struct capiminor *minors = 0;
+#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */
 
-/* -------- function called by lower level -------------------------- */
 
-static void capi_signal(__u16 applid, __u32 minor)
+#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE
+/* -------- datahandles --------------------------------------------- */
+
+int capincci_add_ack(struct capiminor *mp, __u16 datahandle)
+{
+	struct datahandle_queue *n, **pp;
+
+	n = (struct datahandle_queue *)
+	kmalloc(sizeof(struct datahandle_queue), GFP_ATOMIC);
+	if (!n) {
+	   printk(KERN_ERR "capi: alloc datahandle failed\n");
+	   return -1;
+	}
+	n->next = 0;
+	n->datahandle = datahandle;
+	for (pp = &mp->ackqueue; *pp; pp = &(*pp)->next) ;
+	*pp = n;
+	mp->nack++;
+	return 0;
+}
+
+int capiminor_del_ack(struct capiminor *mp, __u16 datahandle)
+{
+	struct datahandle_queue **pp, *p;
+
+	for (pp = &mp->ackqueue; *pp; pp = &(*pp)->next) {
+ 		if ((*pp)->datahandle == datahandle) {
+			p = *pp;
+			*pp = (*pp)->next;
+		        kfree(p);
+			mp->nack--;
+			return 0;
+		}
+	}
+	return -1;
+}
+
+void capiminor_del_all_ack(struct capiminor *mp)
+{
+	struct datahandle_queue **pp, *p;
+
+	pp = &mp->ackqueue;
+	while (*pp) {
+		p = *pp;
+		*pp = (*pp)->next;
+		kfree(p);
+		mp->nack--;
+	}
+}
+
+
+/* -------- struct capiminor ---------------------------------------- */
+
+struct capiminor *capiminor_alloc(__u16 applid, __u32 ncci)
+{
+	struct capiminor *mp, **pp;
+        unsigned int minor = 0;
+
+	MOD_INC_USE_COUNT;
+	mp = (struct capiminor *)kmalloc(sizeof(struct capiminor), GFP_ATOMIC);
+	if (!mp) {
+		MOD_DEC_USE_COUNT;
+		printk(KERN_ERR "capi: can't alloc capiminor\n");
+		return 0;
+	}
+#ifdef _DEBUG_REFCOUNT
+	printk(KERN_DEBUG "capiminor_alloc %d\n", GET_USE_COUNT(&__this_module));
+#endif
+	memset(mp, 0, sizeof(struct capiminor));
+	mp->applid = applid;
+	mp->ncci = ncci;
+	mp->msgid = 0;
+	atomic_set(&mp->ttyopencount,0);
+
+	skb_queue_head_init(&mp->inqueue);
+	skb_queue_head_init(&mp->outqueue);
+
+	skb_queue_head_init(&mp->recvqueue);
+	init_waitqueue_head(&mp->recvwait);
+	init_waitqueue_head(&mp->sendwait);
+
+	for (pp = &minors; *pp; pp = &(*pp)->next) {
+		if ((*pp)->minor < minor)
+			continue;
+		if ((*pp)->minor > minor)
+			break;
+		minor++;
+	}
+	mp->minor = minor;
+	mp->next = *pp;
+	*pp = mp;
+	return mp;
+}
+
+void capiminor_free(struct capiminor *mp)
+{
+	struct capiminor **pp;
+	struct sk_buff *skb;
+
+	pp = &minors;
+	while (*pp) {
+		if (*pp == mp) {
+			*pp = (*pp)->next;
+			if (mp->ttyskb) kfree_skb(mp->ttyskb);
+			mp->ttyskb = 0;
+			while ((skb = skb_dequeue(&mp->recvqueue)) != 0)
+				kfree_skb(skb);
+			while ((skb = skb_dequeue(&mp->inqueue)) != 0)
+				kfree_skb(skb);
+			while ((skb = skb_dequeue(&mp->outqueue)) != 0)
+				kfree_skb(skb);
+			capiminor_del_all_ack(mp);
+			kfree(mp);
+			MOD_DEC_USE_COUNT;
+#ifdef _DEBUG_REFCOUNT
+			printk(KERN_DEBUG "capiminor_free %d\n", GET_USE_COUNT(&__this_module));
+#endif
+			return;
+		} else {
+			pp = &(*pp)->next;
+		}
+	}
+}
+
+struct capiminor *capiminor_find(unsigned int minor)
+{
+	struct capiminor *p;
+	for (p = minors; p && p->minor != minor; p = p->next)
+		;
+	return p;
+}
+#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */
+
+/* -------- struct capincci ----------------------------------------- */
+
+static struct capincci *capincci_alloc(struct capidev *cdev, __u32 ncci)
+{
+	struct capincci *np, **pp;
+#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE
+	struct capiminor *mp = 0;
+	kdev_t kdev;
+#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */
+
+	np = (struct capincci *)kmalloc(sizeof(struct capincci), GFP_ATOMIC);
+	if (!np)
+		return 0;
+	memset(np, 0, sizeof(struct capincci));
+	np->ncci = ncci;
+	np->cdev = cdev;
+	for (pp=&cdev->nccis; *pp; pp = &(*pp)->next)
+		;
+	*pp = np;
+#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE
+	mp = 0;
+	if (cdev->userflags & CAPIFLAG_HIGHJACKING)
+		mp = np->minorp = capiminor_alloc(cdev->applid, ncci);
+	if (mp) {
+		mp->nccip = np;
+#ifdef _DEBUG_REFCOUNT
+		printk(KERN_DEBUG "set mp->nccip\n");
+#endif
+#if defined(CONFIG_ISDN_CAPI_CAPIFS) || defined(CONFIG_ISDN_CAPI_CAPIFS_MODULE)
+		kdev = MKDEV(capi_rawmajor, mp->minor);
+		capifs_new_ncci('r', mp->minor, kdev);
+		kdev = MKDEV(capi_ttymajor, mp->minor);
+		capifs_new_ncci(0, mp->minor, kdev);
+#endif
+	}
+#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */
+        return np;
+}
+
+static void capincci_free(struct capidev *cdev, __u32 ncci)
+{
+	struct capincci *np, **pp;
+#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE
+	struct capiminor *mp;
+#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */
+
+	pp=&cdev->nccis;
+	while (*pp) {
+		np = *pp;
+		if (ncci == 0xffffffff || np->ncci == ncci) {
+			*pp = (*pp)->next;
+#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE
+			if ((mp = np->minorp) != 0) {
+#if defined(CONFIG_ISDN_CAPI_CAPIFS) || defined(CONFIG_ISDN_CAPI_CAPIFS_MODULE)
+				capifs_free_ncci('r', mp->minor);
+				capifs_free_ncci(0, mp->minor);
+#endif
+				if (mp->tty) {
+					mp->nccip = 0;
+#ifdef _DEBUG_REFCOUNT
+					printk(KERN_DEBUG "reset mp->nccip\n");
+#endif
+					tty_hangup(mp->tty);
+				} else if (mp->file) {
+					mp->nccip = 0;
+#ifdef _DEBUG_REFCOUNT
+					printk(KERN_DEBUG "reset mp->nccip\n");
+#endif
+					wake_up_interruptible(&mp->recvwait);
+					wake_up_interruptible(&mp->sendwait);
+				} else {
+					capiminor_free(mp);
+				}
+			}
+#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */
+			kfree(np);
+			if (*pp == 0) return;
+		} else {
+			pp = &(*pp)->next;
+		}
+	}
+}
+
+struct capincci *capincci_find(struct capidev *cdev, __u32 ncci)
+{
+	struct capincci *p;
+
+	for (p=cdev->nccis; p ; p = p->next) {
+		if (p->ncci == ncci)
+			break;
+	}
+	return p;
+}
+
+/* -------- struct capidev ------------------------------------------ */
+
+static struct capidev *capidev_alloc(struct file *file)
 {
 	struct capidev *cdev;
+	struct capidev **pp;
+
+	cdev = (struct capidev *)kmalloc(sizeof(struct capidev), GFP_KERNEL);
+	if (!cdev)
+		return 0;
+	memset(cdev, 0, sizeof(struct capidev));
+	cdev->file = file;
+	cdev->minor = MINOR(file->f_dentry->d_inode->i_rdev);
+
+	skb_queue_head_init(&cdev->recvqueue);
+	init_waitqueue_head(&cdev->recvwait);
+	pp=&capidev_openlist;
+	while (*pp) pp = &(*pp)->next;
+	*pp = cdev;
+        return cdev;
+}
+
+static void capidev_free(struct capidev *cdev)
+{
+	struct capidev **pp;
+	struct sk_buff *skb;
+
+	if (cdev->applid)
+		(*capifuncs->capi_release) (cdev->applid);
+	cdev->applid = 0;
+
+	while ((skb = skb_dequeue(&cdev->recvqueue)) != 0) {
+		kfree_skb(skb);
+	}
+	
+	pp=&capidev_openlist;
+	while (*pp && *pp != cdev) pp = &(*pp)->next;
+	if (*pp)
+		*pp = cdev->next;
+
+	kfree(cdev);
+}
+
+static struct capidev *capidev_find(__u16 applid)
+{
+	struct capidev *p;
+	for (p=capidev_openlist; p; p = p->next) {
+		if (p->applid == applid)
+			break;
+	}
+	return p;
+}
+
+#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE
+/* -------- handle data queue --------------------------------------- */
+
+struct sk_buff *
+gen_data_b3_resp_for(struct capiminor *mp, struct sk_buff *skb)
+{
+	struct sk_buff *nskb;
+	nskb = alloc_skb(CAPI_DATA_B3_RESP_LEN, GFP_ATOMIC);
+	if (nskb) {
+		__u16 datahandle = CAPIMSG_U16(skb->data,CAPIMSG_BASELEN+4+4+2);
+		unsigned char *s = skb_put(nskb, CAPI_DATA_B3_RESP_LEN);
+		capimsg_setu16(s, 0, CAPI_DATA_B3_RESP_LEN);
+		capimsg_setu16(s, 2, mp->applid);
+		capimsg_setu8 (s, 4, CAPI_DATA_B3);
+		capimsg_setu8 (s, 5, CAPI_RESP);
+		capimsg_setu16(s, 6, mp->msgid++);
+		capimsg_setu32(s, 8, mp->ncci);
+		capimsg_setu16(s, 12, datahandle);
+	}
+	return nskb;
+}
+
+int handle_recv_skb(struct capiminor *mp, struct sk_buff *skb)
+{
+	struct sk_buff *nskb;
+	unsigned int datalen;
+	__u16 errcode, datahandle;
+
+	datalen = skb->len - CAPIMSG_LEN(skb->data);
+	if (mp->tty) {
+		if (mp->tty->ldisc.receive_buf == 0) {
+			printk(KERN_ERR "capi: ldisc has no receive_buf function\n");
+			return -1;
+		}
+		if (mp->ttyinstop) {
+#if defined(_DEBUG_DATAFLOW) || defined(_DEBUG_TTYFUNCS)
+			printk(KERN_DEBUG "capi: recv tty throttled\n");
+#endif
+			return -1;
+		}
+		if (mp->tty->ldisc.receive_room &&
+		    mp->tty->ldisc.receive_room(mp->tty) < datalen) {
+#if defined(_DEBUG_DATAFLOW) || defined(_DEBUG_TTYFUNCS)
+			printk(KERN_DEBUG "capi: no room in tty\n");
+#endif
+			return -1;
+		}
+		if ((nskb = gen_data_b3_resp_for(mp, skb)) == 0) {
+			printk(KERN_ERR "capi: gen_data_b3_resp failed\n");
+			return -1;
+		}
+		datahandle = CAPIMSG_U16(skb->data,CAPIMSG_BASELEN+4);
+		errcode = (*capifuncs->capi_put_message)(mp->applid, nskb);
+		if (errcode != CAPI_NOERROR) {
+			printk(KERN_ERR "capi: send DATA_B3_RESP failed=%x\n",
+					errcode);
+			kfree_skb(nskb);
+			return -1;
+		}
+		(void)skb_pull(skb, CAPIMSG_LEN(skb->data));
+#ifdef _DEBUG_DATAFLOW
+		printk(KERN_DEBUG "capi: DATA_B3_RESP %u len=%d => ldisc\n",
+					datahandle, skb->len);
+#endif
+		mp->tty->ldisc.receive_buf(mp->tty, skb->data, 0, skb->len);
+		kfree_skb(skb);
+		return 0;
+
+	} else if (mp->file) {
+		if (skb_queue_len(&mp->recvqueue) > CAPINC_MAX_RECVQUEUE) {
+#if defined(_DEBUG_DATAFLOW) || defined(_DEBUG_TTYFUNCS)
+			printk(KERN_DEBUG "capi: no room in raw queue\n");
+#endif
+			return -1;
+		}
+		if ((nskb = gen_data_b3_resp_for(mp, skb)) == 0) {
+			printk(KERN_ERR "capi: gen_data_b3_resp failed\n");
+			return -1;
+		}
+		datahandle = CAPIMSG_U16(skb->data,CAPIMSG_BASELEN+4);
+		errcode = (*capifuncs->capi_put_message)(mp->applid, nskb);
+		if (errcode != CAPI_NOERROR) {
+			printk(KERN_ERR "capi: send DATA_B3_RESP failed=%x\n",
+					errcode);
+			kfree_skb(nskb);
+			return -1;
+		}
+		(void)skb_pull(skb, CAPIMSG_LEN(skb->data));
+#ifdef _DEBUG_DATAFLOW
+		printk(KERN_DEBUG "capi: DATA_B3_RESP %u len=%d => raw\n",
+					datahandle, skb->len);
+#endif
+		skb_queue_tail(&mp->recvqueue, skb);
+		wake_up_interruptible(&mp->recvwait);
+		return 0;
+	}
+#ifdef _DEBUG_DATAFLOW
+	printk(KERN_DEBUG "capi: currently no receiver\n");
+#endif
+	return -1;
+}
+
+void handle_minor_recv(struct capiminor *mp)
+{
+	struct sk_buff *skb;
+	while ((skb = skb_dequeue(&mp->inqueue)) != 0) {
+		unsigned int len = skb->len;
+		mp->inbytes -= len;
+		if (handle_recv_skb(mp, skb) < 0) {
+			skb_queue_head(&mp->inqueue, skb);
+			mp->inbytes += len;
+			return;
+		}
+	}
+}
+
+int handle_minor_send(struct capiminor *mp)
+{
+	struct sk_buff *skb;
+	__u16 len;
+	int count = 0;
+	__u16 errcode;
+	__u16 datahandle;
+
+	if (mp->tty && mp->ttyoutstop) {
+#if defined(_DEBUG_DATAFLOW) || defined(_DEBUG_TTYFUNCS)
+		printk(KERN_DEBUG "capi: send: tty stopped\n");
+#endif
+		return 0;
+	}
+
+	while ((skb = skb_dequeue(&mp->outqueue)) != 0) {
+		datahandle = mp->datahandle;
+		len = (__u16)skb->len;
+		skb_push(skb, CAPI_DATA_B3_REQ_LEN);
+		memset(skb->data, 0, CAPI_DATA_B3_REQ_LEN);
+		capimsg_setu16(skb->data, 0, CAPI_DATA_B3_REQ_LEN);
+		capimsg_setu16(skb->data, 2, mp->applid);
+		capimsg_setu8 (skb->data, 4, CAPI_DATA_B3);
+		capimsg_setu8 (skb->data, 5, CAPI_REQ);
+		capimsg_setu16(skb->data, 6, mp->msgid++);
+		capimsg_setu32(skb->data, 8, mp->ncci);	/* NCCI */
+		capimsg_setu32(skb->data, 12, (__u32) skb->data); /* Data32 */
+		capimsg_setu16(skb->data, 16, len);	/* Data length */
+		capimsg_setu16(skb->data, 18, datahandle);
+		capimsg_setu16(skb->data, 20, 0);	/* Flags */
+
+		if (capincci_add_ack(mp, datahandle) < 0) {
+			skb_pull(skb, CAPI_DATA_B3_REQ_LEN);
+			skb_queue_head(&mp->outqueue, skb);
+			return count;
+		}
+		errcode = (*capifuncs->capi_put_message) (mp->applid, skb);
+		if (errcode == CAPI_NOERROR) {
+			mp->datahandle++;
+			count++;
+			mp->outbytes -= len;
+#ifdef _DEBUG_DATAFLOW
+			printk(KERN_DEBUG "capi: DATA_B3_REQ %u len=%u\n",
+							datahandle, len);
+#endif
+			continue;
+		}
+		capiminor_del_ack(mp, datahandle);
+
+		if (errcode == CAPI_SENDQUEUEFULL) {
+			skb_pull(skb, CAPI_DATA_B3_REQ_LEN);
+			skb_queue_head(&mp->outqueue, skb);
+			break;
+		}
+
+		/* ups, drop packet */
+		printk(KERN_ERR "capi: put_message = %x\n", errcode);
+		mp->outbytes -= len;
+		kfree_skb(skb);
+	}
+	if (count)
+		wake_up_interruptible(&mp->sendwait);
+	return count;
+}
+
+#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */
+/* -------- function called by lower level -------------------------- */
+
+static void capi_signal(__u16 applid, void *param)
+{
+	struct capidev *cdev = (struct capidev *)param;
+#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE
+	struct capiminor *mp;
+	__u16 datahandle;
+#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */
+	struct capincci *np;
 	struct sk_buff *skb = 0;
+	__u32 ncci;
 
-	if (minor > CAPI_MAXMINOR || !capidevs[minor].is_registered) {
-		printk(KERN_ERR "BUG: capi_signal: illegal minor %d\n", minor);
+	(void) (*capifuncs->capi_get_message) (applid, &skb);
+	if (!skb) {
+		printk(KERN_ERR "BUG: capi_signal: no skb\n");
 		return;
 	}
-	cdev = &capidevs[minor];
-	(void) (*capifuncs->capi_get_message) (applid, &skb);
-	if (skb) {
-		skb_queue_tail(&cdev->recv_queue, skb);
-		wake_up_interruptible(&cdev->recv_wait);
+
+	if (CAPIMSG_COMMAND(skb->data) != CAPI_DATA_B3) {
+		skb_queue_tail(&cdev->recvqueue, skb);
+		wake_up_interruptible(&cdev->recvwait);
+		return;
+	}
+	ncci = CAPIMSG_CONTROL(skb->data);
+	for (np = cdev->nccis; np && np->ncci != ncci; np = np->next)
+		;
+	if (!np) {
+		printk(KERN_ERR "BUG: capi_signal: ncci not found\n");
+		skb_queue_tail(&cdev->recvqueue, skb);
+		wake_up_interruptible(&cdev->recvwait);
+		return;
+	}
+#ifndef CONFIG_ISDN_CAPI_MIDDLEWARE
+	skb_queue_tail(&cdev->recvqueue, skb);
+	wake_up_interruptible(&cdev->recvwait);
+#else /* CONFIG_ISDN_CAPI_MIDDLEWARE */
+	mp = np->minorp;
+	if (!mp) {
+		skb_queue_tail(&cdev->recvqueue, skb);
+		wake_up_interruptible(&cdev->recvwait);
+		return;
+	}
+
+
+	if (CAPIMSG_SUBCOMMAND(skb->data) == CAPI_IND) {
+		datahandle = CAPIMSG_U16(skb->data, CAPIMSG_BASELEN+4+4+2);
+#ifdef _DEBUG_DATAFLOW
+		printk(KERN_DEBUG "capi_signal: DATA_B3_IND %u len=%d\n",
+				datahandle, skb->len-CAPIMSG_LEN(skb->data));
+#endif
+		skb_queue_tail(&mp->inqueue, skb);
+		mp->inbytes += skb->len;
+		handle_minor_recv(mp);
+
+	} else if (CAPIMSG_SUBCOMMAND(skb->data) == CAPI_CONF) {
+
+		datahandle = CAPIMSG_U16(skb->data, CAPIMSG_BASELEN+4);
+#ifdef _DEBUG_DATAFLOW
+		printk(KERN_DEBUG "capi_signal: DATA_B3_CONF %u 0x%x\n",
+				datahandle,
+				CAPIMSG_U16(skb->data, CAPIMSG_BASELEN+4+2));
+#endif
+		kfree_skb(skb);
+		(void)capiminor_del_ack(mp, datahandle);
+		if (mp->tty) {
+			if (mp->tty->ldisc.write_wakeup)
+				mp->tty->ldisc.write_wakeup(mp->tty);
+		} else {
+			wake_up_interruptible(&mp->sendwait);
+		}
+		(void)handle_minor_send(mp);
+
 	} else {
-		printk(KERN_ERR "BUG: capi_signal: no skb\n");
+		/* ups, let capi application handle it :-) */
+		skb_queue_tail(&cdev->recvqueue, skb);
+		wake_up_interruptible(&cdev->recvwait);
 	}
+#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */
 }
 
-/* -------- file_operations ----------------------------------------- */
+/* -------- file_operations for capidev ----------------------------- */
 
-static long long capi_llseek(struct file *file,
-			     long long offset, int origin)
+static loff_t
+capi_llseek(struct file *file, loff_t offset, int origin)
 {
 	return -ESPIPE;
 }
 
-static ssize_t capi_read(struct file *file, char *buf,
-		      size_t count, loff_t *ppos)
+static ssize_t
+capi_read(struct file *file, char *buf, size_t count, loff_t *ppos)
 {
-        struct inode *inode = file->f_dentry->d_inode;
-	unsigned int minor = MINOR(inode->i_rdev);
-	struct capidev *cdev;
+	struct capidev *cdev = (struct capidev *)file->private_data;
 	struct sk_buff *skb;
 	int retval;
 	size_t copied;
 
-       if (ppos != &file->f_pos)
+	if (ppos != &file->f_pos)
 		return -ESPIPE;
 
-	if (!minor || minor > CAPI_MAXMINOR || !capidevs[minor].is_registered)
+	if (!cdev->applid)
 		return -ENODEV;
 
-	cdev = &capidevs[minor];
-
-	if ((skb = skb_dequeue(&cdev->recv_queue)) == 0) {
+	if ((skb = skb_dequeue(&cdev->recvqueue)) == 0) {
 
 		if (file->f_flags & O_NONBLOCK)
 			return -EAGAIN;
 
 		for (;;) {
-			interruptible_sleep_on(&cdev->recv_wait);
-			if ((skb = skb_dequeue(&cdev->recv_queue)) != 0)
+			interruptible_sleep_on(&cdev->recvwait);
+			if ((skb = skb_dequeue(&cdev->recvqueue)) != 0)
 				break;
 			if (signal_pending(current))
 				break;
@@ -212,63 +949,58 @@
 			return -ERESTARTNOHAND;
 	}
 	if (skb->len > count) {
-		skb_queue_head(&cdev->recv_queue, skb);
+		skb_queue_head(&cdev->recvqueue, skb);
 		return -EMSGSIZE;
 	}
 	retval = copy_to_user(buf, skb->data, skb->len);
 	if (retval) {
-		skb_queue_head(&cdev->recv_queue, skb);
+		skb_queue_head(&cdev->recvqueue, skb);
 		return retval;
 	}
 	copied = skb->len;
 
-	if (CAPIMSG_COMMAND(skb->data) == CAPI_DATA_B3
-	    && CAPIMSG_SUBCOMMAND(skb->data) == CAPI_IND)
+	if (CAPIMSG_CMD(skb->data) == CAPI_DATA_B3_IND) {
 		cdev->nrecvdatapkt++;
-        else cdev->nrecvctlpkt++;
+	} else {
+		cdev->nrecvctlpkt++;
+	}
+
 	kfree_skb(skb);
 
 	return copied;
 }
 
-static ssize_t capi_write(struct file *file, const char *buf,
-		       size_t count, loff_t *ppos)
+static ssize_t
+capi_write(struct file *file, const char *buf, size_t count, loff_t *ppos)
 {
-        struct inode *inode = file->f_dentry->d_inode;
-	unsigned int minor = MINOR(inode->i_rdev);
-	struct capidev *cdev;
+	struct capidev *cdev = (struct capidev *)file->private_data;
 	struct sk_buff *skb;
 	int retval;
-	__u8 cmd;
-	__u8 subcmd;
 	__u16 mlen;
 
-       if (ppos != &file->f_pos)
+        if (ppos != &file->f_pos)
 		return -ESPIPE;
 
-	if (!minor || minor > CAPI_MAXMINOR || !capidevs[minor].is_registered)
+	if (!cdev->applid)
 		return -ENODEV;
 
-	cdev = &capidevs[minor];
-
 	skb = alloc_skb(count, GFP_USER);
 
 	if ((retval = copy_from_user(skb_put(skb, count), buf, count))) {
 		kfree_skb(skb);
 		return retval;
 	}
-	cmd = CAPIMSG_COMMAND(skb->data);
-	subcmd = CAPIMSG_SUBCOMMAND(skb->data);
 	mlen = CAPIMSG_LEN(skb->data);
-	if (cmd == CAPI_DATA_B3 && subcmd == CAPI_REQ) {
-		__u16 dlen = CAPIMSG_DATALEN(skb->data);
-		if (mlen + dlen != count) {
+	if (CAPIMSG_CMD(skb->data) == CAPI_DATA_B3_REQ) {
+		if (mlen + CAPIMSG_DATALEN(skb->data) != count) {
+			kfree_skb(skb);
+			return -EINVAL;
+		}
+	} else {
+		if (mlen != count) {
 			kfree_skb(skb);
 			return -EINVAL;
 		}
-	} else if (mlen != count) {
-		kfree_skb(skb);
-		return -EINVAL;
 	}
 	CAPIMSG_SETAPPID(skb->data, cdev->applid);
 
@@ -278,63 +1010,56 @@
 		kfree_skb(skb);
 		return -EIO;
 	}
-	if (cmd == CAPI_DATA_B3 && subcmd == CAPI_REQ)
+	if (CAPIMSG_CMD(skb->data) == CAPI_DATA_B3_REQ) {
 		cdev->nsentdatapkt++;
-	else cdev->nsentctlpkt++;
+	} else {
+		cdev->nsentctlpkt++;
+	}
 	return count;
 }
 
 static unsigned int
 capi_poll(struct file *file, poll_table * wait)
 {
+	struct capidev *cdev = (struct capidev *)file->private_data;
 	unsigned int mask = 0;
-	unsigned int minor = MINOR(file->f_dentry->d_inode->i_rdev);
-	struct capidev *cdev;
 
-	if (!minor || minor > CAPI_MAXMINOR || !capidevs[minor].is_registered)
+	if (!cdev->applid)
 		return POLLERR;
 
-	cdev = &capidevs[minor];
-	poll_wait(file, &(cdev->recv_wait), wait);
+	poll_wait(file, &(cdev->recvwait), wait);
 	mask = POLLOUT | POLLWRNORM;
-	if (!skb_queue_empty(&cdev->recv_queue))
+	if (!skb_queue_empty(&cdev->recvqueue))
 		mask |= POLLIN | POLLRDNORM;
 	return mask;
 }
 
-static int capi_ioctl(struct inode *inode, struct file *file,
+static int
+capi_ioctl(struct inode *inode, struct file *file,
 		      unsigned int cmd, unsigned long arg)
 {
-	unsigned int minor = MINOR(inode->i_rdev);
-	struct capidev *cdev;
+	struct capidev *cdev = (struct capidev *)file->private_data;
 	capi_ioctl_struct data;
-	int retval;
-
-
-	if (minor >= CAPI_MAXMINOR || !capidevs[minor].is_open)
-		return -ENODEV;
-
-	cdev = &capidevs[minor];
+	int retval = -EINVAL;
 
 	switch (cmd) {
 	case CAPI_REGISTER:
 		{
-			if (!minor)
-				return -EINVAL;
 			retval = copy_from_user((void *) &data.rparams,
 						(void *) arg, sizeof(struct capi_register_params));
 			if (retval)
 				return -EFAULT;
-			if (cdev->is_registered)
+			if (cdev->applid)
 				return -EEXIST;
 			cdev->errcode = (*capifuncs->capi_register) (&data.rparams,
 							  &cdev->applid);
-			if (cdev->errcode)
+			if (cdev->errcode) {
+				cdev->applid = 0;
 				return -EIO;
-			(void) (*capifuncs->capi_set_signal) (cdev->applid, capi_signal, minor);
-			cdev->is_registered = 1;
+			}
+			(void) (*capifuncs->capi_set_signal) (cdev->applid, capi_signal, cdev);
 		}
-		return 0;
+		return (int)cdev->applid;
 
 	case CAPI_GET_VERSION:
 		{
@@ -440,8 +1165,6 @@
 	case CAPI_MANUFACTURER_CMD:
 		{
 			struct capi_manufacturer_cmd mcmd;
-			if (minor)
-				return -EINVAL;
 			if (!capable(CAP_SYS_ADMIN))
 				return -EPERM;
 			retval = copy_from_user((void *) &mcmd, (void *) arg,
@@ -451,121 +1174,785 @@
 			return (*capifuncs->capi_manufacturer) (mcmd.cmd, mcmd.data);
 		}
 		return 0;
+
+	case CAPI_SET_FLAGS:
+	case CAPI_CLR_FLAGS:
+		{
+			unsigned userflags;
+			retval = copy_from_user((void *) &userflags,
+						(void *) arg,
+						sizeof(userflags));
+			if (retval)
+				return -EFAULT;
+			if (cmd == CAPI_SET_FLAGS)
+				cdev->userflags |= userflags;
+			else
+				cdev->userflags &= ~userflags;
+		}
+		return 0;
+
+	case CAPI_GET_FLAGS:
+		{
+			retval = copy_to_user((void *) arg,
+					      (void *) &cdev->userflags,
+					      sizeof(cdev->userflags));
+			if (retval)
+				return -EFAULT;
+		}
+		return 0;
+
+	case CAPI_NCCI_OPENCOUNT:
+		{
+			struct capincci *nccip;
+#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE
+			struct capiminor *mp;
+#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */
+			unsigned ncci;
+			int count = 0;
+			retval = copy_from_user((void *) &ncci,
+						(void *) arg,
+						sizeof(ncci));
+			if (retval)
+				return -EFAULT;
+			nccip = capincci_find(cdev, (__u32) ncci);
+			if (!nccip)
+				return 0;
+#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE
+			if ((mp = nccip->minorp) != 0) {
+				count += atomic_read(&mp->ttyopencount);
+				if (mp->file)
+					count++;
+			}
+#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */
+			return count;
+		}
+		return 0;
+
+#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE
+	case CAPI_NCCI_GETUNIT:
+		{
+			struct capincci *nccip;
+			struct capiminor *mp;
+			unsigned ncci;
+			retval = copy_from_user((void *) &ncci,
+						(void *) arg,
+						sizeof(ncci));
+			if (retval)
+				return -EFAULT;
+			nccip = capincci_find(cdev, (__u32) ncci);
+			if (!nccip || (mp = nccip->minorp) == 0)
+				return -ESRCH;
+			return mp->minor;
+		}
+		return 0;
+#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */
 	}
 	return -EINVAL;
 }
 
-static int capi_open(struct inode *inode, struct file *file)
+static int
+capi_open(struct inode *inode, struct file *file)
 {
-	unsigned int minor = MINOR(inode->i_rdev);
-
-	if (minor >= CAPI_MAXMINOR)
-		return -ENXIO;
-
-	if (minor) {
-		if (capidevs[minor].is_open)
-			return -EEXIST;
-
-		capidevs[minor].is_open = 1;
-		skb_queue_head_init(&capidevs[minor].recv_queue);
-		MOD_INC_USE_COUNT;
-		capidevs[minor].nopen++;
+	if (file->private_data)
+		return -EEXIST;
 
-	} else {
-		capidevs[minor].is_open++;
-		MOD_INC_USE_COUNT;
-	}
+	if ((file->private_data = capidev_alloc(file)) == 0)
+		return -ENOMEM;
 
+	MOD_INC_USE_COUNT;
+#ifdef _DEBUG_REFCOUNT
+	printk(KERN_DEBUG "capi_open %d\n", GET_USE_COUNT(&__this_module));
+#endif
 	return 0;
 }
 
 static int
 capi_release(struct inode *inode, struct file *file)
 {
-	unsigned int minor = MINOR(inode->i_rdev);
-	struct capidev *cdev;
-	struct sk_buff *skb;
-
-	if (minor >= CAPI_MAXMINOR || !capidevs[minor].is_open) {
-		printk(KERN_ERR "capi20: release minor %d ???\n", minor);
-		return 0;
-	}
-	cdev = &capidevs[minor];
-
-	if (minor) {
-
-		if (cdev->is_registered)
-			(*capifuncs->capi_release) (cdev->applid);
-
-		cdev->is_registered = 0;
-		cdev->applid = 0;
-
-		while ((skb = skb_dequeue(&cdev->recv_queue)) != 0) {
-			kfree_skb(skb);
-		}
-		cdev->is_open = 0;
-	} else {
-		cdev->is_open--;
-	}
+	struct capidev *cdev = (struct capidev *)file->private_data;
 
+	lock_kernel();
+	capincci_free(cdev, 0xffffffff);
+	capidev_free(cdev);
+	file->private_data = NULL;
+	
 	MOD_DEC_USE_COUNT;
+#ifdef _DEBUG_REFCOUNT
+	printk(KERN_DEBUG "capi_release %d\n", GET_USE_COUNT(&__this_module));
+#endif
+	unlock_kernel();
 	return 0;
 }
 
 static struct file_operations capi_fops =
 {
-	capi_llseek,
-	capi_read,
-	capi_write,
-	NULL,			/* capi_readdir */
-	capi_poll,
-	capi_ioctl,
-	NULL,			/* capi_mmap */
-	capi_open,
-        NULL,                   /* capi_flush */
-	capi_release,
-	NULL,			/* capi_fsync */
-	NULL,			/* capi_fasync */
+	llseek:		capi_llseek,
+	read:		capi_read,
+	write:		capi_write,
+	poll:		capi_poll,
+	ioctl:		capi_ioctl,
+	open:		capi_open,
+	release:	capi_release,
 };
 
-/* -------- /proc functions ----------------------------------- */
+#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE
+/* -------- file_operations for capincci ---------------------------- */
 
-/*
- * /proc/capi/capi20:
- *  minor opencount nrecvctlpkt nrecvdatapkt nsendctlpkt nsenddatapkt
- */
-static int proc_capidev_read_proc(char *page, char **start, off_t off,
-                                       int count, int *eof, void *data)
+static int
+capinc_raw_open(struct inode *inode, struct file *file)
 {
-        struct capidev *cp;
-	int i;
-	int len = 0;
-	off_t begin = 0;
+	struct capiminor *mp;
 
-	for (i=0; i < CAPI_MAXMINOR; i++) {
-		cp = &capidevs[i+1];
-		if (cp->nopen == 0) continue;
-		len += sprintf(page+len, "%d %lu %lu %lu %lu %lu\n",
-			i+1,
-			cp->nopen,
-			cp->nrecvctlpkt,
-			cp->nrecvdatapkt,
-			cp->nsentctlpkt,
-			cp->nsentdatapkt);
-		if (len+begin > off+count)
-			goto endloop;
-		if (len+begin < off) {
-			begin += len;
-			len = 0;
+	if (file->private_data)
+		return -EEXIST;
+	if ((mp = capiminor_find(MINOR(file->f_dentry->d_inode->i_rdev))) == 0)
+		return -ENXIO;
+	if (mp->nccip == 0)
+		return -ENXIO;
+	if (mp->file)
+		return -EBUSY;
+
+#ifdef _DEBUG_REFCOUNT
+	printk(KERN_DEBUG "capi_raw_open %d\n", GET_USE_COUNT(&__this_module));
+#endif
+
+	mp->datahandle = 0;
+	mp->file = file;
+	file->private_data = (void *)mp;
+	handle_minor_recv(mp);
+	return 0;
+}
+
+static loff_t
+capinc_raw_llseek(struct file *file, loff_t offset, int origin)
+{
+	return -ESPIPE;
+}
+
+static ssize_t
+capinc_raw_read(struct file *file, char *buf, size_t count, loff_t *ppos)
+{
+	struct capiminor *mp = (struct capiminor *)file->private_data;
+	struct sk_buff *skb;
+	int retval;
+	size_t copied = 0;
+
+        if (ppos != &file->f_pos)
+		return -ESPIPE;
+
+	if (!mp || !mp->nccip)
+		return -EINVAL;
+
+	if ((skb = skb_dequeue(&mp->recvqueue)) == 0) {
+
+		if (file->f_flags & O_NONBLOCK)
+			return -EAGAIN;
+
+		for (;;) {
+			interruptible_sleep_on(&mp->recvwait);
+			if (mp->nccip == 0)
+				return 0;
+			if ((skb = skb_dequeue(&mp->recvqueue)) != 0)
+				break;
+			if (signal_pending(current))
+				break;
+		}
+		if (skb == 0)
+			return -ERESTARTNOHAND;
+	}
+	do {
+		if (count < skb->len) {
+			retval = copy_to_user(buf, skb->data, count);
+			if (retval) {
+				skb_queue_head(&mp->recvqueue, skb);
+				return retval;
+			}
+			skb_pull(skb, count);
+			skb_queue_head(&mp->recvqueue, skb);
+			copied += count;
+			return copied;
+		} else {
+			retval = copy_to_user(buf, skb->data, skb->len);
+			if (retval) {
+				skb_queue_head(&mp->recvqueue, skb);
+				return copied;
+			}
+			copied += skb->len;
+			count -= skb->len;
+			buf += skb->len;
+			kfree_skb(skb);
+		}
+	} while ((skb = skb_dequeue(&mp->recvqueue)) != 0);
+
+	return copied;
+}
+
+static ssize_t
+capinc_raw_write(struct file *file, const char *buf, size_t count, loff_t *ppos)
+{
+	struct capiminor *mp = (struct capiminor *)file->private_data;
+	struct sk_buff *skb;
+	int retval;
+
+        if (ppos != &file->f_pos)
+		return -ESPIPE;
+
+	if (!mp || !mp->nccip)
+		return -EINVAL;
+
+	skb = alloc_skb(CAPI_DATA_B3_REQ_LEN+count, GFP_USER);
+
+	skb_reserve(skb, CAPI_DATA_B3_REQ_LEN);
+	if ((retval = copy_from_user(skb_put(skb, count), buf, count))) {
+		kfree_skb(skb);
+		return retval;
+	}
+
+	while (skb_queue_len(&mp->outqueue) > CAPINC_MAX_SENDQUEUE) {
+		if (file->f_flags & O_NONBLOCK)
+			return -EAGAIN;
+		interruptible_sleep_on(&mp->sendwait);
+		if (mp->nccip == 0) {
+			kfree_skb(skb);
+			return -EIO;
+		}
+		if (signal_pending(current))
+			return -ERESTARTNOHAND;
+	}
+	skb_queue_tail(&mp->outqueue, skb);
+	mp->outbytes += skb->len;
+	(void)handle_minor_send(mp);
+	return count;
+}
+
+static unsigned int
+capinc_raw_poll(struct file *file, poll_table * wait)
+{
+	struct capiminor *mp = (struct capiminor *)file->private_data;
+	unsigned int mask = 0;
+
+	if (!mp || !mp->nccip)
+		return POLLERR|POLLHUP;
+
+	poll_wait(file, &(mp->recvwait), wait);
+	if (!skb_queue_empty(&mp->recvqueue))
+		mask |= POLLIN | POLLRDNORM;
+	poll_wait(file, &(mp->sendwait), wait);
+	if (skb_queue_len(&mp->outqueue) > CAPINC_MAX_SENDQUEUE)
+		mask = POLLOUT | POLLWRNORM;
+	return mask;
+}
+
+static int
+capinc_raw_ioctl(struct inode *inode, struct file *file,
+		      unsigned int cmd, unsigned long arg)
+{
+	struct capiminor *mp = (struct capiminor *)file->private_data;
+	if (!mp || !mp->nccip)
+		return -EINVAL;
+
+	switch (cmd) {
+	}
+	return -EINVAL;
+}
+
+static int
+capinc_raw_release(struct inode *inode, struct file *file)
+{
+	struct capiminor *mp = (struct capiminor *)file->private_data;
+
+	if (mp) {
+		lock_kernel();
+		mp->file = 0;
+		if (mp->nccip == 0) {
+			capiminor_free(mp);
+			file->private_data = NULL;
+		}
+		unlock_kernel();
+	}
+
+#ifdef _DEBUG_REFCOUNT
+	printk(KERN_DEBUG "capinc_raw_release %d\n", GET_USE_COUNT(&__this_module));
+#endif
+	return 0;
+}
+
+static struct file_operations capinc_raw_fops =
+{
+	llseek:		capinc_raw_llseek,
+	read:		capinc_raw_read,
+	write:		capinc_raw_write,
+	poll:		capinc_raw_poll,
+	ioctl:		capinc_raw_ioctl,
+	open:		capinc_raw_open,
+	release:	capinc_raw_release,
+};
+
+/* -------- tty_operations for capincci ----------------------------- */
+
+int capinc_tty_open(struct tty_struct * tty, struct file * file)
+{
+	struct capiminor *mp;
+
+	if ((mp = capiminor_find(MINOR(file->f_dentry->d_inode->i_rdev))) == 0)
+		return -ENXIO;
+	if (mp->nccip == 0)
+		return -ENXIO;
+	if (mp->file)
+		return -EBUSY;
+
+	skb_queue_head_init(&mp->recvqueue);
+	init_waitqueue_head(&mp->recvwait);
+	init_waitqueue_head(&mp->sendwait);
+	tty->driver_data = (void *)mp;
+#ifdef _DEBUG_REFCOUNT
+	printk(KERN_DEBUG "capi_tty_open %d\n", GET_USE_COUNT(&__this_module));
+#endif
+	if (atomic_read(&mp->ttyopencount) == 0)
+		mp->tty = tty;
+	atomic_inc(&mp->ttyopencount);
+#ifdef _DEBUG_REFCOUNT
+	printk(KERN_DEBUG "capinc_tty_open ocount=%d\n", atomic_read(&mp->ttyopencount));
+#endif
+	handle_minor_recv(mp);
+	return 0;
+}
+
+void capinc_tty_close(struct tty_struct * tty, struct file * file)
+{
+	struct capiminor *mp;
+
+	mp = (struct capiminor *)tty->driver_data;
+	if (mp)	{
+		if (atomic_dec_and_test(&mp->ttyopencount)) {
+#ifdef _DEBUG_REFCOUNT
+			printk(KERN_DEBUG "capinc_tty_close lastclose\n");
+#endif
+			tty->driver_data = (void *)0;
+			mp->tty = 0;
+		}
+#ifdef _DEBUG_REFCOUNT
+		printk(KERN_DEBUG "capinc_tty_close ocount=%d\n", atomic_read(&mp->ttyopencount));
+#endif
+		if (mp->nccip == 0)
+			capiminor_free(mp);
+	}
+
+#ifdef _DEBUG_REFCOUNT
+	printk(KERN_DEBUG "capinc_tty_close\n");
+#endif
+}
+
+int capinc_tty_write(struct tty_struct * tty, int from_user,
+		      const unsigned char *buf, int count)
+{
+	struct capiminor *mp = (struct capiminor *)tty->driver_data;
+	struct sk_buff *skb;
+	int retval;
+
+#ifdef _DEBUG_TTYFUNCS
+	printk(KERN_DEBUG "capinc_tty_write(from_user=%d,count=%d)\n",
+				from_user, count);
+#endif
+
+	if (!mp || !mp->nccip) {
+#ifdef _DEBUG_TTYFUNCS
+		printk(KERN_DEBUG "capinc_tty_write: mp or mp->ncci NULL\n");
+#endif
+		return 0;
+	}
+
+	skb = mp->ttyskb;
+	if (skb) {
+		mp->ttyskb = 0;
+		skb_queue_tail(&mp->outqueue, skb);
+		mp->outbytes += skb->len;
+	}
+
+	skb = alloc_skb(CAPI_DATA_B3_REQ_LEN+count, GFP_ATOMIC);
+	if (!skb) {
+		printk(KERN_ERR "capinc_tty_write: alloc_skb failed\n");
+		return -ENOMEM;
+	}
+
+	skb_reserve(skb, CAPI_DATA_B3_REQ_LEN);
+	if (from_user) {
+		if ((retval = copy_from_user(skb_put(skb, count), buf, count))) {
+			kfree_skb(skb);
+#ifdef _DEBUG_TTYFUNCS
+			printk(KERN_DEBUG "capinc_tty_write: copy_from_user=%d\n", retval);
+#endif
+			return retval;
+		}
+	} else {
+		memcpy(skb_put(skb, count), buf, count);
+	}
+
+	skb_queue_tail(&mp->outqueue, skb);
+	mp->outbytes += skb->len;
+	(void)handle_minor_send(mp);
+	(void)handle_minor_recv(mp);
+	return count;
+}
+
+void capinc_tty_put_char(struct tty_struct *tty, unsigned char ch)
+{
+	struct capiminor *mp = (struct capiminor *)tty->driver_data;
+	struct sk_buff *skb;
+
+#ifdef _DEBUG_TTYFUNCS
+	printk(KERN_DEBUG "capinc_put_char(%u)\n", ch);
+#endif
+
+	if (!mp || !mp->nccip) {
+#ifdef _DEBUG_TTYFUNCS
+		printk(KERN_DEBUG "capinc_tty_put_char: mp or mp->ncci NULL\n");
+#endif
+		return;
+	}
+
+	skb = mp->ttyskb;
+	if (skb) {
+		if (skb_tailroom(skb) > 0) {
+			*(skb_put(skb, 1)) = ch;
+			return;
+		}
+		mp->ttyskb = 0;
+		skb_queue_tail(&mp->outqueue, skb);
+		mp->outbytes += skb->len;
+		(void)handle_minor_send(mp);
+	}
+	skb = alloc_skb(CAPI_DATA_B3_REQ_LEN+CAPI_MAX_BLKSIZE, GFP_ATOMIC);
+	if (skb) {
+		skb_reserve(skb, CAPI_DATA_B3_REQ_LEN);
+		*(skb_put(skb, 1)) = ch;
+		mp->ttyskb = skb;
+	} else {
+		printk(KERN_ERR "capinc_put_char: char %u lost\n", ch);
+	}
+}
+
+void capinc_tty_flush_chars(struct tty_struct *tty)
+{
+	struct capiminor *mp = (struct capiminor *)tty->driver_data;
+	struct sk_buff *skb;
+
+#ifdef _DEBUG_TTYFUNCS
+	printk(KERN_DEBUG "capinc_tty_flush_chars\n");
+#endif
+
+	if (!mp || !mp->nccip) {
+#ifdef _DEBUG_TTYFUNCS
+		printk(KERN_DEBUG "capinc_tty_flush_chars: mp or mp->ncci NULL\n");
+#endif
+		return;
+	}
+
+	skb = mp->ttyskb;
+	if (skb) {
+		mp->ttyskb = 0;
+		skb_queue_tail(&mp->outqueue, skb);
+		mp->outbytes += skb->len;
+		(void)handle_minor_send(mp);
+	}
+	(void)handle_minor_recv(mp);
+}
+
+int capinc_tty_write_room(struct tty_struct *tty)
+{
+	struct capiminor *mp = (struct capiminor *)tty->driver_data;
+	int room;
+	if (!mp || !mp->nccip) {
+#ifdef _DEBUG_TTYFUNCS
+		printk(KERN_DEBUG "capinc_tty_write_room: mp or mp->ncci NULL\n");
+#endif
+		return 0;
+	}
+	room = CAPINC_MAX_SENDQUEUE-skb_queue_len(&mp->outqueue);
+	room *= CAPI_MAX_BLKSIZE;
+#ifdef _DEBUG_TTYFUNCS
+	printk(KERN_DEBUG "capinc_tty_write_room = %d\n", room);
+#endif
+	return room;
+}
+
+int capinc_tty_chars_in_buffer(struct tty_struct *tty)
+{
+	struct capiminor *mp = (struct capiminor *)tty->driver_data;
+	if (!mp || !mp->nccip) {
+#ifdef _DEBUG_TTYFUNCS
+		printk(KERN_DEBUG "capinc_tty_chars_in_buffer: mp or mp->ncci NULL\n");
+#endif
+		return 0;
+	}
+#ifdef _DEBUG_TTYFUNCS
+	printk(KERN_DEBUG "capinc_tty_chars_in_buffer = %d nack=%d sq=%d rq=%d\n",
+			mp->outbytes, mp->nack,
+			skb_queue_len(&mp->outqueue),
+			skb_queue_len(&mp->inqueue));
+#endif
+	return mp->outbytes;
+}
+
+int capinc_tty_ioctl(struct tty_struct *tty, struct file * file,
+		    unsigned int cmd, unsigned long arg)
+{
+	int error = 0;
+	switch (cmd) {
+	default:
+		error = n_tty_ioctl (tty, file, cmd, arg);
+		break;
+	}
+	return error;
+}
+
+void capinc_tty_set_termios(struct tty_struct *tty, struct termios * old)
+{
+#ifdef _DEBUG_TTYFUNCS
+	printk(KERN_DEBUG "capinc_tty_set_termios\n");
+#endif
+}
+
+void capinc_tty_throttle(struct tty_struct * tty)
+{
+	struct capiminor *mp = (struct capiminor *)tty->driver_data;
+#ifdef _DEBUG_TTYFUNCS
+	printk(KERN_DEBUG "capinc_tty_throttle\n");
+#endif
+	if (mp)
+		mp->ttyinstop = 1;
+}
+
+void capinc_tty_unthrottle(struct tty_struct * tty)
+{
+	struct capiminor *mp = (struct capiminor *)tty->driver_data;
+#ifdef _DEBUG_TTYFUNCS
+	printk(KERN_DEBUG "capinc_tty_unthrottle\n");
+#endif
+	if (mp) {
+		mp->ttyinstop = 0;
+		handle_minor_recv(mp);
+	}
+}
+
+void capinc_tty_stop(struct tty_struct *tty)
+{
+	struct capiminor *mp = (struct capiminor *)tty->driver_data;
+#ifdef _DEBUG_TTYFUNCS
+	printk(KERN_DEBUG "capinc_tty_stop\n");
+#endif
+	if (mp) {
+		mp->ttyoutstop = 1;
+	}
+}
+
+void capinc_tty_start(struct tty_struct *tty)
+{
+	struct capiminor *mp = (struct capiminor *)tty->driver_data;
+#ifdef _DEBUG_TTYFUNCS
+	printk(KERN_DEBUG "capinc_tty_start\n");
+#endif
+	if (mp) {
+		mp->ttyoutstop = 0;
+		(void)handle_minor_send(mp);
+	}
+}
+
+void capinc_tty_hangup(struct tty_struct *tty)
+{
+#ifdef _DEBUG_TTYFUNCS
+	printk(KERN_DEBUG "capinc_tty_hangup\n");
+#endif
+}
+
+void capinc_tty_break_ctl(struct tty_struct *tty, int state)
+{
+#ifdef _DEBUG_TTYFUNCS
+	printk(KERN_DEBUG "capinc_tty_break_ctl(%d)\n", state);
+#endif
+}
+
+void capinc_tty_flush_buffer(struct tty_struct *tty)
+{
+#ifdef _DEBUG_TTYFUNCS
+	printk(KERN_DEBUG "capinc_tty_flush_buffer\n");
+#endif
+}
+
+void capinc_tty_set_ldisc(struct tty_struct *tty)
+{
+#ifdef _DEBUG_TTYFUNCS
+	printk(KERN_DEBUG "capinc_tty_set_ldisc\n");
+#endif
+}
+
+void capinc_tty_send_xchar(struct tty_struct *tty, char ch)
+{
+#ifdef _DEBUG_TTYFUNCS
+	printk(KERN_DEBUG "capinc_tty_send_xchar(%d)\n", ch);
+#endif
+}
+
+int capinc_tty_read_proc(char *page, char **start, off_t off,
+			  int count, int *eof, void *data)
+{
+	return 0;
+}
+
+int capinc_write_proc(struct file *file, const char *buffer,
+			  unsigned long count, void *data)
+{
+	return 0;
+}
+
+#define CAPINC_NR_PORTS 256
+static struct tty_driver capinc_tty_driver;
+static int capinc_tty_refcount;
+static struct tty_struct *capinc_tty_table[CAPINC_NR_PORTS];
+static struct termios *capinc_tty_termios[CAPINC_NR_PORTS];
+static struct termios *capinc_tty_termios_locked[CAPINC_NR_PORTS];
+
+int capinc_tty_init(void)
+{
+	struct tty_driver *drv = &capinc_tty_driver;
+
+	/* Initialize the tty_driver structure */
+	
+	memset(drv, 0, sizeof(struct tty_driver));
+	drv->magic = TTY_DRIVER_MAGIC;
+#if (LINUX_VERSION_CODE > 0x20100)
+	drv->driver_name = "capi_nc";
+#endif
+	drv->name = "capi/%d";
+	drv->major = capi_ttymajor;
+	drv->minor_start = 0;
+	drv->num = CAPINC_NR_PORTS;
+	drv->type = TTY_DRIVER_TYPE_SERIAL;
+	drv->subtype = SERIAL_TYPE_NORMAL;
+	drv->init_termios = tty_std_termios;
+	drv->init_termios.c_iflag = ICRNL;
+	drv->init_termios.c_oflag = OPOST | ONLCR;
+	drv->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+	drv->init_termios.c_lflag = 0;
+	drv->flags = TTY_DRIVER_REAL_RAW|TTY_DRIVER_RESET_TERMIOS;
+	drv->refcount = &capinc_tty_refcount;
+	drv->table = capinc_tty_table;
+	drv->termios = capinc_tty_termios;
+	drv->termios_locked = capinc_tty_termios_locked;
+
+	drv->open = capinc_tty_open;
+	drv->close = capinc_tty_close;
+	drv->write = capinc_tty_write;
+	drv->put_char = capinc_tty_put_char;
+	drv->flush_chars = capinc_tty_flush_chars;
+	drv->write_room = capinc_tty_write_room;
+	drv->chars_in_buffer = capinc_tty_chars_in_buffer;
+	drv->ioctl = capinc_tty_ioctl;
+	drv->set_termios = capinc_tty_set_termios;
+	drv->throttle = capinc_tty_throttle;
+	drv->unthrottle = capinc_tty_unthrottle;
+	drv->stop = capinc_tty_stop;
+	drv->start = capinc_tty_start;
+	drv->hangup = capinc_tty_hangup;
+#if (LINUX_VERSION_CODE >= 131394) /* Linux 2.1.66 */
+	drv->break_ctl = capinc_tty_break_ctl;
+#endif
+	drv->flush_buffer = capinc_tty_flush_buffer;
+	drv->set_ldisc = capinc_tty_set_ldisc;
+#if (LINUX_VERSION_CODE >= 131343)
+	drv->send_xchar = capinc_tty_send_xchar;
+	drv->read_proc = capinc_tty_read_proc;
+#endif
+	if (tty_register_driver(drv)) {
+		printk(KERN_ERR "Couldn't register capi_nc driver\n");
+		return -1;
+	}
+	return 0;
+}
+
+void capinc_tty_exit(void)
+{
+	struct tty_driver *drv = &capinc_tty_driver;
+	int retval;
+	if ((retval = tty_unregister_driver(drv)))
+		printk(KERN_ERR "capi: failed to unregister capi_nc driver (%d)\n", retval);
+}
+
+#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */
+
+/* -------- /proc functions ----------------------------------------- */
+
+/*
+ * /proc/capi/capi20:
+ *  minor applid nrecvctlpkt nrecvdatapkt nsendctlpkt nsenddatapkt
+ */
+static int proc_capidev_read_proc(char *page, char **start, off_t off,
+                                       int count, int *eof, void *data)
+{
+        struct capidev *cdev;
+	int len = 0;
+
+	for (cdev=capidev_openlist; cdev; cdev = cdev->next) {
+		len += sprintf(page+len, "%d %d %lu %lu %lu %lu\n",
+			cdev->minor,
+			cdev->applid,
+			cdev->nrecvctlpkt,
+			cdev->nrecvdatapkt,
+			cdev->nsentctlpkt,
+			cdev->nsentdatapkt);
+		if (len <= off) {
+			off -= len;
+			len = 0;
+		} else {
+			if (len-off > count)
+				goto endloop;
 		}
 	}
 endloop:
-	if (i >= CAPI_MAXMINOR)
+	if (len < count)
 		*eof = 1;
-	if (off >= len+begin)
-		return 0;
-	*start = page + (off-begin);
-	return ((count < begin+len-off) ? count : begin+len-off);
+	if (len>count) len = count;
+	if (len<0) len = 0;
+	return len;
+}
+
+/*
+ * /proc/capi/capi20ncci:
+ *  applid ncci
+ */
+static int proc_capincci_read_proc(char *page, char **start, off_t off,
+                                       int count, int *eof, void *data)
+{
+        struct capidev *cdev;
+        struct capincci *np;
+	int len = 0;
+
+	for (cdev=capidev_openlist; cdev; cdev = cdev->next) {
+		for (np=cdev->nccis; np; np = np->next) {
+			len += sprintf(page+len, "%d 0x%x%s\n",
+				cdev->applid,
+				np->ncci,
+#ifndef CONFIG_ISDN_CAPI_MIDDLEWARE
+				"");
+#else /* CONFIG_ISDN_CAPI_MIDDLEWARE */
+				np->minorp && np->minorp->file ? " open" : "");
+#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */
+			if (len <= off) {
+				off -= len;
+				len = 0;
+			} else {
+				if (len-off > count)
+					goto endloop;
+			}
+		}
+	}
+endloop:
+	*start = page+off;
+	if (len < count)
+		*eof = 1;
+	if (len>count) len = count;
+	if (len<0) len = 0;
+	return len;
 }
 
 static struct procfsentries {
@@ -577,9 +1964,10 @@
 } procfsentries[] = {
    /* { "capi",		  S_IFDIR, 0 }, */
    { "capi/capi20", 	  0	 , proc_capidev_read_proc },
+   { "capi/capi20ncci",   0	 , proc_capincci_read_proc },
 };
 
-static void proc_init(void)
+static void __init proc_init(void)
 {
     int nelem = sizeof(procfsentries)/sizeof(procfsentries[0]);
     int i;
@@ -591,7 +1979,7 @@
     }
 }
 
-static void proc_exit(void)
+static void  proc_exit(void)
 {
     int nelem = sizeof(procfsentries)/sizeof(procfsentries[0]);
     int i;
@@ -604,42 +1992,118 @@
 	}
     }
 }
+
 /* -------- init function and module interface ---------------------- */
 
-#ifdef MODULE
-#define	 capi_init	init_module
-#endif
+
+static void lower_callback(unsigned int cmd, __u32 contr, void *data)
+{
+	struct capi_ncciinfo *np;
+	struct capidev *cdev;
+
+	switch (cmd) {
+	case KCI_CONTRUP:
+		printk(KERN_INFO "capi: controller %hu up\n", contr);
+		break;
+	case KCI_CONTRDOWN:
+		printk(KERN_INFO "capi: controller %hu down\n", contr);
+		break;
+	case KCI_NCCIUP:
+		np = (struct capi_ncciinfo *)data;
+		if ((cdev = capidev_find(np->applid)) == 0)
+			return;
+		(void)capincci_alloc(cdev, np->ncci);
+		break;
+	case KCI_NCCIDOWN:
+		np = (struct capi_ncciinfo *)data;
+		if ((cdev = capidev_find(np->applid)) == 0)
+			return;
+		(void)capincci_free(cdev, np->ncci);
+		break;
+	}
+}
 
 static struct capi_interface_user cuser = {
-	"capi20",
-	0,
+	name: "capi20",
+	callback: lower_callback,
 };
 
-int capi_init(void)
+static char rev[10];
+
+static int __init capi_init(void)
 {
-	
-	memset(capidevs, 0, sizeof(capidevs));
+	char *p;
+
+	MOD_INC_USE_COUNT;
+
+	if ((p = strchr(revision, ':'))) {
+		strcpy(rev, p + 2);
+		p = strchr(rev, '$');
+		*(p-1) = 0;
+	} else
+		strcpy(rev, "???");
 
 	if (register_chrdev(capi_major, "capi20", &capi_fops)) {
 		printk(KERN_ERR "capi20: unable to get major %d\n", capi_major);
+		MOD_DEC_USE_COUNT;
+		return -EIO;
+	}
+
+#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE
+	if (register_chrdev(capi_rawmajor, "capi/r%d", &capinc_raw_fops)) {
+		unregister_chrdev(capi_major, "capi20");
+		printk(KERN_ERR "capi20: unable to get major %d\n", capi_rawmajor);
+		MOD_DEC_USE_COUNT;
 		return -EIO;
 	}
+#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */
 	printk(KERN_NOTICE "capi20: started up with major %d\n", capi_major);
 
 	if ((capifuncs = attach_capi_interface(&cuser)) == 0) {
+
+		MOD_DEC_USE_COUNT;
 		unregister_chrdev(capi_major, "capi20");
+#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE
+		unregister_chrdev(capi_rawmajor, "capi/r%d");
+#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */
 		return -EIO;
 	}
+
+#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE
+	if (capinc_tty_init() < 0) {
+		(void) detach_capi_interface(&cuser);
+		unregister_chrdev(capi_major, "capi20");
+		unregister_chrdev(capi_rawmajor, "capi/r%d");
+		MOD_DEC_USE_COUNT;
+		return -ENOMEM;
+	}
+#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */
+
+
 	(void)proc_init();
+
+	printk(KERN_NOTICE "capi20: Rev%s: started up with major %d\n",
+				rev, capi_major);
+
+	MOD_DEC_USE_COUNT;
 	return 0;
 }
 
-#ifdef MODULE
-void cleanup_module(void)
+static void  capi_exit(void)
 {
+#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE
+#endif
 	(void)proc_exit();
+
 	unregister_chrdev(capi_major, "capi20");
+
+#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE
+	capinc_tty_exit();
+	unregister_chrdev(capi_rawmajor, "capi/r%d");
+#endif
 	(void) detach_capi_interface(&cuser);
+	printk(KERN_NOTICE "capi: Rev%s: unloaded\n", rev);
 }
 
-#endif
+module_init(capi_init);
+module_exit(capi_exit);

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