/* $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 * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "capicmd.h" #include "capiutil.h" #include "capilli.h" #ifdef CONFIG_AVMB1_COMPAT #include #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);