Path: blob/master/drivers/isdn/isdnloop/isdnloop.c
15111 views
/* $Id: isdnloop.c,v 1.11.6.7 2001/11/11 19:54:31 kai Exp $1*2* ISDN low-level module implementing a dummy loop driver.3*4* Copyright 1997 by Fritz Elfert ([email protected])5*6* This software may be used and distributed according to the terms7* of the GNU General Public License, incorporated herein by reference.8*9*/1011#include <linux/module.h>12#include <linux/interrupt.h>13#include <linux/slab.h>14#include <linux/init.h>15#include <linux/sched.h>16#include "isdnloop.h"1718static char *revision = "$Revision: 1.11.6.7 $";19static char *isdnloop_id = "loop0";2021MODULE_DESCRIPTION("ISDN4Linux: Pseudo Driver that simulates an ISDN card");22MODULE_AUTHOR("Fritz Elfert");23MODULE_LICENSE("GPL");24module_param(isdnloop_id, charp, 0);25MODULE_PARM_DESC(isdnloop_id, "ID-String of first card");2627static int isdnloop_addcard(char *);2829/*30* Free queue completely.31*32* Parameter:33* card = pointer to card struct34* channel = channel number35*/36static void37isdnloop_free_queue(isdnloop_card * card, int channel)38{39struct sk_buff_head *queue = &card->bqueue[channel];4041skb_queue_purge(queue);42card->sndcount[channel] = 0;43}4445/*46* Send B-Channel data to another virtual card.47* This routine is called via timer-callback from isdnloop_pollbchan().48*49* Parameter:50* card = pointer to card struct.51* ch = channel number (0-based)52*/53static void54isdnloop_bchan_send(isdnloop_card * card, int ch)55{56isdnloop_card *rcard = card->rcard[ch];57int rch = card->rch[ch], len, ack;58struct sk_buff *skb;59isdn_ctrl cmd;6061while (card->sndcount[ch]) {62if ((skb = skb_dequeue(&card->bqueue[ch]))) {63len = skb->len;64card->sndcount[ch] -= len;65ack = *(skb->head); /* used as scratch area */66cmd.driver = card->myid;67cmd.arg = ch;68if (rcard){69rcard->interface.rcvcallb_skb(rcard->myid, rch, skb);70} else {71printk(KERN_WARNING "isdnloop: no rcard, skb dropped\n");72dev_kfree_skb(skb);7374};75cmd.command = ISDN_STAT_BSENT;76cmd.parm.length = len;77card->interface.statcallb(&cmd);78} else79card->sndcount[ch] = 0;80}81}8283/*84* Send/Receive Data to/from the B-Channel.85* This routine is called via timer-callback.86* It schedules itself while any B-Channel is open.87*88* Parameter:89* data = pointer to card struct, set by kernel timer.data90*/91static void92isdnloop_pollbchan(unsigned long data)93{94isdnloop_card *card = (isdnloop_card *) data;95unsigned long flags;9697if (card->flags & ISDNLOOP_FLAGS_B1ACTIVE)98isdnloop_bchan_send(card, 0);99if (card->flags & ISDNLOOP_FLAGS_B2ACTIVE)100isdnloop_bchan_send(card, 1);101if (card->flags & (ISDNLOOP_FLAGS_B1ACTIVE | ISDNLOOP_FLAGS_B2ACTIVE)) {102/* schedule b-channel polling again */103spin_lock_irqsave(&card->isdnloop_lock, flags);104card->rb_timer.expires = jiffies + ISDNLOOP_TIMER_BCREAD;105add_timer(&card->rb_timer);106card->flags |= ISDNLOOP_FLAGS_RBTIMER;107spin_unlock_irqrestore(&card->isdnloop_lock, flags);108} else109card->flags &= ~ISDNLOOP_FLAGS_RBTIMER;110}111112/*113* Parse ICN-type setup string and fill fields of setup-struct114* with parsed data.115*116* Parameter:117* setup = setup string, format: [caller-id],si1,si2,[called-id]118* cmd = pointer to struct to be filled.119*/120static void121isdnloop_parse_setup(char *setup, isdn_ctrl * cmd)122{123char *t = setup;124char *s = strchr(t, ',');125126*s++ = '\0';127strlcpy(cmd->parm.setup.phone, t, sizeof(cmd->parm.setup.phone));128s = strchr(t = s, ',');129*s++ = '\0';130if (!strlen(t))131cmd->parm.setup.si1 = 0;132else133cmd->parm.setup.si1 = simple_strtoul(t, NULL, 10);134s = strchr(t = s, ',');135*s++ = '\0';136if (!strlen(t))137cmd->parm.setup.si2 = 0;138else139cmd->parm.setup.si2 =140simple_strtoul(t, NULL, 10);141strlcpy(cmd->parm.setup.eazmsn, s, sizeof(cmd->parm.setup.eazmsn));142cmd->parm.setup.plan = 0;143cmd->parm.setup.screen = 0;144}145146typedef struct isdnloop_stat {147char *statstr;148int command;149int action;150} isdnloop_stat;151/* *INDENT-OFF* */152static isdnloop_stat isdnloop_stat_table[] =153{154{"BCON_", ISDN_STAT_BCONN, 1}, /* B-Channel connected */155{"BDIS_", ISDN_STAT_BHUP, 2}, /* B-Channel disconnected */156{"DCON_", ISDN_STAT_DCONN, 0}, /* D-Channel connected */157{"DDIS_", ISDN_STAT_DHUP, 0}, /* D-Channel disconnected */158{"DCAL_I", ISDN_STAT_ICALL, 3}, /* Incoming call dialup-line */159{"DSCA_I", ISDN_STAT_ICALL, 3}, /* Incoming call 1TR6-SPV */160{"FCALL", ISDN_STAT_ICALL, 4}, /* Leased line connection up */161{"CIF", ISDN_STAT_CINF, 5}, /* Charge-info, 1TR6-type */162{"AOC", ISDN_STAT_CINF, 6}, /* Charge-info, DSS1-type */163{"CAU", ISDN_STAT_CAUSE, 7}, /* Cause code */164{"TEI OK", ISDN_STAT_RUN, 0}, /* Card connected to wallplug */165{"E_L1: ACT FAIL", ISDN_STAT_BHUP, 8}, /* Layer-1 activation failed */166{"E_L2: DATA LIN", ISDN_STAT_BHUP, 8}, /* Layer-2 data link lost */167{"E_L1: ACTIVATION FAILED",168ISDN_STAT_BHUP, 8}, /* Layer-1 activation failed */169{NULL, 0, -1}170};171/* *INDENT-ON* */172173174/*175* Parse Status message-strings from virtual card.176* Depending on status, call statcallb for sending messages to upper177* levels. Also set/reset B-Channel active-flags.178*179* Parameter:180* status = status string to parse.181* channel = channel where message comes from.182* card = card where message comes from.183*/184static void185isdnloop_parse_status(u_char * status, int channel, isdnloop_card * card)186{187isdnloop_stat *s = isdnloop_stat_table;188int action = -1;189isdn_ctrl cmd;190191while (s->statstr) {192if (!strncmp(status, s->statstr, strlen(s->statstr))) {193cmd.command = s->command;194action = s->action;195break;196}197s++;198}199if (action == -1)200return;201cmd.driver = card->myid;202cmd.arg = channel;203switch (action) {204case 1:205/* BCON_x */206card->flags |= (channel) ?207ISDNLOOP_FLAGS_B2ACTIVE : ISDNLOOP_FLAGS_B1ACTIVE;208break;209case 2:210/* BDIS_x */211card->flags &= ~((channel) ?212ISDNLOOP_FLAGS_B2ACTIVE : ISDNLOOP_FLAGS_B1ACTIVE);213isdnloop_free_queue(card, channel);214break;215case 3:216/* DCAL_I and DSCA_I */217isdnloop_parse_setup(status + 6, &cmd);218break;219case 4:220/* FCALL */221sprintf(cmd.parm.setup.phone, "LEASED%d", card->myid);222sprintf(cmd.parm.setup.eazmsn, "%d", channel + 1);223cmd.parm.setup.si1 = 7;224cmd.parm.setup.si2 = 0;225cmd.parm.setup.plan = 0;226cmd.parm.setup.screen = 0;227break;228case 5:229/* CIF */230strlcpy(cmd.parm.num, status + 3, sizeof(cmd.parm.num));231break;232case 6:233/* AOC */234snprintf(cmd.parm.num, sizeof(cmd.parm.num), "%d",235(int) simple_strtoul(status + 7, NULL, 16));236break;237case 7:238/* CAU */239status += 3;240if (strlen(status) == 4)241snprintf(cmd.parm.num, sizeof(cmd.parm.num), "%s%c%c",242status + 2, *status, *(status + 1));243else244strlcpy(cmd.parm.num, status + 1, sizeof(cmd.parm.num));245break;246case 8:247/* Misc Errors on L1 and L2 */248card->flags &= ~ISDNLOOP_FLAGS_B1ACTIVE;249isdnloop_free_queue(card, 0);250cmd.arg = 0;251cmd.driver = card->myid;252card->interface.statcallb(&cmd);253cmd.command = ISDN_STAT_DHUP;254cmd.arg = 0;255cmd.driver = card->myid;256card->interface.statcallb(&cmd);257cmd.command = ISDN_STAT_BHUP;258card->flags &= ~ISDNLOOP_FLAGS_B2ACTIVE;259isdnloop_free_queue(card, 1);260cmd.arg = 1;261cmd.driver = card->myid;262card->interface.statcallb(&cmd);263cmd.command = ISDN_STAT_DHUP;264cmd.arg = 1;265cmd.driver = card->myid;266break;267}268card->interface.statcallb(&cmd);269}270271/*272* Store a cwcharacter into ringbuffer for reading from /dev/isdnctrl273*274* Parameter:275* card = pointer to card struct.276* c = char to store.277*/278static void279isdnloop_putmsg(isdnloop_card * card, unsigned char c)280{281ulong flags;282283spin_lock_irqsave(&card->isdnloop_lock, flags);284*card->msg_buf_write++ = (c == 0xff) ? '\n' : c;285if (card->msg_buf_write == card->msg_buf_read) {286if (++card->msg_buf_read > card->msg_buf_end)287card->msg_buf_read = card->msg_buf;288}289if (card->msg_buf_write > card->msg_buf_end)290card->msg_buf_write = card->msg_buf;291spin_unlock_irqrestore(&card->isdnloop_lock, flags);292}293294/*295* Poll a virtual cards message queue.296* If there are new status-replies from the card, copy them to297* ringbuffer for reading on /dev/isdnctrl and call298* isdnloop_parse_status() for processing them. Watch for special299* Firmware bootmessage and parse it, to get the D-Channel protocol.300* If there are B-Channels open, initiate a timer-callback to301* isdnloop_pollbchan().302* This routine is called periodically via timer interrupt.303*304* Parameter:305* data = pointer to card struct306*/307static void308isdnloop_polldchan(unsigned long data)309{310isdnloop_card *card = (isdnloop_card *) data;311struct sk_buff *skb;312int avail;313int left;314u_char c;315int ch;316unsigned long flags;317u_char *p;318isdn_ctrl cmd;319320if ((skb = skb_dequeue(&card->dqueue)))321avail = skb->len;322else323avail = 0;324for (left = avail; left > 0; left--) {325c = *skb->data;326skb_pull(skb, 1);327isdnloop_putmsg(card, c);328card->imsg[card->iptr] = c;329if (card->iptr < 59)330card->iptr++;331if (!skb->len) {332avail++;333isdnloop_putmsg(card, '\n');334card->imsg[card->iptr] = 0;335card->iptr = 0;336if (card->imsg[0] == '0' && card->imsg[1] >= '0' &&337card->imsg[1] <= '2' && card->imsg[2] == ';') {338ch = (card->imsg[1] - '0') - 1;339p = &card->imsg[3];340isdnloop_parse_status(p, ch, card);341} else {342p = card->imsg;343if (!strncmp(p, "DRV1.", 5)) {344printk(KERN_INFO "isdnloop: (%s) %s\n", CID, p);345if (!strncmp(p + 7, "TC", 2)) {346card->ptype = ISDN_PTYPE_1TR6;347card->interface.features |= ISDN_FEATURE_P_1TR6;348printk(KERN_INFO349"isdnloop: (%s) 1TR6-Protocol loaded and running\n", CID);350}351if (!strncmp(p + 7, "EC", 2)) {352card->ptype = ISDN_PTYPE_EURO;353card->interface.features |= ISDN_FEATURE_P_EURO;354printk(KERN_INFO355"isdnloop: (%s) Euro-Protocol loaded and running\n", CID);356}357continue;358359}360}361}362}363if (avail) {364cmd.command = ISDN_STAT_STAVAIL;365cmd.driver = card->myid;366cmd.arg = avail;367card->interface.statcallb(&cmd);368}369if (card->flags & (ISDNLOOP_FLAGS_B1ACTIVE | ISDNLOOP_FLAGS_B2ACTIVE))370if (!(card->flags & ISDNLOOP_FLAGS_RBTIMER)) {371/* schedule b-channel polling */372card->flags |= ISDNLOOP_FLAGS_RBTIMER;373spin_lock_irqsave(&card->isdnloop_lock, flags);374del_timer(&card->rb_timer);375card->rb_timer.function = isdnloop_pollbchan;376card->rb_timer.data = (unsigned long) card;377card->rb_timer.expires = jiffies + ISDNLOOP_TIMER_BCREAD;378add_timer(&card->rb_timer);379spin_unlock_irqrestore(&card->isdnloop_lock, flags);380}381/* schedule again */382spin_lock_irqsave(&card->isdnloop_lock, flags);383card->st_timer.expires = jiffies + ISDNLOOP_TIMER_DCREAD;384add_timer(&card->st_timer);385spin_unlock_irqrestore(&card->isdnloop_lock, flags);386}387388/*389* Append a packet to the transmit buffer-queue.390*391* Parameter:392* channel = Number of B-channel393* skb = packet to send.394* card = pointer to card-struct395* Return:396* Number of bytes transferred, -E??? on error397*/398static int399isdnloop_sendbuf(int channel, struct sk_buff *skb, isdnloop_card * card)400{401int len = skb->len;402unsigned long flags;403struct sk_buff *nskb;404405if (len > 4000) {406printk(KERN_WARNING407"isdnloop: Send packet too large\n");408return -EINVAL;409}410if (len) {411if (!(card->flags & (channel) ? ISDNLOOP_FLAGS_B2ACTIVE : ISDNLOOP_FLAGS_B1ACTIVE))412return 0;413if (card->sndcount[channel] > ISDNLOOP_MAX_SQUEUE)414return 0;415spin_lock_irqsave(&card->isdnloop_lock, flags);416nskb = dev_alloc_skb(skb->len);417if (nskb) {418skb_copy_from_linear_data(skb,419skb_put(nskb, len), len);420skb_queue_tail(&card->bqueue[channel], nskb);421dev_kfree_skb(skb);422} else423len = 0;424card->sndcount[channel] += len;425spin_unlock_irqrestore(&card->isdnloop_lock, flags);426}427return len;428}429430/*431* Read the messages from the card's ringbuffer432*433* Parameter:434* buf = pointer to buffer.435* len = number of bytes to read.436* user = flag, 1: called from userlevel 0: called from kernel.437* card = pointer to card struct.438* Return:439* number of bytes actually transferred.440*/441static int442isdnloop_readstatus(u_char __user *buf, int len, isdnloop_card * card)443{444int count;445u_char __user *p;446447for (p = buf, count = 0; count < len; p++, count++) {448if (card->msg_buf_read == card->msg_buf_write)449return count;450if (put_user(*card->msg_buf_read++, p))451return -EFAULT;452if (card->msg_buf_read > card->msg_buf_end)453card->msg_buf_read = card->msg_buf;454}455return count;456}457458/*459* Simulate a card's response by appending it to the cards460* message queue.461*462* Parameter:463* card = pointer to card struct.464* s = pointer to message-string.465* ch = channel: 0 = generic messages, 1 and 2 = D-channel messages.466* Return:467* 0 on success, 1 on memory squeeze.468*/469static int470isdnloop_fake(isdnloop_card * card, char *s, int ch)471{472struct sk_buff *skb;473int len = strlen(s) + ((ch >= 0) ? 3 : 0);474475if (!(skb = dev_alloc_skb(len))) {476printk(KERN_WARNING "isdnloop: Out of memory in isdnloop_fake\n");477return 1;478}479if (ch >= 0)480sprintf(skb_put(skb, 3), "%02d;", ch);481memcpy(skb_put(skb, strlen(s)), s, strlen(s));482skb_queue_tail(&card->dqueue, skb);483return 0;484}485/* *INDENT-OFF* */486static isdnloop_stat isdnloop_cmd_table[] =487{488{"BCON_R", 0, 1}, /* B-Channel connect */489{"BCON_I", 0, 17}, /* B-Channel connect ind */490{"BDIS_R", 0, 2}, /* B-Channel disconnect */491{"DDIS_R", 0, 3}, /* D-Channel disconnect */492{"DCON_R", 0, 16}, /* D-Channel connect */493{"DSCA_R", 0, 4}, /* Dial 1TR6-SPV */494{"DCAL_R", 0, 5}, /* Dial */495{"EAZC", 0, 6}, /* Clear EAZ listener */496{"EAZ", 0, 7}, /* Set EAZ listener */497{"SEEAZ", 0, 8}, /* Get EAZ listener */498{"MSN", 0, 9}, /* Set/Clear MSN listener */499{"MSALL", 0, 10}, /* Set multi MSN listeners */500{"SETSIL", 0, 11}, /* Set SI list */501{"SEESIL", 0, 12}, /* Get SI list */502{"SILC", 0, 13}, /* Clear SI list */503{"LOCK", 0, -1}, /* LOCK channel */504{"UNLOCK", 0, -1}, /* UNLOCK channel */505{"FV2ON", 1, 14}, /* Leased mode on */506{"FV2OFF", 1, 15}, /* Leased mode off */507{NULL, 0, -1}508};509/* *INDENT-ON* */510511512/*513* Simulate an error-response from a card.514*515* Parameter:516* card = pointer to card struct.517*/518static void519isdnloop_fake_err(isdnloop_card * card)520{521char buf[60];522523sprintf(buf, "E%s", card->omsg);524isdnloop_fake(card, buf, -1);525isdnloop_fake(card, "NAK", -1);526}527528static u_char ctable_eu[] =529{0x00, 0x11, 0x01, 0x12};530static u_char ctable_1t[] =531{0x00, 0x3b, 0x01, 0x3a};532533/*534* Assemble a simplified cause message depending on the535* D-channel protocol used.536*537* Parameter:538* card = pointer to card struct.539* loc = location: 0 = local, 1 = remote.540* cau = cause: 1 = busy, 2 = nonexistent callerid, 3 = no user responding.541* Return:542* Pointer to buffer containing the assembled message.543*/544static char *545isdnloop_unicause(isdnloop_card * card, int loc, int cau)546{547static char buf[6];548549switch (card->ptype) {550case ISDN_PTYPE_EURO:551sprintf(buf, "E%02X%02X", (loc) ? 4 : 2, ctable_eu[cau]);552break;553case ISDN_PTYPE_1TR6:554sprintf(buf, "%02X44", ctable_1t[cau]);555break;556default:557return ("0000");558}559return (buf);560}561562/*563* Release a virtual connection. Called from timer interrupt, when564* called party did not respond.565*566* Parameter:567* card = pointer to card struct.568* ch = channel (0-based)569*/570static void571isdnloop_atimeout(isdnloop_card * card, int ch)572{573unsigned long flags;574char buf[60];575576spin_lock_irqsave(&card->isdnloop_lock, flags);577if (card->rcard) {578isdnloop_fake(card->rcard[ch], "DDIS_I", card->rch[ch] + 1);579card->rcard[ch]->rcard[card->rch[ch]] = NULL;580card->rcard[ch] = NULL;581}582isdnloop_fake(card, "DDIS_I", ch + 1);583/* No user responding */584sprintf(buf, "CAU%s", isdnloop_unicause(card, 1, 3));585isdnloop_fake(card, buf, ch + 1);586spin_unlock_irqrestore(&card->isdnloop_lock, flags);587}588589/*590* Wrapper for isdnloop_atimeout().591*/592static void593isdnloop_atimeout0(unsigned long data)594{595isdnloop_card *card = (isdnloop_card *) data;596isdnloop_atimeout(card, 0);597}598599/*600* Wrapper for isdnloop_atimeout().601*/602static void603isdnloop_atimeout1(unsigned long data)604{605isdnloop_card *card = (isdnloop_card *) data;606isdnloop_atimeout(card, 1);607}608609/*610* Install a watchdog for a user, not responding.611*612* Parameter:613* card = pointer to card struct.614* ch = channel to watch for.615*/616static void617isdnloop_start_ctimer(isdnloop_card * card, int ch)618{619unsigned long flags;620621spin_lock_irqsave(&card->isdnloop_lock, flags);622init_timer(&card->c_timer[ch]);623card->c_timer[ch].expires = jiffies + ISDNLOOP_TIMER_ALERTWAIT;624if (ch)625card->c_timer[ch].function = isdnloop_atimeout1;626else627card->c_timer[ch].function = isdnloop_atimeout0;628card->c_timer[ch].data = (unsigned long) card;629add_timer(&card->c_timer[ch]);630spin_unlock_irqrestore(&card->isdnloop_lock, flags);631}632633/*634* Kill a pending channel watchdog.635*636* Parameter:637* card = pointer to card struct.638* ch = channel (0-based).639*/640static void641isdnloop_kill_ctimer(isdnloop_card * card, int ch)642{643unsigned long flags;644645spin_lock_irqsave(&card->isdnloop_lock, flags);646del_timer(&card->c_timer[ch]);647spin_unlock_irqrestore(&card->isdnloop_lock, flags);648}649650static u_char si2bit[] =651{0, 1, 0, 0, 0, 2, 0, 4, 0, 0};652static u_char bit2si[] =653{1, 5, 7};654655/*656* Try finding a listener for an outgoing call.657*658* Parameter:659* card = pointer to calling card.660* p = pointer to ICN-type setup-string.661* lch = channel of calling card.662* cmd = pointer to struct to be filled when parsing setup.663* Return:664* 0 = found match, alerting should happen.665* 1 = found matching number but it is busy.666* 2 = no matching listener.667* 3 = found matching number but SI does not match.668*/669static int670isdnloop_try_call(isdnloop_card * card, char *p, int lch, isdn_ctrl * cmd)671{672isdnloop_card *cc = cards;673unsigned long flags;674int ch;675int num_match;676int i;677char *e;678char nbuf[32];679680isdnloop_parse_setup(p, cmd);681while (cc) {682for (ch = 0; ch < 2; ch++) {683/* Exclude ourself */684if ((cc == card) && (ch == lch))685continue;686num_match = 0;687switch (cc->ptype) {688case ISDN_PTYPE_EURO:689for (i = 0; i < 3; i++)690if (!(strcmp(cc->s0num[i], cmd->parm.setup.phone)))691num_match = 1;692break;693case ISDN_PTYPE_1TR6:694e = cc->eazlist[ch];695while (*e) {696sprintf(nbuf, "%s%c", cc->s0num[0], *e);697if (!(strcmp(nbuf, cmd->parm.setup.phone)))698num_match = 1;699e++;700}701}702if (num_match) {703spin_lock_irqsave(&card->isdnloop_lock, flags);704/* channel idle? */705if (!(cc->rcard[ch])) {706/* Check SI */707if (!(si2bit[cmd->parm.setup.si1] & cc->sil[ch])) {708spin_unlock_irqrestore(&card->isdnloop_lock, flags);709return 3;710}711/* ch is idle, si and number matches */712cc->rcard[ch] = card;713cc->rch[ch] = lch;714card->rcard[lch] = cc;715card->rch[lch] = ch;716spin_unlock_irqrestore(&card->isdnloop_lock, flags);717return 0;718} else {719spin_unlock_irqrestore(&card->isdnloop_lock, flags);720/* num matches, but busy */721if (ch == 1)722return 1;723}724}725}726cc = cc->next;727}728return 2;729}730731/*732* Depending on D-channel protocol and caller/called, modify733* phone number.734*735* Parameter:736* card = pointer to card struct.737* phone = pointer phone number.738* caller = flag: 1 = caller, 0 = called.739* Return:740* pointer to new phone number.741*/742static char *743isdnloop_vstphone(isdnloop_card * card, char *phone, int caller)744{745int i;746static char nphone[30];747748if (!card) {749printk("BUG!!!\n");750return "";751}752switch (card->ptype) {753case ISDN_PTYPE_EURO:754if (caller) {755for (i = 0; i < 2; i++)756if (!(strcmp(card->s0num[i], phone)))757return (phone);758return (card->s0num[0]);759}760return (phone);761break;762case ISDN_PTYPE_1TR6:763if (caller) {764sprintf(nphone, "%s%c", card->s0num[0], phone[0]);765return (nphone);766} else767return (&phone[strlen(phone) - 1]);768break;769}770return "";771}772773/*774* Parse an ICN-type command string sent to the 'card'.775* Perform misc. actions depending on the command.776*777* Parameter:778* card = pointer to card struct.779*/780static void781isdnloop_parse_cmd(isdnloop_card * card)782{783char *p = card->omsg;784isdn_ctrl cmd;785char buf[60];786isdnloop_stat *s = isdnloop_cmd_table;787int action = -1;788int i;789int ch;790791if ((card->omsg[0] != '0') && (card->omsg[2] != ';')) {792isdnloop_fake_err(card);793return;794}795ch = card->omsg[1] - '0';796if ((ch < 0) || (ch > 2)) {797isdnloop_fake_err(card);798return;799}800p += 3;801while (s->statstr) {802if (!strncmp(p, s->statstr, strlen(s->statstr))) {803action = s->action;804if (s->command && (ch != 0)) {805isdnloop_fake_err(card);806return;807}808break;809}810s++;811}812if (action == -1)813return;814switch (action) {815case 1:816/* 0x;BCON_R */817if (card->rcard[ch - 1]) {818isdnloop_fake(card->rcard[ch - 1], "BCON_I",819card->rch[ch - 1] + 1);820isdnloop_fake(card, "BCON_C", ch);821}822break;823case 17:824/* 0x;BCON_I */825if (card->rcard[ch - 1]) {826isdnloop_fake(card->rcard[ch - 1], "BCON_C",827card->rch[ch - 1] + 1);828}829break;830case 2:831/* 0x;BDIS_R */832isdnloop_fake(card, "BDIS_C", ch);833if (card->rcard[ch - 1]) {834isdnloop_fake(card->rcard[ch - 1], "BDIS_I",835card->rch[ch - 1] + 1);836}837break;838case 16:839/* 0x;DCON_R */840isdnloop_kill_ctimer(card, ch - 1);841if (card->rcard[ch - 1]) {842isdnloop_kill_ctimer(card->rcard[ch - 1], card->rch[ch - 1]);843isdnloop_fake(card->rcard[ch - 1], "DCON_C",844card->rch[ch - 1] + 1);845isdnloop_fake(card, "DCON_C", ch);846}847break;848case 3:849/* 0x;DDIS_R */850isdnloop_kill_ctimer(card, ch - 1);851if (card->rcard[ch - 1]) {852isdnloop_kill_ctimer(card->rcard[ch - 1], card->rch[ch - 1]);853isdnloop_fake(card->rcard[ch - 1], "DDIS_I",854card->rch[ch - 1] + 1);855card->rcard[ch - 1] = NULL;856}857isdnloop_fake(card, "DDIS_C", ch);858break;859case 4:860/* 0x;DSCA_Rdd,yy,zz,oo */861if (card->ptype != ISDN_PTYPE_1TR6) {862isdnloop_fake_err(card);863return;864}865/* Fall through */866case 5:867/* 0x;DCAL_Rdd,yy,zz,oo */868p += 6;869switch (isdnloop_try_call(card, p, ch - 1, &cmd)) {870case 0:871/* Alerting */872sprintf(buf, "D%s_I%s,%02d,%02d,%s",873(action == 4) ? "SCA" : "CAL",874isdnloop_vstphone(card, cmd.parm.setup.eazmsn, 1),875cmd.parm.setup.si1,876cmd.parm.setup.si2,877isdnloop_vstphone(card->rcard[ch - 1],878cmd.parm.setup.phone, 0));879isdnloop_fake(card->rcard[ch - 1], buf, card->rch[ch - 1] + 1);880/* Fall through */881case 3:882/* si1 does not match, don't alert but start timer */883isdnloop_start_ctimer(card, ch - 1);884break;885case 1:886/* Remote busy */887isdnloop_fake(card, "DDIS_I", ch);888sprintf(buf, "CAU%s", isdnloop_unicause(card, 1, 1));889isdnloop_fake(card, buf, ch);890break;891case 2:892/* No such user */893isdnloop_fake(card, "DDIS_I", ch);894sprintf(buf, "CAU%s", isdnloop_unicause(card, 1, 2));895isdnloop_fake(card, buf, ch);896break;897}898break;899case 6:900/* 0x;EAZC */901card->eazlist[ch - 1][0] = '\0';902break;903case 7:904/* 0x;EAZ */905p += 3;906strcpy(card->eazlist[ch - 1], p);907break;908case 8:909/* 0x;SEEAZ */910sprintf(buf, "EAZ-LIST: %s", card->eazlist[ch - 1]);911isdnloop_fake(card, buf, ch + 1);912break;913case 9:914/* 0x;MSN */915break;916case 10:917/* 0x;MSNALL */918break;919case 11:920/* 0x;SETSIL */921p += 6;922i = 0;923while (strchr("0157", *p)) {924if (i)925card->sil[ch - 1] |= si2bit[*p - '0'];926i = (*p++ == '0');927}928if (*p)929isdnloop_fake_err(card);930break;931case 12:932/* 0x;SEESIL */933sprintf(buf, "SIN-LIST: ");934p = buf + 10;935for (i = 0; i < 3; i++)936if (card->sil[ch - 1] & (1 << i))937p += sprintf(p, "%02d", bit2si[i]);938isdnloop_fake(card, buf, ch + 1);939break;940case 13:941/* 0x;SILC */942card->sil[ch - 1] = 0;943break;944case 14:945/* 00;FV2ON */946break;947case 15:948/* 00;FV2OFF */949break;950}951}952953/*954* Put command-strings into the of the 'card'. In reality, execute them955* right in place by calling isdnloop_parse_cmd(). Also copy every956* command to the read message ringbuffer, preceding it with a '>'.957* These mesagges can be read at /dev/isdnctrl.958*959* Parameter:960* buf = pointer to command buffer.961* len = length of buffer data.962* user = flag: 1 = called form userlevel, 0 called from kernel.963* card = pointer to card struct.964* Return:965* number of bytes transferred (currently always equals len).966*/967static int968isdnloop_writecmd(const u_char * buf, int len, int user, isdnloop_card * card)969{970int xcount = 0;971int ocount = 1;972isdn_ctrl cmd;973974while (len) {975int count = len;976u_char *p;977u_char msg[0x100];978979if (count > 255)980count = 255;981if (user) {982if (copy_from_user(msg, buf, count))983return -EFAULT;984} else985memcpy(msg, buf, count);986isdnloop_putmsg(card, '>');987for (p = msg; count > 0; count--, p++) {988len--;989xcount++;990isdnloop_putmsg(card, *p);991card->omsg[card->optr] = *p;992if (*p == '\n') {993card->omsg[card->optr] = '\0';994card->optr = 0;995isdnloop_parse_cmd(card);996if (len) {997isdnloop_putmsg(card, '>');998ocount++;999}1000} else {1001if (card->optr < 59)1002card->optr++;1003}1004ocount++;1005}1006}1007cmd.command = ISDN_STAT_STAVAIL;1008cmd.driver = card->myid;1009cmd.arg = ocount;1010card->interface.statcallb(&cmd);1011return xcount;1012}10131014/*1015* Delete card's pending timers, send STOP to linklevel1016*/1017static void1018isdnloop_stopcard(isdnloop_card * card)1019{1020unsigned long flags;1021isdn_ctrl cmd;10221023spin_lock_irqsave(&card->isdnloop_lock, flags);1024if (card->flags & ISDNLOOP_FLAGS_RUNNING) {1025card->flags &= ~ISDNLOOP_FLAGS_RUNNING;1026del_timer(&card->st_timer);1027del_timer(&card->rb_timer);1028del_timer(&card->c_timer[0]);1029del_timer(&card->c_timer[1]);1030cmd.command = ISDN_STAT_STOP;1031cmd.driver = card->myid;1032card->interface.statcallb(&cmd);1033}1034spin_unlock_irqrestore(&card->isdnloop_lock, flags);1035}10361037/*1038* Stop all cards before unload.1039*/1040static void1041isdnloop_stopallcards(void)1042{1043isdnloop_card *p = cards;10441045while (p) {1046isdnloop_stopcard(p);1047p = p->next;1048}1049}10501051/*1052* Start a 'card'. Simulate card's boot message and set the phone1053* number(s) of the virtual 'S0-Interface'. Install D-channel1054* poll timer.1055*1056* Parameter:1057* card = pointer to card struct.1058* sdefp = pointer to struct holding ioctl parameters.1059* Return:1060* 0 on success, -E??? otherwise.1061*/1062static int1063isdnloop_start(isdnloop_card * card, isdnloop_sdef * sdefp)1064{1065unsigned long flags;1066isdnloop_sdef sdef;1067int i;10681069if (card->flags & ISDNLOOP_FLAGS_RUNNING)1070return -EBUSY;1071if (copy_from_user((char *) &sdef, (char *) sdefp, sizeof(sdef)))1072return -EFAULT;1073spin_lock_irqsave(&card->isdnloop_lock, flags);1074switch (sdef.ptype) {1075case ISDN_PTYPE_EURO:1076if (isdnloop_fake(card, "DRV1.23EC-Q.931-CAPI-CNS-BASIS-20.02.96",1077-1)) {1078spin_unlock_irqrestore(&card->isdnloop_lock, flags);1079return -ENOMEM;1080}1081card->sil[0] = card->sil[1] = 4;1082if (isdnloop_fake(card, "TEI OK", 0)) {1083spin_unlock_irqrestore(&card->isdnloop_lock, flags);1084return -ENOMEM;1085}1086for (i = 0; i < 3; i++)1087strcpy(card->s0num[i], sdef.num[i]);1088break;1089case ISDN_PTYPE_1TR6:1090if (isdnloop_fake(card, "DRV1.04TC-1TR6-CAPI-CNS-BASIS-29.11.95",1091-1)) {1092spin_unlock_irqrestore(&card->isdnloop_lock, flags);1093return -ENOMEM;1094}1095card->sil[0] = card->sil[1] = 4;1096if (isdnloop_fake(card, "TEI OK", 0)) {1097spin_unlock_irqrestore(&card->isdnloop_lock, flags);1098return -ENOMEM;1099}1100strcpy(card->s0num[0], sdef.num[0]);1101card->s0num[1][0] = '\0';1102card->s0num[2][0] = '\0';1103break;1104default:1105spin_unlock_irqrestore(&card->isdnloop_lock, flags);1106printk(KERN_WARNING "isdnloop: Illegal D-channel protocol %d\n",1107sdef.ptype);1108return -EINVAL;1109}1110init_timer(&card->st_timer);1111card->st_timer.expires = jiffies + ISDNLOOP_TIMER_DCREAD;1112card->st_timer.function = isdnloop_polldchan;1113card->st_timer.data = (unsigned long) card;1114add_timer(&card->st_timer);1115card->flags |= ISDNLOOP_FLAGS_RUNNING;1116spin_unlock_irqrestore(&card->isdnloop_lock, flags);1117return 0;1118}11191120/*1121* Main handler for commands sent by linklevel.1122*/1123static int1124isdnloop_command(isdn_ctrl * c, isdnloop_card * card)1125{1126ulong a;1127int i;1128char cbuf[60];1129isdn_ctrl cmd;1130isdnloop_cdef cdef;11311132switch (c->command) {1133case ISDN_CMD_IOCTL:1134memcpy(&a, c->parm.num, sizeof(ulong));1135switch (c->arg) {1136case ISDNLOOP_IOCTL_DEBUGVAR:1137return (ulong) card;1138case ISDNLOOP_IOCTL_STARTUP:1139if (!access_ok(VERIFY_READ, (void *) a, sizeof(isdnloop_sdef)))1140return -EFAULT;1141return (isdnloop_start(card, (isdnloop_sdef *) a));1142break;1143case ISDNLOOP_IOCTL_ADDCARD:1144if (copy_from_user((char *)&cdef,1145(char *)a,1146sizeof(cdef)))1147return -EFAULT;1148return (isdnloop_addcard(cdef.id1));1149break;1150case ISDNLOOP_IOCTL_LEASEDCFG:1151if (a) {1152if (!card->leased) {1153card->leased = 1;1154while (card->ptype == ISDN_PTYPE_UNKNOWN)1155schedule_timeout_interruptible(10);1156schedule_timeout_interruptible(10);1157sprintf(cbuf, "00;FV2ON\n01;EAZ1\n02;EAZ2\n");1158i = isdnloop_writecmd(cbuf, strlen(cbuf), 0, card);1159printk(KERN_INFO1160"isdnloop: (%s) Leased-line mode enabled\n",1161CID);1162cmd.command = ISDN_STAT_RUN;1163cmd.driver = card->myid;1164cmd.arg = 0;1165card->interface.statcallb(&cmd);1166}1167} else {1168if (card->leased) {1169card->leased = 0;1170sprintf(cbuf, "00;FV2OFF\n");1171i = isdnloop_writecmd(cbuf, strlen(cbuf), 0, card);1172printk(KERN_INFO1173"isdnloop: (%s) Leased-line mode disabled\n",1174CID);1175cmd.command = ISDN_STAT_RUN;1176cmd.driver = card->myid;1177cmd.arg = 0;1178card->interface.statcallb(&cmd);1179}1180}1181return 0;1182default:1183return -EINVAL;1184}1185break;1186case ISDN_CMD_DIAL:1187if (!(card->flags & ISDNLOOP_FLAGS_RUNNING))1188return -ENODEV;1189if (card->leased)1190break;1191if ((c->arg & 255) < ISDNLOOP_BCH) {1192char *p;1193char dial[50];1194char dcode[4];11951196a = c->arg;1197p = c->parm.setup.phone;1198if (*p == 's' || *p == 'S') {1199/* Dial for SPV */1200p++;1201strcpy(dcode, "SCA");1202} else1203/* Normal Dial */1204strcpy(dcode, "CAL");1205strcpy(dial, p);1206sprintf(cbuf, "%02d;D%s_R%s,%02d,%02d,%s\n", (int) (a + 1),1207dcode, dial, c->parm.setup.si1,1208c->parm.setup.si2, c->parm.setup.eazmsn);1209i = isdnloop_writecmd(cbuf, strlen(cbuf), 0, card);1210}1211break;1212case ISDN_CMD_ACCEPTD:1213if (!(card->flags & ISDNLOOP_FLAGS_RUNNING))1214return -ENODEV;1215if (c->arg < ISDNLOOP_BCH) {1216a = c->arg + 1;1217cbuf[0] = 0;1218switch (card->l2_proto[a - 1]) {1219case ISDN_PROTO_L2_X75I:1220sprintf(cbuf, "%02d;BX75\n", (int) a);1221break;1222#ifdef CONFIG_ISDN_X251223case ISDN_PROTO_L2_X25DTE:1224sprintf(cbuf, "%02d;BX2T\n", (int) a);1225break;1226case ISDN_PROTO_L2_X25DCE:1227sprintf(cbuf, "%02d;BX2C\n", (int) a);1228break;1229#endif1230case ISDN_PROTO_L2_HDLC:1231sprintf(cbuf, "%02d;BTRA\n", (int) a);1232break;1233}1234if (strlen(cbuf))1235i = isdnloop_writecmd(cbuf, strlen(cbuf), 0, card);1236sprintf(cbuf, "%02d;DCON_R\n", (int) a);1237i = isdnloop_writecmd(cbuf, strlen(cbuf), 0, card);1238}1239break;1240case ISDN_CMD_ACCEPTB:1241if (!(card->flags & ISDNLOOP_FLAGS_RUNNING))1242return -ENODEV;1243if (c->arg < ISDNLOOP_BCH) {1244a = c->arg + 1;1245switch (card->l2_proto[a - 1]) {1246case ISDN_PROTO_L2_X75I:1247sprintf(cbuf, "%02d;BCON_R,BX75\n", (int) a);1248break;1249#ifdef CONFIG_ISDN_X251250case ISDN_PROTO_L2_X25DTE:1251sprintf(cbuf, "%02d;BCON_R,BX2T\n", (int) a);1252break;1253case ISDN_PROTO_L2_X25DCE:1254sprintf(cbuf, "%02d;BCON_R,BX2C\n", (int) a);1255break;1256#endif1257case ISDN_PROTO_L2_HDLC:1258sprintf(cbuf, "%02d;BCON_R,BTRA\n", (int) a);1259break;1260default:1261sprintf(cbuf, "%02d;BCON_R\n", (int) a);1262}1263printk(KERN_DEBUG "isdnloop writecmd '%s'\n", cbuf);1264i = isdnloop_writecmd(cbuf, strlen(cbuf), 0, card);1265break;1266case ISDN_CMD_HANGUP:1267if (!(card->flags & ISDNLOOP_FLAGS_RUNNING))1268return -ENODEV;1269if (c->arg < ISDNLOOP_BCH) {1270a = c->arg + 1;1271sprintf(cbuf, "%02d;BDIS_R\n%02d;DDIS_R\n", (int) a, (int) a);1272i = isdnloop_writecmd(cbuf, strlen(cbuf), 0, card);1273}1274break;1275case ISDN_CMD_SETEAZ:1276if (!(card->flags & ISDNLOOP_FLAGS_RUNNING))1277return -ENODEV;1278if (card->leased)1279break;1280if (c->arg < ISDNLOOP_BCH) {1281a = c->arg + 1;1282if (card->ptype == ISDN_PTYPE_EURO) {1283sprintf(cbuf, "%02d;MS%s%s\n", (int) a,1284c->parm.num[0] ? "N" : "ALL", c->parm.num);1285} else1286sprintf(cbuf, "%02d;EAZ%s\n", (int) a,1287c->parm.num[0] ? c->parm.num : (u_char *) "0123456789");1288i = isdnloop_writecmd(cbuf, strlen(cbuf), 0, card);1289}1290break;1291case ISDN_CMD_CLREAZ:1292if (!(card->flags & ISDNLOOP_FLAGS_RUNNING))1293return -ENODEV;1294if (card->leased)1295break;1296if (c->arg < ISDNLOOP_BCH) {1297a = c->arg + 1;1298if (card->ptype == ISDN_PTYPE_EURO)1299sprintf(cbuf, "%02d;MSNC\n", (int) a);1300else1301sprintf(cbuf, "%02d;EAZC\n", (int) a);1302i = isdnloop_writecmd(cbuf, strlen(cbuf), 0, card);1303}1304break;1305case ISDN_CMD_SETL2:1306if (!(card->flags & ISDNLOOP_FLAGS_RUNNING))1307return -ENODEV;1308if ((c->arg & 255) < ISDNLOOP_BCH) {1309a = c->arg;1310switch (a >> 8) {1311case ISDN_PROTO_L2_X75I:1312sprintf(cbuf, "%02d;BX75\n", (int) (a & 255) + 1);1313break;1314#ifdef CONFIG_ISDN_X251315case ISDN_PROTO_L2_X25DTE:1316sprintf(cbuf, "%02d;BX2T\n", (int) (a & 255) + 1);1317break;1318case ISDN_PROTO_L2_X25DCE:1319sprintf(cbuf, "%02d;BX2C\n", (int) (a & 255) + 1);1320break;1321#endif1322case ISDN_PROTO_L2_HDLC:1323sprintf(cbuf, "%02d;BTRA\n", (int) (a & 255) + 1);1324break;1325case ISDN_PROTO_L2_TRANS:1326sprintf(cbuf, "%02d;BTRA\n", (int) (a & 255) + 1);1327break;1328default:1329return -EINVAL;1330}1331i = isdnloop_writecmd(cbuf, strlen(cbuf), 0, card);1332card->l2_proto[a & 255] = (a >> 8);1333}1334break;1335case ISDN_CMD_SETL3:1336if (!(card->flags & ISDNLOOP_FLAGS_RUNNING))1337return -ENODEV;1338return 0;1339default:1340return -EINVAL;1341}1342}1343return 0;1344}13451346/*1347* Find card with given driverId1348*/1349static inline isdnloop_card *1350isdnloop_findcard(int driverid)1351{1352isdnloop_card *p = cards;13531354while (p) {1355if (p->myid == driverid)1356return p;1357p = p->next;1358}1359return (isdnloop_card *) 0;1360}13611362/*1363* Wrapper functions for interface to linklevel1364*/1365static int1366if_command(isdn_ctrl * c)1367{1368isdnloop_card *card = isdnloop_findcard(c->driver);13691370if (card)1371return (isdnloop_command(c, card));1372printk(KERN_ERR1373"isdnloop: if_command called with invalid driverId!\n");1374return -ENODEV;1375}13761377static int1378if_writecmd(const u_char __user *buf, int len, int id, int channel)1379{1380isdnloop_card *card = isdnloop_findcard(id);13811382if (card) {1383if (!(card->flags & ISDNLOOP_FLAGS_RUNNING))1384return -ENODEV;1385return (isdnloop_writecmd(buf, len, 1, card));1386}1387printk(KERN_ERR1388"isdnloop: if_writecmd called with invalid driverId!\n");1389return -ENODEV;1390}13911392static int1393if_readstatus(u_char __user *buf, int len, int id, int channel)1394{1395isdnloop_card *card = isdnloop_findcard(id);13961397if (card) {1398if (!(card->flags & ISDNLOOP_FLAGS_RUNNING))1399return -ENODEV;1400return (isdnloop_readstatus(buf, len, card));1401}1402printk(KERN_ERR1403"isdnloop: if_readstatus called with invalid driverId!\n");1404return -ENODEV;1405}14061407static int1408if_sendbuf(int id, int channel, int ack, struct sk_buff *skb)1409{1410isdnloop_card *card = isdnloop_findcard(id);14111412if (card) {1413if (!(card->flags & ISDNLOOP_FLAGS_RUNNING))1414return -ENODEV;1415/* ack request stored in skb scratch area */1416*(skb->head) = ack;1417return (isdnloop_sendbuf(channel, skb, card));1418}1419printk(KERN_ERR1420"isdnloop: if_sendbuf called with invalid driverId!\n");1421return -ENODEV;1422}14231424/*1425* Allocate a new card-struct, initialize it1426* link it into cards-list and register it at linklevel.1427*/1428static isdnloop_card *1429isdnloop_initcard(char *id)1430{1431isdnloop_card *card;1432int i;14331434if (!(card = kzalloc(sizeof(isdnloop_card), GFP_KERNEL))) {1435printk(KERN_WARNING1436"isdnloop: (%s) Could not allocate card-struct.\n", id);1437return (isdnloop_card *) 0;1438}1439card->interface.owner = THIS_MODULE;1440card->interface.channels = ISDNLOOP_BCH;1441card->interface.hl_hdrlen = 1; /* scratch area for storing ack flag*/1442card->interface.maxbufsize = 4000;1443card->interface.command = if_command;1444card->interface.writebuf_skb = if_sendbuf;1445card->interface.writecmd = if_writecmd;1446card->interface.readstat = if_readstatus;1447card->interface.features = ISDN_FEATURE_L2_X75I |1448#ifdef CONFIG_ISDN_X251449ISDN_FEATURE_L2_X25DTE |1450ISDN_FEATURE_L2_X25DCE |1451#endif1452ISDN_FEATURE_L2_HDLC |1453ISDN_FEATURE_L3_TRANS |1454ISDN_FEATURE_P_UNKNOWN;1455card->ptype = ISDN_PTYPE_UNKNOWN;1456strlcpy(card->interface.id, id, sizeof(card->interface.id));1457card->msg_buf_write = card->msg_buf;1458card->msg_buf_read = card->msg_buf;1459card->msg_buf_end = &card->msg_buf[sizeof(card->msg_buf) - 1];1460for (i = 0; i < ISDNLOOP_BCH; i++) {1461card->l2_proto[i] = ISDN_PROTO_L2_X75I;1462skb_queue_head_init(&card->bqueue[i]);1463}1464skb_queue_head_init(&card->dqueue);1465spin_lock_init(&card->isdnloop_lock);1466card->next = cards;1467cards = card;1468if (!register_isdn(&card->interface)) {1469cards = cards->next;1470printk(KERN_WARNING1471"isdnloop: Unable to register %s\n", id);1472kfree(card);1473return (isdnloop_card *) 0;1474}1475card->myid = card->interface.channels;1476return card;1477}14781479static int1480isdnloop_addcard(char *id1)1481{1482isdnloop_card *card;14831484if (!(card = isdnloop_initcard(id1))) {1485return -EIO;1486}1487printk(KERN_INFO1488"isdnloop: (%s) virtual card added\n",1489card->interface.id);1490return 0;1491}14921493static int __init1494isdnloop_init(void)1495{1496char *p;1497char rev[10];14981499if ((p = strchr(revision, ':'))) {1500strcpy(rev, p + 1);1501p = strchr(rev, '$');1502*p = 0;1503} else1504strcpy(rev, " ??? ");1505printk(KERN_NOTICE "isdnloop-ISDN-driver Rev%s\n", rev);15061507if (isdnloop_id)1508return (isdnloop_addcard(isdnloop_id));15091510return 0;1511}15121513static void __exit1514isdnloop_exit(void)1515{1516isdn_ctrl cmd;1517isdnloop_card *card = cards;1518isdnloop_card *last;1519int i;15201521isdnloop_stopallcards();1522while (card) {1523cmd.command = ISDN_STAT_UNLOAD;1524cmd.driver = card->myid;1525card->interface.statcallb(&cmd);1526for (i = 0; i < ISDNLOOP_BCH; i++)1527isdnloop_free_queue(card, i);1528card = card->next;1529}1530card = cards;1531while (card) {1532last = card;1533skb_queue_purge(&card->dqueue);1534card = card->next;1535kfree(last);1536}1537printk(KERN_NOTICE "isdnloop-ISDN-driver unloaded\n");1538}15391540module_init(isdnloop_init);1541module_exit(isdnloop_exit);154215431544