summaryrefslogtreecommitdiffstats
path: root/drivers/isdn/capi
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:27:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:27:49 +0000
commitace9429bb58fd418f0c81d4c2835699bddf6bde6 (patch)
treeb2d64bc10158fdd5497876388cd68142ca374ed3 /drivers/isdn/capi
parentInitial commit. (diff)
downloadlinux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.tar.xz
linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.zip
Adding upstream version 6.6.15.upstream/6.6.15
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/isdn/capi')
-rw-r--r--drivers/isdn/capi/Kconfig32
-rw-r--r--drivers/isdn/capi/Makefile5
-rw-r--r--drivers/isdn/capi/capi.c1440
-rw-r--r--drivers/isdn/capi/capiutil.c677
-rw-r--r--drivers/isdn/capi/kcapi.c930
-rw-r--r--drivers/isdn/capi/kcapi.h182
-rw-r--r--drivers/isdn/capi/kcapi_proc.c231
7 files changed, 3497 insertions, 0 deletions
diff --git a/drivers/isdn/capi/Kconfig b/drivers/isdn/capi/Kconfig
new file mode 100644
index 0000000000..fdb43a6322
--- /dev/null
+++ b/drivers/isdn/capi/Kconfig
@@ -0,0 +1,32 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config ISDN_CAPI
+ def_bool ISDN && BT
+ help
+ This provides CAPI (the Common ISDN Application Programming
+ Interface) Version 2.0, a standard making it easy for programs to
+ access ISDN hardware in a device independent way. (For details see
+ <https://www.capi.org/>.) CAPI supports making and accepting voice
+ and data connections, controlling call options and protocols,
+ as well as ISDN supplementary services like call forwarding or
+ three-party conferences (if supported by the specific hardware
+ driver).
+
+ This subsystem requires a hardware specific driver.
+ See CONFIG_BT_CMTP for the last remaining regular driver
+ in the kernel that uses the CAPI subsystem.
+
+config CAPI_TRACE
+ def_bool BT_CMTP
+ 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).
+
+config ISDN_CAPI_MIDDLEWARE
+ def_bool BT_CMTP && 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.
diff --git a/drivers/isdn/capi/Makefile b/drivers/isdn/capi/Makefile
new file mode 100644
index 0000000000..352217ebab
--- /dev/null
+++ b/drivers/isdn/capi/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+# Makefile for the CAPI subsystem used by BT_CMTP
+
+obj-$(CONFIG_BT_CMTP) += kernelcapi.o
+kernelcapi-y := kcapi.o capiutil.o capi.o kcapi_proc.o
diff --git a/drivers/isdn/capi/capi.c b/drivers/isdn/capi/capi.c
new file mode 100644
index 0000000000..2f37895154
--- /dev/null
+++ b/drivers/isdn/capi/capi.c
@@ -0,0 +1,1440 @@
+/* $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/compiler.h>
+#include <linux/module.h>
+#include <linux/ethtool.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>
+
+#include "kcapi.h"
+
+MODULE_DESCRIPTION("CAPI4Linux: kernel CAPI layer and /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 __poll_t
+capi_poll(struct file *file, poll_table *wait)
+{
+ struct capidev *cdev = file->private_data;
+ __poll_t mask = 0;
+
+ if (!cdev->ap.applid)
+ return EPOLLERR;
+
+ poll_wait(file, &(cdev->recvwait), wait);
+ mask = EPOLLOUT | EPOLLWRNORM;
+ if (!skb_queue_empty_lockless(&cdev->recvqueue))
+ mask |= EPOLLIN | EPOLLRDNORM;
+ 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;
+}
+
+#ifdef CONFIG_COMPAT
+static long
+capi_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ int ret;
+
+ if (cmd == CAPI_MANUFACTURER_CMD) {
+ struct {
+ compat_ulong_t cmd;
+ compat_uptr_t data;
+ } mcmd32;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+ if (copy_from_user(&mcmd32, compat_ptr(arg), sizeof(mcmd32)))
+ return -EFAULT;
+
+ mutex_lock(&capi_mutex);
+ ret = capi20_manufacturer(mcmd32.cmd, compat_ptr(mcmd32.data));
+ mutex_unlock(&capi_mutex);
+
+ return ret;
+ }
+
+ return capi_unlocked_ioctl(file, cmd, (unsigned long)compat_ptr(arg));
+}
+#endif
+
+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 stream_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,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = capi_compat_ioctl,
+#endif
+ .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 ssize_t capinc_tty_write(struct tty_struct *tty, const u8 *buf,
+ size_t count)
+{
+ struct capiminor *mp = tty->driver_data;
+ struct sk_buff *skb;
+
+ pr_debug("capinc_tty_write(count=%zu)\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, u8 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;
+
+ 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 unsigned int capinc_tty_write_room(struct tty_struct *tty)
+{
+ struct capiminor *mp = tty->driver_data;
+ unsigned int room;
+
+ room = CAPINC_MAX_SENDQUEUE-skb_queue_len(&mp->outqueue);
+ room *= CAPI_MAX_BLKSIZE;
+ pr_debug("capinc_tty_write_room = %u\n", room);
+ return room;
+}
+
+static unsigned 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 void capinc_tty_throttle(struct tty_struct *tty)
+{
+ struct capiminor *mp = tty->driver_data;
+ mp->ttyinstop = 1;
+}
+
+static void capinc_tty_unthrottle(struct tty_struct *tty)
+{
+ struct capiminor *mp = tty->driver_data;
+
+ mp->ttyinstop = 0;
+ handle_minor_recv(mp);
+}
+
+static void capinc_tty_stop(struct tty_struct *tty)
+{
+ struct capiminor *mp = tty->driver_data;
+
+ mp->ttyoutstop = 1;
+}
+
+static void capinc_tty_start(struct tty_struct *tty)
+{
+ struct capiminor *mp = tty->driver_data;
+
+ mp->ttyoutstop = 0;
+ handle_minor_send(mp);
+}
+
+static void capinc_tty_hangup(struct tty_struct *tty)
+{
+ struct capiminor *mp = tty->driver_data;
+
+ tty_port_hangup(&mp->port);
+}
+
+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,
+ .throttle = capinc_tty_throttle,
+ .unthrottle = capinc_tty_unthrottle,
+ .stop = capinc_tty_stop,
+ .start = capinc_tty_start,
+ .hangup = capinc_tty_hangup,
+ .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 = kcalloc(capi_ttyminors, sizeof(struct capiminor *),
+ GFP_KERNEL);
+ if (!capiminors)
+ return -ENOMEM;
+
+ drv = tty_alloc_driver(capi_ttyminors, TTY_DRIVER_REAL_RAW |
+ TTY_DRIVER_RESET_TERMIOS | TTY_DRIVER_DYNAMIC_DEV);
+ if (IS_ERR(drv)) {
+ kfree(capiminors);
+ return PTR_ERR(drv);
+ }
+ 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;
+ tty_set_operations(drv, &capinc_ops);
+
+ err = tty_register_driver(drv);
+ if (err) {
+ tty_driver_kref_put(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);
+ tty_driver_kref_put(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 __maybe_unused 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;
+}
+
+/*
+ * /proc/capi/capi20ncci:
+ * applid ncci
+ */
+static int __maybe_unused 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 void __init proc_init(void)
+{
+ proc_create_single("capi/capi20", 0, NULL, capi20_proc_show);
+ proc_create_single("capi/capi20ncci", 0, NULL, capi20ncci_proc_show);
+}
+
+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;
+ int ret;
+
+ ret = kcapi_init();
+ if (ret)
+ return 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);
+ kcapi_exit();
+ return major_ret;
+ }
+ capi_class = class_create("capi");
+ if (IS_ERR(capi_class)) {
+ unregister_chrdev(capi_major, "capi20");
+ kcapi_exit();
+ 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");
+ kcapi_exit();
+ 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();
+
+ kcapi_exit();
+}
+
+module_init(capi_init);
+module_exit(capi_exit);
diff --git a/drivers/isdn/capi/capiutil.c b/drivers/isdn/capi/capiutil.c
new file mode 100644
index 0000000000..d7ae42edc4
--- /dev/null
+++ b/drivers/isdn/capi/capiutil.c
@@ -0,0 +1,677 @@
+/* $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>
+
+#include "kcapi.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 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 <linux/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;
+}
+
+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 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 cdebug_exit(void)
+{
+}
+
+#endif
diff --git a/drivers/isdn/capi/kcapi.c b/drivers/isdn/capi/kcapi.c
new file mode 100644
index 0000000000..ae24848af2
--- /dev/null
+++ b/drivers/isdn/capi/kcapi.c
@@ -0,0 +1,930 @@
+/* $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.
+ *
+ */
+
+#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>
+#include <linux/mutex.h>
+#include <linux/rcupdate.h>
+
+static int showcapimsgs;
+static struct workqueue_struct *kcapi_wq;
+
+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)
+
+struct capi_ctr *capi_controller[CAPI_MAXCONTR];
+DEFINE_MUTEX(capi_controller_lock);
+
+struct capi20_appl *capi_applications[CAPI_MAXAPPL];
+
+static int ncontrollers;
+
+/* -------- 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);
+ }
+ } 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);
+ }
+}
+
+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 void do_notify_work(struct work_struct *work)
+{
+ struct capictr_event *event =
+ container_of(work, struct capictr_event, work);
+
+ switch (event->type) {
+ case CAPICTR_UP:
+ notify_up(event->controller);
+ break;
+ case CAPICTR_DOWN:
+ notify_down(event->controller);
+ break;
+ }
+
+ kfree(event);
+}
+
+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;
+}
+
+/* -------- 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);
+
+/* ------------------------------------------------------------- */
+
+/**
+ * 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;
+
+ sprintf(ctr->procfn, "capi/controllers/%d", ctr->cnr);
+ ctr->procent = proc_create_single_data(ctr->procfn, 0, NULL,
+ ctr->proc_show, 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 (ctr->cnr < 1 || ctr->cnr - 1 >= CAPI_MAXCONTR) {
+ err = -EINVAL;
+ goto unlock_out;
+ }
+
+ 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);
+
+/* ------------------------------------------------------------- */
+/* -------- 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;
+}
+
+/**
+ * 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;
+}
+
+/**
+ * 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;
+}
+
+/**
+ * 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);
+}
+
+/**
+ * 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[CAPI_MANUFACTURER_LEN])
+{
+ 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;
+}
+
+/**
+ * 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;
+}
+
+/**
+ * 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[CAPI_SERIAL_LEN])
+{
+ struct capi_ctr *ctr;
+ u16 ret;
+
+ if (contr == 0) {
+ strscpy(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) {
+ strscpy(serial, ctr->serial, CAPI_SERIAL_LEN);
+ ret = CAPI_NOERROR;
+ } else
+ ret = CAPI_REGNOTINSTALLED;
+
+ mutex_unlock(&capi_controller_lock);
+ return ret;
+}
+
+/**
+ * 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;
+}
+
+/**
+ * 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) {
+ 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;
+ }
+
+ default:
+ printk(KERN_ERR "kcapi: manufacturer command %lu unknown.\n",
+ cmd);
+ break;
+
+ }
+ return -EINVAL;
+}
+
+/* ------------------------------------------------------------- */
+/* -------- Init & Cleanup ------------------------------------- */
+/* ------------------------------------------------------------- */
+
+/*
+ * init / exit functions
+ */
+
+int __init kcapi_init(void)
+{
+ int err;
+
+ kcapi_wq = alloc_workqueue("kcapi", 0, 0);
+ if (!kcapi_wq)
+ return -ENOMEM;
+
+ err = cdebug_init();
+ if (err) {
+ destroy_workqueue(kcapi_wq);
+ return err;
+ }
+
+ kcapi_proc_init();
+ return 0;
+}
+
+void kcapi_exit(void)
+{
+ kcapi_proc_exit();
+
+ cdebug_exit();
+ destroy_workqueue(kcapi_wq);
+}
diff --git a/drivers/isdn/capi/kcapi.h b/drivers/isdn/capi/kcapi.h
new file mode 100644
index 0000000000..479623e1db
--- /dev/null
+++ b/drivers/isdn/capi/kcapi.h
@@ -0,0 +1,182 @@
+/*
+ * 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 capi_ctr *capi_controller[CAPI_MAXCONTR];
+extern struct mutex capi_controller_lock;
+
+extern struct capi20_appl *capi_applications[CAPI_MAXAPPL];
+
+void kcapi_proc_init(void);
+void kcapi_proc_exit(void);
+
+struct capi20_appl {
+ u16 applid;
+ capi_register_params rparam;
+ void (*recv_message)(struct capi20_appl *ap, struct sk_buff *skb);
+ void *private;
+
+ /* internal to kernelcapi.o */
+ unsigned long nrecvctlpkt;
+ unsigned long nrecvdatapkt;
+ unsigned long nsentctlpkt;
+ unsigned long nsentdatapkt;
+ struct mutex recv_mtx;
+ struct sk_buff_head recv_queue;
+ struct work_struct recv_work;
+ int release_in_progress;
+};
+
+u16 capi20_isinstalled(void);
+u16 capi20_register(struct capi20_appl *ap);
+u16 capi20_release(struct capi20_appl *ap);
+u16 capi20_put_message(struct capi20_appl *ap, struct sk_buff *skb);
+u16 capi20_get_manufacturer(u32 contr, u8 buf[CAPI_MANUFACTURER_LEN]);
+u16 capi20_get_version(u32 contr, struct capi_version *verp);
+u16 capi20_get_serial(u32 contr, u8 serial[CAPI_SERIAL_LEN]);
+u16 capi20_get_profile(u32 contr, struct capi_profile *profp);
+int capi20_manufacturer(unsigned long cmd, void __user *data);
+
+#define CAPICTR_UP 0
+#define CAPICTR_DOWN 1
+
+int kcapi_init(void);
+void kcapi_exit(void);
+
+/*----- basic-type definitions -----*/
+
+typedef __u8 *_cstruct;
+
+typedef enum {
+ CAPI_COMPOSE,
+ CAPI_DEFAULT
+} _cmstruct;
+
+/*
+ The _cmsg structure contains all possible CAPI 2.0 parameter.
+ All parameters are stored here first. The function CAPI_CMSG_2_MESSAGE
+ assembles the parameter and builds CAPI2.0 conform messages.
+ CAPI_MESSAGE_2_CMSG disassembles CAPI 2.0 messages and stores the
+ parameter in the _cmsg structure
+ */
+
+typedef struct {
+ /* Header */
+ __u16 ApplId;
+ __u8 Command;
+ __u8 Subcommand;
+ __u16 Messagenumber;
+
+ /* Parameter */
+ union {
+ __u32 adrController;
+ __u32 adrPLCI;
+ __u32 adrNCCI;
+ } adr;
+
+ _cmstruct AdditionalInfo;
+ _cstruct B1configuration;
+ __u16 B1protocol;
+ _cstruct B2configuration;
+ __u16 B2protocol;
+ _cstruct B3configuration;
+ __u16 B3protocol;
+ _cstruct BC;
+ _cstruct BChannelinformation;
+ _cmstruct BProtocol;
+ _cstruct CalledPartyNumber;
+ _cstruct CalledPartySubaddress;
+ _cstruct CallingPartyNumber;
+ _cstruct CallingPartySubaddress;
+ __u32 CIPmask;
+ __u32 CIPmask2;
+ __u16 CIPValue;
+ __u32 Class;
+ _cstruct ConnectedNumber;
+ _cstruct ConnectedSubaddress;
+ __u32 Data;
+ __u16 DataHandle;
+ __u16 DataLength;
+ _cstruct FacilityConfirmationParameter;
+ _cstruct Facilitydataarray;
+ _cstruct FacilityIndicationParameter;
+ _cstruct FacilityRequestParameter;
+ __u16 FacilitySelector;
+ __u16 Flags;
+ __u32 Function;
+ _cstruct HLC;
+ __u16 Info;
+ _cstruct InfoElement;
+ __u32 InfoMask;
+ __u16 InfoNumber;
+ _cstruct Keypadfacility;
+ _cstruct LLC;
+ _cstruct ManuData;
+ __u32 ManuID;
+ _cstruct NCPI;
+ __u16 Reason;
+ __u16 Reason_B3;
+ __u16 Reject;
+ _cstruct Useruserdata;
+
+ /* intern */
+ unsigned l, p;
+ unsigned char *par;
+ __u8 *m;
+
+ /* buffer to construct message */
+ __u8 buf[180];
+
+} _cmsg;
+
+/*-----------------------------------------------------------------------*/
+
+/*
+ * Debugging / Tracing functions
+ */
+
+char *capi_cmd2str(__u8 cmd, __u8 subcmd);
+
+typedef struct {
+ u_char *buf;
+ u_char *p;
+ size_t size;
+ size_t pos;
+} _cdebbuf;
+
+#define CDEBUG_SIZE 1024
+#define CDEBUG_GSIZE 4096
+
+void cdebbuf_free(_cdebbuf *cdb);
+int cdebug_init(void);
+void cdebug_exit(void);
+
+_cdebbuf *capi_message2str(__u8 *msg);
diff --git a/drivers/isdn/capi/kcapi_proc.c b/drivers/isdn/capi/kcapi_proc.c
new file mode 100644
index 0000000000..77e9512068
--- /dev/null
+++ b/drivers/isdn/capi/kcapi_proc.c
@@ -0,0 +1,231 @@
+/*
+ * 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,
+};
+
+// /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,
+};
+
+// ---------------------------------------------------------------------------
+
+/* /proc/capi/drivers is always empty */
+static ssize_t empty_read(struct file *file, char __user *buf,
+ size_t size, loff_t *off)
+{
+ return 0;
+}
+
+static const struct proc_ops empty_proc_ops = {
+ .proc_read = empty_read,
+ .proc_lseek = default_llseek,
+};
+
+// ---------------------------------------------------------------------------
+
+void __init
+kcapi_proc_init(void)
+{
+ proc_mkdir("capi", NULL);
+ proc_mkdir("capi/controllers", NULL);
+ proc_create_seq("capi/controller", 0, NULL, &seq_controller_ops);
+ proc_create_seq("capi/contrstats", 0, NULL, &seq_contrstats_ops);
+ proc_create_seq("capi/applications", 0, NULL, &seq_applications_ops);
+ proc_create_seq("capi/applstats", 0, NULL, &seq_applstats_ops);
+ proc_create("capi/driver", 0, NULL, &empty_proc_ops);
+}
+
+void
+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);
+}