| /* $Id: kcapi.c,v 1.1.4.1 2001/11/20 14:19:34 kai Exp $ |
| * |
| * Kernel CAPI 2.0 Module |
| * |
| * Copyright 1999 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. |
| * |
| */ |
| |
| #define CONFIG_AVMB1_COMPAT |
| |
| #include <linux/config.h> |
| #include <linux/module.h> |
| #include <linux/kernel.h> |
| #include <linux/mm.h> |
| #include <linux/interrupt.h> |
| #include <linux/ioport.h> |
| #include <asm/segment.h> |
| #include <linux/proc_fs.h> |
| #include <linux/skbuff.h> |
| #include <linux/tqueue.h> |
| #include <linux/capi.h> |
| #include <linux/kernelcapi.h> |
| #include <linux/locks.h> |
| #include <linux/init.h> |
| #include <asm/uaccess.h> |
| #include "capicmd.h" |
| #include "capiutil.h" |
| #include "capilli.h" |
| #ifdef CONFIG_AVMB1_COMPAT |
| #include <linux/b1lli.h> |
| #endif |
| |
| static char *revision = "$Revision: 1.1.4.1 $"; |
| |
| /* ------------------------------------------------------------- */ |
| |
| #define CARD_FREE 0 |
| #define CARD_DETECTED 1 |
| #define CARD_LOADING 2 |
| #define CARD_RUNNING 3 |
| |
| /* ------------------------------------------------------------- */ |
| |
| static int showcapimsgs = 0; |
| |
| MODULE_DESCRIPTION("CAPI4Linux: kernel CAPI layer"); |
| MODULE_AUTHOR("Carsten Paeth"); |
| MODULE_LICENSE("GPL"); |
| MODULE_PARM(showcapimsgs, "i"); |
| |
| /* ------------------------------------------------------------- */ |
| |
| struct msgidqueue { |
| struct msgidqueue *next; |
| __u16 msgid; |
| }; |
| |
| struct capi_ncci { |
| struct capi_ncci *next; |
| __u16 applid; |
| __u32 ncci; |
| __u32 winsize; |
| int nmsg; |
| spinlock_t lock; |
| struct msgidqueue *msgidqueue; |
| struct msgidqueue *msgidlast; |
| struct msgidqueue *msgidfree; |
| struct msgidqueue msgidpool[CAPI_MAXDATAWINDOW]; |
| }; |
| |
| struct capi_appl { |
| __u16 applid; |
| capi_register_params rparam; |
| int releasing; |
| void *param; |
| void (*signal) (__u16 applid, void *param); |
| struct sk_buff_head recv_queue; |
| int nncci; |
| struct capi_ncci *nccilist; |
| |
| unsigned long nrecvctlpkt; |
| unsigned long nrecvdatapkt; |
| unsigned long nsentctlpkt; |
| unsigned long nsentdatapkt; |
| }; |
| |
| struct capi_notifier { |
| struct capi_notifier *next; |
| 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 APPL(a) (&applications[(a)-1]) |
| #define VALID_APPLID(a) ((a) && (a) <= CAPI_MAXAPPL && APPL(a)->applid == a) |
| #define APPL_IS_FREE(a) (APPL(a)->applid == 0) |
| #define APPL_MARK_FREE(a) do{ APPL(a)->applid=0; MOD_DEC_USE_COUNT; }while(0) |
| #define APPL_MARK_USED(a) do{ APPL(a)->applid=(a); MOD_INC_USE_COUNT; }while(0) |
| |
| #define NCCI2CTRL(ncci) (((ncci) >> 24) & 0x7f) |
| |
| #define VALID_CARD(c) ((c) > 0 && (c) <= CAPI_MAXCONTR) |
| #define CARD(c) (&cards[(c)-1]) |
| #define CARDNR(cp) ((((cp)-cards)+1) & 0xff) |
| |
| static struct capi_appl applications[CAPI_MAXAPPL]; |
| static struct capi_ctr cards[CAPI_MAXCONTR]; |
| static int ncards = 0; |
| static struct sk_buff_head recv_queue; |
| static struct capi_interface_user *capi_users = 0; |
| static spinlock_t capi_users_lock = SPIN_LOCK_UNLOCKED; |
| static struct capi_driver *drivers; |
| static spinlock_t drivers_lock = SPIN_LOCK_UNLOCKED; |
| |
| static struct tq_struct tq_state_notify; |
| static struct tq_struct tq_recv_notify; |
| |
| /* -------- util functions ------------------------------------ */ |
| |
| static char *cardstate2str(unsigned short cardstate) |
| { |
| switch (cardstate) { |
| default: |
| case CARD_FREE: return "free"; |
| case CARD_DETECTED: return "detected"; |
| case CARD_LOADING: return "loading"; |
| case CARD_RUNNING: return "running"; |
| } |
| } |
| |
| 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; |
| } |
| |
| /* -------- /proc functions ----------------------------------- */ |
| /* |
| * /proc/capi/applications: |
| * applid l3cnt dblkcnt dblklen #ncci recvqueuelen |
| */ |
| static int proc_applications_read_proc(char *page, char **start, off_t off, |
| int count, int *eof, void *data) |
| { |
| struct capi_appl *ap; |
| int i; |
| int len = 0; |
| |
| for (i=0; i < CAPI_MAXAPPL; i++) { |
| ap = &applications[i]; |
| if (ap->applid == 0) continue; |
| len += sprintf(page+len, "%u %d %d %d %d %d\n", |
| ap->applid, |
| ap->rparam.level3cnt, |
| ap->rparam.datablkcnt, |
| ap->rparam.datablklen, |
| ap->nncci, |
| skb_queue_len(&ap->recv_queue)); |
| if (len <= off) { |
| off -= len; |
| len = 0; |
| } else { |
| if (len-off > count) |
| goto endloop; |
| } |
| } |
| endloop: |
| *start = page+off; |
| if (len < count) |
| *eof = 1; |
| if (len>count) len = count; |
| if (len<0) len = 0; |
| return len; |
| } |
| |
| /* |
| * /proc/capi/ncci: |
| * applid ncci winsize nblk |
| */ |
| static int proc_ncci_read_proc(char *page, char **start, off_t off, |
| int count, int *eof, void *data) |
| { |
| struct capi_appl *ap; |
| struct capi_ncci *np; |
| int i; |
| int len = 0; |
| |
| for (i=0; i < CAPI_MAXAPPL; i++) { |
| ap = &applications[i]; |
| if (ap->applid == 0) continue; |
| for (np = ap->nccilist; np; np = np->next) { |
| len += sprintf(page+len, "%d 0x%x %d %d\n", |
| np->applid, |
| np->ncci, |
| np->winsize, |
| np->nmsg); |
| if (len <= off) { |
| off -= len; |
| len = 0; |
| } else { |
| if (len-off > count) |
| goto endloop; |
| } |
| } |
| } |
| endloop: |
| *start = page+off; |
| if (len < count) |
| *eof = 1; |
| if (len>count) len = count; |
| if (len<0) len = 0; |
| return len; |
| } |
| |
| /* |
| * /proc/capi/driver: |
| * driver ncontroller |
| */ |
| static int proc_driver_read_proc(char *page, char **start, off_t off, |
| int count, int *eof, void *data) |
| { |
| struct capi_driver *driver; |
| int len = 0; |
| |
| spin_lock(&drivers_lock); |
| for (driver = drivers; driver; driver = driver->next) { |
| len += sprintf(page+len, "%-32s %d %s\n", |
| driver->name, |
| driver->ncontroller, |
| driver->revision); |
| if (len <= off) { |
| off -= len; |
| len = 0; |
| } else { |
| if (len-off > count) |
| goto endloop; |
| } |
| } |
| endloop: |
| spin_unlock(&drivers_lock); |
| *start = page+off; |
| if (len < count) |
| *eof = 1; |
| if (len>count) len = count; |
| if (len<0) len = 0; |
| return len; |
| } |
| |
| /* |
| * /proc/capi/users: |
| * name |
| */ |
| static int proc_users_read_proc(char *page, char **start, off_t off, |
| int count, int *eof, void *data) |
| { |
| struct capi_interface_user *cp; |
| int len = 0; |
| |
| spin_lock(&capi_users_lock); |
| for (cp = capi_users; cp ; cp = cp->next) { |
| len += sprintf(page+len, "%s\n", cp->name); |
| if (len <= off) { |
| off -= len; |
| len = 0; |
| } else { |
| if (len-off > count) |
| goto endloop; |
| } |
| } |
| endloop: |
| spin_unlock(&capi_users_lock); |
| *start = page+off; |
| if (len < count) |
| *eof = 1; |
| if (len>count) len = count; |
| if (len<0) len = 0; |
| return len; |
| } |
| |
| /* |
| * /proc/capi/controller: |
| * cnr driver cardstate name driverinfo |
| */ |
| static int proc_controller_read_proc(char *page, char **start, off_t off, |
| int count, int *eof, void *data) |
| { |
| struct capi_ctr *cp; |
| int i; |
| int len = 0; |
| |
| for (i=0; i < CAPI_MAXCONTR; i++) { |
| cp = &cards[i]; |
| if (cp->cardstate == CARD_FREE) continue; |
| len += sprintf(page+len, "%d %-10s %-8s %-16s %s\n", |
| cp->cnr, cp->driver->name, |
| cardstate2str(cp->cardstate), |
| cp->name, |
| cp->driver->procinfo ? cp->driver->procinfo(cp) : "" |
| ); |
| if (len <= off) { |
| off -= len; |
| len = 0; |
| } else { |
| if (len-off > count) |
| goto endloop; |
| } |
| } |
| endloop: |
| *start = page+off; |
| if (len < count) |
| *eof = 1; |
| if (len>count) len = count; |
| if (len<0) len = 0; |
| return len; |
| } |
| |
| /* |
| * /proc/capi/applstats: |
| * applid nrecvctlpkt nrecvdatapkt nsentctlpkt nsentdatapkt |
| */ |
| static int proc_applstats_read_proc(char *page, char **start, off_t off, |
| int count, int *eof, void *data) |
| { |
| struct capi_appl *ap; |
| int i; |
| int len = 0; |
| |
| for (i=0; i < CAPI_MAXAPPL; i++) { |
| ap = &applications[i]; |
| if (ap->applid == 0) continue; |
| len += sprintf(page+len, "%u %lu %lu %lu %lu\n", |
| ap->applid, |
| ap->nrecvctlpkt, |
| ap->nrecvdatapkt, |
| ap->nsentctlpkt, |
| ap->nsentdatapkt); |
| if (len <= off) { |
| off -= len; |
| len = 0; |
| } else { |
| if (len-off > count) |
| goto endloop; |
| } |
| } |
| endloop: |
| *start = page+off; |
| if (len < count) |
| *eof = 1; |
| if (len>count) len = count; |
| if (len<0) len = 0; |
| return len; |
| } |
| |
| /* |
| * /proc/capi/contrstats: |
| * cnr nrecvctlpkt nrecvdatapkt nsentctlpkt nsentdatapkt |
| */ |
| static int proc_contrstats_read_proc(char *page, char **start, off_t off, |
| int count, int *eof, void *data) |
| { |
| struct capi_ctr *cp; |
| int i; |
| int len = 0; |
| |
| for (i=0; i < CAPI_MAXCONTR; i++) { |
| cp = &cards[i]; |
| if (cp->cardstate == CARD_FREE) continue; |
| len += sprintf(page+len, "%d %lu %lu %lu %lu\n", |
| cp->cnr, |
| cp->nrecvctlpkt, |
| cp->nrecvdatapkt, |
| cp->nsentctlpkt, |
| cp->nsentdatapkt); |
| if (len <= off) { |
| off -= len; |
| len = 0; |
| } else { |
| if (len-off > count) |
| goto endloop; |
| } |
| } |
| endloop: |
| *start = page+off; |
| if (len < count) |
| *eof = 1; |
| if (len>count) len = count; |
| if (len<0) len = 0; |
| return len; |
| } |
| |
| static struct procfsentries { |
| char *name; |
| mode_t mode; |
| int (*read_proc)(char *page, char **start, off_t off, |
| int count, int *eof, void *data); |
| struct proc_dir_entry *procent; |
| } procfsentries[] = { |
| { "capi", S_IFDIR, 0 }, |
| { "capi/applications", 0 , proc_applications_read_proc }, |
| { "capi/ncci", 0 , proc_ncci_read_proc }, |
| { "capi/driver", 0 , proc_driver_read_proc }, |
| { "capi/users", 0 , proc_users_read_proc }, |
| { "capi/controller", 0 , proc_controller_read_proc }, |
| { "capi/applstats", 0 , proc_applstats_read_proc }, |
| { "capi/contrstats", 0 , proc_contrstats_read_proc }, |
| { "capi/drivers", S_IFDIR, 0 }, |
| { "capi/controllers", S_IFDIR, 0 }, |
| }; |
| |
| static void proc_capi_init(void) |
| { |
| int nelem = sizeof(procfsentries)/sizeof(procfsentries[0]); |
| int i; |
| |
| for (i=0; i < nelem; i++) { |
| struct procfsentries *p = procfsentries + i; |
| p->procent = create_proc_entry(p->name, p->mode, 0); |
| if (p->procent) p->procent->read_proc = p->read_proc; |
| } |
| } |
| |
| static void proc_capi_exit(void) |
| { |
| int nelem = sizeof(procfsentries)/sizeof(procfsentries[0]); |
| int i; |
| |
| for (i=nelem-1; i >= 0; i--) { |
| struct procfsentries *p = procfsentries + i; |
| if (p->procent) { |
| remove_proc_entry(p->name, 0); |
| p->procent = 0; |
| } |
| } |
| } |
| |
| /* -------- Notifier handling --------------------------------- */ |
| |
| static struct capi_notifier_list{ |
| struct capi_notifier *head; |
| struct capi_notifier *tail; |
| } notifier_list; |
| |
| static spinlock_t notifier_lock = SPIN_LOCK_UNLOCKED; |
| |
| static inline void notify_enqueue(struct capi_notifier *np) |
| { |
| struct capi_notifier_list *q = ¬ifier_list; |
| unsigned long flags; |
| |
| spin_lock_irqsave(¬ifier_lock, flags); |
| if (q->tail) { |
| q->tail->next = np; |
| q->tail = np; |
| } else { |
| q->head = q->tail = np; |
| } |
| spin_unlock_irqrestore(¬ifier_lock, flags); |
| } |
| |
| static inline struct capi_notifier *notify_dequeue(void) |
| { |
| struct capi_notifier_list *q = ¬ifier_list; |
| struct capi_notifier *np = 0; |
| unsigned long flags; |
| |
| spin_lock_irqsave(¬ifier_lock, flags); |
| if (q->head) { |
| np = q->head; |
| if ((q->head = np->next) == 0) |
| q->tail = 0; |
| np->next = 0; |
| } |
| spin_unlock_irqrestore(¬ifier_lock, flags); |
| return np; |
| } |
| |
| static int notify_push(unsigned int cmd, __u32 controller, |
| __u16 applid, __u32 ncci) |
| { |
| struct capi_notifier *np; |
| |
| MOD_INC_USE_COUNT; |
| np = (struct capi_notifier *)kmalloc(sizeof(struct capi_notifier), GFP_ATOMIC); |
| if (!np) { |
| MOD_DEC_USE_COUNT; |
| return -1; |
| } |
| memset(np, 0, sizeof(struct capi_notifier)); |
| np->cmd = cmd; |
| np->controller = controller; |
| np->applid = applid; |
| np->ncci = ncci; |
| notify_enqueue(np); |
| /* |
| * The notifier will result in adding/deleteing |
| * of devices. Devices can only removed in |
| * user process, not in bh. |
| */ |
| MOD_INC_USE_COUNT; |
| if (schedule_task(&tq_state_notify) == 0) |
| MOD_DEC_USE_COUNT; |
| return 0; |
| } |
| |
| /* -------- KCI_CONTRUP --------------------------------------- */ |
| |
| static void notify_up(__u32 contr) |
| { |
| struct capi_interface_user *p; |
| __u16 appl; |
| |
| for (appl = 1; appl <= CAPI_MAXAPPL; appl++) { |
| if (!VALID_APPLID(appl)) continue; |
| if (APPL(appl)->releasing) continue; |
| CARD(contr)->driver->register_appl(CARD(contr), appl, &APPL(appl)->rparam); |
| } |
| printk(KERN_NOTICE "kcapi: notify up contr %d\n", contr); |
| spin_lock(&capi_users_lock); |
| for (p = capi_users; p; p = p->next) { |
| if (!p->callback) continue; |
| (*p->callback) (KCI_CONTRUP, contr, &CARD(contr)->profile); |
| } |
| spin_unlock(&capi_users_lock); |
| } |
| |
| /* -------- KCI_CONTRDOWN ------------------------------------- */ |
| |
| static void notify_down(__u32 contr) |
| { |
| struct capi_interface_user *p; |
| printk(KERN_NOTICE "kcapi: notify down contr %d\n", contr); |
| spin_lock(&capi_users_lock); |
| for (p = capi_users; p; p = p->next) { |
| if (!p->callback) continue; |
| (*p->callback) (KCI_CONTRDOWN, contr, 0); |
| } |
| spin_unlock(&capi_users_lock); |
| } |
| |
| /* -------- KCI_NCCIUP ---------------------------------------- */ |
| |
| static void notify_ncciup(__u32 contr, __u16 applid, __u32 ncci) |
| { |
| struct capi_interface_user *p; |
| struct capi_ncciinfo n; |
| n.applid = applid; |
| n.ncci = ncci; |
| /*printk(KERN_NOTICE "kcapi: notify up contr %d\n", contr);*/ |
| spin_lock(&capi_users_lock); |
| for (p = capi_users; p; p = p->next) { |
| if (!p->callback) continue; |
| (*p->callback) (KCI_NCCIUP, contr, &n); |
| } |
| spin_unlock(&capi_users_lock); |
| }; |
| |
| /* -------- KCI_NCCIDOWN -------------------------------------- */ |
| |
| static void notify_nccidown(__u32 contr, __u16 applid, __u32 ncci) |
| { |
| struct capi_interface_user *p; |
| struct capi_ncciinfo n; |
| n.applid = applid; |
| n.ncci = ncci; |
| /*printk(KERN_NOTICE "kcapi: notify down contr %d\n", contr);*/ |
| spin_lock(&capi_users_lock); |
| for (p = capi_users; p; p = p->next) { |
| if (!p->callback) continue; |
| (*p->callback) (KCI_NCCIDOWN, contr, &n); |
| } |
| spin_unlock(&capi_users_lock); |
| }; |
| |
| /* ------------------------------------------------------------ */ |
| |
| static void inline notify_doit(struct capi_notifier *np) |
| { |
| switch (np->cmd) { |
| case KCI_CONTRUP: |
| notify_up(np->controller); |
| break; |
| case KCI_CONTRDOWN: |
| notify_down(np->controller); |
| break; |
| case KCI_NCCIUP: |
| notify_ncciup(np->controller, np->applid, np->ncci); |
| break; |
| case KCI_NCCIDOWN: |
| notify_nccidown(np->controller, np->applid, np->ncci); |
| break; |
| } |
| } |
| |
| static void notify_handler(void *dummy) |
| { |
| struct capi_notifier *np; |
| |
| while ((np = notify_dequeue()) != 0) { |
| notify_doit(np); |
| kfree(np); |
| MOD_DEC_USE_COUNT; |
| } |
| MOD_DEC_USE_COUNT; |
| } |
| |
| /* -------- NCCI Handling ------------------------------------- */ |
| |
| static inline void mq_init(struct capi_ncci * np) |
| { |
| int i; |
| np->lock = SPIN_LOCK_UNLOCKED; |
| np->msgidqueue = 0; |
| np->msgidlast = 0; |
| np->nmsg = 0; |
| memset(np->msgidpool, 0, sizeof(np->msgidpool)); |
| np->msgidfree = &np->msgidpool[0]; |
| for (i = 1; i < np->winsize; i++) { |
| np->msgidpool[i].next = np->msgidfree; |
| np->msgidfree = &np->msgidpool[i]; |
| } |
| } |
| |
| static inline int mq_enqueue(struct capi_ncci * np, __u16 msgid) |
| { |
| struct msgidqueue *mq; |
| spin_lock_bh(&np->lock); |
| if ((mq = np->msgidfree) == 0) { |
| spin_unlock_bh(&np->lock); |
| return 0; |
| } |
| np->msgidfree = mq->next; |
| mq->msgid = msgid; |
| mq->next = 0; |
| if (np->msgidlast) |
| np->msgidlast->next = mq; |
| np->msgidlast = mq; |
| if (!np->msgidqueue) |
| np->msgidqueue = mq; |
| np->nmsg++; |
| spin_unlock_bh(&np->lock); |
| return 1; |
| } |
| |
| static inline int mq_dequeue(struct capi_ncci * np, __u16 msgid) |
| { |
| struct msgidqueue **pp; |
| spin_lock_bh(&np->lock); |
| for (pp = &np->msgidqueue; *pp; pp = &(*pp)->next) { |
| if ((*pp)->msgid == msgid) { |
| struct msgidqueue *mq = *pp; |
| *pp = mq->next; |
| if (mq == np->msgidlast) |
| np->msgidlast = 0; |
| mq->next = np->msgidfree; |
| np->msgidfree = mq; |
| np->nmsg--; |
| spin_unlock_bh(&np->lock); |
| return 1; |
| } |
| } |
| spin_unlock_bh(&np->lock); |
| return 0; |
| } |
| |
| static void controllercb_appl_registered(struct capi_ctr * card, __u16 appl) |
| { |
| } |
| |
| static void controllercb_appl_released(struct capi_ctr * card, __u16 appl) |
| { |
| struct capi_ncci **pp, **nextpp; |
| for (pp = &APPL(appl)->nccilist; *pp; pp = nextpp) { |
| if (NCCI2CTRL((*pp)->ncci) == card->cnr) { |
| struct capi_ncci *np = *pp; |
| *pp = np->next; |
| printk(KERN_INFO "kcapi: appl %d ncci 0x%x down!\n", appl, np->ncci); |
| kfree(np); |
| APPL(appl)->nncci--; |
| nextpp = pp; |
| } else { |
| nextpp = &(*pp)->next; |
| } |
| } |
| if (APPL(appl)->releasing) { /* only release if the application was marked for release */ |
| printk(KERN_DEBUG "kcapi: appl %d releasing(%d)\n", appl, APPL(appl)->releasing); |
| APPL(appl)->releasing--; |
| if (APPL(appl)->releasing <= 0) { |
| APPL(appl)->signal = 0; |
| APPL_MARK_FREE(appl); |
| printk(KERN_INFO "kcapi: appl %d down\n", appl); |
| } |
| } else |
| printk(KERN_WARNING "kcapi: appl %d card%d released without request\n", appl, card->cnr); |
| } |
| /* |
| * ncci management |
| */ |
| |
| static void controllercb_new_ncci(struct capi_ctr * card, |
| __u16 appl, __u32 ncci, __u32 winsize) |
| { |
| struct capi_ncci *np; |
| if (!VALID_APPLID(appl)) { |
| printk(KERN_ERR "avmb1_handle_new_ncci: illegal appl %d\n", appl); |
| return; |
| } |
| if ((np = (struct capi_ncci *) kmalloc(sizeof(struct capi_ncci), GFP_ATOMIC)) == 0) { |
| printk(KERN_ERR "capi_new_ncci: alloc failed ncci 0x%x\n", ncci); |
| return; |
| } |
| if (winsize > CAPI_MAXDATAWINDOW) { |
| printk(KERN_ERR "capi_new_ncci: winsize %d too big, set to %d\n", |
| winsize, CAPI_MAXDATAWINDOW); |
| winsize = CAPI_MAXDATAWINDOW; |
| } |
| np->applid = appl; |
| np->ncci = ncci; |
| np->winsize = winsize; |
| mq_init(np); |
| np->next = APPL(appl)->nccilist; |
| APPL(appl)->nccilist = np; |
| APPL(appl)->nncci++; |
| printk(KERN_INFO "kcapi: appl %d ncci 0x%x up\n", appl, ncci); |
| |
| notify_push(KCI_NCCIUP, CARDNR(card), appl, ncci); |
| } |
| |
| static void controllercb_free_ncci(struct capi_ctr * card, |
| __u16 appl, __u32 ncci) |
| { |
| struct capi_ncci **pp; |
| if (!VALID_APPLID(appl)) { |
| printk(KERN_ERR "free_ncci: illegal appl %d\n", appl); |
| return; |
| } |
| for (pp = &APPL(appl)->nccilist; *pp; pp = &(*pp)->next) { |
| if ((*pp)->ncci == ncci) { |
| struct capi_ncci *np = *pp; |
| *pp = np->next; |
| kfree(np); |
| APPL(appl)->nncci--; |
| printk(KERN_INFO "kcapi: appl %d ncci 0x%x down\n", appl, ncci); |
| notify_push(KCI_NCCIDOWN, CARDNR(card), appl, ncci); |
| return; |
| } |
| } |
| printk(KERN_ERR "free_ncci: ncci 0x%x not found\n", ncci); |
| } |
| |
| |
| static struct capi_ncci *find_ncci(struct capi_appl * app, __u32 ncci) |
| { |
| struct capi_ncci *np; |
| for (np = app->nccilist; np; np = np->next) { |
| if (np->ncci == ncci) |
| return np; |
| } |
| return 0; |
| } |
| |
| /* -------- Receiver ------------------------------------------ */ |
| |
| static void recv_handler(void *dummy) |
| { |
| struct sk_buff *skb; |
| |
| while ((skb = skb_dequeue(&recv_queue)) != 0) { |
| __u16 appl = CAPIMSG_APPID(skb->data); |
| struct capi_ncci *np; |
| if (!VALID_APPLID(appl)) { |
| printk(KERN_ERR "kcapi: recv_handler: applid %d ? (%s)\n", |
| appl, capi_message2str(skb->data)); |
| kfree_skb(skb); |
| continue; |
| } |
| if (APPL(appl)->signal == 0) { |
| printk(KERN_ERR "kcapi: recv_handler: applid %d has no signal function\n", |
| appl); |
| kfree_skb(skb); |
| continue; |
| } |
| if ( CAPIMSG_COMMAND(skb->data) == CAPI_DATA_B3 |
| && CAPIMSG_SUBCOMMAND(skb->data) == CAPI_CONF |
| && (np = find_ncci(APPL(appl), CAPIMSG_NCCI(skb->data))) != 0 |
| && mq_dequeue(np, CAPIMSG_MSGID(skb->data)) == 0) { |
| printk(KERN_ERR "kcapi: msgid %hu ncci 0x%x not on queue\n", |
| CAPIMSG_MSGID(skb->data), np->ncci); |
| } |
| if ( CAPIMSG_COMMAND(skb->data) == CAPI_DATA_B3 |
| && CAPIMSG_SUBCOMMAND(skb->data) == CAPI_IND) { |
| APPL(appl)->nrecvdatapkt++; |
| } else { |
| APPL(appl)->nrecvctlpkt++; |
| } |
| skb_queue_tail(&APPL(appl)->recv_queue, skb); |
| (APPL(appl)->signal) (APPL(appl)->applid, APPL(appl)->param); |
| } |
| } |
| |
| static void controllercb_handle_capimsg(struct capi_ctr * card, |
| __u16 appl, struct sk_buff *skb) |
| { |
| int showctl = 0; |
| __u8 cmd, subcmd; |
| |
| 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)); |
| } |
| |
| } |
| skb_queue_tail(&recv_queue, skb); |
| queue_task(&tq_recv_notify, &tq_immediate); |
| mark_bh(IMMEDIATE_BH); |
| return; |
| |
| error: |
| kfree_skb(skb); |
| } |
| |
| static void controllercb_ready(struct capi_ctr * card) |
| { |
| card->cardstate = CARD_RUNNING; |
| printk(KERN_NOTICE "kcapi: card %d \"%s\" ready.\n", |
| CARDNR(card), card->name); |
| |
| notify_push(KCI_CONTRUP, CARDNR(card), 0, 0); |
| } |
| |
| static void controllercb_reseted(struct capi_ctr * card) |
| { |
| __u16 appl; |
| |
| if (card->cardstate == CARD_FREE) |
| return; |
| 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 capi_ncci **pp, **nextpp; |
| for (pp = &APPL(appl)->nccilist; *pp; pp = nextpp) { |
| if (NCCI2CTRL((*pp)->ncci) == card->cnr) { |
| struct capi_ncci *np = *pp; |
| *pp = np->next; |
| printk(KERN_INFO "kcapi: appl %d ncci 0x%x forced down!\n", appl, np->ncci); |
| notify_push(KCI_NCCIDOWN, CARDNR(card), appl, np->ncci); |
| kfree(np); |
| nextpp = pp; |
| } else { |
| nextpp = &(*pp)->next; |
| } |
| } |
| } |
| |
| printk(KERN_NOTICE "kcapi: card %d down.\n", CARDNR(card)); |
| |
| notify_push(KCI_CONTRDOWN, CARDNR(card), 0, 0); |
| } |
| |
| static void controllercb_suspend_output(struct capi_ctr *card) |
| { |
| if (!card->blocked) { |
| printk(KERN_DEBUG "kcapi: card %d suspend\n", CARDNR(card)); |
| card->blocked = 1; |
| } |
| } |
| |
| static void controllercb_resume_output(struct capi_ctr *card) |
| { |
| if (card->blocked) { |
| printk(KERN_DEBUG "kcapi: card %d resume\n", CARDNR(card)); |
| card->blocked = 0; |
| } |
| } |
| |
| /* ------------------------------------------------------------- */ |
| |
| |
| struct capi_ctr * |
| drivercb_attach_ctr(struct capi_driver *driver, char *name, void *driverdata) |
| { |
| struct capi_ctr *card, **pp; |
| int i; |
| |
| for (i=0; i < CAPI_MAXCONTR && cards[i].cardstate != CARD_FREE; i++) ; |
| |
| if (i == CAPI_MAXCONTR) { |
| printk(KERN_ERR "kcapi: out of controller slots\n"); |
| return 0; |
| } |
| card = &cards[i]; |
| memset(card, 0, sizeof(struct capi_ctr)); |
| card->driver = driver; |
| card->cnr = CARDNR(card); |
| strncpy(card->name, name, sizeof(card->name)); |
| card->cardstate = CARD_DETECTED; |
| card->blocked = 0; |
| card->driverdata = driverdata; |
| card->traceflag = showcapimsgs; |
| |
| card->ready = controllercb_ready; |
| card->reseted = controllercb_reseted; |
| card->suspend_output = controllercb_suspend_output; |
| card->resume_output = controllercb_resume_output; |
| card->handle_capimsg = controllercb_handle_capimsg; |
| card->appl_registered = controllercb_appl_registered; |
| card->appl_released = controllercb_appl_released; |
| card->new_ncci = controllercb_new_ncci; |
| card->free_ncci = controllercb_free_ncci; |
| |
| for (pp = &driver->controller; *pp; pp = &(*pp)->next) ; |
| card->next = 0; |
| *pp = card; |
| driver->ncontroller++; |
| sprintf(card->procfn, "capi/controllers/%d", card->cnr); |
| card->procent = create_proc_entry(card->procfn, 0, 0); |
| if (card->procent) { |
| card->procent->read_proc = |
| (int (*)(char *,char **,off_t,int,int *,void *)) |
| driver->ctr_read_proc; |
| card->procent->data = card; |
| } |
| |
| ncards++; |
| printk(KERN_NOTICE "kcapi: Controller %d: %s attached\n", |
| card->cnr, card->name); |
| return card; |
| } |
| |
| static int drivercb_detach_ctr(struct capi_ctr *card) |
| { |
| struct capi_driver *driver = card->driver; |
| struct capi_ctr **pp; |
| |
| if (card->cardstate == CARD_FREE) |
| return 0; |
| if (card->cardstate != CARD_DETECTED) |
| controllercb_reseted(card); |
| for (pp = &driver->controller; *pp ; pp = &(*pp)->next) { |
| if (*pp == card) { |
| *pp = card->next; |
| driver->ncontroller--; |
| ncards--; |
| break; |
| } |
| } |
| if (card->procent) { |
| remove_proc_entry(card->procfn, 0); |
| card->procent = 0; |
| } |
| card->cardstate = CARD_FREE; |
| printk(KERN_NOTICE "kcapi: Controller %d: %s unregistered\n", |
| card->cnr, card->name); |
| return 0; |
| } |
| |
| /* ------------------------------------------------------------- */ |
| |
| /* fallback if no driver read_proc function defined by driver */ |
| |
| static int driver_read_proc(char *page, char **start, off_t off, |
| int count, int *eof, void *data) |
| { |
| struct capi_driver *driver = (struct capi_driver *)data; |
| int len = 0; |
| |
| len += sprintf(page+len, "%-16s %s\n", "name", driver->name); |
| len += sprintf(page+len, "%-16s %s\n", "revision", driver->revision); |
| |
| if (len < off) |
| return 0; |
| *eof = 1; |
| *start = page + off; |
| return ((count < len-off) ? count : len-off); |
| } |
| |
| /* ------------------------------------------------------------- */ |
| |
| static struct capi_driver_interface di = { |
| drivercb_attach_ctr, |
| drivercb_detach_ctr, |
| }; |
| |
| struct capi_driver_interface *attach_capi_driver(struct capi_driver *driver) |
| { |
| struct capi_driver **pp; |
| |
| MOD_INC_USE_COUNT; |
| spin_lock(&drivers_lock); |
| for (pp = &drivers; *pp; pp = &(*pp)->next) ; |
| driver->next = 0; |
| *pp = driver; |
| spin_unlock(&drivers_lock); |
| printk(KERN_NOTICE "kcapi: driver %s attached\n", driver->name); |
| sprintf(driver->procfn, "capi/drivers/%s", driver->name); |
| driver->procent = create_proc_entry(driver->procfn, 0, 0); |
| if (driver->procent) { |
| if (driver->driver_read_proc) { |
| driver->procent->read_proc = |
| (int (*)(char *,char **,off_t,int,int *,void *)) |
| driver->driver_read_proc; |
| } else { |
| driver->procent->read_proc = driver_read_proc; |
| } |
| driver->procent->data = driver; |
| } |
| return &di; |
| } |
| |
| void detach_capi_driver(struct capi_driver *driver) |
| { |
| struct capi_driver **pp; |
| spin_lock(&drivers_lock); |
| for (pp = &drivers; *pp && *pp != driver; pp = &(*pp)->next) ; |
| if (*pp) { |
| *pp = (*pp)->next; |
| printk(KERN_NOTICE "kcapi: driver %s detached\n", driver->name); |
| } else { |
| printk(KERN_ERR "kcapi: driver %s double detach ?\n", driver->name); |
| } |
| spin_unlock(&drivers_lock); |
| if (driver->procent) { |
| remove_proc_entry(driver->procfn, 0); |
| driver->procent = 0; |
| } |
| MOD_DEC_USE_COUNT; |
| } |
| |
| /* ------------------------------------------------------------- */ |
| /* -------- CAPI2.0 Interface ---------------------------------- */ |
| /* ------------------------------------------------------------- */ |
| |
| static __u16 capi_isinstalled(void) |
| { |
| int i; |
| for (i = 0; i < CAPI_MAXCONTR; i++) { |
| if (cards[i].cardstate == CARD_RUNNING) |
| return CAPI_NOERROR; |
| } |
| return CAPI_REGNOTINSTALLED; |
| } |
| |
| static __u16 capi_register(capi_register_params * rparam, __u16 * applidp) |
| { |
| int appl; |
| int i; |
| |
| if (rparam->datablklen < 128) |
| return CAPI_LOGBLKSIZETOSMALL; |
| |
| for (appl = 1; appl <= CAPI_MAXAPPL; appl++) { |
| if (APPL_IS_FREE(appl)) |
| break; |
| } |
| if (appl > CAPI_MAXAPPL) |
| return CAPI_TOOMANYAPPLS; |
| |
| APPL_MARK_USED(appl); |
| skb_queue_head_init(&APPL(appl)->recv_queue); |
| APPL(appl)->nncci = 0; |
| |
| memcpy(&APPL(appl)->rparam, rparam, sizeof(capi_register_params)); |
| |
| for (i = 0; i < CAPI_MAXCONTR; i++) { |
| if (cards[i].cardstate != CARD_RUNNING) |
| continue; |
| cards[i].driver->register_appl(&cards[i], appl, |
| &APPL(appl)->rparam); |
| } |
| *applidp = appl; |
| printk(KERN_INFO "kcapi: appl %d up\n", appl); |
| |
| return CAPI_NOERROR; |
| } |
| |
| static __u16 capi_release(__u16 applid) |
| { |
| int i; |
| |
| if (!VALID_APPLID(applid) || APPL(applid)->releasing) |
| return CAPI_ILLAPPNR; |
| APPL(applid)->releasing++; |
| skb_queue_purge(&APPL(applid)->recv_queue); |
| for (i = 0; i < CAPI_MAXCONTR; i++) { |
| if (cards[i].cardstate != CARD_RUNNING) |
| continue; |
| APPL(applid)->releasing++; |
| cards[i].driver->release_appl(&cards[i], applid); |
| } |
| APPL(applid)->releasing--; |
| if (APPL(applid)->releasing <= 0) { |
| APPL(applid)->signal = 0; |
| APPL_MARK_FREE(applid); |
| printk(KERN_INFO "kcapi: appl %d down\n", applid); |
| } |
| return CAPI_NOERROR; |
| } |
| |
| static __u16 capi_put_message(__u16 applid, struct sk_buff *skb) |
| { |
| struct capi_ncci *np; |
| __u32 contr; |
| int showctl = 0; |
| __u8 cmd, subcmd; |
| |
| if (ncards == 0) |
| return CAPI_REGNOTINSTALLED; |
| if (!VALID_APPLID(applid)) |
| return CAPI_ILLAPPNR; |
| if (skb->len < 12 |
| || !capi_cmd_valid(CAPIMSG_COMMAND(skb->data)) |
| || !capi_subcmd_valid(CAPIMSG_SUBCOMMAND(skb->data))) |
| return CAPI_ILLCMDORSUBCMDORMSGTOSMALL; |
| contr = CAPIMSG_CONTROLLER(skb->data); |
| if (!VALID_CARD(contr) || CARD(contr)->cardstate != CARD_RUNNING) { |
| contr = 1; |
| if (CARD(contr)->cardstate != CARD_RUNNING) |
| return CAPI_REGNOTINSTALLED; |
| } |
| if (CARD(contr)->blocked) |
| return CAPI_SENDQUEUEFULL; |
| |
| cmd = CAPIMSG_COMMAND(skb->data); |
| subcmd = CAPIMSG_SUBCOMMAND(skb->data); |
| |
| if (cmd == CAPI_DATA_B3 && subcmd== CAPI_REQ) { |
| if ((np = find_ncci(APPL(applid), CAPIMSG_NCCI(skb->data))) != 0 |
| && mq_enqueue(np, CAPIMSG_MSGID(skb->data)) == 0) |
| return CAPI_SENDQUEUEFULL; |
| CARD(contr)->nsentdatapkt++; |
| APPL(applid)->nsentdatapkt++; |
| if (CARD(contr)->traceflag > 2) showctl |= 2; |
| } else { |
| CARD(contr)->nsentctlpkt++; |
| APPL(applid)->nsentctlpkt++; |
| if (CARD(contr)->traceflag) showctl |= 2; |
| } |
| showctl |= (CARD(contr)->traceflag & 1); |
| if (showctl & 2) { |
| if (showctl & 1) { |
| printk(KERN_DEBUG "kcapi: put [0x%lx] id#%d %s len=%u\n", |
| (unsigned long) contr, |
| CAPIMSG_APPID(skb->data), |
| capi_cmd2str(cmd, subcmd), |
| CAPIMSG_LEN(skb->data)); |
| } else { |
| printk(KERN_DEBUG "kcapi: put [0x%lx] %s\n", |
| (unsigned long) contr, |
| capi_message2str(skb->data)); |
| } |
| |
| } |
| CARD(contr)->driver->send_message(CARD(contr), skb); |
| return CAPI_NOERROR; |
| } |
| |
| static __u16 capi_get_message(__u16 applid, struct sk_buff **msgp) |
| { |
| struct sk_buff *skb; |
| |
| if (!VALID_APPLID(applid)) |
| return CAPI_ILLAPPNR; |
| if ((skb = skb_dequeue(&APPL(applid)->recv_queue)) == 0) |
| return CAPI_RECEIVEQUEUEEMPTY; |
| *msgp = skb; |
| return CAPI_NOERROR; |
| } |
| |
| static __u16 capi_set_signal(__u16 applid, |
| void (*signal) (__u16 applid, void *param), |
| void *param) |
| { |
| if (!VALID_APPLID(applid)) |
| return CAPI_ILLAPPNR; |
| APPL(applid)->signal = signal; |
| APPL(applid)->param = param; |
| return CAPI_NOERROR; |
| } |
| |
| static __u16 capi_get_manufacturer(__u32 contr, __u8 buf[CAPI_MANUFACTURER_LEN]) |
| { |
| if (contr == 0) { |
| strncpy(buf, capi_manufakturer, CAPI_MANUFACTURER_LEN); |
| return CAPI_NOERROR; |
| } |
| if (!VALID_CARD(contr) || CARD(contr)->cardstate != CARD_RUNNING) |
| return CAPI_REGNOTINSTALLED; |
| |
| strncpy(buf, CARD(contr)->manu, CAPI_MANUFACTURER_LEN); |
| return CAPI_NOERROR; |
| } |
| |
| static __u16 capi_get_version(__u32 contr, struct capi_version *verp) |
| { |
| if (contr == 0) { |
| *verp = driver_version; |
| return CAPI_NOERROR; |
| } |
| if (!VALID_CARD(contr) || CARD(contr)->cardstate != CARD_RUNNING) |
| return CAPI_REGNOTINSTALLED; |
| |
| memcpy((void *) verp, &CARD(contr)->version, sizeof(capi_version)); |
| return CAPI_NOERROR; |
| } |
| |
| static __u16 capi_get_serial(__u32 contr, __u8 serial[CAPI_SERIAL_LEN]) |
| { |
| if (contr == 0) { |
| strncpy(serial, driver_serial, CAPI_SERIAL_LEN); |
| return CAPI_NOERROR; |
| } |
| if (!VALID_CARD(contr) || CARD(contr)->cardstate != CARD_RUNNING) |
| return CAPI_REGNOTINSTALLED; |
| |
| strncpy((void *) serial, CARD(contr)->serial, CAPI_SERIAL_LEN); |
| return CAPI_NOERROR; |
| } |
| |
| static __u16 capi_get_profile(__u32 contr, struct capi_profile *profp) |
| { |
| if (contr == 0) { |
| profp->ncontroller = ncards; |
| return CAPI_NOERROR; |
| } |
| if (!VALID_CARD(contr) || CARD(contr)->cardstate != CARD_RUNNING) |
| return CAPI_REGNOTINSTALLED; |
| |
| memcpy((void *) profp, &CARD(contr)->profile, |
| sizeof(struct capi_profile)); |
| return CAPI_NOERROR; |
| } |
| |
| static struct capi_driver *find_driver(char *name) |
| { |
| struct capi_driver *dp; |
| spin_lock(&drivers_lock); |
| for (dp = drivers; dp; dp = dp->next) |
| if (strcmp(dp->name, name) == 0) |
| break; |
| spin_unlock(&drivers_lock); |
| return dp; |
| } |
| |
| #ifdef CONFIG_AVMB1_COMPAT |
| static int old_capi_manufacturer(unsigned int cmd, void *data) |
| { |
| avmb1_loadandconfigdef ldef; |
| avmb1_extcarddef cdef; |
| avmb1_resetdef rdef; |
| avmb1_getdef gdef; |
| struct capi_driver *driver; |
| struct capi_ctr *card; |
| capicardparams cparams; |
| capiloaddata ldata; |
| int retval; |
| |
| switch (cmd) { |
| case AVMB1_ADDCARD: |
| case AVMB1_ADDCARD_WITH_TYPE: |
| if (cmd == AVMB1_ADDCARD) { |
| if ((retval = copy_from_user((void *) &cdef, data, |
| sizeof(avmb1_carddef)))) |
| return retval; |
| cdef.cardtype = AVM_CARDTYPE_B1; |
| } else { |
| if ((retval = copy_from_user((void *) &cdef, data, |
| sizeof(avmb1_extcarddef)))) |
| return retval; |
| } |
| cparams.port = cdef.port; |
| cparams.irq = cdef.irq; |
| cparams.cardnr = cdef.cardnr; |
| |
| switch (cdef.cardtype) { |
| case AVM_CARDTYPE_B1: |
| driver = find_driver("b1isa"); |
| break; |
| case AVM_CARDTYPE_T1: |
| driver = find_driver("t1isa"); |
| break; |
| default: |
| driver = 0; |
| break; |
| } |
| if (!driver) { |
| printk(KERN_ERR "kcapi: driver not loaded.\n"); |
| return -EIO; |
| } |
| if (!driver->add_card) { |
| printk(KERN_ERR "kcapi: driver has no add card function.\n"); |
| return -EIO; |
| } |
| |
| return driver->add_card(driver, &cparams); |
| |
| case AVMB1_LOAD: |
| case AVMB1_LOAD_AND_CONFIG: |
| |
| if (cmd == AVMB1_LOAD) { |
| if ((retval = copy_from_user((void *) &ldef, data, |
| sizeof(avmb1_loaddef)))) |
| return retval; |
| ldef.t4config.len = 0; |
| ldef.t4config.data = 0; |
| } else { |
| if ((retval = copy_from_user((void *) &ldef, data, |
| sizeof(avmb1_loadandconfigdef)))) |
| return retval; |
| } |
| if (!VALID_CARD(ldef.contr)) |
| return -ESRCH; |
| |
| card = CARD(ldef.contr); |
| if (card->cardstate == CARD_FREE) |
| return -ESRCH; |
| if (card->driver->load_firmware == 0) { |
| printk(KERN_DEBUG "kcapi: load: driver \%s\" has no load function\n", card->driver->name); |
| 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->driver->load_firmware(card, &ldata); |
| |
| if (retval) { |
| card->cardstate = CARD_DETECTED; |
| return retval; |
| } |
| |
| while (card->cardstate != CARD_RUNNING) { |
| |
| set_current_state(TASK_INTERRUPTIBLE); |
| schedule_timeout(HZ/10); /* 0.1 sec */ |
| |
| if (signal_pending(current)) |
| return -EINTR; |
| } |
| return 0; |
| |
| case AVMB1_RESETCARD: |
| if ((retval = copy_from_user((void *) &rdef, data, |
| sizeof(avmb1_resetdef)))) |
| return retval; |
| if (!VALID_CARD(rdef.contr)) |
| return -ESRCH; |
| card = CARD(rdef.contr); |
| |
| if (card->cardstate == CARD_FREE) |
| return -ESRCH; |
| if (card->cardstate == CARD_DETECTED) |
| return 0; |
| |
| card->driver->reset_ctr(card); |
| |
| while (card->cardstate > CARD_DETECTED) { |
| |
| set_current_state(TASK_INTERRUPTIBLE); |
| schedule_timeout(HZ/10); /* 0.1 sec */ |
| |
| if (signal_pending(current)) |
| return -EINTR; |
| } |
| return 0; |
| |
| case AVMB1_GET_CARDINFO: |
| if ((retval = copy_from_user((void *) &gdef, data, |
| sizeof(avmb1_getdef)))) |
| return retval; |
| |
| if (!VALID_CARD(gdef.contr)) |
| return -ESRCH; |
| |
| card = CARD(gdef.contr); |
| |
| if (card->cardstate == CARD_FREE) |
| return -ESRCH; |
| |
| gdef.cardstate = card->cardstate; |
| if (card->driver == find_driver("t1isa")) |
| gdef.cardtype = AVM_CARDTYPE_T1; |
| else gdef.cardtype = AVM_CARDTYPE_B1; |
| |
| if ((retval = copy_to_user(data, (void *) &gdef, |
| sizeof(avmb1_getdef)))) |
| return retval; |
| |
| return 0; |
| |
| case AVMB1_REMOVECARD: |
| if ((retval = copy_from_user((void *) &rdef, data, |
| sizeof(avmb1_resetdef)))) |
| return retval; |
| |
| if (!VALID_CARD(rdef.contr)) |
| return -ESRCH; |
| card = CARD(rdef.contr); |
| |
| if (card->cardstate == CARD_FREE) |
| return -ESRCH; |
| |
| if (card->cardstate != CARD_DETECTED) |
| return -EBUSY; |
| |
| card->driver->remove_ctr(card); |
| |
| while (card->cardstate != CARD_FREE) { |
| |
| set_current_state(TASK_INTERRUPTIBLE); |
| schedule_timeout(HZ/10); /* 0.1 sec */ |
| |
| if (signal_pending(current)) |
| return -EINTR; |
| } |
| return 0; |
| } |
| return -EINVAL; |
| } |
| #endif |
| |
| static int capi_manufacturer(unsigned int cmd, void *data) |
| { |
| struct capi_ctr *card; |
| int retval; |
| |
| switch (cmd) { |
| #ifdef CONFIG_AVMB1_COMPAT |
| case AVMB1_ADDCARD: |
| case AVMB1_ADDCARD_WITH_TYPE: |
| 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 ((retval = copy_from_user((void *) &fdef, data, |
| sizeof(kcapi_flagdef)))) |
| return retval; |
| |
| if (!VALID_CARD(fdef.contr)) |
| return -ESRCH; |
| card = CARD(fdef.contr); |
| if (card->cardstate == CARD_FREE) |
| 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 capi_driver *driver; |
| capicardparams cparams; |
| kcapi_carddef cdef; |
| |
| if ((retval = copy_from_user((void *) &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; |
| |
| if ((driver = find_driver(cdef.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; |
| } |
| |
| struct capi_interface avmb1_interface = |
| { |
| capi_isinstalled, |
| capi_register, |
| capi_release, |
| capi_put_message, |
| capi_get_message, |
| capi_set_signal, |
| capi_get_manufacturer, |
| capi_get_version, |
| capi_get_serial, |
| capi_get_profile, |
| capi_manufacturer |
| }; |
| |
| /* ------------------------------------------------------------- */ |
| /* -------- Exported Functions --------------------------------- */ |
| /* ------------------------------------------------------------- */ |
| |
| struct capi_interface *attach_capi_interface(struct capi_interface_user *userp) |
| { |
| struct capi_interface_user *p; |
| |
| MOD_INC_USE_COUNT; |
| spin_lock(&capi_users_lock); |
| for (p = capi_users; p; p = p->next) { |
| if (p == userp) { |
| spin_unlock(&capi_users_lock); |
| printk(KERN_ERR "kcapi: double attach from %s\n", |
| userp->name); |
| MOD_DEC_USE_COUNT; |
| return 0; |
| } |
| } |
| userp->next = capi_users; |
| capi_users = userp; |
| spin_unlock(&capi_users_lock); |
| printk(KERN_NOTICE "kcapi: %s attached\n", userp->name); |
| |
| return &avmb1_interface; |
| } |
| |
| int detach_capi_interface(struct capi_interface_user *userp) |
| { |
| struct capi_interface_user **pp; |
| |
| spin_lock(&capi_users_lock); |
| for (pp = &capi_users; *pp; pp = &(*pp)->next) { |
| if (*pp == userp) { |
| *pp = userp->next; |
| spin_unlock(&capi_users_lock); |
| userp->next = 0; |
| printk(KERN_NOTICE "kcapi: %s detached\n", userp->name); |
| MOD_DEC_USE_COUNT; |
| return 0; |
| } |
| } |
| spin_unlock(&capi_users_lock); |
| printk(KERN_ERR "kcapi: double detach from %s\n", userp->name); |
| return -1; |
| } |
| |
| /* ------------------------------------------------------------- */ |
| /* -------- Init & Cleanup ------------------------------------- */ |
| /* ------------------------------------------------------------- */ |
| |
| EXPORT_SYMBOL(attach_capi_interface); |
| EXPORT_SYMBOL(detach_capi_interface); |
| EXPORT_SYMBOL(attach_capi_driver); |
| EXPORT_SYMBOL(detach_capi_driver); |
| |
| /* |
| * init / exit functions |
| */ |
| |
| static int __init kcapi_init(void) |
| { |
| char *p; |
| char rev[32]; |
| |
| MOD_INC_USE_COUNT; |
| |
| skb_queue_head_init(&recv_queue); |
| |
| tq_state_notify.routine = notify_handler; |
| tq_state_notify.data = 0; |
| |
| tq_recv_notify.routine = recv_handler; |
| tq_recv_notify.data = 0; |
| |
| proc_capi_init(); |
| |
| if ((p = strchr(revision, ':')) != 0 && p[1]) { |
| strncpy(rev, p + 2, sizeof(rev)); |
| rev[sizeof(rev)-1] = 0; |
| if ((p = strchr(rev, '$')) != 0 && p > rev) |
| *(p-1) = 0; |
| } else |
| strcpy(rev, "1.0"); |
| |
| #ifdef MODULE |
| printk(KERN_NOTICE "CAPI-driver Rev %s: loaded\n", rev); |
| #else |
| printk(KERN_NOTICE "CAPI-driver Rev %s: started\n", rev); |
| #endif |
| MOD_DEC_USE_COUNT; |
| return 0; |
| } |
| |
| static void __exit kcapi_exit(void) |
| { |
| char rev[10]; |
| char *p; |
| |
| if ((p = strchr(revision, ':'))) { |
| strcpy(rev, p + 1); |
| p = strchr(rev, '$'); |
| *p = 0; |
| } else { |
| strcpy(rev, "1.0"); |
| } |
| |
| proc_capi_exit(); |
| printk(KERN_NOTICE "CAPI-driver Rev%s: unloaded\n", rev); |
| } |
| |
| module_init(kcapi_init); |
| module_exit(kcapi_exit); |