| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1 | /* $Id: isdnloop.c,v 1.11.6.7 2001/11/11 19:54:31 kai Exp $ | 
|  | 2 | * | 
|  | 3 | * ISDN low-level module implementing a dummy loop driver. | 
|  | 4 | * | 
|  | 5 | * Copyright 1997 by Fritz Elfert (fritz@isdn4linux.de) | 
|  | 6 | * | 
|  | 7 | * This software may be used and distributed according to the terms | 
|  | 8 | * of the GNU General Public License, incorporated herein by reference. | 
|  | 9 | * | 
|  | 10 | */ | 
|  | 11 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 12 | #include <linux/module.h> | 
|  | 13 | #include <linux/interrupt.h> | 
|  | 14 | #include <linux/init.h> | 
|  | 15 | #include <linux/sched.h> | 
|  | 16 | #include "isdnloop.h" | 
|  | 17 |  | 
|  | 18 | static char *revision = "$Revision: 1.11.6.7 $"; | 
|  | 19 | static char *isdnloop_id = "loop0"; | 
|  | 20 |  | 
|  | 21 | MODULE_DESCRIPTION("ISDN4Linux: Pseudo Driver that simulates an ISDN card"); | 
|  | 22 | MODULE_AUTHOR("Fritz Elfert"); | 
|  | 23 | MODULE_LICENSE("GPL"); | 
| Rusty Russell | 8d3b33f | 2006-03-25 03:07:05 -0800 | [diff] [blame] | 24 | module_param(isdnloop_id, charp, 0); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 25 | MODULE_PARM_DESC(isdnloop_id, "ID-String of first card"); | 
|  | 26 |  | 
|  | 27 | static int isdnloop_addcard(char *); | 
|  | 28 |  | 
|  | 29 | /* | 
|  | 30 | * Free queue completely. | 
|  | 31 | * | 
|  | 32 | * Parameter: | 
|  | 33 | *   card    = pointer to card struct | 
|  | 34 | *   channel = channel number | 
|  | 35 | */ | 
|  | 36 | static void | 
|  | 37 | isdnloop_free_queue(isdnloop_card * card, int channel) | 
|  | 38 | { | 
|  | 39 | struct sk_buff_head *queue = &card->bqueue[channel]; | 
|  | 40 |  | 
|  | 41 | skb_queue_purge(queue); | 
|  | 42 | card->sndcount[channel] = 0; | 
|  | 43 | } | 
|  | 44 |  | 
|  | 45 | /* | 
|  | 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 | */ | 
|  | 53 | static void | 
|  | 54 | isdnloop_bchan_send(isdnloop_card * card, int ch) | 
|  | 55 | { | 
|  | 56 | isdnloop_card *rcard = card->rcard[ch]; | 
|  | 57 | int rch = card->rch[ch], len, ack; | 
|  | 58 | struct sk_buff *skb; | 
|  | 59 | isdn_ctrl cmd; | 
|  | 60 |  | 
|  | 61 | while (card->sndcount[ch]) { | 
|  | 62 | if ((skb = skb_dequeue(&card->bqueue[ch]))) { | 
|  | 63 | len = skb->len; | 
|  | 64 | card->sndcount[ch] -= len; | 
|  | 65 | ack = *(skb->head); /* used as scratch area */ | 
|  | 66 | cmd.driver = card->myid; | 
|  | 67 | cmd.arg = ch; | 
|  | 68 | if (rcard){ | 
|  | 69 | rcard->interface.rcvcallb_skb(rcard->myid, rch, skb); | 
|  | 70 | } else { | 
|  | 71 | printk(KERN_WARNING "isdnloop: no rcard, skb dropped\n"); | 
|  | 72 | dev_kfree_skb(skb); | 
|  | 73 |  | 
|  | 74 | }; | 
|  | 75 | cmd.command = ISDN_STAT_BSENT; | 
|  | 76 | cmd.parm.length = len; | 
|  | 77 | card->interface.statcallb(&cmd); | 
|  | 78 | } else | 
|  | 79 | card->sndcount[ch] = 0; | 
|  | 80 | } | 
|  | 81 | } | 
|  | 82 |  | 
|  | 83 | /* | 
|  | 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.data | 
|  | 90 | */ | 
|  | 91 | static void | 
|  | 92 | isdnloop_pollbchan(unsigned long data) | 
|  | 93 | { | 
|  | 94 | isdnloop_card *card = (isdnloop_card *) data; | 
|  | 95 | unsigned long flags; | 
|  | 96 |  | 
|  | 97 | if (card->flags & ISDNLOOP_FLAGS_B1ACTIVE) | 
|  | 98 | isdnloop_bchan_send(card, 0); | 
|  | 99 | if (card->flags & ISDNLOOP_FLAGS_B2ACTIVE) | 
|  | 100 | isdnloop_bchan_send(card, 1); | 
|  | 101 | if (card->flags & (ISDNLOOP_FLAGS_B1ACTIVE | ISDNLOOP_FLAGS_B2ACTIVE)) { | 
|  | 102 | /* schedule b-channel polling again */ | 
| Amol Lad | 078d396 | 2006-10-17 00:10:37 -0700 | [diff] [blame] | 103 | spin_lock_irqsave(&card->isdnloop_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 104 | card->rb_timer.expires = jiffies + ISDNLOOP_TIMER_BCREAD; | 
|  | 105 | add_timer(&card->rb_timer); | 
|  | 106 | card->flags |= ISDNLOOP_FLAGS_RBTIMER; | 
| Amol Lad | 078d396 | 2006-10-17 00:10:37 -0700 | [diff] [blame] | 107 | spin_unlock_irqrestore(&card->isdnloop_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 108 | } else | 
|  | 109 | card->flags &= ~ISDNLOOP_FLAGS_RBTIMER; | 
|  | 110 | } | 
|  | 111 |  | 
|  | 112 | /* | 
|  | 113 | * Parse ICN-type setup string and fill fields of setup-struct | 
|  | 114 | * 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 | */ | 
|  | 120 | static void | 
|  | 121 | isdnloop_parse_setup(char *setup, isdn_ctrl * cmd) | 
|  | 122 | { | 
|  | 123 | char *t = setup; | 
|  | 124 | char *s = strchr(t, ','); | 
|  | 125 |  | 
|  | 126 | *s++ = '\0'; | 
|  | 127 | strlcpy(cmd->parm.setup.phone, t, sizeof(cmd->parm.setup.phone)); | 
|  | 128 | s = strchr(t = s, ','); | 
|  | 129 | *s++ = '\0'; | 
|  | 130 | if (!strlen(t)) | 
|  | 131 | cmd->parm.setup.si1 = 0; | 
|  | 132 | else | 
|  | 133 | cmd->parm.setup.si1 = simple_strtoul(t, NULL, 10); | 
|  | 134 | s = strchr(t = s, ','); | 
|  | 135 | *s++ = '\0'; | 
|  | 136 | if (!strlen(t)) | 
|  | 137 | cmd->parm.setup.si2 = 0; | 
|  | 138 | else | 
|  | 139 | cmd->parm.setup.si2 = | 
|  | 140 | simple_strtoul(t, NULL, 10); | 
|  | 141 | strlcpy(cmd->parm.setup.eazmsn, s, sizeof(cmd->parm.setup.eazmsn)); | 
|  | 142 | cmd->parm.setup.plan = 0; | 
|  | 143 | cmd->parm.setup.screen = 0; | 
|  | 144 | } | 
|  | 145 |  | 
|  | 146 | typedef struct isdnloop_stat { | 
|  | 147 | char *statstr; | 
|  | 148 | int command; | 
|  | 149 | int action; | 
|  | 150 | } isdnloop_stat; | 
|  | 151 | /* *INDENT-OFF* */ | 
|  | 152 | static 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", | 
|  | 168 | ISDN_STAT_BHUP,  8},         /* Layer-1 activation failed  */ | 
|  | 169 | {NULL, 0, -1} | 
|  | 170 | }; | 
|  | 171 | /* *INDENT-ON* */ | 
|  | 172 |  | 
|  | 173 |  | 
|  | 174 | /* | 
|  | 175 | * Parse Status message-strings from virtual card. | 
|  | 176 | * Depending on status, call statcallb for sending messages to upper | 
|  | 177 | * 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 | */ | 
|  | 184 | static void | 
|  | 185 | isdnloop_parse_status(u_char * status, int channel, isdnloop_card * card) | 
|  | 186 | { | 
|  | 187 | isdnloop_stat *s = isdnloop_stat_table; | 
|  | 188 | int action = -1; | 
|  | 189 | isdn_ctrl cmd; | 
|  | 190 |  | 
|  | 191 | while (s->statstr) { | 
|  | 192 | if (!strncmp(status, s->statstr, strlen(s->statstr))) { | 
|  | 193 | cmd.command = s->command; | 
|  | 194 | action = s->action; | 
|  | 195 | break; | 
|  | 196 | } | 
|  | 197 | s++; | 
|  | 198 | } | 
|  | 199 | if (action == -1) | 
|  | 200 | return; | 
|  | 201 | cmd.driver = card->myid; | 
|  | 202 | cmd.arg = channel; | 
|  | 203 | switch (action) { | 
|  | 204 | case 1: | 
|  | 205 | /* BCON_x */ | 
|  | 206 | card->flags |= (channel) ? | 
|  | 207 | ISDNLOOP_FLAGS_B2ACTIVE : ISDNLOOP_FLAGS_B1ACTIVE; | 
|  | 208 | break; | 
|  | 209 | case 2: | 
|  | 210 | /* BDIS_x */ | 
|  | 211 | card->flags &= ~((channel) ? | 
|  | 212 | ISDNLOOP_FLAGS_B2ACTIVE : ISDNLOOP_FLAGS_B1ACTIVE); | 
|  | 213 | isdnloop_free_queue(card, channel); | 
|  | 214 | break; | 
|  | 215 | case 3: | 
|  | 216 | /* DCAL_I and DSCA_I */ | 
|  | 217 | isdnloop_parse_setup(status + 6, &cmd); | 
|  | 218 | break; | 
|  | 219 | case 4: | 
|  | 220 | /* FCALL */ | 
|  | 221 | sprintf(cmd.parm.setup.phone, "LEASED%d", card->myid); | 
|  | 222 | sprintf(cmd.parm.setup.eazmsn, "%d", channel + 1); | 
|  | 223 | cmd.parm.setup.si1 = 7; | 
|  | 224 | cmd.parm.setup.si2 = 0; | 
|  | 225 | cmd.parm.setup.plan = 0; | 
|  | 226 | cmd.parm.setup.screen = 0; | 
|  | 227 | break; | 
|  | 228 | case 5: | 
|  | 229 | /* CIF */ | 
|  | 230 | strlcpy(cmd.parm.num, status + 3, sizeof(cmd.parm.num)); | 
|  | 231 | break; | 
|  | 232 | case 6: | 
|  | 233 | /* AOC */ | 
|  | 234 | snprintf(cmd.parm.num, sizeof(cmd.parm.num), "%d", | 
|  | 235 | (int) simple_strtoul(status + 7, NULL, 16)); | 
|  | 236 | break; | 
|  | 237 | case 7: | 
|  | 238 | /* CAU */ | 
|  | 239 | status += 3; | 
|  | 240 | if (strlen(status) == 4) | 
|  | 241 | snprintf(cmd.parm.num, sizeof(cmd.parm.num), "%s%c%c", | 
|  | 242 | status + 2, *status, *(status + 1)); | 
|  | 243 | else | 
|  | 244 | strlcpy(cmd.parm.num, status + 1, sizeof(cmd.parm.num)); | 
|  | 245 | break; | 
|  | 246 | case 8: | 
|  | 247 | /* Misc Errors on L1 and L2 */ | 
|  | 248 | card->flags &= ~ISDNLOOP_FLAGS_B1ACTIVE; | 
|  | 249 | isdnloop_free_queue(card, 0); | 
|  | 250 | cmd.arg = 0; | 
|  | 251 | cmd.driver = card->myid; | 
|  | 252 | card->interface.statcallb(&cmd); | 
|  | 253 | cmd.command = ISDN_STAT_DHUP; | 
|  | 254 | cmd.arg = 0; | 
|  | 255 | cmd.driver = card->myid; | 
|  | 256 | card->interface.statcallb(&cmd); | 
|  | 257 | cmd.command = ISDN_STAT_BHUP; | 
|  | 258 | card->flags &= ~ISDNLOOP_FLAGS_B2ACTIVE; | 
|  | 259 | isdnloop_free_queue(card, 1); | 
|  | 260 | cmd.arg = 1; | 
|  | 261 | cmd.driver = card->myid; | 
|  | 262 | card->interface.statcallb(&cmd); | 
|  | 263 | cmd.command = ISDN_STAT_DHUP; | 
|  | 264 | cmd.arg = 1; | 
|  | 265 | cmd.driver = card->myid; | 
|  | 266 | break; | 
|  | 267 | } | 
|  | 268 | card->interface.statcallb(&cmd); | 
|  | 269 | } | 
|  | 270 |  | 
|  | 271 | /* | 
|  | 272 | * Store a cwcharacter into ringbuffer for reading from /dev/isdnctrl | 
|  | 273 | * | 
|  | 274 | * Parameter: | 
|  | 275 | *   card = pointer to card struct. | 
|  | 276 | *   c    = char to store. | 
|  | 277 | */ | 
|  | 278 | static void | 
|  | 279 | isdnloop_putmsg(isdnloop_card * card, unsigned char c) | 
|  | 280 | { | 
|  | 281 | ulong flags; | 
|  | 282 |  | 
| Amol Lad | 078d396 | 2006-10-17 00:10:37 -0700 | [diff] [blame] | 283 | spin_lock_irqsave(&card->isdnloop_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 284 | *card->msg_buf_write++ = (c == 0xff) ? '\n' : c; | 
|  | 285 | if (card->msg_buf_write == card->msg_buf_read) { | 
|  | 286 | if (++card->msg_buf_read > card->msg_buf_end) | 
|  | 287 | card->msg_buf_read = card->msg_buf; | 
|  | 288 | } | 
|  | 289 | if (card->msg_buf_write > card->msg_buf_end) | 
|  | 290 | card->msg_buf_write = card->msg_buf; | 
| Amol Lad | 078d396 | 2006-10-17 00:10:37 -0700 | [diff] [blame] | 291 | spin_unlock_irqrestore(&card->isdnloop_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 292 | } | 
|  | 293 |  | 
|  | 294 | /* | 
|  | 295 | * Poll a virtual cards message queue. | 
|  | 296 | * If there are new status-replies from the card, copy them to | 
|  | 297 | * ringbuffer for reading on /dev/isdnctrl and call | 
|  | 298 | * isdnloop_parse_status() for processing them. Watch for special | 
|  | 299 | * Firmware bootmessage and parse it, to get the D-Channel protocol. | 
|  | 300 | * If there are B-Channels open, initiate a timer-callback to | 
|  | 301 | * isdnloop_pollbchan(). | 
|  | 302 | * This routine is called periodically via timer interrupt. | 
|  | 303 | * | 
|  | 304 | * Parameter: | 
|  | 305 | *   data = pointer to card struct | 
|  | 306 | */ | 
|  | 307 | static void | 
|  | 308 | isdnloop_polldchan(unsigned long data) | 
|  | 309 | { | 
|  | 310 | isdnloop_card *card = (isdnloop_card *) data; | 
|  | 311 | struct sk_buff *skb; | 
|  | 312 | int avail; | 
|  | 313 | int left; | 
|  | 314 | u_char c; | 
|  | 315 | int ch; | 
|  | 316 | unsigned long flags; | 
|  | 317 | u_char *p; | 
|  | 318 | isdn_ctrl cmd; | 
|  | 319 |  | 
|  | 320 | if ((skb = skb_dequeue(&card->dqueue))) | 
|  | 321 | avail = skb->len; | 
|  | 322 | else | 
|  | 323 | avail = 0; | 
|  | 324 | for (left = avail; left > 0; left--) { | 
|  | 325 | c = *skb->data; | 
|  | 326 | skb_pull(skb, 1); | 
|  | 327 | isdnloop_putmsg(card, c); | 
|  | 328 | card->imsg[card->iptr] = c; | 
|  | 329 | if (card->iptr < 59) | 
|  | 330 | card->iptr++; | 
|  | 331 | if (!skb->len) { | 
|  | 332 | avail++; | 
|  | 333 | isdnloop_putmsg(card, '\n'); | 
|  | 334 | card->imsg[card->iptr] = 0; | 
|  | 335 | card->iptr = 0; | 
|  | 336 | if (card->imsg[0] == '0' && card->imsg[1] >= '0' && | 
|  | 337 | card->imsg[1] <= '2' && card->imsg[2] == ';') { | 
|  | 338 | ch = (card->imsg[1] - '0') - 1; | 
|  | 339 | p = &card->imsg[3]; | 
|  | 340 | isdnloop_parse_status(p, ch, card); | 
|  | 341 | } else { | 
|  | 342 | p = card->imsg; | 
|  | 343 | if (!strncmp(p, "DRV1.", 5)) { | 
|  | 344 | printk(KERN_INFO "isdnloop: (%s) %s\n", CID, p); | 
|  | 345 | if (!strncmp(p + 7, "TC", 2)) { | 
|  | 346 | card->ptype = ISDN_PTYPE_1TR6; | 
|  | 347 | card->interface.features |= ISDN_FEATURE_P_1TR6; | 
|  | 348 | printk(KERN_INFO | 
|  | 349 | "isdnloop: (%s) 1TR6-Protocol loaded and running\n", CID); | 
|  | 350 | } | 
|  | 351 | if (!strncmp(p + 7, "EC", 2)) { | 
|  | 352 | card->ptype = ISDN_PTYPE_EURO; | 
|  | 353 | card->interface.features |= ISDN_FEATURE_P_EURO; | 
|  | 354 | printk(KERN_INFO | 
|  | 355 | "isdnloop: (%s) Euro-Protocol loaded and running\n", CID); | 
|  | 356 | } | 
|  | 357 | continue; | 
|  | 358 |  | 
|  | 359 | } | 
|  | 360 | } | 
|  | 361 | } | 
|  | 362 | } | 
|  | 363 | if (avail) { | 
|  | 364 | cmd.command = ISDN_STAT_STAVAIL; | 
|  | 365 | cmd.driver = card->myid; | 
|  | 366 | cmd.arg = avail; | 
|  | 367 | card->interface.statcallb(&cmd); | 
|  | 368 | } | 
|  | 369 | if (card->flags & (ISDNLOOP_FLAGS_B1ACTIVE | ISDNLOOP_FLAGS_B2ACTIVE)) | 
|  | 370 | if (!(card->flags & ISDNLOOP_FLAGS_RBTIMER)) { | 
|  | 371 | /* schedule b-channel polling */ | 
|  | 372 | card->flags |= ISDNLOOP_FLAGS_RBTIMER; | 
| Amol Lad | 078d396 | 2006-10-17 00:10:37 -0700 | [diff] [blame] | 373 | spin_lock_irqsave(&card->isdnloop_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 374 | del_timer(&card->rb_timer); | 
|  | 375 | card->rb_timer.function = isdnloop_pollbchan; | 
|  | 376 | card->rb_timer.data = (unsigned long) card; | 
|  | 377 | card->rb_timer.expires = jiffies + ISDNLOOP_TIMER_BCREAD; | 
|  | 378 | add_timer(&card->rb_timer); | 
| Amol Lad | 078d396 | 2006-10-17 00:10:37 -0700 | [diff] [blame] | 379 | spin_unlock_irqrestore(&card->isdnloop_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 380 | } | 
|  | 381 | /* schedule again */ | 
| Amol Lad | 078d396 | 2006-10-17 00:10:37 -0700 | [diff] [blame] | 382 | spin_lock_irqsave(&card->isdnloop_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 383 | card->st_timer.expires = jiffies + ISDNLOOP_TIMER_DCREAD; | 
|  | 384 | add_timer(&card->st_timer); | 
| Amol Lad | 078d396 | 2006-10-17 00:10:37 -0700 | [diff] [blame] | 385 | spin_unlock_irqrestore(&card->isdnloop_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 386 | } | 
|  | 387 |  | 
|  | 388 | /* | 
|  | 389 | * Append a packet to the transmit buffer-queue. | 
|  | 390 | * | 
|  | 391 | * Parameter: | 
|  | 392 | *   channel = Number of B-channel | 
|  | 393 | *   skb     = packet to send. | 
|  | 394 | *   card    = pointer to card-struct | 
|  | 395 | * Return: | 
|  | 396 | *   Number of bytes transferred, -E??? on error | 
|  | 397 | */ | 
|  | 398 | static int | 
|  | 399 | isdnloop_sendbuf(int channel, struct sk_buff *skb, isdnloop_card * card) | 
|  | 400 | { | 
|  | 401 | int len = skb->len; | 
|  | 402 | unsigned long flags; | 
|  | 403 | struct sk_buff *nskb; | 
|  | 404 |  | 
|  | 405 | if (len > 4000) { | 
|  | 406 | printk(KERN_WARNING | 
|  | 407 | "isdnloop: Send packet too large\n"); | 
|  | 408 | return -EINVAL; | 
|  | 409 | } | 
|  | 410 | if (len) { | 
|  | 411 | if (!(card->flags & (channel) ? ISDNLOOP_FLAGS_B2ACTIVE : ISDNLOOP_FLAGS_B1ACTIVE)) | 
|  | 412 | return 0; | 
|  | 413 | if (card->sndcount[channel] > ISDNLOOP_MAX_SQUEUE) | 
|  | 414 | return 0; | 
| Amol Lad | 078d396 | 2006-10-17 00:10:37 -0700 | [diff] [blame] | 415 | spin_lock_irqsave(&card->isdnloop_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 416 | nskb = dev_alloc_skb(skb->len); | 
|  | 417 | if (nskb) { | 
| Arnaldo Carvalho de Melo | d626f62 | 2007-03-27 18:55:52 -0300 | [diff] [blame] | 418 | skb_copy_from_linear_data(skb, | 
|  | 419 | skb_put(nskb, len), len); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 420 | skb_queue_tail(&card->bqueue[channel], nskb); | 
|  | 421 | dev_kfree_skb(skb); | 
|  | 422 | } else | 
|  | 423 | len = 0; | 
|  | 424 | card->sndcount[channel] += len; | 
| Amol Lad | 078d396 | 2006-10-17 00:10:37 -0700 | [diff] [blame] | 425 | spin_unlock_irqrestore(&card->isdnloop_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 426 | } | 
|  | 427 | return len; | 
|  | 428 | } | 
|  | 429 |  | 
|  | 430 | /* | 
|  | 431 | * Read the messages from the card's ringbuffer | 
|  | 432 | * | 
|  | 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 | */ | 
|  | 441 | static int | 
|  | 442 | isdnloop_readstatus(u_char __user *buf, int len, isdnloop_card * card) | 
|  | 443 | { | 
|  | 444 | int count; | 
|  | 445 | u_char __user *p; | 
|  | 446 |  | 
|  | 447 | for (p = buf, count = 0; count < len; p++, count++) { | 
|  | 448 | if (card->msg_buf_read == card->msg_buf_write) | 
|  | 449 | return count; | 
| Jeff Garzik | 7786ce1 | 2006-10-17 00:10:40 -0700 | [diff] [blame] | 450 | if (put_user(*card->msg_buf_read++, p)) | 
|  | 451 | return -EFAULT; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 452 | if (card->msg_buf_read > card->msg_buf_end) | 
|  | 453 | card->msg_buf_read = card->msg_buf; | 
|  | 454 | } | 
|  | 455 | return count; | 
|  | 456 | } | 
|  | 457 |  | 
|  | 458 | /* | 
|  | 459 | * Simulate a card's response by appending it to the cards | 
|  | 460 | * 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 | */ | 
|  | 469 | static int | 
|  | 470 | isdnloop_fake(isdnloop_card * card, char *s, int ch) | 
|  | 471 | { | 
|  | 472 | struct sk_buff *skb; | 
|  | 473 | int len = strlen(s) + ((ch >= 0) ? 3 : 0); | 
|  | 474 |  | 
|  | 475 | if (!(skb = dev_alloc_skb(len))) { | 
|  | 476 | printk(KERN_WARNING "isdnloop: Out of memory in isdnloop_fake\n"); | 
|  | 477 | return 1; | 
|  | 478 | } | 
|  | 479 | if (ch >= 0) | 
|  | 480 | sprintf(skb_put(skb, 3), "%02d;", ch); | 
|  | 481 | memcpy(skb_put(skb, strlen(s)), s, strlen(s)); | 
|  | 482 | skb_queue_tail(&card->dqueue, skb); | 
|  | 483 | return 0; | 
|  | 484 | } | 
|  | 485 | /* *INDENT-OFF* */ | 
|  | 486 | static 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* */ | 
|  | 510 |  | 
|  | 511 |  | 
|  | 512 | /* | 
|  | 513 | * Simulate an error-response from a card. | 
|  | 514 | * | 
|  | 515 | * Parameter: | 
|  | 516 | *   card = pointer to card struct. | 
|  | 517 | */ | 
|  | 518 | static void | 
|  | 519 | isdnloop_fake_err(isdnloop_card * card) | 
|  | 520 | { | 
|  | 521 | char buf[60]; | 
|  | 522 |  | 
|  | 523 | sprintf(buf, "E%s", card->omsg); | 
|  | 524 | isdnloop_fake(card, buf, -1); | 
|  | 525 | isdnloop_fake(card, "NAK", -1); | 
|  | 526 | } | 
|  | 527 |  | 
|  | 528 | static u_char ctable_eu[] = | 
|  | 529 | {0x00, 0x11, 0x01, 0x12}; | 
|  | 530 | static u_char ctable_1t[] = | 
|  | 531 | {0x00, 0x3b, 0x01, 0x3a}; | 
|  | 532 |  | 
|  | 533 | /* | 
|  | 534 | * Assemble a simplified cause message depending on the | 
|  | 535 | * 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 | */ | 
|  | 544 | static char * | 
|  | 545 | isdnloop_unicause(isdnloop_card * card, int loc, int cau) | 
|  | 546 | { | 
|  | 547 | static char buf[6]; | 
|  | 548 |  | 
|  | 549 | switch (card->ptype) { | 
|  | 550 | case ISDN_PTYPE_EURO: | 
|  | 551 | sprintf(buf, "E%02X%02X", (loc) ? 4 : 2, ctable_eu[cau]); | 
|  | 552 | break; | 
|  | 553 | case ISDN_PTYPE_1TR6: | 
|  | 554 | sprintf(buf, "%02X44", ctable_1t[cau]); | 
|  | 555 | break; | 
|  | 556 | default: | 
|  | 557 | return ("0000"); | 
|  | 558 | } | 
|  | 559 | return (buf); | 
|  | 560 | } | 
|  | 561 |  | 
|  | 562 | /* | 
|  | 563 | * Release a virtual connection. Called from timer interrupt, when | 
|  | 564 | * called party did not respond. | 
|  | 565 | * | 
|  | 566 | * Parameter: | 
|  | 567 | *   card = pointer to card struct. | 
|  | 568 | *   ch   = channel (0-based) | 
|  | 569 | */ | 
|  | 570 | static void | 
|  | 571 | isdnloop_atimeout(isdnloop_card * card, int ch) | 
|  | 572 | { | 
|  | 573 | unsigned long flags; | 
|  | 574 | char buf[60]; | 
|  | 575 |  | 
| Amol Lad | 078d396 | 2006-10-17 00:10:37 -0700 | [diff] [blame] | 576 | spin_lock_irqsave(&card->isdnloop_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 577 | if (card->rcard) { | 
|  | 578 | isdnloop_fake(card->rcard[ch], "DDIS_I", card->rch[ch] + 1); | 
|  | 579 | card->rcard[ch]->rcard[card->rch[ch]] = NULL; | 
|  | 580 | card->rcard[ch] = NULL; | 
|  | 581 | } | 
|  | 582 | isdnloop_fake(card, "DDIS_I", ch + 1); | 
|  | 583 | /* No user responding */ | 
|  | 584 | sprintf(buf, "CAU%s", isdnloop_unicause(card, 1, 3)); | 
|  | 585 | isdnloop_fake(card, buf, ch + 1); | 
| Amol Lad | 078d396 | 2006-10-17 00:10:37 -0700 | [diff] [blame] | 586 | spin_unlock_irqrestore(&card->isdnloop_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 587 | } | 
|  | 588 |  | 
|  | 589 | /* | 
|  | 590 | * Wrapper for isdnloop_atimeout(). | 
|  | 591 | */ | 
|  | 592 | static void | 
|  | 593 | isdnloop_atimeout0(unsigned long data) | 
|  | 594 | { | 
|  | 595 | isdnloop_card *card = (isdnloop_card *) data; | 
|  | 596 | isdnloop_atimeout(card, 0); | 
|  | 597 | } | 
|  | 598 |  | 
|  | 599 | /* | 
|  | 600 | * Wrapper for isdnloop_atimeout(). | 
|  | 601 | */ | 
|  | 602 | static void | 
|  | 603 | isdnloop_atimeout1(unsigned long data) | 
|  | 604 | { | 
|  | 605 | isdnloop_card *card = (isdnloop_card *) data; | 
|  | 606 | isdnloop_atimeout(card, 1); | 
|  | 607 | } | 
|  | 608 |  | 
|  | 609 | /* | 
|  | 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 | */ | 
|  | 616 | static void | 
|  | 617 | isdnloop_start_ctimer(isdnloop_card * card, int ch) | 
|  | 618 | { | 
|  | 619 | unsigned long flags; | 
|  | 620 |  | 
| Amol Lad | 078d396 | 2006-10-17 00:10:37 -0700 | [diff] [blame] | 621 | spin_lock_irqsave(&card->isdnloop_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 622 | init_timer(&card->c_timer[ch]); | 
|  | 623 | card->c_timer[ch].expires = jiffies + ISDNLOOP_TIMER_ALERTWAIT; | 
|  | 624 | if (ch) | 
|  | 625 | card->c_timer[ch].function = isdnloop_atimeout1; | 
|  | 626 | else | 
|  | 627 | card->c_timer[ch].function = isdnloop_atimeout0; | 
|  | 628 | card->c_timer[ch].data = (unsigned long) card; | 
|  | 629 | add_timer(&card->c_timer[ch]); | 
| Amol Lad | 078d396 | 2006-10-17 00:10:37 -0700 | [diff] [blame] | 630 | spin_unlock_irqrestore(&card->isdnloop_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 631 | } | 
|  | 632 |  | 
|  | 633 | /* | 
|  | 634 | * Kill a pending channel watchdog. | 
|  | 635 | * | 
|  | 636 | * Parameter: | 
|  | 637 | *   card = pointer to card struct. | 
|  | 638 | *   ch   = channel (0-based). | 
|  | 639 | */ | 
|  | 640 | static void | 
|  | 641 | isdnloop_kill_ctimer(isdnloop_card * card, int ch) | 
|  | 642 | { | 
|  | 643 | unsigned long flags; | 
|  | 644 |  | 
| Amol Lad | 078d396 | 2006-10-17 00:10:37 -0700 | [diff] [blame] | 645 | spin_lock_irqsave(&card->isdnloop_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 646 | del_timer(&card->c_timer[ch]); | 
| Amol Lad | 078d396 | 2006-10-17 00:10:37 -0700 | [diff] [blame] | 647 | spin_unlock_irqrestore(&card->isdnloop_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 648 | } | 
|  | 649 |  | 
|  | 650 | static u_char si2bit[] = | 
|  | 651 | {0, 1, 0, 0, 0, 2, 0, 4, 0, 0}; | 
|  | 652 | static u_char bit2si[] = | 
|  | 653 | {1, 5, 7}; | 
|  | 654 |  | 
|  | 655 | /* | 
|  | 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 | */ | 
|  | 669 | static int | 
|  | 670 | isdnloop_try_call(isdnloop_card * card, char *p, int lch, isdn_ctrl * cmd) | 
|  | 671 | { | 
|  | 672 | isdnloop_card *cc = cards; | 
|  | 673 | unsigned long flags; | 
|  | 674 | int ch; | 
|  | 675 | int num_match; | 
|  | 676 | int i; | 
|  | 677 | char *e; | 
|  | 678 | char nbuf[32]; | 
|  | 679 |  | 
|  | 680 | isdnloop_parse_setup(p, cmd); | 
|  | 681 | while (cc) { | 
|  | 682 | for (ch = 0; ch < 2; ch++) { | 
|  | 683 | /* Exclude ourself */ | 
|  | 684 | if ((cc == card) && (ch == lch)) | 
|  | 685 | continue; | 
|  | 686 | num_match = 0; | 
|  | 687 | switch (cc->ptype) { | 
|  | 688 | case ISDN_PTYPE_EURO: | 
|  | 689 | for (i = 0; i < 3; i++) | 
|  | 690 | if (!(strcmp(cc->s0num[i], cmd->parm.setup.phone))) | 
|  | 691 | num_match = 1; | 
|  | 692 | break; | 
|  | 693 | case ISDN_PTYPE_1TR6: | 
|  | 694 | e = cc->eazlist[ch]; | 
|  | 695 | while (*e) { | 
|  | 696 | sprintf(nbuf, "%s%c", cc->s0num[0], *e); | 
|  | 697 | if (!(strcmp(nbuf, cmd->parm.setup.phone))) | 
|  | 698 | num_match = 1; | 
|  | 699 | e++; | 
|  | 700 | } | 
|  | 701 | } | 
|  | 702 | if (num_match) { | 
| Amol Lad | 078d396 | 2006-10-17 00:10:37 -0700 | [diff] [blame] | 703 | spin_lock_irqsave(&card->isdnloop_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 704 | /* channel idle? */ | 
|  | 705 | if (!(cc->rcard[ch])) { | 
|  | 706 | /* Check SI */ | 
|  | 707 | if (!(si2bit[cmd->parm.setup.si1] & cc->sil[ch])) { | 
| Amol Lad | 078d396 | 2006-10-17 00:10:37 -0700 | [diff] [blame] | 708 | spin_unlock_irqrestore(&card->isdnloop_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 709 | return 3; | 
|  | 710 | } | 
|  | 711 | /* ch is idle, si and number matches */ | 
|  | 712 | cc->rcard[ch] = card; | 
|  | 713 | cc->rch[ch] = lch; | 
|  | 714 | card->rcard[lch] = cc; | 
|  | 715 | card->rch[lch] = ch; | 
| Amol Lad | 078d396 | 2006-10-17 00:10:37 -0700 | [diff] [blame] | 716 | spin_unlock_irqrestore(&card->isdnloop_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 717 | return 0; | 
|  | 718 | } else { | 
| Amol Lad | 078d396 | 2006-10-17 00:10:37 -0700 | [diff] [blame] | 719 | spin_unlock_irqrestore(&card->isdnloop_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 720 | /* num matches, but busy */ | 
|  | 721 | if (ch == 1) | 
|  | 722 | return 1; | 
|  | 723 | } | 
|  | 724 | } | 
|  | 725 | } | 
|  | 726 | cc = cc->next; | 
|  | 727 | } | 
|  | 728 | return 2; | 
|  | 729 | } | 
|  | 730 |  | 
|  | 731 | /* | 
|  | 732 | * Depending on D-channel protocol and caller/called, modify | 
|  | 733 | * 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 | */ | 
|  | 742 | static char * | 
|  | 743 | isdnloop_vstphone(isdnloop_card * card, char *phone, int caller) | 
|  | 744 | { | 
|  | 745 | int i; | 
|  | 746 | static char nphone[30]; | 
|  | 747 |  | 
|  | 748 | if (!card) { | 
|  | 749 | printk("BUG!!!\n"); | 
|  | 750 | return ""; | 
|  | 751 | } | 
|  | 752 | switch (card->ptype) { | 
|  | 753 | case ISDN_PTYPE_EURO: | 
|  | 754 | if (caller) { | 
|  | 755 | for (i = 0; i < 2; i++) | 
|  | 756 | if (!(strcmp(card->s0num[i], phone))) | 
|  | 757 | return (phone); | 
|  | 758 | return (card->s0num[0]); | 
|  | 759 | } | 
|  | 760 | return (phone); | 
|  | 761 | break; | 
|  | 762 | case ISDN_PTYPE_1TR6: | 
|  | 763 | if (caller) { | 
|  | 764 | sprintf(nphone, "%s%c", card->s0num[0], phone[0]); | 
|  | 765 | return (nphone); | 
|  | 766 | } else | 
|  | 767 | return (&phone[strlen(phone) - 1]); | 
|  | 768 | break; | 
|  | 769 | } | 
|  | 770 | return ""; | 
|  | 771 | } | 
|  | 772 |  | 
|  | 773 | /* | 
|  | 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 | */ | 
|  | 780 | static void | 
|  | 781 | isdnloop_parse_cmd(isdnloop_card * card) | 
|  | 782 | { | 
|  | 783 | char *p = card->omsg; | 
|  | 784 | isdn_ctrl cmd; | 
|  | 785 | char buf[60]; | 
|  | 786 | isdnloop_stat *s = isdnloop_cmd_table; | 
|  | 787 | int action = -1; | 
|  | 788 | int i; | 
|  | 789 | int ch; | 
|  | 790 |  | 
|  | 791 | if ((card->omsg[0] != '0') && (card->omsg[2] != ';')) { | 
|  | 792 | isdnloop_fake_err(card); | 
|  | 793 | return; | 
|  | 794 | } | 
|  | 795 | ch = card->omsg[1] - '0'; | 
|  | 796 | if ((ch < 0) || (ch > 2)) { | 
|  | 797 | isdnloop_fake_err(card); | 
|  | 798 | return; | 
|  | 799 | } | 
|  | 800 | p += 3; | 
|  | 801 | while (s->statstr) { | 
|  | 802 | if (!strncmp(p, s->statstr, strlen(s->statstr))) { | 
|  | 803 | action = s->action; | 
|  | 804 | if (s->command && (ch != 0)) { | 
|  | 805 | isdnloop_fake_err(card); | 
|  | 806 | return; | 
|  | 807 | } | 
|  | 808 | break; | 
|  | 809 | } | 
|  | 810 | s++; | 
|  | 811 | } | 
|  | 812 | if (action == -1) | 
|  | 813 | return; | 
|  | 814 | switch (action) { | 
|  | 815 | case 1: | 
|  | 816 | /* 0x;BCON_R */ | 
|  | 817 | if (card->rcard[ch - 1]) { | 
|  | 818 | isdnloop_fake(card->rcard[ch - 1], "BCON_I", | 
|  | 819 | card->rch[ch - 1] + 1); | 
|  | 820 | isdnloop_fake(card, "BCON_C", ch); | 
|  | 821 | } | 
|  | 822 | break; | 
|  | 823 | case 17: | 
|  | 824 | /* 0x;BCON_I */ | 
|  | 825 | if (card->rcard[ch - 1]) { | 
|  | 826 | isdnloop_fake(card->rcard[ch - 1], "BCON_C", | 
|  | 827 | card->rch[ch - 1] + 1); | 
|  | 828 | } | 
|  | 829 | break; | 
|  | 830 | case 2: | 
|  | 831 | /* 0x;BDIS_R */ | 
|  | 832 | isdnloop_fake(card, "BDIS_C", ch); | 
|  | 833 | if (card->rcard[ch - 1]) { | 
|  | 834 | isdnloop_fake(card->rcard[ch - 1], "BDIS_I", | 
|  | 835 | card->rch[ch - 1] + 1); | 
|  | 836 | } | 
|  | 837 | break; | 
|  | 838 | case 16: | 
|  | 839 | /* 0x;DCON_R */ | 
|  | 840 | isdnloop_kill_ctimer(card, ch - 1); | 
|  | 841 | if (card->rcard[ch - 1]) { | 
|  | 842 | isdnloop_kill_ctimer(card->rcard[ch - 1], card->rch[ch - 1]); | 
|  | 843 | isdnloop_fake(card->rcard[ch - 1], "DCON_C", | 
|  | 844 | card->rch[ch - 1] + 1); | 
|  | 845 | isdnloop_fake(card, "DCON_C", ch); | 
|  | 846 | } | 
|  | 847 | break; | 
|  | 848 | case 3: | 
|  | 849 | /* 0x;DDIS_R */ | 
|  | 850 | isdnloop_kill_ctimer(card, ch - 1); | 
|  | 851 | if (card->rcard[ch - 1]) { | 
|  | 852 | isdnloop_kill_ctimer(card->rcard[ch - 1], card->rch[ch - 1]); | 
|  | 853 | isdnloop_fake(card->rcard[ch - 1], "DDIS_I", | 
|  | 854 | card->rch[ch - 1] + 1); | 
|  | 855 | card->rcard[ch - 1] = NULL; | 
|  | 856 | } | 
|  | 857 | isdnloop_fake(card, "DDIS_C", ch); | 
|  | 858 | break; | 
|  | 859 | case 4: | 
|  | 860 | /* 0x;DSCA_Rdd,yy,zz,oo */ | 
|  | 861 | if (card->ptype != ISDN_PTYPE_1TR6) { | 
|  | 862 | isdnloop_fake_err(card); | 
|  | 863 | return; | 
|  | 864 | } | 
|  | 865 | /* Fall through */ | 
|  | 866 | case 5: | 
|  | 867 | /* 0x;DCAL_Rdd,yy,zz,oo */ | 
|  | 868 | p += 6; | 
|  | 869 | switch (isdnloop_try_call(card, p, ch - 1, &cmd)) { | 
|  | 870 | case 0: | 
|  | 871 | /* Alerting */ | 
|  | 872 | sprintf(buf, "D%s_I%s,%02d,%02d,%s", | 
|  | 873 | (action == 4) ? "SCA" : "CAL", | 
|  | 874 | isdnloop_vstphone(card, cmd.parm.setup.eazmsn, 1), | 
|  | 875 | cmd.parm.setup.si1, | 
|  | 876 | cmd.parm.setup.si2, | 
|  | 877 | isdnloop_vstphone(card->rcard[ch - 1], | 
|  | 878 | cmd.parm.setup.phone, 0)); | 
|  | 879 | isdnloop_fake(card->rcard[ch - 1], buf, card->rch[ch - 1] + 1); | 
|  | 880 | /* Fall through */ | 
|  | 881 | case 3: | 
|  | 882 | /* si1 does not match, don't alert but start timer */ | 
|  | 883 | isdnloop_start_ctimer(card, ch - 1); | 
|  | 884 | break; | 
|  | 885 | case 1: | 
|  | 886 | /* Remote busy */ | 
|  | 887 | isdnloop_fake(card, "DDIS_I", ch); | 
|  | 888 | sprintf(buf, "CAU%s", isdnloop_unicause(card, 1, 1)); | 
|  | 889 | isdnloop_fake(card, buf, ch); | 
|  | 890 | break; | 
|  | 891 | case 2: | 
|  | 892 | /* No such user */ | 
|  | 893 | isdnloop_fake(card, "DDIS_I", ch); | 
|  | 894 | sprintf(buf, "CAU%s", isdnloop_unicause(card, 1, 2)); | 
|  | 895 | isdnloop_fake(card, buf, ch); | 
|  | 896 | break; | 
|  | 897 | } | 
|  | 898 | break; | 
|  | 899 | case 6: | 
|  | 900 | /* 0x;EAZC */ | 
|  | 901 | card->eazlist[ch - 1][0] = '\0'; | 
|  | 902 | break; | 
|  | 903 | case 7: | 
|  | 904 | /* 0x;EAZ */ | 
|  | 905 | p += 3; | 
|  | 906 | strcpy(card->eazlist[ch - 1], p); | 
|  | 907 | break; | 
|  | 908 | case 8: | 
|  | 909 | /* 0x;SEEAZ */ | 
|  | 910 | sprintf(buf, "EAZ-LIST: %s", card->eazlist[ch - 1]); | 
|  | 911 | isdnloop_fake(card, buf, ch + 1); | 
|  | 912 | break; | 
|  | 913 | case 9: | 
|  | 914 | /* 0x;MSN */ | 
|  | 915 | break; | 
|  | 916 | case 10: | 
|  | 917 | /* 0x;MSNALL */ | 
|  | 918 | break; | 
|  | 919 | case 11: | 
|  | 920 | /* 0x;SETSIL */ | 
|  | 921 | p += 6; | 
|  | 922 | i = 0; | 
|  | 923 | while (strchr("0157", *p)) { | 
|  | 924 | if (i) | 
|  | 925 | card->sil[ch - 1] |= si2bit[*p - '0']; | 
|  | 926 | i = (*p++ == '0'); | 
|  | 927 | } | 
|  | 928 | if (*p) | 
|  | 929 | isdnloop_fake_err(card); | 
|  | 930 | break; | 
|  | 931 | case 12: | 
|  | 932 | /* 0x;SEESIL */ | 
|  | 933 | sprintf(buf, "SIN-LIST: "); | 
|  | 934 | p = buf + 10; | 
|  | 935 | for (i = 0; i < 3; i++) | 
|  | 936 | if (card->sil[ch - 1] & (1 << i)) | 
|  | 937 | p += sprintf(p, "%02d", bit2si[i]); | 
|  | 938 | isdnloop_fake(card, buf, ch + 1); | 
|  | 939 | break; | 
|  | 940 | case 13: | 
|  | 941 | /* 0x;SILC */ | 
|  | 942 | card->sil[ch - 1] = 0; | 
|  | 943 | break; | 
|  | 944 | case 14: | 
|  | 945 | /* 00;FV2ON */ | 
|  | 946 | break; | 
|  | 947 | case 15: | 
|  | 948 | /* 00;FV2OFF */ | 
|  | 949 | break; | 
|  | 950 | } | 
|  | 951 | } | 
|  | 952 |  | 
|  | 953 | /* | 
|  | 954 | * Put command-strings into the of the 'card'. In reality, execute them | 
|  | 955 | * right in place by calling isdnloop_parse_cmd(). Also copy every | 
|  | 956 | * command to the read message ringbuffer, preceeding 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 | */ | 
|  | 967 | static int | 
|  | 968 | isdnloop_writecmd(const u_char * buf, int len, int user, isdnloop_card * card) | 
|  | 969 | { | 
|  | 970 | int xcount = 0; | 
|  | 971 | int ocount = 1; | 
|  | 972 | isdn_ctrl cmd; | 
|  | 973 |  | 
|  | 974 | while (len) { | 
|  | 975 | int count = len; | 
|  | 976 | u_char *p; | 
|  | 977 | u_char msg[0x100]; | 
|  | 978 |  | 
|  | 979 | if (count > 255) | 
|  | 980 | count = 255; | 
|  | 981 | if (user) { | 
|  | 982 | if (copy_from_user(msg, buf, count)) | 
|  | 983 | return -EFAULT; | 
|  | 984 | } else | 
|  | 985 | memcpy(msg, buf, count); | 
|  | 986 | isdnloop_putmsg(card, '>'); | 
|  | 987 | for (p = msg; count > 0; count--, p++) { | 
|  | 988 | len--; | 
|  | 989 | xcount++; | 
|  | 990 | isdnloop_putmsg(card, *p); | 
|  | 991 | card->omsg[card->optr] = *p; | 
|  | 992 | if (*p == '\n') { | 
|  | 993 | card->omsg[card->optr] = '\0'; | 
|  | 994 | card->optr = 0; | 
|  | 995 | isdnloop_parse_cmd(card); | 
|  | 996 | if (len) { | 
|  | 997 | isdnloop_putmsg(card, '>'); | 
|  | 998 | ocount++; | 
|  | 999 | } | 
|  | 1000 | } else { | 
|  | 1001 | if (card->optr < 59) | 
|  | 1002 | card->optr++; | 
|  | 1003 | } | 
|  | 1004 | ocount++; | 
|  | 1005 | } | 
|  | 1006 | } | 
|  | 1007 | cmd.command = ISDN_STAT_STAVAIL; | 
|  | 1008 | cmd.driver = card->myid; | 
|  | 1009 | cmd.arg = ocount; | 
|  | 1010 | card->interface.statcallb(&cmd); | 
|  | 1011 | return xcount; | 
|  | 1012 | } | 
|  | 1013 |  | 
|  | 1014 | /* | 
|  | 1015 | * Delete card's pending timers, send STOP to linklevel | 
|  | 1016 | */ | 
|  | 1017 | static void | 
|  | 1018 | isdnloop_stopcard(isdnloop_card * card) | 
|  | 1019 | { | 
|  | 1020 | unsigned long flags; | 
|  | 1021 | isdn_ctrl cmd; | 
|  | 1022 |  | 
| Amol Lad | 078d396 | 2006-10-17 00:10:37 -0700 | [diff] [blame] | 1023 | spin_lock_irqsave(&card->isdnloop_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1024 | if (card->flags & ISDNLOOP_FLAGS_RUNNING) { | 
|  | 1025 | card->flags &= ~ISDNLOOP_FLAGS_RUNNING; | 
|  | 1026 | del_timer(&card->st_timer); | 
|  | 1027 | del_timer(&card->rb_timer); | 
|  | 1028 | del_timer(&card->c_timer[0]); | 
|  | 1029 | del_timer(&card->c_timer[1]); | 
|  | 1030 | cmd.command = ISDN_STAT_STOP; | 
|  | 1031 | cmd.driver = card->myid; | 
|  | 1032 | card->interface.statcallb(&cmd); | 
|  | 1033 | } | 
| Amol Lad | 078d396 | 2006-10-17 00:10:37 -0700 | [diff] [blame] | 1034 | spin_unlock_irqrestore(&card->isdnloop_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1035 | } | 
|  | 1036 |  | 
|  | 1037 | /* | 
|  | 1038 | * Stop all cards before unload. | 
|  | 1039 | */ | 
|  | 1040 | static void | 
|  | 1041 | isdnloop_stopallcards(void) | 
|  | 1042 | { | 
|  | 1043 | isdnloop_card *p = cards; | 
|  | 1044 |  | 
|  | 1045 | while (p) { | 
|  | 1046 | isdnloop_stopcard(p); | 
|  | 1047 | p = p->next; | 
|  | 1048 | } | 
|  | 1049 | } | 
|  | 1050 |  | 
|  | 1051 | /* | 
|  | 1052 | * Start a 'card'. Simulate card's boot message and set the phone | 
|  | 1053 | * number(s) of the virtual 'S0-Interface'. Install D-channel | 
|  | 1054 | * 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 | */ | 
|  | 1062 | static int | 
|  | 1063 | isdnloop_start(isdnloop_card * card, isdnloop_sdef * sdefp) | 
|  | 1064 | { | 
|  | 1065 | unsigned long flags; | 
|  | 1066 | isdnloop_sdef sdef; | 
|  | 1067 | int i; | 
|  | 1068 |  | 
|  | 1069 | if (card->flags & ISDNLOOP_FLAGS_RUNNING) | 
|  | 1070 | return -EBUSY; | 
|  | 1071 | if (copy_from_user((char *) &sdef, (char *) sdefp, sizeof(sdef))) | 
|  | 1072 | return -EFAULT; | 
| Amol Lad | 078d396 | 2006-10-17 00:10:37 -0700 | [diff] [blame] | 1073 | spin_lock_irqsave(&card->isdnloop_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1074 | switch (sdef.ptype) { | 
|  | 1075 | case ISDN_PTYPE_EURO: | 
|  | 1076 | if (isdnloop_fake(card, "DRV1.23EC-Q.931-CAPI-CNS-BASIS-20.02.96", | 
|  | 1077 | -1)) { | 
| Amol Lad | 078d396 | 2006-10-17 00:10:37 -0700 | [diff] [blame] | 1078 | spin_unlock_irqrestore(&card->isdnloop_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1079 | return -ENOMEM; | 
|  | 1080 | } | 
|  | 1081 | card->sil[0] = card->sil[1] = 4; | 
|  | 1082 | if (isdnloop_fake(card, "TEI OK", 0)) { | 
| Amol Lad | 078d396 | 2006-10-17 00:10:37 -0700 | [diff] [blame] | 1083 | spin_unlock_irqrestore(&card->isdnloop_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1084 | return -ENOMEM; | 
|  | 1085 | } | 
|  | 1086 | for (i = 0; i < 3; i++) | 
|  | 1087 | strcpy(card->s0num[i], sdef.num[i]); | 
|  | 1088 | break; | 
|  | 1089 | case ISDN_PTYPE_1TR6: | 
|  | 1090 | if (isdnloop_fake(card, "DRV1.04TC-1TR6-CAPI-CNS-BASIS-29.11.95", | 
|  | 1091 | -1)) { | 
| Amol Lad | 078d396 | 2006-10-17 00:10:37 -0700 | [diff] [blame] | 1092 | spin_unlock_irqrestore(&card->isdnloop_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1093 | return -ENOMEM; | 
|  | 1094 | } | 
|  | 1095 | card->sil[0] = card->sil[1] = 4; | 
|  | 1096 | if (isdnloop_fake(card, "TEI OK", 0)) { | 
| Amol Lad | 078d396 | 2006-10-17 00:10:37 -0700 | [diff] [blame] | 1097 | spin_unlock_irqrestore(&card->isdnloop_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1098 | return -ENOMEM; | 
|  | 1099 | } | 
|  | 1100 | strcpy(card->s0num[0], sdef.num[0]); | 
|  | 1101 | card->s0num[1][0] = '\0'; | 
|  | 1102 | card->s0num[2][0] = '\0'; | 
|  | 1103 | break; | 
|  | 1104 | default: | 
| Amol Lad | 078d396 | 2006-10-17 00:10:37 -0700 | [diff] [blame] | 1105 | spin_unlock_irqrestore(&card->isdnloop_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1106 | printk(KERN_WARNING "isdnloop: Illegal D-channel protocol %d\n", | 
|  | 1107 | sdef.ptype); | 
|  | 1108 | return -EINVAL; | 
|  | 1109 | } | 
|  | 1110 | init_timer(&card->st_timer); | 
|  | 1111 | card->st_timer.expires = jiffies + ISDNLOOP_TIMER_DCREAD; | 
|  | 1112 | card->st_timer.function = isdnloop_polldchan; | 
|  | 1113 | card->st_timer.data = (unsigned long) card; | 
|  | 1114 | add_timer(&card->st_timer); | 
|  | 1115 | card->flags |= ISDNLOOP_FLAGS_RUNNING; | 
| Amol Lad | 078d396 | 2006-10-17 00:10:37 -0700 | [diff] [blame] | 1116 | spin_unlock_irqrestore(&card->isdnloop_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1117 | return 0; | 
|  | 1118 | } | 
|  | 1119 |  | 
|  | 1120 | /* | 
|  | 1121 | * Main handler for commands sent by linklevel. | 
|  | 1122 | */ | 
|  | 1123 | static int | 
|  | 1124 | isdnloop_command(isdn_ctrl * c, isdnloop_card * card) | 
|  | 1125 | { | 
|  | 1126 | ulong a; | 
|  | 1127 | int i; | 
|  | 1128 | char cbuf[60]; | 
|  | 1129 | isdn_ctrl cmd; | 
|  | 1130 | isdnloop_cdef cdef; | 
|  | 1131 |  | 
|  | 1132 | switch (c->command) { | 
|  | 1133 | case ISDN_CMD_IOCTL: | 
|  | 1134 | memcpy(&a, c->parm.num, sizeof(ulong)); | 
|  | 1135 | switch (c->arg) { | 
|  | 1136 | case ISDNLOOP_IOCTL_DEBUGVAR: | 
|  | 1137 | return (ulong) card; | 
|  | 1138 | case ISDNLOOP_IOCTL_STARTUP: | 
|  | 1139 | if (!access_ok(VERIFY_READ, (void *) a, sizeof(isdnloop_sdef))) | 
|  | 1140 | return -EFAULT; | 
|  | 1141 | return (isdnloop_start(card, (isdnloop_sdef *) a)); | 
|  | 1142 | break; | 
|  | 1143 | case ISDNLOOP_IOCTL_ADDCARD: | 
|  | 1144 | if (copy_from_user((char *)&cdef, | 
|  | 1145 | (char *)a, | 
|  | 1146 | sizeof(cdef))) | 
|  | 1147 | return -EFAULT; | 
|  | 1148 | return (isdnloop_addcard(cdef.id1)); | 
|  | 1149 | break; | 
|  | 1150 | case ISDNLOOP_IOCTL_LEASEDCFG: | 
|  | 1151 | if (a) { | 
|  | 1152 | if (!card->leased) { | 
|  | 1153 | card->leased = 1; | 
| Nishanth Aravamudan | 24763c4 | 2005-11-07 01:01:16 -0800 | [diff] [blame] | 1154 | while (card->ptype == ISDN_PTYPE_UNKNOWN) | 
|  | 1155 | schedule_timeout_interruptible(10); | 
|  | 1156 | schedule_timeout_interruptible(10); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1157 | sprintf(cbuf, "00;FV2ON\n01;EAZ1\n02;EAZ2\n"); | 
|  | 1158 | i = isdnloop_writecmd(cbuf, strlen(cbuf), 0, card); | 
|  | 1159 | printk(KERN_INFO | 
|  | 1160 | "isdnloop: (%s) Leased-line mode enabled\n", | 
|  | 1161 | CID); | 
|  | 1162 | cmd.command = ISDN_STAT_RUN; | 
|  | 1163 | cmd.driver = card->myid; | 
|  | 1164 | cmd.arg = 0; | 
|  | 1165 | card->interface.statcallb(&cmd); | 
|  | 1166 | } | 
|  | 1167 | } else { | 
|  | 1168 | if (card->leased) { | 
|  | 1169 | card->leased = 0; | 
|  | 1170 | sprintf(cbuf, "00;FV2OFF\n"); | 
|  | 1171 | i = isdnloop_writecmd(cbuf, strlen(cbuf), 0, card); | 
|  | 1172 | printk(KERN_INFO | 
|  | 1173 | "isdnloop: (%s) Leased-line mode disabled\n", | 
|  | 1174 | CID); | 
|  | 1175 | cmd.command = ISDN_STAT_RUN; | 
|  | 1176 | cmd.driver = card->myid; | 
|  | 1177 | cmd.arg = 0; | 
|  | 1178 | card->interface.statcallb(&cmd); | 
|  | 1179 | } | 
|  | 1180 | } | 
|  | 1181 | return 0; | 
|  | 1182 | default: | 
|  | 1183 | return -EINVAL; | 
|  | 1184 | } | 
|  | 1185 | break; | 
|  | 1186 | case ISDN_CMD_DIAL: | 
| Alexey Dobriyan | eaa0ff1 | 2008-02-06 01:36:06 -0800 | [diff] [blame] | 1187 | if (!(card->flags & ISDNLOOP_FLAGS_RUNNING)) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1188 | return -ENODEV; | 
|  | 1189 | if (card->leased) | 
|  | 1190 | break; | 
|  | 1191 | if ((c->arg & 255) < ISDNLOOP_BCH) { | 
|  | 1192 | char *p; | 
|  | 1193 | char dial[50]; | 
|  | 1194 | char dcode[4]; | 
|  | 1195 |  | 
|  | 1196 | a = c->arg; | 
|  | 1197 | p = c->parm.setup.phone; | 
|  | 1198 | if (*p == 's' || *p == 'S') { | 
|  | 1199 | /* Dial for SPV */ | 
|  | 1200 | p++; | 
|  | 1201 | strcpy(dcode, "SCA"); | 
|  | 1202 | } else | 
|  | 1203 | /* Normal Dial */ | 
|  | 1204 | strcpy(dcode, "CAL"); | 
|  | 1205 | strcpy(dial, p); | 
|  | 1206 | sprintf(cbuf, "%02d;D%s_R%s,%02d,%02d,%s\n", (int) (a + 1), | 
|  | 1207 | dcode, dial, c->parm.setup.si1, | 
|  | 1208 | c->parm.setup.si2, c->parm.setup.eazmsn); | 
|  | 1209 | i = isdnloop_writecmd(cbuf, strlen(cbuf), 0, card); | 
|  | 1210 | } | 
|  | 1211 | break; | 
|  | 1212 | case ISDN_CMD_ACCEPTD: | 
| Alexey Dobriyan | eaa0ff1 | 2008-02-06 01:36:06 -0800 | [diff] [blame] | 1213 | if (!(card->flags & ISDNLOOP_FLAGS_RUNNING)) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1214 | return -ENODEV; | 
|  | 1215 | if (c->arg < ISDNLOOP_BCH) { | 
|  | 1216 | a = c->arg + 1; | 
|  | 1217 | cbuf[0] = 0; | 
|  | 1218 | switch (card->l2_proto[a - 1]) { | 
|  | 1219 | case ISDN_PROTO_L2_X75I: | 
|  | 1220 | sprintf(cbuf, "%02d;BX75\n", (int) a); | 
|  | 1221 | break; | 
|  | 1222 | #ifdef CONFIG_ISDN_X25 | 
|  | 1223 | case ISDN_PROTO_L2_X25DTE: | 
|  | 1224 | sprintf(cbuf, "%02d;BX2T\n", (int) a); | 
|  | 1225 | break; | 
|  | 1226 | case ISDN_PROTO_L2_X25DCE: | 
|  | 1227 | sprintf(cbuf, "%02d;BX2C\n", (int) a); | 
|  | 1228 | break; | 
|  | 1229 | #endif | 
|  | 1230 | case ISDN_PROTO_L2_HDLC: | 
|  | 1231 | sprintf(cbuf, "%02d;BTRA\n", (int) a); | 
|  | 1232 | break; | 
|  | 1233 | } | 
|  | 1234 | if (strlen(cbuf)) | 
|  | 1235 | i = isdnloop_writecmd(cbuf, strlen(cbuf), 0, card); | 
|  | 1236 | sprintf(cbuf, "%02d;DCON_R\n", (int) a); | 
|  | 1237 | i = isdnloop_writecmd(cbuf, strlen(cbuf), 0, card); | 
|  | 1238 | } | 
|  | 1239 | break; | 
|  | 1240 | case ISDN_CMD_ACCEPTB: | 
| Alexey Dobriyan | eaa0ff1 | 2008-02-06 01:36:06 -0800 | [diff] [blame] | 1241 | if (!(card->flags & ISDNLOOP_FLAGS_RUNNING)) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1242 | return -ENODEV; | 
|  | 1243 | if (c->arg < ISDNLOOP_BCH) { | 
|  | 1244 | a = c->arg + 1; | 
|  | 1245 | switch (card->l2_proto[a - 1]) { | 
|  | 1246 | case ISDN_PROTO_L2_X75I: | 
|  | 1247 | sprintf(cbuf, "%02d;BCON_R,BX75\n", (int) a); | 
|  | 1248 | break; | 
|  | 1249 | #ifdef CONFIG_ISDN_X25 | 
|  | 1250 | case ISDN_PROTO_L2_X25DTE: | 
|  | 1251 | sprintf(cbuf, "%02d;BCON_R,BX2T\n", (int) a); | 
|  | 1252 | break; | 
|  | 1253 | case ISDN_PROTO_L2_X25DCE: | 
|  | 1254 | sprintf(cbuf, "%02d;BCON_R,BX2C\n", (int) a); | 
|  | 1255 | break; | 
|  | 1256 | #endif | 
|  | 1257 | case ISDN_PROTO_L2_HDLC: | 
|  | 1258 | sprintf(cbuf, "%02d;BCON_R,BTRA\n", (int) a); | 
|  | 1259 | break; | 
|  | 1260 | default: | 
|  | 1261 | sprintf(cbuf, "%02d;BCON_R\n", (int) a); | 
|  | 1262 | } | 
|  | 1263 | printk(KERN_DEBUG "isdnloop writecmd '%s'\n", cbuf); | 
|  | 1264 | i = isdnloop_writecmd(cbuf, strlen(cbuf), 0, card); | 
|  | 1265 | break; | 
|  | 1266 | case ISDN_CMD_HANGUP: | 
| Alexey Dobriyan | eaa0ff1 | 2008-02-06 01:36:06 -0800 | [diff] [blame] | 1267 | if (!(card->flags & ISDNLOOP_FLAGS_RUNNING)) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1268 | return -ENODEV; | 
|  | 1269 | if (c->arg < ISDNLOOP_BCH) { | 
|  | 1270 | a = c->arg + 1; | 
|  | 1271 | sprintf(cbuf, "%02d;BDIS_R\n%02d;DDIS_R\n", (int) a, (int) a); | 
|  | 1272 | i = isdnloop_writecmd(cbuf, strlen(cbuf), 0, card); | 
|  | 1273 | } | 
|  | 1274 | break; | 
|  | 1275 | case ISDN_CMD_SETEAZ: | 
| Alexey Dobriyan | eaa0ff1 | 2008-02-06 01:36:06 -0800 | [diff] [blame] | 1276 | if (!(card->flags & ISDNLOOP_FLAGS_RUNNING)) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1277 | return -ENODEV; | 
|  | 1278 | if (card->leased) | 
|  | 1279 | break; | 
|  | 1280 | if (c->arg < ISDNLOOP_BCH) { | 
|  | 1281 | a = c->arg + 1; | 
|  | 1282 | if (card->ptype == ISDN_PTYPE_EURO) { | 
|  | 1283 | sprintf(cbuf, "%02d;MS%s%s\n", (int) a, | 
|  | 1284 | c->parm.num[0] ? "N" : "ALL", c->parm.num); | 
|  | 1285 | } else | 
|  | 1286 | sprintf(cbuf, "%02d;EAZ%s\n", (int) a, | 
|  | 1287 | c->parm.num[0] ? c->parm.num : (u_char *) "0123456789"); | 
|  | 1288 | i = isdnloop_writecmd(cbuf, strlen(cbuf), 0, card); | 
|  | 1289 | } | 
|  | 1290 | break; | 
|  | 1291 | case ISDN_CMD_CLREAZ: | 
| Julia Lawall | ae91d60 | 2008-03-04 14:29:18 -0800 | [diff] [blame] | 1292 | if (!(card->flags & ISDNLOOP_FLAGS_RUNNING)) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1293 | return -ENODEV; | 
|  | 1294 | if (card->leased) | 
|  | 1295 | break; | 
|  | 1296 | if (c->arg < ISDNLOOP_BCH) { | 
|  | 1297 | a = c->arg + 1; | 
|  | 1298 | if (card->ptype == ISDN_PTYPE_EURO) | 
|  | 1299 | sprintf(cbuf, "%02d;MSNC\n", (int) a); | 
|  | 1300 | else | 
|  | 1301 | sprintf(cbuf, "%02d;EAZC\n", (int) a); | 
|  | 1302 | i = isdnloop_writecmd(cbuf, strlen(cbuf), 0, card); | 
|  | 1303 | } | 
|  | 1304 | break; | 
|  | 1305 | case ISDN_CMD_SETL2: | 
| Alexey Dobriyan | eaa0ff1 | 2008-02-06 01:36:06 -0800 | [diff] [blame] | 1306 | if (!(card->flags & ISDNLOOP_FLAGS_RUNNING)) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1307 | return -ENODEV; | 
|  | 1308 | if ((c->arg & 255) < ISDNLOOP_BCH) { | 
|  | 1309 | a = c->arg; | 
|  | 1310 | switch (a >> 8) { | 
|  | 1311 | case ISDN_PROTO_L2_X75I: | 
|  | 1312 | sprintf(cbuf, "%02d;BX75\n", (int) (a & 255) + 1); | 
|  | 1313 | break; | 
|  | 1314 | #ifdef CONFIG_ISDN_X25 | 
|  | 1315 | case ISDN_PROTO_L2_X25DTE: | 
|  | 1316 | sprintf(cbuf, "%02d;BX2T\n", (int) (a & 255) + 1); | 
|  | 1317 | break; | 
|  | 1318 | case ISDN_PROTO_L2_X25DCE: | 
|  | 1319 | sprintf(cbuf, "%02d;BX2C\n", (int) (a & 255) + 1); | 
|  | 1320 | break; | 
|  | 1321 | #endif | 
|  | 1322 | case ISDN_PROTO_L2_HDLC: | 
|  | 1323 | sprintf(cbuf, "%02d;BTRA\n", (int) (a & 255) + 1); | 
|  | 1324 | break; | 
|  | 1325 | case ISDN_PROTO_L2_TRANS: | 
|  | 1326 | sprintf(cbuf, "%02d;BTRA\n", (int) (a & 255) + 1); | 
|  | 1327 | break; | 
|  | 1328 | default: | 
|  | 1329 | return -EINVAL; | 
|  | 1330 | } | 
|  | 1331 | i = isdnloop_writecmd(cbuf, strlen(cbuf), 0, card); | 
|  | 1332 | card->l2_proto[a & 255] = (a >> 8); | 
|  | 1333 | } | 
|  | 1334 | break; | 
|  | 1335 | case ISDN_CMD_SETL3: | 
| Julia Lawall | ae91d60 | 2008-03-04 14:29:18 -0800 | [diff] [blame] | 1336 | if (!(card->flags & ISDNLOOP_FLAGS_RUNNING)) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1337 | return -ENODEV; | 
|  | 1338 | return 0; | 
|  | 1339 | default: | 
|  | 1340 | return -EINVAL; | 
|  | 1341 | } | 
|  | 1342 | } | 
|  | 1343 | return 0; | 
|  | 1344 | } | 
|  | 1345 |  | 
|  | 1346 | /* | 
|  | 1347 | * Find card with given driverId | 
|  | 1348 | */ | 
|  | 1349 | static inline isdnloop_card * | 
|  | 1350 | isdnloop_findcard(int driverid) | 
|  | 1351 | { | 
|  | 1352 | isdnloop_card *p = cards; | 
|  | 1353 |  | 
|  | 1354 | while (p) { | 
|  | 1355 | if (p->myid == driverid) | 
|  | 1356 | return p; | 
|  | 1357 | p = p->next; | 
|  | 1358 | } | 
|  | 1359 | return (isdnloop_card *) 0; | 
|  | 1360 | } | 
|  | 1361 |  | 
|  | 1362 | /* | 
|  | 1363 | * Wrapper functions for interface to linklevel | 
|  | 1364 | */ | 
|  | 1365 | static int | 
|  | 1366 | if_command(isdn_ctrl * c) | 
|  | 1367 | { | 
|  | 1368 | isdnloop_card *card = isdnloop_findcard(c->driver); | 
|  | 1369 |  | 
|  | 1370 | if (card) | 
|  | 1371 | return (isdnloop_command(c, card)); | 
|  | 1372 | printk(KERN_ERR | 
|  | 1373 | "isdnloop: if_command called with invalid driverId!\n"); | 
|  | 1374 | return -ENODEV; | 
|  | 1375 | } | 
|  | 1376 |  | 
|  | 1377 | static int | 
|  | 1378 | if_writecmd(const u_char __user *buf, int len, int id, int channel) | 
|  | 1379 | { | 
|  | 1380 | isdnloop_card *card = isdnloop_findcard(id); | 
|  | 1381 |  | 
|  | 1382 | if (card) { | 
| Julia Lawall | ae91d60 | 2008-03-04 14:29:18 -0800 | [diff] [blame] | 1383 | if (!(card->flags & ISDNLOOP_FLAGS_RUNNING)) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1384 | return -ENODEV; | 
|  | 1385 | return (isdnloop_writecmd(buf, len, 1, card)); | 
|  | 1386 | } | 
|  | 1387 | printk(KERN_ERR | 
|  | 1388 | "isdnloop: if_writecmd called with invalid driverId!\n"); | 
|  | 1389 | return -ENODEV; | 
|  | 1390 | } | 
|  | 1391 |  | 
|  | 1392 | static int | 
|  | 1393 | if_readstatus(u_char __user *buf, int len, int id, int channel) | 
|  | 1394 | { | 
|  | 1395 | isdnloop_card *card = isdnloop_findcard(id); | 
|  | 1396 |  | 
|  | 1397 | if (card) { | 
| Alexey Dobriyan | eaa0ff1 | 2008-02-06 01:36:06 -0800 | [diff] [blame] | 1398 | if (!(card->flags & ISDNLOOP_FLAGS_RUNNING)) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1399 | return -ENODEV; | 
|  | 1400 | return (isdnloop_readstatus(buf, len, card)); | 
|  | 1401 | } | 
|  | 1402 | printk(KERN_ERR | 
|  | 1403 | "isdnloop: if_readstatus called with invalid driverId!\n"); | 
|  | 1404 | return -ENODEV; | 
|  | 1405 | } | 
|  | 1406 |  | 
|  | 1407 | static int | 
|  | 1408 | if_sendbuf(int id, int channel, int ack, struct sk_buff *skb) | 
|  | 1409 | { | 
|  | 1410 | isdnloop_card *card = isdnloop_findcard(id); | 
|  | 1411 |  | 
|  | 1412 | if (card) { | 
| Alexey Dobriyan | eaa0ff1 | 2008-02-06 01:36:06 -0800 | [diff] [blame] | 1413 | if (!(card->flags & ISDNLOOP_FLAGS_RUNNING)) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1414 | return -ENODEV; | 
|  | 1415 | /* ack request stored in skb scratch area */ | 
|  | 1416 | *(skb->head) = ack; | 
|  | 1417 | return (isdnloop_sendbuf(channel, skb, card)); | 
|  | 1418 | } | 
|  | 1419 | printk(KERN_ERR | 
|  | 1420 | "isdnloop: if_sendbuf called with invalid driverId!\n"); | 
|  | 1421 | return -ENODEV; | 
|  | 1422 | } | 
|  | 1423 |  | 
|  | 1424 | /* | 
|  | 1425 | * Allocate a new card-struct, initialize it | 
|  | 1426 | * link it into cards-list and register it at linklevel. | 
|  | 1427 | */ | 
|  | 1428 | static isdnloop_card * | 
|  | 1429 | isdnloop_initcard(char *id) | 
|  | 1430 | { | 
|  | 1431 | isdnloop_card *card; | 
|  | 1432 | int i; | 
|  | 1433 |  | 
| Burman Yan | 41f9693 | 2006-12-08 02:39:35 -0800 | [diff] [blame] | 1434 | if (!(card = kzalloc(sizeof(isdnloop_card), GFP_KERNEL))) { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1435 | printk(KERN_WARNING | 
|  | 1436 | "isdnloop: (%s) Could not allocate card-struct.\n", id); | 
|  | 1437 | return (isdnloop_card *) 0; | 
|  | 1438 | } | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1439 | card->interface.owner = THIS_MODULE; | 
|  | 1440 | card->interface.channels = ISDNLOOP_BCH; | 
|  | 1441 | card->interface.hl_hdrlen  = 1; /* scratch area for storing ack flag*/ | 
|  | 1442 | card->interface.maxbufsize = 4000; | 
|  | 1443 | card->interface.command = if_command; | 
|  | 1444 | card->interface.writebuf_skb = if_sendbuf; | 
|  | 1445 | card->interface.writecmd = if_writecmd; | 
|  | 1446 | card->interface.readstat = if_readstatus; | 
|  | 1447 | card->interface.features = ISDN_FEATURE_L2_X75I | | 
|  | 1448 | #ifdef CONFIG_ISDN_X25 | 
|  | 1449 | ISDN_FEATURE_L2_X25DTE | | 
|  | 1450 | ISDN_FEATURE_L2_X25DCE | | 
|  | 1451 | #endif | 
|  | 1452 | ISDN_FEATURE_L2_HDLC | | 
|  | 1453 | ISDN_FEATURE_L3_TRANS | | 
|  | 1454 | ISDN_FEATURE_P_UNKNOWN; | 
|  | 1455 | card->ptype = ISDN_PTYPE_UNKNOWN; | 
|  | 1456 | strlcpy(card->interface.id, id, sizeof(card->interface.id)); | 
|  | 1457 | card->msg_buf_write = card->msg_buf; | 
|  | 1458 | card->msg_buf_read = card->msg_buf; | 
|  | 1459 | card->msg_buf_end = &card->msg_buf[sizeof(card->msg_buf) - 1]; | 
|  | 1460 | for (i = 0; i < ISDNLOOP_BCH; i++) { | 
|  | 1461 | card->l2_proto[i] = ISDN_PROTO_L2_X75I; | 
|  | 1462 | skb_queue_head_init(&card->bqueue[i]); | 
|  | 1463 | } | 
|  | 1464 | skb_queue_head_init(&card->dqueue); | 
| Thomas Gleixner | 2bd7e20 | 2007-05-08 00:32:45 -0700 | [diff] [blame] | 1465 | spin_lock_init(&card->isdnloop_lock); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1466 | card->next = cards; | 
|  | 1467 | cards = card; | 
|  | 1468 | if (!register_isdn(&card->interface)) { | 
|  | 1469 | cards = cards->next; | 
|  | 1470 | printk(KERN_WARNING | 
|  | 1471 | "isdnloop: Unable to register %s\n", id); | 
|  | 1472 | kfree(card); | 
|  | 1473 | return (isdnloop_card *) 0; | 
|  | 1474 | } | 
|  | 1475 | card->myid = card->interface.channels; | 
|  | 1476 | return card; | 
|  | 1477 | } | 
|  | 1478 |  | 
|  | 1479 | static int | 
|  | 1480 | isdnloop_addcard(char *id1) | 
|  | 1481 | { | 
|  | 1482 | isdnloop_card *card; | 
|  | 1483 |  | 
|  | 1484 | if (!(card = isdnloop_initcard(id1))) { | 
|  | 1485 | return -EIO; | 
|  | 1486 | } | 
|  | 1487 | printk(KERN_INFO | 
|  | 1488 | "isdnloop: (%s) virtual card added\n", | 
|  | 1489 | card->interface.id); | 
|  | 1490 | return 0; | 
|  | 1491 | } | 
|  | 1492 |  | 
|  | 1493 | static int __init | 
|  | 1494 | isdnloop_init(void) | 
|  | 1495 | { | 
|  | 1496 | char *p; | 
|  | 1497 | char rev[10]; | 
|  | 1498 |  | 
|  | 1499 | if ((p = strchr(revision, ':'))) { | 
|  | 1500 | strcpy(rev, p + 1); | 
|  | 1501 | p = strchr(rev, '$'); | 
|  | 1502 | *p = 0; | 
|  | 1503 | } else | 
|  | 1504 | strcpy(rev, " ??? "); | 
|  | 1505 | printk(KERN_NOTICE "isdnloop-ISDN-driver Rev%s\n", rev); | 
|  | 1506 |  | 
|  | 1507 | if (isdnloop_id) | 
|  | 1508 | return (isdnloop_addcard(isdnloop_id)); | 
|  | 1509 |  | 
|  | 1510 | return 0; | 
|  | 1511 | } | 
|  | 1512 |  | 
|  | 1513 | static void __exit | 
|  | 1514 | isdnloop_exit(void) | 
|  | 1515 | { | 
|  | 1516 | isdn_ctrl cmd; | 
|  | 1517 | isdnloop_card *card = cards; | 
|  | 1518 | isdnloop_card *last; | 
|  | 1519 | int i; | 
|  | 1520 |  | 
|  | 1521 | isdnloop_stopallcards(); | 
|  | 1522 | while (card) { | 
|  | 1523 | cmd.command = ISDN_STAT_UNLOAD; | 
|  | 1524 | cmd.driver = card->myid; | 
|  | 1525 | card->interface.statcallb(&cmd); | 
|  | 1526 | for (i = 0; i < ISDNLOOP_BCH; i++) | 
|  | 1527 | isdnloop_free_queue(card, i); | 
|  | 1528 | card = card->next; | 
|  | 1529 | } | 
|  | 1530 | card = cards; | 
|  | 1531 | while (card) { | 
|  | 1532 | last = card; | 
|  | 1533 | skb_queue_purge(&card->dqueue); | 
|  | 1534 | card = card->next; | 
|  | 1535 | kfree(last); | 
|  | 1536 | } | 
|  | 1537 | printk(KERN_NOTICE "isdnloop-ISDN-driver unloaded\n"); | 
|  | 1538 | } | 
|  | 1539 |  | 
|  | 1540 | module_init(isdnloop_init); | 
|  | 1541 | module_exit(isdnloop_exit); |