diff options
author | André Fabian Silva Delgado <emulatorman@parabola.nu> | 2015-08-05 17:04:01 -0300 |
---|---|---|
committer | André Fabian Silva Delgado <emulatorman@parabola.nu> | 2015-08-05 17:04:01 -0300 |
commit | 57f0f512b273f60d52568b8c6b77e17f5636edc0 (patch) | |
tree | 5e910f0e82173f4ef4f51111366a3f1299037a7b /drivers/isdn/icn |
Initial import
Diffstat (limited to 'drivers/isdn/icn')
-rw-r--r-- | drivers/isdn/icn/Kconfig | 12 | ||||
-rw-r--r-- | drivers/isdn/icn/Makefile | 5 | ||||
-rw-r--r-- | drivers/isdn/icn/icn.c | 1693 | ||||
-rw-r--r-- | drivers/isdn/icn/icn.h | 253 |
4 files changed, 1963 insertions, 0 deletions
diff --git a/drivers/isdn/icn/Kconfig b/drivers/isdn/icn/Kconfig new file mode 100644 index 000000000..4534f21a1 --- /dev/null +++ b/drivers/isdn/icn/Kconfig @@ -0,0 +1,12 @@ +config ISDN_DRV_ICN + tristate "ICN 2B and 4B support" + depends on ISA + help + This enables support for two kinds of ISDN-cards made by a German + company called ICN. 2B is the standard version for a single ISDN + line with two B-channels, 4B supports two ISDN lines. For running + this card, additional firmware is necessary, which has to be + downloaded into the card using a utility which is distributed + separately. See <file:Documentation/isdn/README> and + <file:Documentation/isdn/README.icn> for more + information. diff --git a/drivers/isdn/icn/Makefile b/drivers/isdn/icn/Makefile new file mode 100644 index 000000000..d9b476fcf --- /dev/null +++ b/drivers/isdn/icn/Makefile @@ -0,0 +1,5 @@ +# Makefile for the icn ISDN device driver + +# Each configuration option enables a list of files. + +obj-$(CONFIG_ISDN_DRV_ICN) += icn.o diff --git a/drivers/isdn/icn/icn.c b/drivers/isdn/icn/icn.c new file mode 100644 index 000000000..358a574d9 --- /dev/null +++ b/drivers/isdn/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; +#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); diff --git a/drivers/isdn/icn/icn.h b/drivers/isdn/icn/icn.h new file mode 100644 index 000000000..b71346699 --- /dev/null +++ b/drivers/isdn/icn/icn.h @@ -0,0 +1,253 @@ +/* $Id: icn.h,v 1.30.6.5 2001/09/23 22:24:55 kai Exp $ + * + * ISDN lowlevel-module for the ICN active ISDN-Card. + * + * Copyright 1994 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. + * + */ + +#ifndef icn_h +#define icn_h + +#define ICN_IOCTL_SETMMIO 0 +#define ICN_IOCTL_GETMMIO 1 +#define ICN_IOCTL_SETPORT 2 +#define ICN_IOCTL_GETPORT 3 +#define ICN_IOCTL_LOADBOOT 4 +#define ICN_IOCTL_LOADPROTO 5 +#define ICN_IOCTL_LEASEDCFG 6 +#define ICN_IOCTL_GETDOUBLE 7 +#define ICN_IOCTL_DEBUGVAR 8 +#define ICN_IOCTL_ADDCARD 9 + +/* Struct for adding new cards */ +typedef struct icn_cdef { + int port; + char id1[10]; + char id2[10]; +} icn_cdef; + +#if defined(__KERNEL__) || defined(__DEBUGVAR__) + +#ifdef __KERNEL__ +/* Kernel includes */ + +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/major.h> +#include <asm/io.h> +#include <linux/kernel.h> +#include <linux/signal.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/mman.h> +#include <linux/ioport.h> +#include <linux/timer.h> +#include <linux/wait.h> +#include <linux/delay.h> +#include <linux/isdnif.h> + +#endif /* __KERNEL__ */ + +/* some useful macros for debugging */ +#ifdef ICN_DEBUG_PORT +#define OUTB_P(v, p) {printk(KERN_DEBUG "icn: outb_p(0x%02x,0x%03x)\n", v, p); outb_p(v, p);} +#else +#define OUTB_P outb +#endif + +/* Defaults for Port-Address and shared-memory */ +#define ICN_BASEADDR 0x320 +#define ICN_PORTLEN (0x04) +#define ICN_MEMADDR 0x0d0000 + +#define ICN_FLAGS_B1ACTIVE 1 /* B-Channel-1 is open */ +#define ICN_FLAGS_B2ACTIVE 2 /* B-Channel-2 is open */ +#define ICN_FLAGS_RUNNING 4 /* Cards driver activated */ +#define ICN_FLAGS_RBTIMER 8 /* cyclic scheduling of B-Channel-poll */ + +#define ICN_BOOT_TIMEOUT1 1000 /* Delay for Boot-download (msecs) */ + +#define ICN_TIMER_BCREAD (HZ / 100) /* B-Channel poll-cycle */ +#define ICN_TIMER_DCREAD (HZ / 2) /* D-Channel poll-cycle */ + +#define ICN_CODE_STAGE1 4096 /* Size of bootcode */ +#define ICN_CODE_STAGE2 65536 /* Size of protocol-code */ + +#define ICN_MAX_SQUEUE 8000 /* Max. outstanding send-data (2* hw-buf.) */ +#define ICN_FRAGSIZE (250) /* Max. size of send-fragments */ +#define ICN_BCH 2 /* Number of supported channels per card */ + +/* type-definitions for accessing the mmap-io-areas */ + +#define SHM_DCTL_OFFSET (0) /* Offset to data-controlstructures in shm */ +#define SHM_CCTL_OFFSET (0x1d2) /* Offset to comm-controlstructures in shm */ +#define SHM_CBUF_OFFSET (0x200) /* Offset to comm-buffers in shm */ +#define SHM_DBUF_OFFSET (0x2000) /* Offset to data-buffers in shm */ + +/* + * Layout of card's data buffers + */ +typedef struct { + unsigned char length; /* Bytecount of fragment (max 250) */ + unsigned char endflag; /* 0=last frag., 0xff=frag. continued */ + unsigned char data[ICN_FRAGSIZE]; /* The data */ + /* Fill to 256 bytes */ + char unused[0x100 - ICN_FRAGSIZE - 2]; +} frag_buf; + +/* + * Layout of card's shared memory + */ +typedef union { + struct { + unsigned char scns; /* Index to free SendFrag. */ + unsigned char scnr; /* Index to active SendFrag READONLY */ + unsigned char ecns; /* Index to free RcvFrag. READONLY */ + unsigned char ecnr; /* Index to valid RcvFrag */ + char unused[6]; + unsigned short fuell1; /* Internal Buf Bytecount */ + } data_control; + struct { + char unused[SHM_CCTL_OFFSET]; + unsigned char iopc_i; /* Read-Ptr Status-Queue READONLY */ + unsigned char iopc_o; /* Write-Ptr Status-Queue */ + unsigned char pcio_i; /* Write-Ptr Command-Queue */ + unsigned char pcio_o; /* Read-Ptr Command Queue READONLY */ + } comm_control; + struct { + char unused[SHM_CBUF_OFFSET]; + unsigned char pcio_buf[0x100]; /* Ring-Buffer Command-Queue */ + unsigned char iopc_buf[0x100]; /* Ring-Buffer Status-Queue */ + } comm_buffers; + struct { + char unused[SHM_DBUF_OFFSET]; + frag_buf receive_buf[0x10]; + frag_buf send_buf[0x10]; + } data_buffers; +} icn_shmem; + +/* + * Per card driver data + */ +typedef struct icn_card { + struct icn_card *next; /* Pointer to next device struct */ + struct icn_card *other; /* Pointer to other card for ICN4B */ + unsigned short port; /* Base-port-address */ + int myid; /* Driver-Nr. assigned by linklevel */ + int rvalid; /* IO-portregion has been requested */ + int leased; /* Flag: This Adapter is connected */ + /* to a leased line */ + unsigned short flags; /* Statusflags */ + int doubleS0; /* Flag: ICN4B */ + int secondhalf; /* Flag: Second half of a doubleS0 */ + int fw_rev; /* Firmware revision loaded */ + int ptype; /* Protocol type (1TR6 or Euro) */ + struct timer_list st_timer; /* Timer for Status-Polls */ + struct timer_list rb_timer; /* Timer for B-Channel-Polls */ + u_char rcvbuf[ICN_BCH][4096]; /* B-Channel-Receive-Buffers */ + int rcvidx[ICN_BCH]; /* Index for above buffers */ + int l2_proto[ICN_BCH]; /* Current layer-2-protocol */ + isdn_if interface; /* Interface to upper layer */ + int iptr; /* Index to imsg-buffer */ + char imsg[60]; /* Internal buf for status-parsing */ + char msg_buf[2048]; /* Buffer for status-messages */ + char *msg_buf_write; /* Writepointer for statusbuffer */ + char *msg_buf_read; /* Readpointer for statusbuffer */ + char *msg_buf_end; /* Pointer to end of statusbuffer */ + int sndcount[ICN_BCH]; /* Byte-counters for B-Ch.-send */ + int xlen[ICN_BCH]; /* Byte-counters/Flags for sent-ACK */ + struct sk_buff *xskb[ICN_BCH]; /* Current transmitted skb */ + struct sk_buff_head spqueue[ICN_BCH]; /* Sendqueue */ + char regname[35]; /* Name used for request_region */ + u_char xmit_lock[ICN_BCH]; /* Semaphore for pollbchan_send()*/ + spinlock_t lock; /* protect critical operations */ +} icn_card; + +/* + * Main driver data + */ +typedef struct icn_dev { + spinlock_t devlock; /* spinlock to protect this struct */ + unsigned long memaddr; /* Address of memory mapped buffers */ + icn_shmem __iomem *shmem; /* Pointer to memory-mapped-buffers */ + int mvalid; /* IO-shmem has been requested */ + int channel; /* Currently mapped channel */ + struct icn_card *mcard; /* Currently mapped card */ + int chanlock; /* Semaphore for channel-mapping */ + int firstload; /* Flag: firmware never loaded */ +} icn_dev; + +typedef icn_dev *icn_devptr; + +#ifdef __KERNEL__ + +static icn_card *cards = (icn_card *) 0; +static u_char chan2bank[] = +{0, 4, 8, 12}; /* for icn_map_channel() */ + +static icn_dev dev; + +#endif /* __KERNEL__ */ + +/* Utility-Macros */ + +/* Macros for accessing ports */ +#define ICN_CFG (card->port) +#define ICN_MAPRAM (card->port + 1) +#define ICN_RUN (card->port + 2) +#define ICN_BANK (card->port + 3) + +/* Return true, if there is a free transmit-buffer */ +#define sbfree (((readb(&dev.shmem->data_control.scns) + 1) & 0xf) != \ + readb(&dev.shmem->data_control.scnr)) + +/* Switch to next transmit-buffer */ +#define sbnext (writeb((readb(&dev.shmem->data_control.scns) + 1) & 0xf, \ + &dev.shmem->data_control.scns)) + +/* Shortcuts for transmit-buffer-access */ +#define sbuf_n dev.shmem->data_control.scns +#define sbuf_d dev.shmem->data_buffers.send_buf[readb(&sbuf_n)].data +#define sbuf_l dev.shmem->data_buffers.send_buf[readb(&sbuf_n)].length +#define sbuf_f dev.shmem->data_buffers.send_buf[readb(&sbuf_n)].endflag + +/* Return true, if there is receive-data is available */ +#define rbavl (readb(&dev.shmem->data_control.ecnr) != \ + readb(&dev.shmem->data_control.ecns)) + +/* Switch to next receive-buffer */ +#define rbnext (writeb((readb(&dev.shmem->data_control.ecnr) + 1) & 0xf, \ + &dev.shmem->data_control.ecnr)) + +/* Shortcuts for receive-buffer-access */ +#define rbuf_n dev.shmem->data_control.ecnr +#define rbuf_d dev.shmem->data_buffers.receive_buf[readb(&rbuf_n)].data +#define rbuf_l dev.shmem->data_buffers.receive_buf[readb(&rbuf_n)].length +#define rbuf_f dev.shmem->data_buffers.receive_buf[readb(&rbuf_n)].endflag + +/* Shortcuts for command-buffer-access */ +#define cmd_o (dev.shmem->comm_control.pcio_o) +#define cmd_i (dev.shmem->comm_control.pcio_i) + +/* Return free space in command-buffer */ +#define cmd_free ((readb(&cmd_i) >= readb(&cmd_o)) ? \ + 0x100 - readb(&cmd_i) + readb(&cmd_o) : \ + readb(&cmd_o) - readb(&cmd_i)) + +/* Shortcuts for message-buffer-access */ +#define msg_o (dev.shmem->comm_control.iopc_o) +#define msg_i (dev.shmem->comm_control.iopc_i) + +/* Return length of Message, if avail. */ +#define msg_avail ((readb(&msg_o) > readb(&msg_i)) ? \ + 0x100 - readb(&msg_o) + readb(&msg_i) : \ + readb(&msg_i) - readb(&msg_o)) + +#define CID (card->interface.id) + +#endif /* defined(__KERNEL__) || defined(__DEBUGVAR__) */ +#endif /* icn_h */ |