diff options
Diffstat (limited to 'drivers/isdn/capi/kcapi.c')
-rw-r--r-- | drivers/isdn/capi/kcapi.c | 1316 |
1 files changed, 1316 insertions, 0 deletions
diff --git a/drivers/isdn/capi/kcapi.c b/drivers/isdn/capi/kcapi.c new file mode 100644 index 00000000..9b1b274c --- /dev/null +++ b/drivers/isdn/capi/kcapi.c @@ -0,0 +1,1316 @@ +/* $Id: kcapi.c,v 1.1.2.8 2004/03/26 19:57:20 armin Exp $ + * + * Kernel CAPI 2.0 Module + * + * Copyright 1999 by Carsten Paeth <calle@calle.de> + * Copyright 2002 by Kai Germaschewski <kai@germaschewski.name> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#define AVMB1_COMPAT + +#include "kcapi.h" +#include <linux/module.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/proc_fs.h> +#include <linux/sched.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 <asm/uaccess.h> +#include <linux/isdn/capicmd.h> +#include <linux/isdn/capiutil.h> +#ifdef AVMB1_COMPAT +#include <linux/b1lli.h> +#endif +#include <linux/mutex.h> +#include <linux/rcupdate.h> + +static int showcapimsgs = 0; +static struct workqueue_struct *kcapi_wq; + +MODULE_DESCRIPTION("CAPI4Linux: kernel CAPI layer"); +MODULE_AUTHOR("Carsten Paeth"); +MODULE_LICENSE("GPL"); +module_param(showcapimsgs, uint, 0); + +/* ------------------------------------------------------------- */ + +struct capictr_event { + struct work_struct work; + unsigned int type; + u32 controller; +}; + +/* ------------------------------------------------------------- */ + +static struct capi_version driver_version = {2, 0, 1, 1 << 4}; +static char driver_serial[CAPI_SERIAL_LEN] = "0004711"; +static char capi_manufakturer[64] = "AVM Berlin"; + +#define NCCI2CTRL(ncci) (((ncci) >> 24) & 0x7f) + +LIST_HEAD(capi_drivers); +DEFINE_MUTEX(capi_drivers_lock); + +struct capi_ctr *capi_controller[CAPI_MAXCONTR]; +DEFINE_MUTEX(capi_controller_lock); + +struct capi20_appl *capi_applications[CAPI_MAXAPPL]; + +static int ncontrollers; + +static BLOCKING_NOTIFIER_HEAD(ctr_notifier_list); + +/* -------- controller ref counting -------------------------------------- */ + +static inline struct capi_ctr * +capi_ctr_get(struct capi_ctr *ctr) +{ + if (!try_module_get(ctr->owner)) + return NULL; + return ctr; +} + +static inline void +capi_ctr_put(struct capi_ctr *ctr) +{ + module_put(ctr->owner); +} + +/* ------------------------------------------------------------- */ + +static inline struct capi_ctr *get_capi_ctr_by_nr(u16 contr) +{ + if (contr - 1 >= 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 >= CAPI_MAXAPPL) + return NULL; + + return capi_applications[applid - 1]; +} + +static inline struct capi20_appl *get_capi_appl_by_nr(u16 applid) +{ + if (applid - 1 >= CAPI_MAXAPPL) + return NULL; + + return rcu_dereference(capi_applications[applid - 1]); +} + +/* -------- util functions ------------------------------------ */ + +static inline int capi_cmd_valid(u8 cmd) +{ + switch (cmd) { + case CAPI_ALERT: + case CAPI_CONNECT: + case CAPI_CONNECT_ACTIVE: + case CAPI_CONNECT_B3_ACTIVE: + case CAPI_CONNECT_B3: + case CAPI_CONNECT_B3_T90_ACTIVE: + case CAPI_DATA_B3: + case CAPI_DISCONNECT_B3: + case CAPI_DISCONNECT: + case CAPI_FACILITY: + case CAPI_INFO: + case CAPI_LISTEN: + case CAPI_MANUFACTURER: + case CAPI_RESET_B3: + case CAPI_SELECT_B_PROTOCOL: + return 1; + } + return 0; +} + +static inline int capi_subcmd_valid(u8 subcmd) +{ + switch (subcmd) { + case CAPI_REQ: + case CAPI_CONF: + case CAPI_IND: + case CAPI_RESP: + return 1; + } + return 0; +} + +/* ------------------------------------------------------------ */ + +static void +register_appl(struct capi_ctr *ctr, u16 applid, capi_register_params *rparam) +{ + ctr = capi_ctr_get(ctr); + + if (ctr) + ctr->register_appl(ctr, applid, rparam); + else + printk(KERN_WARNING "%s: cannot get controller resources\n", + __func__); +} + + +static void release_appl(struct capi_ctr *ctr, u16 applid) +{ + DBG("applid %#x", applid); + + ctr->release_appl(ctr, applid); + capi_ctr_put(ctr); +} + +static void notify_up(u32 contr) +{ + struct capi20_appl *ap; + struct capi_ctr *ctr; + u16 applid; + + mutex_lock(&capi_controller_lock); + + if (showcapimsgs & 1) + printk(KERN_DEBUG "kcapi: notify up contr %d\n", contr); + + ctr = get_capi_ctr_by_nr(contr); + if (ctr) { + if (ctr->state == CAPI_CTR_RUNNING) + goto unlock_out; + + ctr->state = CAPI_CTR_RUNNING; + + for (applid = 1; applid <= CAPI_MAXAPPL; applid++) { + ap = __get_capi_appl_by_nr(applid); + if (ap) + register_appl(ctr, applid, &ap->rparam); + } + + wake_up_interruptible_all(&ctr->state_wait_queue); + } else + printk(KERN_WARNING "%s: invalid contr %d\n", __func__, contr); + +unlock_out: + mutex_unlock(&capi_controller_lock); +} + +static void ctr_down(struct capi_ctr *ctr, int new_state) +{ + struct capi20_appl *ap; + u16 applid; + + if (ctr->state == CAPI_CTR_DETECTED || ctr->state == CAPI_CTR_DETACHED) + return; + + ctr->state = new_state; + + memset(ctr->manu, 0, sizeof(ctr->manu)); + memset(&ctr->version, 0, sizeof(ctr->version)); + memset(&ctr->profile, 0, sizeof(ctr->profile)); + memset(ctr->serial, 0, sizeof(ctr->serial)); + + for (applid = 1; applid <= CAPI_MAXAPPL; applid++) { + ap = __get_capi_appl_by_nr(applid); + if (ap) + capi_ctr_put(ctr); + } + + wake_up_interruptible_all(&ctr->state_wait_queue); +} + +static void notify_down(u32 contr) +{ + struct capi_ctr *ctr; + + mutex_lock(&capi_controller_lock); + + if (showcapimsgs & 1) + printk(KERN_DEBUG "kcapi: notify down contr %d\n", contr); + + ctr = get_capi_ctr_by_nr(contr); + if (ctr) + ctr_down(ctr, CAPI_CTR_DETECTED); + else + printk(KERN_WARNING "%s: invalid contr %d\n", __func__, contr); + + mutex_unlock(&capi_controller_lock); +} + +static int +notify_handler(struct notifier_block *nb, unsigned long val, void *v) +{ + u32 contr = (long)v; + + switch (val) { + case CAPICTR_UP: + notify_up(contr); + break; + case CAPICTR_DOWN: + notify_down(contr); + break; + } + return NOTIFY_OK; +} + +static void do_notify_work(struct work_struct *work) +{ + struct capictr_event *event = + container_of(work, struct capictr_event, work); + + blocking_notifier_call_chain(&ctr_notifier_list, event->type, + (void *)(long)event->controller); + kfree(event); +} + +/* + * The notifier will result in adding/deleteing of devices. Devices can + * only removed in user process, not in bh. + */ +static int notify_push(unsigned int event_type, u32 controller) +{ + struct capictr_event *event = kmalloc(sizeof(*event), GFP_ATOMIC); + + if (!event) + return -ENOMEM; + + INIT_WORK(&event->work, do_notify_work); + event->type = event_type; + event->controller = controller; + + queue_work(kcapi_wq, &event->work); + return 0; +} + +int register_capictr_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&ctr_notifier_list, nb); +} +EXPORT_SYMBOL_GPL(register_capictr_notifier); + +int unregister_capictr_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&ctr_notifier_list, nb); +} +EXPORT_SYMBOL_GPL(unregister_capictr_notifier); + +/* -------- Receiver ------------------------------------------ */ + +static void recv_handler(struct work_struct *work) +{ + struct sk_buff *skb; + struct capi20_appl *ap = + container_of(work, struct capi20_appl, recv_work); + + if ((!ap) || (ap->release_in_progress)) + return; + + mutex_lock(&ap->recv_mtx); + while ((skb = skb_dequeue(&ap->recv_queue))) { + if (CAPIMSG_CMD(skb->data) == CAPI_DATA_B3_IND) + ap->nrecvdatapkt++; + else + ap->nrecvctlpkt++; + + ap->recv_message(ap, skb); + } + mutex_unlock(&ap->recv_mtx); +} + +/** + * capi_ctr_handle_message() - handle incoming CAPI message + * @ctr: controller descriptor structure. + * @appl: application ID. + * @skb: message. + * + * Called by hardware driver to pass a CAPI message to the application. + */ + +void capi_ctr_handle_message(struct capi_ctr *ctr, u16 appl, + struct sk_buff *skb) +{ + struct capi20_appl *ap; + int showctl = 0; + u8 cmd, subcmd; + _cdebbuf *cdb; + + if (ctr->state != CAPI_CTR_RUNNING) { + cdb = capi_message2str(skb->data); + if (cdb) { + printk(KERN_INFO "kcapi: controller [%03d] not active, got: %s", + ctr->cnr, cdb->buf); + cdebbuf_free(cdb); + } else + printk(KERN_INFO "kcapi: controller [%03d] not active, cannot trace\n", + ctr->cnr); + goto error; + } + + cmd = CAPIMSG_COMMAND(skb->data); + subcmd = CAPIMSG_SUBCOMMAND(skb->data); + if (cmd == CAPI_DATA_B3 && subcmd == CAPI_IND) { + ctr->nrecvdatapkt++; + if (ctr->traceflag > 2) + showctl |= 2; + } else { + ctr->nrecvctlpkt++; + if (ctr->traceflag) + showctl |= 2; + } + showctl |= (ctr->traceflag & 1); + if (showctl & 2) { + if (showctl & 1) { + printk(KERN_DEBUG "kcapi: got [%03d] id#%d %s len=%u\n", + ctr->cnr, CAPIMSG_APPID(skb->data), + capi_cmd2str(cmd, subcmd), + CAPIMSG_LEN(skb->data)); + } else { + cdb = capi_message2str(skb->data); + if (cdb) { + printk(KERN_DEBUG "kcapi: got [%03d] %s\n", + ctr->cnr, cdb->buf); + cdebbuf_free(cdb); + } else + printk(KERN_DEBUG "kcapi: got [%03d] id#%d %s len=%u, cannot trace\n", + ctr->cnr, CAPIMSG_APPID(skb->data), + capi_cmd2str(cmd, subcmd), + CAPIMSG_LEN(skb->data)); + } + + } + + rcu_read_lock(); + ap = get_capi_appl_by_nr(CAPIMSG_APPID(skb->data)); + if (!ap) { + rcu_read_unlock(); + cdb = capi_message2str(skb->data); + if (cdb) { + printk(KERN_ERR "kcapi: handle_message: applid %d state released (%s)\n", + CAPIMSG_APPID(skb->data), cdb->buf); + cdebbuf_free(cdb); + } else + printk(KERN_ERR "kcapi: handle_message: applid %d state released (%s) cannot trace\n", + CAPIMSG_APPID(skb->data), + capi_cmd2str(cmd, subcmd)); + goto error; + } + skb_queue_tail(&ap->recv_queue, skb); + queue_work(kcapi_wq, &ap->recv_work); + rcu_read_unlock(); + + return; + +error: + kfree_skb(skb); +} + +EXPORT_SYMBOL(capi_ctr_handle_message); + +/** + * capi_ctr_ready() - signal CAPI controller ready + * @ctr: controller descriptor structure. + * + * Called by hardware driver to signal that the controller is up and running. + */ + +void capi_ctr_ready(struct capi_ctr *ctr) +{ + printk(KERN_NOTICE "kcapi: controller [%03d] \"%s\" ready.\n", + ctr->cnr, ctr->name); + + notify_push(CAPICTR_UP, ctr->cnr); +} + +EXPORT_SYMBOL(capi_ctr_ready); + +/** + * capi_ctr_down() - signal CAPI controller not ready + * @ctr: controller descriptor structure. + * + * Called by hardware driver to signal that the controller is down and + * unavailable for use. + */ + +void capi_ctr_down(struct capi_ctr *ctr) +{ + printk(KERN_NOTICE "kcapi: controller [%03d] down.\n", ctr->cnr); + + notify_push(CAPICTR_DOWN, ctr->cnr); +} + +EXPORT_SYMBOL(capi_ctr_down); + +/** + * capi_ctr_suspend_output() - suspend controller + * @ctr: controller descriptor structure. + * + * Called by hardware driver to stop data flow. + * + * Note: The caller is responsible for synchronizing concurrent state changes + * as well as invocations of capi_ctr_handle_message. + */ + +void capi_ctr_suspend_output(struct capi_ctr *ctr) +{ + if (!ctr->blocked) { + printk(KERN_DEBUG "kcapi: controller [%03d] suspend\n", + ctr->cnr); + ctr->blocked = 1; + } +} + +EXPORT_SYMBOL(capi_ctr_suspend_output); + +/** + * capi_ctr_resume_output() - resume controller + * @ctr: controller descriptor structure. + * + * Called by hardware driver to resume data flow. + * + * Note: The caller is responsible for synchronizing concurrent state changes + * as well as invocations of capi_ctr_handle_message. + */ + +void capi_ctr_resume_output(struct capi_ctr *ctr) +{ + if (ctr->blocked) { + printk(KERN_DEBUG "kcapi: controller [%03d] resumed\n", + ctr->cnr); + ctr->blocked = 0; + } +} + +EXPORT_SYMBOL(capi_ctr_resume_output); + +/* ------------------------------------------------------------- */ + +/** + * attach_capi_ctr() - register CAPI controller + * @ctr: controller descriptor structure. + * + * Called by hardware driver to register a controller with the CAPI subsystem. + * Return value: 0 on success, error code < 0 on error + */ + +int attach_capi_ctr(struct capi_ctr *ctr) +{ + int i; + + mutex_lock(&capi_controller_lock); + + for (i = 0; i < CAPI_MAXCONTR; i++) { + if (!capi_controller[i]) + break; + } + if (i == CAPI_MAXCONTR) { + mutex_unlock(&capi_controller_lock); + printk(KERN_ERR "kcapi: out of controller slots\n"); + return -EBUSY; + } + capi_controller[i] = ctr; + + ctr->nrecvctlpkt = 0; + ctr->nrecvdatapkt = 0; + ctr->nsentctlpkt = 0; + ctr->nsentdatapkt = 0; + ctr->cnr = i + 1; + ctr->state = CAPI_CTR_DETECTED; + ctr->blocked = 0; + ctr->traceflag = showcapimsgs; + init_waitqueue_head(&ctr->state_wait_queue); + + sprintf(ctr->procfn, "capi/controllers/%d", ctr->cnr); + ctr->procent = proc_create_data(ctr->procfn, 0, NULL, ctr->proc_fops, ctr); + + ncontrollers++; + + mutex_unlock(&capi_controller_lock); + + printk(KERN_NOTICE "kcapi: controller [%03d]: %s attached\n", + ctr->cnr, ctr->name); + return 0; +} + +EXPORT_SYMBOL(attach_capi_ctr); + +/** + * detach_capi_ctr() - unregister CAPI controller + * @ctr: controller descriptor structure. + * + * Called by hardware driver to remove the registration of a controller + * with the CAPI subsystem. + * Return value: 0 on success, error code < 0 on error + */ + +int detach_capi_ctr(struct capi_ctr *ctr) +{ + int err = 0; + + mutex_lock(&capi_controller_lock); + + ctr_down(ctr, CAPI_CTR_DETACHED); + + if (capi_controller[ctr->cnr - 1] != ctr) { + err = -EINVAL; + goto unlock_out; + } + capi_controller[ctr->cnr - 1] = NULL; + ncontrollers--; + + if (ctr->procent) + remove_proc_entry(ctr->procfn, NULL); + + printk(KERN_NOTICE "kcapi: controller [%03d]: %s unregistered\n", + ctr->cnr, ctr->name); + +unlock_out: + mutex_unlock(&capi_controller_lock); + + return err; +} + +EXPORT_SYMBOL(detach_capi_ctr); + +/** + * register_capi_driver() - register CAPI driver + * @driver: driver descriptor structure. + * + * Called by hardware driver to register itself with the CAPI subsystem. + */ + +void register_capi_driver(struct capi_driver *driver) +{ + mutex_lock(&capi_drivers_lock); + list_add_tail(&driver->list, &capi_drivers); + mutex_unlock(&capi_drivers_lock); +} + +EXPORT_SYMBOL(register_capi_driver); + +/** + * unregister_capi_driver() - unregister CAPI driver + * @driver: driver descriptor structure. + * + * Called by hardware driver to unregister itself from the CAPI subsystem. + */ + +void unregister_capi_driver(struct capi_driver *driver) +{ + mutex_lock(&capi_drivers_lock); + list_del(&driver->list); + mutex_unlock(&capi_drivers_lock); +} + +EXPORT_SYMBOL(unregister_capi_driver); + +/* ------------------------------------------------------------- */ +/* -------- CAPI2.0 Interface ---------------------------------- */ +/* ------------------------------------------------------------- */ + +/** + * capi20_isinstalled() - CAPI 2.0 operation CAPI_INSTALLED + * + * Return value: CAPI result code (CAPI_NOERROR if at least one ISDN controller + * is ready for use, CAPI_REGNOTINSTALLED otherwise) + */ + +u16 capi20_isinstalled(void) +{ + u16 ret = CAPI_REGNOTINSTALLED; + int i; + + mutex_lock(&capi_controller_lock); + + for (i = 0; i < CAPI_MAXCONTR; i++) + if (capi_controller[i] && + capi_controller[i]->state == CAPI_CTR_RUNNING) { + ret = CAPI_NOERROR; + break; + } + + mutex_unlock(&capi_controller_lock); + + return ret; +} + +EXPORT_SYMBOL(capi20_isinstalled); + +/** + * capi20_register() - CAPI 2.0 operation CAPI_REGISTER + * @ap: CAPI application descriptor structure. + * + * Register an application's presence with CAPI. + * A unique application ID is assigned and stored in @ap->applid. + * After this function returns successfully, the message receive + * callback function @ap->recv_message() may be called at any time + * until capi20_release() has been called for the same @ap. + * Return value: CAPI result code + */ + +u16 capi20_register(struct capi20_appl *ap) +{ + int i; + u16 applid; + + DBG(""); + + if (ap->rparam.datablklen < 128) + return CAPI_LOGBLKSIZETOSMALL; + + ap->nrecvctlpkt = 0; + ap->nrecvdatapkt = 0; + ap->nsentctlpkt = 0; + ap->nsentdatapkt = 0; + mutex_init(&ap->recv_mtx); + skb_queue_head_init(&ap->recv_queue); + INIT_WORK(&ap->recv_work, recv_handler); + ap->release_in_progress = 0; + + mutex_lock(&capi_controller_lock); + + for (applid = 1; applid <= CAPI_MAXAPPL; applid++) { + if (capi_applications[applid - 1] == NULL) + break; + } + if (applid > CAPI_MAXAPPL) { + mutex_unlock(&capi_controller_lock); + return CAPI_TOOMANYAPPLS; + } + + ap->applid = applid; + capi_applications[applid - 1] = ap; + + for (i = 0; i < CAPI_MAXCONTR; i++) { + if (!capi_controller[i] || + capi_controller[i]->state != CAPI_CTR_RUNNING) + continue; + register_appl(capi_controller[i], applid, &ap->rparam); + } + + mutex_unlock(&capi_controller_lock); + + if (showcapimsgs & 1) { + printk(KERN_DEBUG "kcapi: appl %d up\n", applid); + } + + return CAPI_NOERROR; +} + +EXPORT_SYMBOL(capi20_register); + +/** + * capi20_release() - CAPI 2.0 operation CAPI_RELEASE + * @ap: CAPI application descriptor structure. + * + * Terminate an application's registration with CAPI. + * After this function returns successfully, the message receive + * callback function @ap->recv_message() will no longer be called. + * Return value: CAPI result code + */ + +u16 capi20_release(struct capi20_appl *ap) +{ + int i; + + DBG("applid %#x", ap->applid); + + mutex_lock(&capi_controller_lock); + + ap->release_in_progress = 1; + capi_applications[ap->applid - 1] = NULL; + + synchronize_rcu(); + + for (i = 0; i < CAPI_MAXCONTR; i++) { + if (!capi_controller[i] || + capi_controller[i]->state != CAPI_CTR_RUNNING) + continue; + release_appl(capi_controller[i], ap->applid); + } + + mutex_unlock(&capi_controller_lock); + + flush_workqueue(kcapi_wq); + skb_queue_purge(&ap->recv_queue); + + if (showcapimsgs & 1) { + printk(KERN_DEBUG "kcapi: appl %d down\n", ap->applid); + } + + return CAPI_NOERROR; +} + +EXPORT_SYMBOL(capi20_release); + +/** + * capi20_put_message() - CAPI 2.0 operation CAPI_PUT_MESSAGE + * @ap: CAPI application descriptor structure. + * @skb: CAPI message. + * + * Transfer a single message to CAPI. + * Return value: CAPI result code + */ + +u16 capi20_put_message(struct capi20_appl *ap, struct sk_buff *skb) +{ + struct capi_ctr *ctr; + int showctl = 0; + u8 cmd, subcmd; + + DBG("applid %#x", ap->applid); + + if (ncontrollers == 0) + return CAPI_REGNOTINSTALLED; + if ((ap->applid == 0) || ap->release_in_progress) + return CAPI_ILLAPPNR; + if (skb->len < 12 + || !capi_cmd_valid(CAPIMSG_COMMAND(skb->data)) + || !capi_subcmd_valid(CAPIMSG_SUBCOMMAND(skb->data))) + return CAPI_ILLCMDORSUBCMDORMSGTOSMALL; + + /* + * The controller reference is protected by the existence of the + * application passed to us. We assume that the caller properly + * synchronizes this service with capi20_release. + */ + ctr = get_capi_ctr_by_nr(CAPIMSG_CONTROLLER(skb->data)); + if (!ctr || ctr->state != CAPI_CTR_RUNNING) + return CAPI_REGNOTINSTALLED; + if (ctr->blocked) + return CAPI_SENDQUEUEFULL; + + cmd = CAPIMSG_COMMAND(skb->data); + subcmd = CAPIMSG_SUBCOMMAND(skb->data); + + if (cmd == CAPI_DATA_B3 && subcmd == CAPI_REQ) { + ctr->nsentdatapkt++; + ap->nsentdatapkt++; + if (ctr->traceflag > 2) + showctl |= 2; + } else { + ctr->nsentctlpkt++; + ap->nsentctlpkt++; + if (ctr->traceflag) + showctl |= 2; + } + showctl |= (ctr->traceflag & 1); + if (showctl & 2) { + if (showctl & 1) { + printk(KERN_DEBUG "kcapi: put [%03d] id#%d %s len=%u\n", + CAPIMSG_CONTROLLER(skb->data), + CAPIMSG_APPID(skb->data), + capi_cmd2str(cmd, subcmd), + CAPIMSG_LEN(skb->data)); + } else { + _cdebbuf *cdb = capi_message2str(skb->data); + if (cdb) { + printk(KERN_DEBUG "kcapi: put [%03d] %s\n", + CAPIMSG_CONTROLLER(skb->data), + cdb->buf); + cdebbuf_free(cdb); + } else + printk(KERN_DEBUG "kcapi: put [%03d] id#%d %s len=%u cannot trace\n", + CAPIMSG_CONTROLLER(skb->data), + CAPIMSG_APPID(skb->data), + capi_cmd2str(cmd, subcmd), + CAPIMSG_LEN(skb->data)); + } + } + return ctr->send_message(ctr, skb); +} + +EXPORT_SYMBOL(capi20_put_message); + +/** + * capi20_get_manufacturer() - CAPI 2.0 operation CAPI_GET_MANUFACTURER + * @contr: controller number. + * @buf: result buffer (64 bytes). + * + * Retrieve information about the manufacturer of the specified ISDN controller + * or (for @contr == 0) the driver itself. + * Return value: CAPI result code + */ + +u16 capi20_get_manufacturer(u32 contr, u8 *buf) +{ + struct capi_ctr *ctr; + u16 ret; + + if (contr == 0) { + strlcpy(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) { + strlcpy(buf, ctr->manu, CAPI_MANUFACTURER_LEN); + ret = CAPI_NOERROR; + } else + ret = CAPI_REGNOTINSTALLED; + + mutex_unlock(&capi_controller_lock); + return ret; +} + +EXPORT_SYMBOL(capi20_get_manufacturer); + +/** + * capi20_get_version() - CAPI 2.0 operation CAPI_GET_VERSION + * @contr: controller number. + * @verp: result structure. + * + * Retrieve version information for the specified ISDN controller + * or (for @contr == 0) the driver itself. + * Return value: CAPI result code + */ + +u16 capi20_get_version(u32 contr, struct capi_version *verp) +{ + struct capi_ctr *ctr; + u16 ret; + + if (contr == 0) { + *verp = driver_version; + return CAPI_NOERROR; + } + + mutex_lock(&capi_controller_lock); + + ctr = get_capi_ctr_by_nr(contr); + if (ctr && ctr->state == CAPI_CTR_RUNNING) { + memcpy(verp, &ctr->version, sizeof(capi_version)); + ret = CAPI_NOERROR; + } else + ret = CAPI_REGNOTINSTALLED; + + mutex_unlock(&capi_controller_lock); + return ret; +} + +EXPORT_SYMBOL(capi20_get_version); + +/** + * capi20_get_serial() - CAPI 2.0 operation CAPI_GET_SERIAL_NUMBER + * @contr: controller number. + * @serial: result buffer (8 bytes). + * + * Retrieve the serial number of the specified ISDN controller + * or (for @contr == 0) the driver itself. + * Return value: CAPI result code + */ + +u16 capi20_get_serial(u32 contr, u8 *serial) +{ + struct capi_ctr *ctr; + u16 ret; + + if (contr == 0) { + strlcpy(serial, driver_serial, CAPI_SERIAL_LEN); + return CAPI_NOERROR; + } + + mutex_lock(&capi_controller_lock); + + ctr = get_capi_ctr_by_nr(contr); + if (ctr && ctr->state == CAPI_CTR_RUNNING) { + strlcpy(serial, ctr->serial, CAPI_SERIAL_LEN); + ret = CAPI_NOERROR; + } else + ret = CAPI_REGNOTINSTALLED; + + mutex_unlock(&capi_controller_lock); + return ret; +} + +EXPORT_SYMBOL(capi20_get_serial); + +/** + * capi20_get_profile() - CAPI 2.0 operation CAPI_GET_PROFILE + * @contr: controller number. + * @profp: result structure. + * + * Retrieve capability information for the specified ISDN controller + * or (for @contr == 0) the number of installed controllers. + * Return value: CAPI result code + */ + +u16 capi20_get_profile(u32 contr, struct capi_profile *profp) +{ + struct capi_ctr *ctr; + u16 ret; + + if (contr == 0) { + profp->ncontroller = ncontrollers; + return CAPI_NOERROR; + } + + mutex_lock(&capi_controller_lock); + + ctr = get_capi_ctr_by_nr(contr); + if (ctr && ctr->state == CAPI_CTR_RUNNING) { + memcpy(profp, &ctr->profile, sizeof(struct capi_profile)); + ret = CAPI_NOERROR; + } else + ret = CAPI_REGNOTINSTALLED; + + mutex_unlock(&capi_controller_lock); + return ret; +} + +EXPORT_SYMBOL(capi20_get_profile); + +/* Must be called with capi_controller_lock held. */ +static int wait_on_ctr_state(struct capi_ctr *ctr, unsigned int state) +{ + DEFINE_WAIT(wait); + int retval = 0; + + ctr = capi_ctr_get(ctr); + if (!ctr) + return -ESRCH; + + for (;;) { + prepare_to_wait(&ctr->state_wait_queue, &wait, + TASK_INTERRUPTIBLE); + + if (ctr->state == state) + break; + if (ctr->state == CAPI_CTR_DETACHED) { + retval = -ESRCH; + break; + } + if (signal_pending(current)) { + retval = -EINTR; + break; + } + + mutex_unlock(&capi_controller_lock); + schedule(); + mutex_lock(&capi_controller_lock); + } + finish_wait(&ctr->state_wait_queue, &wait); + + capi_ctr_put(ctr); + + return retval; +} + +#ifdef AVMB1_COMPAT +static int old_capi_manufacturer(unsigned int cmd, void __user *data) +{ + avmb1_loadandconfigdef ldef; + avmb1_extcarddef cdef; + avmb1_resetdef rdef; + capicardparams cparams; + struct capi_ctr *ctr; + struct capi_driver *driver = NULL; + capiloaddata ldata; + struct list_head *l; + int retval; + + switch (cmd) { + case AVMB1_ADDCARD: + case AVMB1_ADDCARD_WITH_TYPE: + if (cmd == AVMB1_ADDCARD) { + if ((retval = copy_from_user(&cdef, data, + sizeof(avmb1_carddef)))) + return -EFAULT; + cdef.cardtype = AVM_CARDTYPE_B1; + } else { + if ((retval = copy_from_user(&cdef, data, + sizeof(avmb1_extcarddef)))) + return -EFAULT; + } + cparams.port = cdef.port; + cparams.irq = cdef.irq; + cparams.cardnr = cdef.cardnr; + + mutex_lock(&capi_drivers_lock); + + switch (cdef.cardtype) { + case AVM_CARDTYPE_B1: + list_for_each(l, &capi_drivers) { + driver = list_entry(l, struct capi_driver, list); + if (strcmp(driver->name, "b1isa") == 0) + break; + } + break; + case AVM_CARDTYPE_T1: + list_for_each(l, &capi_drivers) { + driver = list_entry(l, struct capi_driver, list); + if (strcmp(driver->name, "t1isa") == 0) + break; + } + break; + default: + driver = NULL; + break; + } + if (!driver) { + printk(KERN_ERR "kcapi: driver not loaded.\n"); + retval = -EIO; + } else if (!driver->add_card) { + printk(KERN_ERR "kcapi: driver has no add card function.\n"); + retval = -EIO; + } else + retval = driver->add_card(driver, &cparams); + + mutex_unlock(&capi_drivers_lock); + return retval; + + case AVMB1_LOAD: + case AVMB1_LOAD_AND_CONFIG: + + if (cmd == AVMB1_LOAD) { + if (copy_from_user(&ldef, data, + sizeof(avmb1_loaddef))) + return -EFAULT; + ldef.t4config.len = 0; + ldef.t4config.data = NULL; + } else { + if (copy_from_user(&ldef, data, + sizeof(avmb1_loadandconfigdef))) + return -EFAULT; + } + + mutex_lock(&capi_controller_lock); + + ctr = get_capi_ctr_by_nr(ldef.contr); + if (!ctr) { + retval = -EINVAL; + goto load_unlock_out; + } + + if (ctr->load_firmware == NULL) { + printk(KERN_DEBUG "kcapi: load: no load function\n"); + retval = -ESRCH; + goto load_unlock_out; + } + + if (ldef.t4file.len <= 0) { + printk(KERN_DEBUG "kcapi: load: invalid parameter: length of t4file is %d ?\n", ldef.t4file.len); + retval = -EINVAL; + goto load_unlock_out; + } + if (ldef.t4file.data == NULL) { + printk(KERN_DEBUG "kcapi: load: invalid parameter: dataptr is 0\n"); + retval = -EINVAL; + goto load_unlock_out; + } + + ldata.firmware.user = 1; + ldata.firmware.data = ldef.t4file.data; + ldata.firmware.len = ldef.t4file.len; + ldata.configuration.user = 1; + ldata.configuration.data = ldef.t4config.data; + ldata.configuration.len = ldef.t4config.len; + + if (ctr->state != CAPI_CTR_DETECTED) { + printk(KERN_INFO "kcapi: load: contr=%d not in detect state\n", ldef.contr); + retval = -EBUSY; + goto load_unlock_out; + } + ctr->state = CAPI_CTR_LOADING; + + retval = ctr->load_firmware(ctr, &ldata); + if (retval) { + ctr->state = CAPI_CTR_DETECTED; + goto load_unlock_out; + } + + retval = wait_on_ctr_state(ctr, CAPI_CTR_RUNNING); + + load_unlock_out: + mutex_unlock(&capi_controller_lock); + return retval; + + case AVMB1_RESETCARD: + if (copy_from_user(&rdef, data, sizeof(avmb1_resetdef))) + return -EFAULT; + + retval = 0; + + mutex_lock(&capi_controller_lock); + + ctr = get_capi_ctr_by_nr(rdef.contr); + if (!ctr) { + retval = -ESRCH; + goto reset_unlock_out; + } + + if (ctr->state == CAPI_CTR_DETECTED) + goto reset_unlock_out; + + if (ctr->reset_ctr == NULL) { + printk(KERN_DEBUG "kcapi: reset: no reset function\n"); + retval = -ESRCH; + goto reset_unlock_out; + } + + ctr->reset_ctr(ctr); + + retval = wait_on_ctr_state(ctr, CAPI_CTR_DETECTED); + + reset_unlock_out: + mutex_unlock(&capi_controller_lock); + return retval; + } + return -EINVAL; +} +#endif + +/** + * capi20_manufacturer() - CAPI 2.0 operation CAPI_MANUFACTURER + * @cmd: command. + * @data: parameter. + * + * Perform manufacturer specific command. + * Return value: CAPI result code + */ + +int capi20_manufacturer(unsigned int cmd, void __user *data) +{ + struct capi_ctr *ctr; + int retval; + + switch (cmd) { +#ifdef AVMB1_COMPAT + case AVMB1_LOAD: + case AVMB1_LOAD_AND_CONFIG: + case AVMB1_RESETCARD: + case AVMB1_GET_CARDINFO: + case AVMB1_REMOVECARD: + return old_capi_manufacturer(cmd, data); +#endif + case KCAPI_CMD_TRACE: + { + kcapi_flagdef fdef; + + if (copy_from_user(&fdef, data, sizeof(kcapi_flagdef))) + return -EFAULT; + + mutex_lock(&capi_controller_lock); + + ctr = get_capi_ctr_by_nr(fdef.contr); + if (ctr) { + ctr->traceflag = fdef.flag; + printk(KERN_INFO "kcapi: contr [%03d] set trace=%d\n", + ctr->cnr, ctr->traceflag); + retval = 0; + } else + retval = -ESRCH; + + mutex_unlock(&capi_controller_lock); + + return retval; + } + case KCAPI_CMD_ADDCARD: + { + struct list_head *l; + struct capi_driver *driver = NULL; + capicardparams cparams; + kcapi_carddef cdef; + + if ((retval = copy_from_user(&cdef, data, sizeof(cdef)))) + return -EFAULT; + + cparams.port = cdef.port; + cparams.irq = cdef.irq; + cparams.membase = cdef.membase; + cparams.cardnr = cdef.cardnr; + cparams.cardtype = 0; + cdef.driver[sizeof(cdef.driver) - 1] = 0; + + mutex_lock(&capi_drivers_lock); + + list_for_each(l, &capi_drivers) { + driver = list_entry(l, struct capi_driver, list); + if (strcmp(driver->name, cdef.driver) == 0) + break; + } + if (driver == NULL) { + printk(KERN_ERR "kcapi: driver \"%s\" not loaded.\n", + cdef.driver); + retval = -ESRCH; + } else if (!driver->add_card) { + printk(KERN_ERR "kcapi: driver \"%s\" has no add card function.\n", cdef.driver); + retval = -EIO; + } else + retval = driver->add_card(driver, &cparams); + + mutex_unlock(&capi_drivers_lock); + return retval; + } + + default: + printk(KERN_ERR "kcapi: manufacturer command %d unknown.\n", + cmd); + break; + + } + return -EINVAL; +} + +EXPORT_SYMBOL(capi20_manufacturer); + +/* ------------------------------------------------------------- */ +/* -------- Init & Cleanup ------------------------------------- */ +/* ------------------------------------------------------------- */ + +/* + * init / exit functions + */ + +static struct notifier_block capictr_nb = { + .notifier_call = notify_handler, + .priority = INT_MAX, +}; + +static int __init kcapi_init(void) +{ + int err; + + kcapi_wq = alloc_workqueue("kcapi", 0, 0); + if (!kcapi_wq) + return -ENOMEM; + + register_capictr_notifier(&capictr_nb); + + err = cdebug_init(); + if (err) { + unregister_capictr_notifier(&capictr_nb); + destroy_workqueue(kcapi_wq); + return err; + } + + kcapi_proc_init(); + return 0; +} + +static void __exit kcapi_exit(void) +{ + kcapi_proc_exit(); + + unregister_capictr_notifier(&capictr_nb); + cdebug_exit(); + destroy_workqueue(kcapi_wq); +} + +module_init(kcapi_init); +module_exit(kcapi_exit); |