diff options
Diffstat (limited to 'drivers/isdn')
45 files changed, 24991 insertions, 0 deletions
diff --git a/drivers/isdn/Config.in b/drivers/isdn/Config.in new file mode 100644 index 000000000..96f7e5a43 --- /dev/null +++ b/drivers/isdn/Config.in @@ -0,0 +1,14 @@ +# +# ISDN device configuration +# +if [ "$CONFIG_INET" != "n" ]; then + bool 'Support synchronous PPP' CONFIG_ISDN_PPP + if [ "$CONFIG_ISDN_PPP" != "n" ]; then + bool 'Use VJ-compression with synchronous PPP' CONFIG_ISDN_PPP_VJ + bool 'Support generic MP (RFC 1717)' CONFIG_ISDN_MPP + fi +fi +bool 'Support audio via ISDN' CONFIG_ISDN_AUDIO +dep_tristate 'ICN 2B and 4B support' CONFIG_ISDN_DRV_ICN $CONFIG_ISDN +dep_tristate 'PCBIT-D support' CONFIG_ISDN_DRV_PCBIT $CONFIG_ISDN +dep_tristate 'Teles/NICCY1016PC/Creatix support' CONFIG_ISDN_DRV_TELES $CONFIG_ISDN diff --git a/drivers/isdn/Makefile b/drivers/isdn/Makefile new file mode 100644 index 000000000..3b441835e --- /dev/null +++ b/drivers/isdn/Makefile @@ -0,0 +1,70 @@ +SUB_DIRS := +MOD_SUB_DIRS := +ALL_SUB_DIRS := icn teles pcbit + +L_OBJS := +LX_OBJS := +M_OBJS := +MX_OBJS := +O_OBJS := +OX_OBJS := +L_TARGET := +O_TARGET := + +ifeq ($(CONFIG_ISDN),y) + L_TARGET := isdn.a + L_OBJS += isdn_net.o isdn_tty.o isdn_cards.o + LX_OBJS += isdn_common.o + ifdef CONFIG_ISDN_PPP + L_OBJS += isdn_ppp.o + endif + ifdef CONFIG_ISDN_AUDIO + L_OBJS += isdn_audio.o + endif +else + ifeq ($(CONFIG_ISDN),m) + M_OBJS += isdn.o + O_TARGET += isdn.o + O_OBJS += isdn_net.o isdn_tty.o + OX_OBJS += isdn_common.o + ifdef CONFIG_ISDN_PPP + O_OBJS += isdn_ppp.o + endif + ifdef CONFIG_ISDN_AUDIO + O_OBJS += isdn_audio.o + endif + endif +endif + +ifeq ($(CONFIG_ISDN_DRV_TELES),y) + L_OBJS += teles/teles.o + SUB_DIRS += teles + MOD_SUB_DIRS += teles +else + ifeq ($(CONFIG_ISDN_DRV_TELES),m) + MOD_SUB_DIRS += teles + endif +endif + +ifeq ($(CONFIG_ISDN_DRV_ICN),y) + L_OBJS += icn/icn.o + SUB_DIRS += icn + MOD_SUB_DIRS += icn +else + ifeq ($(CONFIG_ISDN_DRV_ICN),m) + MOD_SUB_DIRS += icn + endif +endif + +ifeq ($(CONFIG_ISDN_DRV_PCBIT),y) + L_OBJS += pcbit/pcbit.o + SUB_DIRS += pcbit + MOD_SUB_DIRS += pcbit +else + ifeq ($(CONFIG_ISDN_DRV_PCBIT),m) + MOD_SUB_DIRS += pcbit + endif +endif + +include $(TOPDIR)/Rules.make + diff --git a/drivers/isdn/icn/Makefile b/drivers/isdn/icn/Makefile new file mode 100644 index 000000000..2427bf292 --- /dev/null +++ b/drivers/isdn/icn/Makefile @@ -0,0 +1,11 @@ +L_OBJS := +M_OBJS := + +ifeq ($(CONFIG_ISDN_DRV_ICN),y) + L_OBJS += icn.o +else + M_OBJS += icn.o +endif + +include $(TOPDIR)/Rules.make + diff --git a/drivers/isdn/icn/icn.c b/drivers/isdn/icn/icn.c new file mode 100644 index 000000000..aca9fe72b --- /dev/null +++ b/drivers/isdn/icn/icn.c @@ -0,0 +1,1675 @@ +/* $Id: icn.c,v 1.29 1996/08/29 20:34:54 fritz Exp $ + * + * ISDN low-level module for the ICN active ISDN-Card. + * + * Copyright 1994,95,96 by Fritz Elfert (fritz@wuemaus.franken.de) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Log: icn.c,v $ + * Revision 1.29 1996/08/29 20:34:54 fritz + * Bugfix in send queue management: + * sndcount was not updated correctly. + * Minor Bugfixes. + * + * Revision 1.28 1996/06/28 17:02:53 fritz + * replaced memcpy_fromfs_toio. + * + * Revision 1.27 1996/06/25 18:38:59 fritz + * Fixed function name in error message. + * + * Revision 1.26 1996/06/24 17:20:35 fritz + * Bugfixes in pollbchan_send(): + * - Using lock field of skbuff breaks networking. + * - Added channel locking + * - changed dequeuing scheme. + * Eliminated misc. compiler warnings. + * + * Revision 1.25 1996/06/11 22:53:35 tsbogend + * fixed problem with large array on stack + * made the driver working on Linux/alpha + * + * Revision 1.24 1996/06/06 13:58:33 fritz + * Changed code to be architecture independent + * + * Revision 1.23 1996/06/03 19:59:00 fritz + * Fixed typos. + * + * Revision 1.22 1996/05/17 15:46:41 fritz + * Removed own queue management. + * Changed queue management to use sk_buffs. + * + * Revision 1.21 1996/05/02 04:01:20 fritz + * Bugfix: + * - icn_addcard() evaluated wrong driverId. + * + * Revision 1.20 1996/05/02 00:40:27 fritz + * Major rewrite to support more than one card + * with a single module. + * Support for new firmware. + * + * Revision 1.19 1996/04/21 17:43:32 fritz + * Changes for Support of new Firmware BRV3.02 + * + * Revision 1.18 1996/04/20 16:50:26 fritz + * Fixed status-buffer overrun. + * Misc. typos + * + * Revision 1.17 1996/02/11 02:39:04 fritz + * Increased Buffer for status-messages. + * Removed conditionals for HDLC-firmware. + * + * Revision 1.16 1996/01/22 05:01:55 fritz + * Revert to GPL. + * + * Revision 1.15 1996/01/10 20:57:39 fritz + * Bugfix: Loading firmware twice caused the device stop working. + * + * Revision 1.14 1995/12/18 18:23:37 fritz + * Support for ICN-2B Cards. + * Change for supporting user-settable service-octet. + * + * Revision 1.13 1995/10/29 21:41:07 fritz + * Added support for DriverId's, added Jan's patches for Kernel versions. + * + * Revision 1.12 1995/04/29 13:07:35 fritz + * Added support for new Euro-ISDN-firmware + * + * Revision 1.11 1995/04/23 13:40:45 fritz + * Added support for SPV's. + * Changed Dial-Command to support MSN's on DSS1-Lines. + * + * Revision 1.10 1995/03/25 23:23:24 fritz + * Changed configurable Ports, to allow settings for DIP-Switch Cardversions. + * + * Revision 1.9 1995/03/25 23:17:30 fritz + * Fixed race-condition in pollbchan_send + * + * Revision 1.8 1995/03/15 12:49:44 fritz + * Added support for SPV's + * Split pollbchan_work for calling send-routine directly + * + * Revision 1.7 1995/02/20 03:48:03 fritz + * Added support of new request_region-function. + * Minor bugfixes. + * + * Revision 1.6 1995/01/31 15:48:45 fritz + * Added Cause-Messages to be signaled to upper layers. + * Added Revision-Info on load. + * + * Revision 1.5 1995/01/29 23:34:59 fritz + * Added stopdriver() and appropriate calls. + * Changed printk-statements to support loglevels. + * + * Revision 1.4 1995/01/09 07:40:46 fritz + * Added GPL-Notice + * + * Revision 1.3 1995/01/04 05:15:18 fritz + * Added undocumented "bootload-finished"-command in download-code + * to satisfy some brain-damaged icn card-versions. + * + * Revision 1.2 1995/01/02 02:14:45 fritz + * Misc Bugfixes + * + * Revision 1.1 1994/12/14 17:56:06 fritz + * Initial revision + * + */ + +#include "icn.h" + +/* + * Verbose bootcode- and protocol-downloading. + */ +#undef BOOT_DEBUG + +/* + * Verbose Shmem-Mapping. + */ +#undef MAP_DEBUG + +static char +*revision = "$Revision: 1.29 $"; + +static int icn_addcard(int, char *, char *); + +/* + * Free queue completely. + * Parameter: + * queue = pointer to queue-head + */ +static void icn_free_queue(struct sk_buff_head *queue) +{ + struct sk_buff *skb; + unsigned long flags; + + save_flags(flags); + cli(); + while ((skb = skb_dequeue(queue))) + dev_kfree_skb(skb, FREE_WRITE); + restore_flags(flags); +} + +/* 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) + */ +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. + */ +static inline int icn_lock_channel(icn_card *card, int channel) +{ + register int retval; + ulong flags; + +#ifdef MAP_DEBUG + printk(KERN_DEBUG "icn_lock_channel %d\n", channel); +#endif + save_flags(flags); + cli(); + 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, device->channel); +#endif + } + restore_flags(flags); + return retval; +} + +/* + * Release current card/channel lock + */ +static inline void icn_release_channel(void) +{ + ulong flags; + +#ifdef MAP_DEBUG + printk(KERN_DEBUG "icn_release_channel l=%d\n", device->chanlock); +#endif + save_flags(flags); + cli(); + if (dev.chanlock) + dev.chanlock--; + restore_flags(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; + + save_flags(flags); + cli(); +#ifdef MAP_DEBUG + printk(KERN_DEBUG "trymaplock c=%d dc=%d l=%d\n", channel, dev.channel, + dev.chanlock); +#endif + if ((!dev.chanlock) || + ((dev.channel == channel) && (dev.mcard == card))) { + dev.chanlock++; + icn_map_channel(card,channel); + restore_flags(flags); +#ifdef MAP_DEBUG + printk(KERN_DEBUG "trymaplock %d OK\n", channel); +#endif + return 1; + } + restore_flags(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; + + save_flags(flags); + cli(); +#ifdef MAP_DEBUG + printk(KERN_DEBUG "map_release c=%d l=%d\n", channel, dev.chanlock); +#endif + if (dev.chanlock) + dev.chanlock--; + if (!dev.chanlock) + icn_map_channel(card,channel); + restore_flags(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 "ïcn: 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]) + return; + if (icn_trymaplock_channel(card,mch)) { + while (sbfree && card->sndcount[channel]) { + save_flags(flags); + cli(); + if (card->xmit_lock[channel]) { + restore_flags(flags); + break; + } + card->xmit_lock[channel]++; + restore_flags(flags); + skb = skb_dequeue(&card->spqueue[channel]); + 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); + card->sndcount[channel] -= cnt; + sbnext; /* switch to next buffer */ + icn_maprelease_channel(card, mch & 2); + if (!skb->len) { + dev_kfree_skb(skb, FREE_WRITE); + cmd.command = ISDN_STAT_BSENT; + cmd.driver = card->myid; + cmd.arg = channel; + card->interface.statcallb(&cmd); + } else + skb_queue_head(&card->spqueue[channel], skb); + card->xmit_lock[channel] = 0; + 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 */ + save_flags(flags); + cli(); + del_timer(&card->rb_timer); + card->rb_timer.expires = jiffies + ICN_TIMER_BCREAD; + add_timer(&card->rb_timer); + card->flags |= ICN_FLAGS_RBTIMER; + restore_flags(flags); + } else + card->flags &= ~ICN_FLAGS_RBTIMER; +} + +typedef struct icn_stat { + char *statstr; + int command; + int action; +} icn_stat; + +static icn_stat icn_stat_table[] = { + {"BCON_", ISDN_STAT_BCONN, 1}, /* B-Channel connected */ + {"BDIS_", ISDN_STAT_BHUP, 2}, /* B-Channel disconnected */ + {"DCON_", ISDN_STAT_DCONN, 0}, /* D-Channel connected */ + {"DDIS_", ISDN_STAT_DHUP, 0}, /* 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 */ + {"NO D-CHAN", ISDN_STAT_NODCH, 0}, /* No D-channel available */ + {"E_L1: ACT FAIL", ISDN_STAT_BHUP, 8}, /* Layer-1 activation failed */ + {NULL, 0 , -1} +}; + +/* + * 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 int icn_parse_status(u_char *status, int channel, icn_card *card) +{ + icn_stat *s = icn_stat_table; + int action = -1; + int dflag = 0; + 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 0; + cmd.driver = card->myid; + cmd.arg = channel; + switch (action) { + case 1: + card->flags |= (channel)? + ICN_FLAGS_B2ACTIVE:ICN_FLAGS_B1ACTIVE; + break; + case 2: + card->flags &= ~((channel)? + ICN_FLAGS_B2ACTIVE:ICN_FLAGS_B1ACTIVE); + icn_free_queue(&card->spqueue[channel]); + save_flags(flags); + cli(); + card->rcvidx[channel] = 0; + restore_flags(flags); + dflag |= (channel+1); + break; + case 3: + strncpy(cmd.num, status + 6, sizeof(cmd.num) - 1); + break; + case 4: + sprintf(cmd.num,"LEASED%d,07,00,%d", + card->myid,channel+1); + break; + case 5: + strncpy(cmd.num, status + 3, sizeof(cmd.num) - 1); + break; + case 6: + sprintf(cmd.num,"%d", + (int)simple_strtoul(status + 7,NULL,16)); + break; + case 7: + status += 3; + if (strlen(status)==4) + sprintf(cmd.num,"%s%c%c", + status+2,*status,*(status+1)); + else + strncpy(cmd.num, status+1, sizeof(cmd.num) - 1); + break; + case 8: + 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; + 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 dflag; +} + +static void icn_polldchan(unsigned long data) +{ + icn_card *card = (icn_card *)data; + int mch = card->secondhalf ? 2 : 0; + int avail = 0; + int dflag = 0; + int left; + u_char c; + int ch; + int 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]); + save_flags(flags); + cli(); + *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; + restore_flags(flags); + 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]; + dflag |= 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); + } + if (dflag & 1) + card->interface.rcvcallb(card->myid, 0, card->rcvbuf[0], 0); + if (dflag & 2) + card->interface.rcvcallb(card->myid, 1, card->rcvbuf[1], 0); + if (card->flags & (ICN_FLAGS_B1ACTIVE | ICN_FLAGS_B2ACTIVE)) + if (!(card->flags & ICN_FLAGS_RBTIMER)) { + /* schedule b-channel polling */ + card->flags |= ICN_FLAGS_RBTIMER; + save_flags(flags); + cli(); + 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); + restore_flags(flags); + } + /* schedule again */ + save_flags(flags); + cli(); + del_timer(&card->st_timer); + card->st_timer.expires = jiffies + ICN_TIMER_DCREAD; + add_timer(&card->st_timer); + restore_flags(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, 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; + save_flags(flags); + cli(); + nskb = skb_clone(skb, GFP_ATOMIC); + if (nskb) { + skb_queue_tail(&card->spqueue[channel], nskb); + dev_kfree_skb(skb, FREE_WRITE); + } else + len = 0; + card->sndcount[channel] += len; + restore_flags(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 + current->state = TASK_INTERRUPTIBLE; + current->timeout = jiffies + ICN_BOOT_TIMEOUT1; + schedule(); + } 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) { \ + current->state = TASK_INTERRUPTIBLE; \ + current->timeout = jiffies + HZ; \ + schedule(); \ + slsec--; \ + } \ +} +#else +#define SLEEP(sec) +#endif + +static int icn_loadboot(u_char * buffer, icn_card * card) +{ + int ret; + ulong flags; + u_char *codebuf; + +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "icn_loadboot called, buffaddr=%08lx\n", (ulong) buffer); +#endif + if ((ret = verify_area(VERIFY_READ, (void *) buffer, ICN_CODE_STAGE1))) + return ret; + if (!(codebuf = kmalloc(ICN_CODE_STAGE1,GFP_KERNEL))) { + printk(KERN_WARNING "icn: Could not allocate code buffer\n"); + return -ENOMEM; + } + save_flags(flags); + cli(); + if (!card->rvalid) { + if (check_region(card->port, ICN_PORTLEN)) { + printk(KERN_WARNING + "icn: (%s) ports 0x%03x-0x%03x in use.\n", + CID, + card->port, + card->port + ICN_PORTLEN); + restore_flags(flags); + kfree(codebuf); + return -EBUSY; + } + request_region(card->port, ICN_PORTLEN, card->regname); + card->rvalid = 1; + if (card->doubleS0) + card->other->rvalid = 1; + } + if (!dev.mvalid) { + if (check_shmem((ulong) dev.shmem, 0x4000)) { + printk(KERN_WARNING + "icn: memory at 0x%08lx in use.\n", + (ulong) dev.shmem); + restore_flags(flags); + return -EBUSY; + } + request_shmem((ulong) dev.shmem, 0x4000, "icn"); + dev.mvalid = 1; + } + restore_flags(flags); + 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, (unsigned long) dev.shmem, 23, 10); /* Set RAM-Addr. */ +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "shmem=%08lx\n", (ulong) dev.shmem); +#endif + SLEEP(1); + save_flags(flags); + cli(); +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "Map Bank 0\n"); +#endif + icn_map_channel(card,0); /* Select Bank 0 */ + icn_lock_channel(card,0); /* Lock Bank 0 */ + restore_flags(flags); + SLEEP(1); + if (copy_from_user(codebuf, buffer, ICN_CODE_STAGE1)) + return -EFAULT; + memcpy_toio(dev.shmem, codebuf, ICN_CODE_STAGE1); /* Copy code */ +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "Bootloader transfered\n"); +#endif + if (card->doubleS0) { + SLEEP(1); + save_flags(flags); + cli(); + icn_release_channel(); +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "Map Bank 8\n"); +#endif + icn_map_channel(card,2); /* Select Bank 8 */ + icn_lock_channel(card,2); /* Lock Bank 8 */ + restore_flags(flags); + SLEEP(1); + memcpy_toio(dev.shmem, codebuf, ICN_CODE_STAGE1); /* Copy code */ +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "Bootloader transfered\n"); +#endif + } + kfree(codebuf); + SLEEP(1); + OUTB_P(0xff, ICN_RUN); /* Start Boot-Code */ + if ((ret = icn_check_loader(card->doubleS0 ? 2 : 1))) + return ret; + if (!card->doubleS0) + return 0; + /* reached only, if we have a Double-S0-Card */ + save_flags(flags); + cli(); +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "Map Bank 0\n"); +#endif + icn_map_channel(card,0); /* Select Bank 0 */ + icn_lock_channel(card,0); /* Lock Bank 0 */ + restore_flags(flags); + SLEEP(1); + return (icn_check_loader(1)); +} + +static int icn_loadproto(u_char * buffer, icn_card * card) +{ + register u_char *p = buffer; + u_char codebuf[256]; + uint left = ICN_CODE_STAGE2; + uint cnt; + int timer; + int ret; + unsigned long flags; + +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "icn_loadproto called\n"); +#endif + if ((ret = verify_area(VERIFY_READ, (void *) buffer, ICN_CODE_STAGE2))) + return ret; + timer = 0; + save_flags(flags); + cli(); + if (card->secondhalf) { + icn_map_channel(card, 2); + icn_lock_channel(card, 2); + } else { + icn_map_channel(card, 0); + icn_lock_channel(card, 0); + } + restore_flags(flags); + while (left) { + if (sbfree) { /* If there is a free buffer... */ + cnt = MIN(256, left); + if (copy_from_user(codebuf, p, cnt)) + /* FIXME -WRONG */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; + } + current->state = TASK_INTERRUPTIBLE; + current->timeout = jiffies + 10; + schedule(); + } + } + 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 + current->state = TASK_INTERRUPTIBLE; + current->timeout = jiffies + ICN_BOOT_TIMEOUT1; + schedule(); + } else { + if ((card->secondhalf) || (!card->doubleS0)) { + save_flags(flags); + cli(); +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "Proto loaded, install poll-timer %d\n", + card->secondhalf); +#endif + 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; + } + restore_flags(flags); + } + icn_maprelease_channel(card, 0); + return 0; + } + } +} + +/* Read the Status-replies from the Interface */ +static int icn_readstatus(u_char * buf, int len, int user, icn_card * card) +{ + int count; + u_char *p; + + for (p = buf, count = 0; count < len; p++, count++) { + if (card->msg_buf_read == card->msg_buf_write) + return count; + if (user) + put_user(*card->msg_buf_read++, p); + else + *p = *card->msg_buf_read++; + 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 waitflg) +{ + int mch = card->secondhalf ? 2 : 0; + int avail; + int pp; + int i; + int count; + int ocount; + unsigned long flags; + u_char *p; + isdn_ctrl cmd; + u_char msg[0x100]; + + while (1) { + if (icn_trymaplock_channel(card, mch)) { + avail = cmd_free; + count = MIN(avail, len); + if (user) + { + if (copy_from_user(msg, buf, count) != 0) + { + icn_release_channel(); + return -EFAULT; + } + } + else + memcpy(msg, buf, count); + save_flags(flags); + cli(); + ocount = 1; + *card->msg_buf_write++ = '>'; + if (card->msg_buf_write > card->msg_buf_end) + card->msg_buf_write = card->msg_buf; + 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]); + *card->msg_buf_write++ = *p; + if ((*p == '\n') && (i > 1)) { + *card->msg_buf_write++ = '>'; + if (card->msg_buf_write > card->msg_buf_end) + card->msg_buf_write = card->msg_buf; + ocount++; + } + /* No checks for buffer overflow of raw-status-device */ + if (card->msg_buf_write > card->msg_buf_end) + card->msg_buf_write = card->msg_buf; + ocount++; + } + restore_flags(flags); + cmd.command = ISDN_STAT_STAVAIL; + cmd.driver = card->myid; + cmd.arg = ocount; + card->interface.statcallb(&cmd); + writeb((readb(&cmd_i) + count) & 0xff, &cmd_i); + icn_release_channel(); + waitflg = 0; + } else + count = 0; + if (!waitflg) + break; + current->timeout = jiffies + 10; + schedule(); + } + return count; +} + +/* + * Delete card's pending timers, send STOP to linklevel + */ +static void icn_stopcard(icn_card * card) +{ + unsigned long flags; + isdn_ctrl cmd; + + save_flags(flags); + cli(); + if (card->flags & ICN_FLAGS_RUNNING) { + card->flags &= ~ICN_FLAGS_RUNNING; + del_timer(&card->st_timer); + del_timer(&card->rb_timer); + cmd.command = ISDN_STAT_STOP; + cmd.driver = card->myid; + card->interface.statcallb(&cmd); + if (card->doubleS0) + icn_stopcard(card->other); + } + restore_flags(flags); +} + +static void icn_stopallcards(void) +{ + icn_card *p = cards; + + while (p) { + icn_stopcard(p); + p = p->next; + } +} + +static int icn_command(isdn_ctrl * c, icn_card * card) +{ + ulong a; + ulong flags; + int i; + char cbuf[60]; + isdn_ctrl cmd; + icn_cdef cdef; + + switch (c->command) { + case ISDN_CMD_IOCTL: + memcpy(&a, c->num, sizeof(ulong)); + switch (c->arg) { + case ICN_IOCTL_SETMMIO: + if ((unsigned long) dev.shmem != (a & 0x0ffc000)) { + if (check_shmem((ulong) (a & 0x0ffc000), 0x4000)) { + printk(KERN_WARNING + "icn: memory at 0x%08lx in use.\n", + (ulong) (a & 0x0ffc000)); + return -EINVAL; + } + icn_stopallcards(); + save_flags(flags); + cli(); + if (dev.mvalid) + release_shmem((ulong) dev.shmem, 0x4000); + dev.mvalid = 0; + dev.shmem = (icn_shmem *) (a & 0x0ffc000); + restore_flags(flags); + printk(KERN_INFO + "icn: (%s) mmio set to 0x%08lx\n", + CID, + (unsigned long) dev.shmem); + } + break; + case ICN_IOCTL_GETMMIO: + return (long) dev.shmem; + 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 (check_region((unsigned short) a, ICN_PORTLEN)) { + printk(KERN_WARNING + "icn: (%s) ports 0x%03x-0x%03x in use.\n", + CID, (int) a, (int) a + ICN_PORTLEN); + return -EINVAL; + } + icn_stopcard(card); + save_flags(flags); + cli(); + 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; + } + restore_flags(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 ((i = verify_area(VERIFY_WRITE, + (void *) a, + sizeof(ulong) * 2))) + return i; + if (copy_to_user((char *)a, + (char *)&card, sizeof(ulong))) + return -EFAULT; + a += sizeof(ulong); + { + ulong l = (ulong)&dev; + if (copy_to_user((char *)a, + (char *)&l, sizeof(ulong))) + return -EFAULT; + } + return 0; + case ICN_IOCTL_LOADBOOT: + icn_stopcard(card); + return (icn_loadboot((u_char *) a, card)); + case ICN_IOCTL_LOADPROTO: + icn_stopcard(card); + if ((i = (icn_loadproto((u_char *) a, card)))) + return i; + if (card->doubleS0) + i = icn_loadproto((u_char *) (a + ICN_CODE_STAGE2), card->other); + return i; + break; + case ICN_IOCTL_ADDCARD: + if ((i = verify_area(VERIFY_READ, (void *) a, sizeof(icn_cdef)))) + return i; + if (copy_from_user((char *)&cdef, (char *)a, 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) { + current->timeout = jiffies + ICN_BOOT_TIMEOUT1; + schedule(); + } + current->timeout = jiffies + ICN_BOOT_TIMEOUT1; + schedule(); + sprintf(cbuf, "00;FV2ON\n01;EAZ1\n"); + i = icn_writecmd(cbuf, strlen(cbuf), 0, card, 1); + 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, 1); + 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 *p2; + char dial[50]; + char sis[50]; + char dcode[4]; + int si1, si2; + + a = c->arg; + strcpy(sis, c->num); + p = strrchr(sis, ','); + *p++ = '\0'; + si2 = simple_strtoul(p,NULL,10); + p = strrchr(sis, ',') + 1; + si1 = simple_strtoul(p,NULL,10); + p = c->num; + if (*p == 's' || *p == 'S') { + /* Dial for SPV */ + p++; + strcpy(dcode, "SCA"); + } else + /* Normal Dial */ + strcpy(dcode, "CAL"); + strcpy(dial, p); + p = strchr(dial, ','); + *p++ = '\0'; + p2 = strchr(p, ','); + *p2 = '\0'; + sprintf(cbuf, "%02d;D%s_R%s,%02d,%02d,%s\n", (int) (a + 1), dcode, dial, si1, + si2, p); + i = icn_writecmd(cbuf, strlen(cbuf), 0, card, 1); + } + 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, 1); + } + sprintf(cbuf, "%02d;DCON_R\n", (int) a); + i = icn_writecmd(cbuf, strlen(cbuf), 0, card, 1); + } + 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, 1); + } + 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, 1); + } + 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->num[0] ? "N" : "ALL", c->num); + } else + sprintf(cbuf, "%02d;EAZ%s\n", (int) a, + c->num[0] ? c->num : "0123456789"); + i = icn_writecmd(cbuf, strlen(cbuf), 0, card, 1); + } + 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, 1); + } + 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, 1); + card->l2_proto[a & 255] = (a >> 8); + } + break; + case ISDN_CMD_GETL2: + if (!card->flags & ICN_FLAGS_RUNNING) + return -ENODEV; + if ((c->arg & 255) < ICN_BCH) + return card->l2_proto[c->arg & 255]; + else + return -ENODEV; + case ISDN_CMD_SETL3: + if (!card->flags & ICN_FLAGS_RUNNING) + return -ENODEV; + return 0; + case ISDN_CMD_GETL3: + if (!card->flags & ICN_FLAGS_RUNNING) + return -ENODEV; + if ((c->arg & 255) < ICN_BCH) + return ISDN_PROTO_L3_TRANS; + else + return -ENODEV; + case ISDN_CMD_GETEAZ: + if (!card->flags & ICN_FLAGS_RUNNING) + return -ENODEV; + break; + case ISDN_CMD_SETSIL: + if (!card->flags & ICN_FLAGS_RUNNING) + return -ENODEV; + break; + case ISDN_CMD_GETSIL: + if (!card->flags & ICN_FLAGS_RUNNING) + return -ENODEV; + break; + case ISDN_CMD_LOCK: + MOD_INC_USE_COUNT; + break; + case ISDN_CMD_UNLOCK: + MOD_DEC_USE_COUNT; + break; + 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 * buf, int len, int user, 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, user, card, 0)); + } + printk(KERN_ERR + "icn: if_writecmd called with invalid driverId!\n"); + return -ENODEV; +} + +static int if_readstatus(u_char * buf, int len, int user, 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, user, card)); + } + printk(KERN_ERR + "icn: if_readstatus called with invalid driverId!\n"); + return -ENODEV; +} + +static int if_sendbuf(int id, int channel, struct sk_buff *skb) +{ + icn_card *card = icn_findcard(id); + + if (card) { + if (!card->flags & ICN_FLAGS_RUNNING) + return -ENODEV; + return (icn_sendbuf(channel, 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 = (icn_card *) kmalloc(sizeof(icn_card), GFP_KERNEL))) { + printk(KERN_WARNING + "icn: (%s) Could not allocate card-struct.\n", id); + return (icn_card *)0; + } + memset((char *) card, 0, sizeof(icn_card)); + card->port = port; + 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; + strncpy(card->interface.id, id, sizeof(card->interface.id) - 1); + 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) +{ + ulong flags; + icn_card *card; + icn_card *card2; + + save_flags(flags); + cli(); + if (!(card = icn_initcard(port,id1))) { + restore_flags(flags); + return -EIO; + } + if (!strlen(id2)) { + restore_flags(flags); + printk(KERN_INFO + "icn: (%s) ICN-2B, port 0x%x added\n", + card->interface.id, port); + return 0; + } + if (!(card2 = icn_initcard(port,id2))) { + restore_flags(flags); + printk(KERN_INFO + "icn: (%s) half ICN-4B, port 0x%x added\n", + card2->interface.id, port); + return 0; + } + card->doubleS0 = 1; + card->secondhalf = 0; + card->other = card2; + card2->doubleS0 = 1; + card2->secondhalf = 1; + card2->other = card; + restore_flags(flags); + printk(KERN_INFO + "icn: (%s and %s) ICN-4B, port 0x%x added\n", + card->interface.id, card2->interface.id, port); + return 0; +} + +#ifdef MODULE +#define icn_init init_module +#else +void icn_setup(char *str, int *ints) +{ + char *p; + static char sid[20]; + static char sid2[20]; + + if (ints[0]) + portbase = ints[1]; + if (ints[0]>1) + membase = ints[2]; + if (strlen(str)) { + strcpy(sid,str); + icn_id = sid; + if ((p = strchr(sid,','))) { + *p++ = 0; + strcpy(sid2,p); + icn_id2 = sid2; + } + } +} +#endif + +int icn_init(void) +{ + char *p; + char rev[10]; + + memset(&dev, 0, sizeof(icn_dev)); + dev.shmem = (icn_shmem *) ((unsigned long)membase & 0x0ffc000); + dev.channel = -1; + dev.mcard = NULL; + + /* No symbols to export, hide all symbols */ + register_symtab(NULL); + + if ((p = strchr(revision, ':'))) { + strcpy(rev, p + 1); + p = strchr(rev, '$'); + *p = 0; + } else + strcpy(rev, " ??? "); + printk(KERN_NOTICE "ICN-ISDN-driver Rev%smem=0x%08lx\n", rev, + (ulong) dev.shmem); + return (icn_addcard(portbase,icn_id,icn_id2)); +} + +#ifdef MODULE +void cleanup_module(void) +{ + isdn_ctrl cmd; + icn_card *card = cards; + icn_card *last; + int i; + + icn_stopallcards(); + while (card) { + cmd.command = ISDN_STAT_UNLOAD; + cmd.driver = card->myid; + card->interface.statcallb(&cmd); + 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->spqueue[i]); + } + card = card->next; + } + card = cards; + while (card) { + last = card; + card = card->next; + kfree(last); + } + if (dev.mvalid) + release_shmem((ulong) dev.shmem, 0x4000); + printk(KERN_NOTICE "ICN-ISDN-driver unloaded\n"); +} +#endif diff --git a/drivers/isdn/icn/icn.h b/drivers/isdn/icn/icn.h new file mode 100644 index 000000000..655117f42 --- /dev/null +++ b/drivers/isdn/icn/icn.h @@ -0,0 +1,358 @@ +/* $Id: icn.h,v 1.21 1996/08/29 20:35:57 fritz Exp $ + * + * ISDN lowlevel-module for the ICN active ISDN-Card. + * + * Copyright 1994 by Fritz Elfert (fritz@wuemaus.franken.de) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Log: icn.h,v $ + * Revision 1.21 1996/08/29 20:35:57 fritz + * Speed up B-Channel polling interval. + * + * Revision 1.20 1996/06/24 17:20:37 fritz + * Bugfixes in pollbchan_send(): + * - Using lock field of skbuff breaks networking. + * - Added channel locking + * - changed dequeuing scheme. + * Eliminated misc. compiler warnings. + * + * Revision 1.19 1996/06/06 13:58:35 fritz + * Changed code to be architecture independent + * + * Revision 1.18 1996/06/03 19:59:30 fritz + * Removed include of config.h + * + * Revision 1.17 1996/05/18 00:47:04 fritz + * Removed callback debug code. + * + * Revision 1.16 1996/05/17 15:46:43 fritz + * Removed own queue management. + * Changed queue management to use sk_buffs. + * + * Revision 1.15 1996/05/02 04:01:57 fritz + * Removed ICN_MAXCARDS + * + * Revision 1.14 1996/05/02 00:40:29 fritz + * Major rewrite to support more than one card + * with a single module. + * Support for new firmware. + * + * Revision 1.13 1996/04/20 16:51:41 fritz + * Increased status buffer. + * Misc. typos + * + * Revision 1.12 1996/01/22 05:01:22 fritz + * Revert to GPL. + * + * Revision 1.11 1995/12/18 18:25:00 fritz + * Support for ICN-2B Cards. + * Change for supporting user-settable service-octet. + * + * Revision 1.10 1995/10/29 21:43:10 fritz + * Added support for leased lines. + * + * Revision 1.9 1995/04/23 13:42:10 fritz + * Added some constants for distinguishing 1TR6 and DSS1 + * + * Revision 1.8 1995/03/25 23:18:55 fritz + * Changed ICN_PORTLEN to reflect correct number of ports. + * + * Revision 1.7 1995/03/15 12:52:06 fritz + * Some cleanup + * + * Revision 1.6 1995/02/20 03:49:22 fritz + * Fixed ICN_MAX_SQUEUE to correctly reflect outstanding bytes, not number + * of buffers. + * + * Revision 1.5 1995/01/29 23:36:50 fritz + * Minor cleanup. + * + * Revision 1.4 1995/01/09 07:41:20 fritz + * Added GPL-Notice + * + * Revision 1.3 1995/01/04 05:14:20 fritz + * removed include of linux/asm/string.h for compiling with Linux 1.1.76 + * + * Revision 1.2 1995/01/02 02:15:57 fritz + * Misc. Bugfixes + * + * Revision 1.1 1994/12/14 18:02:38 fritz + * Initial revision + * + */ + +#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/module.h> +#include <linux/version.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/major.h> +#include <asm/segment.h> +#include <asm/io.h> +#include <asm/uaccess.h> +#include <linux/kernel.h> +#include <linux/signal.h> +#include <linux/malloc.h> +#include <linux/mm.h> +#include <linux/mman.h> +#include <linux/ioport.h> +#include <linux/timer.h> +#include <linux/wait.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 100 /* Delay for Boot-download (jiffies) */ +#define ICN_CHANLOCK_DELAY 10 /* Delay for Channel-mapping (jiffies) */ + +#define ICN_TIMER_BCREAD 1 /* B-Channel poll-cycle */ +#define ICN_TIMER_DCREAD 50 /* 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 */ + 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() */ +} icn_card; + +/* + * Main driver data + */ +typedef struct icn_dev { + icn_shmem *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 */ +} 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; + +/* With modutils >= 1.1.67 Integers can be changed while loading a + * module. For this reason define the Port-Base an Shmem-Base as + * integers. + */ +int portbase = ICN_BASEADDR; +int membase = ICN_MEMADDR; +char *icn_id = "\0"; +char *icn_id2 = "\0"; + +#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) + +#define MIN(a,b) ((a<b)?a:b) +#define MAX(a,b) ((a>b)?a:b) + +/* Hopefully, a separate resource-registration-scheme for shared-memory + * will be introduced into the kernel. Until then, we use the normal + * routines, designed for port-registration. + */ +#define check_shmem check_region +#define release_shmem release_region +#define request_shmem request_region + +#endif /* defined(__KERNEL__) || defined(__DEBUGVAR__) */ +#endif /* icn_h */ diff --git a/drivers/isdn/isdn_audio.c b/drivers/isdn/isdn_audio.c new file mode 100644 index 000000000..a356b512e --- /dev/null +++ b/drivers/isdn/isdn_audio.c @@ -0,0 +1,622 @@ +/* $Id: isdn_audio.c,v 1.6 1996/06/06 14:43:31 fritz Exp $ + * + * Linux ISDN subsystem, audio conversion and compression (linklevel). + * + * Copyright 1994,95,96 by Fritz Elfert (fritz@wuemaus.franken.de) + * DTMF code (c) 1996 by Christian Mock (cm@kukuruz.ping.at) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Log: isdn_audio.c,v $ + * Revision 1.6 1996/06/06 14:43:31 fritz + * Changed to support DTMF decoding on audio playback also. + * + * Revision 1.5 1996/06/05 02:24:08 fritz + * Added DTMF decoder for audio mode. + * + * Revision 1.4 1996/05/17 03:48:01 fritz + * Removed some test statements. + * Added revision string. + * + * Revision 1.3 1996/05/10 08:48:11 fritz + * Corrected adpcm bugs. + * + * Revision 1.2 1996/04/30 09:31:17 fritz + * General rewrite. + * + * Revision 1.1.1.1 1996/04/28 12:25:40 fritz + * Taken under CVS control + * + */ + +#define __NO_VERSION__ +#include <linux/module.h> +#include <linux/isdn.h> +#include "isdn_audio.h" +#include "isdn_common.h" + +char *isdn_audio_revision = "$Revision: 1.6 $"; + +/* + * Misc. lookup-tables. + */ + +/* ulaw -> signed 16-bit */ +static short isdn_audio_ulaw_to_s16[] = { + 0x8284, 0x8684, 0x8a84, 0x8e84, 0x9284, 0x9684, 0x9a84, 0x9e84, + 0xa284, 0xa684, 0xaa84, 0xae84, 0xb284, 0xb684, 0xba84, 0xbe84, + 0xc184, 0xc384, 0xc584, 0xc784, 0xc984, 0xcb84, 0xcd84, 0xcf84, + 0xd184, 0xd384, 0xd584, 0xd784, 0xd984, 0xdb84, 0xdd84, 0xdf84, + 0xe104, 0xe204, 0xe304, 0xe404, 0xe504, 0xe604, 0xe704, 0xe804, + 0xe904, 0xea04, 0xeb04, 0xec04, 0xed04, 0xee04, 0xef04, 0xf004, + 0xf0c4, 0xf144, 0xf1c4, 0xf244, 0xf2c4, 0xf344, 0xf3c4, 0xf444, + 0xf4c4, 0xf544, 0xf5c4, 0xf644, 0xf6c4, 0xf744, 0xf7c4, 0xf844, + 0xf8a4, 0xf8e4, 0xf924, 0xf964, 0xf9a4, 0xf9e4, 0xfa24, 0xfa64, + 0xfaa4, 0xfae4, 0xfb24, 0xfb64, 0xfba4, 0xfbe4, 0xfc24, 0xfc64, + 0xfc94, 0xfcb4, 0xfcd4, 0xfcf4, 0xfd14, 0xfd34, 0xfd54, 0xfd74, + 0xfd94, 0xfdb4, 0xfdd4, 0xfdf4, 0xfe14, 0xfe34, 0xfe54, 0xfe74, + 0xfe8c, 0xfe9c, 0xfeac, 0xfebc, 0xfecc, 0xfedc, 0xfeec, 0xfefc, + 0xff0c, 0xff1c, 0xff2c, 0xff3c, 0xff4c, 0xff5c, 0xff6c, 0xff7c, + 0xff88, 0xff90, 0xff98, 0xffa0, 0xffa8, 0xffb0, 0xffb8, 0xffc0, + 0xffc8, 0xffd0, 0xffd8, 0xffe0, 0xffe8, 0xfff0, 0xfff8, 0x0000, + 0x7d7c, 0x797c, 0x757c, 0x717c, 0x6d7c, 0x697c, 0x657c, 0x617c, + 0x5d7c, 0x597c, 0x557c, 0x517c, 0x4d7c, 0x497c, 0x457c, 0x417c, + 0x3e7c, 0x3c7c, 0x3a7c, 0x387c, 0x367c, 0x347c, 0x327c, 0x307c, + 0x2e7c, 0x2c7c, 0x2a7c, 0x287c, 0x267c, 0x247c, 0x227c, 0x207c, + 0x1efc, 0x1dfc, 0x1cfc, 0x1bfc, 0x1afc, 0x19fc, 0x18fc, 0x17fc, + 0x16fc, 0x15fc, 0x14fc, 0x13fc, 0x12fc, 0x11fc, 0x10fc, 0x0ffc, + 0x0f3c, 0x0ebc, 0x0e3c, 0x0dbc, 0x0d3c, 0x0cbc, 0x0c3c, 0x0bbc, + 0x0b3c, 0x0abc, 0x0a3c, 0x09bc, 0x093c, 0x08bc, 0x083c, 0x07bc, + 0x075c, 0x071c, 0x06dc, 0x069c, 0x065c, 0x061c, 0x05dc, 0x059c, + 0x055c, 0x051c, 0x04dc, 0x049c, 0x045c, 0x041c, 0x03dc, 0x039c, + 0x036c, 0x034c, 0x032c, 0x030c, 0x02ec, 0x02cc, 0x02ac, 0x028c, + 0x026c, 0x024c, 0x022c, 0x020c, 0x01ec, 0x01cc, 0x01ac, 0x018c, + 0x0174, 0x0164, 0x0154, 0x0144, 0x0134, 0x0124, 0x0114, 0x0104, + 0x00f4, 0x00e4, 0x00d4, 0x00c4, 0x00b4, 0x00a4, 0x0094, 0x0084, + 0x0078, 0x0070, 0x0068, 0x0060, 0x0058, 0x0050, 0x0048, 0x0040, + 0x0038, 0x0030, 0x0028, 0x0020, 0x0018, 0x0010, 0x0008, 0x0000 +}; + +/* alaw -> signed 16-bit */ +static short isdn_audio_alaw_to_s16[] = { + 0x13fc, 0xec04, 0x0144, 0xfebc, 0x517c, 0xae84, 0x051c, 0xfae4, + 0x0a3c, 0xf5c4, 0x0048, 0xffb8, 0x287c, 0xd784, 0x028c, 0xfd74, + 0x1bfc, 0xe404, 0x01cc, 0xfe34, 0x717c, 0x8e84, 0x071c, 0xf8e4, + 0x0e3c, 0xf1c4, 0x00c4, 0xff3c, 0x387c, 0xc784, 0x039c, 0xfc64, + 0x0ffc, 0xf004, 0x0104, 0xfefc, 0x417c, 0xbe84, 0x041c, 0xfbe4, + 0x083c, 0xf7c4, 0x0008, 0xfff8, 0x207c, 0xdf84, 0x020c, 0xfdf4, + 0x17fc, 0xe804, 0x018c, 0xfe74, 0x617c, 0x9e84, 0x061c, 0xf9e4, + 0x0c3c, 0xf3c4, 0x0084, 0xff7c, 0x307c, 0xcf84, 0x030c, 0xfcf4, + 0x15fc, 0xea04, 0x0164, 0xfe9c, 0x597c, 0xa684, 0x059c, 0xfa64, + 0x0b3c, 0xf4c4, 0x0068, 0xff98, 0x2c7c, 0xd384, 0x02cc, 0xfd34, + 0x1dfc, 0xe204, 0x01ec, 0xfe14, 0x797c, 0x8684, 0x07bc, 0xf844, + 0x0f3c, 0xf0c4, 0x00e4, 0xff1c, 0x3c7c, 0xc384, 0x03dc, 0xfc24, + 0x11fc, 0xee04, 0x0124, 0xfedc, 0x497c, 0xb684, 0x049c, 0xfb64, + 0x093c, 0xf6c4, 0x0028, 0xffd8, 0x247c, 0xdb84, 0x024c, 0xfdb4, + 0x19fc, 0xe604, 0x01ac, 0xfe54, 0x697c, 0x9684, 0x069c, 0xf964, + 0x0d3c, 0xf2c4, 0x00a4, 0xff5c, 0x347c, 0xcb84, 0x034c, 0xfcb4, + 0x12fc, 0xed04, 0x0134, 0xfecc, 0x4d7c, 0xb284, 0x04dc, 0xfb24, + 0x09bc, 0xf644, 0x0038, 0xffc8, 0x267c, 0xd984, 0x026c, 0xfd94, + 0x1afc, 0xe504, 0x01ac, 0xfe54, 0x6d7c, 0x9284, 0x06dc, 0xf924, + 0x0dbc, 0xf244, 0x00b4, 0xff4c, 0x367c, 0xc984, 0x036c, 0xfc94, + 0x0f3c, 0xf0c4, 0x00f4, 0xff0c, 0x3e7c, 0xc184, 0x03dc, 0xfc24, + 0x07bc, 0xf844, 0x0008, 0xfff8, 0x1efc, 0xe104, 0x01ec, 0xfe14, + 0x16fc, 0xe904, 0x0174, 0xfe8c, 0x5d7c, 0xa284, 0x05dc, 0xfa24, + 0x0bbc, 0xf444, 0x0078, 0xff88, 0x2e7c, 0xd184, 0x02ec, 0xfd14, + 0x14fc, 0xeb04, 0x0154, 0xfeac, 0x557c, 0xaa84, 0x055c, 0xfaa4, + 0x0abc, 0xf544, 0x0058, 0xffa8, 0x2a7c, 0xd584, 0x02ac, 0xfd54, + 0x1cfc, 0xe304, 0x01cc, 0xfe34, 0x757c, 0x8a84, 0x075c, 0xf8a4, + 0x0ebc, 0xf144, 0x00d4, 0xff2c, 0x3a7c, 0xc584, 0x039c, 0xfc64, + 0x10fc, 0xef04, 0x0114, 0xfeec, 0x457c, 0xba84, 0x045c, 0xfba4, + 0x08bc, 0xf744, 0x0018, 0xffe8, 0x227c, 0xdd84, 0x022c, 0xfdd4, + 0x18fc, 0xe704, 0x018c, 0xfe74, 0x657c, 0x9a84, 0x065c, 0xf9a4, + 0x0cbc, 0xf344, 0x0094, 0xff6c, 0x327c, 0xcd84, 0x032c, 0xfcd4 +}; + +/* alaw -> ulaw */ +static char isdn_audio_alaw_to_ulaw[] = { + 0xab, 0x2b, 0xe3, 0x63, 0x8b, 0x0b, 0xc9, 0x49, + 0xba, 0x3a, 0xf6, 0x76, 0x9b, 0x1b, 0xd7, 0x57, + 0xa3, 0x23, 0xdd, 0x5d, 0x83, 0x03, 0xc1, 0x41, + 0xb2, 0x32, 0xeb, 0x6b, 0x93, 0x13, 0xcf, 0x4f, + 0xaf, 0x2f, 0xe7, 0x67, 0x8f, 0x0f, 0xcd, 0x4d, + 0xbe, 0x3e, 0xfe, 0x7e, 0x9f, 0x1f, 0xdb, 0x5b, + 0xa7, 0x27, 0xdf, 0x5f, 0x87, 0x07, 0xc5, 0x45, + 0xb6, 0x36, 0xef, 0x6f, 0x97, 0x17, 0xd3, 0x53, + 0xa9, 0x29, 0xe1, 0x61, 0x89, 0x09, 0xc7, 0x47, + 0xb8, 0x38, 0xf2, 0x72, 0x99, 0x19, 0xd5, 0x55, + 0xa1, 0x21, 0xdc, 0x5c, 0x81, 0x01, 0xbf, 0x3f, + 0xb0, 0x30, 0xe9, 0x69, 0x91, 0x11, 0xce, 0x4e, + 0xad, 0x2d, 0xe5, 0x65, 0x8d, 0x0d, 0xcb, 0x4b, + 0xbc, 0x3c, 0xfa, 0x7a, 0x9d, 0x1d, 0xd9, 0x59, + 0xa5, 0x25, 0xde, 0x5e, 0x85, 0x05, 0xc3, 0x43, + 0xb4, 0x34, 0xed, 0x6d, 0x95, 0x15, 0xd1, 0x51, + 0xac, 0x2c, 0xe4, 0x64, 0x8c, 0x0c, 0xca, 0x4a, + 0xbb, 0x3b, 0xf8, 0x78, 0x9c, 0x1c, 0xd8, 0x58, + 0xa4, 0x24, 0xde, 0x5e, 0x84, 0x04, 0xc2, 0x42, + 0xb3, 0x33, 0xec, 0x6c, 0x94, 0x14, 0xd0, 0x50, + 0xb0, 0x30, 0xe8, 0x68, 0x90, 0x10, 0xce, 0x4e, + 0xbf, 0x3f, 0xfe, 0x7e, 0xa0, 0x20, 0xdc, 0x5c, + 0xa8, 0x28, 0xe0, 0x60, 0x88, 0x08, 0xc6, 0x46, + 0xb7, 0x37, 0xf0, 0x70, 0x98, 0x18, 0xd4, 0x54, + 0xaa, 0x2a, 0xe2, 0x62, 0x8a, 0x0a, 0xc8, 0x48, + 0xb9, 0x39, 0xf4, 0x74, 0x9a, 0x1a, 0xd6, 0x56, + 0xa2, 0x22, 0xdd, 0x5d, 0x82, 0x02, 0xc0, 0x40, + 0xb1, 0x31, 0xea, 0x6a, 0x92, 0x12, 0xcf, 0x4f, + 0xae, 0x2e, 0xe6, 0x66, 0x8e, 0x0e, 0xcc, 0x4c, + 0xbd, 0x3d, 0xfc, 0x7c, 0x9e, 0x1e, 0xda, 0x5a, + 0xa6, 0x26, 0xdf, 0x5f, 0x86, 0x06, 0xc4, 0x44, + 0xb5, 0x35, 0xee, 0x6e, 0x96, 0x16, 0xd2, 0x52 +}; + +/* ulaw -> alaw */ +static char isdn_audio_ulaw_to_alaw[] = { + 0xab, 0x55, 0xd5, 0x15, 0x95, 0x75, 0xf5, 0x35, + 0xb5, 0x45, 0xc5, 0x05, 0x85, 0x65, 0xe5, 0x25, + 0xa5, 0x5d, 0xdd, 0x1d, 0x9d, 0x7d, 0xfd, 0x3d, + 0xbd, 0x4d, 0xcd, 0x0d, 0x8d, 0x6d, 0xed, 0x2d, + 0xad, 0x51, 0xd1, 0x11, 0x91, 0x71, 0xf1, 0x31, + 0xb1, 0x41, 0xc1, 0x01, 0x81, 0x61, 0xe1, 0x21, + 0x59, 0xd9, 0x19, 0x99, 0x79, 0xf9, 0x39, 0xb9, + 0x49, 0xc9, 0x09, 0x89, 0x69, 0xe9, 0x29, 0xa9, + 0xd7, 0x17, 0x97, 0x77, 0xf7, 0x37, 0xb7, 0x47, + 0xc7, 0x07, 0x87, 0x67, 0xe7, 0x27, 0xa7, 0xdf, + 0x9f, 0x7f, 0xff, 0x3f, 0xbf, 0x4f, 0xcf, 0x0f, + 0x8f, 0x6f, 0xef, 0x2f, 0x53, 0x13, 0x73, 0x33, + 0xb3, 0x43, 0xc3, 0x03, 0x83, 0x63, 0xe3, 0x23, + 0xa3, 0x5b, 0xdb, 0x1b, 0x9b, 0x7b, 0xfb, 0x3b, + 0xbb, 0xbb, 0x4b, 0x4b, 0xcb, 0xcb, 0x0b, 0x0b, + 0x8b, 0x8b, 0x6b, 0x6b, 0xeb, 0xeb, 0x2b, 0x2b, + 0xab, 0x54, 0xd4, 0x14, 0x94, 0x74, 0xf4, 0x34, + 0xb4, 0x44, 0xc4, 0x04, 0x84, 0x64, 0xe4, 0x24, + 0xa4, 0x5c, 0xdc, 0x1c, 0x9c, 0x7c, 0xfc, 0x3c, + 0xbc, 0x4c, 0xcc, 0x0c, 0x8c, 0x6c, 0xec, 0x2c, + 0xac, 0x50, 0xd0, 0x10, 0x90, 0x70, 0xf0, 0x30, + 0xb0, 0x40, 0xc0, 0x00, 0x80, 0x60, 0xe0, 0x20, + 0x58, 0xd8, 0x18, 0x98, 0x78, 0xf8, 0x38, 0xb8, + 0x48, 0xc8, 0x08, 0x88, 0x68, 0xe8, 0x28, 0xa8, + 0xd6, 0x16, 0x96, 0x76, 0xf6, 0x36, 0xb6, 0x46, + 0xc6, 0x06, 0x86, 0x66, 0xe6, 0x26, 0xa6, 0xde, + 0x9e, 0x7e, 0xfe, 0x3e, 0xbe, 0x4e, 0xce, 0x0e, + 0x8e, 0x6e, 0xee, 0x2e, 0x52, 0x12, 0x72, 0x32, + 0xb2, 0x42, 0xc2, 0x02, 0x82, 0x62, 0xe2, 0x22, + 0xa2, 0x5a, 0xda, 0x1a, 0x9a, 0x7a, 0xfa, 0x3a, + 0xba, 0xba, 0x4a, 0x4a, 0xca, 0xca, 0x0a, 0x0a, + 0x8a, 0x8a, 0x6a, 0x6a, 0xea, 0xea, 0x2a, 0x2a +}; + +#define NCOEFF 16 /* number of frequencies to be analyzed */ +#define DTMF_TRESH 50000 /* above this is dtmf */ +#define SILENCE_TRESH 100 /* below this is silence */ +#define H2_TRESH 10000 /* 2nd harmonic */ +#define AMP_BITS 9 /* bits per sample, reduced to avoid overflow */ +#define LOGRP 0 +#define HIGRP 1 + +typedef struct { + int grp; /* low/high group */ + int k; /* k */ + int k2; /* k fuer 2. harmonic */ +} dtmf_t; + +/* For DTMF recognition: + * 2 * cos(2 * PI * k / N) precalculated for all k + */ +static int cos2pik[NCOEFF] = { + 55812, 29528, 53603, 24032, 51193, 14443, 48590, 6517, + 38113, -21204, 33057, -32186, 25889, -45081, 18332, -55279 +}; + +static dtmf_t dtmf_tones[8] = { + { LOGRP, 0, 1 }, /* 697 Hz */ + { LOGRP, 2, 3 }, /* 770 Hz */ + { LOGRP, 4, 5 }, /* 852 Hz */ + { LOGRP, 6, 7 }, /* 941 Hz */ + { HIGRP, 8, 9 }, /* 1209 Hz */ + { HIGRP, 10, 11 }, /* 1336 Hz */ + { HIGRP, 12, 13 }, /* 1477 Hz */ + { HIGRP, 14, 15 } /* 1633 Hz */ +}; + +static char dtmf_matrix[4][4] = { + {'1', '2', '3', 'A'}, + {'4', '5', '6', 'B'}, + {'7', '8', '9', 'C'}, + {'*', '0', '#', 'D'} +}; + +#if ((CPU == 386) || (CPU == 486) || (CPU == 586)) +static inline void +isdn_audio_tlookup(const void *table, void *buff, unsigned long n) +{ + __asm__("cld\n" + "1:\tlodsb\n\t" + "xlatb\n\t" + "stosb\n\t" + "loop 1b\n\t" + ::"b" ((long)table), "c" (n), "D" ((long)buff), "S" ((long)buff) + :"bx","cx","di","si","ax"); +} +#else +static inline void +isdn_audio_tlookup(const char *table, char *buff, unsigned long n) +{ + while (n--) + *buff++ = table[*buff]; +} +#endif + +void +isdn_audio_ulaw2alaw(unsigned char *buff, unsigned long len) +{ + isdn_audio_tlookup(isdn_audio_ulaw_to_alaw, buff, len); +} + +void +isdn_audio_alaw2ulaw(unsigned char *buff, unsigned long len) +{ + isdn_audio_tlookup(isdn_audio_alaw_to_ulaw, buff, len); +} + +/* + * linear <-> adpcm conversion stuff + * Most parts from the mgetty-package. + * (C) by Gert Doering and Klaus Weidner + * Used by permission of Gert Doering + */ + + +#define ZEROTRAP /* turn on the trap as per the MIL-STD */ +#undef ZEROTRAP +#define BIAS 0x84 /* define the add-in bias for 16 bit samples */ +#define CLIP 32635 + +static unsigned char +isdn_audio_linear2ulaw(int sample) { + static int exp_lut[256] = { + 0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3, + 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7 + }; + int sign, exponent, mantissa; + unsigned char ulawbyte; + + /* Get the sample into sign-magnitude. */ + sign = (sample >> 8) & 0x80; /* set aside the sign */ + if(sign != 0) sample = -sample; /* get magnitude */ + if(sample > CLIP) sample = CLIP; /* clip the magnitude */ + + /* Convert from 16 bit linear to ulaw. */ + sample = sample + BIAS; + exponent = exp_lut[( sample >> 7 ) & 0xFF]; + mantissa = (sample >> (exponent + 3)) & 0x0F; + ulawbyte = ~(sign | (exponent << 4) | mantissa); +#ifdef ZEROTRAP + /* optional CCITT trap */ + if (ulawbyte == 0) ulawbyte = 0x02; +#endif + return(ulawbyte); +} + + +static int Mx[3][8] = { + { 0x3800, 0x5600, 0,0,0,0,0,0 }, + { 0x399a, 0x3a9f, 0x4d14, 0x6607, 0,0,0,0 }, + { 0x3556, 0x3556, 0x399A, 0x3A9F, 0x4200, 0x4D14, 0x6607, 0x6607 }, +}; + +static int bitmask[9] = { + 0, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff +}; + +static int +isdn_audio_get_bits (adpcm_state *s, unsigned char **in, int *len) +{ + while( s->nleft < s->nbits) { + int d = *((*in)++); + (*len)--; + s->word = (s->word << 8) | d; + s->nleft += 8; + } + s->nleft -= s->nbits; + return (s->word >> s->nleft) & bitmask[s->nbits]; +} + +static void +isdn_audio_put_bits (int data, int nbits, adpcm_state *s, + unsigned char **out, int *len) +{ + s->word = (s->word << nbits) | (data & bitmask[nbits]); + s->nleft += nbits; + while(s->nleft >= 8) { + int d = (s->word >> (s->nleft-8)); + *(out[0]++) = d & 255; + (*len)++; + s->nleft -= 8; + } +} + +adpcm_state * +isdn_audio_adpcm_init(adpcm_state *s, int nbits) +{ + if (!s) + s = (adpcm_state *) kmalloc(sizeof(adpcm_state), GFP_ATOMIC); + if (s) { + s->a = 0; + s->d = 5; + s->word = 0; + s->nleft = 0; + s->nbits = nbits; + } + return s; +} + +dtmf_state * +isdn_audio_dtmf_init(dtmf_state *s) +{ + if (!s) + s = (dtmf_state *) kmalloc(sizeof(dtmf_state), GFP_ATOMIC); + if (s) { + s->idx = 0; + s->last = ' '; + } + return s; +} + +/* + * Decompression of adpcm data to a/u-law + * + */ + +int +isdn_audio_adpcm2xlaw (adpcm_state *s, int fmt, unsigned char *in, + unsigned char *out, int len) +{ + int a = s->a; + int d = s->d; + int nbits = s->nbits; + int olen = 0; + + while (len) { + int e = isdn_audio_get_bits(s, &in, &len); + int sign; + + if (nbits == 4 && e == 0) + d = 4; + sign = (e >> (nbits-1))?-1:1; + e &= bitmask[nbits-1]; + a += sign * ((e << 1) + 1) * d >> 1; + if (d & 1) + a++; + if (fmt) + *out++ = isdn_audio_ulaw_to_alaw[ + isdn_audio_linear2ulaw(a << 2)]; + else + *out++ = isdn_audio_linear2ulaw(a << 2); + olen++; + d = (d * Mx[nbits-2][ e ] + 0x2000) >> 14; + if ( d < 5 ) + d = 5; + } + s->a = a; + s->d = d; + return olen; +} + +int +isdn_audio_2adpcm_flush (adpcm_state *s, unsigned char *out) +{ + int olen = 0; + + if (s->nleft) + isdn_audio_put_bits(0, 8-s->nleft, s, &out, &olen); + return olen; +} + +int +isdn_audio_xlaw2adpcm (adpcm_state *s, int fmt, unsigned char *in, + unsigned char *out, int len) +{ + int a = s->a; + int d = s->d; + int nbits = s->nbits; + int olen = 0; + + while (len--) { + int e = 0, nmax = 1 << (nbits - 1); + int sign, delta; + + if (fmt) + delta = (isdn_audio_alaw_to_s16[*in++] >> 2) - a; + else + delta = (isdn_audio_ulaw_to_s16[*in++] >> 2) - a; + if (delta < 0) { + e = nmax; + delta = -delta; + } + while( --nmax && delta > d ) { + delta -= d; + e++; + } + if (nbits == 4 && ((e & 0x0f) == 0)) + e = 8; + isdn_audio_put_bits(e, nbits, s, &out, &olen); + sign = (e >> (nbits-1))?-1:1 ; + e &= bitmask[nbits-1]; + + a += sign * ((e << 1) + 1) * d >> 1; + if (d & 1) + a++; + d = (d * Mx[nbits-2][ e ] + 0x2000) >> 14; + if (d < 5) + d=5; + } + s->a = a; + s->d = d; + return olen; +} + +/* + * Goertzel algorithm. + * See http://ptolemy.eecs.berkeley.edu/~pino/Ptolemy/papers/96/dtmf_ict/ + * for more info. + * Result is stored into an sk_buff and queued up for later + * evaluation. + */ +void +isdn_audio_goertzel(int *sample, modem_info *info) { + int sk, sk1, sk2; + int k, n; + struct sk_buff *skb; + int *result; + + skb = dev_alloc_skb(sizeof(int) * NCOEFF); + if (!skb) { + printk(KERN_WARNING + "isdn_audio: Could not alloc DTMF result for ttyI%d\n", + info->line); + return; + } + result = (int *)skb_put(skb, sizeof(int) * NCOEFF); + skb->free = 1; + skb->users = 0; + for (k = 0; k < NCOEFF; k++) { + sk = sk1 = sk2 = 0; + for (n = 0; n < DTMF_NPOINTS; n++) { + sk = sample[n] + ((cos2pik[k] * sk1) >> 15) - sk2; + sk2 = sk1; + sk1 = sk; + } + result[k] = + ((sk * sk) >> AMP_BITS) - + ((((cos2pik[k] * sk) >> 15) * sk2) >> AMP_BITS) + + ((sk2 * sk2) >> AMP_BITS); + } + skb_queue_tail(&info->dtmf_queue, skb); + isdn_timer_ctrl(ISDN_TIMER_MODEMREAD, 1); +} + +void +isdn_audio_eval_dtmf(modem_info *info) +{ + struct sk_buff *skb; + int *result; + dtmf_state *s; + int silence; + int i; + int di; + int ch; + unsigned long flags; + int grp[2]; + char what; + char *p; + + while ((skb = skb_dequeue(&info->dtmf_queue))) { + result = (int *)skb->data; + s = info->dtmf_state; + grp[LOGRP] = grp[HIGRP] = -2; + silence = 0; + for(i = 0; i < 8; i++) { + if ((result[dtmf_tones[i].k] > DTMF_TRESH) && + (result[dtmf_tones[i].k2] < H2_TRESH) ) + grp[dtmf_tones[i].grp] = (grp[dtmf_tones[i].grp] == -2)?i:-1; + else + if ((result[dtmf_tones[i].k] < SILENCE_TRESH) && + (result[dtmf_tones[i].k2] < SILENCE_TRESH) ) + silence++; + } + if(silence == 8) + what = ' '; + else { + if((grp[LOGRP] >= 0) && (grp[HIGRP] >= 0)) { + what = dtmf_matrix[grp[LOGRP]][grp[HIGRP] - 4]; + if(s->last != ' ' && s->last != '.') + s->last = what; /* min. 1 non-DTMF between DTMF */ + } else + what = '.'; + } + if ((what != s->last) && (what != ' ') && (what != '.')) { + printk(KERN_DEBUG "dtmf: tt='%c'\n", what); + p = skb->data; + *p++ = 0x10; + *p = what; + skb_trim(skb, 2); + save_flags(flags); + cli(); + di = info->isdn_driver; + ch = info->isdn_channel; + __skb_queue_tail(&dev->drv[di]->rpqueue[ch], skb); + dev->drv[di]->rcvcount[ch] += 2; + restore_flags(flags); + /* Schedule dequeuing */ + if ((dev->modempoll) && (info->rcvsched)) + isdn_timer_ctrl(ISDN_TIMER_MODEMREAD, 1); + wake_up_interruptible(&dev->drv[di]->rcv_waitq[ch]); + } else + kfree_skb(skb, FREE_READ); + s->last = what; + } +} + +/* + * Decode DTMF tones, queue result in separate sk_buf for + * later examination. + * Parameters: + * s = pointer to state-struct. + * buf = input audio data + * len = size of audio data. + * fmt = audio data format (0 = ulaw, 1 = alaw) + */ +void +isdn_audio_calc_dtmf(modem_info *info, unsigned char *buf, int len, int fmt) +{ + dtmf_state *s = info->dtmf_state; + int i; + int c; + + while (len) { + c = MIN(len, (DTMF_NPOINTS - s->idx)); + if (c <= 0) + break; + for (i = 0; i < c; i++) { + if (fmt) + s->buf[s->idx++] = + isdn_audio_alaw_to_s16[*buf++] >> (15 - AMP_BITS); + else + s->buf[s->idx++] = + isdn_audio_ulaw_to_s16[*buf++] >> (15 - AMP_BITS); + } + if (s->idx == DTMF_NPOINTS) { + isdn_audio_goertzel(s->buf, info); + s->idx = 0; + } + len -= c; + } +} + + diff --git a/drivers/isdn/isdn_audio.h b/drivers/isdn/isdn_audio.h new file mode 100644 index 000000000..a4a5c9a13 --- /dev/null +++ b/drivers/isdn/isdn_audio.h @@ -0,0 +1,59 @@ +/* $Id: isdn_audio.h,v 1.4 1996/06/06 14:43:32 fritz Exp $ + * + * Linux ISDN subsystem, audio conversion and compression (linklevel). + * + * Copyright 1994,95,96 by Fritz Elfert (fritz@wuemaus.franken.de) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Log: isdn_audio.h,v $ + * Revision 1.4 1996/06/06 14:43:32 fritz + * Changed to support DTMF decoding on audio playback also. + * + * Revision 1.3 1996/06/05 02:24:09 fritz + * Added DTMF decoder for audio mode. + * + * Revision 1.2 1996/05/10 08:48:32 fritz + * Corrected adpcm bugs. + * + * Revision 1.1 1996/04/30 09:29:06 fritz + * Taken under CVS control. + * + */ + +#define DTMF_NPOINTS 205 /* Number of samples for DTMF recognition */ +typedef struct adpcm_state { + int a; + int d; + int word; + int nleft; + int nbits; +} adpcm_state; + +typedef struct dtmf_state { + char last; + int idx; + int buf[DTMF_NPOINTS]; +} dtmf_state; + +extern void isdn_audio_ulaw2alaw(unsigned char *, unsigned long); +extern void isdn_audio_alaw2ulaw(unsigned char *, unsigned long); +extern adpcm_state *isdn_audio_adpcm_init(adpcm_state *, int); +extern int isdn_audio_adpcm2xlaw(adpcm_state *, int, unsigned char *, unsigned char *, int); +extern int isdn_audio_xlaw2adpcm(adpcm_state *, int, unsigned char *, unsigned char *, int); +extern int isdn_audio_2adpcm_flush(adpcm_state *s, unsigned char *out); +extern void isdn_audio_calc_dtmf(modem_info *, unsigned char *, int, int); +extern void isdn_audio_eval_dtmf(modem_info *); +dtmf_state *isdn_audio_dtmf_init(dtmf_state *); diff --git a/drivers/isdn/isdn_cards.c b/drivers/isdn/isdn_cards.c new file mode 100644 index 000000000..28ca52c4e --- /dev/null +++ b/drivers/isdn/isdn_cards.c @@ -0,0 +1,53 @@ +/* $Id: isdn_cards.c,v 1.1 1996/04/20 16:04:36 fritz Exp $ + * + * Linux ISDN subsystem, initialization for non-modularized drivers. + * + * Copyright 1994,95,96 by Fritz Elfert (fritz@wuemaus.franken.de) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Log: isdn_cards.c,v $ + * Revision 1.1 1996/04/20 16:04:36 fritz + * Initial revision + * + */ + +#include <linux/config.h> + +#ifdef CONFIG_ISDN_DRV_ICN +extern void icn_init(void); +#endif + +#ifdef CONFIG_ISDN_DRV_TELES +extern void teles_init(void); +#endif + +#ifdef CONFIG_ISDN_DRV_PCBIT +extern void pcbit_init(void); +#endif + +void isdn_cards_init(void) +{ +#if CONFIG_ISDN_DRV_ICN + icn_init(); +#endif +#if CONFIG_ISDN_DRV_TELES + teles_init(); +#endif +#if CONFIG_ISDN_DRV_PCBIT + pcbit_init(); +#endif +} + diff --git a/drivers/isdn/isdn_cards.h b/drivers/isdn/isdn_cards.h new file mode 100644 index 000000000..5bece603b --- /dev/null +++ b/drivers/isdn/isdn_cards.h @@ -0,0 +1,28 @@ +/* $Id: isdn_cards.h,v 1.1 1996/04/20 16:04:03 fritz Exp $ + * + * Linux ISDN subsystem, initialization for non-modularized drivers. + * + * Copyright 1994,95,96 by Fritz Elfert (fritz@wuemaus.franken.de) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Log: isdn_cards.h,v $ + * Revision 1.1 1996/04/20 16:04:03 fritz + * Initial revision + * + */ + +extern void isdn_cards_init(void); + diff --git a/drivers/isdn/isdn_common.c b/drivers/isdn/isdn_common.c new file mode 100644 index 000000000..04415c797 --- /dev/null +++ b/drivers/isdn/isdn_common.c @@ -0,0 +1,2160 @@ +/* $Id: isdn_common.c,v 1.23 1996/06/25 18:35:38 fritz Exp $ + * + * Linux ISDN subsystem, common used functions (linklevel). + * + * Copyright 1994,95,96 by Fritz Elfert (fritz@wuemaus.franken.de) + * Copyright 1995,96 Thinking Objects Software GmbH Wuerzburg + * Copyright 1995,96 by Michael Hipp (Michael.Hipp@student.uni-tuebingen.de) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Log: isdn_common.c,v $ + * Revision 1.23 1996/06/25 18:35:38 fritz + * Fixed bogus memory access in isdn_set_allcfg(). + * + * Revision 1.22 1996/06/24 17:37:37 fritz + * Bugfix: isdn_timer_ctrl() did restart timer, even if it + * was already running. + * lowlevel driver locking did use wrong parameters. + * + * Revision 1.21 1996/06/15 14:58:20 fritz + * Added version signatures for data structures used + * by userlevel programs. + * + * Revision 1.20 1996/06/12 16:01:49 fritz + * Bugfix: Remote B-channel hangup sometimes did not result + * in a NO CARRIER on tty. + * + * Revision 1.19 1996/06/11 14:52:04 hipp + * minor bugfix in isdn_writebuf_skb_stub() + * + * Revision 1.18 1996/06/06 14:51:51 fritz + * Changed to support DTMF decoding on audio playback also. + * + * Revision 1.17 1996/06/05 02:24:10 fritz + * Added DTMF decoder for audio mode. + * + * Revision 1.16 1996/06/03 20:09:05 fritz + * Bugfix: called wrong function pointer for locking in + * isdn_get_free_channel(). + * + * Revision 1.15 1996/05/31 01:10:54 fritz + * Bugfixes: + * Lowlevel modules did not get locked correctly. + * Did show wrong revision when initializing. + * Minor fixes in ioctl code. + * sk_buff did not get freed, if error in writebuf_stub. + * + * Revision 1.14 1996/05/18 01:36:55 fritz + * Added spelling corrections and some minor changes + * to stay in sync with kernel. + * + * Revision 1.13 1996/05/17 15:43:30 fritz + * Bugfix: decrement of rcvcount in readbchan() corrected. + * + * Revision 1.12 1996/05/17 03:55:43 fritz + * Changed DLE handling for audio receive. + * Some cleanup. + * Added display of isdn_audio_revision. + * + * Revision 1.11 1996/05/11 21:51:32 fritz + * Changed queue management to use sk_buffs. + * + * Revision 1.10 1996/05/10 08:49:16 fritz + * Checkin before major changes of tty-code. + * + * Revision 1.9 1996/05/07 09:19:41 fritz + * Adapted to changes in isdn_tty.c + * + * Revision 1.8 1996/05/06 11:34:51 hipp + * fixed a few bugs + * + * Revision 1.7 1996/05/02 03:55:17 fritz + * Bugfixes: + * - B-channel connect message for modem devices + * sometimes did not result in a CONNECT-message. + * - register_isdn did not check for driverId-conflicts. + * + * Revision 1.6 1996/04/30 20:57:21 fritz + * Commit test + * + * Revision 1.5 1996/04/20 16:19:07 fritz + * Changed slow timer handlers to increase accuracy. + * Added statistic information for usage by xisdnload. + * Fixed behaviour of isdnctrl-device on non-blocked io. + * Fixed all io to go through generic writebuf-function without + * bypassing. Same for incoming data. + * Fixed bug: Last channel had been unusable. + * Fixed kfree of tty xmit_buf on ppp initialization failure. + * + * Revision 1.4 1996/02/11 02:33:26 fritz + * Fixed bug in main timer-dispatcher. + * Bugfix: Lot of tty-callbacks got called regardless of the events already + * been handled by network-devices. + * Changed ioctl-names. + * + * Revision 1.3 1996/01/22 05:16:11 fritz + * Changed ioctl-names. + * Fixed bugs in isdn_open and isdn_close regarding PPP_MINOR. + * + * Revision 1.2 1996/01/21 16:52:40 fritz + * Support for sk_buffs added, changed header-stuffing. + * + * Revision 1.1 1996/01/09 04:12:52 fritz + * Initial revision + * + */ + +#include <asm/uaccess.h> +#include <linux/config.h> +#include <linux/module.h> +#include <linux/version.h> +#ifndef __GENKSYMS__ /* Don't want genksyms report unneeded structs */ +#include <linux/isdn.h> +#endif +#include "isdn_common.h" +#include "isdn_tty.h" +#include "isdn_net.h" +#include "isdn_ppp.h" +#ifdef CONFIG_ISDN_AUDIO +#include "isdn_audio.h" +#endif +#include "isdn_cards.h" + +/* Debugflags */ +#undef ISDN_DEBUG_STATCALLB +#define NEW_ISDN_TIMER_CTRL + +isdn_dev *dev = (isdn_dev *) 0; + +static int has_exported = 0; +static char *isdn_revision = "$Revision: 1.23 $"; + +extern char *isdn_net_revision; +extern char *isdn_tty_revision; +#ifdef CONFIG_ISDN_PPP +extern char *isdn_ppp_revision; +#else +static char *isdn_ppp_revision = ": none $"; +#endif +#ifdef CONFIG_ISDN_AUDIO +extern char *isdn_audio_revision; +#else +static char *isdn_audio_revision = ": none $"; +#endif + +void isdn_MOD_INC_USE_COUNT(void) +{ + MOD_INC_USE_COUNT; +} + +void isdn_MOD_DEC_USE_COUNT(void) +{ + MOD_DEC_USE_COUNT; +} + +#if defined(ISDN_DEBUG_NET_DUMP) || defined(ISDN_DEBUG_MODEM_DUMP) +void isdn_dumppkt(char *s, u_char * p, int len, int dumplen) +{ + int dumpc; + + printk(KERN_DEBUG "%s(%d) ", s, len); + for (dumpc = 0; (dumpc < dumplen) && (len); len--, dumpc++) + printk(" %02x", *p++); + printk("\n"); +} +#endif + +static __inline void isdn_trash_skb(struct sk_buff *skb, int rw) +{ + skb->free = 1; + kfree_skb(skb, rw); +} + +static void isdn_free_queue(struct sk_buff_head *queue) +{ + struct sk_buff *skb; + unsigned long flags; + + save_flags(flags); + cli(); + if (skb_queue_len(queue)) + while ((skb = skb_dequeue(queue))) + isdn_trash_skb(skb, FREE_READ); + restore_flags(flags); +} + +int isdn_dc2minor(int di, int ch) +{ + int i; + for (i = 0; i < ISDN_MAX_CHANNELS; i++) + if (dev->chanmap[i] == ch && dev->drvmap[i] == di) + return i; + return -1; +} + +static int isdn_timer_cnt1 = 0; +static int isdn_timer_cnt2 = 0; + +static void isdn_timer_funct(ulong dummy) +{ + int tf = dev->tflags; + + if (tf & ISDN_TIMER_FAST) { + if (tf & ISDN_TIMER_MODEMREAD) + isdn_tty_readmodem(); + if (tf & ISDN_TIMER_MODEMPLUS) + isdn_tty_modem_escape(); + if (tf & ISDN_TIMER_MODEMXMIT) + isdn_tty_modem_xmit(); + } + if (tf & ISDN_TIMER_SLOW) { + if (++isdn_timer_cnt1 >= ISDN_TIMER_02SEC) { + isdn_timer_cnt1 = 0; + if (tf & ISDN_TIMER_NETDIAL) + isdn_net_dial(); + } + if (++isdn_timer_cnt2 >= ISDN_TIMER_1SEC) { + isdn_timer_cnt2 = 0; + if (tf & ISDN_TIMER_NETHANGUP) + isdn_net_autohup(); + if (tf & ISDN_TIMER_MODEMRING) + isdn_tty_modem_ring(); +#if (defined CONFIG_ISDN_PPP ) && (defined ISDN_CONFIG_MPP) + if (tf & ISDN_TIMER_IPPP) + isdn_ppp_timer_timeout(); +#endif + } + } + if (tf) { + int flags; + + save_flags(flags); + cli(); + del_timer(&dev->timer); +#ifndef NEW_ISDN_TIMER_CTRL + dev->timer.function = isdn_timer_funct; +#endif + dev->timer.expires = jiffies + ISDN_TIMER_RES; + add_timer(&dev->timer); + restore_flags(flags); + } +} + +void isdn_timer_ctrl(int tf, int onoff) +{ + int flags; + + save_flags(flags); + cli(); + if ((tf & ISDN_TIMER_SLOW) && (!(dev->tflags & ISDN_TIMER_SLOW))) { + /* If the slow-timer wasn't activated until now */ + isdn_timer_cnt1 = 0; + isdn_timer_cnt2 = 0; + } + if (onoff) + dev->tflags |= tf; + else + dev->tflags &= ~tf; +#ifdef NEW_ISDN_TIMER_CTRL + if (dev->tflags) { + if (!del_timer(&dev->timer)) /* del_timer is 1, when active */ + dev->timer.expires = jiffies + ISDN_TIMER_RES; + add_timer(&dev->timer); + } +#else + if (dev->tflags) { + del_timer(&dev->timer); + dev->timer.function = isdn_timer_funct; + dev->timer.expires = jiffies + ISDN_TIMER_RES; + add_timer(&dev->timer); + } +#endif + restore_flags(flags); +} + +/* + * Receive a packet from B-Channel. (Called from low-level-module) + */ +static void isdn_receive_skb_callback(int di, int channel, struct sk_buff *skb) +{ + ulong flags; + int i; + int midx; +#ifdef CONFIG_ISDN_AUDIO + int ifmt; +#endif + modem_info *info; + + if ((i = isdn_dc2minor(di,channel))==-1) { + isdn_trash_skb(skb, FREE_READ); + return; + } + /* Update statistics */ + dev->ibytes[i] += skb->len; + /* First, try to deliver data to network-device */ + if (isdn_net_rcv_skb(i, skb)) + return; + /* No network-device found, deliver to tty or raw-channel */ + skb->free = 1; + if (skb->len) { + if ((midx = dev->m_idx[i])<0) { + /* if midx is invalid, drop packet */ + isdn_trash_skb(skb, FREE_READ); + return; + } + info = &dev->mdm.info[midx]; +#ifdef CONFIG_ISDN_AUDIO + ifmt = 1; + + if (info->vonline) + isdn_audio_calc_dtmf(info, skb->data, skb->len, ifmt); +#endif + if ((info->online < 2) && + (!(info->vonline & 1))) { + /* If Modem not listening, drop data */ + isdn_trash_skb(skb, FREE_READ); + return; + } + if (info->emu.mdmreg[13] & 2) + /* T.70 decoding: Simply throw away the T.70 header (4 bytes) */ + if ((skb->data[0] == 1) && ((skb->data[1] == 0) || (skb->data[1] == 1))) + skb_pull(skb,4); + /* The users field of an sk_buff is used in a special way + * with tty's incoming data: + * users is set to the number of DLE codes when in audio mode. + */ + skb->users = 0; +#ifdef CONFIG_ISDN_AUDIO + if (info->vonline & 1) { + /* voice conversion/compression */ + switch (info->emu.vpar[3]) { + case 2: + case 3: + case 4: + /* adpcm + * Since compressed data takes less + * space, we can overwrite the buffer. + */ + skb_trim(skb,isdn_audio_xlaw2adpcm(info->adpcmr, + ifmt, + skb->data, + skb->data, + skb->len)); + break; + case 5: + /* a-law */ + if (!ifmt) + isdn_audio_ulaw2alaw(skb->data,skb->len); + break; + case 6: + /* u-law */ + if (ifmt) + isdn_audio_alaw2ulaw(skb->data,skb->len); + break; + } + skb->users = isdn_tty_countDLE(skb->data,skb->len); + } +#endif + /* Try to deliver directly via tty-flip-buf if queue is empty */ + save_flags(flags); + cli(); + if (skb_queue_empty(&dev->drv[di]->rpqueue[channel])) + if (isdn_tty_try_read(info, skb)) { + restore_flags(flags); + return; + } + /* Direct deliver failed or queue wasn't empty. + * Queue up for later dequeueing via timer-irq. + */ + __skb_queue_tail(&dev->drv[di]->rpqueue[channel], skb); + dev->drv[di]->rcvcount[channel] += (skb->len + skb->users); + restore_flags(flags); + /* Schedule dequeuing */ + if ((dev->modempoll) && (info->rcvsched)) + isdn_timer_ctrl(ISDN_TIMER_MODEMREAD, 1); + wake_up_interruptible(&dev->drv[di]->rcv_waitq[channel]); + } else + isdn_trash_skb(skb, FREE_READ); +} + +void isdn_all_eaz(int di, int ch) +{ + isdn_ctrl cmd; + + cmd.driver = di; + cmd.arg = ch; + cmd.command = ISDN_CMD_SETEAZ; + cmd.num[0] = '\0'; + (void) dev->drv[di]->interface->command(&cmd); +} + +static int isdn_status_callback(isdn_ctrl * c) +{ + int di; + int mi; + ulong flags; + int i; + int r; + modem_info *info; + isdn_ctrl cmd; + + di = c->driver; + i = isdn_dc2minor(di, c->arg); + switch (c->command) { + case ISDN_STAT_BSENT: + if (i<0) + return -1; + if (dev->global_flags & ISDN_GLOBAL_STOPPED) + return 0; + if (isdn_net_stat_callback(i, c->command)) + return 0; + isdn_tty_bsent(di, c->arg); + wake_up_interruptible(&dev->drv[di]->snd_waitq[c->arg]); + break; + case ISDN_STAT_STAVAIL: + save_flags(flags); + cli(); + dev->drv[di]->stavail += c->arg; + restore_flags(flags); + wake_up_interruptible(&dev->drv[di]->st_waitq); + break; + case ISDN_STAT_RUN: + dev->drv[di]->running = 1; + for (i = 0; i < ISDN_MAX_CHANNELS; i++) + if (dev->drvmap[i] == di) + isdn_all_eaz(di, dev->chanmap[i]); + break; + case ISDN_STAT_STOP: + dev->drv[di]->running = 0; + break; + case ISDN_STAT_ICALL: + if (i<0) + return -1; +#ifdef ISDN_DEBUG_STATCALLB + printk(KERN_DEBUG "ICALL (net): %d %ld %s\n", di, c->arg, c->num); +#endif + if (dev->global_flags & ISDN_GLOBAL_STOPPED) { + cmd.driver = di; + cmd.arg = c->arg; + cmd.command = ISDN_CMD_HANGUP; + dev->drv[di]->interface->command(&cmd); + return 0; + } + + /* Try to find a network-interface which will accept incoming call */ + cmd.driver = di; + cmd.arg = c->arg; + cmd.command = ISDN_CMD_LOCK; + dev->drv[di]->interface->command(&cmd); + r = isdn_net_find_icall(di, c->arg, i, c->num); + switch (r) { + case 0: + /* No network-device replies. Schedule RING-message to + * tty and set RI-bit of modem-status. + */ + if ((mi = isdn_tty_find_icall(di, c->arg, c->num)) >= 0) { + info = &dev->mdm.info[mi]; + info->msr |= UART_MSR_RI; + isdn_tty_modem_result(2, info); + isdn_timer_ctrl(ISDN_TIMER_MODEMRING, 1); + return 0; + } else if (dev->drv[di]->reject_bus) { + cmd.driver = di; + cmd.arg = c->arg; + cmd.command = ISDN_CMD_HANGUP; + dev->drv[di]->interface->command(&cmd); + } + break; + case 1: + /* Schedule connection-setup */ + isdn_net_dial(); + cmd.driver = di; + cmd.arg = c->arg; + cmd.command = ISDN_CMD_ACCEPTD; + dev->drv[di]->interface->command(&cmd); + return 0; + break; + case 2: /* For calling back, first reject incoming call ... */ + case 3: /* Interface found, but down, reject call actively */ + printk(KERN_INFO "isdn: Rejecting Call\n"); + cmd.driver = di; + cmd.arg = c->arg; + cmd.command = ISDN_CMD_HANGUP; + dev->drv[di]->interface->command(&cmd); + if (r == 3) + break; + /* Fall through */ + case 4: + /* ... then start callback. */ + isdn_net_dial(); + return 0; + } + cmd.driver = di; + cmd.arg = c->arg; + cmd.command = ISDN_CMD_UNLOCK; + dev->drv[di]->interface->command(&cmd); + return 0; + break; + case ISDN_STAT_CINF: + if (i<0) + return -1; +#ifdef ISDN_DEBUG_STATCALLB + printk(KERN_DEBUG "CINF: %ld %s\n", c->arg, c->num); +#endif + if (dev->global_flags & ISDN_GLOBAL_STOPPED) + return 0; + if (strcmp(c->num, "0")) + isdn_net_stat_callback(i, c->command); + break; + case ISDN_STAT_CAUSE: +#ifdef ISDN_DEBUG_STATCALLB + printk(KERN_DEBUG "CAUSE: %ld %s\n", c->arg, c->num); +#endif + printk(KERN_INFO "isdn: cause: %s\n", c->num); + break; + case ISDN_STAT_DCONN: + if (i<0) + return -1; +#ifdef ISDN_DEBUG_STATCALLB + printk(KERN_DEBUG "DCONN: %ld\n", c->arg); +#endif + if (dev->global_flags & ISDN_GLOBAL_STOPPED) + return 0; + /* Find any network-device, waiting for D-channel setup */ + if (isdn_net_stat_callback(i, c->command)) + break; + + if ((mi = dev->m_idx[i]) >= 0) { + /* If any tty has just dialed-out, setup B-Channel */ + info = &dev->mdm.info[mi]; + if (info->flags & + (ISDN_ASYNC_NORMAL_ACTIVE | ISDN_ASYNC_CALLOUT_ACTIVE)) { + if (info->dialing == 1) { + info->dialing = 2; + cmd.driver = di; + cmd.arg = c->arg; + cmd.command = ISDN_CMD_ACCEPTB; + dev->drv[di]->interface->command(&cmd); + return 0; + } + } + } + break; + case ISDN_STAT_DHUP: + if (i<0) + return -1; +#ifdef ISDN_DEBUG_STATCALLB + printk(KERN_DEBUG "DHUP: %ld\n", c->arg); +#endif + if (dev->global_flags & ISDN_GLOBAL_STOPPED) + return 0; + dev->drv[di]->flags &= ~(1 << (c->arg)); + isdn_info_update(); + /* Signal hangup to network-devices */ + if (isdn_net_stat_callback(i, c->command)) + break; + if ((mi = dev->m_idx[i]) >= 0) { + /* Signal hangup to tty-device */ + info = &dev->mdm.info[mi]; + if (info->flags & + (ISDN_ASYNC_NORMAL_ACTIVE | ISDN_ASYNC_CALLOUT_ACTIVE)) { + if (info->dialing == 1) { + info->dialing = 0; + isdn_tty_modem_result(7, info); + } + if (info->online) + isdn_tty_modem_result(3, info); +#ifdef ISDN_DEBUG_MODEM_HUP + printk(KERN_DEBUG "Mhup in ISDN_STAT_DHUP\n"); +#endif + isdn_tty_modem_hup(info); + return 0; + } + } + break; + case ISDN_STAT_BCONN: + if (i<0) + return -1; +#ifdef ISDN_DEBUG_STATCALLB + printk(KERN_DEBUG "BCONN: %ld\n", c->arg); +#endif + /* Signal B-channel-connect to network-devices */ + if (dev->global_flags & ISDN_GLOBAL_STOPPED) + return 0; + dev->drv[di]->flags |= (1 << (c->arg)); + isdn_info_update(); + if (isdn_net_stat_callback(i, c->command)) + break; + if ((mi = dev->m_idx[i]) >= 0) { + /* Schedule CONNECT-Message to any tty, waiting for it and + * set DCD-bit of its modem-status. + */ + info = &dev->mdm.info[mi]; + if (info->flags & + (ISDN_ASYNC_NORMAL_ACTIVE | ISDN_ASYNC_CALLOUT_ACTIVE)) { + info->msr |= UART_MSR_DCD; + if (info->dialing) + info->dialing = 0; + info->rcvsched = 1; + if (USG_MODEM(dev->usage[i])) + isdn_tty_modem_result(5, info); + if (USG_VOICE(dev->usage[i])) + isdn_tty_modem_result(11, info); + } + } + break; + case ISDN_STAT_BHUP: + if (i<0) + return -1; +#ifdef ISDN_DEBUG_STATCALLB + printk(KERN_DEBUG "BHUP: %ld\n", c->arg); +#endif + if (dev->global_flags & ISDN_GLOBAL_STOPPED) + return 0; + dev->drv[di]->flags &= ~(1 << (c->arg)); + isdn_info_update(); + if ((mi = dev->m_idx[i]) >= 0) { + /* Signal hangup to tty-device, schedule NO CARRIER-message */ + info = &dev->mdm.info[mi]; + if (info->flags & + (ISDN_ASYNC_NORMAL_ACTIVE | ISDN_ASYNC_CALLOUT_ACTIVE)) { + if (info->msr & UART_MSR_DCD) + isdn_tty_modem_result(3, info); + info->msr &= ~(UART_MSR_DCD | UART_MSR_RI); +#ifdef ISDN_DEBUG_MODEM_HUP + printk(KERN_DEBUG "Mhup in ISDN_STAT_BHUP\n"); +#endif + isdn_tty_modem_hup(info); + } + } + break; + case ISDN_STAT_NODCH: + if (i<0) + return -1; +#ifdef ISDN_DEBUG_STATCALLB + printk(KERN_DEBUG "NODCH: %ld\n", c->arg); +#endif + if (dev->global_flags & ISDN_GLOBAL_STOPPED) + return 0; + if (isdn_net_stat_callback(i, c->command)) + break; + if ((mi = dev->m_idx[i]) >= 0) { + info = &dev->mdm.info[mi]; + if (info->flags & + (ISDN_ASYNC_NORMAL_ACTIVE | ISDN_ASYNC_CALLOUT_ACTIVE)) { + if (info->dialing) { + info->dialing = 0; + isdn_tty_modem_result(6, info); + } + info->msr &= ~UART_MSR_DCD; + if (info->online) { + isdn_tty_modem_result(3, info); + info->online = 0; + } + } + } + break; + case ISDN_STAT_ADDCH: + break; + case ISDN_STAT_UNLOAD: + save_flags(flags); + cli(); + for (i = 0; i < ISDN_MAX_CHANNELS; i++) + if (dev->drvmap[i] == di) { + dev->drvmap[i] = -1; + dev->chanmap[i] = -1; + } + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + modem_info *info = &dev->mdm.info[i]; + + if (info->isdn_driver == di) { + info->isdn_driver = -1; + info->isdn_channel = -1; + if (info->online) { + isdn_tty_modem_result(3, info); + isdn_tty_modem_hup(info); + } + } + } + dev->drivers--; + dev->channels -= dev->drv[di]->channels; + kfree(dev->drv[di]->rcverr); + kfree(dev->drv[di]->rcvcount); + for (i = 0; i < dev->drv[di]->channels; i++) + isdn_free_queue(&dev->drv[di]->rpqueue[i]); + kfree(dev->drv[di]->rpqueue); + kfree(dev->drv[di]->rcv_waitq); + kfree(dev->drv[di]->snd_waitq); + kfree(dev->drv[di]); + dev->drv[di] = NULL; + dev->drvid[di][0] = '\0'; + isdn_info_update(); + restore_flags(flags); + return 0; + default: + return -1; + } + return 0; +} + +/* + * Get integer from char-pointer, set pointer to end of number + */ +int isdn_getnum(char **p) +{ + int v = -1; + + while (*p[0] >= '0' && *p[0] <= '9') + v = ((v < 0) ? 0 : (v * 10)) + (int) ((*p[0]++) - '0'); + return v; +} + +#define DLE 0x10 + +/* + * isdn_readbchan() tries to get data from the read-queue. + * It MUST be called with interrupts off. + * + * I hope I got the EFAULT error path right -AK. + */ +int isdn_readbchan(int di, int channel, u_char * buf, u_char * fp, int len, int user) +{ + int left; + int count; + int count_pull; + int count_put; + int dflag; + struct sk_buff *skb; + u_char *cp; + int ret = 0; + + if (!dev->drv[di]) + return 0; + if (skb_queue_empty(&dev->drv[di]->rpqueue[channel])) { + if (user) + interruptible_sleep_on(&dev->drv[di]->rcv_waitq[channel]); + else + return 0; + } + left = MIN(len, dev->drv[di]->rcvcount[channel]); + cp = buf; + count = 0; + while (left) { + ret = 0; + if (!(skb = skb_peek(&dev->drv[di]->rpqueue[channel]))) + break; + if (skb->lock) + break; + skb->lock = 1; + if (skb->users) { + /* users is the count of DLE's in + * this buff when in voice mode. + */ + char *p = skb->data; + unsigned long DLEmask = (1 << channel); + + dflag = 0; + count_pull = count_put = 0; + while ((count_pull < skb->len) && (left-- > 0)) { + if (dev->drv[di]->DLEflag & DLEmask) { + if (user) { + ret = put_user(DLE,cp); + cp++; + if (ret) break; + } else + *cp++ = DLE; + dev->drv[di]->DLEflag &= ~DLEmask; + } else { + if (user) { + ret = put_user(*p,cp); + if (ret) break; + cp++; + } else + *cp++ = *p; + if (*p == DLE) { + dev->drv[di]->DLEflag |= DLEmask; + skb->users--; + } + p++; + count_pull++; + } + count_put++; + } + if (count_pull >= skb->len) + dflag = 1; + } else { + /* No DLE's in buff, so simply copy it */ + dflag = 1; + if ((count_pull = skb->len) > left) { + count_pull = left; + dflag = 0; + } + count_put = count_pull; + ret = 0; + if (user) + ret = copy_to_user(cp, skb->data, count_put); + else + memcpy(cp, skb->data, count_put); + count_put -= ret; + cp += count_put; + left -= count_put; + } + count += count_put; + if (fp) { + memset(fp, 0, count_put); + fp += count_put; + } + if (dflag) { + /* We got all the data in this buff. + * Now we can dequeue it. + */ + if (fp) + *(fp - 1) = 0xff; + skb->lock = 0; + skb = skb_dequeue(&dev->drv[di]->rpqueue[channel]); + isdn_trash_skb(skb, FREE_READ); + } else { + /* Not yet emptied this buff, so it + * must stay in the queue, for further calls + * but we pull off the data we got until now. + */ + skb_pull(skb,count_pull); + skb->lock = 0; + } + dev->drv[di]->rcvcount[channel] -= count_put; + } + return ret ? -EFAULT : count; +} + +static __inline int isdn_minor2drv(int minor) +{ + return (dev->drvmap[minor]); +} + +static __inline int isdn_minor2chan(int minor) +{ + return (dev->chanmap[minor]); +} + +#define INF_DV 0x01 /* Data version for /dev/isdninfo */ + +static char * + isdn_statstr(void) +{ + static char istatbuf[2048]; + char *p; + int i; + + sprintf(istatbuf, "idmap:\t"); + p = istatbuf + strlen(istatbuf); + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + sprintf(p, "%s ", (dev->drvmap[i] < 0) ? "-" : dev->drvid[dev->drvmap[i]]); + p = istatbuf + strlen(istatbuf); + } + sprintf(p, "\nchmap:\t"); + p = istatbuf + strlen(istatbuf); + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + sprintf(p, "%d ", dev->chanmap[i]); + p = istatbuf + strlen(istatbuf); + } + sprintf(p, "\ndrmap:\t"); + p = istatbuf + strlen(istatbuf); + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + sprintf(p, "%d ", dev->drvmap[i]); + p = istatbuf + strlen(istatbuf); + } + sprintf(p, "\nusage:\t"); + p = istatbuf + strlen(istatbuf); + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + sprintf(p, "%d ", dev->usage[i]); + p = istatbuf + strlen(istatbuf); + } + sprintf(p, "\nflags:\t"); + p = istatbuf + strlen(istatbuf); + for (i = 0; i < ISDN_MAX_DRIVERS; i++) { + if (dev->drv[i]) { + sprintf(p, "%ld ", dev->drv[i]->flags); + p = istatbuf + strlen(istatbuf); + } else { + sprintf(p, "? "); + p = istatbuf + strlen(istatbuf); + } + } + sprintf(p, "\nphone:\t"); + p = istatbuf + strlen(istatbuf); + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + sprintf(p, "%s ", dev->num[i]); + p = istatbuf + strlen(istatbuf); + } + sprintf(p, "\n"); + return istatbuf; +} + +/* Module interface-code */ + +void isdn_info_update(void) +{ + infostruct *p = dev->infochain; + + while (p) { + *(p->private) = 1; + p = (infostruct *) p->next; + } + wake_up_interruptible(&(dev->info_waitq)); +} + +static long isdn_read(struct inode *inode, struct file *file, char *buf, unsigned long count) +{ + uint minor = MINOR(inode->i_rdev); + int len = 0; + ulong flags; + int drvidx; + int chidx; + + if (minor == ISDN_MINOR_STATUS) { + char *p; + if (!file->private_data) { + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + interruptible_sleep_on(&(dev->info_waitq)); + } + p = isdn_statstr(); + file->private_data = 0; + if ((len = strlen(p)) <= count) { + if (copy_to_user(buf, p, len)) + return -EFAULT; + file->f_pos += len; + return len; + } + return 0; + } + if (!dev->drivers) + return -ENODEV; + if (minor < ISDN_MINOR_CTRL) { + drvidx = isdn_minor2drv(minor); + if (drvidx < 0) + return -ENODEV; + if (!dev->drv[drvidx]->running) + return -ENODEV; + chidx = isdn_minor2chan(minor); + save_flags(flags); + cli(); + len = isdn_readbchan(drvidx, chidx, buf, 0, count, 1); + file->f_pos += len; + restore_flags(flags); + return len; + } + if (minor <= ISDN_MINOR_CTRLMAX) { + drvidx = isdn_minor2drv(minor - ISDN_MINOR_CTRL); + if (drvidx < 0) + return -ENODEV; + if (!dev->drv[drvidx]->stavail) { + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + interruptible_sleep_on(&(dev->drv[drvidx]->st_waitq)); + } + if (dev->drv[drvidx]->interface->readstat) + len = dev->drv[drvidx]->interface-> + readstat(buf, MIN(count, dev->drv[drvidx]->stavail), + 1, drvidx, isdn_minor2chan(minor)); + else + len = 0; + save_flags(flags); + cli(); + if (len) + dev->drv[drvidx]->stavail -= len; + else + dev->drv[drvidx]->stavail = 0; + restore_flags(flags); + file->f_pos += len; + return len; + } +#ifdef CONFIG_ISDN_PPP + if (minor <= ISDN_MINOR_PPPMAX) + return (isdn_ppp_read(minor - ISDN_MINOR_PPP, file, buf, count)); +#endif + return -ENODEV; +} + +static long long isdn_lseek(struct inode *inode, struct file *file, long long offset, int orig) +{ + return -ESPIPE; +} + +static long isdn_write(struct inode *inode, struct file *file, + const char *buf, unsigned long count) +{ + uint minor = MINOR(inode->i_rdev); + int drvidx; + int chidx; + + if (minor == ISDN_MINOR_STATUS) + return -EPERM; + if (!dev->drivers) + return -ENODEV; + if (minor < ISDN_MINOR_CTRL) { + drvidx = isdn_minor2drv(minor); + if (drvidx < 0) + return -ENODEV; + if (!dev->drv[drvidx]->running) + return -ENODEV; + chidx = isdn_minor2chan(minor); + while (isdn_writebuf_stub(drvidx, chidx, buf, count, 1) != count) + interruptible_sleep_on(&dev->drv[drvidx]->snd_waitq[chidx]); + return count; + } + if (minor <= ISDN_MINOR_CTRLMAX) { + drvidx = isdn_minor2drv(minor - ISDN_MINOR_CTRL); + if (drvidx < 0) + return -ENODEV; + /* + * We want to use the isdnctrl device to load the firmware + * + if (!dev->drv[drvidx]->running) + return -ENODEV; + */ + if (dev->drv[drvidx]->interface->writecmd) + return (dev->drv[drvidx]->interface-> + writecmd(buf, count, 1, drvidx, isdn_minor2chan(minor))); + else + return count; + } +#ifdef CONFIG_ISDN_PPP + if (minor <= ISDN_MINOR_PPPMAX) + return (isdn_ppp_write(minor - ISDN_MINOR_PPP, file, buf, count)); +#endif + return -ENODEV; +} + +static int isdn_select(struct inode *inode, struct file *file, int type, select_table * st) +{ + uint minor = MINOR(inode->i_rdev); + int drvidx = isdn_minor2drv(minor - ISDN_MINOR_CTRL); + + if (minor == ISDN_MINOR_STATUS) { + if (file->private_data) + return 1; + else { + if (st) + select_wait(&(dev->info_waitq), st); + return 0; + } + } + if (minor >= ISDN_MINOR_CTRL && minor <= ISDN_MINOR_CTRLMAX) { + if (drvidx < 0) + return -ENODEV; + if (dev->drv[drvidx]->stavail) + return 1; + else { + if (st) + select_wait(&(dev->drv[drvidx]->st_waitq), st); + return 0; + } + return 1; + } +#ifdef CONFIG_ISDN_PPP + if (minor <= ISDN_MINOR_PPPMAX) + return (isdn_ppp_select(minor - ISDN_MINOR_PPP, file, type, st)); +#endif + return -ENODEV; +} + +static int isdn_set_allcfg(char *src) +{ + int ret; + int i; + ulong flags; + char buf[1024]; + isdn_net_ioctl_cfg cfg; + isdn_net_ioctl_phone phone; + + if ((ret = isdn_net_rmall())) + return ret; + save_flags(flags); + cli(); + ret = get_user(i, src); + if (ret) + goto out; + src += sizeof(int); + while (i) { + char *c; + char *c2; + + if(copy_from_user((char *) &cfg, src, sizeof(cfg))) + goto fault; + src += sizeof(cfg); + if (!isdn_net_new(cfg.name, NULL)) { + restore_flags(flags); + return -EIO; + } + if ((ret = isdn_net_setcfg(&cfg))) + goto out; + if(copy_from_user(buf, src, sizeof(buf))) + goto fault; + src += sizeof(buf); + c = buf; + while (*c) { + if ((c2 = strchr(c, ' '))) + *c2++ = '\0'; + strcpy(phone.phone, c); + strcpy(phone.name, cfg.name); + phone.outgoing = 0; + if ((ret = isdn_net_addphone(&phone))) + goto fault; + if (c2) + c = c2; + else + c += strlen(c); + } + if(copy_from_user(buf, src, sizeof(buf))) + goto fault; + src += sizeof(buf); + c = buf; + while (*c) { + if ((c2 = strchr(c, ' '))) + *c2++ = '\0'; + strcpy(phone.phone, c); + strcpy(phone.name, cfg.name); + phone.outgoing = 1; + if ((ret = isdn_net_addphone(&phone))) + goto out; + if (c2) + c = c2; + else + c += strlen(c); + } + i--; + } +out: + restore_flags(flags); + return ret; +fault: + restore_flags(flags); + return -EFAULT; +} + +static int isdn_get_allcfg(char *dest) +{ + isdn_net_ioctl_cfg cfg; + isdn_net_ioctl_phone phone; + isdn_net_dev *p; + ulong flags; + int ret = 0; + + /* Walk through netdev-chain */ + save_flags(flags); + cli(); + p = dev->netdev; + while (p) { + strcpy(cfg.eaz, p->local.msn); + cfg.exclusive = p->local.exclusive; + if (p->local.pre_device >= 0) { + sprintf(cfg.drvid, "%s,%d", dev->drvid[p->local.pre_device], + p->local.pre_channel); + } else + cfg.drvid[0] = '\0'; + cfg.onhtime = p->local.onhtime; + cfg.charge = p->local.charge; + cfg.l2_proto = p->local.l2_proto; + cfg.l3_proto = p->local.l3_proto; + cfg.p_encap = p->local.p_encap; + cfg.secure = (p->local.flags & ISDN_NET_SECURE) ? 1 : 0; + cfg.callback = (p->local.flags & ISDN_NET_CALLBACK) ? 1 : 0; + cfg.chargehup = (p->local.hupflags & 4) ? 1 : 0; + cfg.ihup = (p->local.hupflags & 8) ? 1 : 0; + ret = 0; + ret += copy_to_user(dest, p->local.name, 10); + dest += 10; + ret += copy_to_user(dest, (char *) &cfg, sizeof(cfg)); + dest += sizeof(cfg); + strcpy(phone.name, p->local.name); + phone.outgoing = 0; + if (ret) + break; + if ((ret = isdn_net_getphones(&phone, dest)) < 0) + break; + else + dest += ret; + strcpy(phone.name, p->local.name); + phone.outgoing = 1; + if ((ret = isdn_net_getphones(&phone, dest)) < 0) + break; + else + dest += ret; + p = p->next; + } + restore_flags(flags); + return ret; +} + +static int isdn_ioctl(struct inode *inode, struct file *file, uint cmd, ulong arg) +{ + uint minor = MINOR(inode->i_rdev); + isdn_ctrl c; + int drvidx; + int chidx; + int ret = 0; + char *s; + char name[10]; + char bname[21]; + isdn_ioctl_struct iocts; + isdn_net_ioctl_phone phone; + isdn_net_ioctl_cfg cfg; + + if (minor == ISDN_MINOR_STATUS) { + switch (cmd) { + case IIOCGETDVR: + return(TTY_DV + + (NET_DV << 8) + + (INF_DV << 16)); + case IIOCGETCPS: + if (arg) { + ulong *p = (ulong *)arg; + int i; + for (i = 0;i<ISDN_MAX_CHANNELS;i++) { + ret = put_user(dev->ibytes[i],p); + if (ret) break; + p++; + ret = put_user(dev->obytes[i],p); + p++; + if (ret) break; + } + return ret; + } else + return -EINVAL; + break; + default: + return -EINVAL; + } + } + if (!dev->drivers) + return -ENODEV; + if (minor < ISDN_MINOR_CTRL) { + drvidx = isdn_minor2drv(minor); + if (drvidx < 0) + return -ENODEV; + chidx = isdn_minor2chan(minor); + if (!dev->drv[drvidx]->running) + return -ENODEV; + return 0; + } + if (minor <= ISDN_MINOR_CTRLMAX) { + switch (cmd) { +#ifdef CONFIG_NETDEVICES + case IIOCNETAIF: + /* Add a network-interface */ + if (arg) { + if(copy_from_user(name, (char *) arg, sizeof(name))) + return -EFAULT; + s = name; + } else + s = NULL; + if ((s = isdn_net_new(s, NULL))) { + return copy_to_user((char *) arg, s, strlen(s) + 1) ? -EFAULT : ret; + } else + return -ENODEV; + case IIOCNETASL: + /* Add a slave to a network-interface */ + if (arg) { + if(copy_from_user(bname, (char *) arg, sizeof(bname))) + return -EFAULT; + } else + return -EINVAL; + if ((s = isdn_net_newslave(bname))) { + return copy_to_user((char *) arg, s, strlen(s) + 1) ? -EFAULT : 0; + } else + return -ENODEV; + case IIOCNETDIF: + /* Delete a network-interface */ + if (arg) { + ret = copy_from_user(name, (char *) arg, sizeof(name)); + return ret ? -EFAULT : isdn_net_rm(name); + } else + return -EINVAL; + case IIOCNETSCF: + /* Set configurable parameters of a network-interface */ + if (arg) { + ret = copy_from_user((char *) &cfg, (char *) arg, sizeof(cfg)); + return ret ? -EFAULT : isdn_net_setcfg(&cfg); + } else + return -EINVAL; + case IIOCNETGCF: + /* Get configurable parameters of a network-interface */ + if (arg) { + if(copy_from_user((char *) &cfg, (char *) arg, sizeof(cfg))) + return -EFAULT; + if (!(ret = isdn_net_getcfg(&cfg))) { + if(copy_to_user((char *) arg, (char *) &cfg, sizeof(cfg))) + return -EFAULT; + } + return ret; + } else + return -EINVAL; + case IIOCNETANM: + /* Add a phone-number to a network-interface */ + if (arg) { + ret = copy_from_user((char *) &phone, (char *) arg, sizeof(phone)); + return ret ? -EFAULT : isdn_net_addphone(&phone); + } else + return -EINVAL; + case IIOCNETGNM: + /* Get list of phone-numbers of a network-interface */ + if (arg) { + ret = copy_from_user((char *) &phone, (char *) arg, sizeof(phone)); + return ret ? -EFAULT : isdn_net_getphones(&phone, (char *) arg); + } else + return -EINVAL; + case IIOCNETDNM: + /* Delete a phone-number of a network-interface */ + if (arg) { + ret = copy_from_user((char *) &phone, (char *) arg, sizeof(phone)); + return ret ? -EFAULT : isdn_net_delphone(&phone); + } else + return -EINVAL; + case IIOCNETDIL: + /* Force dialing of a network-interface */ + if (arg) { + ret = copy_from_user(name, (char *) arg, sizeof(name)); + return ret ? -EFAULT : isdn_net_force_dial(name); + } else + return -EINVAL; +#ifdef CONFIG_ISDN_PPP + case IIOCNETALN: + if (arg) + ret = copy_from_user(name,(char*)arg,sizeof(name)); + else + return -EINVAL; + return ret ? -EFAULT : isdn_ppp_dial_slave(name); + case IIOCNETDLN: + + if(arg) { + ret = copy_from_user(name,(char*)arg,sizeof(name)); + } else + return -EINVAL; + return ret ? -EFAULT : isdn_ppp_hangup_slave(name); +#endif + case IIOCNETHUP: + /* Force hangup of a network-interface */ + if (arg) { + ret = copy_from_user(name, (char *) arg, sizeof(name)); + return ret ? -EFAULT : isdn_net_force_hangup(name); + } else + return -EINVAL; + break; +#endif /* CONFIG_NETDEVICES */ + case IIOCSETVER: + dev->net_verbose = arg; + printk(KERN_INFO "isdn: Verbose-Level is %d\n", dev->net_verbose); + return 0; + case IIOCSETGST: + if (arg) + dev->global_flags |= ISDN_GLOBAL_STOPPED; + else + dev->global_flags &= ~ISDN_GLOBAL_STOPPED; + printk(KERN_INFO "isdn: Global Mode %s\n", + (dev->global_flags & ISDN_GLOBAL_STOPPED) ? "stopped" : "running"); + return 0; + case IIOCSETBRJ: + drvidx = -1; + if (arg) { + int i; + char *p; + if(copy_from_user((char *) &iocts, (char *) arg, + sizeof(isdn_ioctl_struct))) + return -EFAULT; + if (strlen(iocts.drvid)) { + if ((p = strchr(iocts.drvid, ','))) + *p = 0; + drvidx = -1; + for (i = 0; i < ISDN_MAX_DRIVERS; i++) + if (!(strcmp(dev->drvid[i], iocts.drvid))) { + drvidx = i; + break; + } + } + } + if (drvidx == -1) + return -ENODEV; + dev->drv[drvidx]->reject_bus = iocts.arg; + return 0; + case IIOCGETSET: + /* Get complete setup (all network-interfaces and profile- + settings of all tty-devices */ + if (arg) + return (isdn_get_allcfg((char *) arg)); + else + return -EINVAL; + break; + case IIOCSETSET: + /* Set complete setup (all network-interfaces and profile- + settings of all tty-devices */ + if (arg) + return (isdn_set_allcfg((char *) arg)); + else + return -EINVAL; + break; + case IIOCSIGPRF: + dev->profd = current; + return 0; + break; + case IIOCGETPRF: + /* Get all Modem-Profiles */ + if (arg) { + char *p = (char *) arg; + int i; + + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + if(copy_to_user(p, dev->mdm.info[i].emu.profile, + ISDN_MODEM_ANZREG)) + return -EFAULT; + p += ISDN_MODEM_ANZREG; + if(copy_to_user(p, dev->mdm.info[i].emu.pmsn, ISDN_MSNLEN)) + return -EFAULT; + p += ISDN_MSNLEN; + } + return (ISDN_MODEM_ANZREG + ISDN_MSNLEN) * ISDN_MAX_CHANNELS; + } else + return -EINVAL; + break; + case IIOCSETPRF: + /* Set all Modem-Profiles */ + if (arg) { + char *p = (char *) arg; + int i; + + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + if(copy_from_user(dev->mdm.info[i].emu.profile, p, + ISDN_MODEM_ANZREG)) + return -EFAULT; + p += ISDN_MODEM_ANZREG; + if(copy_from_user(dev->mdm.info[i].emu.pmsn, p, ISDN_MSNLEN)) + return -EFAULT; + p += ISDN_MSNLEN; + } + return 0; + } else + return -EINVAL; + break; + case IIOCSETMAP: + case IIOCGETMAP: + /* Set/Get MSN->EAZ-Mapping for a driver */ + if (arg) { + int i; + char *p; + char nstring[255]; + + ret = copy_from_user((char *) &iocts, (char *) arg, sizeof(isdn_ioctl_struct)); + + if (ret) return -EFAULT; + if (strlen(iocts.drvid)) { + drvidx = -1; + for (i = 0; i < ISDN_MAX_DRIVERS; i++) + if (!(strcmp(dev->drvid[i], iocts.drvid))) { + drvidx = i; + break; + } + } else + drvidx = 0; + if (drvidx == -1) + return -ENODEV; + if (cmd == IIOCSETMAP) { + ret = copy_from_user(nstring, (char *) iocts.arg, 255); + if (ret) return -EFAULT; + memset(dev->drv[drvidx]->msn2eaz, 0, + sizeof(dev->drv[drvidx]->msn2eaz)); + p = strtok(nstring, ","); + i = 0; + while ((p) && (i < 10)) { + strcpy(dev->drv[drvidx]->msn2eaz[i++], p); + p = strtok(NULL, ","); + } + } else { + p = nstring; + for (i = 0; i < 10; i++) + p += sprintf(p, "%s%s", + strlen(dev->drv[drvidx]->msn2eaz[i]) ? + dev->drv[drvidx]->msn2eaz[i] : "-", + (i < 9) ? "," : "\0"); + if(copy_to_user((char *) iocts.arg, nstring, strlen(nstring) + 1)) + return -EFAULT; + } + return 0; + } else + return -EINVAL; + case IIOCDBGVAR: + if (arg) { + return copy_to_user((char *) arg, (char *) &dev, sizeof(ulong)) ? -EFAULT : 0; + } else + return -EINVAL; + break; + default: + if ((cmd&IIOCDRVCTL)==IIOCDRVCTL) + cmd = ((cmd>>_IOC_NRSHIFT)&_IOC_NRMASK)& ISDN_DRVIOCTL_MASK; + else + return -EINVAL; + if (arg) { + int i; + char *p; + + ret = copy_from_user((char *) &iocts, (char *) arg, sizeof(isdn_ioctl_struct)); + if (ret) + return -EFAULT; + if (strlen(iocts.drvid)) { + if ((p = strchr(iocts.drvid, ','))) + *p = 0; + drvidx = -1; + for (i = 0; i < ISDN_MAX_DRIVERS; i++) + if (!(strcmp(dev->drvid[i], iocts.drvid))) { + drvidx = i; + break; + } + } else + drvidx = 0; + if (drvidx == -1) + return -ENODEV; + c.driver = drvidx; + c.command = ISDN_CMD_IOCTL; + c.arg = cmd; + memcpy(c.num, (char *) &iocts.arg, sizeof(ulong)); + ret = dev->drv[drvidx]->interface->command(&c); + memcpy((char *) &iocts.arg, c.num, sizeof(ulong)); + return copy_to_user((char *) arg, &iocts, sizeof(isdn_ioctl_struct)) ? -EFAULT : 0; + } else + return -EINVAL; + } + } +#ifdef CONFIG_ISDN_PPP + if (minor <= ISDN_MINOR_PPPMAX) + return (isdn_ppp_ioctl(minor - ISDN_MINOR_PPP, file, cmd, arg)); +#endif + return -ENODEV; +} + +/* + * Open the device code. + * MOD_INC_USE_COUNT make sure that the driver memory is not freed + * while the device is in use. + */ +static int isdn_open(struct inode *ino, struct file *filep) +{ + uint minor = MINOR(ino->i_rdev); + int drvidx; + int chidx; + isdn_ctrl c; + + if (minor == ISDN_MINOR_STATUS) { + infostruct *p; + + if ((p = (infostruct *) kmalloc(sizeof(infostruct), GFP_KERNEL))) { + MOD_INC_USE_COUNT; + p->next = (char *) dev->infochain; + p->private = (char *) &(filep->private_data); + dev->infochain = p; + /* At opening we allow a single update */ + filep->private_data = (char *) 1; + return 0; + } else + return -ENOMEM; + } + if (!dev->channels) + return -ENODEV; + if (minor < ISDN_MINOR_CTRL) { + drvidx = isdn_minor2drv(minor); + if (drvidx < 0) + return -ENODEV; + chidx = isdn_minor2chan(minor); + if (!dev->drv[drvidx]->running) + return -ENODEV; + if (!(dev->drv[drvidx]->flags & (1 << chidx))) + return -ENODEV; + c.command = ISDN_CMD_LOCK; + c.driver = drvidx; + (void) dev->drv[drvidx]->interface->command(&c); + MOD_INC_USE_COUNT; + return 0; + } + if (minor <= ISDN_MINOR_CTRLMAX) { + drvidx = isdn_minor2drv(minor - ISDN_MINOR_CTRL); + if (drvidx < 0) + return -ENODEV; + c.command = ISDN_CMD_LOCK; + c.driver = drvidx; + MOD_INC_USE_COUNT; + (void) dev->drv[drvidx]->interface->command(&c); + return 0; + } +#ifdef CONFIG_ISDN_PPP + if (minor <= ISDN_MINOR_PPPMAX) { + int ret; + if (!(ret = isdn_ppp_open(minor - ISDN_MINOR_PPP, filep))) + MOD_INC_USE_COUNT; + return ret; + } +#endif + return -ENODEV; +} + +static void isdn_close(struct inode *ino, struct file *filep) +{ + uint minor = MINOR(ino->i_rdev); + int drvidx; + isdn_ctrl c; + + MOD_DEC_USE_COUNT; + if (minor == ISDN_MINOR_STATUS) { + infostruct *p = dev->infochain; + infostruct *q = NULL; + while (p) { + if (p->private == (char *) &(filep->private_data)) { + if (q) + q->next = p->next; + else + dev->infochain = (infostruct *) (p->next); + return; + } + q = p; + p = (infostruct *) (p->next); + } + printk(KERN_WARNING "isdn: No private data while closing isdnctrl\n"); + return; + } + if (minor < ISDN_MINOR_CTRL) { + drvidx = isdn_minor2drv(minor); + if (drvidx < 0) + return; + c.command = ISDN_CMD_UNLOCK; + c.driver = drvidx; + (void) dev->drv[drvidx]->interface->command(&c); + return; + } + if (minor <= ISDN_MINOR_CTRLMAX) { + drvidx = isdn_minor2drv(minor - ISDN_MINOR_CTRL); + if (drvidx < 0) + return; + if (dev->profd == current) + dev->profd = NULL; + c.command = ISDN_CMD_UNLOCK; + c.driver = drvidx; + (void) dev->drv[drvidx]->interface->command(&c); + return; + } +#ifdef CONFIG_ISDN_PPP + if (minor <= ISDN_MINOR_PPPMAX) + isdn_ppp_release(minor - ISDN_MINOR_PPP, filep); +#endif +} + +static struct file_operations isdn_fops = +{ + isdn_lseek, + isdn_read, + isdn_write, + NULL, /* isdn_readdir */ + isdn_select, /* isdn_select */ + isdn_ioctl, /* isdn_ioctl */ + NULL, /* isdn_mmap */ + isdn_open, + isdn_close, + NULL /* fsync */ +}; + +char * + isdn_map_eaz2msn(char *msn, int di) +{ + driver *this = dev->drv[di]; + int i; + + if (strlen(msn) == 1) { + i = msn[0] - '0'; + if ((i >= 0) && (i <= 9)) + if (strlen(this->msn2eaz[i])) + return (this->msn2eaz[i]); + } + return (msn); +} + +/* + * Find an unused ISDN-channel, whose feature-flags match the + * given L2- and L3-protocols. + */ +int isdn_get_free_channel(int usage, int l2_proto, int l3_proto, int pre_dev + ,int pre_chan) +{ + int i; + ulong flags; + ulong features; + isdn_ctrl cmd; + + save_flags(flags); + cli(); + features = (1 << l2_proto) | (0x100 << l3_proto); + for (i = 0; i < ISDN_MAX_CHANNELS; i++) + if (USG_NONE(dev->usage[i]) && + (dev->drvmap[i] != -1)) { + int d = dev->drvmap[i]; + if ((dev->usage[i] & ISDN_USAGE_EXCLUSIVE) && + ((pre_dev != d) || (pre_chan != dev->chanmap[i]))) + continue; + if ((dev->drv[d]->running)) { + if ((dev->drv[d]->interface->features & features) == features) { + if ((pre_dev < 0) || (pre_chan < 0)) { + dev->usage[i] &= ISDN_USAGE_EXCLUSIVE; + dev->usage[i] |= usage; + isdn_info_update(); + cmd.driver = d; + cmd.arg = 0; + cmd.command = ISDN_CMD_LOCK; + (void) dev->drv[d]->interface->command(&cmd); + restore_flags(flags); + return i; + } else { + if ((pre_dev == d) && (pre_chan == dev->chanmap[i])) { + dev->usage[i] &= ISDN_USAGE_EXCLUSIVE; + dev->usage[i] |= usage; + isdn_info_update(); + cmd.driver = d; + cmd.arg = 0; + cmd.command = ISDN_CMD_LOCK; + (void) dev->drv[d]->interface->command(&cmd); + restore_flags(flags); + return i; + } + } + } + } + } + restore_flags(flags); + return -1; +} + +/* + * Set state of ISDN-channel to 'unused' + */ +void isdn_free_channel(int di, int ch, int usage) +{ + int i; + ulong flags; + isdn_ctrl cmd; + + save_flags(flags); + cli(); + for (i = 0; i < ISDN_MAX_CHANNELS; i++) + if (((dev->usage[i] & ISDN_USAGE_MASK) == usage) && + (dev->drvmap[i] == di) && + (dev->chanmap[i] == ch)) { + dev->usage[i] &= (ISDN_USAGE_NONE | ISDN_USAGE_EXCLUSIVE); + strcpy(dev->num[i], "???"); + dev->ibytes[i] = 0; + dev->obytes[i] = 0; + isdn_info_update(); + isdn_free_queue(&dev->drv[di]->rpqueue[ch]); + cmd.driver = di; + cmd.arg = ch; + cmd.command = ISDN_CMD_UNLOCK; + (void) dev->drv[di]->interface->command(&cmd); + restore_flags(flags); + return; + } + restore_flags(flags); +} + +/* + * Cancel Exclusive-Flag for ISDN-channel + */ +void isdn_unexclusive_channel(int di, int ch) +{ + int i; + ulong flags; + + save_flags(flags); + cli(); + for (i = 0; i < ISDN_MAX_CHANNELS; i++) + if ((dev->drvmap[i] == di) && + (dev->chanmap[i] == ch)) { + dev->usage[i] &= ~ISDN_USAGE_EXCLUSIVE; + isdn_info_update(); + restore_flags(flags); + return; + } + restore_flags(flags); +} + +/* + * receive callback handler for drivers not supporting sk_buff's. + * Parameters: + * + * di = Driver-Index. + * channel = Number of B-Channel (0...) + * buf = pointer to packet-data + * len = Length of packet-data + * + */ +void isdn_receive_callback(int drvidx, int chan, u_char *buf, int len) +{ + struct sk_buff *skb; + + if (dev->global_flags & ISDN_GLOBAL_STOPPED) + return; + skb = dev_alloc_skb(len); + if (skb) { + memcpy(skb_put(skb, len), buf, len); + isdn_receive_skb_callback(drvidx, chan, skb); + } else + printk(KERN_WARNING "isdn: rcv alloc_skb failed, packet dropped.\n"); +} + +/* + * writebuf replacement for SKB_ABLE drivers + */ +int isdn_writebuf_stub(int drvidx, int chan, const u_char *buf, int len, + int user) +{ + int ret; + + if (dev->drv[drvidx]->interface->writebuf) + ret = dev->drv[drvidx]->interface->writebuf(drvidx, chan, buf, + len, user); + else { + struct sk_buff * skb; + + skb = alloc_skb(dev->drv[drvidx]->interface->hl_hdrlen + len, + GFP_ATOMIC); + if (skb == NULL) + return 0; + + skb_reserve(skb, dev->drv[drvidx]->interface->hl_hdrlen); + skb->free = 1; + + if (user) { + if(copy_from_user(skb_put(skb, len), buf, len)) { + kfree_skb(skb,FREE_WRITE); + return -EFAULT; + } + } else + memcpy(skb_put(skb, len), buf, len); + + ret = dev->drv[drvidx]->interface->writebuf_skb(drvidx, + chan, skb); + if (ret <= 0) + kfree_skb(skb, FREE_WRITE); + } + if (ret > 0) + dev->obytes[isdn_dc2minor(drvidx,chan)] += ret; + return ret; +} + +/* + * writebuf_skb replacement for NON SKB_ABLE drivers + * If lowlevel-device does not support supports skbufs, use + * standard send-routine, else sind directly. + * + * Return: length of data on success, -ERRcode on failure. + */ + +int isdn_writebuf_skb_stub(int drvidx, int chan, struct sk_buff * skb) +{ + int ret; + int len = skb->len; /* skb pointer no longer valid after free */ + + if (dev->drv[drvidx]->interface->writebuf_skb) + ret = dev->drv[drvidx]->interface-> + writebuf_skb(drvidx, chan, skb); + else { + if ((ret = dev->drv[drvidx]->interface-> + writebuf(drvidx,chan,skb->data,skb->len,0)) == len) + dev_kfree_skb(skb, FREE_WRITE); + } + if (ret > 0) + dev->obytes[isdn_dc2minor(drvidx,chan)] += len; + return ret; +} + +/* + * Low-level-driver registration + */ + +int register_isdn(isdn_if * i) +{ + driver *d; + int n, j, k; + ulong flags; + int drvidx; + + if (dev->drivers >= ISDN_MAX_DRIVERS) { + printk(KERN_WARNING "register_isdn: Max. %d drivers supported\n", + ISDN_MAX_DRIVERS); + return 0; + } + n = i->channels; + if (dev->channels + n > ISDN_MAX_CHANNELS) { + printk(KERN_WARNING "register_isdn: Max. %d channels supported\n", + ISDN_MAX_CHANNELS); + return 0; + } + if ((!i->writebuf_skb) && (!i->writebuf)) { + printk(KERN_WARNING "register_isdn: No write routine given.\n"); + return 0; + } + if (!(d = (driver *) kmalloc(sizeof(driver), GFP_KERNEL))) { + printk(KERN_WARNING "register_isdn: Could not alloc driver-struct\n"); + return 0; + } + memset((char *) d, 0, sizeof(driver)); + if (!(d->rcverr = (int *) kmalloc(sizeof(int) * n, GFP_KERNEL))) { + printk(KERN_WARNING "register_isdn: Could not alloc rcverr\n"); + kfree(d); + return 0; + } + memset((char *) d->rcverr, 0, sizeof(int) * n); + if (!(d->rcvcount = (int *) kmalloc(sizeof(int) * n, GFP_KERNEL))) { + printk(KERN_WARNING "register_isdn: Could not alloc rcvcount\n"); + kfree(d->rcverr); + kfree(d); + return 0; + } + memset((char *) d->rcvcount, 0, sizeof(int) * n); + if (!(d->rpqueue = + (struct sk_buff_head *) kmalloc(sizeof(struct sk_buff_head) * n, GFP_KERNEL))) { + printk(KERN_WARNING "register_isdn: Could not alloc rpqueue\n"); + kfree(d->rcvcount); + kfree(d->rcverr); + kfree(d); + return 0; + } + for (j = 0; j < n; j++) { + skb_queue_head_init(&d->rpqueue[j]); + } + if (!(d->rcv_waitq = (struct wait_queue **) + kmalloc(sizeof(struct wait_queue *) * n, GFP_KERNEL))) { + printk(KERN_WARNING "register_isdn: Could not alloc rcv_waitq\n"); + kfree(d->rpqueue); + kfree(d->rcvcount); + kfree(d->rcverr); + kfree(d); + return 0; + } + memset((char *) d->rcv_waitq, 0, sizeof(struct wait_queue *) * n); + if (!(d->snd_waitq = (struct wait_queue **) + kmalloc(sizeof(struct wait_queue *) * n, GFP_KERNEL))) { + printk(KERN_WARNING "register_isdn: Could not alloc snd_waitq\n"); + kfree(d->rcv_waitq); + kfree(d->rpqueue); + kfree(d->rcvcount); + kfree(d->rcverr); + kfree(d); + return 0; + } + memset((char *) d->snd_waitq, 0, sizeof(struct wait_queue *) * n); + d->channels = n; + d->loaded = 1; + d->maxbufsize = i->maxbufsize; + d->pktcount = 0; + d->stavail = 0; + d->running = 0; + d->flags = 0; + d->interface = i; + for (drvidx = 0; drvidx < ISDN_MAX_DRIVERS; drvidx++) + if (!dev->drv[drvidx]) + break; + i->channels = drvidx; + + i->rcvcallb_skb = isdn_receive_skb_callback; + i->rcvcallb = isdn_receive_callback; + i->statcallb = isdn_status_callback; + if (!strlen(i->id)) + sprintf(i->id, "line%d", drvidx); + save_flags(flags); + cli(); + for (j = 0; j < drvidx; j++) + if (!strcmp(i->id,dev->drvid[j])) + sprintf(i->id, "line%d", drvidx); + for (j = 0; j < n; j++) + for (k = 0; k < ISDN_MAX_CHANNELS; k++) + if (dev->chanmap[k] < 0) { + dev->chanmap[k] = j; + dev->drvmap[k] = drvidx; + break; + } + dev->drv[drvidx] = d; + dev->channels += n; + strcpy(dev->drvid[drvidx], i->id); + isdn_info_update(); + dev->drivers++; + restore_flags(flags); + return 1; +} + +/* + ***************************************************************************** + * And now the modules code. + ***************************************************************************** + */ + +extern int printk(const char *fmt,...); + +#ifdef MODULE +#define isdn_init init_module +#endif + +static char *isdn_getrev(const char *revision) +{ + char *rev; + char *p; + + if ((p = strchr(revision, ':'))) { + rev = p + 2; + p = strchr(rev, '$'); + *--p = 0; + } else + rev = "???"; + return rev; +} + +static struct symbol_table isdn_syms = { +#include <linux/symtab_begin.h> + X(register_isdn), +#include <linux/symtab_end.h> +}; + +static void isdn_export_syms(void) +{ + register_symtab(&isdn_syms); + has_exported = 1; +} + +/* + * Allocate and initialize all data, register modem-devices + */ +int isdn_init(void) +{ + int i; + char irev[50]; + char trev[50]; + char nrev[50]; + char prev[50]; + char arev[50]; + + sti(); + if (!(dev = (isdn_dev *) kmalloc(sizeof(isdn_dev), GFP_KERNEL))) { + printk(KERN_WARNING "isdn: Could not allocate device-struct.\n"); + return -EIO; + } + memset((char *) dev, 0, sizeof(isdn_dev)); +#ifdef NEW_ISDN_TIMER_CTRL + init_timer(&dev->timer); + dev->timer.function = isdn_timer_funct; +#endif + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + dev->drvmap[i] = -1; + dev->chanmap[i] = -1; + dev->m_idx[i] = -1; + strcpy(dev->num[i], "???"); + } + if (register_chrdev(ISDN_MAJOR, "isdn", &isdn_fops)) { + printk(KERN_WARNING "isdn: Could not register control devices\n"); + kfree(dev); + return -EIO; + } + if ((i = isdn_tty_modem_init()) < 0) { + printk(KERN_WARNING "isdn: Could not register tty devices\n"); + if (i == -3) + tty_unregister_driver(&dev->mdm.cua_modem); + if (i <= -2) + tty_unregister_driver(&dev->mdm.tty_modem); + kfree(dev); + unregister_chrdev(ISDN_MAJOR, "isdn"); + return -EIO; + } +#ifdef CONFIG_ISDN_PPP + if (isdn_ppp_init() < 0) { + printk(KERN_WARNING "isdn: Could not create PPP-device-structs\n"); + tty_unregister_driver(&dev->mdm.tty_modem); + tty_unregister_driver(&dev->mdm.cua_modem); + for (i = 0; i < ISDN_MAX_CHANNELS; i++) + kfree(dev->mdm.info[i].xmit_buf - 4); + unregister_chrdev(ISDN_MAJOR, "isdn"); + kfree(dev); + return -EIO; + } +#endif /* CONFIG_ISDN_PPP */ + + if (!has_exported) + isdn_export_syms(); + + strcpy(irev,isdn_revision); + strcpy(trev,isdn_tty_revision); + strcpy(nrev,isdn_net_revision); + strcpy(prev,isdn_ppp_revision); + strcpy(arev,isdn_audio_revision); + printk(KERN_NOTICE "ISDN subsystem Rev: %s/", isdn_getrev(irev)); + printk("%s/", isdn_getrev(trev)); + printk("%s/", isdn_getrev(nrev)); + printk("%s/", isdn_getrev(prev)); + printk("%s", isdn_getrev(arev)); + +#ifdef MODULE + printk(" loaded\n"); +#else + printk("\n"); + isdn_cards_init(); +#endif + isdn_info_update(); + return 0; +} + +#ifdef MODULE +/* + * Unload module + */ +void cleanup_module(void) +{ + int flags; + int i; + +#ifdef CONFIG_ISDN_PPP + isdn_ppp_cleanup(); +#endif + save_flags(flags); + cli(); + if (isdn_net_rmall() < 0) { + printk(KERN_WARNING "isdn: net-device busy, remove cancelled\n"); + restore_flags(flags); + return; + } + if (tty_unregister_driver(&dev->mdm.tty_modem)) { + printk(KERN_WARNING "isdn: ttyI-device busy, remove cancelled\n"); + restore_flags(flags); + return; + } + if (tty_unregister_driver(&dev->mdm.cua_modem)) { + printk(KERN_WARNING "isdn: cui-device busy, remove cancelled\n"); + restore_flags(flags); + return; + } + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + isdn_tty_cleanup_xmit(&dev->mdm.info[i]); + kfree(dev->mdm.info[i].xmit_buf - 4); + } + if (unregister_chrdev(ISDN_MAJOR, "isdn") != 0) { + printk(KERN_WARNING "isdn: controldevice busy, remove cancelled\n"); + } else { + del_timer(&dev->timer); + kfree(dev); + printk(KERN_NOTICE "ISDN-subsystem unloaded\n"); + } + restore_flags(flags); +} +#endif diff --git a/drivers/isdn/isdn_common.h b/drivers/isdn/isdn_common.h new file mode 100644 index 000000000..81d7905a2 --- /dev/null +++ b/drivers/isdn/isdn_common.h @@ -0,0 +1,63 @@ +/* $Id: isdn_common.h,v 1.3 1996/05/19 00:13:05 fritz Exp $ + * + * header for Linux ISDN subsystem, common used functions and debugging-switches (linklevel). + * + * Copyright 1994,95,96 by Fritz Elfert (fritz@wuemaus.franken.de) + * Copyright 1995,96 by Thinking Objects Software GmbH Wuerzburg + * Copyright 1995,96 by Michael Hipp (Michael.Hipp@student.uni-tuebingen.de) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Log: isdn_common.h,v $ + * Revision 1.3 1996/05/19 00:13:05 fritz + * Removed debug flag. + * + * Revision 1.2 1996/04/20 16:20:40 fritz + * Misc. typos. + * + * Revision 1.1 1996/01/10 21:37:19 fritz + * Initial revision + * + */ + +#undef ISDN_DEBUG_MODEM_OPEN +#undef ISDN_DEBUG_MODEM_IOCTL +#undef ISDN_DEBUG_MODEM_WAITSENT +#undef ISDN_DEBUG_MODEM_HUP +#undef ISDN_DEBUG_MODEM_ICALL +#undef ISDN_DEBUG_MODEM_DUMP +#undef ISDN_DEBUG_AT +#undef ISDN_DEBUG_NET_DUMP +#undef ISDN_DEBUG_NET_DIAL +#undef ISDN_DEBUG_NET_ICALL + +/* Prototypes */ +extern void isdn_MOD_INC_USE_COUNT(void); +extern void isdn_MOD_DEC_USE_COUNT(void); +extern void isdn_free_channel(int di, int ch, int usage); +extern void isdn_all_eaz(int di, int ch); +extern int isdn_dc2minor(int di, int ch); +extern void isdn_info_update(void); +extern char* isdn_map_eaz2msn(char *msn, int di); +extern void isdn_timer_ctrl(int tf, int onoff); +extern void isdn_unexclusive_channel(int di, int ch); +extern int isdn_getnum(char **); +extern int isdn_readbchan (int, int, u_char *, u_char *, int, int); +extern int isdn_get_free_channel(int, int, int, int, int); +extern int isdn_writebuf_stub(int, int, const u_char *, int, int); +extern int isdn_writebuf_skb_stub(int, int, struct sk_buff *); +#if defined(ISDN_DEBUG_NET_DUMP) || defined(ISDN_DEBUG_MODEM_DUMP) +extern void isdn_dumppkt(char *, u_char *, int, int); +#endif diff --git a/drivers/isdn/isdn_net.c b/drivers/isdn/isdn_net.c new file mode 100644 index 000000000..99b3ae746 --- /dev/null +++ b/drivers/isdn/isdn_net.c @@ -0,0 +1,2431 @@ +/* $Id: isdn_net.c,v 1.20 1996/08/29 20:06:03 fritz Exp $ + * + * Linux ISDN subsystem, network interfaces and related functions (linklevel). + * + * Copyright 1994,95,96 by Fritz Elfert (fritz@wuemaus.franken.de) + * Copyright 1995,96 by Thinking Objects Software GmbH Wuerzburg + * Copyright 1995,96 by Michael Hipp (Michael.Hipp@student.uni-tuebingen.de) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Log: isdn_net.c,v $ + * Revision 1.20 1996/08/29 20:06:03 fritz + * Bugfix: Transmission timeout had been much to low. + * + * Revision 1.19 1996/08/12 16:24:32 hipp + * removed some (now) obsolete functions for syncPPP in rebuild_header etc. + * + * Revision 1.18 1996/07/03 13:48:51 hipp + * bugfix: Call dev_purge_queues() only for master device + * + * Revision 1.17 1996/06/25 18:37:37 fritz + * Fixed return count for empty return string in isdn_net_getphones(). + * + * Revision 1.16 1996/06/24 17:48:08 fritz + * Bugfixes: + * - Did not free channel on unbinding. + * - ioctl returned wrong callback settings. + * + * Revision 1.15 1996/06/16 17:42:54 tsbogend + * fixed problem with IP addresses on Linux/Alpha (long is 8 byte there) + * + * Revision 1.14 1996/06/11 14:54:08 hipp + * minor bugfix in isdn_net_send_skb + * changes in BSENT callback handler for syncPPP + * added lp->sav_skb stuff + * + * Revision 1.13 1996/06/06 14:25:44 fritz + * Changed loglevel of "incoming ... without OAD" message, since + * with audio support this is quite normal. + * + * Revision 1.12 1996/06/05 02:36:45 fritz + * Minor bugfixes by M. Hipp. + * + * Revision 1.11 1996/05/18 01:36:59 fritz + * Added spelling corrections and some minor changes + * to stay in sync with kernel. + * + * Revision 1.10 1996/05/17 03:49:01 fritz + * Some cleanup. + * + * Revision 1.9 1996/05/06 11:34:57 hipp + * fixed a few bugs + * + * Revision 1.8 1996/04/30 21:04:40 fritz + * Test commit + * + * Revision 1.7 1996/04/30 11:10:42 fritz + * Added Michael's ippp-bind patch. + * + * Revision 1.6 1996/04/30 09:34:35 fritz + * Removed compatibility-macros. + * + * Revision 1.5 1996/04/20 16:28:38 fritz + * Made more parameters of the dial statemachine user-configurable and + * added hangup after dial for more reliability using callback. + * Changed all io going through generic routines in isdn_common.c + * Added missing call to dev_free_skb on failed dialing. + * Added uihdlc encapsulation. + * Fixed isdn_net_setcfg not to destroy interface-flags anymore. + * Misc. typos. + * + * Revision 1.4 1996/02/19 15:23:38 fritz + * Bugfix: Sync-PPP packets got compressed twice, when resent due to + * send-queue-full reject. + * + * Revision 1.3 1996/02/11 02:22:28 fritz + * Changed status- receive-callbacks to use pointer-arrays for finding + * a corresponding interface instead of looping over all interfaces. + * Activate Auto-hangup-timer only when interface is online. + * Some bugfixes in the dialing-statemachine. + * Lot of bugfixes in sk_buff'ized encapsulation handling. + * For speedup connection-setup after dialing, remember sk_buf that triggered + * dialing. + * Fixed isdn_net_log_packet according to different encapsulations. + * Correct ARP-handling for ETHERNET-encapsulation. + * + * Revision 1.2 1996/01/22 05:05:12 fritz + * Changed returncode-logic for isdn_net_start_xmit() and its + * helper-functions. + * Changed handling of buildheader for RAWIP and ETHERNET-encapsulation. + * + * Revision 1.1 1996/01/09 04:12:34 fritz + * Initial revision + * + */ + +#include <asm/uaccess.h> +#include <linux/config.h> +#define __NO_VERSION__ +#include <linux/module.h> +#include <linux/isdn.h> +#include <linux/if_arp.h> +#include "isdn_common.h" +#include "isdn_net.h" +#ifdef CONFIG_ISDN_PPP +#include "isdn_ppp.h" +#endif + +/* In ksyms.c, but why not in some .h ??? */ +extern int arp_find(unsigned char *, u32, struct device *, u32, + struct sk_buff *); + +/* Prototypes */ + +int isdn_net_force_dial_lp(isdn_net_local *); +static int isdn_net_wildmat(char *s, char *p); +static int isdn_net_start_xmit(struct sk_buff *, struct device *); +static int isdn_net_xmit(struct device *, isdn_net_local *, struct sk_buff *); + +extern void dev_purge_queues(struct device *dev); /* move this to net/core/dev.c */ + +char *isdn_net_revision = "$Revision: 1.20 $"; + + /* + * Code for raw-networking over ISDN + */ + +static void +isdn_net_reset(struct device *dev) +{ + ulong flags; + + save_flags(flags); + cli(); /* Avoid glitch on writes to CMD regs */ + dev->interrupt = 0; + dev->tbusy = 0; + restore_flags(flags); +} + +/* Open/initialize the board. */ +static int +isdn_net_open(struct device *dev) +{ + int i; + struct device *p; + + isdn_net_reset(dev); + dev->start = 1; + /* Fill in the MAC-level header. */ + for (i = 0; i < ETH_ALEN - sizeof(u32); i++) + dev->dev_addr[i] = 0xfc; + memcpy(&(dev->dev_addr[i]), &dev->pa_addr, sizeof(u32)); + + /* If this interface has slaves, start them also */ + + if ((p = (((isdn_net_local *) dev->priv)->slave))) { + while (p) { + isdn_net_reset(p); + p->start = 1; + p = (((isdn_net_local *) p->priv)->slave); + } + } + + isdn_MOD_INC_USE_COUNT(); + return 0; +} + +/* + Assign an ISDN-channel to a net-interface + */ +static void +isdn_net_bind_channel(isdn_net_local * lp, int idx) +{ + ulong flags; + + save_flags(flags); + cli(); + lp->isdn_device = dev->drvmap[idx]; + lp->isdn_channel = dev->chanmap[idx]; + dev->rx_netdev[idx] = lp->netdev; + dev->st_netdev[idx] = lp->netdev; + restore_flags(flags); +} + +/* + * unbind a net-interface (resets interface after an error) + */ +static void +isdn_net_unbind_channel(isdn_net_local * lp) +{ + ulong flags; + + save_flags(flags); + cli(); + if (lp->first_skb) { + dev_kfree_skb(lp->first_skb,FREE_WRITE); + lp->first_skb = NULL; + } + if(lp->sav_skb) { + dev_kfree_skb(lp->sav_skb,FREE_WRITE); + lp->sav_skb = NULL; + } + if(!lp->master) /* purge only for master device */ + dev_purge_queues(&lp->netdev->dev); + lp->dialstate = 0; + dev->rx_netdev[isdn_dc2minor(lp->isdn_device,lp->isdn_channel)] = NULL; + dev->st_netdev[isdn_dc2minor(lp->isdn_device,lp->isdn_channel)] = NULL; + isdn_free_channel(lp->isdn_device, lp->isdn_channel, ISDN_USAGE_NET); + lp->flags &= ~ISDN_NET_CONNECTED; + lp->isdn_device = -1; + lp->isdn_channel = -1; + + restore_flags(flags); +} + +/* + * Perform auto-hangup and cps-calculation for net-interfaces. + * + * auto-hangup: + * Increment idle-counter (this counter is reset on any incoming or + * outgoing packet), if counter exceeds configured limit either do a + * hangup immediately or - if configured - wait until just before the next + * charge-info. + * + * cps-calculation (needed for dynamic channel-bundling): + * Since this function is called every second, simply reset the + * byte-counter of the interface after copying it to the cps-variable. + */ +void +isdn_net_autohup() +{ + isdn_net_dev *p = dev->netdev; + int anymore; + ulong flags; + + save_flags(flags); + cli(); + anymore = 0; + while (p) { + isdn_net_local *l = (isdn_net_local *) & (p->local); + l->cps = l->transcount; + l->transcount = 0; + if (dev->net_verbose > 3) + printk(KERN_DEBUG "%s: %d bogocps\n", l->name, l->cps); + if ((l->flags & ISDN_NET_CONNECTED) && (!l->dialstate)) { + anymore = 1; + l->huptimer++; + if ((l->onhtime) && (l->huptimer > l->onhtime)) + if (l->outgoing) { + if (l->hupflags & 4) { + if (l->hupflags & 1) + isdn_net_hangup(&p->dev); + else if (jiffies - l->chargetime > l->chargeint) + isdn_net_hangup(&p->dev); + } else + isdn_net_hangup(&p->dev); + } else if (l->hupflags & 8) + isdn_net_hangup(&p->dev); + } + p = (isdn_net_dev *) p->next; + } + isdn_timer_ctrl(ISDN_TIMER_NETHANGUP,anymore); + restore_flags(flags); +} + +/* + * Handle status-messages from ISDN-interfacecard. + * This function is called from within the main-status-dispatcher + * isdn_status_callback, which itself is called from the low-level driver. + * Return: 1 = Event handled, 0 = not for us or unknown Event. + */ +int +isdn_net_stat_callback(int idx, int cmd) +{ + isdn_net_dev *p = dev->st_netdev[idx]; + + if (p) { + isdn_net_local *lp = &(p->local); + switch (cmd) { + case ISDN_STAT_BSENT: + /* A packet has successfully been sent out */ + if ((lp->flags & ISDN_NET_CONNECTED) && + (!lp->dialstate)) { + lp->stats.tx_packets++; + if(lp->p_encap == ISDN_NET_ENCAP_SYNCPPP && lp->sav_skb) { + struct device *mdev; + if(lp->master) + mdev = lp->master; + else + mdev = &lp->netdev->dev; + if(!isdn_net_send_skb(mdev,lp,lp->sav_skb)) { + lp->sav_skb = NULL; + mark_bh(NET_BH); + } + else { + return 1; + } + } + if (clear_bit(0,(void*)&(p->dev.tbusy))) + mark_bh(NET_BH); + } + return 1; + case ISDN_STAT_DCONN: + /* D-Channel is up */ + switch (lp->dialstate) { + case 4: + case 7: + case 8: + lp->dialstate++; + return 1; + case 12: + lp->dialstate = 5; + return 1; + } + break; + case ISDN_STAT_DHUP: + /* Either D-Channel-hangup or error during dialout */ + if ((!lp->dialstate) && (lp->flags & ISDN_NET_CONNECTED)) { + lp->flags &= ~ISDN_NET_CONNECTED; + if(lp->first_skb) { + dev_kfree_skb(lp->first_skb,FREE_WRITE); + lp->first_skb = NULL; + } + if(lp->sav_skb) { + dev_kfree_skb(lp->sav_skb,FREE_WRITE); + lp->sav_skb = NULL; + } + isdn_free_channel(lp->isdn_device, lp->isdn_channel, + ISDN_USAGE_NET); +#ifdef CONFIG_ISDN_PPP + isdn_ppp_free(lp); +#endif + isdn_all_eaz(lp->isdn_device, lp->isdn_channel); + printk(KERN_INFO "%s: remote hangup\n", lp->name); + printk(KERN_INFO "%s: Chargesum is %d\n", lp->name, + lp->charge); + lp->isdn_device = -1; + lp->isdn_channel = -1; + dev->st_netdev[idx] = NULL; + dev->rx_netdev[idx] = NULL; + return 1; + } + break; + case ISDN_STAT_BCONN: + /* B-Channel is up */ + switch (lp->dialstate) { + case 5: + case 6: + case 7: + case 8: + case 9: + case 10: + case 12: + if (lp->dialstate <= 6) { + dev->usage[idx] |= ISDN_USAGE_OUTGOING; + isdn_info_update(); + } else + dev->rx_netdev[idx] = p; + lp->dialstate = 0; + isdn_timer_ctrl(ISDN_TIMER_NETHANGUP,1); + printk(KERN_INFO "isdn_net: %s connected\n", lp->name); + /* If first Chargeinfo comes before B-Channel connect, + * we correct the timestamp here. + */ + lp->chargetime = jiffies; + /* Immediately send first skb to speed up arp */ +#ifdef CONFIG_ISDN_PPP + if(lp->p_encap == ISDN_NET_ENCAP_SYNCPPP) + isdn_ppp_wakeup_daemon(lp); +#endif + if (lp->first_skb) { + if (!(isdn_net_xmit(&p->dev,lp,lp->first_skb))) + lp->first_skb = NULL; + } + return 1; + } + break; + case ISDN_STAT_NODCH: + /* No D-Channel avail. */ + if (lp->dialstate == 4) { + lp->dialstate--; + return 1; + } + break; + case ISDN_STAT_CINF: + /* Charge-info from TelCo. Calculate interval between + * charge-infos and set timestamp for last info for + * usage by isdn_net_autohup() + */ + lp->charge++; + if (lp->hupflags & 2) { + lp->hupflags &= ~1; + lp->chargeint = jiffies - lp->chargetime - (2 * HZ); + } + if (lp->hupflags & 1) + lp->hupflags |= 2; + lp->chargetime = jiffies; + return 1; + } + } + return 0; +} + +/* + * Check, if a number contains wildcard-characters, in which case it + * is for incoming purposes only. + */ +static int +isdn_net_checkwild(char *num) +{ + return ((strchr(num, '?')) || + (strchr(num, '*')) || + (strchr(num, '[')) || + (strchr(num, ']')) || + (strchr(num, '^'))); +} + +/* + * Perform dialout for net-interfaces and timeout-handling for + * D-Channel-up and B-Channel-up Messages. + * This function is initially called from within isdn_net_start_xmit() or + * or isdn_net_find_icall() after initializing the dialstate for an + * interface. If further calls are needed, the function schedules itself + * for a timer-callback via isdn_timer_function(). + * The dialstate is also affected by incoming status-messages from + * the ISDN-Channel which are handled in isdn_net_stat_callback() above. + */ +void +isdn_net_dial(void) +{ + isdn_net_dev *p = dev->netdev; + int anymore = 0; + int i; + isdn_ctrl cmd; + + while (p) { +#ifdef ISDN_DEBUG_NET_DIAL + if (p->local.dialstate) + printk(KERN_DEBUG "%s: dialstate=%d\n", p->local.name,p->local.dialstate); +#endif + switch (p->local.dialstate) { + case 0: + /* Nothing to do for this interface */ + break; + case 1: + /* Initiate dialout. Set phone-number-pointer to first number + * of interface. + */ + p->local.dial = p->local.phone[1]; + anymore = 1; + p->local.dialstate++; + break; + /* Prepare dialing. Clear EAZ, then set EAZ. */ + case 2: + cmd.driver = p->local.isdn_device; + cmd.arg = p->local.isdn_channel; + cmd.command = ISDN_CMD_CLREAZ; + dev->drv[p->local.isdn_device]->interface->command(&cmd); + sprintf(cmd.num, "%s", isdn_map_eaz2msn(p->local.msn, cmd.driver)); + cmd.command = ISDN_CMD_SETEAZ; + dev->drv[p->local.isdn_device]->interface->command(&cmd); + p->local.dialretry = 0; + anymore = 1; + p->local.dialstate++; + break; + case 3: + /* Setup interface, dial current phone-number, switch to next number. + * If list of phone-numbers is exhausted, increment + * retry-counter. + */ + cmd.driver = p->local.isdn_device; + cmd.command = ISDN_CMD_SETL2; + cmd.arg = p->local.isdn_channel + (p->local.l2_proto << 8); + dev->drv[p->local.isdn_device]->interface->command(&cmd); + cmd.driver = p->local.isdn_device; + cmd.command = ISDN_CMD_SETL3; + cmd.arg = p->local.isdn_channel + (p->local.l3_proto << 8); + dev->drv[p->local.isdn_device]->interface->command(&cmd); + cmd.driver = p->local.isdn_device; + cmd.arg = p->local.isdn_channel; + p->local.huptimer = 0; + p->local.outgoing = 1; + p->local.hupflags |= 1; + p->local.hupflags &= ~2; + if (!strcmp(p->local.dial->num, "LEASED")) { + p->local.dialstate = 4; + printk(KERN_INFO "%s: Open leased line ...\n", p->local.name); + } else { + cmd.command = ISDN_CMD_DIAL; + sprintf(cmd.num, "%s,%s,7,0", p->local.dial->num, + isdn_map_eaz2msn(p->local.msn, cmd.driver)); + i = isdn_dc2minor(p->local.isdn_device, p->local.isdn_channel); + if (i >= 0) { + strcpy(dev->num[i], p->local.dial->num); + isdn_info_update(); + } + printk(KERN_INFO "%s: dialing %d %s...\n", p->local.name, + p->local.dialretry, p->local.dial->num); + /* + * Switch to next number or back to start if at end of list. + */ + if (!(p->local.dial = (isdn_net_phone *) p->local.dial->next)) { + p->local.dial = p->local.phone[1]; + p->local.dialretry++; + } + p->local.dtimer = 0; +#ifdef ISDN_DEBUG_NET_DIAL + printk(KERN_DEBUG "dial: d=%d c=%d\n", p->local.isdn_device, + p->local.isdn_channel); +#endif + dev->drv[p->local.isdn_device]->interface->command(&cmd); + } + anymore = 1; + p->local.dialstate = + (p->local.cbdelay && + (p->local.flags & ISDN_NET_CBOUT))?12:4; + break; + case 4: + /* Wait for D-Channel-connect. + * If timeout and max retries not + * reached, switch back to state 3. + */ + if (p->local.dtimer++ > ISDN_TIMER_DTIMEOUT10) + if (p->local.dialretry < p->local.dialmax) { + p->local.dialstate = 3; + } else + isdn_net_hangup(&p->dev); + anymore = 1; + break; + case 5: + /* Got D-Channel-Connect, send B-Channel-request */ + cmd.driver = p->local.isdn_device; + cmd.arg = p->local.isdn_channel; + cmd.command = ISDN_CMD_ACCEPTB; + anymore = 1; + p->local.dtimer = 0; + p->local.dialstate++; + dev->drv[p->local.isdn_device]->interface->command(&cmd); + break; + case 6: + /* Wait for B- or D-Channel-connect. If timeout, + * switch back to state 3. + */ +#ifdef ISDN_DEBUG_NET_DIAL + printk(KERN_DEBUG "dialtimer2: %d\n", p->local.dtimer); +#endif + if (p->local.dtimer++ > ISDN_TIMER_DTIMEOUT10) + p->local.dialstate = 3; + anymore = 1; + break; + case 7: + /* Got incoming Call, setup L2 and L3 protocols, + * then wait for D-Channel-connect + */ +#ifdef ISDN_DEBUG_NET_DIAL + printk(KERN_DEBUG "dialtimer4: %d\n", p->local.dtimer); +#endif + cmd.driver = p->local.isdn_device; + cmd.command = ISDN_CMD_SETL2; + cmd.arg = p->local.isdn_channel + (p->local.l2_proto << 8); + dev->drv[p->local.isdn_device]->interface->command(&cmd); + cmd.driver = p->local.isdn_device; + cmd.command = ISDN_CMD_SETL3; + cmd.arg = p->local.isdn_channel + (p->local.l3_proto << 8); + dev->drv[p->local.isdn_device]->interface->command(&cmd); + if (p->local.dtimer++ > ISDN_TIMER_DTIMEOUT15) + isdn_net_hangup(&p->dev); + else { + anymore = 1; + p->local.dialstate++; + } + break; + case 9: + /* Got incoming D-Channel-Connect, send B-Channel-request */ + cmd.driver = p->local.isdn_device; + cmd.arg = p->local.isdn_channel; + cmd.command = ISDN_CMD_ACCEPTB; + dev->drv[p->local.isdn_device]->interface->command(&cmd); + anymore = 1; + p->local.dtimer = 0; + p->local.dialstate++; + break; + case 8: + case 10: + /* Wait for B- or D-channel-connect */ +#ifdef ISDN_DEBUG_NET_DIAL + printk(KERN_DEBUG "dialtimer4: %d\n", p->local.dtimer); +#endif + if (p->local.dtimer++ > ISDN_TIMER_DTIMEOUT10) + isdn_net_hangup(&p->dev); + else + anymore = 1; + break; + case 11: + /* Callback Delay */ + if (p->local.dtimer++ > p->local.cbdelay) + p->local.dialstate = 1; + anymore = 1; + break; + case 12: + /* Remote does callback. Hangup after cbdelay, then wait for incoming + * call (in state 4). + */ + if (p->local.dtimer++ > p->local.cbdelay) { + printk(KERN_INFO "%s: hangup waiting for callback ...\n", p->local.name); + p->local.dtimer = 0; + p->local.dialstate = 4; + cmd.driver = p->local.isdn_device; + cmd.command = ISDN_CMD_HANGUP; + cmd.arg = p->local.isdn_channel; + (void) dev->drv[cmd.driver]->interface->command(&cmd); + isdn_all_eaz(p->local.isdn_device, p->local.isdn_channel); + } + anymore = 1; + break; + default: + printk(KERN_WARNING "isdn_net: Illegal dialstate %d for device %s\n", + p->local.dialstate, p->local.name); + } + p = (isdn_net_dev *) p->next; + } + isdn_timer_ctrl(ISDN_TIMER_NETDIAL, anymore); +} + +/* + * Perform hangup for a net-interface. + */ +void +isdn_net_hangup(struct device *d) +{ + isdn_net_local *lp = (isdn_net_local *) d->priv; + isdn_ctrl cmd; + + if (lp->flags & ISDN_NET_CONNECTED) { + printk(KERN_INFO "isdn_net: local hangup %s\n", lp->name); +#ifdef CONFIG_ISDN_PPP + isdn_ppp_free(lp); +#endif + cmd.driver = lp->isdn_device; + cmd.command = ISDN_CMD_HANGUP; + cmd.arg = lp->isdn_channel; + (void) dev->drv[cmd.driver]->interface->command(&cmd); + printk(KERN_INFO "%s: Chargesum is %d\n", lp->name, lp->charge); + isdn_all_eaz(lp->isdn_device, lp->isdn_channel); + } + isdn_net_unbind_channel(lp); +} + +typedef struct { + unsigned short source; + unsigned short dest; +} ip_ports; + +static void +isdn_net_log_packet(u_char * buf, isdn_net_local * lp) +{ + u_char *p = buf; + unsigned short proto = ETH_P_IP; + int data_ofs; + ip_ports *ipp; + char addinfo[100]; + + addinfo[0] = '\0'; + switch (lp->p_encap) { + case ISDN_NET_ENCAP_IPTYP: + proto = ntohs(*(unsigned short *)&buf[0]); + p = &buf[2]; + break; + case ISDN_NET_ENCAP_ETHER: + proto = ntohs(*(unsigned short *)&buf[12]); + p = &buf[14]; + break; + case ISDN_NET_ENCAP_CISCOHDLC: + proto = ntohs(*(unsigned short *)&buf[2]); + p = &buf[4]; + break; + } + data_ofs = ((p[0] & 15) * 4); + switch (proto) { + case ETH_P_IP: + switch (p[9]) { + case 1: + strcpy(addinfo, " ICMP"); + break; + case 2: + strcpy(addinfo, " IGMP"); + break; + case 4: + strcpy(addinfo, " IPIP"); + break; + case 6: + ipp = (ip_ports *) (&p[data_ofs]); + sprintf(addinfo, " TCP, port: %d -> %d", ntohs(ipp->source), + ntohs(ipp->dest)); + break; + case 8: + strcpy(addinfo, " EGP"); + break; + case 12: + strcpy(addinfo, " PUP"); + break; + case 17: + ipp = (ip_ports *) (&p[data_ofs]); + sprintf(addinfo, " UDP, port: %d -> %d", ntohs(ipp->source), + ntohs(ipp->dest)); + break; + case 22: + strcpy(addinfo, " IDP"); + break; + } + printk(KERN_INFO "OPEN: %d.%d.%d.%d -> %d.%d.%d.%d%s\n", + p[12], p[13], p[14], p[15], + p[16], p[17], p[18], p[19], + addinfo); + break; + case ETH_P_ARP: + printk(KERN_INFO "OPEN: ARP %d.%d.%d.%d -> *.*.*.* ?%d.%d.%d.%d\n", + p[14], p[15], p[16], p[17], + p[24], p[25], p[26], p[27]); + break; + } +} + +/* + * Generic routine to send out an skbuf. + * If lowlevel-device does not support supports skbufs, use + * standard send-routine, else send directly. + * + * Return: 0 on success, !0 on failure. + * Side-effects: ndev->tbusy is cleared on success. + */ +int +isdn_net_send_skb(struct device *ndev, isdn_net_local *lp, + struct sk_buff *skb) +{ + int ret; + int len = skb->len; /* save len */ + + ret = isdn_writebuf_skb_stub(lp->isdn_device, lp->isdn_channel, skb); + if (ret == len) { + lp->transcount += len; + clear_bit(0, (void *)&(ndev->tbusy)); + return 0; + } + if (ret < 0) { + skb->free = 1; + dev_kfree_skb(skb, FREE_WRITE); + lp->stats.tx_errors++; + clear_bit(0, (void *)&(ndev->tbusy)); + return 0; + } + return 1; +} + + +/* + * Helper function for isdn_net_start_xmit. + * When called, the connection is already established. + * Based on cps-calculation, check if device is overloaded. + * If so, and if a slave exists, trigger dialing for it. + * If any slave is online, deliver packets using a simple round robin + * scheme. + * + * Return: 0 on success, !0 on failure. + */ + +static int +isdn_net_xmit(struct device *ndev, isdn_net_local *lp, struct sk_buff *skb) +{ + int ret; + + /* For the other encaps the header has already been built */ +#ifdef CONFIG_ISDN_PPP + if (lp->p_encap == ISDN_NET_ENCAP_SYNCPPP) { + return isdn_ppp_xmit(skb, ndev); + } +#endif + /* Reset hangup-timeout */ + lp->huptimer = 0; + if (lp->cps > 7000) { + /* Device overloaded */ + + /* + * Packet-delivery via round-robin over master + * and all connected slaves. + */ + if (lp->master) + /* Slaves always deliver themselves */ + ret = isdn_net_send_skb(ndev, lp, skb); + else { + isdn_net_local *slp = (isdn_net_local *) (lp->srobin->priv); + /* Master delivers via srobin and maintains srobin */ + if (lp->srobin == ndev) + ret = isdn_net_send_skb(ndev, lp, skb); + else + ret = ndev->tbusy = isdn_net_start_xmit(skb, lp->srobin); + lp->srobin = (slp->slave) ? slp->slave : ndev; + slp = (isdn_net_local *) (lp->srobin->priv); + if (!((slp->flags & ISDN_NET_CONNECTED) && (slp->dialstate == 0))) + lp->srobin = ndev; + } + /* Slave-startup using delay-variable */ + if (lp->slave) { + if (!lp->sqfull) { + /* First time overload: set timestamp only */ + lp->sqfull = 1; + lp->sqfull_stamp = jiffies; + } + else { + /* subsequent overload: if slavedelay exceeded, start dialing */ + if ((jiffies - lp->sqfull_stamp) > lp->slavedelay) + isdn_net_force_dial_lp((isdn_net_local *) lp->slave->priv); + } + } + } + else { + /* Not overloaded, deliver locally */ + ret = isdn_net_send_skb(ndev, lp, skb); + if (lp->sqfull && ((jiffies - lp->sqfull_stamp) > (lp->slavedelay + (10*HZ) ))) + lp->sqfull = 0; + } + return ret; +} + +/* + * Try sending a packet. + * If this interface isn't connected to a ISDN-Channel, find a free channel, + * and start dialing. + */ +int +isdn_net_start_xmit(struct sk_buff *skb, struct device *ndev) +{ + isdn_net_local *lp = (isdn_net_local *) ndev->priv; + + if (ndev->tbusy) { + if (jiffies - ndev->trans_start < (2 * HZ)) + return 1; + if (!lp->dialstate) + lp->stats.tx_errors++; + ndev->tbusy = 0; + ndev->trans_start = jiffies; + } + if (skb == NULL) { + dev_tint(ndev); + return 0; + } + /* Avoid timer-based retransmission conflicts. */ + if (set_bit(0, (void *) &ndev->tbusy) != 0) + printk(KERN_WARNING + "%s: Transmitter access conflict.\n", + ndev->name); + else { + u_char *buf = skb->data; +#ifdef ISDN_DEBUG_NET_DUMP + isdn_dumppkt("S:", buf, skb->len, 40); +#endif + if (!(lp->flags & ISDN_NET_CONNECTED)) { + int chi; + if (lp->phone[1]) { + ulong flags; + save_flags(flags); + cli(); + /* Grab a free ISDN-Channel */ + if ((chi = + isdn_get_free_channel(ISDN_USAGE_NET, + lp->l2_proto, + lp->l3_proto, + lp->pre_device, + lp->pre_channel)) < 0) { + printk(KERN_WARNING + "isdn_net_start_xmit: No channel for %s\n", + ndev->name); + restore_flags(flags); + /* we probably should drop the skb here and return 0 to omit + 'socket destroy delayed' messages */ + return 1; + } + /* Log packet, which triggered dialing */ + if (dev->net_verbose) + isdn_net_log_packet(buf, lp); + lp->dialstate = 1; + lp->flags |= ISDN_NET_CONNECTED; + /* Connect interface with channel */ + isdn_net_bind_channel(lp, chi); +#ifdef CONFIG_ISDN_PPP + if (lp->p_encap == ISDN_NET_ENCAP_SYNCPPP) { + /* no 'first_skb' handling for syncPPP */ + if (isdn_ppp_bind(lp) < 0) { + dev_kfree_skb(skb,FREE_WRITE); + isdn_net_unbind_channel(lp); + restore_flags(flags); + return 0; /* STN (skb to nirvana) ;) */ + } + isdn_net_dial(); /* Initiate dialing */ + restore_flags(flags); + return 1; /* let upper layer requeue skb packet */ + } +#endif + /* remember first skb to speed up arp + * when using encap ETHER + */ + if (lp->first_skb) { + printk(KERN_WARNING "isdn_net_start_xmit: First skb already set!\n"); + dev_kfree_skb(lp->first_skb,FREE_WRITE); + lp->first_skb = NULL; + } + lp->first_skb = skb; + /* Initiate dialing */ + isdn_net_dial(); + ndev->tbusy = 0; + restore_flags(flags); + return 0; + } else { + /* + * Having no phone-number is a permanent + * failure or misconfiguration. + * Instead of just dropping, we should also + * have the upper layers to respond + * with an ICMP No route to host in the + * future, however at the moment, i don't + * know a simple way to do that. + * The same applies, when the telecom replies + * "no destination" to our dialing-attempt. + */ + printk(KERN_WARNING + "isdn_net: No phone number for %s, packet dropped\n", + ndev->name); + dev_kfree_skb(skb, FREE_WRITE); + ndev->tbusy = 0; + return 0; + } + } else { + /* Connection is established, try sending */ + ndev->trans_start = jiffies; + if (!lp->dialstate) { + if (lp->first_skb) { + if (isdn_net_xmit(ndev,lp,lp->first_skb)) + return 1; + lp->first_skb = NULL; + } + return(isdn_net_xmit(ndev, lp, skb)); + } else + ndev->tbusy = 1; + } + } + return 1; +} + +/* + * Shutdown a net-interface. + */ +static int +isdn_net_close(struct device *dev) +{ + struct device *p; + + dev->tbusy = 1; + dev->start = 0; + isdn_net_hangup(dev); + if ((p = (((isdn_net_local *) dev->priv)->slave))) { + /* If this interface has slaves, stop them also */ + while (p) { + isdn_net_hangup(p); + p->tbusy = 1; + p->start = 0; + p = (((isdn_net_local *) p->priv)->slave); + } + } + isdn_MOD_DEC_USE_COUNT(); + return 0; +} + +/* + * Get statistics + */ +static struct enet_statistics * + isdn_net_get_stats(struct device *dev) +{ + isdn_net_local *lp = (isdn_net_local *) dev->priv; + return &lp->stats; +} + +/* This is simply a copy from std. eth.c EXCEPT we pull ETH_HLEN + * instead of dev->hard_header_len off. This is done because the + * lowlevel-driver has already pulled off its stuff when we get + * here and this routine only gets called with p_encap == ETHER. + * Determine the packet's protocol ID. The rule here is that we + * assume 802.3 if the type field is short enough to be a length. + * This is normal practice and works for any 'now in use' protocol. + */ + +unsigned short isdn_net_type_trans(struct sk_buff *skb, struct device *dev) +{ + struct ethhdr *eth; + unsigned char *rawp; + + skb_pull(skb,ETH_HLEN); + eth= skb->mac.ethernet; + + if(*eth->h_dest&1) { + if(memcmp(eth->h_dest,dev->broadcast, ETH_ALEN)==0) + skb->pkt_type=PACKET_BROADCAST; + else + skb->pkt_type=PACKET_MULTICAST; + } + + /* + * This ALLMULTI check should be redundant by 1.4 + * so don't forget to remove it. + */ + + else if (dev->flags&(IFF_PROMISC|IFF_ALLMULTI)) { + if (memcmp(eth->h_dest,dev->dev_addr, ETH_ALEN)) + skb->pkt_type=PACKET_OTHERHOST; + } + + if (ntohs(eth->h_proto) >= 1536) + return eth->h_proto; + + rawp = skb->data; + + /* + * This is a magic hack to spot IPX packets. Older Novell breaks + * the protocol design and runs IPX over 802.3 without an 802.2 LLC + * layer. We look for FFFF which isn't a used 802.2 SSAP/DSAP. This + * won't work for fault tolerant netware but does for the rest. + */ + if (*(unsigned short *)rawp == 0xFFFF) + return htons(ETH_P_802_3); + /* + * Real 802.2 LLC + */ + return htons(ETH_P_802_2); +} + +/* + * Got a packet from ISDN-Channel. + */ +static void +isdn_net_receive(struct device *ndev, struct sk_buff *skb) +{ + isdn_net_local *lp = (isdn_net_local *) ndev->priv; +#ifdef CONFIG_ISDN_PPP + isdn_net_local *olp = lp; /* original 'lp' */ +#endif + + lp->transcount += skb->len; + lp->stats.rx_packets++; + lp->huptimer = 0; + + if (lp->master) { + /* Bundling: If device is a slave-device, deliver to master, also + * handle master's statistics and hangup-timeout + */ + ndev = lp->master; + lp = (isdn_net_local *) ndev->priv; + lp->stats.rx_packets++; + lp->huptimer = 0; + } + + skb->dev = ndev; + skb->pkt_type = PACKET_HOST; + skb->mac.raw = skb->data; +#ifdef ISDN_DEBUG_NET_DUMP + isdn_dumppkt("R:", skb->data, skb->len, 40); +#endif + switch (lp->p_encap) { + case ISDN_NET_ENCAP_ETHER: + /* Ethernet over ISDN */ + skb->protocol = isdn_net_type_trans(skb,ndev); + break; + case ISDN_NET_ENCAP_UIHDLC: + /* HDLC with UI-frame (for ispa with -h1 option) */ + skb_pull(skb,2); + /* Fall through */ + case ISDN_NET_ENCAP_RAWIP: + /* RAW-IP without MAC-Header */ + skb->protocol = htons(ETH_P_IP); + break; + case ISDN_NET_ENCAP_CISCOHDLC: + /* CISCO-HDLC IP with type field and fake I-frame-header */ + skb_pull(skb, 2); + /* Fall through */ + case ISDN_NET_ENCAP_IPTYP: + /* IP with type field */ + skb->protocol = *(unsigned short *)&(skb->data[0]); + skb_pull(skb, 2); + if (*(unsigned short *)skb->data == 0xFFFF) + skb->protocol = htons(ETH_P_802_3); + break; +#ifdef CONFIG_ISDN_PPP + case ISDN_NET_ENCAP_SYNCPPP: + isdn_ppp_receive(lp->netdev, olp, skb); + return; +#endif + default: + printk(KERN_WARNING "%s: unknown encapsulation, dropping\n", + lp->name); + kfree_skb(skb,FREE_READ); + return; + } + netif_rx(skb); + return; +} + +/* + * A packet arrived via ISDN. Search interface-chain for a corresponding + * interface. If found, deliver packet to receiver-function and return 1, + * else return 0. + */ +int +isdn_net_receive_callback(int idx, u_char * buf, int len) +{ + isdn_net_dev *p = dev->rx_netdev[idx]; + struct sk_buff *skb; + + if (p) { + isdn_net_local *lp = &p->local; + if ((lp->flags & ISDN_NET_CONNECTED) && + (!lp->dialstate)) { + skb = dev_alloc_skb(len); + if (skb == NULL) { + printk(KERN_WARNING "out of memory\n"); + return 0; + } + memcpy(skb_put(skb, len), buf, len); + isdn_net_receive(&p->dev, skb); + return 1; + } + } + return 0; +} + +/* + * receive callback for lowlevel drivers, which support skb's + */ + +int +isdn_net_rcv_skb(int idx, struct sk_buff *skb) +{ + isdn_net_dev *p = dev->rx_netdev[idx]; + + if (p) { + isdn_net_local *lp = &p->local; + if ((lp->flags & ISDN_NET_CONNECTED) && + (!lp->dialstate)) { + isdn_net_receive(&p->dev, skb); + return 1; + } + } + return 0; +} + +static int +my_eth_header(struct sk_buff *skb, struct device *dev, unsigned short type, + void *daddr, void *saddr, unsigned len) +{ + struct ethhdr *eth = (struct ethhdr *)skb_push(skb,ETH_HLEN); + + /* + * Set the protocol type. For a packet of type ETH_P_802_3 we + * put the length here instead. It is up to the 802.2 layer to + * carry protocol information. + */ + + if(type!=ETH_P_802_3) + eth->h_proto = htons(type); + else + eth->h_proto = htons(len); + + /* + * Set the source hardware address. + */ + if(saddr) + memcpy(eth->h_source,saddr,dev->addr_len); + else + memcpy(eth->h_source,dev->dev_addr,dev->addr_len); + + /* + * Anyway, the loopback-device should never use this function... + */ + + if (dev->flags & IFF_LOOPBACK) { + memset(eth->h_dest, 0, dev->addr_len); + return(dev->hard_header_len); + } + + if(daddr) { + memcpy(eth->h_dest,daddr,dev->addr_len); + return dev->hard_header_len; + } + + return -dev->hard_header_len; +} + +/* + * build an header + * depends on encaps that is being used. + */ + +static int +isdn_net_header(struct sk_buff *skb, struct device *dev, unsigned short type, + void *daddr, void *saddr, unsigned plen) +{ + isdn_net_local *lp = dev->priv; + ushort len = 0; + + switch (lp->p_encap) { + case ISDN_NET_ENCAP_ETHER: + len = my_eth_header(skb, dev, type, daddr, saddr, plen); + break; + case ISDN_NET_ENCAP_RAWIP: + printk(KERN_WARNING "isdn_net_header called with RAW_IP!\n"); + len = 0; + break; + case ISDN_NET_ENCAP_IPTYP: + /* ethernet type field */ + *((ushort*) skb_push(skb, 2)) = htons(type); + len = 2; + break; + case ISDN_NET_ENCAP_UIHDLC: + /* HDLC with UI-Frames (for ispa with -h1 option) */ + *((ushort*) skb_push(skb, 2)) = htons(0x0103); + len = 2; + break; + case ISDN_NET_ENCAP_CISCOHDLC: + skb_push(skb, 4); + skb->data[0] = 0x0f; + skb->data[1] = 0x00; + *((ushort*)&skb->data[2]) = htons(type); + len = 4; + break; + } + return len; +} + +/* We don't need to send arp, because we have point-to-point connections. */ + +static int +isdn_net_rebuild_header(void *buff, struct device *dev, unsigned long dst, + struct sk_buff *skb) +{ + isdn_net_local *lp = dev->priv; + int ret = 0; + + if (lp->p_encap == ISDN_NET_ENCAP_ETHER) { + struct ethhdr *eth = (struct ethhdr *)buff; + + /* + * Only ARP/IP is currently supported + */ + + if(eth->h_proto != htons(ETH_P_IP)) { + printk(KERN_WARNING + "isdn_net: %s don't know how to resolve type %d addresses?\n", + dev->name, (int)eth->h_proto); + memcpy(eth->h_source, dev->dev_addr, dev->addr_len); + return 0; + } + /* + * Try to get ARP to resolve the header. + */ +#ifdef CONFIG_INET + ret = arp_find(eth->h_dest, dst, dev, dev->pa_addr, skb)? 1 : 0; +#endif + } + return ret; +} + +/* + * Interface-setup. (called just after registering a new interface) + */ +static int +isdn_net_init(struct device *ndev) +{ + ushort max_hlhdr_len = 0; + isdn_net_local *lp = (isdn_net_local *)ndev->priv; + int drvidx, i; + + if (ndev == NULL) { + printk(KERN_WARNING "isdn_net_init: dev = NULL!\n"); + return -ENODEV; + } + if (ndev->priv == NULL) { + printk(KERN_WARNING "isdn_net_init: dev->priv = NULL!\n"); + return -ENODEV; + } + + ether_setup(ndev); + lp->org_hcb = ndev->header_cache_bind; + lp->org_hcu = ndev->header_cache_update; + + /* Setup the generic properties */ + + ndev->hard_header = NULL; + ndev->header_cache_bind = NULL; + ndev->header_cache_update = NULL; + ndev->mtu = 1500; + ndev->flags = IFF_NOARP; + ndev->family = AF_INET; + ndev->type = ARPHRD_ETHER; + ndev->addr_len = ETH_ALEN; + ndev->pa_addr = 0; + ndev->pa_brdaddr = 0; + ndev->pa_mask = 0; + ndev->pa_alen = 4; + + for (i = 0; i < ETH_ALEN; i++) + ndev->broadcast[i]=0xff; + + for (i = 0; i < DEV_NUMBUFFS; i++) + skb_queue_head_init(&ndev->buffs[i]); + + /* The ISDN-specific entries in the device structure. */ + ndev->open = &isdn_net_open; + ndev->hard_start_xmit = &isdn_net_start_xmit; + + /* + * up till binding we ask the protocol layer to reserve as much + * as we might need for HL layer + */ + + for (drvidx = 0; drvidx < ISDN_MAX_DRIVERS; drvidx++) + if (dev->drv[drvidx]) + if (max_hlhdr_len < dev->drv[drvidx]->interface->hl_hdrlen) + max_hlhdr_len = dev->drv[drvidx]->interface->hl_hdrlen; + + ndev->hard_header_len = ETH_HLEN + max_hlhdr_len; + + ndev->stop = &isdn_net_close; + ndev->get_stats = &isdn_net_get_stats; + ndev->rebuild_header = &isdn_net_rebuild_header; + +#ifdef CONFIG_ISDN_PPP + ndev->do_ioctl = isdn_ppp_dev_ioctl; +#endif + return 0; +} + +/* + * I picked the pattern-matching-functions from an old GNU-tar version (1.10) + * It was originally written and put to PD by rs@mirror.TMC.COM (Rich Salz) + */ + +static int +isdn_net_Star(char *s, char *p) +{ + while (isdn_net_wildmat(s, p) == 0) + if (*++s == '\0') + return (0); + return (1); +} + +/* + * Shell-type Pattern-matching for incoming caller-Ids + * This function gets a string in s and checks, if it matches the pattern + * given in p. It returns 1 on success, 0 otherwise. + * + * Possible Patterns: + * + * '?' matches one character + * '*' matches zero or more characters + * [xyz] matches the set of characters in brackets. + * [^xyz] matches any single character not in the set of characters + */ + +static int +isdn_net_wildmat(char *s, char *p) +{ + register int last; + register int matched; + register int reverse; + + for (; *p; s++, p++) + switch (*p) { + case '\\': + /* + * Literal match with following character, + * fall through. + */ + p++; + default: + if (*s != *p) + return (0); + continue; + case '?': + /* Match anything. */ + if (*s == '\0') + return (0); + continue; + case '*': + /* Trailing star matches everything. */ + return (*++p ? isdn_net_Star(s, p) : 1); + case '[': + /* [^....] means inverse character class. */ + if ((reverse = (p[1] == '^'))) + p++; + for (last = 0, matched = 0; *++p && (*p != ']'); last = *p) + /* This next line requires a good C compiler. */ + if (*p == '-' ? *s <= *++p && *s >= last : *s == *p) + matched = 1; + if (matched == reverse) + return (0); + continue; + } + return (*s == '\0'); +} + +static void +isdn_net_swapbind(int drvidx) +{ + isdn_net_dev *p; + +#ifdef ISDN_DEBUG_NET_ICALL + printk(KERN_DEBUG "n_fi: swapping ch of %d\n", drvidx); +#endif + p = dev->netdev; + while (p) { + if (p->local.pre_device == drvidx) + switch (p->local.pre_channel) { + case 0: + p->local.pre_channel = 1; + break; + case 1: + p->local.pre_channel = 0; + break; + } + p = (isdn_net_dev *) p->next; + } +} + +static void +isdn_net_swap_usage(int i1, int i2) +{ + int u1 = dev->usage[i1] & ISDN_USAGE_EXCLUSIVE; + int u2 = dev->usage[i2] & ISDN_USAGE_EXCLUSIVE; + +#ifdef ISDN_DEBUG_NET_ICALL + printk(KERN_DEBUG "n_fi: usage of %d and %d\n", i1, i2); +#endif + dev->usage[i1] &= ~ISDN_USAGE_EXCLUSIVE; + dev->usage[i1] |= u2; + dev->usage[i2] &= ~ISDN_USAGE_EXCLUSIVE; + dev->usage[i2] |= u1; + isdn_info_update(); +} + +/* + * An incoming call-request has arrived. + * Search the interface-chain for an appropriate interface. + * If found, connect the interface to the ISDN-channel and initiate + * D- and B-Channel-setup. If secure-flag is set, accept only + * configured phone-numbers. If callback-flag is set, initiate + * callback-dialing. + * + * Return-Value: 0 = No appropriate interface for this call. + * 1 = Call accepted + * 2 = Reject call, wait cbdelay, then call back + * 3 = Reject call + * 4 = Wait cbdelay, then call back + */ +int +isdn_net_find_icall(int di, int ch, int idx, char *num) +{ + char *eaz; + int si1; + int si2; + int ematch; + int swapped; + int sidx = 0; + isdn_net_dev *p; + isdn_net_phone *n; + ulong flags; + char nr[31]; + char *s; + + /* Search name in netdev-chain */ + save_flags(flags); + cli(); + if (num[0] == ',') { + nr[0] = '0'; + strncpy(&nr[1], num, 30); + printk(KERN_INFO "isdn_net: Incoming call without OAD, assuming '0'\n"); + } else + strncpy(nr, num, 30); + s = strtok(nr, ","); + s = strtok(NULL, ","); + if (!s) { + printk(KERN_WARNING "isdn_net: Incoming callinfo garbled, ignored: %s\n", + num); + restore_flags(flags); + return 0; + } + si1 = (int)simple_strtoul(s,NULL,10); + s = strtok(NULL, ","); + if (!s) { + printk(KERN_WARNING "isdn_net: Incoming callinfo garbled, ignored: %s\n", + num); + restore_flags(flags); + return 0; + } + si2 = (int)simple_strtoul(s,NULL,10); + eaz = strtok(NULL, ","); + if (!eaz) { + printk(KERN_WARNING "isdn_net: Incoming call without CPN, assuming '0'\n"); + eaz = "0"; + } + if (dev->net_verbose > 1) + printk(KERN_INFO "isdn_net: call from %s,%d,%d -> %s\n", nr, si1, si2, eaz); + /* Accept only calls with Si1 = 7 (Data-Transmission) */ + if (si1 != 7) { + if (dev->net_verbose > 1) + printk(KERN_INFO "isdn_net: Service-Indicator not 7, ignored\n"); + return 0; + } + n = (isdn_net_phone *) 0; + p = dev->netdev; + ematch = 0; +#ifdef ISDN_DEBUG_NET_ICALL + printk(KERN_DEBUG "n_fi: di=%d ch=%d idx=%d usg=%d\n", di, ch, idx, + dev->usage[idx]); +#endif + swapped = 0; + while (p) { + /* If last check has triggered as binding-swap, revert it */ + switch (swapped) { + case 2: + isdn_net_swap_usage(idx, sidx); + /* fall through */ + case 1: + isdn_net_swapbind(di); + break; + } + swapped = 0; + if (!strcmp(isdn_map_eaz2msn(p->local.msn, di), eaz)) + ematch = 1; +#ifdef ISDN_DEBUG_NET_ICALL + printk(KERN_DEBUG "n_fi: if='%s', l.msn=%s, l.flags=%d, l.dstate=%d\n", + p->local.name, p->local.msn, p->local.flags, p->local.dialstate); +#endif + if ((!strcmp(isdn_map_eaz2msn(p->local.msn, di), eaz)) && /* EAZ is matching */ + (((!(p->local.flags & ISDN_NET_CONNECTED)) && /* but not connected */ + (USG_NONE(dev->usage[idx]))) || /* and ch. unused or */ + ((((p->local.dialstate == 4) || (p->local.dialstate == 12)) && /* if dialing */ + (!(p->local.flags & ISDN_NET_CALLBACK))) /* but no callback */ + ))) { +#ifdef ISDN_DEBUG_NET_ICALL + printk(KERN_DEBUG "n_fi: match1, pdev=%d pch=%d\n", + p->local.pre_device, p->local.pre_channel); +#endif + if (dev->usage[idx] & ISDN_USAGE_EXCLUSIVE) { + if ((p->local.pre_channel != ch) || + (p->local.pre_device != di)) { + /* Here we got a problem: + If using an ICN-Card, an incoming call is always signaled on + on the first channel of the card, if both channels are + down. However this channel may be bound exclusive. If the + second channel is free, this call should be accepted. + The solution is horribly but it runs, so what: + We exchange the exclusive bindings of the two channels, the + corresponding variables in the interface-structs. + */ + if (ch == 0) { + sidx = isdn_dc2minor(di, 1); +#ifdef ISDN_DEBUG_NET_ICALL + printk(KERN_DEBUG "n_fi: ch is 0\n"); +#endif + if (USG_NONE(dev->usage[sidx])) { + /* Second Channel is free, now see if it is bound + exclusive too. */ + if (dev->usage[sidx] & ISDN_USAGE_EXCLUSIVE) { +#ifdef ISDN_DEBUG_NET_ICALL + printk(KERN_DEBUG "n_fi: 2nd channel is down and bound\n"); +#endif + /* Yes, swap bindings only, if the original + binding is bound to channel 1 of this driver */ + if ((p->local.pre_device == di) && + (p->local.pre_channel == 1)) { + isdn_net_swapbind(di); + swapped = 1; + } else { + /* ... else iterate next device */ + p = (isdn_net_dev *) p->next; + continue; + } + } else { +#ifdef ISDN_DEBUG_NET_ICALL + printk(KERN_DEBUG "n_fi: 2nd channel is down and unbound\n"); +#endif + /* No, swap always and swap excl-usage also */ + isdn_net_swap_usage(idx, sidx); + isdn_net_swapbind(di); + swapped = 2; + } + /* Now check for exclusive binding again */ +#ifdef ISDN_DEBUG_NET_ICALL + printk(KERN_DEBUG "n_fi: final check\n"); +#endif + if ((dev->usage[idx] & ISDN_USAGE_EXCLUSIVE) && + ((p->local.pre_channel != ch) || + (p->local.pre_device != di))) { +#ifdef ISDN_DEBUG_NET_ICALL + printk(KERN_DEBUG "n_fi: final check failed\n"); +#endif + p = (isdn_net_dev *) p->next; + continue; + } + } + } else { + /* We are already on the second channel, so nothing to do */ +#ifdef ISDN_DEBUG_NET_ICALL + printk(KERN_DEBUG "n_fi: already on 2nd channel\n"); +#endif + p = (isdn_net_dev *) p->next; + continue; + } + } + } /* if (dev->usage[idx] & ISDN_USAGE_EXCLUSIVE) */ +#ifdef ISDN_DEBUG_NET_ICALL + printk(KERN_DEBUG "n_fi: match2\n"); +#endif + n = p->local.phone[0]; + if (p->local.flags & ISDN_NET_SECURE) { + while (n) { + if (isdn_net_wildmat(nr, n->num)) + break; + n = (isdn_net_phone *) n->next; + } + } + if (n || (!(p->local.flags & ISDN_NET_SECURE))) { + isdn_net_local *lp = &(p->local); +#ifdef ISDN_DEBUG_NET_ICALL + printk(KERN_DEBUG "n_fi: match3\n"); +#endif + /* Here we got an interface matched, now see if it is up. + * If not, reject the call actively. + */ + if (!p->dev.start) { + restore_flags(flags); + printk(KERN_INFO "%s: incoming call, if down -> rejected\n", + lp->name); + return 3; + } + /* Interface is up, now see if it's a slave. If so, see if + * it's master and parent slave is online. If not, reject the call. + */ + if (lp->master) { + isdn_net_local *mlp = (isdn_net_local *) lp->master->priv; + printk(KERN_DEBUG "ICALLslv: %s\n", lp->name); + printk(KERN_DEBUG "master=%s\n", mlp->name); + if (mlp->flags & ISDN_NET_CONNECTED) { + printk(KERN_DEBUG "master online\n"); + /* Master is online, find parent-slave (master if first slave) */ + while (mlp->slave) { + if ((isdn_net_local *) mlp->slave->priv == lp) + break; + mlp = (isdn_net_local *) mlp->slave->priv; + } + } else + printk(KERN_DEBUG "master offline\n"); + /* Found parent, if it's offline iterate next device */ + printk(KERN_DEBUG "mlpf: %d\n", mlp->flags & ISDN_NET_CONNECTED); + if (!(mlp->flags & ISDN_NET_CONNECTED)) { + p = (isdn_net_dev *) p->next; + continue; + } + } + if (lp->flags & ISDN_NET_CALLBACK) { + int chi; + printk(KERN_DEBUG "%s: call from %s -> %s, start callback\n", + lp->name, nr, eaz); + if (lp->phone[1]) { + /* Grab a free ISDN-Channel */ + if ((chi = isdn_get_free_channel(ISDN_USAGE_NET, lp->l2_proto, + lp->l3_proto, + lp->pre_device, + lp->pre_channel)) < 0) { + printk(KERN_WARNING "isdn_net_find_icall: No channel for %s\n", lp->name); + restore_flags(flags); + return 0; + } + /* Setup dialstate. */ + lp->dtimer = 0; + lp->dialstate = 11; + lp->flags |= ISDN_NET_CONNECTED; + /* Connect interface with channel */ + isdn_net_bind_channel(lp, chi); +#ifdef CONFIG_ISDN_PPP + if (lp->p_encap == ISDN_NET_ENCAP_SYNCPPP) + if (isdn_ppp_bind(lp) < 0) { + isdn_net_unbind_channel(lp); + restore_flags(flags); + return 0; + } +#endif + /* Initiate dialing by returning 2 or 4 */ + restore_flags(flags); + return (lp->flags & ISDN_NET_CBHUP)?2:4; + } else + printk(KERN_WARNING "isdn_net: %s: No phone number\n", lp->name); + restore_flags(flags); + return 0; + } else { + printk(KERN_DEBUG "%s: call from %s -> %s accepted\n", lp->name, nr, + eaz); + /* if this interface is dialing, it does it probably on a different + device, so free this device */ + if ((p->local.dialstate == 4) || (p->local.dialstate == 12)) + isdn_free_channel(p->local.isdn_device, p->local.isdn_channel, + ISDN_USAGE_NET); + dev->usage[idx] &= ISDN_USAGE_EXCLUSIVE; + dev->usage[idx] |= ISDN_USAGE_NET; + strcpy(dev->num[idx], nr); + isdn_info_update(); + dev->st_netdev[idx] = lp->netdev; + p->local.isdn_device = di; + p->local.isdn_channel = ch; + p->local.ppp_slot = -1; + p->local.pppbind = -1; + p->local.flags |= ISDN_NET_CONNECTED; + p->local.dialstate = 7; + p->local.dtimer = 0; + p->local.outgoing = 0; + p->local.huptimer = 0; + p->local.hupflags |= 1; + p->local.hupflags &= ~2; +#ifdef CONFIG_ISDN_PPP + if (lp->p_encap == ISDN_NET_ENCAP_SYNCPPP) + if (isdn_ppp_bind(lp) < 0) { + isdn_net_unbind_channel(lp); + restore_flags(flags); + return 0; + } +#endif + restore_flags(flags); + return 1; + } + } + } + p = (isdn_net_dev *) p->next; + } + /* If none of configured EAZ/MSN matched and not verbose, be silent */ + if (ematch || dev->net_verbose) + printk(KERN_INFO "isdn_net: call from %s -> %d %s ignored\n", nr, di, eaz); + restore_flags(flags); + return 0; +} + +/* + * Search list of net-interfaces for an interface with given name. + */ +isdn_net_dev * + isdn_net_findif(char *name) +{ + isdn_net_dev *p = dev->netdev; + + while (p) { + if (!strcmp(p->local.name, name)) + return p; + p = (isdn_net_dev *) p->next; + } + return (isdn_net_dev *) NULL; +} + +/* + * Force a net-interface to dial out. + * This is called from the userlevel-routine below or + * from isdn_net_start_xmit(). + */ +int isdn_net_force_dial_lp(isdn_net_local * lp) +{ + if ((!(lp->flags & ISDN_NET_CONNECTED)) && !lp->dialstate) { + int chi; + if (lp->phone[1]) { + ulong flags; + save_flags(flags); + cli(); + /* Grab a free ISDN-Channel */ + if ((chi = isdn_get_free_channel(ISDN_USAGE_NET, lp->l2_proto, + lp->l3_proto, + lp->pre_device, + lp->pre_channel)) < 0) { + printk(KERN_WARNING "isdn_net_force_dial: No channel for %s\n", lp->name); + restore_flags(flags); + return -EAGAIN; + } + lp->dialstate = 1; + lp->flags |= ISDN_NET_CONNECTED; + /* Connect interface with channel */ + isdn_net_bind_channel(lp, chi); +#ifdef CONFIG_ISDN_PPP + if (lp->p_encap == ISDN_NET_ENCAP_SYNCPPP) + if (isdn_ppp_bind(lp) < 0) { + isdn_net_unbind_channel(lp); + restore_flags(flags); + return -EAGAIN; + } +#endif + /* Initiate dialing */ + isdn_net_dial(); + restore_flags(flags); + return 0; + } else + return -EINVAL; + } else + return -EBUSY; +} + +/* + * Force a net-interface to dial out. + * This is always called from within userspace (ISDN_IOCTL_NET_DIAL). + */ +int +isdn_net_force_dial(char *name) +{ + isdn_net_dev *p = isdn_net_findif(name); + + if (!p) + return -ENODEV; + return (isdn_net_force_dial_lp(&p->local)); +} + +/* + * Allocate a new network-interface and initialize its data structures. + */ +char * +isdn_net_new(char *name, struct device *master) +{ + isdn_net_dev *netdev; + + /* Avoid creating an existing interface */ + if (isdn_net_findif(name)) { + printk(KERN_WARNING "isdn_net: interface %s already exists\n", name); + return NULL; + } + if (!(netdev = (isdn_net_dev *) kmalloc(sizeof(isdn_net_dev), GFP_KERNEL))) { + printk(KERN_WARNING "isdn_net: Could not allocate net-device\n"); + return NULL; + } + memset(netdev, 0, sizeof(isdn_net_dev)); + if (name == NULL) + strcpy(netdev->local.name, " "); + else + strcpy(netdev->local.name, name); + netdev->dev.name = netdev->local.name; + netdev->dev.priv = &netdev->local; + netdev->dev.init = isdn_net_init; + netdev->local.p_encap = ISDN_NET_ENCAP_RAWIP; + if (master) { + /* Device shall be a slave */ + struct device *p = (((isdn_net_local *) master->priv)->slave); + struct device *q = master; + + netdev->local.master = master; + /* Put device at end of slave-chain */ + while (p) { + q = p; + p = (((isdn_net_local *) p->priv)->slave); + } + ((isdn_net_local *) q->priv)->slave = &(netdev->dev); + q->interrupt = 0; + q->tbusy = 0; + q->start = master->start; + } else { + /* Device shall be a master */ + if (register_netdev(&netdev->dev) != 0) { + printk(KERN_WARNING "isdn_net: Could not register net-device\n"); + kfree(netdev); + return NULL; + } + } + netdev->local.magic = ISDN_NET_MAGIC; + +#ifdef CONFIG_ISDN_PPP + netdev->mp_last = NULL; /* mpqueue is empty */ + netdev->ib.next_num = 0; + netdev->ib.last = NULL; +#endif + netdev->queue = &netdev->local; + netdev->local.last = &netdev->local; + netdev->local.netdev = netdev; + netdev->local.next = &netdev->local; + + netdev->local.isdn_device = -1; + netdev->local.isdn_channel = -1; + netdev->local.pre_device = -1; + netdev->local.pre_channel = -1; + netdev->local.exclusive = -1; + netdev->local.ppp_slot = -1; + netdev->local.pppbind = -1; + netdev->local.l2_proto = ISDN_PROTO_L2_X75I; + netdev->local.l3_proto = ISDN_PROTO_L3_TRANS; + netdev->local.slavedelay = 10 * HZ; + netdev->local.srobin = &netdev->dev; + netdev->local.hupflags = 8; /* Do hangup even on incoming calls */ + netdev->local.onhtime = 10; /* Default hangup-time for saving costs + of those who forget configuring this */ + netdev->local.dialmax = 1; + netdev->local.flags = ISDN_NET_CBHUP; /* Hangup before Callback */ + netdev->local.cbdelay = 25; /* Wait 5 secs before Callback */ + /* Put into to netdev-chain */ + netdev->next = (void *) dev->netdev; + dev->netdev = netdev; + return netdev->dev.name; +} + +char * +isdn_net_newslave(char *parm) +{ + char *p = strchr(parm, ','); + isdn_net_dev *n; + char newname[10]; + + if (p) { + /* Slave-Name MUST not be empty */ + if (!strlen(p + 1)) + return NULL; + strcpy(newname, p + 1); + *p = 0; + /* Master must already exist */ + if (!(n = isdn_net_findif(parm))) + return NULL; + /* Master must be a real interface, not a slave */ + if (n->local.master) + return NULL; + return (isdn_net_new(newname, &(n->dev))); + } + return NULL; +} + +/* + * Set interface-parameters. + * Always set all parameters, so the user-level application is responsible + * for not overwriting existing setups. It has to get the current + * setup first, if only selected parameters are to be changed. + */ +int isdn_net_setcfg(isdn_net_ioctl_cfg * cfg) +{ + isdn_net_dev *p = isdn_net_findif(cfg->name); + ulong features; + int i; + int drvidx; + int chidx; + char drvid[25]; + + if (p) { + /* See if any registered driver supports the features we want */ + features = (1 << cfg->l2_proto) | (256 << cfg->l3_proto); + for (i = 0; i < ISDN_MAX_DRIVERS; i++) + if (dev->drv[i]) + if ((dev->drv[i]->interface->features & features) == features) + break; + if (i == ISDN_MAX_DRIVERS) { + printk(KERN_WARNING "isdn_net: No driver with selected features\n"); + return -ENODEV; + } + if (p->local.p_encap != cfg->p_encap) + if (p->dev.start) { + printk(KERN_WARNING + "%s: cannot change encap when if is up\n", + p->local.name); + return -EBUSY; + } +#ifndef CONFIG_ISDN_PPP + if (cfg->p_encap == ISDN_NET_ENCAP_SYNCPPP) { + printk(KERN_WARNING "%s: SyncPPP not configured\n", + p->local.name); + return -EINVAL; + } +#endif + if (strlen(cfg->drvid)) { + /* A bind has been requested ... */ + char *c,*e; + + drvidx = -1; + chidx = -1; + strcpy(drvid, cfg->drvid); + if ((c = strchr(drvid, ','))) { + /* The channel-number is appended to the driver-Id with a comma */ + chidx = (int)simple_strtoul(c + 1,&e,10); + if (e == c) + chidx = -1; + *c = '\0'; + } + for (i = 0; i < ISDN_MAX_DRIVERS; i++) + /* Lookup driver-Id in array */ + if (!(strcmp(dev->drvid[i], drvid))) { + drvidx = i; + break; + } + if ((drvidx == -1) || (chidx == -1)) + /* Either driver-Id or channel-number invalid */ + return -ENODEV; + } else { + /* Parameters are valid, so get them */ + drvidx = p->local.pre_device; + chidx = p->local.pre_channel; + } + if (cfg->exclusive > 0) { + int flags; + + /* If binding is exclusive, try to grab the channel */ + save_flags(flags); + if ((i = isdn_get_free_channel(ISDN_USAGE_NET, p->local.l2_proto, + p->local.l3_proto, + drvidx, + chidx)) < 0) { + /* Grab failed, because desired channel is in use */ + p->local.exclusive = -1; + restore_flags(flags); + return -EBUSY; + } + /* All went ok, so update isdninfo */ + dev->usage[i] = ISDN_USAGE_EXCLUSIVE; + isdn_info_update(); + restore_flags(flags); + p->local.exclusive = i; + } else { + /* Non-exclusive binding or unbind. */ + p->local.exclusive = -1; + if ((p->local.pre_device != -1) && (cfg->exclusive == -1)) { + isdn_unexclusive_channel(p->local.pre_device, p->local.pre_channel); + isdn_free_channel(p->local.pre_device, p->local.pre_channel,ISDN_USAGE_NET); + drvidx = -1; + chidx = -1; + } + } + strcpy(p->local.msn, cfg->eaz); + p->local.pre_device = drvidx; + p->local.pre_channel = chidx; + p->local.onhtime = cfg->onhtime; + p->local.charge = cfg->charge; + p->local.l2_proto = cfg->l2_proto; + p->local.l3_proto = cfg->l3_proto; + p->local.cbdelay = cfg->cbdelay; + p->local.dialmax = cfg->dialmax; + p->local.slavedelay = cfg->slavedelay * HZ; + p->local.pppbind = cfg->pppbind; + if (cfg->secure) + p->local.flags |= ISDN_NET_SECURE; + else + p->local.flags &= ~ISDN_NET_SECURE; + if (cfg->cbhup) + p->local.flags |= ISDN_NET_CBHUP; + else + p->local.flags &= ~ISDN_NET_CBHUP; + switch (cfg->callback) { + case 0: + p->local.flags &= ~(ISDN_NET_CALLBACK|ISDN_NET_CBOUT); + break; + case 1: + p->local.flags |= ISDN_NET_CALLBACK; + p->local.flags &= ~ISDN_NET_CBOUT; + break; + case 2: + p->local.flags |= ISDN_NET_CBOUT; + p->local.flags &= ~ISDN_NET_CALLBACK; + break; + } + if (cfg->chargehup) + p->local.hupflags |= 4; + else + p->local.hupflags &= ~4; + if (cfg->ihup) + p->local.hupflags |= 8; + else + p->local.hupflags &= ~8; + if (cfg->p_encap != p->local.p_encap) { + if (cfg->p_encap == ISDN_NET_ENCAP_RAWIP) { + p->dev.hard_header = NULL; + p->dev.header_cache_bind = NULL; + p->dev.header_cache_update = NULL; + p->dev.flags = IFF_NOARP; + } else { + p->dev.hard_header = isdn_net_header; + if (cfg->p_encap == ISDN_NET_ENCAP_ETHER) { + p->dev.header_cache_bind = p->local.org_hcb; + p->dev.header_cache_update = p->local.org_hcu; + p->dev.flags = IFF_BROADCAST | IFF_MULTICAST; + } else { + p->dev.header_cache_bind = NULL; + p->dev.header_cache_update = NULL; + p->dev.flags = IFF_NOARP; + } + } + } + p->local.p_encap = cfg->p_encap; + return 0; + } + return -ENODEV; +} + +/* + * Perform get-interface-parameters.ioctl + */ +int isdn_net_getcfg(isdn_net_ioctl_cfg * cfg) +{ + isdn_net_dev *p = isdn_net_findif(cfg->name); + + if (p) { + strcpy(cfg->eaz, p->local.msn); + cfg->exclusive = p->local.exclusive; + if (p->local.pre_device >= 0) { + sprintf(cfg->drvid, "%s,%d", dev->drvid[p->local.pre_device], + p->local.pre_channel); + } else + cfg->drvid[0] = '\0'; + cfg->onhtime = p->local.onhtime; + cfg->charge = p->local.charge; + cfg->l2_proto = p->local.l2_proto; + cfg->l3_proto = p->local.l3_proto; + cfg->p_encap = p->local.p_encap; + cfg->secure = (p->local.flags & ISDN_NET_SECURE) ? 1 : 0; + cfg->callback = 0; + if (p->local.flags & ISDN_NET_CALLBACK) + cfg->callback = 1; + if (p->local.flags & ISDN_NET_CBOUT) + cfg->callback = 2; + cfg->cbhup = (p->local.flags & ISDN_NET_CBHUP) ? 1 : 0; + cfg->chargehup = (p->local.hupflags & 4) ? 1 : 0; + cfg->ihup = (p->local.hupflags & 8) ? 1 : 0; + cfg->cbdelay = p->local.cbdelay; + cfg->dialmax = p->local.dialmax; + cfg->slavedelay = p->local.slavedelay / HZ; + cfg->pppbind = p->local.pppbind; + if (p->local.slave) + strcpy(cfg->slave, ((isdn_net_local *) p->local.slave->priv)->name); + else + cfg->slave[0] = '\0'; + if (p->local.master) + strcpy(cfg->master, ((isdn_net_local *) p->local.master->priv)->name); + else + cfg->master[0] = '\0'; + return 0; + } + return -ENODEV; +} + +/* + * Add a phone-number to an interface. + */ +int isdn_net_addphone(isdn_net_ioctl_phone * phone) +{ + isdn_net_dev *p = isdn_net_findif(phone->name); + isdn_net_phone *n; + + if (isdn_net_checkwild(phone->phone) && (phone->outgoing & 1)) + return -EINVAL; + if (p) { + if (!(n = (isdn_net_phone *) kmalloc(sizeof(isdn_net_phone), GFP_KERNEL))) + return -ENOMEM; + strcpy(n->num, phone->phone); + n->next = p->local.phone[phone->outgoing & 1]; + p->local.phone[phone->outgoing & 1] = n; + return 0; + } + return -ENODEV; +} + +/* + * Return a string of all phone-numbers of an interface. + */ +int isdn_net_getphones(isdn_net_ioctl_phone * phone, char *phones) +{ + isdn_net_dev *p = isdn_net_findif(phone->name); + int inout = phone->outgoing & 1; + int more = 0; + int count = 0; + isdn_net_phone *n; + int flags; + int ret = 0; + + if (!p) + return -ENODEV; + save_flags(flags); + cli(); + inout &= 1; + for (n = p->local.phone[inout]; n; n = n->next) { + if (more) { + ret = put_user(((char)' '), phones); + phones++; + count++; + } + if (ret || copy_to_user(phones, n->num, strlen(n->num) + 1)) { + restore_flags(flags); + return -EFAULT; + } + phones += strlen(n->num); + count += strlen(n->num); + more = 1; + } + ret = put_user(((char)0),phones); + count++; + restore_flags(flags); + return ret ? -EFAULT : count; +} + +/* + * Delete a phone-number from an interface. + */ + +int isdn_net_delphone(isdn_net_ioctl_phone * phone) +{ + isdn_net_dev *p = isdn_net_findif(phone->name); + int inout = phone->outgoing & 1; + isdn_net_phone *n; + isdn_net_phone *m; + + if (p) { + n = p->local.phone[inout]; + m = NULL; + while (n) { + if (!strcmp(n->num, phone->phone)) { + if (m) + m->next = n->next; + else + p->local.phone[inout] = n->next; + kfree(n); + return 0; + } + m = n; + n = (isdn_net_phone *) n->next; + } + return -EINVAL; + } + return -ENODEV; +} + +/* + * Delete all phone-numbers of an interface. + */ +static int isdn_net_rmallphone(isdn_net_dev * p) +{ + isdn_net_phone *n; + isdn_net_phone *m; + int flags; + int i; + + save_flags(flags); + cli(); + for (i = 0; i < 2; i++) { + n = p->local.phone[i]; + while (n) { + m = n->next; + kfree(n); + n = m; + } + p->local.phone[i] = NULL; + } + restore_flags(flags); + return 0; +} + +/* + * Force a hangup of a network-interface. + */ +int isdn_net_force_hangup(char *name) +{ + isdn_net_dev *p = isdn_net_findif(name); + int flags; + struct device *q; + + if (p) { + save_flags(flags); + cli(); + if (p->local.isdn_device < 0) { + restore_flags(flags); + return 1; + } + isdn_net_hangup(&p->dev); + q = p->local.slave; + /* If this interface has slaves, do a hangup for them also. */ + while (q) { + isdn_net_hangup(q); + q = (((isdn_net_local *) q->priv)->slave); + } + restore_flags(flags); + return 0; + } + return -ENODEV; +} + +/* + * Helper-function for isdn_net_rm: Do the real work. + */ +static int isdn_net_realrm(isdn_net_dev * p, isdn_net_dev * q) +{ + int flags; + + save_flags(flags); + cli(); + if (p->local.master) { + /* If it's a slave, it may be removed even if it is busy. However + * it has to be hung up first. + */ + isdn_net_hangup(&p->dev); + p->dev.start = 0; + } + if (p->dev.start) { + restore_flags(flags); + return -EBUSY; + } + /* Free all phone-entries */ + isdn_net_rmallphone(p); + /* If interface is bound exclusive, free channel-usage */ + if (p->local.exclusive != -1) + isdn_unexclusive_channel(p->local.pre_device, p->local.pre_channel); + if (p->local.master) { + /* It's a slave-device, so update master's slave-pointer if necessary */ + if (((isdn_net_local *) (p->local.master->priv))->slave == &p->dev) + ((isdn_net_local *) (p->local.master->priv))->slave = p->local.slave; + } else + /* Unregister only if it's a master-device */ + unregister_netdev(&p->dev); + /* Unlink device from chain */ + if (q) + q->next = p->next; + else + dev->netdev = p->next; + if (p->local.slave) { + /* If this interface has a slave, remove it also */ + char *slavename = ((isdn_net_local *) (p->local.slave->priv))->name; + isdn_net_dev *n = dev->netdev; + q = NULL; + while (n) { + if (!strcmp(n->local.name, slavename)) { + isdn_net_realrm(n, q); + break; + } + q = n; + n = (isdn_net_dev *) n->next; + } + } + /* If no more net-devices remain, disable auto-hangup timer */ + if (dev->netdev == NULL) + isdn_timer_ctrl(ISDN_TIMER_NETHANGUP, 0); + restore_flags(flags); + +#ifdef CONFIG_ISDN_PPP + isdn_ppp_free_mpqueue(p); /* still necessary? */ +#endif + kfree(p); + + return 0; +} + +/* + * Remove a single network-interface. + */ +int isdn_net_rm(char *name) +{ + isdn_net_dev *p; + isdn_net_dev *q; + + /* Search name in netdev-chain */ + p = dev->netdev; + q = NULL; + while (p) { + if (!strcmp(p->local.name, name)) + return (isdn_net_realrm(p, q)); + q = p; + p = (isdn_net_dev *) p->next; + } + /* If no more net-devices remain, disable auto-hangup timer */ + if (dev->netdev == NULL) + isdn_timer_ctrl(ISDN_TIMER_NETHANGUP, 0); + return -ENODEV; +} + +/* + * Remove all network-interfaces + */ +int isdn_net_rmall(void) +{ + int flags; + int ret; + + /* Walk through netdev-chain */ + save_flags(flags); + cli(); + while (dev->netdev) { + if (!dev->netdev->local.master) { + /* Remove master-devices only, slaves get removed with their master */ + if ((ret = isdn_net_realrm(dev->netdev, NULL))) { + restore_flags(flags); + return ret; + } + } + } + dev->netdev = NULL; + restore_flags(flags); + return 0; +} + +/* + * helper function to flush device queues + * the better place would be net/core/dev.c + */ +void dev_purge_queues(struct device *dev) +{ + int i; + for(i=0;i<DEV_NUMBUFFS;i++) { + struct sk_buff *skb; + while((skb=skb_dequeue(&dev->buffs[i]))) + dev_kfree_skb(skb,FREE_WRITE); + } + +} + + + + + + + + + + + diff --git a/drivers/isdn/isdn_net.h b/drivers/isdn/isdn_net.h new file mode 100644 index 000000000..5b6754e66 --- /dev/null +++ b/drivers/isdn/isdn_net.h @@ -0,0 +1,52 @@ +/* $Id: isdn_net.h,v 1.2 1996/04/20 16:29:43 fritz Exp $ + * + * header for Linux ISDN subsystem, network related functions (linklevel). + * + * Copyright 1994,95,96 by Fritz Elfert (fritz@wuemaus.franken.de) + * Copyright 1995,96 by Thinking Objects Software GmbH Wuerzburg + * Copyright 1995,96 by Michael Hipp (Michael.Hipp@student.uni-tuebingen.de) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Log: isdn_net.h,v $ + * Revision 1.2 1996/04/20 16:29:43 fritz + * Misc. typos + * + * Revision 1.1 1996/02/11 02:35:13 fritz + * Initial revision + * + */ + +extern char* isdn_net_new(char *, struct device *); +extern char* isdn_net_newslave(char *); +extern int isdn_net_rm(char *); +extern int isdn_net_rmall(void); +extern int isdn_net_stat_callback(int, int); +extern int isdn_net_receive_callback(int, u_char *, int); +extern int isdn_net_setcfg(isdn_net_ioctl_cfg *); +extern int isdn_net_getcfg(isdn_net_ioctl_cfg *); +extern int isdn_net_addphone(isdn_net_ioctl_phone *); +extern int isdn_net_getphones(isdn_net_ioctl_phone *, char *); +extern int isdn_net_delphone(isdn_net_ioctl_phone *); +extern int isdn_net_find_icall(int, int, int, char *); +extern void isdn_net_hangup(struct device *); +extern void isdn_net_dial(void); +extern void isdn_net_autohup(void); +extern int isdn_net_force_hangup(char *); +extern int isdn_net_force_dial(char *); +extern isdn_net_dev* isdn_net_findif(char *); +extern int isdn_net_send_skb(struct device *, isdn_net_local *, + struct sk_buff *); +extern int isdn_net_rcv_skb(int, struct sk_buff *); diff --git a/drivers/isdn/isdn_ppp.c b/drivers/isdn/isdn_ppp.c new file mode 100644 index 000000000..6d678ed51 --- /dev/null +++ b/drivers/isdn/isdn_ppp.c @@ -0,0 +1,1591 @@ +/* $Id: isdn_ppp.c,v 1.14 1996/08/12 16:26:47 hipp Exp $ + * + * Linux ISDN subsystem, functions for synchronous PPP (linklevel). + * + * Copyright 1995,96 by Michael Hipp (Michael.Hipp@student.uni-tuebingen.de) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Log: isdn_ppp.c,v $ + * Revision 1.14 1996/08/12 16:26:47 hipp + * code cleanup + * changed connection management from minors to slots + * + * Revision 1.13 1996/07/01 19:47:24 hipp + * Fixed memory leak in VJ handling and more VJ changes + * + * Revision 1.12 1996/06/24 17:42:03 fritz + * Minor bugfixes. + * + * Revision 1.11 1996/06/16 17:46:05 tsbogend + * changed unsigned long to u32 to make Alpha people happy + * + * Revision 1.10 1996/06/11 14:50:29 hipp + * Lot of changes and bugfixes. + * New scheme to resend packets to busy LL devices. + * + * Revision 1.9 1996/05/18 01:37:01 fritz + * Added spelling corrections and some minor changes + * to stay in sync with kernel. + * + * Revision 1.8 1996/05/06 11:34:55 hipp + * fixed a few bugs + * + * Revision 1.7 1996/04/30 11:07:42 fritz + * Added Michael's ippp-bind patch. + * + * Revision 1.6 1996/04/30 09:33:09 fritz + * Removed compatibility-macros. + * + * Revision 1.5 1996/04/20 16:32:32 fritz + * Changed ippp_table to an array of pointers, allocating each part + * separately. + * + * Revision 1.4 1996/02/19 15:25:50 fritz + * Bugfix: Sync-PPP packets got compressed twice, when resent due to + * send-queue-full reject. + * + * Revision 1.3 1996/02/11 02:27:12 fritz + * Lot of Bugfixes my Michael. + * Moved calls to skb_push() into isdn_net_header() + * Fixed a possible race-condition in isdn_ppp_timer_timeout(). + * + * Revision 1.2 1996/01/22 05:08:06 fritz + * Merged in Michael's patches for MP. + * Minor changes in isdn_ppp_xmit. + * + * Revision 1.1 1996/01/09 04:11:29 fritz + * Initial revision + * + */ + +/* TODO: right tbusy handling when using MP */ + +#include <asm/uaccess.h> +#include <linux/config.h> +#define __NO_VERSION__ +#include <linux/module.h> +#include <linux/isdn.h> +#include "isdn_common.h" +#include "isdn_ppp.h" +#include "isdn_net.h" + +#ifndef PPP_IPX +#define PPP_IPX 0x002b +#endif + +/* Prototypes */ +static int isdn_ppp_fill_rq(unsigned char *buf, int len,int proto, int slot); +static int isdn_ppp_closewait(int slot); +static void isdn_ppp_push_higher(isdn_net_dev * net_dev, isdn_net_local * lp, + struct sk_buff *skb, int proto); +static int isdn_ppp_if_get_unit(char *namebuf); + +#ifdef CONFIG_ISDN_MPP +static int isdn_ppp_bundle(struct ippp_struct *, int unit); +static void isdn_ppp_mask_queue(isdn_net_dev * dev, long mask); +static void isdn_ppp_cleanup_queue(isdn_net_dev * dev, long min); +static int isdn_ppp_fill_mpqueue(isdn_net_dev *, struct sk_buff **skb, + int BEbyte, int *sqno, int min_sqno); +#endif + +char *isdn_ppp_revision = "$Revision: 1.14 $"; +struct ippp_struct *ippp_table[ISDN_MAX_CHANNELS]; + +extern int isdn_net_force_dial_lp(isdn_net_local *); + +/* + * unbind isdn_net_local <=> ippp-device + * note: it can happen, that we hangup/free the master before the slaves + */ +int isdn_ppp_free(isdn_net_local *lp) +{ + isdn_net_local *master_lp=lp; + unsigned long flags; + struct ippp_struct *is; + + if (lp->ppp_slot < 0 || lp->ppp_slot > ISDN_MAX_CHANNELS) + return 0; + + is = ippp_table[lp->ppp_slot]; + + save_flags(flags); + cli(); +#ifdef CONFIG_ISDN_MPP + if(lp->master) + master_lp = (isdn_net_local *) lp->master->priv; + + lp->last->next = lp->next; + lp->next->last = lp->last; + if(master_lp->netdev->queue == lp) { + master_lp->netdev->queue = lp->next; + if(lp->next == lp) { /* last link in queue? */ + master_lp->netdev->ib.bundled = 0; + isdn_ppp_free_mpqueue(master_lp->netdev); + isdn_ppp_free_sqqueue(master_lp->netdev); + } + } + lp->next = lp->last = lp; /* (re)set own pointers */ +#endif + + isdn_ppp_closewait(lp->ppp_slot); /* force wakeup on ippp device */ + + if(is->debug & 0x1) + printk(KERN_DEBUG "isdn_ppp_free %d %lx %lx\n", lp->ppp_slot, (long) lp,(long) is->lp); + + is->lp = NULL; /* link is down .. set lp to NULL */ + lp->ppp_slot = -1; /* is this OK ?? */ + restore_flags(flags); + + return 0; +} + +/* + * bind isdn_net_local <=> ippp-device + */ +int isdn_ppp_bind(isdn_net_local * lp) +{ + int i; + int unit = 0; + long flags; + struct ippp_struct *is; + + if (lp->p_encap != ISDN_NET_ENCAP_SYNCPPP) + return -1; + + save_flags(flags); + cli(); + + if(lp->pppbind < 0) /* device bounded to ippp device ? */ + { + isdn_net_dev *net_dev = dev->netdev; + char exclusive[ISDN_MAX_CHANNELS]; /* exclusive flags */ + memset(exclusive,0,ISDN_MAX_CHANNELS); + while (net_dev) { /* step through net devices to find exclusive minors */ + isdn_net_local *lp = &net_dev->local; + if(lp->pppbind >= 0) + exclusive[lp->pppbind] = 1; + net_dev = net_dev->next; + } + /* + * search a free device / slot + */ + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + if (ippp_table[i]->state == IPPP_OPEN && !exclusive[ippp_table[i]->minor]) { /* OPEN, but not connected! */ + break; + } + } + } + else { + for(i=0;i<ISDN_MAX_CHANNELS;i++) + if(ippp_table[i]->minor == lp->pppbind && ippp_table[i]->state == IPPP_OPEN) + break; + } + + if (i >= ISDN_MAX_CHANNELS) { + restore_flags(flags); + printk(KERN_WARNING "isdn_ppp_bind: Can't find usable ippp device.\n"); + return -1; + } + + unit = isdn_ppp_if_get_unit(lp->name); /* get unit number from interface name .. ugly! */ + if(unit < 0) { + printk(KERN_ERR "isdn_ppp_bind: illegal interface name %s.\n",lp->name); + return -1; + } + + lp->ppp_slot = i; + is = ippp_table[i]; + is->lp = lp; + is->unit = unit; + is->state = IPPP_OPEN | IPPP_CONNECT | IPPP_NOBLOCK; + + restore_flags(flags); + + return lp->ppp_slot; +} + +/* + * kick the ipppd on the device + * (wakes up daemon after B-channel connect) + */ + +void isdn_ppp_wakeup_daemon(isdn_net_local *lp) +{ + if (ippp_table[lp->ppp_slot]->wq) + wake_up_interruptible(&ippp_table[lp->ppp_slot]->wq); +} + +/* + * there was a hangup on the netdevice + * force wakeup of the ippp device + * go into 'device waits for release' state + */ +static int isdn_ppp_closewait(int slot) +{ + struct ippp_struct *is; + + if (slot < 0 || slot >= ISDN_MAX_CHANNELS) + return 0; + is = ippp_table[slot]; + + if (is->state && is->wq) + wake_up_interruptible(&is->wq); + + is->state = IPPP_CLOSEWAIT; + return 1; +} + +/* + * isdn_ppp_find_slot / isdn_ppp_free_slot + */ + +static int isdn_ppp_get_slot(void) +{ + int i; + for(i=0;i<ISDN_MAX_CHANNELS;i++) { + if(!ippp_table[i]->state) + return i; + } + return -1; +} + +/* + * isdn_ppp_open + */ + +int isdn_ppp_open(int min, struct file *file) +{ + int slot; + struct ippp_struct *is; + + slot = isdn_ppp_get_slot(); + if(slot < 0) { + return -EBUSY; + } + is = file->private_data = ippp_table[slot]; + + if(is->debug & 0x1) + printk(KERN_DEBUG "ippp, open, slot: %d, minor: %d, state: %04x\n",slot, min,is->state); + + is->lp = 0; + is->mp_seqno = 0; /* MP sequence number */ + is->pppcfg = 0; /* ppp configuration */ + is->mpppcfg = 0; /* mppp configuration */ + is->range = 0x1000000; /* MP: 24 bit range */ + is->last_link_seqno = -1; /* MP: maybe set to Bundle-MIN, when joining a bundle ?? */ + is->unit = -1; /* set, when we have our interface */ + is->mru = 1524; /* MRU, default 1524 */ + is->maxcid = 16; /* VJ: maxcid */ + is->tk = current; + is->wq = NULL; /* read() wait queue */ + is->wq1 = NULL; /* select() wait queue */ + is->first = is->rq + NUM_RCV_BUFFS - 1; /* receive queue */ + is->last = is->rq; + is->minor = min; +#ifdef CONFIG_ISDN_PPP_VJ + /* + * VJ header compression init + */ + is->slcomp = slhc_init(16, 16); /* not necessary for 2. link in bundle */ +#endif + + is->state = IPPP_OPEN; + + return 0; +} + +/* + * release ippp device + */ +void isdn_ppp_release(int min, struct file *file) +{ + int i; + struct ippp_struct *is; + + if (min < 0 || min >= ISDN_MAX_CHANNELS) + return; + is = file->private_data; + + if(is->debug & 0x1) + printk(KERN_DEBUG "ippp: release, minor: %d %lx\n", min, (long) is->lp); + + if (is->lp) { /* a lp address says: this link is still up */ + isdn_net_dev *p = dev->netdev; + p = is->lp->netdev; + is->lp->ppp_slot = -1; + isdn_net_hangup(&p->dev); /* lp->ppp_slot==-1 => no calling of isdn_ppp_closewait() */ + is->lp = NULL; + } + for (i = 0; i < NUM_RCV_BUFFS; i++) { + if (is->rq[i].buf) { + kfree(is->rq[i].buf); + is->rq[i].buf = NULL; + } + } + is->first = is->rq + NUM_RCV_BUFFS - 1; /* receive queue */ + is->last = is->rq; + +#ifdef CONFIG_ISDN_PPP_VJ + slhc_free(is->slcomp); + is->slcomp = NULL; +#endif + + is->state = 0; +} + +#if 0 /* get_user() / put_user() in 2.1 replace them 1:1 */ +/* + * get_arg .. ioctl helper + */ +static int get_arg(void *b, unsigned long *val) +{ + int r; + if (copy_from_user((void *) val, b, sizeof(unsigned long))) + return -EFAULT; + return 0; +} + +/* + * set arg .. ioctl helper + */ +static int set_arg(void *b, unsigned long val) +{ + int r; + if ((r = verify_area(VERIFY_WRITE, b, sizeof(unsigned long)))) + return r; + copy_to_user(b, (void *) &val, sizeof(unsigned long)); + return 0; +} +#endif + +/* + * ippp device ioctl + */ +int isdn_ppp_ioctl(int min, struct file *file, unsigned int cmd, unsigned long arg) +{ + unsigned long val; + int r; + struct ippp_struct *is; + unsigned long *argp = (unsigned long*)arg; + + is = file->private_data; + + if(is->debug & 0x1) + printk(KERN_DEBUG "isdn_ppp_ioctl: minor: %d cmd: %x state: %x\n",min,cmd,is->state); + + if (!(is->state & IPPP_OPEN)) + return -EINVAL; + + switch (cmd) { + case PPPIOCBUNDLE: +#ifdef CONFIG_ISDN_MPP + if ((r = get_user(val, argp))) + return r; + printk(KERN_DEBUG "iPPP-bundle: minor: %d, slave unit: %d, master unit: %d\n", + (int) min, (int) is->unit, (int) val); + return isdn_ppp_bundle(is, val); +#else + return -1; +#endif + break; + case PPPIOCGUNIT: /* get ppp/isdn unit number */ + if ((r = put_user(is->unit, argp))) + return r; + break; + case PPPIOCGMPFLAGS: /* get configuration flags */ + if ((r = put_user(is->mpppcfg, argp))) + return r; + break; + case PPPIOCSMPFLAGS: /* set configuration flags */ + if ((r = get_user(val, argp))) + return r; + is->mpppcfg = val; + break; + case PPPIOCGFLAGS: /* get configuration flags */ + if ((r = put_user(is->pppcfg ,argp))) + return r; + break; + case PPPIOCSFLAGS: /* set configuration flags */ + if ((r = get_user(val, argp))) { + return r; + } + if (val & SC_ENABLE_IP && !(is->pppcfg & SC_ENABLE_IP)) { + isdn_net_local *lp = is->lp; + lp->netdev->dev.tbusy = 0; + mark_bh(NET_BH); /* OK .. we are ready to send buffers */ + } + is->pppcfg = val; + break; +#if 0 + case PPPIOCGSTAT: /* read PPP statistic information */ + break; + case PPPIOCGTIME: /* read time delta information */ + break; +#endif + case PPPIOCSMRU: /* set receive unit size for PPP */ + if ((r = get_user(val, argp))) + return r; + is->mru = val; + break; + case PPPIOCSMPMRU: + break; + case PPPIOCSMPMTU: + break; + case PPPIOCSMAXCID: /* set the maximum compression slot id */ + if ((r = get_user(val, argp))) + return r; + val++; + if(is->maxcid != val) { +#ifdef CONFIG_ISDN_PPP_VJ + struct slcompress *sltmp; +#endif + if(is->debug & 0x1) + printk(KERN_DEBUG "ippp, ioctl: changed MAXCID to %ld\n",val); + is->maxcid = val; +#ifdef CONFIG_ISDN_PPP_VJ + sltmp = slhc_init(16,val); + if(!sltmp) { + printk(KERN_ERR "ippp, can't realloc slhc struct\n"); + return -ENOMEM; + } + if(is->slcomp) + slhc_free(is->slcomp); + is->slcomp = sltmp; +#endif + } + break; + case PPPIOCGDEBUG: + if ((r = put_user(is->debug, argp))) + return r; + break; + case PPPIOCSDEBUG: + if ((r = get_user(val, argp))) + return r; + is->debug = val; + break; + default: + break; + } + return 0; +} + +int isdn_ppp_select(int min, struct file *file, int type, select_table * st) +{ + struct ippp_buf_queue *bf, *bl; + unsigned long flags; + struct ippp_struct *is; + + is = file->private_data; + + if(is->debug & 0x2) + printk(KERN_DEBUG "isdn_ppp_select: minor: %d, type: %d \n",min,type); + + if (!(is->state & IPPP_OPEN)) + return -EINVAL; + + switch (type) { + case SEL_IN: + save_flags(flags); + cli(); + bl = is->last; + bf = is->first; + /* + * if IPPP_NOBLOCK is set we return even if we have nothing to read + */ + if (bf->next == bl && !(is->state & IPPP_NOBLOCK)) { + select_wait(&is->wq, st); + restore_flags(flags); + return 0; + } + is->state &= ~IPPP_NOBLOCK; + restore_flags(flags); + return 1; + case SEL_OUT: + /* we're always ready to send .. */ + return 1; + case SEL_EX: + select_wait(&is->wq1, st); + return 0; + } + return 1; +} + +/* + * fill up isdn_ppp_read() queue .. + */ + +static int isdn_ppp_fill_rq(unsigned char *buf, int len,int proto, int slot) +{ + struct ippp_buf_queue *bf, *bl; + unsigned long flags; + unsigned char *nbuf; + struct ippp_struct *is; + + if (slot < 0 || slot >= ISDN_MAX_CHANNELS) { + printk(KERN_WARNING "ippp: illegal slot.\n"); + return 0; + } + is = ippp_table[slot]; + + if (!(is->state & IPPP_CONNECT)) { + printk(KERN_DEBUG "ippp: device not activated.\n"); + return 0; + } + + nbuf = (unsigned char *) kmalloc(len+4, GFP_ATOMIC); + if(!nbuf) { + printk(KERN_WARNING "ippp: Can't alloc buf\n"); + return 0; + } + nbuf[0] = PPP_ALLSTATIONS; + nbuf[1] = PPP_UI; + nbuf[2] = proto >> 8; + nbuf[3] = proto & 0xff; + memcpy(nbuf+4, buf, len); + + save_flags(flags); + cli(); + + bf = is->first; + bl = is->last; + + if (bf == bl) { + printk(KERN_WARNING "ippp: Queue is full; discarding first buffer\n"); + bf = bf->next; + kfree(bf->buf); + is->first = bf; + } + bl->buf = (char *) nbuf; + bl->len = len+4; + + is->last = bl->next; + restore_flags(flags); + + if (is->wq) + wake_up_interruptible(&is->wq); + + return len; +} + +/* + * read() .. non-blocking: ipppd calls it only after select() + * reports, that there is data + */ + +int isdn_ppp_read(int min, struct file *file, char *buf, int count) +{ + struct ippp_struct *is; + struct ippp_buf_queue *b; + unsigned long flags; + + is = file->private_data; + + if (!(is->state & IPPP_OPEN)) + return 0; + + save_flags(flags); + cli(); + + b = is->first->next; + if (!b->buf) { + restore_flags(flags); + return -EAGAIN; + } + if (b->len < count) + count = b->len; + if (copy_to_user(buf, b->buf, count)) + count = -EFAULT; /* ugly */ + kfree(b->buf); + b->buf = NULL; + is->first = b; + restore_flags(flags); + + return count; +} + +/* + * ipppd wanna write a packet to the card .. non-blocking + */ + +int isdn_ppp_write(int min, struct file *file, const char *buf, int count) +{ + isdn_net_local *lp; + struct ippp_struct *is; + + is = file->private_data; + + if (!(is->state & IPPP_CONNECT)) + return 0; + + lp = is->lp; + + /* -> push it directly to the lowlevel interface */ + + if (!lp) + printk(KERN_DEBUG "isdn_ppp_write: lp == NULL\n"); + else { + lp->huptimer = 0; + if (lp->isdn_device < 0 || lp->isdn_channel < 0) + return 0; + + if (dev->drv[lp->isdn_device]->running && lp->dialstate == 0 && + (lp->flags & ISDN_NET_CONNECTED)) { + struct sk_buff *skb; + skb = dev_alloc_skb(count); + if(!skb) { + printk(KERN_WARNING "isdn_ppp_write: out of memory!\n"); + return count; + } + skb->free = 1; + copy_from_user(skb_put(skb, count), buf, count); + if(isdn_writebuf_skb_stub(lp->isdn_device,lp->isdn_channel,skb) != count) { + if(lp->sav_skb) { + dev_kfree_skb(lp->sav_skb,FREE_WRITE); + printk(KERN_INFO "isdn_ppp_write: freeing sav_skb!\n"); + } + lp->sav_skb = skb; + } + } + } + + return count; +} + +/* + * init memory, structures etc. + */ + +int isdn_ppp_init(void) +{ + int i, j; + + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + if (!(ippp_table[i] = (struct ippp_struct *) + kmalloc(sizeof(struct ippp_struct), GFP_KERNEL))) { + printk(KERN_WARNING "isdn_ppp_init: Could not alloc ippp_table\n"); + for (j = 0; j < i; j++) + kfree(ippp_table[i]); + return -1; + } + memset((char *) ippp_table[i], 0, sizeof(struct ippp_struct)); + ippp_table[i]->state = 0; + ippp_table[i]->first = ippp_table[i]->rq + NUM_RCV_BUFFS - 1; + ippp_table[i]->last = ippp_table[i]->rq; + + for (j = 0; j < NUM_RCV_BUFFS; j++) { + ippp_table[i]->rq[j].buf = NULL; + ippp_table[i]->rq[j].last = ippp_table[i]->rq + + (NUM_RCV_BUFFS + j - 1) % NUM_RCV_BUFFS; + ippp_table[i]->rq[j].next = ippp_table[i]->rq + (j + 1) % NUM_RCV_BUFFS; + } + } + return 0; +} + +void isdn_ppp_cleanup(void) +{ + int i; + + for (i = 0; i < ISDN_MAX_CHANNELS; i++) + kfree(ippp_table[i]); +} + +/* + * handler for incoming packets on a syncPPP interface + */ + +void isdn_ppp_receive(isdn_net_dev * net_dev, isdn_net_local * lp, struct sk_buff *skb) +{ + struct ippp_struct *is; + is = ippp_table[lp->ppp_slot]; + + if(is->debug & 0x4) + printk(KERN_DEBUG "recv skb, len: %ld\n",skb->len); + + if(net_dev->local.master) { + printk(KERN_WARNING "isdn_ppp_receice: net_dev != master\n"); + net_dev = ((isdn_net_local*) net_dev->local.master->priv)->netdev; + } + + if(skb->data[0] == 0xff && skb->data[1] == 0x03) + skb_pull(skb,2); + else if (is->pppcfg & SC_REJ_COMP_AC) { + skb->free = 1; + dev_kfree_skb(skb,0 /* FREE_READ */ ); + return; /* discard it silently */ + } + +#ifdef CONFIG_ISDN_MPP + if (!(is->mpppcfg & SC_REJ_MP_PROT)) { + int proto; + int sqno_end; + if (skb->data[0] & 0x1) { + proto = skb->data[0]; + skb_pull(skb,1); /* protocol ID is only 8 bit */ + } else { + proto = ((int) skb->data[0] << 8) + skb->data[1]; + skb_pull(skb,2); + } + if (proto == PPP_MP) { + isdn_net_local *lpq; + int sqno, min_sqno, tseq; + u_char BEbyte = skb->data[0]; + if(is->debug & 0x8) + printk(KERN_DEBUG "recv: %d/%04x/%d -> %02x %02x %02x %02x %02x %02x\n", lp->ppp_slot, proto , + (int) skb->len, (int) skb->data[0], (int) skb->data[1], (int) skb->data[2], + (int) skb->data[3], (int) skb->data[4], (int) skb->data[5]); + if (!(is->mpppcfg & SC_IN_SHORT_SEQ)) { + sqno = ((int) skb->data[1] << 16) + ((int) skb->data[2] << 8) + (int) skb->data[3]; + skb_pull(skb,4); + } else { + sqno = (((int) skb->data[0] & 0xf) << 8) + (int) skb->data[1]; + skb_pull(skb,2); + } + + if ((tseq = is->last_link_seqno) >= sqno) { + int range = is->range; + if (tseq + 1024 < range + sqno) /* redundancy check .. not MP conform */ + printk(KERN_WARNING "isdn_ppp_receive, MP, detected overflow with sqno: %d, last: %d !!!\n", sqno, tseq); + else { + sqno += range; + is->last_link_seqno = sqno; + } + } else + is->last_link_seqno = sqno; + + for (min_sqno = 0, lpq = net_dev->queue;;) { + if (ippp_table[lpq->ppp_slot]->last_link_seqno > min_sqno) + min_sqno = ippp_table[lpq->ppp_slot]->last_link_seqno; + lpq = lpq->next; + if (lpq == net_dev->queue) + break; + } + if (min_sqno >= ippp_table[lpq->ppp_slot]->range) { /* OK, every link overflowed */ + int mask = ippp_table[lpq->ppp_slot]->range - 1; /* range is a power of 2 */ + isdn_ppp_cleanup_queue(net_dev, min_sqno); + isdn_ppp_mask_queue(net_dev, mask); + net_dev->ib.next_num &= mask; + { + struct sqqueue *q = net_dev->ib.sq; + while (q) { + q->sqno_start &= mask; + q->sqno_end &= mask; + } + } + min_sqno &= mask; + for (lpq = net_dev->queue;;) { + ippp_table[lpq->ppp_slot]->last_link_seqno &= mask; + lpq = lpq->next; + if (lpq == net_dev->queue) + break; + } + } + if ((BEbyte & (MP_BEGIN_FRAG | MP_END_FRAG)) != (MP_BEGIN_FRAG | MP_END_FRAG)) { + printk(KERN_DEBUG "ippp: trying ;) to fill mp_queue %d .. UNTESTED!!\n", lp->ppp_slot); + if ((sqno_end = isdn_ppp_fill_mpqueue(net_dev, &skb , BEbyte, &sqno, min_sqno)) < 0) + return; /* no packet complete */ + } else + sqno_end = sqno; + + /* + * MP buffer management .. reorders incoming packets .. + * lotsa mem-copies and not heavily tested. + * + * first check whether there is more than one link in the bundle + * then check whether the number is in order + */ + net_dev->ib.modify = 1; /* block timeout-timer */ + if (net_dev->ib.bundled && net_dev->ib.next_num != sqno) { + /* + * packet is not 'in order' + */ + struct sqqueue *q; + + q = (struct sqqueue *) kmalloc(sizeof(struct sqqueue), GFP_ATOMIC); + if (!q) { + net_dev->ib.modify = 0; + printk(KERN_WARNING "ippp/MPPP: Bad! Can't alloc sq node!\n"); + skb->free = 1; + dev_kfree_skb(skb, 0 /* FREE_READ */ ); + return; /* discard */ + } + q->skb = skb; + q->sqno_end = sqno_end; + q->sqno_start = sqno; + q->timer = jiffies + (ISDN_TIMER_1SEC) * 5; /* timeout after 5 seconds */ + + if (!net_dev->ib.sq) { + net_dev->ib.sq = q; + q->next = NULL; + } else { + struct sqqueue *ql = net_dev->ib.sq; + if (ql->sqno_start > q->sqno_start) { + q->next = ql; + net_dev->ib.sq = q; + } else { + while (ql->next && ql->next->sqno_start < q->sqno_start) + ql = ql->next; + q->next = ql->next; + ql->next = q; + } + } + net_dev->ib.modify = 0; + return; + } else { + /* + * packet was 'in order' .. push it higher + */ + struct sqqueue *q; + + net_dev->ib.next_num = sqno_end + 1; + isdn_ppp_push_higher(net_dev, lp, skb, -1); + + /* + * check queue, whether we have still buffered the next packet(s) + */ + while ((q = net_dev->ib.sq) && q->sqno_start == net_dev->ib.next_num) { + isdn_ppp_push_higher(net_dev, lp, q->skb, -1); + net_dev->ib.sq = q->next; + net_dev->ib.next_num = q->sqno_end + 1; + kfree(q); + } + } + net_dev->ib.modify = 0; + + } else + isdn_ppp_push_higher(net_dev, lp, skb , proto); + } else +#endif + isdn_ppp_push_higher(net_dev, lp, skb , -1); +} + + +/* + * push frame to higher layers + * note: net_dev has to be master net_dev + */ +static void isdn_ppp_push_higher(isdn_net_dev *net_dev, isdn_net_local *lp, struct sk_buff *skb,int proto) +{ + struct device *dev = &net_dev->dev; + struct ippp_struct *is = ippp_table[lp->ppp_slot]; + + if (proto < 0) { /* MP, oder normales Paket bei REJ_MP, MP Pakete gehen bei REJ zum pppd */ + if (skb->data[0] & 0x01) { /* is it odd? */ + proto = (unsigned char) skb->data[0]; + skb_pull(skb,1); /* protocol ID is only 8 bit */ + } else { + proto = ((int) (unsigned char) skb->data[0] << 8) + (unsigned char) skb->data[1]; + skb_pull(skb,2); + } + } + + if(is->debug & 0x10) + printk(KERN_DEBUG "push, skb %ld %04x\n",skb->len,proto); + + switch (proto) { + case PPP_IPX: /* untested */ + if(is->debug & 0x20) + printk(KERN_DEBUG "isdn_ppp: _IPX\n"); + skb->dev = dev; + skb->mac.raw = skb->data; + skb->protocol = htons(ETH_P_IPX); + break; +#ifdef CONFIG_ISDN_PPP_VJ + case PPP_VJC_UNCOMP: + if(is->debug & 0x20) + printk(KERN_DEBUG "isdn_ppp: VJC_UNCOMP\n"); + if(slhc_remember(ippp_table[net_dev->local.ppp_slot]->slcomp, skb->data, skb->len) <= 0) { + printk(KERN_WARNING "isdn_ppp: received illegal VJC_UNCOMP frame!\n"); + net_dev->local.stats.rx_dropped++; + skb->free = 1; + dev_kfree_skb(skb,0 /* FREE_READ */ ); + return; + } +#endif + case PPP_IP: + if(is->debug & 0x20) + printk(KERN_DEBUG "isdn_ppp: IP\n"); + skb->dev = dev; + skb->mac.raw = skb->data; + skb->protocol = htons(ETH_P_IP); + break; + case PPP_VJC_COMP: + if(is->debug & 0x20) + printk(KERN_DEBUG "isdn_ppp: VJC_COMP\n"); +#ifdef CONFIG_ISDN_PPP_VJ + { + struct sk_buff *skb_old = skb; + int pkt_len; + skb = dev_alloc_skb(skb_old->len + 40); + + skb_old->free = 1; + + if (!skb) { + printk(KERN_WARNING "%s: Memory squeeze, dropping packet.\n", dev->name); + net_dev->local.stats.rx_dropped++; + dev_kfree_skb(skb_old,0 /* FREE_READ */ ); + return; + } + skb->dev = dev; + skb_put(skb,skb_old->len + 40); + memcpy(skb->data, skb_old->data, skb_old->len); + skb->mac.raw = skb->data; + pkt_len = slhc_uncompress(ippp_table[net_dev->local.ppp_slot]->slcomp, + skb->data, skb_old->len); + dev_kfree_skb(skb_old,0 /* FREE_READ */ ); + if(pkt_len < 0) { + skb->free = 1; + dev_kfree_skb(skb, 0 /* FREE_READ */ ); + lp->stats.rx_dropped++; + return; + } + skb_trim(skb, pkt_len); + skb->protocol = htons(ETH_P_IP); + } +#else + printk(KERN_INFO "isdn: Ooopsa .. VJ-Compression support not compiled into isdn driver.\n"); + lp->stats.rx_dropped++; + skb->free = 1; + dev_kfree_skb(skb,0 /* FREE_READ */ ); + return; +#endif + break; + default: + isdn_ppp_fill_rq(skb->data, skb->len,proto, lp->ppp_slot); /* push data to pppd device */ + skb->free = 1; + dev_kfree_skb(skb,0 /* FREE_READ */ ); + return; + } + + netif_rx(skb); + net_dev->local.stats.rx_packets++; + /* Reset hangup-timer */ + lp->huptimer = 0; + + return; +} + +/* + * send ppp frame .. we expect a PIDCOMPressable proto -- + * (here: currently always PPP_IP,PPP_VJC_COMP,PPP_VJC_UNCOMP) + * + * VJ compression may change skb pointer!!! .. requeue with old + * skb isn't allowed!! + */ + +static void isdn_ppp_skb_destructor(struct sk_buff *skb) +{ + char outstr[80],*outpnt=outstr; + int i; + + *outpnt = 0; + for(i=0;i<24 && i<skb->len;i++) { + sprintf(outpnt,"%02x ",skb->data[i]); + outpnt += 3; + } + printk(KERN_DEBUG "ippp_dstrct: %s\n",outstr); +} + +int isdn_ppp_xmit(struct sk_buff *skb, struct device *dev) +{ + struct device *mdev = ((isdn_net_local *) (dev->priv) )->master; /* get master (for redundancy) */ + isdn_net_local *lp,*mlp; + isdn_net_dev *nd; + int proto = PPP_IP; /* 0x21 */ + struct ippp_struct *ipt,*ipts; + + if(mdev) + mlp = (isdn_net_local *) (mdev->priv); + else + mlp = (isdn_net_local *) (dev->priv); + nd = mlp->netdev; /* get master lp */ + ipts = ippp_table[mlp->ppp_slot]; + + if (!(ipts->pppcfg & SC_ENABLE_IP)) { /* PPP connected ? */ + if(ipts->debug & 0x1) { + printk(KERN_INFO "%s: IP frame delayed.\n",dev->name); + skb->destructor = isdn_ppp_skb_destructor; + } + return 1; + } + + skb->destructor = NULL; + + lp = nd->queue; /* get lp on top of queue */ + if(lp->sav_skb) { /* find a non-busy device */ + isdn_net_local *nlp = lp->next; + while(lp->sav_skb) { + if(lp == nlp) + return 1; + nlp = nd->queue = nd->queue->next; + } + lp = nlp; + } + ipt = ippp_table[lp->ppp_slot]; + + lp->huptimer = 0; + + /* + * after this line .. requeueing in the device queue is no longer allowed!!! + */ + + if(ipt->debug & 0x4) + printk(KERN_DEBUG "xmit skb, len %ld\n",skb->len); + +#ifdef CONFIG_ISDN_PPP_VJ + if (ipts->pppcfg & SC_COMP_TCP) { /* ipts here? probably yes .. but this check again */ + struct sk_buff *new_skb; + + new_skb = dev_alloc_skb(skb->len); + if(new_skb) { + u_char *buf; + int pktlen; + + new_skb->dev = skb->dev; + new_skb->free = 1; + skb_put(new_skb,skb->len); + buf = skb->data; + + pktlen = slhc_compress(ipts->slcomp, skb->data, skb->len, new_skb->data, + &buf, !(ipts->pppcfg & SC_NO_TCP_CCID)); + + if(buf != skb->data) { /* copied to new buffer ??? (btw: WHY must slhc copy it?? *sigh*) */ + if(new_skb->data != buf) + printk(KERN_ERR "isdn_ppp: FATAL error after slhc_compress!!\n"); + dev_kfree_skb(skb,FREE_WRITE); + skb = new_skb; + } + else { + dev_kfree_skb(new_skb,0 /* FREE_WRITE */ ); + } + + skb_trim(skb,pktlen); + if (skb->data[0] & SL_TYPE_COMPRESSED_TCP) { /* cslip? style -> PPP */ + proto = PPP_VJC_COMP; + skb->data[0] ^= SL_TYPE_COMPRESSED_TCP; + } else { + if (skb->data[0] >= SL_TYPE_UNCOMPRESSED_TCP) + proto = PPP_VJC_UNCOMP; + skb->data[0] = (skb->data[0] & 0x0f) | 0x40; + } + } + } +#endif + + if(ipt->debug & 0x24) + printk(KERN_DEBUG "xmit2 skb, len %ld, proto %04x\n",skb->len,proto); + +#ifdef CONFIG_ISDN_MPP + if (ipt->mpppcfg & SC_MP_PROT) { + /* we get mp_seqno from static isdn_net_local */ + long mp_seqno = ipts->mp_seqno; + ipts->mp_seqno++; + nd->queue = nd->queue->next; + if (ipt->mpppcfg & SC_OUT_SHORT_SEQ) { + skb_push(skb, 3); + mp_seqno &= 0xfff; + skb->data[0] = MP_BEGIN_FRAG | MP_END_FRAG | (mp_seqno >> 8); /* (B)egin & (E)ndbit .. */ + skb->data[1] = mp_seqno & 0xff; + skb->data[2] = proto; /* PID compression */ + } else { + skb_push(skb, 5); + skb->data[0] = MP_BEGIN_FRAG | MP_END_FRAG; /* (B)egin & (E)ndbit .. */ + skb->data[1] = (mp_seqno >> 16) & 0xff; /* sequence number: 24bit */ + skb->data[2] = (mp_seqno >> 8) & 0xff; + skb->data[3] = (mp_seqno >> 0) & 0xff; + skb->data[4] = proto; /* PID compression */ + } + proto = PPP_MP; /* MP Protocol, 0x003d */ + } +#endif + skb_push(skb,4); + skb->data[0] = 0xff; /* All Stations */ + skb->data[1] = 0x03; /* Unnumbered information */ + skb->data[2] = proto >> 8; + skb->data[3] = proto & 0xff; + + /* tx-stats are now updated via BSENT-callback */ + if(isdn_net_send_skb(dev , lp , skb)) { + if(lp->sav_skb) { /* whole sav_skb processing with disabled IRQs ?? */ + printk(KERN_ERR "%s: whoops .. there is another stored skb!\n",dev->name); + dev_kfree_skb(skb,FREE_WRITE); + } + else + lp->sav_skb = skb; + } + return 0; +} + +void isdn_ppp_free_sqqueue(isdn_net_dev * p) +{ + struct sqqueue *q = p->ib.sq; + + p->ib.sq = NULL; + while(q) { + struct sqqueue *qn = q->next; + if(q->skb) { + q->skb->free = 1; + dev_kfree_skb(q->skb,0 /* FREE_READ */ ); + } + kfree(q); + q = qn; + } + +} + +void isdn_ppp_free_mpqueue(isdn_net_dev * p) +{ + struct mpqueue *ql, *q = p->mp_last; + while (q) { + ql = q->next; + q->skb->free = 1; + dev_kfree_skb(q->skb,0 /* FREE_READ */ ); + kfree(q); + q = ql; + } +} + +#ifdef CONFIG_ISDN_MPP + +static int isdn_ppp_bundle(struct ippp_struct *is, int unit) +{ + char ifn[IFNAMSIZ + 1]; + long flags; + isdn_net_dev *p; + isdn_net_local *lp,*nlp; + + sprintf(ifn, "ippp%d", unit); + p = isdn_net_findif(ifn); + if (!p) + return -1; + + isdn_timer_ctrl(ISDN_TIMER_IPPP, 1); /* enable timer for ippp/MP */ + + save_flags(flags); + cli(); + + nlp = is->lp; + + lp = p->queue; + p->ib.bundled = 1; + nlp->last = lp->last; + lp->last->next = nlp; + lp->last = nlp; + nlp->next = lp; + p->queue = nlp; + + ippp_table[nlp->ppp_slot]->unit = ippp_table[lp->ppp_slot]->unit; +/* maybe also SC_CCP stuff */ + ippp_table[nlp->ppp_slot]->pppcfg |= ippp_table[lp->ppp_slot]->pppcfg & + (SC_ENABLE_IP | SC_NO_TCP_CCID | SC_REJ_COMP_TCP); + + ippp_table[nlp->ppp_slot]->mpppcfg |= ippp_table[lp->ppp_slot]->mpppcfg & + (SC_MP_PROT | SC_REJ_MP_PROT | SC_OUT_SHORT_SEQ | SC_IN_SHORT_SEQ); +#if 0 + if (ippp_table[nlp->ppp_slot]->mpppcfg != ippp_table[lp->ppp_slot]->mpppcfg) { + printk(KERN_WARNING "isdn_ppp_bundle: different MP options %04x and %04x\n", + ippp_table[nlp->ppp_slot]->mpppcfg, ippp_table[lp->ppp_slot]->mpppcfg); + } +#endif + + restore_flags(flags); + return 0; +} + + +static void isdn_ppp_mask_queue(isdn_net_dev * dev, long mask) +{ + struct mpqueue *q = dev->mp_last; + while (q) { + q->sqno &= mask; + q = q->next; + } +} + + +static int isdn_ppp_fill_mpqueue(isdn_net_dev * dev, struct sk_buff ** skb, int BEbyte, int *sqnop, int min_sqno) +{ + struct mpqueue *qe, *q1, *q; + long cnt, flags; + int pktlen, sqno_end; + int sqno = *sqnop; + + q1 = (struct mpqueue *) kmalloc(sizeof(struct mpqueue), GFP_KERNEL); + if (!q1) { + printk(KERN_WARNING "isdn_ppp_fill_mpqueue: Can't alloc struct memory.\n"); + save_flags(flags); + cli(); + isdn_ppp_cleanup_queue(dev, min_sqno); + restore_flags(flags); + return -1; + } + q1->skb = *skb; + q1->sqno = sqno; + q1->BEbyte = BEbyte; + q1->time = jiffies; + + save_flags(flags); + cli(); + + if (!(q = dev->mp_last)) { + dev->mp_last = q1; + q1->next = NULL; + q1->last = NULL; + isdn_ppp_cleanup_queue(dev, min_sqno); /* not necessary */ + restore_flags(flags); + return -1; + } + for (;;) { /* the faster way would be to step from the queue-end to the start */ + if (sqno > q->sqno) { + if (q->next) { + q = q->next; + continue; + } + q->next = q1; + q1->next = NULL; + q1->last = q; + break; + } + if (sqno == q->sqno) + printk(KERN_WARNING "isdn_fill_mpqueue: illegal sqno received!!\n"); + q1->last = q->last; + q1->next = q; + if (q->last) { + q->last->next = q1; + } else + dev->mp_last = q1; + q->last = q1; + break; + } + +/* now we check whether we completed a packet with this fragment */ + pktlen = -q1->skb->len; + q = q1; + cnt = q1->sqno; + while (!(q->BEbyte & MP_END_FRAG)) { + cnt++; + if (!(q->next) || q->next->sqno != cnt) { + isdn_ppp_cleanup_queue(dev, min_sqno); + restore_flags(flags); + return -1; + } + pktlen += q->skb->len; + q = q->next; + } + pktlen += q->skb->len; + qe = q; + + q = q1; + cnt = q1->sqno; + while (!(q->BEbyte & MP_BEGIN_FRAG)) { + cnt--; + if (!(q->last) || q->last->sqno != cnt) { + isdn_ppp_cleanup_queue(dev, min_sqno); + restore_flags(flags); + return -1; + } + pktlen += q->skb->len; + q = q->last; + } + pktlen += q->skb->len; + + if (q->last) + q->last->next = qe->next; + else + dev->mp_last = qe->next; + + if (qe->next) + qe->next->last = q->last; + qe->next = NULL; + sqno_end = qe->sqno; + *sqnop = q->sqno; + + isdn_ppp_cleanup_queue(dev, min_sqno); + restore_flags(flags); + + *skb = dev_alloc_skb(pktlen + 40); /* not needed: +40 for VJ compression .. */ + + if (!(*skb)) { + while (q) { + struct mpqueue *ql = q->next; + q->skb->free = 1; + dev_kfree_skb(q->skb,0 /* FREE_READ */ ); + kfree(q); + q = ql; + } + return -2; + } + cnt = 0; + skb_put(*skb,pktlen); + while (q) { + struct mpqueue *ql = q->next; + memcpy((*skb)->data + cnt, q->skb->data, q->skb->len); + cnt += q->skb->len; + q->skb->free = 1; + dev_kfree_skb(q->skb,0 /* FREE_READ */ ); + kfree(q); + q = ql; + } + + return sqno_end; +} + +/* + * remove stale packets from list + */ + +static void isdn_ppp_cleanup_queue(isdn_net_dev * dev, long min_sqno) +{ +#ifdef CONFIG_ISDN_PPP_VJ + int toss = 0; +#endif +/* z.z einfaches aussortieren gammeliger pakete. Fuer die Zukunft: + eventuell, solange vorne kein B-paket ist und sqno<=min_sqno: auch rauswerfen + wenn sqno<min_sqno und Luecken vorhanden sind: auch weg (die koennen nicht mehr gefuellt werden) + bei paketen groesser min_sqno: ueber mp_mrru: wenn summe ueber pktlen der rumhaengenden Pakete + groesser als mrru ist: raus damit , Pakete muessen allerdings zusammenhaengen sonst koennte + ja ein Paket mit B und eins mit E dazwischenpassen */ + + struct mpqueue *ql, *q = dev->mp_last; + while (q) { + if (q->sqno < min_sqno) { + if (q->BEbyte & MP_END_FRAG) { + printk(KERN_DEBUG "ippp: freeing stale packet!\n"); + if ((dev->mp_last = q->next)) + q->next->last = NULL; + while (q) { + ql = q->last; + q->skb->free = 1; + dev_kfree_skb(q->skb,0 /* FREE_READ */ ); + kfree(q); +#ifdef CONFIG_ISDN_PPP_VJ + toss = 1; +#endif + q = ql; + } + q = dev->mp_last; + } else + q = q->next; + } else + break; + } +#ifdef CONFIG_ISDN_PPP_VJ + /* did we free a stale frame ? */ + if(toss) + slhc_toss(ippp_table[dev->local.ppp_slot]->slcomp); +#endif +} + +/* + * a buffered packet timed-out? + */ + +#endif + +void isdn_ppp_timer_timeout(void) +{ +#ifdef CONFIG_ISDN_MPP + isdn_net_dev *net_dev = dev->netdev; + struct sqqueue *q, *ql = NULL, *qn; + + while (net_dev) { + isdn_net_local *lp = &net_dev->local; + if (net_dev->ib.modify || lp->master) { /* interface locked or slave?*/ + net_dev = net_dev->next; + continue; + } + + q = net_dev->ib.sq; + while (q) { + if (q->sqno_start == net_dev->ib.next_num || q->timer < jiffies) { + +#ifdef CONFIG_ISDN_PPP_VJ + /* did we step over a missing frame ? */ + if(q->sqno_start != net_dev->ib.next_num) + slhc_toss(ippp_table[lp->ppp_slot]->slcomp); +#endif + + ql = net_dev->ib.sq; + net_dev->ib.sq = q->next; + net_dev->ib.next_num = q->sqno_end + 1; + q->next = NULL; + for (; ql;) { + isdn_ppp_push_higher(net_dev, lp, ql->skb, -1); + qn = ql->next; + kfree(ql); + ql = qn; + } + q = net_dev->ib.sq; + } else + q = q->next; + } + net_dev = net_dev->next; + } +#endif +} + +/* + * network device ioctl handlers + */ + +static int isdn_ppp_dev_ioctl_stats(int slot,struct ifreq *ifr,struct device *dev) +{ + struct ppp_stats *res, t; + isdn_net_local *lp = (isdn_net_local *) dev->priv; + + res = (struct ppp_stats *) ifr->ifr_ifru.ifru_data; + + /* build a temporary stat struct and copy it to user space */ + + memset (&t, 0, sizeof(struct ppp_stats)); + if(dev->flags & IFF_UP) { + t.p.ppp_ipackets = lp->stats.rx_packets; + t.p.ppp_ierrors = lp->stats.rx_errors; + t.p.ppp_opackets = lp->stats.tx_packets; + t.p.ppp_oerrors = lp->stats.tx_errors; +#ifdef CONFIG_ISDN_PPP_VJ + if(slot >= 0 && ippp_table[slot]->slcomp) { + struct slcompress *slcomp = ippp_table[slot]->slcomp; + t.vj.vjs_packets = slcomp->sls_o_compressed+slcomp->sls_o_uncompressed; + t.vj.vjs_compressed = slcomp->sls_o_compressed; + t.vj.vjs_searches = slcomp->sls_o_searches; + t.vj.vjs_misses = slcomp->sls_o_misses; + t.vj.vjs_errorin = slcomp->sls_i_error; + t.vj.vjs_tossed = slcomp->sls_i_tossed; + t.vj.vjs_uncompressedin = slcomp->sls_i_uncompressed; + t.vj.vjs_compressedin = slcomp->sls_i_compressed; + } +#endif + } + return copy_to_user (res, &t, sizeof (struct ppp_stats)) ? -EFAULT : 0; +} + +int isdn_ppp_dev_ioctl(struct device *dev, struct ifreq *ifr, int cmd) +{ + int error = 0; + char *r; + int len; + isdn_net_local *lp = (isdn_net_local *) dev->priv; + +#if 0 + printk(KERN_DEBUG "ippp, dev_ioctl: cmd %#08x , %d \n",cmd,lp->ppp_slot); +#endif + + if (lp->p_encap != ISDN_NET_ENCAP_SYNCPPP) + return -EINVAL; + + switch (cmd) { + case SIOCGPPPVER: + r = (char *) ifr->ifr_ifru.ifru_data; + len = strlen(PPP_VERSION) + 1; + if (copy_to_user(r, PPP_VERSION, len)) + error = -EFAULT; + break; + case SIOCGPPPSTATS: + error = isdn_ppp_dev_ioctl_stats (lp->ppp_slot, ifr, dev); + break; + default: + error = -EINVAL; + break; + } + return error; +} + +static int isdn_ppp_if_get_unit(char *name) +{ + int len, i, unit = 0, deci; + + len = strlen(name); + + if(strncmp("ippp",name,4) || len > 8) + return -1; + + for (i = 0, deci = 1; i < len; i++, deci *= 10) { + char a = name[len-i-1]; + if (a >= '0' && a <= '9') + unit += (a - '0') * deci; + else + break; + } + if (!i || len-i != 4) + unit = -1; + + return unit; +} + + +int isdn_ppp_dial_slave(char *name) +{ +#ifdef CONFIG_ISDN_MPP + isdn_net_dev *ndev; + isdn_net_local *lp; + struct device *sdev; + + if(!(ndev = isdn_net_findif(name))) + return 1; + lp = &ndev->local; + if(!(lp->flags & ISDN_NET_CONNECTED)) + return 5; + + sdev = lp->slave; + while(sdev) + { + isdn_net_local *mlp = (isdn_net_local *) sdev->priv; + if(!(mlp->flags & ISDN_NET_CONNECTED)) + break; + sdev = mlp->slave; + } + if(!sdev) + return 2; + + isdn_net_force_dial_lp((isdn_net_local *) sdev->priv); + return 0; +#else + return -1; +#endif +} + +int isdn_ppp_hangup_slave(char *name) +{ +#ifdef CONFIG_ISDN_MPP + isdn_net_dev *ndev; + isdn_net_local *lp; + struct device *sdev; + + if(!(ndev = isdn_net_findif(name))) + return 1; + lp = &ndev->local; + if(!(lp->flags & ISDN_NET_CONNECTED)) + return 5; + + sdev = lp->slave; + while(sdev) + { + isdn_net_local *mlp = (isdn_net_local *) sdev->priv; + if((mlp->flags & ISDN_NET_CONNECTED)) + break; + sdev = mlp->slave; + } + if(!sdev) + return 2; + + isdn_net_hangup(sdev); + return 0; +#else + return -1; +#endif +} + diff --git a/drivers/isdn/isdn_ppp.h b/drivers/isdn/isdn_ppp.h new file mode 100644 index 000000000..70de75950 --- /dev/null +++ b/drivers/isdn/isdn_ppp.h @@ -0,0 +1,56 @@ +/* $Id: isdn_ppp.h,v 1.4 1996/05/06 11:34:56 hipp Exp $ + * + * header for Linux ISDN subsystem, functions for synchronous PPP (linklevel). + * + * Copyright 1995,96 by Michael Hipp (Michael.Hipp@student.uni-tuebingen.de) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Log: isdn_ppp.h,v $ + * Revision 1.4 1996/05/06 11:34:56 hipp + * fixed a few bugs + * + * Revision 1.3 1996/04/30 09:33:10 fritz + * Removed compatibility-macros. + * + * Revision 1.2 1996/04/20 16:35:11 fritz + * Changed isdn_ppp_receive to use sk_buff as parameter. + * Added definition of isdn_ppp_dial_slave and ippp_table. + * + * Revision 1.1 1996/01/10 21:39:10 fritz + * Initial revision + * + */ + +extern void isdn_ppp_timer_timeout(void); +extern int isdn_ppp_read(int , struct file *, char *, int); +extern int isdn_ppp_write(int , struct file *, const char *, int); +extern int isdn_ppp_open(int , struct file *); +extern int isdn_ppp_init(void); +extern void isdn_ppp_cleanup(void); +extern int isdn_ppp_free(isdn_net_local *); +extern int isdn_ppp_bind(isdn_net_local *); +extern int isdn_ppp_xmit(struct sk_buff *, struct device *); +extern void isdn_ppp_receive(isdn_net_dev *, isdn_net_local *, struct sk_buff *); +extern int isdn_ppp_dev_ioctl(struct device *, struct ifreq *, int); +extern void isdn_ppp_free_mpqueue(isdn_net_dev *); +extern void isdn_ppp_free_sqqueue(isdn_net_dev *); +extern int isdn_ppp_select(int, struct file *, int, select_table *); +extern int isdn_ppp_ioctl(int, struct file *, unsigned int, unsigned long); +extern void isdn_ppp_release(int, struct file *); +extern int isdn_ppp_dial_slave(char *); +extern void isdn_ppp_wakeup_daemon(isdn_net_local *); + +extern struct ippp_struct *ippp_table[ISDN_MAX_CHANNELS]; diff --git a/drivers/isdn/isdn_tty.c b/drivers/isdn/isdn_tty.c new file mode 100644 index 000000000..e1522022f --- /dev/null +++ b/drivers/isdn/isdn_tty.c @@ -0,0 +1,2805 @@ +/* $Id: isdn_tty.c,v 1.21 1996/06/24 17:40:28 fritz Exp $ + * + * Linux ISDN subsystem, tty functions and AT-command emulator (linklevel). + * + * Copyright 1994,95,96 by Fritz Elfert (fritz@wuemaus.franken.de) + * Copyright 1995,96 by Thinking Objects Software GmbH Wuerzburg + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Log: isdn_tty.c,v $ + * Revision 1.21 1996/06/24 17:40:28 fritz + * Bugfix: Did not compile without CONFIG_ISDN_AUDIO + * + * Revision 1.20 1996/06/15 14:59:39 fritz + * Fixed isdn_tty_tint() to handle partially sent + * sk_buffs. + * + * Revision 1.19 1996/06/12 15:53:56 fritz + * Bugfix: AT+VTX and AT+VRX could be executed without + * having a connection. + * Missing check for NULL tty in isdn_tty_flush_buffer(). + * + * Revision 1.18 1996/06/07 11:17:33 tsbogend + * added missing #ifdef CONFIG_ISDN_AUDIO to make compiling without + * audio support possible + * + * Revision 1.17 1996/06/06 14:55:47 fritz + * Changed to support DTMF decoding on audio playback also. + * Bugfix: Added check for invalid info->isdn_driver in + * isdn_tty_senddown(). + * Clear ncarrier flag on last close() of a tty. + * + * Revision 1.16 1996/06/05 02:24:12 fritz + * Added DTMF decoder for audio mode. + * + * Revision 1.15 1996/06/03 20:35:01 fritz + * Fixed typos. + * + * Revision 1.14 1996/06/03 20:12:19 fritz + * Fixed typos. + * Added call to write_wakeup via isdn_tty_flush_buffer() + * in isdn_tty_modem_hup(). + * + * Revision 1.13 1996/05/31 01:33:29 fritz + * Changed buffering due to bad performance with mgetty. + * Now sk_buff is delayed allocated in isdn_tty_senddown + * using xmit_buff like in standard serial driver. + * Fixed module locking. + * Added DLE-DC4 handling in voice mode. + * + * Revision 1.12 1996/05/19 01:34:40 fritz + * Bugfix: ATS returned error. + * Register 20 made readonly. + * + * Revision 1.11 1996/05/18 01:37:03 fritz + * Added spelling corrections and some minor changes + * to stay in sync with kernel. + * + * Revision 1.10 1996/05/17 03:51:49 fritz + * Changed DLE handling for audio receive. + * + * Revision 1.9 1996/05/11 21:52:07 fritz + * Changed queue management to use sk_buffs. + * + * Revision 1.8 1996/05/10 08:49:43 fritz + * Checkin before major changes of tty-code. + * + * Revision 1.7 1996/05/07 09:15:09 fritz + * Reorganized and general cleanup. + * Bugfixes: + * - Audio-transmit working now. + * - "NO CARRIER" now reported, when hanging up with DTR low. + * - Corrected CTS handling. + * + * Revision 1.6 1996/05/02 03:59:25 fritz + * Bugfixes: + * - On dialout, layer-2 setup had been incomplete + * when using new auto-layer2 feature. + * - On hangup, "NO CARRIER" message sometimes missing. + * + * Revision 1.5 1996/04/30 21:05:25 fritz + * Test commit + * + * Revision 1.4 1996/04/20 16:39:54 fritz + * Changed all io to go through generic routines in isdn_common.c + * Fixed a real ugly bug in modem-emulator: 'ATA' had been accepted + * even when a call has been cancelled from the remote machine. + * + * Revision 1.3 1996/02/11 02:12:32 fritz + * Bugfixes according to similar fixes in standard serial.c of kernel. + * + * Revision 1.2 1996/01/22 05:12:25 fritz + * replaced my_atoi by simple_strtoul + * + * Revision 1.1 1996/01/09 04:13:18 fritz + * Initial revision + * + */ + +#include <asm/uaccess.h> +#define __NO_VERSION__ +#include <linux/config.h> +#include <linux/module.h> +#include <linux/isdn.h> +#include "isdn_common.h" +#include "isdn_tty.h" +#ifdef CONFIG_ISDN_AUDIO +#include "isdn_audio.h" +#define VBUF 0x3e0 +#define VBUFX (VBUF/16) +#endif + +/* Prototypes */ + +static int isdn_tty_edit_at(const char *, int, modem_info *, int); +static void isdn_tty_check_esc(const u_char *, u_char, int, int *, int *, int); +static void isdn_tty_modem_reset_regs(atemu *, int); +static void isdn_tty_cmd_ATA(modem_info *); +static void isdn_tty_at_cout(char *, modem_info *); +static void isdn_tty_flush_buffer(struct tty_struct *); + +/* Leave this unchanged unless you know what you do! */ +#define MODEM_PARANOIA_CHECK +#define MODEM_DO_RESTART + +static char *isdn_ttyname_ttyI = "ttyI"; +static char *isdn_ttyname_cui = "cui"; +static int bit2si[8] = {1,5,7,7,7,7,7,7}; +static int si2bit[8] = {4,1,4,4,4,4,4,4}; + +char *isdn_tty_revision = "$Revision: 1.21 $"; + +#define DLE 0x10 +#define ETX 0x03 +#define DC4 0x14 + +/* isdn_tty_try_read() is called from within isdn_receive_callback() + * to stuff incoming data directly into a tty's flip-buffer. This + * is done to speed up tty-receiving if the receive-queue is empty. + * This routine MUST be called with interrupts off. + * Return: + * 1 = Success + * 0 = Failure, data has to be buffered and later processed by + * isdn_tty_readmodem(). + */ +int isdn_tty_try_read(modem_info *info, struct sk_buff *skb) +{ + int c; + int len; + struct tty_struct *tty; + + if (info->online) { + if ((tty = info->tty)) { + if (info->mcr & UART_MCR_RTS) { + c = TTY_FLIPBUF_SIZE - tty->flip.count; + len = skb->len + skb->users; + if (c >= len) { + if (skb->users) + while (skb->len--) { + if (*skb->data == DLE) + tty_insert_flip_char(tty, DLE, 0); + tty_insert_flip_char(tty, *skb->data++, 0); + } + else { + memcpy(tty->flip.char_buf_ptr, + skb->data, len); + tty->flip.count += len; + tty->flip.char_buf_ptr += len; + memset(tty->flip.flag_buf_ptr, 0, len); + tty->flip.flag_buf_ptr += len; + } + if (info->emu.mdmreg[12] & 128) + tty->flip.flag_buf_ptr[len - 1] = 0xff; + queue_task_irq_off(&tty->flip.tqueue, &tq_timer); + skb->free = 1; + kfree_skb(skb, FREE_READ); + return 1; + } + } + } + } + return 0; +} + +/* isdn_tty_readmodem() is called periodically from within timer-interrupt. + * It tries getting received data from the receive queue an stuff it into + * the tty's flip-buffer. + */ +void isdn_tty_readmodem(void) +{ + int resched = 0; + int midx; + int i; + int c; + int r; + ulong flags; + struct tty_struct *tty; + modem_info *info; + + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + if ((midx = dev->m_idx[i]) >= 0) { + info = &dev->mdm.info[midx]; + if (info->online) { + r = 0; +#ifdef CONFIG_ISDN_AUDIO + isdn_audio_eval_dtmf(info); +#endif + if ((tty = info->tty)) { + if (info->mcr & UART_MCR_RTS) { + c = TTY_FLIPBUF_SIZE - tty->flip.count; + if (c > 0) { + save_flags(flags); + cli(); + r = isdn_readbchan(info->isdn_driver, info->isdn_channel, + tty->flip.char_buf_ptr, + tty->flip.flag_buf_ptr, c, 0); + /* CISCO AsyncPPP Hack */ + if (!(info->emu.mdmreg[12] & 128)) + memset(tty->flip.flag_buf_ptr, 0, r); + tty->flip.count += r; + tty->flip.flag_buf_ptr += r; + tty->flip.char_buf_ptr += r; + if (r) + queue_task_irq_off(&tty->flip.tqueue, &tq_timer); + restore_flags(flags); + } + } else + r = 1; + } else + r = 1; + if (r) { + info->rcvsched = 0; + resched = 1; + } else + info->rcvsched = 1; + } + } + } + if (!resched) + isdn_timer_ctrl(ISDN_TIMER_MODEMREAD, 0); +} + +void isdn_tty_cleanup_xmit(modem_info *info) +{ + struct sk_buff *skb; + unsigned long flags; + + save_flags(flags); + cli(); + if (skb_queue_len(&info->xmit_queue)) + while ((skb = skb_dequeue(&info->xmit_queue))) { + skb->free = 1; + kfree_skb(skb, FREE_WRITE); + } + if (skb_queue_len(&info->dtmf_queue)) + while ((skb = skb_dequeue(&info->dtmf_queue))) { + skb->free = 1; + kfree_skb(skb, FREE_WRITE); + } + restore_flags(flags); +} + +static void isdn_tty_tint(modem_info *info) +{ + struct sk_buff *skb = skb_dequeue(&info->xmit_queue); + int len, slen; + + if (!skb) + return; + len = skb->len; + if ((slen = isdn_writebuf_skb_stub(info->isdn_driver, + info->isdn_channel, skb)) == len) { + struct tty_struct *tty = info->tty; + info->send_outstanding++; + info->msr |= UART_MSR_CTS; + info->lsr |= UART_LSR_TEMT; + if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && + tty->ldisc.write_wakeup) + (tty->ldisc.write_wakeup) (tty); + wake_up_interruptible(&tty->write_wait); + return; + } + if (slen > 0) + skb_pull(skb,slen); + skb_queue_head(&info->xmit_queue, skb); +} + +#ifdef CONFIG_ISDN_AUDIO +int isdn_tty_countDLE(unsigned char *buf, int len) +{ + int count = 0; + + while (len--) + if (*buf++ == DLE) + count++; + return count; +} + +/* This routine is called from within isdn_tty_write() to perform + * DLE-decoding when sending audio-data. + */ +static int isdn_tty_handleDLEdown(modem_info *info, atemu *m, int len) +{ + unsigned char *p = &info->xmit_buf[info->xmit_count]; + int count = 0; + + while (len>0) { + if (m->lastDLE) { + m->lastDLE = 0; + switch (*p) { + case DLE: + /* Escape code */ + if (len>1) + memmove(p,p+1,len-1); + p--; + count++; + break; + case ETX: + /* End of data */ + info->vonline |= 4; + return count; + case DC4: + /* Abort RX */ + info->vonline &= ~1; + isdn_tty_at_cout("\020\003", info); + if (!info->vonline) + isdn_tty_at_cout("\r\nVCON\r\n", info); + /* Fall through */ + case 'q': + case 's': + /* Silence */ + if (len>1) + memmove(p,p+1,len-1); + p--; + break; + } + } else { + if (*p == DLE) + m->lastDLE = 1; + else + count++; + } + p++; + len--; + } + if (len<0) { + printk(KERN_WARNING "isdn_tty: len<0 in DLEdown\n"); + return 0; + } + return count; +} + +/* This routine is called from within isdn_tty_write() when receiving + * audio-data. It interrupts receiving, if an character other than + * ^S or ^Q is sent. + */ +static int isdn_tty_end_vrx(const char *buf, int c, int from_user) +{ + char tmpbuf[VBUF]; + char *p; + + if (c > VBUF) { + printk(KERN_ERR "isdn_tty: (end_vrx) BUFFER OVERFLOW!!!\n"); + return 1; + } + if (from_user) { + copy_from_user(tmpbuf, buf, c); + p = tmpbuf; + } else + p = (char *)buf; + while (c--) { + if ((*p != 0x11) && (*p != 0x13)) + return 1; + p++; + } + return 0; +} + +static int voice_cf[7] = { 1, 1, 4, 3, 2, 1, 1 }; + +#endif /* CONFIG_ISDN_AUDIO */ + +/* isdn_tty_senddown() is called either directly from within isdn_tty_write() + * or via timer-interrupt from within isdn_tty_modem_xmit(). It pulls + * outgoing data from the tty's xmit-buffer, handles voice-decompression or + * T.70 if necessary, and finally queues it up for sending via isdn_tty_tint. + */ +static void isdn_tty_senddown(modem_info * info) +{ + unsigned char *buf = info->xmit_buf; + int buflen; + int skb_res; + struct sk_buff *skb; + unsigned long flags; + + save_flags(flags); + cli(); + if (!(buflen = info->xmit_count)) { + restore_flags(flags); + return; + } + if (info->isdn_driver < 0) { + info->xmit_count = 0; + restore_flags(flags); + return; + } + skb_res = dev->drv[info->isdn_driver]->interface->hl_hdrlen + 4; +#ifdef CONFIG_ISDN_AUDIO + if (info->vonline & 2) { + /* For now, ifmt is fixed to 1 (alaw), since this + * is used with ISDN everywhere in the world, except + * US, Canada and Japan. + * Later, when US-ISDN protocols are implemented, + * this setting will depend on the D-channel protocol. + */ + int ifmt = 1; + int skb_len; + unsigned char hbuf[VBUF]; + + memcpy(hbuf,info->xmit_buf,buflen); + info->xmit_count = 0; + restore_flags(flags); + /* voice conversion/decompression */ + skb_len = buflen * voice_cf[info->emu.vpar[3]]; + skb = dev_alloc_skb(skb_len + skb_res); + if (!skb) { + printk(KERN_WARNING + "isdn_tty: Out of memory in ttyI%d senddown\n", info->line); + return; + } + skb_reserve(skb, skb_res); + switch (info->emu.vpar[3]) { + case 2: + case 3: + case 4: + /* adpcm, compatible to ZyXel 1496 modem + * with ROM revision 6.01 + */ + buflen = isdn_audio_adpcm2xlaw(info->adpcms, + ifmt, + hbuf, + skb_put(skb,skb_len), + buflen); + skb_trim(skb, buflen); + break; + case 5: + /* a-law */ + if (!ifmt) + isdn_audio_alaw2ulaw(hbuf,buflen); + memcpy(skb_put(skb,buflen),hbuf,buflen); + break; + case 6: + /* u-law */ + if (ifmt) + isdn_audio_ulaw2alaw(hbuf,buflen); + memcpy(skb_put(skb,buflen),hbuf,buflen); + break; + } + if (info->vonline & 4) { + info->vonline &= ~6; + if (!info->vonline) + isdn_tty_at_cout("\r\nVCON\r\n",info); + } + } else { +#endif /* CONFIG_ISDN_AUDIO */ + skb = dev_alloc_skb(buflen + skb_res); + if (!skb) { + printk(KERN_WARNING + "isdn_tty: Out of memory in ttyI%d senddown\n", info->line); + restore_flags(flags); + return; + } + skb_reserve(skb, skb_res); + memcpy(skb_put(skb,buflen),buf,buflen); + info->xmit_count = 0; + restore_flags(flags); +#ifdef CONFIG_ISDN_AUDIO + } +#endif + skb->free = 1; + if (info->emu.mdmreg[13] & 2) + /* Add T.70 simplified header */ + memcpy(skb_push(skb, 4), "\1\0\1\0", 4); + skb_queue_tail(&info->xmit_queue, skb); + if ((info->emu.mdmreg[12] & 0x10) != 0) + info->msr &= UART_MSR_CTS; + info->lsr &= UART_LSR_TEMT; +} + +/************************************************************ + * + * Modem-functions + * + * mostly "stolen" from original Linux-serial.c and friends. + * + ************************************************************/ + +/* The next routine is called once from within timer-interrupt + * triggered within isdn_tty_modem_ncarrier(). It calls + * isdn_tty_modem_result() to stuff a "NO CARRIER" Message + * into the tty's flip-buffer. + */ +static void isdn_tty_modem_do_ncarrier(unsigned long data) +{ + modem_info * info = (modem_info *)data; + isdn_tty_modem_result(3, info); +} + +/* Next routine is called, whenever the DTR-signal is raised. + * It checks the ncarrier-flag, and triggers the above routine + * when necessary. The ncarrier-flag is set, whenever DTR goes + * low. + */ +static void isdn_tty_modem_ncarrier(modem_info * info) +{ + if (info->ncarrier) { + info->ncarrier = 0; + info->nc_timer.expires = jiffies + HZ; + info->nc_timer.function = isdn_tty_modem_do_ncarrier; + info->nc_timer.data = (unsigned long)info; + add_timer(&info->nc_timer); + } +} + +/* isdn_tty_dial() performs dialing of a tty an the necessary + * setup of the lower levels before that. + */ +static void isdn_tty_dial(char *n, modem_info * info, atemu * m) +{ + int usg = ISDN_USAGE_MODEM; + int si = 7; + int l2 = m->mdmreg[14]; + isdn_ctrl cmd; + ulong flags; + int i; + int j; + + for (j=7;j>=0;j--) + if (m->mdmreg[18] & (1<<j)) { + si = bit2si[j]; + break; + } +#ifdef CONFIG_ISDN_AUDIO + if (si == 1) { + l2 = 4; + usg = ISDN_USAGE_VOICE; + } +#endif + m->mdmreg[20] = si2bit[si]; + save_flags(flags); + cli(); + i = isdn_get_free_channel(usg, l2, m->mdmreg[15], -1, -1); + if (i < 0) { + restore_flags(flags); + isdn_tty_modem_result(6, info); + } else { + info->isdn_driver = dev->drvmap[i]; + info->isdn_channel = dev->chanmap[i]; + info->drv_index = i; + dev->m_idx[i] = info->line; + dev->usage[i] |= ISDN_USAGE_OUTGOING; + isdn_info_update(); + restore_flags(flags); + cmd.driver = info->isdn_driver; + cmd.arg = info->isdn_channel; + cmd.command = ISDN_CMD_CLREAZ; + dev->drv[info->isdn_driver]->interface->command(&cmd); + strcpy(cmd.num, isdn_map_eaz2msn(m->msn, info->isdn_driver)); + cmd.driver = info->isdn_driver; + cmd.command = ISDN_CMD_SETEAZ; + dev->drv[info->isdn_driver]->interface->command(&cmd); + cmd.driver = info->isdn_driver; + cmd.command = ISDN_CMD_SETL2; + cmd.arg = info->isdn_channel + (l2 << 8); + dev->drv[info->isdn_driver]->interface->command(&cmd); + cmd.driver = info->isdn_driver; + cmd.command = ISDN_CMD_SETL3; + cmd.arg = info->isdn_channel + (m->mdmreg[15] << 8); + dev->drv[info->isdn_driver]->interface->command(&cmd); + cmd.driver = info->isdn_driver; + cmd.arg = info->isdn_channel; + sprintf(cmd.num, "%s,%s,%d,%d", n, isdn_map_eaz2msn(m->msn, info->isdn_driver), + si, m->mdmreg[19]); + cmd.command = ISDN_CMD_DIAL; + info->dialing = 1; + strcpy(dev->num[i], n); + isdn_info_update(); + dev->drv[info->isdn_driver]->interface->command(&cmd); + } +} + +/* isdn_tty_hangup() disassociates a tty from the real + * ISDN-line (hangup). The usage-status is cleared + * and some cleanup is done also. + */ +void isdn_tty_modem_hup(modem_info * info) +{ + isdn_ctrl cmd; + int usage; + + if (!info) + return; +#ifdef ISDN_DEBUG_MODEM_HUP + printk(KERN_DEBUG "Mhup ttyI%d\n", info->line); +#endif + info->rcvsched = 0; + info->online = 0; + isdn_tty_flush_buffer(info->tty); + if (info->vonline & 1) { + /* voice-recording, add DLE-ETX */ + isdn_tty_at_cout("\020\003", info); + } + if (info->vonline & 2) { + /* voice-playing, add DLE-DC4 */ + isdn_tty_at_cout("\020\024", info); + } + info->vonline = 0; +#ifdef CONFIG_ISDN_AUDIO + if (info->dtmf_state) { + kfree(info->dtmf_state); + info->dtmf_state = NULL; + } + if (info->adpcms) { + kfree(info->adpcms); + info->adpcms = NULL; + } + if (info->adpcmr) { + kfree(info->adpcmr); + info->adpcmr = NULL; + } +#endif + info->msr &= ~(UART_MSR_DCD | UART_MSR_RI); + info->lsr |= UART_LSR_TEMT; + if (info->isdn_driver >= 0) { + cmd.driver = info->isdn_driver; + cmd.command = ISDN_CMD_HANGUP; + cmd.arg = info->isdn_channel; + dev->drv[info->isdn_driver]->interface->command(&cmd); + isdn_all_eaz(info->isdn_driver, info->isdn_channel); + usage = (info->emu.mdmreg[20] == 1)? + ISDN_USAGE_VOICE:ISDN_USAGE_MODEM; + isdn_free_channel(info->isdn_driver, info->isdn_channel, + usage); + } + info->isdn_driver = -1; + info->isdn_channel = -1; + if (info->drv_index >= 0) { + dev->m_idx[info->drv_index] = -1; + info->drv_index = -1; + } +} + +static inline int isdn_tty_paranoia_check(modem_info * info, kdev_t device, const char *routine) +{ +#ifdef MODEM_PARANOIA_CHECK + if (!info) { + printk(KERN_WARNING "isdn_tty: null info_struct for (%d, %d) in %s\n", + MAJOR(device), MINOR(device), routine); + return 1; + } + if (info->magic != ISDN_ASYNC_MAGIC) { + printk(KERN_WARNING "isdn_tty: bad magic for modem struct (%d, %d) in %s\n", + MAJOR(device), MINOR(device), routine); + return 1; + } +#endif + return 0; +} + +/* + * This routine is called to set the UART divisor registers to match + * the specified baud rate for a serial port. + */ +static void isdn_tty_change_speed(modem_info * info) +{ + uint cflag, cval, fcr, quot; + int i; + + if (!info->tty || !info->tty->termios) + return; + cflag = info->tty->termios->c_cflag; + + quot = i = cflag & CBAUD; + if (i & CBAUDEX) { + i &= ~CBAUDEX; + if (i < 1 || i > 2) + info->tty->termios->c_cflag &= ~CBAUDEX; + else + i += 15; + } + if (quot) { + info->mcr |= UART_MCR_DTR; + isdn_tty_modem_ncarrier(info); + } else { + info->mcr &= ~UART_MCR_DTR; + if (info->emu.mdmreg[13] & 4) { +#ifdef ISDN_DEBUG_MODEM_HUP + printk(KERN_DEBUG "Mhup in changespeed\n"); +#endif + if (info->online) + info->ncarrier = 1; + isdn_tty_modem_reset_regs(&info->emu, 0); + isdn_tty_modem_hup(info); + } + return; + } + /* byte size and parity */ + cval = cflag & (CSIZE | CSTOPB); + cval >>= 4; + if (cflag & PARENB) + cval |= UART_LCR_PARITY; + if (!(cflag & PARODD)) + cval |= UART_LCR_EPAR; + fcr = 0; + + /* CTS flow control flag and modem status interrupts */ + if (cflag & CRTSCTS) { + info->flags |= ISDN_ASYNC_CTS_FLOW; + } else + info->flags &= ~ISDN_ASYNC_CTS_FLOW; + if (cflag & CLOCAL) + info->flags &= ~ISDN_ASYNC_CHECK_CD; + else { + info->flags |= ISDN_ASYNC_CHECK_CD; + } +} + +static int isdn_tty_startup(modem_info * info) +{ + ulong flags; + + if (info->flags & ISDN_ASYNC_INITIALIZED) + return 0; + save_flags(flags); + cli(); + isdn_MOD_INC_USE_COUNT(); +#ifdef ISDN_DEBUG_MODEM_OPEN + printk(KERN_DEBUG "starting up ttyi%d ...\n", info->line); +#endif + /* + * Now, initialize the UART + */ + info->mcr = UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2; + if (info->tty) + clear_bit(TTY_IO_ERROR, &info->tty->flags); + /* + * and set the speed of the serial port + */ + isdn_tty_change_speed(info); + + info->flags |= ISDN_ASYNC_INITIALIZED; + info->msr |= (UART_MSR_DSR | UART_MSR_CTS); + info->send_outstanding = 0; + restore_flags(flags); + return 0; +} + +/* + * This routine will shutdown a serial port; interrupts are disabled, and + * DTR is dropped if the hangup on close termio flag is on. + */ +static void isdn_tty_shutdown(modem_info * info) +{ + ulong flags; + + if (!(info->flags & ISDN_ASYNC_INITIALIZED)) + return; +#ifdef ISDN_DEBUG_MODEM_OPEN + printk(KERN_DEBUG "Shutting down isdnmodem port %d ....\n", info->line); +#endif + save_flags(flags); + cli(); /* Disable interrupts */ + isdn_MOD_DEC_USE_COUNT(); + if (!info->tty || (info->tty->termios->c_cflag & HUPCL)) { + info->mcr &= ~(UART_MCR_DTR | UART_MCR_RTS); + if (info->emu.mdmreg[13] & 4) { + isdn_tty_modem_reset_regs(&info->emu, 0); +#ifdef ISDN_DEBUG_MODEM_HUP + printk(KERN_DEBUG "Mhup in isdn_tty_shutdown\n"); +#endif + isdn_tty_modem_hup(info); + } + } + if (info->tty) + set_bit(TTY_IO_ERROR, &info->tty->flags); + + info->flags &= ~ISDN_ASYNC_INITIALIZED; + restore_flags(flags); +} + +/* isdn_tty_write() is the main send-routine. It is called from the upper + * levels within the kernel to perform sending data. Depending on the + * online-flag it either directs output to the at-command-interpreter or + * to the lower level. Additional tasks done here: + * - If online, check for escape-sequence (+++) + * - If sending audio-data, call isdn_tty_DLEdown() to parse DLE-codes. + * - If receiving audio-data, call isdn_tty_end_vrx() to abort if needed. + * - If dialing, abort dial. + */ +static int isdn_tty_write(struct tty_struct *tty, int from_user, const u_char * buf, int count) +{ + int c, total = 0; + ulong flags; + modem_info *info = (modem_info *) tty->driver_data; + + if (isdn_tty_paranoia_check(info, tty->device, "isdn_tty_write")) + return 0; + if (!tty) + return 0; + save_flags(flags); + cli(); + while (1) { + c = MIN(count, info->xmit_size - info->xmit_count); + if (info->isdn_driver >= 0) + c = MIN(c, dev->drv[info->isdn_driver]->maxbufsize); + if (c <= 0) + break; + if ((info->online > 1) || + (info->vonline & 2)) { + atemu *m = &info->emu; + + if (!(info->vonline & 2)) + isdn_tty_check_esc(buf, m->mdmreg[2], c, + &(m->pluscount), + &(m->lastplus), + from_user); + if (from_user) + copy_from_user(&(info->xmit_buf[info->xmit_count]), buf, c); + else + memcpy(&(info->xmit_buf[info->xmit_count]), buf, c); +#ifdef CONFIG_ISDN_AUDIO + if (info->vonline & 2) { + int cc; + + if (!(cc = isdn_tty_handleDLEdown(info,m,c))) { + /* If DLE decoding results in zero-transmit, but + * c originally was non-zero, do a wakeup. + */ + if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && + tty->ldisc.write_wakeup) + (tty->ldisc.write_wakeup) (tty); + wake_up_interruptible(&tty->write_wait); + info->msr |= UART_MSR_CTS; + info->lsr |= UART_LSR_TEMT; + } + info->xmit_count += cc; + } else +#endif + info->xmit_count += c; + if (m->mdmreg[13] & 1) { + isdn_tty_senddown(info); + isdn_tty_tint(info); + } + } else { + info->msr |= UART_MSR_CTS; + info->lsr |= UART_LSR_TEMT; +#ifdef CONFIG_ISDN_AUDIO + if (info->vonline & 1) { + if (isdn_tty_end_vrx(buf, c, from_user)) { + info->vonline &= ~1; + isdn_tty_at_cout("\020\003\r\nVCON\r\n", info); + } + } else +#endif + if (info->dialing) { + info->dialing = 0; +#ifdef ISDN_DEBUG_MODEM_HUP + printk(KERN_DEBUG "Mhup in isdn_tty_write\n"); +#endif + isdn_tty_modem_result(3, info); + isdn_tty_modem_hup(info); + } else + c = isdn_tty_edit_at(buf, c, info, from_user); + } + buf += c; + count -= c; + total += c; + } + if ((info->xmit_count) || (skb_queue_len(&info->xmit_queue))) + isdn_timer_ctrl(ISDN_TIMER_MODEMXMIT, 1); + restore_flags(flags); + return total; +} + +static int isdn_tty_write_room(struct tty_struct *tty) +{ + modem_info *info = (modem_info *) tty->driver_data; + int ret; + + if (isdn_tty_paranoia_check(info, tty->device, "isdn_tty_write_room")) + return 0; + if (!info->online) + return info->xmit_size; + ret = info->xmit_size - info->xmit_count; + return (ret < 0) ? 0 : ret; +} + +static int isdn_tty_chars_in_buffer(struct tty_struct *tty) +{ + modem_info *info = (modem_info *) tty->driver_data; + + if (isdn_tty_paranoia_check(info, tty->device, "isdn_tty_chars_in_buffer")) + return 0; + if (!info->online) + return 0; + return (info->xmit_count); +} + +static void isdn_tty_flush_buffer(struct tty_struct *tty) +{ + modem_info *info; + unsigned long flags; + + save_flags(flags); + cli(); + if (!tty) { + restore_flags(flags); + return; + } + info = (modem_info *) tty->driver_data; + if (isdn_tty_paranoia_check(info, tty->device, "isdn_tty_flush_buffer")) { + restore_flags(flags); + return; + } + isdn_tty_cleanup_xmit(info); + info->xmit_count = 0; + restore_flags(flags); + wake_up_interruptible(&tty->write_wait); + if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && + tty->ldisc.write_wakeup) + (tty->ldisc.write_wakeup) (tty); +} + +static void isdn_tty_flush_chars(struct tty_struct *tty) +{ + modem_info *info = (modem_info *) tty->driver_data; + + if (isdn_tty_paranoia_check(info, tty->device, "isdn_tty_flush_chars")) + return; + if ((info->xmit_count) || (skb_queue_len(&info->xmit_queue))) + isdn_timer_ctrl(ISDN_TIMER_MODEMXMIT, 1); +} + +/* + * ------------------------------------------------------------ + * isdn_tty_throttle() + * + * This routine is called by the upper-layer tty layer to signal that + * incoming characters should be throttled. + * ------------------------------------------------------------ + */ +static void isdn_tty_throttle(struct tty_struct *tty) +{ + modem_info *info = (modem_info *) tty->driver_data; + + if (isdn_tty_paranoia_check(info, tty->device, "isdn_tty_throttle")) + return; + if (I_IXOFF(tty)) + info->x_char = STOP_CHAR(tty); + info->mcr &= ~UART_MCR_RTS; +} + +static void isdn_tty_unthrottle(struct tty_struct *tty) +{ + modem_info *info = (modem_info *) tty->driver_data; + + if (isdn_tty_paranoia_check(info, tty->device, "isdn_tty_unthrottle")) + return; + if (I_IXOFF(tty)) { + if (info->x_char) + info->x_char = 0; + else + info->x_char = START_CHAR(tty); + } + info->mcr |= UART_MCR_RTS; +} + +/* + * ------------------------------------------------------------ + * isdn_tty_ioctl() and friends + * ------------------------------------------------------------ + */ + +/* + * isdn_tty_get_lsr_info - get line status register info + * + * Purpose: Let user call ioctl() to get info when the UART physically + * is emptied. On bus types like RS485, the transmitter must + * release the bus after transmitting. This must be done when + * the transmit shift register is empty, not be done when the + * transmit holding register is empty. This functionality + * allows RS485 driver to be written in user space. + */ +static int isdn_tty_get_lsr_info(modem_info * info, uint * value) +{ + u_char status; + uint result; + ulong flags; + + save_flags(flags); + cli(); + status = info->lsr; + restore_flags(flags); + result = ((status & UART_LSR_TEMT) ? TIOCSER_TEMT : 0); + return put_user(result, value); +} + + +static int isdn_tty_get_modem_info(modem_info * info, uint * value) +{ + u_char control, status; + uint result; + ulong flags; + + control = info->mcr; + save_flags(flags); + cli(); + status = info->msr; + restore_flags(flags); + result = ((control & UART_MCR_RTS) ? TIOCM_RTS : 0) + | ((control & UART_MCR_DTR) ? TIOCM_DTR : 0) + | ((status & UART_MSR_DCD) ? TIOCM_CAR : 0) + | ((status & UART_MSR_RI) ? TIOCM_RNG : 0) + | ((status & UART_MSR_DSR) ? TIOCM_DSR : 0) + | ((status & UART_MSR_CTS) ? TIOCM_CTS : 0); + return put_user(result, value); +} + +static int isdn_tty_set_modem_info(modem_info * info, uint cmd, uint * value) +{ + uint arg; + int pre_dtr; + int error; + + error = get_user(arg, ((uint *) value)); + if (error) + return error; + + switch (cmd) { + case TIOCMBIS: +#ifdef ISDN_DEBUG_MODEM_IOCTL + printk(KERN_DEBUG "ttyI%d ioctl TIOCMBIS\n", info->line); +#endif + if (arg & TIOCM_RTS) { + info->mcr |= UART_MCR_RTS; + } + if (arg & TIOCM_DTR) { + info->mcr |= UART_MCR_DTR; + isdn_tty_modem_ncarrier(info); + } + break; + case TIOCMBIC: +#ifdef ISDN_DEBUG_MODEM_IOCTL + printk(KERN_DEBUG "ttyI%d ioctl TIOCMBIC\n", info->line); +#endif + if (arg & TIOCM_RTS) { + info->mcr &= ~UART_MCR_RTS; + } + if (arg & TIOCM_DTR) { + info->mcr &= ~UART_MCR_DTR; + if (info->emu.mdmreg[13] & 4) { + isdn_tty_modem_reset_regs(&info->emu, 0); +#ifdef ISDN_DEBUG_MODEM_HUP + printk(KERN_DEBUG "Mhup in TIOCMBIC\n"); +#endif + if (info->online) + info->ncarrier = 1; + isdn_tty_modem_hup(info); + } + } + break; + case TIOCMSET: +#ifdef ISDN_DEBUG_MODEM_IOCTL + printk(KERN_DEBUG "ttyI%d ioctl TIOCMSET\n", info->line); +#endif + pre_dtr = (info->mcr & UART_MCR_DTR); + info->mcr = ((info->mcr & ~(UART_MCR_RTS | UART_MCR_DTR)) + | ((arg & TIOCM_RTS) ? UART_MCR_RTS : 0) + | ((arg & TIOCM_DTR) ? UART_MCR_DTR : 0)); + if (pre_dtr |= (info->mcr & UART_MCR_DTR)) { + if (!(info->mcr & UART_MCR_DTR)) { + if (info->emu.mdmreg[13] & 4) { + isdn_tty_modem_reset_regs(&info->emu, 0); +#ifdef ISDN_DEBUG_MODEM_HUP + printk(KERN_DEBUG "Mhup in TIOCMSET\n"); +#endif + if (info->online) + info->ncarrier = 1; + isdn_tty_modem_hup(info); + } + } else + isdn_tty_modem_ncarrier(info); + } + break; + default: + return -EINVAL; + } + return 0; +} + +static int isdn_tty_ioctl(struct tty_struct *tty, struct file *file, + uint cmd, ulong arg) +{ + modem_info *info = (modem_info *) tty->driver_data; + int error; + int retval; + + if (isdn_tty_paranoia_check(info, tty->device, "isdn_tty_ioctl")) + return -ENODEV; + if (tty->flags & (1 << TTY_IO_ERROR)) + return -EIO; + switch (cmd) { + case TCSBRK: /* SVID version: non-zero arg --> no break */ +#ifdef ISDN_DEBUG_MODEM_IOCTL + printk(KERN_DEBUG "ttyI%d ioctl TCSBRK\n", info->line); +#endif + retval = tty_check_change(tty); + if (retval) + return retval; + tty_wait_until_sent(tty, 0); + return 0; + case TCSBRKP: /* support for POSIX tcsendbreak() */ +#ifdef ISDN_DEBUG_MODEM_IOCTL + printk(KERN_DEBUG "ttyI%d ioctl TCSBRKP\n", info->line); +#endif + retval = tty_check_change(tty); + if (retval) + return retval; + tty_wait_until_sent(tty, 0); + return 0; + case TIOCGSOFTCAR: +#ifdef ISDN_DEBUG_MODEM_IOCTL + printk(KERN_DEBUG "ttyI%d ioctl TIOCGSOFTCAR\n", info->line); +#endif + return put_user(C_CLOCAL(tty) ? 1 : 0, (uint *) arg); + case TIOCSSOFTCAR: +#ifdef ISDN_DEBUG_MODEM_IOCTL + printk(KERN_DEBUG "ttyI%d ioctl TIOCSSOFTCAR\n", info->line); +#endif + error = get_user(arg ,((uint *) arg)); + if (error) + return error; + tty->termios->c_cflag = + ((tty->termios->c_cflag & ~CLOCAL) | + (arg ? CLOCAL : 0)); + return 0; + case TIOCMGET: +#ifdef ISDN_DEBUG_MODEM_IOCTL + printk(KERN_DEBUG "ttyI%d ioctl TIOCMGET\n", info->line); +#endif + return isdn_tty_get_modem_info(info, (uint *) arg); + case TIOCMBIS: + case TIOCMBIC: + case TIOCMSET: + return isdn_tty_set_modem_info(info, cmd, (uint *) arg); + case TIOCSERGETLSR: /* Get line status register */ +#ifdef ISDN_DEBUG_MODEM_IOCTL + printk(KERN_DEBUG "ttyI%d ioctl TIOCSERGETLSR\n", info->line); +#endif + return isdn_tty_get_lsr_info(info, (uint *) arg); + + default: +#ifdef ISDN_DEBUG_MODEM_IOCTL + printk(KERN_DEBUG "UNKNOWN ioctl 0x%08x on ttyi%d\n", cmd, info->line); +#endif + return -ENOIOCTLCMD; + } + return 0; +} + +static void isdn_tty_set_termios(struct tty_struct *tty, struct termios *old_termios) +{ + modem_info *info = (modem_info *) tty->driver_data; + + if (!old_termios) + isdn_tty_change_speed(info); + else { + if (tty->termios->c_cflag == old_termios->c_cflag) + return; + isdn_tty_change_speed(info); + if ((old_termios->c_cflag & CRTSCTS) && + !(tty->termios->c_cflag & CRTSCTS)) { + tty->hw_stopped = 0; + } + } +} + +/* + * ------------------------------------------------------------ + * isdn_tty_open() and friends + * ------------------------------------------------------------ + */ +static int isdn_tty_block_til_ready(struct tty_struct *tty, struct file *filp, modem_info * info) +{ + struct wait_queue wait = {current, NULL}; + int do_clocal = 0; + unsigned long flags; + int retval; + + /* + * If the device is in the middle of being closed, then block + * until it's done, and then try again. + */ + if (tty_hung_up_p(filp) || + (info->flags & ISDN_ASYNC_CLOSING)) { + if (info->flags & ISDN_ASYNC_CLOSING) + interruptible_sleep_on(&info->close_wait); +#ifdef MODEM_DO_RESTART + if (info->flags & ISDN_ASYNC_HUP_NOTIFY) + return -EAGAIN; + else + return -ERESTARTSYS; +#else + return -EAGAIN; +#endif + } + /* + * If this is a callout device, then just make sure the normal + * device isn't being used. + */ + if (tty->driver.subtype == ISDN_SERIAL_TYPE_CALLOUT) { + if (info->flags & ISDN_ASYNC_NORMAL_ACTIVE) + return -EBUSY; + if ((info->flags & ISDN_ASYNC_CALLOUT_ACTIVE) && + (info->flags & ISDN_ASYNC_SESSION_LOCKOUT) && + (info->session != current->session)) + return -EBUSY; + if ((info->flags & ISDN_ASYNC_CALLOUT_ACTIVE) && + (info->flags & ISDN_ASYNC_PGRP_LOCKOUT) && + (info->pgrp != current->pgrp)) + return -EBUSY; + info->flags |= ISDN_ASYNC_CALLOUT_ACTIVE; + return 0; + } + /* + * If non-blocking mode is set, then make the check up front + * and then exit. + */ + if ((filp->f_flags & O_NONBLOCK) || + (tty->flags & (1 << TTY_IO_ERROR))) { + if (info->flags & ISDN_ASYNC_CALLOUT_ACTIVE) + return -EBUSY; + info->flags |= ISDN_ASYNC_NORMAL_ACTIVE; + return 0; + } + if (info->flags & ISDN_ASYNC_CALLOUT_ACTIVE) { + if (info->normal_termios.c_cflag & CLOCAL) + do_clocal = 1; + } else { + if (tty->termios->c_cflag & CLOCAL) + do_clocal = 1; + } + /* + * Block waiting for the carrier detect and the line to become + * free (i.e., not in use by the callout). While we are in + * this loop, info->count is dropped by one, so that + * isdn_tty_close() knows when to free things. We restore it upon + * exit, either normal or abnormal. + */ + retval = 0; + add_wait_queue(&info->open_wait, &wait); +#ifdef ISDN_DEBUG_MODEM_OPEN + printk(KERN_DEBUG "isdn_tty_block_til_ready before block: ttyi%d, count = %d\n", + info->line, info->count); +#endif + save_flags(flags); + cli(); + if (!(tty_hung_up_p(filp))) + info->count--; + restore_flags(flags); + info->blocked_open++; + while (1) { + current->state = TASK_INTERRUPTIBLE; + if (tty_hung_up_p(filp) || + !(info->flags & ISDN_ASYNC_INITIALIZED)) { +#ifdef MODEM_DO_RESTART + if (info->flags & ISDN_ASYNC_HUP_NOTIFY) + retval = -EAGAIN; + else + retval = -ERESTARTSYS; +#else + retval = -EAGAIN; +#endif + break; + } + if (!(info->flags & ISDN_ASYNC_CALLOUT_ACTIVE) && + !(info->flags & ISDN_ASYNC_CLOSING) && + (do_clocal || (info->msr & UART_MSR_DCD))) { + break; + } + if (current->signal & ~current->blocked) { + retval = -ERESTARTSYS; + break; + } +#ifdef ISDN_DEBUG_MODEM_OPEN + printk(KERN_DEBUG "isdn_tty_block_til_ready blocking: ttyi%d, count = %d\n", + info->line, info->count); +#endif + schedule(); + } + current->state = TASK_RUNNING; + remove_wait_queue(&info->open_wait, &wait); + if (!tty_hung_up_p(filp)) + info->count++; + info->blocked_open--; +#ifdef ISDN_DEBUG_MODEM_OPEN + printk(KERN_DEBUG "isdn_tty_block_til_ready after blocking: ttyi%d, count = %d\n", + info->line, info->count); +#endif + if (retval) + return retval; + info->flags |= ISDN_ASYNC_NORMAL_ACTIVE; + return 0; +} + +/* + * This routine is called whenever a serial port is opened. It + * enables interrupts for a serial port, linking in its async structure into + * the IRQ chain. It also performs the serial-specific + * initialization for the tty structure. + */ +static int isdn_tty_open(struct tty_struct *tty, struct file *filp) +{ + modem_info *info; + int retval, line; + + line = MINOR(tty->device) - tty->driver.minor_start; + if (line < 0 || line > ISDN_MAX_CHANNELS) + return -ENODEV; + info = &dev->mdm.info[line]; + if (isdn_tty_paranoia_check(info, tty->device, "isdn_tty_open")) + return -ENODEV; +#ifdef ISDN_DEBUG_MODEM_OPEN + printk(KERN_DEBUG "isdn_tty_open %s%d, count = %d\n", tty->driver.name, + info->line, info->count); +#endif + info->count++; + tty->driver_data = info; + info->tty = tty; + /* + * Start up serial port + */ + retval = isdn_tty_startup(info); + if (retval) { +#ifdef ISDN_DEBUG_MODEM_OPEN + printk(KERN_DEBUG "isdn_tty_open return after startup\n"); +#endif + return retval; + } + retval = isdn_tty_block_til_ready(tty, filp, info); + if (retval) { +#ifdef ISDN_DEBUG_MODEM_OPEN + printk(KERN_DEBUG "isdn_tty_open return after isdn_tty_block_til_ready \n"); +#endif + return retval; + } + if ((info->count == 1) && (info->flags & ISDN_ASYNC_SPLIT_TERMIOS)) { + if (tty->driver.subtype == ISDN_SERIAL_TYPE_NORMAL) + *tty->termios = info->normal_termios; + else + *tty->termios = info->callout_termios; + isdn_tty_change_speed(info); + } + info->session = current->session; + info->pgrp = current->pgrp; +#ifdef ISDN_DEBUG_MODEM_OPEN + printk(KERN_DEBUG "isdn_tty_open ttyi%d successful...\n", info->line); +#endif + dev->modempoll++; +#ifdef ISDN_DEBUG_MODEM_OPEN + printk(KERN_DEBUG "isdn_tty_open normal exit\n"); +#endif + return 0; +} + +static void isdn_tty_close(struct tty_struct *tty, struct file *filp) +{ + modem_info *info = (modem_info *) tty->driver_data; + ulong flags; + ulong timeout; + + if (!info || isdn_tty_paranoia_check(info, tty->device, "isdn_tty_close")) + return; + save_flags(flags); + cli(); + if (tty_hung_up_p(filp)) { + restore_flags(flags); +#ifdef ISDN_DEBUG_MODEM_OPEN + printk(KERN_DEBUG "isdn_tty_close return after tty_hung_up_p\n"); +#endif + return; + } + if ((tty->count == 1) && (info->count != 1)) { + /* + * Uh, oh. tty->count is 1, which means that the tty + * structure will be freed. Info->count should always + * be one in these conditions. If it's greater than + * one, we've got real problems, since it means the + * serial port won't be shutdown. + */ + printk(KERN_ERR "isdn_tty_close: bad port count; tty->count is 1, " + "info->count is %d\n", info->count); + info->count = 1; + } + if (--info->count < 0) { + printk(KERN_ERR "isdn_tty_close: bad port count for ttyi%d: %d\n", + info->line, info->count); + info->count = 0; + } + if (info->count) { + restore_flags(flags); +#ifdef ISDN_DEBUG_MODEM_OPEN + printk(KERN_DEBUG "isdn_tty_close after info->count != 0\n"); +#endif + return; + } + info->flags |= ISDN_ASYNC_CLOSING; + /* + * Save the termios structure, since this port may have + * separate termios for callout and dialin. + */ + if (info->flags & ISDN_ASYNC_NORMAL_ACTIVE) + info->normal_termios = *tty->termios; + if (info->flags & ISDN_ASYNC_CALLOUT_ACTIVE) + info->callout_termios = *tty->termios; + + tty->closing = 1; + /* + * At this point we stop accepting input. To do this, we + * disable the receive line status interrupts, and tell the + * interrupt driver to stop checking the data ready bit in the + * line status register. + */ + if (info->flags & ISDN_ASYNC_INITIALIZED) { + tty_wait_until_sent(tty, 3000); /* 30 seconds timeout */ + /* + * Before we drop DTR, make sure the UART transmitter + * has completely drained; this is especially + * important if there is a transmit FIFO! + */ + timeout = jiffies + HZ; + while (!(info->lsr & UART_LSR_TEMT)) { + current->state = TASK_INTERRUPTIBLE; + current->timeout = jiffies + 20; + schedule(); + if (jiffies > timeout) + break; + } + } + dev->modempoll--; + isdn_tty_shutdown(info); + if (tty->driver.flush_buffer) + tty->driver.flush_buffer(tty); + if (tty->ldisc.flush_buffer) + tty->ldisc.flush_buffer(tty); + info->tty = 0; + info->ncarrier = 0; + tty->closing = 0; + if (info->blocked_open) { + current->state = TASK_INTERRUPTIBLE; + current->timeout = jiffies + 50; + schedule(); + wake_up_interruptible(&info->open_wait); + } + info->flags &= ~(ISDN_ASYNC_NORMAL_ACTIVE | ISDN_ASYNC_CALLOUT_ACTIVE | + ISDN_ASYNC_CLOSING); + wake_up_interruptible(&info->close_wait); + restore_flags(flags); +#ifdef ISDN_DEBUG_MODEM_OPEN + printk(KERN_DEBUG "isdn_tty_close normal exit\n"); +#endif +} + +/* + * isdn_tty_hangup() --- called by tty_hangup() when a hangup is signaled. + */ +static void isdn_tty_hangup(struct tty_struct *tty) +{ + modem_info *info = (modem_info *) tty->driver_data; + + if (isdn_tty_paranoia_check(info, tty->device, "isdn_tty_hangup")) + return; + isdn_tty_shutdown(info); + info->count = 0; + info->flags &= ~(ISDN_ASYNC_NORMAL_ACTIVE | ISDN_ASYNC_CALLOUT_ACTIVE); + info->tty = 0; + wake_up_interruptible(&info->open_wait); +} + +/* This routine initializes all emulator-data. + */ +static void isdn_tty_reset_profile(atemu * m) +{ + m->profile[0] = 0; + m->profile[1] = 0; + m->profile[2] = 43; + m->profile[3] = 13; + m->profile[4] = 10; + m->profile[5] = 8; + m->profile[6] = 3; + m->profile[7] = 60; + m->profile[8] = 2; + m->profile[9] = 6; + m->profile[10] = 7; + m->profile[11] = 70; + m->profile[12] = 0x45; + m->profile[13] = 4; + m->profile[14] = ISDN_PROTO_L2_X75I; + m->profile[15] = ISDN_PROTO_L3_TRANS; + m->profile[16] = ISDN_SERIAL_XMIT_SIZE / 16; + m->profile[17] = ISDN_MODEM_WINSIZE; + m->profile[18] = 4; + m->profile[19] = 0; + m->profile[20] = 0; + m->pmsn[0] = '\0'; +} + +static void isdn_tty_modem_reset_vpar(atemu *m) +{ + m->vpar[0] = 2; /* Voice-device (2 = phone line) */ + m->vpar[1] = 0; /* Silence detection level (0 = none ) */ + m->vpar[2] = 70; /* Silence interval (7 sec. ) */ + m->vpar[3] = 2; /* Compression type (1 = ADPCM-2 ) */ +} + +static void isdn_tty_modem_reset_regs(atemu * m, int force) +{ + if ((m->mdmreg[12] & 32) || force) { + memcpy(m->mdmreg, m->profile, ISDN_MODEM_ANZREG); + memcpy(m->msn, m->pmsn, ISDN_MSNLEN); + } + isdn_tty_modem_reset_vpar(m); + m->mdmcmdl = 0; +} + +static void modem_write_profile(atemu * m) +{ + memcpy(m->profile, m->mdmreg, ISDN_MODEM_ANZREG); + memcpy(m->pmsn, m->msn, ISDN_MSNLEN); + if (dev->profd) + send_sig(SIGIO, dev->profd, 1); +} + +int isdn_tty_modem_init(void) +{ + modem *m; + int i; + modem_info *info; + + m = &dev->mdm; + memset(&m->tty_modem, 0, sizeof(struct tty_driver)); + m->tty_modem.magic = TTY_DRIVER_MAGIC; + m->tty_modem.name = isdn_ttyname_ttyI; + m->tty_modem.major = ISDN_TTY_MAJOR; + m->tty_modem.minor_start = 0; + m->tty_modem.num = ISDN_MAX_CHANNELS; + m->tty_modem.type = TTY_DRIVER_TYPE_SERIAL; + m->tty_modem.subtype = ISDN_SERIAL_TYPE_NORMAL; + m->tty_modem.init_termios = tty_std_termios; + m->tty_modem.init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; + m->tty_modem.flags = TTY_DRIVER_REAL_RAW; + m->tty_modem.refcount = &m->refcount; + m->tty_modem.table = m->modem_table; + m->tty_modem.termios = m->modem_termios; + m->tty_modem.termios_locked = m->modem_termios_locked; + m->tty_modem.open = isdn_tty_open; + m->tty_modem.close = isdn_tty_close; + m->tty_modem.write = isdn_tty_write; + m->tty_modem.put_char = NULL; + m->tty_modem.flush_chars = isdn_tty_flush_chars; + m->tty_modem.write_room = isdn_tty_write_room; + m->tty_modem.chars_in_buffer = isdn_tty_chars_in_buffer; + m->tty_modem.flush_buffer = isdn_tty_flush_buffer; + m->tty_modem.ioctl = isdn_tty_ioctl; + m->tty_modem.throttle = isdn_tty_throttle; + m->tty_modem.unthrottle = isdn_tty_unthrottle; + m->tty_modem.set_termios = isdn_tty_set_termios; + m->tty_modem.stop = NULL; + m->tty_modem.start = NULL; + m->tty_modem.hangup = isdn_tty_hangup; + /* + * The callout device is just like normal device except for + * major number and the subtype code. + */ + m->cua_modem = m->tty_modem; + m->cua_modem.name = isdn_ttyname_cui; + m->cua_modem.major = ISDN_TTYAUX_MAJOR; + m->tty_modem.minor_start = 0; + m->cua_modem.subtype = ISDN_SERIAL_TYPE_CALLOUT; + + if (tty_register_driver(&m->tty_modem)) { + printk(KERN_WARNING "isdn_tty: Couldn't register modem-device\n"); + return -1; + } + if (tty_register_driver(&m->cua_modem)) { + printk(KERN_WARNING "isdn_tty: Couldn't register modem-callout-device\n"); + return -2; + } + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + info = &m->info[i]; + isdn_tty_reset_profile(&info->emu); + isdn_tty_modem_reset_regs(&info->emu, 1); + info->magic = ISDN_ASYNC_MAGIC; + info->line = i; + info->tty = 0; + info->x_char = 0; + info->count = 0; + info->blocked_open = 0; + info->callout_termios = m->cua_modem.init_termios; + info->normal_termios = m->tty_modem.init_termios; + info->open_wait = 0; + info->close_wait = 0; + info->isdn_driver = -1; + info->isdn_channel = -1; + info->drv_index = -1; + info->xmit_size = ISDN_SERIAL_XMIT_SIZE; + skb_queue_head_init(&info->xmit_queue); + skb_queue_head_init(&info->dtmf_queue); + if (!(info->xmit_buf = kmalloc(ISDN_SERIAL_XMIT_SIZE + 5, GFP_KERNEL))) { + printk(KERN_ERR "Could not allocate modem xmit-buffer\n"); + return -3; + } + /* Make room for T.70 header */ + info->xmit_buf += 4; + } + return 0; +} + +/* + * An incoming call-request has arrived. + * Search the tty-devices for an appropriate device and bind + * it to the ISDN-Channel. + * Return Index to dev->mdm or -1 if none found. + */ +int isdn_tty_find_icall(int di, int ch, char *num) +{ + char *eaz; + int i; + int idx; + int si1; + int si2; + char *s; + char nr[31]; + ulong flags; + + save_flags(flags); + cli(); + if (num[0] == ',') { + nr[0] = '0'; + strncpy(&nr[1], num, 29); + printk(KERN_INFO "isdn_tty: Incoming call without OAD, assuming '0'\n"); + } else + strncpy(nr, num, 30); + s = strtok(nr, ","); + s = strtok(NULL, ","); + if (!s) { + printk(KERN_WARNING "isdn_tty: Incoming callinfo garbled, ignored: %s\n", + num); + restore_flags(flags); + return -1; + } + si1 = (int)simple_strtoul(s,NULL,10); + s = strtok(NULL, ","); + if (!s) { + printk(KERN_WARNING "isdn_tty: Incoming callinfo garbled, ignored: %s\n", + num); + restore_flags(flags); + return -1; + } + si2 = (int)simple_strtoul(s,NULL,10); + eaz = strtok(NULL, ","); + if (!eaz) { + printk(KERN_WARNING "isdn_tty: Incoming call without CPN, assuming '0'\n"); + eaz = "0"; + } +#ifdef ISDN_DEBUG_MODEM_ICALL + printk(KERN_DEBUG "m_fi: eaz=%s si1=%d si2=%d\n", eaz, si1, si2); +#endif + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + modem_info *info = &dev->mdm.info[i]; +#ifdef ISDN_DEBUG_MODEM_ICALL + printk(KERN_DEBUG "m_fi: i=%d msn=%s mmsn=%s mreg18=%d mreg19=%d\n", i, + info->emu.msn, isdn_map_eaz2msn(info->emu.msn, di), + info->emu.mdmreg[18], info->emu.mdmreg[19]); +#endif + if ((!strcmp(isdn_map_eaz2msn(info->emu.msn, di), + eaz)) && /* EAZ is matching */ + (info->emu.mdmreg[18] & si2bit[si1]) && /* SI1 is matching */ + ((info->emu.mdmreg[19] == si2) || !si2)) { /* SI2 is matching or 0 */ + idx = isdn_dc2minor(di, ch); +#ifdef ISDN_DEBUG_MODEM_ICALL + printk(KERN_DEBUG "m_fi: match1\n"); + printk(KERN_DEBUG "m_fi: idx=%d flags=%08lx drv=%d ch=%d usg=%d\n", idx, + info->flags, info->isdn_driver, info->isdn_channel, + dev->usage[idx]); +#endif + if ((info->flags & ISDN_ASYNC_NORMAL_ACTIVE) && + (info->isdn_driver == -1) && + (info->isdn_channel == -1) && + (USG_NONE(dev->usage[idx]))) { + info->isdn_driver = di; + info->isdn_channel = ch; + info->drv_index = idx; + dev->m_idx[idx] = info->line; + dev->usage[idx] &= ISDN_USAGE_EXCLUSIVE; + dev->usage[idx] |= (si1==1)?ISDN_USAGE_VOICE:ISDN_USAGE_MODEM; + strcpy(dev->num[idx], nr); + info->emu.mdmreg[20] = si2bit[si1]; + isdn_info_update(); + restore_flags(flags); + printk(KERN_INFO "isdn_tty: call from %s, -> RING on ttyI%d\n", nr, + info->line); + return info->line; + } + } + } + printk(KERN_INFO "isdn_tty: call from %s -> %s %s\n", nr, eaz, + dev->drv[di]->reject_bus ? "rejected" : "ignored"); + restore_flags(flags); + return -1; +} + +/********************************************************************* + Modem-Emulator-Routines + *********************************************************************/ + +#define cmdchar(c) ((c>' ')&&(c<=0x7f)) + +/* + * Put a message from the AT-emulator into receive-buffer of tty, + * convert CR, LF, and BS to values in modem-registers 3, 4 and 5. + */ +static void isdn_tty_at_cout(char *msg, modem_info * info) +{ + struct tty_struct *tty; + atemu *m = &info->emu; + char *p; + char c; + ulong flags; + + if (!msg) { + printk(KERN_WARNING "isdn_tty: Null-Message in isdn_tty_at_cout\n"); + return; + } + save_flags(flags); + cli(); + tty = info->tty; + for (p = msg; *p; p++) { + switch (*p) { + case '\r': + c = m->mdmreg[3]; + break; + case '\n': + c = m->mdmreg[4]; + break; + case '\b': + c = m->mdmreg[5]; + break; + default: + c = *p; + } + if ((info->flags & ISDN_ASYNC_CLOSING) || (!tty)) { + restore_flags(flags); + return; + } + if (tty->flip.count >= TTY_FLIPBUF_SIZE) + break; + tty_insert_flip_char(tty, c, 0); + } + restore_flags(flags); + queue_task(&tty->flip.tqueue, &tq_timer); +} + +/* + * Perform ATH Hangup + */ +static void isdn_tty_on_hook(modem_info * info) +{ + if (info->isdn_channel >= 0) { +#ifdef ISDN_DEBUG_MODEM_HUP + printk(KERN_DEBUG "Mhup in isdn_tty_on_hook\n"); +#endif + isdn_tty_modem_result(3, info); + isdn_tty_modem_hup(info); + } +} + +static void isdn_tty_off_hook(void) +{ + printk(KERN_DEBUG "isdn_tty_off_hook\n"); +} + +#define PLUSWAIT1 (HZ/2) /* 0.5 sec. */ +#define PLUSWAIT2 (HZ*3/2) /* 1.5 sec */ + +/* + * Check Buffer for Modem-escape-sequence, activate timer-callback to + * isdn_tty_modem_escape() if sequence found. + * + * Parameters: + * p pointer to databuffer + * plus escape-character + * count length of buffer + * pluscount count of valid escape-characters so far + * lastplus timestamp of last character + */ +static void isdn_tty_check_esc(const u_char * p, u_char plus, int count, int *pluscount, + int *lastplus, int from_user) +{ + char cbuf[3]; + + if (plus > 127) + return; + if (count > 3) { + p += count - 3; + count = 3; + *pluscount = 0; + } + if (from_user) { + copy_from_user(cbuf, p, count); + p = cbuf; + } + while (count > 0) { + if (*(p++) == plus) { + if ((*pluscount)++) { + /* Time since last '+' > 0.5 sec. ? */ + if ((jiffies - *lastplus) > PLUSWAIT1) + *pluscount = 1; + } else { + /* Time since last non-'+' < 1.5 sec. ? */ + if ((jiffies - *lastplus) < PLUSWAIT2) + *pluscount = 0; + } + if ((*pluscount == 3) && (count = 1)) + isdn_timer_ctrl(ISDN_TIMER_MODEMPLUS, 1); + if (*pluscount > 3) + *pluscount = 1; + } else + *pluscount = 0; + *lastplus = jiffies; + count--; + } +} + +/* + * Return result of AT-emulator to tty-receive-buffer, depending on + * modem-register 12, bit 0 and 1. + * For CONNECT-messages also switch to online-mode. + * For RING-message handle auto-ATA if register 0 != 0 + */ +void isdn_tty_modem_result(int code, modem_info * info) +{ + atemu *m = &info->emu; + static char *msg[] = + {"OK", "CONNECT", "RING", "NO CARRIER", "ERROR", + "CONNECT 64000", "NO DIALTONE", "BUSY", "NO ANSWER", + "RINGING", "NO MSN/EAZ", "VCON"}; + ulong flags; + char s[4]; + + switch (code) { + case 2: + m->mdmreg[1]++; /* RING */ + if (m->mdmreg[1] == m->mdmreg[0]) + /* Automatically accept incoming call */ + isdn_tty_cmd_ATA(info); + break; + case 3: + /* NO CARRIER */ + save_flags(flags); + cli(); +#ifdef ISDN_DEBUG_MODEM_HUP + printk(KERN_DEBUG "modem_result: NO CARRIER %d %d\n", + (info->flags & ISDN_ASYNC_CLOSING), + (!info->tty)); +#endif + if ((info->flags & ISDN_ASYNC_CLOSING) || (!info->tty)) { + restore_flags(flags); + return; + } + restore_flags(flags); + if (info->vonline & 1) { + /* voice-recording, add DLE-ETX */ + isdn_tty_at_cout("\020\003", info); + } + if (info->vonline & 2) { + /* voice-playing, add DLE-DC4 */ + isdn_tty_at_cout("\020\024", info); + } + break; + case 1: + case 5: + if (!info->online) + info->online = 2; + break; + case 11: + if (!info->online) + info->online = 1; + break; + } + if (m->mdmreg[12] & 1) { + /* Show results */ + isdn_tty_at_cout("\r\n", info); + if (m->mdmreg[12] & 2) { + /* Show numeric results */ + sprintf(s, "%d", code); + isdn_tty_at_cout(s, info); + } else { + if (code == 2) { + isdn_tty_at_cout("CALLER NUMBER: ", info); + isdn_tty_at_cout(dev->num[info->drv_index], info); + isdn_tty_at_cout("\r\n", info); + } + isdn_tty_at_cout(msg[code], info); + if (code == 5) { + /* Append Protocol to CONNECT message */ + isdn_tty_at_cout((m->mdmreg[14] != 3) ? "/X.75" : "/HDLC", info); + if (m->mdmreg[13] & 2) + isdn_tty_at_cout("/T.70", info); + } + } + isdn_tty_at_cout("\r\n", info); + } + if (code == 3) { + save_flags(flags); + cli(); + if ((info->flags & ISDN_ASYNC_CLOSING) || (!info->tty)) { + restore_flags(flags); + return; + } + if (info->tty->ldisc.flush_buffer) + info->tty->ldisc.flush_buffer(info->tty); + if ((info->flags & ISDN_ASYNC_CHECK_CD) && + (!((info->flags & ISDN_ASYNC_CALLOUT_ACTIVE) && + (info->flags & ISDN_ASYNC_CALLOUT_NOHUP)))) { + tty_hangup(info->tty); + } + restore_flags(flags); + } +} + +/* + * Display a modem-register-value. + */ +static void isdn_tty_show_profile(int ridx, modem_info * info) +{ + char v[6]; + + sprintf(v, "\r\n%d", info->emu.mdmreg[ridx]); + isdn_tty_at_cout(v, info); +} + +/* + * Get MSN-string from char-pointer, set pointer to end of number + */ +static void isdn_tty_get_msnstr(char *n, char **p) +{ + while ((*p[0] >= '0' && *p[0] <= '9') || (*p[0] == ',')) + *n++ = *p[0]++; + *n = '\0'; +} + +/* + * Get phone-number from modem-commandbuffer + */ +static void isdn_tty_getdial(char *p, char *q) +{ + int first = 1; + + while (strchr("0123456789,#.*WPTS-", *p) && *p) { + if ((*p >= '0' && *p <= '9') || ((*p == 'S') && first)) + *q++ = *p; + p++; + first = 0; + } + *q = 0; +} + +#define PARSE_ERROR { isdn_tty_modem_result(4, info); return; } +#define PARSE_ERROR1 { isdn_tty_modem_result(4, info); return 1; } + +/* + * Parse AT&.. commands. + */ +static int isdn_tty_cmd_ATand(char **p, modem_info * info) +{ + atemu *m = &info->emu; + int i; + char rb[100]; + + switch (*p[0]) { + case 'B': + /* &B - Set Buffersize */ + p[0]++; + i = isdn_getnum(p); + if ((i < 0) || (i > ISDN_SERIAL_XMIT_SIZE)) + PARSE_ERROR1; +#ifdef CONFIG_ISDN_AUDIO + if ((m->mdmreg[18] & 1) && (i > VBUF)) + PARSE_ERROR1; +#endif + m->mdmreg[16] = i / 16; + info->xmit_size = m->mdmreg[16] * 16; + break; + case 'D': + /* &D - Set DCD-Low-behavior */ + p[0]++; + switch (isdn_getnum(p)) { + case 0: + m->mdmreg[13] &= ~4; + m->mdmreg[12] &= ~32; + break; + case 2: + m->mdmreg[13] |= 4; + m->mdmreg[12] &= ~32; + break; + case 3: + m->mdmreg[13] |= 4; + m->mdmreg[12] |= 32; + break; + default: + PARSE_ERROR1 + } + break; + case 'E': + /* &E -Set EAZ/MSN */ + p[0]++; + isdn_tty_get_msnstr(m->msn, p); + break; + case 'F': + /* &F -Set Factory-Defaults */ + p[0]++; + isdn_tty_reset_profile(m); + isdn_tty_modem_reset_regs(m, 1); + break; + case 'S': + /* &S - Set Windowsize */ + p[0]++; + i = isdn_getnum(p); + if ((i > 0) && (i < 9)) + m->mdmreg[17] = i; + else + PARSE_ERROR1; + break; + case 'V': + /* &V - Show registers */ + p[0]++; + for (i = 0; i < ISDN_MODEM_ANZREG; i++) { + sprintf(rb, "S%d=%d%s", i, + m->mdmreg[i], (i == 6) ? "\r\n" : " "); + isdn_tty_at_cout(rb, info); + } + sprintf(rb, "\r\nEAZ/MSN: %s\r\n", + strlen(m->msn) ? m->msn : "None"); + isdn_tty_at_cout(rb, info); + break; + case 'W': + /* &W - Write Profile */ + p[0]++; + switch (*p[0]) { + case '0': + p[0]++; + modem_write_profile(m); + break; + default: + PARSE_ERROR1; + } + break; + case 'X': + /* &X - Switch to BTX-Mode */ + p[0]++; + switch (isdn_getnum(p)) { + case 0: + m->mdmreg[13] &= ~2; + info->xmit_size = m->mdmreg[16] * 16; + break; + case 1: + m->mdmreg[13] |= 2; + m->mdmreg[14] = 0; + info->xmit_size = 112; + m->mdmreg[18] = 4; + m->mdmreg[19] = 0; + break; + default: + PARSE_ERROR1; + } + break; + default: + PARSE_ERROR1; + } + return 0; +} + +/* + * Perform ATS command + */ +static int isdn_tty_cmd_ATS(char **p, modem_info * info) +{ + atemu *m = &info->emu; + int mreg; + int mval; + + mreg = isdn_getnum(p); + if (mreg < 0 || mreg > ISDN_MODEM_ANZREG) + PARSE_ERROR1; + switch (*p[0]) { + case '=': + p[0]++; + mval = isdn_getnum(p); + if (mval < 0 || mval > 255) + PARSE_ERROR1; + switch (mreg) { + /* Some plausibility checks */ + case 14: + if (mval > ISDN_PROTO_L2_TRANS) + PARSE_ERROR1; + break; + case 16: + if ((mval * 16) > ISDN_SERIAL_XMIT_SIZE) + PARSE_ERROR1; +#ifdef CONFIG_ISDN_AUDIO + if ((m->mdmreg[18] & 1) && (mval > VBUFX)) + PARSE_ERROR1; +#endif + info->xmit_size = mval * 16; + break; + case 20: + PARSE_ERROR1; + } + m->mdmreg[mreg] = mval; + break; + case '?': + p[0]++; + isdn_tty_show_profile(mreg, info); + break; + default: + PARSE_ERROR1; + break; + } + return 0; +} + +/* + * Perform ATA command + */ +static void isdn_tty_cmd_ATA(modem_info * info) +{ + atemu *m = &info->emu; + isdn_ctrl cmd; + int l2; + + if (info->msr & UART_MSR_RI) { + /* Accept incoming call */ + m->mdmreg[1] = 0; + info->msr &= ~UART_MSR_RI; + l2 = m->mdmreg[14]; +#ifdef CONFIG_ISDN_AUDIO + /* If more than one bit set in reg18, autoselect Layer2 */ + if ((m->mdmreg[18] & m->mdmreg[20]) != m->mdmreg[18]) + if (m->mdmreg[20] == 1) l2 = 4; +#endif + cmd.driver = info->isdn_driver; + cmd.command = ISDN_CMD_SETL2; + cmd.arg = info->isdn_channel + (l2 << 8); + dev->drv[info->isdn_driver]->interface->command(&cmd); + cmd.driver = info->isdn_driver; + cmd.command = ISDN_CMD_SETL3; + cmd.arg = info->isdn_channel + (m->mdmreg[15] << 8); + dev->drv[info->isdn_driver]->interface->command(&cmd); + cmd.driver = info->isdn_driver; + cmd.arg = info->isdn_channel; + cmd.command = ISDN_CMD_ACCEPTD; + dev->drv[info->isdn_driver]->interface->command(&cmd); + } else + isdn_tty_modem_result(8, info); +} + +#ifdef CONFIG_ISDN_AUDIO +/* + * Parse AT+F.. commands + */ +static int isdn_tty_cmd_PLUSF(char **p, modem_info * info) +{ + atemu *m = &info->emu; + int par; + char rs[20]; + + if (!strncmp(p[0],"CLASS",5)) { + p[0] += 5; + switch (*p[0]) { + case '?': + p[0]++; + sprintf(rs,"\r\n%d", + (m->mdmreg[18]&1)?8:0); + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + switch (*p[0]) { + case '0': + p[0]++; + m->mdmreg[18] = 4; + info->xmit_size = + m->mdmreg[16] * 16; + break; + case '8': + p[0]++; + m->mdmreg[18] = 5; + info->xmit_size = VBUF; + break; + case '?': + p[0]++; + isdn_tty_at_cout("\r\n0,8", + info); + break; + default: + PARSE_ERROR1; + } + break; + default: + PARSE_ERROR1; + } + return 0; + } + if (!strncmp(p[0],"AA",2)) { + p[0] += 2; + switch (*p[0]) { + case '?': + p[0]++; + sprintf(rs,"\r\n%d", + m->mdmreg[0]); + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + par = isdn_getnum(p); + if ((par < 0) || (par > 255)) + PARSE_ERROR1; + m->mdmreg[0]=par; + break; + default: + PARSE_ERROR1; + } + return 0; + } + PARSE_ERROR1; +} + +/* + * Parse AT+V.. commands + */ +static int isdn_tty_cmd_PLUSV(char **p, modem_info * info) +{ + atemu *m = &info->emu; + static char *vcmd[] = {"NH","IP","LS","RX","SD","SM","TX",NULL}; + int i; + int par1; + int par2; + char rs[20]; + + i = 0; + while (vcmd[i]) { + if (!strncmp(vcmd[i],p[0],2)) { + p[0] += 2; + break; + } + i++; + } + switch (i) { + case 0: + /* AT+VNH - Auto hangup feature */ + switch (*p[0]) { + case '?': + p[0]++; + isdn_tty_at_cout("\r\n1", info); + break; + case '=': + p[0]++; + switch (*p[0]) { + case '1': + p[0]++; + break; + case '?': + p[0]++; + isdn_tty_at_cout("\r\n1", info); + break; + default: + PARSE_ERROR1; + } + break; + default: + PARSE_ERROR1; + } + break; + case 1: + /* AT+VIP - Reset all voice parameters */ + isdn_tty_modem_reset_vpar(m); + break; + case 2: + /* AT+VLS - Select device, accept incoming call */ + switch (*p[0]) { + case '?': + p[0]++; + sprintf(rs,"\r\n%d",m->vpar[0]); + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + switch (*p[0]) { + case '0': + p[0]++; + m->vpar[0] = 0; + break; + case '2': + p[0]++; + m->vpar[0] = 2; + break; + case '?': + p[0]++; + isdn_tty_at_cout("\r\n0,2", info); + break; + default: + PARSE_ERROR1; + } + break; + default: + PARSE_ERROR1; + } + break; + case 3: + /* AT+VRX - Start recording */ + if (!m->vpar[0]) + PARSE_ERROR1; + if (info->online != 1) { + isdn_tty_modem_result(8, info); + return 1; + } + info->dtmf_state = isdn_audio_dtmf_init(info->dtmf_state); + if (!info->dtmf_state) { + printk(KERN_WARNING "isdn_tty: Couldn't malloc dtmf state\n"); + PARSE_ERROR1; + } + if (m->vpar[3] < 5) { + info->adpcmr = isdn_audio_adpcm_init(info->adpcmr, m->vpar[3]); + if (!info->adpcmr) { + printk(KERN_WARNING "isdn_tty: Couldn't malloc adpcm state\n"); + PARSE_ERROR1; + } + } + info->vonline = 1; + isdn_tty_modem_result(1, info); + return 1; + break; + case 4: + /* AT+VSD - Silence detection */ + switch (*p[0]) { + case '?': + p[0]++; + sprintf(rs,"\r\n<%d>,<%d>", + m->vpar[1], + m->vpar[2]); + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + switch (*p[0]) { + case '0': + case '1': + case '2': + case '3': + par1 = isdn_getnum(p); + if ((par1 < 0) || (par1 > 31)) + PARSE_ERROR1; + if (*p[0] != ',') + PARSE_ERROR1; + p[0]++; + par2 = isdn_getnum(p); + if ((par2 < 0) || (par2 > 255)) + PARSE_ERROR1; + m->vpar[1] = par1; + m->vpar[2] = par2; + break; + case '?': + p[0]++; + isdn_tty_at_cout("\r\n<0-31>,<0-255>", + info); + break; + default: + PARSE_ERROR1; + } + break; + default: + PARSE_ERROR1; + } + break; + case 5: + /* AT+VSM - Select compression */ + switch (*p[0]) { + case '?': + p[0]++; + sprintf(rs,"\r\n<%d>,<%d><8000>", + m->vpar[3], + m->vpar[1]); + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + switch (*p[0]) { + case '2': + case '3': + case '4': + case '5': + case '6': + par1 = isdn_getnum(p); + if ((par1 < 2) || (par1 > 6)) + PARSE_ERROR1; + m->vpar[3] = par1; + break; + case '?': + p[0]++; + isdn_tty_at_cout("\r\n2;ADPCM;2;0;(8000)\r\n", + info); + isdn_tty_at_cout("3;ADPCM;3;0;(8000)\r\n", + info); + isdn_tty_at_cout("4;ADPCM;4;0;(8000)\r\n", + info); + isdn_tty_at_cout("5;ALAW;8;0;(8000)", + info); + isdn_tty_at_cout("6;ULAW;8;0;(8000)", + info); + break; + default: + PARSE_ERROR1; + } + break; + default: + PARSE_ERROR1; + } + break; + case 6: + /* AT+VTX - Start sending */ + if (!m->vpar[0]) + PARSE_ERROR1; + if (info->online != 1) { + isdn_tty_modem_result(8, info); + return 1; + } + info->dtmf_state = isdn_audio_dtmf_init(info->dtmf_state); + if (!info->dtmf_state) { + printk(KERN_WARNING "isdn_tty: Couldn't malloc dtmf state\n"); + PARSE_ERROR1; + } + if (m->vpar[3] < 5) { + info->adpcms = isdn_audio_adpcm_init(info->adpcms, m->vpar[3]); + if (!info->adpcms) { + printk(KERN_WARNING "isdn_tty: Couldn't malloc adpcm state\n"); + PARSE_ERROR1; + } + } + m->lastDLE = 0; + info->vonline = 2; + isdn_tty_modem_result(1, info); + return 1; + break; + default: + PARSE_ERROR1; + } + return 0; +} +#endif /* CONFIG_ISDN_AUDIO */ + +/* + * Parse and perform an AT-command-line. + */ +static void isdn_tty_parse_at(modem_info * info) +{ + atemu *m = &info->emu; + char *p; + char ds[40]; + +#ifdef ISDN_DEBUG_AT + printk(KERN_DEBUG "AT: '%s'\n", m->mdmcmd); +#endif + for (p = &m->mdmcmd[2]; *p;) { + switch (*p) { + case 'A': + /* A - Accept incoming call */ + p++; + isdn_tty_cmd_ATA(info); + return; + break; + case 'D': + /* D - Dial */ + isdn_tty_getdial(++p, ds); + p += strlen(p); + if (!strlen(m->msn)) + isdn_tty_modem_result(10, info); + else if (strlen(ds)) + isdn_tty_dial(ds, info, m); + else + isdn_tty_modem_result(4, info); + return; + case 'E': + /* E - Turn Echo on/off */ + p++; + switch (isdn_getnum(&p)) { + case 0: + m->mdmreg[12] &= ~4; + break; + case 1: + m->mdmreg[12] |= 4; + break; + default: + PARSE_ERROR; + } + break; + case 'H': + /* H - On/Off-hook */ + p++; + switch (*p) { + case '0': + p++; + isdn_tty_on_hook(info); + break; + case '1': + p++; + isdn_tty_off_hook(); + break; + default: + isdn_tty_on_hook(info); + break; + } + break; + case 'I': + /* I - Information */ + p++; + isdn_tty_at_cout("\r\nLinux ISDN", info); + switch (*p) { + case '0': + case '1': + p++; + break; + default: + } + break; + case 'O': + /* O - Go online */ + p++; + if (info->msr & UART_MSR_DCD) + /* if B-Channel is up */ + isdn_tty_modem_result(5, info); + else + isdn_tty_modem_result(3, info); + return; + case 'Q': + /* Q - Turn Emulator messages on/off */ + p++; + switch (isdn_getnum(&p)) { + case 0: + m->mdmreg[12] |= 1; + break; + case 1: + m->mdmreg[12] &= ~1; + break; + default: + PARSE_ERROR; + } + break; + case 'S': + /* S - Set/Get Register */ + p++; + if (isdn_tty_cmd_ATS(&p, info)) + return; + break; + case 'V': + /* V - Numeric or ASCII Emulator-messages */ + p++; + switch (isdn_getnum(&p)) { + case 0: + m->mdmreg[12] |= 2; + break; + case 1: + m->mdmreg[12] &= ~2; + break; + default: + PARSE_ERROR; + } + break; + case 'Z': + /* Z - Load Registers from Profile */ + p++; + isdn_tty_modem_reset_regs(m, 1); + break; +#ifdef CONFIG_ISDN_AUDIO + case '+': + p++; + switch (*p) { + case 'F': + p++; + if (isdn_tty_cmd_PLUSF(&p, info)) + return; + break; + case 'V': + if (!(m->mdmreg[18] & 1)) + PARSE_ERROR; + p++; + if (isdn_tty_cmd_PLUSV(&p, info)) + return; + break; + } + break; +#endif /* CONFIG_ISDN_AUDIO */ + case '&': + p++; + if (isdn_tty_cmd_ATand(&p, info)) + return; + break; + default: + isdn_tty_modem_result(4, info); + return; + } + } + isdn_tty_modem_result(0, info); +} + +/* Need own toupper() because standard-toupper is not available + * within modules. + */ +#define my_toupper(c) (((c>='a')&&(c<='z'))?(c&0xdf):c) + +/* + * Perform line-editing of AT-commands + * + * Parameters: + * p inputbuffer + * count length of buffer + * channel index to line (minor-device) + * user flag: buffer is in userspace + */ +static int isdn_tty_edit_at(const char *p, int count, modem_info * info, int user) +{ + atemu *m = &info->emu; + int total = 0; + u_char c; + char eb[2]; + int cnt; + + for (cnt = count; cnt > 0; p++, cnt--) { + if (user) + get_user(c, p); + else + c = *p; + total++; + if (c == m->mdmreg[3] || c == m->mdmreg[4]) { + /* Separator (CR oder LF) */ + m->mdmcmd[m->mdmcmdl] = 0; + if (m->mdmreg[12] & 4) { + eb[0] = c; + eb[1] = 0; + isdn_tty_at_cout(eb, info); + } + if (m->mdmcmdl >= 2) + isdn_tty_parse_at(info); + m->mdmcmdl = 0; + continue; + } + if (c == m->mdmreg[5] && m->mdmreg[5] < 128) { + /* Backspace-Funktion */ + if ((m->mdmcmdl > 2) || (!m->mdmcmdl)) { + if (m->mdmcmdl) + m->mdmcmdl--; + if (m->mdmreg[12] & 4) + isdn_tty_at_cout("\b", info); + } + continue; + } + if (cmdchar(c)) { + if (m->mdmreg[12] & 4) { + eb[0] = c; + eb[1] = 0; + isdn_tty_at_cout(eb, info); + } + if (m->mdmcmdl < 255) { + c = my_toupper(c); + switch (m->mdmcmdl) { + case 0: + if (c == 'A') + m->mdmcmd[m->mdmcmdl++] = c; + break; + case 1: + if (c == 'T') + m->mdmcmd[m->mdmcmdl++] = c; + break; + default: + m->mdmcmd[m->mdmcmdl++] = c; + } + } + } + } + return total; +} + +/* + * Switch all modem-channels who are online and got a valid + * escape-sequence 1.5 seconds ago, to command-mode. + * This function is called every second via timer-interrupt from within + * timer-dispatcher isdn_timer_function() + */ +void isdn_tty_modem_escape(void) +{ + int ton = 0; + int i; + int midx; + + for (i = 0; i < ISDN_MAX_CHANNELS; i++) + if (USG_MODEM(dev->usage[i])) + if ((midx = dev->m_idx[i]) >= 0) { + modem_info *info = &dev->mdm.info[midx]; + if (info->online) { + ton = 1; + if ((info->emu.pluscount == 3) && + ((jiffies - info->emu.lastplus) > PLUSWAIT2)) { + info->emu.pluscount = 0; + info->online = 0; + isdn_tty_modem_result(0, info); + } + } + } + isdn_timer_ctrl(ISDN_TIMER_MODEMPLUS, ton); +} + +/* + * Put a RING-message to all modem-channels who have the RI-bit set. + * This function is called every second via timer-interrupt from within + * timer-dispatcher isdn_timer_function() + */ +void isdn_tty_modem_ring(void) +{ + int ton = 0; + int i; + + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + modem_info *info = &dev->mdm.info[i]; + if (info->msr & UART_MSR_RI) { + ton = 1; + isdn_tty_modem_result(2, info); + } + } + isdn_timer_ctrl(ISDN_TIMER_MODEMRING, ton); +} + +/* + * For all online tty's, try sending data to + * the lower levels. + */ +void isdn_tty_modem_xmit(void) +{ + int ton = 1; + int i; + + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + modem_info *info = &dev->mdm.info[i]; + if (info->online) { + ton = 1; + isdn_tty_senddown(info); + isdn_tty_tint(info); + } + } + isdn_timer_ctrl(ISDN_TIMER_MODEMXMIT, ton); +} + +/* + * A packet has been output successfully. + * Search the tty-devices for an appropriate device, decrement its + * counter for outstanding packets, and set CTS. + */ +void isdn_tty_bsent(int drv, int chan) +{ + int i; + + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + modem_info *info = &dev->mdm.info[i]; + if ((info->isdn_driver == drv) && + (info->isdn_channel == chan) ) { + info->msr |= UART_MSR_CTS; + if (info->send_outstanding) + if (!(--info->send_outstanding)) + info->lsr |= UART_LSR_TEMT; + isdn_tty_tint(info); + } + } + return; +} diff --git a/drivers/isdn/isdn_tty.h b/drivers/isdn/isdn_tty.h new file mode 100644 index 000000000..f317d23ae --- /dev/null +++ b/drivers/isdn/isdn_tty.h @@ -0,0 +1,51 @@ +/* $Id: isdn_tty.h,v 1.5 1996/05/17 03:52:31 fritz Exp $ + * + * header for Linux ISDN subsystem, tty related functions (linklevel). + * + * Copyright 1994,95,96 by Fritz Elfert (fritz@wuemaus.franken.de) + * Copyright 1995,96 by Thinking Objects Software GmbH Wuerzburg + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Log: isdn_tty.h,v $ + * Revision 1.5 1996/05/17 03:52:31 fritz + * Changed DLE handling for audio receive. + * + * Revision 1.4 1996/05/11 21:52:34 fritz + * Changed queue management to use sk_buffs. + * + * Revision 1.3 1996/05/07 09:16:34 fritz + * Changed isdn_try_read parameter. + * + * Revision 1.2 1996/04/30 21:05:27 fritz + * Test commit + * + * Revision 1.1 1996/01/10 21:39:22 fritz + * Initial revision + * + */ + +extern void isdn_tty_modem_result(int, modem_info *); +extern void isdn_tty_modem_escape(void); +extern void isdn_tty_modem_ring(void); +extern void isdn_tty_modem_xmit(void); +extern void isdn_tty_modem_hup(modem_info *); +extern int isdn_tty_modem_init(void); +extern void isdn_tty_readmodem(void); +extern int isdn_tty_try_read(modem_info *, struct sk_buff *); +extern int isdn_tty_find_icall(int, int, char *); +extern int isdn_tty_countDLE(unsigned char *, int); +extern void isdn_tty_bsent(int, int); +extern void isdn_tty_cleanup_xmit(modem_info *); diff --git a/drivers/isdn/pcbit/Makefile b/drivers/isdn/pcbit/Makefile new file mode 100644 index 000000000..50a67269e --- /dev/null +++ b/drivers/isdn/pcbit/Makefile @@ -0,0 +1,15 @@ +L_OBJS := +M_OBJS := +O_OBJS := module.o edss1.o drv.o layer2.o capi.o callbacks.o + +O_TARGET := +ifeq ($(CONFIG_ISDN_DRV_PCBIT),y) + O_TARGET += pcbit.o +else + ifeq ($(CONFIG_ISDN_DRV_PCBIT),m) + O_TARGET += pcbit.o + M_OBJS += pcbit.o + endif +endif + +include $(TOPDIR)/Rules.make diff --git a/drivers/isdn/pcbit/callbacks.c b/drivers/isdn/pcbit/callbacks.c new file mode 100644 index 000000000..f3051e2e5 --- /dev/null +++ b/drivers/isdn/pcbit/callbacks.c @@ -0,0 +1,362 @@ +/* + * Copyright (C) 1996 Universidade de Lisboa + * + * Written by Pedro Roque Marques (roque@di.fc.ul.pt) + * + * This software may be used and distributed according to the terms of + * the GNU Public License, incorporated herein by reference. + */ + +/* + * callbacks for the FSM + */ + +#define __NO_VERSION__ + +#include <linux/module.h> + +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/kernel.h> + +#include <linux/types.h> +#include <linux/malloc.h> +#include <linux/mm.h> +#include <linux/tqueue.h> +#include <linux/skbuff.h> + +#include <asm/io.h> + +#include <linux/isdnif.h> + +#include "pcbit.h" +#include "layer2.h" +#include "edss1.h" +#include "callbacks.h" +#include "capi.h" + +ushort last_ref_num = 1; + +/* + * send_conn_req + * + */ + +void cb_out_1(struct pcbit_dev * dev, struct pcbit_chan* chan, + struct callb_data *cbdata) +{ + struct sk_buff *skb; + int len; + ushort refnum; + + +#ifdef DEBUG + printk(KERN_DEBUG "Called Party Number: %s\n", + cbdata->data.setup.CalledPN); +#endif + /* + * hdr - kmalloc in capi_conn_req + * - kfree when msg has been sent + */ + + if ((len = capi_conn_req(cbdata->data.setup.CalledPN, &skb, + chan->proto)) < 0) + { + printk("capi_conn_req failed\n"); + return; + } + + + refnum = last_ref_num++ & 0x7fffU; + + chan->callref = 0; + chan->layer2link = 0; + chan->snum = 0; + chan->s_refnum = refnum; + + pcbit_l2_write(dev, MSG_CONN_REQ, refnum, skb, len); +} + +/* + * rcv CONNECT + * will go into ACTIVE state + * send CONN_ACTIVE_RESP + * send Select protocol request + */ + +void cb_out_2(struct pcbit_dev * dev, struct pcbit_chan* chan, + struct callb_data *data) +{ + isdn_ctrl ictl; + struct sk_buff *skb; + int len; + ushort refnum; + + if ((len=capi_conn_active_resp(chan, &skb)) < 0) + { + printk("capi_conn_active_req failed\n"); + return; + } + + refnum = last_ref_num++ & 0x7fffU; + chan->s_refnum = refnum; + + pcbit_l2_write(dev, MSG_CONN_ACTV_RESP, refnum, skb, len); + + + ictl.command = ISDN_STAT_DCONN; + ictl.driver=dev->id; + ictl.arg=chan->id; + dev->dev_if->statcallb(&ictl); + + /* ACTIVE D-channel */ + + /* Select protocol */ + + if ((len=capi_select_proto_req(chan, &skb, 1 /*outgoing*/)) < 0) { + printk("capi_select_proto_req failed\n"); + return; + } + + refnum = last_ref_num++ & 0x7fffU; + chan->s_refnum = refnum; + + pcbit_l2_write(dev, MSG_SELP_REQ, refnum, skb, len); +} + + +/* + * Disconnect received (actually RELEASE COMPLETE) + * This means we were not able to establish connection with remote + * Inform the big boss above + */ +void cb_out_3(struct pcbit_dev * dev, struct pcbit_chan* chan, + struct callb_data *data) +{ + isdn_ctrl ictl; + + ictl.command = ISDN_STAT_DHUP; + ictl.driver=dev->id; + ictl.arg=chan->id; + dev->dev_if->statcallb(&ictl); +} + + +/* + * Incoming call received + * inform user + */ + +void cb_in_1(struct pcbit_dev * dev, struct pcbit_chan* chan, + struct callb_data *cbdata) +{ + isdn_ctrl ictl; + unsigned short refnum; + struct sk_buff *skb; + int len; + + + ictl.command = ISDN_STAT_ICALL; + ictl.driver=dev->id; + ictl.arg=chan->id; + + /* + * ictl.num >= strlen() + strlen() + 5 + */ + + if (cbdata->data.setup.CalledPN) + sprintf(ictl.num, "%s,%d,%d,%s", + cbdata->data.setup.CallingPN, + 7, 0, + cbdata->data.setup.CalledPN); + + else + sprintf(ictl.num, "%s,%d,%d,%s", + cbdata->data.setup.CallingPN, + 7, 0, + "0"); + + +#ifdef DEBUG + printk(KERN_DEBUG "statstr: %s\n", ictl.num); +#endif + + dev->dev_if->statcallb(&ictl); + + + if ((len=capi_conn_resp(chan, &skb)) < 0) { + printk(KERN_DEBUG "capi_conn_resp failed\n"); + return; + } + + refnum = last_ref_num++ & 0x7fffU; + chan->s_refnum = refnum; + + pcbit_l2_write(dev, MSG_CONN_RESP, refnum, skb, len); +} + +/* + * user has replied + * open the channel + * send CONNECT message CONNECT_ACTIVE_REQ in CAPI + */ + +void cb_in_2(struct pcbit_dev * dev, struct pcbit_chan* chan, + struct callb_data *data) +{ + unsigned short refnum; + struct sk_buff *skb; + int len; + + if ((len = capi_conn_active_req(chan, &skb)) < 0) { + printk(KERN_DEBUG "capi_conn_active_req failed\n"); + return; + } + + + refnum = last_ref_num++ & 0x7fffU; + chan->s_refnum = refnum; + + printk(KERN_DEBUG "sending MSG_CONN_ACTV_REQ\n"); + pcbit_l2_write(dev, MSG_CONN_ACTV_REQ, refnum, skb, len); +} + +/* + * CONN_ACK arrived + * start b-proto selection + * + */ + +void cb_in_3(struct pcbit_dev * dev, struct pcbit_chan* chan, + struct callb_data *data) +{ + unsigned short refnum; + struct sk_buff *skb; + int len; + + if ((len = capi_select_proto_req(chan, &skb, 0 /*incoming*/)) < 0) + { + printk("capi_select_proto_req failed\n"); + return; + } + + refnum = last_ref_num++ & 0x7fffU; + chan->s_refnum = refnum; + + pcbit_l2_write(dev, MSG_SELP_REQ, refnum, skb, len); + +} + + +/* + * Received disconnect ind on active state + * send disconnect resp + * send msg to user + */ +void cb_disc_1(struct pcbit_dev * dev, struct pcbit_chan* chan, + struct callb_data *data) +{ + struct sk_buff *skb; + int len; + ushort refnum; + isdn_ctrl ictl; + + if ((len = capi_disc_resp(chan, &skb)) < 0) { + printk("capi_disc_resp failed\n"); + return; + } + + refnum = last_ref_num++ & 0x7fffU; + chan->s_refnum = refnum; + + pcbit_l2_write(dev, MSG_DISC_RESP, refnum, skb, len); + + ictl.command = ISDN_STAT_BHUP; + ictl.driver=dev->id; + ictl.arg=chan->id; + dev->dev_if->statcallb(&ictl); +} + + +/* + * User HANGUP on active/call proceeding state + * send disc.req + */ +void cb_disc_2(struct pcbit_dev * dev, struct pcbit_chan* chan, + struct callb_data *data) +{ + struct sk_buff *skb; + int len; + ushort refnum; + + if ((len = capi_disc_req(chan->callref, &skb, CAUSE_NORMAL)) < 0) + { + printk("capi_disc_req failed\n"); + return; + } + + refnum = last_ref_num++ & 0x7fffU; + chan->s_refnum = refnum; + + pcbit_l2_write(dev, MSG_DISC_REQ, refnum, skb, len); +} + +/* + * Disc confirm received send BHUP + * Problem: when the HL driver sends the disc req itself + * LL receives BHUP + */ +void cb_disc_3(struct pcbit_dev * dev, struct pcbit_chan* chan, + struct callb_data *data) +{ + isdn_ctrl ictl; + + ictl.command = ISDN_STAT_BHUP; + ictl.driver=dev->id; + ictl.arg=chan->id; + dev->dev_if->statcallb(&ictl); +} + +void cb_notdone(struct pcbit_dev * dev, struct pcbit_chan* chan, + struct callb_data *data) +{ +} + +/* + * send activate b-chan protocol + */ +void cb_selp_1(struct pcbit_dev * dev, struct pcbit_chan* chan, + struct callb_data *data) +{ + struct sk_buff *skb; + int len; + ushort refnum; + + if ((len = capi_activate_transp_req(chan, &skb)) < 0) + { + printk("capi_conn_activate_transp_req failed\n"); + return; + } + + refnum = last_ref_num++ & 0x7fffU; + chan->s_refnum = refnum; + + pcbit_l2_write(dev, MSG_ACT_TRANSP_REQ, refnum, skb, len); +} + +/* + * Inform User that the B-channel is available + */ +void cb_open(struct pcbit_dev * dev, struct pcbit_chan* chan, + struct callb_data *data) +{ + isdn_ctrl ictl; + + ictl.command = ISDN_STAT_BCONN; + ictl.driver=dev->id; + ictl.arg=chan->id; + dev->dev_if->statcallb(&ictl); +} + + + diff --git a/drivers/isdn/pcbit/callbacks.h b/drivers/isdn/pcbit/callbacks.h new file mode 100644 index 000000000..62e296dee --- /dev/null +++ b/drivers/isdn/pcbit/callbacks.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 1996 Universidade de Lisboa + * + * Written by Pedro Roque Marques (roque@di.fc.ul.pt) + * + * This software may be used and distributed according to the terms of + * the GNU Public License, incorporated herein by reference. + */ + +/* + * Callbacks prototypes for FSM + * + */ + +#ifndef CALLBACKS_H +#define CALLBACKS_H + + +extern void cb_out_1(struct pcbit_dev * dev, struct pcbit_chan* chan, + struct callb_data *data); + +extern void cb_out_2(struct pcbit_dev * dev, struct pcbit_chan* chan, + struct callb_data *data); + +extern void cb_out_3(struct pcbit_dev * dev, struct pcbit_chan* chan, + struct callb_data *data); + +extern void cb_in_1(struct pcbit_dev * dev, struct pcbit_chan* chan, + struct callb_data *data); +extern void cb_in_2(struct pcbit_dev * dev, struct pcbit_chan* chan, + struct callb_data *data); +extern void cb_in_3(struct pcbit_dev * dev, struct pcbit_chan* chan, + struct callb_data *data); + +extern void cb_disc_1(struct pcbit_dev * dev, struct pcbit_chan* chan, + struct callb_data *data); +extern void cb_disc_2(struct pcbit_dev * dev, struct pcbit_chan* chan, + struct callb_data *data); +extern void cb_disc_3(struct pcbit_dev * dev, struct pcbit_chan* chan, + struct callb_data *data); + +extern void cb_notdone(struct pcbit_dev * dev, struct pcbit_chan* chan, + struct callb_data *data); + +extern void cb_selp_1(struct pcbit_dev * dev, struct pcbit_chan* chan, + struct callb_data *data); +extern void cb_open(struct pcbit_dev * dev, struct pcbit_chan* chan, + struct callb_data *data); + +#endif + + diff --git a/drivers/isdn/pcbit/capi.c b/drivers/isdn/pcbit/capi.c new file mode 100644 index 000000000..1c752835d --- /dev/null +++ b/drivers/isdn/pcbit/capi.c @@ -0,0 +1,679 @@ +/* + * Copyright (C) 1996 Universidade de Lisboa + * + * Written by Pedro Roque Marques (roque@di.fc.ul.pt) + * + * This software may be used and distributed according to the terms of + * the GNU Public License, incorporated herein by reference. + */ + +/* + * CAPI encoder/decoder for + * Portugal Telecom CAPI 2.0 + * + * Not compatible with the AVM Gmbh. CAPI 2.0 + */ + +/* + * Documentation: + * - "Common ISDN API - Perfil Português - Versão 2.1", + * Telecom Portugal, Fev 1992. + * - "Common ISDN API - Especificação de protocolos para + * acesso aos canais B", Inesc, Jan 1994. + */ + +/* + * TODO: better decoding of Information Elements + * for debug purposes mainly + * encode our number in CallerPN and ConnectedPN + */ + +#define __NO_VERSION__ + +#include <linux/module.h> + +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/kernel.h> + +#include <linux/types.h> +#include <linux/malloc.h> +#include <linux/mm.h> + +#include <linux/tqueue.h> +#include <linux/skbuff.h> + +#include <asm/io.h> +#include <asm/string.h> + +#include <linux/isdnif.h> + +#include "pcbit.h" +#include "edss1.h" +#include "capi.h" + + +/* + * Encoding of CAPI messages + * + */ + +int capi_conn_req(const char * calledPN, struct sk_buff **skb, int proto) +{ + ushort len; + + /* + * length + * AppInfoMask - 2 + * BC0 - 3 + * BC1 - 1 + * Chan - 2 + * Keypad - 1 + * CPN - 1 + * CPSA - 1 + * CalledPN - 2 + strlen + * CalledPSA - 1 + * rest... - 4 + * ---------------- + * Total 18 + strlen + */ + + len = 18 + strlen(calledPN); + + if (proto == ISDN_PROTO_L2_TRANS) + len++; + + if ((*skb = dev_alloc_skb(len)) == NULL) { + + printk(KERN_WARNING "capi_conn_req: alloc_skb failed\n"); + return -1; + } + + /* InfoElmMask */ + *((ushort*) skb_put(*skb, 2)) = AppInfoMask; + + if (proto == ISDN_PROTO_L2_TRANS) + { + /* Bearer Capability - Mandatory*/ + *(skb_put(*skb, 1)) = 3; /* BC0.Length */ + *(skb_put(*skb, 1)) = 0x80; /* Speech */ + *(skb_put(*skb, 1)) = 0x10; /* Circuit Mode */ + *(skb_put(*skb, 1)) = 0x23; /* A-law */ + } + else + { + /* Bearer Capability - Mandatory*/ + *(skb_put(*skb, 1)) = 2; /* BC0.Length */ + *(skb_put(*skb, 1)) = 0x88; /* Digital Information */ + *(skb_put(*skb, 1)) = 0x90; /* BC0.Octect4 */ + } + + /* Bearer Capability - Optional*/ + *(skb_put(*skb, 1)) = 0; /* BC1.Length = 0 */ + + *(skb_put(*skb, 1)) = 1; /* ChannelID.Length = 1 */ + *(skb_put(*skb, 1)) = 0x83; /* Basic Interface - Any Channel */ + + *(skb_put(*skb, 1)) = 0; /* Keypad.Length = 0 */ + + + *(skb_put(*skb, 1)) = 0; /* CallingPN.Length = 0 */ + *(skb_put(*skb, 1)) = 0; /* CallingPSA.Length = 0 */ + + /* Called Party Number */ + *(skb_put(*skb, 1)) = strlen(calledPN) + 1; + *(skb_put(*skb, 1)) = 0x81; + memcpy(skb_put(*skb, strlen(calledPN)), calledPN, strlen(calledPN)); + + /* '#' */ + + *(skb_put(*skb, 1)) = 0; /* CalledPSA.Length = 0 */ + + /* LLC.Length = 0; */ + /* HLC0.Length = 0; */ + /* HLC1.Length = 0; */ + /* UTUS.Length = 0; */ + memset(skb_put(*skb, 4), 0, 4); + + return len; +} + +int capi_conn_resp(struct pcbit_chan* chan, struct sk_buff **skb) +{ + + if ((*skb = dev_alloc_skb(5)) == NULL) { + + printk(KERN_WARNING "capi_conn_resp: alloc_skb failed\n"); + return -1; + } + + (*skb)->free = 1; + + + *((ushort*) skb_put(*skb, 2) ) = chan->callref; + *(skb_put(*skb, 1)) = 0x01; /* ACCEPT_CALL */ + *(skb_put(*skb, 1)) = 0; + *(skb_put(*skb, 1)) = 0; + + return 5; +} + +int capi_conn_active_req(struct pcbit_chan* chan, struct sk_buff **skb) +{ + /* + * 8 bytes + */ + + if ((*skb = dev_alloc_skb(8)) == NULL) { + + printk(KERN_WARNING "capi_conn_active_req: alloc_skb failed\n"); + return -1; + } + + (*skb)->free = 1; + + *((ushort*) skb_put(*skb, 2) ) = chan->callref; + +#ifdef DEBUG + printk(KERN_DEBUG "Call Reference: %04x\n", chan->callref); +#endif + + *(skb_put(*skb, 1)) = 0; /* BC.Length = 0; */ + *(skb_put(*skb, 1)) = 0; /* ConnectedPN.Length = 0 */ + *(skb_put(*skb, 1)) = 0; /* PSA.Length */ + *(skb_put(*skb, 1)) = 0; /* LLC.Length = 0; */ + *(skb_put(*skb, 1)) = 0; /* HLC.Length = 0; */ + *(skb_put(*skb, 1)) = 0; /* UTUS.Length = 0; */ + + return 8; +} + +int capi_conn_active_resp(struct pcbit_chan* chan, struct sk_buff **skb) +{ + /* + * 2 bytes + */ + + if ((*skb = dev_alloc_skb(2)) == NULL) { + + printk(KERN_WARNING "capi_conn_active_resp: alloc_skb failed\n"); + return -1; + } + + (*skb)->free = 1; + + *((ushort*) skb_put(*skb, 2) ) = chan->callref; + + return 2; +} + + +int capi_select_proto_req(struct pcbit_chan *chan, struct sk_buff **skb, + int outgoing) +{ + + /* + * 18 bytes + */ + + if ((*skb = dev_alloc_skb(18)) == NULL) { + + printk(KERN_WARNING "capi_select_proto_req: alloc_skb failed\n"); + return -1; + } + + (*skb)->free = 1; + + *((ushort*) skb_put(*skb, 2) ) = chan->callref; + + /* Layer2 protocol */ + + switch (chan->proto) { + case ISDN_PROTO_L2_X75I: + *(skb_put(*skb, 1)) = 0x05; /* LAPB */ + break; + case ISDN_PROTO_L2_HDLC: + *(skb_put(*skb, 1)) = 0x02; + break; + case ISDN_PROTO_L2_TRANS: + /* + * Voice (a-law) + */ + *(skb_put(*skb, 1)) = 0x06; + break; + default: +#ifdef DEBUG + printk(KERN_DEBUG "Transparent\n"); +#endif + *(skb_put(*skb, 1)) = 0x03; + break; + } + + *(skb_put(*skb, 1)) = (outgoing ? 0x02 : 0x42); /* Don't ask */ + *(skb_put(*skb, 1)) = 0x00; + + *((ushort *) skb_put(*skb, 2)) = MRU; + + + *(skb_put(*skb, 1)) = 0x08; /* Modulo */ + *(skb_put(*skb, 1)) = 0x07; /* Max Window */ + + *(skb_put(*skb, 1)) = 0x01; /* No Layer3 Protocol */ + + /* + * 2 - layer3 MTU [10] + * - Modulo [12] + * - Window + * - layer1 proto [14] + * - bitrate + * - sub-channel [16] + * - layer1dataformat [17] + */ + + memset(skb_put(*skb, 8), 0, 8); + + return 18; +} + + +int capi_activate_transp_req(struct pcbit_chan *chan, struct sk_buff **skb) +{ + + if ((*skb = dev_alloc_skb(7)) == NULL) { + + printk(KERN_WARNING "capi_activate_transp_req: alloc_skb failed\n"); + return -1; + } + + (*skb)->free = 1; + + *((ushort*) skb_put(*skb, 2) ) = chan->callref; + + + *(skb_put(*skb, 1)) = chan->layer2link; /* Layer2 id */ + *(skb_put(*skb, 1)) = 0x00; /* Transmit by default */ + + *((ushort *) skb_put(*skb, 2)) = MRU; + + *(skb_put(*skb, 1)) = 0x01; /* Enables reception*/ + + return 7; +} + +int capi_tdata_req(struct pcbit_chan* chan, struct sk_buff *skb) +{ + ushort data_len; + + + /* + * callref - 2 + * layer2link - 1 + * wBlockLength - 2 + * data - 4 + * sernum - 1 + */ + + data_len = skb->len; + + skb_push(skb, 10); + + *((u16 *) (skb->data)) = chan->callref; + skb->data[2] = chan->layer2link; + *((u16 *) (skb->data + 3)) = data_len; + + chan->s_refnum = (chan->s_refnum + 1) % 8; + *((u32 *) (skb->data + 5)) = chan->s_refnum; + + skb->data[9] = 0; /* HDLC frame number */ + + return 10; +} + +int capi_tdata_resp(struct pcbit_chan *chan, struct sk_buff ** skb) + +{ + if ((*skb = dev_alloc_skb(4)) == NULL) { + + printk(KERN_WARNING "capi_tdata_resp: alloc_skb failed\n"); + return -1; + } + + (*skb)->free = 1; + + *((ushort*) skb_put(*skb, 2) ) = chan->callref; + + *(skb_put(*skb, 1)) = chan->layer2link; + *(skb_put(*skb, 1)) = chan->r_refnum; + + return (*skb)->len; +} + +int capi_disc_req(ushort callref, struct sk_buff **skb, u_char cause) +{ + + if ((*skb = dev_alloc_skb(6)) == NULL) { + + printk(KERN_WARNING "capi_disc_req: alloc_skb failed\n"); + return -1; + } + + (*skb)->free = 1; + + *((ushort*) skb_put(*skb, 2) ) = callref; + + *(skb_put(*skb, 1)) = 2; /* Cause.Length = 2; */ + *(skb_put(*skb, 1)) = 0x80; + *(skb_put(*skb, 1)) = 0x80 | cause; + + /* + * Change it: we should send 'Sic transit gloria Mundi' here ;-) + */ + + *(skb_put(*skb, 1)) = 0; /* UTUS.Length = 0; */ + + return 6; +} + +int capi_disc_resp(struct pcbit_chan *chan, struct sk_buff **skb) +{ + if ((*skb = dev_alloc_skb(2)) == NULL) { + + printk(KERN_WARNING "capi_disc_resp: alloc_skb failed\n"); + return -1; + } + + (*skb)->free = 1; + + *((ushort*) skb_put(*skb, 2)) = chan->callref; + + return 2; +} + + +/* + * Decoding of CAPI messages + * + */ + +int capi_decode_conn_ind(struct pcbit_chan * chan, + struct sk_buff *skb, + struct callb_data *info) +{ + int CIlen, len; + + /* Call Reference [CAPI] */ + chan->callref = *((ushort*) skb->data); + skb_pull(skb, 2); + +#ifdef DEBUG + printk(KERN_DEBUG "Call Reference: %04x\n", chan->callref); +#endif + + /* Channel Identification */ + + /* Expect + Len = 1 + Octect 3 = 0100 10CC - [ 7 Basic, 4 , 2-1 chan ] + */ + + CIlen = skb->data[0]; +#ifdef DEBUG + if (CIlen == 1) { + + if ( ((skb->data[1]) & 0xFC) == 0x48 ) + printk(KERN_DEBUG "decode_conn_ind: chan ok\n"); + printk(KERN_DEBUG "phyChan = %d\n", skb->data[1] & 0x03); + } + else + printk(KERN_DEBUG "conn_ind: CIlen = %d\n", CIlen); +#endif + skb_pull(skb, CIlen + 1); + + /* Calling Party Number */ + /* An "additional service" as far as Portugal Telecom is concerned */ + + len = skb->data[0]; + + if (len > 0) { + int count = 1; + +#ifdef DEBUG + printk(KERN_DEBUG "CPN: Octect 3 %02x\n", skb->data[1]); +#endif + if ((skb->data[1] & 0x80) == 0) + count = 2; + + if (!(info->data.setup.CallingPN = kmalloc(len - count + 1, GFP_ATOMIC))) + return -1; + + memcpy(info->data.setup.CallingPN, skb->data + count + 1, + len - count); + info->data.setup.CallingPN[len - count] = 0; + + } + else { + info->data.setup.CallingPN = NULL; + printk(KERN_DEBUG "NULL CallingPN\n"); + } + + skb_pull(skb, len + 1); + + /* Calling Party Subaddress */ + skb_pull(skb, skb->data[0] + 1); + + /* Called Party Number */ + + len = skb->data[0]; + + if (len > 0) { + int count = 1; + + if ((skb->data[1] & 0x80) == 0) + count = 2; + + if (!(info->data.setup.CalledPN = kmalloc(len - count + 1, GFP_ATOMIC))) + return -1; + + memcpy(info->data.setup.CalledPN, skb->data + count + 1, + len - count); + info->data.setup.CalledPN[len - count] = 0; + + } + else { + info->data.setup.CalledPN = NULL; + printk(KERN_DEBUG "NULL CalledPN\n"); + } + + skb_pull(skb, len + 1); + + /* Called Party Subaddress */ + skb_pull(skb, skb->data[0] + 1); + + /* LLC */ + skb_pull(skb, skb->data[0] + 1); + + /* HLC */ + skb_pull(skb, skb->data[0] + 1); + + /* U2U */ + skb_pull(skb, skb->data[0] + 1); + + return 0; +} + +/* + * returns errcode + */ + +int capi_decode_conn_conf(struct pcbit_chan * chan, struct sk_buff *skb, + int *complete) +{ + int errcode; + + chan->callref = *((ushort *) skb->data); /* Update CallReference */ + skb_pull(skb, 2); + + errcode = *((ushort *) skb->data); /* read errcode */ + skb_pull(skb, 2); + + *complete = *(skb->data); + skb_pull(skb, 1); + + /* FIX ME */ + /* This is actually a firmware bug */ + if (!*complete) + { + printk(KERN_DEBUG "complete=%02x\n", *complete); + *complete = 1; + } + + + /* Optional Bearer Capability */ + skb_pull(skb, *(skb->data) + 1); + + /* Channel Identification */ + skb_pull(skb, *(skb->data) + 1); + + /* High Layer Compatibility follows */ + skb_pull(skb, *(skb->data) + 1); + + return errcode; +} + +int capi_decode_conn_actv_ind(struct pcbit_chan * chan, struct sk_buff *skb) +{ + ushort len; +#ifdef DEBUG + char str[32]; +#endif + + /* Yet Another Bearer Capability */ + skb_pull(skb, *(skb->data) + 1); + + + /* Connected Party Number */ + len=*(skb->data); + +#ifdef DEBUG + if (len > 1 && len < 31) { + memcpy(str, skb->data + 2, len - 1); + str[len] = 0; + printk(KERN_DEBUG "Connected Party Number: %s\n", str); + } + else + printk(KERN_DEBUG "actv_ind CPN len = %d\n", len); +#endif + + skb_pull(skb, len + 1); + + /* Connected Subaddress */ + skb_pull(skb, *(skb->data) + 1); + + /* Low Layer Capability */ + skb_pull(skb, *(skb->data) + 1); + + /* High Layer Capability */ + skb_pull(skb, *(skb->data) + 1); + + return 0; +} + +int capi_decode_conn_actv_conf(struct pcbit_chan * chan, struct sk_buff *skb) +{ + ushort errcode; + + errcode = *((ushort*) skb->data); + skb_pull(skb, 2); + + /* Channel Identification + skb_pull(skb, skb->data[0] + 1); + */ + return errcode; +} + + +int capi_decode_sel_proto_conf(struct pcbit_chan *chan, struct sk_buff *skb) +{ + ushort errcode; + + chan->layer2link = *(skb->data); + skb_pull(skb, 1); + + errcode = *((ushort*) skb->data); + skb_pull(skb, 2); + + return errcode; +} + +int capi_decode_actv_trans_conf(struct pcbit_chan *chan, struct sk_buff *skb) +{ + ushort errcode; + + if (chan->layer2link != *(skb->data) ) + printk("capi_decode_actv_trans_conf: layer2link doesn't match\n"); + + skb_pull(skb, 1); + + errcode = *((ushort*) skb->data); + skb_pull(skb, 2); + + return errcode; +} + +int capi_decode_disc_ind(struct pcbit_chan *chan, struct sk_buff *skb) +{ + ushort len; +#ifdef DEBUG + int i; +#endif + /* Cause */ + + len = *(skb->data); + skb_pull(skb, 1); + +#ifdef DEBUG + + for (i=0; i<len; i++) + printk(KERN_DEBUG "Cause Octect %d: %02x\n", i+3, + *(skb->data + i)); +#endif + + skb_pull(skb, len); + + return 0; +} + +int capi_decode_disc_conf(struct pcbit_chan *chan, struct sk_buff *skb) +{ + ushort errcode; + + errcode = *((ushort*) skb->data); + skb_pull(skb, 2); + + return errcode; +} + +#ifdef DEBUG +int capi_decode_debug_188(u_char *hdr, ushort hdrlen) +{ + char str[64]; + int len; + + len = hdr[0]; + + if (len < 64 && len == hdrlen - 1) { + memcpy(str, hdr + 1, hdrlen - 1); + str[hdrlen - 1] = 0; + printk("%s\n", str); + } + else + printk("debug message incorrect\n"); + + return 0; +} +#endif + + + + + diff --git a/drivers/isdn/pcbit/capi.h b/drivers/isdn/pcbit/capi.h new file mode 100644 index 000000000..10bb6bf91 --- /dev/null +++ b/drivers/isdn/pcbit/capi.h @@ -0,0 +1,91 @@ +/* + * Copyright (C) 1996 Universidade de Lisboa + * + * Written by Pedro Roque Marques (roque@di.fc.ul.pt) + * + * This software may be used and distributed according to the terms of + * the GNU Public License, incorporated herein by reference. + */ + +/* + * CAPI encode/decode prototypes and defines + */ + +#ifndef CAPI_H +#define CAPI_H + + +#define REQ_CAUSE 0x01 +#define REQ_DISPLAY 0x04 +#define REQ_USER_TO_USER 0x08 + +#define AppInfoMask REQ_CAUSE|REQ_DISPLAY|REQ_USER_TO_USER + +/* Connection Setup */ +extern int capi_conn_req(const char * calledPN, struct sk_buff **buf, + int proto); +extern int capi_decode_conn_conf(struct pcbit_chan * chan, struct sk_buff *skb, + int *complete); + +extern int capi_decode_conn_ind(struct pcbit_chan * chan, struct sk_buff *skb, + struct callb_data *info); +extern int capi_conn_resp(struct pcbit_chan* chan, struct sk_buff **skb); + +extern int capi_conn_active_req(struct pcbit_chan* chan, struct sk_buff **skb); +extern int capi_decode_conn_actv_conf(struct pcbit_chan * chan, + struct sk_buff *skb); + +extern int capi_decode_conn_actv_ind(struct pcbit_chan * chan, + struct sk_buff *skb); +extern int capi_conn_active_resp(struct pcbit_chan* chan, + struct sk_buff **skb); + +/* Data */ +extern int capi_select_proto_req(struct pcbit_chan *chan, struct sk_buff **skb, + int outgoing); +extern int capi_decode_sel_proto_conf(struct pcbit_chan *chan, + struct sk_buff *skb); + +extern int capi_activate_transp_req(struct pcbit_chan *chan, + struct sk_buff **skb); +extern int capi_decode_actv_trans_conf(struct pcbit_chan *chan, + struct sk_buff *skb); + +extern int capi_tdata_req(struct pcbit_chan* chan, struct sk_buff *skb); +extern int capi_tdata_resp(struct pcbit_chan *chan, struct sk_buff ** skb); + +/* Connection Termination */ +extern int capi_disc_req(ushort callref, struct sk_buff **skb, u_char cause); +extern int capi_decode_disc_conf(struct pcbit_chan *chan, struct sk_buff *skb); + +extern int capi_decode_disc_ind(struct pcbit_chan *chan, struct sk_buff *skb); +extern int capi_disc_resp(struct pcbit_chan *chan, struct sk_buff **skb); + +#ifdef DEBUG +extern int capi_decode_debug_188(u_char *hdr, ushort hdrlen); +#endif + +extern __inline__ +struct pcbit_chan * +capi_channel(struct pcbit_dev *dev, struct sk_buff *skb) +{ + ushort callref; + + callref = *((ushort*) skb->data); + skb_pull(skb, 2); + + if (dev->b1->callref == callref) + return dev->b1; + else if (dev->b2->callref == callref) + return dev->b2; + + return NULL; +} + +#endif + + + + + + diff --git a/drivers/isdn/pcbit/drv.c b/drivers/isdn/pcbit/drv.c new file mode 100644 index 000000000..7347fa888 --- /dev/null +++ b/drivers/isdn/pcbit/drv.c @@ -0,0 +1,1154 @@ +/* + * Copyright (C) 1996 Universidade de Lisboa + * + * Written by Pedro Roque Marques (roque@di.fc.ul.pt) + * + * This software may be used and distributed according to the terms of + * the GNU Public License, incorporated herein by reference. + */ + +/* + * PCBIT-D interface with isdn4linux + */ + +/* + * Fixes: + * + * Nuno Grilo <l38486@alfa.ist.utl.pt> + * fixed msn_list NULL pointer dereference. + * + */ + +#define __NO_VERSION__ + +#include <linux/module.h> + +#include <linux/sched.h> + +#include <linux/kernel.h> + +#include <linux/types.h> +#include <linux/malloc.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/string.h> +#include <linux/skbuff.h> + +#include <linux/isdnif.h> +#include <asm/string.h> +#include <asm/io.h> +#include <asm/uaccess.h> + +#include "pcbit.h" +#include "edss1.h" +#include "layer2.h" +#include "capi.h" + + +extern ushort last_ref_num; + +static int pcbit_ioctl(isdn_ctrl* ctl); + +static char* pcbit_devname[MAX_PCBIT_CARDS] = { + "pcbit0", + "pcbit1", + "pcbit2", + "pcbit3" +}; + +/* + * prototypes + */ + +int pcbit_command(isdn_ctrl* ctl); +int pcbit_stat(u_char* buf, int len, int user, int, int); +int pcbit_xmit(int driver, int chan, struct sk_buff *skb); +int pcbit_writecmd(const u_char*, int, int, int, int); + +static int set_protocol_running(struct pcbit_dev * dev); + +static void pcbit_clear_msn(struct pcbit_dev *dev); +static void pcbit_set_msn(struct pcbit_dev *dev, char *list); +static int pcbit_check_msn(struct pcbit_dev *dev, char *msn); + + +extern void pcbit_deliver(void * data); + +int pcbit_init_dev(int board, int mem_base, int irq) +{ + struct pcbit_dev *dev; + isdn_if *dev_if; + + if ((dev=kmalloc(sizeof(struct pcbit_dev), GFP_KERNEL)) == NULL) + { + printk("pcbit_init: couldn't malloc pcbit_dev struct\n"); + return -ENOMEM; + } + + dev_pcbit[board] = dev; + memset(dev, 0, sizeof(struct pcbit_dev)); + + if (mem_base >= 0xA0000 && mem_base <= 0xFFFFF ) + dev->sh_mem = (unsigned char*) mem_base; + else + { + printk("memory address invalid"); + kfree(dev); + dev_pcbit[board] = NULL; + return -EACCES; + } + + dev->b1 = kmalloc(sizeof(struct pcbit_chan), GFP_KERNEL); + if (!dev->b1) { + printk("pcbit_init: couldn't malloc pcbit_chan struct\n"); + kfree(dev); + return -ENOMEM; + } + + dev->b2 = kmalloc(sizeof(struct pcbit_chan), GFP_KERNEL); + if (!dev->b2) { + printk("pcbit_init: couldn't malloc pcbit_chan struct\n"); + kfree(dev->b1); + kfree(dev); + return -ENOMEM; + } + + memset(dev->b1, 0, sizeof(struct pcbit_chan)); + memset(dev->b2, 0, sizeof(struct pcbit_chan)); + dev->b2->id = 1; + + + dev->qdelivery.next = NULL; + dev->qdelivery.sync = 0; + dev->qdelivery.routine = pcbit_deliver; + dev->qdelivery.data = dev; + + /* + * interrupts + */ + + if (request_irq(irq, &pcbit_irq_handler, 0, pcbit_devname[board], dev) != 0) + { + kfree(dev->b1); + kfree(dev->b2); + kfree(dev); + dev_pcbit[board] = NULL; + return -EIO; + } + + dev->irq = irq; + + /* next frame to be received */ + dev->rcv_seq = 0; + dev->send_seq = 0; + dev->unack_seq = 0; + + dev->hl_hdrlen = 10; + + dev_if = kmalloc(sizeof(isdn_if), GFP_KERNEL); + + if (!dev_if) { + free_irq(irq, dev); + kfree(dev->b1); + kfree(dev->b2); + kfree(dev); + dev_pcbit[board] = NULL; + return -EIO; + } + + dev->dev_if = dev_if; + + dev_if->channels = 2; + + + dev_if->features = (ISDN_FEATURE_P_EURO | ISDN_FEATURE_L3_TRANS | + ISDN_FEATURE_L2_HDLC | ISDN_FEATURE_L2_TRANS ); + + dev_if->writebuf_skb = pcbit_xmit; + dev_if->writebuf = NULL; + dev_if->hl_hdrlen = 10; + + dev_if->maxbufsize = MAXBUFSIZE; + dev_if->command = pcbit_command; + + dev_if->writecmd = pcbit_writecmd; + dev_if->readstat = pcbit_stat; + + + strcpy(dev_if->id, pcbit_devname[board]); + + if (!register_isdn(dev_if)) { + free_irq(irq, dev); + kfree(dev->b1); + kfree(dev->b2); + kfree(dev); + dev_pcbit[board] = NULL; + return -EIO; + } + + dev->id = dev_if->channels; + + + dev->l2_state = L2_DOWN; + dev->free = 511; + + /* + * set_protocol_running(dev); + */ + + return 0; +} + +#ifdef MODULE +void pcbit_terminate(int board) +{ + struct pcbit_dev * dev; + + dev = dev_pcbit[board]; + + if (dev) { + /* unregister_isdn(dev->dev_if); */ + free_irq(dev->irq, dev); + pcbit_clear_msn(dev); + kfree(dev->dev_if); + if (dev->b1->fsm_timer.function) + del_timer(&dev->b1->fsm_timer); + if (dev->b2->fsm_timer.function) + del_timer(&dev->b2->fsm_timer); + kfree(dev->b1); + kfree(dev->b2); + kfree(dev); + } +} +#endif + +int pcbit_command(isdn_ctrl* ctl) +{ + struct pcbit_dev *dev; + struct pcbit_chan *chan; + struct callb_data info; + char *cp; + + dev = finddev(ctl->driver); + + if (!dev) + { + printk("pcbit_command: unknown device\n"); + return -1; + } + + chan = (ctl->arg & 0x0F) ? dev->b2 : dev->b1; + + + switch(ctl->command) { + case ISDN_CMD_IOCTL: + return pcbit_ioctl(ctl); + break; + case ISDN_CMD_DIAL: + info.type = EV_USR_SETUP_REQ; + info.data.setup.CalledPN = (char *) &ctl->num; + cp = strchr(info.data.setup.CalledPN, ','); + if (cp) + *cp = 0; + else { + printk(KERN_DEBUG "DIAL: error in CalledPN\n"); + return -1; + } + pcbit_fsm_event(dev, chan, EV_USR_SETUP_REQ, &info); + break; + case ISDN_CMD_ACCEPTD: + pcbit_fsm_event(dev, chan, EV_USR_SETUP_RESP, NULL); + break; + case ISDN_CMD_ACCEPTB: + printk("ISDN_CMD_ACCEPTB - not really needed\n"); + break; + case ISDN_CMD_HANGUP: + pcbit_fsm_event(dev, chan, EV_USR_RELEASE_REQ, NULL); + break; + case ISDN_CMD_SETL2: + chan->proto = (ctl->arg >> 8); + break; + case ISDN_CMD_GETL2: + return chan->proto; + break; + case ISDN_CMD_LOCK: + MOD_INC_USE_COUNT; + break; + case ISDN_CMD_UNLOCK: + MOD_DEC_USE_COUNT; + break; + case ISDN_CMD_CLREAZ: + pcbit_clear_msn(dev); + break; + case ISDN_CMD_SETEAZ: + pcbit_set_msn(dev, ctl->num); + break; + case ISDN_CMD_SETL3: + if ((ctl->arg >> 8) != ISDN_PROTO_L3_TRANS) + printk(KERN_DEBUG "L3 protocol unknown\n"); + break; + case ISDN_CMD_GETL3: + return ISDN_PROTO_L3_TRANS; + break; + case ISDN_CMD_GETEAZ: + case ISDN_CMD_SETSIL: + case ISDN_CMD_GETSIL: + printk(KERN_DEBUG "pcbit_command: code %d not implemented yet\n", ctl->command); + break; + default: + printk(KERN_DEBUG "pcbit_command: unknown command\n"); + break; + }; + + return 0; +} + +/* + * Another Hack :-( + * on some conditions the board stops sending TDATA_CONFs + * let's see if we can turn around the problem + */ + +#ifdef BLOCK_TIMER +static void pcbit_block_timer(unsigned long data) +{ + struct pcbit_chan *chan; + struct pcbit_dev * dev; + isdn_ctrl ictl; + + chan = (struct pcbit_chan *) data; + + dev = chan2dev(chan); + + if (dev == NULL) { + printk(KERN_DEBUG "pcbit: chan2dev failed\n"); + return; + } + + del_timer(&chan->block_timer); + chan->block_timer.function = NULL; + +#ifdef DEBUG + printk(KERN_DEBUG "pcbit_block_timer\n"); +#endif + chan->queued = 0; + ictl.driver = dev->id; + ictl.command = ISDN_STAT_BSENT; + ictl.arg = chan->id; + dev->dev_if->statcallb(&ictl); +} +#endif + +int pcbit_xmit(int driver, int chnum, struct sk_buff *skb) +{ + ushort hdrlen; + int refnum, len; + struct pcbit_chan * chan; + struct pcbit_dev *dev; + + dev = finddev(driver); + if (dev == NULL) + { + printk("finddev returned NULL"); + return -1; + } + + chan = chnum ? dev->b2 : dev->b1; + + + if (chan->fsm_state != ST_ACTIVE) + return -1; + + if (chan->queued >= MAX_QUEUED ) + { +#ifdef DEBUG_QUEUE + printk(KERN_DEBUG + "pcbit: %d packets already in queue - write fails\n", + chan->queued); +#endif + /* + * packet stays on the head of the device queue + * since dev_start_xmit will fail + * see net/core/dev.c + */ +#ifdef BLOCK_TIMER + if (chan->block_timer.function == NULL) { + init_timer(&chan->block_timer); + chan->block_timer.function = &pcbit_block_timer; + chan->block_timer.data = (long) chan; + chan->block_timer.expires = jiffies + 1 * HZ; + add_timer(&chan->block_timer); + } +#endif + return 0; + } + + + chan->queued++; + + len = skb->len; + + hdrlen = capi_tdata_req(chan, skb); + + refnum = last_ref_num++ & 0x7fffU; + chan->s_refnum = refnum; + + pcbit_l2_write(dev, MSG_TDATA_REQ, refnum, skb, hdrlen); + + return len; +} + + +int pcbit_writecmd(const u_char* buf, int len, int user, int driver, int channel) +{ + struct pcbit_dev * dev; + int i, j; + const u_char * loadbuf; + u_char * ptr = NULL; + + int errstat; + + dev = finddev(driver); + + if (!dev) + { + printk("pcbit_writecmd: couldn't find device"); + return -ENODEV; + } + + switch(dev->l2_state) { + case L2_LWMODE: + /* check (size <= rdp_size); write buf into board */ + if (len > BANK4 + 1) + { + printk("pcbit_writecmd: invalid length %d\n", len); + return -EFAULT; + } + + if (user) + { + u_char cbuf[1024]; + + if (copy_from_user(cbuf, buf, len)) + return -EFAULT; + for (i=0; i<len; i++) + writeb(cbuf[i], dev->sh_mem + i); + } + else + memcpy_toio(dev->sh_mem, buf, len); + return len; + break; + case L2_FWMODE: + /* this is the hard part */ + /* dumb board */ + if (len < 0) + return -EINVAL; + + if (user) { + /* get it into kernel space */ + if ((ptr = kmalloc(len, GFP_KERNEL))==NULL) + return -ENOMEM; + if (copy_from_user(ptr, buf, len)) + { + kfree(ptr); + return -EFAULT; + } + loadbuf = ptr; + } + else + loadbuf = buf; + + errstat = 0; + + for (i=0; i < len; i++) + { + for(j=0; j < LOAD_RETRY; j++) + { + __volatile__ unsigned char * ptr; + + ptr = dev->sh_mem + dev->loadptr; + if (*ptr == 0) + break; + + } + + if (j == LOAD_RETRY) + { + errstat = -ETIME; + printk("TIMEOUT i=%d\n", i); + break; + } + writeb(loadbuf[i], dev->sh_mem + dev->loadptr + 1); + writeb(0x01, dev->sh_mem + dev->loadptr); + + dev->loadptr += 2; + if (dev->loadptr > LOAD_ZONE_END) + dev->loadptr = LOAD_ZONE_START; + } + + if (user) + kfree(ptr); + + return errstat ? errstat : len; + + break; + default: + return -EBUSY; + } + return 0; +} + +/* + * demultiplexing of messages + * + */ + +void pcbit_l3_receive(struct pcbit_dev * dev, ulong msg, + struct sk_buff * skb, + ushort hdr_len, ushort refnum) +{ + struct pcbit_chan *chan; + struct sk_buff *skb2; + unsigned short len; + struct callb_data cbdata; + int complete, err; + isdn_ctrl ictl; +#ifdef DEBUG + struct msg_fmt * fmsg; +#endif + + switch(msg) { + + case MSG_TDATA_IND: + if (!(chan = capi_channel(dev, skb))) { + printk(KERN_WARNING + "CAPI header: unknown channel id\n"); + break; + } + chan->r_refnum = skb->data[7]; + skb_pull(skb, 8); + + dev->dev_if->rcvcallb_skb(dev->id, chan->id, skb); + + if (capi_tdata_resp(chan, &skb2) > 0) + pcbit_l2_write(dev, MSG_TDATA_RESP, refnum, + skb2, skb2->len); + return; + break; + case MSG_TDATA_CONF: + if (!(chan = capi_channel(dev, skb))) { + printk(KERN_WARNING + "CAPI header: unknown channel id\n"); + break; + } + +#ifdef DEBUG + if ( (*((ushort *) (skb->data + 2) )) != 0) { + printk(KERN_DEBUG "TDATA_CONF error\n"); + } +#endif +#ifdef BLOCK_TIMER + if (chan->queued == MAX_QUEUED) { + del_timer(&chan->block_timer); + chan->block_timer.function = NULL; + } + +#endif + chan->queued--; + + ictl.driver = dev->id; + ictl.command = ISDN_STAT_BSENT; + ictl.arg = chan->id; + dev->dev_if->statcallb(&ictl); + break; + + case MSG_CONN_IND: + /* + * channel: 1st not used will do + * if both are used we're in trouble + */ + + if (!dev->b1->fsm_state) + chan = dev->b1; + else if (!dev->b2->fsm_state) + chan = dev->b2; + else { + printk(KERN_INFO + "Incoming connection: no channels available"); + + if ((len = capi_disc_req(*(ushort*)(skb->data), &skb2, CAUSE_NOCHAN)) > 0) + pcbit_l2_write(dev, MSG_DISC_REQ, refnum, skb2, len); + break; + } + + cbdata.data.setup.CalledPN = NULL; + cbdata.data.setup.CallingPN = NULL; + + capi_decode_conn_ind(chan, skb, &cbdata); + cbdata.type = EV_NET_SETUP; + + pcbit_fsm_event(dev, chan, EV_NET_SETUP, NULL); + + if (pcbit_check_msn(dev, cbdata.data.setup.CallingPN)) + pcbit_fsm_event(dev, chan, EV_USR_PROCED_REQ, &cbdata); + else + pcbit_fsm_event(dev, chan, EV_USR_RELEASE_REQ, NULL); + + if (cbdata.data.setup.CalledPN) + kfree(cbdata.data.setup.CalledPN); + if (cbdata.data.setup.CallingPN) + kfree(cbdata.data.setup.CallingPN); + break; + + case MSG_CONN_CONF: + /* + * We should be able to find the channel by the message + * reference number. The current version of the firmware + * doesn't sent the ref number correctly. + */ +#ifdef DEBUG + printk(KERN_DEBUG "refnum=%04x b1=%04x b2=%04x\n", refnum, + dev->b1->s_refnum, + dev->b2->s_refnum); +#endif +#if 0 + if (dev->b1->s_refnum == refnum) + chan = dev->b1; + else { + + if (dev->b2->s_refnum == refnum) + chan = dev->b2; + else { + chan = NULL; + printk(KERN_WARNING "Connection Confirm - refnum doesn't match chan\n"); + break; + } + } +#else + /* We just try to find a channel in the right state */ + + if (dev->b1->fsm_state == ST_CALL_INIT) + chan = dev->b1; + else { + if (dev->b2->s_refnum == ST_CALL_INIT) + chan = dev->b2; + else { + chan = NULL; + printk(KERN_WARNING "Connection Confirm - no channel in Call Init state\n"); + break; + } + } +#endif + if (capi_decode_conn_conf(chan, skb, &complete)) { + printk(KERN_DEBUG "conn_conf indicates error\n"); + pcbit_fsm_event(dev, chan, EV_ERROR, NULL); + } + else + if (complete) + pcbit_fsm_event(dev, chan, EV_NET_CALL_PROC, NULL); + else + pcbit_fsm_event(dev, chan, EV_NET_SETUP_ACK, NULL); + break; + case MSG_CONN_ACTV_IND: + + if (!(chan = capi_channel(dev, skb))) { + printk(KERN_WARNING + "CAPI header: unknown channel id\n"); + break; + } + + if (capi_decode_conn_actv_ind(chan, skb)) { + printk("error in capi_decode_conn_actv_ind\n"); + /* pcbit_fsm_event(dev, chan, EV_ERROR, NULL); */ + break; + } + chan->r_refnum = refnum; + pcbit_fsm_event(dev, chan, EV_NET_CONN, NULL); + break; + case MSG_CONN_ACTV_CONF: + + if (!(chan = capi_channel(dev, skb))) { + printk(KERN_WARNING + "CAPI header: unknown channel id\n"); + break; + } + + if (capi_decode_conn_actv_conf(chan, skb) == 0) + pcbit_fsm_event(dev, chan, EV_NET_CONN_ACK, NULL); + + else + printk(KERN_DEBUG "decode_conn_actv_conf failed\n"); + break; + + case MSG_SELP_CONF: + + if (!(chan = capi_channel(dev, skb))) { + printk(KERN_WARNING + "CAPI header: unknown channel id\n"); + break; + } + + if (!(err = capi_decode_sel_proto_conf(chan, skb))) + pcbit_fsm_event(dev, chan, EV_NET_SELP_RESP, NULL); + else { + /* Error */ + printk("error %d - capi_decode_sel_proto_conf\n", err); + } + break; + case MSG_ACT_TRANSP_CONF: + if (!(chan = capi_channel(dev, skb))) { + printk(KERN_WARNING + "CAPI header: unknown channel id\n"); + break; + } + + if (!capi_decode_actv_trans_conf(chan, skb)) + pcbit_fsm_event(dev, chan, EV_NET_ACTV_RESP, NULL); + break; + + case MSG_DISC_IND: + + if (!(chan = capi_channel(dev, skb))) { + printk(KERN_WARNING + "CAPI header: unknown channel id\n"); + break; + } + + if (!capi_decode_disc_ind(chan, skb)) + pcbit_fsm_event(dev, chan, EV_NET_DISC, NULL); + else + printk(KERN_WARNING "capi_decode_disc_ind - error\n"); + break; + case MSG_DISC_CONF: + if (!(chan = capi_channel(dev, skb))) { + printk(KERN_WARNING + "CAPI header: unknown channel id\n"); + break; + } + + if (!capi_decode_disc_ind(chan, skb)) + pcbit_fsm_event(dev, chan, EV_NET_RELEASE, NULL); + else + printk(KERN_WARNING "capi_decode_disc_conf - error\n"); + break; + case MSG_INFO_IND: +#ifdef DEBUG + printk(KERN_DEBUG "received Info Indication - discarded\n"); +#endif + break; +#ifdef DEBUG + case MSG_DEBUG_188: + capi_decode_debug_188(skb->data, skb->len); + break; + + default: + printk(KERN_DEBUG "pcbit_l3_receive: unknown message %08lx\n", + msg); + fmsg = (struct msg_fmt *) &msg; + printk(KERN_DEBUG "cmd=%02x sub=%02x\n", + fmsg->cmd, fmsg->scmd); + break; +#endif + } + + skb->free = 1; + + kfree_skb(skb, FREE_READ); + +} + +/* + * Single statbuf + * should be a statbuf per device + */ + +static char statbuf[STATBUF_LEN]; +static int stat_st = 0; +static int stat_end = 0; + + +extern inline int memcpy_to_COND(int flag, void *d, void *s, int len) +{ + if (flag) + return copy_to_user(d, s, len); + memcpy(d, s, len); + return 0; +} + + +int pcbit_stat(u_char* buf, int len, int user, int driver, int channel) +{ + int stat_count; + stat_count = stat_end - stat_st; + + if (stat_count < 0) + stat_count = STATBUF_LEN - stat_st + stat_end; + + /* FIXME: should we sleep and wait for more cookies ? */ + if (len > stat_count) + len = stat_count; + + if (stat_st < stat_end) + { + if (memcpy_to_COND(user, buf, statbuf + stat_st, len)) + return -EFAULT; + stat_st += len; + } + else + { + if (len > STATBUF_LEN - stat_st) + { + if (memcpy_to_COND(user, buf, statbuf + stat_st, + STATBUF_LEN - stat_st)) + return -EFAULT; + if (memcpy_to_COND(user, buf, statbuf, + len - (STATBUF_LEN - stat_st))) + return -EFAULT; + stat_st = len - (STATBUF_LEN - stat_st); + } + else + { + if (memcpy_to_COND(user, buf, statbuf + stat_st, + len)) + return -EFAULT; + + stat_st += len; + + if (stat_st == STATBUF_LEN) + stat_st = 0; + } + } + + if (stat_st == stat_end) + stat_st = stat_end = 0; + + return len; +} + +static void pcbit_logstat(struct pcbit_dev *dev, char *str) +{ + int i; + isdn_ctrl ictl; + + for (i=stat_end; i<strlen(str); i++) + { + statbuf[i]=str[i]; + stat_end = (stat_end + 1) % STATBUF_LEN; + if (stat_end == stat_st) + stat_st = (stat_st + 1) % STATBUF_LEN; + } + + ictl.command=ISDN_STAT_STAVAIL; + ictl.driver=dev->id; + ictl.arg=strlen(str); + dev->dev_if->statcallb(&ictl); +} + +extern char * isdn_state_table[]; +extern char * strisdnevent(unsigned short); + + +void pcbit_state_change(struct pcbit_dev * dev, struct pcbit_chan * chan, + unsigned short i, unsigned short ev, unsigned short f) +{ + char buf[256]; + + sprintf(buf, "change on device: %d channel:%d\n%s -> %s -> %s\n", + dev->id, chan->id, + isdn_state_table[i], strisdnevent(ev), isdn_state_table[f] + ); + +#ifdef DEBUG + printk("%s", buf); +#endif + + pcbit_logstat(dev, buf); +} + +static void set_running_timeout(unsigned long ptr) +{ + struct pcbit_dev * dev; + +#ifdef DEBUG + printk(KERN_DEBUG "set_running_timeout\n"); +#endif + dev = (struct pcbit_dev *) ptr; + + wake_up_interruptible(&dev->set_running_wq); +} + +static int set_protocol_running(struct pcbit_dev * dev) +{ + isdn_ctrl ctl; + + init_timer(&dev->set_running_timer); + + dev->set_running_timer.function = &set_running_timeout; + dev->set_running_timer.data = (ulong) dev; + dev->set_running_timer.expires = jiffies + SET_RUN_TIMEOUT; + + /* kick it */ + + dev->l2_state = L2_STARTING; + + writeb((0x80U | ((dev->rcv_seq & 0x07) << 3) | (dev->send_seq & 0x07)), + dev->sh_mem + BANK4); + + add_timer(&dev->set_running_timer); + + interruptible_sleep_on(&dev->set_running_wq); + + del_timer(&dev->set_running_timer); + + if (dev->l2_state == L2_RUNNING) + { + printk(KERN_DEBUG "pcbit: running\n"); + + dev->unack_seq = dev->send_seq; + + dev->writeptr = dev->sh_mem; + dev->readptr = dev->sh_mem + BANK2; + + /* tell the good news to the upper layer */ + ctl.driver = dev->id; + ctl.command = ISDN_STAT_RUN; + + dev->dev_if->statcallb(&ctl); + } + else + { + printk(KERN_DEBUG "pcbit: initialization failed\n"); + printk(KERN_DEBUG "pcbit: firmware not loaded\n"); + + dev->l2_state = L2_DOWN; + +#ifdef DEBUG + printk(KERN_DEBUG "Bank3 = %02x\n", + readb(dev->sh_mem + BANK3)); +#endif + *(dev->sh_mem + BANK4) = 0x40U; + + /* warn the upper layer */ + ctl.driver = dev->id; + ctl.command = ISDN_STAT_STOP; + + dev->dev_if->statcallb(&ctl); + + return -EL2HLT; /* Level 2 halted */ + } + + return 0; +} + +static int pcbit_ioctl(isdn_ctrl* ctl) +{ + struct pcbit_dev * dev; + struct pcbit_ioctl *cmd; + + dev = finddev(ctl->driver); + + if (!dev) + { + printk(KERN_DEBUG "pcbit_ioctl: unknown device\n"); + return -ENODEV; + } + + cmd = (struct pcbit_ioctl *) ctl->num; + + switch(ctl->arg) { + case PCBIT_IOCTL_GETSTAT: + cmd->info.l2_status = dev->l2_state; + break; + + case PCBIT_IOCTL_STRLOAD: + if (dev->l2_state == L2_RUNNING) + return -EBUSY; + + dev->unack_seq = dev->send_seq = dev->rcv_seq = 0; + + dev->writeptr = dev->sh_mem; + dev->readptr = dev->sh_mem + BANK2; + + dev->l2_state = L2_LOADING; + break; + + case PCBIT_IOCTL_LWMODE: + if (dev->l2_state != L2_LOADING) + return -EINVAL; + + dev->l2_state = L2_LWMODE; + break; + + case PCBIT_IOCTL_FWMODE: + if (dev->l2_state == L2_RUNNING) + return -EBUSY; + dev->loadptr = LOAD_ZONE_START; + dev->l2_state = L2_FWMODE; + + break; + case PCBIT_IOCTL_ENDLOAD: + if (dev->l2_state == L2_RUNNING) + return -EBUSY; + dev->l2_state = L2_DOWN; + break; + + case PCBIT_IOCTL_SETBYTE: + if (dev->l2_state == L2_RUNNING) + return -EBUSY; + + /* check addr */ + if (cmd->info.rdp_byte.addr > BANK4) + return -EFAULT; + + writeb(cmd->info.rdp_byte.value, dev->sh_mem + cmd->info.rdp_byte.addr); + break; + case PCBIT_IOCTL_GETBYTE: + if (dev->l2_state == L2_RUNNING) + return -EBUSY; + + /* check addr */ + + if (cmd->info.rdp_byte.addr > BANK4) + { + printk("getbyte: invalid addr %04x\n", cmd->info.rdp_byte.addr); + return -EFAULT; + } + + cmd->info.rdp_byte.value = readb(dev->sh_mem + cmd->info.rdp_byte.addr); + break; + case PCBIT_IOCTL_RUNNING: + if (dev->l2_state == L2_RUNNING) + return -EBUSY; + return set_protocol_running(dev); + break; + case PCBIT_IOCTL_WATCH188: + if (dev->l2_state != L2_LOADING) + return -EINVAL; + pcbit_l2_write(dev, MSG_WATCH188, 0x0001, NULL, 0); + break; + case PCBIT_IOCTL_PING188: + if (dev->l2_state != L2_LOADING) + return -EINVAL; + pcbit_l2_write(dev, MSG_PING188_REQ, 0x0001, NULL, 0); + break; + case PCBIT_IOCTL_APION: + if (dev->l2_state != L2_LOADING) + return -EINVAL; + pcbit_l2_write(dev, MSG_API_ON, 0x0001, NULL, 0); + break; + case PCBIT_IOCTL_STOP: + dev->l2_state = L2_DOWN; + writeb(0x40, dev->sh_mem + BANK4); + dev->rcv_seq = 0; + dev->send_seq = 0; + dev->unack_seq = 0; + break; + default: + printk("error: unknown ioctl\n"); + break; + }; + return 0; +} + +/* + * MSN list handling + * + * if null reject all calls + * if first entry has null MSN accept all calls + */ + +static void pcbit_clear_msn(struct pcbit_dev *dev) +{ + struct msn_entry *ptr, *back; + + for (ptr=dev->msn_list; ptr; ) + { + back = ptr->next; + kfree(ptr); + ptr = back; + } + + dev->msn_list = NULL; +} + +static void pcbit_set_msn(struct pcbit_dev *dev, char *list) +{ + struct msn_entry *ptr; + struct msn_entry *back = NULL; + char *cp, *sp; + int len; + + if (strlen(list) == 0) { + ptr = kmalloc(sizeof(struct msn_entry), GFP_ATOMIC); + if (!ptr) { + printk(KERN_WARNING "kmalloc failed\n"); + return; + } + + ptr->msn = NULL; + + ptr->next = dev->msn_list; + dev->msn_list = ptr; + + return; + } + + if (dev->msn_list) + for (back=dev->msn_list; back->next; back=back->next); + + sp = list; + + do { + cp=strchr(sp, ','); + if (cp) + len = cp - sp; + else + len = strlen(sp); + + ptr = kmalloc(sizeof(struct msn_entry), GFP_ATOMIC); + + if (!ptr) { + printk(KERN_WARNING "kmalloc failed\n"); + return; + } + ptr->next = NULL; + + ptr->msn = kmalloc(len, GFP_ATOMIC); + if (!ptr->msn) { + printk(KERN_WARNING "kmalloc failed\n"); + return; + } + + memcpy(ptr->msn, sp, len - 1); + ptr->msn[len] = 0; + +#ifdef DEBUG + printk(KERN_DEBUG "msn: %s\n", ptr->msn); +#endif + if (dev->msn_list == NULL) + dev->msn_list = ptr; + else + back->next = ptr; + back = ptr; + sp += len; + } while(cp); +} + +/* + * check if we do signal or reject an incoming call + */ +static int pcbit_check_msn(struct pcbit_dev *dev, char *msn) +{ + struct msn_entry *ptr; + + for (ptr=dev->msn_list; ptr; ptr=ptr->next) { + + if (ptr->msn == NULL) + return 1; + + if (strcmp(ptr->msn, msn) == 0) + return 1; + } + + return 0; +} diff --git a/drivers/isdn/pcbit/edss1.c b/drivers/isdn/pcbit/edss1.c new file mode 100644 index 000000000..1b76b3462 --- /dev/null +++ b/drivers/isdn/pcbit/edss1.c @@ -0,0 +1,334 @@ +/* + * Copyright (C) 1996 Universidade de Lisboa + * + * Written by Pedro Roque Marques (roque@di.fc.ul.pt) + * + * This software may be used and distributed according to the terms of + * the GNU Public License, incorporated herein by reference. + */ + +/* + * DSS.1 Finite State Machine + * base: ITU-T Rec Q.931 + */ + +/* + * TODO: complete the FSM + * move state/event descriptions to a user space logger + */ + +#define __NO_VERSION__ + +#include <linux/module.h> + +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/kernel.h> + +#include <linux/types.h> +#include <linux/malloc.h> +#include <linux/mm.h> +#include <linux/tqueue.h> +#include <linux/skbuff.h> + +#include <linux/timer.h> +#include <asm/io.h> + +#include <linux/isdnif.h> + +#include "pcbit.h" +#include "edss1.h" +#include "layer2.h" +#include "callbacks.h" + + +extern void pcbit_state_change(struct pcbit_dev *, struct pcbit_chan *, + unsigned short i, unsigned short ev, + unsigned short f); + +extern struct pcbit_dev * dev_pcbit[MAX_PCBIT_CARDS]; + +char * isdn_state_table[] = { + "Closed", + "Call initiated", + "Overlap sending", + "Outgoing call proceeding", + "NOT DEFINED", + "Call delivered", + "Call present", + "Call received", + "Connect request", + "Incoming call proceeding", + "Active", + "Disconnect request", + "Disconnect indication", + "NOT DEFINED", + "NOT DEFINED", + "Suspend request", + "NOT DEFINED", + "Resume request", + "NOT DEFINED", + "Release Request", + "NOT DEFINED", + "NOT DEFINED", + "NOT DEFINED", + "NOT DEFINED", + "NOT DEFINED", + "Overlap receiving", + "Select protocol on B-Channel", + "Activate B-channel protocol" +}; + +#ifdef DEBUG_ERRS +static +struct CauseValue { + byte nr; + char *descr; +} cvlist[]={ + {0x01,"Unallocated (unassigned) number"}, + {0x02,"No route to specified transit network"}, + {0x03,"No route to destination"}, + {0x04,"Send special information tone"}, + {0x05,"Misdialled trunk prefix"}, + {0x06,"Channel unacceptable"}, + {0x07,"Channel awarded and being delivered in an established channel"}, + {0x08,"Preemption"}, + {0x09,"Preemption - circuit reserved for reuse"}, + {0x10,"Normal call clearing"}, + {0x11,"User busy"}, + {0x12,"No user responding"}, + {0x13,"No answer from user (user alerted)"}, + {0x14,"Subscriber absent"}, + {0x15,"Call rejected"}, + {0x16,"Number changed"}, + {0x1a,"non-selected user clearing"}, + {0x1b,"Destination out of order"}, + {0x1c,"Invalid number format (address incomplete)"}, + {0x1d,"Facility rejected"}, + {0x1e,"Response to Status enquiry"}, + {0x1f,"Normal, unspecified"}, + {0x22,"No circuit/channel available"}, + {0x26,"Network out of order"}, + {0x27,"Permanent frame mode connection out-of-service"}, + {0x28,"Permanent frame mode connection operational"}, + {0x29,"Temporary failure"}, + {0x2a,"Switching equipment congestion"}, + {0x2b,"Access information discarded"}, + {0x2c,"Requested circuit/channel not available"}, + {0x2e,"Precedence call blocked"}, + {0x2f,"Resource unavailable, unspecified"}, + {0x31,"Quality of service unavailable"}, + {0x32,"Requested facility not subscribed"}, + {0x35,"Outgoing calls barred within CUG"}, + {0x37,"Incoming calls barred within CUG"}, + {0x39,"Bearer capability not authorized"}, + {0x3a,"Bearer capability not presently available"}, + {0x3e,"Inconsistency in designated outgoing access information and subscriber class"}, + {0x3f,"Service or option not available, unspecified"}, + {0x41,"Bearer capability not implemented"}, + {0x42,"Channel type not implemented"}, + {0x43,"Requested facility not implemented"}, + {0x44,"Only restricted digital information bearer capability is available"}, + {0x4f,"Service or option not implemented"}, + {0x51,"Invalid call reference value"}, + {0x52,"Identified channel does not exist"}, + {0x53,"A suspended call exists, but this call identity does not"}, + {0x54,"Call identity in use"}, + {0x55,"No call suspended"}, + {0x56,"Call having the requested call identity has been cleared"}, + {0x57,"User not member of CUG"}, + {0x58,"Incompatible destination"}, + {0x5a,"Non-existent CUG"}, + {0x5b,"Invalid transit network selection"}, + {0x5f,"Invalid message, unspecified"}, + {0x60,"Mandatory information element is missing"}, + {0x61,"Message type non-existent or not implemented"}, + {0x62,"Message not compatible with call state or message type non-existent or not implemented"}, + {0x63,"Information element/parameter non-existent or not implemented"}, + {0x64,"Invalid information element contents"}, + {0x65,"Message not compatible with call state"}, + {0x66,"Recovery on timer expiry"}, + {0x67,"Parameter non-existent or not implemented - passed on"}, + {0x6e,"Message with unrecognized parameter discarded"}, + {0x6f,"Protocol error, unspecified"}, + {0x7f,"Interworking, unspecified"} +}; + +#endif + +static struct isdn_event_desc { + unsigned short ev; + char * desc; +} isdn_event_table [] = { + {EV_USR_SETUP_REQ, "CC->L3: Setup Request"}, + {EV_USR_SETUP_RESP, "CC->L3: Setup Response"}, + {EV_USR_PROCED_REQ, "CC->L3: Proceeding Request"}, + {EV_USR_RELEASE_REQ, "CC->L3: Release Request"}, + + {EV_NET_SETUP, "NET->TE: setup "}, + {EV_NET_CALL_PROC, "NET->TE: call proceeding"}, + {EV_NET_SETUP_ACK, "NET->TE: setup acknowledge (more info needed)"}, + {EV_NET_CONN, "NET->TE: connect"}, + {EV_NET_CONN_ACK, "NET->TE: connect acknowledge"}, + {EV_NET_DISC, "NET->TE: disconnect indication"}, + {EV_NET_RELEASE, "NET->TE: release"}, + {EV_NET_RELEASE_COMP, "NET->TE: release complete"}, + {EV_NET_SELP_RESP, "Board: Select B-channel protocol ack"}, + {EV_NET_ACTV_RESP, "Board: Activate B-channel protocol ack"}, + {EV_TIMER, "Timeout"}, + {0, "NULL"} +}; + +char * strisdnevent(ushort ev) +{ + struct isdn_event_desc * entry; + + for (entry = isdn_event_table; entry->ev; entry++) + if (entry->ev == ev) + break; + + return entry->desc; +} + +/* + * Euro ISDN finite state machine + */ + +static struct fsm_timer_entry fsm_timers[] = { + {ST_CALL_PROC, 10}, + {ST_DISC_REQ, 2}, + {ST_ACTIVE_SELP, 5}, + {ST_ACTIVE_ACTV, 5}, + {ST_INCM_PROC, 10}, + {ST_CONN_REQ, 2}, + {0xff, 0} +}; + +static struct fsm_entry fsm_table[] = { +/* Connect Phase */ + /* Outgoing */ + {ST_NULL, ST_CALL_INIT, EV_USR_SETUP_REQ, cb_out_1}, + + {ST_CALL_INIT, ST_OVER_SEND, EV_NET_SETUP_ACK, cb_notdone}, + {ST_CALL_INIT, ST_CALL_PROC, EV_NET_CALL_PROC, NULL}, + {ST_CALL_INIT, ST_NULL, EV_NET_DISC, cb_out_2}, + + {ST_CALL_PROC, ST_ACTIVE_SELP, EV_NET_CONN, cb_out_2}, + {ST_CALL_PROC, ST_NULL, EV_NET_DISC, cb_disc_1}, + {ST_CALL_PROC, ST_DISC_REQ, EV_USR_RELEASE_REQ, cb_disc_2}, + + /* Incoming */ + {ST_NULL, ST_CALL_PRES, EV_NET_SETUP, NULL}, + + {ST_CALL_PRES, ST_INCM_PROC, EV_USR_PROCED_REQ, cb_in_1}, + {ST_CALL_PRES, ST_DISC_REQ, EV_USR_RELEASE_REQ, cb_disc_2}, + + {ST_INCM_PROC, ST_CONN_REQ, EV_USR_SETUP_RESP, cb_in_2}, + {ST_INCM_PROC, ST_DISC_REQ, EV_USR_RELEASE_REQ, cb_disc_2}, + + {ST_CONN_REQ, ST_ACTIVE_SELP, EV_NET_CONN_ACK, cb_in_3}, + + /* Active */ + {ST_ACTIVE, ST_NULL, EV_NET_DISC, cb_disc_1}, + {ST_ACTIVE, ST_DISC_REQ, EV_USR_RELEASE_REQ, cb_disc_2}, + {ST_ACTIVE, ST_NULL, EV_NET_RELEASE, cb_disc_3}, + + /* Disconnect */ + + {ST_DISC_REQ, ST_NULL, EV_NET_DISC, cb_disc_1}, + {ST_DISC_REQ, ST_NULL, EV_NET_RELEASE, cb_disc_3}, + + /* protocol selection */ + {ST_ACTIVE_SELP, ST_ACTIVE_ACTV, EV_NET_SELP_RESP, cb_selp_1}, + {ST_ACTIVE_SELP, ST_DISC_REQ, EV_USR_RELEASE_REQ, cb_disc_2}, + + {ST_ACTIVE_ACTV, ST_ACTIVE, EV_NET_ACTV_RESP, cb_open}, + {ST_ACTIVE_ACTV, ST_DISC_REQ, EV_USR_RELEASE_REQ, cb_disc_2}, + + /* Timers */ + {ST_CALL_PROC, ST_DISC_REQ, EV_TIMER, cb_disc_2}, + {ST_DISC_REQ, ST_NULL, EV_TIMER, cb_disc_3}, + {ST_ACTIVE_SELP, ST_DISC_REQ, EV_TIMER, cb_disc_2}, + {ST_ACTIVE_ACTV, ST_DISC_REQ, EV_TIMER, cb_disc_2}, + {ST_INCM_PROC, ST_DISC_REQ, EV_TIMER, cb_disc_2}, + {ST_CONN_REQ, ST_CONN_REQ, EV_TIMER, cb_in_2}, + + {0xff, 0, 0, NULL} +}; + + +static void pcbit_fsm_timer(unsigned long data) +{ + struct pcbit_dev *dev; + struct pcbit_chan *chan; + + chan = (struct pcbit_chan *) data; + + del_timer(&chan->fsm_timer); + chan->fsm_timer.function = NULL; + + dev = chan2dev(chan); + + if (dev == NULL) { + printk(KERN_WARNING "pcbit: timer for unknown device\n"); + return; + } + + pcbit_fsm_event(dev, chan, EV_TIMER, NULL); +} + + +void pcbit_fsm_event(struct pcbit_dev *dev, struct pcbit_chan *chan, + unsigned short event, struct callb_data *data) +{ + struct fsm_entry * action; + struct fsm_timer_entry *tentry; + unsigned long flags; + + save_flags(flags); + cli(); + + + for (action = fsm_table; action->init != 0xff; action++) + if (action->init == chan->fsm_state && action->event == event) + break; + + if (action->init == 0xff) { + + printk(KERN_DEBUG "fsm error: event %x on state %x\n", + event, chan->fsm_state); + restore_flags(flags); + return; + } + + if (chan->fsm_timer.function) { + del_timer(&chan->fsm_timer); + chan->fsm_timer.function = NULL; + } + + chan->fsm_state = action->final; + + pcbit_state_change(dev, chan, action->init, event, action->final); + + for (tentry = fsm_timers; tentry->init != 0xff; tentry++) + if (tentry->init == chan->fsm_state) + break; + + if (tentry->init != 0xff) { + init_timer(&chan->fsm_timer); + chan->fsm_timer.function = &pcbit_fsm_timer; + chan->fsm_timer.data = (ulong) chan; + chan->fsm_timer.expires = jiffies + tentry->timeout * HZ; + add_timer(&chan->fsm_timer); + } + + restore_flags(flags); + + if (action->callb) + action->callb(dev, chan, data); + +} + + + + diff --git a/drivers/isdn/pcbit/edss1.h b/drivers/isdn/pcbit/edss1.h new file mode 100644 index 000000000..1bf554dd5 --- /dev/null +++ b/drivers/isdn/pcbit/edss1.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 1996 Universidade de Lisboa + * + * Written by Pedro Roque Marques (roque@di.fc.ul.pt) + * + * This software may be used and distributed according to the terms of + * the GNU Public License, incorporated herein by reference. + */ + +/* + * DSS.1 module definitions + */ + +#ifndef EDSS1_H +#define EDSS1_H + +/* ISDN states */ + +#define ST_NULL 0 +#define ST_CALL_INIT 1 /* Call initiated */ +#define ST_OVER_SEND 2 /* Overlap sending - Requests More Info 4 call */ +#define ST_CALL_PROC 3 /* Call Proceeding */ +#define ST_CALL_DELV 4 +#define ST_CALL_PRES 6 /* Call Present - Received CONN.IND */ +#define ST_CALL_RECV 7 /* Alerting sent */ +#define ST_CONN_REQ 8 /* Answered - waiting 4 CONN.CONF */ +#define ST_INCM_PROC 9 +#define ST_ACTIVE 10 +#define ST_DISC_REQ 11 +#define ST_DISC_IND 12 +#define ST_SUSP_REQ 15 +#define ST_RESM_REQ 17 +#define ST_RELS_REQ 19 +#define ST_OVER_RECV 25 + +#define ST_ACTIVE_SELP 26 /* Select protocol on B-Channel */ +#define ST_ACTIVE_ACTV 27 /* Activate B-channel protocol */ + +#define MAX_STATE ST_ACTIVE_ACTV + +#define EV_NULL 0 +#define EV_USR_SETUP_REQ 1 +#define EV_USR_SETUP_RESP 2 +#define EV_USR_PROCED_REQ 3 +#define EV_USR_RELEASE_REQ 4 +#define EV_USR_REJECT_REQ 4 + +#define EV_NET_SETUP 16 +#define EV_NET_CALL_PROC 17 +#define EV_NET_SETUP_ACK 18 +#define EV_NET_CONN 19 +#define EV_NET_CONN_ACK 20 + +#define EV_NET_SELP_RESP 21 +#define EV_NET_ACTV_RESP 22 + +#define EV_NET_DISC 23 +#define EV_NET_RELEASE 24 +#define EV_NET_RELEASE_COMP 25 + +#define EV_TIMER 26 +#define EV_ERROR 32 + +/* + * Cause values + * only the ones we use + */ + +#define CAUSE_NORMAL 0x10U +#define CAUSE_NOCHAN 0x22U + +struct callb_data { + unsigned short type; + union { + struct ConnInfo { + char *CalledPN; + char *CallingPN; + } setup; + unsigned short cause; + } data; +}; + +struct fsm_entry { + unsigned short init; + unsigned short final; + unsigned short event; + void (*callb)(struct pcbit_dev *, struct pcbit_chan *, struct callb_data*); +}; + +struct fsm_timer_entry { + unsigned short init; + unsigned long timeout; /* in seconds */ +}; + + +extern void pcbit_fsm_event(struct pcbit_dev *, struct pcbit_chan *, + unsigned short event, struct callb_data *); +#endif + + + diff --git a/drivers/isdn/pcbit/layer2.c b/drivers/isdn/pcbit/layer2.c new file mode 100644 index 000000000..4410d1e0c --- /dev/null +++ b/drivers/isdn/pcbit/layer2.c @@ -0,0 +1,808 @@ +/* + * Copyright (C) 1996 Universidade de Lisboa + * + * Written by Pedro Roque Marques (roque@di.fc.ul.pt) + * + * This software may be used and distributed according to the terms of + * the GNU Public License, incorporated herein by reference. + */ + +/* + * PCBIT-D low-layer interface + */ + +/* + * Based on documentation provided by Inesc: + * - "Interface com bus do PC para o PCBIT e PCBIT-D", Inesc, Jan 93 + */ + +/* + * TODO: better handling of errors + * re-write/remove debug printks + */ + +#define __NO_VERSION__ + + +#ifdef MODULE +#define INCLUDE_INLINE_FUNCS +#endif + + +#include <linux/module.h> + +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/malloc.h> +#include <linux/interrupt.h> +#include <linux/tqueue.h> +#include <linux/mm.h> +#include <linux/skbuff.h> + +#include <linux/isdnif.h> + +#include <asm/system.h> +#include <asm/io.h> + + +#include "pcbit.h" +#include "layer2.h" +#include "edss1.h" + +#undef DEBUG_FRAG + + + +/* + * task queue struct + */ + + + +/* + * Layer 3 packet demultiplexer + * drv.c + */ + +extern void pcbit_l3_receive(struct pcbit_dev * dev, ulong msg, + struct sk_buff * skb, + ushort hdr_len, ushort refnum); + +/* + * Prototypes + */ + +void pcbit_deliver(void * data); +static void pcbit_transmit(struct pcbit_dev * dev); + +static void pcbit_recv_ack(struct pcbit_dev *dev, unsigned char ack); + +static void pcbit_l2_error(struct pcbit_dev *dev); +static void pcbit_l2_active_conf(struct pcbit_dev *dev, u_char info); +static void pcbit_l2_err_recover(unsigned long data); + +static void pcbit_firmware_bug(struct pcbit_dev * dev); + +static __inline__ void pcbit_sched_delivery(struct pcbit_dev *dev) +{ + queue_task(&dev->qdelivery, &tq_immediate); + mark_bh(IMMEDIATE_BH); +} + + +/* + * Called from layer3 + */ + +int pcbit_l2_write(struct pcbit_dev * dev, ulong msg, ushort refnum, + struct sk_buff *skb, unsigned short hdr_len) + +{ + struct frame_buf * frame, * ptr; + unsigned long flags; + + if (dev->l2_state != L2_RUNNING && dev->l2_state != L2_LOADING) { + dev_kfree_skb(skb, FREE_WRITE); + return -1; + } + + if ( (frame = (struct frame_buf *) kmalloc(sizeof(struct frame_buf), + GFP_ATOMIC)) == NULL ) { + printk(KERN_WARNING "pcbit_2_write: kmalloc failed\n"); + dev_kfree_skb(skb, FREE_WRITE); + return -1; + } + + frame->msg = msg; + frame->refnum = refnum; + frame->copied = 0; + frame->hdr_len = hdr_len; + + if (skb) { + frame->dt_len = skb->len - hdr_len; + if (frame->dt_len == 0) + skb->lock++; + } + else + frame->dt_len = 0; + + frame->skb = skb; + + frame->next = NULL; + + save_flags(flags); + cli(); + + if (dev->write_queue == NULL) { + dev->write_queue = frame; + restore_flags(flags); + pcbit_transmit(dev); + } + else { + for(ptr=dev->write_queue; ptr->next; ptr=ptr->next); + ptr->next = frame; + + restore_flags(flags); + } + return 0; +} + +static __inline__ void pcbit_tx_update(struct pcbit_dev *dev, ushort len) +{ + u_char info; + + dev->send_seq = (dev->send_seq + 1) % 8; + + dev->fsize[dev->send_seq] = len; + info = 0; + info |= dev->rcv_seq << 3; + info |= dev->send_seq; + + writeb(info, dev->sh_mem + BANK4); + +} + +/* + * called by interrupt service routine or by write_2 + */ + +static void pcbit_transmit(struct pcbit_dev * dev) +{ + struct frame_buf * frame = NULL; + unsigned char unacked; + int flen; /* fragment frame length including all headers */ + int totlen; /* non-fragmented frame length */ + int free; + int count, cp_len; + unsigned long flags; + unsigned short tt; + + if (dev->l2_state != L2_RUNNING && dev->l2_state != L2_LOADING) + return; + + unacked = (dev->send_seq + (8 - dev->unack_seq) ) & 0x07; + + save_flags(flags); + cli(); + + if (dev->free > 16 && dev->write_queue && unacked < 7) { + + if (!dev->w_busy) + dev->w_busy = 1; + else + { + restore_flags(flags); + return; + } + + + frame = dev->write_queue; + free = dev->free; + + restore_flags(flags); + + if (frame->copied == 0) { + + /* Type 0 frame */ + + struct msg_fmt * msg; + + if (frame->skb) + totlen = FRAME_HDR_LEN + PREHDR_LEN + frame->skb->len; + else + totlen = FRAME_HDR_LEN + PREHDR_LEN; + + flen = MIN(totlen, free); + + msg = (struct msg_fmt *) &(frame->msg); + + /* + * Board level 2 header + */ + + pcbit_writew(dev, flen - FRAME_HDR_LEN); + + pcbit_writeb(dev, msg->cpu); + + pcbit_writeb(dev, msg->proc); + + /* TH */ + pcbit_writew(dev, frame->hdr_len + PREHDR_LEN); + + /* TD */ + pcbit_writew(dev, frame->dt_len); + + + /* + * Board level 3 fixed-header + */ + + /* LEN = TH */ + pcbit_writew(dev, frame->hdr_len + PREHDR_LEN); + + /* XX */ + pcbit_writew(dev, 0); + + /* C + S */ + pcbit_writeb(dev, msg->cmd); + pcbit_writeb(dev, msg->scmd); + + /* NUM */ + pcbit_writew(dev, frame->refnum); + + count = FRAME_HDR_LEN + PREHDR_LEN; + } + else { + /* Type 1 frame */ + + totlen = 2 + (frame->skb->len - frame->copied); + + flen = MIN(totlen, free); + + /* TT */ + tt = ((ushort) (flen - 2)) | 0x8000U; /* Type 1 */ + pcbit_writew(dev, tt); + + count = 2; + } + + if (frame->skb) { + cp_len = MIN(frame->skb->len - frame->copied, + flen - count); + + memcpy_topcbit(dev, frame->skb->data + frame->copied, + cp_len); + frame->copied += cp_len; + } + + /* bookkeeping */ + dev->free -= flen; + pcbit_tx_update(dev, flen); + + save_flags(flags); + cli(); + + + if (frame->skb == NULL || frame->copied == frame->skb->len) { + + dev->write_queue = frame->next; + + if (frame->skb != NULL) { + /* free frame */ + dev_kfree_skb(frame->skb, FREE_WRITE); + } + + kfree(frame); + } + + dev->w_busy = 0; + restore_flags(flags); + } + else + { + restore_flags(flags); +#ifdef DEBUG + printk(KERN_DEBUG "unacked %d free %d write_queue %s\n", + unacked, dev->free, dev->write_queue ? "not empty" : + "empty"); +#endif + } +} + + +/* + * deliver a queued frame to the upper layer + */ + +void pcbit_deliver(void * data) +{ + struct frame_buf *frame; + unsigned long flags; + struct msg_fmt msg; + struct pcbit_dev *dev = (struct pcbit_dev *) data; + + save_flags(flags); + cli(); + + while((frame=dev->read_queue)) + { + dev->read_queue = frame->next; + restore_flags(flags); + + msg.cpu = 0; + msg.proc = 0; + msg.cmd = frame->skb->data[2]; + msg.scmd = frame->skb->data[3]; + + frame->refnum = *((ushort*) frame->skb->data + 4); + frame->msg = *((ulong*) &msg); + + skb_pull(frame->skb, 6); + + pcbit_l3_receive(dev, frame->msg, frame->skb, frame->hdr_len, + frame->refnum); + + kfree(frame); + + save_flags(flags); + cli(); + } + + restore_flags(flags); +} + +/* + * Reads BANK 2 & Reassembles + */ + +static void pcbit_receive(struct pcbit_dev * dev) +{ + unsigned short tt; + u_char cpu, proc; + struct frame_buf * frame = NULL; + unsigned long flags; + u_char type1; + + if (dev->l2_state != L2_RUNNING && dev->l2_state != L2_LOADING) + return; + + tt = pcbit_readw(dev); + + if ((tt & 0x7fffU) > 511) { + printk(KERN_INFO "pcbit: invalid frame length -> TT=%04x\n", + tt); + pcbit_l2_error(dev); + return; + } + + if (!(tt & 0x8000U)) + { /* Type 0 */ + type1 = 0; + + if (dev->read_frame) { + printk(KERN_DEBUG "pcbit_receive: Type 0 frame and read_frame != NULL\n"); +#if 0 + pcbit_l2_error(dev); + return; +#else + /* discard previous queued frame */ + if (dev->read_frame->skb) { + dev->read_frame->skb->free = 1; + kfree_skb(dev->read_frame->skb, FREE_READ); + } + kfree(dev->read_frame); + dev->read_frame = NULL; +#endif + } + + frame = kmalloc(sizeof(struct frame_buf), GFP_ATOMIC); + + if (frame == NULL) { + printk(KERN_WARNING "kmalloc failed\n"); + return; + } + memset(frame, 0, sizeof(struct frame_buf)); + + cpu = pcbit_readb(dev); + proc = pcbit_readb(dev); + + + if (cpu != 0x06 && cpu != 0x02) + { + printk (KERN_DEBUG "pcbit: invalid cpu value\n"); + kfree(frame); + pcbit_l2_error(dev); + return; + } + + /* + * we discard cpu & proc on receiving + * but we read it to update the pointer + */ + + frame->hdr_len = pcbit_readw(dev); + frame->dt_len = pcbit_readw(dev); + + /* + * 0 sized packet + * I don't know if they are an error or not... + * But they are very frequent + * Not documented + */ + + if (frame->hdr_len == 0) { + kfree(frame); +#ifdef DEBUG + printk(KERN_DEBUG "0 sized frame\n"); +#endif + pcbit_firmware_bug(dev); + return; + } + + /* sanity check the length values */ + if (frame->hdr_len > 1024 || frame->dt_len > 2048) + { +#ifdef DEBUG + printk(KERN_DEBUG "length problem: "); + printk(KERN_DEBUG "TH=%04x TD=%04x\n", + frame->hdr_len, + frame->dt_len); +#endif + pcbit_l2_error(dev); + kfree(frame); + return; + } + + /* minimum frame read */ + + frame->skb = dev_alloc_skb(frame->hdr_len + frame->dt_len + + ((frame->hdr_len + 15) & ~15)); + + if (!frame->skb) { + printk(KERN_DEBUG "pcbit_receive: out of memory\n"); + kfree(frame); + return; + } + + /* 16 byte alignment for IP */ + if (frame->dt_len) + skb_reserve(frame->skb, (frame->hdr_len + 15) & ~15); + + } + else { + /* Type 1 */ + type1 = 1; + tt &= 0x7fffU; + + if (!(frame = dev->read_frame)) { + printk("Type 1 frame and no frame queued\n"); +#if 1 + /* usually after an error: toss frame */ + dev->readptr += tt; + if (dev->readptr > dev->sh_mem + BANK2 + BANKLEN) + dev->readptr -= BANKLEN; +#else + pcbit_l2_error(dev); +#endif + return; + + } + } + + memcpy_frompcbit(dev, skb_put(frame->skb, tt), tt); + + frame->copied += tt; + + if (frame->copied == frame->hdr_len + frame->dt_len) { + + save_flags(flags); + cli(); + + if (type1) { + dev->read_frame = NULL; + } + + if (dev->read_queue) { + struct frame_buf *ptr; + for(ptr=dev->read_queue;ptr->next;ptr=ptr->next); + ptr->next = frame; + } + else + dev->read_queue = frame; + + restore_flags(flags); + + } + else { + save_flags(flags); + cli(); + dev->read_frame = frame; + restore_flags(flags); + } +} + +/* + * The board sends 0 sized frames + * They are TDATA_CONFs that get messed up somehow + * gotta send a fake acknowledgment to the upper layer somehow + */ + +static __inline__ void pcbit_fake_conf(struct pcbit_dev *dev, struct pcbit_chan * chan) +{ + isdn_ctrl ictl; + + if (chan->queued) { + chan->queued--; + + ictl.driver = dev->id; + ictl.command = ISDN_STAT_BSENT; + ictl.arg = chan->id; + dev->dev_if->statcallb(&ictl); + } +} + +static void pcbit_firmware_bug(struct pcbit_dev * dev) +{ + struct pcbit_chan *chan; + + chan = dev->b1; + + if (chan->fsm_state == ST_ACTIVE) { + pcbit_fake_conf(dev, chan); + } + + chan = dev->b2; + + if (chan->fsm_state == ST_ACTIVE) { + pcbit_fake_conf(dev, chan); + } + +} + +void pcbit_irq_handler(int interrupt, void * devptr, struct pt_regs *regs) +{ + struct pcbit_dev * dev; + u_char info, ack_seq, read_seq; + + dev = (struct pcbit_dev *) devptr; + + if (!dev) + { + printk(KERN_WARNING "pcbit_irq_handler: wrong device\n"); + return; + } + + if (dev->interrupt) { + printk(KERN_DEBUG "pcbit: reentering interrupt hander\n"); + return; + } + + dev->interrupt = 1; + + info = readb(dev->sh_mem + BANK3); + + if (dev->l2_state == L2_STARTING || dev->l2_state == L2_ERROR) + { + pcbit_l2_active_conf(dev, info); + dev->interrupt = 0; + return; + } + + if (info & 0x40U) /* E bit set */ + { +#ifdef DEBUG + printk(KERN_DEBUG "pcbit_irq_handler: E bit on\n"); +#endif + pcbit_l2_error(dev); + dev->interrupt = 0; + return; + } + + if (dev->l2_state != L2_RUNNING && dev->l2_state != L2_LOADING) + { + dev->interrupt = 0; + return; + } + + ack_seq = (info >> 3) & 0x07U; + read_seq = (info & 0x07U); + + dev->interrupt = 0; + + if (read_seq != dev->rcv_seq) + { + while (read_seq != dev->rcv_seq) + { + pcbit_receive(dev); + dev->rcv_seq = (dev->rcv_seq + 1) % 8; + } + pcbit_sched_delivery(dev); + } + + if (ack_seq != dev->unack_seq) + { + pcbit_recv_ack(dev, ack_seq); + } + + + info = dev->rcv_seq << 3; + info |= dev->send_seq; + + writeb(info, dev->sh_mem + BANK4); +} + + +static void pcbit_l2_active_conf(struct pcbit_dev *dev, u_char info) +{ + u_char state; + + state = dev->l2_state; + +#ifdef DEBUG + printk(KERN_DEBUG "layer2_active_confirm\n"); +#endif + + + if (info & 0x80U ) { + dev->rcv_seq = info & 0x07U; + dev->l2_state = L2_RUNNING; + } + else + dev->l2_state = L2_DOWN; + + if (state == L2_STARTING) + wake_up_interruptible(&dev->set_running_wq); + + if (state == L2_ERROR && dev->l2_state == L2_RUNNING) { + pcbit_transmit(dev); + } + +} + +static void pcbit_l2_err_recover(unsigned long data) +{ + + struct pcbit_dev * dev; + struct frame_buf *frame; + + dev = (struct pcbit_dev *) data; + + del_timer(&dev->error_recover_timer); + if (dev->w_busy || dev->r_busy) + { + init_timer(&dev->error_recover_timer); + dev->error_recover_timer.expires = jiffies + ERRTIME; + add_timer(&dev->error_recover_timer); + return; + } + + dev->w_busy = dev->r_busy = 1; + + if (dev->read_frame) + { + if (dev->read_frame->skb) + { + dev->read_frame->skb->free = 1; + kfree_skb(dev->read_frame->skb, FREE_READ); + } + kfree(dev->read_frame); + dev->read_frame = NULL; + } + + + if (dev->write_queue) + { + frame = dev->write_queue; +#ifdef FREE_ON_ERROR + dev->write_queue = dev->write_queue->next; + + if (frame->skb) { + dev_kfree_skb(frame->skb, FREE_WRITE); + } + + kfree(frame); +#else + frame->copied = 0; +#endif + } + + dev->rcv_seq = dev->send_seq = dev->unack_seq = 0; + dev->free = 511; + dev->l2_state = L2_ERROR; + + /* this is an hack... */ + pcbit_firmware_bug(dev); + + dev->writeptr = dev->sh_mem; + dev->readptr = dev->sh_mem + BANK2; + + writeb((0x80U | ((dev->rcv_seq & 0x07) << 3) | (dev->send_seq & 0x07)), + dev->sh_mem + BANK4); + dev->w_busy = dev->r_busy = 0; + +} + +static void pcbit_l2_error(struct pcbit_dev *dev) +{ + if (dev->l2_state == L2_RUNNING) { + + printk(KERN_INFO "pcbit: layer 2 error\n"); + +#ifdef DEBUG + log_state(dev); +#endif + + dev->l2_state = L2_DOWN; + + init_timer(&dev->error_recover_timer); + dev->error_recover_timer.function = &pcbit_l2_err_recover; + dev->error_recover_timer.data = (ulong) dev; + dev->error_recover_timer.expires = jiffies + ERRTIME; + add_timer(&dev->error_recover_timer); + } +} + +/* + * Description: + * if board acks frames + * update dev->free + * call pcbit_transmit to write possible queued frames + */ + +static void pcbit_recv_ack(struct pcbit_dev *dev, unsigned char ack) +{ + int i, count; + int unacked; + + unacked = (dev->send_seq + (8 - dev->unack_seq) ) & 0x07; + + /* dev->unack_seq < ack <= dev->send_seq; */ + + if (unacked) + { + + if (dev->send_seq > dev->unack_seq) + if (ack <= dev->unack_seq || ack > dev->send_seq) + { + printk(KERN_DEBUG + "layer 2 ack unacceptable - dev %d", + dev->id); + + pcbit_l2_error(dev); + } + else + if (ack > dev->send_seq && ack <= dev->unack_seq) + { + printk(KERN_DEBUG + "layer 2 ack unacceptable - dev %d", + dev->id); + pcbit_l2_error(dev); + } + + /* ack is acceptable */ + + + i = dev->unack_seq; + + do { + dev->unack_seq = i = (i + 1) % 8; + dev->free += dev->fsize[i]; + } while (i != ack); + + count = 0; + while (count < 7 && dev->write_queue) + { + u8 lsend_seq = dev->send_seq; + + pcbit_transmit(dev); + + if (dev->send_seq == lsend_seq) + break; + count++; + } + } + else + printk(KERN_DEBUG "recv_ack: unacked = 0\n"); +} diff --git a/drivers/isdn/pcbit/layer2.h b/drivers/isdn/pcbit/layer2.h new file mode 100644 index 000000000..2f56a5844 --- /dev/null +++ b/drivers/isdn/pcbit/layer2.h @@ -0,0 +1,287 @@ +/* + * Copyright (C) 1996 Universidade de Lisboa + * + * Written by Pedro Roque Marques (roque@di.fc.ul.pt) + * + * This software may be used and distributed according to the terms of + * the GNU Public License, incorporated herein by reference. + */ + +/* + * PCBIT-D low-layer interface definitions + */ + +#ifndef LAYER2_H +#define LAYER2_H + +#include <asm/byteorder.h> + +#define BANK1 0x0000U /* PC -> Board */ +#define BANK2 0x01ffU /* Board -> PC */ +#define BANK3 0x03feU /* Att Board */ +#define BANK4 0x03ffU /* Att PC */ + +#define BANKLEN 0x01FFU + +#define LOAD_ZONE_START 0x03f8U +#define LOAD_ZONE_END 0x03fdU + +#define LOAD_RETRY 18000000 + + + +/* TAM - XX - C - S - NUM */ +#define PREHDR_LEN 8 +/* TT - M - I - TH - TD */ +#define FRAME_HDR_LEN 8 + +#define MSG_CONN_REQ 0x08000100 +#define MSG_CONN_CONF 0x00000101 +#define MSG_CONN_IND 0x00000102 +#define MSG_CONN_RESP 0x08000103 + +#define MSG_CONN_ACTV_REQ 0x08000300 +#define MSG_CONN_ACTV_CONF 0x00000301 +#define MSG_CONN_ACTV_IND 0x00000302 +#define MSG_CONN_ACTV_RESP 0x08000303 + +#define MSG_DISC_REQ 0x08000400 +#define MSG_DISC_CONF 0x00000401 +#define MSG_DISC_IND 0x00000402 +#define MSG_DISC_RESP 0x08000403 + +#define MSG_TDATA_REQ 0x0908E200 +#define MSG_TDATA_CONF 0x0000E201 +#define MSG_TDATA_IND 0x0000E202 +#define MSG_TDATA_RESP 0x0908E203 + +#define MSG_SELP_REQ 0x09004000 +#define MSG_SELP_CONF 0x00004001 + +#define MSG_ACT_TRANSP_REQ 0x0908E000 +#define MSG_ACT_TRANSP_CONF 0x0000E001 + +#define MSG_STPROT_REQ 0x09004100 +#define MSG_STPROT_CONF 0x00004101 + +#define MSG_PING188_REQ 0x09030500 +#define MSG_PING188_CONF 0x000005bc + +#define MSG_WATCH188 0x09030400 + +#define MSG_API_ON 0x08020102 +#define MSG_POOL_PCBIT 0x08020400 +#define MSG_POOL_PCBIT_CONF 0x00000401 + +#define MSG_INFO_IND 0x00002602 +#define MSG_INFO_RESP 0x08002603 + +#define MSG_DEBUG_188 0x0000ff00 + +/* + + long 4 3 2 1 + Intel 1 2 3 4 +*/ + +struct msg_fmt { +#ifdef __LITTLE_ENDIAN /* Little Endian */ + u_char scmd; + u_char cmd; + u_char proc; + u_char cpu; +#else +#error "Non-Intel CPU" + u_char cpu; + u_char proc; + u_char cmd; + u_char scmd; +#endif +}; + + +#define MAX_QUEUED 7 + +#define SCHED_READ 0x01 +#define SCHED_WRITE 0x02 + +#define SET_RUN_TIMEOUT 2*HZ /* 2 seconds */ + + +struct frame_buf { + ulong msg; + unsigned short refnum; + unsigned short dt_len; + unsigned short hdr_len; + struct sk_buff *skb; + unsigned short copied; + struct frame_buf * next; +}; + +#define MIN(a,b) ((a<b)?a:b) + +extern int pcbit_l2_write(struct pcbit_dev * dev, ulong msg, ushort refnum, + struct sk_buff *skb, unsigned short hdr_len); + +extern void pcbit_irq_handler(int interrupt, void *, struct pt_regs *regs); + +extern struct pcbit_dev * dev_pcbit[MAX_PCBIT_CARDS]; + +#ifdef DEBUG +static __inline__ void log_state(struct pcbit_dev *dev) { + printk(KERN_DEBUG "writeptr = %ld\n", + (ulong) (dev->writeptr - dev->sh_mem)); + printk(KERN_DEBUG "readptr = %ld\n", + (ulong) (dev->readptr - (dev->sh_mem + BANK2))); + printk(KERN_DEBUG "{rcv_seq=%01x, send_seq=%01x, unack_seq=%01x}\n", + dev->rcv_seq, dev->send_seq, dev->unack_seq); +} +#endif + +static __inline__ struct pcbit_dev * chan2dev(struct pcbit_chan * chan) +{ + struct pcbit_dev * dev; + int i; + + + for (i=0; i<MAX_PCBIT_CARDS; i++) + if ((dev=dev_pcbit[i])) + if (dev->b1 == chan || dev->b2 == chan) + return dev; + return NULL; + +} + +static __inline__ struct pcbit_dev * finddev(int id) +{ + struct pcbit_dev * dev; + int i; + + for (i=0; i<MAX_PCBIT_CARDS; i++) + if ((dev=dev_pcbit[i])) + if (dev->id == id) + return dev; + return NULL; +} + + +/* + * Support routines for reading and writing in the board + */ + +static __inline__ void pcbit_writeb(struct pcbit_dev *dev, unsigned char dt) +{ + writeb(dt, dev->writeptr++); + if (dev->writeptr == dev->sh_mem + BANKLEN) + dev->writeptr = dev->sh_mem; +} + +static __inline__ void pcbit_writew(struct pcbit_dev *dev, unsigned short dt) +{ + int dist; + + dist = BANKLEN - (dev->writeptr - dev->sh_mem); + switch (dist) { + case 2: + writew(dt, dev->writeptr); + dev->writeptr = dev->sh_mem; + break; + case 1: + writeb((u_char) (dt & 0x00ffU), dev->writeptr); + dev->writeptr = dev->sh_mem; + writeb((u_char) (dt >> 8), dev->writeptr++); + break; + default: + writew(dt, dev->writeptr); + dev->writeptr += 2; + break; + }; +} + +static __inline__ void memcpy_topcbit(struct pcbit_dev * dev, u_char * data, + int len) +{ + int diff; + + diff = len - (BANKLEN - (dev->writeptr - dev->sh_mem) ); + + if (diff > 0) + { + memcpy_toio(dev->writeptr, data, len - diff); + memcpy_toio(dev->sh_mem, data + (len - diff), diff); + dev->writeptr = dev->sh_mem + diff; + } + else + { + memcpy_toio(dev->writeptr, data, len); + + dev->writeptr += len; + if (diff == 0) + dev->writeptr = dev->sh_mem; + } +} + +static __inline__ unsigned char pcbit_readb(struct pcbit_dev *dev) +{ + unsigned char val; + + val = readb(dev->readptr++); + if (dev->readptr == dev->sh_mem + BANK2 + BANKLEN) + dev->readptr = dev->sh_mem + BANK2; + + return val; +} + +static __inline__ unsigned short pcbit_readw(struct pcbit_dev *dev) +{ + int dist; + unsigned short val; + + dist = BANKLEN - ( dev->readptr - (dev->sh_mem + BANK2 ) ); + switch (dist) { + case 2: + val = readw(dev->readptr); + dev->readptr = dev->sh_mem + BANK2; + break; + case 1: + val = readb(dev->readptr); + dev->readptr = dev->sh_mem + BANK2; + val = (readb(dev->readptr++) << 8) | val; + break; + default: + val = readw(dev->readptr); + dev->readptr += 2; + break; + }; + return val; +} + +static __inline__ void memcpy_frompcbit(struct pcbit_dev * dev, u_char * data, int len) +{ + int diff; + + diff = len - (BANKLEN - (dev->readptr - (dev->sh_mem + BANK2) ) ); + if (diff > 0) + { + memcpy_fromio(data, dev->readptr, len - diff); + memcpy_fromio(data + (len - diff), dev->sh_mem + BANK2 , diff); + dev->readptr = dev->sh_mem + BANK2 + diff; + } + else + { + memcpy_fromio(data, dev->readptr, len); + dev->readptr += len; + if (diff == 0) + dev->readptr = dev->sh_mem + BANK2; + } +} + + +#endif + + + + + + + diff --git a/drivers/isdn/pcbit/module.c b/drivers/isdn/pcbit/module.c new file mode 100644 index 000000000..2f2aa374f --- /dev/null +++ b/drivers/isdn/pcbit/module.c @@ -0,0 +1,129 @@ +/* + * Copyright (C) 1996 Universidade de Lisboa + * + * Written by Pedro Roque Marques (roque@di.fc.ul.pt) + * + * This software may be used and distributed according to the terms of + * the GNU Public License, incorporated herein by reference. + */ + +/* + * PCBIT-D module support + */ + +#include <linux/module.h> + +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/kernel.h> +#include <linux/tqueue.h> +#include <linux/skbuff.h> + +#include <linux/isdnif.h> +#include "pcbit.h" + +int mem[MAX_PCBIT_CARDS] = {0, }; +int irq[MAX_PCBIT_CARDS] = {0, }; + +int num_boards; +struct pcbit_dev * dev_pcbit[MAX_PCBIT_CARDS] = {0, 0, 0, 0}; + +int init_module(void); +void cleanup_module(void); + +extern void pcbit_terminate(int board); +extern int pcbit_init_dev(int board, int mem_base, int irq); + +#ifdef MODULE +#define pcbit_init init_module +#endif + +int pcbit_init(void) +{ + int board; + + num_boards = 0; + + printk(KERN_INFO + "PCBIT-D device driver v 0.5 - " + "Copyright (C) 1996 Universidade de Lisboa\n"); + + if (mem[0] || irq[0]) + { + for (board=0; board < MAX_PCBIT_CARDS && mem[board] && irq[board]; board++) + { + if (!mem[board]) + mem[board] = 0xD0000; + if (!irq[board]) + irq[board] = 5; + + if (pcbit_init_dev(board, mem[board], irq[board]) == 0) + num_boards++; + + else + { + printk(KERN_WARNING + "pcbit_init failed for dev %d", + board + 1); + return -EIO; + } + } + } + + /* Hardcoded default settings detection */ + + if (!num_boards) + { + printk(KERN_INFO + "Trying to detect board using default settings\n"); + if (pcbit_init_dev(0, 0xD0000, 5) == 0) + num_boards++; + else + return -EIO; + } + + /* No symbols to export, hide all symbols */ + register_symtab(NULL); + + return 0; +} + +#ifdef MODULE +void cleanup_module(void) +{ + int board; + + for (board = 0; board < num_boards; board++) + pcbit_terminate(board); + printk(KERN_INFO + "PCBIT-D module unloaded\n"); +} + +#else +void pcbit_setup(char *str, int *ints) +{ + int i, j, argc; + + argc = ints[0]; + i = 0; + j = 1; + + while (argc && (i<MAX_PCBIT_CARDS)) { + + if (argc) { + mem[i] = ints[j]; + j++; argc--; + } + + if (argc) { + irq[i] = ints[j]; + j++; argc--; + } + + i++; + } +} +#endif + + + diff --git a/drivers/isdn/pcbit/pcbit.h b/drivers/isdn/pcbit/pcbit.h new file mode 100644 index 000000000..89a608904 --- /dev/null +++ b/drivers/isdn/pcbit/pcbit.h @@ -0,0 +1,175 @@ +/* + * Copyright (C) 1996 Universidade de Lisboa + * + * Written by Pedro Roque Marques (roque@di.fc.ul.pt) + * + * This software may be used and distributed according to the terms of + * the GNU Public License, incorporated herein by reference. + */ + +/* + * PCBIT-D device driver definitions + */ + +#ifndef PCBIT_H +#define PCBIT_H + +#define MAX_PCBIT_CARDS 4 + + +#define BLOCK_TIMER + +#ifdef __KERNEL__ + +struct pcbit_chan { + unsigned short id; + unsigned short callref; /* Call Reference */ + unsigned char proto; /* layer2protocol */ + unsigned char queued; /* unacked data messages */ + unsigned char layer2link; /* used in TData */ + unsigned char snum; /* used in TData */ + unsigned short s_refnum; + unsigned short r_refnum; + unsigned short fsm_state; + struct timer_list fsm_timer; +#ifdef BLOCK_TIMER + struct timer_list block_timer; +#endif +}; + +struct msn_entry { + char *msn; + struct msn_entry * next; +}; + +struct pcbit_dev { + /* board */ + + volatile unsigned char* sh_mem; /* RDP address */ + unsigned int irq; + unsigned int id; + unsigned int interrupt; /* set during interrupt + processing */ + + /* isdn4linux */ + + struct msn_entry * msn_list; /* ISDN address list */ + + isdn_if * dev_if; + + ushort ll_hdrlen; + ushort hl_hdrlen; + + /* link layer */ + unsigned char l2_state; + + struct frame_buf *read_queue; + struct frame_buf *read_frame; + struct frame_buf *write_queue; + + /* Protocol start */ + struct wait_queue *set_running_wq; + struct timer_list set_running_timer; + + struct timer_list error_recover_timer; + + struct tq_struct qdelivery; + + u_char w_busy; + u_char r_busy; + + volatile unsigned char *readptr; + volatile unsigned char *writeptr; + + ushort loadptr; + + unsigned short fsize[8]; /* sent layer2 frames size */ + + unsigned char send_seq; + unsigned char rcv_seq; + unsigned char unack_seq; + + unsigned short free; + + /* channels */ + + struct pcbit_chan *b1; + struct pcbit_chan *b2; +}; + +#define STATS_TIMER (10*HZ) +#define ERRTIME (0.1*HZ) + +/* MRU */ +#define MAXBUFSIZE 1534 +#define MRU MAXBUFSIZE + +#define STATBUF_LEN 2048 +/* + * + */ + +#endif /* __KERNEL__ */ + +/* isdn_ctrl only allows a long sized argument */ + +struct pcbit_ioctl { + union { + struct byte_op { + ushort addr; + ushort value; + } rdp_byte; + unsigned long l2_status; + } info; +}; + + + +#define PCBIT_IOCTL_GETSTAT 0x01 /* layer2 status */ +#define PCBIT_IOCTL_LWMODE 0x02 /* linear write mode */ +#define PCBIT_IOCTL_STRLOAD 0x03 /* start load mode */ +#define PCBIT_IOCTL_ENDLOAD 0x04 /* end load mode */ +#define PCBIT_IOCTL_SETBYTE 0x05 /* set byte */ +#define PCBIT_IOCTL_GETBYTE 0x06 /* get byte */ +#define PCBIT_IOCTL_RUNNING 0x07 /* set protocol running */ +#define PCBIT_IOCTL_WATCH188 0x08 /* set watch 188 */ +#define PCBIT_IOCTL_PING188 0x09 /* ping 188 */ +#define PCBIT_IOCTL_FWMODE 0x0A /* firmware write mode */ +#define PCBIT_IOCTL_STOP 0x0B /* stop protocol */ +#define PCBIT_IOCTL_APION 0x0C /* issue API_ON */ + +#ifndef __KERNEL__ + +#define PCBIT_GETSTAT (PCBIT_IOCTL_GETSTAT + IIOCDRVCTL) +#define PCBIT_LWMODE (PCBIT_IOCTL_LWMODE + IIOCDRVCTL) +#define PCBIT_STRLOAD (PCBIT_IOCTL_STRLOAD + IIOCDRVCTL) +#define PCBIT_ENDLOAD (PCBIT_IOCTL_ENDLOAD + IIOCDRVCTL) +#define PCBIT_SETBYTE (PCBIT_IOCTL_SETBYTE + IIOCDRVCTL) +#define PCBIT_GETBYTE (PCBIT_IOCTL_GETBYTE + IIOCDRVCTL) +#define PCBIT_RUNNING (PCBIT_IOCTL_RUNNING + IIOCDRVCTL) +#define PCBIT_WATCH188 (PCBIT_IOCTL_WATCH188 + IIOCDRVCTL) +#define PCBIT_PING188 (PCBIT_IOCTL_PING188 + IIOCDRVCTL) +#define PCBIT_FWMODE (PCBIT_IOCTL_FWMODE + IIOCDRVCTL) +#define PCBIT_STOP (PCBIT_IOCTL_STOP + IIOCDRVCTL) +#define PCBIT_APION (PCBIT_IOCTL_APION + IIOCDRVCTL) + +#define MAXSUPERLINE 3000 + +#endif + +#define L2_DOWN 0 +#define L2_LOADING 1 +#define L2_LWMODE 2 +#define L2_FWMODE 3 +#define L2_STARTING 4 +#define L2_RUNNING 5 +#define L2_ERROR 6 + +#endif + + + + + + + diff --git a/drivers/isdn/teles/Makefile b/drivers/isdn/teles/Makefile new file mode 100644 index 000000000..a252f46ba --- /dev/null +++ b/drivers/isdn/teles/Makefile @@ -0,0 +1,17 @@ +L_OBJS := +M_OBJS := +O_OBJS := mod.o card.o config.o buffers.o tei.o isdnl2.o isdnl3.o \ +llglue.o q931.o callc.o fsm.o + +O_TARGET := +ifeq ($(CONFIG_ISDN_DRV_TELES),y) + O_TARGET += teles.o +else + ifeq ($(CONFIG_ISDN_DRV_TELES),m) + O_TARGET += teles.o + M_OBJS += teles.o + endif +endif + +include $(TOPDIR)/Rules.make + diff --git a/drivers/isdn/teles/buffers.c b/drivers/isdn/teles/buffers.c new file mode 100644 index 000000000..b01a5ab06 --- /dev/null +++ b/drivers/isdn/teles/buffers.c @@ -0,0 +1,326 @@ +/* $Id: buffers.c,v 1.3 1996/05/31 00:56:53 fritz Exp $ + * + * $Log: buffers.c,v $ + * Revision 1.3 1996/05/31 00:56:53 fritz + * removed cli() from BufPoolAdd, since it is called + * with interrupts off anyway. + * + * Revision 1.2 1996/04/29 22:48:14 fritz + * Removed compatibility-macros. No longer needed. + * + * Revision 1.1 1996/04/13 10:19:28 fritz + * Initial revision + * + * + */ +#define __NO_VERSION__ +#include "teles.h" +#include <linux/mm.h> +#include <linux/malloc.h> + + +void +BufPoolInit(struct BufPool *bp, int order, int bpps, + int maxpages) +{ +#ifdef DEBUG_MAGIC + generateerror + bp->magic = 010167; +#endif + +#if 0 + printk(KERN_DEBUG "BufPoolInit bp %x\n", bp); +#endif + + bp->freelist = NULL; + bp->pageslist = NULL; + bp->pageorder = order; + bp->pagescount = 0; + bp->bpps = bpps; + bp->bufsize = BUFFER_SIZE(order, bpps); + bp->maxpages = maxpages; +} + +int +BufPoolAdd(struct BufPool *bp, int priority) +{ + struct Pages *ptr; + byte *bptr; + int i; + struct BufHeader *bh = NULL, *prev, *first; + +#if 0 + printk(KERN_DEBUG "BufPoolAdd bp %x\n", bp); +#endif + + ptr = (struct Pages *) __get_free_pages(priority, bp->pageorder, 0); + if (!ptr) { + printk(KERN_WARNING "BufPoolAdd couldn't get pages!\n"); + return (-1); + } +#if 0 + printk(KERN_DEBUG "Order %d pages allocated at %x\n", bp->pageorder, ptr); +#endif + + ptr->next = bp->pageslist; + bp->pageslist = ptr; + bp->pagescount++; + + bptr = (byte *) ptr + sizeof(struct Pages *); + + i = bp->bpps; + first = (struct BufHeader *) bptr; + prev = NULL; + while (i--) { + bh = (struct BufHeader *) bptr; +#ifdef DEBUG_MAGIC + bh->magic = 020167; +#endif + bh->next = prev; + prev = bh; + bh->bp = bp; + bptr += PART_SIZE(bp->pageorder, bp->bpps); + } + + first->next = bp->freelist; + bp->freelist = bh; + return (0); +} + +void +BufPoolFree(struct BufPool *bp) +{ + struct Pages *p; + +#if 0 + printk(KERN_DEBUG "BufPoolFree bp %x\n", bp); +#endif + + while (bp->pagescount--) { + p = bp->pageslist->next; + free_pages((unsigned long) bp->pageslist, bp->pageorder); +#if 0 + printk(KERN_DEBUG "Free pages %x order %d\n", bp->pageslist, bp->pageorder); +#endif + bp->pageslist = p; + } +} + +int +BufPoolGet(struct BufHeader **bh, + struct BufPool *bp, int priority, void *heldby, int where) +{ + long flags; + int i; + +#ifdef DEBUG_MAGIC + if (bp->magic != 010167) { + printk(KERN_DEBUG "BufPoolGet: not a BufHeader\n"); + return (-1); + } +#endif + + save_flags(flags); + cli(); + i = 0; + while (!0) { + if (bp->freelist) { + *bh = bp->freelist; + bp->freelist = bp->freelist->next; + (*bh)->heldby = heldby; + (*bh)->where = where; + restore_flags(flags); + return (0); + } + if ((i == 0) && (bp->pagescount < bp->maxpages)) { + if (BufPoolAdd(bp, priority)) { + restore_flags(flags); + return -1; + } + i++; + } else { + *bh = NULL; + restore_flags(flags); + return (-1); + } + } + +} + +void +BufPoolRelease(struct BufHeader *bh) +{ + struct BufPool *bp; + long flags; + +#ifdef DEBUG_MAGIC + if (bh->magic != 020167) { + printk(KERN_DEBUG "BufPoolRelease: not a BufHeader\n"); + printk(KERN_DEBUG "called from %x\n", return_address()); + return; + } +#endif + + bp = bh->bp; + +#ifdef DEBUG_MAGIC + if (bp->magic != 010167) { + printk(KERN_DEBUG "BufPoolRelease: not a BufPool\n"); + return; + } +#endif + + save_flags(flags); + cli(); + bh->next = bp->freelist; + bp->freelist = bh; + restore_flags(flags); +} + +void +BufQueueLink(struct BufQueue *bq, + struct BufHeader *bh) +{ + unsigned long flags; + + save_flags(flags); + cli(); + if (!bq->head) + bq->head = bh; + if (bq->tail) + bq->tail->next = bh; + bq->tail = bh; + bh->next = NULL; + restore_flags(flags); +} + +void +BufQueueLinkFront(struct BufQueue *bq, + struct BufHeader *bh) +{ + unsigned long flags; + + save_flags(flags); + cli(); + bh->next = bq->head; + bq->head = bh; + if (!bq->tail) + bq->tail = bh; + restore_flags(flags); +} + +int +BufQueueUnlink(struct BufHeader **bh, struct BufQueue *bq) +{ + long flags; + + save_flags(flags); + cli(); + + if (bq->head) { + if (bq->tail == bq->head) + bq->tail = NULL; + *bh = bq->head; + bq->head = (*bh)->next; + restore_flags(flags); + return (0); + } else { + restore_flags(flags); + return (-1); + } +} + +void +BufQueueInit(struct BufQueue *bq) +{ +#ifdef DEBUG_MAGIC + bq->magic = 030167; +#endif + bq->head = NULL; + bq->tail = NULL; +} + +void +BufQueueRelease(struct BufQueue *bq) +{ + struct BufHeader *bh; + + while (bq->head) { + BufQueueUnlink(&bh, bq); + BufPoolRelease(bh); + } +} + +int +BufQueueLength(struct BufQueue *bq) +{ + int i = 0; + struct BufHeader *bh; + + bh = bq->head; + while (bh) { + i++; + bh = bh->next; + } + return (i); +} + +void +BufQueueDiscard(struct BufQueue *q, int pr, void *heldby, + int releasetoo) +{ + long flags; + struct BufHeader *sp; + + save_flags(flags); + cli(); + + while (!0) { + sp = q->head; + if (!sp) + break; + if ((sp->primitive == pr) && (sp->heldby == heldby)) { + q->head = sp->next; + if (q->tail == sp) + q->tail = NULL; + if (releasetoo) + BufPoolRelease(sp); + } else + break; + } + + sp = q->head; + if (sp) + while (sp->next) { + if ((sp->next->primitive == pr) && (sp->next->heldby == heldby)) { + if (q->tail == sp->next) + q->tail = sp; + if (releasetoo) + BufPoolRelease(sp->next); + sp->next = sp->next->next; + } else + sp = sp->next; + } + restore_flags(flags); +} + +void +Sfree(byte * ptr) +{ +#if 0 + printk(KERN_DEBUG "Sfree %x\n", ptr); +#endif + kfree(ptr); +} + +byte * +Smalloc(int size, int pr, char *why) +{ + byte *p; + + p = (byte *) kmalloc(size, pr); +#if 0 + printk(KERN_DEBUG "Smalloc %s size %d res %x\n", why, size, p); +#endif + return (p); +} diff --git a/drivers/isdn/teles/callc.c b/drivers/isdn/teles/callc.c new file mode 100644 index 000000000..ead53cb36 --- /dev/null +++ b/drivers/isdn/teles/callc.c @@ -0,0 +1,1481 @@ +/* $Id: callc.c,v 1.13 1996/06/24 17:15:55 fritz Exp $ + * + * $Log: callc.c,v $ + * Revision 1.13 1996/06/24 17:15:55 fritz + * corrected return code of teles_writebuf() + * + * Revision 1.12 1996/06/12 16:15:33 fritz + * Extended user-configurable debugging flags. + * + * Revision 1.11 1996/06/07 12:32:20 fritz + * More changes to support suspend/resume. + * + * Revision 1.10 1996/06/06 21:24:21 fritz + * Started adding support for suspend/resume. + * + * Revision 1.9 1996/05/31 12:23:57 jdenoud + * Jan: added channel open check to teles_writebuf + * + * Revision 1.8 1996/05/31 01:00:38 fritz + * Changed return code of teles_writebuf, when out of memory. + * + * Revision 1.7 1996/05/17 03:40:37 fritz + * General cleanup. + * + * Revision 1.6 1996/05/10 22:42:07 fritz + * Added entry for EV_RELEASE_CNF in ST_OUT (if no D-Channel avail.) + * + * Revision 1.5 1996/05/06 10:16:15 fritz + * Added voice stuff. + * + * Revision 1.4 1996/04/30 22:04:05 isdn4dev + * improved callback Karsten Keil + * + * Revision 1.3 1996/04/30 10:04:19 fritz + * Started voice support. + * Added printk() to debug-switcher for easier + * synchronization between printk()'s and output + * of /dev/isdnctrl. + * + * Revision 1.2 1996/04/20 16:42:29 fritz + * Changed statemachine to allow reject of incoming calls. + * + * Revision 1.1 1996/04/13 10:20:59 fritz + * Initial revision + * + * + */ +#define __NO_VERSION__ +#include "teles.h" +#include <asm/uaccess.h> + +extern struct IsdnCard cards[]; +extern int nrcards; +extern int drid; +extern isdn_if iif; +extern void teles_mod_dec_use_count(void); +extern void teles_mod_inc_use_count(void); + +static int init_ds(int chan, int incoming); +static void release_ds(int chan); +static char *strcpyupto(char *dest, char *src, char upto); + +static struct Fsm callcfsm = +{NULL, 0, 0}, lcfsm = +{NULL, 0, 0}; + +struct Channel *chanlist; +static int chancount = 0; +unsigned int debugflags = 0; + +#define TMR_DCHAN_EST 2000 + +static void +stat_debug(struct Channel *chanp, char *s) +{ + char tmp[100], tm[32]; + + jiftime(tm, jiffies); + sprintf(tmp, "%s Channel %d HL->LL %s\n", tm, chanp->chan, s); + teles_putstatus(tmp); +} + +enum { + ST_NULL, /* 0 inactive */ + ST_OUT, /* 1 outgoing, awaiting SETUP confirm */ + ST_CLEAR, /* 2 call release, awaiting RELEASE confirm */ + ST_OUT_W, /* 3 outgoing, awaiting d-channel establishment */ + ST_REL_W, /* 4 awaiting d-channel release */ + ST_IN_W, /* 5 incoming, awaiting d-channel establishment */ + ST_IN, /* 6 incoming call received */ + ST_IN_SETUP, /* 7 incoming, SETUP response sent */ + ST_IN_DACT, /* 8 incoming connected, no b-channel prot. */ + ST_OUT_ESTB, /* 10 outgoing connected, awaiting b-channel prot. estbl. */ + ST_ACTIVE, /* 11 active, b channel prot. established */ + ST_BC_HANGUP, /* 12 call clear. (initiator), awaiting b channel prot. rel. */ + ST_PRO_W, /* 13 call clear. (initiator), DISCONNECT req. sent */ + ST_ANT_W, /* 14 call clear. (receiver), awaiting DISCONNECT ind. */ + ST_DISC_BC_HANGUP, /* d channel gone, wait for b channel deactivation */ + ST_OUT_W_HANGUP, /* Outgoing waiting for D-Channel hangup received */ + ST_D_ERR, /* d channel released while active */ +}; + +#define STATE_COUNT (ST_D_ERR+1) + +static char *strState[] = +{ + "ST_NULL", + "ST_OUT", + "ST_CLEAR", + "ST_OUT_W", + "ST_REL_W", + "ST_IN_W", + "ST_IN", + "ST_IN_SETUP", + "ST_IN_DACT", + "ST_OUT_ESTB", + "ST_ACTIVE", + "ST_BC_HANGUP", + "ST_PRO_W", + "ST_ANT_W", + "ST_DISC_BC_HANGUP", + "ST_OUT_W_HANGUP", + "ST_D_ERR", +}; + +enum { + EV_DIAL, /* 0 */ + EV_SETUP_CNF, /* 1 */ + EV_ACCEPTB, /* 2 */ + EV_DISCONNECT_CNF, /* 5 */ + EV_DISCONNECT_IND, /* 6 */ + EV_RELEASE_CNF, /* 7 */ + EV_DLEST, /* 8 */ + EV_DLRL, /* 9 */ + EV_SETUP_IND, /* 10 */ + EV_RELEASE_IND, /* 11 */ + EV_ACCEPTD, /* 12 */ + EV_SETUP_CMPL_IND, /* 13 */ + EV_BC_EST, /* 14 */ + EV_WRITEBUF, /* 15 */ + EV_DATAIN, /* 16 */ + EV_HANGUP, /* 17 */ + EV_BC_REL, /* 18 */ + EV_CINF, /* 19 */ + EV_SUSPEND, /* 20 */ + EV_RESUME, /* 21 */ +}; + +#define EVENT_COUNT (EV_CINF+1) + +static char *strEvent[] = +{ + "EV_DIAL", + "EV_SETUP_CNF", + "EV_ACCEPTB", + "EV_DISCONNECT_CNF", + "EV_DISCONNECT_IND", + "EV_RELEASE_CNF", + "EV_DLEST", + "EV_DLRL", + "EV_SETUP_IND", + "EV_RELEASE_IND", + "EV_ACCEPTD", + "EV_SETUP_CMPL_IND", + "EV_BC_EST", + "EV_WRITEBUF", + "EV_DATAIN", + "EV_HANGUP", + "EV_BC_REL", + "EV_CINF", + "EV_SUSPEND", + "EV_RESUME", +}; + +enum { + ST_LC_NULL, + ST_LC_ACTIVATE_WAIT, + ST_LC_DELAY, + ST_LC_ESTABLISH_WAIT, + ST_LC_CONNECTED, + ST_LC_RELEASE_WAIT, +}; + +#define LC_STATE_COUNT (ST_LC_RELEASE_WAIT+1) + +static char *strLcState[] = +{ + "ST_LC_NULL", + "ST_LC_ACTIVATE_WAIT", + "ST_LC_DELAY", + "ST_LC_ESTABLISH_WAIT", + "ST_LC_CONNECTED", + "ST_LC_RELEASE_WAIT", +}; + +enum { + EV_LC_ESTABLISH, + EV_LC_PH_ACTIVATE, + EV_LC_PH_DEACTIVATE, + EV_LC_DL_ESTABLISH, + EV_LC_TIMER, + EV_LC_DL_RELEASE, + EV_LC_RELEASE, +}; + +#define LC_EVENT_COUNT (EV_LC_RELEASE+1) + +static char *strLcEvent[] = +{ + "EV_LC_ESTABLISH", + "EV_LC_PH_ACTIVATE", + "EV_LC_PH_DEACTIVATE", + "EV_LC_DL_ESTABLISH", + "EV_LC_TIMER", + "EV_LC_DL_RELEASE", + "EV_LC_RELEASE", +}; + +#define LC_D 0 +#define LC_B 1 + +static int +my_atoi(char *s) +{ + int i, n; + + n = 0; + if (!s) + return -1; + for (i = 0; *s >= '0' && *s <= '9'; i++, s++) + n = 10 * n + (*s - '0'); + return n; +} + +/* + * Dial out + */ +static void +r1(struct FsmInst *fi, int event, void *arg) +{ + isdn_ctrl *ic = arg; + struct Channel *chanp = fi->userdata; + char *ptr; + char sis[3]; + + /* Destination Phone-Number */ + ptr = strcpyupto(chanp->para.called, ic->num, ','); + /* Source Phone-Number */ + ptr = strcpyupto(chanp->para.calling, ptr + 1, ','); + if (!strcmp(chanp->para.calling, "0")) + chanp->para.calling[0] = '\0'; + + /* Service-Indicator 1 */ + ptr = strcpyupto(sis, ptr + 1, ','); + chanp->para.info = my_atoi(sis); + + /* Service-Indicator 2 */ + ptr = strcpyupto(sis, ptr + 1, '\0'); + chanp->para.info2 = my_atoi(sis); + + chanp->l2_active_protocol = chanp->l2_protocol; + chanp->incoming = 0; + chanp->lc_b.l2_start = !0; + + switch (chanp->l2_active_protocol) { + case (ISDN_PROTO_L2_X75I): + chanp->lc_b.l2_establish = !0; + break; + case (ISDN_PROTO_L2_HDLC): + case (ISDN_PROTO_L2_TRANS): + chanp->lc_b.l2_establish = 0; + break; + default: + printk(KERN_WARNING "r1 unknown protocol\n"); + break; + } + + FsmChangeState(fi, ST_OUT_W); + FsmEvent(&chanp->lc_d.lcfi, EV_LC_ESTABLISH, NULL); +} + +static void +ll_hangup(struct Channel *chanp, int bchantoo) +{ + isdn_ctrl ic; + + if (bchantoo) { + if (chanp->debug & 1) + stat_debug(chanp, "STAT_BHUP"); + ic.driver = drid; + ic.command = ISDN_STAT_BHUP; + ic.arg = chanp->chan; + iif.statcallb(&ic); + } + if (chanp->debug & 1) + stat_debug(chanp, "STAT_DHUP"); + ic.driver = drid; + ic.command = ISDN_STAT_DHUP; + ic.arg = chanp->chan; + iif.statcallb(&ic); +} + +static void +r2(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + chanp->is.l4.l4l3(&chanp->is, CC_RELEASE_REQ, NULL); + + FsmChangeState(fi, ST_CLEAR); + ll_hangup(chanp, 0); +} + + +static void +r2_1(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + chanp->is.l4.l4l3(&chanp->is, CC_DISCONNECT_REQ, NULL); + + FsmChangeState(fi, ST_OUT_W_HANGUP); +} + + +static void +r2_2(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + FsmChangeState(fi, ST_REL_W); + FsmEvent(&chanp->lc_d.lcfi, EV_LC_RELEASE, NULL); + ll_hangup(chanp, 0); +} + + +static void +r3(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + FsmEvent(&chanp->lc_d.lcfi, EV_LC_RELEASE, NULL); + FsmChangeState(fi, ST_REL_W); +} + + +static void +r3_1(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + chanp->is.l4.l4l3(&chanp->is,CC_DLRL,NULL); + + FsmEvent(&chanp->lc_d.lcfi, EV_LC_RELEASE, NULL); + FsmChangeState(fi, ST_REL_W); + ll_hangup(chanp, 0); +} + + +static void +r4(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp=fi->userdata; + + chanp->is.l4.l4l3(&chanp->is,CC_DLRL,NULL); + FsmChangeState(fi, ST_NULL); +} + +static void +r5(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + chanp->para.callref = chanp->outcallref; + + chanp->outcallref++; + if (chanp->outcallref == 128) + chanp->outcallref = 64; + + chanp->is.l4.l4l3(&chanp->is, CC_SETUP_REQ, NULL); + + FsmChangeState(fi, ST_OUT); +} + +static void +r6(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + FsmChangeState(fi, ST_IN_W); + FsmEvent(&chanp->lc_d.lcfi, EV_LC_ESTABLISH, NULL); +} + +static void +r7(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + isdn_ctrl ic; + + /* + * Report incoming calls only once to linklevel, use octet 3 of + * channel identification information element. (it's value + * is copied to chanp->para.bchannel in l3s12(), file isdnl3.c) + */ + if (((chanp->chan & 1) + 1) & chanp->para.bchannel) { + chanp->is.l4.l4l3(&chanp->is, CC_ALERTING_REQ, NULL); + FsmChangeState(fi, ST_IN); + if (chanp->debug & 1) + stat_debug(chanp, "STAT_ICALL"); + ic.driver = drid; + ic.command = ISDN_STAT_ICALL; + ic.arg = chanp->chan; + /* + * No need to return "unknown" for calls without OAD, + * cause that's handled in linklevel now (replaced by '0') + */ + sprintf(ic.num, "%s,%d,0,%s", chanp->para.calling, chanp->para.info, + chanp->para.called); + iif.statcallb(&ic); + } else { + chanp->is.l4.l4l3(&chanp->is,CC_DLRL,NULL); + FsmEvent(&chanp->lc_d.lcfi, EV_LC_RELEASE, NULL); + FsmChangeState(fi, ST_REL_W); + } +} + +static void +r8(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + FsmChangeState(fi, ST_IN_SETUP); + chanp->is.l4.l4l3(&chanp->is, CC_SETUP_RSP, NULL); + +} + +static void +r9(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + FsmChangeState(fi, ST_IN_DACT); + + chanp->l2_active_protocol = chanp->l2_protocol; + chanp->incoming = !0; + chanp->lc_b.l2_start = 0; + + switch (chanp->l2_active_protocol) { + case (ISDN_PROTO_L2_X75I): + chanp->lc_b.l2_establish = !0; + break; + case (ISDN_PROTO_L2_HDLC): + case (ISDN_PROTO_L2_TRANS): + chanp->lc_b.l2_establish = 0; + break; + default: + printk(KERN_WARNING "r9 unknown protocol\n"); + break; + } + + init_ds(chanp->chan, !0); + + FsmEvent(&chanp->lc_b.lcfi, EV_LC_ESTABLISH, NULL); +} + +static void +r10(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + FsmChangeState(fi, ST_OUT_ESTB); + + init_ds(chanp->chan, 0); + FsmEvent(&chanp->lc_b.lcfi, EV_LC_ESTABLISH, NULL); + +} + +static void +r12(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + isdn_ctrl ic; + + FsmChangeState(fi, ST_ACTIVE); + chanp->data_open = !0; + + if (chanp->debug & 1) + stat_debug(chanp, "STAT_DCONN"); + ic.driver = drid; + ic.command = ISDN_STAT_DCONN; + ic.arg = chanp->chan; + iif.statcallb(&ic); + + if (chanp->debug & 1) + stat_debug(chanp, "STAT_BCONN"); + ic.driver = drid; + ic.command = ISDN_STAT_BCONN; + ic.arg = chanp->chan; + iif.statcallb(&ic); +} + +static void +r15(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + chanp->data_open = 0; + FsmChangeState(fi, ST_BC_HANGUP); + FsmEvent(&chanp->lc_b.lcfi, EV_LC_RELEASE, NULL); +} + +static void +r16(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + release_ds(chanp->chan); + + FsmChangeState(fi, ST_PRO_W); + chanp->is.l4.l4l3(&chanp->is, CC_DISCONNECT_REQ, NULL); +} + +static void +r17(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + chanp->data_open = 0; + release_ds(chanp->chan); + + FsmChangeState(fi, ST_ANT_W); +} + + +static void +r17_1(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + chanp->data_open = 0; + release_ds(chanp->chan); + + chanp->is.l4.l4l3(&chanp->is,CC_DLRL,NULL); + + FsmEvent(&chanp->lc_d.lcfi,EV_LC_RELEASE,NULL); + + FsmChangeState(fi, ST_NULL); + + ll_hangup(chanp,!0); +} + +static void +r18(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + FsmChangeState(fi, ST_REL_W); + FsmEvent(&chanp->lc_d.lcfi, EV_LC_RELEASE, NULL); + + ll_hangup(chanp, !0); +} + +static void +r19(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + FsmChangeState(fi, ST_CLEAR); + + chanp->is.l4.l4l3(&chanp->is, CC_RELEASE_REQ, NULL); + + ll_hangup(chanp, !0); +} + +static void +r20(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + chanp->is.l4.l4l3(&chanp->is,CC_DLRL,NULL); + + FsmEvent(&chanp->lc_d.lcfi,EV_LC_RELEASE,NULL); + + FsmChangeState(fi, ST_NULL); + + ll_hangup(chanp, 0); +} + + +static void +r21(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + chanp->data_open = 0; + FsmChangeState(fi, ST_DISC_BC_HANGUP); + FsmEvent(&chanp->lc_b.lcfi, EV_LC_RELEASE, NULL); +} + +static void +r22(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + release_ds(chanp->chan); + + FsmChangeState(fi, ST_CLEAR); + + chanp->is.l4.l4l3(&chanp->is, CC_RELEASE_REQ, NULL); + + ll_hangup(chanp, !0); +} + +static void +r23(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + release_ds(chanp->chan); + + FsmChangeState(fi, ST_PRO_W); + chanp->is.l4.l4l3(&chanp->is, CC_DISCONNECT_REQ, NULL); +} + +static void +r23_1(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + release_ds(chanp->chan); + + chanp->is.l4.l4l3(&chanp->is, CC_DLRL,NULL); + + FsmEvent(&chanp->lc_d.lcfi, EV_LC_RELEASE,NULL); + + FsmChangeState(fi, ST_NULL); + + ll_hangup(chanp,!0); +} + +static void +r24(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + chanp->data_open = 0; + FsmChangeState(fi, ST_D_ERR); + FsmEvent(&chanp->lc_b.lcfi, EV_LC_RELEASE, NULL); +} + +static void +r25(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + release_ds(chanp->chan); + + FsmChangeState(fi, ST_NULL); + + ll_hangup(chanp, !0); +} + +static void +r26(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + isdn_ctrl ic; + + + ic.driver = drid; + ic.command = ISDN_STAT_CINF; + ic.arg = chanp->chan; + sprintf(ic.num, "%d", chanp->para.chargeinfo); + iif.statcallb(&ic); +} + + + +static struct FsmNode fnlist[] = +{ + {ST_NULL, EV_DIAL, r1}, + {ST_OUT_W, EV_DLEST, r5}, + {ST_OUT_W, EV_DLRL, r20}, + {ST_OUT_W, EV_RELEASE_CNF, r2_2 }, + {ST_OUT, EV_DISCONNECT_IND, r2}, + {ST_OUT, EV_SETUP_CNF, r10}, + {ST_OUT, EV_HANGUP, r2_1}, + {ST_OUT, EV_RELEASE_IND, r20}, + {ST_OUT, EV_RELEASE_CNF, r20}, + {ST_OUT, EV_DLRL, r2_2}, + {ST_OUT_W_HANGUP, EV_RELEASE_IND, r2_2}, + {ST_OUT_W_HANGUP, EV_DLRL, r20}, + {ST_CLEAR, EV_RELEASE_CNF, r3}, + {ST_CLEAR, EV_DLRL, r20}, + {ST_REL_W, EV_DLRL, r4}, + {ST_NULL, EV_SETUP_IND, r6}, + {ST_IN_W, EV_DLEST, r7}, + {ST_IN_W, EV_DLRL, r3_1}, + {ST_IN, EV_DLRL, r3_1}, + {ST_IN, EV_HANGUP, r2_1}, + {ST_IN, EV_RELEASE_IND, r2_2}, + {ST_IN, EV_RELEASE_CNF, r2_2}, + {ST_IN, EV_ACCEPTD, r8}, + {ST_IN_SETUP, EV_HANGUP, r2_1}, + {ST_IN_SETUP, EV_SETUP_CMPL_IND, r9}, + {ST_IN_SETUP, EV_RELEASE_IND, r2_2}, + {ST_IN_SETUP, EV_DISCONNECT_IND, r2}, + {ST_IN_SETUP, EV_DLRL, r20}, + {ST_OUT_ESTB, EV_BC_EST, r12}, + {ST_OUT_ESTB, EV_BC_REL, r23}, + {ST_OUT_ESTB, EV_DLRL, r23_1}, + {ST_IN_DACT, EV_BC_EST, r12}, + {ST_IN_DACT, EV_BC_REL, r17}, + {ST_IN_DACT, EV_DLRL, r17_1}, + {ST_ACTIVE, EV_HANGUP, r15}, + {ST_ACTIVE, EV_BC_REL, r17}, + {ST_ACTIVE, EV_DISCONNECT_IND, r21}, + {ST_ACTIVE, EV_DLRL, r24}, + {ST_ACTIVE, EV_CINF, r26}, + {ST_ACTIVE, EV_RELEASE_IND, r17}, + {ST_BC_HANGUP, EV_BC_REL, r16}, + {ST_BC_HANGUP, EV_DISCONNECT_IND, r21}, + {ST_PRO_W, EV_RELEASE_IND, r18}, + {ST_ANT_W, EV_DISCONNECT_IND, r19}, + {ST_DISC_BC_HANGUP, EV_BC_REL, r22}, + {ST_D_ERR, EV_BC_REL, r25}, +}; + +#define FNCOUNT (sizeof(fnlist)/sizeof(struct FsmNode)) + +static void +lc_r1(struct FsmInst *fi, int event, void *arg) +{ + struct LcFsm *lf = fi->userdata; + + FsmChangeState(fi, ST_LC_ACTIVATE_WAIT); + FsmAddTimer(&lf->act_timer, 1000, EV_LC_TIMER, NULL, 50); + lf->st->ma.manl1(lf->st, PH_ACTIVATE, NULL); + +} + +static void +lc_r6(struct FsmInst *fi, int event, void *arg) +{ + struct LcFsm *lf = fi->userdata; + + FsmDelTimer(&lf->act_timer, 50); + FsmChangeState(fi, ST_LC_DELAY); + FsmAddTimer(&lf->act_timer, 40, EV_LC_TIMER, NULL, 51); +} + +static void +lc_r2(struct FsmInst *fi, int event, void *arg) +{ + struct LcFsm *lf = fi->userdata; + + if (lf->l2_establish) { + FsmChangeState(fi, ST_LC_ESTABLISH_WAIT); + if (lf->l2_start) + lf->st->ma.manl2(lf->st, DL_ESTABLISH, NULL); + } else { + FsmChangeState(fi, ST_LC_CONNECTED); + lf->lccall(lf, LC_ESTABLISH, NULL); + } +} + +static void +lc_r3(struct FsmInst *fi, int event, void *arg) +{ + struct LcFsm *lf = fi->userdata; + + FsmChangeState(fi, ST_LC_CONNECTED); + lf->lccall(lf, LC_ESTABLISH, NULL); +} + +static void +lc_r4(struct FsmInst *fi, int event, void *arg) +{ + struct LcFsm *lf = fi->userdata; + + if (lf->l2_establish) { + FsmChangeState(fi, ST_LC_RELEASE_WAIT); + lf->st->ma.manl2(lf->st, DL_RELEASE, NULL); + } else { + FsmChangeState(fi, ST_LC_NULL); + lf->st->ma.manl1(lf->st, PH_DEACTIVATE, NULL); + lf->lccall(lf, LC_RELEASE, NULL); + } +} + +static void +lc_r5(struct FsmInst *fi, int event, void *arg) +{ + struct LcFsm *lf = fi->userdata; + + FsmChangeState(fi, ST_LC_NULL); + lf->st->ma.manl1(lf->st, PH_DEACTIVATE, NULL); + lf->lccall(lf, LC_RELEASE, NULL); +} + +static struct FsmNode LcFnList[] = +{ + {ST_LC_NULL, EV_LC_ESTABLISH, lc_r1}, + {ST_LC_ACTIVATE_WAIT, EV_LC_PH_ACTIVATE, lc_r6}, + {ST_LC_DELAY, EV_LC_TIMER, lc_r2}, + {ST_LC_ESTABLISH_WAIT, EV_LC_DL_ESTABLISH, lc_r3}, + {ST_LC_CONNECTED, EV_LC_RELEASE, lc_r4}, + {ST_LC_CONNECTED, EV_LC_DL_RELEASE, lc_r5}, + {ST_LC_RELEASE_WAIT, EV_LC_DL_RELEASE, lc_r5}, + {ST_LC_ACTIVATE_WAIT, EV_LC_TIMER, lc_r5}, + {ST_LC_ESTABLISH_WAIT, EV_LC_DL_RELEASE, lc_r5}, +}; + +#define LC_FN_COUNT (sizeof(LcFnList)/sizeof(struct FsmNode)) + +void +CallcNew(void) +{ + callcfsm.state_count = STATE_COUNT; + callcfsm.event_count = EVENT_COUNT; + callcfsm.strEvent = strEvent; + callcfsm.strState = strState; + FsmNew(&callcfsm, fnlist, FNCOUNT); + + lcfsm.state_count = LC_STATE_COUNT; + lcfsm.event_count = LC_EVENT_COUNT; + lcfsm.strEvent = strLcEvent; + lcfsm.strState = strLcState; + FsmNew(&lcfsm, LcFnList, LC_FN_COUNT); +} + +void +CallcFree(void) +{ + FsmFree(&lcfsm); + FsmFree(&callcfsm); +} + +static void +release_ds(int chan) +{ + struct PStack *st = &chanlist[chan].ds; + struct IsdnCardState *sp; + struct HscxState *hsp; + + sp = st->l1.hardware; + hsp = sp->hs + chanlist[chan].hscx; + + close_hscxstate(hsp); + + switch (chanlist[chan].l2_active_protocol) { + case (ISDN_PROTO_L2_X75I): + releasestack_isdnl2(st); + break; + case (ISDN_PROTO_L2_HDLC): + case (ISDN_PROTO_L2_TRANS): + releasestack_transl2(st); + break; + } +} + +static void +cc_l1man(struct PStack *st, int pr, void *arg) +{ + struct Channel *chanp = (struct Channel *) st->l4.userdata; + + switch (pr) { + case (PH_ACTIVATE): + FsmEvent(&chanp->lc_d.lcfi, EV_LC_PH_ACTIVATE, NULL); + break; + case (PH_DEACTIVATE): + FsmEvent(&chanp->lc_d.lcfi, EV_LC_PH_DEACTIVATE, NULL); + break; + } +} + +static void +cc_l2man(struct PStack *st, int pr, void *arg) +{ + struct Channel *chanp = (struct Channel *) st->l4.userdata; + + switch (pr) { + case (DL_ESTABLISH): + FsmEvent(&chanp->lc_d.lcfi, EV_LC_DL_ESTABLISH, NULL); + break; + case (DL_RELEASE): + FsmEvent(&chanp->lc_d.lcfi, EV_LC_DL_RELEASE, NULL); + break; + } +} + +static void +dcc_l1man(struct PStack *st, int pr, void *arg) +{ + struct Channel *chanp = (struct Channel *) st->l4.userdata; + + switch (pr) { + case (PH_ACTIVATE): + FsmEvent(&chanp->lc_b.lcfi, EV_LC_PH_ACTIVATE, NULL); + break; + case (PH_DEACTIVATE): + FsmEvent(&chanp->lc_b.lcfi, EV_LC_PH_DEACTIVATE, NULL); + break; + } +} + +static void +dcc_l2man(struct PStack *st, int pr, void *arg) +{ + struct Channel *chanp = (struct Channel *) st->l4.userdata; + + switch (pr) { + case (DL_ESTABLISH): + FsmEvent(&chanp->lc_b.lcfi, EV_LC_DL_ESTABLISH, NULL); + break; + case (DL_RELEASE): + FsmEvent(&chanp->lc_b.lcfi, EV_LC_DL_RELEASE, NULL); + break; + } +} + +static void +ll_handler(struct PStack *st, int pr, + struct BufHeader *ibh) +{ + struct Channel *chanp = (struct Channel *) st->l4.userdata; + + switch (pr) { + case (CC_DISCONNECT_IND): + FsmEvent(&chanp->fi, EV_DISCONNECT_IND, NULL); + break; + case (CC_RELEASE_CNF): + FsmEvent(&chanp->fi, EV_RELEASE_CNF, NULL); + break; + case (CC_SETUP_IND): + FsmEvent(&chanp->fi, EV_SETUP_IND, NULL); + break; + case (CC_RELEASE_IND): + FsmEvent(&chanp->fi, EV_RELEASE_IND, NULL); + break; + case (CC_SETUP_COMPLETE_IND): + FsmEvent(&chanp->fi, EV_SETUP_CMPL_IND, NULL); + break; + case (CC_SETUP_CNF): + FsmEvent(&chanp->fi, EV_SETUP_CNF, NULL); + break; + case (CC_INFO_CHARGE): + FsmEvent(&chanp->fi, EV_CINF, NULL); + break; + } +} + +static void +init_is(int chan, unsigned int ces) +{ + struct PStack *st = &(chanlist[chan].is); + struct IsdnCardState *sp = chanlist[chan].sp; + char tmp[128]; + + setstack_teles(st, sp); + + st->l2.sap = 0; + + st->l2.tei = 255; + + st->l2.ces = ces; + st->l2.extended = !0; + st->l2.laptype = LAPD; + st->l2.window = 1; + st->l2.orig = !0; + st->l2.t200 = 1000; /* 1000 milliseconds */ + if (st->protocol == ISDN_PTYPE_1TR6) { + st->l2.n200 = 3; /* try 3 times */ + st->l2.t203 = 10000; /* 10000 milliseconds */ + } else { + st->l2.n200 = 4; /* try 4 times */ + st->l2.t203 = 5000; /* 5000 milliseconds */ + } + + sprintf(tmp, "Channel %d q.921", chan); + setstack_isdnl2(st, tmp); + setstack_isdnl3(st); + st->l2.debug = 2; + st->l3.debug = 2; + st->l2.debug = 0xff; + st->l3.debug = 0xff; + st->l4.userdata = chanlist + chan; + st->l4.l2writewakeup = NULL; + + st->l3.l3l4 = ll_handler; + st->l1.l1man = cc_l1man; + st->l2.l2man = cc_l2man; + + st->pa = &chanlist[chan].para; + teles_addlist(sp, st); +} + +static void +callc_debug(struct FsmInst *fi, char *s) +{ + char str[80], tm[32]; + struct Channel *chanp = fi->userdata; + + jiftime(tm, jiffies); + sprintf(str, "%s Channel %d callc %s\n", tm, chanp->chan, s); + teles_putstatus(str); +} + +static void +lc_debug(struct FsmInst *fi, char *s) +{ + char str[256], tm[32]; + struct LcFsm *lf = fi->userdata; + + jiftime(tm, jiffies); + sprintf(str, "%s Channel %d lc %s\n", tm, lf->ch->chan, s); + teles_putstatus(str); +} + +static void +dlc_debug(struct FsmInst *fi, char *s) +{ + char str[256], tm[32]; + struct LcFsm *lf = fi->userdata; + + jiftime(tm, jiffies); + sprintf(str, "%s Channel %d dlc %s\n", tm, lf->ch->chan, s); + teles_putstatus(str); +} + +static void +lccall_d(struct LcFsm *lf, int pr, void *arg) +{ + struct Channel *chanp = lf->ch; + + switch (pr) { + case (LC_ESTABLISH): + FsmEvent(&chanp->fi, EV_DLEST, NULL); + break; + case (LC_RELEASE): + FsmEvent(&chanp->fi, EV_DLRL, NULL); + break; + } +} + +static void +lccall_b(struct LcFsm *lf, int pr, void *arg) +{ + struct Channel *chanp = lf->ch; + + switch (pr) { + case (LC_ESTABLISH): + FsmEvent(&chanp->fi, EV_BC_EST, NULL); + break; + case (LC_RELEASE): + FsmEvent(&chanp->fi, EV_BC_REL, NULL); + break; + } +} + +static void +init_chan(int chan, int cardnr, int hscx, + unsigned int ces) +{ + struct IsdnCard *card = cards + cardnr; + struct Channel *chanp = chanlist + chan; + + chanp->sp = card->sp; + chanp->hscx = hscx; + chanp->chan = chan; + chanp->incoming = 0; + chanp->debug = 0; + init_is(chan, ces); + + chanp->fi.fsm = &callcfsm; + chanp->fi.state = ST_NULL; + chanp->fi.debug = 0; + chanp->fi.userdata = chanp; + chanp->fi.printdebug = callc_debug; + + chanp->lc_d.lcfi.fsm = &lcfsm; + chanp->lc_d.lcfi.state = ST_LC_NULL; + chanp->lc_d.lcfi.debug = 0; + chanp->lc_d.lcfi.userdata = &chanp->lc_d; + chanp->lc_d.lcfi.printdebug = lc_debug; + chanp->lc_d.type = LC_D; + chanp->lc_d.ch = chanp; + chanp->lc_d.st = &chanp->is; + chanp->lc_d.l2_establish = !0; + chanp->lc_d.l2_start = !0; + chanp->lc_d.lccall = lccall_d; + FsmInitTimer(&chanp->lc_d.lcfi, &chanp->lc_d.act_timer); + + chanp->lc_b.lcfi.fsm = &lcfsm; + chanp->lc_b.lcfi.state = ST_LC_NULL; + chanp->lc_b.lcfi.debug = 0; + chanp->lc_b.lcfi.userdata = &chanp->lc_b; + chanp->lc_b.lcfi.printdebug = dlc_debug; + chanp->lc_b.type = LC_B; + chanp->lc_b.ch = chanp; + chanp->lc_b.st = &chanp->ds; + chanp->lc_b.l2_establish = !0; + chanp->lc_b.l2_start = !0; + chanp->lc_b.lccall = lccall_b; + FsmInitTimer(&chanp->lc_b.lcfi, &chanp->lc_b.act_timer); + + chanp->outcallref = 64; + chanp->data_open = 0; +} + +int +CallcNewChan(void) +{ + int i, ces, c; + + chancount = 0; + for (i = 0; i < nrcards; i++) + if (cards[i].sp) + chancount += 2; + + chanlist = (struct Channel *) Smalloc(sizeof(struct Channel) * + chancount, GFP_KERNEL, "chanlist"); + + c = 0; + ces = randomces(); + for (i = 0; i < nrcards; i++) + if (cards[i].sp) { + init_chan(c++, i, 1, ces++); + ces %= 0xffff; + init_chan(c++, i, 0, ces++); + ces %= 0xffff; + } + printk(KERN_INFO "channels %d\n", chancount); + return (chancount); + +} + +static void +release_is(int chan) +{ + struct PStack *st = &chanlist[chan].is; + + releasestack_isdnl2(st); + teles_rmlist(st->l1.hardware, st); + BufQueueRelease(&st->l2.i_queue); +} + +void +CallcFreeChan(void) +{ + int i; + + for (i = 0; i < chancount; i++) + release_is(i); + Sfree((void *) chanlist); +} + +static void +lldata_handler(struct PStack *st, int pr, + void *arg) +{ + struct Channel *chanp = (struct Channel *) st->l4.userdata; + byte *ptr; + int size; + struct BufHeader *ibh = arg; + + switch (pr) { + case (DL_DATA): + if (chanp->data_open) { + ptr = DATAPTR(ibh); + ptr += chanp->ds.l2.ihsize; + size = ibh->datasize - chanp->ds.l2.ihsize; + iif.rcvcallb(drid, chanp->chan, ptr, size); + } + BufPoolRelease(ibh); + break; + default: + printk(KERN_WARNING "lldata_handler unknown primitive\n"); + break; + } +} + +static void +lltrans_handler(struct PStack *st, int pr, + struct BufHeader *ibh) +{ + struct Channel *chanp = (struct Channel *) st->l4.userdata; + byte *ptr; + + switch (pr) { + case (PH_DATA): + if (chanp->data_open) { + ptr = DATAPTR(ibh); + iif.rcvcallb(drid, chanp->chan, ptr, ibh->datasize); + } + BufPoolRelease(ibh); + break; + default: + printk(KERN_WARNING "lltrans_handler unknown primitive\n"); + break; + } +} + +static void +ll_writewakeup(struct PStack *st) +{ + struct Channel *chanp = st->l4.userdata; + isdn_ctrl ic; + + ic.driver = drid; + ic.command = ISDN_STAT_BSENT; + ic.arg = chanp->chan; + iif.statcallb(&ic); +} + +static int +init_ds(int chan, int incoming) +{ + struct PStack *st = &(chanlist[chan].ds); + struct IsdnCardState *sp = (struct IsdnCardState *) + chanlist[chan].is.l1.hardware; + struct HscxState *hsp = sp->hs + chanlist[chan].hscx; + char tmp[128]; + + st->l1.hardware = sp; + + hsp->mode = 2; + hsp->transbufsize = 4000; + + if (setstack_hscx(st, hsp)) + return (-1); + + st->l2.extended = 0; + st->l2.laptype = LAPB; + st->l2.orig = !incoming; + st->l2.t200 = 1000; /* 1000 milliseconds */ + st->l2.window = 3; + st->l2.n200 = 4; /* try 4 times */ + st->l2.t203 = 5000; /* 5000 milliseconds */ + + st->l2.debug = 0xff; + st->l3.debug = 0xff; + switch (chanlist[chan].l2_active_protocol) { + case (ISDN_PROTO_L2_X75I): + sprintf(tmp, "Channel %d x.75", chan); + setstack_isdnl2(st, tmp); + st->l2.l2l3 = lldata_handler; + st->l1.l1man = dcc_l1man; + st->l2.l2man = dcc_l2man; + st->l4.userdata = chanlist + chan; + st->l4.l1writewakeup = NULL; + st->l4.l2writewakeup = ll_writewakeup; + st->l2.l2m.debug = debugflags & 16; + st->ma.manl2(st, MDL_NOTEIPROC, NULL); + st->l1.hscxmode = 2; /* Packet-Mode ? */ + st->l1.hscxchannel = chanlist[chan].para.bchannel - 1; + break; + case (ISDN_PROTO_L2_HDLC): + st->l1.l1l2 = lltrans_handler; + st->l1.l1man = dcc_l1man; + st->l4.userdata = chanlist + chan; + st->l4.l1writewakeup = ll_writewakeup; + st->l1.hscxmode = 2; + st->l1.hscxchannel = chanlist[chan].para.bchannel - 1; + break; + case (ISDN_PROTO_L2_TRANS): + st->l1.l1l2 = lltrans_handler; + st->l1.l1man = dcc_l1man; + st->l4.userdata = chanlist + chan; + st->l4.l1writewakeup = ll_writewakeup; + st->l1.hscxmode = 1; + st->l1.hscxchannel = chanlist[chan].para.bchannel - 1; + break; + } + + return (0); + +} + +static void +channel_report(int i) +{ +} + +static void +command_debug(struct Channel *chanp, char *s) +{ + char tmp[64], tm[32]; + + jiftime(tm, jiffies); + sprintf(tmp, "%s Channel %d LL->HL %s\n", tm, chanp->chan, s); + teles_putstatus(tmp); +} + +static void +distr_debug(void) +{ + int i; + + for (i = 0; i < chancount; i++) { + chanlist[i].debug = debugflags & 1; + chanlist[i].fi.debug = debugflags & 2; + chanlist[i].is.l2.l2m.debug = debugflags & 8; + chanlist[i].ds.l2.l2m.debug = debugflags & 16; + } + for (i = 0; i < nrcards; i++) + if (cards[i].sp) { + cards[i].sp->dlogflag = debugflags & 4; + cards[i].sp->debug = debugflags & 32; + } +} + +int +teles_command(isdn_ctrl * ic) +{ + struct Channel *chanp; + char tmp[64]; + int i; + unsigned int num; + + switch (ic->command) { + case (ISDN_CMD_SETEAZ): + chanp = chanlist + ic->arg; + if (chanp->debug & 1) + command_debug(chanp, "SETEAZ"); + return (0); + case (ISDN_CMD_DIAL): + chanp = chanlist + (ic->arg & 0xff); + if (chanp->debug & 1) { + sprintf(tmp, "DIAL %s", ic->num); + command_debug(chanp, tmp); + } + FsmEvent(&chanp->fi, EV_DIAL, ic); + return (0); + case (ISDN_CMD_ACCEPTB): + chanp = chanlist + ic->arg; + if (chanp->debug & 1) + command_debug(chanp, "ACCEPTB"); + FsmEvent(&chanp->fi, EV_ACCEPTB, NULL); + break; + case (ISDN_CMD_ACCEPTD): + chanp = chanlist + ic->arg; + if (chanp->debug & 1) + command_debug(chanp, "ACCEPTD"); + FsmEvent(&chanp->fi, EV_ACCEPTD, NULL); + break; + case (ISDN_CMD_HANGUP): + chanp = chanlist + ic->arg; + if (chanp->debug & 1) + command_debug(chanp, "HANGUP"); + FsmEvent(&chanp->fi, EV_HANGUP, NULL); + break; + case (ISDN_CMD_SUSPEND): + chanp = chanlist + ic->arg; + if (chanp->debug & 1) { + sprintf(tmp, "SUSPEND %s", ic->num); + command_debug(chanp, tmp); + } + FsmEvent(&chanp->fi, EV_SUSPEND, ic); + break; + case (ISDN_CMD_RESUME): + chanp = chanlist + ic->arg; + if (chanp->debug & 1) { + sprintf(tmp, "RESUME %s", ic->num); + command_debug(chanp, tmp); + } + FsmEvent(&chanp->fi, EV_RESUME, ic); + break; + case (ISDN_CMD_LOCK): + teles_mod_inc_use_count(); + break; + case (ISDN_CMD_UNLOCK): + teles_mod_dec_use_count(); + break; + case (ISDN_CMD_IOCTL): + switch (ic->arg) { + case (0): + for (i = 0; i < nrcards; i++) + if (cards[i].sp) + teles_reportcard(i); + for (i = 0; i < chancount; i++) + channel_report(i); + break; + case (1): + debugflags = *(unsigned int *) ic->num; + distr_debug(); + sprintf(tmp, "debugging flags set to %x\n", debugflags); + teles_putstatus(tmp); + printk(KERN_DEBUG "%s", tmp); + break; + case (2): + num = *(unsigned int *) ic->num; + i = num >> 8; + if (i >= chancount) + break; + chanp = chanlist + i; + chanp->impair = num & 0xff; + if (chanp->debug & 1) { + sprintf(tmp, "IMPAIR %x", chanp->impair); + command_debug(chanp, tmp); + } + break; + } + break; + case (ISDN_CMD_SETL2): + chanp = chanlist + (ic->arg & 0xff); + if (chanp->debug & 1) { + sprintf(tmp, "SETL2 %ld", ic->arg >> 8); + command_debug(chanp, tmp); + } + chanp->l2_protocol = ic->arg >> 8; + break; + default: + break; + } + + return (0); +} + +int +teles_writebuf(int id, int chan, const u_char * buf, int count, int user) +{ + struct Channel *chanp = chanlist + chan; + struct PStack *st = &chanp->ds; + struct BufHeader *ibh; + int err, i; + byte *ptr; + + if (!chanp->data_open) { + printk(KERN_DEBUG "teles_writebuf: channel not open\n"); + return -EIO; + } + + err = BufPoolGet(&ibh, st->l1.sbufpool, GFP_ATOMIC, st, 21); + if (err) + /* Must return 0 here, since this is not an error + * but a temporary lack of resources. + */ + return 0; + + ptr = DATAPTR(ibh); + if (chanp->lc_b.l2_establish) + i = st->l2.ihsize; + else + i = 0; + + if ((count+i) > BUFFER_SIZE(HSCX_SBUF_ORDER, HSCX_SBUF_BPPS)) { + printk(KERN_WARNING "teles_writebuf: packet too large!\n"); + return (-EINVAL); + } + + ptr += i; + + if (user) { + if (copy_from_user(ptr, buf, count)) + return -EFAULT; + } else + memcpy(ptr, buf, count); + ibh->datasize = count + i; + + if (chanp->data_open) { + if (chanp->lc_b.l2_establish) + chanp->ds.l3.l3l2(&chanp->ds, DL_DATA, ibh); + else + chanp->ds.l2.l2l1(&chanp->ds, PH_DATA, ibh); + return (count); + } else { + BufPoolRelease(ibh); + return (0); + } + +} + +static char * +strcpyupto(char *dest, char *src, char upto) +{ + while (*src && (*src != upto) && (*src != '\0')) + *dest++ = *src++; + *dest = '\0'; + return (src); +} diff --git a/drivers/isdn/teles/card.c b/drivers/isdn/teles/card.c new file mode 100644 index 000000000..c0595e862 --- /dev/null +++ b/drivers/isdn/teles/card.c @@ -0,0 +1,1892 @@ +/* $Id: card.c,v 1.13 1996/07/18 11:21:24 jdenoud Exp $ + * + * card.c low level stuff for the Teles S0 isdn card + * + * Author Jan den Ouden + * + * Beat Doebeli log all D channel traffic + * + * $Log: card.c,v $ + * Revision 1.13 1996/07/18 11:21:24 jdenoud + * Use small buffers for incoming audio data + * + * Revision 1.12 1996/06/24 17:16:52 fritz + * Added check for misconfigured membase. + * + * Revision 1.11 1996/06/14 03:30:37 fritz + * Added recovery from EXIR 40 interrupt. + * Some cleanup. + * + * Revision 1.10 1996/06/11 14:57:20 hipp + * minor changes to ensure, that SKBs are sent in the right order + * + * Revision 1.9 1996/06/06 14:42:09 fritz + * Bugfix: forgot hsp-> in last change. + * + * Revision 1.7 1996/05/31 01:02:21 fritz + * Cosmetic changes. + * + * Revision 1.6 1996/05/26 14:58:10 fritz + * Bugfix: Did not show port correctly, when no card found. + * + * Revision 1.5 1996/05/17 03:45:02 fritz + * Made error messages more clearly. + * Bugfix: Only 31 bytes of 32-byte audio frames + * have been transfered to upper layers. + * + * Revision 1.4 1996/05/06 10:17:57 fritz + * Added voice-send stuff + * (Not reporting EXIR when in voice-mode, since it's normal). + * + * Revision 1.3 1996/04/30 22:02:40 isdn4dev + * Bugfixes for 16.3 + * -improved IO allocation + * -fix second B channel problem + * -correct ph_command patch + * + * Revision 1.2 1996/04/30 10:00:59 fritz + * Bugfix: Added ph_command(8) for 16.3. + * Bugfix: Ports did not get registered correctly + * when using a 16.3. + * Started voice support. + * Some experimental changes of waitforXFW(). + * + * Revision 1.1 1996/04/13 10:22:42 fritz + * Initial revision + * + * + */ + +#define __NO_VERSION__ +#include "teles.h" + +#define INCLUDE_INLINE_FUNCS +#include <linux/tqueue.h> +#include <linux/interrupt.h> + +#include <asm/io.h> + +#undef DCHAN_VERBOSE + +extern void tei_handler(struct PStack *st, byte pr, + struct BufHeader *ibh); +extern struct IsdnCard cards[]; +extern int nrcards; + +#define byteout(addr,val) outb_p(val,addr) +#define bytein(addr) inb_p(addr) + +static inline byte +readisac_0(unsigned int cardm, byte offset) +{ + return readb(cardm + 0x100 + ((offset & 1) ? 0x1ff : 0) + offset); +} + +static inline byte +readisac_3(int iobase, byte offset) +{ + return (bytein(iobase - 0x420 + offset)); +} + +#define READISAC(mbase,ibase,ofs) \ + ((mbase)?readisac_0(mbase,ofs):readisac_3(ibase,ofs)) + +static inline void +writeisac_0(unsigned int cardm, byte offset, byte value) +{ + writeb(value, cardm + 0x100 + ((offset & 1) ? 0x1ff : 0) + offset); +} + +static inline void +writeisac_3(int iobase, byte offset, byte value) +{ + byteout(iobase - 0x420 + offset, value); +} + +#define WRITEISAC(mbase,ibase,ofs,val) \ + ((mbase)?writeisac_0(mbase,ofs,val):writeisac_3(ibase,ofs,val)) + +static inline void +readisac_s(int iobase, byte offset, byte * dest, int count) +{ + insb(iobase - 0x420 + offset, dest, count); +} + +static inline void +writeisac_s(int iobase, byte offset, byte * src, int count) +{ + outsb(iobase - 0x420 + offset, src, count); +} + +static inline byte +readhscx_0(unsigned int base, byte hscx, byte offset) +{ + return readb(base + 0x180 + ((offset & 1) ? 0x1FF : 0) + + ((hscx & 1) ? 0x40 : 0) + offset); +} + +static inline byte +readhscx_3(int iobase, byte hscx, byte offset) +{ + return (bytein(iobase - (hscx ? 0x820 : 0xc20) + offset)); +} + +#define READHSCX(mbase,ibase,hscx,ofs) \ + ((mbase)?readhscx_0(mbase,hscx,ofs):readhscx_3(ibase,hscx,ofs)) + +static inline void +writehscx_0(unsigned int base, byte hscx, byte offset, byte data) +{ + writeb(data, base + 0x180 + ((offset & 1) ? 0x1FF : 0) + + ((hscx & 1) ? 0x40 : 0) + offset); +} + +static inline void +writehscx_3(int iobase, byte hscx, byte offset, byte data) +{ + byteout(iobase - (hscx ? 0x820 : 0xc20) + offset, data); +} + +static inline void +readhscx_s(int iobase, byte hscx, byte offset, byte * dest, int count) +{ + insb(iobase - (hscx ? 0x820 : 0xc20) + offset, dest, count); +} + +static inline void +writehscx_s(int iobase, byte hscx, byte offset, byte * src, int count) +{ + outsb(iobase - (hscx ? 0x820 : 0xc20) + offset, src, count); +} + +#define ISAC_MASK 0x20 +#define ISAC_ISTA 0x20 +#define ISAC_STAR 0x21 +#define ISAC_CMDR 0x21 +#define ISAC_EXIR 0x24 + +#define ISAC_RBCH 0x2a + +#define ISAC_ADF2 0x39 +#define ISAC_SPCR 0x30 +#define ISAC_ADF1 0x38 +#define ISAC_CIX0 0x31 +#define ISAC_STCR 0x37 +#define ISAC_MODE 0x22 +#define ISAC_RSTA 0x27 +#define ISAC_RBCL 0x25 +#define ISAC_TIMR 0x23 +#define ISAC_SQXR 0x3b + +#define HSCX_ISTA 0x20 +#define HSCX_CCR1 0x2f +#define HSCX_CCR2 0x2c +#define HSCX_TSAR 0x31 +#define HSCX_TSAX 0x30 +#define HSCX_XCCR 0x32 +#define HSCX_RCCR 0x33 +#define HSCX_MODE 0x22 +#define HSCX_CMDR 0x21 +#define HSCX_EXIR 0x24 +#define HSCX_XAD1 0x24 +#define HSCX_XAD2 0x25 +#define HSCX_RAH2 0x27 +#define HSCX_RSTA 0x27 +#define HSCX_TIMR 0x23 +#define HSCX_STAR 0x21 +#define HSCX_RBCL 0x25 +#define HSCX_XBCH 0x2d +#define HSCX_VSTR 0x2e +#define HSCX_RLCR 0x2e +#define HSCX_MASK 0x20 + +static inline void +waitforCEC_0(unsigned int base, byte hscx) +{ + long to = 10; + + while ((readhscx_0(base, hscx, HSCX_STAR) & 0x04) && to) { + udelay(5); + to--; + } + if (!to) + printk(KERN_WARNING "waitforCEC timeout\n"); +} + +static inline void +waitforCEC_3(int iobase, byte hscx) +{ + long to = 10; + + while ((readhscx_3(iobase, hscx, HSCX_STAR) & 0x04) && to) { + udelay(5); + to--; + } + if (!to) + printk(KERN_WARNING "waitforCEC timeout\n"); +} + +static inline void +waitforXFW_0(unsigned int base, byte hscx) +{ + long to = 20; + + while ((!(readhscx_0(base, hscx, HSCX_STAR) & 0x44)==0x40) && to) { + udelay(5); + to--; + } + if (!to) + printk(KERN_WARNING "waitforXFW timeout\n"); +} + +static inline void +waitforXFW_3(int iobase, byte hscx) +{ + long to = 20; + + while ((!(readhscx_3(iobase, hscx, HSCX_STAR) & 0x44)==0x40) && to) { + udelay(5); + to--; + } + if (!to) + printk(KERN_WARNING "waitforXFW timeout\n"); +} + +static inline void +writehscxCMDR_0(unsigned int base, byte hscx, byte data) +{ + long flags; + + save_flags(flags); + cli(); + waitforCEC_0(base, hscx); + writehscx_0(base, hscx, HSCX_CMDR, data); + restore_flags(flags); +} + +static inline void +writehscxCMDR_3(int iobase, byte hscx, byte data) +{ + long flags; + + save_flags(flags); + cli(); + waitforCEC_3(iobase, hscx); + writehscx_3(iobase, hscx, HSCX_CMDR, data); + restore_flags(flags); +} + +#define WRITEHSCX_CMDR(mbase,ibase,hscx,data) \ + ((mbase)?writehscxCMDR_0(mbase,hscx,data):writehscxCMDR_3(ibase,hscx,data)) + +/* + * fast interrupt here + */ + +#define ISAC_RCVBUFREADY 0 +#define ISAC_XMTBUFREADY 1 +#define ISAC_PHCHANGE 2 + +#define HSCX_RCVBUFREADY 0 +#define HSCX_XMTBUFREADY 1 + +void +teles_hscxreport(struct IsdnCardState *sp, int hscx) +{ + printk(KERN_DEBUG "HSCX %d\n", hscx); + if (sp->membase) { + printk(KERN_DEBUG " ISTA %x\n", readhscx_0(sp->membase, + hscx, HSCX_ISTA)); + printk(KERN_DEBUG " STAR %x\n", readhscx_0(sp->membase, + hscx, HSCX_STAR)); + printk(KERN_DEBUG " EXIR %x\n", readhscx_0(sp->membase, + hscx, HSCX_EXIR)); + } else { + printk(KERN_DEBUG " ISTA %x\n", readhscx_3(sp->iobase, + hscx, HSCX_ISTA)); + printk(KERN_DEBUG " STAR %x\n", readhscx_3(sp->iobase, + hscx, HSCX_STAR)); + printk(KERN_DEBUG " EXIR %x\n", readhscx_3(sp->iobase, + hscx, HSCX_EXIR)); + } +} + +void +teles_report(struct IsdnCardState *sp) +{ + printk(KERN_DEBUG "ISAC\n"); + if (sp->membase) { + printk(KERN_DEBUG " ISTA %x\n", readisac_0(sp->membase, + ISAC_ISTA)); + printk(KERN_DEBUG " STAR %x\n", readisac_0(sp->membase, + ISAC_STAR)); + printk(KERN_DEBUG " EXIR %x\n", readisac_0(sp->membase, + ISAC_EXIR)); + } else { + printk(KERN_DEBUG " ISTA %x\n", readisac_3(sp->iobase, + ISAC_ISTA)); + printk(KERN_DEBUG " STAR %x\n", readisac_3(sp->iobase, + ISAC_STAR)); + printk(KERN_DEBUG " EXIR %x\n", readisac_3(sp->iobase, + ISAC_EXIR)); + } + teles_hscxreport(sp, 0); + teles_hscxreport(sp, 1); +} + +/* + * HSCX stuff goes here + */ + +static void +hscx_sched_event(struct HscxState *hsp, int event) +{ + hsp->event |= 1 << event; + queue_task_irq_off(&hsp->tqueue, &tq_immediate); + mark_bh(IMMEDIATE_BH); +} + +static void +hscx_empty_fifo(struct HscxState *hsp, int count) +{ + byte *ptr; + struct BufHeader *ibh = hsp->rcvibh; + + if (hsp->sp->debug) + printk(KERN_DEBUG "hscx_empty_fifo\n"); + + if (hsp->rcvptr + count > BUFFER_SIZE(HSCX_RBUF_ORDER, + HSCX_RBUF_BPPS)) { + printk(KERN_WARNING + "hscx_empty_fifo: incoming packet too large\n"); + WRITEHSCX_CMDR(hsp->membase, hsp->iobase, hsp->hscx, 0x80); + return; + } + ptr = DATAPTR(ibh); + ptr += hsp->rcvptr; + + hsp->rcvptr += count; + if (hsp->membase) { + while (count--) + *ptr++ = readhscx_0(hsp->membase, hsp->hscx, 0x0); + writehscxCMDR_0(hsp->membase, hsp->hscx, 0x80); + } else { + readhscx_s(hsp->iobase, hsp->hscx, 0x3e, ptr, count); + writehscxCMDR_3(hsp->iobase, hsp->hscx, 0x80); + } +} + +static void +hscx_fill_fifo(struct HscxState *hsp) +{ + struct BufHeader *ibh; + int more, count; + byte *ptr; + + if (hsp->sp->debug) + printk(KERN_DEBUG "hscx_fill_fifo\n"); + + ibh = hsp->xmtibh; + if (!ibh) + return; + + count = ibh->datasize - hsp->sendptr; + if (count <= 0) + return; + + more = (hsp->mode == 1)?1:0; + if (count > 32) { + more = !0; + count = 32; + } + ptr = DATAPTR(ibh); + ptr += hsp->sendptr; + hsp->sendptr += count; + +#ifdef BCHAN_VERBOSE + { + int i; + printk(KERN_DEBUG "hscx_fill_fifo "); + for (i = 0; i < count; i++) + printk(" %2x", ptr[i]); + printk("\n"); + } +#endif + if (hsp->membase) { + waitforXFW_0(hsp->membase, hsp->hscx); + while (count--) + writehscx_0(hsp->membase, hsp->hscx, 0x0, *ptr++); + writehscxCMDR_0(hsp->membase, hsp->hscx, more ? 0x8 : 0xa); + } else { + waitforXFW_3(hsp->iobase, hsp->hscx); + writehscx_s(hsp->iobase, hsp->hscx, 0x3e, ptr, count); + writehscxCMDR_3(hsp->iobase, hsp->hscx, more ? 0x8 : 0xa); + } +} + +static inline void +hscx_interrupt(struct IsdnCardState *sp, byte val, byte hscx) +{ + byte r; + struct HscxState *hsp = sp->hs + hscx; + int count, err; + + if (!hsp->init) + return; + + if (val & 0x80) { /* RME */ + + r = READHSCX(hsp->membase, sp->iobase, hsp->hscx, HSCX_RSTA); + if ((r & 0xf0) != 0xa0) { + if (!r & 0x80) + printk(KERN_WARNING + "Teles: HSCX invalid frame\n"); + if ((r & 0x40) && hsp->mode) + printk(KERN_WARNING "Teles: HSCX RDO mode=%d\n",hsp->mode); + if (!r & 0x20) + printk(KERN_WARNING "Teles: HSCX CRC error\n"); + if (hsp->rcvibh) + BufPoolRelease(hsp->rcvibh); + hsp->rcvibh = NULL; + WRITEHSCX_CMDR(hsp->membase, hsp->iobase, hsp->hscx, + 0x80); + goto afterRME; + } + if (!hsp->rcvibh) + if (BufPoolGet(&hsp->rcvibh, &hsp->rbufpool, + GFP_ATOMIC, (void *) 1, 1)) { + printk(KERN_WARNING + "HSCX RME out of buffers at %ld\n", + jiffies); + WRITEHSCX_CMDR(hsp->membase, hsp->iobase, + hsp->hscx, 0x80); + goto afterRME; + } else + hsp->rcvptr = 0; + + count = READHSCX(hsp->membase, sp->iobase, hsp->hscx, + HSCX_RBCL) & 0x1f; + if (count == 0) + count = 32; + hscx_empty_fifo(hsp, count); + hsp->rcvibh->datasize = hsp->rcvptr - 1; + BufQueueLink(&hsp->rq, hsp->rcvibh); + hsp->rcvibh = NULL; + hscx_sched_event(hsp, HSCX_RCVBUFREADY); + } + afterRME: + if (val & 0x40) { /* RPF */ + if (!hsp->rcvibh) { + if (hsp->mode == 1) + err=BufPoolGet(&hsp->rcvibh, &hsp->smallpool, + GFP_ATOMIC, (void *)1, 2); + else + err=BufPoolGet(&hsp->rcvibh, &hsp->rbufpool, + GFP_ATOMIC, (void *)1, 2); + + if (err) { + printk(KERN_WARNING + "HSCX RPF out of buffers at %ld\n", + jiffies); + WRITEHSCX_CMDR(hsp->membase, hsp->iobase, + hsp->hscx, 0x80); + goto afterRPF; + } else + hsp->rcvptr = 0; + } + + hscx_empty_fifo(hsp, 32); + if (hsp->mode == 1) { + /* receive audio data */ + hsp->rcvibh->datasize = hsp->rcvptr; + BufQueueLink(&hsp->rq, hsp->rcvibh); + hsp->rcvibh = NULL; + hscx_sched_event(hsp, HSCX_RCVBUFREADY); + } + + } + afterRPF: + if (val & 0x10) { /* XPR */ + if (hsp->xmtibh) + if (hsp->xmtibh->datasize > hsp->sendptr) { + hscx_fill_fifo(hsp); + goto afterXPR; + } else { + if (hsp->releasebuf) + BufPoolRelease(hsp->xmtibh); + hsp->sendptr = 0; + if (hsp->st->l4.l1writewakeup) + hsp->st->l4.l1writewakeup(hsp->st); + hsp->xmtibh = NULL; + } + if (!BufQueueUnlink(&hsp->xmtibh, &hsp->sq)) { + hsp->releasebuf = !0; + hscx_fill_fifo(hsp); + } else + hscx_sched_event(hsp, HSCX_XMTBUFREADY); + } + afterXPR: +} + +/* + * ISAC stuff goes here + */ + +static void +isac_sched_event(struct IsdnCardState *sp, int event) +{ + sp->event |= 1 << event; + queue_task_irq_off(&sp->tqueue, &tq_immediate); + mark_bh(IMMEDIATE_BH); +} + +static void +empty_fifo(struct IsdnCardState *sp, int count) +{ + byte *ptr; + struct BufHeader *ibh = sp->rcvibh; + + if (sp->debug) + printk(KERN_DEBUG "empty_fifo\n"); + + if (sp->rcvptr >= 3072) { + printk(KERN_WARNING "empty_fifo rcvptr %d\n", sp->rcvptr); + return; + } + ptr = DATAPTR(ibh); + ptr += sp->rcvptr; + sp->rcvptr += count; + + if (sp->membase) { +#ifdef DCHAN_VERBOSE + printk(KERN_DEBUG "empty_fifo "); + while (count--) { + *ptr = readisac_0(sp->membase, 0x0); + printk("%2x ", *ptr); + ptr++; + } + printk("\n"); +#else + while (count--) + *ptr++ = readisac_0(sp->membase, 0x0); +#endif + writeisac_0(sp->membase, ISAC_CMDR, 0x80); + } else { +#ifdef DCHAN_VERBOSE + int i; + printk(KERN_DEBUG "empty_fifo "); + readisac_s(sp->iobase, 0x3e, ptr, count); + for (i = 0; i < count; i++) + printk("%2x ", ptr[i]); + printk("\n"); +#else + readisac_s(sp->iobase, 0x3e, ptr, count); +#endif + writeisac_3(sp->iobase, ISAC_CMDR, 0x80); + } +} + +static void +fill_fifo(struct IsdnCardState *sp) +{ + struct BufHeader *ibh; + int count, more; + byte *ptr; + + if (sp->debug) + printk(KERN_DEBUG "fill_fifo\n"); + + ibh = sp->xmtibh; + if (!ibh) + return; + + count = ibh->datasize - sp->sendptr; + if (count <= 0) + return; + if (count >= 3072) + return; + + more = 0; + if (count > 32) { + more = !0; + count = 32; + } + ptr = DATAPTR(ibh); + ptr += sp->sendptr; + sp->sendptr += count; + + if (sp->membase) { +#ifdef DCHAN_VERBOSE + printk(KERN_DEBUG "fill_fifo "); + while (count--) { + writeisac_0(sp->membase, 0x0, *ptr); + printk("%2x ", *ptr); + ptr++; + } + printk("\n"); +#else + while (count--) + writeisac_0(sp->membase, 0x0, *ptr++); +#endif + writeisac_0(sp->membase, ISAC_CMDR, more ? 0x8 : 0xa); + } else { +#ifdef DCHAN_VERBOSE + int i; + writeisac_s(sp->iobase, 0x3e, ptr, count); + printk(KERN_DEBUG "fill_fifo "); + for (i = 0; i < count; i++) + printk("%2x ", ptr[i]); + printk("\n"); +#else + writeisac_s(sp->iobase, 0x3e, ptr, count); +#endif + writeisac_3(sp->iobase, ISAC_CMDR, more ? 0x8 : 0xa); + } +} + +static int +act_wanted(struct IsdnCardState *sp) +{ + struct PStack *st; + + st = sp->stlist; + while (st) + if (st->l1.act_state) + return (!0); + else + st = st->next; + return (0); +} + +static void +ph_command(struct IsdnCardState *sp, unsigned int command) +{ + printk(KERN_DEBUG "ph_command %d\n", command); + WRITEISAC(sp->membase, sp->iobase, ISAC_CIX0, (command << 2) | 3); +} + +static void +isac_new_ph(struct IsdnCardState *sp) +{ + int enq; + + enq = act_wanted(sp); + + switch (sp->ph_state) { + case (0): + case (6): + if (enq) + ph_command(sp, 0); + else + ph_command(sp, 15); + break; + case (7): + if (enq) + ph_command(sp, 9); + break; + case (12): + ph_command(sp, 8); + sp->ph_active = 5; + isac_sched_event(sp, ISAC_PHCHANGE); + if (!sp->xmtibh) + if (!BufQueueUnlink(&sp->xmtibh, &sp->sq)) + sp->sendptr = 0; + if (sp->xmtibh) + fill_fifo(sp); + break; + case (13): + ph_command(sp, 9); + sp->ph_active = 5; + isac_sched_event(sp, ISAC_PHCHANGE); + if (!sp->xmtibh) + if (!BufQueueUnlink(&sp->xmtibh, &sp->sq)) + sp->sendptr = 0; + if (sp->xmtibh) + fill_fifo(sp); + break; + case (4): + case (8): + break; + default: + sp->ph_active = 0; + break; + } +} + +static void +teles_interrupt(int intno, void *dev_id, struct pt_regs *regs) +{ + byte val, r, exval; + struct IsdnCardState *sp; + unsigned int count; + struct HscxState *hsp; + + sp = (struct IsdnCardState *) irq2dev_map[intno]; + + if (!sp) { + printk(KERN_WARNING "Teles: Spurious interrupt!\n"); + return; + } + val = READHSCX(sp->membase, sp->iobase, 1, HSCX_ISTA); + + if (val & 0x01) { + hsp = sp->hs + 1; + exval = READHSCX(sp->membase, sp->iobase, 1, HSCX_EXIR); + if (exval == 0x40) { + if (hsp->mode == 1) + hscx_fill_fifo(hsp); + else { + /* Here we lost an TX interrupt, so + * restart transmitting the whole frame. + */ + hsp->sendptr = 0; + WRITEHSCX_CMDR(hsp->membase, hsp->iobase, + hsp->hscx, 0x01); + printk(KERN_DEBUG "HSCX B EXIR %x\n", exval); + } + } else + printk(KERN_WARNING "HSCX B EXIR %x\n", exval); + } + if (val & 0xf8) { + if (sp->debug) + printk(KERN_DEBUG "HSCX B interrupt %x\n", val); + hscx_interrupt(sp, val, 1); + } + if (val & 0x02) { + hsp = sp->hs; + exval = READHSCX(sp->membase, sp->iobase, 0, HSCX_EXIR); + if (exval == 0x40) { + if (hsp->mode == 1) + hscx_fill_fifo(hsp); + else { + /* Here we lost an TX interrupt, so + * restart transmitting the whole frame. + */ + hsp->sendptr = 0; + WRITEHSCX_CMDR(hsp->membase, hsp->iobase, + hsp->hscx, 0x01); + printk(KERN_DEBUG "HSCX A EXIR %x\n", exval); + } + } else + printk(KERN_WARNING "HSCX A EXIR %x\n", exval); + } + if (val & 0x04) { + val = READHSCX(sp->membase, sp->iobase, 0, HSCX_ISTA); + if (sp->debug) + printk(KERN_DEBUG "HSCX A interrupt %x\n", + val); + hscx_interrupt(sp, val, 0); + } + + val = READISAC(sp->membase, sp->iobase, ISAC_ISTA); + + if (sp->debug) + printk(KERN_DEBUG "ISAC interrupt %x\n", val); + + if (val & 0x80) { /* RME */ + + r = READISAC(sp->membase, sp->iobase, ISAC_RSTA); + if ((r & 0x70) != 0x20) { + if (r & 0x40) + printk(KERN_WARNING "Teles: ISAC RDO\n"); + if (!r & 0x20) + printk(KERN_WARNING "Teles: ISAC CRC error\n"); + if (sp->rcvibh) + BufPoolRelease(sp->rcvibh); + sp->rcvibh = NULL; + WRITEISAC(sp->membase, sp->iobase, ISAC_CMDR, 0x80); + goto afterRME; + } + if (!sp->rcvibh) + if (BufPoolGet(&(sp->rcvibh), &(sp->rbufpool), + GFP_ATOMIC, + (void *) 1, 3)) { + printk(KERN_WARNING + "ISAC RME out of buffers!\n"); + WRITEISAC(sp->membase, sp->iobase, + ISAC_CMDR, 0x80); + goto afterRME; + } else + sp->rcvptr = 0; + + count = READISAC(sp->membase, sp->iobase, ISAC_RBCL) & 0x1f; + if (count == 0) + count = 32; + empty_fifo(sp, count); + sp->rcvibh->datasize = sp->rcvptr; + BufQueueLink(&(sp->rq), sp->rcvibh); + sp->rcvibh = NULL; + isac_sched_event(sp, ISAC_RCVBUFREADY); + } + afterRME: + if (val & 0x40) { /* RPF */ + if (!sp->rcvibh) + if (BufPoolGet(&(sp->rcvibh), &(sp->rbufpool), + GFP_ATOMIC, + (void *) 1, 4)) { + printk(KERN_WARNING + "ISAC RME out of buffers!\n"); + WRITEISAC(sp->membase, sp->iobase, + ISAC_CMDR, 0x80); + goto afterRPF; + } else + sp->rcvptr = 0; + empty_fifo(sp, 32); + } + afterRPF: + if (val & 0x20) { + } + if (val & 0x10) { /* XPR */ + if (sp->xmtibh) + if (sp->xmtibh->datasize > sp->sendptr) { + fill_fifo(sp); + goto afterXPR; + } else { + if (sp->releasebuf) + BufPoolRelease(sp->xmtibh); + sp->xmtibh = NULL; + sp->sendptr = 0; + } + if (!BufQueueUnlink(&sp->xmtibh, &sp->sq)) { + sp->releasebuf = !0; + fill_fifo(sp); + } else + isac_sched_event(sp, ISAC_XMTBUFREADY); + } + afterXPR: + if (val & 0x04) { /* CISQ */ + sp->ph_state = (READISAC(sp->membase, sp->iobase, ISAC_CIX0) + >> 2) & 0xf; + printk(KERN_DEBUG "l1state %d\n", sp->ph_state); + isac_new_ph(sp); + } + if (sp->membase) { + writeisac_0(sp->membase, ISAC_MASK, 0xFF); + writehscx_0(sp->membase, 0, HSCX_MASK, 0xFF); + writehscx_0(sp->membase, 1, HSCX_MASK, 0xFF); + writeisac_0(sp->membase, ISAC_MASK, 0x0); + writehscx_0(sp->membase, 0, HSCX_MASK, 0x0); + writehscx_0(sp->membase, 1, HSCX_MASK, 0x0); + } else { + writeisac_3(sp->iobase, ISAC_MASK, 0xFF); + writehscx_3(sp->iobase, 0, HSCX_MASK, 0xFF); + writehscx_3(sp->iobase, 1, HSCX_MASK, 0xFF); + writeisac_3(sp->iobase, ISAC_MASK, 0x0); + writehscx_3(sp->iobase, 0, HSCX_MASK, 0x0); + writehscx_3(sp->iobase, 1, HSCX_MASK, 0x0); + } +} + +/* + * soft interrupt + */ + +static void +act_ivated(struct IsdnCardState *sp) +{ + struct PStack *st; + + st = sp->stlist; + while (st) { + if (st->l1.act_state == 1) { + st->l1.act_state = 2; + st->l1.l1man(st, PH_ACTIVATE, NULL); + } + st = st->next; + } +} + +static void +process_new_ph(struct IsdnCardState *sp) +{ + if (sp->ph_active == 5) + act_ivated(sp); +} + +static void +process_xmt(struct IsdnCardState *sp) +{ + struct PStack *stptr; + + if (sp->xmtibh) + return; + + stptr = sp->stlist; + while (stptr != NULL) + if (stptr->l1.requestpull) { + stptr->l1.requestpull = 0; + stptr->l1.l1l2(stptr, PH_PULL_ACK, NULL); + break; + } else + stptr = stptr->next; +} + +static void +process_rcv(struct IsdnCardState *sp) +{ + struct BufHeader *ibh, *cibh; + struct PStack *stptr; + byte *ptr; + int found, broadc; + char tmp[64]; + + while (!BufQueueUnlink(&ibh, &sp->rq)) { + stptr = sp->stlist; + ptr = DATAPTR(ibh); + broadc = (ptr[1] >> 1) == 127; + + if (broadc && sp->dlogflag && (!(ptr[0] >> 2))) + dlogframe(sp, ptr + 3, ibh->datasize - 3, + "Q.931 frame network->user broadcast"); + + if (broadc) { + while (stptr != NULL) { + if ((ptr[0] >> 2) == stptr->l2.sap) + if (!BufPoolGet(&cibh, &sp->rbufpool, GFP_ATOMIC, + (void *) 1, 5)) { + memcpy(DATAPTR(cibh), DATAPTR(ibh), ibh->datasize); + cibh->datasize = ibh->datasize; + stptr->l1.l1l2(stptr, PH_DATA, cibh); + } else + printk(KERN_WARNING "isdn broadcast buffer shortage\n"); + stptr = stptr->next; + } + BufPoolRelease(ibh); + } else { + found = 0; + while (stptr != NULL) + if (((ptr[0] >> 2) == stptr->l2.sap) && + ((ptr[1] >> 1) == stptr->l2.tei)) { + stptr->l1.l1l2(stptr, PH_DATA, ibh); + found = !0; + break; + } else + stptr = stptr->next; + if (!found) { + /* BD 10.10.95 + * Print out D-Channel msg not processed + * by isdn4linux + */ + + if ((!(ptr[0] >> 2)) && (!(ptr[2] & 0x01))) { + sprintf(tmp, "Q.931 frame network->user with tei %d (not for us)", ptr[1] >> 1); + dlogframe(sp, ptr + 4, ibh->datasize - 4, tmp); + } + BufPoolRelease(ibh); + } + } + + } + +} + +static void +isac_bh(struct IsdnCardState *sp) +{ + if (!sp) + return; + + if (clear_bit(ISAC_PHCHANGE, &sp->event)) + process_new_ph(sp); + if (clear_bit(ISAC_RCVBUFREADY, &sp->event)) + process_rcv(sp); + if (clear_bit(ISAC_XMTBUFREADY, &sp->event)) + process_xmt(sp); +} + + +static void +hscx_process_xmt(struct HscxState *hsp) +{ + struct PStack *st = hsp->st; + + if (hsp->xmtibh) + return; + + if (st->l1.requestpull) { + st->l1.requestpull = 0; + st->l1.l1l2(st, PH_PULL_ACK, NULL); + } + if (!hsp->active) + if ((!hsp->xmtibh) && (!hsp->sq.head)) + modehscx(hsp, 0, 0); +} + +static void +hscx_process_rcv(struct HscxState *hsp) +{ + struct BufHeader *ibh; + +#ifdef DEBUG_MAGIC + if (hsp->magic != 301270) { + printk(KERN_DEBUG "hscx_process_rcv magic not 301270\n"); + return; + } +#endif + while (!BufQueueUnlink(&ibh, &hsp->rq)) { + hsp->st->l1.l1l2(hsp->st, PH_DATA, ibh); + } +} + +static void +hscx_bh(struct HscxState *hsp) +{ + + if (!hsp) + return; + + if (clear_bit(HSCX_RCVBUFREADY, &hsp->event)) + hscx_process_rcv(hsp); + if (clear_bit(HSCX_XMTBUFREADY, &hsp->event)) + hscx_process_xmt(hsp); + +} + +/* + * interrupt stuff ends here + */ + +static void +restart_ph(struct IsdnCardState *sp) +{ + switch (sp->ph_active) { + case (0): + if (sp->ph_state == 6) + ph_command(sp, 0); + else + ph_command(sp, 1); + sp->ph_active = 1; + break; + } +} + +static void +initisac(unsigned int cardmem, int iobase) +{ + if (cardmem) { + writeisac_0(cardmem, ISAC_MASK, 0xff); + writeisac_0(cardmem, ISAC_ADF2, 0x0); + writeisac_0(cardmem, ISAC_SPCR, 0xa); + writeisac_0(cardmem, ISAC_ADF1, 0x2); + writeisac_0(cardmem, ISAC_STCR, 0x70); + writeisac_0(cardmem, ISAC_MODE, 0xc9); + writeisac_0(cardmem, ISAC_CMDR, 0x41); + writeisac_0(cardmem, ISAC_CIX0, (1 << 2) | 3); + } else { + writeisac_3(iobase, ISAC_MASK, 0xff); + writeisac_3(iobase, ISAC_ADF2, 0x80); + writeisac_3(iobase, ISAC_SQXR, 0x2f); + writeisac_3(iobase, ISAC_SPCR, 0x00); + writeisac_3(iobase, ISAC_ADF1, 0x02); + writeisac_3(iobase, ISAC_STCR, 0x70); + writeisac_3(iobase, ISAC_MODE, 0xc9); + writeisac_3(iobase, ISAC_TIMR, 0x00); + writeisac_3(iobase, ISAC_ADF1, 0x00); + writeisac_3(iobase, ISAC_CMDR, 0x41); + writeisac_3(iobase, ISAC_CIX0, (1 << 2) | 3); + } +} + +static int +checkcard(int cardnr) +{ + int timout; + byte cfval, val; + struct IsdnCard *card = cards + cardnr; + + if (card->membase) + if (card->membase < 0x10000) { + card->membase <<= 4; + printk(KERN_INFO + "Teles membase configured DOSish, assuming 0x%x\n", + card->membase); + } + if (!card->iobase) { + if (card->membase) { + printk(KERN_NOTICE + "Teles 8 assumed, mem: %x irq: %d proto: %s\n", + card->membase, card->interrupt, + (card->protocol == ISDN_PTYPE_1TR6) ? + "1TR6" : "EDSS1"); + printk(KERN_INFO "HSCX version A:%x B:%x\n", + readhscx_0(card->membase, 0, HSCX_VSTR) & 0xf, + readhscx_0(card->membase, 1, HSCX_VSTR) & 0xf); + } + } else { + switch (card->iobase) { + case 0x180: + case 0x280: + case 0x380: + card->iobase |= 0xc00; + break; + } + if (card->membase) { /* 16.0 */ + if (check_region(card->iobase, 8)) { + printk(KERN_WARNING + "teles: ports %x-%x already in use\n", + card->iobase, + card->iobase + 8 ); + return -1; + } + } else { /* 16.3 */ + if (check_region(card->iobase, 16)) { + printk(KERN_WARNING + "teles: 16.3 ports %x-%x already in use\n", + card->iobase, + card->iobase + 16 ); + return -1; + } + if (check_region((card->iobase - 0xc00) , 32)) { + printk(KERN_WARNING + "teles: 16.3 ports %x-%x already in use\n", + card->iobase - 0xc00, + card->iobase - 0xc00 + 32); + return -1; + } + if (check_region((card->iobase - 0x800) , 32)) { + printk(KERN_WARNING + "teles: 16.3 ports %x-%x already in use\n", + card->iobase - 0x800, + card->iobase - 0x800 + 32); + return -1; + } + if (check_region((card->iobase - 0x400) , 32)) { + printk(KERN_WARNING + "teles: 16.3 ports %x-%x already in use\n", + card->iobase - 0x400, + card->iobase - 0x400 + 32); + return -1; + } + } + switch (card->interrupt) { + case 2: + cfval = 0x00; + break; + case 3: + cfval = 0x02; + break; + case 4: + cfval = 0x04; + break; + case 5: + cfval = 0x06; + break; + case 10: + cfval = 0x08; + break; + case 11: + cfval = 0x0A; + break; + case 12: + cfval = 0x0C; + break; + case 15: + cfval = 0x0E; + break; + default: + cfval = 0x00; + break; + } + if (card->membase) { + cfval |= (card->membase >> 9) & 0xF0; + } + if (bytein(card->iobase + 0) != 0x51) { + printk(KERN_INFO "XXX Byte at %x is %x\n", + card->iobase + 0, + bytein(card->iobase + 0)); + return -2; + } + if (bytein(card->iobase + 1) != 0x93) { + printk(KERN_INFO "XXX Byte at %x is %x\n", + card->iobase + 1, + bytein(card->iobase + 1)); + return -2; + } + val = bytein(card->iobase + 2); /* 0x1e=without AB + * 0x1f=with AB + * 0x1c 16.3 ??? + */ + if (val != 0x1c && val != 0x1e && val != 0x1f) { + printk(KERN_INFO "XXX Byte at %x is %x\n", + card->iobase + 2, + bytein(card->iobase + 2)); + return -2; + } + if (card->membase) { /* 16.0 */ + request_region(card->iobase, 8, "teles 16.0"); + } else { + request_region(card->iobase, 16, "teles 16.3"); + request_region(card->iobase - 0xC00, 32, "teles HSCX0"); + request_region(card->iobase - 0x800, 32, "teles HSCX1"); + request_region(card->iobase - 0x400, 32, "teles ISAC"); + } + cli(); + timout = jiffies + (HZ / 10) + 1; + byteout(card->iobase + 4, cfval); + sti(); + while (jiffies <= timout); + + cli(); + timout = jiffies + (HZ / 10) + 1; + byteout(card->iobase + 4, cfval | 1); + sti(); + while (jiffies <= timout); + + if (card->membase) + printk(KERN_NOTICE + "Teles 16.0 found, io: %x mem: %x irq: %d proto: %s\n", + card->iobase, card->membase, + card->interrupt, + (card->protocol == ISDN_PTYPE_1TR6) ? + "1TR6" : "EDSS1"); + else + printk(KERN_NOTICE + "Teles 16.3 found, io: %x irq: %d proto: %s\n", + card->iobase, card->interrupt, + (card->protocol == ISDN_PTYPE_1TR6) ? + "1TR6" : "EDSS1"); + printk(KERN_INFO "HSCX version A:%x B:%x\n", + READHSCX(card->membase, card->iobase, 0, + HSCX_VSTR) & 0xf, + READHSCX(card->membase, card->iobase, 1, + HSCX_VSTR) & 0xf); + + } + if (card->membase) { + cli(); + timout = jiffies + (HZ / 5) + 1; + writeb(0, card->membase + 0x80); + sti(); + while (jiffies <= timout); + + cli(); + writeb(1, card->membase + 0x80); + timout = jiffies + (HZ / 5) + 1; + sti(); + while (jiffies <= timout); + } + return (0); +} + +void +modehscx(struct HscxState *hs, int mode, + int ichan) +{ + struct IsdnCardState *sp = hs->sp; + int hscx = hs->hscx; + + printk(KERN_DEBUG "modehscx hscx %d mode %d ichan %d\n", + hscx, mode, ichan); + + hs->mode = mode; + if (sp->membase) { + /* What's that ??? KKeil */ + if (hscx == 0) + ichan = 1 - ichan; /* raar maar waar... */ + writehscx_0(sp->membase, hscx, HSCX_CCR1, 0x85); + writehscx_0(sp->membase, hscx, HSCX_XAD1, 0xFF); + writehscx_0(sp->membase, hscx, HSCX_XAD2, 0xFF); + writehscx_0(sp->membase, hscx, HSCX_RAH2, 0xFF); + writehscx_0(sp->membase, hscx, HSCX_XBCH, 0x0); + + switch (mode) { + case (0): + writehscx_0(sp->membase, hscx, HSCX_CCR2, 0x30); + writehscx_0(sp->membase, hscx, HSCX_TSAX, 0xff); + writehscx_0(sp->membase, hscx, HSCX_TSAR, 0xff); + writehscx_0(sp->membase, hscx, HSCX_XCCR, 7); + writehscx_0(sp->membase, hscx, HSCX_RCCR, 7); + writehscx_0(sp->membase, hscx, HSCX_MODE, 0x84); + break; + case (1): + if (ichan == 0) { + writehscx_0(sp->membase, hscx, HSCX_CCR2, 0x30); + writehscx_0(sp->membase, hscx, HSCX_TSAX, 0x7); + writehscx_0(sp->membase, hscx, HSCX_TSAR, 0x7); + writehscx_0(sp->membase, hscx, HSCX_XCCR, 7); + writehscx_0(sp->membase, hscx, HSCX_RCCR, 7); + } else { + writehscx_0(sp->membase, hscx, HSCX_CCR2, 0x30); + writehscx_0(sp->membase, hscx, HSCX_TSAX, 0x3); + writehscx_0(sp->membase, hscx, HSCX_TSAR, 0x3); + writehscx_0(sp->membase, hscx, HSCX_XCCR, 7); + writehscx_0(sp->membase, hscx, HSCX_RCCR, 7); + } + writehscx_0(sp->membase, hscx, HSCX_MODE, 0xe4); + writehscx_0(sp->membase, hscx, HSCX_CMDR, 0x41); + break; + case (2): + if (ichan == 0) { + writehscx_0(sp->membase, hscx, HSCX_CCR2, 0x30); + writehscx_0(sp->membase, hscx, HSCX_TSAX, 0x7); + writehscx_0(sp->membase, hscx, HSCX_TSAR, 0x7); + writehscx_0(sp->membase, hscx, HSCX_XCCR, 7); + writehscx_0(sp->membase, hscx, HSCX_RCCR, 7); + } else { + writehscx_0(sp->membase, hscx, HSCX_CCR2, 0x30); + writehscx_0(sp->membase, hscx, HSCX_TSAX, 0x3); + writehscx_0(sp->membase, hscx, HSCX_TSAR, 0x3); + writehscx_0(sp->membase, hscx, HSCX_XCCR, 7); + writehscx_0(sp->membase, hscx, HSCX_RCCR, 7); + } + writehscx_0(sp->membase, hscx, HSCX_MODE, 0x8c); + writehscx_0(sp->membase, hscx, HSCX_CMDR, 0x41); + break; + } + writehscx_0(sp->membase, hscx, HSCX_ISTA, 0x00); + } else { + writehscx_3(sp->iobase, hscx, HSCX_CCR1, 0x85); + writehscx_3(sp->iobase, hscx, HSCX_XAD1, 0xFF); + writehscx_3(sp->iobase, hscx, HSCX_XAD2, 0xFF); + writehscx_3(sp->iobase, hscx, HSCX_RAH2, 0xFF); + writehscx_3(sp->iobase, hscx, HSCX_XBCH, 0x00); + writehscx_3(sp->iobase, hscx, HSCX_RLCR, 0x00); + + switch (mode) { + case (0): + writehscx_3(sp->iobase, hscx, HSCX_CCR2, 0x30); + writehscx_3(sp->iobase, hscx, HSCX_TSAX, 0xff); + writehscx_3(sp->iobase, hscx, HSCX_TSAR, 0xff); + writehscx_3(sp->iobase, hscx, HSCX_XCCR, 7); + writehscx_3(sp->iobase, hscx, HSCX_RCCR, 7); + writehscx_3(sp->iobase, hscx, HSCX_MODE, 0x84); + break; + case (1): + if (ichan == 0) { + writehscx_3(sp->iobase, hscx, HSCX_CCR2, 0x30); + writehscx_3(sp->iobase, hscx, HSCX_TSAX, 0x2f); + writehscx_3(sp->iobase, hscx, HSCX_TSAR, 0x2f); + writehscx_3(sp->iobase, hscx, HSCX_XCCR, 7); + writehscx_3(sp->iobase, hscx, HSCX_RCCR, 7); + } else { + writehscx_3(sp->iobase, hscx, HSCX_CCR2, 0x30); + writehscx_3(sp->iobase, hscx, HSCX_TSAX, 0x3); + writehscx_3(sp->iobase, hscx, HSCX_TSAR, 0x3); + writehscx_3(sp->iobase, hscx, HSCX_XCCR, 7); + writehscx_3(sp->iobase, hscx, HSCX_RCCR, 7); + } + writehscx_3(sp->iobase, hscx, HSCX_MODE, 0xe4); + writehscx_3(sp->iobase, hscx, HSCX_CMDR, 0x41); + break; + case (2): + if (ichan == 0) { + writehscx_3(sp->iobase, hscx, HSCX_CCR2, 0x30); + writehscx_3(sp->iobase, hscx, HSCX_TSAX, 0x2f); + writehscx_3(sp->iobase, hscx, HSCX_TSAR, 0x2f); + writehscx_3(sp->iobase, hscx, HSCX_XCCR, 7); + writehscx_3(sp->iobase, hscx, HSCX_RCCR, 7); + } else { + writehscx_3(sp->iobase, hscx, HSCX_CCR2, 0x30); + writehscx_3(sp->iobase, hscx, HSCX_TSAX, 0x3); + writehscx_3(sp->iobase, hscx, HSCX_TSAR, 0x3); + writehscx_3(sp->iobase, hscx, HSCX_XCCR, 7); + writehscx_3(sp->iobase, hscx, HSCX_RCCR, 7); + } + writehscx_3(sp->iobase, hscx, HSCX_MODE, 0x8c); + writehscx_3(sp->iobase, hscx, HSCX_CMDR, 0x41); + break; + } + writehscx_3(sp->iobase, hscx, HSCX_ISTA, 0x00); + } +} + +void +teles_addlist(struct IsdnCardState *sp, + struct PStack *st) +{ + st->next = sp->stlist; + sp->stlist = st; +} + +void +teles_rmlist(struct IsdnCardState *sp, + struct PStack *st) +{ + struct PStack *p; + + if (sp->stlist == st) + sp->stlist = st->next; + else { + p = sp->stlist; + while (p) + if (p->next == st) { + p->next = st->next; + return; + } else + p = p->next; + } +} + + +static void +teles_l2l1(struct PStack *st, int pr, + struct BufHeader *ibh) +{ + struct IsdnCardState *sp = (struct IsdnCardState *) + st->l1.hardware; + + + switch (pr) { + case (PH_DATA): + if (sp->xmtibh) + BufQueueLink(&sp->sq, ibh); + else { + sp->xmtibh = ibh; + sp->sendptr = 0; + sp->releasebuf = !0; + fill_fifo(sp); + } + break; + case (PH_DATA_PULLED): + if (sp->xmtibh) { + printk(KERN_DEBUG "teles_l2l1: this shouldn't happen\n"); + break; + } + sp->xmtibh = ibh; + sp->sendptr = 0; + sp->releasebuf = 0; + fill_fifo(sp); + break; + case (PH_REQUEST_PULL): + if (!sp->xmtibh) { + st->l1.requestpull = 0; + st->l1.l1l2(st, PH_PULL_ACK, NULL); + } else + st->l1.requestpull = !0; + break; + } +} + +static void +check_ph_act(struct IsdnCardState *sp) +{ + struct PStack *st = sp->stlist; + + while (st) { + if (st->l1.act_state) + return; + st = st->next; + } + sp->ph_active = 0; +} + +static void +teles_manl1(struct PStack *st, int pr, + void *arg) +{ + struct IsdnCardState *sp = (struct IsdnCardState *) + st->l1.hardware; + long flags; + + switch (pr) { + case (PH_ACTIVATE): + save_flags(flags); + cli(); + if (sp->ph_active == 5) { + st->l1.act_state = 2; + restore_flags(flags); + st->l1.l1man(st, PH_ACTIVATE, NULL); + } else { + st->l1.act_state = 1; + if (sp->ph_active == 0) + restart_ph(sp); + restore_flags(flags); + } + break; + case (PH_DEACTIVATE): + st->l1.act_state = 0; + check_ph_act(sp); + break; + } +} + +static void +teles_l2l1discardq(struct PStack *st, int pr, + void *heldby, int releasetoo) +{ + struct IsdnCardState *sp = (struct IsdnCardState *) st->l1.hardware; + +#ifdef DEBUG_MAGIC + if (sp->magic != 301271) { + printk(KERN_DEBUG "isac_discardq magic not 301271\n"); + return; + } +#endif + + BufQueueDiscard(&sp->sq, pr, heldby, releasetoo); +} + +void +setstack_teles(struct PStack *st, struct IsdnCardState *sp) +{ + st->l1.hardware = sp; + st->l1.sbufpool = &(sp->sbufpool); + st->l1.rbufpool = &(sp->rbufpool); + st->l1.smallpool = &(sp->smallpool); + st->protocol = sp->teistack->protocol; + + setstack_tei(st); + + st->l1.stlistp = &(sp->stlist); + st->l1.act_state = 0; + st->l2.l2l1 = teles_l2l1; + st->l2.l2l1discardq = teles_l2l1discardq; + st->ma.manl1 = teles_manl1; + st->l1.requestpull = 0; +} + +void +init_hscxstate(struct IsdnCardState *sp, + int hscx) +{ + struct HscxState *hsp = sp->hs + hscx; + + hsp->sp = sp; + hsp->hscx = hscx; + hsp->membase = sp->membase; + hsp->iobase = sp->iobase; + + hsp->tqueue.next = 0; + hsp->tqueue.sync = 0; + hsp->tqueue.routine = (void *) (void *) hscx_bh; + hsp->tqueue.data = hsp; + + hsp->inuse = 0; + hsp->init = 0; + hsp->active = 0; + +#ifdef DEBUG_MAGIC + hsp->magic = 301270; +#endif +} + +void +initcard(int cardnr) +{ + struct IsdnCardState *sp; + struct IsdnCard *card = cards + cardnr; + + sp = (struct IsdnCardState *) + Smalloc(sizeof(struct IsdnCardState), GFP_KERNEL, + "struct IsdnCardState"); + + sp->membase = card->membase; + sp->iobase = card->iobase; + sp->cardnr = cardnr; + + BufPoolInit(&sp->sbufpool, ISAC_SBUF_ORDER, ISAC_SBUF_BPPS, + ISAC_SBUF_MAXPAGES); + BufPoolInit(&sp->rbufpool, ISAC_RBUF_ORDER, ISAC_RBUF_BPPS, + ISAC_RBUF_MAXPAGES); + BufPoolInit(&sp->smallpool, ISAC_SMALLBUF_ORDER, ISAC_SMALLBUF_BPPS, + ISAC_SMALLBUF_MAXPAGES); + + sp->dlogspace = Smalloc(4096, GFP_KERNEL, "dlogspace"); + + initisac(card->membase, card->iobase); + + sp->rcvibh = NULL; + sp->rcvptr = 0; + sp->xmtibh = NULL; + sp->sendptr = 0; + sp->event = 0; + sp->tqueue.next = 0; + sp->tqueue.sync = 0; + sp->tqueue.routine = (void *) (void *) isac_bh; + sp->tqueue.data = sp; + + BufQueueInit(&sp->rq); + BufQueueInit(&sp->sq); + + sp->stlist = NULL; + + sp->ph_active = 0; + + sp->dlogflag = 0; + sp->debug = 0; + + sp->releasebuf = 0; +#ifdef DEBUG_MAGIC + sp->magic = 301271; +#endif + + cards[sp->cardnr].sp = sp; + + init_hscxstate(sp, 0); + init_hscxstate(sp, 1); + + modehscx(sp->hs, 0, 0); + modehscx(sp->hs + 1, 0, 0); + + WRITEISAC(sp->membase, sp->iobase, ISAC_MASK, 0x0); +} + +static int +get_irq(int cardnr) +{ + struct IsdnCard *card = cards + cardnr; + long flags; + + save_flags(flags); + cli(); + if (request_irq(card->interrupt, &teles_interrupt, + SA_INTERRUPT, "teles", NULL)) { + printk(KERN_WARNING "Teles couldn't get interrupt %d\n", + card->interrupt); + restore_flags(flags); + return (!0); + } + irq2dev_map[card->interrupt] = (void *) card->sp; + restore_flags(flags); + return (0); +} + +static void +release_irq(int cardnr) +{ + struct IsdnCard *card = cards + cardnr; + + irq2dev_map[card->interrupt] = NULL; + free_irq(card->interrupt, NULL); +} + +void +close_hscxstate(struct HscxState *hs) +{ + modehscx(hs, 0, 0); + hs->inuse = 0; + + if (hs->init) { + BufPoolFree(&hs->smallpool); + BufPoolFree(&hs->rbufpool); + BufPoolFree(&hs->sbufpool); + } + hs->init = 0; +} + +void +closecard(int cardnr) +{ + struct IsdnCardState *sp = cards[cardnr].sp; + + cards[cardnr].sp = NULL; + + Sfree(sp->dlogspace); + + BufPoolFree(&sp->smallpool); + BufPoolFree(&sp->rbufpool); + BufPoolFree(&sp->sbufpool); + + close_hscxstate(sp->hs + 1); + close_hscxstate(sp->hs); + + if (cards[cardnr].iobase) + if (cards[cardnr].membase) { /* 16.0 */ + release_region(cards[cardnr].iobase, 8); + } else { + release_region(cards[cardnr].iobase, 16); + release_region(cards[cardnr].iobase - 0xC00, 32); + release_region(cards[cardnr].iobase - 0x800, 32); + release_region(cards[cardnr].iobase - 0x400, 32); + } + + Sfree((void *) sp); +} + +void +teles_shiftcards(int idx) +{ + int i; + + for (i = idx; i < 15; i++) + memcpy(&cards[i],&cards[i+1],sizeof(cards[i])); +} + +int +teles_inithardware(void) +{ + int foundcards = 0; + int i = 0; + + while (i < nrcards) { + if (!cards[i].protocol) + break; + switch (checkcard(i)) { + case (0): + initcard(i); + if (get_irq(i)) { + closecard(i); + teles_shiftcards(i); + } else { + foundcards++; + i++; + } + break; + case (-1): + teles_shiftcards(i); + break; + case (-2): + release_region(cards[i].iobase, 8); + printk(KERN_WARNING "NO Teles card found at 0x%x!\n", cards[i].iobase); + teles_shiftcards(i); + break; + } + } + return foundcards; +} + +void +teles_closehardware(void) +{ + int i; + + for (i = 0; i < nrcards; i++) + if (cards[i].sp) { + release_irq(i); + closecard(i); + } +} + +static void +hscx_l2l1(struct PStack *st, int pr, + struct BufHeader *ibh) +{ + struct IsdnCardState *sp = (struct IsdnCardState *) + st->l1.hardware; + struct HscxState *hsp = sp->hs + st->l1.hscx; + long flags; + + switch (pr) { + case (PH_DATA): + save_flags(flags); + cli(); + if (hsp->xmtibh) { + BufQueueLink(&hsp->sq, ibh); + restore_flags(flags); + } + else { + restore_flags(flags); + hsp->xmtibh = ibh; + hsp->sendptr = 0; + hsp->releasebuf = !0; + hscx_fill_fifo(hsp); + } + break; + case (PH_DATA_PULLED): + if (hsp->xmtibh) { + printk(KERN_DEBUG "hscx_l2l1: this shouldn't happen\n"); + break; + } + hsp->xmtibh = ibh; + hsp->sendptr = 0; + hsp->releasebuf = 0; + hscx_fill_fifo(hsp); + break; + case (PH_REQUEST_PULL): + if (!hsp->xmtibh) { + st->l1.requestpull = 0; + st->l1.l1l2(st, PH_PULL_ACK, NULL); + } else + st->l1.requestpull = !0; + break; + } + +} + +extern struct IsdnBuffers *tracebuf; + +static void +hscx_l2l1discardq(struct PStack *st, int pr, void *heldby, + int releasetoo) +{ + struct IsdnCardState *sp = (struct IsdnCardState *) + st->l1.hardware; + struct HscxState *hsp = sp->hs + st->l1.hscx; + +#ifdef DEBUG_MAGIC + if (hsp->magic != 301270) { + printk(KERN_DEBUG "hscx_discardq magic not 301270\n"); + return; + } +#endif + + BufQueueDiscard(&hsp->sq, pr, heldby, releasetoo); +} + +static int +open_hscxstate(struct IsdnCardState *sp, + int hscx) +{ + struct HscxState *hsp = sp->hs + hscx; + + if (!hsp->init) { + BufPoolInit(&hsp->sbufpool, HSCX_SBUF_ORDER, HSCX_SBUF_BPPS, + HSCX_SBUF_MAXPAGES); + BufPoolInit(&hsp->rbufpool, HSCX_RBUF_ORDER, HSCX_RBUF_BPPS, + HSCX_RBUF_MAXPAGES); + BufPoolInit(&hsp->smallpool, HSCX_SMALLBUF_ORDER, HSCX_SMALLBUF_BPPS, + HSCX_SMALLBUF_MAXPAGES); + } + hsp->init = !0; + + BufQueueInit(&hsp->rq); + BufQueueInit(&hsp->sq); + + hsp->releasebuf = 0; + hsp->rcvibh = NULL; + hsp->xmtibh = NULL; + hsp->rcvptr = 0; + hsp->sendptr = 0; + hsp->event = 0; + return (0); +} + +static void +hscx_manl1(struct PStack *st, int pr, + void *arg) +{ + struct IsdnCardState *sp = (struct IsdnCardState *) + st->l1.hardware; + struct HscxState *hsp = sp->hs + st->l1.hscx; + + switch (pr) { + case (PH_ACTIVATE): + hsp->active = !0; + modehscx(hsp, st->l1.hscxmode, st->l1.hscxchannel); + st->l1.l1man(st, PH_ACTIVATE, NULL); + break; + case (PH_DEACTIVATE): + if (!hsp->xmtibh) + modehscx(hsp, 0, 0); + + hsp->active = 0; + break; + } +} + +int +setstack_hscx(struct PStack *st, struct HscxState *hs) +{ + if (open_hscxstate(st->l1.hardware, hs->hscx)) + return (-1); + + st->l1.hscx = hs->hscx; + st->l2.l2l1 = hscx_l2l1; + st->ma.manl1 = hscx_manl1; + st->l2.l2l1discardq = hscx_l2l1discardq; + + st->l1.sbufpool = &hs->sbufpool; + st->l1.rbufpool = &hs->rbufpool; + st->l1.smallpool = &hs->smallpool; + st->l1.act_state = 0; + st->l1.requestpull = 0; + + hs->st = st; + return (0); +} + +void +teles_reportcard(int cardnr) +{ + printk(KERN_DEBUG "teles_reportcard\n"); +} diff --git a/drivers/isdn/teles/config.c b/drivers/isdn/teles/config.c new file mode 100644 index 000000000..86d29afa5 --- /dev/null +++ b/drivers/isdn/teles/config.c @@ -0,0 +1,48 @@ +/* $Id: config.c,v 1.1 1996/04/13 10:23:11 fritz Exp $ + * + * $Log: config.c,v $ + * Revision 1.1 1996/04/13 10:23:11 fritz + * Initial revision + * + * + */ +#define __NO_VERSION__ +#include <linux/types.h> +#include <linux/stddef.h> +#include <linux/timer.h> +#include "teles.h" + +/* + * This structure array contains one entry per card. An entry looks + * like this: + * + * { membase,irq,portbase,protocol,NULL } + * + * protocol can be either ISDN_PTYPE_EURO or ISDN_PTYPE_1TR6 + * + * Cards which don't have an io port (Teles 8 bit cards for + * example) can be entered with io port 0x0 + * + * For the Teles 16.3, membase has to be set to 0. + * + */ + +struct IsdnCard cards[] = +{ + {0xd0000, 15, 0xd80, ISDN_PTYPE_EURO, NULL}, /* example */ + {0, 0, 0, 0, NULL}, + {0, 0, 0, 0, NULL}, + {0, 0, 0, 0, NULL}, + {0, 0, 0, 0, NULL}, + {0, 0, 0, 0, NULL}, + {0, 0, 0, 0, NULL}, + {0, 0, 0, 0, NULL}, + {0, 0, 0, 0, NULL}, + {0, 0, 0, 0, NULL}, + {0, 0, 0, 0, NULL}, + {0, 0, 0, 0, NULL}, + {0, 0, 0, 0, NULL}, + {0, 0, 0, 0, NULL}, + {0, 0, 0, 0, NULL}, + {0, 0, 0, 0, NULL}, +}; diff --git a/drivers/isdn/teles/fsm.c b/drivers/isdn/teles/fsm.c new file mode 100644 index 000000000..c0b2f494b --- /dev/null +++ b/drivers/isdn/teles/fsm.c @@ -0,0 +1,159 @@ +/* $Id: fsm.c,v 1.2 1996/04/29 22:49:57 fritz Exp $ + * + * $Log: fsm.c,v $ + * Revision 1.2 1996/04/29 22:49:57 fritz + * Removed compatibility-macros. + * + * Revision 1.1 1996/04/13 10:23:41 fritz + * Initial revision + * + * + */ +#define __NO_VERSION__ +#include "teles.h" + +void +FsmNew(struct Fsm *fsm, + struct FsmNode *fnlist, int fncount) +{ + int i; + + fsm->jumpmatrix = (int *) Smalloc(4L * fsm->state_count * fsm->event_count, + GFP_KERNEL, "Fsm jumpmatrix"); + memset(fsm->jumpmatrix, 0, 4L * fsm->state_count * fsm->event_count); + + for (i = 0; i < fncount; i++) + fsm->jumpmatrix[fsm->state_count * fnlist[i].event + + fnlist[i].state] = (int) fnlist[i].routine; +} + +void +FsmFree(struct Fsm *fsm) +{ + Sfree((void *) fsm->jumpmatrix); +} + +int +FsmEvent(struct FsmInst *fi, int event, void *arg) +{ + void (*r) (struct FsmInst *, int, void *); + char str[80]; + + r = (void (*)) fi->fsm->jumpmatrix[fi->fsm->state_count * event + fi->state]; + if (r) { + if (fi->debug) { + sprintf(str, "State %s Event %s", + fi->fsm->strState[fi->state], + fi->fsm->strEvent[event]); + fi->printdebug(fi, str); + } + r(fi, event, arg); + return (0); + } else { + if (fi->debug) { + sprintf(str, "State %s Event %s no routine", + fi->fsm->strState[fi->state], + fi->fsm->strEvent[event]); + fi->printdebug(fi, str); + } + return (!0); + } +} + +void +FsmChangeState(struct FsmInst *fi, int newstate) +{ + char str[80]; + + fi->state = newstate; + if (fi->debug) { + sprintf(str, "ChangeState %s", + fi->fsm->strState[newstate]); + fi->printdebug(fi, str); + } +} + +static void +FsmExpireTimer(struct FsmTimer *ft) +{ + FsmEvent(ft->fi, ft->event, ft->arg); +} + +void +FsmInitTimer(struct FsmInst *fi, struct FsmTimer *ft) +{ + ft->fi = fi; + ft->tl.function = (void *) FsmExpireTimer; + ft->tl.data = (long) ft; + init_timer(&ft->tl); +} + +void +FsmDelTimer(struct FsmTimer *ft, int where) +{ + long flags; + +#if 0 + if (ft->fi->debug) { + sprintf(str, "FsmDelTimer %lx %d", ft, where); + ft->fi->printdebug(ft->fi, str); + } +#endif + + save_flags(flags); + cli(); + if (ft->tl.next) + del_timer(&ft->tl); + restore_flags(flags); +} + +int +FsmAddTimer(struct FsmTimer *ft, + int millisec, int event, void *arg, int where) +{ + +#if 0 + if (ft->fi->debug) { + sprintf(str, "FsmAddTimer %lx %d %d", ft, millisec, where); + ft->fi->printdebug(ft->fi, str); + } +#endif + + if (ft->tl.next) { + printk(KERN_WARNING "FsmAddTimer: timer already active!\n"); + return -1; + } + init_timer(&ft->tl); + ft->event = event; + ft->arg = arg; + ft->tl.expires = jiffies + (millisec * HZ) / 1000; + add_timer(&ft->tl); + return 0; +} + +int +FsmTimerRunning(struct FsmTimer *ft) +{ + return (ft->tl.next != NULL); +} + +void +jiftime(char *s, long mark) +{ + s += 8; + + *s-- = '\0'; + *s-- = mark % 10 + '0'; + mark /= 10; + *s-- = mark % 10 + '0'; + mark /= 10; + *s-- = '.'; + *s-- = mark % 10 + '0'; + mark /= 10; + *s-- = mark % 6 + '0'; + mark /= 6; + *s-- = ':'; + *s-- = mark % 10 + '0'; + mark /= 10; + *s-- = mark % 10 + '0'; +} diff --git a/drivers/isdn/teles/isdnl2.c b/drivers/isdn/teles/isdnl2.c new file mode 100644 index 000000000..779b7b70a --- /dev/null +++ b/drivers/isdn/teles/isdnl2.c @@ -0,0 +1,1317 @@ +/* $Id: isdnl2.c,v 1.2 1996/05/17 03:46:15 fritz Exp $ + * + * $Log: isdnl2.c,v $ + * Revision 1.2 1996/05/17 03:46:15 fritz + * General cleanup. + * + * Revision 1.1 1996/04/13 10:24:16 fritz + * Initial revision + * + * + */ +#define __NO_VERSION__ +#include "teles.h" + +#define TIMER_1 2000 + +static void l2m_debug(struct FsmInst *fi, char *s); + +struct Fsm l2fsm = +{NULL, 0, 0}; + +enum { + ST_L2_1, + ST_L2_3, + ST_L2_4, + ST_L2_5, + ST_L2_6, + ST_L2_7, + ST_L2_8, +}; + +#define L2_STATE_COUNT (ST_L2_8+1) + +static char *strL2State[] = +{ + "ST_L2_1", + "ST_L2_3", + "ST_L2_4", + "ST_L2_5", + "ST_L2_6", + "ST_L2_7", + "ST_L2_8", +}; + +enum { + EV_L2_UI, + EV_L2_SABMX, + EV_L2_UA, + EV_L2_DISC, + EV_L2_I, + EV_L2_RR, + EV_L2_REJ, + EV_L2_FRMR, + EV_L2_DL_DATA, + EV_L2_DL_ESTABLISH, + EV_L2_MDL_ASSIGN, + EV_L2_DL_UNIT_DATA, + EV_L2_DL_RELEASE, + EV_L2_MDL_NOTEIPROC, + EV_L2_T200, + EV_L2_ACK_PULL, + EV_L2_T203, + EV_L2_RNR, +}; + +#define L2_EVENT_COUNT (EV_L2_RNR+1) + +static char *strL2Event[] = +{ + "EV_L2_UI", + "EV_L2_SABMX", + "EV_L2_UA", + "EV_L2_DISC", + "EV_L2_I", + "EV_L2_RR", + "EV_L2_REJ", + "EV_L2_FRMR", + "EV_L2_DL_DATA", + "EV_L2_DL_ESTABLISH", + "EV_L2_MDL_ASSIGN", + "EV_L2_DL_UNIT_DATA", + "EV_L2_DL_RELEASE", + "EV_L2_MDL_NOTEIPROC", + "EV_L2_T200", + "EV_L2_ACK_PULL", + "EV_L2_T203", + "EV_L2_RNR", +}; + +int errcount = 0; + +static int l2addrsize(struct Layer2 *tsp); + +static int +cansend(struct PStack *st) +{ + int p1; + + p1 = (st->l2.va + st->l2.window) % (st->l2.extended ? 128 : 8); + return (st->l2.vs != p1); +} + +static void +discard_i_queue(struct PStack *st) +{ + struct BufHeader *ibh; + + while (!BufQueueUnlink(&ibh, &st->l2.i_queue)) + BufPoolRelease(ibh); +} + +int +l2headersize(struct Layer2 *tsp, int UI) +{ + return ((tsp->extended && (!UI) ? 2 : 1) + (tsp->laptype == LAPD ? 2 : 1)); +} + +int +l2addrsize(struct Layer2 *tsp) +{ + return (tsp->laptype == LAPD ? 2 : 1); +} + +static int +sethdraddr(struct Layer2 *tsp, + struct BufHeader *ibh, int rsp) +{ + byte *ptr = DATAPTR(ibh); + int crbit; + + if (tsp->laptype == LAPD) { + crbit = rsp; + if (!tsp->orig) + crbit = !crbit; + *ptr++ = (tsp->sap << 2) | (crbit ? 2 : 0); + *ptr++ = (tsp->tei << 1) | 1; + return (2); + } else { + crbit = rsp; + if (tsp->orig) + crbit = !crbit; + if (crbit) + *ptr++ = 1; + else + *ptr++ = 3; + return (1); + } +} + +static void +enqueue_ui(struct PStack *st, + struct BufHeader *ibh) +{ + st->l2.l2l1(st, PH_DATA, ibh); +} + +static void +enqueue_super(struct PStack *st, + struct BufHeader *ibh) +{ + st->l2.l2l1(st, PH_DATA, ibh); +} + +static int +legalnr(struct PStack *st, int nr) +{ + struct Layer2 *l2 = &st->l2; + int lnr, lvs; + + lvs = (l2->vs >= l2->va) ? l2->vs : (l2->vs + l2->extended ? 128 : 8); + lnr = (nr >= l2->va) ? nr : (nr + l2->extended ? 128 : 8); + return (lnr <= lvs); +} + +static void +setva(struct PStack *st, int nr) +{ + struct Layer2 *l2 = &st->l2; + + if (l2->va != nr) { + while (l2->va != nr) { + l2->va = (l2->va + 1) % (l2->extended ? 128 : 8); + BufPoolRelease(l2->windowar[l2->sow]); + l2->sow = (l2->sow + 1) % l2->window; + } + if (st->l4.l2writewakeup) + st->l4.l2writewakeup(st); + } +} + +static void +l2s1(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + st->l2.l2tei(st, MDL_ASSIGN, (void *)st->l2.ces); + FsmChangeState(fi, ST_L2_3); +} + +static void +l2s2(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct BufHeader *ibh = arg; + + byte *ptr; + int i; + + i = sethdraddr(&(st->l2), ibh, 0); + ptr = DATAPTR(ibh); + ptr += i; + *ptr = 0x3; + + enqueue_ui(st, ibh); +} + +static void +l2s3(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct BufHeader *ibh = arg; + + st->l2.l2l3(st, DL_UNIT_DATA, ibh); +} + +static void +establishlink(struct FsmInst *fi) +{ + struct PStack *st = fi->userdata; + struct BufHeader *ibh; + int i; + byte *ptr; + + FsmChangeState(fi, ST_L2_5); + st->l2.rc = 0; + + if (FsmAddTimer(&st->l2.t200_timer, st->l2.t200, EV_L2_T200, NULL, 1)) + if (st->l2.l2m.debug) + l2m_debug(&st->l2.l2m, "FAT 1"); + + + if (BufPoolGet(&ibh, st->l1.smallpool, GFP_ATOMIC, (void *) st, 15)) + return; + i = sethdraddr(&st->l2, ibh, 0); + ptr = DATAPTR(ibh); + ptr += i; + if (st->l2.extended) + *ptr = 0x7f; + else + *ptr = 0x3f; + ibh->datasize = i + 1; + + enqueue_super(st, ibh); +} + +static void +l2s11(struct FsmInst *fi, int event, void *arg) +{ + establishlink(fi); +} + +static void +l2s13(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct Channel *chanp = st->l4.userdata; + byte *ptr; + struct BufHeader *ibh; + int i; + + FsmChangeState(fi, ST_L2_6); + + FsmDelTimer(&st->l2.t203_timer, 1); + if (st->l2.t200_running) { + FsmDelTimer(&st->l2.t200_timer, 2); + st->l2.t200_running = 0; + } + st->l2.rc = 0; + if (FsmAddTimer(&st->l2.t200_timer, st->l2.t200, EV_L2_T200, NULL, 2)) + if (st->l2.l2m.debug) + l2m_debug(&st->l2.l2m, "FAT 2"); + + + if ((chanp->impair == 2) && (st->l2.laptype == LAPB)) + goto nodisc; + + if (BufPoolGet(&ibh, st->l1.smallpool, GFP_ATOMIC, (void *) st, 9)) + return; + i = sethdraddr(&(st->l2), ibh, 0); + ptr = DATAPTR(ibh); + ptr += i; + *ptr = 0x53; + ibh->datasize = i + 1; + enqueue_super(st, ibh); + + nodisc: + discard_i_queue(st); +} + +static void +l2s12(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct BufHeader *ibh = arg; + byte *ptr; + int i; + + BufPoolRelease(ibh); + st->l2.vs = 0; + st->l2.va = 0; + st->l2.vr = 0; + st->l2.sow = 0; + FsmChangeState(fi, ST_L2_7); + if (FsmAddTimer(&st->l2.t203_timer, st->l2.t203, EV_L2_T203, NULL, 3)) + if (st->l2.l2m.debug) + l2m_debug(&st->l2.l2m, "FAT 3"); + + st->l2.l2man(st, DL_ESTABLISH, NULL); + + if (BufPoolGet(&ibh, st->l1.smallpool, GFP_ATOMIC, (void *) st, 10)) + return; + i = sethdraddr(&(st->l2), ibh, 0); + ptr = DATAPTR(ibh); + ptr += i; + *ptr = 0x73; + ibh->datasize = i + 1; + enqueue_super(st, ibh); + +} + +static void +l2s14(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct BufHeader *ibh = arg; + struct Channel *chanp = st->l4.userdata; + byte *ptr; + int i, p; + + ptr = DATAPTR(ibh); + ptr += l2addrsize(&(st->l2)); + p = (*ptr) & 0x10; + BufPoolRelease(ibh); + + FsmChangeState(fi, ST_L2_4); + + FsmDelTimer(&st->l2.t203_timer, 3); + if (st->l2.t200_running) { + FsmDelTimer(&st->l2.t200_timer, 4); + st->l2.t200_running = 0; + } + if ((chanp->impair == 1) && (st->l2.laptype == LAPB)) + goto noresponse; + + if (BufPoolGet(&ibh, st->l1.smallpool, GFP_ATOMIC, (void *) st, 11)) + return; + i = sethdraddr(&(st->l2), ibh, 0); + ptr = DATAPTR(ibh); + ptr += i; + *ptr = 0x63 | (p ? 0x10 : 0x0); + ibh->datasize = i + 1; + enqueue_super(st, ibh); + + noresponse: + st->l2.l2man(st, DL_RELEASE, NULL); + +} + +static void +l2s5(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct BufHeader *ibh = arg; + int f; + byte *data; + + data = DATAPTR(ibh); + data += l2addrsize(&(st->l2)); + + f = *data & 0x10; + BufPoolRelease(ibh); + + if (f) { + st->l2.vs = 0; + st->l2.va = 0; + st->l2.vr = 0; + st->l2.sow = 0; + FsmChangeState(fi, ST_L2_7); + + FsmDelTimer(&st->l2.t200_timer, 5); + if (FsmAddTimer(&st->l2.t203_timer, st->l2.t203, EV_L2_T203, NULL, 4)) + if (st->l2.l2m.debug) + l2m_debug(&st->l2.l2m, "FAT 4"); + + + st->l2.l2man(st, DL_ESTABLISH, NULL); + } +} + +static void +l2s15(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct BufHeader *ibh = arg; + int f; + byte *data; + + data = DATAPTR(ibh); + data += l2addrsize(&st->l2); + + f = *data & 0x10; + BufPoolRelease(ibh); + + if (f) { + FsmDelTimer(&st->l2.t200_timer, 6); + FsmChangeState(fi, ST_L2_4); + st->l2.l2man(st, DL_RELEASE, NULL); + } +} + +static void +l2s6(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct Channel *chanp = st->l4.userdata; + struct BufHeader *ibh = arg; + int p, i, seq, rsp; + byte *ptr; + struct Layer2 *l2; + + l2 = &st->l2; + ptr = DATAPTR(ibh); + + if (l2->laptype == LAPD) { + rsp = ptr[0] & 0x2; + if (l2->orig) + rsp = !rsp; + } else { + rsp = ptr[0] == 0x3; + if (l2->orig) + rsp = !rsp; + } + + ptr += l2addrsize(l2); + + if (l2->extended) { + p = (ptr[1] & 0x1) == 0x1; + seq = ptr[1] >> 1; + } else { + p = (ptr[0] & 0x10); + seq = (ptr[0] >> 5) & 0x7; + } + BufPoolRelease(ibh); + + if ((chanp->impair == 4) && (st->l2.laptype == LAPB)) + goto noresp; + + if ((!rsp) && p) { + if (!BufPoolGet(&ibh, st->l1.smallpool, GFP_ATOMIC, (void *) st, 12)) { + i = sethdraddr(l2, ibh, !0); + ptr = DATAPTR(ibh); + ptr += i; + + if (l2->extended) { + *ptr++ = 0x1; + *ptr++ = (l2->vr << 1) | (p ? 1 : 0); + i += 2; + } else { + *ptr++ = (l2->vr << 5) | 0x1 | (p ? 0x10 : 0x0); + i += 1; + } + ibh->datasize = i; + enqueue_super(st, ibh); + } + } + noresp: + if (legalnr(st, seq)) + if (seq == st->l2.vs) { + setva(st, seq); + FsmDelTimer(&st->l2.t200_timer, 7); + st->l2.t200_running = 0; + FsmDelTimer(&st->l2.t203_timer, 8); + if (FsmAddTimer(&st->l2.t203_timer, st->l2.t203, EV_L2_T203, NULL, 5)) + if (st->l2.l2m.debug) + l2m_debug(&st->l2.l2m, "FAT 5"); + + if (st->l2.i_queue.head) + st->l2.l2l1(st, PH_REQUEST_PULL, NULL); + } else if (st->l2.va != seq) { + setva(st, seq); + FsmDelTimer(&st->l2.t200_timer, 9); + if (FsmAddTimer(&st->l2.t200_timer, st->l2.t200, EV_L2_T200, NULL, 6)) + if (st->l2.l2m.debug) + l2m_debug(&st->l2.l2m, "FAT 6"); + + if (st->l2.i_queue.head) + st->l2.l2l1(st, PH_REQUEST_PULL, NULL); + } +} + +static void +l2s7(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct BufHeader *ibh = arg; + int i; + byte *ptr; + struct IsdnCardState *sp = st->l1.hardware; + char str[64]; + + i = sethdraddr(&st->l2, ibh, 0); + ptr = DATAPTR(ibh); + + if (st->l2.laptype == LAPD) + if (sp->dlogflag) { + sprintf(str, "Q.931 frame user->network tei %d", st->l2.tei); + dlogframe(sp, ptr + st->l2.ihsize, ibh->datasize - st->l2.ihsize, + str); + } + BufQueueLink(&st->l2.i_queue, ibh); + + st->l2.l2l1(st, PH_REQUEST_PULL, NULL); +} + +static void +l2s8(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct Channel *chanp = st->l4.userdata; + struct BufHeader *ibh = arg; + byte *ptr; + struct BufHeader *ibh2; + struct IsdnCardState *sp = st->l1.hardware; + struct Layer2 *l2 = &(st->l2); + int i, p, seq, nr, wasok; + char str[64]; + + ptr = DATAPTR(ibh); + ptr += l2addrsize(l2); + if (l2->extended) { + p = (ptr[1] & 0x1) == 0x1; + seq = ptr[0] >> 1; + nr = (ptr[1] >> 1) & 0x7f; + } else { + p = (ptr[0] & 0x10); + seq = (ptr[0] >> 1) & 0x7; + nr = (ptr[0] >> 5) & 0x7; + } + + if (l2->vr == seq) { + wasok = !0; + + l2->vr = (l2->vr + 1) % (l2->extended ? 128 : 8); + l2->rejexp = 0; + + ptr = DATAPTR(ibh); + if (st->l2.laptype == LAPD) + if (sp->dlogflag) { + sprintf(str, "Q.931 frame network->user tei %d", st->l2.tei); + dlogframe(st->l1.hardware, ptr + l2->ihsize, + ibh->datasize - l2->ihsize, str); + } + label8_1: + if ((chanp->impair == 3) && (st->l2.laptype == LAPB)) + goto noRR; + + if (!BufPoolGet(&ibh2, st->l1.smallpool, GFP_ATOMIC, (void *) st, 13)) { + i = sethdraddr(&(st->l2), ibh2, p); + ptr = DATAPTR(ibh2); + ptr += i; + + if (l2->extended) { + *ptr++ = 0x1; + *ptr++ = (l2->vr << 1) | (p ? 1 : 0); + i += 2; + } else { + *ptr++ = (l2->vr << 5) | 0x1 | (p ? 0x10 : 0x0); + i += 1; + } + ibh2->datasize = i; + enqueue_super(st, ibh2); + noRR: + } + } else { + /* n(s)!=v(r) */ + wasok = 0; + BufPoolRelease(ibh); + if (st->l2.rejexp) { + if (p) + goto label8_1; + } else { + st->l2.rejexp = !0; + if (!BufPoolGet(&ibh2, st->l1.smallpool, GFP_ATOMIC, (void *) st, 14)) { + i = sethdraddr(&(st->l2), ibh2, p); + ptr = DATAPTR(ibh2); + ptr += i; + + if (l2->extended) { + *ptr++ = 0x9; + *ptr++ = (l2->vr << 1) | (p ? 1 : 0); + i += 2; + } else { + *ptr++ = (l2->vr << 5) | 0x9 | (p ? 0x10 : 0x0); + i += 1; + } + ibh2->datasize = i; + enqueue_super(st, ibh2); + } + } + } + + if (legalnr(st, nr)) + if (nr == st->l2.vs) { + setva(st, nr); + FsmDelTimer(&st->l2.t200_timer, 10); + st->l2.t200_running = 0; + FsmDelTimer(&st->l2.t203_timer, 11); + if (FsmAddTimer(&st->l2.t203_timer, st->l2.t203, EV_L2_T203, NULL, 7)) + if (st->l2.l2m.debug) + l2m_debug(&st->l2.l2m, "FAT 5"); + + if (st->l2.i_queue.head) + st->l2.l2l1(st, PH_REQUEST_PULL, NULL); + } else if (nr != st->l2.va) { + setva(st, nr); + FsmDelTimer(&st->l2.t200_timer, 12); + if (FsmAddTimer(&st->l2.t200_timer, st->l2.t200, EV_L2_T200, NULL, 8)) + if (st->l2.l2m.debug) + l2m_debug(&st->l2.l2m, "FAT 6"); + + if (st->l2.i_queue.head) + st->l2.l2l1(st, PH_REQUEST_PULL, NULL); + } + if (wasok) + st->l2.l2l3(st, DL_DATA, ibh); + +} + +static void +l2s17(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + st->l2.tei = (int) arg; + establishlink(fi); +} + +static void +enquiry_response(struct PStack *st) +{ + struct BufHeader *ibh2; + int i; + byte *ptr; + struct Layer2 *l2; + + l2 = &st->l2; + if (!BufPoolGet(&ibh2, st->l1.smallpool, GFP_ATOMIC, (void *) st, 16)) { + i = sethdraddr(&(st->l2), ibh2, !0); + ptr = DATAPTR(ibh2); + ptr += i; + + if (l2->extended) { + *ptr++ = 0x1; + *ptr++ = (l2->vr << 1) | 0x1; + i += 2; + } else { + *ptr++ = (l2->vr << 5) | 0x1 | 0x10; + i += 1; + } + ibh2->datasize = i; + enqueue_super(st, ibh2); + } +} + +static void +invoke_retransmission(struct PStack *st, int nr) +{ + struct Layer2 *l2 = &st->l2; + int p1; + + if (l2->vs != nr) { + while (l2->vs != nr) { + + l2->vs = l2->vs - 1; + if (l2->vs < 0) + l2->vs += l2->extended ? 128 : 8; + + p1 = l2->vs - l2->va; + if (p1 < 0) + p1 += l2->extended ? 128 : 8; + p1 = (p1 + l2->sow) % l2->window; + + BufQueueLinkFront(&l2->i_queue, l2->windowar[p1]); + } + st->l2.l2l1(st, PH_REQUEST_PULL, NULL); + } +} + +static void +l2s16(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct BufHeader *ibh = arg; + int p, seq, rsp; + byte *ptr; + struct Layer2 *l2; + + l2 = &(st->l2); + ptr = DATAPTR(ibh); + + if (l2->laptype == LAPD) { + rsp = ptr[0] & 0x2; + if (l2->orig) + rsp = !rsp; + } else { + rsp = ptr[0] == 0x3; + if (l2->orig) + rsp = !rsp; + } + + + ptr += l2addrsize(l2); + + if (l2->extended) { + p = (ptr[1] & 0x1) == 0x1; + seq = ptr[1] >> 1; + } else { + p = (ptr[0] & 0x10); + seq = (ptr[0] >> 5) & 0x7; + } + BufPoolRelease(ibh); + + if ((!rsp) && p) + enquiry_response(st); + + if (!legalnr(st, seq)) + return; + + setva(st, seq); + invoke_retransmission(st, seq); + +} + +static void +l2s19(struct FsmInst *fi, int event, void *arg) +{ + FsmChangeState(fi, ST_L2_4); +} + +static void +l2s20(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + int i; + struct BufHeader *ibh; + byte *ptr; + + if (st->l2.rc == st->l2.n200) { + FsmChangeState(fi, ST_L2_4); + st->l2.l2man(st, DL_RELEASE, NULL); + } else { + st->l2.rc++; + + if (FsmAddTimer(&st->l2.t200_timer, st->l2.t200, EV_L2_T200, NULL, 9)) + if (st->l2.l2m.debug) + l2m_debug(&st->l2.l2m, "FAT 7"); + + if (BufPoolGet(&ibh, st->l1.smallpool, GFP_ATOMIC, (void *) st, 15)) + return; + + i = sethdraddr(&st->l2, ibh, 0); + ptr = DATAPTR(ibh); + ptr += i; + if (st->l2.extended) + *ptr = 0x7f; + else + *ptr = 0x3f; + ibh->datasize = i + 1; + enqueue_super(st, ibh); + } +} + +static void +l2s21(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct Channel *chanp = st->l4.userdata; + int i; + struct BufHeader *ibh; + byte *ptr; + + if (st->l2.rc == st->l2.n200) { + FsmChangeState(fi, ST_L2_4); + st->l2.l2man(st, DL_RELEASE, NULL); + } else { + st->l2.rc++; + + if (FsmAddTimer(&st->l2.t200_timer, st->l2.t200, EV_L2_T200, NULL, 10)) + if (st->l2.l2m.debug) + l2m_debug(&st->l2.l2m, "FAT 8"); + + + if ((chanp->impair == 2) && (st->l2.laptype == LAPB)) + goto nodisc; + + if (BufPoolGet(&ibh, st->l1.smallpool, GFP_ATOMIC, (void *) st, 15)) + return; + + i = sethdraddr(&st->l2, ibh, 0); + ptr = DATAPTR(ibh); + ptr += i; + *ptr = 0x53; + ibh->datasize = i + 1; + enqueue_super(st, ibh); + nodisc: + + } +} + +static void +l2s22(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct BufHeader *ibh; + struct Layer2 *l2 = &st->l2; + byte *ptr; + int p1; + + if (!cansend(st)) + return; + + if (BufQueueUnlink(&ibh, &l2->i_queue)) + return; + + + p1 = l2->vs - l2->va; + if (p1 < 0) + p1 += l2->extended ? 128 : 8; + p1 = (p1 + l2->sow) % l2->window; + l2->windowar[p1] = ibh; + + ptr = DATAPTR(ibh); + ptr += l2addrsize(l2); + + if (l2->extended) { + *ptr++ = l2->vs << 1; + *ptr++ = (l2->vr << 1) | 0x1; + l2->vs = (l2->vs + 1) % 128; + } else { + *ptr++ = (l2->vr << 5) | (l2->vs << 1) | 0x10; + l2->vs = (l2->vs + 1) % 8; + } + + st->l2.l2l1(st, PH_DATA_PULLED, ibh); + + if (!st->l2.t200_running) { + FsmDelTimer(&st->l2.t203_timer, 13); + if (FsmAddTimer(&st->l2.t200_timer, st->l2.t200, EV_L2_T200, NULL, 11)) + if (st->l2.l2m.debug) + l2m_debug(&st->l2.l2m, "FAT 9"); + + st->l2.t200_running = !0; + } + if (l2->i_queue.head && cansend(st)) + st->l2.l2l1(st, PH_REQUEST_PULL, NULL); + +} + +static void +transmit_enquiry(struct PStack *st) +{ + struct BufHeader *ibh; + byte *ptr; + + if (!BufPoolGet(&ibh, st->l1.smallpool, GFP_ATOMIC, (void *) st, 12)) { + ptr = DATAPTR(ibh); + ptr += sethdraddr(&st->l2, ibh, 0); + + if (st->l2.extended) { + *ptr++ = 0x1; + *ptr++ = (st->l2.vr << 1) | 1; + } else { + *ptr++ = (st->l2.vr << 5) | 0x11; + } + ibh->datasize = ptr - DATAPTR(ibh); + enqueue_super(st, ibh); + if (FsmAddTimer(&st->l2.t200_timer, st->l2.t200, EV_L2_T200, NULL, 12)) + if (st->l2.l2m.debug) + l2m_debug(&st->l2.l2m, "FAT 10"); + + st->l2.t200_running = !0; + } +} + +static void +l2s23(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + st->l2.t200_running = 0; + + st->l2.rc = 1; + FsmChangeState(fi, ST_L2_8); + transmit_enquiry(st); +} + +static void +l2s24(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct BufHeader *ibh = arg; + int p, seq, rsp; + byte *ptr; + struct Layer2 *l2; + + l2 = &st->l2; + ptr = DATAPTR(ibh); + + if (l2->laptype == LAPD) { + rsp = ptr[0] & 0x2; + if (l2->orig) + rsp = !rsp; + } else { + rsp = ptr[0] == 0x3; + if (l2->orig) + rsp = !rsp; + } + + + ptr += l2addrsize(l2); + + if (l2->extended) { + p = (ptr[1] & 0x1) == 0x1; + seq = ptr[1] >> 1; + } else { + p = (ptr[0] & 0x10); + seq = (ptr[0] >> 5) & 0x7; + } + BufPoolRelease(ibh); + + if (rsp && p) { + if (legalnr(st, seq)) { + FsmChangeState(fi, ST_L2_7); + setva(st, seq); + if (st->l2.t200_running) { + FsmDelTimer(&st->l2.t200_timer, 14); + st->l2.t200_running = 0; + } + if (FsmAddTimer(&st->l2.t203_timer, st->l2.t203, EV_L2_T203, NULL, 13)) + if (st->l2.l2m.debug) + l2m_debug(&st->l2.l2m, "FAT 11"); + + invoke_retransmission(st, seq); + } + } else { + if (!rsp && p) + enquiry_response(st); + if (legalnr(st, seq)) { + setva(st, seq); + } + } +} + +static void +l2s25(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + st->l2.rc = 0; + FsmChangeState(fi, ST_L2_8); + transmit_enquiry(st); +} + +static void +l2s26(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + if (st->l2.rc == st->l2.n200) { + l2s13(fi, event, NULL); + } else { + st->l2.rc++; + transmit_enquiry(st); + } +} + +static void +l2s27(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct BufHeader *ibh = arg; + byte *ptr; + int i, p, est; + + ptr = DATAPTR(ibh); + ptr += l2addrsize(&st->l2); + + if (st->l2.extended) + p = ptr[1] & 0x1; + else + p = ptr[0] & 0x10; + + BufPoolRelease(ibh); + + if (BufPoolGet(&ibh, st->l1.smallpool, GFP_ATOMIC, (void *) st, 10)) + return; + i = sethdraddr(&st->l2, ibh, 0); + ptr = DATAPTR(ibh); + ptr += i; + *ptr = 0x63 | p; + ibh->datasize = i + 1; + enqueue_super(st, ibh); + + if (st->l2.vs != st->l2.va) { + discard_i_queue(st); + est = !0; + } else + est = 0; + + FsmDelTimer(&st->l2.t200_timer, 15); + st->l2.t200_running = 0; + + if (FsmAddTimer(&st->l2.t203_timer, st->l2.t203, EV_L2_T203, NULL, 3)) + if (st->l2.l2m.debug) + l2m_debug(&st->l2.l2m, "FAT 12"); + + st->l2.vs = 0; + st->l2.va = 0; + st->l2.vr = 0; + st->l2.sow = 0; + + + if (est) + st->l2.l2man(st, DL_ESTABLISH, NULL); + +} + +static void +l2s28(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct BufHeader *ibh = arg; + byte *ptr; + char tmp[64]; + + ptr = DATAPTR(ibh); + ptr += l2addrsize(&st->l2); + ptr++; + + if (st->l2.l2m.debug) { + if (st->l2.extended) + sprintf(tmp, "FRMR information %2x %2x %2x %2x %2x", + ptr[0], ptr[1], ptr[2], ptr[3], ptr[4]); + else + sprintf(tmp, "FRMR information %2x %2x %2x", + ptr[0], ptr[1], ptr[2]); + + l2m_debug(&st->l2.l2m, tmp); + } + BufPoolRelease(ibh); +} + +static int +IsUI(byte * data, int ext) +{ + return ((data[0] & 0xef) == 0x3); +} + +static int +IsUA(byte * data, int ext) +{ + return ((data[0] & 0xef) == 0x63); +} + +static int +IsDISC(byte * data, int ext) +{ + return ((data[0] & 0xef) == 0x43); +} + +static int +IsRR(byte * data, int ext) +{ + if (ext) + return (data[0] == 0x1); + else + return ((data[0] & 0xf) == 1); +} + +static int +IsI(byte * data, int ext) +{ + return ((data[0] & 0x1) == 0x0); +} + +static int +IsSABMX(byte * data, int ext) +{ + return (ext ? data[0] == 0x7f : data[0] == 0x3f); +} + +static int +IsREJ(byte * data, int ext) +{ + return (ext ? data[0] == 0x9 : (data[0] & 0xf) == 0x9); +} + +static int +IsFRMR(byte * data, int ext) +{ + return ((data[0] & 0xef) == 0x87); +} + +static int +IsRNR(byte * data, int ext) +{ + if (ext) + return (data[0] == 0x5); + else + return ((data[0] & 0xf) == 5); +} + +static struct FsmNode L2FnList[] = +{ + {ST_L2_1, EV_L2_DL_ESTABLISH, l2s1}, + {ST_L2_1, EV_L2_MDL_NOTEIPROC, l2s19}, + {ST_L2_3, EV_L2_MDL_ASSIGN, l2s17}, + {ST_L2_4, EV_L2_DL_UNIT_DATA, l2s2}, + {ST_L2_4, EV_L2_DL_ESTABLISH, l2s11}, + {ST_L2_7, EV_L2_DL_UNIT_DATA, l2s2}, + {ST_L2_7, EV_L2_DL_DATA, l2s7}, + {ST_L2_7, EV_L2_DL_RELEASE, l2s13}, + {ST_L2_7, EV_L2_ACK_PULL, l2s22}, + {ST_L2_8, EV_L2_DL_RELEASE, l2s13}, + + {ST_L2_1, EV_L2_UI, l2s3}, + {ST_L2_4, EV_L2_UI, l2s3}, + {ST_L2_4, EV_L2_SABMX, l2s12}, + {ST_L2_5, EV_L2_UA, l2s5}, + {ST_L2_6, EV_L2_UA, l2s15}, + {ST_L2_7, EV_L2_UI, l2s3}, + {ST_L2_7, EV_L2_DISC, l2s14}, + {ST_L2_7, EV_L2_I, l2s8}, + {ST_L2_7, EV_L2_RR, l2s6}, + {ST_L2_7, EV_L2_REJ, l2s16}, + {ST_L2_7, EV_L2_SABMX, l2s27}, + {ST_L2_7, EV_L2_FRMR, l2s28}, + {ST_L2_8, EV_L2_RR, l2s24}, + {ST_L2_8, EV_L2_DISC, l2s14}, + {ST_L2_8, EV_L2_FRMR, l2s28}, + + {ST_L2_5, EV_L2_T200, l2s20}, + {ST_L2_6, EV_L2_T200, l2s21}, + {ST_L2_7, EV_L2_T200, l2s23}, + {ST_L2_7, EV_L2_T203, l2s25}, + {ST_L2_8, EV_L2_T200, l2s26}, +}; + +#define L2_FN_COUNT (sizeof(L2FnList)/sizeof(struct FsmNode)) + +static void +isdnl2_l1l2(struct PStack *st, int pr, struct BufHeader *arg) +{ + struct BufHeader *ibh; + byte *datap; + int ret = !0; + + switch (pr) { + case (PH_DATA): + + ibh = arg; + datap = DATAPTR(ibh); + datap += l2addrsize(&st->l2); + + if (IsI(datap, st->l2.extended)) + ret = FsmEvent(&st->l2.l2m, EV_L2_I, ibh); + else if (IsRR(datap, st->l2.extended)) + ret = FsmEvent(&st->l2.l2m, EV_L2_RR, ibh); + else if (IsUI(datap, st->l2.extended)) + ret = FsmEvent(&st->l2.l2m, EV_L2_UI, ibh); + else if (IsSABMX(datap, st->l2.extended)) + ret = FsmEvent(&st->l2.l2m, EV_L2_SABMX, ibh); + else if (IsUA(datap, st->l2.extended)) + ret = FsmEvent(&st->l2.l2m, EV_L2_UA, ibh); + else if (IsDISC(datap, st->l2.extended)) + ret = FsmEvent(&st->l2.l2m, EV_L2_DISC, ibh); + else if (IsREJ(datap, st->l2.extended)) + ret = FsmEvent(&st->l2.l2m, EV_L2_REJ, ibh); + else if (IsFRMR(datap, st->l2.extended)) + ret = FsmEvent(&st->l2.l2m, EV_L2_FRMR, ibh); + else if (IsRNR(datap, st->l2.extended)) + ret = FsmEvent(&st->l2.l2m, EV_L2_RNR, ibh); + + if (ret) + BufPoolRelease(ibh); + + break; + case (PH_PULL_ACK): + FsmEvent(&st->l2.l2m, EV_L2_ACK_PULL, arg); + break; + } +} + +static void +isdnl2_l3l2(struct PStack *st, int pr, + void *arg) +{ + switch (pr) { + case (DL_DATA): + if (FsmEvent(&st->l2.l2m, EV_L2_DL_DATA, arg)) + BufPoolRelease((struct BufHeader *) arg); + break; + case (DL_UNIT_DATA): + if (FsmEvent(&st->l2.l2m, EV_L2_DL_UNIT_DATA, arg)) + BufPoolRelease((struct BufHeader *) arg); + break; + } +} + +static void +isdnl2_manl2(struct PStack *st, int pr, + void *arg) +{ + switch (pr) { + case (DL_ESTABLISH): + FsmEvent(&st->l2.l2m, EV_L2_DL_ESTABLISH, arg); + break; + case (DL_RELEASE): + FsmEvent(&st->l2.l2m, EV_L2_DL_RELEASE, arg); + break; + case (MDL_NOTEIPROC): + FsmEvent(&st->l2.l2m, EV_L2_MDL_NOTEIPROC, NULL); + break; + } +} + +static void +isdnl2_teil2(struct PStack *st, int pr, + void *arg) +{ + switch (pr) { + case (MDL_ASSIGN): + FsmEvent(&st->l2.l2m, EV_L2_MDL_ASSIGN, arg); + break; + } +} + +void +releasestack_isdnl2(struct PStack *st) +{ + FsmDelTimer(&st->l2.t200_timer, 15); + FsmDelTimer(&st->l2.t203_timer, 16); +} + +static void +l2m_debug(struct FsmInst *fi, char *s) +{ + struct PStack *st = fi->userdata; + char tm[32], str[256]; + + jiftime(tm, jiffies); + sprintf(str, "%s %s %s\n", tm, st->l2.debug_id, s); + teles_putstatus(str); +} + + +void +setstack_isdnl2(struct PStack *st, char *debug_id) +{ + st->l1.l1l2 = isdnl2_l1l2; + st->l3.l3l2 = isdnl2_l3l2; + st->ma.manl2 = isdnl2_manl2; + st->ma.teil2 = isdnl2_teil2; + + st->l2.uihsize = l2headersize(&st->l2, !0); + st->l2.ihsize = l2headersize(&st->l2, 0); + BufQueueInit(&(st->l2.i_queue)); + st->l2.rejexp = 0; + st->l2.debug = 1; + + st->l2.l2m.fsm = &l2fsm; + st->l2.l2m.state = ST_L2_1; + st->l2.l2m.debug = 0; + st->l2.l2m.userdata = st; + st->l2.l2m.printdebug = l2m_debug; + strcpy(st->l2.debug_id, debug_id); + + FsmInitTimer(&st->l2.l2m, &st->l2.t200_timer); + FsmInitTimer(&st->l2.l2m, &st->l2.t203_timer); + st->l2.t200_running = 0; +} + +void +setstack_transl2(struct PStack *st) +{ +} + +void +releasestack_transl2(struct PStack *st) +{ +} + +void +Isdnl2New(void) +{ + l2fsm.state_count = L2_STATE_COUNT; + l2fsm.event_count = L2_EVENT_COUNT; + l2fsm.strEvent = strL2Event; + l2fsm.strState = strL2State; + FsmNew(&l2fsm, L2FnList, L2_FN_COUNT); +} + +void +Isdnl2Free(void) +{ + FsmFree(&l2fsm); +} diff --git a/drivers/isdn/teles/isdnl3.c b/drivers/isdn/teles/isdnl3.c new file mode 100644 index 000000000..411dc4ad4 --- /dev/null +++ b/drivers/isdn/teles/isdnl3.c @@ -0,0 +1,603 @@ +/* $Id: isdnl3.c,v 1.9 1996/06/06 14:22:27 fritz Exp $ + * + * $Log: isdnl3.c,v $ + * Revision 1.9 1996/06/06 14:22:27 fritz + * Changed level of "non-digital call..." message, since + * with audio support, this is quite normal. + * + * Revision 1.8 1996/06/03 20:35:04 fritz + * Fixed typos. + * + * Revision 1.7 1996/06/03 20:03:39 fritz + * Fixed typos. + * + * Revision 1.6 1996/05/21 11:33:50 keil + * Adding SETUP_ACKNOWLEDGE as answer of a SETUP message. + * + * Revision 1.5 1996/05/18 01:37:16 fritz + * Added spelling corrections and some minor changes + * to stay in sync with kernel. + * + * Revision 1.4 1996/05/17 03:46:16 fritz + * General cleanup. + * + * Revision 1.3 1996/04/30 21:57:53 isdn4dev + * remove some debugging code, improve callback Karsten Keil + * + * Revision 1.2 1996/04/20 16:45:05 fritz + * Changed to report all incoming calls to Linklevel, not just those + * with Service 7. + * Misc. typos + * + * Revision 1.1 1996/04/13 10:24:45 fritz + * Initial revision + * + * + */ +#define __NO_VERSION__ +#define P_1TR6 +#include "teles.h" +#include "l3_1TR6.h" +#define DEBUG_1TR6 0 + +static void +i_down(struct PStack *st, + struct BufHeader *ibh) +{ + st->l3.l3l2(st, DL_DATA, ibh); +} + +static void +newl3state(struct PStack *st, int state) +{ + st->l3.state = state; + if (DEBUG_1TR6 > 4) + printk(KERN_INFO "isdnl3: bc:%d cr:%x new state %d\n", + st->pa->bchannel, st->pa->callref, state); + +} + +static void +l3_message(struct PStack *st, int mt) +{ + struct BufHeader *dibh; + byte *p; + int size; + + BufPoolGet(&dibh, st->l1.sbufpool, GFP_ATOMIC, (void *) st, 18); + p = DATAPTR(dibh); + p += st->l2.ihsize; + size = st->l2.ihsize; + + *p++ = 0x8; + *p++ = 0x1; + *p++ = st->l3.callref; + *p++ = mt; + size += 4; + + dibh->datasize = size; + i_down(st, dibh); +} + +static void +l3s3(struct PStack *st, byte pr, void *arg) +{ + l3_message(st, MT_RELEASE); + newl3state(st, 19); +} + +static void +l3s4(struct PStack *st, byte pr, void *arg) +{ + struct BufHeader *ibh = arg; + + BufPoolRelease(ibh); + newl3state(st, 0); + st->l3.l3l4(st, CC_RELEASE_CNF, NULL); +} + +static void +l3s4_1(struct PStack *st, byte pr, void *arg) +{ + struct BufHeader *ibh = arg; + + BufPoolRelease(ibh); + newl3state(st, 19); + l3_message(st, MT_RELEASE); + st->l3.l3l4(st, CC_RELEASE_CNF, NULL); +} + +static void +l3s5(struct PStack *st, byte pr, + void *arg) +{ + struct BufHeader *dibh; + byte *p; + char *teln; + + st->l3.callref = st->pa->callref; + BufPoolGet(&dibh, st->l1.sbufpool, GFP_ATOMIC, (void *) st, 19); + p = DATAPTR(dibh); + p += st->l2.ihsize; + + *p++ = 0x8; + *p++ = 0x1; + *p++ = st->l3.callref; + *p++ = MT_SETUP; + *p++ = 0xa1; + + /* + * Set Bearer Capability, Map info from 1TR6-convention to EDSS1 + */ + switch (st->pa->info) { + case 1: /* Telephony */ + *p++ = 0x4; /* BC-IE-code */ + *p++ = 0x3; /* Length */ + *p++ = 0x90; /* Coding Std. national, 3.1 kHz audio */ + *p++ = 0x90; /* Circuit-Mode 64kbps */ + *p++ = 0xa3; /* A-Law Audio */ + break; + case 5: /* Datatransmission 64k, BTX */ + case 7: /* Datatransmission 64k */ + default: + *p++ = 0x4; /* BC-IE-code */ + *p++ = 0x2; /* Length */ + *p++ = 0x88; /* Coding Std. nat., unrestr. dig. Inform. */ + *p++ = 0x90; /* Packet-Mode 64kbps */ + break; + } + /* + * What about info2? Mapping to High-Layer-Compatibility? + */ + if (st->pa->calling[0] != '\0') { + *p++ = 0x6c; + *p++ = strlen(st->pa->calling) + 1; + /* Classify as AnyPref. */ + *p++ = 0x81; /* Ext = '1'B, Type = '000'B, Plan = '0001'B. */ + teln = st->pa->calling; + while (*teln) + *p++ = *teln++ & 0x7f; + } + *p++ = 0x70; + *p++ = strlen(st->pa->called) + 1; + /* Classify as AnyPref. */ + *p++ = 0x81; /* Ext = '1'B, Type = '000'B, Plan = '0001'B. */ + + teln = st->pa->called; + while (*teln) + *p++ = *teln++ & 0x7f; + + + dibh->datasize = p - DATAPTR(dibh); + + newl3state(st, 1); + i_down(st, dibh); + +} + +static void +l3s6(struct PStack *st, byte pr, void *arg) +{ + byte *p; + struct BufHeader *ibh = arg; + + p = DATAPTR(ibh); + if ((p = findie(p + st->l2.ihsize, ibh->datasize - st->l2.ihsize, + 0x18, 0))) { + st->pa->bchannel = p[2] & 0x3; + } else + printk(KERN_WARNING "octect 3 not found\n"); + + BufPoolRelease(ibh); + newl3state(st, 3); + st->l3.l3l4(st, CC_PROCEEDING_IND, NULL); +} + +static void +l3s7(struct PStack *st, byte pr, void *arg) +{ + struct BufHeader *ibh = arg; + + BufPoolRelease(ibh); + newl3state(st, 12); + st->l3.l3l4(st, CC_DISCONNECT_IND, NULL); +} + +static void +l3s8(struct PStack *st, byte pr, void *arg) +{ + struct BufHeader *ibh = arg; + + BufPoolRelease(ibh); + st->l3.l3l4(st, CC_SETUP_CNF, NULL); + newl3state(st, 10); +} + +static void +l3s11(struct PStack *st, byte pr, void *arg) +{ + struct BufHeader *ibh = arg; + + BufPoolRelease(ibh); + newl3state(st, 4); + st->l3.l3l4(st, CC_ALERTING_IND, NULL); +} + +static void +l3s12(struct PStack *st, byte pr, void *arg) +{ + byte *p; + int bcfound = 0; + struct BufHeader *ibh = arg; + + p = DATAPTR(ibh); + p += st->l2.uihsize; + st->pa->callref = getcallref(p); + st->l3.callref = 0x80 + st->pa->callref; + + /* + * Channel Identification + */ + p = DATAPTR(ibh); + if ((p = findie(p + st->l2.uihsize, ibh->datasize - st->l2.uihsize, + 0x18, 0))) { + st->pa->bchannel = p[2] & 0x3; + bcfound++ ; + } else + printk(KERN_WARNING "l3s12: Channel ident not found\n"); + + p = DATAPTR(ibh); + if (st->protocol == ISDN_PTYPE_1TR6) { + if ((p = findie(p + st->l2.uihsize, ibh->datasize - st->l2.uihsize, 0x01, 6))) { + st->pa->info = p[2]; + st->pa->info2 = p[3]; + } else + printk(KERN_WARNING "l3s12(1TR6): ServiceIndicator not found\n"); + } else { + /* + * Bearer Capabilities + */ + if ((p = findie(p + st->l2.uihsize, ibh->datasize - st->l2.uihsize, 0x04, 0))) { + switch (p[2] & 0x1f) { + case 0x00: + /* Speech */ + case 0x10: + /* 3.1 Khz audio */ + st->pa->info = 1; + break; + case 0x08: + /* Unrestricted digital information */ + st->pa->info = 7; + break; + case 0x09: + /* Restricted digital information */ + st->pa->info = 2; + break; + case 0x11: + /* Unrestr. digital information with tones/announcements */ + st->pa->info = 3; + break; + case 0x18: + /* Video */ + st->pa->info = 4; + break; + default: + st->pa->info = 0; + } + } else + printk(KERN_WARNING "l3s12: Bearer capabilities not found\n"); + } + + p = DATAPTR(ibh); + if ((p = findie(p + st->l2.uihsize, ibh->datasize - st->l2.uihsize, + 0x70, 0))) + iecpy(st->pa->called, p, 1); + else + strcpy(st->pa->called, ""); + + p = DATAPTR(ibh); + if ((p = findie(p + st->l2.uihsize, ibh->datasize - st->l2.uihsize, + 0x6c, 0))) { + if (st->protocol == ISDN_PTYPE_1TR6) + iecpy(st->pa->calling, p, 1); + else + iecpy(st->pa->calling, p, 2); + } else + strcpy(st->pa->calling, ""); + BufPoolRelease(ibh); + + if (bcfound) { + if (st->pa->info != 7) { + printk(KERN_DEBUG "non-digital call: %s -> %s\n", + st->pa->calling, + st->pa->called); + } + newl3state(st, 6); + st->l3.l3l4(st, CC_SETUP_IND, NULL); + } +} + +static void +l3s13(struct PStack *st, byte pr, void *arg) +{ + newl3state(st, 0); +} + +static void +l3s16(struct PStack *st, byte pr, + void *arg) +{ + st->l3.callref = 0x80 + st->pa->callref; + l3_message(st, MT_CONNECT); + newl3state(st, 8); +} + +static void +l3s17(struct PStack *st, byte pr, void *arg) +{ + struct BufHeader *ibh = arg; + + BufPoolRelease(ibh); + st->l3.l3l4(st, CC_SETUP_COMPLETE_IND, NULL); + newl3state(st, 10); +} + +static void +l3s18(struct PStack *st, byte pr, void *arg) +{ + struct BufHeader *dibh; + byte *p; + int size; + + BufPoolGet(&dibh, st->l1.sbufpool, GFP_ATOMIC, (void *) st, 20); + p = DATAPTR(dibh); + p += st->l2.ihsize; + size = st->l2.ihsize; + + *p++ = 0x8; + *p++ = 0x1; + *p++ = st->l3.callref; + *p++ = MT_DISCONNECT; + size += 4; + + *p++ = IE_CAUSE; + *p++ = 0x2; + *p++ = 0x80; + *p++ = 0x90; + size += 4; + + dibh->datasize = size; + i_down(st, dibh); + + newl3state(st, 11); +} + +static void +l3s19(struct PStack *st, byte pr, void *arg) +{ + struct BufHeader *ibh = arg; + + BufPoolRelease(ibh); + newl3state(st, 0); + l3_message(st, MT_RELEASE_COMPLETE); + st->l3.l3l4(st, CC_RELEASE_IND, NULL); +} + +static void +l3s20(struct PStack *st, byte pr, + void *arg) +{ + l3_message(st, MT_ALERTING); + newl3state(st, 7); +} + +struct stateentry { + int state; + byte primitive; + void (*rout) (struct PStack *, byte, void *); +}; + +static struct stateentry downstatelist[] = +{ + {0,CC_SETUP_REQ,l3s5}, + {1,CC_DISCONNECT_REQ,l3s18}, + {1,CC_RELEASE_REQ,l3s3}, + {1,CC_DLRL,l3s13}, + {3,CC_DISCONNECT_REQ,l3s18}, + {3,CC_RELEASE_REQ,l3s3}, + {3,CC_DLRL,l3s13}, + {4,CC_RELEASE_REQ,l3s3}, + {4,CC_DISCONNECT_REQ,l3s18}, + {4,CC_DLRL,l3s13}, + {6,CC_RELEASE_REQ,l3s3}, + {6,CC_DISCONNECT_REQ,l3s18}, + {6,CC_ALERTING_REQ,l3s20}, + {6,CC_DLRL,l3s13}, + {7,CC_RELEASE_REQ,l3s3}, + {7,CC_SETUP_RSP,l3s16}, + {7,CC_DLRL,l3s13}, + {8,CC_RELEASE_REQ,l3s3}, + {8,CC_DISCONNECT_REQ,l3s18}, + {8,CC_DLRL,l3s13}, + {10,CC_DISCONNECT_REQ,l3s18}, + {10,CC_RELEASE_REQ,l3s3}, + {10,CC_DLRL,l3s13}, + {11,CC_RELEASE_REQ,l3s3}, + {12,CC_RELEASE_REQ,l3s3}, + {19,CC_DLRL,l3s13}, +}; + +static int downsllen = sizeof(downstatelist) / +sizeof(struct stateentry); + +static struct stateentry datastatelist[] = +{ + {0,MT_SETUP,l3s12}, + {1,MT_CALL_PROCEEDING,l3s6}, + {1,MT_SETUP_ACKNOWLEDGE,l3s6}, + {1,MT_RELEASE_COMPLETE,l3s4}, + {1,MT_RELEASE,l3s19}, + {1,MT_DISCONNECT,l3s7}, + {3,MT_DISCONNECT,l3s7}, + {3,MT_CONNECT,l3s8}, + {3,MT_ALERTING,l3s11}, + {3,MT_RELEASE,l3s19}, + {3,MT_RELEASE_COMPLETE,l3s4}, + {4,MT_CONNECT,l3s8}, + {4,MT_DISCONNECT,l3s7}, + {4,MT_RELEASE,l3s19}, + {4,MT_RELEASE_COMPLETE,l3s4}, + {6,MT_SETUP,l3s12}, + {7,MT_RELEASE,l3s19}, + {7,MT_RELEASE_COMPLETE,l3s4_1}, + {7,MT_DISCONNECT,l3s7}, + {8,MT_RELEASE,l3s19}, + {8,MT_CONNECT_ACKNOWLEDGE,l3s17}, + {8,MT_DISCONNECT,l3s7}, + {8,MT_RELEASE_COMPLETE,l3s4_1}, + {10,MT_DISCONNECT,l3s7}, + {10,MT_RELEASE,l3s19}, + {10,MT_RELEASE_COMPLETE,l3s4_1}, + {11,MT_RELEASE,l3s19}, + {11,MT_RELEASE_COMPLETE,l3s4}, + {19,MT_RELEASE_COMPLETE,l3s4}, +}; + +static int datasllen = sizeof(datastatelist) / +sizeof(struct stateentry); + +#ifdef P_1TR6 +#include "l3_1TR6.c" +#endif + +static void +l3up(struct PStack *st, + int pr, void *arg) +{ + int i, mt, size; + byte *ptr; + struct BufHeader *ibh = arg; + + if (pr == DL_DATA) { + ptr = DATAPTR(ibh); + ptr += st->l2.ihsize; + size = ibh->datasize - st->l2.ihsize; + mt = ptr[3]; + switch (ptr[0]) { +#ifdef P_1TR6 + case PROTO_DIS_N0: + BufPoolRelease(ibh); + break; + case PROTO_DIS_N1: + for (i = 0; i < datasl_1tr6t_len; i++) + if ((st->l3.state == datastatelist_1tr6t[i].state) && + (mt == datastatelist_1tr6t[i].primitive)) + break; + if (i == datasl_1tr6t_len) { + BufPoolRelease(ibh); + if (DEBUG_1TR6 > 0) + printk(KERN_INFO "isdnl3up unhandled 1tr6 state %d MT %x\n", + st->l3.state, mt); + } else + datastatelist_1tr6t[i].rout(st, pr, ibh); + break; +#endif + default: /* E-DSS1 */ + for (i = 0; i < datasllen; i++) + if ((st->l3.state == datastatelist[i].state) && + (mt == datastatelist[i].primitive)) + break; + if (i == datasllen) { + BufPoolRelease(ibh); + if (DEBUG_1TR6 > 0) + printk(KERN_INFO "isdnl3up unhandled E-DSS1 state %d MT %x\n", + st->l3.state, mt); + } else + datastatelist[i].rout(st, pr, ibh); + } + } else if (pr == DL_UNIT_DATA) { + ptr = DATAPTR(ibh); + ptr += st->l2.uihsize; + size = ibh->datasize - st->l2.uihsize; + mt = ptr[3]; + switch (ptr[0]) { +#ifdef P_1TR6 + case PROTO_DIS_N0: + BufPoolRelease(ibh); + break; + case PROTO_DIS_N1: + for (i = 0; i < datasl_1tr6t_len; i++) + if ((st->l3.state == datastatelist_1tr6t[i].state) && + (mt == datastatelist_1tr6t[i].primitive)) + break; + if (i == datasl_1tr6t_len) { + if (DEBUG_1TR6 > 0) { + printk(KERN_INFO "isdnl3up unhandled 1tr6 state %d MT %x\n" + ,st->l3.state, mt); + } + BufPoolRelease(ibh); + } else + datastatelist_1tr6t[i].rout(st, pr, ibh); + break; +#endif + default: /* E-DSS1 */ + for (i = 0; i < datasllen; i++) + if ((st->l3.state == datastatelist[i].state) && + (mt == datastatelist[i].primitive)) + break; + if (i == datasllen) { + BufPoolRelease(ibh); + if (DEBUG_1TR6 > 0) + printk(KERN_INFO "isdnl3up unhandled E-DSS1 state %d MT %x\n", + st->l3.state, mt); + } else + datastatelist[i].rout(st, pr, ibh); + } + } +} + +static void +l3down(struct PStack *st, + int pr, void *arg) +{ + int i; + struct BufHeader *ibh = arg; + + switch (st->protocol) { +#ifdef P_1TR6 + case ISDN_PTYPE_1TR6: + for (i = 0; i < downsl_1tr6t_len; i++) + if ((st->l3.state == downstatelist_1tr6t[i].state) && + (pr == downstatelist_1tr6t[i].primitive)) + break; + if (i == downsl_1tr6t_len) { + if (DEBUG_1TR6 > 0) { + printk(KERN_INFO "isdnl3down unhandled 1tr6 state %d primitive %x\n", st->l3.state, pr); + } + } else + downstatelist_1tr6t[i].rout(st, pr, ibh); + break; +#endif + default: + for (i = 0; i < downsllen; i++) + if ((st->l3.state == downstatelist[i].state) && + (pr == downstatelist[i].primitive)) + break; + if (i == downsllen) { + if (DEBUG_1TR6 > 0) { + printk(KERN_INFO "isdnl3down unhandled E-DSS1 state %d primitive %x\n", st->l3.state, pr); + } + } else + downstatelist[i].rout(st, pr, ibh); + } +} + +void +setstack_isdnl3(struct PStack *st) +{ + st->l4.l4l3 = l3down; + st->l2.l2l3 = l3up; + st->l3.state = 0; + st->l3.callref = 0; + st->l3.debug = 0; +} diff --git a/drivers/isdn/teles/l3_1TR6.c b/drivers/isdn/teles/l3_1TR6.c new file mode 100644 index 000000000..e3716769c --- /dev/null +++ b/drivers/isdn/teles/l3_1TR6.c @@ -0,0 +1,507 @@ +/* $Id: l3_1TR6.c,v 1.4 1996/06/06 14:22:28 fritz Exp $ + * + * $Log: l3_1TR6.c,v $ + * Revision 1.4 1996/06/06 14:22:28 fritz + * Changed level of "non-digital call..." message, since + * with audio support, this is quite normal. + * + * Revision 1.3 1996/04/30 21:54:42 isdn4dev + * SPV, callback , remove some debugging code Karsten Keil + * + * Revision 1.2 1996/04/20 16:47:23 fritz + * Changed statemachine to allow reject of an incoming call. + * Report all incoming calls, not just those with Service = 7. + * Misc. typos + * + * Revision 1.1 1996/04/13 10:25:16 fritz + * Initial revision + * + * + */ + +static void +l3_1TR6_message(struct PStack *st, int mt, int pd) +{ + struct BufHeader *dibh; + byte *p; + + BufPoolGet(&dibh, st->l1.sbufpool, GFP_ATOMIC, (void *) st, 18); + p = DATAPTR(dibh); + p += st->l2.ihsize; + + *p++ = pd; + *p++ = 0x1; + *p++ = st->l3.callref; + *p++ = mt; + + dibh->datasize = p - DATAPTR(dibh); + i_down(st, dibh); +} + +static void +l3_1tr6_setup(struct PStack *st, byte pr, void *arg) +{ + struct BufHeader *dibh; + byte *p; + char *teln; + + st->l3.callref = st->pa->callref; + BufPoolGet(&dibh, st->l1.sbufpool, GFP_ATOMIC, (void *) st, 19); + p = DATAPTR(dibh); + p += st->l2.ihsize; + + *p++ = PROTO_DIS_N1; + *p++ = 0x1; + *p++ = st->l3.callref; + *p++ = MT_N1_SETUP; + + if ('S' == (st->pa->called[0] & 0x5f)) { /* SPV ??? */ + /* NSF SPV */ + *p++ = WE0_netSpecFac; + *p++ = 4; /* Laenge */ + *p++ = 0; + *p++ = FAC_SPV; /* SPV */ + *p++ = st->pa->info; /* 0 for all Services */ + *p++ = st->pa->info2; /* 0 for all Services */ + *p++ = WE0_netSpecFac; + *p++ = 4; /* Laenge */ + *p++ = 0; + *p++ = FAC_Activate; /* aktiviere SPV (default) */ + *p++ = st->pa->info; /* 0 for all Services */ + *p++ = st->pa->info2; /* 0 for all Services */ + } + if (st->pa->calling[0] != '\0') { + *p++ = WE0_origAddr; + *p++ = strlen(st->pa->calling) + 1; + /* Classify as AnyPref. */ + *p++ = 0x81; /* Ext = '1'B, Type = '000'B, Plan = '0001'B. */ + teln = st->pa->calling; + while (*teln) + *p++ = *teln++ & 0x7f; + } + *p++ = WE0_destAddr; + teln = st->pa->called; + if ('S' != (st->pa->called[0] & 0x5f)) { /* Keine SPV ??? */ + *p++ = strlen(st->pa->called) + 1; + st->pa->spv = 0; + } else { /* SPV */ + *p++ = strlen(st->pa->called); + teln++; /* skip S */ + st->pa->spv = 1; + } + /* Classify as AnyPref. */ + *p++ = 0x81; /* Ext = '1'B, Type = '000'B, Plan = '0001'B. */ + while (*teln) + *p++ = *teln++ & 0x7f; + + *p++ = WE_Shift_F6; + /* Codesatz 6 fuer Service */ + *p++ = WE6_serviceInd; + *p++ = 2; /* len=2 info,info2 */ + *p++ = st->pa->info; + *p++ = st->pa->info2; + + dibh->datasize = p - DATAPTR(dibh); + + newl3state(st, 1); + i_down(st, dibh); + +} + +static void +l3_1tr6_tu_setup(struct PStack *st, byte pr, void *arg) +{ + byte *p; + struct BufHeader *ibh = arg; + + p = DATAPTR(ibh); + p += st->l2.uihsize; + st->pa->callref = getcallref(p); + st->l3.callref = 0x80 + st->pa->callref; + + /* Channel Identification */ + p = DATAPTR(ibh); + if ((p = findie(p + st->l2.uihsize, ibh->datasize - st->l2.uihsize, + WE0_chanID, 0))) { + st->pa->bchannel = p[2] & 0x3; + } else + printk(KERN_INFO "l3tu_setup: Channel ident not found\n"); + + p = DATAPTR(ibh); + + if ((p = findie(p + st->l2.uihsize, ibh->datasize - st->l2.uihsize, WE6_serviceInd, 6))) { + st->pa->info = p[2]; + st->pa->info2 = p[3]; + } else + printk(KERN_INFO "l3s12(1TR6): ServiceIndicator not found\n"); + + p = DATAPTR(ibh); + if ((p = findie(p + st->l2.uihsize, ibh->datasize - st->l2.uihsize, + WE0_destAddr, 0))) + iecpy(st->pa->called, p, 1); + else + strcpy(st->pa->called, ""); + + p = DATAPTR(ibh); + if ((p = findie(p + st->l2.uihsize, ibh->datasize - st->l2.uihsize, + WE0_origAddr, 0))) { + iecpy(st->pa->calling, p, 1); + } else + strcpy(st->pa->calling, ""); + + p = DATAPTR(ibh); + st->pa->spv = 0; + if ((p = findie(p + st->l2.uihsize, ibh->datasize - st->l2.uihsize, + WE0_netSpecFac, 0))) { + if ((FAC_SPV == p[3]) || (FAC_Activate == p[3])) + st->pa->spv = 1; + } + BufPoolRelease(ibh); + + /* Signal all services, linklevel takes care of Service-Indicator */ + if (st->pa->info != 7) { + printk(KERN_DEBUG "non-digital call: %s -> %s\n", + st->pa->calling, + st->pa->called); + } + newl3state(st, 6); + st->l3.l3l4(st, CC_SETUP_IND, NULL); +} + +static void +l3_1tr6_tu_setup_ack(struct PStack *st, byte pr, void *arg) +{ + byte *p; + struct BufHeader *ibh = arg; + + p = DATAPTR(ibh); + if ((p = findie(p + st->l2.ihsize, ibh->datasize - st->l2.ihsize, + WE0_chanID, 0))) { + st->pa->bchannel = p[2] & 0x3; + } else + printk(KERN_INFO "octect 3 not found\n"); + + + BufPoolRelease(ibh); + newl3state(st, 2); +} + +static void +l3_1tr6_tu_call_sent(struct PStack *st, byte pr, void *arg) +{ + byte *p; + struct BufHeader *ibh = arg; + + p = DATAPTR(ibh); + if ((p = findie(p + st->l2.ihsize, ibh->datasize - st->l2.ihsize, + WE0_chanID, 0))) { + st->pa->bchannel = p[2] & 0x3; + } else + printk(KERN_INFO "octect 3 not found\n"); + + BufPoolRelease(ibh); + newl3state(st, 3); + st->l3.l3l4(st, CC_PROCEEDING_IND, NULL); +} + +static void +l3_1tr6_tu_alert(struct PStack *st, byte pr, void *arg) +{ + byte *p; + struct BufHeader *ibh = arg; + + + p = DATAPTR(ibh); + if ((p = findie(p + st->l2.ihsize, ibh->datasize - st->l2.ihsize, + WE6_statusCalled, 6))) { + if (DEBUG_1TR6 > 2) + printk(KERN_INFO "status called %x\n", p[2]); + } else if (DEBUG_1TR6 > 0) + printk(KERN_INFO "statusCalled not found\n"); + + BufPoolRelease(ibh); + newl3state(st, 4); + st->l3.l3l4(st, CC_ALERTING_IND, NULL); +} + +static void +l3_1tr6_tu_info(struct PStack *st, byte pr, void *arg) +{ + byte *p; + int i,tmpcharge=0; + char a_charge[8]; + struct BufHeader *ibh = arg; + + p = DATAPTR(ibh); + if ((p = findie(p + st->l2.ihsize, ibh->datasize - st->l2.ihsize, + WE6_chargingInfo, 6))) { + iecpy(a_charge, p, 1); + for (i = 0; i < strlen (a_charge); i++) { + tmpcharge *= 10; + tmpcharge += a_charge[i] & 0xf; + } + if (tmpcharge > st->pa->chargeinfo) { + st->pa->chargeinfo = tmpcharge; + st->l3.l3l4 (st, CC_INFO_CHARGE, NULL); + } + if (DEBUG_1TR6 > 2) + printk(KERN_INFO "chargingInfo %d\n", st->pa->chargeinfo); + } else if (DEBUG_1TR6 > 2) + printk(KERN_INFO "chargingInfo not found\n"); + + BufPoolRelease(ibh); +} + +static void +l3_1tr6_tu_info_s2(struct PStack *st, byte pr, void *arg) +{ + byte *p; + int i; + struct BufHeader *ibh = arg; + + if (DEBUG_1TR6 > 4) { + p = DATAPTR(ibh); + for (i = 0; i < ibh->datasize; i++) { + printk(KERN_INFO "Info DATA %x\n", p[i]); + } + } + BufPoolRelease(ibh); +} + +static void +l3_1tr6_tu_connect(struct PStack *st, byte pr, void *arg) +{ + struct BufHeader *ibh = arg; + + st->pa->chargeinfo=0; + BufPoolRelease(ibh); + st->l3.l3l4(st, CC_SETUP_CNF, NULL); + newl3state(st, 10); +} + +static void +l3_1tr6_tu_rel(struct PStack *st, byte pr, void *arg) +{ + struct BufHeader *ibh = arg; + + BufPoolRelease(ibh); + l3_1TR6_message(st, MT_N1_REL_ACK, PROTO_DIS_N1); + st->l3.l3l4(st, CC_RELEASE_IND, NULL); + newl3state(st, 0); +} + +static void +l3_1tr6_tu_rel_ack(struct PStack *st, byte pr, void *arg) +{ + struct BufHeader *ibh = arg; + + BufPoolRelease(ibh); + newl3state(st, 0); + st->l3.l3l4(st, CC_RELEASE_CNF, NULL); +} + +static void +l3_1tr6_tu_disc(struct PStack *st, byte pr, void *arg) +{ + struct BufHeader *ibh = arg; + byte *p; + int i,tmpcharge=0; + char a_charge[8]; + + p = DATAPTR(ibh); + if ((p = findie(p + st->l2.ihsize, ibh->datasize - st->l2.ihsize, + WE6_chargingInfo, 6))) { + iecpy(a_charge, p, 1); + for (i = 0; i < strlen (a_charge); i++) { + tmpcharge *= 10; + tmpcharge += a_charge[i] & 0xf; + } + if (tmpcharge > st->pa->chargeinfo) { + st->pa->chargeinfo = tmpcharge; + st->l3.l3l4 (st, CC_INFO_CHARGE, NULL); + } + if (DEBUG_1TR6 > 2) + printk(KERN_INFO "chargingInfo %d\n", st->pa->chargeinfo); + } else if (DEBUG_1TR6 > 2) + printk(KERN_INFO "chargingInfo not found\n"); + + p = DATAPTR(ibh); + if ((p = findie(p + st->l2.ihsize, ibh->datasize - st->l2.ihsize, + WE0_cause, 0))) { + if (p[1] > 0) { + st->pa->cause = p[2]; + } else { + st->pa->cause = 0; + } + if (DEBUG_1TR6 > 1) + printk(KERN_INFO "Cause %x\n", st->pa->cause); + } else if (DEBUG_1TR6 > 0) + printk(KERN_INFO "Cause not found\n"); + + BufPoolRelease(ibh); + newl3state(st, 12); + st->l3.l3l4(st, CC_DISCONNECT_IND, NULL); +} + + +static void +l3_1tr6_tu_connect_ack(struct PStack *st, byte pr, void *arg) +{ + struct BufHeader *ibh = arg; + + BufPoolRelease(ibh); + st->pa->chargeinfo = 0; + st->l3.l3l4(st, CC_SETUP_COMPLETE_IND, NULL); + newl3state(st, 10); +} + +static void +l3_1tr6_alert(struct PStack *st, byte pr, + void *arg) +{ + l3_1TR6_message(st, MT_N1_ALERT, PROTO_DIS_N1); + newl3state(st, 7); +} + +static void +l3_1tr6_conn(struct PStack *st, byte pr, + void *arg) +{ + struct BufHeader *dibh; + byte *p; + + st->l3.callref = 0x80 + st->pa->callref; + + BufPoolGet(&dibh, st->l1.sbufpool, GFP_ATOMIC, (void *) st, 20); + p = DATAPTR(dibh); + p += st->l2.ihsize; + + *p++ = PROTO_DIS_N1; + *p++ = 0x1; + *p++ = st->l3.callref; + *p++ = MT_N1_CONN; + + if (st->pa->spv) { /* SPV ??? */ + /* NSF SPV */ + *p++ = WE0_netSpecFac; + *p++ = 4; /* Laenge */ + *p++ = 0; + *p++ = FAC_SPV; /* SPV */ + *p++ = st->pa->info; + *p++ = st->pa->info2; + *p++ = WE0_netSpecFac; + *p++ = 4; /* Laenge */ + *p++ = 0; + *p++ = FAC_Activate; /* aktiviere SPV */ + *p++ = st->pa->info; + *p++ = st->pa->info2; + } + dibh->datasize = p - DATAPTR(dibh); + + i_down(st, dibh); + + newl3state(st, 8); +} + +static void +l3_1tr6_ignore(struct PStack *st, byte pr, void *arg) +{ + newl3state(st, 0); +} + +static void +l3_1tr6_disconn_req(struct PStack *st, byte pr, void *arg) +{ + struct BufHeader *dibh; + byte *p; + byte rejflg; + + BufPoolGet(&dibh, st->l1.sbufpool, GFP_ATOMIC, (void *) st, 21); + p = DATAPTR(dibh); + p += st->l2.ihsize; + + *p++ = PROTO_DIS_N1; + *p++ = 0x1; + *p++ = st->l3.callref; + *p++ = MT_N1_DISC; + + if (st->l3.state == 7) { + rejflg = 1; + *p++ = WE0_cause; /* Anruf abweisen */ + *p++ = 0x01; /* Laenge = 1 */ + *p++ = CAUSE_CallRejected; + } else { + rejflg = 0; + *p++ = WE0_cause; + *p++ = 0x0; /* Laenge = 0 normales Ausloesen */ + } + + dibh->datasize = p - DATAPTR(dibh); + + i_down(st, dibh); + + newl3state(st, 11); +} + +static void +l3_1tr6_rel_req(struct PStack *st, byte pr, void *arg) +{ + l3_1TR6_message(st, MT_N1_REL, PROTO_DIS_N1); + newl3state(st, 19); +} + +static struct stateentry downstatelist_1tr6t[] = +{ + {0, CC_SETUP_REQ, l3_1tr6_setup}, + {4, CC_DISCONNECT_REQ, l3_1tr6_disconn_req}, + {6, CC_REJECT_REQ, l3_1tr6_ignore}, + {6, CC_SETUP_RSP, l3_1tr6_conn}, + {6, CC_ALERTING_REQ, l3_1tr6_alert}, + {7, CC_SETUP_RSP, l3_1tr6_conn}, + {7, CC_DISCONNECT_REQ, l3_1tr6_disconn_req}, + {7, CC_DLRL, l3_1tr6_disconn_req}, + {8, CC_DISCONNECT_REQ, l3_1tr6_disconn_req}, + {10, CC_DISCONNECT_REQ, l3_1tr6_disconn_req}, + {12, CC_RELEASE_REQ, l3_1tr6_rel_req} +}; + +static int downsl_1tr6t_len = sizeof(downstatelist_1tr6t) / +sizeof(struct stateentry); + +static struct stateentry datastatelist_1tr6t[] = +{ + {0, MT_N1_SETUP, l3_1tr6_tu_setup}, + {0, MT_N1_REL, l3_1tr6_tu_rel}, + {1, MT_N1_SETUP_ACK, l3_1tr6_tu_setup_ack}, + {1, MT_N1_CALL_SENT, l3_1tr6_tu_call_sent}, + {1, MT_N1_REL, l3_1tr6_tu_rel}, + {1, MT_N1_DISC, l3_1tr6_tu_disc}, + {2, MT_N1_CALL_SENT, l3_1tr6_tu_call_sent}, + {2, MT_N1_ALERT, l3_1tr6_tu_alert}, + {2, MT_N1_CONN, l3_1tr6_tu_connect}, + {2, MT_N1_REL, l3_1tr6_tu_rel}, + {2, MT_N1_DISC, l3_1tr6_tu_disc}, + {2, MT_N1_INFO, l3_1tr6_tu_info_s2}, + {3, MT_N1_ALERT, l3_1tr6_tu_alert}, + {3, MT_N1_CONN, l3_1tr6_tu_connect}, + {3, MT_N1_REL, l3_1tr6_tu_rel}, + {3, MT_N1_DISC, l3_1tr6_tu_disc}, + {4, MT_N1_ALERT, l3_1tr6_tu_alert}, + {4, MT_N1_CONN, l3_1tr6_tu_connect}, + {4, MT_N1_REL, l3_1tr6_tu_rel}, + {4, MT_N1_DISC, l3_1tr6_tu_disc}, + {7, MT_N1_REL, l3_1tr6_tu_rel}, + {7, MT_N1_DISC, l3_1tr6_tu_disc}, + {8, MT_N1_REL, l3_1tr6_tu_rel}, + {8, MT_N1_DISC, l3_1tr6_tu_disc}, + {8, MT_N1_CONN_ACK, l3_1tr6_tu_connect_ack}, + {10, MT_N1_REL, l3_1tr6_tu_rel}, + {10, MT_N1_DISC, l3_1tr6_tu_disc}, + {10, MT_N1_INFO, l3_1tr6_tu_info}, + {11, MT_N1_REL, l3_1tr6_tu_rel}, + {12, MT_N1_REL, l3_1tr6_tu_rel}, + {19, MT_N1_REL_ACK, l3_1tr6_tu_rel_ack} +}; + +static int datasl_1tr6t_len = sizeof(datastatelist_1tr6t) / +sizeof(struct stateentry); diff --git a/drivers/isdn/teles/l3_1TR6.h b/drivers/isdn/teles/l3_1TR6.h new file mode 100644 index 000000000..a3502fb36 --- /dev/null +++ b/drivers/isdn/teles/l3_1TR6.h @@ -0,0 +1,160 @@ +/* $Id: l3_1TR6.h,v 1.3 1996/04/30 21:53:48 isdn4dev Exp $ + * + * $Log: l3_1TR6.h,v $ + * Revision 1.3 1996/04/30 21:53:48 isdn4dev + * Bugs, SPV, Logging in q931.c Karsten Keil + * + * Revision 1.1 1996/04/13 10:25:42 fritz + * Initial revision + * + * + */ +#ifndef l3_1TR6 +#define l3_1TR6 + +#define PROTO_DIS_N0 0x40 +#define PROTO_DIS_N1 0x41 + +/* + * MsgType N0 + */ +#define MT_N0_REG_IND 0x61 +#define MT_N0_CANC_IND 0x62 +#define MT_N0_FAC_STA 0x63 +#define MT_N0_STA_ACK 0x64 +#define MT_N0_STA_REJ 0x65 +#define MT_N0_FAC_INF 0x66 +#define MT_N0_INF_ACK 0x67 +#define MT_N0_INF_REJ 0x68 +#define MT_N0_CLOSE 0x75 +#define MT_N0_CLO_ACK 0x77 + + +/* + * MsgType N1 + */ + +#define MT_N1_ESC 0x00 +#define MT_N1_ALERT 0x01 +#define MT_N1_CALL_SENT 0x02 +#define MT_N1_CONN 0x07 +#define MT_N1_CONN_ACK 0x0F +#define MT_N1_SETUP 0x05 +#define MT_N1_SETUP_ACK 0x0D +#define MT_N1_RES 0x26 +#define MT_N1_RES_ACK 0x2E +#define MT_N1_RES_REJ 0x22 +#define MT_N1_SUSP 0x25 +#define MT_N1_SUSP_ACK 0x2D +#define MT_N1_SUSP_REJ 0x21 +#define MT_N1_USER_INFO 0x20 +#define MT_N1_DET 0x40 +#define MT_N1_DISC 0x45 +#define MT_N1_REL 0x4D +#define MT_N1_REL_ACK 0x5A +#define MT_N1_CANC_ACK 0x6E +#define MT_N1_CANC_REJ 0x67 +#define MT_N1_CON_CON 0x69 +#define MT_N1_FAC 0x60 +#define MT_N1_FAC_ACK 0x68 +#define MT_N1_FAC_CAN 0x66 +#define MT_N1_FAC_REG 0x64 +#define MT_N1_FAC_REJ 0x65 +#define MT_N1_INFO 0x6D +#define MT_N1_REG_ACK 0x6C +#define MT_N1_REG_REJ 0x6F +#define MT_N1_STAT 0x63 + + + +/* + * W Elemente + */ + +#define WE_Shift_F0 0x90 +#define WE_Shift_F6 0x96 +#define WE_Shift_OF0 0x98 +#define WE_Shift_OF6 0x9E + +#define WE0_cause 0x08 +#define WE0_connAddr 0x0C +#define WE0_callID 0x10 +#define WE0_chanID 0x18 +#define WE0_netSpecFac 0x20 +#define WE0_display 0x28 +#define WE0_keypad 0x2C +#define WE0_origAddr 0x6C +#define WE0_destAddr 0x70 +#define WE0_userInfo 0x7E + +#define WE0_moreData 0xA0 +#define WE0_congestLevel 0xB0 + +#define WE6_serviceInd 0x01 +#define WE6_chargingInfo 0x02 +#define WE6_date 0x03 +#define WE6_facSelect 0x05 +#define WE6_facStatus 0x06 +#define WE6_statusCalled 0x07 +#define WE6_addTransAttr 0x08 + +/* + * FacCodes + */ +#define FAC_Sperre 0x01 +#define FAC_Sperre_All 0x02 +#define FAC_Sperre_Fern 0x03 +#define FAC_Sperre_Intl 0x04 +#define FAC_Sperre_Interk 0x05 + +#define FAC_Forward1 0x02 +#define FAC_Forward2 0x03 +#define FAC_Konferenz 0x06 +#define FAC_GrabBchan 0x0F +#define FAC_Reactivate 0x10 +#define FAC_Konferenz3 0x11 +#define FAC_Dienstwechsel1 0x12 +#define FAC_Dienstwechsel2 0x13 +#define FAC_NummernIdent 0x14 +#define FAC_GBG 0x15 +#define FAC_DisplayUebergeben 0x17 +#define FAC_DisplayUmgeleitet 0x1A +#define FAC_Unterdruecke 0x1B +#define FAC_Deactivate 0x1E +#define FAC_Activate 0x1D +#define FAC_SPV 0x1F +#define FAC_Rueckwechsel 0x23 +#define FAC_Umleitung 0x24 + +/* + * Cause codes + */ +#define CAUSE_InvCRef 0x01 +#define CAUSE_BearerNotImpl 0x03 +#define CAUSE_CIDunknown 0x07 +#define CAUSE_CIDinUse 0x08 +#define CAUSE_NoChans 0x0A +#define CAUSE_FacNotImpl 0x10 +#define CAUSE_FacNotSubscr 0x11 +#define CAUSE_OutgoingBarred 0x20 +#define CAUSE_UserAccessBusy 0x21 +#define CAUSE_NegativeGBG 0x22 +#define CAUSE_UnknownGBG 0x23 +#define CAUSE_NoSPVknown 0x25 +#define CAUSE_DestNotObtain 0x35 +#define CAUSE_NumberChanged 0x38 +#define CAUSE_OutOfOrder 0x39 +#define CAUSE_NoUserResponse 0x3A +#define CAUSE_UserBusy 0x3B +#define CAUSE_IncomingBarred 0x3D +#define CAUSE_CallRejected 0x3E +#define CAUSE_NetworkCongestion 0x59 +#define CAUSE_RemoteUser 0x5A +#define CAUSE_LocalProcErr 0x70 +#define CAUSE_RemoteProcErr 0x71 +#define CAUSE_RemoteUserSuspend 0x72 +#define CAUSE_RemoteUserResumed 0x73 +#define CAUSE_UserInfoDiscarded 0x7F + + +#endif diff --git a/drivers/isdn/teles/llglue.c b/drivers/isdn/teles/llglue.c new file mode 100644 index 000000000..81a0a1e00 --- /dev/null +++ b/drivers/isdn/teles/llglue.c @@ -0,0 +1,149 @@ +/* $Id: llglue.c,v 1.6 1996/06/03 20:03:39 fritz Exp $ + * + * $Log: llglue.c,v $ + * Revision 1.6 1996/06/03 20:03:39 fritz + * Fixed typos. + * + * Revision 1.5 1996/05/31 00:58:47 fritz + * Errata: Reverted change from rev 1.4. + * + * Revision 1.4 1996/05/26 14:59:57 fritz + * Bugfix: maxbufsize had been set without respect to possible X.75 header. + * + * Revision 1.3 1996/05/01 14:19:57 fritz + * Added ISDN_FEATURE_L2_TRANS + * + * Revision 1.2 1996/04/29 23:01:46 fritz + * Added driverId and channel to readstatus(). + * + * Revision 1.1 1996/04/13 10:26:29 fritz + * Initial revision + * + * + */ +#define __NO_VERSION__ +#include "teles.h" +#include <linux/malloc.h> +#include <linux/timer.h> + + +extern struct Channel *chanlist; +int drid; +char *teles_id = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; + +isdn_if iif; + +#define TELES_STATUS_BUFSIZE 4096 +static byte *teles_status_buf = NULL; +static byte *teles_status_read = NULL; +static byte *teles_status_write = NULL; +static byte *teles_status_end = NULL; + +int +teles_readstatus(byte * buf, int len, int user, int id, int channel) +{ + int count; + byte *p; + + for (p = buf, count = 0; count < len; p++, count++) { + if (user) { + put_user(*teles_status_read, p); + teles_status_read++; + } else + *p++ = *teles_status_read++; + if (teles_status_read > teles_status_end) + teles_status_read = teles_status_buf; + } + return count; +} + +void +teles_putstatus(char *buf) +{ + long flags; + int len, count, i; + byte *p; + isdn_ctrl ic; + + save_flags(flags); + cli(); + count = 0; + len = strlen(buf); + for (p = buf, i = len; i > 0; i--, p++) { + *teles_status_write++ = *p; + if (teles_status_write > teles_status_end) + teles_status_write = teles_status_buf; + count++; + } + restore_flags(flags); + if (count) { + ic.command = ISDN_STAT_STAVAIL; + ic.driver = drid; + ic.arg = count; + iif.statcallb(&ic); + } +} + + +int +ll_init(void) +{ + isdn_ctrl ic; + + teles_status_buf = Smalloc(TELES_STATUS_BUFSIZE, + GFP_KERNEL, "teles_status_buf"); + if (!teles_status_buf) { + printk(KERN_ERR "teles: Could not allocate status-buffer\n"); + return (-EIO); + } else { + teles_status_read = teles_status_buf; + teles_status_write = teles_status_buf; + teles_status_end = teles_status_buf + TELES_STATUS_BUFSIZE - 1; + } + + iif.channels = CallcNewChan(); + iif.maxbufsize = BUFFER_SIZE(HSCX_SBUF_ORDER, HSCX_SBUF_BPPS); + iif.features = + ISDN_FEATURE_L2_X75I | + ISDN_FEATURE_L2_HDLC | + ISDN_FEATURE_L2_TRANS | + ISDN_FEATURE_L3_TRANS | + ISDN_FEATURE_P_1TR6 | + ISDN_FEATURE_P_EURO; + + iif.command = teles_command; + iif.writebuf = teles_writebuf; + iif.writecmd = NULL; + iif.readstat = teles_readstatus; + strncpy(iif.id, teles_id, sizeof(iif.id) - 1); + + register_isdn(&iif); + drid = iif.channels; + + ic.driver = drid; + ic.command = ISDN_STAT_RUN; + iif.statcallb(&ic); + return 0; +} + +void +ll_stop(void) +{ + isdn_ctrl ic; + + ic.command = ISDN_STAT_STOP; + ic.driver = drid; + iif.statcallb(&ic); + + CallcFreeChan(); +} + +void +ll_unload(void) +{ + isdn_ctrl ic; + + ic.command = ISDN_STAT_UNLOAD; + ic.driver = drid; + iif.statcallb(&ic); +} diff --git a/drivers/isdn/teles/mod.c b/drivers/isdn/teles/mod.c new file mode 100644 index 000000000..0b12d9c92 --- /dev/null +++ b/drivers/isdn/teles/mod.c @@ -0,0 +1,143 @@ +/* $Id: mod.c,v 1.1 1996/04/13 10:27:02 fritz Exp $ + * + * $Log: mod.c,v $ + * Revision 1.1 1996/04/13 10:27:02 fritz + * Initial revision + * + * + */ +#include "teles.h" + +extern struct IsdnCard cards[]; +extern char *teles_id; + +int nrcards; + +typedef struct { + unsigned int membase; + int interrupt; + unsigned int iobase; + unsigned int protocol; +} io_type; + +io_type io[] = +{ + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, +}; + +void +teles_mod_dec_use_count(void) +{ + MOD_DEC_USE_COUNT; +} + +void +teles_mod_inc_use_count(void) +{ + MOD_INC_USE_COUNT; +} + +#ifdef MODULE +#define teles_init init_module +#else +void teles_setup(char *str, int *ints) +{ + int i, j, argc; + static char sid[20]; + + argc = ints[0]; + i = 0; + j = 1; + while (argc && (i<16)) { + if (argc) { + io[i].iobase = ints[j]; + j++; argc--; + } + if (argc) { + io[i].interrupt = ints[j]; + j++; argc--; + } + if (argc) { + io[i].membase = ints[j]; + j++; argc--; + } + if (argc) { + io[i].protocol = ints[j]; + j++; argc--; + } + i++; + } + if (strlen(str)) { + strcpy(sid,str); + teles_id = sid; + } +} +#endif + +int +teles_init(void) +{ + int i; + + nrcards = 0; + for (i = 0; i < 16; i++) { + if (io[i].protocol) { + cards[i].membase = io[i].membase; + cards[i].interrupt = io[i].interrupt; + cards[i].iobase = io[i].iobase; + cards[i].protocol = io[i].protocol; + } + } + for (i = 0; i < 16; i++) + if (cards[i].protocol) + nrcards++; + printk(KERN_DEBUG "teles: Total %d card%s defined\n", + nrcards, (nrcards > 1) ? "s" : ""); + if (teles_inithardware()) { + /* Install only, if at least one card found */ + Isdnl2New(); + TeiNew(); + CallcNew(); + ll_init(); + + /* No symbols to export, hide all symbols */ + register_symtab(NULL); + +#ifdef MODULE + printk(KERN_NOTICE "Teles module installed\n"); +#endif + return (0); + } else + return -EIO; +} + +#ifdef MODULE +void +cleanup_module(void) +{ + + ll_stop(); + TeiFree(); + Isdnl2Free(); + CallcFree(); + teles_closehardware(); + ll_unload(); + printk(KERN_NOTICE "Teles module removed\n"); + +} +#endif diff --git a/drivers/isdn/teles/proto.h b/drivers/isdn/teles/proto.h new file mode 100644 index 000000000..0d7ae8ef4 --- /dev/null +++ b/drivers/isdn/teles/proto.h @@ -0,0 +1,18 @@ +/* $Id: proto.h,v 1.1 1996/09/23 01:53:52 fritz Exp $ + * + * not much now - just the l3 proto discriminator + * + * $Log: proto.h,v $ + * Revision 1.1 1996/09/23 01:53:52 fritz + * Bugfix: discard unknown frames (non-EDSS1 and non-1TR6). + * + */ + +#ifndef PROTO_H +#define PROTO_H + +#define PROTO_EURO 0x08 +#define PROTO_DIS_N0 0x40 +#define PROTO_DIS_N1 0x41 + +#endif diff --git a/drivers/isdn/teles/q931.c b/drivers/isdn/teles/q931.c new file mode 100644 index 000000000..0b93388cf --- /dev/null +++ b/drivers/isdn/teles/q931.c @@ -0,0 +1,1149 @@ +/* $Id: q931.c,v 1.5 1996/06/03 20:03:40 fritz Exp $ + * + * q931.c code to decode ITU Q.931 call control messages + * + * Author Jan den Ouden + * + * Changelog + * + * Pauline Middelink general improvements + * + * Beat Doebeli cause texts, display information element + * + * Karsten Keil cause texts, display information element for 1TR6 + * + * + * $Log: q931.c,v $ + * Revision 1.5 1996/06/03 20:03:40 fritz + * Fixed typos. + * + * Revision 1.4 1996/05/17 03:46:17 fritz + * General cleanup. + * + * Revision 1.3 1996/04/30 22:06:50 isdn4dev + * logging 1TR6 messages correctly Karsten Keil + * + * Revision 1.2 1996/04/20 16:48:19 fritz + * Misc. typos + * + * Revision 1.1 1996/04/13 10:27:49 fritz + * Initial revision + * + * + */ + + +#define __NO_VERSION__ +#include "teles.h" +#include "l3_1TR6.h" + +byte * +findie(byte * p, int size, byte ie, int wanted_set) +{ + int l, codeset, maincodeset; + byte *pend = p + size; + + /* skip protocol discriminator, callref and message type */ + p++; + l = (*p++) & 0xf; + p += l; + p++; + codeset = 0; + maincodeset = 0; + /* while there are bytes left... */ + while (p < pend) { + if ((*p & 0xf0) == 0x90) { + codeset = *p & 0x07; + if (!(*p & 0x08)) + maincodeset = codeset; + } + if (*p & 0x80) + p++; + else { + if (codeset == wanted_set) { + if (*p == ie) + return (p); + if (*p > ie) + return (NULL); + } + p++; + l = *p++; + p += l; + codeset = maincodeset; + } + } + return (NULL); +} + +void +iecpy(byte * dest, byte * iestart, int ieoffset) +{ + byte *p; + int l; + + p = iestart + ieoffset + 2; + l = iestart[1] - ieoffset; + while (l--) + *dest++ = *p++; + *dest++ = '\0'; +} + +int +getcallref(byte * p) +{ + p++; /* prot discr */ + p++; /* callref length */ + return (*p); /* assuming one-byte callref */ +} + +/* + * According to Table 4-2/Q.931 + */ +static +struct MessageType { + byte nr; + char *descr; +} mtlist[] = { + + { + 0x1, "ALERTING" + }, + { + 0x2, "CALL PROCEEDING" + }, + { + 0x7, "CONNECT" + }, + { + 0xf, "CONNECT ACKNOWLEDGE" + }, + { + 0x3, "PROGRESS" + }, + { + 0x5, "SETUP" + }, + { + 0xd, "SETUP ACKNOWLEDGE" + }, + { + 0x26, "RESUME" + }, + { + 0x2e, "RESUME ACKNOWLEDGE" + }, + { + 0x22, "RESUME REJECT" + }, + { + 0x25, "SUSPEND" + }, + { + 0x2d, "SUSPEND ACKNOWLEDGE" + }, + { + 0x21, "SUSPEND REJECT" + }, + { + 0x20, "USER INFORMATION" + }, + { + 0x45, "DISCONNECT" + }, + { + 0x4d, "RELEASE" + }, + { + 0x5a, "RELEASE COMPLETE" + }, + { + 0x46, "RESTART" + }, + { + 0x4e, "RESTART ACKNOWLEDGE" + }, + { + 0x60, "SEGMENT" + }, + { + 0x79, "CONGESTION CONTROL" + }, + { + 0x7b, "INFORMATION" + }, + { + 0x62, "FACILITY" + }, + { + 0x6e, "NOTIFY" + }, + { + 0x7d, "STATUS" + }, + { + 0x75, "STATUS ENQUIRY" + } +}; + +#define MTSIZE sizeof(mtlist)/sizeof(struct MessageType) + +static +struct MessageType mt_n0[] = +{ + {MT_N0_REG_IND, "REGister INDication"}, + {MT_N0_CANC_IND, "CANCel INDication"}, + {MT_N0_FAC_STA, "FACility STAtus"}, + {MT_N0_STA_ACK, "STAtus ACKnowledge"}, + {MT_N0_STA_REJ, "STAtus REJect"}, + {MT_N0_FAC_INF, "FACility INFormation"}, + {MT_N0_INF_ACK, "INFormation ACKnowledge"}, + {MT_N0_INF_REJ, "INFormation REJect"}, + {MT_N0_CLOSE, "CLOSE"}, + {MT_N0_CLO_ACK, "CLOse ACKnowledge"} +}; + +int mt_n0_len = (sizeof(mt_n0) / sizeof(struct MessageType)); + +static +struct MessageType mt_n1[] = +{ + {MT_N1_ESC, "ESCape"}, + {MT_N1_ALERT, "ALERT"}, + {MT_N1_CALL_SENT, "CALL SENT"}, + {MT_N1_CONN, "CONNect"}, + {MT_N1_CONN_ACK, "CONNect ACKnowledge"}, + {MT_N1_SETUP, "SETUP"}, + {MT_N1_SETUP_ACK, "SETUP ACKnowledge"}, + {MT_N1_RES, "RESume"}, + {MT_N1_RES_ACK, "RESume ACKnowledge"}, + {MT_N1_RES_REJ, "RESume REJect"}, + {MT_N1_SUSP, "SUSPend"}, + {MT_N1_SUSP_ACK, "SUSPend ACKnowledge"}, + {MT_N1_SUSP_REJ, "SUSPend REJect"}, + {MT_N1_USER_INFO, "USER INFO"}, + {MT_N1_DET, "DETach"}, + {MT_N1_DISC, "DISConnect"}, + {MT_N1_REL, "RELease"}, + {MT_N1_REL_ACK, "RELease ACKnowledge"}, + {MT_N1_CANC_ACK, "CANCel ACKnowledge"}, + {MT_N1_CANC_REJ, "CANCel REJect"}, + {MT_N1_CON_CON, "CONgestion CONtrol"}, + {MT_N1_FAC, "FACility"}, + {MT_N1_FAC_ACK, "FACility ACKnowledge"}, + {MT_N1_FAC_CAN, "FACility CANcel"}, + {MT_N1_FAC_REG, "FACility REGister"}, + {MT_N1_FAC_REJ, "FACility REJect"}, + {MT_N1_INFO, "INFOrmation"}, + {MT_N1_REG_ACK, "REGister ACKnowledge"}, + {MT_N1_REG_REJ, "REGister REJect"}, + {MT_N1_STAT, "STATus"} +}; + +int mt_n1_len = (sizeof(mt_n1) / sizeof(struct MessageType)); + +static struct MessageType fac_1tr6[] = +{ + {FAC_Sperre, "Sperre"}, + {FAC_Forward1, "Forward 1"}, + {FAC_Forward2, "Forward 2"}, + {FAC_Konferenz, "Konferenz"}, + {FAC_GrabBchan, "Grab Bchannel"}, + {FAC_Reactivate, "Reactivate"}, + {FAC_Konferenz3, "Dreier Konferenz"}, + {FAC_Dienstwechsel1, "Einseitiger Dienstwechsel"}, + {FAC_Dienstwechsel2, "Zweiseitiger Dienstwechsel"}, + {FAC_NummernIdent, "Rufnummer-Identifizierung"}, + {FAC_GBG, "GBG"}, + {FAC_DisplayUebergeben, "Display Uebergeben"}, + {FAC_DisplayUmgeleitet, "Display Umgeleitet"}, + {FAC_Unterdruecke, "Unterdruecke Rufnummer"}, + {FAC_Deactivate, "Deactivate"}, + {FAC_Activate, "Activate"}, + {FAC_SPV, "SPV"}, + {FAC_Rueckwechsel, "Rueckwechsel"}, + {FAC_Umleitung, "Umleitung"} +}; +int fac_1tr6_len = (sizeof(fac_1tr6) / sizeof(struct MessageType)); + + + +static int +prbits(char *dest, byte b, int start, int len) +{ + char *dp = dest; + + b = b << (8 - start); + while (len--) { + if (b & 0x80) + *dp++ = '1'; + else + *dp++ = '0'; + b = b << 1; + } + return (dp - dest); +} + +static +byte * +skipext(byte * p) +{ + while (!(*p++ & 0x80)); + return (p); +} + +/* + * Cause Values According to Q.850 + * edescr: English description + * ddescr: German description used by Swissnet II (Swiss Telecom + * not yet written... + */ + +static +struct CauseValue { + byte nr; + char *edescr; + char *ddescr; +} cvlist[] = { + + { + 0x01, "Unallocated (unassigned) number", "Nummer nicht zugeteilt" + }, + { + 0x02, "No route to specified transit network", "" + }, + { + 0x03, "No route to destination", "" + }, + { + 0x04, "Send special information tone", "" + }, + { + 0x05, "Misdialled trunk prefix", "" + }, + { + 0x06, "Channel unacceptable", "Kanal nicht akzeptierbar" + }, + { + 0x07, "Channel awarded and being delivered in an established channel", "" + }, + { + 0x08, "Preemption", "" + }, + { + 0x09, "Preemption - circuit reserved for reuse", "" + }, + { + 0x10, "Normal call clearing", "Normale Ausloesung" + }, + { + 0x11, "User busy", "TNB besetzt" + }, + { + 0x12, "No user responding", "" + }, + { + 0x13, "No answer from user (user alerted)", "" + }, + { + 0x14, "Subscriber absent", "" + }, + { + 0x15, "Call rejected", "" + }, + { + 0x16, "Number changed", "" + }, + { + 0x1a, "non-selected user clearing", "" + }, + { + 0x1b, "Destination out of order", "" + }, + { + 0x1c, "Invalid number format (address incomplete)", "" + }, + { + 0x1d, "Facility rejected", "" + }, + { + 0x1e, "Response to Status enquiry", "" + }, + { + 0x1f, "Normal, unspecified", "" + }, + { + 0x22, "No circuit/channel available", "" + }, + { + 0x26, "Network out of order", "" + }, + { + 0x27, "Permanent frame mode connection out-of-service", "" + }, + { + 0x28, "Permanent frame mode connection operational", "" + }, + { + 0x29, "Temporary failure", "" + }, + { + 0x2a, "Switching equipment congestion", "" + }, + { + 0x2b, "Access information discarded", "" + }, + { + 0x2c, "Requested circuit/channel not available", "" + }, + { + 0x2e, "Precedence call blocked", "" + }, + { + 0x2f, "Resource unavailable, unspecified", "" + }, + { + 0x31, "Quality of service unavailable", "" + }, + { + 0x32, "Requested facility not subscribed", "" + }, + { + 0x35, "Outgoing calls barred within CUG", "" + }, + { + 0x37, "Incoming calls barred within CUG", "" + }, + { + 0x39, "Bearer capability not authorized", "" + }, + { + 0x3a, "Bearer capability not presently available", "" + }, + { + 0x3e, "Inconsistency in designated outgoing access information and subscriber class ", " " + }, + { + 0x3f, "Service or option not available, unspecified", "" + }, + { + 0x41, "Bearer capability not implemented", "" + }, + { + 0x42, "Channel type not implemented", "" + }, + { + 0x43, "Requested facility not implemented", "" + }, + { + 0x44, "Only restricted digital information bearer capability is available", "" + }, + { + 0x4f, "Service or option not implemented", "" + }, + { + 0x51, "Invalid call reference value", "" + }, + { + 0x52, "Identified channel does not exist", "" + }, + { + 0x53, "A suspended call exists, but this call identity does not", "" + }, + { + 0x54, "Call identity in use", "" + }, + { + 0x55, "No call suspended", "" + }, + { + 0x56, "Call having the requested call identity has been cleared", "" + }, + { + 0x57, "User not member of CUG", "" + }, + { + 0x58, "Incompatible destination", "" + }, + { + 0x5a, "Non-existent CUG", "" + }, + { + 0x5b, "Invalid transit network selection", "" + }, + { + 0x5f, "Invalid message, unspecified", "" + }, + { + 0x60, "Mandatory information element is missing", "" + }, + { + 0x61, "Message type non-existent or not implemented", "" + }, + { + 0x62, "Message not compatible with call state or message type non-existent or not implemented ", " " + }, + { + 0x63, "Information element/parameter non-existent or not implemented", "" + }, + { + 0x64, "Invalid information element contents", "" + }, + { + 0x65, "Message not compatible with call state", "" + }, + { + 0x66, "Recovery on timer expiry", "" + }, + { + 0x67, "Parameter non-existent or not implemented - passed on", "" + }, + { + 0x6e, "Message with unrecognized parameter discarded", "" + }, + { + 0x6f, "Protocol error, unspecified", "" + }, + { + 0x7f, "Interworking, unspecified", "" + }, +}; + +#define CVSIZE sizeof(cvlist)/sizeof(struct CauseValue) + +static +int +prcause(char *dest, byte * p) +{ + byte *end; + char *dp = dest; + int i, cause; + + end = p + p[1] + 1; + p += 2; + dp += sprintf(dp, " coding "); + dp += prbits(dp, *p, 7, 2); + dp += sprintf(dp, " location "); + dp += prbits(dp, *p, 4, 4); + *dp++ = '\n'; + p = skipext(p); + + cause = 0x7f & *p++; + + /* locate cause value */ + for (i = 0; i < CVSIZE; i++) + if (cvlist[i].nr == cause) + break; + + /* display cause value if it exists */ + if (i == CVSIZE) + dp += sprintf(dp, "Unknown cause type %x!\n", cause); + else + dp += sprintf(dp, " cause value %x : %s \n", cause, cvlist[i].edescr); + + while (!0) { + if (p > end) + break; + dp += sprintf(dp, " diag attribute %d ", *p++ & 0x7f); + dp += sprintf(dp, " rej %d ", *p & 0x7f); + if (*p & 0x80) { + *dp++ = '\n'; + break; + } else + dp += sprintf(dp, " av %d\n", (*++p) & 0x7f); + } + return (dp - dest); + +} + +static +struct MessageType cause_1tr6[] = +{ + {CAUSE_InvCRef, "Invalid Call Reference"}, + {CAUSE_BearerNotImpl, "Bearer Service Not Implemented"}, + {CAUSE_CIDunknown, "Caller Identity unknown"}, + {CAUSE_CIDinUse, "Caller Identity in Use"}, + {CAUSE_NoChans, "No Channels available"}, + {CAUSE_FacNotImpl, "Facility Not Implemented"}, + {CAUSE_FacNotSubscr, "Facility Not Subscribed"}, + {CAUSE_OutgoingBarred, "Outgoing calls barred"}, + {CAUSE_UserAccessBusy, "User Access Busy"}, + {CAUSE_NegativeGBG, "Negative GBG"}, + {CAUSE_UnknownGBG, "Unknown GBG"}, + {CAUSE_NoSPVknown, "No SPV known"}, + {CAUSE_DestNotObtain, "Destination not obtainable"}, + {CAUSE_NumberChanged, "Number changed"}, + {CAUSE_OutOfOrder, "Out Of Order"}, + {CAUSE_NoUserResponse, "No User Response"}, + {CAUSE_UserBusy, "User Busy"}, + {CAUSE_IncomingBarred, "Incoming Barred"}, + {CAUSE_CallRejected, "Call Rejected"}, + {CAUSE_NetworkCongestion, "Network Congestion"}, + {CAUSE_RemoteUser, "Remote User initiated"}, + {CAUSE_LocalProcErr, "Local Procedure Error"}, + {CAUSE_RemoteProcErr, "Remote Procedure Error"}, + {CAUSE_RemoteUserSuspend, "Remote User Suspend"}, + {CAUSE_RemoteUserResumed, "Remote User Resumed"}, + {CAUSE_UserInfoDiscarded, "User Info Discarded"} +}; + +int cause_1tr6_len = (sizeof(cause_1tr6) / sizeof(struct MessageType)); + +static int +prcause_1tr6(char *dest, byte * p) +{ + char *dp = dest; + int i, cause; + + p++; + if (0 == *p) { + dp += sprintf(dp, " OK (cause length=0)\n"); + return (dp - dest); + } else if (*p > 1) { + dp += sprintf(dp, " coding "); + dp += prbits(dp, p[2], 7, 2); + dp += sprintf(dp, " location "); + dp += prbits(dp, p[2], 4, 4); + *dp++ = '\n'; + } + p++; + cause = 0x7f & *p; + + /* locate cause value */ + for (i = 0; i < cause_1tr6_len; i++) + if (cause_1tr6[i].nr == cause) + break; + + /* display cause value if it exists */ + if (i == cause_1tr6_len) + dp += sprintf(dp, "Unknown cause type %x!\n", cause); + else + dp += sprintf(dp, " cause value %x : %s \n", cause, cause_1tr6[i].descr); + + return (dp - dest); + +} + +static int +prchident(char *dest, byte * p) { + char *dp = dest; + + p += 2; + dp += sprintf(dp, " octet 3 "); + dp += prbits(dp, *p, 8, 8); + *dp++ = '\n'; + return (dp - dest); +} + +static int +prcalled(char *dest, byte * p) { + int l; + char *dp = dest; + + p++; + l = *p++ - 1; + dp += sprintf(dp, " octet 3 "); + dp += prbits(dp, *p++, 8, 8); + *dp++ = '\n'; + dp += sprintf(dp, " number digits "); + while (l--) + *dp++ = *p++; + *dp++ = '\n'; + return (dp - dest); +} +static int +prcalling(char *dest, byte * p) { + int l; + char *dp = dest; + + p++; + l = *p++ - 1; + dp += sprintf(dp, " octet 3 "); + dp += prbits(dp, *p, 8, 8); + *dp++ = '\n'; + if (!(*p & 0x80)) { + dp += sprintf(dp, " octet 3a "); + dp += prbits(dp, *++p, 8, 8); + *dp++ = '\n'; + l--; + }; + p++; + + dp += sprintf(dp, " number digits "); + while (l--) + *dp++ = *p++; + *dp++ = '\n'; + return (dp - dest); +} + +static +int +prbearer(char *dest, byte * p) +{ + char *dp = dest, ch; + + p += 2; + dp += sprintf(dp, " octet 3 "); + dp += prbits(dp, *p++, 8, 8); + *dp++ = '\n'; + dp += sprintf(dp, " octet 4 "); + dp += prbits(dp, *p, 8, 8); + *dp++ = '\n'; + if ((*p++ & 0x1f) == 0x18) { + dp += sprintf(dp, " octet 4.1 "); + dp += prbits(dp, *p++, 8, 8); + *dp++ = '\n'; + } + /* check for user information layer 1 */ + if ((*p & 0x60) == 0x20) { + ch = ' '; + do { + dp += sprintf(dp, " octet 5%c ", ch); + dp += prbits(dp, *p, 8, 8); + *dp++ = '\n'; + if (ch == ' ') + ch = 'a'; + else + ch++; + } + while (!(*p++ & 0x80)); + } + /* check for user information layer 2 */ + if ((*p & 0x60) == 0x40) { + dp += sprintf(dp, " octet 6 "); + dp += prbits(dp, *p++, 8, 8); + *dp++ = '\n'; + } + /* check for user information layer 3 */ + if ((*p & 0x60) == 0x60) { + dp += sprintf(dp, " octet 7 "); + dp += prbits(dp, *p++, 8, 8); + *dp++ = '\n'; + } + return (dp - dest); +} + +static int +general(char *dest, byte * p) { + char *dp = dest; + char ch = ' '; + int l, octet = 3; + + p++; + l = *p++; + /* Iterate over all octets in the information element */ + while (l--) { + dp += sprintf(dp, " octet %d%c ", octet, ch); + dp += prbits(dp, *p++, 8, 8); + *dp++ = '\n'; + + /* last octet in group? */ + if (*p & 0x80) { + octet++; + ch = ' '; + } else if (ch == ' ') + ch = 'a'; + else + ch++; + } + return (dp - dest); +} + +static int +prcharge(char *dest, byte * p) { + char *dp = dest; + int l; + + p++; + l = *p++ - 1; + dp += sprintf(dp, " GEA "); + dp += prbits(dp, *p++, 8, 8); + dp += sprintf(dp, " Anzahl: "); + /* Iterate over all octets in the * information element */ + while (l--) + *dp++ = *p++; + *dp++ = '\n'; + return (dp - dest); +} +static int +prtext(char *dest, byte * p) { + char *dp = dest; + int l; + + p++; + l = *p++; + dp += sprintf(dp, " "); + /* Iterate over all octets in the * information element */ + while (l--) + *dp++ = *p++; + *dp++ = '\n'; + return (dp - dest); +} +static int +display(char *dest, byte * p) { + char *dp = dest; + char ch = ' '; + int l, octet = 3; + + p++; + l = *p++; + /* Iterate over all octets in the * display-information element */ + dp += sprintf(dp, " \""); + while (l--) { + dp += sprintf(dp, "%c", *p++); + + /* last octet in group? */ + if (*p & 0x80) { + octet++; + ch = ' '; + } else if (ch == ' ') + ch = 'a'; + + else + ch++; + } + *dp++ = '\"'; + *dp++ = '\n'; + return (dp - dest); +} + +int +prfacility(char *dest, byte * p) +{ + char *dp = dest; + int l, l2; + + p++; + l = *p++; + dp += sprintf(dp, " octet 3 "); + dp += prbits(dp, *p++, 8, 8); + dp += sprintf(dp, "\n"); + l -= 1; + + while (l > 0) { + dp += sprintf(dp, " octet 4 "); + dp += prbits(dp, *p++, 8, 8); + dp += sprintf(dp, "\n"); + dp += sprintf(dp, " octet 5 %d\n", l2 = *p++ & 0x7f); + l -= 2; + dp += sprintf(dp, " contents "); + while (l2--) { + dp += sprintf(dp, "%2x ", *p++); + l--; + } + dp += sprintf(dp, "\n"); + } + + return (dp - dest); +} + +static +struct InformationElement { + byte nr; + char *descr; + int (*f) (char *, byte *); +} ielist[] = { + + { + 0x00, "Segmented message", general + }, + { + 0x04, "Bearer capability", prbearer + }, + { + 0x08, "Cause", prcause + }, + { + 0x10, "Call identity", general + }, + { + 0x14, "Call state", general + }, + { + 0x18, "Channel identification", prchident + }, + { + 0x1c, "Facility", prfacility + }, + { + 0x1e, "Progress indicator", general + }, + { + 0x20, "Network-specific facilities", general + }, + { + 0x27, "Notification indicator", general + }, + { + 0x28, "Display", display + }, + { + 0x29, "Date/Time", general + }, + { + 0x2c, "Keypad facility", general + }, + { + 0x34, "Signal", general + }, + { + 0x40, "Information rate", general + }, + { + 0x42, "End-to-end delay", general + }, + { + 0x43, "Transit delay selection and indication", general + }, + { + 0x44, "Packet layer binary parameters", general + }, + { + 0x45, "Packet layer window size", general + }, + { + 0x46, "Packet size", general + }, + { + 0x47, "Closed user group", general + }, + { + 0x4a, "Reverse charge indication", general + }, + { + 0x6c, "Calling party number", prcalling + }, + { + 0x6d, "Calling party subaddress", general + }, + { + 0x70, "Called party number", prcalled + }, + { + 0x71, "Called party subaddress", general + }, + { + 0x74, "Redirecting number", general + }, + { + 0x78, "Transit network selection", general + }, + { + 0x79, "Restart indicator", general + }, + { + 0x7c, "Low layer compatibility", general + }, + { + 0x7d, "High layer compatibility", general + }, + { + 0x7e, "User-user", general + }, + { + 0x7f, "Escape for extension", general + }, +}; + + +#define IESIZE sizeof(ielist)/sizeof(struct InformationElement) + +static struct InformationElement we_0[] = +{ + {WE0_cause, "Cause", prcause_1tr6}, + {WE0_connAddr, "Connecting Address", prcalled}, + {WE0_callID, "Call IDentity", general}, + {WE0_chanID, "Channel IDentity", general}, + {WE0_netSpecFac, "Network Specific Facility", general}, + {WE0_display, "Display", general}, + {WE0_keypad, "Keypad", general}, + {WE0_origAddr, "Origination Address", prcalled}, + {WE0_destAddr, "Destination Address", prcalled}, + {WE0_userInfo, "User Info", general} +}; + +static int we_0_len = (sizeof(we_0) / sizeof(struct InformationElement)); + +static struct InformationElement we_6[] = +{ + {WE6_serviceInd, "Service Indicator", general}, + {WE6_chargingInfo, "Charging Information", prcharge}, + {WE6_date, "Date", prtext}, + {WE6_facSelect, "Facility Select", general}, + {WE6_facStatus, "Facility Status", general}, + {WE6_statusCalled, "Status Called", general}, + {WE6_addTransAttr, "Additional Transmission Attributes", general} +}; +static int we_6_len = (sizeof(we_6) / sizeof(struct InformationElement)); + +void +dlogframe(struct IsdnCardState *sp, byte * buf, int size, char *comment) { + byte *bend = buf + size; + char *dp; + int i, cs = 0, cs_old = 0, cs_fest = 0; + + /* display header */ + dp = sp->dlogspace; + dp += sprintf(dp, "%s\n", comment); + + { + byte *p = buf; + dp += sprintf(dp, "hex: "); + while (p < bend) + dp += sprintf(dp, "%02x ", *p++); + dp += sprintf(dp, "\n"); + teles_putstatus(sp->dlogspace); + dp = sp->dlogspace; + } + if ((0xfe & buf[0]) == PROTO_DIS_N0) { /* 1TR6 */ + /* locate message type */ + if (buf[0] == PROTO_DIS_N0) { /* N0 */ + for (i = 0; i < mt_n0_len; i++) + if (mt_n0[i].nr == buf[3]) + break; + /* display message type iff it exists */ + if (i == mt_n0_len) + dp += sprintf(dp, "Unknown message type N0 %x!\n", buf[3]); + else + dp += sprintf(dp, "call reference %d size %d message type %s\n", + buf[2], size, mt_n0[i].descr); + } else { /* N1 */ + for (i = 0; i < mt_n1_len; i++) + if (mt_n1[i].nr == buf[3]) + break; + /* display message type iff it exists */ + if (i == mt_n1_len) + dp += sprintf(dp, "Unknown message type N1 %x!\n", buf[3]); + else + dp += sprintf(dp, "call reference %d size %d message type %s\n", + buf[2], size, mt_n1[i].descr); + } + + /* display each information element */ + buf += 4; + while (buf < bend) { + /* Is it a single octet information element? */ + if (*buf & 0x80) { + switch ((*buf >> 4) & 7) { + case 1: + dp += sprintf(dp, " Shift %x\n", *buf & 0xf); + cs_old = cs; + cs = *buf & 7; + cs_fest = *buf & 8; + break; + case 3: + dp += sprintf(dp, " Congestion level %x\n", *buf & 0xf); + break; + case 2: + if (*buf == 0xa0) { + dp += sprintf(dp, " More data\n"); + break; + } + if (*buf == 0xa1) { + dp += sprintf(dp, " Sending complete\n"); + } + break; + /* fall through */ + default: + dp += sprintf(dp, " Reserved %x\n", *buf); + break; + } + buf++; + continue; + } + /* No, locate it in the table */ + if (cs == 0) { + for (i = 0; i < we_0_len; i++) + if (*buf == we_0[i].nr) + break; + + /* When found, give appropriate msg */ + if (i != we_0_len) { + dp += sprintf(dp, " %s\n", we_0[i].descr); + dp += we_0[i].f(dp, buf); + } else + dp += sprintf(dp, " Codeset %d attribute %x attribute size %d\n", cs, *buf, buf[1]); + } else if (cs == 6) { + for (i = 0; i < we_6_len; i++) + if (*buf == we_6[i].nr) + break; + + /* When found, give appropriate msg */ + if (i != we_6_len) { + dp += sprintf(dp, " %s\n", we_6[i].descr); + dp += we_6[i].f(dp, buf); + } else + dp += sprintf(dp, " Codeset %d attribute %x attribute size %d\n", cs, *buf, buf[1]); + } else + dp += sprintf(dp, " Unknown Codeset %d attribute %x attribute size %d\n", cs, *buf, buf[1]); + /* Skip to next element */ + if (cs_fest == 8) { + cs = cs_old; + cs_old = 0; + cs_fest = 0; + } + buf += buf[1] + 2; + } + } else { /* EURO */ + /* locate message type */ + for (i = 0; i < MTSIZE; i++) + if (mtlist[i].nr == buf[3]) + break; + + /* display message type iff it exists */ + if (i == MTSIZE) + dp += sprintf(dp, "Unknown message type %x!\n", buf[3]); + else + dp += sprintf(dp, "call reference %d size %d message type %s\n", + buf[2], size, mtlist[i].descr); + + /* display each information element */ + buf += 4; + while (buf < bend) { + /* Is it a single octet information element? */ + if (*buf & 0x80) { + switch ((*buf >> 4) & 7) { + case 1: + dp += sprintf(dp, " Shift %x\n", *buf & 0xf); + break; + case 3: + dp += sprintf(dp, " Congestion level %x\n", *buf & 0xf); + break; + case 5: + dp += sprintf(dp, " Repeat indicator %x\n", *buf & 0xf); + break; + case 2: + if (*buf == 0xa0) { + dp += sprintf(dp, " More data\n"); + break; + } + if (*buf == 0xa1) { + dp += sprintf(dp, " Sending complete\n"); + } + break; + /* fall through */ + default: + dp += sprintf(dp, " Reserved %x\n", *buf); + break; + } + buf++; + continue; + } + /* No, locate it in the table */ + for (i = 0; i < IESIZE; i++) + if (*buf == ielist[i].nr) + break; + + /* When not found, give appropriate msg */ + if (i != IESIZE) { + dp += sprintf(dp, " %s\n", ielist[i].descr); + dp += ielist[i].f(dp, buf); + } else + dp += sprintf(dp, " attribute %x attribute size %d\n", *buf, buf[1]); + + /* Skip to next element */ + buf += buf[1] + 2; + } + } + dp += sprintf(dp, "\n"); + teles_putstatus(sp->dlogspace); +} diff --git a/drivers/isdn/teles/tei.c b/drivers/isdn/teles/tei.c new file mode 100644 index 000000000..3ab9f3646 --- /dev/null +++ b/drivers/isdn/teles/tei.c @@ -0,0 +1,248 @@ +/* $Id: tei.c,v 1.1 1996/04/13 10:28:25 fritz Exp $ + * + * $Log: tei.c,v $ + * Revision 1.1 1996/04/13 10:28:25 fritz + * Initial revision + * + * + */ +#define __NO_VERSION__ +#include "teles.h" + +extern struct IsdnCard cards[]; +extern int nrcards; + +static struct PStack * +findces(struct PStack *st, int ces) +{ + struct PStack *ptr = *(st->l1.stlistp); + + while (ptr) + if (ptr->l2.ces == ces) + return (ptr); + else + ptr = ptr->next; + return (NULL); +} + +static struct PStack * +findtei(struct PStack *st, int tei) +{ + struct PStack *ptr = *(st->l1.stlistp); + + if (tei == 127) + return (NULL); + + while (ptr) + if (ptr->l2.tei == tei) + return (ptr); + else + ptr = ptr->next; + return (NULL); +} + +void +tei_handler(struct PStack *st, + byte pr, struct BufHeader *ibh) +{ + byte *bp; + unsigned int tces; + struct PStack *otsp, *ptr; + unsigned int data; + + if (st->l2.debug) + printk(KERN_DEBUG "teihandler %d\n", pr); + + switch (pr) { + case (MDL_ASSIGN): + data = (unsigned int) ibh; + BufPoolGet(&ibh, st->l1.smallpool, GFP_ATOMIC, (void *) st, 6); + if (!ibh) + return; + bp = DATAPTR(ibh); + bp += st->l2.uihsize; + bp[0] = 0xf; + bp[1] = data >> 8; + bp[2] = data & 0xff; + bp[3] = 0x1; + bp[4] = 0xff; + ibh->datasize = 8; + st->l3.l3l2(st, DL_UNIT_DATA, ibh); + break; + case (DL_UNIT_DATA): + bp = DATAPTR(ibh); + bp += 3; + if (bp[0] != 0xf) + break; + switch (bp[3]) { + case (2): + tces = (bp[1] << 8) | bp[2]; + BufPoolRelease(ibh); + if (st->l3.debug) + printk(KERN_DEBUG "tei identity assigned for %d=%d\n", tces, + bp[4] >> 1); + if ((otsp = findces(st, tces))) + otsp->ma.teil2(otsp, MDL_ASSIGN, + (void *)(bp[4] >> 1)); + break; + case (4): + if (st->l3.debug) + printk(KERN_DEBUG "checking identity for %d\n", bp[4] >> 1); + if (bp[4] >> 1 == 0x7f) { + BufPoolRelease(ibh); + ptr = *(st->l1.stlistp); + while (ptr) { + if ((ptr->l2.tei & 0x7f) != 0x7f) { + if (BufPoolGet(&ibh, st->l1.smallpool, GFP_ATOMIC, (void *) st, 7)) + break; + bp = DATAPTR(ibh); + bp += 3; + bp[0] = 0xf; + bp[1] = ptr->l2.ces >> 8; + bp[2] = ptr->l2.ces & 0xff; + bp[3] = 0x5; + bp[4] = (ptr->l2.tei << 1) | 1; + ibh->datasize = 8; + st->l3.l3l2(st, DL_UNIT_DATA, ibh); + } + ptr = ptr->next; + } + } else { + otsp = findtei(st, bp[4] >> 1); + BufPoolRelease(ibh); + if (!otsp) + break; + if (st->l3.debug) + printk(KERN_DEBUG "ces is %d\n", otsp->l2.ces); + if (BufPoolGet(&ibh, st->l1.smallpool, GFP_ATOMIC, (void *) st, 7)) + break; + bp = DATAPTR(ibh); + bp += 3; + bp[0] = 0xf; + bp[1] = otsp->l2.ces >> 8; + bp[2] = otsp->l2.ces & 0xff; + bp[3] = 0x5; + bp[4] = (otsp->l2.tei << 1) | 1; + ibh->datasize = 8; + st->l3.l3l2(st, DL_UNIT_DATA, ibh); + } + break; + default: + BufPoolRelease(ibh); + if (st->l3.debug) + printk(KERN_DEBUG "tei message unknown %d ai %d\n", bp[3], bp[4] >> 1); + } + break; + default: + printk(KERN_WARNING "tei handler unknown primitive %d\n", pr); + break; + } +} + +unsigned int +randomces(void) +{ + int x = jiffies & 0xffff; + + return (x); +} + +static void +tei_man(struct PStack *sp, int i, void *v) +{ + printk(KERN_DEBUG "tei_man\n"); +} + +static void +tei_l2tei(struct PStack *st, int pr, void *arg) +{ + struct IsdnCardState *sp = st->l1.hardware; + + tei_handler(sp->teistack, pr, arg); +} + +void +setstack_tei(struct PStack *st) +{ + st->l2.l2tei = tei_l2tei; +} + +static void +init_tei(struct IsdnCardState *sp, int protocol) +{ + struct PStack *st; + char tmp[128]; + +#define DIRTY_HACK_AGAINST_SIGSEGV + + st = (struct PStack *) Smalloc(sizeof(struct PStack), GFP_KERNEL, + "struct PStack"); + +#ifdef DIRTY_HACK_AGAINST_SIGSEGV + sp->teistack = st; /* struct is not initialized yet */ + sp->teistack->protocol = protocol; /* struct is not initialized yet */ +#endif /* DIRTY_HACK_AGAINST_SIGSEGV */ + + + setstack_teles(st, sp); + + st->l2.extended = !0; + st->l2.laptype = LAPD; + st->l2.window = 1; + st->l2.orig = !0; + st->protocol = protocol; + +/* + * the following is not necessary for tei mng. (broadcast only) + */ + + st->l2.t200 = 500; /* 500 milliseconds */ + st->l2.n200 = 4; /* try 4 times */ + + st->l2.sap = 63; + st->l2.tei = 127; + + sprintf(tmp, "Card %d tei ", sp->cardnr); + setstack_isdnl2(st, tmp); + st->l2.debug = 0; + st->l3.debug = 0; + + st->ma.manl2(st, MDL_NOTEIPROC, NULL); + + st->l2.l2l3 = (void *) tei_handler; + st->l1.l1man = tei_man; + st->l2.l2man = tei_man; + st->l4.l2writewakeup = NULL; + + teles_addlist(sp, st); + sp->teistack = st; +} + +static void +release_tei(struct IsdnCardState *sp) +{ + struct PStack *st = sp->teistack; + + teles_rmlist(sp, st); + Sfree((void *) st); +} + +void +TeiNew(void) +{ + int i; + + for (i = 0; i < nrcards; i++) + if (cards[i].sp) + init_tei(cards[i].sp, cards[i].protocol); +} + +void +TeiFree(void) +{ + int i; + + for (i = 0; i < nrcards; i++) + if (cards[i].sp) + release_tei(cards[i].sp); +} diff --git a/drivers/isdn/teles/teles.h b/drivers/isdn/teles/teles.h new file mode 100644 index 000000000..502169a6b --- /dev/null +++ b/drivers/isdn/teles/teles.h @@ -0,0 +1,488 @@ +/* $Id: teles.h,v 1.2 1996/04/30 21:52:04 isdn4dev Exp $ + * + * $Log: teles.h,v $ + * Revision 1.2 1996/04/30 21:52:04 isdn4dev + * SPV for 1TR6 - Karsten + * + * Revision 1.1 1996/04/13 10:29:00 fritz + * Initial revision + * + * + */ +#include <linux/module.h> +#include <linux/version.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/major.h> +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/signal.h> +#include <linux/malloc.h> +#include <linux/mm.h> +#include <linux/mman.h> +#include <linux/ioport.h> +#include <linux/timer.h> +#include <linux/wait.h> +#include <linux/isdnif.h> +#include <linux/tty.h> + +#include <asm/uaccess.h> +#include <asm/io.h> + +#define PH_ACTIVATE 1 +#define PH_DATA 2 +#define PH_DEACTIVATE 3 + +#define MDL_ASSIGN 4 +#define DL_UNIT_DATA 5 +#define SC_STARTUP 6 +#define CC_ESTABLISH 7 +#define DL_ESTABLISH 8 +#define DL_DATA 9 +#define CC_S_STATUS_ENQ 10 + +#define CC_CONNECT 15 +#define CC_CONNECT_ACKNOWLEDGE 16 +#define CO_EOF 17 +#define SC_DISCONNECT 18 +#define CO_DTMF 19 +#define DL_RELEASE 20 + +#define CO_ALARM 22 +#define CC_REJECT 23 + +#define CC_SETUP_REQ 24 +#define CC_SETUP_CNF 25 +#define CC_SETUP_IND 26 +#define CC_SETUP_RSP 27 +#define CC_SETUP_COMPLETE_IND 28 + +#define CC_DISCONNECT_REQ 29 +#define CC_DISCONNECT_IND 30 + +#define CC_RELEASE_CNF 31 +#define CC_RELEASE_IND 32 +#define CC_RELEASE_REQ 33 + +#define CC_REJECT_REQ 34 + +#define CC_PROCEEDING_IND 35 + +#define CC_DLRL 36 +#define CC_DLEST 37 + +#define CC_ALERTING_REQ 38 +#define CC_ALERTING_IND 39 + +#define DL_STOP 40 +#define DL_START 41 + +#define MDL_NOTEIPROC 46 + +#define LC_ESTABLISH 47 +#define LC_RELEASE 48 + +#define PH_REQUEST_PULL 49 +#define PH_PULL_ACK 50 +#define PH_DATA_PULLED 51 +#define CC_INFO_CHARGE 52 + +/* + * Message-Types + */ + +#define MT_ALERTING 0x01 +#define MT_CALL_PROCEEDING 0x02 +#define MT_CONNECT 0x07 +#define MT_CONNECT_ACKNOWLEDGE 0x0f +#define MT_PROGRESS 0x03 +#define MT_SETUP 0x05 +#define MT_SETUP_ACKNOWLEDGE 0x0d +#define MT_RESUME 0x26 +#define MT_RESUME_ACKNOWLEDGE 0x2e +#define MT_RESUME_REJECT 0x22 +#define MT_SUSPEND 0x25 +#define MT_SUSPEND_ACKNOWLEDGE 0x2d +#define MT_SUSPEND_REJECT 0x21 +#define MT_USER_INFORMATION 0x20 +#define MT_DISCONNECT 0x45 +#define MT_RELEASE 0x4d +#define MT_RELEASE_COMPLETE 0x5a +#define MT_RESTART 0x46 +#define MT_RESTART_ACKNOWLEDGE 0x4e +#define MT_SEGMENT 0x60 +#define MT_CONGESTION_CONTROL 0x79 +#define MT_INFORMATION 0x7b +#define MT_FACILITY 0x62 +#define MT_NOTIFY 0x6e +#define MT_STATUS 0x7d +#define MT_STATUS_ENQUIRY 0x75 + +#define IE_CAUSE 0x08 + +struct HscxIoctlArg { + int channel; + int mode; + int transbufsize; +}; + +#ifdef __KERNEL__ + +#undef DEBUG_MAGIC + +#define HSCX_SBUF_ORDER 1 +#define HSCX_SBUF_BPPS 2 +#define HSCX_SBUF_MAXPAGES 3 + +#define HSCX_RBUF_ORDER 1 +#define HSCX_RBUF_BPPS 2 +#define HSCX_RBUF_MAXPAGES 3 + +#define HSCX_SMALLBUF_ORDER 0 +#define HSCX_SMALLBUF_BPPS 40 +#define HSCX_SMALLBUF_MAXPAGES 1 + +#define ISAC_SBUF_ORDER 0 +#define ISAC_SBUF_BPPS 16 +#define ISAC_SBUF_MAXPAGES 1 + +#define ISAC_RBUF_ORDER 0 +#define ISAC_RBUF_BPPS 16 +#define ISAC_RBUF_MAXPAGES 1 + +#define ISAC_SMALLBUF_ORDER 0 +#define ISAC_SMALLBUF_BPPS 40 +#define ISAC_SMALLBUF_MAXPAGES 1 + +#define byte unsigned char + +#define MAX_WINDOW 8 + +byte *Smalloc(int size, int pr, char *why); +void Sfree(byte * ptr); + +/* + * Statemachine + */ +struct Fsm { + int *jumpmatrix; + int state_count, event_count; + char **strEvent, **strState; +}; + +struct FsmInst { + struct Fsm *fsm; + int state; + int debug; + void *userdata; + int userint; + void (*printdebug) (struct FsmInst *, char *); +}; + +struct FsmNode { + int state, event; + void (*routine) (struct FsmInst *, int, void *); +}; + +struct FsmTimer { + struct FsmInst *fi; + struct timer_list tl; + int event; + void *arg; +}; + +struct BufHeader { +#ifdef DEBUG_MAGIC + int magic; +#endif + struct BufHeader *next; + struct BufPool *bp; + int datasize; + byte primitive, where; + void *heldby; +}; + +struct Pages { + struct Pages *next; +}; + +struct BufPool { +#ifdef DEBUG_MAGIC + int magic; +#endif + struct BufHeader *freelist; + struct Pages *pageslist; + int pageorder; + int pagescount; + int bpps; + int bufsize; + int maxpages; +}; + +struct BufQueue { +#ifdef DEBUG_MAGIC + int magic; +#endif + struct BufHeader *head, *tail; +}; + +struct Layer1 { + void *hardware; + int hscx; + struct BufPool *sbufpool, *rbufpool, *smallpool; + struct PStack **stlistp; + int act_state; + void (*l1l2) (struct PStack *, int, struct BufHeader *); + void (*l1man) (struct PStack *, int, void *); + int hscxmode, hscxchannel, requestpull; +}; + +struct Layer2 { + int sap, tei, ces; + int extended, laptype; + int uihsize, ihsize; + int vs, va, vr; + struct BufQueue i_queue; + int window, orig; + int rejexp; + int debug; + struct BufHeader *windowar[MAX_WINDOW]; + int sow; + struct FsmInst l2m; + void (*l2l1) (struct PStack *, int, struct BufHeader *); + void (*l2l1discardq) (struct PStack *, int, void *, int); + void (*l2man) (struct PStack *, int, void *); + void (*l2l3) (struct PStack *, int, void *); + void (*l2tei) (struct PStack *, int, void *); + struct FsmTimer t200_timer, t203_timer; + int t200, n200, t203; + int rc, t200_running; + char debug_id[32]; +}; + +struct Layer3 { + void (*l3l4) (struct PStack *, int, struct BufHeader *); + void (*l3l2) (struct PStack *, int, void *); + int state, callref; + int debug; +}; + +struct Layer4 { + void (*l4l3) (struct PStack *, int, void *); + void *userdata; + void (*l1writewakeup) (struct PStack *); + void (*l2writewakeup) (struct PStack *); +}; + +struct Management { + void (*manl1) (struct PStack *, int, void *); + void (*manl2) (struct PStack *, int, void *); + void (*teil2) (struct PStack *, int, void *); +}; + +struct Param { + int cause; + int bchannel; + int callref; /* TEI-Number */ + int itc; + int info; /* Service-Indicator */ + int info2; /* Service-Indicator, second octet */ + char calling[40]; /* Called Id */ + char called[40]; /* Caller Id */ + int chargeinfo; /* Charge Info - only for 1tr6 in + * the moment + */ + int spv; /* SPV Flag */ +}; + +struct PStack { + struct PStack *next; + struct Layer1 l1; + struct Layer2 l2; + struct Layer3 l3; + struct Layer4 l4; + struct Management ma; + struct Param *pa; + int protocol; /* EDSS1 or 1TR6 */ +}; + +struct HscxState { + unsigned int membase; + int iobase; + int inuse, init, active; + struct BufPool sbufpool, rbufpool, smallpool; + struct IsdnCardState *sp; + int hscx, mode; + int transbufsize, receive; + struct BufHeader *rcvibh, *xmtibh; + int rcvptr, sendptr; + struct PStack *st; + struct tq_struct tqueue; + int event; + struct BufQueue rq, sq; + int releasebuf; +#ifdef DEBUG_MAGIC + int magic; /* 301270 */ +#endif +}; + +struct IsdnCardState { +#ifdef DEBUG_MAGIC + int magic; +#endif + unsigned int membase; + int iobase; + struct BufPool sbufpool, rbufpool, smallpool; + struct PStack *stlist; + struct BufHeader *xmtibh, *rcvibh; + int rcvptr, sendptr; + int event; + struct tq_struct tqueue; + int ph_active; + struct BufQueue rq, sq; + + int cardnr, ph_state; + struct PStack *teistack; + struct HscxState hs[2]; + + int dlogflag; + char *dlogspace; + int debug; + int releasebuf; +}; + +struct IsdnCard { + unsigned int membase; + int interrupt; + unsigned int iobase; + int protocol; /* EDSS1 or 1TR6 */ + struct IsdnCardState *sp; +}; + +#define DATAPTR(x) ((byte *)x+sizeof(struct BufHeader)) + +#define LAPD 0 +#define LAPB 1 + +void BufPoolInit(struct BufPool *bp, int order, int bpps, + int maxpages); +int BufPoolAdd(struct BufPool *bp, int priority); +void BufPoolFree(struct BufPool *bp); +int BufPoolGet(struct BufHeader **bh, + struct BufPool *bp, int priority, void *heldby, int where); +void BufPoolRelease(struct BufHeader *bh); +void BufQueueLink(struct BufQueue *bq, + struct BufHeader *bh); +int BufQueueUnlink(struct BufHeader **bh, struct BufQueue *bq); +void BufQueueInit(struct BufQueue *bq); +void BufQueueRelease(struct BufQueue *bq); +void BufQueueDiscard(struct BufQueue *q, int pr, void *heldby, + int releasetoo); +int BufQueueLength(struct BufQueue *bq); +void BufQueueLinkFront(struct BufQueue *bq, + struct BufHeader *bh); + +void l2down(struct PStack *st, + byte pr, struct BufHeader *ibh); +void l2up(struct PStack *st, + byte pr, struct BufHeader *ibh); +void acceptph(struct PStack *st, + struct BufHeader *ibh); +void setstack_isdnl2(struct PStack *st, char *debug_id); +int teles_inithardware(void); +void teles_closehardware(void); + +void setstack_teles(struct PStack *st, struct IsdnCardState *sp); +unsigned int randomces(void); +void setstack_isdnl3(struct PStack *st); +void teles_addlist(struct IsdnCardState *sp, + struct PStack *st); +void releasestack_isdnl2(struct PStack *st); +void teles_rmlist(struct IsdnCardState *sp, + struct PStack *st); +void newcallref(struct PStack *st); + +int ll_init(void); +void ll_stop(void), ll_unload(void); +int setstack_hscx(struct PStack *st, struct HscxState *hs); +void modehscx(struct HscxState *hs, int mode, int ichan); +byte *findie(byte * p, int size, byte ie, int wanted_set); +int getcallref(byte * p); + +void FsmNew(struct Fsm *fsm, + struct FsmNode *fnlist, int fncount); +void FsmFree(struct Fsm *fsm); +int FsmEvent(struct FsmInst *fi, + int event, void *arg); +void FsmChangeState(struct FsmInst *fi, + int newstate); +void FsmInitTimer(struct FsmInst *fi, struct FsmTimer *ft); +int FsmAddTimer(struct FsmTimer *ft, + int millisec, int event, void *arg, int where); +void FsmDelTimer(struct FsmTimer *ft, int where); +int FsmTimerRunning(struct FsmTimer *ft); +void jiftime(char *s, long mark); + +void CallcNew(void); +void CallcFree(void); +int CallcNewChan(void); +void CallcFreeChan(void); +int teles_command(isdn_ctrl * ic); +int teles_writebuf(int id, int chan, const u_char * buf, int count, int user); +void teles_putstatus(char *buf); +void teles_reportcard(int cardnr); +int ListLength(struct BufHeader *ibh); +void dlogframe(struct IsdnCardState *sp, byte * p, int size, char *comment); +void iecpy(byte * dest, byte * iestart, int ieoffset); +void setstack_transl2(struct PStack *st); +void releasestack_transl2(struct PStack *st); +void close_hscxstate(struct HscxState *); +void setstack_tei(struct PStack *st); + +struct LcFsm { + struct FsmInst lcfi; + int type; + struct Channel *ch; + void (*lccall) (struct LcFsm *, int, void *); + struct PStack *st; + int l2_establish; + int l2_start; + struct FsmTimer act_timer; + char debug_id[32]; +}; + +struct Channel { + struct PStack ds, is; + struct IsdnCardState *sp; + int hscx; + int chan; + int incoming; + struct FsmInst fi; + struct LcFsm lc_d, lc_b; + struct Param para; + int debug; +#ifdef DEBUG_MAGIC + int magic; /* 301272 */ +#endif + int l2_protocol, l2_active_protocol; + int l2_primitive, l2_headersize; + int data_open; + int outcallref; + int impair; +}; + +#define PART_SIZE(order,bpps) (( (PAGE_SIZE<<order) -\ + sizeof(void *))/bpps) +#define BUFFER_SIZE(order,bpps) (PART_SIZE(order,bpps)-\ + sizeof(struct BufHeader)) + +#endif + +void Isdnl2New(void); +void Isdnl2Free(void); +void TeiNew(void); +void TeiFree(void); + + + + |