/* $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 CONFIG_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/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 <asm/uaccess.h> #include <linux/isdn/capicmd.h> #include <linux/isdn/capiutil.h> #ifdef CONFIG_AVMB1_COMPAT #include <linux/b1lli.h> #endif static char *revision = "$Revision: 1.1.2.8 $"; /* ------------------------------------------------------------- */ static int showcapimsgs = 0; MODULE_DESCRIPTION("CAPI4Linux: kernel CAPI layer"); MODULE_AUTHOR("Carsten Paeth"); MODULE_LICENSE("GPL"); module_param(showcapimsgs, uint, 0); /* ------------------------------------------------------------- */ struct capi_notifier { struct work_struct work; unsigned int cmd; u32 controller; u16 applid; u32 ncci; }; /* ------------------------------------------------------------- */ 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_RWLOCK(capi_drivers_list_lock); static DEFINE_RWLOCK(application_lock); static DECLARE_MUTEX(controller_sem); struct capi20_appl *capi_applications[CAPI_MAXAPPL]; struct capi_ctr *capi_cards[CAPI_MAXCONTR]; static int ncards; /* -------- controller ref counting -------------------------------------- */ static inline struct capi_ctr * capi_ctr_get(struct capi_ctr *card) { if (!try_module_get(card->owner)) return NULL; return card; } static inline void capi_ctr_put(struct capi_ctr *card) { module_put(card->owner); } /* ------------------------------------------------------------- */ static inline struct capi_ctr *get_capi_ctr_by_nr(u16 contr) { if (contr - 1 >= CAPI_MAXCONTR) return NULL; return capi_cards[contr - 1]; } static inline struct capi20_appl *get_capi_appl_by_nr(u16 applid) { if (applid - 1 >= CAPI_MAXAPPL) return NULL; return 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 *card, u16 applid, capi_register_params *rparam) { card = capi_ctr_get(card); if (card) card->register_appl(card, applid, rparam); else printk(KERN_WARNING "%s: cannot get card resources\n", __FUNCTION__); } static void release_appl(struct capi_ctr *card, u16 applid) { DBG("applid %#x", applid); card->release_appl(card, applid); capi_ctr_put(card); } /* -------- KCI_CONTRUP --------------------------------------- */ static void notify_up(u32 contr) { struct capi_ctr *card = get_capi_ctr_by_nr(contr); struct capi20_appl *ap; u16 applid; if (showcapimsgs & 1) { printk(KERN_DEBUG "kcapi: notify up contr %d\n", contr); } if (!card) { printk(KERN_WARNING "%s: invalid contr %d\n", __FUNCTION__, contr); return; } for (applid = 1; applid <= CAPI_MAXAPPL; applid++) { ap = get_capi_appl_by_nr(applid); if (!ap || ap->release_in_progress) continue; register_appl(card, applid, &ap->rparam); if (ap->callback && !ap->release_in_progress) ap->callback(KCI_CONTRUP, contr, &card->profile); } } /* -------- KCI_CONTRDOWN ------------------------------------- */ static void notify_down(u32 contr) { struct capi20_appl *ap; u16 applid; if (showcapimsgs & 1) { printk(KERN_DEBUG "kcapi: notify down contr %d\n", contr); } for (applid = 1; applid <= CAPI_MAXAPPL; applid++) { ap = get_capi_appl_by_nr(applid); if (ap && ap->callback && !ap->release_in_progress) ap->callback(KCI_CONTRDOWN, contr, NULL); } } static void notify_handler(void *data) { struct capi_notifier *np = data; switch (np->cmd) { case KCI_CONTRUP: notify_up(np->controller); break; case KCI_CONTRDOWN: notify_down(np->controller); break; } kfree(np); } /* * 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 cmd, u32 controller, u16 applid, u32 ncci) { struct capi_notifier *np = kmalloc(sizeof(*np), GFP_ATOMIC); if (!np) return -ENOMEM; INIT_WORK(&np->work, notify_handler, np); np->cmd = cmd; np->controller = controller; np->applid = applid; np->ncci = ncci; schedule_work(&np->work); return 0; } /* -------- Receiver ------------------------------------------ */ static void recv_handler(void *_ap) { struct sk_buff *skb; struct capi20_appl *ap = (struct capi20_appl *) _ap; if ((!ap) || (ap->release_in_progress)) return; down(&ap->recv_sem); 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); } up(&ap->recv_sem); } void capi_ctr_handle_message(struct capi_ctr * card, u16 appl, struct sk_buff *skb) { struct capi20_appl *ap; int showctl = 0; u8 cmd, subcmd; unsigned long flags; if (card->cardstate != CARD_RUNNING) { printk(KERN_INFO "kcapi: controller %d not active, got: %s", card->cnr, capi_message2str(skb->data)); goto error; } cmd = CAPIMSG_COMMAND(skb->data); subcmd = CAPIMSG_SUBCOMMAND(skb->data); if (cmd == CAPI_DATA_B3 && subcmd == CAPI_IND) { card->nrecvdatapkt++; if (card->traceflag > 2) showctl |= 2; } else { card->nrecvctlpkt++; if (card->traceflag) showctl |= 2; } showctl |= (card->traceflag & 1); if (showctl & 2) { if (showctl & 1) { printk(KERN_DEBUG "kcapi: got [0x%lx] id#%d %s len=%u\n", (unsigned long) card->cnr, CAPIMSG_APPID(skb->data), capi_cmd2str(cmd, subcmd), CAPIMSG_LEN(skb->data)); } else { printk(KERN_DEBUG "kcapi: got [0x%lx] %s\n", (unsigned long) card->cnr, capi_message2str(skb->data)); } } read_lock_irqsave(&application_lock, flags); ap = get_capi_appl_by_nr(CAPIMSG_APPID(skb->data)); if ((!ap) || (ap->release_in_progress)) { read_unlock_irqrestore(&application_lock, flags); printk(KERN_ERR "kcapi: handle_message: applid %d state released (%s)\n", CAPIMSG_APPID(skb->data), capi_message2str(skb->data)); goto error; } skb_queue_tail(&ap->recv_queue, skb); schedule_work(&ap->recv_work); read_unlock_irqrestore(&application_lock, flags); return; error: kfree_skb(skb); } EXPORT_SYMBOL(capi_ctr_handle_message); void capi_ctr_ready(struct capi_ctr * card) { card->cardstate = CARD_RUNNING; printk(KERN_NOTICE "kcapi: card %d \"%s\" ready.\n", card->cnr, card->name); notify_push(KCI_CONTRUP, card->cnr, 0, 0); } EXPORT_SYMBOL(capi_ctr_ready); void capi_ctr_reseted(struct capi_ctr * card) { u16 appl; DBG(""); if (card->cardstate == CARD_DETECTED) return; card->cardstate = CARD_DETECTED; memset(card->manu, 0, sizeof(card->manu)); memset(&card->version, 0, sizeof(card->version)); memset(&card->profile, 0, sizeof(card->profile)); memset(card->serial, 0, sizeof(card->serial)); for (appl = 1; appl <= CAPI_MAXAPPL; appl++) { struct capi20_appl *ap = get_capi_appl_by_nr(appl); if (!ap || ap->release_in_progress) continue; capi_ctr_put(card); } printk(KERN_NOTICE "kcapi: card %d down.\n", card->cnr); notify_push(KCI_CONTRDOWN, card->cnr, 0, 0); } EXPORT_SYMBOL(capi_ctr_reseted); void capi_ctr_suspend_output(struct capi_ctr *card) { if (!card->blocked) { printk(KERN_DEBUG "kcapi: card %d suspend\n", card->cnr); card->blocked = 1; } } EXPORT_SYMBOL(capi_ctr_suspend_output); void capi_ctr_resume_output(struct capi_ctr *card) { if (card->blocked) { printk(KERN_DEBUG "kcapi: card %d resume\n", card->cnr); card->blocked = 0; } } EXPORT_SYMBOL(capi_ctr_resume_output); /* ------------------------------------------------------------- */ int attach_capi_ctr(struct capi_ctr *card) { int i; down(&controller_sem); for (i = 0; i < CAPI_MAXCONTR; i++) { if (capi_cards[i] == NULL) break; } if (i == CAPI_MAXCONTR) { up(&controller_sem); printk(KERN_ERR "kcapi: out of controller slots\n"); return -EBUSY; } capi_cards[i] = card; up(&controller_sem); card->nrecvctlpkt = 0; card->nrecvdatapkt = 0; card->nsentctlpkt = 0; card->nsentdatapkt = 0; card->cnr = i + 1; card->cardstate = CARD_DETECTED; card->blocked = 0; card->traceflag = showcapimsgs; sprintf(card->procfn, "capi/controllers/%d", card->cnr); card->procent = create_proc_entry(card->procfn, 0, NULL); if (card->procent) { card->procent->read_proc = (int (*)(char *,char **,off_t,int,int *,void *)) card->ctr_read_proc; card->procent->data = card; } ncards++; printk(KERN_NOTICE "kcapi: Controller %d: %s attached\n", card->cnr, card->name); return 0; } EXPORT_SYMBOL(attach_capi_ctr); int detach_capi_ctr(struct capi_ctr *card) { if (card->cardstate != CARD_DETECTED) capi_ctr_reseted(card); ncards--; if (card->procent) { remove_proc_entry(card->procfn, NULL); card->procent = NULL; } capi_cards[card->cnr - 1] = NULL; printk(KERN_NOTICE "kcapi: Controller %d: %s unregistered\n", card->cnr, card->name); return 0; } EXPORT_SYMBOL(detach_capi_ctr); void register_capi_driver(struct capi_driver *driver) { unsigned long flags; write_lock_irqsave(&capi_drivers_list_lock, flags); list_add_tail(&driver->list, &capi_drivers); write_unlock_irqrestore(&capi_drivers_list_lock, flags); } EXPORT_SYMBOL(register_capi_driver); void unregister_capi_driver(struct capi_driver *driver) { unsigned long flags; write_lock_irqsave(&capi_drivers_list_lock, flags); list_del(&driver->list); write_unlock_irqrestore(&capi_drivers_list_lock, flags); } EXPORT_SYMBOL(unregister_capi_driver); /* ------------------------------------------------------------- */ /* -------- CAPI2.0 Interface ---------------------------------- */ /* ------------------------------------------------------------- */ u16 capi20_isinstalled(void) { int i; for (i = 0; i < CAPI_MAXCONTR; i++) { if (capi_cards[i] && capi_cards[i]->cardstate == CARD_RUNNING) return CAPI_NOERROR; } return CAPI_REGNOTINSTALLED; } EXPORT_SYMBOL(capi20_isinstalled); u16 capi20_register(struct capi20_appl *ap) { int i; u16 applid; unsigned long flags; DBG(""); if (ap->rparam.datablklen < 128) return CAPI_LOGBLKSIZETOSMALL; write_lock_irqsave(&application_lock, flags); for (applid = 1; applid <= CAPI_MAXAPPL; applid++) { if (capi_applications[applid - 1] == NULL) break; } if (applid > CAPI_MAXAPPL) { write_unlock_irqrestore(&application_lock, flags); return CAPI_TOOMANYAPPLS; } ap->applid = applid; capi_applications[applid - 1] = ap; ap->nrecvctlpkt = 0; ap->nrecvdatapkt = 0; ap->nsentctlpkt = 0; ap->nsentdatapkt = 0; ap->callback = NULL; init_MUTEX(&ap->recv_sem); skb_queue_head_init(&ap->recv_queue); INIT_WORK(&ap->recv_work, recv_handler, (void *)ap); ap->release_in_progress = 0; write_unlock_irqrestore(&application_lock, flags); down(&controller_sem); for (i = 0; i < CAPI_MAXCONTR; i++) { if (!capi_cards[i] || capi_cards[i]->cardstate != CARD_RUNNING) continue; register_appl(capi_cards[i], applid, &ap->rparam); } up(&controller_sem); if (showcapimsgs & 1) { printk(KERN_DEBUG "kcapi: appl %d up\n", applid); } return CAPI_NOERROR; } EXPORT_SYMBOL(capi20_register); u16 capi20_release(struct capi20_appl *ap) { int i; unsigned long flags; DBG("applid %#x", ap->applid); write_lock_irqsave(&application_lock, flags); ap->release_in_progress = 1; capi_applications[ap->applid - 1] = NULL; write_unlock_irqrestore(&application_lock, flags); down(&controller_sem); for (i = 0; i < CAPI_MAXCONTR; i++) { if (!capi_cards[i] || capi_cards[i]->cardstate != CARD_RUNNING) continue; release_appl(capi_cards[i], ap->applid); } up(&controller_sem); flush_scheduled_work(); 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); u16 capi20_put_message(struct capi20_appl *ap, struct sk_buff *skb) { struct capi_ctr *card; int showctl = 0; u8 cmd, subcmd; DBG("applid %#x", ap->applid); if (ncards == 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; card = get_capi_ctr_by_nr(CAPIMSG_CONTROLLER(skb->data)); if (!card || card->cardstate != CARD_RUNNING) { card = get_capi_ctr_by_nr(1); // XXX why? if (!card || card->cardstate != CARD_RUNNING) return CAPI_REGNOTINSTALLED; } if (card->blocked) return CAPI_SENDQUEUEFULL; cmd = CAPIMSG_COMMAND(skb->data); subcmd = CAPIMSG_SUBCOMMAND(skb->data); if (cmd == CAPI_DATA_B3 && subcmd== CAPI_REQ) { card->nsentdatapkt++; ap->nsentdatapkt++; if (card->traceflag > 2) showctl |= 2; } else { card->nsentctlpkt++; ap->nsentctlpkt++; if (card->traceflag) showctl |= 2; } showctl |= (card->traceflag & 1); if (showctl & 2) { if (showctl & 1) { printk(KERN_DEBUG "kcapi: put [%#x] id#%d %s len=%u\n", CAPIMSG_CONTROLLER(skb->data), CAPIMSG_APPID(skb->data), capi_cmd2str(cmd, subcmd), CAPIMSG_LEN(skb->data)); } else { printk(KERN_DEBUG "kcapi: put [%#x] %s\n", CAPIMSG_CONTROLLER(skb->data), capi_message2str(skb->data)); } } return card->send_message(card, skb); } EXPORT_SYMBOL(capi20_put_message); u16 capi20_get_manufacturer(u32 contr, u8 *buf) { struct capi_ctr *card; if (contr == 0) { strlcpy(buf, capi_manufakturer, CAPI_MANUFACTURER_LEN); return CAPI_NOERROR; } card = get_capi_ctr_by_nr(contr); if (!card || card->cardstate != CARD_RUNNING) return CAPI_REGNOTINSTALLED; strlcpy(buf, card->manu, CAPI_MANUFACTURER_LEN); return CAPI_NOERROR; } EXPORT_SYMBOL(capi20_get_manufacturer); u16 capi20_get_version(u32 contr, struct capi_version *verp) { struct capi_ctr *card; if (contr == 0) { *verp = driver_version; return CAPI_NOERROR; } card = get_capi_ctr_by_nr(contr); if (!card || card->cardstate != CARD_RUNNING) return CAPI_REGNOTINSTALLED; memcpy((void *) verp, &card->version, sizeof(capi_version)); return CAPI_NOERROR; } EXPORT_SYMBOL(capi20_get_version); u16 capi20_get_serial(u32 contr, u8 *serial) { struct capi_ctr *card; if (contr == 0) { strlcpy(serial, driver_serial, CAPI_SERIAL_LEN); return CAPI_NOERROR; } card = get_capi_ctr_by_nr(contr); if (!card || card->cardstate != CARD_RUNNING) return CAPI_REGNOTINSTALLED; strlcpy((void *) serial, card->serial, CAPI_SERIAL_LEN); return CAPI_NOERROR; } EXPORT_SYMBOL(capi20_get_serial); u16 capi20_get_profile(u32 contr, struct capi_profile *profp) { struct capi_ctr *card; if (contr == 0) { profp->ncontroller = ncards; return CAPI_NOERROR; } card = get_capi_ctr_by_nr(contr); if (!card || card->cardstate != CARD_RUNNING) return CAPI_REGNOTINSTALLED; memcpy((void *) profp, &card->profile, sizeof(struct capi_profile)); return CAPI_NOERROR; } EXPORT_SYMBOL(capi20_get_profile); #ifdef CONFIG_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 *card; struct capi_driver *driver = NULL; capiloaddata ldata; struct list_head *l; unsigned long flags; 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 retval; cdef.cardtype = AVM_CARDTYPE_B1; } else { if ((retval = copy_from_user(&cdef, data, sizeof(avmb1_extcarddef)))) return retval; } cparams.port = cdef.port; cparams.irq = cdef.irq; cparams.cardnr = cdef.cardnr; read_lock_irqsave(&capi_drivers_list_lock, flags); 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) { read_unlock_irqrestore(&capi_drivers_list_lock, flags); printk(KERN_ERR "kcapi: driver not loaded.\n"); return -EIO; } if (!driver->add_card) { read_unlock_irqrestore(&capi_drivers_list_lock, flags); printk(KERN_ERR "kcapi: driver has no add card function.\n"); return -EIO; } retval = driver->add_card(driver, &cparams); read_unlock_irqrestore(&capi_drivers_list_lock, flags); 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; } card = get_capi_ctr_by_nr(ldef.contr); card = capi_ctr_get(card); if (!card) return -ESRCH; if (card->load_firmware == 0) { printk(KERN_DEBUG "kcapi: load: no load function\n"); return -ESRCH; } if (ldef.t4file.len <= 0) { printk(KERN_DEBUG "kcapi: load: invalid parameter: length of t4file is %d ?\n", ldef.t4file.len); return -EINVAL; } if (ldef.t4file.data == 0) { printk(KERN_DEBUG "kcapi: load: invalid parameter: dataptr is 0\n"); return -EINVAL; } 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 (card->cardstate != CARD_DETECTED) { printk(KERN_INFO "kcapi: load: contr=%d not in detect state\n", ldef.contr); return -EBUSY; } card->cardstate = CARD_LOADING; retval = card->load_firmware(card, &ldata); if (retval) { card->cardstate = CARD_DETECTED; capi_ctr_put(card); return retval; } while (card->cardstate != CARD_RUNNING) { msleep_interruptible(100); /* 0.1 sec */ if (signal_pending(current)) { capi_ctr_put(card); return -EINTR; } } capi_ctr_put(card); return 0; case AVMB1_RESETCARD: if (copy_from_user(&rdef, data, sizeof(avmb1_resetdef))) return -EFAULT; card = get_capi_ctr_by_nr(rdef.contr); if (!card) return -ESRCH; if (card->cardstate == CARD_DETECTED) return 0; card->reset_ctr(card); while (card->cardstate > CARD_DETECTED) { msleep_interruptible(100); /* 0.1 sec */ if (signal_pending(current)) return -EINTR; } return 0; } return -EINVAL; } #endif int capi20_manufacturer(unsigned int cmd, void __user *data) { struct capi_ctr *card; switch (cmd) { #ifdef CONFIG_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; card = get_capi_ctr_by_nr(fdef.contr); if (!card) return -ESRCH; card->traceflag = fdef.flag; printk(KERN_INFO "kcapi: contr %d set trace=%d\n", card->cnr, card->traceflag); return 0; } case KCAPI_CMD_ADDCARD: { struct list_head *l; struct capi_driver *driver = NULL; capicardparams cparams; kcapi_carddef cdef; int retval; if ((retval = copy_from_user(&cdef, data, sizeof(cdef)))) return retval; 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; list_for_each(l, &capi_drivers) { driver = list_entry(l, struct capi_driver, list); if (strcmp(driver->name, cdef.driver) == 0) break; } if (driver == 0) { printk(KERN_ERR "kcapi: driver \"%s\" not loaded.\n", cdef.driver); return -ESRCH; } if (!driver->add_card) { printk(KERN_ERR "kcapi: driver \"%s\" has no add card function.\n", cdef.driver); return -EIO; } return driver->add_card(driver, &cparams); } default: printk(KERN_ERR "kcapi: manufacturer command %d unknown.\n", cmd); break; } return -EINVAL; } EXPORT_SYMBOL(capi20_manufacturer); /* temporary hack */ void capi20_set_callback(struct capi20_appl *ap, void (*callback) (unsigned int cmd, __u32 contr, void *data)) { ap->callback = callback; } EXPORT_SYMBOL(capi20_set_callback); /* ------------------------------------------------------------- */ /* -------- Init & Cleanup ------------------------------------- */ /* ------------------------------------------------------------- */ /* * init / exit functions */ static int __init kcapi_init(void) { char *p; char rev[32]; kcapi_proc_init(); if ((p = strchr(revision, ':')) != 0 && p[1]) { strlcpy(rev, p + 2, sizeof(rev)); if ((p = strchr(rev, '$')) != 0 && p > rev) *(p-1) = 0; } else strcpy(rev, "1.0"); printk(KERN_NOTICE "CAPI Subsystem Rev %s\n", rev); return 0; } static void __exit kcapi_exit(void) { kcapi_proc_exit(); /* make sure all notifiers are finished */ flush_scheduled_work(); } module_init(kcapi_init); module_exit(kcapi_exit);