mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-18 09:44:18 +08:00
9ea8aa8d50
Function capi20_manufacturer() is declared with unsigned int cmd argument but called with unsigned long. Fix by correcting the function prototype since the actual argument is part of the user visible API. Spotted with Coverity. Signed-off-by: Tilman Schmidt <tilman@imap.cc> Signed-off-by: David S. Miller <davem@davemloft.net>
1317 lines
30 KiB
C
1317 lines
30 KiB
C
/* $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 || contr - 1 >= CAPI_MAXCONTR)
|
|
return NULL;
|
|
|
|
return capi_controller[contr - 1];
|
|
}
|
|
|
|
static inline struct capi20_appl *__get_capi_appl_by_nr(u16 applid)
|
|
{
|
|
lockdep_assert_held(&capi_controller_lock);
|
|
|
|
if (applid < 1 || applid - 1 >= CAPI_MAXAPPL)
|
|
return NULL;
|
|
|
|
return capi_applications[applid - 1];
|
|
}
|
|
|
|
static inline struct capi20_appl *get_capi_appl_by_nr(u16 applid)
|
|
{
|
|
if (applid < 1 || applid - 1 >= CAPI_MAXAPPL)
|
|
return NULL;
|
|
|
|
return rcu_dereference(capi_applications[applid - 1]);
|
|
}
|
|
|
|
/* -------- util functions ------------------------------------ */
|
|
|
|
static inline int capi_cmd_valid(u8 cmd)
|
|
{
|
|
switch (cmd) {
|
|
case CAPI_ALERT:
|
|
case CAPI_CONNECT:
|
|
case CAPI_CONNECT_ACTIVE:
|
|
case CAPI_CONNECT_B3_ACTIVE:
|
|
case CAPI_CONNECT_B3:
|
|
case CAPI_CONNECT_B3_T90_ACTIVE:
|
|
case CAPI_DATA_B3:
|
|
case CAPI_DISCONNECT_B3:
|
|
case CAPI_DISCONNECT:
|
|
case CAPI_FACILITY:
|
|
case CAPI_INFO:
|
|
case CAPI_LISTEN:
|
|
case CAPI_MANUFACTURER:
|
|
case CAPI_RESET_B3:
|
|
case CAPI_SELECT_B_PROTOCOL:
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static inline int capi_subcmd_valid(u8 subcmd)
|
|
{
|
|
switch (subcmd) {
|
|
case CAPI_REQ:
|
|
case CAPI_CONF:
|
|
case CAPI_IND:
|
|
case CAPI_RESP:
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* ------------------------------------------------------------ */
|
|
|
|
static void
|
|
register_appl(struct capi_ctr *ctr, u16 applid, capi_register_params *rparam)
|
|
{
|
|
ctr = capi_ctr_get(ctr);
|
|
|
|
if (ctr)
|
|
ctr->register_appl(ctr, applid, rparam);
|
|
else
|
|
printk(KERN_WARNING "%s: cannot get controller resources\n",
|
|
__func__);
|
|
}
|
|
|
|
|
|
static void release_appl(struct capi_ctr *ctr, u16 applid)
|
|
{
|
|
DBG("applid %#x", applid);
|
|
|
|
ctr->release_appl(ctr, applid);
|
|
capi_ctr_put(ctr);
|
|
}
|
|
|
|
static void notify_up(u32 contr)
|
|
{
|
|
struct capi20_appl *ap;
|
|
struct capi_ctr *ctr;
|
|
u16 applid;
|
|
|
|
mutex_lock(&capi_controller_lock);
|
|
|
|
if (showcapimsgs & 1)
|
|
printk(KERN_DEBUG "kcapi: notify up contr %d\n", contr);
|
|
|
|
ctr = get_capi_ctr_by_nr(contr);
|
|
if (ctr) {
|
|
if (ctr->state == CAPI_CTR_RUNNING)
|
|
goto unlock_out;
|
|
|
|
ctr->state = CAPI_CTR_RUNNING;
|
|
|
|
for (applid = 1; applid <= CAPI_MAXAPPL; applid++) {
|
|
ap = __get_capi_appl_by_nr(applid);
|
|
if (ap)
|
|
register_appl(ctr, applid, &ap->rparam);
|
|
}
|
|
|
|
wake_up_interruptible_all(&ctr->state_wait_queue);
|
|
} else
|
|
printk(KERN_WARNING "%s: invalid contr %d\n", __func__, contr);
|
|
|
|
unlock_out:
|
|
mutex_unlock(&capi_controller_lock);
|
|
}
|
|
|
|
static void ctr_down(struct capi_ctr *ctr, int new_state)
|
|
{
|
|
struct capi20_appl *ap;
|
|
u16 applid;
|
|
|
|
if (ctr->state == CAPI_CTR_DETECTED || ctr->state == CAPI_CTR_DETACHED)
|
|
return;
|
|
|
|
ctr->state = new_state;
|
|
|
|
memset(ctr->manu, 0, sizeof(ctr->manu));
|
|
memset(&ctr->version, 0, sizeof(ctr->version));
|
|
memset(&ctr->profile, 0, sizeof(ctr->profile));
|
|
memset(ctr->serial, 0, sizeof(ctr->serial));
|
|
|
|
for (applid = 1; applid <= CAPI_MAXAPPL; applid++) {
|
|
ap = __get_capi_appl_by_nr(applid);
|
|
if (ap)
|
|
capi_ctr_put(ctr);
|
|
}
|
|
|
|
wake_up_interruptible_all(&ctr->state_wait_queue);
|
|
}
|
|
|
|
static void notify_down(u32 contr)
|
|
{
|
|
struct capi_ctr *ctr;
|
|
|
|
mutex_lock(&capi_controller_lock);
|
|
|
|
if (showcapimsgs & 1)
|
|
printk(KERN_DEBUG "kcapi: notify down contr %d\n", contr);
|
|
|
|
ctr = get_capi_ctr_by_nr(contr);
|
|
if (ctr)
|
|
ctr_down(ctr, CAPI_CTR_DETECTED);
|
|
else
|
|
printk(KERN_WARNING "%s: invalid contr %d\n", __func__, contr);
|
|
|
|
mutex_unlock(&capi_controller_lock);
|
|
}
|
|
|
|
static int
|
|
notify_handler(struct notifier_block *nb, unsigned long val, void *v)
|
|
{
|
|
u32 contr = (long)v;
|
|
|
|
switch (val) {
|
|
case CAPICTR_UP:
|
|
notify_up(contr);
|
|
break;
|
|
case CAPICTR_DOWN:
|
|
notify_down(contr);
|
|
break;
|
|
}
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
static void do_notify_work(struct work_struct *work)
|
|
{
|
|
struct capictr_event *event =
|
|
container_of(work, struct capictr_event, work);
|
|
|
|
blocking_notifier_call_chain(&ctr_notifier_list, event->type,
|
|
(void *)(long)event->controller);
|
|
kfree(event);
|
|
}
|
|
|
|
/*
|
|
* The notifier will result in adding/deleteing of devices. Devices can
|
|
* only removed in user process, not in bh.
|
|
*/
|
|
static int notify_push(unsigned int event_type, u32 controller)
|
|
{
|
|
struct capictr_event *event = kmalloc(sizeof(*event), GFP_ATOMIC);
|
|
|
|
if (!event)
|
|
return -ENOMEM;
|
|
|
|
INIT_WORK(&event->work, do_notify_work);
|
|
event->type = event_type;
|
|
event->controller = controller;
|
|
|
|
queue_work(kcapi_wq, &event->work);
|
|
return 0;
|
|
}
|
|
|
|
int register_capictr_notifier(struct notifier_block *nb)
|
|
{
|
|
return blocking_notifier_chain_register(&ctr_notifier_list, nb);
|
|
}
|
|
EXPORT_SYMBOL_GPL(register_capictr_notifier);
|
|
|
|
int unregister_capictr_notifier(struct notifier_block *nb)
|
|
{
|
|
return blocking_notifier_chain_unregister(&ctr_notifier_list, nb);
|
|
}
|
|
EXPORT_SYMBOL_GPL(unregister_capictr_notifier);
|
|
|
|
/* -------- Receiver ------------------------------------------ */
|
|
|
|
static void recv_handler(struct work_struct *work)
|
|
{
|
|
struct sk_buff *skb;
|
|
struct capi20_appl *ap =
|
|
container_of(work, struct capi20_appl, recv_work);
|
|
|
|
if ((!ap) || (ap->release_in_progress))
|
|
return;
|
|
|
|
mutex_lock(&ap->recv_mtx);
|
|
while ((skb = skb_dequeue(&ap->recv_queue))) {
|
|
if (CAPIMSG_CMD(skb->data) == CAPI_DATA_B3_IND)
|
|
ap->nrecvdatapkt++;
|
|
else
|
|
ap->nrecvctlpkt++;
|
|
|
|
ap->recv_message(ap, skb);
|
|
}
|
|
mutex_unlock(&ap->recv_mtx);
|
|
}
|
|
|
|
/**
|
|
* capi_ctr_handle_message() - handle incoming CAPI message
|
|
* @ctr: controller descriptor structure.
|
|
* @appl: application ID.
|
|
* @skb: message.
|
|
*
|
|
* Called by hardware driver to pass a CAPI message to the application.
|
|
*/
|
|
|
|
void capi_ctr_handle_message(struct capi_ctr *ctr, u16 appl,
|
|
struct sk_buff *skb)
|
|
{
|
|
struct capi20_appl *ap;
|
|
int showctl = 0;
|
|
u8 cmd, subcmd;
|
|
_cdebbuf *cdb;
|
|
|
|
if (ctr->state != CAPI_CTR_RUNNING) {
|
|
cdb = capi_message2str(skb->data);
|
|
if (cdb) {
|
|
printk(KERN_INFO "kcapi: controller [%03d] not active, got: %s",
|
|
ctr->cnr, cdb->buf);
|
|
cdebbuf_free(cdb);
|
|
} else
|
|
printk(KERN_INFO "kcapi: controller [%03d] not active, cannot trace\n",
|
|
ctr->cnr);
|
|
goto error;
|
|
}
|
|
|
|
cmd = CAPIMSG_COMMAND(skb->data);
|
|
subcmd = CAPIMSG_SUBCOMMAND(skb->data);
|
|
if (cmd == CAPI_DATA_B3 && subcmd == CAPI_IND) {
|
|
ctr->nrecvdatapkt++;
|
|
if (ctr->traceflag > 2)
|
|
showctl |= 2;
|
|
} else {
|
|
ctr->nrecvctlpkt++;
|
|
if (ctr->traceflag)
|
|
showctl |= 2;
|
|
}
|
|
showctl |= (ctr->traceflag & 1);
|
|
if (showctl & 2) {
|
|
if (showctl & 1) {
|
|
printk(KERN_DEBUG "kcapi: got [%03d] id#%d %s len=%u\n",
|
|
ctr->cnr, CAPIMSG_APPID(skb->data),
|
|
capi_cmd2str(cmd, subcmd),
|
|
CAPIMSG_LEN(skb->data));
|
|
} else {
|
|
cdb = capi_message2str(skb->data);
|
|
if (cdb) {
|
|
printk(KERN_DEBUG "kcapi: got [%03d] %s\n",
|
|
ctr->cnr, cdb->buf);
|
|
cdebbuf_free(cdb);
|
|
} else
|
|
printk(KERN_DEBUG "kcapi: got [%03d] id#%d %s len=%u, cannot trace\n",
|
|
ctr->cnr, CAPIMSG_APPID(skb->data),
|
|
capi_cmd2str(cmd, subcmd),
|
|
CAPIMSG_LEN(skb->data));
|
|
}
|
|
|
|
}
|
|
|
|
rcu_read_lock();
|
|
ap = get_capi_appl_by_nr(CAPIMSG_APPID(skb->data));
|
|
if (!ap) {
|
|
rcu_read_unlock();
|
|
cdb = capi_message2str(skb->data);
|
|
if (cdb) {
|
|
printk(KERN_ERR "kcapi: handle_message: applid %d state released (%s)\n",
|
|
CAPIMSG_APPID(skb->data), cdb->buf);
|
|
cdebbuf_free(cdb);
|
|
} else
|
|
printk(KERN_ERR "kcapi: handle_message: applid %d state released (%s) cannot trace\n",
|
|
CAPIMSG_APPID(skb->data),
|
|
capi_cmd2str(cmd, subcmd));
|
|
goto error;
|
|
}
|
|
skb_queue_tail(&ap->recv_queue, skb);
|
|
queue_work(kcapi_wq, &ap->recv_work);
|
|
rcu_read_unlock();
|
|
|
|
return;
|
|
|
|
error:
|
|
kfree_skb(skb);
|
|
}
|
|
|
|
EXPORT_SYMBOL(capi_ctr_handle_message);
|
|
|
|
/**
|
|
* capi_ctr_ready() - signal CAPI controller ready
|
|
* @ctr: controller descriptor structure.
|
|
*
|
|
* Called by hardware driver to signal that the controller is up and running.
|
|
*/
|
|
|
|
void capi_ctr_ready(struct capi_ctr *ctr)
|
|
{
|
|
printk(KERN_NOTICE "kcapi: controller [%03d] \"%s\" ready.\n",
|
|
ctr->cnr, ctr->name);
|
|
|
|
notify_push(CAPICTR_UP, ctr->cnr);
|
|
}
|
|
|
|
EXPORT_SYMBOL(capi_ctr_ready);
|
|
|
|
/**
|
|
* capi_ctr_down() - signal CAPI controller not ready
|
|
* @ctr: controller descriptor structure.
|
|
*
|
|
* Called by hardware driver to signal that the controller is down and
|
|
* unavailable for use.
|
|
*/
|
|
|
|
void capi_ctr_down(struct capi_ctr *ctr)
|
|
{
|
|
printk(KERN_NOTICE "kcapi: controller [%03d] down.\n", ctr->cnr);
|
|
|
|
notify_push(CAPICTR_DOWN, ctr->cnr);
|
|
}
|
|
|
|
EXPORT_SYMBOL(capi_ctr_down);
|
|
|
|
/**
|
|
* capi_ctr_suspend_output() - suspend controller
|
|
* @ctr: controller descriptor structure.
|
|
*
|
|
* Called by hardware driver to stop data flow.
|
|
*
|
|
* Note: The caller is responsible for synchronizing concurrent state changes
|
|
* as well as invocations of capi_ctr_handle_message.
|
|
*/
|
|
|
|
void capi_ctr_suspend_output(struct capi_ctr *ctr)
|
|
{
|
|
if (!ctr->blocked) {
|
|
printk(KERN_DEBUG "kcapi: controller [%03d] suspend\n",
|
|
ctr->cnr);
|
|
ctr->blocked = 1;
|
|
}
|
|
}
|
|
|
|
EXPORT_SYMBOL(capi_ctr_suspend_output);
|
|
|
|
/**
|
|
* capi_ctr_resume_output() - resume controller
|
|
* @ctr: controller descriptor structure.
|
|
*
|
|
* Called by hardware driver to resume data flow.
|
|
*
|
|
* Note: The caller is responsible for synchronizing concurrent state changes
|
|
* as well as invocations of capi_ctr_handle_message.
|
|
*/
|
|
|
|
void capi_ctr_resume_output(struct capi_ctr *ctr)
|
|
{
|
|
if (ctr->blocked) {
|
|
printk(KERN_DEBUG "kcapi: controller [%03d] resumed\n",
|
|
ctr->cnr);
|
|
ctr->blocked = 0;
|
|
}
|
|
}
|
|
|
|
EXPORT_SYMBOL(capi_ctr_resume_output);
|
|
|
|
/* ------------------------------------------------------------- */
|
|
|
|
/**
|
|
* attach_capi_ctr() - register CAPI controller
|
|
* @ctr: controller descriptor structure.
|
|
*
|
|
* Called by hardware driver to register a controller with the CAPI subsystem.
|
|
* Return value: 0 on success, error code < 0 on error
|
|
*/
|
|
|
|
int attach_capi_ctr(struct capi_ctr *ctr)
|
|
{
|
|
int i;
|
|
|
|
mutex_lock(&capi_controller_lock);
|
|
|
|
for (i = 0; i < CAPI_MAXCONTR; i++) {
|
|
if (!capi_controller[i])
|
|
break;
|
|
}
|
|
if (i == CAPI_MAXCONTR) {
|
|
mutex_unlock(&capi_controller_lock);
|
|
printk(KERN_ERR "kcapi: out of controller slots\n");
|
|
return -EBUSY;
|
|
}
|
|
capi_controller[i] = ctr;
|
|
|
|
ctr->nrecvctlpkt = 0;
|
|
ctr->nrecvdatapkt = 0;
|
|
ctr->nsentctlpkt = 0;
|
|
ctr->nsentdatapkt = 0;
|
|
ctr->cnr = i + 1;
|
|
ctr->state = CAPI_CTR_DETECTED;
|
|
ctr->blocked = 0;
|
|
ctr->traceflag = showcapimsgs;
|
|
init_waitqueue_head(&ctr->state_wait_queue);
|
|
|
|
sprintf(ctr->procfn, "capi/controllers/%d", ctr->cnr);
|
|
ctr->procent = proc_create_data(ctr->procfn, 0, NULL, ctr->proc_fops, ctr);
|
|
|
|
ncontrollers++;
|
|
|
|
mutex_unlock(&capi_controller_lock);
|
|
|
|
printk(KERN_NOTICE "kcapi: controller [%03d]: %s attached\n",
|
|
ctr->cnr, ctr->name);
|
|
return 0;
|
|
}
|
|
|
|
EXPORT_SYMBOL(attach_capi_ctr);
|
|
|
|
/**
|
|
* detach_capi_ctr() - unregister CAPI controller
|
|
* @ctr: controller descriptor structure.
|
|
*
|
|
* Called by hardware driver to remove the registration of a controller
|
|
* with the CAPI subsystem.
|
|
* Return value: 0 on success, error code < 0 on error
|
|
*/
|
|
|
|
int detach_capi_ctr(struct capi_ctr *ctr)
|
|
{
|
|
int err = 0;
|
|
|
|
mutex_lock(&capi_controller_lock);
|
|
|
|
ctr_down(ctr, CAPI_CTR_DETACHED);
|
|
|
|
if (capi_controller[ctr->cnr - 1] != ctr) {
|
|
err = -EINVAL;
|
|
goto unlock_out;
|
|
}
|
|
capi_controller[ctr->cnr - 1] = NULL;
|
|
ncontrollers--;
|
|
|
|
if (ctr->procent)
|
|
remove_proc_entry(ctr->procfn, NULL);
|
|
|
|
printk(KERN_NOTICE "kcapi: controller [%03d]: %s unregistered\n",
|
|
ctr->cnr, ctr->name);
|
|
|
|
unlock_out:
|
|
mutex_unlock(&capi_controller_lock);
|
|
|
|
return err;
|
|
}
|
|
|
|
EXPORT_SYMBOL(detach_capi_ctr);
|
|
|
|
/**
|
|
* register_capi_driver() - register CAPI driver
|
|
* @driver: driver descriptor structure.
|
|
*
|
|
* Called by hardware driver to register itself with the CAPI subsystem.
|
|
*/
|
|
|
|
void register_capi_driver(struct capi_driver *driver)
|
|
{
|
|
mutex_lock(&capi_drivers_lock);
|
|
list_add_tail(&driver->list, &capi_drivers);
|
|
mutex_unlock(&capi_drivers_lock);
|
|
}
|
|
|
|
EXPORT_SYMBOL(register_capi_driver);
|
|
|
|
/**
|
|
* unregister_capi_driver() - unregister CAPI driver
|
|
* @driver: driver descriptor structure.
|
|
*
|
|
* Called by hardware driver to unregister itself from the CAPI subsystem.
|
|
*/
|
|
|
|
void unregister_capi_driver(struct capi_driver *driver)
|
|
{
|
|
mutex_lock(&capi_drivers_lock);
|
|
list_del(&driver->list);
|
|
mutex_unlock(&capi_drivers_lock);
|
|
}
|
|
|
|
EXPORT_SYMBOL(unregister_capi_driver);
|
|
|
|
/* ------------------------------------------------------------- */
|
|
/* -------- CAPI2.0 Interface ---------------------------------- */
|
|
/* ------------------------------------------------------------- */
|
|
|
|
/**
|
|
* capi20_isinstalled() - CAPI 2.0 operation CAPI_INSTALLED
|
|
*
|
|
* Return value: CAPI result code (CAPI_NOERROR if at least one ISDN controller
|
|
* is ready for use, CAPI_REGNOTINSTALLED otherwise)
|
|
*/
|
|
|
|
u16 capi20_isinstalled(void)
|
|
{
|
|
u16 ret = CAPI_REGNOTINSTALLED;
|
|
int i;
|
|
|
|
mutex_lock(&capi_controller_lock);
|
|
|
|
for (i = 0; i < CAPI_MAXCONTR; i++)
|
|
if (capi_controller[i] &&
|
|
capi_controller[i]->state == CAPI_CTR_RUNNING) {
|
|
ret = CAPI_NOERROR;
|
|
break;
|
|
}
|
|
|
|
mutex_unlock(&capi_controller_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
EXPORT_SYMBOL(capi20_isinstalled);
|
|
|
|
/**
|
|
* capi20_register() - CAPI 2.0 operation CAPI_REGISTER
|
|
* @ap: CAPI application descriptor structure.
|
|
*
|
|
* Register an application's presence with CAPI.
|
|
* A unique application ID is assigned and stored in @ap->applid.
|
|
* After this function returns successfully, the message receive
|
|
* callback function @ap->recv_message() may be called at any time
|
|
* until capi20_release() has been called for the same @ap.
|
|
* Return value: CAPI result code
|
|
*/
|
|
|
|
u16 capi20_register(struct capi20_appl *ap)
|
|
{
|
|
int i;
|
|
u16 applid;
|
|
|
|
DBG("");
|
|
|
|
if (ap->rparam.datablklen < 128)
|
|
return CAPI_LOGBLKSIZETOSMALL;
|
|
|
|
ap->nrecvctlpkt = 0;
|
|
ap->nrecvdatapkt = 0;
|
|
ap->nsentctlpkt = 0;
|
|
ap->nsentdatapkt = 0;
|
|
mutex_init(&ap->recv_mtx);
|
|
skb_queue_head_init(&ap->recv_queue);
|
|
INIT_WORK(&ap->recv_work, recv_handler);
|
|
ap->release_in_progress = 0;
|
|
|
|
mutex_lock(&capi_controller_lock);
|
|
|
|
for (applid = 1; applid <= CAPI_MAXAPPL; applid++) {
|
|
if (capi_applications[applid - 1] == NULL)
|
|
break;
|
|
}
|
|
if (applid > CAPI_MAXAPPL) {
|
|
mutex_unlock(&capi_controller_lock);
|
|
return CAPI_TOOMANYAPPLS;
|
|
}
|
|
|
|
ap->applid = applid;
|
|
capi_applications[applid - 1] = ap;
|
|
|
|
for (i = 0; i < CAPI_MAXCONTR; i++) {
|
|
if (!capi_controller[i] ||
|
|
capi_controller[i]->state != CAPI_CTR_RUNNING)
|
|
continue;
|
|
register_appl(capi_controller[i], applid, &ap->rparam);
|
|
}
|
|
|
|
mutex_unlock(&capi_controller_lock);
|
|
|
|
if (showcapimsgs & 1) {
|
|
printk(KERN_DEBUG "kcapi: appl %d up\n", applid);
|
|
}
|
|
|
|
return CAPI_NOERROR;
|
|
}
|
|
|
|
EXPORT_SYMBOL(capi20_register);
|
|
|
|
/**
|
|
* capi20_release() - CAPI 2.0 operation CAPI_RELEASE
|
|
* @ap: CAPI application descriptor structure.
|
|
*
|
|
* Terminate an application's registration with CAPI.
|
|
* After this function returns successfully, the message receive
|
|
* callback function @ap->recv_message() will no longer be called.
|
|
* Return value: CAPI result code
|
|
*/
|
|
|
|
u16 capi20_release(struct capi20_appl *ap)
|
|
{
|
|
int i;
|
|
|
|
DBG("applid %#x", ap->applid);
|
|
|
|
mutex_lock(&capi_controller_lock);
|
|
|
|
ap->release_in_progress = 1;
|
|
capi_applications[ap->applid - 1] = NULL;
|
|
|
|
synchronize_rcu();
|
|
|
|
for (i = 0; i < CAPI_MAXCONTR; i++) {
|
|
if (!capi_controller[i] ||
|
|
capi_controller[i]->state != CAPI_CTR_RUNNING)
|
|
continue;
|
|
release_appl(capi_controller[i], ap->applid);
|
|
}
|
|
|
|
mutex_unlock(&capi_controller_lock);
|
|
|
|
flush_workqueue(kcapi_wq);
|
|
skb_queue_purge(&ap->recv_queue);
|
|
|
|
if (showcapimsgs & 1) {
|
|
printk(KERN_DEBUG "kcapi: appl %d down\n", ap->applid);
|
|
}
|
|
|
|
return CAPI_NOERROR;
|
|
}
|
|
|
|
EXPORT_SYMBOL(capi20_release);
|
|
|
|
/**
|
|
* capi20_put_message() - CAPI 2.0 operation CAPI_PUT_MESSAGE
|
|
* @ap: CAPI application descriptor structure.
|
|
* @skb: CAPI message.
|
|
*
|
|
* Transfer a single message to CAPI.
|
|
* Return value: CAPI result code
|
|
*/
|
|
|
|
u16 capi20_put_message(struct capi20_appl *ap, struct sk_buff *skb)
|
|
{
|
|
struct capi_ctr *ctr;
|
|
int showctl = 0;
|
|
u8 cmd, subcmd;
|
|
|
|
DBG("applid %#x", ap->applid);
|
|
|
|
if (ncontrollers == 0)
|
|
return CAPI_REGNOTINSTALLED;
|
|
if ((ap->applid == 0) || ap->release_in_progress)
|
|
return CAPI_ILLAPPNR;
|
|
if (skb->len < 12
|
|
|| !capi_cmd_valid(CAPIMSG_COMMAND(skb->data))
|
|
|| !capi_subcmd_valid(CAPIMSG_SUBCOMMAND(skb->data)))
|
|
return CAPI_ILLCMDORSUBCMDORMSGTOSMALL;
|
|
|
|
/*
|
|
* The controller reference is protected by the existence of the
|
|
* application passed to us. We assume that the caller properly
|
|
* synchronizes this service with capi20_release.
|
|
*/
|
|
ctr = get_capi_ctr_by_nr(CAPIMSG_CONTROLLER(skb->data));
|
|
if (!ctr || ctr->state != CAPI_CTR_RUNNING)
|
|
return CAPI_REGNOTINSTALLED;
|
|
if (ctr->blocked)
|
|
return CAPI_SENDQUEUEFULL;
|
|
|
|
cmd = CAPIMSG_COMMAND(skb->data);
|
|
subcmd = CAPIMSG_SUBCOMMAND(skb->data);
|
|
|
|
if (cmd == CAPI_DATA_B3 && subcmd == CAPI_REQ) {
|
|
ctr->nsentdatapkt++;
|
|
ap->nsentdatapkt++;
|
|
if (ctr->traceflag > 2)
|
|
showctl |= 2;
|
|
} else {
|
|
ctr->nsentctlpkt++;
|
|
ap->nsentctlpkt++;
|
|
if (ctr->traceflag)
|
|
showctl |= 2;
|
|
}
|
|
showctl |= (ctr->traceflag & 1);
|
|
if (showctl & 2) {
|
|
if (showctl & 1) {
|
|
printk(KERN_DEBUG "kcapi: put [%03d] id#%d %s len=%u\n",
|
|
CAPIMSG_CONTROLLER(skb->data),
|
|
CAPIMSG_APPID(skb->data),
|
|
capi_cmd2str(cmd, subcmd),
|
|
CAPIMSG_LEN(skb->data));
|
|
} else {
|
|
_cdebbuf *cdb = capi_message2str(skb->data);
|
|
if (cdb) {
|
|
printk(KERN_DEBUG "kcapi: put [%03d] %s\n",
|
|
CAPIMSG_CONTROLLER(skb->data),
|
|
cdb->buf);
|
|
cdebbuf_free(cdb);
|
|
} else
|
|
printk(KERN_DEBUG "kcapi: put [%03d] id#%d %s len=%u cannot trace\n",
|
|
CAPIMSG_CONTROLLER(skb->data),
|
|
CAPIMSG_APPID(skb->data),
|
|
capi_cmd2str(cmd, subcmd),
|
|
CAPIMSG_LEN(skb->data));
|
|
}
|
|
}
|
|
return ctr->send_message(ctr, skb);
|
|
}
|
|
|
|
EXPORT_SYMBOL(capi20_put_message);
|
|
|
|
/**
|
|
* capi20_get_manufacturer() - CAPI 2.0 operation CAPI_GET_MANUFACTURER
|
|
* @contr: controller number.
|
|
* @buf: result buffer (64 bytes).
|
|
*
|
|
* Retrieve information about the manufacturer of the specified ISDN controller
|
|
* or (for @contr == 0) the driver itself.
|
|
* Return value: CAPI result code
|
|
*/
|
|
|
|
u16 capi20_get_manufacturer(u32 contr, u8 *buf)
|
|
{
|
|
struct capi_ctr *ctr;
|
|
u16 ret;
|
|
|
|
if (contr == 0) {
|
|
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 long cmd, void __user *data)
|
|
{
|
|
struct capi_ctr *ctr;
|
|
int retval;
|
|
|
|
switch (cmd) {
|
|
#ifdef AVMB1_COMPAT
|
|
case AVMB1_LOAD:
|
|
case AVMB1_LOAD_AND_CONFIG:
|
|
case AVMB1_RESETCARD:
|
|
case AVMB1_GET_CARDINFO:
|
|
case AVMB1_REMOVECARD:
|
|
return old_capi_manufacturer(cmd, data);
|
|
#endif
|
|
case KCAPI_CMD_TRACE:
|
|
{
|
|
kcapi_flagdef fdef;
|
|
|
|
if (copy_from_user(&fdef, data, sizeof(kcapi_flagdef)))
|
|
return -EFAULT;
|
|
|
|
mutex_lock(&capi_controller_lock);
|
|
|
|
ctr = get_capi_ctr_by_nr(fdef.contr);
|
|
if (ctr) {
|
|
ctr->traceflag = fdef.flag;
|
|
printk(KERN_INFO "kcapi: contr [%03d] set trace=%d\n",
|
|
ctr->cnr, ctr->traceflag);
|
|
retval = 0;
|
|
} else
|
|
retval = -ESRCH;
|
|
|
|
mutex_unlock(&capi_controller_lock);
|
|
|
|
return retval;
|
|
}
|
|
case KCAPI_CMD_ADDCARD:
|
|
{
|
|
struct list_head *l;
|
|
struct capi_driver *driver = NULL;
|
|
capicardparams cparams;
|
|
kcapi_carddef cdef;
|
|
|
|
if ((retval = copy_from_user(&cdef, data, sizeof(cdef))))
|
|
return -EFAULT;
|
|
|
|
cparams.port = cdef.port;
|
|
cparams.irq = cdef.irq;
|
|
cparams.membase = cdef.membase;
|
|
cparams.cardnr = cdef.cardnr;
|
|
cparams.cardtype = 0;
|
|
cdef.driver[sizeof(cdef.driver) - 1] = 0;
|
|
|
|
mutex_lock(&capi_drivers_lock);
|
|
|
|
list_for_each(l, &capi_drivers) {
|
|
driver = list_entry(l, struct capi_driver, list);
|
|
if (strcmp(driver->name, cdef.driver) == 0)
|
|
break;
|
|
}
|
|
if (driver == NULL) {
|
|
printk(KERN_ERR "kcapi: driver \"%s\" not loaded.\n",
|
|
cdef.driver);
|
|
retval = -ESRCH;
|
|
} else if (!driver->add_card) {
|
|
printk(KERN_ERR "kcapi: driver \"%s\" has no add card function.\n", cdef.driver);
|
|
retval = -EIO;
|
|
} else
|
|
retval = driver->add_card(driver, &cparams);
|
|
|
|
mutex_unlock(&capi_drivers_lock);
|
|
return retval;
|
|
}
|
|
|
|
default:
|
|
printk(KERN_ERR "kcapi: manufacturer command %lu unknown.\n",
|
|
cmd);
|
|
break;
|
|
|
|
}
|
|
return -EINVAL;
|
|
}
|
|
|
|
EXPORT_SYMBOL(capi20_manufacturer);
|
|
|
|
/* ------------------------------------------------------------- */
|
|
/* -------- Init & Cleanup ------------------------------------- */
|
|
/* ------------------------------------------------------------- */
|
|
|
|
/*
|
|
* init / exit functions
|
|
*/
|
|
|
|
static struct notifier_block capictr_nb = {
|
|
.notifier_call = notify_handler,
|
|
.priority = INT_MAX,
|
|
};
|
|
|
|
static int __init kcapi_init(void)
|
|
{
|
|
int err;
|
|
|
|
kcapi_wq = alloc_workqueue("kcapi", 0, 0);
|
|
if (!kcapi_wq)
|
|
return -ENOMEM;
|
|
|
|
register_capictr_notifier(&capictr_nb);
|
|
|
|
err = cdebug_init();
|
|
if (err) {
|
|
unregister_capictr_notifier(&capictr_nb);
|
|
destroy_workqueue(kcapi_wq);
|
|
return err;
|
|
}
|
|
|
|
kcapi_proc_init();
|
|
return 0;
|
|
}
|
|
|
|
static void __exit kcapi_exit(void)
|
|
{
|
|
kcapi_proc_exit();
|
|
|
|
unregister_capictr_notifier(&capictr_nb);
|
|
cdebug_exit();
|
|
destroy_workqueue(kcapi_wq);
|
|
}
|
|
|
|
module_init(kcapi_init);
|
|
module_exit(kcapi_exit);
|