linux/drivers/isdn/isdnloop/isdnloop.c

1529 lines
38 KiB
C
Raw Normal View History

/* $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/module.h>
#include <linux/interrupt.h>
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h percpu.h is included by sched.h and module.h and thus ends up being included when building most .c files. percpu.h includes slab.h which in turn includes gfp.h making everything defined by the two files universally available and complicating inclusion dependencies. percpu.h -> slab.h dependency is about to be removed. Prepare for this change by updating users of gfp and slab facilities include those headers directly instead of assuming availability. As this conversion needs to touch large number of source files, the following script is used as the basis of conversion. http://userweb.kernel.org/~tj/misc/slabh-sweep.py The script does the followings. * Scan files for gfp and slab usages and update includes such that only the necessary includes are there. ie. if only gfp is used, gfp.h, if slab is used, slab.h. * When the script inserts a new include, it looks at the include blocks and try to put the new include such that its order conforms to its surrounding. It's put in the include block which contains core kernel includes, in the same order that the rest are ordered - alphabetical, Christmas tree, rev-Xmas-tree or at the end if there doesn't seem to be any matching order. * If the script can't find a place to put a new include (mostly because the file doesn't have fitting include block), it prints out an error message indicating which .h file needs to be added to the file. The conversion was done in the following steps. 1. The initial automatic conversion of all .c files updated slightly over 4000 files, deleting around 700 includes and adding ~480 gfp.h and ~3000 slab.h inclusions. The script emitted errors for ~400 files. 2. Each error was manually checked. Some didn't need the inclusion, some needed manual addition while adding it to implementation .h or embedding .c file was more appropriate for others. This step added inclusions to around 150 files. 3. The script was run again and the output was compared to the edits from #2 to make sure no file was left behind. 4. Several build tests were done and a couple of problems were fixed. e.g. lib/decompress_*.c used malloc/free() wrappers around slab APIs requiring slab.h to be added manually. 5. The script was run on all .h files but without automatically editing them as sprinkling gfp.h and slab.h inclusions around .h files could easily lead to inclusion dependency hell. Most gfp.h inclusion directives were ignored as stuff from gfp.h was usually wildly available and often used in preprocessor macros. Each slab.h inclusion directive was examined and added manually as necessary. 6. percpu.h was updated not to include slab.h. 7. Build test were done on the following configurations and failures were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my distributed build env didn't work with gcov compiles) and a few more options had to be turned off depending on archs to make things build (like ipr on powerpc/64 which failed due to missing writeq). * x86 and x86_64 UP and SMP allmodconfig and a custom test config. * powerpc and powerpc64 SMP allmodconfig * sparc and sparc64 SMP allmodconfig * ia64 SMP allmodconfig * s390 SMP allmodconfig * alpha SMP allmodconfig * um on x86_64 SMP allmodconfig 8. percpu.h modifications were reverted so that it could be applied as a separate patch and serve as bisection point. Given the fact that I had only a couple of failures from tests on step 6, I'm fairly confident about the coverage of this conversion patch. If there is a breakage, it's likely to be something in one of the arch headers which should be easily discoverable easily on most builds of the specific arch. Signed-off-by: Tejun Heo <tj@kernel.org> Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org> Cc: Ingo Molnar <mingo@redhat.com> Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 11:04:11 +03:00
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/sched.h>
#include "isdnloop.h"
static char *isdnloop_id = "loop0";
MODULE_DESCRIPTION("ISDN4Linux: Pseudo Driver that simulates an ISDN card");
MODULE_AUTHOR("Fritz Elfert");
MODULE_LICENSE("GPL");
module_param(isdnloop_id, charp, 0);
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 void
isdnloop_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 void
isdnloop_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]) {
skb = skb_dequeue(&card->bqueue[ch]);
if (skb) {
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 void
isdnloop_pollbchan(struct timer_list *t)
{
isdnloop_card *card = from_timer(card, t, rb_timer);
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 */
spin_lock_irqsave(&card->isdnloop_lock, flags);
card->rb_timer.expires = jiffies + ISDNLOOP_TIMER_BCREAD;
add_timer(&card->rb_timer);
card->flags |= ISDNLOOP_FLAGS_RBTIMER;
spin_unlock_irqrestore(&card->isdnloop_lock, 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 void
isdnloop_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 void
isdnloop_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 void
isdnloop_putmsg(isdnloop_card *card, unsigned char c)
{
ulong flags;
spin_lock_irqsave(&card->isdnloop_lock, flags);
*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;
spin_unlock_irqrestore(&card->isdnloop_lock, 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 void
isdnloop_polldchan(struct timer_list *t)
{
isdnloop_card *card = from_timer(card, t, st_timer);
struct sk_buff *skb;
int avail;
int left;
u_char c;
int ch;
unsigned long flags;
u_char *p;
isdn_ctrl cmd;
skb = skb_dequeue(&card->dqueue);
if (skb)
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;
spin_lock_irqsave(&card->isdnloop_lock, flags);
del_timer(&card->rb_timer);
card->rb_timer.expires = jiffies + ISDNLOOP_TIMER_BCREAD;
add_timer(&card->rb_timer);
spin_unlock_irqrestore(&card->isdnloop_lock, flags);
}
/* schedule again */
spin_lock_irqsave(&card->isdnloop_lock, flags);
card->st_timer.expires = jiffies + ISDNLOOP_TIMER_DCREAD;
add_timer(&card->st_timer);
spin_unlock_irqrestore(&card->isdnloop_lock, 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 int
isdnloop_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;
spin_lock_irqsave(&card->isdnloop_lock, flags);
nskb = dev_alloc_skb(skb->len);
if (nskb) {
skb_copy_from_linear_data(skb,
skb_put(nskb, len), len);
skb_queue_tail(&card->bqueue[channel], nskb);
dev_kfree_skb(skb);
} else
len = 0;
card->sndcount[channel] += len;
spin_unlock_irqrestore(&card->isdnloop_lock, 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 int
isdnloop_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;
if (put_user(*card->msg_buf_read++, p))
return -EFAULT;
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 int
isdnloop_fake(isdnloop_card *card, char *s, int ch)
{
struct sk_buff *skb;
int len = strlen(s) + ((ch >= 0) ? 3 : 0);
skb = dev_alloc_skb(len);
if (!skb) {
printk(KERN_WARNING "isdnloop: Out of memory in isdnloop_fake\n");
return 1;
}
if (ch >= 0)
sprintf(skb_put(skb, 3), "%02d;", ch);
skb_put_data(skb, 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.
*
* Parameter:
* card = pointer to card struct.
*/
static void
isdnloop_fake_err(isdnloop_card *card)
{
char buf[64];
snprintf(buf, sizeof(buf), "E%s", card->omsg);
isdnloop_fake(card, buf, -1);
isdnloop_fake(card, "NAK", -1);
}
static u_char ctable_eu[] = {0x00, 0x11, 0x01, 0x12};
static u_char ctable_1t[] = {0x00, 0x3b, 0x01, 0x3a};
/*
* Assemble a simplified cause message depending on the
* D-channel protocol used.
*
* Parameter:
* card = pointer to card struct.
* loc = location: 0 = local, 1 = remote.
* cau = cause: 1 = busy, 2 = nonexistent callerid, 3 = no user responding.
* Return:
* Pointer to buffer containing the assembled message.
*/
static char *
isdnloop_unicause(isdnloop_card *card, int loc, int cau)
{
static char buf[6];
switch (card->ptype) {
case ISDN_PTYPE_EURO:
sprintf(buf, "E%02X%02X", (loc) ? 4 : 2, ctable_eu[cau]);
break;
case ISDN_PTYPE_1TR6:
sprintf(buf, "%02X44", ctable_1t[cau]);
break;
default:
return "0000";
}
return buf;
}
/*
* Release a virtual connection. Called from timer interrupt, when
* called party did not respond.
*
* Parameter:
* card = pointer to card struct.
* ch = channel (0-based)
*/
static void
isdnloop_atimeout(isdnloop_card *card, int ch)
{
unsigned long flags;
char buf[60];
spin_lock_irqsave(&card->isdnloop_lock, flags);
if (card->rcard) {
isdnloop_fake(card->rcard[ch], "DDIS_I", card->rch[ch] + 1);
card->rcard[ch]->rcard[card->rch[ch]] = NULL;
card->rcard[ch] = NULL;
}
isdnloop_fake(card, "DDIS_I", ch + 1);
/* No user responding */
sprintf(buf, "CAU%s", isdnloop_unicause(card, 1, 3));
isdnloop_fake(card, buf, ch + 1);
spin_unlock_irqrestore(&card->isdnloop_lock, flags);
}
/*
* Wrapper for isdnloop_atimeout().
*/
static void
isdnloop_atimeout0(struct timer_list *t)
{
isdnloop_card *card = from_timer(card, t, c_timer[0]);
isdnloop_atimeout(card, 0);
}
/*
* Wrapper for isdnloop_atimeout().
*/
static void
isdnloop_atimeout1(struct timer_list *t)
{
isdnloop_card *card = from_timer(card, t, c_timer[1]);
isdnloop_atimeout(card, 1);
}
/*
* Install a watchdog for a user, not responding.
*
* Parameter:
* card = pointer to card struct.
* ch = channel to watch for.
*/
static void
isdnloop_start_ctimer(isdnloop_card *card, int ch)
{
unsigned long flags;
spin_lock_irqsave(&card->isdnloop_lock, flags);
timer_setup(&card->c_timer[ch], ch ? isdnloop_atimeout1
: isdnloop_atimeout0, 0);
card->c_timer[ch].expires = jiffies + ISDNLOOP_TIMER_ALERTWAIT;
add_timer(&card->c_timer[ch]);
spin_unlock_irqrestore(&card->isdnloop_lock, flags);
}
/*
* Kill a pending channel watchdog.
*
* Parameter:
* card = pointer to card struct.
* ch = channel (0-based).
*/
static void
isdnloop_kill_ctimer(isdnloop_card *card, int ch)
{
unsigned long flags;
spin_lock_irqsave(&card->isdnloop_lock, flags);
del_timer(&card->c_timer[ch]);
spin_unlock_irqrestore(&card->isdnloop_lock, flags);
}
static u_char si2bit[] = {0, 1, 0, 0, 0, 2, 0, 4, 0, 0};
static u_char bit2si[] = {1, 5, 7};
/*
* Try finding a listener for an outgoing call.
*
* Parameter:
* card = pointer to calling card.
* p = pointer to ICN-type setup-string.
* lch = channel of calling card.
* cmd = pointer to struct to be filled when parsing setup.
* Return:
* 0 = found match, alerting should happen.
* 1 = found matching number but it is busy.
* 2 = no matching listener.
* 3 = found matching number but SI does not match.
*/
static int
isdnloop_try_call(isdnloop_card *card, char *p, int lch, isdn_ctrl *cmd)
{
isdnloop_card *cc = cards;
unsigned long flags;
int ch;
int num_match;
int i;
char *e;
char nbuf[32];
isdnloop_parse_setup(p, cmd);
while (cc) {
for (ch = 0; ch < 2; ch++) {
/* Exclude ourself */
if ((cc == card) && (ch == lch))
continue;
num_match = 0;
switch (cc->ptype) {
case ISDN_PTYPE_EURO:
for (i = 0; i < 3; i++)
if (!(strcmp(cc->s0num[i], cmd->parm.setup.phone)))
num_match = 1;
break;
case ISDN_PTYPE_1TR6:
e = cc->eazlist[ch];
while (*e) {
sprintf(nbuf, "%s%c", cc->s0num[0], *e);
if (!(strcmp(nbuf, cmd->parm.setup.phone)))
num_match = 1;
e++;
}
}
if (num_match) {
spin_lock_irqsave(&card->isdnloop_lock, flags);
/* channel idle? */
if (!(cc->rcard[ch])) {
/* Check SI */
if (!(si2bit[cmd->parm.setup.si1] & cc->sil[ch])) {
spin_unlock_irqrestore(&card->isdnloop_lock, flags);
return 3;
}
/* ch is idle, si and number matches */
cc->rcard[ch] = card;
cc->rch[ch] = lch;
card->rcard[lch] = cc;
card->rch[lch] = ch;
spin_unlock_irqrestore(&card->isdnloop_lock, flags);
return 0;
} else {
spin_unlock_irqrestore(&card->isdnloop_lock, flags);
/* num matches, but busy */
if (ch == 1)
return 1;
}
}
}
cc = cc->next;
}
return 2;
}
/*
* Depending on D-channel protocol and caller/called, modify
* phone number.
*
* Parameter:
* card = pointer to card struct.
* phone = pointer phone number.
* caller = flag: 1 = caller, 0 = called.
* Return:
* pointer to new phone number.
*/
static char *
isdnloop_vstphone(isdnloop_card *card, char *phone, int caller)
{
int i;
static char nphone[30];
if (!card) {
printk("BUG!!!\n");
return "";
}
switch (card->ptype) {
case ISDN_PTYPE_EURO:
if (caller) {
for (i = 0; i < 2; i++)
if (!(strcmp(card->s0num[i], phone)))
return phone;
return card->s0num[0];
}
return phone;
break;
case ISDN_PTYPE_1TR6:
if (caller) {
sprintf(nphone, "%s%c", card->s0num[0], phone[0]);
return nphone;
} else
return &phone[strlen(phone) - 1];
break;
}
return "";
}
/*
* Parse an ICN-type command string sent to the 'card'.
* Perform misc. actions depending on the command.
*
* Parameter:
* card = pointer to card struct.
*/
static void
isdnloop_parse_cmd(isdnloop_card *card)
{
char *p = card->omsg;
isdn_ctrl cmd;
char buf[60];
isdnloop_stat *s = isdnloop_cmd_table;
int action = -1;
int i;
int ch;
if ((card->omsg[0] != '0') && (card->omsg[2] != ';')) {
isdnloop_fake_err(card);
return;
}
ch = card->omsg[1] - '0';
if ((ch < 0) || (ch > 2)) {
isdnloop_fake_err(card);
return;
}
p += 3;
while (s->statstr) {
if (!strncmp(p, s->statstr, strlen(s->statstr))) {
action = s->action;
if (s->command && (ch != 0)) {
isdnloop_fake_err(card);
return;
}
break;
}
s++;
}
if (action == -1)
return;
switch (action) {
case 1:
/* 0x;BCON_R */
if (card->rcard[ch - 1]) {
isdnloop_fake(card->rcard[ch - 1], "BCON_I",
card->rch[ch - 1] + 1);
isdnloop_fake(card, "BCON_C", ch);
}
break;
case 17:
/* 0x;BCON_I */
if (card->rcard[ch - 1]) {
isdnloop_fake(card->rcard[ch - 1], "BCON_C",
card->rch[ch - 1] + 1);
}
break;
case 2:
/* 0x;BDIS_R */
isdnloop_fake(card, "BDIS_C", ch);
if (card->rcard[ch - 1]) {
isdnloop_fake(card->rcard[ch - 1], "BDIS_I",
card->rch[ch - 1] + 1);
}
break;
case 16:
/* 0x;DCON_R */
isdnloop_kill_ctimer(card, ch - 1);
if (card->rcard[ch - 1]) {
isdnloop_kill_ctimer(card->rcard[ch - 1], card->rch[ch - 1]);
isdnloop_fake(card->rcard[ch - 1], "DCON_C",
card->rch[ch - 1] + 1);
isdnloop_fake(card, "DCON_C", ch);
}
break;
case 3:
/* 0x;DDIS_R */
isdnloop_kill_ctimer(card, ch - 1);
if (card->rcard[ch - 1]) {
isdnloop_kill_ctimer(card->rcard[ch - 1], card->rch[ch - 1]);
isdnloop_fake(card->rcard[ch - 1], "DDIS_I",
card->rch[ch - 1] + 1);
card->rcard[ch - 1] = NULL;
}
isdnloop_fake(card, "DDIS_C", ch);
break;
case 4:
/* 0x;DSCA_Rdd,yy,zz,oo */
if (card->ptype != ISDN_PTYPE_1TR6) {
isdnloop_fake_err(card);
return;
}
/* Fall through */
case 5:
/* 0x;DCAL_Rdd,yy,zz,oo */
p += 6;
switch (isdnloop_try_call(card, p, ch - 1, &cmd)) {
case 0:
/* Alerting */
sprintf(buf, "D%s_I%s,%02d,%02d,%s",
(action == 4) ? "SCA" : "CAL",
isdnloop_vstphone(card, cmd.parm.setup.eazmsn, 1),
cmd.parm.setup.si1,
cmd.parm.setup.si2,
isdnloop_vstphone(card->rcard[ch - 1],
cmd.parm.setup.phone, 0));
isdnloop_fake(card->rcard[ch - 1], buf, card->rch[ch - 1] + 1);
/* Fall through */
case 3:
/* si1 does not match, don't alert but start timer */
isdnloop_start_ctimer(card, ch - 1);
break;
case 1:
/* Remote busy */
isdnloop_fake(card, "DDIS_I", ch);
sprintf(buf, "CAU%s", isdnloop_unicause(card, 1, 1));
isdnloop_fake(card, buf, ch);
break;
case 2:
/* No such user */
isdnloop_fake(card, "DDIS_I", ch);
sprintf(buf, "CAU%s", isdnloop_unicause(card, 1, 2));
isdnloop_fake(card, buf, ch);
break;
}
break;
case 6:
/* 0x;EAZC */
card->eazlist[ch - 1][0] = '\0';
break;
case 7:
/* 0x;EAZ */
p += 3;
if (strlen(p) >= sizeof(card->eazlist[0]))
break;
strcpy(card->eazlist[ch - 1], p);
break;
case 8:
/* 0x;SEEAZ */
sprintf(buf, "EAZ-LIST: %s", card->eazlist[ch - 1]);
isdnloop_fake(card, buf, ch + 1);
break;
case 9:
/* 0x;MSN */
break;
case 10:
/* 0x;MSNALL */
break;
case 11:
/* 0x;SETSIL */
p += 6;
i = 0;
while (strchr("0157", *p)) {
if (i)
card->sil[ch - 1] |= si2bit[*p - '0'];
i = (*p++ == '0');
}
if (*p)
isdnloop_fake_err(card);
break;
case 12:
/* 0x;SEESIL */
sprintf(buf, "SIN-LIST: ");
p = buf + 10;
for (i = 0; i < 3; i++)
if (card->sil[ch - 1] & (1 << i))
p += sprintf(p, "%02d", bit2si[i]);
isdnloop_fake(card, buf, ch + 1);
break;
case 13:
/* 0x;SILC */
card->sil[ch - 1] = 0;
break;
case 14:
/* 00;FV2ON */
break;
case 15:
/* 00;FV2OFF */
break;
}
}
/*
* Put command-strings into the of the 'card'. In reality, execute them
* right in place by calling isdnloop_parse_cmd(). Also copy every
* command to the read message ringbuffer, preceding it with a '>'.
* These mesagges can be read at /dev/isdnctrl.
*
* Parameter:
* buf = pointer to command buffer.
* len = length of buffer data.
* user = flag: 1 = called form userlevel, 0 called from kernel.
* card = pointer to card struct.
* Return:
* number of bytes transferred (currently always equals len).
*/
static int
isdnloop_writecmd(const u_char *buf, int len, int user, isdnloop_card *card)
{
int xcount = 0;
int ocount = 1;
isdn_ctrl cmd;
while (len) {
int count = len;
u_char *p;
u_char msg[0x100];
if (count > 255)
count = 255;
if (user) {
if (copy_from_user(msg, buf, count))
return -EFAULT;
} else
memcpy(msg, buf, count);
isdnloop_putmsg(card, '>');
for (p = msg; count > 0; count--, p++) {
len--;
xcount++;
isdnloop_putmsg(card, *p);
card->omsg[card->optr] = *p;
if (*p == '\n') {
card->omsg[card->optr] = '\0';
card->optr = 0;
isdnloop_parse_cmd(card);
if (len) {
isdnloop_putmsg(card, '>');
ocount++;
}
} else {
if (card->optr < 59)
card->optr++;
}
ocount++;
}
}
cmd.command = ISDN_STAT_STAVAIL;
cmd.driver = card->myid;
cmd.arg = ocount;
card->interface.statcallb(&cmd);
return xcount;
}
/*
* Delete card's pending timers, send STOP to linklevel
*/
static void
isdnloop_stopcard(isdnloop_card *card)
{
unsigned long flags;
isdn_ctrl cmd;
spin_lock_irqsave(&card->isdnloop_lock, flags);
if (card->flags & ISDNLOOP_FLAGS_RUNNING) {
card->flags &= ~ISDNLOOP_FLAGS_RUNNING;
del_timer(&card->st_timer);
del_timer(&card->rb_timer);
del_timer(&card->c_timer[0]);
del_timer(&card->c_timer[1]);
cmd.command = ISDN_STAT_STOP;
cmd.driver = card->myid;
card->interface.statcallb(&cmd);
}
spin_unlock_irqrestore(&card->isdnloop_lock, flags);
}
/*
* Stop all cards before unload.
*/
static void
isdnloop_stopallcards(void)
{
isdnloop_card *p = cards;
while (p) {
isdnloop_stopcard(p);
p = p->next;
}
}
/*
* Start a 'card'. Simulate card's boot message and set the phone
* number(s) of the virtual 'S0-Interface'. Install D-channel
* poll timer.
*
* Parameter:
* card = pointer to card struct.
* sdefp = pointer to struct holding ioctl parameters.
* Return:
* 0 on success, -E??? otherwise.
*/
static int
isdnloop_start(isdnloop_card *card, isdnloop_sdef *sdefp)
{
unsigned long flags;
isdnloop_sdef sdef;
int i;
if (card->flags & ISDNLOOP_FLAGS_RUNNING)
return -EBUSY;
if (copy_from_user((char *) &sdef, (char *) sdefp, sizeof(sdef)))
return -EFAULT;
for (i = 0; i < 3; i++) {
if (!memchr(sdef.num[i], 0, sizeof(sdef.num[i])))
return -EINVAL;
}
spin_lock_irqsave(&card->isdnloop_lock, flags);
switch (sdef.ptype) {
case ISDN_PTYPE_EURO:
if (isdnloop_fake(card, "DRV1.23EC-Q.931-CAPI-CNS-BASIS-20.02.96",
-1)) {
spin_unlock_irqrestore(&card->isdnloop_lock, flags);
return -ENOMEM;
}
card->sil[0] = card->sil[1] = 4;
if (isdnloop_fake(card, "TEI OK", 0)) {
spin_unlock_irqrestore(&card->isdnloop_lock, flags);
return -ENOMEM;
}
for (i = 0; i < 3; i++) {
strlcpy(card->s0num[i], sdef.num[i],
sizeof(card->s0num[0]));
}
break;
case ISDN_PTYPE_1TR6:
if (isdnloop_fake(card, "DRV1.04TC-1TR6-CAPI-CNS-BASIS-29.11.95",
-1)) {
spin_unlock_irqrestore(&card->isdnloop_lock, flags);
return -ENOMEM;
}
card->sil[0] = card->sil[1] = 4;
if (isdnloop_fake(card, "TEI OK", 0)) {
spin_unlock_irqrestore(&card->isdnloop_lock, flags);
return -ENOMEM;
}
strlcpy(card->s0num[0], sdef.num[0], sizeof(card->s0num[0]));
card->s0num[1][0] = '\0';
card->s0num[2][0] = '\0';
break;
default:
spin_unlock_irqrestore(&card->isdnloop_lock, flags);
printk(KERN_WARNING "isdnloop: Illegal D-channel protocol %d\n",
sdef.ptype);
return -EINVAL;
}
timer_setup(&card->rb_timer, isdnloop_pollbchan, 0);
timer_setup(&card->st_timer, isdnloop_polldchan, 0);
card->st_timer.expires = jiffies + ISDNLOOP_TIMER_DCREAD;
add_timer(&card->st_timer);
card->flags |= ISDNLOOP_FLAGS_RUNNING;
spin_unlock_irqrestore(&card->isdnloop_lock, flags);
return 0;
}
/*
* Main handler for commands sent by linklevel.
*/
static int
isdnloop_command(isdn_ctrl *c, isdnloop_card *card)
{
ulong a;
int i;
char cbuf[80];
isdn_ctrl cmd;
isdnloop_cdef cdef;
switch (c->command) {
case ISDN_CMD_IOCTL:
memcpy(&a, c->parm.num, sizeof(ulong));
switch (c->arg) {
case ISDNLOOP_IOCTL_DEBUGVAR:
return (ulong) card;
case ISDNLOOP_IOCTL_STARTUP:
return isdnloop_start(card, (isdnloop_sdef *) a);
break;
case ISDNLOOP_IOCTL_ADDCARD:
if (copy_from_user((char *)&cdef,
(char *)a,
sizeof(cdef)))
return -EFAULT;
return isdnloop_addcard(cdef.id1);
break;
case ISDNLOOP_IOCTL_LEASEDCFG:
if (a) {
if (!card->leased) {
card->leased = 1;
while (card->ptype == ISDN_PTYPE_UNKNOWN)
schedule_timeout_interruptible(10);
schedule_timeout_interruptible(10);
sprintf(cbuf, "00;FV2ON\n01;EAZ1\n02;EAZ2\n");
i = isdnloop_writecmd(cbuf, strlen(cbuf), 0, card);
printk(KERN_INFO
"isdnloop: (%s) Leased-line mode enabled\n",
CID);
cmd.command = ISDN_STAT_RUN;
cmd.driver = card->myid;
cmd.arg = 0;
card->interface.statcallb(&cmd);
}
} else {
if (card->leased) {
card->leased = 0;
sprintf(cbuf, "00;FV2OFF\n");
i = isdnloop_writecmd(cbuf, strlen(cbuf), 0, card);
printk(KERN_INFO
"isdnloop: (%s) Leased-line mode disabled\n",
CID);
cmd.command = ISDN_STAT_RUN;
cmd.driver = card->myid;
cmd.arg = 0;
card->interface.statcallb(&cmd);
}
}
return 0;
default:
return -EINVAL;
}
break;
case ISDN_CMD_DIAL:
if (!(card->flags & ISDNLOOP_FLAGS_RUNNING))
return -ENODEV;
if (card->leased)
break;
if ((c->arg & 255) < ISDNLOOP_BCH) {
char *p;
char dcode[4];
a = c->arg;
p = c->parm.setup.phone;
if (*p == 's' || *p == 'S') {
/* Dial for SPV */
p++;
strcpy(dcode, "SCA");
} else
/* Normal Dial */
strcpy(dcode, "CAL");
snprintf(cbuf, sizeof(cbuf),
"%02d;D%s_R%s,%02d,%02d,%s\n", (int) (a + 1),
dcode, p, c->parm.setup.si1,
c->parm.setup.si2, c->parm.setup.eazmsn);
i = isdnloop_writecmd(cbuf, strlen(cbuf), 0, card);
}
break;
case ISDN_CMD_ACCEPTD:
if (!(card->flags & ISDNLOOP_FLAGS_RUNNING))
return -ENODEV;
if (c->arg < ISDNLOOP_BCH) {
a = c->arg + 1;
cbuf[0] = 0;
switch (card->l2_proto[a - 1]) {
case ISDN_PROTO_L2_X75I:
sprintf(cbuf, "%02d;BX75\n", (int) a);
break;
#ifdef CONFIG_ISDN_X25
case ISDN_PROTO_L2_X25DTE:
sprintf(cbuf, "%02d;BX2T\n", (int) a);
break;
case ISDN_PROTO_L2_X25DCE:
sprintf(cbuf, "%02d;BX2C\n", (int) a);
break;
#endif
case ISDN_PROTO_L2_HDLC:
sprintf(cbuf, "%02d;BTRA\n", (int) a);
break;
}
if (strlen(cbuf))
i = isdnloop_writecmd(cbuf, strlen(cbuf), 0, card);
sprintf(cbuf, "%02d;DCON_R\n", (int) a);
i = isdnloop_writecmd(cbuf, strlen(cbuf), 0, card);
}
break;
case ISDN_CMD_ACCEPTB:
if (!(card->flags & ISDNLOOP_FLAGS_RUNNING))
return -ENODEV;
if (c->arg < ISDNLOOP_BCH) {
a = c->arg + 1;
switch (card->l2_proto[a - 1]) {
case ISDN_PROTO_L2_X75I:
sprintf(cbuf, "%02d;BCON_R,BX75\n", (int) a);
break;
#ifdef CONFIG_ISDN_X25
case ISDN_PROTO_L2_X25DTE:
sprintf(cbuf, "%02d;BCON_R,BX2T\n", (int) a);
break;
case ISDN_PROTO_L2_X25DCE:
sprintf(cbuf, "%02d;BCON_R,BX2C\n", (int) a);
break;
#endif
case ISDN_PROTO_L2_HDLC:
sprintf(cbuf, "%02d;BCON_R,BTRA\n", (int) a);
break;
default:
sprintf(cbuf, "%02d;BCON_R\n", (int) a);
}
printk(KERN_DEBUG "isdnloop writecmd '%s'\n", cbuf);
i = isdnloop_writecmd(cbuf, strlen(cbuf), 0, card);
break;
case ISDN_CMD_HANGUP:
if (!(card->flags & ISDNLOOP_FLAGS_RUNNING))
return -ENODEV;
if (c->arg < ISDNLOOP_BCH) {
a = c->arg + 1;
sprintf(cbuf, "%02d;BDIS_R\n%02d;DDIS_R\n", (int) a, (int) a);
i = isdnloop_writecmd(cbuf, strlen(cbuf), 0, card);
}
break;
case ISDN_CMD_SETEAZ:
if (!(card->flags & ISDNLOOP_FLAGS_RUNNING))
return -ENODEV;
if (card->leased)
break;
if (c->arg < ISDNLOOP_BCH) {
a = c->arg + 1;
if (card->ptype == ISDN_PTYPE_EURO) {
sprintf(cbuf, "%02d;MS%s%s\n", (int) a,
c->parm.num[0] ? "N" : "ALL", c->parm.num);
} else
sprintf(cbuf, "%02d;EAZ%s\n", (int) a,
c->parm.num[0] ? c->parm.num : (u_char *) "0123456789");
i = isdnloop_writecmd(cbuf, strlen(cbuf), 0, card);
}
break;
case ISDN_CMD_CLREAZ:
if (!(card->flags & ISDNLOOP_FLAGS_RUNNING))
return -ENODEV;
if (card->leased)
break;
if (c->arg < ISDNLOOP_BCH) {
a = c->arg + 1;
if (card->ptype == ISDN_PTYPE_EURO)
sprintf(cbuf, "%02d;MSNC\n", (int) a);
else
sprintf(cbuf, "%02d;EAZC\n", (int) a);
i = isdnloop_writecmd(cbuf, strlen(cbuf), 0, card);
}
break;
case ISDN_CMD_SETL2:
if (!(card->flags & ISDNLOOP_FLAGS_RUNNING))
return -ENODEV;
if ((c->arg & 255) < ISDNLOOP_BCH) {
a = c->arg;
switch (a >> 8) {
case ISDN_PROTO_L2_X75I:
sprintf(cbuf, "%02d;BX75\n", (int) (a & 255) + 1);
break;
#ifdef CONFIG_ISDN_X25
case ISDN_PROTO_L2_X25DTE:
sprintf(cbuf, "%02d;BX2T\n", (int) (a & 255) + 1);
break;
case ISDN_PROTO_L2_X25DCE:
sprintf(cbuf, "%02d;BX2C\n", (int) (a & 255) + 1);
break;
#endif
case ISDN_PROTO_L2_HDLC:
sprintf(cbuf, "%02d;BTRA\n", (int) (a & 255) + 1);
break;
case ISDN_PROTO_L2_TRANS:
sprintf(cbuf, "%02d;BTRA\n", (int) (a & 255) + 1);
break;
default:
return -EINVAL;
}
i = isdnloop_writecmd(cbuf, strlen(cbuf), 0, card);
card->l2_proto[a & 255] = (a >> 8);
}
break;
case ISDN_CMD_SETL3:
if (!(card->flags & ISDNLOOP_FLAGS_RUNNING))
return -ENODEV;
return 0;
default:
return -EINVAL;
}
}
return 0;
}
/*
* Find card with given driverId
*/
static inline isdnloop_card *
isdnloop_findcard(int driverid)
{
isdnloop_card *p = cards;
while (p) {
if (p->myid == driverid)
return p;
p = p->next;
}
return (isdnloop_card *) 0;
}
/*
* Wrapper functions for interface to linklevel
*/
static int
if_command(isdn_ctrl *c)
{
isdnloop_card *card = isdnloop_findcard(c->driver);
if (card)
return isdnloop_command(c, card);
printk(KERN_ERR
"isdnloop: if_command called with invalid driverId!\n");
return -ENODEV;
}
static int
if_writecmd(const u_char __user *buf, int len, int id, int channel)
{
isdnloop_card *card = isdnloop_findcard(id);
if (card) {
if (!(card->flags & ISDNLOOP_FLAGS_RUNNING))
return -ENODEV;
return isdnloop_writecmd(buf, len, 1, card);
}
printk(KERN_ERR
"isdnloop: if_writecmd called with invalid driverId!\n");
return -ENODEV;
}
static int
if_readstatus(u_char __user *buf, int len, int id, int channel)
{
isdnloop_card *card = isdnloop_findcard(id);
if (card) {
if (!(card->flags & ISDNLOOP_FLAGS_RUNNING))
return -ENODEV;
return isdnloop_readstatus(buf, len, card);
}
printk(KERN_ERR
"isdnloop: if_readstatus called with invalid driverId!\n");
return -ENODEV;
}
static int
if_sendbuf(int id, int channel, int ack, struct sk_buff *skb)
{
isdnloop_card *card = isdnloop_findcard(id);
if (card) {
if (!(card->flags & ISDNLOOP_FLAGS_RUNNING))
return -ENODEV;
/* ack request stored in skb scratch area */
*(skb->head) = ack;
return isdnloop_sendbuf(channel, skb, card);
}
printk(KERN_ERR
"isdnloop: if_sendbuf called with invalid driverId!\n");
return -ENODEV;
}
/*
* Allocate a new card-struct, initialize it
* link it into cards-list and register it at linklevel.
*/
static isdnloop_card *
isdnloop_initcard(char *id)
{
isdnloop_card *card;
int i;
card = kzalloc(sizeof(isdnloop_card), GFP_KERNEL);
if (!card) {
printk(KERN_WARNING
"isdnloop: (%s) Could not allocate card-struct.\n", id);
return (isdnloop_card *) 0;
}
card->interface.owner = THIS_MODULE;
card->interface.channels = ISDNLOOP_BCH;
card->interface.hl_hdrlen = 1; /* scratch area for storing ack flag*/
card->interface.maxbufsize = 4000;
card->interface.command = if_command;
card->interface.writebuf_skb = if_sendbuf;
card->interface.writecmd = if_writecmd;
card->interface.readstat = if_readstatus;
card->interface.features = ISDN_FEATURE_L2_X75I |
#ifdef CONFIG_ISDN_X25
ISDN_FEATURE_L2_X25DTE |
ISDN_FEATURE_L2_X25DCE |
#endif
ISDN_FEATURE_L2_HDLC |
ISDN_FEATURE_L3_TRANS |
ISDN_FEATURE_P_UNKNOWN;
card->ptype = ISDN_PTYPE_UNKNOWN;
strlcpy(card->interface.id, id, sizeof(card->interface.id));
card->msg_buf_write = card->msg_buf;
card->msg_buf_read = card->msg_buf;
card->msg_buf_end = &card->msg_buf[sizeof(card->msg_buf) - 1];
for (i = 0; i < ISDNLOOP_BCH; i++) {
card->l2_proto[i] = ISDN_PROTO_L2_X75I;
skb_queue_head_init(&card->bqueue[i]);
}
skb_queue_head_init(&card->dqueue);
spin_lock_init(&card->isdnloop_lock);
card->next = cards;
cards = card;
if (!register_isdn(&card->interface)) {
cards = cards->next;
printk(KERN_WARNING
"isdnloop: Unable to register %s\n", id);
kfree(card);
return (isdnloop_card *) 0;
}
card->myid = card->interface.channels;
return card;
}
static int
isdnloop_addcard(char *id1)
{
isdnloop_card *card;
card = isdnloop_initcard(id1);
if (!card) {
return -EIO;
}
printk(KERN_INFO
"isdnloop: (%s) virtual card added\n",
card->interface.id);
return 0;
}
static int __init
isdnloop_init(void)
{
if (isdnloop_id)
return isdnloop_addcard(isdnloop_id);
return 0;
}
static void __exit
isdnloop_exit(void)
{
isdn_ctrl cmd;
isdnloop_card *card = cards;
isdnloop_card *last;
int i;
isdnloop_stopallcards();
while (card) {
cmd.command = ISDN_STAT_UNLOAD;
cmd.driver = card->myid;
card->interface.statcallb(&cmd);
for (i = 0; i < ISDNLOOP_BCH; i++)
isdnloop_free_queue(card, i);
card = card->next;
}
card = cards;
while (card) {
last = card;
skb_queue_purge(&card->dqueue);
card = card->next;
kfree(last);
}
printk(KERN_NOTICE "isdnloop-ISDN-driver unloaded\n");
}
module_init(isdnloop_init);
module_exit(isdnloop_exit);