diff options
author | André Fabian Silva Delgado <emulatorman@parabola.nu> | 2016-06-10 05:30:17 -0300 |
---|---|---|
committer | André Fabian Silva Delgado <emulatorman@parabola.nu> | 2016-06-10 05:30:17 -0300 |
commit | d635711daa98be86d4c7fd01499c34f566b54ccb (patch) | |
tree | aa5cc3760a27c3d57146498cb82fa549547de06c /drivers/staging/i4l/icn/icn.c | |
parent | c91265cd0efb83778f015b4d4b1129bd2cfd075e (diff) |
Linux-libre 4.6.2-gnu
Diffstat (limited to 'drivers/staging/i4l/icn/icn.c')
-rw-r--r-- | drivers/staging/i4l/icn/icn.c | 1693 |
1 files changed, 1693 insertions, 0 deletions
diff --git a/drivers/staging/i4l/icn/icn.c b/drivers/staging/i4l/icn/icn.c new file mode 100644 index 000000000..46d957c34 --- /dev/null +++ b/drivers/staging/i4l/icn/icn.c @@ -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; + /* 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); |