Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / drivers / isdn / icn / icn.c
diff --git a/kernel/drivers/isdn/icn/icn.c b/kernel/drivers/isdn/icn/icn.c
new file mode 100644 (file)
index 0000000..358a574
--- /dev/null
@@ -0,0 +1,1693 @@
+/* $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>
+#include <linux/slab.h>
+#include <linux/sched.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[80];
+       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 dcode[4];
+
+                       a = c->arg;
+                       p = c->parm.setup.phone;
+                       if (*p == 's' || *p == 'S') {
+                               /* Dial for SPV */
+                               p++;
+                               strcpy(dcode, "SCA");
+                       } else
+                               /* Normal Dial */
+                               strcpy(dcode, "CAL");
+                       snprintf(cbuf, sizeof(cbuf),
+                                "%02d;D%s_R%s,%02d,%02d,%s\n", (int) (a + 1),
+                                dcode, p, c->parm.setup.si1,
+                                c->parm.setup.si2, c->parm.setup.eazmsn);
+                       i = 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", id2, 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) {
+               strlcpy(sid, str, sizeof(sid));
+               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[21];
+
+       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, ':'))) {
+               strncpy(rev, p + 1, 20);
+               rev[20] = '\0';
+               p = strchr(rev, '$');
+               if (p)
+                       *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);