[Feature]add MT2731_MP2_MR2_SVN388 baseline version

Change-Id: Ief04314834b31e27effab435d3ca8ba33b499059
diff --git a/src/kernel/linux/v4.14/drivers/isdn/capi/Kconfig b/src/kernel/linux/v4.14/drivers/isdn/capi/Kconfig
new file mode 100644
index 0000000..7641b30
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/isdn/capi/Kconfig
@@ -0,0 +1,44 @@
+config CAPI_TRACE
+	bool "CAPI trace support"
+	default y
+	help
+	  If you say Y here, the kernelcapi driver can make verbose traces
+	  of CAPI messages. This feature can be enabled/disabled via IOCTL for
+	  every controller (default disabled).
+	  This will increase the size of the kernelcapi module by 20 KB.
+	  If unsure, say Y.
+
+config ISDN_CAPI_CAPI20
+	tristate "CAPI2.0 /dev/capi20 support"
+	help
+	  This option will provide the CAPI 2.0 interface to userspace
+	  applications via /dev/capi20. Applications should use the
+	  standardized libcapi20 to access this functionality.  You should say
+	  Y/M here.
+
+config ISDN_CAPI_MIDDLEWARE
+	bool "CAPI2.0 Middleware support"
+	depends on ISDN_CAPI_CAPI20 && TTY
+	help
+	  This option will enhance the capabilities of the /dev/capi20
+	  interface.  It will provide a means of moving a data connection,
+	  established via the usual /dev/capi20 interface to a special tty
+	  device.  If you want to use pppd with pppdcapiplugin to dial up to
+	  your ISP, say Y here.
+
+config ISDN_CAPI_CAPIDRV
+	tristate "CAPI2.0 capidrv interface support"
+	depends on ISDN_I4L
+	help
+	  This option provides the glue code to hook up CAPI driven cards to
+	  the legacy isdn4linux link layer.  If you have a card which is
+	  supported by a CAPI driver, but still want to use old features like
+	  ippp interfaces or ttyI emulation, say Y/M here.
+
+config ISDN_CAPI_CAPIDRV_VERBOSE
+	bool "Verbose reason code reporting"
+	depends on ISDN_CAPI_CAPIDRV
+	help
+	  If you say Y here, the capidrv interface will give verbose reasons
+	  for disconnecting. This will increase the size of the kernel by 7 KB.
+	  If unsure, say N.
diff --git a/src/kernel/linux/v4.14/drivers/isdn/capi/Makefile b/src/kernel/linux/v4.14/drivers/isdn/capi/Makefile
new file mode 100644
index 0000000..06da3ed
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/isdn/capi/Makefile
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0
+# Makefile for the CAPI subsystem.
+
+# Ordering constraints: kernelcapi.o first
+
+# Each configuration option enables a list of files.
+
+obj-$(CONFIG_ISDN_CAPI)			+= kernelcapi.o
+obj-$(CONFIG_ISDN_CAPI_CAPI20)		+= capi.o 
+obj-$(CONFIG_ISDN_CAPI_CAPIDRV)		+= capidrv.o
+
+# Multipart objects.
+
+kernelcapi-y				:= kcapi.o capiutil.o capilib.o
+kernelcapi-$(CONFIG_PROC_FS)		+= kcapi_proc.o
diff --git a/src/kernel/linux/v4.14/drivers/isdn/capi/capi.c b/src/kernel/linux/v4.14/drivers/isdn/capi/capi.c
new file mode 100644
index 0000000..da45049
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/isdn/capi/capi.c
@@ -0,0 +1,1462 @@
+/* $Id: capi.c,v 1.1.2.7 2004/04/28 09:48:59 armin Exp $
+ *
+ * CAPI 2.0 Interface for Linux
+ *
+ * Copyright 1996 by Carsten Paeth <calle@calle.de>
+ *
+ * This software may be used and distributed according to the terms
+ * of the GNU General Public License, incorporated herein by reference.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/fcntl.h>
+#include <linux/fs.h>
+#include <linux/signal.h>
+#include <linux/mutex.h>
+#include <linux/mm.h>
+#include <linux/timer.h>
+#include <linux/wait.h>
+#include <linux/tty.h>
+#include <linux/netdevice.h>
+#include <linux/ppp_defs.h>
+#include <linux/ppp-ioctl.h>
+#include <linux/skbuff.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/poll.h>
+#include <linux/capi.h>
+#include <linux/kernelcapi.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/moduleparam.h>
+#include <linux/isdn/capiutil.h>
+#include <linux/isdn/capicmd.h>
+
+MODULE_DESCRIPTION("CAPI4Linux: Userspace /dev/capi20 interface");
+MODULE_AUTHOR("Carsten Paeth");
+MODULE_LICENSE("GPL");
+
+/* -------- driver information -------------------------------------- */
+
+static DEFINE_MUTEX(capi_mutex);
+static struct class *capi_class;
+static int capi_major = 68;		/* allocated */
+
+module_param_named(major, capi_major, uint, 0);
+
+#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE
+#define CAPINC_NR_PORTS		32
+#define CAPINC_MAX_PORTS	256
+
+static int capi_ttyminors = CAPINC_NR_PORTS;
+
+module_param_named(ttyminors, capi_ttyminors, uint, 0);
+#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;
+struct capiminor;
+
+struct ackqueue_entry {
+	struct list_head	list;
+	u16			datahandle;
+};
+
+struct capiminor {
+	unsigned int      minor;
+
+	struct capi20_appl	*ap;
+	u32			ncci;
+	atomic_t		datahandle;
+	atomic_t		msgid;
+
+	struct tty_port port;
+	int                ttyinstop;
+	int                ttyoutstop;
+
+	struct sk_buff_head	inqueue;
+
+	struct sk_buff_head	outqueue;
+	int			outbytes;
+	struct sk_buff		*outskb;
+	spinlock_t		outlock;
+
+	/* transmit path */
+	struct list_head ackqueue;
+	int nack;
+	spinlock_t ackqlock;
+};
+
+struct capincci {
+	struct list_head list;
+	u32		 ncci;
+	struct capidev	*cdev;
+#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE
+	struct capiminor *minorp;
+#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */
+};
+
+struct capidev {
+	struct list_head list;
+	struct capi20_appl ap;
+	u16		errcode;
+	unsigned        userflags;
+
+	struct sk_buff_head recvqueue;
+	wait_queue_head_t recvwait;
+
+	struct list_head nccis;
+
+	struct mutex lock;
+};
+
+/* -------- global variables ---------------------------------------- */
+
+static DEFINE_MUTEX(capidev_list_lock);
+static LIST_HEAD(capidev_list);
+
+#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE
+
+static DEFINE_SPINLOCK(capiminors_lock);
+static struct capiminor **capiminors;
+
+static struct tty_driver *capinc_tty_driver;
+
+/* -------- datahandles --------------------------------------------- */
+
+static int capiminor_add_ack(struct capiminor *mp, u16 datahandle)
+{
+	struct ackqueue_entry *n;
+
+	n = kmalloc(sizeof(*n), GFP_ATOMIC);
+	if (unlikely(!n)) {
+		printk(KERN_ERR "capi: alloc datahandle failed\n");
+		return -1;
+	}
+	n->datahandle = datahandle;
+	INIT_LIST_HEAD(&n->list);
+	spin_lock_bh(&mp->ackqlock);
+	list_add_tail(&n->list, &mp->ackqueue);
+	mp->nack++;
+	spin_unlock_bh(&mp->ackqlock);
+	return 0;
+}
+
+static int capiminor_del_ack(struct capiminor *mp, u16 datahandle)
+{
+	struct ackqueue_entry *p, *tmp;
+
+	spin_lock_bh(&mp->ackqlock);
+	list_for_each_entry_safe(p, tmp, &mp->ackqueue, list) {
+		if (p->datahandle == datahandle) {
+			list_del(&p->list);
+			mp->nack--;
+			spin_unlock_bh(&mp->ackqlock);
+			kfree(p);
+			return 0;
+		}
+	}
+	spin_unlock_bh(&mp->ackqlock);
+	return -1;
+}
+
+static void capiminor_del_all_ack(struct capiminor *mp)
+{
+	struct ackqueue_entry *p, *tmp;
+
+	list_for_each_entry_safe(p, tmp, &mp->ackqueue, list) {
+		list_del(&p->list);
+		kfree(p);
+		mp->nack--;
+	}
+}
+
+
+/* -------- struct capiminor ---------------------------------------- */
+
+static void capiminor_destroy(struct tty_port *port)
+{
+	struct capiminor *mp = container_of(port, struct capiminor, port);
+
+	kfree_skb(mp->outskb);
+	skb_queue_purge(&mp->inqueue);
+	skb_queue_purge(&mp->outqueue);
+	capiminor_del_all_ack(mp);
+	kfree(mp);
+}
+
+static const struct tty_port_operations capiminor_port_ops = {
+	.destruct = capiminor_destroy,
+};
+
+static struct capiminor *capiminor_alloc(struct capi20_appl *ap, u32 ncci)
+{
+	struct capiminor *mp;
+	struct device *dev;
+	unsigned int minor;
+
+	mp = kzalloc(sizeof(*mp), GFP_KERNEL);
+	if (!mp) {
+		printk(KERN_ERR "capi: can't alloc capiminor\n");
+		return NULL;
+	}
+
+	mp->ap = ap;
+	mp->ncci = ncci;
+	INIT_LIST_HEAD(&mp->ackqueue);
+	spin_lock_init(&mp->ackqlock);
+
+	skb_queue_head_init(&mp->inqueue);
+	skb_queue_head_init(&mp->outqueue);
+	spin_lock_init(&mp->outlock);
+
+	tty_port_init(&mp->port);
+	mp->port.ops = &capiminor_port_ops;
+
+	/* Allocate the least unused minor number. */
+	spin_lock(&capiminors_lock);
+	for (minor = 0; minor < capi_ttyminors; minor++)
+		if (!capiminors[minor]) {
+			capiminors[minor] = mp;
+			break;
+		}
+	spin_unlock(&capiminors_lock);
+
+	if (minor == capi_ttyminors) {
+		printk(KERN_NOTICE "capi: out of minors\n");
+		goto err_out1;
+	}
+
+	mp->minor = minor;
+
+	dev = tty_port_register_device(&mp->port, capinc_tty_driver, minor,
+			NULL);
+	if (IS_ERR(dev))
+		goto err_out2;
+
+	return mp;
+
+err_out2:
+	spin_lock(&capiminors_lock);
+	capiminors[minor] = NULL;
+	spin_unlock(&capiminors_lock);
+
+err_out1:
+	tty_port_put(&mp->port);
+	return NULL;
+}
+
+static struct capiminor *capiminor_get(unsigned int minor)
+{
+	struct capiminor *mp;
+
+	spin_lock(&capiminors_lock);
+	mp = capiminors[minor];
+	if (mp)
+		tty_port_get(&mp->port);
+	spin_unlock(&capiminors_lock);
+
+	return mp;
+}
+
+static inline void capiminor_put(struct capiminor *mp)
+{
+	tty_port_put(&mp->port);
+}
+
+static void capiminor_free(struct capiminor *mp)
+{
+	tty_unregister_device(capinc_tty_driver, mp->minor);
+
+	spin_lock(&capiminors_lock);
+	capiminors[mp->minor] = NULL;
+	spin_unlock(&capiminors_lock);
+
+	capiminor_put(mp);
+}
+
+/* -------- struct capincci ----------------------------------------- */
+
+static void capincci_alloc_minor(struct capidev *cdev, struct capincci *np)
+{
+	if (cdev->userflags & CAPIFLAG_HIGHJACKING)
+		np->minorp = capiminor_alloc(&cdev->ap, np->ncci);
+}
+
+static void capincci_free_minor(struct capincci *np)
+{
+	struct capiminor *mp = np->minorp;
+	struct tty_struct *tty;
+
+	if (mp) {
+		tty = tty_port_tty_get(&mp->port);
+		if (tty) {
+			tty_vhangup(tty);
+			tty_kref_put(tty);
+		}
+
+		capiminor_free(mp);
+	}
+}
+
+static inline unsigned int capincci_minor_opencount(struct capincci *np)
+{
+	struct capiminor *mp = np->minorp;
+	unsigned int count = 0;
+	struct tty_struct *tty;
+
+	if (mp) {
+		tty = tty_port_tty_get(&mp->port);
+		if (tty) {
+			count = tty->count;
+			tty_kref_put(tty);
+		}
+	}
+	return count;
+}
+
+#else /* !CONFIG_ISDN_CAPI_MIDDLEWARE */
+
+static inline void
+capincci_alloc_minor(struct capidev *cdev, struct capincci *np) { }
+static inline void capincci_free_minor(struct capincci *np) { }
+
+#endif /* !CONFIG_ISDN_CAPI_MIDDLEWARE */
+
+static struct capincci *capincci_alloc(struct capidev *cdev, u32 ncci)
+{
+	struct capincci *np;
+
+	np = kzalloc(sizeof(*np), GFP_KERNEL);
+	if (!np)
+		return NULL;
+	np->ncci = ncci;
+	np->cdev = cdev;
+
+	capincci_alloc_minor(cdev, np);
+
+	list_add_tail(&np->list, &cdev->nccis);
+
+	return np;
+}
+
+static void capincci_free(struct capidev *cdev, u32 ncci)
+{
+	struct capincci *np, *tmp;
+
+	list_for_each_entry_safe(np, tmp, &cdev->nccis, list)
+		if (ncci == 0xffffffff || np->ncci == ncci) {
+			capincci_free_minor(np);
+			list_del(&np->list);
+			kfree(np);
+		}
+}
+
+#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE
+static struct capincci *capincci_find(struct capidev *cdev, u32 ncci)
+{
+	struct capincci *np;
+
+	list_for_each_entry(np, &cdev->nccis, list)
+		if (np->ncci == ncci)
+			return np;
+	return NULL;
+}
+
+/* -------- handle data queue --------------------------------------- */
+
+static 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_KERNEL);
+	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->ap->applid);
+		capimsg_setu8 (s, 4, CAPI_DATA_B3);
+		capimsg_setu8 (s, 5, CAPI_RESP);
+		capimsg_setu16(s, 6, atomic_inc_return(&mp->msgid));
+		capimsg_setu32(s, 8, mp->ncci);
+		capimsg_setu16(s, 12, datahandle);
+	}
+	return nskb;
+}
+
+static int handle_recv_skb(struct capiminor *mp, struct sk_buff *skb)
+{
+	unsigned int datalen = skb->len - CAPIMSG_LEN(skb->data);
+	struct tty_struct *tty;
+	struct sk_buff *nskb;
+	u16 errcode, datahandle;
+	struct tty_ldisc *ld;
+	int ret = -1;
+
+	tty = tty_port_tty_get(&mp->port);
+	if (!tty) {
+		pr_debug("capi: currently no receiver\n");
+		return -1;
+	}
+
+	ld = tty_ldisc_ref(tty);
+	if (!ld) {
+		/* fatal error, do not requeue */
+		ret = 0;
+		kfree_skb(skb);
+		goto deref_tty;
+	}
+
+	if (ld->ops->receive_buf == NULL) {
+		pr_debug("capi: ldisc has no receive_buf function\n");
+		/* fatal error, do not requeue */
+		goto free_skb;
+	}
+	if (mp->ttyinstop) {
+		pr_debug("capi: recv tty throttled\n");
+		goto deref_ldisc;
+	}
+
+	if (tty->receive_room < datalen) {
+		pr_debug("capi: no room in tty\n");
+		goto deref_ldisc;
+	}
+
+	nskb = gen_data_b3_resp_for(mp, skb);
+	if (!nskb) {
+		printk(KERN_ERR "capi: gen_data_b3_resp failed\n");
+		goto deref_ldisc;
+	}
+
+	datahandle = CAPIMSG_U16(skb->data, CAPIMSG_BASELEN + 4);
+
+	errcode = capi20_put_message(mp->ap, nskb);
+
+	if (errcode == CAPI_NOERROR) {
+		skb_pull(skb, CAPIMSG_LEN(skb->data));
+		pr_debug("capi: DATA_B3_RESP %u len=%d => ldisc\n",
+			 datahandle, skb->len);
+		ld->ops->receive_buf(tty, skb->data, NULL, skb->len);
+	} else {
+		printk(KERN_ERR "capi: send DATA_B3_RESP failed=%x\n",
+		       errcode);
+		kfree_skb(nskb);
+
+		if (errcode == CAPI_SENDQUEUEFULL)
+			goto deref_ldisc;
+	}
+
+free_skb:
+	ret = 0;
+	kfree_skb(skb);
+
+deref_ldisc:
+	tty_ldisc_deref(ld);
+
+deref_tty:
+	tty_kref_put(tty);
+	return ret;
+}
+
+static void handle_minor_recv(struct capiminor *mp)
+{
+	struct sk_buff *skb;
+
+	while ((skb = skb_dequeue(&mp->inqueue)) != NULL)
+		if (handle_recv_skb(mp, skb) < 0) {
+			skb_queue_head(&mp->inqueue, skb);
+			return;
+		}
+}
+
+static void handle_minor_send(struct capiminor *mp)
+{
+	struct tty_struct *tty;
+	struct sk_buff *skb;
+	u16 len;
+	u16 errcode;
+	u16 datahandle;
+
+	tty = tty_port_tty_get(&mp->port);
+	if (!tty)
+		return;
+
+	if (mp->ttyoutstop) {
+		pr_debug("capi: send: tty stopped\n");
+		tty_kref_put(tty);
+		return;
+	}
+
+	while (1) {
+		spin_lock_bh(&mp->outlock);
+		skb = __skb_dequeue(&mp->outqueue);
+		if (!skb) {
+			spin_unlock_bh(&mp->outlock);
+			break;
+		}
+		len = (u16)skb->len;
+		mp->outbytes -= len;
+		spin_unlock_bh(&mp->outlock);
+
+		datahandle = atomic_inc_return(&mp->datahandle);
+		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->ap->applid);
+		capimsg_setu8 (skb->data, 4, CAPI_DATA_B3);
+		capimsg_setu8 (skb->data, 5, CAPI_REQ);
+		capimsg_setu16(skb->data, 6, atomic_inc_return(&mp->msgid));
+		capimsg_setu32(skb->data, 8, mp->ncci);	/* NCCI */
+		capimsg_setu32(skb->data, 12, (u32)(long)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 (capiminor_add_ack(mp, datahandle) < 0) {
+			skb_pull(skb, CAPI_DATA_B3_REQ_LEN);
+
+			spin_lock_bh(&mp->outlock);
+			__skb_queue_head(&mp->outqueue, skb);
+			mp->outbytes += len;
+			spin_unlock_bh(&mp->outlock);
+
+			break;
+		}
+		errcode = capi20_put_message(mp->ap, skb);
+		if (errcode == CAPI_NOERROR) {
+			pr_debug("capi: DATA_B3_REQ %u len=%u\n",
+				 datahandle, len);
+			continue;
+		}
+		capiminor_del_ack(mp, datahandle);
+
+		if (errcode == CAPI_SENDQUEUEFULL) {
+			skb_pull(skb, CAPI_DATA_B3_REQ_LEN);
+
+			spin_lock_bh(&mp->outlock);
+			__skb_queue_head(&mp->outqueue, skb);
+			mp->outbytes += len;
+			spin_unlock_bh(&mp->outlock);
+
+			break;
+		}
+
+		/* ups, drop packet */
+		printk(KERN_ERR "capi: put_message = %x\n", errcode);
+		kfree_skb(skb);
+	}
+	tty_kref_put(tty);
+}
+
+#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */
+/* -------- function called by lower level -------------------------- */
+
+static void capi_recv_message(struct capi20_appl *ap, struct sk_buff *skb)
+{
+	struct capidev *cdev = ap->private;
+#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE
+	struct capiminor *mp;
+	u16 datahandle;
+	struct capincci *np;
+#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */
+
+	mutex_lock(&cdev->lock);
+
+	if (CAPIMSG_CMD(skb->data) == CAPI_CONNECT_B3_CONF) {
+		u16 info = CAPIMSG_U16(skb->data, 12); // Info field
+		if ((info & 0xff00) == 0)
+			capincci_alloc(cdev, CAPIMSG_NCCI(skb->data));
+	}
+	if (CAPIMSG_CMD(skb->data) == CAPI_CONNECT_B3_IND)
+		capincci_alloc(cdev, CAPIMSG_NCCI(skb->data));
+
+	if (CAPIMSG_COMMAND(skb->data) != CAPI_DATA_B3) {
+		skb_queue_tail(&cdev->recvqueue, skb);
+		wake_up_interruptible(&cdev->recvwait);
+		goto unlock_out;
+	}
+
+#ifndef CONFIG_ISDN_CAPI_MIDDLEWARE
+	skb_queue_tail(&cdev->recvqueue, skb);
+	wake_up_interruptible(&cdev->recvwait);
+
+#else /* CONFIG_ISDN_CAPI_MIDDLEWARE */
+
+	np = capincci_find(cdev, CAPIMSG_CONTROL(skb->data));
+	if (!np) {
+		printk(KERN_ERR "BUG: capi_signal: ncci not found\n");
+		skb_queue_tail(&cdev->recvqueue, skb);
+		wake_up_interruptible(&cdev->recvwait);
+		goto unlock_out;
+	}
+
+	mp = np->minorp;
+	if (!mp) {
+		skb_queue_tail(&cdev->recvqueue, skb);
+		wake_up_interruptible(&cdev->recvwait);
+		goto unlock_out;
+	}
+	if (CAPIMSG_SUBCOMMAND(skb->data) == CAPI_IND) {
+		datahandle = CAPIMSG_U16(skb->data, CAPIMSG_BASELEN + 4 + 4 + 2);
+		pr_debug("capi_signal: DATA_B3_IND %u len=%d\n",
+			 datahandle, skb->len-CAPIMSG_LEN(skb->data));
+		skb_queue_tail(&mp->inqueue, skb);
+
+		handle_minor_recv(mp);
+
+	} else if (CAPIMSG_SUBCOMMAND(skb->data) == CAPI_CONF) {
+
+		datahandle = CAPIMSG_U16(skb->data, CAPIMSG_BASELEN + 4);
+		pr_debug("capi_signal: DATA_B3_CONF %u 0x%x\n",
+			 datahandle,
+			 CAPIMSG_U16(skb->data, CAPIMSG_BASELEN + 4 + 2));
+		kfree_skb(skb);
+		capiminor_del_ack(mp, datahandle);
+		tty_port_tty_wakeup(&mp->port);
+		handle_minor_send(mp);
+
+	} else {
+		/* ups, let capi application handle it :-) */
+		skb_queue_tail(&cdev->recvqueue, skb);
+		wake_up_interruptible(&cdev->recvwait);
+	}
+#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */
+
+unlock_out:
+	mutex_unlock(&cdev->lock);
+}
+
+/* -------- file_operations for capidev ----------------------------- */
+
+static ssize_t
+capi_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
+{
+	struct capidev *cdev = file->private_data;
+	struct sk_buff *skb;
+	size_t copied;
+	int err;
+
+	if (!cdev->ap.applid)
+		return -ENODEV;
+
+	skb = skb_dequeue(&cdev->recvqueue);
+	if (!skb) {
+		if (file->f_flags & O_NONBLOCK)
+			return -EAGAIN;
+		err = wait_event_interruptible(cdev->recvwait,
+					       (skb = skb_dequeue(&cdev->recvqueue)));
+		if (err)
+			return err;
+	}
+	if (skb->len > count) {
+		skb_queue_head(&cdev->recvqueue, skb);
+		return -EMSGSIZE;
+	}
+	if (copy_to_user(buf, skb->data, skb->len)) {
+		skb_queue_head(&cdev->recvqueue, skb);
+		return -EFAULT;
+	}
+	copied = skb->len;
+
+	kfree_skb(skb);
+
+	return copied;
+}
+
+static ssize_t
+capi_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
+{
+	struct capidev *cdev = file->private_data;
+	struct sk_buff *skb;
+	u16 mlen;
+
+	if (!cdev->ap.applid)
+		return -ENODEV;
+
+	if (count < CAPIMSG_BASELEN)
+		return -EINVAL;
+
+	skb = alloc_skb(count, GFP_USER);
+	if (!skb)
+		return -ENOMEM;
+
+	if (copy_from_user(skb_put(skb, count), buf, count)) {
+		kfree_skb(skb);
+		return -EFAULT;
+	}
+	mlen = CAPIMSG_LEN(skb->data);
+	if (CAPIMSG_CMD(skb->data) == CAPI_DATA_B3_REQ) {
+		if (count < CAPI_DATA_B3_REQ_LEN ||
+		    (size_t)(mlen + CAPIMSG_DATALEN(skb->data)) != count) {
+			kfree_skb(skb);
+			return -EINVAL;
+		}
+	} else {
+		if (mlen != count) {
+			kfree_skb(skb);
+			return -EINVAL;
+		}
+	}
+	CAPIMSG_SETAPPID(skb->data, cdev->ap.applid);
+
+	if (CAPIMSG_CMD(skb->data) == CAPI_DISCONNECT_B3_RESP) {
+		if (count < CAPI_DISCONNECT_B3_RESP_LEN) {
+			kfree_skb(skb);
+			return -EINVAL;
+		}
+		mutex_lock(&cdev->lock);
+		capincci_free(cdev, CAPIMSG_NCCI(skb->data));
+		mutex_unlock(&cdev->lock);
+	}
+
+	cdev->errcode = capi20_put_message(&cdev->ap, skb);
+
+	if (cdev->errcode) {
+		kfree_skb(skb);
+		return -EIO;
+	}
+	return count;
+}
+
+static unsigned int
+capi_poll(struct file *file, poll_table *wait)
+{
+	struct capidev *cdev = file->private_data;
+	unsigned int mask = 0;
+
+	if (!cdev->ap.applid)
+		return POLLERR;
+
+	poll_wait(file, &(cdev->recvwait), wait);
+	mask = POLLOUT | POLLWRNORM;
+	if (!skb_queue_empty_lockless(&cdev->recvqueue))
+		mask |= POLLIN | POLLRDNORM;
+	return mask;
+}
+
+static int
+capi_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct capidev *cdev = file->private_data;
+	capi_ioctl_struct data;
+	int retval = -EINVAL;
+	void __user *argp = (void __user *)arg;
+
+	switch (cmd) {
+	case CAPI_REGISTER:
+		mutex_lock(&cdev->lock);
+
+		if (cdev->ap.applid) {
+			retval = -EEXIST;
+			goto register_out;
+		}
+		if (copy_from_user(&cdev->ap.rparam, argp,
+				   sizeof(struct capi_register_params))) {
+			retval = -EFAULT;
+			goto register_out;
+		}
+		cdev->ap.private = cdev;
+		cdev->ap.recv_message = capi_recv_message;
+		cdev->errcode = capi20_register(&cdev->ap);
+		retval = (int)cdev->ap.applid;
+		if (cdev->errcode) {
+			cdev->ap.applid = 0;
+			retval = -EIO;
+		}
+
+register_out:
+		mutex_unlock(&cdev->lock);
+		return retval;
+
+	case CAPI_GET_VERSION:
+		if (copy_from_user(&data.contr, argp,
+				   sizeof(data.contr)))
+			return -EFAULT;
+		cdev->errcode = capi20_get_version(data.contr, &data.version);
+		if (cdev->errcode)
+			return -EIO;
+		if (copy_to_user(argp, &data.version,
+				 sizeof(data.version)))
+			return -EFAULT;
+		return 0;
+
+	case CAPI_GET_SERIAL:
+		if (copy_from_user(&data.contr, argp,
+				   sizeof(data.contr)))
+			return -EFAULT;
+		cdev->errcode = capi20_get_serial(data.contr, data.serial);
+		if (cdev->errcode)
+			return -EIO;
+		if (copy_to_user(argp, data.serial,
+				 sizeof(data.serial)))
+			return -EFAULT;
+		return 0;
+
+	case CAPI_GET_PROFILE:
+		if (copy_from_user(&data.contr, argp,
+				   sizeof(data.contr)))
+			return -EFAULT;
+
+		if (data.contr == 0) {
+			cdev->errcode = capi20_get_profile(data.contr, &data.profile);
+			if (cdev->errcode)
+				return -EIO;
+
+			retval = copy_to_user(argp,
+					      &data.profile.ncontroller,
+					      sizeof(data.profile.ncontroller));
+
+		} else {
+			cdev->errcode = capi20_get_profile(data.contr, &data.profile);
+			if (cdev->errcode)
+				return -EIO;
+
+			retval = copy_to_user(argp, &data.profile,
+					      sizeof(data.profile));
+		}
+		if (retval)
+			return -EFAULT;
+		return 0;
+
+	case CAPI_GET_MANUFACTURER:
+		if (copy_from_user(&data.contr, argp,
+				   sizeof(data.contr)))
+			return -EFAULT;
+		cdev->errcode = capi20_get_manufacturer(data.contr, data.manufacturer);
+		if (cdev->errcode)
+			return -EIO;
+
+		if (copy_to_user(argp, data.manufacturer,
+				 sizeof(data.manufacturer)))
+			return -EFAULT;
+
+		return 0;
+
+	case CAPI_GET_ERRCODE:
+		data.errcode = cdev->errcode;
+		cdev->errcode = CAPI_NOERROR;
+		if (arg) {
+			if (copy_to_user(argp, &data.errcode,
+					 sizeof(data.errcode)))
+				return -EFAULT;
+		}
+		return data.errcode;
+
+	case CAPI_INSTALLED:
+		if (capi20_isinstalled() == CAPI_NOERROR)
+			return 0;
+		return -ENXIO;
+
+	case CAPI_MANUFACTURER_CMD: {
+		struct capi_manufacturer_cmd mcmd;
+		if (!capable(CAP_SYS_ADMIN))
+			return -EPERM;
+		if (copy_from_user(&mcmd, argp, sizeof(mcmd)))
+			return -EFAULT;
+		return capi20_manufacturer(mcmd.cmd, mcmd.data);
+	}
+	case CAPI_SET_FLAGS:
+	case CAPI_CLR_FLAGS: {
+		unsigned userflags;
+
+		if (copy_from_user(&userflags, argp, sizeof(userflags)))
+			return -EFAULT;
+
+		mutex_lock(&cdev->lock);
+		if (cmd == CAPI_SET_FLAGS)
+			cdev->userflags |= userflags;
+		else
+			cdev->userflags &= ~userflags;
+		mutex_unlock(&cdev->lock);
+		return 0;
+	}
+	case CAPI_GET_FLAGS:
+		if (copy_to_user(argp, &cdev->userflags,
+				 sizeof(cdev->userflags)))
+			return -EFAULT;
+		return 0;
+
+#ifndef CONFIG_ISDN_CAPI_MIDDLEWARE
+	case CAPI_NCCI_OPENCOUNT:
+		return 0;
+
+#else /* CONFIG_ISDN_CAPI_MIDDLEWARE */
+	case CAPI_NCCI_OPENCOUNT: {
+		struct capincci *nccip;
+		unsigned ncci;
+		int count = 0;
+
+		if (copy_from_user(&ncci, argp, sizeof(ncci)))
+			return -EFAULT;
+
+		mutex_lock(&cdev->lock);
+		nccip = capincci_find(cdev, (u32)ncci);
+		if (nccip)
+			count = capincci_minor_opencount(nccip);
+		mutex_unlock(&cdev->lock);
+		return count;
+	}
+
+	case CAPI_NCCI_GETUNIT: {
+		struct capincci *nccip;
+		struct capiminor *mp;
+		unsigned ncci;
+		int unit = -ESRCH;
+
+		if (copy_from_user(&ncci, argp, sizeof(ncci)))
+			return -EFAULT;
+
+		mutex_lock(&cdev->lock);
+		nccip = capincci_find(cdev, (u32)ncci);
+		if (nccip) {
+			mp = nccip->minorp;
+			if (mp)
+				unit = mp->minor;
+		}
+		mutex_unlock(&cdev->lock);
+		return unit;
+	}
+#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static long
+capi_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	int ret;
+
+	mutex_lock(&capi_mutex);
+	ret = capi_ioctl(file, cmd, arg);
+	mutex_unlock(&capi_mutex);
+
+	return ret;
+}
+
+static int capi_open(struct inode *inode, struct file *file)
+{
+	struct capidev *cdev;
+
+	cdev = kzalloc(sizeof(*cdev), GFP_KERNEL);
+	if (!cdev)
+		return -ENOMEM;
+
+	mutex_init(&cdev->lock);
+	skb_queue_head_init(&cdev->recvqueue);
+	init_waitqueue_head(&cdev->recvwait);
+	INIT_LIST_HEAD(&cdev->nccis);
+	file->private_data = cdev;
+
+	mutex_lock(&capidev_list_lock);
+	list_add_tail(&cdev->list, &capidev_list);
+	mutex_unlock(&capidev_list_lock);
+
+	return nonseekable_open(inode, file);
+}
+
+static int capi_release(struct inode *inode, struct file *file)
+{
+	struct capidev *cdev = file->private_data;
+
+	mutex_lock(&capidev_list_lock);
+	list_del(&cdev->list);
+	mutex_unlock(&capidev_list_lock);
+
+	if (cdev->ap.applid)
+		capi20_release(&cdev->ap);
+	skb_queue_purge(&cdev->recvqueue);
+	capincci_free(cdev, 0xffffffff);
+
+	kfree(cdev);
+	return 0;
+}
+
+static const struct file_operations capi_fops =
+{
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.read		= capi_read,
+	.write		= capi_write,
+	.poll		= capi_poll,
+	.unlocked_ioctl	= capi_unlocked_ioctl,
+	.open		= capi_open,
+	.release	= capi_release,
+};
+
+#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE
+/* -------- tty_operations for capincci ----------------------------- */
+
+static int
+capinc_tty_install(struct tty_driver *driver, struct tty_struct *tty)
+{
+	struct capiminor *mp = capiminor_get(tty->index);
+	int ret = tty_standard_install(driver, tty);
+
+	if (ret == 0)
+		tty->driver_data = mp;
+	else
+		capiminor_put(mp);
+	return ret;
+}
+
+static void capinc_tty_cleanup(struct tty_struct *tty)
+{
+	struct capiminor *mp = tty->driver_data;
+	tty->driver_data = NULL;
+	capiminor_put(mp);
+}
+
+static int capinc_tty_open(struct tty_struct *tty, struct file *filp)
+{
+	struct capiminor *mp = tty->driver_data;
+	int err;
+
+	err = tty_port_open(&mp->port, tty, filp);
+	if (err)
+		return err;
+
+	handle_minor_recv(mp);
+	return 0;
+}
+
+static void capinc_tty_close(struct tty_struct *tty, struct file *filp)
+{
+	struct capiminor *mp = tty->driver_data;
+
+	tty_port_close(&mp->port, tty, filp);
+}
+
+static int capinc_tty_write(struct tty_struct *tty,
+			    const unsigned char *buf, int count)
+{
+	struct capiminor *mp = tty->driver_data;
+	struct sk_buff *skb;
+
+	pr_debug("capinc_tty_write(count=%d)\n", count);
+
+	spin_lock_bh(&mp->outlock);
+	skb = mp->outskb;
+	if (skb) {
+		mp->outskb = NULL;
+		__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");
+		spin_unlock_bh(&mp->outlock);
+		return -ENOMEM;
+	}
+
+	skb_reserve(skb, CAPI_DATA_B3_REQ_LEN);
+	skb_put_data(skb, buf, count);
+
+	__skb_queue_tail(&mp->outqueue, skb);
+	mp->outbytes += skb->len;
+	spin_unlock_bh(&mp->outlock);
+
+	handle_minor_send(mp);
+
+	return count;
+}
+
+static int capinc_tty_put_char(struct tty_struct *tty, unsigned char ch)
+{
+	struct capiminor *mp = tty->driver_data;
+	bool invoke_send = false;
+	struct sk_buff *skb;
+	int ret = 1;
+
+	pr_debug("capinc_put_char(%u)\n", ch);
+
+	spin_lock_bh(&mp->outlock);
+	skb = mp->outskb;
+	if (skb) {
+		if (skb_tailroom(skb) > 0) {
+			skb_put_u8(skb, ch);
+			goto unlock_out;
+		}
+		mp->outskb = NULL;
+		__skb_queue_tail(&mp->outqueue, skb);
+		mp->outbytes += skb->len;
+		invoke_send = true;
+	}
+
+	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_u8(skb, ch);
+		mp->outskb = skb;
+	} else {
+		printk(KERN_ERR "capinc_put_char: char %u lost\n", ch);
+		ret = 0;
+	}
+
+unlock_out:
+	spin_unlock_bh(&mp->outlock);
+
+	if (invoke_send)
+		handle_minor_send(mp);
+
+	return ret;
+}
+
+static void capinc_tty_flush_chars(struct tty_struct *tty)
+{
+	struct capiminor *mp = tty->driver_data;
+	struct sk_buff *skb;
+
+	pr_debug("capinc_tty_flush_chars\n");
+
+	spin_lock_bh(&mp->outlock);
+	skb = mp->outskb;
+	if (skb) {
+		mp->outskb = NULL;
+		__skb_queue_tail(&mp->outqueue, skb);
+		mp->outbytes += skb->len;
+		spin_unlock_bh(&mp->outlock);
+
+		handle_minor_send(mp);
+	} else
+		spin_unlock_bh(&mp->outlock);
+
+	handle_minor_recv(mp);
+}
+
+static int capinc_tty_write_room(struct tty_struct *tty)
+{
+	struct capiminor *mp = tty->driver_data;
+	int room;
+
+	room = CAPINC_MAX_SENDQUEUE-skb_queue_len(&mp->outqueue);
+	room *= CAPI_MAX_BLKSIZE;
+	pr_debug("capinc_tty_write_room = %d\n", room);
+	return room;
+}
+
+static int capinc_tty_chars_in_buffer(struct tty_struct *tty)
+{
+	struct capiminor *mp = tty->driver_data;
+
+	pr_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));
+	return mp->outbytes;
+}
+
+static int capinc_tty_ioctl(struct tty_struct *tty,
+			    unsigned int cmd, unsigned long arg)
+{
+	return -ENOIOCTLCMD;
+}
+
+static void capinc_tty_set_termios(struct tty_struct *tty, struct ktermios *old)
+{
+	pr_debug("capinc_tty_set_termios\n");
+}
+
+static void capinc_tty_throttle(struct tty_struct *tty)
+{
+	struct capiminor *mp = tty->driver_data;
+	pr_debug("capinc_tty_throttle\n");
+	mp->ttyinstop = 1;
+}
+
+static void capinc_tty_unthrottle(struct tty_struct *tty)
+{
+	struct capiminor *mp = tty->driver_data;
+
+	pr_debug("capinc_tty_unthrottle\n");
+	mp->ttyinstop = 0;
+	handle_minor_recv(mp);
+}
+
+static void capinc_tty_stop(struct tty_struct *tty)
+{
+	struct capiminor *mp = tty->driver_data;
+
+	pr_debug("capinc_tty_stop\n");
+	mp->ttyoutstop = 1;
+}
+
+static void capinc_tty_start(struct tty_struct *tty)
+{
+	struct capiminor *mp = tty->driver_data;
+
+	pr_debug("capinc_tty_start\n");
+	mp->ttyoutstop = 0;
+	handle_minor_send(mp);
+}
+
+static void capinc_tty_hangup(struct tty_struct *tty)
+{
+	struct capiminor *mp = tty->driver_data;
+
+	pr_debug("capinc_tty_hangup\n");
+	tty_port_hangup(&mp->port);
+}
+
+static int capinc_tty_break_ctl(struct tty_struct *tty, int state)
+{
+	pr_debug("capinc_tty_break_ctl(%d)\n", state);
+	return 0;
+}
+
+static void capinc_tty_flush_buffer(struct tty_struct *tty)
+{
+	pr_debug("capinc_tty_flush_buffer\n");
+}
+
+static void capinc_tty_set_ldisc(struct tty_struct *tty)
+{
+	pr_debug("capinc_tty_set_ldisc\n");
+}
+
+static void capinc_tty_send_xchar(struct tty_struct *tty, char ch)
+{
+	pr_debug("capinc_tty_send_xchar(%d)\n", ch);
+}
+
+static const struct tty_operations capinc_ops = {
+	.open = capinc_tty_open,
+	.close = capinc_tty_close,
+	.write = capinc_tty_write,
+	.put_char = capinc_tty_put_char,
+	.flush_chars = capinc_tty_flush_chars,
+	.write_room = capinc_tty_write_room,
+	.chars_in_buffer = capinc_tty_chars_in_buffer,
+	.ioctl = capinc_tty_ioctl,
+	.set_termios = capinc_tty_set_termios,
+	.throttle = capinc_tty_throttle,
+	.unthrottle = capinc_tty_unthrottle,
+	.stop = capinc_tty_stop,
+	.start = capinc_tty_start,
+	.hangup = capinc_tty_hangup,
+	.break_ctl = capinc_tty_break_ctl,
+	.flush_buffer = capinc_tty_flush_buffer,
+	.set_ldisc = capinc_tty_set_ldisc,
+	.send_xchar = capinc_tty_send_xchar,
+	.install = capinc_tty_install,
+	.cleanup = capinc_tty_cleanup,
+};
+
+static int __init capinc_tty_init(void)
+{
+	struct tty_driver *drv;
+	int err;
+
+	if (capi_ttyminors > CAPINC_MAX_PORTS)
+		capi_ttyminors = CAPINC_MAX_PORTS;
+	if (capi_ttyminors <= 0)
+		capi_ttyminors = CAPINC_NR_PORTS;
+
+	capiminors = kzalloc(sizeof(struct capiminor *) * capi_ttyminors,
+			     GFP_KERNEL);
+	if (!capiminors)
+		return -ENOMEM;
+
+	drv = alloc_tty_driver(capi_ttyminors);
+	if (!drv) {
+		kfree(capiminors);
+		return -ENOMEM;
+	}
+	drv->driver_name = "capi_nc";
+	drv->name = "capi!";
+	drv->major = 0;
+	drv->minor_start = 0;
+	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 |
+		TTY_DRIVER_DYNAMIC_DEV;
+	tty_set_operations(drv, &capinc_ops);
+
+	err = tty_register_driver(drv);
+	if (err) {
+		put_tty_driver(drv);
+		kfree(capiminors);
+		printk(KERN_ERR "Couldn't register capi_nc driver\n");
+		return err;
+	}
+	capinc_tty_driver = drv;
+	return 0;
+}
+
+static void __exit capinc_tty_exit(void)
+{
+	tty_unregister_driver(capinc_tty_driver);
+	put_tty_driver(capinc_tty_driver);
+	kfree(capiminors);
+}
+
+#else /* !CONFIG_ISDN_CAPI_MIDDLEWARE */
+
+static inline int capinc_tty_init(void)
+{
+	return 0;
+}
+
+static inline void capinc_tty_exit(void) { }
+
+#endif /* !CONFIG_ISDN_CAPI_MIDDLEWARE */
+
+/* -------- /proc functions ----------------------------------------- */
+
+/*
+ * /proc/capi/capi20:
+ *  minor applid nrecvctlpkt nrecvdatapkt nsendctlpkt nsenddatapkt
+ */
+static int capi20_proc_show(struct seq_file *m, void *v)
+{
+	struct capidev *cdev;
+	struct list_head *l;
+
+	mutex_lock(&capidev_list_lock);
+	list_for_each(l, &capidev_list) {
+		cdev = list_entry(l, struct capidev, list);
+		seq_printf(m, "0 %d %lu %lu %lu %lu\n",
+			   cdev->ap.applid,
+			   cdev->ap.nrecvctlpkt,
+			   cdev->ap.nrecvdatapkt,
+			   cdev->ap.nsentctlpkt,
+			   cdev->ap.nsentdatapkt);
+	}
+	mutex_unlock(&capidev_list_lock);
+	return 0;
+}
+
+static int capi20_proc_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, capi20_proc_show, NULL);
+}
+
+static const struct file_operations capi20_proc_fops = {
+	.owner		= THIS_MODULE,
+	.open		= capi20_proc_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+/*
+ * /proc/capi/capi20ncci:
+ *  applid ncci
+ */
+static int capi20ncci_proc_show(struct seq_file *m, void *v)
+{
+	struct capidev *cdev;
+	struct capincci *np;
+
+	mutex_lock(&capidev_list_lock);
+	list_for_each_entry(cdev, &capidev_list, list) {
+		mutex_lock(&cdev->lock);
+		list_for_each_entry(np, &cdev->nccis, list)
+			seq_printf(m, "%d 0x%x\n", cdev->ap.applid, np->ncci);
+		mutex_unlock(&cdev->lock);
+	}
+	mutex_unlock(&capidev_list_lock);
+	return 0;
+}
+
+static int capi20ncci_proc_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, capi20ncci_proc_show, NULL);
+}
+
+static const struct file_operations capi20ncci_proc_fops = {
+	.owner		= THIS_MODULE,
+	.open		= capi20ncci_proc_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+static void __init proc_init(void)
+{
+	proc_create("capi/capi20", 0, NULL, &capi20_proc_fops);
+	proc_create("capi/capi20ncci", 0, NULL, &capi20ncci_proc_fops);
+}
+
+static void __exit proc_exit(void)
+{
+	remove_proc_entry("capi/capi20", NULL);
+	remove_proc_entry("capi/capi20ncci", NULL);
+}
+
+/* -------- init function and module interface ---------------------- */
+
+
+static int __init capi_init(void)
+{
+	const char *compileinfo;
+	int major_ret;
+
+	major_ret = register_chrdev(capi_major, "capi20", &capi_fops);
+	if (major_ret < 0) {
+		printk(KERN_ERR "capi20: unable to get major %d\n", capi_major);
+		return major_ret;
+	}
+	capi_class = class_create(THIS_MODULE, "capi");
+	if (IS_ERR(capi_class)) {
+		unregister_chrdev(capi_major, "capi20");
+		return PTR_ERR(capi_class);
+	}
+
+	device_create(capi_class, NULL, MKDEV(capi_major, 0), NULL, "capi20");
+
+	if (capinc_tty_init() < 0) {
+		device_destroy(capi_class, MKDEV(capi_major, 0));
+		class_destroy(capi_class);
+		unregister_chrdev(capi_major, "capi20");
+		return -ENOMEM;
+	}
+
+	proc_init();
+
+#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE
+	compileinfo = " (middleware)";
+#else
+	compileinfo = " (no middleware)";
+#endif
+	printk(KERN_NOTICE "CAPI 2.0 started up with major %d%s\n",
+	       capi_major, compileinfo);
+
+	return 0;
+}
+
+static void __exit capi_exit(void)
+{
+	proc_exit();
+
+	device_destroy(capi_class, MKDEV(capi_major, 0));
+	class_destroy(capi_class);
+	unregister_chrdev(capi_major, "capi20");
+
+	capinc_tty_exit();
+}
+
+module_init(capi_init);
+module_exit(capi_exit);
diff --git a/src/kernel/linux/v4.14/drivers/isdn/capi/capidrv.c b/src/kernel/linux/v4.14/drivers/isdn/capi/capidrv.c
new file mode 100644
index 0000000..89dd130
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/isdn/capi/capidrv.c
@@ -0,0 +1,2536 @@
+/* $Id: capidrv.c,v 1.1.2.2 2004/01/12 23:17:24 keil Exp $
+ *
+ * ISDN4Linux Driver, using capi20 interface (kernelcapi)
+ *
+ * Copyright 1997 by Carsten Paeth <calle@calle.de>
+ *
+ * This software may be used and distributed according to the terms
+ * of the GNU General Public License, incorporated herein by reference.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/slab.h>
+#include <linux/fcntl.h>
+#include <linux/fs.h>
+#include <linux/signal.h>
+#include <linux/mm.h>
+#include <linux/timer.h>
+#include <linux/wait.h>
+#include <linux/skbuff.h>
+#include <linux/isdn.h>
+#include <linux/isdnif.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/capi.h>
+#include <linux/kernelcapi.h>
+#include <linux/ctype.h>
+#include <linux/init.h>
+#include <linux/moduleparam.h>
+
+#include <linux/isdn/capiutil.h>
+#include <linux/isdn/capicmd.h>
+#include "capidrv.h"
+
+static int debugmode = 0;
+
+MODULE_DESCRIPTION("CAPI4Linux: Interface to ISDN4Linux");
+MODULE_AUTHOR("Carsten Paeth");
+MODULE_LICENSE("GPL");
+module_param(debugmode, uint, S_IRUGO | S_IWUSR);
+
+/* -------- type definitions ----------------------------------------- */
+
+
+struct capidrv_contr {
+
+	struct capidrv_contr *next;
+	struct module *owner;
+	u32 contrnr;
+	char name[20];
+
+	/*
+	 * for isdn4linux
+	 */
+	isdn_if interface;
+	int myid;
+
+	/*
+	 * LISTEN state
+	 */
+	int state;
+	u32 cipmask;
+	u32 cipmask2;
+	struct timer_list listentimer;
+
+	/*
+	 * ID of capi message sent
+	 */
+	u16 msgid;
+
+	/*
+	 * B-Channels
+	 */
+	int nbchan;
+	struct capidrv_bchan {
+		struct capidrv_contr *contr;
+		u8 msn[ISDN_MSNLEN];
+		int l2;
+		int l3;
+		u8 num[ISDN_MSNLEN];
+		u8 mynum[ISDN_MSNLEN];
+		int si1;
+		int si2;
+		int incoming;
+		int disconnecting;
+		struct capidrv_plci {
+			struct capidrv_plci *next;
+			u32 plci;
+			u32 ncci;	/* ncci for CONNECT_ACTIVE_IND */
+			u16 msgid;	/* to identfy CONNECT_CONF */
+			int chan;
+			int state;
+			int leasedline;
+			struct capidrv_ncci {
+				struct capidrv_ncci *next;
+				struct capidrv_plci *plcip;
+				u32 ncci;
+				u16 msgid;	/* to identfy CONNECT_B3_CONF */
+				int chan;
+				int state;
+				int oldstate;
+				/* */
+				u16 datahandle;
+				struct ncci_datahandle_queue {
+					struct ncci_datahandle_queue *next;
+					u16                         datahandle;
+					int                           len;
+				} *ackqueue;
+			} *ncci_list;
+		} *plcip;
+		struct capidrv_ncci *nccip;
+	} *bchans;
+
+	struct capidrv_plci *plci_list;
+
+	/* for q931 data */
+	u8  q931_buf[4096];
+	u8 *q931_read;
+	u8 *q931_write;
+	u8 *q931_end;
+};
+
+
+struct capidrv_data {
+	struct capi20_appl ap;
+	int ncontr;
+	struct capidrv_contr *contr_list;
+};
+
+typedef struct capidrv_plci capidrv_plci;
+typedef struct capidrv_ncci capidrv_ncci;
+typedef struct capidrv_contr capidrv_contr;
+typedef struct capidrv_data capidrv_data;
+typedef struct capidrv_bchan capidrv_bchan;
+
+/* -------- data definitions ----------------------------------------- */
+
+static capidrv_data global;
+static DEFINE_SPINLOCK(global_lock);
+
+static void handle_dtrace_data(capidrv_contr *card,
+			       int send, int level2, u8 *data, u16 len);
+
+/* -------- convert functions ---------------------------------------- */
+
+static inline u32 b1prot(int l2, int l3)
+{
+	switch (l2) {
+	case ISDN_PROTO_L2_X75I:
+	case ISDN_PROTO_L2_X75UI:
+	case ISDN_PROTO_L2_X75BUI:
+		return 0;
+	case ISDN_PROTO_L2_HDLC:
+	default:
+		return 0;
+	case ISDN_PROTO_L2_TRANS:
+		return 1;
+	case ISDN_PROTO_L2_V11096:
+	case ISDN_PROTO_L2_V11019:
+	case ISDN_PROTO_L2_V11038:
+		return 2;
+	case ISDN_PROTO_L2_FAX:
+		return 4;
+	case ISDN_PROTO_L2_MODEM:
+		return 8;
+	}
+}
+
+static inline u32 b2prot(int l2, int l3)
+{
+	switch (l2) {
+	case ISDN_PROTO_L2_X75I:
+	case ISDN_PROTO_L2_X75UI:
+	case ISDN_PROTO_L2_X75BUI:
+	default:
+		return 0;
+	case ISDN_PROTO_L2_HDLC:
+	case ISDN_PROTO_L2_TRANS:
+	case ISDN_PROTO_L2_V11096:
+	case ISDN_PROTO_L2_V11019:
+	case ISDN_PROTO_L2_V11038:
+	case ISDN_PROTO_L2_MODEM:
+		return 1;
+	case ISDN_PROTO_L2_FAX:
+		return 4;
+	}
+}
+
+static inline u32 b3prot(int l2, int l3)
+{
+	switch (l2) {
+	case ISDN_PROTO_L2_X75I:
+	case ISDN_PROTO_L2_X75UI:
+	case ISDN_PROTO_L2_X75BUI:
+	case ISDN_PROTO_L2_HDLC:
+	case ISDN_PROTO_L2_TRANS:
+	case ISDN_PROTO_L2_V11096:
+	case ISDN_PROTO_L2_V11019:
+	case ISDN_PROTO_L2_V11038:
+	case ISDN_PROTO_L2_MODEM:
+	default:
+		return 0;
+	case ISDN_PROTO_L2_FAX:
+		return 4;
+	}
+}
+
+static _cstruct b1config_async_v110(u16 rate)
+{
+	/* CAPI-Spec "B1 Configuration" */
+	static unsigned char buf[9];
+	buf[0] = 8; /* len */
+	/* maximum bitrate */
+	buf[1] = rate & 0xff; buf[2] = (rate >> 8) & 0xff;
+	buf[3] = 8; buf[4] = 0; /* 8 bits per character */
+	buf[5] = 0; buf[6] = 0; /* parity none */
+	buf[7] = 0; buf[8] = 0; /* 1 stop bit */
+	return buf;
+}
+
+static _cstruct b1config(int l2, int l3)
+{
+	switch (l2) {
+	case ISDN_PROTO_L2_X75I:
+	case ISDN_PROTO_L2_X75UI:
+	case ISDN_PROTO_L2_X75BUI:
+	case ISDN_PROTO_L2_HDLC:
+	case ISDN_PROTO_L2_TRANS:
+	default:
+		return NULL;
+	case ISDN_PROTO_L2_V11096:
+		return b1config_async_v110(9600);
+	case ISDN_PROTO_L2_V11019:
+		return b1config_async_v110(19200);
+	case ISDN_PROTO_L2_V11038:
+		return b1config_async_v110(38400);
+	}
+}
+
+static inline u16 si2cip(u8 si1, u8 si2)
+{
+	static const u8 cip[17][5] =
+		{
+			/*  0  1  2  3  4  */
+			{0, 0, 0, 0, 0},	/*0 */
+			{16, 16, 4, 26, 16},	/*1 */
+			{17, 17, 17, 4, 4},	/*2 */
+			{2, 2, 2, 2, 2},	/*3 */
+			{18, 18, 18, 18, 18},	/*4 */
+			{2, 2, 2, 2, 2},	/*5 */
+			{0, 0, 0, 0, 0},	/*6 */
+			{2, 2, 2, 2, 2},	/*7 */
+			{2, 2, 2, 2, 2},	/*8 */
+			{21, 21, 21, 21, 21},	/*9 */
+			{19, 19, 19, 19, 19},	/*10 */
+			{0, 0, 0, 0, 0},	/*11 */
+			{0, 0, 0, 0, 0},	/*12 */
+			{0, 0, 0, 0, 0},	/*13 */
+			{0, 0, 0, 0, 0},	/*14 */
+			{22, 22, 22, 22, 22},	/*15 */
+			{27, 27, 27, 28, 27}	/*16 */
+		};
+	if (si1 > 16)
+		si1 = 0;
+	if (si2 > 4)
+		si2 = 0;
+
+	return (u16) cip[si1][si2];
+}
+
+static inline u8 cip2si1(u16 cipval)
+{
+	static const u8 si[32] =
+		{7, 1, 7, 7, 1, 1, 7, 7,	/*0-7 */
+		 7, 1, 0, 0, 0, 0, 0, 0,	/*8-15 */
+		 1, 2, 4, 10, 9, 9, 15, 7,	/*16-23 */
+		 7, 7, 1, 16, 16, 0, 0, 0};	/*24-31 */
+
+	if (cipval > 31)
+		cipval = 0;	/* .... */
+	return si[cipval];
+}
+
+static inline u8 cip2si2(u16 cipval)
+{
+	static const u8 si[32] =
+		{0, 0, 0, 0, 2, 3, 0, 0,	/*0-7 */
+		 0, 3, 0, 0, 0, 0, 0, 0,	/*8-15 */
+		 1, 2, 0, 0, 9, 0, 0, 0,	/*16-23 */
+		 0, 0, 3, 2, 3, 0, 0, 0};	/*24-31 */
+
+	if (cipval > 31)
+		cipval = 0;	/* .... */
+	return si[cipval];
+}
+
+
+/* -------- controller management ------------------------------------- */
+
+static inline capidrv_contr *findcontrbydriverid(int driverid)
+{
+	unsigned long flags;
+	capidrv_contr *p;
+
+	spin_lock_irqsave(&global_lock, flags);
+	for (p = global.contr_list; p; p = p->next)
+		if (p->myid == driverid)
+			break;
+	spin_unlock_irqrestore(&global_lock, flags);
+	return p;
+}
+
+static capidrv_contr *findcontrbynumber(u32 contr)
+{
+	unsigned long flags;
+	capidrv_contr *p = global.contr_list;
+
+	spin_lock_irqsave(&global_lock, flags);
+	for (p = global.contr_list; p; p = p->next)
+		if (p->contrnr == contr)
+			break;
+	spin_unlock_irqrestore(&global_lock, flags);
+	return p;
+}
+
+
+/* -------- plci management ------------------------------------------ */
+
+static capidrv_plci *new_plci(capidrv_contr *card, int chan)
+{
+	capidrv_plci *plcip;
+
+	plcip = kzalloc(sizeof(capidrv_plci), GFP_ATOMIC);
+
+	if (plcip == NULL)
+		return NULL;
+
+	plcip->state = ST_PLCI_NONE;
+	plcip->plci = 0;
+	plcip->msgid = 0;
+	plcip->chan = chan;
+	plcip->next = card->plci_list;
+	card->plci_list = plcip;
+	card->bchans[chan].plcip = plcip;
+
+	return plcip;
+}
+
+static capidrv_plci *find_plci_by_plci(capidrv_contr *card, u32 plci)
+{
+	capidrv_plci *p;
+	for (p = card->plci_list; p; p = p->next)
+		if (p->plci == plci)
+			return p;
+	return NULL;
+}
+
+static capidrv_plci *find_plci_by_msgid(capidrv_contr *card, u16 msgid)
+{
+	capidrv_plci *p;
+	for (p = card->plci_list; p; p = p->next)
+		if (p->msgid == msgid)
+			return p;
+	return NULL;
+}
+
+static capidrv_plci *find_plci_by_ncci(capidrv_contr *card, u32 ncci)
+{
+	capidrv_plci *p;
+	for (p = card->plci_list; p; p = p->next)
+		if (p->plci == (ncci & 0xffff))
+			return p;
+	return NULL;
+}
+
+static void free_plci(capidrv_contr *card, capidrv_plci *plcip)
+{
+	capidrv_plci **pp;
+
+	for (pp = &card->plci_list; *pp; pp = &(*pp)->next) {
+		if (*pp == plcip) {
+			*pp = (*pp)->next;
+			card->bchans[plcip->chan].plcip = NULL;
+			card->bchans[plcip->chan].disconnecting = 0;
+			card->bchans[plcip->chan].incoming = 0;
+			kfree(plcip);
+			return;
+		}
+	}
+	printk(KERN_ERR "capidrv-%d: free_plci %p (0x%x) not found, Huh?\n",
+	       card->contrnr, plcip, plcip->plci);
+}
+
+/* -------- ncci management ------------------------------------------ */
+
+static inline capidrv_ncci *new_ncci(capidrv_contr *card,
+				     capidrv_plci *plcip,
+				     u32 ncci)
+{
+	capidrv_ncci *nccip;
+
+	nccip = kzalloc(sizeof(capidrv_ncci), GFP_ATOMIC);
+
+	if (nccip == NULL)
+		return NULL;
+
+	nccip->ncci = ncci;
+	nccip->state = ST_NCCI_NONE;
+	nccip->plcip = plcip;
+	nccip->chan = plcip->chan;
+	nccip->datahandle = 0;
+
+	nccip->next = plcip->ncci_list;
+	plcip->ncci_list = nccip;
+
+	card->bchans[plcip->chan].nccip = nccip;
+
+	return nccip;
+}
+
+static inline capidrv_ncci *find_ncci(capidrv_contr *card, u32 ncci)
+{
+	capidrv_plci *plcip;
+	capidrv_ncci *p;
+
+	if ((plcip = find_plci_by_ncci(card, ncci)) == NULL)
+		return NULL;
+
+	for (p = plcip->ncci_list; p; p = p->next)
+		if (p->ncci == ncci)
+			return p;
+	return NULL;
+}
+
+static inline capidrv_ncci *find_ncci_by_msgid(capidrv_contr *card,
+					       u32 ncci, u16 msgid)
+{
+	capidrv_plci *plcip;
+	capidrv_ncci *p;
+
+	if ((plcip = find_plci_by_ncci(card, ncci)) == NULL)
+		return NULL;
+
+	for (p = plcip->ncci_list; p; p = p->next)
+		if (p->msgid == msgid)
+			return p;
+	return NULL;
+}
+
+static void free_ncci(capidrv_contr *card, struct capidrv_ncci *nccip)
+{
+	struct capidrv_ncci **pp;
+
+	for (pp = &(nccip->plcip->ncci_list); *pp; pp = &(*pp)->next) {
+		if (*pp == nccip) {
+			*pp = (*pp)->next;
+			break;
+		}
+	}
+	card->bchans[nccip->chan].nccip = NULL;
+	kfree(nccip);
+}
+
+static int capidrv_add_ack(struct capidrv_ncci *nccip,
+			   u16 datahandle, int len)
+{
+	struct ncci_datahandle_queue *n, **pp;
+
+	n = kmalloc(sizeof(struct ncci_datahandle_queue), GFP_ATOMIC);
+	if (!n) {
+		printk(KERN_ERR "capidrv: kmalloc ncci_datahandle failed\n");
+		return -1;
+	}
+	n->next = NULL;
+	n->datahandle = datahandle;
+	n->len = len;
+	for (pp = &nccip->ackqueue; *pp; pp = &(*pp)->next);
+	*pp = n;
+	return 0;
+}
+
+static int capidrv_del_ack(struct capidrv_ncci *nccip, u16 datahandle)
+{
+	struct ncci_datahandle_queue **pp, *p;
+	int len;
+
+	for (pp = &nccip->ackqueue; *pp; pp = &(*pp)->next) {
+		if ((*pp)->datahandle == datahandle) {
+			p = *pp;
+			len = p->len;
+			*pp = (*pp)->next;
+			kfree(p);
+			return len;
+		}
+	}
+	return -1;
+}
+
+/* -------- convert and send capi message ---------------------------- */
+
+static void send_message(capidrv_contr *card, _cmsg *cmsg)
+{
+	struct sk_buff *skb;
+	size_t len;
+
+	if (capi_cmsg2message(cmsg, cmsg->buf)) {
+		printk(KERN_ERR "capidrv::send_message: parser failure\n");
+		return;
+	}
+	len = CAPIMSG_LEN(cmsg->buf);
+	skb = alloc_skb(len, GFP_ATOMIC);
+	if (!skb) {
+		printk(KERN_ERR "capidrv::send_message: can't allocate mem\n");
+		return;
+	}
+	skb_put_data(skb, cmsg->buf, len);
+	if (capi20_put_message(&global.ap, skb) != CAPI_NOERROR)
+		kfree_skb(skb);
+}
+
+/* -------- state machine -------------------------------------------- */
+
+struct listenstatechange {
+	int actstate;
+	int nextstate;
+	int event;
+};
+
+static struct listenstatechange listentable[] =
+{
+	{ST_LISTEN_NONE, ST_LISTEN_WAIT_CONF, EV_LISTEN_REQ},
+	{ST_LISTEN_ACTIVE, ST_LISTEN_ACTIVE_WAIT_CONF, EV_LISTEN_REQ},
+	{ST_LISTEN_WAIT_CONF, ST_LISTEN_NONE, EV_LISTEN_CONF_ERROR},
+	{ST_LISTEN_ACTIVE_WAIT_CONF, ST_LISTEN_ACTIVE, EV_LISTEN_CONF_ERROR},
+	{ST_LISTEN_WAIT_CONF, ST_LISTEN_NONE, EV_LISTEN_CONF_EMPTY},
+	{ST_LISTEN_ACTIVE_WAIT_CONF, ST_LISTEN_NONE, EV_LISTEN_CONF_EMPTY},
+	{ST_LISTEN_WAIT_CONF, ST_LISTEN_ACTIVE, EV_LISTEN_CONF_OK},
+	{ST_LISTEN_ACTIVE_WAIT_CONF, ST_LISTEN_ACTIVE, EV_LISTEN_CONF_OK},
+	{},
+};
+
+static void listen_change_state(capidrv_contr *card, int event)
+{
+	struct listenstatechange *p = listentable;
+	while (p->event) {
+		if (card->state == p->actstate && p->event == event) {
+			if (debugmode)
+				printk(KERN_DEBUG "capidrv-%d: listen_change_state %d -> %d\n",
+				       card->contrnr, card->state, p->nextstate);
+			card->state = p->nextstate;
+			return;
+		}
+		p++;
+	}
+	printk(KERN_ERR "capidrv-%d: listen_change_state state=%d event=%d ????\n",
+	       card->contrnr, card->state, event);
+
+}
+
+/* ------------------------------------------------------------------ */
+
+static void p0(capidrv_contr *card, capidrv_plci *plci)
+{
+	isdn_ctrl cmd;
+
+	card->bchans[plci->chan].contr = NULL;
+	cmd.command = ISDN_STAT_DHUP;
+	cmd.driver = card->myid;
+	cmd.arg = plci->chan;
+	card->interface.statcallb(&cmd);
+	free_plci(card, plci);
+}
+
+/* ------------------------------------------------------------------ */
+
+struct plcistatechange {
+	int actstate;
+	int nextstate;
+	int event;
+	void (*changefunc)(capidrv_contr *card, capidrv_plci *plci);
+};
+
+static struct plcistatechange plcitable[] =
+{
+	/* P-0 */
+	{ST_PLCI_NONE, ST_PLCI_OUTGOING, EV_PLCI_CONNECT_REQ, NULL},
+	{ST_PLCI_NONE, ST_PLCI_ALLOCATED, EV_PLCI_FACILITY_IND_UP, NULL},
+	{ST_PLCI_NONE, ST_PLCI_INCOMING, EV_PLCI_CONNECT_IND, NULL},
+	{ST_PLCI_NONE, ST_PLCI_RESUMEING, EV_PLCI_RESUME_REQ, NULL},
+	/* P-0.1 */
+	{ST_PLCI_OUTGOING, ST_PLCI_NONE, EV_PLCI_CONNECT_CONF_ERROR, p0},
+	{ST_PLCI_OUTGOING, ST_PLCI_ALLOCATED, EV_PLCI_CONNECT_CONF_OK, NULL},
+	/* P-1 */
+	{ST_PLCI_ALLOCATED, ST_PLCI_ACTIVE, EV_PLCI_CONNECT_ACTIVE_IND, NULL},
+	{ST_PLCI_ALLOCATED, ST_PLCI_DISCONNECTING, EV_PLCI_DISCONNECT_REQ, NULL},
+	{ST_PLCI_ALLOCATED, ST_PLCI_DISCONNECTING, EV_PLCI_FACILITY_IND_DOWN, NULL},
+	{ST_PLCI_ALLOCATED, ST_PLCI_DISCONNECTED, EV_PLCI_DISCONNECT_IND, NULL},
+	/* P-ACT */
+	{ST_PLCI_ACTIVE, ST_PLCI_DISCONNECTING, EV_PLCI_DISCONNECT_REQ, NULL},
+	{ST_PLCI_ACTIVE, ST_PLCI_DISCONNECTING, EV_PLCI_FACILITY_IND_DOWN, NULL},
+	{ST_PLCI_ACTIVE, ST_PLCI_DISCONNECTED, EV_PLCI_DISCONNECT_IND, NULL},
+	{ST_PLCI_ACTIVE, ST_PLCI_HELD, EV_PLCI_HOLD_IND, NULL},
+	{ST_PLCI_ACTIVE, ST_PLCI_DISCONNECTING, EV_PLCI_SUSPEND_IND, NULL},
+	/* P-2 */
+	{ST_PLCI_INCOMING, ST_PLCI_DISCONNECTING, EV_PLCI_CONNECT_REJECT, NULL},
+	{ST_PLCI_INCOMING, ST_PLCI_FACILITY_IND, EV_PLCI_FACILITY_IND_UP, NULL},
+	{ST_PLCI_INCOMING, ST_PLCI_ACCEPTING, EV_PLCI_CONNECT_RESP, NULL},
+	{ST_PLCI_INCOMING, ST_PLCI_DISCONNECTING, EV_PLCI_DISCONNECT_REQ, NULL},
+	{ST_PLCI_INCOMING, ST_PLCI_DISCONNECTING, EV_PLCI_FACILITY_IND_DOWN, NULL},
+	{ST_PLCI_INCOMING, ST_PLCI_DISCONNECTED, EV_PLCI_DISCONNECT_IND, NULL},
+	{ST_PLCI_INCOMING, ST_PLCI_DISCONNECTING, EV_PLCI_CD_IND, NULL},
+	/* P-3 */
+	{ST_PLCI_FACILITY_IND, ST_PLCI_DISCONNECTING, EV_PLCI_CONNECT_REJECT, NULL},
+	{ST_PLCI_FACILITY_IND, ST_PLCI_ACCEPTING, EV_PLCI_CONNECT_ACTIVE_IND, NULL},
+	{ST_PLCI_FACILITY_IND, ST_PLCI_DISCONNECTING, EV_PLCI_DISCONNECT_REQ, NULL},
+	{ST_PLCI_FACILITY_IND, ST_PLCI_DISCONNECTING, EV_PLCI_FACILITY_IND_DOWN, NULL},
+	{ST_PLCI_FACILITY_IND, ST_PLCI_DISCONNECTED, EV_PLCI_DISCONNECT_IND, NULL},
+	/* P-4 */
+	{ST_PLCI_ACCEPTING, ST_PLCI_ACTIVE, EV_PLCI_CONNECT_ACTIVE_IND, NULL},
+	{ST_PLCI_ACCEPTING, ST_PLCI_DISCONNECTING, EV_PLCI_DISCONNECT_REQ, NULL},
+	{ST_PLCI_ACCEPTING, ST_PLCI_DISCONNECTING, EV_PLCI_FACILITY_IND_DOWN, NULL},
+	{ST_PLCI_ACCEPTING, ST_PLCI_DISCONNECTED, EV_PLCI_DISCONNECT_IND, NULL},
+	/* P-5 */
+	{ST_PLCI_DISCONNECTING, ST_PLCI_DISCONNECTED, EV_PLCI_DISCONNECT_IND, NULL},
+	/* P-6 */
+	{ST_PLCI_DISCONNECTED, ST_PLCI_NONE, EV_PLCI_DISCONNECT_RESP, p0},
+	/* P-0.Res */
+	{ST_PLCI_RESUMEING, ST_PLCI_NONE, EV_PLCI_RESUME_CONF_ERROR, p0},
+	{ST_PLCI_RESUMEING, ST_PLCI_RESUME, EV_PLCI_RESUME_CONF_OK, NULL},
+	/* P-RES */
+	{ST_PLCI_RESUME, ST_PLCI_ACTIVE, EV_PLCI_RESUME_IND, NULL},
+	/* P-HELD */
+	{ST_PLCI_HELD, ST_PLCI_ACTIVE, EV_PLCI_RETRIEVE_IND, NULL},
+	{},
+};
+
+static void plci_change_state(capidrv_contr *card, capidrv_plci *plci, int event)
+{
+	struct plcistatechange *p = plcitable;
+	while (p->event) {
+		if (plci->state == p->actstate && p->event == event) {
+			if (debugmode)
+				printk(KERN_DEBUG "capidrv-%d: plci_change_state:0x%x %d -> %d\n",
+				       card->contrnr, plci->plci, plci->state, p->nextstate);
+			plci->state = p->nextstate;
+			if (p->changefunc)
+				p->changefunc(card, plci);
+			return;
+		}
+		p++;
+	}
+	printk(KERN_ERR "capidrv-%d: plci_change_state:0x%x state=%d event=%d ????\n",
+	       card->contrnr, plci->plci, plci->state, event);
+}
+
+/* ------------------------------------------------------------------ */
+
+static _cmsg cmsg;
+
+static void n0(capidrv_contr *card, capidrv_ncci *ncci)
+{
+	isdn_ctrl cmd;
+
+	capi_fill_DISCONNECT_REQ(&cmsg,
+				 global.ap.applid,
+				 card->msgid++,
+				 ncci->plcip->plci,
+				 NULL,	/* BChannelinformation */
+				 NULL,	/* Keypadfacility */
+				 NULL,	/* Useruserdata */   /* $$$$ */
+				 NULL	/* Facilitydataarray */
+		);
+	plci_change_state(card, ncci->plcip, EV_PLCI_DISCONNECT_REQ);
+	send_message(card, &cmsg);
+
+	cmd.command = ISDN_STAT_BHUP;
+	cmd.driver = card->myid;
+	cmd.arg = ncci->chan;
+	card->interface.statcallb(&cmd);
+	free_ncci(card, ncci);
+}
+
+/* ------------------------------------------------------------------ */
+
+struct nccistatechange {
+	int actstate;
+	int nextstate;
+	int event;
+	void (*changefunc)(capidrv_contr *card, capidrv_ncci *ncci);
+};
+
+static struct nccistatechange nccitable[] =
+{
+	/* N-0 */
+	{ST_NCCI_NONE, ST_NCCI_OUTGOING, EV_NCCI_CONNECT_B3_REQ, NULL},
+	{ST_NCCI_NONE, ST_NCCI_INCOMING, EV_NCCI_CONNECT_B3_IND, NULL},
+	/* N-0.1 */
+	{ST_NCCI_OUTGOING, ST_NCCI_ALLOCATED, EV_NCCI_CONNECT_B3_CONF_OK, NULL},
+	{ST_NCCI_OUTGOING, ST_NCCI_NONE, EV_NCCI_CONNECT_B3_CONF_ERROR, n0},
+	/* N-1 */
+	{ST_NCCI_INCOMING, ST_NCCI_DISCONNECTING, EV_NCCI_CONNECT_B3_REJECT, NULL},
+	{ST_NCCI_INCOMING, ST_NCCI_ALLOCATED, EV_NCCI_CONNECT_B3_RESP, NULL},
+	{ST_NCCI_INCOMING, ST_NCCI_DISCONNECTED, EV_NCCI_DISCONNECT_B3_IND, NULL},
+	{ST_NCCI_INCOMING, ST_NCCI_DISCONNECTING, EV_NCCI_DISCONNECT_B3_REQ, NULL},
+	/* N-2 */
+	{ST_NCCI_ALLOCATED, ST_NCCI_ACTIVE, EV_NCCI_CONNECT_B3_ACTIVE_IND, NULL},
+	{ST_NCCI_ALLOCATED, ST_NCCI_DISCONNECTED, EV_NCCI_DISCONNECT_B3_IND, NULL},
+	{ST_NCCI_ALLOCATED, ST_NCCI_DISCONNECTING, EV_NCCI_DISCONNECT_B3_REQ, NULL},
+	/* N-ACT */
+	{ST_NCCI_ACTIVE, ST_NCCI_ACTIVE, EV_NCCI_RESET_B3_IND, NULL},
+	{ST_NCCI_ACTIVE, ST_NCCI_RESETING, EV_NCCI_RESET_B3_REQ, NULL},
+	{ST_NCCI_ACTIVE, ST_NCCI_DISCONNECTED, EV_NCCI_DISCONNECT_B3_IND, NULL},
+	{ST_NCCI_ACTIVE, ST_NCCI_DISCONNECTING, EV_NCCI_DISCONNECT_B3_REQ, NULL},
+	/* N-3 */
+	{ST_NCCI_RESETING, ST_NCCI_ACTIVE, EV_NCCI_RESET_B3_IND, NULL},
+	{ST_NCCI_RESETING, ST_NCCI_DISCONNECTED, EV_NCCI_DISCONNECT_B3_IND, NULL},
+	{ST_NCCI_RESETING, ST_NCCI_DISCONNECTING, EV_NCCI_DISCONNECT_B3_REQ, NULL},
+	/* N-4 */
+	{ST_NCCI_DISCONNECTING, ST_NCCI_DISCONNECTED, EV_NCCI_DISCONNECT_B3_IND, NULL},
+	{ST_NCCI_DISCONNECTING, ST_NCCI_PREVIOUS, EV_NCCI_DISCONNECT_B3_CONF_ERROR, NULL},
+	/* N-5 */
+	{ST_NCCI_DISCONNECTED, ST_NCCI_NONE, EV_NCCI_DISCONNECT_B3_RESP, n0},
+	{},
+};
+
+static void ncci_change_state(capidrv_contr *card, capidrv_ncci *ncci, int event)
+{
+	struct nccistatechange *p = nccitable;
+	while (p->event) {
+		if (ncci->state == p->actstate && p->event == event) {
+			if (debugmode)
+				printk(KERN_DEBUG "capidrv-%d: ncci_change_state:0x%x %d -> %d\n",
+				       card->contrnr, ncci->ncci, ncci->state, p->nextstate);
+			if (p->nextstate == ST_NCCI_PREVIOUS) {
+				ncci->state = ncci->oldstate;
+				ncci->oldstate = p->actstate;
+			} else {
+				ncci->oldstate = p->actstate;
+				ncci->state = p->nextstate;
+			}
+			if (p->changefunc)
+				p->changefunc(card, ncci);
+			return;
+		}
+		p++;
+	}
+	printk(KERN_ERR "capidrv-%d: ncci_change_state:0x%x state=%d event=%d ????\n",
+	       card->contrnr, ncci->ncci, ncci->state, event);
+}
+
+/* ------------------------------------------------------------------- */
+
+static inline int new_bchan(capidrv_contr *card)
+{
+	int i;
+	for (i = 0; i < card->nbchan; i++) {
+		if (card->bchans[i].plcip == NULL) {
+			card->bchans[i].disconnecting = 0;
+			return i;
+		}
+	}
+	return -1;
+}
+
+/* ------------------------------------------------------------------- */
+static char *capi_info2str(u16 reason)
+{
+#ifndef CONFIG_ISDN_CAPI_CAPIDRV_VERBOSE
+	return "..";
+#else
+	switch (reason) {
+
+/*-- informative values (corresponding message was processed) -----*/
+	case 0x0001:
+		return "NCPI not supported by current protocol, NCPI ignored";
+	case 0x0002:
+		return "Flags not supported by current protocol, flags ignored";
+	case 0x0003:
+		return "Alert already sent by another application";
+
+/*-- error information concerning CAPI_REGISTER -----*/
+	case 0x1001:
+		return "Too many applications";
+	case 0x1002:
+		return "Logical block size too small, must be at least 128 Bytes";
+	case 0x1003:
+		return "Buffer exceeds 64 kByte";
+	case 0x1004:
+		return "Message buffer size too small, must be at least 1024 Bytes";
+	case 0x1005:
+		return "Max. number of logical connections not supported";
+	case 0x1006:
+		return "Reserved";
+	case 0x1007:
+		return "The message could not be accepted because of an internal busy condition";
+	case 0x1008:
+		return "OS resource error (no memory ?)";
+	case 0x1009:
+		return "CAPI not installed";
+	case 0x100A:
+		return "Controller does not support external equipment";
+	case 0x100B:
+		return "Controller does only support external equipment";
+
+/*-- error information concerning message exchange functions -----*/
+	case 0x1101:
+		return "Illegal application number";
+	case 0x1102:
+		return "Illegal command or subcommand or message length less than 12 bytes";
+	case 0x1103:
+		return "The message could not be accepted because of a queue full condition !! The error code does not imply that CAPI cannot receive messages directed to another controller, PLCI or NCCI";
+	case 0x1104:
+		return "Queue is empty";
+	case 0x1105:
+		return "Queue overflow, a message was lost !! This indicates a configuration error. The only recovery from this error is to perform a CAPI_RELEASE";
+	case 0x1106:
+		return "Unknown notification parameter";
+	case 0x1107:
+		return "The Message could not be accepted because of an internal busy condition";
+	case 0x1108:
+		return "OS Resource error (no memory ?)";
+	case 0x1109:
+		return "CAPI not installed";
+	case 0x110A:
+		return "Controller does not support external equipment";
+	case 0x110B:
+		return "Controller does only support external equipment";
+
+/*-- error information concerning resource / coding problems -----*/
+	case 0x2001:
+		return "Message not supported in current state";
+	case 0x2002:
+		return "Illegal Controller / PLCI / NCCI";
+	case 0x2003:
+		return "Out of PLCI";
+	case 0x2004:
+		return "Out of NCCI";
+	case 0x2005:
+		return "Out of LISTEN";
+	case 0x2006:
+		return "Out of FAX resources (protocol T.30)";
+	case 0x2007:
+		return "Illegal message parameter coding";
+
+/*-- error information concerning requested services  -----*/
+	case 0x3001:
+		return "B1 protocol not supported";
+	case 0x3002:
+		return "B2 protocol not supported";
+	case 0x3003:
+		return "B3 protocol not supported";
+	case 0x3004:
+		return "B1 protocol parameter not supported";
+	case 0x3005:
+		return "B2 protocol parameter not supported";
+	case 0x3006:
+		return "B3 protocol parameter not supported";
+	case 0x3007:
+		return "B protocol combination not supported";
+	case 0x3008:
+		return "NCPI not supported";
+	case 0x3009:
+		return "CIP Value unknown";
+	case 0x300A:
+		return "Flags not supported (reserved bits)";
+	case 0x300B:
+		return "Facility not supported";
+	case 0x300C:
+		return "Data length not supported by current protocol";
+	case 0x300D:
+		return "Reset procedure not supported by current protocol";
+
+/*-- informations about the clearing of a physical connection -----*/
+	case 0x3301:
+		return "Protocol error layer 1 (broken line or B-channel removed by signalling protocol)";
+	case 0x3302:
+		return "Protocol error layer 2";
+	case 0x3303:
+		return "Protocol error layer 3";
+	case 0x3304:
+		return "Another application got that call";
+/*-- T.30 specific reasons -----*/
+	case 0x3311:
+		return "Connecting not successful (remote station is no FAX G3 machine)";
+	case 0x3312:
+		return "Connecting not successful (training error)";
+	case 0x3313:
+		return "Disconnected before transfer (remote station does not support transfer mode, e.g. resolution)";
+	case 0x3314:
+		return "Disconnected during transfer (remote abort)";
+	case 0x3315:
+		return "Disconnected during transfer (remote procedure error, e.g. unsuccessful repetition of T.30 commands)";
+	case 0x3316:
+		return "Disconnected during transfer (local tx data underrun)";
+	case 0x3317:
+		return "Disconnected during transfer (local rx data overflow)";
+	case 0x3318:
+		return "Disconnected during transfer (local abort)";
+	case 0x3319:
+		return "Illegal parameter coding (e.g. SFF coding error)";
+
+/*-- disconnect causes from the network according to ETS 300 102-1/Q.931 -----*/
+	case 0x3481: return "Unallocated (unassigned) number";
+	case 0x3482: return "No route to specified transit network";
+	case 0x3483: return "No route to destination";
+	case 0x3486: return "Channel unacceptable";
+	case 0x3487:
+		return "Call awarded and being delivered in an established channel";
+	case 0x3490: return "Normal call clearing";
+	case 0x3491: return "User busy";
+	case 0x3492: return "No user responding";
+	case 0x3493: return "No answer from user (user alerted)";
+	case 0x3495: return "Call rejected";
+	case 0x3496: return "Number changed";
+	case 0x349A: return "Non-selected user clearing";
+	case 0x349B: return "Destination out of order";
+	case 0x349C: return "Invalid number format";
+	case 0x349D: return "Facility rejected";
+	case 0x349E: return "Response to STATUS ENQUIRY";
+	case 0x349F: return "Normal, unspecified";
+	case 0x34A2: return "No circuit / channel available";
+	case 0x34A6: return "Network out of order";
+	case 0x34A9: return "Temporary failure";
+	case 0x34AA: return "Switching equipment congestion";
+	case 0x34AB: return "Access information discarded";
+	case 0x34AC: return "Requested circuit / channel not available";
+	case 0x34AF: return "Resources unavailable, unspecified";
+	case 0x34B1: return "Quality of service unavailable";
+	case 0x34B2: return "Requested facility not subscribed";
+	case 0x34B9: return "Bearer capability not authorized";
+	case 0x34BA: return "Bearer capability not presently available";
+	case 0x34BF: return "Service or option not available, unspecified";
+	case 0x34C1: return "Bearer capability not implemented";
+	case 0x34C2: return "Channel type not implemented";
+	case 0x34C5: return "Requested facility not implemented";
+	case 0x34C6: return "Only restricted digital information bearer capability is available";
+	case 0x34CF: return "Service or option not implemented, unspecified";
+	case 0x34D1: return "Invalid call reference value";
+	case 0x34D2: return "Identified channel does not exist";
+	case 0x34D3: return "A suspended call exists, but this call identity does not";
+	case 0x34D4: return "Call identity in use";
+	case 0x34D5: return "No call suspended";
+	case 0x34D6: return "Call having the requested call identity has been cleared";
+	case 0x34D8: return "Incompatible destination";
+	case 0x34DB: return "Invalid transit network selection";
+	case 0x34DF: return "Invalid message, unspecified";
+	case 0x34E0: return "Mandatory information element is missing";
+	case 0x34E1: return "Message type non-existent or not implemented";
+	case 0x34E2: return "Message not compatible with call state or message type non-existent or not implemented";
+	case 0x34E3: return "Information element non-existent or not implemented";
+	case 0x34E4: return "Invalid information element contents";
+	case 0x34E5: return "Message not compatible with call state";
+	case 0x34E6: return "Recovery on timer expiry";
+	case 0x34EF: return "Protocol error, unspecified";
+	case 0x34FF: return "Interworking, unspecified";
+
+	default: return "No additional information";
+	}
+#endif
+}
+
+static void handle_controller(_cmsg *cmsg)
+{
+	capidrv_contr *card = findcontrbynumber(cmsg->adr.adrController & 0x7f);
+
+	if (!card) {
+		printk(KERN_ERR "capidrv: %s from unknown controller 0x%x\n",
+		       capi_cmd2str(cmsg->Command, cmsg->Subcommand),
+		       cmsg->adr.adrController & 0x7f);
+		return;
+	}
+	switch (CAPICMD(cmsg->Command, cmsg->Subcommand)) {
+
+	case CAPI_LISTEN_CONF:	/* Controller */
+		if (debugmode)
+			printk(KERN_DEBUG "capidrv-%d: listenconf Info=0x%4x (%s) cipmask=0x%x\n",
+			       card->contrnr, cmsg->Info, capi_info2str(cmsg->Info), card->cipmask);
+		if (cmsg->Info) {
+			listen_change_state(card, EV_LISTEN_CONF_ERROR);
+		} else if (card->cipmask == 0) {
+			listen_change_state(card, EV_LISTEN_CONF_EMPTY);
+		} else {
+			listen_change_state(card, EV_LISTEN_CONF_OK);
+		}
+		break;
+
+	case CAPI_MANUFACTURER_IND:	/* Controller */
+		if (cmsg->ManuID == 0x214D5641
+		    && cmsg->Class == 0
+		    && cmsg->Function == 1) {
+			u8  *data = cmsg->ManuData + 3;
+			u16  len = cmsg->ManuData[0];
+			u16 layer;
+			int direction;
+			if (len == 255) {
+				len = (cmsg->ManuData[1] | (cmsg->ManuData[2] << 8));
+				data += 2;
+			}
+			len -= 2;
+			layer = ((*(data - 1)) << 8) | *(data - 2);
+			if (layer & 0x300)
+				direction = (layer & 0x200) ? 0 : 1;
+			else direction = (layer & 0x800) ? 0 : 1;
+			if (layer & 0x0C00) {
+				if ((layer & 0xff) == 0x80) {
+					handle_dtrace_data(card, direction, 1, data, len);
+					break;
+				}
+			} else if ((layer & 0xff) < 0x80) {
+				handle_dtrace_data(card, direction, 0, data, len);
+				break;
+			}
+			printk(KERN_INFO "capidrv-%d: %s from controller 0x%x layer 0x%x, ignored\n",
+			       card->contrnr,
+			       capi_cmd2str(cmsg->Command, cmsg->Subcommand),
+			       cmsg->adr.adrController, layer);
+			break;
+		}
+		goto ignored;
+	case CAPI_MANUFACTURER_CONF:	/* Controller */
+		if (cmsg->ManuID == 0x214D5641) {
+			char *s = NULL;
+			switch (cmsg->Class) {
+			case 0: break;
+			case 1: s = "unknown class"; break;
+			case 2: s = "unknown function"; break;
+			default: s = "unknown error"; break;
+			}
+			if (s)
+				printk(KERN_INFO "capidrv-%d: %s from controller 0x%x function %d: %s\n",
+				       card->contrnr,
+				       capi_cmd2str(cmsg->Command, cmsg->Subcommand),
+				       cmsg->adr.adrController,
+				       cmsg->Function, s);
+			break;
+		}
+		goto ignored;
+	case CAPI_FACILITY_IND:	/* Controller/plci/ncci */
+		goto ignored;
+	case CAPI_FACILITY_CONF:	/* Controller/plci/ncci */
+		goto ignored;
+	case CAPI_INFO_IND:	/* Controller/plci */
+		goto ignored;
+	case CAPI_INFO_CONF:	/* Controller/plci */
+		goto ignored;
+
+	default:
+		printk(KERN_ERR "capidrv-%d: got %s from controller 0x%x ???",
+		       card->contrnr,
+		       capi_cmd2str(cmsg->Command, cmsg->Subcommand),
+		       cmsg->adr.adrController);
+	}
+	return;
+
+ignored:
+	printk(KERN_INFO "capidrv-%d: %s from controller 0x%x ignored\n",
+	       card->contrnr,
+	       capi_cmd2str(cmsg->Command, cmsg->Subcommand),
+	       cmsg->adr.adrController);
+}
+
+static void handle_incoming_call(capidrv_contr *card, _cmsg *cmsg)
+{
+	capidrv_plci *plcip;
+	capidrv_bchan *bchan;
+	isdn_ctrl cmd;
+	int chan;
+
+	if ((chan = new_bchan(card)) == -1) {
+		printk(KERN_ERR "capidrv-%d: incoming call on not existing bchan ?\n", card->contrnr);
+		return;
+	}
+	bchan = &card->bchans[chan];
+	if ((plcip = new_plci(card, chan)) == NULL) {
+		printk(KERN_ERR "capidrv-%d: incoming call: no memory, sorry.\n", card->contrnr);
+		return;
+	}
+	bchan->incoming = 1;
+	plcip->plci = cmsg->adr.adrPLCI;
+	plci_change_state(card, plcip, EV_PLCI_CONNECT_IND);
+
+	cmd.command = ISDN_STAT_ICALL;
+	cmd.driver = card->myid;
+	cmd.arg = chan;
+	memset(&cmd.parm.setup, 0, sizeof(cmd.parm.setup));
+	strncpy(cmd.parm.setup.phone,
+		cmsg->CallingPartyNumber + 3,
+		cmsg->CallingPartyNumber[0] - 2);
+	strncpy(cmd.parm.setup.eazmsn,
+		cmsg->CalledPartyNumber + 2,
+		cmsg->CalledPartyNumber[0] - 1);
+	cmd.parm.setup.si1 = cip2si1(cmsg->CIPValue);
+	cmd.parm.setup.si2 = cip2si2(cmsg->CIPValue);
+	cmd.parm.setup.plan = cmsg->CallingPartyNumber[1];
+	cmd.parm.setup.screen = cmsg->CallingPartyNumber[2];
+
+	printk(KERN_INFO "capidrv-%d: incoming call %s,%d,%d,%s\n",
+	       card->contrnr,
+	       cmd.parm.setup.phone,
+	       cmd.parm.setup.si1,
+	       cmd.parm.setup.si2,
+	       cmd.parm.setup.eazmsn);
+
+	if (cmd.parm.setup.si1 == 1 && cmd.parm.setup.si2 != 0) {
+		printk(KERN_INFO "capidrv-%d: patching si2=%d to 0 for VBOX\n",
+		       card->contrnr,
+		       cmd.parm.setup.si2);
+		cmd.parm.setup.si2 = 0;
+	}
+
+	switch (card->interface.statcallb(&cmd)) {
+	case 0:
+	case 3:
+		/* No device matching this call.
+		 * and isdn_common.c has send a HANGUP command
+		 * which is ignored in state ST_PLCI_INCOMING,
+		 * so we send RESP to ignore the call
+		 */
+		capi_cmsg_answer(cmsg);
+		cmsg->Reject = 1;	/* ignore */
+		plci_change_state(card, plcip, EV_PLCI_CONNECT_REJECT);
+		send_message(card, cmsg);
+		printk(KERN_INFO "capidrv-%d: incoming call %s,%d,%d,%s ignored\n",
+		       card->contrnr,
+		       cmd.parm.setup.phone,
+		       cmd.parm.setup.si1,
+		       cmd.parm.setup.si2,
+		       cmd.parm.setup.eazmsn);
+		break;
+	case 1:
+		/* At least one device matching this call (RING on ttyI)
+		 * HL-driver may send ALERTING on the D-channel in this
+		 * case.
+		 * really means: RING on ttyI or a net interface
+		 * accepted this call already.
+		 *
+		 * If the call was accepted, state has already changed,
+		 * and CONNECT_RESP already sent.
+		 */
+		if (plcip->state == ST_PLCI_INCOMING) {
+			printk(KERN_INFO "capidrv-%d: incoming call %s,%d,%d,%s tty alerting\n",
+			       card->contrnr,
+			       cmd.parm.setup.phone,
+			       cmd.parm.setup.si1,
+			       cmd.parm.setup.si2,
+			       cmd.parm.setup.eazmsn);
+			capi_fill_ALERT_REQ(cmsg,
+					    global.ap.applid,
+					    card->msgid++,
+					    plcip->plci,	/* adr */
+					    NULL,/* BChannelinformation */
+					    NULL,/* Keypadfacility */
+					    NULL,/* Useruserdata */
+					    NULL /* Facilitydataarray */
+				);
+			plcip->msgid = cmsg->Messagenumber;
+			send_message(card, cmsg);
+		} else {
+			printk(KERN_INFO "capidrv-%d: incoming call %s,%d,%d,%s on netdev\n",
+			       card->contrnr,
+			       cmd.parm.setup.phone,
+			       cmd.parm.setup.si1,
+			       cmd.parm.setup.si2,
+			       cmd.parm.setup.eazmsn);
+		}
+		break;
+
+	case 2:		/* Call will be rejected. */
+		capi_cmsg_answer(cmsg);
+		cmsg->Reject = 2;	/* reject call, normal call clearing */
+		plci_change_state(card, plcip, EV_PLCI_CONNECT_REJECT);
+		send_message(card, cmsg);
+		break;
+
+	default:
+		/* An error happened. (Invalid parameters for example.) */
+		capi_cmsg_answer(cmsg);
+		cmsg->Reject = 8;	/* reject call,
+					   destination out of order */
+		plci_change_state(card, plcip, EV_PLCI_CONNECT_REJECT);
+		send_message(card, cmsg);
+		break;
+	}
+	return;
+}
+
+static void handle_plci(_cmsg *cmsg)
+{
+	capidrv_contr *card = findcontrbynumber(cmsg->adr.adrController & 0x7f);
+	capidrv_plci *plcip;
+	isdn_ctrl cmd;
+	_cdebbuf *cdb;
+
+	if (!card) {
+		printk(KERN_ERR "capidrv: %s from unknown controller 0x%x\n",
+		       capi_cmd2str(cmsg->Command, cmsg->Subcommand),
+		       cmsg->adr.adrController & 0x7f);
+		return;
+	}
+	switch (CAPICMD(cmsg->Command, cmsg->Subcommand)) {
+
+	case CAPI_DISCONNECT_IND:	/* plci */
+		if (cmsg->Reason) {
+			printk(KERN_INFO "capidrv-%d: %s reason 0x%x (%s) for plci 0x%x\n",
+			       card->contrnr,
+			       capi_cmd2str(cmsg->Command, cmsg->Subcommand),
+			       cmsg->Reason, capi_info2str(cmsg->Reason), cmsg->adr.adrPLCI);
+		}
+		if (!(plcip = find_plci_by_plci(card, cmsg->adr.adrPLCI))) {
+			capi_cmsg_answer(cmsg);
+			send_message(card, cmsg);
+			goto notfound;
+		}
+		card->bchans[plcip->chan].disconnecting = 1;
+		plci_change_state(card, plcip, EV_PLCI_DISCONNECT_IND);
+		capi_cmsg_answer(cmsg);
+		plci_change_state(card, plcip, EV_PLCI_DISCONNECT_RESP);
+		send_message(card, cmsg);
+		break;
+
+	case CAPI_DISCONNECT_CONF:	/* plci */
+		if (cmsg->Info) {
+			printk(KERN_INFO "capidrv-%d: %s info 0x%x (%s) for plci 0x%x\n",
+			       card->contrnr,
+			       capi_cmd2str(cmsg->Command, cmsg->Subcommand),
+			       cmsg->Info, capi_info2str(cmsg->Info),
+			       cmsg->adr.adrPLCI);
+		}
+		if (!(plcip = find_plci_by_plci(card, cmsg->adr.adrPLCI)))
+			goto notfound;
+
+		card->bchans[plcip->chan].disconnecting = 1;
+		break;
+
+	case CAPI_ALERT_CONF:	/* plci */
+		if (cmsg->Info) {
+			printk(KERN_INFO "capidrv-%d: %s info 0x%x (%s) for plci 0x%x\n",
+			       card->contrnr,
+			       capi_cmd2str(cmsg->Command, cmsg->Subcommand),
+			       cmsg->Info, capi_info2str(cmsg->Info),
+			       cmsg->adr.adrPLCI);
+		}
+		break;
+
+	case CAPI_CONNECT_IND:	/* plci */
+		handle_incoming_call(card, cmsg);
+		break;
+
+	case CAPI_CONNECT_CONF:	/* plci */
+		if (cmsg->Info) {
+			printk(KERN_INFO "capidrv-%d: %s info 0x%x (%s) for plci 0x%x\n",
+			       card->contrnr,
+			       capi_cmd2str(cmsg->Command, cmsg->Subcommand),
+			       cmsg->Info, capi_info2str(cmsg->Info),
+			       cmsg->adr.adrPLCI);
+		}
+		if (!(plcip = find_plci_by_msgid(card, cmsg->Messagenumber)))
+			goto notfound;
+
+		plcip->plci = cmsg->adr.adrPLCI;
+		if (cmsg->Info) {
+			plci_change_state(card, plcip, EV_PLCI_CONNECT_CONF_ERROR);
+		} else {
+			plci_change_state(card, plcip, EV_PLCI_CONNECT_CONF_OK);
+		}
+		break;
+
+	case CAPI_CONNECT_ACTIVE_IND:	/* plci */
+
+		if (!(plcip = find_plci_by_plci(card, cmsg->adr.adrPLCI)))
+			goto notfound;
+
+		if (card->bchans[plcip->chan].incoming) {
+			capi_cmsg_answer(cmsg);
+			plci_change_state(card, plcip, EV_PLCI_CONNECT_ACTIVE_IND);
+			send_message(card, cmsg);
+		} else {
+			capidrv_ncci *nccip;
+			capi_cmsg_answer(cmsg);
+			send_message(card, cmsg);
+
+			nccip = new_ncci(card, plcip, cmsg->adr.adrPLCI);
+
+			if (!nccip) {
+				printk(KERN_ERR "capidrv-%d: no mem for ncci, sorry\n", card->contrnr);
+				break;	/* $$$$ */
+			}
+			capi_fill_CONNECT_B3_REQ(cmsg,
+						 global.ap.applid,
+						 card->msgid++,
+						 plcip->plci,	/* adr */
+						 NULL	/* NCPI */
+				);
+			nccip->msgid = cmsg->Messagenumber;
+			plci_change_state(card, plcip,
+					  EV_PLCI_CONNECT_ACTIVE_IND);
+			ncci_change_state(card, nccip, EV_NCCI_CONNECT_B3_REQ);
+			send_message(card, cmsg);
+			cmd.command = ISDN_STAT_DCONN;
+			cmd.driver = card->myid;
+			cmd.arg = plcip->chan;
+			card->interface.statcallb(&cmd);
+		}
+		break;
+
+	case CAPI_INFO_IND:	/* Controller/plci */
+
+		if (!(plcip = find_plci_by_plci(card, cmsg->adr.adrPLCI)))
+			goto notfound;
+
+		if (cmsg->InfoNumber == 0x4000) {
+			if (cmsg->InfoElement[0] == 4) {
+				cmd.command = ISDN_STAT_CINF;
+				cmd.driver = card->myid;
+				cmd.arg = plcip->chan;
+				sprintf(cmd.parm.num, "%lu",
+					(unsigned long)
+					((u32) cmsg->InfoElement[1]
+					 | ((u32) (cmsg->InfoElement[2]) << 8)
+					 | ((u32) (cmsg->InfoElement[3]) << 16)
+					 | ((u32) (cmsg->InfoElement[4]) << 24)));
+				card->interface.statcallb(&cmd);
+				break;
+			}
+		}
+		cdb = capi_cmsg2str(cmsg);
+		if (cdb) {
+			printk(KERN_WARNING "capidrv-%d: %s\n",
+			       card->contrnr, cdb->buf);
+			cdebbuf_free(cdb);
+		} else
+			printk(KERN_WARNING "capidrv-%d: CAPI_INFO_IND InfoNumber %x not handled\n",
+			       card->contrnr, cmsg->InfoNumber);
+
+		break;
+
+	case CAPI_CONNECT_ACTIVE_CONF:		/* plci */
+		goto ignored;
+	case CAPI_SELECT_B_PROTOCOL_CONF:	/* plci */
+		goto ignored;
+	case CAPI_FACILITY_IND:	/* Controller/plci/ncci */
+		goto ignored;
+	case CAPI_FACILITY_CONF:	/* Controller/plci/ncci */
+		goto ignored;
+
+	case CAPI_INFO_CONF:	/* Controller/plci */
+		goto ignored;
+
+	default:
+		printk(KERN_ERR "capidrv-%d: got %s for plci 0x%x ???",
+		       card->contrnr,
+		       capi_cmd2str(cmsg->Command, cmsg->Subcommand),
+		       cmsg->adr.adrPLCI);
+	}
+	return;
+ignored:
+	printk(KERN_INFO "capidrv-%d: %s for plci 0x%x ignored\n",
+	       card->contrnr,
+	       capi_cmd2str(cmsg->Command, cmsg->Subcommand),
+	       cmsg->adr.adrPLCI);
+	return;
+notfound:
+	printk(KERN_ERR "capidrv-%d: %s: plci 0x%x not found\n",
+	       card->contrnr,
+	       capi_cmd2str(cmsg->Command, cmsg->Subcommand),
+	       cmsg->adr.adrPLCI);
+	return;
+}
+
+static void handle_ncci(_cmsg *cmsg)
+{
+	capidrv_contr *card = findcontrbynumber(cmsg->adr.adrController & 0x7f);
+	capidrv_plci *plcip;
+	capidrv_ncci *nccip;
+	isdn_ctrl cmd;
+	int len;
+
+	if (!card) {
+		printk(KERN_ERR "capidrv: %s from unknown controller 0x%x\n",
+		       capi_cmd2str(cmsg->Command, cmsg->Subcommand),
+		       cmsg->adr.adrController & 0x7f);
+		return;
+	}
+	switch (CAPICMD(cmsg->Command, cmsg->Subcommand)) {
+
+	case CAPI_CONNECT_B3_ACTIVE_IND:	/* ncci */
+		if (!(nccip = find_ncci(card, cmsg->adr.adrNCCI)))
+			goto notfound;
+
+		capi_cmsg_answer(cmsg);
+		ncci_change_state(card, nccip, EV_NCCI_CONNECT_B3_ACTIVE_IND);
+		send_message(card, cmsg);
+
+		cmd.command = ISDN_STAT_BCONN;
+		cmd.driver = card->myid;
+		cmd.arg = nccip->chan;
+		card->interface.statcallb(&cmd);
+
+		printk(KERN_INFO "capidrv-%d: chan %d up with ncci 0x%x\n",
+		       card->contrnr, nccip->chan, nccip->ncci);
+		break;
+
+	case CAPI_CONNECT_B3_ACTIVE_CONF:	/* ncci */
+		goto ignored;
+
+	case CAPI_CONNECT_B3_IND:	/* ncci */
+
+		plcip = find_plci_by_ncci(card, cmsg->adr.adrNCCI);
+		if (plcip) {
+			nccip = new_ncci(card, plcip, cmsg->adr.adrNCCI);
+			if (nccip) {
+				ncci_change_state(card, nccip, EV_NCCI_CONNECT_B3_IND);
+				capi_fill_CONNECT_B3_RESP(cmsg,
+							  global.ap.applid,
+							  card->msgid++,
+							  nccip->ncci,	/* adr */
+							  0,	/* Reject */
+							  NULL	/* NCPI */
+					);
+				ncci_change_state(card, nccip, EV_NCCI_CONNECT_B3_RESP);
+				send_message(card, cmsg);
+				break;
+			}
+			printk(KERN_ERR "capidrv-%d: no mem for ncci, sorry\n",							card->contrnr);
+		} else {
+			printk(KERN_ERR "capidrv-%d: %s: plci for ncci 0x%x not found\n",
+			       card->contrnr,
+			       capi_cmd2str(cmsg->Command, cmsg->Subcommand),
+			       cmsg->adr.adrNCCI);
+		}
+		capi_fill_CONNECT_B3_RESP(cmsg,
+					  global.ap.applid,
+					  card->msgid++,
+					  cmsg->adr.adrNCCI,
+					  2,	/* Reject */
+					  NULL	/* NCPI */
+			);
+		send_message(card, cmsg);
+		break;
+
+	case CAPI_CONNECT_B3_CONF:	/* ncci */
+
+		if (!(nccip = find_ncci_by_msgid(card,
+						 cmsg->adr.adrNCCI,
+						 cmsg->Messagenumber)))
+			goto notfound;
+
+		nccip->ncci = cmsg->adr.adrNCCI;
+		if (cmsg->Info) {
+			printk(KERN_INFO "capidrv-%d: %s info 0x%x (%s) for ncci 0x%x\n",
+			       card->contrnr,
+			       capi_cmd2str(cmsg->Command, cmsg->Subcommand),
+			       cmsg->Info, capi_info2str(cmsg->Info),
+			       cmsg->adr.adrNCCI);
+		}
+
+		if (cmsg->Info)
+			ncci_change_state(card, nccip, EV_NCCI_CONNECT_B3_CONF_ERROR);
+		else
+			ncci_change_state(card, nccip, EV_NCCI_CONNECT_B3_CONF_OK);
+		break;
+
+	case CAPI_CONNECT_B3_T90_ACTIVE_IND:	/* ncci */
+		capi_cmsg_answer(cmsg);
+		send_message(card, cmsg);
+		break;
+
+	case CAPI_DATA_B3_IND:	/* ncci */
+		/* handled in handle_data() */
+		goto ignored;
+
+	case CAPI_DATA_B3_CONF:	/* ncci */
+		if (cmsg->Info) {
+			printk(KERN_WARNING "CAPI_DATA_B3_CONF: Info %x - %s\n",
+			       cmsg->Info, capi_info2str(cmsg->Info));
+		}
+		if (!(nccip = find_ncci(card, cmsg->adr.adrNCCI)))
+			goto notfound;
+
+		len = capidrv_del_ack(nccip, cmsg->DataHandle);
+		if (len < 0)
+			break;
+		cmd.command = ISDN_STAT_BSENT;
+		cmd.driver = card->myid;
+		cmd.arg = nccip->chan;
+		cmd.parm.length = len;
+		card->interface.statcallb(&cmd);
+		break;
+
+	case CAPI_DISCONNECT_B3_IND:	/* ncci */
+		if (!(nccip = find_ncci(card, cmsg->adr.adrNCCI)))
+			goto notfound;
+
+		card->bchans[nccip->chan].disconnecting = 1;
+		ncci_change_state(card, nccip, EV_NCCI_DISCONNECT_B3_IND);
+		capi_cmsg_answer(cmsg);
+		ncci_change_state(card, nccip, EV_NCCI_DISCONNECT_B3_RESP);
+		send_message(card, cmsg);
+		break;
+
+	case CAPI_DISCONNECT_B3_CONF:	/* ncci */
+		if (!(nccip = find_ncci(card, cmsg->adr.adrNCCI)))
+			goto notfound;
+		if (cmsg->Info) {
+			printk(KERN_INFO "capidrv-%d: %s info 0x%x (%s) for ncci 0x%x\n",
+			       card->contrnr,
+			       capi_cmd2str(cmsg->Command, cmsg->Subcommand),
+			       cmsg->Info, capi_info2str(cmsg->Info),
+			       cmsg->adr.adrNCCI);
+			ncci_change_state(card, nccip, EV_NCCI_DISCONNECT_B3_CONF_ERROR);
+		}
+		break;
+
+	case CAPI_RESET_B3_IND:	/* ncci */
+		if (!(nccip = find_ncci(card, cmsg->adr.adrNCCI)))
+			goto notfound;
+		ncci_change_state(card, nccip, EV_NCCI_RESET_B3_IND);
+		capi_cmsg_answer(cmsg);
+		send_message(card, cmsg);
+		break;
+
+	case CAPI_RESET_B3_CONF:	/* ncci */
+		goto ignored;	/* $$$$ */
+
+	case CAPI_FACILITY_IND:	/* Controller/plci/ncci */
+		goto ignored;
+	case CAPI_FACILITY_CONF:	/* Controller/plci/ncci */
+		goto ignored;
+
+	default:
+		printk(KERN_ERR "capidrv-%d: got %s for ncci 0x%x ???",
+		       card->contrnr,
+		       capi_cmd2str(cmsg->Command, cmsg->Subcommand),
+		       cmsg->adr.adrNCCI);
+	}
+	return;
+ignored:
+	printk(KERN_INFO "capidrv-%d: %s for ncci 0x%x ignored\n",
+	       card->contrnr,
+	       capi_cmd2str(cmsg->Command, cmsg->Subcommand),
+	       cmsg->adr.adrNCCI);
+	return;
+notfound:
+	printk(KERN_ERR "capidrv-%d: %s: ncci 0x%x not found\n",
+	       card->contrnr,
+	       capi_cmd2str(cmsg->Command, cmsg->Subcommand),
+	       cmsg->adr.adrNCCI);
+}
+
+
+static void handle_data(_cmsg *cmsg, struct sk_buff *skb)
+{
+	capidrv_contr *card = findcontrbynumber(cmsg->adr.adrController & 0x7f);
+	capidrv_ncci *nccip;
+
+	if (!card) {
+		printk(KERN_ERR "capidrv: %s from unknown controller 0x%x\n",
+		       capi_cmd2str(cmsg->Command, cmsg->Subcommand),
+		       cmsg->adr.adrController & 0x7f);
+		kfree_skb(skb);
+		return;
+	}
+	if (!(nccip = find_ncci(card, cmsg->adr.adrNCCI))) {
+		printk(KERN_ERR "capidrv-%d: %s: ncci 0x%x not found\n",
+		       card->contrnr,
+		       capi_cmd2str(cmsg->Command, cmsg->Subcommand),
+		       cmsg->adr.adrNCCI);
+		kfree_skb(skb);
+		return;
+	}
+	(void) skb_pull(skb, CAPIMSG_LEN(skb->data));
+	card->interface.rcvcallb_skb(card->myid, nccip->chan, skb);
+	capi_cmsg_answer(cmsg);
+	send_message(card, cmsg);
+}
+
+static _cmsg s_cmsg;
+
+static void capidrv_recv_message(struct capi20_appl *ap, struct sk_buff *skb)
+{
+	if (capi_message2cmsg(&s_cmsg, skb->data)) {
+		printk(KERN_ERR "capidrv: applid=%d: received invalid message\n",
+		       ap->applid);
+		kfree_skb(skb);
+		return;
+	}
+	if (debugmode > 3) {
+		_cdebbuf *cdb = capi_cmsg2str(&s_cmsg);
+
+		if (cdb) {
+			printk(KERN_DEBUG "%s: applid=%d %s\n", __func__,
+			       ap->applid, cdb->buf);
+			cdebbuf_free(cdb);
+		} else
+			printk(KERN_DEBUG "%s: applid=%d %s not traced\n",
+			       __func__, ap->applid,
+			       capi_cmd2str(s_cmsg.Command, s_cmsg.Subcommand));
+	}
+	if (s_cmsg.Command == CAPI_DATA_B3
+	    && s_cmsg.Subcommand == CAPI_IND) {
+		handle_data(&s_cmsg, skb);
+		return;
+	}
+	if ((s_cmsg.adr.adrController & 0xffffff00) == 0)
+		handle_controller(&s_cmsg);
+	else if ((s_cmsg.adr.adrPLCI & 0xffff0000) == 0)
+		handle_plci(&s_cmsg);
+	else
+		handle_ncci(&s_cmsg);
+	/*
+	 * data of skb used in s_cmsg,
+	 * free data when s_cmsg is not used again
+	 * thanks to Lars Heete <hel@admin.de>
+	 */
+	kfree_skb(skb);
+}
+
+/* ------------------------------------------------------------------- */
+
+#define PUTBYTE_TO_STATUS(card, byte)				\
+	do {							\
+		*(card)->q931_write++ = (byte);			\
+		if ((card)->q931_write > (card)->q931_end)	\
+			(card)->q931_write = (card)->q931_buf;	\
+	} while (0)
+
+static void handle_dtrace_data(capidrv_contr *card,
+			       int send, int level2, u8 *data, u16 len)
+{
+	u8 *p, *end;
+	isdn_ctrl cmd;
+
+	if (!len) {
+		printk(KERN_DEBUG "capidrv-%d: avmb1_q931_data: len == %d\n",
+		       card->contrnr, len);
+		return;
+	}
+
+	if (level2) {
+		PUTBYTE_TO_STATUS(card, 'D');
+		PUTBYTE_TO_STATUS(card, '2');
+		PUTBYTE_TO_STATUS(card, send ? '>' : '<');
+		PUTBYTE_TO_STATUS(card, ':');
+	} else {
+		PUTBYTE_TO_STATUS(card, 'D');
+		PUTBYTE_TO_STATUS(card, '3');
+		PUTBYTE_TO_STATUS(card, send ? '>' : '<');
+		PUTBYTE_TO_STATUS(card, ':');
+	}
+
+	for (p = data, end = data + len; p < end; p++) {
+		PUTBYTE_TO_STATUS(card, ' ');
+		PUTBYTE_TO_STATUS(card, hex_asc_hi(*p));
+		PUTBYTE_TO_STATUS(card, hex_asc_lo(*p));
+	}
+	PUTBYTE_TO_STATUS(card, '\n');
+
+	cmd.command = ISDN_STAT_STAVAIL;
+	cmd.driver = card->myid;
+	cmd.arg = len * 3 + 5;
+	card->interface.statcallb(&cmd);
+}
+
+/* ------------------------------------------------------------------- */
+
+static _cmsg cmdcmsg;
+
+static int capidrv_ioctl(isdn_ctrl *c, capidrv_contr *card)
+{
+	switch (c->arg) {
+	case 1:
+		debugmode = (int)(*((unsigned int *)c->parm.num));
+		printk(KERN_DEBUG "capidrv-%d: debugmode=%d\n",
+		       card->contrnr, debugmode);
+		return 0;
+	default:
+		printk(KERN_DEBUG "capidrv-%d: capidrv_ioctl(%ld) called ??\n",
+		       card->contrnr, c->arg);
+		return -EINVAL;
+	}
+	return -EINVAL;
+}
+
+/*
+ * Handle leased lines (CAPI-Bundling)
+ */
+
+struct internal_bchannelinfo {
+	unsigned short channelalloc;
+	unsigned short operation;
+	unsigned char  cmask[31];
+};
+
+static int decodeFVteln(char *teln, unsigned long *bmaskp, int *activep)
+{
+	unsigned long bmask = 0;
+	int active = !0;
+	char *s;
+	int i;
+
+	if (strncmp(teln, "FV:", 3) != 0)
+		return 1;
+	s = teln + 3;
+	while (*s && *s == ' ') s++;
+	if (!*s) return -2;
+	if (*s == 'p' || *s == 'P') {
+		active = 0;
+		s++;
+	}
+	if (*s == 'a' || *s == 'A') {
+		active = !0;
+		s++;
+	}
+	while (*s) {
+		int digit1 = 0;
+		int digit2 = 0;
+		char *endp;
+
+		digit1 = simple_strtoul(s, &endp, 10);
+		if (s == endp)
+			return -3;
+		s = endp;
+
+		if (digit1 <= 0 || digit1 > 30) return -4;
+		if (*s == 0 || *s == ',' || *s == ' ') {
+			bmask |= (1 << digit1);
+			digit1 = 0;
+			if (*s) s++;
+			continue;
+		}
+		if (*s != '-') return -5;
+		s++;
+
+		digit2 = simple_strtoul(s, &endp, 10);
+		if (s == endp)
+			return -3;
+		s = endp;
+
+		if (digit2 <= 0 || digit2 > 30) return -4;
+		if (*s == 0 || *s == ',' || *s == ' ') {
+			if (digit1 > digit2)
+				for (i = digit2; i <= digit1; i++)
+					bmask |= (1 << i);
+			else
+				for (i = digit1; i <= digit2; i++)
+					bmask |= (1 << i);
+			digit1 = digit2 = 0;
+			if (*s) s++;
+			continue;
+		}
+		return -6;
+	}
+	if (activep) *activep = active;
+	if (bmaskp) *bmaskp = bmask;
+	return 0;
+}
+
+static int FVteln2capi20(char *teln, u8 AdditionalInfo[1 + 2 + 2 + 31])
+{
+	unsigned long bmask;
+	int active;
+	int rc, i;
+
+	rc = decodeFVteln(teln, &bmask, &active);
+	if (rc) return rc;
+	/* Length */
+	AdditionalInfo[0] = 2 + 2 + 31;
+	/* Channel: 3 => use channel allocation */
+	AdditionalInfo[1] = 3; AdditionalInfo[2] = 0;
+	/* Operation: 0 => DTE mode, 1 => DCE mode */
+	if (active) {
+		AdditionalInfo[3] = 0; AdditionalInfo[4] = 0;
+	} else {
+		AdditionalInfo[3] = 1; AdditionalInfo[4] = 0;
+	}
+	/* Channel mask array */
+	AdditionalInfo[5] = 0; /* no D-Channel */
+	for (i = 1; i <= 30; i++)
+		AdditionalInfo[5 + i] = (bmask & (1 << i)) ? 0xff : 0;
+	return 0;
+}
+
+static int capidrv_command(isdn_ctrl *c, capidrv_contr *card)
+{
+	isdn_ctrl cmd;
+	struct capidrv_bchan *bchan;
+	struct capidrv_plci *plcip;
+	u8 AdditionalInfo[1 + 2 + 2 + 31];
+	int rc, isleasedline = 0;
+
+	if (c->command == ISDN_CMD_IOCTL)
+		return capidrv_ioctl(c, card);
+
+	switch (c->command) {
+	case ISDN_CMD_DIAL: {
+		u8 calling[ISDN_MSNLEN + 3];
+		u8 called[ISDN_MSNLEN + 2];
+
+		if (debugmode)
+			printk(KERN_DEBUG "capidrv-%d: ISDN_CMD_DIAL(ch=%ld,\"%s,%d,%d,%s\")\n",
+			       card->contrnr,
+			       c->arg,
+			       c->parm.setup.phone,
+			       c->parm.setup.si1,
+			       c->parm.setup.si2,
+			       c->parm.setup.eazmsn);
+
+		bchan = &card->bchans[c->arg % card->nbchan];
+
+		if (bchan->plcip) {
+			printk(KERN_ERR "capidrv-%d: dail ch=%ld,\"%s,%d,%d,%s\" in use (plci=0x%x)\n",
+			       card->contrnr,
+			       c->arg,
+			       c->parm.setup.phone,
+			       c->parm.setup.si1,
+			       c->parm.setup.si2,
+			       c->parm.setup.eazmsn,
+			       bchan->plcip->plci);
+			return 0;
+		}
+		bchan->si1 = c->parm.setup.si1;
+		bchan->si2 = c->parm.setup.si2;
+
+		strncpy(bchan->num, c->parm.setup.phone, sizeof(bchan->num));
+		strncpy(bchan->mynum, c->parm.setup.eazmsn, sizeof(bchan->mynum));
+		rc = FVteln2capi20(bchan->num, AdditionalInfo);
+		isleasedline = (rc == 0);
+		if (rc < 0)
+			printk(KERN_ERR "capidrv-%d: WARNING: invalid leased linedefinition \"%s\"\n", card->contrnr, bchan->num);
+
+		if (isleasedline) {
+			calling[0] = 0;
+			called[0] = 0;
+			if (debugmode)
+				printk(KERN_DEBUG "capidrv-%d: connecting leased line\n", card->contrnr);
+		} else {
+			calling[0] = strlen(bchan->mynum) + 2;
+			calling[1] = 0;
+			calling[2] = 0x80;
+			strncpy(calling + 3, bchan->mynum, ISDN_MSNLEN);
+			called[0] = strlen(bchan->num) + 1;
+			called[1] = 0x80;
+			strncpy(called + 2, bchan->num, ISDN_MSNLEN);
+		}
+
+		capi_fill_CONNECT_REQ(&cmdcmsg,
+				      global.ap.applid,
+				      card->msgid++,
+				      card->contrnr,	/* adr */
+				      si2cip(bchan->si1, bchan->si2),	/* cipvalue */
+				      called,	/* CalledPartyNumber */
+				      calling,	/* CallingPartyNumber */
+				      NULL,	/* CalledPartySubaddress */
+				      NULL,	/* CallingPartySubaddress */
+				      b1prot(bchan->l2, bchan->l3),	/* B1protocol */
+				      b2prot(bchan->l2, bchan->l3),	/* B2protocol */
+				      b3prot(bchan->l2, bchan->l3),	/* B3protocol */
+				      b1config(bchan->l2, bchan->l3),	/* B1configuration */
+				      NULL,	/* B2configuration */
+				      NULL,	/* B3configuration */
+				      NULL,	/* BC */
+				      NULL,	/* LLC */
+				      NULL,	/* HLC */
+				      /* BChannelinformation */
+				      isleasedline ? AdditionalInfo : NULL,
+				      NULL,	/* Keypadfacility */
+				      NULL,	/* Useruserdata */
+				      NULL	/* Facilitydataarray */
+			);
+		if ((plcip = new_plci(card, (c->arg % card->nbchan))) == NULL) {
+			cmd.command = ISDN_STAT_DHUP;
+			cmd.driver = card->myid;
+			cmd.arg = (c->arg % card->nbchan);
+			card->interface.statcallb(&cmd);
+			return -1;
+		}
+		plcip->msgid = cmdcmsg.Messagenumber;
+		plcip->leasedline = isleasedline;
+		plci_change_state(card, plcip, EV_PLCI_CONNECT_REQ);
+		send_message(card, &cmdcmsg);
+		return 0;
+	}
+
+	case ISDN_CMD_ACCEPTD:
+
+		bchan = &card->bchans[c->arg % card->nbchan];
+		if (debugmode)
+			printk(KERN_DEBUG "capidrv-%d: ISDN_CMD_ACCEPTD(ch=%ld) l2=%d l3=%d\n",
+			       card->contrnr,
+			       c->arg, bchan->l2, bchan->l3);
+
+		capi_fill_CONNECT_RESP(&cmdcmsg,
+				       global.ap.applid,
+				       card->msgid++,
+				       bchan->plcip->plci,	/* adr */
+				       0,	/* Reject */
+				       b1prot(bchan->l2, bchan->l3),	/* B1protocol */
+				       b2prot(bchan->l2, bchan->l3),	/* B2protocol */
+				       b3prot(bchan->l2, bchan->l3),	/* B3protocol */
+				       b1config(bchan->l2, bchan->l3),	/* B1configuration */
+				       NULL,	/* B2configuration */
+				       NULL,	/* B3configuration */
+				       NULL,	/* ConnectedNumber */
+				       NULL,	/* ConnectedSubaddress */
+				       NULL,	/* LLC */
+				       NULL,	/* BChannelinformation */
+				       NULL,	/* Keypadfacility */
+				       NULL,	/* Useruserdata */
+				       NULL	/* Facilitydataarray */
+			);
+		if (capi_cmsg2message(&cmdcmsg, cmdcmsg.buf)) {
+			printk(KERN_ERR "capidrv-%d: capidrv_command: parser failure\n",
+			       card->contrnr);
+			return -EINVAL;
+		}
+		plci_change_state(card, bchan->plcip, EV_PLCI_CONNECT_RESP);
+		send_message(card, &cmdcmsg);
+		return 0;
+
+	case ISDN_CMD_ACCEPTB:
+		if (debugmode)
+			printk(KERN_DEBUG "capidrv-%d: ISDN_CMD_ACCEPTB(ch=%ld)\n",
+			       card->contrnr,
+			       c->arg);
+		return -ENOSYS;
+
+	case ISDN_CMD_HANGUP:
+		if (debugmode)
+			printk(KERN_DEBUG "capidrv-%d: ISDN_CMD_HANGUP(ch=%ld)\n",
+			       card->contrnr,
+			       c->arg);
+		bchan = &card->bchans[c->arg % card->nbchan];
+
+		if (bchan->disconnecting) {
+			if (debugmode)
+				printk(KERN_DEBUG "capidrv-%d: chan %ld already disconnecting ...\n",
+				       card->contrnr,
+				       c->arg);
+			return 0;
+		}
+		if (bchan->nccip) {
+			bchan->disconnecting = 1;
+			capi_fill_DISCONNECT_B3_REQ(&cmdcmsg,
+						    global.ap.applid,
+						    card->msgid++,
+						    bchan->nccip->ncci,
+						    NULL	/* NCPI */
+				);
+			ncci_change_state(card, bchan->nccip, EV_NCCI_DISCONNECT_B3_REQ);
+			send_message(card, &cmdcmsg);
+			return 0;
+		} else if (bchan->plcip) {
+			if (bchan->plcip->state == ST_PLCI_INCOMING) {
+				/*
+				 * just ignore, we a called from
+				 * isdn_status_callback(),
+				 * which will return 0 or 2, this is handled
+				 * by the CONNECT_IND handler
+				 */
+				bchan->disconnecting = 1;
+				return 0;
+			} else if (bchan->plcip->plci) {
+				bchan->disconnecting = 1;
+				capi_fill_DISCONNECT_REQ(&cmdcmsg,
+							 global.ap.applid,
+							 card->msgid++,
+							 bchan->plcip->plci,
+							 NULL,	/* BChannelinformation */
+							 NULL,	/* Keypadfacility */
+							 NULL,	/* Useruserdata */
+							 NULL	/* Facilitydataarray */
+					);
+				plci_change_state(card, bchan->plcip, EV_PLCI_DISCONNECT_REQ);
+				send_message(card, &cmdcmsg);
+				return 0;
+			} else {
+				printk(KERN_ERR "capidrv-%d: chan %ld disconnect request while waiting for CONNECT_CONF\n",
+				       card->contrnr,
+				       c->arg);
+				return -EINVAL;
+			}
+		}
+		printk(KERN_ERR "capidrv-%d: chan %ld disconnect request on free channel\n",
+		       card->contrnr,
+		       c->arg);
+		return -EINVAL;
+/* ready */
+
+	case ISDN_CMD_SETL2:
+		if (debugmode)
+			printk(KERN_DEBUG "capidrv-%d: set L2 on chan %ld to %ld\n",
+			       card->contrnr,
+			       (c->arg & 0xff), (c->arg >> 8));
+		bchan = &card->bchans[(c->arg & 0xff) % card->nbchan];
+		bchan->l2 = (c->arg >> 8);
+		return 0;
+
+	case ISDN_CMD_SETL3:
+		if (debugmode)
+			printk(KERN_DEBUG "capidrv-%d: set L3 on chan %ld to %ld\n",
+			       card->contrnr,
+			       (c->arg & 0xff), (c->arg >> 8));
+		bchan = &card->bchans[(c->arg & 0xff) % card->nbchan];
+		bchan->l3 = (c->arg >> 8);
+		return 0;
+
+	case ISDN_CMD_SETEAZ:
+		if (debugmode)
+			printk(KERN_DEBUG "capidrv-%d: set EAZ \"%s\" on chan %ld\n",
+			       card->contrnr,
+			       c->parm.num, c->arg);
+		bchan = &card->bchans[c->arg % card->nbchan];
+		strncpy(bchan->msn, c->parm.num, ISDN_MSNLEN);
+		return 0;
+
+	case ISDN_CMD_CLREAZ:
+		if (debugmode)
+			printk(KERN_DEBUG "capidrv-%d: clearing EAZ on chan %ld\n",
+			       card->contrnr, c->arg);
+		bchan = &card->bchans[c->arg % card->nbchan];
+		bchan->msn[0] = 0;
+		return 0;
+
+	default:
+		printk(KERN_ERR "capidrv-%d: ISDN_CMD_%d, Huh?\n",
+		       card->contrnr, c->command);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int if_command(isdn_ctrl *c)
+{
+	capidrv_contr *card = findcontrbydriverid(c->driver);
+
+	if (card)
+		return capidrv_command(c, card);
+
+	printk(KERN_ERR
+	       "capidrv: if_command %d called with invalid driverId %d!\n",
+	       c->command, c->driver);
+	return -ENODEV;
+}
+
+static _cmsg sendcmsg;
+
+static int if_sendbuf(int id, int channel, int doack, struct sk_buff *skb)
+{
+	capidrv_contr *card = findcontrbydriverid(id);
+	capidrv_bchan *bchan;
+	capidrv_ncci *nccip;
+	int len = skb->len;
+	int msglen;
+	u16 errcode;
+	u16 datahandle;
+	u32 data;
+
+	if (!card) {
+		printk(KERN_ERR "capidrv: if_sendbuf called with invalid driverId %d!\n",
+		       id);
+		return 0;
+	}
+	if (debugmode > 4)
+		printk(KERN_DEBUG "capidrv-%d: sendbuf len=%d skb=%p doack=%d\n",
+		       card->contrnr, len, skb, doack);
+	bchan = &card->bchans[channel % card->nbchan];
+	nccip = bchan->nccip;
+	if (!nccip || nccip->state != ST_NCCI_ACTIVE) {
+		printk(KERN_ERR "capidrv-%d: if_sendbuf: %s:%d: chan not up!\n",
+		       card->contrnr, card->name, channel);
+		return 0;
+	}
+	datahandle = nccip->datahandle;
+
+	/*
+	 * Here we copy pointer skb->data into the 32-bit 'Data' field.
+	 * The 'Data' field is not used in practice in linux kernel
+	 * (neither in 32 or 64 bit), but should have some value,
+	 * since a CAPI message trace will display it.
+	 *
+	 * The correct value in the 32 bit case is the address of the
+	 * data, in 64 bit it makes no sense, we use 0 there.
+	 */
+
+#ifdef CONFIG_64BIT
+	data = 0;
+#else
+	data = (unsigned long) skb->data;
+#endif
+
+	capi_fill_DATA_B3_REQ(&sendcmsg, global.ap.applid, card->msgid++,
+			      nccip->ncci,	/* adr */
+			      data,		/* Data */
+			      skb->len,		/* DataLength */
+			      datahandle,	/* DataHandle */
+			      0	/* Flags */
+		);
+
+	if (capidrv_add_ack(nccip, datahandle, doack ? (int)skb->len : -1) < 0)
+		return 0;
+
+	if (capi_cmsg2message(&sendcmsg, sendcmsg.buf)) {
+		printk(KERN_ERR "capidrv-%d: if_sendbuf: parser failure\n",
+		       card->contrnr);
+		return -EINVAL;
+	}
+	msglen = CAPIMSG_LEN(sendcmsg.buf);
+	if (skb_headroom(skb) < msglen) {
+		struct sk_buff *nskb = skb_realloc_headroom(skb, msglen);
+		if (!nskb) {
+			printk(KERN_ERR "capidrv-%d: if_sendbuf: no memory\n",
+			       card->contrnr);
+			(void)capidrv_del_ack(nccip, datahandle);
+			return 0;
+		}
+		printk(KERN_DEBUG "capidrv-%d: only %d bytes headroom, need %d\n",
+		       card->contrnr, skb_headroom(skb), msglen);
+		memcpy(skb_push(nskb, msglen), sendcmsg.buf, msglen);
+		errcode = capi20_put_message(&global.ap, nskb);
+		if (errcode == CAPI_NOERROR) {
+			dev_kfree_skb(skb);
+			nccip->datahandle++;
+			return len;
+		}
+		if (debugmode > 3)
+			printk(KERN_DEBUG "capidrv-%d: sendbuf putmsg ret(%x) - %s\n",
+			       card->contrnr, errcode, capi_info2str(errcode));
+		(void)capidrv_del_ack(nccip, datahandle);
+		dev_kfree_skb(nskb);
+		return errcode == CAPI_SENDQUEUEFULL ? 0 : -1;
+	} else {
+		memcpy(skb_push(skb, msglen), sendcmsg.buf, msglen);
+		errcode = capi20_put_message(&global.ap, skb);
+		if (errcode == CAPI_NOERROR) {
+			nccip->datahandle++;
+			return len;
+		}
+		if (debugmode > 3)
+			printk(KERN_DEBUG "capidrv-%d: sendbuf putmsg ret(%x) - %s\n",
+			       card->contrnr, errcode, capi_info2str(errcode));
+		skb_pull(skb, msglen);
+		(void)capidrv_del_ack(nccip, datahandle);
+		return errcode == CAPI_SENDQUEUEFULL ? 0 : -1;
+	}
+}
+
+static int if_readstat(u8 __user *buf, int len, int id, int channel)
+{
+	capidrv_contr *card = findcontrbydriverid(id);
+	int count;
+	u8 __user *p;
+
+	if (!card) {
+		printk(KERN_ERR "capidrv: if_readstat called with invalid driverId %d!\n",
+		       id);
+		return -ENODEV;
+	}
+
+	for (p = buf, count = 0; count < len; p++, count++) {
+		if (put_user(*card->q931_read++, p))
+			return -EFAULT;
+		if (card->q931_read > card->q931_end)
+			card->q931_read = card->q931_buf;
+	}
+	return count;
+
+}
+
+static void enable_dchannel_trace(capidrv_contr *card)
+{
+	u8 manufacturer[CAPI_MANUFACTURER_LEN];
+	capi_version version;
+	u16 contr = card->contrnr;
+	u16 errcode;
+	u16 avmversion[3];
+
+	errcode = capi20_get_manufacturer(contr, manufacturer);
+	if (errcode != CAPI_NOERROR) {
+		printk(KERN_ERR "%s: can't get manufacturer (0x%x)\n",
+		       card->name, errcode);
+		return;
+	}
+	if (strstr(manufacturer, "AVM") == NULL) {
+		printk(KERN_ERR "%s: not from AVM, no d-channel trace possible (%s)\n",
+		       card->name, manufacturer);
+		return;
+	}
+	errcode = capi20_get_version(contr, &version);
+	if (errcode != CAPI_NOERROR) {
+		printk(KERN_ERR "%s: can't get version (0x%x)\n",
+		       card->name, errcode);
+		return;
+	}
+	avmversion[0] = (version.majormanuversion >> 4) & 0x0f;
+	avmversion[1] = (version.majormanuversion << 4) & 0xf0;
+	avmversion[1] |= (version.minormanuversion >> 4) & 0x0f;
+	avmversion[2] |= version.minormanuversion & 0x0f;
+
+	if (avmversion[0] > 3 || (avmversion[0] == 3 && avmversion[1] > 5)) {
+		printk(KERN_INFO "%s: D2 trace enabled\n", card->name);
+		capi_fill_MANUFACTURER_REQ(&cmdcmsg, global.ap.applid,
+					   card->msgid++,
+					   contr,
+					   0x214D5641,  /* ManuID */
+					   0,           /* Class */
+					   1,           /* Function */
+					   (_cstruct)"\004\200\014\000\000");
+	} else {
+		printk(KERN_INFO "%s: D3 trace enabled\n", card->name);
+		capi_fill_MANUFACTURER_REQ(&cmdcmsg, global.ap.applid,
+					   card->msgid++,
+					   contr,
+					   0x214D5641,  /* ManuID */
+					   0,           /* Class */
+					   1,           /* Function */
+					   (_cstruct)"\004\002\003\000\000");
+	}
+	send_message(card, &cmdcmsg);
+}
+
+
+static void send_listen(capidrv_contr *card)
+{
+	capi_fill_LISTEN_REQ(&cmdcmsg, global.ap.applid,
+			     card->msgid++,
+			     card->contrnr, /* controller */
+			     1 << 6,	/* Infomask */
+			     card->cipmask,
+			     card->cipmask2,
+			     NULL, NULL);
+	listen_change_state(card, EV_LISTEN_REQ);
+	send_message(card, &cmdcmsg);
+}
+
+static void listentimerfunc(unsigned long x)
+{
+	capidrv_contr *card = (capidrv_contr *)x;
+	if (card->state != ST_LISTEN_NONE && card->state != ST_LISTEN_ACTIVE)
+		printk(KERN_ERR "%s: controller dead ??\n", card->name);
+	send_listen(card);
+	mod_timer(&card->listentimer, jiffies + 60 * HZ);
+}
+
+
+static int capidrv_addcontr(u16 contr, struct capi_profile *profp)
+{
+	capidrv_contr *card;
+	unsigned long flags;
+	isdn_ctrl cmd;
+	char id[20];
+	int i;
+
+	sprintf(id, "capidrv-%d", contr);
+	if (!try_module_get(THIS_MODULE)) {
+		printk(KERN_WARNING "capidrv: (%s) Could not reserve module\n", id);
+		return -1;
+	}
+	if (!(card = kzalloc(sizeof(capidrv_contr), GFP_ATOMIC))) {
+		printk(KERN_WARNING
+		       "capidrv: (%s) Could not allocate contr-struct.\n", id);
+		return -1;
+	}
+	card->owner = THIS_MODULE;
+	setup_timer(&card->listentimer, listentimerfunc, (unsigned long)card);
+	strcpy(card->name, id);
+	card->contrnr = contr;
+	card->nbchan = profp->nbchannel;
+	card->bchans = kmalloc(sizeof(capidrv_bchan) * card->nbchan, GFP_ATOMIC);
+	if (!card->bchans) {
+		printk(KERN_WARNING
+		       "capidrv: (%s) Could not allocate bchan-structs.\n", id);
+		module_put(card->owner);
+		kfree(card);
+		return -1;
+	}
+	card->interface.channels = profp->nbchannel;
+	card->interface.maxbufsize = 2048;
+	card->interface.command = if_command;
+	card->interface.writebuf_skb = if_sendbuf;
+	card->interface.writecmd = NULL;
+	card->interface.readstat = if_readstat;
+	card->interface.features =
+		ISDN_FEATURE_L2_HDLC |
+		ISDN_FEATURE_L2_TRANS |
+		ISDN_FEATURE_L3_TRANS |
+		ISDN_FEATURE_P_UNKNOWN |
+		ISDN_FEATURE_L2_X75I |
+		ISDN_FEATURE_L2_X75UI |
+		ISDN_FEATURE_L2_X75BUI;
+	if (profp->support1 & (1 << 2))
+		card->interface.features |=
+			ISDN_FEATURE_L2_V11096 |
+			ISDN_FEATURE_L2_V11019 |
+			ISDN_FEATURE_L2_V11038;
+	if (profp->support1 & (1 << 8))
+		card->interface.features |= ISDN_FEATURE_L2_MODEM;
+	card->interface.hl_hdrlen = 22; /* len of DATA_B3_REQ */
+	strncpy(card->interface.id, id, sizeof(card->interface.id) - 1);
+
+
+	card->q931_read = card->q931_buf;
+	card->q931_write = card->q931_buf;
+	card->q931_end = card->q931_buf + sizeof(card->q931_buf) - 1;
+
+	if (!register_isdn(&card->interface)) {
+		printk(KERN_ERR "capidrv: Unable to register contr %s\n", id);
+		kfree(card->bchans);
+		module_put(card->owner);
+		kfree(card);
+		return -1;
+	}
+	card->myid = card->interface.channels;
+	memset(card->bchans, 0, sizeof(capidrv_bchan) * card->nbchan);
+	for (i = 0; i < card->nbchan; i++) {
+		card->bchans[i].contr = card;
+	}
+
+	spin_lock_irqsave(&global_lock, flags);
+	card->next = global.contr_list;
+	global.contr_list = card;
+	global.ncontr++;
+	spin_unlock_irqrestore(&global_lock, flags);
+
+	cmd.command = ISDN_STAT_RUN;
+	cmd.driver = card->myid;
+	card->interface.statcallb(&cmd);
+
+	card->cipmask = 0x1FFF03FF;	/* any */
+	card->cipmask2 = 0;
+
+	send_listen(card);
+	mod_timer(&card->listentimer, jiffies + 60 * HZ);
+
+	printk(KERN_INFO "%s: now up (%d B channels)\n",
+	       card->name, card->nbchan);
+
+	enable_dchannel_trace(card);
+
+	return 0;
+}
+
+static int capidrv_delcontr(u16 contr)
+{
+	capidrv_contr **pp, *card;
+	unsigned long flags;
+	isdn_ctrl cmd;
+
+	spin_lock_irqsave(&global_lock, flags);
+	for (card = global.contr_list; card; card = card->next) {
+		if (card->contrnr == contr)
+			break;
+	}
+	if (!card) {
+		spin_unlock_irqrestore(&global_lock, flags);
+		printk(KERN_ERR "capidrv: delcontr: no contr %u\n", contr);
+		return -1;
+	}
+
+	/* FIXME: maybe a race condition the card should be removed
+	 * here from global list /kkeil
+	 */
+	spin_unlock_irqrestore(&global_lock, flags);
+
+	del_timer(&card->listentimer);
+
+	if (debugmode)
+		printk(KERN_DEBUG "capidrv-%d: id=%d unloading\n",
+		       card->contrnr, card->myid);
+
+	cmd.command = ISDN_STAT_STOP;
+	cmd.driver = card->myid;
+	card->interface.statcallb(&cmd);
+
+	while (card->nbchan) {
+
+		cmd.command = ISDN_STAT_DISCH;
+		cmd.driver = card->myid;
+		cmd.arg = card->nbchan - 1;
+		cmd.parm.num[0] = 0;
+		if (debugmode)
+			printk(KERN_DEBUG "capidrv-%d: id=%d disable chan=%ld\n",
+			       card->contrnr, card->myid, cmd.arg);
+		card->interface.statcallb(&cmd);
+
+		if (card->bchans[card->nbchan - 1].nccip)
+			free_ncci(card, card->bchans[card->nbchan - 1].nccip);
+		if (card->bchans[card->nbchan - 1].plcip)
+			free_plci(card, card->bchans[card->nbchan - 1].plcip);
+		if (card->plci_list)
+			printk(KERN_ERR "capidrv: bug in free_plci()\n");
+		card->nbchan--;
+	}
+	kfree(card->bchans);
+	card->bchans = NULL;
+
+	if (debugmode)
+		printk(KERN_DEBUG "capidrv-%d: id=%d isdn unload\n",
+		       card->contrnr, card->myid);
+
+	cmd.command = ISDN_STAT_UNLOAD;
+	cmd.driver = card->myid;
+	card->interface.statcallb(&cmd);
+
+	if (debugmode)
+		printk(KERN_DEBUG "capidrv-%d: id=%d remove contr from list\n",
+		       card->contrnr, card->myid);
+
+	spin_lock_irqsave(&global_lock, flags);
+	for (pp = &global.contr_list; *pp; pp = &(*pp)->next) {
+		if (*pp == card) {
+			*pp = (*pp)->next;
+			card->next = NULL;
+			global.ncontr--;
+			break;
+		}
+	}
+	spin_unlock_irqrestore(&global_lock, flags);
+
+	module_put(card->owner);
+	printk(KERN_INFO "%s: now down.\n", card->name);
+	kfree(card);
+	return 0;
+}
+
+
+static int
+lower_callback(struct notifier_block *nb, unsigned long val, void *v)
+{
+	capi_profile profile;
+	u32 contr = (long)v;
+
+	switch (val) {
+	case CAPICTR_UP:
+		printk(KERN_INFO "capidrv: controller %hu up\n", contr);
+		if (capi20_get_profile(contr, &profile) == CAPI_NOERROR)
+			(void) capidrv_addcontr(contr, &profile);
+		break;
+	case CAPICTR_DOWN:
+		printk(KERN_INFO "capidrv: controller %hu down\n", contr);
+		(void) capidrv_delcontr(contr);
+		break;
+	}
+	return NOTIFY_OK;
+}
+
+/*
+ * /proc/capi/capidrv:
+ * nrecvctlpkt nrecvdatapkt nsendctlpkt nsenddatapkt
+ */
+static int capidrv_proc_show(struct seq_file *m, void *v)
+{
+	seq_printf(m, "%lu %lu %lu %lu\n",
+		   global.ap.nrecvctlpkt,
+		   global.ap.nrecvdatapkt,
+		   global.ap.nsentctlpkt,
+		   global.ap.nsentdatapkt);
+	return 0;
+}
+
+static int capidrv_proc_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, capidrv_proc_show, NULL);
+}
+
+static const struct file_operations capidrv_proc_fops = {
+	.owner		= THIS_MODULE,
+	.open		= capidrv_proc_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+static void __init proc_init(void)
+{
+	proc_create("capi/capidrv", 0, NULL, &capidrv_proc_fops);
+}
+
+static void __exit proc_exit(void)
+{
+	remove_proc_entry("capi/capidrv", NULL);
+}
+
+static struct notifier_block capictr_nb = {
+	.notifier_call = lower_callback,
+};
+
+static int __init capidrv_init(void)
+{
+	capi_profile profile;
+	u32 ncontr, contr;
+	u16 errcode;
+
+	global.ap.rparam.level3cnt = -2;  /* number of bchannels twice */
+	global.ap.rparam.datablkcnt = 16;
+	global.ap.rparam.datablklen = 2048;
+
+	global.ap.recv_message = capidrv_recv_message;
+	errcode = capi20_register(&global.ap);
+	if (errcode) {
+		return -EIO;
+	}
+
+	register_capictr_notifier(&capictr_nb);
+
+	errcode = capi20_get_profile(0, &profile);
+	if (errcode != CAPI_NOERROR) {
+		unregister_capictr_notifier(&capictr_nb);
+		capi20_release(&global.ap);
+		return -EIO;
+	}
+
+	ncontr = profile.ncontroller;
+	for (contr = 1; contr <= ncontr; contr++) {
+		errcode = capi20_get_profile(contr, &profile);
+		if (errcode != CAPI_NOERROR)
+			continue;
+		(void) capidrv_addcontr(contr, &profile);
+	}
+	proc_init();
+
+	return 0;
+}
+
+static void __exit capidrv_exit(void)
+{
+	unregister_capictr_notifier(&capictr_nb);
+	capi20_release(&global.ap);
+
+	proc_exit();
+}
+
+module_init(capidrv_init);
+module_exit(capidrv_exit);
diff --git a/src/kernel/linux/v4.14/drivers/isdn/capi/capidrv.h b/src/kernel/linux/v4.14/drivers/isdn/capi/capidrv.h
new file mode 100644
index 0000000..4466b2e
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/isdn/capi/capidrv.h
@@ -0,0 +1,140 @@
+/* $Id: capidrv.h,v 1.2.8.2 2001/09/23 22:24:33 kai Exp $
+ *
+ * ISDN4Linux Driver, using capi20 interface (kernelcapi)
+ *
+ * Copyright 1997 by Carsten Paeth <calle@calle.de>
+ *
+ * This software may be used and distributed according to the terms
+ * of the GNU General Public License, incorporated herein by reference.
+ *
+ */
+
+#ifndef __CAPIDRV_H__
+#define __CAPIDRV_H__
+
+/*
+ * LISTEN state machine
+ */
+#define ST_LISTEN_NONE			0	/* L-0 */
+#define ST_LISTEN_WAIT_CONF		1	/* L-0.1 */
+#define ST_LISTEN_ACTIVE		2	/* L-1 */
+#define ST_LISTEN_ACTIVE_WAIT_CONF	3	/* L-1.1 */
+
+
+#define EV_LISTEN_REQ			1	/* L-0 -> L-0.1
+						   L-1 -> L-1.1 */
+#define EV_LISTEN_CONF_ERROR		2	/* L-0.1 -> L-0
+						   L-1.1 -> L-1 */
+#define EV_LISTEN_CONF_EMPTY		3	/* L-0.1 -> L-0
+						   L-1.1 -> L-0 */
+#define EV_LISTEN_CONF_OK		4	/* L-0.1 -> L-1
+						   L-1.1 -> L.1 */
+
+/*
+ * per plci state machine
+ */
+#define ST_PLCI_NONE			0	/* P-0 */
+#define ST_PLCI_OUTGOING		1	/* P-0.1 */
+#define ST_PLCI_ALLOCATED		2	/* P-1 */
+#define ST_PLCI_ACTIVE			3	/* P-ACT */
+#define ST_PLCI_INCOMING		4	/* P-2 */
+#define ST_PLCI_FACILITY_IND		5	/* P-3 */
+#define ST_PLCI_ACCEPTING		6	/* P-4 */
+#define ST_PLCI_DISCONNECTING		7	/* P-5 */
+#define ST_PLCI_DISCONNECTED		8	/* P-6 */
+#define ST_PLCI_RESUMEING		9	/* P-0.Res */
+#define ST_PLCI_RESUME			10	/* P-Res */
+#define ST_PLCI_HELD			11	/* P-HELD */
+
+#define EV_PLCI_CONNECT_REQ		1	/* P-0 -> P-0.1
+						 */
+#define EV_PLCI_CONNECT_CONF_ERROR	2	/* P-0.1 -> P-0
+						 */
+#define EV_PLCI_CONNECT_CONF_OK		3	/* P-0.1 -> P-1
+						 */
+#define EV_PLCI_FACILITY_IND_UP		4	/* P-0 -> P-1
+						 */
+#define EV_PLCI_CONNECT_IND		5	/* P-0 -> P-2
+						 */
+#define EV_PLCI_CONNECT_ACTIVE_IND	6	/* P-1 -> P-ACT
+						 */
+#define EV_PLCI_CONNECT_REJECT		7	/* P-2 -> P-5
+						   P-3 -> P-5
+						*/
+#define EV_PLCI_DISCONNECT_REQ		8	/* P-1 -> P-5
+						   P-2 -> P-5
+						   P-3 -> P-5
+						   P-4 -> P-5
+						   P-ACT -> P-5
+						   P-Res -> P-5 (*)
+						   P-HELD -> P-5 (*)
+						*/
+#define EV_PLCI_DISCONNECT_IND		9	/* P-1 -> P-6
+						   P-2 -> P-6
+						   P-3 -> P-6
+						   P-4 -> P-6
+						   P-5 -> P-6
+						   P-ACT -> P-6
+						   P-Res -> P-6 (*)
+						   P-HELD -> P-6 (*)
+						*/
+#define EV_PLCI_FACILITY_IND_DOWN	10	/* P-0.1 -> P-5
+						   P-1 -> P-5
+						   P-ACT -> P-5
+						   P-2 -> P-5
+						   P-3 -> P-5
+						   P-4 -> P-5
+						*/
+#define EV_PLCI_DISCONNECT_RESP		11	/* P-6 -> P-0
+						 */
+#define EV_PLCI_CONNECT_RESP		12	/* P-6 -> P-0
+						 */
+
+#define EV_PLCI_RESUME_REQ		13	/* P-0 -> P-0.Res
+						 */
+#define EV_PLCI_RESUME_CONF_OK		14	/* P-0.Res -> P-Res
+						 */
+#define EV_PLCI_RESUME_CONF_ERROR	15	/* P-0.Res -> P-0
+						 */
+#define EV_PLCI_RESUME_IND		16	/* P-Res -> P-ACT
+						 */
+#define EV_PLCI_HOLD_IND		17	/* P-ACT -> P-HELD
+						 */
+#define EV_PLCI_RETRIEVE_IND		18	/* P-HELD -> P-ACT
+						 */
+#define EV_PLCI_SUSPEND_IND		19	/* P-ACT -> P-5
+						 */
+#define EV_PLCI_CD_IND			20	/* P-2 -> P-5
+						 */
+
+/*
+ * per ncci state machine
+ */
+#define ST_NCCI_PREVIOUS			-1
+#define ST_NCCI_NONE				0	/* N-0 */
+#define ST_NCCI_OUTGOING			1	/* N-0.1 */
+#define ST_NCCI_INCOMING			2	/* N-1 */
+#define ST_NCCI_ALLOCATED			3	/* N-2 */
+#define ST_NCCI_ACTIVE				4	/* N-ACT */
+#define ST_NCCI_RESETING			5	/* N-3 */
+#define ST_NCCI_DISCONNECTING			6	/* N-4 */
+#define ST_NCCI_DISCONNECTED			7	/* N-5 */
+
+#define EV_NCCI_CONNECT_B3_REQ			1	/* N-0 -> N-0.1 */
+#define EV_NCCI_CONNECT_B3_IND			2	/* N-0 -> N.1 */
+#define EV_NCCI_CONNECT_B3_CONF_OK		3	/* N-0.1 -> N.2 */
+#define EV_NCCI_CONNECT_B3_CONF_ERROR		4	/* N-0.1 -> N.0 */
+#define EV_NCCI_CONNECT_B3_REJECT		5	/* N-1 -> N-4 */
+#define EV_NCCI_CONNECT_B3_RESP			6	/* N-1 -> N-2 */
+#define EV_NCCI_CONNECT_B3_ACTIVE_IND		7	/* N-2 -> N-ACT */
+#define EV_NCCI_RESET_B3_REQ			8	/* N-ACT -> N-3 */
+#define EV_NCCI_RESET_B3_IND			9	/* N-3 -> N-ACT */
+#define EV_NCCI_DISCONNECT_B3_IND		10	/* N-4 -> N.5 */
+#define EV_NCCI_DISCONNECT_B3_CONF_ERROR	11	/* N-4 -> previous */
+#define EV_NCCI_DISCONNECT_B3_REQ		12	/* N-1 -> N-4
+							   N-2 -> N-4
+							   N-3 -> N-4
+							   N-ACT -> N-4 */
+#define EV_NCCI_DISCONNECT_B3_RESP		13	/* N-5 -> N-0 */
+
+#endif				/* __CAPIDRV_H__ */
diff --git a/src/kernel/linux/v4.14/drivers/isdn/capi/capilib.c b/src/kernel/linux/v4.14/drivers/isdn/capi/capilib.c
new file mode 100644
index 0000000..a39ad37
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/isdn/capi/capilib.c
@@ -0,0 +1,202 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/isdn/capilli.h>
+
+#define DBG(format, arg...) do {					\
+		printk(KERN_DEBUG "%s: " format "\n" , __func__ , ## arg); \
+	} while (0)
+
+struct capilib_msgidqueue {
+	struct capilib_msgidqueue *next;
+	u16 msgid;
+};
+
+struct capilib_ncci {
+	struct list_head list;
+	u16 applid;
+	u32 ncci;
+	u32 winsize;
+	int   nmsg;
+	struct capilib_msgidqueue *msgidqueue;
+	struct capilib_msgidqueue *msgidlast;
+	struct capilib_msgidqueue *msgidfree;
+	struct capilib_msgidqueue msgidpool[CAPI_MAXDATAWINDOW];
+};
+
+// ---------------------------------------------------------------------------
+// NCCI Handling
+
+static inline void mq_init(struct capilib_ncci *np)
+{
+	u_int i;
+	np->msgidqueue = NULL;
+	np->msgidlast = NULL;
+	np->nmsg = 0;
+	memset(np->msgidpool, 0, sizeof(np->msgidpool));
+	np->msgidfree = &np->msgidpool[0];
+	for (i = 1; i < np->winsize; i++) {
+		np->msgidpool[i].next = np->msgidfree;
+		np->msgidfree = &np->msgidpool[i];
+	}
+}
+
+static inline int mq_enqueue(struct capilib_ncci *np, u16 msgid)
+{
+	struct capilib_msgidqueue *mq;
+	if ((mq = np->msgidfree) == NULL)
+		return 0;
+	np->msgidfree = mq->next;
+	mq->msgid = msgid;
+	mq->next = NULL;
+	if (np->msgidlast)
+		np->msgidlast->next = mq;
+	np->msgidlast = mq;
+	if (!np->msgidqueue)
+		np->msgidqueue = mq;
+	np->nmsg++;
+	return 1;
+}
+
+static inline int mq_dequeue(struct capilib_ncci *np, u16 msgid)
+{
+	struct capilib_msgidqueue **pp;
+	for (pp = &np->msgidqueue; *pp; pp = &(*pp)->next) {
+		if ((*pp)->msgid == msgid) {
+			struct capilib_msgidqueue *mq = *pp;
+			*pp = mq->next;
+			if (mq == np->msgidlast)
+				np->msgidlast = NULL;
+			mq->next = np->msgidfree;
+			np->msgidfree = mq;
+			np->nmsg--;
+			return 1;
+		}
+	}
+	return 0;
+}
+
+void capilib_new_ncci(struct list_head *head, u16 applid, u32 ncci, u32 winsize)
+{
+	struct capilib_ncci *np;
+
+	np = kmalloc(sizeof(*np), GFP_ATOMIC);
+	if (!np) {
+		printk(KERN_WARNING "capilib_new_ncci: no memory.\n");
+		return;
+	}
+	if (winsize > CAPI_MAXDATAWINDOW) {
+		printk(KERN_ERR "capi_new_ncci: winsize %d too big\n",
+		       winsize);
+		winsize = CAPI_MAXDATAWINDOW;
+	}
+	np->applid = applid;
+	np->ncci = ncci;
+	np->winsize = winsize;
+	mq_init(np);
+	list_add_tail(&np->list, head);
+	DBG("kcapi: appl %d ncci 0x%x up", applid, ncci);
+}
+
+EXPORT_SYMBOL(capilib_new_ncci);
+
+void capilib_free_ncci(struct list_head *head, u16 applid, u32 ncci)
+{
+	struct list_head *l;
+	struct capilib_ncci *np;
+
+	list_for_each(l, head) {
+		np = list_entry(l, struct capilib_ncci, list);
+		if (np->applid != applid)
+			continue;
+		if (np->ncci != ncci)
+			continue;
+		printk(KERN_INFO "kcapi: appl %d ncci 0x%x down\n", applid, ncci);
+		list_del(&np->list);
+		kfree(np);
+		return;
+	}
+	printk(KERN_ERR "capilib_free_ncci: ncci 0x%x not found\n", ncci);
+}
+
+EXPORT_SYMBOL(capilib_free_ncci);
+
+void capilib_release_appl(struct list_head *head, u16 applid)
+{
+	struct list_head *l, *n;
+	struct capilib_ncci *np;
+
+	list_for_each_safe(l, n, head) {
+		np = list_entry(l, struct capilib_ncci, list);
+		if (np->applid != applid)
+			continue;
+		printk(KERN_INFO "kcapi: appl %d ncci 0x%x forced down\n", applid, np->ncci);
+		list_del(&np->list);
+		kfree(np);
+	}
+}
+
+EXPORT_SYMBOL(capilib_release_appl);
+
+void capilib_release(struct list_head *head)
+{
+	struct list_head *l, *n;
+	struct capilib_ncci *np;
+
+	list_for_each_safe(l, n, head) {
+		np = list_entry(l, struct capilib_ncci, list);
+		printk(KERN_INFO "kcapi: appl %d ncci 0x%x forced down\n", np->applid, np->ncci);
+		list_del(&np->list);
+		kfree(np);
+	}
+}
+
+EXPORT_SYMBOL(capilib_release);
+
+u16 capilib_data_b3_req(struct list_head *head, u16 applid, u32 ncci, u16 msgid)
+{
+	struct list_head *l;
+	struct capilib_ncci *np;
+
+	list_for_each(l, head) {
+		np = list_entry(l, struct capilib_ncci, list);
+		if (np->applid != applid)
+			continue;
+		if (np->ncci != ncci)
+			continue;
+
+		if (mq_enqueue(np, msgid) == 0)
+			return CAPI_SENDQUEUEFULL;
+
+		return CAPI_NOERROR;
+	}
+	printk(KERN_ERR "capilib_data_b3_req: ncci 0x%x not found\n", ncci);
+	return CAPI_NOERROR;
+}
+
+EXPORT_SYMBOL(capilib_data_b3_req);
+
+void capilib_data_b3_conf(struct list_head *head, u16 applid, u32 ncci, u16 msgid)
+{
+	struct list_head *l;
+	struct capilib_ncci *np;
+
+	list_for_each(l, head) {
+		np = list_entry(l, struct capilib_ncci, list);
+		if (np->applid != applid)
+			continue;
+		if (np->ncci != ncci)
+			continue;
+
+		if (mq_dequeue(np, msgid) == 0) {
+			printk(KERN_ERR "kcapi: msgid %hu ncci 0x%x not on queue\n",
+			       msgid, ncci);
+		}
+		return;
+	}
+	printk(KERN_ERR "capilib_data_b3_conf: ncci 0x%x not found\n", ncci);
+}
+
+EXPORT_SYMBOL(capilib_data_b3_conf);
diff --git a/src/kernel/linux/v4.14/drivers/isdn/capi/capiutil.c b/src/kernel/linux/v4.14/drivers/isdn/capi/capiutil.c
new file mode 100644
index 0000000..9846d82
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/isdn/capi/capiutil.c
@@ -0,0 +1,900 @@
+/* $Id: capiutil.c,v 1.13.6.4 2001/09/23 22:24:33 kai Exp $
+ *
+ * CAPI 2.0 convert capi message to capi message struct
+ *
+ * From CAPI 2.0 Development Kit AVM 1995 (msg.c)
+ * Rewritten for Linux 1996 by Carsten Paeth <calle@calle.de>
+ *
+ * This software may be used and distributed according to the terms
+ * of the GNU General Public License, incorporated herein by reference.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/ctype.h>
+#include <linux/stddef.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/init.h>
+#include <linux/isdn/capiutil.h>
+#include <linux/slab.h>
+
+/* from CAPI2.0 DDK AVM Berlin GmbH */
+
+typedef struct {
+	int typ;
+	size_t off;
+} _cdef;
+
+#define _CBYTE	       1
+#define _CWORD	       2
+#define _CDWORD        3
+#define _CSTRUCT       4
+#define _CMSTRUCT      5
+#define _CEND	       6
+
+static _cdef cdef[] =
+{
+	/*00 */
+	{_CEND},
+	/*01 */
+	{_CEND},
+	/*02 */
+	{_CEND},
+	/*03 */
+	{_CDWORD, offsetof(_cmsg, adr.adrController)},
+	/*04 */
+	{_CMSTRUCT, offsetof(_cmsg, AdditionalInfo)},
+	/*05 */
+	{_CSTRUCT, offsetof(_cmsg, B1configuration)},
+	/*06 */
+	{_CWORD, offsetof(_cmsg, B1protocol)},
+	/*07 */
+	{_CSTRUCT, offsetof(_cmsg, B2configuration)},
+	/*08 */
+	{_CWORD, offsetof(_cmsg, B2protocol)},
+	/*09 */
+	{_CSTRUCT, offsetof(_cmsg, B3configuration)},
+	/*0a */
+	{_CWORD, offsetof(_cmsg, B3protocol)},
+	/*0b */
+	{_CSTRUCT, offsetof(_cmsg, BC)},
+	/*0c */
+	{_CSTRUCT, offsetof(_cmsg, BChannelinformation)},
+	/*0d */
+	{_CMSTRUCT, offsetof(_cmsg, BProtocol)},
+	/*0e */
+	{_CSTRUCT, offsetof(_cmsg, CalledPartyNumber)},
+	/*0f */
+	{_CSTRUCT, offsetof(_cmsg, CalledPartySubaddress)},
+	/*10 */
+	{_CSTRUCT, offsetof(_cmsg, CallingPartyNumber)},
+	/*11 */
+	{_CSTRUCT, offsetof(_cmsg, CallingPartySubaddress)},
+	/*12 */
+	{_CDWORD, offsetof(_cmsg, CIPmask)},
+	/*13 */
+	{_CDWORD, offsetof(_cmsg, CIPmask2)},
+	/*14 */
+	{_CWORD, offsetof(_cmsg, CIPValue)},
+	/*15 */
+	{_CDWORD, offsetof(_cmsg, Class)},
+	/*16 */
+	{_CSTRUCT, offsetof(_cmsg, ConnectedNumber)},
+	/*17 */
+	{_CSTRUCT, offsetof(_cmsg, ConnectedSubaddress)},
+	/*18 */
+	{_CDWORD, offsetof(_cmsg, Data)},
+	/*19 */
+	{_CWORD, offsetof(_cmsg, DataHandle)},
+	/*1a */
+	{_CWORD, offsetof(_cmsg, DataLength)},
+	/*1b */
+	{_CSTRUCT, offsetof(_cmsg, FacilityConfirmationParameter)},
+	/*1c */
+	{_CSTRUCT, offsetof(_cmsg, Facilitydataarray)},
+	/*1d */
+	{_CSTRUCT, offsetof(_cmsg, FacilityIndicationParameter)},
+	/*1e */
+	{_CSTRUCT, offsetof(_cmsg, FacilityRequestParameter)},
+	/*1f */
+	{_CWORD, offsetof(_cmsg, FacilitySelector)},
+	/*20 */
+	{_CWORD, offsetof(_cmsg, Flags)},
+	/*21 */
+	{_CDWORD, offsetof(_cmsg, Function)},
+	/*22 */
+	{_CSTRUCT, offsetof(_cmsg, HLC)},
+	/*23 */
+	{_CWORD, offsetof(_cmsg, Info)},
+	/*24 */
+	{_CSTRUCT, offsetof(_cmsg, InfoElement)},
+	/*25 */
+	{_CDWORD, offsetof(_cmsg, InfoMask)},
+	/*26 */
+	{_CWORD, offsetof(_cmsg, InfoNumber)},
+	/*27 */
+	{_CSTRUCT, offsetof(_cmsg, Keypadfacility)},
+	/*28 */
+	{_CSTRUCT, offsetof(_cmsg, LLC)},
+	/*29 */
+	{_CSTRUCT, offsetof(_cmsg, ManuData)},
+	/*2a */
+	{_CDWORD, offsetof(_cmsg, ManuID)},
+	/*2b */
+	{_CSTRUCT, offsetof(_cmsg, NCPI)},
+	/*2c */
+	{_CWORD, offsetof(_cmsg, Reason)},
+	/*2d */
+	{_CWORD, offsetof(_cmsg, Reason_B3)},
+	/*2e */
+	{_CWORD, offsetof(_cmsg, Reject)},
+	/*2f */
+	{_CSTRUCT, offsetof(_cmsg, Useruserdata)}
+};
+
+static unsigned char *cpars[] =
+{
+	/* ALERT_REQ */ [0x01] = "\x03\x04\x0c\x27\x2f\x1c\x01\x01",
+	/* CONNECT_REQ */ [0x02] = "\x03\x14\x0e\x10\x0f\x11\x0d\x06\x08\x0a\x05\x07\x09\x01\x0b\x28\x22\x04\x0c\x27\x2f\x1c\x01\x01",
+	/* DISCONNECT_REQ */ [0x04] = "\x03\x04\x0c\x27\x2f\x1c\x01\x01",
+	/* LISTEN_REQ */ [0x05] = "\x03\x25\x12\x13\x10\x11\x01",
+	/* INFO_REQ */ [0x08] = "\x03\x0e\x04\x0c\x27\x2f\x1c\x01\x01",
+	/* FACILITY_REQ */ [0x09] = "\x03\x1f\x1e\x01",
+	/* SELECT_B_PROTOCOL_REQ */ [0x0a] = "\x03\x0d\x06\x08\x0a\x05\x07\x09\x01\x01",
+	/* CONNECT_B3_REQ */ [0x0b] = "\x03\x2b\x01",
+	/* DISCONNECT_B3_REQ */ [0x0d] = "\x03\x2b\x01",
+	/* DATA_B3_REQ */ [0x0f] = "\x03\x18\x1a\x19\x20\x01",
+	/* RESET_B3_REQ */ [0x10] = "\x03\x2b\x01",
+	/* ALERT_CONF */ [0x13] = "\x03\x23\x01",
+	/* CONNECT_CONF */ [0x14] = "\x03\x23\x01",
+	/* DISCONNECT_CONF */ [0x16] = "\x03\x23\x01",
+	/* LISTEN_CONF */ [0x17] = "\x03\x23\x01",
+	/* MANUFACTURER_REQ */ [0x18] = "\x03\x2a\x15\x21\x29\x01",
+	/* INFO_CONF */ [0x1a] = "\x03\x23\x01",
+	/* FACILITY_CONF */ [0x1b] = "\x03\x23\x1f\x1b\x01",
+	/* SELECT_B_PROTOCOL_CONF */ [0x1c] = "\x03\x23\x01",
+	/* CONNECT_B3_CONF */ [0x1d] = "\x03\x23\x01",
+	/* DISCONNECT_B3_CONF */ [0x1f] = "\x03\x23\x01",
+	/* DATA_B3_CONF */ [0x21] = "\x03\x19\x23\x01",
+	/* RESET_B3_CONF */ [0x22] = "\x03\x23\x01",
+	/* CONNECT_IND */ [0x26] = "\x03\x14\x0e\x10\x0f\x11\x0b\x28\x22\x04\x0c\x27\x2f\x1c\x01\x01",
+	/* CONNECT_ACTIVE_IND */ [0x27] = "\x03\x16\x17\x28\x01",
+	/* DISCONNECT_IND */ [0x28] = "\x03\x2c\x01",
+	/* MANUFACTURER_CONF */ [0x2a] = "\x03\x2a\x15\x21\x29\x01",
+	/* INFO_IND */ [0x2c] = "\x03\x26\x24\x01",
+	/* FACILITY_IND */ [0x2d] = "\x03\x1f\x1d\x01",
+	/* CONNECT_B3_IND */ [0x2f] = "\x03\x2b\x01",
+	/* CONNECT_B3_ACTIVE_IND */ [0x30] = "\x03\x2b\x01",
+	/* DISCONNECT_B3_IND */ [0x31] = "\x03\x2d\x2b\x01",
+	/* DATA_B3_IND */ [0x33] = "\x03\x18\x1a\x19\x20\x01",
+	/* RESET_B3_IND */ [0x34] = "\x03\x2b\x01",
+	/* CONNECT_B3_T90_ACTIVE_IND */ [0x35] = "\x03\x2b\x01",
+	/* CONNECT_RESP */ [0x38] = "\x03\x2e\x0d\x06\x08\x0a\x05\x07\x09\x01\x16\x17\x28\x04\x0c\x27\x2f\x1c\x01\x01",
+	/* CONNECT_ACTIVE_RESP */ [0x39] = "\x03\x01",
+	/* DISCONNECT_RESP */ [0x3a] = "\x03\x01",
+	/* MANUFACTURER_IND */ [0x3c] = "\x03\x2a\x15\x21\x29\x01",
+	/* INFO_RESP */ [0x3e] = "\x03\x01",
+	/* FACILITY_RESP */ [0x3f] = "\x03\x1f\x01",
+	/* CONNECT_B3_RESP */ [0x41] = "\x03\x2e\x2b\x01",
+	/* CONNECT_B3_ACTIVE_RESP */ [0x42] = "\x03\x01",
+	/* DISCONNECT_B3_RESP */ [0x43] = "\x03\x01",
+	/* DATA_B3_RESP */ [0x45] = "\x03\x19\x01",
+	/* RESET_B3_RESP */ [0x46] = "\x03\x01",
+	/* CONNECT_B3_T90_ACTIVE_RESP */ [0x47] = "\x03\x01",
+	/* MANUFACTURER_RESP */ [0x4e] = "\x03\x2a\x15\x21\x29\x01",
+};
+
+/*-------------------------------------------------------*/
+
+#define byteTLcpy(x, y)         *(u8 *)(x) = *(u8 *)(y);
+#define wordTLcpy(x, y)         *(u16 *)(x) = *(u16 *)(y);
+#define dwordTLcpy(x, y)        memcpy(x, y, 4);
+#define structTLcpy(x, y, l)    memcpy(x, y, l)
+#define structTLcpyovl(x, y, l) memmove(x, y, l)
+
+#define byteTRcpy(x, y)         *(u8 *)(y) = *(u8 *)(x);
+#define wordTRcpy(x, y)         *(u16 *)(y) = *(u16 *)(x);
+#define dwordTRcpy(x, y)        memcpy(y, x, 4);
+#define structTRcpy(x, y, l)    memcpy(y, x, l)
+#define structTRcpyovl(x, y, l) memmove(y, x, l)
+
+/*-------------------------------------------------------*/
+static unsigned command_2_index(u8 c, u8 sc)
+{
+	if (c & 0x80)
+		c = 0x9 + (c & 0x0f);
+	else if (c == 0x41)
+		c = 0x9 + 0x1;
+	if (c > 0x18)
+		c = 0x00;
+	return (sc & 3) * (0x9 + 0x9) + c;
+}
+
+/**
+ * capi_cmd2par() - find parameter string for CAPI 2.0 command/subcommand
+ * @cmd:	command number
+ * @subcmd:	subcommand number
+ *
+ * Return value: static string, NULL if command/subcommand unknown
+ */
+
+static unsigned char *capi_cmd2par(u8 cmd, u8 subcmd)
+{
+	return cpars[command_2_index(cmd, subcmd)];
+}
+
+/*-------------------------------------------------------*/
+#define TYP (cdef[cmsg->par[cmsg->p]].typ)
+#define OFF (((u8 *)cmsg) + cdef[cmsg->par[cmsg->p]].off)
+
+static void jumpcstruct(_cmsg *cmsg)
+{
+	unsigned layer;
+	for (cmsg->p++, layer = 1; layer;) {
+		/* $$$$$ assert (cmsg->p); */
+		cmsg->p++;
+		switch (TYP) {
+		case _CMSTRUCT:
+			layer++;
+			break;
+		case _CEND:
+			layer--;
+			break;
+		}
+	}
+}
+/*-------------------------------------------------------*/
+static void pars_2_message(_cmsg *cmsg)
+{
+
+	for (; TYP != _CEND; cmsg->p++) {
+		switch (TYP) {
+		case _CBYTE:
+			byteTLcpy(cmsg->m + cmsg->l, OFF);
+			cmsg->l++;
+			break;
+		case _CWORD:
+			wordTLcpy(cmsg->m + cmsg->l, OFF);
+			cmsg->l += 2;
+			break;
+		case _CDWORD:
+			dwordTLcpy(cmsg->m + cmsg->l, OFF);
+			cmsg->l += 4;
+			break;
+		case _CSTRUCT:
+			if (*(u8 **) OFF == NULL) {
+				*(cmsg->m + cmsg->l) = '\0';
+				cmsg->l++;
+			} else if (**(_cstruct *) OFF != 0xff) {
+				structTLcpy(cmsg->m + cmsg->l, *(_cstruct *) OFF, 1 + **(_cstruct *) OFF);
+				cmsg->l += 1 + **(_cstruct *) OFF;
+			} else {
+				_cstruct s = *(_cstruct *) OFF;
+				structTLcpy(cmsg->m + cmsg->l, s, 3 + *(u16 *) (s + 1));
+				cmsg->l += 3 + *(u16 *) (s + 1);
+			}
+			break;
+		case _CMSTRUCT:
+/*----- Metastruktur 0 -----*/
+			if (*(_cmstruct *) OFF == CAPI_DEFAULT) {
+				*(cmsg->m + cmsg->l) = '\0';
+				cmsg->l++;
+				jumpcstruct(cmsg);
+			}
+/*----- Metastruktur wird composed -----*/
+			else {
+				unsigned _l = cmsg->l;
+				unsigned _ls;
+				cmsg->l++;
+				cmsg->p++;
+				pars_2_message(cmsg);
+				_ls = cmsg->l - _l - 1;
+				if (_ls < 255)
+					(cmsg->m + _l)[0] = (u8) _ls;
+				else {
+					structTLcpyovl(cmsg->m + _l + 3, cmsg->m + _l + 1, _ls);
+					(cmsg->m + _l)[0] = 0xff;
+					wordTLcpy(cmsg->m + _l + 1, &_ls);
+				}
+			}
+			break;
+		}
+	}
+}
+
+/**
+ * capi_cmsg2message() - assemble CAPI 2.0 message from _cmsg structure
+ * @cmsg:	_cmsg structure
+ * @msg:	buffer for assembled message
+ *
+ * Return value: 0 for success
+ */
+
+unsigned capi_cmsg2message(_cmsg *cmsg, u8 *msg)
+{
+	cmsg->m = msg;
+	cmsg->l = 8;
+	cmsg->p = 0;
+	cmsg->par = capi_cmd2par(cmsg->Command, cmsg->Subcommand);
+	if (!cmsg->par)
+		return 1;	/* invalid command/subcommand */
+
+	pars_2_message(cmsg);
+
+	wordTLcpy(msg + 0, &cmsg->l);
+	byteTLcpy(cmsg->m + 4, &cmsg->Command);
+	byteTLcpy(cmsg->m + 5, &cmsg->Subcommand);
+	wordTLcpy(cmsg->m + 2, &cmsg->ApplId);
+	wordTLcpy(cmsg->m + 6, &cmsg->Messagenumber);
+
+	return 0;
+}
+
+/*-------------------------------------------------------*/
+static void message_2_pars(_cmsg *cmsg)
+{
+	for (; TYP != _CEND; cmsg->p++) {
+
+		switch (TYP) {
+		case _CBYTE:
+			byteTRcpy(cmsg->m + cmsg->l, OFF);
+			cmsg->l++;
+			break;
+		case _CWORD:
+			wordTRcpy(cmsg->m + cmsg->l, OFF);
+			cmsg->l += 2;
+			break;
+		case _CDWORD:
+			dwordTRcpy(cmsg->m + cmsg->l, OFF);
+			cmsg->l += 4;
+			break;
+		case _CSTRUCT:
+			*(u8 **) OFF = cmsg->m + cmsg->l;
+
+			if (cmsg->m[cmsg->l] != 0xff)
+				cmsg->l += 1 + cmsg->m[cmsg->l];
+			else
+				cmsg->l += 3 + *(u16 *) (cmsg->m + cmsg->l + 1);
+			break;
+		case _CMSTRUCT:
+/*----- Metastruktur 0 -----*/
+			if (cmsg->m[cmsg->l] == '\0') {
+				*(_cmstruct *) OFF = CAPI_DEFAULT;
+				cmsg->l++;
+				jumpcstruct(cmsg);
+			} else {
+				unsigned _l = cmsg->l;
+				*(_cmstruct *) OFF = CAPI_COMPOSE;
+				cmsg->l = (cmsg->m + _l)[0] == 255 ? cmsg->l + 3 : cmsg->l + 1;
+				cmsg->p++;
+				message_2_pars(cmsg);
+			}
+			break;
+		}
+	}
+}
+
+/**
+ * capi_message2cmsg() - disassemble CAPI 2.0 message into _cmsg structure
+ * @cmsg:	_cmsg structure
+ * @msg:	buffer for assembled message
+ *
+ * Return value: 0 for success
+ */
+
+unsigned capi_message2cmsg(_cmsg *cmsg, u8 *msg)
+{
+	memset(cmsg, 0, sizeof(_cmsg));
+	cmsg->m = msg;
+	cmsg->l = 8;
+	cmsg->p = 0;
+	byteTRcpy(cmsg->m + 4, &cmsg->Command);
+	byteTRcpy(cmsg->m + 5, &cmsg->Subcommand);
+	cmsg->par = capi_cmd2par(cmsg->Command, cmsg->Subcommand);
+	if (!cmsg->par)
+		return 1;	/* invalid command/subcommand */
+
+	message_2_pars(cmsg);
+
+	wordTRcpy(msg + 0, &cmsg->l);
+	wordTRcpy(cmsg->m + 2, &cmsg->ApplId);
+	wordTRcpy(cmsg->m + 6, &cmsg->Messagenumber);
+
+	return 0;
+}
+
+/**
+ * capi_cmsg_header() - initialize header part of _cmsg structure
+ * @cmsg:	_cmsg structure
+ * @_ApplId:	ApplID field value
+ * @_Command:	Command field value
+ * @_Subcommand:	Subcommand field value
+ * @_Messagenumber:	Message Number field value
+ * @_Controller:	Controller/PLCI/NCCI field value
+ *
+ * Return value: 0 for success
+ */
+
+unsigned capi_cmsg_header(_cmsg *cmsg, u16 _ApplId,
+			  u8 _Command, u8 _Subcommand,
+			  u16 _Messagenumber, u32 _Controller)
+{
+	memset(cmsg, 0, sizeof(_cmsg));
+	cmsg->ApplId = _ApplId;
+	cmsg->Command = _Command;
+	cmsg->Subcommand = _Subcommand;
+	cmsg->Messagenumber = _Messagenumber;
+	cmsg->adr.adrController = _Controller;
+	return 0;
+}
+
+/*-------------------------------------------------------*/
+
+static char *mnames[] =
+{
+	[0x01] = "ALERT_REQ",
+	[0x02] = "CONNECT_REQ",
+	[0x04] = "DISCONNECT_REQ",
+	[0x05] = "LISTEN_REQ",
+	[0x08] = "INFO_REQ",
+	[0x09] = "FACILITY_REQ",
+	[0x0a] = "SELECT_B_PROTOCOL_REQ",
+	[0x0b] = "CONNECT_B3_REQ",
+	[0x0d] = "DISCONNECT_B3_REQ",
+	[0x0f] = "DATA_B3_REQ",
+	[0x10] = "RESET_B3_REQ",
+	[0x13] = "ALERT_CONF",
+	[0x14] = "CONNECT_CONF",
+	[0x16] = "DISCONNECT_CONF",
+	[0x17] = "LISTEN_CONF",
+	[0x18] = "MANUFACTURER_REQ",
+	[0x1a] = "INFO_CONF",
+	[0x1b] = "FACILITY_CONF",
+	[0x1c] = "SELECT_B_PROTOCOL_CONF",
+	[0x1d] = "CONNECT_B3_CONF",
+	[0x1f] = "DISCONNECT_B3_CONF",
+	[0x21] = "DATA_B3_CONF",
+	[0x22] = "RESET_B3_CONF",
+	[0x26] = "CONNECT_IND",
+	[0x27] = "CONNECT_ACTIVE_IND",
+	[0x28] = "DISCONNECT_IND",
+	[0x2a] = "MANUFACTURER_CONF",
+	[0x2c] = "INFO_IND",
+	[0x2d] = "FACILITY_IND",
+	[0x2f] = "CONNECT_B3_IND",
+	[0x30] = "CONNECT_B3_ACTIVE_IND",
+	[0x31] = "DISCONNECT_B3_IND",
+	[0x33] = "DATA_B3_IND",
+	[0x34] = "RESET_B3_IND",
+	[0x35] = "CONNECT_B3_T90_ACTIVE_IND",
+	[0x38] = "CONNECT_RESP",
+	[0x39] = "CONNECT_ACTIVE_RESP",
+	[0x3a] = "DISCONNECT_RESP",
+	[0x3c] = "MANUFACTURER_IND",
+	[0x3e] = "INFO_RESP",
+	[0x3f] = "FACILITY_RESP",
+	[0x41] = "CONNECT_B3_RESP",
+	[0x42] = "CONNECT_B3_ACTIVE_RESP",
+	[0x43] = "DISCONNECT_B3_RESP",
+	[0x45] = "DATA_B3_RESP",
+	[0x46] = "RESET_B3_RESP",
+	[0x47] = "CONNECT_B3_T90_ACTIVE_RESP",
+	[0x4e] = "MANUFACTURER_RESP"
+};
+
+/**
+ * capi_cmd2str() - convert CAPI 2.0 command/subcommand number to name
+ * @cmd:	command number
+ * @subcmd:	subcommand number
+ *
+ * Return value: static string
+ */
+
+char *capi_cmd2str(u8 cmd, u8 subcmd)
+{
+	char *result;
+
+	result = mnames[command_2_index(cmd, subcmd)];
+	if (result == NULL)
+		result = "INVALID_COMMAND";
+	return result;
+}
+
+
+/*-------------------------------------------------------*/
+
+#ifdef CONFIG_CAPI_TRACE
+
+/*-------------------------------------------------------*/
+
+static char *pnames[] =
+{
+	/*00 */ NULL,
+	/*01 */ NULL,
+	/*02 */ NULL,
+	/*03 */ "Controller/PLCI/NCCI",
+	/*04 */ "AdditionalInfo",
+	/*05 */ "B1configuration",
+	/*06 */ "B1protocol",
+	/*07 */ "B2configuration",
+	/*08 */ "B2protocol",
+	/*09 */ "B3configuration",
+	/*0a */ "B3protocol",
+	/*0b */ "BC",
+	/*0c */ "BChannelinformation",
+	/*0d */ "BProtocol",
+	/*0e */ "CalledPartyNumber",
+	/*0f */ "CalledPartySubaddress",
+	/*10 */ "CallingPartyNumber",
+	/*11 */ "CallingPartySubaddress",
+	/*12 */ "CIPmask",
+	/*13 */ "CIPmask2",
+	/*14 */ "CIPValue",
+	/*15 */ "Class",
+	/*16 */ "ConnectedNumber",
+	/*17 */ "ConnectedSubaddress",
+	/*18 */ "Data32",
+	/*19 */ "DataHandle",
+	/*1a */ "DataLength",
+	/*1b */ "FacilityConfirmationParameter",
+	/*1c */ "Facilitydataarray",
+	/*1d */ "FacilityIndicationParameter",
+	/*1e */ "FacilityRequestParameter",
+	/*1f */ "FacilitySelector",
+	/*20 */ "Flags",
+	/*21 */ "Function",
+	/*22 */ "HLC",
+	/*23 */ "Info",
+	/*24 */ "InfoElement",
+	/*25 */ "InfoMask",
+	/*26 */ "InfoNumber",
+	/*27 */ "Keypadfacility",
+	/*28 */ "LLC",
+	/*29 */ "ManuData",
+	/*2a */ "ManuID",
+	/*2b */ "NCPI",
+	/*2c */ "Reason",
+	/*2d */ "Reason_B3",
+	/*2e */ "Reject",
+	/*2f */ "Useruserdata"
+};
+
+
+
+#include <stdarg.h>
+
+/*-------------------------------------------------------*/
+static _cdebbuf *bufprint(_cdebbuf *cdb, char *fmt, ...)
+{
+	va_list f;
+	size_t n, r;
+
+	if (!cdb)
+		return NULL;
+	va_start(f, fmt);
+	r = cdb->size - cdb->pos;
+	n = vsnprintf(cdb->p, r, fmt, f);
+	va_end(f);
+	if (n >= r) {
+		/* truncated, need bigger buffer */
+		size_t ns = 2 * cdb->size;
+		u_char *nb;
+
+		while ((ns - cdb->pos) <= n)
+			ns *= 2;
+		nb = kmalloc(ns, GFP_ATOMIC);
+		if (!nb) {
+			cdebbuf_free(cdb);
+			return NULL;
+		}
+		memcpy(nb, cdb->buf, cdb->pos);
+		kfree(cdb->buf);
+		nb[cdb->pos] = 0;
+		cdb->buf = nb;
+		cdb->p = cdb->buf + cdb->pos;
+		cdb->size = ns;
+		va_start(f, fmt);
+		r = cdb->size - cdb->pos;
+		n = vsnprintf(cdb->p, r, fmt, f);
+		va_end(f);
+	}
+	cdb->p += n;
+	cdb->pos += n;
+	return cdb;
+}
+
+static _cdebbuf *printstructlen(_cdebbuf *cdb, u8 *m, unsigned len)
+{
+	unsigned hex = 0;
+
+	if (!cdb)
+		return NULL;
+	for (; len; len--, m++)
+		if (isalnum(*m) || *m == ' ') {
+			if (hex)
+				cdb = bufprint(cdb, ">");
+			cdb = bufprint(cdb, "%c", *m);
+			hex = 0;
+		} else {
+			if (!hex)
+				cdb = bufprint(cdb, "<%02x", *m);
+			else
+				cdb = bufprint(cdb, " %02x", *m);
+			hex = 1;
+		}
+	if (hex)
+		cdb = bufprint(cdb, ">");
+	return cdb;
+}
+
+static _cdebbuf *printstruct(_cdebbuf *cdb, u8 *m)
+{
+	unsigned len;
+
+	if (m[0] != 0xff) {
+		len = m[0];
+		m += 1;
+	} else {
+		len = ((u16 *) (m + 1))[0];
+		m += 3;
+	}
+	cdb = printstructlen(cdb, m, len);
+	return cdb;
+}
+
+/*-------------------------------------------------------*/
+#define NAME (pnames[cmsg->par[cmsg->p]])
+
+static _cdebbuf *protocol_message_2_pars(_cdebbuf *cdb, _cmsg *cmsg, int level)
+{
+	if (!cmsg->par)
+		return NULL;	/* invalid command/subcommand */
+
+	for (; TYP != _CEND; cmsg->p++) {
+		int slen = 29 + 3 - level;
+		int i;
+
+		if (!cdb)
+			return NULL;
+		cdb = bufprint(cdb, "  ");
+		for (i = 0; i < level - 1; i++)
+			cdb = bufprint(cdb, " ");
+
+		switch (TYP) {
+		case _CBYTE:
+			cdb = bufprint(cdb, "%-*s = 0x%x\n", slen, NAME, *(u8 *) (cmsg->m + cmsg->l));
+			cmsg->l++;
+			break;
+		case _CWORD:
+			cdb = bufprint(cdb, "%-*s = 0x%x\n", slen, NAME, *(u16 *) (cmsg->m + cmsg->l));
+			cmsg->l += 2;
+			break;
+		case _CDWORD:
+			cdb = bufprint(cdb, "%-*s = 0x%lx\n", slen, NAME, *(u32 *) (cmsg->m + cmsg->l));
+			cmsg->l += 4;
+			break;
+		case _CSTRUCT:
+			cdb = bufprint(cdb, "%-*s = ", slen, NAME);
+			if (cmsg->m[cmsg->l] == '\0')
+				cdb = bufprint(cdb, "default");
+			else
+				cdb = printstruct(cdb, cmsg->m + cmsg->l);
+			cdb = bufprint(cdb, "\n");
+			if (cmsg->m[cmsg->l] != 0xff)
+				cmsg->l += 1 + cmsg->m[cmsg->l];
+			else
+				cmsg->l += 3 + *(u16 *) (cmsg->m + cmsg->l + 1);
+
+			break;
+
+		case _CMSTRUCT:
+/*----- Metastruktur 0 -----*/
+			if (cmsg->m[cmsg->l] == '\0') {
+				cdb = bufprint(cdb, "%-*s = default\n", slen, NAME);
+				cmsg->l++;
+				jumpcstruct(cmsg);
+			} else {
+				char *name = NAME;
+				unsigned _l = cmsg->l;
+				cdb = bufprint(cdb, "%-*s\n", slen, name);
+				cmsg->l = (cmsg->m + _l)[0] == 255 ? cmsg->l + 3 : cmsg->l + 1;
+				cmsg->p++;
+				cdb = protocol_message_2_pars(cdb, cmsg, level + 1);
+			}
+			break;
+		}
+	}
+	return cdb;
+}
+/*-------------------------------------------------------*/
+
+static _cdebbuf *g_debbuf;
+static u_long g_debbuf_lock;
+static _cmsg *g_cmsg;
+
+static _cdebbuf *cdebbuf_alloc(void)
+{
+	_cdebbuf *cdb;
+
+	if (likely(!test_and_set_bit(1, &g_debbuf_lock))) {
+		cdb = g_debbuf;
+		goto init;
+	} else
+		cdb = kmalloc(sizeof(_cdebbuf), GFP_ATOMIC);
+	if (!cdb)
+		return NULL;
+	cdb->buf = kmalloc(CDEBUG_SIZE, GFP_ATOMIC);
+	if (!cdb->buf) {
+		kfree(cdb);
+		return NULL;
+	}
+	cdb->size = CDEBUG_SIZE;
+init:
+	cdb->buf[0] = 0;
+	cdb->p = cdb->buf;
+	cdb->pos = 0;
+	return cdb;
+}
+
+/**
+ * cdebbuf_free() - free CAPI debug buffer
+ * @cdb:	buffer to free
+ */
+
+void cdebbuf_free(_cdebbuf *cdb)
+{
+	if (likely(cdb == g_debbuf)) {
+		test_and_clear_bit(1, &g_debbuf_lock);
+		return;
+	}
+	if (likely(cdb))
+		kfree(cdb->buf);
+	kfree(cdb);
+}
+
+
+/**
+ * capi_message2str() - format CAPI 2.0 message for printing
+ * @msg:	CAPI 2.0 message
+ *
+ * Allocates a CAPI debug buffer and fills it with a printable representation
+ * of the CAPI 2.0 message in @msg.
+ * Return value: allocated debug buffer, NULL on error
+ * The returned buffer should be freed by a call to cdebbuf_free() after use.
+ */
+
+_cdebbuf *capi_message2str(u8 *msg)
+{
+	_cdebbuf *cdb;
+	_cmsg	*cmsg;
+
+	cdb = cdebbuf_alloc();
+	if (unlikely(!cdb))
+		return NULL;
+	if (likely(cdb == g_debbuf))
+		cmsg = g_cmsg;
+	else
+		cmsg = kmalloc(sizeof(_cmsg), GFP_ATOMIC);
+	if (unlikely(!cmsg)) {
+		cdebbuf_free(cdb);
+		return NULL;
+	}
+	cmsg->m = msg;
+	cmsg->l = 8;
+	cmsg->p = 0;
+	byteTRcpy(cmsg->m + 4, &cmsg->Command);
+	byteTRcpy(cmsg->m + 5, &cmsg->Subcommand);
+	cmsg->par = capi_cmd2par(cmsg->Command, cmsg->Subcommand);
+
+	cdb = bufprint(cdb, "%-26s ID=%03d #0x%04x LEN=%04d\n",
+		       capi_cmd2str(cmsg->Command, cmsg->Subcommand),
+		       ((unsigned short *) msg)[1],
+		       ((unsigned short *) msg)[3],
+		       ((unsigned short *) msg)[0]);
+
+	cdb = protocol_message_2_pars(cdb, cmsg, 1);
+	if (unlikely(cmsg != g_cmsg))
+		kfree(cmsg);
+	return cdb;
+}
+
+/**
+ * capi_cmsg2str() - format _cmsg structure for printing
+ * @cmsg:	_cmsg structure
+ *
+ * Allocates a CAPI debug buffer and fills it with a printable representation
+ * of the CAPI 2.0 message stored in @cmsg by a previous call to
+ * capi_cmsg2message() or capi_message2cmsg().
+ * Return value: allocated debug buffer, NULL on error
+ * The returned buffer should be freed by a call to cdebbuf_free() after use.
+ */
+
+_cdebbuf *capi_cmsg2str(_cmsg *cmsg)
+{
+	_cdebbuf *cdb;
+
+	if (!cmsg->m)
+		return NULL;	/* no message */
+	cdb = cdebbuf_alloc();
+	if (!cdb)
+		return NULL;
+	cmsg->l = 8;
+	cmsg->p = 0;
+	cdb = bufprint(cdb, "%s ID=%03d #0x%04x LEN=%04d\n",
+		       capi_cmd2str(cmsg->Command, cmsg->Subcommand),
+		       ((u16 *) cmsg->m)[1],
+		       ((u16 *) cmsg->m)[3],
+		       ((u16 *) cmsg->m)[0]);
+	cdb = protocol_message_2_pars(cdb, cmsg, 1);
+	return cdb;
+}
+
+int __init cdebug_init(void)
+{
+	g_cmsg = kmalloc(sizeof(_cmsg), GFP_KERNEL);
+	if (!g_cmsg)
+		return -ENOMEM;
+	g_debbuf = kmalloc(sizeof(_cdebbuf), GFP_KERNEL);
+	if (!g_debbuf) {
+		kfree(g_cmsg);
+		return -ENOMEM;
+	}
+	g_debbuf->buf = kmalloc(CDEBUG_GSIZE, GFP_KERNEL);
+	if (!g_debbuf->buf) {
+		kfree(g_cmsg);
+		kfree(g_debbuf);
+		return -ENOMEM;
+	}
+	g_debbuf->size = CDEBUG_GSIZE;
+	g_debbuf->buf[0] = 0;
+	g_debbuf->p = g_debbuf->buf;
+	g_debbuf->pos = 0;
+	return 0;
+}
+
+void __exit cdebug_exit(void)
+{
+	if (g_debbuf)
+		kfree(g_debbuf->buf);
+	kfree(g_debbuf);
+	kfree(g_cmsg);
+}
+
+#else /* !CONFIG_CAPI_TRACE */
+
+static _cdebbuf g_debbuf = {"CONFIG_CAPI_TRACE not enabled", NULL, 0, 0};
+
+_cdebbuf *capi_message2str(u8 *msg)
+{
+	return &g_debbuf;
+}
+
+_cdebbuf *capi_cmsg2str(_cmsg *cmsg)
+{
+	return &g_debbuf;
+}
+
+void cdebbuf_free(_cdebbuf *cdb)
+{
+}
+
+int __init cdebug_init(void)
+{
+	return 0;
+}
+
+void __exit cdebug_exit(void)
+{
+}
+
+#endif
+
+EXPORT_SYMBOL(cdebbuf_free);
+EXPORT_SYMBOL(capi_cmsg2message);
+EXPORT_SYMBOL(capi_message2cmsg);
+EXPORT_SYMBOL(capi_cmsg_header);
+EXPORT_SYMBOL(capi_cmd2str);
+EXPORT_SYMBOL(capi_cmsg2str);
+EXPORT_SYMBOL(capi_message2str);
diff --git a/src/kernel/linux/v4.14/drivers/isdn/capi/kcapi.c b/src/kernel/linux/v4.14/drivers/isdn/capi/kcapi.c
new file mode 100644
index 0000000..28a1c6b
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/isdn/capi/kcapi.c
@@ -0,0 +1,1317 @@
+/* $Id: kcapi.c,v 1.1.2.8 2004/03/26 19:57:20 armin Exp $
+ *
+ * Kernel CAPI 2.0 Module
+ *
+ * Copyright 1999 by Carsten Paeth <calle@calle.de>
+ * Copyright 2002 by Kai Germaschewski <kai@germaschewski.name>
+ *
+ * This software may be used and distributed according to the terms
+ * of the GNU General Public License, incorporated herein by reference.
+ *
+ */
+
+#define AVMB1_COMPAT
+
+#include "kcapi.h"
+#include <linux/module.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <linux/proc_fs.h>
+#include <linux/sched/signal.h>
+#include <linux/seq_file.h>
+#include <linux/skbuff.h>
+#include <linux/workqueue.h>
+#include <linux/capi.h>
+#include <linux/kernelcapi.h>
+#include <linux/init.h>
+#include <linux/moduleparam.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/isdn/capicmd.h>
+#include <linux/isdn/capiutil.h>
+#ifdef AVMB1_COMPAT
+#include <linux/b1lli.h>
+#endif
+#include <linux/mutex.h>
+#include <linux/rcupdate.h>
+
+static int showcapimsgs = 0;
+static struct workqueue_struct *kcapi_wq;
+
+MODULE_DESCRIPTION("CAPI4Linux: kernel CAPI layer");
+MODULE_AUTHOR("Carsten Paeth");
+MODULE_LICENSE("GPL");
+module_param(showcapimsgs, uint, 0);
+
+/* ------------------------------------------------------------- */
+
+struct capictr_event {
+	struct work_struct work;
+	unsigned int type;
+	u32 controller;
+};
+
+/* ------------------------------------------------------------- */
+
+static const struct capi_version driver_version = {2, 0, 1, 1 << 4};
+static char driver_serial[CAPI_SERIAL_LEN] = "0004711";
+static char capi_manufakturer[64] = "AVM Berlin";
+
+#define NCCI2CTRL(ncci)    (((ncci) >> 24) & 0x7f)
+
+LIST_HEAD(capi_drivers);
+DEFINE_MUTEX(capi_drivers_lock);
+
+struct capi_ctr *capi_controller[CAPI_MAXCONTR];
+DEFINE_MUTEX(capi_controller_lock);
+
+struct capi20_appl *capi_applications[CAPI_MAXAPPL];
+
+static int ncontrollers;
+
+static BLOCKING_NOTIFIER_HEAD(ctr_notifier_list);
+
+/* -------- controller ref counting -------------------------------------- */
+
+static inline struct capi_ctr *
+capi_ctr_get(struct capi_ctr *ctr)
+{
+	if (!try_module_get(ctr->owner))
+		return NULL;
+	return ctr;
+}
+
+static inline void
+capi_ctr_put(struct capi_ctr *ctr)
+{
+	module_put(ctr->owner);
+}
+
+/* ------------------------------------------------------------- */
+
+static inline struct capi_ctr *get_capi_ctr_by_nr(u16 contr)
+{
+	if (contr < 1 || contr - 1 >= CAPI_MAXCONTR)
+		return NULL;
+
+	return capi_controller[contr - 1];
+}
+
+static inline struct capi20_appl *__get_capi_appl_by_nr(u16 applid)
+{
+	lockdep_assert_held(&capi_controller_lock);
+
+	if (applid < 1 || applid - 1 >= CAPI_MAXAPPL)
+		return NULL;
+
+	return capi_applications[applid - 1];
+}
+
+static inline struct capi20_appl *get_capi_appl_by_nr(u16 applid)
+{
+	if (applid < 1 || applid - 1 >= CAPI_MAXAPPL)
+		return NULL;
+
+	return rcu_dereference(capi_applications[applid - 1]);
+}
+
+/* -------- util functions ------------------------------------ */
+
+static inline int capi_cmd_valid(u8 cmd)
+{
+	switch (cmd) {
+	case CAPI_ALERT:
+	case CAPI_CONNECT:
+	case CAPI_CONNECT_ACTIVE:
+	case CAPI_CONNECT_B3_ACTIVE:
+	case CAPI_CONNECT_B3:
+	case CAPI_CONNECT_B3_T90_ACTIVE:
+	case CAPI_DATA_B3:
+	case CAPI_DISCONNECT_B3:
+	case CAPI_DISCONNECT:
+	case CAPI_FACILITY:
+	case CAPI_INFO:
+	case CAPI_LISTEN:
+	case CAPI_MANUFACTURER:
+	case CAPI_RESET_B3:
+	case CAPI_SELECT_B_PROTOCOL:
+		return 1;
+	}
+	return 0;
+}
+
+static inline int capi_subcmd_valid(u8 subcmd)
+{
+	switch (subcmd) {
+	case CAPI_REQ:
+	case CAPI_CONF:
+	case CAPI_IND:
+	case CAPI_RESP:
+		return 1;
+	}
+	return 0;
+}
+
+/* ------------------------------------------------------------ */
+
+static void
+register_appl(struct capi_ctr *ctr, u16 applid, capi_register_params *rparam)
+{
+	ctr = capi_ctr_get(ctr);
+
+	if (ctr)
+		ctr->register_appl(ctr, applid, rparam);
+	else
+		printk(KERN_WARNING "%s: cannot get controller resources\n",
+		       __func__);
+}
+
+
+static void release_appl(struct capi_ctr *ctr, u16 applid)
+{
+	DBG("applid %#x", applid);
+
+	ctr->release_appl(ctr, applid);
+	capi_ctr_put(ctr);
+}
+
+static void notify_up(u32 contr)
+{
+	struct capi20_appl *ap;
+	struct capi_ctr *ctr;
+	u16 applid;
+
+	mutex_lock(&capi_controller_lock);
+
+	if (showcapimsgs & 1)
+		printk(KERN_DEBUG "kcapi: notify up contr %d\n", contr);
+
+	ctr = get_capi_ctr_by_nr(contr);
+	if (ctr) {
+		if (ctr->state == CAPI_CTR_RUNNING)
+			goto unlock_out;
+
+		ctr->state = CAPI_CTR_RUNNING;
+
+		for (applid = 1; applid <= CAPI_MAXAPPL; applid++) {
+			ap = __get_capi_appl_by_nr(applid);
+			if (ap)
+				register_appl(ctr, applid, &ap->rparam);
+		}
+
+		wake_up_interruptible_all(&ctr->state_wait_queue);
+	} else
+		printk(KERN_WARNING "%s: invalid contr %d\n", __func__, contr);
+
+unlock_out:
+	mutex_unlock(&capi_controller_lock);
+}
+
+static void ctr_down(struct capi_ctr *ctr, int new_state)
+{
+	struct capi20_appl *ap;
+	u16 applid;
+
+	if (ctr->state == CAPI_CTR_DETECTED || ctr->state == CAPI_CTR_DETACHED)
+		return;
+
+	ctr->state = new_state;
+
+	memset(ctr->manu, 0, sizeof(ctr->manu));
+	memset(&ctr->version, 0, sizeof(ctr->version));
+	memset(&ctr->profile, 0, sizeof(ctr->profile));
+	memset(ctr->serial, 0, sizeof(ctr->serial));
+
+	for (applid = 1; applid <= CAPI_MAXAPPL; applid++) {
+		ap = __get_capi_appl_by_nr(applid);
+		if (ap)
+			capi_ctr_put(ctr);
+	}
+
+	wake_up_interruptible_all(&ctr->state_wait_queue);
+}
+
+static void notify_down(u32 contr)
+{
+	struct capi_ctr *ctr;
+
+	mutex_lock(&capi_controller_lock);
+
+	if (showcapimsgs & 1)
+		printk(KERN_DEBUG "kcapi: notify down contr %d\n", contr);
+
+	ctr = get_capi_ctr_by_nr(contr);
+	if (ctr)
+		ctr_down(ctr, CAPI_CTR_DETECTED);
+	else
+		printk(KERN_WARNING "%s: invalid contr %d\n", __func__, contr);
+
+	mutex_unlock(&capi_controller_lock);
+}
+
+static int
+notify_handler(struct notifier_block *nb, unsigned long val, void *v)
+{
+	u32 contr = (long)v;
+
+	switch (val) {
+	case CAPICTR_UP:
+		notify_up(contr);
+		break;
+	case CAPICTR_DOWN:
+		notify_down(contr);
+		break;
+	}
+	return NOTIFY_OK;
+}
+
+static void do_notify_work(struct work_struct *work)
+{
+	struct capictr_event *event =
+		container_of(work, struct capictr_event, work);
+
+	blocking_notifier_call_chain(&ctr_notifier_list, event->type,
+				     (void *)(long)event->controller);
+	kfree(event);
+}
+
+/*
+ * The notifier will result in adding/deleteing of devices. Devices can
+ * only removed in user process, not in bh.
+ */
+static int notify_push(unsigned int event_type, u32 controller)
+{
+	struct capictr_event *event = kmalloc(sizeof(*event), GFP_ATOMIC);
+
+	if (!event)
+		return -ENOMEM;
+
+	INIT_WORK(&event->work, do_notify_work);
+	event->type = event_type;
+	event->controller = controller;
+
+	queue_work(kcapi_wq, &event->work);
+	return 0;
+}
+
+int register_capictr_notifier(struct notifier_block *nb)
+{
+	return blocking_notifier_chain_register(&ctr_notifier_list, nb);
+}
+EXPORT_SYMBOL_GPL(register_capictr_notifier);
+
+int unregister_capictr_notifier(struct notifier_block *nb)
+{
+	return blocking_notifier_chain_unregister(&ctr_notifier_list, nb);
+}
+EXPORT_SYMBOL_GPL(unregister_capictr_notifier);
+
+/* -------- Receiver ------------------------------------------ */
+
+static void recv_handler(struct work_struct *work)
+{
+	struct sk_buff *skb;
+	struct capi20_appl *ap =
+		container_of(work, struct capi20_appl, recv_work);
+
+	if ((!ap) || (ap->release_in_progress))
+		return;
+
+	mutex_lock(&ap->recv_mtx);
+	while ((skb = skb_dequeue(&ap->recv_queue))) {
+		if (CAPIMSG_CMD(skb->data) == CAPI_DATA_B3_IND)
+			ap->nrecvdatapkt++;
+		else
+			ap->nrecvctlpkt++;
+
+		ap->recv_message(ap, skb);
+	}
+	mutex_unlock(&ap->recv_mtx);
+}
+
+/**
+ * capi_ctr_handle_message() - handle incoming CAPI message
+ * @ctr:	controller descriptor structure.
+ * @appl:	application ID.
+ * @skb:	message.
+ *
+ * Called by hardware driver to pass a CAPI message to the application.
+ */
+
+void capi_ctr_handle_message(struct capi_ctr *ctr, u16 appl,
+			     struct sk_buff *skb)
+{
+	struct capi20_appl *ap;
+	int showctl = 0;
+	u8 cmd, subcmd;
+	_cdebbuf *cdb;
+
+	if (ctr->state != CAPI_CTR_RUNNING) {
+		cdb = capi_message2str(skb->data);
+		if (cdb) {
+			printk(KERN_INFO "kcapi: controller [%03d] not active, got: %s",
+			       ctr->cnr, cdb->buf);
+			cdebbuf_free(cdb);
+		} else
+			printk(KERN_INFO "kcapi: controller [%03d] not active, cannot trace\n",
+			       ctr->cnr);
+		goto error;
+	}
+
+	cmd = CAPIMSG_COMMAND(skb->data);
+	subcmd = CAPIMSG_SUBCOMMAND(skb->data);
+	if (cmd == CAPI_DATA_B3 && subcmd == CAPI_IND) {
+		ctr->nrecvdatapkt++;
+		if (ctr->traceflag > 2)
+			showctl |= 2;
+	} else {
+		ctr->nrecvctlpkt++;
+		if (ctr->traceflag)
+			showctl |= 2;
+	}
+	showctl |= (ctr->traceflag & 1);
+	if (showctl & 2) {
+		if (showctl & 1) {
+			printk(KERN_DEBUG "kcapi: got [%03d] id#%d %s len=%u\n",
+			       ctr->cnr, CAPIMSG_APPID(skb->data),
+			       capi_cmd2str(cmd, subcmd),
+			       CAPIMSG_LEN(skb->data));
+		} else {
+			cdb = capi_message2str(skb->data);
+			if (cdb) {
+				printk(KERN_DEBUG "kcapi: got [%03d] %s\n",
+				       ctr->cnr, cdb->buf);
+				cdebbuf_free(cdb);
+			} else
+				printk(KERN_DEBUG "kcapi: got [%03d] id#%d %s len=%u, cannot trace\n",
+				       ctr->cnr, CAPIMSG_APPID(skb->data),
+				       capi_cmd2str(cmd, subcmd),
+				       CAPIMSG_LEN(skb->data));
+		}
+
+	}
+
+	rcu_read_lock();
+	ap = get_capi_appl_by_nr(CAPIMSG_APPID(skb->data));
+	if (!ap) {
+		rcu_read_unlock();
+		cdb = capi_message2str(skb->data);
+		if (cdb) {
+			printk(KERN_ERR "kcapi: handle_message: applid %d state released (%s)\n",
+			       CAPIMSG_APPID(skb->data), cdb->buf);
+			cdebbuf_free(cdb);
+		} else
+			printk(KERN_ERR "kcapi: handle_message: applid %d state released (%s) cannot trace\n",
+			       CAPIMSG_APPID(skb->data),
+			       capi_cmd2str(cmd, subcmd));
+		goto error;
+	}
+	skb_queue_tail(&ap->recv_queue, skb);
+	queue_work(kcapi_wq, &ap->recv_work);
+	rcu_read_unlock();
+
+	return;
+
+error:
+	kfree_skb(skb);
+}
+
+EXPORT_SYMBOL(capi_ctr_handle_message);
+
+/**
+ * capi_ctr_ready() - signal CAPI controller ready
+ * @ctr:	controller descriptor structure.
+ *
+ * Called by hardware driver to signal that the controller is up and running.
+ */
+
+void capi_ctr_ready(struct capi_ctr *ctr)
+{
+	printk(KERN_NOTICE "kcapi: controller [%03d] \"%s\" ready.\n",
+	       ctr->cnr, ctr->name);
+
+	notify_push(CAPICTR_UP, ctr->cnr);
+}
+
+EXPORT_SYMBOL(capi_ctr_ready);
+
+/**
+ * capi_ctr_down() - signal CAPI controller not ready
+ * @ctr:	controller descriptor structure.
+ *
+ * Called by hardware driver to signal that the controller is down and
+ * unavailable for use.
+ */
+
+void capi_ctr_down(struct capi_ctr *ctr)
+{
+	printk(KERN_NOTICE "kcapi: controller [%03d] down.\n", ctr->cnr);
+
+	notify_push(CAPICTR_DOWN, ctr->cnr);
+}
+
+EXPORT_SYMBOL(capi_ctr_down);
+
+/**
+ * capi_ctr_suspend_output() - suspend controller
+ * @ctr:	controller descriptor structure.
+ *
+ * Called by hardware driver to stop data flow.
+ *
+ * Note: The caller is responsible for synchronizing concurrent state changes
+ * as well as invocations of capi_ctr_handle_message.
+ */
+
+void capi_ctr_suspend_output(struct capi_ctr *ctr)
+{
+	if (!ctr->blocked) {
+		printk(KERN_DEBUG "kcapi: controller [%03d] suspend\n",
+		       ctr->cnr);
+		ctr->blocked = 1;
+	}
+}
+
+EXPORT_SYMBOL(capi_ctr_suspend_output);
+
+/**
+ * capi_ctr_resume_output() - resume controller
+ * @ctr:	controller descriptor structure.
+ *
+ * Called by hardware driver to resume data flow.
+ *
+ * Note: The caller is responsible for synchronizing concurrent state changes
+ * as well as invocations of capi_ctr_handle_message.
+ */
+
+void capi_ctr_resume_output(struct capi_ctr *ctr)
+{
+	if (ctr->blocked) {
+		printk(KERN_DEBUG "kcapi: controller [%03d] resumed\n",
+		       ctr->cnr);
+		ctr->blocked = 0;
+	}
+}
+
+EXPORT_SYMBOL(capi_ctr_resume_output);
+
+/* ------------------------------------------------------------- */
+
+/**
+ * attach_capi_ctr() - register CAPI controller
+ * @ctr:	controller descriptor structure.
+ *
+ * Called by hardware driver to register a controller with the CAPI subsystem.
+ * Return value: 0 on success, error code < 0 on error
+ */
+
+int attach_capi_ctr(struct capi_ctr *ctr)
+{
+	int i;
+
+	mutex_lock(&capi_controller_lock);
+
+	for (i = 0; i < CAPI_MAXCONTR; i++) {
+		if (!capi_controller[i])
+			break;
+	}
+	if (i == CAPI_MAXCONTR) {
+		mutex_unlock(&capi_controller_lock);
+		printk(KERN_ERR "kcapi: out of controller slots\n");
+		return -EBUSY;
+	}
+	capi_controller[i] = ctr;
+
+	ctr->nrecvctlpkt = 0;
+	ctr->nrecvdatapkt = 0;
+	ctr->nsentctlpkt = 0;
+	ctr->nsentdatapkt = 0;
+	ctr->cnr = i + 1;
+	ctr->state = CAPI_CTR_DETECTED;
+	ctr->blocked = 0;
+	ctr->traceflag = showcapimsgs;
+	init_waitqueue_head(&ctr->state_wait_queue);
+
+	sprintf(ctr->procfn, "capi/controllers/%d", ctr->cnr);
+	ctr->procent = proc_create_data(ctr->procfn, 0, NULL, ctr->proc_fops, ctr);
+
+	ncontrollers++;
+
+	mutex_unlock(&capi_controller_lock);
+
+	printk(KERN_NOTICE "kcapi: controller [%03d]: %s attached\n",
+	       ctr->cnr, ctr->name);
+	return 0;
+}
+
+EXPORT_SYMBOL(attach_capi_ctr);
+
+/**
+ * detach_capi_ctr() - unregister CAPI controller
+ * @ctr:	controller descriptor structure.
+ *
+ * Called by hardware driver to remove the registration of a controller
+ * with the CAPI subsystem.
+ * Return value: 0 on success, error code < 0 on error
+ */
+
+int detach_capi_ctr(struct capi_ctr *ctr)
+{
+	int err = 0;
+
+	mutex_lock(&capi_controller_lock);
+
+	ctr_down(ctr, CAPI_CTR_DETACHED);
+
+	if (capi_controller[ctr->cnr - 1] != ctr) {
+		err = -EINVAL;
+		goto unlock_out;
+	}
+	capi_controller[ctr->cnr - 1] = NULL;
+	ncontrollers--;
+
+	if (ctr->procent)
+		remove_proc_entry(ctr->procfn, NULL);
+
+	printk(KERN_NOTICE "kcapi: controller [%03d]: %s unregistered\n",
+	       ctr->cnr, ctr->name);
+
+unlock_out:
+	mutex_unlock(&capi_controller_lock);
+
+	return err;
+}
+
+EXPORT_SYMBOL(detach_capi_ctr);
+
+/**
+ * register_capi_driver() - register CAPI driver
+ * @driver:	driver descriptor structure.
+ *
+ * Called by hardware driver to register itself with the CAPI subsystem.
+ */
+
+void register_capi_driver(struct capi_driver *driver)
+{
+	mutex_lock(&capi_drivers_lock);
+	list_add_tail(&driver->list, &capi_drivers);
+	mutex_unlock(&capi_drivers_lock);
+}
+
+EXPORT_SYMBOL(register_capi_driver);
+
+/**
+ * unregister_capi_driver() - unregister CAPI driver
+ * @driver:	driver descriptor structure.
+ *
+ * Called by hardware driver to unregister itself from the CAPI subsystem.
+ */
+
+void unregister_capi_driver(struct capi_driver *driver)
+{
+	mutex_lock(&capi_drivers_lock);
+	list_del(&driver->list);
+	mutex_unlock(&capi_drivers_lock);
+}
+
+EXPORT_SYMBOL(unregister_capi_driver);
+
+/* ------------------------------------------------------------- */
+/* -------- CAPI2.0 Interface ---------------------------------- */
+/* ------------------------------------------------------------- */
+
+/**
+ * capi20_isinstalled() - CAPI 2.0 operation CAPI_INSTALLED
+ *
+ * Return value: CAPI result code (CAPI_NOERROR if at least one ISDN controller
+ *	is ready for use, CAPI_REGNOTINSTALLED otherwise)
+ */
+
+u16 capi20_isinstalled(void)
+{
+	u16 ret = CAPI_REGNOTINSTALLED;
+	int i;
+
+	mutex_lock(&capi_controller_lock);
+
+	for (i = 0; i < CAPI_MAXCONTR; i++)
+		if (capi_controller[i] &&
+		    capi_controller[i]->state == CAPI_CTR_RUNNING) {
+			ret = CAPI_NOERROR;
+			break;
+		}
+
+	mutex_unlock(&capi_controller_lock);
+
+	return ret;
+}
+
+EXPORT_SYMBOL(capi20_isinstalled);
+
+/**
+ * capi20_register() - CAPI 2.0 operation CAPI_REGISTER
+ * @ap:		CAPI application descriptor structure.
+ *
+ * Register an application's presence with CAPI.
+ * A unique application ID is assigned and stored in @ap->applid.
+ * After this function returns successfully, the message receive
+ * callback function @ap->recv_message() may be called at any time
+ * until capi20_release() has been called for the same @ap.
+ * Return value: CAPI result code
+ */
+
+u16 capi20_register(struct capi20_appl *ap)
+{
+	int i;
+	u16 applid;
+
+	DBG("");
+
+	if (ap->rparam.datablklen < 128)
+		return CAPI_LOGBLKSIZETOSMALL;
+
+	ap->nrecvctlpkt = 0;
+	ap->nrecvdatapkt = 0;
+	ap->nsentctlpkt = 0;
+	ap->nsentdatapkt = 0;
+	mutex_init(&ap->recv_mtx);
+	skb_queue_head_init(&ap->recv_queue);
+	INIT_WORK(&ap->recv_work, recv_handler);
+	ap->release_in_progress = 0;
+
+	mutex_lock(&capi_controller_lock);
+
+	for (applid = 1; applid <= CAPI_MAXAPPL; applid++) {
+		if (capi_applications[applid - 1] == NULL)
+			break;
+	}
+	if (applid > CAPI_MAXAPPL) {
+		mutex_unlock(&capi_controller_lock);
+		return CAPI_TOOMANYAPPLS;
+	}
+
+	ap->applid = applid;
+	capi_applications[applid - 1] = ap;
+
+	for (i = 0; i < CAPI_MAXCONTR; i++) {
+		if (!capi_controller[i] ||
+		    capi_controller[i]->state != CAPI_CTR_RUNNING)
+			continue;
+		register_appl(capi_controller[i], applid, &ap->rparam);
+	}
+
+	mutex_unlock(&capi_controller_lock);
+
+	if (showcapimsgs & 1) {
+		printk(KERN_DEBUG "kcapi: appl %d up\n", applid);
+	}
+
+	return CAPI_NOERROR;
+}
+
+EXPORT_SYMBOL(capi20_register);
+
+/**
+ * capi20_release() - CAPI 2.0 operation CAPI_RELEASE
+ * @ap:		CAPI application descriptor structure.
+ *
+ * Terminate an application's registration with CAPI.
+ * After this function returns successfully, the message receive
+ * callback function @ap->recv_message() will no longer be called.
+ * Return value: CAPI result code
+ */
+
+u16 capi20_release(struct capi20_appl *ap)
+{
+	int i;
+
+	DBG("applid %#x", ap->applid);
+
+	mutex_lock(&capi_controller_lock);
+
+	ap->release_in_progress = 1;
+	capi_applications[ap->applid - 1] = NULL;
+
+	synchronize_rcu();
+
+	for (i = 0; i < CAPI_MAXCONTR; i++) {
+		if (!capi_controller[i] ||
+		    capi_controller[i]->state != CAPI_CTR_RUNNING)
+			continue;
+		release_appl(capi_controller[i], ap->applid);
+	}
+
+	mutex_unlock(&capi_controller_lock);
+
+	flush_workqueue(kcapi_wq);
+	skb_queue_purge(&ap->recv_queue);
+
+	if (showcapimsgs & 1) {
+		printk(KERN_DEBUG "kcapi: appl %d down\n", ap->applid);
+	}
+
+	return CAPI_NOERROR;
+}
+
+EXPORT_SYMBOL(capi20_release);
+
+/**
+ * capi20_put_message() - CAPI 2.0 operation CAPI_PUT_MESSAGE
+ * @ap:		CAPI application descriptor structure.
+ * @skb:	CAPI message.
+ *
+ * Transfer a single message to CAPI.
+ * Return value: CAPI result code
+ */
+
+u16 capi20_put_message(struct capi20_appl *ap, struct sk_buff *skb)
+{
+	struct capi_ctr *ctr;
+	int showctl = 0;
+	u8 cmd, subcmd;
+
+	DBG("applid %#x", ap->applid);
+
+	if (ncontrollers == 0)
+		return CAPI_REGNOTINSTALLED;
+	if ((ap->applid == 0) || ap->release_in_progress)
+		return CAPI_ILLAPPNR;
+	if (skb->len < 12
+	    || !capi_cmd_valid(CAPIMSG_COMMAND(skb->data))
+	    || !capi_subcmd_valid(CAPIMSG_SUBCOMMAND(skb->data)))
+		return CAPI_ILLCMDORSUBCMDORMSGTOSMALL;
+
+	/*
+	 * The controller reference is protected by the existence of the
+	 * application passed to us. We assume that the caller properly
+	 * synchronizes this service with capi20_release.
+	 */
+	ctr = get_capi_ctr_by_nr(CAPIMSG_CONTROLLER(skb->data));
+	if (!ctr || ctr->state != CAPI_CTR_RUNNING)
+		return CAPI_REGNOTINSTALLED;
+	if (ctr->blocked)
+		return CAPI_SENDQUEUEFULL;
+
+	cmd = CAPIMSG_COMMAND(skb->data);
+	subcmd = CAPIMSG_SUBCOMMAND(skb->data);
+
+	if (cmd == CAPI_DATA_B3 && subcmd == CAPI_REQ) {
+		ctr->nsentdatapkt++;
+		ap->nsentdatapkt++;
+		if (ctr->traceflag > 2)
+			showctl |= 2;
+	} else {
+		ctr->nsentctlpkt++;
+		ap->nsentctlpkt++;
+		if (ctr->traceflag)
+			showctl |= 2;
+	}
+	showctl |= (ctr->traceflag & 1);
+	if (showctl & 2) {
+		if (showctl & 1) {
+			printk(KERN_DEBUG "kcapi: put [%03d] id#%d %s len=%u\n",
+			       CAPIMSG_CONTROLLER(skb->data),
+			       CAPIMSG_APPID(skb->data),
+			       capi_cmd2str(cmd, subcmd),
+			       CAPIMSG_LEN(skb->data));
+		} else {
+			_cdebbuf *cdb = capi_message2str(skb->data);
+			if (cdb) {
+				printk(KERN_DEBUG "kcapi: put [%03d] %s\n",
+				       CAPIMSG_CONTROLLER(skb->data),
+				       cdb->buf);
+				cdebbuf_free(cdb);
+			} else
+				printk(KERN_DEBUG "kcapi: put [%03d] id#%d %s len=%u cannot trace\n",
+				       CAPIMSG_CONTROLLER(skb->data),
+				       CAPIMSG_APPID(skb->data),
+				       capi_cmd2str(cmd, subcmd),
+				       CAPIMSG_LEN(skb->data));
+		}
+	}
+	return ctr->send_message(ctr, skb);
+}
+
+EXPORT_SYMBOL(capi20_put_message);
+
+/**
+ * capi20_get_manufacturer() - CAPI 2.0 operation CAPI_GET_MANUFACTURER
+ * @contr:	controller number.
+ * @buf:	result buffer (64 bytes).
+ *
+ * Retrieve information about the manufacturer of the specified ISDN controller
+ * or (for @contr == 0) the driver itself.
+ * Return value: CAPI result code
+ */
+
+u16 capi20_get_manufacturer(u32 contr, u8 *buf)
+{
+	struct capi_ctr *ctr;
+	u16 ret;
+
+	if (contr == 0) {
+		strncpy(buf, capi_manufakturer, CAPI_MANUFACTURER_LEN);
+		return CAPI_NOERROR;
+	}
+
+	mutex_lock(&capi_controller_lock);
+
+	ctr = get_capi_ctr_by_nr(contr);
+	if (ctr && ctr->state == CAPI_CTR_RUNNING) {
+		strncpy(buf, ctr->manu, CAPI_MANUFACTURER_LEN);
+		ret = CAPI_NOERROR;
+	} else
+		ret = CAPI_REGNOTINSTALLED;
+
+	mutex_unlock(&capi_controller_lock);
+	return ret;
+}
+
+EXPORT_SYMBOL(capi20_get_manufacturer);
+
+/**
+ * capi20_get_version() - CAPI 2.0 operation CAPI_GET_VERSION
+ * @contr:	controller number.
+ * @verp:	result structure.
+ *
+ * Retrieve version information for the specified ISDN controller
+ * or (for @contr == 0) the driver itself.
+ * Return value: CAPI result code
+ */
+
+u16 capi20_get_version(u32 contr, struct capi_version *verp)
+{
+	struct capi_ctr *ctr;
+	u16 ret;
+
+	if (contr == 0) {
+		*verp = driver_version;
+		return CAPI_NOERROR;
+	}
+
+	mutex_lock(&capi_controller_lock);
+
+	ctr = get_capi_ctr_by_nr(contr);
+	if (ctr && ctr->state == CAPI_CTR_RUNNING) {
+		memcpy(verp, &ctr->version, sizeof(capi_version));
+		ret = CAPI_NOERROR;
+	} else
+		ret = CAPI_REGNOTINSTALLED;
+
+	mutex_unlock(&capi_controller_lock);
+	return ret;
+}
+
+EXPORT_SYMBOL(capi20_get_version);
+
+/**
+ * capi20_get_serial() - CAPI 2.0 operation CAPI_GET_SERIAL_NUMBER
+ * @contr:	controller number.
+ * @serial:	result buffer (8 bytes).
+ *
+ * Retrieve the serial number of the specified ISDN controller
+ * or (for @contr == 0) the driver itself.
+ * Return value: CAPI result code
+ */
+
+u16 capi20_get_serial(u32 contr, u8 *serial)
+{
+	struct capi_ctr *ctr;
+	u16 ret;
+
+	if (contr == 0) {
+		strlcpy(serial, driver_serial, CAPI_SERIAL_LEN);
+		return CAPI_NOERROR;
+	}
+
+	mutex_lock(&capi_controller_lock);
+
+	ctr = get_capi_ctr_by_nr(contr);
+	if (ctr && ctr->state == CAPI_CTR_RUNNING) {
+		strlcpy(serial, ctr->serial, CAPI_SERIAL_LEN);
+		ret = CAPI_NOERROR;
+	} else
+		ret = CAPI_REGNOTINSTALLED;
+
+	mutex_unlock(&capi_controller_lock);
+	return ret;
+}
+
+EXPORT_SYMBOL(capi20_get_serial);
+
+/**
+ * capi20_get_profile() - CAPI 2.0 operation CAPI_GET_PROFILE
+ * @contr:	controller number.
+ * @profp:	result structure.
+ *
+ * Retrieve capability information for the specified ISDN controller
+ * or (for @contr == 0) the number of installed controllers.
+ * Return value: CAPI result code
+ */
+
+u16 capi20_get_profile(u32 contr, struct capi_profile *profp)
+{
+	struct capi_ctr *ctr;
+	u16 ret;
+
+	if (contr == 0) {
+		profp->ncontroller = ncontrollers;
+		return CAPI_NOERROR;
+	}
+
+	mutex_lock(&capi_controller_lock);
+
+	ctr = get_capi_ctr_by_nr(contr);
+	if (ctr && ctr->state == CAPI_CTR_RUNNING) {
+		memcpy(profp, &ctr->profile, sizeof(struct capi_profile));
+		ret = CAPI_NOERROR;
+	} else
+		ret = CAPI_REGNOTINSTALLED;
+
+	mutex_unlock(&capi_controller_lock);
+	return ret;
+}
+
+EXPORT_SYMBOL(capi20_get_profile);
+
+/* Must be called with capi_controller_lock held. */
+static int wait_on_ctr_state(struct capi_ctr *ctr, unsigned int state)
+{
+	DEFINE_WAIT(wait);
+	int retval = 0;
+
+	ctr = capi_ctr_get(ctr);
+	if (!ctr)
+		return -ESRCH;
+
+	for (;;) {
+		prepare_to_wait(&ctr->state_wait_queue, &wait,
+				TASK_INTERRUPTIBLE);
+
+		if (ctr->state == state)
+			break;
+		if (ctr->state == CAPI_CTR_DETACHED) {
+			retval = -ESRCH;
+			break;
+		}
+		if (signal_pending(current)) {
+			retval = -EINTR;
+			break;
+		}
+
+		mutex_unlock(&capi_controller_lock);
+		schedule();
+		mutex_lock(&capi_controller_lock);
+	}
+	finish_wait(&ctr->state_wait_queue, &wait);
+
+	capi_ctr_put(ctr);
+
+	return retval;
+}
+
+#ifdef AVMB1_COMPAT
+static int old_capi_manufacturer(unsigned int cmd, void __user *data)
+{
+	avmb1_loadandconfigdef ldef;
+	avmb1_extcarddef cdef;
+	avmb1_resetdef rdef;
+	capicardparams cparams;
+	struct capi_ctr *ctr;
+	struct capi_driver *driver = NULL;
+	capiloaddata ldata;
+	struct list_head *l;
+	int retval;
+
+	switch (cmd) {
+	case AVMB1_ADDCARD:
+	case AVMB1_ADDCARD_WITH_TYPE:
+		if (cmd == AVMB1_ADDCARD) {
+			if ((retval = copy_from_user(&cdef, data,
+						     sizeof(avmb1_carddef))))
+				return -EFAULT;
+			cdef.cardtype = AVM_CARDTYPE_B1;
+			cdef.cardnr = 0;
+		} else {
+			if ((retval = copy_from_user(&cdef, data,
+						     sizeof(avmb1_extcarddef))))
+				return -EFAULT;
+		}
+		cparams.port = cdef.port;
+		cparams.irq = cdef.irq;
+		cparams.cardnr = cdef.cardnr;
+
+		mutex_lock(&capi_drivers_lock);
+
+		switch (cdef.cardtype) {
+		case AVM_CARDTYPE_B1:
+			list_for_each(l, &capi_drivers) {
+				driver = list_entry(l, struct capi_driver, list);
+				if (strcmp(driver->name, "b1isa") == 0)
+					break;
+			}
+			break;
+		case AVM_CARDTYPE_T1:
+			list_for_each(l, &capi_drivers) {
+				driver = list_entry(l, struct capi_driver, list);
+				if (strcmp(driver->name, "t1isa") == 0)
+					break;
+			}
+			break;
+		default:
+			driver = NULL;
+			break;
+		}
+		if (!driver) {
+			printk(KERN_ERR "kcapi: driver not loaded.\n");
+			retval = -EIO;
+		} else if (!driver->add_card) {
+			printk(KERN_ERR "kcapi: driver has no add card function.\n");
+			retval = -EIO;
+		} else
+			retval = driver->add_card(driver, &cparams);
+
+		mutex_unlock(&capi_drivers_lock);
+		return retval;
+
+	case AVMB1_LOAD:
+	case AVMB1_LOAD_AND_CONFIG:
+
+		if (cmd == AVMB1_LOAD) {
+			if (copy_from_user(&ldef, data,
+					   sizeof(avmb1_loaddef)))
+				return -EFAULT;
+			ldef.t4config.len = 0;
+			ldef.t4config.data = NULL;
+		} else {
+			if (copy_from_user(&ldef, data,
+					   sizeof(avmb1_loadandconfigdef)))
+				return -EFAULT;
+		}
+
+		mutex_lock(&capi_controller_lock);
+
+		ctr = get_capi_ctr_by_nr(ldef.contr);
+		if (!ctr) {
+			retval = -EINVAL;
+			goto load_unlock_out;
+		}
+
+		if (ctr->load_firmware == NULL) {
+			printk(KERN_DEBUG "kcapi: load: no load function\n");
+			retval = -ESRCH;
+			goto load_unlock_out;
+		}
+
+		if (ldef.t4file.len <= 0) {
+			printk(KERN_DEBUG "kcapi: load: invalid parameter: length of t4file is %d ?\n", ldef.t4file.len);
+			retval = -EINVAL;
+			goto load_unlock_out;
+		}
+		if (ldef.t4file.data == NULL) {
+			printk(KERN_DEBUG "kcapi: load: invalid parameter: dataptr is 0\n");
+			retval = -EINVAL;
+			goto load_unlock_out;
+		}
+
+		ldata.firmware.user = 1;
+		ldata.firmware.data = ldef.t4file.data;
+		ldata.firmware.len = ldef.t4file.len;
+		ldata.configuration.user = 1;
+		ldata.configuration.data = ldef.t4config.data;
+		ldata.configuration.len = ldef.t4config.len;
+
+		if (ctr->state != CAPI_CTR_DETECTED) {
+			printk(KERN_INFO "kcapi: load: contr=%d not in detect state\n", ldef.contr);
+			retval = -EBUSY;
+			goto load_unlock_out;
+		}
+		ctr->state = CAPI_CTR_LOADING;
+
+		retval = ctr->load_firmware(ctr, &ldata);
+		if (retval) {
+			ctr->state = CAPI_CTR_DETECTED;
+			goto load_unlock_out;
+		}
+
+		retval = wait_on_ctr_state(ctr, CAPI_CTR_RUNNING);
+
+	load_unlock_out:
+		mutex_unlock(&capi_controller_lock);
+		return retval;
+
+	case AVMB1_RESETCARD:
+		if (copy_from_user(&rdef, data, sizeof(avmb1_resetdef)))
+			return -EFAULT;
+
+		retval = 0;
+
+		mutex_lock(&capi_controller_lock);
+
+		ctr = get_capi_ctr_by_nr(rdef.contr);
+		if (!ctr) {
+			retval = -ESRCH;
+			goto reset_unlock_out;
+		}
+
+		if (ctr->state == CAPI_CTR_DETECTED)
+			goto reset_unlock_out;
+
+		if (ctr->reset_ctr == NULL) {
+			printk(KERN_DEBUG "kcapi: reset: no reset function\n");
+			retval = -ESRCH;
+			goto reset_unlock_out;
+		}
+
+		ctr->reset_ctr(ctr);
+
+		retval = wait_on_ctr_state(ctr, CAPI_CTR_DETECTED);
+
+	reset_unlock_out:
+		mutex_unlock(&capi_controller_lock);
+		return retval;
+	}
+	return -EINVAL;
+}
+#endif
+
+/**
+ * capi20_manufacturer() - CAPI 2.0 operation CAPI_MANUFACTURER
+ * @cmd:	command.
+ * @data:	parameter.
+ *
+ * Perform manufacturer specific command.
+ * Return value: CAPI result code
+ */
+
+int capi20_manufacturer(unsigned long cmd, void __user *data)
+{
+	struct capi_ctr *ctr;
+	int retval;
+
+	switch (cmd) {
+#ifdef AVMB1_COMPAT
+	case AVMB1_LOAD:
+	case AVMB1_LOAD_AND_CONFIG:
+	case AVMB1_RESETCARD:
+	case AVMB1_GET_CARDINFO:
+	case AVMB1_REMOVECARD:
+		return old_capi_manufacturer(cmd, data);
+#endif
+	case KCAPI_CMD_TRACE:
+	{
+		kcapi_flagdef fdef;
+
+		if (copy_from_user(&fdef, data, sizeof(kcapi_flagdef)))
+			return -EFAULT;
+
+		mutex_lock(&capi_controller_lock);
+
+		ctr = get_capi_ctr_by_nr(fdef.contr);
+		if (ctr) {
+			ctr->traceflag = fdef.flag;
+			printk(KERN_INFO "kcapi: contr [%03d] set trace=%d\n",
+			       ctr->cnr, ctr->traceflag);
+			retval = 0;
+		} else
+			retval = -ESRCH;
+
+		mutex_unlock(&capi_controller_lock);
+
+		return retval;
+	}
+	case KCAPI_CMD_ADDCARD:
+	{
+		struct list_head *l;
+		struct capi_driver *driver = NULL;
+		capicardparams cparams;
+		kcapi_carddef cdef;
+
+		if ((retval = copy_from_user(&cdef, data, sizeof(cdef))))
+			return -EFAULT;
+
+		cparams.port = cdef.port;
+		cparams.irq = cdef.irq;
+		cparams.membase = cdef.membase;
+		cparams.cardnr = cdef.cardnr;
+		cparams.cardtype = 0;
+		cdef.driver[sizeof(cdef.driver) - 1] = 0;
+
+		mutex_lock(&capi_drivers_lock);
+
+		list_for_each(l, &capi_drivers) {
+			driver = list_entry(l, struct capi_driver, list);
+			if (strcmp(driver->name, cdef.driver) == 0)
+				break;
+		}
+		if (driver == NULL) {
+			printk(KERN_ERR "kcapi: driver \"%s\" not loaded.\n",
+			       cdef.driver);
+			retval = -ESRCH;
+		} else if (!driver->add_card) {
+			printk(KERN_ERR "kcapi: driver \"%s\" has no add card function.\n", cdef.driver);
+			retval = -EIO;
+		} else
+			retval = driver->add_card(driver, &cparams);
+
+		mutex_unlock(&capi_drivers_lock);
+		return retval;
+	}
+
+	default:
+		printk(KERN_ERR "kcapi: manufacturer command %lu unknown.\n",
+		       cmd);
+		break;
+
+	}
+	return -EINVAL;
+}
+
+EXPORT_SYMBOL(capi20_manufacturer);
+
+/* ------------------------------------------------------------- */
+/* -------- Init & Cleanup ------------------------------------- */
+/* ------------------------------------------------------------- */
+
+/*
+ * init / exit functions
+ */
+
+static struct notifier_block capictr_nb = {
+	.notifier_call = notify_handler,
+	.priority = INT_MAX,
+};
+
+static int __init kcapi_init(void)
+{
+	int err;
+
+	kcapi_wq = alloc_workqueue("kcapi", 0, 0);
+	if (!kcapi_wq)
+		return -ENOMEM;
+
+	register_capictr_notifier(&capictr_nb);
+
+	err = cdebug_init();
+	if (err) {
+		unregister_capictr_notifier(&capictr_nb);
+		destroy_workqueue(kcapi_wq);
+		return err;
+	}
+
+	kcapi_proc_init();
+	return 0;
+}
+
+static void __exit kcapi_exit(void)
+{
+	kcapi_proc_exit();
+
+	unregister_capictr_notifier(&capictr_nb);
+	cdebug_exit();
+	destroy_workqueue(kcapi_wq);
+}
+
+module_init(kcapi_init);
+module_exit(kcapi_exit);
diff --git a/src/kernel/linux/v4.14/drivers/isdn/capi/kcapi.h b/src/kernel/linux/v4.14/drivers/isdn/capi/kcapi.h
new file mode 100644
index 0000000..6d439f9
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/isdn/capi/kcapi.h
@@ -0,0 +1,51 @@
+/*
+ * Kernel CAPI 2.0 Module
+ *
+ * Copyright 1999 by Carsten Paeth <calle@calle.de>
+ * Copyright 2002 by Kai Germaschewski <kai@germaschewski.name>
+ *
+ * This software may be used and distributed according to the terms
+ * of the GNU General Public License, incorporated herein by reference.
+ *
+ */
+
+
+#include <linux/kernel.h>
+#include <linux/spinlock.h>
+#include <linux/list.h>
+#include <linux/isdn/capilli.h>
+
+#ifdef KCAPI_DEBUG
+#define DBG(format, arg...) do {					\
+		printk(KERN_DEBUG "%s: " format "\n" , __func__ , ## arg); \
+	} while (0)
+#else
+#define DBG(format, arg...) /* */
+#endif
+
+enum {
+	CAPI_CTR_DETACHED = 0,
+	CAPI_CTR_DETECTED = 1,
+	CAPI_CTR_LOADING  = 2,
+	CAPI_CTR_RUNNING  = 3,
+};
+
+extern struct list_head capi_drivers;
+extern struct mutex capi_drivers_lock;
+
+extern struct capi_ctr *capi_controller[CAPI_MAXCONTR];
+extern struct mutex capi_controller_lock;
+
+extern struct capi20_appl *capi_applications[CAPI_MAXAPPL];
+
+#ifdef CONFIG_PROC_FS
+
+void kcapi_proc_init(void);
+void kcapi_proc_exit(void);
+
+#else
+
+static inline void kcapi_proc_init(void) { };
+static inline void kcapi_proc_exit(void) { };
+
+#endif
diff --git a/src/kernel/linux/v4.14/drivers/isdn/capi/kcapi_proc.c b/src/kernel/linux/v4.14/drivers/isdn/capi/kcapi_proc.c
new file mode 100644
index 0000000..68db3c5
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/isdn/capi/kcapi_proc.c
@@ -0,0 +1,322 @@
+/*
+ * Kernel CAPI 2.0 Module - /proc/capi handling
+ *
+ * Copyright 1999 by Carsten Paeth <calle@calle.de>
+ * Copyright 2002 by Kai Germaschewski <kai@germaschewski.name>
+ *
+ * This software may be used and distributed according to the terms
+ * of the GNU General Public License, incorporated herein by reference.
+ *
+ */
+
+
+#include "kcapi.h"
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/init.h>
+#include <linux/export.h>
+
+static char *state2str(unsigned short state)
+{
+	switch (state) {
+	case CAPI_CTR_DETECTED:	return "detected";
+	case CAPI_CTR_LOADING:	return "loading";
+	case CAPI_CTR_RUNNING:	return "running";
+	default:	        return "???";
+	}
+}
+
+// /proc/capi
+// ===========================================================================
+
+// /proc/capi/controller:
+//      cnr driver cardstate name driverinfo
+// /proc/capi/contrstats:
+//      cnr nrecvctlpkt nrecvdatapkt nsentctlpkt nsentdatapkt
+// ---------------------------------------------------------------------------
+
+static void *controller_start(struct seq_file *seq, loff_t *pos)
+	__acquires(capi_controller_lock)
+{
+	mutex_lock(&capi_controller_lock);
+
+	if (*pos < CAPI_MAXCONTR)
+		return &capi_controller[*pos];
+
+	return NULL;
+}
+
+static void *controller_next(struct seq_file *seq, void *v, loff_t *pos)
+{
+	++*pos;
+	if (*pos < CAPI_MAXCONTR)
+		return &capi_controller[*pos];
+
+	return NULL;
+}
+
+static void controller_stop(struct seq_file *seq, void *v)
+	__releases(capi_controller_lock)
+{
+	mutex_unlock(&capi_controller_lock);
+}
+
+static int controller_show(struct seq_file *seq, void *v)
+{
+	struct capi_ctr *ctr = *(struct capi_ctr **) v;
+
+	if (!ctr)
+		return 0;
+
+	seq_printf(seq, "%d %-10s %-8s %-16s %s\n",
+		   ctr->cnr, ctr->driver_name,
+		   state2str(ctr->state),
+		   ctr->name,
+		   ctr->procinfo ?  ctr->procinfo(ctr) : "");
+
+	return 0;
+}
+
+static int contrstats_show(struct seq_file *seq, void *v)
+{
+	struct capi_ctr *ctr = *(struct capi_ctr **) v;
+
+	if (!ctr)
+		return 0;
+
+	seq_printf(seq, "%d %lu %lu %lu %lu\n",
+		   ctr->cnr,
+		   ctr->nrecvctlpkt,
+		   ctr->nrecvdatapkt,
+		   ctr->nsentctlpkt,
+		   ctr->nsentdatapkt);
+
+	return 0;
+}
+
+static const struct seq_operations seq_controller_ops = {
+	.start	= controller_start,
+	.next	= controller_next,
+	.stop	= controller_stop,
+	.show	= controller_show,
+};
+
+static const struct seq_operations seq_contrstats_ops = {
+	.start	= controller_start,
+	.next	= controller_next,
+	.stop	= controller_stop,
+	.show	= contrstats_show,
+};
+
+static int seq_controller_open(struct inode *inode, struct file *file)
+{
+	return seq_open(file, &seq_controller_ops);
+}
+
+static int seq_contrstats_open(struct inode *inode, struct file *file)
+{
+	return seq_open(file, &seq_contrstats_ops);
+}
+
+static const struct file_operations proc_controller_ops = {
+	.owner		= THIS_MODULE,
+	.open		= seq_controller_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= seq_release,
+};
+
+static const struct file_operations proc_contrstats_ops = {
+	.owner		= THIS_MODULE,
+	.open		= seq_contrstats_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= seq_release,
+};
+
+// /proc/capi/applications:
+//      applid l3cnt dblkcnt dblklen #ncci recvqueuelen
+// /proc/capi/applstats:
+//      applid nrecvctlpkt nrecvdatapkt nsentctlpkt nsentdatapkt
+// ---------------------------------------------------------------------------
+
+static void *applications_start(struct seq_file *seq, loff_t *pos)
+	__acquires(capi_controller_lock)
+{
+	mutex_lock(&capi_controller_lock);
+
+	if (*pos < CAPI_MAXAPPL)
+		return &capi_applications[*pos];
+
+	return NULL;
+}
+
+static void *
+applications_next(struct seq_file *seq, void *v, loff_t *pos)
+{
+	++*pos;
+	if (*pos < CAPI_MAXAPPL)
+		return &capi_applications[*pos];
+
+	return NULL;
+}
+
+static void applications_stop(struct seq_file *seq, void *v)
+	__releases(capi_controller_lock)
+{
+	mutex_unlock(&capi_controller_lock);
+}
+
+static int
+applications_show(struct seq_file *seq, void *v)
+{
+	struct capi20_appl *ap = *(struct capi20_appl **) v;
+
+	if (!ap)
+		return 0;
+
+	seq_printf(seq, "%u %d %d %d\n",
+		   ap->applid,
+		   ap->rparam.level3cnt,
+		   ap->rparam.datablkcnt,
+		   ap->rparam.datablklen);
+
+	return 0;
+}
+
+static int
+applstats_show(struct seq_file *seq, void *v)
+{
+	struct capi20_appl *ap = *(struct capi20_appl **) v;
+
+	if (!ap)
+		return 0;
+
+	seq_printf(seq, "%u %lu %lu %lu %lu\n",
+		   ap->applid,
+		   ap->nrecvctlpkt,
+		   ap->nrecvdatapkt,
+		   ap->nsentctlpkt,
+		   ap->nsentdatapkt);
+
+	return 0;
+}
+
+static const struct seq_operations seq_applications_ops = {
+	.start	= applications_start,
+	.next	= applications_next,
+	.stop	= applications_stop,
+	.show	= applications_show,
+};
+
+static const struct seq_operations seq_applstats_ops = {
+	.start	= applications_start,
+	.next	= applications_next,
+	.stop	= applications_stop,
+	.show	= applstats_show,
+};
+
+static int
+seq_applications_open(struct inode *inode, struct file *file)
+{
+	return seq_open(file, &seq_applications_ops);
+}
+
+static int
+seq_applstats_open(struct inode *inode, struct file *file)
+{
+	return seq_open(file, &seq_applstats_ops);
+}
+
+static const struct file_operations proc_applications_ops = {
+	.owner		= THIS_MODULE,
+	.open		= seq_applications_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= seq_release,
+};
+
+static const struct file_operations proc_applstats_ops = {
+	.owner		= THIS_MODULE,
+	.open		= seq_applstats_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= seq_release,
+};
+
+// ---------------------------------------------------------------------------
+
+static void *capi_driver_start(struct seq_file *seq, loff_t *pos)
+	__acquires(&capi_drivers_lock)
+{
+	mutex_lock(&capi_drivers_lock);
+	return seq_list_start(&capi_drivers, *pos);
+}
+
+static void *capi_driver_next(struct seq_file *seq, void *v, loff_t *pos)
+{
+	return seq_list_next(v, &capi_drivers, pos);
+}
+
+static void capi_driver_stop(struct seq_file *seq, void *v)
+	__releases(&capi_drivers_lock)
+{
+	mutex_unlock(&capi_drivers_lock);
+}
+
+static int capi_driver_show(struct seq_file *seq, void *v)
+{
+	struct capi_driver *drv = list_entry(v, struct capi_driver, list);
+
+	seq_printf(seq, "%-32s %s\n", drv->name, drv->revision);
+	return 0;
+}
+
+static const struct seq_operations seq_capi_driver_ops = {
+	.start	= capi_driver_start,
+	.next	= capi_driver_next,
+	.stop	= capi_driver_stop,
+	.show	= capi_driver_show,
+};
+
+static int
+seq_capi_driver_open(struct inode *inode, struct file *file)
+{
+	int err;
+	err = seq_open(file, &seq_capi_driver_ops);
+	return err;
+}
+
+static const struct file_operations proc_driver_ops = {
+	.owner		= THIS_MODULE,
+	.open		= seq_capi_driver_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= seq_release,
+};
+
+// ---------------------------------------------------------------------------
+
+void __init
+kcapi_proc_init(void)
+{
+	proc_mkdir("capi",             NULL);
+	proc_mkdir("capi/controllers", NULL);
+	proc_create("capi/controller",   0, NULL, &proc_controller_ops);
+	proc_create("capi/contrstats",   0, NULL, &proc_contrstats_ops);
+	proc_create("capi/applications", 0, NULL, &proc_applications_ops);
+	proc_create("capi/applstats",    0, NULL, &proc_applstats_ops);
+	proc_create("capi/driver",       0, NULL, &proc_driver_ops);
+}
+
+void __exit
+kcapi_proc_exit(void)
+{
+	remove_proc_entry("capi/driver",       NULL);
+	remove_proc_entry("capi/controller",   NULL);
+	remove_proc_entry("capi/contrstats",   NULL);
+	remove_proc_entry("capi/applications", NULL);
+	remove_proc_entry("capi/applstats",    NULL);
+	remove_proc_entry("capi/controllers",  NULL);
+	remove_proc_entry("capi",              NULL);
+}