diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
commit | 2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch) | |
tree | 848558de17fb3008cdf4d861b01ac7781903ce39 /drivers/isdn/capi | |
parent | Initial commit. (diff) | |
download | linux-upstream.tar.xz linux-upstream.zip |
Adding upstream version 6.1.76.upstream/6.1.76upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/isdn/capi')
-rw-r--r-- | drivers/isdn/capi/Kconfig | 32 | ||||
-rw-r--r-- | drivers/isdn/capi/Makefile | 5 | ||||
-rw-r--r-- | drivers/isdn/capi/capi.c | 1440 | ||||
-rw-r--r-- | drivers/isdn/capi/capiutil.c | 677 | ||||
-rw-r--r-- | drivers/isdn/capi/kcapi.c | 930 | ||||
-rw-r--r-- | drivers/isdn/capi/kcapi.h | 182 | ||||
-rw-r--r-- | drivers/isdn/capi/kcapi_proc.c | 231 |
7 files changed, 3497 insertions, 0 deletions
diff --git a/drivers/isdn/capi/Kconfig b/drivers/isdn/capi/Kconfig new file mode 100644 index 000000000..fdb43a632 --- /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 000000000..352217eba --- /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 000000000..0f00be624 --- /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 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; + + 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(THIS_MODULE, "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 000000000..d7ae42edc --- /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 000000000..ae24848af --- /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 000000000..479623e1d --- /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 000000000..77e951206 --- /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); +} |