| /* $Id: icn.c,v 1.65.6.8 2001/09/23 22:24:55 kai Exp $ |
| * |
| * ISDN low-level module for the ICN active ISDN-Card. |
| * |
| * Copyright 1994,95,96 by Fritz Elfert (fritz@isdn4linux.de) |
| * |
| * This software may be used and distributed according to the terms |
| * of the GNU General Public License, incorporated herein by reference. |
| * |
| */ |
| |
| #include "icn.h" |
| #include <linux/module.h> |
| #include <linux/init.h> |
| |
| static int portbase = ICN_BASEADDR; |
| static unsigned long membase = ICN_MEMADDR; |
| static char *icn_id = "\0"; |
| static char *icn_id2 = "\0"; |
| |
| MODULE_DESCRIPTION("ISDN4Linux: Driver for ICN active ISDN card"); |
| MODULE_AUTHOR("Fritz Elfert"); |
| MODULE_LICENSE("GPL"); |
| module_param(portbase, int, 0); |
| MODULE_PARM_DESC(portbase, "Port address of first card"); |
| module_param(membase, ulong, 0); |
| MODULE_PARM_DESC(membase, "Shared memory address of all cards"); |
| module_param(icn_id, charp, 0); |
| MODULE_PARM_DESC(icn_id, "ID-String of first card"); |
| module_param(icn_id2, charp, 0); |
| MODULE_PARM_DESC(icn_id2, "ID-String of first card, second S0 (4B only)"); |
| |
| /* |
| * Verbose bootcode- and protocol-downloading. |
| */ |
| #undef BOOT_DEBUG |
| |
| /* |
| * Verbose Shmem-Mapping. |
| */ |
| #undef MAP_DEBUG |
| |
| static char |
| *revision = "$Revision: 1.65.6.8 $"; |
| |
| static int icn_addcard(int, char *, char *); |
| |
| /* |
| * Free send-queue completely. |
| * Parameter: |
| * card = pointer to card struct |
| * channel = channel number |
| */ |
| static void |
| icn_free_queue(icn_card * card, int channel) |
| { |
| struct sk_buff_head *queue = &card->spqueue[channel]; |
| struct sk_buff *skb; |
| |
| skb_queue_purge(queue); |
| card->xlen[channel] = 0; |
| card->sndcount[channel] = 0; |
| if ((skb = card->xskb[channel])) { |
| card->xskb[channel] = NULL; |
| dev_kfree_skb(skb); |
| } |
| } |
| |
| /* Put a value into a shift-register, highest bit first. |
| * Parameters: |
| * port = port for output (bit 0 is significant) |
| * val = value to be output |
| * firstbit = Bit-Number of highest bit |
| * bitcount = Number of bits to output |
| */ |
| static inline void |
| icn_shiftout(unsigned short port, |
| unsigned long val, |
| int firstbit, |
| int bitcount) |
| { |
| |
| register u_char s; |
| register u_char c; |
| |
| for (s = firstbit, c = bitcount; c > 0; s--, c--) |
| OUTB_P((u_char) ((val >> s) & 1) ? 0xff : 0, port); |
| } |
| |
| /* |
| * disable a cards shared memory |
| */ |
| static inline void |
| icn_disable_ram(icn_card * card) |
| { |
| OUTB_P(0, ICN_MAPRAM); |
| } |
| |
| /* |
| * enable a cards shared memory |
| */ |
| static inline void |
| icn_enable_ram(icn_card * card) |
| { |
| OUTB_P(0xff, ICN_MAPRAM); |
| } |
| |
| /* |
| * Map a cards channel0 (Bank0/Bank8) or channel1 (Bank4/Bank12) |
| * |
| * must called with holding the devlock |
| */ |
| static inline void |
| icn_map_channel(icn_card * card, int channel) |
| { |
| #ifdef MAP_DEBUG |
| printk(KERN_DEBUG "icn_map_channel %d %d\n", dev.channel, channel); |
| #endif |
| if ((channel == dev.channel) && (card == dev.mcard)) |
| return; |
| if (dev.mcard) |
| icn_disable_ram(dev.mcard); |
| icn_shiftout(ICN_BANK, chan2bank[channel], 3, 4); /* Select Bank */ |
| icn_enable_ram(card); |
| dev.mcard = card; |
| dev.channel = channel; |
| #ifdef MAP_DEBUG |
| printk(KERN_DEBUG "icn_map_channel done\n"); |
| #endif |
| } |
| |
| /* |
| * Lock a cards channel. |
| * Return 0 if requested card/channel is unmapped (failure). |
| * Return 1 on success. |
| * |
| * must called with holding the devlock |
| */ |
| static inline int |
| icn_lock_channel(icn_card * card, int channel) |
| { |
| register int retval; |
| |
| #ifdef MAP_DEBUG |
| printk(KERN_DEBUG "icn_lock_channel %d\n", channel); |
| #endif |
| if ((dev.channel == channel) && (card == dev.mcard)) { |
| dev.chanlock++; |
| retval = 1; |
| #ifdef MAP_DEBUG |
| printk(KERN_DEBUG "icn_lock_channel %d OK\n", channel); |
| #endif |
| } else { |
| retval = 0; |
| #ifdef MAP_DEBUG |
| printk(KERN_DEBUG "icn_lock_channel %d FAILED, dc=%d\n", channel, dev.channel); |
| #endif |
| } |
| return retval; |
| } |
| |
| /* |
| * Release current card/channel lock |
| * |
| * must called with holding the devlock |
| */ |
| static inline void |
| __icn_release_channel(void) |
| { |
| #ifdef MAP_DEBUG |
| printk(KERN_DEBUG "icn_release_channel l=%d\n", dev.chanlock); |
| #endif |
| if (dev.chanlock > 0) |
| dev.chanlock--; |
| } |
| |
| /* |
| * Release current card/channel lock |
| */ |
| static inline void |
| icn_release_channel(void) |
| { |
| ulong flags; |
| |
| spin_lock_irqsave(&dev.devlock, flags); |
| __icn_release_channel(); |
| spin_unlock_irqrestore(&dev.devlock, flags); |
| } |
| |
| /* |
| * Try to map and lock a cards channel. |
| * Return 1 on success, 0 on failure. |
| */ |
| static inline int |
| icn_trymaplock_channel(icn_card * card, int channel) |
| { |
| ulong flags; |
| |
| #ifdef MAP_DEBUG |
| printk(KERN_DEBUG "trymaplock c=%d dc=%d l=%d\n", channel, dev.channel, |
| dev.chanlock); |
| #endif |
| spin_lock_irqsave(&dev.devlock, flags); |
| if ((!dev.chanlock) || |
| ((dev.channel == channel) && (dev.mcard == card))) { |
| dev.chanlock++; |
| icn_map_channel(card, channel); |
| spin_unlock_irqrestore(&dev.devlock, flags); |
| #ifdef MAP_DEBUG |
| printk(KERN_DEBUG "trymaplock %d OK\n", channel); |
| #endif |
| return 1; |
| } |
| spin_unlock_irqrestore(&dev.devlock, flags); |
| #ifdef MAP_DEBUG |
| printk(KERN_DEBUG "trymaplock %d FAILED\n", channel); |
| #endif |
| return 0; |
| } |
| |
| /* |
| * Release current card/channel lock, |
| * then map same or other channel without locking. |
| */ |
| static inline void |
| icn_maprelease_channel(icn_card * card, int channel) |
| { |
| ulong flags; |
| |
| #ifdef MAP_DEBUG |
| printk(KERN_DEBUG "map_release c=%d l=%d\n", channel, dev.chanlock); |
| #endif |
| spin_lock_irqsave(&dev.devlock, flags); |
| if (dev.chanlock > 0) |
| dev.chanlock--; |
| if (!dev.chanlock) |
| icn_map_channel(card, channel); |
| spin_unlock_irqrestore(&dev.devlock, flags); |
| } |
| |
| /* Get Data from the B-Channel, assemble fragmented packets and put them |
| * into receive-queue. Wake up any B-Channel-reading processes. |
| * This routine is called via timer-callback from icn_pollbchan(). |
| */ |
| |
| static void |
| icn_pollbchan_receive(int channel, icn_card * card) |
| { |
| int mch = channel + ((card->secondhalf) ? 2 : 0); |
| int eflag; |
| int cnt; |
| struct sk_buff *skb; |
| |
| if (icn_trymaplock_channel(card, mch)) { |
| while (rbavl) { |
| cnt = readb(&rbuf_l); |
| if ((card->rcvidx[channel] + cnt) > 4000) { |
| printk(KERN_WARNING |
| "icn: (%s) bogus packet on ch%d, dropping.\n", |
| CID, |
| channel + 1); |
| card->rcvidx[channel] = 0; |
| eflag = 0; |
| } else { |
| memcpy_fromio(&card->rcvbuf[channel][card->rcvidx[channel]], |
| &rbuf_d, cnt); |
| card->rcvidx[channel] += cnt; |
| eflag = readb(&rbuf_f); |
| } |
| rbnext; |
| icn_maprelease_channel(card, mch & 2); |
| if (!eflag) { |
| if ((cnt = card->rcvidx[channel])) { |
| if (!(skb = dev_alloc_skb(cnt))) { |
| printk(KERN_WARNING "icn: receive out of memory\n"); |
| break; |
| } |
| memcpy(skb_put(skb, cnt), card->rcvbuf[channel], cnt); |
| card->rcvidx[channel] = 0; |
| card->interface.rcvcallb_skb(card->myid, channel, skb); |
| } |
| } |
| if (!icn_trymaplock_channel(card, mch)) |
| break; |
| } |
| icn_maprelease_channel(card, mch & 2); |
| } |
| } |
| |
| /* Send data-packet to B-Channel, split it up into fragments of |
| * ICN_FRAGSIZE length. If last fragment is sent out, signal |
| * success to upper layers via statcallb with ISDN_STAT_BSENT argument. |
| * This routine is called via timer-callback from icn_pollbchan() or |
| * directly from icn_sendbuf(). |
| */ |
| |
| static void |
| icn_pollbchan_send(int channel, icn_card * card) |
| { |
| int mch = channel + ((card->secondhalf) ? 2 : 0); |
| int cnt; |
| unsigned long flags; |
| struct sk_buff *skb; |
| isdn_ctrl cmd; |
| |
| if (!(card->sndcount[channel] || card->xskb[channel] || |
| !skb_queue_empty(&card->spqueue[channel]))) |
| return; |
| if (icn_trymaplock_channel(card, mch)) { |
| while (sbfree && |
| (card->sndcount[channel] || |
| !skb_queue_empty(&card->spqueue[channel]) || |
| card->xskb[channel])) { |
| spin_lock_irqsave(&card->lock, flags); |
| if (card->xmit_lock[channel]) { |
| spin_unlock_irqrestore(&card->lock, flags); |
| break; |
| } |
| card->xmit_lock[channel]++; |
| spin_unlock_irqrestore(&card->lock, flags); |
| skb = card->xskb[channel]; |
| if (!skb) { |
| skb = skb_dequeue(&card->spqueue[channel]); |
| if (skb) { |
| /* Pop ACK-flag off skb. |
| * Store length to xlen. |
| */ |
| if (*(skb_pull(skb,1))) |
| card->xlen[channel] = skb->len; |
| else |
| card->xlen[channel] = 0; |
| } |
| } |
| if (!skb) |
| break; |
| if (skb->len > ICN_FRAGSIZE) { |
| writeb(0xff, &sbuf_f); |
| cnt = ICN_FRAGSIZE; |
| } else { |
| writeb(0x0, &sbuf_f); |
| cnt = skb->len; |
| } |
| writeb(cnt, &sbuf_l); |
| memcpy_toio(&sbuf_d, skb->data, cnt); |
| skb_pull(skb, cnt); |
| sbnext; /* switch to next buffer */ |
| icn_maprelease_channel(card, mch & 2); |
| spin_lock_irqsave(&card->lock, flags); |
| card->sndcount[channel] -= cnt; |
| if (!skb->len) { |
| if (card->xskb[channel]) |
| card->xskb[channel] = NULL; |
| card->xmit_lock[channel] = 0; |
| spin_unlock_irqrestore(&card->lock, flags); |
| dev_kfree_skb(skb); |
| if (card->xlen[channel]) { |
| cmd.command = ISDN_STAT_BSENT; |
| cmd.driver = card->myid; |
| cmd.arg = channel; |
| cmd.parm.length = card->xlen[channel]; |
| card->interface.statcallb(&cmd); |
| } |
| } else { |
| card->xskb[channel] = skb; |
| card->xmit_lock[channel] = 0; |
| spin_unlock_irqrestore(&card->lock, flags); |
| } |
| if (!icn_trymaplock_channel(card, mch)) |
| break; |
| } |
| icn_maprelease_channel(card, mch & 2); |
| } |
| } |
| |
| /* Send/Receive Data to/from the B-Channel. |
| * This routine is called via timer-callback. |
| * It schedules itself while any B-Channel is open. |
| */ |
| |
| static void |
| icn_pollbchan(unsigned long data) |
| { |
| icn_card *card = (icn_card *) data; |
| unsigned long flags; |
| |
| if (card->flags & ICN_FLAGS_B1ACTIVE) { |
| icn_pollbchan_receive(0, card); |
| icn_pollbchan_send(0, card); |
| } |
| if (card->flags & ICN_FLAGS_B2ACTIVE) { |
| icn_pollbchan_receive(1, card); |
| icn_pollbchan_send(1, card); |
| } |
| if (card->flags & (ICN_FLAGS_B1ACTIVE | ICN_FLAGS_B2ACTIVE)) { |
| /* schedule b-channel polling again */ |
| spin_lock_irqsave(&card->lock, flags); |
| mod_timer(&card->rb_timer, jiffies+ICN_TIMER_BCREAD); |
| card->flags |= ICN_FLAGS_RBTIMER; |
| spin_unlock_irqrestore(&card->lock, flags); |
| } else |
| card->flags &= ~ICN_FLAGS_RBTIMER; |
| } |
| |
| typedef struct icn_stat { |
| char *statstr; |
| int command; |
| int action; |
| } icn_stat; |
| /* *INDENT-OFF* */ |
| static icn_stat icn_stat_table[] = |
| { |
| {"BCON_", ISDN_STAT_BCONN, 1}, /* B-Channel connected */ |
| {"BDIS_", ISDN_STAT_BHUP, 2}, /* B-Channel disconnected */ |
| /* |
| ** add d-channel connect and disconnect support to link-level |
| */ |
| {"DCON_", ISDN_STAT_DCONN, 10}, /* D-Channel connected */ |
| {"DDIS_", ISDN_STAT_DHUP, 11}, /* D-Channel disconnected */ |
| {"DCAL_I", ISDN_STAT_ICALL, 3}, /* Incoming call dialup-line */ |
| {"DSCA_I", ISDN_STAT_ICALL, 3}, /* Incoming call 1TR6-SPV */ |
| {"FCALL", ISDN_STAT_ICALL, 4}, /* Leased line connection up */ |
| {"CIF", ISDN_STAT_CINF, 5}, /* Charge-info, 1TR6-type */ |
| {"AOC", ISDN_STAT_CINF, 6}, /* Charge-info, DSS1-type */ |
| {"CAU", ISDN_STAT_CAUSE, 7}, /* Cause code */ |
| {"TEI OK", ISDN_STAT_RUN, 0}, /* Card connected to wallplug */ |
| {"E_L1: ACT FAIL", ISDN_STAT_BHUP, 8}, /* Layer-1 activation failed */ |
| {"E_L2: DATA LIN", ISDN_STAT_BHUP, 8}, /* Layer-2 data link lost */ |
| {"E_L1: ACTIVATION FAILED", |
| ISDN_STAT_BHUP, 8}, /* Layer-1 activation failed */ |
| {NULL, 0, -1} |
| }; |
| /* *INDENT-ON* */ |
| |
| |
| /* |
| * Check Statusqueue-Pointer from isdn-cards. |
| * If there are new status-replies from the interface, check |
| * them against B-Channel-connects/disconnects and set flags accordingly. |
| * Wake-Up any processes, who are reading the status-device. |
| * If there are B-Channels open, initiate a timer-callback to |
| * icn_pollbchan(). |
| * This routine is called periodically via timer. |
| */ |
| |
| static void |
| icn_parse_status(u_char * status, int channel, icn_card * card) |
| { |
| icn_stat *s = icn_stat_table; |
| int action = -1; |
| unsigned long flags; |
| isdn_ctrl cmd; |
| |
| while (s->statstr) { |
| if (!strncmp(status, s->statstr, strlen(s->statstr))) { |
| cmd.command = s->command; |
| action = s->action; |
| break; |
| } |
| s++; |
| } |
| if (action == -1) |
| return; |
| cmd.driver = card->myid; |
| cmd.arg = channel; |
| switch (action) { |
| case 11: |
| spin_lock_irqsave(&card->lock, flags); |
| icn_free_queue(card,channel); |
| card->rcvidx[channel] = 0; |
| |
| if (card->flags & |
| ((channel)?ICN_FLAGS_B2ACTIVE:ICN_FLAGS_B1ACTIVE)) { |
| |
| isdn_ctrl ncmd; |
| |
| card->flags &= ~((channel)? |
| ICN_FLAGS_B2ACTIVE:ICN_FLAGS_B1ACTIVE); |
| |
| memset(&ncmd, 0, sizeof(ncmd)); |
| |
| ncmd.driver = card->myid; |
| ncmd.arg = channel; |
| ncmd.command = ISDN_STAT_BHUP; |
| spin_unlock_irqrestore(&card->lock, flags); |
| card->interface.statcallb(&cmd); |
| } else |
| spin_unlock_irqrestore(&card->lock, flags); |
| break; |
| case 1: |
| spin_lock_irqsave(&card->lock, flags); |
| icn_free_queue(card,channel); |
| card->flags |= (channel) ? |
| ICN_FLAGS_B2ACTIVE : ICN_FLAGS_B1ACTIVE; |
| spin_unlock_irqrestore(&card->lock, flags); |
| break; |
| case 2: |
| spin_lock_irqsave(&card->lock, flags); |
| card->flags &= ~((channel) ? |
| ICN_FLAGS_B2ACTIVE : ICN_FLAGS_B1ACTIVE); |
| icn_free_queue(card, channel); |
| card->rcvidx[channel] = 0; |
| spin_unlock_irqrestore(&card->lock, flags); |
| break; |
| case 3: |
| { |
| char *t = status + 6; |
| char *s = strchr(t, ','); |
| |
| *s++ = '\0'; |
| strlcpy(cmd.parm.setup.phone, t, |
| sizeof(cmd.parm.setup.phone)); |
| s = strchr(t = s, ','); |
| *s++ = '\0'; |
| if (!strlen(t)) |
| cmd.parm.setup.si1 = 0; |
| else |
| cmd.parm.setup.si1 = |
| simple_strtoul(t, NULL, 10); |
| s = strchr(t = s, ','); |
| *s++ = '\0'; |
| if (!strlen(t)) |
| cmd.parm.setup.si2 = 0; |
| else |
| cmd.parm.setup.si2 = |
| simple_strtoul(t, NULL, 10); |
| strlcpy(cmd.parm.setup.eazmsn, s, |
| sizeof(cmd.parm.setup.eazmsn)); |
| } |
| cmd.parm.setup.plan = 0; |
| cmd.parm.setup.screen = 0; |
| break; |
| case 4: |
| sprintf(cmd.parm.setup.phone, "LEASED%d", card->myid); |
| sprintf(cmd.parm.setup.eazmsn, "%d", channel + 1); |
| cmd.parm.setup.si1 = 7; |
| cmd.parm.setup.si2 = 0; |
| cmd.parm.setup.plan = 0; |
| cmd.parm.setup.screen = 0; |
| break; |
| case 5: |
| strlcpy(cmd.parm.num, status + 3, sizeof(cmd.parm.num)); |
| break; |
| case 6: |
| snprintf(cmd.parm.num, sizeof(cmd.parm.num), "%d", |
| (int) simple_strtoul(status + 7, NULL, 16)); |
| break; |
| case 7: |
| status += 3; |
| if (strlen(status) == 4) |
| snprintf(cmd.parm.num, sizeof(cmd.parm.num), "%s%c%c", |
| status + 2, *status, *(status + 1)); |
| else |
| strlcpy(cmd.parm.num, status + 1, sizeof(cmd.parm.num)); |
| break; |
| case 8: |
| spin_lock_irqsave(&card->lock, flags); |
| card->flags &= ~ICN_FLAGS_B1ACTIVE; |
| icn_free_queue(card, 0); |
| card->rcvidx[0] = 0; |
| spin_unlock_irqrestore(&card->lock, flags); |
| cmd.arg = 0; |
| cmd.driver = card->myid; |
| card->interface.statcallb(&cmd); |
| cmd.command = ISDN_STAT_DHUP; |
| cmd.arg = 0; |
| cmd.driver = card->myid; |
| card->interface.statcallb(&cmd); |
| cmd.command = ISDN_STAT_BHUP; |
| spin_lock_irqsave(&card->lock, flags); |
| card->flags &= ~ICN_FLAGS_B2ACTIVE; |
| icn_free_queue(card, 1); |
| card->rcvidx[1] = 0; |
| spin_unlock_irqrestore(&card->lock, flags); |
| cmd.arg = 1; |
| cmd.driver = card->myid; |
| card->interface.statcallb(&cmd); |
| cmd.command = ISDN_STAT_DHUP; |
| cmd.arg = 1; |
| cmd.driver = card->myid; |
| break; |
| } |
| card->interface.statcallb(&cmd); |
| return; |
| } |
| |
| static void |
| icn_putmsg(icn_card * card, unsigned char c) |
| { |
| ulong flags; |
| |
| spin_lock_irqsave(&card->lock, flags); |
| *card->msg_buf_write++ = (c == 0xff) ? '\n' : c; |
| if (card->msg_buf_write == card->msg_buf_read) { |
| if (++card->msg_buf_read > card->msg_buf_end) |
| card->msg_buf_read = card->msg_buf; |
| } |
| if (card->msg_buf_write > card->msg_buf_end) |
| card->msg_buf_write = card->msg_buf; |
| spin_unlock_irqrestore(&card->lock, flags); |
| } |
| |
| static void |
| icn_polldchan(unsigned long data) |
| { |
| icn_card *card = (icn_card *) data; |
| int mch = card->secondhalf ? 2 : 0; |
| int avail = 0; |
| int left; |
| u_char c; |
| int ch; |
| unsigned long flags; |
| int i; |
| u_char *p; |
| isdn_ctrl cmd; |
| |
| if (icn_trymaplock_channel(card, mch)) { |
| avail = msg_avail; |
| for (left = avail, i = readb(&msg_o); left > 0; i++, left--) { |
| c = readb(&dev.shmem->comm_buffers.iopc_buf[i & 0xff]); |
| icn_putmsg(card, c); |
| if (c == 0xff) { |
| card->imsg[card->iptr] = 0; |
| card->iptr = 0; |
| if (card->imsg[0] == '0' && card->imsg[1] >= '0' && |
| card->imsg[1] <= '2' && card->imsg[2] == ';') { |
| ch = (card->imsg[1] - '0') - 1; |
| p = &card->imsg[3]; |
| icn_parse_status(p, ch, card); |
| } else { |
| p = card->imsg; |
| if (!strncmp(p, "DRV1.", 5)) { |
| u_char vstr[10]; |
| u_char *q = vstr; |
| |
| printk(KERN_INFO "icn: (%s) %s\n", CID, p); |
| if (!strncmp(p + 7, "TC", 2)) { |
| card->ptype = ISDN_PTYPE_1TR6; |
| card->interface.features |= ISDN_FEATURE_P_1TR6; |
| printk(KERN_INFO |
| "icn: (%s) 1TR6-Protocol loaded and running\n", CID); |
| } |
| if (!strncmp(p + 7, "EC", 2)) { |
| card->ptype = ISDN_PTYPE_EURO; |
| card->interface.features |= ISDN_FEATURE_P_EURO; |
| printk(KERN_INFO |
| "icn: (%s) Euro-Protocol loaded and running\n", CID); |
| } |
| p = strstr(card->imsg, "BRV") + 3; |
| while (*p) { |
| if (*p >= '0' && *p <= '9') |
| *q++ = *p; |
| p++; |
| } |
| *q = '\0'; |
| strcat(vstr, "000"); |
| vstr[3] = '\0'; |
| card->fw_rev = (int) simple_strtoul(vstr, NULL, 10); |
| continue; |
| |
| } |
| } |
| } else { |
| card->imsg[card->iptr] = c; |
| if (card->iptr < 59) |
| card->iptr++; |
| } |
| } |
| writeb((readb(&msg_o) + avail) & 0xff, &msg_o); |
| icn_release_channel(); |
| } |
| if (avail) { |
| cmd.command = ISDN_STAT_STAVAIL; |
| cmd.driver = card->myid; |
| cmd.arg = avail; |
| card->interface.statcallb(&cmd); |
| } |
| spin_lock_irqsave(&card->lock, flags); |
| if (card->flags & (ICN_FLAGS_B1ACTIVE | ICN_FLAGS_B2ACTIVE)) |
| if (!(card->flags & ICN_FLAGS_RBTIMER)) { |
| /* schedule b-channel polling */ |
| card->flags |= ICN_FLAGS_RBTIMER; |
| del_timer(&card->rb_timer); |
| card->rb_timer.function = icn_pollbchan; |
| card->rb_timer.data = (unsigned long) card; |
| card->rb_timer.expires = jiffies + ICN_TIMER_BCREAD; |
| add_timer(&card->rb_timer); |
| } |
| /* schedule again */ |
| mod_timer(&card->st_timer, jiffies+ICN_TIMER_DCREAD); |
| spin_unlock_irqrestore(&card->lock, flags); |
| } |
| |
| /* Append a packet to the transmit buffer-queue. |
| * Parameters: |
| * channel = Number of B-channel |
| * skb = pointer to sk_buff |
| * card = pointer to card-struct |
| * Return: |
| * Number of bytes transferred, -E??? on error |
| */ |
| |
| static int |
| icn_sendbuf(int channel, int ack, struct sk_buff *skb, icn_card * card) |
| { |
| int len = skb->len; |
| unsigned long flags; |
| struct sk_buff *nskb; |
| |
| if (len > 4000) { |
| printk(KERN_WARNING |
| "icn: Send packet too large\n"); |
| return -EINVAL; |
| } |
| if (len) { |
| if (!(card->flags & (channel) ? ICN_FLAGS_B2ACTIVE : ICN_FLAGS_B1ACTIVE)) |
| return 0; |
| if (card->sndcount[channel] > ICN_MAX_SQUEUE) |
| return 0; |
| #warning TODO test headroom or use skb->nb to flag ACK |
| nskb = skb_clone(skb, GFP_ATOMIC); |
| if (nskb) { |
| /* Push ACK flag as one |
| * byte in front of data. |
| */ |
| *(skb_push(nskb, 1)) = ack?1:0; |
| skb_queue_tail(&card->spqueue[channel], nskb); |
| dev_kfree_skb(skb); |
| } else |
| len = 0; |
| spin_lock_irqsave(&card->lock, flags); |
| card->sndcount[channel] += len; |
| spin_unlock_irqrestore(&card->lock, flags); |
| } |
| return len; |
| } |
| |
| /* |
| * Check card's status after starting the bootstrap loader. |
| * On entry, the card's shared memory has already to be mapped. |
| * Return: |
| * 0 on success (Boot loader ready) |
| * -EIO on failure (timeout) |
| */ |
| static int |
| icn_check_loader(int cardnumber) |
| { |
| int timer = 0; |
| |
| while (1) { |
| #ifdef BOOT_DEBUG |
| printk(KERN_DEBUG "Loader %d ?\n", cardnumber); |
| #endif |
| if (readb(&dev.shmem->data_control.scns) || |
| readb(&dev.shmem->data_control.scnr)) { |
| if (timer++ > 5) { |
| printk(KERN_WARNING |
| "icn: Boot-Loader %d timed out.\n", |
| cardnumber); |
| icn_release_channel(); |
| return -EIO; |
| } |
| #ifdef BOOT_DEBUG |
| printk(KERN_DEBUG "Loader %d TO?\n", cardnumber); |
| #endif |
| msleep_interruptible(ICN_BOOT_TIMEOUT1); |
| } else { |
| #ifdef BOOT_DEBUG |
| printk(KERN_DEBUG "Loader %d OK\n", cardnumber); |
| #endif |
| icn_release_channel(); |
| return 0; |
| } |
| } |
| } |
| |
| /* Load the boot-code into the interface-card's memory and start it. |
| * Always called from user-process. |
| * |
| * Parameters: |
| * buffer = pointer to packet |
| * Return: |
| * 0 if successfully loaded |
| */ |
| |
| #ifdef BOOT_DEBUG |
| #define SLEEP(sec) { \ |
| int slsec = sec; \ |
| printk(KERN_DEBUG "SLEEP(%d)\n",slsec); \ |
| while (slsec) { \ |
| msleep_interruptible(1000); \ |
| slsec--; \ |
| } \ |
| } |
| #else |
| #define SLEEP(sec) |
| #endif |
| |
| static int |
| icn_loadboot(u_char __user * buffer, icn_card * card) |
| { |
| int ret; |
| u_char *codebuf; |
| unsigned long flags; |
| |
| #ifdef BOOT_DEBUG |
| printk(KERN_DEBUG "icn_loadboot called, buffaddr=%08lx\n", (ulong) buffer); |
| #endif |
| if (!(codebuf = kmalloc(ICN_CODE_STAGE1, GFP_KERNEL))) { |
| printk(KERN_WARNING "icn: Could not allocate code buffer\n"); |
| ret = -ENOMEM; |
| goto out; |
| } |
| if (copy_from_user(codebuf, buffer, ICN_CODE_STAGE1)) { |
| ret = -EFAULT; |
| goto out_kfree; |
| } |
| if (!card->rvalid) { |
| if (!request_region(card->port, ICN_PORTLEN, card->regname)) { |
| printk(KERN_WARNING |
| "icn: (%s) ports 0x%03x-0x%03x in use.\n", |
| CID, |
| card->port, |
| card->port + ICN_PORTLEN); |
| ret = -EBUSY; |
| goto out_kfree; |
| } |
| card->rvalid = 1; |
| if (card->doubleS0) |
| card->other->rvalid = 1; |
| } |
| if (!dev.mvalid) { |
| if (!request_mem_region(dev.memaddr, 0x4000, "icn-isdn (all cards)")) { |
| printk(KERN_WARNING |
| "icn: memory at 0x%08lx in use.\n", dev.memaddr); |
| ret = -EBUSY; |
| goto out_kfree; |
| } |
| dev.shmem = ioremap(dev.memaddr, 0x4000); |
| dev.mvalid = 1; |
| } |
| OUTB_P(0, ICN_RUN); /* Reset Controller */ |
| OUTB_P(0, ICN_MAPRAM); /* Disable RAM */ |
| icn_shiftout(ICN_CFG, 0x0f, 3, 4); /* Windowsize= 16k */ |
| icn_shiftout(ICN_CFG, dev.memaddr, 23, 10); /* Set RAM-Addr. */ |
| #ifdef BOOT_DEBUG |
| printk(KERN_DEBUG "shmem=%08lx\n", dev.memaddr); |
| #endif |
| SLEEP(1); |
| #ifdef BOOT_DEBUG |
| printk(KERN_DEBUG "Map Bank 0\n"); |
| #endif |
| spin_lock_irqsave(&dev.devlock, flags); |
| icn_map_channel(card, 0); /* Select Bank 0 */ |
| icn_lock_channel(card, 0); /* Lock Bank 0 */ |
| spin_unlock_irqrestore(&dev.devlock, flags); |
| SLEEP(1); |
| memcpy_toio(dev.shmem, codebuf, ICN_CODE_STAGE1); /* Copy code */ |
| #ifdef BOOT_DEBUG |
| printk(KERN_DEBUG "Bootloader transferred\n"); |
| #endif |
| if (card->doubleS0) { |
| SLEEP(1); |
| #ifdef BOOT_DEBUG |
| printk(KERN_DEBUG "Map Bank 8\n"); |
| #endif |
| spin_lock_irqsave(&dev.devlock, flags); |
| __icn_release_channel(); |
| icn_map_channel(card, 2); /* Select Bank 8 */ |
| icn_lock_channel(card, 2); /* Lock Bank 8 */ |
| spin_unlock_irqrestore(&dev.devlock, flags); |
| SLEEP(1); |
| memcpy_toio(dev.shmem, codebuf, ICN_CODE_STAGE1); /* Copy code */ |
| #ifdef BOOT_DEBUG |
| printk(KERN_DEBUG "Bootloader transferred\n"); |
| #endif |
| } |
| SLEEP(1); |
| OUTB_P(0xff, ICN_RUN); /* Start Boot-Code */ |
| if ((ret = icn_check_loader(card->doubleS0 ? 2 : 1))) { |
| goto out_kfree; |
| } |
| if (!card->doubleS0) { |
| ret = 0; |
| goto out_kfree; |
| } |
| /* reached only, if we have a Double-S0-Card */ |
| #ifdef BOOT_DEBUG |
| printk(KERN_DEBUG "Map Bank 0\n"); |
| #endif |
| spin_lock_irqsave(&dev.devlock, flags); |
| icn_map_channel(card, 0); /* Select Bank 0 */ |
| icn_lock_channel(card, 0); /* Lock Bank 0 */ |
| spin_unlock_irqrestore(&dev.devlock, flags); |
| SLEEP(1); |
| ret = (icn_check_loader(1)); |
| |
| out_kfree: |
| kfree(codebuf); |
| out: |
| return ret; |
| } |
| |
| static int |
| icn_loadproto(u_char __user * buffer, icn_card * card) |
| { |
| register u_char __user *p = buffer; |
| u_char codebuf[256]; |
| uint left = ICN_CODE_STAGE2; |
| uint cnt; |
| int timer; |
| unsigned long flags; |
| |
| #ifdef BOOT_DEBUG |
| printk(KERN_DEBUG "icn_loadproto called\n"); |
| #endif |
| if (!access_ok(VERIFY_READ, buffer, ICN_CODE_STAGE2)) |
| return -EFAULT; |
| timer = 0; |
| spin_lock_irqsave(&dev.devlock, flags); |
| if (card->secondhalf) { |
| icn_map_channel(card, 2); |
| icn_lock_channel(card, 2); |
| } else { |
| icn_map_channel(card, 0); |
| icn_lock_channel(card, 0); |
| } |
| spin_unlock_irqrestore(&dev.devlock, flags); |
| while (left) { |
| if (sbfree) { /* If there is a free buffer... */ |
| cnt = left; |
| if (cnt > 256) |
| cnt = 256; |
| if (copy_from_user(codebuf, p, cnt)) { |
| icn_maprelease_channel(card, 0); |
| return -EFAULT; |
| } |
| memcpy_toio(&sbuf_l, codebuf, cnt); /* copy data */ |
| sbnext; /* switch to next buffer */ |
| p += cnt; |
| left -= cnt; |
| timer = 0; |
| } else { |
| #ifdef BOOT_DEBUG |
| printk(KERN_DEBUG "boot 2 !sbfree\n"); |
| #endif |
| if (timer++ > 5) { |
| icn_maprelease_channel(card, 0); |
| return -EIO; |
| } |
| schedule_timeout_interruptible(10); |
| } |
| } |
| writeb(0x20, &sbuf_n); |
| timer = 0; |
| while (1) { |
| if (readb(&cmd_o) || readb(&cmd_i)) { |
| #ifdef BOOT_DEBUG |
| printk(KERN_DEBUG "Proto?\n"); |
| #endif |
| if (timer++ > 5) { |
| printk(KERN_WARNING |
| "icn: (%s) Protocol timed out.\n", |
| CID); |
| #ifdef BOOT_DEBUG |
| printk(KERN_DEBUG "Proto TO!\n"); |
| #endif |
| icn_maprelease_channel(card, 0); |
| return -EIO; |
| } |
| #ifdef BOOT_DEBUG |
| printk(KERN_DEBUG "Proto TO?\n"); |
| #endif |
| msleep_interruptible(ICN_BOOT_TIMEOUT1); |
| } else { |
| if ((card->secondhalf) || (!card->doubleS0)) { |
| #ifdef BOOT_DEBUG |
| printk(KERN_DEBUG "Proto loaded, install poll-timer %d\n", |
| card->secondhalf); |
| #endif |
| spin_lock_irqsave(&card->lock, flags); |
| init_timer(&card->st_timer); |
| card->st_timer.expires = jiffies + ICN_TIMER_DCREAD; |
| card->st_timer.function = icn_polldchan; |
| card->st_timer.data = (unsigned long) card; |
| add_timer(&card->st_timer); |
| card->flags |= ICN_FLAGS_RUNNING; |
| if (card->doubleS0) { |
| init_timer(&card->other->st_timer); |
| card->other->st_timer.expires = jiffies + ICN_TIMER_DCREAD; |
| card->other->st_timer.function = icn_polldchan; |
| card->other->st_timer.data = (unsigned long) card->other; |
| add_timer(&card->other->st_timer); |
| card->other->flags |= ICN_FLAGS_RUNNING; |
| } |
| spin_unlock_irqrestore(&card->lock, flags); |
| } |
| icn_maprelease_channel(card, 0); |
| return 0; |
| } |
| } |
| } |
| |
| /* Read the Status-replies from the Interface */ |
| static int |
| icn_readstatus(u_char __user *buf, int len, icn_card * card) |
| { |
| int count; |
| u_char __user *p; |
| |
| for (p = buf, count = 0; count < len; p++, count++) { |
| if (card->msg_buf_read == card->msg_buf_write) |
| return count; |
| if (put_user(*card->msg_buf_read++, p)) |
| return -EFAULT; |
| if (card->msg_buf_read > card->msg_buf_end) |
| card->msg_buf_read = card->msg_buf; |
| } |
| return count; |
| } |
| |
| /* Put command-strings into the command-queue of the Interface */ |
| static int |
| icn_writecmd(const u_char * buf, int len, int user, icn_card * card) |
| { |
| int mch = card->secondhalf ? 2 : 0; |
| int pp; |
| int i; |
| int count; |
| int xcount; |
| int ocount; |
| int loop; |
| unsigned long flags; |
| int lastmap_channel; |
| struct icn_card *lastmap_card; |
| u_char *p; |
| isdn_ctrl cmd; |
| u_char msg[0x100]; |
| |
| ocount = 1; |
| xcount = loop = 0; |
| while (len) { |
| count = cmd_free; |
| if (count > len) |
| count = len; |
| if (user) { |
| if (copy_from_user(msg, buf, count)) |
| return -EFAULT; |
| } else |
| memcpy(msg, buf, count); |
| |
| spin_lock_irqsave(&dev.devlock, flags); |
| lastmap_card = dev.mcard; |
| lastmap_channel = dev.channel; |
| icn_map_channel(card, mch); |
| |
| icn_putmsg(card, '>'); |
| for (p = msg, pp = readb(&cmd_i), i = count; i > 0; i--, p++, pp |
| ++) { |
| writeb((*p == '\n') ? 0xff : *p, |
| &dev.shmem->comm_buffers.pcio_buf[pp & 0xff]); |
| len--; |
| xcount++; |
| icn_putmsg(card, *p); |
| if ((*p == '\n') && (i > 1)) { |
| icn_putmsg(card, '>'); |
| ocount++; |
| } |
| ocount++; |
| } |
| writeb((readb(&cmd_i) + count) & 0xff, &cmd_i); |
| if (lastmap_card) |
| icn_map_channel(lastmap_card, lastmap_channel); |
| spin_unlock_irqrestore(&dev.devlock, flags); |
| if (len) { |
| mdelay(1); |
| if (loop++ > 20) |
| break; |
| } else |
| break; |
| } |
| if (len && (!user)) |
| printk(KERN_WARNING "icn: writemsg incomplete!\n"); |
| cmd.command = ISDN_STAT_STAVAIL; |
| cmd.driver = card->myid; |
| cmd.arg = ocount; |
| card->interface.statcallb(&cmd); |
| return xcount; |
| } |
| |
| /* |
| * Delete card's pending timers, send STOP to linklevel |
| */ |
| static void |
| icn_stopcard(icn_card * card) |
| { |
| unsigned long flags; |
| isdn_ctrl cmd; |
| |
| spin_lock_irqsave(&card->lock, flags); |
| if (card->flags & ICN_FLAGS_RUNNING) { |
| card->flags &= ~ICN_FLAGS_RUNNING; |
| del_timer(&card->st_timer); |
| del_timer(&card->rb_timer); |
| spin_unlock_irqrestore(&card->lock, flags); |
| cmd.command = ISDN_STAT_STOP; |
| cmd.driver = card->myid; |
| card->interface.statcallb(&cmd); |
| if (card->doubleS0) |
| icn_stopcard(card->other); |
| } else |
| spin_unlock_irqrestore(&card->lock, flags); |
| } |
| |
| static void |
| icn_stopallcards(void) |
| { |
| icn_card *p = cards; |
| |
| while (p) { |
| icn_stopcard(p); |
| p = p->next; |
| } |
| } |
| |
| /* |
| * Unmap all cards, because some of them may be mapped accidetly during |
| * autoprobing of some network drivers (SMC-driver?) |
| */ |
| static void |
| icn_disable_cards(void) |
| { |
| icn_card *card = cards; |
| |
| while (card) { |
| if (!request_region(card->port, ICN_PORTLEN, "icn-isdn")) { |
| printk(KERN_WARNING |
| "icn: (%s) ports 0x%03x-0x%03x in use.\n", |
| CID, |
| card->port, |
| card->port + ICN_PORTLEN); |
| } else { |
| OUTB_P(0, ICN_RUN); /* Reset Controller */ |
| OUTB_P(0, ICN_MAPRAM); /* Disable RAM */ |
| release_region(card->port, ICN_PORTLEN); |
| } |
| card = card->next; |
| } |
| } |
| |
| static int |
| icn_command(isdn_ctrl * c, icn_card * card) |
| { |
| ulong a; |
| ulong flags; |
| int i; |
| char cbuf[60]; |
| isdn_ctrl cmd; |
| icn_cdef cdef; |
| char __user *arg; |
| |
| switch (c->command) { |
| case ISDN_CMD_IOCTL: |
| memcpy(&a, c->parm.num, sizeof(ulong)); |
| arg = (char __user *)a; |
| switch (c->arg) { |
| case ICN_IOCTL_SETMMIO: |
| if (dev.memaddr != (a & 0x0ffc000)) { |
| if (!request_mem_region(a & 0x0ffc000, 0x4000, "icn-isdn (all cards)")) { |
| printk(KERN_WARNING |
| "icn: memory at 0x%08lx in use.\n", |
| a & 0x0ffc000); |
| return -EINVAL; |
| } |
| release_mem_region(a & 0x0ffc000, 0x4000); |
| icn_stopallcards(); |
| spin_lock_irqsave(&card->lock, flags); |
| if (dev.mvalid) { |
| iounmap(dev.shmem); |
| release_mem_region(dev.memaddr, 0x4000); |
| } |
| dev.mvalid = 0; |
| dev.memaddr = a & 0x0ffc000; |
| spin_unlock_irqrestore(&card->lock, flags); |
| printk(KERN_INFO |
| "icn: (%s) mmio set to 0x%08lx\n", |
| CID, |
| dev.memaddr); |
| } |
| break; |
| case ICN_IOCTL_GETMMIO: |
| return (long) dev.memaddr; |
| case ICN_IOCTL_SETPORT: |
| if (a == 0x300 || a == 0x310 || a == 0x320 || a == 0x330 |
| || a == 0x340 || a == 0x350 || a == 0x360 || |
| a == 0x308 || a == 0x318 || a == 0x328 || a == 0x338 |
| || a == 0x348 || a == 0x358 || a == 0x368) { |
| if (card->port != (unsigned short) a) { |
| if (!request_region((unsigned short) a, ICN_PORTLEN, "icn-isdn")) { |
| printk(KERN_WARNING |
| "icn: (%s) ports 0x%03x-0x%03x in use.\n", |
| CID, (int) a, (int) a + ICN_PORTLEN); |
| return -EINVAL; |
| } |
| release_region((unsigned short) a, ICN_PORTLEN); |
| icn_stopcard(card); |
| spin_lock_irqsave(&card->lock, flags); |
| if (card->rvalid) |
| release_region(card->port, ICN_PORTLEN); |
| card->port = (unsigned short) a; |
| card->rvalid = 0; |
| if (card->doubleS0) { |
| card->other->port = (unsigned short) a; |
| card->other->rvalid = 0; |
| } |
| spin_unlock_irqrestore(&card->lock, flags); |
| printk(KERN_INFO |
| "icn: (%s) port set to 0x%03x\n", |
| CID, card->port); |
| } |
| } else |
| return -EINVAL; |
| break; |
| case ICN_IOCTL_GETPORT: |
| return (int) card->port; |
| case ICN_IOCTL_GETDOUBLE: |
| return (int) card->doubleS0; |
| case ICN_IOCTL_DEBUGVAR: |
| if (copy_to_user(arg, |
| &card, |
| sizeof(ulong))) |
| return -EFAULT; |
| a += sizeof(ulong); |
| { |
| ulong l = (ulong) & dev; |
| if (copy_to_user(arg, |
| &l, |
| sizeof(ulong))) |
| return -EFAULT; |
| } |
| return 0; |
| case ICN_IOCTL_LOADBOOT: |
| if (dev.firstload) { |
| icn_disable_cards(); |
| dev.firstload = 0; |
| } |
| icn_stopcard(card); |
| return (icn_loadboot(arg, card)); |
| case ICN_IOCTL_LOADPROTO: |
| icn_stopcard(card); |
| if ((i = (icn_loadproto(arg, card)))) |
| return i; |
| if (card->doubleS0) |
| i = icn_loadproto(arg + ICN_CODE_STAGE2, card->other); |
| return i; |
| break; |
| case ICN_IOCTL_ADDCARD: |
| if (!dev.firstload) |
| return -EBUSY; |
| if (copy_from_user(&cdef, |
| arg, |
| sizeof(cdef))) |
| return -EFAULT; |
| return (icn_addcard(cdef.port, cdef.id1, cdef.id2)); |
| break; |
| case ICN_IOCTL_LEASEDCFG: |
| if (a) { |
| if (!card->leased) { |
| card->leased = 1; |
| while (card->ptype == ISDN_PTYPE_UNKNOWN) { |
| msleep_interruptible(ICN_BOOT_TIMEOUT1); |
| } |
| msleep_interruptible(ICN_BOOT_TIMEOUT1); |
| sprintf(cbuf, "00;FV2ON\n01;EAZ%c\n02;EAZ%c\n", |
| (a & 1)?'1':'C', (a & 2)?'2':'C'); |
| i = icn_writecmd(cbuf, strlen(cbuf), 0, card); |
| printk(KERN_INFO |
| "icn: (%s) Leased-line mode enabled\n", |
| CID); |
| cmd.command = ISDN_STAT_RUN; |
| cmd.driver = card->myid; |
| cmd.arg = 0; |
| card->interface.statcallb(&cmd); |
| } |
| } else { |
| if (card->leased) { |
| card->leased = 0; |
| sprintf(cbuf, "00;FV2OFF\n"); |
| i = icn_writecmd(cbuf, strlen(cbuf), 0, card); |
| printk(KERN_INFO |
| "icn: (%s) Leased-line mode disabled\n", |
| CID); |
| cmd.command = ISDN_STAT_RUN; |
| cmd.driver = card->myid; |
| cmd.arg = 0; |
| card->interface.statcallb(&cmd); |
| } |
| } |
| return 0; |
| default: |
| return -EINVAL; |
| } |
| break; |
| case ISDN_CMD_DIAL: |
| if (!card->flags & ICN_FLAGS_RUNNING) |
| return -ENODEV; |
| if (card->leased) |
| break; |
| if ((c->arg & 255) < ICN_BCH) { |
| char *p; |
| char dial[50]; |
| char dcode[4]; |
| |
| a = c->arg; |
| p = c->parm.setup.phone; |
| if (*p == 's' || *p == 'S') { |
| /* Dial for SPV */ |
| p++; |
| strcpy(dcode, "SCA"); |
| } else |
| /* Normal Dial */ |
| strcpy(dcode, "CAL"); |
| strcpy(dial, p); |
| sprintf(cbuf, "%02d;D%s_R%s,%02d,%02d,%s\n", (int) (a + 1), |
| dcode, dial, c->parm.setup.si1, |
| c->parm.setup.si2, c->parm.setup.eazmsn); |
| i = icn_writecmd(cbuf, strlen(cbuf), 0, card); |
| } |
| break; |
| case ISDN_CMD_ACCEPTD: |
| if (!card->flags & ICN_FLAGS_RUNNING) |
| return -ENODEV; |
| if (c->arg < ICN_BCH) { |
| a = c->arg + 1; |
| if (card->fw_rev >= 300) { |
| switch (card->l2_proto[a - 1]) { |
| case ISDN_PROTO_L2_X75I: |
| sprintf(cbuf, "%02d;BX75\n", (int) a); |
| break; |
| case ISDN_PROTO_L2_HDLC: |
| sprintf(cbuf, "%02d;BTRA\n", (int) a); |
| break; |
| } |
| i = icn_writecmd(cbuf, strlen(cbuf), 0, card); |
| } |
| sprintf(cbuf, "%02d;DCON_R\n", (int) a); |
| i = icn_writecmd(cbuf, strlen(cbuf), 0, card); |
| } |
| break; |
| case ISDN_CMD_ACCEPTB: |
| if (!card->flags & ICN_FLAGS_RUNNING) |
| return -ENODEV; |
| if (c->arg < ICN_BCH) { |
| a = c->arg + 1; |
| if (card->fw_rev >= 300) |
| switch (card->l2_proto[a - 1]) { |
| case ISDN_PROTO_L2_X75I: |
| sprintf(cbuf, "%02d;BCON_R,BX75\n", (int) a); |
| break; |
| case ISDN_PROTO_L2_HDLC: |
| sprintf(cbuf, "%02d;BCON_R,BTRA\n", (int) a); |
| break; |
| } else |
| sprintf(cbuf, "%02d;BCON_R\n", (int) a); |
| i = icn_writecmd(cbuf, strlen(cbuf), 0, card); |
| } |
| break; |
| case ISDN_CMD_HANGUP: |
| if (!card->flags & ICN_FLAGS_RUNNING) |
| return -ENODEV; |
| if (c->arg < ICN_BCH) { |
| a = c->arg + 1; |
| sprintf(cbuf, "%02d;BDIS_R\n%02d;DDIS_R\n", (int) a, (int) a); |
| i = icn_writecmd(cbuf, strlen(cbuf), 0, card); |
| } |
| break; |
| case ISDN_CMD_SETEAZ: |
| if (!card->flags & ICN_FLAGS_RUNNING) |
| return -ENODEV; |
| if (card->leased) |
| break; |
| if (c->arg < ICN_BCH) { |
| a = c->arg + 1; |
| if (card->ptype == ISDN_PTYPE_EURO) { |
| sprintf(cbuf, "%02d;MS%s%s\n", (int) a, |
| c->parm.num[0] ? "N" : "ALL", c->parm.num); |
| } else |
| sprintf(cbuf, "%02d;EAZ%s\n", (int) a, |
| c->parm.num[0] ? (char *)(c->parm.num) : "0123456789"); |
| i = icn_writecmd(cbuf, strlen(cbuf), 0, card); |
| } |
| break; |
| case ISDN_CMD_CLREAZ: |
| if (!card->flags & ICN_FLAGS_RUNNING) |
| return -ENODEV; |
| if (card->leased) |
| break; |
| if (c->arg < ICN_BCH) { |
| a = c->arg + 1; |
| if (card->ptype == ISDN_PTYPE_EURO) |
| sprintf(cbuf, "%02d;MSNC\n", (int) a); |
| else |
| sprintf(cbuf, "%02d;EAZC\n", (int) a); |
| i = icn_writecmd(cbuf, strlen(cbuf), 0, card); |
| } |
| break; |
| case ISDN_CMD_SETL2: |
| if (!card->flags & ICN_FLAGS_RUNNING) |
| return -ENODEV; |
| if ((c->arg & 255) < ICN_BCH) { |
| a = c->arg; |
| switch (a >> 8) { |
| case ISDN_PROTO_L2_X75I: |
| sprintf(cbuf, "%02d;BX75\n", (int) (a & 255) + 1); |
| break; |
| case ISDN_PROTO_L2_HDLC: |
| sprintf(cbuf, "%02d;BTRA\n", (int) (a & 255) + 1); |
| break; |
| default: |
| return -EINVAL; |
| } |
| i = icn_writecmd(cbuf, strlen(cbuf), 0, card); |
| card->l2_proto[a & 255] = (a >> 8); |
| } |
| break; |
| case ISDN_CMD_SETL3: |
| if (!card->flags & ICN_FLAGS_RUNNING) |
| return -ENODEV; |
| return 0; |
| default: |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| /* |
| * Find card with given driverId |
| */ |
| static inline icn_card * |
| icn_findcard(int driverid) |
| { |
| icn_card *p = cards; |
| |
| while (p) { |
| if (p->myid == driverid) |
| return p; |
| p = p->next; |
| } |
| return (icn_card *) 0; |
| } |
| |
| /* |
| * Wrapper functions for interface to linklevel |
| */ |
| static int |
| if_command(isdn_ctrl * c) |
| { |
| icn_card *card = icn_findcard(c->driver); |
| |
| if (card) |
| return (icn_command(c, card)); |
| printk(KERN_ERR |
| "icn: if_command %d called with invalid driverId %d!\n", |
| c->command, c->driver); |
| return -ENODEV; |
| } |
| |
| static int |
| if_writecmd(const u_char __user *buf, int len, int id, int channel) |
| { |
| icn_card *card = icn_findcard(id); |
| |
| if (card) { |
| if (!card->flags & ICN_FLAGS_RUNNING) |
| return -ENODEV; |
| return (icn_writecmd(buf, len, 1, card)); |
| } |
| printk(KERN_ERR |
| "icn: if_writecmd called with invalid driverId!\n"); |
| return -ENODEV; |
| } |
| |
| static int |
| if_readstatus(u_char __user *buf, int len, int id, int channel) |
| { |
| icn_card *card = icn_findcard(id); |
| |
| if (card) { |
| if (!card->flags & ICN_FLAGS_RUNNING) |
| return -ENODEV; |
| return (icn_readstatus(buf, len, card)); |
| } |
| printk(KERN_ERR |
| "icn: if_readstatus called with invalid driverId!\n"); |
| return -ENODEV; |
| } |
| |
| static int |
| if_sendbuf(int id, int channel, int ack, struct sk_buff *skb) |
| { |
| icn_card *card = icn_findcard(id); |
| |
| if (card) { |
| if (!card->flags & ICN_FLAGS_RUNNING) |
| return -ENODEV; |
| return (icn_sendbuf(channel, ack, skb, card)); |
| } |
| printk(KERN_ERR |
| "icn: if_sendbuf called with invalid driverId!\n"); |
| return -ENODEV; |
| } |
| |
| /* |
| * Allocate a new card-struct, initialize it |
| * link it into cards-list and register it at linklevel. |
| */ |
| static icn_card * |
| icn_initcard(int port, char *id) |
| { |
| icn_card *card; |
| int i; |
| |
| if (!(card = kzalloc(sizeof(icn_card), GFP_KERNEL))) { |
| printk(KERN_WARNING |
| "icn: (%s) Could not allocate card-struct.\n", id); |
| return (icn_card *) 0; |
| } |
| spin_lock_init(&card->lock); |
| card->port = port; |
| card->interface.owner = THIS_MODULE; |
| card->interface.hl_hdrlen = 1; |
| card->interface.channels = ICN_BCH; |
| card->interface.maxbufsize = 4000; |
| card->interface.command = if_command; |
| card->interface.writebuf_skb = if_sendbuf; |
| card->interface.writecmd = if_writecmd; |
| card->interface.readstat = if_readstatus; |
| card->interface.features = ISDN_FEATURE_L2_X75I | |
| ISDN_FEATURE_L2_HDLC | |
| ISDN_FEATURE_L3_TRANS | |
| ISDN_FEATURE_P_UNKNOWN; |
| card->ptype = ISDN_PTYPE_UNKNOWN; |
| strlcpy(card->interface.id, id, sizeof(card->interface.id)); |
| card->msg_buf_write = card->msg_buf; |
| card->msg_buf_read = card->msg_buf; |
| card->msg_buf_end = &card->msg_buf[sizeof(card->msg_buf) - 1]; |
| for (i = 0; i < ICN_BCH; i++) { |
| card->l2_proto[i] = ISDN_PROTO_L2_X75I; |
| skb_queue_head_init(&card->spqueue[i]); |
| } |
| card->next = cards; |
| cards = card; |
| if (!register_isdn(&card->interface)) { |
| cards = cards->next; |
| printk(KERN_WARNING |
| "icn: Unable to register %s\n", id); |
| kfree(card); |
| return (icn_card *) 0; |
| } |
| card->myid = card->interface.channels; |
| sprintf(card->regname, "icn-isdn (%s)", card->interface.id); |
| return card; |
| } |
| |
| static int |
| icn_addcard(int port, char *id1, char *id2) |
| { |
| icn_card *card; |
| icn_card *card2; |
| |
| if (!(card = icn_initcard(port, id1))) { |
| return -EIO; |
| } |
| if (!strlen(id2)) { |
| printk(KERN_INFO |
| "icn: (%s) ICN-2B, port 0x%x added\n", |
| card->interface.id, port); |
| return 0; |
| } |
| if (!(card2 = icn_initcard(port, id2))) { |
| printk(KERN_INFO |
| "icn: (%s) half ICN-4B, port 0x%x added\n", |
| card2->interface.id, port); |
| return 0; |
| } |
| card->doubleS0 = 1; |
| card->secondhalf = 0; |
| card->other = card2; |
| card2->doubleS0 = 1; |
| card2->secondhalf = 1; |
| card2->other = card; |
| printk(KERN_INFO |
| "icn: (%s and %s) ICN-4B, port 0x%x added\n", |
| card->interface.id, card2->interface.id, port); |
| return 0; |
| } |
| |
| #ifndef MODULE |
| static int __init |
| icn_setup(char *line) |
| { |
| char *p, *str; |
| int ints[3]; |
| static char sid[20]; |
| static char sid2[20]; |
| |
| str = get_options(line, 2, ints); |
| if (ints[0]) |
| portbase = ints[1]; |
| if (ints[0] > 1) |
| membase = (unsigned long)ints[2]; |
| if (str && *str) { |
| strcpy(sid, str); |
| icn_id = sid; |
| if ((p = strchr(sid, ','))) { |
| *p++ = 0; |
| strcpy(sid2, p); |
| icn_id2 = sid2; |
| } |
| } |
| return(1); |
| } |
| __setup("icn=", icn_setup); |
| #endif /* MODULE */ |
| |
| static int __init icn_init(void) |
| { |
| char *p; |
| char rev[10]; |
| |
| memset(&dev, 0, sizeof(icn_dev)); |
| dev.memaddr = (membase & 0x0ffc000); |
| dev.channel = -1; |
| dev.mcard = NULL; |
| dev.firstload = 1; |
| spin_lock_init(&dev.devlock); |
| |
| if ((p = strchr(revision, ':'))) { |
| strcpy(rev, p + 1); |
| p = strchr(rev, '$'); |
| *p = 0; |
| } else |
| strcpy(rev, " ??? "); |
| printk(KERN_NOTICE "ICN-ISDN-driver Rev%smem=0x%08lx\n", rev, |
| dev.memaddr); |
| return (icn_addcard(portbase, icn_id, icn_id2)); |
| } |
| |
| static void __exit icn_exit(void) |
| { |
| isdn_ctrl cmd; |
| icn_card *card = cards; |
| icn_card *last, *tmpcard; |
| int i; |
| unsigned long flags; |
| |
| icn_stopallcards(); |
| while (card) { |
| cmd.command = ISDN_STAT_UNLOAD; |
| cmd.driver = card->myid; |
| card->interface.statcallb(&cmd); |
| spin_lock_irqsave(&card->lock, flags); |
| if (card->rvalid) { |
| OUTB_P(0, ICN_RUN); /* Reset Controller */ |
| OUTB_P(0, ICN_MAPRAM); /* Disable RAM */ |
| if (card->secondhalf || (!card->doubleS0)) { |
| release_region(card->port, ICN_PORTLEN); |
| card->rvalid = 0; |
| } |
| for (i = 0; i < ICN_BCH; i++) |
| icn_free_queue(card, i); |
| } |
| tmpcard = card->next; |
| spin_unlock_irqrestore(&card->lock, flags); |
| card = tmpcard; |
| } |
| card = cards; |
| cards = NULL; |
| while (card) { |
| last = card; |
| card = card->next; |
| kfree(last); |
| } |
| if (dev.mvalid) { |
| iounmap(dev.shmem); |
| release_mem_region(dev.memaddr, 0x4000); |
| } |
| printk(KERN_NOTICE "ICN-ISDN-driver unloaded\n"); |
| } |
| |
| module_init(icn_init); |
| module_exit(icn_exit); |