📄 isdnloop.c
字号:
/* $Id: isdnloop.c,v 1.11.6.7 2001/11/11 19:54:31 kai Exp $ * * ISDN low-level module implementing a dummy loop driver. * * Copyright 1997 by Fritz Elfert (fritz@isdn4linux.de) * * This software may be used and distributed according to the terms * of the GNU General Public License, incorporated herein by reference. * */#include <linux/config.h>#include <linux/module.h>#include <linux/interrupt.h>#include <linux/init.h>#include <linux/sched.h>#include "isdnloop.h"static char *revision = "$Revision: 1.11.6.7 $";static char *isdnloop_id = "loop0";MODULE_DESCRIPTION("ISDN4Linux: Pseudo Driver that simulates an ISDN card");MODULE_AUTHOR("Fritz Elfert");MODULE_LICENSE("GPL");MODULE_PARM(isdnloop_id, "s");MODULE_PARM_DESC(isdnloop_id, "ID-String of first card");static int isdnloop_addcard(char *);/* * Free queue completely. * * Parameter: * card = pointer to card struct * channel = channel number */static voidisdnloop_free_queue(isdnloop_card * card, int channel){ struct sk_buff_head *queue = &card->bqueue[channel]; skb_queue_purge(queue); card->sndcount[channel] = 0;}/* * Send B-Channel data to another virtual card. * This routine is called via timer-callback from isdnloop_pollbchan(). * * Parameter: * card = pointer to card struct. * ch = channel number (0-based) */static voidisdnloop_bchan_send(isdnloop_card * card, int ch){ isdnloop_card *rcard = card->rcard[ch]; int rch = card->rch[ch], len, ack; struct sk_buff *skb; isdn_ctrl cmd; while (card->sndcount[ch]) { if ((skb = skb_dequeue(&card->bqueue[ch]))) { len = skb->len; card->sndcount[ch] -= len; ack = *(skb->head); /* used as scratch area */ cmd.driver = card->myid; cmd.arg = ch; if (rcard){ rcard->interface.rcvcallb_skb(rcard->myid, rch, skb); } else { printk(KERN_WARNING "isdnloop: no rcard, skb dropped\n"); dev_kfree_skb(skb); }; cmd.command = ISDN_STAT_BSENT; cmd.parm.length = len; card->interface.statcallb(&cmd); } else card->sndcount[ch] = 0; }}/* * Send/Receive Data to/from the B-Channel. * This routine is called via timer-callback. * It schedules itself while any B-Channel is open. * * Parameter: * data = pointer to card struct, set by kernel timer.data */static voidisdnloop_pollbchan(unsigned long data){ isdnloop_card *card = (isdnloop_card *) data; unsigned long flags; if (card->flags & ISDNLOOP_FLAGS_B1ACTIVE) isdnloop_bchan_send(card, 0); if (card->flags & ISDNLOOP_FLAGS_B2ACTIVE) isdnloop_bchan_send(card, 1); if (card->flags & (ISDNLOOP_FLAGS_B1ACTIVE | ISDNLOOP_FLAGS_B2ACTIVE)) { /* schedule b-channel polling again */ save_flags(flags); cli(); card->rb_timer.expires = jiffies + ISDNLOOP_TIMER_BCREAD; add_timer(&card->rb_timer); card->flags |= ISDNLOOP_FLAGS_RBTIMER; restore_flags(flags); } else card->flags &= ~ISDNLOOP_FLAGS_RBTIMER;}/* * Parse ICN-type setup string and fill fields of setup-struct * with parsed data. * * Parameter: * setup = setup string, format: [caller-id],si1,si2,[called-id] * cmd = pointer to struct to be filled. */static voidisdnloop_parse_setup(char *setup, isdn_ctrl * cmd){ char *t = setup; char *s = strchr(t, ','); *s++ = '\0'; strlcpy(cmd->parm.setup.phone, t, sizeof(cmd->parm.setup.phone)); s = strchr(t = s, ','); *s++ = '\0'; if (!strlen(t)) cmd->parm.setup.si1 = 0; else cmd->parm.setup.si1 = simple_strtoul(t, NULL, 10); s = strchr(t = s, ','); *s++ = '\0'; if (!strlen(t)) cmd->parm.setup.si2 = 0; else cmd->parm.setup.si2 = simple_strtoul(t, NULL, 10); strlcpy(cmd->parm.setup.eazmsn, s, sizeof(cmd->parm.setup.eazmsn)); cmd->parm.setup.plan = 0; cmd->parm.setup.screen = 0;}typedef struct isdnloop_stat { char *statstr; int command; int action;} isdnloop_stat;/* *INDENT-OFF* */static isdnloop_stat isdnloop_stat_table[] ={ {"BCON_", ISDN_STAT_BCONN, 1}, /* B-Channel connected */ {"BDIS_", ISDN_STAT_BHUP, 2}, /* B-Channel disconnected */ {"DCON_", ISDN_STAT_DCONN, 0}, /* D-Channel connected */ {"DDIS_", ISDN_STAT_DHUP, 0}, /* D-Channel disconnected */ {"DCAL_I", ISDN_STAT_ICALL, 3}, /* Incoming call dialup-line */ {"DSCA_I", ISDN_STAT_ICALL, 3}, /* Incoming call 1TR6-SPV */ {"FCALL", ISDN_STAT_ICALL, 4}, /* Leased line connection up */ {"CIF", ISDN_STAT_CINF, 5}, /* Charge-info, 1TR6-type */ {"AOC", ISDN_STAT_CINF, 6}, /* Charge-info, DSS1-type */ {"CAU", ISDN_STAT_CAUSE, 7}, /* Cause code */ {"TEI OK", ISDN_STAT_RUN, 0}, /* Card connected to wallplug */ {"E_L1: ACT FAIL", ISDN_STAT_BHUP, 8}, /* Layer-1 activation failed */ {"E_L2: DATA LIN", ISDN_STAT_BHUP, 8}, /* Layer-2 data link lost */ {"E_L1: ACTIVATION FAILED", ISDN_STAT_BHUP, 8}, /* Layer-1 activation failed */ {NULL, 0, -1}};/* *INDENT-ON* *//* * Parse Status message-strings from virtual card. * Depending on status, call statcallb for sending messages to upper * levels. Also set/reset B-Channel active-flags. * * Parameter: * status = status string to parse. * channel = channel where message comes from. * card = card where message comes from. */static voidisdnloop_parse_status(u_char * status, int channel, isdnloop_card * card){ isdnloop_stat *s = isdnloop_stat_table; int action = -1; isdn_ctrl cmd; while (s->statstr) { if (!strncmp(status, s->statstr, strlen(s->statstr))) { cmd.command = s->command; action = s->action; break; } s++; } if (action == -1) return; cmd.driver = card->myid; cmd.arg = channel; switch (action) { case 1: /* BCON_x */ card->flags |= (channel) ? ISDNLOOP_FLAGS_B2ACTIVE : ISDNLOOP_FLAGS_B1ACTIVE; break; case 2: /* BDIS_x */ card->flags &= ~((channel) ? ISDNLOOP_FLAGS_B2ACTIVE : ISDNLOOP_FLAGS_B1ACTIVE); isdnloop_free_queue(card, channel); break; case 3: /* DCAL_I and DSCA_I */ isdnloop_parse_setup(status + 6, &cmd); break; case 4: /* FCALL */ sprintf(cmd.parm.setup.phone, "LEASED%d", card->myid); sprintf(cmd.parm.setup.eazmsn, "%d", channel + 1); cmd.parm.setup.si1 = 7; cmd.parm.setup.si2 = 0; cmd.parm.setup.plan = 0; cmd.parm.setup.screen = 0; break; case 5: /* CIF */ strlcpy(cmd.parm.num, status + 3, sizeof(cmd.parm.num)); break; case 6: /* AOC */ snprintf(cmd.parm.num, sizeof(cmd.parm.num), "%d", (int) simple_strtoul(status + 7, NULL, 16)); break; case 7: /* CAU */ status += 3; if (strlen(status) == 4) snprintf(cmd.parm.num, sizeof(cmd.parm.num), "%s%c%c", status + 2, *status, *(status + 1)); else strlcpy(cmd.parm.num, status + 1, sizeof(cmd.parm.num)); break; case 8: /* Misc Errors on L1 and L2 */ card->flags &= ~ISDNLOOP_FLAGS_B1ACTIVE; isdnloop_free_queue(card, 0); cmd.arg = 0; cmd.driver = card->myid; card->interface.statcallb(&cmd); cmd.command = ISDN_STAT_DHUP; cmd.arg = 0; cmd.driver = card->myid; card->interface.statcallb(&cmd); cmd.command = ISDN_STAT_BHUP; card->flags &= ~ISDNLOOP_FLAGS_B2ACTIVE; isdnloop_free_queue(card, 1); cmd.arg = 1; cmd.driver = card->myid; card->interface.statcallb(&cmd); cmd.command = ISDN_STAT_DHUP; cmd.arg = 1; cmd.driver = card->myid; break; } card->interface.statcallb(&cmd);}/* * Store a cwcharacter into ringbuffer for reading from /dev/isdnctrl * * Parameter: * card = pointer to card struct. * c = char to store. */static voidisdnloop_putmsg(isdnloop_card * card, unsigned char c){ ulong flags; save_flags(flags); cli(); *card->msg_buf_write++ = (c == 0xff) ? '\n' : c; if (card->msg_buf_write == card->msg_buf_read) { if (++card->msg_buf_read > card->msg_buf_end) card->msg_buf_read = card->msg_buf; } if (card->msg_buf_write > card->msg_buf_end) card->msg_buf_write = card->msg_buf; restore_flags(flags);}/* * Poll a virtual cards message queue. * If there are new status-replies from the card, copy them to * ringbuffer for reading on /dev/isdnctrl and call * isdnloop_parse_status() for processing them. Watch for special * Firmware bootmessage and parse it, to get the D-Channel protocol. * If there are B-Channels open, initiate a timer-callback to * isdnloop_pollbchan(). * This routine is called periodically via timer interrupt. * * Parameter: * data = pointer to card struct */static voidisdnloop_polldchan(unsigned long data){ isdnloop_card *card = (isdnloop_card *) data; struct sk_buff *skb; int avail; int left; u_char c; int ch; unsigned long flags; u_char *p; isdn_ctrl cmd; if ((skb = skb_dequeue(&card->dqueue))) avail = skb->len; else avail = 0; for (left = avail; left > 0; left--) { c = *skb->data; skb_pull(skb, 1); isdnloop_putmsg(card, c); card->imsg[card->iptr] = c; if (card->iptr < 59) card->iptr++; if (!skb->len) { avail++; isdnloop_putmsg(card, '\n'); card->imsg[card->iptr] = 0; card->iptr = 0; if (card->imsg[0] == '0' && card->imsg[1] >= '0' && card->imsg[1] <= '2' && card->imsg[2] == ';') { ch = (card->imsg[1] - '0') - 1; p = &card->imsg[3]; isdnloop_parse_status(p, ch, card); } else { p = card->imsg; if (!strncmp(p, "DRV1.", 5)) { printk(KERN_INFO "isdnloop: (%s) %s\n", CID, p); if (!strncmp(p + 7, "TC", 2)) { card->ptype = ISDN_PTYPE_1TR6; card->interface.features |= ISDN_FEATURE_P_1TR6; printk(KERN_INFO "isdnloop: (%s) 1TR6-Protocol loaded and running\n", CID); } if (!strncmp(p + 7, "EC", 2)) { card->ptype = ISDN_PTYPE_EURO; card->interface.features |= ISDN_FEATURE_P_EURO; printk(KERN_INFO "isdnloop: (%s) Euro-Protocol loaded and running\n", CID); } continue; } } } } if (avail) { cmd.command = ISDN_STAT_STAVAIL; cmd.driver = card->myid; cmd.arg = avail; card->interface.statcallb(&cmd); } if (card->flags & (ISDNLOOP_FLAGS_B1ACTIVE | ISDNLOOP_FLAGS_B2ACTIVE)) if (!(card->flags & ISDNLOOP_FLAGS_RBTIMER)) { /* schedule b-channel polling */ card->flags |= ISDNLOOP_FLAGS_RBTIMER; save_flags(flags); cli(); del_timer(&card->rb_timer); card->rb_timer.function = isdnloop_pollbchan; card->rb_timer.data = (unsigned long) card; card->rb_timer.expires = jiffies + ISDNLOOP_TIMER_BCREAD; add_timer(&card->rb_timer); restore_flags(flags); } /* schedule again */ save_flags(flags); cli(); card->st_timer.expires = jiffies + ISDNLOOP_TIMER_DCREAD; add_timer(&card->st_timer); restore_flags(flags);}/* * Append a packet to the transmit buffer-queue. * * Parameter: * channel = Number of B-channel * skb = packet to send. * card = pointer to card-struct * Return: * Number of bytes transferred, -E??? on error */static intisdnloop_sendbuf(int channel, struct sk_buff *skb, isdnloop_card * card){ int len = skb->len; unsigned long flags; struct sk_buff *nskb; if (len > 4000) { printk(KERN_WARNING "isdnloop: Send packet too large\n"); return -EINVAL; } if (len) { if (!(card->flags & (channel) ? ISDNLOOP_FLAGS_B2ACTIVE : ISDNLOOP_FLAGS_B1ACTIVE)) return 0; if (card->sndcount[channel] > ISDNLOOP_MAX_SQUEUE) return 0; save_flags(flags); cli(); nskb = dev_alloc_skb(skb->len); if (nskb) { memcpy(skb_put(nskb, len), skb->data, len); skb_queue_tail(&card->bqueue[channel], nskb); dev_kfree_skb(skb); } else len = 0; card->sndcount[channel] += len; restore_flags(flags); } return len;}/* * Read the messages from the card's ringbuffer * * Parameter: * buf = pointer to buffer. * len = number of bytes to read. * user = flag, 1: called from userlevel 0: called from kernel. * card = pointer to card struct. * Return: * number of bytes actually transferred. */static intisdnloop_readstatus(u_char __user *buf, int len, isdnloop_card * card){ int count; u_char __user *p; for (p = buf, count = 0; count < len; p++, count++) { if (card->msg_buf_read == card->msg_buf_write) return count; put_user(*card->msg_buf_read++, p); if (card->msg_buf_read > card->msg_buf_end) card->msg_buf_read = card->msg_buf; } return count;}/* * Simulate a card's response by appending it to the cards * message queue. * * Parameter: * card = pointer to card struct. * s = pointer to message-string. * ch = channel: 0 = generic messages, 1 and 2 = D-channel messages. * Return: * 0 on success, 1 on memory squeeze. */static intisdnloop_fake(isdnloop_card * card, char *s, int ch){ struct sk_buff *skb; int len = strlen(s) + ((ch >= 0) ? 3 : 0); if (!(skb = dev_alloc_skb(len))) { printk(KERN_WARNING "isdnloop: Out of memory in isdnloop_fake\n"); return 1; } if (ch >= 0) sprintf(skb_put(skb, 3), "%02d;", ch); memcpy(skb_put(skb, strlen(s)), s, strlen(s)); skb_queue_tail(&card->dqueue, skb); return 0;}/* *INDENT-OFF* */static isdnloop_stat isdnloop_cmd_table[] ={ {"BCON_R", 0, 1}, /* B-Channel connect */ {"BCON_I", 0, 17}, /* B-Channel connect ind */ {"BDIS_R", 0, 2}, /* B-Channel disconnect */ {"DDIS_R", 0, 3}, /* D-Channel disconnect */ {"DCON_R", 0, 16}, /* D-Channel connect */ {"DSCA_R", 0, 4}, /* Dial 1TR6-SPV */ {"DCAL_R", 0, 5}, /* Dial */ {"EAZC", 0, 6}, /* Clear EAZ listener */ {"EAZ", 0, 7}, /* Set EAZ listener */ {"SEEAZ", 0, 8}, /* Get EAZ listener */ {"MSN", 0, 9}, /* Set/Clear MSN listener */ {"MSALL", 0, 10}, /* Set multi MSN listeners */ {"SETSIL", 0, 11}, /* Set SI list */ {"SEESIL", 0, 12}, /* Get SI list */ {"SILC", 0, 13}, /* Clear SI list */ {"LOCK", 0, -1}, /* LOCK channel */ {"UNLOCK", 0, -1}, /* UNLOCK channel */ {"FV2ON", 1, 14}, /* Leased mode on */ {"FV2OFF", 1, 15}, /* Leased mode off */ {NULL, 0, -1}};/* *INDENT-ON* *//* * Simulate an error-response from a card. *
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -