diff options
author | Linus Torvalds | 2005-04-17 00:20:36 +0200 |
---|---|---|
committer | Linus Torvalds | 2005-04-17 00:20:36 +0200 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/isdn | |
download | kernel-qcow2-linux-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.tar.gz kernel-qcow2-linux-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.tar.xz kernel-qcow2-linux-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.zip |
Linux-2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/isdn')
303 files changed, 150218 insertions, 0 deletions
diff --git a/drivers/isdn/Kconfig b/drivers/isdn/Kconfig new file mode 100644 index 000000000000..c90afeea54aa --- /dev/null +++ b/drivers/isdn/Kconfig @@ -0,0 +1,67 @@ +# +# ISDN device configuration +# + +menu "ISDN subsystem" + +config ISDN + tristate "ISDN support" + depends on NET + ---help--- + ISDN ("Integrated Services Digital Networks", called RNIS in France) + is a special type of fully digital telephone service; it's mostly + used to connect to your Internet service provider (with SLIP or + PPP). The main advantage is that the speed is higher than ordinary + modem/telephone connections, and that you can have voice + conversations while downloading stuff. It only works if your + computer is equipped with an ISDN card and both you and your service + provider purchased an ISDN line from the phone company. For + details, read <http://www.alumni.caltech.edu/~dank/isdn/> on the WWW. + + Select this option if you want your kernel to support ISDN. + + +menu "Old ISDN4Linux" + depends on NET && ISDN + +config ISDN_I4L + tristate "Old ISDN4Linux (obsolete)" + ---help--- + This driver allows you to use an ISDN-card for networking + connections and as dialin/out device. The isdn-tty's have a built + in AT-compatible modem emulator. Network devices support autodial, + channel-bundling, callback and caller-authentication without having + a daemon running. A reduced T.70 protocol is supported with tty's + suitable for German BTX. On D-Channel, the protocols EDSS1 + (Euro-ISDN) and 1TR6 (German style) are supported. See + <file:Documentation/isdn/README> for more information. + + ISDN support in the linux kernel is moving towards a new API, + called CAPI (Common ISDN Application Programming Interface). + Therefore the old ISDN4Linux layer is becoming obsolete. It is + still usable, though, if you select this option. + +if ISDN_I4L +source "drivers/isdn/i4l/Kconfig" +endif + +endmenu + +comment "CAPI subsystem" + depends on NET && ISDN + +config ISDN_CAPI + tristate "CAPI2.0 support" + depends on ISDN + help + This provides the CAPI (Common ISDN Application Programming + Interface, a standard making it easy for programs to access ISDN + hardware, see <http://www.capi.org/>. This is needed for AVM's set + of active ISDN controllers like B1, T1, M1. + +source "drivers/isdn/capi/Kconfig" + +source "drivers/isdn/hardware/Kconfig" + +endmenu + diff --git a/drivers/isdn/Makefile b/drivers/isdn/Makefile new file mode 100644 index 000000000000..03d8ccd51955 --- /dev/null +++ b/drivers/isdn/Makefile @@ -0,0 +1,15 @@ +# Makefile for the kernel ISDN subsystem and device drivers. + +# Object files in subdirectories + +obj-$(CONFIG_ISDN_I4L) += i4l/ +obj-$(CONFIG_ISDN_CAPI) += capi/ +obj-$(CONFIG_ISDN_CAPI) += hardware/ +obj-$(CONFIG_ISDN_DIVERSION) += divert/ +obj-$(CONFIG_ISDN_DRV_HISAX) += hisax/ +obj-$(CONFIG_ISDN_DRV_ICN) += icn/ +obj-$(CONFIG_ISDN_DRV_PCBIT) += pcbit/ +obj-$(CONFIG_ISDN_DRV_SC) += sc/ +obj-$(CONFIG_ISDN_DRV_LOOP) += isdnloop/ +obj-$(CONFIG_ISDN_DRV_ACT2000) += act2000/ +obj-$(CONFIG_HYSDN) += hysdn/ diff --git a/drivers/isdn/act2000/Kconfig b/drivers/isdn/act2000/Kconfig new file mode 100644 index 000000000000..78e6ad8d57c5 --- /dev/null +++ b/drivers/isdn/act2000/Kconfig @@ -0,0 +1,13 @@ +# +# Config.in for IBM Active 2000 ISDN driver +# +config ISDN_DRV_ACT2000 + tristate "IBM Active 2000 support" + depends on ISDN_I4L && ISA + help + Say Y here if you have an IBM Active 2000 ISDN card. In order to use + this card, additional firmware is necessary, which has to be loaded + into the card using a utility which is part of the latest + isdn4k-utils package. Please read the file + <file:Documentation/isdn/README.act2000> for more information. + diff --git a/drivers/isdn/act2000/Makefile b/drivers/isdn/act2000/Makefile new file mode 100644 index 000000000000..05e582fb5c00 --- /dev/null +++ b/drivers/isdn/act2000/Makefile @@ -0,0 +1,9 @@ +# Makefile for the act2000 ISDN device driver + +# Each configuration option enables a list of files. + +obj-$(CONFIG_ISDN_DRV_ACT2000) += act2000.o + +# Multipart objects. + +act2000-y := module.o capi.o act2000_isa.o diff --git a/drivers/isdn/act2000/act2000.h b/drivers/isdn/act2000/act2000.h new file mode 100644 index 000000000000..b091d1a54125 --- /dev/null +++ b/drivers/isdn/act2000/act2000.h @@ -0,0 +1,202 @@ +/* $Id: act2000.h,v 1.8.6.3 2001/09/23 22:24:32 kai Exp $ + * + * ISDN lowlevel-module for the IBM ISDN-S0 Active 2000. + * + * Author Fritz Elfert + * Copyright by Fritz Elfert <fritz@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * Thanks to Friedemann Baitinger and IBM Germany + * + */ + +#ifndef act2000_h +#define act2000_h + +#include <linux/compiler.h> + +#define ACT2000_IOCTL_SETPORT 1 +#define ACT2000_IOCTL_GETPORT 2 +#define ACT2000_IOCTL_SETIRQ 3 +#define ACT2000_IOCTL_GETIRQ 4 +#define ACT2000_IOCTL_SETBUS 5 +#define ACT2000_IOCTL_GETBUS 6 +#define ACT2000_IOCTL_SETPROTO 7 +#define ACT2000_IOCTL_GETPROTO 8 +#define ACT2000_IOCTL_SETMSN 9 +#define ACT2000_IOCTL_GETMSN 10 +#define ACT2000_IOCTL_LOADBOOT 11 +#define ACT2000_IOCTL_ADDCARD 12 + +#define ACT2000_IOCTL_TEST 98 +#define ACT2000_IOCTL_DEBUGVAR 99 + +#define ACT2000_BUS_ISA 1 +#define ACT2000_BUS_MCA 2 +#define ACT2000_BUS_PCMCIA 3 + +/* Struct for adding new cards */ +typedef struct act2000_cdef { + int bus; + int port; + int irq; + char id[10]; +} act2000_cdef; + +/* Struct for downloading firmware */ +typedef struct act2000_ddef { + int length; /* Length of code */ + char __user *buffer; /* Ptr. to code */ +} act2000_ddef; + +typedef struct act2000_fwid { + char isdn[4]; + char revlen[2]; + char revision[504]; +} act2000_fwid; + +#if defined(__KERNEL__) || defined(__DEBUGVAR__) + +#ifdef __KERNEL__ +/* Kernel includes */ + +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/workqueue.h> +#include <linux/interrupt.h> +#include <linux/skbuff.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/major.h> +#include <asm/io.h> +#include <linux/kernel.h> +#include <linux/signal.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/mman.h> +#include <linux/ioport.h> +#include <linux/timer.h> +#include <linux/wait.h> +#include <linux/delay.h> +#include <linux/ctype.h> +#include <linux/isdnif.h> + +#endif /* __KERNEL__ */ + +#define ACT2000_PORTLEN 8 + +#define ACT2000_FLAGS_RUNNING 1 /* Cards driver activated */ +#define ACT2000_FLAGS_PVALID 2 /* Cards port is valid */ +#define ACT2000_FLAGS_IVALID 4 /* Cards irq is valid */ +#define ACT2000_FLAGS_LOADED 8 /* Firmware loaded */ + +#define ACT2000_BCH 2 /* # of channels per card */ + +/* D-Channel states */ +#define ACT2000_STATE_NULL 0 +#define ACT2000_STATE_ICALL 1 +#define ACT2000_STATE_OCALL 2 +#define ACT2000_STATE_IWAIT 3 +#define ACT2000_STATE_OWAIT 4 +#define ACT2000_STATE_IBWAIT 5 +#define ACT2000_STATE_OBWAIT 6 +#define ACT2000_STATE_BWAIT 7 +#define ACT2000_STATE_BHWAIT 8 +#define ACT2000_STATE_BHWAIT2 9 +#define ACT2000_STATE_DHWAIT 10 +#define ACT2000_STATE_DHWAIT2 11 +#define ACT2000_STATE_BSETUP 12 +#define ACT2000_STATE_ACTIVE 13 + +#define ACT2000_MAX_QUEUED 8000 /* 2 * maxbuff */ + +#define ACT2000_LOCK_TX 0 +#define ACT2000_LOCK_RX 1 + +typedef struct act2000_chan { + unsigned short callref; /* Call Reference */ + unsigned short fsm_state; /* Current D-Channel state */ + unsigned short eazmask; /* EAZ-Mask for this Channel */ + short queued; /* User-Data Bytes in TX queue */ + unsigned short plci; + unsigned short ncci; + unsigned char l2prot; /* Layer 2 protocol */ + unsigned char l3prot; /* Layer 3 protocol */ +} act2000_chan; + +typedef struct msn_entry { + char eaz; + char msn[16]; + struct msn_entry * next; +} msn_entry; + +typedef struct irq_data_isa { + __u8 *rcvptr; + __u16 rcvidx; + __u16 rcvlen; + struct sk_buff *rcvskb; + __u8 rcvignore; + __u8 rcvhdr[8]; +} irq_data_isa; + +typedef union irq_data { + irq_data_isa isa; +} irq_data; + +/* + * Per card driver data + */ +typedef struct act2000_card { + unsigned short port; /* Base-port-address */ + unsigned short irq; /* Interrupt */ + u_char ptype; /* Protocol type (1TR6 or Euro) */ + u_char bus; /* Cardtype (ISA, MCA, PCMCIA) */ + struct act2000_card *next; /* Pointer to next device struct */ + spinlock_t lock; /* protect critical operations */ + int myid; /* Driver-Nr. assigned by linklevel */ + unsigned long flags; /* Statusflags */ + unsigned long ilock; /* Semaphores for IRQ-Routines */ + struct sk_buff_head rcvq; /* Receive-Message queue */ + struct sk_buff_head sndq; /* Send-Message queue */ + struct sk_buff_head ackq; /* Data-Ack-Message queue */ + u_char *ack_msg; /* Ptr to User Data in User skb */ + __u16 need_b3ack; /* Flag: Need ACK for current skb */ + struct sk_buff *sbuf; /* skb which is currently sent */ + struct timer_list ptimer; /* Poll timer */ + struct work_struct snd_tq; /* Task struct for xmit bh */ + struct work_struct rcv_tq; /* Task struct for rcv bh */ + struct work_struct poll_tq; /* Task struct for polled rcv bh */ + msn_entry *msn_list; + unsigned short msgnum; /* Message number for sending */ + spinlock_t mnlock; /* lock for msgnum */ + act2000_chan bch[ACT2000_BCH]; /* B-Channel status/control */ + char status_buf[256]; /* Buffer for status messages */ + char *status_buf_read; + char *status_buf_write; + char *status_buf_end; + irq_data idat; /* Data used for IRQ handler */ + isdn_if interface; /* Interface to upper layer */ + char regname[35]; /* Name used for request_region */ +} act2000_card; + +extern __inline__ void act2000_schedule_tx(act2000_card *card) +{ + schedule_work(&card->snd_tq); +} + +extern __inline__ void act2000_schedule_rx(act2000_card *card) +{ + schedule_work(&card->rcv_tq); +} + +extern __inline__ void act2000_schedule_poll(act2000_card *card) +{ + schedule_work(&card->poll_tq); +} + +extern char *act2000_find_eaz(act2000_card *, char); + +#endif /* defined(__KERNEL__) || defined(__DEBUGVAR__) */ +#endif /* act2000_h */ diff --git a/drivers/isdn/act2000/act2000_isa.c b/drivers/isdn/act2000/act2000_isa.c new file mode 100644 index 000000000000..bc98d77c5ecd --- /dev/null +++ b/drivers/isdn/act2000/act2000_isa.c @@ -0,0 +1,449 @@ +/* $Id: act2000_isa.c,v 1.11.6.3 2001/09/23 22:24:32 kai Exp $ + * + * ISDN lowlevel-module for the IBM ISDN-S0 Active 2000 (ISA-Version). + * + * Author Fritz Elfert + * Copyright by Fritz Elfert <fritz@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * Thanks to Friedemann Baitinger and IBM Germany + * + */ + +#include "act2000.h" +#include "act2000_isa.h" +#include "capi.h" + +static act2000_card *irq2card_map[16]; + +/* + * Reset Controller, then try to read the Card's signature. + + Return: + * 1 = Signature found. + * 0 = Signature not found. + */ +static int +act2000_isa_reset(unsigned short portbase) +{ + unsigned char reg; + int i; + int found; + int serial = 0; + + found = 0; + if ((reg = inb(portbase + ISA_COR)) != 0xff) { + outb(reg | ISA_COR_RESET, portbase + ISA_COR); + mdelay(10); + outb(reg, portbase + ISA_COR); + mdelay(10); + + for (i = 0; i < 16; i++) { + if (inb(portbase + ISA_ISR) & ISA_ISR_SERIAL) + serial |= 0x10000; + serial >>= 1; + } + if (serial == ISA_SER_ID) + found++; + } + return found; +} + +int +act2000_isa_detect(unsigned short portbase) +{ + int ret = 0; + + if (request_region(portbase, ACT2000_PORTLEN, "act2000isa")) { + ret = act2000_isa_reset(portbase); + release_region(portbase, ISA_REGION); + } + return ret; +} + +static irqreturn_t +act2000_isa_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + act2000_card *card = irq2card_map[irq]; + u_char istatus; + + if (!card) { + printk(KERN_WARNING + "act2000: Spurious interrupt!\n"); + return IRQ_NONE; + } + istatus = (inb(ISA_PORT_ISR) & 0x07); + if (istatus & ISA_ISR_OUT) { + /* RX fifo has data */ + istatus &= ISA_ISR_OUT_MASK; + outb(0, ISA_PORT_SIS); + act2000_isa_receive(card); + outb(ISA_SIS_INT, ISA_PORT_SIS); + } + if (istatus & ISA_ISR_ERR) { + /* Error Interrupt */ + istatus &= ISA_ISR_ERR_MASK; + printk(KERN_WARNING "act2000: errIRQ\n"); + } + if (istatus) + printk(KERN_DEBUG "act2000: ?IRQ %d %02x\n", irq, istatus); + return IRQ_HANDLED; +} + +static void +act2000_isa_select_irq(act2000_card * card) +{ + unsigned char reg; + + reg = (inb(ISA_PORT_COR) & ~ISA_COR_IRQOFF) | ISA_COR_PERR; + switch (card->irq) { + case 3: + reg = ISA_COR_IRQ03; + break; + case 5: + reg = ISA_COR_IRQ05; + break; + case 7: + reg = ISA_COR_IRQ07; + break; + case 10: + reg = ISA_COR_IRQ10; + break; + case 11: + reg = ISA_COR_IRQ11; + break; + case 12: + reg = ISA_COR_IRQ12; + break; + case 15: + reg = ISA_COR_IRQ15; + break; + } + outb(reg, ISA_PORT_COR); +} + +static void +act2000_isa_enable_irq(act2000_card * card) +{ + act2000_isa_select_irq(card); + /* Enable READ irq */ + outb(ISA_SIS_INT, ISA_PORT_SIS); +} + +/* + * Install interrupt handler, enable irq on card. + * If irq is -1, choose next free irq, else irq is given explicitely. + */ +int +act2000_isa_config_irq(act2000_card * card, short irq) +{ + if (card->flags & ACT2000_FLAGS_IVALID) { + free_irq(card->irq, NULL); + irq2card_map[card->irq] = NULL; + } + card->flags &= ~ACT2000_FLAGS_IVALID; + outb(ISA_COR_IRQOFF, ISA_PORT_COR); + if (!irq) + return 0; + + if (!request_irq(irq, &act2000_isa_interrupt, 0, card->regname, NULL)) { + card->irq = irq; + irq2card_map[card->irq] = card; + card->flags |= ACT2000_FLAGS_IVALID; + printk(KERN_WARNING + "act2000: Could not request irq %d\n",irq); + return -EBUSY; + } else { + act2000_isa_select_irq(card); + /* Disable READ and WRITE irq */ + outb(0, ISA_PORT_SIS); + outb(0, ISA_PORT_SOS); + } + return 0; +} + +int +act2000_isa_config_port(act2000_card * card, unsigned short portbase) +{ + if (card->flags & ACT2000_FLAGS_PVALID) { + release_region(card->port, ISA_REGION); + card->flags &= ~ACT2000_FLAGS_PVALID; + } + if (request_region(portbase, ACT2000_PORTLEN, card->regname) == NULL) + return -EBUSY; + else { + card->port = portbase; + card->flags |= ACT2000_FLAGS_PVALID; + return 0; + } +} + +/* + * Release ressources, used by an adaptor. + */ +void +act2000_isa_release(act2000_card * card) +{ + unsigned long flags; + + spin_lock_irqsave(&card->lock, flags); + if (card->flags & ACT2000_FLAGS_IVALID) { + free_irq(card->irq, NULL); + irq2card_map[card->irq] = NULL; + } + card->flags &= ~ACT2000_FLAGS_IVALID; + if (card->flags & ACT2000_FLAGS_PVALID) + release_region(card->port, ISA_REGION); + card->flags &= ~ACT2000_FLAGS_PVALID; + spin_unlock_irqrestore(&card->lock, flags); +} + +static int +act2000_isa_writeb(act2000_card * card, u_char data) +{ + u_char timeout = 40; + + while (timeout) { + if (inb(ISA_PORT_SOS) & ISA_SOS_READY) { + outb(data, ISA_PORT_SDO); + return 0; + } else { + timeout--; + udelay(10); + } + } + return 1; +} + +static int +act2000_isa_readb(act2000_card * card, u_char * data) +{ + u_char timeout = 40; + + while (timeout) { + if (inb(ISA_PORT_SIS) & ISA_SIS_READY) { + *data = inb(ISA_PORT_SDI); + return 0; + } else { + timeout--; + udelay(10); + } + } + return 1; +} + +void +act2000_isa_receive(act2000_card *card) +{ + u_char c; + + if (test_and_set_bit(ACT2000_LOCK_RX, (void *) &card->ilock) != 0) + return; + while (!act2000_isa_readb(card, &c)) { + if (card->idat.isa.rcvidx < 8) { + card->idat.isa.rcvhdr[card->idat.isa.rcvidx++] = c; + if (card->idat.isa.rcvidx == 8) { + int valid = actcapi_chkhdr(card, (actcapi_msghdr *)&card->idat.isa.rcvhdr); + + if (valid) { + card->idat.isa.rcvlen = ((actcapi_msghdr *)&card->idat.isa.rcvhdr)->len; + card->idat.isa.rcvskb = dev_alloc_skb(card->idat.isa.rcvlen); + if (card->idat.isa.rcvskb == NULL) { + card->idat.isa.rcvignore = 1; + printk(KERN_WARNING + "act2000_isa_receive: no memory\n"); + test_and_clear_bit(ACT2000_LOCK_RX, (void *) &card->ilock); + return; + } + memcpy(skb_put(card->idat.isa.rcvskb, 8), card->idat.isa.rcvhdr, 8); + card->idat.isa.rcvptr = skb_put(card->idat.isa.rcvskb, card->idat.isa.rcvlen - 8); + } else { + card->idat.isa.rcvidx = 0; + printk(KERN_WARNING + "act2000_isa_receive: Invalid CAPI msg\n"); + { + int i; __u8 *p; __u8 *c; __u8 tmp[30]; + for (i = 0, p = (__u8 *)&card->idat.isa.rcvhdr, c = tmp; i < 8; i++) + c += sprintf(c, "%02x ", *(p++)); + printk(KERN_WARNING "act2000_isa_receive: %s\n", tmp); + } + } + } + } else { + if (!card->idat.isa.rcvignore) + *card->idat.isa.rcvptr++ = c; + if (++card->idat.isa.rcvidx >= card->idat.isa.rcvlen) { + if (!card->idat.isa.rcvignore) { + skb_queue_tail(&card->rcvq, card->idat.isa.rcvskb); + act2000_schedule_rx(card); + } + card->idat.isa.rcvidx = 0; + card->idat.isa.rcvlen = 8; + card->idat.isa.rcvignore = 0; + card->idat.isa.rcvskb = NULL; + card->idat.isa.rcvptr = card->idat.isa.rcvhdr; + } + } + } + if (!(card->flags & ACT2000_FLAGS_IVALID)) { + /* In polling mode, schedule myself */ + if ((card->idat.isa.rcvidx) && + (card->idat.isa.rcvignore || + (card->idat.isa.rcvidx < card->idat.isa.rcvlen))) + act2000_schedule_poll(card); + } + test_and_clear_bit(ACT2000_LOCK_RX, (void *) &card->ilock); +} + +void +act2000_isa_send(act2000_card * card) +{ + unsigned long flags; + struct sk_buff *skb; + actcapi_msg *msg; + int l; + + if (test_and_set_bit(ACT2000_LOCK_TX, (void *) &card->ilock) != 0) + return; + while (1) { + spin_lock_irqsave(&card->lock, flags); + if (!(card->sbuf)) { + if ((card->sbuf = skb_dequeue(&card->sndq))) { + card->ack_msg = card->sbuf->data; + msg = (actcapi_msg *)card->sbuf->data; + if ((msg->hdr.cmd.cmd == 0x86) && + (msg->hdr.cmd.subcmd == 0) ) { + /* Save flags in message */ + card->need_b3ack = msg->msg.data_b3_req.flags; + msg->msg.data_b3_req.flags = 0; + } + } + } + spin_unlock_irqrestore(&card->lock, flags); + if (!(card->sbuf)) { + /* No more data to send */ + test_and_clear_bit(ACT2000_LOCK_TX, (void *) &card->ilock); + return; + } + skb = card->sbuf; + l = 0; + while (skb->len) { + if (act2000_isa_writeb(card, *(skb->data))) { + /* Fifo is full, but more data to send */ + test_and_clear_bit(ACT2000_LOCK_TX, (void *) &card->ilock); + /* Schedule myself */ + act2000_schedule_tx(card); + return; + } + skb_pull(skb, 1); + l++; + } + msg = (actcapi_msg *)card->ack_msg; + if ((msg->hdr.cmd.cmd == 0x86) && + (msg->hdr.cmd.subcmd == 0) ) { + /* + * If it's user data, reset data-ptr + * and put skb into ackq. + */ + skb->data = card->ack_msg; + /* Restore flags in message */ + msg->msg.data_b3_req.flags = card->need_b3ack; + skb_queue_tail(&card->ackq, skb); + } else + dev_kfree_skb(skb); + card->sbuf = NULL; + } +} + +/* + * Get firmware ID, check for 'ISDN' signature. + */ +static int +act2000_isa_getid(act2000_card * card) +{ + + act2000_fwid fid; + u_char *p = (u_char *) & fid; + int count = 0; + + while (1) { + if (count > 510) + return -EPROTO; + if (act2000_isa_readb(card, p++)) + break; + count++; + } + if (count <= 20) { + printk(KERN_WARNING "act2000: No Firmware-ID!\n"); + return -ETIME; + } + *p = '\0'; + fid.revlen[0] = '\0'; + if (strcmp(fid.isdn, "ISDN")) { + printk(KERN_WARNING "act2000: Wrong Firmware-ID!\n"); + return -EPROTO; + } + if ((p = strchr(fid.revision, '\n'))) + *p = '\0'; + printk(KERN_INFO "act2000: Firmware-ID: %s\n", fid.revision); + if (card->flags & ACT2000_FLAGS_IVALID) { + printk(KERN_DEBUG "Enabling Interrupts ...\n"); + act2000_isa_enable_irq(card); + } + return 0; +} + +/* + * Download microcode into card, check Firmware signature. + */ +int +act2000_isa_download(act2000_card * card, act2000_ddef __user * cb) +{ + unsigned int length; + int l; + int c; + long timeout; + u_char *b; + u_char __user *p; + u_char *buf; + act2000_ddef cblock; + + if (!act2000_isa_reset(card->port)) + return -ENXIO; + msleep_interruptible(500); + if (copy_from_user(&cblock, cb, sizeof(cblock))) + return -EFAULT; + length = cblock.length; + p = cblock.buffer; + if (!access_ok(VERIFY_READ, p, length)) + return -EFAULT; + buf = (u_char *) kmalloc(1024, GFP_KERNEL); + if (!buf) + return -ENOMEM; + timeout = 0; + while (length) { + l = (length > 1024) ? 1024 : length; + c = 0; + b = buf; + if (copy_from_user(buf, p, l)) { + kfree(buf); + return -EFAULT; + } + while (c < l) { + if (act2000_isa_writeb(card, *b++)) { + printk(KERN_WARNING + "act2000: loader timed out" + " len=%d c=%d\n", length, c); + kfree(buf); + return -ETIME; + } + c++; + } + length -= l; + p += l; + } + kfree(buf); + msleep_interruptible(500); + return (act2000_isa_getid(card)); +} diff --git a/drivers/isdn/act2000/act2000_isa.h b/drivers/isdn/act2000/act2000_isa.h new file mode 100644 index 000000000000..ad86c5ed9aad --- /dev/null +++ b/drivers/isdn/act2000/act2000_isa.h @@ -0,0 +1,136 @@ +/* $Id: act2000_isa.h,v 1.4.6.1 2001/09/23 22:24:32 kai Exp $ + * + * ISDN lowlevel-module for the IBM ISDN-S0 Active 2000 (ISA-Version). + * + * Author Fritz Elfert + * Copyright by Fritz Elfert <fritz@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * Thanks to Friedemann Baitinger and IBM Germany + * + */ + +#ifndef act2000_isa_h +#define act2000_isa_h + +#define ISA_POLL_LOOP 40 /* Try to read-write before give up */ + +typedef enum { + INT_NO_CHANGE = 0, /* Do not change the Mask */ + INT_ON = 1, /* Set to Enable */ + INT_OFF = 2, /* Set to Disable */ +} ISA_INT_T; + +/**************************************************************************/ +/* Configuration Register COR (RW) */ +/**************************************************************************/ +/* 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 */ +/* Soft Res| IRQM | IRQ Select | N/A | WAIT |Proc err */ +/**************************************************************************/ +#define ISA_COR 0 /* Offset for ISA config register */ +#define ISA_COR_PERR 0x01 /* Processor Error Enabled */ +#define ISA_COR_WS 0x02 /* Insert Wait State if 1 */ +#define ISA_COR_IRQOFF 0x38 /* No Interrupt */ +#define ISA_COR_IRQ07 0x30 /* IRQ 7 Enable */ +#define ISA_COR_IRQ05 0x28 /* IRQ 5 Enable */ +#define ISA_COR_IRQ03 0x20 /* IRQ 3 Enable */ +#define ISA_COR_IRQ10 0x18 /* IRQ 10 Enable */ +#define ISA_COR_IRQ11 0x10 /* IRQ 11 Enable */ +#define ISA_COR_IRQ12 0x08 /* IRQ 12 Enable */ +#define ISA_COR_IRQ15 0x00 /* IRQ 15 Enable */ +#define ISA_COR_IRQPULSE 0x40 /* 0 = Level 1 = Pulse Interrupt */ +#define ISA_COR_RESET 0x80 /* Soft Reset for Transputer */ + +/**************************************************************************/ +/* Interrupt Source Register ISR (RO) */ +/**************************************************************************/ +/* 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 */ +/* N/A | N/A | N/A |Err sig |Ser ID |IN Intr |Out Intr| Error */ +/**************************************************************************/ +#define ISA_ISR 1 /* Offset for Interrupt Register */ +#define ISA_ISR_ERR 0x01 /* Error Interrupt */ +#define ISA_ISR_OUT 0x02 /* Output Interrupt */ +#define ISA_ISR_INP 0x04 /* Input Interrupt */ +#define ISA_ISR_SERIAL 0x08 /* Read out Serial ID after Reset */ +#define ISA_ISR_ERRSIG 0x10 /* Error Signal Input */ +#define ISA_ISR_ERR_MASK 0xfe /* Mask Error Interrupt */ +#define ISA_ISR_OUT_MASK 0xfd /* Mask Output Interrupt */ +#define ISA_ISR_INP_MASK 0xfb /* Mask Input Interrupt */ + +/* Signature delivered after Reset at ISA_ISR_SERIAL (LSB first) */ +#define ISA_SER_ID 0x0201 /* ID for ISA Card */ + +/**************************************************************************/ +/* EEPROM Register EPR (RW) */ +/**************************************************************************/ +/* 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 */ +/* N/A | N/A | N/A |ROM Hold| ROM CS |ROM CLK | ROM IN |ROM Out */ +/**************************************************************************/ +#define ISA_EPR 2 /* Offset for this Register */ +#define ISA_EPR_OUT 0x01 /* Rome Register Out (RO) */ +#define ISA_EPR_IN 0x02 /* Rom Register In (WR) */ +#define ISA_EPR_CLK 0x04 /* Rom Clock (WR) */ +#define ISA_EPR_CS 0x08 /* Rom Cip Select (WR) */ +#define ISA_EPR_HOLD 0x10 /* Rom Hold Signal (WR) */ + +/**************************************************************************/ +/* EEPROM enable Register EER (unused) */ +/**************************************************************************/ +#define ISA_EER 3 /* Offset for this Register */ + +/**************************************************************************/ +/* SLC Data Input SDI (RO) */ +/**************************************************************************/ +#define ISA_SDI 4 /* Offset for this Register */ + +/**************************************************************************/ +/* SLC Data Output SDO (WO) */ +/**************************************************************************/ +#define ISA_SDO 5 /* Offset for this Register */ + +/**************************************************************************/ +/* IMS C011 Mode 2 Input Status Register for INMOS CPU SIS (RW) */ +/**************************************************************************/ +/* 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 */ +/* N/A | N/A | N/A | N/A | N/A | N/A |Int Ena |Data Pre */ +/**************************************************************************/ +#define ISA_SIS 6 /* Offset for this Register */ +#define ISA_SIS_READY 0x01 /* If 1 : data is available */ +#define ISA_SIS_INT 0x02 /* Enable Interrupt for READ */ + +/**************************************************************************/ +/* IMS C011 Mode 2 Output Status Register from INMOS CPU SOS (RW) */ +/**************************************************************************/ +/* 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 */ +/* N/A | N/A | N/A | N/A | N/A | N/A |Int Ena |Out Rdy */ +/**************************************************************************/ +#define ISA_SOS 7 /* Offset for this Register */ +#define ISA_SOS_READY 0x01 /* If 1 : we can write Data */ +#define ISA_SOS_INT 0x02 /* Enable Interrupt for WRITE */ + +#define ISA_REGION 8 /* Number of Registers */ + + +/* Macros for accessing ports */ +#define ISA_PORT_COR (card->port+ISA_COR) +#define ISA_PORT_ISR (card->port+ISA_ISR) +#define ISA_PORT_EPR (card->port+ISA_EPR) +#define ISA_PORT_EER (card->port+ISA_EER) +#define ISA_PORT_SDI (card->port+ISA_SDI) +#define ISA_PORT_SDO (card->port+ISA_SDO) +#define ISA_PORT_SIS (card->port+ISA_SIS) +#define ISA_PORT_SOS (card->port+ISA_SOS) + +/* Prototypes */ + +extern int act2000_isa_detect(unsigned short portbase); +extern int act2000_isa_config_irq(act2000_card * card, short irq); +extern int act2000_isa_config_port(act2000_card * card, unsigned short portbase); +extern int act2000_isa_download(act2000_card * card, act2000_ddef __user * cb); +extern void act2000_isa_release(act2000_card * card); +extern void act2000_isa_receive(act2000_card *card); +extern void act2000_isa_send(act2000_card *card); + +#endif /* act2000_isa_h */ diff --git a/drivers/isdn/act2000/capi.c b/drivers/isdn/act2000/capi.c new file mode 100644 index 000000000000..40395f567231 --- /dev/null +++ b/drivers/isdn/act2000/capi.c @@ -0,0 +1,1177 @@ +/* $Id: capi.c,v 1.9.6.2 2001/09/23 22:24:32 kai Exp $ + * + * ISDN lowlevel-module for the IBM ISDN-S0 Active 2000. + * CAPI encoder/decoder + * + * Author Fritz Elfert + * Copyright by Fritz Elfert <fritz@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * Thanks to Friedemann Baitinger and IBM Germany + * + */ + +#include "act2000.h" +#include "capi.h" + +static actcapi_msgdsc valid_msg[] = { + {{ 0x86, 0x02}, "DATA_B3_IND"}, /* DATA_B3_IND/CONF must be first because of speed!!! */ + {{ 0x86, 0x01}, "DATA_B3_CONF"}, + {{ 0x02, 0x01}, "CONNECT_CONF"}, + {{ 0x02, 0x02}, "CONNECT_IND"}, + {{ 0x09, 0x01}, "CONNECT_INFO_CONF"}, + {{ 0x03, 0x02}, "CONNECT_ACTIVE_IND"}, + {{ 0x04, 0x01}, "DISCONNECT_CONF"}, + {{ 0x04, 0x02}, "DISCONNECT_IND"}, + {{ 0x05, 0x01}, "LISTEN_CONF"}, + {{ 0x06, 0x01}, "GET_PARAMS_CONF"}, + {{ 0x07, 0x01}, "INFO_CONF"}, + {{ 0x07, 0x02}, "INFO_IND"}, + {{ 0x08, 0x01}, "DATA_CONF"}, + {{ 0x08, 0x02}, "DATA_IND"}, + {{ 0x40, 0x01}, "SELECT_B2_PROTOCOL_CONF"}, + {{ 0x80, 0x01}, "SELECT_B3_PROTOCOL_CONF"}, + {{ 0x81, 0x01}, "LISTEN_B3_CONF"}, + {{ 0x82, 0x01}, "CONNECT_B3_CONF"}, + {{ 0x82, 0x02}, "CONNECT_B3_IND"}, + {{ 0x83, 0x02}, "CONNECT_B3_ACTIVE_IND"}, + {{ 0x84, 0x01}, "DISCONNECT_B3_CONF"}, + {{ 0x84, 0x02}, "DISCONNECT_B3_IND"}, + {{ 0x85, 0x01}, "GET_B3_PARAMS_CONF"}, + {{ 0x01, 0x01}, "RESET_B3_CONF"}, + {{ 0x01, 0x02}, "RESET_B3_IND"}, + /* {{ 0x87, 0x02, "HANDSET_IND"}, not implemented */ + {{ 0xff, 0x01}, "MANUFACTURER_CONF"}, + {{ 0xff, 0x02}, "MANUFACTURER_IND"}, +#ifdef DEBUG_MSG + /* Requests */ + {{ 0x01, 0x00}, "RESET_B3_REQ"}, + {{ 0x02, 0x00}, "CONNECT_REQ"}, + {{ 0x04, 0x00}, "DISCONNECT_REQ"}, + {{ 0x05, 0x00}, "LISTEN_REQ"}, + {{ 0x06, 0x00}, "GET_PARAMS_REQ"}, + {{ 0x07, 0x00}, "INFO_REQ"}, + {{ 0x08, 0x00}, "DATA_REQ"}, + {{ 0x09, 0x00}, "CONNECT_INFO_REQ"}, + {{ 0x40, 0x00}, "SELECT_B2_PROTOCOL_REQ"}, + {{ 0x80, 0x00}, "SELECT_B3_PROTOCOL_REQ"}, + {{ 0x81, 0x00}, "LISTEN_B3_REQ"}, + {{ 0x82, 0x00}, "CONNECT_B3_REQ"}, + {{ 0x84, 0x00}, "DISCONNECT_B3_REQ"}, + {{ 0x85, 0x00}, "GET_B3_PARAMS_REQ"}, + {{ 0x86, 0x00}, "DATA_B3_REQ"}, + {{ 0xff, 0x00}, "MANUFACTURER_REQ"}, + /* Responses */ + {{ 0x01, 0x03}, "RESET_B3_RESP"}, + {{ 0x02, 0x03}, "CONNECT_RESP"}, + {{ 0x03, 0x03}, "CONNECT_ACTIVE_RESP"}, + {{ 0x04, 0x03}, "DISCONNECT_RESP"}, + {{ 0x07, 0x03}, "INFO_RESP"}, + {{ 0x08, 0x03}, "DATA_RESP"}, + {{ 0x82, 0x03}, "CONNECT_B3_RESP"}, + {{ 0x83, 0x03}, "CONNECT_B3_ACTIVE_RESP"}, + {{ 0x84, 0x03}, "DISCONNECT_B3_RESP"}, + {{ 0x86, 0x03}, "DATA_B3_RESP"}, + {{ 0xff, 0x03}, "MANUFACTURER_RESP"}, +#endif + {{ 0x00, 0x00}, NULL}, +}; +#define num_valid_msg (sizeof(valid_msg)/sizeof(actcapi_msgdsc)) +#define num_valid_imsg 27 /* MANUFACTURER_IND */ + +/* + * Check for a valid incoming CAPI message. + * Return: + * 0 = Invalid message + * 1 = Valid message, no B-Channel-data + * 2 = Valid message, B-Channel-data + */ +int +actcapi_chkhdr(act2000_card * card, actcapi_msghdr *hdr) +{ + int i; + + if (hdr->applicationID != 1) + return 0; + if (hdr->len < 9) + return 0; + for (i = 0; i < num_valid_imsg; i++) + if ((hdr->cmd.cmd == valid_msg[i].cmd.cmd) && + (hdr->cmd.subcmd == valid_msg[i].cmd.subcmd)) { + return (i?1:2); + } + return 0; +} + +#define ACTCAPI_MKHDR(l, c, s) { \ + skb = alloc_skb(l + 8, GFP_ATOMIC); \ + if (skb) { \ + m = (actcapi_msg *)skb_put(skb, l + 8); \ + m->hdr.len = l + 8; \ + m->hdr.applicationID = 1; \ + m->hdr.cmd.cmd = c; \ + m->hdr.cmd.subcmd = s; \ + m->hdr.msgnum = actcapi_nextsmsg(card); \ + } else m = NULL;\ +} + +#define ACTCAPI_CHKSKB if (!skb) { \ + printk(KERN_WARNING "actcapi: alloc_skb failed\n"); \ + return; \ +} + +#define ACTCAPI_QUEUE_TX { \ + actcapi_debug_msg(skb, 1); \ + skb_queue_tail(&card->sndq, skb); \ + act2000_schedule_tx(card); \ +} + +int +actcapi_listen_req(act2000_card *card) +{ + __u16 eazmask = 0; + int i; + actcapi_msg *m; + struct sk_buff *skb; + + for (i = 0; i < ACT2000_BCH; i++) + eazmask |= card->bch[i].eazmask; + ACTCAPI_MKHDR(9, 0x05, 0x00); + if (!skb) { + printk(KERN_WARNING "actcapi: alloc_skb failed\n"); + return -ENOMEM; + } + m->msg.listen_req.controller = 0; + m->msg.listen_req.infomask = 0x3f; /* All information */ + m->msg.listen_req.eazmask = eazmask; + m->msg.listen_req.simask = (eazmask)?0x86:0; /* All SI's */ + ACTCAPI_QUEUE_TX; + return 0; +} + +int +actcapi_connect_req(act2000_card *card, act2000_chan *chan, char *phone, + char eaz, int si1, int si2) +{ + actcapi_msg *m; + struct sk_buff *skb; + + ACTCAPI_MKHDR((11 + strlen(phone)), 0x02, 0x00); + if (!skb) { + printk(KERN_WARNING "actcapi: alloc_skb failed\n"); + chan->fsm_state = ACT2000_STATE_NULL; + return -ENOMEM; + } + m->msg.connect_req.controller = 0; + m->msg.connect_req.bchan = 0x83; + m->msg.connect_req.infomask = 0x3f; + m->msg.connect_req.si1 = si1; + m->msg.connect_req.si2 = si2; + m->msg.connect_req.eaz = eaz?eaz:'0'; + m->msg.connect_req.addr.len = strlen(phone) + 1; + m->msg.connect_req.addr.tnp = 0x81; + memcpy(m->msg.connect_req.addr.num, phone, strlen(phone)); + chan->callref = m->hdr.msgnum; + ACTCAPI_QUEUE_TX; + return 0; +} + +static void +actcapi_connect_b3_req(act2000_card *card, act2000_chan *chan) +{ + actcapi_msg *m; + struct sk_buff *skb; + + ACTCAPI_MKHDR(17, 0x82, 0x00); + ACTCAPI_CHKSKB; + m->msg.connect_b3_req.plci = chan->plci; + memset(&m->msg.connect_b3_req.ncpi, 0, + sizeof(m->msg.connect_b3_req.ncpi)); + m->msg.connect_b3_req.ncpi.len = 13; + m->msg.connect_b3_req.ncpi.modulo = 8; + ACTCAPI_QUEUE_TX; +} + +/* + * Set net type (1TR6) or (EDSS1) + */ +int +actcapi_manufacturer_req_net(act2000_card *card) +{ + actcapi_msg *m; + struct sk_buff *skb; + + ACTCAPI_MKHDR(5, 0xff, 0x00); + if (!skb) { + printk(KERN_WARNING "actcapi: alloc_skb failed\n"); + return -ENOMEM; + } + m->msg.manufacturer_req_net.manuf_msg = 0x11; + m->msg.manufacturer_req_net.controller = 1; + m->msg.manufacturer_req_net.nettype = (card->ptype == ISDN_PTYPE_EURO)?1:0; + ACTCAPI_QUEUE_TX; + printk(KERN_INFO "act2000 %s: D-channel protocol now %s\n", + card->interface.id, (card->ptype == ISDN_PTYPE_EURO)?"euro":"1tr6"); + card->interface.features &= + ~(ISDN_FEATURE_P_UNKNOWN | ISDN_FEATURE_P_EURO | ISDN_FEATURE_P_1TR6); + card->interface.features |= + ((card->ptype == ISDN_PTYPE_EURO)?ISDN_FEATURE_P_EURO:ISDN_FEATURE_P_1TR6); + return 0; +} + +/* + * Switch V.42 on or off + */ +int +actcapi_manufacturer_req_v42(act2000_card *card, ulong arg) +{ + actcapi_msg *m; + struct sk_buff *skb; + + ACTCAPI_MKHDR(8, 0xff, 0x00); + if (!skb) { + + printk(KERN_WARNING "actcapi: alloc_skb failed\n"); + return -ENOMEM; + } + m->msg.manufacturer_req_v42.manuf_msg = 0x10; + m->msg.manufacturer_req_v42.controller = 0; + m->msg.manufacturer_req_v42.v42control = (arg?1:0); + ACTCAPI_QUEUE_TX; + return 0; +} + +/* + * Set error-handler + */ +int +actcapi_manufacturer_req_errh(act2000_card *card) +{ + actcapi_msg *m; + struct sk_buff *skb; + + ACTCAPI_MKHDR(4, 0xff, 0x00); + if (!skb) { + + printk(KERN_WARNING "actcapi: alloc_skb failed\n"); + return -ENOMEM; + } + m->msg.manufacturer_req_err.manuf_msg = 0x03; + m->msg.manufacturer_req_err.controller = 0; + ACTCAPI_QUEUE_TX; + return 0; +} + +/* + * Set MSN-Mapping. + */ +int +actcapi_manufacturer_req_msn(act2000_card *card) +{ + msn_entry *p = card->msn_list; + actcapi_msg *m; + struct sk_buff *skb; + int len; + + while (p) { + int i; + + len = strlen(p->msn); + for (i = 0; i < 2; i++) { + ACTCAPI_MKHDR(6 + len, 0xff, 0x00); + if (!skb) { + printk(KERN_WARNING "actcapi: alloc_skb failed\n"); + return -ENOMEM; + } + m->msg.manufacturer_req_msn.manuf_msg = 0x13 + i; + m->msg.manufacturer_req_msn.controller = 0; + m->msg.manufacturer_req_msn.msnmap.eaz = p->eaz; + m->msg.manufacturer_req_msn.msnmap.len = len; + memcpy(m->msg.manufacturer_req_msn.msnmap.msn, p->msn, len); + ACTCAPI_QUEUE_TX; + } + p = p->next; + } + return 0; +} + +void +actcapi_select_b2_protocol_req(act2000_card *card, act2000_chan *chan) +{ + actcapi_msg *m; + struct sk_buff *skb; + + ACTCAPI_MKHDR(10, 0x40, 0x00); + ACTCAPI_CHKSKB; + m->msg.select_b2_protocol_req.plci = chan->plci; + memset(&m->msg.select_b2_protocol_req.dlpd, 0, + sizeof(m->msg.select_b2_protocol_req.dlpd)); + m->msg.select_b2_protocol_req.dlpd.len = 6; + switch (chan->l2prot) { + case ISDN_PROTO_L2_TRANS: + m->msg.select_b2_protocol_req.protocol = 0x03; + m->msg.select_b2_protocol_req.dlpd.dlen = 4000; + break; + case ISDN_PROTO_L2_HDLC: + m->msg.select_b2_protocol_req.protocol = 0x02; + m->msg.select_b2_protocol_req.dlpd.dlen = 4000; + break; + case ISDN_PROTO_L2_X75I: + case ISDN_PROTO_L2_X75UI: + case ISDN_PROTO_L2_X75BUI: + m->msg.select_b2_protocol_req.protocol = 0x01; + m->msg.select_b2_protocol_req.dlpd.dlen = 4000; + m->msg.select_b2_protocol_req.dlpd.laa = 3; + m->msg.select_b2_protocol_req.dlpd.lab = 1; + m->msg.select_b2_protocol_req.dlpd.win = 7; + m->msg.select_b2_protocol_req.dlpd.modulo = 8; + break; + } + ACTCAPI_QUEUE_TX; +} + +static void +actcapi_select_b3_protocol_req(act2000_card *card, act2000_chan *chan) +{ + actcapi_msg *m; + struct sk_buff *skb; + + ACTCAPI_MKHDR(17, 0x80, 0x00); + ACTCAPI_CHKSKB; + m->msg.select_b3_protocol_req.plci = chan->plci; + memset(&m->msg.select_b3_protocol_req.ncpd, 0, + sizeof(m->msg.select_b3_protocol_req.ncpd)); + switch (chan->l3prot) { + case ISDN_PROTO_L3_TRANS: + m->msg.select_b3_protocol_req.protocol = 0x04; + m->msg.select_b3_protocol_req.ncpd.len = 13; + m->msg.select_b3_protocol_req.ncpd.modulo = 8; + break; + } + ACTCAPI_QUEUE_TX; +} + +static void +actcapi_listen_b3_req(act2000_card *card, act2000_chan *chan) +{ + actcapi_msg *m; + struct sk_buff *skb; + + ACTCAPI_MKHDR(2, 0x81, 0x00); + ACTCAPI_CHKSKB; + m->msg.listen_b3_req.plci = chan->plci; + ACTCAPI_QUEUE_TX; +} + +static void +actcapi_disconnect_req(act2000_card *card, act2000_chan *chan) +{ + actcapi_msg *m; + struct sk_buff *skb; + + ACTCAPI_MKHDR(3, 0x04, 0x00); + ACTCAPI_CHKSKB; + m->msg.disconnect_req.plci = chan->plci; + m->msg.disconnect_req.cause = 0; + ACTCAPI_QUEUE_TX; +} + +void +actcapi_disconnect_b3_req(act2000_card *card, act2000_chan *chan) +{ + actcapi_msg *m; + struct sk_buff *skb; + + ACTCAPI_MKHDR(17, 0x84, 0x00); + ACTCAPI_CHKSKB; + m->msg.disconnect_b3_req.ncci = chan->ncci; + memset(&m->msg.disconnect_b3_req.ncpi, 0, + sizeof(m->msg.disconnect_b3_req.ncpi)); + m->msg.disconnect_b3_req.ncpi.len = 13; + m->msg.disconnect_b3_req.ncpi.modulo = 8; + chan->fsm_state = ACT2000_STATE_BHWAIT; + ACTCAPI_QUEUE_TX; +} + +void +actcapi_connect_resp(act2000_card *card, act2000_chan *chan, __u8 cause) +{ + actcapi_msg *m; + struct sk_buff *skb; + + ACTCAPI_MKHDR(3, 0x02, 0x03); + ACTCAPI_CHKSKB; + m->msg.connect_resp.plci = chan->plci; + m->msg.connect_resp.rejectcause = cause; + if (cause) { + chan->fsm_state = ACT2000_STATE_NULL; + chan->plci = 0x8000; + } else + chan->fsm_state = ACT2000_STATE_IWAIT; + ACTCAPI_QUEUE_TX; +} + +static void +actcapi_connect_active_resp(act2000_card *card, act2000_chan *chan) +{ + actcapi_msg *m; + struct sk_buff *skb; + + ACTCAPI_MKHDR(2, 0x03, 0x03); + ACTCAPI_CHKSKB; + m->msg.connect_resp.plci = chan->plci; + if (chan->fsm_state == ACT2000_STATE_IWAIT) + chan->fsm_state = ACT2000_STATE_IBWAIT; + ACTCAPI_QUEUE_TX; +} + +static void +actcapi_connect_b3_resp(act2000_card *card, act2000_chan *chan, __u8 rejectcause) +{ + actcapi_msg *m; + struct sk_buff *skb; + + ACTCAPI_MKHDR((rejectcause?3:17), 0x82, 0x03); + ACTCAPI_CHKSKB; + m->msg.connect_b3_resp.ncci = chan->ncci; + m->msg.connect_b3_resp.rejectcause = rejectcause; + if (!rejectcause) { + memset(&m->msg.connect_b3_resp.ncpi, 0, + sizeof(m->msg.connect_b3_resp.ncpi)); + m->msg.connect_b3_resp.ncpi.len = 13; + m->msg.connect_b3_resp.ncpi.modulo = 8; + chan->fsm_state = ACT2000_STATE_BWAIT; + } + ACTCAPI_QUEUE_TX; +} + +static void +actcapi_connect_b3_active_resp(act2000_card *card, act2000_chan *chan) +{ + actcapi_msg *m; + struct sk_buff *skb; + + ACTCAPI_MKHDR(2, 0x83, 0x03); + ACTCAPI_CHKSKB; + m->msg.connect_b3_active_resp.ncci = chan->ncci; + chan->fsm_state = ACT2000_STATE_ACTIVE; + ACTCAPI_QUEUE_TX; +} + +static void +actcapi_info_resp(act2000_card *card, act2000_chan *chan) +{ + actcapi_msg *m; + struct sk_buff *skb; + + ACTCAPI_MKHDR(2, 0x07, 0x03); + ACTCAPI_CHKSKB; + m->msg.info_resp.plci = chan->plci; + ACTCAPI_QUEUE_TX; +} + +static void +actcapi_disconnect_b3_resp(act2000_card *card, act2000_chan *chan) +{ + actcapi_msg *m; + struct sk_buff *skb; + + ACTCAPI_MKHDR(2, 0x84, 0x03); + ACTCAPI_CHKSKB; + m->msg.disconnect_b3_resp.ncci = chan->ncci; + chan->ncci = 0x8000; + chan->queued = 0; + ACTCAPI_QUEUE_TX; +} + +static void +actcapi_disconnect_resp(act2000_card *card, act2000_chan *chan) +{ + actcapi_msg *m; + struct sk_buff *skb; + + ACTCAPI_MKHDR(2, 0x04, 0x03); + ACTCAPI_CHKSKB; + m->msg.disconnect_resp.plci = chan->plci; + chan->plci = 0x8000; + ACTCAPI_QUEUE_TX; +} + +static int +new_plci(act2000_card *card, __u16 plci) +{ + int i; + for (i = 0; i < ACT2000_BCH; i++) + if (card->bch[i].plci == 0x8000) { + card->bch[i].plci = plci; + return i; + } + return -1; +} + +static int +find_plci(act2000_card *card, __u16 plci) +{ + int i; + for (i = 0; i < ACT2000_BCH; i++) + if (card->bch[i].plci == plci) + return i; + return -1; +} + +static int +find_ncci(act2000_card *card, __u16 ncci) +{ + int i; + for (i = 0; i < ACT2000_BCH; i++) + if (card->bch[i].ncci == ncci) + return i; + return -1; +} + +static int +find_dialing(act2000_card *card, __u16 callref) +{ + int i; + for (i = 0; i < ACT2000_BCH; i++) + if ((card->bch[i].callref == callref) && + (card->bch[i].fsm_state == ACT2000_STATE_OCALL)) + return i; + return -1; +} + +static int +actcapi_data_b3_ind(act2000_card *card, struct sk_buff *skb) { + __u16 plci; + __u16 ncci; + __u16 controller; + __u8 blocknr; + int chan; + actcapi_msg *msg = (actcapi_msg *)skb->data; + + EVAL_NCCI(msg->msg.data_b3_ind.fakencci, plci, controller, ncci); + chan = find_ncci(card, ncci); + if (chan < 0) + return 0; + if (card->bch[chan].fsm_state != ACT2000_STATE_ACTIVE) + return 0; + if (card->bch[chan].plci != plci) + return 0; + blocknr = msg->msg.data_b3_ind.blocknr; + skb_pull(skb, 19); + card->interface.rcvcallb_skb(card->myid, chan, skb); + if (!(skb = alloc_skb(11, GFP_ATOMIC))) { + printk(KERN_WARNING "actcapi: alloc_skb failed\n"); + return 1; + } + msg = (actcapi_msg *)skb_put(skb, 11); + msg->hdr.len = 11; + msg->hdr.applicationID = 1; + msg->hdr.cmd.cmd = 0x86; + msg->hdr.cmd.subcmd = 0x03; + msg->hdr.msgnum = actcapi_nextsmsg(card); + msg->msg.data_b3_resp.ncci = ncci; + msg->msg.data_b3_resp.blocknr = blocknr; + ACTCAPI_QUEUE_TX; + return 1; +} + +/* + * Walk over ackq, unlink DATA_B3_REQ from it, if + * ncci and blocknr are matching. + * Decrement queued-bytes counter. + */ +static int +handle_ack(act2000_card *card, act2000_chan *chan, __u8 blocknr) { + unsigned long flags; + struct sk_buff *skb; + struct sk_buff *tmp; + struct actcapi_msg *m; + int ret = 0; + + spin_lock_irqsave(&card->lock, flags); + skb = skb_peek(&card->ackq); + spin_unlock_irqrestore(&card->lock, flags); + if (!skb) { + printk(KERN_WARNING "act2000: handle_ack nothing found!\n"); + return 0; + } + tmp = skb; + while (1) { + m = (actcapi_msg *)tmp->data; + if ((((m->msg.data_b3_req.fakencci >> 8) & 0xff) == chan->ncci) && + (m->msg.data_b3_req.blocknr == blocknr)) { + /* found corresponding DATA_B3_REQ */ + skb_unlink(tmp); + chan->queued -= m->msg.data_b3_req.datalen; + if (m->msg.data_b3_req.flags) + ret = m->msg.data_b3_req.datalen; + dev_kfree_skb(tmp); + if (chan->queued < 0) + chan->queued = 0; + return ret; + } + spin_lock_irqsave(&card->lock, flags); + tmp = skb_peek((struct sk_buff_head *)tmp); + spin_unlock_irqrestore(&card->lock, flags); + if ((tmp == skb) || (tmp == NULL)) { + /* reached end of queue */ + printk(KERN_WARNING "act2000: handle_ack nothing found!\n"); + return 0; + } + } +} + +void +actcapi_dispatch(act2000_card *card) +{ + struct sk_buff *skb; + actcapi_msg *msg; + __u16 ccmd; + int chan; + int len; + act2000_chan *ctmp; + isdn_ctrl cmd; + char tmp[170]; + + while ((skb = skb_dequeue(&card->rcvq))) { + actcapi_debug_msg(skb, 0); + msg = (actcapi_msg *)skb->data; + ccmd = ((msg->hdr.cmd.cmd << 8) | msg->hdr.cmd.subcmd); + switch (ccmd) { + case 0x8602: + /* DATA_B3_IND */ + if (actcapi_data_b3_ind(card, skb)) + return; + break; + case 0x8601: + /* DATA_B3_CONF */ + chan = find_ncci(card, msg->msg.data_b3_conf.ncci); + if ((chan >= 0) && (card->bch[chan].fsm_state == ACT2000_STATE_ACTIVE)) { + if (msg->msg.data_b3_conf.info != 0) + printk(KERN_WARNING "act2000: DATA_B3_CONF: %04x\n", + msg->msg.data_b3_conf.info); + len = handle_ack(card, &card->bch[chan], + msg->msg.data_b3_conf.blocknr); + if (len) { + cmd.driver = card->myid; + cmd.command = ISDN_STAT_BSENT; + cmd.arg = chan; + cmd.parm.length = len; + card->interface.statcallb(&cmd); + } + } + break; + case 0x0201: + /* CONNECT_CONF */ + chan = find_dialing(card, msg->hdr.msgnum); + if (chan >= 0) { + if (msg->msg.connect_conf.info) { + card->bch[chan].fsm_state = ACT2000_STATE_NULL; + cmd.driver = card->myid; + cmd.command = ISDN_STAT_DHUP; + cmd.arg = chan; + card->interface.statcallb(&cmd); + } else { + card->bch[chan].fsm_state = ACT2000_STATE_OWAIT; + card->bch[chan].plci = msg->msg.connect_conf.plci; + } + } + break; + case 0x0202: + /* CONNECT_IND */ + chan = new_plci(card, msg->msg.connect_ind.plci); + if (chan < 0) { + ctmp = (act2000_chan *)tmp; + ctmp->plci = msg->msg.connect_ind.plci; + actcapi_connect_resp(card, ctmp, 0x11); /* All Card-Cannels busy */ + } else { + card->bch[chan].fsm_state = ACT2000_STATE_ICALL; + cmd.driver = card->myid; + cmd.command = ISDN_STAT_ICALL; + cmd.arg = chan; + cmd.parm.setup.si1 = msg->msg.connect_ind.si1; + cmd.parm.setup.si2 = msg->msg.connect_ind.si2; + if (card->ptype == ISDN_PTYPE_EURO) + strcpy(cmd.parm.setup.eazmsn, + act2000_find_eaz(card, msg->msg.connect_ind.eaz)); + else { + cmd.parm.setup.eazmsn[0] = msg->msg.connect_ind.eaz; + cmd.parm.setup.eazmsn[1] = 0; + } + memset(cmd.parm.setup.phone, 0, sizeof(cmd.parm.setup.phone)); + memcpy(cmd.parm.setup.phone, msg->msg.connect_ind.addr.num, + msg->msg.connect_ind.addr.len - 1); + cmd.parm.setup.plan = msg->msg.connect_ind.addr.tnp; + cmd.parm.setup.screen = 0; + if (card->interface.statcallb(&cmd) == 2) + actcapi_connect_resp(card, &card->bch[chan], 0x15); /* Reject Call */ + } + break; + case 0x0302: + /* CONNECT_ACTIVE_IND */ + chan = find_plci(card, msg->msg.connect_active_ind.plci); + if (chan >= 0) + switch (card->bch[chan].fsm_state) { + case ACT2000_STATE_IWAIT: + actcapi_connect_active_resp(card, &card->bch[chan]); + break; + case ACT2000_STATE_OWAIT: + actcapi_connect_active_resp(card, &card->bch[chan]); + actcapi_select_b2_protocol_req(card, &card->bch[chan]); + break; + } + break; + case 0x8202: + /* CONNECT_B3_IND */ + chan = find_plci(card, msg->msg.connect_b3_ind.plci); + if ((chan >= 0) && (card->bch[chan].fsm_state == ACT2000_STATE_IBWAIT)) { + card->bch[chan].ncci = msg->msg.connect_b3_ind.ncci; + actcapi_connect_b3_resp(card, &card->bch[chan], 0); + } else { + ctmp = (act2000_chan *)tmp; + ctmp->ncci = msg->msg.connect_b3_ind.ncci; + actcapi_connect_b3_resp(card, ctmp, 0x11); /* All Card-Cannels busy */ + } + break; + case 0x8302: + /* CONNECT_B3_ACTIVE_IND */ + chan = find_ncci(card, msg->msg.connect_b3_active_ind.ncci); + if ((chan >= 0) && (card->bch[chan].fsm_state == ACT2000_STATE_BWAIT)) { + actcapi_connect_b3_active_resp(card, &card->bch[chan]); + cmd.driver = card->myid; + cmd.command = ISDN_STAT_BCONN; + cmd.arg = chan; + card->interface.statcallb(&cmd); + } + break; + case 0x8402: + /* DISCONNECT_B3_IND */ + chan = find_ncci(card, msg->msg.disconnect_b3_ind.ncci); + if (chan >= 0) { + ctmp = &card->bch[chan]; + actcapi_disconnect_b3_resp(card, ctmp); + switch (ctmp->fsm_state) { + case ACT2000_STATE_ACTIVE: + ctmp->fsm_state = ACT2000_STATE_DHWAIT2; + cmd.driver = card->myid; + cmd.command = ISDN_STAT_BHUP; + cmd.arg = chan; + card->interface.statcallb(&cmd); + break; + case ACT2000_STATE_BHWAIT2: + actcapi_disconnect_req(card, ctmp); + ctmp->fsm_state = ACT2000_STATE_DHWAIT; + cmd.driver = card->myid; + cmd.command = ISDN_STAT_BHUP; + cmd.arg = chan; + card->interface.statcallb(&cmd); + break; + } + } + break; + case 0x0402: + /* DISCONNECT_IND */ + chan = find_plci(card, msg->msg.disconnect_ind.plci); + if (chan >= 0) { + ctmp = &card->bch[chan]; + actcapi_disconnect_resp(card, ctmp); + ctmp->fsm_state = ACT2000_STATE_NULL; + cmd.driver = card->myid; + cmd.command = ISDN_STAT_DHUP; + cmd.arg = chan; + card->interface.statcallb(&cmd); + } else { + ctmp = (act2000_chan *)tmp; + ctmp->plci = msg->msg.disconnect_ind.plci; + actcapi_disconnect_resp(card, ctmp); + } + break; + case 0x4001: + /* SELECT_B2_PROTOCOL_CONF */ + chan = find_plci(card, msg->msg.select_b2_protocol_conf.plci); + if (chan >= 0) + switch (card->bch[chan].fsm_state) { + case ACT2000_STATE_ICALL: + case ACT2000_STATE_OWAIT: + ctmp = &card->bch[chan]; + if (msg->msg.select_b2_protocol_conf.info == 0) + actcapi_select_b3_protocol_req(card, ctmp); + else { + ctmp->fsm_state = ACT2000_STATE_NULL; + cmd.driver = card->myid; + cmd.command = ISDN_STAT_DHUP; + cmd.arg = chan; + card->interface.statcallb(&cmd); + } + break; + } + break; + case 0x8001: + /* SELECT_B3_PROTOCOL_CONF */ + chan = find_plci(card, msg->msg.select_b3_protocol_conf.plci); + if (chan >= 0) + switch (card->bch[chan].fsm_state) { + case ACT2000_STATE_ICALL: + case ACT2000_STATE_OWAIT: + ctmp = &card->bch[chan]; + if (msg->msg.select_b3_protocol_conf.info == 0) + actcapi_listen_b3_req(card, ctmp); + else { + ctmp->fsm_state = ACT2000_STATE_NULL; + cmd.driver = card->myid; + cmd.command = ISDN_STAT_DHUP; + cmd.arg = chan; + card->interface.statcallb(&cmd); + } + } + break; + case 0x8101: + /* LISTEN_B3_CONF */ + chan = find_plci(card, msg->msg.listen_b3_conf.plci); + if (chan >= 0) + switch (card->bch[chan].fsm_state) { + case ACT2000_STATE_ICALL: + ctmp = &card->bch[chan]; + if (msg->msg.listen_b3_conf.info == 0) + actcapi_connect_resp(card, ctmp, 0); + else { + ctmp->fsm_state = ACT2000_STATE_NULL; + cmd.driver = card->myid; + cmd.command = ISDN_STAT_DHUP; + cmd.arg = chan; + card->interface.statcallb(&cmd); + } + break; + case ACT2000_STATE_OWAIT: + ctmp = &card->bch[chan]; + if (msg->msg.listen_b3_conf.info == 0) { + actcapi_connect_b3_req(card, ctmp); + ctmp->fsm_state = ACT2000_STATE_OBWAIT; + cmd.driver = card->myid; + cmd.command = ISDN_STAT_DCONN; + cmd.arg = chan; + card->interface.statcallb(&cmd); + } else { + ctmp->fsm_state = ACT2000_STATE_NULL; + cmd.driver = card->myid; + cmd.command = ISDN_STAT_DHUP; + cmd.arg = chan; + card->interface.statcallb(&cmd); + } + break; + } + break; + case 0x8201: + /* CONNECT_B3_CONF */ + chan = find_plci(card, msg->msg.connect_b3_conf.plci); + if ((chan >= 0) && (card->bch[chan].fsm_state == ACT2000_STATE_OBWAIT)) { + ctmp = &card->bch[chan]; + if (msg->msg.connect_b3_conf.info) { + ctmp->fsm_state = ACT2000_STATE_NULL; + cmd.driver = card->myid; + cmd.command = ISDN_STAT_DHUP; + cmd.arg = chan; + card->interface.statcallb(&cmd); + } else { + ctmp->ncci = msg->msg.connect_b3_conf.ncci; + ctmp->fsm_state = ACT2000_STATE_BWAIT; + } + } + break; + case 0x8401: + /* DISCONNECT_B3_CONF */ + chan = find_ncci(card, msg->msg.disconnect_b3_conf.ncci); + if ((chan >= 0) && (card->bch[chan].fsm_state == ACT2000_STATE_BHWAIT)) + card->bch[chan].fsm_state = ACT2000_STATE_BHWAIT2; + break; + case 0x0702: + /* INFO_IND */ + chan = find_plci(card, msg->msg.info_ind.plci); + if (chan >= 0) + /* TODO: Eval Charging info / cause */ + actcapi_info_resp(card, &card->bch[chan]); + break; + case 0x0401: + /* LISTEN_CONF */ + case 0x0501: + /* LISTEN_CONF */ + case 0xff01: + /* MANUFACTURER_CONF */ + break; + case 0xff02: + /* MANUFACTURER_IND */ + if (msg->msg.manuf_msg == 3) { + memset(tmp, 0, sizeof(tmp)); + strncpy(tmp, + &msg->msg.manufacturer_ind_err.errstring, + msg->hdr.len - 16); + if (msg->msg.manufacturer_ind_err.errcode) + printk(KERN_WARNING "act2000: %s\n", tmp); + else { + printk(KERN_DEBUG "act2000: %s\n", tmp); + if ((!strncmp(tmp, "INFO: Trace buffer con", 22)) || + (!strncmp(tmp, "INFO: Compile Date/Tim", 22))) { + card->flags |= ACT2000_FLAGS_RUNNING; + cmd.command = ISDN_STAT_RUN; + cmd.driver = card->myid; + cmd.arg = 0; + actcapi_manufacturer_req_net(card); + actcapi_manufacturer_req_msn(card); + actcapi_listen_req(card); + card->interface.statcallb(&cmd); + } + } + } + break; + default: + printk(KERN_WARNING "act2000: UNHANDLED Message %04x\n", ccmd); + break; + } + dev_kfree_skb(skb); + } +} + +#ifdef DEBUG_MSG +static void +actcapi_debug_caddr(actcapi_addr *addr) +{ + char tmp[30]; + + printk(KERN_DEBUG " Alen = %d\n", addr->len); + if (addr->len > 0) + printk(KERN_DEBUG " Atnp = 0x%02x\n", addr->tnp); + if (addr->len > 1) { + memset(tmp, 0, 30); + memcpy(tmp, addr->num, addr->len - 1); + printk(KERN_DEBUG " Anum = '%s'\n", tmp); + } +} + +static void +actcapi_debug_ncpi(actcapi_ncpi *ncpi) +{ + printk(KERN_DEBUG " ncpi.len = %d\n", ncpi->len); + if (ncpi->len >= 2) + printk(KERN_DEBUG " ncpi.lic = 0x%04x\n", ncpi->lic); + if (ncpi->len >= 4) + printk(KERN_DEBUG " ncpi.hic = 0x%04x\n", ncpi->hic); + if (ncpi->len >= 6) + printk(KERN_DEBUG " ncpi.ltc = 0x%04x\n", ncpi->ltc); + if (ncpi->len >= 8) + printk(KERN_DEBUG " ncpi.htc = 0x%04x\n", ncpi->htc); + if (ncpi->len >= 10) + printk(KERN_DEBUG " ncpi.loc = 0x%04x\n", ncpi->loc); + if (ncpi->len >= 12) + printk(KERN_DEBUG " ncpi.hoc = 0x%04x\n", ncpi->hoc); + if (ncpi->len >= 13) + printk(KERN_DEBUG " ncpi.mod = %d\n", ncpi->modulo); +} + +static void +actcapi_debug_dlpd(actcapi_dlpd *dlpd) +{ + printk(KERN_DEBUG " dlpd.len = %d\n", dlpd->len); + if (dlpd->len >= 2) + printk(KERN_DEBUG " dlpd.dlen = 0x%04x\n", dlpd->dlen); + if (dlpd->len >= 3) + printk(KERN_DEBUG " dlpd.laa = 0x%02x\n", dlpd->laa); + if (dlpd->len >= 4) + printk(KERN_DEBUG " dlpd.lab = 0x%02x\n", dlpd->lab); + if (dlpd->len >= 5) + printk(KERN_DEBUG " dlpd.modulo = %d\n", dlpd->modulo); + if (dlpd->len >= 6) + printk(KERN_DEBUG " dlpd.win = %d\n", dlpd->win); +} + +#ifdef DEBUG_DUMP_SKB +static void dump_skb(struct sk_buff *skb) { + char tmp[80]; + char *p = skb->data; + char *t = tmp; + int i; + + for (i = 0; i < skb->len; i++) { + t += sprintf(t, "%02x ", *p++ & 0xff); + if ((i & 0x0f) == 8) { + printk(KERN_DEBUG "dump: %s\n", tmp); + t = tmp; + } + } + if (i & 0x07) + printk(KERN_DEBUG "dump: %s\n", tmp); +} +#endif + +void +actcapi_debug_msg(struct sk_buff *skb, int direction) +{ + actcapi_msg *msg = (actcapi_msg *)skb->data; + char *descr; + int i; + char tmp[170]; + +#ifndef DEBUG_DATA_MSG + if (msg->hdr.cmd.cmd == 0x86) + return; +#endif + descr = "INVALID"; +#ifdef DEBUG_DUMP_SKB + dump_skb(skb); +#endif + for (i = 0; i < num_valid_msg; i++) + if ((msg->hdr.cmd.cmd == valid_msg[i].cmd.cmd) && + (msg->hdr.cmd.subcmd == valid_msg[i].cmd.subcmd)) { + descr = valid_msg[i].description; + break; + } + printk(KERN_DEBUG "%s %s msg\n", direction?"Outgoing":"Incoming", descr); + printk(KERN_DEBUG " ApplID = %d\n", msg->hdr.applicationID); + printk(KERN_DEBUG " Len = %d\n", msg->hdr.len); + printk(KERN_DEBUG " MsgNum = 0x%04x\n", msg->hdr.msgnum); + printk(KERN_DEBUG " Cmd = 0x%02x\n", msg->hdr.cmd.cmd); + printk(KERN_DEBUG " SubCmd = 0x%02x\n", msg->hdr.cmd.subcmd); + switch (i) { + case 0: + /* DATA B3 IND */ + printk(KERN_DEBUG " BLOCK = 0x%02x\n", + msg->msg.data_b3_ind.blocknr); + break; + case 2: + /* CONNECT CONF */ + printk(KERN_DEBUG " PLCI = 0x%04x\n", + msg->msg.connect_conf.plci); + printk(KERN_DEBUG " Info = 0x%04x\n", + msg->msg.connect_conf.info); + break; + case 3: + /* CONNECT IND */ + printk(KERN_DEBUG " PLCI = 0x%04x\n", + msg->msg.connect_ind.plci); + printk(KERN_DEBUG " Contr = %d\n", + msg->msg.connect_ind.controller); + printk(KERN_DEBUG " SI1 = %d\n", + msg->msg.connect_ind.si1); + printk(KERN_DEBUG " SI2 = %d\n", + msg->msg.connect_ind.si2); + printk(KERN_DEBUG " EAZ = '%c'\n", + msg->msg.connect_ind.eaz); + actcapi_debug_caddr(&msg->msg.connect_ind.addr); + break; + case 5: + /* CONNECT ACTIVE IND */ + printk(KERN_DEBUG " PLCI = 0x%04x\n", + msg->msg.connect_active_ind.plci); + actcapi_debug_caddr(&msg->msg.connect_active_ind.addr); + break; + case 8: + /* LISTEN CONF */ + printk(KERN_DEBUG " Contr = %d\n", + msg->msg.listen_conf.controller); + printk(KERN_DEBUG " Info = 0x%04x\n", + msg->msg.listen_conf.info); + break; + case 11: + /* INFO IND */ + printk(KERN_DEBUG " PLCI = 0x%04x\n", + msg->msg.info_ind.plci); + printk(KERN_DEBUG " Imsk = 0x%04x\n", + msg->msg.info_ind.nr.mask); + if (msg->hdr.len > 12) { + int l = msg->hdr.len - 12; + int j; + char *p = tmp; + for (j = 0; j < l ; j++) + p += sprintf(p, "%02x ", msg->msg.info_ind.el.display[j]); + printk(KERN_DEBUG " D = '%s'\n", tmp); + } + break; + case 14: + /* SELECT B2 PROTOCOL CONF */ + printk(KERN_DEBUG " PLCI = 0x%04x\n", + msg->msg.select_b2_protocol_conf.plci); + printk(KERN_DEBUG " Info = 0x%04x\n", + msg->msg.select_b2_protocol_conf.info); + break; + case 15: + /* SELECT B3 PROTOCOL CONF */ + printk(KERN_DEBUG " PLCI = 0x%04x\n", + msg->msg.select_b3_protocol_conf.plci); + printk(KERN_DEBUG " Info = 0x%04x\n", + msg->msg.select_b3_protocol_conf.info); + break; + case 16: + /* LISTEN B3 CONF */ + printk(KERN_DEBUG " PLCI = 0x%04x\n", + msg->msg.listen_b3_conf.plci); + printk(KERN_DEBUG " Info = 0x%04x\n", + msg->msg.listen_b3_conf.info); + break; + case 18: + /* CONNECT B3 IND */ + printk(KERN_DEBUG " NCCI = 0x%04x\n", + msg->msg.connect_b3_ind.ncci); + printk(KERN_DEBUG " PLCI = 0x%04x\n", + msg->msg.connect_b3_ind.plci); + actcapi_debug_ncpi(&msg->msg.connect_b3_ind.ncpi); + break; + case 19: + /* CONNECT B3 ACTIVE IND */ + printk(KERN_DEBUG " NCCI = 0x%04x\n", + msg->msg.connect_b3_active_ind.ncci); + actcapi_debug_ncpi(&msg->msg.connect_b3_active_ind.ncpi); + break; + case 26: + /* MANUFACTURER IND */ + printk(KERN_DEBUG " Mmsg = 0x%02x\n", + msg->msg.manufacturer_ind_err.manuf_msg); + switch (msg->msg.manufacturer_ind_err.manuf_msg) { + case 3: + printk(KERN_DEBUG " Contr = %d\n", + msg->msg.manufacturer_ind_err.controller); + printk(KERN_DEBUG " Code = 0x%08x\n", + msg->msg.manufacturer_ind_err.errcode); + memset(tmp, 0, sizeof(tmp)); + strncpy(tmp, &msg->msg.manufacturer_ind_err.errstring, + msg->hdr.len - 16); + printk(KERN_DEBUG " Emsg = '%s'\n", tmp); + break; + } + break; + case 30: + /* LISTEN REQ */ + printk(KERN_DEBUG " Imsk = 0x%08x\n", + msg->msg.listen_req.infomask); + printk(KERN_DEBUG " Emsk = 0x%04x\n", + msg->msg.listen_req.eazmask); + printk(KERN_DEBUG " Smsk = 0x%04x\n", + msg->msg.listen_req.simask); + break; + case 35: + /* SELECT_B2_PROTOCOL_REQ */ + printk(KERN_DEBUG " PLCI = 0x%04x\n", + msg->msg.select_b2_protocol_req.plci); + printk(KERN_DEBUG " prot = 0x%02x\n", + msg->msg.select_b2_protocol_req.protocol); + if (msg->hdr.len >= 11) + printk(KERN_DEBUG "No dlpd\n"); + else + actcapi_debug_dlpd(&msg->msg.select_b2_protocol_req.dlpd); + break; + case 44: + /* CONNECT RESP */ + printk(KERN_DEBUG " PLCI = 0x%04x\n", + msg->msg.connect_resp.plci); + printk(KERN_DEBUG " CAUSE = 0x%02x\n", + msg->msg.connect_resp.rejectcause); + break; + case 45: + /* CONNECT ACTIVE RESP */ + printk(KERN_DEBUG " PLCI = 0x%04x\n", + msg->msg.connect_active_resp.plci); + break; + } +} +#endif diff --git a/drivers/isdn/act2000/capi.h b/drivers/isdn/act2000/capi.h new file mode 100644 index 000000000000..04d2bcdd37a7 --- /dev/null +++ b/drivers/isdn/act2000/capi.h @@ -0,0 +1,366 @@ +/* $Id: capi.h,v 1.6.6.2 2001/09/23 22:24:32 kai Exp $ + * + * ISDN lowlevel-module for the IBM ISDN-S0 Active 2000. + * + * Author Fritz Elfert + * Copyright by Fritz Elfert <fritz@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * Thanks to Friedemann Baitinger and IBM Germany + * + */ + +#ifndef CAPI_H +#define CAPI_H + +/* Command-part of a CAPI message */ +typedef struct actcapi_msgcmd { + __u8 cmd; + __u8 subcmd; +} actcapi_msgcmd; + +/* CAPI message header */ +typedef struct actcapi_msghdr { + __u16 len; + __u16 applicationID; + actcapi_msgcmd cmd; + __u16 msgnum; +} actcapi_msghdr; + +/* CAPI message description (for debugging) */ +typedef struct actcapi_msgdsc { + actcapi_msgcmd cmd; + char *description; +} actcapi_msgdsc; + +/* CAPI Address */ +typedef struct actcapi_addr { + __u8 len; /* Length of element */ + __u8 tnp; /* Type/Numbering Plan */ + __u8 num[20]; /* Caller ID */ +} actcapi_addr; + +/* CAPI INFO element mask */ +typedef union actcapi_infonr { /* info number */ + __u16 mask; /* info-mask field */ + struct bmask { /* bit definitions */ + unsigned codes : 3; /* code set */ + unsigned rsvd : 5; /* reserved */ + unsigned svind : 1; /* single, variable length ind. */ + unsigned wtype : 7; /* W-element type */ + } bmask; +} actcapi_infonr; + +/* CAPI INFO element */ +typedef union actcapi_infoel { /* info element */ + __u8 len; /* length of info element */ + __u8 display[40]; /* display contents */ + __u8 uuinfo[40]; /* User-user info field */ + struct cause { /* Cause information */ + unsigned ext2 : 1; /* extension */ + unsigned cod : 2; /* coding standard */ + unsigned spare : 1; /* spare */ + unsigned loc : 4; /* location */ + unsigned ext1 : 1; /* extension */ + unsigned cval : 7; /* Cause value */ + } cause; + struct charge { /* Charging information */ + __u8 toc; /* type of charging info */ + __u8 unit[10]; /* charging units */ + } charge; + __u8 date[20]; /* date fields */ + __u8 stat; /* state of remote party */ +} actcapi_infoel; + +/* Message for EAZ<->MSN Mapping */ +typedef struct actcapi_msn { + __u8 eaz; + __u8 len; /* Length of MSN */ + __u8 msn[15] __attribute__ ((packed)); +} actcapi_msn; + +typedef struct actcapi_dlpd { + __u8 len; /* Length of structure */ + __u16 dlen __attribute__ ((packed)); /* Data Length */ + __u8 laa __attribute__ ((packed)); /* Link Address A */ + __u8 lab; /* Link Address B */ + __u8 modulo; /* Modulo Mode */ + __u8 win; /* Window size */ + __u8 xid[100]; /* XID Information */ +} actcapi_dlpd; + +typedef struct actcapi_ncpd { + __u8 len; /* Length of structure */ + __u16 lic __attribute__ ((packed)); + __u16 hic __attribute__ ((packed)); + __u16 ltc __attribute__ ((packed)); + __u16 htc __attribute__ ((packed)); + __u16 loc __attribute__ ((packed)); + __u16 hoc __attribute__ ((packed)); + __u8 modulo __attribute__ ((packed)); +} actcapi_ncpd; +#define actcapi_ncpi actcapi_ncpd + +/* + * Layout of NCCI field in a B3 DATA CAPI message is different from + * standard at act2000: + * + * Bit 0-4 = PLCI + * Bit 5-7 = Controller + * Bit 8-15 = NCCI + */ +#define MAKE_NCCI(plci,contr,ncci) \ + ((plci & 0x1f) | ((contr & 0x7) << 5) | ((ncci & 0xff) << 8)) + +#define EVAL_NCCI(fakencci,plci,contr,ncci) { \ + plci = fakencci & 0x1f; \ + contr = (fakencci >> 5) & 0x7; \ + ncci = (fakencci >> 8) & 0xff; \ +} + +/* + * Layout of PLCI field in a B3 DATA CAPI message is different from + * standard at act2000: + * + * Bit 0-4 = PLCI + * Bit 5-7 = Controller + * Bit 8-15 = reserved (must be 0) + */ +#define MAKE_PLCI(plci,contr) \ + ((plci & 0x1f) | ((contr & 0x7) << 5)) + +#define EVAL_PLCI(fakeplci,plci,contr) { \ + plci = fakeplci & 0x1f; \ + contr = (fakeplci >> 5) & 0x7; \ +} + +typedef struct actcapi_msg { + actcapi_msghdr hdr; + union { + __u16 manuf_msg; + struct manufacturer_req_net { + __u16 manuf_msg; + __u16 controller; + __u8 nettype; + } manufacturer_req_net; + struct manufacturer_req_v42 { + __u16 manuf_msg; + __u16 controller; + __u32 v42control; + } manufacturer_req_v42; + struct manufacturer_conf_v42 { + __u16 manuf_msg; + __u16 controller; + } manufacturer_conf_v42; + struct manufacturer_req_err { + __u16 manuf_msg; + __u16 controller; + } manufacturer_req_err; + struct manufacturer_ind_err { + __u16 manuf_msg; + __u16 controller; + __u32 errcode; + __u8 errstring; /* actually up to 160 */ + } manufacturer_ind_err; + struct manufacturer_req_msn { + __u16 manuf_msg; + __u16 controller; + actcapi_msn msnmap; + } manufacturer_req_msn; + /* TODO: TraceInit-req/conf/ind/resp and + * TraceDump-req/conf/ind/resp + */ + struct connect_req { + __u8 controller; + __u8 bchan; + __u32 infomask __attribute__ ((packed)); + __u8 si1; + __u8 si2; + __u8 eaz; + actcapi_addr addr; + } connect_req; + struct connect_conf { + __u16 plci; + __u16 info; + } connect_conf; + struct connect_ind { + __u16 plci; + __u8 controller; + __u8 si1; + __u8 si2; + __u8 eaz; + actcapi_addr addr; + } connect_ind; + struct connect_resp { + __u16 plci; + __u8 rejectcause; + } connect_resp; + struct connect_active_ind { + __u16 plci; + actcapi_addr addr; + } connect_active_ind; + struct connect_active_resp { + __u16 plci; + } connect_active_resp; + struct connect_b3_req { + __u16 plci; + actcapi_ncpi ncpi; + } connect_b3_req; + struct connect_b3_conf { + __u16 plci; + __u16 ncci; + __u16 info; + } connect_b3_conf; + struct connect_b3_ind { + __u16 ncci; + __u16 plci; + actcapi_ncpi ncpi; + } connect_b3_ind; + struct connect_b3_resp { + __u16 ncci; + __u8 rejectcause; + actcapi_ncpi ncpi __attribute__ ((packed)); + } connect_b3_resp; + struct disconnect_req { + __u16 plci; + __u8 cause; + } disconnect_req; + struct disconnect_conf { + __u16 plci; + __u16 info; + } disconnect_conf; + struct disconnect_ind { + __u16 plci; + __u16 info; + } disconnect_ind; + struct disconnect_resp { + __u16 plci; + } disconnect_resp; + struct connect_b3_active_ind { + __u16 ncci; + actcapi_ncpi ncpi; + } connect_b3_active_ind; + struct connect_b3_active_resp { + __u16 ncci; + } connect_b3_active_resp; + struct disconnect_b3_req { + __u16 ncci; + actcapi_ncpi ncpi; + } disconnect_b3_req; + struct disconnect_b3_conf { + __u16 ncci; + __u16 info; + } disconnect_b3_conf; + struct disconnect_b3_ind { + __u16 ncci; + __u16 info; + actcapi_ncpi ncpi; + } disconnect_b3_ind; + struct disconnect_b3_resp { + __u16 ncci; + } disconnect_b3_resp; + struct info_ind { + __u16 plci; + actcapi_infonr nr; + actcapi_infoel el; + } info_ind; + struct info_resp { + __u16 plci; + } info_resp; + struct listen_b3_req { + __u16 plci; + } listen_b3_req; + struct listen_b3_conf { + __u16 plci; + __u16 info; + } listen_b3_conf; + struct select_b2_protocol_req { + __u16 plci; + __u8 protocol; + actcapi_dlpd dlpd __attribute__ ((packed)); + } select_b2_protocol_req; + struct select_b2_protocol_conf { + __u16 plci; + __u16 info; + } select_b2_protocol_conf; + struct select_b3_protocol_req { + __u16 plci; + __u8 protocol; + actcapi_ncpd ncpd __attribute__ ((packed)); + } select_b3_protocol_req; + struct select_b3_protocol_conf { + __u16 plci; + __u16 info; + } select_b3_protocol_conf; + struct listen_req { + __u8 controller; + __u32 infomask __attribute__ ((packed)); + __u16 eazmask __attribute__ ((packed)); + __u16 simask __attribute__ ((packed)); + } listen_req; + struct listen_conf { + __u8 controller; + __u16 info __attribute__ ((packed)); + } listen_conf; + struct data_b3_req { + __u16 fakencci; + __u16 datalen; + __u32 unused; + __u8 blocknr; + __u16 flags __attribute__ ((packed)); + } data_b3_req; + struct data_b3_ind { + __u16 fakencci; + __u16 datalen; + __u32 unused; + __u8 blocknr; + __u16 flags __attribute__ ((packed)); + } data_b3_ind; + struct data_b3_resp { + __u16 ncci; + __u8 blocknr; + } data_b3_resp; + struct data_b3_conf { + __u16 ncci; + __u8 blocknr; + __u16 info __attribute__ ((packed)); + } data_b3_conf; + } msg; +} actcapi_msg; + +extern __inline__ unsigned short +actcapi_nextsmsg(act2000_card *card) +{ + unsigned long flags; + unsigned short n; + + spin_lock_irqsave(&card->mnlock, flags); + n = card->msgnum; + card->msgnum++; + card->msgnum &= 0x7fff; + spin_unlock_irqrestore(&card->mnlock, flags); + return n; +} +#define DEBUG_MSG +#undef DEBUG_DATA_MSG +#undef DEBUG_DUMP_SKB + +extern int actcapi_chkhdr(act2000_card *, actcapi_msghdr *); +extern int actcapi_listen_req(act2000_card *); +extern int actcapi_manufacturer_req_net(act2000_card *); +extern int actcapi_manufacturer_req_v42(act2000_card *, ulong); +extern int actcapi_manufacturer_req_errh(act2000_card *); +extern int actcapi_manufacturer_req_msn(act2000_card *); +extern int actcapi_connect_req(act2000_card *, act2000_chan *, char *, char, int, int); +extern void actcapi_select_b2_protocol_req(act2000_card *, act2000_chan *); +extern void actcapi_disconnect_b3_req(act2000_card *, act2000_chan *); +extern void actcapi_connect_resp(act2000_card *, act2000_chan *, __u8); +extern void actcapi_dispatch(act2000_card *); +#ifdef DEBUG_MSG +extern void actcapi_debug_msg(struct sk_buff *skb, int); +#else +#define actcapi_debug_msg(skb, len) +#endif +#endif diff --git a/drivers/isdn/act2000/module.c b/drivers/isdn/act2000/module.c new file mode 100644 index 000000000000..d89dcde4eade --- /dev/null +++ b/drivers/isdn/act2000/module.c @@ -0,0 +1,808 @@ +/* $Id: module.c,v 1.14.6.4 2001/09/23 22:24:32 kai Exp $ + * + * ISDN lowlevel-module for the IBM ISDN-S0 Active 2000. + * + * Author Fritz Elfert + * Copyright by Fritz Elfert <fritz@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * Thanks to Friedemann Baitinger and IBM Germany + * + */ + +#include "act2000.h" +#include "act2000_isa.h" +#include "capi.h" +#include <linux/module.h> +#include <linux/init.h> + +static unsigned short act2000_isa_ports[] = +{ + 0x0200, 0x0240, 0x0280, 0x02c0, 0x0300, 0x0340, 0x0380, + 0xcfe0, 0xcfa0, 0xcf60, 0xcf20, 0xcee0, 0xcea0, 0xce60, +}; +#define ISA_NRPORTS (sizeof(act2000_isa_ports)/sizeof(unsigned short)) + +static act2000_card *cards = (act2000_card *) NULL; + +/* Parameters to be set by insmod */ +static int act_bus = 0; +static int act_port = -1; /* -1 = Autoprobe */ +static int act_irq = -1; +static char *act_id = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; + +MODULE_DESCRIPTION( "ISDN4Linux: Driver for IBM Active 2000 ISDN card"); +MODULE_AUTHOR( "Fritz Elfert"); +MODULE_LICENSE( "GPL"); +MODULE_PARM_DESC(act_bus, "BusType of first card, 1=ISA, 2=MCA, 3=PCMCIA, currently only ISA"); +MODULE_PARM_DESC(membase, "Base port address of first card"); +MODULE_PARM_DESC(act_irq, "IRQ of first card"); +MODULE_PARM_DESC(act_id, "ID-String of first card"); +module_param(act_bus, int, 0); +module_param(act_port, int, 0); +module_param(act_irq, int, 0); +module_param(act_id, charp, 0); + +static int act2000_addcard(int, int, int, char *); + +static act2000_chan * +find_channel(act2000_card *card, int channel) +{ + if ((channel >= 0) && (channel < ACT2000_BCH)) + return &(card->bch[channel]); + printk(KERN_WARNING "act2000: Invalid channel %d\n", channel); + return NULL; +} + +/* + * Free MSN list + */ +static void +act2000_clear_msn(act2000_card *card) +{ + struct msn_entry *p = card->msn_list; + struct msn_entry *q; + unsigned long flags; + + spin_lock_irqsave(&card->lock, flags); + card->msn_list = NULL; + spin_unlock_irqrestore(&card->lock, flags); + while (p) { + q = p->next; + kfree(p); + p = q; + } +} + +/* + * Find an MSN entry in the list. + * If ia5 != 0, return IA5-encoded EAZ, else + * return a bitmask with corresponding bit set. + */ +static __u16 +act2000_find_msn(act2000_card *card, char *msn, int ia5) +{ + struct msn_entry *p = card->msn_list; + __u8 eaz = '0'; + + while (p) { + if (!strcmp(p->msn, msn)) { + eaz = p->eaz; + break; + } + p = p->next; + } + if (!ia5) + return (1 << (eaz - '0')); + else + return eaz; +} + +/* + * Find an EAZ entry in the list. + * return a string with corresponding msn. + */ +char * +act2000_find_eaz(act2000_card *card, char eaz) +{ + struct msn_entry *p = card->msn_list; + + while (p) { + if (p->eaz == eaz) + return(p->msn); + p = p->next; + } + return("\0"); +} + +/* + * Add or delete an MSN to the MSN list + * + * First character of msneaz is EAZ, rest is MSN. + * If length of eazmsn is 1, delete that entry. + */ +static int +act2000_set_msn(act2000_card *card, char *eazmsn) +{ + struct msn_entry *p = card->msn_list; + struct msn_entry *q = NULL; + unsigned long flags; + int i; + + if (!strlen(eazmsn)) + return 0; + if (strlen(eazmsn) > 16) + return -EINVAL; + for (i = 0; i < strlen(eazmsn); i++) + if (!isdigit(eazmsn[i])) + return -EINVAL; + if (strlen(eazmsn) == 1) { + /* Delete a single MSN */ + while (p) { + if (p->eaz == eazmsn[0]) { + spin_lock_irqsave(&card->lock, flags); + if (q) + q->next = p->next; + else + card->msn_list = p->next; + spin_unlock_irqrestore(&card->lock, flags); + kfree(p); + printk(KERN_DEBUG + "Mapping for EAZ %c deleted\n", + eazmsn[0]); + return 0; + } + q = p; + p = p->next; + } + return 0; + } + /* Add a single MSN */ + while (p) { + /* Found in list, replace MSN */ + if (p->eaz == eazmsn[0]) { + spin_lock_irqsave(&card->lock, flags); + strcpy(p->msn, &eazmsn[1]); + spin_unlock_irqrestore(&card->lock, flags); + printk(KERN_DEBUG + "Mapping for EAZ %c changed to %s\n", + eazmsn[0], + &eazmsn[1]); + return 0; + } + p = p->next; + } + /* Not found in list, add new entry */ + p = kmalloc(sizeof(msn_entry), GFP_KERNEL); + if (!p) + return -ENOMEM; + p->eaz = eazmsn[0]; + strcpy(p->msn, &eazmsn[1]); + p->next = card->msn_list; + spin_lock_irqsave(&card->lock, flags); + card->msn_list = p; + spin_unlock_irqrestore(&card->lock, flags); + printk(KERN_DEBUG + "Mapping %c -> %s added\n", + eazmsn[0], + &eazmsn[1]); + return 0; +} + +static void +act2000_transmit(struct act2000_card *card) +{ + switch (card->bus) { + case ACT2000_BUS_ISA: + act2000_isa_send(card); + break; + case ACT2000_BUS_PCMCIA: + case ACT2000_BUS_MCA: + default: + printk(KERN_WARNING + "act2000_transmit: Illegal bustype %d\n", card->bus); + } +} + +static void +act2000_receive(struct act2000_card *card) +{ + switch (card->bus) { + case ACT2000_BUS_ISA: + act2000_isa_receive(card); + break; + case ACT2000_BUS_PCMCIA: + case ACT2000_BUS_MCA: + default: + printk(KERN_WARNING + "act2000_receive: Illegal bustype %d\n", card->bus); + } +} + +static void +act2000_poll(unsigned long data) +{ + act2000_card * card = (act2000_card *)data; + unsigned long flags; + + act2000_receive(card); + spin_lock_irqsave(&card->lock, flags); + mod_timer(&card->ptimer, jiffies+3); + spin_unlock_irqrestore(&card->lock, flags); +} + +static int +act2000_command(act2000_card * card, isdn_ctrl * c) +{ + ulong a; + act2000_chan *chan; + act2000_cdef cdef; + isdn_ctrl cmd; + char tmp[17]; + int ret; + unsigned long flags; + void __user *arg; + + switch (c->command) { + case ISDN_CMD_IOCTL: + memcpy(&a, c->parm.num, sizeof(ulong)); + arg = (void __user *)a; + switch (c->arg) { + case ACT2000_IOCTL_LOADBOOT: + switch (card->bus) { + case ACT2000_BUS_ISA: + ret = act2000_isa_download(card, + arg); + if (!ret) { + card->flags |= ACT2000_FLAGS_LOADED; + if (!(card->flags & ACT2000_FLAGS_IVALID)) { + card->ptimer.expires = jiffies + 3; + card->ptimer.function = act2000_poll; + card->ptimer.data = (unsigned long)card; + add_timer(&card->ptimer); + } + actcapi_manufacturer_req_errh(card); + } + break; + default: + printk(KERN_WARNING + "act2000: Illegal BUS type %d\n", + card->bus); + ret = -EIO; + } + return ret; + case ACT2000_IOCTL_SETPROTO: + card->ptype = a?ISDN_PTYPE_EURO:ISDN_PTYPE_1TR6; + if (!(card->flags & ACT2000_FLAGS_RUNNING)) + return 0; + actcapi_manufacturer_req_net(card); + return 0; + case ACT2000_IOCTL_SETMSN: + if (copy_from_user(tmp, arg, + sizeof(tmp))) + return -EFAULT; + if ((ret = act2000_set_msn(card, tmp))) + return ret; + if (card->flags & ACT2000_FLAGS_RUNNING) + return(actcapi_manufacturer_req_msn(card)); + return 0; + case ACT2000_IOCTL_ADDCARD: + if (copy_from_user(&cdef, arg, + sizeof(cdef))) + return -EFAULT; + if (act2000_addcard(cdef.bus, cdef.port, cdef.irq, cdef.id)) + return -EIO; + return 0; + case ACT2000_IOCTL_TEST: + if (!(card->flags & ACT2000_FLAGS_RUNNING)) + return -ENODEV; + return 0; + default: + return -EINVAL; + } + break; + case ISDN_CMD_DIAL: + if (!card->flags & ACT2000_FLAGS_RUNNING) + return -ENODEV; + if (!(chan = find_channel(card, c->arg & 0x0f))) + break; + spin_lock_irqsave(&card->lock, flags); + if (chan->fsm_state != ACT2000_STATE_NULL) { + spin_unlock_irqrestore(&card->lock, flags); + printk(KERN_WARNING "Dial on channel with state %d\n", + chan->fsm_state); + return -EBUSY; + } + if (card->ptype == ISDN_PTYPE_EURO) + tmp[0] = act2000_find_msn(card, c->parm.setup.eazmsn, 1); + else + tmp[0] = c->parm.setup.eazmsn[0]; + chan->fsm_state = ACT2000_STATE_OCALL; + chan->callref = 0xffff; + spin_unlock_irqrestore(&card->lock, flags); + ret = actcapi_connect_req(card, chan, c->parm.setup.phone, + tmp[0], c->parm.setup.si1, + c->parm.setup.si2); + if (ret) { + cmd.driver = card->myid; + cmd.command = ISDN_STAT_DHUP; + cmd.arg &= 0x0f; + card->interface.statcallb(&cmd); + } + return ret; + case ISDN_CMD_ACCEPTD: + if (!card->flags & ACT2000_FLAGS_RUNNING) + return -ENODEV; + if (!(chan = find_channel(card, c->arg & 0x0f))) + break; + if (chan->fsm_state == ACT2000_STATE_ICALL) + actcapi_select_b2_protocol_req(card, chan); + return 0; + case ISDN_CMD_ACCEPTB: + if (!card->flags & ACT2000_FLAGS_RUNNING) + return -ENODEV; + return 0; + case ISDN_CMD_HANGUP: + if (!card->flags & ACT2000_FLAGS_RUNNING) + return -ENODEV; + if (!(chan = find_channel(card, c->arg & 0x0f))) + break; + switch (chan->fsm_state) { + case ACT2000_STATE_ICALL: + case ACT2000_STATE_BSETUP: + actcapi_connect_resp(card, chan, 0x15); + break; + case ACT2000_STATE_ACTIVE: + actcapi_disconnect_b3_req(card, chan); + break; + } + return 0; + case ISDN_CMD_SETEAZ: + if (!card->flags & ACT2000_FLAGS_RUNNING) + return -ENODEV; + if (!(chan = find_channel(card, c->arg & 0x0f))) + break; + if (strlen(c->parm.num)) { + if (card->ptype == ISDN_PTYPE_EURO) { + chan->eazmask = act2000_find_msn(card, c->parm.num, 0); + } + if (card->ptype == ISDN_PTYPE_1TR6) { + int i; + chan->eazmask = 0; + for (i = 0; i < strlen(c->parm.num); i++) + if (isdigit(c->parm.num[i])) + chan->eazmask |= (1 << (c->parm.num[i] - '0')); + } + } else + chan->eazmask = 0x3ff; + actcapi_listen_req(card); + return 0; + case ISDN_CMD_CLREAZ: + if (!card->flags & ACT2000_FLAGS_RUNNING) + return -ENODEV; + if (!(chan = find_channel(card, c->arg & 0x0f))) + break; + chan->eazmask = 0; + actcapi_listen_req(card); + return 0; + case ISDN_CMD_SETL2: + if (!card->flags & ACT2000_FLAGS_RUNNING) + return -ENODEV; + if (!(chan = find_channel(card, c->arg & 0x0f))) + break; + chan->l2prot = (c->arg >> 8); + return 0; + case ISDN_CMD_SETL3: + if (!card->flags & ACT2000_FLAGS_RUNNING) + return -ENODEV; + if ((c->arg >> 8) != ISDN_PROTO_L3_TRANS) { + printk(KERN_WARNING "L3 protocol unknown\n"); + return -1; + } + if (!(chan = find_channel(card, c->arg & 0x0f))) + break; + chan->l3prot = (c->arg >> 8); + return 0; + } + + return -EINVAL; +} + +static int +act2000_sendbuf(act2000_card *card, int channel, int ack, struct sk_buff *skb) +{ + struct sk_buff *xmit_skb; + int len; + act2000_chan *chan; + actcapi_msg *msg; + + if (!(chan = find_channel(card, channel))) + return -1; + if (chan->fsm_state != ACT2000_STATE_ACTIVE) + return -1; + len = skb->len; + if ((chan->queued + len) >= ACT2000_MAX_QUEUED) + return 0; + if (!len) + return 0; + if (skb_headroom(skb) < 19) { + printk(KERN_WARNING "act2000_sendbuf: Headroom only %d\n", + skb_headroom(skb)); + xmit_skb = alloc_skb(len + 19, GFP_ATOMIC); + if (!xmit_skb) { + printk(KERN_WARNING "act2000_sendbuf: Out of memory\n"); + return 0; + } + skb_reserve(xmit_skb, 19); + memcpy(skb_put(xmit_skb, len), skb->data, len); + } else { + xmit_skb = skb_clone(skb, GFP_ATOMIC); + if (!xmit_skb) { + printk(KERN_WARNING "act2000_sendbuf: Out of memory\n"); + return 0; + } + } + dev_kfree_skb(skb); + msg = (actcapi_msg *)skb_push(xmit_skb, 19); + msg->hdr.len = 19 + len; + msg->hdr.applicationID = 1; + msg->hdr.cmd.cmd = 0x86; + msg->hdr.cmd.subcmd = 0x00; + msg->hdr.msgnum = actcapi_nextsmsg(card); + msg->msg.data_b3_req.datalen = len; + msg->msg.data_b3_req.blocknr = (msg->hdr.msgnum & 0xff); + msg->msg.data_b3_req.fakencci = MAKE_NCCI(chan->plci, 0, chan->ncci); + msg->msg.data_b3_req.flags = ack; /* Will be set to 0 on actual sending */ + actcapi_debug_msg(xmit_skb, 1); + chan->queued += len; + skb_queue_tail(&card->sndq, xmit_skb); + act2000_schedule_tx(card); + return len; +} + + +/* Read the Status-replies from the Interface */ +static int +act2000_readstatus(u_char __user * buf, int len, act2000_card * card) +{ + int count; + u_char __user *p; + + for (p = buf, count = 0; count < len; p++, count++) { + if (card->status_buf_read == card->status_buf_write) + return count; + put_user(*card->status_buf_read++, p); + if (card->status_buf_read > card->status_buf_end) + card->status_buf_read = card->status_buf; + } + return count; +} + +/* + * Find card with given driverId + */ +static inline act2000_card * +act2000_findcard(int driverid) +{ + act2000_card *p = cards; + + while (p) { + if (p->myid == driverid) + return p; + p = p->next; + } + return (act2000_card *) 0; +} + +/* + * Wrapper functions for interface to linklevel + */ +static int +if_command(isdn_ctrl * c) +{ + act2000_card *card = act2000_findcard(c->driver); + + if (card) + return (act2000_command(card, c)); + printk(KERN_ERR + "act2000: if_command %d called with invalid driverId %d!\n", + c->command, c->driver); + return -ENODEV; +} + +static int +if_writecmd(const u_char __user *buf, int len, int id, int channel) +{ + act2000_card *card = act2000_findcard(id); + + if (card) { + if (!card->flags & ACT2000_FLAGS_RUNNING) + return -ENODEV; + return (len); + } + printk(KERN_ERR + "act2000: if_writecmd called with invalid driverId!\n"); + return -ENODEV; +} + +static int +if_readstatus(u_char __user * buf, int len, int id, int channel) +{ + act2000_card *card = act2000_findcard(id); + + if (card) { + if (!card->flags & ACT2000_FLAGS_RUNNING) + return -ENODEV; + return (act2000_readstatus(buf, len, card)); + } + printk(KERN_ERR + "act2000: if_readstatus called with invalid driverId!\n"); + return -ENODEV; +} + +static int +if_sendbuf(int id, int channel, int ack, struct sk_buff *skb) +{ + act2000_card *card = act2000_findcard(id); + + if (card) { + if (!card->flags & ACT2000_FLAGS_RUNNING) + return -ENODEV; + return (act2000_sendbuf(card, channel, ack, skb)); + } + printk(KERN_ERR + "act2000: if_sendbuf called with invalid driverId!\n"); + return -ENODEV; +} + + +/* + * Allocate a new card-struct, initialize it + * link it into cards-list. + */ +static void +act2000_alloccard(int bus, int port, int irq, char *id) +{ + int i; + act2000_card *card; + if (!(card = (act2000_card *) kmalloc(sizeof(act2000_card), GFP_KERNEL))) { + printk(KERN_WARNING + "act2000: (%s) Could not allocate card-struct.\n", id); + return; + } + memset((char *) card, 0, sizeof(act2000_card)); + spin_lock_init(&card->lock); + spin_lock_init(&card->mnlock); + skb_queue_head_init(&card->sndq); + skb_queue_head_init(&card->rcvq); + skb_queue_head_init(&card->ackq); + INIT_WORK(&card->snd_tq, (void *) (void *) act2000_transmit, card); + INIT_WORK(&card->rcv_tq, (void *) (void *) actcapi_dispatch, card); + INIT_WORK(&card->poll_tq, (void *) (void *) act2000_receive, card); + init_timer(&card->ptimer); + card->interface.owner = THIS_MODULE; + card->interface.channels = ACT2000_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->interface.hl_hdrlen = 20; + card->ptype = ISDN_PTYPE_EURO; + strlcpy(card->interface.id, id, sizeof(card->interface.id)); + for (i=0; i<ACT2000_BCH; i++) { + card->bch[i].plci = 0x8000; + card->bch[i].ncci = 0x8000; + card->bch[i].l2prot = ISDN_PROTO_L2_X75I; + card->bch[i].l3prot = ISDN_PROTO_L3_TRANS; + } + card->myid = -1; + card->bus = bus; + card->port = port; + card->irq = irq; + card->next = cards; + cards = card; +} + +/* + * register card at linklevel + */ +static int +act2000_registercard(act2000_card * card) +{ + switch (card->bus) { + case ACT2000_BUS_ISA: + break; + case ACT2000_BUS_MCA: + case ACT2000_BUS_PCMCIA: + default: + printk(KERN_WARNING + "act2000: Illegal BUS type %d\n", + card->bus); + return -1; + } + if (!register_isdn(&card->interface)) { + printk(KERN_WARNING + "act2000: Unable to register %s\n", + card->interface.id); + return -1; + } + card->myid = card->interface.channels; + sprintf(card->regname, "act2000-isdn (%s)", card->interface.id); + return 0; +} + +static void +unregister_card(act2000_card * card) +{ + isdn_ctrl cmd; + + cmd.command = ISDN_STAT_UNLOAD; + cmd.driver = card->myid; + card->interface.statcallb(&cmd); + switch (card->bus) { + case ACT2000_BUS_ISA: + act2000_isa_release(card); + break; + case ACT2000_BUS_MCA: + case ACT2000_BUS_PCMCIA: + default: + printk(KERN_WARNING + "act2000: Invalid BUS type %d\n", + card->bus); + break; + } +} + +static int +act2000_addcard(int bus, int port, int irq, char *id) +{ + act2000_card *p; + act2000_card *q = NULL; + int initialized; + int added = 0; + int failed = 0; + int i; + + if (!bus) + bus = ACT2000_BUS_ISA; + if (port != -1) { + /* Port defined, do fixed setup */ + act2000_alloccard(bus, port, irq, id); + } else { + /* No port defined, perform autoprobing. + * This may result in more than one card detected. + */ + switch (bus) { + case ACT2000_BUS_ISA: + for (i = 0; i < ISA_NRPORTS; i++) + if (act2000_isa_detect(act2000_isa_ports[i])) { + printk(KERN_INFO + "act2000: Detected ISA card at port 0x%x\n", + act2000_isa_ports[i]); + act2000_alloccard(bus, act2000_isa_ports[i], irq, id); + } + break; + case ACT2000_BUS_MCA: + case ACT2000_BUS_PCMCIA: + default: + printk(KERN_WARNING + "act2000: addcard: Invalid BUS type %d\n", + bus); + } + } + if (!cards) + return 1; + p = cards; + while (p) { + initialized = 0; + if (!p->interface.statcallb) { + /* Not yet registered. + * Try to register and activate it. + */ + added++; + switch (p->bus) { + case ACT2000_BUS_ISA: + if (act2000_isa_detect(p->port)) { + if (act2000_registercard(p)) + break; + if (act2000_isa_config_port(p, p->port)) { + printk(KERN_WARNING + "act2000: Could not request port 0x%04x\n", + p->port); + unregister_card(p); + p->interface.statcallb = NULL; + break; + } + if (act2000_isa_config_irq(p, p->irq)) { + printk(KERN_INFO + "act2000: No IRQ available, fallback to polling\n"); + /* Fall back to polled operation */ + p->irq = 0; + } + printk(KERN_INFO + "act2000: ISA" + "-type card at port " + "0x%04x ", + p->port); + if (p->irq) + printk("irq %d\n", p->irq); + else + printk("polled\n"); + initialized = 1; + } + break; + case ACT2000_BUS_MCA: + case ACT2000_BUS_PCMCIA: + default: + printk(KERN_WARNING + "act2000: addcard: Invalid BUS type %d\n", + p->bus); + } + } else + /* Card already initialized */ + initialized = 1; + if (initialized) { + /* Init OK, next card ... */ + q = p; + p = p->next; + } else { + /* Init failed, remove card from list, free memory */ + printk(KERN_WARNING + "act2000: Initialization of %s failed\n", + p->interface.id); + if (q) { + q->next = p->next; + kfree(p); + p = q->next; + } else { + cards = p->next; + kfree(p); + p = cards; + } + failed++; + } + } + return (added - failed); +} + +#define DRIVERNAME "IBM Active 2000 ISDN driver" + +static int __init act2000_init(void) +{ + printk(KERN_INFO "%s\n", DRIVERNAME); + if (!cards) + act2000_addcard(act_bus, act_port, act_irq, act_id); + if (!cards) + printk(KERN_INFO "act2000: No cards defined yet\n"); + return 0; +} + +static void __exit act2000_exit(void) +{ + act2000_card *card = cards; + act2000_card *last; + while (card) { + unregister_card(card); + del_timer(&card->ptimer); + card = card->next; + } + card = cards; + while (card) { + last = card; + card = card->next; + act2000_clear_msn(last); + kfree(last); + } + printk(KERN_INFO "%s unloaded\n", DRIVERNAME); +} + +module_init(act2000_init); +module_exit(act2000_exit); diff --git a/drivers/isdn/capi/Kconfig b/drivers/isdn/capi/Kconfig new file mode 100644 index 000000000000..8b6c9a431ffa --- /dev/null +++ b/drivers/isdn/capi/Kconfig @@ -0,0 +1,53 @@ +# +# Config.in for the CAPI subsystem +# +config ISDN_DRV_AVMB1_VERBOSE_REASON + bool "Verbose reason code reporting (kernel size +=7K)" + depends on ISDN_CAPI + help + If you say Y here, the AVM B1 driver will give verbose reasons for + disconnecting. This will increase the size of the kernel by 7 KB. If + unsure, say Y. + +config ISDN_CAPI_MIDDLEWARE + bool "CAPI2.0 Middleware support (EXPERIMENTAL)" + depends on ISDN_CAPI && EXPERIMENTAL + help + This option will enhance the capabilities of the /dev/capi20 + interface. It will provide a means of moving a data connection, + established via the usual /dev/capi20 interface to a special tty + device. If you want to use pppd with pppdcapiplugin to dial up to + your ISP, say Y here. + +config ISDN_CAPI_CAPI20 + tristate "CAPI2.0 /dev/capi support" + depends on ISDN_CAPI + help + This option will provide the CAPI 2.0 interface to userspace + applications via /dev/capi20. Applications should use the + standardized libcapi20 to access this functionality. You should say + Y/M here. + +config ISDN_CAPI_CAPIFS_BOOL + bool "CAPI2.0 filesystem support" + depends on ISDN_CAPI_MIDDLEWARE && ISDN_CAPI_CAPI20 + +config ISDN_CAPI_CAPIFS + tristate + depends on ISDN_CAPI_CAPIFS_BOOL + default ISDN_CAPI_CAPI20 + help + This option provides a special file system, similar to /dev/pts with + device nodes for the special ttys established by using the + middleware extension above. If you want to use pppd with + pppdcapiplugin to dial up to your ISP, say Y here. + +config ISDN_CAPI_CAPIDRV + tristate "CAPI2.0 capidrv interface support" + depends on ISDN_CAPI && ISDN_I4L + help + This option provides the glue code to hook up CAPI driven cards to + the legacy isdn4linux link layer. If you have a card which is + supported by a CAPI driver, but still want to use old features like + ippp interfaces or ttyI emulation, say Y/M here. + diff --git a/drivers/isdn/capi/Makefile b/drivers/isdn/capi/Makefile new file mode 100644 index 000000000000..57123e3e4978 --- /dev/null +++ b/drivers/isdn/capi/Makefile @@ -0,0 +1,15 @@ +# Makefile for the CAPI subsystem. + +# Ordering constraints: kernelcapi.o first + +# Each configuration option enables a list of files. + +obj-$(CONFIG_ISDN_CAPI) += kernelcapi.o +obj-$(CONFIG_ISDN_CAPI_CAPI20) += capi.o +obj-$(CONFIG_ISDN_CAPI_CAPIDRV) += capidrv.o +obj-$(CONFIG_ISDN_CAPI_CAPIFS) += capifs.o + +# Multipart objects. + +kernelcapi-y := kcapi.o capiutil.o capilib.o +kernelcapi-$(CONFIG_PROC_FS) += kcapi_proc.o diff --git a/drivers/isdn/capi/capi.c b/drivers/isdn/capi/capi.c new file mode 100644 index 000000000000..06163538bb20 --- /dev/null +++ b/drivers/isdn/capi/capi.c @@ -0,0 +1,1554 @@ +/* $Id: capi.c,v 1.1.2.7 2004/04/28 09:48:59 armin Exp $ + * + * CAPI 2.0 Interface for Linux + * + * Copyright 1996 by Carsten Paeth <calle@calle.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/fcntl.h> +#include <linux/fs.h> +#include <linux/signal.h> +#include <linux/mm.h> +#include <linux/smp_lock.h> +#include <linux/timer.h> +#include <linux/wait.h> +#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE +#include <linux/tty.h> +#ifdef CONFIG_PPP +#include <linux/netdevice.h> +#include <linux/ppp_defs.h> +#include <linux/if_ppp.h> +#endif /* CONFIG_PPP */ +#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */ +#include <linux/skbuff.h> +#include <linux/proc_fs.h> +#include <linux/poll.h> +#include <linux/capi.h> +#include <linux/kernelcapi.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/moduleparam.h> +#include <linux/devfs_fs_kernel.h> +#include <linux/isdn/capiutil.h> +#include <linux/isdn/capicmd.h> +#if defined(CONFIG_ISDN_CAPI_CAPIFS) || defined(CONFIG_ISDN_CAPI_CAPIFS_MODULE) +#include "capifs.h" +#endif + +static char *revision = "$Revision: 1.1.2.7 $"; + +MODULE_DESCRIPTION("CAPI4Linux: Userspace /dev/capi20 interface"); +MODULE_AUTHOR("Carsten Paeth"); +MODULE_LICENSE("GPL"); + +#undef _DEBUG_REFCOUNT /* alloc/free and open/close debug */ +#undef _DEBUG_TTYFUNCS /* call to tty_driver */ +#undef _DEBUG_DATAFLOW /* data flow */ + +/* -------- driver information -------------------------------------- */ + +static struct class_simple *capi_class; + +int capi_major = 68; /* allocated */ +#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE +#define CAPINC_NR_PORTS 32 +#define CAPINC_MAX_PORTS 256 +int capi_ttymajor = 191; +int capi_ttyminors = CAPINC_NR_PORTS; +#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */ + +module_param_named(major, capi_major, uint, 0); +#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE +module_param_named(ttymajor, capi_ttymajor, uint, 0); +module_param_named(ttyminors, capi_ttyminors, uint, 0); +#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */ + +/* -------- defines ------------------------------------------------- */ + +#define CAPINC_MAX_RECVQUEUE 10 +#define CAPINC_MAX_SENDQUEUE 10 +#define CAPI_MAX_BLKSIZE 2048 + +/* -------- data structures ----------------------------------------- */ + +struct capidev; +struct capincci; +#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE +struct capiminor; + +struct capiminor { + struct list_head list; + struct capincci *nccip; + unsigned int minor; + + struct capi20_appl *ap; + u32 ncci; + u16 datahandle; + u16 msgid; + + struct tty_struct *tty; + int ttyinstop; + int ttyoutstop; + struct sk_buff *ttyskb; + atomic_t ttyopencount; + + struct sk_buff_head inqueue; + int inbytes; + struct sk_buff_head outqueue; + int outbytes; + + /* transmit path */ + struct datahandle_queue { + struct datahandle_queue *next; + u16 datahandle; + } *ackqueue; + int nack; + +}; +#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */ + +struct capincci { + struct capincci *next; + u32 ncci; + struct capidev *cdev; +#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE + struct capiminor *minorp; +#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */ +}; + +struct capidev { + struct list_head list; + struct capi20_appl ap; + u16 errcode; + unsigned userflags; + + struct sk_buff_head recvqueue; + wait_queue_head_t recvwait; + + struct capincci *nccis; + + struct semaphore ncci_list_sem; +}; + +/* -------- global variables ---------------------------------------- */ + +static DEFINE_RWLOCK(capidev_list_lock); +static LIST_HEAD(capidev_list); + +#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE +static DEFINE_RWLOCK(capiminor_list_lock); +static LIST_HEAD(capiminor_list); +#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */ + +#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE +/* -------- datahandles --------------------------------------------- */ + +static int capincci_add_ack(struct capiminor *mp, u16 datahandle) +{ + struct datahandle_queue *n, **pp; + + n = kmalloc(sizeof(*n), GFP_ATOMIC); + if (!n) { + printk(KERN_ERR "capi: alloc datahandle failed\n"); + return -1; + } + n->next = NULL; + n->datahandle = datahandle; + for (pp = &mp->ackqueue; *pp; pp = &(*pp)->next) ; + *pp = n; + mp->nack++; + return 0; +} + +static int capiminor_del_ack(struct capiminor *mp, u16 datahandle) +{ + struct datahandle_queue **pp, *p; + + for (pp = &mp->ackqueue; *pp; pp = &(*pp)->next) { + if ((*pp)->datahandle == datahandle) { + p = *pp; + *pp = (*pp)->next; + kfree(p); + mp->nack--; + return 0; + } + } + return -1; +} + +static void capiminor_del_all_ack(struct capiminor *mp) +{ + struct datahandle_queue **pp, *p; + + pp = &mp->ackqueue; + while (*pp) { + p = *pp; + *pp = (*pp)->next; + kfree(p); + mp->nack--; + } +} + + +/* -------- struct capiminor ---------------------------------------- */ + +static struct capiminor *capiminor_alloc(struct capi20_appl *ap, u32 ncci) +{ + struct capiminor *mp, *p; + unsigned int minor = 0; + unsigned long flags; + + mp = kmalloc(sizeof(*mp), GFP_ATOMIC); + if (!mp) { + printk(KERN_ERR "capi: can't alloc capiminor\n"); + return NULL; + } + + memset(mp, 0, sizeof(struct capiminor)); + mp->ap = ap; + mp->ncci = ncci; + mp->msgid = 0; + atomic_set(&mp->ttyopencount,0); + + skb_queue_head_init(&mp->inqueue); + skb_queue_head_init(&mp->outqueue); + + /* Allocate the least unused minor number. + */ + write_lock_irqsave(&capiminor_list_lock, flags); + if (list_empty(&capiminor_list)) + list_add(&mp->list, &capiminor_list); + else { + list_for_each_entry(p, &capiminor_list, list) { + if (p->minor > minor) + break; + minor++; + } + + if (minor < capi_ttyminors) { + mp->minor = minor; + list_add(&mp->list, p->list.prev); + } + } + write_unlock_irqrestore(&capiminor_list_lock, flags); + + if (!(minor < capi_ttyminors)) { + printk(KERN_NOTICE "capi: out of minors\n"); + kfree(mp); + return NULL; + } + + return mp; +} + +static void capiminor_free(struct capiminor *mp) +{ + unsigned long flags; + + write_lock_irqsave(&capiminor_list_lock, flags); + list_del(&mp->list); + write_unlock_irqrestore(&capiminor_list_lock, flags); + + if (mp->ttyskb) kfree_skb(mp->ttyskb); + mp->ttyskb = NULL; + skb_queue_purge(&mp->inqueue); + skb_queue_purge(&mp->outqueue); + capiminor_del_all_ack(mp); + kfree(mp); +} + +struct capiminor *capiminor_find(unsigned int minor) +{ + struct list_head *l; + struct capiminor *p = NULL; + + read_lock(&capiminor_list_lock); + list_for_each(l, &capiminor_list) { + p = list_entry(l, struct capiminor, list); + if (p->minor == minor) + break; + } + read_unlock(&capiminor_list_lock); + if (l == &capiminor_list) + return NULL; + + return p; +} +#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */ + +/* -------- struct capincci ----------------------------------------- */ + +static struct capincci *capincci_alloc(struct capidev *cdev, u32 ncci) +{ + struct capincci *np, **pp; +#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE + struct capiminor *mp = NULL; +#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */ + + np = kmalloc(sizeof(*np), GFP_ATOMIC); + if (!np) + return NULL; + memset(np, 0, sizeof(struct capincci)); + np->ncci = ncci; + np->cdev = cdev; +#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE + mp = NULL; + if (cdev->userflags & CAPIFLAG_HIGHJACKING) + mp = np->minorp = capiminor_alloc(&cdev->ap, ncci); + if (mp) { + mp->nccip = np; +#ifdef _DEBUG_REFCOUNT + printk(KERN_DEBUG "set mp->nccip\n"); +#endif +#if defined(CONFIG_ISDN_CAPI_CAPIFS) || defined(CONFIG_ISDN_CAPI_CAPIFS_MODULE) + capifs_new_ncci(mp->minor, MKDEV(capi_ttymajor, mp->minor)); +#endif + } +#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */ + for (pp=&cdev->nccis; *pp; pp = &(*pp)->next) + ; + *pp = np; + return np; +} + +static void capincci_free(struct capidev *cdev, u32 ncci) +{ + struct capincci *np, **pp; +#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE + struct capiminor *mp; +#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */ + + pp=&cdev->nccis; + while (*pp) { + np = *pp; + if (ncci == 0xffffffff || np->ncci == ncci) { + *pp = (*pp)->next; +#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE + if ((mp = np->minorp) != 0) { +#if defined(CONFIG_ISDN_CAPI_CAPIFS) || defined(CONFIG_ISDN_CAPI_CAPIFS_MODULE) + capifs_free_ncci(mp->minor); +#endif + if (mp->tty) { + mp->nccip = NULL; +#ifdef _DEBUG_REFCOUNT + printk(KERN_DEBUG "reset mp->nccip\n"); +#endif + tty_hangup(mp->tty); + } else { + capiminor_free(mp); + } + } +#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */ + kfree(np); + if (*pp == 0) return; + } else { + pp = &(*pp)->next; + } + } +} + +static struct capincci *capincci_find(struct capidev *cdev, u32 ncci) +{ + struct capincci *p; + + for (p=cdev->nccis; p ; p = p->next) { + if (p->ncci == ncci) + break; + } + return p; +} + +/* -------- struct capidev ------------------------------------------ */ + +static struct capidev *capidev_alloc(void) +{ + struct capidev *cdev; + unsigned long flags; + + cdev = kmalloc(sizeof(*cdev), GFP_KERNEL); + if (!cdev) + return NULL; + memset(cdev, 0, sizeof(struct capidev)); + + init_MUTEX(&cdev->ncci_list_sem); + skb_queue_head_init(&cdev->recvqueue); + init_waitqueue_head(&cdev->recvwait); + write_lock_irqsave(&capidev_list_lock, flags); + list_add_tail(&cdev->list, &capidev_list); + write_unlock_irqrestore(&capidev_list_lock, flags); + return cdev; +} + +static void capidev_free(struct capidev *cdev) +{ + unsigned long flags; + + if (cdev->ap.applid) { + capi20_release(&cdev->ap); + cdev->ap.applid = 0; + } + skb_queue_purge(&cdev->recvqueue); + + down(&cdev->ncci_list_sem); + capincci_free(cdev, 0xffffffff); + up(&cdev->ncci_list_sem); + + write_lock_irqsave(&capidev_list_lock, flags); + list_del(&cdev->list); + write_unlock_irqrestore(&capidev_list_lock, flags); + kfree(cdev); +} + +#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE +/* -------- handle data queue --------------------------------------- */ + +static struct sk_buff * +gen_data_b3_resp_for(struct capiminor *mp, struct sk_buff *skb) +{ + struct sk_buff *nskb; + nskb = alloc_skb(CAPI_DATA_B3_RESP_LEN, GFP_ATOMIC); + if (nskb) { + u16 datahandle = CAPIMSG_U16(skb->data,CAPIMSG_BASELEN+4+4+2); + unsigned char *s = skb_put(nskb, CAPI_DATA_B3_RESP_LEN); + capimsg_setu16(s, 0, CAPI_DATA_B3_RESP_LEN); + capimsg_setu16(s, 2, mp->ap->applid); + capimsg_setu8 (s, 4, CAPI_DATA_B3); + capimsg_setu8 (s, 5, CAPI_RESP); + capimsg_setu16(s, 6, mp->msgid++); + capimsg_setu32(s, 8, mp->ncci); + capimsg_setu16(s, 12, datahandle); + } + return nskb; +} + +static int handle_recv_skb(struct capiminor *mp, struct sk_buff *skb) +{ + struct sk_buff *nskb; + int datalen; + u16 errcode, datahandle; + struct tty_ldisc *ld; + + datalen = skb->len - CAPIMSG_LEN(skb->data); + if (mp->tty == NULL) + { +#ifdef _DEBUG_DATAFLOW + printk(KERN_DEBUG "capi: currently no receiver\n"); +#endif + return -1; + } + + ld = tty_ldisc_ref(mp->tty); + if (ld == NULL) + return -1; + if (ld->receive_buf == NULL) { +#if defined(_DEBUG_DATAFLOW) || defined(_DEBUG_TTYFUNCS) + printk(KERN_DEBUG "capi: ldisc has no receive_buf function\n"); +#endif + goto bad; + } + if (mp->ttyinstop) { +#if defined(_DEBUG_DATAFLOW) || defined(_DEBUG_TTYFUNCS) + printk(KERN_DEBUG "capi: recv tty throttled\n"); +#endif + goto bad; + } + if (ld->receive_room && + ld->receive_room(mp->tty) < datalen) { +#if defined(_DEBUG_DATAFLOW) || defined(_DEBUG_TTYFUNCS) + printk(KERN_DEBUG "capi: no room in tty\n"); +#endif + goto bad; + } + if ((nskb = gen_data_b3_resp_for(mp, skb)) == 0) { + printk(KERN_ERR "capi: gen_data_b3_resp failed\n"); + goto bad; + } + datahandle = CAPIMSG_U16(skb->data,CAPIMSG_BASELEN+4); + errcode = capi20_put_message(mp->ap, nskb); + if (errcode != CAPI_NOERROR) { + printk(KERN_ERR "capi: send DATA_B3_RESP failed=%x\n", + errcode); + kfree_skb(nskb); + goto bad; + } + (void)skb_pull(skb, CAPIMSG_LEN(skb->data)); +#ifdef _DEBUG_DATAFLOW + printk(KERN_DEBUG "capi: DATA_B3_RESP %u len=%d => ldisc\n", + datahandle, skb->len); +#endif + ld->receive_buf(mp->tty, skb->data, NULL, skb->len); + kfree_skb(skb); + tty_ldisc_deref(ld); + return 0; +bad: + tty_ldisc_deref(ld); + return -1; +} + +static void handle_minor_recv(struct capiminor *mp) +{ + struct sk_buff *skb; + while ((skb = skb_dequeue(&mp->inqueue)) != 0) { + unsigned int len = skb->len; + mp->inbytes -= len; + if (handle_recv_skb(mp, skb) < 0) { + skb_queue_head(&mp->inqueue, skb); + mp->inbytes += len; + return; + } + } +} + +static int handle_minor_send(struct capiminor *mp) +{ + struct sk_buff *skb; + u16 len; + int count = 0; + u16 errcode; + u16 datahandle; + + if (mp->tty && mp->ttyoutstop) { +#if defined(_DEBUG_DATAFLOW) || defined(_DEBUG_TTYFUNCS) + printk(KERN_DEBUG "capi: send: tty stopped\n"); +#endif + return 0; + } + + while ((skb = skb_dequeue(&mp->outqueue)) != 0) { + datahandle = mp->datahandle; + len = (u16)skb->len; + skb_push(skb, CAPI_DATA_B3_REQ_LEN); + memset(skb->data, 0, CAPI_DATA_B3_REQ_LEN); + capimsg_setu16(skb->data, 0, CAPI_DATA_B3_REQ_LEN); + capimsg_setu16(skb->data, 2, mp->ap->applid); + capimsg_setu8 (skb->data, 4, CAPI_DATA_B3); + capimsg_setu8 (skb->data, 5, CAPI_REQ); + capimsg_setu16(skb->data, 6, mp->msgid++); + capimsg_setu32(skb->data, 8, mp->ncci); /* NCCI */ + capimsg_setu32(skb->data, 12, (u32) skb->data); /* Data32 */ + capimsg_setu16(skb->data, 16, len); /* Data length */ + capimsg_setu16(skb->data, 18, datahandle); + capimsg_setu16(skb->data, 20, 0); /* Flags */ + + if (capincci_add_ack(mp, datahandle) < 0) { + skb_pull(skb, CAPI_DATA_B3_REQ_LEN); + skb_queue_head(&mp->outqueue, skb); + return count; + } + errcode = capi20_put_message(mp->ap, skb); + if (errcode == CAPI_NOERROR) { + mp->datahandle++; + count++; + mp->outbytes -= len; +#ifdef _DEBUG_DATAFLOW + printk(KERN_DEBUG "capi: DATA_B3_REQ %u len=%u\n", + datahandle, len); +#endif + continue; + } + capiminor_del_ack(mp, datahandle); + + if (errcode == CAPI_SENDQUEUEFULL) { + skb_pull(skb, CAPI_DATA_B3_REQ_LEN); + skb_queue_head(&mp->outqueue, skb); + break; + } + + /* ups, drop packet */ + printk(KERN_ERR "capi: put_message = %x\n", errcode); + mp->outbytes -= len; + kfree_skb(skb); + } + return count; +} + +#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */ +/* -------- function called by lower level -------------------------- */ + +static void capi_recv_message(struct capi20_appl *ap, struct sk_buff *skb) +{ + struct capidev *cdev = ap->private; +#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE + struct capiminor *mp; + u16 datahandle; +#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */ + struct capincci *np; + u32 ncci; + + if (CAPIMSG_CMD(skb->data) == CAPI_CONNECT_B3_CONF) { + u16 info = CAPIMSG_U16(skb->data, 12); // Info field + if (info == 0) { + down(&cdev->ncci_list_sem); + capincci_alloc(cdev, CAPIMSG_NCCI(skb->data)); + up(&cdev->ncci_list_sem); + } + } + if (CAPIMSG_CMD(skb->data) == CAPI_CONNECT_B3_IND) { + down(&cdev->ncci_list_sem); + capincci_alloc(cdev, CAPIMSG_NCCI(skb->data)); + up(&cdev->ncci_list_sem); + } + if (CAPIMSG_COMMAND(skb->data) != CAPI_DATA_B3) { + skb_queue_tail(&cdev->recvqueue, skb); + wake_up_interruptible(&cdev->recvwait); + return; + } + ncci = CAPIMSG_CONTROL(skb->data); + for (np = cdev->nccis; np && np->ncci != ncci; np = np->next) + ; + if (!np) { + printk(KERN_ERR "BUG: capi_signal: ncci not found\n"); + skb_queue_tail(&cdev->recvqueue, skb); + wake_up_interruptible(&cdev->recvwait); + return; + } +#ifndef CONFIG_ISDN_CAPI_MIDDLEWARE + skb_queue_tail(&cdev->recvqueue, skb); + wake_up_interruptible(&cdev->recvwait); +#else /* CONFIG_ISDN_CAPI_MIDDLEWARE */ + mp = np->minorp; + if (!mp) { + skb_queue_tail(&cdev->recvqueue, skb); + wake_up_interruptible(&cdev->recvwait); + return; + } + + + if (CAPIMSG_SUBCOMMAND(skb->data) == CAPI_IND) { + + datahandle = CAPIMSG_U16(skb->data, CAPIMSG_BASELEN+4+4+2); +#ifdef _DEBUG_DATAFLOW + printk(KERN_DEBUG "capi_signal: DATA_B3_IND %u len=%d\n", + datahandle, skb->len-CAPIMSG_LEN(skb->data)); +#endif + skb_queue_tail(&mp->inqueue, skb); + mp->inbytes += skb->len; + handle_minor_recv(mp); + + } else if (CAPIMSG_SUBCOMMAND(skb->data) == CAPI_CONF) { + + datahandle = CAPIMSG_U16(skb->data, CAPIMSG_BASELEN+4); +#ifdef _DEBUG_DATAFLOW + printk(KERN_DEBUG "capi_signal: DATA_B3_CONF %u 0x%x\n", + datahandle, + CAPIMSG_U16(skb->data, CAPIMSG_BASELEN+4+2)); +#endif + kfree_skb(skb); + (void)capiminor_del_ack(mp, datahandle); + if (mp->tty) + tty_wakeup(mp->tty); + (void)handle_minor_send(mp); + + } else { + /* ups, let capi application handle it :-) */ + skb_queue_tail(&cdev->recvqueue, skb); + wake_up_interruptible(&cdev->recvwait); + } +#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */ +} + +/* -------- file_operations for capidev ----------------------------- */ + +static ssize_t +capi_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) +{ + struct capidev *cdev = (struct capidev *)file->private_data; + struct sk_buff *skb; + size_t copied; + + if (!cdev->ap.applid) + return -ENODEV; + + if ((skb = skb_dequeue(&cdev->recvqueue)) == 0) { + + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + for (;;) { + interruptible_sleep_on(&cdev->recvwait); + if ((skb = skb_dequeue(&cdev->recvqueue)) != 0) + break; + if (signal_pending(current)) + break; + } + if (skb == 0) + return -ERESTARTNOHAND; + } + if (skb->len > count) { + skb_queue_head(&cdev->recvqueue, skb); + return -EMSGSIZE; + } + if (copy_to_user(buf, skb->data, skb->len)) { + skb_queue_head(&cdev->recvqueue, skb); + return -EFAULT; + } + copied = skb->len; + + kfree_skb(skb); + + return copied; +} + +static ssize_t +capi_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) +{ + struct capidev *cdev = (struct capidev *)file->private_data; + struct sk_buff *skb; + u16 mlen; + + if (!cdev->ap.applid) + return -ENODEV; + + skb = alloc_skb(count, GFP_USER); + if (!skb) + return -ENOMEM; + + if (copy_from_user(skb_put(skb, count), buf, count)) { + kfree_skb(skb); + return -EFAULT; + } + mlen = CAPIMSG_LEN(skb->data); + if (CAPIMSG_CMD(skb->data) == CAPI_DATA_B3_REQ) { + if ((size_t)(mlen + CAPIMSG_DATALEN(skb->data)) != count) { + kfree_skb(skb); + return -EINVAL; + } + } else { + if (mlen != count) { + kfree_skb(skb); + return -EINVAL; + } + } + CAPIMSG_SETAPPID(skb->data, cdev->ap.applid); + + if (CAPIMSG_CMD(skb->data) == CAPI_DISCONNECT_B3_RESP) { + down(&cdev->ncci_list_sem); + capincci_free(cdev, CAPIMSG_NCCI(skb->data)); + up(&cdev->ncci_list_sem); + } + + cdev->errcode = capi20_put_message(&cdev->ap, skb); + + if (cdev->errcode) { + kfree_skb(skb); + return -EIO; + } + return count; +} + +static unsigned int +capi_poll(struct file *file, poll_table * wait) +{ + struct capidev *cdev = (struct capidev *)file->private_data; + unsigned int mask = 0; + + if (!cdev->ap.applid) + return POLLERR; + + poll_wait(file, &(cdev->recvwait), wait); + mask = POLLOUT | POLLWRNORM; + if (!skb_queue_empty(&cdev->recvqueue)) + mask |= POLLIN | POLLRDNORM; + return mask; +} + +static int +capi_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct capidev *cdev = file->private_data; + struct capi20_appl *ap = &cdev->ap; + capi_ioctl_struct data; + int retval = -EINVAL; + void __user *argp = (void __user *)arg; + + switch (cmd) { + case CAPI_REGISTER: + { + if (ap->applid) + return -EEXIST; + + if (copy_from_user(&cdev->ap.rparam, argp, + sizeof(struct capi_register_params))) + return -EFAULT; + + cdev->ap.private = cdev; + cdev->ap.recv_message = capi_recv_message; + cdev->errcode = capi20_register(ap); + if (cdev->errcode) { + ap->applid = 0; + return -EIO; + } + } + return (int)ap->applid; + + case CAPI_GET_VERSION: + { + if (copy_from_user(&data.contr, argp, + sizeof(data.contr))) + return -EFAULT; + cdev->errcode = capi20_get_version(data.contr, &data.version); + if (cdev->errcode) + return -EIO; + if (copy_to_user(argp, &data.version, + sizeof(data.version))) + return -EFAULT; + } + return 0; + + case CAPI_GET_SERIAL: + { + if (copy_from_user(&data.contr, argp, + sizeof(data.contr))) + return -EFAULT; + cdev->errcode = capi20_get_serial (data.contr, data.serial); + if (cdev->errcode) + return -EIO; + if (copy_to_user(argp, data.serial, + sizeof(data.serial))) + return -EFAULT; + } + return 0; + case CAPI_GET_PROFILE: + { + if (copy_from_user(&data.contr, argp, + sizeof(data.contr))) + return -EFAULT; + + if (data.contr == 0) { + cdev->errcode = capi20_get_profile(data.contr, &data.profile); + if (cdev->errcode) + return -EIO; + + retval = copy_to_user(argp, + &data.profile.ncontroller, + sizeof(data.profile.ncontroller)); + + } else { + cdev->errcode = capi20_get_profile(data.contr, &data.profile); + if (cdev->errcode) + return -EIO; + + retval = copy_to_user(argp, &data.profile, + sizeof(data.profile)); + } + if (retval) + return -EFAULT; + } + return 0; + + case CAPI_GET_MANUFACTURER: + { + if (copy_from_user(&data.contr, argp, + sizeof(data.contr))) + return -EFAULT; + cdev->errcode = capi20_get_manufacturer(data.contr, data.manufacturer); + if (cdev->errcode) + return -EIO; + + if (copy_to_user(argp, data.manufacturer, + sizeof(data.manufacturer))) + return -EFAULT; + + } + return 0; + case CAPI_GET_ERRCODE: + data.errcode = cdev->errcode; + cdev->errcode = CAPI_NOERROR; + if (arg) { + if (copy_to_user(argp, &data.errcode, + sizeof(data.errcode))) + return -EFAULT; + } + return data.errcode; + + case CAPI_INSTALLED: + if (capi20_isinstalled() == CAPI_NOERROR) + return 0; + return -ENXIO; + + case CAPI_MANUFACTURER_CMD: + { + struct capi_manufacturer_cmd mcmd; + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + if (copy_from_user(&mcmd, argp, sizeof(mcmd))) + return -EFAULT; + return capi20_manufacturer(mcmd.cmd, mcmd.data); + } + return 0; + + case CAPI_SET_FLAGS: + case CAPI_CLR_FLAGS: + { + unsigned userflags; + if (copy_from_user(&userflags, argp, + sizeof(userflags))) + return -EFAULT; + if (cmd == CAPI_SET_FLAGS) + cdev->userflags |= userflags; + else + cdev->userflags &= ~userflags; + } + return 0; + + case CAPI_GET_FLAGS: + if (copy_to_user(argp, &cdev->userflags, + sizeof(cdev->userflags))) + return -EFAULT; + return 0; + + case CAPI_NCCI_OPENCOUNT: + { + struct capincci *nccip; +#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE + struct capiminor *mp; +#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */ + unsigned ncci; + int count = 0; + if (copy_from_user(&ncci, argp, sizeof(ncci))) + return -EFAULT; + + down(&cdev->ncci_list_sem); + if ((nccip = capincci_find(cdev, (u32) ncci)) == 0) { + up(&cdev->ncci_list_sem); + return 0; + } +#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE + if ((mp = nccip->minorp) != 0) { + count += atomic_read(&mp->ttyopencount); + } +#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */ + up(&cdev->ncci_list_sem); + return count; + } + return 0; + +#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE + case CAPI_NCCI_GETUNIT: + { + struct capincci *nccip; + struct capiminor *mp; + unsigned ncci; + int unit = 0; + if (copy_from_user(&ncci, argp, + sizeof(ncci))) + return -EFAULT; + down(&cdev->ncci_list_sem); + nccip = capincci_find(cdev, (u32) ncci); + if (!nccip || (mp = nccip->minorp) == 0) { + up(&cdev->ncci_list_sem); + return -ESRCH; + } + unit = mp->minor; + up(&cdev->ncci_list_sem); + return unit; + } + return 0; +#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */ + } + return -EINVAL; +} + +static int +capi_open(struct inode *inode, struct file *file) +{ + if (file->private_data) + return -EEXIST; + + if ((file->private_data = capidev_alloc()) == 0) + return -ENOMEM; + + return nonseekable_open(inode, file); +} + +static int +capi_release(struct inode *inode, struct file *file) +{ + struct capidev *cdev = (struct capidev *)file->private_data; + + capidev_free(cdev); + file->private_data = NULL; + + return 0; +} + +static struct file_operations capi_fops = +{ + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = capi_read, + .write = capi_write, + .poll = capi_poll, + .ioctl = capi_ioctl, + .open = capi_open, + .release = capi_release, +}; + +#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE +/* -------- tty_operations for capincci ----------------------------- */ + +static int capinc_tty_open(struct tty_struct * tty, struct file * file) +{ + struct capiminor *mp; + + if ((mp = capiminor_find(iminor(file->f_dentry->d_inode))) == 0) + return -ENXIO; + if (mp->nccip == 0) + return -ENXIO; + + tty->driver_data = (void *)mp; + + if (atomic_read(&mp->ttyopencount) == 0) + mp->tty = tty; + atomic_inc(&mp->ttyopencount); +#ifdef _DEBUG_REFCOUNT + printk(KERN_DEBUG "capinc_tty_open ocount=%d\n", atomic_read(&mp->ttyopencount)); +#endif + handle_minor_recv(mp); + return 0; +} + +static void capinc_tty_close(struct tty_struct * tty, struct file * file) +{ + struct capiminor *mp; + + mp = (struct capiminor *)tty->driver_data; + if (mp) { + if (atomic_dec_and_test(&mp->ttyopencount)) { +#ifdef _DEBUG_REFCOUNT + printk(KERN_DEBUG "capinc_tty_close lastclose\n"); +#endif + tty->driver_data = NULL; + mp->tty = NULL; + } +#ifdef _DEBUG_REFCOUNT + printk(KERN_DEBUG "capinc_tty_close ocount=%d\n", atomic_read(&mp->ttyopencount)); +#endif + if (mp->nccip == 0) + capiminor_free(mp); + } + +#ifdef _DEBUG_REFCOUNT + printk(KERN_DEBUG "capinc_tty_close\n"); +#endif +} + +static int capinc_tty_write(struct tty_struct * tty, + const unsigned char *buf, int count) +{ + struct capiminor *mp = (struct capiminor *)tty->driver_data; + struct sk_buff *skb; + +#ifdef _DEBUG_TTYFUNCS + printk(KERN_DEBUG "capinc_tty_write(count=%d)\n", count); +#endif + + if (!mp || !mp->nccip) { +#ifdef _DEBUG_TTYFUNCS + printk(KERN_DEBUG "capinc_tty_write: mp or mp->ncci NULL\n"); +#endif + return 0; + } + + skb = mp->ttyskb; + if (skb) { + mp->ttyskb = NULL; + skb_queue_tail(&mp->outqueue, skb); + mp->outbytes += skb->len; + } + + skb = alloc_skb(CAPI_DATA_B3_REQ_LEN+count, GFP_ATOMIC); + if (!skb) { + printk(KERN_ERR "capinc_tty_write: alloc_skb failed\n"); + return -ENOMEM; + } + + skb_reserve(skb, CAPI_DATA_B3_REQ_LEN); + memcpy(skb_put(skb, count), buf, count); + + skb_queue_tail(&mp->outqueue, skb); + mp->outbytes += skb->len; + (void)handle_minor_send(mp); + (void)handle_minor_recv(mp); + return count; +} + +static void capinc_tty_put_char(struct tty_struct *tty, unsigned char ch) +{ + struct capiminor *mp = (struct capiminor *)tty->driver_data; + struct sk_buff *skb; + +#ifdef _DEBUG_TTYFUNCS + printk(KERN_DEBUG "capinc_put_char(%u)\n", ch); +#endif + + if (!mp || !mp->nccip) { +#ifdef _DEBUG_TTYFUNCS + printk(KERN_DEBUG "capinc_tty_put_char: mp or mp->ncci NULL\n"); +#endif + return; + } + + skb = mp->ttyskb; + if (skb) { + if (skb_tailroom(skb) > 0) { + *(skb_put(skb, 1)) = ch; + return; + } + mp->ttyskb = NULL; + skb_queue_tail(&mp->outqueue, skb); + mp->outbytes += skb->len; + (void)handle_minor_send(mp); + } + skb = alloc_skb(CAPI_DATA_B3_REQ_LEN+CAPI_MAX_BLKSIZE, GFP_ATOMIC); + if (skb) { + skb_reserve(skb, CAPI_DATA_B3_REQ_LEN); + *(skb_put(skb, 1)) = ch; + mp->ttyskb = skb; + } else { + printk(KERN_ERR "capinc_put_char: char %u lost\n", ch); + } +} + +static void capinc_tty_flush_chars(struct tty_struct *tty) +{ + struct capiminor *mp = (struct capiminor *)tty->driver_data; + struct sk_buff *skb; + +#ifdef _DEBUG_TTYFUNCS + printk(KERN_DEBUG "capinc_tty_flush_chars\n"); +#endif + + if (!mp || !mp->nccip) { +#ifdef _DEBUG_TTYFUNCS + printk(KERN_DEBUG "capinc_tty_flush_chars: mp or mp->ncci NULL\n"); +#endif + return; + } + + skb = mp->ttyskb; + if (skb) { + mp->ttyskb = NULL; + skb_queue_tail(&mp->outqueue, skb); + mp->outbytes += skb->len; + (void)handle_minor_send(mp); + } + (void)handle_minor_recv(mp); +} + +static int capinc_tty_write_room(struct tty_struct *tty) +{ + struct capiminor *mp = (struct capiminor *)tty->driver_data; + int room; + if (!mp || !mp->nccip) { +#ifdef _DEBUG_TTYFUNCS + printk(KERN_DEBUG "capinc_tty_write_room: mp or mp->ncci NULL\n"); +#endif + return 0; + } + room = CAPINC_MAX_SENDQUEUE-skb_queue_len(&mp->outqueue); + room *= CAPI_MAX_BLKSIZE; +#ifdef _DEBUG_TTYFUNCS + printk(KERN_DEBUG "capinc_tty_write_room = %d\n", room); +#endif + return room; +} + +int capinc_tty_chars_in_buffer(struct tty_struct *tty) +{ + struct capiminor *mp = (struct capiminor *)tty->driver_data; + if (!mp || !mp->nccip) { +#ifdef _DEBUG_TTYFUNCS + printk(KERN_DEBUG "capinc_tty_chars_in_buffer: mp or mp->ncci NULL\n"); +#endif + return 0; + } +#ifdef _DEBUG_TTYFUNCS + printk(KERN_DEBUG "capinc_tty_chars_in_buffer = %d nack=%d sq=%d rq=%d\n", + mp->outbytes, mp->nack, + skb_queue_len(&mp->outqueue), + skb_queue_len(&mp->inqueue)); +#endif + return mp->outbytes; +} + +static int capinc_tty_ioctl(struct tty_struct *tty, struct file * file, + unsigned int cmd, unsigned long arg) +{ + int error = 0; + switch (cmd) { + default: + error = n_tty_ioctl (tty, file, cmd, arg); + break; + } + return error; +} + +static void capinc_tty_set_termios(struct tty_struct *tty, struct termios * old) +{ +#ifdef _DEBUG_TTYFUNCS + printk(KERN_DEBUG "capinc_tty_set_termios\n"); +#endif +} + +static void capinc_tty_throttle(struct tty_struct * tty) +{ + struct capiminor *mp = (struct capiminor *)tty->driver_data; +#ifdef _DEBUG_TTYFUNCS + printk(KERN_DEBUG "capinc_tty_throttle\n"); +#endif + if (mp) + mp->ttyinstop = 1; +} + +static void capinc_tty_unthrottle(struct tty_struct * tty) +{ + struct capiminor *mp = (struct capiminor *)tty->driver_data; +#ifdef _DEBUG_TTYFUNCS + printk(KERN_DEBUG "capinc_tty_unthrottle\n"); +#endif + if (mp) { + mp->ttyinstop = 0; + handle_minor_recv(mp); + } +} + +static void capinc_tty_stop(struct tty_struct *tty) +{ + struct capiminor *mp = (struct capiminor *)tty->driver_data; +#ifdef _DEBUG_TTYFUNCS + printk(KERN_DEBUG "capinc_tty_stop\n"); +#endif + if (mp) { + mp->ttyoutstop = 1; + } +} + +static void capinc_tty_start(struct tty_struct *tty) +{ + struct capiminor *mp = (struct capiminor *)tty->driver_data; +#ifdef _DEBUG_TTYFUNCS + printk(KERN_DEBUG "capinc_tty_start\n"); +#endif + if (mp) { + mp->ttyoutstop = 0; + (void)handle_minor_send(mp); + } +} + +static void capinc_tty_hangup(struct tty_struct *tty) +{ +#ifdef _DEBUG_TTYFUNCS + printk(KERN_DEBUG "capinc_tty_hangup\n"); +#endif +} + +static void capinc_tty_break_ctl(struct tty_struct *tty, int state) +{ +#ifdef _DEBUG_TTYFUNCS + printk(KERN_DEBUG "capinc_tty_break_ctl(%d)\n", state); +#endif +} + +static void capinc_tty_flush_buffer(struct tty_struct *tty) +{ +#ifdef _DEBUG_TTYFUNCS + printk(KERN_DEBUG "capinc_tty_flush_buffer\n"); +#endif +} + +static void capinc_tty_set_ldisc(struct tty_struct *tty) +{ +#ifdef _DEBUG_TTYFUNCS + printk(KERN_DEBUG "capinc_tty_set_ldisc\n"); +#endif +} + +static void capinc_tty_send_xchar(struct tty_struct *tty, char ch) +{ +#ifdef _DEBUG_TTYFUNCS + printk(KERN_DEBUG "capinc_tty_send_xchar(%d)\n", ch); +#endif +} + +static int capinc_tty_read_proc(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + return 0; +} + +static struct tty_driver *capinc_tty_driver; + +static struct tty_operations capinc_ops = { + .open = capinc_tty_open, + .close = capinc_tty_close, + .write = capinc_tty_write, + .put_char = capinc_tty_put_char, + .flush_chars = capinc_tty_flush_chars, + .write_room = capinc_tty_write_room, + .chars_in_buffer = capinc_tty_chars_in_buffer, + .ioctl = capinc_tty_ioctl, + .set_termios = capinc_tty_set_termios, + .throttle = capinc_tty_throttle, + .unthrottle = capinc_tty_unthrottle, + .stop = capinc_tty_stop, + .start = capinc_tty_start, + .hangup = capinc_tty_hangup, + .break_ctl = capinc_tty_break_ctl, + .flush_buffer = capinc_tty_flush_buffer, + .set_ldisc = capinc_tty_set_ldisc, + .send_xchar = capinc_tty_send_xchar, + .read_proc = capinc_tty_read_proc, +}; + +static int capinc_tty_init(void) +{ + struct tty_driver *drv; + + if (capi_ttyminors > CAPINC_MAX_PORTS) + capi_ttyminors = CAPINC_MAX_PORTS; + if (capi_ttyminors <= 0) + capi_ttyminors = CAPINC_NR_PORTS; + + drv = alloc_tty_driver(capi_ttyminors); + if (!drv) + return -ENOMEM; + + drv->owner = THIS_MODULE; + drv->driver_name = "capi_nc"; + drv->devfs_name = "capi/"; + drv->name = "capi"; + drv->major = capi_ttymajor; + drv->minor_start = 0; + drv->type = TTY_DRIVER_TYPE_SERIAL; + drv->subtype = SERIAL_TYPE_NORMAL; + drv->init_termios = tty_std_termios; + drv->init_termios.c_iflag = ICRNL; + drv->init_termios.c_oflag = OPOST | ONLCR; + drv->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; + drv->init_termios.c_lflag = 0; + drv->flags = TTY_DRIVER_REAL_RAW|TTY_DRIVER_RESET_TERMIOS; + tty_set_operations(drv, &capinc_ops); + if (tty_register_driver(drv)) { + put_tty_driver(drv); + printk(KERN_ERR "Couldn't register capi_nc driver\n"); + return -1; + } + capinc_tty_driver = drv; + return 0; +} + +static void capinc_tty_exit(void) +{ + struct tty_driver *drv = capinc_tty_driver; + int retval; + if ((retval = tty_unregister_driver(drv))) + printk(KERN_ERR "capi: failed to unregister capi_nc driver (%d)\n", retval); + put_tty_driver(drv); +} + +#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */ + +/* -------- /proc functions ----------------------------------------- */ + +/* + * /proc/capi/capi20: + * minor applid nrecvctlpkt nrecvdatapkt nsendctlpkt nsenddatapkt + */ +static int proc_capidev_read_proc(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + struct capidev *cdev; + struct list_head *l; + int len = 0; + + read_lock(&capidev_list_lock); + list_for_each(l, &capidev_list) { + cdev = list_entry(l, struct capidev, list); + len += sprintf(page+len, "0 %d %lu %lu %lu %lu\n", + cdev->ap.applid, + cdev->ap.nrecvctlpkt, + cdev->ap.nrecvdatapkt, + cdev->ap.nsentctlpkt, + cdev->ap.nsentdatapkt); + if (len <= off) { + off -= len; + len = 0; + } else { + if (len-off > count) + goto endloop; + } + } + +endloop: + read_unlock(&capidev_list_lock); + if (len < count) + *eof = 1; + if (len > count) len = count; + if (len < 0) len = 0; + return len; +} + +/* + * /proc/capi/capi20ncci: + * applid ncci + */ +static int proc_capincci_read_proc(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + struct capidev *cdev; + struct capincci *np; + struct list_head *l; + int len = 0; + + read_lock(&capidev_list_lock); + list_for_each(l, &capidev_list) { + cdev = list_entry(l, struct capidev, list); + for (np=cdev->nccis; np; np = np->next) { + len += sprintf(page+len, "%d 0x%x\n", + cdev->ap.applid, + np->ncci); + if (len <= off) { + off -= len; + len = 0; + } else { + if (len-off > count) + goto endloop; + } + } + } +endloop: + read_unlock(&capidev_list_lock); + *start = page+off; + if (len < count) + *eof = 1; + if (len>count) len = count; + if (len<0) len = 0; + return len; +} + +static struct procfsentries { + char *name; + mode_t mode; + int (*read_proc)(char *page, char **start, off_t off, + int count, int *eof, void *data); + struct proc_dir_entry *procent; +} procfsentries[] = { + /* { "capi", S_IFDIR, 0 }, */ + { "capi/capi20", 0 , proc_capidev_read_proc }, + { "capi/capi20ncci", 0 , proc_capincci_read_proc }, +}; + +static void __init proc_init(void) +{ + int nelem = sizeof(procfsentries)/sizeof(procfsentries[0]); + int i; + + for (i=0; i < nelem; i++) { + struct procfsentries *p = procfsentries + i; + p->procent = create_proc_entry(p->name, p->mode, NULL); + if (p->procent) p->procent->read_proc = p->read_proc; + } +} + +static void __exit proc_exit(void) +{ + int nelem = sizeof(procfsentries)/sizeof(procfsentries[0]); + int i; + + for (i=nelem-1; i >= 0; i--) { + struct procfsentries *p = procfsentries + i; + if (p->procent) { + remove_proc_entry(p->name, NULL); + p->procent = NULL; + } + } +} + +/* -------- init function and module interface ---------------------- */ + + +static char rev[32]; + +static int __init capi_init(void) +{ + char *p; + char *compileinfo; + + if ((p = strchr(revision, ':')) != 0 && p[1]) { + strlcpy(rev, p + 2, sizeof(rev)); + if ((p = strchr(rev, '$')) != 0 && p > rev) + *(p-1) = 0; + } else + strcpy(rev, "1.0"); + + if (register_chrdev(capi_major, "capi20", &capi_fops)) { + printk(KERN_ERR "capi20: unable to get major %d\n", capi_major); + return -EIO; + } + + capi_class = class_simple_create(THIS_MODULE, "capi"); + if (IS_ERR(capi_class)) { + unregister_chrdev(capi_major, "capi20"); + return PTR_ERR(capi_class); + } + + class_simple_device_add(capi_class, MKDEV(capi_major, 0), NULL, "capi"); + devfs_mk_cdev(MKDEV(capi_major, 0), S_IFCHR | S_IRUSR | S_IWUSR, + "isdn/capi20"); + +#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE + if (capinc_tty_init() < 0) { + class_simple_device_remove(MKDEV(capi_major, 0)); + class_simple_destroy(capi_class); + unregister_chrdev(capi_major, "capi20"); + return -ENOMEM; + } +#endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */ + + proc_init(); + +#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE +#if defined(CONFIG_ISDN_CAPI_CAPIFS) || defined(CONFIG_ISDN_CAPI_CAPIFS_MODULE) + compileinfo = " (middleware+capifs)"; +#else + compileinfo = " (no capifs)"; +#endif +#else + compileinfo = " (no middleware)"; +#endif + printk(KERN_NOTICE "capi20: Rev %s: started up with major %d%s\n", + rev, capi_major, compileinfo); + + return 0; +} + +static void __exit capi_exit(void) +{ + proc_exit(); + + class_simple_device_remove(MKDEV(capi_major, 0)); + class_simple_destroy(capi_class); + unregister_chrdev(capi_major, "capi20"); + devfs_remove("isdn/capi20"); + +#ifdef CONFIG_ISDN_CAPI_MIDDLEWARE + capinc_tty_exit(); +#endif + printk(KERN_NOTICE "capi: Rev %s: unloaded\n", rev); +} + +module_init(capi_init); +module_exit(capi_exit); diff --git a/drivers/isdn/capi/capidrv.c b/drivers/isdn/capi/capidrv.c new file mode 100644 index 000000000000..d10c8b82e6aa --- /dev/null +++ b/drivers/isdn/capi/capidrv.c @@ -0,0 +1,2315 @@ +/* $Id: capidrv.c,v 1.1.2.2 2004/01/12 23:17:24 keil Exp $ + * + * ISDN4Linux Driver, using capi20 interface (kernelcapi) + * + * Copyright 1997 by Carsten Paeth <calle@calle.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/fcntl.h> +#include <linux/fs.h> +#include <linux/signal.h> +#include <linux/mm.h> +#include <linux/timer.h> +#include <linux/wait.h> +#include <linux/skbuff.h> +#include <linux/isdn.h> +#include <linux/isdnif.h> +#include <linux/proc_fs.h> +#include <linux/capi.h> +#include <linux/kernelcapi.h> +#include <linux/ctype.h> +#include <linux/init.h> +#include <linux/moduleparam.h> + +#include <linux/isdn/capiutil.h> +#include <linux/isdn/capicmd.h> +#include "capidrv.h" + +static char *revision = "$Revision: 1.1.2.2 $"; +static int debugmode = 0; + +MODULE_DESCRIPTION("CAPI4Linux: Interface to ISDN4Linux"); +MODULE_AUTHOR("Carsten Paeth"); +MODULE_LICENSE("GPL"); +module_param(debugmode, uint, 0); + +/* -------- type definitions ----------------------------------------- */ + + +struct capidrv_contr { + + struct capidrv_contr *next; + struct module *owner; + u32 contrnr; + char name[20]; + + /* + * for isdn4linux + */ + isdn_if interface; + int myid; + + /* + * LISTEN state + */ + int state; + u32 cipmask; + u32 cipmask2; + struct timer_list listentimer; + + /* + * ID of capi message sent + */ + u16 msgid; + + /* + * B-Channels + */ + int nbchan; + struct capidrv_bchan { + struct capidrv_contr *contr; + u8 msn[ISDN_MSNLEN]; + int l2; + int l3; + u8 num[ISDN_MSNLEN]; + u8 mynum[ISDN_MSNLEN]; + int si1; + int si2; + int incoming; + int disconnecting; + struct capidrv_plci { + struct capidrv_plci *next; + u32 plci; + u32 ncci; /* ncci for CONNECT_ACTIVE_IND */ + u16 msgid; /* to identfy CONNECT_CONF */ + int chan; + int state; + int leasedline; + struct capidrv_ncci { + struct capidrv_ncci *next; + struct capidrv_plci *plcip; + u32 ncci; + u16 msgid; /* to identfy CONNECT_B3_CONF */ + int chan; + int state; + int oldstate; + /* */ + u16 datahandle; + struct ncci_datahandle_queue { + struct ncci_datahandle_queue *next; + u16 datahandle; + int len; + } *ackqueue; + } *ncci_list; + } *plcip; + struct capidrv_ncci *nccip; + } *bchans; + + struct capidrv_plci *plci_list; + + /* for q931 data */ + u8 q931_buf[4096]; + u8 *q931_read; + u8 *q931_write; + u8 *q931_end; +}; + + +struct capidrv_data { + struct capi20_appl ap; + int ncontr; + struct capidrv_contr *contr_list; +}; + +typedef struct capidrv_plci capidrv_plci; +typedef struct capidrv_ncci capidrv_ncci; +typedef struct capidrv_contr capidrv_contr; +typedef struct capidrv_data capidrv_data; +typedef struct capidrv_bchan capidrv_bchan; + +/* -------- data definitions ----------------------------------------- */ + +static capidrv_data global; +static DEFINE_SPINLOCK(global_lock); + +static void handle_dtrace_data(capidrv_contr *card, + int send, int level2, u8 *data, u16 len); + +/* -------- convert functions ---------------------------------------- */ + +static inline u32 b1prot(int l2, int l3) +{ + switch (l2) { + case ISDN_PROTO_L2_X75I: + case ISDN_PROTO_L2_X75UI: + case ISDN_PROTO_L2_X75BUI: + return 0; + case ISDN_PROTO_L2_HDLC: + default: + return 0; + case ISDN_PROTO_L2_TRANS: + return 1; + case ISDN_PROTO_L2_V11096: + case ISDN_PROTO_L2_V11019: + case ISDN_PROTO_L2_V11038: + return 2; + case ISDN_PROTO_L2_FAX: + return 4; + case ISDN_PROTO_L2_MODEM: + return 8; + } +} + +static inline u32 b2prot(int l2, int l3) +{ + switch (l2) { + case ISDN_PROTO_L2_X75I: + case ISDN_PROTO_L2_X75UI: + case ISDN_PROTO_L2_X75BUI: + default: + return 0; + case ISDN_PROTO_L2_HDLC: + case ISDN_PROTO_L2_TRANS: + case ISDN_PROTO_L2_V11096: + case ISDN_PROTO_L2_V11019: + case ISDN_PROTO_L2_V11038: + case ISDN_PROTO_L2_MODEM: + return 1; + case ISDN_PROTO_L2_FAX: + return 4; + } +} + +static inline u32 b3prot(int l2, int l3) +{ + switch (l2) { + case ISDN_PROTO_L2_X75I: + case ISDN_PROTO_L2_X75UI: + case ISDN_PROTO_L2_X75BUI: + case ISDN_PROTO_L2_HDLC: + case ISDN_PROTO_L2_TRANS: + case ISDN_PROTO_L2_V11096: + case ISDN_PROTO_L2_V11019: + case ISDN_PROTO_L2_V11038: + case ISDN_PROTO_L2_MODEM: + default: + return 0; + case ISDN_PROTO_L2_FAX: + return 4; + } +} + +static _cstruct b1config_async_v110(u16 rate) +{ + /* CAPI-Spec "B1 Configuration" */ + static unsigned char buf[9]; + buf[0] = 8; /* len */ + /* maximum bitrate */ + buf[1] = rate & 0xff; buf[2] = (rate >> 8) & 0xff; + buf[3] = 8; buf[4] = 0; /* 8 bits per character */ + buf[5] = 0; buf[6] = 0; /* parity none */ + buf[7] = 0; buf[8] = 0; /* 1 stop bit */ + return buf; +} + +static _cstruct b1config(int l2, int l3) +{ + switch (l2) { + case ISDN_PROTO_L2_X75I: + case ISDN_PROTO_L2_X75UI: + case ISDN_PROTO_L2_X75BUI: + case ISDN_PROTO_L2_HDLC: + case ISDN_PROTO_L2_TRANS: + default: + return NULL; + case ISDN_PROTO_L2_V11096: + return b1config_async_v110(9600); + case ISDN_PROTO_L2_V11019: + return b1config_async_v110(19200); + case ISDN_PROTO_L2_V11038: + return b1config_async_v110(38400); + } +} + +static inline u16 si2cip(u8 si1, u8 si2) +{ + static const u8 cip[17][5] = + { + /* 0 1 2 3 4 */ + {0, 0, 0, 0, 0}, /*0 */ + {16, 16, 4, 26, 16}, /*1 */ + {17, 17, 17, 4, 4}, /*2 */ + {2, 2, 2, 2, 2}, /*3 */ + {18, 18, 18, 18, 18}, /*4 */ + {2, 2, 2, 2, 2}, /*5 */ + {0, 0, 0, 0, 0}, /*6 */ + {2, 2, 2, 2, 2}, /*7 */ + {2, 2, 2, 2, 2}, /*8 */ + {21, 21, 21, 21, 21}, /*9 */ + {19, 19, 19, 19, 19}, /*10 */ + {0, 0, 0, 0, 0}, /*11 */ + {0, 0, 0, 0, 0}, /*12 */ + {0, 0, 0, 0, 0}, /*13 */ + {0, 0, 0, 0, 0}, /*14 */ + {22, 22, 22, 22, 22}, /*15 */ + {27, 27, 27, 28, 27} /*16 */ + }; + if (si1 > 16) + si1 = 0; + if (si2 > 4) + si2 = 0; + + return (u16) cip[si1][si2]; +} + +static inline u8 cip2si1(u16 cipval) +{ + static const u8 si[32] = + {7, 1, 7, 7, 1, 1, 7, 7, /*0-7 */ + 7, 1, 0, 0, 0, 0, 0, 0, /*8-15 */ + 1, 2, 4, 10, 9, 9, 15, 7, /*16-23 */ + 7, 7, 1, 16, 16, 0, 0, 0}; /*24-31 */ + + if (cipval > 31) + cipval = 0; /* .... */ + return si[cipval]; +} + +static inline u8 cip2si2(u16 cipval) +{ + static const u8 si[32] = + {0, 0, 0, 0, 2, 3, 0, 0, /*0-7 */ + 0, 3, 0, 0, 0, 0, 0, 0, /*8-15 */ + 1, 2, 0, 0, 9, 0, 0, 0, /*16-23 */ + 0, 0, 3, 2, 3, 0, 0, 0}; /*24-31 */ + + if (cipval > 31) + cipval = 0; /* .... */ + return si[cipval]; +} + + +/* -------- controller management ------------------------------------- */ + +static inline capidrv_contr *findcontrbydriverid(int driverid) +{ + unsigned long flags; + capidrv_contr *p; + + spin_lock_irqsave(&global_lock, flags); + for (p = global.contr_list; p; p = p->next) + if (p->myid == driverid) + break; + spin_unlock_irqrestore(&global_lock, flags); + return p; +} + +static capidrv_contr *findcontrbynumber(u32 contr) +{ + unsigned long flags; + capidrv_contr *p = global.contr_list; + + spin_lock_irqsave(&global_lock, flags); + for (p = global.contr_list; p; p = p->next) + if (p->contrnr == contr) + break; + spin_unlock_irqrestore(&global_lock, flags); + return p; +} + + +/* -------- plci management ------------------------------------------ */ + +static capidrv_plci *new_plci(capidrv_contr * card, int chan) +{ + capidrv_plci *plcip; + + plcip = (capidrv_plci *) kmalloc(sizeof(capidrv_plci), GFP_ATOMIC); + + if (plcip == 0) + return NULL; + + memset(plcip, 0, sizeof(capidrv_plci)); + plcip->state = ST_PLCI_NONE; + plcip->plci = 0; + plcip->msgid = 0; + plcip->chan = chan; + plcip->next = card->plci_list; + card->plci_list = plcip; + card->bchans[chan].plcip = plcip; + + return plcip; +} + +static capidrv_plci *find_plci_by_plci(capidrv_contr * card, u32 plci) +{ + capidrv_plci *p; + for (p = card->plci_list; p; p = p->next) + if (p->plci == plci) + return p; + return NULL; +} + +static capidrv_plci *find_plci_by_msgid(capidrv_contr * card, u16 msgid) +{ + capidrv_plci *p; + for (p = card->plci_list; p; p = p->next) + if (p->msgid == msgid) + return p; + return NULL; +} + +static capidrv_plci *find_plci_by_ncci(capidrv_contr * card, u32 ncci) +{ + capidrv_plci *p; + for (p = card->plci_list; p; p = p->next) + if (p->plci == (ncci & 0xffff)) + return p; + return NULL; +} + +static void free_plci(capidrv_contr * card, capidrv_plci * plcip) +{ + capidrv_plci **pp; + + for (pp = &card->plci_list; *pp; pp = &(*pp)->next) { + if (*pp == plcip) { + *pp = (*pp)->next; + card->bchans[plcip->chan].plcip = NULL; + card->bchans[plcip->chan].disconnecting = 0; + card->bchans[plcip->chan].incoming = 0; + kfree(plcip); + return; + } + } + printk(KERN_ERR "capidrv-%d: free_plci %p (0x%x) not found, Huh?\n", + card->contrnr, plcip, plcip->plci); +} + +/* -------- ncci management ------------------------------------------ */ + +static inline capidrv_ncci *new_ncci(capidrv_contr * card, + capidrv_plci * plcip, + u32 ncci) +{ + capidrv_ncci *nccip; + + nccip = (capidrv_ncci *) kmalloc(sizeof(capidrv_ncci), GFP_ATOMIC); + + if (nccip == 0) + return NULL; + + memset(nccip, 0, sizeof(capidrv_ncci)); + nccip->ncci = ncci; + nccip->state = ST_NCCI_NONE; + nccip->plcip = plcip; + nccip->chan = plcip->chan; + nccip->datahandle = 0; + + nccip->next = plcip->ncci_list; + plcip->ncci_list = nccip; + + card->bchans[plcip->chan].nccip = nccip; + + return nccip; +} + +static inline capidrv_ncci *find_ncci(capidrv_contr * card, u32 ncci) +{ + capidrv_plci *plcip; + capidrv_ncci *p; + + if ((plcip = find_plci_by_ncci(card, ncci)) == 0) + return NULL; + + for (p = plcip->ncci_list; p; p = p->next) + if (p->ncci == ncci) + return p; + return NULL; +} + +static inline capidrv_ncci *find_ncci_by_msgid(capidrv_contr * card, + u32 ncci, u16 msgid) +{ + capidrv_plci *plcip; + capidrv_ncci *p; + + if ((plcip = find_plci_by_ncci(card, ncci)) == 0) + return NULL; + + for (p = plcip->ncci_list; p; p = p->next) + if (p->msgid == msgid) + return p; + return NULL; +} + +static void free_ncci(capidrv_contr * card, struct capidrv_ncci *nccip) +{ + struct capidrv_ncci **pp; + + for (pp = &(nccip->plcip->ncci_list); *pp; pp = &(*pp)->next) { + if (*pp == nccip) { + *pp = (*pp)->next; + break; + } + } + card->bchans[nccip->chan].nccip = NULL; + kfree(nccip); +} + +static int capidrv_add_ack(struct capidrv_ncci *nccip, + u16 datahandle, int len) +{ + struct ncci_datahandle_queue *n, **pp; + + n = (struct ncci_datahandle_queue *) + kmalloc(sizeof(struct ncci_datahandle_queue), GFP_ATOMIC); + if (!n) { + printk(KERN_ERR "capidrv: kmalloc ncci_datahandle failed\n"); + return -1; + } + n->next = NULL; + n->datahandle = datahandle; + n->len = len; + for (pp = &nccip->ackqueue; *pp; pp = &(*pp)->next) ; + *pp = n; + return 0; +} + +static int capidrv_del_ack(struct capidrv_ncci *nccip, u16 datahandle) +{ + struct ncci_datahandle_queue **pp, *p; + int len; + + for (pp = &nccip->ackqueue; *pp; pp = &(*pp)->next) { + if ((*pp)->datahandle == datahandle) { + p = *pp; + len = p->len; + *pp = (*pp)->next; + kfree(p); + return len; + } + } + return -1; +} + +/* -------- convert and send capi message ---------------------------- */ + +static void send_message(capidrv_contr * card, _cmsg * cmsg) +{ + struct sk_buff *skb; + size_t len; + capi_cmsg2message(cmsg, cmsg->buf); + len = CAPIMSG_LEN(cmsg->buf); + skb = alloc_skb(len, GFP_ATOMIC); + memcpy(skb_put(skb, len), cmsg->buf, len); + if (capi20_put_message(&global.ap, skb) != CAPI_NOERROR) + kfree_skb(skb); +} + +/* -------- state machine -------------------------------------------- */ + +struct listenstatechange { + int actstate; + int nextstate; + int event; +}; + +static struct listenstatechange listentable[] = +{ + {ST_LISTEN_NONE, ST_LISTEN_WAIT_CONF, EV_LISTEN_REQ}, + {ST_LISTEN_ACTIVE, ST_LISTEN_ACTIVE_WAIT_CONF, EV_LISTEN_REQ}, + {ST_LISTEN_WAIT_CONF, ST_LISTEN_NONE, EV_LISTEN_CONF_ERROR}, + {ST_LISTEN_ACTIVE_WAIT_CONF, ST_LISTEN_ACTIVE, EV_LISTEN_CONF_ERROR}, + {ST_LISTEN_WAIT_CONF, ST_LISTEN_NONE, EV_LISTEN_CONF_EMPTY}, + {ST_LISTEN_ACTIVE_WAIT_CONF, ST_LISTEN_NONE, EV_LISTEN_CONF_EMPTY}, + {ST_LISTEN_WAIT_CONF, ST_LISTEN_ACTIVE, EV_LISTEN_CONF_OK}, + {ST_LISTEN_ACTIVE_WAIT_CONF, ST_LISTEN_ACTIVE, EV_LISTEN_CONF_OK}, + {}, +}; + +static void listen_change_state(capidrv_contr * card, int event) +{ + struct listenstatechange *p = listentable; + while (p->event) { + if (card->state == p->actstate && p->event == event) { + if (debugmode) + printk(KERN_DEBUG "capidrv-%d: listen_change_state %d -> %d\n", + card->contrnr, card->state, p->nextstate); + card->state = p->nextstate; + return; + } + p++; + } + printk(KERN_ERR "capidrv-%d: listen_change_state state=%d event=%d ????\n", + card->contrnr, card->state, event); + +} + +/* ------------------------------------------------------------------ */ + +static void p0(capidrv_contr * card, capidrv_plci * plci) +{ + isdn_ctrl cmd; + + card->bchans[plci->chan].contr = NULL; + cmd.command = ISDN_STAT_DHUP; + cmd.driver = card->myid; + cmd.arg = plci->chan; + card->interface.statcallb(&cmd); + free_plci(card, plci); +} + +/* ------------------------------------------------------------------ */ + +struct plcistatechange { + int actstate; + int nextstate; + int event; + void (*changefunc) (capidrv_contr * card, capidrv_plci * plci); +}; + +static struct plcistatechange plcitable[] = +{ + /* P-0 */ + {ST_PLCI_NONE, ST_PLCI_OUTGOING, EV_PLCI_CONNECT_REQ, NULL}, + {ST_PLCI_NONE, ST_PLCI_ALLOCATED, EV_PLCI_FACILITY_IND_UP, NULL}, + {ST_PLCI_NONE, ST_PLCI_INCOMING, EV_PLCI_CONNECT_IND, NULL}, + {ST_PLCI_NONE, ST_PLCI_RESUMEING, EV_PLCI_RESUME_REQ, NULL}, + /* P-0.1 */ + {ST_PLCI_OUTGOING, ST_PLCI_NONE, EV_PLCI_CONNECT_CONF_ERROR, p0}, + {ST_PLCI_OUTGOING, ST_PLCI_ALLOCATED, EV_PLCI_CONNECT_CONF_OK, NULL}, + /* P-1 */ + {ST_PLCI_ALLOCATED, ST_PLCI_ACTIVE, EV_PLCI_CONNECT_ACTIVE_IND, NULL}, + {ST_PLCI_ALLOCATED, ST_PLCI_DISCONNECTING, EV_PLCI_DISCONNECT_REQ, NULL}, + {ST_PLCI_ALLOCATED, ST_PLCI_DISCONNECTING, EV_PLCI_FACILITY_IND_DOWN, NULL}, + {ST_PLCI_ALLOCATED, ST_PLCI_DISCONNECTED, EV_PLCI_DISCONNECT_IND, NULL}, + /* P-ACT */ + {ST_PLCI_ACTIVE, ST_PLCI_DISCONNECTING, EV_PLCI_DISCONNECT_REQ, NULL}, + {ST_PLCI_ACTIVE, ST_PLCI_DISCONNECTING, EV_PLCI_FACILITY_IND_DOWN, NULL}, + {ST_PLCI_ACTIVE, ST_PLCI_DISCONNECTED, EV_PLCI_DISCONNECT_IND, NULL}, + {ST_PLCI_ACTIVE, ST_PLCI_HELD, EV_PLCI_HOLD_IND, NULL}, + {ST_PLCI_ACTIVE, ST_PLCI_DISCONNECTING, EV_PLCI_SUSPEND_IND, NULL}, + /* P-2 */ + {ST_PLCI_INCOMING, ST_PLCI_DISCONNECTING, EV_PLCI_CONNECT_REJECT, NULL}, + {ST_PLCI_INCOMING, ST_PLCI_FACILITY_IND, EV_PLCI_FACILITY_IND_UP, NULL}, + {ST_PLCI_INCOMING, ST_PLCI_ACCEPTING, EV_PLCI_CONNECT_RESP, NULL}, + {ST_PLCI_INCOMING, ST_PLCI_DISCONNECTING, EV_PLCI_DISCONNECT_REQ, NULL}, + {ST_PLCI_INCOMING, ST_PLCI_DISCONNECTING, EV_PLCI_FACILITY_IND_DOWN, NULL}, + {ST_PLCI_INCOMING, ST_PLCI_DISCONNECTED, EV_PLCI_DISCONNECT_IND, NULL}, + {ST_PLCI_INCOMING, ST_PLCI_DISCONNECTING, EV_PLCI_CD_IND, NULL}, + /* P-3 */ + {ST_PLCI_FACILITY_IND, ST_PLCI_DISCONNECTING, EV_PLCI_CONNECT_REJECT, NULL}, + {ST_PLCI_FACILITY_IND, ST_PLCI_ACCEPTING, EV_PLCI_CONNECT_ACTIVE_IND, NULL}, + {ST_PLCI_FACILITY_IND, ST_PLCI_DISCONNECTING, EV_PLCI_DISCONNECT_REQ, NULL}, + {ST_PLCI_FACILITY_IND, ST_PLCI_DISCONNECTING, EV_PLCI_FACILITY_IND_DOWN, NULL}, + {ST_PLCI_FACILITY_IND, ST_PLCI_DISCONNECTED, EV_PLCI_DISCONNECT_IND, NULL}, + /* P-4 */ + {ST_PLCI_ACCEPTING, ST_PLCI_ACTIVE, EV_PLCI_CONNECT_ACTIVE_IND, NULL}, + {ST_PLCI_ACCEPTING, ST_PLCI_DISCONNECTING, EV_PLCI_DISCONNECT_REQ, NULL}, + {ST_PLCI_ACCEPTING, ST_PLCI_DISCONNECTING, EV_PLCI_FACILITY_IND_DOWN, NULL}, + {ST_PLCI_ACCEPTING, ST_PLCI_DISCONNECTED, EV_PLCI_DISCONNECT_IND, NULL}, + /* P-5 */ + {ST_PLCI_DISCONNECTING, ST_PLCI_DISCONNECTED, EV_PLCI_DISCONNECT_IND, NULL}, + /* P-6 */ + {ST_PLCI_DISCONNECTED, ST_PLCI_NONE, EV_PLCI_DISCONNECT_RESP, p0}, + /* P-0.Res */ + {ST_PLCI_RESUMEING, ST_PLCI_NONE, EV_PLCI_RESUME_CONF_ERROR, p0}, + {ST_PLCI_RESUMEING, ST_PLCI_RESUME, EV_PLCI_RESUME_CONF_OK, NULL}, + /* P-RES */ + {ST_PLCI_RESUME, ST_PLCI_ACTIVE, EV_PLCI_RESUME_IND, NULL}, + /* P-HELD */ + {ST_PLCI_HELD, ST_PLCI_ACTIVE, EV_PLCI_RETRIEVE_IND, NULL}, + {}, +}; + +static void plci_change_state(capidrv_contr * card, capidrv_plci * plci, int event) +{ + struct plcistatechange *p = plcitable; + while (p->event) { + if (plci->state == p->actstate && p->event == event) { + if (debugmode) + printk(KERN_DEBUG "capidrv-%d: plci_change_state:0x%x %d -> %d\n", + card->contrnr, plci->plci, plci->state, p->nextstate); + plci->state = p->nextstate; + if (p->changefunc) + p->changefunc(card, plci); + return; + } + p++; + } + printk(KERN_ERR "capidrv-%d: plci_change_state:0x%x state=%d event=%d ????\n", + card->contrnr, plci->plci, plci->state, event); +} + +/* ------------------------------------------------------------------ */ + +static _cmsg cmsg; + +static void n0(capidrv_contr * card, capidrv_ncci * ncci) +{ + isdn_ctrl cmd; + + capi_fill_DISCONNECT_REQ(&cmsg, + global.ap.applid, + card->msgid++, + ncci->plcip->plci, + NULL, /* BChannelinformation */ + NULL, /* Keypadfacility */ + NULL, /* Useruserdata */ /* $$$$ */ + NULL /* Facilitydataarray */ + ); + send_message(card, &cmsg); + plci_change_state(card, ncci->plcip, EV_PLCI_DISCONNECT_REQ); + + cmd.command = ISDN_STAT_BHUP; + cmd.driver = card->myid; + cmd.arg = ncci->chan; + card->interface.statcallb(&cmd); + free_ncci(card, ncci); +} + +/* ------------------------------------------------------------------ */ + +struct nccistatechange { + int actstate; + int nextstate; + int event; + void (*changefunc) (capidrv_contr * card, capidrv_ncci * ncci); +}; + +static struct nccistatechange nccitable[] = +{ + /* N-0 */ + {ST_NCCI_NONE, ST_NCCI_OUTGOING, EV_NCCI_CONNECT_B3_REQ, NULL}, + {ST_NCCI_NONE, ST_NCCI_INCOMING, EV_NCCI_CONNECT_B3_IND, NULL}, + /* N-0.1 */ + {ST_NCCI_OUTGOING, ST_NCCI_ALLOCATED, EV_NCCI_CONNECT_B3_CONF_OK, NULL}, + {ST_NCCI_OUTGOING, ST_NCCI_NONE, EV_NCCI_CONNECT_B3_CONF_ERROR, n0}, + /* N-1 */ + {ST_NCCI_INCOMING, ST_NCCI_DISCONNECTING, EV_NCCI_CONNECT_B3_REJECT, NULL}, + {ST_NCCI_INCOMING, ST_NCCI_ALLOCATED, EV_NCCI_CONNECT_B3_RESP, NULL}, + {ST_NCCI_INCOMING, ST_NCCI_DISCONNECTED, EV_NCCI_DISCONNECT_B3_IND, NULL}, + {ST_NCCI_INCOMING, ST_NCCI_DISCONNECTING, EV_NCCI_DISCONNECT_B3_REQ, NULL}, + /* N-2 */ + {ST_NCCI_ALLOCATED, ST_NCCI_ACTIVE, EV_NCCI_CONNECT_B3_ACTIVE_IND, NULL}, + {ST_NCCI_ALLOCATED, ST_NCCI_DISCONNECTED, EV_NCCI_DISCONNECT_B3_IND, NULL}, + {ST_NCCI_ALLOCATED, ST_NCCI_DISCONNECTING, EV_NCCI_DISCONNECT_B3_REQ, NULL}, + /* N-ACT */ + {ST_NCCI_ACTIVE, ST_NCCI_ACTIVE, EV_NCCI_RESET_B3_IND, NULL}, + {ST_NCCI_ACTIVE, ST_NCCI_RESETING, EV_NCCI_RESET_B3_REQ, NULL}, + {ST_NCCI_ACTIVE, ST_NCCI_DISCONNECTED, EV_NCCI_DISCONNECT_B3_IND, NULL}, + {ST_NCCI_ACTIVE, ST_NCCI_DISCONNECTING, EV_NCCI_DISCONNECT_B3_REQ, NULL}, + /* N-3 */ + {ST_NCCI_RESETING, ST_NCCI_ACTIVE, EV_NCCI_RESET_B3_IND, NULL}, + {ST_NCCI_RESETING, ST_NCCI_DISCONNECTED, EV_NCCI_DISCONNECT_B3_IND, NULL}, + {ST_NCCI_RESETING, ST_NCCI_DISCONNECTING, EV_NCCI_DISCONNECT_B3_REQ, NULL}, + /* N-4 */ + {ST_NCCI_DISCONNECTING, ST_NCCI_DISCONNECTED, EV_NCCI_DISCONNECT_B3_IND, NULL}, + {ST_NCCI_DISCONNECTING, ST_NCCI_PREVIOUS, EV_NCCI_DISCONNECT_B3_CONF_ERROR,NULL}, + /* N-5 */ + {ST_NCCI_DISCONNECTED, ST_NCCI_NONE, EV_NCCI_DISCONNECT_B3_RESP, n0}, + {}, +}; + +static void ncci_change_state(capidrv_contr * card, capidrv_ncci * ncci, int event) +{ + struct nccistatechange *p = nccitable; + while (p->event) { + if (ncci->state == p->actstate && p->event == event) { + if (debugmode) + printk(KERN_DEBUG "capidrv-%d: ncci_change_state:0x%x %d -> %d\n", + card->contrnr, ncci->ncci, ncci->state, p->nextstate); + if (p->nextstate == ST_NCCI_PREVIOUS) { + ncci->state = ncci->oldstate; + ncci->oldstate = p->actstate; + } else { + ncci->oldstate = p->actstate; + ncci->state = p->nextstate; + } + if (p->changefunc) + p->changefunc(card, ncci); + return; + } + p++; + } + printk(KERN_ERR "capidrv-%d: ncci_change_state:0x%x state=%d event=%d ????\n", + card->contrnr, ncci->ncci, ncci->state, event); +} + +/* ------------------------------------------------------------------- */ + +static inline int new_bchan(capidrv_contr * card) +{ + int i; + for (i = 0; i < card->nbchan; i++) { + if (card->bchans[i].plcip == 0) { + card->bchans[i].disconnecting = 0; + return i; + } + } + return -1; +} + +/* ------------------------------------------------------------------- */ + +static void handle_controller(_cmsg * cmsg) +{ + capidrv_contr *card = findcontrbynumber(cmsg->adr.adrController & 0x7f); + + if (!card) { + printk(KERN_ERR "capidrv: %s from unknown controller 0x%x\n", + capi_cmd2str(cmsg->Command, cmsg->Subcommand), + cmsg->adr.adrController & 0x7f); + return; + } + switch (CAPICMD(cmsg->Command, cmsg->Subcommand)) { + + case CAPI_LISTEN_CONF: /* Controller */ + if (debugmode) + printk(KERN_DEBUG "capidrv-%d: listenconf Info=0x%4x (%s) cipmask=0x%x\n", + card->contrnr, cmsg->Info, capi_info2str(cmsg->Info), card->cipmask); + if (cmsg->Info) { + listen_change_state(card, EV_LISTEN_CONF_ERROR); + } else if (card->cipmask == 0) { + listen_change_state(card, EV_LISTEN_CONF_EMPTY); + } else { + listen_change_state(card, EV_LISTEN_CONF_OK); + } + break; + + case CAPI_MANUFACTURER_IND: /* Controller */ + if ( cmsg->ManuID == 0x214D5641 + && cmsg->Class == 0 + && cmsg->Function == 1) { + u8 *data = cmsg->ManuData+3; + u16 len = cmsg->ManuData[0]; + u16 layer; + int direction; + if (len == 255) { + len = (cmsg->ManuData[1] | (cmsg->ManuData[2] << 8)); + data += 2; + } + len -= 2; + layer = ((*(data-1)) << 8) | *(data-2); + if (layer & 0x300) + direction = (layer & 0x200) ? 0 : 1; + else direction = (layer & 0x800) ? 0 : 1; + if (layer & 0x0C00) { + if ((layer & 0xff) == 0x80) { + handle_dtrace_data(card, direction, 1, data, len); + break; + } + } else if ((layer & 0xff) < 0x80) { + handle_dtrace_data(card, direction, 0, data, len); + break; + } + printk(KERN_INFO "capidrv-%d: %s from controller 0x%x layer 0x%x, ignored\n", + card->contrnr, + capi_cmd2str(cmsg->Command, cmsg->Subcommand), + cmsg->adr.adrController, layer); + break; + } + goto ignored; + case CAPI_MANUFACTURER_CONF: /* Controller */ + if (cmsg->ManuID == 0x214D5641) { + char *s = NULL; + switch (cmsg->Class) { + case 0: break; + case 1: s = "unknown class"; break; + case 2: s = "unknown function"; break; + default: s = "unkown error"; break; + } + if (s) + printk(KERN_INFO "capidrv-%d: %s from controller 0x%x function %d: %s\n", + card->contrnr, + capi_cmd2str(cmsg->Command, cmsg->Subcommand), + cmsg->adr.adrController, + cmsg->Function, s); + break; + } + goto ignored; + case CAPI_FACILITY_IND: /* Controller/plci/ncci */ + goto ignored; + case CAPI_FACILITY_CONF: /* Controller/plci/ncci */ + goto ignored; + case CAPI_INFO_IND: /* Controller/plci */ + goto ignored; + case CAPI_INFO_CONF: /* Controller/plci */ + goto ignored; + + default: + printk(KERN_ERR "capidrv-%d: got %s from controller 0x%x ???", + card->contrnr, + capi_cmd2str(cmsg->Command, cmsg->Subcommand), + cmsg->adr.adrController); + } + return; + + ignored: + printk(KERN_INFO "capidrv-%d: %s from controller 0x%x ignored\n", + card->contrnr, + capi_cmd2str(cmsg->Command, cmsg->Subcommand), + cmsg->adr.adrController); +} + +static void handle_incoming_call(capidrv_contr * card, _cmsg * cmsg) +{ + capidrv_plci *plcip; + capidrv_bchan *bchan; + isdn_ctrl cmd; + int chan; + + if ((chan = new_bchan(card)) == -1) { + printk(KERN_ERR "capidrv-%d: incoming call on not existing bchan ?\n", card->contrnr); + return; + } + bchan = &card->bchans[chan]; + if ((plcip = new_plci(card, chan)) == 0) { + printk(KERN_ERR "capidrv-%d: incoming call: no memory, sorry.\n", card->contrnr); + return; + } + bchan->incoming = 1; + plcip->plci = cmsg->adr.adrPLCI; + plci_change_state(card, plcip, EV_PLCI_CONNECT_IND); + + cmd.command = ISDN_STAT_ICALL; + cmd.driver = card->myid; + cmd.arg = chan; + memset(&cmd.parm.setup, 0, sizeof(cmd.parm.setup)); + strncpy(cmd.parm.setup.phone, + cmsg->CallingPartyNumber + 3, + cmsg->CallingPartyNumber[0] - 2); + strncpy(cmd.parm.setup.eazmsn, + cmsg->CalledPartyNumber + 2, + cmsg->CalledPartyNumber[0] - 1); + cmd.parm.setup.si1 = cip2si1(cmsg->CIPValue); + cmd.parm.setup.si2 = cip2si2(cmsg->CIPValue); + cmd.parm.setup.plan = cmsg->CallingPartyNumber[1]; + cmd.parm.setup.screen = cmsg->CallingPartyNumber[2]; + + printk(KERN_INFO "capidrv-%d: incoming call %s,%d,%d,%s\n", + card->contrnr, + cmd.parm.setup.phone, + cmd.parm.setup.si1, + cmd.parm.setup.si2, + cmd.parm.setup.eazmsn); + + if (cmd.parm.setup.si1 == 1 && cmd.parm.setup.si2 != 0) { + printk(KERN_INFO "capidrv-%d: patching si2=%d to 0 for VBOX\n", + card->contrnr, + cmd.parm.setup.si2); + cmd.parm.setup.si2 = 0; + } + + switch (card->interface.statcallb(&cmd)) { + case 0: + case 3: + /* No device matching this call. + * and isdn_common.c has send a HANGUP command + * which is ignored in state ST_PLCI_INCOMING, + * so we send RESP to ignore the call + */ + capi_cmsg_answer(cmsg); + cmsg->Reject = 1; /* ignore */ + send_message(card, cmsg); + plci_change_state(card, plcip, EV_PLCI_CONNECT_REJECT); + printk(KERN_INFO "capidrv-%d: incoming call %s,%d,%d,%s ignored\n", + card->contrnr, + cmd.parm.setup.phone, + cmd.parm.setup.si1, + cmd.parm.setup.si2, + cmd.parm.setup.eazmsn); + break; + case 1: + /* At least one device matching this call (RING on ttyI) + * HL-driver may send ALERTING on the D-channel in this + * case. + * really means: RING on ttyI or a net interface + * accepted this call already. + * + * If the call was accepted, state has already changed, + * and CONNECT_RESP already sent. + */ + if (plcip->state == ST_PLCI_INCOMING) { + printk(KERN_INFO "capidrv-%d: incoming call %s,%d,%d,%s tty alerting\n", + card->contrnr, + cmd.parm.setup.phone, + cmd.parm.setup.si1, + cmd.parm.setup.si2, + cmd.parm.setup.eazmsn); + capi_fill_ALERT_REQ(cmsg, + global.ap.applid, + card->msgid++, + plcip->plci, /* adr */ + NULL,/* BChannelinformation */ + NULL,/* Keypadfacility */ + NULL,/* Useruserdata */ + NULL /* Facilitydataarray */ + ); + plcip->msgid = cmsg->Messagenumber; + send_message(card, cmsg); + } else { + printk(KERN_INFO "capidrv-%d: incoming call %s,%d,%d,%s on netdev\n", + card->contrnr, + cmd.parm.setup.phone, + cmd.parm.setup.si1, + cmd.parm.setup.si2, + cmd.parm.setup.eazmsn); + } + break; + + case 2: /* Call will be rejected. */ + capi_cmsg_answer(cmsg); + cmsg->Reject = 2; /* reject call, normal call clearing */ + send_message(card, cmsg); + plci_change_state(card, plcip, EV_PLCI_CONNECT_REJECT); + break; + + default: + /* An error happened. (Invalid parameters for example.) */ + capi_cmsg_answer(cmsg); + cmsg->Reject = 8; /* reject call, + destination out of order */ + send_message(card, cmsg); + plci_change_state(card, plcip, EV_PLCI_CONNECT_REJECT); + break; + } + return; +} + +static void handle_plci(_cmsg * cmsg) +{ + capidrv_contr *card = findcontrbynumber(cmsg->adr.adrController & 0x7f); + capidrv_plci *plcip; + isdn_ctrl cmd; + + if (!card) { + printk(KERN_ERR "capidrv: %s from unknown controller 0x%x\n", + capi_cmd2str(cmsg->Command, cmsg->Subcommand), + cmsg->adr.adrController & 0x7f); + return; + } + switch (CAPICMD(cmsg->Command, cmsg->Subcommand)) { + + case CAPI_DISCONNECT_IND: /* plci */ + if (cmsg->Reason) { + printk(KERN_INFO "capidrv-%d: %s reason 0x%x (%s) for plci 0x%x\n", + card->contrnr, + capi_cmd2str(cmsg->Command, cmsg->Subcommand), + cmsg->Reason, capi_info2str(cmsg->Reason), cmsg->adr.adrPLCI); + } + if (!(plcip = find_plci_by_plci(card, cmsg->adr.adrPLCI))) { + capi_cmsg_answer(cmsg); + send_message(card, cmsg); + goto notfound; + } + card->bchans[plcip->chan].disconnecting = 1; + plci_change_state(card, plcip, EV_PLCI_DISCONNECT_IND); + capi_cmsg_answer(cmsg); + send_message(card, cmsg); + plci_change_state(card, plcip, EV_PLCI_DISCONNECT_RESP); + break; + + case CAPI_DISCONNECT_CONF: /* plci */ + if (cmsg->Info) { + printk(KERN_INFO "capidrv-%d: %s info 0x%x (%s) for plci 0x%x\n", + card->contrnr, + capi_cmd2str(cmsg->Command, cmsg->Subcommand), + cmsg->Info, capi_info2str(cmsg->Info), + cmsg->adr.adrPLCI); + } + if (!(plcip = find_plci_by_plci(card, cmsg->adr.adrPLCI))) + goto notfound; + + card->bchans[plcip->chan].disconnecting = 1; + break; + + case CAPI_ALERT_CONF: /* plci */ + if (cmsg->Info) { + printk(KERN_INFO "capidrv-%d: %s info 0x%x (%s) for plci 0x%x\n", + card->contrnr, + capi_cmd2str(cmsg->Command, cmsg->Subcommand), + cmsg->Info, capi_info2str(cmsg->Info), + cmsg->adr.adrPLCI); + } + break; + + case CAPI_CONNECT_IND: /* plci */ + handle_incoming_call(card, cmsg); + break; + + case CAPI_CONNECT_CONF: /* plci */ + if (cmsg->Info) { + printk(KERN_INFO "capidrv-%d: %s info 0x%x (%s) for plci 0x%x\n", + card->contrnr, + capi_cmd2str(cmsg->Command, cmsg->Subcommand), + cmsg->Info, capi_info2str(cmsg->Info), + cmsg->adr.adrPLCI); + } + if (!(plcip = find_plci_by_msgid(card, cmsg->Messagenumber))) + goto notfound; + + plcip->plci = cmsg->adr.adrPLCI; + if (cmsg->Info) { + plci_change_state(card, plcip, EV_PLCI_CONNECT_CONF_ERROR); + } else { + plci_change_state(card, plcip, EV_PLCI_CONNECT_CONF_OK); + } + break; + + case CAPI_CONNECT_ACTIVE_IND: /* plci */ + + if (!(plcip = find_plci_by_plci(card, cmsg->adr.adrPLCI))) + goto notfound; + + if (card->bchans[plcip->chan].incoming) { + capi_cmsg_answer(cmsg); + send_message(card, cmsg); + plci_change_state(card, plcip, EV_PLCI_CONNECT_ACTIVE_IND); + } else { + capidrv_ncci *nccip; + capi_cmsg_answer(cmsg); + send_message(card, cmsg); + + nccip = new_ncci(card, plcip, cmsg->adr.adrPLCI); + + if (!nccip) { + printk(KERN_ERR "capidrv-%d: no mem for ncci, sorry\n", card->contrnr); + break; /* $$$$ */ + } + capi_fill_CONNECT_B3_REQ(cmsg, + global.ap.applid, + card->msgid++, + plcip->plci, /* adr */ + NULL /* NCPI */ + ); + nccip->msgid = cmsg->Messagenumber; + send_message(card, cmsg); + cmd.command = ISDN_STAT_DCONN; + cmd.driver = card->myid; + cmd.arg = plcip->chan; + card->interface.statcallb(&cmd); + plci_change_state(card, plcip, EV_PLCI_CONNECT_ACTIVE_IND); + ncci_change_state(card, nccip, EV_NCCI_CONNECT_B3_REQ); + } + break; + + case CAPI_INFO_IND: /* Controller/plci */ + + if (!(plcip = find_plci_by_plci(card, cmsg->adr.adrPLCI))) + goto notfound; + + if (cmsg->InfoNumber == 0x4000) { + if (cmsg->InfoElement[0] == 4) { + cmd.command = ISDN_STAT_CINF; + cmd.driver = card->myid; + cmd.arg = plcip->chan; + sprintf(cmd.parm.num, "%lu", + (unsigned long) + ((u32) cmsg->InfoElement[1] + | ((u32) (cmsg->InfoElement[2]) << 8) + | ((u32) (cmsg->InfoElement[3]) << 16) + | ((u32) (cmsg->InfoElement[4]) << 24))); + card->interface.statcallb(&cmd); + break; + } + } + printk(KERN_ERR "capidrv-%d: %s\n", + card->contrnr, capi_cmsg2str(cmsg)); + break; + + case CAPI_CONNECT_ACTIVE_CONF: /* plci */ + goto ignored; + case CAPI_SELECT_B_PROTOCOL_CONF: /* plci */ + goto ignored; + case CAPI_FACILITY_IND: /* Controller/plci/ncci */ + goto ignored; + case CAPI_FACILITY_CONF: /* Controller/plci/ncci */ + goto ignored; + + case CAPI_INFO_CONF: /* Controller/plci */ + goto ignored; + + default: + printk(KERN_ERR "capidrv-%d: got %s for plci 0x%x ???", + card->contrnr, + capi_cmd2str(cmsg->Command, cmsg->Subcommand), + cmsg->adr.adrPLCI); + } + return; + ignored: + printk(KERN_INFO "capidrv-%d: %s for plci 0x%x ignored\n", + card->contrnr, + capi_cmd2str(cmsg->Command, cmsg->Subcommand), + cmsg->adr.adrPLCI); + return; + notfound: + printk(KERN_ERR "capidrv-%d: %s: plci 0x%x not found\n", + card->contrnr, + capi_cmd2str(cmsg->Command, cmsg->Subcommand), + cmsg->adr.adrPLCI); + return; +} + +static void handle_ncci(_cmsg * cmsg) +{ + capidrv_contr *card = findcontrbynumber(cmsg->adr.adrController & 0x7f); + capidrv_plci *plcip; + capidrv_ncci *nccip; + isdn_ctrl cmd; + int len; + + if (!card) { + printk(KERN_ERR "capidrv: %s from unknown controller 0x%x\n", + capi_cmd2str(cmsg->Command, cmsg->Subcommand), + cmsg->adr.adrController & 0x7f); + return; + } + switch (CAPICMD(cmsg->Command, cmsg->Subcommand)) { + + case CAPI_CONNECT_B3_ACTIVE_IND: /* ncci */ + if (!(nccip = find_ncci(card, cmsg->adr.adrNCCI))) + goto notfound; + + capi_cmsg_answer(cmsg); + send_message(card, cmsg); + ncci_change_state(card, nccip, EV_NCCI_CONNECT_B3_ACTIVE_IND); + + cmd.command = ISDN_STAT_BCONN; + cmd.driver = card->myid; + cmd.arg = nccip->chan; + card->interface.statcallb(&cmd); + + printk(KERN_INFO "capidrv-%d: chan %d up with ncci 0x%x\n", + card->contrnr, nccip->chan, nccip->ncci); + break; + + case CAPI_CONNECT_B3_ACTIVE_CONF: /* ncci */ + goto ignored; + + case CAPI_CONNECT_B3_IND: /* ncci */ + + plcip = find_plci_by_ncci(card, cmsg->adr.adrNCCI); + if (plcip) { + nccip = new_ncci(card, plcip, cmsg->adr.adrNCCI); + if (nccip) { + ncci_change_state(card, nccip, EV_NCCI_CONNECT_B3_IND); + capi_fill_CONNECT_B3_RESP(cmsg, + global.ap.applid, + card->msgid++, + nccip->ncci, /* adr */ + 0, /* Reject */ + NULL /* NCPI */ + ); + send_message(card, cmsg); + ncci_change_state(card, nccip, EV_NCCI_CONNECT_B3_RESP); + break; + } + printk(KERN_ERR "capidrv-%d: no mem for ncci, sorry\n", card->contrnr); + } else { + printk(KERN_ERR "capidrv-%d: %s: plci for ncci 0x%x not found\n", + card->contrnr, + capi_cmd2str(cmsg->Command, cmsg->Subcommand), + cmsg->adr.adrNCCI); + } + capi_fill_CONNECT_B3_RESP(cmsg, + global.ap.applid, + card->msgid++, + cmsg->adr.adrNCCI, + 2, /* Reject */ + NULL /* NCPI */ + ); + send_message(card, cmsg); + break; + + case CAPI_CONNECT_B3_CONF: /* ncci */ + + if (!(nccip = find_ncci_by_msgid(card, + cmsg->adr.adrNCCI, + cmsg->Messagenumber))) + goto notfound; + + nccip->ncci = cmsg->adr.adrNCCI; + if (cmsg->Info) { + printk(KERN_INFO "capidrv-%d: %s info 0x%x (%s) for ncci 0x%x\n", + card->contrnr, + capi_cmd2str(cmsg->Command, cmsg->Subcommand), + cmsg->Info, capi_info2str(cmsg->Info), + cmsg->adr.adrNCCI); + } + + if (cmsg->Info) + ncci_change_state(card, nccip, EV_NCCI_CONNECT_B3_CONF_ERROR); + else + ncci_change_state(card, nccip, EV_NCCI_CONNECT_B3_CONF_OK); + break; + + case CAPI_CONNECT_B3_T90_ACTIVE_IND: /* ncci */ + capi_cmsg_answer(cmsg); + send_message(card, cmsg); + break; + + case CAPI_DATA_B3_IND: /* ncci */ + /* handled in handle_data() */ + goto ignored; + + case CAPI_DATA_B3_CONF: /* ncci */ + if (cmsg->Info) { + printk(KERN_WARNING "CAPI_DATA_B3_CONF: Info %x - %s\n", + cmsg->Info, capi_info2str(cmsg->Info)); + } + if (!(nccip = find_ncci(card, cmsg->adr.adrNCCI))) + goto notfound; + + len = capidrv_del_ack(nccip, cmsg->DataHandle); + if (len < 0) + break; + cmd.command = ISDN_STAT_BSENT; + cmd.driver = card->myid; + cmd.arg = nccip->chan; + cmd.parm.length = len; + card->interface.statcallb(&cmd); + break; + + case CAPI_DISCONNECT_B3_IND: /* ncci */ + if (!(nccip = find_ncci(card, cmsg->adr.adrNCCI))) + goto notfound; + + card->bchans[nccip->chan].disconnecting = 1; + ncci_change_state(card, nccip, EV_NCCI_DISCONNECT_B3_IND); + capi_cmsg_answer(cmsg); + send_message(card, cmsg); + ncci_change_state(card, nccip, EV_NCCI_DISCONNECT_B3_RESP); + break; + + case CAPI_DISCONNECT_B3_CONF: /* ncci */ + if (!(nccip = find_ncci(card, cmsg->adr.adrNCCI))) + goto notfound; + if (cmsg->Info) { + printk(KERN_INFO "capidrv-%d: %s info 0x%x (%s) for ncci 0x%x\n", + card->contrnr, + capi_cmd2str(cmsg->Command, cmsg->Subcommand), + cmsg->Info, capi_info2str(cmsg->Info), + cmsg->adr.adrNCCI); + ncci_change_state(card, nccip, EV_NCCI_DISCONNECT_B3_CONF_ERROR); + } + break; + + case CAPI_RESET_B3_IND: /* ncci */ + if (!(nccip = find_ncci(card, cmsg->adr.adrNCCI))) + goto notfound; + ncci_change_state(card, nccip, EV_NCCI_RESET_B3_IND); + capi_cmsg_answer(cmsg); + send_message(card, cmsg); + break; + + case CAPI_RESET_B3_CONF: /* ncci */ + goto ignored; /* $$$$ */ + + case CAPI_FACILITY_IND: /* Controller/plci/ncci */ + goto ignored; + case CAPI_FACILITY_CONF: /* Controller/plci/ncci */ + goto ignored; + + default: + printk(KERN_ERR "capidrv-%d: got %s for ncci 0x%x ???", + card->contrnr, + capi_cmd2str(cmsg->Command, cmsg->Subcommand), + cmsg->adr.adrNCCI); + } + return; + ignored: + printk(KERN_INFO "capidrv-%d: %s for ncci 0x%x ignored\n", + card->contrnr, + capi_cmd2str(cmsg->Command, cmsg->Subcommand), + cmsg->adr.adrNCCI); + return; + notfound: + printk(KERN_ERR "capidrv-%d: %s: ncci 0x%x not found\n", + card->contrnr, + capi_cmd2str(cmsg->Command, cmsg->Subcommand), + cmsg->adr.adrNCCI); +} + + +static void handle_data(_cmsg * cmsg, struct sk_buff *skb) +{ + capidrv_contr *card = findcontrbynumber(cmsg->adr.adrController & 0x7f); + capidrv_ncci *nccip; + + if (!card) { + printk(KERN_ERR "capidrv: %s from unknown controller 0x%x\n", + capi_cmd2str(cmsg->Command, cmsg->Subcommand), + cmsg->adr.adrController & 0x7f); + kfree_skb(skb); + return; + } + if (!(nccip = find_ncci(card, cmsg->adr.adrNCCI))) { + printk(KERN_ERR "capidrv-%d: %s: ncci 0x%x not found\n", + card->contrnr, + capi_cmd2str(cmsg->Command, cmsg->Subcommand), + cmsg->adr.adrNCCI); + kfree_skb(skb); + return; + } + (void) skb_pull(skb, CAPIMSG_LEN(skb->data)); + card->interface.rcvcallb_skb(card->myid, nccip->chan, skb); + capi_cmsg_answer(cmsg); + send_message(card, cmsg); +} + +static _cmsg s_cmsg; + +static void capidrv_recv_message(struct capi20_appl *ap, struct sk_buff *skb) +{ + capi_message2cmsg(&s_cmsg, skb->data); + if (debugmode > 3) + printk(KERN_DEBUG "capidrv_signal: applid=%d %s\n", + ap->applid, capi_cmsg2str(&s_cmsg)); + + if (s_cmsg.Command == CAPI_DATA_B3 + && s_cmsg.Subcommand == CAPI_IND) { + handle_data(&s_cmsg, skb); + return; + } + if ((s_cmsg.adr.adrController & 0xffffff00) == 0) + handle_controller(&s_cmsg); + else if ((s_cmsg.adr.adrPLCI & 0xffff0000) == 0) + handle_plci(&s_cmsg); + else + handle_ncci(&s_cmsg); + /* + * data of skb used in s_cmsg, + * free data when s_cmsg is not used again + * thanks to Lars Heete <hel@admin.de> + */ + kfree_skb(skb); +} + +/* ------------------------------------------------------------------- */ + +#define PUTBYTE_TO_STATUS(card, byte) \ + do { \ + *(card)->q931_write++ = (byte); \ + if ((card)->q931_write > (card)->q931_end) \ + (card)->q931_write = (card)->q931_buf; \ + } while (0) + +static void handle_dtrace_data(capidrv_contr *card, + int send, int level2, u8 *data, u16 len) +{ + u8 *p, *end; + isdn_ctrl cmd; + + if (!len) { + printk(KERN_DEBUG "capidrv-%d: avmb1_q931_data: len == %d\n", + card->contrnr, len); + return; + } + + if (level2) { + PUTBYTE_TO_STATUS(card, 'D'); + PUTBYTE_TO_STATUS(card, '2'); + PUTBYTE_TO_STATUS(card, send ? '>' : '<'); + PUTBYTE_TO_STATUS(card, ':'); + } else { + PUTBYTE_TO_STATUS(card, 'D'); + PUTBYTE_TO_STATUS(card, '3'); + PUTBYTE_TO_STATUS(card, send ? '>' : '<'); + PUTBYTE_TO_STATUS(card, ':'); + } + + for (p = data, end = data+len; p < end; p++) { + u8 w; + PUTBYTE_TO_STATUS(card, ' '); + w = (*p >> 4) & 0xf; + PUTBYTE_TO_STATUS(card, (w < 10) ? '0'+w : 'A'-10+w); + w = *p & 0xf; + PUTBYTE_TO_STATUS(card, (w < 10) ? '0'+w : 'A'-10+w); + } + PUTBYTE_TO_STATUS(card, '\n'); + + cmd.command = ISDN_STAT_STAVAIL; + cmd.driver = card->myid; + cmd.arg = len*3+5; + card->interface.statcallb(&cmd); +} + +/* ------------------------------------------------------------------- */ + +static _cmsg cmdcmsg; + +static int capidrv_ioctl(isdn_ctrl * c, capidrv_contr * card) +{ + switch (c->arg) { + case 1: + debugmode = (int)(*((unsigned int *)c->parm.num)); + printk(KERN_DEBUG "capidrv-%d: debugmode=%d\n", + card->contrnr, debugmode); + return 0; + default: + printk(KERN_DEBUG "capidrv-%d: capidrv_ioctl(%ld) called ??\n", + card->contrnr, c->arg); + return -EINVAL; + } + return -EINVAL; +} + +/* + * Handle leased lines (CAPI-Bundling) + */ + +struct internal_bchannelinfo { + unsigned short channelalloc; + unsigned short operation; + unsigned char cmask[31]; +}; + +static int decodeFVteln(char *teln, unsigned long *bmaskp, int *activep) +{ + unsigned long bmask = 0; + int active = !0; + char *s; + int i; + + if (strncmp(teln, "FV:", 3) != 0) + return 1; + s = teln + 3; + while (*s && *s == ' ') s++; + if (!*s) return -2; + if (*s == 'p' || *s == 'P') { + active = 0; + s++; + } + if (*s == 'a' || *s == 'A') { + active = !0; + s++; + } + while (*s) { + int digit1 = 0; + int digit2 = 0; + if (!isdigit(*s)) return -3; + while (isdigit(*s)) { digit1 = digit1*10 + (*s - '0'); s++; } + if (digit1 <= 0 && digit1 > 30) return -4; + if (*s == 0 || *s == ',' || *s == ' ') { + bmask |= (1 << digit1); + digit1 = 0; + if (*s) s++; + continue; + } + if (*s != '-') return -5; + s++; + if (!isdigit(*s)) return -3; + while (isdigit(*s)) { digit2 = digit2*10 + (*s - '0'); s++; } + if (digit2 <= 0 && digit2 > 30) return -4; + if (*s == 0 || *s == ',' || *s == ' ') { + if (digit1 > digit2) + for (i = digit2; i <= digit1 ; i++) + bmask |= (1 << i); + else + for (i = digit1; i <= digit2 ; i++) + bmask |= (1 << i); + digit1 = digit2 = 0; + if (*s) s++; + continue; + } + return -6; + } + if (activep) *activep = active; + if (bmaskp) *bmaskp = bmask; + return 0; +} + +static int FVteln2capi20(char *teln, u8 AdditionalInfo[1+2+2+31]) +{ + unsigned long bmask; + int active; + int rc, i; + + rc = decodeFVteln(teln, &bmask, &active); + if (rc) return rc; + /* Length */ + AdditionalInfo[0] = 2+2+31; + /* Channel: 3 => use channel allocation */ + AdditionalInfo[1] = 3; AdditionalInfo[2] = 0; + /* Operation: 0 => DTE mode, 1 => DCE mode */ + if (active) { + AdditionalInfo[3] = 0; AdditionalInfo[4] = 0; + } else { + AdditionalInfo[3] = 1; AdditionalInfo[4] = 0; + } + /* Channel mask array */ + AdditionalInfo[5] = 0; /* no D-Channel */ + for (i=1; i <= 30; i++) + AdditionalInfo[5+i] = (bmask & (1 << i)) ? 0xff : 0; + return 0; +} + +static int capidrv_command(isdn_ctrl * c, capidrv_contr * card) +{ + isdn_ctrl cmd; + struct capidrv_bchan *bchan; + struct capidrv_plci *plcip; + u8 AdditionalInfo[1+2+2+31]; + int rc, isleasedline = 0; + + if (c->command == ISDN_CMD_IOCTL) + return capidrv_ioctl(c, card); + + switch (c->command) { + case ISDN_CMD_DIAL:{ + u8 calling[ISDN_MSNLEN + 3]; + u8 called[ISDN_MSNLEN + 2]; + + if (debugmode) + printk(KERN_DEBUG "capidrv-%d: ISDN_CMD_DIAL(ch=%ld,\"%s,%d,%d,%s\")\n", + card->contrnr, + c->arg, + c->parm.setup.phone, + c->parm.setup.si1, + c->parm.setup.si2, + c->parm.setup.eazmsn); + + bchan = &card->bchans[c->arg % card->nbchan]; + + if (bchan->plcip) { + printk(KERN_ERR "capidrv-%d: dail ch=%ld,\"%s,%d,%d,%s\" in use (plci=0x%x)\n", + card->contrnr, + c->arg, + c->parm.setup.phone, + c->parm.setup.si1, + c->parm.setup.si2, + c->parm.setup.eazmsn, + bchan->plcip->plci); + return 0; + } + bchan->si1 = c->parm.setup.si1; + bchan->si2 = c->parm.setup.si2; + + strncpy(bchan->num, c->parm.setup.phone, sizeof(bchan->num)); + strncpy(bchan->mynum, c->parm.setup.eazmsn, sizeof(bchan->mynum)); + rc = FVteln2capi20(bchan->num, AdditionalInfo); + isleasedline = (rc == 0); + if (rc < 0) + printk(KERN_ERR "capidrv-%d: WARNING: invalid leased linedefinition \"%s\"\n", card->contrnr, bchan->num); + + if (isleasedline) { + calling[0] = 0; + called[0] = 0; + if (debugmode) + printk(KERN_DEBUG "capidrv-%d: connecting leased line\n", card->contrnr); + } else { + calling[0] = strlen(bchan->mynum) + 2; + calling[1] = 0; + calling[2] = 0x80; + strncpy(calling + 3, bchan->mynum, ISDN_MSNLEN); + called[0] = strlen(bchan->num) + 1; + called[1] = 0x80; + strncpy(called + 2, bchan->num, ISDN_MSNLEN); + } + + capi_fill_CONNECT_REQ(&cmdcmsg, + global.ap.applid, + card->msgid++, + card->contrnr, /* adr */ + si2cip(bchan->si1, bchan->si2), /* cipvalue */ + called, /* CalledPartyNumber */ + calling, /* CallingPartyNumber */ + NULL, /* CalledPartySubaddress */ + NULL, /* CallingPartySubaddress */ + b1prot(bchan->l2, bchan->l3), /* B1protocol */ + b2prot(bchan->l2, bchan->l3), /* B2protocol */ + b3prot(bchan->l2, bchan->l3), /* B3protocol */ + b1config(bchan->l2, bchan->l3), /* B1configuration */ + NULL, /* B2configuration */ + NULL, /* B3configuration */ + NULL, /* BC */ + NULL, /* LLC */ + NULL, /* HLC */ + /* BChannelinformation */ + isleasedline ? AdditionalInfo : NULL, + NULL, /* Keypadfacility */ + NULL, /* Useruserdata */ + NULL /* Facilitydataarray */ + ); + if ((plcip = new_plci(card, (c->arg % card->nbchan))) == 0) { + cmd.command = ISDN_STAT_DHUP; + cmd.driver = card->myid; + cmd.arg = (c->arg % card->nbchan); + card->interface.statcallb(&cmd); + return -1; + } + plcip->msgid = cmdcmsg.Messagenumber; + plcip->leasedline = isleasedline; + plci_change_state(card, plcip, EV_PLCI_CONNECT_REQ); + send_message(card, &cmdcmsg); + return 0; + } + + case ISDN_CMD_ACCEPTD: + + bchan = &card->bchans[c->arg % card->nbchan]; + if (debugmode) + printk(KERN_DEBUG "capidrv-%d: ISDN_CMD_ACCEPTD(ch=%ld) l2=%d l3=%d\n", + card->contrnr, + c->arg, bchan->l2, bchan->l3); + + capi_fill_CONNECT_RESP(&cmdcmsg, + global.ap.applid, + card->msgid++, + bchan->plcip->plci, /* adr */ + 0, /* Reject */ + b1prot(bchan->l2, bchan->l3), /* B1protocol */ + b2prot(bchan->l2, bchan->l3), /* B2protocol */ + b3prot(bchan->l2, bchan->l3), /* B3protocol */ + b1config(bchan->l2, bchan->l3), /* B1configuration */ + NULL, /* B2configuration */ + NULL, /* B3configuration */ + NULL, /* ConnectedNumber */ + NULL, /* ConnectedSubaddress */ + NULL, /* LLC */ + NULL, /* BChannelinformation */ + NULL, /* Keypadfacility */ + NULL, /* Useruserdata */ + NULL /* Facilitydataarray */ + ); + capi_cmsg2message(&cmdcmsg, cmdcmsg.buf); + plci_change_state(card, bchan->plcip, EV_PLCI_CONNECT_RESP); + send_message(card, &cmdcmsg); + return 0; + + case ISDN_CMD_ACCEPTB: + if (debugmode) + printk(KERN_DEBUG "capidrv-%d: ISDN_CMD_ACCEPTB(ch=%ld)\n", + card->contrnr, + c->arg); + return -ENOSYS; + + case ISDN_CMD_HANGUP: + if (debugmode) + printk(KERN_DEBUG "capidrv-%d: ISDN_CMD_HANGUP(ch=%ld)\n", + card->contrnr, + c->arg); + bchan = &card->bchans[c->arg % card->nbchan]; + + if (bchan->disconnecting) { + if (debugmode) + printk(KERN_DEBUG "capidrv-%d: chan %ld already disconnecting ...\n", + card->contrnr, + c->arg); + return 0; + } + if (bchan->nccip) { + bchan->disconnecting = 1; + capi_fill_DISCONNECT_B3_REQ(&cmdcmsg, + global.ap.applid, + card->msgid++, + bchan->nccip->ncci, + NULL /* NCPI */ + ); + ncci_change_state(card, bchan->nccip, EV_NCCI_DISCONNECT_B3_REQ); + send_message(card, &cmdcmsg); + return 0; + } else if (bchan->plcip) { + if (bchan->plcip->state == ST_PLCI_INCOMING) { + /* + * just ignore, we a called from + * isdn_status_callback(), + * which will return 0 or 2, this is handled + * by the CONNECT_IND handler + */ + bchan->disconnecting = 1; + return 0; + } else if (bchan->plcip->plci) { + bchan->disconnecting = 1; + capi_fill_DISCONNECT_REQ(&cmdcmsg, + global.ap.applid, + card->msgid++, + bchan->plcip->plci, + NULL, /* BChannelinformation */ + NULL, /* Keypadfacility */ + NULL, /* Useruserdata */ + NULL /* Facilitydataarray */ + ); + plci_change_state(card, bchan->plcip, EV_PLCI_DISCONNECT_REQ); + send_message(card, &cmdcmsg); + return 0; + } else { + printk(KERN_ERR "capidrv-%d: chan %ld disconnect request while waiting for CONNECT_CONF\n", + card->contrnr, + c->arg); + return -EINVAL; + } + } + printk(KERN_ERR "capidrv-%d: chan %ld disconnect request on free channel\n", + card->contrnr, + c->arg); + return -EINVAL; +/* ready */ + + case ISDN_CMD_SETL2: + if (debugmode) + printk(KERN_DEBUG "capidrv-%d: set L2 on chan %ld to %ld\n", + card->contrnr, + (c->arg & 0xff), (c->arg >> 8)); + bchan = &card->bchans[(c->arg & 0xff) % card->nbchan]; + bchan->l2 = (c->arg >> 8); + return 0; + + case ISDN_CMD_SETL3: + if (debugmode) + printk(KERN_DEBUG "capidrv-%d: set L3 on chan %ld to %ld\n", + card->contrnr, + (c->arg & 0xff), (c->arg >> 8)); + bchan = &card->bchans[(c->arg & 0xff) % card->nbchan]; + bchan->l3 = (c->arg >> 8); + return 0; + + case ISDN_CMD_SETEAZ: + if (debugmode) + printk(KERN_DEBUG "capidrv-%d: set EAZ \"%s\" on chan %ld\n", + card->contrnr, + c->parm.num, c->arg); + bchan = &card->bchans[c->arg % card->nbchan]; + strncpy(bchan->msn, c->parm.num, ISDN_MSNLEN); + return 0; + + case ISDN_CMD_CLREAZ: + if (debugmode) + printk(KERN_DEBUG "capidrv-%d: clearing EAZ on chan %ld\n", + card->contrnr, c->arg); + bchan = &card->bchans[c->arg % card->nbchan]; + bchan->msn[0] = 0; + return 0; + + default: + printk(KERN_ERR "capidrv-%d: ISDN_CMD_%d, Huh?\n", + card->contrnr, c->command); + return -EINVAL; + } + return 0; +} + +static int if_command(isdn_ctrl * c) +{ + capidrv_contr *card = findcontrbydriverid(c->driver); + + if (card) + return capidrv_command(c, card); + + printk(KERN_ERR + "capidrv: if_command %d called with invalid driverId %d!\n", + c->command, c->driver); + return -ENODEV; +} + +static _cmsg sendcmsg; + +static int if_sendbuf(int id, int channel, int doack, struct sk_buff *skb) +{ + capidrv_contr *card = findcontrbydriverid(id); + capidrv_bchan *bchan; + capidrv_ncci *nccip; + int len = skb->len; + int msglen; + u16 errcode; + u16 datahandle; + + if (!card) { + printk(KERN_ERR "capidrv: if_sendbuf called with invalid driverId %d!\n", + id); + return 0; + } + if (debugmode > 4) + printk(KERN_DEBUG "capidrv-%d: sendbuf len=%d skb=%p doack=%d\n", + card->contrnr, len, skb, doack); + bchan = &card->bchans[channel % card->nbchan]; + nccip = bchan->nccip; + if (!nccip || nccip->state != ST_NCCI_ACTIVE) { + printk(KERN_ERR "capidrv-%d: if_sendbuf: %s:%d: chan not up!\n", + card->contrnr, card->name, channel); + return 0; + } + datahandle = nccip->datahandle; + capi_fill_DATA_B3_REQ(&sendcmsg, global.ap.applid, card->msgid++, + nccip->ncci, /* adr */ + (u32) skb->data, /* Data */ + skb->len, /* DataLength */ + datahandle, /* DataHandle */ + 0 /* Flags */ + ); + + if (capidrv_add_ack(nccip, datahandle, doack ? (int)skb->len : -1) < 0) + return 0; + + capi_cmsg2message(&sendcmsg, sendcmsg.buf); + msglen = CAPIMSG_LEN(sendcmsg.buf); + if (skb_headroom(skb) < msglen) { + struct sk_buff *nskb = skb_realloc_headroom(skb, msglen); + if (!nskb) { + printk(KERN_ERR "capidrv-%d: if_sendbuf: no memory\n", + card->contrnr); + (void)capidrv_del_ack(nccip, datahandle); + return 0; + } + printk(KERN_DEBUG "capidrv-%d: only %d bytes headroom, need %d\n", + card->contrnr, skb_headroom(skb), msglen); + memcpy(skb_push(nskb, msglen), sendcmsg.buf, msglen); + errcode = capi20_put_message(&global.ap, nskb); + if (errcode == CAPI_NOERROR) { + dev_kfree_skb(skb); + nccip->datahandle++; + return len; + } + if (debugmode > 3) + printk(KERN_DEBUG "capidrv-%d: sendbuf putmsg ret(%x) - %s\n", + card->contrnr, errcode, capi_info2str(errcode)); + (void)capidrv_del_ack(nccip, datahandle); + dev_kfree_skb(nskb); + return errcode == CAPI_SENDQUEUEFULL ? 0 : -1; + } else { + memcpy(skb_push(skb, msglen), sendcmsg.buf, msglen); + errcode = capi20_put_message(&global.ap, skb); + if (errcode == CAPI_NOERROR) { + nccip->datahandle++; + return len; + } + if (debugmode > 3) + printk(KERN_DEBUG "capidrv-%d: sendbuf putmsg ret(%x) - %s\n", + card->contrnr, errcode, capi_info2str(errcode)); + skb_pull(skb, msglen); + (void)capidrv_del_ack(nccip, datahandle); + return errcode == CAPI_SENDQUEUEFULL ? 0 : -1; + } +} + +static int if_readstat(u8 __user *buf, int len, int id, int channel) +{ + capidrv_contr *card = findcontrbydriverid(id); + int count; + u8 __user *p; + + if (!card) { + printk(KERN_ERR "capidrv: if_readstat called with invalid driverId %d!\n", + id); + return -ENODEV; + } + + for (p=buf, count=0; count < len; p++, count++) { + put_user(*card->q931_read++, p); + if (card->q931_read > card->q931_end) + card->q931_read = card->q931_buf; + } + return count; + +} + +static void enable_dchannel_trace(capidrv_contr *card) +{ + u8 manufacturer[CAPI_MANUFACTURER_LEN]; + capi_version version; + u16 contr = card->contrnr; + u16 errcode; + u16 avmversion[3]; + + errcode = capi20_get_manufacturer(contr, manufacturer); + if (errcode != CAPI_NOERROR) { + printk(KERN_ERR "%s: can't get manufacturer (0x%x)\n", + card->name, errcode); + return; + } + if (strstr(manufacturer, "AVM") == 0) { + printk(KERN_ERR "%s: not from AVM, no d-channel trace possible (%s)\n", + card->name, manufacturer); + return; + } + errcode = capi20_get_version(contr, &version); + if (errcode != CAPI_NOERROR) { + printk(KERN_ERR "%s: can't get version (0x%x)\n", + card->name, errcode); + return; + } + avmversion[0] = (version.majormanuversion >> 4) & 0x0f; + avmversion[1] = (version.majormanuversion << 4) & 0xf0; + avmversion[1] |= (version.minormanuversion >> 4) & 0x0f; + avmversion[2] |= version.minormanuversion & 0x0f; + + if (avmversion[0] > 3 || (avmversion[0] == 3 && avmversion[1] > 5)) { + printk(KERN_INFO "%s: D2 trace enabled\n", card->name); + capi_fill_MANUFACTURER_REQ(&cmdcmsg, global.ap.applid, + card->msgid++, + contr, + 0x214D5641, /* ManuID */ + 0, /* Class */ + 1, /* Function */ + (_cstruct)"\004\200\014\000\000"); + } else { + printk(KERN_INFO "%s: D3 trace enabled\n", card->name); + capi_fill_MANUFACTURER_REQ(&cmdcmsg, global.ap.applid, + card->msgid++, + contr, + 0x214D5641, /* ManuID */ + 0, /* Class */ + 1, /* Function */ + (_cstruct)"\004\002\003\000\000"); + } + send_message(card, &cmdcmsg); +} + + +static void send_listen(capidrv_contr *card) +{ + capi_fill_LISTEN_REQ(&cmdcmsg, global.ap.applid, + card->msgid++, + card->contrnr, /* controller */ + 1 << 6, /* Infomask */ + card->cipmask, + card->cipmask2, + NULL, NULL); + send_message(card, &cmdcmsg); + listen_change_state(card, EV_LISTEN_REQ); +} + +static void listentimerfunc(unsigned long x) +{ + capidrv_contr *card = (capidrv_contr *)x; + if (card->state != ST_LISTEN_NONE && card->state != ST_LISTEN_ACTIVE) + printk(KERN_ERR "%s: controller dead ??\n", card->name); + send_listen(card); + mod_timer(&card->listentimer, jiffies + 60*HZ); +} + + +static int capidrv_addcontr(u16 contr, struct capi_profile *profp) +{ + capidrv_contr *card; + unsigned long flags; + isdn_ctrl cmd; + char id[20]; + int i; + + sprintf(id, "capidrv-%d", contr); + if (!try_module_get(THIS_MODULE)) { + printk(KERN_WARNING "capidrv: (%s) Could not reserve module\n", id); + return -1; + } + if (!(card = (capidrv_contr *) kmalloc(sizeof(capidrv_contr), GFP_ATOMIC))) { + printk(KERN_WARNING + "capidrv: (%s) Could not allocate contr-struct.\n", id); + return -1; + } + memset(card, 0, sizeof(capidrv_contr)); + card->owner = THIS_MODULE; + init_timer(&card->listentimer); + strcpy(card->name, id); + card->contrnr = contr; + card->nbchan = profp->nbchannel; + card->bchans = (capidrv_bchan *) kmalloc(sizeof(capidrv_bchan) * card->nbchan, GFP_ATOMIC); + if (!card->bchans) { + printk(KERN_WARNING + "capidrv: (%s) Could not allocate bchan-structs.\n", id); + module_put(card->owner); + kfree(card); + return -1; + } + card->interface.channels = profp->nbchannel; + card->interface.maxbufsize = 2048; + card->interface.command = if_command; + card->interface.writebuf_skb = if_sendbuf; + card->interface.writecmd = NULL; + card->interface.readstat = if_readstat; + card->interface.features = ISDN_FEATURE_L2_HDLC | + ISDN_FEATURE_L2_TRANS | + ISDN_FEATURE_L3_TRANS | + ISDN_FEATURE_P_UNKNOWN | + ISDN_FEATURE_L2_X75I | + ISDN_FEATURE_L2_X75UI | + ISDN_FEATURE_L2_X75BUI; + if (profp->support1 & (1<<2)) + card->interface.features |= ISDN_FEATURE_L2_V11096 | + ISDN_FEATURE_L2_V11019 | + ISDN_FEATURE_L2_V11038; + if (profp->support1 & (1<<8)) + card->interface.features |= ISDN_FEATURE_L2_MODEM; + card->interface.hl_hdrlen = 22; /* len of DATA_B3_REQ */ + strncpy(card->interface.id, id, sizeof(card->interface.id) - 1); + + + card->q931_read = card->q931_buf; + card->q931_write = card->q931_buf; + card->q931_end = card->q931_buf + sizeof(card->q931_buf) - 1; + + if (!register_isdn(&card->interface)) { + printk(KERN_ERR "capidrv: Unable to register contr %s\n", id); + kfree(card->bchans); + module_put(card->owner); + kfree(card); + return -1; + } + card->myid = card->interface.channels; + memset(card->bchans, 0, sizeof(capidrv_bchan) * card->nbchan); + for (i = 0; i < card->nbchan; i++) { + card->bchans[i].contr = card; + } + + spin_lock_irqsave(&global_lock, flags); + card->next = global.contr_list; + global.contr_list = card; + global.ncontr++; + spin_unlock_irqrestore(&global_lock, flags); + + cmd.command = ISDN_STAT_RUN; + cmd.driver = card->myid; + card->interface.statcallb(&cmd); + + card->cipmask = 0x1FFF03FF; /* any */ + card->cipmask2 = 0; + + card->listentimer.data = (unsigned long)card; + card->listentimer.function = listentimerfunc; + send_listen(card); + mod_timer(&card->listentimer, jiffies + 60*HZ); + + printk(KERN_INFO "%s: now up (%d B channels)\n", + card->name, card->nbchan); + + enable_dchannel_trace(card); + + return 0; +} + +static int capidrv_delcontr(u16 contr) +{ + capidrv_contr **pp, *card; + unsigned long flags; + isdn_ctrl cmd; + + spin_lock_irqsave(&global_lock, flags); + for (card = global.contr_list; card; card = card->next) { + if (card->contrnr == contr) + break; + } + if (!card) { + spin_unlock_irqrestore(&global_lock, flags); + printk(KERN_ERR "capidrv: delcontr: no contr %u\n", contr); + return -1; + } + #warning FIXME: maybe a race condition the card should be removed here from global list /kkeil + spin_unlock_irqrestore(&global_lock, flags); + + del_timer(&card->listentimer); + + if (debugmode) + printk(KERN_DEBUG "capidrv-%d: id=%d unloading\n", + card->contrnr, card->myid); + + cmd.command = ISDN_STAT_STOP; + cmd.driver = card->myid; + card->interface.statcallb(&cmd); + + while (card->nbchan) { + + cmd.command = ISDN_STAT_DISCH; + cmd.driver = card->myid; + cmd.arg = card->nbchan-1; + cmd.parm.num[0] = 0; + if (debugmode) + printk(KERN_DEBUG "capidrv-%d: id=%d disable chan=%ld\n", + card->contrnr, card->myid, cmd.arg); + card->interface.statcallb(&cmd); + + if (card->bchans[card->nbchan-1].nccip) + free_ncci(card, card->bchans[card->nbchan-1].nccip); + if (card->bchans[card->nbchan-1].plcip) + free_plci(card, card->bchans[card->nbchan-1].plcip); + if (card->plci_list) + printk(KERN_ERR "capidrv: bug in free_plci()\n"); + card->nbchan--; + } + kfree(card->bchans); + card->bchans = NULL; + + if (debugmode) + printk(KERN_DEBUG "capidrv-%d: id=%d isdn unload\n", + card->contrnr, card->myid); + + cmd.command = ISDN_STAT_UNLOAD; + cmd.driver = card->myid; + card->interface.statcallb(&cmd); + + if (debugmode) + printk(KERN_DEBUG "capidrv-%d: id=%d remove contr from list\n", + card->contrnr, card->myid); + + spin_lock_irqsave(&global_lock, flags); + for (pp = &global.contr_list; *pp; pp = &(*pp)->next) { + if (*pp == card) { + *pp = (*pp)->next; + card->next = NULL; + global.ncontr--; + break; + } + } + spin_unlock_irqrestore(&global_lock, flags); + + module_put(card->owner); + printk(KERN_INFO "%s: now down.\n", card->name); + kfree(card); + return 0; +} + + +static void lower_callback(unsigned int cmd, u32 contr, void *data) +{ + + switch (cmd) { + case KCI_CONTRUP: + printk(KERN_INFO "capidrv: controller %hu up\n", contr); + (void) capidrv_addcontr(contr, (capi_profile *) data); + break; + case KCI_CONTRDOWN: + printk(KERN_INFO "capidrv: controller %hu down\n", contr); + (void) capidrv_delcontr(contr); + break; + } +} + +/* + * /proc/capi/capidrv: + * nrecvctlpkt nrecvdatapkt nsendctlpkt nsenddatapkt + */ +static int proc_capidrv_read_proc(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + int len = 0; + + len += sprintf(page+len, "%lu %lu %lu %lu\n", + global.ap.nrecvctlpkt, + global.ap.nrecvdatapkt, + global.ap.nsentctlpkt, + global.ap.nsentdatapkt); + if (off+count >= len) + *eof = 1; + if (len < off) + return 0; + *start = page + off; + return ((count < len-off) ? count : len-off); +} + +static struct procfsentries { + char *name; + mode_t mode; + int (*read_proc)(char *page, char **start, off_t off, + int count, int *eof, void *data); + struct proc_dir_entry *procent; +} procfsentries[] = { + /* { "capi", S_IFDIR, 0 }, */ + { "capi/capidrv", 0 , proc_capidrv_read_proc }, +}; + +static void __init proc_init(void) +{ + int nelem = sizeof(procfsentries)/sizeof(procfsentries[0]); + int i; + + for (i=0; i < nelem; i++) { + struct procfsentries *p = procfsentries + i; + p->procent = create_proc_entry(p->name, p->mode, NULL); + if (p->procent) p->procent->read_proc = p->read_proc; + } +} + +static void __exit proc_exit(void) +{ + int nelem = sizeof(procfsentries)/sizeof(procfsentries[0]); + int i; + + for (i=nelem-1; i >= 0; i--) { + struct procfsentries *p = procfsentries + i; + if (p->procent) { + remove_proc_entry(p->name, NULL); + p->procent = NULL; + } + } +} + +static int __init capidrv_init(void) +{ + capi_profile profile; + char rev[32]; + char *p; + u32 ncontr, contr; + u16 errcode; + + if ((p = strchr(revision, ':')) != 0 && p[1]) { + strncpy(rev, p + 2, sizeof(rev)); + rev[sizeof(rev)-1] = 0; + if ((p = strchr(rev, '$')) != 0 && p > rev) + *(p-1) = 0; + } else + strcpy(rev, "1.0"); + + global.ap.rparam.level3cnt = -2; /* number of bchannels twice */ + global.ap.rparam.datablkcnt = 16; + global.ap.rparam.datablklen = 2048; + + global.ap.recv_message = capidrv_recv_message; + errcode = capi20_register(&global.ap); + if (errcode) { + return -EIO; + } + + capi20_set_callback(&global.ap, lower_callback); + + errcode = capi20_get_profile(0, &profile); + if (errcode != CAPI_NOERROR) { + capi20_release(&global.ap); + return -EIO; + } + + ncontr = profile.ncontroller; + for (contr = 1; contr <= ncontr; contr++) { + errcode = capi20_get_profile(contr, &profile); + if (errcode != CAPI_NOERROR) + continue; + (void) capidrv_addcontr(contr, &profile); + } + proc_init(); + + printk(KERN_NOTICE "capidrv: Rev %s: loaded\n", rev); + return 0; +} + +static void __exit capidrv_exit(void) +{ + char rev[10]; + char *p; + + if ((p = strchr(revision, ':')) != 0) { + strcpy(rev, p + 1); + p = strchr(rev, '$'); + *p = 0; + } else { + strcpy(rev, " ??? "); + } + + capi20_release(&global.ap); + + proc_exit(); + + printk(KERN_NOTICE "capidrv: Rev%s: unloaded\n", rev); +} + +module_init(capidrv_init); +module_exit(capidrv_exit); diff --git a/drivers/isdn/capi/capidrv.h b/drivers/isdn/capi/capidrv.h new file mode 100644 index 000000000000..1e698e1e269f --- /dev/null +++ b/drivers/isdn/capi/capidrv.h @@ -0,0 +1,140 @@ +/* $Id: capidrv.h,v 1.2.8.2 2001/09/23 22:24:33 kai Exp $ + * + * ISDN4Linux Driver, using capi20 interface (kernelcapi) + * + * Copyright 1997 by Carsten Paeth <calle@calle.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#ifndef __CAPIDRV_H__ +#define __CAPIDRV_H__ + +/* + * LISTEN state machine + */ +#define ST_LISTEN_NONE 0 /* L-0 */ +#define ST_LISTEN_WAIT_CONF 1 /* L-0.1 */ +#define ST_LISTEN_ACTIVE 2 /* L-1 */ +#define ST_LISTEN_ACTIVE_WAIT_CONF 3 /* L-1.1 */ + + +#define EV_LISTEN_REQ 1 /* L-0 -> L-0.1 + L-1 -> L-1.1 */ +#define EV_LISTEN_CONF_ERROR 2 /* L-0.1 -> L-0 + L-1.1 -> L-1 */ +#define EV_LISTEN_CONF_EMPTY 3 /* L-0.1 -> L-0 + L-1.1 -> L-0 */ +#define EV_LISTEN_CONF_OK 4 /* L-0.1 -> L-1 + L-1.1 -> L.1 */ + +/* + * per plci state machine + */ +#define ST_PLCI_NONE 0 /* P-0 */ +#define ST_PLCI_OUTGOING 1 /* P-0.1 */ +#define ST_PLCI_ALLOCATED 2 /* P-1 */ +#define ST_PLCI_ACTIVE 3 /* P-ACT */ +#define ST_PLCI_INCOMING 4 /* P-2 */ +#define ST_PLCI_FACILITY_IND 5 /* P-3 */ +#define ST_PLCI_ACCEPTING 6 /* P-4 */ +#define ST_PLCI_DISCONNECTING 7 /* P-5 */ +#define ST_PLCI_DISCONNECTED 8 /* P-6 */ +#define ST_PLCI_RESUMEING 9 /* P-0.Res */ +#define ST_PLCI_RESUME 10 /* P-Res */ +#define ST_PLCI_HELD 11 /* P-HELD */ + +#define EV_PLCI_CONNECT_REQ 1 /* P-0 -> P-0.1 + */ +#define EV_PLCI_CONNECT_CONF_ERROR 2 /* P-0.1 -> P-0 + */ +#define EV_PLCI_CONNECT_CONF_OK 3 /* P-0.1 -> P-1 + */ +#define EV_PLCI_FACILITY_IND_UP 4 /* P-0 -> P-1 + */ +#define EV_PLCI_CONNECT_IND 5 /* P-0 -> P-2 + */ +#define EV_PLCI_CONNECT_ACTIVE_IND 6 /* P-1 -> P-ACT + */ +#define EV_PLCI_CONNECT_REJECT 7 /* P-2 -> P-5 + P-3 -> P-5 + */ +#define EV_PLCI_DISCONNECT_REQ 8 /* P-1 -> P-5 + P-2 -> P-5 + P-3 -> P-5 + P-4 -> P-5 + P-ACT -> P-5 + P-Res -> P-5 (*) + P-HELD -> P-5 (*) + */ +#define EV_PLCI_DISCONNECT_IND 9 /* P-1 -> P-6 + P-2 -> P-6 + P-3 -> P-6 + P-4 -> P-6 + P-5 -> P-6 + P-ACT -> P-6 + P-Res -> P-6 (*) + P-HELD -> P-6 (*) + */ +#define EV_PLCI_FACILITY_IND_DOWN 10 /* P-0.1 -> P-5 + P-1 -> P-5 + P-ACT -> P-5 + P-2 -> P-5 + P-3 -> P-5 + P-4 -> P-5 + */ +#define EV_PLCI_DISCONNECT_RESP 11 /* P-6 -> P-0 + */ +#define EV_PLCI_CONNECT_RESP 12 /* P-6 -> P-0 + */ + +#define EV_PLCI_RESUME_REQ 13 /* P-0 -> P-0.Res + */ +#define EV_PLCI_RESUME_CONF_OK 14 /* P-0.Res -> P-Res + */ +#define EV_PLCI_RESUME_CONF_ERROR 15 /* P-0.Res -> P-0 + */ +#define EV_PLCI_RESUME_IND 16 /* P-Res -> P-ACT + */ +#define EV_PLCI_HOLD_IND 17 /* P-ACT -> P-HELD + */ +#define EV_PLCI_RETRIEVE_IND 18 /* P-HELD -> P-ACT + */ +#define EV_PLCI_SUSPEND_IND 19 /* P-ACT -> P-5 + */ +#define EV_PLCI_CD_IND 20 /* P-2 -> P-5 + */ + +/* + * per ncci state machine + */ +#define ST_NCCI_PREVIOUS -1 +#define ST_NCCI_NONE 0 /* N-0 */ +#define ST_NCCI_OUTGOING 1 /* N-0.1 */ +#define ST_NCCI_INCOMING 2 /* N-1 */ +#define ST_NCCI_ALLOCATED 3 /* N-2 */ +#define ST_NCCI_ACTIVE 4 /* N-ACT */ +#define ST_NCCI_RESETING 5 /* N-3 */ +#define ST_NCCI_DISCONNECTING 6 /* N-4 */ +#define ST_NCCI_DISCONNECTED 7 /* N-5 */ + +#define EV_NCCI_CONNECT_B3_REQ 1 /* N-0 -> N-0.1 */ +#define EV_NCCI_CONNECT_B3_IND 2 /* N-0 -> N.1 */ +#define EV_NCCI_CONNECT_B3_CONF_OK 3 /* N-0.1 -> N.2 */ +#define EV_NCCI_CONNECT_B3_CONF_ERROR 4 /* N-0.1 -> N.0 */ +#define EV_NCCI_CONNECT_B3_REJECT 5 /* N-1 -> N-4 */ +#define EV_NCCI_CONNECT_B3_RESP 6 /* N-1 -> N-2 */ +#define EV_NCCI_CONNECT_B3_ACTIVE_IND 7 /* N-2 -> N-ACT */ +#define EV_NCCI_RESET_B3_REQ 8 /* N-ACT -> N-3 */ +#define EV_NCCI_RESET_B3_IND 9 /* N-3 -> N-ACT */ +#define EV_NCCI_DISCONNECT_B3_IND 10 /* N-4 -> N.5 */ +#define EV_NCCI_DISCONNECT_B3_CONF_ERROR 11 /* N-4 -> previous */ +#define EV_NCCI_DISCONNECT_B3_REQ 12 /* N-1 -> N-4 + N-2 -> N-4 + N-3 -> N-4 + N-ACT -> N-4 */ +#define EV_NCCI_DISCONNECT_B3_RESP 13 /* N-5 -> N-0 */ + +#endif /* __CAPIDRV_H__ */ diff --git a/drivers/isdn/capi/capifs.c b/drivers/isdn/capi/capifs.c new file mode 100644 index 000000000000..f8570fd9d2ab --- /dev/null +++ b/drivers/isdn/capi/capifs.c @@ -0,0 +1,212 @@ +/* $Id: capifs.c,v 1.1.2.3 2004/01/16 21:09:26 keil Exp $ + * + * Copyright 2000 by Carsten Paeth <calle@calle.de> + * + * Heavily based on devpts filesystem from H. Peter Anvin + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/fs.h> +#include <linux/mount.h> +#include <linux/namei.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/ctype.h> + +MODULE_DESCRIPTION("CAPI4Linux: /dev/capi/ filesystem"); +MODULE_AUTHOR("Carsten Paeth"); +MODULE_LICENSE("GPL"); + +/* ------------------------------------------------------------------ */ + +static char *revision = "$Revision: 1.1.2.3 $"; + +/* ------------------------------------------------------------------ */ + +#define CAPIFS_SUPER_MAGIC (('C'<<8)|'N') + +static struct vfsmount *capifs_mnt; +static struct dentry *capifs_root; + +static struct { + int setuid; + int setgid; + uid_t uid; + gid_t gid; + umode_t mode; +} config = {.mode = 0600}; + +/* ------------------------------------------------------------------ */ + +static int capifs_remount(struct super_block *s, int *flags, char *data) +{ + int setuid = 0; + int setgid = 0; + uid_t uid = 0; + gid_t gid = 0; + umode_t mode = 0600; + char *this_char; + + this_char = NULL; + while ((this_char = strsep(&data, ",")) != NULL) { + int n; + char dummy; + if (!*this_char) + continue; + if (sscanf(this_char, "uid=%i%c", &n, &dummy) == 1) { + setuid = 1; + uid = n; + } else if (sscanf(this_char, "gid=%i%c", &n, &dummy) == 1) { + setgid = 1; + gid = n; + } else if (sscanf(this_char, "mode=%o%c", &n, &dummy) == 1) + mode = n & ~S_IFMT; + else { + printk("capifs: called with bogus options\n"); + return -EINVAL; + } + } + config.setuid = setuid; + config.setgid = setgid; + config.uid = uid; + config.gid = gid; + config.mode = mode; + return 0; +} + +static struct super_operations capifs_sops = +{ + .statfs = simple_statfs, + .remount_fs = capifs_remount, +}; + + +static int +capifs_fill_super(struct super_block *s, void *data, int silent) +{ + struct inode * inode; + + s->s_blocksize = 1024; + s->s_blocksize_bits = 10; + s->s_magic = CAPIFS_SUPER_MAGIC; + s->s_op = &capifs_sops; + s->s_time_gran = 1; + + inode = new_inode(s); + if (!inode) + goto fail; + inode->i_ino = 1; + inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME; + inode->i_blocks = 0; + inode->i_blksize = 1024; + inode->i_uid = inode->i_gid = 0; + inode->i_mode = S_IFDIR | S_IRUGO | S_IXUGO | S_IWUSR; + inode->i_op = &simple_dir_inode_operations; + inode->i_fop = &simple_dir_operations; + inode->i_nlink = 2; + + capifs_root = s->s_root = d_alloc_root(inode); + if (s->s_root) + return 0; + + printk("capifs: get root dentry failed\n"); + iput(inode); +fail: + return -ENOMEM; +} + +static struct super_block *capifs_get_sb(struct file_system_type *fs_type, + int flags, const char *dev_name, void *data) +{ + return get_sb_single(fs_type, flags, data, capifs_fill_super); +} + +static struct file_system_type capifs_fs_type = { + .owner = THIS_MODULE, + .name = "capifs", + .get_sb = capifs_get_sb, + .kill_sb = kill_anon_super, +}; + +static struct dentry *get_node(int num) +{ + char s[10]; + struct dentry *root = capifs_root; + down(&root->d_inode->i_sem); + return lookup_one_len(s, root, sprintf(s, "%d", num)); +} + +void capifs_new_ncci(unsigned int number, dev_t device) +{ + struct dentry *dentry; + struct inode *inode = new_inode(capifs_mnt->mnt_sb); + if (!inode) + return; + inode->i_ino = number+2; + inode->i_blksize = 1024; + inode->i_uid = config.setuid ? config.uid : current->fsuid; + inode->i_gid = config.setgid ? config.gid : current->fsgid; + inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME; + init_special_inode(inode, S_IFCHR|config.mode, device); + //inode->i_op = &capifs_file_inode_operations; + + dentry = get_node(number); + if (!IS_ERR(dentry) && !dentry->d_inode) + d_instantiate(dentry, inode); + up(&capifs_root->d_inode->i_sem); +} + +void capifs_free_ncci(unsigned int number) +{ + struct dentry *dentry = get_node(number); + + if (!IS_ERR(dentry)) { + struct inode *inode = dentry->d_inode; + if (inode) { + inode->i_nlink--; + d_delete(dentry); + dput(dentry); + } + dput(dentry); + } + up(&capifs_root->d_inode->i_sem); +} + +static int __init capifs_init(void) +{ + char rev[32]; + char *p; + int err; + + if ((p = strchr(revision, ':')) != 0 && p[1]) { + strlcpy(rev, p + 2, sizeof(rev)); + if ((p = strchr(rev, '$')) != 0 && p > rev) + *(p-1) = 0; + } else + strcpy(rev, "1.0"); + + err = register_filesystem(&capifs_fs_type); + if (!err) { + capifs_mnt = kern_mount(&capifs_fs_type); + if (IS_ERR(capifs_mnt)) + err = PTR_ERR(capifs_mnt); + } + if (!err) + printk(KERN_NOTICE "capifs: Rev %s\n", rev); + return err; +} + +static void __exit capifs_exit(void) +{ + unregister_filesystem(&capifs_fs_type); + mntput(capifs_mnt); +} + +EXPORT_SYMBOL(capifs_new_ncci); +EXPORT_SYMBOL(capifs_free_ncci); + +module_init(capifs_init); +module_exit(capifs_exit); diff --git a/drivers/isdn/capi/capifs.h b/drivers/isdn/capi/capifs.h new file mode 100644 index 000000000000..d0bd4c3c430a --- /dev/null +++ b/drivers/isdn/capi/capifs.h @@ -0,0 +1,11 @@ +/* $Id: capifs.h,v 1.1.2.2 2004/01/16 21:09:26 keil Exp $ + * + * Copyright 2000 by Carsten Paeth <calle@calle.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +void capifs_new_ncci(unsigned int num, dev_t device); +void capifs_free_ncci(unsigned int num); diff --git a/drivers/isdn/capi/capilib.c b/drivers/isdn/capi/capilib.c new file mode 100644 index 000000000000..68409d971e73 --- /dev/null +++ b/drivers/isdn/capi/capilib.c @@ -0,0 +1,200 @@ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/isdn/capilli.h> + +#define DBG(format, arg...) do { \ +printk(KERN_DEBUG "%s: " format "\n" , __FUNCTION__ , ## arg); \ +} while (0) + +struct capilib_msgidqueue { + struct capilib_msgidqueue *next; + u16 msgid; +}; + +struct capilib_ncci { + struct list_head list; + u16 applid; + u32 ncci; + u32 winsize; + int nmsg; + struct capilib_msgidqueue *msgidqueue; + struct capilib_msgidqueue *msgidlast; + struct capilib_msgidqueue *msgidfree; + struct capilib_msgidqueue msgidpool[CAPI_MAXDATAWINDOW]; +}; + +// --------------------------------------------------------------------------- +// NCCI Handling + +static inline void mq_init(struct capilib_ncci * np) +{ + u_int i; + np->msgidqueue = NULL; + np->msgidlast = NULL; + np->nmsg = 0; + memset(np->msgidpool, 0, sizeof(np->msgidpool)); + np->msgidfree = &np->msgidpool[0]; + for (i = 1; i < np->winsize; i++) { + np->msgidpool[i].next = np->msgidfree; + np->msgidfree = &np->msgidpool[i]; + } +} + +static inline int mq_enqueue(struct capilib_ncci * np, u16 msgid) +{ + struct capilib_msgidqueue *mq; + if ((mq = np->msgidfree) == 0) + return 0; + np->msgidfree = mq->next; + mq->msgid = msgid; + mq->next = NULL; + if (np->msgidlast) + np->msgidlast->next = mq; + np->msgidlast = mq; + if (!np->msgidqueue) + np->msgidqueue = mq; + np->nmsg++; + return 1; +} + +static inline int mq_dequeue(struct capilib_ncci * np, u16 msgid) +{ + struct capilib_msgidqueue **pp; + for (pp = &np->msgidqueue; *pp; pp = &(*pp)->next) { + if ((*pp)->msgid == msgid) { + struct capilib_msgidqueue *mq = *pp; + *pp = mq->next; + if (mq == np->msgidlast) + np->msgidlast = NULL; + mq->next = np->msgidfree; + np->msgidfree = mq; + np->nmsg--; + return 1; + } + } + return 0; +} + +void capilib_new_ncci(struct list_head *head, u16 applid, u32 ncci, u32 winsize) +{ + struct capilib_ncci *np; + + np = kmalloc(sizeof(*np), GFP_ATOMIC); + if (!np) { + printk(KERN_WARNING "capilib_new_ncci: no memory.\n"); + return; + } + if (winsize > CAPI_MAXDATAWINDOW) { + printk(KERN_ERR "capi_new_ncci: winsize %d too big\n", + winsize); + winsize = CAPI_MAXDATAWINDOW; + } + np->applid = applid; + np->ncci = ncci; + np->winsize = winsize; + mq_init(np); + list_add_tail(&np->list, head); + DBG("kcapi: appl %d ncci 0x%x up", applid, ncci); +} + +EXPORT_SYMBOL(capilib_new_ncci); + +void capilib_free_ncci(struct list_head *head, u16 applid, u32 ncci) +{ + struct list_head *l; + struct capilib_ncci *np; + + list_for_each(l, head) { + np = list_entry(l, struct capilib_ncci, list); + if (np->applid != applid) + continue; + if (np->ncci != ncci) + continue; + printk(KERN_INFO "kcapi: appl %d ncci 0x%x down\n", applid, ncci); + list_del(&np->list); + kfree(np); + return; + } + printk(KERN_ERR "capilib_free_ncci: ncci 0x%x not found\n", ncci); +} + +EXPORT_SYMBOL(capilib_free_ncci); + +void capilib_release_appl(struct list_head *head, u16 applid) +{ + struct list_head *l, *n; + struct capilib_ncci *np; + + list_for_each_safe(l, n, head) { + np = list_entry(l, struct capilib_ncci, list); + if (np->applid != applid) + continue; + printk(KERN_INFO "kcapi: appl %d ncci 0x%x forced down\n", applid, np->ncci); + list_del(&np->list); + kfree(np); + } +} + +EXPORT_SYMBOL(capilib_release_appl); + +void capilib_release(struct list_head *head) +{ + struct list_head *l, *n; + struct capilib_ncci *np; + + list_for_each_safe(l, n, head) { + np = list_entry(l, struct capilib_ncci, list); + printk(KERN_INFO "kcapi: appl %d ncci 0x%x forced down\n", np->applid, np->ncci); + list_del(&np->list); + kfree(np); + } +} + +EXPORT_SYMBOL(capilib_release); + +u16 capilib_data_b3_req(struct list_head *head, u16 applid, u32 ncci, u16 msgid) +{ + struct list_head *l; + struct capilib_ncci *np; + + list_for_each(l, head) { + np = list_entry(l, struct capilib_ncci, list); + if (np->applid != applid) + continue; + if (np->ncci != ncci) + continue; + + if (mq_enqueue(np, msgid) == 0) + return CAPI_SENDQUEUEFULL; + + return CAPI_NOERROR; + } + printk(KERN_ERR "capilib_data_b3_req: ncci 0x%x not found\n", ncci); + return CAPI_NOERROR; +} + +EXPORT_SYMBOL(capilib_data_b3_req); + +void capilib_data_b3_conf(struct list_head *head, u16 applid, u32 ncci, u16 msgid) +{ + struct list_head *l; + struct capilib_ncci *np; + + list_for_each(l, head) { + np = list_entry(l, struct capilib_ncci, list); + if (np->applid != applid) + continue; + if (np->ncci != ncci) + continue; + + if (mq_dequeue(np, msgid) == 0) { + printk(KERN_ERR "kcapi: msgid %hu ncci 0x%x not on queue\n", + msgid, ncci); + } + return; + } + printk(KERN_ERR "capilib_data_b3_conf: ncci 0x%x not found\n", ncci); +} + +EXPORT_SYMBOL(capilib_data_b3_conf); diff --git a/drivers/isdn/capi/capiutil.c b/drivers/isdn/capi/capiutil.c new file mode 100644 index 000000000000..e7cf6bc286a6 --- /dev/null +++ b/drivers/isdn/capi/capiutil.c @@ -0,0 +1,859 @@ +/* $Id: capiutil.c,v 1.13.6.4 2001/09/23 22:24:33 kai Exp $ + * + * CAPI 2.0 convert capi message to capi message struct + * + * From CAPI 2.0 Development Kit AVM 1995 (msg.c) + * Rewritten for Linux 1996 by Carsten Paeth <calle@calle.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/module.h> +#include <linux/string.h> +#include <linux/ctype.h> +#include <linux/stddef.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/init.h> +#include <linux/config.h> +#include <linux/isdn/capiutil.h> + +/* from CAPI2.0 DDK AVM Berlin GmbH */ + +#ifndef CONFIG_ISDN_DRV_AVMB1_VERBOSE_REASON +char *capi_info2str(u16 reason) +{ + return ".."; +} +#else +char *capi_info2str(u16 reason) +{ + switch (reason) { + +/*-- informative values (corresponding message was processed) -----*/ + case 0x0001: + return "NCPI not supported by current protocol, NCPI ignored"; + case 0x0002: + return "Flags not supported by current protocol, flags ignored"; + case 0x0003: + return "Alert already sent by another application"; + +/*-- error information concerning CAPI_REGISTER -----*/ + case 0x1001: + return "Too many applications"; + case 0x1002: + return "Logical block size too small, must be at least 128 Bytes"; + case 0x1003: + return "Buffer exceeds 64 kByte"; + case 0x1004: + return "Message buffer size too small, must be at least 1024 Bytes"; + case 0x1005: + return "Max. number of logical connections not supported"; + case 0x1006: + return "Reserved"; + case 0x1007: + return "The message could not be accepted because of an internal busy condition"; + case 0x1008: + return "OS resource error (no memory ?)"; + case 0x1009: + return "CAPI not installed"; + case 0x100A: + return "Controller does not support external equipment"; + case 0x100B: + return "Controller does only support external equipment"; + +/*-- error information concerning message exchange functions -----*/ + case 0x1101: + return "Illegal application number"; + case 0x1102: + return "Illegal command or subcommand or message length less than 12 bytes"; + case 0x1103: + return "The message could not be accepted because of a queue full condition !! The error code does not imply that CAPI cannot receive messages directed to another controller, PLCI or NCCI"; + case 0x1104: + return "Queue is empty"; + case 0x1105: + return "Queue overflow, a message was lost !! This indicates a configuration error. The only recovery from this error is to perform a CAPI_RELEASE"; + case 0x1106: + return "Unknown notification parameter"; + case 0x1107: + return "The Message could not be accepted because of an internal busy condition"; + case 0x1108: + return "OS Resource error (no memory ?)"; + case 0x1109: + return "CAPI not installed"; + case 0x110A: + return "Controller does not support external equipment"; + case 0x110B: + return "Controller does only support external equipment"; + +/*-- error information concerning resource / coding problems -----*/ + case 0x2001: + return "Message not supported in current state"; + case 0x2002: + return "Illegal Controller / PLCI / NCCI"; + case 0x2003: + return "Out of PLCI"; + case 0x2004: + return "Out of NCCI"; + case 0x2005: + return "Out of LISTEN"; + case 0x2006: + return "Out of FAX resources (protocol T.30)"; + case 0x2007: + return "Illegal message parameter coding"; + +/*-- error information concerning requested services -----*/ + case 0x3001: + return "B1 protocol not supported"; + case 0x3002: + return "B2 protocol not supported"; + case 0x3003: + return "B3 protocol not supported"; + case 0x3004: + return "B1 protocol parameter not supported"; + case 0x3005: + return "B2 protocol parameter not supported"; + case 0x3006: + return "B3 protocol parameter not supported"; + case 0x3007: + return "B protocol combination not supported"; + case 0x3008: + return "NCPI not supported"; + case 0x3009: + return "CIP Value unknown"; + case 0x300A: + return "Flags not supported (reserved bits)"; + case 0x300B: + return "Facility not supported"; + case 0x300C: + return "Data length not supported by current protocol"; + case 0x300D: + return "Reset procedure not supported by current protocol"; + +/*-- informations about the clearing of a physical connection -----*/ + case 0x3301: + return "Protocol error layer 1 (broken line or B-channel removed by signalling protocol)"; + case 0x3302: + return "Protocol error layer 2"; + case 0x3303: + return "Protocol error layer 3"; + case 0x3304: + return "Another application got that call"; +/*-- T.30 specific reasons -----*/ + case 0x3311: + return "Connecting not successful (remote station is no FAX G3 machine)"; + case 0x3312: + return "Connecting not successful (training error)"; + case 0x3313: + return "Disconnected before transfer (remote station does not support transfer mode, e.g. resolution)"; + case 0x3314: + return "Disconnected during transfer (remote abort)"; + case 0x3315: + return "Disconnected during transfer (remote procedure error, e.g. unsuccessful repetition of T.30 commands)"; + case 0x3316: + return "Disconnected during transfer (local tx data underrun)"; + case 0x3317: + return "Disconnected during transfer (local rx data overflow)"; + case 0x3318: + return "Disconnected during transfer (local abort)"; + case 0x3319: + return "Illegal parameter coding (e.g. SFF coding error)"; + +/*-- disconnect causes from the network according to ETS 300 102-1/Q.931 -----*/ + case 0x3481: return "Unallocated (unassigned) number"; + case 0x3482: return "No route to specified transit network"; + case 0x3483: return "No route to destination"; + case 0x3486: return "Channel unacceptable"; + case 0x3487: + return "Call awarded and being delivered in an established channel"; + case 0x3490: return "Normal call clearing"; + case 0x3491: return "User busy"; + case 0x3492: return "No user responding"; + case 0x3493: return "No answer from user (user alerted)"; + case 0x3495: return "Call rejected"; + case 0x3496: return "Number changed"; + case 0x349A: return "Non-selected user clearing"; + case 0x349B: return "Destination out of order"; + case 0x349C: return "Invalid number format"; + case 0x349D: return "Facility rejected"; + case 0x349E: return "Response to STATUS ENQUIRY"; + case 0x349F: return "Normal, unspecified"; + case 0x34A2: return "No circuit / channel available"; + case 0x34A6: return "Network out of order"; + case 0x34A9: return "Temporary failure"; + case 0x34AA: return "Switching equipment congestion"; + case 0x34AB: return "Access information discarded"; + case 0x34AC: return "Requested circuit / channel not available"; + case 0x34AF: return "Resources unavailable, unspecified"; + case 0x34B1: return "Quality of service unavailable"; + case 0x34B2: return "Requested facility not subscribed"; + case 0x34B9: return "Bearer capability not authorized"; + case 0x34BA: return "Bearer capability not presently available"; + case 0x34BF: return "Service or option not available, unspecified"; + case 0x34C1: return "Bearer capability not implemented"; + case 0x34C2: return "Channel type not implemented"; + case 0x34C5: return "Requested facility not implemented"; + case 0x34C6: return "Only restricted digital information bearer capability is available"; + case 0x34CF: return "Service or option not implemented, unspecified"; + case 0x34D1: return "Invalid call reference value"; + case 0x34D2: return "Identified channel does not exist"; + case 0x34D3: return "A suspended call exists, but this call identity does not"; + case 0x34D4: return "Call identity in use"; + case 0x34D5: return "No call suspended"; + case 0x34D6: return "Call having the requested call identity has been cleared"; + case 0x34D8: return "Incompatible destination"; + case 0x34DB: return "Invalid transit network selection"; + case 0x34DF: return "Invalid message, unspecified"; + case 0x34E0: return "Mandatory information element is missing"; + case 0x34E1: return "Message type non-existent or not implemented"; + case 0x34E2: return "Message not compatible with call state or message type non-existent or not implemented"; + case 0x34E3: return "Information element non-existent or not implemented"; + case 0x34E4: return "Invalid information element contents"; + case 0x34E5: return "Message not compatible with call state"; + case 0x34E6: return "Recovery on timer expiry"; + case 0x34EF: return "Protocol error, unspecified"; + case 0x34FF: return "Interworking, unspecified"; + + default: return "No additional information"; + } +} +#endif + +typedef struct { + int typ; + size_t off; +} _cdef; + +#define _CBYTE 1 +#define _CWORD 2 +#define _CDWORD 3 +#define _CSTRUCT 4 +#define _CMSTRUCT 5 +#define _CEND 6 + +static _cdef cdef[] = +{ + /*00 */ + {_CEND}, + /*01 */ + {_CEND}, + /*02 */ + {_CEND}, + /*03 */ + {_CDWORD, offsetof(_cmsg, adr.adrController)}, + /*04 */ + {_CMSTRUCT, offsetof(_cmsg, AdditionalInfo)}, + /*05 */ + {_CSTRUCT, offsetof(_cmsg, B1configuration)}, + /*06 */ + {_CWORD, offsetof(_cmsg, B1protocol)}, + /*07 */ + {_CSTRUCT, offsetof(_cmsg, B2configuration)}, + /*08 */ + {_CWORD, offsetof(_cmsg, B2protocol)}, + /*09 */ + {_CSTRUCT, offsetof(_cmsg, B3configuration)}, + /*0a */ + {_CWORD, offsetof(_cmsg, B3protocol)}, + /*0b */ + {_CSTRUCT, offsetof(_cmsg, BC)}, + /*0c */ + {_CSTRUCT, offsetof(_cmsg, BChannelinformation)}, + /*0d */ + {_CMSTRUCT, offsetof(_cmsg, BProtocol)}, + /*0e */ + {_CSTRUCT, offsetof(_cmsg, CalledPartyNumber)}, + /*0f */ + {_CSTRUCT, offsetof(_cmsg, CalledPartySubaddress)}, + /*10 */ + {_CSTRUCT, offsetof(_cmsg, CallingPartyNumber)}, + /*11 */ + {_CSTRUCT, offsetof(_cmsg, CallingPartySubaddress)}, + /*12 */ + {_CDWORD, offsetof(_cmsg, CIPmask)}, + /*13 */ + {_CDWORD, offsetof(_cmsg, CIPmask2)}, + /*14 */ + {_CWORD, offsetof(_cmsg, CIPValue)}, + /*15 */ + {_CDWORD, offsetof(_cmsg, Class)}, + /*16 */ + {_CSTRUCT, offsetof(_cmsg, ConnectedNumber)}, + /*17 */ + {_CSTRUCT, offsetof(_cmsg, ConnectedSubaddress)}, + /*18 */ + {_CDWORD, offsetof(_cmsg, Data)}, + /*19 */ + {_CWORD, offsetof(_cmsg, DataHandle)}, + /*1a */ + {_CWORD, offsetof(_cmsg, DataLength)}, + /*1b */ + {_CSTRUCT, offsetof(_cmsg, FacilityConfirmationParameter)}, + /*1c */ + {_CSTRUCT, offsetof(_cmsg, Facilitydataarray)}, + /*1d */ + {_CSTRUCT, offsetof(_cmsg, FacilityIndicationParameter)}, + /*1e */ + {_CSTRUCT, offsetof(_cmsg, FacilityRequestParameter)}, + /*1f */ + {_CWORD, offsetof(_cmsg, FacilitySelector)}, + /*20 */ + {_CWORD, offsetof(_cmsg, Flags)}, + /*21 */ + {_CDWORD, offsetof(_cmsg, Function)}, + /*22 */ + {_CSTRUCT, offsetof(_cmsg, HLC)}, + /*23 */ + {_CWORD, offsetof(_cmsg, Info)}, + /*24 */ + {_CSTRUCT, offsetof(_cmsg, InfoElement)}, + /*25 */ + {_CDWORD, offsetof(_cmsg, InfoMask)}, + /*26 */ + {_CWORD, offsetof(_cmsg, InfoNumber)}, + /*27 */ + {_CSTRUCT, offsetof(_cmsg, Keypadfacility)}, + /*28 */ + {_CSTRUCT, offsetof(_cmsg, LLC)}, + /*29 */ + {_CSTRUCT, offsetof(_cmsg, ManuData)}, + /*2a */ + {_CDWORD, offsetof(_cmsg, ManuID)}, + /*2b */ + {_CSTRUCT, offsetof(_cmsg, NCPI)}, + /*2c */ + {_CWORD, offsetof(_cmsg, Reason)}, + /*2d */ + {_CWORD, offsetof(_cmsg, Reason_B3)}, + /*2e */ + {_CWORD, offsetof(_cmsg, Reject)}, + /*2f */ + {_CSTRUCT, offsetof(_cmsg, Useruserdata)} +}; + +static unsigned char *cpars[] = +{ + /* ALERT_REQ */ [0x01] = "\x03\x04\x0c\x27\x2f\x1c\x01\x01", + /* CONNECT_REQ */ [0x02] = "\x03\x14\x0e\x10\x0f\x11\x0d\x06\x08\x0a\x05\x07\x09\x01\x0b\x28\x22\x04\x0c\x27\x2f\x1c\x01\x01", + /* DISCONNECT_REQ */ [0x04] = "\x03\x04\x0c\x27\x2f\x1c\x01\x01", + /* LISTEN_REQ */ [0x05] = "\x03\x25\x12\x13\x10\x11\x01", + /* INFO_REQ */ [0x08] = "\x03\x0e\x04\x0c\x27\x2f\x1c\x01\x01", + /* FACILITY_REQ */ [0x09] = "\x03\x1f\x1e\x01", + /* SELECT_B_PROTOCOL_REQ */ [0x0a] = "\x03\x0d\x06\x08\x0a\x05\x07\x09\x01\x01", + /* CONNECT_B3_REQ */ [0x0b] = "\x03\x2b\x01", + /* DISCONNECT_B3_REQ */ [0x0d] = "\x03\x2b\x01", + /* DATA_B3_REQ */ [0x0f] = "\x03\x18\x1a\x19\x20\x01", + /* RESET_B3_REQ */ [0x10] = "\x03\x2b\x01", + /* ALERT_CONF */ [0x13] = "\x03\x23\x01", + /* CONNECT_CONF */ [0x14] = "\x03\x23\x01", + /* DISCONNECT_CONF */ [0x16] = "\x03\x23\x01", + /* LISTEN_CONF */ [0x17] = "\x03\x23\x01", + /* MANUFACTURER_REQ */ [0x18] = "\x03\x2a\x15\x21\x29\x01", + /* INFO_CONF */ [0x1a] = "\x03\x23\x01", + /* FACILITY_CONF */ [0x1b] = "\x03\x23\x1f\x1b\x01", + /* SELECT_B_PROTOCOL_CONF */ [0x1c] = "\x03\x23\x01", + /* CONNECT_B3_CONF */ [0x1d] = "\x03\x23\x01", + /* DISCONNECT_B3_CONF */ [0x1f] = "\x03\x23\x01", + /* DATA_B3_CONF */ [0x21] = "\x03\x19\x23\x01", + /* RESET_B3_CONF */ [0x22] = "\x03\x23\x01", + /* CONNECT_IND */ [0x26] = "\x03\x14\x0e\x10\x0f\x11\x0b\x28\x22\x04\x0c\x27\x2f\x1c\x01\x01", + /* CONNECT_ACTIVE_IND */ [0x27] = "\x03\x16\x17\x28\x01", + /* DISCONNECT_IND */ [0x28] = "\x03\x2c\x01", + /* MANUFACTURER_CONF */ [0x2a] = "\x03\x2a\x15\x21\x29\x01", + /* INFO_IND */ [0x2c] = "\x03\x26\x24\x01", + /* FACILITY_IND */ [0x2d] = "\x03\x1f\x1d\x01", + /* CONNECT_B3_IND */ [0x2f] = "\x03\x2b\x01", + /* CONNECT_B3_ACTIVE_IND */ [0x30] = "\x03\x2b\x01", + /* DISCONNECT_B3_IND */ [0x31] = "\x03\x2d\x2b\x01", + /* DATA_B3_IND */ [0x33] = "\x03\x18\x1a\x19\x20\x01", + /* RESET_B3_IND */ [0x34] = "\x03\x2b\x01", + /* CONNECT_B3_T90_ACTIVE_IND */ [0x35] = "\x03\x2b\x01", + /* CONNECT_RESP */ [0x38] = "\x03\x2e\x0d\x06\x08\x0a\x05\x07\x09\x01\x16\x17\x28\x04\x0c\x27\x2f\x1c\x01\x01", + /* CONNECT_ACTIVE_RESP */ [0x39] = "\x03\x01", + /* DISCONNECT_RESP */ [0x3a] = "\x03\x01", + /* MANUFACTURER_IND */ [0x3c] = "\x03\x2a\x15\x21\x29\x01", + /* INFO_RESP */ [0x3e] = "\x03\x01", + /* FACILITY_RESP */ [0x3f] = "\x03\x1f\x01", + /* CONNECT_B3_RESP */ [0x41] = "\x03\x2e\x2b\x01", + /* CONNECT_B3_ACTIVE_RESP */ [0x42] = "\x03\x01", + /* DISCONNECT_B3_RESP */ [0x43] = "\x03\x01", + /* DATA_B3_RESP */ [0x45] = "\x03\x19\x01", + /* RESET_B3_RESP */ [0x46] = "\x03\x01", + /* CONNECT_B3_T90_ACTIVE_RESP */ [0x47] = "\x03\x01", + /* MANUFACTURER_RESP */ [0x4e] = "\x03\x2a\x15\x21\x29\x01", +}; + +/*-------------------------------------------------------*/ + +#define byteTLcpy(x,y) *(u8 *)(x)=*(u8 *)(y); +#define wordTLcpy(x,y) *(u16 *)(x)=*(u16 *)(y); +#define dwordTLcpy(x,y) memcpy(x,y,4); +#define structTLcpy(x,y,l) memcpy (x,y,l) +#define structTLcpyovl(x,y,l) memmove (x,y,l) + +#define byteTRcpy(x,y) *(u8 *)(y)=*(u8 *)(x); +#define wordTRcpy(x,y) *(u16 *)(y)=*(u16 *)(x); +#define dwordTRcpy(x,y) memcpy(y,x,4); +#define structTRcpy(x,y,l) memcpy (y,x,l) +#define structTRcpyovl(x,y,l) memmove (y,x,l) + +/*-------------------------------------------------------*/ +static unsigned command_2_index(unsigned c, unsigned sc) +{ + if (c & 0x80) + c = 0x9 + (c & 0x0f); + else if (c <= 0x0f); + else if (c == 0x41) + c = 0x9 + 0x1; + else if (c == 0xff) + c = 0x00; + return (sc & 3) * (0x9 + 0x9) + c; +} + +/*-------------------------------------------------------*/ +#define TYP (cdef[cmsg->par[cmsg->p]].typ) +#define OFF (((u8 *)cmsg)+cdef[cmsg->par[cmsg->p]].off) + +static void jumpcstruct(_cmsg * cmsg) +{ + unsigned layer; + for (cmsg->p++, layer = 1; layer;) { + /* $$$$$ assert (cmsg->p); */ + cmsg->p++; + switch (TYP) { + case _CMSTRUCT: + layer++; + break; + case _CEND: + layer--; + break; + } + } +} +/*-------------------------------------------------------*/ +static void pars_2_message(_cmsg * cmsg) +{ + + for (; TYP != _CEND; cmsg->p++) { + switch (TYP) { + case _CBYTE: + byteTLcpy(cmsg->m + cmsg->l, OFF); + cmsg->l++; + break; + case _CWORD: + wordTLcpy(cmsg->m + cmsg->l, OFF); + cmsg->l += 2; + break; + case _CDWORD: + dwordTLcpy(cmsg->m + cmsg->l, OFF); + cmsg->l += 4; + break; + case _CSTRUCT: + if (*(u8 **) OFF == 0) { + *(cmsg->m + cmsg->l) = '\0'; + cmsg->l++; + } else if (**(_cstruct *) OFF != 0xff) { + structTLcpy(cmsg->m + cmsg->l, *(_cstruct *) OFF, 1 + **(_cstruct *) OFF); + cmsg->l += 1 + **(_cstruct *) OFF; + } else { + _cstruct s = *(_cstruct *) OFF; + structTLcpy(cmsg->m + cmsg->l, s, 3 + *(u16 *) (s + 1)); + cmsg->l += 3 + *(u16 *) (s + 1); + } + break; + case _CMSTRUCT: +/*----- Metastruktur 0 -----*/ + if (*(_cmstruct *) OFF == CAPI_DEFAULT) { + *(cmsg->m + cmsg->l) = '\0'; + cmsg->l++; + jumpcstruct(cmsg); + } +/*----- Metastruktur wird composed -----*/ + else { + unsigned _l = cmsg->l; + unsigned _ls; + cmsg->l++; + cmsg->p++; + pars_2_message(cmsg); + _ls = cmsg->l - _l - 1; + if (_ls < 255) + (cmsg->m + _l)[0] = (u8) _ls; + else { + structTLcpyovl(cmsg->m + _l + 3, cmsg->m + _l + 1, _ls); + (cmsg->m + _l)[0] = 0xff; + wordTLcpy(cmsg->m + _l + 1, &_ls); + } + } + break; + } + } +} + +/*-------------------------------------------------------*/ +unsigned capi_cmsg2message(_cmsg * cmsg, u8 * msg) +{ + cmsg->m = msg; + cmsg->l = 8; + cmsg->p = 0; + cmsg->par = cpars[command_2_index(cmsg->Command, cmsg->Subcommand)]; + + pars_2_message(cmsg); + + wordTLcpy(msg + 0, &cmsg->l); + byteTLcpy(cmsg->m + 4, &cmsg->Command); + byteTLcpy(cmsg->m + 5, &cmsg->Subcommand); + wordTLcpy(cmsg->m + 2, &cmsg->ApplId); + wordTLcpy(cmsg->m + 6, &cmsg->Messagenumber); + + return 0; +} + +/*-------------------------------------------------------*/ +static void message_2_pars(_cmsg * cmsg) +{ + for (; TYP != _CEND; cmsg->p++) { + + switch (TYP) { + case _CBYTE: + byteTRcpy(cmsg->m + cmsg->l, OFF); + cmsg->l++; + break; + case _CWORD: + wordTRcpy(cmsg->m + cmsg->l, OFF); + cmsg->l += 2; + break; + case _CDWORD: + dwordTRcpy(cmsg->m + cmsg->l, OFF); + cmsg->l += 4; + break; + case _CSTRUCT: + *(u8 **) OFF = cmsg->m + cmsg->l; + + if (cmsg->m[cmsg->l] != 0xff) + cmsg->l += 1 + cmsg->m[cmsg->l]; + else + cmsg->l += 3 + *(u16 *) (cmsg->m + cmsg->l + 1); + break; + case _CMSTRUCT: +/*----- Metastruktur 0 -----*/ + if (cmsg->m[cmsg->l] == '\0') { + *(_cmstruct *) OFF = CAPI_DEFAULT; + cmsg->l++; + jumpcstruct(cmsg); + } else { + unsigned _l = cmsg->l; + *(_cmstruct *) OFF = CAPI_COMPOSE; + cmsg->l = (cmsg->m + _l)[0] == 255 ? cmsg->l + 3 : cmsg->l + 1; + cmsg->p++; + message_2_pars(cmsg); + } + break; + } + } +} + +/*-------------------------------------------------------*/ +unsigned capi_message2cmsg(_cmsg * cmsg, u8 * msg) +{ + memset(cmsg, 0, sizeof(_cmsg)); + cmsg->m = msg; + cmsg->l = 8; + cmsg->p = 0; + byteTRcpy(cmsg->m + 4, &cmsg->Command); + byteTRcpy(cmsg->m + 5, &cmsg->Subcommand); + cmsg->par = cpars[command_2_index(cmsg->Command, cmsg->Subcommand)]; + + message_2_pars(cmsg); + + wordTRcpy(msg + 0, &cmsg->l); + wordTRcpy(cmsg->m + 2, &cmsg->ApplId); + wordTRcpy(cmsg->m + 6, &cmsg->Messagenumber); + + return 0; +} + +/*-------------------------------------------------------*/ +unsigned capi_cmsg_header(_cmsg * cmsg, u16 _ApplId, + u8 _Command, u8 _Subcommand, + u16 _Messagenumber, u32 _Controller) +{ + memset(cmsg, 0, sizeof(_cmsg)); + cmsg->ApplId = _ApplId; + cmsg->Command = _Command; + cmsg->Subcommand = _Subcommand; + cmsg->Messagenumber = _Messagenumber; + cmsg->adr.adrController = _Controller; + return 0; +} + +/*-------------------------------------------------------*/ + +static char *mnames[] = +{ + [0x01] = "ALERT_REQ", + [0x02] = "CONNECT_REQ", + [0x04] = "DISCONNECT_REQ", + [0x05] = "LISTEN_REQ", + [0x08] = "INFO_REQ", + [0x09] = "FACILITY_REQ", + [0x0a] = "SELECT_B_PROTOCOL_REQ", + [0x0b] = "CONNECT_B3_REQ", + [0x0d] = "DISCONNECT_B3_REQ", + [0x0f] = "DATA_B3_REQ", + [0x10] = "RESET_B3_REQ", + [0x13] = "ALERT_CONF", + [0x14] = "CONNECT_CONF", + [0x16] = "DISCONNECT_CONF", + [0x17] = "LISTEN_CONF", + [0x18] = "MANUFACTURER_REQ", + [0x1a] = "INFO_CONF", + [0x1b] = "FACILITY_CONF", + [0x1c] = "SELECT_B_PROTOCOL_CONF", + [0x1d] = "CONNECT_B3_CONF", + [0x1f] = "DISCONNECT_B3_CONF", + [0x21] = "DATA_B3_CONF", + [0x22] = "RESET_B3_CONF", + [0x26] = "CONNECT_IND", + [0x27] = "CONNECT_ACTIVE_IND", + [0x28] = "DISCONNECT_IND", + [0x2a] = "MANUFACTURER_CONF", + [0x2c] = "INFO_IND", + [0x2d] = "FACILITY_IND", + [0x2f] = "CONNECT_B3_IND", + [0x30] = "CONNECT_B3_ACTIVE_IND", + [0x31] = "DISCONNECT_B3_IND", + [0x33] = "DATA_B3_IND", + [0x34] = "RESET_B3_IND", + [0x35] = "CONNECT_B3_T90_ACTIVE_IND", + [0x38] = "CONNECT_RESP", + [0x39] = "CONNECT_ACTIVE_RESP", + [0x3a] = "DISCONNECT_RESP", + [0x3c] = "MANUFACTURER_IND", + [0x3e] = "INFO_RESP", + [0x3f] = "FACILITY_RESP", + [0x41] = "CONNECT_B3_RESP", + [0x42] = "CONNECT_B3_ACTIVE_RESP", + [0x43] = "DISCONNECT_B3_RESP", + [0x45] = "DATA_B3_RESP", + [0x46] = "RESET_B3_RESP", + [0x47] = "CONNECT_B3_T90_ACTIVE_RESP", + [0x4e] = "MANUFACTURER_RESP" +}; + +char *capi_cmd2str(u8 cmd, u8 subcmd) +{ + return mnames[command_2_index(cmd, subcmd)]; +} + + +/*-------------------------------------------------------*/ +/*-------------------------------------------------------*/ + +static char *pnames[] = +{ + /*00 */ NULL, + /*01 */ NULL, + /*02 */ NULL, + /*03 */ "Controller/PLCI/NCCI", + /*04 */ "AdditionalInfo", + /*05 */ "B1configuration", + /*06 */ "B1protocol", + /*07 */ "B2configuration", + /*08 */ "B2protocol", + /*09 */ "B3configuration", + /*0a */ "B3protocol", + /*0b */ "BC", + /*0c */ "BChannelinformation", + /*0d */ "BProtocol", + /*0e */ "CalledPartyNumber", + /*0f */ "CalledPartySubaddress", + /*10 */ "CallingPartyNumber", + /*11 */ "CallingPartySubaddress", + /*12 */ "CIPmask", + /*13 */ "CIPmask2", + /*14 */ "CIPValue", + /*15 */ "Class", + /*16 */ "ConnectedNumber", + /*17 */ "ConnectedSubaddress", + /*18 */ "Data32", + /*19 */ "DataHandle", + /*1a */ "DataLength", + /*1b */ "FacilityConfirmationParameter", + /*1c */ "Facilitydataarray", + /*1d */ "FacilityIndicationParameter", + /*1e */ "FacilityRequestParameter", + /*1f */ "FacilitySelector", + /*20 */ "Flags", + /*21 */ "Function", + /*22 */ "HLC", + /*23 */ "Info", + /*24 */ "InfoElement", + /*25 */ "InfoMask", + /*26 */ "InfoNumber", + /*27 */ "Keypadfacility", + /*28 */ "LLC", + /*29 */ "ManuData", + /*2a */ "ManuID", + /*2b */ "NCPI", + /*2c */ "Reason", + /*2d */ "Reason_B3", + /*2e */ "Reject", + /*2f */ "Useruserdata" +}; + + +static char buf[8192]; +static char *p = NULL; + +#include <stdarg.h> + +/*-------------------------------------------------------*/ +static void bufprint(char *fmt,...) +{ + va_list f; + va_start(f, fmt); + vsprintf(p, fmt, f); + va_end(f); + p += strlen(p); +} + +static void printstructlen(u8 * m, unsigned len) +{ + unsigned hex = 0; + for (; len; len--, m++) + if (isalnum(*m) || *m == ' ') { + if (hex) + bufprint(">"); + bufprint("%c", *m); + hex = 0; + } else { + if (!hex) + bufprint("<%02x", *m); + else + bufprint(" %02x", *m); + hex = 1; + } + if (hex) + bufprint(">"); +} + +static void printstruct(u8 * m) +{ + unsigned len; + if (m[0] != 0xff) { + len = m[0]; + m += 1; + } else { + len = ((u16 *) (m + 1))[0]; + m += 3; + } + printstructlen(m, len); +} + +/*-------------------------------------------------------*/ +#define NAME (pnames[cmsg->par[cmsg->p]]) + +static void protocol_message_2_pars(_cmsg * cmsg, int level) +{ + for (; TYP != _CEND; cmsg->p++) { + int slen = 29 + 3 - level; + int i; + + bufprint(" "); + for (i = 0; i < level - 1; i++) + bufprint(" "); + + switch (TYP) { + case _CBYTE: + bufprint("%-*s = 0x%x\n", slen, NAME, *(u8 *) (cmsg->m + cmsg->l)); + cmsg->l++; + break; + case _CWORD: + bufprint("%-*s = 0x%x\n", slen, NAME, *(u16 *) (cmsg->m + cmsg->l)); + cmsg->l += 2; + break; + case _CDWORD: + bufprint("%-*s = 0x%lx\n", slen, NAME, *(u32 *) (cmsg->m + cmsg->l)); + cmsg->l += 4; + break; + case _CSTRUCT: + bufprint("%-*s = ", slen, NAME); + if (cmsg->m[cmsg->l] == '\0') + bufprint("default"); + else + printstruct(cmsg->m + cmsg->l); + bufprint("\n"); + if (cmsg->m[cmsg->l] != 0xff) + cmsg->l += 1 + cmsg->m[cmsg->l]; + else + cmsg->l += 3 + *(u16 *) (cmsg->m + cmsg->l + 1); + + break; + + case _CMSTRUCT: +/*----- Metastruktur 0 -----*/ + if (cmsg->m[cmsg->l] == '\0') { + bufprint("%-*s = default\n", slen, NAME); + cmsg->l++; + jumpcstruct(cmsg); + } else { + char *name = NAME; + unsigned _l = cmsg->l; + bufprint("%-*s\n", slen, name); + cmsg->l = (cmsg->m + _l)[0] == 255 ? cmsg->l + 3 : cmsg->l + 1; + cmsg->p++; + protocol_message_2_pars(cmsg, level + 1); + } + break; + } + } +} +/*-------------------------------------------------------*/ +char *capi_message2str(u8 * msg) +{ + + _cmsg cmsg; + p = buf; + p[0] = 0; + + cmsg.m = msg; + cmsg.l = 8; + cmsg.p = 0; + byteTRcpy(cmsg.m + 4, &cmsg.Command); + byteTRcpy(cmsg.m + 5, &cmsg.Subcommand); + cmsg.par = cpars[command_2_index(cmsg.Command, cmsg.Subcommand)]; + + bufprint("%-26s ID=%03d #0x%04x LEN=%04d\n", + mnames[command_2_index(cmsg.Command, cmsg.Subcommand)], + ((unsigned short *) msg)[1], + ((unsigned short *) msg)[3], + ((unsigned short *) msg)[0]); + + protocol_message_2_pars(&cmsg, 1); + return buf; +} + +char *capi_cmsg2str(_cmsg * cmsg) +{ + p = buf; + p[0] = 0; + cmsg->l = 8; + cmsg->p = 0; + bufprint("%s ID=%03d #0x%04x LEN=%04d\n", + mnames[command_2_index(cmsg->Command, cmsg->Subcommand)], + ((u16 *) cmsg->m)[1], + ((u16 *) cmsg->m)[3], + ((u16 *) cmsg->m)[0]); + protocol_message_2_pars(cmsg, 1); + return buf; +} + +EXPORT_SYMBOL(capi_cmsg2message); +EXPORT_SYMBOL(capi_message2cmsg); +EXPORT_SYMBOL(capi_cmsg_header); +EXPORT_SYMBOL(capi_cmd2str); +EXPORT_SYMBOL(capi_cmsg2str); +EXPORT_SYMBOL(capi_message2str); +EXPORT_SYMBOL(capi_info2str); diff --git a/drivers/isdn/capi/kcapi.c b/drivers/isdn/capi/kcapi.c new file mode 100644 index 000000000000..feec40cf5900 --- /dev/null +++ b/drivers/isdn/capi/kcapi.c @@ -0,0 +1,991 @@ +/* $Id: kcapi.c,v 1.1.2.8 2004/03/26 19:57:20 armin Exp $ + * + * Kernel CAPI 2.0 Module + * + * Copyright 1999 by Carsten Paeth <calle@calle.de> + * Copyright 2002 by Kai Germaschewski <kai@germaschewski.name> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#define CONFIG_AVMB1_COMPAT + +#include "kcapi.h" +#include <linux/module.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/proc_fs.h> +#include <linux/seq_file.h> +#include <linux/skbuff.h> +#include <linux/workqueue.h> +#include <linux/capi.h> +#include <linux/kernelcapi.h> +#include <linux/init.h> +#include <linux/moduleparam.h> +#include <linux/delay.h> +#include <asm/uaccess.h> +#include <linux/isdn/capicmd.h> +#include <linux/isdn/capiutil.h> +#ifdef CONFIG_AVMB1_COMPAT +#include <linux/b1lli.h> +#endif + +static char *revision = "$Revision: 1.1.2.8 $"; + +/* ------------------------------------------------------------- */ + +static int showcapimsgs = 0; + +MODULE_DESCRIPTION("CAPI4Linux: kernel CAPI layer"); +MODULE_AUTHOR("Carsten Paeth"); +MODULE_LICENSE("GPL"); +module_param(showcapimsgs, uint, 0); + +/* ------------------------------------------------------------- */ + +struct capi_notifier { + struct work_struct work; + unsigned int cmd; + u32 controller; + u16 applid; + u32 ncci; +}; + +/* ------------------------------------------------------------- */ + +static struct capi_version driver_version = {2, 0, 1, 1<<4}; +static char driver_serial[CAPI_SERIAL_LEN] = "0004711"; +static char capi_manufakturer[64] = "AVM Berlin"; + +#define NCCI2CTRL(ncci) (((ncci) >> 24) & 0x7f) + +LIST_HEAD(capi_drivers); +DEFINE_RWLOCK(capi_drivers_list_lock); + +static DEFINE_RWLOCK(application_lock); +static DECLARE_MUTEX(controller_sem); + +struct capi20_appl *capi_applications[CAPI_MAXAPPL]; +struct capi_ctr *capi_cards[CAPI_MAXCONTR]; + +static int ncards; + +/* -------- controller ref counting -------------------------------------- */ + +static inline struct capi_ctr * +capi_ctr_get(struct capi_ctr *card) +{ + if (!try_module_get(card->owner)) + return NULL; + return card; +} + +static inline void +capi_ctr_put(struct capi_ctr *card) +{ + module_put(card->owner); +} + +/* ------------------------------------------------------------- */ + +static inline struct capi_ctr *get_capi_ctr_by_nr(u16 contr) +{ + if (contr - 1 >= CAPI_MAXCONTR) + return NULL; + + return capi_cards[contr - 1]; +} + +static inline struct capi20_appl *get_capi_appl_by_nr(u16 applid) +{ + if (applid - 1 >= CAPI_MAXAPPL) + return NULL; + + return capi_applications[applid - 1]; +} + +/* -------- util functions ------------------------------------ */ + +static inline int capi_cmd_valid(u8 cmd) +{ + switch (cmd) { + case CAPI_ALERT: + case CAPI_CONNECT: + case CAPI_CONNECT_ACTIVE: + case CAPI_CONNECT_B3_ACTIVE: + case CAPI_CONNECT_B3: + case CAPI_CONNECT_B3_T90_ACTIVE: + case CAPI_DATA_B3: + case CAPI_DISCONNECT_B3: + case CAPI_DISCONNECT: + case CAPI_FACILITY: + case CAPI_INFO: + case CAPI_LISTEN: + case CAPI_MANUFACTURER: + case CAPI_RESET_B3: + case CAPI_SELECT_B_PROTOCOL: + return 1; + } + return 0; +} + +static inline int capi_subcmd_valid(u8 subcmd) +{ + switch (subcmd) { + case CAPI_REQ: + case CAPI_CONF: + case CAPI_IND: + case CAPI_RESP: + return 1; + } + return 0; +} + +/* ------------------------------------------------------------ */ + +static void register_appl(struct capi_ctr *card, u16 applid, capi_register_params *rparam) +{ + card = capi_ctr_get(card); + + if (card) + card->register_appl(card, applid, rparam); + else + printk(KERN_WARNING "%s: cannot get card resources\n", __FUNCTION__); +} + + +static void release_appl(struct capi_ctr *card, u16 applid) +{ + DBG("applid %#x", applid); + + card->release_appl(card, applid); + capi_ctr_put(card); +} + +/* -------- KCI_CONTRUP --------------------------------------- */ + +static void notify_up(u32 contr) +{ + struct capi_ctr *card = get_capi_ctr_by_nr(contr); + struct capi20_appl *ap; + u16 applid; + + if (showcapimsgs & 1) { + printk(KERN_DEBUG "kcapi: notify up contr %d\n", contr); + } + if (!card) { + printk(KERN_WARNING "%s: invalid contr %d\n", __FUNCTION__, contr); + return; + } + for (applid = 1; applid <= CAPI_MAXAPPL; applid++) { + ap = get_capi_appl_by_nr(applid); + if (!ap || ap->release_in_progress) continue; + register_appl(card, applid, &ap->rparam); + if (ap->callback && !ap->release_in_progress) + ap->callback(KCI_CONTRUP, contr, &card->profile); + } +} + +/* -------- KCI_CONTRDOWN ------------------------------------- */ + +static void notify_down(u32 contr) +{ + struct capi20_appl *ap; + u16 applid; + + if (showcapimsgs & 1) { + printk(KERN_DEBUG "kcapi: notify down contr %d\n", contr); + } + + for (applid = 1; applid <= CAPI_MAXAPPL; applid++) { + ap = get_capi_appl_by_nr(applid); + if (ap && ap->callback && !ap->release_in_progress) + ap->callback(KCI_CONTRDOWN, contr, NULL); + } +} + +static void notify_handler(void *data) +{ + struct capi_notifier *np = data; + + switch (np->cmd) { + case KCI_CONTRUP: + notify_up(np->controller); + break; + case KCI_CONTRDOWN: + notify_down(np->controller); + break; + } + + kfree(np); +} + +/* + * The notifier will result in adding/deleteing of devices. Devices can + * only removed in user process, not in bh. + */ +static int notify_push(unsigned int cmd, u32 controller, u16 applid, u32 ncci) +{ + struct capi_notifier *np = kmalloc(sizeof(*np), GFP_ATOMIC); + + if (!np) + return -ENOMEM; + + INIT_WORK(&np->work, notify_handler, np); + np->cmd = cmd; + np->controller = controller; + np->applid = applid; + np->ncci = ncci; + + schedule_work(&np->work); + return 0; +} + + +/* -------- Receiver ------------------------------------------ */ + +static void recv_handler(void *_ap) +{ + struct sk_buff *skb; + struct capi20_appl *ap = (struct capi20_appl *) _ap; + + if ((!ap) || (ap->release_in_progress)) + return; + + down(&ap->recv_sem); + while ((skb = skb_dequeue(&ap->recv_queue))) { + if (CAPIMSG_CMD(skb->data) == CAPI_DATA_B3_IND) + ap->nrecvdatapkt++; + else + ap->nrecvctlpkt++; + + ap->recv_message(ap, skb); + } + up(&ap->recv_sem); +} + +void capi_ctr_handle_message(struct capi_ctr * card, u16 appl, struct sk_buff *skb) +{ + struct capi20_appl *ap; + int showctl = 0; + u8 cmd, subcmd; + unsigned long flags; + + if (card->cardstate != CARD_RUNNING) { + printk(KERN_INFO "kcapi: controller %d not active, got: %s", + card->cnr, capi_message2str(skb->data)); + goto error; + } + + cmd = CAPIMSG_COMMAND(skb->data); + subcmd = CAPIMSG_SUBCOMMAND(skb->data); + if (cmd == CAPI_DATA_B3 && subcmd == CAPI_IND) { + card->nrecvdatapkt++; + if (card->traceflag > 2) showctl |= 2; + } else { + card->nrecvctlpkt++; + if (card->traceflag) showctl |= 2; + } + showctl |= (card->traceflag & 1); + if (showctl & 2) { + if (showctl & 1) { + printk(KERN_DEBUG "kcapi: got [0x%lx] id#%d %s len=%u\n", + (unsigned long) card->cnr, + CAPIMSG_APPID(skb->data), + capi_cmd2str(cmd, subcmd), + CAPIMSG_LEN(skb->data)); + } else { + printk(KERN_DEBUG "kcapi: got [0x%lx] %s\n", + (unsigned long) card->cnr, + capi_message2str(skb->data)); + } + + } + + read_lock_irqsave(&application_lock, flags); + ap = get_capi_appl_by_nr(CAPIMSG_APPID(skb->data)); + if ((!ap) || (ap->release_in_progress)) { + read_unlock_irqrestore(&application_lock, flags); + printk(KERN_ERR "kcapi: handle_message: applid %d state released (%s)\n", + CAPIMSG_APPID(skb->data), capi_message2str(skb->data)); + goto error; + } + skb_queue_tail(&ap->recv_queue, skb); + schedule_work(&ap->recv_work); + read_unlock_irqrestore(&application_lock, flags); + + return; + +error: + kfree_skb(skb); +} + +EXPORT_SYMBOL(capi_ctr_handle_message); + +void capi_ctr_ready(struct capi_ctr * card) +{ + card->cardstate = CARD_RUNNING; + + printk(KERN_NOTICE "kcapi: card %d \"%s\" ready.\n", + card->cnr, card->name); + + notify_push(KCI_CONTRUP, card->cnr, 0, 0); +} + +EXPORT_SYMBOL(capi_ctr_ready); + +void capi_ctr_reseted(struct capi_ctr * card) +{ + u16 appl; + + DBG(""); + + if (card->cardstate == CARD_DETECTED) + return; + + card->cardstate = CARD_DETECTED; + + memset(card->manu, 0, sizeof(card->manu)); + memset(&card->version, 0, sizeof(card->version)); + memset(&card->profile, 0, sizeof(card->profile)); + memset(card->serial, 0, sizeof(card->serial)); + + for (appl = 1; appl <= CAPI_MAXAPPL; appl++) { + struct capi20_appl *ap = get_capi_appl_by_nr(appl); + if (!ap || ap->release_in_progress) + continue; + + capi_ctr_put(card); + } + + printk(KERN_NOTICE "kcapi: card %d down.\n", card->cnr); + + notify_push(KCI_CONTRDOWN, card->cnr, 0, 0); +} + +EXPORT_SYMBOL(capi_ctr_reseted); + +void capi_ctr_suspend_output(struct capi_ctr *card) +{ + if (!card->blocked) { + printk(KERN_DEBUG "kcapi: card %d suspend\n", card->cnr); + card->blocked = 1; + } +} + +EXPORT_SYMBOL(capi_ctr_suspend_output); + +void capi_ctr_resume_output(struct capi_ctr *card) +{ + if (card->blocked) { + printk(KERN_DEBUG "kcapi: card %d resume\n", card->cnr); + card->blocked = 0; + } +} + +EXPORT_SYMBOL(capi_ctr_resume_output); + +/* ------------------------------------------------------------- */ + +int +attach_capi_ctr(struct capi_ctr *card) +{ + int i; + + down(&controller_sem); + + for (i = 0; i < CAPI_MAXCONTR; i++) { + if (capi_cards[i] == NULL) + break; + } + if (i == CAPI_MAXCONTR) { + up(&controller_sem); + printk(KERN_ERR "kcapi: out of controller slots\n"); + return -EBUSY; + } + capi_cards[i] = card; + + up(&controller_sem); + + card->nrecvctlpkt = 0; + card->nrecvdatapkt = 0; + card->nsentctlpkt = 0; + card->nsentdatapkt = 0; + card->cnr = i + 1; + card->cardstate = CARD_DETECTED; + card->blocked = 0; + card->traceflag = showcapimsgs; + + sprintf(card->procfn, "capi/controllers/%d", card->cnr); + card->procent = create_proc_entry(card->procfn, 0, NULL); + if (card->procent) { + card->procent->read_proc = + (int (*)(char *,char **,off_t,int,int *,void *)) + card->ctr_read_proc; + card->procent->data = card; + } + + ncards++; + printk(KERN_NOTICE "kcapi: Controller %d: %s attached\n", + card->cnr, card->name); + return 0; +} + +EXPORT_SYMBOL(attach_capi_ctr); + +int detach_capi_ctr(struct capi_ctr *card) +{ + if (card->cardstate != CARD_DETECTED) + capi_ctr_reseted(card); + + ncards--; + + if (card->procent) { + remove_proc_entry(card->procfn, NULL); + card->procent = NULL; + } + capi_cards[card->cnr - 1] = NULL; + printk(KERN_NOTICE "kcapi: Controller %d: %s unregistered\n", + card->cnr, card->name); + + return 0; +} + +EXPORT_SYMBOL(detach_capi_ctr); + +void register_capi_driver(struct capi_driver *driver) +{ + unsigned long flags; + + write_lock_irqsave(&capi_drivers_list_lock, flags); + list_add_tail(&driver->list, &capi_drivers); + write_unlock_irqrestore(&capi_drivers_list_lock, flags); +} + +EXPORT_SYMBOL(register_capi_driver); + +void unregister_capi_driver(struct capi_driver *driver) +{ + unsigned long flags; + + write_lock_irqsave(&capi_drivers_list_lock, flags); + list_del(&driver->list); + write_unlock_irqrestore(&capi_drivers_list_lock, flags); +} + +EXPORT_SYMBOL(unregister_capi_driver); + +/* ------------------------------------------------------------- */ +/* -------- CAPI2.0 Interface ---------------------------------- */ +/* ------------------------------------------------------------- */ + +u16 capi20_isinstalled(void) +{ + int i; + for (i = 0; i < CAPI_MAXCONTR; i++) { + if (capi_cards[i] && capi_cards[i]->cardstate == CARD_RUNNING) + return CAPI_NOERROR; + } + return CAPI_REGNOTINSTALLED; +} + +EXPORT_SYMBOL(capi20_isinstalled); + +u16 capi20_register(struct capi20_appl *ap) +{ + int i; + u16 applid; + unsigned long flags; + + DBG(""); + + if (ap->rparam.datablklen < 128) + return CAPI_LOGBLKSIZETOSMALL; + + write_lock_irqsave(&application_lock, flags); + + for (applid = 1; applid <= CAPI_MAXAPPL; applid++) { + if (capi_applications[applid - 1] == NULL) + break; + } + if (applid > CAPI_MAXAPPL) { + write_unlock_irqrestore(&application_lock, flags); + return CAPI_TOOMANYAPPLS; + } + + ap->applid = applid; + capi_applications[applid - 1] = ap; + + ap->nrecvctlpkt = 0; + ap->nrecvdatapkt = 0; + ap->nsentctlpkt = 0; + ap->nsentdatapkt = 0; + ap->callback = NULL; + init_MUTEX(&ap->recv_sem); + skb_queue_head_init(&ap->recv_queue); + INIT_WORK(&ap->recv_work, recv_handler, (void *)ap); + ap->release_in_progress = 0; + + write_unlock_irqrestore(&application_lock, flags); + + down(&controller_sem); + for (i = 0; i < CAPI_MAXCONTR; i++) { + if (!capi_cards[i] || capi_cards[i]->cardstate != CARD_RUNNING) + continue; + register_appl(capi_cards[i], applid, &ap->rparam); + } + up(&controller_sem); + + if (showcapimsgs & 1) { + printk(KERN_DEBUG "kcapi: appl %d up\n", applid); + } + + return CAPI_NOERROR; +} + +EXPORT_SYMBOL(capi20_register); + +u16 capi20_release(struct capi20_appl *ap) +{ + int i; + unsigned long flags; + + DBG("applid %#x", ap->applid); + + write_lock_irqsave(&application_lock, flags); + ap->release_in_progress = 1; + capi_applications[ap->applid - 1] = NULL; + write_unlock_irqrestore(&application_lock, flags); + + down(&controller_sem); + for (i = 0; i < CAPI_MAXCONTR; i++) { + if (!capi_cards[i] || capi_cards[i]->cardstate != CARD_RUNNING) + continue; + release_appl(capi_cards[i], ap->applid); + } + up(&controller_sem); + + flush_scheduled_work(); + skb_queue_purge(&ap->recv_queue); + + if (showcapimsgs & 1) { + printk(KERN_DEBUG "kcapi: appl %d down\n", ap->applid); + } + + return CAPI_NOERROR; +} + +EXPORT_SYMBOL(capi20_release); + +u16 capi20_put_message(struct capi20_appl *ap, struct sk_buff *skb) +{ + struct capi_ctr *card; + int showctl = 0; + u8 cmd, subcmd; + + DBG("applid %#x", ap->applid); + + if (ncards == 0) + return CAPI_REGNOTINSTALLED; + if ((ap->applid == 0) || ap->release_in_progress) + return CAPI_ILLAPPNR; + if (skb->len < 12 + || !capi_cmd_valid(CAPIMSG_COMMAND(skb->data)) + || !capi_subcmd_valid(CAPIMSG_SUBCOMMAND(skb->data))) + return CAPI_ILLCMDORSUBCMDORMSGTOSMALL; + card = get_capi_ctr_by_nr(CAPIMSG_CONTROLLER(skb->data)); + if (!card || card->cardstate != CARD_RUNNING) { + card = get_capi_ctr_by_nr(1); // XXX why? + if (!card || card->cardstate != CARD_RUNNING) + return CAPI_REGNOTINSTALLED; + } + if (card->blocked) + return CAPI_SENDQUEUEFULL; + + cmd = CAPIMSG_COMMAND(skb->data); + subcmd = CAPIMSG_SUBCOMMAND(skb->data); + + if (cmd == CAPI_DATA_B3 && subcmd== CAPI_REQ) { + card->nsentdatapkt++; + ap->nsentdatapkt++; + if (card->traceflag > 2) showctl |= 2; + } else { + card->nsentctlpkt++; + ap->nsentctlpkt++; + if (card->traceflag) showctl |= 2; + } + showctl |= (card->traceflag & 1); + if (showctl & 2) { + if (showctl & 1) { + printk(KERN_DEBUG "kcapi: put [%#x] id#%d %s len=%u\n", + CAPIMSG_CONTROLLER(skb->data), + CAPIMSG_APPID(skb->data), + capi_cmd2str(cmd, subcmd), + CAPIMSG_LEN(skb->data)); + } else { + printk(KERN_DEBUG "kcapi: put [%#x] %s\n", + CAPIMSG_CONTROLLER(skb->data), + capi_message2str(skb->data)); + } + + } + return card->send_message(card, skb); +} + +EXPORT_SYMBOL(capi20_put_message); + +u16 capi20_get_manufacturer(u32 contr, u8 *buf) +{ + struct capi_ctr *card; + + if (contr == 0) { + strlcpy(buf, capi_manufakturer, CAPI_MANUFACTURER_LEN); + return CAPI_NOERROR; + } + card = get_capi_ctr_by_nr(contr); + if (!card || card->cardstate != CARD_RUNNING) + return CAPI_REGNOTINSTALLED; + strlcpy(buf, card->manu, CAPI_MANUFACTURER_LEN); + return CAPI_NOERROR; +} + +EXPORT_SYMBOL(capi20_get_manufacturer); + +u16 capi20_get_version(u32 contr, struct capi_version *verp) +{ + struct capi_ctr *card; + + if (contr == 0) { + *verp = driver_version; + return CAPI_NOERROR; + } + card = get_capi_ctr_by_nr(contr); + if (!card || card->cardstate != CARD_RUNNING) + return CAPI_REGNOTINSTALLED; + + memcpy((void *) verp, &card->version, sizeof(capi_version)); + return CAPI_NOERROR; +} + +EXPORT_SYMBOL(capi20_get_version); + +u16 capi20_get_serial(u32 contr, u8 *serial) +{ + struct capi_ctr *card; + + if (contr == 0) { + strlcpy(serial, driver_serial, CAPI_SERIAL_LEN); + return CAPI_NOERROR; + } + card = get_capi_ctr_by_nr(contr); + if (!card || card->cardstate != CARD_RUNNING) + return CAPI_REGNOTINSTALLED; + + strlcpy((void *) serial, card->serial, CAPI_SERIAL_LEN); + return CAPI_NOERROR; +} + +EXPORT_SYMBOL(capi20_get_serial); + +u16 capi20_get_profile(u32 contr, struct capi_profile *profp) +{ + struct capi_ctr *card; + + if (contr == 0) { + profp->ncontroller = ncards; + return CAPI_NOERROR; + } + card = get_capi_ctr_by_nr(contr); + if (!card || card->cardstate != CARD_RUNNING) + return CAPI_REGNOTINSTALLED; + + memcpy((void *) profp, &card->profile, + sizeof(struct capi_profile)); + return CAPI_NOERROR; +} + +EXPORT_SYMBOL(capi20_get_profile); + +#ifdef CONFIG_AVMB1_COMPAT +static int old_capi_manufacturer(unsigned int cmd, void __user *data) +{ + avmb1_loadandconfigdef ldef; + avmb1_extcarddef cdef; + avmb1_resetdef rdef; + capicardparams cparams; + struct capi_ctr *card; + struct capi_driver *driver = NULL; + capiloaddata ldata; + struct list_head *l; + unsigned long flags; + int retval; + + switch (cmd) { + case AVMB1_ADDCARD: + case AVMB1_ADDCARD_WITH_TYPE: + if (cmd == AVMB1_ADDCARD) { + if ((retval = copy_from_user(&cdef, data, + sizeof(avmb1_carddef)))) + return retval; + cdef.cardtype = AVM_CARDTYPE_B1; + } else { + if ((retval = copy_from_user(&cdef, data, + sizeof(avmb1_extcarddef)))) + return retval; + } + cparams.port = cdef.port; + cparams.irq = cdef.irq; + cparams.cardnr = cdef.cardnr; + + read_lock_irqsave(&capi_drivers_list_lock, flags); + switch (cdef.cardtype) { + case AVM_CARDTYPE_B1: + list_for_each(l, &capi_drivers) { + driver = list_entry(l, struct capi_driver, list); + if (strcmp(driver->name, "b1isa") == 0) + break; + } + break; + case AVM_CARDTYPE_T1: + list_for_each(l, &capi_drivers) { + driver = list_entry(l, struct capi_driver, list); + if (strcmp(driver->name, "t1isa") == 0) + break; + } + break; + default: + driver = NULL; + break; + } + if (!driver) { + read_unlock_irqrestore(&capi_drivers_list_lock, flags); + printk(KERN_ERR "kcapi: driver not loaded.\n"); + return -EIO; + } + if (!driver->add_card) { + read_unlock_irqrestore(&capi_drivers_list_lock, flags); + printk(KERN_ERR "kcapi: driver has no add card function.\n"); + return -EIO; + } + + retval = driver->add_card(driver, &cparams); + read_unlock_irqrestore(&capi_drivers_list_lock, flags); + return retval; + + case AVMB1_LOAD: + case AVMB1_LOAD_AND_CONFIG: + + if (cmd == AVMB1_LOAD) { + if (copy_from_user(&ldef, data, + sizeof(avmb1_loaddef))) + return -EFAULT; + ldef.t4config.len = 0; + ldef.t4config.data = NULL; + } else { + if (copy_from_user(&ldef, data, + sizeof(avmb1_loadandconfigdef))) + return -EFAULT; + } + card = get_capi_ctr_by_nr(ldef.contr); + card = capi_ctr_get(card); + if (!card) + return -ESRCH; + if (card->load_firmware == 0) { + printk(KERN_DEBUG "kcapi: load: no load function\n"); + return -ESRCH; + } + + if (ldef.t4file.len <= 0) { + printk(KERN_DEBUG "kcapi: load: invalid parameter: length of t4file is %d ?\n", ldef.t4file.len); + return -EINVAL; + } + if (ldef.t4file.data == 0) { + printk(KERN_DEBUG "kcapi: load: invalid parameter: dataptr is 0\n"); + return -EINVAL; + } + + ldata.firmware.user = 1; + ldata.firmware.data = ldef.t4file.data; + ldata.firmware.len = ldef.t4file.len; + ldata.configuration.user = 1; + ldata.configuration.data = ldef.t4config.data; + ldata.configuration.len = ldef.t4config.len; + + if (card->cardstate != CARD_DETECTED) { + printk(KERN_INFO "kcapi: load: contr=%d not in detect state\n", ldef.contr); + return -EBUSY; + } + card->cardstate = CARD_LOADING; + + retval = card->load_firmware(card, &ldata); + + if (retval) { + card->cardstate = CARD_DETECTED; + capi_ctr_put(card); + return retval; + } + + while (card->cardstate != CARD_RUNNING) { + + msleep_interruptible(100); /* 0.1 sec */ + + if (signal_pending(current)) { + capi_ctr_put(card); + return -EINTR; + } + } + capi_ctr_put(card); + return 0; + + case AVMB1_RESETCARD: + if (copy_from_user(&rdef, data, sizeof(avmb1_resetdef))) + return -EFAULT; + card = get_capi_ctr_by_nr(rdef.contr); + if (!card) + return -ESRCH; + + if (card->cardstate == CARD_DETECTED) + return 0; + + card->reset_ctr(card); + + while (card->cardstate > CARD_DETECTED) { + + msleep_interruptible(100); /* 0.1 sec */ + + if (signal_pending(current)) + return -EINTR; + } + return 0; + + } + return -EINVAL; +} +#endif + +int capi20_manufacturer(unsigned int cmd, void __user *data) +{ + struct capi_ctr *card; + + switch (cmd) { +#ifdef CONFIG_AVMB1_COMPAT + case AVMB1_LOAD: + case AVMB1_LOAD_AND_CONFIG: + case AVMB1_RESETCARD: + case AVMB1_GET_CARDINFO: + case AVMB1_REMOVECARD: + return old_capi_manufacturer(cmd, data); +#endif + case KCAPI_CMD_TRACE: + { + kcapi_flagdef fdef; + + if (copy_from_user(&fdef, data, sizeof(kcapi_flagdef))) + return -EFAULT; + + card = get_capi_ctr_by_nr(fdef.contr); + if (!card) + return -ESRCH; + + card->traceflag = fdef.flag; + printk(KERN_INFO "kcapi: contr %d set trace=%d\n", + card->cnr, card->traceflag); + return 0; + } + case KCAPI_CMD_ADDCARD: + { + struct list_head *l; + struct capi_driver *driver = NULL; + capicardparams cparams; + kcapi_carddef cdef; + int retval; + + if ((retval = copy_from_user(&cdef, data, sizeof(cdef)))) + return retval; + + cparams.port = cdef.port; + cparams.irq = cdef.irq; + cparams.membase = cdef.membase; + cparams.cardnr = cdef.cardnr; + cparams.cardtype = 0; + cdef.driver[sizeof(cdef.driver)-1] = 0; + + list_for_each(l, &capi_drivers) { + driver = list_entry(l, struct capi_driver, list); + if (strcmp(driver->name, cdef.driver) == 0) + break; + } + if (driver == 0) { + printk(KERN_ERR "kcapi: driver \"%s\" not loaded.\n", + cdef.driver); + return -ESRCH; + } + + if (!driver->add_card) { + printk(KERN_ERR "kcapi: driver \"%s\" has no add card function.\n", cdef.driver); + return -EIO; + } + + return driver->add_card(driver, &cparams); + } + + default: + printk(KERN_ERR "kcapi: manufacturer command %d unknown.\n", + cmd); + break; + + } + return -EINVAL; +} + +EXPORT_SYMBOL(capi20_manufacturer); + +/* temporary hack */ +void capi20_set_callback(struct capi20_appl *ap, + void (*callback) (unsigned int cmd, __u32 contr, void *data)) +{ + ap->callback = callback; +} + +EXPORT_SYMBOL(capi20_set_callback); + +/* ------------------------------------------------------------- */ +/* -------- Init & Cleanup ------------------------------------- */ +/* ------------------------------------------------------------- */ + +/* + * init / exit functions + */ + +static int __init kcapi_init(void) +{ + char *p; + char rev[32]; + + kcapi_proc_init(); + + if ((p = strchr(revision, ':')) != 0 && p[1]) { + strlcpy(rev, p + 2, sizeof(rev)); + if ((p = strchr(rev, '$')) != 0 && p > rev) + *(p-1) = 0; + } else + strcpy(rev, "1.0"); + + printk(KERN_NOTICE "CAPI Subsystem Rev %s\n", rev); + + return 0; +} + +static void __exit kcapi_exit(void) +{ + kcapi_proc_exit(); + + /* make sure all notifiers are finished */ + flush_scheduled_work(); +} + +module_init(kcapi_init); +module_exit(kcapi_exit); diff --git a/drivers/isdn/capi/kcapi.h b/drivers/isdn/capi/kcapi.h new file mode 100644 index 000000000000..1cb2c40f9921 --- /dev/null +++ b/drivers/isdn/capi/kcapi.h @@ -0,0 +1,49 @@ +/* + * Kernel CAPI 2.0 Module + * + * Copyright 1999 by Carsten Paeth <calle@calle.de> + * Copyright 2002 by Kai Germaschewski <kai@germaschewski.name> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + + +#include <linux/kernel.h> +#include <linux/spinlock.h> +#include <linux/list.h> +#include <linux/isdn/capilli.h> + +#ifdef KCAPI_DEBUG +#define DBG(format, arg...) do { \ +printk(KERN_DEBUG "%s: " format "\n" , __FUNCTION__ , ## arg); \ +} while (0) +#else +#define DBG(format, arg...) /* */ +#endif + +enum { + CARD_DETECTED = 1, + CARD_LOADING = 2, + CARD_RUNNING = 3, +}; + +extern struct list_head capi_drivers; +extern rwlock_t capi_drivers_list_lock; + +extern struct capi20_appl *capi_applications[CAPI_MAXAPPL]; +extern struct capi_ctr *capi_cards[CAPI_MAXCONTR]; + +#ifdef CONFIG_PROC_FS + +void kcapi_proc_init(void); +void kcapi_proc_exit(void); + +#else + +static inline void kcapi_proc_init(void) { }; +static inline void kcapi_proc_exit(void) { }; + +#endif + diff --git a/drivers/isdn/capi/kcapi_proc.c b/drivers/isdn/capi/kcapi_proc.c new file mode 100644 index 000000000000..16dc5418ff41 --- /dev/null +++ b/drivers/isdn/capi/kcapi_proc.c @@ -0,0 +1,336 @@ +/* + * Kernel CAPI 2.0 Module - /proc/capi handling + * + * Copyright 1999 by Carsten Paeth <calle@calle.de> + * Copyright 2002 by Kai Germaschewski <kai@germaschewski.name> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + + +#include "kcapi.h" +#include <linux/proc_fs.h> +#include <linux/seq_file.h> +#include <linux/init.h> + +static char * +cardstate2str(unsigned short cardstate) +{ + switch (cardstate) { + case CARD_DETECTED: return "detected"; + case CARD_LOADING: return "loading"; + case CARD_RUNNING: return "running"; + default: return "???"; + } +} + +// /proc/capi +// =========================================================================== + +// /proc/capi/controller: +// cnr driver cardstate name driverinfo +// /proc/capi/contrstats: +// cnr nrecvctlpkt nrecvdatapkt nsentctlpkt nsentdatapkt +// --------------------------------------------------------------------------- + +static void *controller_start(struct seq_file *seq, loff_t *pos) +{ + if (*pos < CAPI_MAXCONTR) + return &capi_cards[*pos]; + + return NULL; +} + +static void *controller_next(struct seq_file *seq, void *v, loff_t *pos) +{ + ++*pos; + if (*pos < CAPI_MAXCONTR) + return &capi_cards[*pos]; + + return NULL; +} + +static void controller_stop(struct seq_file *seq, void *v) +{ +} + +static int controller_show(struct seq_file *seq, void *v) +{ + struct capi_ctr *ctr = *(struct capi_ctr **) v; + + if (!ctr) + return 0; + + seq_printf(seq, "%d %-10s %-8s %-16s %s\n", + ctr->cnr, ctr->driver_name, + cardstate2str(ctr->cardstate), + ctr->name, + ctr->procinfo ? ctr->procinfo(ctr) : ""); + + return 0; +} + +static int contrstats_show(struct seq_file *seq, void *v) +{ + struct capi_ctr *ctr = *(struct capi_ctr **) v; + + if (!ctr) + return 0; + + seq_printf(seq, "%d %lu %lu %lu %lu\n", + ctr->cnr, + ctr->nrecvctlpkt, + ctr->nrecvdatapkt, + ctr->nsentctlpkt, + ctr->nsentdatapkt); + + return 0; +} + +struct seq_operations seq_controller_ops = { + .start = controller_start, + .next = controller_next, + .stop = controller_stop, + .show = controller_show, +}; + +struct seq_operations seq_contrstats_ops = { + .start = controller_start, + .next = controller_next, + .stop = controller_stop, + .show = contrstats_show, +}; + +static int seq_controller_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &seq_controller_ops); +} + +static int seq_contrstats_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &seq_contrstats_ops); +} + +static struct file_operations proc_controller_ops = { + .open = seq_controller_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static struct file_operations proc_contrstats_ops = { + .open = seq_contrstats_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +// /proc/capi/applications: +// applid l3cnt dblkcnt dblklen #ncci recvqueuelen +// /proc/capi/applstats: +// applid nrecvctlpkt nrecvdatapkt nsentctlpkt nsentdatapkt +// --------------------------------------------------------------------------- + +static void * +applications_start(struct seq_file *seq, loff_t *pos) +{ + if (*pos < CAPI_MAXAPPL) + return &capi_applications[*pos]; + + return NULL; +} + +static void * +applications_next(struct seq_file *seq, void *v, loff_t *pos) +{ + ++*pos; + if (*pos < CAPI_MAXAPPL) + return &capi_applications[*pos]; + + return NULL; +} + +static void +applications_stop(struct seq_file *seq, void *v) +{ +} + +static int +applications_show(struct seq_file *seq, void *v) +{ + struct capi20_appl *ap = *(struct capi20_appl **) v; + + if (!ap) + return 0; + + seq_printf(seq, "%u %d %d %d\n", + ap->applid, + ap->rparam.level3cnt, + ap->rparam.datablkcnt, + ap->rparam.datablklen); + + return 0; +} + +static int +applstats_show(struct seq_file *seq, void *v) +{ + struct capi20_appl *ap = *(struct capi20_appl **) v; + + if (!ap) + return 0; + + seq_printf(seq, "%u %lu %lu %lu %lu\n", + ap->applid, + ap->nrecvctlpkt, + ap->nrecvdatapkt, + ap->nsentctlpkt, + ap->nsentdatapkt); + + return 0; +} + +struct seq_operations seq_applications_ops = { + .start = applications_start, + .next = applications_next, + .stop = applications_stop, + .show = applications_show, +}; + +struct seq_operations seq_applstats_ops = { + .start = applications_start, + .next = applications_next, + .stop = applications_stop, + .show = applstats_show, +}; + +static int +seq_applications_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &seq_applications_ops); +} + +static int +seq_applstats_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &seq_applstats_ops); +} + +static struct file_operations proc_applications_ops = { + .open = seq_applications_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static struct file_operations proc_applstats_ops = { + .open = seq_applstats_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static void +create_seq_entry(char *name, mode_t mode, struct file_operations *f) +{ + struct proc_dir_entry *entry; + entry = create_proc_entry(name, mode, NULL); + if (entry) + entry->proc_fops = f; +} + +// --------------------------------------------------------------------------- + + +static __inline__ struct capi_driver *capi_driver_get_idx(loff_t pos) +{ + struct capi_driver *drv = NULL; + struct list_head *l; + loff_t i; + + i = 0; + list_for_each(l, &capi_drivers) { + drv = list_entry(l, struct capi_driver, list); + if (i++ == pos) + return drv; + } + return NULL; +} + +static void *capi_driver_start(struct seq_file *seq, loff_t *pos) +{ + struct capi_driver *drv; + read_lock(&capi_drivers_list_lock); + drv = capi_driver_get_idx(*pos); + return drv; +} + +static void *capi_driver_next(struct seq_file *seq, void *v, loff_t *pos) +{ + struct capi_driver *drv = (struct capi_driver *)v; + ++*pos; + if (drv->list.next == &capi_drivers) return NULL; + return list_entry(drv->list.next, struct capi_driver, list); +} + +static void capi_driver_stop(struct seq_file *seq, void *v) +{ + read_unlock(&capi_drivers_list_lock); +} + +static int capi_driver_show(struct seq_file *seq, void *v) +{ + struct capi_driver *drv = (struct capi_driver *)v; + seq_printf(seq, "%-32s %s\n", drv->name, drv->revision); + return 0; +} + +struct seq_operations seq_capi_driver_ops = { + .start = capi_driver_start, + .next = capi_driver_next, + .stop = capi_driver_stop, + .show = capi_driver_show, +}; + +static int +seq_capi_driver_open(struct inode *inode, struct file *file) +{ + int err; + err = seq_open(file, &seq_capi_driver_ops); + return err; +} + +static struct file_operations proc_driver_ops = { + .open = seq_capi_driver_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +// --------------------------------------------------------------------------- + +void __init +kcapi_proc_init(void) +{ + proc_mkdir("capi", NULL); + proc_mkdir("capi/controllers", NULL); + create_seq_entry("capi/controller", 0, &proc_controller_ops); + create_seq_entry("capi/contrstats", 0, &proc_contrstats_ops); + create_seq_entry("capi/applications", 0, &proc_applications_ops); + create_seq_entry("capi/applstats", 0, &proc_applstats_ops); + create_seq_entry("capi/driver", 0, &proc_driver_ops); +} + +void __exit +kcapi_proc_exit(void) +{ + remove_proc_entry("capi/driver", NULL); + remove_proc_entry("capi/controller", NULL); + remove_proc_entry("capi/contrstats", NULL); + remove_proc_entry("capi/applications", NULL); + remove_proc_entry("capi/applstats", NULL); + remove_proc_entry("capi/controllers", NULL); + remove_proc_entry("capi", NULL); +} diff --git a/drivers/isdn/divert/Makefile b/drivers/isdn/divert/Makefile new file mode 100644 index 000000000000..dd4a202e0bc2 --- /dev/null +++ b/drivers/isdn/divert/Makefile @@ -0,0 +1,9 @@ +# Makefile for the dss1_divert ISDN module + +# Each configuration option enables a list of files. + +obj-$(CONFIG_ISDN_DIVERSION) += dss1_divert.o + +# Multipart objects. + +dss1_divert-y := isdn_divert.o divert_procfs.o divert_init.o diff --git a/drivers/isdn/divert/divert_init.c b/drivers/isdn/divert/divert_init.c new file mode 100644 index 000000000000..434e684f5dbb --- /dev/null +++ b/drivers/isdn/divert/divert_init.c @@ -0,0 +1,83 @@ +/* $Id divert_init.c,v 1.5.6.2 2001/01/24 22:18:17 kai Exp $ + * + * Module init for DSS1 diversion services for i4l. + * + * Copyright 1999 by Werner Cornelius (werner@isdn4linux.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/module.h> +#include <linux/version.h> +#include <linux/init.h> +#include <linux/kernel.h> + +#include "isdn_divert.h" + +MODULE_DESCRIPTION("ISDN4Linux: Call diversion support"); +MODULE_AUTHOR("Werner Cornelius"); +MODULE_LICENSE("GPL"); + +/****************************************/ +/* structure containing interface to hl */ +/****************************************/ +isdn_divert_if divert_if = + { DIVERT_IF_MAGIC, /* magic value */ + DIVERT_CMD_REG, /* register cmd */ + ll_callback, /* callback routine from ll */ + NULL, /* command still not specified */ + NULL, /* drv_to_name */ + NULL, /* name_to_drv */ + }; + +/*************************/ +/* Module interface code */ +/* no cmd line parms */ +/*************************/ +static int __init divert_init(void) +{ int i; + + if (divert_dev_init()) + { printk(KERN_WARNING "dss1_divert: cannot install device, not loaded\n"); + return(-EIO); + } + if ((i = DIVERT_REG_NAME(&divert_if)) != DIVERT_NO_ERR) + { divert_dev_deinit(); + printk(KERN_WARNING "dss1_divert: error %d registering module, not loaded\n",i); + return(-EIO); + } + printk(KERN_INFO "dss1_divert module successfully installed\n"); + return(0); +} + +/**********************/ +/* Module deinit code */ +/**********************/ +static void __exit divert_exit(void) +{ + unsigned long flags; + int i; + + spin_lock_irqsave(&divert_lock, flags); + divert_if.cmd = DIVERT_CMD_REL; /* release */ + if ((i = DIVERT_REG_NAME(&divert_if)) != DIVERT_NO_ERR) + { printk(KERN_WARNING "dss1_divert: error %d releasing module\n",i); + spin_unlock_irqrestore(&divert_lock, flags); + return; + } + if (divert_dev_deinit()) + { printk(KERN_WARNING "dss1_divert: device busy, remove cancelled\n"); + spin_unlock_irqrestore(&divert_lock, flags); + return; + } + spin_unlock_irqrestore(&divert_lock, flags); + deleterule(-1); /* delete all rules and free mem */ + deleteprocs(); + printk(KERN_INFO "dss1_divert module successfully removed \n"); +} + +module_init(divert_init); +module_exit(divert_exit); + diff --git a/drivers/isdn/divert/divert_procfs.c b/drivers/isdn/divert/divert_procfs.c new file mode 100644 index 000000000000..e1f0d87de0eb --- /dev/null +++ b/drivers/isdn/divert/divert_procfs.c @@ -0,0 +1,319 @@ +/* $Id: divert_procfs.c,v 1.11.6.2 2001/09/23 22:24:36 kai Exp $ + * + * Filesystem handling for the diversion supplementary services. + * + * Copyright 1998 by Werner Cornelius (werner@isdn4linux.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/version.h> +#include <linux/poll.h> +#include <linux/smp_lock.h> +#ifdef CONFIG_PROC_FS +#include <linux/proc_fs.h> +#else +#include <linux/fs.h> +#endif +#include <linux/isdnif.h> +#include "isdn_divert.h" + + +/*********************************/ +/* Variables for interface queue */ +/*********************************/ +ulong if_used = 0; /* number of interface users */ +static struct divert_info *divert_info_head = NULL; /* head of queue */ +static struct divert_info *divert_info_tail = NULL; /* pointer to last entry */ +static DEFINE_SPINLOCK(divert_info_lock);/* lock for queue */ +static wait_queue_head_t rd_queue; + +/*********************************/ +/* put an info buffer into queue */ +/*********************************/ +void +put_info_buffer(char *cp) +{ + struct divert_info *ib; + unsigned long flags; + + if (if_used <= 0) + return; + if (!cp) + return; + if (!*cp) + return; + if (!(ib = (struct divert_info *) kmalloc(sizeof(struct divert_info) + strlen(cp), GFP_ATOMIC))) + return; /* no memory */ + strcpy(ib->info_start, cp); /* set output string */ + ib->next = NULL; + spin_lock_irqsave( &divert_info_lock, flags ); + ib->usage_cnt = if_used; + if (!divert_info_head) + divert_info_head = ib; /* new head */ + else + divert_info_tail->next = ib; /* follows existing messages */ + divert_info_tail = ib; /* new tail */ + + /* delete old entrys */ + while (divert_info_head->next) { + if ((divert_info_head->usage_cnt <= 0) && + (divert_info_head->next->usage_cnt <= 0)) { + ib = divert_info_head; + divert_info_head = divert_info_head->next; + kfree(ib); + } else + break; + } /* divert_info_head->next */ + spin_unlock_irqrestore( &divert_info_lock, flags ); + wake_up_interruptible(&(rd_queue)); +} /* put_info_buffer */ + +/**********************************/ +/* deflection device read routine */ +/**********************************/ +static ssize_t +isdn_divert_read(struct file *file, char __user *buf, size_t count, loff_t * off) +{ + struct divert_info *inf; + int len; + + if (!*((struct divert_info **) file->private_data)) { + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + interruptible_sleep_on(&(rd_queue)); + } + if (!(inf = *((struct divert_info **) file->private_data))) + return (0); + + inf->usage_cnt--; /* new usage count */ + file->private_data = &inf->next; /* next structure */ + if ((len = strlen(inf->info_start)) <= count) { + if (copy_to_user(buf, inf->info_start, len)) + return -EFAULT; + *off += len; + return (len); + } + return (0); +} /* isdn_divert_read */ + +/**********************************/ +/* deflection device write routine */ +/**********************************/ +static ssize_t +isdn_divert_write(struct file *file, const char __user *buf, size_t count, loff_t * off) +{ + return (-ENODEV); +} /* isdn_divert_write */ + + +/***************************************/ +/* select routines for various kernels */ +/***************************************/ +static unsigned int +isdn_divert_poll(struct file *file, poll_table * wait) +{ + unsigned int mask = 0; + + poll_wait(file, &(rd_queue), wait); + /* mask = POLLOUT | POLLWRNORM; */ + if (*((struct divert_info **) file->private_data)) { + mask |= POLLIN | POLLRDNORM; + } + return mask; +} /* isdn_divert_poll */ + +/****************/ +/* Open routine */ +/****************/ +static int +isdn_divert_open(struct inode *ino, struct file *filep) +{ + unsigned long flags; + + spin_lock_irqsave( &divert_info_lock, flags ); + if_used++; + if (divert_info_head) + filep->private_data = &(divert_info_tail->next); + else + filep->private_data = &divert_info_head; + spin_unlock_irqrestore( &divert_info_lock, flags ); + /* start_divert(); */ + return nonseekable_open(ino, filep); +} /* isdn_divert_open */ + +/*******************/ +/* close routine */ +/*******************/ +static int +isdn_divert_close(struct inode *ino, struct file *filep) +{ + struct divert_info *inf; + unsigned long flags; + + spin_lock_irqsave( &divert_info_lock, flags ); + if_used--; + inf = *((struct divert_info **) filep->private_data); + while (inf) { + inf->usage_cnt--; + inf = inf->next; + } + if (if_used <= 0) + while (divert_info_head) { + inf = divert_info_head; + divert_info_head = divert_info_head->next; + kfree(inf); + } + spin_unlock_irqrestore( &divert_info_lock, flags ); + return (0); +} /* isdn_divert_close */ + +/*********/ +/* IOCTL */ +/*********/ +static int +isdn_divert_ioctl(struct inode *inode, struct file *file, + uint cmd, ulong arg) +{ + divert_ioctl dioctl; + int i; + unsigned long flags; + divert_rule *rulep; + char *cp; + + if (copy_from_user(&dioctl, (void __user *) arg, sizeof(dioctl))) + return -EFAULT; + + switch (cmd) { + case IIOCGETVER: + dioctl.drv_version = DIVERT_IIOC_VERSION; /* set version */ + break; + + case IIOCGETDRV: + if ((dioctl.getid.drvid = divert_if.name_to_drv(dioctl.getid.drvnam)) < 0) + return (-EINVAL); + break; + + case IIOCGETNAM: + cp = divert_if.drv_to_name(dioctl.getid.drvid); + if (!cp) + return (-EINVAL); + if (!*cp) + return (-EINVAL); + strcpy(dioctl.getid.drvnam, cp); + break; + + case IIOCGETRULE: + if (!(rulep = getruleptr(dioctl.getsetrule.ruleidx))) + return (-EINVAL); + dioctl.getsetrule.rule = *rulep; /* copy data */ + break; + + case IIOCMODRULE: + if (!(rulep = getruleptr(dioctl.getsetrule.ruleidx))) + return (-EINVAL); + spin_lock_irqsave(&divert_lock, flags); + *rulep = dioctl.getsetrule.rule; /* copy data */ + spin_unlock_irqrestore(&divert_lock, flags); + return (0); /* no copy required */ + break; + + case IIOCINSRULE: + return (insertrule(dioctl.getsetrule.ruleidx, &dioctl.getsetrule.rule)); + break; + + case IIOCDELRULE: + return (deleterule(dioctl.getsetrule.ruleidx)); + break; + + case IIOCDODFACT: + return (deflect_extern_action(dioctl.fwd_ctrl.subcmd, + dioctl.fwd_ctrl.callid, + dioctl.fwd_ctrl.to_nr)); + + case IIOCDOCFACT: + case IIOCDOCFDIS: + case IIOCDOCFINT: + if (!divert_if.drv_to_name(dioctl.cf_ctrl.drvid)) + return (-EINVAL); /* invalid driver */ + if ((i = cf_command(dioctl.cf_ctrl.drvid, + (cmd == IIOCDOCFACT) ? 1 : (cmd == IIOCDOCFDIS) ? 0 : 2, + dioctl.cf_ctrl.cfproc, + dioctl.cf_ctrl.msn, + dioctl.cf_ctrl.service, + dioctl.cf_ctrl.fwd_nr, + &dioctl.cf_ctrl.procid))) + return (i); + break; + + default: + return (-EINVAL); + } /* switch cmd */ + return copy_to_user((void __user *)arg, &dioctl, sizeof(dioctl)) ? -EFAULT : 0; +} /* isdn_divert_ioctl */ + + +#ifdef CONFIG_PROC_FS +static struct file_operations isdn_fops = +{ + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = isdn_divert_read, + .write = isdn_divert_write, + .poll = isdn_divert_poll, + .ioctl = isdn_divert_ioctl, + .open = isdn_divert_open, + .release = isdn_divert_close, +}; + +/****************************/ +/* isdn subdir in /proc/net */ +/****************************/ +static struct proc_dir_entry *isdn_proc_entry = NULL; +static struct proc_dir_entry *isdn_divert_entry = NULL; +#endif /* CONFIG_PROC_FS */ + +/***************************************************************************/ +/* divert_dev_init must be called before the proc filesystem may be used */ +/***************************************************************************/ +int +divert_dev_init(void) +{ + + init_waitqueue_head(&rd_queue); + +#ifdef CONFIG_PROC_FS + isdn_proc_entry = create_proc_entry("isdn", S_IFDIR | S_IRUGO | S_IXUGO, proc_net); + if (!isdn_proc_entry) + return (-1); + isdn_divert_entry = create_proc_entry("divert", S_IFREG | S_IRUGO, isdn_proc_entry); + if (!isdn_divert_entry) { + remove_proc_entry("isdn", proc_net); + return (-1); + } + isdn_divert_entry->proc_fops = &isdn_fops; + isdn_divert_entry->owner = THIS_MODULE; +#endif /* CONFIG_PROC_FS */ + + return (0); +} /* divert_dev_init */ + +/***************************************************************************/ +/* divert_dev_deinit must be called before leaving isdn when included as */ +/* a module. */ +/***************************************************************************/ +int +divert_dev_deinit(void) +{ + +#ifdef CONFIG_PROC_FS + remove_proc_entry("divert", isdn_proc_entry); + remove_proc_entry("isdn", proc_net); +#endif /* CONFIG_PROC_FS */ + + return (0); +} /* divert_dev_deinit */ diff --git a/drivers/isdn/divert/isdn_divert.c b/drivers/isdn/divert/isdn_divert.c new file mode 100644 index 000000000000..1eb112213f0c --- /dev/null +++ b/drivers/isdn/divert/isdn_divert.c @@ -0,0 +1,861 @@ +/* $Id: isdn_divert.c,v 1.6.6.3 2001/09/23 22:24:36 kai Exp $ + * + * DSS1 main diversion supplementary handling for i4l. + * + * Copyright 1999 by Werner Cornelius (werner@isdn4linux.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/version.h> +#include <linux/proc_fs.h> + +#include "isdn_divert.h" + +/**********************************/ +/* structure keeping calling info */ +/**********************************/ +struct call_struc + { isdn_ctrl ics; /* delivered setup + driver parameters */ + ulong divert_id; /* Id delivered to user */ + unsigned char akt_state; /* actual state */ + char deflect_dest[35]; /* deflection destination */ + struct timer_list timer; /* timer control structure */ + char info[90]; /* device info output */ + struct call_struc *next; /* pointer to next entry */ + struct call_struc *prev; + }; + + +/********************************************/ +/* structure keeping deflection table entry */ +/********************************************/ +struct deflect_struc + { struct deflect_struc *next,*prev; + divert_rule rule; /* used rule */ + }; + + +/*****************************************/ +/* variables for main diversion services */ +/*****************************************/ +/* diversion/deflection processes */ +static struct call_struc *divert_head = NULL; /* head of remembered entrys */ +static ulong next_id = 1; /* next info id */ +static struct deflect_struc *table_head = NULL; +static struct deflect_struc *table_tail = NULL; +static unsigned char extern_wait_max = 4; /* maximum wait in s for external process */ + +DEFINE_SPINLOCK(divert_lock); + +/***************************/ +/* timer callback function */ +/***************************/ +static void deflect_timer_expire(ulong arg) +{ + unsigned long flags; + struct call_struc *cs = (struct call_struc *) arg; + + spin_lock_irqsave(&divert_lock, flags); + del_timer(&cs->timer); /* delete active timer */ + spin_unlock_irqrestore(&divert_lock, flags); + + switch(cs->akt_state) + { case DEFLECT_PROCEED: + cs->ics.command = ISDN_CMD_HANGUP; /* cancel action */ + divert_if.ll_cmd(&cs->ics); + spin_lock_irqsave(&divert_lock, flags); + cs->akt_state = DEFLECT_AUTODEL; /* delete after timeout */ + cs->timer.expires = jiffies + (HZ * AUTODEL_TIME); + add_timer(&cs->timer); + spin_unlock_irqrestore(&divert_lock, flags); + break; + + case DEFLECT_ALERT: + cs->ics.command = ISDN_CMD_REDIR; /* protocol */ + strcpy(cs->ics.parm.setup.phone,cs->deflect_dest); + strcpy(cs->ics.parm.setup.eazmsn,"Testtext delayed"); + divert_if.ll_cmd(&cs->ics); + spin_lock_irqsave(&divert_lock, flags); + cs->akt_state = DEFLECT_AUTODEL; /* delete after timeout */ + cs->timer.expires = jiffies + (HZ * AUTODEL_TIME); + add_timer(&cs->timer); + spin_unlock_irqrestore(&divert_lock, flags); + break; + + case DEFLECT_AUTODEL: + default: + spin_lock_irqsave(&divert_lock, flags); + if (cs->prev) + cs->prev->next = cs->next; /* forward link */ + else + divert_head = cs->next; + if (cs->next) + cs->next->prev = cs->prev; /* back link */ + spin_unlock_irqrestore(&divert_lock, flags); + kfree(cs); + return; + + } /* switch */ +} /* deflect_timer_func */ + + +/*****************************************/ +/* handle call forwarding de/activations */ +/* 0 = deact, 1 = act, 2 = interrogate */ +/*****************************************/ +int cf_command(int drvid, int mode, + u_char proc, char *msn, + u_char service, char *fwd_nr, ulong *procid) +{ unsigned long flags; + int retval,msnlen; + int fwd_len; + char *p,*ielenp,tmp[60]; + struct call_struc *cs; + + if (strchr(msn,'.')) return(-EINVAL); /* subaddress not allowed in msn */ + if ((proc & 0x7F) > 2) return(-EINVAL); + proc &= 3; + p = tmp; + *p++ = 0x30; /* enumeration */ + ielenp = p++; /* remember total length position */ + *p++ = 0xa; /* proc tag */ + *p++ = 1; /* length */ + *p++ = proc & 0x7F; /* procedure to de/activate/interrogate */ + *p++ = 0xa; /* service tag */ + *p++ = 1; /* length */ + *p++ = service; /* service to handle */ + + if (mode == 1) + { if (!*fwd_nr) return(-EINVAL); /* destination missing */ + if (strchr(fwd_nr,'.')) return(-EINVAL); /* subaddress not allowed */ + fwd_len = strlen(fwd_nr); + *p++ = 0x30; /* number enumeration */ + *p++ = fwd_len + 2; /* complete forward to len */ + *p++ = 0x80; /* fwd to nr */ + *p++ = fwd_len; /* length of number */ + strcpy(p,fwd_nr); /* copy number */ + p += fwd_len; /* pointer beyond fwd */ + } /* activate */ + + msnlen = strlen(msn); + *p++ = 0x80; /* msn number */ + if (msnlen > 1) + { *p++ = msnlen; /* length */ + strcpy(p,msn); + p += msnlen; + } + else *p++ = 0; + + *ielenp = p - ielenp - 1; /* set total IE length */ + + /* allocate mem for information struct */ + if (!(cs = (struct call_struc *) kmalloc(sizeof(struct call_struc), GFP_ATOMIC))) + return(-ENOMEM); /* no memory */ + init_timer(&cs->timer); + cs->info[0] = '\0'; + cs->timer.function = deflect_timer_expire; + cs->timer.data = (ulong) cs; /* pointer to own structure */ + cs->ics.driver = drvid; + cs->ics.command = ISDN_CMD_PROT_IO; /* protocol specific io */ + cs->ics.arg = DSS1_CMD_INVOKE; /* invoke supplementary service */ + cs->ics.parm.dss1_io.proc = (mode == 1) ? 7: (mode == 2) ? 11:8; /* operation */ + cs->ics.parm.dss1_io.timeout = 4000; /* from ETS 300 207-1 */ + cs->ics.parm.dss1_io.datalen = p - tmp; /* total len */ + cs->ics.parm.dss1_io.data = tmp; /* start of buffer */ + + spin_lock_irqsave(&divert_lock, flags); + cs->ics.parm.dss1_io.ll_id = next_id++; /* id for callback */ + spin_unlock_irqrestore(&divert_lock, flags); + *procid = cs->ics.parm.dss1_io.ll_id; + + sprintf(cs->info,"%d 0x%lx %s%s 0 %s %02x %d%s%s\n", + (!mode ) ? DIVERT_DEACTIVATE : (mode == 1) ? DIVERT_ACTIVATE : DIVERT_REPORT, + cs->ics.parm.dss1_io.ll_id, + (mode != 2) ? "" : "0 ", + divert_if.drv_to_name(cs->ics.driver), + msn, + service & 0xFF, + proc, + (mode != 1) ? "" : " 0 ", + (mode != 1) ? "" : fwd_nr); + + retval = divert_if.ll_cmd(&cs->ics); /* excute command */ + + if (!retval) + { cs->prev = NULL; + spin_lock_irqsave(&divert_lock, flags); + cs->next = divert_head; + divert_head = cs; + spin_unlock_irqrestore(&divert_lock, flags); + } + else + kfree(cs); + return(retval); +} /* cf_command */ + + +/****************************************/ +/* handle a external deflection command */ +/****************************************/ +int deflect_extern_action(u_char cmd, ulong callid, char *to_nr) +{ struct call_struc *cs; + isdn_ctrl ic; + unsigned long flags; + int i; + + if ((cmd & 0x7F) > 2) return(-EINVAL); /* invalid command */ + cs = divert_head; /* start of parameter list */ + while (cs) + { if (cs->divert_id == callid) break; /* found */ + cs = cs->next; + } /* search entry */ + if (!cs) return(-EINVAL); /* invalid callid */ + + ic.driver = cs->ics.driver; + ic.arg = cs->ics.arg; + i = -EINVAL; + if (cs->akt_state == DEFLECT_AUTODEL) return(i); /* no valid call */ + switch (cmd & 0x7F) + { case 0: /* hangup */ + del_timer(&cs->timer); + ic.command = ISDN_CMD_HANGUP; + i = divert_if.ll_cmd(&ic); + spin_lock_irqsave(&divert_lock, flags); + cs->akt_state = DEFLECT_AUTODEL; /* delete after timeout */ + cs->timer.expires = jiffies + (HZ * AUTODEL_TIME); + add_timer(&cs->timer); + spin_unlock_irqrestore(&divert_lock, flags); + break; + + case 1: /* alert */ + if (cs->akt_state == DEFLECT_ALERT) return(0); + cmd &= 0x7F; /* never wait */ + del_timer(&cs->timer); + ic.command = ISDN_CMD_ALERT; + if ((i = divert_if.ll_cmd(&ic))) + { + spin_lock_irqsave(&divert_lock, flags); + cs->akt_state = DEFLECT_AUTODEL; /* delete after timeout */ + cs->timer.expires = jiffies + (HZ * AUTODEL_TIME); + add_timer(&cs->timer); + spin_unlock_irqrestore(&divert_lock, flags); + } + else + cs->akt_state = DEFLECT_ALERT; + break; + + case 2: /* redir */ + del_timer(&cs->timer); + strcpy(cs->ics.parm.setup.phone, to_nr); + strcpy(cs->ics.parm.setup.eazmsn, "Testtext manual"); + ic.command = ISDN_CMD_REDIR; + if ((i = divert_if.ll_cmd(&ic))) + { + spin_lock_irqsave(&divert_lock, flags); + cs->akt_state = DEFLECT_AUTODEL; /* delete after timeout */ + cs->timer.expires = jiffies + (HZ * AUTODEL_TIME); + add_timer(&cs->timer); + spin_unlock_irqrestore(&divert_lock, flags); + } + else + cs->akt_state = DEFLECT_ALERT; + break; + + } /* switch */ + return(i); +} /* deflect_extern_action */ + +/********************************/ +/* insert a new rule before idx */ +/********************************/ +int insertrule(int idx, divert_rule *newrule) +{ struct deflect_struc *ds,*ds1=NULL; + unsigned long flags; + + if (!(ds = (struct deflect_struc *) kmalloc(sizeof(struct deflect_struc), + GFP_KERNEL))) + return(-ENOMEM); /* no memory */ + + ds->rule = *newrule; /* set rule */ + + spin_lock_irqsave(&divert_lock, flags); + + if (idx >= 0) + { ds1 = table_head; + while ((ds1) && (idx > 0)) + { idx--; + ds1 = ds1->next; + } + if (!ds1) idx = -1; + } + + if (idx < 0) + { ds->prev = table_tail; /* previous entry */ + ds->next = NULL; /* end of chain */ + if (ds->prev) + ds->prev->next = ds; /* last forward */ + else + table_head = ds; /* is first entry */ + table_tail = ds; /* end of queue */ + } + else + { ds->next = ds1; /* next entry */ + ds->prev = ds1->prev; /* prev entry */ + ds1->prev = ds; /* backward chain old element */ + if (!ds->prev) + table_head = ds; /* first element */ + } + + spin_unlock_irqrestore(&divert_lock, flags); + return(0); +} /* insertrule */ + +/***********************************/ +/* delete the rule at position idx */ +/***********************************/ +int deleterule(int idx) +{ struct deflect_struc *ds,*ds1; + unsigned long flags; + + if (idx < 0) + { spin_lock_irqsave(&divert_lock, flags); + ds = table_head; + table_head = NULL; + table_tail = NULL; + spin_unlock_irqrestore(&divert_lock, flags); + while (ds) + { ds1 = ds; + ds = ds->next; + kfree(ds1); + } + return(0); + } + + spin_lock_irqsave(&divert_lock, flags); + ds = table_head; + + while ((ds) && (idx > 0)) + { idx--; + ds = ds->next; + } + + if (!ds) + { + spin_unlock_irqrestore(&divert_lock, flags); + return(-EINVAL); + } + + if (ds->next) + ds->next->prev = ds->prev; /* backward chain */ + else + table_tail = ds->prev; /* end of chain */ + + if (ds->prev) + ds->prev->next = ds->next; /* forward chain */ + else + table_head = ds->next; /* start of chain */ + + spin_unlock_irqrestore(&divert_lock, flags); + kfree(ds); + return(0); +} /* deleterule */ + +/*******************************************/ +/* get a pointer to a specific rule number */ +/*******************************************/ +divert_rule *getruleptr(int idx) +{ struct deflect_struc *ds = table_head; + + if (idx < 0) return(NULL); + while ((ds) && (idx >= 0)) + { if (!(idx--)) + { return(&ds->rule); + break; + } + ds = ds->next; + } + return(NULL); +} /* getruleptr */ + +/*************************************************/ +/* called from common module on an incoming call */ +/*************************************************/ +int isdn_divert_icall(isdn_ctrl *ic) +{ int retval = 0; + unsigned long flags; + struct call_struc *cs = NULL; + struct deflect_struc *dv; + char *p,*p1; + u_char accept; + + /* first check the internal deflection table */ + for (dv = table_head; dv ; dv = dv->next ) + { /* scan table */ + if (((dv->rule.callopt == 1) && (ic->command == ISDN_STAT_ICALLW)) || + ((dv->rule.callopt == 2) && (ic->command == ISDN_STAT_ICALL))) + continue; /* call option check */ + if (!(dv->rule.drvid & (1L << ic->driver))) + continue; /* driver not matching */ + if ((dv->rule.si1) && (dv->rule.si1 != ic->parm.setup.si1)) + continue; /* si1 not matching */ + if ((dv->rule.si2) && (dv->rule.si2 != ic->parm.setup.si2)) + continue; /* si2 not matching */ + + p = dv->rule.my_msn; + p1 = ic->parm.setup.eazmsn; + accept = 0; + while (*p) + { /* complete compare */ + if (*p == '-') + { accept = 1; /* call accepted */ + break; + } + if (*p++ != *p1++) + break; /* not accepted */ + if ((!*p) && (!*p1)) + accept = 1; + } /* complete compare */ + if (!accept) continue; /* not accepted */ + + if ((strcmp(dv->rule.caller,"0")) || (ic->parm.setup.phone[0])) + { p = dv->rule.caller; + p1 = ic->parm.setup.phone; + accept = 0; + while (*p) + { /* complete compare */ + if (*p == '-') + { accept = 1; /* call accepted */ + break; + } + if (*p++ != *p1++) + break; /* not accepted */ + if ((!*p) && (!*p1)) + accept = 1; + } /* complete compare */ + if (!accept) continue; /* not accepted */ + } + + switch (dv->rule.action) + { case DEFLECT_IGNORE: + return(0); + break; + + case DEFLECT_ALERT: + case DEFLECT_PROCEED: + case DEFLECT_REPORT: + case DEFLECT_REJECT: + if (dv->rule.action == DEFLECT_PROCEED) + if ((!if_used) || ((!extern_wait_max) && (!dv->rule.waittime))) + return(0); /* no external deflection needed */ + if (!(cs = (struct call_struc *) kmalloc(sizeof(struct call_struc), GFP_ATOMIC))) + return(0); /* no memory */ + init_timer(&cs->timer); + cs->info[0] = '\0'; + cs->timer.function = deflect_timer_expire; + cs->timer.data = (ulong) cs; /* pointer to own structure */ + + cs->ics = *ic; /* copy incoming data */ + if (!cs->ics.parm.setup.phone[0]) strcpy(cs->ics.parm.setup.phone,"0"); + if (!cs->ics.parm.setup.eazmsn[0]) strcpy(cs->ics.parm.setup.eazmsn,"0"); + cs->ics.parm.setup.screen = dv->rule.screen; + if (dv->rule.waittime) + cs->timer.expires = jiffies + (HZ * dv->rule.waittime); + else + if (dv->rule.action == DEFLECT_PROCEED) + cs->timer.expires = jiffies + (HZ * extern_wait_max); + else + cs->timer.expires = 0; + cs->akt_state = dv->rule.action; + spin_lock_irqsave(&divert_lock, flags); + cs->divert_id = next_id++; /* new sequence number */ + spin_unlock_irqrestore(&divert_lock, flags); + cs->prev = NULL; + if (cs->akt_state == DEFLECT_ALERT) + { strcpy(cs->deflect_dest,dv->rule.to_nr); + if (!cs->timer.expires) + { strcpy(ic->parm.setup.eazmsn,"Testtext direct"); + ic->parm.setup.screen = dv->rule.screen; + strcpy(ic->parm.setup.phone,dv->rule.to_nr); + cs->akt_state = DEFLECT_AUTODEL; /* delete after timeout */ + cs->timer.expires = jiffies + (HZ * AUTODEL_TIME); + retval = 5; + } + else + retval = 1; /* alerting */ + } + else + { cs->deflect_dest[0] = '\0'; + retval = 4; /* only proceed */ + } + sprintf(cs->info,"%d 0x%lx %s %s %s %s 0x%x 0x%x %d %d %s\n", + cs->akt_state, + cs->divert_id, + divert_if.drv_to_name(cs->ics.driver), + (ic->command == ISDN_STAT_ICALLW) ? "1":"0", + cs->ics.parm.setup.phone, + cs->ics.parm.setup.eazmsn, + cs->ics.parm.setup.si1, + cs->ics.parm.setup.si2, + cs->ics.parm.setup.screen, + dv->rule.waittime, + cs->deflect_dest); + if ((dv->rule.action == DEFLECT_REPORT) || + (dv->rule.action == DEFLECT_REJECT)) + { put_info_buffer(cs->info); + kfree(cs); /* remove */ + return((dv->rule.action == DEFLECT_REPORT) ? 0:2); /* nothing to do */ + } + break; + + default: + return(0); /* ignore call */ + break; + } /* switch action */ + break; + } /* scan_table */ + + if (cs) + { cs->prev = NULL; + spin_lock_irqsave(&divert_lock, flags); + cs->next = divert_head; + divert_head = cs; + if (cs->timer.expires) add_timer(&cs->timer); + spin_unlock_irqrestore(&divert_lock, flags); + + put_info_buffer(cs->info); + return(retval); + } + else + return(0); +} /* isdn_divert_icall */ + + +void deleteprocs(void) +{ struct call_struc *cs, *cs1; + unsigned long flags; + + spin_lock_irqsave(&divert_lock, flags); + cs = divert_head; + divert_head = NULL; + while (cs) + { del_timer(&cs->timer); + cs1 = cs; + cs = cs->next; + kfree(cs1); + } + spin_unlock_irqrestore(&divert_lock, flags); +} /* deleteprocs */ + +/****************************************************/ +/* put a address including address type into buffer */ +/****************************************************/ +int put_address(char *st, u_char *p, int len) +{ u_char retval = 0; + u_char adr_typ = 0; /* network standard */ + + if (len < 2) return(retval); + if (*p == 0xA1) + { retval = *(++p) + 2; /* total length */ + if (retval > len) return(0); /* too short */ + len = retval - 2; /* remaining length */ + if (len < 3) return(0); + if ((*(++p) != 0x0A) || (*(++p) != 1)) return(0); + adr_typ = *(++p); + len -= 3; + p++; + if (len < 2) return(0); + if (*p++ != 0x12) return(0); + if (*p > len) return(0); /* check number length */ + len = *p++; + } + else + if (*p == 0x80) + { retval = *(++p) + 2; /* total length */ + if (retval > len) return(0); + len = retval - 2; + p++; + } + else + return(0); /* invalid address information */ + + sprintf(st,"%d ",adr_typ); + st += strlen(st); + if (!len) + *st++ = '-'; + else + while (len--) + *st++ = *p++; + *st = '\0'; + return(retval); +} /* put_address */ + +/*************************************/ +/* report a succesfull interrogation */ +/*************************************/ +int interrogate_success(isdn_ctrl *ic, struct call_struc *cs) +{ char *src = ic->parm.dss1_io.data; + int restlen = ic->parm.dss1_io.datalen; + int cnt = 1; + u_char n,n1; + char st[90], *p, *stp; + + if (restlen < 2) return(-100); /* frame too short */ + if (*src++ != 0x30) return(-101); + if ((n = *src++) > 0x81) return(-102); /* invalid length field */ + restlen -= 2; /* remaining bytes */ + if (n == 0x80) + { if (restlen < 2) return(-103); + if ((*(src+restlen-1)) || (*(src+restlen-2))) return(-104); + restlen -= 2; + } + else + if ( n == 0x81) + { n = *src++; + restlen--; + if (n > restlen) return(-105); + restlen = n; + } + else + if (n > restlen) return(-106); + else + restlen = n; /* standard format */ + if (restlen < 3) return(-107); /* no procedure */ + if ((*src++ != 2) || (*src++ != 1) || (*src++ != 0x0B)) return(-108); + restlen -= 3; + if (restlen < 2) return(-109); /* list missing */ + if (*src == 0x31) + { src++; + if ((n = *src++) > 0x81) return(-110); /* invalid length field */ + restlen -= 2; /* remaining bytes */ + if (n == 0x80) + { if (restlen < 2) return(-111); + if ((*(src+restlen-1)) || (*(src+restlen-2))) return(-112); + restlen -= 2; + } + else + if ( n == 0x81) + { n = *src++; + restlen--; + if (n > restlen) return(-113); + restlen = n; + } + else + if (n > restlen) return(-114); + else + restlen = n; /* standard format */ + } /* result list header */ + + while (restlen >= 2) + { stp = st; + sprintf(stp,"%d 0x%lx %d %s ",DIVERT_REPORT, ic->parm.dss1_io.ll_id, + cnt++,divert_if.drv_to_name(ic->driver)); + stp += strlen(stp); + if (*src++ != 0x30) return(-115); /* invalid enum */ + n = *src++; + restlen -= 2; + if (n > restlen) return(-116); /* enum length wrong */ + restlen -= n; + p = src; /* one entry */ + src += n; + if (!(n1 = put_address(stp,p,n & 0xFF))) continue; + stp += strlen(stp); + p += n1; + n -= n1; + if (n < 6) continue; /* no service and proc */ + if ((*p++ != 0x0A) || (*p++ != 1)) continue; + sprintf(stp," 0x%02x ",(*p++) & 0xFF); + stp += strlen(stp); + if ((*p++ != 0x0A) || (*p++ != 1)) continue; + sprintf(stp,"%d ",(*p++) & 0xFF); + stp += strlen(stp); + n -= 6; + if (n > 2) + { if (*p++ != 0x30) continue; + if (*p > (n-2)) continue; + n = *p++; + if (!(n1 = put_address(stp,p,n & 0xFF))) continue; + stp += strlen(stp); + } + sprintf(stp,"\n"); + put_info_buffer(st); + } /* while restlen */ + if (restlen) return(-117); + return(0); +} /* interrogate_success */ + +/*********************************************/ +/* callback for protocol specific extensions */ +/*********************************************/ +int prot_stat_callback(isdn_ctrl *ic) +{ struct call_struc *cs, *cs1; + int i; + unsigned long flags; + + cs = divert_head; /* start of list */ + cs1 = NULL; + while (cs) + { if (ic->driver == cs->ics.driver) + { switch (cs->ics.arg) + { case DSS1_CMD_INVOKE: + if ((cs->ics.parm.dss1_io.ll_id == ic->parm.dss1_io.ll_id) && + (cs->ics.parm.dss1_io.hl_id == ic->parm.dss1_io.hl_id)) + { switch (ic->arg) + { case DSS1_STAT_INVOKE_ERR: + sprintf(cs->info,"128 0x%lx 0x%x\n", + ic->parm.dss1_io.ll_id, + ic->parm.dss1_io.timeout); + put_info_buffer(cs->info); + break; + + case DSS1_STAT_INVOKE_RES: + switch (cs->ics.parm.dss1_io.proc) + { case 7: + case 8: + put_info_buffer(cs->info); + break; + + case 11: + i = interrogate_success(ic,cs); + if (i) + sprintf(cs->info,"%d 0x%lx %d\n",DIVERT_REPORT, + ic->parm.dss1_io.ll_id,i); + put_info_buffer(cs->info); + break; + + default: + printk(KERN_WARNING "dss1_divert: unknown proc %d\n",cs->ics.parm.dss1_io.proc); + break; + } + + + break; + + default: + printk(KERN_WARNING "dss1_divert unknown invoke answer %lx\n",ic->arg); + break; + } + cs1 = cs; /* remember structure */ + cs = NULL; + continue; /* abort search */ + } /* id found */ + break; + + case DSS1_CMD_INVOKE_ABORT: + printk(KERN_WARNING "dss1_divert unhandled invoke abort\n"); + break; + + default: + printk(KERN_WARNING "dss1_divert unknown cmd 0x%lx\n",cs->ics.arg); + break; + } /* switch ics.arg */ + cs = cs->next; + } /* driver ok */ + } + + if (!cs1) + { printk(KERN_WARNING "dss1_divert unhandled process\n"); + return(0); + } + + if (cs1->ics.driver == -1) + { + spin_lock_irqsave(&divert_lock, flags); + del_timer(&cs1->timer); + if (cs1->prev) + cs1->prev->next = cs1->next; /* forward link */ + else + divert_head = cs1->next; + if (cs1->next) + cs1->next->prev = cs1->prev; /* back link */ + spin_unlock_irqrestore(&divert_lock, flags); + kfree(cs1); + } + + return(0); +} /* prot_stat_callback */ + + +/***************************/ +/* status callback from HL */ +/***************************/ +int isdn_divert_stat_callback(isdn_ctrl *ic) +{ struct call_struc *cs, *cs1; + unsigned long flags; + int retval; + + retval = -1; + cs = divert_head; /* start of list */ + while (cs) + { if ((ic->driver == cs->ics.driver) && (ic->arg == cs->ics.arg)) + { switch (ic->command) + { case ISDN_STAT_DHUP: + sprintf(cs->info,"129 0x%lx\n",cs->divert_id); + del_timer(&cs->timer); + cs->ics.driver = -1; + break; + + case ISDN_STAT_CAUSE: + sprintf(cs->info,"130 0x%lx %s\n",cs->divert_id,ic->parm.num); + break; + + case ISDN_STAT_REDIR: + sprintf(cs->info,"131 0x%lx\n",cs->divert_id); + del_timer(&cs->timer); + cs->ics.driver = -1; + break; + + default: + sprintf(cs->info,"999 0x%lx 0x%x\n",cs->divert_id,(int)(ic->command)); + break; + } + put_info_buffer(cs->info); + retval = 0; + } + cs1 = cs; + cs = cs->next; + if (cs1->ics.driver == -1) + { + spin_lock_irqsave(&divert_lock, flags); + if (cs1->prev) + cs1->prev->next = cs1->next; /* forward link */ + else + divert_head = cs1->next; + if (cs1->next) + cs1->next->prev = cs1->prev; /* back link */ + spin_unlock_irqrestore(&divert_lock, flags); + kfree(cs1); + } + } + return(retval); /* not found */ +} /* isdn_divert_stat_callback */ + + +/********************/ +/* callback from ll */ +/********************/ +int ll_callback(isdn_ctrl *ic) +{ + switch (ic->command) + { case ISDN_STAT_ICALL: + case ISDN_STAT_ICALLW: + return(isdn_divert_icall(ic)); + break; + + case ISDN_STAT_PROT: + if ((ic->arg & 0xFF) == ISDN_PTYPE_EURO) + { if (ic->arg != DSS1_STAT_INVOKE_BRD) + return(prot_stat_callback(ic)); + else + return(0); /* DSS1 invoke broadcast */ + } + else + return(-1); /* protocol not euro */ + + default: + return(isdn_divert_stat_callback(ic)); + } +} /* ll_callback */ + diff --git a/drivers/isdn/divert/isdn_divert.h b/drivers/isdn/divert/isdn_divert.h new file mode 100644 index 000000000000..19439a6176a9 --- /dev/null +++ b/drivers/isdn/divert/isdn_divert.h @@ -0,0 +1,132 @@ +/* $Id: isdn_divert.h,v 1.5.6.1 2001/09/23 22:24:36 kai Exp $ + * + * Header for the diversion supplementary ioctl interface. + * + * Copyright 1998 by Werner Cornelius (werner@ikt.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/ioctl.h> +#include <linux/types.h> + +/******************************************/ +/* IOCTL codes for interface to user prog */ +/******************************************/ +#define DIVERT_IIOC_VERSION 0x01 /* actual version */ +#define IIOCGETVER _IO('I', 1) /* get version of interface */ +#define IIOCGETDRV _IO('I', 2) /* get driver number */ +#define IIOCGETNAM _IO('I', 3) /* get driver name */ +#define IIOCGETRULE _IO('I', 4) /* read one rule */ +#define IIOCMODRULE _IO('I', 5) /* modify/replace a rule */ +#define IIOCINSRULE _IO('I', 6) /* insert/append one rule */ +#define IIOCDELRULE _IO('I', 7) /* delete a rule */ +#define IIOCDODFACT _IO('I', 8) /* hangup/reject/alert/immediately deflect a call */ +#define IIOCDOCFACT _IO('I', 9) /* activate control forwarding in PBX */ +#define IIOCDOCFDIS _IO('I',10) /* deactivate control forwarding in PBX */ +#define IIOCDOCFINT _IO('I',11) /* interrogate control forwarding in PBX */ + +/*************************************/ +/* states reported through interface */ +/*************************************/ +#define DEFLECT_IGNORE 0 /* ignore incoming call */ +#define DEFLECT_REPORT 1 /* only report */ +#define DEFLECT_PROCEED 2 /* deflect when externally triggered */ +#define DEFLECT_ALERT 3 /* alert and deflect after delay */ +#define DEFLECT_REJECT 4 /* reject immediately */ +#define DIVERT_ACTIVATE 5 /* diversion activate */ +#define DIVERT_DEACTIVATE 6 /* diversion deactivate */ +#define DIVERT_REPORT 7 /* interrogation result */ +#define DEFLECT_AUTODEL 255 /* only for internal use */ + +#define DEFLECT_ALL_IDS 0xFFFFFFFF /* all drivers selected */ + +typedef struct + { ulong drvid; /* driver ids, bit mapped */ + char my_msn[35]; /* desired msn, subaddr allowed */ + char caller[35]; /* caller id, partial string with * + subaddr allowed */ + char to_nr[35]; /* deflected to number incl. subaddress */ + u_char si1,si2; /* service indicators, si1=bitmask, si1+2 0 = all */ + u_char screen; /* screening: 0 = no info, 1 = info, 2 = nfo with nr */ + u_char callopt; /* option for call handling: + 0 = all calls + 1 = only non waiting calls + 2 = only waiting calls */ + u_char action; /* desired action: + 0 = don't report call -> ignore + 1 = report call, do not allow/proceed for deflection + 2 = report call, send proceed, wait max waittime secs + 3 = report call, alert and deflect after waittime + 4 = report call, reject immediately + actions 1-2 only take place if interface is opened + */ + u_char waittime; /* maximum wait time for proceeding */ + } divert_rule; + +typedef union + { int drv_version; /* return of driver version */ + struct + { int drvid; /* id of driver */ + char drvnam[30]; /* name of driver */ + } getid; + struct + { int ruleidx; /* index of rule */ + divert_rule rule; /* rule parms */ + } getsetrule; + struct + { u_char subcmd; /* 0 = hangup/reject, + 1 = alert, + 2 = deflect */ + ulong callid; /* id of call delivered by ascii output */ + char to_nr[35]; /* destination when deflect, + else uus1 string (maxlen 31), + data from rule used if empty */ + } fwd_ctrl; + struct + { int drvid; /* id of driver */ + u_char cfproc; /* cfu = 0, cfb = 1, cfnr = 2 */ + ulong procid; /* process id returned when no error */ + u_char service; /* basically coded service, 0 = all */ + char msn[25]; /* desired msn, empty = all */ + char fwd_nr[35];/* forwarded to number + subaddress */ + } cf_ctrl; + } divert_ioctl; + +#ifdef __KERNEL__ + +#include <linux/isdnif.h> +#include <linux/isdn_divertif.h> + +#define AUTODEL_TIME 30 /* timeout in s to delete internal entries */ + +/**************************************************/ +/* structure keeping ascii info for device output */ +/**************************************************/ +struct divert_info + { struct divert_info *next; + ulong usage_cnt; /* number of files still to work */ + char info_start[2]; /* info string start */ + }; + + +/**************/ +/* Prototypes */ +/**************/ +extern spinlock_t divert_lock; + +extern ulong if_used; /* number of interface users */ +extern int divert_dev_deinit(void); +extern int divert_dev_init(void); +extern void put_info_buffer(char *); +extern int ll_callback(isdn_ctrl *); +extern isdn_divert_if divert_if; +extern divert_rule *getruleptr(int); +extern int insertrule(int, divert_rule *); +extern int deleterule(int); +extern void deleteprocs(void); +extern int deflect_extern_action(u_char, ulong, char *); +extern int cf_command(int, int, u_char, char *, u_char, char *, ulong *); + +#endif /* __KERNEL__ */ diff --git a/drivers/isdn/hardware/Kconfig b/drivers/isdn/hardware/Kconfig new file mode 100644 index 000000000000..139f19797713 --- /dev/null +++ b/drivers/isdn/hardware/Kconfig @@ -0,0 +1,10 @@ +# +# ISDN hardware drivers +# +comment "CAPI hardware drivers" + depends on NET && ISDN && ISDN_CAPI + +source "drivers/isdn/hardware/avm/Kconfig" + +source "drivers/isdn/hardware/eicon/Kconfig" + diff --git a/drivers/isdn/hardware/Makefile b/drivers/isdn/hardware/Makefile new file mode 100644 index 000000000000..11c8a183948c --- /dev/null +++ b/drivers/isdn/hardware/Makefile @@ -0,0 +1,6 @@ +# Makefile for the CAPI hardware drivers + +# Object files in subdirectories + +obj-$(CONFIG_CAPI_AVM) += avm/ +obj-$(CONFIG_CAPI_EICON) += eicon/ diff --git a/drivers/isdn/hardware/avm/Kconfig b/drivers/isdn/hardware/avm/Kconfig new file mode 100644 index 000000000000..29a32a8830c0 --- /dev/null +++ b/drivers/isdn/hardware/avm/Kconfig @@ -0,0 +1,66 @@ +# +# ISDN AVM drivers +# + +menu "Active AVM cards" + depends on NET && ISDN && ISDN_CAPI!=n + +config CAPI_AVM + bool "Support AVM cards" + help + Enable support for AVM active ISDN cards. + +config ISDN_DRV_AVMB1_B1ISA + tristate "AVM B1 ISA support" + depends on CAPI_AVM && ISDN_CAPI && ISA + help + Enable support for the ISA version of the AVM B1 card. + +config ISDN_DRV_AVMB1_B1PCI + tristate "AVM B1 PCI support" + depends on CAPI_AVM && ISDN_CAPI && PCI + help + Enable support for the PCI version of the AVM B1 card. + +config ISDN_DRV_AVMB1_B1PCIV4 + bool "AVM B1 PCI V4 support" + depends on ISDN_DRV_AVMB1_B1PCI + help + Enable support for the V4 version of AVM B1 PCI card. + +config ISDN_DRV_AVMB1_T1ISA + tristate "AVM T1/T1-B ISA support" + depends on CAPI_AVM && ISDN_CAPI && ISA + help + Enable support for the AVM T1 T1B card. + Note: This is a PRI card and handle 30 B-channels. + +config ISDN_DRV_AVMB1_B1PCMCIA + tristate "AVM B1/M1/M2 PCMCIA support" + depends on CAPI_AVM && ISDN_CAPI + help + Enable support for the PCMCIA version of the AVM B1 card. + +config ISDN_DRV_AVMB1_AVM_CS + tristate "AVM B1/M1/M2 PCMCIA cs module" + depends on ISDN_DRV_AVMB1_B1PCMCIA && PCMCIA + help + Enable the PCMCIA client driver for the AVM B1/M1/M2 + PCMCIA cards. + +config ISDN_DRV_AVMB1_T1PCI + tristate "AVM T1/T1-B PCI support" + depends on CAPI_AVM && ISDN_CAPI && PCI + help + Enable support for the AVM T1 T1B card. + Note: This is a PRI card and handle 30 B-channels. + +config ISDN_DRV_AVMB1_C4 + tristate "AVM C4/C2 support" + depends on CAPI_AVM && ISDN_CAPI && PCI + help + Enable support for the AVM C4/C2 PCI cards. + These cards handle 4/2 BRI ISDN lines (8/4 channels). + +endmenu + diff --git a/drivers/isdn/hardware/avm/Makefile b/drivers/isdn/hardware/avm/Makefile new file mode 100644 index 000000000000..b540e8f2efb6 --- /dev/null +++ b/drivers/isdn/hardware/avm/Makefile @@ -0,0 +1,11 @@ +# Makefile for the AVM ISDN device drivers + +# Each configuration option enables a list of files. + +obj-$(CONFIG_ISDN_DRV_AVMB1_B1ISA) += b1isa.o b1.o +obj-$(CONFIG_ISDN_DRV_AVMB1_B1PCI) += b1pci.o b1.o b1dma.o +obj-$(CONFIG_ISDN_DRV_AVMB1_B1PCMCIA) += b1pcmcia.o b1.o +obj-$(CONFIG_ISDN_DRV_AVMB1_AVM_CS) += avm_cs.o +obj-$(CONFIG_ISDN_DRV_AVMB1_T1ISA) += t1isa.o b1.o +obj-$(CONFIG_ISDN_DRV_AVMB1_T1PCI) += t1pci.o b1.o b1dma.o +obj-$(CONFIG_ISDN_DRV_AVMB1_C4) += c4.o b1.o diff --git a/drivers/isdn/hardware/avm/avm_cs.c b/drivers/isdn/hardware/avm/avm_cs.c new file mode 100644 index 000000000000..dc00c85e3e35 --- /dev/null +++ b/drivers/isdn/hardware/avm/avm_cs.c @@ -0,0 +1,510 @@ +/* $Id: avm_cs.c,v 1.4.6.3 2001/09/23 22:24:33 kai Exp $ + * + * A PCMCIA client driver for AVM B1/M1/M2 + * + * Copyright 1999 by Carsten Paeth <calle@calle.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/ptrace.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/tty.h> +#include <linux/serial.h> +#include <linux/major.h> +#include <asm/io.h> +#include <asm/system.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/ciscode.h> +#include <pcmcia/ds.h> +#include <pcmcia/cisreg.h> + +#include <linux/skbuff.h> +#include <linux/capi.h> +#include <linux/b1lli.h> +#include <linux/b1pcmcia.h> + +/*====================================================================*/ + +MODULE_DESCRIPTION("CAPI4Linux: PCMCIA client driver for AVM B1/M1/M2"); +MODULE_AUTHOR("Carsten Paeth"); +MODULE_LICENSE("GPL"); + +/*====================================================================*/ + +/* + The event() function is this driver's Card Services event handler. + It will be called by Card Services when an appropriate card status + event is received. The config() and release() entry points are + used to configure or release a socket, in response to card insertion + and ejection events. They are invoked from the skeleton event + handler. +*/ + +static void avmcs_config(dev_link_t *link); +static void avmcs_release(dev_link_t *link); +static int avmcs_event(event_t event, int priority, + event_callback_args_t *args); + +/* + The attach() and detach() entry points are used to create and destroy + "instances" of the driver, where each instance represents everything + needed to manage one actual PCMCIA card. +*/ + +static dev_link_t *avmcs_attach(void); +static void avmcs_detach(dev_link_t *); + +/* + The dev_info variable is the "key" that is used to match up this + device driver with appropriate cards, through the card configuration + database. +*/ + +static dev_info_t dev_info = "avm_cs"; + +/* + A linked list of "instances" of the skeleton device. Each actual + PCMCIA card corresponds to one device instance, and is described + by one dev_link_t structure (defined in ds.h). + + You may not want to use a linked list for this -- for example, the + memory card driver uses an array of dev_link_t pointers, where minor + device numbers are used to derive the corresponding array index. +*/ + +static dev_link_t *dev_list = NULL; + +/* + A dev_link_t structure has fields for most things that are needed + to keep track of a socket, but there will usually be some device + specific information that also needs to be kept track of. The + 'priv' pointer in a dev_link_t structure can be used to point to + a device-specific private data structure, like this. + + A driver needs to provide a dev_node_t structure for each device + on a card. In some cases, there is only one device per card (for + example, ethernet cards, modems). In other cases, there may be + many actual or logical devices (SCSI adapters, memory cards with + multiple partitions). The dev_node_t structures need to be kept + in a linked list starting at the 'dev' field of a dev_link_t + structure. We allocate them in the card's private data structure, + because they generally can't be allocated dynamically. +*/ + +typedef struct local_info_t { + dev_node_t node; +} local_info_t; + +/*====================================================================== + + avmcs_attach() creates an "instance" of the driver, allocating + local data structures for one device. The device is registered + with Card Services. + + The dev_link structure is initialized, but we don't actually + configure the card at this point -- we wait until we receive a + card insertion event. + +======================================================================*/ + +static dev_link_t *avmcs_attach(void) +{ + client_reg_t client_reg; + dev_link_t *link; + local_info_t *local; + int ret; + + /* Initialize the dev_link_t structure */ + link = kmalloc(sizeof(struct dev_link_t), GFP_KERNEL); + if (!link) + goto err; + memset(link, 0, sizeof(struct dev_link_t)); + + /* The io structure describes IO port mapping */ + link->io.NumPorts1 = 16; + link->io.Attributes1 = IO_DATA_PATH_WIDTH_8; + link->io.NumPorts2 = 0; + + /* Interrupt setup */ + link->irq.Attributes = IRQ_TYPE_EXCLUSIVE; + link->irq.Attributes = IRQ_TYPE_DYNAMIC_SHARING|IRQ_FIRST_SHARED; + + link->irq.IRQInfo1 = IRQ_LEVEL_ID; + + /* General socket configuration */ + link->conf.Attributes = CONF_ENABLE_IRQ; + link->conf.Vcc = 50; + link->conf.IntType = INT_MEMORY_AND_IO; + link->conf.ConfigIndex = 1; + link->conf.Present = PRESENT_OPTION; + + /* Allocate space for private device-specific data */ + local = kmalloc(sizeof(local_info_t), GFP_KERNEL); + if (!local) + goto err_kfree; + memset(local, 0, sizeof(local_info_t)); + link->priv = local; + + /* Register with Card Services */ + link->next = dev_list; + dev_list = link; + client_reg.dev_info = &dev_info; + client_reg.EventMask = + CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL | + CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET | + CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME; + client_reg.event_handler = &avmcs_event; + client_reg.Version = 0x0210; + client_reg.event_callback_args.client_data = link; + ret = pcmcia_register_client(&link->handle, &client_reg); + if (ret != 0) { + cs_error(link->handle, RegisterClient, ret); + avmcs_detach(link); + goto err; + } + return link; + + err_kfree: + kfree(link); + err: + return NULL; +} /* avmcs_attach */ + +/*====================================================================== + + This deletes a driver "instance". The device is de-registered + with Card Services. If it has been released, all local data + structures are freed. Otherwise, the structures will be freed + when the device is released. + +======================================================================*/ + +static void avmcs_detach(dev_link_t *link) +{ + dev_link_t **linkp; + + /* Locate device structure */ + for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) + if (*linkp == link) break; + if (*linkp == NULL) + return; + + /* + If the device is currently configured and active, we won't + actually delete it yet. Instead, it is marked so that when + the release() function is called, that will trigger a proper + detach(). + */ + if (link->state & DEV_CONFIG) { + link->state |= DEV_STALE_LINK; + return; + } + + /* Break the link with Card Services */ + if (link->handle) + pcmcia_deregister_client(link->handle); + + /* Unlink device structure, free pieces */ + *linkp = link->next; + if (link->priv) { + kfree(link->priv); + } + kfree(link); + +} /* avmcs_detach */ + +/*====================================================================== + + avmcs_config() is scheduled to run after a CARD_INSERTION event + is received, to configure the PCMCIA socket, and to make the + ethernet device available to the system. + +======================================================================*/ + +static int get_tuple(client_handle_t handle, tuple_t *tuple, + cisparse_t *parse) +{ + int i = pcmcia_get_tuple_data(handle, tuple); + if (i != CS_SUCCESS) return i; + return pcmcia_parse_tuple(handle, tuple, parse); +} + +static int first_tuple(client_handle_t handle, tuple_t *tuple, + cisparse_t *parse) +{ + int i = pcmcia_get_first_tuple(handle, tuple); + if (i != CS_SUCCESS) return i; + return get_tuple(handle, tuple, parse); +} + +static int next_tuple(client_handle_t handle, tuple_t *tuple, + cisparse_t *parse) +{ + int i = pcmcia_get_next_tuple(handle, tuple); + if (i != CS_SUCCESS) return i; + return get_tuple(handle, tuple, parse); +} + +static void avmcs_config(dev_link_t *link) +{ + client_handle_t handle; + tuple_t tuple; + cisparse_t parse; + cistpl_cftable_entry_t *cf = &parse.cftable_entry; + local_info_t *dev; + int i; + u_char buf[64]; + char devname[128]; + int cardtype; + int (*addcard)(unsigned int port, unsigned irq); + + handle = link->handle; + dev = link->priv; + + /* + This reads the card's CONFIG tuple to find its configuration + registers. + */ + do { + tuple.DesiredTuple = CISTPL_CONFIG; + i = pcmcia_get_first_tuple(handle, &tuple); + if (i != CS_SUCCESS) break; + tuple.TupleData = buf; + tuple.TupleDataMax = 64; + tuple.TupleOffset = 0; + i = pcmcia_get_tuple_data(handle, &tuple); + if (i != CS_SUCCESS) break; + i = pcmcia_parse_tuple(handle, &tuple, &parse); + if (i != CS_SUCCESS) break; + link->conf.ConfigBase = parse.config.base; + } while (0); + if (i != CS_SUCCESS) { + cs_error(link->handle, ParseTuple, i); + link->state &= ~DEV_CONFIG_PENDING; + return; + } + + /* Configure card */ + link->state |= DEV_CONFIG; + + do { + + tuple.Attributes = 0; + tuple.TupleData = buf; + tuple.TupleDataMax = 254; + tuple.TupleOffset = 0; + tuple.DesiredTuple = CISTPL_VERS_1; + + devname[0] = 0; + if( !first_tuple(handle, &tuple, &parse) && parse.version_1.ns > 1 ) { + strlcpy(devname,parse.version_1.str + parse.version_1.ofs[1], + sizeof(devname)); + } + /* + * find IO port + */ + tuple.TupleData = (cisdata_t *)buf; + tuple.TupleOffset = 0; tuple.TupleDataMax = 255; + tuple.Attributes = 0; + tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY; + i = first_tuple(handle, &tuple, &parse); + while (i == CS_SUCCESS) { + if (cf->io.nwin > 0) { + link->conf.ConfigIndex = cf->index; + link->io.BasePort1 = cf->io.win[0].base; + link->io.NumPorts1 = cf->io.win[0].len; + link->io.NumPorts2 = 0; + printk(KERN_INFO "avm_cs: testing i/o %#x-%#x\n", + link->io.BasePort1, + link->io.BasePort1+link->io.NumPorts1-1); + i = pcmcia_request_io(link->handle, &link->io); + if (i == CS_SUCCESS) goto found_port; + } + i = next_tuple(handle, &tuple, &parse); + } + +found_port: + if (i != CS_SUCCESS) { + cs_error(link->handle, RequestIO, i); + break; + } + + /* + * allocate an interrupt line + */ + i = pcmcia_request_irq(link->handle, &link->irq); + if (i != CS_SUCCESS) { + cs_error(link->handle, RequestIRQ, i); + pcmcia_release_io(link->handle, &link->io); + break; + } + + /* + * configure the PCMCIA socket + */ + i = pcmcia_request_configuration(link->handle, &link->conf); + if (i != CS_SUCCESS) { + cs_error(link->handle, RequestConfiguration, i); + pcmcia_release_io(link->handle, &link->io); + pcmcia_release_irq(link->handle, &link->irq); + break; + } + + } while (0); + + /* At this point, the dev_node_t structure(s) should be + initialized and arranged in a linked list at link->dev. */ + + if (devname[0]) { + char *s = strrchr(devname, ' '); + if (!s) + s = devname; + else s++; + strcpy(dev->node.dev_name, s); + if (strcmp("M1", s) == 0) { + cardtype = AVM_CARDTYPE_M1; + } else if (strcmp("M2", s) == 0) { + cardtype = AVM_CARDTYPE_M2; + } else { + cardtype = AVM_CARDTYPE_B1; + } + } else { + strcpy(dev->node.dev_name, "b1"); + cardtype = AVM_CARDTYPE_B1; + } + + dev->node.major = 64; + dev->node.minor = 0; + link->dev = &dev->node; + + link->state &= ~DEV_CONFIG_PENDING; + /* If any step failed, release any partially configured state */ + if (i != 0) { + avmcs_release(link); + return; + } + + + switch (cardtype) { + case AVM_CARDTYPE_M1: addcard = b1pcmcia_addcard_m1; break; + case AVM_CARDTYPE_M2: addcard = b1pcmcia_addcard_m2; break; + default: + case AVM_CARDTYPE_B1: addcard = b1pcmcia_addcard_b1; break; + } + if ((i = (*addcard)(link->io.BasePort1, link->irq.AssignedIRQ)) < 0) { + printk(KERN_ERR "avm_cs: failed to add AVM-%s-Controller at i/o %#x, irq %d\n", + dev->node.dev_name, link->io.BasePort1, link->irq.AssignedIRQ); + avmcs_release(link); + return; + } + dev->node.minor = i; + +} /* avmcs_config */ + +/*====================================================================== + + After a card is removed, avmcs_release() will unregister the net + device, and release the PCMCIA configuration. If the device is + still open, this will be postponed until it is closed. + +======================================================================*/ + +static void avmcs_release(dev_link_t *link) +{ + b1pcmcia_delcard(link->io.BasePort1, link->irq.AssignedIRQ); + + /* Unlink the device chain */ + link->dev = NULL; + + /* Don't bother checking to see if these succeed or not */ + pcmcia_release_configuration(link->handle); + pcmcia_release_io(link->handle, &link->io); + pcmcia_release_irq(link->handle, &link->irq); + link->state &= ~DEV_CONFIG; + + if (link->state & DEV_STALE_LINK) + avmcs_detach(link); + +} /* avmcs_release */ + +/*====================================================================== + + The card status event handler. Mostly, this schedules other + stuff to run after an event is received. A CARD_REMOVAL event + also sets some flags to discourage the net drivers from trying + to talk to the card any more. + + When a CARD_REMOVAL event is received, we immediately set a flag + to block future accesses to this device. All the functions that + actually access the device should check this flag to make sure + the card is still present. + +======================================================================*/ + +static int avmcs_event(event_t event, int priority, + event_callback_args_t *args) +{ + dev_link_t *link = args->client_data; + + switch (event) { + case CS_EVENT_CARD_REMOVAL: + link->state &= ~DEV_PRESENT; + if (link->state & DEV_CONFIG) + avmcs_release(link); + break; + case CS_EVENT_CARD_INSERTION: + link->state |= DEV_PRESENT | DEV_CONFIG_PENDING; + avmcs_config(link); + break; + case CS_EVENT_PM_SUSPEND: + link->state |= DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_RESET_PHYSICAL: + if (link->state & DEV_CONFIG) + pcmcia_release_configuration(link->handle); + break; + case CS_EVENT_PM_RESUME: + link->state &= ~DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_CARD_RESET: + if (link->state & DEV_CONFIG) + pcmcia_request_configuration(link->handle, &link->conf); + break; + } + return 0; +} /* avmcs_event */ + +static struct pcmcia_driver avmcs_driver = { + .owner = THIS_MODULE, + .drv = { + .name = "avm_cs", + }, + .attach = avmcs_attach, + .detach = avmcs_detach, +}; + +static int __init avmcs_init(void) +{ + return pcmcia_register_driver(&avmcs_driver); +} + +static void __exit avmcs_exit(void) +{ + pcmcia_unregister_driver(&avmcs_driver); + BUG_ON(dev_list != NULL); +} + +module_init(avmcs_init); +module_exit(avmcs_exit); diff --git a/drivers/isdn/hardware/avm/avmcard.h b/drivers/isdn/hardware/avm/avmcard.h new file mode 100644 index 000000000000..296d6a6f749f --- /dev/null +++ b/drivers/isdn/hardware/avm/avmcard.h @@ -0,0 +1,585 @@ +/* $Id: avmcard.h,v 1.1.4.1.2.1 2001/12/21 15:00:17 kai Exp $ + * + * Copyright 1999 by Carsten Paeth <calle@calle.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#ifndef _AVMCARD_H_ +#define _AVMCARD_H_ + +#include <linux/spinlock.h> +#include <linux/list.h> +#include <linux/interrupt.h> + +#define AVMB1_PORTLEN 0x1f +#define AVM_MAXVERSION 8 +#define AVM_NCCI_PER_CHANNEL 4 + +/* + * Versions + */ + +#define VER_DRIVER 0 +#define VER_CARDTYPE 1 +#define VER_HWID 2 +#define VER_SERIAL 3 +#define VER_OPTION 4 +#define VER_PROTO 5 +#define VER_PROFILE 6 +#define VER_CAPI 7 + +enum avmcardtype { + avm_b1isa, + avm_b1pci, + avm_b1pcmcia, + avm_m1, + avm_m2, + avm_t1isa, + avm_t1pci, + avm_c4, + avm_c2 +}; + +typedef struct avmcard_dmabuf { + long size; + u8 *dmabuf; + dma_addr_t dmaaddr; +} avmcard_dmabuf; + +typedef struct avmcard_dmainfo { + u32 recvlen; + avmcard_dmabuf recvbuf; + + avmcard_dmabuf sendbuf; + struct sk_buff_head send_queue; + + struct pci_dev *pcidev; +} avmcard_dmainfo; + +typedef struct avmctrl_info { + char cardname[32]; + + int versionlen; + char versionbuf[1024]; + char *version[AVM_MAXVERSION]; + + char infobuf[128]; /* for function procinfo */ + + struct avmcard *card; + struct capi_ctr capi_ctrl; + + struct list_head ncci_head; +} avmctrl_info; + +typedef struct avmcard { + char name[32]; + + spinlock_t lock; + unsigned int port; + unsigned irq; + unsigned long membase; + enum avmcardtype cardtype; + unsigned char revision; + unsigned char class; + int cardnr; /* for t1isa */ + + char msgbuf[128]; /* capimsg msg part */ + char databuf[2048]; /* capimsg data part */ + + void __iomem *mbase; + volatile u32 csr; + avmcard_dmainfo *dma; + + struct avmctrl_info *ctrlinfo; + + u_int nr_controllers; + u_int nlogcontr; + struct list_head list; +} avmcard; + +extern int b1_irq_table[16]; + +/* + * LLI Messages to the ISDN-ControllerISDN Controller + */ + +#define SEND_POLL 0x72 /* + * after load <- RECEIVE_POLL + */ +#define SEND_INIT 0x11 /* + * first message <- RECEIVE_INIT + * int32 NumApplications int32 + * NumNCCIs int32 BoardNumber + */ +#define SEND_REGISTER 0x12 /* + * register an application int32 + * ApplIDId int32 NumMessages + * int32 NumB3Connections int32 + * NumB3Blocks int32 B3Size + * + * AnzB3Connection != 0 && + * AnzB3Blocks >= 1 && B3Size >= 1 + */ +#define SEND_RELEASE 0x14 /* + * deregister an application int32 + * ApplID + */ +#define SEND_MESSAGE 0x15 /* + * send capi-message int32 length + * capi-data ... + */ +#define SEND_DATA_B3_REQ 0x13 /* + * send capi-data-message int32 + * MsgLength capi-data ... int32 + * B3Length data .... + */ + +#define SEND_CONFIG 0x21 /* + */ + +#define SEND_POLLACK 0x73 /* T1 Watchdog */ + +/* + * LLI Messages from the ISDN-ControllerISDN Controller + */ + +#define RECEIVE_POLL 0x32 /* + * <- after SEND_POLL + */ +#define RECEIVE_INIT 0x27 /* + * <- after SEND_INIT int32 length + * byte total length b1struct board + * driver revision b1struct card + * type b1struct reserved b1struct + * serial number b1struct driver + * capability b1struct d-channel + * protocol b1struct CAPI-2.0 + * profile b1struct capi version + */ +#define RECEIVE_MESSAGE 0x21 /* + * <- after SEND_MESSAGE int32 + * AppllID int32 Length capi-data + * .... + */ +#define RECEIVE_DATA_B3_IND 0x22 /* + * received data int32 AppllID + * int32 Length capi-data ... + * int32 B3Length data ... + */ +#define RECEIVE_START 0x23 /* + * Handshake + */ +#define RECEIVE_STOP 0x24 /* + * Handshake + */ +#define RECEIVE_NEW_NCCI 0x25 /* + * int32 AppllID int32 NCCI int32 + * WindowSize + */ +#define RECEIVE_FREE_NCCI 0x26 /* + * int32 AppllID int32 NCCI + */ +#define RECEIVE_RELEASE 0x26 /* + * int32 AppllID int32 0xffffffff + */ +#define RECEIVE_TASK_READY 0x31 /* + * int32 tasknr + * int32 Length Taskname ... + */ +#define RECEIVE_DEBUGMSG 0x71 /* + * int32 Length message + * + */ +#define RECEIVE_POLLDWORD 0x75 /* t1pci in dword mode */ + +#define WRITE_REGISTER 0x00 +#define READ_REGISTER 0x01 + +/* + * port offsets + */ + +#define B1_READ 0x00 +#define B1_WRITE 0x01 +#define B1_INSTAT 0x02 +#define B1_OUTSTAT 0x03 +#define B1_ANALYSE 0x04 +#define B1_REVISION 0x05 +#define B1_RESET 0x10 + + +#define B1_STAT0(cardtype) ((cardtype) == avm_m1 ? 0x81200000l : 0x80A00000l) +#define B1_STAT1(cardtype) (0x80E00000l) + +/* ---------------------------------------------------------------- */ + +static inline unsigned char b1outp(unsigned int base, + unsigned short offset, + unsigned char value) +{ + outb(value, base + offset); + return inb(base + B1_ANALYSE); +} + + +static inline int b1_rx_full(unsigned int base) +{ + return inb(base + B1_INSTAT) & 0x1; +} + +static inline unsigned char b1_get_byte(unsigned int base) +{ + unsigned long stop = jiffies + 1 * HZ; /* maximum wait time 1 sec */ + while (!b1_rx_full(base) && time_before(jiffies, stop)); + if (b1_rx_full(base)) + return inb(base + B1_READ); + printk(KERN_CRIT "b1lli(0x%x): rx not full after 1 second\n", base); + return 0; +} + +static inline unsigned int b1_get_word(unsigned int base) +{ + unsigned int val = 0; + val |= b1_get_byte(base); + val |= (b1_get_byte(base) << 8); + val |= (b1_get_byte(base) << 16); + val |= (b1_get_byte(base) << 24); + return val; +} + +static inline int b1_tx_empty(unsigned int base) +{ + return inb(base + B1_OUTSTAT) & 0x1; +} + +static inline void b1_put_byte(unsigned int base, unsigned char val) +{ + while (!b1_tx_empty(base)); + b1outp(base, B1_WRITE, val); +} + +static inline int b1_save_put_byte(unsigned int base, unsigned char val) +{ + unsigned long stop = jiffies + 2 * HZ; + while (!b1_tx_empty(base) && time_before(jiffies,stop)); + if (!b1_tx_empty(base)) return -1; + b1outp(base, B1_WRITE, val); + return 0; +} + +static inline void b1_put_word(unsigned int base, unsigned int val) +{ + b1_put_byte(base, val & 0xff); + b1_put_byte(base, (val >> 8) & 0xff); + b1_put_byte(base, (val >> 16) & 0xff); + b1_put_byte(base, (val >> 24) & 0xff); +} + +static inline unsigned int b1_get_slice(unsigned int base, + unsigned char *dp) +{ + unsigned int len, i; + + len = i = b1_get_word(base); + while (i-- > 0) *dp++ = b1_get_byte(base); + return len; +} + +static inline void b1_put_slice(unsigned int base, + unsigned char *dp, unsigned int len) +{ + unsigned i = len; + b1_put_word(base, i); + while (i-- > 0) + b1_put_byte(base, *dp++); +} + +static void b1_wr_reg(unsigned int base, + unsigned int reg, + unsigned int value) +{ + b1_put_byte(base, WRITE_REGISTER); + b1_put_word(base, reg); + b1_put_word(base, value); +} + +static inline unsigned int b1_rd_reg(unsigned int base, + unsigned int reg) +{ + b1_put_byte(base, READ_REGISTER); + b1_put_word(base, reg); + return b1_get_word(base); + +} + +static inline void b1_reset(unsigned int base) +{ + b1outp(base, B1_RESET, 0); + mdelay(55 * 2); /* 2 TIC's */ + + b1outp(base, B1_RESET, 1); + mdelay(55 * 2); /* 2 TIC's */ + + b1outp(base, B1_RESET, 0); + mdelay(55 * 2); /* 2 TIC's */ +} + +static inline unsigned char b1_disable_irq(unsigned int base) +{ + return b1outp(base, B1_INSTAT, 0x00); +} + +/* ---------------------------------------------------------------- */ + +static inline void b1_set_test_bit(unsigned int base, + enum avmcardtype cardtype, + int onoff) +{ + b1_wr_reg(base, B1_STAT0(cardtype), onoff ? 0x21 : 0x20); +} + +static inline int b1_get_test_bit(unsigned int base, + enum avmcardtype cardtype) +{ + return (b1_rd_reg(base, B1_STAT0(cardtype)) & 0x01) != 0; +} + +/* ---------------------------------------------------------------- */ + +#define T1_FASTLINK 0x00 +#define T1_SLOWLINK 0x08 + +#define T1_READ B1_READ +#define T1_WRITE B1_WRITE +#define T1_INSTAT B1_INSTAT +#define T1_OUTSTAT B1_OUTSTAT +#define T1_IRQENABLE 0x05 +#define T1_FIFOSTAT 0x06 +#define T1_RESETLINK 0x10 +#define T1_ANALYSE 0x11 +#define T1_IRQMASTER 0x12 +#define T1_IDENT 0x17 +#define T1_RESETBOARD 0x1f + +#define T1F_IREADY 0x01 +#define T1F_IHALF 0x02 +#define T1F_IFULL 0x04 +#define T1F_IEMPTY 0x08 +#define T1F_IFLAGS 0xF0 + +#define T1F_OREADY 0x10 +#define T1F_OHALF 0x20 +#define T1F_OEMPTY 0x40 +#define T1F_OFULL 0x80 +#define T1F_OFLAGS 0xF0 + +/* there are HEMA cards with 1k and 4k FIFO out */ +#define FIFO_OUTBSIZE 256 +#define FIFO_INPBSIZE 512 + +#define HEMA_VERSION_ID 0 +#define HEMA_PAL_ID 0 + +static inline void t1outp(unsigned int base, + unsigned short offset, + unsigned char value) +{ + outb(value, base + offset); +} + +static inline unsigned char t1inp(unsigned int base, + unsigned short offset) +{ + return inb(base + offset); +} + +static inline int t1_isfastlink(unsigned int base) +{ + return (inb(base + T1_IDENT) & ~0x82) == 1; +} + +static inline unsigned char t1_fifostatus(unsigned int base) +{ + return inb(base + T1_FIFOSTAT); +} + +static inline unsigned int t1_get_slice(unsigned int base, + unsigned char *dp) +{ + unsigned int len, i; +#ifdef FASTLINK_DEBUG + unsigned wcnt = 0, bcnt = 0; +#endif + + len = i = b1_get_word(base); + if (t1_isfastlink(base)) { + int status; + while (i > 0) { + status = t1_fifostatus(base) & (T1F_IREADY|T1F_IHALF); + if (i >= FIFO_INPBSIZE) status |= T1F_IFULL; + + switch (status) { + case T1F_IREADY|T1F_IHALF|T1F_IFULL: + insb(base+B1_READ, dp, FIFO_INPBSIZE); + dp += FIFO_INPBSIZE; + i -= FIFO_INPBSIZE; +#ifdef FASTLINK_DEBUG + wcnt += FIFO_INPBSIZE; +#endif + break; + case T1F_IREADY|T1F_IHALF: + insb(base+B1_READ,dp, i); +#ifdef FASTLINK_DEBUG + wcnt += i; +#endif + dp += i; + i = 0; + if (i == 0) + break; + /* fall through */ + default: + *dp++ = b1_get_byte(base); + i--; +#ifdef FASTLINK_DEBUG + bcnt++; +#endif + break; + } + } +#ifdef FASTLINK_DEBUG + if (wcnt) + printk(KERN_DEBUG "b1lli(0x%x): get_slice l=%d w=%d b=%d\n", + base, len, wcnt, bcnt); +#endif + } else { + while (i-- > 0) + *dp++ = b1_get_byte(base); + } + return len; +} + +static inline void t1_put_slice(unsigned int base, + unsigned char *dp, unsigned int len) +{ + unsigned i = len; + b1_put_word(base, i); + if (t1_isfastlink(base)) { + int status; + while (i > 0) { + status = t1_fifostatus(base) & (T1F_OREADY|T1F_OHALF); + if (i >= FIFO_OUTBSIZE) status |= T1F_OEMPTY; + switch (status) { + case T1F_OREADY|T1F_OHALF|T1F_OEMPTY: + outsb(base+B1_WRITE, dp, FIFO_OUTBSIZE); + dp += FIFO_OUTBSIZE; + i -= FIFO_OUTBSIZE; + break; + case T1F_OREADY|T1F_OHALF: + outsb(base+B1_WRITE, dp, i); + dp += i; + i = 0; + break; + default: + b1_put_byte(base, *dp++); + i--; + break; + } + } + } else { + while (i-- > 0) + b1_put_byte(base, *dp++); + } +} + +static inline void t1_disable_irq(unsigned int base) +{ + t1outp(base, T1_IRQMASTER, 0x00); +} + +static inline void t1_reset(unsigned int base) +{ + /* reset T1 Controller */ + b1_reset(base); + /* disable irq on HEMA */ + t1outp(base, B1_INSTAT, 0x00); + t1outp(base, B1_OUTSTAT, 0x00); + t1outp(base, T1_IRQMASTER, 0x00); + /* reset HEMA board configuration */ + t1outp(base, T1_RESETBOARD, 0xf); +} + +static inline void b1_setinterrupt(unsigned int base, unsigned irq, + enum avmcardtype cardtype) +{ + switch (cardtype) { + case avm_t1isa: + t1outp(base, B1_INSTAT, 0x00); + t1outp(base, B1_INSTAT, 0x02); + t1outp(base, T1_IRQMASTER, 0x08); + break; + case avm_b1isa: + b1outp(base, B1_INSTAT, 0x00); + b1outp(base, B1_RESET, b1_irq_table[irq]); + b1outp(base, B1_INSTAT, 0x02); + break; + default: + case avm_m1: + case avm_m2: + case avm_b1pci: + b1outp(base, B1_INSTAT, 0x00); + b1outp(base, B1_RESET, 0xf0); + b1outp(base, B1_INSTAT, 0x02); + break; + case avm_c4: + case avm_t1pci: + b1outp(base, B1_RESET, 0xf0); + break; + } +} + +/* b1.c */ +avmcard *b1_alloc_card(int nr_controllers); +void b1_free_card(avmcard *card); +int b1_detect(unsigned int base, enum avmcardtype cardtype); +void b1_getrevision(avmcard *card); +int b1_load_t4file(avmcard *card, capiloaddatapart * t4file); +int b1_load_config(avmcard *card, capiloaddatapart * config); +int b1_loaded(avmcard *card); + +int b1_load_firmware(struct capi_ctr *ctrl, capiloaddata *data); +void b1_reset_ctr(struct capi_ctr *ctrl); +void b1_register_appl(struct capi_ctr *ctrl, u16 appl, + capi_register_params *rp); +void b1_release_appl(struct capi_ctr *ctrl, u16 appl); +u16 b1_send_message(struct capi_ctr *ctrl, struct sk_buff *skb); +void b1_parse_version(avmctrl_info *card); +irqreturn_t b1_interrupt(int interrupt, void *devptr, struct pt_regs *regs); + +int b1ctl_read_proc(char *page, char **start, off_t off, + int count, int *eof, struct capi_ctr *ctrl); + +avmcard_dmainfo *avmcard_dma_alloc(char *name, struct pci_dev *, + long rsize, long ssize); +void avmcard_dma_free(avmcard_dmainfo *); + +/* b1dma.c */ +int b1pciv4_detect(avmcard *card); +int t1pci_detect(avmcard *card); +void b1dma_reset(avmcard *card); +irqreturn_t b1dma_interrupt(int interrupt, void *devptr, struct pt_regs *regs); + +int b1dma_load_firmware(struct capi_ctr *ctrl, capiloaddata *data); +void b1dma_reset_ctr(struct capi_ctr *ctrl); +void b1dma_remove_ctr(struct capi_ctr *ctrl); +void b1dma_register_appl(struct capi_ctr *ctrl, + u16 appl, + capi_register_params *rp); +void b1dma_release_appl(struct capi_ctr *ctrl, u16 appl); +u16 b1dma_send_message(struct capi_ctr *ctrl, struct sk_buff *skb); +int b1dmactl_read_proc(char *page, char **start, off_t off, + int count, int *eof, struct capi_ctr *ctrl); + +#endif /* _AVMCARD_H_ */ diff --git a/drivers/isdn/hardware/avm/b1.c b/drivers/isdn/hardware/avm/b1.c new file mode 100644 index 000000000000..0c7061d55027 --- /dev/null +++ b/drivers/isdn/hardware/avm/b1.c @@ -0,0 +1,814 @@ +/* $Id: b1.c,v 1.1.2.2 2004/01/16 21:09:27 keil Exp $ + * + * Common module for AVM B1 cards. + * + * Copyright 1999 by Carsten Paeth <calle@calle.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/skbuff.h> +#include <linux/delay.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/capi.h> +#include <linux/kernelcapi.h> +#include <asm/io.h> +#include <linux/init.h> +#include <asm/uaccess.h> +#include <linux/netdevice.h> +#include <linux/isdn/capilli.h> +#include "avmcard.h" +#include <linux/isdn/capicmd.h> +#include <linux/isdn/capiutil.h> + +static char *revision = "$Revision: 1.1.2.2 $"; + +/* ------------------------------------------------------------- */ + +MODULE_DESCRIPTION("CAPI4Linux: Common support for active AVM cards"); +MODULE_AUTHOR("Carsten Paeth"); +MODULE_LICENSE("GPL"); + +/* ------------------------------------------------------------- */ + +int b1_irq_table[16] = +{0, + 0, + 0, + 192, /* irq 3 */ + 32, /* irq 4 */ + 160, /* irq 5 */ + 96, /* irq 6 */ + 224, /* irq 7 */ + 0, + 64, /* irq 9 */ + 80, /* irq 10 */ + 208, /* irq 11 */ + 48, /* irq 12 */ + 0, + 0, + 112, /* irq 15 */ +}; + +/* ------------------------------------------------------------- */ + +avmcard *b1_alloc_card(int nr_controllers) +{ + avmcard *card; + avmctrl_info *cinfo; + int i; + + card = kmalloc(sizeof(*card), GFP_KERNEL); + if (!card) + return NULL; + + memset(card, 0, sizeof(*card)); + + cinfo = kmalloc(sizeof(*cinfo) * nr_controllers, GFP_KERNEL); + if (!cinfo) { + kfree(card); + return NULL; + } + memset(cinfo, 0, sizeof(*cinfo) * nr_controllers); + + card->ctrlinfo = cinfo; + for (i = 0; i < nr_controllers; i++) { + INIT_LIST_HEAD(&cinfo[i].ncci_head); + cinfo[i].card = card; + } + spin_lock_init(&card->lock); + card->nr_controllers = nr_controllers; + + return card; +} + +/* ------------------------------------------------------------- */ + +void b1_free_card(avmcard *card) +{ + kfree(card->ctrlinfo); + kfree(card); +} + +/* ------------------------------------------------------------- */ + +int b1_detect(unsigned int base, enum avmcardtype cardtype) +{ + int onoff, i; + + /* + * Statusregister 0000 00xx + */ + if ((inb(base + B1_INSTAT) & 0xfc) + || (inb(base + B1_OUTSTAT) & 0xfc)) + return 1; + /* + * Statusregister 0000 001x + */ + b1outp(base, B1_INSTAT, 0x2); /* enable irq */ + /* b1outp(base, B1_OUTSTAT, 0x2); */ + if ((inb(base + B1_INSTAT) & 0xfe) != 0x2 + /* || (inb(base + B1_OUTSTAT) & 0xfe) != 0x2 */) + return 2; + /* + * Statusregister 0000 000x + */ + b1outp(base, B1_INSTAT, 0x0); /* disable irq */ + b1outp(base, B1_OUTSTAT, 0x0); + if ((inb(base + B1_INSTAT) & 0xfe) + || (inb(base + B1_OUTSTAT) & 0xfe)) + return 3; + + for (onoff = !0, i= 0; i < 10 ; i++) { + b1_set_test_bit(base, cardtype, onoff); + if (b1_get_test_bit(base, cardtype) != onoff) + return 4; + onoff = !onoff; + } + + if (cardtype == avm_m1) + return 0; + + if ((b1_rd_reg(base, B1_STAT1(cardtype)) & 0x0f) != 0x01) + return 5; + + return 0; +} + +void b1_getrevision(avmcard *card) +{ + card->class = inb(card->port + B1_ANALYSE); + card->revision = inb(card->port + B1_REVISION); +} + +#define FWBUF_SIZE 256 +int b1_load_t4file(avmcard *card, capiloaddatapart * t4file) +{ + unsigned char buf[FWBUF_SIZE]; + unsigned char *dp; + int i, left; + unsigned int base = card->port; + + dp = t4file->data; + left = t4file->len; + while (left > FWBUF_SIZE) { + if (t4file->user) { + if (copy_from_user(buf, dp, FWBUF_SIZE)) + return -EFAULT; + } else { + memcpy(buf, dp, FWBUF_SIZE); + } + for (i = 0; i < FWBUF_SIZE; i++) + if (b1_save_put_byte(base, buf[i]) < 0) { + printk(KERN_ERR "%s: corrupted firmware file ?\n", + card->name); + return -EIO; + } + left -= FWBUF_SIZE; + dp += FWBUF_SIZE; + } + if (left) { + if (t4file->user) { + if (copy_from_user(buf, dp, left)) + return -EFAULT; + } else { + memcpy(buf, dp, left); + } + for (i = 0; i < left; i++) + if (b1_save_put_byte(base, buf[i]) < 0) { + printk(KERN_ERR "%s: corrupted firmware file ?\n", + card->name); + return -EIO; + } + } + return 0; +} + +int b1_load_config(avmcard *card, capiloaddatapart * config) +{ + unsigned char buf[FWBUF_SIZE]; + unsigned char *dp; + unsigned int base = card->port; + int i, j, left; + + dp = config->data; + left = config->len; + if (left) { + b1_put_byte(base, SEND_CONFIG); + b1_put_word(base, 1); + b1_put_byte(base, SEND_CONFIG); + b1_put_word(base, left); + } + while (left > FWBUF_SIZE) { + if (config->user) { + if (copy_from_user(buf, dp, FWBUF_SIZE)) + return -EFAULT; + } else { + memcpy(buf, dp, FWBUF_SIZE); + } + for (i = 0; i < FWBUF_SIZE; ) { + b1_put_byte(base, SEND_CONFIG); + for (j=0; j < 4; j++) { + b1_put_byte(base, buf[i++]); + } + } + left -= FWBUF_SIZE; + dp += FWBUF_SIZE; + } + if (left) { + if (config->user) { + if (copy_from_user(buf, dp, left)) + return -EFAULT; + } else { + memcpy(buf, dp, left); + } + for (i = 0; i < left; ) { + b1_put_byte(base, SEND_CONFIG); + for (j=0; j < 4; j++) { + if (i < left) + b1_put_byte(base, buf[i++]); + else + b1_put_byte(base, 0); + } + } + } + return 0; +} + +int b1_loaded(avmcard *card) +{ + unsigned int base = card->port; + unsigned long stop; + unsigned char ans; + unsigned long tout = 2; + + for (stop = jiffies + tout * HZ; time_before(jiffies, stop);) { + if (b1_tx_empty(base)) + break; + } + if (!b1_tx_empty(base)) { + printk(KERN_ERR "%s: b1_loaded: tx err, corrupted t4 file ?\n", + card->name); + return 0; + } + b1_put_byte(base, SEND_POLL); + for (stop = jiffies + tout * HZ; time_before(jiffies, stop);) { + if (b1_rx_full(base)) { + if ((ans = b1_get_byte(base)) == RECEIVE_POLL) { + return 1; + } + printk(KERN_ERR "%s: b1_loaded: got 0x%x, firmware not running\n", + card->name, ans); + return 0; + } + } + printk(KERN_ERR "%s: b1_loaded: firmware not running\n", card->name); + return 0; +} + +/* ------------------------------------------------------------- */ + +int b1_load_firmware(struct capi_ctr *ctrl, capiloaddata *data) +{ + avmctrl_info *cinfo = (avmctrl_info *)(ctrl->driverdata); + avmcard *card = cinfo->card; + unsigned int port = card->port; + unsigned long flags; + int retval; + + b1_reset(port); + + if ((retval = b1_load_t4file(card, &data->firmware))) { + b1_reset(port); + printk(KERN_ERR "%s: failed to load t4file!!\n", + card->name); + return retval; + } + + b1_disable_irq(port); + + if (data->configuration.len > 0 && data->configuration.data) { + if ((retval = b1_load_config(card, &data->configuration))) { + b1_reset(port); + printk(KERN_ERR "%s: failed to load config!!\n", + card->name); + return retval; + } + } + + if (!b1_loaded(card)) { + printk(KERN_ERR "%s: failed to load t4file.\n", card->name); + return -EIO; + } + + spin_lock_irqsave(&card->lock, flags); + b1_setinterrupt(port, card->irq, card->cardtype); + b1_put_byte(port, SEND_INIT); + b1_put_word(port, CAPI_MAXAPPL); + b1_put_word(port, AVM_NCCI_PER_CHANNEL*2); + b1_put_word(port, ctrl->cnr - 1); + spin_unlock_irqrestore(&card->lock, flags); + + return 0; +} + +void b1_reset_ctr(struct capi_ctr *ctrl) +{ + avmctrl_info *cinfo = (avmctrl_info *)(ctrl->driverdata); + avmcard *card = cinfo->card; + unsigned int port = card->port; + + b1_reset(port); + b1_reset(port); + + memset(cinfo->version, 0, sizeof(cinfo->version)); + capilib_release(&cinfo->ncci_head); + capi_ctr_reseted(ctrl); +} + +void b1_register_appl(struct capi_ctr *ctrl, + u16 appl, + capi_register_params *rp) +{ + avmctrl_info *cinfo = (avmctrl_info *)(ctrl->driverdata); + avmcard *card = cinfo->card; + unsigned int port = card->port; + unsigned long flags; + int nconn, want = rp->level3cnt; + + if (want > 0) nconn = want; + else nconn = ctrl->profile.nbchannel * -want; + if (nconn == 0) nconn = ctrl->profile.nbchannel; + + spin_lock_irqsave(&card->lock, flags); + b1_put_byte(port, SEND_REGISTER); + b1_put_word(port, appl); + b1_put_word(port, 1024 * (nconn+1)); + b1_put_word(port, nconn); + b1_put_word(port, rp->datablkcnt); + b1_put_word(port, rp->datablklen); + spin_unlock_irqrestore(&card->lock, flags); +} + +void b1_release_appl(struct capi_ctr *ctrl, u16 appl) +{ + avmctrl_info *cinfo = (avmctrl_info *)(ctrl->driverdata); + avmcard *card = cinfo->card; + unsigned int port = card->port; + unsigned long flags; + + capilib_release_appl(&cinfo->ncci_head, appl); + + spin_lock_irqsave(&card->lock, flags); + b1_put_byte(port, SEND_RELEASE); + b1_put_word(port, appl); + spin_unlock_irqrestore(&card->lock, flags); +} + +u16 b1_send_message(struct capi_ctr *ctrl, struct sk_buff *skb) +{ + avmctrl_info *cinfo = (avmctrl_info *)(ctrl->driverdata); + avmcard *card = cinfo->card; + unsigned int port = card->port; + unsigned long flags; + u16 len = CAPIMSG_LEN(skb->data); + u8 cmd = CAPIMSG_COMMAND(skb->data); + u8 subcmd = CAPIMSG_SUBCOMMAND(skb->data); + u16 dlen, retval; + + if (CAPICMD(cmd, subcmd) == CAPI_DATA_B3_REQ) { + retval = capilib_data_b3_req(&cinfo->ncci_head, + CAPIMSG_APPID(skb->data), + CAPIMSG_NCCI(skb->data), + CAPIMSG_MSGID(skb->data)); + if (retval != CAPI_NOERROR) + return retval; + + dlen = CAPIMSG_DATALEN(skb->data); + + spin_lock_irqsave(&card->lock, flags); + b1_put_byte(port, SEND_DATA_B3_REQ); + b1_put_slice(port, skb->data, len); + b1_put_slice(port, skb->data + len, dlen); + spin_unlock_irqrestore(&card->lock, flags); + } else { + spin_lock_irqsave(&card->lock, flags); + b1_put_byte(port, SEND_MESSAGE); + b1_put_slice(port, skb->data, len); + spin_unlock_irqrestore(&card->lock, flags); + } + + dev_kfree_skb_any(skb); + return CAPI_NOERROR; +} + +/* ------------------------------------------------------------- */ + +void b1_parse_version(avmctrl_info *cinfo) +{ + struct capi_ctr *ctrl = &cinfo->capi_ctrl; + avmcard *card = cinfo->card; + capi_profile *profp; + u8 *dversion; + u8 flag; + int i, j; + + for (j = 0; j < AVM_MAXVERSION; j++) + cinfo->version[j] = "\0\0" + 1; + for (i = 0, j = 0; + j < AVM_MAXVERSION && i < cinfo->versionlen; + j++, i += cinfo->versionbuf[i] + 1) + cinfo->version[j] = &cinfo->versionbuf[i + 1]; + + strlcpy(ctrl->serial, cinfo->version[VER_SERIAL], sizeof(ctrl->serial)); + memcpy(&ctrl->profile, cinfo->version[VER_PROFILE],sizeof(capi_profile)); + strlcpy(ctrl->manu, "AVM GmbH", sizeof(ctrl->manu)); + dversion = cinfo->version[VER_DRIVER]; + ctrl->version.majorversion = 2; + ctrl->version.minorversion = 0; + ctrl->version.majormanuversion = (((dversion[0] - '0') & 0xf) << 4); + ctrl->version.majormanuversion |= ((dversion[2] - '0') & 0xf); + ctrl->version.minormanuversion = (dversion[3] - '0') << 4; + ctrl->version.minormanuversion |= + (dversion[5] - '0') * 10 + ((dversion[6] - '0') & 0xf); + + profp = &ctrl->profile; + + flag = ((u8 *)(profp->manu))[1]; + switch (flag) { + case 0: if (cinfo->version[VER_CARDTYPE]) + strcpy(cinfo->cardname, cinfo->version[VER_CARDTYPE]); + else strcpy(cinfo->cardname, "B1"); + break; + case 3: strcpy(cinfo->cardname,"PCMCIA B"); break; + case 4: strcpy(cinfo->cardname,"PCMCIA M1"); break; + case 5: strcpy(cinfo->cardname,"PCMCIA M2"); break; + case 6: strcpy(cinfo->cardname,"B1 V3.0"); break; + case 7: strcpy(cinfo->cardname,"B1 PCI"); break; + default: sprintf(cinfo->cardname, "AVM?%u", (unsigned int)flag); break; + } + printk(KERN_NOTICE "%s: card %d \"%s\" ready.\n", + card->name, ctrl->cnr, cinfo->cardname); + + flag = ((u8 *)(profp->manu))[3]; + if (flag) + printk(KERN_NOTICE "%s: card %d Protocol:%s%s%s%s%s%s%s\n", + card->name, + ctrl->cnr, + (flag & 0x01) ? " DSS1" : "", + (flag & 0x02) ? " CT1" : "", + (flag & 0x04) ? " VN3" : "", + (flag & 0x08) ? " NI1" : "", + (flag & 0x10) ? " AUSTEL" : "", + (flag & 0x20) ? " ESS" : "", + (flag & 0x40) ? " 1TR6" : "" + ); + + flag = ((u8 *)(profp->manu))[5]; + if (flag) + printk(KERN_NOTICE "%s: card %d Linetype:%s%s%s%s\n", + card->name, + ctrl->cnr, + (flag & 0x01) ? " point to point" : "", + (flag & 0x02) ? " point to multipoint" : "", + (flag & 0x08) ? " leased line without D-channel" : "", + (flag & 0x04) ? " leased line with D-channel" : "" + ); +} + +/* ------------------------------------------------------------- */ + +irqreturn_t b1_interrupt(int interrupt, void *devptr, struct pt_regs *regs) +{ + avmcard *card = devptr; + avmctrl_info *cinfo = &card->ctrlinfo[0]; + struct capi_ctr *ctrl = &cinfo->capi_ctrl; + unsigned char b1cmd; + struct sk_buff *skb; + + unsigned ApplId; + unsigned MsgLen; + unsigned DataB3Len; + unsigned NCCI; + unsigned WindowSize; + unsigned long flags; + + spin_lock_irqsave(&card->lock, flags); + + if (!b1_rx_full(card->port)) { + spin_unlock_irqrestore(&card->lock, flags); + return IRQ_NONE; + } + + b1cmd = b1_get_byte(card->port); + + switch (b1cmd) { + + case RECEIVE_DATA_B3_IND: + + ApplId = (unsigned) b1_get_word(card->port); + MsgLen = b1_get_slice(card->port, card->msgbuf); + DataB3Len = b1_get_slice(card->port, card->databuf); + spin_unlock_irqrestore(&card->lock, flags); + + if (MsgLen < 30) { /* not CAPI 64Bit */ + memset(card->msgbuf+MsgLen, 0, 30-MsgLen); + MsgLen = 30; + CAPIMSG_SETLEN(card->msgbuf, 30); + } + if (!(skb = alloc_skb(DataB3Len + MsgLen, GFP_ATOMIC))) { + printk(KERN_ERR "%s: incoming packet dropped\n", + card->name); + } else { + memcpy(skb_put(skb, MsgLen), card->msgbuf, MsgLen); + memcpy(skb_put(skb, DataB3Len), card->databuf, DataB3Len); + capi_ctr_handle_message(ctrl, ApplId, skb); + } + break; + + case RECEIVE_MESSAGE: + + ApplId = (unsigned) b1_get_word(card->port); + MsgLen = b1_get_slice(card->port, card->msgbuf); + spin_unlock_irqrestore(&card->lock, flags); + if (!(skb = alloc_skb(MsgLen, GFP_ATOMIC))) { + printk(KERN_ERR "%s: incoming packet dropped\n", + card->name); + } else { + memcpy(skb_put(skb, MsgLen), card->msgbuf, MsgLen); + if (CAPIMSG_CMD(skb->data) == CAPI_DATA_B3_CONF) + capilib_data_b3_conf(&cinfo->ncci_head, ApplId, + CAPIMSG_NCCI(skb->data), + CAPIMSG_MSGID(skb->data)); + + capi_ctr_handle_message(ctrl, ApplId, skb); + } + break; + + case RECEIVE_NEW_NCCI: + + ApplId = b1_get_word(card->port); + NCCI = b1_get_word(card->port); + WindowSize = b1_get_word(card->port); + spin_unlock_irqrestore(&card->lock, flags); + + capilib_new_ncci(&cinfo->ncci_head, ApplId, NCCI, WindowSize); + + break; + + case RECEIVE_FREE_NCCI: + + ApplId = b1_get_word(card->port); + NCCI = b1_get_word(card->port); + spin_unlock_irqrestore(&card->lock, flags); + + if (NCCI != 0xffffffff) + capilib_free_ncci(&cinfo->ncci_head, ApplId, NCCI); + + break; + + case RECEIVE_START: + /* b1_put_byte(card->port, SEND_POLLACK); */ + spin_unlock_irqrestore(&card->lock, flags); + capi_ctr_resume_output(ctrl); + break; + + case RECEIVE_STOP: + spin_unlock_irqrestore(&card->lock, flags); + capi_ctr_suspend_output(ctrl); + break; + + case RECEIVE_INIT: + + cinfo->versionlen = b1_get_slice(card->port, cinfo->versionbuf); + spin_unlock_irqrestore(&card->lock, flags); + b1_parse_version(cinfo); + printk(KERN_INFO "%s: %s-card (%s) now active\n", + card->name, + cinfo->version[VER_CARDTYPE], + cinfo->version[VER_DRIVER]); + capi_ctr_ready(ctrl); + break; + + case RECEIVE_TASK_READY: + ApplId = (unsigned) b1_get_word(card->port); + MsgLen = b1_get_slice(card->port, card->msgbuf); + spin_unlock_irqrestore(&card->lock, flags); + card->msgbuf[MsgLen] = 0; + while ( MsgLen > 0 + && ( card->msgbuf[MsgLen-1] == '\n' + || card->msgbuf[MsgLen-1] == '\r')) { + card->msgbuf[MsgLen-1] = 0; + MsgLen--; + } + printk(KERN_INFO "%s: task %d \"%s\" ready.\n", + card->name, ApplId, card->msgbuf); + break; + + case RECEIVE_DEBUGMSG: + MsgLen = b1_get_slice(card->port, card->msgbuf); + spin_unlock_irqrestore(&card->lock, flags); + card->msgbuf[MsgLen] = 0; + while ( MsgLen > 0 + && ( card->msgbuf[MsgLen-1] == '\n' + || card->msgbuf[MsgLen-1] == '\r')) { + card->msgbuf[MsgLen-1] = 0; + MsgLen--; + } + printk(KERN_INFO "%s: DEBUG: %s\n", card->name, card->msgbuf); + break; + + case 0xff: + spin_unlock_irqrestore(&card->lock, flags); + printk(KERN_ERR "%s: card removed ?\n", card->name); + return IRQ_NONE; + default: + spin_unlock_irqrestore(&card->lock, flags); + printk(KERN_ERR "%s: b1_interrupt: 0x%x ???\n", + card->name, b1cmd); + return IRQ_HANDLED; + } + return IRQ_HANDLED; +} + +/* ------------------------------------------------------------- */ +int b1ctl_read_proc(char *page, char **start, off_t off, + int count, int *eof, struct capi_ctr *ctrl) +{ + avmctrl_info *cinfo = (avmctrl_info *)(ctrl->driverdata); + avmcard *card = cinfo->card; + u8 flag; + int len = 0; + char *s; + + len += sprintf(page+len, "%-16s %s\n", "name", card->name); + len += sprintf(page+len, "%-16s 0x%x\n", "io", card->port); + len += sprintf(page+len, "%-16s %d\n", "irq", card->irq); + switch (card->cardtype) { + case avm_b1isa: s = "B1 ISA"; break; + case avm_b1pci: s = "B1 PCI"; break; + case avm_b1pcmcia: s = "B1 PCMCIA"; break; + case avm_m1: s = "M1"; break; + case avm_m2: s = "M2"; break; + case avm_t1isa: s = "T1 ISA (HEMA)"; break; + case avm_t1pci: s = "T1 PCI"; break; + case avm_c4: s = "C4"; break; + case avm_c2: s = "C2"; break; + default: s = "???"; break; + } + len += sprintf(page+len, "%-16s %s\n", "type", s); + if (card->cardtype == avm_t1isa) + len += sprintf(page+len, "%-16s %d\n", "cardnr", card->cardnr); + if ((s = cinfo->version[VER_DRIVER]) != 0) + len += sprintf(page+len, "%-16s %s\n", "ver_driver", s); + if ((s = cinfo->version[VER_CARDTYPE]) != 0) + len += sprintf(page+len, "%-16s %s\n", "ver_cardtype", s); + if ((s = cinfo->version[VER_SERIAL]) != 0) + len += sprintf(page+len, "%-16s %s\n", "ver_serial", s); + + if (card->cardtype != avm_m1) { + flag = ((u8 *)(ctrl->profile.manu))[3]; + if (flag) + len += sprintf(page+len, "%-16s%s%s%s%s%s%s%s\n", + "protocol", + (flag & 0x01) ? " DSS1" : "", + (flag & 0x02) ? " CT1" : "", + (flag & 0x04) ? " VN3" : "", + (flag & 0x08) ? " NI1" : "", + (flag & 0x10) ? " AUSTEL" : "", + (flag & 0x20) ? " ESS" : "", + (flag & 0x40) ? " 1TR6" : "" + ); + } + if (card->cardtype != avm_m1) { + flag = ((u8 *)(ctrl->profile.manu))[5]; + if (flag) + len += sprintf(page+len, "%-16s%s%s%s%s\n", + "linetype", + (flag & 0x01) ? " point to point" : "", + (flag & 0x02) ? " point to multipoint" : "", + (flag & 0x08) ? " leased line without D-channel" : "", + (flag & 0x04) ? " leased line with D-channel" : "" + ); + } + len += sprintf(page+len, "%-16s %s\n", "cardname", cinfo->cardname); + + if (off+count >= len) + *eof = 1; + if (len < off) + return 0; + *start = page + off; + return ((count < len-off) ? count : len-off); +} + +/* ------------------------------------------------------------- */ + +#ifdef CONFIG_PCI + +avmcard_dmainfo * +avmcard_dma_alloc(char *name, struct pci_dev *pdev, long rsize, long ssize) +{ + avmcard_dmainfo *p; + void *buf; + + p = kmalloc(sizeof(avmcard_dmainfo), GFP_KERNEL); + if (!p) { + printk(KERN_WARNING "%s: no memory.\n", name); + goto err; + } + memset(p, 0, sizeof(avmcard_dmainfo)); + + p->recvbuf.size = rsize; + buf = pci_alloc_consistent(pdev, rsize, &p->recvbuf.dmaaddr); + if (!buf) { + printk(KERN_WARNING "%s: allocation of receive dma buffer failed.\n", name); + goto err_kfree; + } + p->recvbuf.dmabuf = buf; + + p->sendbuf.size = ssize; + buf = pci_alloc_consistent(pdev, ssize, &p->sendbuf.dmaaddr); + if (!buf) { + printk(KERN_WARNING "%s: allocation of send dma buffer failed.\n", name); + goto err_free_consistent; + } + + p->sendbuf.dmabuf = buf; + skb_queue_head_init(&p->send_queue); + + return p; + + err_free_consistent: + pci_free_consistent(p->pcidev, p->recvbuf.size, + p->recvbuf.dmabuf, p->recvbuf.dmaaddr); + err_kfree: + kfree(p); + err: + return NULL; +} + +void avmcard_dma_free(avmcard_dmainfo *p) +{ + pci_free_consistent(p->pcidev, p->recvbuf.size, + p->recvbuf.dmabuf, p->recvbuf.dmaaddr); + pci_free_consistent(p->pcidev, p->sendbuf.size, + p->sendbuf.dmabuf, p->sendbuf.dmaaddr); + skb_queue_purge(&p->send_queue); + kfree(p); +} + +EXPORT_SYMBOL(avmcard_dma_alloc); +EXPORT_SYMBOL(avmcard_dma_free); + +#endif + +EXPORT_SYMBOL(b1_irq_table); + +EXPORT_SYMBOL(b1_alloc_card); +EXPORT_SYMBOL(b1_free_card); +EXPORT_SYMBOL(b1_detect); +EXPORT_SYMBOL(b1_getrevision); +EXPORT_SYMBOL(b1_load_t4file); +EXPORT_SYMBOL(b1_load_config); +EXPORT_SYMBOL(b1_loaded); +EXPORT_SYMBOL(b1_load_firmware); +EXPORT_SYMBOL(b1_reset_ctr); +EXPORT_SYMBOL(b1_register_appl); +EXPORT_SYMBOL(b1_release_appl); +EXPORT_SYMBOL(b1_send_message); + +EXPORT_SYMBOL(b1_parse_version); +EXPORT_SYMBOL(b1_interrupt); + +EXPORT_SYMBOL(b1ctl_read_proc); + +static int __init b1_init(void) +{ + char *p; + char rev[32]; + + if ((p = strchr(revision, ':')) != 0 && p[1]) { + strlcpy(rev, p + 2, 32); + if ((p = strchr(rev, '$')) != 0 && p > rev) + *(p-1) = 0; + } else + strcpy(rev, "1.0"); + + printk(KERN_INFO "b1: revision %s\n", rev); + + return 0; +} + +static void __exit b1_exit(void) +{ +} + +module_init(b1_init); +module_exit(b1_exit); diff --git a/drivers/isdn/hardware/avm/b1dma.c b/drivers/isdn/hardware/avm/b1dma.c new file mode 100644 index 000000000000..55bed00ca865 --- /dev/null +++ b/drivers/isdn/hardware/avm/b1dma.c @@ -0,0 +1,980 @@ +/* $Id: b1dma.c,v 1.1.2.3 2004/02/10 01:07:12 keil Exp $ + * + * Common module for AVM B1 cards that support dma with AMCC + * + * Copyright 2000 by Carsten Paeth <calle@calle.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/skbuff.h> +#include <linux/delay.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/capi.h> +#include <linux/kernelcapi.h> +#include <asm/io.h> +#include <linux/init.h> +#include <asm/uaccess.h> +#include <linux/netdevice.h> +#include <linux/isdn/capilli.h> +#include "avmcard.h" +#include <linux/isdn/capicmd.h> +#include <linux/isdn/capiutil.h> + +static char *revision = "$Revision: 1.1.2.3 $"; + +#undef CONFIG_B1DMA_DEBUG + +/* ------------------------------------------------------------- */ + +MODULE_DESCRIPTION("CAPI4Linux: DMA support for active AVM cards"); +MODULE_AUTHOR("Carsten Paeth"); +MODULE_LICENSE("GPL"); + +static int suppress_pollack = 0; +MODULE_PARM(suppress_pollack, "0-1i"); + +/* ------------------------------------------------------------- */ + +static void b1dma_dispatch_tx(avmcard *card); + +/* ------------------------------------------------------------- */ + +/* S5933 */ + +#define AMCC_RXPTR 0x24 +#define AMCC_RXLEN 0x28 +#define AMCC_TXPTR 0x2c +#define AMCC_TXLEN 0x30 + +#define AMCC_INTCSR 0x38 +# define EN_READ_TC_INT 0x00008000L +# define EN_WRITE_TC_INT 0x00004000L +# define EN_TX_TC_INT EN_READ_TC_INT +# define EN_RX_TC_INT EN_WRITE_TC_INT +# define AVM_FLAG 0x30000000L + +# define ANY_S5933_INT 0x00800000L +# define READ_TC_INT 0x00080000L +# define WRITE_TC_INT 0x00040000L +# define TX_TC_INT READ_TC_INT +# define RX_TC_INT WRITE_TC_INT +# define MASTER_ABORT_INT 0x00100000L +# define TARGET_ABORT_INT 0x00200000L +# define BUS_MASTER_INT 0x00200000L +# define ALL_INT 0x000C0000L + +#define AMCC_MCSR 0x3c +# define A2P_HI_PRIORITY 0x00000100L +# define EN_A2P_TRANSFERS 0x00000400L +# define P2A_HI_PRIORITY 0x00001000L +# define EN_P2A_TRANSFERS 0x00004000L +# define RESET_A2P_FLAGS 0x04000000L +# define RESET_P2A_FLAGS 0x02000000L + +/* ------------------------------------------------------------- */ + +static inline void b1dma_writel(avmcard *card, u32 value, int off) +{ + writel(value, card->mbase + off); +} + +static inline u32 b1dma_readl(avmcard *card, int off) +{ + return readl(card->mbase + off); +} + +/* ------------------------------------------------------------- */ + +static inline int b1dma_tx_empty(unsigned int port) +{ + return inb(port + 0x03) & 0x1; +} + +static inline int b1dma_rx_full(unsigned int port) +{ + return inb(port + 0x02) & 0x1; +} + +static int b1dma_tolink(avmcard *card, void *buf, unsigned int len) +{ + unsigned long stop = jiffies + 1 * HZ; /* maximum wait time 1 sec */ + unsigned char *s = (unsigned char *)buf; + while (len--) { + while ( !b1dma_tx_empty(card->port) + && time_before(jiffies, stop)); + if (!b1dma_tx_empty(card->port)) + return -1; + t1outp(card->port, 0x01, *s++); + } + return 0; +} + +static int b1dma_fromlink(avmcard *card, void *buf, unsigned int len) +{ + unsigned long stop = jiffies + 1 * HZ; /* maximum wait time 1 sec */ + unsigned char *s = (unsigned char *)buf; + while (len--) { + while ( !b1dma_rx_full(card->port) + && time_before(jiffies, stop)); + if (!b1dma_rx_full(card->port)) + return -1; + *s++ = t1inp(card->port, 0x00); + } + return 0; +} + +static int WriteReg(avmcard *card, u32 reg, u8 val) +{ + u8 cmd = 0x00; + if ( b1dma_tolink(card, &cmd, 1) == 0 + && b1dma_tolink(card, ®, 4) == 0) { + u32 tmp = val; + return b1dma_tolink(card, &tmp, 4); + } + return -1; +} + +static u8 ReadReg(avmcard *card, u32 reg) +{ + u8 cmd = 0x01; + if ( b1dma_tolink(card, &cmd, 1) == 0 + && b1dma_tolink(card, ®, 4) == 0) { + u32 tmp; + if (b1dma_fromlink(card, &tmp, 4) == 0) + return (u8)tmp; + } + return 0xff; +} + +/* ------------------------------------------------------------- */ + +static inline void _put_byte(void **pp, u8 val) +{ + u8 *s = *pp; + *s++ = val; + *pp = s; +} + +static inline void _put_word(void **pp, u32 val) +{ + u8 *s = *pp; + *s++ = val & 0xff; + *s++ = (val >> 8) & 0xff; + *s++ = (val >> 16) & 0xff; + *s++ = (val >> 24) & 0xff; + *pp = s; +} + +static inline void _put_slice(void **pp, unsigned char *dp, unsigned int len) +{ + unsigned i = len; + _put_word(pp, i); + while (i-- > 0) + _put_byte(pp, *dp++); +} + +static inline u8 _get_byte(void **pp) +{ + u8 *s = *pp; + u8 val; + val = *s++; + *pp = s; + return val; +} + +static inline u32 _get_word(void **pp) +{ + u8 *s = *pp; + u32 val; + val = *s++; + val |= (*s++ << 8); + val |= (*s++ << 16); + val |= (*s++ << 24); + *pp = s; + return val; +} + +static inline u32 _get_slice(void **pp, unsigned char *dp) +{ + unsigned int len, i; + + len = i = _get_word(pp); + while (i-- > 0) *dp++ = _get_byte(pp); + return len; +} + +/* ------------------------------------------------------------- */ + +void b1dma_reset(avmcard *card) +{ + card->csr = 0x0; + b1dma_writel(card, card->csr, AMCC_INTCSR); + b1dma_writel(card, 0, AMCC_MCSR); + b1dma_writel(card, 0, AMCC_RXLEN); + b1dma_writel(card, 0, AMCC_TXLEN); + + t1outp(card->port, 0x10, 0x00); + t1outp(card->port, 0x07, 0x00); + + b1dma_writel(card, 0, AMCC_MCSR); + mdelay(10); + b1dma_writel(card, 0x0f000000, AMCC_MCSR); /* reset all */ + mdelay(10); + b1dma_writel(card, 0, AMCC_MCSR); + if (card->cardtype == avm_t1pci) + mdelay(42); + else + mdelay(10); +} + +/* ------------------------------------------------------------- */ + +static int b1dma_detect(avmcard *card) +{ + b1dma_writel(card, 0, AMCC_MCSR); + mdelay(10); + b1dma_writel(card, 0x0f000000, AMCC_MCSR); /* reset all */ + mdelay(10); + b1dma_writel(card, 0, AMCC_MCSR); + mdelay(42); + + b1dma_writel(card, 0, AMCC_RXLEN); + b1dma_writel(card, 0, AMCC_TXLEN); + card->csr = 0x0; + b1dma_writel(card, card->csr, AMCC_INTCSR); + + if (b1dma_readl(card, AMCC_MCSR) != 0x000000E6) + return 1; + + b1dma_writel(card, 0xffffffff, AMCC_RXPTR); + b1dma_writel(card, 0xffffffff, AMCC_TXPTR); + if ( b1dma_readl(card, AMCC_RXPTR) != 0xfffffffc + || b1dma_readl(card, AMCC_TXPTR) != 0xfffffffc) + return 2; + + b1dma_writel(card, 0x0, AMCC_RXPTR); + b1dma_writel(card, 0x0, AMCC_TXPTR); + if ( b1dma_readl(card, AMCC_RXPTR) != 0x0 + || b1dma_readl(card, AMCC_TXPTR) != 0x0) + return 3; + + t1outp(card->port, 0x10, 0x00); + t1outp(card->port, 0x07, 0x00); + + t1outp(card->port, 0x02, 0x02); + t1outp(card->port, 0x03, 0x02); + + if ( (t1inp(card->port, 0x02) & 0xFE) != 0x02 + || t1inp(card->port, 0x3) != 0x03) + return 4; + + t1outp(card->port, 0x02, 0x00); + t1outp(card->port, 0x03, 0x00); + + if ( (t1inp(card->port, 0x02) & 0xFE) != 0x00 + || t1inp(card->port, 0x3) != 0x01) + return 5; + + return 0; +} + +int t1pci_detect(avmcard *card) +{ + int ret; + + if ((ret = b1dma_detect(card)) != 0) + return ret; + + /* Transputer test */ + + if ( WriteReg(card, 0x80001000, 0x11) != 0 + || WriteReg(card, 0x80101000, 0x22) != 0 + || WriteReg(card, 0x80201000, 0x33) != 0 + || WriteReg(card, 0x80301000, 0x44) != 0) + return 6; + + if ( ReadReg(card, 0x80001000) != 0x11 + || ReadReg(card, 0x80101000) != 0x22 + || ReadReg(card, 0x80201000) != 0x33 + || ReadReg(card, 0x80301000) != 0x44) + return 7; + + if ( WriteReg(card, 0x80001000, 0x55) != 0 + || WriteReg(card, 0x80101000, 0x66) != 0 + || WriteReg(card, 0x80201000, 0x77) != 0 + || WriteReg(card, 0x80301000, 0x88) != 0) + return 8; + + if ( ReadReg(card, 0x80001000) != 0x55 + || ReadReg(card, 0x80101000) != 0x66 + || ReadReg(card, 0x80201000) != 0x77 + || ReadReg(card, 0x80301000) != 0x88) + return 9; + + return 0; +} + +int b1pciv4_detect(avmcard *card) +{ + int ret, i; + + if ((ret = b1dma_detect(card)) != 0) + return ret; + + for (i=0; i < 5 ; i++) { + if (WriteReg(card, 0x80A00000, 0x21) != 0) + return 6; + if ((ReadReg(card, 0x80A00000) & 0x01) != 0x01) + return 7; + } + for (i=0; i < 5 ; i++) { + if (WriteReg(card, 0x80A00000, 0x20) != 0) + return 8; + if ((ReadReg(card, 0x80A00000) & 0x01) != 0x00) + return 9; + } + + return 0; +} + +static void b1dma_queue_tx(avmcard *card, struct sk_buff *skb) +{ + unsigned long flags; + + spin_lock_irqsave(&card->lock, flags); + + skb_queue_tail(&card->dma->send_queue, skb); + + if (!(card->csr & EN_TX_TC_INT)) { + b1dma_dispatch_tx(card); + b1dma_writel(card, card->csr, AMCC_INTCSR); + } + + spin_unlock_irqrestore(&card->lock, flags); +} + +/* ------------------------------------------------------------- */ + +static void b1dma_dispatch_tx(avmcard *card) +{ + avmcard_dmainfo *dma = card->dma; + struct sk_buff *skb; + u8 cmd, subcmd; + u16 len; + u32 txlen; + void *p; + + skb = skb_dequeue(&dma->send_queue); + + len = CAPIMSG_LEN(skb->data); + + if (len) { + cmd = CAPIMSG_COMMAND(skb->data); + subcmd = CAPIMSG_SUBCOMMAND(skb->data); + + p = dma->sendbuf.dmabuf; + + if (CAPICMD(cmd, subcmd) == CAPI_DATA_B3_REQ) { + u16 dlen = CAPIMSG_DATALEN(skb->data); + _put_byte(&p, SEND_DATA_B3_REQ); + _put_slice(&p, skb->data, len); + _put_slice(&p, skb->data + len, dlen); + } else { + _put_byte(&p, SEND_MESSAGE); + _put_slice(&p, skb->data, len); + } + txlen = (u8 *)p - (u8 *)dma->sendbuf.dmabuf; +#ifdef CONFIG_B1DMA_DEBUG + printk(KERN_DEBUG "tx: put msg len=%d\n", txlen); +#endif + } else { + txlen = skb->len-2; +#ifdef CONFIG_B1DMA_POLLDEBUG + if (skb->data[2] == SEND_POLLACK) + printk(KERN_INFO "%s: send ack\n", card->name); +#endif +#ifdef CONFIG_B1DMA_DEBUG + printk(KERN_DEBUG "tx: put 0x%x len=%d\n", + skb->data[2], txlen); +#endif + memcpy(dma->sendbuf.dmabuf, skb->data+2, skb->len-2); + } + txlen = (txlen + 3) & ~3; + + b1dma_writel(card, dma->sendbuf.dmaaddr, AMCC_TXPTR); + b1dma_writel(card, txlen, AMCC_TXLEN); + + card->csr |= EN_TX_TC_INT; + + dev_kfree_skb_any(skb); +} + +/* ------------------------------------------------------------- */ + +static void queue_pollack(avmcard *card) +{ + struct sk_buff *skb; + void *p; + + skb = alloc_skb(3, GFP_ATOMIC); + if (!skb) { + printk(KERN_CRIT "%s: no memory, lost poll ack\n", + card->name); + return; + } + p = skb->data; + _put_byte(&p, 0); + _put_byte(&p, 0); + _put_byte(&p, SEND_POLLACK); + skb_put(skb, (u8 *)p - (u8 *)skb->data); + + b1dma_queue_tx(card, skb); +} + +/* ------------------------------------------------------------- */ + +static void b1dma_handle_rx(avmcard *card) +{ + avmctrl_info *cinfo = &card->ctrlinfo[0]; + avmcard_dmainfo *dma = card->dma; + struct capi_ctr *ctrl = &cinfo->capi_ctrl; + struct sk_buff *skb; + void *p = dma->recvbuf.dmabuf+4; + u32 ApplId, MsgLen, DataB3Len, NCCI, WindowSize; + u8 b1cmd = _get_byte(&p); + +#ifdef CONFIG_B1DMA_DEBUG + printk(KERN_DEBUG "rx: 0x%x %lu\n", b1cmd, (unsigned long)dma->recvlen); +#endif + + switch (b1cmd) { + case RECEIVE_DATA_B3_IND: + + ApplId = (unsigned) _get_word(&p); + MsgLen = _get_slice(&p, card->msgbuf); + DataB3Len = _get_slice(&p, card->databuf); + + if (MsgLen < 30) { /* not CAPI 64Bit */ + memset(card->msgbuf+MsgLen, 0, 30-MsgLen); + MsgLen = 30; + CAPIMSG_SETLEN(card->msgbuf, 30); + } + if (!(skb = alloc_skb(DataB3Len+MsgLen, GFP_ATOMIC))) { + printk(KERN_ERR "%s: incoming packet dropped\n", + card->name); + } else { + memcpy(skb_put(skb, MsgLen), card->msgbuf, MsgLen); + memcpy(skb_put(skb, DataB3Len), card->databuf, DataB3Len); + capi_ctr_handle_message(ctrl, ApplId, skb); + } + break; + + case RECEIVE_MESSAGE: + + ApplId = (unsigned) _get_word(&p); + MsgLen = _get_slice(&p, card->msgbuf); + if (!(skb = alloc_skb(MsgLen, GFP_ATOMIC))) { + printk(KERN_ERR "%s: incoming packet dropped\n", + card->name); + } else { + memcpy(skb_put(skb, MsgLen), card->msgbuf, MsgLen); + if (CAPIMSG_CMD(skb->data) == CAPI_DATA_B3_CONF) + capilib_data_b3_conf(&cinfo->ncci_head, ApplId, + CAPIMSG_NCCI(skb->data), + CAPIMSG_MSGID(skb->data)); + + capi_ctr_handle_message(ctrl, ApplId, skb); + } + break; + + case RECEIVE_NEW_NCCI: + + ApplId = _get_word(&p); + NCCI = _get_word(&p); + WindowSize = _get_word(&p); + + capilib_new_ncci(&cinfo->ncci_head, ApplId, NCCI, WindowSize); + + break; + + case RECEIVE_FREE_NCCI: + + ApplId = _get_word(&p); + NCCI = _get_word(&p); + + if (NCCI != 0xffffffff) + capilib_free_ncci(&cinfo->ncci_head, ApplId, NCCI); + + break; + + case RECEIVE_START: +#ifdef CONFIG_B1DMA_POLLDEBUG + printk(KERN_INFO "%s: receive poll\n", card->name); +#endif + if (!suppress_pollack) + queue_pollack(card); + capi_ctr_resume_output(ctrl); + break; + + case RECEIVE_STOP: + capi_ctr_suspend_output(ctrl); + break; + + case RECEIVE_INIT: + + cinfo->versionlen = _get_slice(&p, cinfo->versionbuf); + b1_parse_version(cinfo); + printk(KERN_INFO "%s: %s-card (%s) now active\n", + card->name, + cinfo->version[VER_CARDTYPE], + cinfo->version[VER_DRIVER]); + capi_ctr_ready(ctrl); + break; + + case RECEIVE_TASK_READY: + ApplId = (unsigned) _get_word(&p); + MsgLen = _get_slice(&p, card->msgbuf); + card->msgbuf[MsgLen] = 0; + while ( MsgLen > 0 + && ( card->msgbuf[MsgLen-1] == '\n' + || card->msgbuf[MsgLen-1] == '\r')) { + card->msgbuf[MsgLen-1] = 0; + MsgLen--; + } + printk(KERN_INFO "%s: task %d \"%s\" ready.\n", + card->name, ApplId, card->msgbuf); + break; + + case RECEIVE_DEBUGMSG: + MsgLen = _get_slice(&p, card->msgbuf); + card->msgbuf[MsgLen] = 0; + while ( MsgLen > 0 + && ( card->msgbuf[MsgLen-1] == '\n' + || card->msgbuf[MsgLen-1] == '\r')) { + card->msgbuf[MsgLen-1] = 0; + MsgLen--; + } + printk(KERN_INFO "%s: DEBUG: %s\n", card->name, card->msgbuf); + break; + + default: + printk(KERN_ERR "%s: b1dma_interrupt: 0x%x ???\n", + card->name, b1cmd); + return; + } +} + +/* ------------------------------------------------------------- */ + +static void b1dma_handle_interrupt(avmcard *card) +{ + u32 status; + u32 newcsr; + + spin_lock(&card->lock); + + status = b1dma_readl(card, AMCC_INTCSR); + if ((status & ANY_S5933_INT) == 0) { + spin_unlock(&card->lock); + return; + } + + newcsr = card->csr | (status & ALL_INT); + if (status & TX_TC_INT) newcsr &= ~EN_TX_TC_INT; + if (status & RX_TC_INT) newcsr &= ~EN_RX_TC_INT; + b1dma_writel(card, newcsr, AMCC_INTCSR); + + if ((status & RX_TC_INT) != 0) { + struct avmcard_dmainfo *dma = card->dma; + u32 rxlen; + if (card->dma->recvlen == 0) { + rxlen = b1dma_readl(card, AMCC_RXLEN); + if (rxlen == 0) { + dma->recvlen = *((u32 *)dma->recvbuf.dmabuf); + rxlen = (dma->recvlen + 3) & ~3; + b1dma_writel(card, dma->recvbuf.dmaaddr+4, AMCC_RXPTR); + b1dma_writel(card, rxlen, AMCC_RXLEN); +#ifdef CONFIG_B1DMA_DEBUG + } else { + printk(KERN_ERR "%s: rx not complete (%d).\n", + card->name, rxlen); +#endif + } + } else { + spin_unlock(&card->lock); + b1dma_handle_rx(card); + dma->recvlen = 0; + spin_lock(&card->lock); + b1dma_writel(card, dma->recvbuf.dmaaddr, AMCC_RXPTR); + b1dma_writel(card, 4, AMCC_RXLEN); + } + } + + if ((status & TX_TC_INT) != 0) { + if (skb_queue_empty(&card->dma->send_queue)) + card->csr &= ~EN_TX_TC_INT; + else + b1dma_dispatch_tx(card); + } + b1dma_writel(card, card->csr, AMCC_INTCSR); + + spin_unlock(&card->lock); +} + +irqreturn_t b1dma_interrupt(int interrupt, void *devptr, struct pt_regs *regs) +{ + avmcard *card = devptr; + + b1dma_handle_interrupt(card); + return IRQ_HANDLED; +} + +/* ------------------------------------------------------------- */ + +static int b1dma_loaded(avmcard *card) +{ + unsigned long stop; + unsigned char ans; + unsigned long tout = 2; + unsigned int base = card->port; + + for (stop = jiffies + tout * HZ; time_before(jiffies, stop);) { + if (b1_tx_empty(base)) + break; + } + if (!b1_tx_empty(base)) { + printk(KERN_ERR "%s: b1dma_loaded: tx err, corrupted t4 file ?\n", + card->name); + return 0; + } + b1_put_byte(base, SEND_POLLACK); + for (stop = jiffies + tout * HZ; time_before(jiffies, stop);) { + if (b1_rx_full(base)) { + if ((ans = b1_get_byte(base)) == RECEIVE_POLLDWORD) { + return 1; + } + printk(KERN_ERR "%s: b1dma_loaded: got 0x%x, firmware not running in dword mode\n", card->name, ans); + return 0; + } + } + printk(KERN_ERR "%s: b1dma_loaded: firmware not running\n", card->name); + return 0; +} + +/* ------------------------------------------------------------- */ + +static void b1dma_send_init(avmcard *card) +{ + struct sk_buff *skb; + void *p; + + skb = alloc_skb(15, GFP_ATOMIC); + if (!skb) { + printk(KERN_CRIT "%s: no memory, lost register appl.\n", + card->name); + return; + } + p = skb->data; + _put_byte(&p, 0); + _put_byte(&p, 0); + _put_byte(&p, SEND_INIT); + _put_word(&p, CAPI_MAXAPPL); + _put_word(&p, AVM_NCCI_PER_CHANNEL*30); + _put_word(&p, card->cardnr - 1); + skb_put(skb, (u8 *)p - (u8 *)skb->data); + + b1dma_queue_tx(card, skb); +} + +int b1dma_load_firmware(struct capi_ctr *ctrl, capiloaddata *data) +{ + avmctrl_info *cinfo = (avmctrl_info *)(ctrl->driverdata); + avmcard *card = cinfo->card; + int retval; + + b1dma_reset(card); + + if ((retval = b1_load_t4file(card, &data->firmware))) { + b1dma_reset(card); + printk(KERN_ERR "%s: failed to load t4file!!\n", + card->name); + return retval; + } + + if (data->configuration.len > 0 && data->configuration.data) { + if ((retval = b1_load_config(card, &data->configuration))) { + b1dma_reset(card); + printk(KERN_ERR "%s: failed to load config!!\n", + card->name); + return retval; + } + } + + if (!b1dma_loaded(card)) { + b1dma_reset(card); + printk(KERN_ERR "%s: failed to load t4file.\n", card->name); + return -EIO; + } + + card->csr = AVM_FLAG; + b1dma_writel(card, card->csr, AMCC_INTCSR); + b1dma_writel(card, EN_A2P_TRANSFERS|EN_P2A_TRANSFERS|A2P_HI_PRIORITY| + P2A_HI_PRIORITY|RESET_A2P_FLAGS|RESET_P2A_FLAGS, + AMCC_MCSR); + t1outp(card->port, 0x07, 0x30); + t1outp(card->port, 0x10, 0xF0); + + card->dma->recvlen = 0; + b1dma_writel(card, card->dma->recvbuf.dmaaddr, AMCC_RXPTR); + b1dma_writel(card, 4, AMCC_RXLEN); + card->csr |= EN_RX_TC_INT; + b1dma_writel(card, card->csr, AMCC_INTCSR); + + b1dma_send_init(card); + + return 0; +} + +void b1dma_reset_ctr(struct capi_ctr *ctrl) +{ + avmctrl_info *cinfo = (avmctrl_info *)(ctrl->driverdata); + avmcard *card = cinfo->card; + unsigned long flags; + + spin_lock_irqsave(&card->lock, flags); + b1dma_reset(card); + spin_unlock_irqrestore(&card->lock, flags); + + memset(cinfo->version, 0, sizeof(cinfo->version)); + capilib_release(&cinfo->ncci_head); + capi_ctr_reseted(ctrl); +} + +/* ------------------------------------------------------------- */ + +void b1dma_register_appl(struct capi_ctr *ctrl, + u16 appl, + capi_register_params *rp) +{ + avmctrl_info *cinfo = (avmctrl_info *)(ctrl->driverdata); + avmcard *card = cinfo->card; + struct sk_buff *skb; + int want = rp->level3cnt; + int nconn; + void *p; + + if (want > 0) nconn = want; + else nconn = ctrl->profile.nbchannel * -want; + if (nconn == 0) nconn = ctrl->profile.nbchannel; + + skb = alloc_skb(23, GFP_ATOMIC); + if (!skb) { + printk(KERN_CRIT "%s: no memory, lost register appl.\n", + card->name); + return; + } + p = skb->data; + _put_byte(&p, 0); + _put_byte(&p, 0); + _put_byte(&p, SEND_REGISTER); + _put_word(&p, appl); + _put_word(&p, 1024 * (nconn+1)); + _put_word(&p, nconn); + _put_word(&p, rp->datablkcnt); + _put_word(&p, rp->datablklen); + skb_put(skb, (u8 *)p - (u8 *)skb->data); + + b1dma_queue_tx(card, skb); +} + +/* ------------------------------------------------------------- */ + +void b1dma_release_appl(struct capi_ctr *ctrl, u16 appl) +{ + avmctrl_info *cinfo = (avmctrl_info *)(ctrl->driverdata); + avmcard *card = cinfo->card; + struct sk_buff *skb; + void *p; + + capilib_release_appl(&cinfo->ncci_head, appl); + + skb = alloc_skb(7, GFP_ATOMIC); + if (!skb) { + printk(KERN_CRIT "%s: no memory, lost release appl.\n", + card->name); + return; + } + p = skb->data; + _put_byte(&p, 0); + _put_byte(&p, 0); + _put_byte(&p, SEND_RELEASE); + _put_word(&p, appl); + + skb_put(skb, (u8 *)p - (u8 *)skb->data); + + b1dma_queue_tx(card, skb); +} + +/* ------------------------------------------------------------- */ + +u16 b1dma_send_message(struct capi_ctr *ctrl, struct sk_buff *skb) +{ + avmctrl_info *cinfo = (avmctrl_info *)(ctrl->driverdata); + avmcard *card = cinfo->card; + u16 retval = CAPI_NOERROR; + + if (CAPIMSG_CMD(skb->data) == CAPI_DATA_B3_REQ) { + retval = capilib_data_b3_req(&cinfo->ncci_head, + CAPIMSG_APPID(skb->data), + CAPIMSG_NCCI(skb->data), + CAPIMSG_MSGID(skb->data)); + } + if (retval == CAPI_NOERROR) + b1dma_queue_tx(card, skb); + + return retval; +} + +/* ------------------------------------------------------------- */ + +int b1dmactl_read_proc(char *page, char **start, off_t off, + int count, int *eof, struct capi_ctr *ctrl) +{ + avmctrl_info *cinfo = (avmctrl_info *)(ctrl->driverdata); + avmcard *card = cinfo->card; + u8 flag; + int len = 0; + char *s; + u32 txoff, txlen, rxoff, rxlen, csr; + unsigned long flags; + + len += sprintf(page+len, "%-16s %s\n", "name", card->name); + len += sprintf(page+len, "%-16s 0x%x\n", "io", card->port); + len += sprintf(page+len, "%-16s %d\n", "irq", card->irq); + len += sprintf(page+len, "%-16s 0x%lx\n", "membase", card->membase); + switch (card->cardtype) { + case avm_b1isa: s = "B1 ISA"; break; + case avm_b1pci: s = "B1 PCI"; break; + case avm_b1pcmcia: s = "B1 PCMCIA"; break; + case avm_m1: s = "M1"; break; + case avm_m2: s = "M2"; break; + case avm_t1isa: s = "T1 ISA (HEMA)"; break; + case avm_t1pci: s = "T1 PCI"; break; + case avm_c4: s = "C4"; break; + case avm_c2: s = "C2"; break; + default: s = "???"; break; + } + len += sprintf(page+len, "%-16s %s\n", "type", s); + if ((s = cinfo->version[VER_DRIVER]) != 0) + len += sprintf(page+len, "%-16s %s\n", "ver_driver", s); + if ((s = cinfo->version[VER_CARDTYPE]) != 0) + len += sprintf(page+len, "%-16s %s\n", "ver_cardtype", s); + if ((s = cinfo->version[VER_SERIAL]) != 0) + len += sprintf(page+len, "%-16s %s\n", "ver_serial", s); + + if (card->cardtype != avm_m1) { + flag = ((u8 *)(ctrl->profile.manu))[3]; + if (flag) + len += sprintf(page+len, "%-16s%s%s%s%s%s%s%s\n", + "protocol", + (flag & 0x01) ? " DSS1" : "", + (flag & 0x02) ? " CT1" : "", + (flag & 0x04) ? " VN3" : "", + (flag & 0x08) ? " NI1" : "", + (flag & 0x10) ? " AUSTEL" : "", + (flag & 0x20) ? " ESS" : "", + (flag & 0x40) ? " 1TR6" : "" + ); + } + if (card->cardtype != avm_m1) { + flag = ((u8 *)(ctrl->profile.manu))[5]; + if (flag) + len += sprintf(page+len, "%-16s%s%s%s%s\n", + "linetype", + (flag & 0x01) ? " point to point" : "", + (flag & 0x02) ? " point to multipoint" : "", + (flag & 0x08) ? " leased line without D-channel" : "", + (flag & 0x04) ? " leased line with D-channel" : "" + ); + } + len += sprintf(page+len, "%-16s %s\n", "cardname", cinfo->cardname); + + + spin_lock_irqsave(&card->lock, flags); + + txoff = (dma_addr_t)b1dma_readl(card, AMCC_TXPTR)-card->dma->sendbuf.dmaaddr; + txlen = b1dma_readl(card, AMCC_TXLEN); + + rxoff = (dma_addr_t)b1dma_readl(card, AMCC_RXPTR)-card->dma->recvbuf.dmaaddr; + rxlen = b1dma_readl(card, AMCC_RXLEN); + + csr = b1dma_readl(card, AMCC_INTCSR); + + spin_unlock_irqrestore(&card->lock, flags); + + len += sprintf(page+len, "%-16s 0x%lx\n", + "csr (cached)", (unsigned long)card->csr); + len += sprintf(page+len, "%-16s 0x%lx\n", + "csr", (unsigned long)csr); + len += sprintf(page+len, "%-16s %lu\n", + "txoff", (unsigned long)txoff); + len += sprintf(page+len, "%-16s %lu\n", + "txlen", (unsigned long)txlen); + len += sprintf(page+len, "%-16s %lu\n", + "rxoff", (unsigned long)rxoff); + len += sprintf(page+len, "%-16s %lu\n", + "rxlen", (unsigned long)rxlen); + + if (off+count >= len) + *eof = 1; + if (len < off) + return 0; + *start = page + off; + return ((count < len-off) ? count : len-off); +} + +/* ------------------------------------------------------------- */ + +EXPORT_SYMBOL(b1dma_reset); +EXPORT_SYMBOL(t1pci_detect); +EXPORT_SYMBOL(b1pciv4_detect); +EXPORT_SYMBOL(b1dma_interrupt); + +EXPORT_SYMBOL(b1dma_load_firmware); +EXPORT_SYMBOL(b1dma_reset_ctr); +EXPORT_SYMBOL(b1dma_register_appl); +EXPORT_SYMBOL(b1dma_release_appl); +EXPORT_SYMBOL(b1dma_send_message); +EXPORT_SYMBOL(b1dmactl_read_proc); + +int b1dma_init(void) +{ + char *p; + char rev[32]; + + if ((p = strchr(revision, ':')) != 0 && p[1]) { + strlcpy(rev, p + 2, sizeof(rev)); + if ((p = strchr(rev, '$')) != 0 && p > rev) + *(p-1) = 0; + } else + strcpy(rev, "1.0"); + + printk(KERN_INFO "b1dma: revision %s\n", rev); + + return 0; +} + +void b1dma_exit(void) +{ +} + +module_init(b1dma_init); +module_exit(b1dma_exit); diff --git a/drivers/isdn/hardware/avm/b1isa.c b/drivers/isdn/hardware/avm/b1isa.c new file mode 100644 index 000000000000..38bd4dfecbd1 --- /dev/null +++ b/drivers/isdn/hardware/avm/b1isa.c @@ -0,0 +1,245 @@ +/* $Id: b1isa.c,v 1.1.2.3 2004/02/10 01:07:12 keil Exp $ + * + * Module for AVM B1 ISA-card. + * + * Copyright 1999 by Carsten Paeth <calle@calle.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/skbuff.h> +#include <linux/delay.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/capi.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <asm/io.h> +#include <linux/isdn/capicmd.h> +#include <linux/isdn/capiutil.h> +#include <linux/isdn/capilli.h> +#include "avmcard.h" + +/* ------------------------------------------------------------- */ + +static char *revision = "$Revision: 1.1.2.3 $"; + +/* ------------------------------------------------------------- */ + +MODULE_DESCRIPTION("CAPI4Linux: Driver for AVM B1 ISA card"); +MODULE_AUTHOR("Carsten Paeth"); +MODULE_LICENSE("GPL"); + +/* ------------------------------------------------------------- */ + +static void b1isa_remove(struct pci_dev *pdev) +{ + avmctrl_info *cinfo = pci_get_drvdata(pdev); + avmcard *card; + + if (!cinfo) + return; + + card = cinfo->card; + + b1_reset(card->port); + b1_reset(card->port); + + detach_capi_ctr(&cinfo->capi_ctrl); + free_irq(card->irq, card); + release_region(card->port, AVMB1_PORTLEN); + b1_free_card(card); +} + +/* ------------------------------------------------------------- */ + +static char *b1isa_procinfo(struct capi_ctr *ctrl); + +static int b1isa_probe(struct pci_dev *pdev) +{ + avmctrl_info *cinfo; + avmcard *card; + int retval; + + card = b1_alloc_card(1); + if (!card) { + printk(KERN_WARNING "b1isa: no memory.\n"); + retval = -ENOMEM; + goto err; + } + + cinfo = card->ctrlinfo; + + card->port = pci_resource_start(pdev, 0); + card->irq = pdev->irq; + card->cardtype = avm_b1isa; + sprintf(card->name, "b1isa-%x", card->port); + + if ( card->port != 0x150 && card->port != 0x250 + && card->port != 0x300 && card->port != 0x340) { + printk(KERN_WARNING "b1isa: invalid port 0x%x.\n", card->port); + retval = -EINVAL; + goto err_free; + } + if (b1_irq_table[card->irq & 0xf] == 0) { + printk(KERN_WARNING "b1isa: irq %d not valid.\n", card->irq); + retval = -EINVAL; + goto err_free; + } + if (!request_region(card->port, AVMB1_PORTLEN, card->name)) { + printk(KERN_WARNING "b1isa: ports 0x%03x-0x%03x in use.\n", + card->port, card->port + AVMB1_PORTLEN); + retval = -EBUSY; + goto err_free; + } + retval = request_irq(card->irq, b1_interrupt, 0, card->name, card); + if (retval) { + printk(KERN_ERR "b1isa: unable to get IRQ %d.\n", card->irq); + goto err_release_region; + } + b1_reset(card->port); + if ((retval = b1_detect(card->port, card->cardtype)) != 0) { + printk(KERN_NOTICE "b1isa: NO card at 0x%x (%d)\n", + card->port, retval); + retval = -ENODEV; + goto err_free_irq; + } + b1_reset(card->port); + b1_getrevision(card); + + cinfo->capi_ctrl.owner = THIS_MODULE; + cinfo->capi_ctrl.driver_name = "b1isa"; + cinfo->capi_ctrl.driverdata = cinfo; + cinfo->capi_ctrl.register_appl = b1_register_appl; + cinfo->capi_ctrl.release_appl = b1_release_appl; + cinfo->capi_ctrl.send_message = b1_send_message; + cinfo->capi_ctrl.load_firmware = b1_load_firmware; + cinfo->capi_ctrl.reset_ctr = b1_reset_ctr; + cinfo->capi_ctrl.procinfo = b1isa_procinfo; + cinfo->capi_ctrl.ctr_read_proc = b1ctl_read_proc; + strcpy(cinfo->capi_ctrl.name, card->name); + + retval = attach_capi_ctr(&cinfo->capi_ctrl); + if (retval) { + printk(KERN_ERR "b1isa: attach controller failed.\n"); + goto err_free_irq; + } + + printk(KERN_INFO "b1isa: AVM B1 ISA at i/o %#x, irq %d, revision %d\n", + card->port, card->irq, card->revision); + + pci_set_drvdata(pdev, cinfo); + return 0; + + err_free_irq: + free_irq(card->irq, card); + err_release_region: + release_region(card->port, AVMB1_PORTLEN); + err_free: + b1_free_card(card); + err: + return retval; +} + +static char *b1isa_procinfo(struct capi_ctr *ctrl) +{ + avmctrl_info *cinfo = (avmctrl_info *)(ctrl->driverdata); + + if (!cinfo) + return ""; + sprintf(cinfo->infobuf, "%s %s 0x%x %d r%d", + cinfo->cardname[0] ? cinfo->cardname : "-", + cinfo->version[VER_DRIVER] ? cinfo->version[VER_DRIVER] : "-", + cinfo->card ? cinfo->card->port : 0x0, + cinfo->card ? cinfo->card->irq : 0, + cinfo->card ? cinfo->card->revision : 0 + ); + return cinfo->infobuf; +} + +/* ------------------------------------------------------------- */ + +#define MAX_CARDS 4 +static struct pci_dev isa_dev[MAX_CARDS]; +static int io[MAX_CARDS]; +static int irq[MAX_CARDS]; + +MODULE_PARM(io, "1-" __MODULE_STRING(MAX_CARDS) "i"); +MODULE_PARM(irq, "1-" __MODULE_STRING(MAX_CARDS) "i"); +MODULE_PARM_DESC(io, "I/O base address(es)"); +MODULE_PARM_DESC(irq, "IRQ number(s) (assigned)"); + +static int b1isa_add_card(struct capi_driver *driver, capicardparams *data) +{ + int i; + + for (i = 0; i < MAX_CARDS; i++) { + if (isa_dev[i].resource[0].start) + continue; + + isa_dev[i].resource[0].start = data->port; + isa_dev[i].irq = data->irq; + + if (b1isa_probe(&isa_dev[i]) == 0) + return 0; + } + return -ENODEV; +} + +static struct capi_driver capi_driver_b1isa = { + .name = "b1isa", + .revision = "1.0", + .add_card = b1isa_add_card, +}; + +static int __init b1isa_init(void) +{ + char *p; + char rev[32]; + int i; + + if ((p = strchr(revision, ':')) != 0 && p[1]) { + strlcpy(rev, p + 2, 32); + if ((p = strchr(rev, '$')) != 0 && p > rev) + *(p-1) = 0; + } else + strcpy(rev, "1.0"); + + for (i = 0; i < MAX_CARDS; i++) { + if (!io[i]) + break; + + isa_dev[i].resource[0].start = io[i]; + isa_dev[i].irq = irq[i]; + + if (b1isa_probe(&isa_dev[i]) != 0) + return -ENODEV; + } + + strlcpy(capi_driver_b1isa.revision, rev, 32); + register_capi_driver(&capi_driver_b1isa); + printk(KERN_INFO "b1isa: revision %s\n", rev); + + return 0; +} + +static void __exit b1isa_exit(void) +{ + int i; + + for (i = 0; i < MAX_CARDS; i++) { + if (!io[i]) + break; + + b1isa_remove(&isa_dev[i]); + } + unregister_capi_driver(&capi_driver_b1isa); +} + +module_init(b1isa_init); +module_exit(b1isa_exit); diff --git a/drivers/isdn/hardware/avm/b1pci.c b/drivers/isdn/hardware/avm/b1pci.c new file mode 100644 index 000000000000..5435a6cfb5e7 --- /dev/null +++ b/drivers/isdn/hardware/avm/b1pci.c @@ -0,0 +1,417 @@ +/* $Id: b1pci.c,v 1.1.2.2 2004/01/16 21:09:27 keil Exp $ + * + * Module for AVM B1 PCI-card. + * + * Copyright 1999 by Carsten Paeth <calle@calle.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/skbuff.h> +#include <linux/delay.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/pci.h> +#include <linux/capi.h> +#include <asm/io.h> +#include <linux/init.h> +#include <linux/isdn/capicmd.h> +#include <linux/isdn/capiutil.h> +#include <linux/isdn/capilli.h> +#include "avmcard.h" + +/* ------------------------------------------------------------- */ + +static char *revision = "$Revision: 1.1.2.2 $"; + +/* ------------------------------------------------------------- */ + +static struct pci_device_id b1pci_pci_tbl[] = { + { PCI_VENDOR_ID_AVM, PCI_DEVICE_ID_AVM_B1, PCI_ANY_ID, PCI_ANY_ID }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(pci, b1pci_pci_tbl); +MODULE_DESCRIPTION("CAPI4Linux: Driver for AVM B1 PCI card"); +MODULE_AUTHOR("Carsten Paeth"); +MODULE_LICENSE("GPL"); + +/* ------------------------------------------------------------- */ + +static char *b1pci_procinfo(struct capi_ctr *ctrl) +{ + avmctrl_info *cinfo = (avmctrl_info *)(ctrl->driverdata); + + if (!cinfo) + return ""; + sprintf(cinfo->infobuf, "%s %s 0x%x %d r%d", + cinfo->cardname[0] ? cinfo->cardname : "-", + cinfo->version[VER_DRIVER] ? cinfo->version[VER_DRIVER] : "-", + cinfo->card ? cinfo->card->port : 0x0, + cinfo->card ? cinfo->card->irq : 0, + cinfo->card ? cinfo->card->revision : 0 + ); + return cinfo->infobuf; +} + +/* ------------------------------------------------------------- */ + +static int b1pci_probe(struct capicardparams *p, struct pci_dev *pdev) +{ + avmcard *card; + avmctrl_info *cinfo; + int retval; + + card = b1_alloc_card(1); + if (!card) { + printk(KERN_WARNING "b1pci: no memory.\n"); + retval = -ENOMEM; + goto err; + } + + cinfo = card->ctrlinfo; + sprintf(card->name, "b1pci-%x", p->port); + card->port = p->port; + card->irq = p->irq; + card->cardtype = avm_b1pci; + + if (!request_region(card->port, AVMB1_PORTLEN, card->name)) { + printk(KERN_WARNING "b1pci: ports 0x%03x-0x%03x in use.\n", + card->port, card->port + AVMB1_PORTLEN); + retval = -EBUSY; + goto err_free; + } + b1_reset(card->port); + retval = b1_detect(card->port, card->cardtype); + if (retval) { + printk(KERN_NOTICE "b1pci: NO card at 0x%x (%d)\n", + card->port, retval); + retval = -ENODEV; + goto err_release_region; + } + b1_reset(card->port); + b1_getrevision(card); + + retval = request_irq(card->irq, b1_interrupt, SA_SHIRQ, card->name, card); + if (retval) { + printk(KERN_ERR "b1pci: unable to get IRQ %d.\n", card->irq); + retval = -EBUSY; + goto err_release_region; + } + + cinfo->capi_ctrl.driver_name = "b1pci"; + cinfo->capi_ctrl.driverdata = cinfo; + cinfo->capi_ctrl.register_appl = b1_register_appl; + cinfo->capi_ctrl.release_appl = b1_release_appl; + cinfo->capi_ctrl.send_message = b1_send_message; + cinfo->capi_ctrl.load_firmware = b1_load_firmware; + cinfo->capi_ctrl.reset_ctr = b1_reset_ctr; + cinfo->capi_ctrl.procinfo = b1pci_procinfo; + cinfo->capi_ctrl.ctr_read_proc = b1ctl_read_proc; + strcpy(cinfo->capi_ctrl.name, card->name); + cinfo->capi_ctrl.owner = THIS_MODULE; + + retval = attach_capi_ctr(&cinfo->capi_ctrl); + if (retval) { + printk(KERN_ERR "b1pci: attach controller failed.\n"); + goto err_free_irq; + } + + if (card->revision >= 4) { + printk(KERN_INFO "b1pci: AVM B1 PCI V4 at i/o %#x, irq %d, revision %d (no dma)\n", + card->port, card->irq, card->revision); + } else { + printk(KERN_INFO "b1pci: AVM B1 PCI at i/o %#x, irq %d, revision %d\n", + card->port, card->irq, card->revision); + } + + pci_set_drvdata(pdev, card); + return 0; + + err_free_irq: + free_irq(card->irq, card); + err_release_region: + release_region(card->port, AVMB1_PORTLEN); + err_free: + b1_free_card(card); + err: + return retval; +} + +static void b1pci_remove(struct pci_dev *pdev) +{ + avmcard *card = pci_get_drvdata(pdev); + avmctrl_info *cinfo = card->ctrlinfo; + unsigned int port = card->port; + + b1_reset(port); + b1_reset(port); + + detach_capi_ctr(&cinfo->capi_ctrl); + free_irq(card->irq, card); + release_region(card->port, AVMB1_PORTLEN); + b1_free_card(card); +} + +#ifdef CONFIG_ISDN_DRV_AVMB1_B1PCIV4 +/* ------------------------------------------------------------- */ + +static char *b1pciv4_procinfo(struct capi_ctr *ctrl) +{ + avmctrl_info *cinfo = (avmctrl_info *)(ctrl->driverdata); + + if (!cinfo) + return ""; + sprintf(cinfo->infobuf, "%s %s 0x%x %d 0x%lx r%d", + cinfo->cardname[0] ? cinfo->cardname : "-", + cinfo->version[VER_DRIVER] ? cinfo->version[VER_DRIVER] : "-", + cinfo->card ? cinfo->card->port : 0x0, + cinfo->card ? cinfo->card->irq : 0, + cinfo->card ? cinfo->card->membase : 0, + cinfo->card ? cinfo->card->revision : 0 + ); + return cinfo->infobuf; +} + +/* ------------------------------------------------------------- */ + +static int b1pciv4_probe(struct capicardparams *p, struct pci_dev *pdev) +{ + avmcard *card; + avmctrl_info *cinfo; + int retval; + + card = b1_alloc_card(1); + if (!card) { + printk(KERN_WARNING "b1pci: no memory.\n"); + retval = -ENOMEM; + goto err; + } + + card->dma = avmcard_dma_alloc("b1pci", pdev, 2048+128, 2048+128); + if (!card->dma) { + printk(KERN_WARNING "b1pci: dma alloc.\n"); + retval = -ENOMEM; + goto err_free; + } + + cinfo = card->ctrlinfo; + sprintf(card->name, "b1pciv4-%x", p->port); + card->port = p->port; + card->irq = p->irq; + card->membase = p->membase; + card->cardtype = avm_b1pci; + + if (!request_region(card->port, AVMB1_PORTLEN, card->name)) { + printk(KERN_WARNING "b1pci: ports 0x%03x-0x%03x in use.\n", + card->port, card->port + AVMB1_PORTLEN); + retval = -EBUSY; + goto err_free_dma; + } + + card->mbase = ioremap(card->membase, 64); + if (!card->mbase) { + printk(KERN_NOTICE "b1pci: can't remap memory at 0x%lx\n", + card->membase); + retval = -ENOMEM; + goto err_release_region; + } + + b1dma_reset(card); + + retval = b1pciv4_detect(card); + if (retval) { + printk(KERN_NOTICE "b1pci: NO card at 0x%x (%d)\n", + card->port, retval); + retval = -ENODEV; + goto err_unmap; + } + b1dma_reset(card); + b1_getrevision(card); + + retval = request_irq(card->irq, b1dma_interrupt, SA_SHIRQ, card->name, card); + if (retval) { + printk(KERN_ERR "b1pci: unable to get IRQ %d.\n", + card->irq); + retval = -EBUSY; + goto err_unmap; + } + + cinfo->capi_ctrl.owner = THIS_MODULE; + cinfo->capi_ctrl.driver_name = "b1pciv4"; + cinfo->capi_ctrl.driverdata = cinfo; + cinfo->capi_ctrl.register_appl = b1dma_register_appl; + cinfo->capi_ctrl.release_appl = b1dma_release_appl; + cinfo->capi_ctrl.send_message = b1dma_send_message; + cinfo->capi_ctrl.load_firmware = b1dma_load_firmware; + cinfo->capi_ctrl.reset_ctr = b1dma_reset_ctr; + cinfo->capi_ctrl.procinfo = b1pciv4_procinfo; + cinfo->capi_ctrl.ctr_read_proc = b1dmactl_read_proc; + strcpy(cinfo->capi_ctrl.name, card->name); + + retval = attach_capi_ctr(&cinfo->capi_ctrl); + if (retval) { + printk(KERN_ERR "b1pci: attach controller failed.\n"); + goto err_free_irq; + } + card->cardnr = cinfo->capi_ctrl.cnr; + + printk(KERN_INFO "b1pci: AVM B1 PCI V4 at i/o %#x, irq %d, mem %#lx, revision %d (dma)\n", + card->port, card->irq, card->membase, card->revision); + + pci_set_drvdata(pdev, card); + return 0; + + err_free_irq: + free_irq(card->irq, card); + err_unmap: + iounmap(card->mbase); + err_release_region: + release_region(card->port, AVMB1_PORTLEN); + err_free_dma: + avmcard_dma_free(card->dma); + err_free: + b1_free_card(card); + err: + return retval; + +} + +static void b1pciv4_remove(struct pci_dev *pdev) +{ + avmcard *card = pci_get_drvdata(pdev); + avmctrl_info *cinfo = card->ctrlinfo; + + b1dma_reset(card); + + detach_capi_ctr(&cinfo->capi_ctrl); + free_irq(card->irq, card); + iounmap(card->mbase); + release_region(card->port, AVMB1_PORTLEN); + avmcard_dma_free(card->dma); + b1_free_card(card); +} + +#endif /* CONFIG_ISDN_DRV_AVMB1_B1PCIV4 */ + +static int __devinit b1pci_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + struct capicardparams param; + int retval; + + if (pci_enable_device(pdev) < 0) { + printk(KERN_ERR "b1pci: failed to enable AVM-B1\n"); + return -ENODEV; + } + param.irq = pdev->irq; + + if (pci_resource_start(pdev, 2)) { /* B1 PCI V4 */ +#ifdef CONFIG_ISDN_DRV_AVMB1_B1PCIV4 + pci_set_master(pdev); +#endif + param.membase = pci_resource_start(pdev, 0); + param.port = pci_resource_start(pdev, 2); + + printk(KERN_INFO "b1pci: PCI BIOS reports AVM-B1 V4 at i/o %#x, irq %d, mem %#x\n", + param.port, param.irq, param.membase); +#ifdef CONFIG_ISDN_DRV_AVMB1_B1PCIV4 + retval = b1pciv4_probe(¶m, pdev); +#else + retval = b1pci_probe(¶m, pdev); +#endif + if (retval != 0) { + printk(KERN_ERR "b1pci: no AVM-B1 V4 at i/o %#x, irq %d, mem %#x detected\n", + param.port, param.irq, param.membase); + } + } else { + param.membase = 0; + param.port = pci_resource_start(pdev, 1); + + printk(KERN_INFO "b1pci: PCI BIOS reports AVM-B1 at i/o %#x, irq %d\n", + param.port, param.irq); + retval = b1pci_probe(¶m, pdev); + if (retval != 0) { + printk(KERN_ERR "b1pci: no AVM-B1 at i/o %#x, irq %d detected\n", + param.port, param.irq); + } + } + return retval; +} + +static void __devexit b1pci_pci_remove(struct pci_dev *pdev) +{ +#ifdef CONFIG_ISDN_DRV_AVMB1_B1PCIV4 + avmcard *card = pci_get_drvdata(pdev); + + if (card->dma) + b1pciv4_remove(pdev); + else + b1pci_remove(pdev); +#else + b1pci_remove(pdev); +#endif +} + +static struct pci_driver b1pci_pci_driver = { + .name = "b1pci", + .id_table = b1pci_pci_tbl, + .probe = b1pci_pci_probe, + .remove = __devexit_p(b1pci_pci_remove), +}; + +static struct capi_driver capi_driver_b1pci = { + .name = "b1pci", + .revision = "1.0", +}; +#ifdef CONFIG_ISDN_DRV_AVMB1_B1PCIV4 +static struct capi_driver capi_driver_b1pciv4 = { + .name = "b1pciv4", + .revision = "1.0", +}; +#endif + +static int __init b1pci_init(void) +{ + char *p; + char rev[32]; + int err; + + if ((p = strchr(revision, ':')) != 0 && p[1]) { + strlcpy(rev, p + 2, 32); + if ((p = strchr(rev, '$')) != 0 && p > rev) + *(p-1) = 0; + } else + strcpy(rev, "1.0"); + + + err = pci_register_driver(&b1pci_pci_driver); + if (!err) { + strlcpy(capi_driver_b1pci.revision, rev, 32); + register_capi_driver(&capi_driver_b1pci); +#ifdef CONFIG_ISDN_DRV_AVMB1_B1PCIV4 + strlcpy(capi_driver_b1pciv4.revision, rev, 32); + register_capi_driver(&capi_driver_b1pciv4); +#endif + printk(KERN_INFO "b1pci: revision %s\n", rev); + } + return err; +} + +static void __exit b1pci_exit(void) +{ + unregister_capi_driver(&capi_driver_b1pci); +#ifdef CONFIG_ISDN_DRV_AVMB1_B1PCIV4 + unregister_capi_driver(&capi_driver_b1pciv4); +#endif + pci_unregister_driver(&b1pci_pci_driver); +} + +module_init(b1pci_init); +module_exit(b1pci_exit); diff --git a/drivers/isdn/hardware/avm/b1pcmcia.c b/drivers/isdn/hardware/avm/b1pcmcia.c new file mode 100644 index 000000000000..9746cc5ffff8 --- /dev/null +++ b/drivers/isdn/hardware/avm/b1pcmcia.c @@ -0,0 +1,224 @@ +/* $Id: b1pcmcia.c,v 1.1.2.2 2004/01/16 21:09:27 keil Exp $ + * + * Module for AVM B1/M1/M2 PCMCIA-card. + * + * Copyright 1999 by Carsten Paeth <calle@calle.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/skbuff.h> +#include <linux/delay.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <asm/io.h> +#include <linux/capi.h> +#include <linux/b1pcmcia.h> +#include <linux/isdn/capicmd.h> +#include <linux/isdn/capiutil.h> +#include <linux/isdn/capilli.h> +#include "avmcard.h" + +/* ------------------------------------------------------------- */ + +static char *revision = "$Revision: 1.1.2.2 $"; + +/* ------------------------------------------------------------- */ + +MODULE_DESCRIPTION("CAPI4Linux: Driver for AVM PCMCIA cards"); +MODULE_AUTHOR("Carsten Paeth"); +MODULE_LICENSE("GPL"); + +/* ------------------------------------------------------------- */ + +static void b1pcmcia_remove_ctr(struct capi_ctr *ctrl) +{ + avmctrl_info *cinfo = (avmctrl_info *)(ctrl->driverdata); + avmcard *card = cinfo->card; + unsigned int port = card->port; + + b1_reset(port); + b1_reset(port); + + detach_capi_ctr(ctrl); + free_irq(card->irq, card); + b1_free_card(card); +} + +/* ------------------------------------------------------------- */ + +static LIST_HEAD(cards); + +static char *b1pcmcia_procinfo(struct capi_ctr *ctrl); + +static int b1pcmcia_add_card(unsigned int port, unsigned irq, + enum avmcardtype cardtype) +{ + avmctrl_info *cinfo; + avmcard *card; + char *cardname; + int retval; + + card = b1_alloc_card(1); + if (!card) { + printk(KERN_WARNING "b1pcmcia: no memory.\n"); + retval = -ENOMEM; + goto err; + } + cinfo = card->ctrlinfo; + + switch (cardtype) { + case avm_m1: sprintf(card->name, "m1-%x", port); break; + case avm_m2: sprintf(card->name, "m2-%x", port); break; + default: sprintf(card->name, "b1pcmcia-%x", port); break; + } + card->port = port; + card->irq = irq; + card->cardtype = cardtype; + + retval = request_irq(card->irq, b1_interrupt, 0, card->name, card); + if (retval) { + printk(KERN_ERR "b1pcmcia: unable to get IRQ %d.\n", + card->irq); + retval = -EBUSY; + goto err_free; + } + b1_reset(card->port); + if ((retval = b1_detect(card->port, card->cardtype)) != 0) { + printk(KERN_NOTICE "b1pcmcia: NO card at 0x%x (%d)\n", + card->port, retval); + retval = -ENODEV; + goto err_free_irq; + } + b1_reset(card->port); + b1_getrevision(card); + + cinfo->capi_ctrl.owner = THIS_MODULE; + cinfo->capi_ctrl.driver_name = "b1pcmcia"; + cinfo->capi_ctrl.driverdata = cinfo; + cinfo->capi_ctrl.register_appl = b1_register_appl; + cinfo->capi_ctrl.release_appl = b1_release_appl; + cinfo->capi_ctrl.send_message = b1_send_message; + cinfo->capi_ctrl.load_firmware = b1_load_firmware; + cinfo->capi_ctrl.reset_ctr = b1_reset_ctr; + cinfo->capi_ctrl.procinfo = b1pcmcia_procinfo; + cinfo->capi_ctrl.ctr_read_proc = b1ctl_read_proc; + strcpy(cinfo->capi_ctrl.name, card->name); + + retval = attach_capi_ctr(&cinfo->capi_ctrl); + if (retval) { + printk(KERN_ERR "b1pcmcia: attach controller failed.\n"); + goto err_free_irq; + } + switch (cardtype) { + case avm_m1: cardname = "M1"; break; + case avm_m2: cardname = "M2"; break; + default : cardname = "B1 PCMCIA"; break; + } + + printk(KERN_INFO "b1pcmcia: AVM %s at i/o %#x, irq %d, revision %d\n", + cardname, card->port, card->irq, card->revision); + + list_add(&card->list, &cards); + return cinfo->capi_ctrl.cnr; + + err_free_irq: + free_irq(card->irq, card); + err_free: + b1_free_card(card); + err: + return retval; +} + +/* ------------------------------------------------------------- */ + +static char *b1pcmcia_procinfo(struct capi_ctr *ctrl) +{ + avmctrl_info *cinfo = (avmctrl_info *)(ctrl->driverdata); + + if (!cinfo) + return ""; + sprintf(cinfo->infobuf, "%s %s 0x%x %d r%d", + cinfo->cardname[0] ? cinfo->cardname : "-", + cinfo->version[VER_DRIVER] ? cinfo->version[VER_DRIVER] : "-", + cinfo->card ? cinfo->card->port : 0x0, + cinfo->card ? cinfo->card->irq : 0, + cinfo->card ? cinfo->card->revision : 0 + ); + return cinfo->infobuf; +} + +/* ------------------------------------------------------------- */ + +int b1pcmcia_addcard_b1(unsigned int port, unsigned irq) +{ + return b1pcmcia_add_card(port, irq, avm_b1pcmcia); +} + +int b1pcmcia_addcard_m1(unsigned int port, unsigned irq) +{ + return b1pcmcia_add_card(port, irq, avm_m1); +} + +int b1pcmcia_addcard_m2(unsigned int port, unsigned irq) +{ + return b1pcmcia_add_card(port, irq, avm_m2); +} + +int b1pcmcia_delcard(unsigned int port, unsigned irq) +{ + struct list_head *l; + avmcard *card; + + list_for_each(l, &cards) { + card = list_entry(l, avmcard, list); + if (card->port == port && card->irq == irq) { + b1pcmcia_remove_ctr(&card->ctrlinfo[0].capi_ctrl); + return 0; + } + } + return -ESRCH; +} + +EXPORT_SYMBOL(b1pcmcia_addcard_b1); +EXPORT_SYMBOL(b1pcmcia_addcard_m1); +EXPORT_SYMBOL(b1pcmcia_addcard_m2); +EXPORT_SYMBOL(b1pcmcia_delcard); + +static struct capi_driver capi_driver_b1pcmcia = { + .name = "b1pcmcia", + .revision = "1.0", +}; + +static int __init b1pcmcia_init(void) +{ + char *p; + char rev[32]; + + if ((p = strchr(revision, ':')) != 0 && p[1]) { + strlcpy(rev, p + 2, 32); + if ((p = strchr(rev, '$')) != 0 && p > rev) + *(p-1) = 0; + } else + strcpy(rev, "1.0"); + + strlcpy(capi_driver_b1pcmcia.revision, rev, 32); + register_capi_driver(&capi_driver_b1pcmcia); + printk(KERN_INFO "b1pci: revision %s\n", rev); + + return 0; +} + +static void __exit b1pcmcia_exit(void) +{ + unregister_capi_driver(&capi_driver_b1pcmcia); +} + +module_init(b1pcmcia_init); +module_exit(b1pcmcia_exit); diff --git a/drivers/isdn/hardware/avm/c4.c b/drivers/isdn/hardware/avm/c4.c new file mode 100644 index 000000000000..fa6b93b1a42d --- /dev/null +++ b/drivers/isdn/hardware/avm/c4.c @@ -0,0 +1,1310 @@ +/* $Id: c4.c,v 1.1.2.2 2004/01/16 21:09:27 keil Exp $ + * + * Module for AVM C4 & C2 card. + * + * Copyright 1999 by Carsten Paeth <calle@calle.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/skbuff.h> +#include <linux/delay.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/pci.h> +#include <linux/capi.h> +#include <linux/kernelcapi.h> +#include <linux/init.h> +#include <asm/io.h> +#include <asm/uaccess.h> +#include <linux/netdevice.h> +#include <linux/isdn/capicmd.h> +#include <linux/isdn/capiutil.h> +#include <linux/isdn/capilli.h> +#include "avmcard.h" + +#undef CONFIG_C4_DEBUG +#undef CONFIG_C4_POLLDEBUG + +/* ------------------------------------------------------------- */ + +static char *revision = "$Revision: 1.1.2.2 $"; + +/* ------------------------------------------------------------- */ + +static int suppress_pollack; + +static struct pci_device_id c4_pci_tbl[] = { + { PCI_VENDOR_ID_DEC, PCI_DEVICE_ID_DEC_21285, PCI_VENDOR_ID_AVM, PCI_DEVICE_ID_AVM_C4, 0, 0, (unsigned long)4 }, + { PCI_VENDOR_ID_DEC, PCI_DEVICE_ID_DEC_21285, PCI_VENDOR_ID_AVM, PCI_DEVICE_ID_AVM_C2, 0, 0, (unsigned long)2 }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(pci, c4_pci_tbl); +MODULE_DESCRIPTION("CAPI4Linux: Driver for AVM C2/C4 cards"); +MODULE_AUTHOR("Carsten Paeth"); +MODULE_LICENSE("GPL"); +MODULE_PARM(suppress_pollack, "0-1i"); + +/* ------------------------------------------------------------- */ + +static void c4_dispatch_tx(avmcard *card); + +/* ------------------------------------------------------------- */ + +#define DC21285_DRAM_A0MR 0x40000000 +#define DC21285_DRAM_A1MR 0x40004000 +#define DC21285_DRAM_A2MR 0x40008000 +#define DC21285_DRAM_A3MR 0x4000C000 + +#define CAS_OFFSET 0x88 + +#define DC21285_ARMCSR_BASE 0x42000000 + +#define PCI_OUT_INT_STATUS 0x30 +#define PCI_OUT_INT_MASK 0x34 +#define MAILBOX_0 0x50 +#define MAILBOX_1 0x54 +#define MAILBOX_2 0x58 +#define MAILBOX_3 0x5C +#define DOORBELL 0x60 +#define DOORBELL_SETUP 0x64 + +#define CHAN_1_CONTROL 0x90 +#define CHAN_2_CONTROL 0xB0 +#define DRAM_TIMING 0x10C +#define DRAM_ADDR_SIZE_0 0x110 +#define DRAM_ADDR_SIZE_1 0x114 +#define DRAM_ADDR_SIZE_2 0x118 +#define DRAM_ADDR_SIZE_3 0x11C +#define SA_CONTROL 0x13C +#define XBUS_CYCLE 0x148 +#define XBUS_STROBE 0x14C +#define DBELL_PCI_MASK 0x150 +#define DBELL_SA_MASK 0x154 + +#define SDRAM_SIZE 0x1000000 + +/* ------------------------------------------------------------- */ + +#define MBOX_PEEK_POKE MAILBOX_0 + +#define DBELL_ADDR 0x01 +#define DBELL_DATA 0x02 +#define DBELL_RNWR 0x40 +#define DBELL_INIT 0x80 + +/* ------------------------------------------------------------- */ + +#define MBOX_UP_ADDR MAILBOX_0 +#define MBOX_UP_LEN MAILBOX_1 +#define MBOX_DOWN_ADDR MAILBOX_2 +#define MBOX_DOWN_LEN MAILBOX_3 + +#define DBELL_UP_HOST 0x00000100 +#define DBELL_UP_ARM 0x00000200 +#define DBELL_DOWN_HOST 0x00000400 +#define DBELL_DOWN_ARM 0x00000800 +#define DBELL_RESET_HOST 0x40000000 +#define DBELL_RESET_ARM 0x80000000 + +/* ------------------------------------------------------------- */ + +#define DRAM_TIMING_DEF 0x001A01A5 +#define DRAM_AD_SZ_DEF0 0x00000045 +#define DRAM_AD_SZ_NULL 0x00000000 + +#define SA_CTL_ALLRIGHT 0x64AA0271 + +#define INIT_XBUS_CYCLE 0x100016DB +#define INIT_XBUS_STROBE 0xF1F1F1F1 + +/* ------------------------------------------------------------- */ + +#define RESET_TIMEOUT (15*HZ) /* 15 sec */ +#define PEEK_POKE_TIMEOUT (HZ/10) /* 0.1 sec */ + +/* ------------------------------------------------------------- */ + +#define c4outmeml(addr, value) writel(value, addr) +#define c4inmeml(addr) readl(addr) +#define c4outmemw(addr, value) writew(value, addr) +#define c4inmemw(addr) readw(addr) +#define c4outmemb(addr, value) writeb(value, addr) +#define c4inmemb(addr) readb(addr) + +/* ------------------------------------------------------------- */ + +static inline int wait_for_doorbell(avmcard *card, unsigned long t) +{ + unsigned long stop; + + stop = jiffies + t; + while (c4inmeml(card->mbase+DOORBELL) != 0xffffffff) { + if (!time_before(jiffies, stop)) + return -1; + mb(); + } + return 0; +} + +static int c4_poke(avmcard *card, unsigned long off, unsigned long value) +{ + + if (wait_for_doorbell(card, HZ/10) < 0) + return -1; + + c4outmeml(card->mbase+MBOX_PEEK_POKE, off); + c4outmeml(card->mbase+DOORBELL, DBELL_ADDR); + + if (wait_for_doorbell(card, HZ/10) < 0) + return -1; + + c4outmeml(card->mbase+MBOX_PEEK_POKE, value); + c4outmeml(card->mbase+DOORBELL, DBELL_DATA | DBELL_ADDR); + + return 0; +} + +static int c4_peek(avmcard *card, unsigned long off, unsigned long *valuep) +{ + if (wait_for_doorbell(card, HZ/10) < 0) + return -1; + + c4outmeml(card->mbase+MBOX_PEEK_POKE, off); + c4outmeml(card->mbase+DOORBELL, DBELL_RNWR | DBELL_ADDR); + + if (wait_for_doorbell(card, HZ/10) < 0) + return -1; + + *valuep = c4inmeml(card->mbase+MBOX_PEEK_POKE); + + return 0; +} + +/* ------------------------------------------------------------- */ + +static int c4_load_t4file(avmcard *card, capiloaddatapart * t4file) +{ + u32 val; + unsigned char *dp; + u_int left; + u32 loadoff = 0; + + dp = t4file->data; + left = t4file->len; + while (left >= sizeof(u32)) { + if (t4file->user) { + if (copy_from_user(&val, dp, sizeof(val))) + return -EFAULT; + } else { + memcpy(&val, dp, sizeof(val)); + } + if (c4_poke(card, loadoff, val)) { + printk(KERN_ERR "%s: corrupted firmware file ?\n", + card->name); + return -EIO; + } + left -= sizeof(u32); + dp += sizeof(u32); + loadoff += sizeof(u32); + } + if (left) { + val = 0; + if (t4file->user) { + if (copy_from_user(&val, dp, left)) + return -EFAULT; + } else { + memcpy(&val, dp, left); + } + if (c4_poke(card, loadoff, val)) { + printk(KERN_ERR "%s: corrupted firmware file ?\n", + card->name); + return -EIO; + } + } + return 0; +} + +/* ------------------------------------------------------------- */ + +static inline void _put_byte(void **pp, u8 val) +{ + u8 *s = *pp; + *s++ = val; + *pp = s; +} + +static inline void _put_word(void **pp, u32 val) +{ + u8 *s = *pp; + *s++ = val & 0xff; + *s++ = (val >> 8) & 0xff; + *s++ = (val >> 16) & 0xff; + *s++ = (val >> 24) & 0xff; + *pp = s; +} + +static inline void _put_slice(void **pp, unsigned char *dp, unsigned int len) +{ + unsigned i = len; + _put_word(pp, i); + while (i-- > 0) + _put_byte(pp, *dp++); +} + +static inline u8 _get_byte(void **pp) +{ + u8 *s = *pp; + u8 val; + val = *s++; + *pp = s; + return val; +} + +static inline u32 _get_word(void **pp) +{ + u8 *s = *pp; + u32 val; + val = *s++; + val |= (*s++ << 8); + val |= (*s++ << 16); + val |= (*s++ << 24); + *pp = s; + return val; +} + +static inline u32 _get_slice(void **pp, unsigned char *dp) +{ + unsigned int len, i; + + len = i = _get_word(pp); + while (i-- > 0) *dp++ = _get_byte(pp); + return len; +} + +/* ------------------------------------------------------------- */ + +static void c4_reset(avmcard *card) +{ + unsigned long stop; + + c4outmeml(card->mbase+DOORBELL, DBELL_RESET_ARM); + + stop = jiffies + HZ*10; + while (c4inmeml(card->mbase+DOORBELL) != 0xffffffff) { + if (!time_before(jiffies, stop)) + return; + c4outmeml(card->mbase+DOORBELL, DBELL_ADDR); + mb(); + } + + c4_poke(card, DC21285_ARMCSR_BASE + CHAN_1_CONTROL, 0); + c4_poke(card, DC21285_ARMCSR_BASE + CHAN_2_CONTROL, 0); +} + +/* ------------------------------------------------------------- */ + +static int c4_detect(avmcard *card) +{ + unsigned long stop, dummy; + + c4outmeml(card->mbase+PCI_OUT_INT_MASK, 0x0c); + if (c4inmeml(card->mbase+PCI_OUT_INT_MASK) != 0x0c) + return 1; + + c4outmeml(card->mbase+DOORBELL, DBELL_RESET_ARM); + + stop = jiffies + HZ*10; + while (c4inmeml(card->mbase+DOORBELL) != 0xffffffff) { + if (!time_before(jiffies, stop)) + return 2; + c4outmeml(card->mbase+DOORBELL, DBELL_ADDR); + mb(); + } + + c4_poke(card, DC21285_ARMCSR_BASE + CHAN_1_CONTROL, 0); + c4_poke(card, DC21285_ARMCSR_BASE + CHAN_2_CONTROL, 0); + + c4outmeml(card->mbase+MAILBOX_0, 0x55aa55aa); + if (c4inmeml(card->mbase+MAILBOX_0) != 0x55aa55aa) return 3; + + c4outmeml(card->mbase+MAILBOX_0, 0xaa55aa55); + if (c4inmeml(card->mbase+MAILBOX_0) != 0xaa55aa55) return 4; + + if (c4_poke(card, DC21285_ARMCSR_BASE+DBELL_SA_MASK, 0)) return 5; + if (c4_poke(card, DC21285_ARMCSR_BASE+DBELL_PCI_MASK, 0)) return 6; + if (c4_poke(card, DC21285_ARMCSR_BASE+SA_CONTROL, SA_CTL_ALLRIGHT)) + return 7; + if (c4_poke(card, DC21285_ARMCSR_BASE+XBUS_CYCLE, INIT_XBUS_CYCLE)) + return 8; + if (c4_poke(card, DC21285_ARMCSR_BASE+XBUS_STROBE, INIT_XBUS_STROBE)) + return 8; + if (c4_poke(card, DC21285_ARMCSR_BASE+DRAM_TIMING, 0)) return 9; + + mdelay(1); + + if (c4_peek(card, DC21285_DRAM_A0MR, &dummy)) return 10; + if (c4_peek(card, DC21285_DRAM_A1MR, &dummy)) return 11; + if (c4_peek(card, DC21285_DRAM_A2MR, &dummy)) return 12; + if (c4_peek(card, DC21285_DRAM_A3MR, &dummy)) return 13; + + if (c4_poke(card, DC21285_DRAM_A0MR+CAS_OFFSET, 0)) return 14; + if (c4_poke(card, DC21285_DRAM_A1MR+CAS_OFFSET, 0)) return 15; + if (c4_poke(card, DC21285_DRAM_A2MR+CAS_OFFSET, 0)) return 16; + if (c4_poke(card, DC21285_DRAM_A3MR+CAS_OFFSET, 0)) return 17; + + mdelay(1); + + if (c4_poke(card, DC21285_ARMCSR_BASE+DRAM_TIMING, DRAM_TIMING_DEF)) + return 18; + + if (c4_poke(card, DC21285_ARMCSR_BASE+DRAM_ADDR_SIZE_0,DRAM_AD_SZ_DEF0)) + return 19; + if (c4_poke(card, DC21285_ARMCSR_BASE+DRAM_ADDR_SIZE_1,DRAM_AD_SZ_NULL)) + return 20; + if (c4_poke(card, DC21285_ARMCSR_BASE+DRAM_ADDR_SIZE_2,DRAM_AD_SZ_NULL)) + return 21; + if (c4_poke(card, DC21285_ARMCSR_BASE+DRAM_ADDR_SIZE_3,DRAM_AD_SZ_NULL)) + return 22; + + /* Transputer test */ + + if ( c4_poke(card, 0x000000, 0x11111111) + || c4_poke(card, 0x400000, 0x22222222) + || c4_poke(card, 0x800000, 0x33333333) + || c4_poke(card, 0xC00000, 0x44444444)) + return 23; + + if ( c4_peek(card, 0x000000, &dummy) || dummy != 0x11111111 + || c4_peek(card, 0x400000, &dummy) || dummy != 0x22222222 + || c4_peek(card, 0x800000, &dummy) || dummy != 0x33333333 + || c4_peek(card, 0xC00000, &dummy) || dummy != 0x44444444) + return 24; + + if ( c4_poke(card, 0x000000, 0x55555555) + || c4_poke(card, 0x400000, 0x66666666) + || c4_poke(card, 0x800000, 0x77777777) + || c4_poke(card, 0xC00000, 0x88888888)) + return 25; + + if ( c4_peek(card, 0x000000, &dummy) || dummy != 0x55555555 + || c4_peek(card, 0x400000, &dummy) || dummy != 0x66666666 + || c4_peek(card, 0x800000, &dummy) || dummy != 0x77777777 + || c4_peek(card, 0xC00000, &dummy) || dummy != 0x88888888) + return 26; + + return 0; +} + +/* ------------------------------------------------------------- */ + +static void c4_dispatch_tx(avmcard *card) +{ + avmcard_dmainfo *dma = card->dma; + struct sk_buff *skb; + u8 cmd, subcmd; + u16 len; + u32 txlen; + void *p; + + + if (card->csr & DBELL_DOWN_ARM) { /* tx busy */ + return; + } + + skb = skb_dequeue(&dma->send_queue); + if (!skb) { +#ifdef CONFIG_C4_DEBUG + printk(KERN_DEBUG "%s: tx underrun\n", card->name); +#endif + return; + } + + len = CAPIMSG_LEN(skb->data); + + if (len) { + cmd = CAPIMSG_COMMAND(skb->data); + subcmd = CAPIMSG_SUBCOMMAND(skb->data); + + p = dma->sendbuf.dmabuf; + + if (CAPICMD(cmd, subcmd) == CAPI_DATA_B3_REQ) { + u16 dlen = CAPIMSG_DATALEN(skb->data); + _put_byte(&p, SEND_DATA_B3_REQ); + _put_slice(&p, skb->data, len); + _put_slice(&p, skb->data + len, dlen); + } else { + _put_byte(&p, SEND_MESSAGE); + _put_slice(&p, skb->data, len); + } + txlen = (u8 *)p - (u8 *)dma->sendbuf.dmabuf; +#ifdef CONFIG_C4_DEBUG + printk(KERN_DEBUG "%s: tx put msg len=%d\n", card->name, txlen); +#endif + } else { + txlen = skb->len-2; +#ifdef CONFIG_C4_POLLDEBUG + if (skb->data[2] == SEND_POLLACK) + printk(KERN_INFO "%s: ack to c4\n", card->name); +#endif +#ifdef CONFIG_C4_DEBUG + printk(KERN_DEBUG "%s: tx put 0x%x len=%d\n", + card->name, skb->data[2], txlen); +#endif + memcpy(dma->sendbuf.dmabuf, skb->data+2, skb->len-2); + } + txlen = (txlen + 3) & ~3; + + c4outmeml(card->mbase+MBOX_DOWN_ADDR, dma->sendbuf.dmaaddr); + c4outmeml(card->mbase+MBOX_DOWN_LEN, txlen); + + card->csr |= DBELL_DOWN_ARM; + + c4outmeml(card->mbase+DOORBELL, DBELL_DOWN_ARM); + + dev_kfree_skb_any(skb); +} + +/* ------------------------------------------------------------- */ + +static void queue_pollack(avmcard *card) +{ + struct sk_buff *skb; + void *p; + + skb = alloc_skb(3, GFP_ATOMIC); + if (!skb) { + printk(KERN_CRIT "%s: no memory, lost poll ack\n", + card->name); + return; + } + p = skb->data; + _put_byte(&p, 0); + _put_byte(&p, 0); + _put_byte(&p, SEND_POLLACK); + skb_put(skb, (u8 *)p - (u8 *)skb->data); + + skb_queue_tail(&card->dma->send_queue, skb); + c4_dispatch_tx(card); +} + +/* ------------------------------------------------------------- */ + +static void c4_handle_rx(avmcard *card) +{ + avmcard_dmainfo *dma = card->dma; + struct capi_ctr *ctrl; + avmctrl_info *cinfo; + struct sk_buff *skb; + void *p = dma->recvbuf.dmabuf; + u32 ApplId, MsgLen, DataB3Len, NCCI, WindowSize; + u8 b1cmd = _get_byte(&p); + u32 cidx; + + +#ifdef CONFIG_C4_DEBUG + printk(KERN_DEBUG "%s: rx 0x%x len=%lu\n", card->name, + b1cmd, (unsigned long)dma->recvlen); +#endif + + switch (b1cmd) { + case RECEIVE_DATA_B3_IND: + + ApplId = (unsigned) _get_word(&p); + MsgLen = _get_slice(&p, card->msgbuf); + DataB3Len = _get_slice(&p, card->databuf); + cidx = CAPIMSG_CONTROLLER(card->msgbuf)-card->cardnr; + if (cidx >= card->nlogcontr) cidx = 0; + ctrl = &card->ctrlinfo[cidx].capi_ctrl; + + if (MsgLen < 30) { /* not CAPI 64Bit */ + memset(card->msgbuf+MsgLen, 0, 30-MsgLen); + MsgLen = 30; + CAPIMSG_SETLEN(card->msgbuf, 30); + } + if (!(skb = alloc_skb(DataB3Len+MsgLen, GFP_ATOMIC))) { + printk(KERN_ERR "%s: incoming packet dropped\n", + card->name); + } else { + memcpy(skb_put(skb, MsgLen), card->msgbuf, MsgLen); + memcpy(skb_put(skb, DataB3Len), card->databuf, DataB3Len); + capi_ctr_handle_message(ctrl, ApplId, skb); + } + break; + + case RECEIVE_MESSAGE: + + ApplId = (unsigned) _get_word(&p); + MsgLen = _get_slice(&p, card->msgbuf); + cidx = CAPIMSG_CONTROLLER(card->msgbuf)-card->cardnr; + if (cidx >= card->nlogcontr) cidx = 0; + cinfo = &card->ctrlinfo[cidx]; + ctrl = &card->ctrlinfo[cidx].capi_ctrl; + + if (!(skb = alloc_skb(MsgLen, GFP_ATOMIC))) { + printk(KERN_ERR "%s: incoming packet dropped\n", + card->name); + } else { + memcpy(skb_put(skb, MsgLen), card->msgbuf, MsgLen); + if (CAPIMSG_CMD(skb->data) == CAPI_DATA_B3_CONF) + capilib_data_b3_conf(&cinfo->ncci_head, ApplId, + CAPIMSG_NCCI(skb->data), + CAPIMSG_MSGID(skb->data)); + + capi_ctr_handle_message(ctrl, ApplId, skb); + } + break; + + case RECEIVE_NEW_NCCI: + + ApplId = _get_word(&p); + NCCI = _get_word(&p); + WindowSize = _get_word(&p); + cidx = (NCCI&0x7f) - card->cardnr; + if (cidx >= card->nlogcontr) cidx = 0; + + capilib_new_ncci(&card->ctrlinfo[cidx].ncci_head, ApplId, NCCI, WindowSize); + + break; + + case RECEIVE_FREE_NCCI: + + ApplId = _get_word(&p); + NCCI = _get_word(&p); + + if (NCCI != 0xffffffff) { + cidx = (NCCI&0x7f) - card->cardnr; + if (cidx >= card->nlogcontr) cidx = 0; + capilib_free_ncci(&card->ctrlinfo[cidx].ncci_head, ApplId, NCCI); + } + break; + + case RECEIVE_START: +#ifdef CONFIG_C4_POLLDEBUG + printk(KERN_INFO "%s: poll from c4\n", card->name); +#endif + if (!suppress_pollack) + queue_pollack(card); + for (cidx=0; cidx < card->nr_controllers; cidx++) { + ctrl = &card->ctrlinfo[cidx].capi_ctrl; + capi_ctr_resume_output(ctrl); + } + break; + + case RECEIVE_STOP: + for (cidx=0; cidx < card->nr_controllers; cidx++) { + ctrl = &card->ctrlinfo[cidx].capi_ctrl; + capi_ctr_suspend_output(ctrl); + } + break; + + case RECEIVE_INIT: + + cidx = card->nlogcontr; + if (cidx >= card->nr_controllers) { + printk(KERN_ERR "%s: card with %d controllers ??\n", + card->name, cidx+1); + break; + } + card->nlogcontr++; + cinfo = &card->ctrlinfo[cidx]; + ctrl = &cinfo->capi_ctrl; + cinfo->versionlen = _get_slice(&p, cinfo->versionbuf); + b1_parse_version(cinfo); + printk(KERN_INFO "%s: %s-card (%s) now active\n", + card->name, + cinfo->version[VER_CARDTYPE], + cinfo->version[VER_DRIVER]); + capi_ctr_ready(&cinfo->capi_ctrl); + break; + + case RECEIVE_TASK_READY: + ApplId = (unsigned) _get_word(&p); + MsgLen = _get_slice(&p, card->msgbuf); + card->msgbuf[MsgLen] = 0; + while ( MsgLen > 0 + && ( card->msgbuf[MsgLen-1] == '\n' + || card->msgbuf[MsgLen-1] == '\r')) { + card->msgbuf[MsgLen-1] = 0; + MsgLen--; + } + printk(KERN_INFO "%s: task %d \"%s\" ready.\n", + card->name, ApplId, card->msgbuf); + break; + + case RECEIVE_DEBUGMSG: + MsgLen = _get_slice(&p, card->msgbuf); + card->msgbuf[MsgLen] = 0; + while ( MsgLen > 0 + && ( card->msgbuf[MsgLen-1] == '\n' + || card->msgbuf[MsgLen-1] == '\r')) { + card->msgbuf[MsgLen-1] = 0; + MsgLen--; + } + printk(KERN_INFO "%s: DEBUG: %s\n", card->name, card->msgbuf); + break; + + default: + printk(KERN_ERR "%s: c4_interrupt: 0x%x ???\n", + card->name, b1cmd); + return; + } +} + +/* ------------------------------------------------------------- */ + +static irqreturn_t c4_handle_interrupt(avmcard *card) +{ + unsigned long flags; + u32 status; + + spin_lock_irqsave(&card->lock, flags); + status = c4inmeml(card->mbase+DOORBELL); + + if (status & DBELL_RESET_HOST) { + u_int i; + c4outmeml(card->mbase+PCI_OUT_INT_MASK, 0x0c); + spin_unlock_irqrestore(&card->lock, flags); + if (card->nlogcontr == 0) + return IRQ_HANDLED; + printk(KERN_ERR "%s: unexpected reset\n", card->name); + for (i=0; i < card->nr_controllers; i++) { + avmctrl_info *cinfo = &card->ctrlinfo[i]; + memset(cinfo->version, 0, sizeof(cinfo->version)); + capilib_release(&cinfo->ncci_head); + capi_ctr_reseted(&cinfo->capi_ctrl); + } + card->nlogcontr = 0; + return IRQ_HANDLED; + } + + status &= (DBELL_UP_HOST | DBELL_DOWN_HOST); + if (!status) { + spin_unlock_irqrestore(&card->lock, flags); + return IRQ_HANDLED; + } + c4outmeml(card->mbase+DOORBELL, status); + + if ((status & DBELL_UP_HOST) != 0) { + card->dma->recvlen = c4inmeml(card->mbase+MBOX_UP_LEN); + c4outmeml(card->mbase+MBOX_UP_LEN, 0); + c4_handle_rx(card); + card->dma->recvlen = 0; + c4outmeml(card->mbase+MBOX_UP_LEN, card->dma->recvbuf.size); + c4outmeml(card->mbase+DOORBELL, DBELL_UP_ARM); + } + + if ((status & DBELL_DOWN_HOST) != 0) { + card->csr &= ~DBELL_DOWN_ARM; + c4_dispatch_tx(card); + } else if (card->csr & DBELL_DOWN_HOST) { + if (c4inmeml(card->mbase+MBOX_DOWN_LEN) == 0) { + card->csr &= ~DBELL_DOWN_ARM; + c4_dispatch_tx(card); + } + } + spin_unlock_irqrestore(&card->lock, flags); + return IRQ_HANDLED; +} + +static irqreturn_t c4_interrupt(int interrupt, void *devptr, struct pt_regs *regs) +{ + avmcard *card = devptr; + + return c4_handle_interrupt(card); +} + +/* ------------------------------------------------------------- */ + +static void c4_send_init(avmcard *card) +{ + struct sk_buff *skb; + void *p; + + skb = alloc_skb(15, GFP_ATOMIC); + if (!skb) { + printk(KERN_CRIT "%s: no memory, lost register appl.\n", + card->name); + return; + } + p = skb->data; + _put_byte(&p, 0); + _put_byte(&p, 0); + _put_byte(&p, SEND_INIT); + _put_word(&p, CAPI_MAXAPPL); + _put_word(&p, AVM_NCCI_PER_CHANNEL*30); + _put_word(&p, card->cardnr - 1); + skb_put(skb, (u8 *)p - (u8 *)skb->data); + + skb_queue_tail(&card->dma->send_queue, skb); + c4_dispatch_tx(card); +} + +static int queue_sendconfigword(avmcard *card, u32 val) +{ + struct sk_buff *skb; + void *p; + + skb = alloc_skb(3+4, GFP_ATOMIC); + if (!skb) { + printk(KERN_CRIT "%s: no memory, send config\n", + card->name); + return -ENOMEM; + } + p = skb->data; + _put_byte(&p, 0); + _put_byte(&p, 0); + _put_byte(&p, SEND_CONFIG); + _put_word(&p, val); + skb_put(skb, (u8 *)p - (u8 *)skb->data); + + skb_queue_tail(&card->dma->send_queue, skb); + c4_dispatch_tx(card); + return 0; +} + +static int queue_sendconfig(avmcard *card, char cval[4]) +{ + struct sk_buff *skb; + unsigned long flags; + void *p; + + skb = alloc_skb(3+4, GFP_ATOMIC); + if (!skb) { + printk(KERN_CRIT "%s: no memory, send config\n", + card->name); + return -ENOMEM; + } + p = skb->data; + _put_byte(&p, 0); + _put_byte(&p, 0); + _put_byte(&p, SEND_CONFIG); + _put_byte(&p, cval[0]); + _put_byte(&p, cval[1]); + _put_byte(&p, cval[2]); + _put_byte(&p, cval[3]); + skb_put(skb, (u8 *)p - (u8 *)skb->data); + + skb_queue_tail(&card->dma->send_queue, skb); + + spin_lock_irqsave(&card->lock, flags); + c4_dispatch_tx(card); + spin_unlock_irqrestore(&card->lock, flags); + return 0; +} + +static int c4_send_config(avmcard *card, capiloaddatapart * config) +{ + u8 val[4]; + unsigned char *dp; + u_int left; + int retval; + + if ((retval = queue_sendconfigword(card, 1)) != 0) + return retval; + if ((retval = queue_sendconfigword(card, config->len)) != 0) + return retval; + + dp = config->data; + left = config->len; + while (left >= sizeof(u32)) { + if (config->user) { + if (copy_from_user(val, dp, sizeof(val))) + return -EFAULT; + } else { + memcpy(val, dp, sizeof(val)); + } + if ((retval = queue_sendconfig(card, val)) != 0) + return retval; + left -= sizeof(val); + dp += sizeof(val); + } + if (left) { + memset(val, 0, sizeof(val)); + if (config->user) { + if (copy_from_user(&val, dp, left)) + return -EFAULT; + } else { + memcpy(&val, dp, left); + } + if ((retval = queue_sendconfig(card, val)) != 0) + return retval; + } + + return 0; +} + +static int c4_load_firmware(struct capi_ctr *ctrl, capiloaddata *data) +{ + avmctrl_info *cinfo = (avmctrl_info *)(ctrl->driverdata); + avmcard *card = cinfo->card; + int retval; + + if ((retval = c4_load_t4file(card, &data->firmware))) { + printk(KERN_ERR "%s: failed to load t4file!!\n", + card->name); + c4_reset(card); + return retval; + } + + card->csr = 0; + c4outmeml(card->mbase+MBOX_UP_LEN, 0); + c4outmeml(card->mbase+MBOX_DOWN_LEN, 0); + c4outmeml(card->mbase+DOORBELL, DBELL_INIT); + mdelay(1); + c4outmeml(card->mbase+DOORBELL, + DBELL_UP_HOST | DBELL_DOWN_HOST | DBELL_RESET_HOST); + + c4outmeml(card->mbase+PCI_OUT_INT_MASK, 0x08); + + card->dma->recvlen = 0; + c4outmeml(card->mbase+MBOX_UP_ADDR, card->dma->recvbuf.dmaaddr); + c4outmeml(card->mbase+MBOX_UP_LEN, card->dma->recvbuf.size); + c4outmeml(card->mbase+DOORBELL, DBELL_UP_ARM); + + if (data->configuration.len > 0 && data->configuration.data) { + retval = c4_send_config(card, &data->configuration); + if (retval) { + printk(KERN_ERR "%s: failed to set config!!\n", + card->name); + c4_reset(card); + return retval; + } + } + + c4_send_init(card); + + return 0; +} + + +void c4_reset_ctr(struct capi_ctr *ctrl) +{ + avmcard *card = ((avmctrl_info *)(ctrl->driverdata))->card; + avmctrl_info *cinfo; + u_int i; + unsigned long flags; + + spin_lock_irqsave(&card->lock, flags); + + c4_reset(card); + + spin_unlock_irqrestore(&card->lock, flags); + + for (i=0; i < card->nr_controllers; i++) { + cinfo = &card->ctrlinfo[i]; + memset(cinfo->version, 0, sizeof(cinfo->version)); + capi_ctr_reseted(&cinfo->capi_ctrl); + } + card->nlogcontr = 0; +} + +static void c4_remove(struct pci_dev *pdev) +{ + avmcard *card = pci_get_drvdata(pdev); + avmctrl_info *cinfo; + u_int i; + + if (!card) + return; + + c4_reset(card); + + for (i=0; i < card->nr_controllers; i++) { + cinfo = &card->ctrlinfo[i]; + detach_capi_ctr(&cinfo->capi_ctrl); + } + + free_irq(card->irq, card); + iounmap(card->mbase); + release_region(card->port, AVMB1_PORTLEN); + avmcard_dma_free(card->dma); + pci_set_drvdata(pdev, NULL); + b1_free_card(card); +} + +/* ------------------------------------------------------------- */ + + +void c4_register_appl(struct capi_ctr *ctrl, + u16 appl, + capi_register_params *rp) +{ + avmctrl_info *cinfo = (avmctrl_info *)(ctrl->driverdata); + avmcard *card = cinfo->card; + struct sk_buff *skb; + int want = rp->level3cnt; + unsigned long flags; + int nconn; + void *p; + + if (ctrl->cnr == card->cardnr) { + + if (want > 0) nconn = want; + else nconn = ctrl->profile.nbchannel * 4 * -want; + if (nconn == 0) nconn = ctrl->profile.nbchannel * 4; + + skb = alloc_skb(23, GFP_ATOMIC); + if (!skb) { + printk(KERN_CRIT "%s: no memory, lost register appl.\n", + card->name); + return; + } + p = skb->data; + _put_byte(&p, 0); + _put_byte(&p, 0); + _put_byte(&p, SEND_REGISTER); + _put_word(&p, appl); + _put_word(&p, 1024 * (nconn+1)); + _put_word(&p, nconn); + _put_word(&p, rp->datablkcnt); + _put_word(&p, rp->datablklen); + skb_put(skb, (u8 *)p - (u8 *)skb->data); + + skb_queue_tail(&card->dma->send_queue, skb); + + spin_lock_irqsave(&card->lock, flags); + c4_dispatch_tx(card); + spin_unlock_irqrestore(&card->lock, flags); + } +} + +/* ------------------------------------------------------------- */ + +void c4_release_appl(struct capi_ctr *ctrl, u16 appl) +{ + avmctrl_info *cinfo = (avmctrl_info *)(ctrl->driverdata); + avmcard *card = cinfo->card; + unsigned long flags; + struct sk_buff *skb; + void *p; + + capilib_release_appl(&cinfo->ncci_head, appl); + + if (ctrl->cnr == card->cardnr) { + skb = alloc_skb(7, GFP_ATOMIC); + if (!skb) { + printk(KERN_CRIT "%s: no memory, lost release appl.\n", + card->name); + return; + } + p = skb->data; + _put_byte(&p, 0); + _put_byte(&p, 0); + _put_byte(&p, SEND_RELEASE); + _put_word(&p, appl); + + skb_put(skb, (u8 *)p - (u8 *)skb->data); + skb_queue_tail(&card->dma->send_queue, skb); + spin_lock_irqsave(&card->lock, flags); + c4_dispatch_tx(card); + spin_unlock_irqrestore(&card->lock, flags); + } +} + +/* ------------------------------------------------------------- */ + + +static u16 c4_send_message(struct capi_ctr *ctrl, struct sk_buff *skb) +{ + avmctrl_info *cinfo = (avmctrl_info *)(ctrl->driverdata); + avmcard *card = cinfo->card; + u16 retval = CAPI_NOERROR; + unsigned long flags; + + if (CAPIMSG_CMD(skb->data) == CAPI_DATA_B3_REQ) { + retval = capilib_data_b3_req(&cinfo->ncci_head, + CAPIMSG_APPID(skb->data), + CAPIMSG_NCCI(skb->data), + CAPIMSG_MSGID(skb->data)); + } + if (retval == CAPI_NOERROR) { + skb_queue_tail(&card->dma->send_queue, skb); + spin_lock_irqsave(&card->lock, flags); + c4_dispatch_tx(card); + spin_unlock_irqrestore(&card->lock, flags); + } + return retval; +} + +/* ------------------------------------------------------------- */ + +static char *c4_procinfo(struct capi_ctr *ctrl) +{ + avmctrl_info *cinfo = (avmctrl_info *)(ctrl->driverdata); + + if (!cinfo) + return ""; + sprintf(cinfo->infobuf, "%s %s 0x%x %d 0x%lx", + cinfo->cardname[0] ? cinfo->cardname : "-", + cinfo->version[VER_DRIVER] ? cinfo->version[VER_DRIVER] : "-", + cinfo->card ? cinfo->card->port : 0x0, + cinfo->card ? cinfo->card->irq : 0, + cinfo->card ? cinfo->card->membase : 0 + ); + return cinfo->infobuf; +} + +static int c4_read_proc(char *page, char **start, off_t off, + int count, int *eof, struct capi_ctr *ctrl) +{ + avmctrl_info *cinfo = (avmctrl_info *)(ctrl->driverdata); + avmcard *card = cinfo->card; + u8 flag; + int len = 0; + char *s; + + len += sprintf(page+len, "%-16s %s\n", "name", card->name); + len += sprintf(page+len, "%-16s 0x%x\n", "io", card->port); + len += sprintf(page+len, "%-16s %d\n", "irq", card->irq); + len += sprintf(page+len, "%-16s 0x%lx\n", "membase", card->membase); + switch (card->cardtype) { + case avm_b1isa: s = "B1 ISA"; break; + case avm_b1pci: s = "B1 PCI"; break; + case avm_b1pcmcia: s = "B1 PCMCIA"; break; + case avm_m1: s = "M1"; break; + case avm_m2: s = "M2"; break; + case avm_t1isa: s = "T1 ISA (HEMA)"; break; + case avm_t1pci: s = "T1 PCI"; break; + case avm_c4: s = "C4"; break; + case avm_c2: s = "C2"; break; + default: s = "???"; break; + } + len += sprintf(page+len, "%-16s %s\n", "type", s); + if ((s = cinfo->version[VER_DRIVER]) != 0) + len += sprintf(page+len, "%-16s %s\n", "ver_driver", s); + if ((s = cinfo->version[VER_CARDTYPE]) != 0) + len += sprintf(page+len, "%-16s %s\n", "ver_cardtype", s); + if ((s = cinfo->version[VER_SERIAL]) != 0) + len += sprintf(page+len, "%-16s %s\n", "ver_serial", s); + + if (card->cardtype != avm_m1) { + flag = ((u8 *)(ctrl->profile.manu))[3]; + if (flag) + len += sprintf(page+len, "%-16s%s%s%s%s%s%s%s\n", + "protocol", + (flag & 0x01) ? " DSS1" : "", + (flag & 0x02) ? " CT1" : "", + (flag & 0x04) ? " VN3" : "", + (flag & 0x08) ? " NI1" : "", + (flag & 0x10) ? " AUSTEL" : "", + (flag & 0x20) ? " ESS" : "", + (flag & 0x40) ? " 1TR6" : "" + ); + } + if (card->cardtype != avm_m1) { + flag = ((u8 *)(ctrl->profile.manu))[5]; + if (flag) + len += sprintf(page+len, "%-16s%s%s%s%s\n", + "linetype", + (flag & 0x01) ? " point to point" : "", + (flag & 0x02) ? " point to multipoint" : "", + (flag & 0x08) ? " leased line without D-channel" : "", + (flag & 0x04) ? " leased line with D-channel" : "" + ); + } + len += sprintf(page+len, "%-16s %s\n", "cardname", cinfo->cardname); + + if (off+count >= len) + *eof = 1; + if (len < off) + return 0; + *start = page + off; + return ((count < len-off) ? count : len-off); +} + +/* ------------------------------------------------------------- */ + +static int c4_add_card(struct capicardparams *p, struct pci_dev *dev, + int nr_controllers) +{ + avmcard *card; + avmctrl_info *cinfo; + int retval; + int i; + + card = b1_alloc_card(nr_controllers); + if (!card) { + printk(KERN_WARNING "c4: no memory.\n"); + retval = -ENOMEM; + goto err; + } + card->dma = avmcard_dma_alloc("c4", dev, 2048+128, 2048+128); + if (!card->dma) { + printk(KERN_WARNING "c4: no memory.\n"); + retval = -ENOMEM; + goto err_free; + } + + sprintf(card->name, "c%d-%x", nr_controllers, p->port); + card->port = p->port; + card->irq = p->irq; + card->membase = p->membase; + card->cardtype = (nr_controllers == 4) ? avm_c4 : avm_c2; + + if (!request_region(card->port, AVMB1_PORTLEN, card->name)) { + printk(KERN_WARNING "c4: ports 0x%03x-0x%03x in use.\n", + card->port, card->port + AVMB1_PORTLEN); + retval = -EBUSY; + goto err_free_dma; + } + + card->mbase = ioremap(card->membase, 128); + if (card->mbase == 0) { + printk(KERN_NOTICE "c4: can't remap memory at 0x%lx\n", + card->membase); + retval = -EIO; + goto err_release_region; + } + + retval = c4_detect(card); + if (retval != 0) { + printk(KERN_NOTICE "c4: NO card at 0x%x error(%d)\n", + card->port, retval); + retval = -EIO; + goto err_unmap; + } + c4_reset(card); + + retval = request_irq(card->irq, c4_interrupt, SA_SHIRQ, card->name, card); + if (retval) { + printk(KERN_ERR "c4: unable to get IRQ %d.\n",card->irq); + retval = -EBUSY; + goto err_unmap; + } + + for (i=0; i < nr_controllers ; i++) { + cinfo = &card->ctrlinfo[i]; + cinfo->capi_ctrl.owner = THIS_MODULE; + cinfo->capi_ctrl.driver_name = "c4"; + cinfo->capi_ctrl.driverdata = cinfo; + cinfo->capi_ctrl.register_appl = c4_register_appl; + cinfo->capi_ctrl.release_appl = c4_release_appl; + cinfo->capi_ctrl.send_message = c4_send_message; + cinfo->capi_ctrl.load_firmware = c4_load_firmware; + cinfo->capi_ctrl.reset_ctr = c4_reset_ctr; + cinfo->capi_ctrl.procinfo = c4_procinfo; + cinfo->capi_ctrl.ctr_read_proc = c4_read_proc; + strcpy(cinfo->capi_ctrl.name, card->name); + + retval = attach_capi_ctr(&cinfo->capi_ctrl); + if (retval) { + printk(KERN_ERR "c4: attach controller failed (%d).\n", i); + for (i--; i >= 0; i--) { + cinfo = &card->ctrlinfo[i]; + detach_capi_ctr(&cinfo->capi_ctrl); + } + goto err_free_irq; + } + if (i == 0) + card->cardnr = cinfo->capi_ctrl.cnr; + } + + printk(KERN_INFO "c4: AVM C%d at i/o %#x, irq %d, mem %#lx\n", + nr_controllers, card->port, card->irq, + card->membase); + pci_set_drvdata(dev, card); + return 0; + + err_free_irq: + free_irq(card->irq, card); + err_unmap: + iounmap(card->mbase); + err_release_region: + release_region(card->port, AVMB1_PORTLEN); + err_free_dma: + avmcard_dma_free(card->dma); + err_free: + b1_free_card(card); + err: + return retval; +} + +/* ------------------------------------------------------------- */ + +static int __devinit c4_probe(struct pci_dev *dev, + const struct pci_device_id *ent) +{ + int nr = ent->driver_data; + int retval = 0; + struct capicardparams param; + + if (pci_enable_device(dev) < 0) { + printk(KERN_ERR "c4: failed to enable AVM-C%d\n", nr); + return -ENODEV; + } + pci_set_master(dev); + + param.port = pci_resource_start(dev, 1); + param.irq = dev->irq; + param.membase = pci_resource_start(dev, 0); + + printk(KERN_INFO "c4: PCI BIOS reports AVM-C%d at i/o %#x, irq %d, mem %#x\n", + nr, param.port, param.irq, param.membase); + + retval = c4_add_card(¶m, dev, nr); + if (retval != 0) { + printk(KERN_ERR "c4: no AVM-C%d at i/o %#x, irq %d detected, mem %#x\n", + nr, param.port, param.irq, param.membase); + return -ENODEV; + } + return 0; +} + +static struct pci_driver c4_pci_driver = { + .name = "c4", + .id_table = c4_pci_tbl, + .probe = c4_probe, + .remove = c4_remove, +}; + +static struct capi_driver capi_driver_c2 = { + .name = "c2", + .revision = "1.0", +}; + +static struct capi_driver capi_driver_c4 = { + .name = "c4", + .revision = "1.0", +}; + +static int __init c4_init(void) +{ + char *p; + char rev[32]; + int err; + + if ((p = strchr(revision, ':')) != 0 && p[1]) { + strlcpy(rev, p + 2, 32); + if ((p = strchr(rev, '$')) != 0 && p > rev) + *(p-1) = 0; + } else + strcpy(rev, "1.0"); + + err = pci_register_driver(&c4_pci_driver); + if (!err) { + strlcpy(capi_driver_c2.revision, rev, 32); + register_capi_driver(&capi_driver_c2); + strlcpy(capi_driver_c4.revision, rev, 32); + register_capi_driver(&capi_driver_c4); + printk(KERN_INFO "c4: revision %s\n", rev); + } + return err; +} + +static void __exit c4_exit(void) +{ + unregister_capi_driver(&capi_driver_c2); + unregister_capi_driver(&capi_driver_c4); + pci_unregister_driver(&c4_pci_driver); +} + +module_init(c4_init); +module_exit(c4_exit); diff --git a/drivers/isdn/hardware/avm/t1isa.c b/drivers/isdn/hardware/avm/t1isa.c new file mode 100644 index 000000000000..cb9d9cee2a64 --- /dev/null +++ b/drivers/isdn/hardware/avm/t1isa.c @@ -0,0 +1,596 @@ +/* $Id: t1isa.c,v 1.1.2.3 2004/02/10 01:07:12 keil Exp $ + * + * Module for AVM T1 HEMA-card. + * + * Copyright 1999 by Carsten Paeth <calle@calle.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/skbuff.h> +#include <linux/delay.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/capi.h> +#include <linux/netdevice.h> +#include <linux/kernelcapi.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <asm/io.h> +#include <linux/isdn/capicmd.h> +#include <linux/isdn/capiutil.h> +#include <linux/isdn/capilli.h> +#include "avmcard.h" + +/* ------------------------------------------------------------- */ + +static char *revision = "$Revision: 1.1.2.3 $"; + +/* ------------------------------------------------------------- */ + +MODULE_DESCRIPTION("CAPI4Linux: Driver for AVM T1 HEMA ISA card"); +MODULE_AUTHOR("Carsten Paeth"); +MODULE_LICENSE("GPL"); + +/* ------------------------------------------------------------- */ + +static int hema_irq_table[16] = +{0, + 0, + 0, + 0x80, /* irq 3 */ + 0, + 0x90, /* irq 5 */ + 0, + 0xA0, /* irq 7 */ + 0, + 0xB0, /* irq 9 */ + 0xC0, /* irq 10 */ + 0xD0, /* irq 11 */ + 0xE0, /* irq 12 */ + 0, + 0, + 0xF0, /* irq 15 */ +}; + +static int t1_detectandinit(unsigned int base, unsigned irq, int cardnr) +{ + unsigned char cregs[8]; + unsigned char reverse_cardnr; + unsigned char dummy; + int i; + + reverse_cardnr = ((cardnr & 0x01) << 3) | ((cardnr & 0x02) << 1) + | ((cardnr & 0x04) >> 1) | ((cardnr & 0x08) >> 3); + cregs[0] = (HEMA_VERSION_ID << 4) | (reverse_cardnr & 0xf); + cregs[1] = 0x00; /* fast & slow link connected to CON1 */ + cregs[2] = 0x05; /* fast link 20MBit, slow link 20 MBit */ + cregs[3] = 0; + cregs[4] = 0x11; /* zero wait state */ + cregs[5] = hema_irq_table[irq & 0xf]; + cregs[6] = 0; + cregs[7] = 0; + + /* + * no one else should use the ISA bus in this moment, + * but no function there to prevent this :-( + * save_flags(flags); cli(); + */ + + /* board reset */ + t1outp(base, T1_RESETBOARD, 0xf); + mdelay(100); + dummy = t1inp(base, T1_FASTLINK+T1_OUTSTAT); /* first read */ + + /* write config */ + dummy = (base >> 4) & 0xff; + for (i=1;i<=0xf;i++) t1outp(base, i, dummy); + t1outp(base, HEMA_PAL_ID & 0xf, dummy); + t1outp(base, HEMA_PAL_ID >> 4, cregs[0]); + for(i=1;i<7;i++) t1outp(base, 0, cregs[i]); + t1outp(base, ((base >> 4)) & 0x3, cregs[7]); + /* restore_flags(flags); */ + + mdelay(100); + t1outp(base, T1_FASTLINK+T1_RESETLINK, 0); + t1outp(base, T1_SLOWLINK+T1_RESETLINK, 0); + mdelay(10); + t1outp(base, T1_FASTLINK+T1_RESETLINK, 1); + t1outp(base, T1_SLOWLINK+T1_RESETLINK, 1); + mdelay(100); + t1outp(base, T1_FASTLINK+T1_RESETLINK, 0); + t1outp(base, T1_SLOWLINK+T1_RESETLINK, 0); + mdelay(10); + t1outp(base, T1_FASTLINK+T1_ANALYSE, 0); + mdelay(5); + t1outp(base, T1_SLOWLINK+T1_ANALYSE, 0); + + if (t1inp(base, T1_FASTLINK+T1_OUTSTAT) != 0x1) /* tx empty */ + return 1; + if (t1inp(base, T1_FASTLINK+T1_INSTAT) != 0x0) /* rx empty */ + return 2; + if (t1inp(base, T1_FASTLINK+T1_IRQENABLE) != 0x0) + return 3; + if ((t1inp(base, T1_FASTLINK+T1_FIFOSTAT) & 0xf0) != 0x70) + return 4; + if ((t1inp(base, T1_FASTLINK+T1_IRQMASTER) & 0x0e) != 0) + return 5; + if ((t1inp(base, T1_FASTLINK+T1_IDENT) & 0x7d) != 1) + return 6; + if (t1inp(base, T1_SLOWLINK+T1_OUTSTAT) != 0x1) /* tx empty */ + return 7; + if ((t1inp(base, T1_SLOWLINK+T1_IRQMASTER) & 0x0e) != 0) + return 8; + if ((t1inp(base, T1_SLOWLINK+T1_IDENT) & 0x7d) != 0) + return 9; + return 0; +} + +static irqreturn_t t1isa_interrupt(int interrupt, void *devptr, struct pt_regs *regs) +{ + avmcard *card = devptr; + avmctrl_info *cinfo = &card->ctrlinfo[0]; + struct capi_ctr *ctrl = &cinfo->capi_ctrl; + unsigned char b1cmd; + struct sk_buff *skb; + + unsigned ApplId; + unsigned MsgLen; + unsigned DataB3Len; + unsigned NCCI; + unsigned WindowSize; + unsigned long flags; + + spin_lock_irqsave(&card->lock, flags); + + while (b1_rx_full(card->port)) { + + b1cmd = b1_get_byte(card->port); + + switch (b1cmd) { + + case RECEIVE_DATA_B3_IND: + + ApplId = (unsigned) b1_get_word(card->port); + MsgLen = t1_get_slice(card->port, card->msgbuf); + DataB3Len = t1_get_slice(card->port, card->databuf); + spin_unlock_irqrestore(&card->lock, flags); + + if (MsgLen < 30) { /* not CAPI 64Bit */ + memset(card->msgbuf+MsgLen, 0, 30-MsgLen); + MsgLen = 30; + CAPIMSG_SETLEN(card->msgbuf, 30); + } + if (!(skb = alloc_skb(DataB3Len+MsgLen, GFP_ATOMIC))) { + printk(KERN_ERR "%s: incoming packet dropped\n", + card->name); + } else { + memcpy(skb_put(skb, MsgLen), card->msgbuf, MsgLen); + memcpy(skb_put(skb, DataB3Len), card->databuf, DataB3Len); + capi_ctr_handle_message(ctrl, ApplId, skb); + } + break; + + case RECEIVE_MESSAGE: + + ApplId = (unsigned) b1_get_word(card->port); + MsgLen = t1_get_slice(card->port, card->msgbuf); + spin_unlock_irqrestore(&card->lock, flags); + if (!(skb = alloc_skb(MsgLen, GFP_ATOMIC))) { + printk(KERN_ERR "%s: incoming packet dropped\n", + card->name); + } else { + memcpy(skb_put(skb, MsgLen), card->msgbuf, MsgLen); + if (CAPIMSG_CMD(skb->data) == CAPI_DATA_B3) + capilib_data_b3_conf(&cinfo->ncci_head, ApplId, + CAPIMSG_NCCI(skb->data), + CAPIMSG_MSGID(skb->data)); + + capi_ctr_handle_message(ctrl, ApplId, skb); + } + break; + + case RECEIVE_NEW_NCCI: + + ApplId = b1_get_word(card->port); + NCCI = b1_get_word(card->port); + WindowSize = b1_get_word(card->port); + spin_unlock_irqrestore(&card->lock, flags); + + capilib_new_ncci(&cinfo->ncci_head, ApplId, NCCI, WindowSize); + + break; + + case RECEIVE_FREE_NCCI: + + ApplId = b1_get_word(card->port); + NCCI = b1_get_word(card->port); + spin_unlock_irqrestore(&card->lock, flags); + + if (NCCI != 0xffffffff) + capilib_free_ncci(&cinfo->ncci_head, ApplId, NCCI); + + break; + + case RECEIVE_START: + b1_put_byte(card->port, SEND_POLLACK); + spin_unlock_irqrestore(&card->lock, flags); + capi_ctr_resume_output(ctrl); + break; + + case RECEIVE_STOP: + spin_unlock_irqrestore(&card->lock, flags); + capi_ctr_suspend_output(ctrl); + break; + + case RECEIVE_INIT: + + cinfo->versionlen = t1_get_slice(card->port, cinfo->versionbuf); + spin_unlock_irqrestore(&card->lock, flags); + b1_parse_version(cinfo); + printk(KERN_INFO "%s: %s-card (%s) now active\n", + card->name, + cinfo->version[VER_CARDTYPE], + cinfo->version[VER_DRIVER]); + capi_ctr_ready(ctrl); + break; + + case RECEIVE_TASK_READY: + ApplId = (unsigned) b1_get_word(card->port); + MsgLen = t1_get_slice(card->port, card->msgbuf); + spin_unlock_irqrestore(&card->lock, flags); + card->msgbuf[MsgLen] = 0; + while ( MsgLen > 0 + && ( card->msgbuf[MsgLen-1] == '\n' + || card->msgbuf[MsgLen-1] == '\r')) { + card->msgbuf[MsgLen-1] = 0; + MsgLen--; + } + printk(KERN_INFO "%s: task %d \"%s\" ready.\n", + card->name, ApplId, card->msgbuf); + break; + + case RECEIVE_DEBUGMSG: + MsgLen = t1_get_slice(card->port, card->msgbuf); + spin_unlock_irqrestore(&card->lock, flags); + card->msgbuf[MsgLen] = 0; + while ( MsgLen > 0 + && ( card->msgbuf[MsgLen-1] == '\n' + || card->msgbuf[MsgLen-1] == '\r')) { + card->msgbuf[MsgLen-1] = 0; + MsgLen--; + } + printk(KERN_INFO "%s: DEBUG: %s\n", card->name, card->msgbuf); + break; + + + case 0xff: + spin_unlock_irqrestore(&card->lock, flags); + printk(KERN_ERR "%s: card reseted ?\n", card->name); + return IRQ_HANDLED; + default: + spin_unlock_irqrestore(&card->lock, flags); + printk(KERN_ERR "%s: b1_interrupt: 0x%x ???\n", + card->name, b1cmd); + return IRQ_NONE; + } + } + return IRQ_HANDLED; +} + +/* ------------------------------------------------------------- */ + +static int t1isa_load_firmware(struct capi_ctr *ctrl, capiloaddata *data) +{ + avmctrl_info *cinfo = (avmctrl_info *)(ctrl->driverdata); + avmcard *card = cinfo->card; + unsigned int port = card->port; + unsigned long flags; + int retval; + + t1_disable_irq(port); + b1_reset(port); + + if ((retval = b1_load_t4file(card, &data->firmware))) { + b1_reset(port); + printk(KERN_ERR "%s: failed to load t4file!!\n", + card->name); + return retval; + } + + if (data->configuration.len > 0 && data->configuration.data) { + if ((retval = b1_load_config(card, &data->configuration))) { + b1_reset(port); + printk(KERN_ERR "%s: failed to load config!!\n", + card->name); + return retval; + } + } + + if (!b1_loaded(card)) { + printk(KERN_ERR "%s: failed to load t4file.\n", card->name); + return -EIO; + } + + spin_lock_irqsave(&card->lock, flags); + b1_setinterrupt(port, card->irq, card->cardtype); + b1_put_byte(port, SEND_INIT); + b1_put_word(port, CAPI_MAXAPPL); + b1_put_word(port, AVM_NCCI_PER_CHANNEL*30); + b1_put_word(port, ctrl->cnr - 1); + spin_unlock_irqrestore(&card->lock, flags); + + return 0; +} + +void t1isa_reset_ctr(struct capi_ctr *ctrl) +{ + avmctrl_info *cinfo = (avmctrl_info *)(ctrl->driverdata); + avmcard *card = cinfo->card; + unsigned int port = card->port; + + t1_disable_irq(port); + b1_reset(port); + b1_reset(port); + + memset(cinfo->version, 0, sizeof(cinfo->version)); + capilib_release(&cinfo->ncci_head); + capi_ctr_reseted(ctrl); +} + +static void t1isa_remove(struct pci_dev *pdev) +{ + avmctrl_info *cinfo = pci_get_drvdata(pdev); + avmcard *card; + + if (!cinfo) + return; + + card = cinfo->card; + + t1_disable_irq(card->port); + b1_reset(card->port); + b1_reset(card->port); + t1_reset(card->port); + + detach_capi_ctr(&cinfo->capi_ctrl); + free_irq(card->irq, card); + release_region(card->port, AVMB1_PORTLEN); + b1_free_card(card); +} + +/* ------------------------------------------------------------- */ + +static u16 t1isa_send_message(struct capi_ctr *ctrl, struct sk_buff *skb); +static char *t1isa_procinfo(struct capi_ctr *ctrl); + +static int t1isa_probe(struct pci_dev *pdev, int cardnr) +{ + avmctrl_info *cinfo; + avmcard *card; + int retval; + + card = b1_alloc_card(1); + if (!card) { + printk(KERN_WARNING "t1isa: no memory.\n"); + retval = -ENOMEM; + goto err; + } + + cinfo = card->ctrlinfo; + card->port = pci_resource_start(pdev, 0); + card->irq = pdev->irq; + card->cardtype = avm_t1isa; + card->cardnr = cardnr; + sprintf(card->name, "t1isa-%x", card->port); + + if (!(((card->port & 0x7) == 0) && ((card->port & 0x30) != 0x30))) { + printk(KERN_WARNING "t1isa: invalid port 0x%x.\n", card->port); + retval = -EINVAL; + goto err_free; + } + if (hema_irq_table[card->irq & 0xf] == 0) { + printk(KERN_WARNING "t1isa: irq %d not valid.\n", card->irq); + retval = -EINVAL; + goto err_free; + } + if (!request_region(card->port, AVMB1_PORTLEN, card->name)) { + printk(KERN_INFO "t1isa: ports 0x%03x-0x%03x in use.\n", + card->port, card->port + AVMB1_PORTLEN); + retval = -EBUSY; + goto err_free; + } + retval = request_irq(card->irq, t1isa_interrupt, 0, card->name, card); + if (retval) { + printk(KERN_INFO "t1isa: unable to get IRQ %d.\n", card->irq); + retval = -EBUSY; + goto err_release_region; + } + + if ((retval = t1_detectandinit(card->port, card->irq, card->cardnr)) != 0) { + printk(KERN_INFO "t1isa: NO card at 0x%x (%d)\n", + card->port, retval); + retval = -ENODEV; + goto err_free_irq; + } + t1_disable_irq(card->port); + b1_reset(card->port); + + cinfo->capi_ctrl.owner = THIS_MODULE; + cinfo->capi_ctrl.driver_name = "t1isa"; + cinfo->capi_ctrl.driverdata = cinfo; + cinfo->capi_ctrl.register_appl = b1_register_appl; + cinfo->capi_ctrl.release_appl = b1_release_appl; + cinfo->capi_ctrl.send_message = t1isa_send_message; + cinfo->capi_ctrl.load_firmware = t1isa_load_firmware; + cinfo->capi_ctrl.reset_ctr = t1isa_reset_ctr; + cinfo->capi_ctrl.procinfo = t1isa_procinfo; + cinfo->capi_ctrl.ctr_read_proc = b1ctl_read_proc; + strcpy(cinfo->capi_ctrl.name, card->name); + + retval = attach_capi_ctr(&cinfo->capi_ctrl); + if (retval) { + printk(KERN_INFO "t1isa: attach controller failed.\n"); + goto err_free_irq; + } + + printk(KERN_INFO "t1isa: AVM T1 ISA at i/o %#x, irq %d, card %d\n", + card->port, card->irq, card->cardnr); + + pci_set_drvdata(pdev, cinfo); + return 0; + + err_free_irq: + free_irq(card->irq, card); + err_release_region: + release_region(card->port, AVMB1_PORTLEN); + err_free: + b1_free_card(card); + err: + return retval; +} + +static u16 t1isa_send_message(struct capi_ctr *ctrl, struct sk_buff *skb) +{ + avmctrl_info *cinfo = (avmctrl_info *)(ctrl->driverdata); + avmcard *card = cinfo->card; + unsigned int port = card->port; + unsigned long flags; + u16 len = CAPIMSG_LEN(skb->data); + u8 cmd = CAPIMSG_COMMAND(skb->data); + u8 subcmd = CAPIMSG_SUBCOMMAND(skb->data); + u16 dlen, retval; + + if (CAPICMD(cmd, subcmd) == CAPI_DATA_B3_REQ) { + retval = capilib_data_b3_req(&cinfo->ncci_head, + CAPIMSG_APPID(skb->data), + CAPIMSG_NCCI(skb->data), + CAPIMSG_MSGID(skb->data)); + if (retval != CAPI_NOERROR) + return retval; + + dlen = CAPIMSG_DATALEN(skb->data); + + spin_lock_irqsave(&card->lock, flags); + b1_put_byte(port, SEND_DATA_B3_REQ); + t1_put_slice(port, skb->data, len); + t1_put_slice(port, skb->data + len, dlen); + spin_unlock_irqrestore(&card->lock, flags); + } else { + + spin_lock_irqsave(&card->lock, flags); + b1_put_byte(port, SEND_MESSAGE); + t1_put_slice(port, skb->data, len); + spin_unlock_irqrestore(&card->lock, flags); + } + + dev_kfree_skb_any(skb); + return CAPI_NOERROR; +} +/* ------------------------------------------------------------- */ + +static char *t1isa_procinfo(struct capi_ctr *ctrl) +{ + avmctrl_info *cinfo = (avmctrl_info *)(ctrl->driverdata); + + if (!cinfo) + return ""; + sprintf(cinfo->infobuf, "%s %s 0x%x %d %d", + cinfo->cardname[0] ? cinfo->cardname : "-", + cinfo->version[VER_DRIVER] ? cinfo->version[VER_DRIVER] : "-", + cinfo->card ? cinfo->card->port : 0x0, + cinfo->card ? cinfo->card->irq : 0, + cinfo->card ? cinfo->card->cardnr : 0 + ); + return cinfo->infobuf; +} + + +/* ------------------------------------------------------------- */ + +#define MAX_CARDS 4 +static struct pci_dev isa_dev[MAX_CARDS]; +static int io[MAX_CARDS]; +static int irq[MAX_CARDS]; +static int cardnr[MAX_CARDS]; + +MODULE_PARM(io, "1-" __MODULE_STRING(MAX_CARDS) "i"); +MODULE_PARM(irq, "1-" __MODULE_STRING(MAX_CARDS) "i"); +MODULE_PARM(cardnr, "1-" __MODULE_STRING(MAX_CARDS) "i"); +MODULE_PARM_DESC(io, "I/O base address(es)"); +MODULE_PARM_DESC(irq, "IRQ number(s) (assigned)"); +MODULE_PARM_DESC(cardnr, "Card number(s) (as jumpered)"); + +static int t1isa_add_card(struct capi_driver *driver, capicardparams *data) +{ + int i; + + for (i = 0; i < MAX_CARDS; i++) { + if (isa_dev[i].resource[0].start) + continue; + + isa_dev[i].resource[0].start = data->port; + isa_dev[i].irq = data->irq; + + if (t1isa_probe(&isa_dev[i], data->cardnr) == 0) + return 0; + } + return -ENODEV; +} + +static struct capi_driver capi_driver_t1isa = { + .name = "t1isa", + .revision = "1.0", + .add_card = t1isa_add_card, +}; + +static int __init t1isa_init(void) +{ + char rev[32]; + char *p; + int i; + + if ((p = strchr(revision, ':')) != 0 && p[1]) { + strlcpy(rev, p + 2, 32); + if ((p = strchr(rev, '$')) != 0 && p > rev) + *(p-1) = 0; + } else + strcpy(rev, "1.0"); + + for (i = 0; i < MAX_CARDS; i++) { + if (!io[i]) + break; + + isa_dev[i].resource[0].start = io[i]; + isa_dev[i].irq = irq[i]; + + if (t1isa_probe(&isa_dev[i], cardnr[i]) != 0) + return -ENODEV; + } + + strlcpy(capi_driver_t1isa.revision, rev, 32); + register_capi_driver(&capi_driver_t1isa); + printk(KERN_INFO "t1isa: revision %s\n", rev); + + return 0; +} + +static void __exit t1isa_exit(void) +{ + int i; + + for (i = 0; i < MAX_CARDS; i++) { + if (!io[i]) + break; + + t1isa_remove(&isa_dev[i]); + } +} + +module_init(t1isa_init); +module_exit(t1isa_exit); diff --git a/drivers/isdn/hardware/avm/t1pci.c b/drivers/isdn/hardware/avm/t1pci.c new file mode 100644 index 000000000000..2ceec8e8419f --- /dev/null +++ b/drivers/isdn/hardware/avm/t1pci.c @@ -0,0 +1,260 @@ +/* $Id: t1pci.c,v 1.1.2.2 2004/01/16 21:09:27 keil Exp $ + * + * Module for AVM T1 PCI-card. + * + * Copyright 1999 by Carsten Paeth <calle@calle.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/skbuff.h> +#include <linux/delay.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/pci.h> +#include <linux/capi.h> +#include <linux/init.h> +#include <asm/io.h> +#include <linux/isdn/capicmd.h> +#include <linux/isdn/capiutil.h> +#include <linux/isdn/capilli.h> +#include "avmcard.h" + +#undef CONFIG_T1PCI_DEBUG +#undef CONFIG_T1PCI_POLLDEBUG + +/* ------------------------------------------------------------- */ +static char *revision = "$Revision: 1.1.2.2 $"; +/* ------------------------------------------------------------- */ + +static struct pci_device_id t1pci_pci_tbl[] = { + { PCI_VENDOR_ID_AVM, PCI_DEVICE_ID_AVM_T1, PCI_ANY_ID, PCI_ANY_ID }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(pci, t1pci_pci_tbl); +MODULE_DESCRIPTION("CAPI4Linux: Driver for AVM T1 PCI card"); +MODULE_AUTHOR("Carsten Paeth"); +MODULE_LICENSE("GPL"); + +/* ------------------------------------------------------------- */ + +static char *t1pci_procinfo(struct capi_ctr *ctrl); + +static int t1pci_add_card(struct capicardparams *p, struct pci_dev *pdev) +{ + avmcard *card; + avmctrl_info *cinfo; + int retval; + + card = b1_alloc_card(1); + if (!card) { + printk(KERN_WARNING "t1pci: no memory.\n"); + retval = -ENOMEM; + goto err; + } + + card->dma = avmcard_dma_alloc("t1pci", pdev, 2048+128, 2048+128); + if (!card->dma) { + printk(KERN_WARNING "t1pci: no memory.\n"); + retval = -ENOMEM; + goto err_free; + } + + cinfo = card->ctrlinfo; + sprintf(card->name, "t1pci-%x", p->port); + card->port = p->port; + card->irq = p->irq; + card->membase = p->membase; + card->cardtype = avm_t1pci; + + if (!request_region(card->port, AVMB1_PORTLEN, card->name)) { + printk(KERN_WARNING "t1pci: ports 0x%03x-0x%03x in use.\n", + card->port, card->port + AVMB1_PORTLEN); + retval = -EBUSY; + goto err_free_dma; + } + + card->mbase = ioremap(card->membase, 64); + if (!card->mbase) { + printk(KERN_NOTICE "t1pci: can't remap memory at 0x%lx\n", + card->membase); + retval = -EIO; + goto err_release_region; + } + + b1dma_reset(card); + + retval = t1pci_detect(card); + if (retval != 0) { + if (retval < 6) + printk(KERN_NOTICE "t1pci: NO card at 0x%x (%d)\n", + card->port, retval); + else + printk(KERN_NOTICE "t1pci: card at 0x%x, but cable not connected or T1 has no power (%d)\n", + card->port, retval); + retval = -EIO; + goto err_unmap; + } + b1dma_reset(card); + + retval = request_irq(card->irq, b1dma_interrupt, SA_SHIRQ, card->name, card); + if (retval) { + printk(KERN_ERR "t1pci: unable to get IRQ %d.\n", card->irq); + retval = -EBUSY; + goto err_unmap; + } + + cinfo->capi_ctrl.owner = THIS_MODULE; + cinfo->capi_ctrl.driver_name = "t1pci"; + cinfo->capi_ctrl.driverdata = cinfo; + cinfo->capi_ctrl.register_appl = b1dma_register_appl; + cinfo->capi_ctrl.release_appl = b1dma_release_appl; + cinfo->capi_ctrl.send_message = b1dma_send_message; + cinfo->capi_ctrl.load_firmware = b1dma_load_firmware; + cinfo->capi_ctrl.reset_ctr = b1dma_reset_ctr; + cinfo->capi_ctrl.procinfo = t1pci_procinfo; + cinfo->capi_ctrl.ctr_read_proc = b1dmactl_read_proc; + strcpy(cinfo->capi_ctrl.name, card->name); + + retval = attach_capi_ctr(&cinfo->capi_ctrl); + if (retval) { + printk(KERN_ERR "t1pci: attach controller failed.\n"); + retval = -EBUSY; + goto err_free_irq; + } + card->cardnr = cinfo->capi_ctrl.cnr; + + printk(KERN_INFO "t1pci: AVM T1 PCI at i/o %#x, irq %d, mem %#lx\n", + card->port, card->irq, card->membase); + + pci_set_drvdata(pdev, card); + return 0; + + err_free_irq: + free_irq(card->irq, card); + err_unmap: + iounmap(card->mbase); + err_release_region: + release_region(card->port, AVMB1_PORTLEN); + err_free_dma: + avmcard_dma_free(card->dma); + err_free: + b1_free_card(card); + err: + return retval; +} + +/* ------------------------------------------------------------- */ + +static void t1pci_remove(struct pci_dev *pdev) +{ + avmcard *card = pci_get_drvdata(pdev); + avmctrl_info *cinfo = card->ctrlinfo; + + b1dma_reset(card); + + detach_capi_ctr(&cinfo->capi_ctrl); + free_irq(card->irq, card); + iounmap(card->mbase); + release_region(card->port, AVMB1_PORTLEN); + avmcard_dma_free(card->dma); + b1_free_card(card); +} + +/* ------------------------------------------------------------- */ + +static char *t1pci_procinfo(struct capi_ctr *ctrl) +{ + avmctrl_info *cinfo = (avmctrl_info *)(ctrl->driverdata); + + if (!cinfo) + return ""; + sprintf(cinfo->infobuf, "%s %s 0x%x %d 0x%lx", + cinfo->cardname[0] ? cinfo->cardname : "-", + cinfo->version[VER_DRIVER] ? cinfo->version[VER_DRIVER] : "-", + cinfo->card ? cinfo->card->port : 0x0, + cinfo->card ? cinfo->card->irq : 0, + cinfo->card ? cinfo->card->membase : 0 + ); + return cinfo->infobuf; +} + +/* ------------------------------------------------------------- */ + +static int __devinit t1pci_probe(struct pci_dev *dev, + const struct pci_device_id *ent) +{ + struct capicardparams param; + int retval; + + if (pci_enable_device(dev) < 0) { + printk(KERN_ERR "t1pci: failed to enable AVM-T1-PCI\n"); + return -ENODEV; + } + pci_set_master(dev); + + param.port = pci_resource_start(dev, 1); + param.irq = dev->irq; + param.membase = pci_resource_start(dev, 0); + + printk(KERN_INFO "t1pci: PCI BIOS reports AVM-T1-PCI at i/o %#x, irq %d, mem %#x\n", + param.port, param.irq, param.membase); + + retval = t1pci_add_card(¶m, dev); + if (retval != 0) { + printk(KERN_ERR "t1pci: no AVM-T1-PCI at i/o %#x, irq %d detected, mem %#x\n", + param.port, param.irq, param.membase); + return -ENODEV; + } + return 0; +} + +static struct pci_driver t1pci_pci_driver = { + .name = "t1pci", + .id_table = t1pci_pci_tbl, + .probe = t1pci_probe, + .remove = t1pci_remove, +}; + +static struct capi_driver capi_driver_t1pci = { + .name = "t1pci", + .revision = "1.0", +}; + +static int __init t1pci_init(void) +{ + char *p; + char rev[32]; + int err; + + if ((p = strchr(revision, ':')) != 0 && p[1]) { + strlcpy(rev, p + 2, 32); + if ((p = strchr(rev, '$')) != 0 && p > rev) + *(p-1) = 0; + } else + strcpy(rev, "1.0"); + + err = pci_register_driver(&t1pci_pci_driver); + if (!err) { + strlcpy(capi_driver_t1pci.revision, rev, 32); + register_capi_driver(&capi_driver_t1pci); + printk(KERN_INFO "t1pci: revision %s\n", rev); + } + return err; +} + +static void __exit t1pci_exit(void) +{ + unregister_capi_driver(&capi_driver_t1pci); + pci_unregister_driver(&t1pci_pci_driver); +} + +module_init(t1pci_init); +module_exit(t1pci_exit); diff --git a/drivers/isdn/hardware/eicon/Kconfig b/drivers/isdn/hardware/eicon/Kconfig new file mode 100644 index 000000000000..51e66bc64208 --- /dev/null +++ b/drivers/isdn/hardware/eicon/Kconfig @@ -0,0 +1,53 @@ +# +# ISDN DIVAS Eicon driver +# + +menu "Active Eicon DIVA Server cards" + depends on NET && ISDN && ISDN_CAPI!=n + +config CAPI_EICON + bool "Support Eicon cards" + help + Enable support for Eicon Networks active ISDN cards. + +config ISDN_DIVAS + tristate "Support Eicon DIVA Server cards" + depends on CAPI_EICON && PROC_FS && PCI + help + Say Y here if you have an Eicon Networks DIVA Server PCI ISDN card. + In order to use this card, additional firmware is necessary, which + has to be downloaded into the card using the divactrl utility. + +config ISDN_DIVAS_BRIPCI + bool "DIVA Server BRI/PCI support" + depends on ISDN_DIVAS + help + Enable support for DIVA Server BRI-PCI. + +config ISDN_DIVAS_PRIPCI + bool "DIVA Server PRI/PCI support" + depends on ISDN_DIVAS + help + Enable support for DIVA Server PRI-PCI. + +config ISDN_DIVAS_DIVACAPI + tristate "DIVA CAPI2.0 interface support" + depends on ISDN_DIVAS && ISDN_CAPI + help + You need this to provide the CAPI interface + for DIVA Server cards. + +config ISDN_DIVAS_USERIDI + tristate "DIVA User-IDI interface support" + depends on ISDN_DIVAS + help + Enable support for user-mode IDI interface. + +config ISDN_DIVAS_MAINT + tristate "DIVA Maint driver support" + depends on ISDN_DIVAS && m + help + Enable Divas Maintainance driver. + +endmenu + diff --git a/drivers/isdn/hardware/eicon/Makefile b/drivers/isdn/hardware/eicon/Makefile new file mode 100644 index 000000000000..4fa7fdb7df0d --- /dev/null +++ b/drivers/isdn/hardware/eicon/Makefile @@ -0,0 +1,23 @@ +# Makefile for the Eicon DIVA ISDN drivers. + +# Each configuration option enables a list of files. + +obj-$(CONFIG_ISDN_DIVAS) += divadidd.o divas.o +obj-$(CONFIG_ISDN_DIVAS_MAINT) += diva_mnt.o +obj-$(CONFIG_ISDN_DIVAS_USERIDI) += diva_idi.o +obj-$(CONFIG_ISDN_DIVAS_DIVACAPI) += divacapi.o + +# Multipart objects. + +divas-y := divasmain.o divasfunc.o di.o io.o istream.o \ + diva.o divasproc.o diva_dma.o +divas-$(CONFIG_ISDN_DIVAS_BRIPCI) += os_bri.o s_bri.o os_4bri.o s_4bri.o +divas-$(CONFIG_ISDN_DIVAS_PRIPCI) += os_pri.o s_pri.o + +divacapi-y := capimain.o capifunc.o message.o capidtmf.o + +divadidd-y := diva_didd.o diddfunc.o dadapter.o + +diva_mnt-y := divamnt.o mntfunc.o debug.o maintidi.o + +diva_idi-y := divasi.o idifunc.o um_idi.o dqueue.o diff --git a/drivers/isdn/hardware/eicon/adapter.h b/drivers/isdn/hardware/eicon/adapter.h new file mode 100644 index 000000000000..71a7c2f084a7 --- /dev/null +++ b/drivers/isdn/hardware/eicon/adapter.h @@ -0,0 +1,17 @@ +/* $Id: adapter.h,v 1.4 2004/03/21 17:26:01 armin Exp $ */ + +#ifndef __DIVA_USER_MODE_IDI_ADAPTER_H__ +#define __DIVA_USER_MODE_IDI_ADAPTER_H__ + +#define DIVA_UM_IDI_ADAPTER_REMOVED 0x00000001 + +typedef struct _diva_um_idi_adapter { + struct list_head link; + DESCRIPTOR d; + int adapter_nr; + struct list_head entity_q; /* entities linked to this adapter */ + dword status; +} diva_um_idi_adapter_t; + + +#endif diff --git a/drivers/isdn/hardware/eicon/capi20.h b/drivers/isdn/hardware/eicon/capi20.h new file mode 100644 index 000000000000..7ebcccda74d8 --- /dev/null +++ b/drivers/isdn/hardware/eicon/capi20.h @@ -0,0 +1,699 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +#ifndef _INC_CAPI20 +#define _INC_CAPI20 + /* operations on message queues */ + /* the common device type for CAPI20 drivers */ +#define FILE_DEVICE_CAPI20 0x8001 + /* DEVICE_CONTROL codes for user and kernel mode applications */ +#define CAPI20_CTL_REGISTER 0x0801 +#define CAPI20_CTL_RELEASE 0x0802 +#define CAPI20_CTL_GET_MANUFACTURER 0x0805 +#define CAPI20_CTL_GET_VERSION 0x0806 +#define CAPI20_CTL_GET_SERIAL 0x0807 +#define CAPI20_CTL_GET_PROFILE 0x0808 + /* INTERNAL_DEVICE_CONTROL codes for kernel mode applicatios only */ +#define CAPI20_CTL_PUT_MESSAGE 0x0803 +#define CAPI20_CTL_GET_MESSAGE 0x0804 + /* the wrapped codes as required by the system */ +#define CAPI_CTL_CODE(f,m) CTL_CODE(FILE_DEVICE_CAPI20,f,m,FILE_ANY_ACCESS) +#define IOCTL_CAPI_REGISTER CAPI_CTL_CODE(CAPI20_CTL_REGISTER,METHOD_BUFFERED) +#define IOCTL_CAPI_RELEASE CAPI_CTL_CODE(CAPI20_CTL_RELEASE,METHOD_BUFFERED) +#define IOCTL_CAPI_GET_MANUFACTURER CAPI_CTL_CODE(CAPI20_CTL_GET_MANUFACTURER,METHOD_BUFFERED) +#define IOCTL_CAPI_GET_VERSION CAPI_CTL_CODE(CAPI20_CTL_GET_VERSION,METHOD_BUFFERED) +#define IOCTL_CAPI_GET_SERIAL CAPI_CTL_CODE(CAPI20_CTL_GET_SERIAL,METHOD_BUFFERED) +#define IOCTL_CAPI_GET_PROFILE CAPI_CTL_CODE(CAPI20_CTL_GET_PROFILE,METHOD_BUFFERED) +#define IOCTL_CAPI_PUT_MESSAGE CAPI_CTL_CODE(CAPI20_CTL_PUT_MESSAGE,METHOD_BUFFERED) +#define IOCTL_CAPI_GET_MESSAGE CAPI_CTL_CODE(CAPI20_CTL_GET_MESSAGE,METHOD_BUFFERED) +struct divas_capi_register_params { + word MessageBufferSize; + word maxLogicalConnection; + word maxBDataBlocks; + word maxBDataLen; +}; +struct divas_capi_version { + word CapiMajor; + word CapiMinor; + word ManuMajor; + word ManuMinor; +}; +typedef struct api_profile_s { + word Number; + word Channels; + dword Global_Options; + dword B1_Protocols; + dword B2_Protocols; + dword B3_Protocols; +} API_PROFILE; + /* ISDN Common API message types */ +#define _ALERT_R 0x8001 +#define _CONNECT_R 0x8002 +#define _CONNECT_I 0x8202 +#define _CONNECT_ACTIVE_I 0x8203 +#define _DISCONNECT_R 0x8004 +#define _DISCONNECT_I 0x8204 +#define _LISTEN_R 0x8005 +#define _INFO_R 0x8008 +#define _INFO_I 0x8208 +#define _SELECT_B_REQ 0x8041 +#define _FACILITY_R 0x8080 +#define _FACILITY_I 0x8280 +#define _CONNECT_B3_R 0x8082 +#define _CONNECT_B3_I 0x8282 +#define _CONNECT_B3_ACTIVE_I 0x8283 +#define _DISCONNECT_B3_R 0x8084 +#define _DISCONNECT_B3_I 0x8284 +#define _DATA_B3_R 0x8086 +#define _DATA_B3_I 0x8286 +#define _RESET_B3_R 0x8087 +#define _RESET_B3_I 0x8287 +#define _CONNECT_B3_T90_ACTIVE_I 0x8288 +#define _MANUFACTURER_R 0x80ff +#define _MANUFACTURER_I 0x82ff + /* OR this to convert a REQUEST to a CONFIRM */ +#define CONFIRM 0x0100 + /* OR this to convert a INDICATION to a RESPONSE */ +#define RESPONSE 0x0100 +/*------------------------------------------------------------------*/ +/* diehl isdn private MANUFACTURER codes */ +/*------------------------------------------------------------------*/ +#define _DI_MANU_ID 0x44444944 +#define _DI_ASSIGN_PLCI 0x0001 +#define _DI_ADV_CODEC 0x0002 +#define _DI_DSP_CTRL 0x0003 +#define _DI_SIG_CTRL 0x0004 +#define _DI_RXT_CTRL 0x0005 +#define _DI_IDI_CTRL 0x0006 +#define _DI_CFG_CTRL 0x0007 +#define _DI_REMOVE_CODEC 0x0008 +#define _DI_OPTIONS_REQUEST 0x0009 +#define _DI_SSEXT_CTRL 0x000a +#define _DI_NEGOTIATE_B3 0x000b +/*------------------------------------------------------------------*/ +/* parameter structures */ +/*------------------------------------------------------------------*/ + /* ALERT-REQUEST */ +typedef struct { + byte structs[1]; /* Additional Info */ +} _ALT_REQP; + /* ALERT-CONFIRM */ +typedef struct { + word Info; +} _ALT_CONP; + /* CONNECT-REQUEST */ +typedef struct { + word CIP_Value; + byte structs[1]; /* Called party number, + Called party subaddress, + Calling party number, + Calling party subaddress, + B_protocol, + BC, + LLC, + HLC, + Additional Info */ +} _CON_REQP; + /* CONNECT-CONFIRM */ +typedef struct { + word Info; +} _CON_CONP; + /* CONNECT-INDICATION */ +typedef struct { + word CIP_Value; + byte structs[1]; /* Called party number, + Called party subaddress, + Calling party number, + Calling party subaddress, + BC, + LLC, + HLC, + Additional Info */ +} _CON_INDP; + /* CONNECT-RESPONSE */ +typedef struct { + word Accept; + byte structs[1]; /* B_protocol, + Connected party number, + Connected party subaddress, + LLC */ +} _CON_RESP; + /* CONNECT-ACTIVE-INDICATION */ +typedef struct { + byte structs[1]; /* Connected party number, + Connected party subaddress, + LLC */ +} _CON_A_INDP; + /* CONNECT-ACTIVE-RESPONSE */ +typedef struct { + byte structs[1]; /* empty */ +} _CON_A_RESP; + /* DISCONNECT-REQUEST */ +typedef struct { + byte structs[1]; /* Additional Info */ +} _DIS_REQP; + /* DISCONNECT-CONFIRM */ +typedef struct { + word Info; +} _DIS_CONP; + /* DISCONNECT-INDICATION */ +typedef struct { + word Info; +} _DIS_INDP; + /* DISCONNECT-RESPONSE */ +typedef struct { + byte structs[1]; /* empty */ +} _DIS_RESP; + /* LISTEN-REQUEST */ +typedef struct { + dword Info_Mask; + dword CIP_Mask; + byte structs[1]; /* Calling party number, + Calling party subaddress */ +} _LIS_REQP; + /* LISTEN-CONFIRM */ +typedef struct { + word Info; +} _LIS_CONP; + /* INFO-REQUEST */ +typedef struct { + byte structs[1]; /* Called party number, + Additional Info */ +} _INF_REQP; + /* INFO-CONFIRM */ +typedef struct { + word Info; +} _INF_CONP; + /* INFO-INDICATION */ +typedef struct { + word Number; + byte structs[1]; /* Info element */ +} _INF_INDP; + /* INFO-RESPONSE */ +typedef struct { + byte structs[1]; /* empty */ +} _INF_RESP; + /* SELECT-B-REQUEST */ +typedef struct { + byte structs[1]; /* B-protocol */ +} _SEL_B_REQP; + /* SELECT-B-CONFIRM */ +typedef struct { + word Info; +} _SEL_B_CONP; + /* FACILITY-REQUEST */ +typedef struct { + word Selector; + byte structs[1]; /* Facility parameters */ +} _FAC_REQP; + /* FACILITY-CONFIRM STRUCT FOR SUPPLEMENT. SERVICES */ +typedef struct { + byte struct_length; + word function; + byte length; + word SupplementaryServiceInfo; + dword SupportedServices; +} _FAC_CON_STRUCTS; + /* FACILITY-CONFIRM */ +typedef struct { + word Info; + word Selector; + byte structs[1]; /* Facility parameters */ +} _FAC_CONP; + /* FACILITY-INDICATION */ +typedef struct { + word Selector; + byte structs[1]; /* Facility parameters */ +} _FAC_INDP; + /* FACILITY-RESPONSE */ +typedef struct { + word Selector; + byte structs[1]; /* Facility parameters */ +} _FAC_RESP; + /* CONNECT-B3-REQUEST */ +typedef struct { + byte structs[1]; /* NCPI */ +} _CON_B3_REQP; + /* CONNECT-B3-CONFIRM */ +typedef struct { + word Info; +} _CON_B3_CONP; + /* CONNECT-B3-INDICATION */ +typedef struct { + byte structs[1]; /* NCPI */ +} _CON_B3_INDP; + /* CONNECT-B3-RESPONSE */ +typedef struct { + word Accept; + byte structs[1]; /* NCPI */ +} _CON_B3_RESP; + /* CONNECT-B3-ACTIVE-INDICATION */ +typedef struct { + byte structs[1]; /* NCPI */ +} _CON_B3_A_INDP; + /* CONNECT-B3-ACTIVE-RESPONSE */ +typedef struct { + byte structs[1]; /* empty */ +} _CON_B3_A_RESP; + /* DISCONNECT-B3-REQUEST */ +typedef struct { + byte structs[1]; /* NCPI */ +} _DIS_B3_REQP; + /* DISCONNECT-B3-CONFIRM */ +typedef struct { + word Info; +} _DIS_B3_CONP; + /* DISCONNECT-B3-INDICATION */ +typedef struct { + word Info; + byte structs[1]; /* NCPI */ +} _DIS_B3_INDP; + /* DISCONNECT-B3-RESPONSE */ +typedef struct { + byte structs[1]; /* empty */ +} _DIS_B3_RESP; + /* DATA-B3-REQUEST */ +typedef struct { + dword Data; + word Data_Length; + word Number; + word Flags; +} _DAT_B3_REQP; + /* DATA-B3-REQUEST 64 BIT Systems */ +typedef struct { + dword Data; + word Data_Length; + word Number; + word Flags; + void *pData; +} _DAT_B3_REQ64P; + /* DATA-B3-CONFIRM */ +typedef struct { + word Number; + word Info; +} _DAT_B3_CONP; + /* DATA-B3-INDICATION */ +typedef struct { + dword Data; + word Data_Length; + word Number; + word Flags; +} _DAT_B3_INDP; + /* DATA-B3-INDICATION 64 BIT Systems */ +typedef struct { + dword Data; + word Data_Length; + word Number; + word Flags; + void *pData; +} _DAT_B3_IND64P; + /* DATA-B3-RESPONSE */ +typedef struct { + word Number; +} _DAT_B3_RESP; + /* RESET-B3-REQUEST */ +typedef struct { + byte structs[1]; /* NCPI */ +} _RES_B3_REQP; + /* RESET-B3-CONFIRM */ +typedef struct { + word Info; +} _RES_B3_CONP; + /* RESET-B3-INDICATION */ +typedef struct { + byte structs[1]; /* NCPI */ +} _RES_B3_INDP; + /* RESET-B3-RESPONSE */ +typedef struct { + byte structs[1]; /* empty */ +} _RES_B3_RESP; + /* CONNECT-B3-T90-ACTIVE-INDICATION */ +typedef struct { + byte structs[1]; /* NCPI */ +} _CON_B3_T90_A_INDP; + /* CONNECT-B3-T90-ACTIVE-RESPONSE */ +typedef struct { + word Reject; + byte structs[1]; /* NCPI */ +} _CON_B3_T90_A_RESP; +/*------------------------------------------------------------------*/ +/* message structure */ +/*------------------------------------------------------------------*/ +typedef struct _API_MSG CAPI_MSG; +typedef struct _MSG_HEADER CAPI_MSG_HEADER; +struct _API_MSG { + struct _MSG_HEADER { + word length; + word appl_id; + word command; + word number; + byte controller; + byte plci; + word ncci; + } header; + union { + _ALT_REQP alert_req; + _ALT_CONP alert_con; + _CON_REQP connect_req; + _CON_CONP connect_con; + _CON_INDP connect_ind; + _CON_RESP connect_res; + _CON_A_INDP connect_a_ind; + _CON_A_RESP connect_a_res; + _DIS_REQP disconnect_req; + _DIS_CONP disconnect_con; + _DIS_INDP disconnect_ind; + _DIS_RESP disconnect_res; + _LIS_REQP listen_req; + _LIS_CONP listen_con; + _INF_REQP info_req; + _INF_CONP info_con; + _INF_INDP info_ind; + _INF_RESP info_res; + _SEL_B_REQP select_b_req; + _SEL_B_CONP select_b_con; + _FAC_REQP facility_req; + _FAC_CONP facility_con; + _FAC_INDP facility_ind; + _FAC_RESP facility_res; + _CON_B3_REQP connect_b3_req; + _CON_B3_CONP connect_b3_con; + _CON_B3_INDP connect_b3_ind; + _CON_B3_RESP connect_b3_res; + _CON_B3_A_INDP connect_b3_a_ind; + _CON_B3_A_RESP connect_b3_a_res; + _DIS_B3_REQP disconnect_b3_req; + _DIS_B3_CONP disconnect_b3_con; + _DIS_B3_INDP disconnect_b3_ind; + _DIS_B3_RESP disconnect_b3_res; + _DAT_B3_REQP data_b3_req; + _DAT_B3_REQ64P data_b3_req64; + _DAT_B3_CONP data_b3_con; + _DAT_B3_INDP data_b3_ind; + _DAT_B3_IND64P data_b3_ind64; + _DAT_B3_RESP data_b3_res; + _RES_B3_REQP reset_b3_req; + _RES_B3_CONP reset_b3_con; + _RES_B3_INDP reset_b3_ind; + _RES_B3_RESP reset_b3_res; + _CON_B3_T90_A_INDP connect_b3_t90_a_ind; + _CON_B3_T90_A_RESP connect_b3_t90_a_res; + byte b[200]; + } info; +}; +/*------------------------------------------------------------------*/ +/* non-fatal errors */ +/*------------------------------------------------------------------*/ +#define _NCPI_IGNORED 0x0001 +#define _FLAGS_IGNORED 0x0002 +#define _ALERT_IGNORED 0x0003 +/*------------------------------------------------------------------*/ +/* API function error codes */ +/*------------------------------------------------------------------*/ +#define GOOD 0x0000 +#define _TOO_MANY_APPLICATIONS 0x1001 +#define _BLOCK_TOO_SMALL 0x1002 +#define _BUFFER_TOO_BIG 0x1003 +#define _MSG_BUFFER_TOO_SMALL 0x1004 +#define _TOO_MANY_CONNECTIONS 0x1005 +#define _REG_CAPI_BUSY 0x1007 +#define _REG_RESOURCE_ERROR 0x1008 +#define _REG_CAPI_NOT_INSTALLED 0x1009 +#define _WRONG_APPL_ID 0x1101 +#define _BAD_MSG 0x1102 +#define _QUEUE_FULL 0x1103 +#define _GET_NO_MSG 0x1104 +#define _MSG_LOST 0x1105 +#define _WRONG_NOTIFY 0x1106 +#define _CAPI_BUSY 0x1107 +#define _RESOURCE_ERROR 0x1108 +#define _CAPI_NOT_INSTALLED 0x1109 +#define _NO_EXTERNAL_EQUIPMENT 0x110a +#define _ONLY_EXTERNAL_EQUIPMENT 0x110b +/*------------------------------------------------------------------*/ +/* addressing/coding error codes */ +/*------------------------------------------------------------------*/ +#define _WRONG_STATE 0x2001 +#define _WRONG_IDENTIFIER 0x2002 +#define _OUT_OF_PLCI 0x2003 +#define _OUT_OF_NCCI 0x2004 +#define _OUT_OF_LISTEN 0x2005 +#define _OUT_OF_FAX 0x2006 +#define _WRONG_MESSAGE_FORMAT 0x2007 +#define _OUT_OF_INTERCONNECT_RESOURCES 0x2008 +/*------------------------------------------------------------------*/ +/* configuration error codes */ +/*------------------------------------------------------------------*/ +#define _B1_NOT_SUPPORTED 0x3001 +#define _B2_NOT_SUPPORTED 0x3002 +#define _B3_NOT_SUPPORTED 0x3003 +#define _B1_PARM_NOT_SUPPORTED 0x3004 +#define _B2_PARM_NOT_SUPPORTED 0x3005 +#define _B3_PARM_NOT_SUPPORTED 0x3006 +#define _B_STACK_NOT_SUPPORTED 0x3007 +#define _NCPI_NOT_SUPPORTED 0x3008 +#define _CIP_NOT_SUPPORTED 0x3009 +#define _FLAGS_NOT_SUPPORTED 0x300a +#define _FACILITY_NOT_SUPPORTED 0x300b +#define _DATA_LEN_NOT_SUPPORTED 0x300c +#define _RESET_NOT_SUPPORTED 0x300d +#define _SUPPLEMENTARY_SERVICE_NOT_SUPPORTED 0x300e +#define _REQUEST_NOT_ALLOWED_IN_THIS_STATE 0x3010 +#define _FACILITY_SPECIFIC_FUNCTION_NOT_SUPP 0x3011 +/*------------------------------------------------------------------*/ +/* reason codes */ +/*------------------------------------------------------------------*/ +#define _L1_ERROR 0x3301 +#define _L2_ERROR 0x3302 +#define _L3_ERROR 0x3303 +#define _OTHER_APPL_CONNECTED 0x3304 +#define _CAPI_GUARD_ERROR 0x3305 +#define _L3_CAUSE 0x3400 +/*------------------------------------------------------------------*/ +/* b3 reason codes */ +/*------------------------------------------------------------------*/ +#define _B_CHANNEL_LOST 0x3301 +#define _B2_ERROR 0x3302 +#define _B3_ERROR 0x3303 +/*------------------------------------------------------------------*/ +/* fax error codes */ +/*------------------------------------------------------------------*/ +#define _FAX_NO_CONNECTION 0x3311 +#define _FAX_TRAINING_ERROR 0x3312 +#define _FAX_REMOTE_REJECT 0x3313 +#define _FAX_REMOTE_ABORT 0x3314 +#define _FAX_PROTOCOL_ERROR 0x3315 +#define _FAX_TX_UNDERRUN 0x3316 +#define _FAX_RX_OVERFLOW 0x3317 +#define _FAX_LOCAL_ABORT 0x3318 +#define _FAX_PARAMETER_ERROR 0x3319 +/*------------------------------------------------------------------*/ +/* line interconnect error codes */ +/*------------------------------------------------------------------*/ +#define _LI_USER_INITIATED 0x0000 +#define _LI_LINE_NO_LONGER_AVAILABLE 0x3805 +#define _LI_INTERCONNECT_NOT_ESTABLISHED 0x3806 +#define _LI_LINES_NOT_COMPATIBLE 0x3807 +#define _LI2_USER_INITIATED 0x0000 +#define _LI2_PLCI_HAS_NO_BCHANNEL 0x3800 +#define _LI2_LINES_NOT_COMPATIBLE 0x3801 +#define _LI2_NOT_IN_SAME_INTERCONNECTION 0x3802 +/*------------------------------------------------------------------*/ +/* global options */ +/*------------------------------------------------------------------*/ +#define GL_INTERNAL_CONTROLLER_SUPPORTED 0x00000001L +#define GL_EXTERNAL_EQUIPMENT_SUPPORTED 0x00000002L +#define GL_HANDSET_SUPPORTED 0x00000004L +#define GL_DTMF_SUPPORTED 0x00000008L +#define GL_SUPPLEMENTARY_SERVICES_SUPPORTED 0x00000010L +#define GL_CHANNEL_ALLOCATION_SUPPORTED 0x00000020L +#define GL_BCHANNEL_OPERATION_SUPPORTED 0x00000040L +#define GL_LINE_INTERCONNECT_SUPPORTED 0x00000080L +#define GL_ECHO_CANCELLER_SUPPORTED 0x00000100L +/*------------------------------------------------------------------*/ +/* protocol selection */ +/*------------------------------------------------------------------*/ +#define B1_HDLC 0 +#define B1_TRANSPARENT 1 +#define B1_V110_ASYNC 2 +#define B1_V110_SYNC 3 +#define B1_T30 4 +#define B1_HDLC_INVERTED 5 +#define B1_TRANSPARENT_R 6 +#define B1_MODEM_ALL_NEGOTIATE 7 +#define B1_MODEM_ASYNC 8 +#define B1_MODEM_SYNC_HDLC 9 +#define B2_X75 0 +#define B2_TRANSPARENT 1 +#define B2_SDLC 2 +#define B2_LAPD 3 +#define B2_T30 4 +#define B2_PPP 5 +#define B2_TRANSPARENT_NO_CRC 6 +#define B2_MODEM_EC_COMPRESSION 7 +#define B2_X75_V42BIS 8 +#define B2_V120_ASYNC 9 +#define B2_V120_ASYNC_V42BIS 10 +#define B2_V120_BIT_TRANSPARENT 11 +#define B2_LAPD_FREE_SAPI_SEL 12 +#define B3_TRANSPARENT 0 +#define B3_T90NL 1 +#define B3_ISO8208 2 +#define B3_X25_DCE 3 +#define B3_T30 4 +#define B3_T30_WITH_EXTENSIONS 5 +#define B3_RESERVED 6 +#define B3_MODEM 7 +/*------------------------------------------------------------------*/ +/* facility definitions */ +/*------------------------------------------------------------------*/ +#define SELECTOR_HANDSET 0 +#define SELECTOR_DTMF 1 +#define SELECTOR_V42BIS 2 +#define SELECTOR_SU_SERV 3 +#define SELECTOR_POWER_MANAGEMENT 4 +#define SELECTOR_LINE_INTERCONNECT 5 +#define SELECTOR_ECHO_CANCELLER 6 +/*------------------------------------------------------------------*/ +/* supplementary services definitions */ +/*------------------------------------------------------------------*/ +#define S_GET_SUPPORTED_SERVICES 0x0000 +#define S_LISTEN 0x0001 +#define S_HOLD 0x0002 +#define S_RETRIEVE 0x0003 +#define S_SUSPEND 0x0004 +#define S_RESUME 0x0005 +#define S_ECT 0x0006 +#define S_3PTY_BEGIN 0x0007 +#define S_3PTY_END 0x0008 +#define S_CALL_DEFLECTION 0x000d +#define S_CALL_FORWARDING_START 0x0009 +#define S_CALL_FORWARDING_STOP 0x000a +#define S_INTERROGATE_DIVERSION 0x000b /* or interrogate parameters */ +#define S_INTERROGATE_NUMBERS 0x000c +#define S_CCBS_REQUEST 0x000f +#define S_CCBS_DEACTIVATE 0x0010 +#define S_CCBS_INTERROGATE 0x0011 +#define S_CCBS_CALL 0x0012 +#define S_MWI_ACTIVATE 0x0013 +#define S_MWI_DEACTIVATE 0x0014 +#define S_CONF_BEGIN 0x0017 +#define S_CONF_ADD 0x0018 +#define S_CONF_SPLIT 0x0019 +#define S_CONF_DROP 0x001a +#define S_CONF_ISOLATE 0x001b +#define S_CONF_REATTACH 0x001c +#define S_CCBS_ERASECALLLINKAGEID 0x800d +#define S_CCBS_STOP_ALERTING 0x8012 +#define S_CCBS_INFO_RETAIN 0x8013 +#define S_MWI_INDICATE 0x8014 +#define S_CONF_PARTYDISC 0x8016 +#define S_CONF_NOTIFICATION 0x8017 +/* Service Masks */ +#define MASK_HOLD_RETRIEVE 0x00000001 +#define MASK_TERMINAL_PORTABILITY 0x00000002 +#define MASK_ECT 0x00000004 +#define MASK_3PTY 0x00000008 +#define MASK_CALL_FORWARDING 0x00000010 +#define MASK_CALL_DEFLECTION 0x00000020 +#define MASK_MWI 0x00000100 +#define MASK_CCNR 0x00000200 +#define MASK_CONF 0x00000400 +/*------------------------------------------------------------------*/ +/* dtmf definitions */ +/*------------------------------------------------------------------*/ +#define DTMF_LISTEN_START 1 +#define DTMF_LISTEN_STOP 2 +#define DTMF_DIGITS_SEND 3 +#define DTMF_SUCCESS 0 +#define DTMF_INCORRECT_DIGIT 1 +#define DTMF_UNKNOWN_REQUEST 2 +/*------------------------------------------------------------------*/ +/* line interconnect definitions */ +/*------------------------------------------------------------------*/ +#define LI_GET_SUPPORTED_SERVICES 0 +#define LI_REQ_CONNECT 1 +#define LI_REQ_DISCONNECT 2 +#define LI_IND_CONNECT_ACTIVE 1 +#define LI_IND_DISCONNECT 2 +#define LI_FLAG_CONFERENCE_A_B ((dword) 0x00000001L) +#define LI_FLAG_CONFERENCE_B_A ((dword) 0x00000002L) +#define LI_FLAG_MONITOR_A ((dword) 0x00000004L) +#define LI_FLAG_MONITOR_B ((dword) 0x00000008L) +#define LI_FLAG_ANNOUNCEMENT_A ((dword) 0x00000010L) +#define LI_FLAG_ANNOUNCEMENT_B ((dword) 0x00000020L) +#define LI_FLAG_MIX_A ((dword) 0x00000040L) +#define LI_FLAG_MIX_B ((dword) 0x00000080L) +#define LI_CONFERENCING_SUPPORTED ((dword) 0x00000001L) +#define LI_MONITORING_SUPPORTED ((dword) 0x00000002L) +#define LI_ANNOUNCEMENTS_SUPPORTED ((dword) 0x00000004L) +#define LI_MIXING_SUPPORTED ((dword) 0x00000008L) +#define LI_CROSS_CONTROLLER_SUPPORTED ((dword) 0x00000010L) +#define LI2_GET_SUPPORTED_SERVICES 0 +#define LI2_REQ_CONNECT 1 +#define LI2_REQ_DISCONNECT 2 +#define LI2_IND_CONNECT_ACTIVE 1 +#define LI2_IND_DISCONNECT 2 +#define LI2_FLAG_INTERCONNECT_A_B ((dword) 0x00000001L) +#define LI2_FLAG_INTERCONNECT_B_A ((dword) 0x00000002L) +#define LI2_FLAG_MONITOR_B ((dword) 0x00000004L) +#define LI2_FLAG_MIX_B ((dword) 0x00000008L) +#define LI2_FLAG_MONITOR_X ((dword) 0x00000010L) +#define LI2_FLAG_MIX_X ((dword) 0x00000020L) +#define LI2_FLAG_LOOP_B ((dword) 0x00000040L) +#define LI2_FLAG_LOOP_PC ((dword) 0x00000080L) +#define LI2_FLAG_LOOP_X ((dword) 0x00000100L) +#define LI2_CROSS_CONTROLLER_SUPPORTED ((dword) 0x00000001L) +#define LI2_ASYMMETRIC_SUPPORTED ((dword) 0x00000002L) +#define LI2_MONITORING_SUPPORTED ((dword) 0x00000004L) +#define LI2_MIXING_SUPPORTED ((dword) 0x00000008L) +#define LI2_REMOTE_MONITORING_SUPPORTED ((dword) 0x00000010L) +#define LI2_REMOTE_MIXING_SUPPORTED ((dword) 0x00000020L) +#define LI2_B_LOOPING_SUPPORTED ((dword) 0x00000040L) +#define LI2_PC_LOOPING_SUPPORTED ((dword) 0x00000080L) +#define LI2_X_LOOPING_SUPPORTED ((dword) 0x00000100L) +/*------------------------------------------------------------------*/ +/* echo canceller definitions */ +/*------------------------------------------------------------------*/ +#define EC_GET_SUPPORTED_SERVICES 0 +#define EC_ENABLE_OPERATION 1 +#define EC_DISABLE_OPERATION 2 +#define EC_ENABLE_NON_LINEAR_PROCESSING 0x0001 +#define EC_DO_NOT_REQUIRE_REVERSALS 0x0002 +#define EC_DETECT_DISABLE_TONE 0x0004 +#define EC_ENABLE_ADAPTIVE_PREDELAY 0x0008 +#define EC_NON_LINEAR_PROCESSING_SUPPORTED 0x0001 +#define EC_BYPASS_ON_ANY_2100HZ_SUPPORTED 0x0002 +#define EC_BYPASS_ON_REV_2100HZ_SUPPORTED 0x0004 +#define EC_ADAPTIVE_PREDELAY_SUPPORTED 0x0008 +#define EC_BYPASS_INDICATION 1 +#define EC_BYPASS_DUE_TO_CONTINUOUS_2100HZ 1 +#define EC_BYPASS_DUE_TO_REVERSED_2100HZ 2 +#define EC_BYPASS_RELEASED 3 +/*------------------------------------------------------------------*/ +/* function prototypes */ +/*------------------------------------------------------------------*/ +/*------------------------------------------------------------------*/ +#endif /* _INC_CAPI20 */ diff --git a/drivers/isdn/hardware/eicon/capidtmf.c b/drivers/isdn/hardware/eicon/capidtmf.c new file mode 100644 index 000000000000..f130724144f3 --- /dev/null +++ b/drivers/isdn/hardware/eicon/capidtmf.c @@ -0,0 +1,685 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ + +#include "platform.h" + + + + + + + + + +#include "capidtmf.h" + +/* #define TRACE_ */ + +#define FILE_ "CAPIDTMF.C" + +/*---------------------------------------------------------------------------*/ + + +#define trace(a) + + + +/*---------------------------------------------------------------------------*/ + +static short capidtmf_expand_table_alaw[0x0100] = +{ + -5504, 5504, -344, 344, -22016, 22016, -1376, 1376, + -2752, 2752, -88, 88, -11008, 11008, -688, 688, + -7552, 7552, -472, 472, -30208, 30208, -1888, 1888, + -3776, 3776, -216, 216, -15104, 15104, -944, 944, + -4480, 4480, -280, 280, -17920, 17920, -1120, 1120, + -2240, 2240, -24, 24, -8960, 8960, -560, 560, + -6528, 6528, -408, 408, -26112, 26112, -1632, 1632, + -3264, 3264, -152, 152, -13056, 13056, -816, 816, + -6016, 6016, -376, 376, -24064, 24064, -1504, 1504, + -3008, 3008, -120, 120, -12032, 12032, -752, 752, + -8064, 8064, -504, 504, -32256, 32256, -2016, 2016, + -4032, 4032, -248, 248, -16128, 16128, -1008, 1008, + -4992, 4992, -312, 312, -19968, 19968, -1248, 1248, + -2496, 2496, -56, 56, -9984, 9984, -624, 624, + -7040, 7040, -440, 440, -28160, 28160, -1760, 1760, + -3520, 3520, -184, 184, -14080, 14080, -880, 880, + -5248, 5248, -328, 328, -20992, 20992, -1312, 1312, + -2624, 2624, -72, 72, -10496, 10496, -656, 656, + -7296, 7296, -456, 456, -29184, 29184, -1824, 1824, + -3648, 3648, -200, 200, -14592, 14592, -912, 912, + -4224, 4224, -264, 264, -16896, 16896, -1056, 1056, + -2112, 2112, -8, 8, -8448, 8448, -528, 528, + -6272, 6272, -392, 392, -25088, 25088, -1568, 1568, + -3136, 3136, -136, 136, -12544, 12544, -784, 784, + -5760, 5760, -360, 360, -23040, 23040, -1440, 1440, + -2880, 2880, -104, 104, -11520, 11520, -720, 720, + -7808, 7808, -488, 488, -31232, 31232, -1952, 1952, + -3904, 3904, -232, 232, -15616, 15616, -976, 976, + -4736, 4736, -296, 296, -18944, 18944, -1184, 1184, + -2368, 2368, -40, 40, -9472, 9472, -592, 592, + -6784, 6784, -424, 424, -27136, 27136, -1696, 1696, + -3392, 3392, -168, 168, -13568, 13568, -848, 848 +}; + +static short capidtmf_expand_table_ulaw[0x0100] = +{ + -32124, 32124, -1884, 1884, -7932, 7932, -372, 372, + -15996, 15996, -876, 876, -3900, 3900, -120, 120, + -23932, 23932, -1372, 1372, -5884, 5884, -244, 244, + -11900, 11900, -620, 620, -2876, 2876, -56, 56, + -28028, 28028, -1628, 1628, -6908, 6908, -308, 308, + -13948, 13948, -748, 748, -3388, 3388, -88, 88, + -19836, 19836, -1116, 1116, -4860, 4860, -180, 180, + -9852, 9852, -492, 492, -2364, 2364, -24, 24, + -30076, 30076, -1756, 1756, -7420, 7420, -340, 340, + -14972, 14972, -812, 812, -3644, 3644, -104, 104, + -21884, 21884, -1244, 1244, -5372, 5372, -212, 212, + -10876, 10876, -556, 556, -2620, 2620, -40, 40, + -25980, 25980, -1500, 1500, -6396, 6396, -276, 276, + -12924, 12924, -684, 684, -3132, 3132, -72, 72, + -17788, 17788, -988, 988, -4348, 4348, -148, 148, + -8828, 8828, -428, 428, -2108, 2108, -8, 8, + -31100, 31100, -1820, 1820, -7676, 7676, -356, 356, + -15484, 15484, -844, 844, -3772, 3772, -112, 112, + -22908, 22908, -1308, 1308, -5628, 5628, -228, 228, + -11388, 11388, -588, 588, -2748, 2748, -48, 48, + -27004, 27004, -1564, 1564, -6652, 6652, -292, 292, + -13436, 13436, -716, 716, -3260, 3260, -80, 80, + -18812, 18812, -1052, 1052, -4604, 4604, -164, 164, + -9340, 9340, -460, 460, -2236, 2236, -16, 16, + -29052, 29052, -1692, 1692, -7164, 7164, -324, 324, + -14460, 14460, -780, 780, -3516, 3516, -96, 96, + -20860, 20860, -1180, 1180, -5116, 5116, -196, 196, + -10364, 10364, -524, 524, -2492, 2492, -32, 32, + -24956, 24956, -1436, 1436, -6140, 6140, -260, 260, + -12412, 12412, -652, 652, -3004, 3004, -64, 64, + -16764, 16764, -924, 924, -4092, 4092, -132, 132, + -8316, 8316, -396, 396, -1980, 1980, 0, 0 +}; + + +/*---------------------------------------------------------------------------*/ + +static short capidtmf_recv_window_function[CAPIDTMF_RECV_ACCUMULATE_CYCLES] = +{ + -500L, -999L, -1499L, -1998L, -2496L, -2994L, -3491L, -3988L, + -4483L, -4978L, -5471L, -5963L, -6454L, -6943L, -7431L, -7917L, + -8401L, -8883L, -9363L, -9840L, -10316L, -10789L, -11259L, -11727L, + -12193L, -12655L, -13115L, -13571L, -14024L, -14474L, -14921L, -15364L, + -15804L, -16240L, -16672L, -17100L, -17524L, -17944L, -18360L, -18772L, + -19180L, -19583L, -19981L, -20375L, -20764L, -21148L, -21527L, -21901L, + -22270L, -22634L, -22993L, -23346L, -23694L, -24037L, -24374L, -24705L, + -25030L, -25350L, -25664L, -25971L, -26273L, -26568L, -26858L, -27141L, + -27418L, -27688L, -27952L, -28210L, -28461L, -28705L, -28943L, -29174L, + -29398L, -29615L, -29826L, -30029L, -30226L, -30415L, -30598L, -30773L, + -30941L, -31102L, -31256L, -31402L, -31541L, -31673L, -31797L, -31914L, + -32024L, -32126L, -32221L, -32308L, -32388L, -32460L, -32524L, -32581L, + -32631L, -32673L, -32707L, -32734L, -32753L, -32764L, -32768L, -32764L, + -32753L, -32734L, -32707L, -32673L, -32631L, -32581L, -32524L, -32460L, + -32388L, -32308L, -32221L, -32126L, -32024L, -31914L, -31797L, -31673L, + -31541L, -31402L, -31256L, -31102L, -30941L, -30773L, -30598L, -30415L, + -30226L, -30029L, -29826L, -29615L, -29398L, -29174L, -28943L, -28705L, + -28461L, -28210L, -27952L, -27688L, -27418L, -27141L, -26858L, -26568L, + -26273L, -25971L, -25664L, -25350L, -25030L, -24705L, -24374L, -24037L, + -23694L, -23346L, -22993L, -22634L, -22270L, -21901L, -21527L, -21148L, + -20764L, -20375L, -19981L, -19583L, -19180L, -18772L, -18360L, -17944L, + -17524L, -17100L, -16672L, -16240L, -15804L, -15364L, -14921L, -14474L, + -14024L, -13571L, -13115L, -12655L, -12193L, -11727L, -11259L, -10789L, + -10316L, -9840L, -9363L, -8883L, -8401L, -7917L, -7431L, -6943L, + -6454L, -5963L, -5471L, -4978L, -4483L, -3988L, -3491L, -2994L, + -2496L, -1998L, -1499L, -999L, -500L, +}; + +static byte capidtmf_leading_zeroes_table[0x100] = +{ + 8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 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, + 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 +}; + +#define capidtmf_byte_leading_zeroes(b) (capidtmf_leading_zeroes_table[(BYTE)(b)]) +#define capidtmf_word_leading_zeroes(w) (((w) & 0xff00) ? capidtmf_leading_zeroes_table[(w) >> 8] : 8 + capidtmf_leading_zeroes_table[(w)]) +#define capidtmf_dword_leading_zeroes(d) (((d) & 0xffff0000L) ? (((d) & 0xff000000L) ? capidtmf_leading_zeroes_table[(d) >> 24] : 8 + capidtmf_leading_zeroes_table[(d) >> 16]) : (((d) & 0xff00) ? 16 + capidtmf_leading_zeroes_table[(d) >> 8] : 24 + capidtmf_leading_zeroes_table[(d)])) + + +/*---------------------------------------------------------------------------*/ + + +static void capidtmf_goertzel_loop (long *buffer, long *coeffs, short *sample, long count) +{ + int i, j; + long c, d, q0, q1, q2; + + for (i = 0; i < CAPIDTMF_RECV_TOTAL_FREQUENCY_COUNT - 1; i++) + { + q1 = buffer[i]; + q2 = buffer[i + CAPIDTMF_RECV_TOTAL_FREQUENCY_COUNT]; + d = coeffs[i] >> 1; + c = d << 1; + if (c >= 0) + { + for (j = 0; j < count; j++) + { + q0 = sample[j] - q2 + (c * (q1 >> 16)) + (((dword)(((dword) d) * ((dword)(q1 & 0xffff)))) >> 15); + q2 = q1; + q1 = q0; + } + } + else + { + c = -c; + d = -d; + for (j = 0; j < count; j++) + { + q0 = sample[j] - q2 - ((c * (q1 >> 16)) + (((dword)(((dword) d) * ((dword)(q1 & 0xffff)))) >> 15)); + q2 = q1; + q1 = q0; + } + } + buffer[i] = q1; + buffer[i + CAPIDTMF_RECV_TOTAL_FREQUENCY_COUNT] = q2; + } + q1 = buffer[i]; + q2 = buffer[i + CAPIDTMF_RECV_TOTAL_FREQUENCY_COUNT]; + c = (coeffs[i] >> 1) << 1; + if (c >= 0) + { + for (j = 0; j < count; j++) + { + q0 = sample[j] - q2 + (c * (q1 >> 16)) + (((dword)(((dword)(c >> 1)) * ((dword)(q1 & 0xffff)))) >> 15); + q2 = q1; + q1 = q0; + c -= CAPIDTMF_RECV_FUNDAMENTAL_DECREMENT; + } + } + else + { + c = -c; + for (j = 0; j < count; j++) + { + q0 = sample[j] - q2 - ((c * (q1 >> 16)) + (((dword)(((dword)(c >> 1)) * ((dword)(q1 & 0xffff)))) >> 15)); + q2 = q1; + q1 = q0; + c += CAPIDTMF_RECV_FUNDAMENTAL_DECREMENT; + } + } + coeffs[i] = c; + buffer[i] = q1; + buffer[i + CAPIDTMF_RECV_TOTAL_FREQUENCY_COUNT] = q2; +} + + +static void capidtmf_goertzel_result (long *buffer, long *coeffs) +{ + int i; + long d, e, q1, q2, lo, mid, hi; + dword k; + + for (i = 0; i < CAPIDTMF_RECV_TOTAL_FREQUENCY_COUNT; i++) + { + q1 = buffer[i]; + q2 = buffer[i + CAPIDTMF_RECV_TOTAL_FREQUENCY_COUNT]; + d = coeffs[i] >> 1; + if (d >= 0) + d = ((d << 1) * (-q1 >> 16)) + (((dword)(((dword) d) * ((dword)(-q1 & 0xffff)))) >> 15); + else + d = ((-d << 1) * (-q1 >> 16)) + (((dword)(((dword) -d) * ((dword)(-q1 & 0xffff)))) >> 15); + e = (q2 >= 0) ? q2 : -q2; + if (d >= 0) + { + k = ((dword)(d & 0xffff)) * ((dword)(e & 0xffff)); + lo = k & 0xffff; + mid = k >> 16; + k = ((dword)(d >> 16)) * ((dword)(e & 0xffff)); + mid += k & 0xffff; + hi = k >> 16; + k = ((dword)(d & 0xffff)) * ((dword)(e >> 16)); + mid += k & 0xffff; + hi += k >> 16; + hi += ((dword)(d >> 16)) * ((dword)(e >> 16)); + } + else + { + d = -d; + k = ((dword)(d & 0xffff)) * ((dword)(e & 0xffff)); + lo = -((long)(k & 0xffff)); + mid = -((long)(k >> 16)); + k = ((dword)(d >> 16)) * ((dword)(e & 0xffff)); + mid -= k & 0xffff; + hi = -((long)(k >> 16)); + k = ((dword)(d & 0xffff)) * ((dword)(e >> 16)); + mid -= k & 0xffff; + hi -= k >> 16; + hi -= ((dword)(d >> 16)) * ((dword)(e >> 16)); + } + if (q2 < 0) + { + lo = -lo; + mid = -mid; + hi = -hi; + } + d = (q1 >= 0) ? q1 : -q1; + k = ((dword)(d & 0xffff)) * ((dword)(d & 0xffff)); + lo += k & 0xffff; + mid += k >> 16; + k = ((dword)(d >> 16)) * ((dword)(d & 0xffff)); + mid += (k & 0xffff) << 1; + hi += (k >> 16) << 1; + hi += ((dword)(d >> 16)) * ((dword)(d >> 16)); + d = (q2 >= 0) ? q2 : -q2; + k = ((dword)(d & 0xffff)) * ((dword)(d & 0xffff)); + lo += k & 0xffff; + mid += k >> 16; + k = ((dword)(d >> 16)) * ((dword)(d & 0xffff)); + mid += (k & 0xffff) << 1; + hi += (k >> 16) << 1; + hi += ((dword)(d >> 16)) * ((dword)(d >> 16)); + mid += lo >> 16; + hi += mid >> 16; + buffer[i] = (lo & 0xffff) | (mid << 16); + buffer[i + CAPIDTMF_RECV_TOTAL_FREQUENCY_COUNT] = hi; + } +} + + +/*---------------------------------------------------------------------------*/ + +#define CAPIDTMF_RECV_GUARD_SNR_INDEX_697 0 +#define CAPIDTMF_RECV_GUARD_SNR_INDEX_770 1 +#define CAPIDTMF_RECV_GUARD_SNR_INDEX_852 2 +#define CAPIDTMF_RECV_GUARD_SNR_INDEX_941 3 +#define CAPIDTMF_RECV_GUARD_SNR_INDEX_1209 4 +#define CAPIDTMF_RECV_GUARD_SNR_INDEX_1336 5 +#define CAPIDTMF_RECV_GUARD_SNR_INDEX_1477 6 +#define CAPIDTMF_RECV_GUARD_SNR_INDEX_1633 7 +#define CAPIDTMF_RECV_GUARD_SNR_INDEX_635 8 +#define CAPIDTMF_RECV_GUARD_SNR_INDEX_1010 9 +#define CAPIDTMF_RECV_GUARD_SNR_INDEX_1140 10 +#define CAPIDTMF_RECV_GUARD_SNR_INDEX_1272 11 +#define CAPIDTMF_RECV_GUARD_SNR_INDEX_1405 12 +#define CAPIDTMF_RECV_GUARD_SNR_INDEX_1555 13 +#define CAPIDTMF_RECV_GUARD_SNR_INDEX_1715 14 +#define CAPIDTMF_RECV_GUARD_SNR_INDEX_1875 15 + +#define CAPIDTMF_RECV_GUARD_SNR_DONTCARE 0xc000 +#define CAPIDTMF_RECV_NO_DIGIT 0xff +#define CAPIDTMF_RECV_TIME_GRANULARITY (CAPIDTMF_RECV_ACCUMULATE_CYCLES + 1) + +#define CAPIDTMF_RECV_INDICATION_DIGIT 0x0001 + +static long capidtmf_recv_goertzel_coef_table[CAPIDTMF_RECV_TOTAL_FREQUENCY_COUNT] = +{ + 0xda97L * 2, /* 697 Hz (Low group 697 Hz) */ + 0xd299L * 2, /* 770 Hz (Low group 770 Hz) */ + 0xc8cbL * 2, /* 852 Hz (Low group 852 Hz) */ + 0xbd36L * 2, /* 941 Hz (Low group 941 Hz) */ + 0x9501L * 2, /* 1209 Hz (High group 1209 Hz) */ + 0x7f89L * 2, /* 1336 Hz (High group 1336 Hz) */ + 0x6639L * 2, /* 1477 Hz (High group 1477 Hz) */ + 0x48c6L * 2, /* 1633 Hz (High group 1633 Hz) */ + 0xe14cL * 2, /* 630 Hz (Lower guard of low group 631 Hz) */ + 0xb2e0L * 2, /* 1015 Hz (Upper guard of low group 1039 Hz) */ + 0xa1a0L * 2, /* 1130 Hz (Lower guard of high group 1140 Hz) */ + 0x8a87L * 2, /* 1272 Hz (Guard between 1209 Hz and 1336 Hz: 1271 Hz) */ + 0x7353L * 2, /* 1405 Hz (2nd harmonics of 697 Hz and guard between 1336 Hz and 1477 Hz: 1405 Hz) */ + 0x583bL * 2, /* 1552 Hz (2nd harmonics of 770 Hz and guard between 1477 Hz and 1633 Hz: 1553 Hz) */ + 0x37d8L * 2, /* 1720 Hz (2nd harmonics of 852 Hz and upper guard of high group: 1715 Hz) */ + 0x0000L * 2 /* 100-630 Hz (fundamentals) */ +}; + + +static word capidtmf_recv_guard_snr_low_table[CAPIDTMF_RECV_TOTAL_FREQUENCY_COUNT] = +{ + 14, /* Low group peak versus 697 Hz */ + 14, /* Low group peak versus 770 Hz */ + 16, /* Low group peak versus 852 Hz */ + 16, /* Low group peak versus 941 Hz */ + CAPIDTMF_RECV_GUARD_SNR_DONTCARE, /* Low group peak versus 1209 Hz */ + CAPIDTMF_RECV_GUARD_SNR_DONTCARE, /* Low group peak versus 1336 Hz */ + CAPIDTMF_RECV_GUARD_SNR_DONTCARE, /* Low group peak versus 1477 Hz */ + CAPIDTMF_RECV_GUARD_SNR_DONTCARE, /* Low group peak versus 1633 Hz */ + 14, /* Low group peak versus 635 Hz */ + 16, /* Low group peak versus 1010 Hz */ + CAPIDTMF_RECV_GUARD_SNR_DONTCARE, /* Low group peak versus 1140 Hz */ + CAPIDTMF_RECV_GUARD_SNR_DONTCARE, /* Low group peak versus 1272 Hz */ + DSPDTMF_RX_HARMONICS_SEL_DEFAULT - 8, /* Low group peak versus 1405 Hz */ + DSPDTMF_RX_HARMONICS_SEL_DEFAULT - 4, /* Low group peak versus 1555 Hz */ + DSPDTMF_RX_HARMONICS_SEL_DEFAULT - 4, /* Low group peak versus 1715 Hz */ + 12 /* Low group peak versus 100-630 Hz */ +}; + + +static word capidtmf_recv_guard_snr_high_table[CAPIDTMF_RECV_TOTAL_FREQUENCY_COUNT] = +{ + CAPIDTMF_RECV_GUARD_SNR_DONTCARE, /* High group peak versus 697 Hz */ + CAPIDTMF_RECV_GUARD_SNR_DONTCARE, /* High group peak versus 770 Hz */ + CAPIDTMF_RECV_GUARD_SNR_DONTCARE, /* High group peak versus 852 Hz */ + CAPIDTMF_RECV_GUARD_SNR_DONTCARE, /* High group peak versus 941 Hz */ + 20, /* High group peak versus 1209 Hz */ + 20, /* High group peak versus 1336 Hz */ + 20, /* High group peak versus 1477 Hz */ + 20, /* High group peak versus 1633 Hz */ + CAPIDTMF_RECV_GUARD_SNR_DONTCARE, /* High group peak versus 635 Hz */ + CAPIDTMF_RECV_GUARD_SNR_DONTCARE, /* High group peak versus 1010 Hz */ + 16, /* High group peak versus 1140 Hz */ + 4, /* High group peak versus 1272 Hz */ + 6, /* High group peak versus 1405 Hz */ + 8, /* High group peak versus 1555 Hz */ + 16, /* High group peak versus 1715 Hz */ + 12 /* High group peak versus 100-630 Hz */ +}; + + +/*---------------------------------------------------------------------------*/ + +static void capidtmf_recv_init (t_capidtmf_state *p_state) +{ + p_state->recv.min_gap_duration = 1; + p_state->recv.min_digit_duration = 1; + + p_state->recv.cycle_counter = 0; + p_state->recv.current_digit_on_time = 0; + p_state->recv.current_digit_off_time = 0; + p_state->recv.current_digit_value = CAPIDTMF_RECV_NO_DIGIT; + + p_state->recv.digit_write_pos = 0; + p_state->recv.digit_read_pos = 0; + p_state->recv.indication_state = 0; + p_state->recv.indication_state_ack = 0; + p_state->recv.state = CAPIDTMF_RECV_STATE_IDLE; +} + + +void capidtmf_recv_enable (t_capidtmf_state *p_state, word min_digit_duration, word min_gap_duration) +{ + p_state->recv.indication_state_ack &= CAPIDTMF_RECV_INDICATION_DIGIT; + p_state->recv.min_digit_duration = (word)(((((dword) min_digit_duration) * 8) + + ((dword)(CAPIDTMF_RECV_TIME_GRANULARITY / 2))) / ((dword) CAPIDTMF_RECV_TIME_GRANULARITY)); + if (p_state->recv.min_digit_duration <= 1) + p_state->recv.min_digit_duration = 1; + else + (p_state->recv.min_digit_duration)--; + p_state->recv.min_gap_duration = + (word)((((dword) min_gap_duration) * 8) / ((dword) CAPIDTMF_RECV_TIME_GRANULARITY)); + if (p_state->recv.min_gap_duration <= 1) + p_state->recv.min_gap_duration = 1; + else + (p_state->recv.min_gap_duration)--; + p_state->recv.state |= CAPIDTMF_RECV_STATE_DTMF_ACTIVE; +} + + +void capidtmf_recv_disable (t_capidtmf_state *p_state) +{ + p_state->recv.state &= ~CAPIDTMF_RECV_STATE_DTMF_ACTIVE; + if (p_state->recv.state == CAPIDTMF_RECV_STATE_IDLE) + capidtmf_recv_init (p_state); + else + { + p_state->recv.cycle_counter = 0; + p_state->recv.current_digit_on_time = 0; + p_state->recv.current_digit_off_time = 0; + p_state->recv.current_digit_value = CAPIDTMF_RECV_NO_DIGIT; + } +} + + +word capidtmf_recv_indication (t_capidtmf_state *p_state, byte *buffer) +{ + word i, j, k, flags; + + flags = p_state->recv.indication_state ^ p_state->recv.indication_state_ack; + p_state->recv.indication_state_ack ^= flags & CAPIDTMF_RECV_INDICATION_DIGIT; + if (p_state->recv.digit_write_pos != p_state->recv.digit_read_pos) + { + i = 0; + k = p_state->recv.digit_write_pos; + j = p_state->recv.digit_read_pos; + do + { + buffer[i++] = p_state->recv.digit_buffer[j]; + j = (j == CAPIDTMF_RECV_DIGIT_BUFFER_SIZE - 1) ? 0 : j + 1; + } while (j != k); + p_state->recv.digit_read_pos = k; + return (i); + } + p_state->recv.indication_state_ack ^= flags; + return (0); +} + + +#define CAPIDTMF_RECV_WINDOWED_SAMPLES 32 + +void capidtmf_recv_block (t_capidtmf_state *p_state, byte *buffer, word length) +{ + byte result_digit; + word sample_number, cycle_counter, n, i; + word low_peak, high_peak; + dword lo, hi; + byte *p; + short *q; + byte goertzel_result_buffer[CAPIDTMF_RECV_TOTAL_FREQUENCY_COUNT]; + short windowed_sample_buffer[CAPIDTMF_RECV_WINDOWED_SAMPLES]; + + + if (p_state->recv.state & CAPIDTMF_RECV_STATE_DTMF_ACTIVE) + { + cycle_counter = p_state->recv.cycle_counter; + sample_number = 0; + while (sample_number < length) + { + if (cycle_counter < CAPIDTMF_RECV_ACCUMULATE_CYCLES) + { + if (cycle_counter == 0) + { + for (i = 0; i < CAPIDTMF_RECV_TOTAL_FREQUENCY_COUNT; i++) + { + p_state->recv.goertzel_buffer[0][i] = 0; + p_state->recv.goertzel_buffer[1][i] = 0; + } + } + n = CAPIDTMF_RECV_ACCUMULATE_CYCLES - cycle_counter; + if (n > length - sample_number) + n = length - sample_number; + if (n > CAPIDTMF_RECV_WINDOWED_SAMPLES) + n = CAPIDTMF_RECV_WINDOWED_SAMPLES; + p = buffer + sample_number; + q = capidtmf_recv_window_function + cycle_counter; + if (p_state->ulaw) + { + for (i = 0; i < n; i++) + { + windowed_sample_buffer[i] = + (short)((capidtmf_expand_table_ulaw[p[i]] * ((long)(q[i]))) >> 15); + } + } + else + { + for (i = 0; i < n; i++) + { + windowed_sample_buffer[i] = + (short)((capidtmf_expand_table_alaw[p[i]] * ((long)(q[i]))) >> 15); + } + } + capidtmf_recv_goertzel_coef_table[CAPIDTMF_RECV_TOTAL_FREQUENCY_COUNT - 1] = CAPIDTMF_RECV_FUNDAMENTAL_OFFSET; + capidtmf_goertzel_loop (p_state->recv.goertzel_buffer[0], + capidtmf_recv_goertzel_coef_table, windowed_sample_buffer, n); + cycle_counter += n; + sample_number += n; + } + else + { + capidtmf_goertzel_result (p_state->recv.goertzel_buffer[0], + capidtmf_recv_goertzel_coef_table); + for (i = 0; i < CAPIDTMF_RECV_TOTAL_FREQUENCY_COUNT; i++) + { + lo = (dword)(p_state->recv.goertzel_buffer[0][i]); + hi = (dword)(p_state->recv.goertzel_buffer[1][i]); + if (hi != 0) + { + n = capidtmf_dword_leading_zeroes (hi); + hi = (hi << n) | (lo >> (32 - n)); + } + else + { + n = capidtmf_dword_leading_zeroes (lo); + hi = lo << n; + n += 32; + } + n = 195 - 3 * n; + if (hi >= 0xcb300000L) + n += 2; + else if (hi >= 0xa1450000L) + n++; + goertzel_result_buffer[i] = (byte) n; + } + low_peak = DSPDTMF_RX_SENSITIVITY_LOW_DEFAULT; + result_digit = CAPIDTMF_RECV_NO_DIGIT; + for (i = 0; i < CAPIDTMF_LOW_GROUP_FREQUENCIES; i++) + { + if (goertzel_result_buffer[i] > low_peak) + { + low_peak = goertzel_result_buffer[i]; + result_digit = (byte) i; + } + } + high_peak = DSPDTMF_RX_SENSITIVITY_HIGH_DEFAULT; + n = CAPIDTMF_RECV_NO_DIGIT; + for (i = CAPIDTMF_LOW_GROUP_FREQUENCIES; i < CAPIDTMF_RECV_BASE_FREQUENCY_COUNT; i++) + { + if (goertzel_result_buffer[i] > high_peak) + { + high_peak = goertzel_result_buffer[i]; + n = (i - CAPIDTMF_LOW_GROUP_FREQUENCIES) << 2; + } + } + result_digit |= (byte) n; + if (low_peak + DSPDTMF_RX_HIGH_EXCEEDING_LOW_DEFAULT < high_peak) + result_digit = CAPIDTMF_RECV_NO_DIGIT; + if (high_peak + DSPDTMF_RX_LOW_EXCEEDING_HIGH_DEFAULT < low_peak) + result_digit = CAPIDTMF_RECV_NO_DIGIT; + n = 0; + for (i = 0; i < CAPIDTMF_RECV_TOTAL_FREQUENCY_COUNT; i++) + { + if ((((short)(low_peak - goertzel_result_buffer[i] - capidtmf_recv_guard_snr_low_table[i])) < 0) + || (((short)(high_peak - goertzel_result_buffer[i] - capidtmf_recv_guard_snr_high_table[i])) < 0)) + { + n++; + } + } + if (n != 2) + result_digit = CAPIDTMF_RECV_NO_DIGIT; + + if (result_digit == CAPIDTMF_RECV_NO_DIGIT) + { + if (p_state->recv.current_digit_on_time != 0) + { + if (++(p_state->recv.current_digit_off_time) >= p_state->recv.min_gap_duration) + { + p_state->recv.current_digit_on_time = 0; + p_state->recv.current_digit_off_time = 0; + } + } + else + { + if (p_state->recv.current_digit_off_time != 0) + (p_state->recv.current_digit_off_time)--; + } + } + else + { + if ((p_state->recv.current_digit_on_time == 0) + && (p_state->recv.current_digit_off_time != 0)) + { + (p_state->recv.current_digit_off_time)--; + } + else + { + n = p_state->recv.current_digit_off_time; + if ((p_state->recv.current_digit_on_time != 0) + && (result_digit != p_state->recv.current_digit_value)) + { + p_state->recv.current_digit_on_time = 0; + n = 0; + } + p_state->recv.current_digit_value = result_digit; + p_state->recv.current_digit_off_time = 0; + if (p_state->recv.current_digit_on_time != 0xffff) + { + p_state->recv.current_digit_on_time += n + 1; + if (p_state->recv.current_digit_on_time >= p_state->recv.min_digit_duration) + { + p_state->recv.current_digit_on_time = 0xffff; + i = (p_state->recv.digit_write_pos == CAPIDTMF_RECV_DIGIT_BUFFER_SIZE - 1) ? + 0 : p_state->recv.digit_write_pos + 1; + if (i == p_state->recv.digit_read_pos) + { + trace (dprintf ("%s,%d: Receive digit overrun", + (char *)(FILE_), __LINE__)); + } + else + { + p_state->recv.digit_buffer[p_state->recv.digit_write_pos] = result_digit; + p_state->recv.digit_write_pos = i; + p_state->recv.indication_state = + (p_state->recv.indication_state & ~CAPIDTMF_RECV_INDICATION_DIGIT) | + (~p_state->recv.indication_state_ack & CAPIDTMF_RECV_INDICATION_DIGIT); + } + } + } + } + } + cycle_counter = 0; + sample_number++; + } + } + p_state->recv.cycle_counter = cycle_counter; + } +} + + +void capidtmf_init (t_capidtmf_state *p_state, byte ulaw) +{ + p_state->ulaw = ulaw; + capidtmf_recv_init (p_state); +} + + +/*---------------------------------------------------------------------------*/ diff --git a/drivers/isdn/hardware/eicon/capidtmf.h b/drivers/isdn/hardware/eicon/capidtmf.h new file mode 100644 index 000000000000..242048fb2dd7 --- /dev/null +++ b/drivers/isdn/hardware/eicon/capidtmf.h @@ -0,0 +1,79 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +#ifndef CAPIDTMF_H_ +#define CAPIDTMF_H_ +/*---------------------------------------------------------------------------*/ +/*---------------------------------------------------------------------------*/ +#define CAPIDTMF_TONE_GROUP_COUNT 2 +#define CAPIDTMF_LOW_GROUP_FREQUENCIES 4 +#define CAPIDTMF_HIGH_GROUP_FREQUENCIES 4 +#define DSPDTMF_RX_SENSITIVITY_LOW_DEFAULT 50 /* -52 dBm */ +#define DSPDTMF_RX_SENSITIVITY_HIGH_DEFAULT 50 /* -52 dBm */ +#define DSPDTMF_RX_HIGH_EXCEEDING_LOW_DEFAULT 10 /* dB */ +#define DSPDTMF_RX_LOW_EXCEEDING_HIGH_DEFAULT 10 /* dB */ +#define DSPDTMF_RX_HARMONICS_SEL_DEFAULT 12 /* dB */ +#define CAPIDTMF_RECV_BASE_FREQUENCY_COUNT (CAPIDTMF_LOW_GROUP_FREQUENCIES + CAPIDTMF_HIGH_GROUP_FREQUENCIES) +#define CAPIDTMF_RECV_GUARD_FREQUENCY_COUNT 8 +#define CAPIDTMF_RECV_TOTAL_FREQUENCY_COUNT (CAPIDTMF_RECV_BASE_FREQUENCY_COUNT + CAPIDTMF_RECV_GUARD_FREQUENCY_COUNT) +#define CAPIDTMF_RECV_POSITIVE_COEFF_COUNT 16 +#define CAPIDTMF_RECV_NEGATIVE_COEFF_COUNT (CAPIDTMF_RECV_TOTAL_FREQUENCY_COUNT - CAPIDTMF_RECV_POSITIVE_COEFF_COUNT) +#define CAPIDTMF_RECV_ACCUMULATE_CYCLES 205 +#define CAPIDTMF_RECV_FUNDAMENTAL_OFFSET (0xff35L * 2) +#define CAPIDTMF_RECV_FUNDAMENTAL_DECREMENT (0x0028L * 2) +#define CAPIDTMF_RECV_DIGIT_BUFFER_SIZE 32 +#define CAPIDTMF_RECV_STATE_IDLE 0x00 +#define CAPIDTMF_RECV_STATE_DTMF_ACTIVE 0x01 +typedef struct tag_capidtmf_recv_state +{ + byte digit_buffer[CAPIDTMF_RECV_DIGIT_BUFFER_SIZE]; + word digit_write_pos; + word digit_read_pos; + word indication_state; + word indication_state_ack; + long goertzel_buffer[2][CAPIDTMF_RECV_TOTAL_FREQUENCY_COUNT]; + word min_gap_duration; + word min_digit_duration; + word cycle_counter; + word current_digit_on_time; + word current_digit_off_time; + byte current_digit_value; + byte state; +} t_capidtmf_recv_state; +typedef struct tag_capidtmf_state +{ + byte ulaw; + t_capidtmf_recv_state recv; +} t_capidtmf_state; +word capidtmf_recv_indication (t_capidtmf_state *p_state, byte *buffer); +void capidtmf_recv_block (t_capidtmf_state *p_state, byte *buffer, word length); +void capidtmf_init (t_capidtmf_state *p_state, byte ulaw); +void capidtmf_recv_enable (t_capidtmf_state *p_state, word min_digit_duration, word min_gap_duration); +void capidtmf_recv_disable (t_capidtmf_state *p_state); +#define capidtmf_indication(p_state,buffer) (((p_state)->recv.indication_state != (p_state)->recv.indication_state_ack) ? capidtmf_recv_indication (p_state, buffer) : 0) +#define capidtmf_recv_process_block(p_state,buffer,length) { if ((p_state)->recv.state != CAPIDTMF_RECV_STATE_IDLE) capidtmf_recv_block (p_state, buffer, length); } +/*---------------------------------------------------------------------------*/ +/*---------------------------------------------------------------------------*/ +#endif diff --git a/drivers/isdn/hardware/eicon/capifunc.c b/drivers/isdn/hardware/eicon/capifunc.c new file mode 100644 index 000000000000..0afd7633556d --- /dev/null +++ b/drivers/isdn/hardware/eicon/capifunc.c @@ -0,0 +1,1219 @@ +/* $Id: capifunc.c,v 1.61.4.7 2005/02/11 19:40:25 armin Exp $ + * + * ISDN interface module for Eicon active cards DIVA. + * CAPI Interface common functions + * + * Copyright 2000-2003 by Armin Schindler (mac@melware.de) + * Copyright 2000-2003 Cytronics & Melware (info@melware.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include "platform.h" +#include "os_capi.h" +#include "di_defs.h" +#include "capi20.h" +#include "divacapi.h" +#include "divasync.h" +#include "capifunc.h" + +#define DBG_MINIMUM (DL_LOG + DL_FTL + DL_ERR) +#define DBG_DEFAULT (DBG_MINIMUM + DL_XLOG + DL_REG) + +DIVA_CAPI_ADAPTER *adapter = (DIVA_CAPI_ADAPTER *) NULL; +APPL *application = (APPL *) NULL; +byte max_appl = MAX_APPL; +byte max_adapter = 0; +static CAPI_MSG *mapped_msg = (CAPI_MSG *) NULL; + +byte UnMapController(byte); +char DRIVERRELEASE_CAPI[32]; + +extern void AutomaticLaw(DIVA_CAPI_ADAPTER *); +extern void callback(ENTITY *); +extern word api_remove_start(void); +extern word CapiRelease(word); +extern word CapiRegister(word); +extern word api_put(APPL *, CAPI_MSG *); + +static diva_os_spin_lock_t api_lock; + +static LIST_HEAD(cards); + +static dword notify_handle; +static void DIRequest(ENTITY * e); +static DESCRIPTOR MAdapter; +static DESCRIPTOR DAdapter; +static byte ControllerMap[MAX_DESCRIPTORS + 1]; + + +static void diva_register_appl(struct capi_ctr *, __u16, + capi_register_params *); +static void diva_release_appl(struct capi_ctr *, __u16); +static char *diva_procinfo(struct capi_ctr *); +static u16 diva_send_message(struct capi_ctr *, + diva_os_message_buffer_s *); +extern void diva_os_set_controller_struct(struct capi_ctr *); + +extern void DIVA_DIDD_Read(DESCRIPTOR *, int); + +/* + * debug + */ +static void no_printf(unsigned char *, ...); +#include "debuglib.c" +static void xlog(char *x, ...) +{ +#ifndef DIVA_NO_DEBUGLIB + va_list ap; + if (myDriverDebugHandle.dbgMask & DL_XLOG) { + va_start(ap, x); + if (myDriverDebugHandle.dbg_irq) { + myDriverDebugHandle.dbg_irq(myDriverDebugHandle.id, + DLI_XLOG, x, ap); + } else if (myDriverDebugHandle.dbg_old) { + myDriverDebugHandle.dbg_old(myDriverDebugHandle.id, + x, ap); + } + va_end(ap); + } +#endif +} + +/* + * info for proc + */ +static char *diva_procinfo(struct capi_ctr *ctrl) +{ + return (ctrl->serial); +} + +/* + * stop debugging + */ +static void stop_dbg(void) +{ + DbgDeregister(); + memset(&MAdapter, 0, sizeof(MAdapter)); + dprintf = no_printf; +} + +/* + * dummy debug function + */ +static void no_printf(unsigned char *x, ...) +{ +} + +/* + * Controller mapping + */ +byte MapController(byte Controller) +{ + byte i; + byte MappedController = 0; + byte ctrl = Controller & 0x7f; /* mask external controller bit off */ + + for (i = 1; i < max_adapter + 1; i++) { + if (ctrl == ControllerMap[i]) { + MappedController = (byte) i; + break; + } + } + if (i > max_adapter) { + ControllerMap[0] = ctrl; + MappedController = 0; + } + return (MappedController | (Controller & 0x80)); /* put back external controller bit */ +} + +/* + * Controller unmapping + */ +byte UnMapController(byte MappedController) +{ + byte Controller; + byte ctrl = MappedController & 0x7f; /* mask external controller bit off */ + + if (ctrl <= max_adapter) { + Controller = ControllerMap[ctrl]; + } else { + Controller = 0; + } + + return (Controller | (MappedController & 0x80)); /* put back external controller bit */ +} + +/* + * find a new free id + */ +static int find_free_id(void) +{ + int num = 0; + DIVA_CAPI_ADAPTER *a; + + while (num < MAX_DESCRIPTORS) { + a = &adapter[num]; + if (!a->Id) + break; + num++; + } + return(num + 1); +} + +/* + * find a card structure by controller number + */ +static diva_card *find_card_by_ctrl(word controller) +{ + struct list_head *tmp; + diva_card *card; + + list_for_each(tmp, &cards) { + card = list_entry(tmp, diva_card, list); + if (ControllerMap[card->Id] == controller) { + if (card->remove_in_progress) + card = NULL; + return(card); + } + } + return (diva_card *) 0; +} + +/* + * Buffer RX/TX + */ +void *TransmitBufferSet(APPL * appl, dword ref) +{ + appl->xbuffer_used[ref] = TRUE; + DBG_PRV1(("%d:xbuf_used(%d)", appl->Id, ref + 1)) + return (void *) ref; +} + +void *TransmitBufferGet(APPL * appl, void *p) +{ + if (appl->xbuffer_internal[(dword) p]) + return appl->xbuffer_internal[(dword) p]; + + return appl->xbuffer_ptr[(dword) p]; +} + +void TransmitBufferFree(APPL * appl, void *p) +{ + appl->xbuffer_used[(dword) p] = FALSE; + DBG_PRV1(("%d:xbuf_free(%d)", appl->Id, ((dword) p) + 1)) +} + +void *ReceiveBufferGet(APPL * appl, int Num) +{ + return &appl->ReceiveBuffer[Num * appl->MaxDataLength]; +} + +/* + * api_remove_start/complete for cleanup + */ +void api_remove_complete(void) +{ + DBG_PRV1(("api_remove_complete")) +} + +/* + * main function called by message.c + */ +void sendf(APPL * appl, word command, dword Id, word Number, byte * format, ...) +{ + word i, j; + word length = 12, dlength = 0; + byte *write; + CAPI_MSG msg; + byte *string = NULL; + va_list ap; + diva_os_message_buffer_s *dmb; + diva_card *card = NULL; + dword tmp; + + if (!appl) + return; + + DBG_PRV1(("sendf(a=%d,cmd=%x,format=%s)", + appl->Id, command, (byte *) format)) + + PUT_WORD(&msg.header.appl_id, appl->Id); + PUT_WORD(&msg.header.command, command); + if ((byte) (command >> 8) == 0x82) + Number = appl->Number++; + PUT_WORD(&msg.header.number, Number); + + PUT_DWORD(&msg.header.controller, Id); + write = (byte *) & msg; + write += 12; + + va_start(ap, format); + for (i = 0; format[i]; i++) { + switch (format[i]) { + case 'b': + tmp = va_arg(ap, dword); + *(byte *) write = (byte) (tmp & 0xff); + write += 1; + length += 1; + break; + case 'w': + tmp = va_arg(ap, dword); + PUT_WORD(write, (tmp & 0xffff)); + write += 2; + length += 2; + break; + case 'd': + tmp = va_arg(ap, dword); + PUT_DWORD(write, tmp); + write += 4; + length += 4; + break; + case 's': + case 'S': + string = va_arg(ap, byte *); + length += string[0] + 1; + for (j = 0; j <= string[0]; j++) + *write++ = string[j]; + break; + } + } + va_end(ap); + + PUT_WORD(&msg.header.length, length); + msg.header.controller = UnMapController(msg.header.controller); + + if (command == _DATA_B3_I) + dlength = GET_WORD( + ((byte *) & msg.info.data_b3_ind.Data_Length)); + + if (!(dmb = diva_os_alloc_message_buffer(length + dlength, + (void **) &write))) { + DBG_ERR(("sendf: alloc_message_buffer failed, incoming msg dropped.")) + return; + } + + /* copy msg header to sk_buff */ + memcpy(write, (byte *) & msg, length); + + /* if DATA_B3_IND, copy data too */ + if (command == _DATA_B3_I) { + dword data = GET_DWORD(&msg.info.data_b3_ind.Data); + memcpy(write + length, (void *) data, dlength); + } + +#ifndef DIVA_NO_DEBUGLIB + if (myDriverDebugHandle.dbgMask & DL_XLOG) { + switch (command) { + default: + xlog("\x00\x02", &msg, 0x81, length); + break; + case _DATA_B3_R | CONFIRM: + if (myDriverDebugHandle.dbgMask & DL_BLK) + xlog("\x00\x02", &msg, 0x81, length); + break; + case _DATA_B3_I: + if (myDriverDebugHandle.dbgMask & DL_BLK) { + xlog("\x00\x02", &msg, 0x81, length); + for (i = 0; i < dlength; i += 256) { + DBG_BLK((((char *) GET_DWORD(&msg.info.data_b3_ind.Data)) + i, + ((dlength - i) < 256) ? (dlength - i) : 256)) + if (!(myDriverDebugHandle.dbgMask & DL_PRV0)) + break; /* not more if not explicitely requested */ + } + } + break; + } + } +#endif + + /* find the card structure for this controller */ + if (!(card = find_card_by_ctrl(write[8] & 0x7f))) { + DBG_ERR(("sendf - controller %d not found, incoming msg dropped", + write[8] & 0x7f)) + diva_os_free_message_buffer(dmb); + return; + } + /* send capi msg to capi layer */ + capi_ctr_handle_message(&card->capi_ctrl, appl->Id, dmb); +} + +/* + * cleanup adapter + */ +static void clean_adapter(int id, struct list_head *free_mem_q) +{ + DIVA_CAPI_ADAPTER *a; + int i, k; + + a = &adapter[id]; + k = li_total_channels - a->li_channels; + if (k == 0) { + if (li_config_table) { + list_add((struct list_head *)li_config_table, free_mem_q); + li_config_table = NULL; + } + } else { + if (a->li_base < k) { + memmove(&li_config_table[a->li_base], + &li_config_table[a->li_base + a->li_channels], + (k - a->li_base) * sizeof(LI_CONFIG)); + for (i = 0; i < k; i++) { + memmove(&li_config_table[i].flag_table[a->li_base], + &li_config_table[i].flag_table[a->li_base + a->li_channels], + k - a->li_base); + memmove(&li_config_table[i]. + coef_table[a->li_base], + &li_config_table[i].coef_table[a->li_base + a->li_channels], + k - a->li_base); + } + } + } + li_total_channels = k; + for (i = id; i < max_adapter; i++) { + if (adapter[i].request) + adapter[i].li_base -= a->li_channels; + } + if (a->plci) + list_add((struct list_head *)a->plci, free_mem_q); + + memset(a, 0x00, sizeof(DIVA_CAPI_ADAPTER)); + while ((max_adapter != 0) && !adapter[max_adapter - 1].request) + max_adapter--; +} + +/* + * remove a card, but ensures consistent state of LI tables + * in the time adapter is removed + */ +static void divacapi_remove_card(DESCRIPTOR * d) +{ + diva_card *card = NULL; + diva_os_spin_lock_magic_t old_irql; + LIST_HEAD(free_mem_q); + struct list_head *link; + struct list_head *tmp; + + /* + * Set "remove in progress flag". + * Ensures that there is no call from sendf to CAPI in + * the time CAPI controller is about to be removed. + */ + diva_os_enter_spin_lock(&api_lock, &old_irql, "remove card"); + list_for_each(tmp, &cards) { + card = list_entry(tmp, diva_card, list); + if (card->d.request == d->request) { + card->remove_in_progress = 1; + list_del(tmp); + break; + } + } + diva_os_leave_spin_lock(&api_lock, &old_irql, "remove card"); + + if (card) { + /* + * Detach CAPI. Sendf cannot call to CAPI any more. + * After detach no call to send_message() is done too. + */ + detach_capi_ctr(&card->capi_ctrl); + + /* + * Now get API lock (to ensure stable state of LI tables) + * and update the adapter map/LI table. + */ + diva_os_enter_spin_lock(&api_lock, &old_irql, "remove card"); + + clean_adapter(card->Id - 1, &free_mem_q); + DBG_TRC(("DelAdapterMap (%d) -> (%d)", + ControllerMap[card->Id], card->Id)) + ControllerMap[card->Id] = 0; + DBG_TRC(("adapter remove, max_adapter=%d", + max_adapter)); + diva_os_leave_spin_lock(&api_lock, &old_irql, "remove card"); + + /* After releasing the lock, we can free the memory */ + diva_os_free (0, card); + } + + /* free queued memory areas */ + list_for_each_safe(link, tmp, &free_mem_q) { + list_del(link); + diva_os_free(0, link); + } +} + +/* + * remove cards + */ +static void divacapi_remove_cards(void) +{ + DESCRIPTOR d; + struct list_head *tmp; + diva_card *card; + diva_os_spin_lock_magic_t old_irql; + +rescan: + diva_os_enter_spin_lock(&api_lock, &old_irql, "remove cards"); + list_for_each(tmp, &cards) { + card = list_entry(tmp, diva_card, list); + diva_os_leave_spin_lock(&api_lock, &old_irql, "remove cards"); + d.request = card->d.request; + divacapi_remove_card(&d); + goto rescan; + } + diva_os_leave_spin_lock(&api_lock, &old_irql, "remove cards"); +} + +/* + * sync_callback + */ +static void sync_callback(ENTITY * e) +{ + diva_os_spin_lock_magic_t old_irql; + + DBG_TRC(("cb:Id=%x,Rc=%x,Ind=%x", e->Id, e->Rc, e->Ind)) + + diva_os_enter_spin_lock(&api_lock, &old_irql, "sync_callback"); + callback(e); + diva_os_leave_spin_lock(&api_lock, &old_irql, "sync_callback"); +} + +/* + * add a new card + */ +static int diva_add_card(DESCRIPTOR * d) +{ + int k = 0, i = 0; + diva_os_spin_lock_magic_t old_irql; + diva_card *card = NULL; + struct capi_ctr *ctrl = NULL; + DIVA_CAPI_ADAPTER *a = NULL; + IDI_SYNC_REQ sync_req; + char serial[16]; + void* mem_to_free; + LI_CONFIG *new_li_config_table; + int j; + + if (!(card = (diva_card *) diva_os_malloc(0, sizeof(diva_card)))) { + DBG_ERR(("diva_add_card: failed to allocate card struct.")) + return (0); + } + memset((char *) card, 0x00, sizeof(diva_card)); + memcpy(&card->d, d, sizeof(DESCRIPTOR)); + sync_req.GetName.Req = 0; + sync_req.GetName.Rc = IDI_SYNC_REQ_GET_NAME; + card->d.request((ENTITY *) & sync_req); + strlcpy(card->name, sync_req.GetName.name, sizeof(card->name)); + ctrl = &card->capi_ctrl; + strcpy(ctrl->name, card->name); + ctrl->register_appl = diva_register_appl; + ctrl->release_appl = diva_release_appl; + ctrl->send_message = diva_send_message; + ctrl->procinfo = diva_procinfo; + ctrl->driverdata = card; + diva_os_set_controller_struct(ctrl); + + if (attach_capi_ctr(ctrl)) { + DBG_ERR(("diva_add_card: failed to attach controller.")) + diva_os_free(0, card); + return (0); + } + + diva_os_enter_spin_lock(&api_lock, &old_irql, "find id"); + card->Id = find_free_id(); + diva_os_leave_spin_lock(&api_lock, &old_irql, "find id"); + + strlcpy(ctrl->manu, M_COMPANY, sizeof(ctrl->manu)); + ctrl->version.majorversion = 2; + ctrl->version.minorversion = 0; + ctrl->version.majormanuversion = DRRELMAJOR; + ctrl->version.minormanuversion = DRRELMINOR; + sync_req.GetSerial.Req = 0; + sync_req.GetSerial.Rc = IDI_SYNC_REQ_GET_SERIAL; + sync_req.GetSerial.serial = 0; + card->d.request((ENTITY *) & sync_req); + if ((i = ((sync_req.GetSerial.serial & 0xff000000) >> 24))) { + sprintf(serial, "%ld-%d", + sync_req.GetSerial.serial & 0x00ffffff, i + 1); + } else { + sprintf(serial, "%ld", sync_req.GetSerial.serial); + } + serial[CAPI_SERIAL_LEN - 1] = 0; + strlcpy(ctrl->serial, serial, sizeof(ctrl->serial)); + + a = &adapter[card->Id - 1]; + card->adapter = a; + a->os_card = card; + ControllerMap[card->Id] = (byte) (ctrl->cnr); + + DBG_TRC(("AddAdapterMap (%d) -> (%d)", ctrl->cnr, card->Id)) + + sync_req.xdi_capi_prms.Req = 0; + sync_req.xdi_capi_prms.Rc = IDI_SYNC_REQ_XDI_GET_CAPI_PARAMS; + sync_req.xdi_capi_prms.info.structure_length = + sizeof(diva_xdi_get_capi_parameters_t); + card->d.request((ENTITY *) & sync_req); + a->flag_dynamic_l1_down = + sync_req.xdi_capi_prms.info.flag_dynamic_l1_down; + a->group_optimization_enabled = + sync_req.xdi_capi_prms.info.group_optimization_enabled; + a->request = DIRequest; /* card->d.request; */ + a->max_plci = card->d.channels + 30; + a->max_listen = (card->d.channels > 2) ? 8 : 2; + if (! + (a->plci = + (PLCI *) diva_os_malloc(0, sizeof(PLCI) * a->max_plci))) { + DBG_ERR(("diva_add_card: failed alloc plci struct.")) + memset(a, 0, sizeof(DIVA_CAPI_ADAPTER)); + return (0); + } + memset(a->plci, 0, sizeof(PLCI) * a->max_plci); + + for (k = 0; k < a->max_plci; k++) { + a->Id = (byte) card->Id; + a->plci[k].Sig.callback = sync_callback; + a->plci[k].Sig.XNum = 1; + a->plci[k].Sig.X = a->plci[k].XData; + a->plci[k].Sig.user[0] = (word) (card->Id - 1); + a->plci[k].Sig.user[1] = (word) k; + a->plci[k].NL.callback = sync_callback; + a->plci[k].NL.XNum = 1; + a->plci[k].NL.X = a->plci[k].XData; + a->plci[k].NL.user[0] = (word) ((card->Id - 1) | 0x8000); + a->plci[k].NL.user[1] = (word) k; + a->plci[k].adapter = a; + } + + a->profile.Number = card->Id; + a->profile.Channels = card->d.channels; + if (card->d.features & DI_FAX3) { + a->profile.Global_Options = 0x71; + if (card->d.features & DI_CODEC) + a->profile.Global_Options |= 0x6; +#if IMPLEMENT_DTMF + a->profile.Global_Options |= 0x8; +#endif /* IMPLEMENT_DTMF */ + a->profile.Global_Options |= 0x80; /* Line Interconnect */ +#if IMPLEMENT_ECHO_CANCELLER + a->profile.Global_Options |= 0x100; +#endif /* IMPLEMENT_ECHO_CANCELLER */ + a->profile.B1_Protocols = 0xdf; + a->profile.B2_Protocols = 0x1fdb; + a->profile.B3_Protocols = 0xb7; + a->manufacturer_features = MANUFACTURER_FEATURE_HARDDTMF; + } else { + a->profile.Global_Options = 0x71; + if (card->d.features & DI_CODEC) + a->profile.Global_Options |= 0x2; + a->profile.B1_Protocols = 0x43; + a->profile.B2_Protocols = 0x1f0f; + a->profile.B3_Protocols = 0x07; + a->manufacturer_features = 0; + } + + a->li_pri = (a->profile.Channels > 2); + a->li_channels = a->li_pri ? MIXER_CHANNELS_PRI : MIXER_CHANNELS_BRI; + a->li_base = 0; + for (i = 0; &adapter[i] != a; i++) { + if (adapter[i].request) + a->li_base = adapter[i].li_base + adapter[i].li_channels; + } + k = li_total_channels + a->li_channels; + new_li_config_table = + (LI_CONFIG *) diva_os_malloc(0, ((k * sizeof(LI_CONFIG) + 3) & ~3) + (2 * k) * ((k + 3) & ~3)); + if (new_li_config_table == NULL) { + DBG_ERR(("diva_add_card: failed alloc li_config table.")) + memset(a, 0, sizeof(DIVA_CAPI_ADAPTER)); + return (0); + } + + /* Prevent access to line interconnect table in process update */ + diva_os_enter_spin_lock(&api_lock, &old_irql, "add card"); + + j = 0; + for (i = 0; i < k; i++) { + if ((i >= a->li_base) && (i < a->li_base + a->li_channels)) + memset(&new_li_config_table[i], 0, sizeof(LI_CONFIG)); + else + memcpy(&new_li_config_table[i], &li_config_table[j], sizeof(LI_CONFIG)); + new_li_config_table[i].flag_table = + ((byte *) new_li_config_table) + (((k * sizeof(LI_CONFIG) + 3) & ~3) + (2 * i) * ((k + 3) & ~3)); + new_li_config_table[i].coef_table = + ((byte *) new_li_config_table) + (((k * sizeof(LI_CONFIG) + 3) & ~3) + (2 * i + 1) * ((k + 3) & ~3)); + if ((i >= a->li_base) && (i < a->li_base + a->li_channels)) { + new_li_config_table[i].adapter = a; + memset(&new_li_config_table[i].flag_table[0], 0, k); + memset(&new_li_config_table[i].coef_table[0], 0, k); + } else { + if (a->li_base != 0) { + memcpy(&new_li_config_table[i].flag_table[0], + &li_config_table[j].flag_table[0], + a->li_base); + memcpy(&new_li_config_table[i].coef_table[0], + &li_config_table[j].coef_table[0], + a->li_base); + } + memset(&new_li_config_table[i].flag_table[a->li_base], 0, a->li_channels); + memset(&new_li_config_table[i].coef_table[a->li_base], 0, a->li_channels); + if (a->li_base + a->li_channels < k) { + memcpy(&new_li_config_table[i].flag_table[a->li_base + + a->li_channels], + &li_config_table[j].flag_table[a->li_base], + k - (a->li_base + a->li_channels)); + memcpy(&new_li_config_table[i].coef_table[a->li_base + + a->li_channels], + &li_config_table[j].coef_table[a->li_base], + k - (a->li_base + a->li_channels)); + } + j++; + } + } + li_total_channels = k; + + mem_to_free = li_config_table; + + li_config_table = new_li_config_table; + for (i = card->Id; i < max_adapter; i++) { + if (adapter[i].request) + adapter[i].li_base += a->li_channels; + } + + if (a == &adapter[max_adapter]) + max_adapter++; + + list_add(&(card->list), &cards); + AutomaticLaw(a); + + diva_os_leave_spin_lock(&api_lock, &old_irql, "add card"); + + if (mem_to_free) { + diva_os_free (0, mem_to_free); + } + + i = 0; + while (i++ < 30) { + if (a->automatic_law > 3) + break; + diva_os_sleep(10); + } + + /* profile information */ + PUT_WORD(&ctrl->profile.nbchannel, card->d.channels); + ctrl->profile.goptions = a->profile.Global_Options; + ctrl->profile.support1 = a->profile.B1_Protocols; + ctrl->profile.support2 = a->profile.B2_Protocols; + ctrl->profile.support3 = a->profile.B3_Protocols; + /* manufacturer profile information */ + ctrl->profile.manu[0] = a->man_profile.private_options; + ctrl->profile.manu[1] = a->man_profile.rtp_primary_payloads; + ctrl->profile.manu[2] = a->man_profile.rtp_additional_payloads; + ctrl->profile.manu[3] = 0; + ctrl->profile.manu[4] = 0; + + capi_ctr_ready(ctrl); + + DBG_TRC(("adapter added, max_adapter=%d", max_adapter)); + return (1); +} + +/* + * register appl + */ +static void diva_register_appl(struct capi_ctr *ctrl, __u16 appl, + capi_register_params * rp) +{ + APPL *this; + word bnum, xnum; + int i = 0; + unsigned char *p; + void *DataNCCI, *DataFlags, *ReceiveBuffer, *xbuffer_used; + void **xbuffer_ptr, **xbuffer_internal; + diva_os_spin_lock_magic_t old_irql; + unsigned int mem_len; + int nconn = rp->level3cnt; + + + if (diva_os_in_irq()) { + DBG_ERR(("CAPI_REGISTER - in irq context !")) + return; + } + + DBG_TRC(("application register Id=%d", appl)) + + if (appl > MAX_APPL) { + DBG_ERR(("CAPI_REGISTER - appl.Id exceeds MAX_APPL")) + return; + } + + if (nconn <= 0) + nconn = ctrl->profile.nbchannel * -nconn; + + if (nconn == 0) + nconn = ctrl->profile.nbchannel; + + DBG_LOG(("CAPI_REGISTER - Id = %d", appl)) + DBG_LOG((" MaxLogicalConnections = %d(%d)", nconn, rp->level3cnt)) + DBG_LOG((" MaxBDataBuffers = %d", rp->datablkcnt)) + DBG_LOG((" MaxBDataLength = %d", rp->datablklen)) + + if (nconn < 1 || + nconn > 255 || + rp->datablklen < 80 || + rp->datablklen > 2150 || rp->datablkcnt > 255) { + DBG_ERR(("CAPI_REGISTER - invalid parameters")) + return; + } + + if (application[appl - 1].Id == appl) { + DBG_LOG(("CAPI_REGISTER - appl already registered")) + return; /* appl already registered */ + } + + /* alloc memory */ + + bnum = nconn * rp->datablkcnt; + xnum = nconn * MAX_DATA_B3; + + mem_len = bnum * sizeof(word); /* DataNCCI */ + mem_len += bnum * sizeof(word); /* DataFlags */ + mem_len += bnum * rp->datablklen; /* ReceiveBuffer */ + mem_len += xnum; /* xbuffer_used */ + mem_len += xnum * sizeof(void *); /* xbuffer_ptr */ + mem_len += xnum * sizeof(void *); /* xbuffer_internal */ + mem_len += xnum * rp->datablklen; /* xbuffer_ptr[xnum] */ + + DBG_LOG((" Allocated Memory = %d", mem_len)) + if (!(p = diva_os_malloc(0, mem_len))) { + DBG_ERR(("CAPI_REGISTER - memory allocation failed")) + return; + } + memset(p, 0, mem_len); + + DataNCCI = (void *)p; + p += bnum * sizeof(word); + DataFlags = (void *)p; + p += bnum * sizeof(word); + ReceiveBuffer = (void *)p; + p += bnum * rp->datablklen; + xbuffer_used = (void *)p; + p += xnum; + xbuffer_ptr = (void **)p; + p += xnum * sizeof(void *); + xbuffer_internal = (void **)p; + p += xnum * sizeof(void *); + for (i = 0; i < xnum; i++) { + xbuffer_ptr[i] = (void *)p; + p += rp->datablklen; + } + + /* initialize application data */ + diva_os_enter_spin_lock(&api_lock, &old_irql, "register_appl"); + + this = &application[appl - 1]; + memset(this, 0, sizeof(APPL)); + + this->Id = appl; + + for (i = 0; i < max_adapter; i++) { + adapter[i].CIP_Mask[appl - 1] = 0; + } + + this->queue_size = 1000; + + this->MaxNCCI = (byte) nconn; + this->MaxNCCIData = (byte) rp->datablkcnt; + this->MaxBuffer = bnum; + this->MaxDataLength = rp->datablklen; + + this->DataNCCI = DataNCCI; + this->DataFlags = DataFlags; + this->ReceiveBuffer = ReceiveBuffer; + this->xbuffer_used = xbuffer_used; + this->xbuffer_ptr = xbuffer_ptr; + this->xbuffer_internal = xbuffer_internal; + for (i = 0; i < xnum; i++) { + this->xbuffer_ptr[i] = xbuffer_ptr[i]; + } + + CapiRegister(this->Id); + diva_os_leave_spin_lock(&api_lock, &old_irql, "register_appl"); + +} + +/* + * release appl + */ +static void diva_release_appl(struct capi_ctr *ctrl, __u16 appl) +{ + diva_os_spin_lock_magic_t old_irql; + APPL *this = &application[appl - 1]; + void *mem_to_free = NULL; + + DBG_TRC(("application %d(%d) cleanup", this->Id, appl)) + + if (diva_os_in_irq()) { + DBG_ERR(("CAPI_RELEASE - in irq context !")) + return; + } + + diva_os_enter_spin_lock(&api_lock, &old_irql, "release_appl"); + if (this->Id) { + CapiRelease(this->Id); + mem_to_free = this->DataNCCI; + this->DataNCCI = NULL; + this->Id = 0; + } + diva_os_leave_spin_lock(&api_lock, &old_irql, "release_appl"); + + if (mem_to_free) + diva_os_free(0, mem_to_free); + +} + +/* + * send message + */ +static u16 diva_send_message(struct capi_ctr *ctrl, + diva_os_message_buffer_s * dmb) +{ + int i = 0; + word ret = 0; + diva_os_spin_lock_magic_t old_irql; + CAPI_MSG *msg = (CAPI_MSG *) DIVA_MESSAGE_BUFFER_DATA(dmb); + APPL *this = &application[GET_WORD(&msg->header.appl_id) - 1]; + diva_card *card = ctrl->driverdata; + __u32 length = DIVA_MESSAGE_BUFFER_LEN(dmb); + word clength = GET_WORD(&msg->header.length); + word command = GET_WORD(&msg->header.command); + u16 retval = CAPI_NOERROR; + + if (diva_os_in_irq()) { + DBG_ERR(("CAPI_SEND_MSG - in irq context !")) + return CAPI_REGOSRESOURCEERR; + } + DBG_PRV1(("Write - appl = %d, cmd = 0x%x", this->Id, command)) + + if (card->remove_in_progress) { + DBG_ERR(("CAPI_SEND_MSG - remove in progress!")) + return CAPI_REGOSRESOURCEERR; + } + + diva_os_enter_spin_lock(&api_lock, &old_irql, "send message"); + + if (!this->Id) { + diva_os_leave_spin_lock(&api_lock, &old_irql, "send message"); + return CAPI_ILLAPPNR; + } + + /* patch controller number */ + msg->header.controller = ControllerMap[card->Id] + | (msg->header.controller & 0x80); /* preserve external controller bit */ + + switch (command) { + default: + xlog("\x00\x02", msg, 0x80, clength); + break; + + case _DATA_B3_I | RESPONSE: +#ifndef DIVA_NO_DEBUGLIB + if (myDriverDebugHandle.dbgMask & DL_BLK) + xlog("\x00\x02", msg, 0x80, clength); +#endif + break; + + case _DATA_B3_R: +#ifndef DIVA_NO_DEBUGLIB + if (myDriverDebugHandle.dbgMask & DL_BLK) + xlog("\x00\x02", msg, 0x80, clength); +#endif + + if (clength == 24) + clength = 22; /* workaround for PPcom bug */ + /* header is always 22 */ + if (GET_WORD(&msg->info.data_b3_req.Data_Length) > + this->MaxDataLength + || GET_WORD(&msg->info.data_b3_req.Data_Length) > + (length - clength)) { + DBG_ERR(("Write - invalid message size")) + retval = CAPI_ILLCMDORSUBCMDORMSGTOSMALL; + goto write_end; + } + + for (i = 0; i < (MAX_DATA_B3 * this->MaxNCCI) + && this->xbuffer_used[i]; i++); + if (i == (MAX_DATA_B3 * this->MaxNCCI)) { + DBG_ERR(("Write - too many data pending")) + retval = CAPI_SENDQUEUEFULL; + goto write_end; + } + msg->info.data_b3_req.Data = i; + + this->xbuffer_internal[i] = NULL; + memcpy(this->xbuffer_ptr[i], &((__u8 *) msg)[clength], + GET_WORD(&msg->info.data_b3_req.Data_Length)); + +#ifndef DIVA_NO_DEBUGLIB + if ((myDriverDebugHandle.dbgMask & DL_BLK) + && (myDriverDebugHandle.dbgMask & DL_XLOG)) { + int j; + for (j = 0; j < + GET_WORD(&msg->info.data_b3_req.Data_Length); + j += 256) { + DBG_BLK((((char *) this->xbuffer_ptr[i]) + j, + ((GET_WORD(&msg->info.data_b3_req.Data_Length) - j) < + 256) ? (GET_WORD(&msg->info.data_b3_req.Data_Length) - j) : 256)) + if (!(myDriverDebugHandle.dbgMask & DL_PRV0)) + break; /* not more if not explicitely requested */ + } + } +#endif + break; + } + + memcpy(mapped_msg, msg, (__u32) clength); + mapped_msg->header.controller = MapController(mapped_msg->header.controller); + mapped_msg->header.length = clength; + mapped_msg->header.command = command; + mapped_msg->header.number = GET_WORD(&msg->header.number); + + ret = api_put(this, mapped_msg); + switch (ret) { + case 0: + break; + case _BAD_MSG: + DBG_ERR(("Write - bad message")) + retval = CAPI_ILLCMDORSUBCMDORMSGTOSMALL; + break; + case _QUEUE_FULL: + DBG_ERR(("Write - queue full")) + retval = CAPI_SENDQUEUEFULL; + break; + default: + DBG_ERR(("Write - api_put returned unknown error")) + retval = CAPI_UNKNOWNNOTPAR; + break; + } + + write_end: + diva_os_leave_spin_lock(&api_lock, &old_irql, "send message"); + if (retval == CAPI_NOERROR) + diva_os_free_message_buffer(dmb); + return retval; +} + + +/* + * cards request function + */ +static void DIRequest(ENTITY * e) +{ + DIVA_CAPI_ADAPTER *a = &(adapter[(byte) e->user[0]]); + diva_card *os_card = (diva_card *) a->os_card; + + if (e->Req && (a->FlowControlIdTable[e->ReqCh] == e->Id)) { + a->FlowControlSkipTable[e->ReqCh] = 1; + } + + (*(os_card->d.request)) (e); +} + +/* + * callback function from didd + */ +static void didd_callback(void *context, DESCRIPTOR * adapter, int removal) +{ + if (adapter->type == IDI_DADAPTER) { + DBG_ERR(("Notification about IDI_DADAPTER change ! Oops.")); + return; + } else if (adapter->type == IDI_DIMAINT) { + if (removal) { + stop_dbg(); + } else { + memcpy(&MAdapter, adapter, sizeof(MAdapter)); + dprintf = (DIVA_DI_PRINTF) MAdapter.request; + DbgRegister("CAPI20", DRIVERRELEASE_CAPI, DBG_DEFAULT); + } + } else if ((adapter->type > 0) && (adapter->type < 16)) { /* IDI Adapter */ + if (removal) { + divacapi_remove_card(adapter); + } else { + diva_add_card(adapter); + } + } + return; +} + +/* + * connect to didd + */ +static int divacapi_connect_didd(void) +{ + int x = 0; + int dadapter = 0; + IDI_SYNC_REQ req; + DESCRIPTOR DIDD_Table[MAX_DESCRIPTORS]; + + DIVA_DIDD_Read(DIDD_Table, sizeof(DIDD_Table)); + + for (x = 0; x < MAX_DESCRIPTORS; x++) { + if (DIDD_Table[x].type == IDI_DIMAINT) { /* MAINT found */ + memcpy(&MAdapter, &DIDD_Table[x], sizeof(DAdapter)); + dprintf = (DIVA_DI_PRINTF) MAdapter.request; + DbgRegister("CAPI20", DRIVERRELEASE_CAPI, DBG_DEFAULT); + break; + } + } + for (x = 0; x < MAX_DESCRIPTORS; x++) { + if (DIDD_Table[x].type == IDI_DADAPTER) { /* DADAPTER found */ + dadapter = 1; + memcpy(&DAdapter, &DIDD_Table[x], sizeof(DAdapter)); + req.didd_notify.e.Req = 0; + req.didd_notify.e.Rc = + IDI_SYNC_REQ_DIDD_REGISTER_ADAPTER_NOTIFY; + req.didd_notify.info.callback = (void *)didd_callback; + req.didd_notify.info.context = NULL; + DAdapter.request((ENTITY *) & req); + if (req.didd_notify.e.Rc != 0xff) { + stop_dbg(); + return (0); + } + notify_handle = req.didd_notify.info.handle; + } + else if ((DIDD_Table[x].type > 0) && (DIDD_Table[x].type < 16)) { /* IDI Adapter found */ + diva_add_card(&DIDD_Table[x]); + } + } + + if (!dadapter) { + stop_dbg(); + } + + return (dadapter); +} + +/* + * diconnect from didd + */ +static void divacapi_disconnect_didd(void) +{ + IDI_SYNC_REQ req; + + stop_dbg(); + + req.didd_notify.e.Req = 0; + req.didd_notify.e.Rc = IDI_SYNC_REQ_DIDD_REMOVE_ADAPTER_NOTIFY; + req.didd_notify.info.handle = notify_handle; + DAdapter.request((ENTITY *) & req); +} + +/* + * we do not provide date/time here, + * the application should do this. + */ +int fax_head_line_time(char *buffer) +{ + return (0); +} + +/* + * init (alloc) main structures + */ +static int DIVA_INIT_FUNCTION init_main_structs(void) +{ + if (!(mapped_msg = (CAPI_MSG *) diva_os_malloc(0, MAX_MSG_SIZE))) { + DBG_ERR(("init: failed alloc mapped_msg.")) + return 0; + } + + if (!(adapter = diva_os_malloc(0, sizeof(DIVA_CAPI_ADAPTER) * MAX_DESCRIPTORS))) { + DBG_ERR(("init: failed alloc adapter struct.")) + diva_os_free(0, mapped_msg); + return 0; + } + memset(adapter, 0, sizeof(DIVA_CAPI_ADAPTER) * MAX_DESCRIPTORS); + + if (!(application = diva_os_malloc(0, sizeof(APPL) * MAX_APPL))) { + DBG_ERR(("init: failed alloc application struct.")) + diva_os_free(0, mapped_msg); + diva_os_free(0, adapter); + return 0; + } + memset(application, 0, sizeof(APPL) * MAX_APPL); + + return (1); +} + +/* + * remove (free) main structures + */ +static void remove_main_structs(void) +{ + if (application) + diva_os_free(0, application); + if (adapter) + diva_os_free(0, adapter); + if (mapped_msg) + diva_os_free(0, mapped_msg); +} + +/* + * api_remove_start + */ +static void do_api_remove_start(void) +{ + diva_os_spin_lock_magic_t old_irql; + int ret = 1, count = 100; + + do { + diva_os_enter_spin_lock(&api_lock, &old_irql, "api remove start"); + ret = api_remove_start(); + diva_os_leave_spin_lock(&api_lock, &old_irql, "api remove start"); + + diva_os_sleep(10); + } while (ret && count--); + + if (ret) + DBG_ERR(("could not remove signaling ID's")) +} + +/* + * init + */ +int DIVA_INIT_FUNCTION init_capifunc(void) +{ + diva_os_initialize_spin_lock(&api_lock, "capifunc"); + memset(ControllerMap, 0, MAX_DESCRIPTORS + 1); + max_adapter = 0; + + + if (!init_main_structs()) { + DBG_ERR(("init: failed to init main structs.")) + diva_os_destroy_spin_lock(&api_lock, "capifunc"); + return (0); + } + + if (!divacapi_connect_didd()) { + DBG_ERR(("init: failed to connect to DIDD.")) + do_api_remove_start(); + divacapi_remove_cards(); + remove_main_structs(); + diva_os_destroy_spin_lock(&api_lock, "capifunc"); + return (0); + } + + return (1); +} + +/* + * finit + */ +void DIVA_EXIT_FUNCTION finit_capifunc(void) +{ + do_api_remove_start(); + divacapi_disconnect_didd(); + divacapi_remove_cards(); + remove_main_structs(); + diva_os_destroy_spin_lock(&api_lock, "capifunc"); +} diff --git a/drivers/isdn/hardware/eicon/capifunc.h b/drivers/isdn/hardware/eicon/capifunc.h new file mode 100644 index 000000000000..bd256f29738c --- /dev/null +++ b/drivers/isdn/hardware/eicon/capifunc.h @@ -0,0 +1,40 @@ +/* $Id: capifunc.h,v 1.11.4.1 2004/08/28 20:03:53 armin Exp $ + * + * ISDN interface module for Eicon active cards DIVA. + * CAPI Interface common functions + * + * Copyright 2000-2003 by Armin Schindler (mac@melware.de) + * Copyright 2000-2003 Cytronics & Melware (info@melware.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + */ + +#ifndef __CAPIFUNC_H__ +#define __CAPIFUNC_H__ + +#define DRRELMAJOR 2 +#define DRRELMINOR 0 +#define DRRELEXTRA "" + +#define M_COMPANY "Eicon Networks" + +extern char DRIVERRELEASE_CAPI[]; + +typedef struct _diva_card { + struct list_head list; + int remove_in_progress; + int Id; + struct capi_ctr capi_ctrl; + DIVA_CAPI_ADAPTER *adapter; + DESCRIPTOR d; + char name[32]; +} diva_card; + +/* + * prototypes + */ +int init_capifunc(void); +void finit_capifunc(void); + +#endif /* __CAPIFUNC_H__ */ diff --git a/drivers/isdn/hardware/eicon/capimain.c b/drivers/isdn/hardware/eicon/capimain.c new file mode 100644 index 000000000000..8fe4f3f09353 --- /dev/null +++ b/drivers/isdn/hardware/eicon/capimain.c @@ -0,0 +1,147 @@ +/* $Id: capimain.c,v 1.24 2003/09/09 06:51:05 schindler Exp $ + * + * ISDN interface module for Eicon active cards DIVA. + * CAPI Interface + * + * Copyright 2000-2003 by Armin Schindler (mac@melware.de) + * Copyright 2000-2003 Cytronics & Melware (info@melware.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/init.h> +#include <asm/uaccess.h> +#include <linux/smp_lock.h> +#include <linux/skbuff.h> + +#include "os_capi.h" + +#include "platform.h" +#include "di_defs.h" +#include "capi20.h" +#include "divacapi.h" +#include "cp_vers.h" +#include "capifunc.h" + +static char *main_revision = "$Revision: 1.24 $"; +static char *DRIVERNAME = + "Eicon DIVA - CAPI Interface driver (http://www.melware.net)"; +static char *DRIVERLNAME = "divacapi"; + +MODULE_DESCRIPTION("CAPI driver for Eicon DIVA cards"); +MODULE_AUTHOR("Cytronics & Melware, Eicon Networks"); +MODULE_SUPPORTED_DEVICE("CAPI and DIVA card drivers"); +MODULE_LICENSE("GPL"); + +/* + * get revision number from revision string + */ +static char *getrev(const char *revision) +{ + char *rev; + char *p; + if ((p = strchr(revision, ':'))) { + rev = p + 2; + p = strchr(rev, '$'); + *--p = 0; + } else + rev = "1.0"; + return rev; + +} + +/* + * alloc a message buffer + */ +diva_os_message_buffer_s *diva_os_alloc_message_buffer(unsigned long size, + void **data_buf) +{ + diva_os_message_buffer_s *dmb = alloc_skb(size, GFP_ATOMIC); + if (dmb) { + *data_buf = skb_put(dmb, size); + } + return (dmb); +} + +/* + * free a message buffer + */ +void diva_os_free_message_buffer(diva_os_message_buffer_s * dmb) +{ + kfree_skb(dmb); +} + +/* + * proc function for controller info + */ +static int diva_ctl_read_proc(char *page, char **start, off_t off, + int count, int *eof, struct capi_ctr *ctrl) +{ + diva_card *card = (diva_card *) ctrl->driverdata; + int len = 0; + + len += sprintf(page + len, "%s\n", ctrl->name); + len += sprintf(page + len, "Serial No. : %s\n", ctrl->serial); + len += sprintf(page + len, "Id : %d\n", card->Id); + len += sprintf(page + len, "Channels : %d\n", card->d.channels); + + if (off + count >= len) + *eof = 1; + if (len < off) + return 0; + *start = page + off; + return ((count < len - off) ? count : len - off); +} + +/* + * set additional os settings in capi_ctr struct + */ +void diva_os_set_controller_struct(struct capi_ctr *ctrl) +{ + ctrl->driver_name = DRIVERLNAME; + ctrl->load_firmware = NULL; + ctrl->reset_ctr = NULL; + ctrl->ctr_read_proc = diva_ctl_read_proc; + ctrl->owner = THIS_MODULE; +} + +/* + * module init + */ +static int DIVA_INIT_FUNCTION divacapi_init(void) +{ + char tmprev[32]; + int ret = 0; + + sprintf(DRIVERRELEASE_CAPI, "%d.%d%s", DRRELMAJOR, DRRELMINOR, + DRRELEXTRA); + + printk(KERN_INFO "%s\n", DRIVERNAME); + printk(KERN_INFO "%s: Rel:%s Rev:", DRIVERLNAME, DRIVERRELEASE_CAPI); + strcpy(tmprev, main_revision); + printk("%s Build: %s(%s)\n", getrev(tmprev), + diva_capi_common_code_build, DIVA_BUILD); + + if (!(init_capifunc())) { + printk(KERN_ERR "%s: failed init capi_driver.\n", + DRIVERLNAME); + ret = -EIO; + } + + return ret; +} + +/* + * module exit + */ +static void DIVA_EXIT_FUNCTION divacapi_exit(void) +{ + finit_capifunc(); + printk(KERN_INFO "%s: module unloaded.\n", DRIVERLNAME); +} + +module_init(divacapi_init); +module_exit(divacapi_exit); diff --git a/drivers/isdn/hardware/eicon/cardtype.h b/drivers/isdn/hardware/eicon/cardtype.h new file mode 100644 index 000000000000..18a5c42fffdb --- /dev/null +++ b/drivers/isdn/hardware/eicon/cardtype.h @@ -0,0 +1,1098 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +#ifndef _CARDTYPE_H_ +#define _CARDTYPE_H_ +#ifndef CARDTYPE_H_WANT_DATA +#define CARDTYPE_H_WANT_DATA 0 +#endif +#ifndef CARDTYPE_H_WANT_IDI_DATA +#define CARDTYPE_H_WANT_IDI_DATA 0 +#endif +#ifndef CARDTYPE_H_WANT_RESOURCE_DATA +#define CARDTYPE_H_WANT_RESOURCE_DATA 1 +#endif +#ifndef CARDTYPE_H_WANT_FILE_DATA +#define CARDTYPE_H_WANT_FILE_DATA 1 +#endif +/* + * D-channel protocol identifiers + * + * Attention: Unfortunately the identifiers defined here differ from + * the identifiers used in Protocol/1/Common/prot/q931.h . + * The only reason for this is that q931.h has not a global + * scope and we did not know about the definitions there. + * But the definitions here cannot be changed easily because + * they are used in setup scripts and programs. + * Thus the definitions here have to be mapped if they are + * used in the protocol code context ! + * + * Now the identifiers are defined in the q931lib/constant.h file. + * Unfortunately this file has also not a global scope. + * But beginning with PROTTYPE_US any new identifier will get the same + * value as the corresponding PROT_* definition in q931lib/constant.h ! + */ +#define PROTTYPE_MINVAL 0 +#define PROTTYPE_ETSI 0 +#define PROTTYPE_1TR6 1 +#define PROTTYPE_BELG 2 +#define PROTTYPE_FRANC 3 +#define PROTTYPE_ATEL 4 +#define PROTTYPE_NI 5 /* DMS 100, Nortel, National ISDN */ +#define PROTTYPE_5ESS 6 /* 5ESS , AT&T, 5ESS Custom */ +#define PROTTYPE_JAPAN 7 +#define PROTTYPE_SWED 8 +#define PROTTYPE_US 9 /* US autodetect */ +#define PROTTYPE_ITALY 10 +#define PROTTYPE_TWAN 11 +#define PROTTYPE_AUSTRAL 12 +#define PROTTYPE_4ESDN 13 +#define PROTTYPE_4ESDS 14 +#define PROTTYPE_4ELDS 15 +#define PROTTYPE_4EMGC 16 +#define PROTTYPE_4EMGI 17 +#define PROTTYPE_HONGKONG 18 +#define PROTTYPE_RBSCAS 19 +#define PROTTYPE_CORNETN 20 +#define PROTTYPE_QSIG 21 +#define PROTTYPE_NI_EWSD 22 /* EWSD, Siemens, National ISDN */ +#define PROTTYPE_5ESS_NI 23 /* 5ESS, Lucent, National ISDN */ +#define PROTTYPE_T1CORNETN 24 +#define PROTTYPE_CORNETNQ 25 +#define PROTTYPE_T1CORNETNQ 26 +#define PROTTYPE_T1QSIG 27 +#define PROTTYPE_E1UNCH 28 +#define PROTTYPE_T1UNCH 29 +#define PROTTYPE_E1CHAN 30 +#define PROTTYPE_T1CHAN 31 +#define PROTTYPE_R2CAS 32 +#define PROTTYPE_MAXVAL 32 +/* + * Card type identifiers + */ +#define CARD_UNKNOWN 0 +#define CARD_NONE 0 + /* DIVA cards */ +#define CARDTYPE_DIVA_MCA 0 +#define CARDTYPE_DIVA_ISA 1 +#define CARDTYPE_DIVA_PCM 2 +#define CARDTYPE_DIVAPRO_ISA 3 +#define CARDTYPE_DIVAPRO_PCM 4 +#define CARDTYPE_DIVAPICO_ISA 5 +#define CARDTYPE_DIVAPICO_PCM 6 + /* DIVA 2.0 cards */ +#define CARDTYPE_DIVAPRO20_PCI 7 +#define CARDTYPE_DIVA20_PCI 8 + /* S cards */ +#define CARDTYPE_QUADRO_ISA 9 +#define CARDTYPE_S_ISA 10 +#define CARDTYPE_S_MCA 11 +#define CARDTYPE_SX_ISA 12 +#define CARDTYPE_SX_MCA 13 +#define CARDTYPE_SXN_ISA 14 +#define CARDTYPE_SXN_MCA 15 +#define CARDTYPE_SCOM_ISA 16 +#define CARDTYPE_SCOM_MCA 17 +#define CARDTYPE_PR_ISA 18 +#define CARDTYPE_PR_MCA 19 + /* Diva Server cards (formerly called Maestra, later Amadeo) */ +#define CARDTYPE_MAESTRA_ISA 20 +#define CARDTYPE_MAESTRA_PCI 21 + /* Diva Server cards to be developed (Quadro, Primary rate) */ +#define CARDTYPE_DIVASRV_Q_8M_PCI 22 +#define CARDTYPE_DIVASRV_P_30M_PCI 23 +#define CARDTYPE_DIVASRV_P_2M_PCI 24 +#define CARDTYPE_DIVASRV_P_9M_PCI 25 + /* DIVA 2.0 cards */ +#define CARDTYPE_DIVA20_ISA 26 +#define CARDTYPE_DIVA20U_ISA 27 +#define CARDTYPE_DIVA20U_PCI 28 +#define CARDTYPE_DIVAPRO20_ISA 29 +#define CARDTYPE_DIVAPRO20U_ISA 30 +#define CARDTYPE_DIVAPRO20U_PCI 31 + /* DIVA combi cards (piccola ISDN + rockwell V.34 modem) */ +#define CARDTYPE_DIVAMOBILE_PCM 32 +#define CARDTYPE_TDKGLOBALPRO_PCM 33 + /* DIVA Pro PC OEM card for 'New Media Corporation' */ +#define CARDTYPE_NMC_DIVAPRO_PCM 34 + /* DIVA Pro 2.0 OEM cards for 'British Telecom' */ +#define CARDTYPE_BT_EXLANE_PCI 35 +#define CARDTYPE_BT_EXLANE_ISA 36 + /* DIVA low cost cards, 1st name DIVA 3.0, 2nd DIVA 2.01, 3rd ??? */ +#define CARDTYPE_DIVALOW_ISA 37 +#define CARDTYPE_DIVALOWU_ISA 38 +#define CARDTYPE_DIVALOW_PCI 39 +#define CARDTYPE_DIVALOWU_PCI 40 + /* DIVA combi cards (piccola ISDN + rockwell V.90 modem) */ +#define CARDTYPE_DIVAMOBILE_V90_PCM 41 +#define CARDTYPE_TDKGLOBPRO_V90_PCM 42 +#define CARDTYPE_DIVASRV_P_23M_PCI 43 +#define CARDTYPE_DIVALOW_USB 44 + /* DIVA Audio (CT) family */ +#define CARDTYPE_DIVA_CT_ST 45 +#define CARDTYPE_DIVA_CT_U 46 +#define CARDTYPE_DIVA_CTLITE_ST 47 +#define CARDTYPE_DIVA_CTLITE_U 48 + /* DIVA ISDN plus V.90 series */ +#define CARDTYPE_DIVAISDN_V90_PCM 49 +#define CARDTYPE_DIVAISDN_V90_PCI 50 +#define CARDTYPE_DIVAISDN_TA 51 + /* DIVA Server Voice cards */ +#define CARDTYPE_DIVASRV_VOICE_Q_8M_PCI 52 + /* DIVA Server V2 cards */ +#define CARDTYPE_DIVASRV_Q_8M_V2_PCI 53 +#define CARDTYPE_DIVASRV_P_30M_V2_PCI 54 + /* DIVA Server Voice V2 cards */ +#define CARDTYPE_DIVASRV_VOICE_Q_8M_V2_PCI 55 +#define CARDTYPE_DIVASRV_VOICE_P_30M_V2_PCI 56 + /* Diva LAN */ +#define CARDTYPE_DIVAISDN_LAN 57 +#define CARDTYPE_DIVA_202_PCI_ST 58 +#define CARDTYPE_DIVA_202_PCI_U 59 +#define CARDTYPE_DIVASRV_B_2M_V2_PCI 60 +#define CARDTYPE_DIVASRV_B_2F_PCI 61 +#define CARDTYPE_DIVALOW_USBV2 62 +#define CARDTYPE_DIVASRV_VOICE_B_2M_V2_PCI 63 +#define CARDTYPE_DIVA_PRO_30_PCI_ST 64 +#define CARDTYPE_DIVA_CT_ST_V20 65 +/* Diva Mobile V.90 PC Card and Diva ISDN PC Card */ +#define CARDTYPE_DIVAMOBILE_V2_PCM 66 +#define CARDTYPE_DIVA_V2_PCM 67 +/* Re-badged Diva Pro PC Card */ +#define CARDTYPE_DIVA_PC_CARD 68 + /* next free card type identifier */ +#define CARDTYPE_MAX 69 +/* + * The card families + */ +#define FAMILY_DIVA 1 +#define FAMILY_S 2 +#define FAMILY_MAESTRA 3 +#define FAMILY_MAX 4 +/* + * The basic card types + */ +#define CARD_DIVA 1 /* DSP based, old DSP */ +#define CARD_PRO 2 /* DSP based, new DSP */ +#define CARD_PICO 3 /* HSCX based */ +#define CARD_S 4 /* IDI on board based */ +#define CARD_SX 5 /* IDI on board based */ +#define CARD_SXN 6 /* IDI on board based */ +#define CARD_SCOM 7 /* IDI on board based */ +#define CARD_QUAD 8 /* IDI on board based */ +#define CARD_PR 9 /* IDI on board based */ +#define CARD_MAE 10 /* IDI on board based */ +#define CARD_MAEQ 11 /* IDI on board based */ +#define CARD_MAEP 12 /* IDI on board based */ +#define CARD_DIVALOW 13 /* IPAC based */ +#define CARD_CT 14 /* SCOUT based */ +#define CARD_DIVATA 15 /* DIVA TA */ +#define CARD_DIVALAN 16 /* DIVA LAN */ +#define CARD_MAE2 17 /* IDI on board based */ +#define CARD_MAX 18 +/* + * The internal card types of the S family + */ +#define CARD_I_NONE 0 +#define CARD_I_S 0 +#define CARD_I_SX 1 +#define CARD_I_SCOM 2 +#define CARD_I_QUAD 3 +#define CARD_I_PR 4 +/* + * The bus types we support + */ +#define BUS_ISA 1 +#define BUS_PCM 2 +#define BUS_PCI 3 +#define BUS_MCA 4 +#define BUS_USB 5 +#define BUS_COM 6 +#define BUS_LAN 7 +/* + * The chips we use for B-channel traffic + */ +#define CHIP_NONE 0 +#define CHIP_DSP 1 +#define CHIP_HSCX 2 +#define CHIP_IPAC 3 +#define CHIP_SCOUT 4 +#define CHIP_EXTERN 5 +#define CHIP_IPACX 6 +/* + * The structures where the card properties are aggregated by id + */ +typedef struct CARD_PROPERTIES +{ char *Name; /* official marketing name */ + unsigned short PnPId; /* plug and play ID (for non PCMIA cards) */ + unsigned short Version; /* major and minor version no of the card */ + unsigned char DescType; /* card type to set in the IDI descriptor */ + unsigned char Family; /* basic family of the card */ + unsigned short Features; /* features bits to set in the IDI desc. */ + unsigned char Card; /* basic card type */ + unsigned char IType; /* internal type of S cards (read from ram) */ + unsigned char Bus; /* bus type this card is designed for */ + unsigned char Chip; /* chipset used on card */ + unsigned char Adapters; /* number of adapters on card */ + unsigned char Channels; /* # of channels per adapter */ + unsigned short E_info; /* # of ram entity info structs per adapter */ + unsigned short SizeIo; /* size of IO window per adapter */ + unsigned short SizeMem; /* size of memory window per adapter */ +} CARD_PROPERTIES; +typedef struct CARD_RESOURCE +{ unsigned char Int [10]; + unsigned short IoFirst; + unsigned short IoStep; + unsigned short IoCnt; + unsigned long MemFirst; + unsigned long MemStep; + unsigned short MemCnt; +} CARD_RESOURCE; +/* test if the card of type 't' is a plug & play card */ +#define IS_PNP(t) \ +( \ + ( \ + CardProperties[t].Bus != BUS_ISA \ + && \ + CardProperties[t].Bus != BUS_MCA \ + ) \ + || \ + ( \ + CardProperties[t].Family != FAMILY_S \ + && \ + CardProperties[t].Card != CARD_DIVA \ + ) \ +) +/* extract IDI Descriptor info for card type 't' (p == DescType/Features) */ +#define IDI_PROP(t,p) (CardProperties[t].p) +#if CARDTYPE_H_WANT_DATA +#if CARDTYPE_H_WANT_IDI_DATA +/* include "di_defs.h" for IDI adapter type and feature flag definitions */ +#include "di_defs.h" +#else /*!CARDTYPE_H_WANT_IDI_DATA*/ +/* define IDI adapter types and feature flags here to prevent inclusion */ +#ifndef IDI_ADAPTER_S +#define IDI_ADAPTER_S 1 +#define IDI_ADAPTER_PR 2 +#define IDI_ADAPTER_DIVA 3 +#define IDI_ADAPTER_MAESTRA 4 +#endif +#ifndef DI_VOICE +#define DI_VOICE 0x0 /* obsolete define */ +#define DI_FAX3 0x1 +#define DI_MODEM 0x2 +#define DI_POST 0x4 +#define DI_V110 0x8 +#define DI_V120 0x10 +#define DI_POTS 0x20 +#define DI_CODEC 0x40 +#define DI_MANAGE 0x80 +#define DI_V_42 0x0100 +#define DI_EXTD_FAX 0x0200 /* Extended FAX (ECM, 2D, T.6, Polling) */ +#define DI_AT_PARSER 0x0400 /* Build-in AT Parser in the L2 */ +#define DI_VOICE_OVER_IP 0x0800 /* Voice over IP support */ +#endif +#endif /*CARDTYPE_H_WANT_IDI_DATA*/ +#define DI_V1x0 (DI_V110 | DI_V120) +#define DI_NULL 0x0000 +#if defined(SOFT_DSP_SUPPORT) +#define SOFT_DSP_ADD_FEATURES (DI_MODEM | DI_FAX3 | DI_AT_PARSER) +#else +#define SOFT_DSP_ADD_FEATURES 0 +#endif +#if defined(SOFT_V110_SUPPORT) +#define DI_SOFT_V110 DI_V110 +#else +#define DI_SOFT_V110 0 +#endif +/*--- CardProperties [Index=CARDTYPE_....] ---------------------------------*/ +CARD_PROPERTIES CardProperties [ ] = +{ +{ /* 0 */ + "Diva MCA", 0x6336, 0x0100, + IDI_ADAPTER_DIVA, FAMILY_DIVA, DI_V1x0 | DI_FAX3, + CARD_DIVA, CARD_I_NONE, BUS_MCA, CHIP_DSP, + 1, 2, 0, 8, 0 +}, +{ /* 1 */ + "Diva ISA", 0x0000, 0x0100, + IDI_ADAPTER_DIVA, FAMILY_DIVA, DI_V1x0 | DI_FAX3, + CARD_DIVA, CARD_I_NONE, BUS_ISA, CHIP_DSP, + 1, 2, 0, 8, 0 +}, +{ /* 2 */ + "Diva/PCM", 0x0000, 0x0100, + IDI_ADAPTER_DIVA, FAMILY_DIVA, DI_V1x0 | DI_FAX3, + CARD_DIVA, CARD_I_NONE, BUS_PCM, CHIP_DSP, + 1, 2, 0, 8, 0 +}, +{ /* 3 */ + "Diva PRO ISA", 0x0031, 0x0100, + IDI_ADAPTER_DIVA, FAMILY_DIVA, DI_V1x0 | DI_FAX3 | DI_MODEM | DI_CODEC, + CARD_PRO, CARD_I_NONE, BUS_ISA, CHIP_DSP, + 1, 2, 0, 8, 0 +}, +{ /* 4 */ + "Diva PRO PC-Card", 0x0000, 0x0100, + IDI_ADAPTER_DIVA, FAMILY_DIVA, DI_V1x0 | DI_FAX3 | DI_MODEM, + CARD_PRO, CARD_I_NONE, BUS_PCM, CHIP_DSP, + 1, 2, 0, 8, 0 +}, +{ /* 5 */ + "Diva PICCOLA ISA", 0x0051, 0x0100, + IDI_ADAPTER_DIVA, FAMILY_DIVA, DI_V120 | SOFT_DSP_ADD_FEATURES, + CARD_PICO, CARD_I_NONE, BUS_ISA, CHIP_HSCX, + 1, 2, 0, 8, 0 +}, +{ /* 6 */ + "Diva PICCOLA PCM", 0x0000, 0x0100, + IDI_ADAPTER_DIVA, FAMILY_DIVA, DI_V120 | SOFT_DSP_ADD_FEATURES, + CARD_PICO, CARD_I_NONE, BUS_PCM, CHIP_HSCX, + 1, 2, 0, 8, 0 +}, +{ /* 7 */ + "Diva PRO 2.0 S/T PCI", 0xe001, 0x0200, + IDI_ADAPTER_DIVA, FAMILY_DIVA, DI_V1x0 | DI_FAX3 | DI_MODEM | DI_POTS, + CARD_PRO, CARD_I_NONE, BUS_PCI, CHIP_DSP, + 1, 2, 0, 8, 0 +}, +{ /* 8 */ + "Diva 2.0 S/T PCI", 0xe002, 0x0200, + IDI_ADAPTER_DIVA, FAMILY_DIVA, DI_V120 | DI_POTS | SOFT_DSP_ADD_FEATURES, + CARD_PICO, CARD_I_NONE, BUS_PCI, CHIP_HSCX, + 1, 2, 0, 8, 0 +}, +{ /* 9 */ + "QUADRO ISA", 0x0000, 0x0100, + IDI_ADAPTER_S, FAMILY_S, DI_NULL, + CARD_QUAD, CARD_I_QUAD, BUS_ISA, CHIP_NONE, + 4, 2, 16, 0, 0x800 +}, +{ /* 10 */ + "S ISA", 0x0000, 0x0100, + IDI_ADAPTER_S, FAMILY_S, DI_CODEC, + CARD_S, CARD_I_S, BUS_ISA, CHIP_NONE, + 1, 1, 16, 0, 0x800 +}, +{ /* 11 */ + "S MCA", 0x6a93, 0x0100, + IDI_ADAPTER_S, FAMILY_S, DI_CODEC, + CARD_S, CARD_I_S, BUS_MCA, CHIP_NONE, + 1, 1, 16, 16, 0x400 +}, +{ /* 12 */ + "SX ISA", 0x0000, 0x0100, + IDI_ADAPTER_S, FAMILY_S, DI_NULL, + CARD_SX, CARD_I_SX, BUS_ISA, CHIP_NONE, + 1, 2, 16, 0, 0x800 +}, +{ /* 13 */ + "SX MCA", 0x6a93, 0x0100, + IDI_ADAPTER_S, FAMILY_S, DI_NULL, + CARD_SX, CARD_I_SX, BUS_MCA, CHIP_NONE, + 1, 2, 16, 16, 0x400 +}, +{ /* 14 */ + "SXN ISA", 0x0000, 0x0100, + IDI_ADAPTER_S, FAMILY_S, DI_NULL, + CARD_SXN, CARD_I_SCOM, BUS_ISA, CHIP_NONE, + 1, 2, 16, 0, 0x800 +}, +{ /* 15 */ + "SXN MCA", 0x6a93, 0x0100, + IDI_ADAPTER_S, FAMILY_S, DI_NULL, + CARD_SXN, CARD_I_SCOM, BUS_MCA, CHIP_NONE, + 1, 2, 16, 16, 0x400 +}, +{ /* 16 */ + "SCOM ISA", 0x0000, 0x0100, + IDI_ADAPTER_S, FAMILY_S, DI_CODEC, + CARD_SCOM, CARD_I_SCOM, BUS_ISA, CHIP_NONE, + 1, 2, 16, 0, 0x800 +}, +{ /* 17 */ + "SCOM MCA", 0x6a93, 0x0100, + IDI_ADAPTER_S, FAMILY_S, DI_CODEC, + CARD_SCOM, CARD_I_SCOM, BUS_MCA, CHIP_NONE, + 1, 2, 16, 16, 0x400 +}, +{ /* 18 */ + "S2M ISA", 0x0000, 0x0100, + IDI_ADAPTER_PR, FAMILY_S, DI_NULL, + CARD_PR, CARD_I_PR, BUS_ISA, CHIP_NONE, + 1, 30, 256, 0, 0x4000 +}, +{ /* 19 */ + "S2M MCA", 0x6abb, 0x0100, + IDI_ADAPTER_PR, FAMILY_S, DI_NULL, + CARD_PR, CARD_I_PR, BUS_MCA, CHIP_NONE, + 1, 30, 256, 16, 0x4000 +}, +{ /* 20 */ + "Diva Server BRI-2M ISA", 0x0041, 0x0100, + IDI_ADAPTER_MAESTRA,FAMILY_MAESTRA, DI_V1x0 | DI_FAX3 | DI_MODEM, + CARD_MAE, CARD_I_NONE, BUS_ISA, CHIP_DSP, + 1, 2, 16, 8, 0 +}, +{ /* 21 */ + "Diva Server BRI-2M PCI", 0xE010, 0x0100, + IDI_ADAPTER_MAESTRA,FAMILY_MAESTRA, DI_V1x0 | DI_FAX3 | DI_MODEM, + CARD_MAE, CARD_I_NONE, BUS_PCI, CHIP_DSP, + 1, 2, 16, 8, 0 +}, +{ /* 22 */ + "Diva Server 4BRI-8M PCI", 0xE012, 0x0100, + IDI_ADAPTER_MAESTRA,FAMILY_MAESTRA, DI_V1x0 | DI_FAX3 | DI_MODEM, + CARD_MAEQ, CARD_I_NONE, BUS_PCI, CHIP_DSP, + 4, 2, 16, 8, 0 +}, +{ /* 23 */ + "Diva Server PRI-30M PCI", 0xE014, 0x0100, + IDI_ADAPTER_MAESTRA,FAMILY_MAESTRA, DI_V1x0 | DI_FAX3 | DI_MODEM, + CARD_MAEP, CARD_I_NONE, BUS_PCI, CHIP_DSP, + 1, 30, 256, 8, 0 +}, +{ /* 24 */ + "Diva Server PRI-2M PCI", 0xe014, 0x0100, + IDI_ADAPTER_MAESTRA,FAMILY_MAESTRA, DI_V1x0 | DI_FAX3 | DI_MODEM, + CARD_MAEP, CARD_I_NONE, BUS_PCI, CHIP_DSP, + 1, 30, 256, 8, 0 +}, +{ /* 25 */ + "Diva Server PRI-9M PCI", 0x0000, 0x0100, + IDI_ADAPTER_MAESTRA,FAMILY_MAESTRA, DI_V1x0 | DI_FAX3 | DI_MODEM, + CARD_MAEP, CARD_I_NONE, BUS_PCI, CHIP_DSP, + 1, 30, 256, 8, 0 +}, +{ /* 26 */ + "Diva 2.0 S/T ISA", 0x0071, 0x0200, + IDI_ADAPTER_DIVA, FAMILY_DIVA, DI_V120 | DI_POTS | SOFT_DSP_ADD_FEATURES, + CARD_PICO, CARD_I_NONE, BUS_ISA, CHIP_HSCX, + 1, 2, 0, 8, 0 +}, +{ /* 27 */ + "Diva 2.0 U ISA", 0x0091, 0x0200, + IDI_ADAPTER_DIVA, FAMILY_DIVA, DI_V120 | DI_POTS | SOFT_DSP_ADD_FEATURES, + CARD_PICO, CARD_I_NONE, BUS_ISA, CHIP_HSCX, + 1, 2, 0, 8, 0 +}, +{ /* 28 */ + "Diva 2.0 U PCI", 0xe004, 0x0200, + IDI_ADAPTER_DIVA, FAMILY_DIVA, DI_V120 | DI_POTS | SOFT_DSP_ADD_FEATURES, + CARD_PICO, CARD_I_NONE, BUS_PCI, CHIP_HSCX, + 1, 2, 0, 8, 0 +}, +{ /* 29 */ + "Diva PRO 2.0 S/T ISA", 0x0061, 0x0200, + IDI_ADAPTER_DIVA, FAMILY_DIVA, DI_V1x0 | DI_FAX3 | DI_MODEM | DI_POTS, + CARD_PRO, CARD_I_NONE, BUS_ISA, CHIP_DSP, + 1, 2, 0, 8, 0 +}, +{ /* 30 */ + "Diva PRO 2.0 U ISA", 0x0081, 0x0200, + IDI_ADAPTER_DIVA, FAMILY_DIVA, DI_V1x0 | DI_FAX3 | DI_MODEM | DI_POTS, + CARD_PRO, CARD_I_NONE, BUS_ISA, CHIP_DSP, + 1, 2, 0, 8, 0 +}, +{ /* 31 */ + "Diva PRO 2.0 U PCI", 0xe003, 0x0200, + IDI_ADAPTER_DIVA, FAMILY_DIVA, DI_V1x0 | DI_FAX3 | DI_MODEM | DI_POTS, + CARD_PRO, CARD_I_NONE, BUS_PCI, CHIP_DSP, + 1, 2, 0, 8, 0 +}, +{ /* 32 */ + "Diva MOBILE", 0x0000, 0x0100, + IDI_ADAPTER_DIVA, FAMILY_DIVA, DI_V120 | SOFT_DSP_ADD_FEATURES, + CARD_PICO, CARD_I_NONE, BUS_PCM, CHIP_HSCX, + 1, 2, 0, 8, 0 +}, +{ /* 33 */ + "TDK DFI3600", 0x0000, 0x0100, + IDI_ADAPTER_DIVA, FAMILY_DIVA, DI_V120 | SOFT_DSP_ADD_FEATURES, + CARD_PICO, CARD_I_NONE, BUS_PCM, CHIP_HSCX, + 1, 2, 0, 8, 0 +}, +{ /* 34 (OEM version of 4 - "Diva PRO PC-Card") */ + "New Media ISDN", 0x0000, 0x0100, + IDI_ADAPTER_DIVA, FAMILY_DIVA, DI_V1x0 | DI_FAX3 | DI_MODEM, + CARD_PRO, CARD_I_NONE, BUS_PCM, CHIP_DSP, + 1, 2, 0, 8, 0 +}, +{ /* 35 (OEM version of 7 - "Diva PRO 2.0 S/T PCI") */ + "BT ExLane PCI", 0xe101, 0x0200, + IDI_ADAPTER_DIVA, FAMILY_DIVA, DI_V1x0 | DI_FAX3 | DI_MODEM | DI_POTS, + CARD_PRO, CARD_I_NONE, BUS_PCI, CHIP_DSP, + 1, 2, 0, 8, 0 +}, +{ /* 36 (OEM version of 29 - "Diva PRO 2.0 S/T ISA") */ + "BT ExLane ISA", 0x1061, 0x0200, + IDI_ADAPTER_DIVA, FAMILY_DIVA, DI_V1x0 | DI_FAX3 | DI_MODEM | DI_POTS, + CARD_PRO, CARD_I_NONE, BUS_ISA, CHIP_DSP, + 1, 2, 0, 8, 0 +}, +{ /* 37 */ + "Diva 2.01 S/T ISA", 0x00A1, 0x0300, + IDI_ADAPTER_DIVA, FAMILY_DIVA, DI_V120 | SOFT_DSP_ADD_FEATURES, + CARD_DIVALOW, CARD_I_NONE, BUS_ISA, CHIP_IPAC, + 1, 2, 0, 8, 0 +}, +{ /* 38 */ + "Diva 2.01 U ISA", 0x00B1, 0x0300, + IDI_ADAPTER_DIVA, FAMILY_DIVA, DI_V120 | SOFT_DSP_ADD_FEATURES, + CARD_DIVALOW, CARD_I_NONE, BUS_ISA, CHIP_IPAC, + 1, 2, 0, 8, 0 +}, +{ /* 39 */ + "Diva 2.01 S/T PCI", 0xe005, 0x0300, + IDI_ADAPTER_DIVA, FAMILY_DIVA, DI_V120 | SOFT_DSP_ADD_FEATURES, + CARD_DIVALOW, CARD_I_NONE, BUS_PCI, CHIP_IPAC, + 1, 2, 0, 8, 0 +}, +{ /* 40 no ID yet */ + "Diva 2.01 U PCI", 0x0000, 0x0300, + IDI_ADAPTER_DIVA, FAMILY_DIVA, DI_V120 | SOFT_DSP_ADD_FEATURES, + CARD_DIVALOW, CARD_I_NONE, BUS_PCI, CHIP_IPAC, + 1, 2, 0, 8, 0 +}, +{ /* 41 */ + "Diva MOBILE V.90", 0x0000, 0x0100, + IDI_ADAPTER_DIVA, FAMILY_DIVA, DI_V120 | SOFT_DSP_ADD_FEATURES, + CARD_PICO, CARD_I_NONE, BUS_PCM, CHIP_HSCX, + 1, 2, 0, 8, 0 +}, +{ /* 42 */ + "TDK DFI3600 V.90", 0x0000, 0x0100, + IDI_ADAPTER_DIVA, FAMILY_DIVA, DI_V120 | SOFT_DSP_ADD_FEATURES, + CARD_PICO, CARD_I_NONE, BUS_PCM, CHIP_HSCX, + 1, 2, 0, 8, 0 +}, +{ /* 43 */ + "Diva Server PRI-23M PCI", 0xe014, 0x0100, + IDI_ADAPTER_MAESTRA,FAMILY_MAESTRA, DI_V1x0 | DI_FAX3 | DI_MODEM, + CARD_MAEP, CARD_I_NONE, BUS_PCI, CHIP_DSP, + 1, 30, 256, 8, 0 +}, +{ /* 44 */ + "Diva 2.01 S/T USB", 0x1000, 0x0300, + IDI_ADAPTER_DIVA ,FAMILY_DIVA, DI_V120 | SOFT_DSP_ADD_FEATURES, + CARD_DIVALOW, CARD_I_NONE, BUS_USB, CHIP_IPAC, + 1, 2, 0, 8, 0 +}, +{ /* 45 */ + "Diva CT S/T PCI", 0xe006, 0x0300, + IDI_ADAPTER_DIVA ,FAMILY_DIVA, DI_V1x0 | DI_FAX3 | DI_MODEM | DI_CODEC, + CARD_CT, CARD_I_NONE, BUS_PCI, CHIP_DSP, + 1, 2, 0, 0, 0 +}, +{ /* 46 */ + "Diva CT U PCI", 0xe007, 0x0300, + IDI_ADAPTER_DIVA ,FAMILY_DIVA, DI_V1x0 | DI_FAX3 | DI_MODEM | DI_CODEC, + CARD_CT, CARD_I_NONE, BUS_PCI, CHIP_DSP, + 1, 2, 0, 0, 0 +}, +{ /* 47 */ + "Diva CT Lite S/T PCI", 0xe008, 0x0300, + IDI_ADAPTER_DIVA ,FAMILY_DIVA, DI_V1x0 | DI_FAX3 | DI_MODEM | DI_CODEC, + CARD_CT, CARD_I_NONE, BUS_PCI, CHIP_DSP, + 1, 2, 0, 0, 0 +}, +{ /* 48 */ + "Diva CT Lite U PCI", 0xe009, 0x0300, + IDI_ADAPTER_DIVA ,FAMILY_DIVA, DI_V1x0 | DI_FAX3 | DI_MODEM | DI_CODEC, + CARD_CT, CARD_I_NONE, BUS_PCI, CHIP_DSP, + 1, 2, 0, 0, 0 +}, +{ /* 49 */ + "Diva ISDN+V.90 PC Card", 0x8D8C, 0x0100, + IDI_ADAPTER_DIVA, FAMILY_DIVA, DI_V1x0 | DI_FAX3 | DI_MODEM | DI_CODEC, + CARD_DIVALOW, CARD_I_NONE, BUS_PCM, CHIP_IPAC, + 1, 2, 0, 8, 0 +}, +{ /* 50 */ + "Diva ISDN+V.90 PCI", 0xe00A, 0x0100, + IDI_ADAPTER_DIVA, FAMILY_DIVA, DI_V120 | SOFT_DSP_ADD_FEATURES, + CARD_DIVALOW, CARD_I_NONE, BUS_PCI, CHIP_IPAC, + 1, 2, 0, 8, 0 +}, +{ /* 51 (DivaTA) no ID */ + "Diva TA", 0x0000, 0x0300, + IDI_ADAPTER_DIVA, FAMILY_DIVA, DI_V110 | DI_FAX3 | SOFT_DSP_ADD_FEATURES, + CARD_DIVATA, CARD_I_NONE, BUS_COM, CHIP_EXTERN, + 1, 1, 0, 8, 0 +}, +{ /* 52 (Diva Server 4BRI-8M PCI adapter enabled for Voice) */ + "Diva Server Voice 4BRI-8M PCI", 0xE016, 0x0100, + IDI_ADAPTER_MAESTRA,FAMILY_MAESTRA, DI_V1x0 | DI_FAX3 | DI_MODEM | DI_VOICE_OVER_IP, + CARD_MAEQ, CARD_I_NONE, BUS_PCI, CHIP_DSP, + 4, 2, 16, 8, 0 +}, +{ /* 53 (Diva Server 4BRI 2.0 adapter) */ + "Diva Server 4BRI-8M 2.0 PCI", 0xE013, 0x0200, + IDI_ADAPTER_MAESTRA,FAMILY_MAESTRA, DI_V1x0 | DI_FAX3 | DI_MODEM, + CARD_MAEQ, CARD_I_NONE, BUS_PCI, CHIP_DSP, + 4, 2, 16, 8, 0 +}, +{ /* 54 (Diva Server PRI 2.0 adapter) */ + "Diva Server PRI 2.0 PCI", 0xE015, 0x0200, + IDI_ADAPTER_MAESTRA,FAMILY_MAESTRA, DI_V1x0 | DI_FAX3 | DI_MODEM, + CARD_MAEP, CARD_I_NONE, BUS_PCI, CHIP_DSP, + 1, 30, 256, 8, 0 +}, +{ /* 55 (Diva Server 4BRI-8M 2.0 PCI adapter enabled for Voice) */ + "Diva Server Voice 4BRI-8M 2.0 PCI", 0xE017, 0x0200, + IDI_ADAPTER_MAESTRA,FAMILY_MAESTRA, DI_V1x0 | DI_FAX3 | DI_MODEM | DI_VOICE_OVER_IP, + CARD_MAEQ, CARD_I_NONE, BUS_PCI, CHIP_DSP, + 4, 2, 16, 8, 0 +}, +{ /* 56 (Diva Server PRI 2.0 PCI adapter enabled for Voice) */ + "Diva Server Voice PRI 2.0 PCI", 0xE019, 0x0200, + IDI_ADAPTER_MAESTRA,FAMILY_MAESTRA, DI_V1x0 | DI_FAX3 | DI_MODEM | DI_VOICE_OVER_IP, + CARD_MAEP, CARD_I_NONE, BUS_PCI, CHIP_DSP, + 1, 30, 256, 8, 0 +}, +{ + /* 57 (DivaLan ) no ID */ + "Diva LAN", 0x0000, 0x0300, + IDI_ADAPTER_DIVA, FAMILY_DIVA, DI_V110 | DI_FAX3 | SOFT_DSP_ADD_FEATURES, + CARD_DIVALAN, CARD_I_NONE, BUS_LAN, CHIP_EXTERN, + 1, 1, 0, 8, 0 +}, +{ /* 58 */ + "Diva 2.02 PCI S/T", 0xE00B, 0x0300, + IDI_ADAPTER_DIVA, FAMILY_DIVA, DI_V120 | SOFT_DSP_ADD_FEATURES | DI_SOFT_V110, + CARD_DIVALOW, CARD_I_NONE, BUS_PCI, CHIP_IPACX, + 1, 2, 0, 8, 0 +}, +{ /* 59 */ + "Diva 2.02 PCI U", 0xE00C, 0x0300, + IDI_ADAPTER_DIVA, FAMILY_DIVA, DI_V120 | SOFT_DSP_ADD_FEATURES, + CARD_DIVALOW, CARD_I_NONE, BUS_PCI, CHIP_IPACX, + 1, 2, 0, 8, 0 +}, +{ /* 60 */ + "Diva Server BRI-2M 2.0 PCI", 0xE018, 0x0200, + IDI_ADAPTER_MAESTRA,FAMILY_MAESTRA, DI_V1x0 | DI_FAX3 | DI_MODEM, + CARD_MAE2, CARD_I_NONE, BUS_PCI, CHIP_DSP, + 1, 2, 16, 8, 0 +}, +{ /* 61 (the previous name was Diva Server BRI-2F 2.0 PCI) */ + "Diva Server 2FX", 0xE01A, 0x0200, + IDI_ADAPTER_MAESTRA,FAMILY_MAESTRA, DI_V1x0 | DI_FAX3 | DI_MODEM | DI_SOFT_V110, + CARD_MAE2, CARD_I_NONE, BUS_PCI, CHIP_IPACX, + 1, 2, 16, 8, 0 +}, +{ /* 62 */ + " Diva ISDN USB 2.0", 0x1003, 0x0300, + IDI_ADAPTER_DIVA ,FAMILY_DIVA, DI_V120 | SOFT_DSP_ADD_FEATURES, + CARD_DIVALOW, CARD_I_NONE, BUS_USB, CHIP_IPACX, + 1, 2, 0, 8, 0 +}, +{ /* 63 (Diva Server BRI-2M 2.0 PCI adapter enabled for Voice) */ + "Diva Server Voice BRI-2M 2.0 PCI", 0xE01B, 0x0200, + IDI_ADAPTER_MAESTRA,FAMILY_MAESTRA, DI_V1x0 | DI_FAX3 | DI_MODEM | DI_VOICE_OVER_IP, + CARD_MAE2, CARD_I_NONE, BUS_PCI, CHIP_DSP, + 1, 2, 16, 8, 0 +}, +{ /* 64 */ + "Diva Pro 3.0 PCI", 0xe00d, 0x0300, + IDI_ADAPTER_DIVA ,FAMILY_DIVA, DI_V1x0 | DI_FAX3 | DI_MODEM, + CARD_PRO, CARD_I_NONE, BUS_PCI, CHIP_DSP, + 1, 2, 0, 0, 0 +}, +{ /* 65 */ + "Diva ISDN + CT 2.0", 0xE00E, 0x0300, + IDI_ADAPTER_DIVA ,FAMILY_DIVA, DI_V1x0 | DI_FAX3 | DI_MODEM | DI_CODEC, + CARD_CT, CARD_I_NONE, BUS_PCI, CHIP_DSP, + 1, 2, 0, 0, 0 +}, +{ /* 66 */ + "Diva Mobile V.90 PC Card", 0x8331, 0x0100, + IDI_ADAPTER_DIVA, FAMILY_DIVA, DI_V120 | SOFT_DSP_ADD_FEATURES, + CARD_PICO, CARD_I_NONE, BUS_PCM, CHIP_IPACX, + 1, 2, 0, 8, 0 +}, +{ /* 67 */ + "Diva ISDN PC Card", 0x8311, 0x0100, + IDI_ADAPTER_DIVA, FAMILY_DIVA, DI_V120 | SOFT_DSP_ADD_FEATURES, + CARD_PICO, CARD_I_NONE, BUS_PCM, CHIP_IPACX, + 1, 2, 0, 8, 0 +}, +{ /* 68 */ + "Diva ISDN PC Card", 0x0000, 0x0100, + IDI_ADAPTER_DIVA, FAMILY_DIVA, DI_V120 | SOFT_DSP_ADD_FEATURES, + CARD_PRO, CARD_I_NONE, BUS_PCM, CHIP_DSP, + 1, 2, 0, 8, 0 +}, +} ; +#if CARDTYPE_H_WANT_RESOURCE_DATA +/*--- CardResource [Index=CARDTYPE_....] ---------------------------(GEI)-*/ +CARD_RESOURCE CardResource [ ] = { +/* Interrupts IO-Address Mem-Address */ +/* 0*/ { 3,4,9,0,0,0,0,0,0,0, 0x200,0x20,16, 0x0,0x0,0 }, // DIVA MCA +/* 1*/ { 3,4,9,10,11,12,0,0,0,0, 0x200,0x20,16, 0x0,0x0,0 }, // DIVA ISA +/* 2*/ { 3,4,5,7,9,10,11,12,14,15, 0x0,0x8,8192, 0x0,0x0,0 }, // DIVA PCMCIA +/* 3*/ { 3,5,7,9,10,11,12,14,15,0, 0x200,0x20,16, 0x0,0x0,0 }, // DIVA PRO ISA +/* 4*/ { 3,4,5,7,9,10,11,12,14,15, 0x0,0x8,8192, 0x0,0x0,0 }, // DIVA PRO PCMCIA +/* 5*/ { 3,5,7,9,10,11,12,14,15,0, 0x200,0x20,16, 0x0,0x0,0 }, // DIVA PICCOLA ISA +/* 6*/ { 0,0,0,0,0,0,0,0,0,0, 0x0,0x0,0, 0x0,0x0,0 }, // DIVA PICCOLA PCMCIA +/* 7*/ { 3,4,5,7,9,10,11,12,14,15, 0x0,0x8,8192, 0x0,0x0,0 }, // DIVA PRO 2.0 PCI +/* 8*/ { 3,4,5,7,9,10,11,12,14,15, 0x0,0x8,8192, 0x0,0x0,0 }, // DIVA 2.0 PCI +/* 9*/ { 3,4,5,7,9,10,11,12,0,0, 0x0,0x0,0, 0x80000,0x2000,64 }, // QUADRO ISA +/*10*/ { 3,4,9,10,11,12,0,0,0,0, 0x0,0x0,0, 0xc0000,0x2000,16 }, // S ISA +/*11*/ { 3,4,9,0,0,0,0,0,0,0, 0xc00,0x10,16, 0xc0000,0x2000,16 }, // S MCA +/*12*/ { 3,4,9,10,11,12,0,0,0,0, 0x0,0x0,0, 0xc0000,0x2000,16 }, // SX ISA +/*13*/ { 3,4,9,0,0,0,0,0,0,0, 0xc00,0x10,16, 0xc0000,0x2000,16 }, // SX MCA +/*14*/ { 3,4,5,7,9,10,11,12,0,0, 0x0,0x0,0, 0x80000,0x0800,256 }, // SXN ISA +/*15*/ { 3,4,9,0,0,0,0,0,0,0, 0xc00,0x10,16, 0xc0000,0x2000,16 }, // SXN MCA +/*16*/ { 3,4,5,7,9,10,11,12,0,0, 0x0,0x0,0, 0x80000,0x0800,256 }, // SCOM ISA +/*17*/ { 3,4,9,0,0,0,0,0,0,0, 0xc00,0x10,16, 0xc0000,0x2000,16 }, // SCOM MCA +/*18*/ { 3,4,5,7,9,10,11,12,0,0, 0x0,0x0,0, 0xc0000,0x4000,16 }, // S2M ISA +/*19*/ { 3,4,9,0,0,0,0,0,0,0, 0xc00,0x10,16, 0xc0000,0x4000,16 }, // S2M MCA +/*20*/ { 3,5,7,9,10,11,12,14,15,0, 0x200,0x20,16, 0x0,0x0,0 }, // MAESTRA ISA +/*21*/ { 3,4,5,7,9,10,11,12,14,15, 0x0,0x8,8192, 0x0,0x0,0 }, // MAESTRA PCI +/*22*/ { 3,5,7,9,10,11,12,14,15,0, 0x200,0x20,16, 0x0,0x0,0 }, // MAESTRA QUADRO ISA +/*23*/ { 3,4,5,7,9,10,11,12,14,15, 0x0,0x20,2048, 0x0,0x0,0 }, // MAESTRA QUADRO PCI +/*24*/ { 3,5,7,9,10,11,12,14,15,0, 0x200,0x20,16, 0x0,0x0,0 }, // MAESTRA PRIMARY ISA +/*25*/ { 3,4,5,7,9,10,11,12,14,15, 0x0,0x8,8192, 0x0,0x0,0 }, // MAESTRA PRIMARY PCI +/*26*/ { 3,5,7,9,10,11,12,14,15,0, 0x200,0x20,16, 0x0,0x0,0 }, // DIVA 2.0 ISA +/*27*/ { 3,5,7,9,10,11,12,14,15,0, 0x200,0x20,16, 0x0,0x0,0 }, // DIVA 2.0 /U ISA +/*28*/ { 3,4,5,7,9,10,11,12,14,15, 0x0,0x8,8192, 0x0,0x0,0 }, // DIVA 2.0 /U PCI +/*29*/ { 3,5,7,9,10,11,12,14,15,0, 0x200,0x20,16, 0x0,0x0,0 }, // DIVA PRO 2.0 ISA +/*30*/ { 3,5,7,9,10,11,12,14,15,0, 0x200,0x20,16, 0x0,0x0,0 }, // DIVA PRO 2.0 /U ISA +/*31*/ { 3,4,5,7,9,10,11,12,14,15, 0x0,0x8,8192, 0x0,0x0,0 }, // DIVA PRO 2.0 /U PCI +/*32*/ { 0,0,0,0,0,0,0,0,0,0, 0x0,0x0,0, 0x0,0x0,0 }, // DIVA MOBILE +/*33*/ { 0,0,0,0,0,0,0,0,0,0, 0x0,0x0,0, 0x0,0x0,0 }, // TDK DFI3600 (same as DIVA MOBILE [32]) +/*34*/ { 3,4,5,7,9,10,11,12,14,15, 0x0,0x8,8192, 0x0,0x0,0 }, // New Media ISDN (same as DIVA PRO PCMCIA [4]) +/*35*/ { 3,4,5,7,9,10,11,12,14,15, 0x0,0x8,8192, 0x0,0x0,0 }, // BT ExLane PCI (same as DIVA PRO 2.0 PCI [7]) +/*36*/ { 3,5,7,9,10,11,12,14,15,0, 0x200,0x20,16, 0x0,0x0,0 }, // BT ExLane ISA (same as DIVA PRO 2.0 ISA [29]) +/*37*/ { 3,5,7,9,10,11,12,14,15,0, 0x200,0x20,16, 0x0,0x0,0 }, // DIVA 2.01 S/T ISA +/*38*/ { 3,5,7,9,10,11,12,14,15,0, 0x200,0x20,16, 0x0,0x0,0 }, // DIVA 2.01 U ISA +/*39*/ { 3,4,5,7,9,10,11,12,14,15, 0x0,0x8,8192, 0x0,0x0,0 }, // DIVA 2.01 S/T PCI +/*40*/ { 3,4,5,7,9,10,11,12,14,15, 0x0,0x8,8192, 0x0,0x0,0 }, // DIVA 2.01 U PCI +/*41*/ { 0,0,0,0,0,0,0,0,0,0, 0x0,0x0,0, 0x0,0x0,0 }, // DIVA MOBILE V.90 +/*42*/ { 0,0,0,0,0,0,0,0,0,0, 0x0,0x0,0, 0x0,0x0,0 }, // TDK DFI3600 V.90 (same as DIVA MOBILE V.90 [39]) +/*43*/ { 3,4,5,7,9,10,11,12,14,15, 0x0,0x20,2048, 0x0,0x0,0 }, // DIVA Server PRI-23M PCI +/*44*/ { 0,0,0,0,0,0,0,0,0,0, 0x0,0x0,0, 0x0,0x0,0 }, // DIVA 2.01 S/T USB +/*45*/ { 0,0,0,0,0,0,0,0,0,0, 0x0,0x0,0, 0x0,0x0,0 }, // DIVA CT S/T PCI +/*46*/ { 0,0,0,0,0,0,0,0,0,0, 0x0,0x0,0, 0x0,0x0,0 }, // DIVA CT U PCI +/*47*/ { 0,0,0,0,0,0,0,0,0,0, 0x0,0x0,0, 0x0,0x0,0 }, // DIVA CT Lite S/T PCI +/*48*/ { 0,0,0,0,0,0,0,0,0,0, 0x0,0x0,0, 0x0,0x0,0 }, // DIVA CT Lite U PCI +/*49*/ { 0,0,0,0,0,0,0,0,0,0, 0x0,0x0,0, 0x0,0x0,0 }, // DIVA ISDN+V.90 PC Card +/*50*/ { 3,4,5,7,9,10,11,12,14,15, 0x0,0x8,8192, 0x0,0x0,0 }, // DIVA ISDN+V.90 PCI +/*51*/ { 0,0,0,0,0,0,0,0,0,0, 0x0,0x0,0, 0x0,0x0,0 }, // DIVA TA +/*52*/ { 3,4,5,7,9,10,11,12,14,15, 0x0,0x20,2048, 0x0,0x0,0 }, // MAESTRA VOICE QUADRO PCI +/*53*/ { 3,4,5,7,9,10,11,12,14,15, 0x0,0x20,2048, 0x0,0x0,0 }, // MAESTRA VOICE QUADRO PCI +/*54*/ { 3,4,5,7,9,10,11,12,14,15, 0x0,0x8,8192, 0x0,0x0,0 }, // MAESTRA VOICE PRIMARY PCI +/*55*/ { 3,4,5,7,9,10,11,12,14,15, 0x0,0x20,2048, 0x0,0x0,0 }, // MAESTRA VOICE QUADRO PCI +/*56*/ { 3,4,5,7,9,10,11,12,14,15, 0x0,0x8,8192, 0x0,0x0,0 }, // MAESTRA VOICE PRIMARY PCI +/*57*/ { 0,0,0,0,0,0,0,0,0,0, 0x0,0x0,0, 0x0,0x0,0 }, // DIVA LAN +/*58*/ { 3,4,5,7,9,10,11,12,14,15, 0x0,0x8,8192, 0x0,0x0,0 }, // DIVA 2.02 S/T PCI +/*59*/ { 3,4,5,7,9,10,11,12,14,15, 0x0,0x8,8192, 0x0,0x0,0 }, // DIVA 2.02 U PCI +/*60*/ { 0,0,0,0,0,0,0,0,0,0, 0x0,0x0,0, 0x0,0x0,0 }, // Diva Server BRI-2M 2.0 PCI +/*61*/ { 0,0,0,0,0,0,0,0,0,0, 0x0,0x0,0, 0x0,0x0,0 }, // Diva Server BRI-2F PCI +/*62*/ { 0,0,0,0,0,0,0,0,0,0, 0x0,0x0,0, 0x0,0x0,0 }, // DIVA 2.01 S/T USB +/*63*/ { 0,0,0,0,0,0,0,0,0,0, 0x0,0x0,0, 0x0,0x0,0 }, // Diva Server Voice BRI-2M 2.0 PCI +/*64*/ { 3,4,5,7,9,10,11,12,14,15, 0x0,0x8,8192, 0x0,0x0,0 }, // DIVA 3.0 PCI +/*65*/ { 0,0,0,0,0,0,0,0,0,0, 0x0,0x0,0, 0x0,0x0,0 }, // DIVA CT S/T PCI V2.0 +/*66*/ { 0,0,0,0,0,0,0,0,0,0, 0x0,0x0,0, 0x0,0x0,0 }, // DIVA Mobile V.90 PC Card +/*67*/ { 0,0,0,0,0,0,0,0,0,0, 0x0,0x0,0, 0x0,0x0,0 }, // DIVA ISDN PC Card +/*68*/ { 3,4,5,7,9,10,11,12,14,15, 0x0,0x8,8192, 0x0,0x0,0 }, // DIVA ISDN PC Card +}; +#endif /*CARDTYPE_H_WANT_RESOURCE_DATA*/ +#else /*!CARDTYPE_H_WANT_DATA*/ +extern CARD_PROPERTIES CardProperties [] ; +extern CARD_RESOURCE CardResource [] ; +#endif /*CARDTYPE_H_WANT_DATA*/ +/* + * all existing download files + */ +#define CARD_DSP_CNT 5 +#define CARD_PROT_CNT 9 +#define CARD_FT_UNKNOWN 0 +#define CARD_FT_B 1 +#define CARD_FT_D 2 +#define CARD_FT_S 3 +#define CARD_FT_M 4 +#define CARD_FT_NEW_DSP_COMBIFILE 5 /* File format of new DSP code (the DSP code powered by Telindus) */ +#define CARD_FILE_NONE 0 +#define CARD_B_S 1 +#define CARD_B_P 2 +#define CARD_D_K1 3 +#define CARD_D_K2 4 +#define CARD_D_H 5 +#define CARD_D_V 6 +#define CARD_D_M 7 +#define CARD_D_F 8 +#define CARD_P_S_E 9 +#define CARD_P_S_1 10 +#define CARD_P_S_B 11 +#define CARD_P_S_F 12 +#define CARD_P_S_A 13 +#define CARD_P_S_N 14 +#define CARD_P_S_5 15 +#define CARD_P_S_J 16 +#define CARD_P_SX_E 17 +#define CARD_P_SX_1 18 +#define CARD_P_SX_B 19 +#define CARD_P_SX_F 20 +#define CARD_P_SX_A 21 +#define CARD_P_SX_N 22 +#define CARD_P_SX_5 23 +#define CARD_P_SX_J 24 +#define CARD_P_SY_E 25 +#define CARD_P_SY_1 26 +#define CARD_P_SY_B 27 +#define CARD_P_SY_F 28 +#define CARD_P_SY_A 29 +#define CARD_P_SY_N 30 +#define CARD_P_SY_5 31 +#define CARD_P_SY_J 32 +#define CARD_P_SQ_E 33 +#define CARD_P_SQ_1 34 +#define CARD_P_SQ_B 35 +#define CARD_P_SQ_F 36 +#define CARD_P_SQ_A 37 +#define CARD_P_SQ_N 38 +#define CARD_P_SQ_5 39 +#define CARD_P_SQ_J 40 +#define CARD_P_P_E 41 +#define CARD_P_P_1 42 +#define CARD_P_P_B 43 +#define CARD_P_P_F 44 +#define CARD_P_P_A 45 +#define CARD_P_P_N 46 +#define CARD_P_P_5 47 +#define CARD_P_P_J 48 +#define CARD_P_M_E 49 +#define CARD_P_M_1 50 +#define CARD_P_M_B 51 +#define CARD_P_M_F 52 +#define CARD_P_M_A 53 +#define CARD_P_M_N 54 +#define CARD_P_M_5 55 +#define CARD_P_M_J 56 +#define CARD_P_S_S 57 +#define CARD_P_SX_S 58 +#define CARD_P_SY_S 59 +#define CARD_P_SQ_S 60 +#define CARD_P_P_S 61 +#define CARD_P_M_S 62 +#define CARD_D_NEW_DSP_COMBIFILE 63 +typedef struct CARD_FILES_DATA +{ + char * Name; + unsigned char Type; +} +CARD_FILES_DATA; +typedef struct CARD_FILES +{ + unsigned char Boot; + unsigned char Dsp [CARD_DSP_CNT]; + unsigned char DspTelindus; + unsigned char Prot [CARD_PROT_CNT]; +} +CARD_FILES; +#if CARDTYPE_H_WANT_DATA +#if CARDTYPE_H_WANT_FILE_DATA +CARD_FILES_DATA CardFData [] = { +// Filename Filetype + 0, CARD_FT_UNKNOWN, + "didnload.bin", CARD_FT_B, + "diprload.bin", CARD_FT_B, + "didiva.bin", CARD_FT_D, + "didivapp.bin", CARD_FT_D, + "dihscx.bin", CARD_FT_D, + "div110.bin", CARD_FT_D, + "dimodem.bin", CARD_FT_D, + "difax.bin", CARD_FT_D, + "di_etsi.bin", CARD_FT_S, + "di_1tr6.bin", CARD_FT_S, + "di_belg.bin", CARD_FT_S, + "di_franc.bin", CARD_FT_S, + "di_atel.bin", CARD_FT_S, + "di_ni.bin", CARD_FT_S, + "di_5ess.bin", CARD_FT_S, + "di_japan.bin", CARD_FT_S, + "di_etsi.sx", CARD_FT_S, + "di_1tr6.sx", CARD_FT_S, + "di_belg.sx", CARD_FT_S, + "di_franc.sx", CARD_FT_S, + "di_atel.sx", CARD_FT_S, + "di_ni.sx", CARD_FT_S, + "di_5ess.sx", CARD_FT_S, + "di_japan.sx", CARD_FT_S, + "di_etsi.sy", CARD_FT_S, + "di_1tr6.sy", CARD_FT_S, + "di_belg.sy", CARD_FT_S, + "di_franc.sy", CARD_FT_S, + "di_atel.sy", CARD_FT_S, + "di_ni.sy", CARD_FT_S, + "di_5ess.sy", CARD_FT_S, + "di_japan.sy", CARD_FT_S, + "di_etsi.sq", CARD_FT_S, + "di_1tr6.sq", CARD_FT_S, + "di_belg.sq", CARD_FT_S, + "di_franc.sq", CARD_FT_S, + "di_atel.sq", CARD_FT_S, + "di_ni.sq", CARD_FT_S, + "di_5ess.sq", CARD_FT_S, + "di_japan.sq", CARD_FT_S, + "di_etsi.p", CARD_FT_S, + "di_1tr6.p", CARD_FT_S, + "di_belg.p", CARD_FT_S, + "di_franc.p", CARD_FT_S, + "di_atel.p", CARD_FT_S, + "di_ni.p", CARD_FT_S, + "di_5ess.p", CARD_FT_S, + "di_japan.p", CARD_FT_S, + "di_etsi.sm", CARD_FT_M, + "di_1tr6.sm", CARD_FT_M, + "di_belg.sm", CARD_FT_M, + "di_franc.sm", CARD_FT_M, + "di_atel.sm", CARD_FT_M, + "di_ni.sm", CARD_FT_M, + "di_5ess.sm", CARD_FT_M, + "di_japan.sm", CARD_FT_M, + "di_swed.bin", CARD_FT_S, + "di_swed.sx", CARD_FT_S, + "di_swed.sy", CARD_FT_S, + "di_swed.sq", CARD_FT_S, + "di_swed.p", CARD_FT_S, + "di_swed.sm", CARD_FT_M, + "didspdld.bin", CARD_FT_NEW_DSP_COMBIFILE +}; +CARD_FILES CardFiles [] = +{ + { /* CARD_UNKNOWN */ + CARD_FILE_NONE, + CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, + CARD_FILE_NONE, + CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, + CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, + CARD_FILE_NONE + }, + { /* CARD_DIVA */ + CARD_FILE_NONE, + CARD_D_K1, CARD_D_H, CARD_D_V, CARD_FILE_NONE, CARD_D_F, + CARD_D_NEW_DSP_COMBIFILE, + CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, + CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, + CARD_FILE_NONE + }, + { /* CARD_PRO */ + CARD_FILE_NONE, + CARD_D_K2, CARD_D_H, CARD_D_V, CARD_D_M, CARD_D_F, + CARD_D_NEW_DSP_COMBIFILE, + CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, + CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, + CARD_FILE_NONE + }, + { /* CARD_PICO */ + CARD_FILE_NONE, + CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, + CARD_FILE_NONE, + CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, + CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, + CARD_FILE_NONE + }, + { /* CARD_S */ + CARD_B_S, + CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, + CARD_FILE_NONE, + CARD_P_S_E, CARD_P_S_1, CARD_P_S_B, CARD_P_S_F, + CARD_P_S_A, CARD_P_S_N, CARD_P_S_5, CARD_P_S_J, + CARD_P_S_S + }, + { /* CARD_SX */ + CARD_B_S, + CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, + CARD_FILE_NONE, + CARD_P_SX_E, CARD_P_SX_1, CARD_P_SX_B, CARD_P_SX_F, + CARD_P_SX_A, CARD_P_SX_N, CARD_P_SX_5, CARD_P_SX_J, + CARD_P_SX_S + }, + { /* CARD_SXN */ + CARD_B_S, + CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, + CARD_FILE_NONE, + CARD_P_SY_E, CARD_P_SY_1, CARD_P_SY_B, CARD_P_SY_F, + CARD_P_SY_A, CARD_P_SY_N, CARD_P_SY_5, CARD_P_SY_J, + CARD_P_SY_S + }, + { /* CARD_SCOM */ + CARD_B_S, + CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, + CARD_FILE_NONE, + CARD_P_SY_E, CARD_P_SY_1, CARD_P_SY_B, CARD_P_SY_F, + CARD_P_SY_A, CARD_P_SY_N, CARD_P_SY_5, CARD_P_SY_J, + CARD_P_SY_S + }, + { /* CARD_QUAD */ + CARD_B_S, + CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, + CARD_FILE_NONE, + CARD_P_SQ_E, CARD_P_SQ_1, CARD_P_SQ_B, CARD_P_SQ_F, + CARD_P_SQ_A, CARD_P_SQ_N, CARD_P_SQ_5, CARD_P_SQ_J, + CARD_P_SQ_S + }, + { /* CARD_PR */ + CARD_B_P, + CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, + CARD_FILE_NONE, + CARD_P_P_E, CARD_P_P_1, CARD_P_P_B, CARD_P_P_F, + CARD_P_P_A, CARD_P_P_N, CARD_P_P_5, CARD_P_P_J, + CARD_P_P_S + }, + { /* CARD_MAE */ + CARD_FILE_NONE, + CARD_D_K2, CARD_D_H, CARD_D_V, CARD_D_M, CARD_D_F, + CARD_D_NEW_DSP_COMBIFILE, + CARD_P_M_E, CARD_P_M_1, CARD_P_M_B, CARD_P_M_F, + CARD_P_M_A, CARD_P_M_N, CARD_P_M_5, CARD_P_M_J, + CARD_P_M_S + }, + { /* CARD_MAEQ */ /* currently not supported */ + CARD_FILE_NONE, + CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, + CARD_FILE_NONE, + CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, + CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, + CARD_FILE_NONE + }, + { /* CARD_MAEP */ /* currently not supported */ + CARD_FILE_NONE, + CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, + CARD_FILE_NONE, + CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, + CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, CARD_FILE_NONE, + CARD_FILE_NONE + } +}; +#endif /*CARDTYPE_H_WANT_FILE_DATA*/ +#else /*!CARDTYPE_H_WANT_DATA*/ +extern CARD_FILES_DATA CardFData [] ; +extern CARD_FILES CardFiles [] ; +#endif /*CARDTYPE_H_WANT_DATA*/ +#endif /* _CARDTYPE_H_ */ diff --git a/drivers/isdn/hardware/eicon/cp_vers.h b/drivers/isdn/hardware/eicon/cp_vers.h new file mode 100644 index 000000000000..cb5ada31111c --- /dev/null +++ b/drivers/isdn/hardware/eicon/cp_vers.h @@ -0,0 +1,26 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +static char diva_capi_common_code_build[] = "102-28"; diff --git a/drivers/isdn/hardware/eicon/dadapter.c b/drivers/isdn/hardware/eicon/dadapter.c new file mode 100644 index 000000000000..6e548a222ef1 --- /dev/null +++ b/drivers/isdn/hardware/eicon/dadapter.c @@ -0,0 +1,366 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +#include "platform.h" +#include "pc.h" +#include "debuglib.h" +#include "di_defs.h" +#include "divasync.h" +#include "dadapter.h" +/* -------------------------------------------------------------------------- + Adapter array change notification framework + -------------------------------------------------------------------------- */ +typedef struct _didd_adapter_change_notification { + didd_adapter_change_callback_t callback; + void IDI_CALL_ENTITY_T * context; +} didd_adapter_change_notification_t, \ + * IDI_CALL_ENTITY_T pdidd_adapter_change_notification_t; +#define DIVA_DIDD_MAX_NOTIFICATIONS 256 +static didd_adapter_change_notification_t\ + NotificationTable[DIVA_DIDD_MAX_NOTIFICATIONS]; +/* -------------------------------------------------------------------------- + Array to held adapter information + -------------------------------------------------------------------------- */ +static DESCRIPTOR HandleTable[NEW_MAX_DESCRIPTORS]; +dword Adapters = 0; /* Number of adapters */ +/* -------------------------------------------------------------------------- + Shadow IDI_DIMAINT + and 'shadow' debug stuff + -------------------------------------------------------------------------- */ +static void no_printf (unsigned char * format, ...) +{ +#ifdef EBUG + va_list ap; + va_start (ap, format); + debug((format, ap)); + va_end (ap); +#endif +} + +/* ------------------------------------------------------------------------- + Portable debug Library + ------------------------------------------------------------------------- */ +#include "debuglib.c" + +static DESCRIPTOR MAdapter = {IDI_DIMAINT, /* Adapter Type */ + 0x00, /* Channels */ + 0x0000, /* Features */ + (IDI_CALL)no_printf}; +/* -------------------------------------------------------------------------- + DAdapter. Only IDI clients with buffer, that is huge enough to + get all descriptors will receive information about DAdapter + { byte type, byte channels, word features, IDI_CALL request } + -------------------------------------------------------------------------- */ +static void IDI_CALL_LINK_T diva_dadapter_request (ENTITY IDI_CALL_ENTITY_T *); +static DESCRIPTOR DAdapter = {IDI_DADAPTER, /* Adapter Type */ + 0x00, /* Channels */ + 0x0000, /* Features */ + diva_dadapter_request }; +/* -------------------------------------------------------------------------- + LOCALS + -------------------------------------------------------------------------- */ +static dword diva_register_adapter_callback (\ + didd_adapter_change_callback_t callback, + void IDI_CALL_ENTITY_T* context); +static void diva_remove_adapter_callback (dword handle); +static void diva_notify_adapter_change (DESCRIPTOR* d, int removal); +static diva_os_spin_lock_t didd_spin; +/* -------------------------------------------------------------------------- + Should be called as first step, after driver init + -------------------------------------------------------------------------- */ +void diva_didd_load_time_init (void) { + memset (&HandleTable[0], 0x00, sizeof(HandleTable)); + memset (&NotificationTable[0], 0x00, sizeof(NotificationTable)); + diva_os_initialize_spin_lock (&didd_spin, "didd"); +} +/* -------------------------------------------------------------------------- + Should be called as last step, if driver does unload + -------------------------------------------------------------------------- */ +void diva_didd_load_time_finit (void) { + diva_os_destroy_spin_lock (&didd_spin, "didd"); +} +/* -------------------------------------------------------------------------- + Called in order to register new adapter in adapter array + return adapter handle (> 0) on success + return -1 adapter array overflow + -------------------------------------------------------------------------- */ +static int diva_didd_add_descriptor (DESCRIPTOR* d) { + diva_os_spin_lock_magic_t irql; + int i; + if (d->type == IDI_DIMAINT) { + if (d->request) { + MAdapter.request = d->request; + dprintf = (DIVA_DI_PRINTF)d->request; + diva_notify_adapter_change (&MAdapter, 0); /* Inserted */ + DBG_TRC (("DIMAINT registered, dprintf=%08x", d->request)) + } else { + DBG_TRC (("DIMAINT removed")) + diva_notify_adapter_change (&MAdapter, 1); /* About to remove */ + MAdapter.request = (IDI_CALL)no_printf; + dprintf = no_printf; + } + return (NEW_MAX_DESCRIPTORS); + } + for (i = 0; i < NEW_MAX_DESCRIPTORS; i++) { + diva_os_enter_spin_lock (&didd_spin, &irql, "didd_add"); + if (HandleTable[i].type == 0) { + memcpy (&HandleTable[i], d, sizeof(*d)); + Adapters++; + diva_os_leave_spin_lock (&didd_spin, &irql, "didd_add"); + diva_notify_adapter_change (d, 0); /* we have new adapter */ + DBG_TRC (("Add adapter[%d], request=%08x", (i+1), d->request)) + return (i+1); + } + diva_os_leave_spin_lock (&didd_spin, &irql, "didd_add"); + } + DBG_ERR (("Can't add adapter, out of resources")) + return (-1); +} +/* -------------------------------------------------------------------------- + Called in order to remove one registered adapter from array + return adapter handle (> 0) on success + return 0 on success + -------------------------------------------------------------------------- */ +static int diva_didd_remove_descriptor (IDI_CALL request) { + diva_os_spin_lock_magic_t irql; + int i; + if (request == MAdapter.request) { + DBG_TRC(("DIMAINT removed")) + dprintf = no_printf; + diva_notify_adapter_change (&MAdapter, 1); /* About to remove */ + MAdapter.request = (IDI_CALL)no_printf; + return (0); + } + for (i = 0; (Adapters && (i < NEW_MAX_DESCRIPTORS)); i++) { + if (HandleTable[i].request == request) { + diva_notify_adapter_change (&HandleTable[i], 1); /* About to remove */ + diva_os_enter_spin_lock (&didd_spin, &irql, "didd_rm"); + memset (&HandleTable[i], 0x00, sizeof(HandleTable[0])); + Adapters--; + diva_os_leave_spin_lock (&didd_spin, &irql, "didd_rm"); + DBG_TRC (("Remove adapter[%d], request=%08x", (i+1), request)) + return (0); + } + } + DBG_ERR (("Invalid request=%08x, can't remove adapter", request)) + return (-1); +} +/* -------------------------------------------------------------------------- + Read adapter array + return 1 if not enough space to save all available adapters + -------------------------------------------------------------------------- */ +static int diva_didd_read_adapter_array (DESCRIPTOR* buffer, int length) { + diva_os_spin_lock_magic_t irql; + int src, dst; + memset (buffer, 0x00, length); + length /= sizeof(DESCRIPTOR); + DBG_TRC (("DIDD_Read, space = %d, Adapters = %d", length, Adapters+2)) + + diva_os_enter_spin_lock (&didd_spin, &irql, "didd_read"); + for (src = 0, dst = 0; + (Adapters && (src < NEW_MAX_DESCRIPTORS) && (dst < length)); + src++) { + if (HandleTable[src].type) { + memcpy (&buffer[dst], &HandleTable[src], sizeof(DESCRIPTOR)); + dst++; + } + } + diva_os_leave_spin_lock (&didd_spin, &irql, "didd_read"); + if (dst < length) { + memcpy (&buffer[dst], &MAdapter, sizeof(DESCRIPTOR)); + dst++; + } else { + DBG_ERR (("Can't write DIMAINT. Array too small")) + } + if (dst < length) { + memcpy (&buffer[dst], &DAdapter, sizeof(DESCRIPTOR)); + dst++; + } else { + DBG_ERR (("Can't write DADAPTER. Array too small")) + } + DBG_TRC (("Read %d adapters", dst)) + return (dst == length); +} +/* -------------------------------------------------------------------------- + DAdapter request function. + This function does process only synchronous requests, and is used + for reception/registration of new interfaces + -------------------------------------------------------------------------- */ +static void IDI_CALL_LINK_T diva_dadapter_request (\ + ENTITY IDI_CALL_ENTITY_T *e) { + IDI_SYNC_REQ *syncReq = (IDI_SYNC_REQ *)e ; + if (e->Req) { /* We do not process it, also return error */ + e->Rc = OUT_OF_RESOURCES; + DBG_ERR (("Can't process async request, Req=%02x", e->Req)) + return; + } + /* + So, we process sync request + */ + switch (e->Rc) { + case IDI_SYNC_REQ_DIDD_REGISTER_ADAPTER_NOTIFY: { + diva_didd_adapter_notify_t* pinfo = &syncReq->didd_notify.info; + pinfo->handle = diva_register_adapter_callback (\ + (didd_adapter_change_callback_t)pinfo->callback, + (void IDI_CALL_ENTITY_T *)pinfo->context); + e->Rc = 0xff; + } break; + case IDI_SYNC_REQ_DIDD_REMOVE_ADAPTER_NOTIFY: { + diva_didd_adapter_notify_t* pinfo = &syncReq->didd_notify.info; + diva_remove_adapter_callback (pinfo->handle); + e->Rc = 0xff; + } break; + case IDI_SYNC_REQ_DIDD_ADD_ADAPTER: { + diva_didd_add_adapter_t* pinfo = &syncReq->didd_add_adapter.info; + if (diva_didd_add_descriptor ((DESCRIPTOR*)pinfo->descriptor) < 0) { + e->Rc = OUT_OF_RESOURCES; + } else { + e->Rc = 0xff; + } + } break; + case IDI_SYNC_REQ_DIDD_REMOVE_ADAPTER: { + diva_didd_remove_adapter_t* pinfo = &syncReq->didd_remove_adapter.info; + if (diva_didd_remove_descriptor ((IDI_CALL)pinfo->p_request) < 0) { + e->Rc = OUT_OF_RESOURCES; + } else { + e->Rc = 0xff; + } + } break; + case IDI_SYNC_REQ_DIDD_READ_ADAPTER_ARRAY: { + diva_didd_read_adapter_array_t* pinfo =\ + &syncReq->didd_read_adapter_array.info; + if (diva_didd_read_adapter_array ((DESCRIPTOR*)pinfo->buffer, + (int)pinfo->length)) { + e->Rc = OUT_OF_RESOURCES; + } else { + e->Rc = 0xff; + } + } break; + default: + DBG_ERR (("Can't process sync request, Req=%02x", e->Rc)) + e->Rc = OUT_OF_RESOURCES; + } +} +/* -------------------------------------------------------------------------- + IDI client does register his notification function + -------------------------------------------------------------------------- */ +static dword diva_register_adapter_callback (\ + didd_adapter_change_callback_t callback, + void IDI_CALL_ENTITY_T* context) { + diva_os_spin_lock_magic_t irql; + dword i; + + for (i = 0; i < DIVA_DIDD_MAX_NOTIFICATIONS; i++) { + diva_os_enter_spin_lock (&didd_spin, &irql, "didd_nfy_add"); + if (!NotificationTable[i].callback) { + NotificationTable[i].callback = callback; + NotificationTable[i].context = context; + diva_os_leave_spin_lock (&didd_spin, &irql, "didd_nfy_add"); + DBG_TRC(("Register adapter notification[%d]=%08x", i+1, callback)) + return (i+1); + } + diva_os_leave_spin_lock (&didd_spin, &irql, "didd_nfy_add"); + } + DBG_ERR (("Can't register adapter notification, overflow")) + return (0); +} +/* -------------------------------------------------------------------------- + IDI client does register his notification function + -------------------------------------------------------------------------- */ +static void diva_remove_adapter_callback (dword handle) { + diva_os_spin_lock_magic_t irql; + if (handle && ((--handle) < DIVA_DIDD_MAX_NOTIFICATIONS)) { + diva_os_enter_spin_lock (&didd_spin, &irql, "didd_nfy_rm"); + NotificationTable[handle].callback = NULL; + NotificationTable[handle].context = NULL; + diva_os_leave_spin_lock (&didd_spin, &irql, "didd_nfy_rm"); + DBG_TRC(("Remove adapter notification[%d]", (int)(handle+1))) + return; + } + DBG_ERR(("Can't remove adapter notification, handle=%d", handle)) +} +/* -------------------------------------------------------------------------- + Notify all client about adapter array change + Does suppose following behavior in the client side: + Step 1: Redister Notification + Step 2: Read Adapter Array + -------------------------------------------------------------------------- */ +static void diva_notify_adapter_change (DESCRIPTOR* d, int removal) { + int i, do_notify; + didd_adapter_change_notification_t nfy; + diva_os_spin_lock_magic_t irql; + for (i = 0; i < DIVA_DIDD_MAX_NOTIFICATIONS; i++) { + do_notify = 0; + diva_os_enter_spin_lock (&didd_spin, &irql, "didd_nfy"); + if (NotificationTable[i].callback) { + memcpy (&nfy, &NotificationTable[i], sizeof(nfy)); + do_notify = 1; + } + diva_os_leave_spin_lock (&didd_spin, &irql, "didd_nfy"); + if (do_notify) { + (*(nfy.callback))(nfy.context, d, removal); + } + } +} +/* -------------------------------------------------------------------------- + For all systems, that are linked by Kernel Mode Linker this is ONLY one + function thet should be exported by this device driver + IDI clients should look for IDI_DADAPTER, and use request function + of this adapter (sync request) in order to receive appropriate services: + - add new adapter + - remove existing adapter + - add adapter array notification + - remove adapter array notification + (read adapter is redundant in this case) + INPUT: + buffer - pointer to buffer that will receive adapter array + length - length (in bytes) of space in buffer + OUTPUT: + Adapter array will be written to memory described by 'buffer' + If the last adapter seen in the returned adapter array is + IDI_DADAPTER or if last adapter in array does have type '0', then + it was enougth space in buffer to accommodate all available + adapter descriptors + *NOTE 1 (debug interface): + The IDI adapter of type 'IDI_DIMAINT' does register as 'request' + famous 'dprintf' function (of type DI_PRINTF, please look + include/debuglib.c and include/debuglib.h) for details. + So dprintf is not exported from module debug module directly, + instead of this IDI_DIMAINT is registered. + Module load order will receive in this case: + 1. DIDD (this file) + 2. DIMAINT does load and register 'IDI_DIMAINT', at this step + DIDD should be able to get 'dprintf', save it, and + register with DIDD by means of 'dprintf' function. + 3. any other driver is loaded and is able to access adapter array + and debug interface + This approach does allow to load/unload debug interface on demand, + and save memory, it it is necessary. + -------------------------------------------------------------------------- */ +void IDI_CALL_LINK_T DIVA_DIDD_Read (void IDI_CALL_ENTITY_T * buffer, + int length) { + diva_didd_read_adapter_array (buffer, length); +} + diff --git a/drivers/isdn/hardware/eicon/dadapter.h b/drivers/isdn/hardware/eicon/dadapter.h new file mode 100644 index 000000000000..3575ac912e6c --- /dev/null +++ b/drivers/isdn/hardware/eicon/dadapter.h @@ -0,0 +1,34 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +#ifndef __DIVA_DIDD_DADAPTER_INC__ +#define __DIVA_DIDD_DADAPTER_INC__ + +void diva_didd_load_time_init (void); +void diva_didd_load_time_finit (void); + +#define NEW_MAX_DESCRIPTORS 64 + +#endif diff --git a/drivers/isdn/hardware/eicon/dbgioctl.h b/drivers/isdn/hardware/eicon/dbgioctl.h new file mode 100644 index 000000000000..0fb6f5e88b60 --- /dev/null +++ b/drivers/isdn/hardware/eicon/dbgioctl.h @@ -0,0 +1,198 @@ + +/* + * + Copyright (c) Eicon Technology Corporation, 2000. + * + This source file is supplied for the use with Eicon + Technology Corporation's range of DIVA Server Adapters. + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +/*------------------------------------------------------------------*/ +/* file: dbgioctl.h */ +/*------------------------------------------------------------------*/ + +#if !defined(__DBGIOCTL_H__) + +#define __DBGIOCTL_H__ + +#ifdef NOT_YET_NEEDED +/* + * The requested operation is passed in arg0 of DbgIoctlArgs, + * additional arguments (if any) in arg1, arg2 and arg3. + */ + +typedef struct +{ ULONG arg0 ; + ULONG arg1 ; + ULONG arg2 ; + ULONG arg3 ; +} DbgIoctlArgs ; + +#define DBG_COPY_LOGS 0 /* copy debugs to user until buffer full */ + /* arg1: size threshold */ + /* arg2: timeout in milliseconds */ + +#define DBG_FLUSH_LOGS 1 /* flush pending debugs to user buffer */ + /* arg1: internal driver id */ + +#define DBG_LIST_DRVS 2 /* return the list of registered drivers */ + +#define DBG_GET_MASK 3 /* get current debug mask of driver */ + /* arg1: internal driver id */ + +#define DBG_SET_MASK 4 /* set/change debug mask of driver */ + /* arg1: internal driver id */ + /* arg2: new debug mask */ + +#define DBG_GET_BUFSIZE 5 /* get current buffer size of driver */ + /* arg1: internal driver id */ + /* arg2: new debug mask */ + +#define DBG_SET_BUFSIZE 6 /* set new buffer size of driver */ + /* arg1: new buffer size */ + +/* + * common internal debug message structure + */ + +typedef struct +{ unsigned short id ; /* virtual driver id */ + unsigned short type ; /* special message type */ + unsigned long seq ; /* sequence number of message */ + unsigned long size ; /* size of message in bytes */ + unsigned long next ; /* offset to next buffered message */ + LARGE_INTEGER NTtime ; /* 100 ns since 1.1.1601 */ + unsigned char data[4] ;/* message data */ +} OldDbgMessage ; + +typedef struct +{ LARGE_INTEGER NTtime ; /* 100 ns since 1.1.1601 */ + unsigned short size ; /* size of message in bytes */ + unsigned short ffff ; /* always 0xffff to indicate new msg */ + unsigned short id ; /* virtual driver id */ + unsigned short type ; /* special message type */ + unsigned long seq ; /* sequence number of message */ + unsigned char data[4] ;/* message data */ +} DbgMessage ; + +#endif + +#define DRV_ID_UNKNOWN 0x0C /* for messages via prtComp() */ + +#define MSG_PROC_FLAG 0x80 +#define MSG_PROC_NO_GET(x) (((x) & MSG_PROC_FLAG) ? (((x) >> 4) & 7) : -1) +#define MSG_PROC_NO_SET(x) (MSG_PROC_FLAG | (((x) & 7) << 4)) + +#define MSG_TYPE_DRV_ID 0x0001 +#define MSG_TYPE_FLAGS 0x0002 +#define MSG_TYPE_STRING 0x0003 +#define MSG_TYPE_BINARY 0x0004 + +#define MSG_HEAD_SIZE ((unsigned long)&(((DbgMessage *)0)->data[0])) +#define MSG_ALIGN(len) (((unsigned long)(len) + MSG_HEAD_SIZE + 3) & ~3) +#define MSG_SIZE(pMsg) MSG_ALIGN((pMsg)->size) +#define MSG_NEXT(pMsg) ((DbgMessage *)( ((char *)(pMsg)) + MSG_SIZE(pMsg) )) + +#define OLD_MSG_HEAD_SIZE ((unsigned long)&(((OldDbgMessage *)0)->data[0])) +#define OLD_MSG_ALIGN(len) (((unsigned long)(len)+OLD_MSG_HEAD_SIZE+3) & ~3) + +/* + * manifest constants + */ + +#define MSG_FRAME_MAX_SIZE 2150 /* maximum size of B1 frame */ +#define MSG_TEXT_MAX_SIZE 1024 /* maximum size of msg text */ +#define MSG_MAX_SIZE MSG_ALIGN(MSG_FRAME_MAX_SIZE) +#define DBG_MIN_BUFFER_SIZE 0x00008000 /* minimal total buffer size 32 KB */ +#define DBG_DEF_BUFFER_SIZE 0x00020000 /* default total buffer size 128 KB */ +#define DBG_MAX_BUFFER_SIZE 0x00400000 /* maximal total buffer size 4 MB */ + +#define DBGDRV_NAME "Diehl_DIMAINT" +#define UNIDBG_DRIVER L"\\Device\\Diehl_DIMAINT" /* UNICODE name for kernel */ +#define DEBUG_DRIVER "\\\\.\\" DBGDRV_NAME /* traditional string for apps */ +#define DBGVXD_NAME "DIMAINT" +#define DEBUG_VXD "\\\\.\\" DBGVXD_NAME /* traditional string for apps */ + +/* + * Special IDI interface debug construction + */ + +#define DBG_IDI_SIG_REQ (unsigned long)0xF479C402 +#define DBG_IDI_SIG_IND (unsigned long)0xF479C403 +#define DBG_IDI_NL_REQ (unsigned long)0xF479C404 +#define DBG_IDI_NL_IND (unsigned long)0xF479C405 + +typedef struct +{ unsigned long magic_type ; + unsigned short data_len ; + unsigned char layer_ID ; + unsigned char entity_ID ; + unsigned char request ; + unsigned char ret_code ; + unsigned char indication ; + unsigned char complete ; + unsigned char data[4] ; +} DbgIdiAct, *DbgIdiAction ; + +/* + * We want to use the same IOCTL codes in Win95 and WinNT. + * The official constructor for IOCTL codes is the CTL_CODE macro + * from <winoctl.h> (<devioctl.h> in WinNT DDK environment). + * The problem here is that we don't know how to get <winioctl.h> + * working in a Win95 DDK environment! + */ + +# ifdef CTL_CODE /*{*/ + +/* Assert that we have the same idea of the CTL_CODE macro. */ + +#define CTL_CODE( DeviceType, Function, Method, Access ) ( \ + ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) \ +) + +# else /* !CTL_CODE */ /*}{*/ + +/* Use the definitions stolen from <winioctl.h>. */ + +#define CTL_CODE( DeviceType, Function, Method, Access ) ( \ + ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) \ +) + +#define METHOD_BUFFERED 0 +#define METHOD_IN_DIRECT 1 +#define METHOD_OUT_DIRECT 2 +#define METHOD_NEITHER 3 + +#define FILE_ANY_ACCESS 0 +#define FILE_READ_ACCESS ( 0x0001 ) // file & pipe +#define FILE_WRITE_ACCESS ( 0x0002 ) // file & pipe + +# endif /* CTL_CODE */ /*}*/ + +/* + * Now we can define WinNT/Win95 DeviceIoControl codes. + * + * These codes are defined in di_defs.h too, a possible mismatch will be + * detected when the dbgtool is compiled. + */ + +#define IOCTL_DRIVER_LNK \ + CTL_CODE(0x8001U,0x701,METHOD_OUT_DIRECT,FILE_ANY_ACCESS) +#define IOCTL_DRIVER_DBG \ + CTL_CODE(0x8001U,0x702,METHOD_OUT_DIRECT,FILE_ANY_ACCESS) + +#endif /* __DBGIOCTL_H__ */ diff --git a/drivers/isdn/hardware/eicon/debug.c b/drivers/isdn/hardware/eicon/debug.c new file mode 100644 index 000000000000..6851c6270ce8 --- /dev/null +++ b/drivers/isdn/hardware/eicon/debug.c @@ -0,0 +1,2133 @@ +#include "platform.h" +#include "pc.h" +#include "di_defs.h" +#include "debug_if.h" +#include "divasync.h" +#include "kst_ifc.h" +#include "maintidi.h" +#include "man_defs.h" + +/* + LOCALS + */ +#define DBG_MAGIC (0x47114711L) + +static void DI_register (void *arg); +static void DI_deregister (pDbgHandle hDbg); +static void DI_format (int do_lock, word id, int type, char *format, va_list argument_list); +static void DI_format_locked (word id, int type, char *format, va_list argument_list); +static void DI_format_old (word id, char *format, va_list ap) { } +static void DiProcessEventLog (unsigned short id, unsigned long msgID, va_list ap) { } +static void single_p (byte * P, word * PLength, byte Id); +static void diva_maint_xdi_cb (ENTITY* e); +static word SuperTraceCreateReadReq (byte* P, const char* path); +static int diva_mnt_cmp_nmbr (const char* nmbr); +static void diva_free_dma_descriptor (IDI_CALL request, int nr); +static int diva_get_dma_descriptor (IDI_CALL request, dword *dma_magic); +void diva_mnt_internal_dprintf (dword drv_id, dword type, char* p, ...); + +static dword MaxDumpSize = 256 ; +static dword MaxXlogSize = 2 + 128 ; +static char TraceFilter[DIVA_MAX_SELECTIVE_FILTER_LENGTH+1]; +static int TraceFilterIdent = -1; +static int TraceFilterChannel = -1; + +typedef struct _diva_maint_client { + dword sec; + dword usec; + pDbgHandle hDbg; + char drvName[128]; + dword dbgMask; + dword last_dbgMask; + IDI_CALL request; + _DbgHandle_ Dbg; + int logical; + int channels; + diva_strace_library_interface_t* pIdiLib; + BUFFERS XData; + char xbuffer[2048+512]; + byte* pmem; + int request_pending; + int dma_handle; +} diva_maint_client_t; +static diva_maint_client_t clients[MAX_DESCRIPTORS]; + +static void diva_change_management_debug_mask (diva_maint_client_t* pC, dword old_mask); + +static void diva_maint_error (void* user_context, + diva_strace_library_interface_t* hLib, + int Adapter, + int error, + const char* file, + int line); +static void diva_maint_state_change_notify (void* user_context, + diva_strace_library_interface_t* hLib, + int Adapter, + diva_trace_line_state_t* channel, + int notify_subject); +static void diva_maint_trace_notify (void* user_context, + diva_strace_library_interface_t* hLib, + int Adapter, + void* xlog_buffer, + int length); + + + +typedef struct MSG_QUEUE { + dword Size; /* total size of queue (constant) */ + byte *Base; /* lowest address (constant) */ + byte *High; /* Base + Size (constant) */ + byte *Head; /* first message in queue (if any) */ + byte *Tail; /* first free position */ + byte *Wrap; /* current wraparound position */ + dword Count; /* current no of bytes in queue */ +} MSG_QUEUE; + +typedef struct MSG_HEAD { + volatile dword Size; /* size of data following MSG_HEAD */ +#define MSG_INCOMPLETE 0x8000 /* ored to Size until queueCompleteMsg */ +} MSG_HEAD; + +#define queueCompleteMsg(p) do{ ((MSG_HEAD *)p - 1)->Size &= ~MSG_INCOMPLETE; }while(0) +#define queueCount(q) ((q)->Count) +#define MSG_NEED(size) \ + ( (sizeof(MSG_HEAD) + size + sizeof(dword) - 1) & ~(sizeof(dword) - 1) ) + +static void queueInit (MSG_QUEUE *Q, byte *Buffer, dword sizeBuffer) { + Q->Size = sizeBuffer; + Q->Base = Q->Head = Q->Tail = Buffer; + Q->High = Buffer + sizeBuffer; + Q->Wrap = NULL; + Q->Count= 0; +} + +static byte *queueAllocMsg (MSG_QUEUE *Q, word size) { + /* Allocate 'size' bytes at tail of queue which will be filled later + * directly with callers own message header info and/or message. + * An 'alloced' message is marked incomplete by oring the 'Size' field + * with MSG_INCOMPLETE. + * This must be reset via queueCompleteMsg() after the message is filled. + * As long as a message is marked incomplete queuePeekMsg() will return + * a 'queue empty' condition when it reaches such a message. */ + + MSG_HEAD *Msg; + word need = MSG_NEED(size); + + if (Q->Tail == Q->Head) { + if (Q->Wrap || need > Q->Size) { + return NULL; /* full */ + } + goto alloc; /* empty */ + } + + if (Q->Tail > Q->Head) { + if (Q->Tail + need <= Q->High) goto alloc; /* append */ + if (Q->Base + need > Q->Head) { + return NULL; /* too much */ + } + /* wraparound the queue (but not the message) */ + Q->Wrap = Q->Tail; + Q->Tail = Q->Base; + goto alloc; + } + + if (Q->Tail + need > Q->Head) { + return NULL; /* too much */ + } + +alloc: + Msg = (MSG_HEAD *)Q->Tail; + + Msg->Size = size | MSG_INCOMPLETE; + + Q->Tail += need; + Q->Count += size; + + + + return ((byte*)(Msg + 1)); +} + +static void queueFreeMsg (MSG_QUEUE *Q) { +/* Free the message at head of queue */ + + word size = ((MSG_HEAD *)Q->Head)->Size & ~MSG_INCOMPLETE; + + Q->Head += MSG_NEED(size); + Q->Count -= size; + + if (Q->Wrap) { + if (Q->Head >= Q->Wrap) { + Q->Head = Q->Base; + Q->Wrap = NULL; + } + } else if (Q->Head >= Q->Tail) { + Q->Head = Q->Tail = Q->Base; + } +} + +static byte *queuePeekMsg (MSG_QUEUE *Q, word *size) { + /* Show the first valid message in queue BUT DON'T free the message. + * After looking on the message contents it can be freed queueFreeMsg() + * or simply remain in message queue. */ + + MSG_HEAD *Msg = (MSG_HEAD *)Q->Head; + + if (((byte *)Msg == Q->Tail && !Q->Wrap) || + (Msg->Size & MSG_INCOMPLETE)) { + return NULL; + } else { + *size = Msg->Size; + return ((byte *)(Msg + 1)); + } +} + +/* + Message queue header + */ +static MSG_QUEUE* dbg_queue; +static byte* dbg_base; +static int external_dbg_queue; +static diva_os_spin_lock_t dbg_q_lock; +static diva_os_spin_lock_t dbg_adapter_lock; +static int dbg_q_busy; +static volatile dword dbg_sequence; +static dword start_sec; +static dword start_usec; + +/* + INTERFACE: + Initialize run time queue structures. + base: base of the message queue + length: length of the message queue + do_init: perfor queue reset + + return: zero on success, -1 on error + */ +int diva_maint_init (byte* base, unsigned long length, int do_init) { + if (dbg_queue || (!base) || (length < (4096*4))) { + return (-1); + } + + TraceFilter[0] = 0; + TraceFilterIdent = -1; + TraceFilterChannel = -1; + + dbg_base = base; + + diva_os_get_time (&start_sec, &start_usec); + + *(dword*)base = (dword)DBG_MAGIC; /* Store Magic */ + base += sizeof(dword); + length -= sizeof(dword); + + *(dword*)base = 2048; /* Extension Field Length */ + base += sizeof(dword); + length -= sizeof(dword); + + strcpy (base, "KERNEL MODE BUFFER\n"); + base += 2048; + length -= 2048; + + *(dword*)base = 0; /* Terminate extension */ + base += sizeof(dword); + length -= sizeof(dword); + + *(void**)base = (void*)(base+sizeof(void*)); /* Store Base */ + base += sizeof(void*); + length -= sizeof(void*); + + dbg_queue = (MSG_QUEUE*)base; + queueInit (dbg_queue, base + sizeof(MSG_QUEUE), length - sizeof(MSG_QUEUE) - 512); + external_dbg_queue = 0; + + if (!do_init) { + external_dbg_queue = 1; /* memory was located on the external device */ + } + + + if (diva_os_initialize_spin_lock (&dbg_q_lock, "dbg_init")) { + dbg_queue = NULL; + dbg_base = NULL; + external_dbg_queue = 0; + return (-1); + } + + if (diva_os_initialize_spin_lock (&dbg_adapter_lock, "dbg_init")) { + diva_os_destroy_spin_lock(&dbg_q_lock, "dbg_init"); + dbg_queue = NULL; + dbg_base = NULL; + external_dbg_queue = 0; + return (-1); + } + + return (0); +} + +/* + INTERFACE: + Finit at unload time + return address of internal queue or zero if queue + was external + */ +void* diva_maint_finit (void) { + void* ret = (void*)dbg_base; + int i; + + dbg_queue = NULL; + dbg_base = NULL; + + if (ret) { + diva_os_destroy_spin_lock(&dbg_q_lock, "dbg_finit"); + diva_os_destroy_spin_lock(&dbg_adapter_lock, "dbg_finit"); + } + + if (external_dbg_queue) { + ret = NULL; + } + external_dbg_queue = 0; + + for (i = 1; i < (sizeof(clients)/sizeof(clients[0])); i++) { + if (clients[i].pmem) { + diva_os_free (0, clients[i].pmem); + } + } + + return (ret); +} + +/* + INTERFACE: + Return amount of messages in debug queue + */ +dword diva_dbg_q_length (void) { + return (dbg_queue ? queueCount(dbg_queue) : 0); +} + +/* + INTERFACE: + Lock message queue and return the pointer to the first + entry. + */ +diva_dbg_entry_head_t* diva_maint_get_message (word* size, + diva_os_spin_lock_magic_t* old_irql) { + diva_dbg_entry_head_t* pmsg = NULL; + + diva_os_enter_spin_lock (&dbg_q_lock, old_irql, "read"); + if (dbg_q_busy) { + diva_os_leave_spin_lock (&dbg_q_lock, old_irql, "read_busy"); + return NULL; + } + dbg_q_busy = 1; + + if (!(pmsg = (diva_dbg_entry_head_t*)queuePeekMsg (dbg_queue, size))) { + dbg_q_busy = 0; + diva_os_leave_spin_lock (&dbg_q_lock, old_irql, "read_empty"); + } + + return (pmsg); +} + +/* + INTERFACE: + acknowledge last message and unlock queue + */ +void diva_maint_ack_message (int do_release, + diva_os_spin_lock_magic_t* old_irql) { + if (!dbg_q_busy) { + return; + } + if (do_release) { + queueFreeMsg (dbg_queue); + } + dbg_q_busy = 0; + diva_os_leave_spin_lock (&dbg_q_lock, old_irql, "read_ack"); +} + + +/* + INTERFACE: + PRT COMP function used to register + with MAINT adapter or log in compatibility + mode in case older driver version is connected too + */ +void diva_maint_prtComp (char *format, ...) { + void *hDbg; + va_list ap; + + if (!format) + return; + + va_start(ap, format); + + /* + register to new log driver functions + */ + if ((format[0] == 0) && ((unsigned char)format[1] == 255)) { + hDbg = va_arg(ap, void *); /* ptr to DbgHandle */ + DI_register (hDbg); + } + + va_end (ap); +} + +static void DI_register (void *arg) { + diva_os_spin_lock_magic_t old_irql; + dword sec, usec; + pDbgHandle hDbg ; + int id, free_id = -1, best_id = 0; + + diva_os_get_time (&sec, &usec); + + hDbg = (pDbgHandle)arg ; + /* + Check for bad args, specially for the old obsolete debug handle + */ + if ((hDbg == NULL) || + ((hDbg->id == 0) && (((_OldDbgHandle_ *)hDbg)->id == -1)) || + (hDbg->Registered != 0)) { + return ; + } + + diva_os_enter_spin_lock (&dbg_q_lock, &old_irql, "register"); + + for (id = 1; id < (sizeof(clients)/sizeof(clients[0])); id++) { + if (clients[id].hDbg == hDbg) { + /* + driver already registered + */ + diva_os_leave_spin_lock (&dbg_q_lock, &old_irql, "register"); + return; + } + if (clients[id].hDbg) { /* slot is busy */ + continue; + } + free_id = id; + if (!strcmp (clients[id].drvName, hDbg->drvName)) { + /* + This driver was already registered with this name + and slot is still free - reuse it + */ + best_id = 1; + break; + } + if (!clients[id].hDbg) { /* slot is busy */ + break; + } + } + + if (free_id != -1) { + diva_dbg_entry_head_t* pmsg = NULL; + int len; + char tmp[256]; + word size; + + /* + Register new driver with id == free_id + */ + clients[free_id].hDbg = hDbg; + clients[free_id].sec = sec; + clients[free_id].usec = usec; + strcpy (clients[free_id].drvName, hDbg->drvName); + + clients[free_id].dbgMask = hDbg->dbgMask; + if (best_id) { + hDbg->dbgMask |= clients[free_id].last_dbgMask; + } else { + clients[free_id].last_dbgMask = 0; + } + + hDbg->Registered = DBG_HANDLE_REG_NEW ; + hDbg->id = (byte)free_id; + hDbg->dbg_end = DI_deregister; + hDbg->dbg_prt = DI_format_locked; + hDbg->dbg_ev = DiProcessEventLog; + hDbg->dbg_irq = DI_format_locked; + if (hDbg->Version > 0) { + hDbg->dbg_old = DI_format_old; + } + hDbg->next = (pDbgHandle)DBG_MAGIC; + + /* + Log driver register, MAINT driver ID is '0' + */ + len = sprintf (tmp, "DIMAINT - drv # %d = '%s' registered", + free_id, hDbg->drvName); + + while (!(pmsg = (diva_dbg_entry_head_t*)queueAllocMsg (dbg_queue, + (word)(len+1+sizeof(*pmsg))))) { + if ((pmsg = (diva_dbg_entry_head_t*)queuePeekMsg (dbg_queue, &size))) { + queueFreeMsg (dbg_queue); + } else { + break; + } + } + + if (pmsg) { + pmsg->sequence = dbg_sequence++; + pmsg->time_sec = sec; + pmsg->time_usec = usec; + pmsg->facility = MSG_TYPE_STRING; + pmsg->dli = DLI_REG; + pmsg->drv_id = 0; /* id 0 - DIMAINT */ + pmsg->di_cpu = 0; + pmsg->data_length = len+1; + + memcpy (&pmsg[1], tmp, len+1); + queueCompleteMsg (pmsg); + diva_maint_wakeup_read(); + } + } + + diva_os_leave_spin_lock (&dbg_q_lock, &old_irql, "register"); +} + +static void DI_deregister (pDbgHandle hDbg) { + diva_os_spin_lock_magic_t old_irql, old_irql1; + dword sec, usec; + int i; + word size; + byte* pmem = NULL; + + diva_os_get_time (&sec, &usec); + + diva_os_enter_spin_lock (&dbg_adapter_lock, &old_irql1, "read"); + diva_os_enter_spin_lock (&dbg_q_lock, &old_irql, "read"); + + for (i = 1; i < (sizeof(clients)/sizeof(clients[0])); i++) { + if (clients[i].hDbg == hDbg) { + diva_dbg_entry_head_t* pmsg; + char tmp[256]; + int len; + + clients[i].hDbg = NULL; + + hDbg->id = -1; + hDbg->dbgMask = 0; + hDbg->dbg_end = NULL; + hDbg->dbg_prt = NULL; + hDbg->dbg_irq = NULL; + if (hDbg->Version > 0) + hDbg->dbg_old = NULL; + hDbg->Registered = 0; + hDbg->next = NULL; + + if (clients[i].pIdiLib) { + (*(clients[i].pIdiLib->DivaSTraceLibraryFinit))(clients[i].pIdiLib->hLib); + clients[i].pIdiLib = NULL; + + pmem = clients[i].pmem; + clients[i].pmem = NULL; + } + + /* + Log driver register, MAINT driver ID is '0' + */ + len = sprintf (tmp, "DIMAINT - drv # %d = '%s' de-registered", + i, hDbg->drvName); + + while (!(pmsg = (diva_dbg_entry_head_t*)queueAllocMsg (dbg_queue, + (word)(len+1+sizeof(*pmsg))))) { + if ((pmsg = (diva_dbg_entry_head_t*)queuePeekMsg (dbg_queue, &size))) { + queueFreeMsg (dbg_queue); + } else { + break; + } + } + + if (pmsg) { + pmsg->sequence = dbg_sequence++; + pmsg->time_sec = sec; + pmsg->time_usec = usec; + pmsg->facility = MSG_TYPE_STRING; + pmsg->dli = DLI_REG; + pmsg->drv_id = 0; /* id 0 - DIMAINT */ + pmsg->di_cpu = 0; + pmsg->data_length = len+1; + + memcpy (&pmsg[1], tmp, len+1); + queueCompleteMsg (pmsg); + diva_maint_wakeup_read(); + } + + break; + } + } + + diva_os_leave_spin_lock (&dbg_q_lock, &old_irql, "read_ack"); + diva_os_leave_spin_lock (&dbg_adapter_lock, &old_irql1, "read_ack"); + + if (pmem) { + diva_os_free (0, pmem); + } +} + +static void DI_format_locked (unsigned short id, + int type, + char *format, + va_list argument_list) { + DI_format (1, id, type, format, argument_list); +} + +static void DI_format (int do_lock, + unsigned short id, + int type, + char *format, + va_list ap) { + diva_os_spin_lock_magic_t old_irql; + dword sec, usec; + diva_dbg_entry_head_t* pmsg = NULL; + dword length; + word size; + static char fmtBuf[MSG_FRAME_MAX_SIZE+sizeof(*pmsg)+1]; + char *data; + unsigned short code; + + if (diva_os_in_irq()) { + dbg_sequence++; + return; + } + + if ((!format) || + ((TraceFilter[0] != 0) && ((TraceFilterIdent < 0) || (TraceFilterChannel < 0)))) { + return; + } + + + + diva_os_get_time (&sec, &usec); + + if (do_lock) { + diva_os_enter_spin_lock (&dbg_q_lock, &old_irql, "format"); + } + + switch (type) { + case DLI_MXLOG : + case DLI_BLK : + case DLI_SEND: + case DLI_RECV: + if (!(length = va_arg(ap, unsigned long))) { + break; + } + if (length > MaxDumpSize) { + length = MaxDumpSize; + } + while (!(pmsg = (diva_dbg_entry_head_t*)queueAllocMsg (dbg_queue, + (word)length+sizeof(*pmsg)))) { + if ((pmsg = (diva_dbg_entry_head_t*)queuePeekMsg (dbg_queue, &size))) { + queueFreeMsg (dbg_queue); + } else { + break; + } + } + if (pmsg) { + memcpy (&pmsg[1], format, length); + pmsg->sequence = dbg_sequence++; + pmsg->time_sec = sec; + pmsg->time_usec = usec; + pmsg->facility = MSG_TYPE_BINARY ; + pmsg->dli = type; /* DLI_XXX */ + pmsg->drv_id = id; /* driver MAINT id */ + pmsg->di_cpu = 0; + pmsg->data_length = length; + queueCompleteMsg (pmsg); + } + break; + + case DLI_XLOG: { + byte* p; + data = va_arg(ap, char*); + code = (unsigned short)va_arg(ap, unsigned int); + length = (unsigned long) va_arg(ap, unsigned int); + + if (length > MaxXlogSize) + length = MaxXlogSize; + + while (!(pmsg = (diva_dbg_entry_head_t*)queueAllocMsg (dbg_queue, + (word)length+sizeof(*pmsg)+2))) { + if ((pmsg = (diva_dbg_entry_head_t*)queuePeekMsg (dbg_queue, &size))) { + queueFreeMsg (dbg_queue); + } else { + break; + } + } + if (pmsg) { + p = (byte*)&pmsg[1]; + p[0] = (char)(code) ; + p[1] = (char)(code >> 8) ; + if (data && length) { + memcpy (&p[2], &data[0], length) ; + } + length += 2 ; + + pmsg->sequence = dbg_sequence++; + pmsg->time_sec = sec; + pmsg->time_usec = usec; + pmsg->facility = MSG_TYPE_BINARY ; + pmsg->dli = type; /* DLI_XXX */ + pmsg->drv_id = id; /* driver MAINT id */ + pmsg->di_cpu = 0; + pmsg->data_length = length; + queueCompleteMsg (pmsg); + } + } break; + + case DLI_LOG : + case DLI_FTL : + case DLI_ERR : + case DLI_TRC : + case DLI_REG : + case DLI_MEM : + case DLI_SPL : + case DLI_IRP : + case DLI_TIM : + case DLI_TAPI: + case DLI_NDIS: + case DLI_CONN: + case DLI_STAT: + case DLI_PRV0: + case DLI_PRV1: + case DLI_PRV2: + case DLI_PRV3: + if ((length = (unsigned long)vsprintf (&fmtBuf[0], format, ap)) > 0) { + length += (sizeof(*pmsg)+1); + + while (!(pmsg = (diva_dbg_entry_head_t*)queueAllocMsg (dbg_queue, + (word)length))) { + if ((pmsg = (diva_dbg_entry_head_t*)queuePeekMsg (dbg_queue, &size))) { + queueFreeMsg (dbg_queue); + } else { + break; + } + } + + pmsg->sequence = dbg_sequence++; + pmsg->time_sec = sec; + pmsg->time_usec = usec; + pmsg->facility = MSG_TYPE_STRING; + pmsg->dli = type; /* DLI_XXX */ + pmsg->drv_id = id; /* driver MAINT id */ + pmsg->di_cpu = 0; + pmsg->data_length = length - sizeof(*pmsg); + + memcpy (&pmsg[1], fmtBuf, pmsg->data_length); + queueCompleteMsg (pmsg); + } + break; + + } /* switch type */ + + + if (queueCount(dbg_queue)) { + diva_maint_wakeup_read(); + } + + if (do_lock) { + diva_os_leave_spin_lock (&dbg_q_lock, &old_irql, "format"); + } +} + +/* + Write driver ID and driver revision to callers buffer + */ +int diva_get_driver_info (dword id, byte* data, int data_length) { + diva_os_spin_lock_magic_t old_irql; + byte* p = data; + int to_copy; + + if (!data || !id || (data_length < 17) || + (id >= (sizeof(clients)/sizeof(clients[0])))) { + return (-1); + } + + diva_os_enter_spin_lock (&dbg_q_lock, &old_irql, "driver info"); + + if (clients[id].hDbg) { + *p++ = 1; + *p++ = (byte)clients[id].sec; /* save seconds */ + *p++ = (byte)(clients[id].sec >> 8); + *p++ = (byte)(clients[id].sec >> 16); + *p++ = (byte)(clients[id].sec >> 24); + + *p++ = (byte)(clients[id].usec/1000); /* save mseconds */ + *p++ = (byte)((clients[id].usec/1000) >> 8); + *p++ = (byte)((clients[id].usec/1000) >> 16); + *p++ = (byte)((clients[id].usec/1000) >> 24); + + data_length -= 9; + + if ((to_copy = MIN(strlen(clients[id].drvName), data_length-1))) { + memcpy (p, clients[id].drvName, to_copy); + p += to_copy; + data_length -= to_copy; + if ((data_length >= 4) && clients[id].hDbg->drvTag[0]) { + *p++ = '('; + data_length -= 1; + if ((to_copy = MIN(strlen(clients[id].hDbg->drvTag), data_length-2))) { + memcpy (p, clients[id].hDbg->drvTag, to_copy); + p += to_copy; + data_length -= to_copy; + if (data_length >= 2) { + *p++ = ')'; + data_length--; + } + } + } + } + } + *p++ = 0; + + diva_os_leave_spin_lock (&dbg_q_lock, &old_irql, "driver info"); + + return (p - data); +} + +int diva_get_driver_dbg_mask (dword id, byte* data) { + diva_os_spin_lock_magic_t old_irql; + int ret = -1; + + if (!data || !id || (id >= (sizeof(clients)/sizeof(clients[0])))) { + return (-1); + } + diva_os_enter_spin_lock (&dbg_q_lock, &old_irql, "driver info"); + + if (clients[id].hDbg) { + ret = 4; + *data++= (byte)(clients[id].hDbg->dbgMask); + *data++= (byte)(clients[id].hDbg->dbgMask >> 8); + *data++= (byte)(clients[id].hDbg->dbgMask >> 16); + *data++= (byte)(clients[id].hDbg->dbgMask >> 24); + } + + diva_os_leave_spin_lock (&dbg_q_lock, &old_irql, "driver info"); + + return (ret); +} + +int diva_set_driver_dbg_mask (dword id, dword mask) { + diva_os_spin_lock_magic_t old_irql, old_irql1; + int ret = -1; + + + if (!id || (id >= (sizeof(clients)/sizeof(clients[0])))) { + return (-1); + } + + diva_os_enter_spin_lock (&dbg_adapter_lock, &old_irql1, "dbg mask"); + diva_os_enter_spin_lock (&dbg_q_lock, &old_irql, "dbg mask"); + + if (clients[id].hDbg) { + dword old_mask = clients[id].hDbg->dbgMask; + mask &= 0x7fffffff; + clients[id].hDbg->dbgMask = mask; + clients[id].last_dbgMask = (clients[id].hDbg->dbgMask | clients[id].dbgMask); + ret = 4; + diva_change_management_debug_mask (&clients[id], old_mask); + } + + + diva_os_leave_spin_lock (&dbg_q_lock, &old_irql, "dbg mask"); + + if (clients[id].request_pending) { + clients[id].request_pending = 0; + (*(clients[id].request))((ENTITY*)(*(clients[id].pIdiLib->DivaSTraceGetHandle))(clients[id].pIdiLib->hLib)); + } + + diva_os_leave_spin_lock (&dbg_adapter_lock, &old_irql1, "dbg mask"); + + return (ret); +} + +static int diva_get_idi_adapter_info (IDI_CALL request, dword* serial, dword* logical) { + IDI_SYNC_REQ sync_req; + + sync_req.xdi_logical_adapter_number.Req = 0; + sync_req.xdi_logical_adapter_number.Rc = IDI_SYNC_REQ_XDI_GET_LOGICAL_ADAPTER_NUMBER; + (*request)((ENTITY *)&sync_req); + *logical = sync_req.xdi_logical_adapter_number.info.logical_adapter_number; + + sync_req.GetSerial.Req = 0; + sync_req.GetSerial.Rc = IDI_SYNC_REQ_GET_SERIAL; + sync_req.GetSerial.serial = 0; + (*request)((ENTITY *)&sync_req); + *serial = sync_req.GetSerial.serial; + + return (0); +} + +/* + Register XDI adapter as MAINT compatible driver + */ +void diva_mnt_add_xdi_adapter (const DESCRIPTOR* d) { + diva_os_spin_lock_magic_t old_irql, old_irql1; + dword sec, usec, logical, serial, org_mask; + int id, best_id = 0, free_id = -1; + char tmp[256]; + diva_dbg_entry_head_t* pmsg = NULL; + int len; + word size; + byte* pmem; + + diva_os_get_time (&sec, &usec); + diva_get_idi_adapter_info (d->request, &serial, &logical); + if (serial & 0xff000000) { + sprintf (tmp, "ADAPTER:%d SN:%u-%d", + (int)logical, + serial & 0x00ffffff, + (byte)(((serial & 0xff000000) >> 24) + 1)); + } else { + sprintf (tmp, "ADAPTER:%d SN:%u", (int)logical, serial); + } + + if (!(pmem = diva_os_malloc (0, DivaSTraceGetMemotyRequirement (d->channels)))) { + return; + } + memset (pmem, 0x00, DivaSTraceGetMemotyRequirement (d->channels)); + + diva_os_enter_spin_lock (&dbg_adapter_lock, &old_irql1, "register"); + diva_os_enter_spin_lock (&dbg_q_lock, &old_irql, "register"); + + for (id = 1; id < (sizeof(clients)/sizeof(clients[0])); id++) { + if (clients[id].hDbg && (clients[id].request == d->request)) { + diva_os_leave_spin_lock (&dbg_q_lock, &old_irql, "register"); + diva_os_leave_spin_lock (&dbg_adapter_lock, &old_irql1, "register"); + diva_os_free(0, pmem); + return; + } + if (clients[id].hDbg) { /* slot is busy */ + continue; + } + if (free_id < 0) { + free_id = id; + } + if (!strcmp (clients[id].drvName, tmp)) { + /* + This driver was already registered with this name + and slot is still free - reuse it + */ + free_id = id; + best_id = 1; + break; + } + } + + if (free_id < 0) { + diva_os_leave_spin_lock (&dbg_q_lock, &old_irql, "register"); + diva_os_leave_spin_lock (&dbg_adapter_lock, &old_irql1, "register"); + diva_os_free (0, pmem); + return; + } + + id = free_id; + clients[id].request = d->request; + clients[id].request_pending = 0; + clients[id].hDbg = &clients[id].Dbg; + clients[id].sec = sec; + clients[id].usec = usec; + strcpy (clients[id].drvName, tmp); + strcpy (clients[id].Dbg.drvName, tmp); + clients[id].Dbg.drvTag[0] = 0; + clients[id].logical = (int)logical; + clients[id].channels = (int)d->channels; + clients[id].dma_handle = -1; + + clients[id].Dbg.dbgMask = 0; + clients[id].dbgMask = clients[id].Dbg.dbgMask; + if (id) { + clients[id].Dbg.dbgMask |= clients[free_id].last_dbgMask; + } else { + clients[id].last_dbgMask = 0; + } + clients[id].Dbg.Registered = DBG_HANDLE_REG_NEW; + clients[id].Dbg.id = (byte)id; + clients[id].Dbg.dbg_end = DI_deregister; + clients[id].Dbg.dbg_prt = DI_format_locked; + clients[id].Dbg.dbg_ev = DiProcessEventLog; + clients[id].Dbg.dbg_irq = DI_format_locked; + clients[id].Dbg.next = (pDbgHandle)DBG_MAGIC; + + { + diva_trace_library_user_interface_t diva_maint_user_ifc = { &clients[id], + diva_maint_state_change_notify, + diva_maint_trace_notify, + diva_maint_error }; + + /* + Attach to adapter management interface + */ + if ((clients[id].pIdiLib = + DivaSTraceLibraryCreateInstance ((int)logical, &diva_maint_user_ifc, pmem))) { + if (((*(clients[id].pIdiLib->DivaSTraceLibraryStart))(clients[id].pIdiLib->hLib))) { + diva_mnt_internal_dprintf (0, DLI_ERR, "Adapter(%d) Start failed", (int)logical); + (*(clients[id].pIdiLib->DivaSTraceLibraryFinit))(clients[id].pIdiLib->hLib); + clients[id].pIdiLib = NULL; + } + } else { + diva_mnt_internal_dprintf (0, DLI_ERR, "A(%d) management init failed", (int)logical); + } + } + + if (!clients[id].pIdiLib) { + clients[id].request = NULL; + clients[id].request_pending = 0; + clients[id].hDbg = NULL; + diva_os_leave_spin_lock (&dbg_q_lock, &old_irql, "register"); + diva_os_leave_spin_lock (&dbg_adapter_lock, &old_irql1, "register"); + diva_os_free (0, pmem); + return; + } + + /* + Log driver register, MAINT driver ID is '0' + */ + len = sprintf (tmp, "DIMAINT - drv # %d = '%s' registered", + id, clients[id].Dbg.drvName); + + while (!(pmsg = (diva_dbg_entry_head_t*)queueAllocMsg (dbg_queue, + (word)(len+1+sizeof(*pmsg))))) { + if ((pmsg = (diva_dbg_entry_head_t*)queuePeekMsg (dbg_queue, &size))) { + queueFreeMsg (dbg_queue); + } else { + break; + } + } + + if (pmsg) { + pmsg->sequence = dbg_sequence++; + pmsg->time_sec = sec; + pmsg->time_usec = usec; + pmsg->facility = MSG_TYPE_STRING; + pmsg->dli = DLI_REG; + pmsg->drv_id = 0; /* id 0 - DIMAINT */ + pmsg->di_cpu = 0; + pmsg->data_length = len+1; + + memcpy (&pmsg[1], tmp, len+1); + queueCompleteMsg (pmsg); + diva_maint_wakeup_read(); + } + + org_mask = clients[id].Dbg.dbgMask; + clients[id].Dbg.dbgMask = 0; + + diva_os_leave_spin_lock (&dbg_q_lock, &old_irql, "register"); + + if (clients[id].request_pending) { + clients[id].request_pending = 0; + (*(clients[id].request))((ENTITY*)(*(clients[id].pIdiLib->DivaSTraceGetHandle))(clients[id].pIdiLib->hLib)); + } + + diva_os_leave_spin_lock (&dbg_adapter_lock, &old_irql1, "register"); + + diva_set_driver_dbg_mask (id, org_mask); +} + +/* + De-Register XDI adapter + */ +void diva_mnt_remove_xdi_adapter (const DESCRIPTOR* d) { + diva_os_spin_lock_magic_t old_irql, old_irql1; + dword sec, usec; + int i; + word size; + byte* pmem = NULL; + + diva_os_get_time (&sec, &usec); + + diva_os_enter_spin_lock (&dbg_adapter_lock, &old_irql1, "read"); + diva_os_enter_spin_lock (&dbg_q_lock, &old_irql, "read"); + + for (i = 1; i < (sizeof(clients)/sizeof(clients[0])); i++) { + if (clients[i].hDbg && (clients[i].request == d->request)) { + diva_dbg_entry_head_t* pmsg; + char tmp[256]; + int len; + + if (clients[i].pIdiLib) { + (*(clients[i].pIdiLib->DivaSTraceLibraryFinit))(clients[i].pIdiLib->hLib); + clients[i].pIdiLib = NULL; + + pmem = clients[i].pmem; + clients[i].pmem = NULL; + } + + clients[i].hDbg = NULL; + clients[i].request_pending = 0; + if (clients[i].dma_handle >= 0) { + /* + Free DMA handle + */ + diva_free_dma_descriptor (clients[i].request, clients[i].dma_handle); + clients[i].dma_handle = -1; + } + clients[i].request = NULL; + + /* + Log driver register, MAINT driver ID is '0' + */ + len = sprintf (tmp, "DIMAINT - drv # %d = '%s' de-registered", + i, clients[i].Dbg.drvName); + + memset (&clients[i].Dbg, 0x00, sizeof(clients[i].Dbg)); + + while (!(pmsg = (diva_dbg_entry_head_t*)queueAllocMsg (dbg_queue, + (word)(len+1+sizeof(*pmsg))))) { + if ((pmsg = (diva_dbg_entry_head_t*)queuePeekMsg (dbg_queue, &size))) { + queueFreeMsg (dbg_queue); + } else { + break; + } + } + + if (pmsg) { + pmsg->sequence = dbg_sequence++; + pmsg->time_sec = sec; + pmsg->time_usec = usec; + pmsg->facility = MSG_TYPE_STRING; + pmsg->dli = DLI_REG; + pmsg->drv_id = 0; /* id 0 - DIMAINT */ + pmsg->di_cpu = 0; + pmsg->data_length = len+1; + + memcpy (&pmsg[1], tmp, len+1); + queueCompleteMsg (pmsg); + diva_maint_wakeup_read(); + } + + break; + } + } + + diva_os_leave_spin_lock (&dbg_q_lock, &old_irql, "read_ack"); + diva_os_leave_spin_lock (&dbg_adapter_lock, &old_irql1, "read_ack"); + + if (pmem) { + diva_os_free (0, pmem); + } +} + +/* ---------------------------------------------------------------- + Low level interface for management interface client + ---------------------------------------------------------------- */ +/* + Return handle to client structure + */ +void* SuperTraceOpenAdapter (int AdapterNumber) { + int i; + + for (i = 1; i < (sizeof(clients)/sizeof(clients[0])); i++) { + if (clients[i].hDbg && clients[i].request && (clients[i].logical == AdapterNumber)) { + return (&clients[i]); + } + } + + return NULL; +} + +int SuperTraceCloseAdapter (void* AdapterHandle) { + return (0); +} + +int SuperTraceReadRequest (void* AdapterHandle, const char* name, byte* data) { + diva_maint_client_t* pC = (diva_maint_client_t*)AdapterHandle; + + if (pC && pC->pIdiLib && pC->request) { + ENTITY* e = (ENTITY*)(*(pC->pIdiLib->DivaSTraceGetHandle))(pC->pIdiLib->hLib); + byte* xdata = (byte*)&pC->xbuffer[0]; + char tmp = 0; + word length; + + if (!strcmp(name, "\\")) { /* Read ROOT */ + name = &tmp; + } + length = SuperTraceCreateReadReq (xdata, name); + single_p (xdata, &length, 0); /* End Of Message */ + + e->Req = MAN_READ; + e->ReqCh = 0; + e->X->PLength = length; + e->X->P = (byte*)xdata; + + pC->request_pending = 1; + + return (0); + } + + return (-1); +} + +int SuperTraceGetNumberOfChannels (void* AdapterHandle) { + if (AdapterHandle) { + diva_maint_client_t* pC = (diva_maint_client_t*)AdapterHandle; + + return (pC->channels); + } + + return (0); +} + +int SuperTraceASSIGN (void* AdapterHandle, byte* data) { + diva_maint_client_t* pC = (diva_maint_client_t*)AdapterHandle; + + if (pC && pC->pIdiLib && pC->request) { + ENTITY* e = (ENTITY*)(*(pC->pIdiLib->DivaSTraceGetHandle))(pC->pIdiLib->hLib); + IDI_SYNC_REQ* preq; + char buffer[((sizeof(preq->xdi_extended_features)+4) > sizeof(ENTITY)) ? (sizeof(preq->xdi_extended_features)+4) : sizeof(ENTITY)]; + char features[4]; + word assign_data_length = 1; + + features[0] = 0; + pC->xbuffer[0] = 0; + preq = (IDI_SYNC_REQ*)&buffer[0]; + preq->xdi_extended_features.Req = 0; + preq->xdi_extended_features.Rc = IDI_SYNC_REQ_XDI_GET_EXTENDED_FEATURES; + preq->xdi_extended_features.info.buffer_length_in_bytes = sizeof(features); + preq->xdi_extended_features.info.features = &features[0]; + + (*(pC->request))((ENTITY*)preq); + + if ((features[0] & DIVA_XDI_EXTENDED_FEATURES_VALID) && + (features[0] & DIVA_XDI_EXTENDED_FEATURE_MANAGEMENT_DMA)) { + dword rx_dma_magic; + if ((pC->dma_handle = diva_get_dma_descriptor (pC->request, &rx_dma_magic)) >= 0) { + pC->xbuffer[0] = LLI; + pC->xbuffer[1] = 8; + pC->xbuffer[2] = 0x40; + pC->xbuffer[3] = (byte)pC->dma_handle; + pC->xbuffer[4] = (byte)rx_dma_magic; + pC->xbuffer[5] = (byte)(rx_dma_magic >> 8); + pC->xbuffer[6] = (byte)(rx_dma_magic >> 16); + pC->xbuffer[7] = (byte)(rx_dma_magic >> 24); + pC->xbuffer[8] = (byte)DIVA_MAX_MANAGEMENT_TRANSFER_SIZE; + pC->xbuffer[9] = (byte)(DIVA_MAX_MANAGEMENT_TRANSFER_SIZE >> 8); + pC->xbuffer[10] = 0; + + assign_data_length = 11; + } + } else { + pC->dma_handle = -1; + } + + e->Id = MAN_ID; + e->callback = diva_maint_xdi_cb; + e->XNum = 1; + e->X = &pC->XData; + e->Req = ASSIGN; + e->ReqCh = 0; + e->X->PLength = assign_data_length; + e->X->P = (byte*)&pC->xbuffer[0]; + + pC->request_pending = 1; + + return (0); + } + + return (-1); +} + +int SuperTraceREMOVE (void* AdapterHandle) { + diva_maint_client_t* pC = (diva_maint_client_t*)AdapterHandle; + + if (pC && pC->pIdiLib && pC->request) { + ENTITY* e = (ENTITY*)(*(pC->pIdiLib->DivaSTraceGetHandle))(pC->pIdiLib->hLib); + + e->XNum = 1; + e->X = &pC->XData; + e->Req = REMOVE; + e->ReqCh = 0; + e->X->PLength = 1; + e->X->P = (byte*)&pC->xbuffer[0]; + pC->xbuffer[0] = 0; + + pC->request_pending = 1; + + return (0); + } + + return (-1); +} + +int SuperTraceTraceOnRequest(void* hAdapter, const char* name, byte* data) { + diva_maint_client_t* pC = (diva_maint_client_t*)hAdapter; + + if (pC && pC->pIdiLib && pC->request) { + ENTITY* e = (ENTITY*)(*(pC->pIdiLib->DivaSTraceGetHandle))(pC->pIdiLib->hLib); + byte* xdata = (byte*)&pC->xbuffer[0]; + char tmp = 0; + word length; + + if (!strcmp(name, "\\")) { /* Read ROOT */ + name = &tmp; + } + length = SuperTraceCreateReadReq (xdata, name); + single_p (xdata, &length, 0); /* End Of Message */ + e->Req = MAN_EVENT_ON; + e->ReqCh = 0; + e->X->PLength = length; + e->X->P = (byte*)xdata; + + pC->request_pending = 1; + + return (0); + } + + return (-1); +} + +int SuperTraceWriteVar (void* AdapterHandle, + byte* data, + const char* name, + void* var, + byte type, + byte var_length) { + diva_maint_client_t* pC = (diva_maint_client_t*)AdapterHandle; + + if (pC && pC->pIdiLib && pC->request) { + ENTITY* e = (ENTITY*)(*(pC->pIdiLib->DivaSTraceGetHandle))(pC->pIdiLib->hLib); + diva_man_var_header_t* pVar = (diva_man_var_header_t*)&pC->xbuffer[0]; + word length = SuperTraceCreateReadReq ((byte*)pVar, name); + + memcpy (&pC->xbuffer[length], var, var_length); + length += var_length; + pVar->length += var_length; + pVar->value_length = var_length; + pVar->type = type; + single_p ((byte*)pVar, &length, 0); /* End Of Message */ + + e->Req = MAN_WRITE; + e->ReqCh = 0; + e->X->PLength = length; + e->X->P = (byte*)pVar; + + pC->request_pending = 1; + + return (0); + } + + return (-1); +} + +int SuperTraceExecuteRequest (void* AdapterHandle, + const char* name, + byte* data) { + diva_maint_client_t* pC = (diva_maint_client_t*)AdapterHandle; + + if (pC && pC->pIdiLib && pC->request) { + ENTITY* e = (ENTITY*)(*(pC->pIdiLib->DivaSTraceGetHandle))(pC->pIdiLib->hLib); + byte* xdata = (byte*)&pC->xbuffer[0]; + word length; + + length = SuperTraceCreateReadReq (xdata, name); + single_p (xdata, &length, 0); /* End Of Message */ + + e->Req = MAN_EXECUTE; + e->ReqCh = 0; + e->X->PLength = length; + e->X->P = (byte*)xdata; + + pC->request_pending = 1; + + return (0); + } + + return (-1); +} + +static word SuperTraceCreateReadReq (byte* P, const char* path) { + byte var_length; + byte* plen; + + var_length = (byte)strlen (path); + + *P++ = ESC; + plen = P++; + *P++ = 0x80; /* MAN_IE */ + *P++ = 0x00; /* Type */ + *P++ = 0x00; /* Attribute */ + *P++ = 0x00; /* Status */ + *P++ = 0x00; /* Variable Length */ + *P++ = var_length; + memcpy (P, path, var_length); + P += var_length; + *plen = var_length + 0x06; + + return ((word)(var_length + 0x08)); +} + +static void single_p (byte * P, word * PLength, byte Id) { + P[(*PLength)++] = Id; +} + +static void diva_maint_xdi_cb (ENTITY* e) { + diva_strace_context_t* pLib = DIVAS_CONTAINING_RECORD(e,diva_strace_context_t,e); + diva_maint_client_t* pC; + diva_os_spin_lock_magic_t old_irql, old_irql1; + + + diva_os_enter_spin_lock (&dbg_adapter_lock, &old_irql1, "xdi_cb"); + diva_os_enter_spin_lock (&dbg_q_lock, &old_irql, "xdi_cb"); + + pC = (diva_maint_client_t*)pLib->hAdapter; + + if ((e->complete == 255) || (pC->dma_handle < 0)) { + if ((*(pLib->instance.DivaSTraceMessageInput))(&pLib->instance)) { + diva_mnt_internal_dprintf (0, DLI_ERR, "Trace internal library error"); + } + } else { + /* + Process combined management interface indication + */ + if ((*(pLib->instance.DivaSTraceMessageInput))(&pLib->instance)) { + diva_mnt_internal_dprintf (0, DLI_ERR, "Trace internal library error (DMA mode)"); + } + } + + diva_os_leave_spin_lock (&dbg_q_lock, &old_irql, "xdi_cb"); + + + if (pC->request_pending) { + pC->request_pending = 0; + (*(pC->request))(e); + } + + diva_os_leave_spin_lock (&dbg_adapter_lock, &old_irql1, "xdi_cb"); +} + + +static void diva_maint_error (void* user_context, + diva_strace_library_interface_t* hLib, + int Adapter, + int error, + const char* file, + int line) { + diva_mnt_internal_dprintf (0, DLI_ERR, + "Trace library error(%d) A(%d) %s %d", error, Adapter, file, line); +} + +static void print_ie (diva_trace_ie_t* ie, char* buffer, int length) { + int i; + + buffer[0] = 0; + + if (length > 32) { + for (i = 0; ((i < ie->length) && (length > 3)); i++) { + sprintf (buffer, "%02x", ie->data[i]); + buffer += 2; + length -= 2; + if (i < (ie->length-1)) { + strcpy (buffer, " "); + buffer++; + length--; + } + } + } +} + +static void diva_maint_state_change_notify (void* user_context, + diva_strace_library_interface_t* hLib, + int Adapter, + diva_trace_line_state_t* channel, + int notify_subject) { + diva_maint_client_t* pC = (diva_maint_client_t*)user_context; + diva_trace_fax_state_t* fax = &channel->fax; + diva_trace_modem_state_t* modem = &channel->modem; + char tmp[256]; + + if (!pC->hDbg) { + return; + } + + switch (notify_subject) { + case DIVA_SUPER_TRACE_NOTIFY_LINE_CHANGE: { + int view = (TraceFilter[0] == 0); + /* + Process selective Trace + */ + if (channel->Line[0] == 'I' && channel->Line[1] == 'd' && + channel->Line[2] == 'l' && channel->Line[3] == 'e') { + if ((TraceFilterIdent == pC->hDbg->id) && (TraceFilterChannel == (int)channel->ChannelNumber)) { + (*(hLib->DivaSTraceSetBChannel))(hLib, (int)channel->ChannelNumber, 0); + (*(hLib->DivaSTraceSetAudioTap))(hLib, (int)channel->ChannelNumber, 0); + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, "Selective Trace OFF for Ch=%d", + (int)channel->ChannelNumber); + TraceFilterIdent = -1; + TraceFilterChannel = -1; + view = 1; + } + } else if (TraceFilter[0] && (TraceFilterIdent < 0) && !(diva_mnt_cmp_nmbr (&channel->RemoteAddress[0]) && + diva_mnt_cmp_nmbr (&channel->LocalAddress[0]))) { + + if ((pC->hDbg->dbgMask & DIVA_MGT_DBG_IFC_BCHANNEL) != 0) { /* Activate B-channel trace */ + (*(hLib->DivaSTraceSetBChannel))(hLib, (int)channel->ChannelNumber, 1); + } + if ((pC->hDbg->dbgMask & DIVA_MGT_DBG_IFC_AUDIO) != 0) { /* Activate AudioTap Trace */ + (*(hLib->DivaSTraceSetAudioTap))(hLib, (int)channel->ChannelNumber, 1); + } + + TraceFilterIdent = pC->hDbg->id; + TraceFilterChannel = (int)channel->ChannelNumber; + + if (TraceFilterIdent >= 0) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, "Selective Trace ON for Ch=%d", + (int)channel->ChannelNumber); + view = 1; + } + } + if (view && (pC->hDbg->dbgMask & DIVA_MGT_DBG_LINE_EVENTS)) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_STAT, "L Ch = %d", + (int)channel->ChannelNumber); + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_STAT, "L Status = <%s>", &channel->Line[0]); + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_STAT, "L Layer1 = <%s>", &channel->Framing[0]); + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_STAT, "L Layer2 = <%s>", &channel->Layer2[0]); + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_STAT, "L Layer3 = <%s>", &channel->Layer3[0]); + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_STAT, "L RAddr = <%s>", + &channel->RemoteAddress[0]); + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_STAT, "L RSAddr = <%s>", + &channel->RemoteSubAddress[0]); + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_STAT, "L LAddr = <%s>", + &channel->LocalAddress[0]); + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_STAT, "L LSAddr = <%s>", + &channel->LocalSubAddress[0]); + print_ie(&channel->call_BC, tmp, sizeof(tmp)); + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_STAT, "L BC = <%s>", tmp); + print_ie(&channel->call_HLC, tmp, sizeof(tmp)); + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_STAT, "L HLC = <%s>", tmp); + print_ie(&channel->call_LLC, tmp, sizeof(tmp)); + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_STAT, "L LLC = <%s>", tmp); + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_STAT, "L CR = 0x%x", channel->CallReference); + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_STAT, "L Disc = 0x%x", + channel->LastDisconnecCause); + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_STAT, "L Owner = <%s>", &channel->UserID[0]); + } + + } break; + + case DIVA_SUPER_TRACE_NOTIFY_MODEM_CHANGE: + if (pC->hDbg->dbgMask & DIVA_MGT_DBG_MDM_PROGRESS) { + { + int ch = TraceFilterChannel; + int id = TraceFilterIdent; + + if ((id >= 0) && (ch >= 0) && (id < sizeof(clients)/sizeof(clients[0])) && + (clients[id].Dbg.id == (byte)id) && (clients[id].pIdiLib == hLib)) { + if (ch != (int)modem->ChannelNumber) { + break; + } + } else if (TraceFilter[0] != 0) { + break; + } + } + + + diva_mnt_internal_dprintf(pC->hDbg->id, DLI_STAT, "MDM Ch = %lu", + (int)modem->ChannelNumber); + diva_mnt_internal_dprintf(pC->hDbg->id, DLI_STAT, "MDM Event = %lu", modem->Event); + diva_mnt_internal_dprintf(pC->hDbg->id, DLI_STAT, "MDM Norm = %lu", modem->Norm); + diva_mnt_internal_dprintf(pC->hDbg->id, DLI_STAT, "MDM Opts. = 0x%08x", modem->Options); + diva_mnt_internal_dprintf(pC->hDbg->id, DLI_STAT, "MDM Tx = %lu Bps", modem->TxSpeed); + diva_mnt_internal_dprintf(pC->hDbg->id, DLI_STAT, "MDM Rx = %lu Bps", modem->RxSpeed); + diva_mnt_internal_dprintf(pC->hDbg->id, DLI_STAT, "MDM RT = %lu mSec", + modem->RoundtripMsec); + diva_mnt_internal_dprintf(pC->hDbg->id, DLI_STAT, "MDM Sr = %lu", modem->SymbolRate); + diva_mnt_internal_dprintf(pC->hDbg->id, DLI_STAT, "MDM Rxl = %d dBm", modem->RxLeveldBm); + diva_mnt_internal_dprintf(pC->hDbg->id, DLI_STAT, "MDM El = %d dBm", modem->EchoLeveldBm); + diva_mnt_internal_dprintf(pC->hDbg->id, DLI_STAT, "MDM SNR = %lu dB", modem->SNRdb); + diva_mnt_internal_dprintf(pC->hDbg->id, DLI_STAT, "MDM MAE = %lu", modem->MAE); + diva_mnt_internal_dprintf(pC->hDbg->id, DLI_STAT, "MDM LRet = %lu", + modem->LocalRetrains); + diva_mnt_internal_dprintf(pC->hDbg->id, DLI_STAT, "MDM RRet = %lu", + modem->RemoteRetrains); + diva_mnt_internal_dprintf(pC->hDbg->id, DLI_STAT, "MDM LRes = %lu", modem->LocalResyncs); + diva_mnt_internal_dprintf(pC->hDbg->id, DLI_STAT, "MDM RRes = %lu", + modem->RemoteResyncs); + if (modem->Event == 3) { + diva_mnt_internal_dprintf(pC->hDbg->id,DLI_STAT,"MDM Disc = %lu", modem->DiscReason); + } + } + if ((modem->Event == 3) && (pC->hDbg->dbgMask & DIVA_MGT_DBG_MDM_STATISTICS)) { + (*(pC->pIdiLib->DivaSTraceGetModemStatistics))(pC->pIdiLib); + } + break; + + case DIVA_SUPER_TRACE_NOTIFY_FAX_CHANGE: + if (pC->hDbg->dbgMask & DIVA_MGT_DBG_FAX_PROGRESS) { + { + int ch = TraceFilterChannel; + int id = TraceFilterIdent; + + if ((id >= 0) && (ch >= 0) && (id < sizeof(clients)/sizeof(clients[0])) && + (clients[id].Dbg.id == (byte)id) && (clients[id].pIdiLib == hLib)) { + if (ch != (int)fax->ChannelNumber) { + break; + } + } else if (TraceFilter[0] != 0) { + break; + } + } + + diva_mnt_internal_dprintf(pC->hDbg->id, DLI_STAT, "FAX Ch = %lu",(int)fax->ChannelNumber); + diva_mnt_internal_dprintf(pC->hDbg->id, DLI_STAT, "FAX Event = %lu", fax->Event); + diva_mnt_internal_dprintf(pC->hDbg->id, DLI_STAT, "FAX Pages = %lu", fax->Page_Counter); + diva_mnt_internal_dprintf(pC->hDbg->id, DLI_STAT, "FAX Feat. = 0x%08x", fax->Features); + diva_mnt_internal_dprintf(pC->hDbg->id, DLI_STAT, "FAX ID = <%s>", &fax->Station_ID[0]); + diva_mnt_internal_dprintf(pC->hDbg->id, DLI_STAT, "FAX Saddr = <%s>", &fax->Subaddress[0]); + diva_mnt_internal_dprintf(pC->hDbg->id, DLI_STAT, "FAX Pwd = <%s>", &fax->Password[0]); + diva_mnt_internal_dprintf(pC->hDbg->id, DLI_STAT, "FAX Speed = %lu", fax->Speed); + diva_mnt_internal_dprintf(pC->hDbg->id, DLI_STAT, "FAX Res. = 0x%08x", fax->Resolution); + diva_mnt_internal_dprintf(pC->hDbg->id, DLI_STAT, "FAX Width = %lu", fax->Paper_Width); + diva_mnt_internal_dprintf(pC->hDbg->id, DLI_STAT, "FAX Length= %lu", fax->Paper_Length); + diva_mnt_internal_dprintf(pC->hDbg->id, DLI_STAT, "FAX SLT = %lu", fax->Scanline_Time); + if (fax->Event == 3) { + diva_mnt_internal_dprintf(pC->hDbg->id, DLI_STAT, "FAX Disc = %lu", fax->Disc_Reason); + } + } + if ((fax->Event == 3) && (pC->hDbg->dbgMask & DIVA_MGT_DBG_FAX_STATISTICS)) { + (*(pC->pIdiLib->DivaSTraceGetFaxStatistics))(pC->pIdiLib); + } + break; + + case DIVA_SUPER_TRACE_INTERFACE_CHANGE: + if (pC->hDbg->dbgMask & DIVA_MGT_DBG_IFC_EVENTS) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_STAT, + "Layer 1 -> [%s]", channel->pInterface->Layer1); + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_STAT, + "Layer 2 -> [%s]", channel->pInterface->Layer2); + } + break; + + case DIVA_SUPER_TRACE_NOTIFY_STAT_CHANGE: + if (pC->hDbg->dbgMask & DIVA_MGT_DBG_IFC_STATISTICS) { + /* + Incoming Statistics + */ + if (channel->pInterfaceStat->inc.Calls) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "Inc Calls =%lu", channel->pInterfaceStat->inc.Calls); + } + if (channel->pInterfaceStat->inc.Connected) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "Inc Connected =%lu", channel->pInterfaceStat->inc.Connected); + } + if (channel->pInterfaceStat->inc.User_Busy) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "Inc Busy =%lu", channel->pInterfaceStat->inc.User_Busy); + } + if (channel->pInterfaceStat->inc.Call_Rejected) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "Inc Rejected =%lu", channel->pInterfaceStat->inc.Call_Rejected); + } + if (channel->pInterfaceStat->inc.Wrong_Number) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "Inc Wrong Nr =%lu", channel->pInterfaceStat->inc.Wrong_Number); + } + if (channel->pInterfaceStat->inc.Incompatible_Dst) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "Inc Incomp. Dest =%lu", channel->pInterfaceStat->inc.Incompatible_Dst); + } + if (channel->pInterfaceStat->inc.Out_of_Order) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "Inc Out of Order =%lu", channel->pInterfaceStat->inc.Out_of_Order); + } + if (channel->pInterfaceStat->inc.Ignored) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "Inc Ignored =%lu", channel->pInterfaceStat->inc.Ignored); + } + + /* + Outgoing Statistics + */ + if (channel->pInterfaceStat->outg.Calls) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "Outg Calls =%lu", channel->pInterfaceStat->outg.Calls); + } + if (channel->pInterfaceStat->outg.Connected) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "Outg Connected =%lu", channel->pInterfaceStat->outg.Connected); + } + if (channel->pInterfaceStat->outg.User_Busy) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "Outg Busy =%lu", channel->pInterfaceStat->outg.User_Busy); + } + if (channel->pInterfaceStat->outg.No_Answer) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "Outg No Answer =%lu", channel->pInterfaceStat->outg.No_Answer); + } + if (channel->pInterfaceStat->outg.Wrong_Number) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "Outg Wrong Nr =%lu", channel->pInterfaceStat->outg.Wrong_Number); + } + if (channel->pInterfaceStat->outg.Call_Rejected) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "Outg Rejected =%lu", channel->pInterfaceStat->outg.Call_Rejected); + } + if (channel->pInterfaceStat->outg.Other_Failures) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "Outg Other Failures =%lu", channel->pInterfaceStat->outg.Other_Failures); + } + } + break; + + case DIVA_SUPER_TRACE_NOTIFY_MDM_STAT_CHANGE: + if (channel->pInterfaceStat->mdm.Disc_Normal) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "MDM Disc Normal = %lu", channel->pInterfaceStat->mdm.Disc_Normal); + } + if (channel->pInterfaceStat->mdm.Disc_Unspecified) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "MDM Disc Unsp. = %lu", channel->pInterfaceStat->mdm.Disc_Unspecified); + } + if (channel->pInterfaceStat->mdm.Disc_Busy_Tone) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "MDM Disc Busy Tone = %lu", channel->pInterfaceStat->mdm.Disc_Busy_Tone); + } + if (channel->pInterfaceStat->mdm.Disc_Congestion) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "MDM Disc Congestion = %lu", channel->pInterfaceStat->mdm.Disc_Congestion); + } + if (channel->pInterfaceStat->mdm.Disc_Carr_Wait) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "MDM Disc Carrier Wait = %lu", channel->pInterfaceStat->mdm.Disc_Carr_Wait); + } + if (channel->pInterfaceStat->mdm.Disc_Trn_Timeout) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "MDM Disc Trn. T.o. = %lu", channel->pInterfaceStat->mdm.Disc_Trn_Timeout); + } + if (channel->pInterfaceStat->mdm.Disc_Incompat) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "MDM Disc Incompatible = %lu", channel->pInterfaceStat->mdm.Disc_Incompat); + } + if (channel->pInterfaceStat->mdm.Disc_Frame_Rej) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "MDM Disc Frame Reject = %lu", channel->pInterfaceStat->mdm.Disc_Frame_Rej); + } + if (channel->pInterfaceStat->mdm.Disc_V42bis) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "MDM Disc V.42bis = %lu", channel->pInterfaceStat->mdm.Disc_V42bis); + } + break; + + case DIVA_SUPER_TRACE_NOTIFY_FAX_STAT_CHANGE: + if (channel->pInterfaceStat->fax.Disc_Normal) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "FAX Disc Normal = %lu", channel->pInterfaceStat->fax.Disc_Normal); + } + if (channel->pInterfaceStat->fax.Disc_Not_Ident) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "FAX Disc Not Ident. = %lu", channel->pInterfaceStat->fax.Disc_Not_Ident); + } + if (channel->pInterfaceStat->fax.Disc_No_Response) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "FAX Disc No Response = %lu", channel->pInterfaceStat->fax.Disc_No_Response); + } + if (channel->pInterfaceStat->fax.Disc_Retries) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "FAX Disc Max Retries = %lu", channel->pInterfaceStat->fax.Disc_Retries); + } + if (channel->pInterfaceStat->fax.Disc_Unexp_Msg) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "FAX Unexp. Msg. = %lu", channel->pInterfaceStat->fax.Disc_Unexp_Msg); + } + if (channel->pInterfaceStat->fax.Disc_No_Polling) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "FAX Disc No Polling = %lu", channel->pInterfaceStat->fax.Disc_No_Polling); + } + if (channel->pInterfaceStat->fax.Disc_Training) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "FAX Disc Training = %lu", channel->pInterfaceStat->fax.Disc_Training); + } + if (channel->pInterfaceStat->fax.Disc_Unexpected) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "FAX Disc Unexpected = %lu", channel->pInterfaceStat->fax.Disc_Unexpected); + } + if (channel->pInterfaceStat->fax.Disc_Application) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "FAX Disc Application = %lu", channel->pInterfaceStat->fax.Disc_Application); + } + if (channel->pInterfaceStat->fax.Disc_Incompat) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "FAX Disc Incompatible = %lu", channel->pInterfaceStat->fax.Disc_Incompat); + } + if (channel->pInterfaceStat->fax.Disc_No_Command) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "FAX Disc No Command = %lu", channel->pInterfaceStat->fax.Disc_No_Command); + } + if (channel->pInterfaceStat->fax.Disc_Long_Msg) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "FAX Disc Long Msg. = %lu", channel->pInterfaceStat->fax.Disc_Long_Msg); + } + if (channel->pInterfaceStat->fax.Disc_Supervisor) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "FAX Disc Supervisor = %lu", channel->pInterfaceStat->fax.Disc_Supervisor); + } + if (channel->pInterfaceStat->fax.Disc_SUB_SEP_PWD) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "FAX Disc SUP SEP PWD = %lu", channel->pInterfaceStat->fax.Disc_SUB_SEP_PWD); + } + if (channel->pInterfaceStat->fax.Disc_Invalid_Msg) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "FAX Disc Invalid Msg. = %lu", channel->pInterfaceStat->fax.Disc_Invalid_Msg); + } + if (channel->pInterfaceStat->fax.Disc_Page_Coding) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "FAX Disc Page Coding = %lu", channel->pInterfaceStat->fax.Disc_Page_Coding); + } + if (channel->pInterfaceStat->fax.Disc_App_Timeout) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "FAX Disc Appl. T.o. = %lu", channel->pInterfaceStat->fax.Disc_App_Timeout); + } + if (channel->pInterfaceStat->fax.Disc_Unspecified) { + diva_mnt_internal_dprintf (pC->hDbg->id, DLI_LOG, + "FAX Disc Unspec. = %lu", channel->pInterfaceStat->fax.Disc_Unspecified); + } + break; + } +} + +/* + Receive trace information from the Management Interface and store it in the + internal trace buffer with MSG_TYPE_MLOG as is, without any filtering. + Event Filtering and formatting is done in Management Interface self. + */ +static void diva_maint_trace_notify (void* user_context, + diva_strace_library_interface_t* hLib, + int Adapter, + void* xlog_buffer, + int length) { + diva_maint_client_t* pC = (diva_maint_client_t*)user_context; + diva_dbg_entry_head_t* pmsg; + word size; + dword sec, usec; + int ch = TraceFilterChannel; + int id = TraceFilterIdent; + + /* + Selective trace + */ + if ((id >= 0) && (ch >= 0) && (id < sizeof(clients)/sizeof(clients[0])) && + (clients[id].Dbg.id == (byte)id) && (clients[id].pIdiLib == hLib)) { + const char* p = NULL; + int ch_value = -1; + MI_XLOG_HDR *TrcData = (MI_XLOG_HDR *)xlog_buffer; + + if (Adapter != clients[id].logical) { + return; /* Ignore all trace messages from other adapters */ + } + + if (TrcData->code == 24) { + p = (char*)&TrcData->code; + p += 2; + } + + /* + All L1 messages start as [dsp,ch], so we can filter this information + and filter out all messages that use different channel + */ + if (p && p[0] == '[') { + if (p[2] == ',') { + p += 3; + ch_value = *p - '0'; + } else if (p[3] == ',') { + p += 4; + ch_value = *p - '0'; + } + if (ch_value >= 0) { + if (p[2] == ']') { + ch_value = ch_value * 10 + p[1] - '0'; + } + if (ch_value != ch) { + return; /* Ignore other channels */ + } + } + } + + } else if (TraceFilter[0] != 0) { + return; /* Ignore trace if trace filter is activated, but idle */ + } + + diva_os_get_time (&sec, &usec); + + while (!(pmsg = (diva_dbg_entry_head_t*)queueAllocMsg (dbg_queue, + (word)length+sizeof(*pmsg)))) { + if ((pmsg = (diva_dbg_entry_head_t*)queuePeekMsg (dbg_queue, &size))) { + queueFreeMsg (dbg_queue); + } else { + break; + } + } + if (pmsg) { + memcpy (&pmsg[1], xlog_buffer, length); + pmsg->sequence = dbg_sequence++; + pmsg->time_sec = sec; + pmsg->time_usec = usec; + pmsg->facility = MSG_TYPE_MLOG; + pmsg->dli = pC->logical; + pmsg->drv_id = pC->hDbg->id; + pmsg->di_cpu = 0; + pmsg->data_length = length; + queueCompleteMsg (pmsg); + if (queueCount(dbg_queue)) { + diva_maint_wakeup_read(); + } + } +} + + +/* + Convert MAINT trace mask to management interface trace mask/work/facility and + issue command to management interface + */ +static void diva_change_management_debug_mask (diva_maint_client_t* pC, dword old_mask) { + if (pC->request && pC->hDbg && pC->pIdiLib) { + dword changed = pC->hDbg->dbgMask ^ old_mask; + + if (changed & DIVA_MGT_DBG_TRACE) { + (*(pC->pIdiLib->DivaSTraceSetInfo))(pC->pIdiLib, + (pC->hDbg->dbgMask & DIVA_MGT_DBG_TRACE) != 0); + } + if (changed & DIVA_MGT_DBG_DCHAN) { + (*(pC->pIdiLib->DivaSTraceSetDChannel))(pC->pIdiLib, + (pC->hDbg->dbgMask & DIVA_MGT_DBG_DCHAN) != 0); + } + if (!TraceFilter[0]) { + if (changed & DIVA_MGT_DBG_IFC_BCHANNEL) { + int i, state = ((pC->hDbg->dbgMask & DIVA_MGT_DBG_IFC_BCHANNEL) != 0); + + for (i = 0; i < pC->channels; i++) { + (*(pC->pIdiLib->DivaSTraceSetBChannel))(pC->pIdiLib, i+1, state); + } + } + if (changed & DIVA_MGT_DBG_IFC_AUDIO) { + int i, state = ((pC->hDbg->dbgMask & DIVA_MGT_DBG_IFC_AUDIO) != 0); + + for (i = 0; i < pC->channels; i++) { + (*(pC->pIdiLib->DivaSTraceSetAudioTap))(pC->pIdiLib, i+1, state); + } + } + } + } +} + + +void diva_mnt_internal_dprintf (dword drv_id, dword type, char* fmt, ...) { + va_list ap; + + va_start(ap, fmt); + DI_format (0, (word)drv_id, (int)type, fmt, ap); + va_end(ap); +} + +/* + Shutdown all adapters before driver removal + */ +int diva_mnt_shutdown_xdi_adapters (void) { + diva_os_spin_lock_magic_t old_irql, old_irql1; + int i, fret = 0; + byte * pmem; + + + for (i = 1; i < (sizeof(clients)/sizeof(clients[0])); i++) { + pmem = NULL; + + diva_os_enter_spin_lock (&dbg_adapter_lock, &old_irql1, "unload"); + diva_os_enter_spin_lock (&dbg_q_lock, &old_irql, "unload"); + + if (clients[i].hDbg && clients[i].pIdiLib && clients[i].request) { + if ((*(clients[i].pIdiLib->DivaSTraceLibraryStop))(clients[i].pIdiLib) == 1) { + /* + Adapter removal complete + */ + if (clients[i].pIdiLib) { + (*(clients[i].pIdiLib->DivaSTraceLibraryFinit))(clients[i].pIdiLib->hLib); + clients[i].pIdiLib = NULL; + + pmem = clients[i].pmem; + clients[i].pmem = NULL; + } + clients[i].hDbg = NULL; + clients[i].request_pending = 0; + + if (clients[i].dma_handle >= 0) { + /* + Free DMA handle + */ + diva_free_dma_descriptor (clients[i].request, clients[i].dma_handle); + clients[i].dma_handle = -1; + } + clients[i].request = NULL; + } else { + fret = -1; + } + } + + diva_os_leave_spin_lock (&dbg_q_lock, &old_irql, "unload"); + if (clients[i].hDbg && clients[i].pIdiLib && clients[i].request && clients[i].request_pending) { + clients[i].request_pending = 0; + (*(clients[i].request))((ENTITY*)(*(clients[i].pIdiLib->DivaSTraceGetHandle))(clients[i].pIdiLib->hLib)); + if (clients[i].dma_handle >= 0) { + diva_free_dma_descriptor (clients[i].request, clients[i].dma_handle); + clients[i].dma_handle = -1; + } + } + diva_os_leave_spin_lock (&dbg_adapter_lock, &old_irql1, "unload"); + + if (pmem) { + diva_os_free (0, pmem); + } + } + + return (fret); +} + +/* + Set/Read the trace filter used for selective tracing. + Affects B- and Audio Tap trace mask at run time + */ +int diva_set_trace_filter (int filter_length, const char* filter) { + diva_os_spin_lock_magic_t old_irql, old_irql1; + int i, ch, on, client_b_on, client_atap_on; + + diva_os_enter_spin_lock (&dbg_adapter_lock, &old_irql1, "dbg mask"); + diva_os_enter_spin_lock (&dbg_q_lock, &old_irql, "write_filter"); + + if (filter_length <= DIVA_MAX_SELECTIVE_FILTER_LENGTH) { + memcpy (&TraceFilter[0], filter, filter_length); + if (TraceFilter[filter_length]) { + TraceFilter[filter_length] = 0; + } + if (TraceFilter[0] == '*') { + TraceFilter[0] = 0; + } + } else { + filter_length = -1; + } + + TraceFilterIdent = -1; + TraceFilterChannel = -1; + + on = (TraceFilter[0] == 0); + + for (i = 1; i < (sizeof(clients)/sizeof(clients[0])); i++) { + if (clients[i].hDbg && clients[i].pIdiLib && clients[i].request) { + client_b_on = on && ((clients[i].hDbg->dbgMask & DIVA_MGT_DBG_IFC_BCHANNEL) != 0); + client_atap_on = on && ((clients[i].hDbg->dbgMask & DIVA_MGT_DBG_IFC_AUDIO) != 0); + for (ch = 0; ch < clients[i].channels; ch++) { + (*(clients[i].pIdiLib->DivaSTraceSetBChannel))(clients[i].pIdiLib->hLib, ch+1, client_b_on); + (*(clients[i].pIdiLib->DivaSTraceSetAudioTap))(clients[i].pIdiLib->hLib, ch+1, client_atap_on); + } + } + } + + for (i = 1; i < (sizeof(clients)/sizeof(clients[0])); i++) { + if (clients[i].hDbg && clients[i].pIdiLib && clients[i].request && clients[i].request_pending) { + diva_os_leave_spin_lock (&dbg_q_lock, &old_irql, "write_filter"); + clients[i].request_pending = 0; + (*(clients[i].request))((ENTITY*)(*(clients[i].pIdiLib->DivaSTraceGetHandle))(clients[i].pIdiLib->hLib)); + diva_os_enter_spin_lock (&dbg_q_lock, &old_irql, "write_filter"); + } + } + + diva_os_leave_spin_lock (&dbg_q_lock, &old_irql, "write_filter"); + diva_os_leave_spin_lock (&dbg_adapter_lock, &old_irql1, "dbg mask"); + + return (filter_length); +} + +int diva_get_trace_filter (int max_length, char* filter) { + diva_os_spin_lock_magic_t old_irql; + int len; + + diva_os_enter_spin_lock (&dbg_q_lock, &old_irql, "read_filter"); + len = strlen (&TraceFilter[0]) + 1; + if (max_length >= len) { + memcpy (filter, &TraceFilter[0], len); + } + diva_os_leave_spin_lock (&dbg_q_lock, &old_irql, "read_filter"); + + return (len); +} + +static int diva_dbg_cmp_key (const char* ref, const char* key) { + while (*key && (*ref++ == *key++)); + return (!*key && !*ref); +} + +/* + In case trace filter starts with "C" character then + all following characters are interpreted as command. + Followings commands are available: + - single, trace single call at time, independent from CPN/CiPN + */ +static int diva_mnt_cmp_nmbr (const char* nmbr) { + const char* ref = &TraceFilter[0]; + int ref_len = strlen(&TraceFilter[0]), nmbr_len = strlen(nmbr); + + if (ref[0] == 'C') { + if (diva_dbg_cmp_key (&ref[1], "single")) { + return (0); + } + return (-1); + } + + if (!ref_len || (ref_len > nmbr_len)) { + return (-1); + } + + nmbr = nmbr + nmbr_len - 1; + ref = ref + ref_len - 1; + + while (ref_len--) { + if (*nmbr-- != *ref--) { + return (-1); + } + } + + return (0); +} + +static int diva_get_dma_descriptor (IDI_CALL request, dword *dma_magic) { + ENTITY e; + IDI_SYNC_REQ* pReq = (IDI_SYNC_REQ*)&e; + + if (!request) { + return (-1); + } + + pReq->xdi_dma_descriptor_operation.Req = 0; + pReq->xdi_dma_descriptor_operation.Rc = IDI_SYNC_REQ_DMA_DESCRIPTOR_OPERATION; + + pReq->xdi_dma_descriptor_operation.info.operation = IDI_SYNC_REQ_DMA_DESCRIPTOR_ALLOC; + pReq->xdi_dma_descriptor_operation.info.descriptor_number = -1; + pReq->xdi_dma_descriptor_operation.info.descriptor_address = NULL; + pReq->xdi_dma_descriptor_operation.info.descriptor_magic = 0; + + (*request)((ENTITY*)pReq); + + if (!pReq->xdi_dma_descriptor_operation.info.operation && + (pReq->xdi_dma_descriptor_operation.info.descriptor_number >= 0) && + pReq->xdi_dma_descriptor_operation.info.descriptor_magic) { + *dma_magic = pReq->xdi_dma_descriptor_operation.info.descriptor_magic; + return (pReq->xdi_dma_descriptor_operation.info.descriptor_number); + } else { + return (-1); + } +} + +static void diva_free_dma_descriptor (IDI_CALL request, int nr) { + ENTITY e; + IDI_SYNC_REQ* pReq = (IDI_SYNC_REQ*)&e; + + if (!request || (nr < 0)) { + return; + } + + pReq->xdi_dma_descriptor_operation.Req = 0; + pReq->xdi_dma_descriptor_operation.Rc = IDI_SYNC_REQ_DMA_DESCRIPTOR_OPERATION; + + pReq->xdi_dma_descriptor_operation.info.operation = IDI_SYNC_REQ_DMA_DESCRIPTOR_FREE; + pReq->xdi_dma_descriptor_operation.info.descriptor_number = nr; + pReq->xdi_dma_descriptor_operation.info.descriptor_address = NULL; + pReq->xdi_dma_descriptor_operation.info.descriptor_magic = 0; + + (*request)((ENTITY*)pReq); +} + diff --git a/drivers/isdn/hardware/eicon/debug_if.h b/drivers/isdn/hardware/eicon/debug_if.h new file mode 100644 index 000000000000..4db739d5803c --- /dev/null +++ b/drivers/isdn/hardware/eicon/debug_if.h @@ -0,0 +1,90 @@ +/* + * + Copyright (c) Eicon Technology Corporation, 2000. + * + This source file is supplied for the use with Eicon + Technology Corporation's range of DIVA Server Adapters. + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +#ifndef __DIVA_DEBUG_IF_H__ +#define __DIVA_DEBUG_IF_H__ +#define MSG_TYPE_DRV_ID 0x0001 +#define MSG_TYPE_FLAGS 0x0002 +#define MSG_TYPE_STRING 0x0003 +#define MSG_TYPE_BINARY 0x0004 +#define MSG_TYPE_MLOG 0x0005 + +#define MSG_FRAME_MAX_SIZE 2150 + +typedef struct _diva_dbg_entry_head { + dword sequence; + dword time_sec; + dword time_usec; + dword facility; + dword dli; + dword drv_id; + dword di_cpu; + dword data_length; +} diva_dbg_entry_head_t; + +int diva_maint_init (byte* base, unsigned long length, int do_init); +void* diva_maint_finit (void); +dword diva_dbg_q_length (void); +diva_dbg_entry_head_t* diva_maint_get_message (word* size, + diva_os_spin_lock_magic_t* old_irql); +void diva_maint_ack_message (int do_release, + diva_os_spin_lock_magic_t* old_irql); +void diva_maint_prtComp (char *format, ...); +void diva_maint_wakeup_read (void); +int diva_get_driver_info (dword id, byte* data, int data_length); +int diva_get_driver_dbg_mask (dword id, byte* data); +int diva_set_driver_dbg_mask (dword id, dword mask); +void diva_mnt_remove_xdi_adapter (const DESCRIPTOR* d); +void diva_mnt_add_xdi_adapter (const DESCRIPTOR* d); +int diva_mnt_shutdown_xdi_adapters (void); + +#define DIVA_MAX_SELECTIVE_FILTER_LENGTH 127 +int diva_set_trace_filter (int filter_length, const char* filter); +int diva_get_trace_filter (int max_length, char* filter); + + +#define DITRACE_CMD_GET_DRIVER_INFO 1 +#define DITRACE_READ_DRIVER_DBG_MASK 2 +#define DITRACE_WRITE_DRIVER_DBG_MASK 3 +#define DITRACE_READ_TRACE_ENTRY 4 +#define DITRACE_READ_TRACE_ENTRYS 5 +#define DITRACE_WRITE_SELECTIVE_TRACE_FILTER 6 +#define DITRACE_READ_SELECTIVE_TRACE_FILTER 7 + +/* + Trace lavels for debug via management interface + */ +#define DIVA_MGT_DBG_TRACE 0x00000001 /* All trace messages from the card */ +#define DIVA_MGT_DBG_DCHAN 0x00000002 /* All D-channel relater trace messages */ +#define DIVA_MGT_DBG_MDM_PROGRESS 0x00000004 /* Modem progress events */ +#define DIVA_MGT_DBG_FAX_PROGRESS 0x00000008 /* Fax progress events */ +#define DIVA_MGT_DBG_IFC_STATISTICS 0x00000010 /* Interface call statistics */ +#define DIVA_MGT_DBG_MDM_STATISTICS 0x00000020 /* Global modem statistics */ +#define DIVA_MGT_DBG_FAX_STATISTICS 0x00000040 /* Global call statistics */ +#define DIVA_MGT_DBG_LINE_EVENTS 0x00000080 /* Line state events */ +#define DIVA_MGT_DBG_IFC_EVENTS 0x00000100 /* Interface/L1/L2 state events */ +#define DIVA_MGT_DBG_IFC_BCHANNEL 0x00000200 /* B-Channel trace for all channels */ +#define DIVA_MGT_DBG_IFC_AUDIO 0x00000400 /* Audio Tap trace for all channels */ + +# endif /* DEBUG_IF___H */ + + diff --git a/drivers/isdn/hardware/eicon/debuglib.c b/drivers/isdn/hardware/eicon/debuglib.c new file mode 100644 index 000000000000..a19b7ffe9ace --- /dev/null +++ b/drivers/isdn/hardware/eicon/debuglib.c @@ -0,0 +1,156 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ + +#include "debuglib.h" + +#ifdef DIVA_NO_DEBUGLIB +static DIVA_DI_PRINTF dprintf; +#else /* DIVA_NO_DEBUGLIB */ + +_DbgHandle_ myDriverDebugHandle = { 0 /*!Registered*/, DBG_HANDLE_VERSION }; +DIVA_DI_PRINTF dprintf = no_printf; +/*****************************************************************************/ +#define DBG_FUNC(name) \ +void \ +myDbgPrint_##name (char *format, ...) \ +{ va_list ap ; \ + if ( myDriverDebugHandle.dbg_prt ) \ + { va_start (ap, format) ; \ + (myDriverDebugHandle.dbg_prt) \ + (myDriverDebugHandle.id, DLI_##name, format, ap) ; \ + va_end (ap) ; \ +} } +DBG_FUNC(LOG) +DBG_FUNC(FTL) +DBG_FUNC(ERR) +DBG_FUNC(TRC) +DBG_FUNC(MXLOG) +DBG_FUNC(FTL_MXLOG) +void +myDbgPrint_EVL (long msgID, ...) +{ va_list ap ; + if ( myDriverDebugHandle.dbg_ev ) + { va_start (ap, msgID) ; + (myDriverDebugHandle.dbg_ev) + (myDriverDebugHandle.id, (unsigned long)msgID, ap) ; + va_end (ap) ; +} } +DBG_FUNC(REG) +DBG_FUNC(MEM) +DBG_FUNC(SPL) +DBG_FUNC(IRP) +DBG_FUNC(TIM) +DBG_FUNC(BLK) +DBG_FUNC(TAPI) +DBG_FUNC(NDIS) +DBG_FUNC(CONN) +DBG_FUNC(STAT) +DBG_FUNC(SEND) +DBG_FUNC(RECV) +DBG_FUNC(PRV0) +DBG_FUNC(PRV1) +DBG_FUNC(PRV2) +DBG_FUNC(PRV3) +/*****************************************************************************/ +int +DbgRegister (char *drvName, char *drvTag, unsigned long dbgMask) +{ + int len; +/* + * deregister (if already registered) and zero out myDriverDebugHandle + */ + DbgDeregister () ; +/* + * initialize the debug handle + */ + myDriverDebugHandle.Version = DBG_HANDLE_VERSION ; + myDriverDebugHandle.id = -1 ; + myDriverDebugHandle.dbgMask = dbgMask | (DL_EVL | DL_FTL | DL_LOG) ; + len = strlen (drvName) ; + memcpy (myDriverDebugHandle.drvName, drvName, + (len < sizeof(myDriverDebugHandle.drvName)) ? + len : sizeof(myDriverDebugHandle.drvName) - 1) ; + len = strlen (drvTag) ; + memcpy (myDriverDebugHandle.drvTag, drvTag, + (len < sizeof(myDriverDebugHandle.drvTag)) ? + len : sizeof(myDriverDebugHandle.drvTag) - 1) ; +/* + * Try to register debugging via old (and only) interface + */ + dprintf("\000\377", &myDriverDebugHandle) ; + if ( myDriverDebugHandle.dbg_prt ) + { + return (1) ; + } +/* + * Check if we registered whith an old maint driver (see debuglib.h) + */ + if ( myDriverDebugHandle.dbg_end != NULL + /* location of 'dbg_prt' in _OldDbgHandle_ struct */ + && (myDriverDebugHandle.regTime.LowPart || + myDriverDebugHandle.regTime.HighPart ) ) + /* same location as in _OldDbgHandle_ struct */ + { + dprintf("%s: Cannot log to old maint driver !", drvName) ; + myDriverDebugHandle.dbg_end = + ((_OldDbgHandle_ *)&myDriverDebugHandle)->dbg_end ; + DbgDeregister () ; + } + return (0) ; +} +/*****************************************************************************/ +void +DbgSetLevel (unsigned long dbgMask) +{ + myDriverDebugHandle.dbgMask = dbgMask | (DL_EVL | DL_FTL | DL_LOG) ; +} +/*****************************************************************************/ +void +DbgDeregister (void) +{ + if ( myDriverDebugHandle.dbg_end ) + { + (myDriverDebugHandle.dbg_end)(&myDriverDebugHandle) ; + } + memset (&myDriverDebugHandle, 0, sizeof(myDriverDebugHandle)) ; +} +void xdi_dbg_xlog (char* x, ...) { + va_list ap; + va_start (ap, x); + if (myDriverDebugHandle.dbg_end && + (myDriverDebugHandle.dbg_irq || myDriverDebugHandle.dbg_old) && + (myDriverDebugHandle.dbgMask & DL_STAT)) { + if (myDriverDebugHandle.dbg_irq) { + (*(myDriverDebugHandle.dbg_irq))(myDriverDebugHandle.id, + (x[0] != 0) ? DLI_TRC : DLI_XLOG, x, ap); + } else { + (*(myDriverDebugHandle.dbg_old))(myDriverDebugHandle.id, x, ap); + } + } + va_end(ap); +} +/*****************************************************************************/ +#endif /* DIVA_NO_DEBUGLIB */ diff --git a/drivers/isdn/hardware/eicon/debuglib.h b/drivers/isdn/hardware/eicon/debuglib.h new file mode 100644 index 000000000000..11b3b9edd1d6 --- /dev/null +++ b/drivers/isdn/hardware/eicon/debuglib.h @@ -0,0 +1,322 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +#if !defined(__DEBUGLIB_H__) +#define __DEBUGLIB_H__ +#include <stdarg.h> +/* + * define global debug priorities + */ +#define DL_LOG 0x00000001 /* always worth mentioning */ +#define DL_FTL 0x00000002 /* always sampled error */ +#define DL_ERR 0x00000004 /* any kind of error */ +#define DL_TRC 0x00000008 /* verbose information */ +#define DL_XLOG 0x00000010 /* old xlog info */ +#define DL_MXLOG 0x00000020 /* maestra xlog info */ +#define DL_FTL_MXLOG 0x00000021 /* fatal maestra xlog info */ +#define DL_EVL 0x00000080 /* special NT eventlog msg */ +#define DL_COMPAT (DL_MXLOG | DL_XLOG) +#define DL_PRIOR_MASK (DL_EVL | DL_COMPAT | DL_TRC | DL_ERR | DL_FTL | DL_LOG) +#define DLI_LOG 0x0100 +#define DLI_FTL 0x0200 +#define DLI_ERR 0x0300 +#define DLI_TRC 0x0400 +#define DLI_XLOG 0x0500 +#define DLI_MXLOG 0x0600 +#define DLI_FTL_MXLOG 0x0600 +#define DLI_EVL 0x0800 +/* + * define OS (operating system interface) debuglevel + */ +#define DL_REG 0x00000100 /* init/query registry */ +#define DL_MEM 0x00000200 /* memory management */ +#define DL_SPL 0x00000400 /* event/spinlock handling */ +#define DL_IRP 0x00000800 /* I/O request handling */ +#define DL_TIM 0x00001000 /* timer/watchdog handling */ +#define DL_BLK 0x00002000 /* raw data block contents */ +#define DL_OS_MASK (DL_BLK | DL_TIM | DL_IRP | DL_SPL | DL_MEM | DL_REG) +#define DLI_REG 0x0900 +#define DLI_MEM 0x0A00 +#define DLI_SPL 0x0B00 +#define DLI_IRP 0x0C00 +#define DLI_TIM 0x0D00 +#define DLI_BLK 0x0E00 +/* + * define ISDN (connection interface) debuglevel + */ +#define DL_TAPI 0x00010000 /* debug TAPI interface */ +#define DL_NDIS 0x00020000 /* debug NDIS interface */ +#define DL_CONN 0x00040000 /* connection handling */ +#define DL_STAT 0x00080000 /* trace state machines */ +#define DL_SEND 0x00100000 /* trace raw xmitted data */ +#define DL_RECV 0x00200000 /* trace raw received data */ +#define DL_DATA (DL_SEND | DL_RECV) +#define DL_ISDN_MASK (DL_DATA | DL_STAT | DL_CONN | DL_NDIS | DL_TAPI) +#define DLI_TAPI 0x1100 +#define DLI_NDIS 0x1200 +#define DLI_CONN 0x1300 +#define DLI_STAT 0x1400 +#define DLI_SEND 0x1500 +#define DLI_RECV 0x1600 +/* + * define some private (unspecified) debuglevel + */ +#define DL_PRV0 0x01000000 +#define DL_PRV1 0x02000000 +#define DL_PRV2 0x04000000 +#define DL_PRV3 0x08000000 +#define DL_PRIV_MASK (DL_PRV0 | DL_PRV1 | DL_PRV2 | DL_PRV3) +#define DLI_PRV0 0x1900 +#define DLI_PRV1 0x1A00 +#define DLI_PRV2 0x1B00 +#define DLI_PRV3 0x1C00 +#define DT_INDEX(x) ((x) & 0x000F) +#define DL_INDEX(x) ((((x) >> 8) & 0x00FF) - 1) +#define DLI_NAME(x) ((x) & 0xFF00) +/* + * Debug mask for kernel mode tracing, if set the output is also sent to + * the system debug function. Requires that the project is compiled + * with _KERNEL_DBG_PRINT_ + */ +#define DL_TO_KERNEL 0x40000000 + +#ifdef DIVA_NO_DEBUGLIB +#define myDbgPrint_LOG(x...) do { } while(0); +#define myDbgPrint_FTL(x...) do { } while(0); +#define myDbgPrint_ERR(x...) do { } while(0); +#define myDbgPrint_TRC(x...) do { } while(0); +#define myDbgPrint_MXLOG(x...) do { } while(0); +#define myDbgPrint_EVL(x...) do { } while(0); +#define myDbgPrint_REG(x...) do { } while(0); +#define myDbgPrint_MEM(x...) do { } while(0); +#define myDbgPrint_SPL(x...) do { } while(0); +#define myDbgPrint_IRP(x...) do { } while(0); +#define myDbgPrint_TIM(x...) do { } while(0); +#define myDbgPrint_BLK(x...) do { } while(0); +#define myDbgPrint_TAPI(x...) do { } while(0); +#define myDbgPrint_NDIS(x...) do { } while(0); +#define myDbgPrint_CONN(x...) do { } while(0); +#define myDbgPrint_STAT(x...) do { } while(0); +#define myDbgPrint_SEND(x...) do { } while(0); +#define myDbgPrint_RECV(x...) do { } while(0); +#define myDbgPrint_PRV0(x...) do { } while(0); +#define myDbgPrint_PRV1(x...) do { } while(0); +#define myDbgPrint_PRV2(x...) do { } while(0); +#define myDbgPrint_PRV3(x...) do { } while(0); +#define DBG_TEST(func,args) do { } while(0); +#define DBG_EVL_ID(args) do { } while(0); + +#else /* DIVA_NO_DEBUGLIB */ +/* + * define low level macros for formatted & raw debugging + */ +#define DBG_DECL(func) extern void myDbgPrint_##func (char *, ...) ; +DBG_DECL(LOG) +DBG_DECL(FTL) +DBG_DECL(ERR) +DBG_DECL(TRC) +DBG_DECL(MXLOG) +DBG_DECL(FTL_MXLOG) +extern void myDbgPrint_EVL (long, ...) ; +DBG_DECL(REG) +DBG_DECL(MEM) +DBG_DECL(SPL) +DBG_DECL(IRP) +DBG_DECL(TIM) +DBG_DECL(BLK) +DBG_DECL(TAPI) +DBG_DECL(NDIS) +DBG_DECL(CONN) +DBG_DECL(STAT) +DBG_DECL(SEND) +DBG_DECL(RECV) +DBG_DECL(PRV0) +DBG_DECL(PRV1) +DBG_DECL(PRV2) +DBG_DECL(PRV3) +#ifdef _KERNEL_DBG_PRINT_ +/* + * tracing to maint and kernel if selected in the trace mask. + */ +#define DBG_TEST(func,args) \ +{ if ( (myDriverDebugHandle.dbgMask) & (unsigned long)DL_##func ) \ + { \ + if ( (myDriverDebugHandle.dbgMask) & DL_TO_KERNEL ) \ + {DbgPrint args; DbgPrint ("\r\n");} \ + myDbgPrint_##func args ; \ +} } +#else +/* + * Standard tracing to maint driver. + */ +#define DBG_TEST(func,args) \ +{ if ( (myDriverDebugHandle.dbgMask) & (unsigned long)DL_##func ) \ + { myDbgPrint_##func args ; \ +} } +#endif +/* + * For event level debug use a separate define, the paramete are + * different and cause compiler errors on some systems. + */ +#define DBG_EVL_ID(args) \ +{ if ( (myDriverDebugHandle.dbgMask) & (unsigned long)DL_EVL ) \ + { myDbgPrint_EVL args ; \ +} } + +#endif /* DIVA_NO_DEBUGLIB */ + +#define DBG_LOG(args) DBG_TEST(LOG, args) +#define DBG_FTL(args) DBG_TEST(FTL, args) +#define DBG_ERR(args) DBG_TEST(ERR, args) +#define DBG_TRC(args) DBG_TEST(TRC, args) +#define DBG_MXLOG(args) DBG_TEST(MXLOG, args) +#define DBG_FTL_MXLOG(args) DBG_TEST(FTL_MXLOG, args) +#define DBG_EVL(args) DBG_EVL_ID(args) +#define DBG_REG(args) DBG_TEST(REG, args) +#define DBG_MEM(args) DBG_TEST(MEM, args) +#define DBG_SPL(args) DBG_TEST(SPL, args) +#define DBG_IRP(args) DBG_TEST(IRP, args) +#define DBG_TIM(args) DBG_TEST(TIM, args) +#define DBG_BLK(args) DBG_TEST(BLK, args) +#define DBG_TAPI(args) DBG_TEST(TAPI, args) +#define DBG_NDIS(args) DBG_TEST(NDIS, args) +#define DBG_CONN(args) DBG_TEST(CONN, args) +#define DBG_STAT(args) DBG_TEST(STAT, args) +#define DBG_SEND(args) DBG_TEST(SEND, args) +#define DBG_RECV(args) DBG_TEST(RECV, args) +#define DBG_PRV0(args) DBG_TEST(PRV0, args) +#define DBG_PRV1(args) DBG_TEST(PRV1, args) +#define DBG_PRV2(args) DBG_TEST(PRV2, args) +#define DBG_PRV3(args) DBG_TEST(PRV3, args) +/* + * prototypes for debug register/deregister functions in "debuglib.c" + */ +#ifdef DIVA_NO_DEBUGLIB +#define DbgRegister(name,tag, mask) do { } while(0) +#define DbgDeregister() do { } while(0) +#define DbgSetLevel(mask) do { } while(0) +#else +extern DIVA_DI_PRINTF dprintf; +extern int DbgRegister (char *drvName, char *drvTag, unsigned long dbgMask) ; +extern void DbgDeregister (void) ; +extern void DbgSetLevel (unsigned long dbgMask) ; +#endif +/* + * driver internal structure for debug handling; + * in client drivers this structure is maintained in "debuglib.c", + * in the debug driver "debug.c" maintains a chain of such structs. + */ +typedef struct _DbgHandle_ *pDbgHandle ; +typedef void ( * DbgEnd) (pDbgHandle) ; +typedef void ( * DbgLog) (unsigned short, int, char *, va_list) ; +typedef void ( * DbgOld) (unsigned short, char *, va_list) ; +typedef void ( * DbgEv) (unsigned short, unsigned long, va_list) ; +typedef void ( * DbgIrq) (unsigned short, int, char *, va_list) ; +typedef struct _DbgHandle_ +{ char Registered ; /* driver successfull registered */ +#define DBG_HANDLE_REG_NEW 0x01 /* this (new) structure */ +#define DBG_HANDLE_REG_OLD 0x7f /* old structure (see below) */ + char Version; /* version of this structure */ +#define DBG_HANDLE_VERSION 1 /* contains dbg_old function now */ +#define DBG_HANDLE_VER_EXT 2 /* pReserved points to extended info*/ + short id ; /* internal id of registered driver */ + struct _DbgHandle_ *next ; /* ptr to next registered driver */ + struct /*LARGE_INTEGER*/ { + unsigned long LowPart; + long HighPart; + } regTime ; /* timestamp for registration */ + void *pIrp ; /* ptr to pending i/o request */ + unsigned long dbgMask ; /* current debug mask */ + char drvName[16] ; /* ASCII name of registered driver */ + char drvTag[64] ; /* revision string */ + DbgEnd dbg_end ; /* function for debug closing */ + DbgLog dbg_prt ; /* function for debug appending */ + DbgOld dbg_old ; /* function for old debug appending */ + DbgEv dbg_ev ; /* function for Windows NT Eventlog */ + DbgIrq dbg_irq ; /* function for irql checked debug */ + void *pReserved3 ; +} _DbgHandle_ ; +extern _DbgHandle_ myDriverDebugHandle ; +typedef struct _OldDbgHandle_ +{ struct _OldDbgHandle_ *next ; + void *pIrp ; + long regTime[2] ; + unsigned long dbgMask ; + short id ; + char drvName[78] ; + DbgEnd dbg_end ; + DbgLog dbg_prt ; +} _OldDbgHandle_ ; +/* the differences in DbgHandles + old: tmp: new: + 0 long next char Registered char Registered + char filler char Version + short id short id + 4 long pIrp long regTime.lo long next + 8 long regTime.lo long regTime.hi long regTime.lo + 12 long regTime.hi long next long regTime.hi + 16 long dbgMask long pIrp long pIrp + 20 short id long dbgMask long dbgMask + 22 char drvName[78] .. + 24 .. char drvName[16] char drvName[16] + 40 .. char drvTag[64] char drvTag[64] + 100 void *dbg_end .. .. + 104 void *dbg_prt void *dbg_end void *dbg_end + 108 .. void *dbg_prt void *dbg_prt + 112 .. .. void *dbg_old + 116 .. .. void *dbg_ev + 120 .. .. void *dbg_irq + 124 .. .. void *pReserved3 + ( new->id == 0 && *((short *)&new->dbgMask) == -1 ) identifies "old", + new->Registered and new->Version overlay old->next, + new->next overlays old->pIrp, new->regTime matches old->regTime and + thus these fields can be maintained in new struct whithout trouble; + id, dbgMask, drvName, dbg_end and dbg_prt need special handling ! +*/ +#define DBG_EXT_TYPE_CARD_TRACE 0x00000001 +typedef struct +{ + unsigned long ExtendedType; + union + { + /* DBG_EXT_TYPE_CARD_TRACE */ + struct + { + void ( * MaskChangedNotify) (void *pContext); + unsigned long ModuleTxtMask; + unsigned long DebugLevel; + unsigned long B_ChannelMask; + unsigned long LogBufferSize; + } CardTrace; + }Data; +} _DbgExtendedInfo_; +#ifndef DIVA_NO_DEBUGLIB +/* ------------------------------------------------------------- + Function used for xlog-style debug + ------------------------------------------------------------- */ +#define XDI_USE_XLOG 1 +void xdi_dbg_xlog (char* x, ...); +#endif /* DIVA_NO_DEBUGLIB */ +#endif /* __DEBUGLIB_H__ */ diff --git a/drivers/isdn/hardware/eicon/dfifo.h b/drivers/isdn/hardware/eicon/dfifo.h new file mode 100644 index 000000000000..9a109c71e935 --- /dev/null +++ b/drivers/isdn/hardware/eicon/dfifo.h @@ -0,0 +1,54 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +#ifndef __DIVA_IDI_DFIFO_INC__ +#define __DIVA_IDI_DFIFO_INC__ +#define DIVA_DFIFO_CACHE_SZ 64 /* Used to isolate pipe from + rest of the world + should be divisible by 4 + */ +#define DIVA_DFIFO_RAW_SZ (2512*8) +#define DIVA_DFIFO_DATA_SZ 68 +#define DIVA_DFIFO_HDR_SZ 4 +#define DIVA_DFIFO_SEGMENT_SZ (DIVA_DFIFO_DATA_SZ+DIVA_DFIFO_HDR_SZ) +#define DIVA_DFIFO_SEGMENTS ((DIVA_DFIFO_RAW_SZ)/(DIVA_DFIFO_SEGMENT_SZ)+1) +#define DIVA_DFIFO_MEM_SZ (\ + (DIVA_DFIFO_SEGMENT_SZ)*(DIVA_DFIFO_SEGMENTS)+\ + (DIVA_DFIFO_CACHE_SZ)*2\ + ) +#define DIVA_DFIFO_STEP DIVA_DFIFO_SEGMENT_SZ +/* ------------------------------------------------------------------------- + Block header layout is: + byte[0] -> flags + byte[1] -> length of data in block + byte[2] -> reserved + byte[4] -> reserved + ------------------------------------------------------------------------- */ +#define DIVA_DFIFO_WRAP 0x80 /* This is the last block in fifo */ +#define DIVA_DFIFO_READY 0x40 /* This block is ready for processing */ +#define DIVA_DFIFO_LAST 0x20 /* This block is last in message */ +#define DIVA_DFIFO_AUTO 0x10 /* Don't look for 'ready', don't ack */ +int diva_dfifo_create (void* start, int length); +#endif diff --git a/drivers/isdn/hardware/eicon/di.c b/drivers/isdn/hardware/eicon/di.c new file mode 100644 index 000000000000..0617d7cabf06 --- /dev/null +++ b/drivers/isdn/hardware/eicon/di.c @@ -0,0 +1,835 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +#include "platform.h" +#include "pc.h" +#include "pr_pc.h" +#include "di_defs.h" +#include "di.h" +#if !defined USE_EXTENDED_DEBUGS + #include "dimaint.h" +#else + #define dprintf +#endif +#include "io.h" +#include "dfifo.h" +#define PR_RAM ((struct pr_ram *)0) +#define RAM ((struct dual *)0) +/*------------------------------------------------------------------*/ +/* local function prototypes */ +/*------------------------------------------------------------------*/ +void pr_out(ADAPTER * a); +byte pr_dpc(ADAPTER * a); +static byte pr_ready(ADAPTER * a); +static byte isdn_rc(ADAPTER *, byte, byte, byte, word, dword, dword); +static byte isdn_ind(ADAPTER *, byte, byte, byte, PBUFFER *, byte, word); +/* ----------------------------------------------------------------- + Functions used for the extended XDI Debug + macros + global convergence counter (used by all adapters) + Look by the implementation part of the functions + about the parameters. + If you change the dubugging parameters, then you should update + the aididbg.doc in the IDI doc's. + ----------------------------------------------------------------- */ +#if defined(XDI_USE_XLOG) +#define XDI_A_NR(_x_) ((byte)(((ISDN_ADAPTER *)(_x_->io))->ANum)) +static void xdi_xlog (byte *msg, word code, int length); +static byte xdi_xlog_sec = 0; +#else +#define XDI_A_NR(_x_) ((byte)0) +#endif +static void xdi_xlog_rc_event (byte Adapter, + byte Id, byte Ch, byte Rc, byte cb, byte type); +static void xdi_xlog_request (byte Adapter, byte Id, + byte Ch, byte Req, byte type); +static void xdi_xlog_ind (byte Adapter, + byte Id, + byte Ch, + byte Ind, + byte rnr_valid, + byte rnr, + byte type); +/*------------------------------------------------------------------*/ +/* output function */ +/*------------------------------------------------------------------*/ +void pr_out(ADAPTER * a) +{ + byte e_no; + ENTITY * this = NULL; + BUFFERS *X; + word length; + word i; + word clength; + REQ * ReqOut; + byte more; + byte ReadyCount; + byte ReqCount; + byte Id; + dtrc(dprintf("pr_out")); + /* while a request is pending ... */ + e_no = look_req(a); + if(!e_no) + { + dtrc(dprintf("no_req")); + return; + } + ReadyCount = pr_ready(a); + if(!ReadyCount) + { + dtrc(dprintf("not_ready")); + return; + } + ReqCount = 0; + while(e_no && ReadyCount) { + next_req(a); + this = entity_ptr(a, e_no); +#ifdef USE_EXTENDED_DEBUGS + if ( !this ) + { + DBG_FTL(("XDI: [%02x] !A%d ==> NULL entity ptr - try to ignore", + xdi_xlog_sec++, (int)((ISDN_ADAPTER *)a->io)->ANum)) + e_no = look_req(a) ; + ReadyCount-- ; + continue ; + } + { + DBG_TRC((">A%d Id=0x%x Req=0x%x", ((ISDN_ADAPTER *)a->io)->ANum, this->Id, this->Req)) + } +#else + dbug(dprintf("out:Req=%x,Id=%x,Ch=%x",this->Req,this->Id,this->ReqCh)); +#endif + /* get address of next available request buffer */ + ReqOut = (REQ *)&PR_RAM->B[a->ram_inw(a, &PR_RAM->NextReq)]; +#if defined(DIVA_ISTREAM) + if (!(a->tx_stream[this->Id] && + this->Req == N_DATA)) { +#endif + /* now copy the data from the current data buffer into the */ + /* adapters request buffer */ + length = 0; + i = this->XCurrent; + X = PTR_X(a,this); + while(i<this->XNum && length<270) { + clength = MIN((word)(270-length),X[i].PLength-this->XOffset); + a->ram_out_buffer(a, + &ReqOut->XBuffer.P[length], + PTR_P(a,this,&X[i].P[this->XOffset]), + clength); + length +=clength; + this->XOffset +=clength; + if(this->XOffset==X[i].PLength) { + this->XCurrent = (byte)++i; + this->XOffset = 0; + } + } +#if defined(DIVA_ISTREAM) + } else { /* Use CMA extension in order to transfer data to the card */ + i = this->XCurrent; + X = PTR_X(a,this); + while (i < this->XNum) { + diva_istream_write (a, + this->Id, + PTR_P(a,this,&X[i].P[0]), + X[i].PLength, + ((i+1) == this->XNum), + 0, 0); + this->XCurrent = (byte)++i; + } + length = 0; + } +#endif + a->ram_outw(a, &ReqOut->XBuffer.length, length); + a->ram_out(a, &ReqOut->ReqId, this->Id); + a->ram_out(a, &ReqOut->ReqCh, this->ReqCh); + /* if it's a specific request (no ASSIGN) ... */ + if(this->Id &0x1f) { + /* if buffers are left in the list of data buffers do */ + /* do chaining (LL_MDATA, N_MDATA) */ + this->More++; + if(i<this->XNum && this->MInd) { + xdi_xlog_request (XDI_A_NR(a), this->Id, this->ReqCh, this->MInd, + a->IdTypeTable[this->No]); + a->ram_out(a, &ReqOut->Req, this->MInd); + more = TRUE; + } + else { + xdi_xlog_request (XDI_A_NR(a), this->Id, this->ReqCh, this->Req, + a->IdTypeTable[this->No]); + this->More |=XMOREF; + a->ram_out(a, &ReqOut->Req, this->Req); + more = FALSE; + if (a->FlowControlIdTable[this->ReqCh] == this->Id) + a->FlowControlSkipTable[this->ReqCh] = TRUE; + /* + Note that remove request was sent to the card + */ + if (this->Req == REMOVE) { + a->misc_flags_table[e_no] |= DIVA_MISC_FLAGS_REMOVE_PENDING; + } + } + /* if we did chaining, this entity is put back into the */ + /* request queue */ + if(more) { + req_queue(a,this->No); + } + } + /* else it's a ASSIGN */ + else { + /* save the request code used for buffer chaining */ + this->MInd = 0; + if (this->Id==BLLC_ID) this->MInd = LL_MDATA; + if (this->Id==NL_ID || + this->Id==TASK_ID || + this->Id==MAN_ID + ) this->MInd = N_MDATA; + /* send the ASSIGN */ + a->IdTypeTable[this->No] = this->Id; + xdi_xlog_request (XDI_A_NR(a),this->Id,this->ReqCh,this->Req, this->Id); + this->More |=XMOREF; + a->ram_out(a, &ReqOut->Req, this->Req); + /* save the reference of the ASSIGN */ + assign_queue(a, this->No, a->ram_inw(a, &ReqOut->Reference)); + } + a->ram_outw(a, &PR_RAM->NextReq, a->ram_inw(a, &ReqOut->next)); + ReadyCount--; + ReqCount++; + e_no = look_req(a); + } + /* send the filled request buffers to the ISDN adapter */ + a->ram_out(a, &PR_RAM->ReqInput, + (byte)(a->ram_in(a, &PR_RAM->ReqInput) + ReqCount)); + /* if it is a 'unreturncoded' UREMOVE request, remove the */ + /* Id from our table after sending the request */ + if(this && (this->Req==UREMOVE) && this->Id) { + Id = this->Id; + e_no = a->IdTable[Id]; + free_entity(a, e_no); + for (i = 0; i < 256; i++) + { + if (a->FlowControlIdTable[i] == Id) + a->FlowControlIdTable[i] = 0; + } + a->IdTable[Id] = 0; + this->Id = 0; + } +} +static byte pr_ready(ADAPTER * a) +{ + byte ReadyCount; + ReadyCount = (byte)(a->ram_in(a, &PR_RAM->ReqOutput) - + a->ram_in(a, &PR_RAM->ReqInput)); + if(!ReadyCount) { + if(!a->ReadyInt) { + a->ram_inc(a, &PR_RAM->ReadyInt); + a->ReadyInt++; + } + } + return ReadyCount; +} +/*------------------------------------------------------------------*/ +/* isdn interrupt handler */ +/*------------------------------------------------------------------*/ +byte pr_dpc(ADAPTER * a) +{ + byte Count; + RC * RcIn; + IND * IndIn; + byte c; + byte RNRId; + byte Rc; + byte Ind; + /* if return codes are available ... */ + if((Count = a->ram_in(a, &PR_RAM->RcOutput)) != 0) { + dtrc(dprintf("#Rc=%x",Count)); + /* get the buffer address of the first return code */ + RcIn = (RC *)&PR_RAM->B[a->ram_inw(a, &PR_RAM->NextRc)]; + /* for all return codes do ... */ + while(Count--) { + if((Rc=a->ram_in(a, &RcIn->Rc)) != 0) { + dword tmp[2]; + /* + Get extended information, associated with return code + */ + a->ram_in_buffer(a, + &RcIn->Reserved2[0], + (byte*)&tmp[0], + 8); + /* call return code handler, if it is not our return code */ + /* the handler returns 2 */ + /* for all return codes we process, we clear the Rc field */ + isdn_rc(a, + Rc, + a->ram_in(a, &RcIn->RcId), + a->ram_in(a, &RcIn->RcCh), + a->ram_inw(a, &RcIn->Reference), + tmp[0], /* type of extended informtion */ + tmp[1]); /* extended information */ + a->ram_out(a, &RcIn->Rc, 0); + } + /* get buffer address of next return code */ + RcIn = (RC *)&PR_RAM->B[a->ram_inw(a, &RcIn->next)]; + } + /* clear all return codes (no chaining!) */ + a->ram_out(a, &PR_RAM->RcOutput ,0); + /* call output function */ + pr_out(a); + } + /* clear RNR flag */ + RNRId = 0; + /* if indications are available ... */ + if((Count = a->ram_in(a, &PR_RAM->IndOutput)) != 0) { + dtrc(dprintf("#Ind=%x",Count)); + /* get the buffer address of the first indication */ + IndIn = (IND *)&PR_RAM->B[a->ram_inw(a, &PR_RAM->NextInd)]; + /* for all indications do ... */ + while(Count--) { + /* if the application marks an indication as RNR, all */ + /* indications from the same Id delivered in this interrupt */ + /* are marked RNR */ + if(RNRId && RNRId==a->ram_in(a, &IndIn->IndId)) { + a->ram_out(a, &IndIn->Ind, 0); + a->ram_out(a, &IndIn->RNR, TRUE); + } + else { + Ind = a->ram_in(a, &IndIn->Ind); + if(Ind) { + RNRId = 0; + /* call indication handler, a return value of 2 means chain */ + /* a return value of 1 means RNR */ + /* for all indications we process, we clear the Ind field */ + c = isdn_ind(a, + Ind, + a->ram_in(a, &IndIn->IndId), + a->ram_in(a, &IndIn->IndCh), + &IndIn->RBuffer, + a->ram_in(a, &IndIn->MInd), + a->ram_inw(a, &IndIn->MLength)); + if(c==1) { + dtrc(dprintf("RNR")); + a->ram_out(a, &IndIn->Ind, 0); + RNRId = a->ram_in(a, &IndIn->IndId); + a->ram_out(a, &IndIn->RNR, TRUE); + } + } + } + /* get buffer address of next indication */ + IndIn = (IND *)&PR_RAM->B[a->ram_inw(a, &IndIn->next)]; + } + a->ram_out(a, &PR_RAM->IndOutput, 0); + } + return FALSE; +} +byte scom_test_int(ADAPTER * a) +{ + return a->ram_in(a,(void *)0x3fe); +} +void scom_clear_int(ADAPTER * a) +{ + a->ram_out(a,(void *)0x3fe,0); +} +/*------------------------------------------------------------------*/ +/* return code handler */ +/*------------------------------------------------------------------*/ +byte isdn_rc(ADAPTER * a, + byte Rc, + byte Id, + byte Ch, + word Ref, + dword extended_info_type, + dword extended_info) +{ + ENTITY * this; + byte e_no; + word i; + int cancel_rc; +#ifdef USE_EXTENDED_DEBUGS + { + DBG_TRC(("<A%d Id=0x%x Rc=0x%x", ((ISDN_ADAPTER *)a->io)->ANum, Id, Rc)) + } +#else + dbug(dprintf("isdn_rc(Rc=%x,Id=%x,Ch=%x)",Rc,Id,Ch)); +#endif + /* check for ready interrupt */ + if(Rc==READY_INT) { + xdi_xlog_rc_event (XDI_A_NR(a), Id, Ch, Rc, 0, 0); + if(a->ReadyInt) { + a->ReadyInt--; + return 0; + } + return 2; + } + /* if we know this Id ... */ + e_no = a->IdTable[Id]; + if(e_no) { + this = entity_ptr(a,e_no); + xdi_xlog_rc_event (XDI_A_NR(a), Id, Ch, Rc, 0, a->IdTypeTable[this->No]); + this->RcCh = Ch; + /* if it is a return code to a REMOVE request, remove the */ + /* Id from our table */ + if ((a->misc_flags_table[e_no] & DIVA_MISC_FLAGS_REMOVE_PENDING) && + (Rc==OK)) { + if (a->IdTypeTable[e_no] == NL_ID) { + if (a->RcExtensionSupported && + (extended_info_type != DIVA_RC_TYPE_REMOVE_COMPLETE)) { + dtrc(dprintf("XDI: N-REMOVE, A(%02x) Id:%02x, ignore RC=OK", + XDI_A_NR(a),Id)); + return (0); + } + if (extended_info_type == DIVA_RC_TYPE_REMOVE_COMPLETE) + a->RcExtensionSupported = TRUE; + } + a->misc_flags_table[e_no] &= ~DIVA_MISC_FLAGS_REMOVE_PENDING; + a->misc_flags_table[e_no] &= ~DIVA_MISC_FLAGS_NO_RC_CANCELLING; + free_entity(a, e_no); + for (i = 0; i < 256; i++) + { + if (a->FlowControlIdTable[i] == Id) + a->FlowControlIdTable[i] = 0; + } + a->IdTable[Id] = 0; + this->Id = 0; + /* --------------------------------------------------------------- + If we send N_DISC or N_DISK_ACK after we have received OK_FC + then the card will respond with OK_FC and later with RC==OK. + If we send N_REMOVE in this state we will receive only RC==OK + This will create the state in that the XDI is waiting for the + additional RC and does not delivery the RC to the client. This + code corrects the counter of outstanding RC's in this case. + --------------------------------------------------------------- */ + if ((this->More & XMOREC) > 1) { + this->More &= ~XMOREC; + this->More |= 1; + dtrc(dprintf("XDI: correct MORE on REMOVE A(%02x) Id:%02x", + XDI_A_NR(a),Id)); + } + } + if (Rc==OK_FC) { + a->FlowControlIdTable[Ch] = Id; + a->FlowControlSkipTable[Ch] = FALSE; + this->Rc = Rc; + this->More &= ~(XBUSY | XMOREC); + this->complete=0xff; + xdi_xlog_rc_event (XDI_A_NR(a), Id, Ch, Rc, 1, a->IdTypeTable[this->No]); + CALLBACK(a, this); + return 0; + } + /* + New protocol code sends return codes that comes from release + of flow control condition marked with DIVA_RC_TYPE_OK_FC extended + information element type. + If like return code arrives then application is able to process + all return codes self and XDI should not cances return codes. + This return code does not decrement XMOREC partial return code + counter due to fact that it was no request for this return code, + also XMOREC was not incremented. + */ + if (extended_info_type == DIVA_RC_TYPE_OK_FC) { + a->misc_flags_table[e_no] |= DIVA_MISC_FLAGS_NO_RC_CANCELLING; + this->Rc = Rc; + this->complete=0xff; + xdi_xlog_rc_event (XDI_A_NR(a), Id, Ch, Rc, 1, a->IdTypeTable[this->No]); + DBG_TRC(("XDI OK_FC A(%02x) Id:%02x Ch:%02x Rc:%02x", + XDI_A_NR(a), Id, Ch, Rc)) + CALLBACK(a, this); + return 0; + } + cancel_rc = !(a->misc_flags_table[e_no] & DIVA_MISC_FLAGS_NO_RC_CANCELLING); + if (cancel_rc && (a->FlowControlIdTable[Ch] == Id)) + { + a->FlowControlIdTable[Ch] = 0; + if ((Rc != OK) || !a->FlowControlSkipTable[Ch]) + { + this->Rc = Rc; + if (Ch == this->ReqCh) + { + this->More &=~(XBUSY | XMOREC); + this->complete=0xff; + } + xdi_xlog_rc_event (XDI_A_NR(a), Id, Ch, Rc, 1, a->IdTypeTable[this->No]); + CALLBACK(a, this); + } + return 0; + } + if (this->More &XMOREC) + this->More--; + /* call the application callback function */ + if (((!cancel_rc) || (this->More & XMOREF)) && !(this->More & XMOREC)) { + this->Rc = Rc; + this->More &=~XBUSY; + this->complete=0xff; + xdi_xlog_rc_event (XDI_A_NR(a), Id, Ch, Rc, 1, a->IdTypeTable[this->No]); + CALLBACK(a, this); + } + return 0; + } + /* if it's an ASSIGN return code check if it's a return */ + /* code to an ASSIGN request from us */ + if((Rc &0xf0)==ASSIGN_RC) { + e_no = get_assign(a, Ref); + if(e_no) { + this = entity_ptr(a,e_no); + this->Id = Id; + xdi_xlog_rc_event (XDI_A_NR(a), Id, Ch, Rc, 2, a->IdTypeTable[this->No]); + /* call the application callback function */ + this->Rc = Rc; + this->More &=~XBUSY; + this->complete=0xff; +#if defined(DIVA_ISTREAM) /* { */ + if ((Rc == ASSIGN_OK) && a->ram_offset && + (a->IdTypeTable[this->No] == NL_ID) && + ((extended_info_type == DIVA_RC_TYPE_RX_DMA) || + (extended_info_type == DIVA_RC_TYPE_CMA_PTR)) && + extended_info) { + dword offset = (*(a->ram_offset)) (a); + dword tmp[2]; + extended_info -= offset; +#ifdef PLATFORM_GT_32BIT + a->ram_in_dw(a, (void*)ULongToPtr(extended_info), (dword*)&tmp[0], 2); +#else + a->ram_in_dw(a, (void*)extended_info, (dword*)&tmp[0], 2); +#endif + a->tx_stream[Id] = tmp[0]; + a->rx_stream[Id] = tmp[1]; + if (extended_info_type == DIVA_RC_TYPE_RX_DMA) { + DBG_TRC(("Id=0x%x RxDMA=%08x:%08x", + Id, a->tx_stream[Id], a->rx_stream[Id])) + a->misc_flags_table[this->No] |= DIVA_MISC_FLAGS_RX_DMA; + } else { + DBG_TRC(("Id=0x%x CMA=%08x:%08x", + Id, a->tx_stream[Id], a->rx_stream[Id])) + a->misc_flags_table[this->No] &= ~DIVA_MISC_FLAGS_RX_DMA; + a->rx_pos[Id] = 0; + a->rx_stream[Id] -= offset; + } + a->tx_pos[Id] = 0; + a->tx_stream[Id] -= offset; + } else { + a->tx_stream[Id] = 0; + a->rx_stream[Id] = 0; + a->misc_flags_table[this->No] &= ~DIVA_MISC_FLAGS_RX_DMA; + } +#endif /* } */ + CALLBACK(a, this); + if(Rc==ASSIGN_OK) { + a->IdTable[Id] = e_no; + } + else + { + free_entity(a, e_no); + for (i = 0; i < 256; i++) + { + if (a->FlowControlIdTable[i] == Id) + a->FlowControlIdTable[i] = 0; + } + a->IdTable[Id] = 0; + this->Id = 0; + } + return 1; + } + } + return 2; +} +/*------------------------------------------------------------------*/ +/* indication handler */ +/*------------------------------------------------------------------*/ +byte isdn_ind(ADAPTER * a, + byte Ind, + byte Id, + byte Ch, + PBUFFER * RBuffer, + byte MInd, + word MLength) +{ + ENTITY * this; + word clength; + word offset; + BUFFERS *R; + byte* cma = NULL; +#ifdef USE_EXTENDED_DEBUGS + { + DBG_TRC(("<A%d Id=0x%x Ind=0x%x", ((ISDN_ADAPTER *)a->io)->ANum, Id, Ind)) + } +#else + dbug(dprintf("isdn_ind(Ind=%x,Id=%x,Ch=%x)",Ind,Id,Ch)); +#endif + if(a->IdTable[Id]) { + this = entity_ptr(a,a->IdTable[Id]); + this->IndCh = Ch; + xdi_xlog_ind (XDI_A_NR(a), Id, Ch, Ind, + 0/* rnr_valid */, 0 /* rnr */, a->IdTypeTable[this->No]); + /* if the Receive More flag is not yet set, this is the */ + /* first buffer of the packet */ + if(this->RCurrent==0xff) { + /* check for receive buffer chaining */ + if(Ind==this->MInd) { + this->complete = 0; + this->Ind = MInd; + } + else { + this->complete = 1; + this->Ind = Ind; + } + /* call the application callback function for the receive */ + /* look ahead */ + this->RLength = MLength; +#if defined(DIVA_ISTREAM) + if ((a->rx_stream[this->Id] || + (a->misc_flags_table[this->No] & DIVA_MISC_FLAGS_RX_DMA)) && + ((Ind == N_DATA) || + (a->protocol_capabilities & PROTCAP_CMA_ALLPR))) { + PISDN_ADAPTER IoAdapter = (PISDN_ADAPTER)a->io ; + if (a->misc_flags_table[this->No] & DIVA_MISC_FLAGS_RX_DMA) { +#if defined(DIVA_IDI_RX_DMA) + dword d; + diva_get_dma_map_entry (\ + (struct _diva_dma_map_entry*)IoAdapter->dma_map, + (int)a->rx_stream[this->Id], (void**)&cma, &d); +#else + cma = &a->stream_buffer[0]; + cma[0] = cma[1] = cma[2] = cma[3] = 0; +#endif + this->RLength = MLength = (word)*(dword*)cma; + cma += 4; + } else { + int final = 0; + cma = &a->stream_buffer[0]; + this->RLength = MLength = (word)diva_istream_read (a, + Id, + cma, + sizeof(a->stream_buffer), + &final, NULL, NULL); + } + IoAdapter->RBuffer.length = MIN(MLength, 270); + if (IoAdapter->RBuffer.length != MLength) { + this->complete = 0; + } else { + this->complete = 1; + } + memcpy (IoAdapter->RBuffer.P, cma, IoAdapter->RBuffer.length) ; + this->RBuffer = (DBUFFER *)&IoAdapter->RBuffer ; + } +#endif + if (!cma) { + a->ram_look_ahead(a, RBuffer, this); + } + this->RNum = 0; + CALLBACK(a, this); + /* map entity ptr, selector could be re-mapped by call to */ + /* IDI from within callback */ + this = entity_ptr(a,a->IdTable[Id]); + xdi_xlog_ind (XDI_A_NR(a), Id, Ch, Ind, + 1/* rnr_valid */, this->RNR/* rnr */, a->IdTypeTable[this->No]); + /* check for RNR */ + if(this->RNR==1) { + this->RNR = 0; + return 1; + } + /* if no buffers are provided by the application, the */ + /* application want to copy the data itself including */ + /* N_MDATA/LL_MDATA chaining */ + if(!this->RNR && !this->RNum) { + xdi_xlog_ind (XDI_A_NR(a), Id, Ch, Ind, + 2/* rnr_valid */, 0/* rnr */, a->IdTypeTable[this->No]); + return 0; + } + /* if there is no RNR, set the More flag */ + this->RCurrent = 0; + this->ROffset = 0; + } + if(this->RNR==2) { + if(Ind!=this->MInd) { + this->RCurrent = 0xff; + this->RNR = 0; + } + return 0; + } + /* if we have received buffers from the application, copy */ + /* the data into these buffers */ + offset = 0; + R = PTR_R(a,this); + do { + if(this->ROffset==R[this->RCurrent].PLength) { + this->ROffset = 0; + this->RCurrent++; + } + if (cma) { + clength = MIN(MLength, R[this->RCurrent].PLength-this->ROffset); + } else { + clength = MIN(a->ram_inw(a, &RBuffer->length)-offset, + R[this->RCurrent].PLength-this->ROffset); + } + if(R[this->RCurrent].P) { + if (cma) { + memcpy (PTR_P(a,this,&R[this->RCurrent].P[this->ROffset]), + &cma[offset], + clength); + } else { + a->ram_in_buffer(a, + &RBuffer->P[offset], + PTR_P(a,this,&R[this->RCurrent].P[this->ROffset]), + clength); + } + } + offset +=clength; + this->ROffset +=clength; + if (cma) { + if (offset >= MLength) { + break; + } + continue; + } + } while(offset<(a->ram_inw(a, &RBuffer->length))); + /* if it's the last buffer of the packet, call the */ + /* application callback function for the receive complete */ + /* call */ + if(Ind!=this->MInd) { + R[this->RCurrent].PLength = this->ROffset; + if(this->ROffset) this->RCurrent++; + this->RNum = this->RCurrent; + this->RCurrent = 0xff; + this->Ind = Ind; + this->complete = 2; + xdi_xlog_ind (XDI_A_NR(a), Id, Ch, Ind, + 3/* rnr_valid */, 0/* rnr */, a->IdTypeTable[this->No]); + CALLBACK(a, this); + } + return 0; + } + return 2; +} +#if defined(XDI_USE_XLOG) +/* ----------------------------------------------------------- + This function works in the same way as xlog on the + active board + ----------------------------------------------------------- */ +static void xdi_xlog (byte *msg, word code, int length) { + xdi_dbg_xlog ("\x00\x02", msg, code, length); +} +#endif +/* ----------------------------------------------------------- + This function writes the information about the Return Code + processing in the trace buffer. Trace ID is 221. + INPUT: + Adapter - system unicue adapter number (0 ... 255) + Id - Id of the entity that had sent this return code + Ch - Channel of the entity that had sent this return code + Rc - return code value + cb: (0...2) + switch (cb) { + case 0: printf ("DELIVERY"); break; + case 1: printf ("CALLBACK"); break; + case 2: printf ("ASSIGN"); break; + } + DELIVERY - have entered isdn_rc with this RC + CALLBACK - about to make callback to the application + for this RC + ASSIGN - about to make callback for RC that is result + of ASSIGN request. It is no DELIVERY message + before of this message + type - the Id that was sent by the ASSIGN of this entity. + This should be global Id like NL_ID, DSIG_ID, MAN_ID. + An unknown Id will cause "?-" in the front of the request. + In this case the log.c is to be extended. + ----------------------------------------------------------- */ +static void xdi_xlog_rc_event (byte Adapter, + byte Id, byte Ch, byte Rc, byte cb, byte type) { +#if defined(XDI_USE_XLOG) + word LogInfo[4]; + PUT_WORD(&LogInfo[0], ((word)Adapter | (word)(xdi_xlog_sec++ << 8))); + PUT_WORD(&LogInfo[1], ((word)Id | (word)(Ch << 8))); + PUT_WORD(&LogInfo[2], ((word)Rc | (word)(type << 8))); + PUT_WORD(&LogInfo[3], cb); + xdi_xlog ((byte*)&LogInfo[0], 221, sizeof(LogInfo)); +#endif +} +/* ------------------------------------------------------------------------ + This function writes the information about the request processing + in the trace buffer. Trace ID is 220. + INPUT: + Adapter - system unicue adapter number (0 ... 255) + Id - Id of the entity that had sent this request + Ch - Channel of the entity that had sent this request + Req - Code of the request + type - the Id that was sent by the ASSIGN of this entity. + This should be global Id like NL_ID, DSIG_ID, MAN_ID. + An unknown Id will cause "?-" in the front of the request. + In this case the log.c is to be extended. + ------------------------------------------------------------------------ */ +static void xdi_xlog_request (byte Adapter, byte Id, + byte Ch, byte Req, byte type) { +#if defined(XDI_USE_XLOG) + word LogInfo[3]; + PUT_WORD(&LogInfo[0], ((word)Adapter | (word)(xdi_xlog_sec++ << 8))); + PUT_WORD(&LogInfo[1], ((word)Id | (word)(Ch << 8))); + PUT_WORD(&LogInfo[2], ((word)Req | (word)(type << 8))); + xdi_xlog ((byte*)&LogInfo[0], 220, sizeof(LogInfo)); +#endif +} +/* ------------------------------------------------------------------------ + This function writes the information about the indication processing + in the trace buffer. Trace ID is 222. + INPUT: + Adapter - system unicue adapter number (0 ... 255) + Id - Id of the entity that had sent this indication + Ch - Channel of the entity that had sent this indication + Ind - Code of the indication + rnr_valid: (0 .. 3) supported + switch (rnr_valid) { + case 0: printf ("DELIVERY"); break; + case 1: printf ("RNR=%d", rnr); + case 2: printf ("RNum=0"); + case 3: printf ("COMPLETE"); + } + DELIVERY - indication entered isdn_rc function + RNR=... - application had returned RNR=... after the + look ahead callback + RNum=0 - aplication had not returned any buffer to copy + this indication and will copy it self + COMPLETE - XDI had copied the data to the buffers provided + bu the application and is about to issue the + final callback + rnr: Look case 1 of the rnr_valid + type: the Id that was sent by the ASSIGN of this entity. This should + be global Id like NL_ID, DSIG_ID, MAN_ID. An unknown Id will + cause "?-" in the front of the request. In this case the + log.c is to be extended. + ------------------------------------------------------------------------ */ +static void xdi_xlog_ind (byte Adapter, + byte Id, + byte Ch, + byte Ind, + byte rnr_valid, + byte rnr, + byte type) { +#if defined(XDI_USE_XLOG) + word LogInfo[4]; + PUT_WORD(&LogInfo[0], ((word)Adapter | (word)(xdi_xlog_sec++ << 8))); + PUT_WORD(&LogInfo[1], ((word)Id | (word)(Ch << 8))); + PUT_WORD(&LogInfo[2], ((word)Ind | (word)(type << 8))); + PUT_WORD(&LogInfo[3], ((word)rnr | (word)(rnr_valid << 8))); + xdi_xlog ((byte*)&LogInfo[0], 222, sizeof(LogInfo)); +#endif +} diff --git a/drivers/isdn/hardware/eicon/di.h b/drivers/isdn/hardware/eicon/di.h new file mode 100644 index 000000000000..dcf37b10f5dc --- /dev/null +++ b/drivers/isdn/hardware/eicon/di.h @@ -0,0 +1,118 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +/* + * some macros for detailed trace management + */ +#include "di_dbg.h" +/*****************************************************************************/ +#define XMOREC 0x1f +#define XMOREF 0x20 +#define XBUSY 0x40 +#define RMORE 0x80 +#define DIVA_MISC_FLAGS_REMOVE_PENDING 0x01 +#define DIVA_MISC_FLAGS_NO_RC_CANCELLING 0x02 +#define DIVA_MISC_FLAGS_RX_DMA 0x04 + /* structure for all information we have to keep on a per */ + /* adapater basis */ +typedef struct adapter_s ADAPTER; +struct adapter_s { + void * io; + byte IdTable[256]; + byte IdTypeTable[256]; + byte FlowControlIdTable[256]; + byte FlowControlSkipTable[256]; + byte ReadyInt; + byte RcExtensionSupported; + byte misc_flags_table[256]; + dword protocol_capabilities; + byte ( * ram_in)(ADAPTER * a, void * adr); + word ( * ram_inw)(ADAPTER * a, void * adr); + void (* ram_in_buffer)(ADAPTER * a, void * adr, void * P, word length); + void (* ram_look_ahead)(ADAPTER * a, PBUFFER * RBuffer, ENTITY * e); + void ( * ram_out)(ADAPTER * a, void * adr, byte data); + void ( * ram_outw)(ADAPTER * a, void * adr, word data); + void (* ram_out_buffer)(ADAPTER * a, void * adr, void * P, word length); + void ( * ram_inc)(ADAPTER * a, void * adr); +#if defined(DIVA_ISTREAM) + dword rx_stream[256]; + dword tx_stream[256]; + word tx_pos[256]; + word rx_pos[256]; + byte stream_buffer[2512]; + dword ( * ram_offset)(ADAPTER * a); + void ( * ram_out_dw) (ADAPTER *a, + void *addr, + const dword* data, + int dwords); + void ( * ram_in_dw) (ADAPTER *a, + void *addr, + dword* data, + int dwords); + void ( * istream_wakeup)(ADAPTER* a); +#else + byte stream_buffer[4]; +#endif +}; +/*------------------------------------------------------------------*/ +/* public functions of IDI common code */ +/*------------------------------------------------------------------*/ +void pr_out(ADAPTER * a); +byte pr_dpc(ADAPTER * a); +byte scom_test_int(ADAPTER * a); +void scom_clear_int(ADAPTER * a); +/*------------------------------------------------------------------*/ +/* OS specific functions used by IDI common code */ +/*------------------------------------------------------------------*/ +void free_entity(ADAPTER * a, byte e_no); +void assign_queue(ADAPTER * a, byte e_no, word ref); +byte get_assign(ADAPTER * a, word ref); +void req_queue(ADAPTER * a, byte e_no); +byte look_req(ADAPTER * a); +void next_req(ADAPTER * a); +ENTITY * entity_ptr(ADAPTER * a, byte e_no); +#if defined(DIVA_ISTREAM) +struct _diva_xdi_stream_interface; +void diva_xdi_provide_istream_info (ADAPTER* a, + struct _diva_xdi_stream_interface* pI); +void pr_stream (ADAPTER * a); +int diva_istream_write (void* context, + int Id, + void* data, + int length, + int final, + byte usr1, + byte usr2); +int diva_istream_read (void* context, + int Id, + void* data, + int max_length, + int* final, + byte* usr1, + byte* usr2); +#if defined(DIVA_IDI_RX_DMA) +#include "diva_dma.h" +#endif +#endif diff --git a/drivers/isdn/hardware/eicon/di_dbg.h b/drivers/isdn/hardware/eicon/di_dbg.h new file mode 100644 index 000000000000..d576ff31d44c --- /dev/null +++ b/drivers/isdn/hardware/eicon/di_dbg.h @@ -0,0 +1,37 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +#ifndef __DIVA_DI_DBG_INC__ +#define __DIVA_DI_DBG_INC__ +#if !defined (dtrc) +#define dtrc(a) +#endif +#if !defined (dbug) +#define dbug(a) +#endif +#if !defined USE_EXTENDED_DEBUGS +extern void (*dprintf)(char*, ...); +#endif +#endif diff --git a/drivers/isdn/hardware/eicon/di_defs.h b/drivers/isdn/hardware/eicon/di_defs.h new file mode 100644 index 000000000000..4c2f61267df1 --- /dev/null +++ b/drivers/isdn/hardware/eicon/di_defs.h @@ -0,0 +1,181 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +#ifndef _DI_DEFS_ +#define _DI_DEFS_ + /* typedefs for our data structures */ +typedef struct get_name_s GET_NAME; +/* The entity_s structure is used to pass all + parameters between application and IDI */ +typedef struct entity_s ENTITY; +typedef struct buffers_s BUFFERS; +typedef struct postcall_s POSTCALL; +typedef struct get_para_s GET_PARA; +#define BOARD_NAME_LENGTH 9 +#define IDI_CALL_LINK_T +#define IDI_CALL_ENTITY_T +/* typedef void ( * IDI_CALL)(ENTITY *); */ +/* -------------------------------------------------------- + IDI_CALL + -------------------------------------------------------- */ +typedef void (IDI_CALL_LINK_T * IDI_CALL)(ENTITY IDI_CALL_ENTITY_T *); +typedef struct { + word length; /* length of data/parameter field */ + byte P[270]; /* data/parameter field */ +} DBUFFER; +struct get_name_s { + word command; /* command = 0x0100 */ + byte name[BOARD_NAME_LENGTH]; +}; +struct postcall_s { + word command; /* command = 0x0300 */ + word dummy; /* not used */ + void ( * callback)(void *); /* call back */ + void *context; /* context pointer */ +}; +#define REQ_PARA 0x0600 /* request command line parameters */ +#define REQ_PARA_LEN 1 /* number of data bytes */ +#define L1_STARTUP_DOWN_POS 0 /* '-y' command line parameter in......*/ +#define L1_STARTUP_DOWN_MSK 0x01 /* first byte position (index 0) with value 0x01 */ +struct get_para_s { + word command; /* command = 0x0600 */ + byte len; /* max length of para field in bytes */ + byte para[REQ_PARA_LEN]; /* parameter field */ +}; +struct buffers_s { + word PLength; + byte * P; +}; +struct entity_s { + byte Req; /* pending request */ + byte Rc; /* return code received */ + byte Ind; /* indication received */ + byte ReqCh; /* channel of current Req */ + byte RcCh; /* channel of current Rc */ + byte IndCh; /* channel of current Ind */ + byte Id; /* ID used by this entity */ + byte GlobalId; /* reserved field */ + byte XNum; /* number of X-buffers */ + byte RNum; /* number of R-buffers */ + BUFFERS * X; /* pointer to X-buffer list */ + BUFFERS * R; /* pointer to R-buffer list */ + word RLength; /* length of current R-data */ + DBUFFER * RBuffer; /* buffer of current R-data */ + byte RNR; /* receive not ready flag */ + byte complete; /* receive complete status */ + IDI_CALL callback; + word user[2]; + /* fields used by the driver internally */ + byte No; /* entity number */ + byte reserved2; /* reserved field */ + byte More; /* R/X More flags */ + byte MInd; /* MDATA coding for this ID */ + byte XCurrent; /* current transmit buffer */ + byte RCurrent; /* current receive buffer */ + word XOffset; /* offset in x-buffer */ + word ROffset; /* offset in r-buffer */ +}; +typedef struct { + byte type; + byte channels; + word features; + IDI_CALL request; +} DESCRIPTOR; + /* descriptor type field coding */ +#define IDI_ADAPTER_S 1 +#define IDI_ADAPTER_PR 2 +#define IDI_ADAPTER_DIVA 3 +#define IDI_ADAPTER_MAESTRA 4 +#define IDI_VADAPTER 0x40 +#define IDI_DRIVER 0x80 +#define IDI_DADAPTER 0xfd +#define IDI_DIDDPNP 0xfe +#define IDI_DIMAINT 0xff + /* Hardware IDs ISA PNP */ +#define HW_ID_DIVA_PRO 3 /* same as IDI_ADAPTER_DIVA */ +#define HW_ID_MAESTRA 4 /* same as IDI_ADAPTER_MAESTRA */ +#define HW_ID_PICCOLA 5 +#define HW_ID_DIVA_PRO20 6 +#define HW_ID_DIVA20 7 +#define HW_ID_DIVA_PRO20_U 8 +#define HW_ID_DIVA20_U 9 +#define HW_ID_DIVA30 10 +#define HW_ID_DIVA30_U 11 + /* Hardware IDs PCI */ +#define HW_ID_EICON_PCI 0x1133 +#define HW_ID_SIEMENS_PCI 0x8001 /* unused SubVendor ID for Siemens Cornet-N cards */ +#define HW_ID_PROTTYPE_CORNETN 0x0014 /* SubDevice ID for Siemens Cornet-N cards */ +#define HW_ID_FUJITSU_SIEMENS_PCI 0x110A /* SubVendor ID for Fujitsu Siemens */ +#define HW_ID_GS03_PCI 0x0021 /* SubDevice ID for Fujitsu Siemens ISDN S0 card */ +#define HW_ID_DIVA_PRO20_PCI 0xe001 +#define HW_ID_DIVA20_PCI 0xe002 +#define HW_ID_DIVA_PRO20_PCI_U 0xe003 +#define HW_ID_DIVA20_PCI_U 0xe004 +#define HW_ID_DIVA201_PCI 0xe005 +#define HW_ID_DIVA_CT_ST 0xe006 +#define HW_ID_DIVA_CT_U 0xe007 +#define HW_ID_DIVA_CTL_ST 0xe008 +#define HW_ID_DIVA_CTL_U 0xe009 +#define HW_ID_DIVA_ISDN_V90_PCI 0xe00a +#define HW_ID_DIVA202_PCI_ST 0xe00b +#define HW_ID_DIVA202_PCI_U 0xe00c +#define HW_ID_DIVA_PRO30_PCI 0xe00d +#define HW_ID_MAESTRA_PCI 0xe010 +#define HW_ID_MAESTRAQ_PCI 0xe012 +#define HW_ID_DSRV_Q8M_V2_PCI 0xe013 +#define HW_ID_MAESTRAP_PCI 0xe014 +#define HW_ID_DSRV_P30M_V2_PCI 0xe015 +#define HW_ID_DSRV_VOICE_Q8M_PCI 0xe016 +#define HW_ID_DSRV_VOICE_Q8M_V2_PCI 0xe017 +#define HW_ID_DSRV_B2M_V2_PCI 0xe018 +#define HW_ID_DSRV_VOICE_P30M_V2_PCI 0xe019 +#define HW_ID_DSRV_B2F_PCI 0xe01a +#define HW_ID_DSRV_VOICE_B2M_V2_PCI 0xe01b + /* Hardware IDs USB */ +#define EICON_USB_VENDOR_ID 0x071D +#define HW_ID_DIVA_USB_REV1 0x1000 +#define HW_ID_DIVA_USB_REV2 0x1003 +#define HW_ID_TELEDAT_SURF_USB_REV2 0x1004 +#define HW_ID_TELEDAT_SURF_USB_REV1 0x2000 +/* -------------------------------------------------------------------------- + Adapter array change notification framework + -------------------------------------------------------------------------- */ +typedef void (IDI_CALL_LINK_T* didd_adapter_change_callback_t)( void IDI_CALL_ENTITY_T * context, DESCRIPTOR* adapter, int removal); +/* -------------------------------------------------------------------------- */ +#define DI_VOICE 0x0 /* obsolete define */ +#define DI_FAX3 0x1 +#define DI_MODEM 0x2 +#define DI_POST 0x4 +#define DI_V110 0x8 +#define DI_V120 0x10 +#define DI_POTS 0x20 +#define DI_CODEC 0x40 +#define DI_MANAGE 0x80 +#define DI_V_42 0x0100 +#define DI_EXTD_FAX 0x0200 /* Extended FAX (ECM, 2D, T.6, Polling) */ +#define DI_AT_PARSER 0x0400 /* Build-in AT Parser in the L2 */ +#define DI_VOICE_OVER_IP 0x0800 /* Voice over IP support */ +typedef void (IDI_CALL_LINK_T* _IDI_CALL)(void*, ENTITY*); +#endif diff --git a/drivers/isdn/hardware/eicon/did_vers.h b/drivers/isdn/hardware/eicon/did_vers.h new file mode 100644 index 000000000000..538c590fdf42 --- /dev/null +++ b/drivers/isdn/hardware/eicon/did_vers.h @@ -0,0 +1,26 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +static char diva_didd_common_code_build[] = "102-51"; diff --git a/drivers/isdn/hardware/eicon/diddfunc.c b/drivers/isdn/hardware/eicon/diddfunc.c new file mode 100644 index 000000000000..3029234178d8 --- /dev/null +++ b/drivers/isdn/hardware/eicon/diddfunc.c @@ -0,0 +1,115 @@ +/* $Id: diddfunc.c,v 1.14.6.2 2004/08/28 20:03:53 armin Exp $ + * + * DIDD Interface module for Eicon active cards. + * + * Functions are in dadapter.c + * + * Copyright 2002-2003 by Armin Schindler (mac@melware.de) + * Copyright 2002-2003 Cytronics & Melware (info@melware.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + */ + +#include "platform.h" +#include "di_defs.h" +#include "dadapter.h" +#include "divasync.h" + +#define DBG_MINIMUM (DL_LOG + DL_FTL + DL_ERR) +#define DBG_DEFAULT (DBG_MINIMUM + DL_XLOG + DL_REG) + + +extern void DIVA_DIDD_Read(void *, int); +extern char *DRIVERRELEASE_DIDD; +static dword notify_handle; +static DESCRIPTOR _DAdapter; + +/* + * didd callback function + */ +static void *didd_callback(void *context, DESCRIPTOR * adapter, + int removal) +{ + if (adapter->type == IDI_DADAPTER) { + DBG_ERR(("Notification about IDI_DADAPTER change ! Oops.")) + return (NULL); + } else if (adapter->type == IDI_DIMAINT) { + if (removal) { + DbgDeregister(); + } else { + DbgRegister("DIDD", DRIVERRELEASE_DIDD, DBG_DEFAULT); + } + } + return (NULL); +} + +/* + * connect to didd + */ +static int DIVA_INIT_FUNCTION connect_didd(void) +{ + int x = 0; + int dadapter = 0; + IDI_SYNC_REQ req; + DESCRIPTOR DIDD_Table[MAX_DESCRIPTORS]; + + DIVA_DIDD_Read(DIDD_Table, sizeof(DIDD_Table)); + + for (x = 0; x < MAX_DESCRIPTORS; x++) { + if (DIDD_Table[x].type == IDI_DADAPTER) { /* DADAPTER found */ + dadapter = 1; + memcpy(&_DAdapter, &DIDD_Table[x], sizeof(_DAdapter)); + req.didd_notify.e.Req = 0; + req.didd_notify.e.Rc = + IDI_SYNC_REQ_DIDD_REGISTER_ADAPTER_NOTIFY; + req.didd_notify.info.callback = (void *)didd_callback; + req.didd_notify.info.context = NULL; + _DAdapter.request((ENTITY *) & req); + if (req.didd_notify.e.Rc != 0xff) + return (0); + notify_handle = req.didd_notify.info.handle; + } else if (DIDD_Table[x].type == IDI_DIMAINT) { /* MAINT found */ + DbgRegister("DIDD", DRIVERRELEASE_DIDD, DBG_DEFAULT); + } + } + return (dadapter); +} + +/* + * disconnect from didd + */ +static void DIVA_EXIT_FUNCTION disconnect_didd(void) +{ + IDI_SYNC_REQ req; + + req.didd_notify.e.Req = 0; + req.didd_notify.e.Rc = IDI_SYNC_REQ_DIDD_REMOVE_ADAPTER_NOTIFY; + req.didd_notify.info.handle = notify_handle; + _DAdapter.request((ENTITY *) & req); +} + +/* + * init + */ +int DIVA_INIT_FUNCTION diddfunc_init(void) +{ + diva_didd_load_time_init(); + + if (!connect_didd()) { + DBG_ERR(("init: failed to connect to DIDD.")) + diva_didd_load_time_finit(); + return (0); + } + return (1); +} + +/* + * finit + */ +void DIVA_EXIT_FUNCTION diddfunc_finit(void) +{ + DbgDeregister(); + disconnect_didd(); + diva_didd_load_time_finit(); +} diff --git a/drivers/isdn/hardware/eicon/diva.c b/drivers/isdn/hardware/eicon/diva.c new file mode 100644 index 000000000000..8ab8027f33c0 --- /dev/null +++ b/drivers/isdn/hardware/eicon/diva.c @@ -0,0 +1,660 @@ +/* $Id: diva.c,v 1.21.4.1 2004/05/08 14:33:43 armin Exp $ */ + +#define CARDTYPE_H_WANT_DATA 1 +#define CARDTYPE_H_WANT_IDI_DATA 0 +#define CARDTYPE_H_WANT_RESOURCE_DATA 0 +#define CARDTYPE_H_WANT_FILE_DATA 0 + +#include "platform.h" +#include "debuglib.h" +#include "cardtype.h" +#include "pc.h" +#include "di_defs.h" +#include "di.h" +#include "io.h" +#include "pc_maint.h" +#include "xdi_msg.h" +#include "xdi_adapter.h" +#include "diva_pci.h" +#include "diva.h" + +#ifdef CONFIG_ISDN_DIVAS_PRIPCI +#include "os_pri.h" +#endif +#ifdef CONFIG_ISDN_DIVAS_BRIPCI +#include "os_bri.h" +#include "os_4bri.h" +#endif + +PISDN_ADAPTER IoAdapters[MAX_ADAPTER]; +extern IDI_CALL Requests[MAX_ADAPTER]; +extern int create_adapter_proc(diva_os_xdi_adapter_t * a); +extern void remove_adapter_proc(diva_os_xdi_adapter_t * a); + +#define DivaIdiReqFunc(N) \ +static void DivaIdiRequest##N(ENTITY *e) \ +{ if ( IoAdapters[N] ) (* IoAdapters[N]->DIRequest)(IoAdapters[N], e) ; } + +/* +** Create own 32 Adapters +*/ +DivaIdiReqFunc(0) +DivaIdiReqFunc(1) +DivaIdiReqFunc(2) +DivaIdiReqFunc(3) +DivaIdiReqFunc(4) +DivaIdiReqFunc(5) +DivaIdiReqFunc(6) +DivaIdiReqFunc(7) +DivaIdiReqFunc(8) +DivaIdiReqFunc(9) +DivaIdiReqFunc(10) +DivaIdiReqFunc(11) +DivaIdiReqFunc(12) +DivaIdiReqFunc(13) +DivaIdiReqFunc(14) +DivaIdiReqFunc(15) +DivaIdiReqFunc(16) +DivaIdiReqFunc(17) +DivaIdiReqFunc(18) +DivaIdiReqFunc(19) +DivaIdiReqFunc(20) +DivaIdiReqFunc(21) +DivaIdiReqFunc(22) +DivaIdiReqFunc(23) +DivaIdiReqFunc(24) +DivaIdiReqFunc(25) +DivaIdiReqFunc(26) +DivaIdiReqFunc(27) +DivaIdiReqFunc(28) +DivaIdiReqFunc(29) +DivaIdiReqFunc(30) +DivaIdiReqFunc(31) + +struct pt_regs; + +/* +** LOCALS +*/ +static LIST_HEAD(adapter_queue); + +typedef struct _diva_get_xlog { + word command; + byte req; + byte rc; + byte data[sizeof(struct mi_pc_maint)]; +} diva_get_xlog_t; + +typedef struct _diva_supported_cards_info { + int CardOrdinal; + diva_init_card_proc_t init_card; +} diva_supported_cards_info_t; + +static diva_supported_cards_info_t divas_supported_cards[] = { +#ifdef CONFIG_ISDN_DIVAS_PRIPCI + /* + PRI Cards + */ + {CARDTYPE_DIVASRV_P_30M_PCI, diva_pri_init_card}, + /* + PRI Rev.2 Cards + */ + {CARDTYPE_DIVASRV_P_30M_V2_PCI, diva_pri_init_card}, + /* + PRI Rev.2 VoIP Cards + */ + {CARDTYPE_DIVASRV_VOICE_P_30M_V2_PCI, diva_pri_init_card}, +#endif +#ifdef CONFIG_ISDN_DIVAS_BRIPCI + /* + 4BRI Rev 1 Cards + */ + {CARDTYPE_DIVASRV_Q_8M_PCI, diva_4bri_init_card}, + {CARDTYPE_DIVASRV_VOICE_Q_8M_PCI, diva_4bri_init_card}, + /* + 4BRI Rev 2 Cards + */ + {CARDTYPE_DIVASRV_Q_8M_V2_PCI, diva_4bri_init_card}, + {CARDTYPE_DIVASRV_VOICE_Q_8M_V2_PCI, diva_4bri_init_card}, + /* + 4BRI Based BRI Rev 2 Cards + */ + {CARDTYPE_DIVASRV_B_2M_V2_PCI, diva_4bri_init_card}, + {CARDTYPE_DIVASRV_B_2F_PCI, diva_4bri_init_card}, + {CARDTYPE_DIVASRV_VOICE_B_2M_V2_PCI, diva_4bri_init_card}, + /* + BRI + */ + {CARDTYPE_MAESTRA_PCI, diva_bri_init_card}, +#endif + + /* + EOL + */ + {-1} +}; + +static void diva_init_request_array(void); +static void *divas_create_pci_card(int handle, void *pci_dev_handle); + +static diva_os_spin_lock_t adapter_lock; + +static int diva_find_free_adapters(int base, int nr) +{ + int i; + + for (i = 0; i < nr; i++) { + if (IoAdapters[base + i]) { + return (-1); + } + } + + return (0); +} + +static diva_os_xdi_adapter_t *diva_q_get_next(struct list_head * what) +{ + diva_os_xdi_adapter_t *a = NULL; + + if (what && (what->next != &adapter_queue)) + a = list_entry(what->next, diva_os_xdi_adapter_t, link); + + return(a); +} + +/* -------------------------------------------------------------------------- + Add card to the card list + -------------------------------------------------------------------------- */ +void *diva_driver_add_card(void *pdev, unsigned long CardOrdinal) +{ + diva_os_spin_lock_magic_t old_irql; + diva_os_xdi_adapter_t *pdiva, *pa; + int i, j, max, nr; + + for (i = 0; divas_supported_cards[i].CardOrdinal != -1; i++) { + if (divas_supported_cards[i].CardOrdinal == CardOrdinal) { + if (!(pdiva = divas_create_pci_card(i, pdev))) { + return NULL; + } + switch (CardOrdinal) { + case CARDTYPE_DIVASRV_Q_8M_PCI: + case CARDTYPE_DIVASRV_VOICE_Q_8M_PCI: + case CARDTYPE_DIVASRV_Q_8M_V2_PCI: + case CARDTYPE_DIVASRV_VOICE_Q_8M_V2_PCI: + max = MAX_ADAPTER - 4; + nr = 4; + break; + + default: + max = MAX_ADAPTER; + nr = 1; + } + + diva_os_enter_spin_lock(&adapter_lock, &old_irql, "add card"); + + for (i = 0; i < max; i++) { + if (!diva_find_free_adapters(i, nr)) { + pdiva->controller = i + 1; + pdiva->xdi_adapter.ANum = pdiva->controller; + IoAdapters[i] = &pdiva->xdi_adapter; + diva_os_leave_spin_lock(&adapter_lock, &old_irql, "add card"); + create_adapter_proc(pdiva); /* add adapter to proc file system */ + + DBG_LOG(("add %s:%d", + CardProperties + [CardOrdinal].Name, + pdiva->controller)) + + diva_os_enter_spin_lock(&adapter_lock, &old_irql, "add card"); + pa = pdiva; + for (j = 1; j < nr; j++) { /* slave adapters, if any */ + pa = diva_q_get_next(&pa->link); + if (pa && !pa->interface.cleanup_adapter_proc) { + pa->controller = i + 1 + j; + pa->xdi_adapter.ANum = pa->controller; + IoAdapters[i + j] = &pa->xdi_adapter; + diva_os_leave_spin_lock(&adapter_lock, &old_irql, "add card"); + DBG_LOG(("add slave adapter (%d)", + pa->controller)) + create_adapter_proc(pa); /* add adapter to proc file system */ + diva_os_enter_spin_lock(&adapter_lock, &old_irql, "add card"); + } else { + DBG_ERR(("slave adapter problem")) + break; + } + } + + diva_os_leave_spin_lock(&adapter_lock, &old_irql, "add card"); + return (pdiva); + } + } + + diva_os_leave_spin_lock(&adapter_lock, &old_irql, "add card"); + + /* + Not able to add adapter - remove it and return error + */ + DBG_ERR(("can not alloc request array")) + diva_driver_remove_card(pdiva); + + return NULL; + } + } + + return NULL; +} + +/* -------------------------------------------------------------------------- + Called on driver load, MAIN, main, DriverEntry + -------------------------------------------------------------------------- */ +int divasa_xdi_driver_entry(void) +{ + diva_os_initialize_spin_lock(&adapter_lock, "adapter"); + memset(&IoAdapters[0], 0x00, sizeof(IoAdapters)); + diva_init_request_array(); + + return (0); +} + +/* -------------------------------------------------------------------------- + Remove adapter from list + -------------------------------------------------------------------------- */ +static diva_os_xdi_adapter_t *get_and_remove_from_queue(void) +{ + diva_os_spin_lock_magic_t old_irql; + diva_os_xdi_adapter_t *a = NULL; + + diva_os_enter_spin_lock(&adapter_lock, &old_irql, "driver_unload"); + + if (!list_empty(&adapter_queue)) { + a = list_entry(adapter_queue.next, diva_os_xdi_adapter_t, link); + list_del(adapter_queue.next); + } + + diva_os_leave_spin_lock(&adapter_lock, &old_irql, "driver_unload"); + return (a); +} + +/* -------------------------------------------------------------------------- + Remove card from the card list + -------------------------------------------------------------------------- */ +void diva_driver_remove_card(void *pdiva) +{ + diva_os_spin_lock_magic_t old_irql; + diva_os_xdi_adapter_t *a[4]; + diva_os_xdi_adapter_t *pa; + int i; + + pa = a[0] = (diva_os_xdi_adapter_t *) pdiva; + a[1] = a[2] = a[3] = NULL; + + diva_os_enter_spin_lock(&adapter_lock, &old_irql, "remode adapter"); + + for (i = 1; i < 4; i++) { + if ((pa = diva_q_get_next(&pa->link)) + && !pa->interface.cleanup_adapter_proc) { + a[i] = pa; + } else { + break; + } + } + + for (i = 0; ((i < 4) && a[i]); i++) { + list_del(&a[i]->link); + } + + diva_os_leave_spin_lock(&adapter_lock, &old_irql, "driver_unload"); + + (*(a[0]->interface.cleanup_adapter_proc)) (a[0]); + + for (i = 0; i < 4; i++) { + if (a[i]) { + if (a[i]->controller) { + DBG_LOG(("remove adapter (%d)", + a[i]->controller)) IoAdapters[a[i]->controller - 1] = NULL; + remove_adapter_proc(a[i]); + } + diva_os_free(0, a[i]); + } + } +} + +/* -------------------------------------------------------------------------- + Create diva PCI adapter and init internal adapter structures + -------------------------------------------------------------------------- */ +static void *divas_create_pci_card(int handle, void *pci_dev_handle) +{ + diva_supported_cards_info_t *pI = &divas_supported_cards[handle]; + diva_os_spin_lock_magic_t old_irql; + diva_os_xdi_adapter_t *a; + + DBG_LOG(("found %d-%s", pI->CardOrdinal, CardProperties[pI->CardOrdinal].Name)) + + if (!(a = (diva_os_xdi_adapter_t *) diva_os_malloc(0, sizeof(*a)))) { + DBG_ERR(("A: can't alloc adapter")); + return NULL; + } + + memset(a, 0x00, sizeof(*a)); + + a->CardIndex = handle; + a->CardOrdinal = pI->CardOrdinal; + a->Bus = DIVAS_XDI_ADAPTER_BUS_PCI; + a->xdi_adapter.cardType = a->CardOrdinal; + a->resources.pci.bus = diva_os_get_pci_bus(pci_dev_handle); + a->resources.pci.func = diva_os_get_pci_func(pci_dev_handle); + a->resources.pci.hdev = pci_dev_handle; + + /* + Add master adapter first, so slave adapters will receive higher + numbers as master adapter + */ + diva_os_enter_spin_lock(&adapter_lock, &old_irql, "found_pci_card"); + list_add_tail(&a->link, &adapter_queue); + diva_os_leave_spin_lock(&adapter_lock, &old_irql, "found_pci_card"); + + if ((*(pI->init_card)) (a)) { + diva_os_enter_spin_lock(&adapter_lock, &old_irql, "found_pci_card"); + list_del(&a->link); + diva_os_leave_spin_lock(&adapter_lock, &old_irql, "found_pci_card"); + diva_os_free(0, a); + DBG_ERR(("A: can't get adapter resources")); + return NULL; + } + + return (a); +} + +/* -------------------------------------------------------------------------- + Called on driver unload FINIT, finit, Unload + -------------------------------------------------------------------------- */ +void divasa_xdi_driver_unload(void) +{ + diva_os_xdi_adapter_t *a; + + while ((a = get_and_remove_from_queue())) { + if (a->interface.cleanup_adapter_proc) { + (*(a->interface.cleanup_adapter_proc)) (a); + } + if (a->controller) { + IoAdapters[a->controller - 1] = NULL; + remove_adapter_proc(a); + } + diva_os_free(0, a); + } + diva_os_destroy_spin_lock(&adapter_lock, "adapter"); +} + +/* +** Receive and process command from user mode utility +*/ +void *diva_xdi_open_adapter(void *os_handle, const void __user *src, + int length, + divas_xdi_copy_from_user_fn_t cp_fn) +{ + diva_xdi_um_cfg_cmd_t msg; + diva_os_xdi_adapter_t *a = NULL; + diva_os_spin_lock_magic_t old_irql; + struct list_head *tmp; + + if (length < sizeof(diva_xdi_um_cfg_cmd_t)) { + DBG_ERR(("A: A(?) open, msg too small (%d < %d)", + length, sizeof(diva_xdi_um_cfg_cmd_t))) + return NULL; + } + if ((*cp_fn) (os_handle, &msg, src, sizeof(msg)) <= 0) { + DBG_ERR(("A: A(?) open, write error")) + return NULL; + } + diva_os_enter_spin_lock(&adapter_lock, &old_irql, "open_adapter"); + list_for_each(tmp, &adapter_queue) { + a = list_entry(tmp, diva_os_xdi_adapter_t, link); + if (a->controller == (int)msg.adapter) + break; + a = NULL; + } + diva_os_leave_spin_lock(&adapter_lock, &old_irql, "open_adapter"); + + if (!a) { + DBG_ERR(("A: A(%d) open, adapter not found", msg.adapter)) + } + + return (a); +} + +/* +** Easy cleanup mailbox status +*/ +void diva_xdi_close_adapter(void *adapter, void *os_handle) +{ + diva_os_xdi_adapter_t *a = (diva_os_xdi_adapter_t *) adapter; + + a->xdi_mbox.status &= ~DIVA_XDI_MBOX_BUSY; + if (a->xdi_mbox.data) { + diva_os_free(0, a->xdi_mbox.data); + a->xdi_mbox.data = NULL; + } +} + +int +diva_xdi_write(void *adapter, void *os_handle, const void __user *src, + int length, divas_xdi_copy_from_user_fn_t cp_fn) +{ + diva_os_xdi_adapter_t *a = (diva_os_xdi_adapter_t *) adapter; + void *data; + + if (a->xdi_mbox.status & DIVA_XDI_MBOX_BUSY) { + DBG_ERR(("A: A(%d) write, mbox busy", a->controller)) + return (-1); + } + + if (length < sizeof(diva_xdi_um_cfg_cmd_t)) { + DBG_ERR(("A: A(%d) write, message too small (%d < %d)", + a->controller, length, + sizeof(diva_xdi_um_cfg_cmd_t))) + return (-3); + } + + if (!(data = diva_os_malloc(0, length))) { + DBG_ERR(("A: A(%d) write, ENOMEM", a->controller)) + return (-2); + } + + length = (*cp_fn) (os_handle, data, src, length); + if (length > 0) { + if ((*(a->interface.cmd_proc)) + (a, (diva_xdi_um_cfg_cmd_t *) data, length)) { + length = -3; + } + } else { + DBG_ERR(("A: A(%d) write error (%d)", a->controller, + length)) + } + + diva_os_free(0, data); + + return (length); +} + +/* +** Write answers to user mode utility, if any +*/ +int +diva_xdi_read(void *adapter, void *os_handle, void __user *dst, + int max_length, divas_xdi_copy_to_user_fn_t cp_fn) +{ + diva_os_xdi_adapter_t *a = (diva_os_xdi_adapter_t *) adapter; + int ret; + + if (!(a->xdi_mbox.status & DIVA_XDI_MBOX_BUSY)) { + DBG_ERR(("A: A(%d) rx mbox empty", a->controller)) + return (-1); + } + if (!a->xdi_mbox.data) { + a->xdi_mbox.status &= ~DIVA_XDI_MBOX_BUSY; + DBG_ERR(("A: A(%d) rx ENOMEM", a->controller)) + return (-2); + } + + if (max_length < a->xdi_mbox.data_length) { + DBG_ERR(("A: A(%d) rx buffer too short(%d < %d)", + a->controller, max_length, + a->xdi_mbox.data_length)) + return (-3); + } + + ret = (*cp_fn) (os_handle, dst, a->xdi_mbox.data, + a->xdi_mbox.data_length); + if (ret > 0) { + diva_os_free(0, a->xdi_mbox.data); + a->xdi_mbox.data = NULL; + a->xdi_mbox.status &= ~DIVA_XDI_MBOX_BUSY; + } + + return (ret); +} + + +irqreturn_t diva_os_irq_wrapper(int irq, void *context, struct pt_regs *regs) +{ + diva_os_xdi_adapter_t *a = (diva_os_xdi_adapter_t *) context; + diva_xdi_clear_interrupts_proc_t clear_int_proc; + + if (!a || !a->xdi_adapter.diva_isr_handler) { + return IRQ_NONE; + } + + if ((clear_int_proc = a->clear_interrupts_proc)) { + (*clear_int_proc) (a); + a->clear_interrupts_proc = NULL; + return IRQ_HANDLED; + } + + (*(a->xdi_adapter.diva_isr_handler)) (&a->xdi_adapter); + return IRQ_HANDLED; +} + +static void diva_init_request_array(void) +{ + Requests[0] = DivaIdiRequest0; + Requests[1] = DivaIdiRequest1; + Requests[2] = DivaIdiRequest2; + Requests[3] = DivaIdiRequest3; + Requests[4] = DivaIdiRequest4; + Requests[5] = DivaIdiRequest5; + Requests[6] = DivaIdiRequest6; + Requests[7] = DivaIdiRequest7; + Requests[8] = DivaIdiRequest8; + Requests[9] = DivaIdiRequest9; + Requests[10] = DivaIdiRequest10; + Requests[11] = DivaIdiRequest11; + Requests[12] = DivaIdiRequest12; + Requests[13] = DivaIdiRequest13; + Requests[14] = DivaIdiRequest14; + Requests[15] = DivaIdiRequest15; + Requests[16] = DivaIdiRequest16; + Requests[17] = DivaIdiRequest17; + Requests[18] = DivaIdiRequest18; + Requests[19] = DivaIdiRequest19; + Requests[20] = DivaIdiRequest20; + Requests[21] = DivaIdiRequest21; + Requests[22] = DivaIdiRequest22; + Requests[23] = DivaIdiRequest23; + Requests[24] = DivaIdiRequest24; + Requests[25] = DivaIdiRequest25; + Requests[26] = DivaIdiRequest26; + Requests[27] = DivaIdiRequest27; + Requests[28] = DivaIdiRequest28; + Requests[29] = DivaIdiRequest29; + Requests[30] = DivaIdiRequest30; + Requests[31] = DivaIdiRequest31; +} + +void diva_xdi_display_adapter_features(int card) +{ + dword features; + if (!card || ((card - 1) >= MAX_ADAPTER) || !IoAdapters[card - 1]) { + return; + } + card--; + features = IoAdapters[card]->Properties.Features; + + DBG_LOG(("FEATURES FOR ADAPTER: %d", card + 1)) + DBG_LOG((" DI_FAX3 : %s", + (features & DI_FAX3) ? "Y" : "N")) + DBG_LOG((" DI_MODEM : %s", + (features & DI_MODEM) ? "Y" : "N")) + DBG_LOG((" DI_POST : %s", + (features & DI_POST) ? "Y" : "N")) + DBG_LOG((" DI_V110 : %s", + (features & DI_V110) ? "Y" : "N")) + DBG_LOG((" DI_V120 : %s", + (features & DI_V120) ? "Y" : "N")) + DBG_LOG((" DI_POTS : %s", + (features & DI_POTS) ? "Y" : "N")) + DBG_LOG((" DI_CODEC : %s", + (features & DI_CODEC) ? "Y" : "N")) + DBG_LOG((" DI_MANAGE : %s", + (features & DI_MANAGE) ? "Y" : "N")) + DBG_LOG((" DI_V_42 : %s", + (features & DI_V_42) ? "Y" : "N")) + DBG_LOG((" DI_EXTD_FAX : %s", + (features & DI_EXTD_FAX) ? "Y" : "N")) + DBG_LOG((" DI_AT_PARSER : %s", + (features & DI_AT_PARSER) ? "Y" : "N")) + DBG_LOG((" DI_VOICE_OVER_IP : %s", + (features & DI_VOICE_OVER_IP) ? "Y" : "N")) +} + +void diva_add_slave_adapter(diva_os_xdi_adapter_t * a) +{ + diva_os_spin_lock_magic_t old_irql; + + diva_os_enter_spin_lock(&adapter_lock, &old_irql, "add_slave"); + list_add_tail(&a->link, &adapter_queue); + diva_os_leave_spin_lock(&adapter_lock, &old_irql, "add_slave"); +} + +int diva_card_read_xlog(diva_os_xdi_adapter_t * a) +{ + diva_get_xlog_t *req; + byte *data; + + if (!a->xdi_adapter.Initialized || !a->xdi_adapter.DIRequest) { + return (-1); + } + if (!(data = diva_os_malloc(0, sizeof(struct mi_pc_maint)))) { + return (-1); + } + memset(data, 0x00, sizeof(struct mi_pc_maint)); + + if (!(req = diva_os_malloc(0, sizeof(*req)))) { + diva_os_free(0, data); + return (-1); + } + req->command = 0x0400; + req->req = LOG; + req->rc = 0x00; + + (*(a->xdi_adapter.DIRequest)) (&a->xdi_adapter, (ENTITY *) req); + + if (!req->rc || req->req) { + diva_os_free(0, data); + diva_os_free(0, req); + return (-1); + } + + memcpy(data, &req->req, sizeof(struct mi_pc_maint)); + + diva_os_free(0, req); + + a->xdi_mbox.data_length = sizeof(struct mi_pc_maint); + a->xdi_mbox.data = data; + a->xdi_mbox.status = DIVA_XDI_MBOX_BUSY; + + return (0); +} + +void xdiFreeFile(void *handle) +{ +} diff --git a/drivers/isdn/hardware/eicon/diva.h b/drivers/isdn/hardware/eicon/diva.h new file mode 100644 index 000000000000..e979085d1b89 --- /dev/null +++ b/drivers/isdn/hardware/eicon/diva.h @@ -0,0 +1,31 @@ +/* $Id: diva.h,v 1.1.2.2 2001/02/08 12:25:43 armin Exp $ */ + +#ifndef __DIVA_XDI_OS_PART_H__ +#define __DIVA_XDI_OS_PART_H__ + + +int divasa_xdi_driver_entry(void); +void divasa_xdi_driver_unload(void); +void *diva_driver_add_card(void *pdev, unsigned long CardOrdinal); +void diva_driver_remove_card(void *pdiva); + +typedef int (*divas_xdi_copy_to_user_fn_t) (void *os_handle, void __user *dst, + const void *src, int length); + +typedef int (*divas_xdi_copy_from_user_fn_t) (void *os_handle, void *dst, + const void __user *src, int length); + +int diva_xdi_read(void *adapter, void *os_handle, void __user *dst, + int max_length, divas_xdi_copy_to_user_fn_t cp_fn); + +int diva_xdi_write(void *adapter, void *os_handle, const void __user *src, + int length, divas_xdi_copy_from_user_fn_t cp_fn); + +void *diva_xdi_open_adapter(void *os_handle, const void __user *src, + int length, + divas_xdi_copy_from_user_fn_t cp_fn); + +void diva_xdi_close_adapter(void *adapter, void *os_handle); + + +#endif diff --git a/drivers/isdn/hardware/eicon/diva_didd.c b/drivers/isdn/hardware/eicon/diva_didd.c new file mode 100644 index 000000000000..7fdf8ae5be52 --- /dev/null +++ b/drivers/isdn/hardware/eicon/diva_didd.c @@ -0,0 +1,151 @@ +/* $Id: diva_didd.c,v 1.13.6.4 2005/02/11 19:40:25 armin Exp $ + * + * DIDD Interface module for Eicon active cards. + * + * Functions are in dadapter.c + * + * Copyright 2002-2003 by Armin Schindler (mac@melware.de) + * Copyright 2002-2003 Cytronics & Melware (info@melware.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/proc_fs.h> + +#include "platform.h" +#include "di_defs.h" +#include "dadapter.h" +#include "divasync.h" +#include "did_vers.h" + +static char *main_revision = "$Revision: 1.13.6.4 $"; + +static char *DRIVERNAME = + "Eicon DIVA - DIDD table (http://www.melware.net)"; +static char *DRIVERLNAME = "divadidd"; +char *DRIVERRELEASE_DIDD = "2.0"; + +static char *main_proc_dir = "eicon"; + +MODULE_DESCRIPTION("DIDD table driver for diva drivers"); +MODULE_AUTHOR("Cytronics & Melware, Eicon Networks"); +MODULE_SUPPORTED_DEVICE("Eicon diva drivers"); +MODULE_LICENSE("GPL"); + +#define DBG_MINIMUM (DL_LOG + DL_FTL + DL_ERR) +#define DBG_DEFAULT (DBG_MINIMUM + DL_XLOG + DL_REG) + +extern int diddfunc_init(void); +extern void diddfunc_finit(void); + +extern void DIVA_DIDD_Read(void *, int); + +static struct proc_dir_entry *proc_didd; +struct proc_dir_entry *proc_net_eicon = NULL; + +EXPORT_SYMBOL(DIVA_DIDD_Read); +EXPORT_SYMBOL(proc_net_eicon); + +static char *getrev(const char *revision) +{ + char *rev; + char *p; + if ((p = strchr(revision, ':'))) { + rev = p + 2; + p = strchr(rev, '$'); + *--p = 0; + } else + rev = "1.0"; + return rev; +} + +static int +proc_read(char *page, char **start, off_t off, int count, int *eof, + void *data) +{ + int len = 0; + char tmprev[32]; + + strcpy(tmprev, main_revision); + len += sprintf(page + len, "%s\n", DRIVERNAME); + len += sprintf(page + len, "name : %s\n", DRIVERLNAME); + len += sprintf(page + len, "release : %s\n", DRIVERRELEASE_DIDD); + len += sprintf(page + len, "build : %s(%s)\n", + diva_didd_common_code_build, DIVA_BUILD); + len += sprintf(page + len, "revision : %s\n", getrev(tmprev)); + + if (off + count >= len) + *eof = 1; + if (len < off) + return 0; + *start = page + off; + return ((count < len - off) ? count : len - off); +} + +static int DIVA_INIT_FUNCTION create_proc(void) +{ + proc_net_eicon = create_proc_entry(main_proc_dir, S_IFDIR, proc_net); + + if (proc_net_eicon) { + if ((proc_didd = + create_proc_entry(DRIVERLNAME, S_IFREG | S_IRUGO, + proc_net_eicon))) { + proc_didd->read_proc = proc_read; + } + return (1); + } + return (0); +} + +static void DIVA_EXIT_FUNCTION remove_proc(void) +{ + remove_proc_entry(DRIVERLNAME, proc_net_eicon); + remove_proc_entry(main_proc_dir, proc_net); +} + +static int DIVA_INIT_FUNCTION divadidd_init(void) +{ + char tmprev[32]; + int ret = 0; + + printk(KERN_INFO "%s\n", DRIVERNAME); + printk(KERN_INFO "%s: Rel:%s Rev:", DRIVERLNAME, DRIVERRELEASE_DIDD); + strcpy(tmprev, main_revision); + printk("%s Build:%s(%s)\n", getrev(tmprev), + diva_didd_common_code_build, DIVA_BUILD); + + if (!create_proc()) { + printk(KERN_ERR "%s: could not create proc entry\n", + DRIVERLNAME); + ret = -EIO; + goto out; + } + + if (!diddfunc_init()) { + printk(KERN_ERR "%s: failed to connect to DIDD.\n", + DRIVERLNAME); +#ifdef MODULE + remove_proc(); +#endif + ret = -EIO; + goto out; + } + + out: + return (ret); +} + +static void DIVA_EXIT_FUNCTION divadidd_exit(void) +{ + diddfunc_finit(); + remove_proc(); + printk(KERN_INFO "%s: module unloaded.\n", DRIVERLNAME); +} + +module_init(divadidd_init); +module_exit(divadidd_exit); diff --git a/drivers/isdn/hardware/eicon/diva_dma.c b/drivers/isdn/hardware/eicon/diva_dma.c new file mode 100644 index 000000000000..f53a7407605f --- /dev/null +++ b/drivers/isdn/hardware/eicon/diva_dma.c @@ -0,0 +1,94 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +#include "platform.h" +#include "diva_dma.h" +/* + Every entry has length of PAGE_SIZE + and represents one single physical page + */ +struct _diva_dma_map_entry { + int busy; + dword phys_bus_addr; /* 32bit address as seen by the card */ + void* local_ram_addr; /* local address as seen by the host */ + void* addr_handle; /* handle uset to free allocated memory */ +}; +/* + Create local mapping structure and init it to default state + */ +struct _diva_dma_map_entry* diva_alloc_dma_map (void* os_context, int nentries) { + diva_dma_map_entry_t* pmap = diva_os_malloc(0, sizeof(*pmap)*(nentries+1)); + if (pmap) + memset (pmap, 0, sizeof(*pmap)*(nentries+1)); + return pmap; +} +/* + Free local map (context should be freed before) if any + */ +void diva_free_dma_mapping (struct _diva_dma_map_entry* pmap) { + if (pmap) { + diva_os_free (0, pmap); + } +} +/* + Set information saved on the map entry + */ +void diva_init_dma_map_entry (struct _diva_dma_map_entry* pmap, + int nr, void* virt, dword phys, + void* addr_handle) { + pmap[nr].phys_bus_addr = phys; + pmap[nr].local_ram_addr = virt; + pmap[nr].addr_handle = addr_handle; +} +/* + Allocate one single entry in the map + */ +int diva_alloc_dma_map_entry (struct _diva_dma_map_entry* pmap) { + int i; + for (i = 0; (pmap && pmap[i].local_ram_addr); i++) { + if (!pmap[i].busy) { + pmap[i].busy = 1; + return (i); + } + } + return (-1); +} +/* + Free one single entry in the map + */ +void diva_free_dma_map_entry (struct _diva_dma_map_entry* pmap, int nr) { + pmap[nr].busy = 0; +} +/* + Get information saved on the map entry + */ +void diva_get_dma_map_entry (struct _diva_dma_map_entry* pmap, int nr, + void** pvirt, dword* pphys) { + *pphys = pmap[nr].phys_bus_addr; + *pvirt = pmap[nr].local_ram_addr; +} +void* diva_get_entry_handle (struct _diva_dma_map_entry* pmap, int nr) { + return (pmap[nr].addr_handle); +} diff --git a/drivers/isdn/hardware/eicon/diva_dma.h b/drivers/isdn/hardware/eicon/diva_dma.h new file mode 100644 index 000000000000..dff80724cdbd --- /dev/null +++ b/drivers/isdn/hardware/eicon/diva_dma.h @@ -0,0 +1,48 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +#ifndef __DIVA_DMA_MAPPING_IFC_H__ +#define __DIVA_DMA_MAPPING_IFC_H__ +typedef struct _diva_dma_map_entry diva_dma_map_entry_t; +struct _diva_dma_map_entry* diva_alloc_dma_map (void* os_context, int nentries); +void diva_init_dma_map_entry (struct _diva_dma_map_entry* pmap, + int nr, void* virt, dword phys, + void* addr_handle); +int diva_alloc_dma_map_entry (struct _diva_dma_map_entry* pmap); +void diva_free_dma_map_entry (struct _diva_dma_map_entry* pmap, int entry); +void diva_get_dma_map_entry (struct _diva_dma_map_entry* pmap, int nr, + void** pvirt, dword* pphys); +void diva_free_dma_mapping (struct _diva_dma_map_entry* pmap); +/* + Functionality to be implemented by OS wrapper + and running in process context + */ +void diva_init_dma_map (void* hdev, + struct _diva_dma_map_entry** ppmap, + int nentries); +void diva_free_dma_map (void* hdev, + struct _diva_dma_map_entry* pmap); +void* diva_get_entry_handle (struct _diva_dma_map_entry* pmap, int nr); +#endif diff --git a/drivers/isdn/hardware/eicon/diva_pci.h b/drivers/isdn/hardware/eicon/diva_pci.h new file mode 100644 index 000000000000..cc0d5102723a --- /dev/null +++ b/drivers/isdn/hardware/eicon/diva_pci.h @@ -0,0 +1,19 @@ +/* $Id: diva_pci.h,v 1.6 2003/01/04 15:29:45 schindler Exp $ */ + +#ifndef __DIVA_PCI_INTERFACE_H__ +#define __DIVA_PCI_INTERFACE_H__ + +void __iomem *divasa_remap_pci_bar(diva_os_xdi_adapter_t *a, + int id, + unsigned long bar, + unsigned long area_length); +void divasa_unmap_pci_bar(void __iomem *bar); +unsigned long divasa_get_pci_irq(unsigned char bus, + unsigned char func, void *pci_dev_handle); +unsigned long divasa_get_pci_bar(unsigned char bus, + unsigned char func, + int bar, void *pci_dev_handle); +byte diva_os_get_pci_bus(void *pci_dev_handle); +byte diva_os_get_pci_func(void *pci_dev_handle); + +#endif diff --git a/drivers/isdn/hardware/eicon/divacapi.h b/drivers/isdn/hardware/eicon/divacapi.h new file mode 100644 index 000000000000..9f5b68037a26 --- /dev/null +++ b/drivers/isdn/hardware/eicon/divacapi.h @@ -0,0 +1,1360 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ + +/*#define DEBUG */ + + + + + + + + + + + +#define IMPLEMENT_DTMF 1 +#define IMPLEMENT_LINE_INTERCONNECT2 1 +#define IMPLEMENT_ECHO_CANCELLER 1 +#define IMPLEMENT_RTP 1 +#define IMPLEMENT_T38 1 +#define IMPLEMENT_FAX_SUB_SEP_PWD 1 +#define IMPLEMENT_V18 1 +#define IMPLEMENT_DTMF_TONE 1 +#define IMPLEMENT_PIAFS 1 +#define IMPLEMENT_FAX_PAPER_FORMATS 1 +#define IMPLEMENT_VOWN 1 +#define IMPLEMENT_CAPIDTMF 1 +#define IMPLEMENT_FAX_NONSTANDARD 1 +#define VSWITCH_SUPPORT 1 + + +#define IMPLEMENT_LINE_INTERCONNECT 0 +#define IMPLEMENT_MARKED_OK_AFTER_FC 1 + +#include "capidtmf.h" + +/*------------------------------------------------------------------*/ +/* Common API internal definitions */ +/*------------------------------------------------------------------*/ + +#define MAX_APPL 240 +#define MAX_NCCI 127 + +#define MSG_IN_QUEUE_SIZE ((4096 + 3) & 0xfffc) /* must be multiple of 4 */ + + +#define MSG_IN_OVERHEAD sizeof(APPL *) + +#define MAX_NL_CHANNEL 255 +#define MAX_DATA_B3 8 +#define MAX_DATA_ACK MAX_DATA_B3 +#define MAX_MULTI_IE 6 +#define MAX_MSG_SIZE 256 +#define MAX_MSG_PARMS 10 +#define MAX_CPN_MASK_SIZE 16 +#define MAX_MSN_CONFIG 10 +#define EXT_CONTROLLER 0x80 +#define CODEC 0x01 +#define CODEC_PERMANENT 0x02 +#define ADV_VOICE 0x03 +#define MAX_CIP_TYPES 5 /* kind of CIP types for group optimization */ +#define C_IND_MASK_DWORDS ((MAX_APPL+32) >> 5) + + +#define FAX_CONNECT_INFO_BUFFER_SIZE 256 +#define NCPI_BUFFER_SIZE 256 + +#define MAX_CHANNELS_PER_PLCI 8 +#define MAX_INTERNAL_COMMAND_LEVELS 4 +#define INTERNAL_REQ_BUFFER_SIZE 272 + +#define INTERNAL_IND_BUFFER_SIZE 768 + +#define DTMF_PARAMETER_BUFFER_SIZE 12 +#define ADV_VOICE_COEF_BUFFER_SIZE 50 + +#define LI_PLCI_B_QUEUE_ENTRIES 256 + + + +typedef struct _APPL APPL; +typedef struct _PLCI PLCI; +typedef struct _NCCI NCCI; +typedef struct _DIVA_CAPI_ADAPTER DIVA_CAPI_ADAPTER; +typedef struct _DATA_B3_DESC DATA_B3_DESC; +typedef struct _DATA_ACK_DESC DATA_ACK_DESC; +typedef struct manufacturer_profile_s MANUFACTURER_PROFILE; +typedef struct fax_ncpi_s FAX_NCPI; +typedef struct api_parse_s API_PARSE; +typedef struct api_save_s API_SAVE; +typedef struct msn_config_s MSN_CONFIG; +typedef struct msn_config_max_s MSN_CONFIG_MAX; +typedef struct msn_ld_s MSN_LD; + +struct manufacturer_profile_s { + dword private_options; + dword rtp_primary_payloads; + dword rtp_additional_payloads; +}; + +struct fax_ncpi_s { + word options; + word format; +}; + +struct msn_config_s { + byte msn[MAX_CPN_MASK_SIZE]; +}; + +struct msn_config_max_s { + MSN_CONFIG msn_conf[MAX_MSN_CONFIG]; +}; + +struct msn_ld_s { + dword low; + dword high; +}; + +struct api_parse_s { + word length; + byte * info; +}; + +struct api_save_s { + API_PARSE parms[MAX_MSG_PARMS+1]; + byte info[MAX_MSG_SIZE]; +}; + +struct _DATA_B3_DESC { + word Handle; + word Number; + word Flags; + word Length; + void * P; +}; + +struct _DATA_ACK_DESC { + word Handle; + word Number; +}; + +typedef void (* t_std_internal_command)(dword Id, PLCI *plci, byte Rc); + +/************************************************************************/ +/* Don't forget to adapt dos.asm after changing the _APPL structure!!!! */ +struct _APPL { + word Id; + word NullCREnable; + word CDEnable; + dword S_Handle; + + + + + + + LIST_ENTRY s_function; + dword s_context; + word s_count; + APPL * s_next; + byte * xbuffer_used; + void ** xbuffer_internal; + void ** xbuffer_ptr; + + + + + + + byte * queue; + word queue_size; + word queue_free; + word queue_read; + word queue_write; + word queue_signal; + byte msg_lost; + byte appl_flags; + word Number; + + word MaxBuffer; + byte MaxNCCI; + byte MaxNCCIData; + word MaxDataLength; + word NCCIDataFlowCtrlTimer; + byte * ReceiveBuffer; + word * DataNCCI; + word * DataFlags; +}; + + +struct _PLCI { + ENTITY Sig; + ENTITY NL; + word RNum; + word RFlags; + BUFFERS RData[2]; + BUFFERS XData[1]; + BUFFERS NData[2]; + + DIVA_CAPI_ADAPTER *adapter; + APPL *appl; + PLCI *relatedPTYPLCI; + byte Id; + byte State; + byte sig_req; + byte nl_req; + byte SuppState; + byte channels; + byte tel; + byte B1_resource; + byte B2_prot; + byte B3_prot; + + word command; + word m_command; + word internal_command; + word number; + word req_in_start; + word req_in; + word req_out; + word msg_in_write_pos; + word msg_in_read_pos; + word msg_in_wrap_pos; + + void * data_sent_ptr; + byte data_sent; + byte send_disc; + byte sig_global_req; + byte sig_remove_id; + byte nl_global_req; + byte nl_remove_id; + byte b_channel; + byte adv_nl; + byte manufacturer; + byte call_dir; + byte hook_state; + byte spoofed_msg; + byte ptyState; + byte cr_enquiry; + word hangup_flow_ctrl_timer; + + word ncci_ring_list; + byte inc_dis_ncci_table[MAX_CHANNELS_PER_PLCI]; + t_std_internal_command internal_command_queue[MAX_INTERNAL_COMMAND_LEVELS]; + dword c_ind_mask_table[C_IND_MASK_DWORDS]; + dword group_optimization_mask_table[C_IND_MASK_DWORDS]; + byte RBuffer[200]; + dword msg_in_queue[MSG_IN_QUEUE_SIZE/sizeof(dword)]; + API_SAVE saved_msg; + API_SAVE B_protocol; + byte fax_connect_info_length; + byte fax_connect_info_buffer[FAX_CONNECT_INFO_BUFFER_SIZE]; + byte fax_edata_ack_length; + word nsf_control_bits; + byte ncpi_state; + byte ncpi_buffer[NCPI_BUFFER_SIZE]; + + byte internal_req_buffer[INTERNAL_REQ_BUFFER_SIZE]; + byte internal_ind_buffer[INTERNAL_IND_BUFFER_SIZE + 3]; + dword requested_options_conn; + dword requested_options; + word B1_facilities; + API_SAVE *adjust_b_parms_msg; + word adjust_b_facilities; + word adjust_b_command; + word adjust_b_ncci; + word adjust_b_mode; + word adjust_b_state; + byte adjust_b_restore; + + byte dtmf_rec_active; + word dtmf_rec_pulse_ms; + word dtmf_rec_pause_ms; + byte dtmf_send_requests; + word dtmf_send_pulse_ms; + word dtmf_send_pause_ms; + word dtmf_cmd; + word dtmf_msg_number_queue[8]; + byte dtmf_parameter_length; + byte dtmf_parameter_buffer[DTMF_PARAMETER_BUFFER_SIZE]; + + + t_capidtmf_state capidtmf_state; + + + byte li_bchannel_id; /* BRI: 1..2, PRI: 1..32 */ + byte li_channel_bits; + byte li_notify_update; + word li_cmd; + word li_write_command; + word li_write_channel; + word li_plci_b_write_pos; + word li_plci_b_read_pos; + word li_plci_b_req_pos; + dword li_plci_b_queue[LI_PLCI_B_QUEUE_ENTRIES]; + + + word ec_cmd; + word ec_idi_options; + word ec_tail_length; + + + byte tone_last_indication_code; + + byte vswitchstate; + byte vsprot; + byte vsprotdialect; + byte notifiedcall; /* Flag if it is a spoofed call */ + + int rx_dma_descriptor; + dword rx_dma_magic; +}; + + +struct _NCCI { + byte data_out; + byte data_pending; + byte data_ack_out; + byte data_ack_pending; + DATA_B3_DESC DBuffer[MAX_DATA_B3]; + DATA_ACK_DESC DataAck[MAX_DATA_ACK]; +}; + + +struct _DIVA_CAPI_ADAPTER { + IDI_CALL request; + byte Id; + byte max_plci; + byte max_listen; + byte listen_active; + PLCI *plci; + byte ch_ncci[MAX_NL_CHANNEL+1]; + byte ncci_ch[MAX_NCCI+1]; + byte ncci_plci[MAX_NCCI+1]; + byte ncci_state[MAX_NCCI+1]; + byte ncci_next[MAX_NCCI+1]; + NCCI ncci[MAX_NCCI+1]; + + byte ch_flow_control[MAX_NL_CHANNEL+1]; /* Used by XON protocol */ + byte ch_flow_control_pending; + byte ch_flow_plci[MAX_NL_CHANNEL+1]; + int last_flow_control_ch; + + dword Info_Mask[MAX_APPL]; + dword CIP_Mask[MAX_APPL]; + + dword Notification_Mask[MAX_APPL]; + PLCI *codec_listen[MAX_APPL]; + dword requested_options_table[MAX_APPL]; + API_PROFILE profile; + MANUFACTURER_PROFILE man_profile; + dword manufacturer_features; + + byte AdvCodecFLAG; + PLCI *AdvCodecPLCI; + PLCI *AdvSignalPLCI; + APPL *AdvSignalAppl; + byte TelOAD[23]; + byte TelOSA[23]; + byte scom_appl_disable; + PLCI *automatic_lawPLCI; + byte automatic_law; + byte u_law; + + byte adv_voice_coef_length; + byte adv_voice_coef_buffer[ADV_VOICE_COEF_BUFFER_SIZE]; + + byte li_pri; + byte li_channels; + word li_base; + + byte adapter_disabled; + byte group_optimization_enabled; /* use application groups if enabled */ + dword sdram_bar; + byte flag_dynamic_l1_down; /* for hunt groups:down layer 1 if no appl present*/ + byte FlowControlIdTable[256]; + byte FlowControlSkipTable[256]; + void* os_card; /* pointer to associated OS dependent adapter structure */ +}; + + +/*------------------------------------------------------------------*/ +/* Application flags */ +/*------------------------------------------------------------------*/ + +#define APPL_FLAG_OLD_LI_SPEC 0x01 +#define APPL_FLAG_PRIV_EC_SPEC 0x02 + + +/*------------------------------------------------------------------*/ +/* API parameter definitions */ +/*------------------------------------------------------------------*/ + +#define X75_TTX 1 /* x.75 for ttx */ +#define TRF 2 /* transparent with hdlc framing */ +#define TRF_IN 3 /* transparent with hdlc fr. inc. */ +#define SDLC 4 /* sdlc, sna layer-2 */ +#define X75_BTX 5 /* x.75 for btx */ +#define LAPD 6 /* lapd (Q.921) */ +#define X25_L2 7 /* x.25 layer-2 */ +#define V120_L2 8 /* V.120 layer-2 protocol */ +#define V42_IN 9 /* V.42 layer-2 protocol, incomming */ +#define V42 10 /* V.42 layer-2 protocol */ +#define MDM_ATP 11 /* AT Parser built in the L2 */ +#define X75_V42BIS 12 /* ISO7776 (X.75 SLP) modified to support V.42 bis compression */ +#define RTPL2_IN 13 /* RTP layer-2 protocol, incomming */ +#define RTPL2 14 /* RTP layer-2 protocol */ +#define V120_V42BIS 15 /* V.120 layer-2 protocol supporting V.42 bis compression */ + +#define T70NL 1 +#define X25PLP 2 +#define T70NLX 3 +#define TRANSPARENT_NL 4 +#define ISO8208 5 +#define T30 6 + + +/*------------------------------------------------------------------*/ +/* FAX interface to IDI */ +/*------------------------------------------------------------------*/ + +#define CAPI_MAX_HEAD_LINE_SPACE 89 +#define CAPI_MAX_DATE_TIME_LENGTH 18 + +#define T30_MAX_STATION_ID_LENGTH 20 +#define T30_MAX_SUBADDRESS_LENGTH 20 +#define T30_MAX_PASSWORD_LENGTH 20 + +typedef struct t30_info_s T30_INFO; +struct t30_info_s { + byte code; + byte rate_div_2400; + byte resolution; + byte data_format; + byte pages_low; + byte pages_high; + byte operating_mode; + byte control_bits_low; + byte control_bits_high; + byte feature_bits_low; + byte feature_bits_high; + byte recording_properties; + byte universal_6; + byte universal_7; + byte station_id_len; + byte head_line_len; + byte station_id[T30_MAX_STATION_ID_LENGTH]; +/* byte head_line[]; */ +/* byte sub_sep_length; */ +/* byte sub_sep_field[]; */ +/* byte pwd_length; */ +/* byte pwd_field[]; */ +/* byte nsf_info_length; */ +/* byte nsf_info_field[]; */ +}; + + +#define T30_RESOLUTION_R8_0385 0x00 +#define T30_RESOLUTION_R8_0770_OR_200 0x01 +#define T30_RESOLUTION_R8_1540 0x02 +#define T30_RESOLUTION_R16_1540_OR_400 0x04 +#define T30_RESOLUTION_R4_0385_OR_100 0x08 +#define T30_RESOLUTION_300_300 0x10 +#define T30_RESOLUTION_INCH_BASED 0x40 +#define T30_RESOLUTION_METRIC_BASED 0x80 + +#define T30_RECORDING_WIDTH_ISO_A4 0 +#define T30_RECORDING_WIDTH_ISO_B4 1 +#define T30_RECORDING_WIDTH_ISO_A3 2 +#define T30_RECORDING_WIDTH_COUNT 3 + +#define T30_RECORDING_LENGTH_ISO_A4 0 +#define T30_RECORDING_LENGTH_ISO_B4 1 +#define T30_RECORDING_LENGTH_UNLIMITED 2 +#define T30_RECORDING_LENGTH_COUNT 3 + +#define T30_MIN_SCANLINE_TIME_00_00_00 0 +#define T30_MIN_SCANLINE_TIME_05_05_05 1 +#define T30_MIN_SCANLINE_TIME_10_05_05 2 +#define T30_MIN_SCANLINE_TIME_10_10_10 3 +#define T30_MIN_SCANLINE_TIME_20_10_10 4 +#define T30_MIN_SCANLINE_TIME_20_20_20 5 +#define T30_MIN_SCANLINE_TIME_40_20_20 6 +#define T30_MIN_SCANLINE_TIME_40_40_40 7 +#define T30_MIN_SCANLINE_TIME_RES_8 8 +#define T30_MIN_SCANLINE_TIME_RES_9 9 +#define T30_MIN_SCANLINE_TIME_RES_10 10 +#define T30_MIN_SCANLINE_TIME_10_10_05 11 +#define T30_MIN_SCANLINE_TIME_20_10_05 12 +#define T30_MIN_SCANLINE_TIME_20_20_10 13 +#define T30_MIN_SCANLINE_TIME_40_20_10 14 +#define T30_MIN_SCANLINE_TIME_40_40_20 15 +#define T30_MIN_SCANLINE_TIME_COUNT 16 + +#define T30_DATA_FORMAT_SFF 0 +#define T30_DATA_FORMAT_ASCII 1 +#define T30_DATA_FORMAT_NATIVE 2 +#define T30_DATA_FORMAT_COUNT 3 + + +#define T30_OPERATING_MODE_STANDARD 0 +#define T30_OPERATING_MODE_CLASS2 1 +#define T30_OPERATING_MODE_CLASS1 2 +#define T30_OPERATING_MODE_CAPI 3 +#define T30_OPERATING_MODE_CAPI_NEG 4 +#define T30_OPERATING_MODE_COUNT 5 + + /* EDATA transmit messages */ +#define EDATA_T30_DIS 0x01 +#define EDATA_T30_FTT 0x02 +#define EDATA_T30_MCF 0x03 +#define EDATA_T30_PARAMETERS 0x04 + + /* EDATA receive messages */ +#define EDATA_T30_DCS 0x81 +#define EDATA_T30_TRAIN_OK 0x82 +#define EDATA_T30_EOP 0x83 +#define EDATA_T30_MPS 0x84 +#define EDATA_T30_EOM 0x85 +#define EDATA_T30_DTC 0x86 +#define EDATA_T30_PAGE_END 0x87 /* Indicates end of page data. Reserved, but not implemented ! */ +#define EDATA_T30_EOP_CAPI 0x88 + + +#define T30_SUCCESS 0 +#define T30_ERR_NO_DIS_RECEIVED 1 +#define T30_ERR_TIMEOUT_NO_RESPONSE 2 +#define T30_ERR_RETRY_NO_RESPONSE 3 +#define T30_ERR_TOO_MANY_REPEATS 4 +#define T30_ERR_UNEXPECTED_MESSAGE 5 +#define T30_ERR_UNEXPECTED_DCN 6 +#define T30_ERR_DTC_UNSUPPORTED 7 +#define T30_ERR_ALL_RATES_FAILED 8 +#define T30_ERR_TOO_MANY_TRAINS 9 +#define T30_ERR_RECEIVE_CORRUPTED 10 +#define T30_ERR_UNEXPECTED_DISC 11 +#define T30_ERR_APPLICATION_DISC 12 +#define T30_ERR_INCOMPATIBLE_DIS 13 +#define T30_ERR_INCOMPATIBLE_DCS 14 +#define T30_ERR_TIMEOUT_NO_COMMAND 15 +#define T30_ERR_RETRY_NO_COMMAND 16 +#define T30_ERR_TIMEOUT_COMMAND_TOO_LONG 17 +#define T30_ERR_TIMEOUT_RESPONSE_TOO_LONG 18 +#define T30_ERR_NOT_IDENTIFIED 19 +#define T30_ERR_SUPERVISORY_TIMEOUT 20 +#define T30_ERR_TOO_LONG_SCAN_LINE 21 +/* #define T30_ERR_RETRY_NO_PAGE_AFTER_MPS 22 */ +#define T30_ERR_RETRY_NO_PAGE_RECEIVED 23 +#define T30_ERR_RETRY_NO_DCS_AFTER_FTT 24 +#define T30_ERR_RETRY_NO_DCS_AFTER_EOM 25 +#define T30_ERR_RETRY_NO_DCS_AFTER_MPS 26 +#define T30_ERR_RETRY_NO_DCN_AFTER_MCF 27 +#define T30_ERR_RETRY_NO_DCN_AFTER_RTN 28 +#define T30_ERR_RETRY_NO_CFR 29 +#define T30_ERR_RETRY_NO_MCF_AFTER_EOP 30 +#define T30_ERR_RETRY_NO_MCF_AFTER_EOM 31 +#define T30_ERR_RETRY_NO_MCF_AFTER_MPS 32 +#define T30_ERR_SUB_SEP_UNSUPPORTED 33 +#define T30_ERR_PWD_UNSUPPORTED 34 +#define T30_ERR_SUB_SEP_PWD_UNSUPPORTED 35 +#define T30_ERR_INVALID_COMMAND_FRAME 36 +#define T30_ERR_UNSUPPORTED_PAGE_CODING 37 +#define T30_ERR_INVALID_PAGE_CODING 38 +#define T30_ERR_INCOMPATIBLE_PAGE_CONFIG 39 +#define T30_ERR_TIMEOUT_FROM_APPLICATION 40 +#define T30_ERR_V34FAX_NO_REACTION_ON_MARK 41 +#define T30_ERR_V34FAX_TRAINING_TIMEOUT 42 +#define T30_ERR_V34FAX_UNEXPECTED_V21 43 +#define T30_ERR_V34FAX_PRIMARY_CTS_ON 44 +#define T30_ERR_V34FAX_TURNAROUND_POLLING 45 +#define T30_ERR_V34FAX_V8_INCOMPATIBILITY 46 + + +#define T30_CONTROL_BIT_DISABLE_FINE 0x0001 +#define T30_CONTROL_BIT_ENABLE_ECM 0x0002 +#define T30_CONTROL_BIT_ECM_64_BYTES 0x0004 +#define T30_CONTROL_BIT_ENABLE_2D_CODING 0x0008 +#define T30_CONTROL_BIT_ENABLE_T6_CODING 0x0010 +#define T30_CONTROL_BIT_ENABLE_UNCOMPR 0x0020 +#define T30_CONTROL_BIT_ACCEPT_POLLING 0x0040 +#define T30_CONTROL_BIT_REQUEST_POLLING 0x0080 +#define T30_CONTROL_BIT_MORE_DOCUMENTS 0x0100 +#define T30_CONTROL_BIT_ACCEPT_SUBADDRESS 0x0200 +#define T30_CONTROL_BIT_ACCEPT_SEL_POLLING 0x0400 +#define T30_CONTROL_BIT_ACCEPT_PASSWORD 0x0800 +#define T30_CONTROL_BIT_ENABLE_V34FAX 0x1000 +#define T30_CONTROL_BIT_EARLY_CONNECT 0x2000 + +#define T30_CONTROL_BIT_ALL_FEATURES (T30_CONTROL_BIT_ENABLE_ECM | T30_CONTROL_BIT_ENABLE_2D_CODING | T30_CONTROL_BIT_ENABLE_T6_CODING | T30_CONTROL_BIT_ENABLE_UNCOMPR | T30_CONTROL_BIT_ENABLE_V34FAX) + +#define T30_FEATURE_BIT_FINE 0x0001 +#define T30_FEATURE_BIT_ECM 0x0002 +#define T30_FEATURE_BIT_ECM_64_BYTES 0x0004 +#define T30_FEATURE_BIT_2D_CODING 0x0008 +#define T30_FEATURE_BIT_T6_CODING 0x0010 +#define T30_FEATURE_BIT_UNCOMPR_ENABLED 0x0020 +#define T30_FEATURE_BIT_POLLING 0x0040 +#define T30_FEATURE_BIT_MORE_DOCUMENTS 0x0100 +#define T30_FEATURE_BIT_V34FAX 0x1000 + + +#define T30_NSF_CONTROL_BIT_ENABLE_NSF 0x0001 +#define T30_NSF_CONTROL_BIT_RAW_INFO 0x0002 +#define T30_NSF_CONTROL_BIT_NEGOTIATE_IND 0x0004 +#define T30_NSF_CONTROL_BIT_NEGOTIATE_RESP 0x0008 + +#define T30_NSF_ELEMENT_NSF_FIF 0x00 +#define T30_NSF_ELEMENT_NSC_FIF 0x01 +#define T30_NSF_ELEMENT_NSS_FIF 0x02 +#define T30_NSF_ELEMENT_COMPANY_NAME 0x03 + + +/*------------------------------------------------------------------*/ +/* Analog modem definitions */ +/*------------------------------------------------------------------*/ + +typedef struct async_s ASYNC_FORMAT; +struct async_s { + unsigned pe: 1; + unsigned parity:2; + unsigned spare: 2; + unsigned stp: 1; + unsigned ch_len:2; /* 3th octett in CAI */ +}; + + +/*------------------------------------------------------------------*/ +/* PLCI/NCCI states */ +/*------------------------------------------------------------------*/ + +#define IDLE 0 +#define OUTG_CON_PENDING 1 +#define INC_CON_PENDING 2 +#define INC_CON_ALERT 3 +#define INC_CON_ACCEPT 4 +#define INC_ACT_PENDING 5 +#define LISTENING 6 +#define CONNECTED 7 +#define OUTG_DIS_PENDING 8 +#define INC_DIS_PENDING 9 +#define LOCAL_CONNECT 10 +#define INC_RES_PENDING 11 +#define OUTG_RES_PENDING 12 +#define SUSPENDING 13 +#define ADVANCED_VOICE_SIG 14 +#define ADVANCED_VOICE_NOSIG 15 +#define RESUMING 16 +#define INC_CON_CONNECTED_ALERT 17 +#define OUTG_REJ_PENDING 18 + + +/*------------------------------------------------------------------*/ +/* auxilliary states for supplementary services */ +/*------------------------------------------------------------------*/ + +#define IDLE 0 +#define HOLD_REQUEST 1 +#define HOLD_INDICATE 2 +#define CALL_HELD 3 +#define RETRIEVE_REQUEST 4 +#define RETRIEVE_INDICATION 5 + +/*------------------------------------------------------------------*/ +/* Capi IE + Msg types */ +/*------------------------------------------------------------------*/ +#define ESC_CAUSE 0x800|CAU /* Escape cause element */ +#define ESC_MSGTYPE 0x800|MSGTYPEIE /* Escape message type */ +#define ESC_CHI 0x800|CHI /* Escape channel id */ +#define ESC_LAW 0x800|BC /* Escape law info */ +#define ESC_CR 0x800|CRIE /* Escape CallReference */ +#define ESC_PROFILE 0x800|PROFILEIE /* Escape profile */ +#define ESC_SSEXT 0x800|SSEXTIE /* Escape Supplem. Serv.*/ +#define ESC_VSWITCH 0x800|VSWITCHIE /* Escape VSwitch */ +#define CST 0x14 /* Call State i.e. */ +#define PI 0x1E /* Progress Indicator */ +#define NI 0x27 /* Notification Ind */ +#define CONN_NR 0x4C /* Connected Number */ +#define CONG_RNR 0xBF /* Congestion RNR */ +#define CONG_RR 0xB0 /* Congestion RR */ +#define RESERVED 0xFF /* Res. for future use */ +#define ON_BOARD_CODEC 0x02 /* external controller */ +#define HANDSET 0x04 /* Codec+Handset(Pro11) */ +#define HOOK_SUPPORT 0x01 /* activate Hook signal */ +#define SCR 0x7a /* unscreened number */ + +#define HOOK_OFF_REQ 0x9001 /* internal conn req */ +#define HOOK_ON_REQ 0x9002 /* internal disc req */ +#define SUSPEND_REQ 0x9003 /* internal susp req */ +#define RESUME_REQ 0x9004 /* internal resume req */ +#define USELAW_REQ 0x9005 /* internal law req */ +#define LISTEN_SIG_ASSIGN_PEND 0x9006 +#define PERM_LIST_REQ 0x900a /* permanent conn DCE */ +#define C_HOLD_REQ 0x9011 +#define C_RETRIEVE_REQ 0x9012 +#define C_NCR_FAC_REQ 0x9013 +#define PERM_COD_ASSIGN 0x9014 +#define PERM_COD_CALL 0x9015 +#define PERM_COD_HOOK 0x9016 +#define PERM_COD_CONN_PEND 0x9017 /* wait for connect_con */ +#define PTY_REQ_PEND 0x9018 +#define CD_REQ_PEND 0x9019 +#define CF_START_PEND 0x901a +#define CF_STOP_PEND 0x901b +#define ECT_REQ_PEND 0x901c +#define GETSERV_REQ_PEND 0x901d +#define BLOCK_PLCI 0x901e +#define INTERR_NUMBERS_REQ_PEND 0x901f +#define INTERR_DIVERSION_REQ_PEND 0x9020 +#define MWI_ACTIVATE_REQ_PEND 0x9021 +#define MWI_DEACTIVATE_REQ_PEND 0x9022 +#define SSEXT_REQ_COMMAND 0x9023 +#define SSEXT_NC_REQ_COMMAND 0x9024 +#define START_L1_SIG_ASSIGN_PEND 0x9025 +#define REM_L1_SIG_ASSIGN_PEND 0x9026 +#define CONF_BEGIN_REQ_PEND 0x9027 +#define CONF_ADD_REQ_PEND 0x9028 +#define CONF_SPLIT_REQ_PEND 0x9029 +#define CONF_DROP_REQ_PEND 0x902a +#define CONF_ISOLATE_REQ_PEND 0x902b +#define CONF_REATTACH_REQ_PEND 0x902c +#define VSWITCH_REQ_PEND 0x902d +#define GET_MWI_STATE 0x902e +#define CCBS_REQUEST_REQ_PEND 0x902f +#define CCBS_DEACTIVATE_REQ_PEND 0x9030 +#define CCBS_INTERROGATE_REQ_PEND 0x9031 + +#define NO_INTERNAL_COMMAND 0 +#define DTMF_COMMAND_1 1 +#define DTMF_COMMAND_2 2 +#define DTMF_COMMAND_3 3 +#define MIXER_COMMAND_1 4 +#define MIXER_COMMAND_2 5 +#define MIXER_COMMAND_3 6 +#define ADV_VOICE_COMMAND_CONNECT_1 7 +#define ADV_VOICE_COMMAND_CONNECT_2 8 +#define ADV_VOICE_COMMAND_CONNECT_3 9 +#define ADV_VOICE_COMMAND_DISCONNECT_1 10 +#define ADV_VOICE_COMMAND_DISCONNECT_2 11 +#define ADV_VOICE_COMMAND_DISCONNECT_3 12 +#define ADJUST_B_RESTORE_1 13 +#define ADJUST_B_RESTORE_2 14 +#define RESET_B3_COMMAND_1 15 +#define SELECT_B_COMMAND_1 16 +#define FAX_CONNECT_INFO_COMMAND_1 17 +#define FAX_CONNECT_INFO_COMMAND_2 18 +#define FAX_ADJUST_B23_COMMAND_1 19 +#define FAX_ADJUST_B23_COMMAND_2 20 +#define EC_COMMAND_1 21 +#define EC_COMMAND_2 22 +#define EC_COMMAND_3 23 +#define RTP_CONNECT_B3_REQ_COMMAND_1 24 +#define RTP_CONNECT_B3_REQ_COMMAND_2 25 +#define RTP_CONNECT_B3_REQ_COMMAND_3 26 +#define RTP_CONNECT_B3_RES_COMMAND_1 27 +#define RTP_CONNECT_B3_RES_COMMAND_2 28 +#define RTP_CONNECT_B3_RES_COMMAND_3 29 +#define HOLD_SAVE_COMMAND_1 30 +#define RETRIEVE_RESTORE_COMMAND_1 31 +#define FAX_DISCONNECT_COMMAND_1 32 +#define FAX_DISCONNECT_COMMAND_2 33 +#define FAX_DISCONNECT_COMMAND_3 34 +#define FAX_EDATA_ACK_COMMAND_1 35 +#define FAX_EDATA_ACK_COMMAND_2 36 +#define FAX_CONNECT_ACK_COMMAND_1 37 +#define FAX_CONNECT_ACK_COMMAND_2 38 +#define STD_INTERNAL_COMMAND_COUNT 39 + +#define UID 0x2d /* User Id for Mgmt */ + +#define CALL_DIR_OUT 0x01 /* call direction of initial call */ +#define CALL_DIR_IN 0x02 +#define CALL_DIR_ORIGINATE 0x04 /* DTE/DCE direction according to */ +#define CALL_DIR_ANSWER 0x08 /* state of B-Channel Operation */ +#define CALL_DIR_FORCE_OUTG_NL 0x10 /* for RESET_B3 reconnect, after DISC_B3... */ + +#define AWAITING_MANUF_CON 0x80 /* command spoofing flags */ +#define SPOOFING_REQUIRED 0xff +#define AWAITING_SELECT_B 0xef + +/*------------------------------------------------------------------*/ +/* B_CTRL / DSP_CTRL */ +/*------------------------------------------------------------------*/ + +#define DSP_CTRL_OLD_SET_MIXER_COEFFICIENTS 0x01 +#define DSP_CTRL_SET_BCHANNEL_PASSIVATION_BRI 0x02 +#define DSP_CTRL_SET_DTMF_PARAMETERS 0x03 + +#define MANUFACTURER_FEATURE_SLAVE_CODEC 0x00000001L +#define MANUFACTURER_FEATURE_FAX_MORE_DOCUMENTS 0x00000002L +#define MANUFACTURER_FEATURE_HARDDTMF 0x00000004L +#define MANUFACTURER_FEATURE_SOFTDTMF_SEND 0x00000008L +#define MANUFACTURER_FEATURE_DTMF_PARAMETERS 0x00000010L +#define MANUFACTURER_FEATURE_SOFTDTMF_RECEIVE 0x00000020L +#define MANUFACTURER_FEATURE_FAX_SUB_SEP_PWD 0x00000040L +#define MANUFACTURER_FEATURE_V18 0x00000080L +#define MANUFACTURER_FEATURE_MIXER_CH_CH 0x00000100L +#define MANUFACTURER_FEATURE_MIXER_CH_PC 0x00000200L +#define MANUFACTURER_FEATURE_MIXER_PC_CH 0x00000400L +#define MANUFACTURER_FEATURE_MIXER_PC_PC 0x00000800L +#define MANUFACTURER_FEATURE_ECHO_CANCELLER 0x00001000L +#define MANUFACTURER_FEATURE_RTP 0x00002000L +#define MANUFACTURER_FEATURE_T38 0x00004000L +#define MANUFACTURER_FEATURE_TRANSP_DELIVERY_CONF 0x00008000L +#define MANUFACTURER_FEATURE_XONOFF_FLOW_CONTROL 0x00010000L +#define MANUFACTURER_FEATURE_OOB_CHANNEL 0x00020000L +#define MANUFACTURER_FEATURE_IN_BAND_CHANNEL 0x00040000L +#define MANUFACTURER_FEATURE_IN_BAND_FEATURE 0x00080000L +#define MANUFACTURER_FEATURE_PIAFS 0x00100000L +#define MANUFACTURER_FEATURE_DTMF_TONE 0x00200000L +#define MANUFACTURER_FEATURE_FAX_PAPER_FORMATS 0x00400000L +#define MANUFACTURER_FEATURE_OK_FC_LABEL 0x00800000L +#define MANUFACTURER_FEATURE_VOWN 0x01000000L +#define MANUFACTURER_FEATURE_XCONNECT 0x02000000L +#define MANUFACTURER_FEATURE_DMACONNECT 0x04000000L +#define MANUFACTURER_FEATURE_AUDIO_TAP 0x08000000L +#define MANUFACTURER_FEATURE_FAX_NONSTANDARD 0x10000000L + +/*------------------------------------------------------------------*/ +/* DTMF interface to IDI */ +/*------------------------------------------------------------------*/ + + +#define DTMF_DIGIT_TONE_LOW_GROUP_697_HZ 0x00 +#define DTMF_DIGIT_TONE_LOW_GROUP_770_HZ 0x01 +#define DTMF_DIGIT_TONE_LOW_GROUP_852_HZ 0x02 +#define DTMF_DIGIT_TONE_LOW_GROUP_941_HZ 0x03 +#define DTMF_DIGIT_TONE_LOW_GROUP_MASK 0x03 +#define DTMF_DIGIT_TONE_HIGH_GROUP_1209_HZ 0x00 +#define DTMF_DIGIT_TONE_HIGH_GROUP_1336_HZ 0x04 +#define DTMF_DIGIT_TONE_HIGH_GROUP_1477_HZ 0x08 +#define DTMF_DIGIT_TONE_HIGH_GROUP_1633_HZ 0x0c +#define DTMF_DIGIT_TONE_HIGH_GROUP_MASK 0x0c +#define DTMF_DIGIT_TONE_CODE_0 0x07 +#define DTMF_DIGIT_TONE_CODE_1 0x00 +#define DTMF_DIGIT_TONE_CODE_2 0x04 +#define DTMF_DIGIT_TONE_CODE_3 0x08 +#define DTMF_DIGIT_TONE_CODE_4 0x01 +#define DTMF_DIGIT_TONE_CODE_5 0x05 +#define DTMF_DIGIT_TONE_CODE_6 0x09 +#define DTMF_DIGIT_TONE_CODE_7 0x02 +#define DTMF_DIGIT_TONE_CODE_8 0x06 +#define DTMF_DIGIT_TONE_CODE_9 0x0a +#define DTMF_DIGIT_TONE_CODE_STAR 0x03 +#define DTMF_DIGIT_TONE_CODE_HASHMARK 0x0b +#define DTMF_DIGIT_TONE_CODE_A 0x0c +#define DTMF_DIGIT_TONE_CODE_B 0x0d +#define DTMF_DIGIT_TONE_CODE_C 0x0e +#define DTMF_DIGIT_TONE_CODE_D 0x0f + +#define DTMF_UDATA_REQUEST_SEND_DIGITS 16 +#define DTMF_UDATA_REQUEST_ENABLE_RECEIVER 17 +#define DTMF_UDATA_REQUEST_DISABLE_RECEIVER 18 +#define DTMF_UDATA_INDICATION_DIGITS_SENT 16 +#define DTMF_UDATA_INDICATION_DIGITS_RECEIVED 17 +#define DTMF_UDATA_INDICATION_MODEM_CALLING_TONE 18 +#define DTMF_UDATA_INDICATION_FAX_CALLING_TONE 19 +#define DTMF_UDATA_INDICATION_ANSWER_TONE 20 + +#define UDATA_REQUEST_MIXER_TAP_DATA 27 +#define UDATA_INDICATION_MIXER_TAP_DATA 27 + +#define DTMF_LISTEN_ACTIVE_FLAG 0x01 +#define DTMF_SEND_DIGIT_FLAG 0x01 + + +/*------------------------------------------------------------------*/ +/* Mixer interface to IDI */ +/*------------------------------------------------------------------*/ + + +#define LI2_FLAG_PCCONNECT_A_B 0x40000000 +#define LI2_FLAG_PCCONNECT_B_A 0x80000000 + +#define MIXER_BCHANNELS_BRI 2 +#define MIXER_IC_CHANNELS_BRI MIXER_BCHANNELS_BRI +#define MIXER_IC_CHANNEL_BASE MIXER_BCHANNELS_BRI +#define MIXER_CHANNELS_BRI (MIXER_BCHANNELS_BRI + MIXER_IC_CHANNELS_BRI) +#define MIXER_CHANNELS_PRI 32 + +typedef struct li_config_s LI_CONFIG; + +struct xconnect_card_address_s { + dword low; + dword high; +}; + +struct xconnect_transfer_address_s { + struct xconnect_card_address_s card_address; + dword offset; +}; + +struct li_config_s { + DIVA_CAPI_ADAPTER *adapter; + PLCI *plci; + struct xconnect_transfer_address_s send_b; + struct xconnect_transfer_address_s send_pc; + byte *flag_table; /* dword aligned and sized */ + byte *coef_table; /* dword aligned and sized */ + byte channel; + byte curchnl; + byte chflags; +}; + +extern LI_CONFIG *li_config_table; +extern word li_total_channels; + +#define LI_CHANNEL_INVOLVED 0x01 +#define LI_CHANNEL_ACTIVE 0x02 +#define LI_CHANNEL_TX_DATA 0x04 +#define LI_CHANNEL_RX_DATA 0x08 +#define LI_CHANNEL_CONFERENCE 0x10 +#define LI_CHANNEL_ADDRESSES_SET 0x80 + +#define LI_CHFLAG_MONITOR 0x01 +#define LI_CHFLAG_MIX 0x02 +#define LI_CHFLAG_LOOP 0x04 + +#define LI_FLAG_INTERCONNECT 0x01 +#define LI_FLAG_MONITOR 0x02 +#define LI_FLAG_MIX 0x04 +#define LI_FLAG_PCCONNECT 0x08 +#define LI_FLAG_CONFERENCE 0x10 +#define LI_FLAG_ANNOUNCEMENT 0x20 + +#define LI_COEF_CH_CH 0x01 +#define LI_COEF_CH_PC 0x02 +#define LI_COEF_PC_CH 0x04 +#define LI_COEF_PC_PC 0x08 +#define LI_COEF_CH_CH_SET 0x10 +#define LI_COEF_CH_PC_SET 0x20 +#define LI_COEF_PC_CH_SET 0x40 +#define LI_COEF_PC_PC_SET 0x80 + +#define LI_REQ_SILENT_UPDATE 0xffff + +#define LI_PLCI_B_LAST_FLAG ((dword) 0x80000000L) +#define LI_PLCI_B_DISC_FLAG ((dword) 0x40000000L) +#define LI_PLCI_B_SKIP_FLAG ((dword) 0x20000000L) +#define LI_PLCI_B_FLAG_MASK ((dword) 0xe0000000L) + +#define UDATA_REQUEST_SET_MIXER_COEFS_BRI 24 +#define UDATA_REQUEST_SET_MIXER_COEFS_PRI_SYNC 25 +#define UDATA_REQUEST_SET_MIXER_COEFS_PRI_ASYN 26 +#define UDATA_INDICATION_MIXER_COEFS_SET 24 + +#define MIXER_FEATURE_ENABLE_TX_DATA 0x0001 +#define MIXER_FEATURE_ENABLE_RX_DATA 0x0002 + +#define MIXER_COEF_LINE_CHANNEL_MASK 0x1f +#define MIXER_COEF_LINE_FROM_PC_FLAG 0x20 +#define MIXER_COEF_LINE_TO_PC_FLAG 0x40 +#define MIXER_COEF_LINE_ROW_FLAG 0x80 + +#define UDATA_REQUEST_XCONNECT_FROM 28 +#define UDATA_INDICATION_XCONNECT_FROM 28 +#define UDATA_REQUEST_XCONNECT_TO 29 +#define UDATA_INDICATION_XCONNECT_TO 29 + +#define XCONNECT_CHANNEL_PORT_B 0x0000 +#define XCONNECT_CHANNEL_PORT_PC 0x8000 +#define XCONNECT_CHANNEL_PORT_MASK 0x8000 +#define XCONNECT_CHANNEL_NUMBER_MASK 0x7fff +#define XCONNECT_CHANNEL_PORT_COUNT 2 + +#define XCONNECT_SUCCESS 0x0000 +#define XCONNECT_ERROR 0x0001 + + +/*------------------------------------------------------------------*/ +/* Echo canceller interface to IDI */ +/*------------------------------------------------------------------*/ + + +#define PRIVATE_ECHO_CANCELLER 0 + +#define PRIV_SELECTOR_ECHO_CANCELLER 255 + +#define EC_ENABLE_OPERATION 1 +#define EC_DISABLE_OPERATION 2 +#define EC_FREEZE_COEFFICIENTS 3 +#define EC_RESUME_COEFFICIENT_UPDATE 4 +#define EC_RESET_COEFFICIENTS 5 + +#define EC_DISABLE_NON_LINEAR_PROCESSING 0x0001 +#define EC_DO_NOT_REQUIRE_REVERSALS 0x0002 +#define EC_DETECT_DISABLE_TONE 0x0004 + +#define EC_SUCCESS 0 +#define EC_UNSUPPORTED_OPERATION 1 + +#define EC_BYPASS_DUE_TO_CONTINUOUS_2100HZ 1 +#define EC_BYPASS_DUE_TO_REVERSED_2100HZ 2 +#define EC_BYPASS_RELEASED 3 + +#define DSP_CTRL_SET_LEC_PARAMETERS 0x05 + +#define LEC_ENABLE_ECHO_CANCELLER 0x0001 +#define LEC_ENABLE_2100HZ_DETECTOR 0x0002 +#define LEC_REQUIRE_2100HZ_REVERSALS 0x0004 +#define LEC_MANUAL_DISABLE 0x0008 +#define LEC_ENABLE_NONLINEAR_PROCESSING 0x0010 +#define LEC_FREEZE_COEFFICIENTS 0x0020 +#define LEC_RESET_COEFFICIENTS 0x8000 + +#define LEC_MAX_SUPPORTED_TAIL_LENGTH 32 + +#define LEC_UDATA_INDICATION_DISABLE_DETECT 9 + +#define LEC_DISABLE_TYPE_CONTIGNUOUS_2100HZ 0x00 +#define LEC_DISABLE_TYPE_REVERSED_2100HZ 0x01 +#define LEC_DISABLE_RELEASED 0x02 + + +/*------------------------------------------------------------------*/ +/* RTP interface to IDI */ +/*------------------------------------------------------------------*/ + + +#define B1_RTP 31 +#define B2_RTP 31 +#define B3_RTP 31 + +#define PRIVATE_RTP 1 + +#define RTP_PRIM_PAYLOAD_PCMU_8000 0 +#define RTP_PRIM_PAYLOAD_1016_8000 1 +#define RTP_PRIM_PAYLOAD_G726_32_8000 2 +#define RTP_PRIM_PAYLOAD_GSM_8000 3 +#define RTP_PRIM_PAYLOAD_G723_8000 4 +#define RTP_PRIM_PAYLOAD_DVI4_8000 5 +#define RTP_PRIM_PAYLOAD_DVI4_16000 6 +#define RTP_PRIM_PAYLOAD_LPC_8000 7 +#define RTP_PRIM_PAYLOAD_PCMA_8000 8 +#define RTP_PRIM_PAYLOAD_G722_16000 9 +#define RTP_PRIM_PAYLOAD_QCELP_8000 12 +#define RTP_PRIM_PAYLOAD_G728_8000 14 +#define RTP_PRIM_PAYLOAD_G729_8000 18 +#define RTP_PRIM_PAYLOAD_GSM_HR_8000 30 +#define RTP_PRIM_PAYLOAD_GSM_EFR_8000 31 + +#define RTP_ADD_PAYLOAD_BASE 32 +#define RTP_ADD_PAYLOAD_RED 32 +#define RTP_ADD_PAYLOAD_CN_8000 33 +#define RTP_ADD_PAYLOAD_DTMF 34 + +#define RTP_SUCCESS 0 +#define RTP_ERR_SSRC_OR_PAYLOAD_CHANGE 1 + +#define UDATA_REQUEST_RTP_RECONFIGURE 64 +#define UDATA_INDICATION_RTP_CHANGE 65 +#define BUDATA_REQUEST_QUERY_RTCP_REPORT 1 +#define BUDATA_INDICATION_RTCP_REPORT 1 + +#define RTP_CONNECT_OPTION_DISC_ON_SSRC_CHANGE 0x00000001L +#define RTP_CONNECT_OPTION_DISC_ON_PT_CHANGE 0x00000002L +#define RTP_CONNECT_OPTION_DISC_ON_UNKNOWN_PT 0x00000004L +#define RTP_CONNECT_OPTION_NO_SILENCE_TRANSMIT 0x00010000L + +#define RTP_PAYLOAD_OPTION_VOICE_ACTIVITY_DETECT 0x0001 +#define RTP_PAYLOAD_OPTION_DISABLE_POST_FILTER 0x0002 +#define RTP_PAYLOAD_OPTION_G723_LOW_CODING_RATE 0x0100 + +#define RTP_PACKET_FILTER_IGNORE_UNKNOWN_SSRC 0x00000001L + +#define RTP_CHANGE_FLAG_SSRC_CHANGE 0x00000001L +#define RTP_CHANGE_FLAG_PAYLOAD_TYPE_CHANGE 0x00000002L +#define RTP_CHANGE_FLAG_UNKNOWN_PAYLOAD_TYPE 0x00000004L + + +/*------------------------------------------------------------------*/ +/* T.38 interface to IDI */ +/*------------------------------------------------------------------*/ + + +#define B1_T38 30 +#define B2_T38 30 +#define B3_T38 30 + +#define PRIVATE_T38 2 + + +/*------------------------------------------------------------------*/ +/* PIAFS interface to IDI */ +/*------------------------------------------------------------------*/ + + +#define B1_PIAFS 29 +#define B2_PIAFS 29 + +#define PRIVATE_PIAFS 29 + +/* + B2 configuration for PIAFS: ++---------------------+------+-----------------------------------------+ +| PIAFS Protocol | byte | Bit 1 - Protocol Speed | +| Speed configuration | | 0 - 32K | +| | | 1 - 64K (default) | +| | | Bit 2 - Variable Protocol Speed | +| | | 0 - Speed is fix | +| | | 1 - Speed is variable (default) | ++---------------------+------+-----------------------------------------+ +| Direction | word | Enable compression/decompression for | +| | | 0: All direction | +| | | 1: disable outgoing data | +| | | 2: disable incomming data | +| | | 3: disable both direction (default) | ++---------------------+------+-----------------------------------------+ +| Number of code | word | Parameter P1 of V.42bis in accordance | +| words | | with V.42bis | ++---------------------+------+-----------------------------------------+ +| Maximum String | word | Parameter P2 of V.42bis in accordance | +| Length | | with V.42bis | ++---------------------+------+-----------------------------------------+ +| control (UDATA) | byte | enable PIAFS control communication | +| abilities | | | ++---------------------+------+-----------------------------------------+ +*/ +#define PIAFS_UDATA_ABILITIES 0x80 + +/*------------------------------------------------------------------*/ +/* FAX SUB/SEP/PWD extension */ +/*------------------------------------------------------------------*/ + + +#define PRIVATE_FAX_SUB_SEP_PWD 3 + + + +/*------------------------------------------------------------------*/ +/* V.18 extension */ +/*------------------------------------------------------------------*/ + + +#define PRIVATE_V18 4 + + + +/*------------------------------------------------------------------*/ +/* DTMF TONE extension */ +/*------------------------------------------------------------------*/ + + +#define DTMF_GET_SUPPORTED_DETECT_CODES 0xf8 +#define DTMF_GET_SUPPORTED_SEND_CODES 0xf9 +#define DTMF_LISTEN_TONE_START 0xfa +#define DTMF_LISTEN_TONE_STOP 0xfb +#define DTMF_SEND_TONE 0xfc +#define DTMF_LISTEN_MF_START 0xfd +#define DTMF_LISTEN_MF_STOP 0xfe +#define DTMF_SEND_MF 0xff + +#define DTMF_MF_DIGIT_TONE_CODE_1 0x10 +#define DTMF_MF_DIGIT_TONE_CODE_2 0x11 +#define DTMF_MF_DIGIT_TONE_CODE_3 0x12 +#define DTMF_MF_DIGIT_TONE_CODE_4 0x13 +#define DTMF_MF_DIGIT_TONE_CODE_5 0x14 +#define DTMF_MF_DIGIT_TONE_CODE_6 0x15 +#define DTMF_MF_DIGIT_TONE_CODE_7 0x16 +#define DTMF_MF_DIGIT_TONE_CODE_8 0x17 +#define DTMF_MF_DIGIT_TONE_CODE_9 0x18 +#define DTMF_MF_DIGIT_TONE_CODE_0 0x19 +#define DTMF_MF_DIGIT_TONE_CODE_K1 0x1a +#define DTMF_MF_DIGIT_TONE_CODE_K2 0x1b +#define DTMF_MF_DIGIT_TONE_CODE_KP 0x1c +#define DTMF_MF_DIGIT_TONE_CODE_S1 0x1d +#define DTMF_MF_DIGIT_TONE_CODE_ST 0x1e + +#define DTMF_DIGIT_CODE_COUNT 16 +#define DTMF_MF_DIGIT_CODE_BASE DSP_DTMF_DIGIT_CODE_COUNT +#define DTMF_MF_DIGIT_CODE_COUNT 15 +#define DTMF_TOTAL_DIGIT_CODE_COUNT (DSP_MF_DIGIT_CODE_BASE + DSP_MF_DIGIT_CODE_COUNT) + +#define DTMF_TONE_DIGIT_BASE 0x80 + +#define DTMF_SIGNAL_NO_TONE (DTMF_TONE_DIGIT_BASE + 0) +#define DTMF_SIGNAL_UNIDENTIFIED_TONE (DTMF_TONE_DIGIT_BASE + 1) + +#define DTMF_SIGNAL_DIAL_TONE (DTMF_TONE_DIGIT_BASE + 2) +#define DTMF_SIGNAL_PABX_INTERNAL_DIAL_TONE (DTMF_TONE_DIGIT_BASE + 3) +#define DTMF_SIGNAL_SPECIAL_DIAL_TONE (DTMF_TONE_DIGIT_BASE + 4) /* stutter dial tone */ +#define DTMF_SIGNAL_SECOND_DIAL_TONE (DTMF_TONE_DIGIT_BASE + 5) +#define DTMF_SIGNAL_RINGING_TONE (DTMF_TONE_DIGIT_BASE + 6) +#define DTMF_SIGNAL_SPECIAL_RINGING_TONE (DTMF_TONE_DIGIT_BASE + 7) +#define DTMF_SIGNAL_BUSY_TONE (DTMF_TONE_DIGIT_BASE + 8) +#define DTMF_SIGNAL_CONGESTION_TONE (DTMF_TONE_DIGIT_BASE + 9) /* reorder tone */ +#define DTMF_SIGNAL_SPECIAL_INFORMATION_TONE (DTMF_TONE_DIGIT_BASE + 10) +#define DTMF_SIGNAL_COMFORT_TONE (DTMF_TONE_DIGIT_BASE + 11) +#define DTMF_SIGNAL_HOLD_TONE (DTMF_TONE_DIGIT_BASE + 12) +#define DTMF_SIGNAL_RECORD_TONE (DTMF_TONE_DIGIT_BASE + 13) +#define DTMF_SIGNAL_CALLER_WAITING_TONE (DTMF_TONE_DIGIT_BASE + 14) +#define DTMF_SIGNAL_CALL_WAITING_TONE (DTMF_TONE_DIGIT_BASE + 15) +#define DTMF_SIGNAL_PAY_TONE (DTMF_TONE_DIGIT_BASE + 16) +#define DTMF_SIGNAL_POSITIVE_INDICATION_TONE (DTMF_TONE_DIGIT_BASE + 17) +#define DTMF_SIGNAL_NEGATIVE_INDICATION_TONE (DTMF_TONE_DIGIT_BASE + 18) +#define DTMF_SIGNAL_WARNING_TONE (DTMF_TONE_DIGIT_BASE + 19) +#define DTMF_SIGNAL_INTRUSION_TONE (DTMF_TONE_DIGIT_BASE + 20) +#define DTMF_SIGNAL_CALLING_CARD_SERVICE_TONE (DTMF_TONE_DIGIT_BASE + 21) +#define DTMF_SIGNAL_PAYPHONE_RECOGNITION_TONE (DTMF_TONE_DIGIT_BASE + 22) +#define DTMF_SIGNAL_CPE_ALERTING_SIGNAL (DTMF_TONE_DIGIT_BASE + 23) +#define DTMF_SIGNAL_OFF_HOOK_WARNING_TONE (DTMF_TONE_DIGIT_BASE + 24) + +#define DTMF_SIGNAL_INTERCEPT_TONE (DTMF_TONE_DIGIT_BASE + 63) + +#define DTMF_SIGNAL_MODEM_CALLING_TONE (DTMF_TONE_DIGIT_BASE + 64) +#define DTMF_SIGNAL_FAX_CALLING_TONE (DTMF_TONE_DIGIT_BASE + 65) +#define DTMF_SIGNAL_ANSWER_TONE (DTMF_TONE_DIGIT_BASE + 66) +#define DTMF_SIGNAL_REVERSED_ANSWER_TONE (DTMF_TONE_DIGIT_BASE + 67) +#define DTMF_SIGNAL_ANSAM_TONE (DTMF_TONE_DIGIT_BASE + 68) +#define DTMF_SIGNAL_REVERSED_ANSAM_TONE (DTMF_TONE_DIGIT_BASE + 69) +#define DTMF_SIGNAL_BELL103_ANSWER_TONE (DTMF_TONE_DIGIT_BASE + 70) +#define DTMF_SIGNAL_FAX_FLAGS (DTMF_TONE_DIGIT_BASE + 71) +#define DTMF_SIGNAL_G2_FAX_GROUP_ID (DTMF_TONE_DIGIT_BASE + 72) +#define DTMF_SIGNAL_HUMAN_SPEECH (DTMF_TONE_DIGIT_BASE + 73) +#define DTMF_SIGNAL_ANSWERING_MACHINE_390 (DTMF_TONE_DIGIT_BASE + 74) + +#define DTMF_MF_LISTEN_ACTIVE_FLAG 0x02 +#define DTMF_SEND_MF_FLAG 0x02 +#define DTMF_TONE_LISTEN_ACTIVE_FLAG 0x04 +#define DTMF_SEND_TONE_FLAG 0x04 + +#define PRIVATE_DTMF_TONE 5 + + +/*------------------------------------------------------------------*/ +/* FAX paper format extension */ +/*------------------------------------------------------------------*/ + + +#define PRIVATE_FAX_PAPER_FORMATS 6 + + + +/*------------------------------------------------------------------*/ +/* V.OWN extension */ +/*------------------------------------------------------------------*/ + + +#define PRIVATE_VOWN 7 + + + +/*------------------------------------------------------------------*/ +/* FAX non-standard facilities extension */ +/*------------------------------------------------------------------*/ + + +#define PRIVATE_FAX_NONSTANDARD 8 + + + +/*------------------------------------------------------------------*/ +/* Advanced voice */ +/*------------------------------------------------------------------*/ + +#define ADV_VOICE_WRITE_ACTIVATION 0 +#define ADV_VOICE_WRITE_DEACTIVATION 1 +#define ADV_VOICE_WRITE_UPDATE 2 + +#define ADV_VOICE_OLD_COEF_COUNT 6 +#define ADV_VOICE_NEW_COEF_BASE (ADV_VOICE_OLD_COEF_COUNT * sizeof(word)) + +/*------------------------------------------------------------------*/ +/* B1 resource switching */ +/*------------------------------------------------------------------*/ + +#define B1_FACILITY_LOCAL 0x01 +#define B1_FACILITY_MIXER 0x02 +#define B1_FACILITY_DTMFX 0x04 +#define B1_FACILITY_DTMFR 0x08 +#define B1_FACILITY_VOICE 0x10 +#define B1_FACILITY_EC 0x20 + +#define ADJUST_B_MODE_SAVE 0x0001 +#define ADJUST_B_MODE_REMOVE_L23 0x0002 +#define ADJUST_B_MODE_SWITCH_L1 0x0004 +#define ADJUST_B_MODE_NO_RESOURCE 0x0008 +#define ADJUST_B_MODE_ASSIGN_L23 0x0010 +#define ADJUST_B_MODE_USER_CONNECT 0x0020 +#define ADJUST_B_MODE_CONNECT 0x0040 +#define ADJUST_B_MODE_RESTORE 0x0080 + +#define ADJUST_B_START 0 +#define ADJUST_B_SAVE_MIXER_1 1 +#define ADJUST_B_SAVE_DTMF_1 2 +#define ADJUST_B_REMOVE_L23_1 3 +#define ADJUST_B_REMOVE_L23_2 4 +#define ADJUST_B_SAVE_EC_1 5 +#define ADJUST_B_SAVE_DTMF_PARAMETER_1 6 +#define ADJUST_B_SAVE_VOICE_1 7 +#define ADJUST_B_SWITCH_L1_1 8 +#define ADJUST_B_SWITCH_L1_2 9 +#define ADJUST_B_RESTORE_VOICE_1 10 +#define ADJUST_B_RESTORE_VOICE_2 11 +#define ADJUST_B_RESTORE_DTMF_PARAMETER_1 12 +#define ADJUST_B_RESTORE_DTMF_PARAMETER_2 13 +#define ADJUST_B_RESTORE_EC_1 14 +#define ADJUST_B_RESTORE_EC_2 15 +#define ADJUST_B_ASSIGN_L23_1 16 +#define ADJUST_B_ASSIGN_L23_2 17 +#define ADJUST_B_CONNECT_1 18 +#define ADJUST_B_CONNECT_2 19 +#define ADJUST_B_CONNECT_3 20 +#define ADJUST_B_CONNECT_4 21 +#define ADJUST_B_RESTORE_DTMF_1 22 +#define ADJUST_B_RESTORE_DTMF_2 23 +#define ADJUST_B_RESTORE_MIXER_1 24 +#define ADJUST_B_RESTORE_MIXER_2 25 +#define ADJUST_B_RESTORE_MIXER_3 26 +#define ADJUST_B_RESTORE_MIXER_4 27 +#define ADJUST_B_RESTORE_MIXER_5 28 +#define ADJUST_B_RESTORE_MIXER_6 29 +#define ADJUST_B_RESTORE_MIXER_7 30 +#define ADJUST_B_END 31 + +/*------------------------------------------------------------------*/ +/* XON Protocol def's */ +/*------------------------------------------------------------------*/ +#define N_CH_XOFF 0x01 +#define N_XON_SENT 0x02 +#define N_XON_REQ 0x04 +#define N_XON_CONNECT_IND 0x08 +#define N_RX_FLOW_CONTROL_MASK 0x3f +#define N_OK_FC_PENDING 0x80 +#define N_TX_FLOW_CONTROL_MASK 0xc0 + +/*------------------------------------------------------------------*/ +/* NCPI state */ +/*------------------------------------------------------------------*/ +#define NCPI_VALID_CONNECT_B3_IND 0x01 +#define NCPI_VALID_CONNECT_B3_ACT 0x02 +#define NCPI_VALID_DISC_B3_IND 0x04 +#define NCPI_CONNECT_B3_ACT_SENT 0x08 +#define NCPI_NEGOTIATE_B3_SENT 0x10 +#define NCPI_MDM_CTS_ON_RECEIVED 0x40 +#define NCPI_MDM_DCD_ON_RECEIVED 0x80 + +/*------------------------------------------------------------------*/ diff --git a/drivers/isdn/hardware/eicon/divamnt.c b/drivers/isdn/hardware/eicon/divamnt.c new file mode 100644 index 000000000000..6146f7633be5 --- /dev/null +++ b/drivers/isdn/hardware/eicon/divamnt.c @@ -0,0 +1,257 @@ +/* $Id: divamnt.c,v 1.32.6.10 2005/02/11 19:40:25 armin Exp $ + * + * Driver for Eicon DIVA Server ISDN cards. + * Maint module + * + * Copyright 2000-2003 by Armin Schindler (mac@melware.de) + * Copyright 2000-2003 Cytronics & Melware (info@melware.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/smp_lock.h> +#include <linux/poll.h> +#include <linux/devfs_fs_kernel.h> +#include <asm/uaccess.h> + +#include "platform.h" +#include "di_defs.h" +#include "divasync.h" +#include "debug_if.h" + +static char *main_revision = "$Revision: 1.32.6.10 $"; + +static int major; + +MODULE_DESCRIPTION("Maint driver for Eicon DIVA Server cards"); +MODULE_AUTHOR("Cytronics & Melware, Eicon Networks"); +MODULE_SUPPORTED_DEVICE("DIVA card driver"); +MODULE_LICENSE("GPL"); + +static int buffer_length = 128; +module_param(buffer_length, int, 0); +static unsigned long diva_dbg_mem = 0; +module_param(diva_dbg_mem, ulong, 0); + +static char *DRIVERNAME = + "Eicon DIVA - MAINT module (http://www.melware.net)"; +static char *DRIVERLNAME = "diva_mnt"; +static char *DEVNAME = "DivasMAINT"; +char *DRIVERRELEASE_MNT = "2.0"; + +static wait_queue_head_t msgwaitq; +static unsigned long opened; +static struct timeval start_time; + +extern int mntfunc_init(int *, void **, unsigned long); +extern void mntfunc_finit(void); +extern int maint_read_write(void __user *buf, int count); + +/* + * helper functions + */ +static char *getrev(const char *revision) +{ + char *rev; + char *p; + + if ((p = strchr(revision, ':'))) { + rev = p + 2; + p = strchr(rev, '$'); + *--p = 0; + } else + rev = "1.0"; + + return rev; +} + +/* + * kernel/user space copy functions + */ +int diva_os_copy_to_user(void *os_handle, void __user *dst, const void *src, + int length) +{ + return (copy_to_user(dst, src, length)); +} +int diva_os_copy_from_user(void *os_handle, void *dst, const void __user *src, + int length) +{ + return (copy_from_user(dst, src, length)); +} + +/* + * get time + */ +void diva_os_get_time(dword * sec, dword * usec) +{ + struct timeval tv; + + do_gettimeofday(&tv); + + if (tv.tv_sec > start_time.tv_sec) { + if (start_time.tv_usec > tv.tv_usec) { + tv.tv_sec--; + tv.tv_usec += 1000000; + } + *sec = (dword) (tv.tv_sec - start_time.tv_sec); + *usec = (dword) (tv.tv_usec - start_time.tv_usec); + } else if (tv.tv_sec == start_time.tv_sec) { + *sec = 0; + if (start_time.tv_usec < tv.tv_usec) { + *usec = (dword) (tv.tv_usec - start_time.tv_usec); + } else { + *usec = 0; + } + } else { + *sec = (dword) tv.tv_sec; + *usec = (dword) tv.tv_usec; + } +} + +/* + * device node operations + */ +static unsigned int maint_poll(struct file *file, poll_table * wait) +{ + unsigned int mask = 0; + + poll_wait(file, &msgwaitq, wait); + mask = POLLOUT | POLLWRNORM; + if (file->private_data || diva_dbg_q_length()) { + mask |= POLLIN | POLLRDNORM; + } + return (mask); +} + +static int maint_open(struct inode *ino, struct file *filep) +{ + /* only one open is allowed, so we test + it atomically */ + if (test_and_set_bit(0, &opened)) + return (-EBUSY); + + filep->private_data = NULL; + + return nonseekable_open(ino, filep); +} + +static int maint_close(struct inode *ino, struct file *filep) +{ + if (filep->private_data) { + diva_os_free(0, filep->private_data); + filep->private_data = NULL; + } + + /* clear 'used' flag */ + clear_bit(0, &opened); + + return (0); +} + +static ssize_t divas_maint_write(struct file *file, const char __user *buf, + size_t count, loff_t * ppos) +{ + return (maint_read_write((char __user *) buf, (int) count)); +} + +static ssize_t divas_maint_read(struct file *file, char __user *buf, + size_t count, loff_t * ppos) +{ + return (maint_read_write(buf, (int) count)); +} + +static struct file_operations divas_maint_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = divas_maint_read, + .write = divas_maint_write, + .poll = maint_poll, + .open = maint_open, + .release = maint_close +}; + +static void divas_maint_unregister_chrdev(void) +{ + devfs_remove(DEVNAME); + unregister_chrdev(major, DEVNAME); +} + +static int DIVA_INIT_FUNCTION divas_maint_register_chrdev(void) +{ + if ((major = register_chrdev(0, DEVNAME, &divas_maint_fops)) < 0) + { + printk(KERN_ERR "%s: failed to create /dev entry.\n", + DRIVERLNAME); + return (0); + } + devfs_mk_cdev(MKDEV(major, 0), S_IFCHR|S_IRUSR|S_IWUSR, DEVNAME); + + return (1); +} + +/* + * wake up reader + */ +void diva_maint_wakeup_read(void) +{ + wake_up_interruptible(&msgwaitq); +} + +/* + * Driver Load + */ +static int DIVA_INIT_FUNCTION maint_init(void) +{ + char tmprev[50]; + int ret = 0; + void *buffer = NULL; + + do_gettimeofday(&start_time); + init_waitqueue_head(&msgwaitq); + + printk(KERN_INFO "%s\n", DRIVERNAME); + printk(KERN_INFO "%s: Rel:%s Rev:", DRIVERLNAME, DRIVERRELEASE_MNT); + strcpy(tmprev, main_revision); + printk("%s Build: %s \n", getrev(tmprev), DIVA_BUILD); + + if (!divas_maint_register_chrdev()) { + ret = -EIO; + goto out; + } + + if (!(mntfunc_init(&buffer_length, &buffer, diva_dbg_mem))) { + printk(KERN_ERR "%s: failed to connect to DIDD.\n", + DRIVERLNAME); + divas_maint_unregister_chrdev(); + ret = -EIO; + goto out; + } + + printk(KERN_INFO "%s: trace buffer = %p - %d kBytes, %s (Major: %d)\n", + DRIVERLNAME, buffer, (buffer_length / 1024), + (diva_dbg_mem == 0) ? "internal" : "external", major); + + out: + return (ret); +} + +/* +** Driver Unload +*/ +static void DIVA_EXIT_FUNCTION maint_exit(void) +{ + divas_maint_unregister_chrdev(); + mntfunc_finit(); + + printk(KERN_INFO "%s: module unloaded.\n", DRIVERLNAME); +} + +module_init(maint_init); +module_exit(maint_exit); + diff --git a/drivers/isdn/hardware/eicon/divasfunc.c b/drivers/isdn/hardware/eicon/divasfunc.c new file mode 100644 index 000000000000..df61e510a28b --- /dev/null +++ b/drivers/isdn/hardware/eicon/divasfunc.c @@ -0,0 +1,238 @@ +/* $Id: divasfunc.c,v 1.23.4.2 2004/08/28 20:03:53 armin Exp $ + * + * Low level driver for Eicon DIVA Server ISDN cards. + * + * Copyright 2000-2003 by Armin Schindler (mac@melware.de) + * Copyright 2000-2003 Cytronics & Melware (info@melware.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + */ + +#include "platform.h" +#include "di_defs.h" +#include "pc.h" +#include "di.h" +#include "io.h" +#include "divasync.h" +#include "diva.h" +#include "xdi_vers.h" + +#define DBG_MINIMUM (DL_LOG + DL_FTL + DL_ERR) +#define DBG_DEFAULT (DBG_MINIMUM + DL_XLOG + DL_REG) + +static int debugmask; + +extern void DIVA_DIDD_Read(void *, int); + +extern PISDN_ADAPTER IoAdapters[MAX_ADAPTER]; + +extern char *DRIVERRELEASE_DIVAS; + +static dword notify_handle; +static DESCRIPTOR DAdapter; +static DESCRIPTOR MAdapter; + +/* -------------------------------------------------------------------------- + MAINT driver connector section + -------------------------------------------------------------------------- */ +static void no_printf(unsigned char *x, ...) +{ + /* dummy debug function */ +} + +#include "debuglib.c" + +/* + * get the adapters serial number + */ +void diva_get_vserial_number(PISDN_ADAPTER IoAdapter, char *buf) +{ + int contr = 0; + + if ((contr = ((IoAdapter->serialNo & 0xff000000) >> 24))) { + sprintf(buf, "%d-%d", + IoAdapter->serialNo & 0x00ffffff, contr + 1); + } else { + sprintf(buf, "%d", IoAdapter->serialNo); + } +} + +/* + * register a new adapter + */ +void diva_xdi_didd_register_adapter(int card) +{ + DESCRIPTOR d; + IDI_SYNC_REQ req; + + if (card && ((card - 1) < MAX_ADAPTER) && + IoAdapters[card - 1] && Requests[card - 1]) { + d.type = IoAdapters[card - 1]->Properties.DescType; + d.request = Requests[card - 1]; + d.channels = IoAdapters[card - 1]->Properties.Channels; + d.features = IoAdapters[card - 1]->Properties.Features; + DBG_TRC(("DIDD register A(%d) channels=%d", card, + d.channels)) + /* workaround for different Name in structure */ + strlcpy(IoAdapters[card - 1]->Name, + IoAdapters[card - 1]->Properties.Name, + sizeof(IoAdapters[card - 1]->Name)); + req.didd_remove_adapter.e.Req = 0; + req.didd_add_adapter.e.Rc = IDI_SYNC_REQ_DIDD_ADD_ADAPTER; + req.didd_add_adapter.info.descriptor = (void *) &d; + DAdapter.request((ENTITY *) & req); + if (req.didd_add_adapter.e.Rc != 0xff) { + DBG_ERR(("DIDD register A(%d) failed !", card)) + } + IoAdapters[card - 1]->os_trap_nfy_Fnc = NULL; + } +} + +/* + * remove an adapter + */ +void diva_xdi_didd_remove_adapter(int card) +{ + IDI_SYNC_REQ req; + ADAPTER *a = &IoAdapters[card - 1]->a; + + IoAdapters[card - 1]->os_trap_nfy_Fnc = NULL; + DBG_TRC(("DIDD de-register A(%d)", card)) + req.didd_remove_adapter.e.Req = 0; + req.didd_remove_adapter.e.Rc = IDI_SYNC_REQ_DIDD_REMOVE_ADAPTER; + req.didd_remove_adapter.info.p_request = + (IDI_CALL) Requests[card - 1]; + DAdapter.request((ENTITY *) & req); + memset(&(a->IdTable), 0x00, 256); +} + +/* + * start debug + */ +static void start_dbg(void) +{ + DbgRegister("DIVAS", DRIVERRELEASE_DIVAS, (debugmask) ? debugmask : DBG_DEFAULT); + DBG_LOG(("DIVA ISDNXDI BUILD (%s[%s]-%s-%s)", + DIVA_BUILD, diva_xdi_common_code_build, __DATE__, + __TIME__)) +} + +/* + * stop debug + */ +static void stop_dbg(void) +{ + DbgDeregister(); + memset(&MAdapter, 0, sizeof(MAdapter)); + dprintf = no_printf; +} + +/* + * didd callback function + */ +static void *didd_callback(void *context, DESCRIPTOR * adapter, + int removal) +{ + if (adapter->type == IDI_DADAPTER) { + DBG_ERR(("Notification about IDI_DADAPTER change ! Oops.")); + return (NULL); + } + + if (adapter->type == IDI_DIMAINT) { + if (removal) { + stop_dbg(); + } else { + memcpy(&MAdapter, adapter, sizeof(MAdapter)); + dprintf = (DIVA_DI_PRINTF) MAdapter.request; + start_dbg(); + } + } + return (NULL); +} + +/* + * connect to didd + */ +static int DIVA_INIT_FUNCTION connect_didd(void) +{ + int x = 0; + int dadapter = 0; + IDI_SYNC_REQ req; + DESCRIPTOR DIDD_Table[MAX_DESCRIPTORS]; + + DIVA_DIDD_Read(DIDD_Table, sizeof(DIDD_Table)); + + for (x = 0; x < MAX_DESCRIPTORS; x++) { + if (DIDD_Table[x].type == IDI_DADAPTER) { /* DADAPTER found */ + dadapter = 1; + memcpy(&DAdapter, &DIDD_Table[x], sizeof(DAdapter)); + req.didd_notify.e.Req = 0; + req.didd_notify.e.Rc = + IDI_SYNC_REQ_DIDD_REGISTER_ADAPTER_NOTIFY; + req.didd_notify.info.callback = (void *)didd_callback; + req.didd_notify.info.context = NULL; + DAdapter.request((ENTITY *) & req); + if (req.didd_notify.e.Rc != 0xff) { + stop_dbg(); + return (0); + } + notify_handle = req.didd_notify.info.handle; + } else if (DIDD_Table[x].type == IDI_DIMAINT) { /* MAINT found */ + memcpy(&MAdapter, &DIDD_Table[x], sizeof(DAdapter)); + dprintf = (DIVA_DI_PRINTF) MAdapter.request; + start_dbg(); + } + } + + if (!dadapter) { + stop_dbg(); + } + + return (dadapter); +} + +/* + * disconnect from didd + */ +static void DIVA_EXIT_FUNCTION disconnect_didd(void) +{ + IDI_SYNC_REQ req; + + stop_dbg(); + + req.didd_notify.e.Req = 0; + req.didd_notify.e.Rc = IDI_SYNC_REQ_DIDD_REMOVE_ADAPTER_NOTIFY; + req.didd_notify.info.handle = notify_handle; + DAdapter.request((ENTITY *) & req); +} + +/* + * init + */ +int DIVA_INIT_FUNCTION divasfunc_init(int dbgmask) +{ + char *version; + + debugmask = dbgmask; + + if (!connect_didd()) { + DBG_ERR(("divasfunc: failed to connect to DIDD.")) + return (0); + } + + version = diva_xdi_common_code_build; + + divasa_xdi_driver_entry(); + + return (1); +} + +/* + * exit + */ +void DIVA_EXIT_FUNCTION divasfunc_exit(void) +{ + divasa_xdi_driver_unload(); + disconnect_didd(); +} diff --git a/drivers/isdn/hardware/eicon/divasi.c b/drivers/isdn/hardware/eicon/divasi.c new file mode 100644 index 000000000000..df715b47e2b4 --- /dev/null +++ b/drivers/isdn/hardware/eicon/divasi.c @@ -0,0 +1,581 @@ +/* $Id: divasi.c,v 1.25.6.2 2005/01/31 12:22:20 armin Exp $ + * + * Driver for Eicon DIVA Server ISDN cards. + * User Mode IDI Interface + * + * Copyright 2000-2003 by Armin Schindler (mac@melware.de) + * Copyright 2000-2003 Cytronics & Melware (info@melware.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/smp_lock.h> +#include <linux/poll.h> +#include <linux/proc_fs.h> +#include <linux/skbuff.h> +#include <linux/devfs_fs_kernel.h> +#include <asm/uaccess.h> + +#include "platform.h" +#include "di_defs.h" +#include "divasync.h" +#include "um_xdi.h" +#include "um_idi.h" + +static char *main_revision = "$Revision: 1.25.6.2 $"; + +static int major; + +MODULE_DESCRIPTION("User IDI Interface for Eicon ISDN cards"); +MODULE_AUTHOR("Cytronics & Melware, Eicon Networks"); +MODULE_SUPPORTED_DEVICE("DIVA card driver"); +MODULE_LICENSE("GPL"); + +typedef struct _diva_um_idi_os_context { + wait_queue_head_t read_wait; + wait_queue_head_t close_wait; + struct timer_list diva_timer_id; + int aborted; + int adapter_nr; +} diva_um_idi_os_context_t; + +static char *DRIVERNAME = "Eicon DIVA - User IDI (http://www.melware.net)"; +static char *DRIVERLNAME = "diva_idi"; +static char *DEVNAME = "DivasIDI"; +char *DRIVERRELEASE_IDI = "2.0"; + +extern int idifunc_init(void); +extern void idifunc_finit(void); + +/* + * helper functions + */ +static char *getrev(const char *revision) +{ + char *rev; + char *p; + if ((p = strchr(revision, ':'))) { + rev = p + 2; + p = strchr(rev, '$'); + *--p = 0; + } else + rev = "1.0"; + return rev; +} + +/* + * LOCALS + */ +static ssize_t um_idi_read(struct file *file, char __user *buf, size_t count, + loff_t * offset); +static ssize_t um_idi_write(struct file *file, const char __user *buf, + size_t count, loff_t * offset); +static unsigned int um_idi_poll(struct file *file, poll_table * wait); +static int um_idi_open(struct inode *inode, struct file *file); +static int um_idi_release(struct inode *inode, struct file *file); +static int remove_entity(void *entity); +static void diva_um_timer_function(unsigned long data); + +/* + * proc entry + */ +extern struct proc_dir_entry *proc_net_eicon; +static struct proc_dir_entry *um_idi_proc_entry = NULL; + +static int +um_idi_proc_read(char *page, char **start, off_t off, int count, int *eof, + void *data) +{ + int len = 0; + char tmprev[32]; + + len += sprintf(page + len, "%s\n", DRIVERNAME); + len += sprintf(page + len, "name : %s\n", DRIVERLNAME); + len += sprintf(page + len, "release : %s\n", DRIVERRELEASE_IDI); + strcpy(tmprev, main_revision); + len += sprintf(page + len, "revision : %s\n", getrev(tmprev)); + len += sprintf(page + len, "build : %s\n", DIVA_BUILD); + len += sprintf(page + len, "major : %d\n", major); + + if (off + count >= len) + *eof = 1; + if (len < off) + return 0; + *start = page + off; + return ((count < len - off) ? count : len - off); +} + +static int DIVA_INIT_FUNCTION create_um_idi_proc(void) +{ + um_idi_proc_entry = create_proc_entry(DRIVERLNAME, + S_IFREG | S_IRUGO | S_IWUSR, + proc_net_eicon); + if (!um_idi_proc_entry) + return (0); + + um_idi_proc_entry->read_proc = um_idi_proc_read; + um_idi_proc_entry->owner = THIS_MODULE; + + return (1); +} + +static void remove_um_idi_proc(void) +{ + if (um_idi_proc_entry) { + remove_proc_entry(DRIVERLNAME, proc_net_eicon); + um_idi_proc_entry = NULL; + } +} + +static struct file_operations divas_idi_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = um_idi_read, + .write = um_idi_write, + .poll = um_idi_poll, + .open = um_idi_open, + .release = um_idi_release +}; + +static void divas_idi_unregister_chrdev(void) +{ + devfs_remove(DEVNAME); + unregister_chrdev(major, DEVNAME); +} + +static int DIVA_INIT_FUNCTION divas_idi_register_chrdev(void) +{ + if ((major = register_chrdev(0, DEVNAME, &divas_idi_fops)) < 0) + { + printk(KERN_ERR "%s: failed to create /dev entry.\n", + DRIVERLNAME); + return (0); + } + devfs_mk_cdev(MKDEV(major, 0), S_IFCHR|S_IRUSR|S_IWUSR, DEVNAME); + + return (1); +} + +/* +** Driver Load +*/ +static int DIVA_INIT_FUNCTION divasi_init(void) +{ + char tmprev[50]; + int ret = 0; + + printk(KERN_INFO "%s\n", DRIVERNAME); + printk(KERN_INFO "%s: Rel:%s Rev:", DRIVERLNAME, DRIVERRELEASE_IDI); + strcpy(tmprev, main_revision); + printk("%s Build: %s\n", getrev(tmprev), DIVA_BUILD); + + if (!divas_idi_register_chrdev()) { + ret = -EIO; + goto out; + } + + if (!create_um_idi_proc()) { + divas_idi_unregister_chrdev(); + printk(KERN_ERR "%s: failed to create proc entry.\n", + DRIVERLNAME); + ret = -EIO; + goto out; + } + + if (!(idifunc_init())) { + remove_um_idi_proc(); + divas_idi_unregister_chrdev(); + printk(KERN_ERR "%s: failed to connect to DIDD.\n", + DRIVERLNAME); + ret = -EIO; + goto out; + } + printk(KERN_INFO "%s: started with major %d\n", DRIVERLNAME, major); + + out: + return (ret); +} + + +/* +** Driver Unload +*/ +static void DIVA_EXIT_FUNCTION divasi_exit(void) +{ + idifunc_finit(); + remove_um_idi_proc(); + divas_idi_unregister_chrdev(); + + printk(KERN_INFO "%s: module unloaded.\n", DRIVERLNAME); +} + +module_init(divasi_init); +module_exit(divasi_exit); + + +/* + * FILE OPERATIONS + */ + +static int +divas_um_idi_copy_to_user(void *os_handle, void *dst, const void *src, + int length) +{ + memcpy(dst, src, length); + return (length); +} + +static ssize_t +um_idi_read(struct file *file, char __user *buf, size_t count, loff_t * offset) +{ + diva_um_idi_os_context_t *p_os; + int ret = -EINVAL; + void *data; + + if (!file->private_data) { + return (-ENODEV); + } + + if (! + (p_os = + (diva_um_idi_os_context_t *) diva_um_id_get_os_context(file-> + private_data))) + { + return (-ENODEV); + } + if (p_os->aborted) { + return (-ENODEV); + } + + if (!(data = diva_os_malloc(0, count))) { + return (-ENOMEM); + } + + ret = diva_um_idi_read(file->private_data, + file, data, count, + divas_um_idi_copy_to_user); + switch (ret) { + case 0: /* no message available */ + ret = (-EAGAIN); + break; + case (-1): /* adapter was removed */ + ret = (-ENODEV); + break; + case (-2): /* message_length > length of user buffer */ + ret = (-EFAULT); + break; + } + + if (ret > 0) { + if (copy_to_user(buf, data, ret)) { + ret = (-EFAULT); + } + } + + diva_os_free(0, data); + DBG_TRC(("read: ret %d", ret)); + return (ret); +} + + +static int +divas_um_idi_copy_from_user(void *os_handle, void *dst, const void *src, + int length) +{ + memcpy(dst, src, length); + return (length); +} + +static int um_idi_open_adapter(struct file *file, int adapter_nr) +{ + diva_um_idi_os_context_t *p_os; + void *e = + divas_um_idi_create_entity((dword) adapter_nr, (void *) file); + + if (!(file->private_data = e)) { + return (0); + } + p_os = (diva_um_idi_os_context_t *) diva_um_id_get_os_context(e); + init_waitqueue_head(&p_os->read_wait); + init_waitqueue_head(&p_os->close_wait); + init_timer(&p_os->diva_timer_id); + p_os->diva_timer_id.function = (void *) diva_um_timer_function; + p_os->diva_timer_id.data = (unsigned long) p_os; + p_os->aborted = 0; + p_os->adapter_nr = adapter_nr; + return (1); +} + +static ssize_t +um_idi_write(struct file *file, const char __user *buf, size_t count, + loff_t * offset) +{ + diva_um_idi_os_context_t *p_os; + int ret = -EINVAL; + void *data; + int adapter_nr = 0; + + if (!file->private_data) { + /* the first write() selects the adapter_nr */ + if (count == sizeof(int)) { + if (copy_from_user + ((void *) &adapter_nr, buf, + count)) return (-EFAULT); + if (!(um_idi_open_adapter(file, adapter_nr))) + return (-ENODEV); + return (count); + } else + return (-ENODEV); + } + + if (!(p_os = + (diva_um_idi_os_context_t *) diva_um_id_get_os_context(file-> + private_data))) + { + return (-ENODEV); + } + if (p_os->aborted) { + return (-ENODEV); + } + + if (!(data = diva_os_malloc(0, count))) { + return (-ENOMEM); + } + + if (copy_from_user(data, buf, count)) { + ret = -EFAULT; + } else { + ret = diva_um_idi_write(file->private_data, + file, data, count, + divas_um_idi_copy_from_user); + switch (ret) { + case 0: /* no space available */ + ret = (-EAGAIN); + break; + case (-1): /* adapter was removed */ + ret = (-ENODEV); + break; + case (-2): /* length of user buffer > max message_length */ + ret = (-EFAULT); + break; + } + } + diva_os_free(0, data); + DBG_TRC(("write: ret %d", ret)); + return (ret); +} + +static unsigned int um_idi_poll(struct file *file, poll_table * wait) +{ + diva_um_idi_os_context_t *p_os; + + if (!file->private_data) { + return (POLLERR); + } + + if ((!(p_os = + (diva_um_idi_os_context_t *) + diva_um_id_get_os_context(file->private_data))) + || p_os->aborted) { + return (POLLERR); + } + + poll_wait(file, &p_os->read_wait, wait); + + if (p_os->aborted) { + return (POLLERR); + } + + switch (diva_user_mode_idi_ind_ready(file->private_data, file)) { + case (-1): + return (POLLERR); + + case 0: + return (0); + } + + return (POLLIN | POLLRDNORM); +} + +static int um_idi_open(struct inode *inode, struct file *file) +{ + return (0); +} + + +static int um_idi_release(struct inode *inode, struct file *file) +{ + diva_um_idi_os_context_t *p_os; + unsigned int adapter_nr; + int ret = 0; + + if (!(file->private_data)) { + ret = -ENODEV; + goto out; + } + + if (!(p_os = + (diva_um_idi_os_context_t *) diva_um_id_get_os_context(file->private_data))) { + ret = -ENODEV; + goto out; + } + + adapter_nr = p_os->adapter_nr; + + if ((ret = remove_entity(file->private_data))) { + goto out; + } + + if (divas_um_idi_delete_entity + ((int) adapter_nr, file->private_data)) { + ret = -ENODEV; + goto out; + } + + out: + return (ret); +} + +int diva_os_get_context_size(void) +{ + return (sizeof(diva_um_idi_os_context_t)); +} + +void diva_os_wakeup_read(void *os_context) +{ + diva_um_idi_os_context_t *p_os = + (diva_um_idi_os_context_t *) os_context; + wake_up_interruptible(&p_os->read_wait); +} + +void diva_os_wakeup_close(void *os_context) +{ + diva_um_idi_os_context_t *p_os = + (diva_um_idi_os_context_t *) os_context; + wake_up_interruptible(&p_os->close_wait); +} + +static +void diva_um_timer_function(unsigned long data) +{ + diva_um_idi_os_context_t *p_os = (diva_um_idi_os_context_t *) data; + + p_os->aborted = 1; + wake_up_interruptible(&p_os->read_wait); + wake_up_interruptible(&p_os->close_wait); + DBG_ERR(("entity removal watchdog")) +} + +/* +** If application exits without entity removal this function will remove +** entity and block until removal is complete +*/ +static int remove_entity(void *entity) +{ + struct task_struct *curtask = current; + diva_um_idi_os_context_t *p_os; + + diva_um_idi_stop_wdog(entity); + + if (!entity) { + DBG_FTL(("Zero entity on remove")) + return (0); + } + + if (!(p_os = + (diva_um_idi_os_context_t *) + diva_um_id_get_os_context(entity))) { + DBG_FTL(("Zero entity os context on remove")) + return (0); + } + + if (!divas_um_idi_entity_assigned(entity) || p_os->aborted) { + /* + Entity is not assigned, also can be removed + */ + return (0); + } + + DBG_TRC(("E(%08x) check remove", entity)) + + /* + If adapter not answers on remove request inside of + 10 Sec, then adapter is dead + */ + diva_um_idi_start_wdog(entity); + + { + DECLARE_WAITQUEUE(wait, curtask); + + add_wait_queue(&p_os->close_wait, &wait); + for (;;) { + set_current_state(TASK_INTERRUPTIBLE); + if (!divas_um_idi_entity_start_remove(entity) + || p_os->aborted) { + break; + } + schedule(); + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&p_os->close_wait, &wait); + } + + DBG_TRC(("E(%08x) start remove", entity)) + { + DECLARE_WAITQUEUE(wait, curtask); + + add_wait_queue(&p_os->close_wait, &wait); + for (;;) { + set_current_state(TASK_INTERRUPTIBLE); + if (!divas_um_idi_entity_assigned(entity) + || p_os->aborted) { + break; + } + schedule(); + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&p_os->close_wait, &wait); + } + + DBG_TRC(("E(%08x) remove complete, aborted:%d", entity, + p_os->aborted)) + + diva_um_idi_stop_wdog(entity); + + p_os->aborted = 0; + + return (0); +} + +/* + * timer watchdog + */ +void diva_um_idi_start_wdog(void *entity) +{ + diva_um_idi_os_context_t *p_os; + + if (entity && + ((p_os = + (diva_um_idi_os_context_t *) + diva_um_id_get_os_context(entity)))) { + mod_timer(&p_os->diva_timer_id, jiffies + 10 * HZ); + } +} + +void diva_um_idi_stop_wdog(void *entity) +{ + diva_um_idi_os_context_t *p_os; + + if (entity && + ((p_os = + (diva_um_idi_os_context_t *) + diva_um_id_get_os_context(entity)))) { + del_timer(&p_os->diva_timer_id); + } +} diff --git a/drivers/isdn/hardware/eicon/divasmain.c b/drivers/isdn/hardware/eicon/divasmain.c new file mode 100644 index 000000000000..c9b26e86d183 --- /dev/null +++ b/drivers/isdn/hardware/eicon/divasmain.c @@ -0,0 +1,856 @@ +/* $Id: divasmain.c,v 1.55.4.6 2005/02/09 19:28:20 armin Exp $ + * + * Low level driver for Eicon DIVA Server ISDN cards. + * + * Copyright 2000-2003 by Armin Schindler (mac@melware.de) + * Copyright 2000-2003 Cytronics & Melware (info@melware.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/devfs_fs_kernel.h> +#include <asm/uaccess.h> +#include <asm/io.h> +#include <linux/ioport.h> +#include <linux/workqueue.h> +#include <linux/pci.h> +#include <linux/smp_lock.h> +#include <linux/interrupt.h> +#include <linux/list.h> +#include <linux/poll.h> +#include <linux/kmod.h> + +#include "platform.h" +#undef ID_MASK +#undef N_DATA +#include "pc.h" +#include "di_defs.h" +#include "divasync.h" +#include "diva.h" +#include "di.h" +#include "io.h" +#include "xdi_msg.h" +#include "xdi_adapter.h" +#include "xdi_vers.h" +#include "diva_dma.h" +#include "diva_pci.h" + +static char *main_revision = "$Revision: 1.55.4.6 $"; + +static int major; + +static int dbgmask; + +MODULE_DESCRIPTION("Kernel driver for Eicon DIVA Server cards"); +MODULE_AUTHOR("Cytronics & Melware, Eicon Networks"); +MODULE_LICENSE("GPL"); + +module_param(dbgmask, int, 0); +MODULE_PARM_DESC(dbgmask, "initial debug mask"); + +static char *DRIVERNAME = + "Eicon DIVA Server driver (http://www.melware.net)"; +static char *DRIVERLNAME = "divas"; +static char *DEVNAME = "Divas"; +char *DRIVERRELEASE_DIVAS = "2.0"; + +extern irqreturn_t diva_os_irq_wrapper(int irq, void *context, + struct pt_regs *regs); +extern int create_divas_proc(void); +extern void remove_divas_proc(void); +extern void diva_get_vserial_number(PISDN_ADAPTER IoAdapter, char *buf); +extern int divasfunc_init(int dbgmask); +extern void divasfunc_exit(void); + +typedef struct _diva_os_thread_dpc { + struct tasklet_struct divas_task; + diva_os_soft_isr_t *psoft_isr; +} diva_os_thread_dpc_t; + +/* -------------------------------------------------------------------------- + PCI driver interface section + -------------------------------------------------------------------------- */ +/* + vendor, device Vendor and device ID to match (or PCI_ANY_ID) + subvendor, Subsystem vendor and device ID to match (or PCI_ANY_ID) + subdevice + class, Device class to match. The class_mask tells which bits + class_mask of the class are honored during the comparison. + driver_data Data private to the driver. + */ + +#if !defined(PCI_DEVICE_ID_EICON_MAESTRAP_2) +#define PCI_DEVICE_ID_EICON_MAESTRAP_2 0xE015 +#endif + +#if !defined(PCI_DEVICE_ID_EICON_4BRI_VOIP) +#define PCI_DEVICE_ID_EICON_4BRI_VOIP 0xE016 +#endif + +#if !defined(PCI_DEVICE_ID_EICON_4BRI_2_VOIP) +#define PCI_DEVICE_ID_EICON_4BRI_2_VOIP 0xE017 +#endif + +#if !defined(PCI_DEVICE_ID_EICON_BRI2M_2) +#define PCI_DEVICE_ID_EICON_BRI2M_2 0xE018 +#endif + +#if !defined(PCI_DEVICE_ID_EICON_MAESTRAP_2_VOIP) +#define PCI_DEVICE_ID_EICON_MAESTRAP_2_VOIP 0xE019 +#endif + +#if !defined(PCI_DEVICE_ID_EICON_2F) +#define PCI_DEVICE_ID_EICON_2F 0xE01A +#endif + +#if !defined(PCI_DEVICE_ID_EICON_BRI2M_2_VOIP) +#define PCI_DEVICE_ID_EICON_BRI2M_2_VOIP 0xE01B +#endif + +/* + This table should be sorted by PCI device ID + */ +static struct pci_device_id divas_pci_tbl[] = { +/* Diva Server BRI-2M PCI 0xE010 */ + {PCI_VENDOR_ID_EICON, PCI_DEVICE_ID_EICON_MAESTRA, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, CARDTYPE_MAESTRA_PCI}, +/* Diva Server 4BRI-8M PCI 0xE012 */ + {PCI_VENDOR_ID_EICON, PCI_DEVICE_ID_EICON_MAESTRAQ, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, CARDTYPE_DIVASRV_Q_8M_PCI}, +/* Diva Server 4BRI-8M 2.0 PCI 0xE013 */ + {PCI_VENDOR_ID_EICON, PCI_DEVICE_ID_EICON_MAESTRAQ_U, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, CARDTYPE_DIVASRV_Q_8M_V2_PCI}, +/* Diva Server PRI-30M PCI 0xE014 */ + {PCI_VENDOR_ID_EICON, PCI_DEVICE_ID_EICON_MAESTRAP, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, CARDTYPE_DIVASRV_P_30M_PCI}, +/* Diva Server PRI 2.0 adapter 0xE015 */ + {PCI_VENDOR_ID_EICON, PCI_DEVICE_ID_EICON_MAESTRAP_2, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, CARDTYPE_DIVASRV_P_30M_V2_PCI}, +/* Diva Server Voice 4BRI-8M PCI 0xE016 */ + {PCI_VENDOR_ID_EICON, PCI_DEVICE_ID_EICON_4BRI_VOIP, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, CARDTYPE_DIVASRV_VOICE_Q_8M_PCI}, +/* Diva Server Voice 4BRI-8M 2.0 PCI 0xE017 */ + {PCI_VENDOR_ID_EICON, PCI_DEVICE_ID_EICON_4BRI_2_VOIP, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, CARDTYPE_DIVASRV_VOICE_Q_8M_V2_PCI}, +/* Diva Server BRI-2M 2.0 PCI 0xE018 */ + {PCI_VENDOR_ID_EICON, PCI_DEVICE_ID_EICON_BRI2M_2, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, CARDTYPE_DIVASRV_B_2M_V2_PCI}, +/* Diva Server Voice PRI 2.0 PCI 0xE019 */ + {PCI_VENDOR_ID_EICON, PCI_DEVICE_ID_EICON_MAESTRAP_2_VOIP, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, + CARDTYPE_DIVASRV_VOICE_P_30M_V2_PCI}, +/* Diva Server 2FX 0xE01A */ + {PCI_VENDOR_ID_EICON, PCI_DEVICE_ID_EICON_2F, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, CARDTYPE_DIVASRV_B_2F_PCI}, +/* Diva Server Voice BRI-2M 2.0 PCI 0xE01B */ + {PCI_VENDOR_ID_EICON, PCI_DEVICE_ID_EICON_BRI2M_2_VOIP, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, CARDTYPE_DIVASRV_VOICE_B_2M_V2_PCI}, + {0,} /* 0 terminated list. */ +}; +MODULE_DEVICE_TABLE(pci, divas_pci_tbl); + +static int divas_init_one(struct pci_dev *pdev, + const struct pci_device_id *ent); +static void __devexit divas_remove_one(struct pci_dev *pdev); + +static struct pci_driver diva_pci_driver = { + .name = "divas", + .probe = divas_init_one, + .remove = __devexit_p(divas_remove_one), + .id_table = divas_pci_tbl, +}; + +/********************************************************* + ** little helper functions + *********************************************************/ +static char *getrev(const char *revision) +{ + char *rev; + char *p; + if ((p = strchr(revision, ':'))) { + rev = p + 2; + p = strchr(rev, '$'); + *--p = 0; + } else + rev = "1.0"; + return rev; +} + +void diva_log_info(unsigned char *format, ...) +{ + va_list args; + unsigned char line[160]; + + va_start(args, format); + vsprintf(line, format, args); + va_end(args); + + printk(KERN_INFO "%s: %s\n", DRIVERLNAME, line); +} + +void divas_get_version(char *p) +{ + char tmprev[32]; + + strcpy(tmprev, main_revision); + sprintf(p, "%s: %s(%s) %s(%s) major=%d\n", DRIVERLNAME, DRIVERRELEASE_DIVAS, + getrev(tmprev), diva_xdi_common_code_build, DIVA_BUILD, major); +} + +/* -------------------------------------------------------------------------- + PCI Bus services + -------------------------------------------------------------------------- */ +byte diva_os_get_pci_bus(void *pci_dev_handle) +{ + struct pci_dev *pdev = (struct pci_dev *) pci_dev_handle; + return ((byte) pdev->bus->number); +} + +byte diva_os_get_pci_func(void *pci_dev_handle) +{ + struct pci_dev *pdev = (struct pci_dev *) pci_dev_handle; + return ((byte) pdev->devfn); +} + +unsigned long divasa_get_pci_irq(unsigned char bus, unsigned char func, + void *pci_dev_handle) +{ + unsigned char irq = 0; + struct pci_dev *dev = (struct pci_dev *) pci_dev_handle; + + irq = dev->irq; + + return ((unsigned long) irq); +} + +unsigned long divasa_get_pci_bar(unsigned char bus, unsigned char func, + int bar, void *pci_dev_handle) +{ + unsigned long ret = 0; + struct pci_dev *dev = (struct pci_dev *) pci_dev_handle; + + if (bar < 6) { + ret = dev->resource[bar].start; + } + + DBG_TRC(("GOT BAR[%d]=%08x", bar, ret)); + + { + unsigned long type = (ret & 0x00000001); + if (type & PCI_BASE_ADDRESS_SPACE_IO) { + DBG_TRC((" I/O")); + ret &= PCI_BASE_ADDRESS_IO_MASK; + } else { + DBG_TRC((" memory")); + ret &= PCI_BASE_ADDRESS_MEM_MASK; + } + DBG_TRC((" final=%08x", ret)); + } + + return (ret); +} + +void PCIwrite(byte bus, byte func, int offset, void *data, int length, + void *pci_dev_handle) +{ + struct pci_dev *dev = (struct pci_dev *) pci_dev_handle; + + switch (length) { + case 1: /* byte */ + pci_write_config_byte(dev, offset, + *(unsigned char *) data); + break; + case 2: /* word */ + pci_write_config_word(dev, offset, + *(unsigned short *) data); + break; + case 4: /* dword */ + pci_write_config_dword(dev, offset, + *(unsigned int *) data); + break; + + default: /* buffer */ + if (!(length % 4) && !(length & 0x03)) { /* Copy as dword */ + dword *p = (dword *) data; + length /= 4; + + while (length--) { + pci_write_config_dword(dev, offset, + *(unsigned int *) + p++); + } + } else { /* copy as byte stream */ + byte *p = (byte *) data; + + while (length--) { + pci_write_config_byte(dev, offset, + *(unsigned char *) + p++); + } + } + } +} + +void PCIread(byte bus, byte func, int offset, void *data, int length, + void *pci_dev_handle) +{ + struct pci_dev *dev = (struct pci_dev *) pci_dev_handle; + + switch (length) { + case 1: /* byte */ + pci_read_config_byte(dev, offset, (unsigned char *) data); + break; + case 2: /* word */ + pci_read_config_word(dev, offset, (unsigned short *) data); + break; + case 4: /* dword */ + pci_read_config_dword(dev, offset, (unsigned int *) data); + break; + + default: /* buffer */ + if (!(length % 4) && !(length & 0x03)) { /* Copy as dword */ + dword *p = (dword *) data; + length /= 4; + + while (length--) { + pci_read_config_dword(dev, offset, + (unsigned int *) + p++); + } + } else { /* copy as byte stream */ + byte *p = (byte *) data; + + while (length--) { + pci_read_config_byte(dev, offset, + (unsigned char *) + p++); + } + } + } +} + +/* + Init map with DMA pages. It is not problem if some allocations fail - + the channels that will not get one DMA page will use standard PIO + interface + */ +static void *diva_pci_alloc_consistent(struct pci_dev *hwdev, + size_t size, + dma_addr_t * dma_handle, + void **addr_handle) +{ + void *addr = pci_alloc_consistent(hwdev, size, dma_handle); + + *addr_handle = addr; + + return (addr); +} + +void diva_init_dma_map(void *hdev, + struct _diva_dma_map_entry **ppmap, int nentries) +{ + struct pci_dev *pdev = (struct pci_dev *) hdev; + struct _diva_dma_map_entry *pmap = + diva_alloc_dma_map(hdev, nentries); + + if (pmap) { + int i; + dma_addr_t dma_handle; + void *cpu_addr; + void *addr_handle; + + for (i = 0; i < nentries; i++) { + if (!(cpu_addr = diva_pci_alloc_consistent(pdev, + PAGE_SIZE, + &dma_handle, + &addr_handle))) + { + break; + } + diva_init_dma_map_entry(pmap, i, cpu_addr, + (dword) dma_handle, + addr_handle); + DBG_TRC(("dma map alloc [%d]=(%08lx:%08x:%08lx)", + i, (unsigned long) cpu_addr, + (dword) dma_handle, + (unsigned long) addr_handle))} + } + + *ppmap = pmap; +} + +/* + Free all contained in the map entries and memory used by the map + Should be always called after adapter removal from DIDD array + */ +void diva_free_dma_map(void *hdev, struct _diva_dma_map_entry *pmap) +{ + struct pci_dev *pdev = (struct pci_dev *) hdev; + int i; + dword phys_addr; + void *cpu_addr; + dma_addr_t dma_handle; + void *addr_handle; + + for (i = 0; (pmap != 0); i++) { + diva_get_dma_map_entry(pmap, i, &cpu_addr, &phys_addr); + if (!cpu_addr) { + break; + } + addr_handle = diva_get_entry_handle(pmap, i); + dma_handle = (dma_addr_t) phys_addr; + pci_free_consistent(pdev, PAGE_SIZE, addr_handle, + dma_handle); + DBG_TRC(("dma map free [%d]=(%08lx:%08x:%08lx)", i, + (unsigned long) cpu_addr, (dword) dma_handle, + (unsigned long) addr_handle)) + } + + diva_free_dma_mapping(pmap); +} + + +/********************************************************* + ** I/O port utilities + *********************************************************/ + +int +diva_os_register_io_port(void *adapter, int on, unsigned long port, + unsigned long length, const char *name, int id) +{ + if (on) { + if (!request_region(port, length, name)) { + DBG_ERR(("A: I/O: can't register port=%08x", port)) + return (-1); + } + } else { + release_region(port, length); + } + return (0); +} + +void __iomem *divasa_remap_pci_bar(diva_os_xdi_adapter_t *a, int id, unsigned long bar, unsigned long area_length) +{ + void __iomem *ret = ioremap(bar, area_length); + DBG_TRC(("remap(%08x)->%p", bar, ret)); + return (ret); +} + +void divasa_unmap_pci_bar(void __iomem *bar) +{ + if (bar) { + iounmap(bar); + } +} + +/********************************************************* + ** I/O port access + *********************************************************/ +byte __inline__ inpp(void __iomem *addr) +{ + return (inb((unsigned long) addr)); +} + +word __inline__ inppw(void __iomem *addr) +{ + return (inw((unsigned long) addr)); +} + +void __inline__ inppw_buffer(void __iomem *addr, void *P, int length) +{ + insw((unsigned long) addr, (word *) P, length >> 1); +} + +void __inline__ outppw_buffer(void __iomem *addr, void *P, int length) +{ + outsw((unsigned long) addr, (word *) P, length >> 1); +} + +void __inline__ outppw(void __iomem *addr, word w) +{ + outw(w, (unsigned long) addr); +} + +void __inline__ outpp(void __iomem *addr, word p) +{ + outb(p, (unsigned long) addr); +} + +/* -------------------------------------------------------------------------- + IRQ request / remove + -------------------------------------------------------------------------- */ +int diva_os_register_irq(void *context, byte irq, const char *name) +{ + int result = request_irq(irq, diva_os_irq_wrapper, + SA_INTERRUPT | SA_SHIRQ, name, context); + return (result); +} + +void diva_os_remove_irq(void *context, byte irq) +{ + free_irq(irq, context); +} + +/* -------------------------------------------------------------------------- + DPC framework implementation + -------------------------------------------------------------------------- */ +static void diva_os_dpc_proc(unsigned long context) +{ + diva_os_thread_dpc_t *psoft_isr = (diva_os_thread_dpc_t *) context; + diva_os_soft_isr_t *pisr = psoft_isr->psoft_isr; + + (*(pisr->callback)) (pisr, pisr->callback_context); +} + +int diva_os_initialize_soft_isr(diva_os_soft_isr_t * psoft_isr, + diva_os_soft_isr_callback_t callback, + void *callback_context) +{ + diva_os_thread_dpc_t *pdpc; + + pdpc = (diva_os_thread_dpc_t *) diva_os_malloc(0, sizeof(*pdpc)); + if (!(psoft_isr->object = pdpc)) { + return (-1); + } + memset(pdpc, 0x00, sizeof(*pdpc)); + psoft_isr->callback = callback; + psoft_isr->callback_context = callback_context; + pdpc->psoft_isr = psoft_isr; + tasklet_init(&pdpc->divas_task, diva_os_dpc_proc, (unsigned long)pdpc); + + return (0); +} + +int diva_os_schedule_soft_isr(diva_os_soft_isr_t * psoft_isr) +{ + if (psoft_isr && psoft_isr->object) { + diva_os_thread_dpc_t *pdpc = + (diva_os_thread_dpc_t *) psoft_isr->object; + + tasklet_schedule(&pdpc->divas_task); + } + + return (1); +} + +int diva_os_cancel_soft_isr(diva_os_soft_isr_t * psoft_isr) +{ + return (0); +} + +void diva_os_remove_soft_isr(diva_os_soft_isr_t * psoft_isr) +{ + if (psoft_isr && psoft_isr->object) { + diva_os_thread_dpc_t *pdpc = + (diva_os_thread_dpc_t *) psoft_isr->object; + void *mem; + + tasklet_kill(&pdpc->divas_task); + flush_scheduled_work(); + mem = psoft_isr->object; + psoft_isr->object = NULL; + diva_os_free(0, mem); + } +} + +/* + * kernel/user space copy functions + */ +static int +xdi_copy_to_user(void *os_handle, void __user *dst, const void *src, int length) +{ + if (copy_to_user(dst, src, length)) { + return (-EFAULT); + } + return (length); +} + +static int +xdi_copy_from_user(void *os_handle, void *dst, const void __user *src, int length) +{ + if (copy_from_user(dst, src, length)) { + return (-EFAULT); + } + return (length); +} + +/* + * device node operations + */ +static int divas_open(struct inode *inode, struct file *file) +{ + return (0); +} + +static int divas_release(struct inode *inode, struct file *file) +{ + if (file->private_data) { + diva_xdi_close_adapter(file->private_data, file); + } + return (0); +} + +static ssize_t divas_write(struct file *file, const char __user *buf, + size_t count, loff_t * ppos) +{ + int ret = -EINVAL; + + if (!file->private_data) { + file->private_data = diva_xdi_open_adapter(file, buf, + count, + xdi_copy_from_user); + } + if (!file->private_data) { + return (-ENODEV); + } + + ret = diva_xdi_write(file->private_data, file, + buf, count, xdi_copy_from_user); + switch (ret) { + case -1: /* Message should be removed from rx mailbox first */ + ret = -EBUSY; + break; + case -2: /* invalid adapter was specified in this call */ + ret = -ENOMEM; + break; + case -3: + ret = -ENXIO; + break; + } + DBG_TRC(("write: ret %d", ret)); + return (ret); +} + +static ssize_t divas_read(struct file *file, char __user *buf, + size_t count, loff_t * ppos) +{ + int ret = -EINVAL; + + if (!file->private_data) { + file->private_data = diva_xdi_open_adapter(file, buf, + count, + xdi_copy_from_user); + } + if (!file->private_data) { + return (-ENODEV); + } + + ret = diva_xdi_read(file->private_data, file, + buf, count, xdi_copy_to_user); + switch (ret) { + case -1: /* RX mailbox is empty */ + ret = -EAGAIN; + break; + case -2: /* no memory, mailbox was cleared, last command is failed */ + ret = -ENOMEM; + break; + case -3: /* can't copy to user, retry */ + ret = -EFAULT; + break; + } + DBG_TRC(("read: ret %d", ret)); + return (ret); +} + +static unsigned int divas_poll(struct file *file, poll_table * wait) +{ + if (!file->private_data) { + return (POLLERR); + } + return (POLLIN | POLLRDNORM); +} + +static struct file_operations divas_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = divas_read, + .write = divas_write, + .poll = divas_poll, + .open = divas_open, + .release = divas_release +}; + +static void divas_unregister_chrdev(void) +{ + devfs_remove(DEVNAME); + unregister_chrdev(major, DEVNAME); +} + +static int DIVA_INIT_FUNCTION divas_register_chrdev(void) +{ + if ((major = register_chrdev(0, DEVNAME, &divas_fops)) < 0) + { + printk(KERN_ERR "%s: failed to create /dev entry.\n", + DRIVERLNAME); + return (0); + } + devfs_mk_cdev(MKDEV(major, 0), S_IFCHR|S_IRUSR|S_IWUSR, DEVNAME); + + return (1); +} + +/* -------------------------------------------------------------------------- + PCI driver section + -------------------------------------------------------------------------- */ +static int __devinit divas_init_one(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + void *pdiva = NULL; + u8 pci_latency; + u8 new_latency = 32; + + DBG_TRC(("%s bus: %08x fn: %08x insertion.\n", + CardProperties[ent->driver_data].Name, + pdev->bus->number, pdev->devfn)) + printk(KERN_INFO "%s: %s bus: %08x fn: %08x insertion.\n", + DRIVERLNAME, CardProperties[ent->driver_data].Name, + pdev->bus->number, pdev->devfn); + + if (pci_enable_device(pdev)) { + DBG_TRC(("%s: %s bus: %08x fn: %08x device init failed.\n", + DRIVERLNAME, + CardProperties[ent->driver_data].Name, + pdev->bus->number, + pdev->devfn)) + printk(KERN_ERR + "%s: %s bus: %08x fn: %08x device init failed.\n", + DRIVERLNAME, + CardProperties[ent->driver_data]. + Name, pdev->bus->number, + pdev->devfn); + return (-EIO); + } + + pci_set_master(pdev); + + pci_read_config_byte(pdev, PCI_LATENCY_TIMER, &pci_latency); + if (!pci_latency) { + DBG_TRC(("%s: bus: %08x fn: %08x fix latency.\n", + DRIVERLNAME, pdev->bus->number, pdev->devfn)) + printk(KERN_INFO + "%s: bus: %08x fn: %08x fix latency.\n", + DRIVERLNAME, pdev->bus->number, pdev->devfn); + pci_write_config_byte(pdev, PCI_LATENCY_TIMER, new_latency); + } + + if (!(pdiva = diva_driver_add_card(pdev, ent->driver_data))) { + DBG_TRC(("%s: %s bus: %08x fn: %08x card init failed.\n", + DRIVERLNAME, + CardProperties[ent->driver_data].Name, + pdev->bus->number, + pdev->devfn)) + printk(KERN_ERR + "%s: %s bus: %08x fn: %08x card init failed.\n", + DRIVERLNAME, + CardProperties[ent->driver_data]. + Name, pdev->bus->number, + pdev->devfn); + return (-EIO); + } + + pci_set_drvdata(pdev, pdiva); + + return (0); +} + +static void __devexit divas_remove_one(struct pci_dev *pdev) +{ + void *pdiva = pci_get_drvdata(pdev); + + DBG_TRC(("bus: %08x fn: %08x removal.\n", + pdev->bus->number, pdev->devfn)) + printk(KERN_INFO "%s: bus: %08x fn: %08x removal.\n", + DRIVERLNAME, pdev->bus->number, pdev->devfn); + + if (pdiva) { + diva_driver_remove_card(pdiva); + } + +} + +/* -------------------------------------------------------------------------- + Driver Load / Startup + -------------------------------------------------------------------------- */ +static int DIVA_INIT_FUNCTION divas_init(void) +{ + char tmprev[50]; + int ret = 0; + + printk(KERN_INFO "%s\n", DRIVERNAME); + printk(KERN_INFO "%s: Rel:%s Rev:", DRIVERLNAME, DRIVERRELEASE_DIVAS); + strcpy(tmprev, main_revision); + printk("%s Build: %s(%s)\n", getrev(tmprev), + diva_xdi_common_code_build, DIVA_BUILD); + printk(KERN_INFO "%s: support for: ", DRIVERLNAME); +#ifdef CONFIG_ISDN_DIVAS_BRIPCI + printk("BRI/PCI "); +#endif +#ifdef CONFIG_ISDN_DIVAS_PRIPCI + printk("PRI/PCI "); +#endif + printk("adapters\n"); + + if (!divasfunc_init(dbgmask)) { + printk(KERN_ERR "%s: failed to connect to DIDD.\n", + DRIVERLNAME); + ret = -EIO; + goto out; + } + + if (!divas_register_chrdev()) { +#ifdef MODULE + divasfunc_exit(); +#endif + ret = -EIO; + goto out; + } + + if (!create_divas_proc()) { +#ifdef MODULE + remove_divas_proc(); + divas_unregister_chrdev(); + divasfunc_exit(); +#endif + printk(KERN_ERR "%s: failed to create proc entry.\n", + DRIVERLNAME); + ret = -EIO; + goto out; + } + + if ((ret = pci_register_driver(&diva_pci_driver))) { +#ifdef MODULE + remove_divas_proc(); + divas_unregister_chrdev(); + divasfunc_exit(); +#endif + printk(KERN_ERR "%s: failed to init pci driver.\n", + DRIVERLNAME); + goto out; + } + printk(KERN_INFO "%s: started with major %d\n", DRIVERLNAME, major); + + out: + return (ret); +} + +/* -------------------------------------------------------------------------- + Driver Unload + -------------------------------------------------------------------------- */ +static void DIVA_EXIT_FUNCTION divas_exit(void) +{ + pci_unregister_driver(&diva_pci_driver); + remove_divas_proc(); + divas_unregister_chrdev(); + divasfunc_exit(); + + printk(KERN_INFO "%s: module unloaded.\n", DRIVERLNAME); +} + +module_init(divas_init); +module_exit(divas_exit); diff --git a/drivers/isdn/hardware/eicon/divasproc.c b/drivers/isdn/hardware/eicon/divasproc.c new file mode 100644 index 000000000000..b6435589d459 --- /dev/null +++ b/drivers/isdn/hardware/eicon/divasproc.c @@ -0,0 +1,441 @@ +/* $Id: divasproc.c,v 1.19.4.3 2005/01/31 12:22:20 armin Exp $ + * + * Low level driver for Eicon DIVA Server ISDN cards. + * /proc functions + * + * Copyright 2000-2003 by Armin Schindler (mac@melware.de) + * Copyright 2000-2003 Cytronics & Melware (info@melware.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/poll.h> +#include <linux/proc_fs.h> +#include <linux/list.h> +#include <asm/uaccess.h> + +#include "platform.h" +#include "debuglib.h" +#undef ID_MASK +#undef N_DATA +#include "pc.h" +#include "di_defs.h" +#include "divasync.h" +#include "di.h" +#include "io.h" +#include "xdi_msg.h" +#include "xdi_adapter.h" +#include "diva.h" +#include "diva_pci.h" + + +extern PISDN_ADAPTER IoAdapters[MAX_ADAPTER]; +extern void divas_get_version(char *); +extern void diva_get_vserial_number(PISDN_ADAPTER IoAdapter, char *buf); + +/********************************************************* + ** Functions for /proc interface / File operations + *********************************************************/ + +static char *divas_proc_name = "divas"; +static char *adapter_dir_name = "adapter"; +static char *info_proc_name = "info"; +static char *grp_opt_proc_name = "group_optimization"; +static char *d_l1_down_proc_name = "dynamic_l1_down"; + +/* +** "divas" entry +*/ + +extern struct proc_dir_entry *proc_net_eicon; +static struct proc_dir_entry *divas_proc_entry = NULL; + +static ssize_t +divas_read(struct file *file, char __user *buf, size_t count, loff_t * off) +{ + int len = 0; + int cadapter; + char tmpbuf[80]; + char tmpser[16]; + + if (*off) + return 0; + + divas_get_version(tmpbuf); + if (copy_to_user(buf + len, &tmpbuf, strlen(tmpbuf))) + return -EFAULT; + len += strlen(tmpbuf); + + for (cadapter = 0; cadapter < MAX_ADAPTER; cadapter++) { + if (IoAdapters[cadapter]) { + diva_get_vserial_number(IoAdapters[cadapter], + tmpser); + sprintf(tmpbuf, + "%2d: %-30s Serial:%-10s IRQ:%2d\n", + cadapter + 1, + IoAdapters[cadapter]->Properties.Name, + tmpser, + IoAdapters[cadapter]->irq_info.irq_nr); + if ((strlen(tmpbuf) + len) > count) + break; + if (copy_to_user + (buf + len, &tmpbuf, + strlen(tmpbuf))) return -EFAULT; + len += strlen(tmpbuf); + } + } + + *off += len; + return (len); +} + +static ssize_t +divas_write(struct file *file, const char __user *buf, size_t count, loff_t * off) +{ + return (-ENODEV); +} + +static unsigned int divas_poll(struct file *file, poll_table * wait) +{ + return (POLLERR); +} + +static int divas_open(struct inode *inode, struct file *file) +{ + return nonseekable_open(inode, file); +} + +static int divas_close(struct inode *inode, struct file *file) +{ + return (0); +} + +static struct file_operations divas_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = divas_read, + .write = divas_write, + .poll = divas_poll, + .open = divas_open, + .release = divas_close +}; + +int create_divas_proc(void) +{ + divas_proc_entry = create_proc_entry(divas_proc_name, + S_IFREG | S_IRUGO, + proc_net_eicon); + if (!divas_proc_entry) + return (0); + + divas_proc_entry->proc_fops = &divas_fops; + divas_proc_entry->owner = THIS_MODULE; + + return (1); +} + +void remove_divas_proc(void) +{ + if (divas_proc_entry) { + remove_proc_entry(divas_proc_name, proc_net_eicon); + divas_proc_entry = NULL; + } +} + +/* +** write group_optimization +*/ +static int +write_grp_opt(struct file *file, const char __user *buffer, unsigned long count, + void *data) +{ + diva_os_xdi_adapter_t *a = (diva_os_xdi_adapter_t *) data; + PISDN_ADAPTER IoAdapter = IoAdapters[a->controller - 1]; + + if ((count == 1) || (count == 2)) { + char c; + if (get_user(c, buffer)) + return -EFAULT; + switch (c) { + case '0': + IoAdapter->capi_cfg.cfg_1 &= + ~DIVA_XDI_CAPI_CFG_1_GROUP_POPTIMIZATION_ON; + break; + case '1': + IoAdapter->capi_cfg.cfg_1 |= + DIVA_XDI_CAPI_CFG_1_GROUP_POPTIMIZATION_ON; + break; + default: + return (-EINVAL); + } + return (count); + } + return (-EINVAL); +} + +/* +** write dynamic_l1_down +*/ +static int +write_d_l1_down(struct file *file, const char __user *buffer, unsigned long count, + void *data) +{ + diva_os_xdi_adapter_t *a = (diva_os_xdi_adapter_t *) data; + PISDN_ADAPTER IoAdapter = IoAdapters[a->controller - 1]; + + if ((count == 1) || (count == 2)) { + char c; + if (get_user(c, buffer)) + return -EFAULT; + switch (c) { + case '0': + IoAdapter->capi_cfg.cfg_1 &= + ~DIVA_XDI_CAPI_CFG_1_DYNAMIC_L1_ON; + break; + case '1': + IoAdapter->capi_cfg.cfg_1 |= + DIVA_XDI_CAPI_CFG_1_DYNAMIC_L1_ON; + break; + default: + return (-EINVAL); + } + return (count); + } + return (-EINVAL); +} + + +/* +** read dynamic_l1_down +*/ +static int +read_d_l1_down(char *page, char **start, off_t off, int count, int *eof, + void *data) +{ + int len = 0; + diva_os_xdi_adapter_t *a = (diva_os_xdi_adapter_t *) data; + PISDN_ADAPTER IoAdapter = IoAdapters[a->controller - 1]; + + len += sprintf(page + len, "%s\n", + (IoAdapter->capi_cfg. + cfg_1 & DIVA_XDI_CAPI_CFG_1_DYNAMIC_L1_ON) ? "1" : + "0"); + + if (off + count >= len) + *eof = 1; + if (len < off) + return 0; + *start = page + off; + return ((count < len - off) ? count : len - off); +} + +/* +** read group_optimization +*/ +static int +read_grp_opt(char *page, char **start, off_t off, int count, int *eof, + void *data) +{ + int len = 0; + diva_os_xdi_adapter_t *a = (diva_os_xdi_adapter_t *) data; + PISDN_ADAPTER IoAdapter = IoAdapters[a->controller - 1]; + + len += sprintf(page + len, "%s\n", + (IoAdapter->capi_cfg. + cfg_1 & DIVA_XDI_CAPI_CFG_1_GROUP_POPTIMIZATION_ON) + ? "1" : "0"); + + if (off + count >= len) + *eof = 1; + if (len < off) + return 0; + *start = page + off; + return ((count < len - off) ? count : len - off); +} + +/* +** info write +*/ +static int +info_write(struct file *file, const char __user *buffer, unsigned long count, + void *data) +{ + diva_os_xdi_adapter_t *a = (diva_os_xdi_adapter_t *) data; + PISDN_ADAPTER IoAdapter = IoAdapters[a->controller - 1]; + char c[4]; + + if (count <= 4) + return -EINVAL; + + if (copy_from_user(c, buffer, 4)) + return -EFAULT; + + /* this is for test purposes only */ + if (!memcmp(c, "trap", 4)) { + (*(IoAdapter->os_trap_nfy_Fnc)) (IoAdapter, IoAdapter->ANum); + return (count); + } + return (-EINVAL); +} + +/* +** info read +*/ +static int +info_read(char *page, char **start, off_t off, int count, int *eof, + void *data) +{ + int i = 0; + int len = 0; + char *p; + char tmpser[16]; + diva_os_xdi_adapter_t *a = (diva_os_xdi_adapter_t *) data; + PISDN_ADAPTER IoAdapter = IoAdapters[a->controller - 1]; + + len += + sprintf(page + len, "Name : %s\n", + IoAdapter->Properties.Name); + len += sprintf(page + len, "DSP state : %08x\n", a->dsp_mask); + len += sprintf(page + len, "Channels : %02d\n", + IoAdapter->Properties.Channels); + len += sprintf(page + len, "E. max/used : %03d/%03d\n", + IoAdapter->e_max, IoAdapter->e_count); + diva_get_vserial_number(IoAdapter, tmpser); + len += sprintf(page + len, "Serial : %s\n", tmpser); + len += + sprintf(page + len, "IRQ : %d\n", + IoAdapter->irq_info.irq_nr); + len += sprintf(page + len, "CardIndex : %d\n", a->CardIndex); + len += sprintf(page + len, "CardOrdinal : %d\n", a->CardOrdinal); + len += sprintf(page + len, "Controller : %d\n", a->controller); + len += sprintf(page + len, "Bus-Type : %s\n", + (a->Bus == + DIVAS_XDI_ADAPTER_BUS_ISA) ? "ISA" : "PCI"); + len += sprintf(page + len, "Port-Name : %s\n", a->port_name); + if (a->Bus == DIVAS_XDI_ADAPTER_BUS_PCI) { + len += + sprintf(page + len, "PCI-bus : %d\n", + a->resources.pci.bus); + len += + sprintf(page + len, "PCI-func : %d\n", + a->resources.pci.func); + for (i = 0; i < 8; i++) { + if (a->resources.pci.bar[i]) { + len += + sprintf(page + len, + "Mem / I/O %d : 0x%x / mapped : 0x%lx", + i, a->resources.pci.bar[i], + (unsigned long) a->resources. + pci.addr[i]); + if (a->resources.pci.length[i]) { + len += + sprintf(page + len, + " / length : %d", + a->resources.pci. + length[i]); + } + len += sprintf(page + len, "\n"); + } + } + } + if ((!a->xdi_adapter.port) && + ((!a->xdi_adapter.ram) || + (!a->xdi_adapter.reset) + || (!a->xdi_adapter.cfg))) { + if (!IoAdapter->irq_info.irq_nr) { + p = "slave"; + } else { + p = "out of service"; + } + } else if (a->xdi_adapter.trapped) { + p = "trapped"; + } else if (a->xdi_adapter.Initialized) { + p = "active"; + } else { + p = "ready"; + } + len += sprintf(page + len, "State : %s\n", p); + + if (off + count >= len) + *eof = 1; + if (len < off) + return 0; + *start = page + off; + return ((count < len - off) ? count : len - off); +} + +/* +** adapter proc init/de-init +*/ + +/* -------------------------------------------------------------------------- + Create adapter directory and files in proc file system + -------------------------------------------------------------------------- */ +int create_adapter_proc(diva_os_xdi_adapter_t * a) +{ + struct proc_dir_entry *de, *pe; + char tmp[16]; + + sprintf(tmp, "%s%d", adapter_dir_name, a->controller); + if (!(de = create_proc_entry(tmp, S_IFDIR, proc_net_eicon))) + return (0); + a->proc_adapter_dir = (void *) de; + + if (!(pe = + create_proc_entry(info_proc_name, S_IFREG | S_IRUGO | S_IWUSR, de))) + return (0); + a->proc_info = (void *) pe; + pe->write_proc = info_write; + pe->read_proc = info_read; + pe->data = a; + + if ((pe = create_proc_entry(grp_opt_proc_name, + S_IFREG | S_IRUGO | S_IWUSR, de))) { + a->proc_grp_opt = (void *) pe; + pe->write_proc = write_grp_opt; + pe->read_proc = read_grp_opt; + pe->data = a; + } + if ((pe = create_proc_entry(d_l1_down_proc_name, + S_IFREG | S_IRUGO | S_IWUSR, de))) { + a->proc_d_l1_down = (void *) pe; + pe->write_proc = write_d_l1_down; + pe->read_proc = read_d_l1_down; + pe->data = a; + } + + DBG_TRC(("proc entry %s created", tmp)); + + return (1); +} + +/* -------------------------------------------------------------------------- + Remove adapter directory and files in proc file system + -------------------------------------------------------------------------- */ +void remove_adapter_proc(diva_os_xdi_adapter_t * a) +{ + char tmp[16]; + + if (a->proc_adapter_dir) { + if (a->proc_d_l1_down) { + remove_proc_entry(d_l1_down_proc_name, + (struct proc_dir_entry *) a->proc_adapter_dir); + } + if (a->proc_grp_opt) { + remove_proc_entry(grp_opt_proc_name, + (struct proc_dir_entry *) a->proc_adapter_dir); + } + if (a->proc_info) { + remove_proc_entry(info_proc_name, + (struct proc_dir_entry *) a->proc_adapter_dir); + } + sprintf(tmp, "%s%d", adapter_dir_name, a->controller); + remove_proc_entry(tmp, proc_net_eicon); + DBG_TRC(("proc entry %s%d removed", adapter_dir_name, + a->controller)); + } +} diff --git a/drivers/isdn/hardware/eicon/divasync.h b/drivers/isdn/hardware/eicon/divasync.h new file mode 100644 index 000000000000..0a5be7f969f2 --- /dev/null +++ b/drivers/isdn/hardware/eicon/divasync.h @@ -0,0 +1,490 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +#ifndef __DIVA_SYNC__H +#define __DIVA_SYNC__H +#define IDI_SYNC_REQ_REMOVE 0x00 +#define IDI_SYNC_REQ_GET_NAME 0x01 +#define IDI_SYNC_REQ_GET_SERIAL 0x02 +#define IDI_SYNC_REQ_SET_POSTCALL 0x03 +#define IDI_SYNC_REQ_GET_XLOG 0x04 +#define IDI_SYNC_REQ_GET_FEATURES 0x05 +#define IDI_SYNC_REQ_USB_REGISTER 0x06 +#define IDI_SYNC_REQ_USB_RELEASE 0x07 +#define IDI_SYNC_REQ_USB_ADD_DEVICE 0x08 +#define IDI_SYNC_REQ_USB_START_DEVICE 0x09 +#define IDI_SYNC_REQ_USB_STOP_DEVICE 0x0A +#define IDI_SYNC_REQ_USB_REMOVE_DEVICE 0x0B +#define IDI_SYNC_REQ_GET_CARDTYPE 0x0C +#define IDI_SYNC_REQ_GET_DBG_XLOG 0x0D +#define DIVA_USB +#define DIVA_USB_REQ 0xAC +#define DIVA_USB_TEST 0xAB +#define DIVA_USB_ADD_ADAPTER 0xAC +#define DIVA_USB_REMOVE_ADAPTER 0xAD +#define IDI_SYNC_REQ_SERIAL_HOOK 0x80 +#define IDI_SYNC_REQ_XCHANGE_STATUS 0x81 +#define IDI_SYNC_REQ_USB_HOOK 0x82 +#define IDI_SYNC_REQ_PORTDRV_HOOK 0x83 +#define IDI_SYNC_REQ_SLI 0x84 /* SLI request from 3signal modem drivers */ +#define IDI_SYNC_REQ_RECONFIGURE 0x85 +#define IDI_SYNC_REQ_RESET 0x86 +#define IDI_SYNC_REQ_GET_85X_DEVICE_DATA 0x87 +#define IDI_SYNC_REQ_LOCK_85X 0x88 +#define IDI_SYNC_REQ_DIVA_85X_USB_DATA_EXCHANGE 0x99 +#define IDI_SYNC_REQ_DIPORT_EXCHANGE_REQ 0x98 +#define IDI_SYNC_REQ_GET_85X_EXT_PORT_TYPE 0xA0 +/******************************************************************************/ +#define IDI_SYNC_REQ_XDI_GET_EXTENDED_FEATURES 0x92 +/* + To receive XDI features: + 1. set 'buffer_length_in_bytes' to length of you buffer + 2. set 'features' to pointer to your buffer + 3. issue synchronous request to XDI + 4. Check that feature 'DIVA_XDI_EXTENDED_FEATURES_VALID' is present + after call. This feature does indicate that your request + was processed and XDI does support this synchronous request + 5. if on return bit 31 (0x80000000) in 'buffer_length_in_bytes' is + set then provided buffer was too small, and bits 30-0 does + contain necessary length of buffer. + in this case only features that do find place in the buffer + are indicated to caller +*/ +typedef struct _diva_xdi_get_extended_xdi_features { + dword buffer_length_in_bytes; + byte *features; +} diva_xdi_get_extended_xdi_features_t; +/* + features[0] + */ +#define DIVA_XDI_EXTENDED_FEATURES_VALID 0x01 +#define DIVA_XDI_EXTENDED_FEATURE_CMA 0x02 +#define DIVA_XDI_EXTENDED_FEATURE_SDRAM_BAR 0x04 +#define DIVA_XDI_EXTENDED_FEATURE_CAPI_PRMS 0x08 +#define DIVA_XDI_EXTENDED_FEATURE_NO_CANCEL_RC 0x10 +#define DIVA_XDI_EXTENDED_FEATURE_RX_DMA 0x20 +#define DIVA_XDI_EXTENDED_FEATURE_MANAGEMENT_DMA 0x40 +#define DIVA_XDI_EXTENDED_FEATURE_WIDE_ID 0x80 +#define DIVA_XDI_EXTENDED_FEATURES_MAX_SZ 1 +/******************************************************************************/ +#define IDI_SYNC_REQ_XDI_GET_ADAPTER_SDRAM_BAR 0x93 +typedef struct _diva_xdi_get_adapter_sdram_bar { + dword bar; +} diva_xdi_get_adapter_sdram_bar_t; +/******************************************************************************/ +#define IDI_SYNC_REQ_XDI_GET_CAPI_PARAMS 0x94 +/* + CAPI Parameters will be written in the caller's buffer + */ +typedef struct _diva_xdi_get_capi_parameters { + dword structure_length; + byte flag_dynamic_l1_down; + byte group_optimization_enabled; +} diva_xdi_get_capi_parameters_t; +/******************************************************************************/ +#define IDI_SYNC_REQ_XDI_GET_LOGICAL_ADAPTER_NUMBER 0x95 +/* + Get logical adapter number, as assigned by XDI + 'controller' is starting with zero 'sub' controller number + in case of one adapter that supports multiple interfaces + 'controller' is zero for Master adapter (and adapter that supports + only one interface) + */ +typedef struct _diva_xdi_get_logical_adapter_number { + dword logical_adapter_number; + dword controller; + dword total_controllers; +} diva_xdi_get_logical_adapter_number_s_t; +/******************************************************************************/ +#define IDI_SYNC_REQ_UP1DM_OPERATION 0x96 +/******************************************************************************/ +#define IDI_SYNC_REQ_DMA_DESCRIPTOR_OPERATION 0x97 +#define IDI_SYNC_REQ_DMA_DESCRIPTOR_ALLOC 0x01 +#define IDI_SYNC_REQ_DMA_DESCRIPTOR_FREE 0x02 +typedef struct _diva_xdi_dma_descriptor_operation { + int operation; + int descriptor_number; + void* descriptor_address; + dword descriptor_magic; +} diva_xdi_dma_descriptor_operation_t; +/******************************************************************************/ +#define IDI_SYNC_REQ_DIDD_REGISTER_ADAPTER_NOTIFY 0x01 +#define IDI_SYNC_REQ_DIDD_REMOVE_ADAPTER_NOTIFY 0x02 +#define IDI_SYNC_REQ_DIDD_ADD_ADAPTER 0x03 +#define IDI_SYNC_REQ_DIDD_REMOVE_ADAPTER 0x04 +#define IDI_SYNC_REQ_DIDD_READ_ADAPTER_ARRAY 0x05 +#define IDI_SYNC_REQ_DIDD_GET_CFG_LIB_IFC 0x10 +typedef struct _diva_didd_adapter_notify { + dword handle; /* Notification handle */ + void * callback; + void * context; +} diva_didd_adapter_notify_t; +typedef struct _diva_didd_add_adapter { + void * descriptor; +} diva_didd_add_adapter_t; +typedef struct _diva_didd_remove_adapter { + IDI_CALL p_request; +} diva_didd_remove_adapter_t; +typedef struct _diva_didd_read_adapter_array { + void * buffer; + dword length; +} diva_didd_read_adapter_array_t; +typedef struct _diva_didd_get_cfg_lib_ifc { + void* ifc; +} diva_didd_get_cfg_lib_ifc_t; +/******************************************************************************/ +#define IDI_SYNC_REQ_XDI_GET_STREAM 0x91 +#define DIVA_XDI_SYNCHRONOUS_SERVICE 0x01 +#define DIVA_XDI_DMA_SERVICE 0x02 +#define DIVA_XDI_AUTO_SERVICE 0x03 +#define DIVA_ISTREAM_COMPLETE_NOTIFY 0 +#define DIVA_ISTREAM_COMPLETE_READ 1 +#define DIVA_ISTREAM_COMPLETE_WRITE 2 +typedef struct _diva_xdi_stream_interface { + unsigned char Id; /* filled by XDI client */ + unsigned char provided_service; /* filled by XDI */ + unsigned char requested_service; /* filled by XDI Client */ + void* xdi_context; /* filled by XDI */ + void* client_context; /* filled by XDI client */ + int (*write)(void* context, + int Id, + void* data, + int length, + int final, + byte usr1, + byte usr2); + int (*read)(void* context, + int Id, + void* data, + int max_length, + int* final, + byte* usr1, + byte* usr2); + int (*complete)(void* client_context, + int Id, + int what, + void* data, + int length, + int* final); +} diva_xdi_stream_interface_t; +/******************************************************************************/ +/* + * IDI_SYNC_REQ_SERIAL_HOOK - special interface for the DIVA Mobile card + */ +typedef struct +{ unsigned char LineState; /* Modem line state (STATUS_R) */ +#define SERIAL_GSM_CELL 0x01 /* GSM or CELL cable attached */ + unsigned char CardState; /* PCMCIA card state (0 = down) */ + unsigned char IsdnState; /* ISDN layer 1 state (0 = down)*/ + unsigned char HookState; /* current logical hook state */ +#define SERIAL_ON_HOOK 0x02 /* set in DIVA CTRL_R register */ +} SERIAL_STATE; +typedef int ( * SERIAL_INT_CB) (void *Context) ; +typedef int ( * SERIAL_DPC_CB) (void *Context) ; +typedef unsigned char ( * SERIAL_I_SYNC) (void *Context) ; +typedef struct +{ /* 'Req' and 'Rc' must be at the same place as in the ENTITY struct */ + unsigned char Req; /* request (must be always 0) */ + unsigned char Rc; /* return code (is the request) */ + unsigned char Function; /* private function code */ +#define SERIAL_HOOK_ATTACH 0x81 +#define SERIAL_HOOK_STATUS 0x82 +#define SERIAL_HOOK_I_SYNC 0x83 +#define SERIAL_HOOK_NOECHO 0x84 +#define SERIAL_HOOK_RING 0x85 +#define SERIAL_HOOK_DETACH 0x8f + unsigned char Flags; /* function refinements */ + /* parameters passed by the the ATTACH request */ + SERIAL_INT_CB InterruptHandler; /* called on each interrupt */ + SERIAL_DPC_CB DeferredHandler; /* called on hook state changes */ + void *HandlerContext; /* context for both handlers */ + /* return values for both the ATTACH and the STATUS request */ + unsigned long IoBase; /* IO port assigned to UART */ + SERIAL_STATE State; + /* parameters and return values for the I_SYNC function */ + SERIAL_I_SYNC SyncFunction; /* to be called synchronized */ + void *SyncContext; /* context for this function */ + unsigned char SyncResult; /* return value of function */ +} SERIAL_HOOK; +/* + * IDI_SYNC_REQ_XCHANGE_STATUS - exchange the status between IDI and WMP + * IDI_SYNC_REQ_RECONFIGURE - reconfiguration of IDI from WMP + */ +typedef struct +{ /* 'Req' and 'Rc' must be at the same place as in the ENTITY struct */ + unsigned char Req; /* request (must be always 0) */ + unsigned char Rc; /* return code (is the request) */ +#define DRIVER_STATUS_BOOT 0xA1 +#define DRIVER_STATUS_INIT_DEV 0xA2 +#define DRIVER_STATUS_RUNNING 0xA3 +#define DRIVER_STATUS_SHUTDOWN 0xAF +#define DRIVER_STATUS_TRAPPED 0xAE + unsigned char wmpStatus; /* exported by WMP */ + unsigned char idiStatus; /* exported by IDI */ + unsigned long wizProto ; /* from WMP registry to IDI */ + /* the cardtype value is defined by cardtype.h */ + unsigned long cardType ; /* from IDI registry to WMP */ + unsigned long nt2 ; /* from IDI registry to WMP */ + unsigned long permanent ; /* from IDI registry to WMP */ + unsigned long stableL2 ; /* from IDI registry to WMP */ + unsigned long tei ; /* from IDI registry to WMP */ +#define CRC4_MASK 0x00000003 +#define L1_TRISTATE_MASK 0x00000004 +#define WATCHDOG_MASK 0x00000008 +#define NO_ORDER_CHECK_MASK 0x00000010 +#define LOW_CHANNEL_MASK 0x00000020 +#define NO_HSCX30_MASK 0x00000040 +#define MODE_MASK 0x00000080 +#define SET_BOARD 0x00001000 +#define SET_CRC4 0x00030000 +#define SET_L1_TRISTATE 0x00040000 +#define SET_WATCHDOG 0x00080000 +#define SET_NO_ORDER_CHECK 0x00100000 +#define SET_LOW_CHANNEL 0x00200000 +#define SET_NO_HSCX30 0x00400000 +#define SET_MODE 0x00800000 +#define SET_PROTO 0x02000000 +#define SET_CARDTYPE 0x04000000 +#define SET_NT2 0x08000000 +#define SET_PERMANENT 0x10000000 +#define SET_STABLEL2 0x20000000 +#define SET_TEI 0x40000000 +#define SET_NUMBERLEN 0x80000000 + unsigned long Flag ; /* |31-Type-16|15-Mask-0| */ + unsigned long NumberLen ; /* reconfiguration: union is empty */ + union { + struct { /* possible reconfiguration, but ... ; SET_BOARD */ + unsigned long SerialNumber ; + char *pCardname ; /* di_defs.h: BOARD_NAME_LENGTH */ + } board ; + struct { /* reset: need resources */ + void * pRawResources ; + void * pXlatResources ; + } res ; + struct { /* reconfiguration: wizProto == PROTTYPE_RBSCAS */ +#define GLARE_RESOLVE_MASK 0x00000001 +#define DID_MASK 0x00000002 +#define BEARER_CAP_MASK 0x0000000c +#define SET_GLARE_RESOLVE 0x00010000 +#define SET_DID 0x00020000 +#define SET_BEARER_CAP 0x000c0000 + unsigned long Flag ; /* |31-Type-16|15-VALUE-0| */ + unsigned short DigitTimeout ; + unsigned short AnswerDelay ; + } rbs ; + struct { /* reconfiguration: wizProto == PROTTYPE_QSIG */ +#define CALL_REF_LENGTH1_MASK 0x00000001 +#define BRI_CHANNEL_ID_MASK 0x00000002 +#define SET_CALL_REF_LENGTH 0x00010000 +#define SET_BRI_CHANNEL_ID 0x00020000 + unsigned long Flag ; /* |31-Type-16|15-VALUE-0| */ + } qsig ; + struct { /* reconfiguration: NumberLen != 0 */ +#define SET_SPID1 0x00010000 +#define SET_NUMBER1 0x00020000 +#define SET_SUBADDRESS1 0x00040000 +#define SET_SPID2 0x00100000 +#define SET_NUMBER2 0x00200000 +#define SET_SUBADDRESS2 0x00400000 +#define MASK_SET 0xffff0000 + unsigned long Flag ; /* |31-Type-16|15-Channel-0| */ + unsigned char *pBuffer ; /* number value */ + } isdnNo ; + } +parms +; +} isdnProps ; +/* + * IDI_SYNC_REQ_PORTDRV_HOOK - signal plug/unplug (Award Cardware only) + */ +typedef void ( * PORTDRV_HOOK_CB) (void *Context, int Plug) ; +typedef struct +{ /* 'Req' and 'Rc' must be at the same place as in the ENTITY struct */ + unsigned char Req; /* request (must be always 0) */ + unsigned char Rc; /* return code (is the request) */ + unsigned char Function; /* private function code */ + unsigned char Flags; /* function refinements */ + PORTDRV_HOOK_CB Callback; /* to be called on plug/unplug */ + void *Context; /* context for callback */ + unsigned long Info; /* more info if needed */ +} PORTDRV_HOOK ; +/* Codes for the 'Rc' element in structure below. */ +#define SLI_INSTALL (0xA1) +#define SLI_UNINSTALL (0xA2) +typedef int ( * SLIENTRYPOINT)(void* p3SignalAPI, void* pContext); +typedef struct +{ /* 'Req' and 'Rc' must be at the same place as in the ENTITY struct */ + unsigned char Req; /* request (must be always 0) */ + unsigned char Rc; /* return code (is the request) */ + unsigned char Function; /* private function code */ + unsigned char Flags; /* function refinements */ + SLIENTRYPOINT Callback; /* to be called on plug/unplug */ + void *Context; /* context for callback */ + unsigned long Info; /* more info if needed */ +} SLIENTRYPOINT_REQ ; +/******************************************************************************/ +/* + * Definitions for DIVA USB + */ +typedef int ( * USB_SEND_REQ) (unsigned char PipeIndex, unsigned char Type,void *Data, int sizeData); +typedef int ( * USB_START_DEV) (void *Adapter, void *Ipac) ; +/* called from WDM */ +typedef void ( * USB_RECV_NOTIFY) (void *Ipac, void *msg) ; +typedef void ( * USB_XMIT_NOTIFY) (void *Ipac, unsigned char PipeIndex) ; +/******************************************************************************/ +/* + * Parameter description for synchronous requests. + * + * Sorry, must repeat some parts of di_defs.h here because + * they are not defined for all operating environments + */ +typedef union +{ ENTITY Entity; + struct + { /* 'Req' and 'Rc' are at the same place as in the ENTITY struct */ + unsigned char Req; /* request (must be always 0) */ + unsigned char Rc; /* return code (is the request) */ + } Request; + struct + { unsigned char Req; /* request (must be always 0) */ + unsigned char Rc; /* return code (0x01) */ + unsigned char name[BOARD_NAME_LENGTH]; + } GetName; + struct + { unsigned char Req; /* request (must be always 0) */ + unsigned char Rc; /* return code (0x02) */ + unsigned long serial; /* serial number */ + } GetSerial; + struct + { unsigned char Req; /* request (must be always 0) */ + unsigned char Rc; /* return code (0x02) */ + unsigned long lineIdx;/* line, 0 if card has only one */ + } GetLineIdx; + struct + { unsigned char Req; /* request (must be always 0) */ + unsigned char Rc; /* return code (0x02) */ + unsigned long cardtype;/* card type */ + } GetCardType; + struct + { unsigned short command;/* command = 0x0300 */ + unsigned short dummy; /* not used */ + IDI_CALL callback;/* routine to call back */ + ENTITY *contxt; /* ptr to entity to use */ + } PostCall; + struct + { unsigned char Req; /* request (must be always 0) */ + unsigned char Rc; /* return code (0x04) */ + unsigned char pcm[1]; /* buffer (a pc_maint struct) */ + } GetXlog; + struct + { unsigned char Req; /* request (must be always 0) */ + unsigned char Rc; /* return code (0x05) */ + unsigned short features;/* feature defines see below */ + } GetFeatures; + SERIAL_HOOK SerialHook; +/* Added for DIVA USB */ + struct + { unsigned char Req; + unsigned char Rc; + USB_SEND_REQ UsbSendRequest; /* function in Diva Usb WDM driver in usb_os.c, */ + /* called from usb_drv.c to send a message to our device */ + /* eg UsbSendRequest (USB_PIPE_SIGNAL, USB_IPAC_START, 0, 0) ; */ + USB_RECV_NOTIFY usb_recv; /* called from usb_os.c to pass a received message and ptr to IPAC */ + /* on to usb_drv.c by a call to usb_recv(). */ + USB_XMIT_NOTIFY usb_xmit; /* called from usb_os.c in DivaUSB.sys WDM to indicate a completed transmit */ + /* to usb_drv.c by a call to usb_xmit(). */ + USB_START_DEV UsbStartDevice; /* Start the USB Device, in usb_os.c */ + IDI_CALL callback; /* routine to call back */ + ENTITY *contxt; /* ptr to entity to use */ + void ** ipac_ptr; /* pointer to struct IPAC in VxD */ + } Usb_Msg_old; +/* message used by WDM and VXD to pass pointers of function and IPAC* */ + struct + { unsigned char Req; + unsigned char Rc; + USB_SEND_REQ pUsbSendRequest;/* function in Diva Usb WDM driver in usb_os.c, */ + /* called from usb_drv.c to send a message to our device */ + /* eg UsbSendRequest (USB_PIPE_SIGNAL, USB_IPAC_START, 0, 0) ; */ + USB_RECV_NOTIFY p_usb_recv; /* called from usb_os.c to pass a received message and ptr to IPAC */ + /* on to usb_drv.c by a call to usb_recv(). */ + USB_XMIT_NOTIFY p_usb_xmit; /* called from usb_os.c in DivaUSB.sys WDM to indicate a completed transmit */ + /* to usb_drv.c by a call to usb_xmit().*/ + void *ipac_ptr; /* &Diva.ipac pointer to struct IPAC in VxD */ + } Usb_Msg; + PORTDRV_HOOK PortdrvHook; + SLIENTRYPOINT_REQ sliEntryPointReq; + struct { + unsigned char Req; + unsigned char Rc; + diva_xdi_stream_interface_t info; + } xdi_stream_info; + struct { + unsigned char Req; + unsigned char Rc; + diva_xdi_get_extended_xdi_features_t info; + } xdi_extended_features; + struct { + unsigned char Req; + unsigned char Rc; + diva_xdi_get_adapter_sdram_bar_t info; + } xdi_sdram_bar; + struct { + unsigned char Req; + unsigned char Rc; + diva_xdi_get_capi_parameters_t info; + } xdi_capi_prms; + struct { + ENTITY e; + diva_didd_adapter_notify_t info; + } didd_notify; + struct { + ENTITY e; + diva_didd_add_adapter_t info; + } didd_add_adapter; + struct { + ENTITY e; + diva_didd_remove_adapter_t info; + } didd_remove_adapter; + struct { + ENTITY e; + diva_didd_read_adapter_array_t info; + } didd_read_adapter_array; + struct { + ENTITY e; + diva_didd_get_cfg_lib_ifc_t info; + } didd_get_cfg_lib_ifc; + struct { + unsigned char Req; + unsigned char Rc; + diva_xdi_get_logical_adapter_number_s_t info; + } xdi_logical_adapter_number; + struct { + unsigned char Req; + unsigned char Rc; + diva_xdi_dma_descriptor_operation_t info; + } xdi_dma_descriptor_operation; +} IDI_SYNC_REQ; +/******************************************************************************/ +#endif /* __DIVA_SYNC__H */ diff --git a/drivers/isdn/hardware/eicon/dqueue.c b/drivers/isdn/hardware/eicon/dqueue.c new file mode 100644 index 000000000000..982258225174 --- /dev/null +++ b/drivers/isdn/hardware/eicon/dqueue.c @@ -0,0 +1,110 @@ +/* $Id: dqueue.c,v 1.5 2003/04/12 21:40:49 schindler Exp $ + * + * Driver for Eicon DIVA Server ISDN cards. + * User Mode IDI Interface + * + * Copyright 2000-2003 by Armin Schindler (mac@melware.de) + * Copyright 2000-2003 Cytronics & Melware (info@melware.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + */ + +#include "platform.h" +#include "dqueue.h" + +int +diva_data_q_init(diva_um_idi_data_queue_t * q, + int max_length, int max_segments) +{ + int i; + + q->max_length = max_length; + q->segments = max_segments; + + for (i = 0; i < q->segments; i++) { + q->data[i] = NULL; + q->length[i] = 0; + } + q->read = q->write = q->count = q->segment_pending = 0; + + for (i = 0; i < q->segments; i++) { + if (!(q->data[i] = diva_os_malloc(0, q->max_length))) { + diva_data_q_finit(q); + return (-1); + } + } + + return (0); +} + +int diva_data_q_finit(diva_um_idi_data_queue_t * q) +{ + int i; + + for (i = 0; i < q->segments; i++) { + if (q->data[i]) { + diva_os_free(0, q->data[i]); + } + q->data[i] = NULL; + q->length[i] = 0; + } + q->read = q->write = q->count = q->segment_pending = 0; + + return (0); +} + +int diva_data_q_get_max_length(const diva_um_idi_data_queue_t * q) +{ + return (q->max_length); +} + +void *diva_data_q_get_segment4write(diva_um_idi_data_queue_t * q) +{ + if ((!q->segment_pending) && (q->count < q->segments)) { + q->segment_pending = 1; + return (q->data[q->write]); + } + + return NULL; +} + +void +diva_data_q_ack_segment4write(diva_um_idi_data_queue_t * q, int length) +{ + if (q->segment_pending) { + q->length[q->write] = length; + q->count++; + q->write++; + if (q->write >= q->segments) { + q->write = 0; + } + q->segment_pending = 0; + } +} + +const void *diva_data_q_get_segment4read(const diva_um_idi_data_queue_t * + q) +{ + if (q->count) { + return (q->data[q->read]); + } + return NULL; +} + +int diva_data_q_get_segment_length(const diva_um_idi_data_queue_t * q) +{ + return (q->length[q->read]); +} + +void diva_data_q_ack_segment4read(diva_um_idi_data_queue_t * q) +{ + if (q->count) { + q->length[q->read] = 0; + q->count--; + q->read++; + if (q->read >= q->segments) { + q->read = 0; + } + } +} diff --git a/drivers/isdn/hardware/eicon/dqueue.h b/drivers/isdn/hardware/eicon/dqueue.h new file mode 100644 index 000000000000..72d21c967227 --- /dev/null +++ b/drivers/isdn/hardware/eicon/dqueue.h @@ -0,0 +1,31 @@ +/* $Id: dqueue.h,v 1.1.2.2 2001/02/08 12:25:43 armin Exp $ */ + +#ifndef _DIVA_USER_MODE_IDI_DATA_QUEUE_H__ +#define _DIVA_USER_MODE_IDI_DATA_QUEUE_H__ + +#define DIVA_UM_IDI_MAX_MSGS 64 + +typedef struct _diva_um_idi_data_queue { + int segments; + int max_length; + int read; + int write; + int count; + int segment_pending; + void *data[DIVA_UM_IDI_MAX_MSGS]; + int length[DIVA_UM_IDI_MAX_MSGS]; +} diva_um_idi_data_queue_t; + +int diva_data_q_init(diva_um_idi_data_queue_t * q, + int max_length, int max_segments); +int diva_data_q_finit(diva_um_idi_data_queue_t * q); +int diva_data_q_get_max_length(const diva_um_idi_data_queue_t * q); +void *diva_data_q_get_segment4write(diva_um_idi_data_queue_t * q); +void diva_data_q_ack_segment4write(diva_um_idi_data_queue_t * q, + int length); +const void *diva_data_q_get_segment4read(const diva_um_idi_data_queue_t * + q); +int diva_data_q_get_segment_length(const diva_um_idi_data_queue_t * q); +void diva_data_q_ack_segment4read(diva_um_idi_data_queue_t * q); + +#endif diff --git a/drivers/isdn/hardware/eicon/dsp_defs.h b/drivers/isdn/hardware/eicon/dsp_defs.h new file mode 100644 index 000000000000..b44950e06f32 --- /dev/null +++ b/drivers/isdn/hardware/eicon/dsp_defs.h @@ -0,0 +1,304 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +#ifndef DSP_DEFS_H_ +#define DSP_DEFS_H_ +#include "dspdids.h" +/*---------------------------------------------------------------------------*/ +#define dsp_download_reserve_space(fp,length) +/*****************************************************************************/ +/* + * OS file access abstraction layer + * + * I/O functions returns -1 on error, 0 on EOF + */ +#define OS_SEEK_SET 0 +#define OS_SEEK_CUR 1 +#define OS_SEEK_END 2 +struct _OsFileHandle_; +typedef long ( * OsFileIo) (struct _OsFileHandle_ *handle, + void *buffer, + long size) ; +typedef long ( * OsFileSeek)(struct _OsFileHandle_ *handle, + long position, + int mode) ; +typedef long ( * OsCardLoad)(struct _OsFileHandle_ *handle, + long length, + void * *addr) ; +typedef struct _OsFileHandle_ +{ void *sysFileDesc ; + unsigned long sysFileSize ; + OsFileIo sysFileRead ; + OsFileSeek sysFileSeek ; + void *sysLoadDesc ; + OsCardLoad sysCardLoad ; +} OsFileHandle ; +extern OsFileHandle *OsOpenFile (char *path_name) ; +extern void OsCloseFile (OsFileHandle *fp) ; +/*****************************************************************************/ +#define DSP_TELINDUS_FILE "dspdload.bin" +/* special DSP file for BRI cards for Qsig and CornetN because of missing memory */ +#define DSP_QSIG_TELINDUS_FILE "dspdqsig.bin" +#define DSP_MDM_TELINDUS_FILE "dspdvmdm.bin" +#define DSP_FAX_TELINDUS_FILE "dspdvfax.bin" +#define DSP_DIRECTORY_ENTRIES 64 +#define DSP_MEMORY_TYPE_EXTERNAL_DM 0 +#define DSP_MEMORY_TYPE_EXTERNAL_PM 1 +#define DSP_MEMORY_TYPE_INTERNAL_DM 2 +#define DSP_MEMORY_TYPE_INTERNAL_PM 3 +#define DSP_DOWNLOAD_FLAG_BOOTABLE 0x0001 +#define DSP_DOWNLOAD_FLAG_2181 0x0002 +#define DSP_DOWNLOAD_FLAG_TIMECRITICAL 0x0004 +#define DSP_DOWNLOAD_FLAG_COMPAND 0x0008 +#define DSP_MEMORY_BLOCK_COUNT 16 +#define DSP_SEGMENT_PM_FLAG 0x0001 +#define DSP_SEGMENT_SHARED_FLAG 0x0002 +#define DSP_SEGMENT_EXTERNAL_DM DSP_MEMORY_TYPE_EXTERNAL_DM +#define DSP_SEGMENT_EXTERNAL_PM DSP_MEMORY_TYPE_EXTERNAL_PM +#define DSP_SEGMENT_INTERNAL_DM DSP_MEMORY_TYPE_INTERNAL_DM +#define DSP_SEGMENT_INTERNAL_PM DSP_MEMORY_TYPE_INTERNAL_PM +#define DSP_SEGMENT_FIRST_RELOCATABLE 4 +#define DSP_DATA_BLOCK_PM_FLAG 0x0001 +#define DSP_DATA_BLOCK_DWORD_FLAG 0x0002 +#define DSP_DATA_BLOCK_RESOLVE_FLAG 0x0004 +#define DSP_RELOC_NONE 0x00 +#define DSP_RELOC_SEGMENT_MASK 0x3f +#define DSP_RELOC_TYPE_MASK 0xc0 +#define DSP_RELOC_TYPE_0 0x00 /* relocation of address in DM word / high part of PM word */ +#define DSP_RELOC_TYPE_1 0x40 /* relocation of address in low part of PM data word */ +#define DSP_RELOC_TYPE_2 0x80 /* relocation of address in standard command */ +#define DSP_RELOC_TYPE_3 0xc0 /* relocation of address in call/jump on flag in */ +#define DSP_COMBIFILE_FORMAT_IDENTIFICATION_SIZE 48 +#define DSP_COMBIFILE_FORMAT_VERSION_BCD 0x0100 +#define DSP_FILE_FORMAT_IDENTIFICATION_SIZE 48 +#define DSP_FILE_FORMAT_VERSION_BCD 0x0100 +typedef struct tag_dsp_combifile_header +{ + char format_identification[DSP_COMBIFILE_FORMAT_IDENTIFICATION_SIZE]; + word format_version_bcd; + word header_size; + word combifile_description_size; + word directory_entries; + word directory_size; + word download_count; + word usage_mask_size; +} t_dsp_combifile_header; +typedef struct tag_dsp_combifile_directory_entry +{ + word card_type_number; + word file_set_number; +} t_dsp_combifile_directory_entry; +typedef struct tag_dsp_file_header +{ + char format_identification[DSP_FILE_FORMAT_IDENTIFICATION_SIZE]; + word format_version_bcd; + word download_id; + word download_flags; + word required_processing_power; + word interface_channel_count; + word header_size; + word download_description_size; + word memory_block_table_size; + word memory_block_count; + word segment_table_size; + word segment_count; + word symbol_table_size; + word symbol_count; + word total_data_size_dm; + word data_block_count_dm; + word total_data_size_pm; + word data_block_count_pm; +} t_dsp_file_header; +typedef struct tag_dsp_memory_block_desc +{ + word alias_memory_block; + word memory_type; + word address; + word size; /* DSP words */ +} t_dsp_memory_block_desc; +typedef struct tag_dsp_segment_desc +{ + word memory_block; + word attributes; + word base; + word size; + word alignment; /* ==0 -> no other legal start address than base */ +} t_dsp_segment_desc; +typedef struct tag_dsp_symbol_desc +{ + word symbol_id; + word segment; + word offset; + word size; /* DSP words */ +} t_dsp_symbol_desc; +typedef struct tag_dsp_data_block_header +{ + word attributes; + word segment; + word offset; + word size; /* DSP words */ +} t_dsp_data_block_header; +typedef struct tag_dsp_download_desc +{ + word download_id; + word download_flags; + word required_processing_power; + word interface_channel_count; + word excess_header_size; + word memory_block_count; + word segment_count; + word symbol_count; + word data_block_count_dm; + word data_block_count_pm; + byte * p_excess_header_data; + char * p_download_description; + t_dsp_memory_block_desc *p_memory_block_table; + t_dsp_segment_desc *p_segment_table; + t_dsp_symbol_desc *p_symbol_table; + word * p_data_blocks_dm; + word * p_data_blocks_pm; +} t_dsp_desc; +typedef struct tag_dsp_portable_download_desc /* be sure to keep native alignment for MAESTRA's */ +{ + word download_id; + word download_flags; + word required_processing_power; + word interface_channel_count; + word excess_header_size; + word memory_block_count; + word segment_count; + word symbol_count; + word data_block_count_dm; + word data_block_count_pm; + dword p_excess_header_data; + dword p_download_description; + dword p_memory_block_table; + dword p_segment_table; + dword p_symbol_table; + dword p_data_blocks_dm; + dword p_data_blocks_pm; +} t_dsp_portable_desc; +#define DSP_DOWNLOAD_INDEX_KERNEL 0 +#define DSP30TX_DOWNLOAD_INDEX_KERNEL 1 +#define DSP30RX_DOWNLOAD_INDEX_KERNEL 2 +#define DSP_MAX_DOWNLOAD_COUNT 64 +#define DSP_DOWNLOAD_MAX_SEGMENTS 16 +#define DSP_UDATA_REQUEST_RECONFIGURE 0 +/* +parameters: + <word> reconfigure delay (in 8kHz samples) + <word> reconfigure code + <byte> reconfigure hdlc preamble flags +*/ +#define DSP_RECONFIGURE_TX_FLAG 0x8000 +#define DSP_RECONFIGURE_SHORT_TRAIN_FLAG 0x4000 +#define DSP_RECONFIGURE_ECHO_PROTECT_FLAG 0x2000 +#define DSP_RECONFIGURE_HDLC_FLAG 0x1000 +#define DSP_RECONFIGURE_SYNC_FLAG 0x0800 +#define DSP_RECONFIGURE_PROTOCOL_MASK 0x00ff +#define DSP_RECONFIGURE_IDLE 0 +#define DSP_RECONFIGURE_V25 1 +#define DSP_RECONFIGURE_V21_CH2 2 +#define DSP_RECONFIGURE_V27_2400 3 +#define DSP_RECONFIGURE_V27_4800 4 +#define DSP_RECONFIGURE_V29_7200 5 +#define DSP_RECONFIGURE_V29_9600 6 +#define DSP_RECONFIGURE_V33_12000 7 +#define DSP_RECONFIGURE_V33_14400 8 +#define DSP_RECONFIGURE_V17_7200 9 +#define DSP_RECONFIGURE_V17_9600 10 +#define DSP_RECONFIGURE_V17_12000 11 +#define DSP_RECONFIGURE_V17_14400 12 +/* +data indications if transparent framer + <byte> data 0 + <byte> data 1 + ... +data indications if HDLC framer + <byte> data 0 + <byte> data 1 + ... + <byte> CRC 0 + <byte> CRC 1 + <byte> preamble flags +*/ +#define DSP_UDATA_INDICATION_SYNC 0 +/* +returns: + <word> time of sync (sampled from counter at 8kHz) +*/ +#define DSP_UDATA_INDICATION_DCD_OFF 1 +/* +returns: + <word> time of DCD off (sampled from counter at 8kHz) +*/ +#define DSP_UDATA_INDICATION_DCD_ON 2 +/* +returns: + <word> time of DCD on (sampled from counter at 8kHz) + <byte> connected norm + <word> connected options + <dword> connected speed (bit/s) +*/ +#define DSP_UDATA_INDICATION_CTS_OFF 3 +/* +returns: + <word> time of CTS off (sampled from counter at 8kHz) +*/ +#define DSP_UDATA_INDICATION_CTS_ON 4 +/* +returns: + <word> time of CTS on (sampled from counter at 8kHz) + <byte> connected norm + <word> connected options + <dword> connected speed (bit/s) +*/ +#define DSP_CONNECTED_NORM_UNSPECIFIED 0 +#define DSP_CONNECTED_NORM_V21 1 +#define DSP_CONNECTED_NORM_V23 2 +#define DSP_CONNECTED_NORM_V22 3 +#define DSP_CONNECTED_NORM_V22_BIS 4 +#define DSP_CONNECTED_NORM_V32_BIS 5 +#define DSP_CONNECTED_NORM_V34 6 +#define DSP_CONNECTED_NORM_V8 7 +#define DSP_CONNECTED_NORM_BELL_212A 8 +#define DSP_CONNECTED_NORM_BELL_103 9 +#define DSP_CONNECTED_NORM_V29_LEASED_LINE 10 +#define DSP_CONNECTED_NORM_V33_LEASED_LINE 11 +#define DSP_CONNECTED_NORM_TFAST 12 +#define DSP_CONNECTED_NORM_V21_CH2 13 +#define DSP_CONNECTED_NORM_V27_TER 14 +#define DSP_CONNECTED_NORM_V29 15 +#define DSP_CONNECTED_NORM_V33 16 +#define DSP_CONNECTED_NORM_V17 17 +#define DSP_CONNECTED_OPTION_TRELLIS 0x0001 +/*---------------------------------------------------------------------------*/ +extern char *dsp_read_file (OsFileHandle *fp, + word card_type_number, + word *p_dsp_download_count, + t_dsp_desc *p_dsp_download_table, + t_dsp_portable_desc *p_dsp_portable_download_table) ; +/*---------------------------------------------------------------------------*/ +#endif /* DSP_DEFS_H_ */ diff --git a/drivers/isdn/hardware/eicon/dsp_tst.h b/drivers/isdn/hardware/eicon/dsp_tst.h new file mode 100644 index 000000000000..a6021e5b1ae7 --- /dev/null +++ b/drivers/isdn/hardware/eicon/dsp_tst.h @@ -0,0 +1,47 @@ +/* $Id: dsp_tst.h,v 1.1.2.2 2001/02/08 12:25:43 armin Exp $ */ + +#ifndef __DIVA_PRI_HOST_TEST_DSPS_H__ +#define __DIVA_PRI_HOST_TEST_DSPS_H__ + +/* + DSP registers on maestra pri + */ +#define DSP1_PORT (0x00) +#define DSP2_PORT (0x8) +#define DSP3_PORT (0x800) +#define DSP4_PORT (0x808) +#define DSP5_PORT (0x810) +#define DSP6_PORT (0x818) +#define DSP7_PORT (0x820) +#define DSP8_PORT (0x828) +#define DSP9_PORT (0x830) +#define DSP10_PORT (0x840) +#define DSP11_PORT (0x848) +#define DSP12_PORT (0x850) +#define DSP13_PORT (0x858) +#define DSP14_PORT (0x860) +#define DSP15_PORT (0x868) +#define DSP16_PORT (0x870) +#define DSP17_PORT (0x1000) +#define DSP18_PORT (0x1008) +#define DSP19_PORT (0x1010) +#define DSP20_PORT (0x1018) +#define DSP21_PORT (0x1020) +#define DSP22_PORT (0x1028) +#define DSP23_PORT (0x1030) +#define DSP24_PORT (0x1040) +#define DSP25_PORT (0x1048) +#define DSP26_PORT (0x1050) +#define DSP27_PORT (0x1058) +#define DSP28_PORT (0x1060) +#define DSP29_PORT (0x1068) +#define DSP30_PORT (0x1070) +#define DSP_ADR_OFFS 0x80 + +/*------------------------------------------------------------------ + Dsp related definitions + ------------------------------------------------------------------ */ +#define DSP_SIGNATURE_PROBE_WORD 0x5a5a +#define dsp_make_address_ex(pm,address) ((word)((pm) ? (address) : (address) + 0x4000)) + +#endif diff --git a/drivers/isdn/hardware/eicon/dspdids.h b/drivers/isdn/hardware/eicon/dspdids.h new file mode 100644 index 000000000000..ebe131a53b9c --- /dev/null +++ b/drivers/isdn/hardware/eicon/dspdids.h @@ -0,0 +1,75 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +#ifndef DSPDIDS_H_ +#define DSPDIDS_H_ +/*---------------------------------------------------------------------------*/ +#define DSP_DID_INVALID 0 +#define DSP_DID_DIVA 1 +#define DSP_DID_DIVA_PRO 2 +#define DSP_DID_DIVA_PRO_20 3 +#define DSP_DID_DIVA_PRO_PCCARD 4 +#define DSP_DID_DIVA_SERVER_BRI_1M 5 +#define DSP_DID_DIVA_SERVER_BRI_2M 6 +#define DSP_DID_DIVA_SERVER_PRI_2M_TX 7 +#define DSP_DID_DIVA_SERVER_PRI_2M_RX 8 +#define DSP_DID_DIVA_SERVER_PRI_30M 9 +#define DSP_DID_TASK_HSCX 100 +#define DSP_DID_TASK_HSCX_PRI_2M_TX 101 +#define DSP_DID_TASK_HSCX_PRI_2M_RX 102 +#define DSP_DID_TASK_V110KRNL 200 +#define DSP_DID_OVERLAY_V1100 201 +#define DSP_DID_OVERLAY_V1101 202 +#define DSP_DID_OVERLAY_V1102 203 +#define DSP_DID_OVERLAY_V1103 204 +#define DSP_DID_OVERLAY_V1104 205 +#define DSP_DID_OVERLAY_V1105 206 +#define DSP_DID_OVERLAY_V1106 207 +#define DSP_DID_OVERLAY_V1107 208 +#define DSP_DID_OVERLAY_V1108 209 +#define DSP_DID_OVERLAY_V1109 210 +#define DSP_DID_TASK_V110_PRI_2M_TX 220 +#define DSP_DID_TASK_V110_PRI_2M_RX 221 +#define DSP_DID_TASK_MODEM 300 +#define DSP_DID_TASK_FAX05 400 +#define DSP_DID_TASK_VOICE 500 +#define DSP_DID_TASK_TIKRNL81 600 +#define DSP_DID_OVERLAY_DIAL 601 +#define DSP_DID_OVERLAY_V22 602 +#define DSP_DID_OVERLAY_V32 603 +#define DSP_DID_OVERLAY_FSK 604 +#define DSP_DID_OVERLAY_FAX 605 +#define DSP_DID_OVERLAY_VXX 606 +#define DSP_DID_OVERLAY_V8 607 +#define DSP_DID_OVERLAY_INFO 608 +#define DSP_DID_OVERLAY_V34 609 +#define DSP_DID_OVERLAY_DFX 610 +#define DSP_DID_PARTIAL_OVERLAY_DIAL 611 +#define DSP_DID_PARTIAL_OVERLAY_FSK 612 +#define DSP_DID_PARTIAL_OVERLAY_FAX 613 +#define DSP_DID_TASK_TIKRNL05 700 +/*---------------------------------------------------------------------------*/ +#endif +/*---------------------------------------------------------------------------*/ diff --git a/drivers/isdn/hardware/eicon/dsrv4bri.h b/drivers/isdn/hardware/eicon/dsrv4bri.h new file mode 100644 index 000000000000..732d22dfe4a5 --- /dev/null +++ b/drivers/isdn/hardware/eicon/dsrv4bri.h @@ -0,0 +1,40 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +#ifndef __DIVA_XDI_DSRV_4_BRI_INC__ +#define __DIVA_XDI_DSRV_4_BRI_INC__ +/* + * Some special registers in the PLX 9054 + */ +#define PLX9054_P2LDBELL 0x60 +#define PLX9054_L2PDBELL 0x64 +#define PLX9054_INTCSR 0x69 +#define PLX9054_INT_ENABLE 0x09 +#define PLX9054_SOFT_RESET 0x4000 +#define PLX9054_RELOAD_EEPROM 0x2000 +#define DIVA_4BRI_REVISION(__x__) (((__x__)->cardType == CARDTYPE_DIVASRV_Q_8M_V2_PCI) || ((__x__)->cardType == CARDTYPE_DIVASRV_VOICE_Q_8M_V2_PCI) || ((__x__)->cardType == CARDTYPE_DIVASRV_B_2M_V2_PCI) || ((__x__)->cardType == CARDTYPE_DIVASRV_B_2F_PCI) || ((__x__)->cardType == CARDTYPE_DIVASRV_VOICE_B_2M_V2_PCI)) +void diva_os_set_qBri_functions (PISDN_ADAPTER IoAdapter); +void diva_os_set_qBri2_functions (PISDN_ADAPTER IoAdapter); +#endif diff --git a/drivers/isdn/hardware/eicon/dsrv_bri.h b/drivers/isdn/hardware/eicon/dsrv_bri.h new file mode 100644 index 000000000000..f38ebbe53332 --- /dev/null +++ b/drivers/isdn/hardware/eicon/dsrv_bri.h @@ -0,0 +1,37 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +#ifndef __DIVA_XDI_DSRV_BRI_INC__ +#define __DIVA_XDI_DSRV_BRI_INC__ +/* + Functions exported from os dependent part of + BRI card configuration and used in + OS independed part + */ +/* + Prepare OS dependent part of BRI functions + */ +void diva_os_prepare_maestra_functions (PISDN_ADAPTER IoAdapter); +#endif diff --git a/drivers/isdn/hardware/eicon/dsrv_pri.h b/drivers/isdn/hardware/eicon/dsrv_pri.h new file mode 100644 index 000000000000..861182666c89 --- /dev/null +++ b/drivers/isdn/hardware/eicon/dsrv_pri.h @@ -0,0 +1,38 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +#ifndef __DIVA_XDI_DSRV_PRI_INC__ +#define __DIVA_XDI_DSRV_PRI_INC__ +/* + Functions exported from os dependent part of + PRI card configuration and used in + OS independed part + */ +/* + Prepare OS dependent part of PRI/PRI Rev.2 functions + */ +void diva_os_prepare_pri_functions (PISDN_ADAPTER IoAdapter); +void diva_os_prepare_pri2_functions (PISDN_ADAPTER IoAdapter); +#endif diff --git a/drivers/isdn/hardware/eicon/entity.h b/drivers/isdn/hardware/eicon/entity.h new file mode 100644 index 000000000000..16252cf164bb --- /dev/null +++ b/drivers/isdn/hardware/eicon/entity.h @@ -0,0 +1,28 @@ +/* $Id: entity.h,v 1.4 2004/03/21 17:26:01 armin Exp $ */ + +#ifndef __DIVAS_USER_MODE_IDI_ENTITY__ +#define __DIVAS_USER_MODE_IDI_ENTITY__ + +#define DIVA_UM_IDI_RC_PENDING 0x00000001 +#define DIVA_UM_IDI_REMOVE_PENDING 0x00000002 +#define DIVA_UM_IDI_TX_FLOW_CONTROL 0x00000004 +#define DIVA_UM_IDI_REMOVED 0x00000008 +#define DIVA_UM_IDI_ASSIGN_PENDING 0x00000010 + +typedef struct _divas_um_idi_entity { + struct list_head link; + diva_um_idi_adapter_t* adapter; /* Back to adapter */ + ENTITY e; + void* os_ref; + dword status; + void* os_context; + int rc_count; + diva_um_idi_data_queue_t data; /* definad by user 1 ... MAX */ + diva_um_idi_data_queue_t rc; /* two entries */ + BUFFERS XData; + BUFFERS RData; + byte buffer[2048+512]; +} divas_um_idi_entity_t; + + +#endif diff --git a/drivers/isdn/hardware/eicon/helpers.h b/drivers/isdn/hardware/eicon/helpers.h new file mode 100644 index 000000000000..b2123119e430 --- /dev/null +++ b/drivers/isdn/hardware/eicon/helpers.h @@ -0,0 +1,51 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +#ifndef __DIVA_XDI_CARD_CONFIG_HELPERS_INC__ +#define __DIVA_XDI_CARD_CONFIG_HELPERS_INC__ +dword diva_get_protocol_file_features (byte* File, + int offset, + char *IdStringBuffer, + dword IdBufferSize); +void diva_configure_protocol (PISDN_ADAPTER IoAdapter); +/* + Low level file access system abstraction + */ +/* ------------------------------------------------------------------------- + Access to single file + Return pointer to the image of the requested file, + write image length to 'FileLength' + ------------------------------------------------------------------------- */ +void *xdiLoadFile (char *FileName, dword *FileLength, unsigned long MaxLoadSize) ; +/* ------------------------------------------------------------------------- + Dependent on the protocol settings does read return pointer + to the image of appropriate protocol file + ------------------------------------------------------------------------- */ +void *xdiLoadArchive (PISDN_ADAPTER IoAdapter, dword *FileLength, unsigned long MaxLoadSize) ; +/* -------------------------------------------------------------------------- + Free all system resources accessed by xdiLoadFile and xdiLoadArchive + -------------------------------------------------------------------------- */ +void xdiFreeFile (void* handle); +#endif diff --git a/drivers/isdn/hardware/eicon/idifunc.c b/drivers/isdn/hardware/eicon/idifunc.c new file mode 100644 index 000000000000..4cbc68cf4dba --- /dev/null +++ b/drivers/isdn/hardware/eicon/idifunc.c @@ -0,0 +1,267 @@ +/* $Id: idifunc.c,v 1.14.4.4 2004/08/28 20:03:53 armin Exp $ + * + * Driver for Eicon DIVA Server ISDN cards. + * User Mode IDI Interface + * + * Copyright 2000-2003 by Armin Schindler (mac@melware.de) + * Copyright 2000-2003 Cytronics & Melware (info@melware.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + */ + +#include "platform.h" +#include "di_defs.h" +#include "divasync.h" +#include "um_xdi.h" +#include "um_idi.h" + +#define DBG_MINIMUM (DL_LOG + DL_FTL + DL_ERR) +#define DBG_DEFAULT (DBG_MINIMUM + DL_XLOG + DL_REG) + +extern char *DRIVERRELEASE_IDI; + +extern void DIVA_DIDD_Read(void *, int); +extern int diva_user_mode_idi_create_adapter(const DESCRIPTOR *, int); +extern void diva_user_mode_idi_remove_adapter(int); + +static dword notify_handle; +static DESCRIPTOR DAdapter; +static DESCRIPTOR MAdapter; + +static void no_printf(unsigned char *x, ...) +{ + /* dummy debug function */ +} + +#include "debuglib.c" + +/* + * stop debug + */ +static void stop_dbg(void) +{ + DbgDeregister(); + memset(&MAdapter, 0, sizeof(MAdapter)); + dprintf = no_printf; +} + +typedef struct _udiva_card { + struct list_head list; + int Id; + DESCRIPTOR d; +} udiva_card; + +static LIST_HEAD(cards); +static diva_os_spin_lock_t ll_lock; + +/* + * find card in list + */ +static udiva_card *find_card_in_list(DESCRIPTOR * d) +{ + udiva_card *card; + struct list_head *tmp; + diva_os_spin_lock_magic_t old_irql; + + diva_os_enter_spin_lock(&ll_lock, &old_irql, "find card"); + list_for_each(tmp, &cards) { + card = list_entry(tmp, udiva_card, list); + if (card->d.request == d->request) { + diva_os_leave_spin_lock(&ll_lock, &old_irql, + "find card"); + return (card); + } + } + diva_os_leave_spin_lock(&ll_lock, &old_irql, "find card"); + return ((udiva_card *) NULL); +} + +/* + * new card + */ +static void um_new_card(DESCRIPTOR * d) +{ + int adapter_nr = 0; + udiva_card *card = NULL; + IDI_SYNC_REQ sync_req; + diva_os_spin_lock_magic_t old_irql; + + if (!(card = diva_os_malloc(0, sizeof(udiva_card)))) { + DBG_ERR(("cannot get buffer for card")); + return; + } + memcpy(&card->d, d, sizeof(DESCRIPTOR)); + sync_req.xdi_logical_adapter_number.Req = 0; + sync_req.xdi_logical_adapter_number.Rc = + IDI_SYNC_REQ_XDI_GET_LOGICAL_ADAPTER_NUMBER; + card->d.request((ENTITY *) & sync_req); + adapter_nr = + sync_req.xdi_logical_adapter_number.info.logical_adapter_number; + card->Id = adapter_nr; + if (!(diva_user_mode_idi_create_adapter(d, adapter_nr))) { + diva_os_enter_spin_lock(&ll_lock, &old_irql, "add card"); + list_add_tail(&card->list, &cards); + diva_os_leave_spin_lock(&ll_lock, &old_irql, "add card"); + } else { + DBG_ERR(("could not create user mode idi card %d", + adapter_nr)); + } +} + +/* + * remove card + */ +static void um_remove_card(DESCRIPTOR * d) +{ + diva_os_spin_lock_magic_t old_irql; + udiva_card *card = NULL; + + if (!(card = find_card_in_list(d))) { + DBG_ERR(("cannot find card to remove")); + return; + } + diva_user_mode_idi_remove_adapter(card->Id); + diva_os_enter_spin_lock(&ll_lock, &old_irql, "remove card"); + list_del(&card->list); + diva_os_leave_spin_lock(&ll_lock, &old_irql, "remove card"); + DBG_LOG(("idi proc entry removed for card %d", card->Id)); + diva_os_free(0, card); +} + +/* + * remove all adapter + */ +static void DIVA_EXIT_FUNCTION remove_all_idi_proc(void) +{ + udiva_card *card; + diva_os_spin_lock_magic_t old_irql; + +rescan: + diva_os_enter_spin_lock(&ll_lock, &old_irql, "remove all"); + if (!list_empty(&cards)) { + card = list_entry(cards.next, udiva_card, list); + list_del(&card->list); + diva_os_leave_spin_lock(&ll_lock, &old_irql, "remove all"); + diva_user_mode_idi_remove_adapter(card->Id); + diva_os_free(0, card); + goto rescan; + } + diva_os_leave_spin_lock(&ll_lock, &old_irql, "remove all"); +} + +/* + * DIDD notify callback + */ +static void *didd_callback(void *context, DESCRIPTOR * adapter, + int removal) +{ + if (adapter->type == IDI_DADAPTER) { + DBG_ERR(("Notification about IDI_DADAPTER change ! Oops.")); + return (NULL); + } else if (adapter->type == IDI_DIMAINT) { + if (removal) { + stop_dbg(); + } else { + memcpy(&MAdapter, adapter, sizeof(MAdapter)); + dprintf = (DIVA_DI_PRINTF) MAdapter.request; + DbgRegister("User IDI", DRIVERRELEASE_IDI, DBG_DEFAULT); + } + } else if ((adapter->type > 0) && (adapter->type < 16)) { /* IDI Adapter */ + if (removal) { + um_remove_card(adapter); + } else { + um_new_card(adapter); + } + } + return (NULL); +} + +/* + * connect DIDD + */ +static int DIVA_INIT_FUNCTION connect_didd(void) +{ + int x = 0; + int dadapter = 0; + IDI_SYNC_REQ req; + DESCRIPTOR DIDD_Table[MAX_DESCRIPTORS]; + + DIVA_DIDD_Read(DIDD_Table, sizeof(DIDD_Table)); + + for (x = 0; x < MAX_DESCRIPTORS; x++) { + if (DIDD_Table[x].type == IDI_DADAPTER) { /* DADAPTER found */ + dadapter = 1; + memcpy(&DAdapter, &DIDD_Table[x], sizeof(DAdapter)); + req.didd_notify.e.Req = 0; + req.didd_notify.e.Rc = + IDI_SYNC_REQ_DIDD_REGISTER_ADAPTER_NOTIFY; + req.didd_notify.info.callback = (void *)didd_callback; + req.didd_notify.info.context = NULL; + DAdapter.request((ENTITY *) & req); + if (req.didd_notify.e.Rc != 0xff) { + stop_dbg(); + return (0); + } + notify_handle = req.didd_notify.info.handle; + } else if (DIDD_Table[x].type == IDI_DIMAINT) { /* MAINT found */ + memcpy(&MAdapter, &DIDD_Table[x], sizeof(DAdapter)); + dprintf = (DIVA_DI_PRINTF) MAdapter.request; + DbgRegister("User IDI", DRIVERRELEASE_IDI, DBG_DEFAULT); + } else if ((DIDD_Table[x].type > 0) + && (DIDD_Table[x].type < 16)) { /* IDI Adapter found */ + um_new_card(&DIDD_Table[x]); + } + } + + if (!dadapter) { + stop_dbg(); + } + + return (dadapter); +} + +/* + * Disconnect from DIDD + */ +static void DIVA_EXIT_FUNCTION disconnect_didd(void) +{ + IDI_SYNC_REQ req; + + stop_dbg(); + + req.didd_notify.e.Req = 0; + req.didd_notify.e.Rc = IDI_SYNC_REQ_DIDD_REMOVE_ADAPTER_NOTIFY; + req.didd_notify.info.handle = notify_handle; + DAdapter.request((ENTITY *) & req); +} + +/* + * init + */ +int DIVA_INIT_FUNCTION idifunc_init(void) +{ + diva_os_initialize_spin_lock(&ll_lock, "idifunc"); + + if (diva_user_mode_idi_init()) { + DBG_ERR(("init: init failed.")); + return (0); + } + + if (!connect_didd()) { + diva_user_mode_idi_finit(); + DBG_ERR(("init: failed to connect to DIDD.")); + return (0); + } + return (1); +} + +/* + * finit + */ +void DIVA_EXIT_FUNCTION idifunc_finit(void) +{ + diva_user_mode_idi_finit(); + disconnect_didd(); + remove_all_idi_proc(); +} diff --git a/drivers/isdn/hardware/eicon/io.c b/drivers/isdn/hardware/eicon/io.c new file mode 100644 index 000000000000..4a27e230b0a5 --- /dev/null +++ b/drivers/isdn/hardware/eicon/io.c @@ -0,0 +1,852 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +#include "platform.h" +#include "di_defs.h" +#include "pc.h" +#include "pr_pc.h" +#include "divasync.h" +#define MIPS_SCOM +#include "pkmaint.h" /* pc_main.h, packed in os-dependent fashion */ +#include "di.h" +#include "mi_pc.h" +#include "io.h" +extern ADAPTER * adapter[MAX_ADAPTER]; +extern PISDN_ADAPTER IoAdapters[MAX_ADAPTER]; +void request (PISDN_ADAPTER, ENTITY *); +static void pcm_req (PISDN_ADAPTER, ENTITY *); +/* -------------------------------------------------------------------------- + local functions + -------------------------------------------------------------------------- */ +#define ReqFunc(N) \ +static void Request##N(ENTITY *e) \ +{ if ( IoAdapters[N] ) (* IoAdapters[N]->DIRequest)(IoAdapters[N], e) ; } +ReqFunc(0) +ReqFunc(1) +ReqFunc(2) +ReqFunc(3) +ReqFunc(4) +ReqFunc(5) +ReqFunc(6) +ReqFunc(7) +ReqFunc(8) +ReqFunc(9) +ReqFunc(10) +ReqFunc(11) +ReqFunc(12) +ReqFunc(13) +ReqFunc(14) +ReqFunc(15) +IDI_CALL Requests[MAX_ADAPTER] = +{ &Request0, &Request1, &Request2, &Request3, + &Request4, &Request5, &Request6, &Request7, + &Request8, &Request9, &Request10, &Request11, + &Request12, &Request13, &Request14, &Request15 +}; +/*****************************************************************************/ +/* + This array should indicate all new services, that this version of XDI + is able to provide to his clients + */ +static byte extended_xdi_features[DIVA_XDI_EXTENDED_FEATURES_MAX_SZ+1] = { + (DIVA_XDI_EXTENDED_FEATURES_VALID | + DIVA_XDI_EXTENDED_FEATURE_SDRAM_BAR | + DIVA_XDI_EXTENDED_FEATURE_CAPI_PRMS | +#if defined(DIVA_IDI_RX_DMA) + DIVA_XDI_EXTENDED_FEATURE_CMA | + DIVA_XDI_EXTENDED_FEATURE_RX_DMA | + DIVA_XDI_EXTENDED_FEATURE_MANAGEMENT_DMA | +#endif + DIVA_XDI_EXTENDED_FEATURE_NO_CANCEL_RC), + 0 +}; +/*****************************************************************************/ +void +dump_xlog_buffer (PISDN_ADAPTER IoAdapter, Xdesc *xlogDesc) +{ + dword logLen ; + word *Xlog = xlogDesc->buf ; + word logCnt = xlogDesc->cnt ; + word logOut = xlogDesc->out / sizeof(*Xlog) ; + DBG_FTL(("%s: ************* XLOG recovery (%d) *************", + &IoAdapter->Name[0], (int)logCnt)) + DBG_FTL(("Microcode: %s", &IoAdapter->ProtocolIdString[0])) + for ( ; logCnt > 0 ; --logCnt ) + { + if ( !GET_WORD(&Xlog[logOut]) ) + { + if ( --logCnt == 0 ) + break ; + logOut = 0 ; + } + if ( GET_WORD(&Xlog[logOut]) <= (logOut * sizeof(*Xlog)) ) + { + if ( logCnt > 2 ) + { + DBG_FTL(("Possibly corrupted XLOG: %d entries left", + (int)logCnt)) + } + break ; + } + logLen = (dword)(GET_WORD(&Xlog[logOut]) - (logOut * sizeof(*Xlog))) ; + DBG_FTL_MXLOG(( (char *)&Xlog[logOut + 1], (dword)(logLen - 2) )) + logOut = (GET_WORD(&Xlog[logOut]) + 1) / sizeof(*Xlog) ; + } + DBG_FTL(("%s: ***************** end of XLOG *****************", + &IoAdapter->Name[0])) +} +/*****************************************************************************/ +#if defined(XDI_USE_XLOG) +static char *(ExceptionCauseTable[]) = +{ + "Interrupt", + "TLB mod /IBOUND", + "TLB load /DBOUND", + "TLB store", + "Address error load", + "Address error store", + "Instruction load bus error", + "Data load/store bus error", + "Syscall", + "Breakpoint", + "Reverd instruction", + "Coprocessor unusable", + "Overflow", + "TRAP", + "VCEI", + "Floating Point Exception", + "CP2", + "Reserved 17", + "Reserved 18", + "Reserved 19", + "Reserved 20", + "Reserved 21", + "Reserved 22", + "WATCH", + "Reserved 24", + "Reserved 25", + "Reserved 26", + "Reserved 27", + "Reserved 28", + "Reserved 29", + "Reserved 30", + "VCED" +} ; +#endif +void +dump_trap_frame (PISDN_ADAPTER IoAdapter, byte __iomem *exceptionFrame) +{ + MP_XCPTC __iomem *xcept = (MP_XCPTC __iomem *)exceptionFrame ; + dword __iomem *regs; + regs = &xcept->regs[0] ; + DBG_FTL(("%s: ***************** CPU TRAPPED *****************", + &IoAdapter->Name[0])) + DBG_FTL(("Microcode: %s", &IoAdapter->ProtocolIdString[0])) + DBG_FTL(("Cause: %s", + ExceptionCauseTable[(READ_DWORD(&xcept->cr) & 0x0000007c) >> 2])) + DBG_FTL(("sr 0x%08x cr 0x%08x epc 0x%08x vaddr 0x%08x", + READ_DWORD(&xcept->sr), READ_DWORD(&xcept->cr), + READ_DWORD(&xcept->epc), READ_DWORD(&xcept->vaddr))) + DBG_FTL(("zero 0x%08x at 0x%08x v0 0x%08x v1 0x%08x", + READ_DWORD(®s[ 0]), READ_DWORD(®s[ 1]), + READ_DWORD(®s[ 2]), READ_DWORD(®s[ 3]))) + DBG_FTL(("a0 0x%08x a1 0x%08x a2 0x%08x a3 0x%08x", + READ_DWORD(®s[ 4]), READ_DWORD(®s[ 5]), + READ_DWORD(®s[ 6]), READ_DWORD(®s[ 7]))) + DBG_FTL(("t0 0x%08x t1 0x%08x t2 0x%08x t3 0x%08x", + READ_DWORD(®s[ 8]), READ_DWORD(®s[ 9]), + READ_DWORD(®s[10]), READ_DWORD(®s[11]))) + DBG_FTL(("t4 0x%08x t5 0x%08x t6 0x%08x t7 0x%08x", + READ_DWORD(®s[12]), READ_DWORD(®s[13]), + READ_DWORD(®s[14]), READ_DWORD(®s[15]))) + DBG_FTL(("s0 0x%08x s1 0x%08x s2 0x%08x s3 0x%08x", + READ_DWORD(®s[16]), READ_DWORD(®s[17]), + READ_DWORD(®s[18]), READ_DWORD(®s[19]))) + DBG_FTL(("s4 0x%08x s5 0x%08x s6 0x%08x s7 0x%08x", + READ_DWORD(®s[20]), READ_DWORD(®s[21]), + READ_DWORD(®s[22]), READ_DWORD(®s[23]))) + DBG_FTL(("t8 0x%08x t9 0x%08x k0 0x%08x k1 0x%08x", + READ_DWORD(®s[24]), READ_DWORD(®s[25]), + READ_DWORD(®s[26]), READ_DWORD(®s[27]))) + DBG_FTL(("gp 0x%08x sp 0x%08x s8 0x%08x ra 0x%08x", + READ_DWORD(®s[28]), READ_DWORD(®s[29]), + READ_DWORD(®s[30]), READ_DWORD(®s[31]))) + DBG_FTL(("md 0x%08x|%08x resvd 0x%08x class 0x%08x", + READ_DWORD(&xcept->mdhi), READ_DWORD(&xcept->mdlo), + READ_DWORD(&xcept->reseverd), READ_DWORD(&xcept->xclass))) +} +/* -------------------------------------------------------------------------- + Real XDI Request function + -------------------------------------------------------------------------- */ +void request(PISDN_ADAPTER IoAdapter, ENTITY * e) +{ + byte i; + diva_os_spin_lock_magic_t irql; +/* + * if the Req field in the entity structure is 0, + * we treat this request as a special function call + */ + if ( !e->Req ) + { + IDI_SYNC_REQ *syncReq = (IDI_SYNC_REQ *)e ; + switch (e->Rc) + { +#if defined(DIVA_IDI_RX_DMA) + case IDI_SYNC_REQ_DMA_DESCRIPTOR_OPERATION: { + diva_xdi_dma_descriptor_operation_t* pI = \ + &syncReq->xdi_dma_descriptor_operation.info; + if (!IoAdapter->dma_map) { + pI->operation = -1; + pI->descriptor_number = -1; + return; + } + diva_os_enter_spin_lock (&IoAdapter->data_spin_lock, &irql, "dma_op"); + if (pI->operation == IDI_SYNC_REQ_DMA_DESCRIPTOR_ALLOC) { + pI->descriptor_number = diva_alloc_dma_map_entry (\ + (struct _diva_dma_map_entry*)IoAdapter->dma_map); + if (pI->descriptor_number >= 0) { + dword dma_magic; + void* local_addr; + diva_get_dma_map_entry (\ + (struct _diva_dma_map_entry*)IoAdapter->dma_map, + pI->descriptor_number, + &local_addr, &dma_magic); + pI->descriptor_address = local_addr; + pI->descriptor_magic = dma_magic; + pI->operation = 0; + } else { + pI->operation = -1; + } + } else if ((pI->operation == IDI_SYNC_REQ_DMA_DESCRIPTOR_FREE) && + (pI->descriptor_number >= 0)) { + diva_free_dma_map_entry((struct _diva_dma_map_entry*)IoAdapter->dma_map, + pI->descriptor_number); + pI->descriptor_number = -1; + pI->operation = 0; + } else { + pI->descriptor_number = -1; + pI->operation = -1; + } + diva_os_leave_spin_lock (&IoAdapter->data_spin_lock, &irql, "dma_op"); + } return; +#endif + case IDI_SYNC_REQ_XDI_GET_LOGICAL_ADAPTER_NUMBER: { + diva_xdi_get_logical_adapter_number_s_t *pI = \ + &syncReq->xdi_logical_adapter_number.info; + pI->logical_adapter_number = IoAdapter->ANum; + pI->controller = IoAdapter->ControllerNumber; + pI->total_controllers = IoAdapter->Properties.Adapters; + } return; + case IDI_SYNC_REQ_XDI_GET_CAPI_PARAMS: { + diva_xdi_get_capi_parameters_t prms, *pI = &syncReq->xdi_capi_prms.info; + memset (&prms, 0x00, sizeof(prms)); + prms.structure_length = MIN(sizeof(prms), pI->structure_length); + memset (pI, 0x00, pI->structure_length); + prms.flag_dynamic_l1_down = (IoAdapter->capi_cfg.cfg_1 & \ + DIVA_XDI_CAPI_CFG_1_DYNAMIC_L1_ON) ? 1 : 0; + prms.group_optimization_enabled = (IoAdapter->capi_cfg.cfg_1 & \ + DIVA_XDI_CAPI_CFG_1_GROUP_POPTIMIZATION_ON) ? 1 : 0; + memcpy (pI, &prms, prms.structure_length); + } return; + case IDI_SYNC_REQ_XDI_GET_ADAPTER_SDRAM_BAR: + syncReq->xdi_sdram_bar.info.bar = IoAdapter->sdram_bar; + return; + case IDI_SYNC_REQ_XDI_GET_EXTENDED_FEATURES: { + dword i; + diva_xdi_get_extended_xdi_features_t* pI =\ + &syncReq->xdi_extended_features.info; + pI->buffer_length_in_bytes &= ~0x80000000; + if (pI->buffer_length_in_bytes && pI->features) { + memset (pI->features, 0x00, pI->buffer_length_in_bytes); + } + for (i = 0; ((pI->features) && (i < pI->buffer_length_in_bytes) && + (i < DIVA_XDI_EXTENDED_FEATURES_MAX_SZ)); i++) { + pI->features[i] = extended_xdi_features[i]; + } + if ((pI->buffer_length_in_bytes < DIVA_XDI_EXTENDED_FEATURES_MAX_SZ) || + (!pI->features)) { + pI->buffer_length_in_bytes =\ + (0x80000000 | DIVA_XDI_EXTENDED_FEATURES_MAX_SZ); + } + } return; + case IDI_SYNC_REQ_XDI_GET_STREAM: + if (IoAdapter) { + diva_xdi_provide_istream_info (&IoAdapter->a, + &syncReq->xdi_stream_info.info); + } else { + syncReq->xdi_stream_info.info.provided_service = 0; + } + return; + case IDI_SYNC_REQ_GET_NAME: + if ( IoAdapter ) + { + strcpy (&syncReq->GetName.name[0], IoAdapter->Name) ; + DBG_TRC(("xdi: Adapter %d / Name '%s'", + IoAdapter->ANum, IoAdapter->Name)) + return ; + } + syncReq->GetName.name[0] = '\0' ; + break ; + case IDI_SYNC_REQ_GET_SERIAL: + if ( IoAdapter ) + { + syncReq->GetSerial.serial = IoAdapter->serialNo ; + DBG_TRC(("xdi: Adapter %d / SerialNo %ld", + IoAdapter->ANum, IoAdapter->serialNo)) + return ; + } + syncReq->GetSerial.serial = 0 ; + break ; + case IDI_SYNC_REQ_GET_CARDTYPE: + if ( IoAdapter ) + { + syncReq->GetCardType.cardtype = IoAdapter->cardType ; + DBG_TRC(("xdi: Adapter %d / CardType %ld", + IoAdapter->ANum, IoAdapter->cardType)) + return ; + } + syncReq->GetCardType.cardtype = 0 ; + break ; + case IDI_SYNC_REQ_GET_XLOG: + if ( IoAdapter ) + { + pcm_req (IoAdapter, e) ; + return ; + } + e->Ind = 0 ; + break ; + case IDI_SYNC_REQ_GET_DBG_XLOG: + if ( IoAdapter ) + { + pcm_req (IoAdapter, e) ; + return ; + } + e->Ind = 0 ; + break ; + case IDI_SYNC_REQ_GET_FEATURES: + if ( IoAdapter ) + { + syncReq->GetFeatures.features = + (unsigned short)IoAdapter->features ; + return ; + } + syncReq->GetFeatures.features = 0 ; + break ; + case IDI_SYNC_REQ_PORTDRV_HOOK: + if ( IoAdapter ) + { + DBG_TRC(("Xdi:IDI_SYNC_REQ_PORTDRV_HOOK - ignored")) + return ; + } + break; + } + if ( IoAdapter ) + { + return ; + } + } + DBG_TRC(("xdi: Id 0x%x / Req 0x%x / Rc 0x%x", e->Id, e->Req, e->Rc)) + if ( !IoAdapter ) + { + DBG_FTL(("xdi: uninitialized Adapter used - ignore request")) + return ; + } + diva_os_enter_spin_lock (&IoAdapter->data_spin_lock, &irql, "data_req"); +/* + * assign an entity + */ + if ( !(e->Id &0x1f) ) + { + if ( IoAdapter->e_count >= IoAdapter->e_max ) + { + DBG_FTL(("xdi: all Ids in use (max=%d) --> Req ignored", + IoAdapter->e_max)) + diva_os_leave_spin_lock (&IoAdapter->data_spin_lock, &irql, "data_req"); + return ; + } +/* + * find a new free id + */ + for ( i = 1 ; IoAdapter->e_tbl[i].e ; ++i ) ; + IoAdapter->e_tbl[i].e = e ; + IoAdapter->e_count++ ; + e->No = (byte)i ; + e->More = 0 ; + e->RCurrent = 0xff ; + } + else + { + i = e->No ; + } +/* + * if the entity is still busy, ignore the request call + */ + if ( e->More & XBUSY ) + { + DBG_FTL(("xdi: Id 0x%x busy --> Req 0x%x ignored", e->Id, e->Req)) + if ( !IoAdapter->trapped && IoAdapter->trapFnc ) + { + IoAdapter->trapFnc (IoAdapter) ; + /* + Firs trap, also notify user if supported + */ + if (IoAdapter->trapped && IoAdapter->os_trap_nfy_Fnc) { + (*(IoAdapter->os_trap_nfy_Fnc))(IoAdapter, IoAdapter->ANum); + } + } + diva_os_leave_spin_lock (&IoAdapter->data_spin_lock, &irql, "data_req"); + return ; + } +/* + * initialize transmit status variables + */ + e->More |= XBUSY ; + e->More &= ~XMOREF ; + e->XCurrent = 0 ; + e->XOffset = 0 ; +/* + * queue this entity in the adapter request queue + */ + IoAdapter->e_tbl[i].next = 0 ; + if ( IoAdapter->head ) + { + IoAdapter->e_tbl[IoAdapter->tail].next = i ; + IoAdapter->tail = i ; + } + else + { + IoAdapter->head = i ; + IoAdapter->tail = i ; + } +/* + * queue the DPC to process the request + */ + diva_os_schedule_soft_isr (&IoAdapter->req_soft_isr); + diva_os_leave_spin_lock (&IoAdapter->data_spin_lock, &irql, "data_req"); +} +/* --------------------------------------------------------------------- + Main DPC routine + --------------------------------------------------------------------- */ +void DIDpcRoutine (struct _diva_os_soft_isr* psoft_isr, void* Context) { + PISDN_ADAPTER IoAdapter = (PISDN_ADAPTER)Context ; + ADAPTER* a = &IoAdapter->a ; + diva_os_atomic_t* pin_dpc = &IoAdapter->in_dpc; + if (diva_os_atomic_increment (pin_dpc) == 1) { + do { + if ( IoAdapter->tst_irq (a) ) + { + if ( !IoAdapter->Unavailable ) + IoAdapter->dpc (a) ; + IoAdapter->clr_irq (a) ; + } + IoAdapter->out (a) ; + } while (diva_os_atomic_decrement (pin_dpc) > 0); + /* ---------------------------------------------------------------- + Look for XLOG request (cards with indirect addressing) + ---------------------------------------------------------------- */ + if (IoAdapter->pcm_pending) { + struct pc_maint *pcm; + diva_os_spin_lock_magic_t OldIrql ; + diva_os_enter_spin_lock (&IoAdapter->data_spin_lock, + &OldIrql, + "data_dpc"); + pcm = (struct pc_maint *)IoAdapter->pcm_data; + switch (IoAdapter->pcm_pending) { + case 1: /* ask card for XLOG */ + a->ram_out (a, &IoAdapter->pcm->rc, 0) ; + a->ram_out (a, &IoAdapter->pcm->req, pcm->req) ; + IoAdapter->pcm_pending = 2; + break; + case 2: /* Try to get XLOG from the card */ + if ((int)(a->ram_in (a, &IoAdapter->pcm->rc))) { + a->ram_in_buffer (a, IoAdapter->pcm, pcm, sizeof(*pcm)) ; + IoAdapter->pcm_pending = 3; + } + break; + case 3: /* let XDI recovery XLOG */ + break; + } + diva_os_leave_spin_lock (&IoAdapter->data_spin_lock, + &OldIrql, + "data_dpc"); + } + /* ---------------------------------------------------------------- */ + } +} +/* -------------------------------------------------------------------------- + XLOG interface + -------------------------------------------------------------------------- */ +static void +pcm_req (PISDN_ADAPTER IoAdapter, ENTITY *e) +{ + diva_os_spin_lock_magic_t OldIrql ; + int i, rc ; + ADAPTER *a = &IoAdapter->a ; + struct pc_maint *pcm = (struct pc_maint *)&e->Ind ; +/* + * special handling of I/O based card interface + * the memory access isn't an atomic operation ! + */ + if ( IoAdapter->Properties.Card == CARD_MAE ) + { + diva_os_enter_spin_lock (&IoAdapter->data_spin_lock, + &OldIrql, + "data_pcm_1"); + IoAdapter->pcm_data = (void *)pcm; + IoAdapter->pcm_pending = 1; + diva_os_schedule_soft_isr (&IoAdapter->req_soft_isr); + diva_os_leave_spin_lock (&IoAdapter->data_spin_lock, + &OldIrql, + "data_pcm_1"); + for ( rc = 0, i = (IoAdapter->trapped ? 3000 : 250) ; !rc && (i > 0) ; --i ) + { + diva_os_sleep (1) ; + if (IoAdapter->pcm_pending == 3) { + diva_os_enter_spin_lock (&IoAdapter->data_spin_lock, + &OldIrql, + "data_pcm_3"); + IoAdapter->pcm_pending = 0; + IoAdapter->pcm_data = NULL ; + diva_os_leave_spin_lock (&IoAdapter->data_spin_lock, + &OldIrql, + "data_pcm_3"); + return ; + } + diva_os_enter_spin_lock (&IoAdapter->data_spin_lock, + &OldIrql, + "data_pcm_2"); + diva_os_schedule_soft_isr (&IoAdapter->req_soft_isr); + diva_os_leave_spin_lock (&IoAdapter->data_spin_lock, + &OldIrql, + "data_pcm_2"); + } + diva_os_enter_spin_lock (&IoAdapter->data_spin_lock, + &OldIrql, + "data_pcm_4"); + IoAdapter->pcm_pending = 0; + IoAdapter->pcm_data = NULL ; + diva_os_leave_spin_lock (&IoAdapter->data_spin_lock, + &OldIrql, + "data_pcm_4"); + goto Trapped ; + } +/* + * memory based shared ram is accessible from different + * processors without disturbing concurrent processes. + */ + a->ram_out (a, &IoAdapter->pcm->rc, 0) ; + a->ram_out (a, &IoAdapter->pcm->req, pcm->req) ; + for ( i = (IoAdapter->trapped ? 3000 : 250) ; --i > 0 ; ) + { + diva_os_sleep (1) ; + rc = (int)(a->ram_in (a, &IoAdapter->pcm->rc)) ; + if ( rc ) + { + a->ram_in_buffer (a, IoAdapter->pcm, pcm, sizeof(*pcm)) ; + return ; + } + } +Trapped: + if ( IoAdapter->trapFnc ) + { + int trapped = IoAdapter->trapped; + IoAdapter->trapFnc (IoAdapter) ; + /* + Firs trap, also notify user if supported + */ + if (!trapped && IoAdapter->trapped && IoAdapter->os_trap_nfy_Fnc) { + (*(IoAdapter->os_trap_nfy_Fnc))(IoAdapter, IoAdapter->ANum); + } + } +} +/*------------------------------------------------------------------*/ +/* ram access functions for memory mapped cards */ +/*------------------------------------------------------------------*/ +byte mem_in (ADAPTER *a, void *addr) +{ + byte val; + volatile byte __iomem *Base = DIVA_OS_MEM_ATTACH_RAM((PISDN_ADAPTER)a->io); + val = READ_BYTE(Base + (unsigned long)addr); + DIVA_OS_MEM_DETACH_RAM((PISDN_ADAPTER)a->io, Base); + return (val); +} +word mem_inw (ADAPTER *a, void *addr) +{ + word val; + volatile byte __iomem *Base = DIVA_OS_MEM_ATTACH_RAM((PISDN_ADAPTER)a->io); + val = READ_WORD((Base + (unsigned long)addr)); + DIVA_OS_MEM_DETACH_RAM((PISDN_ADAPTER)a->io, Base); + return (val); +} +void mem_in_dw (ADAPTER *a, void *addr, dword* data, int dwords) +{ + volatile byte __iomem * Base = DIVA_OS_MEM_ATTACH_RAM((PISDN_ADAPTER)a->io); + while (dwords--) { + *data++ = READ_DWORD((Base + (unsigned long)addr)); + addr+=4; + } + DIVA_OS_MEM_DETACH_RAM((PISDN_ADAPTER)a->io, Base); +} +void mem_in_buffer (ADAPTER *a, void *addr, void *buffer, word length) +{ + volatile byte __iomem *Base = DIVA_OS_MEM_ATTACH_RAM((PISDN_ADAPTER)a->io); + memcpy_fromio(buffer, (Base + (unsigned long)addr), length); + DIVA_OS_MEM_DETACH_RAM((PISDN_ADAPTER)a->io, Base); +} +void mem_look_ahead (ADAPTER *a, PBUFFER *RBuffer, ENTITY *e) +{ + PISDN_ADAPTER IoAdapter = (PISDN_ADAPTER)a->io ; + IoAdapter->RBuffer.length = mem_inw (a, &RBuffer->length) ; + mem_in_buffer (a, RBuffer->P, IoAdapter->RBuffer.P, + IoAdapter->RBuffer.length) ; + e->RBuffer = (DBUFFER *)&IoAdapter->RBuffer ; +} +void mem_out (ADAPTER *a, void *addr, byte data) +{ + volatile byte __iomem *Base = DIVA_OS_MEM_ATTACH_RAM((PISDN_ADAPTER)a->io); + WRITE_BYTE(Base + (unsigned long)addr, data); + DIVA_OS_MEM_DETACH_RAM((PISDN_ADAPTER)a->io, Base); +} +void mem_outw (ADAPTER *a, void *addr, word data) +{ + volatile byte __iomem * Base = DIVA_OS_MEM_ATTACH_RAM((PISDN_ADAPTER)a->io); + WRITE_WORD((Base + (unsigned long)addr), data); + DIVA_OS_MEM_DETACH_RAM((PISDN_ADAPTER)a->io, Base); +} +void mem_out_dw (ADAPTER *a, void *addr, const dword* data, int dwords) +{ + volatile byte __iomem * Base = DIVA_OS_MEM_ATTACH_RAM((PISDN_ADAPTER)a->io); + while (dwords--) { + WRITE_DWORD((Base + (unsigned long)addr), *data); + addr+=4; + data++; + } + DIVA_OS_MEM_DETACH_RAM((PISDN_ADAPTER)a->io, Base); +} +void mem_out_buffer (ADAPTER *a, void *addr, void *buffer, word length) +{ + volatile byte __iomem * Base = DIVA_OS_MEM_ATTACH_RAM((PISDN_ADAPTER)a->io); + memcpy_toio((Base + (unsigned long)addr), buffer, length) ; + DIVA_OS_MEM_DETACH_RAM((PISDN_ADAPTER)a->io, Base); +} +void mem_inc (ADAPTER *a, void *addr) +{ + volatile byte __iomem *Base = DIVA_OS_MEM_ATTACH_RAM((PISDN_ADAPTER)a->io); + byte x = READ_BYTE(Base + (unsigned long)addr); + WRITE_BYTE(Base + (unsigned long)addr, x + 1); + DIVA_OS_MEM_DETACH_RAM((PISDN_ADAPTER)a->io, Base); +} +/*------------------------------------------------------------------*/ +/* ram access functions for io-mapped cards */ +/*------------------------------------------------------------------*/ +byte io_in(ADAPTER * a, void * adr) +{ + byte val; + byte __iomem *Port = DIVA_OS_MEM_ATTACH_PORT((PISDN_ADAPTER)a->io); + outppw(Port + 4, (word)(unsigned long)adr); + val = inpp(Port); + DIVA_OS_MEM_DETACH_PORT((PISDN_ADAPTER)a->io, Port); + return(val); +} +word io_inw(ADAPTER * a, void * adr) +{ + word val; + byte __iomem *Port = DIVA_OS_MEM_ATTACH_PORT((PISDN_ADAPTER)a->io); + outppw(Port + 4, (word)(unsigned long)adr); + val = inppw(Port); + DIVA_OS_MEM_DETACH_PORT((PISDN_ADAPTER)a->io, Port); + return(val); +} +void io_in_buffer(ADAPTER * a, void * adr, void * buffer, word len) +{ + byte __iomem *Port = DIVA_OS_MEM_ATTACH_PORT((PISDN_ADAPTER)a->io); + byte* P = (byte*)buffer; + if ((long)adr & 1) { + outppw(Port+4, (word)(unsigned long)adr); + *P = inpp(Port); + P++; + adr = ((byte *) adr) + 1; + len--; + if (!len) { + DIVA_OS_MEM_DETACH_PORT((PISDN_ADAPTER)a->io, Port); + return; + } + } + outppw(Port+4, (word)(unsigned long)adr); + inppw_buffer (Port, P, len+1); + DIVA_OS_MEM_DETACH_PORT((PISDN_ADAPTER)a->io, Port); +} +void io_look_ahead(ADAPTER * a, PBUFFER * RBuffer, ENTITY * e) +{ + byte __iomem *Port = DIVA_OS_MEM_ATTACH_PORT((PISDN_ADAPTER)a->io); + outppw(Port+4, (word)(unsigned long)RBuffer); + ((PISDN_ADAPTER)a->io)->RBuffer.length = inppw(Port); + inppw_buffer (Port, ((PISDN_ADAPTER)a->io)->RBuffer.P, ((PISDN_ADAPTER)a->io)->RBuffer.length + 1); + e->RBuffer = (DBUFFER *) &(((PISDN_ADAPTER)a->io)->RBuffer); + DIVA_OS_MEM_DETACH_PORT((PISDN_ADAPTER)a->io, Port); +} +void io_out(ADAPTER * a, void * adr, byte data) +{ + byte __iomem *Port = DIVA_OS_MEM_ATTACH_PORT((PISDN_ADAPTER)a->io); + outppw(Port+4, (word)(unsigned long)adr); + outpp(Port, data); + DIVA_OS_MEM_DETACH_PORT((PISDN_ADAPTER)a->io, Port); +} +void io_outw(ADAPTER * a, void * adr, word data) +{ + byte __iomem *Port = DIVA_OS_MEM_ATTACH_PORT((PISDN_ADAPTER)a->io); + outppw(Port+4, (word)(unsigned long)adr); + outppw(Port, data); + DIVA_OS_MEM_DETACH_PORT((PISDN_ADAPTER)a->io, Port); +} +void io_out_buffer(ADAPTER * a, void * adr, void * buffer, word len) +{ + byte __iomem *Port = DIVA_OS_MEM_ATTACH_PORT((PISDN_ADAPTER)a->io); + byte* P = (byte*)buffer; + if ((long)adr & 1) { + outppw(Port+4, (word)(unsigned long)adr); + outpp(Port, *P); + P++; + adr = ((byte *) adr) + 1; + len--; + if (!len) { + DIVA_OS_MEM_DETACH_PORT((PISDN_ADAPTER)a->io, Port); + return; + } + } + outppw(Port+4, (word)(unsigned long)adr); + outppw_buffer (Port, P, len+1); + DIVA_OS_MEM_DETACH_PORT((PISDN_ADAPTER)a->io, Port); +} +void io_inc(ADAPTER * a, void * adr) +{ + byte x; + byte __iomem *Port = DIVA_OS_MEM_ATTACH_PORT((PISDN_ADAPTER)a->io); + outppw(Port+4, (word)(unsigned long)adr); + x = inpp(Port); + outppw(Port+4, (word)(unsigned long)adr); + outpp(Port, x+1); + DIVA_OS_MEM_DETACH_PORT((PISDN_ADAPTER)a->io, Port); +} +/*------------------------------------------------------------------*/ +/* OS specific functions related to queuing of entities */ +/*------------------------------------------------------------------*/ +void free_entity(ADAPTER * a, byte e_no) +{ + PISDN_ADAPTER IoAdapter; + diva_os_spin_lock_magic_t irql; + IoAdapter = (PISDN_ADAPTER) a->io; + diva_os_enter_spin_lock (&IoAdapter->data_spin_lock, &irql, "data_free"); + IoAdapter->e_tbl[e_no].e = NULL; + IoAdapter->e_count--; + diva_os_leave_spin_lock (&IoAdapter->data_spin_lock, &irql, "data_free"); +} +void assign_queue(ADAPTER * a, byte e_no, word ref) +{ + PISDN_ADAPTER IoAdapter; + diva_os_spin_lock_magic_t irql; + IoAdapter = (PISDN_ADAPTER) a->io; + diva_os_enter_spin_lock (&IoAdapter->data_spin_lock, &irql, "data_assign"); + IoAdapter->e_tbl[e_no].assign_ref = ref; + IoAdapter->e_tbl[e_no].next = (byte)IoAdapter->assign; + IoAdapter->assign = e_no; + diva_os_leave_spin_lock (&IoAdapter->data_spin_lock, &irql, "data_assign"); +} +byte get_assign(ADAPTER * a, word ref) +{ + PISDN_ADAPTER IoAdapter; + diva_os_spin_lock_magic_t irql; + byte e_no; + IoAdapter = (PISDN_ADAPTER) a->io; + diva_os_enter_spin_lock (&IoAdapter->data_spin_lock, + &irql, + "data_assign_get"); + for(e_no = (byte)IoAdapter->assign; + e_no && IoAdapter->e_tbl[e_no].assign_ref!=ref; + e_no = IoAdapter->e_tbl[e_no].next); + diva_os_leave_spin_lock (&IoAdapter->data_spin_lock, + &irql, + "data_assign_get"); + return e_no; +} +void req_queue(ADAPTER * a, byte e_no) +{ + PISDN_ADAPTER IoAdapter; + diva_os_spin_lock_magic_t irql; + IoAdapter = (PISDN_ADAPTER) a->io; + diva_os_enter_spin_lock (&IoAdapter->data_spin_lock, &irql, "data_req_q"); + IoAdapter->e_tbl[e_no].next = 0; + if(IoAdapter->head) { + IoAdapter->e_tbl[IoAdapter->tail].next = e_no; + IoAdapter->tail = e_no; + } + else { + IoAdapter->head = e_no; + IoAdapter->tail = e_no; + } + diva_os_leave_spin_lock (&IoAdapter->data_spin_lock, &irql, "data_req_q"); +} +byte look_req(ADAPTER * a) +{ + PISDN_ADAPTER IoAdapter; + IoAdapter = (PISDN_ADAPTER) a->io; + return ((byte)IoAdapter->head) ; +} +void next_req(ADAPTER * a) +{ + PISDN_ADAPTER IoAdapter; + diva_os_spin_lock_magic_t irql; + IoAdapter = (PISDN_ADAPTER) a->io; + diva_os_enter_spin_lock (&IoAdapter->data_spin_lock, &irql, "data_req_next"); + IoAdapter->head = IoAdapter->e_tbl[IoAdapter->head].next; + if(!IoAdapter->head) IoAdapter->tail = 0; + diva_os_leave_spin_lock (&IoAdapter->data_spin_lock, &irql, "data_req_next"); +} +/*------------------------------------------------------------------*/ +/* memory map functions */ +/*------------------------------------------------------------------*/ +ENTITY * entity_ptr(ADAPTER * a, byte e_no) +{ + PISDN_ADAPTER IoAdapter; + IoAdapter = (PISDN_ADAPTER) a->io; + return (IoAdapter->e_tbl[e_no].e); +} +void * PTR_X(ADAPTER * a, ENTITY * e) +{ + return ((void *) e->X); +} +void * PTR_R(ADAPTER * a, ENTITY * e) +{ + return ((void *) e->R); +} +void * PTR_P(ADAPTER * a, ENTITY * e, void * P) +{ + return P; +} +void CALLBACK(ADAPTER * a, ENTITY * e) +{ + if ( e && e->callback ) + e->callback (e) ; +} diff --git a/drivers/isdn/hardware/eicon/io.h b/drivers/isdn/hardware/eicon/io.h new file mode 100644 index 000000000000..0c6c650d76bb --- /dev/null +++ b/drivers/isdn/hardware/eicon/io.h @@ -0,0 +1,308 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +#ifndef __DIVA_XDI_COMMON_IO_H_INC__ /* { */ +#define __DIVA_XDI_COMMON_IO_H_INC__ +/* + maximum = 16 adapters + */ +#define DI_MAX_LINKS MAX_ADAPTER +#define ISDN_MAX_NUM_LEN 60 +/* -------------------------------------------------------------------------- + structure for quadro card management (obsolete for + systems that do provide per card load event) + -------------------------------------------------------------------------- */ +typedef struct { + dword Num ; + DEVICE_NAME DeviceName[4] ; + PISDN_ADAPTER QuadroAdapter[4] ; +} ADAPTER_LIST_ENTRY, *PADAPTER_LIST_ENTRY ; +/* -------------------------------------------------------------------------- + Special OS memory support structures + -------------------------------------------------------------------------- */ +#define MAX_MAPPED_ENTRIES 8 +typedef struct { + void * Address; + dword Length; +} ADAPTER_MEMORY ; +/* -------------------------------------------------------------------------- + Configuration of XDI clients carried by XDI + -------------------------------------------------------------------------- */ +#define DIVA_XDI_CAPI_CFG_1_DYNAMIC_L1_ON 0x01 +#define DIVA_XDI_CAPI_CFG_1_GROUP_POPTIMIZATION_ON 0x02 +typedef struct _diva_xdi_capi_cfg { + byte cfg_1; +} diva_xdi_capi_cfg_t; +/* -------------------------------------------------------------------------- + Main data structure kept per adapter + -------------------------------------------------------------------------- */ +struct _ISDN_ADAPTER { + void (* DIRequest)(PISDN_ADAPTER, ENTITY *) ; + int State ; /* from NT4 1.srv, a good idea, but a poor achievment */ + int Initialized ; + int RegisteredWithDidd ; + int Unavailable ; /* callback function possible? */ + int ResourcesClaimed ; + int PnpBiosConfigUsed ; + dword Logging ; + dword features ; + char ProtocolIdString[80] ; + /* + remember mapped memory areas + */ + ADAPTER_MEMORY MappedMemory[MAX_MAPPED_ENTRIES] ; + CARD_PROPERTIES Properties ; + dword cardType ; + dword protocol_id ; /* configured protocol identifier */ + char protocol_name[8] ; /* readable name of protocol */ + dword BusType ; + dword BusNumber ; + dword slotNumber ; + dword slotId ; + dword ControllerNumber ; /* for QUADRO cards only */ + PISDN_ADAPTER MultiMaster ; /* for 4-BRI card only - use MultiMaster or QuadroList */ + PADAPTER_LIST_ENTRY QuadroList ; /* for QUADRO card only */ + PDEVICE_OBJECT DeviceObject ; + dword DeviceId ; + diva_os_adapter_irq_info_t irq_info; + dword volatile IrqCount ; + int trapped ; + dword DspCodeBaseAddr ; + dword MaxDspCodeSize ; + dword downloadAddr ; + dword DspCodeBaseAddrTable[4] ; /* add. for MultiMaster */ + dword MaxDspCodeSizeTable[4] ; /* add. for MultiMaster */ + dword downloadAddrTable[4] ; /* add. for MultiMaster */ + dword MemoryBase ; + dword MemorySize ; + byte __iomem *Address ; + byte __iomem *Config ; + byte __iomem *Control ; + byte __iomem *reset ; + byte __iomem *port ; + byte __iomem *ram ; + byte __iomem *cfg ; + byte __iomem *prom ; + byte __iomem *ctlReg ; + struct pc_maint *pcm ; + diva_os_dependent_devica_name_t os_name; + byte Name[32] ; + dword serialNo ; + dword ANum ; + dword ArchiveType ; /* ARCHIVE_TYPE_NONE ..._SINGLE ..._USGEN ..._MULTI */ + char *ProtocolSuffix ; /* internal protocolfile table */ + char Archive[32] ; + char Protocol[32] ; + char AddDownload[32] ; /* Dsp- or other additional download files */ + char Oad1[ISDN_MAX_NUM_LEN] ; + char Osa1[ISDN_MAX_NUM_LEN] ; + char Oad2[ISDN_MAX_NUM_LEN] ; + char Osa2[ISDN_MAX_NUM_LEN] ; + char Spid1[ISDN_MAX_NUM_LEN] ; + char Spid2[ISDN_MAX_NUM_LEN] ; + byte nosig ; + byte BriLayer2LinkCount ; /* amount of TEI's that adapter will support in P2MP mode */ + dword Channels ; + dword tei ; + dword nt2 ; + dword TerminalCount ; + dword WatchDog ; + dword Permanent ; + dword BChMask ; /* B channel mask for unchannelized modes */ + dword StableL2 ; + dword DidLen ; + dword NoOrderCheck ; + dword ForceLaw; /* VoiceCoding - default:0, a-law: 1, my-law: 2 */ + dword SigFlags ; + dword LowChannel ; + dword NoHscx30 ; + dword ProtVersion ; + dword crc4 ; + dword L1TristateOrQsig ; /* enable Layer 1 Tristate (bit 2)Or Qsig params (bit 0,1)*/ + dword InitialDspInfo ; + dword ModemGuardTone ; + dword ModemMinSpeed ; + dword ModemMaxSpeed ; + dword ModemOptions ; + dword ModemOptions2 ; + dword ModemNegotiationMode ; + dword ModemModulationsMask ; + dword ModemTransmitLevel ; + dword FaxOptions ; + dword FaxMaxSpeed ; + dword Part68LevelLimiter ; + dword UsEktsNumCallApp ; + byte UsEktsFeatAddConf ; + byte UsEktsFeatRemoveConf ; + byte UsEktsFeatCallTransfer ; + byte UsEktsFeatMsgWaiting ; + byte QsigDialect; + byte ForceVoiceMailAlert; + byte DisableAutoSpid; + byte ModemCarrierWaitTimeSec; + byte ModemCarrierLossWaitTimeTenthSec; + byte PiafsLinkTurnaroundInFrames; + byte DiscAfterProgress; + byte AniDniLimiter[3]; + byte TxAttenuation; /* PRI/E1 only: attenuate TX signal */ + word QsigFeatures; + dword GenerateRingtone ; + dword SupplementaryServicesFeatures; + dword R2Dialect; + dword R2CasOptions; + dword FaxV34Options; + dword DisabledDspMask; + dword AdapterTestMask; + dword DspImageLength; + word AlertToIn20mSecTicks; + word ModemEyeSetup; + byte R2CtryLength; + byte CCBSRelTimer; + byte *PcCfgBufferFile;/* flexible parameter via file */ + byte *PcCfgBuffer ; /* flexible parameter via multistring */ + diva_os_dump_file_t dump_file; /* dump memory to file at lowest irq level */ + diva_os_board_trace_t board_trace ; /* traces from the board */ + diva_os_spin_lock_t isr_spin_lock; + diva_os_spin_lock_t data_spin_lock; + diva_os_soft_isr_t req_soft_isr; + diva_os_soft_isr_t isr_soft_isr; + diva_os_atomic_t in_dpc; + PBUFFER RBuffer; /* Copy of receive lookahead buffer */ + word e_max; + word e_count; + E_INFO *e_tbl; + word assign; /* list of pending ASSIGNs */ + word head; /* head of request queue */ + word tail; /* tail of request queue */ + ADAPTER a ; /* not a separate structure */ + void (* out)(ADAPTER * a) ; + byte (* dpc)(ADAPTER * a) ; + byte (* tst_irq)(ADAPTER * a) ; + void (* clr_irq)(ADAPTER * a) ; + int (* load)(PISDN_ADAPTER) ; + int (* mapmem)(PISDN_ADAPTER) ; + int (* chkIrq)(PISDN_ADAPTER) ; + void (* disIrq)(PISDN_ADAPTER) ; + void (* start)(PISDN_ADAPTER) ; + void (* stop)(PISDN_ADAPTER) ; + void (* rstFnc)(PISDN_ADAPTER) ; + void (* trapFnc)(PISDN_ADAPTER) ; + dword (* DetectDsps)(PISDN_ADAPTER) ; + void (* os_trap_nfy_Fnc)(PISDN_ADAPTER, dword) ; + diva_os_isr_callback_t diva_isr_handler; + dword sdram_bar; /* must be 32 bit */ + dword fpga_features; + volatile int pcm_pending; + volatile void * pcm_data; + diva_xdi_capi_cfg_t capi_cfg; + dword tasks; + void *dma_map; + int (*DivaAdapterTestProc)(PISDN_ADAPTER); + void *AdapterTestMemoryStart; + dword AdapterTestMemoryLength; + const byte* cfg_lib_memory_init; + dword cfg_lib_memory_init_length; +}; +/* --------------------------------------------------------------------- + Entity table + --------------------------------------------------------------------- */ +struct e_info_s { + ENTITY * e; + byte next; /* chaining index */ + word assign_ref; /* assign reference */ +}; +/* --------------------------------------------------------------------- + S-cards shared ram structure for loading + --------------------------------------------------------------------- */ +struct s_load { + byte ctrl; + byte card; + byte msize; + byte fill0; + word ebit; + word elocl; + word eloch; + byte reserved[20]; + word signature; + byte fill[224]; + byte b[256]; +}; +#define PR_RAM ((struct pr_ram *)0) +#define RAM ((struct dual *)0) +/* --------------------------------------------------------------------- + platform specific conversions + --------------------------------------------------------------------- */ +extern void * PTR_P(ADAPTER * a, ENTITY * e, void * P); +extern void * PTR_X(ADAPTER * a, ENTITY * e); +extern void * PTR_R(ADAPTER * a, ENTITY * e); +extern void CALLBACK(ADAPTER * a, ENTITY * e); +extern void set_ram(void * * adr_ptr); +/* --------------------------------------------------------------------- + ram access functions for io mapped cards + --------------------------------------------------------------------- */ +byte io_in(ADAPTER * a, void * adr); +word io_inw(ADAPTER * a, void * adr); +void io_in_buffer(ADAPTER * a, void * adr, void * P, word length); +void io_look_ahead(ADAPTER * a, PBUFFER * RBuffer, ENTITY * e); +void io_out(ADAPTER * a, void * adr, byte data); +void io_outw(ADAPTER * a, void * adr, word data); +void io_out_buffer(ADAPTER * a, void * adr, void * P, word length); +void io_inc(ADAPTER * a, void * adr); +void bri_in_buffer (PISDN_ADAPTER IoAdapter, dword Pos, + void *Buf, dword Len); +int bri_out_buffer (PISDN_ADAPTER IoAdapter, dword Pos, + void *Buf, dword Len, int Verify); +/* --------------------------------------------------------------------- + ram access functions for memory mapped cards + --------------------------------------------------------------------- */ +byte mem_in(ADAPTER * a, void * adr); +word mem_inw(ADAPTER * a, void * adr); +void mem_in_buffer(ADAPTER * a, void * adr, void * P, word length); +void mem_look_ahead(ADAPTER * a, PBUFFER * RBuffer, ENTITY * e); +void mem_out(ADAPTER * a, void * adr, byte data); +void mem_outw(ADAPTER * a, void * adr, word data); +void mem_out_buffer(ADAPTER * a, void * adr, void * P, word length); +void mem_inc(ADAPTER * a, void * adr); +void mem_in_dw (ADAPTER *a, void *addr, dword* data, int dwords); +void mem_out_dw (ADAPTER *a, void *addr, const dword* data, int dwords); +/* --------------------------------------------------------------------- + functions exported by io.c + --------------------------------------------------------------------- */ +extern IDI_CALL Requests[MAX_ADAPTER] ; +extern void DIDpcRoutine (struct _diva_os_soft_isr* psoft_isr, + void* context); +extern void request (PISDN_ADAPTER, ENTITY *) ; +/* --------------------------------------------------------------------- + trapFn helpers, used to recover debug trace from dead card + --------------------------------------------------------------------- */ +typedef struct { + word *buf ; + word cnt ; + word out ; +} Xdesc ; +extern void dump_trap_frame (PISDN_ADAPTER IoAdapter, byte __iomem *exception) ; +extern void dump_xlog_buffer (PISDN_ADAPTER IoAdapter, Xdesc *xlogDesc) ; +/* --------------------------------------------------------------------- */ +#endif /* } __DIVA_XDI_COMMON_IO_H_INC__ */ diff --git a/drivers/isdn/hardware/eicon/istream.c b/drivers/isdn/hardware/eicon/istream.c new file mode 100644 index 000000000000..23139668d9b1 --- /dev/null +++ b/drivers/isdn/hardware/eicon/istream.c @@ -0,0 +1,226 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +#include "platform.h" +#if defined(DIVA_ISTREAM) /* { */ +#include "pc.h" +#include "pr_pc.h" +#include "di_defs.h" +#include "divasync.h" +#include "di.h" +#if !defined USE_EXTENDED_DEBUGS + #include "dimaint.h" +#else + #define dprintf +#endif +#include "dfifo.h" +int diva_istream_write (void* context, + int Id, + void* data, + int length, + int final, + byte usr1, + byte usr2); +int diva_istream_read (void* context, + int Id, + void* data, + int max_length, + int* final, + byte* usr1, + byte* usr2); +/* ------------------------------------------------------------------- + Does provide iStream interface to the client + ------------------------------------------------------------------- */ +void diva_xdi_provide_istream_info (ADAPTER* a, + diva_xdi_stream_interface_t* pi) { + pi->provided_service = 0; +} +/* ------------------------------------------------------------------ + Does write the data from caller's buffer to the card's + stream interface. + If synchronous service was requested, then function + does return amount of data written to stream. + 'final' does indicate that pice of data to be written is + final part of frame (necessary only by structured datatransfer) + return 0 if zero lengh packet was written + return -1 if stream is full + ------------------------------------------------------------------ */ +int diva_istream_write (void* context, + int Id, + void* data, + int length, + int final, + byte usr1, + byte usr2) { + ADAPTER* a = (ADAPTER*)context; + int written = 0, to_write = -1; + char tmp[4]; + byte* data_ptr = (byte*)data; + for (;;) { + a->ram_in_dw (a, +#ifdef PLATFORM_GT_32BIT + ULongToPtr(a->tx_stream[Id] + a->tx_pos[Id]), +#else + (void*)(a->tx_stream[Id] + a->tx_pos[Id]), +#endif + (dword*)&tmp[0], + 1); + if (tmp[0] & DIVA_DFIFO_READY) { /* No free blocks more */ + if (to_write < 0) + return (-1); /* was not able to write */ + break; /* only part of message was written */ + } + to_write = MIN(length, DIVA_DFIFO_DATA_SZ); + if (to_write) { + a->ram_out_buffer (a, +#ifdef PLATFORM_GT_32BIT + ULongToPtr(a->tx_stream[Id] + a->tx_pos[Id]+4), +#else + (void*)(a->tx_stream[Id] + a->tx_pos[Id] + 4), +#endif + data_ptr, + (word)to_write); + length -= to_write; + written += to_write; + data_ptr += to_write; + } + tmp[1] = (char)to_write; + tmp[0] = (tmp[0] & DIVA_DFIFO_WRAP) | + DIVA_DFIFO_READY | + ((!length && final) ? DIVA_DFIFO_LAST : 0); + if (tmp[0] & DIVA_DFIFO_LAST) { + tmp[2] = usr1; + tmp[3] = usr2; + } + a->ram_out_dw (a, +#ifdef PLATFORM_GT_32BIT + ULongToPtr(a->tx_stream[Id] + a->tx_pos[Id]), +#else + (void*)(a->tx_stream[Id] + a->tx_pos[Id]), +#endif + (dword*)&tmp[0], + 1); + if (tmp[0] & DIVA_DFIFO_WRAP) { + a->tx_pos[Id] = 0; + } else { + a->tx_pos[Id] += DIVA_DFIFO_STEP; + } + if (!length) { + break; + } + } + return (written); +} +/* ------------------------------------------------------------------- + In case of SYNCRONOUS service: + Does write data from stream in caller's buffer. + Does return amount of data written to buffer + Final flag is set on return if last part of structured frame + was received + return 0 if zero packet was received + return -1 if stream is empty + return -2 if read buffer does not profide sufficient space + to accommodate entire segment + max_length should be at least 68 bytes + ------------------------------------------------------------------- */ +int diva_istream_read (void* context, + int Id, + void* data, + int max_length, + int* final, + byte* usr1, + byte* usr2) { + ADAPTER* a = (ADAPTER*)context; + int read = 0, to_read = -1; + char tmp[4]; + byte* data_ptr = (byte*)data; + *final = 0; + for (;;) { + a->ram_in_dw (a, +#ifdef PLATFORM_GT_32BIT + ULongToPtr(a->rx_stream[Id] + a->rx_pos[Id]), +#else + (void*)(a->rx_stream[Id] + a->rx_pos[Id]), +#endif + (dword*)&tmp[0], + 1); + if (tmp[1] > max_length) { + if (to_read < 0) + return (-2); /* was not able to read */ + break; + } + if (!(tmp[0] & DIVA_DFIFO_READY)) { + if (to_read < 0) + return (-1); /* was not able to read */ + break; + } + to_read = MIN(max_length, tmp[1]); + if (to_read) { + a->ram_in_buffer(a, +#ifdef PLATFORM_GT_32BIT + ULongToPtr(a->rx_stream[Id] + a->rx_pos[Id] + 4), +#else + (void*)(a->rx_stream[Id] + a->rx_pos[Id] + 4), +#endif + data_ptr, + (word)to_read); + max_length -= to_read; + read += to_read; + data_ptr += to_read; + } + if (tmp[0] & DIVA_DFIFO_LAST) { + *final = 1; + } + tmp[0] &= DIVA_DFIFO_WRAP; + a->ram_out_dw(a, +#ifdef PLATFORM_GT_32BIT + ULongToPtr(a->rx_stream[Id] + a->rx_pos[Id]), +#else + (void*)(a->rx_stream[Id] + a->rx_pos[Id]), +#endif + (dword*)&tmp[0], + 1); + if (tmp[0] & DIVA_DFIFO_WRAP) { + a->rx_pos[Id] = 0; + } else { + a->rx_pos[Id] += DIVA_DFIFO_STEP; + } + if (*final) { + if (usr1) + *usr1 = tmp[2]; + if (usr2) + *usr2 = tmp[3]; + break; + } + } + return (read); +} +/* --------------------------------------------------------------------- + Does check if one of streams had caused interrupt and does + wake up corresponding application + --------------------------------------------------------------------- */ +void pr_stream (ADAPTER * a) { +} +#endif /* } */ diff --git a/drivers/isdn/hardware/eicon/kst_ifc.h b/drivers/isdn/hardware/eicon/kst_ifc.h new file mode 100644 index 000000000000..203189a010c2 --- /dev/null +++ b/drivers/isdn/hardware/eicon/kst_ifc.h @@ -0,0 +1,336 @@ +/* + * + Copyright (c) Eicon Networks, 2000. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 1.9 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +#ifndef __DIVA_EICON_TRACE_API__ +#define __DIVA_EICON_TRACE_API__ + +#define DIVA_TRACE_LINE_TYPE_LEN 64 +#define DIVA_TRACE_IE_LEN 64 +#define DIVA_TRACE_FAX_PRMS_LEN 128 + +typedef struct _diva_trace_ie { + byte length; + byte data[DIVA_TRACE_IE_LEN]; +} diva_trace_ie_t; + +/* + Structure used to represent "State\\BX\\Modem" directory + to user. + */ +typedef struct _diva_trace_modem_state { + dword ChannelNumber; + + dword Event; + + dword Norm; + + dword Options; /* Options received from Application */ + + dword TxSpeed; + dword RxSpeed; + + dword RoundtripMsec; + + dword SymbolRate; + + int RxLeveldBm; + int EchoLeveldBm; + + dword SNRdb; + dword MAE; + + dword LocalRetrains; + dword RemoteRetrains; + dword LocalResyncs; + dword RemoteResyncs; + + dword DiscReason; + +} diva_trace_modem_state_t; + +/* + Representation of "State\\BX\\FAX" directory + */ +typedef struct _diva_trace_fax_state { + dword ChannelNumber; + dword Event; + dword Page_Counter; + dword Features; + char Station_ID[DIVA_TRACE_FAX_PRMS_LEN]; + char Subaddress[DIVA_TRACE_FAX_PRMS_LEN]; + char Password[DIVA_TRACE_FAX_PRMS_LEN]; + dword Speed; + dword Resolution; + dword Paper_Width; + dword Paper_Length; + dword Scanline_Time; + dword Disc_Reason; + dword dummy; +} diva_trace_fax_state_t; + +/* + Structure used to represent Interface State in the abstract + and interface/D-channel protocol independent form. + */ +typedef struct _diva_trace_interface_state { + char Layer1[DIVA_TRACE_LINE_TYPE_LEN]; + char Layer2[DIVA_TRACE_LINE_TYPE_LEN]; +} diva_trace_interface_state_t; + +typedef struct _diva_incoming_call_statistics { + dword Calls; + dword Connected; + dword User_Busy; + dword Call_Rejected; + dword Wrong_Number; + dword Incompatible_Dst; + dword Out_of_Order; + dword Ignored; +} diva_incoming_call_statistics_t; + +typedef struct _diva_outgoing_call_statistics { + dword Calls; + dword Connected; + dword User_Busy; + dword No_Answer; + dword Wrong_Number; + dword Call_Rejected; + dword Other_Failures; +} diva_outgoing_call_statistics_t; + +typedef struct _diva_modem_call_statistics { + dword Disc_Normal; + dword Disc_Unspecified; + dword Disc_Busy_Tone; + dword Disc_Congestion; + dword Disc_Carr_Wait; + dword Disc_Trn_Timeout; + dword Disc_Incompat; + dword Disc_Frame_Rej; + dword Disc_V42bis; +} diva_modem_call_statistics_t; + +typedef struct _diva_fax_call_statistics { + dword Disc_Normal; + dword Disc_Not_Ident; + dword Disc_No_Response; + dword Disc_Retries; + dword Disc_Unexp_Msg; + dword Disc_No_Polling; + dword Disc_Training; + dword Disc_Unexpected; + dword Disc_Application; + dword Disc_Incompat; + dword Disc_No_Command; + dword Disc_Long_Msg; + dword Disc_Supervisor; + dword Disc_SUB_SEP_PWD; + dword Disc_Invalid_Msg; + dword Disc_Page_Coding; + dword Disc_App_Timeout; + dword Disc_Unspecified; +} diva_fax_call_statistics_t; + +typedef struct _diva_prot_statistics { + dword X_Frames; + dword X_Bytes; + dword X_Errors; + dword R_Frames; + dword R_Bytes; + dword R_Errors; +} diva_prot_statistics_t; + +typedef struct _diva_ifc_statistics { + diva_incoming_call_statistics_t inc; + diva_outgoing_call_statistics_t outg; + diva_modem_call_statistics_t mdm; + diva_fax_call_statistics_t fax; + diva_prot_statistics_t b1; + diva_prot_statistics_t b2; + diva_prot_statistics_t d1; + diva_prot_statistics_t d2; +} diva_ifc_statistics_t; + +/* + Structure used to represent "State\\BX" directory + to user. + */ +typedef struct _diva_trace_line_state { + dword ChannelNumber; + + char Line[DIVA_TRACE_LINE_TYPE_LEN]; + + char Framing[DIVA_TRACE_LINE_TYPE_LEN]; + + char Layer2[DIVA_TRACE_LINE_TYPE_LEN]; + char Layer3[DIVA_TRACE_LINE_TYPE_LEN]; + + char RemoteAddress[DIVA_TRACE_LINE_TYPE_LEN]; + char RemoteSubAddress[DIVA_TRACE_LINE_TYPE_LEN]; + + char LocalAddress[DIVA_TRACE_LINE_TYPE_LEN]; + char LocalSubAddress[DIVA_TRACE_LINE_TYPE_LEN]; + + diva_trace_ie_t call_BC; + diva_trace_ie_t call_HLC; + diva_trace_ie_t call_LLC; + + dword Charges; + + dword CallReference; + + dword LastDisconnecCause; + + char UserID[DIVA_TRACE_LINE_TYPE_LEN]; + + diva_trace_modem_state_t modem; + diva_trace_fax_state_t fax; + + diva_trace_interface_state_t* pInterface; + + diva_ifc_statistics_t* pInterfaceStat; + +} diva_trace_line_state_t; + +#define DIVA_SUPER_TRACE_NOTIFY_LINE_CHANGE ('l') +#define DIVA_SUPER_TRACE_NOTIFY_MODEM_CHANGE ('m') +#define DIVA_SUPER_TRACE_NOTIFY_FAX_CHANGE ('f') +#define DIVA_SUPER_TRACE_INTERFACE_CHANGE ('i') +#define DIVA_SUPER_TRACE_NOTIFY_STAT_CHANGE ('s') +#define DIVA_SUPER_TRACE_NOTIFY_MDM_STAT_CHANGE ('M') +#define DIVA_SUPER_TRACE_NOTIFY_FAX_STAT_CHANGE ('F') + +struct _diva_strace_library_interface; +typedef void (*diva_trace_channel_state_change_proc_t)(void* user_context, + struct _diva_strace_library_interface* hLib, + int Adapter, + diva_trace_line_state_t* channel, int notify_subject); +typedef void (*diva_trace_channel_trace_proc_t)(void* user_context, + struct _diva_strace_library_interface* hLib, + int Adapter, void* xlog_buffer, int length); +typedef void (*diva_trace_error_proc_t)(void* user_context, + struct _diva_strace_library_interface* hLib, + int Adapter, + int error, const char* file, int line); + +/* + This structure creates interface from user to library + */ +typedef struct _diva_trace_library_user_interface { + void* user_context; + diva_trace_channel_state_change_proc_t notify_proc; + diva_trace_channel_trace_proc_t trace_proc; + diva_trace_error_proc_t error_notify_proc; +} diva_trace_library_user_interface_t; + +/* + Interface from Library to User + */ +typedef int (*DivaSTraceLibraryStart_proc_t)(void* hLib); +typedef int (*DivaSTraceLibraryFinit_proc_t)(void* hLib); +typedef int (*DivaSTraceMessageInput_proc_t)(void* hLib); +typedef void* (*DivaSTraceGetHandle_proc_t)(void* hLib); + +/* + Turn Audio Tap trace on/off + Channel should be in the range 1 ... Number of Channels + */ +typedef int (*DivaSTraceSetAudioTap_proc_t)(void* hLib, int Channel, int on); + +/* + Turn B-channel trace on/off + Channel should be in the range 1 ... Number of Channels + */ +typedef int (*DivaSTraceSetBChannel_proc_t)(void* hLib, int Channel, int on); + +/* + Turn D-channel (Layer1/Layer2/Layer3) trace on/off + Layer1 - All D-channel frames received/sent over the interface + inclusive Layer 2 headers, Layer 2 frames and TEI management frames + Layer2 - Events from LAPD protocol instance with SAPI of signalling protocol + Layer3 - All D-channel frames addressed to assigned to the card TEI and + SAPI of signalling protocol, and signalling protocol events. + */ +typedef int (*DivaSTraceSetDChannel_proc_t)(void* hLib, int on); + +/* + Get overall card statistics + */ +typedef int (*DivaSTraceGetOutgoingCallStatistics_proc_t)(void* hLib); +typedef int (*DivaSTraceGetIncomingCallStatistics_proc_t)(void* hLib); +typedef int (*DivaSTraceGetModemStatistics_proc_t)(void* hLib); +typedef int (*DivaSTraceGetFaxStatistics_proc_t)(void* hLib); +typedef int (*DivaSTraceGetBLayer1Statistics_proc_t)(void* hLib); +typedef int (*DivaSTraceGetBLayer2Statistics_proc_t)(void* hLib); +typedef int (*DivaSTraceGetDLayer1Statistics_proc_t)(void* hLib); +typedef int (*DivaSTraceGetDLayer2Statistics_proc_t)(void* hLib); + +/* + Call control + */ +typedef int (*DivaSTraceClearCall_proc_t)(void* hLib, int Channel); + +typedef struct _diva_strace_library_interface { + void* hLib; + DivaSTraceLibraryStart_proc_t DivaSTraceLibraryStart; + DivaSTraceLibraryStart_proc_t DivaSTraceLibraryStop; + DivaSTraceLibraryFinit_proc_t DivaSTraceLibraryFinit; + DivaSTraceMessageInput_proc_t DivaSTraceMessageInput; + DivaSTraceGetHandle_proc_t DivaSTraceGetHandle; + DivaSTraceSetAudioTap_proc_t DivaSTraceSetAudioTap; + DivaSTraceSetBChannel_proc_t DivaSTraceSetBChannel; + DivaSTraceSetDChannel_proc_t DivaSTraceSetDChannel; + DivaSTraceSetDChannel_proc_t DivaSTraceSetInfo; + DivaSTraceGetOutgoingCallStatistics_proc_t \ + DivaSTraceGetOutgoingCallStatistics; + DivaSTraceGetIncomingCallStatistics_proc_t \ + DivaSTraceGetIncomingCallStatistics; + DivaSTraceGetModemStatistics_proc_t \ + DivaSTraceGetModemStatistics; + DivaSTraceGetFaxStatistics_proc_t \ + DivaSTraceGetFaxStatistics; + DivaSTraceGetBLayer1Statistics_proc_t \ + DivaSTraceGetBLayer1Statistics; + DivaSTraceGetBLayer2Statistics_proc_t \ + DivaSTraceGetBLayer2Statistics; + DivaSTraceGetDLayer1Statistics_proc_t \ + DivaSTraceGetDLayer1Statistics; + DivaSTraceGetDLayer2Statistics_proc_t \ + DivaSTraceGetDLayer2Statistics; + DivaSTraceClearCall_proc_t DivaSTraceClearCall; +} diva_strace_library_interface_t; + +/* + Create and return Library interface + */ +diva_strace_library_interface_t* DivaSTraceLibraryCreateInstance (int Adapter, + const diva_trace_library_user_interface_t* user_proc, + byte* pmem); +dword DivaSTraceGetMemotyRequirement (int channels); + +#define DIVA_MAX_ADAPTERS 64 +#define DIVA_MAX_LINES 32 + +#endif + diff --git a/drivers/isdn/hardware/eicon/main_if.h b/drivers/isdn/hardware/eicon/main_if.h new file mode 100644 index 000000000000..0ea339afd424 --- /dev/null +++ b/drivers/isdn/hardware/eicon/main_if.h @@ -0,0 +1,50 @@ +/* + * + Copyright (c) Eicon Technology Corporation, 2000. + * + This source file is supplied for the use with Eicon + Technology Corporation's range of DIVA Server Adapters. + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +/*------------------------------------------------------------------*/ +/* file: main_if.h */ +/*------------------------------------------------------------------*/ +# ifndef MAIN_IF___H +# define MAIN_IF___H + +# include "debug_if.h" + +void DI_lock (void) ; +void DI_unlock (void) ; + +#ifdef NOT_YET_NEEDED +void DI_nttime (LARGE_INTEGER *NTtime) ; +void DI_ntlcltime(LARGE_INTEGER *NTtime, LARGE_INTEGER *lclNTtime) ; +void DI_nttimefields(LARGE_INTEGER *NTtime, TIME_FIELDS *TimeFields); +unsigned long DI_wintime(LARGE_INTEGER *NTtime) ; + +unsigned short DiInsertProcessorNumber (int type) ; +void DiProcessEventLog (unsigned short id, unsigned long msgID, va_list ap); + +void StartIoctlTimer (void (*Handler)(void), unsigned long msec) ; +void StopIoctlTimer (void) ; +void UnpendIoctl (DbgRequest *pDbgReq) ; +#endif + +void add_to_q(int, char* , unsigned int); +# endif /* MAIN_IF___H */ + diff --git a/drivers/isdn/hardware/eicon/maintidi.c b/drivers/isdn/hardware/eicon/maintidi.c new file mode 100644 index 000000000000..23960cb6eaab --- /dev/null +++ b/drivers/isdn/hardware/eicon/maintidi.c @@ -0,0 +1,2194 @@ +/* + * + Copyright (c) Eicon Networks, 2000. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 1.9 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +#include "platform.h" +#include "kst_ifc.h" +#include "di_defs.h" +#include "maintidi.h" +#include "pc.h" +#include "man_defs.h" + + +extern void diva_mnt_internal_dprintf (dword drv_id, dword type, char* p, ...); + +#define MODEM_PARSE_ENTRIES 16 /* amount of variables of interest */ +#define FAX_PARSE_ENTRIES 12 /* amount of variables of interest */ +#define LINE_PARSE_ENTRIES 15 /* amount of variables of interest */ +#define STAT_PARSE_ENTRIES 70 /* amount of variables of interest */ + +/* + LOCAL FUNCTIONS + */ +static int DivaSTraceLibraryStart (void* hLib); +static int DivaSTraceLibraryStop (void* hLib); +static int SuperTraceLibraryFinit (void* hLib); +static void* SuperTraceGetHandle (void* hLib); +static int SuperTraceMessageInput (void* hLib); +static int SuperTraceSetAudioTap (void* hLib, int Channel, int on); +static int SuperTraceSetBChannel (void* hLib, int Channel, int on); +static int SuperTraceSetDChannel (void* hLib, int on); +static int SuperTraceSetInfo (void* hLib, int on); +static int SuperTraceClearCall (void* hLib, int Channel); +static int SuperTraceGetOutgoingCallStatistics (void* hLib); +static int SuperTraceGetIncomingCallStatistics (void* hLib); +static int SuperTraceGetModemStatistics (void* hLib); +static int SuperTraceGetFaxStatistics (void* hLib); +static int SuperTraceGetBLayer1Statistics (void* hLib); +static int SuperTraceGetBLayer2Statistics (void* hLib); +static int SuperTraceGetDLayer1Statistics (void* hLib); +static int SuperTraceGetDLayer2Statistics (void* hLib); + +/* + LOCAL FUNCTIONS + */ +static int ScheduleNextTraceRequest (diva_strace_context_t* pLib); +static int process_idi_event (diva_strace_context_t* pLib, + diva_man_var_header_t* pVar); +static int process_idi_info (diva_strace_context_t* pLib, + diva_man_var_header_t* pVar); +static int diva_modem_event (diva_strace_context_t* pLib, int Channel); +static int diva_fax_event (diva_strace_context_t* pLib, int Channel); +static int diva_line_event (diva_strace_context_t* pLib, int Channel); +static int diva_modem_info (diva_strace_context_t* pLib, + int Channel, + diva_man_var_header_t* pVar); +static int diva_fax_info (diva_strace_context_t* pLib, + int Channel, + diva_man_var_header_t* pVar); +static int diva_line_info (diva_strace_context_t* pLib, + int Channel, + diva_man_var_header_t* pVar); +static int diva_ifc_statistics (diva_strace_context_t* pLib, + diva_man_var_header_t* pVar); +static diva_man_var_header_t* get_next_var (diva_man_var_header_t* pVar); +static diva_man_var_header_t* find_var (diva_man_var_header_t* pVar, + const char* name); +static int diva_strace_read_int (diva_man_var_header_t* pVar, int* var); +static int diva_strace_read_uint (diva_man_var_header_t* pVar, dword* var); +static int diva_strace_read_asz (diva_man_var_header_t* pVar, char* var); +static int diva_strace_read_asc (diva_man_var_header_t* pVar, char* var); +static int diva_strace_read_ie (diva_man_var_header_t* pVar, + diva_trace_ie_t* var); +static void diva_create_parse_table (diva_strace_context_t* pLib); +static void diva_trace_error (diva_strace_context_t* pLib, + int error, const char* file, int line); +static void diva_trace_notify_user (diva_strace_context_t* pLib, + int Channel, + int notify_subject); +static int diva_trace_read_variable (diva_man_var_header_t* pVar, + void* variable); + +/* + Initialize the library and return context + of the created trace object that will represent + the IDI adapter. + Return 0 on error. + */ +diva_strace_library_interface_t* DivaSTraceLibraryCreateInstance (int Adapter, + const diva_trace_library_user_interface_t* user_proc, + byte* pmem) { + diva_strace_context_t* pLib = (diva_strace_context_t*)pmem; + int i; + + if (!pLib) { + return NULL; + } + + pmem += sizeof(*pLib); + memset(pLib, 0x00, sizeof(*pLib)); + + pLib->Adapter = Adapter; + + /* + Set up Library Interface + */ + pLib->instance.hLib = pLib; + pLib->instance.DivaSTraceLibraryStart = DivaSTraceLibraryStart; + pLib->instance.DivaSTraceLibraryStop = DivaSTraceLibraryStop; + pLib->instance.DivaSTraceLibraryFinit = SuperTraceLibraryFinit; + pLib->instance.DivaSTraceMessageInput = SuperTraceMessageInput; + pLib->instance.DivaSTraceGetHandle = SuperTraceGetHandle; + pLib->instance.DivaSTraceSetAudioTap = SuperTraceSetAudioTap; + pLib->instance.DivaSTraceSetBChannel = SuperTraceSetBChannel; + pLib->instance.DivaSTraceSetDChannel = SuperTraceSetDChannel; + pLib->instance.DivaSTraceSetInfo = SuperTraceSetInfo; + pLib->instance.DivaSTraceGetOutgoingCallStatistics = \ + SuperTraceGetOutgoingCallStatistics; + pLib->instance.DivaSTraceGetIncomingCallStatistics = \ + SuperTraceGetIncomingCallStatistics; + pLib->instance.DivaSTraceGetModemStatistics = \ + SuperTraceGetModemStatistics; + pLib->instance.DivaSTraceGetFaxStatistics = \ + SuperTraceGetFaxStatistics; + pLib->instance.DivaSTraceGetBLayer1Statistics = \ + SuperTraceGetBLayer1Statistics; + pLib->instance.DivaSTraceGetBLayer2Statistics = \ + SuperTraceGetBLayer2Statistics; + pLib->instance.DivaSTraceGetDLayer1Statistics = \ + SuperTraceGetDLayer1Statistics; + pLib->instance.DivaSTraceGetDLayer2Statistics = \ + SuperTraceGetDLayer2Statistics; + pLib->instance.DivaSTraceClearCall = SuperTraceClearCall; + + + if (user_proc) { + pLib->user_proc_table.user_context = user_proc->user_context; + pLib->user_proc_table.notify_proc = user_proc->notify_proc; + pLib->user_proc_table.trace_proc = user_proc->trace_proc; + pLib->user_proc_table.error_notify_proc = user_proc->error_notify_proc; + } + + if (!(pLib->hAdapter = SuperTraceOpenAdapter (Adapter))) { + diva_mnt_internal_dprintf (0, DLI_ERR, "Can not open XDI adapter"); + return NULL; + } + pLib->Channels = SuperTraceGetNumberOfChannels (pLib->hAdapter); + + /* + Calculate amount of parte table entites necessary to translate + information from all events of onterest + */ + pLib->parse_entries = (MODEM_PARSE_ENTRIES + FAX_PARSE_ENTRIES + \ + STAT_PARSE_ENTRIES + \ + LINE_PARSE_ENTRIES + 1) * pLib->Channels; + pLib->parse_table = (diva_strace_path2action_t*)pmem; + + for (i = 0; i < 30; i++) { + pLib->lines[i].pInterface = &pLib->Interface; + pLib->lines[i].pInterfaceStat = &pLib->InterfaceStat; + } + + pLib->e.R = &pLib->RData; + + pLib->req_busy = 1; + pLib->rc_ok = ASSIGN_OK; + + diva_create_parse_table (pLib); + + return ((diva_strace_library_interface_t*)pLib); +} + +static int DivaSTraceLibraryStart (void* hLib) { + diva_strace_context_t* pLib = (diva_strace_context_t*)hLib; + + return (SuperTraceASSIGN (pLib->hAdapter, pLib->buffer)); +} + +/* + Return (-1) on error + Return (0) if was initiated or pending + Return (1) if removal is complete + */ +static int DivaSTraceLibraryStop (void* hLib) { + diva_strace_context_t* pLib = (diva_strace_context_t*)hLib; + + if (!pLib->e.Id) { /* Was never started/assigned */ + return (1); + } + + switch (pLib->removal_state) { + case 0: + pLib->removal_state = 1; + ScheduleNextTraceRequest(pLib); + break; + + case 3: + return (1); + } + + return (0); +} + +static int SuperTraceLibraryFinit (void* hLib) { + diva_strace_context_t* pLib = (diva_strace_context_t*)hLib; + if (pLib) { + if (pLib->hAdapter) { + SuperTraceCloseAdapter (pLib->hAdapter); + } + return (0); + } + return (-1); +} + +static void* SuperTraceGetHandle (void* hLib) { + diva_strace_context_t* pLib = (diva_strace_context_t*)hLib; + + return (&pLib->e); +} + +/* + After library handle object is gone in signaled state + this function should be called and will pick up incoming + IDI messages (return codes and indications). + */ +static int SuperTraceMessageInput (void* hLib) { + diva_strace_context_t* pLib = (diva_strace_context_t*)hLib; + int ret = 0; + byte Rc, Ind; + + if (pLib->e.complete == 255) { + /* + Process return code + */ + pLib->req_busy = 0; + Rc = pLib->e.Rc; + pLib->e.Rc = 0; + + if (pLib->removal_state == 2) { + pLib->removal_state = 3; + return (0); + } + + if (Rc != pLib->rc_ok) { + int ignore = 0; + /* + Auto-detect amount of events/channels and features + */ + if (pLib->general_b_ch_event == 1) { + pLib->general_b_ch_event = 2; + ignore = 1; + } else if (pLib->general_fax_event == 1) { + pLib->general_fax_event = 2; + ignore = 1; + } else if (pLib->general_mdm_event == 1) { + pLib->general_mdm_event = 2; + ignore = 1; + } else if ((pLib->ChannelsTraceActive < pLib->Channels) && pLib->ChannelsTraceActive) { + pLib->ChannelsTraceActive = pLib->Channels; + ignore = 1; + } else if (pLib->ModemTraceActive < pLib->Channels) { + pLib->ModemTraceActive = pLib->Channels; + ignore = 1; + } else if (pLib->FaxTraceActive < pLib->Channels) { + pLib->FaxTraceActive = pLib->Channels; + ignore = 1; + } else if (pLib->audio_trace_init == 2) { + ignore = 1; + pLib->audio_trace_init = 1; + } else if (pLib->eye_pattern_pending) { + pLib->eye_pattern_pending = 0; + ignore = 1; + } else if (pLib->audio_tap_pending) { + pLib->audio_tap_pending = 0; + ignore = 1; + } + + if (!ignore) { + return (-1); /* request failed */ + } + } else { + if (pLib->general_b_ch_event == 1) { + pLib->ChannelsTraceActive = pLib->Channels; + pLib->general_b_ch_event = 2; + } else if (pLib->general_fax_event == 1) { + pLib->general_fax_event = 2; + pLib->FaxTraceActive = pLib->Channels; + } else if (pLib->general_mdm_event == 1) { + pLib->general_mdm_event = 2; + pLib->ModemTraceActive = pLib->Channels; + } + } + if (pLib->audio_trace_init == 2) { + pLib->audio_trace_init = 1; + } + pLib->rc_ok = 0xff; /* default OK after assign was done */ + if ((ret = ScheduleNextTraceRequest(pLib))) { + return (-1); + } + } else { + /* + Process indication + Always 'RNR' indication if return code is pending + */ + Ind = pLib->e.Ind; + pLib->e.Ind = 0; + if (pLib->removal_state) { + pLib->e.RNum = 0; + pLib->e.RNR = 2; + } else if (pLib->req_busy) { + pLib->e.RNum = 0; + pLib->e.RNR = 1; + } else { + if (pLib->e.complete != 0x02) { + /* + Look-ahead call, set up buffers + */ + pLib->e.RNum = 1; + pLib->e.R->P = (byte*)&pLib->buffer[0]; + pLib->e.R->PLength = (word)(sizeof(pLib->buffer) - 1); + + } else { + /* + Indication reception complete, process it now + */ + byte* p = (byte*)&pLib->buffer[0]; + pLib->buffer[pLib->e.R->PLength] = 0; /* terminate I.E. with zero */ + + switch (Ind) { + case MAN_COMBI_IND: { + int total_length = pLib->e.R->PLength; + word this_ind_length; + + while (total_length > 3 && *p) { + Ind = *p++; + this_ind_length = (word)p[0] | ((word)p[1] << 8); + p += 2; + + switch (Ind) { + case MAN_INFO_IND: + if (process_idi_info (pLib, (diva_man_var_header_t*)p)) { + return (-1); + } + break; + case MAN_EVENT_IND: + if (process_idi_event (pLib, (diva_man_var_header_t*)p)) { + return (-1); + } + break; + case MAN_TRACE_IND: + if (pLib->trace_on == 1) { + /* + Ignore first trace event that is result of + EVENT_ON operation + */ + pLib->trace_on++; + } else { + /* + Delivery XLOG buffer to application + */ + if (pLib->user_proc_table.trace_proc) { + (*(pLib->user_proc_table.trace_proc))(pLib->user_proc_table.user_context, + &pLib->instance, pLib->Adapter, + p, this_ind_length); + } + } + break; + default: + diva_mnt_internal_dprintf (0, DLI_ERR, "Unknon IDI Ind (DMA mode): %02x", Ind); + } + p += (this_ind_length+1); + total_length -= (4 + this_ind_length); + } + } break; + case MAN_INFO_IND: + if (process_idi_info (pLib, (diva_man_var_header_t*)p)) { + return (-1); + } + break; + case MAN_EVENT_IND: + if (process_idi_event (pLib, (diva_man_var_header_t*)p)) { + return (-1); + } + break; + case MAN_TRACE_IND: + if (pLib->trace_on == 1) { + /* + Ignore first trace event that is result of + EVENT_ON operation + */ + pLib->trace_on++; + } else { + /* + Delivery XLOG buffer to application + */ + if (pLib->user_proc_table.trace_proc) { + (*(pLib->user_proc_table.trace_proc))(pLib->user_proc_table.user_context, + &pLib->instance, pLib->Adapter, + p, pLib->e.R->PLength); + } + } + break; + default: + diva_mnt_internal_dprintf (0, DLI_ERR, "Unknon IDI Ind: %02x", Ind); + } + } + } + } + + if ((ret = ScheduleNextTraceRequest(pLib))) { + return (-1); + } + + return (ret); +} + +/* + Internal state machine responsible for scheduling of requests + */ +static int ScheduleNextTraceRequest (diva_strace_context_t* pLib) { + char name[64]; + int ret = 0; + int i; + + if (pLib->req_busy) { + return (0); + } + + if (pLib->removal_state == 1) { + if (SuperTraceREMOVE (pLib->hAdapter)) { + pLib->removal_state = 3; + } else { + pLib->req_busy = 1; + pLib->removal_state = 2; + } + return (0); + } + + if (pLib->removal_state) { + return (0); + } + + if (!pLib->general_b_ch_event) { + if ((ret = SuperTraceTraceOnRequest(pLib->hAdapter, "State\\B Event", pLib->buffer))) { + return (-1); + } + pLib->general_b_ch_event = 1; + pLib->req_busy = 1; + return (0); + } + + if (!pLib->general_fax_event) { + if ((ret = SuperTraceTraceOnRequest(pLib->hAdapter, "State\\FAX Event", pLib->buffer))) { + return (-1); + } + pLib->general_fax_event = 1; + pLib->req_busy = 1; + return (0); + } + + if (!pLib->general_mdm_event) { + if ((ret = SuperTraceTraceOnRequest(pLib->hAdapter, "State\\Modem Event", pLib->buffer))) { + return (-1); + } + pLib->general_mdm_event = 1; + pLib->req_busy = 1; + return (0); + } + + if (pLib->ChannelsTraceActive < pLib->Channels) { + pLib->ChannelsTraceActive++; + sprintf (name, "State\\B%d\\Line", pLib->ChannelsTraceActive); + if ((ret = SuperTraceTraceOnRequest(pLib->hAdapter, name, pLib->buffer))) { + pLib->ChannelsTraceActive--; + return (-1); + } + pLib->req_busy = 1; + return (0); + } + + if (pLib->ModemTraceActive < pLib->Channels) { + pLib->ModemTraceActive++; + sprintf (name, "State\\B%d\\Modem\\Event", pLib->ModemTraceActive); + if ((ret = SuperTraceTraceOnRequest(pLib->hAdapter, name, pLib->buffer))) { + pLib->ModemTraceActive--; + return (-1); + } + pLib->req_busy = 1; + return (0); + } + + if (pLib->FaxTraceActive < pLib->Channels) { + pLib->FaxTraceActive++; + sprintf (name, "State\\B%d\\FAX\\Event", pLib->FaxTraceActive); + if ((ret = SuperTraceTraceOnRequest(pLib->hAdapter, name, pLib->buffer))) { + pLib->FaxTraceActive--; + return (-1); + } + pLib->req_busy = 1; + return (0); + } + + if (!pLib->trace_mask_init) { + word tmp = 0x0000; + if (SuperTraceWriteVar (pLib->hAdapter, + pLib->buffer, + "Trace\\Event Enable", + &tmp, + 0x87, /* MI_BITFLD */ + sizeof(tmp))) { + return (-1); + } + pLib->trace_mask_init = 1; + pLib->req_busy = 1; + return (0); + } + + if (!pLib->audio_trace_init) { + dword tmp = 0x00000000; + if (SuperTraceWriteVar (pLib->hAdapter, + pLib->buffer, + "Trace\\AudioCh# Enable", + &tmp, + 0x87, /* MI_BITFLD */ + sizeof(tmp))) { + return (-1); + } + pLib->audio_trace_init = 2; + pLib->req_busy = 1; + return (0); + } + + if (!pLib->bchannel_init) { + dword tmp = 0x00000000; + if (SuperTraceWriteVar (pLib->hAdapter, + pLib->buffer, + "Trace\\B-Ch# Enable", + &tmp, + 0x87, /* MI_BITFLD */ + sizeof(tmp))) { + return (-1); + } + pLib->bchannel_init = 1; + pLib->req_busy = 1; + return (0); + } + + if (!pLib->trace_length_init) { + word tmp = 30; + if (SuperTraceWriteVar (pLib->hAdapter, + pLib->buffer, + "Trace\\Max Log Length", + &tmp, + 0x82, /* MI_UINT */ + sizeof(tmp))) { + return (-1); + } + pLib->trace_length_init = 1; + pLib->req_busy = 1; + return (0); + } + + if (!pLib->trace_on) { + if (SuperTraceTraceOnRequest (pLib->hAdapter, + "Trace\\Log Buffer", + pLib->buffer)) { + return (-1); + } + pLib->trace_on = 1; + pLib->req_busy = 1; + return (0); + } + + if (pLib->trace_event_mask != pLib->current_trace_event_mask) { + if (SuperTraceWriteVar (pLib->hAdapter, + pLib->buffer, + "Trace\\Event Enable", + &pLib->trace_event_mask, + 0x87, /* MI_BITFLD */ + sizeof(pLib->trace_event_mask))) { + return (-1); + } + pLib->current_trace_event_mask = pLib->trace_event_mask; + pLib->req_busy = 1; + return (0); + } + + if ((pLib->audio_tap_pending >= 0) && (pLib->audio_tap_mask != pLib->current_audio_tap_mask)) { + if (SuperTraceWriteVar (pLib->hAdapter, + pLib->buffer, + "Trace\\AudioCh# Enable", + &pLib->audio_tap_mask, + 0x87, /* MI_BITFLD */ + sizeof(pLib->audio_tap_mask))) { + return (-1); + } + pLib->current_audio_tap_mask = pLib->audio_tap_mask; + pLib->audio_tap_pending = 1; + pLib->req_busy = 1; + return (0); + } + + if ((pLib->eye_pattern_pending >= 0) && (pLib->audio_tap_mask != pLib->current_eye_pattern_mask)) { + if (SuperTraceWriteVar (pLib->hAdapter, + pLib->buffer, + "Trace\\EyeCh# Enable", + &pLib->audio_tap_mask, + 0x87, /* MI_BITFLD */ + sizeof(pLib->audio_tap_mask))) { + return (-1); + } + pLib->current_eye_pattern_mask = pLib->audio_tap_mask; + pLib->eye_pattern_pending = 1; + pLib->req_busy = 1; + return (0); + } + + if (pLib->bchannel_trace_mask != pLib->current_bchannel_trace_mask) { + if (SuperTraceWriteVar (pLib->hAdapter, + pLib->buffer, + "Trace\\B-Ch# Enable", + &pLib->bchannel_trace_mask, + 0x87, /* MI_BITFLD */ + sizeof(pLib->bchannel_trace_mask))) { + return (-1); + } + pLib->current_bchannel_trace_mask = pLib->bchannel_trace_mask; + pLib->req_busy = 1; + return (0); + } + + if (!pLib->trace_events_down) { + if (SuperTraceTraceOnRequest (pLib->hAdapter, + "Events Down", + pLib->buffer)) { + return (-1); + } + pLib->trace_events_down = 1; + pLib->req_busy = 1; + return (0); + } + + if (!pLib->l1_trace) { + if (SuperTraceTraceOnRequest (pLib->hAdapter, + "State\\Layer1", + pLib->buffer)) { + return (-1); + } + pLib->l1_trace = 1; + pLib->req_busy = 1; + return (0); + } + + if (!pLib->l2_trace) { + if (SuperTraceTraceOnRequest (pLib->hAdapter, + "State\\Layer2 No1", + pLib->buffer)) { + return (-1); + } + pLib->l2_trace = 1; + pLib->req_busy = 1; + return (0); + } + + for (i = 0; i < 30; i++) { + if (pLib->pending_line_status & (1L << i)) { + sprintf (name, "State\\B%d", i+1); + if (SuperTraceReadRequest (pLib->hAdapter, name, pLib->buffer)) { + return (-1); + } + pLib->pending_line_status &= ~(1L << i); + pLib->req_busy = 1; + return (0); + } + if (pLib->pending_modem_status & (1L << i)) { + sprintf (name, "State\\B%d\\Modem", i+1); + if (SuperTraceReadRequest (pLib->hAdapter, name, pLib->buffer)) { + return (-1); + } + pLib->pending_modem_status &= ~(1L << i); + pLib->req_busy = 1; + return (0); + } + if (pLib->pending_fax_status & (1L << i)) { + sprintf (name, "State\\B%d\\FAX", i+1); + if (SuperTraceReadRequest (pLib->hAdapter, name, pLib->buffer)) { + return (-1); + } + pLib->pending_fax_status &= ~(1L << i); + pLib->req_busy = 1; + return (0); + } + if (pLib->clear_call_command & (1L << i)) { + sprintf (name, "State\\B%d\\Clear Call", i+1); + if (SuperTraceExecuteRequest (pLib->hAdapter, name, pLib->buffer)) { + return (-1); + } + pLib->clear_call_command &= ~(1L << i); + pLib->req_busy = 1; + return (0); + } + } + + if (pLib->outgoing_ifc_stats) { + if (SuperTraceReadRequest (pLib->hAdapter, + "Statistics\\Outgoing Calls", + pLib->buffer)) { + return (-1); + } + pLib->outgoing_ifc_stats = 0; + pLib->req_busy = 1; + return (0); + } + + if (pLib->incoming_ifc_stats) { + if (SuperTraceReadRequest (pLib->hAdapter, + "Statistics\\Incoming Calls", + pLib->buffer)) { + return (-1); + } + pLib->incoming_ifc_stats = 0; + pLib->req_busy = 1; + return (0); + } + + if (pLib->modem_ifc_stats) { + if (SuperTraceReadRequest (pLib->hAdapter, + "Statistics\\Modem", + pLib->buffer)) { + return (-1); + } + pLib->modem_ifc_stats = 0; + pLib->req_busy = 1; + return (0); + } + + if (pLib->fax_ifc_stats) { + if (SuperTraceReadRequest (pLib->hAdapter, + "Statistics\\FAX", + pLib->buffer)) { + return (-1); + } + pLib->fax_ifc_stats = 0; + pLib->req_busy = 1; + return (0); + } + + if (pLib->b1_ifc_stats) { + if (SuperTraceReadRequest (pLib->hAdapter, + "Statistics\\B-Layer1", + pLib->buffer)) { + return (-1); + } + pLib->b1_ifc_stats = 0; + pLib->req_busy = 1; + return (0); + } + + if (pLib->b2_ifc_stats) { + if (SuperTraceReadRequest (pLib->hAdapter, + "Statistics\\B-Layer2", + pLib->buffer)) { + return (-1); + } + pLib->b2_ifc_stats = 0; + pLib->req_busy = 1; + return (0); + } + + if (pLib->d1_ifc_stats) { + if (SuperTraceReadRequest (pLib->hAdapter, + "Statistics\\D-Layer1", + pLib->buffer)) { + return (-1); + } + pLib->d1_ifc_stats = 0; + pLib->req_busy = 1; + return (0); + } + + if (pLib->d2_ifc_stats) { + if (SuperTraceReadRequest (pLib->hAdapter, + "Statistics\\D-Layer2", + pLib->buffer)) { + return (-1); + } + pLib->d2_ifc_stats = 0; + pLib->req_busy = 1; + return (0); + } + + if (!pLib->IncomingCallsCallsActive) { + pLib->IncomingCallsCallsActive = 1; + sprintf (name, "%s", "Statistics\\Incoming Calls\\Calls"); + if ((ret = SuperTraceTraceOnRequest(pLib->hAdapter, name, pLib->buffer))) { + pLib->IncomingCallsCallsActive = 0; + return (-1); + } + pLib->req_busy = 1; + return (0); + } + if (!pLib->IncomingCallsConnectedActive) { + pLib->IncomingCallsConnectedActive = 1; + sprintf (name, "%s", "Statistics\\Incoming Calls\\Connected"); + if ((ret = SuperTraceTraceOnRequest(pLib->hAdapter, name, pLib->buffer))) { + pLib->IncomingCallsConnectedActive = 0; + return (-1); + } + pLib->req_busy = 1; + return (0); + } + if (!pLib->OutgoingCallsCallsActive) { + pLib->OutgoingCallsCallsActive = 1; + sprintf (name, "%s", "Statistics\\Outgoing Calls\\Calls"); + if ((ret = SuperTraceTraceOnRequest(pLib->hAdapter, name, pLib->buffer))) { + pLib->OutgoingCallsCallsActive = 0; + return (-1); + } + pLib->req_busy = 1; + return (0); + } + if (!pLib->OutgoingCallsConnectedActive) { + pLib->OutgoingCallsConnectedActive = 1; + sprintf (name, "%s", "Statistics\\Outgoing Calls\\Connected"); + if ((ret = SuperTraceTraceOnRequest(pLib->hAdapter, name, pLib->buffer))) { + pLib->OutgoingCallsConnectedActive = 0; + return (-1); + } + pLib->req_busy = 1; + return (0); + } + + return (0); +} + +static int process_idi_event (diva_strace_context_t* pLib, + diva_man_var_header_t* pVar) { + const char* path = (char*)&pVar->path_length+1; + char name[64]; + int i; + + if (!strncmp("State\\B Event", path, pVar->path_length)) { + dword ch_id; + if (!diva_trace_read_variable (pVar, &ch_id)) { + if (!pLib->line_init_event && !pLib->pending_line_status) { + for (i = 1; i <= pLib->Channels; i++) { + diva_line_event(pLib, i); + } + return (0); + } else if (ch_id && ch_id <= pLib->Channels) { + return (diva_line_event(pLib, (int)ch_id)); + } + return (0); + } + return (-1); + } + + if (!strncmp("State\\FAX Event", path, pVar->path_length)) { + dword ch_id; + if (!diva_trace_read_variable (pVar, &ch_id)) { + if (!pLib->pending_fax_status && !pLib->fax_init_event) { + for (i = 1; i <= pLib->Channels; i++) { + diva_fax_event(pLib, i); + } + return (0); + } else if (ch_id && ch_id <= pLib->Channels) { + return (diva_fax_event(pLib, (int)ch_id)); + } + return (0); + } + return (-1); + } + + if (!strncmp("State\\Modem Event", path, pVar->path_length)) { + dword ch_id; + if (!diva_trace_read_variable (pVar, &ch_id)) { + if (!pLib->pending_modem_status && !pLib->modem_init_event) { + for (i = 1; i <= pLib->Channels; i++) { + diva_modem_event(pLib, i); + } + return (0); + } else if (ch_id && ch_id <= pLib->Channels) { + return (diva_modem_event(pLib, (int)ch_id)); + } + return (0); + } + return (-1); + } + + /* + First look for Line Event + */ + for (i = 1; i <= pLib->Channels; i++) { + sprintf (name, "State\\B%d\\Line", i); + if (find_var (pVar, name)) { + return (diva_line_event(pLib, i)); + } + } + + /* + Look for Moden Progress Event + */ + for (i = 1; i <= pLib->Channels; i++) { + sprintf (name, "State\\B%d\\Modem\\Event", i); + if (find_var (pVar, name)) { + return (diva_modem_event (pLib, i)); + } + } + + /* + Look for Fax Event + */ + for (i = 1; i <= pLib->Channels; i++) { + sprintf (name, "State\\B%d\\FAX\\Event", i); + if (find_var (pVar, name)) { + return (diva_fax_event (pLib, i)); + } + } + + /* + Notification about loss of events + */ + if (!strncmp("Events Down", path, pVar->path_length)) { + if (pLib->trace_events_down == 1) { + pLib->trace_events_down = 2; + } else { + diva_trace_error (pLib, 1, "Events Down", 0); + } + return (0); + } + + if (!strncmp("State\\Layer1", path, pVar->path_length)) { + diva_strace_read_asz (pVar, &pLib->lines[0].pInterface->Layer1[0]); + if (pLib->l1_trace == 1) { + pLib->l1_trace = 2; + } else { + diva_trace_notify_user (pLib, 0, DIVA_SUPER_TRACE_INTERFACE_CHANGE); + } + return (0); + } + if (!strncmp("State\\Layer2 No1", path, pVar->path_length)) { + char* tmp = &pLib->lines[0].pInterface->Layer2[0]; + dword l2_state; + diva_strace_read_uint (pVar, &l2_state); + + switch (l2_state) { + case 0: + strcpy (tmp, "Idle"); + break; + case 1: + strcpy (tmp, "Layer2 UP"); + break; + case 2: + strcpy (tmp, "Layer2 Disconnecting"); + break; + case 3: + strcpy (tmp, "Layer2 Connecting"); + break; + case 4: + strcpy (tmp, "SPID Initializing"); + break; + case 5: + strcpy (tmp, "SPID Initialised"); + break; + case 6: + strcpy (tmp, "Layer2 Connecting"); + break; + + case 7: + strcpy (tmp, "Auto SPID Stopped"); + break; + + case 8: + strcpy (tmp, "Auto SPID Idle"); + break; + + case 9: + strcpy (tmp, "Auto SPID Requested"); + break; + + case 10: + strcpy (tmp, "Auto SPID Delivery"); + break; + + case 11: + strcpy (tmp, "Auto SPID Complete"); + break; + + default: + sprintf (tmp, "U:%d", (int)l2_state); + } + if (pLib->l2_trace == 1) { + pLib->l2_trace = 2; + } else { + diva_trace_notify_user (pLib, 0, DIVA_SUPER_TRACE_INTERFACE_CHANGE); + } + return (0); + } + + if (!strncmp("Statistics\\Incoming Calls\\Calls", path, pVar->path_length) || + !strncmp("Statistics\\Incoming Calls\\Connected", path, pVar->path_length)) { + return (SuperTraceGetIncomingCallStatistics (pLib)); + } + + if (!strncmp("Statistics\\Outgoing Calls\\Calls", path, pVar->path_length) || + !strncmp("Statistics\\Outgoing Calls\\Connected", path, pVar->path_length)) { + return (SuperTraceGetOutgoingCallStatistics (pLib)); + } + + return (-1); +} + +static int diva_line_event (diva_strace_context_t* pLib, int Channel) { + pLib->pending_line_status |= (1L << (Channel-1)); + return (0); +} + +static int diva_modem_event (diva_strace_context_t* pLib, int Channel) { + pLib->pending_modem_status |= (1L << (Channel-1)); + return (0); +} + +static int diva_fax_event (diva_strace_context_t* pLib, int Channel) { + pLib->pending_fax_status |= (1L << (Channel-1)); + return (0); +} + +/* + Process INFO indications that arrive from the card + Uses path of first I.E. to detect the source of the + infication + */ +static int process_idi_info (diva_strace_context_t* pLib, + diva_man_var_header_t* pVar) { + const char* path = (char*)&pVar->path_length+1; + char name[64]; + int i, len; + + /* + First look for Modem Status Info + */ + for (i = pLib->Channels; i > 0; i--) { + len = sprintf (name, "State\\B%d\\Modem", i); + if (!strncmp(name, path, len)) { + return (diva_modem_info (pLib, i, pVar)); + } + } + + /* + Look for Fax Status Info + */ + for (i = pLib->Channels; i > 0; i--) { + len = sprintf (name, "State\\B%d\\FAX", i); + if (!strncmp(name, path, len)) { + return (diva_fax_info (pLib, i, pVar)); + } + } + + /* + Look for Line Status Info + */ + for (i = pLib->Channels; i > 0; i--) { + len = sprintf (name, "State\\B%d", i); + if (!strncmp(name, path, len)) { + return (diva_line_info (pLib, i, pVar)); + } + } + + if (!diva_ifc_statistics (pLib, pVar)) { + return (0); + } + + return (-1); +} + +/* + MODEM INSTANCE STATE UPDATE + + Update Modem Status Information and issue notification to user, + that will inform about change in the state of modem instance, that is + associuated with this channel + */ +static int diva_modem_info (diva_strace_context_t* pLib, + int Channel, + diva_man_var_header_t* pVar) { + diva_man_var_header_t* cur; + int i, nr = Channel - 1; + + for (i = pLib->modem_parse_entry_first[nr]; + i <= pLib->modem_parse_entry_last[nr]; i++) { + if ((cur = find_var (pVar, pLib->parse_table[i].path))) { + if (diva_trace_read_variable (cur, pLib->parse_table[i].variable)) { + diva_trace_error (pLib, -3 , __FILE__, __LINE__); + return (-1); + } + } else { + diva_trace_error (pLib, -2 , __FILE__, __LINE__); + return (-1); + } + } + + /* + We do not use first event to notify user - this is the event that is + generated as result of EVENT ON operation and is used only to initialize + internal variables of application + */ + if (pLib->modem_init_event & (1L << nr)) { + diva_trace_notify_user (pLib, nr, DIVA_SUPER_TRACE_NOTIFY_MODEM_CHANGE); + } else { + pLib->modem_init_event |= (1L << nr); + } + + return (0); +} + +static int diva_fax_info (diva_strace_context_t* pLib, + int Channel, + diva_man_var_header_t* pVar) { + diva_man_var_header_t* cur; + int i, nr = Channel - 1; + + for (i = pLib->fax_parse_entry_first[nr]; + i <= pLib->fax_parse_entry_last[nr]; i++) { + if ((cur = find_var (pVar, pLib->parse_table[i].path))) { + if (diva_trace_read_variable (cur, pLib->parse_table[i].variable)) { + diva_trace_error (pLib, -3 , __FILE__, __LINE__); + return (-1); + } + } else { + diva_trace_error (pLib, -2 , __FILE__, __LINE__); + return (-1); + } + } + + /* + We do not use first event to notify user - this is the event that is + generated as result of EVENT ON operation and is used only to initialize + internal variables of application + */ + if (pLib->fax_init_event & (1L << nr)) { + diva_trace_notify_user (pLib, nr, DIVA_SUPER_TRACE_NOTIFY_FAX_CHANGE); + } else { + pLib->fax_init_event |= (1L << nr); + } + + return (0); +} + +/* + LINE STATE UPDATE + Update Line Status Information and issue notification to user, + that will inform about change in the line state. + */ +static int diva_line_info (diva_strace_context_t* pLib, + int Channel, + diva_man_var_header_t* pVar) { + diva_man_var_header_t* cur; + int i, nr = Channel - 1; + + for (i = pLib->line_parse_entry_first[nr]; + i <= pLib->line_parse_entry_last[nr]; i++) { + if ((cur = find_var (pVar, pLib->parse_table[i].path))) { + if (diva_trace_read_variable (cur, pLib->parse_table[i].variable)) { + diva_trace_error (pLib, -3 , __FILE__, __LINE__); + return (-1); + } + } else { + diva_trace_error (pLib, -2 , __FILE__, __LINE__); + return (-1); + } + } + + /* + We do not use first event to notify user - this is the event that is + generated as result of EVENT ON operation and is used only to initialize + internal variables of application + + Exception is is if the line is "online". In this case we have to notify + user about this confition. + */ + if (pLib->line_init_event & (1L << nr)) { + diva_trace_notify_user (pLib, nr, DIVA_SUPER_TRACE_NOTIFY_LINE_CHANGE); + } else { + pLib->line_init_event |= (1L << nr); + if (strcmp (&pLib->lines[nr].Line[0], "Idle")) { + diva_trace_notify_user (pLib, nr, DIVA_SUPER_TRACE_NOTIFY_LINE_CHANGE); + } + } + + return (0); +} + +/* + Move position to next vatianle in the chain + */ +static diva_man_var_header_t* get_next_var (diva_man_var_header_t* pVar) { + byte* msg = (byte*)pVar; + byte* start; + int msg_length; + + if (*msg != ESC) return NULL; + + start = msg + 2; + msg_length = *(msg+1); + msg = (start+msg_length); + + if (*msg != ESC) return NULL; + + return ((diva_man_var_header_t*)msg); +} + +/* + Move position to variable with given name + */ +static diva_man_var_header_t* find_var (diva_man_var_header_t* pVar, + const char* name) { + const char* path; + + do { + path = (char*)&pVar->path_length+1; + + if (!strncmp (name, path, pVar->path_length)) { + break; + } + } while ((pVar = get_next_var (pVar))); + + return (pVar); +} + +static void diva_create_line_parse_table (diva_strace_context_t* pLib, + int Channel) { + diva_trace_line_state_t* pLine = &pLib->lines[Channel]; + int nr = Channel+1; + + if ((pLib->cur_parse_entry + LINE_PARSE_ENTRIES) >= pLib->parse_entries) { + diva_trace_error (pLib, -1, __FILE__, __LINE__); + return; + } + + pLine->ChannelNumber = nr; + + pLib->line_parse_entry_first[Channel] = pLib->cur_parse_entry; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\Framing", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = &pLine->Framing[0]; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\Line", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = &pLine->Line[0]; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\Layer2", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = &pLine->Layer2[0]; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\Layer3", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = &pLine->Layer3[0]; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\Remote Address", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLine->RemoteAddress[0]; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\Remote SubAddr", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLine->RemoteSubAddress[0]; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\Local Address", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLine->LocalAddress[0]; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\Local SubAddr", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLine->LocalSubAddress[0]; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\BC", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = &pLine->call_BC; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\HLC", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = &pLine->call_HLC; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\LLC", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = &pLine->call_LLC; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\Charges", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = &pLine->Charges; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\Call Reference", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = &pLine->CallReference; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\Last Disc Cause", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLine->LastDisconnecCause; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\User ID", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = &pLine->UserID[0]; + + pLib->line_parse_entry_last[Channel] = pLib->cur_parse_entry - 1; +} + +static void diva_create_fax_parse_table (diva_strace_context_t* pLib, + int Channel) { + diva_trace_fax_state_t* pFax = &pLib->lines[Channel].fax; + int nr = Channel+1; + + if ((pLib->cur_parse_entry + FAX_PARSE_ENTRIES) >= pLib->parse_entries) { + diva_trace_error (pLib, -1, __FILE__, __LINE__); + return; + } + pFax->ChannelNumber = nr; + + pLib->fax_parse_entry_first[Channel] = pLib->cur_parse_entry; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\FAX\\Event", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = &pFax->Event; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\FAX\\Page Counter", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = &pFax->Page_Counter; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\FAX\\Features", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = &pFax->Features; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\FAX\\Station ID", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = &pFax->Station_ID[0]; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\FAX\\Subaddress", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = &pFax->Subaddress[0]; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\FAX\\Password", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = &pFax->Password[0]; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\FAX\\Speed", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = &pFax->Speed; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\FAX\\Resolution", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = &pFax->Resolution; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\FAX\\Paper Width", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = &pFax->Paper_Width; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\FAX\\Paper Length", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = &pFax->Paper_Length; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\FAX\\Scanline Time", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = &pFax->Scanline_Time; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\FAX\\Disc Reason", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = &pFax->Disc_Reason; + + pLib->fax_parse_entry_last[Channel] = pLib->cur_parse_entry - 1; +} + +static void diva_create_modem_parse_table (diva_strace_context_t* pLib, + int Channel) { + diva_trace_modem_state_t* pModem = &pLib->lines[Channel].modem; + int nr = Channel+1; + + if ((pLib->cur_parse_entry + MODEM_PARSE_ENTRIES) >= pLib->parse_entries) { + diva_trace_error (pLib, -1, __FILE__, __LINE__); + return; + } + pModem->ChannelNumber = nr; + + pLib->modem_parse_entry_first[Channel] = pLib->cur_parse_entry; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\Modem\\Event", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = &pModem->Event; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\Modem\\Norm", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = &pModem->Norm; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\Modem\\Options", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = &pModem->Options; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\Modem\\TX Speed", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = &pModem->TxSpeed; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\Modem\\RX Speed", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = &pModem->RxSpeed; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\Modem\\Roundtrip ms", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = &pModem->RoundtripMsec; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\Modem\\Symbol Rate", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = &pModem->SymbolRate; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\Modem\\RX Level dBm", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = &pModem->RxLeveldBm; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\Modem\\Echo Level dBm", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = &pModem->EchoLeveldBm; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\Modem\\SNR dB", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = &pModem->SNRdb; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\Modem\\MAE", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = &pModem->MAE; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\Modem\\Local Retrains", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = &pModem->LocalRetrains; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\Modem\\Remote Retrains", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = &pModem->RemoteRetrains; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\Modem\\Local Resyncs", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = &pModem->LocalResyncs; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\Modem\\Remote Resyncs", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = &pModem->RemoteResyncs; + + sprintf (pLib->parse_table[pLib->cur_parse_entry].path, + "State\\B%d\\Modem\\Disc Reason", nr); + pLib->parse_table[pLib->cur_parse_entry++].variable = &pModem->DiscReason; + + pLib->modem_parse_entry_last[Channel] = pLib->cur_parse_entry - 1; +} + +static void diva_create_parse_table (diva_strace_context_t* pLib) { + int i; + + for (i = 0; i < pLib->Channels; i++) { + diva_create_line_parse_table (pLib, i); + diva_create_modem_parse_table (pLib, i); + diva_create_fax_parse_table (pLib, i); + } + + pLib->statistic_parse_first = pLib->cur_parse_entry; + + /* + Outgoing Calls + */ + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\Outgoing Calls\\Calls"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.outg.Calls; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\Outgoing Calls\\Connected"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.outg.Connected; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\Outgoing Calls\\User Busy"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.outg.User_Busy; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\Outgoing Calls\\No Answer"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.outg.No_Answer; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\Outgoing Calls\\Wrong Number"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.outg.Wrong_Number; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\Outgoing Calls\\Call Rejected"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.outg.Call_Rejected; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\Outgoing Calls\\Other Failures"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.outg.Other_Failures; + + /* + Incoming Calls + */ + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\Incoming Calls\\Calls"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.inc.Calls; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\Incoming Calls\\Connected"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.inc.Connected; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\Incoming Calls\\User Busy"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.inc.User_Busy; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\Incoming Calls\\Call Rejected"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.inc.Call_Rejected; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\Incoming Calls\\Wrong Number"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.inc.Wrong_Number; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\Incoming Calls\\Incompatible Dst"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.inc.Incompatible_Dst; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\Incoming Calls\\Out of Order"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.inc.Out_of_Order; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\Incoming Calls\\Ignored"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.inc.Ignored; + + /* + Modem Statistics + */ + pLib->mdm_statistic_parse_first = pLib->cur_parse_entry; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\Modem\\Disc Normal"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.mdm.Disc_Normal; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\Modem\\Disc Unspecified"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.mdm.Disc_Unspecified; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\Modem\\Disc Busy Tone"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.mdm.Disc_Busy_Tone; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\Modem\\Disc Congestion"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.mdm.Disc_Congestion; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\Modem\\Disc Carr. Wait"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.mdm.Disc_Carr_Wait; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\Modem\\Disc Trn Timeout"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.mdm.Disc_Trn_Timeout; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\Modem\\Disc Incompat."); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.mdm.Disc_Incompat; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\Modem\\Disc Frame Rej."); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.mdm.Disc_Frame_Rej; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\Modem\\Disc V42bis"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.mdm.Disc_V42bis; + + pLib->mdm_statistic_parse_last = pLib->cur_parse_entry - 1; + + /* + Fax Statistics + */ + pLib->fax_statistic_parse_first = pLib->cur_parse_entry; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\FAX\\Disc Normal"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.fax.Disc_Normal; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\FAX\\Disc Not Ident."); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.fax.Disc_Not_Ident; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\FAX\\Disc No Response"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.fax.Disc_No_Response; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\FAX\\Disc Retries"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.fax.Disc_Retries; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\FAX\\Disc Unexp. Msg."); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.fax.Disc_Unexp_Msg; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\FAX\\Disc No Polling."); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.fax.Disc_No_Polling; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\FAX\\Disc Training"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.fax.Disc_Training; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\FAX\\Disc Unexpected"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.fax.Disc_Unexpected; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\FAX\\Disc Application"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.fax.Disc_Application; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\FAX\\Disc Incompat."); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.fax.Disc_Incompat; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\FAX\\Disc No Command"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.fax.Disc_No_Command; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\FAX\\Disc Long Msg"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.fax.Disc_Long_Msg; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\FAX\\Disc Supervisor"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.fax.Disc_Supervisor; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\FAX\\Disc SUB SEP PWD"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.fax.Disc_SUB_SEP_PWD; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\FAX\\Disc Invalid Msg"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.fax.Disc_Invalid_Msg; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\FAX\\Disc Page Coding"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.fax.Disc_Page_Coding; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\FAX\\Disc App Timeout"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.fax.Disc_App_Timeout; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\FAX\\Disc Unspecified"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.fax.Disc_Unspecified; + + pLib->fax_statistic_parse_last = pLib->cur_parse_entry - 1; + + /* + B-Layer1" + */ + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\B-Layer1\\X-Frames"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.b1.X_Frames; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\B-Layer1\\X-Bytes"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.b1.X_Bytes; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\B-Layer1\\X-Errors"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.b1.X_Errors; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\B-Layer1\\R-Frames"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.b1.R_Frames; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\B-Layer1\\R-Bytes"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.b1.R_Bytes; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\B-Layer1\\R-Errors"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.b1.R_Errors; + + /* + B-Layer2 + */ + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\B-Layer2\\X-Frames"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.b2.X_Frames; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\B-Layer2\\X-Bytes"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.b2.X_Bytes; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\B-Layer2\\X-Errors"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.b2.X_Errors; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\B-Layer2\\R-Frames"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.b2.R_Frames; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\B-Layer2\\R-Bytes"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.b2.R_Bytes; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\B-Layer2\\R-Errors"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.b2.R_Errors; + + /* + D-Layer1 + */ + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\D-Layer1\\X-Frames"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.d1.X_Frames; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\D-Layer1\\X-Bytes"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.d1.X_Bytes; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\D-Layer1\\X-Errors"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.d1.X_Errors; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\D-Layer1\\R-Frames"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.d1.R_Frames; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\D-Layer1\\R-Bytes"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.d1.R_Bytes; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\D-Layer1\\R-Errors"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.d1.R_Errors; + + /* + D-Layer2 + */ + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\D-Layer2\\X-Frames"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.d2.X_Frames; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\D-Layer2\\X-Bytes"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.d2.X_Bytes; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\D-Layer2\\X-Errors"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.d2.X_Errors; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\D-Layer2\\R-Frames"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.d2.R_Frames; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\D-Layer2\\R-Bytes"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.d2.R_Bytes; + + strcpy (pLib->parse_table[pLib->cur_parse_entry].path, + "Statistics\\D-Layer2\\R-Errors"); + pLib->parse_table[pLib->cur_parse_entry++].variable = \ + &pLib->InterfaceStat.d2.R_Errors; + + + pLib->statistic_parse_last = pLib->cur_parse_entry - 1; +} + +static void diva_trace_error (diva_strace_context_t* pLib, + int error, const char* file, int line) { + if (pLib->user_proc_table.error_notify_proc) { + (*(pLib->user_proc_table.error_notify_proc))(\ + pLib->user_proc_table.user_context, + &pLib->instance, pLib->Adapter, + error, file, line); + } +} + +/* + Delivery notification to user + */ +static void diva_trace_notify_user (diva_strace_context_t* pLib, + int Channel, + int notify_subject) { + if (pLib->user_proc_table.notify_proc) { + (*(pLib->user_proc_table.notify_proc))(pLib->user_proc_table.user_context, + &pLib->instance, + pLib->Adapter, + &pLib->lines[Channel], + notify_subject); + } +} + +/* + Read variable value to they destination based on the variable type + */ +static int diva_trace_read_variable (diva_man_var_header_t* pVar, + void* variable) { + switch (pVar->type) { + case 0x03: /* MI_ASCIIZ - syting */ + return (diva_strace_read_asz (pVar, (char*)variable)); + case 0x04: /* MI_ASCII - string */ + return (diva_strace_read_asc (pVar, (char*)variable)); + case 0x05: /* MI_NUMBER - counted sequence of bytes */ + return (diva_strace_read_ie (pVar, (diva_trace_ie_t*)variable)); + case 0x81: /* MI_INT - signed integer */ + return (diva_strace_read_int (pVar, (int*)variable)); + case 0x82: /* MI_UINT - unsigned integer */ + return (diva_strace_read_uint (pVar, (dword*)variable)); + case 0x83: /* MI_HINT - unsigned integer, hex representetion */ + return (diva_strace_read_uint (pVar, (dword*)variable)); + case 0x87: /* MI_BITFLD - unsigned integer, bit representation */ + return (diva_strace_read_uint (pVar, (dword*)variable)); + } + + /* + This type of variable is not handled, indicate error + Or one problem in management interface, or in application recodeing + table, or this application should handle it. + */ + return (-1); +} + +/* + Read signed integer to destination + */ +static int diva_strace_read_int (diva_man_var_header_t* pVar, int* var) { + byte* ptr = (char*)&pVar->path_length; + int value; + + ptr += (pVar->path_length + 1); + + switch (pVar->value_length) { + case 1: + value = *(char*)ptr; + break; + + case 2: + value = (short)GET_WORD(ptr); + break; + + case 4: + value = (int)GET_DWORD(ptr); + break; + + default: + return (-1); + } + + *var = value; + + return (0); +} + +static int diva_strace_read_uint (diva_man_var_header_t* pVar, dword* var) { + byte* ptr = (char*)&pVar->path_length; + dword value; + + ptr += (pVar->path_length + 1); + + switch (pVar->value_length) { + case 1: + value = (byte)(*ptr); + break; + + case 2: + value = (word)GET_WORD(ptr); + break; + + case 3: + value = (dword)GET_DWORD(ptr); + value &= 0x00ffffff; + break; + + case 4: + value = (dword)GET_DWORD(ptr); + break; + + default: + return (-1); + } + + *var = value; + + return (0); +} + +/* + Read zero terminated ASCII string + */ +static int diva_strace_read_asz (diva_man_var_header_t* pVar, char* var) { + char* ptr = (char*)&pVar->path_length; + int length; + + ptr += (pVar->path_length + 1); + + if (!(length = pVar->value_length)) { + length = strlen (ptr); + } + memcpy (var, ptr, length); + var[length] = 0; + + return (0); +} + +/* + Read counted (with leading length byte) ASCII string + */ +static int diva_strace_read_asc (diva_man_var_header_t* pVar, char* var) { + char* ptr = (char*)&pVar->path_length; + + ptr += (pVar->path_length + 1); + memcpy (var, ptr+1, *ptr); + var[(int)*ptr] = 0; + + return (0); +} + +/* + Read one information element - i.e. one string of byte values with + one length byte in front + */ +static int diva_strace_read_ie (diva_man_var_header_t* pVar, + diva_trace_ie_t* var) { + char* ptr = (char*)&pVar->path_length; + + ptr += (pVar->path_length + 1); + + var->length = *ptr; + memcpy (&var->data[0], ptr+1, *ptr); + + return (0); +} + +static int SuperTraceSetAudioTap (void* hLib, int Channel, int on) { + diva_strace_context_t* pLib = (diva_strace_context_t*)hLib; + + if ((Channel < 1) || (Channel > pLib->Channels)) { + return (-1); + } + Channel--; + + if (on) { + pLib->audio_tap_mask |= (1L << Channel); + } else { + pLib->audio_tap_mask &= ~(1L << Channel); + } + + /* + EYE patterns have TM_M_DATA set as additional + condition + */ + if (pLib->audio_tap_mask) { + pLib->trace_event_mask |= TM_M_DATA; + } else { + pLib->trace_event_mask &= ~TM_M_DATA; + } + + return (ScheduleNextTraceRequest (pLib)); +} + +static int SuperTraceSetBChannel (void* hLib, int Channel, int on) { + diva_strace_context_t* pLib = (diva_strace_context_t*)hLib; + + if ((Channel < 1) || (Channel > pLib->Channels)) { + return (-1); + } + Channel--; + + if (on) { + pLib->bchannel_trace_mask |= (1L << Channel); + } else { + pLib->bchannel_trace_mask &= ~(1L << Channel); + } + + return (ScheduleNextTraceRequest (pLib)); +} + +static int SuperTraceSetDChannel (void* hLib, int on) { + diva_strace_context_t* pLib = (diva_strace_context_t*)hLib; + + if (on) { + pLib->trace_event_mask |= (TM_D_CHAN | TM_C_COMM | TM_DL_ERR | TM_LAYER1); + } else { + pLib->trace_event_mask &= ~(TM_D_CHAN | TM_C_COMM | TM_DL_ERR | TM_LAYER1); + } + + return (ScheduleNextTraceRequest (pLib)); +} + +static int SuperTraceSetInfo (void* hLib, int on) { + diva_strace_context_t* pLib = (diva_strace_context_t*)hLib; + + if (on) { + pLib->trace_event_mask |= TM_STRING; + } else { + pLib->trace_event_mask &= ~TM_STRING; + } + + return (ScheduleNextTraceRequest (pLib)); +} + +static int SuperTraceClearCall (void* hLib, int Channel) { + diva_strace_context_t* pLib = (diva_strace_context_t*)hLib; + + if ((Channel < 1) || (Channel > pLib->Channels)) { + return (-1); + } + Channel--; + + pLib->clear_call_command |= (1L << Channel); + + return (ScheduleNextTraceRequest (pLib)); +} + +/* + Parse and update cumulative statistice + */ +static int diva_ifc_statistics (diva_strace_context_t* pLib, + diva_man_var_header_t* pVar) { + diva_man_var_header_t* cur; + int i, one_updated = 0, mdm_updated = 0, fax_updated = 0; + + for (i = pLib->statistic_parse_first; i <= pLib->statistic_parse_last; i++) { + if ((cur = find_var (pVar, pLib->parse_table[i].path))) { + if (diva_trace_read_variable (cur, pLib->parse_table[i].variable)) { + diva_trace_error (pLib, -3 , __FILE__, __LINE__); + return (-1); + } + one_updated = 1; + if ((i >= pLib->mdm_statistic_parse_first) && (i <= pLib->mdm_statistic_parse_last)) { + mdm_updated = 1; + } + if ((i >= pLib->fax_statistic_parse_first) && (i <= pLib->fax_statistic_parse_last)) { + fax_updated = 1; + } + } + } + + /* + We do not use first event to notify user - this is the event that is + generated as result of EVENT ON operation and is used only to initialize + internal variables of application + */ + if (mdm_updated) { + diva_trace_notify_user (pLib, 0, DIVA_SUPER_TRACE_NOTIFY_MDM_STAT_CHANGE); + } else if (fax_updated) { + diva_trace_notify_user (pLib, 0, DIVA_SUPER_TRACE_NOTIFY_FAX_STAT_CHANGE); + } else if (one_updated) { + diva_trace_notify_user (pLib, 0, DIVA_SUPER_TRACE_NOTIFY_STAT_CHANGE); + } + + return (one_updated ? 0 : -1); +} + +static int SuperTraceGetOutgoingCallStatistics (void* hLib) { + diva_strace_context_t* pLib = (diva_strace_context_t*)hLib; + pLib->outgoing_ifc_stats = 1; + return (ScheduleNextTraceRequest (pLib)); +} + +static int SuperTraceGetIncomingCallStatistics (void* hLib) { + diva_strace_context_t* pLib = (diva_strace_context_t*)hLib; + pLib->incoming_ifc_stats = 1; + return (ScheduleNextTraceRequest (pLib)); +} + +static int SuperTraceGetModemStatistics (void* hLib) { + diva_strace_context_t* pLib = (diva_strace_context_t*)hLib; + pLib->modem_ifc_stats = 1; + return (ScheduleNextTraceRequest (pLib)); +} + +static int SuperTraceGetFaxStatistics (void* hLib) { + diva_strace_context_t* pLib = (diva_strace_context_t*)hLib; + pLib->fax_ifc_stats = 1; + return (ScheduleNextTraceRequest (pLib)); +} + +static int SuperTraceGetBLayer1Statistics (void* hLib) { + diva_strace_context_t* pLib = (diva_strace_context_t*)hLib; + pLib->b1_ifc_stats = 1; + return (ScheduleNextTraceRequest (pLib)); +} + +static int SuperTraceGetBLayer2Statistics (void* hLib) { + diva_strace_context_t* pLib = (diva_strace_context_t*)hLib; + pLib->b2_ifc_stats = 1; + return (ScheduleNextTraceRequest (pLib)); +} + +static int SuperTraceGetDLayer1Statistics (void* hLib) { + diva_strace_context_t* pLib = (diva_strace_context_t*)hLib; + pLib->d1_ifc_stats = 1; + return (ScheduleNextTraceRequest (pLib)); +} + +static int SuperTraceGetDLayer2Statistics (void* hLib) { + diva_strace_context_t* pLib = (diva_strace_context_t*)hLib; + pLib->d2_ifc_stats = 1; + return (ScheduleNextTraceRequest (pLib)); +} + +dword DivaSTraceGetMemotyRequirement (int channels) { + dword parse_entries = (MODEM_PARSE_ENTRIES + FAX_PARSE_ENTRIES + \ + STAT_PARSE_ENTRIES + \ + LINE_PARSE_ENTRIES + 1) * channels; + return (sizeof(diva_strace_context_t) + \ + (parse_entries * sizeof(diva_strace_path2action_t))); +} + diff --git a/drivers/isdn/hardware/eicon/maintidi.h b/drivers/isdn/hardware/eicon/maintidi.h new file mode 100644 index 000000000000..4f06294966b8 --- /dev/null +++ b/drivers/isdn/hardware/eicon/maintidi.h @@ -0,0 +1,172 @@ +/* + * + Copyright (c) Eicon Networks, 2000. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 1.9 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +#ifndef __DIVA_EICON_TRACE_IDI_IFC_H__ +#define __DIVA_EICON_TRACE_IDI_IFC_H__ + +void* SuperTraceOpenAdapter (int AdapterNumber); +int SuperTraceCloseAdapter (void* AdapterHandle); +int SuperTraceWrite (void* AdapterHandle, + const void* data, int length); +int SuperTraceReadRequest (void* AdapterHandle,const char* name,byte* data); +int SuperTraceGetNumberOfChannels (void* AdapterHandle); +int SuperTraceASSIGN (void* AdapterHandle, byte* data); +int SuperTraceREMOVE (void* AdapterHandle); +int SuperTraceTraceOnRequest(void* hAdapter, const char* name, byte* data); +int SuperTraceWriteVar (void* AdapterHandle, + byte* data, + const char* name, + void* var, + byte type, + byte var_length); +int SuperTraceExecuteRequest (void* AdapterHandle, + const char* name, + byte* data); + +typedef struct _diva_strace_path2action { + char path[64]; /* Full path to variable */ + void* variable; /* Variable that will receive value */ +} diva_strace_path2action_t; + +#define DIVA_MAX_MANAGEMENT_TRANSFER_SIZE 4096 + +typedef struct _diva_strace_context { + diva_strace_library_interface_t instance; + + int Adapter; + void* hAdapter; + + int Channels; + int req_busy; + + ENTITY e; + IDI_CALL request; + BUFFERS XData; + BUFFERS RData; + byte buffer[DIVA_MAX_MANAGEMENT_TRANSFER_SIZE + 1]; + int removal_state; + int general_b_ch_event; + int general_fax_event; + int general_mdm_event; + + byte rc_ok; + + /* + Initialization request state machine + */ + int ChannelsTraceActive; + int ModemTraceActive; + int FaxTraceActive; + int IncomingCallsCallsActive; + int IncomingCallsConnectedActive; + int OutgoingCallsCallsActive; + int OutgoingCallsConnectedActive; + + int trace_mask_init; + int audio_trace_init; + int bchannel_init; + int trace_length_init; + int trace_on; + int trace_events_down; + int l1_trace; + int l2_trace; + + /* + Trace\Event Enable + */ + word trace_event_mask; + word current_trace_event_mask; + + dword audio_tap_mask; + dword current_audio_tap_mask; + dword current_eye_pattern_mask; + int audio_tap_pending; + int eye_pattern_pending; + + dword bchannel_trace_mask; + dword current_bchannel_trace_mask; + + + diva_trace_line_state_t lines[30]; + + int parse_entries; + int cur_parse_entry; + diva_strace_path2action_t* parse_table; + + diva_trace_library_user_interface_t user_proc_table; + + int line_parse_entry_first[30]; + int line_parse_entry_last[30]; + + int modem_parse_entry_first[30]; + int modem_parse_entry_last[30]; + + int fax_parse_entry_first[30]; + int fax_parse_entry_last[30]; + + int statistic_parse_first; + int statistic_parse_last; + + int mdm_statistic_parse_first; + int mdm_statistic_parse_last; + + int fax_statistic_parse_first; + int fax_statistic_parse_last; + + dword line_init_event; + dword modem_init_event; + dword fax_init_event; + + dword pending_line_status; + dword pending_modem_status; + dword pending_fax_status; + + dword clear_call_command; + + int outgoing_ifc_stats; + int incoming_ifc_stats; + int modem_ifc_stats; + int fax_ifc_stats; + int b1_ifc_stats; + int b2_ifc_stats; + int d1_ifc_stats; + int d2_ifc_stats; + + diva_trace_interface_state_t Interface; + diva_ifc_statistics_t InterfaceStat; +} diva_strace_context_t; + +typedef struct _diva_man_var_header { + byte escape; + byte length; + byte management_id; + byte type; + byte attribute; + byte status; + byte value_length; + byte path_length; +} diva_man_var_header_t; + +#endif + diff --git a/drivers/isdn/hardware/eicon/man_defs.h b/drivers/isdn/hardware/eicon/man_defs.h new file mode 100644 index 000000000000..cb4ef4cae6c1 --- /dev/null +++ b/drivers/isdn/hardware/eicon/man_defs.h @@ -0,0 +1,133 @@ +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 1.9 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +/* Definitions for use with the Management Information Element */ + +/*------------------------------------------------------------------*/ +/* Management information element */ +/* ---------------------------------------------------------- */ +/* Byte Coding Comment */ +/* ---------------------------------------------------------- */ +/* 0 | 0 1 1 1 1 1 1 1 | ESC */ +/* 1 | 0 x x x x x x x | Length of information element (m-1) */ +/* 2 | 1 0 0 0 0 0 0 0 | Management Information Id */ +/* 3 | x x x x x x x x | Type */ +/* 4 | x x x x x x x x | Attribute */ +/* 5 | x x x x x x x x | Status */ +/* 6 | x x x x x x x x | Variable Value Length (m-n) */ +/* 7 | x x x x x x x x | Path / Variable Name String Length (n-8)*/ +/* 8..n | x x x x x x x x | Path/Node Name String separated by '\' */ +/* n..m | x x x x x x x x | Variable content */ +/*------------------------------------------------------------------*/ + +/*------------------------------------------------------------------*/ +/* Type Field */ +/* */ +/* MAN_READ: not used */ +/* MAN_WRITE: not used */ +/* MAN_EVENT_ON: not used */ +/* MAN_EVENT_OFF: not used */ +/* MAN_INFO_IND: type of variable */ +/* MAN_EVENT_IND: type of variable */ +/* MAN_TRACE_IND not used */ +/*------------------------------------------------------------------*/ +#define MI_DIR 0x01 /* Directory string (zero terminated) */ +#define MI_EXECUTE 0x02 /* Executable function (has no value) */ +#define MI_ASCIIZ 0x03 /* Zero terminated string */ +#define MI_ASCII 0x04 /* String, first byte is length */ +#define MI_NUMBER 0x05 /* Number string, first byte is length*/ +#define MI_TRACE 0x06 /* Trace information, format see below*/ + +#define MI_FIXED_LENGTH 0x80 /* get length from MAN_INFO max_len */ +#define MI_INT 0x81 /* number to display as signed int */ +#define MI_UINT 0x82 /* number to display as unsigned int */ +#define MI_HINT 0x83 /* number to display in hex format */ +#define MI_HSTR 0x84 /* number to display as a hex string */ +#define MI_BOOLEAN 0x85 /* number to display as boolean */ +#define MI_IP_ADDRESS 0x86 /* number to display as IP address */ +#define MI_BITFLD 0x87 /* number to display as bit field */ +#define MI_SPID_STATE 0x88 /* state# of SPID initialisation */ + +/*------------------------------------------------------------------*/ +/* Attribute Field */ +/* */ +/* MAN_READ: not used */ +/* MAN_WRITE: not used */ +/* MAN_EVENT_ON: not used */ +/* MAN_EVENT_OFF: not used */ +/* MAN_INFO_IND: set according to capabilities of that variable */ +/* MAN_EVENT_IND: not used */ +/* MAN_TRACE_IND not used */ +/*------------------------------------------------------------------*/ +#define MI_WRITE 0x01 /* Variable is writeable */ +#define MI_EVENT 0x02 /* Variable can indicate changes */ + +/*------------------------------------------------------------------*/ +/* Status Field */ +/* */ +/* MAN_READ: not used */ +/* MAN_WRITE: not used */ +/* MAN_EVENT_ON: not used */ +/* MAN_EVENT_OFF: not used */ +/* MAN_INFO_IND: set according to the actual status */ +/* MAN_EVENT_IND: set according to the actual statu */ +/* MAN_TRACE_IND not used */ +/*------------------------------------------------------------------*/ +#define MI_LOCKED 0x01 /* write protected by another instance*/ +#define MI_EVENT_ON 0x02 /* Event logging switched on */ +#define MI_PROTECTED 0x04 /* write protected by this instance */ + +/*------------------------------------------------------------------*/ +/* Data Format used for MAN_TRACE_IND (no MI-element used) */ +/*------------------------------------------------------------------*/ +typedef struct mi_xlog_hdr_s MI_XLOG_HDR; +struct mi_xlog_hdr_s +{ + unsigned long time; /* Timestamp in msec units */ + unsigned short size; /* Size of data that follows */ + unsigned short code; /* code of trace event */ +}; /* unspecified data follows this header */ + +/*------------------------------------------------------------------*/ +/* Trace mask definitions for trace events except B channel and */ +/* debug trace events */ +/*------------------------------------------------------------------*/ +#define TM_D_CHAN 0x0001 /* D-Channel (D-.) Code 3,4 */ +#define TM_L_LAYER 0x0002 /* Low Layer (LL) Code 6,7 */ +#define TM_N_LAYER 0x0004 /* Network Layer (N) Code 14,15 */ +#define TM_DL_ERR 0x0008 /* Data Link Error (MDL) Code 9 */ +#define TM_LAYER1 0x0010 /* Layer 1 Code 20 */ +#define TM_C_COMM 0x0020 /* Call Comment (SIG) Code 5,21,22 */ +#define TM_M_DATA 0x0040 /* Modulation Data (EYE) Code 23 */ +#define TM_STRING 0x0080 /* Sting data Code 24 */ +#define TM_N_USED2 0x0100 /* not used */ +#define TM_N_USED3 0x0200 /* not used */ +#define TM_N_USED4 0x0400 /* not used */ +#define TM_N_USED5 0x0800 /* not used */ +#define TM_N_USED6 0x1000 /* not used */ +#define TM_N_USED7 0x2000 /* not used */ +#define TM_N_USED8 0x4000 /* not used */ +#define TM_REST 0x8000 /* Codes 10,11,12,13,16,18,19,128,129 */ + +/*------ End of file -----------------------------------------------*/ diff --git a/drivers/isdn/hardware/eicon/mdm_msg.h b/drivers/isdn/hardware/eicon/mdm_msg.h new file mode 100644 index 000000000000..7a737e10bce0 --- /dev/null +++ b/drivers/isdn/hardware/eicon/mdm_msg.h @@ -0,0 +1,346 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +#ifndef __EICON_MDM_MSG_H__ +#define __EICON_MDM_MSG_H__ +#define DSP_UDATA_INDICATION_DCD_OFF 0x01 +#define DSP_UDATA_INDICATION_DCD_ON 0x02 +#define DSP_UDATA_INDICATION_CTS_OFF 0x03 +#define DSP_UDATA_INDICATION_CTS_ON 0x04 +/* ===================================================================== +DCD_OFF Message: + <word> time of DCD off (sampled from counter at 8kHz) +DCD_ON Message: + <word> time of DCD on (sampled from counter at 8kHz) + <byte> connected norm + <word> connected options + <dword> connected speed (bit/s, max of tx and rx speed) + <word> roundtrip delay (ms) + <dword> connected speed tx (bit/s) + <dword> connected speed rx (bit/s) + Size of this message == 19 bytes, but we will receive only 11 + ===================================================================== */ +#define DSP_CONNECTED_NORM_UNSPECIFIED 0 +#define DSP_CONNECTED_NORM_V21 1 +#define DSP_CONNECTED_NORM_V23 2 +#define DSP_CONNECTED_NORM_V22 3 +#define DSP_CONNECTED_NORM_V22_BIS 4 +#define DSP_CONNECTED_NORM_V32_BIS 5 +#define DSP_CONNECTED_NORM_V34 6 +#define DSP_CONNECTED_NORM_V8 7 +#define DSP_CONNECTED_NORM_BELL_212A 8 +#define DSP_CONNECTED_NORM_BELL_103 9 +#define DSP_CONNECTED_NORM_V29_LEASED_LINE 10 +#define DSP_CONNECTED_NORM_V33_LEASED_LINE 11 +#define DSP_CONNECTED_NORM_V90 12 +#define DSP_CONNECTED_NORM_V21_CH2 13 +#define DSP_CONNECTED_NORM_V27_TER 14 +#define DSP_CONNECTED_NORM_V29 15 +#define DSP_CONNECTED_NORM_V33 16 +#define DSP_CONNECTED_NORM_V17 17 +#define DSP_CONNECTED_NORM_V32 18 +#define DSP_CONNECTED_NORM_K56_FLEX 19 +#define DSP_CONNECTED_NORM_X2 20 +#define DSP_CONNECTED_NORM_V18 21 +#define DSP_CONNECTED_NORM_V18_LOW_HIGH 22 +#define DSP_CONNECTED_NORM_V18_HIGH_LOW 23 +#define DSP_CONNECTED_NORM_V21_LOW_HIGH 24 +#define DSP_CONNECTED_NORM_V21_HIGH_LOW 25 +#define DSP_CONNECTED_NORM_BELL103_LOW_HIGH 26 +#define DSP_CONNECTED_NORM_BELL103_HIGH_LOW 27 +#define DSP_CONNECTED_NORM_V23_75_1200 28 +#define DSP_CONNECTED_NORM_V23_1200_75 29 +#define DSP_CONNECTED_NORM_EDT_110 30 +#define DSP_CONNECTED_NORM_BAUDOT_45 31 +#define DSP_CONNECTED_NORM_BAUDOT_47 32 +#define DSP_CONNECTED_NORM_BAUDOT_50 33 +#define DSP_CONNECTED_NORM_DTMF 34 +#define DSP_CONNECTED_NORM_V18_RESERVED_13 35 +#define DSP_CONNECTED_NORM_V18_RESERVED_14 36 +#define DSP_CONNECTED_NORM_V18_RESERVED_15 37 +#define DSP_CONNECTED_NORM_VOWN 38 +#define DSP_CONNECTED_NORM_V23_OFF_HOOK 39 +#define DSP_CONNECTED_NORM_V23_ON_HOOK 40 +#define DSP_CONNECTED_NORM_VOWN_RESERVED_3 41 +#define DSP_CONNECTED_NORM_VOWN_RESERVED_4 42 +#define DSP_CONNECTED_NORM_VOWN_RESERVED_5 43 +#define DSP_CONNECTED_NORM_VOWN_RESERVED_6 44 +#define DSP_CONNECTED_NORM_VOWN_RESERVED_7 45 +#define DSP_CONNECTED_NORM_VOWN_RESERVED_8 46 +#define DSP_CONNECTED_NORM_VOWN_RESERVED_9 47 +#define DSP_CONNECTED_NORM_VOWN_RESERVED_10 48 +#define DSP_CONNECTED_NORM_VOWN_RESERVED_11 49 +#define DSP_CONNECTED_NORM_VOWN_RESERVED_12 50 +#define DSP_CONNECTED_NORM_VOWN_RESERVED_13 51 +#define DSP_CONNECTED_NORM_VOWN_RESERVED_14 52 +#define DSP_CONNECTED_NORM_VOWN_RESERVED_15 53 +#define DSP_CONNECTED_NORM_VOWN_RESERVED_16 54 +#define DSP_CONNECTED_NORM_VOWN_RESERVED_17 55 +#define DSP_CONNECTED_NORM_VOWN_RESERVED_18 56 +#define DSP_CONNECTED_NORM_VOWN_RESERVED_19 57 +#define DSP_CONNECTED_NORM_VOWN_RESERVED_20 58 +#define DSP_CONNECTED_NORM_VOWN_RESERVED_21 59 +#define DSP_CONNECTED_NORM_VOWN_RESERVED_22 60 +#define DSP_CONNECTED_NORM_VOWN_RESERVED_23 61 +#define DSP_CONNECTED_NORM_VOWN_RESERVED_24 62 +#define DSP_CONNECTED_NORM_VOWN_RESERVED_25 63 +#define DSP_CONNECTED_NORM_VOWN_RESERVED_26 64 +#define DSP_CONNECTED_NORM_VOWN_RESERVED_27 65 +#define DSP_CONNECTED_NORM_VOWN_RESERVED_28 66 +#define DSP_CONNECTED_NORM_VOWN_RESERVED_29 67 +#define DSP_CONNECTED_NORM_VOWN_RESERVED_30 68 +#define DSP_CONNECTED_NORM_VOWN_RESERVED_31 69 +#define DSP_CONNECTED_OPTION_TRELLIS 0x0001 +#define DSP_CONNECTED_OPTION_V42_TRANS 0x0002 +#define DSP_CONNECTED_OPTION_V42_LAPM 0x0004 +#define DSP_CONNECTED_OPTION_SHORT_TRAIN 0x0008 +#define DSP_CONNECTED_OPTION_TALKER_ECHO_PROTECT 0x0010 +#define DSP_CONNECTED_OPTION_V42BIS 0x0020 +#define DSP_CONNECTED_OPTION_MNP2 0x0040 +#define DSP_CONNECTED_OPTION_MNP3 0x0080 +#define DSP_CONNECTED_OPTION_MNP4 0x00c0 +#define DSP_CONNECTED_OPTION_MNP5 0x0100 +#define DSP_CONNECTED_OPTION_MNP10 0x0200 +#define DSP_CONNECTED_OPTION_MASK_V42 0x0024 +#define DSP_CONNECTED_OPTION_MASK_MNP 0x03c0 +#define DSP_CONNECTED_OPTION_MASK_ERROR_CORRECT 0x03e4 +#define DSP_CONNECTED_OPTION_MASK_COMPRESSION 0x0320 +#define DSP_UDATA_INDICATION_DISCONNECT 5 +/* +returns: + <byte> cause +*/ +/* ========================================================== + DLC: B2 modem configuration + ========================================================== */ +/* +Fields in assign DLC information element for modem protocol V.42/MNP: + <byte> length of information element + <word> information field length + <byte> address A (not used, default 3) + <byte> address B (not used, default 1) + <byte> modulo mode (not used, default 7) + <byte> window size (not used, default 7) + <word> XID length (not used, default 0) + ... XID information (not used, default empty) + <byte> modem protocol negotiation options + <byte> modem protocol options + <byte> modem protocol break configuration + <byte> modem protocol application options +*/ +#define DLC_MODEMPROT_DISABLE_V42_V42BIS 0x01 +#define DLC_MODEMPROT_DISABLE_MNP_MNP5 0x02 +#define DLC_MODEMPROT_REQUIRE_PROTOCOL 0x04 +#define DLC_MODEMPROT_DISABLE_V42_DETECT 0x08 +#define DLC_MODEMPROT_DISABLE_COMPRESSION 0x10 +#define DLC_MODEMPROT_REQUIRE_PROTOCOL_V34UP 0x20 +#define DLC_MODEMPROT_NO_PROTOCOL_IF_1200 0x01 +#define DLC_MODEMPROT_BUFFER_IN_V42_DETECT 0x02 +#define DLC_MODEMPROT_DISABLE_V42_SREJ 0x04 +#define DLC_MODEMPROT_DISABLE_MNP3 0x08 +#define DLC_MODEMPROT_DISABLE_MNP4 0x10 +#define DLC_MODEMPROT_DISABLE_MNP10 0x20 +#define DLC_MODEMPROT_NO_PROTOCOL_IF_V22BIS 0x40 +#define DLC_MODEMPROT_NO_PROTOCOL_IF_V32BIS 0x80 +#define DLC_MODEMPROT_BREAK_DISABLED 0x00 +#define DLC_MODEMPROT_BREAK_NORMAL 0x01 +#define DLC_MODEMPROT_BREAK_EXPEDITED 0x02 +#define DLC_MODEMPROT_BREAK_DESTRUCTIVE 0x03 +#define DLC_MODEMPROT_BREAK_CONFIG_MASK 0x03 +#define DLC_MODEMPROT_APPL_EARLY_CONNECT 0x01 +#define DLC_MODEMPROT_APPL_PASS_INDICATIONS 0x02 +/* ========================================================== + CAI parameters used for the modem L1 configuration + ========================================================== */ +/* +Fields in assign CAI information element: + <byte> length of information element + <byte> info field and B-channel hardware + <byte> rate adaptation bit rate + <byte> async framing parameters + <byte> reserved + <word> packet length + <byte> modem line taking options + <byte> modem modulation negotiation parameters + <byte> modem modulation options + <byte> modem disabled modulations mask low + <byte> modem disabled modulations mask high + <byte> modem enabled modulations mask + <word> modem min TX speed + <word> modem max TX speed + <word> modem min RX speed + <word> modem max RX speed + <byte> modem disabled symbol rates mask + <byte> modem info options mask + <byte> modem transmit level adjust + <byte> modem speaker parameters + <word> modem private debug config + <struct> modem reserved + <struct> v18 config parameters + <struct> v18 probing sequence + <struct> v18 probing message +*/ +#define DSP_CAI_HARDWARE_HDLC_64K 0x05 +#define DSP_CAI_HARDWARE_HDLC_56K 0x08 +#define DSP_CAI_HARDWARE_TRANSP 0x09 +#define DSP_CAI_HARDWARE_V110_SYNC 0x0c +#define DSP_CAI_HARDWARE_V110_ASYNC 0x0d +#define DSP_CAI_HARDWARE_HDLC_128K 0x0f +#define DSP_CAI_HARDWARE_FAX 0x10 +#define DSP_CAI_HARDWARE_MODEM_ASYNC 0x11 +#define DSP_CAI_HARDWARE_MODEM_SYNC 0x12 +#define DSP_CAI_HARDWARE_V110_HDLCA 0x13 +#define DSP_CAI_HARDWARE_ADVANCED_VOICE 0x14 +#define DSP_CAI_HARDWARE_TRANSP_DTMF 0x16 +#define DSP_CAI_HARDWARE_DTMF_VOICE_ISDN 0x17 +#define DSP_CAI_HARDWARE_DTMF_VOICE_LOCAL 0x18 +#define DSP_CAI_HARDWARE_MASK 0x3f +#define DSP_CAI_ENABLE_INFO_INDICATIONS 0x80 +#define DSP_CAI_RATE_ADAPTATION_300 0x00 +#define DSP_CAI_RATE_ADAPTATION_600 0x01 +#define DSP_CAI_RATE_ADAPTATION_1200 0x02 +#define DSP_CAI_RATE_ADAPTATION_2400 0x03 +#define DSP_CAI_RATE_ADAPTATION_4800 0x04 +#define DSP_CAI_RATE_ADAPTATION_9600 0x05 +#define DSP_CAI_RATE_ADAPTATION_19200 0x06 +#define DSP_CAI_RATE_ADAPTATION_38400 0x07 +#define DSP_CAI_RATE_ADAPTATION_48000 0x08 +#define DSP_CAI_RATE_ADAPTATION_56000 0x09 +#define DSP_CAI_RATE_ADAPTATION_7200 0x0a +#define DSP_CAI_RATE_ADAPTATION_14400 0x0b +#define DSP_CAI_RATE_ADAPTATION_28800 0x0c +#define DSP_CAI_RATE_ADAPTATION_12000 0x0d +#define DSP_CAI_RATE_ADAPTATION_1200_75 0x0e +#define DSP_CAI_RATE_ADAPTATION_75_1200 0x0f +#define DSP_CAI_RATE_ADAPTATION_MASK 0x0f +#define DSP_CAI_ASYNC_PARITY_ENABLE 0x01 +#define DSP_CAI_ASYNC_PARITY_SPACE 0x00 +#define DSP_CAI_ASYNC_PARITY_ODD 0x02 +#define DSP_CAI_ASYNC_PARITY_EVEN 0x04 +#define DSP_CAI_ASYNC_PARITY_MARK 0x06 +#define DSP_CAI_ASYNC_PARITY_MASK 0x06 +#define DSP_CAI_ASYNC_ONE_STOP_BIT 0x00 +#define DSP_CAI_ASYNC_TWO_STOP_BITS 0x20 +#define DSP_CAI_ASYNC_CHAR_LENGTH_8 0x00 +#define DSP_CAI_ASYNC_CHAR_LENGTH_7 0x40 +#define DSP_CAI_ASYNC_CHAR_LENGTH_6 0x80 +#define DSP_CAI_ASYNC_CHAR_LENGTH_5 0xc0 +#define DSP_CAI_ASYNC_CHAR_LENGTH_MASK 0xc0 +#define DSP_CAI_MODEM_LEASED_LINE_MODE 0x01 +#define DSP_CAI_MODEM_4_WIRE_OPERATION 0x02 +#define DSP_CAI_MODEM_DISABLE_BUSY_DETECT 0x04 +#define DSP_CAI_MODEM_DISABLE_CALLING_TONE 0x08 +#define DSP_CAI_MODEM_DISABLE_ANSWER_TONE 0x10 +#define DSP_CAI_MODEM_ENABLE_DIAL_TONE_DET 0x20 +#define DSP_CAI_MODEM_USE_POTS_INTERFACE 0x40 +#define DSP_CAI_MODEM_FORCE_RAY_TAYLOR_FAX 0x80 +#define DSP_CAI_MODEM_NEGOTIATE_HIGHEST 0x00 +#define DSP_CAI_MODEM_NEGOTIATE_DISABLED 0x01 +#define DSP_CAI_MODEM_NEGOTIATE_IN_CLASS 0x02 +#define DSP_CAI_MODEM_NEGOTIATE_V100 0x03 +#define DSP_CAI_MODEM_NEGOTIATE_V8 0x04 +#define DSP_CAI_MODEM_NEGOTIATE_V8BIS 0x05 +#define DSP_CAI_MODEM_NEGOTIATE_MASK 0x07 +#define DSP_CAI_MODEM_GUARD_TONE_NONE 0x00 +#define DSP_CAI_MODEM_GUARD_TONE_550HZ 0x40 +#define DSP_CAI_MODEM_GUARD_TONE_1800HZ 0x80 +#define DSP_CAI_MODEM_GUARD_TONE_MASK 0xc0 +#define DSP_CAI_MODEM_DISABLE_RETRAIN 0x01 +#define DSP_CAI_MODEM_DISABLE_STEPUPDOWN 0x02 +#define DSP_CAI_MODEM_DISABLE_SPLIT_SPEED 0x04 +#define DSP_CAI_MODEM_DISABLE_TRELLIS 0x08 +#define DSP_CAI_MODEM_ALLOW_RDL_TEST_LOOP 0x10 +#define DSP_CAI_MODEM_DISABLE_FLUSH_TIMER 0x40 +#define DSP_CAI_MODEM_REVERSE_DIRECTION 0x80 +#define DSP_CAI_MODEM_DISABLE_V21 0x01 +#define DSP_CAI_MODEM_DISABLE_V23 0x02 +#define DSP_CAI_MODEM_DISABLE_V22 0x04 +#define DSP_CAI_MODEM_DISABLE_V22BIS 0x08 +#define DSP_CAI_MODEM_DISABLE_V32 0x10 +#define DSP_CAI_MODEM_DISABLE_V32BIS 0x20 +#define DSP_CAI_MODEM_DISABLE_V34 0x40 +#define DSP_CAI_MODEM_DISABLE_V90 0x80 +#define DSP_CAI_MODEM_DISABLE_BELL103 0x01 +#define DSP_CAI_MODEM_DISABLE_BELL212A 0x02 +#define DSP_CAI_MODEM_DISABLE_VFC 0x04 +#define DSP_CAI_MODEM_DISABLE_K56FLEX 0x08 +#define DSP_CAI_MODEM_DISABLE_X2 0x10 +#define DSP_CAI_MODEM_ENABLE_V29FDX 0x01 +#define DSP_CAI_MODEM_ENABLE_V33 0x02 +#define DSP_CAI_MODEM_DISABLE_2400_SYMBOLS 0x01 +#define DSP_CAI_MODEM_DISABLE_2743_SYMBOLS 0x02 +#define DSP_CAI_MODEM_DISABLE_2800_SYMBOLS 0x04 +#define DSP_CAI_MODEM_DISABLE_3000_SYMBOLS 0x08 +#define DSP_CAI_MODEM_DISABLE_3200_SYMBOLS 0x10 +#define DSP_CAI_MODEM_DISABLE_3429_SYMBOLS 0x20 +#define DSP_CAI_MODEM_DISABLE_TX_REDUCTION 0x01 +#define DSP_CAI_MODEM_DISABLE_PRECODING 0x02 +#define DSP_CAI_MODEM_DISABLE_PREEMPHASIS 0x04 +#define DSP_CAI_MODEM_DISABLE_SHAPING 0x08 +#define DSP_CAI_MODEM_DISABLE_NONLINEAR_EN 0x10 +#define DSP_CAI_MODEM_SPEAKER_OFF 0x00 +#define DSP_CAI_MODEM_SPEAKER_DURING_TRAIN 0x01 +#define DSP_CAI_MODEM_SPEAKER_TIL_CONNECT 0x02 +#define DSP_CAI_MODEM_SPEAKER_ALWAYS_ON 0x03 +#define DSP_CAI_MODEM_SPEAKER_CONTROL_MASK 0x03 +#define DSP_CAI_MODEM_SPEAKER_VOLUME_MIN 0x00 +#define DSP_CAI_MODEM_SPEAKER_VOLUME_LOW 0x04 +#define DSP_CAI_MODEM_SPEAKER_VOLUME_HIGH 0x08 +#define DSP_CAI_MODEM_SPEAKER_VOLUME_MAX 0x0c +#define DSP_CAI_MODEM_SPEAKER_VOLUME_MASK 0x0c +/* ========================================================== + DCD/CTS State + ========================================================== */ +#define MDM_WANT_CONNECT_B3_ACTIVE_I 0x01 +#define MDM_NCPI_VALID 0x02 +#define MDM_NCPI_CTS_ON_RECEIVED 0x04 +#define MDM_NCPI_DCD_ON_RECEIVED 0x08 +/* ========================================================== + CAPI NCPI Constants + ========================================================== */ +#define MDM_NCPI_ECM_V42 0x0001 +#define MDM_NCPI_ECM_MNP 0x0002 +#define MDM_NCPI_TRANSPARENT 0x0004 +#define MDM_NCPI_COMPRESSED 0x0010 +/* ========================================================== + CAPI B2 Config Constants + ========================================================== */ +#define MDM_B2_DISABLE_V42bis 0x0001 +#define MDM_B2_DISABLE_MNP 0x0002 +#define MDM_B2_DISABLE_TRANS 0x0004 +#define MDM_B2_DISABLE_V42 0x0008 +#define MDM_B2_DISABLE_COMP 0x0010 +/* ========================================================== + CAPI B1 Config Constants + ========================================================== */ +#define MDM_CAPI_DISABLE_RETRAIN 0x0001 +#define MDM_CAPI_DISABLE_RING_TONE 0x0002 +#define MDM_CAPI_GUARD_1800 0x0004 +#define MDM_CAPI_GUARD_550 0x0008 +#define MDM_CAPI_NEG_V8 0x0003 +#define MDM_CAPI_NEG_V100 0x0002 +#define MDM_CAPI_NEG_MOD_CLASS 0x0001 +#define MDM_CAPI_NEG_DISABLED 0x0000 +#endif diff --git a/drivers/isdn/hardware/eicon/message.c b/drivers/isdn/hardware/eicon/message.c new file mode 100644 index 000000000000..f9b00f19afd2 --- /dev/null +++ b/drivers/isdn/hardware/eicon/message.c @@ -0,0 +1,15047 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ + + + + + +#include "platform.h" +#include "di_defs.h" +#include "pc.h" +#include "capi20.h" +#include "divacapi.h" +#include "mdm_msg.h" +#include "divasync.h" + + + +#define FILE_ "MESSAGE.C" +#define dprintf + + + + + + + + + +/*------------------------------------------------------------------*/ +/* This is options supported for all adapters that are server by */ +/* XDI driver. Allo it is not necessary to ask it from every adapter*/ +/* and it is not necessary to save it separate for every adapter */ +/* Macrose defined here have only local meaning */ +/*------------------------------------------------------------------*/ +static dword diva_xdi_extended_features = 0; + +#define DIVA_CAPI_USE_CMA 0x00000001 +#define DIVA_CAPI_XDI_PROVIDES_SDRAM_BAR 0x00000002 +#define DIVA_CAPI_XDI_PROVIDES_NO_CANCEL 0x00000004 +#define DIVA_CAPI_XDI_PROVIDES_RX_DMA 0x00000008 + +/* + CAPI can request to process all return codes self only if: + protocol code supports this && xdi supports this + */ +#define DIVA_CAPI_SUPPORTS_NO_CANCEL(__a__) (((__a__)->manufacturer_features&MANUFACTURER_FEATURE_XONOFF_FLOW_CONTROL)&& ((__a__)->manufacturer_features & MANUFACTURER_FEATURE_OK_FC_LABEL) && (diva_xdi_extended_features & DIVA_CAPI_XDI_PROVIDES_NO_CANCEL)) + +/*------------------------------------------------------------------*/ +/* local function prototypes */ +/*------------------------------------------------------------------*/ + +static void group_optimization(DIVA_CAPI_ADAPTER * a, PLCI * plci); +static void set_group_ind_mask (PLCI *plci); +static void clear_group_ind_mask_bit (PLCI *plci, word b); +static byte test_group_ind_mask_bit (PLCI *plci, word b); +void AutomaticLaw(DIVA_CAPI_ADAPTER *); +word CapiRelease(word); +word CapiRegister(word); +word api_put(APPL *, CAPI_MSG *); +static word api_parse(byte *, word, byte *, API_PARSE *); +static void api_save_msg(API_PARSE *in, byte *format, API_SAVE *out); +static void api_load_msg(API_SAVE *in, API_PARSE *out); + +word api_remove_start(void); +void api_remove_complete(void); + +static void plci_remove(PLCI *); +static void diva_get_extended_adapter_features (DIVA_CAPI_ADAPTER * a); +static void diva_ask_for_xdi_sdram_bar (DIVA_CAPI_ADAPTER *, IDI_SYNC_REQ *); + +void callback(ENTITY *); + +static void control_rc(PLCI *, byte, byte, byte, byte, byte); +static void data_rc(PLCI *, byte); +static void data_ack(PLCI *, byte); +static void sig_ind(PLCI *); +static void SendInfo(PLCI *, dword, byte * *, byte); +static void SendSetupInfo(APPL *, PLCI *, dword, byte * *, byte); +static void SendSSExtInd(APPL *, PLCI * plci, dword Id, byte * * parms); + +static void VSwitchReqInd(PLCI *plci, dword Id, byte **parms); + +static void nl_ind(PLCI *); + +static byte connect_req(dword, word, DIVA_CAPI_ADAPTER *, PLCI *, APPL *, API_PARSE *); +static byte connect_res(dword, word, DIVA_CAPI_ADAPTER *, PLCI *, APPL *, API_PARSE *); +static byte connect_a_res(dword,word, DIVA_CAPI_ADAPTER *, PLCI *, APPL *, API_PARSE *); +static byte disconnect_req(dword, word, DIVA_CAPI_ADAPTER *, PLCI *, APPL *, API_PARSE *); +static byte disconnect_res(dword, word, DIVA_CAPI_ADAPTER *, PLCI *, APPL *, API_PARSE *); +static byte listen_req(dword, word, DIVA_CAPI_ADAPTER *, PLCI *, APPL *, API_PARSE *); +static byte info_req(dword, word, DIVA_CAPI_ADAPTER *, PLCI *, APPL *, API_PARSE *); +static byte info_res(dword, word, DIVA_CAPI_ADAPTER *, PLCI *, APPL *, API_PARSE *); +static byte alert_req(dword, word, DIVA_CAPI_ADAPTER *, PLCI *, APPL *, API_PARSE *); +static byte facility_req(dword, word, DIVA_CAPI_ADAPTER *, PLCI *, APPL *, API_PARSE *); +static byte facility_res(dword, word, DIVA_CAPI_ADAPTER *, PLCI *, APPL *, API_PARSE *); +static byte connect_b3_req(dword, word, DIVA_CAPI_ADAPTER *, PLCI *, APPL *, API_PARSE *); +static byte connect_b3_res(dword, word, DIVA_CAPI_ADAPTER *, PLCI *, APPL *, API_PARSE *); +static byte connect_b3_a_res(dword, word, DIVA_CAPI_ADAPTER *, PLCI *, APPL *, API_PARSE *); +static byte disconnect_b3_req(dword, word, DIVA_CAPI_ADAPTER *, PLCI *, APPL *, API_PARSE *); +static byte disconnect_b3_res(dword, word, DIVA_CAPI_ADAPTER *, PLCI *, APPL *, API_PARSE *); +static byte data_b3_req(dword, word, DIVA_CAPI_ADAPTER *, PLCI *, APPL *, API_PARSE *); +static byte data_b3_res(dword, word, DIVA_CAPI_ADAPTER *, PLCI *, APPL *, API_PARSE *); +static byte reset_b3_req(dword, word, DIVA_CAPI_ADAPTER *, PLCI *, APPL *, API_PARSE *); +static byte reset_b3_res(dword, word, DIVA_CAPI_ADAPTER *, PLCI *, APPL *, API_PARSE *); +static byte connect_b3_t90_a_res(dword, word, DIVA_CAPI_ADAPTER *, PLCI *, APPL *, API_PARSE *); +static byte select_b_req(dword, word, DIVA_CAPI_ADAPTER *, PLCI *, APPL *, API_PARSE *); +static byte manufacturer_req(dword, word, DIVA_CAPI_ADAPTER *, PLCI *, APPL *, API_PARSE *); +static byte manufacturer_res(dword, word, DIVA_CAPI_ADAPTER *, PLCI *, APPL *, API_PARSE *); + +static word get_plci(DIVA_CAPI_ADAPTER *); +static void add_p(PLCI *, byte, byte *); +static void add_s(PLCI * plci, byte code, API_PARSE * p); +static void add_ss(PLCI * plci, byte code, API_PARSE * p); +static void add_ie(PLCI * plci, byte code, byte * p, word p_length); +static void add_d(PLCI *, word, byte *); +static void add_ai(PLCI *, API_PARSE *); +static word add_b1(PLCI *, API_PARSE *, word, word); +static word add_b23(PLCI *, API_PARSE *); +static word add_modem_b23 (PLCI * plci, API_PARSE* bp_parms); +static void sig_req(PLCI *, byte, byte); +static void nl_req_ncci(PLCI *, byte, byte); +static void send_req(PLCI *); +static void send_data(PLCI *); +static word plci_remove_check(PLCI *); +static void listen_check(DIVA_CAPI_ADAPTER *); +static byte AddInfo(byte **, byte **, byte *, byte *); +static byte getChannel(API_PARSE *); +static void IndParse(PLCI *, word *, byte **, byte); +static byte ie_compare(byte *, byte *); +static word find_cip(DIVA_CAPI_ADAPTER *, byte *, byte *); +static word CPN_filter_ok(byte *cpn,DIVA_CAPI_ADAPTER *,word); + +/* + XON protocol helpers + */ +static void channel_flow_control_remove (PLCI * plci); +static void channel_x_off (PLCI * plci, byte ch, byte flag); +static void channel_x_on (PLCI * plci, byte ch); +static void channel_request_xon (PLCI * plci, byte ch); +static void channel_xmit_xon (PLCI * plci); +static int channel_can_xon (PLCI * plci, byte ch); +static void channel_xmit_extended_xon (PLCI * plci); + +static byte SendMultiIE(PLCI * plci, dword Id, byte * * parms, byte ie_type, dword info_mask, byte setupParse); +static word AdvCodecSupport(DIVA_CAPI_ADAPTER *, PLCI *, APPL *, byte); +static void CodecIdCheck(DIVA_CAPI_ADAPTER *, PLCI *); +static void SetVoiceChannel(PLCI *, byte *, DIVA_CAPI_ADAPTER * ); +static void VoiceChannelOff(PLCI *plci); +static void adv_voice_write_coefs (PLCI *plci, word write_command); +static void adv_voice_clear_config (PLCI *plci); + +static word get_b1_facilities (PLCI * plci, byte b1_resource); +static byte add_b1_facilities (PLCI * plci, byte b1_resource, word b1_facilities); +static void adjust_b1_facilities (PLCI *plci, byte new_b1_resource, word new_b1_facilities); +static word adjust_b_process (dword Id, PLCI *plci, byte Rc); +static void adjust_b1_resource (dword Id, PLCI *plci, API_SAVE *bp_msg, word b1_facilities, word internal_command); +static void adjust_b_restore (dword Id, PLCI *plci, byte Rc); +static void reset_b3_command (dword Id, PLCI *plci, byte Rc); +static void select_b_command (dword Id, PLCI *plci, byte Rc); +static void fax_connect_ack_command (dword Id, PLCI *plci, byte Rc); +static void fax_edata_ack_command (dword Id, PLCI *plci, byte Rc); +static void fax_connect_info_command (dword Id, PLCI *plci, byte Rc); +static void fax_adjust_b23_command (dword Id, PLCI *plci, byte Rc); +static void fax_disconnect_command (dword Id, PLCI *plci, byte Rc); +static void hold_save_command (dword Id, PLCI *plci, byte Rc); +static void retrieve_restore_command (dword Id, PLCI *plci, byte Rc); +static void init_b1_config (PLCI *plci); +static void clear_b1_config (PLCI *plci); + +static void dtmf_command (dword Id, PLCI *plci, byte Rc); +static byte dtmf_request (dword Id, word Number, DIVA_CAPI_ADAPTER *a, PLCI *plci, APPL *appl, API_PARSE *msg); +static void dtmf_confirmation (dword Id, PLCI *plci); +static void dtmf_indication (dword Id, PLCI *plci, byte *msg, word length); +static void dtmf_parameter_write (PLCI *plci); + + +static void mixer_set_bchannel_id_esc (PLCI *plci, byte bchannel_id); +static void mixer_set_bchannel_id (PLCI *plci, byte *chi); +static void mixer_clear_config (PLCI *plci); +static void mixer_notify_update (PLCI *plci, byte others); +static void mixer_command (dword Id, PLCI *plci, byte Rc); +static byte mixer_request (dword Id, word Number, DIVA_CAPI_ADAPTER *a, PLCI *plci, APPL *appl, API_PARSE *msg); +static void mixer_indication_coefs_set (dword Id, PLCI *plci); +static void mixer_indication_xconnect_from (dword Id, PLCI *plci, byte *msg, word length); +static void mixer_indication_xconnect_to (dword Id, PLCI *plci, byte *msg, word length); +static void mixer_remove (PLCI *plci); + + +static void ec_command (dword Id, PLCI *plci, byte Rc); +static byte ec_request (dword Id, word Number, DIVA_CAPI_ADAPTER *a, PLCI *plci, APPL *appl, API_PARSE *msg); +static void ec_indication (dword Id, PLCI *plci, byte *msg, word length); + + +static void rtp_connect_b3_req_command (dword Id, PLCI *plci, byte Rc); +static void rtp_connect_b3_res_command (dword Id, PLCI *plci, byte Rc); + + +static int diva_get_dma_descriptor (PLCI *plci, dword *dma_magic); +static void diva_free_dma_descriptor (PLCI *plci, int nr); + +/*------------------------------------------------------------------*/ +/* external function prototypes */ +/*------------------------------------------------------------------*/ + +extern byte MapController (byte); +extern byte UnMapController (byte); +#define MapId(Id) (((Id) & 0xffffff00L) | MapController ((byte)(Id))) +#define UnMapId(Id) (((Id) & 0xffffff00L) | UnMapController ((byte)(Id))) + +void sendf(APPL *, word, dword, word, byte *, ...); +void * TransmitBufferSet(APPL * appl, dword ref); +void * TransmitBufferGet(APPL * appl, void * p); +void TransmitBufferFree(APPL * appl, void * p); +void * ReceiveBufferGet(APPL * appl, int Num); + +int fax_head_line_time (char *buffer); + + +/*------------------------------------------------------------------*/ +/* Global data definitions */ +/*------------------------------------------------------------------*/ +extern byte max_adapter; +extern byte max_appl; +extern DIVA_CAPI_ADAPTER * adapter; +extern APPL * application; + + + + + + + +static byte remove_started = FALSE; +static PLCI dummy_plci; + + +static struct _ftable { + word command; + byte * format; + byte (* function)(dword, word, DIVA_CAPI_ADAPTER *, PLCI *, APPL *, API_PARSE *); +} ftable[] = { + {_DATA_B3_R, "dwww", data_b3_req}, + {_DATA_B3_I|RESPONSE, "w", data_b3_res}, + {_INFO_R, "ss", info_req}, + {_INFO_I|RESPONSE, "", info_res}, + {_CONNECT_R, "wsssssssss", connect_req}, + {_CONNECT_I|RESPONSE, "wsssss", connect_res}, + {_CONNECT_ACTIVE_I|RESPONSE, "", connect_a_res}, + {_DISCONNECT_R, "s", disconnect_req}, + {_DISCONNECT_I|RESPONSE, "", disconnect_res}, + {_LISTEN_R, "dddss", listen_req}, + {_ALERT_R, "s", alert_req}, + {_FACILITY_R, "ws", facility_req}, + {_FACILITY_I|RESPONSE, "ws", facility_res}, + {_CONNECT_B3_R, "s", connect_b3_req}, + {_CONNECT_B3_I|RESPONSE, "ws", connect_b3_res}, + {_CONNECT_B3_ACTIVE_I|RESPONSE, "", connect_b3_a_res}, + {_DISCONNECT_B3_R, "s", disconnect_b3_req}, + {_DISCONNECT_B3_I|RESPONSE, "", disconnect_b3_res}, + {_RESET_B3_R, "s", reset_b3_req}, + {_RESET_B3_I|RESPONSE, "", reset_b3_res}, + {_CONNECT_B3_T90_ACTIVE_I|RESPONSE, "ws", connect_b3_t90_a_res}, + {_CONNECT_B3_T90_ACTIVE_I|RESPONSE, "", connect_b3_t90_a_res}, + {_SELECT_B_REQ, "s", select_b_req}, + {_MANUFACTURER_R, "dws", manufacturer_req}, + {_MANUFACTURER_I|RESPONSE, "dws", manufacturer_res}, + {_MANUFACTURER_I|RESPONSE, "", manufacturer_res} +}; + +static byte * cip_bc[29][2] = { + { "", "" }, /* 0 */ + { "\x03\x80\x90\xa3", "\x03\x80\x90\xa2" }, /* 1 */ + { "\x02\x88\x90", "\x02\x88\x90" }, /* 2 */ + { "\x02\x89\x90", "\x02\x89\x90" }, /* 3 */ + { "\x03\x90\x90\xa3", "\x03\x90\x90\xa2" }, /* 4 */ + { "\x03\x91\x90\xa5", "\x03\x91\x90\xa5" }, /* 5 */ + { "\x02\x98\x90", "\x02\x98\x90" }, /* 6 */ + { "\x04\x88\xc0\xc6\xe6", "\x04\x88\xc0\xc6\xe6" }, /* 7 */ + { "\x04\x88\x90\x21\x8f", "\x04\x88\x90\x21\x8f" }, /* 8 */ + { "\x03\x91\x90\xa5", "\x03\x91\x90\xa5" }, /* 9 */ + { "", "" }, /* 10 */ + { "", "" }, /* 11 */ + { "", "" }, /* 12 */ + { "", "" }, /* 13 */ + { "", "" }, /* 14 */ + { "", "" }, /* 15 */ + + { "\x03\x80\x90\xa3", "\x03\x80\x90\xa2" }, /* 16 */ + { "\x03\x90\x90\xa3", "\x03\x90\x90\xa2" }, /* 17 */ + { "\x02\x88\x90", "\x02\x88\x90" }, /* 18 */ + { "\x02\x88\x90", "\x02\x88\x90" }, /* 19 */ + { "\x02\x88\x90", "\x02\x88\x90" }, /* 20 */ + { "\x02\x88\x90", "\x02\x88\x90" }, /* 21 */ + { "\x02\x88\x90", "\x02\x88\x90" }, /* 22 */ + { "\x02\x88\x90", "\x02\x88\x90" }, /* 23 */ + { "\x02\x88\x90", "\x02\x88\x90" }, /* 24 */ + { "\x02\x88\x90", "\x02\x88\x90" }, /* 25 */ + { "\x03\x91\x90\xa5", "\x03\x91\x90\xa5" }, /* 26 */ + { "\x03\x91\x90\xa5", "\x03\x91\x90\xa5" }, /* 27 */ + { "\x02\x88\x90", "\x02\x88\x90" } /* 28 */ +}; + +static byte * cip_hlc[29] = { + "", /* 0 */ + "", /* 1 */ + "", /* 2 */ + "", /* 3 */ + "", /* 4 */ + "", /* 5 */ + "", /* 6 */ + "", /* 7 */ + "", /* 8 */ + "", /* 9 */ + "", /* 10 */ + "", /* 11 */ + "", /* 12 */ + "", /* 13 */ + "", /* 14 */ + "", /* 15 */ + + "\x02\x91\x81", /* 16 */ + "\x02\x91\x84", /* 17 */ + "\x02\x91\xa1", /* 18 */ + "\x02\x91\xa4", /* 19 */ + "\x02\x91\xa8", /* 20 */ + "\x02\x91\xb1", /* 21 */ + "\x02\x91\xb2", /* 22 */ + "\x02\x91\xb5", /* 23 */ + "\x02\x91\xb8", /* 24 */ + "\x02\x91\xc1", /* 25 */ + "\x02\x91\x81", /* 26 */ + "\x03\x91\xe0\x01", /* 27 */ + "\x03\x91\xe0\x02" /* 28 */ +}; + +/*------------------------------------------------------------------*/ + +#define V120_HEADER_LENGTH 1 +#define V120_HEADER_EXTEND_BIT 0x80 +#define V120_HEADER_BREAK_BIT 0x40 +#define V120_HEADER_C1_BIT 0x04 +#define V120_HEADER_C2_BIT 0x08 +#define V120_HEADER_FLUSH_COND (V120_HEADER_BREAK_BIT | V120_HEADER_C1_BIT | V120_HEADER_C2_BIT) + +static byte v120_default_header[] = +{ + + 0x83 /* Ext, BR , res, res, C2 , C1 , B , F */ + +}; + +static byte v120_break_header[] = +{ + + 0xc3 | V120_HEADER_BREAK_BIT /* Ext, BR , res, res, C2 , C1 , B , F */ + +}; + + +/*------------------------------------------------------------------*/ +/* API_PUT function */ +/*------------------------------------------------------------------*/ + +word api_put(APPL * appl, CAPI_MSG * msg) +{ + word i, j, k, l, n; + word ret; + byte c; + byte controller; + DIVA_CAPI_ADAPTER * a; + PLCI * plci; + NCCI * ncci_ptr; + word ncci; + CAPI_MSG *m; + API_PARSE msg_parms[MAX_MSG_PARMS+1]; + + if (msg->header.length < sizeof (msg->header) || + msg->header.length > MAX_MSG_SIZE) { + dbug(1,dprintf("bad len")); + return _BAD_MSG; + } + + controller = (byte)((msg->header.controller &0x7f)-1); + + /* controller starts with 0 up to (max_adapter - 1) */ + if ( controller >= max_adapter ) + { + dbug(1,dprintf("invalid ctrl")); + return _BAD_MSG; + } + + a = &adapter[controller]; + plci = NULL; + if ((msg->header.plci != 0) && (msg->header.plci <= a->max_plci) && !a->adapter_disabled) + { + dbug(1,dprintf("plci=%x",msg->header.plci)); + plci = &a->plci[msg->header.plci-1]; + ncci = GET_WORD(&msg->header.ncci); + if (plci->Id + && (plci->appl + || (plci->State == INC_CON_PENDING) + || (plci->State == INC_CON_ALERT) + || (msg->header.command == (_DISCONNECT_I|RESPONSE))) + && ((ncci == 0) + || (msg->header.command == (_DISCONNECT_B3_I|RESPONSE)) + || ((ncci < MAX_NCCI+1) && (a->ncci_plci[ncci] == plci->Id)))) + { + i = plci->msg_in_read_pos; + j = plci->msg_in_write_pos; + if (j >= i) + { + if (j + msg->header.length + MSG_IN_OVERHEAD <= MSG_IN_QUEUE_SIZE) + i += MSG_IN_QUEUE_SIZE - j; + else + j = 0; + } + else + { + + n = (((CAPI_MSG *)(plci->msg_in_queue))->header.length + MSG_IN_OVERHEAD + 3) & 0xfffc; + + if (i > MSG_IN_QUEUE_SIZE - n) + i = MSG_IN_QUEUE_SIZE - n + 1; + i -= j; + } + + if (i <= ((msg->header.length + MSG_IN_OVERHEAD + 3) & 0xfffc)) + + { + dbug(0,dprintf("Q-FULL1(msg) - len=%d write=%d read=%d wrap=%d free=%d", + msg->header.length, plci->msg_in_write_pos, + plci->msg_in_read_pos, plci->msg_in_wrap_pos, i)); + + return _QUEUE_FULL; + } + c = FALSE; + if ((((byte *) msg) < ((byte *)(plci->msg_in_queue))) + || (((byte *) msg) >= ((byte *)(plci->msg_in_queue)) + sizeof(plci->msg_in_queue))) + { + if (plci->msg_in_write_pos != plci->msg_in_read_pos) + c = TRUE; + } + if (msg->header.command == _DATA_B3_R) + { + if (msg->header.length < 20) + { + dbug(1,dprintf("DATA_B3 REQ wrong length %d", msg->header.length)); + return _BAD_MSG; + } + ncci_ptr = &(a->ncci[ncci]); + n = ncci_ptr->data_pending; + l = ncci_ptr->data_ack_pending; + k = plci->msg_in_read_pos; + while (k != plci->msg_in_write_pos) + { + if (k == plci->msg_in_wrap_pos) + k = 0; + if ((((CAPI_MSG *)(&((byte *)(plci->msg_in_queue))[k]))->header.command == _DATA_B3_R) + && (((CAPI_MSG *)(&((byte *)(plci->msg_in_queue))[k]))->header.ncci == ncci)) + { + n++; + if (((CAPI_MSG *)(&((byte *)(plci->msg_in_queue))[k]))->info.data_b3_req.Flags & 0x0004) + l++; + } + + k += (((CAPI_MSG *)(&((byte *)(plci->msg_in_queue))[k]))->header.length + + MSG_IN_OVERHEAD + 3) & 0xfffc; + + } + if ((n >= MAX_DATA_B3) || (l >= MAX_DATA_ACK)) + { + dbug(0,dprintf("Q-FULL2(data) - pending=%d/%d ack_pending=%d/%d", + ncci_ptr->data_pending, n, ncci_ptr->data_ack_pending, l)); + + return _QUEUE_FULL; + } + if (plci->req_in || plci->internal_command) + { + if ((((byte *) msg) >= ((byte *)(plci->msg_in_queue))) + && (((byte *) msg) < ((byte *)(plci->msg_in_queue)) + sizeof(plci->msg_in_queue))) + { + dbug(0,dprintf("Q-FULL3(requeue)")); + + return _QUEUE_FULL; + } + c = TRUE; + } + } + else + { + if (plci->req_in || plci->internal_command) + c = TRUE; + else + { + plci->command = msg->header.command; + plci->number = msg->header.number; + } + } + if (c) + { + dbug(1,dprintf("enqueue msg(0x%04x,0x%x,0x%x) - len=%d write=%d read=%d wrap=%d free=%d", + msg->header.command, plci->req_in, plci->internal_command, + msg->header.length, plci->msg_in_write_pos, + plci->msg_in_read_pos, plci->msg_in_wrap_pos, i)); + if (j == 0) + plci->msg_in_wrap_pos = plci->msg_in_write_pos; + m = (CAPI_MSG *)(&((byte *)(plci->msg_in_queue))[j]); + for (i = 0; i < msg->header.length; i++) + ((byte *)(plci->msg_in_queue))[j++] = ((byte *) msg)[i]; + if (m->header.command == _DATA_B3_R) + { + + m->info.data_b3_req.Data = (dword)(TransmitBufferSet (appl, m->info.data_b3_req.Data)); + + } + + j = (j + 3) & 0xfffc; + + *((APPL * *)(&((byte *)(plci->msg_in_queue))[j])) = appl; + plci->msg_in_write_pos = j + MSG_IN_OVERHEAD; + return 0; + } + } + else + { + plci = NULL; + } + } + dbug(1,dprintf("com=%x",msg->header.command)); + + for(j=0;j<MAX_MSG_PARMS+1;j++) msg_parms[j].length = 0; + for(i=0, ret = _BAD_MSG; + i<(sizeof(ftable)/sizeof(struct _ftable)); + i++) { + + if(ftable[i].command==msg->header.command) { + /* break loop if the message is correct, otherwise continue scan */ + /* (for example: CONNECT_B3_T90_ACT_RES has two specifications) */ + if(!api_parse(msg->info.b,(word)(msg->header.length-12),ftable[i].format,msg_parms)) { + ret = 0; + break; + } + for(j=0;j<MAX_MSG_PARMS+1;j++) msg_parms[j].length = 0; + } + } + if(ret) { + dbug(1,dprintf("BAD_MSG")); + if(plci) plci->command = 0; + return ret; + } + + + c = ftable[i].function(GET_DWORD(&msg->header.controller), + msg->header.number, + a, + plci, + appl, + msg_parms); + + channel_xmit_extended_xon (plci); + + if(c==1) send_req(plci); + if(c==2 && plci) plci->req_in = plci->req_in_start = plci->req_out = 0; + if(plci && !plci->req_in) plci->command = 0; + return 0; +} + + +/*------------------------------------------------------------------*/ +/* api_parse function, check the format of api messages */ +/*------------------------------------------------------------------*/ + +word api_parse(byte * msg, word length, byte * format, API_PARSE * parms) +{ + word i; + word p; + + for(i=0,p=0; format[i]; i++) { + if(parms) + { + parms[i].info = &msg[p]; + } + switch(format[i]) { + case 'b': + p +=1; + break; + case 'w': + p +=2; + break; + case 'd': + p +=4; + break; + case 's': + if(msg[p]==0xff) { + parms[i].info +=2; + parms[i].length = msg[p+1] + (msg[p+2]<<8); + p +=(parms[i].length +3); + } + else { + parms[i].length = msg[p]; + p +=(parms[i].length +1); + } + break; + } + + if(p>length) return TRUE; + } + if(parms) parms[i].info = NULL; + return FALSE; +} + +void api_save_msg(API_PARSE *in, byte *format, API_SAVE *out) +{ + word i, j, n = 0; + byte *p; + + p = out->info; + for (i = 0; format[i] != '\0'; i++) + { + out->parms[i].info = p; + out->parms[i].length = in[i].length; + switch (format[i]) + { + case 'b': + n = 1; + break; + case 'w': + n = 2; + break; + case 'd': + n = 4; + break; + case 's': + n = in[i].length + 1; + break; + } + for (j = 0; j < n; j++) + *(p++) = in[i].info[j]; + } + out->parms[i].info = NULL; + out->parms[i].length = 0; +} + +void api_load_msg(API_SAVE *in, API_PARSE *out) +{ + word i; + + i = 0; + do + { + out[i].info = in->parms[i].info; + out[i].length = in->parms[i].length; + } while (in->parms[i++].info); +} + + +/*------------------------------------------------------------------*/ +/* CAPI remove function */ +/*------------------------------------------------------------------*/ + +word api_remove_start(void) +{ + word i; + word j; + + if(!remove_started) { + remove_started = TRUE; + for(i=0;i<max_adapter;i++) { + if(adapter[i].request) { + for(j=0;j<adapter[i].max_plci;j++) { + if(adapter[i].plci[j].Sig.Id) plci_remove(&adapter[i].plci[j]); + } + } + } + return 1; + } + else { + for(i=0;i<max_adapter;i++) { + if(adapter[i].request) { + for(j=0;j<adapter[i].max_plci;j++) { + if(adapter[i].plci[j].Sig.Id) return 1; + } + } + } + } + api_remove_complete(); + return 0; +} + + +/*------------------------------------------------------------------*/ +/* internal command queue */ +/*------------------------------------------------------------------*/ + +static void init_internal_command_queue (PLCI *plci) +{ + word i; + + dbug (1, dprintf ("%s,%d: init_internal_command_queue", + (char *)(FILE_), __LINE__)); + + plci->internal_command = 0; + for (i = 0; i < MAX_INTERNAL_COMMAND_LEVELS; i++) + plci->internal_command_queue[i] = NULL; +} + + +static void start_internal_command (dword Id, PLCI *plci, t_std_internal_command command_function) +{ + word i; + + dbug (1, dprintf ("[%06lx] %s,%d: start_internal_command", + UnMapId (Id), (char *)(FILE_), __LINE__)); + + if (plci->internal_command == 0) + { + plci->internal_command_queue[0] = command_function; + (* command_function)(Id, plci, OK); + } + else + { + i = 1; + while (plci->internal_command_queue[i] != 0) + i++; + plci->internal_command_queue[i] = command_function; + } +} + + +static void next_internal_command (dword Id, PLCI *plci) +{ + word i; + + dbug (1, dprintf ("[%06lx] %s,%d: next_internal_command", + UnMapId (Id), (char *)(FILE_), __LINE__)); + + plci->internal_command = 0; + plci->internal_command_queue[0] = NULL; + while (plci->internal_command_queue[1] != 0) + { + for (i = 0; i < MAX_INTERNAL_COMMAND_LEVELS - 1; i++) + plci->internal_command_queue[i] = plci->internal_command_queue[i+1]; + plci->internal_command_queue[MAX_INTERNAL_COMMAND_LEVELS - 1] = NULL; + (*(plci->internal_command_queue[0]))(Id, plci, OK); + if (plci->internal_command != 0) + return; + plci->internal_command_queue[0] = NULL; + } +} + + +/*------------------------------------------------------------------*/ +/* NCCI allocate/remove function */ +/*------------------------------------------------------------------*/ + +static dword ncci_mapping_bug = 0; + +static word get_ncci (PLCI *plci, byte ch, word force_ncci) +{ + DIVA_CAPI_ADAPTER *a; + word ncci, i, j, k; + + a = plci->adapter; + if (!ch || a->ch_ncci[ch]) + { + ncci_mapping_bug++; + dbug(1,dprintf("NCCI mapping exists %ld %02x %02x %02x-%02x", + ncci_mapping_bug, ch, force_ncci, a->ncci_ch[a->ch_ncci[ch]], a->ch_ncci[ch])); + ncci = ch; + } + else + { + if (force_ncci) + ncci = force_ncci; + else + { + if ((ch < MAX_NCCI+1) && !a->ncci_ch[ch]) + ncci = ch; + else + { + ncci = 1; + while ((ncci < MAX_NCCI+1) && a->ncci_ch[ncci]) + ncci++; + if (ncci == MAX_NCCI+1) + { + ncci_mapping_bug++; + i = 1; + do + { + j = 1; + while ((j < MAX_NCCI+1) && (a->ncci_ch[j] != i)) + j++; + k = j; + if (j < MAX_NCCI+1) + { + do + { + j++; + } while ((j < MAX_NCCI+1) && (a->ncci_ch[j] != i)); + } + } while ((i < MAX_NL_CHANNEL+1) && (j < MAX_NCCI+1)); + if (i < MAX_NL_CHANNEL+1) + { + dbug(1,dprintf("NCCI mapping overflow %ld %02x %02x %02x-%02x-%02x", + ncci_mapping_bug, ch, force_ncci, i, k, j)); + } + else + { + dbug(1,dprintf("NCCI mapping overflow %ld %02x %02x", + ncci_mapping_bug, ch, force_ncci)); + } + ncci = ch; + } + } + a->ncci_plci[ncci] = plci->Id; + a->ncci_state[ncci] = IDLE; + if (!plci->ncci_ring_list) + plci->ncci_ring_list = ncci; + else + a->ncci_next[ncci] = a->ncci_next[plci->ncci_ring_list]; + a->ncci_next[plci->ncci_ring_list] = (byte) ncci; + } + a->ncci_ch[ncci] = ch; + a->ch_ncci[ch] = (byte) ncci; + dbug(1,dprintf("NCCI mapping established %ld %02x %02x %02x-%02x", + ncci_mapping_bug, ch, force_ncci, ch, ncci)); + } + return (ncci); +} + + +static void ncci_free_receive_buffers (PLCI *plci, word ncci) +{ + DIVA_CAPI_ADAPTER *a; + APPL *appl; + word i, ncci_code; + dword Id; + + a = plci->adapter; + Id = (((dword) ncci) << 16) | (((word)(plci->Id)) << 8) | a->Id; + if (ncci) + { + if (a->ncci_plci[ncci] == plci->Id) + { + if (!plci->appl) + { + ncci_mapping_bug++; + dbug(1,dprintf("NCCI mapping appl expected %ld %08lx", + ncci_mapping_bug, Id)); + } + else + { + appl = plci->appl; + ncci_code = ncci | (((word) a->Id) << 8); + for (i = 0; i < appl->MaxBuffer; i++) + { + if ((appl->DataNCCI[i] == ncci_code) + && (((byte)(appl->DataFlags[i] >> 8)) == plci->Id)) + { + appl->DataNCCI[i] = 0; + } + } + } + } + } + else + { + for (ncci = 1; ncci < MAX_NCCI+1; ncci++) + { + if (a->ncci_plci[ncci] == plci->Id) + { + if (!plci->appl) + { + ncci_mapping_bug++; + dbug(1,dprintf("NCCI mapping no appl %ld %08lx", + ncci_mapping_bug, Id)); + } + else + { + appl = plci->appl; + ncci_code = ncci | (((word) a->Id) << 8); + for (i = 0; i < appl->MaxBuffer; i++) + { + if ((appl->DataNCCI[i] == ncci_code) + && (((byte)(appl->DataFlags[i] >> 8)) == plci->Id)) + { + appl->DataNCCI[i] = 0; + } + } + } + } + } + } +} + + +static void cleanup_ncci_data (PLCI *plci, word ncci) +{ + NCCI *ncci_ptr; + + if (ncci && (plci->adapter->ncci_plci[ncci] == plci->Id)) + { + ncci_ptr = &(plci->adapter->ncci[ncci]); + if (plci->appl) + { + while (ncci_ptr->data_pending != 0) + { + if (!plci->data_sent || (ncci_ptr->DBuffer[ncci_ptr->data_out].P != plci->data_sent_ptr)) + TransmitBufferFree (plci->appl, ncci_ptr->DBuffer[ncci_ptr->data_out].P); + (ncci_ptr->data_out)++; + if (ncci_ptr->data_out == MAX_DATA_B3) + ncci_ptr->data_out = 0; + (ncci_ptr->data_pending)--; + } + } + ncci_ptr->data_out = 0; + ncci_ptr->data_pending = 0; + ncci_ptr->data_ack_out = 0; + ncci_ptr->data_ack_pending = 0; + } +} + + +static void ncci_remove (PLCI *plci, word ncci, byte preserve_ncci) +{ + DIVA_CAPI_ADAPTER *a; + dword Id; + word i; + + a = plci->adapter; + Id = (((dword) ncci) << 16) | (((word)(plci->Id)) << 8) | a->Id; + if (!preserve_ncci) + ncci_free_receive_buffers (plci, ncci); + if (ncci) + { + if (a->ncci_plci[ncci] != plci->Id) + { + ncci_mapping_bug++; + dbug(1,dprintf("NCCI mapping doesn't exist %ld %08lx %02x", + ncci_mapping_bug, Id, preserve_ncci)); + } + else + { + cleanup_ncci_data (plci, ncci); + dbug(1,dprintf("NCCI mapping released %ld %08lx %02x %02x-%02x", + ncci_mapping_bug, Id, preserve_ncci, a->ncci_ch[ncci], ncci)); + a->ch_ncci[a->ncci_ch[ncci]] = 0; + if (!preserve_ncci) + { + a->ncci_ch[ncci] = 0; + a->ncci_plci[ncci] = 0; + a->ncci_state[ncci] = IDLE; + i = plci->ncci_ring_list; + while ((i != 0) && (a->ncci_next[i] != plci->ncci_ring_list) && (a->ncci_next[i] != ncci)) + i = a->ncci_next[i]; + if ((i != 0) && (a->ncci_next[i] == ncci)) + { + if (i == ncci) + plci->ncci_ring_list = 0; + else if (plci->ncci_ring_list == ncci) + plci->ncci_ring_list = i; + a->ncci_next[i] = a->ncci_next[ncci]; + } + a->ncci_next[ncci] = 0; + } + } + } + else + { + for (ncci = 1; ncci < MAX_NCCI+1; ncci++) + { + if (a->ncci_plci[ncci] == plci->Id) + { + cleanup_ncci_data (plci, ncci); + dbug(1,dprintf("NCCI mapping released %ld %08lx %02x %02x-%02x", + ncci_mapping_bug, Id, preserve_ncci, a->ncci_ch[ncci], ncci)); + a->ch_ncci[a->ncci_ch[ncci]] = 0; + if (!preserve_ncci) + { + a->ncci_ch[ncci] = 0; + a->ncci_plci[ncci] = 0; + a->ncci_state[ncci] = IDLE; + a->ncci_next[ncci] = 0; + } + } + } + if (!preserve_ncci) + plci->ncci_ring_list = 0; + } +} + + +/*------------------------------------------------------------------*/ +/* PLCI remove function */ +/*------------------------------------------------------------------*/ + +static void plci_free_msg_in_queue (PLCI *plci) +{ + word i; + + if (plci->appl) + { + i = plci->msg_in_read_pos; + while (i != plci->msg_in_write_pos) + { + if (i == plci->msg_in_wrap_pos) + i = 0; + if (((CAPI_MSG *)(&((byte *)(plci->msg_in_queue))[i]))->header.command == _DATA_B3_R) + { + + TransmitBufferFree (plci->appl, + (byte *)(((CAPI_MSG *)(&((byte *)(plci->msg_in_queue))[i]))->info.data_b3_req.Data)); + + } + + i += (((CAPI_MSG *)(&((byte *)(plci->msg_in_queue))[i]))->header.length + + MSG_IN_OVERHEAD + 3) & 0xfffc; + + } + } + plci->msg_in_write_pos = MSG_IN_QUEUE_SIZE; + plci->msg_in_read_pos = MSG_IN_QUEUE_SIZE; + plci->msg_in_wrap_pos = MSG_IN_QUEUE_SIZE; +} + + +static void plci_remove(PLCI * plci) +{ + + if(!plci) { + dbug(1,dprintf("plci_remove(no plci)")); + return; + } + init_internal_command_queue (plci); + dbug(1,dprintf("plci_remove(%x,tel=%x)",plci->Id,plci->tel)); + if(plci_remove_check(plci)) + { + return; + } + if (plci->Sig.Id == 0xff) + { + dbug(1,dprintf("D-channel X.25 plci->NL.Id:%0x", plci->NL.Id)); + if (plci->NL.Id && !plci->nl_remove_id) + { + nl_req_ncci(plci,REMOVE,0); + send_req(plci); + } + } + else + { + if (!plci->sig_remove_id + && (plci->Sig.Id + || (plci->req_in!=plci->req_out) + || (plci->nl_req || plci->sig_req))) + { + sig_req(plci,HANGUP,0); + send_req(plci); + } + } + ncci_remove (plci, 0, FALSE); + plci_free_msg_in_queue (plci); + + plci->channels = 0; + plci->appl = NULL; + if ((plci->State == INC_CON_PENDING) || (plci->State == INC_CON_ALERT)) + plci->State = OUTG_DIS_PENDING; +} + +/*------------------------------------------------------------------*/ +/* Application Group function helpers */ +/*------------------------------------------------------------------*/ + +static void set_group_ind_mask (PLCI *plci) +{ + word i; + + for (i = 0; i < C_IND_MASK_DWORDS; i++) + plci->group_optimization_mask_table[i] = 0xffffffffL; +} + +static void clear_group_ind_mask_bit (PLCI *plci, word b) +{ + plci->group_optimization_mask_table[b >> 5] &= ~(1L << (b & 0x1f)); +} + +static byte test_group_ind_mask_bit (PLCI *plci, word b) +{ + return ((plci->group_optimization_mask_table[b >> 5] & (1L << (b & 0x1f))) != 0); +} + +/*------------------------------------------------------------------*/ +/* c_ind_mask operations for arbitrary MAX_APPL */ +/*------------------------------------------------------------------*/ + +static void clear_c_ind_mask (PLCI *plci) +{ + word i; + + for (i = 0; i < C_IND_MASK_DWORDS; i++) + plci->c_ind_mask_table[i] = 0; +} + +static byte c_ind_mask_empty (PLCI *plci) +{ + word i; + + i = 0; + while ((i < C_IND_MASK_DWORDS) && (plci->c_ind_mask_table[i] == 0)) + i++; + return (i == C_IND_MASK_DWORDS); +} + +static void set_c_ind_mask_bit (PLCI *plci, word b) +{ + plci->c_ind_mask_table[b >> 5] |= (1L << (b & 0x1f)); +} + +static void clear_c_ind_mask_bit (PLCI *plci, word b) +{ + plci->c_ind_mask_table[b >> 5] &= ~(1L << (b & 0x1f)); +} + +static byte test_c_ind_mask_bit (PLCI *plci, word b) +{ + return ((plci->c_ind_mask_table[b >> 5] & (1L << (b & 0x1f))) != 0); +} + +static void dump_c_ind_mask (PLCI *plci) +{ +static char hex_digit_table[0x10] = + {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'}; + word i, j, k; + dword d; + char *p; + char buf[40]; + + for (i = 0; i < C_IND_MASK_DWORDS; i += 4) + { + p = buf + 36; + *p = '\0'; + for (j = 0; j < 4; j++) + { + if (i+j < C_IND_MASK_DWORDS) + { + d = plci->c_ind_mask_table[i+j]; + for (k = 0; k < 8; k++) + { + *(--p) = hex_digit_table[d & 0xf]; + d >>= 4; + } + } + else if (i != 0) + { + for (k = 0; k < 8; k++) + *(--p) = ' '; + } + *(--p) = ' '; + } + dbug(1,dprintf ("c_ind_mask =%s", (char *) p)); + } +} + + + + + +#define dump_plcis(a) + + + +/*------------------------------------------------------------------*/ +/* translation function for each message */ +/*------------------------------------------------------------------*/ + +byte connect_req(dword Id, word Number, DIVA_CAPI_ADAPTER * a, PLCI * plci, APPL * appl, API_PARSE * parms) +{ + word ch; + word i; + word Info; + word CIP; + byte LinkLayer; + API_PARSE * ai; + API_PARSE * bp; + API_PARSE ai_parms[5]; + word channel = 0; + dword ch_mask; + byte m; + static byte esc_chi[35] = {0x02,0x18,0x01}; + static byte lli[2] = {0x01,0x00}; + byte noCh = 0; + word dir = 0; + byte *p_chi = ""; + + for(i=0;i<5;i++) ai_parms[i].length = 0; + + dbug(1,dprintf("connect_req(%d)",parms->length)); + Info = _WRONG_IDENTIFIER; + if(a) + { + if(a->adapter_disabled) + { + dbug(1,dprintf("adapter disabled")); + Id = ((word)1<<8)|a->Id; + sendf(appl,_CONNECT_R|CONFIRM,Id,Number,"w",0); + sendf(appl, _DISCONNECT_I, Id, 0, "w", _L1_ERROR); + return FALSE; + } + Info = _OUT_OF_PLCI; + if((i=get_plci(a))) + { + Info = 0; + plci = &a->plci[i-1]; + plci->appl = appl; + plci->call_dir = CALL_DIR_OUT | CALL_DIR_ORIGINATE; + /* check 'external controller' bit for codec support */ + if(Id & EXT_CONTROLLER) + { + if(AdvCodecSupport(a, plci, appl, 0) ) + { + plci->Id = 0; + sendf(appl, _CONNECT_R|CONFIRM, Id, Number, "w", _WRONG_IDENTIFIER); + return 2; + } + } + ai = &parms[9]; + bp = &parms[5]; + ch = 0; + if(bp->length)LinkLayer = bp->info[3]; + else LinkLayer = 0; + if(ai->length) + { + ch=0xffff; + if(!api_parse(&ai->info[1],(word)ai->length,"ssss",ai_parms)) + { + ch = 0; + if(ai_parms[0].length) + { + ch = GET_WORD(ai_parms[0].info+1); + if(ch>4) ch=0; /* safety -> ignore ChannelID */ + if(ch==4) /* explizit CHI in message */ + { + /* check length of B-CH struct */ + if((ai_parms[0].info)[3]>=1) + { + if((ai_parms[0].info)[4]==CHI) + { + p_chi = &((ai_parms[0].info)[5]); + } + else + { + p_chi = &((ai_parms[0].info)[3]); + } + if(p_chi[0]>35) /* check length of channel ID */ + { + Info = _WRONG_MESSAGE_FORMAT; + } + } + else Info = _WRONG_MESSAGE_FORMAT; + } + + if(ch==3 && ai_parms[0].length>=7 && ai_parms[0].length<=36) + { + dir = GET_WORD(ai_parms[0].info+3); + ch_mask = 0; + m = 0x3f; + for(i=0; i+5<=ai_parms[0].length; i++) + { + if(ai_parms[0].info[i+5]!=0) + { + if((ai_parms[0].info[i+5] | m) != 0xff) + Info = _WRONG_MESSAGE_FORMAT; + else + { + if (ch_mask == 0) + channel = i; + ch_mask |= 1L << i; + } + } + m = 0; + } + if (ch_mask == 0) + Info = _WRONG_MESSAGE_FORMAT; + if (!Info) + { + if ((ai_parms[0].length == 36) || (ch_mask != ((dword)(1L << channel)))) + { + esc_chi[0] = (byte)(ai_parms[0].length - 2); + for(i=0; i+5<=ai_parms[0].length; i++) + esc_chi[i+3] = ai_parms[0].info[i+5]; + } + else + esc_chi[0] = 2; + esc_chi[2] = (byte)channel; + plci->b_channel = (byte)channel; /* not correct for ETSI ch 17..31 */ + add_p(plci,LLI,lli); + add_p(plci,ESC,esc_chi); + plci->State = LOCAL_CONNECT; + if(!dir) plci->call_dir |= CALL_DIR_FORCE_OUTG_NL; /* dir 0=DTE, 1=DCE */ + } + } + } + } + else Info = _WRONG_MESSAGE_FORMAT; + } + + dbug(1,dprintf("ch=%x,dir=%x,p_ch=%d",ch,dir,channel)); + plci->command = _CONNECT_R; + plci->number = Number; + /* x.31 or D-ch free SAPI in LinkLayer? */ + if(ch==1 && LinkLayer!=3 && LinkLayer!=12) noCh = TRUE; + if((ch==0 || ch==2 || noCh || ch==3 || ch==4) && !Info) + { + /* B-channel used for B3 connections (ch==0), or no B channel */ + /* is used (ch==2) or perm. connection (3) is used do a CALL */ + if(noCh) Info = add_b1(plci,&parms[5],2,0); /* no resource */ + else Info = add_b1(plci,&parms[5],ch,0); + add_s(plci,OAD,&parms[2]); + add_s(plci,OSA,&parms[4]); + add_s(plci,BC,&parms[6]); + add_s(plci,LLC,&parms[7]); + add_s(plci,HLC,&parms[8]); + CIP = GET_WORD(parms[0].info); + if (a->Info_Mask[appl->Id-1] & 0x200) + { + /* early B3 connect (CIP mask bit 9) no release after a disc */ + add_p(plci,LLI,"\x01\x01"); + } + if(GET_WORD(parms[0].info)<29) { + add_p(plci,BC,cip_bc[GET_WORD(parms[0].info)][a->u_law]); + add_p(plci,HLC,cip_hlc[GET_WORD(parms[0].info)]); + } + add_p(plci,UID,"\x06\x43\x61\x70\x69\x32\x30"); + sig_req(plci,ASSIGN,DSIG_ID); + } + else if(ch==1) { + + /* D-Channel used for B3 connections */ + plci->Sig.Id = 0xff; + Info = 0; + } + + if(!Info && ch!=2 && !noCh ) { + Info = add_b23(plci,&parms[5]); + if(!Info) { + if(!(plci->tel && !plci->adv_nl))nl_req_ncci(plci,ASSIGN,0); + } + } + + if(!Info) + { + if(ch==0 || ch==2 || ch==3 || noCh || ch==4) + { + if(plci->spoofed_msg==SPOOFING_REQUIRED) + { + api_save_msg(parms, "wsssssssss", &plci->saved_msg); + plci->spoofed_msg = CALL_REQ; + plci->internal_command = BLOCK_PLCI; + plci->command = 0; + dbug(1,dprintf("Spoof")); + send_req(plci); + return FALSE; + } + if(ch==4)add_p(plci,CHI,p_chi); + add_s(plci,CPN,&parms[1]); + add_s(plci,DSA,&parms[3]); + if(noCh) add_p(plci,ESC,"\x02\x18\xfd"); /* D-channel, no B-L3 */ + add_ai(plci,&parms[9]); + if(!dir)sig_req(plci,CALL_REQ,0); + else + { + plci->command = PERM_LIST_REQ; + plci->appl = appl; + sig_req(plci,LISTEN_REQ,0); + send_req(plci); + return FALSE; + } + } + send_req(plci); + return FALSE; + } + plci->Id = 0; + } + } + sendf(appl, + _CONNECT_R|CONFIRM, + Id, + Number, + "w",Info); + return 2; +} + +byte connect_res(dword Id, word Number, DIVA_CAPI_ADAPTER * a, PLCI * plci, APPL * appl, API_PARSE * parms) +{ + word i, Info; + word Reject; + static byte cau_t[] = {0,0,0x90,0x91,0xac,0x9d,0x86,0xd8,0x9b}; + static byte esc_t[] = {0x03,0x08,0x00,0x00}; + API_PARSE * ai; + API_PARSE ai_parms[5]; + word ch=0; + + if(!plci) { + dbug(1,dprintf("connect_res(no plci)")); + return 0; /* no plci, no send */ + } + + dbug(1,dprintf("connect_res(State=0x%x)",plci->State)); + for(i=0;i<5;i++) ai_parms[i].length = 0; + ai = &parms[5]; + dbug(1,dprintf("ai->length=%d",ai->length)); + + if(ai->length) + { + if(!api_parse(&ai->info[1],(word)ai->length,"ssss",ai_parms)) + { + dbug(1,dprintf("ai_parms[0].length=%d/0x%x",ai_parms[0].length,GET_WORD(ai_parms[0].info+1))); + ch = 0; + if(ai_parms[0].length) + { + ch = GET_WORD(ai_parms[0].info+1); + dbug(1,dprintf("BCH-I=0x%x",ch)); + } + } + } + + if(plci->State==INC_CON_CONNECTED_ALERT) + { + dbug(1,dprintf("Connected Alert Call_Res")); + if (a->Info_Mask[appl->Id-1] & 0x200) + { + /* early B3 connect (CIP mask bit 9) no release after a disc */ + add_p(plci,LLI,"\x01\x01"); + } + add_s(plci, CONN_NR, &parms[2]); + add_s(plci, LLC, &parms[4]); + add_ai(plci, &parms[5]); + plci->State = INC_CON_ACCEPT; + sig_req(plci, CALL_RES,0); + return 1; + } + else if(plci->State==INC_CON_PENDING || plci->State==INC_CON_ALERT) { + clear_c_ind_mask_bit (plci, (word)(appl->Id-1)); + dump_c_ind_mask (plci); + Reject = GET_WORD(parms[0].info); + dbug(1,dprintf("Reject=0x%x",Reject)); + if(Reject) + { + if(c_ind_mask_empty (plci)) + { + if((Reject&0xff00)==0x3400) + { + esc_t[2] = ((byte)(Reject&0x00ff)) | 0x80; + add_p(plci,ESC,esc_t); + add_ai(plci, &parms[5]); + sig_req(plci,REJECT,0); + } + else if(Reject==1 || Reject>9) + { + add_ai(plci, &parms[5]); + sig_req(plci,HANGUP,0); + } + else + { + esc_t[2] = cau_t[(Reject&0x000f)]; + add_p(plci,ESC,esc_t); + add_ai(plci, &parms[5]); + sig_req(plci,REJECT,0); + } + plci->appl = appl; + } + else + { + sendf(appl, _DISCONNECT_I, Id, 0, "w", _OTHER_APPL_CONNECTED); + } + } + else { + plci->appl = appl; + if(Id & EXT_CONTROLLER){ + if(AdvCodecSupport(a, plci, appl, 0)){ + dbug(1,dprintf("connect_res(error from AdvCodecSupport)")); + sig_req(plci,HANGUP,0); + return 1; + } + if(plci->tel == ADV_VOICE && a->AdvCodecPLCI) + { + Info = add_b23(plci, &parms[1]); + if (Info) + { + dbug(1,dprintf("connect_res(error from add_b23)")); + sig_req(plci,HANGUP,0); + return 1; + } + if(plci->adv_nl) + { + nl_req_ncci(plci, ASSIGN, 0); + } + } + } + else + { + plci->tel = 0; + if(ch!=2) + { + Info = add_b23(plci, &parms[1]); + if (Info) + { + dbug(1,dprintf("connect_res(error from add_b23 2)")); + sig_req(plci,HANGUP,0); + return 1; + } + } + nl_req_ncci(plci, ASSIGN, 0); + } + + if(plci->spoofed_msg==SPOOFING_REQUIRED) + { + api_save_msg(parms, "wsssss", &plci->saved_msg); + plci->spoofed_msg = CALL_RES; + plci->internal_command = BLOCK_PLCI; + plci->command = 0; + dbug(1,dprintf("Spoof")); + } + else + { + add_b1 (plci, &parms[1], ch, plci->B1_facilities); + if (a->Info_Mask[appl->Id-1] & 0x200) + { + /* early B3 connect (CIP mask bit 9) no release after a disc */ + add_p(plci,LLI,"\x01\x01"); + } + add_s(plci, CONN_NR, &parms[2]); + add_s(plci, LLC, &parms[4]); + add_ai(plci, &parms[5]); + plci->State = INC_CON_ACCEPT; + sig_req(plci, CALL_RES,0); + } + + for(i=0; i<max_appl; i++) { + if(test_c_ind_mask_bit (plci, i)) { + sendf(&application[i], _DISCONNECT_I, Id, 0, "w", _OTHER_APPL_CONNECTED); + } + } + } + } + return 1; +} + +byte connect_a_res(dword Id, word Number, DIVA_CAPI_ADAPTER * a, PLCI * plci, APPL * appl, API_PARSE * msg) +{ + dbug(1,dprintf("connect_a_res")); + return FALSE; +} + +byte disconnect_req(dword Id, word Number, DIVA_CAPI_ADAPTER * a, PLCI * plci, APPL * appl, API_PARSE * msg) +{ + word Info; + word i; + + dbug(1,dprintf("disconnect_req")); + + Info = _WRONG_IDENTIFIER; + + if(plci) + { + if(plci->State==INC_CON_PENDING || plci->State==INC_CON_ALERT) + { + clear_c_ind_mask_bit (plci, (word)(appl->Id-1)); + plci->appl = appl; + for(i=0; i<max_appl; i++) + { + if(test_c_ind_mask_bit (plci, i)) + sendf(&application[i], _DISCONNECT_I, Id, 0, "w", 0); + } + plci->State = OUTG_DIS_PENDING; + } + if(plci->Sig.Id && plci->appl) + { + Info = 0; + if(plci->Sig.Id!=0xff) + { + if(plci->State!=INC_DIS_PENDING) + { + add_ai(plci, &msg[0]); + sig_req(plci,HANGUP,0); + plci->State = OUTG_DIS_PENDING; + return 1; + } + } + else + { + if (plci->NL.Id && !plci->nl_remove_id) + { + mixer_remove (plci); + nl_req_ncci(plci,REMOVE,0); + sendf(appl,_DISCONNECT_R|CONFIRM,Id,Number,"w",0); + sendf(appl, _DISCONNECT_I, Id, 0, "w", 0); + plci->State = INC_DIS_PENDING; + } + return 1; + } + } + } + + if(!appl) return FALSE; + sendf(appl, _DISCONNECT_R|CONFIRM, Id, Number, "w",Info); + return FALSE; +} + +byte disconnect_res(dword Id, word Number, DIVA_CAPI_ADAPTER * a, PLCI * plci, APPL * appl, API_PARSE * msg) +{ + dbug(1,dprintf("disconnect_res")); + if(plci) + { + /* clear ind mask bit, just in case of collsion of */ + /* DISCONNECT_IND and CONNECT_RES */ + clear_c_ind_mask_bit (plci, (word)(appl->Id-1)); + ncci_free_receive_buffers (plci, 0); + if(plci_remove_check(plci)) + { + return 0; + } + if(plci->State==INC_DIS_PENDING + || plci->State==SUSPENDING) { + if(c_ind_mask_empty (plci)) { + if(plci->State!=SUSPENDING)plci->State = IDLE; + dbug(1,dprintf("chs=%d",plci->channels)); + if(!plci->channels) { + plci_remove(plci); + } + } + } + } + return 0; +} + +byte listen_req(dword Id, word Number, DIVA_CAPI_ADAPTER * a, PLCI * plci, APPL * appl, API_PARSE * parms) +{ + word Info; + byte i; + + dbug(1,dprintf("listen_req(Appl=0x%x)",appl->Id)); + + Info = _WRONG_IDENTIFIER; + if(a) { + Info = 0; + a->Info_Mask[appl->Id-1] = GET_DWORD(parms[0].info); + a->CIP_Mask[appl->Id-1] = GET_DWORD(parms[1].info); + dbug(1,dprintf("CIP_MASK=0x%lx",GET_DWORD(parms[1].info))); + if (a->Info_Mask[appl->Id-1] & 0x200){ /* early B3 connect provides */ + a->Info_Mask[appl->Id-1] |= 0x10; /* call progression infos */ + } + + /* check if external controller listen and switch listen on or off*/ + if(Id&EXT_CONTROLLER && GET_DWORD(parms[1].info)){ + if(a->profile.Global_Options & ON_BOARD_CODEC) { + dummy_plci.State = IDLE; + a->codec_listen[appl->Id-1] = &dummy_plci; + a->TelOAD[0] = (byte)(parms[3].length); + for(i=1;parms[3].length>=i && i<22;i++) { + a->TelOAD[i] = parms[3].info[i]; + } + a->TelOAD[i] = 0; + a->TelOSA[0] = (byte)(parms[4].length); + for(i=1;parms[4].length>=i && i<22;i++) { + a->TelOSA[i] = parms[4].info[i]; + } + a->TelOSA[i] = 0; + } + else Info = 0x2002; /* wrong controller, codec not supported */ + } + else{ /* clear listen */ + a->codec_listen[appl->Id-1] = (PLCI *)0; + } + } + sendf(appl, + _LISTEN_R|CONFIRM, + Id, + Number, + "w",Info); + + if (a) listen_check(a); + return FALSE; +} + +byte info_req(dword Id, word Number, DIVA_CAPI_ADAPTER * a, PLCI * plci, APPL * appl, API_PARSE * msg) +{ + word i; + API_PARSE * ai; + PLCI * rc_plci = NULL; + API_PARSE ai_parms[5]; + word Info = 0; + + dbug(1,dprintf("info_req")); + for(i=0;i<5;i++) ai_parms[i].length = 0; + + ai = &msg[1]; + + if(ai->length) + { + if(api_parse(&ai->info[1],(word)ai->length,"ssss",ai_parms)) + { + dbug(1,dprintf("AddInfo wrong")); + Info = _WRONG_MESSAGE_FORMAT; + } + } + if(!a) Info = _WRONG_STATE; + + if(!Info && plci) + { /* no fac, with CPN, or KEY */ + rc_plci = plci; + if(!ai_parms[3].length && plci->State && (msg[0].length || ai_parms[1].length) ) + { + /* overlap sending option */ + dbug(1,dprintf("OvlSnd")); + add_s(plci,CPN,&msg[0]); + add_s(plci,KEY,&ai_parms[1]); + sig_req(plci,INFO_REQ,0); + send_req(plci); + return FALSE; + } + + if(plci->State && ai_parms[2].length) + { + /* User_Info option */ + dbug(1,dprintf("UUI")); + add_s(plci,UUI,&ai_parms[2]); + sig_req(plci,USER_DATA,0); + } + else if(plci->State && ai_parms[3].length) + { + /* Facility option */ + dbug(1,dprintf("FAC")); + add_s(plci,CPN,&msg[0]); + add_ai(plci, &msg[1]); + sig_req(plci,FACILITY_REQ,0); + } + else + { + Info = _WRONG_STATE; + } + } + else if((ai_parms[1].length || ai_parms[2].length || ai_parms[3].length) && !Info) + { + /* NCR_Facility option -> send UUI and Keypad too */ + dbug(1,dprintf("NCR_FAC")); + if((i=get_plci(a))) + { + rc_plci = &a->plci[i-1]; + appl->NullCREnable = TRUE; + rc_plci->internal_command = C_NCR_FAC_REQ; + rc_plci->appl = appl; + add_p(rc_plci,CAI,"\x01\x80"); + add_p(rc_plci,UID,"\x06\x43\x61\x70\x69\x32\x30"); + sig_req(rc_plci,ASSIGN,DSIG_ID); + send_req(rc_plci); + } + else + { + Info = _OUT_OF_PLCI; + } + + if(!Info) + { + add_s(rc_plci,CPN,&msg[0]); + add_ai(rc_plci, &msg[1]); + sig_req(rc_plci,NCR_FACILITY,0); + send_req(rc_plci); + return FALSE; + /* for application controlled supplementary services */ + } + } + + if (!rc_plci) + { + Info = _WRONG_MESSAGE_FORMAT; + } + + if(!Info) + { + send_req(rc_plci); + } + else + { /* appl is not assigned to a PLCI or error condition */ + dbug(1,dprintf("localInfoCon")); + sendf(appl, + _INFO_R|CONFIRM, + Id, + Number, + "w",Info); + } + return FALSE; +} + +byte info_res(dword Id, word Number, DIVA_CAPI_ADAPTER * a, PLCI * plci, APPL * appl, API_PARSE * msg) +{ + dbug(1,dprintf("info_res")); + return FALSE; +} + +byte alert_req(dword Id, word Number, DIVA_CAPI_ADAPTER * a, PLCI * plci, APPL * appl, API_PARSE * msg) +{ + word Info; + byte ret; + + dbug(1,dprintf("alert_req")); + + Info = _WRONG_IDENTIFIER; + ret = FALSE; + if(plci) { + Info = _ALERT_IGNORED; + if(plci->State!=INC_CON_ALERT) { + Info = _WRONG_STATE; + if(plci->State==INC_CON_PENDING) { + Info = 0; + plci->State=INC_CON_ALERT; + add_ai(plci, &msg[0]); + sig_req(plci,CALL_ALERT,0); + ret = 1; + } + } + } + sendf(appl, + _ALERT_R|CONFIRM, + Id, + Number, + "w",Info); + return ret; +} + +byte facility_req(dword Id, word Number, DIVA_CAPI_ADAPTER * a, PLCI * plci, APPL * appl, API_PARSE * msg) +{ + word Info = 0; + word i = 0; + + word selector; + word SSreq; + long relatedPLCIvalue; + DIVA_CAPI_ADAPTER * relatedadapter; + byte * SSparms = ""; + byte RCparms[] = "\x05\x00\x00\x02\x00\x00"; + byte SSstruct[] = "\x09\x00\x00\x06\x00\x00\x00\x00\x00\x00"; + API_PARSE * parms; + API_PARSE ss_parms[11]; + PLCI *rplci; + byte cai[15]; + dword d; + API_PARSE dummy; + + dbug(1,dprintf("facility_req")); + for(i=0;i<9;i++) ss_parms[i].length = 0; + + parms = &msg[1]; + + if(!a) + { + dbug(1,dprintf("wrong Ctrl")); + Info = _WRONG_IDENTIFIER; + } + + selector = GET_WORD(msg[0].info); + + if(!Info) + { + switch(selector) + { + case SELECTOR_HANDSET: + Info = AdvCodecSupport(a, plci, appl, HOOK_SUPPORT); + break; + + case SELECTOR_SU_SERV: + if(!msg[1].length) + { + Info = _WRONG_MESSAGE_FORMAT; + break; + } + SSreq = GET_WORD(&(msg[1].info[1])); + PUT_WORD(&RCparms[1],SSreq); + SSparms = RCparms; + switch(SSreq) + { + case S_GET_SUPPORTED_SERVICES: + if((i=get_plci(a))) + { + rplci = &a->plci[i-1]; + rplci->appl = appl; + add_p(rplci,CAI,"\x01\x80"); + add_p(rplci,UID,"\x06\x43\x61\x70\x69\x32\x30"); + sig_req(rplci,ASSIGN,DSIG_ID); + send_req(rplci); + } + else + { + PUT_DWORD(&SSstruct[6], MASK_TERMINAL_PORTABILITY); + SSparms = (byte *)SSstruct; + break; + } + rplci->internal_command = GETSERV_REQ_PEND; + rplci->number = Number; + rplci->appl = appl; + sig_req(rplci,S_SUPPORTED,0); + send_req(rplci); + return FALSE; + break; + + case S_LISTEN: + if(parms->length==7) + { + if(api_parse(&parms->info[1],(word)parms->length,"wbd",ss_parms)) + { + dbug(1,dprintf("format wrong")); + Info = _WRONG_MESSAGE_FORMAT; + break; + } + } + else + { + Info = _WRONG_MESSAGE_FORMAT; + break; + } + a->Notification_Mask[appl->Id-1] = GET_DWORD(ss_parms[2].info); + if(a->Notification_Mask[appl->Id-1] & SMASK_MWI) /* MWI active? */ + { + if((i=get_plci(a))) + { + rplci = &a->plci[i-1]; + rplci->appl = appl; + add_p(rplci,CAI,"\x01\x80"); + add_p(rplci,UID,"\x06\x43\x61\x70\x69\x32\x30"); + sig_req(rplci,ASSIGN,DSIG_ID); + send_req(rplci); + } + else + { + break; + } + rplci->internal_command = GET_MWI_STATE; + rplci->number = Number; + sig_req(rplci,MWI_POLL,0); + send_req(rplci); + } + break; + + case S_HOLD: + api_parse(&parms->info[1],(word)parms->length,"ws",ss_parms); + if(plci && plci->State && plci->SuppState==IDLE) + { + plci->SuppState = HOLD_REQUEST; + plci->command = C_HOLD_REQ; + add_s(plci,CAI,&ss_parms[1]); + sig_req(plci,CALL_HOLD,0); + send_req(plci); + return FALSE; + } + else Info = 0x3010; /* wrong state */ + break; + case S_RETRIEVE: + if(plci && plci->State && plci->SuppState==CALL_HELD) + { + if(Id & EXT_CONTROLLER) + { + if(AdvCodecSupport(a, plci, appl, 0)) + { + Info = 0x3010; /* wrong state */ + break; + } + } + else plci->tel = 0; + + plci->SuppState = RETRIEVE_REQUEST; + plci->command = C_RETRIEVE_REQ; + if(plci->spoofed_msg==SPOOFING_REQUIRED) + { + plci->spoofed_msg = CALL_RETRIEVE; + plci->internal_command = BLOCK_PLCI; + plci->command = 0; + dbug(1,dprintf("Spoof")); + return FALSE; + } + else + { + sig_req(plci,CALL_RETRIEVE,0); + send_req(plci); + return FALSE; + } + } + else Info = 0x3010; /* wrong state */ + break; + case S_SUSPEND: + if(parms->length) + { + if(api_parse(&parms->info[1],(word)parms->length,"wbs",ss_parms)) + { + dbug(1,dprintf("format wrong")); + Info = _WRONG_MESSAGE_FORMAT; + break; + } + } + if(plci && plci->State) + { + add_s(plci,CAI,&ss_parms[2]); + plci->command = SUSPEND_REQ; + sig_req(plci,SUSPEND,0); + plci->State = SUSPENDING; + send_req(plci); + } + else Info = 0x3010; /* wrong state */ + break; + + case S_RESUME: + if(!(i=get_plci(a)) ) + { + Info = _OUT_OF_PLCI; + break; + } + rplci = &a->plci[i-1]; + rplci->appl = appl; + rplci->number = Number; + rplci->tel = 0; + rplci->call_dir = CALL_DIR_OUT | CALL_DIR_ORIGINATE; + /* check 'external controller' bit for codec support */ + if(Id & EXT_CONTROLLER) + { + if(AdvCodecSupport(a, rplci, appl, 0) ) + { + rplci->Id = 0; + Info = 0x300A; + break; + } + } + if(parms->length) + { + if(api_parse(&parms->info[1],(word)parms->length,"wbs",ss_parms)) + { + dbug(1,dprintf("format wrong")); + rplci->Id = 0; + Info = _WRONG_MESSAGE_FORMAT; + break; + } + } + dummy.length = 0; + dummy.info = "\x00"; + add_b1(rplci, &dummy, 0, 0); + if (a->Info_Mask[appl->Id-1] & 0x200) + { + /* early B3 connect (CIP mask bit 9) no release after a disc */ + add_p(rplci,LLI,"\x01\x01"); + } + add_p(rplci,UID,"\x06\x43\x61\x70\x69\x32\x30"); + sig_req(rplci,ASSIGN,DSIG_ID); + send_req(rplci); + add_s(rplci,CAI,&ss_parms[2]); + rplci->command = RESUME_REQ; + sig_req(rplci,RESUME,0); + rplci->State = RESUMING; + send_req(rplci); + break; + + case S_CONF_BEGIN: /* Request */ + case S_CONF_DROP: + case S_CONF_ISOLATE: + case S_CONF_REATTACH: + if(api_parse(&parms->info[1],(word)parms->length,"wbd",ss_parms)) + { + dbug(1,dprintf("format wrong")); + Info = _WRONG_MESSAGE_FORMAT; + break; + } + if(plci && plci->State && ((plci->SuppState==IDLE)||(plci->SuppState==CALL_HELD))) + { + d = GET_DWORD(ss_parms[2].info); + if(d>=0x80) + { + dbug(1,dprintf("format wrong")); + Info = _WRONG_MESSAGE_FORMAT; + break; + } + plci->ptyState = (byte)SSreq; + plci->command = 0; + cai[0] = 2; + switch(SSreq) + { + case S_CONF_BEGIN: + cai[1] = CONF_BEGIN; + plci->internal_command = CONF_BEGIN_REQ_PEND; + break; + case S_CONF_DROP: + cai[1] = CONF_DROP; + plci->internal_command = CONF_DROP_REQ_PEND; + break; + case S_CONF_ISOLATE: + cai[1] = CONF_ISOLATE; + plci->internal_command = CONF_ISOLATE_REQ_PEND; + break; + case S_CONF_REATTACH: + cai[1] = CONF_REATTACH; + plci->internal_command = CONF_REATTACH_REQ_PEND; + break; + } + cai[2] = (byte)d; /* Conference Size resp. PartyId */ + add_p(plci,CAI,cai); + sig_req(plci,S_SERVICE,0); + send_req(plci); + return FALSE; + } + else Info = 0x3010; /* wrong state */ + break; + + case S_ECT: + case S_3PTY_BEGIN: + case S_3PTY_END: + case S_CONF_ADD: + if(parms->length==7) + { + if(api_parse(&parms->info[1],(word)parms->length,"wbd",ss_parms)) + { + dbug(1,dprintf("format wrong")); + Info = _WRONG_MESSAGE_FORMAT; + break; + } + } + else if(parms->length==8) /* workaround for the T-View-S */ + { + if(api_parse(&parms->info[1],(word)parms->length,"wbdb",ss_parms)) + { + dbug(1,dprintf("format wrong")); + Info = _WRONG_MESSAGE_FORMAT; + break; + } + } + else + { + Info = _WRONG_MESSAGE_FORMAT; + break; + } + if(!msg[1].length) + { + Info = _WRONG_MESSAGE_FORMAT; + break; + } + if (!plci) + { + Info = _WRONG_IDENTIFIER; + break; + } + relatedPLCIvalue = GET_DWORD(ss_parms[2].info); + relatedPLCIvalue &= 0x0000FFFF; + dbug(1,dprintf("PTY/ECT/addCONF,relPLCI=%lx",relatedPLCIvalue)); + /* controller starts with 0 up to (max_adapter - 1) */ + if (((relatedPLCIvalue & 0x7f) == 0) + || (MapController ((byte)(relatedPLCIvalue & 0x7f)) == 0) + || (MapController ((byte)(relatedPLCIvalue & 0x7f)) > max_adapter)) + { + if(SSreq==S_3PTY_END) + { + dbug(1, dprintf("wrong Controller use 2nd PLCI=PLCI")); + rplci = plci; + } + else + { + Info = 0x3010; /* wrong state */ + break; + } + } + else + { + relatedadapter = &adapter[MapController ((byte)(relatedPLCIvalue & 0x7f))-1]; + relatedPLCIvalue >>=8; + /* find PLCI PTR*/ + for(i=0,rplci=NULL;i<relatedadapter->max_plci;i++) + { + if(relatedadapter->plci[i].Id == (byte)relatedPLCIvalue) + { + rplci = &relatedadapter->plci[i]; + } + } + if(!rplci || !relatedPLCIvalue) + { + if(SSreq==S_3PTY_END) + { + dbug(1, dprintf("use 2nd PLCI=PLCI")); + rplci = plci; + } + else + { + Info = 0x3010; /* wrong state */ + break; + } + } + } +/* + dbug(1,dprintf("rplci:%x",rplci)); + dbug(1,dprintf("plci:%x",plci)); + dbug(1,dprintf("rplci->ptyState:%x",rplci->ptyState)); + dbug(1,dprintf("plci->ptyState:%x",plci->ptyState)); + dbug(1,dprintf("SSreq:%x",SSreq)); + dbug(1,dprintf("rplci->internal_command:%x",rplci->internal_command)); + dbug(1,dprintf("rplci->appl:%x",rplci->appl)); + dbug(1,dprintf("rplci->Id:%x",rplci->Id)); +*/ + /* send PTY/ECT req, cannot check all states because of US stuff */ + if( !rplci->internal_command && rplci->appl ) + { + plci->command = 0; + rplci->relatedPTYPLCI = plci; + plci->relatedPTYPLCI = rplci; + rplci->ptyState = (byte)SSreq; + if(SSreq==S_ECT) + { + rplci->internal_command = ECT_REQ_PEND; + cai[1] = ECT_EXECUTE; + + rplci->vswitchstate=0; + rplci->vsprot=0; + rplci->vsprotdialect=0; + plci->vswitchstate=0; + plci->vsprot=0; + plci->vsprotdialect=0; + + } + else if(SSreq==S_CONF_ADD) + { + rplci->internal_command = CONF_ADD_REQ_PEND; + cai[1] = CONF_ADD; + } + else + { + rplci->internal_command = PTY_REQ_PEND; + cai[1] = (byte)(SSreq-3); + } + rplci->number = Number; + if(plci!=rplci) /* explicit invocation */ + { + cai[0] = 2; + cai[2] = plci->Sig.Id; + dbug(1,dprintf("explicit invocation")); + } + else + { + dbug(1,dprintf("implicit invocation")); + cai[0] = 1; + } + add_p(rplci,CAI,cai); + sig_req(rplci,S_SERVICE,0); + send_req(rplci); + return FALSE; + } + else + { + dbug(0,dprintf("Wrong line")); + Info = 0x3010; /* wrong state */ + break; + } + break; + + case S_CALL_DEFLECTION: + if(api_parse(&parms->info[1],(word)parms->length,"wbwss",ss_parms)) + { + dbug(1,dprintf("format wrong")); + Info = _WRONG_MESSAGE_FORMAT; + break; + } + if (!plci) + { + Info = _WRONG_IDENTIFIER; + break; + } + /* reuse unused screening indicator */ + ss_parms[3].info[3] = (byte)GET_WORD(&(ss_parms[2].info[0])); + plci->command = 0; + plci->internal_command = CD_REQ_PEND; + appl->CDEnable = TRUE; + cai[0] = 1; + cai[1] = CALL_DEFLECTION; + add_p(plci,CAI,cai); + add_p(plci,CPN,ss_parms[3].info); + sig_req(plci,S_SERVICE,0); + send_req(plci); + return FALSE; + break; + + case S_CALL_FORWARDING_START: + if(api_parse(&parms->info[1],(word)parms->length,"wbdwwsss",ss_parms)) + { + dbug(1,dprintf("format wrong")); + Info = _WRONG_MESSAGE_FORMAT; + break; + } + + if((i=get_plci(a))) + { + rplci = &a->plci[i-1]; + rplci->appl = appl; + add_p(rplci,CAI,"\x01\x80"); + add_p(rplci,UID,"\x06\x43\x61\x70\x69\x32\x30"); + sig_req(rplci,ASSIGN,DSIG_ID); + send_req(rplci); + } + else + { + Info = _OUT_OF_PLCI; + break; + } + + /* reuse unused screening indicator */ + rplci->internal_command = CF_START_PEND; + rplci->appl = appl; + rplci->number = Number; + appl->S_Handle = GET_DWORD(&(ss_parms[2].info[0])); + cai[0] = 2; + cai[1] = 0x70|(byte)GET_WORD(&(ss_parms[3].info[0])); /* Function */ + cai[2] = (byte)GET_WORD(&(ss_parms[4].info[0])); /* Basic Service */ + add_p(rplci,CAI,cai); + add_p(rplci,OAD,ss_parms[5].info); + add_p(rplci,CPN,ss_parms[6].info); + sig_req(rplci,S_SERVICE,0); + send_req(rplci); + return FALSE; + break; + + case S_INTERROGATE_DIVERSION: + case S_INTERROGATE_NUMBERS: + case S_CALL_FORWARDING_STOP: + case S_CCBS_REQUEST: + case S_CCBS_DEACTIVATE: + case S_CCBS_INTERROGATE: + switch(SSreq) + { + case S_INTERROGATE_NUMBERS: + if(api_parse(&parms->info[1],(word)parms->length,"wbd",ss_parms)) + { + dbug(0,dprintf("format wrong")); + Info = _WRONG_MESSAGE_FORMAT; + } + break; + case S_CCBS_REQUEST: + case S_CCBS_DEACTIVATE: + if(api_parse(&parms->info[1],(word)parms->length,"wbdw",ss_parms)) + { + dbug(0,dprintf("format wrong")); + Info = _WRONG_MESSAGE_FORMAT; + } + break; + case S_CCBS_INTERROGATE: + if(api_parse(&parms->info[1],(word)parms->length,"wbdws",ss_parms)) + { + dbug(0,dprintf("format wrong")); + Info = _WRONG_MESSAGE_FORMAT; + } + break; + default: + if(api_parse(&parms->info[1],(word)parms->length,"wbdwws",ss_parms)) + { + dbug(0,dprintf("format wrong")); + Info = _WRONG_MESSAGE_FORMAT; + break; + } + break; + } + + if(Info) break; + if((i=get_plci(a))) + { + rplci = &a->plci[i-1]; + switch(SSreq) + { + case S_INTERROGATE_DIVERSION: /* use cai with S_SERVICE below */ + cai[1] = 0x60|(byte)GET_WORD(&(ss_parms[3].info[0])); /* Function */ + rplci->internal_command = INTERR_DIVERSION_REQ_PEND; /* move to rplci if assigned */ + break; + case S_INTERROGATE_NUMBERS: /* use cai with S_SERVICE below */ + cai[1] = DIVERSION_INTERROGATE_NUM; /* Function */ + rplci->internal_command = INTERR_NUMBERS_REQ_PEND; /* move to rplci if assigned */ + break; + case S_CALL_FORWARDING_STOP: + rplci->internal_command = CF_STOP_PEND; + cai[1] = 0x80|(byte)GET_WORD(&(ss_parms[3].info[0])); /* Function */ + break; + case S_CCBS_REQUEST: + cai[1] = CCBS_REQUEST; + rplci->internal_command = CCBS_REQUEST_REQ_PEND; + break; + case S_CCBS_DEACTIVATE: + cai[1] = CCBS_DEACTIVATE; + rplci->internal_command = CCBS_DEACTIVATE_REQ_PEND; + break; + case S_CCBS_INTERROGATE: + cai[1] = CCBS_INTERROGATE; + rplci->internal_command = CCBS_INTERROGATE_REQ_PEND; + break; + default: + cai[1] = 0; + break; + } + rplci->appl = appl; + rplci->number = Number; + add_p(rplci,CAI,"\x01\x80"); + add_p(rplci,UID,"\x06\x43\x61\x70\x69\x32\x30"); + sig_req(rplci,ASSIGN,DSIG_ID); + send_req(rplci); + } + else + { + Info = _OUT_OF_PLCI; + break; + } + + appl->S_Handle = GET_DWORD(&(ss_parms[2].info[0])); + switch(SSreq) + { + case S_INTERROGATE_NUMBERS: + cai[0] = 1; + add_p(rplci,CAI,cai); + break; + case S_CCBS_REQUEST: + case S_CCBS_DEACTIVATE: + cai[0] = 3; + PUT_WORD(&cai[2],GET_WORD(&(ss_parms[3].info[0]))); + add_p(rplci,CAI,cai); + break; + case S_CCBS_INTERROGATE: + cai[0] = 3; + PUT_WORD(&cai[2],GET_WORD(&(ss_parms[3].info[0]))); + add_p(rplci,CAI,cai); + add_p(rplci,OAD,ss_parms[4].info); + break; + default: + cai[0] = 2; + cai[2] = (byte)GET_WORD(&(ss_parms[4].info[0])); /* Basic Service */ + add_p(rplci,CAI,cai); + add_p(rplci,OAD,ss_parms[5].info); + break; + } + + sig_req(rplci,S_SERVICE,0); + send_req(rplci); + return FALSE; + break; + + case S_MWI_ACTIVATE: + if(api_parse(&parms->info[1],(word)parms->length,"wbwdwwwssss",ss_parms)) + { + dbug(1,dprintf("format wrong")); + Info = _WRONG_MESSAGE_FORMAT; + break; + } + if(!plci) + { + if((i=get_plci(a))) + { + rplci = &a->plci[i-1]; + rplci->appl = appl; + rplci->cr_enquiry=TRUE; + add_p(rplci,CAI,"\x01\x80"); + add_p(rplci,UID,"\x06\x43\x61\x70\x69\x32\x30"); + sig_req(rplci,ASSIGN,DSIG_ID); + send_req(rplci); + } + else + { + Info = _OUT_OF_PLCI; + break; + } + } + else + { + rplci = plci; + rplci->cr_enquiry=FALSE; + } + + rplci->command = 0; + rplci->internal_command = MWI_ACTIVATE_REQ_PEND; + rplci->appl = appl; + rplci->number = Number; + + cai[0] = 13; + cai[1] = ACTIVATION_MWI; /* Function */ + PUT_WORD(&cai[2],GET_WORD(&(ss_parms[2].info[0]))); /* Basic Service */ + PUT_DWORD(&cai[4],GET_DWORD(&(ss_parms[3].info[0]))); /* Number of Messages */ + PUT_WORD(&cai[8],GET_WORD(&(ss_parms[4].info[0]))); /* Message Status */ + PUT_WORD(&cai[10],GET_WORD(&(ss_parms[5].info[0]))); /* Message Reference */ + PUT_WORD(&cai[12],GET_WORD(&(ss_parms[6].info[0]))); /* Invocation Mode */ + add_p(rplci,CAI,cai); + add_p(rplci,CPN,ss_parms[7].info); /* Receiving User Number */ + add_p(rplci,OAD,ss_parms[8].info); /* Controlling User Number */ + add_p(rplci,OSA,ss_parms[9].info); /* Controlling User Provided Number */ + add_p(rplci,UID,ss_parms[10].info); /* Time */ + sig_req(rplci,S_SERVICE,0); + send_req(rplci); + return FALSE; + + case S_MWI_DEACTIVATE: + if(api_parse(&parms->info[1],(word)parms->length,"wbwwss",ss_parms)) + { + dbug(1,dprintf("format wrong")); + Info = _WRONG_MESSAGE_FORMAT; + break; + } + if(!plci) + { + if((i=get_plci(a))) + { + rplci = &a->plci[i-1]; + rplci->appl = appl; + rplci->cr_enquiry=TRUE; + add_p(rplci,CAI,"\x01\x80"); + add_p(rplci,UID,"\x06\x43\x61\x70\x69\x32\x30"); + sig_req(rplci,ASSIGN,DSIG_ID); + send_req(rplci); + } + else + { + Info = _OUT_OF_PLCI; + break; + } + } + else + { + rplci = plci; + rplci->cr_enquiry=FALSE; + } + + rplci->command = 0; + rplci->internal_command = MWI_DEACTIVATE_REQ_PEND; + rplci->appl = appl; + rplci->number = Number; + + cai[0] = 5; + cai[1] = DEACTIVATION_MWI; /* Function */ + PUT_WORD(&cai[2],GET_WORD(&(ss_parms[2].info[0]))); /* Basic Service */ + PUT_WORD(&cai[4],GET_WORD(&(ss_parms[3].info[0]))); /* Invocation Mode */ + add_p(rplci,CAI,cai); + add_p(rplci,CPN,ss_parms[4].info); /* Receiving User Number */ + add_p(rplci,OAD,ss_parms[5].info); /* Controlling User Number */ + sig_req(rplci,S_SERVICE,0); + send_req(rplci); + return FALSE; + + default: + Info = 0x300E; /* not supported */ + break; + } + break; /* case SELECTOR_SU_SERV: end */ + + + case SELECTOR_DTMF: + return (dtmf_request (Id, Number, a, plci, appl, msg)); + + + + case SELECTOR_LINE_INTERCONNECT: + return (mixer_request (Id, Number, a, plci, appl, msg)); + + + + case PRIV_SELECTOR_ECHO_CANCELLER: + appl->appl_flags |= APPL_FLAG_PRIV_EC_SPEC; + return (ec_request (Id, Number, a, plci, appl, msg)); + + case SELECTOR_ECHO_CANCELLER: + appl->appl_flags &= ~APPL_FLAG_PRIV_EC_SPEC; + return (ec_request (Id, Number, a, plci, appl, msg)); + + + case SELECTOR_V42BIS: + default: + Info = _FACILITY_NOT_SUPPORTED; + break; + } /* end of switch(selector) */ + } + + dbug(1,dprintf("SendFacRc")); + sendf(appl, + _FACILITY_R|CONFIRM, + Id, + Number, + "wws",Info,selector,SSparms); + return FALSE; +} + +byte facility_res(dword Id, word Number, DIVA_CAPI_ADAPTER * a, PLCI * plci, APPL * appl, API_PARSE * msg) +{ + dbug(1,dprintf("facility_res")); + return FALSE; +} + +byte connect_b3_req(dword Id, word Number, DIVA_CAPI_ADAPTER * a, PLCI * plci, APPL * appl, API_PARSE * parms) +{ + word Info = 0; + byte req; + byte len; + word w; + word fax_control_bits, fax_feature_bits, fax_info_change; + API_PARSE * ncpi; + byte pvc[2]; + + API_PARSE fax_parms[9]; + word i; + + + dbug(1,dprintf("connect_b3_req")); + if(plci) + { + if ((plci->State == IDLE) || (plci->State == OUTG_DIS_PENDING) + || (plci->State == INC_DIS_PENDING) || (plci->SuppState != IDLE)) + { + Info = _WRONG_STATE; + } + else + { + /* local reply if assign unsuccessfull + or B3 protocol allows only one layer 3 connection + and already connected + or B2 protocol not any LAPD + and connect_b3_req contradicts originate/answer direction */ + if (!plci->NL.Id + || (((plci->B3_prot != B3_T90NL) && (plci->B3_prot != B3_ISO8208) && (plci->B3_prot != B3_X25_DCE)) + && ((plci->channels != 0) + || (((plci->B2_prot != B2_SDLC) && (plci->B2_prot != B2_LAPD) && (plci->B2_prot != B2_LAPD_FREE_SAPI_SEL)) + && ((plci->call_dir & CALL_DIR_ANSWER) && !(plci->call_dir & CALL_DIR_FORCE_OUTG_NL)))))) + { + dbug(1,dprintf("B3 already connected=%d or no NL.Id=0x%x, dir=%d sstate=0x%x", + plci->channels,plci->NL.Id,plci->call_dir,plci->SuppState)); + Info = _WRONG_STATE; + sendf(appl, + _CONNECT_B3_R|CONFIRM, + Id, + Number, + "w",Info); + return FALSE; + } + plci->requested_options_conn = 0; + + req = N_CONNECT; + ncpi = &parms[0]; + if(plci->B3_prot==2 || plci->B3_prot==3) + { + if(ncpi->length>2) + { + /* check for PVC */ + if(ncpi->info[2] || ncpi->info[3]) + { + pvc[0] = ncpi->info[3]; + pvc[1] = ncpi->info[2]; + add_d(plci,2,pvc); + req = N_RESET; + } + else + { + if(ncpi->info[1] &1) req = N_CONNECT | N_D_BIT; + add_d(plci,(word)(ncpi->length-3),&ncpi->info[4]); + } + } + } + else if(plci->B3_prot==5) + { + if (plci->NL.Id && !plci->nl_remove_id) + { + fax_control_bits = GET_WORD(&((T30_INFO *)plci->fax_connect_info_buffer)->control_bits_low); + fax_feature_bits = GET_WORD(&((T30_INFO *)plci->fax_connect_info_buffer)->feature_bits_low); + if (!(fax_control_bits & T30_CONTROL_BIT_MORE_DOCUMENTS) + || (fax_feature_bits & T30_FEATURE_BIT_MORE_DOCUMENTS)) + { + len = (byte)(&(((T30_INFO *) 0)->universal_6)); + fax_info_change = FALSE; + if (ncpi->length >= 4) + { + w = GET_WORD(&ncpi->info[3]); + if ((w & 0x0001) != ((word)(((T30_INFO *)(plci->fax_connect_info_buffer))->resolution & 0x0001))) + { + ((T30_INFO *)(plci->fax_connect_info_buffer))->resolution = + (byte)((((T30_INFO *)(plci->fax_connect_info_buffer))->resolution & ~T30_RESOLUTION_R8_0770_OR_200) | + ((w & 0x0001) ? T30_RESOLUTION_R8_0770_OR_200 : 0)); + fax_info_change = TRUE; + } + fax_control_bits &= ~(T30_CONTROL_BIT_REQUEST_POLLING | T30_CONTROL_BIT_MORE_DOCUMENTS); + if (w & 0x0002) /* Fax-polling request */ + fax_control_bits |= T30_CONTROL_BIT_REQUEST_POLLING; + if ((w & 0x0004) /* Request to send / poll another document */ + && (a->manufacturer_features & MANUFACTURER_FEATURE_FAX_MORE_DOCUMENTS)) + { + fax_control_bits |= T30_CONTROL_BIT_MORE_DOCUMENTS; + } + if (ncpi->length >= 6) + { + w = GET_WORD(&ncpi->info[5]); + if (((byte) w) != ((T30_INFO *)(plci->fax_connect_info_buffer))->data_format) + { + ((T30_INFO *)(plci->fax_connect_info_buffer))->data_format = (byte) w; + fax_info_change = TRUE; + } + + if ((a->man_profile.private_options & (1L << PRIVATE_FAX_SUB_SEP_PWD)) + && (GET_WORD(&ncpi->info[5]) & 0x8000)) /* Private SEP/SUB/PWD enable */ + { + plci->requested_options_conn |= (1L << PRIVATE_FAX_SUB_SEP_PWD); + } + if ((a->man_profile.private_options & (1L << PRIVATE_FAX_NONSTANDARD)) + && (GET_WORD(&ncpi->info[5]) & 0x4000)) /* Private non-standard facilities enable */ + { + plci->requested_options_conn |= (1L << PRIVATE_FAX_NONSTANDARD); + } + fax_control_bits &= ~(T30_CONTROL_BIT_ACCEPT_SUBADDRESS | T30_CONTROL_BIT_ACCEPT_SEL_POLLING | + T30_CONTROL_BIT_ACCEPT_PASSWORD); + if ((plci->requested_options_conn | plci->requested_options | a->requested_options_table[appl->Id-1]) + & ((1L << PRIVATE_FAX_SUB_SEP_PWD) | (1L << PRIVATE_FAX_NONSTANDARD))) + { + if (api_parse (&ncpi->info[1], ncpi->length, "wwwwsss", fax_parms)) + Info = _WRONG_MESSAGE_FORMAT; + else + { + if ((plci->requested_options_conn | plci->requested_options | a->requested_options_table[appl->Id-1]) + & (1L << PRIVATE_FAX_SUB_SEP_PWD)) + { + fax_control_bits |= T30_CONTROL_BIT_ACCEPT_SUBADDRESS | T30_CONTROL_BIT_ACCEPT_PASSWORD; + if (fax_control_bits & T30_CONTROL_BIT_ACCEPT_POLLING) + fax_control_bits |= T30_CONTROL_BIT_ACCEPT_SEL_POLLING; + } + w = fax_parms[4].length; + if (w > 20) + w = 20; + ((T30_INFO *)(plci->fax_connect_info_buffer))->station_id_len = (byte) w; + for (i = 0; i < w; i++) + ((T30_INFO *)(plci->fax_connect_info_buffer))->station_id[i] = fax_parms[4].info[1+i]; + ((T30_INFO *)(plci->fax_connect_info_buffer))->head_line_len = 0; + len = (byte)(((T30_INFO *) 0)->station_id + 20); + w = fax_parms[5].length; + if (w > 20) + w = 20; + plci->fax_connect_info_buffer[len++] = (byte) w; + for (i = 0; i < w; i++) + plci->fax_connect_info_buffer[len++] = fax_parms[5].info[1+i]; + w = fax_parms[6].length; + if (w > 20) + w = 20; + plci->fax_connect_info_buffer[len++] = (byte) w; + for (i = 0; i < w; i++) + plci->fax_connect_info_buffer[len++] = fax_parms[6].info[1+i]; + if ((plci->requested_options_conn | plci->requested_options | a->requested_options_table[appl->Id-1]) + & (1L << PRIVATE_FAX_NONSTANDARD)) + { + if (api_parse (&ncpi->info[1], ncpi->length, "wwwwssss", fax_parms)) + { + dbug(1,dprintf("non-standard facilities info missing or wrong format")); + plci->fax_connect_info_buffer[len++] = 0; + } + else + { + if ((fax_parms[7].length >= 3) && (fax_parms[7].info[1] >= 2)) + plci->nsf_control_bits = GET_WORD(&fax_parms[7].info[2]); + plci->fax_connect_info_buffer[len++] = (byte)(fax_parms[7].length); + for (i = 0; i < fax_parms[7].length; i++) + plci->fax_connect_info_buffer[len++] = fax_parms[7].info[1+i]; + } + } + } + } + else + { + len = (byte)(&(((T30_INFO *) 0)->universal_6)); + } + fax_info_change = TRUE; + + } + if (fax_control_bits != GET_WORD(&((T30_INFO *)plci->fax_connect_info_buffer)->control_bits_low)) + { + PUT_WORD (&((T30_INFO *)plci->fax_connect_info_buffer)->control_bits_low, fax_control_bits); + fax_info_change = TRUE; + } + } + if (Info == GOOD) + { + plci->fax_connect_info_length = len; + if (fax_info_change) + { + if (fax_feature_bits & T30_FEATURE_BIT_MORE_DOCUMENTS) + { + start_internal_command (Id, plci, fax_connect_info_command); + return FALSE; + } + else + { + start_internal_command (Id, plci, fax_adjust_b23_command); + return FALSE; + } + } + } + } + else Info = _WRONG_STATE; + } + else Info = _WRONG_STATE; + } + + else if (plci->B3_prot == B3_RTP) + { + plci->internal_req_buffer[0] = ncpi->length + 1; + plci->internal_req_buffer[1] = UDATA_REQUEST_RTP_RECONFIGURE; + for (w = 0; w < ncpi->length; w++) + plci->internal_req_buffer[2+w] = ncpi->info[1+w]; + start_internal_command (Id, plci, rtp_connect_b3_req_command); + return FALSE; + } + + if(!Info) + { + nl_req_ncci(plci,req,0); + return 1; + } + } + } + else Info = _WRONG_IDENTIFIER; + + sendf(appl, + _CONNECT_B3_R|CONFIRM, + Id, + Number, + "w",Info); + return FALSE; +} + +byte connect_b3_res(dword Id, word Number, DIVA_CAPI_ADAPTER * a, PLCI * plci, APPL * appl, API_PARSE * parms) +{ + word ncci; + API_PARSE * ncpi; + byte req; + + word w; + + + API_PARSE fax_parms[9]; + word i; + byte len; + + + dbug(1,dprintf("connect_b3_res")); + + ncci = (word)(Id>>16); + if(plci && ncci) { + if(a->ncci_state[ncci]==INC_CON_PENDING) { + if (GET_WORD (&parms[0].info[0]) != 0) + { + a->ncci_state[ncci] = OUTG_REJ_PENDING; + channel_request_xon (plci, a->ncci_ch[ncci]); + channel_xmit_xon (plci); + cleanup_ncci_data (plci, ncci); + nl_req_ncci(plci,N_DISC,(byte)ncci); + return 1; + } + a->ncci_state[ncci] = INC_ACT_PENDING; + + req = N_CONNECT_ACK; + ncpi = &parms[1]; + if ((plci->B3_prot == 4) || (plci->B3_prot == 5) || (plci->B3_prot == 7)) + { + + if ((plci->requested_options_conn | plci->requested_options | a->requested_options_table[plci->appl->Id-1]) + & (1L << PRIVATE_FAX_NONSTANDARD)) + { + if (((plci->B3_prot == 4) || (plci->B3_prot == 5)) + && (plci->nsf_control_bits & T30_NSF_CONTROL_BIT_ENABLE_NSF) + && (plci->nsf_control_bits & T30_NSF_CONTROL_BIT_NEGOTIATE_RESP)) + { + len = ((byte)(((T30_INFO *) 0)->station_id + 20)); + if (plci->fax_connect_info_length < len) + { + ((T30_INFO *)(plci->fax_connect_info_buffer))->station_id_len = 0; + ((T30_INFO *)(plci->fax_connect_info_buffer))->head_line_len = 0; + } + if (api_parse (&ncpi->info[1], ncpi->length, "wwwwssss", fax_parms)) + { + dbug(1,dprintf("non-standard facilities info missing or wrong format")); + } + else + { + if (plci->fax_connect_info_length <= len) + plci->fax_connect_info_buffer[len] = 0; + len += 1 + plci->fax_connect_info_buffer[len]; + if (plci->fax_connect_info_length <= len) + plci->fax_connect_info_buffer[len] = 0; + len += 1 + plci->fax_connect_info_buffer[len]; + if ((fax_parms[7].length >= 3) && (fax_parms[7].info[1] >= 2)) + plci->nsf_control_bits = GET_WORD(&fax_parms[7].info[2]); + plci->fax_connect_info_buffer[len++] = (byte)(fax_parms[7].length); + for (i = 0; i < fax_parms[7].length; i++) + plci->fax_connect_info_buffer[len++] = fax_parms[7].info[1+i]; + } + plci->fax_connect_info_length = len; + ((T30_INFO *)(plci->fax_connect_info_buffer))->code = 0; + start_internal_command (Id, plci, fax_connect_ack_command); + return FALSE; + } + } + + nl_req_ncci(plci,req,(byte)ncci); + if ((plci->ncpi_state & NCPI_VALID_CONNECT_B3_ACT) + && !(plci->ncpi_state & NCPI_CONNECT_B3_ACT_SENT)) + { + if (plci->B3_prot == 4) + sendf(appl,_CONNECT_B3_ACTIVE_I,Id,0,"s",""); + else + sendf(appl,_CONNECT_B3_ACTIVE_I,Id,0,"S",plci->ncpi_buffer); + plci->ncpi_state |= NCPI_CONNECT_B3_ACT_SENT; + } + } + + else if (plci->B3_prot == B3_RTP) + { + plci->internal_req_buffer[0] = ncpi->length + 1; + plci->internal_req_buffer[1] = UDATA_REQUEST_RTP_RECONFIGURE; + for (w = 0; w < ncpi->length; w++) + plci->internal_req_buffer[2+w] = ncpi->info[1+w]; + start_internal_command (Id, plci, rtp_connect_b3_res_command); + return FALSE; + } + + else + { + if(ncpi->length>2) { + if(ncpi->info[1] &1) req = N_CONNECT_ACK | N_D_BIT; + add_d(plci,(word)(ncpi->length-3),&ncpi->info[4]); + } + nl_req_ncci(plci,req,(byte)ncci); + sendf(appl,_CONNECT_B3_ACTIVE_I,Id,0,"s",""); + if (plci->adjust_b_restore) + { + plci->adjust_b_restore = FALSE; + start_internal_command (Id, plci, adjust_b_restore); + } + } + return 1; + } + } + return FALSE; +} + +byte connect_b3_a_res(dword Id, word Number, DIVA_CAPI_ADAPTER * a, PLCI * plci, APPL * appl, API_PARSE * parms) +{ + word ncci; + + ncci = (word)(Id>>16); + dbug(1,dprintf("connect_b3_a_res(ncci=0x%x)",ncci)); + + if (plci && ncci && (plci->State != IDLE) && (plci->State != INC_DIS_PENDING) + && (plci->State != OUTG_DIS_PENDING)) + { + if(a->ncci_state[ncci]==INC_ACT_PENDING) { + a->ncci_state[ncci] = CONNECTED; + if(plci->State!=INC_CON_CONNECTED_ALERT) plci->State = CONNECTED; + channel_request_xon (plci, a->ncci_ch[ncci]); + channel_xmit_xon (plci); + } + } + return FALSE; +} + +byte disconnect_b3_req(dword Id, word Number, DIVA_CAPI_ADAPTER * a, PLCI * plci, APPL * appl, API_PARSE * parms) +{ + word Info; + word ncci; + API_PARSE * ncpi; + + dbug(1,dprintf("disconnect_b3_req")); + + Info = _WRONG_IDENTIFIER; + ncci = (word)(Id>>16); + if (plci && ncci) + { + Info = _WRONG_STATE; + if ((a->ncci_state[ncci] == CONNECTED) + || (a->ncci_state[ncci] == OUTG_CON_PENDING) + || (a->ncci_state[ncci] == INC_CON_PENDING) + || (a->ncci_state[ncci] == INC_ACT_PENDING)) + { + a->ncci_state[ncci] = OUTG_DIS_PENDING; + channel_request_xon (plci, a->ncci_ch[ncci]); + channel_xmit_xon (plci); + + if (a->ncci[ncci].data_pending + && ((plci->B3_prot == B3_TRANSPARENT) + || (plci->B3_prot == B3_T30) + || (plci->B3_prot == B3_T30_WITH_EXTENSIONS))) + { + plci->send_disc = (byte)ncci; + plci->command = 0; + return FALSE; + } + else + { + cleanup_ncci_data (plci, ncci); + + if(plci->B3_prot==2 || plci->B3_prot==3) + { + ncpi = &parms[0]; + if(ncpi->length>3) + { + add_d(plci, (word)(ncpi->length - 3) ,(byte *)&(ncpi->info[4])); + } + } + nl_req_ncci(plci,N_DISC,(byte)ncci); + } + return 1; + } + } + sendf(appl, + _DISCONNECT_B3_R|CONFIRM, + Id, + Number, + "w",Info); + return FALSE; +} + +byte disconnect_b3_res(dword Id, word Number, DIVA_CAPI_ADAPTER * a, PLCI * plci, APPL * appl, API_PARSE * parms) +{ + word ncci; + word i; + + ncci = (word)(Id>>16); + dbug(1,dprintf("disconnect_b3_res(ncci=0x%x",ncci)); + if(plci && ncci) { + plci->requested_options_conn = 0; + plci->fax_connect_info_length = 0; + plci->ncpi_state = 0x00; + if (((plci->B3_prot != B3_T90NL) && (plci->B3_prot != B3_ISO8208) && (plci->B3_prot != B3_X25_DCE)) + && ((plci->B2_prot != B2_LAPD) && (plci->B2_prot != B2_LAPD_FREE_SAPI_SEL))) + { + plci->call_dir |= CALL_DIR_FORCE_OUTG_NL; + } + for(i=0; i<MAX_CHANNELS_PER_PLCI && plci->inc_dis_ncci_table[i]!=(byte)ncci; i++); + if(i<MAX_CHANNELS_PER_PLCI) { + if(plci->channels)plci->channels--; + for(; i<MAX_CHANNELS_PER_PLCI-1; i++) plci->inc_dis_ncci_table[i] = plci->inc_dis_ncci_table[i+1]; + plci->inc_dis_ncci_table[MAX_CHANNELS_PER_PLCI-1] = 0; + + ncci_free_receive_buffers (plci, ncci); + + if((plci->State==IDLE || plci->State==SUSPENDING) && !plci->channels){ + if(plci->State == SUSPENDING){ + sendf(plci->appl, + _FACILITY_I, + Id & 0xffffL, + 0, + "ws", (word)3, "\x03\x04\x00\x00"); + sendf(plci->appl, _DISCONNECT_I, Id & 0xffffL, 0, "w", 0); + } + plci_remove(plci); + plci->State=IDLE; + } + } + else + { + if ((a->manufacturer_features & MANUFACTURER_FEATURE_FAX_PAPER_FORMATS) + && ((plci->B3_prot == 4) || (plci->B3_prot == 5)) + && (a->ncci_state[ncci] == INC_DIS_PENDING)) + { + ncci_free_receive_buffers (plci, ncci); + + nl_req_ncci(plci,N_EDATA,(byte)ncci); + + plci->adapter->ncci_state[ncci] = IDLE; + start_internal_command (Id, plci, fax_disconnect_command); + return 1; + } + } + } + return FALSE; +} + +byte data_b3_req(dword Id, word Number, DIVA_CAPI_ADAPTER * a, PLCI * plci, APPL * appl, API_PARSE * parms) +{ + NCCI *ncci_ptr; + DATA_B3_DESC *data; + word Info; + word ncci; + word i; + + dbug(1,dprintf("data_b3_req")); + + Info = _WRONG_IDENTIFIER; + ncci = (word)(Id>>16); + dbug(1,dprintf("ncci=0x%x, plci=0x%x",ncci,plci)); + + if (plci && ncci) + { + Info = _WRONG_STATE; + if ((a->ncci_state[ncci] == CONNECTED) + || (a->ncci_state[ncci] == INC_ACT_PENDING)) + { + /* queue data */ + ncci_ptr = &(a->ncci[ncci]); + i = ncci_ptr->data_out + ncci_ptr->data_pending; + if (i >= MAX_DATA_B3) + i -= MAX_DATA_B3; + data = &(ncci_ptr->DBuffer[i]); + data->Number = Number; + if ((((byte *)(parms[0].info)) >= ((byte *)(plci->msg_in_queue))) + && (((byte *)(parms[0].info)) < ((byte *)(plci->msg_in_queue)) + sizeof(plci->msg_in_queue))) + { + + data->P = (byte *)(*((dword *)(parms[0].info))); + + } + else + data->P = TransmitBufferSet(appl,*(dword *)parms[0].info); + data->Length = GET_WORD(parms[1].info); + data->Handle = GET_WORD(parms[2].info); + data->Flags = GET_WORD(parms[3].info); + (ncci_ptr->data_pending)++; + + /* check for delivery confirmation */ + if (data->Flags & 0x0004) + { + i = ncci_ptr->data_ack_out + ncci_ptr->data_ack_pending; + if (i >= MAX_DATA_ACK) + i -= MAX_DATA_ACK; + ncci_ptr->DataAck[i].Number = data->Number; + ncci_ptr->DataAck[i].Handle = data->Handle; + (ncci_ptr->data_ack_pending)++; + } + + send_data(plci); + return FALSE; + } + } + if (appl) + { + if (plci) + { + if ((((byte *)(parms[0].info)) >= ((byte *)(plci->msg_in_queue))) + && (((byte *)(parms[0].info)) < ((byte *)(plci->msg_in_queue)) + sizeof(plci->msg_in_queue))) + { + + TransmitBufferFree (appl, (byte *)(*((dword *)(parms[0].info)))); + + } + } + sendf(appl, + _DATA_B3_R|CONFIRM, + Id, + Number, + "ww",GET_WORD(parms[2].info),Info); + } + return FALSE; +} + +byte data_b3_res(dword Id, word Number, DIVA_CAPI_ADAPTER * a, PLCI * plci, APPL * appl, API_PARSE * parms) +{ + word n; + word ncci; + word NCCIcode; + + dbug(1,dprintf("data_b3_res")); + + ncci = (word)(Id>>16); + if(plci && ncci) { + n = GET_WORD(parms[0].info); + dbug(1,dprintf("free(%d)",n)); + NCCIcode = ncci | (((word) a->Id) << 8); + if(n<appl->MaxBuffer && + appl->DataNCCI[n]==NCCIcode && + (byte)(appl->DataFlags[n]>>8)==plci->Id) { + dbug(1,dprintf("found")); + appl->DataNCCI[n] = 0; + + if (channel_can_xon (plci, a->ncci_ch[ncci])) { + channel_request_xon (plci, a->ncci_ch[ncci]); + } + channel_xmit_xon (plci); + + if(appl->DataFlags[n] &4) { + nl_req_ncci(plci,N_DATA_ACK,(byte)ncci); + return 1; + } + } + } + return FALSE; +} + +byte reset_b3_req(dword Id, word Number, DIVA_CAPI_ADAPTER * a, PLCI * plci, APPL * appl, API_PARSE * parms) +{ + word Info; + word ncci; + + dbug(1,dprintf("reset_b3_req")); + + Info = _WRONG_IDENTIFIER; + ncci = (word)(Id>>16); + if(plci && ncci) + { + Info = _WRONG_STATE; + switch (plci->B3_prot) + { + case B3_ISO8208: + case B3_X25_DCE: + if(a->ncci_state[ncci]==CONNECTED) + { + nl_req_ncci(plci,N_RESET,(byte)ncci); + send_req(plci); + Info = GOOD; + } + break; + case B3_TRANSPARENT: + if(a->ncci_state[ncci]==CONNECTED) + { + start_internal_command (Id, plci, reset_b3_command); + Info = GOOD; + } + break; + } + } + /* reset_b3 must result in a reset_b3_con & reset_b3_Ind */ + sendf(appl, + _RESET_B3_R|CONFIRM, + Id, + Number, + "w",Info); + return FALSE; +} + +byte reset_b3_res(dword Id, word Number, DIVA_CAPI_ADAPTER * a, PLCI * plci, APPL * appl, API_PARSE * parms) +{ + word ncci; + + dbug(1,dprintf("reset_b3_res")); + + ncci = (word)(Id>>16); + if(plci && ncci) { + switch (plci->B3_prot) + { + case B3_ISO8208: + case B3_X25_DCE: + if(a->ncci_state[ncci]==INC_RES_PENDING) + { + a->ncci_state[ncci] = CONNECTED; + nl_req_ncci(plci,N_RESET_ACK,(byte)ncci); + return TRUE; + } + break; + } + } + return FALSE; +} + +byte connect_b3_t90_a_res(dword Id, word Number, DIVA_CAPI_ADAPTER * a, PLCI * plci, APPL * appl, API_PARSE * parms) +{ + word ncci; + API_PARSE * ncpi; + byte req; + + dbug(1,dprintf("connect_b3_t90_a_res")); + + ncci = (word)(Id>>16); + if(plci && ncci) { + if(a->ncci_state[ncci]==INC_ACT_PENDING) { + a->ncci_state[ncci] = CONNECTED; + } + else if(a->ncci_state[ncci]==INC_CON_PENDING) { + a->ncci_state[ncci] = CONNECTED; + + req = N_CONNECT_ACK; + + /* parms[0]==0 for CAPI original message definition! */ + if(parms[0].info) { + ncpi = &parms[1]; + if(ncpi->length>2) { + if(ncpi->info[1] &1) req = N_CONNECT_ACK | N_D_BIT; + add_d(plci,(word)(ncpi->length-3),&ncpi->info[4]); + } + } + nl_req_ncci(plci,req,(byte)ncci); + return 1; + } + } + return FALSE; +} + + +byte select_b_req(dword Id, word Number, DIVA_CAPI_ADAPTER * a, PLCI * plci, APPL * appl, API_PARSE * msg) +{ + word Info=0; + word i; + byte tel; + API_PARSE bp_parms[7]; + + if(!plci || !msg) + { + Info = _WRONG_IDENTIFIER; + } + else + { + dbug(1,dprintf("select_b_req[%d],PLCI=0x%x,Tel=0x%x,NL=0x%x,appl=0x%x,sstate=0x%x", + msg->length,plci->Id,plci->tel,plci->NL.Id,plci->appl,plci->SuppState)); + dbug(1,dprintf("PlciState=0x%x",plci->State)); + for(i=0;i<7;i++) bp_parms[i].length = 0; + + /* check if no channel is open, no B3 connected only */ + if((plci->State == IDLE) || (plci->State == OUTG_DIS_PENDING) || (plci->State == INC_DIS_PENDING) + || (plci->SuppState != IDLE) || plci->channels || plci->nl_remove_id) + { + Info = _WRONG_STATE; + } + /* check message format and fill bp_parms pointer */ + else if(msg->length && api_parse(&msg->info[1], (word)msg->length, "wwwsss", bp_parms)) + { + Info = _WRONG_MESSAGE_FORMAT; + } + else + { + if((plci->State==INC_CON_PENDING) || (plci->State==INC_CON_ALERT)) /* send alert tone inband to the network, */ + { /* e.g. Qsig or RBS or Cornet-N or xess PRI */ + if(Id & EXT_CONTROLLER) + { + sendf(appl, _SELECT_B_REQ|CONFIRM, Id, Number, "w", 0x2002); /* wrong controller */ + return 0; + } + plci->State=INC_CON_CONNECTED_ALERT; + plci->appl = appl; + clear_c_ind_mask_bit (plci, (word)(appl->Id-1)); + dump_c_ind_mask (plci); + for(i=0; i<max_appl; i++) /* disconnect the other appls */ + { /* its quasi a connect */ + if(test_c_ind_mask_bit (plci, i)) + sendf(&application[i], _DISCONNECT_I, Id, 0, "w", _OTHER_APPL_CONNECTED); + } + } + + api_save_msg(msg, "s", &plci->saved_msg); + tel = plci->tel; + if(Id & EXT_CONTROLLER) + { + if(tel) /* external controller in use by this PLCI */ + { + if(a->AdvSignalAppl && a->AdvSignalAppl!=appl) + { + dbug(1,dprintf("Ext_Ctrl in use 1")); + Info = _WRONG_STATE; + } + } + else /* external controller NOT in use by this PLCI ? */ + { + if(a->AdvSignalPLCI) + { + dbug(1,dprintf("Ext_Ctrl in use 2")); + Info = _WRONG_STATE; + } + else /* activate the codec */ + { + dbug(1,dprintf("Ext_Ctrl start")); + if(AdvCodecSupport(a, plci, appl, 0) ) + { + dbug(1,dprintf("Error in codec procedures")); + Info = _WRONG_STATE; + } + else if(plci->spoofed_msg==SPOOFING_REQUIRED) /* wait until codec is active */ + { + plci->spoofed_msg = AWAITING_SELECT_B; + plci->internal_command = BLOCK_PLCI; /* lock other commands */ + plci->command = 0; + dbug(1,dprintf("continue if codec loaded")); + return FALSE; + } + } + } + } + else /* external controller bit is OFF */ + { + if(tel) /* external controller in use, need to switch off */ + { + if(a->AdvSignalAppl==appl) + { + CodecIdCheck(a, plci); + plci->tel = 0; + plci->adv_nl = 0; + dbug(1,dprintf("Ext_Ctrl disable")); + } + else + { + dbug(1,dprintf("Ext_Ctrl not requested")); + } + } + } + if (!Info) + { + if (plci->call_dir & CALL_DIR_OUT) + plci->call_dir = CALL_DIR_OUT | CALL_DIR_ORIGINATE; + else if (plci->call_dir & CALL_DIR_IN) + plci->call_dir = CALL_DIR_IN | CALL_DIR_ANSWER; + start_internal_command (Id, plci, select_b_command); + return FALSE; + } + } + } + sendf(appl, _SELECT_B_REQ|CONFIRM, Id, Number, "w", Info); + return FALSE; +} + +byte manufacturer_req(dword Id, word Number, DIVA_CAPI_ADAPTER * a, PLCI * plci, APPL * appl, API_PARSE * parms) +{ + word command; + word i; + word ncci; + API_PARSE * m; + API_PARSE m_parms[5]; + word codec; + byte req; + byte ch; + byte dir; + static byte chi[2] = {0x01,0x00}; + static byte lli[2] = {0x01,0x00}; + static byte codec_cai[2] = {0x01,0x01}; + static byte null_msg = {0}; + static API_PARSE null_parms = { 0, &null_msg }; + PLCI * v_plci; + word Info=0; + + dbug(1,dprintf("manufacturer_req")); + for(i=0;i<5;i++) m_parms[i].length = 0; + + if(GET_DWORD(parms[0].info)!=_DI_MANU_ID) { + Info = _WRONG_MESSAGE_FORMAT; + } + command = GET_WORD(parms[1].info); + m = &parms[2]; + if (!Info) + { + switch(command) { + case _DI_ASSIGN_PLCI: + if(api_parse(&m->info[1],(word)m->length,"wbbs",m_parms)) { + Info = _WRONG_MESSAGE_FORMAT; + break; + } + codec = GET_WORD(m_parms[0].info); + ch = m_parms[1].info[0]; + dir = m_parms[2].info[0]; + if((i=get_plci(a))) { + plci = &a->plci[i-1]; + plci->appl = appl; + plci->command = _MANUFACTURER_R; + plci->m_command = command; + plci->number = Number; + plci->State = LOCAL_CONNECT; + Id = ( ((word)plci->Id<<8)|plci->adapter->Id|0x80); + dbug(1,dprintf("ManCMD,plci=0x%x",Id)); + + if((ch==1 || ch==2) && (dir<=2)) { + chi[1] = (byte)(0x80|ch); + lli[1] = 0; + plci->call_dir = CALL_DIR_OUT | CALL_DIR_ORIGINATE; + switch(codec) + { + case 0: + Info = add_b1(plci,&m_parms[3],0,0); + break; + case 1: + add_p(plci,CAI,codec_cai); + break; + /* manual 'swich on' to the codec support without signalling */ + /* first 'assign plci' with this function, then use */ + case 2: + if(AdvCodecSupport(a, plci, appl, 0) ) { + Info = _RESOURCE_ERROR; + } + else { + Info = add_b1(plci,&null_parms,0,B1_FACILITY_LOCAL); + lli[1] = 0x10; /* local call codec stream */ + } + break; + } + + plci->State = LOCAL_CONNECT; + plci->manufacturer = TRUE; + plci->command = _MANUFACTURER_R; + plci->m_command = command; + plci->number = Number; + + if(!Info) + { + add_p(plci,LLI,lli); + add_p(plci,CHI,chi); + add_p(plci,UID,"\x06\x43\x61\x70\x69\x32\x30"); + sig_req(plci,ASSIGN,DSIG_ID); + + if(!codec) + { + Info = add_b23(plci,&m_parms[3]); + if(!Info) + { + nl_req_ncci(plci,ASSIGN,0); + send_req(plci); + } + } + if(!Info) + { + dbug(1,dprintf("dir=0x%x,spoof=0x%x",dir,plci->spoofed_msg)); + if (plci->spoofed_msg==SPOOFING_REQUIRED) + { + api_save_msg (m_parms, "wbbs", &plci->saved_msg); + plci->spoofed_msg = AWAITING_MANUF_CON; + plci->internal_command = BLOCK_PLCI; /* reject other req meanwhile */ + plci->command = 0; + send_req(plci); + return FALSE; + } + if(dir==1) { + sig_req(plci,CALL_REQ,0); + } + else if(!dir){ + sig_req(plci,LISTEN_REQ,0); + } + send_req(plci); + } + else + { + sendf(appl, + _MANUFACTURER_R|CONFIRM, + Id, + Number, + "dww",_DI_MANU_ID,command,Info); + return 2; + } + } + } + } + else Info = _OUT_OF_PLCI; + break; + + case _DI_IDI_CTRL: + if(!plci) + { + Info = _WRONG_IDENTIFIER; + break; + } + if(api_parse(&m->info[1],(word)m->length,"bs",m_parms)) { + Info = _WRONG_MESSAGE_FORMAT; + break; + } + req = m_parms[0].info[0]; + plci->command = _MANUFACTURER_R; + plci->m_command = command; + plci->number = Number; + if(req==CALL_REQ) + { + plci->b_channel = getChannel(&m_parms[1]); + mixer_set_bchannel_id_esc (plci, plci->b_channel); + if(plci->spoofed_msg==SPOOFING_REQUIRED) + { + plci->spoofed_msg = CALL_REQ | AWAITING_MANUF_CON; + plci->internal_command = BLOCK_PLCI; /* reject other req meanwhile */ + plci->command = 0; + break; + } + } + else if(req==LAW_REQ) + { + plci->cr_enquiry = TRUE; + } + add_ss(plci,FTY,&m_parms[1]); + sig_req(plci,req,0); + send_req(plci); + if(req==HANGUP) + { + if (plci->NL.Id && !plci->nl_remove_id) + { + if (plci->channels) + { + for (ncci = 1; ncci < MAX_NCCI+1; ncci++) + { + if ((a->ncci_plci[ncci] == plci->Id) && (a->ncci_state[ncci] == CONNECTED)) + { + a->ncci_state[ncci] = OUTG_DIS_PENDING; + cleanup_ncci_data (plci, ncci); + nl_req_ncci(plci,N_DISC,(byte)ncci); + } + } + } + mixer_remove (plci); + nl_req_ncci(plci,REMOVE,0); + send_req(plci); + } + } + break; + + case _DI_SIG_CTRL: + /* signalling control for loop activation B-channel */ + if(!plci) + { + Info = _WRONG_IDENTIFIER; + break; + } + if(m->length){ + plci->command = _MANUFACTURER_R; + plci->number = Number; + add_ss(plci,FTY,m); + sig_req(plci,SIG_CTRL,0); + send_req(plci); + } + else Info = _WRONG_MESSAGE_FORMAT; + break; + + case _DI_RXT_CTRL: + /* activation control for receiver/transmitter B-channel */ + if(!plci) + { + Info = _WRONG_IDENTIFIER; + break; + } + if(m->length){ + plci->command = _MANUFACTURER_R; + plci->number = Number; + add_ss(plci,FTY,m); + sig_req(plci,DSP_CTRL,0); + send_req(plci); + } + else Info = _WRONG_MESSAGE_FORMAT; + break; + + case _DI_ADV_CODEC: + case _DI_DSP_CTRL: + /* TEL_CTRL commands to support non standard adjustments: */ + /* Ring on/off, Handset micro volume, external micro vol. */ + /* handset+external speaker volume, receiver+transm. gain,*/ + /* handsfree on (hookinfo off), set mixer command */ + + if(command == _DI_ADV_CODEC) + { + if(!a->AdvCodecPLCI) { + Info = _WRONG_STATE; + break; + } + v_plci = a->AdvCodecPLCI; + } + else + { + if (plci + && (m->length >= 3) + && (m->info[1] == 0x1c) + && (m->info[2] >= 1)) + { + if (m->info[3] == DSP_CTRL_OLD_SET_MIXER_COEFFICIENTS) + { + if ((plci->tel != ADV_VOICE) || (plci != a->AdvSignalPLCI)) + { + Info = _WRONG_STATE; + break; + } + a->adv_voice_coef_length = m->info[2] - 1; + if (a->adv_voice_coef_length > m->length - 3) + a->adv_voice_coef_length = (byte)(m->length - 3); + if (a->adv_voice_coef_length > ADV_VOICE_COEF_BUFFER_SIZE) + a->adv_voice_coef_length = ADV_VOICE_COEF_BUFFER_SIZE; + for (i = 0; i < a->adv_voice_coef_length; i++) + a->adv_voice_coef_buffer[i] = m->info[4 + i]; + if (plci->B1_facilities & B1_FACILITY_VOICE) + adv_voice_write_coefs (plci, ADV_VOICE_WRITE_UPDATE); + break; + } + else if (m->info[3] == DSP_CTRL_SET_DTMF_PARAMETERS) + { + if (!(a->manufacturer_features & MANUFACTURER_FEATURE_DTMF_PARAMETERS)) + { + Info = _FACILITY_NOT_SUPPORTED; + break; + } + + plci->dtmf_parameter_length = m->info[2] - 1; + if (plci->dtmf_parameter_length > m->length - 3) + plci->dtmf_parameter_length = (byte)(m->length - 3); + if (plci->dtmf_parameter_length > DTMF_PARAMETER_BUFFER_SIZE) + plci->dtmf_parameter_length = DTMF_PARAMETER_BUFFER_SIZE; + for (i = 0; i < plci->dtmf_parameter_length; i++) + plci->dtmf_parameter_buffer[i] = m->info[4+i]; + if (plci->B1_facilities & B1_FACILITY_DTMFR) + dtmf_parameter_write (plci); + break; + + } + } + v_plci = plci; + } + + if(!v_plci) + { + Info = _WRONG_IDENTIFIER; + break; + } + if(m->length){ + add_ss(v_plci,FTY,m); + sig_req(v_plci,TEL_CTRL,0); + send_req(v_plci); + } + else Info = _WRONG_MESSAGE_FORMAT; + + break; + + case _DI_OPTIONS_REQUEST: + if(api_parse(&m->info[1],(word)m->length,"d",m_parms)) { + Info = _WRONG_MESSAGE_FORMAT; + break; + } + if (GET_DWORD (m_parms[0].info) & ~a->man_profile.private_options) + { + Info = _FACILITY_NOT_SUPPORTED; + break; + } + a->requested_options_table[appl->Id-1] = GET_DWORD (m_parms[0].info); + break; + + + + default: + Info = _WRONG_MESSAGE_FORMAT; + break; + } + } + + sendf(appl, + _MANUFACTURER_R|CONFIRM, + Id, + Number, + "dww",_DI_MANU_ID,command,Info); + return FALSE; +} + + +byte manufacturer_res(dword Id, word Number, DIVA_CAPI_ADAPTER * a, PLCI * plci, APPL * appl, API_PARSE * msg) +{ + word indication; + + API_PARSE m_parms[3]; + API_PARSE *ncpi; + API_PARSE fax_parms[9]; + word i; + byte len; + + + dbug(1,dprintf("manufacturer_res")); + + if ((msg[0].length == 0) + || (msg[1].length == 0) + || (GET_DWORD(msg[0].info)!=_DI_MANU_ID)) + { + return FALSE; + } + indication = GET_WORD(msg[1].info); + switch (indication) + { + + case _DI_NEGOTIATE_B3: + if(!plci) + break; + if (((plci->B3_prot != 4) && (plci->B3_prot != 5)) + || !(plci->ncpi_state & NCPI_NEGOTIATE_B3_SENT)) + { + dbug(1,dprintf("wrong state for NEGOTIATE_B3 parameters")); + break; + } + if (api_parse (&msg[2].info[1], msg[2].length, "ws", m_parms)) + { + dbug(1,dprintf("wrong format in NEGOTIATE_B3 parameters")); + break; + } + ncpi = &m_parms[1]; + len = ((byte)(((T30_INFO *) 0)->station_id + 20)); + if (plci->fax_connect_info_length < len) + { + ((T30_INFO *)(plci->fax_connect_info_buffer))->station_id_len = 0; + ((T30_INFO *)(plci->fax_connect_info_buffer))->head_line_len = 0; + } + if (api_parse (&ncpi->info[1], ncpi->length, "wwwwssss", fax_parms)) + { + dbug(1,dprintf("non-standard facilities info missing or wrong format")); + } + else + { + if (plci->fax_connect_info_length <= len) + plci->fax_connect_info_buffer[len] = 0; + len += 1 + plci->fax_connect_info_buffer[len]; + if (plci->fax_connect_info_length <= len) + plci->fax_connect_info_buffer[len] = 0; + len += 1 + plci->fax_connect_info_buffer[len]; + if ((fax_parms[7].length >= 3) && (fax_parms[7].info[1] >= 2)) + plci->nsf_control_bits = GET_WORD(&fax_parms[7].info[2]); + plci->fax_connect_info_buffer[len++] = (byte)(fax_parms[7].length); + for (i = 0; i < fax_parms[7].length; i++) + plci->fax_connect_info_buffer[len++] = fax_parms[7].info[1+i]; + } + plci->fax_connect_info_length = len; + plci->fax_edata_ack_length = plci->fax_connect_info_length; + start_internal_command (Id, plci, fax_edata_ack_command); + break; + + } + return FALSE; +} + +/*------------------------------------------------------------------*/ +/* IDI callback function */ +/*------------------------------------------------------------------*/ + +void callback(ENTITY * e) +{ + DIVA_CAPI_ADAPTER * a; + APPL * appl; + PLCI * plci; + CAPI_MSG *m; + word i, j; + byte rc; + byte ch; + byte req; + byte global_req; + int no_cancel_rc; + + dbug(1,dprintf("%x:CB(%x:Req=%x,Rc=%x,Ind=%x)", + (e->user[0]+1)&0x7fff,e->Id,e->Req,e->Rc,e->Ind)); + + a = &(adapter[(byte)e->user[0]]); + plci = &(a->plci[e->user[1]]); + no_cancel_rc = DIVA_CAPI_SUPPORTS_NO_CANCEL(a); + + /* + If new protocol code and new XDI is used then CAPI should work + fully in accordance with IDI cpec an look on callback field instead + of Rc field for return codes. + */ + if (((e->complete == 0xff) && no_cancel_rc) || + (e->Rc && !no_cancel_rc)) { + rc = e->Rc; + ch = e->RcCh; + req = e->Req; + e->Rc = 0; + + if (e->user[0] & 0x8000) + { + /* + If REMOVE request was sent then we have to wait until + return code with Id set to zero arrives. + All other return codes should be ignored. + */ + if (req == REMOVE) + { + if (e->Id) + { + dbug(1,dprintf("cancel RC in REMOVE state")); + return; + } + channel_flow_control_remove (plci); + for (i = 0; i < 256; i++) + { + if (a->FlowControlIdTable[i] == plci->nl_remove_id) + a->FlowControlIdTable[i] = 0; + } + plci->nl_remove_id = 0; + if (plci->rx_dma_descriptor > 0) { + diva_free_dma_descriptor (plci, plci->rx_dma_descriptor - 1); + plci->rx_dma_descriptor = 0; + } + } + if (rc == OK_FC) + { + a->FlowControlIdTable[ch] = e->Id; + a->FlowControlSkipTable[ch] = 0; + + a->ch_flow_control[ch] |= N_OK_FC_PENDING; + a->ch_flow_plci[ch] = plci->Id; + plci->nl_req = 0; + } + else + { + /* + Cancel return codes self, if feature was requested + */ + if (no_cancel_rc && (a->FlowControlIdTable[ch] == e->Id) && e->Id) { + a->FlowControlIdTable[ch] = 0; + if ((rc == OK) && a->FlowControlSkipTable[ch]) { + dbug(3,dprintf ("XDI CAPI: RC cancelled Id:0x02, Ch:%02x", e->Id, ch)); + return; + } + } + + if (a->ch_flow_control[ch] & N_OK_FC_PENDING) + { + a->ch_flow_control[ch] &= ~N_OK_FC_PENDING; + if (ch == e->ReqCh) + plci->nl_req = 0; + } + else + plci->nl_req = 0; + } + if (plci->nl_req) + control_rc (plci, 0, rc, ch, 0, TRUE); + else + { + if (req == N_XON) + { + channel_x_on (plci, ch); + if (plci->internal_command) + control_rc (plci, req, rc, ch, 0, TRUE); + } + else + { + if (plci->nl_global_req) + { + global_req = plci->nl_global_req; + plci->nl_global_req = 0; + if (rc != ASSIGN_OK) { + e->Id = 0; + if (plci->rx_dma_descriptor > 0) { + diva_free_dma_descriptor (plci, plci->rx_dma_descriptor - 1); + plci->rx_dma_descriptor = 0; + } + } + channel_xmit_xon (plci); + control_rc (plci, 0, rc, ch, global_req, TRUE); + } + else if (plci->data_sent) + { + channel_xmit_xon (plci); + plci->data_sent = FALSE; + plci->NL.XNum = 1; + data_rc (plci, ch); + if (plci->internal_command) + control_rc (plci, req, rc, ch, 0, TRUE); + } + else + { + channel_xmit_xon (plci); + control_rc (plci, req, rc, ch, 0, TRUE); + } + } + } + } + else + { + /* + If REMOVE request was sent then we have to wait until + return code with Id set to zero arrives. + All other return codes should be ignored. + */ + if (req == REMOVE) + { + if (e->Id) + { + dbug(1,dprintf("cancel RC in REMOVE state")); + return; + } + plci->sig_remove_id = 0; + } + plci->sig_req = 0; + if (plci->sig_global_req) + { + global_req = plci->sig_global_req; + plci->sig_global_req = 0; + if (rc != ASSIGN_OK) + e->Id = 0; + channel_xmit_xon (plci); + control_rc (plci, 0, rc, ch, global_req, FALSE); + } + else + { + channel_xmit_xon (plci); + control_rc (plci, req, rc, ch, 0, FALSE); + } + } + /* + Again: in accordance with IDI spec Rc and Ind can't be delivered in the + same callback. Also if new XDI and protocol code used then jump + direct to finish. + */ + if (no_cancel_rc) { + channel_xmit_xon(plci); + goto capi_callback_suffix; + } + } + + channel_xmit_xon(plci); + + if (e->Ind) { + if (e->user[0] &0x8000) { + byte Ind = e->Ind & 0x0f; + byte Ch = e->IndCh; + if (((Ind==N_DISC) || (Ind==N_DISC_ACK)) && + (a->ch_flow_plci[Ch] == plci->Id)) { + if (a->ch_flow_control[Ch] & N_RX_FLOW_CONTROL_MASK) { + dbug(3,dprintf ("XDI CAPI: I: pending N-XON Ch:%02x", Ch)); + } + a->ch_flow_control[Ch] &= ~N_RX_FLOW_CONTROL_MASK; + } + nl_ind(plci); + if ((e->RNR != 1) && + (a->ch_flow_plci[Ch] == plci->Id) && + (a->ch_flow_control[Ch] & N_RX_FLOW_CONTROL_MASK)) { + a->ch_flow_control[Ch] &= ~N_RX_FLOW_CONTROL_MASK; + dbug(3,dprintf ("XDI CAPI: I: remove faked N-XON Ch:%02x", Ch)); + } + } else { + sig_ind(plci); + } + e->Ind = 0; + } + +capi_callback_suffix: + + while (!plci->req_in + && !plci->internal_command + && (plci->msg_in_write_pos != plci->msg_in_read_pos)) + { + j = (plci->msg_in_read_pos == plci->msg_in_wrap_pos) ? 0 : plci->msg_in_read_pos; + + i = (((CAPI_MSG *)(&((byte *)(plci->msg_in_queue))[j]))->header.length + 3) & 0xfffc; + + m = (CAPI_MSG *)(&((byte *)(plci->msg_in_queue))[j]); + appl = *((APPL * *)(&((byte *)(plci->msg_in_queue))[j+i])); + dbug(1,dprintf("dequeue msg(0x%04x) - write=%d read=%d wrap=%d", + m->header.command, plci->msg_in_write_pos, plci->msg_in_read_pos, plci->msg_in_wrap_pos)); + if (plci->msg_in_read_pos == plci->msg_in_wrap_pos) + { + plci->msg_in_wrap_pos = MSG_IN_QUEUE_SIZE; + plci->msg_in_read_pos = i + MSG_IN_OVERHEAD; + } + else + { + plci->msg_in_read_pos = j + i + MSG_IN_OVERHEAD; + } + if (plci->msg_in_read_pos == plci->msg_in_write_pos) + { + plci->msg_in_write_pos = MSG_IN_QUEUE_SIZE; + plci->msg_in_read_pos = MSG_IN_QUEUE_SIZE; + } + else if (plci->msg_in_read_pos == plci->msg_in_wrap_pos) + { + plci->msg_in_read_pos = MSG_IN_QUEUE_SIZE; + plci->msg_in_wrap_pos = MSG_IN_QUEUE_SIZE; + } + i = api_put (appl, m); + if (i != 0) + { + if (m->header.command == _DATA_B3_R) + + TransmitBufferFree (appl, (byte *)(m->info.data_b3_req.Data)); + + dbug(1,dprintf("Error 0x%04x from msg(0x%04x)", i, m->header.command)); + break; + } + + if (plci->li_notify_update) + { + plci->li_notify_update = FALSE; + mixer_notify_update (plci, FALSE); + } + + } + send_data(plci); + send_req(plci); +} + + +void control_rc(PLCI * plci, byte req, byte rc, byte ch, byte global_req, byte nl_rc) +{ + dword Id; + dword rId; + word Number; + word Info=0; + word i; + word ncci; + DIVA_CAPI_ADAPTER * a; + APPL * appl; + PLCI * rplci; + byte SSparms[] = "\x05\x00\x00\x02\x00\x00"; + byte SSstruct[] = "\x09\x00\x00\x06\x00\x00\x00\x00\x00\x00"; + + if (!plci) { + dbug(0,dprintf("A: control_rc, no plci %02x:%02x:%02x:%02x:%02x", req, rc, ch, global_req, nl_rc)); + return; + } + dbug(1,dprintf("req0_in/out=%d/%d",plci->req_in,plci->req_out)); + if(plci->req_in!=plci->req_out) + { + if (nl_rc || (global_req != ASSIGN) || (rc == ASSIGN_OK)) + { + dbug(1,dprintf("req_1return")); + return; + } + /* cancel outstanding request on the PLCI after SIG ASSIGN failure */ + } + plci->req_in = plci->req_in_start = plci->req_out = 0; + dbug(1,dprintf("control_rc")); + + appl = plci->appl; + a = plci->adapter; + ncci = a->ch_ncci[ch]; + if(appl) + { + Id = (((dword)(ncci ? ncci : ch)) << 16) | ((word)plci->Id << 8) | a->Id; + if(plci->tel && plci->SuppState!=CALL_HELD) Id|=EXT_CONTROLLER; + Number = plci->number; + dbug(1,dprintf("Contr_RC-Id=%08lx,plci=%x,tel=%x, entity=0x%x, command=0x%x, int_command=0x%x",Id,plci->Id,plci->tel,plci->Sig.Id,plci->command,plci->internal_command)); + dbug(1,dprintf("channels=0x%x",plci->channels)); + if (plci_remove_check(plci)) + return; + if(req==REMOVE && rc==ASSIGN_OK) + { + sig_req(plci,HANGUP,0); + sig_req(plci,REMOVE,0); + send_req(plci); + } + if(plci->command) + { + switch(plci->command) + { + case C_HOLD_REQ: + dbug(1,dprintf("HoldRC=0x%x",rc)); + SSparms[1] = (byte)S_HOLD; + if(rc!=OK) + { + plci->SuppState = IDLE; + Info = 0x2001; + } + sendf(appl,_FACILITY_R|CONFIRM,Id,Number,"wws",Info,3,SSparms); + break; + + case C_RETRIEVE_REQ: + dbug(1,dprintf("RetrieveRC=0x%x",rc)); + SSparms[1] = (byte)S_RETRIEVE; + if(rc!=OK) + { + plci->SuppState = CALL_HELD; + Info = 0x2001; + } + sendf(appl,_FACILITY_R|CONFIRM,Id,Number,"wws",Info,3,SSparms); + break; + + case _INFO_R: + dbug(1,dprintf("InfoRC=0x%x",rc)); + if(rc!=OK) Info=_WRONG_STATE; + sendf(appl,_INFO_R|CONFIRM,Id,Number,"w",Info); + break; + + case _CONNECT_R: + dbug(1,dprintf("Connect_R=0x%x/0x%x/0x%x/0x%x",req,rc,global_req,nl_rc)); + if (plci->State == INC_DIS_PENDING) + break; + if(plci->Sig.Id!=0xff) + { + if (((global_req == ASSIGN) && (rc != ASSIGN_OK)) + || (!nl_rc && (req == CALL_REQ) && (rc != OK))) + { + dbug(1,dprintf("No more IDs/Call_Req failed")); + sendf(appl,_CONNECT_R|CONFIRM,Id&0xffL,Number,"w",_OUT_OF_PLCI); + plci_remove(plci); + plci->State = IDLE; + break; + } + if(plci->State!=LOCAL_CONNECT)plci->State = OUTG_CON_PENDING; + sendf(appl,_CONNECT_R|CONFIRM,Id,Number,"w",0); + } + else /* D-ch activation */ + { + if (rc != ASSIGN_OK) + { + dbug(1,dprintf("No more IDs/X.25 Call_Req failed")); + sendf(appl,_CONNECT_R|CONFIRM,Id&0xffL,Number,"w",_OUT_OF_PLCI); + plci_remove(plci); + plci->State = IDLE; + break; + } + sendf(appl,_CONNECT_R|CONFIRM,Id,Number,"w",0); + sendf(plci->appl,_CONNECT_ACTIVE_I,Id,0,"sss","","",""); + plci->State = INC_ACT_PENDING; + } + break; + + case _CONNECT_I|RESPONSE: + if (plci->State != INC_DIS_PENDING) + plci->State = INC_CON_ACCEPT; + break; + + case _DISCONNECT_R: + if (plci->State == INC_DIS_PENDING) + break; + if(plci->Sig.Id!=0xff) + { + plci->State = OUTG_DIS_PENDING; + sendf(appl,_DISCONNECT_R|CONFIRM,Id,Number,"w",0); + } + break; + + case SUSPEND_REQ: + break; + + case RESUME_REQ: + break; + + case _CONNECT_B3_R: + if(rc!=OK) + { + sendf(appl,_CONNECT_B3_R|CONFIRM,Id,Number,"w",_WRONG_IDENTIFIER); + break; + } + ncci = get_ncci (plci, ch, 0); + Id = (Id & 0xffff) | (((dword) ncci) << 16); + plci->channels++; + if(req==N_RESET) + { + a->ncci_state[ncci] = INC_ACT_PENDING; + sendf(appl,_CONNECT_B3_R|CONFIRM,Id,Number,"w",0); + sendf(appl,_CONNECT_B3_ACTIVE_I,Id,0,"s",""); + } + else + { + a->ncci_state[ncci] = OUTG_CON_PENDING; + sendf(appl,_CONNECT_B3_R|CONFIRM,Id,Number,"w",0); + } + break; + + case _CONNECT_B3_I|RESPONSE: + break; + + case _RESET_B3_R: +/* sendf(appl,_RESET_B3_R|CONFIRM,Id,Number,"w",0);*/ + break; + + case _DISCONNECT_B3_R: + sendf(appl,_DISCONNECT_B3_R|CONFIRM,Id,Number,"w",0); + break; + + case _MANUFACTURER_R: + break; + + case PERM_LIST_REQ: + if(rc!=OK) + { + Info = _WRONG_IDENTIFIER; + sendf(plci->appl,_CONNECT_R|CONFIRM,Id,Number,"w",Info); + plci_remove(plci); + } + else + sendf(plci->appl,_CONNECT_R|CONFIRM,Id,Number,"w",Info); + break; + + default: + break; + } + plci->command = 0; + } + else if (plci->internal_command) + { + switch(plci->internal_command) + { + case BLOCK_PLCI: + return; + + case GET_MWI_STATE: + if(rc==OK) /* command supported, wait for indication */ + { + return; + } + plci_remove(plci); + break; + + /* Get Supported Services */ + case GETSERV_REQ_PEND: + if(rc==OK) /* command supported, wait for indication */ + { + break; + } + PUT_DWORD(&SSstruct[6], MASK_TERMINAL_PORTABILITY); + sendf(appl, _FACILITY_R|CONFIRM, Id, Number, "wws",0,3,SSstruct); + plci_remove(plci); + break; + + case INTERR_DIVERSION_REQ_PEND: /* Interrogate Parameters */ + case INTERR_NUMBERS_REQ_PEND: + case CF_START_PEND: /* Call Forwarding Start pending */ + case CF_STOP_PEND: /* Call Forwarding Stop pending */ + case CCBS_REQUEST_REQ_PEND: + case CCBS_DEACTIVATE_REQ_PEND: + case CCBS_INTERROGATE_REQ_PEND: + switch(plci->internal_command) + { + case INTERR_DIVERSION_REQ_PEND: + SSparms[1] = S_INTERROGATE_DIVERSION; + break; + case INTERR_NUMBERS_REQ_PEND: + SSparms[1] = S_INTERROGATE_NUMBERS; + break; + case CF_START_PEND: + SSparms[1] = S_CALL_FORWARDING_START; + break; + case CF_STOP_PEND: + SSparms[1] = S_CALL_FORWARDING_STOP; + break; + case CCBS_REQUEST_REQ_PEND: + SSparms[1] = S_CCBS_REQUEST; + break; + case CCBS_DEACTIVATE_REQ_PEND: + SSparms[1] = S_CCBS_DEACTIVATE; + break; + case CCBS_INTERROGATE_REQ_PEND: + SSparms[1] = S_CCBS_INTERROGATE; + break; + } + if(global_req==ASSIGN) + { + dbug(1,dprintf("AssignDiversion_RC=0x%x/0x%x",req,rc)); + return; + } + if(!plci->appl) break; + if(rc==ISDN_GUARD_REJ) + { + Info = _CAPI_GUARD_ERROR; + } + else if(rc!=OK) + { + Info = _SUPPLEMENTARY_SERVICE_NOT_SUPPORTED; + } + sendf(plci->appl,_FACILITY_R|CONFIRM,Id&0x7, + plci->number,"wws",Info,(word)3,SSparms); + if(Info) plci_remove(plci); + break; + + /* 3pty conference pending */ + case PTY_REQ_PEND: + if(!plci->relatedPTYPLCI) break; + rplci = plci->relatedPTYPLCI; + SSparms[1] = plci->ptyState; + rId = ((word)rplci->Id<<8)|rplci->adapter->Id; + if(rplci->tel) rId|=EXT_CONTROLLER; + if(rc!=OK) + { + Info = 0x300E; /* not supported */ + plci->relatedPTYPLCI = NULL; + plci->ptyState = 0; + } + sendf(rplci->appl, + _FACILITY_R|CONFIRM, + rId, + plci->number, + "wws",Info,(word)3,SSparms); + break; + + /* Explicit Call Transfer pending */ + case ECT_REQ_PEND: + dbug(1,dprintf("ECT_RC=0x%x/0x%x",req,rc)); + if(!plci->relatedPTYPLCI) break; + rplci = plci->relatedPTYPLCI; + SSparms[1] = S_ECT; + rId = ((word)rplci->Id<<8)|rplci->adapter->Id; + if(rplci->tel) rId|=EXT_CONTROLLER; + if(rc!=OK) + { + Info = 0x300E; /* not supported */ + plci->relatedPTYPLCI = NULL; + plci->ptyState = 0; + } + sendf(rplci->appl, + _FACILITY_R|CONFIRM, + rId, + plci->number, + "wws",Info,(word)3,SSparms); + break; + + case _MANUFACTURER_R: + dbug(1,dprintf("_Manufacturer_R=0x%x/0x%x",req,rc)); + if ((global_req == ASSIGN) && (rc != ASSIGN_OK)) + { + dbug(1,dprintf("No more IDs")); + sendf(appl,_MANUFACTURER_R|CONFIRM,Id,Number,"dww",_DI_MANU_ID,_MANUFACTURER_R,_OUT_OF_PLCI); + plci_remove(plci); /* after codec init, internal codec commands pending */ + } + break; + + case _CONNECT_R: + dbug(1,dprintf("_Connect_R=0x%x/0x%x",req,rc)); + if ((global_req == ASSIGN) && (rc != ASSIGN_OK)) + { + dbug(1,dprintf("No more IDs")); + sendf(appl,_CONNECT_R|CONFIRM,Id&0xffL,Number,"w",_OUT_OF_PLCI); + plci_remove(plci); /* after codec init, internal codec commands pending */ + } + break; + + case PERM_COD_HOOK: /* finished with Hook_Ind */ + return; + + case PERM_COD_CALL: + dbug(1,dprintf("***Codec Connect_Pending A, Rc = 0x%x",rc)); + plci->internal_command = PERM_COD_CONN_PEND; + return; + + case PERM_COD_ASSIGN: + dbug(1,dprintf("***Codec Assign A, Rc = 0x%x",rc)); + if(rc!=ASSIGN_OK) break; + sig_req(plci,CALL_REQ,0); + send_req(plci); + plci->internal_command = PERM_COD_CALL; + return; + + /* Null Call Reference Request pending */ + case C_NCR_FAC_REQ: + dbug(1,dprintf("NCR_FAC=0x%x/0x%x",req,rc)); + if(global_req==ASSIGN) + { + if(rc==ASSIGN_OK) + { + return; + } + else + { + sendf(appl,_INFO_R|CONFIRM,Id&0xf,Number,"w",_WRONG_STATE); + appl->NullCREnable = FALSE; + plci_remove(plci); + } + } + else if(req==NCR_FACILITY) + { + if(rc==OK) + { + sendf(appl,_INFO_R|CONFIRM,Id&0xf,Number,"w",0); + } + else + { + sendf(appl,_INFO_R|CONFIRM,Id&0xf,Number,"w",_WRONG_STATE); + appl->NullCREnable = FALSE; + } + plci_remove(plci); + } + break; + + case HOOK_ON_REQ: + if(plci->channels) + { + if(a->ncci_state[ncci]==CONNECTED) + { + a->ncci_state[ncci] = OUTG_DIS_PENDING; + cleanup_ncci_data (plci, ncci); + nl_req_ncci(plci,N_DISC,(byte)ncci); + } + break; + } + break; + + case HOOK_OFF_REQ: + if (plci->State == INC_DIS_PENDING) + break; + sig_req(plci,CALL_REQ,0); + send_req(plci); + plci->State=OUTG_CON_PENDING; + break; + + + case MWI_ACTIVATE_REQ_PEND: + case MWI_DEACTIVATE_REQ_PEND: + if(global_req == ASSIGN && rc==ASSIGN_OK) + { + dbug(1,dprintf("MWI_REQ assigned")); + return; + } + else if(rc!=OK) + { + if(rc==WRONG_IE) + { + Info = 0x2007; /* Illegal message parameter coding */ + dbug(1,dprintf("MWI_REQ invalid parameter")); + } + else + { + Info = 0x300B; /* not supported */ + dbug(1,dprintf("MWI_REQ not supported")); + } + /* 0x3010: Request not allowed in this state */ + PUT_WORD(&SSparms[4],0x300E); /* SS not supported */ + + } + if(plci->internal_command==MWI_ACTIVATE_REQ_PEND) + { + PUT_WORD(&SSparms[1],S_MWI_ACTIVATE); + } + else PUT_WORD(&SSparms[1],S_MWI_DEACTIVATE); + + if(plci->cr_enquiry) + { + sendf(plci->appl, + _FACILITY_R|CONFIRM, + Id&0xf, + plci->number, + "wws",Info,(word)3,SSparms); + if(rc!=OK) plci_remove(plci); + } + else + { + sendf(plci->appl, + _FACILITY_R|CONFIRM, + Id, + plci->number, + "wws",Info,(word)3,SSparms); + } + break; + + case CONF_BEGIN_REQ_PEND: + case CONF_ADD_REQ_PEND: + case CONF_SPLIT_REQ_PEND: + case CONF_DROP_REQ_PEND: + case CONF_ISOLATE_REQ_PEND: + case CONF_REATTACH_REQ_PEND: + dbug(1,dprintf("CONF_RC=0x%x/0x%x",req,rc)); + if((plci->internal_command==CONF_ADD_REQ_PEND)&&(!plci->relatedPTYPLCI)) break; + rplci = plci; + rId = Id; + switch(plci->internal_command) + { + case CONF_BEGIN_REQ_PEND: + SSparms[1] = S_CONF_BEGIN; + break; + case CONF_ADD_REQ_PEND: + SSparms[1] = S_CONF_ADD; + rplci = plci->relatedPTYPLCI; + rId = ((word)rplci->Id<<8)|rplci->adapter->Id; + break; + case CONF_SPLIT_REQ_PEND: + SSparms[1] = S_CONF_SPLIT; + break; + case CONF_DROP_REQ_PEND: + SSparms[1] = S_CONF_DROP; + break; + case CONF_ISOLATE_REQ_PEND: + SSparms[1] = S_CONF_ISOLATE; + break; + case CONF_REATTACH_REQ_PEND: + SSparms[1] = S_CONF_REATTACH; + break; + } + + if(rc!=OK) + { + Info = 0x300E; /* not supported */ + plci->relatedPTYPLCI = NULL; + plci->ptyState = 0; + } + sendf(rplci->appl, + _FACILITY_R|CONFIRM, + rId, + plci->number, + "wws",Info,(word)3,SSparms); + break; + + case VSWITCH_REQ_PEND: + if(rc!=OK) + { + if(plci->relatedPTYPLCI) + { + plci->relatedPTYPLCI->vswitchstate=0; + plci->relatedPTYPLCI->vsprot=0; + plci->relatedPTYPLCI->vsprotdialect=0; + } + plci->vswitchstate=0; + plci->vsprot=0; + plci->vsprotdialect=0; + } + else + { + if(plci->relatedPTYPLCI && + plci->vswitchstate==1 && + plci->relatedPTYPLCI->vswitchstate==3) /* join complete */ + plci->vswitchstate=3; + } + break; + + /* Call Deflection Request pending (SSCT) */ + case CD_REQ_PEND: + SSparms[1] = S_CALL_DEFLECTION; + if(rc!=OK) + { + Info = 0x300E; /* not supported */ + plci->appl->CDEnable = 0; + } + sendf(plci->appl,_FACILITY_R|CONFIRM,Id, + plci->number,"wws",Info,(word)3,SSparms); + break; + + case RTP_CONNECT_B3_REQ_COMMAND_2: + if (rc == OK) + { + ncci = get_ncci (plci, ch, 0); + Id = (Id & 0xffff) | (((dword) ncci) << 16); + plci->channels++; + a->ncci_state[ncci] = OUTG_CON_PENDING; + } + + default: + if (plci->internal_command_queue[0]) + { + (*(plci->internal_command_queue[0]))(Id, plci, rc); + if (plci->internal_command) + return; + } + break; + } + next_internal_command (Id, plci); + } + } + else /* appl==0 */ + { + Id = ((word)plci->Id<<8)|plci->adapter->Id; + if(plci->tel) Id|=EXT_CONTROLLER; + + switch(plci->internal_command) + { + case BLOCK_PLCI: + return; + + case START_L1_SIG_ASSIGN_PEND: + case REM_L1_SIG_ASSIGN_PEND: + if(global_req == ASSIGN) + { + break; + } + else + { + dbug(1,dprintf("***L1 Req rem PLCI")); + plci->internal_command = 0; + sig_req(plci,REMOVE,0); + send_req(plci); + } + break; + + /* Call Deflection Request pending, just no appl ptr assigned */ + case CD_REQ_PEND: + SSparms[1] = S_CALL_DEFLECTION; + if(rc!=OK) + { + Info = 0x300E; /* not supported */ + } + for(i=0; i<max_appl; i++) + { + if(application[i].CDEnable) + { + if(!application[i].Id) application[i].CDEnable = 0; + else + { + sendf(&application[i],_FACILITY_R|CONFIRM,Id, + plci->number,"wws",Info,(word)3,SSparms); + if(Info) application[i].CDEnable = 0; + } + } + } + plci->internal_command = 0; + break; + + case PERM_COD_HOOK: /* finished with Hook_Ind */ + return; + + case PERM_COD_CALL: + plci->internal_command = PERM_COD_CONN_PEND; + dbug(1,dprintf("***Codec Connect_Pending, Rc = 0x%x",rc)); + return; + + case PERM_COD_ASSIGN: + dbug(1,dprintf("***Codec Assign, Rc = 0x%x",rc)); + plci->internal_command = 0; + if(rc!=ASSIGN_OK) break; + plci->internal_command = PERM_COD_CALL; + sig_req(plci,CALL_REQ,0); + send_req(plci); + return; + + case LISTEN_SIG_ASSIGN_PEND: + if(rc == ASSIGN_OK) + { + plci->internal_command = 0; + dbug(1,dprintf("ListenCheck, new SIG_ID = 0x%x",plci->Sig.Id)); + add_p(plci,ESC,"\x02\x18\x00"); /* support call waiting */ + sig_req(plci,INDICATE_REQ,0); + send_req(plci); + } + else + { + dbug(1,dprintf("ListenCheck failed (assignRc=0x%x)",rc)); + a->listen_active--; + plci_remove(plci); + plci->State = IDLE; + } + break; + + case USELAW_REQ: + if(global_req == ASSIGN) + { + if (rc==ASSIGN_OK) + { + sig_req(plci,LAW_REQ,0); + send_req(plci); + dbug(1,dprintf("Auto-Law assigned")); + } + else + { + dbug(1,dprintf("Auto-Law assign failed")); + a->automatic_law = 3; + plci->internal_command = 0; + a->automatic_lawPLCI = NULL; + } + break; + } + else if(req == LAW_REQ && rc==OK) + { + dbug(1,dprintf("Auto-Law initiated")); + a->automatic_law = 2; + plci->internal_command = 0; + } + else + { + dbug(1,dprintf("Auto-Law not supported")); + a->automatic_law = 3; + plci->internal_command = 0; + sig_req(plci,REMOVE,0); + send_req(plci); + a->automatic_lawPLCI = NULL; + } + break; + } + plci_remove_check(plci); + } +} + +void data_rc(PLCI * plci, byte ch) +{ + dword Id; + DIVA_CAPI_ADAPTER * a; + NCCI *ncci_ptr; + DATA_B3_DESC *data; + word ncci; + + if (plci->appl) + { + TransmitBufferFree (plci->appl, plci->data_sent_ptr); + a = plci->adapter; + ncci = a->ch_ncci[ch]; + if (ncci && (a->ncci_plci[ncci] == plci->Id)) + { + ncci_ptr = &(a->ncci[ncci]); + dbug(1,dprintf("data_out=%d, data_pending=%d",ncci_ptr->data_out,ncci_ptr->data_pending)); + if (ncci_ptr->data_pending) + { + data = &(ncci_ptr->DBuffer[ncci_ptr->data_out]); + if (!(data->Flags &4) && a->ncci_state[ncci]) + { + Id = (((dword)ncci)<<16)|((word)plci->Id<<8)|a->Id; + if(plci->tel) Id|=EXT_CONTROLLER; + sendf(plci->appl,_DATA_B3_R|CONFIRM,Id,data->Number, + "ww",data->Handle,0); + } + (ncci_ptr->data_out)++; + if (ncci_ptr->data_out == MAX_DATA_B3) + ncci_ptr->data_out = 0; + (ncci_ptr->data_pending)--; + } + } + } +} + +void data_ack(PLCI * plci, byte ch) +{ + dword Id; + DIVA_CAPI_ADAPTER * a; + NCCI *ncci_ptr; + word ncci; + + a = plci->adapter; + ncci = a->ch_ncci[ch]; + ncci_ptr = &(a->ncci[ncci]); + if (ncci_ptr->data_ack_pending) + { + if (a->ncci_state[ncci] && (a->ncci_plci[ncci] == plci->Id)) + { + Id = (((dword)ncci)<<16)|((word)plci->Id<<8)|a->Id; + if(plci->tel) Id|=EXT_CONTROLLER; + sendf(plci->appl,_DATA_B3_R|CONFIRM,Id,ncci_ptr->DataAck[ncci_ptr->data_ack_out].Number, + "ww",ncci_ptr->DataAck[ncci_ptr->data_ack_out].Handle,0); + } + (ncci_ptr->data_ack_out)++; + if (ncci_ptr->data_ack_out == MAX_DATA_ACK) + ncci_ptr->data_ack_out = 0; + (ncci_ptr->data_ack_pending)--; + } +} + +void sig_ind(PLCI * plci) +{ + dword x_Id; + dword Id; + dword rId; + word Number = 0; + word i; + word cip; + dword cip_mask; + byte *ie; + DIVA_CAPI_ADAPTER * a; + API_PARSE saved_parms[MAX_MSG_PARMS+1]; +#define MAXPARMSIDS 31 + byte * parms[MAXPARMSIDS]; + byte * add_i[4]; + byte * multi_fac_parms[MAX_MULTI_IE]; + byte * multi_pi_parms [MAX_MULTI_IE]; + byte * multi_ssext_parms [MAX_MULTI_IE]; + byte * multi_CiPN_parms [MAX_MULTI_IE]; + + byte * multi_vswitch_parms [MAX_MULTI_IE]; + + byte ai_len; + byte *esc_chi = ""; + byte *esc_law = ""; + byte *pty_cai = ""; + byte *esc_cr = ""; + byte *esc_profile = ""; + + byte facility[256]; + PLCI * tplci = NULL; + byte chi[] = "\x02\x18\x01"; + byte voice_cai[] = "\x06\x14\x00\x00\x00\x00\x08"; + byte resume_cau[] = "\x05\x05\x00\x02\x00\x00"; + /* ESC_MSGTYPE must be the last but one message, a new IE has to be */ + /* included before the ESC_MSGTYPE and MAXPARMSIDS has to be incremented */ + /* SMSG is situated at the end because its 0 (for compatibility reasons */ + /* (see Info_Mask Bit 4, first IE. then the message type) */ + word parms_id[] = + {MAXPARMSIDS, CPN, 0xff, DSA, OSA, BC, LLC, HLC, ESC_CAUSE, DSP, DT, CHA, + UUI, CONG_RR, CONG_RNR, ESC_CHI, KEY, CHI, CAU, ESC_LAW, + RDN, RDX, CONN_NR, RIN, NI, CAI, ESC_CR, + CST, ESC_PROFILE, 0xff, ESC_MSGTYPE, SMSG}; + /* 14 FTY repl by ESC_CHI */ + /* 18 PI repl by ESC_LAW */ + /* removed OAD changed to 0xff for future use, OAD is multiIE now */ + word multi_fac_id[] = {1, FTY}; + word multi_pi_id[] = {1, PI}; + word multi_CiPN_id[] = {1, OAD}; + word multi_ssext_id[] = {1, ESC_SSEXT}; + + word multi_vswitch_id[] = {1, ESC_VSWITCH}; + + byte * cau; + word ncci; + byte SS_Ind[] = "\x05\x02\x00\x02\x00\x00"; /* Hold_Ind struct*/ + byte CF_Ind[] = "\x09\x02\x00\x06\x00\x00\x00\x00\x00\x00"; + byte Interr_Err_Ind[] = "\x0a\x02\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; + byte CONF_Ind[] = "\x09\x16\x00\x06\x00\x00\0x00\0x00\0x00\0x00"; + byte force_mt_info = FALSE; + byte dir; + dword d; + word w; + + a = plci->adapter; + Id = ((word)plci->Id<<8)|a->Id; + PUT_WORD(&SS_Ind[4],0x0000); + + if (plci->sig_remove_id) + { + plci->Sig.RNR = 2; /* discard */ + dbug(1,dprintf("SIG discard while remove pending")); + return; + } + if(plci->tel && plci->SuppState!=CALL_HELD) Id|=EXT_CONTROLLER; + dbug(1,dprintf("SigInd-Id=%08lx,plci=%x,tel=%x,state=0x%x,channels=%d,Discflowcl=%d", + Id,plci->Id,plci->tel,plci->State,plci->channels,plci->hangup_flow_ctrl_timer)); + if(plci->Sig.Ind==CALL_HOLD_ACK && plci->channels) + { + plci->Sig.RNR = 1; + return; + } + if(plci->Sig.Ind==HANGUP && plci->channels) + { + plci->Sig.RNR = 1; + plci->hangup_flow_ctrl_timer++; + /* recover the network layer after timeout */ + if(plci->hangup_flow_ctrl_timer==100) + { + dbug(1,dprintf("Exceptional disc")); + plci->Sig.RNR = 0; + plci->hangup_flow_ctrl_timer = 0; + for (ncci = 1; ncci < MAX_NCCI+1; ncci++) + { + if (a->ncci_plci[ncci] == plci->Id) + { + cleanup_ncci_data (plci, ncci); + if(plci->channels)plci->channels--; + if (plci->appl) + sendf(plci->appl,_DISCONNECT_B3_I, (((dword) ncci) << 16) | Id,0,"ws",0,""); + } + } + if (plci->appl) + sendf(plci->appl, _DISCONNECT_I, Id, 0, "w", 0); + plci_remove(plci); + plci->State=IDLE; + } + return; + } + + /* do first parse the info with no OAD in, because OAD will be converted */ + /* first the multiple facility IE, then mult. progress ind. */ + /* then the parameters for the info_ind + conn_ind */ + IndParse(plci,multi_fac_id,multi_fac_parms,MAX_MULTI_IE); + IndParse(plci,multi_pi_id,multi_pi_parms,MAX_MULTI_IE); + IndParse(plci,multi_ssext_id,multi_ssext_parms,MAX_MULTI_IE); + + IndParse(plci,multi_vswitch_id,multi_vswitch_parms,MAX_MULTI_IE); + + IndParse(plci,parms_id,parms,0); + IndParse(plci,multi_CiPN_id,multi_CiPN_parms,MAX_MULTI_IE); + esc_chi = parms[14]; + esc_law = parms[18]; + pty_cai = parms[24]; + esc_cr = parms[25]; + esc_profile = parms[27]; + if(esc_cr[0] && plci) + { + if(plci->cr_enquiry && plci->appl) + { + plci->cr_enquiry = FALSE; + /* d = MANU_ID */ + /* w = m_command */ + /* b = total length */ + /* b = indication type */ + /* b = length of all IEs */ + /* b = IE1 */ + /* S = IE1 length + cont. */ + /* b = IE2 */ + /* S = IE2 lenght + cont. */ + sendf(plci->appl, + _MANUFACTURER_I, + Id, + 0, + "dwbbbbSbS",_DI_MANU_ID,plci->m_command, + 2+1+1+esc_cr[0]+1+1+esc_law[0],plci->Sig.Ind,1+1+esc_cr[0]+1+1+esc_law[0],ESC,esc_cr,ESC,esc_law); + } + } + /* create the additional info structure */ + add_i[1] = parms[15]; /* KEY of additional info */ + add_i[2] = parms[11]; /* UUI of additional info */ + ai_len = AddInfo(add_i,multi_fac_parms, esc_chi, facility); + + /* the ESC_LAW indicates if u-Law or a-Law is actually used by the card */ + /* indication returns by the card if requested by the function */ + /* AutomaticLaw() after driver init */ + if (a->automatic_law<4) + { + if(esc_law[0]){ + if(esc_law[2]){ + dbug(0,dprintf("u-Law selected")); + a->u_law = 1; + } + else { + dbug(0,dprintf("a-Law selected")); + a->u_law = 0; + } + a->automatic_law = 4; + if(plci==a->automatic_lawPLCI) { + plci->internal_command = 0; + sig_req(plci,REMOVE,0); + send_req(plci); + a->automatic_lawPLCI = NULL; + } + } + if (esc_profile[0]) + { + dbug (1, dprintf ("[%06x] CardProfile: %lx %lx %lx %lx %lx", + UnMapController (a->Id), GET_DWORD (&esc_profile[6]), + GET_DWORD (&esc_profile[10]), GET_DWORD (&esc_profile[14]), + GET_DWORD (&esc_profile[18]), GET_DWORD (&esc_profile[46]))); + + a->profile.Global_Options &= 0x000000ffL; + a->profile.B1_Protocols &= 0x000003ffL; + a->profile.B2_Protocols &= 0x00001fdfL; + a->profile.B3_Protocols &= 0x000000b7L; + + a->profile.Global_Options &= GET_DWORD (&esc_profile[6]) | + GL_BCHANNEL_OPERATION_SUPPORTED; + a->profile.B1_Protocols &= GET_DWORD (&esc_profile[10]); + a->profile.B2_Protocols &= GET_DWORD (&esc_profile[14]); + a->profile.B3_Protocols &= GET_DWORD (&esc_profile[18]); + a->manufacturer_features = GET_DWORD (&esc_profile[46]); + a->man_profile.private_options = 0; + + if (a->manufacturer_features & MANUFACTURER_FEATURE_ECHO_CANCELLER) + { + a->man_profile.private_options |= 1L << PRIVATE_ECHO_CANCELLER; + a->profile.Global_Options |= GL_ECHO_CANCELLER_SUPPORTED; + } + + + if (a->manufacturer_features & MANUFACTURER_FEATURE_RTP) + a->man_profile.private_options |= 1L << PRIVATE_RTP; + a->man_profile.rtp_primary_payloads = GET_DWORD (&esc_profile[50]); + a->man_profile.rtp_additional_payloads = GET_DWORD (&esc_profile[54]); + + + if (a->manufacturer_features & MANUFACTURER_FEATURE_T38) + a->man_profile.private_options |= 1L << PRIVATE_T38; + + + if (a->manufacturer_features & MANUFACTURER_FEATURE_FAX_SUB_SEP_PWD) + a->man_profile.private_options |= 1L << PRIVATE_FAX_SUB_SEP_PWD; + + + if (a->manufacturer_features & MANUFACTURER_FEATURE_V18) + a->man_profile.private_options |= 1L << PRIVATE_V18; + + + if (a->manufacturer_features & MANUFACTURER_FEATURE_DTMF_TONE) + a->man_profile.private_options |= 1L << PRIVATE_DTMF_TONE; + + + if (a->manufacturer_features & MANUFACTURER_FEATURE_PIAFS) + a->man_profile.private_options |= 1L << PRIVATE_PIAFS; + + + if (a->manufacturer_features & MANUFACTURER_FEATURE_FAX_PAPER_FORMATS) + a->man_profile.private_options |= 1L << PRIVATE_FAX_PAPER_FORMATS; + + + if (a->manufacturer_features & MANUFACTURER_FEATURE_VOWN) + a->man_profile.private_options |= 1L << PRIVATE_VOWN; + + + if (a->manufacturer_features & MANUFACTURER_FEATURE_FAX_NONSTANDARD) + a->man_profile.private_options |= 1L << PRIVATE_FAX_NONSTANDARD; + + } + else + { + a->profile.Global_Options &= 0x0000007fL; + a->profile.B1_Protocols &= 0x000003dfL; + a->profile.B2_Protocols &= 0x00001adfL; + a->profile.B3_Protocols &= 0x000000b7L; + a->manufacturer_features &= MANUFACTURER_FEATURE_HARDDTMF; + } + if (a->manufacturer_features & (MANUFACTURER_FEATURE_HARDDTMF | + MANUFACTURER_FEATURE_SOFTDTMF_SEND | MANUFACTURER_FEATURE_SOFTDTMF_RECEIVE)) + { + a->profile.Global_Options |= GL_DTMF_SUPPORTED; + } + a->manufacturer_features &= ~MANUFACTURER_FEATURE_OOB_CHANNEL; + dbug (1, dprintf ("[%06x] Profile: %lx %lx %lx %lx %lx", + UnMapController (a->Id), a->profile.Global_Options, + a->profile.B1_Protocols, a->profile.B2_Protocols, + a->profile.B3_Protocols, a->manufacturer_features)); + } + /* codec plci for the handset/hook state support is just an internal id */ + if(plci!=a->AdvCodecPLCI) + { + force_mt_info = SendMultiIE(plci,Id,multi_fac_parms, FTY, 0x20, 0); + force_mt_info |= SendMultiIE(plci,Id,multi_pi_parms, PI, 0x210, 0); + SendSSExtInd(NULL,plci,Id,multi_ssext_parms); + SendInfo(plci,Id, parms, force_mt_info); + + VSwitchReqInd(plci,Id,multi_vswitch_parms); + + } + + /* switch the codec to the b-channel */ + if(esc_chi[0] && plci && !plci->SuppState){ + plci->b_channel = esc_chi[esc_chi[0]]&0x1f; + mixer_set_bchannel_id_esc (plci, plci->b_channel); + dbug(1,dprintf("storeChannel=0x%x",plci->b_channel)); + if(plci->tel==ADV_VOICE && plci->appl) { + SetVoiceChannel(a->AdvCodecPLCI, esc_chi, a); + } + } + + if(plci->appl) Number = plci->appl->Number++; + + switch(plci->Sig.Ind) { + /* Response to Get_Supported_Services request */ + case S_SUPPORTED: + dbug(1,dprintf("S_Supported")); + if(!plci->appl) break; + if(pty_cai[0]==4) + { + PUT_DWORD(&CF_Ind[6],GET_DWORD(&pty_cai[1]) ); + } + else + { + PUT_DWORD(&CF_Ind[6],MASK_TERMINAL_PORTABILITY | MASK_HOLD_RETRIEVE); + } + PUT_WORD (&CF_Ind[1], 0); + PUT_WORD (&CF_Ind[4], 0); + sendf(plci->appl,_FACILITY_R|CONFIRM,Id&0x7,plci->number, "wws",0,3,CF_Ind); + plci_remove(plci); + break; + + /* Supplementary Service rejected */ + case S_SERVICE_REJ: + dbug(1,dprintf("S_Reject=0x%x",pty_cai[5])); + if(!pty_cai[0]) break; + switch (pty_cai[5]) + { + case ECT_EXECUTE: + case THREE_PTY_END: + case THREE_PTY_BEGIN: + if(!plci->relatedPTYPLCI) break; + tplci = plci->relatedPTYPLCI; + rId = ( (word)tplci->Id<<8)|tplci->adapter->Id; + if(tplci->tel) rId|=EXT_CONTROLLER; + if(pty_cai[5]==ECT_EXECUTE) + { + PUT_WORD(&SS_Ind[1],S_ECT); + + plci->vswitchstate=0; + plci->relatedPTYPLCI->vswitchstate=0; + + } + else + { + PUT_WORD(&SS_Ind[1],pty_cai[5]+3); + } + if(pty_cai[2]!=0xff) + { + PUT_WORD(&SS_Ind[4],0x3600|(word)pty_cai[2]); + } + else + { + PUT_WORD(&SS_Ind[4],0x300E); + } + plci->relatedPTYPLCI = NULL; + plci->ptyState = 0; + sendf(tplci->appl,_FACILITY_I,rId,0,"ws",3, SS_Ind); + break; + + case CALL_DEFLECTION: + if(pty_cai[2]!=0xff) + { + PUT_WORD(&SS_Ind[4],0x3600|(word)pty_cai[2]); + } + else + { + PUT_WORD(&SS_Ind[4],0x300E); + } + PUT_WORD(&SS_Ind[1],pty_cai[5]); + for(i=0; i<max_appl; i++) + { + if(application[i].CDEnable) + { + if(application[i].Id) sendf(&application[i],_FACILITY_I,Id,0,"ws",3, SS_Ind); + application[i].CDEnable = FALSE; + } + } + break; + + case DEACTIVATION_DIVERSION: + case ACTIVATION_DIVERSION: + case DIVERSION_INTERROGATE_CFU: + case DIVERSION_INTERROGATE_CFB: + case DIVERSION_INTERROGATE_CFNR: + case DIVERSION_INTERROGATE_NUM: + case CCBS_REQUEST: + case CCBS_DEACTIVATE: + case CCBS_INTERROGATE: + if(!plci->appl) break; + if(pty_cai[2]!=0xff) + { + PUT_WORD(&Interr_Err_Ind[4],0x3600|(word)pty_cai[2]); + } + else + { + PUT_WORD(&Interr_Err_Ind[4],0x300E); + } + switch (pty_cai[5]) + { + case DEACTIVATION_DIVERSION: + dbug(1,dprintf("Deact_Div")); + Interr_Err_Ind[0]=0x9; + Interr_Err_Ind[3]=0x6; + PUT_WORD(&Interr_Err_Ind[1],S_CALL_FORWARDING_STOP); + break; + case ACTIVATION_DIVERSION: + dbug(1,dprintf("Act_Div")); + Interr_Err_Ind[0]=0x9; + Interr_Err_Ind[3]=0x6; + PUT_WORD(&Interr_Err_Ind[1],S_CALL_FORWARDING_START); + break; + case DIVERSION_INTERROGATE_CFU: + case DIVERSION_INTERROGATE_CFB: + case DIVERSION_INTERROGATE_CFNR: + dbug(1,dprintf("Interr_Div")); + Interr_Err_Ind[0]=0xa; + Interr_Err_Ind[3]=0x7; + PUT_WORD(&Interr_Err_Ind[1],S_INTERROGATE_DIVERSION); + break; + case DIVERSION_INTERROGATE_NUM: + dbug(1,dprintf("Interr_Num")); + Interr_Err_Ind[0]=0xa; + Interr_Err_Ind[3]=0x7; + PUT_WORD(&Interr_Err_Ind[1],S_INTERROGATE_NUMBERS); + break; + case CCBS_REQUEST: + dbug(1,dprintf("CCBS Request")); + Interr_Err_Ind[0]=0xd; + Interr_Err_Ind[3]=0xa; + PUT_WORD(&Interr_Err_Ind[1],S_CCBS_REQUEST); + break; + case CCBS_DEACTIVATE: + dbug(1,dprintf("CCBS Deactivate")); + Interr_Err_Ind[0]=0x9; + Interr_Err_Ind[3]=0x6; + PUT_WORD(&Interr_Err_Ind[1],S_CCBS_DEACTIVATE); + break; + case CCBS_INTERROGATE: + dbug(1,dprintf("CCBS Interrogate")); + Interr_Err_Ind[0]=0xb; + Interr_Err_Ind[3]=0x8; + PUT_WORD(&Interr_Err_Ind[1],S_CCBS_INTERROGATE); + break; + } + PUT_DWORD(&Interr_Err_Ind[6],plci->appl->S_Handle); + sendf(plci->appl,_FACILITY_I,Id&0x7,0,"ws",3, Interr_Err_Ind); + plci_remove(plci); + break; + case ACTIVATION_MWI: + case DEACTIVATION_MWI: + if(pty_cai[5]==ACTIVATION_MWI) + { + PUT_WORD(&SS_Ind[1],S_MWI_ACTIVATE); + } + else PUT_WORD(&SS_Ind[1],S_MWI_DEACTIVATE); + + if(pty_cai[2]!=0xff) + { + PUT_WORD(&SS_Ind[4],0x3600|(word)pty_cai[2]); + } + else + { + PUT_WORD(&SS_Ind[4],0x300E); + } + + if(plci->cr_enquiry) + { + sendf(plci->appl,_FACILITY_I,Id&0xf,0,"ws",3, SS_Ind); + plci_remove(plci); + } + else + { + sendf(plci->appl,_FACILITY_I,Id,0,"ws",3, SS_Ind); + } + break; + case CONF_ADD: /* ERROR */ + case CONF_BEGIN: + case CONF_DROP: + case CONF_ISOLATE: + case CONF_REATTACH: + CONF_Ind[0]=9; + CONF_Ind[3]=6; + switch(pty_cai[5]) + { + case CONF_BEGIN: + PUT_WORD(&CONF_Ind[1],S_CONF_BEGIN); + plci->ptyState = 0; + break; + case CONF_DROP: + CONF_Ind[0]=5; + CONF_Ind[3]=2; + PUT_WORD(&CONF_Ind[1],S_CONF_DROP); + plci->ptyState = CONNECTED; + break; + case CONF_ISOLATE: + CONF_Ind[0]=5; + CONF_Ind[3]=2; + PUT_WORD(&CONF_Ind[1],S_CONF_ISOLATE); + plci->ptyState = CONNECTED; + break; + case CONF_REATTACH: + CONF_Ind[0]=5; + CONF_Ind[3]=2; + PUT_WORD(&CONF_Ind[1],S_CONF_REATTACH); + plci->ptyState = CONNECTED; + break; + case CONF_ADD: + PUT_WORD(&CONF_Ind[1],S_CONF_ADD); + plci->relatedPTYPLCI = NULL; + tplci=plci->relatedPTYPLCI; + if(tplci) tplci->ptyState = CONNECTED; + plci->ptyState = CONNECTED; + break; + } + + if(pty_cai[2]!=0xff) + { + PUT_WORD(&CONF_Ind[4],0x3600|(word)pty_cai[2]); + } + else + { + PUT_WORD(&CONF_Ind[4],0x3303); /* Time-out: network did not respond + within the required time */ + } + + PUT_DWORD(&CONF_Ind[6],0x0); + sendf(plci->appl,_FACILITY_I,Id,0,"ws",3, CONF_Ind); + break; + } + break; + + /* Supplementary Service indicates success */ + case S_SERVICE: + dbug(1,dprintf("Service_Ind")); + PUT_WORD (&CF_Ind[4], 0); + switch (pty_cai[5]) + { + case THREE_PTY_END: + case THREE_PTY_BEGIN: + case ECT_EXECUTE: + if(!plci->relatedPTYPLCI) break; + tplci = plci->relatedPTYPLCI; + rId = ( (word)tplci->Id<<8)|tplci->adapter->Id; + if(tplci->tel) rId|=EXT_CONTROLLER; + if(pty_cai[5]==ECT_EXECUTE) + { + PUT_WORD(&SS_Ind[1],S_ECT); + + if(plci->vswitchstate!=3) + { + + plci->ptyState = IDLE; + plci->relatedPTYPLCI = NULL; + plci->ptyState = 0; + + } + + dbug(1,dprintf("ECT OK")); + sendf(tplci->appl,_FACILITY_I,rId,0,"ws",3, SS_Ind); + + + + } + else + { + switch (plci->ptyState) + { + case S_3PTY_BEGIN: + plci->ptyState = CONNECTED; + dbug(1,dprintf("3PTY ON")); + break; + + case S_3PTY_END: + plci->ptyState = IDLE; + plci->relatedPTYPLCI = NULL; + plci->ptyState = 0; + dbug(1,dprintf("3PTY OFF")); + break; + } + PUT_WORD(&SS_Ind[1],pty_cai[5]+3); + sendf(tplci->appl,_FACILITY_I,rId,0,"ws",3, SS_Ind); + } + break; + + case CALL_DEFLECTION: + PUT_WORD(&SS_Ind[1],pty_cai[5]); + for(i=0; i<max_appl; i++) + { + if(application[i].CDEnable) + { + if(application[i].Id) sendf(&application[i],_FACILITY_I,Id,0,"ws",3, SS_Ind); + application[i].CDEnable = FALSE; + } + } + break; + + case DEACTIVATION_DIVERSION: + case ACTIVATION_DIVERSION: + if(!plci->appl) break; + PUT_WORD(&CF_Ind[1],pty_cai[5]+2); + PUT_DWORD(&CF_Ind[6],plci->appl->S_Handle); + sendf(plci->appl,_FACILITY_I,Id&0x7,0,"ws",3, CF_Ind); + plci_remove(plci); + break; + + case DIVERSION_INTERROGATE_CFU: + case DIVERSION_INTERROGATE_CFB: + case DIVERSION_INTERROGATE_CFNR: + case DIVERSION_INTERROGATE_NUM: + case CCBS_REQUEST: + case CCBS_DEACTIVATE: + case CCBS_INTERROGATE: + if(!plci->appl) break; + switch (pty_cai[5]) + { + case DIVERSION_INTERROGATE_CFU: + case DIVERSION_INTERROGATE_CFB: + case DIVERSION_INTERROGATE_CFNR: + dbug(1,dprintf("Interr_Div")); + PUT_WORD(&pty_cai[1],S_INTERROGATE_DIVERSION); + pty_cai[3]=pty_cai[0]-3; /* Supplementary Service-specific parameter len */ + break; + case DIVERSION_INTERROGATE_NUM: + dbug(1,dprintf("Interr_Num")); + PUT_WORD(&pty_cai[1],S_INTERROGATE_NUMBERS); + pty_cai[3]=pty_cai[0]-3; /* Supplementary Service-specific parameter len */ + break; + case CCBS_REQUEST: + dbug(1,dprintf("CCBS Request")); + PUT_WORD(&pty_cai[1],S_CCBS_REQUEST); + pty_cai[3]=pty_cai[0]-3; /* Supplementary Service-specific parameter len */ + break; + case CCBS_DEACTIVATE: + dbug(1,dprintf("CCBS Deactivate")); + PUT_WORD(&pty_cai[1],S_CCBS_DEACTIVATE); + pty_cai[3]=pty_cai[0]-3; /* Supplementary Service-specific parameter len */ + break; + case CCBS_INTERROGATE: + dbug(1,dprintf("CCBS Interrogate")); + PUT_WORD(&pty_cai[1],S_CCBS_INTERROGATE); + pty_cai[3]=pty_cai[0]-3; /* Supplementary Service-specific parameter len */ + break; + } + PUT_WORD(&pty_cai[4],0); /* Supplementary Service Reason */ + PUT_DWORD(&pty_cai[6],plci->appl->S_Handle); + sendf(plci->appl,_FACILITY_I,Id&0x7,0,"wS",3, pty_cai); + plci_remove(plci); + break; + + case ACTIVATION_MWI: + case DEACTIVATION_MWI: + if(pty_cai[5]==ACTIVATION_MWI) + { + PUT_WORD(&SS_Ind[1],S_MWI_ACTIVATE); + } + else PUT_WORD(&SS_Ind[1],S_MWI_DEACTIVATE); + if(plci->cr_enquiry) + { + sendf(plci->appl,_FACILITY_I,Id&0xf,0,"ws",3, SS_Ind); + plci_remove(plci); + } + else + { + sendf(plci->appl,_FACILITY_I,Id,0,"ws",3, SS_Ind); + } + break; + case MWI_INDICATION: + if(pty_cai[0]>=0x12) + { + PUT_WORD(&pty_cai[3],S_MWI_INDICATE); + pty_cai[2]=pty_cai[0]-2; /* len Parameter */ + pty_cai[5]=pty_cai[0]-5; /* Supplementary Service-specific parameter len */ + if(plci->appl && (a->Notification_Mask[plci->appl->Id-1]&SMASK_MWI)) + { + if(plci->internal_command==GET_MWI_STATE) /* result on Message Waiting Listen */ + { + sendf(plci->appl,_FACILITY_I,Id&0xf,0,"wS",3, &pty_cai[2]); + plci_remove(plci); + return; + } + else sendf(plci->appl,_FACILITY_I,Id,0,"wS",3, &pty_cai[2]); + pty_cai[0]=0; + } + else + { + for(i=0; i<max_appl; i++) + { + if(a->Notification_Mask[i]&SMASK_MWI) + { + sendf(&application[i],_FACILITY_I,Id&0x7,0,"wS",3, &pty_cai[2]); + pty_cai[0]=0; + } + } + } + + if(!pty_cai[0]) + { /* acknowledge */ + facility[2]= 0; /* returncode */ + } + else facility[2]= 0xff; + } + else + { + /* reject */ + facility[2]= 0xff; /* returncode */ + } + facility[0]= 2; + facility[1]= MWI_RESPONSE; /* Function */ + add_p(plci,CAI,facility); + add_p(plci,ESC,multi_ssext_parms[0]); /* remembered parameter -> only one possible */ + sig_req(plci,S_SERVICE,0); + send_req(plci); + plci->command = 0; + next_internal_command (Id, plci); + break; + case CONF_ADD: /* OK */ + case CONF_BEGIN: + case CONF_DROP: + case CONF_ISOLATE: + case CONF_REATTACH: + case CONF_PARTYDISC: + CONF_Ind[0]=9; + CONF_Ind[3]=6; + switch(pty_cai[5]) + { + case CONF_BEGIN: + PUT_WORD(&CONF_Ind[1],S_CONF_BEGIN); + if(pty_cai[0]==6) + { + d=pty_cai[6]; + PUT_DWORD(&CONF_Ind[6],d); /* PartyID */ + } + else + { + PUT_DWORD(&CONF_Ind[6],0x0); + } + break; + case CONF_ISOLATE: + PUT_WORD(&CONF_Ind[1],S_CONF_ISOLATE); + CONF_Ind[0]=5; + CONF_Ind[3]=2; + break; + case CONF_REATTACH: + PUT_WORD(&CONF_Ind[1],S_CONF_REATTACH); + CONF_Ind[0]=5; + CONF_Ind[3]=2; + break; + case CONF_DROP: + PUT_WORD(&CONF_Ind[1],S_CONF_DROP); + CONF_Ind[0]=5; + CONF_Ind[3]=2; + break; + case CONF_ADD: + PUT_WORD(&CONF_Ind[1],S_CONF_ADD); + d=pty_cai[6]; + PUT_DWORD(&CONF_Ind[6],d); /* PartyID */ + tplci=plci->relatedPTYPLCI; + if(tplci) tplci->ptyState = CONNECTED; + break; + case CONF_PARTYDISC: + CONF_Ind[0]=7; + CONF_Ind[3]=4; + PUT_WORD(&CONF_Ind[1],S_CONF_PARTYDISC); + d=pty_cai[6]; + PUT_DWORD(&CONF_Ind[4],d); /* PartyID */ + break; + } + plci->ptyState = CONNECTED; + sendf(plci->appl,_FACILITY_I,Id,0,"ws",3, CONF_Ind); + break; + case CCBS_INFO_RETAIN: + case CCBS_ERASECALLLINKAGEID: + case CCBS_STOP_ALERTING: + CONF_Ind[0]=5; + CONF_Ind[3]=2; + switch(pty_cai[5]) + { + case CCBS_INFO_RETAIN: + PUT_WORD(&CONF_Ind[1],S_CCBS_INFO_RETAIN); + break; + case CCBS_STOP_ALERTING: + PUT_WORD(&CONF_Ind[1],S_CCBS_STOP_ALERTING); + break; + case CCBS_ERASECALLLINKAGEID: + PUT_WORD(&CONF_Ind[1],S_CCBS_ERASECALLLINKAGEID); + CONF_Ind[0]=7; + CONF_Ind[3]=4; + CONF_Ind[6]=0; + CONF_Ind[7]=0; + break; + } + w=pty_cai[6]; + PUT_WORD(&CONF_Ind[4],w); /* PartyID */ + + if(plci->appl && (a->Notification_Mask[plci->appl->Id-1]&SMASK_CCBS)) + { + sendf(plci->appl,_FACILITY_I,Id,0,"ws",3, CONF_Ind); + } + else + { + for(i=0; i<max_appl; i++) + if(a->Notification_Mask[i]&SMASK_CCBS) + sendf(&application[i],_FACILITY_I,Id&0x7,0,"ws",3, CONF_Ind); + } + break; + } + break; + case CALL_HOLD_REJ: + cau = parms[7]; + if(cau) + { + i = _L3_CAUSE | cau[2]; + if(cau[2]==0) i = 0x3603; + } + else + { + i = 0x3603; + } + PUT_WORD(&SS_Ind[1],S_HOLD); + PUT_WORD(&SS_Ind[4],i); + if(plci->SuppState == HOLD_REQUEST) + { + plci->SuppState = IDLE; + sendf(plci->appl,_FACILITY_I,Id,0,"ws",3, SS_Ind); + } + break; + + case CALL_HOLD_ACK: + if(plci->SuppState == HOLD_REQUEST) + { + plci->SuppState = CALL_HELD; + CodecIdCheck(a, plci); + start_internal_command (Id, plci, hold_save_command); + } + break; + + case CALL_RETRIEVE_REJ: + cau = parms[7]; + if(cau) + { + i = _L3_CAUSE | cau[2]; + if(cau[2]==0) i = 0x3603; + } + else + { + i = 0x3603; + } + PUT_WORD(&SS_Ind[1],S_RETRIEVE); + PUT_WORD(&SS_Ind[4],i); + if(plci->SuppState == RETRIEVE_REQUEST) + { + plci->SuppState = CALL_HELD; + CodecIdCheck(a, plci); + sendf(plci->appl,_FACILITY_I,Id,0,"ws",3, SS_Ind); + } + break; + + case CALL_RETRIEVE_ACK: + PUT_WORD(&SS_Ind[1],S_RETRIEVE); + if(plci->SuppState == RETRIEVE_REQUEST) + { + plci->SuppState = IDLE; + plci->call_dir |= CALL_DIR_FORCE_OUTG_NL; + plci->b_channel = esc_chi[esc_chi[0]]&0x1f; + if(plci->tel) + { + mixer_set_bchannel_id_esc (plci, plci->b_channel); + dbug(1,dprintf("RetrChannel=0x%x",plci->b_channel)); + SetVoiceChannel(a->AdvCodecPLCI, esc_chi, a); + if(plci->B2_prot==B2_TRANSPARENT && plci->B3_prot==B3_TRANSPARENT) + { + dbug(1,dprintf("Get B-ch")); + start_internal_command (Id, plci, retrieve_restore_command); + } + else + sendf(plci->appl,_FACILITY_I,Id,0,"ws",3, SS_Ind); + } + else + start_internal_command (Id, plci, retrieve_restore_command); + } + break; + + case INDICATE_IND: + if(plci->State != LISTENING) { + sig_req(plci,HANGUP,0); + send_req(plci); + break; + } + cip = find_cip(a,parms[4],parms[6]); + cip_mask = 1L<<cip; + dbug(1,dprintf("cip=%d,cip_mask=%lx",cip,cip_mask)); + clear_c_ind_mask (plci); + if (!remove_started && !a->adapter_disabled) + { + set_c_ind_mask_bit (plci, MAX_APPL); + group_optimization(a, plci); + for(i=0; i<max_appl; i++) { + if(application[i].Id + && (a->CIP_Mask[i]&1 || a->CIP_Mask[i]&cip_mask) + && CPN_filter_ok(parms[0],a,i) + && test_group_ind_mask_bit (plci, i) ) { + dbug(1,dprintf("storedcip_mask[%d]=0x%lx",i,a->CIP_Mask[i] )); + set_c_ind_mask_bit (plci, i); + dump_c_ind_mask (plci); + plci->State = INC_CON_PENDING; + plci->call_dir = (plci->call_dir & ~(CALL_DIR_OUT | CALL_DIR_ORIGINATE)) | + CALL_DIR_IN | CALL_DIR_ANSWER; + if(esc_chi[0]) { + plci->b_channel = esc_chi[esc_chi[0]]&0x1f; + mixer_set_bchannel_id_esc (plci, plci->b_channel); + } + /* if a listen on the ext controller is done, check if hook states */ + /* are supported or if just a on board codec must be activated */ + if(a->codec_listen[i] && !a->AdvSignalPLCI) { + if(a->profile.Global_Options & HANDSET) + plci->tel = ADV_VOICE; + else if(a->profile.Global_Options & ON_BOARD_CODEC) + plci->tel = CODEC; + if(plci->tel) Id|=EXT_CONTROLLER; + a->codec_listen[i] = plci; + } + + sendf(&application[i],_CONNECT_I,Id,0, + "wSSSSSSSbSSSSS", cip, /* CIP */ + parms[0], /* CalledPartyNumber */ + multi_CiPN_parms[0], /* CallingPartyNumber */ + parms[2], /* CalledPartySubad */ + parms[3], /* CallingPartySubad */ + parms[4], /* BearerCapability */ + parms[5], /* LowLC */ + parms[6], /* HighLC */ + ai_len, /* nested struct add_i */ + add_i[0], /* B channel info */ + add_i[1], /* keypad facility */ + add_i[2], /* user user data */ + add_i[3], /* nested facility */ + multi_CiPN_parms[1] /* second CiPN(SCR) */ + ); + SendSSExtInd(&application[i], + plci, + Id, + multi_ssext_parms); + SendSetupInfo(&application[i], + plci, + Id, + parms, + SendMultiIE(plci,Id,multi_pi_parms, PI, 0x210, TRUE)); + } + } + clear_c_ind_mask_bit (plci, MAX_APPL); + dump_c_ind_mask (plci); + } + if(c_ind_mask_empty (plci)) { + sig_req(plci,HANGUP,0); + send_req(plci); + plci->State = IDLE; + } + plci->notifiedcall = 0; + a->listen_active--; + listen_check(a); + break; + + case CALL_PEND_NOTIFY: + plci->notifiedcall = 1; + listen_check(a); + break; + + case CALL_IND: + case CALL_CON: + if(plci->State==ADVANCED_VOICE_SIG || plci->State==ADVANCED_VOICE_NOSIG) + { + if(plci->internal_command==PERM_COD_CONN_PEND) + { + if(plci->State==ADVANCED_VOICE_NOSIG) + { + dbug(1,dprintf("***Codec OK")); + if(a->AdvSignalPLCI) + { + tplci = a->AdvSignalPLCI; + if(tplci->spoofed_msg) + { + dbug(1,dprintf("***Spoofed Msg(0x%x)",tplci->spoofed_msg)); + tplci->command = 0; + tplci->internal_command = 0; + x_Id = ((word)tplci->Id<<8)|tplci->adapter->Id | 0x80; + switch (tplci->spoofed_msg) + { + case CALL_RES: + tplci->command = _CONNECT_I|RESPONSE; + api_load_msg (&tplci->saved_msg, saved_parms); + add_b1(tplci,&saved_parms[1],0,tplci->B1_facilities); + if (tplci->adapter->Info_Mask[tplci->appl->Id-1] & 0x200) + { + /* early B3 connect (CIP mask bit 9) no release after a disc */ + add_p(tplci,LLI,"\x01\x01"); + } + add_s(tplci, CONN_NR, &saved_parms[2]); + add_s(tplci, LLC, &saved_parms[4]); + add_ai(tplci, &saved_parms[5]); + tplci->State = INC_CON_ACCEPT; + sig_req(tplci, CALL_RES,0); + send_req(tplci); + break; + + case AWAITING_SELECT_B: + dbug(1,dprintf("Select_B continue")); + start_internal_command (x_Id, tplci, select_b_command); + break; + + case AWAITING_MANUF_CON: /* Get_Plci per Manufacturer_Req to ext controller */ + if(!tplci->Sig.Id) + { + dbug(1,dprintf("No SigID!")); + sendf(tplci->appl, _MANUFACTURER_R|CONFIRM,x_Id,tplci->number, "dww",_DI_MANU_ID,_MANUFACTURER_R,_OUT_OF_PLCI); + plci_remove(tplci); + break; + } + tplci->command = _MANUFACTURER_R; + api_load_msg (&tplci->saved_msg, saved_parms); + dir = saved_parms[2].info[0]; + if(dir==1) { + sig_req(tplci,CALL_REQ,0); + } + else if(!dir){ + sig_req(tplci,LISTEN_REQ,0); + } + send_req(tplci); + sendf(tplci->appl, _MANUFACTURER_R|CONFIRM,x_Id,tplci->number, "dww",_DI_MANU_ID,_MANUFACTURER_R,0); + break; + + case (CALL_REQ|AWAITING_MANUF_CON): + sig_req(tplci,CALL_REQ,0); + send_req(tplci); + break; + + case CALL_REQ: + if(!tplci->Sig.Id) + { + dbug(1,dprintf("No SigID!")); + sendf(tplci->appl,_CONNECT_R|CONFIRM,tplci->adapter->Id,0,"w",_OUT_OF_PLCI); + plci_remove(tplci); + break; + } + tplci->command = _CONNECT_R; + api_load_msg (&tplci->saved_msg, saved_parms); + add_s(tplci,CPN,&saved_parms[1]); + add_s(tplci,DSA,&saved_parms[3]); + add_ai(tplci,&saved_parms[9]); + sig_req(tplci,CALL_REQ,0); + send_req(tplci); + break; + + case CALL_RETRIEVE: + tplci->command = C_RETRIEVE_REQ; + sig_req(tplci,CALL_RETRIEVE,0); + send_req(tplci); + break; + } + tplci->spoofed_msg = 0; + if (tplci->internal_command == 0) + next_internal_command (x_Id, tplci); + } + } + next_internal_command (Id, plci); + break; + } + dbug(1,dprintf("***Codec Hook Init Req")); + plci->internal_command = PERM_COD_HOOK; + add_p(plci,FTY,"\x01\x09"); /* Get Hook State*/ + sig_req(plci,TEL_CTRL,0); + send_req(plci); + } + } + else if(plci->command != _MANUFACTURER_R /* old style permanent connect */ + && plci->State!=INC_ACT_PENDING) + { + mixer_set_bchannel_id_esc (plci, plci->b_channel); + if(plci->tel == ADV_VOICE && plci->SuppState == IDLE) /* with permanent codec switch on immediately */ + { + chi[2] = plci->b_channel; + SetVoiceChannel(a->AdvCodecPLCI, chi, a); + } + sendf(plci->appl,_CONNECT_ACTIVE_I,Id,0,"Sss",parms[21],"",""); + plci->State = INC_ACT_PENDING; + } + break; + + case TEL_CTRL: + Number = 0; + ie = multi_fac_parms[0]; /* inspect the facility hook indications */ + if(plci->State==ADVANCED_VOICE_SIG && ie[0]){ + switch (ie[1]&0x91) { + case 0x80: /* hook off */ + case 0x81: + if(plci->internal_command==PERM_COD_HOOK) + { + dbug(1,dprintf("init:hook_off")); + plci->hook_state = ie[1]; + next_internal_command (Id, plci); + break; + } + else /* ignore doubled hook indications */ + { + if( ((plci->hook_state)&0xf0)==0x80) + { + dbug(1,dprintf("ignore hook")); + break; + } + plci->hook_state = ie[1]&0x91; + } + /* check for incoming call pending */ + /* and signal '+'.Appl must decide */ + /* with connect_res if call must */ + /* accepted or not */ + for(i=0, tplci=NULL;i<max_appl;i++){ + if(a->codec_listen[i] + && (a->codec_listen[i]->State==INC_CON_PENDING + ||a->codec_listen[i]->State==INC_CON_ALERT) ){ + tplci = a->codec_listen[i]; + tplci->appl = &application[i]; + } + } + /* no incoming call, do outgoing call */ + /* and signal '+' if outg. setup */ + if(!a->AdvSignalPLCI && !tplci){ + if((i=get_plci(a))) { + a->AdvSignalPLCI = &a->plci[i-1]; + tplci = a->AdvSignalPLCI; + tplci->tel = ADV_VOICE; + PUT_WORD(&voice_cai[5],a->AdvSignalAppl->MaxDataLength); + if (a->Info_Mask[a->AdvSignalAppl->Id-1] & 0x200){ + /* early B3 connect (CIP mask bit 9) no release after a disc */ + add_p(tplci,LLI,"\x01\x01"); + } + add_p(tplci, CAI, voice_cai); + add_p(tplci, OAD, a->TelOAD); + add_p(tplci, OSA, a->TelOSA); + add_p(tplci,SHIFT|6,NULL); + add_p(tplci,SIN,"\x02\x01\x00"); + add_p(tplci,UID,"\x06\x43\x61\x70\x69\x32\x30"); + sig_req(tplci,ASSIGN,DSIG_ID); + a->AdvSignalPLCI->internal_command = HOOK_OFF_REQ; + a->AdvSignalPLCI->command = 0; + tplci->appl = a->AdvSignalAppl; + tplci->call_dir = CALL_DIR_OUT | CALL_DIR_ORIGINATE; + send_req(tplci); + } + + } + + if(!tplci) break; + Id = ((word)tplci->Id<<8)|a->Id; + Id|=EXT_CONTROLLER; + sendf(tplci->appl, + _FACILITY_I, + Id, + 0, + "ws", (word)0, "\x01+"); + break; + + case 0x90: /* hook on */ + case 0x91: + if(plci->internal_command==PERM_COD_HOOK) + { + dbug(1,dprintf("init:hook_on")); + plci->hook_state = ie[1]&0x91; + next_internal_command (Id, plci); + break; + } + else /* ignore doubled hook indications */ + { + if( ((plci->hook_state)&0xf0)==0x90) break; + plci->hook_state = ie[1]&0x91; + } + /* hangup the adv. voice call and signal '-' to the appl */ + if(a->AdvSignalPLCI) { + Id = ((word)a->AdvSignalPLCI->Id<<8)|a->Id; + if(plci->tel) Id|=EXT_CONTROLLER; + sendf(a->AdvSignalAppl, + _FACILITY_I, + Id, + 0, + "ws", (word)0, "\x01-"); + a->AdvSignalPLCI->internal_command = HOOK_ON_REQ; + a->AdvSignalPLCI->command = 0; + sig_req(a->AdvSignalPLCI,HANGUP,0); + send_req(a->AdvSignalPLCI); + } + break; + } + } + break; + + case RESUME: + clear_c_ind_mask_bit (plci, (word)(plci->appl->Id-1)); + PUT_WORD(&resume_cau[4],GOOD); + sendf(plci->appl,_FACILITY_I,Id,0,"ws", (word)3, resume_cau); + break; + + case SUSPEND: + clear_c_ind_mask (plci); + + if (plci->NL.Id && !plci->nl_remove_id) { + mixer_remove (plci); + nl_req_ncci(plci,REMOVE,0); + } + if (!plci->sig_remove_id) { + plci->internal_command = 0; + sig_req(plci,REMOVE,0); + } + send_req(plci); + if(!plci->channels) { + sendf(plci->appl,_FACILITY_I,Id,0,"ws", (word)3, "\x05\x04\x00\x02\x00\x00"); + sendf(plci->appl, _DISCONNECT_I, Id, 0, "w", 0); + } + break; + + case SUSPEND_REJ: + break; + + case HANGUP: + plci->hangup_flow_ctrl_timer=0; + if(plci->manufacturer && plci->State==LOCAL_CONNECT) break; + cau = parms[7]; + if(cau) { + i = _L3_CAUSE | cau[2]; + if(cau[2]==0) i = 0; + else if(cau[2]==8) i = _L1_ERROR; + else if(cau[2]==9 || cau[2]==10) i = _L2_ERROR; + else if(cau[2]==5) i = _CAPI_GUARD_ERROR; + } + else { + i = _L3_ERROR; + } + + if(plci->State==INC_CON_PENDING || plci->State==INC_CON_ALERT) + { + for(i=0; i<max_appl; i++) + { + if(test_c_ind_mask_bit (plci, i)) + sendf(&application[i], _DISCONNECT_I, Id, 0, "w", 0); + } + } + else + { + clear_c_ind_mask (plci); + } + if(!plci->appl) + { + if (plci->State == LISTENING) + { + plci->notifiedcall=0; + a->listen_active--; + } + plci->State = INC_DIS_PENDING; + if(c_ind_mask_empty (plci)) + { + plci->State = IDLE; + if (plci->NL.Id && !plci->nl_remove_id) + { + mixer_remove (plci); + nl_req_ncci(plci,REMOVE,0); + } + if (!plci->sig_remove_id) + { + plci->internal_command = 0; + sig_req(plci,REMOVE,0); + } + send_req(plci); + } + } + else + { + /* collision of DISCONNECT or CONNECT_RES with HANGUP can */ + /* result in a second HANGUP! Don't generate another */ + /* DISCONNECT */ + if(plci->State!=IDLE && plci->State!=INC_DIS_PENDING) + { + if(plci->State==RESUMING) + { + PUT_WORD(&resume_cau[4],i); + sendf(plci->appl,_FACILITY_I,Id,0,"ws", (word)3, resume_cau); + } + plci->State = INC_DIS_PENDING; + sendf(plci->appl,_DISCONNECT_I,Id,0,"w",i); + } + } + break; + + case SSEXT_IND: + SendSSExtInd(NULL,plci,Id,multi_ssext_parms); + break; + + case VSWITCH_REQ: + VSwitchReqInd(plci,Id,multi_vswitch_parms); + break; + case VSWITCH_IND: + if(plci->relatedPTYPLCI && + plci->vswitchstate==3 && + plci->relatedPTYPLCI->vswitchstate==3 && + parms[MAXPARMSIDS-1][0]) + { + add_p(plci->relatedPTYPLCI,SMSG,parms[MAXPARMSIDS-1]); + sig_req(plci->relatedPTYPLCI,VSWITCH_REQ,0); + send_req(plci->relatedPTYPLCI); + } + else VSwitchReqInd(plci,Id,multi_vswitch_parms); + break; + + } +} + + +static void SendSetupInfo(APPL * appl, PLCI * plci, dword Id, byte * * parms, byte Info_Sent_Flag) +{ + word i; + byte * ie; + word Info_Number; + byte * Info_Element; + word Info_Mask = 0; + + dbug(1,dprintf("SetupInfo")); + + for(i=0; i<MAXPARMSIDS; i++) { + ie = parms[i]; + Info_Number = 0; + Info_Element = ie; + if(ie[0]) { + switch(i) { + case 0: + dbug(1,dprintf("CPN ")); + Info_Number = 0x0070; + Info_Mask = 0x80; + Info_Sent_Flag = TRUE; + break; + case 8: /* display */ + dbug(1,dprintf("display(%d)",i)); + Info_Number = 0x0028; + Info_Mask = 0x04; + Info_Sent_Flag = TRUE; + break; + case 16: /* Channel Id */ + dbug(1,dprintf("CHI")); + Info_Number = 0x0018; + Info_Mask = 0x100; + Info_Sent_Flag = TRUE; + mixer_set_bchannel_id (plci, Info_Element); + break; + case 19: /* Redirected Number */ + dbug(1,dprintf("RDN")); + Info_Number = 0x0074; + Info_Mask = 0x400; + Info_Sent_Flag = TRUE; + break; + case 20: /* Redirected Number extended */ + dbug(1,dprintf("RDX")); + Info_Number = 0x0073; + Info_Mask = 0x400; + Info_Sent_Flag = TRUE; + break; + case 22: /* Redirecing Number */ + dbug(1,dprintf("RIN")); + Info_Number = 0x0076; + Info_Mask = 0x400; + Info_Sent_Flag = TRUE; + break; + default: + Info_Number = 0; + break; + } + } + + if(i==MAXPARMSIDS-2){ /* to indicate the message type "Setup" */ + Info_Number = 0x8000 |5; + Info_Mask = 0x10; + Info_Element = ""; + } + + if(Info_Sent_Flag && Info_Number){ + if(plci->adapter->Info_Mask[appl->Id-1] & Info_Mask) { + sendf(appl,_INFO_I,Id,0,"wS",Info_Number,Info_Element); + } + } + } +} + + +void SendInfo(PLCI * plci, dword Id, byte * * parms, byte iesent) +{ + word i; + word j; + word k; + byte * ie; + word Info_Number; + byte * Info_Element; + word Info_Mask = 0; + static byte charges[5] = {4,0,0,0,0}; + static byte cause[] = {0x02,0x80,0x00}; + APPL *appl; + + dbug(1,dprintf("InfoParse ")); + + if( + !plci->appl + && !plci->State + && plci->Sig.Ind!=NCR_FACILITY + ) + { + dbug(1,dprintf("NoParse ")); + return; + } + cause[2] = 0; + for(i=0; i<MAXPARMSIDS; i++) { + ie = parms[i]; + Info_Number = 0; + Info_Element = ie; + if(ie[0]) { + switch(i) { + case 0: + dbug(1,dprintf("CPN ")); + Info_Number = 0x0070; + Info_Mask = 0x80; + break; + case 7: /* ESC_CAU */ + dbug(1,dprintf("cau(0x%x)",ie[2])); + Info_Number = 0x0008; + Info_Mask = 0x00; + cause[2] = ie[2]; + Info_Element = NULL; + break; + case 8: /* display */ + dbug(1,dprintf("display(%d)",i)); + Info_Number = 0x0028; + Info_Mask = 0x04; + break; + case 9: /* Date display */ + dbug(1,dprintf("date(%d)",i)); + Info_Number = 0x0029; + Info_Mask = 0x02; + break; + case 10: /* charges */ + for(j=0;j<4;j++) charges[1+j] = 0; + for(j=0; j<ie[0] && !(ie[1+j]&0x80); j++); + for(k=1,j++; j<ie[0] && k<=4; j++,k++) charges[k] = ie[1+j]; + Info_Number = 0x4000; + Info_Mask = 0x40; + Info_Element = charges; + break; + case 11: /* user user info */ + dbug(1,dprintf("uui")); + Info_Number = 0x007E; + Info_Mask = 0x08; + break; + case 12: /* congestion receiver ready */ + dbug(1,dprintf("clRDY")); + Info_Number = 0x00B0; + Info_Mask = 0x08; + Info_Element = ""; + break; + case 13: /* congestion receiver not ready */ + dbug(1,dprintf("clNRDY")); + Info_Number = 0x00BF; + Info_Mask = 0x08; + Info_Element = ""; + break; + case 15: /* Keypad Facility */ + dbug(1,dprintf("KEY")); + Info_Number = 0x002C; + Info_Mask = 0x20; + break; + case 16: /* Channel Id */ + dbug(1,dprintf("CHI")); + Info_Number = 0x0018; + Info_Mask = 0x100; + mixer_set_bchannel_id (plci, Info_Element); + break; + case 17: /* if no 1tr6 cause, send full cause, else esc_cause */ + dbug(1,dprintf("q9cau(0x%x)",ie[2])); + if(!cause[2] || cause[2]<0x80) break; /* eg. layer 1 error */ + Info_Number = 0x0008; + Info_Mask = 0x01; + if(cause[2] != ie[2]) Info_Element = cause; + break; + case 19: /* Redirected Number */ + dbug(1,dprintf("RDN")); + Info_Number = 0x0074; + Info_Mask = 0x400; + break; + case 22: /* Redirecing Number */ + dbug(1,dprintf("RIN")); + Info_Number = 0x0076; + Info_Mask = 0x400; + break; + case 23: /* Notification Indicator */ + dbug(1,dprintf("NI")); + Info_Number = (word)NI; + Info_Mask = 0x210; + break; + case 26: /* Call State */ + dbug(1,dprintf("CST")); + Info_Number = (word)CST; + Info_Mask = 0x01; /* do with cause i.e. for now */ + break; + case MAXPARMSIDS-2: /* Escape Message Type, must be the last indication */ + dbug(1,dprintf("ESC/MT[0x%x]",ie[3])); + Info_Number = 0x8000 |ie[3]; + if(iesent) Info_Mask = 0xffff; + else Info_Mask = 0x10; + Info_Element = ""; + break; + default: + Info_Number = 0; + Info_Mask = 0; + Info_Element = ""; + break; + } + } + + if(plci->Sig.Ind==NCR_FACILITY) /* check controller broadcast */ + { + for(j=0; j<max_appl; j++) + { + appl = &application[j]; + if(Info_Number + && appl->Id + && plci->adapter->Info_Mask[appl->Id-1] &Info_Mask) + { + dbug(1,dprintf("NCR_Ind")); + iesent=TRUE; + sendf(&application[j],_INFO_I,Id&0x0f,0,"wS",Info_Number,Info_Element); + } + } + } + else if(!plci->appl) + { /* overlap receiving broadcast */ + if(Info_Number==CPN + || Info_Number==KEY + || Info_Number==NI + || Info_Number==DSP + || Info_Number==UUI ) + { + for(j=0; j<max_appl; j++) + { + if(test_c_ind_mask_bit (plci, j)) + { + dbug(1,dprintf("Ovl_Ind")); + iesent=TRUE; + sendf(&application[j],_INFO_I,Id,0,"wS",Info_Number,Info_Element); + } + } + } + } /* all other signalling states */ + else if(Info_Number + && plci->adapter->Info_Mask[plci->appl->Id-1] &Info_Mask) + { + dbug(1,dprintf("Std_Ind")); + iesent=TRUE; + sendf(plci->appl,_INFO_I,Id,0,"wS",Info_Number,Info_Element); + } + } +} + + +byte SendMultiIE(PLCI * plci, dword Id, byte * * parms, byte ie_type, dword info_mask, byte setupParse) +{ + word i; + word j; + byte * ie; + word Info_Number; + byte * Info_Element; + APPL *appl; + word Info_Mask = 0; + byte iesent=0; + + if( + !plci->appl + && !plci->State + && plci->Sig.Ind!=NCR_FACILITY + && !setupParse + ) + { + dbug(1,dprintf("NoM-IEParse ")); + return 0; + } + dbug(1,dprintf("M-IEParse ")); + + for(i=0; i<MAX_MULTI_IE; i++) + { + ie = parms[i]; + Info_Number = 0; + Info_Element = ie; + if(ie[0]) + { + dbug(1,dprintf("[Ind0x%x]:IE=0x%x",plci->Sig.Ind,ie_type)); + Info_Number = (word)ie_type; + Info_Mask = (word)info_mask; + } + + if(plci->Sig.Ind==NCR_FACILITY) /* check controller broadcast */ + { + for(j=0; j<max_appl; j++) + { + appl = &application[j]; + if(Info_Number + && appl->Id + && plci->adapter->Info_Mask[appl->Id-1] &Info_Mask) + { + iesent = TRUE; + dbug(1,dprintf("Mlt_NCR_Ind")); + sendf(&application[j],_INFO_I,Id&0x0f,0,"wS",Info_Number,Info_Element); + } + } + } + else if(!plci->appl && Info_Number) + { /* overlap receiving broadcast */ + for(j=0; j<max_appl; j++) + { + if(test_c_ind_mask_bit (plci, j)) + { + iesent = TRUE; + dbug(1,dprintf("Mlt_Ovl_Ind")); + sendf(&application[j],_INFO_I,Id,0,"wS",Info_Number,Info_Element); + } + } + } /* all other signalling states */ + else if(Info_Number + && plci->adapter->Info_Mask[plci->appl->Id-1] &Info_Mask) + { + iesent = TRUE; + dbug(1,dprintf("Mlt_Std_Ind")); + sendf(plci->appl,_INFO_I,Id,0,"wS",Info_Number,Info_Element); + } + } + return iesent; +} + +static void SendSSExtInd(APPL * appl, PLCI * plci, dword Id, byte * * parms) +{ + word i; + /* Format of multi_ssext_parms[i][]: + 0 byte length + 1 byte SSEXTIE + 2 byte SSEXT_REQ/SSEXT_IND + 3 byte length + 4 word SSExtCommand + 6... Params + */ + if( + plci + && plci->State + && plci->Sig.Ind!=NCR_FACILITY + ) + for(i=0;i<MAX_MULTI_IE;i++) + { + if(parms[i][0]<6) continue; + if(parms[i][2]==SSEXT_REQ) continue; + + if(appl) + { + parms[i][0]=0; /* kill it */ + sendf(appl,_MANUFACTURER_I, + Id, + 0, + "dwS", + _DI_MANU_ID, + _DI_SSEXT_CTRL, + &parms[i][3]); + } + else if(plci->appl) + { + parms[i][0]=0; /* kill it */ + sendf(plci->appl,_MANUFACTURER_I, + Id, + 0, + "dwS", + _DI_MANU_ID, + _DI_SSEXT_CTRL, + &parms[i][3]); + } + } +}; + +void nl_ind(PLCI * plci) +{ + byte ch; + word ncci; + dword Id; + DIVA_CAPI_ADAPTER * a; + word NCCIcode; + APPL * APPLptr; + word count; + word Num; + word i, ncpi_state; + byte len, ncci_state; + word msg; + word info = 0; + word fax_feature_bits; + byte fax_send_edata_ack; + static byte v120_header_buffer[2 + 3]; + static word fax_info[] = { + 0, /* T30_SUCCESS */ + _FAX_NO_CONNECTION, /* T30_ERR_NO_DIS_RECEIVED */ + _FAX_PROTOCOL_ERROR, /* T30_ERR_TIMEOUT_NO_RESPONSE */ + _FAX_PROTOCOL_ERROR, /* T30_ERR_RETRY_NO_RESPONSE */ + _FAX_PROTOCOL_ERROR, /* T30_ERR_TOO_MANY_REPEATS */ + _FAX_PROTOCOL_ERROR, /* T30_ERR_UNEXPECTED_MESSAGE */ + _FAX_REMOTE_ABORT, /* T30_ERR_UNEXPECTED_DCN */ + _FAX_LOCAL_ABORT, /* T30_ERR_DTC_UNSUPPORTED */ + _FAX_TRAINING_ERROR, /* T30_ERR_ALL_RATES_FAILED */ + _FAX_TRAINING_ERROR, /* T30_ERR_TOO_MANY_TRAINS */ + _FAX_PARAMETER_ERROR, /* T30_ERR_RECEIVE_CORRUPTED */ + _FAX_REMOTE_ABORT, /* T30_ERR_UNEXPECTED_DISC */ + _FAX_LOCAL_ABORT, /* T30_ERR_APPLICATION_DISC */ + _FAX_REMOTE_REJECT, /* T30_ERR_INCOMPATIBLE_DIS */ + _FAX_LOCAL_ABORT, /* T30_ERR_INCOMPATIBLE_DCS */ + _FAX_PROTOCOL_ERROR, /* T30_ERR_TIMEOUT_NO_COMMAND */ + _FAX_PROTOCOL_ERROR, /* T30_ERR_RETRY_NO_COMMAND */ + _FAX_PROTOCOL_ERROR, /* T30_ERR_TIMEOUT_COMMAND_TOO_LONG */ + _FAX_PROTOCOL_ERROR, /* T30_ERR_TIMEOUT_RESPONSE_TOO_LONG */ + _FAX_NO_CONNECTION, /* T30_ERR_NOT_IDENTIFIED */ + _FAX_PROTOCOL_ERROR, /* T30_ERR_SUPERVISORY_TIMEOUT */ + _FAX_PARAMETER_ERROR, /* T30_ERR_TOO_LONG_SCAN_LINE */ + _FAX_PROTOCOL_ERROR, /* T30_ERR_RETRY_NO_PAGE_AFTER_MPS */ + _FAX_PROTOCOL_ERROR, /* T30_ERR_RETRY_NO_PAGE_AFTER_CFR */ + _FAX_PROTOCOL_ERROR, /* T30_ERR_RETRY_NO_DCS_AFTER_FTT */ + _FAX_PROTOCOL_ERROR, /* T30_ERR_RETRY_NO_DCS_AFTER_EOM */ + _FAX_PROTOCOL_ERROR, /* T30_ERR_RETRY_NO_DCS_AFTER_MPS */ + _FAX_PROTOCOL_ERROR, /* T30_ERR_RETRY_NO_DCN_AFTER_MCF */ + _FAX_PROTOCOL_ERROR, /* T30_ERR_RETRY_NO_DCN_AFTER_RTN */ + _FAX_PROTOCOL_ERROR, /* T30_ERR_RETRY_NO_CFR */ + _FAX_PROTOCOL_ERROR, /* T30_ERR_RETRY_NO_MCF_AFTER_EOP */ + _FAX_PROTOCOL_ERROR, /* T30_ERR_RETRY_NO_MCF_AFTER_EOM */ + _FAX_PROTOCOL_ERROR, /* T30_ERR_RETRY_NO_MCF_AFTER_MPS */ + 0x331d, /* T30_ERR_SUB_SEP_UNSUPPORTED */ + 0x331e, /* T30_ERR_PWD_UNSUPPORTED */ + 0x331f, /* T30_ERR_SUB_SEP_PWD_UNSUPPORTED */ + _FAX_PROTOCOL_ERROR, /* T30_ERR_INVALID_COMMAND_FRAME */ + _FAX_PARAMETER_ERROR, /* T30_ERR_UNSUPPORTED_PAGE_CODING */ + _FAX_PARAMETER_ERROR, /* T30_ERR_INVALID_PAGE_CODING */ + _FAX_REMOTE_REJECT, /* T30_ERR_INCOMPATIBLE_PAGE_CONFIG */ + _FAX_LOCAL_ABORT, /* T30_ERR_TIMEOUT_FROM_APPLICATION */ + _FAX_PROTOCOL_ERROR, /* T30_ERR_V34FAX_NO_REACTION_ON_MARK */ + _FAX_PROTOCOL_ERROR, /* T30_ERR_V34FAX_TRAINING_TIMEOUT */ + _FAX_PROTOCOL_ERROR, /* T30_ERR_V34FAX_UNEXPECTED_V21 */ + _FAX_PROTOCOL_ERROR, /* T30_ERR_V34FAX_PRIMARY_CTS_ON */ + _FAX_LOCAL_ABORT, /* T30_ERR_V34FAX_TURNAROUND_POLLING */ + _FAX_LOCAL_ABORT /* T30_ERR_V34FAX_V8_INCOMPATIBILITY */ + }; + + byte dtmf_code_buffer[CAPIDTMF_RECV_DIGIT_BUFFER_SIZE + 1]; + + + static word rtp_info[] = { + GOOD, /* RTP_SUCCESS */ + 0x3600 /* RTP_ERR_SSRC_OR_PAYLOAD_CHANGE */ + }; + + static dword udata_forwarding_table[0x100 / sizeof(dword)] = + { + 0x0020301e, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000 + }; + + ch = plci->NL.IndCh; + a = plci->adapter; + ncci = a->ch_ncci[ch]; + Id = (((dword)(ncci ? ncci : ch)) << 16) | (((word) plci->Id) << 8) | a->Id; + if(plci->tel) Id|=EXT_CONTROLLER; + APPLptr = plci->appl; + dbug(1,dprintf("NL_IND-Id(NL:0x%x)=0x%08lx,plci=%x,tel=%x,state=0x%x,ch=0x%x,chs=%d,Ind=%x", + plci->NL.Id,Id,plci->Id,plci->tel,plci->State,ch,plci->channels,plci->NL.Ind &0x0f)); + + /* in the case if no connect_active_Ind was sent to the appl we wait for */ + + if (plci->nl_remove_id) + { + plci->NL.RNR = 2; /* discard */ + dbug(1,dprintf("NL discard while remove pending")); + return; + } + if((plci->NL.Ind &0x0f)==N_CONNECT) + { + if(plci->State==INC_DIS_PENDING + || plci->State==OUTG_DIS_PENDING + || plci->State==IDLE) + { + plci->NL.RNR = 2; /* discard */ + dbug(1,dprintf("discard n_connect")); + return; + } + if(plci->State < INC_ACT_PENDING) + { + plci->NL.RNR = 1; /* flow control */ + channel_x_off (plci, ch, N_XON_CONNECT_IND); + return; + } + } + + if(!APPLptr) /* no application or invalid data */ + { /* while reloading the DSP */ + dbug(1,dprintf("discard1")); + plci->NL.RNR = 2; + return; + } + + if (((plci->NL.Ind &0x0f) == N_UDATA) + && (((plci->B2_prot != B2_SDLC) && ((plci->B1_resource == 17) || (plci->B1_resource == 18))) + || (plci->B2_prot == 7) + || (plci->B3_prot == 7)) ) + { + plci->ncpi_buffer[0] = 0; + + ncpi_state = plci->ncpi_state; + if (plci->NL.complete == 1) + { + byte * data = &plci->NL.RBuffer->P[0]; + + if ((plci->NL.RBuffer->length >= 12) + &&( (*data == DSP_UDATA_INDICATION_DCD_ON) + ||(*data == DSP_UDATA_INDICATION_CTS_ON)) ) + { + word conn_opt, ncpi_opt = 0x00; +/* HexDump ("MDM N_UDATA:", plci->NL.RBuffer->length, data); */ + + if (*data == DSP_UDATA_INDICATION_DCD_ON) + plci->ncpi_state |= NCPI_MDM_DCD_ON_RECEIVED; + if (*data == DSP_UDATA_INDICATION_CTS_ON) + plci->ncpi_state |= NCPI_MDM_CTS_ON_RECEIVED; + + data++; /* indication code */ + data += 2; /* timestamp */ + if ((*data == DSP_CONNECTED_NORM_V18) || (*data == DSP_CONNECTED_NORM_VOWN)) + ncpi_state &= ~(NCPI_MDM_DCD_ON_RECEIVED | NCPI_MDM_CTS_ON_RECEIVED); + data++; /* connected norm */ + conn_opt = GET_WORD(data); + data += 2; /* connected options */ + + PUT_WORD (&(plci->ncpi_buffer[1]), (word)(GET_DWORD(data) & 0x0000FFFF)); + + if (conn_opt & DSP_CONNECTED_OPTION_MASK_V42) + { + ncpi_opt |= MDM_NCPI_ECM_V42; + } + else if (conn_opt & DSP_CONNECTED_OPTION_MASK_MNP) + { + ncpi_opt |= MDM_NCPI_ECM_MNP; + } + else + { + ncpi_opt |= MDM_NCPI_TRANSPARENT; + } + if (conn_opt & DSP_CONNECTED_OPTION_MASK_COMPRESSION) + { + ncpi_opt |= MDM_NCPI_COMPRESSED; + } + PUT_WORD (&(plci->ncpi_buffer[3]), ncpi_opt); + plci->ncpi_buffer[0] = 4; + + plci->ncpi_state |= NCPI_VALID_CONNECT_B3_IND | NCPI_VALID_CONNECT_B3_ACT | NCPI_VALID_DISC_B3_IND; + } + } + if (plci->B3_prot == 7) + { + if (((a->ncci_state[ncci] == INC_ACT_PENDING) || (a->ncci_state[ncci] == OUTG_CON_PENDING)) + && (plci->ncpi_state & NCPI_VALID_CONNECT_B3_ACT) + && !(plci->ncpi_state & NCPI_CONNECT_B3_ACT_SENT)) + { + a->ncci_state[ncci] = INC_ACT_PENDING; + sendf(plci->appl,_CONNECT_B3_ACTIVE_I,Id,0,"S",plci->ncpi_buffer); + plci->ncpi_state |= NCPI_CONNECT_B3_ACT_SENT; + } + } + + if (!((plci->requested_options_conn | plci->requested_options | plci->adapter->requested_options_table[plci->appl->Id-1]) + & ((1L << PRIVATE_V18) | (1L << PRIVATE_VOWN))) + || !(ncpi_state & NCPI_MDM_DCD_ON_RECEIVED) + || !(ncpi_state & NCPI_MDM_CTS_ON_RECEIVED)) + + { + plci->NL.RNR = 2; + return; + } + } + + if(plci->NL.complete == 2) + { + if (((plci->NL.Ind &0x0f) == N_UDATA) + && !(udata_forwarding_table[plci->RData[0].P[0] >> 5] & (1L << (plci->RData[0].P[0] & 0x1f)))) + { + switch(plci->RData[0].P[0]) + { + + case DTMF_UDATA_INDICATION_FAX_CALLING_TONE: + if (plci->dtmf_rec_active & DTMF_LISTEN_ACTIVE_FLAG) + sendf(plci->appl, _FACILITY_I, Id & 0xffffL, 0,"ws", SELECTOR_DTMF, "\x01X"); + break; + case DTMF_UDATA_INDICATION_ANSWER_TONE: + if (plci->dtmf_rec_active & DTMF_LISTEN_ACTIVE_FLAG) + sendf(plci->appl, _FACILITY_I, Id & 0xffffL, 0,"ws", SELECTOR_DTMF, "\x01Y"); + break; + case DTMF_UDATA_INDICATION_DIGITS_RECEIVED: + dtmf_indication (Id, plci, plci->RData[0].P, plci->RData[0].PLength); + break; + case DTMF_UDATA_INDICATION_DIGITS_SENT: + dtmf_confirmation (Id, plci); + break; + + + case UDATA_INDICATION_MIXER_TAP_DATA: + capidtmf_recv_process_block (&(plci->capidtmf_state), plci->RData[0].P + 1, (word)(plci->RData[0].PLength - 1)); + i = capidtmf_indication (&(plci->capidtmf_state), dtmf_code_buffer + 1); + if (i != 0) + { + dtmf_code_buffer[0] = DTMF_UDATA_INDICATION_DIGITS_RECEIVED; + dtmf_indication (Id, plci, dtmf_code_buffer, (word)(i + 1)); + } + break; + + + case UDATA_INDICATION_MIXER_COEFS_SET: + mixer_indication_coefs_set (Id, plci); + break; + case UDATA_INDICATION_XCONNECT_FROM: + mixer_indication_xconnect_from (Id, plci, plci->RData[0].P, plci->RData[0].PLength); + break; + case UDATA_INDICATION_XCONNECT_TO: + mixer_indication_xconnect_to (Id, plci, plci->RData[0].P, plci->RData[0].PLength); + break; + + + case LEC_UDATA_INDICATION_DISABLE_DETECT: + ec_indication (Id, plci, plci->RData[0].P, plci->RData[0].PLength); + break; + + + + default: + break; + } + } + else + { + if ((plci->RData[0].PLength != 0) + && ((plci->B2_prot == B2_V120_ASYNC) + || (plci->B2_prot == B2_V120_ASYNC_V42BIS) + || (plci->B2_prot == B2_V120_BIT_TRANSPARENT))) + { + + sendf(plci->appl,_DATA_B3_I,Id,0, + "dwww", + plci->RData[1].P, + (plci->NL.RNum < 2) ? 0 : plci->RData[1].PLength, + plci->RNum, + plci->RFlags); + + } + else + { + + sendf(plci->appl,_DATA_B3_I,Id,0, + "dwww", + plci->RData[0].P, + plci->RData[0].PLength, + plci->RNum, + plci->RFlags); + + } + } + return; + } + + fax_feature_bits = 0; + if((plci->NL.Ind &0x0f)==N_CONNECT || + (plci->NL.Ind &0x0f)==N_CONNECT_ACK || + (plci->NL.Ind &0x0f)==N_DISC || + (plci->NL.Ind &0x0f)==N_EDATA || + (plci->NL.Ind &0x0f)==N_DISC_ACK) + { + info = 0; + plci->ncpi_buffer[0] = 0; + switch (plci->B3_prot) { + case 0: /*XPARENT*/ + case 1: /*T.90 NL*/ + break; /* no network control protocol info - jfr */ + case 2: /*ISO8202*/ + case 3: /*X25 DCE*/ + for(i=0; i<plci->NL.RLength; i++) plci->ncpi_buffer[4+i] = plci->NL.RBuffer->P[i]; + plci->ncpi_buffer[0] = (byte)(i+3); + plci->ncpi_buffer[1] = (byte)(plci->NL.Ind &N_D_BIT? 1:0); + plci->ncpi_buffer[2] = 0; + plci->ncpi_buffer[3] = 0; + break; + case 4: /*T.30 - FAX*/ + case 5: /*T.30 - FAX*/ + if(plci->NL.RLength>=sizeof(T30_INFO)) + { + dbug(1,dprintf("FaxStatus %04x", ((T30_INFO *)plci->NL.RBuffer->P)->code)); + len = 9; + PUT_WORD(&(plci->ncpi_buffer[1]),((T30_INFO *)plci->NL.RBuffer->P)->rate_div_2400 * 2400); + fax_feature_bits = GET_WORD(&((T30_INFO *)plci->NL.RBuffer->P)->feature_bits_low); + i = (((T30_INFO *)plci->NL.RBuffer->P)->resolution & T30_RESOLUTION_R8_0770_OR_200) ? 0x0001 : 0x0000; + if (plci->B3_prot == 5) + { + if (!(fax_feature_bits & T30_FEATURE_BIT_ECM)) + i |= 0x8000; /* This is not an ECM connection */ + if (fax_feature_bits & T30_FEATURE_BIT_T6_CODING) + i |= 0x4000; /* This is a connection with MMR compression */ + if (fax_feature_bits & T30_FEATURE_BIT_2D_CODING) + i |= 0x2000; /* This is a connection with MR compression */ + if (fax_feature_bits & T30_FEATURE_BIT_MORE_DOCUMENTS) + i |= 0x0004; /* More documents */ + if (fax_feature_bits & T30_FEATURE_BIT_POLLING) + i |= 0x0002; /* Fax-polling indication */ + } + dbug(1,dprintf("FAX Options %04x %04x",fax_feature_bits,i)); + PUT_WORD(&(plci->ncpi_buffer[3]),i); + PUT_WORD(&(plci->ncpi_buffer[5]),((T30_INFO *)plci->NL.RBuffer->P)->data_format); + plci->ncpi_buffer[7] = ((T30_INFO *)plci->NL.RBuffer->P)->pages_low; + plci->ncpi_buffer[8] = ((T30_INFO *)plci->NL.RBuffer->P)->pages_high; + plci->ncpi_buffer[len] = 0; + if(((T30_INFO *)plci->NL.RBuffer->P)->station_id_len) + { + plci->ncpi_buffer[len] = 20; + for (i = 0; i < 20; i++) + plci->ncpi_buffer[++len] = ((T30_INFO *)plci->NL.RBuffer->P)->station_id[i]; + } + if (((plci->NL.Ind & 0x0f) == N_DISC) || ((plci->NL.Ind & 0x0f) == N_DISC_ACK)) + { + if (((T30_INFO *)plci->NL.RBuffer->P)->code < sizeof(fax_info) / sizeof(fax_info[0])) + info = fax_info[((T30_INFO *)plci->NL.RBuffer->P)->code]; + else + info = _FAX_PROTOCOL_ERROR; + } + + if ((plci->requested_options_conn | plci->requested_options | a->requested_options_table[plci->appl->Id-1]) + & ((1L << PRIVATE_FAX_SUB_SEP_PWD) | (1L << PRIVATE_FAX_NONSTANDARD))) + { + i = ((word)(((T30_INFO *) 0)->station_id + 20)) + ((T30_INFO *)plci->NL.RBuffer->P)->head_line_len; + while (i < plci->NL.RBuffer->length) + plci->ncpi_buffer[++len] = plci->NL.RBuffer->P[i++]; + } + + plci->ncpi_buffer[0] = len; + fax_feature_bits = GET_WORD(&((T30_INFO *)plci->NL.RBuffer->P)->feature_bits_low); + PUT_WORD(&((T30_INFO *)plci->fax_connect_info_buffer)->feature_bits_low, fax_feature_bits); + + plci->ncpi_state |= NCPI_VALID_CONNECT_B3_IND; + if (((plci->NL.Ind &0x0f) == N_CONNECT_ACK) + || (((plci->NL.Ind &0x0f) == N_CONNECT) + && (fax_feature_bits & T30_FEATURE_BIT_POLLING)) + || (((plci->NL.Ind &0x0f) == N_EDATA) + && ((((T30_INFO *)plci->NL.RBuffer->P)->code == EDATA_T30_TRAIN_OK) + || (((T30_INFO *)plci->NL.RBuffer->P)->code == EDATA_T30_DIS) + || (((T30_INFO *)plci->NL.RBuffer->P)->code == EDATA_T30_DTC)))) + { + plci->ncpi_state |= NCPI_VALID_CONNECT_B3_ACT; + } + if (((plci->NL.Ind &0x0f) == N_DISC) + || ((plci->NL.Ind &0x0f) == N_DISC_ACK) + || (((plci->NL.Ind &0x0f) == N_EDATA) + && (((T30_INFO *)plci->NL.RBuffer->P)->code == EDATA_T30_EOP_CAPI))) + { + plci->ncpi_state |= NCPI_VALID_CONNECT_B3_ACT | NCPI_VALID_DISC_B3_IND; + } + } + break; + + case B3_RTP: + if (((plci->NL.Ind & 0x0f) == N_DISC) || ((plci->NL.Ind & 0x0f) == N_DISC_ACK)) + { + if (plci->NL.RLength != 0) + { + info = rtp_info[plci->NL.RBuffer->P[0]]; + plci->ncpi_buffer[0] = plci->NL.RLength - 1; + for (i = 1; i < plci->NL.RLength; i++) + plci->ncpi_buffer[i] = plci->NL.RBuffer->P[i]; + } + } + break; + + } + plci->NL.RNR = 2; + } + switch(plci->NL.Ind &0x0f) { + case N_EDATA: + if ((plci->B3_prot == 4) || (plci->B3_prot == 5)) + { + dbug(1,dprintf("EDATA ncci=0x%x state=%d code=%02x", ncci, a->ncci_state[ncci], + ((T30_INFO *)plci->NL.RBuffer->P)->code)); + fax_send_edata_ack = (((T30_INFO *)(plci->fax_connect_info_buffer))->operating_mode == T30_OPERATING_MODE_CAPI_NEG); + + if ((plci->nsf_control_bits & T30_NSF_CONTROL_BIT_ENABLE_NSF) + && (plci->nsf_control_bits & (T30_NSF_CONTROL_BIT_NEGOTIATE_IND | T30_NSF_CONTROL_BIT_NEGOTIATE_RESP)) + && (((T30_INFO *)plci->NL.RBuffer->P)->code == EDATA_T30_DIS) + && (a->ncci_state[ncci] == OUTG_CON_PENDING) + && (plci->ncpi_state & NCPI_VALID_CONNECT_B3_ACT) + && !(plci->ncpi_state & NCPI_NEGOTIATE_B3_SENT)) + { + ((T30_INFO *)(plci->fax_connect_info_buffer))->code = ((T30_INFO *)plci->NL.RBuffer->P)->code; + sendf(plci->appl,_MANUFACTURER_I,Id,0,"dwbS",_DI_MANU_ID,_DI_NEGOTIATE_B3, + (byte)(plci->ncpi_buffer[0] + 1), plci->ncpi_buffer); + plci->ncpi_state |= NCPI_NEGOTIATE_B3_SENT; + if (plci->nsf_control_bits & T30_NSF_CONTROL_BIT_NEGOTIATE_RESP) + fax_send_edata_ack = FALSE; + } + + if (a->manufacturer_features & MANUFACTURER_FEATURE_FAX_PAPER_FORMATS) + { + switch (((T30_INFO *)plci->NL.RBuffer->P)->code) + { + case EDATA_T30_DIS: + if ((a->ncci_state[ncci] == OUTG_CON_PENDING) + && !(GET_WORD(&((T30_INFO *)plci->fax_connect_info_buffer)->control_bits_low) & T30_CONTROL_BIT_REQUEST_POLLING) + && (plci->ncpi_state & NCPI_VALID_CONNECT_B3_ACT) + && !(plci->ncpi_state & NCPI_CONNECT_B3_ACT_SENT)) + { + a->ncci_state[ncci] = INC_ACT_PENDING; + if (plci->B3_prot == 4) + sendf(plci->appl,_CONNECT_B3_ACTIVE_I,Id,0,"s",""); + else + sendf(plci->appl,_CONNECT_B3_ACTIVE_I,Id,0,"S",plci->ncpi_buffer); + plci->ncpi_state |= NCPI_CONNECT_B3_ACT_SENT; + } + break; + + case EDATA_T30_TRAIN_OK: + if ((a->ncci_state[ncci] == INC_ACT_PENDING) + && (plci->ncpi_state & NCPI_VALID_CONNECT_B3_ACT) + && !(plci->ncpi_state & NCPI_CONNECT_B3_ACT_SENT)) + { + if (plci->B3_prot == 4) + sendf(plci->appl,_CONNECT_B3_ACTIVE_I,Id,0,"s",""); + else + sendf(plci->appl,_CONNECT_B3_ACTIVE_I,Id,0,"S",plci->ncpi_buffer); + plci->ncpi_state |= NCPI_CONNECT_B3_ACT_SENT; + } + break; + + case EDATA_T30_EOP_CAPI: + if (a->ncci_state[ncci] == CONNECTED) + { + sendf(plci->appl,_DISCONNECT_B3_I,Id,0,"wS",GOOD,plci->ncpi_buffer); + a->ncci_state[ncci] = INC_DIS_PENDING; + plci->ncpi_state = 0; + fax_send_edata_ack = FALSE; + } + break; + } + } + else + { + switch (((T30_INFO *)plci->NL.RBuffer->P)->code) + { + case EDATA_T30_TRAIN_OK: + if ((a->ncci_state[ncci] == INC_ACT_PENDING) + && (plci->ncpi_state & NCPI_VALID_CONNECT_B3_ACT) + && !(plci->ncpi_state & NCPI_CONNECT_B3_ACT_SENT)) + { + if (plci->B3_prot == 4) + sendf(plci->appl,_CONNECT_B3_ACTIVE_I,Id,0,"s",""); + else + sendf(plci->appl,_CONNECT_B3_ACTIVE_I,Id,0,"S",plci->ncpi_buffer); + plci->ncpi_state |= NCPI_CONNECT_B3_ACT_SENT; + } + break; + } + } + if (fax_send_edata_ack) + { + ((T30_INFO *)(plci->fax_connect_info_buffer))->code = ((T30_INFO *)plci->NL.RBuffer->P)->code; + plci->fax_edata_ack_length = 1; + start_internal_command (Id, plci, fax_edata_ack_command); + } + } + else + { + dbug(1,dprintf("EDATA ncci=0x%x state=%d", ncci, a->ncci_state[ncci])); + } + break; + case N_CONNECT: + if (!a->ch_ncci[ch]) + { + ncci = get_ncci (plci, ch, 0); + Id = (Id & 0xffff) | (((dword) ncci) << 16); + } + dbug(1,dprintf("N_CONNECT: ch=%d state=%d plci=%lx plci_Id=%lx plci_State=%d", + ch, a->ncci_state[ncci], a->ncci_plci[ncci], plci->Id, plci->State)); + + msg = _CONNECT_B3_I; + if (a->ncci_state[ncci] == IDLE) + plci->channels++; + else if (plci->B3_prot == 1) + msg = _CONNECT_B3_T90_ACTIVE_I; + + a->ncci_state[ncci] = INC_CON_PENDING; + if(plci->B3_prot == 4) + sendf(plci->appl,msg,Id,0,"s",""); + else + sendf(plci->appl,msg,Id,0,"S",plci->ncpi_buffer); + break; + case N_CONNECT_ACK: + dbug(1,dprintf("N_connect_Ack")); + if (plci->internal_command_queue[0] + && ((plci->adjust_b_state == ADJUST_B_CONNECT_2) + || (plci->adjust_b_state == ADJUST_B_CONNECT_3) + || (plci->adjust_b_state == ADJUST_B_CONNECT_4))) + { + (*(plci->internal_command_queue[0]))(Id, plci, 0); + if (!plci->internal_command) + next_internal_command (Id, plci); + break; + } + msg = _CONNECT_B3_ACTIVE_I; + if (plci->B3_prot == 1) + { + if (a->ncci_state[ncci] != OUTG_CON_PENDING) + msg = _CONNECT_B3_T90_ACTIVE_I; + a->ncci_state[ncci] = INC_ACT_PENDING; + sendf(plci->appl,msg,Id,0,"S",plci->ncpi_buffer); + } + else if ((plci->B3_prot == 4) || (plci->B3_prot == 5) || (plci->B3_prot == 7)) + { + if ((a->ncci_state[ncci] == OUTG_CON_PENDING) + && (plci->ncpi_state & NCPI_VALID_CONNECT_B3_ACT) + && !(plci->ncpi_state & NCPI_CONNECT_B3_ACT_SENT)) + { + a->ncci_state[ncci] = INC_ACT_PENDING; + if (plci->B3_prot == 4) + sendf(plci->appl,msg,Id,0,"s",""); + else + sendf(plci->appl,msg,Id,0,"S",plci->ncpi_buffer); + plci->ncpi_state |= NCPI_CONNECT_B3_ACT_SENT; + } + } + else + { + a->ncci_state[ncci] = INC_ACT_PENDING; + sendf(plci->appl,msg,Id,0,"S",plci->ncpi_buffer); + } + if (plci->adjust_b_restore) + { + plci->adjust_b_restore = FALSE; + start_internal_command (Id, plci, adjust_b_restore); + } + break; + case N_DISC: + case N_DISC_ACK: + if (plci->internal_command_queue[0] + && ((plci->internal_command == FAX_DISCONNECT_COMMAND_1) + || (plci->internal_command == FAX_DISCONNECT_COMMAND_2) + || (plci->internal_command == FAX_DISCONNECT_COMMAND_3))) + { + (*(plci->internal_command_queue[0]))(Id, plci, 0); + if (!plci->internal_command) + next_internal_command (Id, plci); + } + ncci_state = a->ncci_state[ncci]; + ncci_remove (plci, ncci, FALSE); + + /* with N_DISC or N_DISC_ACK the IDI frees the respective */ + /* channel, so we cannot store the state in ncci_state! The */ + /* information which channel we received a N_DISC is thus */ + /* stored in the inc_dis_ncci_table buffer. */ + for(i=0; plci->inc_dis_ncci_table[i]; i++); + plci->inc_dis_ncci_table[i] = (byte) ncci; + + /* need a connect_b3_ind before a disconnect_b3_ind with FAX */ + if (!plci->channels + && (plci->B1_resource == 16) + && (plci->State <= CONNECTED)) + { + len = 9; + i = ((T30_INFO *)plci->fax_connect_info_buffer)->rate_div_2400 * 2400; + PUT_WORD (&plci->ncpi_buffer[1], i); + PUT_WORD (&plci->ncpi_buffer[3], 0); + i = ((T30_INFO *)plci->fax_connect_info_buffer)->data_format; + PUT_WORD (&plci->ncpi_buffer[5], i); + PUT_WORD (&plci->ncpi_buffer[7], 0); + plci->ncpi_buffer[len] = 0; + plci->ncpi_buffer[0] = len; + if(plci->B3_prot == 4) + sendf(plci->appl,_CONNECT_B3_I,Id,0,"s",""); + else + { + + if ((plci->requested_options_conn | plci->requested_options | a->requested_options_table[plci->appl->Id-1]) + & ((1L << PRIVATE_FAX_SUB_SEP_PWD) | (1L << PRIVATE_FAX_NONSTANDARD))) + { + plci->ncpi_buffer[++len] = 0; + plci->ncpi_buffer[++len] = 0; + plci->ncpi_buffer[++len] = 0; + plci->ncpi_buffer[0] = len; + } + + sendf(plci->appl,_CONNECT_B3_I,Id,0,"S",plci->ncpi_buffer); + } + sendf(plci->appl,_DISCONNECT_B3_I,Id,0,"wS",info,plci->ncpi_buffer); + plci->ncpi_state = 0; + sig_req(plci,HANGUP,0); + send_req(plci); + plci->State = OUTG_DIS_PENDING; + /* disc here */ + } + else if ((a->manufacturer_features & MANUFACTURER_FEATURE_FAX_PAPER_FORMATS) + && ((plci->B3_prot == 4) || (plci->B3_prot == 5)) + && ((ncci_state == INC_DIS_PENDING) || (ncci_state == IDLE))) + { + if (ncci_state == IDLE) + { + if (plci->channels) + plci->channels--; + if((plci->State==IDLE || plci->State==SUSPENDING) && !plci->channels){ + if(plci->State == SUSPENDING){ + sendf(plci->appl, + _FACILITY_I, + Id & 0xffffL, + 0, + "ws", (word)3, "\x03\x04\x00\x00"); + sendf(plci->appl, _DISCONNECT_I, Id & 0xffffL, 0, "w", 0); + } + plci_remove(plci); + plci->State=IDLE; + } + } + } + else if (plci->channels) + { + sendf(plci->appl,_DISCONNECT_B3_I,Id,0,"wS",info,plci->ncpi_buffer); + plci->ncpi_state = 0; + if ((ncci_state == OUTG_REJ_PENDING) + && ((plci->B3_prot != B3_T90NL) && (plci->B3_prot != B3_ISO8208) && (plci->B3_prot != B3_X25_DCE))) + { + sig_req(plci,HANGUP,0); + send_req(plci); + plci->State = OUTG_DIS_PENDING; + } + } + break; + case N_RESET: + a->ncci_state[ncci] = INC_RES_PENDING; + sendf(plci->appl,_RESET_B3_I,Id,0,"S",plci->ncpi_buffer); + break; + case N_RESET_ACK: + a->ncci_state[ncci] = CONNECTED; + sendf(plci->appl,_RESET_B3_I,Id,0,"S",plci->ncpi_buffer); + break; + + case N_UDATA: + if (!(udata_forwarding_table[plci->NL.RBuffer->P[0] >> 5] & (1L << (plci->NL.RBuffer->P[0] & 0x1f)))) + { + plci->RData[0].P = plci->internal_ind_buffer + (-((int)(plci->internal_ind_buffer)) & 3); + plci->RData[0].PLength = INTERNAL_IND_BUFFER_SIZE; + plci->NL.R = plci->RData; + plci->NL.RNum = 1; + return; + } + case N_BDATA: + case N_DATA: + if (((a->ncci_state[ncci] != CONNECTED) && (plci->B2_prot == 1)) /* transparent */ + || (a->ncci_state[ncci] == IDLE) + || (a->ncci_state[ncci] == INC_DIS_PENDING)) + { + plci->NL.RNR = 2; + break; + } + if ((a->ncci_state[ncci] != CONNECTED) + && (a->ncci_state[ncci] != OUTG_DIS_PENDING) + && (a->ncci_state[ncci] != OUTG_REJ_PENDING)) + { + dbug(1,dprintf("flow control")); + plci->NL.RNR = 1; /* flow control */ + channel_x_off (plci, ch, 0); + break; + } + + NCCIcode = ncci | (((word)a->Id) << 8); + + /* count all buffers within the Application pool */ + /* belonging to the same NCCI. If this is below the */ + /* number of buffers available per NCCI we accept */ + /* this packet, otherwise we reject it */ + count = 0; + Num = 0xffff; + for(i=0; i<APPLptr->MaxBuffer; i++) { + if(NCCIcode==APPLptr->DataNCCI[i]) count++; + if(!APPLptr->DataNCCI[i] && Num==0xffff) Num = i; + } + + if(count>=APPLptr->MaxNCCIData || Num==0xffff) + { + dbug(3,dprintf("Flow-Control")); + plci->NL.RNR = 1; + if( ++(APPLptr->NCCIDataFlowCtrlTimer)>= + (word)((a->manufacturer_features & MANUFACTURER_FEATURE_OOB_CHANNEL) ? 40 : 2000)) + { + plci->NL.RNR = 2; + dbug(3,dprintf("DiscardData")); + } else { + channel_x_off (plci, ch, 0); + } + break; + } + else + { + APPLptr->NCCIDataFlowCtrlTimer = 0; + } + + plci->RData[0].P = ReceiveBufferGet(APPLptr,Num); + if(!plci->RData[0].P) { + plci->NL.RNR = 1; + channel_x_off (plci, ch, 0); + break; + } + + APPLptr->DataNCCI[Num] = NCCIcode; + APPLptr->DataFlags[Num] = (plci->Id<<8) | (plci->NL.Ind>>4); + dbug(3,dprintf("Buffer(%d), Max = %d",Num,APPLptr->MaxBuffer)); + + plci->RNum = Num; + plci->RFlags = plci->NL.Ind>>4; + plci->RData[0].PLength = APPLptr->MaxDataLength; + plci->NL.R = plci->RData; + if ((plci->NL.RLength != 0) + && ((plci->B2_prot == B2_V120_ASYNC) + || (plci->B2_prot == B2_V120_ASYNC_V42BIS) + || (plci->B2_prot == B2_V120_BIT_TRANSPARENT))) + { + plci->RData[1].P = plci->RData[0].P; + plci->RData[1].PLength = plci->RData[0].PLength; + plci->RData[0].P = v120_header_buffer + (-((int) v120_header_buffer) & 3); + if ((plci->NL.RBuffer->P[0] & V120_HEADER_EXTEND_BIT) || (plci->NL.RLength == 1)) + plci->RData[0].PLength = 1; + else + plci->RData[0].PLength = 2; + if (plci->NL.RBuffer->P[0] & V120_HEADER_BREAK_BIT) + plci->RFlags |= 0x0010; + if (plci->NL.RBuffer->P[0] & (V120_HEADER_C1_BIT | V120_HEADER_C2_BIT)) + plci->RFlags |= 0x8000; + plci->NL.RNum = 2; + } + else + { + if((plci->NL.Ind &0x0f)==N_UDATA) + plci->RFlags |= 0x0010; + + else if ((plci->B3_prot == B3_RTP) && ((plci->NL.Ind & 0x0f) == N_BDATA)) + plci->RFlags |= 0x0001; + + plci->NL.RNum = 1; + } + break; + case N_DATA_ACK: + data_ack (plci, ch); + break; + default: + plci->NL.RNR = 2; + break; + } +} + +/*------------------------------------------------------------------*/ +/* find a free PLCI */ +/*------------------------------------------------------------------*/ + +word get_plci(DIVA_CAPI_ADAPTER * a) +{ + word i,j; + PLCI * plci; + + dump_plcis (a); + for(i=0;i<a->max_plci && a->plci[i].Id;i++); + if(i==a->max_plci) { + dbug(1,dprintf("get_plci: out of PLCIs")); + return 0; + } + plci = &a->plci[i]; + plci->Id = (byte)(i+1); + + plci->Sig.Id = 0; + plci->NL.Id = 0; + plci->sig_req = 0; + plci->nl_req = 0; + + plci->appl = NULL; + plci->relatedPTYPLCI = NULL; + plci->State = IDLE; + plci->SuppState = IDLE; + plci->channels = 0; + plci->tel = 0; + plci->B1_resource = 0; + plci->B2_prot = 0; + plci->B3_prot = 0; + + plci->command = 0; + plci->m_command = 0; + init_internal_command_queue (plci); + plci->number = 0; + plci->req_in_start = 0; + plci->req_in = 0; + plci->req_out = 0; + plci->msg_in_write_pos = MSG_IN_QUEUE_SIZE; + plci->msg_in_read_pos = MSG_IN_QUEUE_SIZE; + plci->msg_in_wrap_pos = MSG_IN_QUEUE_SIZE; + + plci->data_sent = FALSE; + plci->send_disc = 0; + plci->sig_global_req = 0; + plci->sig_remove_id = 0; + plci->nl_global_req = 0; + plci->nl_remove_id = 0; + plci->adv_nl = 0; + plci->manufacturer = FALSE; + plci->call_dir = CALL_DIR_OUT | CALL_DIR_ORIGINATE; + plci->spoofed_msg = 0; + plci->ptyState = 0; + plci->cr_enquiry = FALSE; + plci->hangup_flow_ctrl_timer = 0; + + plci->ncci_ring_list = 0; + for(j=0;j<MAX_CHANNELS_PER_PLCI;j++) plci->inc_dis_ncci_table[j] = 0; + clear_c_ind_mask (plci); + set_group_ind_mask (plci); + plci->fax_connect_info_length = 0; + plci->nsf_control_bits = 0; + plci->ncpi_state = 0x00; + plci->ncpi_buffer[0] = 0; + + plci->requested_options_conn = 0; + plci->requested_options = 0; + plci->notifiedcall = 0; + plci->vswitchstate = 0; + plci->vsprot = 0; + plci->vsprotdialect = 0; + init_b1_config (plci); + dbug(1,dprintf("get_plci(%x)",plci->Id)); + return i+1; +} + +/*------------------------------------------------------------------*/ +/* put a parameter in the parameter buffer */ +/*------------------------------------------------------------------*/ + +static void add_p(PLCI * plci, byte code, byte * p) +{ + word p_length; + + p_length = 0; + if(p) p_length = p[0]; + add_ie(plci, code, p, p_length); +} + +/*------------------------------------------------------------------*/ +/* put a structure in the parameter buffer */ +/*------------------------------------------------------------------*/ +static void add_s(PLCI * plci, byte code, API_PARSE * p) +{ + if(p) add_ie(plci, code, p->info, (word)p->length); +} + +/*------------------------------------------------------------------*/ +/* put multiple structures in the parameter buffer */ +/*------------------------------------------------------------------*/ +static void add_ss(PLCI * plci, byte code, API_PARSE * p) +{ + byte i; + + if(p){ + dbug(1,dprintf("add_ss(%x,len=%d)",code,p->length)); + for(i=2;i<(byte)p->length;i+=p->info[i]+2){ + dbug(1,dprintf("add_ss_ie(%x,len=%d)",p->info[i-1],p->info[i])); + add_ie(plci, p->info[i-1], (byte *)&(p->info[i]), (word)p->info[i]); + } + } +} + +/*------------------------------------------------------------------*/ +/* return the channel number sent by the application in a esc_chi */ +/*------------------------------------------------------------------*/ +static byte getChannel(API_PARSE * p) +{ + byte i; + + if(p){ + for(i=2;i<(byte)p->length;i+=p->info[i]+2){ + if(p->info[i]==2){ + if(p->info[i-1]==ESC && p->info[i+1]==CHI) return (p->info[i+2]); + } + } + } + return 0; +} + + +/*------------------------------------------------------------------*/ +/* put an information element in the parameter buffer */ +/*------------------------------------------------------------------*/ + +static void add_ie(PLCI * plci, byte code, byte * p, word p_length) +{ + word i; + + if(!(code &0x80) && !p_length) return; + + if(plci->req_in==plci->req_in_start) { + plci->req_in +=2; + } + else { + plci->req_in--; + } + plci->RBuffer[plci->req_in++] = code; + + if(p) { + plci->RBuffer[plci->req_in++] = (byte)p_length; + for(i=0;i<p_length;i++) plci->RBuffer[plci->req_in++] = p[1+i]; + } + + plci->RBuffer[plci->req_in++] = 0; +} + +/*------------------------------------------------------------------*/ +/* put a unstructured data into the buffer */ +/*------------------------------------------------------------------*/ + +void add_d(PLCI * plci, word length, byte * p) +{ + word i; + + if(plci->req_in==plci->req_in_start) { + plci->req_in +=2; + } + else { + plci->req_in--; + } + for(i=0;i<length;i++) plci->RBuffer[plci->req_in++] = p[i]; +} + +/*------------------------------------------------------------------*/ +/* put parameters from the Additional Info parameter in the */ +/* parameter buffer */ +/*------------------------------------------------------------------*/ + +void add_ai(PLCI * plci, API_PARSE * ai) +{ + word i; + API_PARSE ai_parms[5]; + + for(i=0;i<5;i++) ai_parms[i].length = 0; + + if(!ai->length) + return; + if(api_parse(&ai->info[1], (word)ai->length, "ssss", ai_parms)) + return; + + add_s (plci,KEY,&ai_parms[1]); + add_s (plci,UUI,&ai_parms[2]); + add_ss(plci,FTY,&ai_parms[3]); +} + +/*------------------------------------------------------------------*/ +/* put parameter for b1 protocol in the parameter buffer */ +/*------------------------------------------------------------------*/ + +word add_b1(PLCI * plci, API_PARSE * bp, word b_channel_info, word b1_facilities) +{ + API_PARSE bp_parms[8]; + API_PARSE mdm_cfg[9]; + API_PARSE global_config[2]; + byte cai[256]; + byte resource[] = {5,9,13,12,16,39,9,17,17,18}; + byte voice_cai[] = "\x06\x14\x00\x00\x00\x00\x08"; + word i; + + API_PARSE mdm_cfg_v18[4]; + word j, n, w; + dword d; + + + for(i=0;i<8;i++) bp_parms[i].length = 0; + for(i=0;i<2;i++) global_config[i].length = 0; + + dbug(1,dprintf("add_b1")); + api_save_msg(bp, "s", &plci->B_protocol); + + if(b_channel_info==2){ + plci->B1_resource = 0; + adjust_b1_facilities (plci, plci->B1_resource, b1_facilities); + add_p(plci, CAI, "\x01\x00"); + dbug(1,dprintf("Cai=1,0 (no resource)")); + return 0; + } + + if(plci->tel == CODEC_PERMANENT) return 0; + else if(plci->tel == CODEC){ + plci->B1_resource = 1; + adjust_b1_facilities (plci, plci->B1_resource, b1_facilities); + add_p(plci, CAI, "\x01\x01"); + dbug(1,dprintf("Cai=1,1 (Codec)")); + return 0; + } + else if(plci->tel == ADV_VOICE){ + plci->B1_resource = add_b1_facilities (plci, 9, (word)(b1_facilities | B1_FACILITY_VOICE)); + adjust_b1_facilities (plci, plci->B1_resource, (word)(b1_facilities | B1_FACILITY_VOICE)); + voice_cai[1] = plci->B1_resource; + PUT_WORD (&voice_cai[5], plci->appl->MaxDataLength); + add_p(plci, CAI, voice_cai); + dbug(1,dprintf("Cai=1,0x%x (AdvVoice)",voice_cai[1])); + return 0; + } + plci->call_dir &= ~(CALL_DIR_ORIGINATE | CALL_DIR_ANSWER); + if (plci->call_dir & CALL_DIR_OUT) + plci->call_dir |= CALL_DIR_ORIGINATE; + else if (plci->call_dir & CALL_DIR_IN) + plci->call_dir |= CALL_DIR_ANSWER; + + if(!bp->length){ + plci->B1_resource = 0x5; + adjust_b1_facilities (plci, plci->B1_resource, b1_facilities); + add_p(plci, CAI, "\x01\x05"); + return 0; + } + + dbug(1,dprintf("b_prot_len=%d",(word)bp->length)); + if(bp->length>256) return _WRONG_MESSAGE_FORMAT; + if(api_parse(&bp->info[1], (word)bp->length, "wwwsssb", bp_parms)) + { + bp_parms[6].length = 0; + if(api_parse(&bp->info[1], (word)bp->length, "wwwsss", bp_parms)) + { + dbug(1,dprintf("b-form.!")); + return _WRONG_MESSAGE_FORMAT; + } + } + else if (api_parse(&bp->info[1], (word)bp->length, "wwwssss", bp_parms)) + { + dbug(1,dprintf("b-form.!")); + return _WRONG_MESSAGE_FORMAT; + } + + if(bp_parms[6].length) + { + if(api_parse(&bp_parms[6].info[1], (word)bp_parms[6].length, "w", global_config)) + { + return _WRONG_MESSAGE_FORMAT; + } + switch(GET_WORD(global_config[0].info)) + { + case 1: + plci->call_dir = (plci->call_dir & ~CALL_DIR_ANSWER) | CALL_DIR_ORIGINATE; + break; + case 2: + plci->call_dir = (plci->call_dir & ~CALL_DIR_ORIGINATE) | CALL_DIR_ANSWER; + break; + } + } + dbug(1,dprintf("call_dir=%04x", plci->call_dir)); + + + if ((GET_WORD(bp_parms[0].info) == B1_RTP) + && (plci->adapter->man_profile.private_options & (1L << PRIVATE_RTP))) + { + plci->B1_resource = add_b1_facilities (plci, 31, (word)(b1_facilities & ~B1_FACILITY_VOICE)); + adjust_b1_facilities (plci, plci->B1_resource, (word)(b1_facilities & ~B1_FACILITY_VOICE)); + cai[1] = plci->B1_resource; + cai[2] = 0; + cai[3] = 0; + cai[4] = 0; + PUT_WORD(&cai[5],plci->appl->MaxDataLength); + for (i = 0; i < bp_parms[3].length; i++) + cai[7+i] = bp_parms[3].info[1+i]; + cai[0] = 6 + bp_parms[3].length; + add_p(plci, CAI, cai); + return 0; + } + + + if ((GET_WORD(bp_parms[0].info) == B1_PIAFS) + && (plci->adapter->man_profile.private_options & (1L << PRIVATE_PIAFS))) + { + plci->B1_resource = add_b1_facilities (plci, 35/* PIAFS HARDWARE FACILITY */, (word)(b1_facilities & ~B1_FACILITY_VOICE)); + adjust_b1_facilities (plci, plci->B1_resource, (word)(b1_facilities & ~B1_FACILITY_VOICE)); + cai[1] = plci->B1_resource; + cai[2] = 0; + cai[3] = 0; + cai[4] = 0; + PUT_WORD(&cai[5],plci->appl->MaxDataLength); + cai[0] = 6; + add_p(plci, CAI, cai); + return 0; + } + + + if ((GET_WORD(bp_parms[0].info) >= 32) + || (!((1L << GET_WORD(bp_parms[0].info)) & plci->adapter->profile.B1_Protocols) + && ((GET_WORD(bp_parms[0].info) != 3) + || !((1L << B1_HDLC) & plci->adapter->profile.B1_Protocols) + || ((bp_parms[3].length != 0) && (GET_WORD(&bp_parms[3].info[1]) != 0) && (GET_WORD(&bp_parms[3].info[1]) != 56000))))) + { + return _B1_NOT_SUPPORTED; + } + plci->B1_resource = add_b1_facilities (plci, resource[GET_WORD(bp_parms[0].info)], + (word)(b1_facilities & ~B1_FACILITY_VOICE)); + adjust_b1_facilities (plci, plci->B1_resource, (word)(b1_facilities & ~B1_FACILITY_VOICE)); + cai[0] = 6; + cai[1] = plci->B1_resource; + for (i=2;i<sizeof(cai);i++) cai[i] = 0; + + if ((GET_WORD(bp_parms[0].info) == B1_MODEM_ALL_NEGOTIATE) + || (GET_WORD(bp_parms[0].info) == B1_MODEM_ASYNC) + || (GET_WORD(bp_parms[0].info) == B1_MODEM_SYNC_HDLC)) + { /* B1 - modem */ + for (i=0;i<7;i++) mdm_cfg[i].length = 0; + + if (bp_parms[3].length) + { + if(api_parse(&bp_parms[3].info[1],(word)bp_parms[3].length,"wwwwww", mdm_cfg)) + { + return (_WRONG_MESSAGE_FORMAT); + } + + cai[2] = 0; /* Bit rate for adaptation */ + + dbug(1,dprintf("MDM Max Bit Rate:<%d>", GET_WORD(mdm_cfg[0].info))); + + PUT_WORD (&cai[13], 0); /* Min Tx speed */ + PUT_WORD (&cai[15], GET_WORD(mdm_cfg[0].info)); /* Max Tx speed */ + PUT_WORD (&cai[17], 0); /* Min Rx speed */ + PUT_WORD (&cai[19], GET_WORD(mdm_cfg[0].info)); /* Max Rx speed */ + + cai[3] = 0; /* Async framing parameters */ + switch (GET_WORD (mdm_cfg[2].info)) + { /* Parity */ + case 1: /* odd parity */ + cai[3] |= (DSP_CAI_ASYNC_PARITY_ENABLE | DSP_CAI_ASYNC_PARITY_ODD); + dbug(1,dprintf("MDM: odd parity")); + break; + + case 2: /* even parity */ + cai[3] |= (DSP_CAI_ASYNC_PARITY_ENABLE | DSP_CAI_ASYNC_PARITY_EVEN); + dbug(1,dprintf("MDM: even parity")); + break; + + default: + dbug(1,dprintf("MDM: no parity")); + break; + } + + switch (GET_WORD (mdm_cfg[3].info)) + { /* stop bits */ + case 1: /* 2 stop bits */ + cai[3] |= DSP_CAI_ASYNC_TWO_STOP_BITS; + dbug(1,dprintf("MDM: 2 stop bits")); + break; + + default: + dbug(1,dprintf("MDM: 1 stop bit")); + break; + } + + switch (GET_WORD (mdm_cfg[1].info)) + { /* char length */ + case 5: + cai[3] |= DSP_CAI_ASYNC_CHAR_LENGTH_5; + dbug(1,dprintf("MDM: 5 bits")); + break; + + case 6: + cai[3] |= DSP_CAI_ASYNC_CHAR_LENGTH_6; + dbug(1,dprintf("MDM: 6 bits")); + break; + + case 7: + cai[3] |= DSP_CAI_ASYNC_CHAR_LENGTH_7; + dbug(1,dprintf("MDM: 7 bits")); + break; + + default: + dbug(1,dprintf("MDM: 8 bits")); + break; + } + + cai[7] = 0; /* Line taking options */ + cai[8] = 0; /* Modulation negotiation options */ + cai[9] = 0; /* Modulation options */ + + if (((plci->call_dir & CALL_DIR_ORIGINATE) != 0) ^ ((plci->call_dir & CALL_DIR_OUT) != 0)) + { + cai[9] |= DSP_CAI_MODEM_REVERSE_DIRECTION; + dbug(1, dprintf("MDM: Reverse direction")); + } + + if (GET_WORD (mdm_cfg[4].info) & MDM_CAPI_DISABLE_RETRAIN) + { + cai[9] |= DSP_CAI_MODEM_DISABLE_RETRAIN; + dbug(1, dprintf("MDM: Disable retrain")); + } + + if (GET_WORD (mdm_cfg[4].info) & MDM_CAPI_DISABLE_RING_TONE) + { + cai[7] |= DSP_CAI_MODEM_DISABLE_CALLING_TONE | DSP_CAI_MODEM_DISABLE_ANSWER_TONE; + dbug(1, dprintf("MDM: Disable ring tone")); + } + + if (GET_WORD (mdm_cfg[4].info) & MDM_CAPI_GUARD_1800) + { + cai[8] |= DSP_CAI_MODEM_GUARD_TONE_1800HZ; + dbug(1, dprintf("MDM: 1800 guard tone")); + } + else if (GET_WORD (mdm_cfg[4].info) & MDM_CAPI_GUARD_550 ) + { + cai[8] |= DSP_CAI_MODEM_GUARD_TONE_550HZ; + dbug(1, dprintf("MDM: 550 guard tone")); + } + + if ((GET_WORD (mdm_cfg[5].info) & 0x00ff) == MDM_CAPI_NEG_V100) + { + cai[8] |= DSP_CAI_MODEM_NEGOTIATE_V100; + dbug(1, dprintf("MDM: V100")); + } + else if ((GET_WORD (mdm_cfg[5].info) & 0x00ff) == MDM_CAPI_NEG_MOD_CLASS) + { + cai[8] |= DSP_CAI_MODEM_NEGOTIATE_IN_CLASS; + dbug(1, dprintf("MDM: IN CLASS")); + } + else if ((GET_WORD (mdm_cfg[5].info) & 0x00ff) == MDM_CAPI_NEG_DISABLED) + { + cai[8] |= DSP_CAI_MODEM_NEGOTIATE_DISABLED; + dbug(1, dprintf("MDM: DISABLED")); + } + cai[0] = 20; + + if ((plci->adapter->man_profile.private_options & (1L << PRIVATE_V18)) + && (GET_WORD(mdm_cfg[5].info) & 0x8000)) /* Private V.18 enable */ + { + plci->requested_options |= 1L << PRIVATE_V18; + } + if (GET_WORD(mdm_cfg[5].info) & 0x4000) /* Private VOWN enable */ + plci->requested_options |= 1L << PRIVATE_VOWN; + + if ((plci->requested_options_conn | plci->requested_options | plci->adapter->requested_options_table[plci->appl->Id-1]) + & ((1L << PRIVATE_V18) | (1L << PRIVATE_VOWN))) + { + if (!api_parse(&bp_parms[3].info[1],(word)bp_parms[3].length,"wwwwwws", mdm_cfg)) + { + i = 27; + if (mdm_cfg[6].length >= 4) + { + d = GET_DWORD(&mdm_cfg[6].info[1]); + cai[7] |= (byte) d; /* line taking options */ + cai[9] |= (byte)(d >> 8); /* modulation options */ + cai[++i] = (byte)(d >> 16); /* vown modulation options */ + cai[++i] = (byte)(d >> 24); + if (mdm_cfg[6].length >= 8) + { + d = GET_DWORD(&mdm_cfg[6].info[5]); + cai[10] |= (byte) d; /* disabled modulations mask */ + cai[11] |= (byte)(d >> 8); + if (mdm_cfg[6].length >= 12) + { + d = GET_DWORD(&mdm_cfg[6].info[9]); + cai[12] = (byte) d; /* enabled modulations mask */ + cai[++i] = (byte)(d >> 8); /* vown enabled modulations */ + cai[++i] = (byte)(d >> 16); + cai[++i] = (byte)(d >> 24); + cai[++i] = 0; + if (mdm_cfg[6].length >= 14) + { + w = GET_WORD(&mdm_cfg[6].info[13]); + if (w != 0) + PUT_WORD(&cai[13], w); /* min tx speed */ + if (mdm_cfg[6].length >= 16) + { + w = GET_WORD(&mdm_cfg[6].info[15]); + if (w != 0) + PUT_WORD(&cai[15], w); /* max tx speed */ + if (mdm_cfg[6].length >= 18) + { + w = GET_WORD(&mdm_cfg[6].info[17]); + if (w != 0) + PUT_WORD(&cai[17], w); /* min rx speed */ + if (mdm_cfg[6].length >= 20) + { + w = GET_WORD(&mdm_cfg[6].info[19]); + if (w != 0) + PUT_WORD(&cai[19], w); /* max rx speed */ + if (mdm_cfg[6].length >= 22) + { + w = GET_WORD(&mdm_cfg[6].info[21]); + cai[23] = (byte)(-((short) w)); /* transmit level */ + if (mdm_cfg[6].length >= 24) + { + w = GET_WORD(&mdm_cfg[6].info[23]); + cai[22] |= (byte) w; /* info options mask */ + cai[21] |= (byte)(w >> 8); /* disabled symbol rates */ + } + } + } + } + } + } + } + } + } + cai[27] = i - 27; + i++; + if (!api_parse(&bp_parms[3].info[1],(word)bp_parms[3].length,"wwwwwwss", mdm_cfg)) + { + if (!api_parse(&mdm_cfg[7].info[1],(word)mdm_cfg[7].length,"sss", mdm_cfg_v18)) + { + for (n = 0; n < 3; n++) + { + cai[i] = (byte)(mdm_cfg_v18[n].length); + for (j = 1; j < ((word)(cai[i] + 1)); j++) + cai[i+j] = mdm_cfg_v18[n].info[j]; + i += cai[i] + 1; + } + } + } + cai[0] = (byte)(i - 1); + } + } + + } + } + if(GET_WORD(bp_parms[0].info)==2 || /* V.110 async */ + GET_WORD(bp_parms[0].info)==3 ) /* V.110 sync */ + { + if(bp_parms[3].length){ + dbug(1,dprintf("V.110,%d",GET_WORD(&bp_parms[3].info[1]))); + switch(GET_WORD(&bp_parms[3].info[1])){ /* Rate */ + case 0: + case 56000: + if(GET_WORD(bp_parms[0].info)==3){ /* V.110 sync 56k */ + dbug(1,dprintf("56k sync HSCX")); + cai[1] = 8; + cai[2] = 0; + cai[3] = 0; + } + else if(GET_WORD(bp_parms[0].info)==2){ + dbug(1,dprintf("56k async DSP")); + cai[2] = 9; + } + break; + case 50: cai[2] = 1; break; + case 75: cai[2] = 1; break; + case 110: cai[2] = 1; break; + case 150: cai[2] = 1; break; + case 200: cai[2] = 1; break; + case 300: cai[2] = 1; break; + case 600: cai[2] = 1; break; + case 1200: cai[2] = 2; break; + case 2400: cai[2] = 3; break; + case 4800: cai[2] = 4; break; + case 7200: cai[2] = 10; break; + case 9600: cai[2] = 5; break; + case 12000: cai[2] = 13; break; + case 24000: cai[2] = 0; break; + case 14400: cai[2] = 11; break; + case 19200: cai[2] = 6; break; + case 28800: cai[2] = 12; break; + case 38400: cai[2] = 7; break; + case 48000: cai[2] = 8; break; + case 76: cai[2] = 15; break; /* 75/1200 */ + case 1201: cai[2] = 14; break; /* 1200/75 */ + case 56001: cai[2] = 9; break; /* V.110 56000 */ + + default: + return _B1_PARM_NOT_SUPPORTED; + } + cai[3] = 0; + if (cai[1] == 13) /* v.110 async */ + { + if (bp_parms[3].length >= 8) + { + switch (GET_WORD (&bp_parms[3].info[3])) + { /* char length */ + case 5: + cai[3] |= DSP_CAI_ASYNC_CHAR_LENGTH_5; + break; + case 6: + cai[3] |= DSP_CAI_ASYNC_CHAR_LENGTH_6; + break; + case 7: + cai[3] |= DSP_CAI_ASYNC_CHAR_LENGTH_7; + break; + } + switch (GET_WORD (&bp_parms[3].info[5])) + { /* Parity */ + case 1: /* odd parity */ + cai[3] |= (DSP_CAI_ASYNC_PARITY_ENABLE | DSP_CAI_ASYNC_PARITY_ODD); + break; + case 2: /* even parity */ + cai[3] |= (DSP_CAI_ASYNC_PARITY_ENABLE | DSP_CAI_ASYNC_PARITY_EVEN); + break; + } + switch (GET_WORD (&bp_parms[3].info[7])) + { /* stop bits */ + case 1: /* 2 stop bits */ + cai[3] |= DSP_CAI_ASYNC_TWO_STOP_BITS; + break; + } + } + } + } + else if(cai[1]==8 || GET_WORD(bp_parms[0].info)==3 ){ + dbug(1,dprintf("V.110 default 56k sync")); + cai[1] = 8; + cai[2] = 0; + cai[3] = 0; + } + else { + dbug(1,dprintf("V.110 default 9600 async")); + cai[2] = 5; + } + } + PUT_WORD(&cai[5],plci->appl->MaxDataLength); + dbug(1,dprintf("CAI[%d]=%x,%x,%x,%x,%x,%x", cai[0], cai[1], cai[2], cai[3], cai[4], cai[5], cai[6])); +/* HexDump ("CAI", sizeof(cai), &cai[0]); */ + + add_p(plci, CAI, cai); + return 0; +} + +/*------------------------------------------------------------------*/ +/* put parameter for b2 and B3 protocol in the parameter buffer */ +/*------------------------------------------------------------------*/ + +word add_b23(PLCI * plci, API_PARSE * bp) +{ + word i, fax_control_bits; + byte pos, len; + byte SAPI = 0x40; /* default SAPI 16 for x.31 */ + API_PARSE bp_parms[8]; + API_PARSE * b1_config; + API_PARSE * b2_config; + API_PARSE b2_config_parms[8]; + API_PARSE * b3_config; + API_PARSE b3_config_parms[6]; + API_PARSE global_config[2]; + + static byte llc[3] = {2,0,0}; + static byte dlc[20] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + static byte nlc[256]; + static byte lli[12] = {1,1}; + + const byte llc2_out[] = {1,2,4,6,2,0,0,0, X75_V42BIS,V120_L2,V120_V42BIS,V120_L2,6}; + const byte llc2_in[] = {1,3,4,6,3,0,0,0, X75_V42BIS,V120_L2,V120_V42BIS,V120_L2,6}; + + const byte llc3[] = {4,3,2,2,6,6,0}; + const byte header[] = {0,2,3,3,0,0,0}; + + for(i=0;i<8;i++) bp_parms[i].length = 0; + for(i=0;i<6;i++) b2_config_parms[i].length = 0; + for(i=0;i<5;i++) b3_config_parms[i].length = 0; + + lli[0] = 1; + lli[1] = 1; + if (plci->adapter->manufacturer_features & MANUFACTURER_FEATURE_XONOFF_FLOW_CONTROL) + lli[1] |= 2; + if (plci->adapter->manufacturer_features & MANUFACTURER_FEATURE_OOB_CHANNEL) + lli[1] |= 4; + + if ((lli[1] & 0x02) && (diva_xdi_extended_features & DIVA_CAPI_USE_CMA)) { + lli[1] |= 0x10; + if (plci->rx_dma_descriptor <= 0) { + plci->rx_dma_descriptor=diva_get_dma_descriptor(plci,&plci->rx_dma_magic); + if (plci->rx_dma_descriptor >= 0) + plci->rx_dma_descriptor++; + } + if (plci->rx_dma_descriptor > 0) { + lli[0] = 6; + lli[1] |= 0x40; + lli[2] = (byte)(plci->rx_dma_descriptor - 1); + lli[3] = (byte)plci->rx_dma_magic; + lli[4] = (byte)(plci->rx_dma_magic >> 8); + lli[5] = (byte)(plci->rx_dma_magic >> 16); + lli[6] = (byte)(plci->rx_dma_magic >> 24); + } + } + + if (DIVA_CAPI_SUPPORTS_NO_CANCEL(plci->adapter)) { + lli[1] |= 0x20; + } + + dbug(1,dprintf("add_b23")); + api_save_msg(bp, "s", &plci->B_protocol); + + if(!bp->length && plci->tel) + { + plci->adv_nl = TRUE; + dbug(1,dprintf("Default adv.Nl")); + add_p(plci,LLI,lli); + plci->B2_prot = 1 /*XPARENT*/; + plci->B3_prot = 0 /*XPARENT*/; + llc[1] = 2; + llc[2] = 4; + add_p(plci, LLC, llc); + dlc[0] = 2; + PUT_WORD(&dlc[1],plci->appl->MaxDataLength); + add_p(plci, DLC, dlc); + return 0; + } + + if(!bp->length) /*default*/ + { + dbug(1,dprintf("ret default")); + add_p(plci,LLI,lli); + plci->B2_prot = 0 /*X.75 */; + plci->B3_prot = 0 /*XPARENT*/; + llc[1] = 1; + llc[2] = 4; + add_p(plci, LLC, llc); + dlc[0] = 2; + PUT_WORD(&dlc[1],plci->appl->MaxDataLength); + add_p(plci, DLC, dlc); + return 0; + } + dbug(1,dprintf("b_prot_len=%d",(word)bp->length)); + if((word)bp->length > 256) return _WRONG_MESSAGE_FORMAT; + + if(api_parse(&bp->info[1], (word)bp->length, "wwwsssb", bp_parms)) + { + bp_parms[6].length = 0; + if(api_parse(&bp->info[1], (word)bp->length, "wwwsss", bp_parms)) + { + dbug(1,dprintf("b-form.!")); + return _WRONG_MESSAGE_FORMAT; + } + } + else if (api_parse(&bp->info[1], (word)bp->length, "wwwssss", bp_parms)) + { + dbug(1,dprintf("b-form.!")); + return _WRONG_MESSAGE_FORMAT; + } + + if(plci->tel==ADV_VOICE) /* transparent B on advanced voice */ + { + if(GET_WORD(bp_parms[1].info)!=1 + || GET_WORD(bp_parms[2].info)!=0) return _B2_NOT_SUPPORTED; + plci->adv_nl = TRUE; + } + else if(plci->tel) return _B2_NOT_SUPPORTED; + + + if ((GET_WORD(bp_parms[1].info) == B2_RTP) + && (GET_WORD(bp_parms[2].info) == B3_RTP) + && (plci->adapter->man_profile.private_options & (1L << PRIVATE_RTP))) + { + add_p(plci,LLI,lli); + plci->B2_prot = (byte) GET_WORD(bp_parms[1].info); + plci->B3_prot = (byte) GET_WORD(bp_parms[2].info); + llc[1] = (plci->call_dir & (CALL_DIR_ORIGINATE | CALL_DIR_FORCE_OUTG_NL)) ? 14 : 13; + llc[2] = 4; + add_p(plci, LLC, llc); + dlc[0] = 2; + PUT_WORD(&dlc[1],plci->appl->MaxDataLength); + dlc[3] = 3; /* Addr A */ + dlc[4] = 1; /* Addr B */ + dlc[5] = 7; /* modulo mode */ + dlc[6] = 7; /* window size */ + dlc[7] = 0; /* XID len Lo */ + dlc[8] = 0; /* XID len Hi */ + for (i = 0; i < bp_parms[4].length; i++) + dlc[9+i] = bp_parms[4].info[1+i]; + dlc[0] = (byte)(8 + bp_parms[4].length); + add_p(plci, DLC, dlc); + for (i = 0; i < bp_parms[5].length; i++) + nlc[1+i] = bp_parms[5].info[1+i]; + nlc[0] = (byte)(bp_parms[5].length); + add_p(plci, NLC, nlc); + return 0; + } + + + + if ((GET_WORD(bp_parms[1].info) >= 32) + || (!((1L << GET_WORD(bp_parms[1].info)) & plci->adapter->profile.B2_Protocols) + && ((GET_WORD(bp_parms[1].info) != B2_PIAFS) + || !(plci->adapter->man_profile.private_options & (1L << PRIVATE_PIAFS))))) + + { + return _B2_NOT_SUPPORTED; + } + if ((GET_WORD(bp_parms[2].info) >= 32) + || !((1L << GET_WORD(bp_parms[2].info)) & plci->adapter->profile.B3_Protocols)) + { + return _B3_NOT_SUPPORTED; + } + if ((GET_WORD(bp_parms[1].info) != B2_SDLC) + && ((GET_WORD(bp_parms[0].info) == B1_MODEM_ALL_NEGOTIATE) + || (GET_WORD(bp_parms[0].info) == B1_MODEM_ASYNC) + || (GET_WORD(bp_parms[0].info) == B1_MODEM_SYNC_HDLC))) + { + return (add_modem_b23 (plci, bp_parms)); + } + + add_p(plci,LLI,lli); + + plci->B2_prot = (byte) GET_WORD(bp_parms[1].info); + plci->B3_prot = (byte) GET_WORD(bp_parms[2].info); + if(plci->B2_prot==12) SAPI = 0; /* default SAPI D-channel */ + + if(bp_parms[6].length) + { + if(api_parse(&bp_parms[6].info[1], (word)bp_parms[6].length, "w", global_config)) + { + return _WRONG_MESSAGE_FORMAT; + } + switch(GET_WORD(global_config[0].info)) + { + case 1: + plci->call_dir = (plci->call_dir & ~CALL_DIR_ANSWER) | CALL_DIR_ORIGINATE; + break; + case 2: + plci->call_dir = (plci->call_dir & ~CALL_DIR_ORIGINATE) | CALL_DIR_ANSWER; + break; + } + } + dbug(1,dprintf("call_dir=%04x", plci->call_dir)); + + + if (plci->B2_prot == B2_PIAFS) + llc[1] = PIAFS_CRC; + else +/* IMPLEMENT_PIAFS */ + { + llc[1] = (plci->call_dir & (CALL_DIR_ORIGINATE | CALL_DIR_FORCE_OUTG_NL)) ? + llc2_out[GET_WORD(bp_parms[1].info)] : llc2_in[GET_WORD(bp_parms[1].info)]; + } + llc[2] = llc3[GET_WORD(bp_parms[2].info)]; + + add_p(plci, LLC, llc); + + dlc[0] = 2; + PUT_WORD(&dlc[1], plci->appl->MaxDataLength + + header[GET_WORD(bp_parms[2].info)]); + + b1_config = &bp_parms[3]; + nlc[0] = 0; + if(plci->B3_prot == 4 + || plci->B3_prot == 5) + { + for (i=0;i<sizeof(T30_INFO);i++) nlc[i] = 0; + nlc[0] = sizeof(T30_INFO); + if (plci->adapter->manufacturer_features & MANUFACTURER_FEATURE_FAX_PAPER_FORMATS) + ((T30_INFO *)&nlc[1])->operating_mode = T30_OPERATING_MODE_CAPI; + ((T30_INFO *)&nlc[1])->rate_div_2400 = 0xff; + if(b1_config->length>=2) + { + ((T30_INFO *)&nlc[1])->rate_div_2400 = (byte)(GET_WORD(&b1_config->info[1])/2400); + } + } + b2_config = &bp_parms[4]; + + + if (llc[1] == PIAFS_CRC) + { + if (plci->B3_prot != B3_TRANSPARENT) + { + return _B_STACK_NOT_SUPPORTED; + } + if(b2_config->length && api_parse(&b2_config->info[1], (word)b2_config->length, "bwww", b2_config_parms)) { + return _WRONG_MESSAGE_FORMAT; + } + PUT_WORD(&dlc[1],plci->appl->MaxDataLength); + dlc[3] = 0; /* Addr A */ + dlc[4] = 0; /* Addr B */ + dlc[5] = 0; /* modulo mode */ + dlc[6] = 0; /* window size */ + if (b2_config->length >= 7){ + dlc[ 7] = 7; + dlc[ 8] = 0; + dlc[ 9] = b2_config_parms[0].info[0]; /* PIAFS protocol Speed configuration */ + dlc[10] = b2_config_parms[1].info[0]; /* V.42bis P0 */ + dlc[11] = b2_config_parms[1].info[1]; /* V.42bis P0 */ + dlc[12] = b2_config_parms[2].info[0]; /* V.42bis P1 */ + dlc[13] = b2_config_parms[2].info[1]; /* V.42bis P1 */ + dlc[14] = b2_config_parms[3].info[0]; /* V.42bis P2 */ + dlc[15] = b2_config_parms[3].info[1]; /* V.42bis P2 */ + dlc[ 0] = 15; + if(b2_config->length >= 8) { /* PIAFS control abilities */ + dlc[ 7] = 10; + dlc[16] = 2; /* Length of PIAFS extention */ + dlc[17] = PIAFS_UDATA_ABILITIES; /* control (UDATA) ability */ + dlc[18] = b2_config_parms[4].info[0]; /* value */ + dlc[ 0] = 18; + } + } + else /* default values, 64K, variable, no compression */ + { + dlc[ 7] = 7; + dlc[ 8] = 0; + dlc[ 9] = 0x03; /* PIAFS protocol Speed configuration */ + dlc[10] = 0x03; /* V.42bis P0 */ + dlc[11] = 0; /* V.42bis P0 */ + dlc[12] = 0; /* V.42bis P1 */ + dlc[13] = 0; /* V.42bis P1 */ + dlc[14] = 0; /* V.42bis P2 */ + dlc[15] = 0; /* V.42bis P2 */ + dlc[ 0] = 15; + } + add_p(plci, DLC, dlc); + } + else + + if ((llc[1] == V120_L2) || (llc[1] == V120_V42BIS)) + { + if (plci->B3_prot != B3_TRANSPARENT) + return _B_STACK_NOT_SUPPORTED; + + dlc[0] = 6; + PUT_WORD (&dlc[1], GET_WORD (&dlc[1]) + 2); + dlc[3] = 0x08; + dlc[4] = 0x01; + dlc[5] = 127; + dlc[6] = 7; + if (b2_config->length != 0) + { + if((llc[1]==V120_V42BIS) && api_parse(&b2_config->info[1], (word)b2_config->length, "bbbbwww", b2_config_parms)) { + return _WRONG_MESSAGE_FORMAT; + } + dlc[3] = (byte)((b2_config->info[2] << 3) | ((b2_config->info[1] >> 5) & 0x04)); + dlc[4] = (byte)((b2_config->info[1] << 1) | 0x01); + if (b2_config->info[3] != 128) + { + dbug(1,dprintf("1D-dlc= %x %x %x %x %x", dlc[0], dlc[1], dlc[2], dlc[3], dlc[4])); + return _B2_PARM_NOT_SUPPORTED; + } + dlc[5] = (byte)(b2_config->info[3] - 1); + dlc[6] = b2_config->info[4]; + if(llc[1]==V120_V42BIS){ + if (b2_config->length >= 10){ + dlc[ 7] = 6; + dlc[ 8] = 0; + dlc[ 9] = b2_config_parms[4].info[0]; + dlc[10] = b2_config_parms[4].info[1]; + dlc[11] = b2_config_parms[5].info[0]; + dlc[12] = b2_config_parms[5].info[1]; + dlc[13] = b2_config_parms[6].info[0]; + dlc[14] = b2_config_parms[6].info[1]; + dlc[ 0] = 14; + dbug(1,dprintf("b2_config_parms[4].info[0] [1]: %x %x", b2_config_parms[4].info[0], b2_config_parms[4].info[1])); + dbug(1,dprintf("b2_config_parms[5].info[0] [1]: %x %x", b2_config_parms[5].info[0], b2_config_parms[5].info[1])); + dbug(1,dprintf("b2_config_parms[6].info[0] [1]: %x %x", b2_config_parms[6].info[0], b2_config_parms[6].info[1])); + } + else { + dlc[ 6] = 14; + } + } + } + } + else + { + if(b2_config->length) + { + dbug(1,dprintf("B2-Config")); + if(llc[1]==X75_V42BIS){ + if(api_parse(&b2_config->info[1], (word)b2_config->length, "bbbbwww", b2_config_parms)) + { + return _WRONG_MESSAGE_FORMAT; + } + } + else { + if(api_parse(&b2_config->info[1], (word)b2_config->length, "bbbbs", b2_config_parms)) + { + return _WRONG_MESSAGE_FORMAT; + } + } + /* if B2 Protocol is LAPD, b2_config structure is different */ + if(llc[1]==6) + { + dlc[0] = 4; + if(b2_config->length>=1) dlc[2] = b2_config->info[1]; /* TEI */ + else dlc[2] = 0x01; + if( (b2_config->length>=2) && (plci->B2_prot==12) ) + { + SAPI = b2_config->info[2]; /* SAPI */ + } + dlc[1] = SAPI; + if( (b2_config->length>=3) && (b2_config->info[3]==128) ) + { + dlc[3] = 127; /* Mode */ + } + else + { + dlc[3] = 7; /* Mode */ + } + + if(b2_config->length>=4) dlc[4] = b2_config->info[4]; /* Window */ + else dlc[4] = 1; + dbug(1,dprintf("D-dlc[%d]=%x,%x,%x,%x", dlc[0], dlc[1], dlc[2], dlc[3], dlc[4])); + if(b2_config->length>5) return _B2_PARM_NOT_SUPPORTED; + } + else + { + dlc[0] = (byte)(b2_config_parms[4].length+6); + dlc[3] = b2_config->info[1]; + dlc[4] = b2_config->info[2]; + if(b2_config->info[3]!=8 && b2_config->info[3]!=128){ + dbug(1,dprintf("1D-dlc= %x %x %x %x %x", dlc[0], dlc[1], dlc[2], dlc[3], dlc[4])); + return _B2_PARM_NOT_SUPPORTED; + } + + dlc[5] = (byte)(b2_config->info[3]-1); + dlc[6] = b2_config->info[4]; + if(dlc[6]>dlc[5]){ + dbug(1,dprintf("2D-dlc= %x %x %x %x %x %x %x", dlc[0], dlc[1], dlc[2], dlc[3], dlc[4], dlc[5], dlc[6])); + return _B2_PARM_NOT_SUPPORTED; + } + + if(llc[1]==X75_V42BIS) { + if (b2_config->length >= 10){ + dlc[ 7] = 6; + dlc[ 8] = 0; + dlc[ 9] = b2_config_parms[4].info[0]; + dlc[10] = b2_config_parms[4].info[1]; + dlc[11] = b2_config_parms[5].info[0]; + dlc[12] = b2_config_parms[5].info[1]; + dlc[13] = b2_config_parms[6].info[0]; + dlc[14] = b2_config_parms[6].info[1]; + dlc[ 0] = 14; + dbug(1,dprintf("b2_config_parms[4].info[0] [1]: %x %x", b2_config_parms[4].info[0], b2_config_parms[4].info[1])); + dbug(1,dprintf("b2_config_parms[5].info[0] [1]: %x %x", b2_config_parms[5].info[0], b2_config_parms[5].info[1])); + dbug(1,dprintf("b2_config_parms[6].info[0] [1]: %x %x", b2_config_parms[6].info[0], b2_config_parms[6].info[1])); + } + else { + dlc[ 6] = 14; + } + + } + else { + PUT_WORD(&dlc[7], (word)b2_config_parms[4].length); + for(i=0; i<b2_config_parms[4].length; i++) + dlc[11+i] = b2_config_parms[4].info[1+i]; + } + } + } + } + add_p(plci, DLC, dlc); + + b3_config = &bp_parms[5]; + if(b3_config->length) + { + if(plci->B3_prot == 4 + || plci->B3_prot == 5) + { + if(api_parse(&b3_config->info[1], (word)b3_config->length, "wwss", b3_config_parms)) + { + return _WRONG_MESSAGE_FORMAT; + } + i = GET_WORD((byte *)(b3_config_parms[0].info)); + ((T30_INFO *)&nlc[1])->resolution = (byte)(((i & 0x0001) || + ((plci->B3_prot == 4) && (((byte)(GET_WORD((byte *)b3_config_parms[1].info))) != 5))) ? T30_RESOLUTION_R8_0770_OR_200 : 0); + ((T30_INFO *)&nlc[1])->data_format = (byte)(GET_WORD((byte *)b3_config_parms[1].info)); + fax_control_bits = T30_CONTROL_BIT_ALL_FEATURES; + if ((((T30_INFO *)&nlc[1])->rate_div_2400 != 0) && (((T30_INFO *)&nlc[1])->rate_div_2400 <= 6)) + fax_control_bits &= ~T30_CONTROL_BIT_ENABLE_V34FAX; + if (plci->adapter->manufacturer_features & MANUFACTURER_FEATURE_FAX_PAPER_FORMATS) + { + + if ((plci->requested_options_conn | plci->requested_options | plci->adapter->requested_options_table[plci->appl->Id-1]) + & (1L << PRIVATE_FAX_PAPER_FORMATS)) + { + ((T30_INFO *)&nlc[1])->resolution |= T30_RESOLUTION_R8_1540 | + T30_RESOLUTION_R16_1540_OR_400 | T30_RESOLUTION_300_300 | + T30_RESOLUTION_INCH_BASED | T30_RESOLUTION_METRIC_BASED; + } + + ((T30_INFO *)&nlc[1])->recording_properties = + T30_RECORDING_WIDTH_ISO_A3 | + (T30_RECORDING_LENGTH_UNLIMITED << 2) | + (T30_MIN_SCANLINE_TIME_00_00_00 << 4); + } + if(plci->B3_prot == 5) + { + if (i & 0x0002) /* Accept incoming fax-polling requests */ + fax_control_bits |= T30_CONTROL_BIT_ACCEPT_POLLING; + if (i & 0x2000) /* Do not use MR compression */ + fax_control_bits &= ~T30_CONTROL_BIT_ENABLE_2D_CODING; + if (i & 0x4000) /* Do not use MMR compression */ + fax_control_bits &= ~T30_CONTROL_BIT_ENABLE_T6_CODING; + if (i & 0x8000) /* Do not use ECM */ + fax_control_bits &= ~T30_CONTROL_BIT_ENABLE_ECM; + if (plci->fax_connect_info_length != 0) + { + ((T30_INFO *)&nlc[1])->resolution = ((T30_INFO *)plci->fax_connect_info_buffer)->resolution; + ((T30_INFO *)&nlc[1])->data_format = ((T30_INFO *)plci->fax_connect_info_buffer)->data_format; + ((T30_INFO *)&nlc[1])->recording_properties = ((T30_INFO *)plci->fax_connect_info_buffer)->recording_properties; + fax_control_bits |= GET_WORD(&((T30_INFO *)plci->fax_connect_info_buffer)->control_bits_low) & + (T30_CONTROL_BIT_REQUEST_POLLING | T30_CONTROL_BIT_MORE_DOCUMENTS); + } + } + /* copy station id to NLC */ + for(i=0; i<20; i++) + { + if(i<b3_config_parms[2].length) + { + ((T30_INFO *)&nlc[1])->station_id[i] = ((byte *)b3_config_parms[2].info)[1+i]; + } + else + { + ((T30_INFO *)&nlc[1])->station_id[i] = ' '; + } + } + ((T30_INFO *)&nlc[1])->station_id_len = 20; + /* copy head line to NLC */ + if(b3_config_parms[3].length) + { + + pos = (byte)(fax_head_line_time (&(((T30_INFO *)&nlc[1])->station_id[20]))); + if (pos != 0) + { + if (CAPI_MAX_DATE_TIME_LENGTH + 2 + b3_config_parms[3].length > CAPI_MAX_HEAD_LINE_SPACE) + pos = 0; + else + { + ((T30_INFO *)&nlc[1])->station_id[20 + pos++] = ' '; + ((T30_INFO *)&nlc[1])->station_id[20 + pos++] = ' '; + len = (byte)b3_config_parms[2].length; + if (len > 20) + len = 20; + if (CAPI_MAX_DATE_TIME_LENGTH + 2 + len + 2 + b3_config_parms[3].length <= CAPI_MAX_HEAD_LINE_SPACE) + { + for (i = 0; i < len; i++) + ((T30_INFO *)&nlc[1])->station_id[20 + pos++] = ((byte *)b3_config_parms[2].info)[1+i]; + ((T30_INFO *)&nlc[1])->station_id[20 + pos++] = ' '; + ((T30_INFO *)&nlc[1])->station_id[20 + pos++] = ' '; + } + } + } + + len = (byte)b3_config_parms[3].length; + if (len > CAPI_MAX_HEAD_LINE_SPACE - pos) + len = (byte)(CAPI_MAX_HEAD_LINE_SPACE - pos); + ((T30_INFO *)&nlc[1])->head_line_len = (byte)(pos + len); + nlc[0] += (byte)(pos + len); + for (i = 0; i < len; i++) + ((T30_INFO *)&nlc[1])->station_id[20 + pos++] = ((byte *)b3_config_parms[3].info)[1+i]; + } + else + ((T30_INFO *)&nlc[1])->head_line_len = 0; + + plci->nsf_control_bits = 0; + if(plci->B3_prot == 5) + { + if ((plci->adapter->man_profile.private_options & (1L << PRIVATE_FAX_SUB_SEP_PWD)) + && (GET_WORD((byte *)b3_config_parms[1].info) & 0x8000)) /* Private SUB/SEP/PWD enable */ + { + plci->requested_options |= 1L << PRIVATE_FAX_SUB_SEP_PWD; + } + if ((plci->adapter->man_profile.private_options & (1L << PRIVATE_FAX_NONSTANDARD)) + && (GET_WORD((byte *)b3_config_parms[1].info) & 0x4000)) /* Private non-standard facilities enable */ + { + plci->requested_options |= 1L << PRIVATE_FAX_NONSTANDARD; + } + if ((plci->requested_options_conn | plci->requested_options | plci->adapter->requested_options_table[plci->appl->Id-1]) + & ((1L << PRIVATE_FAX_SUB_SEP_PWD) | (1L << PRIVATE_FAX_NONSTANDARD))) + { + if ((plci->requested_options_conn | plci->requested_options | plci->adapter->requested_options_table[plci->appl->Id-1]) + & (1L << PRIVATE_FAX_SUB_SEP_PWD)) + { + fax_control_bits |= T30_CONTROL_BIT_ACCEPT_SUBADDRESS | T30_CONTROL_BIT_ACCEPT_PASSWORD; + if (fax_control_bits & T30_CONTROL_BIT_ACCEPT_POLLING) + fax_control_bits |= T30_CONTROL_BIT_ACCEPT_SEL_POLLING; + } + len = nlc[0]; + pos = ((byte)(((T30_INFO *) 0)->station_id + 20)); + if (pos < plci->fax_connect_info_length) + { + for (i = 1 + plci->fax_connect_info_buffer[pos]; i != 0; i--) + nlc[++len] = plci->fax_connect_info_buffer[pos++]; + } + else + nlc[++len] = 0; + if (pos < plci->fax_connect_info_length) + { + for (i = 1 + plci->fax_connect_info_buffer[pos]; i != 0; i--) + nlc[++len] = plci->fax_connect_info_buffer[pos++]; + } + else + nlc[++len] = 0; + if ((plci->requested_options_conn | plci->requested_options | plci->adapter->requested_options_table[plci->appl->Id-1]) + & (1L << PRIVATE_FAX_NONSTANDARD)) + { + if ((pos < plci->fax_connect_info_length) && (plci->fax_connect_info_buffer[pos] != 0)) + { + if ((plci->fax_connect_info_buffer[pos] >= 3) && (plci->fax_connect_info_buffer[pos+1] >= 2)) + plci->nsf_control_bits = GET_WORD(&plci->fax_connect_info_buffer[pos+2]); + for (i = 1 + plci->fax_connect_info_buffer[pos]; i != 0; i--) + nlc[++len] = plci->fax_connect_info_buffer[pos++]; + } + else + { + if(api_parse(&b3_config->info[1], (word)b3_config->length, "wwsss", b3_config_parms)) + { + dbug(1,dprintf("non-standard facilities info missing or wrong format")); + nlc[++len] = 0; + } + else + { + if ((b3_config_parms[4].length >= 3) && (b3_config_parms[4].info[1] >= 2)) + plci->nsf_control_bits = GET_WORD(&b3_config_parms[4].info[2]); + nlc[++len] = (byte)(b3_config_parms[4].length); + for (i = 0; i < b3_config_parms[4].length; i++) + nlc[++len] = b3_config_parms[4].info[1+i]; + } + } + } + nlc[0] = len; + if ((plci->nsf_control_bits & T30_NSF_CONTROL_BIT_ENABLE_NSF) + && (plci->nsf_control_bits & T30_NSF_CONTROL_BIT_NEGOTIATE_RESP)) + { + ((T30_INFO *)&nlc[1])->operating_mode = T30_OPERATING_MODE_CAPI_NEG; + } + } + } + + PUT_WORD(&(((T30_INFO *)&nlc[1])->control_bits_low), fax_control_bits); + len = ((byte)(((T30_INFO *) 0)->station_id + 20)); + for (i = 0; i < len; i++) + plci->fax_connect_info_buffer[i] = nlc[1+i]; + ((T30_INFO *) plci->fax_connect_info_buffer)->head_line_len = 0; + i += ((T30_INFO *)&nlc[1])->head_line_len; + while (i < nlc[0]) + plci->fax_connect_info_buffer[len++] = nlc[++i]; + plci->fax_connect_info_length = len; + } + else + { + nlc[0] = 14; + if(b3_config->length!=16) + return _B3_PARM_NOT_SUPPORTED; + for(i=0; i<12; i++) nlc[1+i] = b3_config->info[1+i]; + if(GET_WORD(&b3_config->info[13])!=8 && GET_WORD(&b3_config->info[13])!=128) + return _B3_PARM_NOT_SUPPORTED; + nlc[13] = b3_config->info[13]; + if(GET_WORD(&b3_config->info[15])>=nlc[13]) + return _B3_PARM_NOT_SUPPORTED; + nlc[14] = b3_config->info[15]; + } + } + else + { + if (plci->B3_prot == 4 + || plci->B3_prot == 5 /*T.30 - FAX*/ ) return _B3_PARM_NOT_SUPPORTED; + } + add_p(plci, NLC, nlc); + return 0; +} + +/*----------------------------------------------------------------*/ +/* make the same as add_b23, but only for the modem related */ +/* L2 and L3 B-Chan protocol. */ +/* */ +/* Enabled L2 and L3 Configurations: */ +/* If L1 == Modem all negotiation */ +/* only L2 == Modem with full negotiation is allowed */ +/* If L1 == Modem async or sync */ +/* only L2 == Transparent is allowed */ +/* L3 == Modem or L3 == Transparent are allowed */ +/* B2 Configuration for modem: */ +/* word : enable/disable compression, bitoptions */ +/* B3 Configuration for modem: */ +/* empty */ +/*----------------------------------------------------------------*/ +static word add_modem_b23 (PLCI * plci, API_PARSE* bp_parms) +{ + static byte lli[12] = {1,1}; + static byte llc[3] = {2,0,0}; + static byte dlc[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + API_PARSE mdm_config[2]; + word i; + word b2_config = 0; + + for(i=0;i<2;i++) mdm_config[i].length = 0; + for(i=0;i<sizeof(dlc);i++) dlc[i] = 0; + + if (((GET_WORD(bp_parms[0].info) == B1_MODEM_ALL_NEGOTIATE) + && (GET_WORD(bp_parms[1].info) != B2_MODEM_EC_COMPRESSION)) + || ((GET_WORD(bp_parms[0].info) != B1_MODEM_ALL_NEGOTIATE) + && (GET_WORD(bp_parms[1].info) != B2_TRANSPARENT))) + { + return (_B_STACK_NOT_SUPPORTED); + } + if ((GET_WORD(bp_parms[2].info) != B3_MODEM) + && (GET_WORD(bp_parms[2].info) != B3_TRANSPARENT)) + { + return (_B_STACK_NOT_SUPPORTED); + } + + plci->B2_prot = (byte) GET_WORD(bp_parms[1].info); + plci->B3_prot = (byte) GET_WORD(bp_parms[2].info); + + if ((GET_WORD(bp_parms[1].info) == B2_MODEM_EC_COMPRESSION) && bp_parms[4].length) + { + if (api_parse (&bp_parms[4].info[1], + (word)bp_parms[4].length, "w", + mdm_config)) + { + return (_WRONG_MESSAGE_FORMAT); + } + b2_config = GET_WORD(mdm_config[0].info); + } + + /* OK, L2 is modem */ + + lli[0] = 1; + lli[1] = 1; + if (plci->adapter->manufacturer_features & MANUFACTURER_FEATURE_XONOFF_FLOW_CONTROL) + lli[1] |= 2; + if (plci->adapter->manufacturer_features & MANUFACTURER_FEATURE_OOB_CHANNEL) + lli[1] |= 4; + + if ((lli[1] & 0x02) && (diva_xdi_extended_features & DIVA_CAPI_USE_CMA)) { + lli[1] |= 0x10; + if (plci->rx_dma_descriptor <= 0) { + plci->rx_dma_descriptor=diva_get_dma_descriptor(plci,&plci->rx_dma_magic); + if (plci->rx_dma_descriptor >= 0) + plci->rx_dma_descriptor++; + } + if (plci->rx_dma_descriptor > 0) { + lli[1] |= 0x40; + lli[0] = 6; + lli[2] = (byte)(plci->rx_dma_descriptor - 1); + lli[3] = (byte)plci->rx_dma_magic; + lli[4] = (byte)(plci->rx_dma_magic >> 8); + lli[5] = (byte)(plci->rx_dma_magic >> 16); + lli[6] = (byte)(plci->rx_dma_magic >> 24); + } + } + + if (DIVA_CAPI_SUPPORTS_NO_CANCEL(plci->adapter)) { + lli[1] |= 0x20; + } + + llc[1] = (plci->call_dir & (CALL_DIR_ORIGINATE | CALL_DIR_FORCE_OUTG_NL)) ? + /*V42*/ 10 : /*V42_IN*/ 9; + llc[2] = 4; /* pass L3 always transparent */ + add_p(plci, LLI, lli); + add_p(plci, LLC, llc); + i = 1; + PUT_WORD (&dlc[i], plci->appl->MaxDataLength); + i += 2; + if (GET_WORD(bp_parms[1].info) == B2_MODEM_EC_COMPRESSION) + { + if (bp_parms[4].length) + { + dbug(1, dprintf("MDM b2_config=%02x", b2_config)); + dlc[i++] = 3; /* Addr A */ + dlc[i++] = 1; /* Addr B */ + dlc[i++] = 7; /* modulo mode */ + dlc[i++] = 7; /* window size */ + dlc[i++] = 0; /* XID len Lo */ + dlc[i++] = 0; /* XID len Hi */ + + if (b2_config & MDM_B2_DISABLE_V42bis) + { + dlc[i] |= DLC_MODEMPROT_DISABLE_V42_V42BIS; + } + if (b2_config & MDM_B2_DISABLE_MNP) + { + dlc[i] |= DLC_MODEMPROT_DISABLE_MNP_MNP5; + } + if (b2_config & MDM_B2_DISABLE_TRANS) + { + dlc[i] |= DLC_MODEMPROT_REQUIRE_PROTOCOL; + } + if (b2_config & MDM_B2_DISABLE_V42) + { + dlc[i] |= DLC_MODEMPROT_DISABLE_V42_DETECT; + } + if (b2_config & MDM_B2_DISABLE_COMP) + { + dlc[i] |= DLC_MODEMPROT_DISABLE_COMPRESSION; + } + i++; + } + } + else + { + dlc[i++] = 3; /* Addr A */ + dlc[i++] = 1; /* Addr B */ + dlc[i++] = 7; /* modulo mode */ + dlc[i++] = 7; /* window size */ + dlc[i++] = 0; /* XID len Lo */ + dlc[i++] = 0; /* XID len Hi */ + dlc[i++] = DLC_MODEMPROT_DISABLE_V42_V42BIS | + DLC_MODEMPROT_DISABLE_MNP_MNP5 | + DLC_MODEMPROT_DISABLE_V42_DETECT | + DLC_MODEMPROT_DISABLE_COMPRESSION; + } + dlc[0] = (byte)(i - 1); +/* HexDump ("DLC", sizeof(dlc), &dlc[0]); */ + add_p(plci, DLC, dlc); + return (0); +} + + +/*------------------------------------------------------------------*/ +/* send a request for the signaling entity */ +/*------------------------------------------------------------------*/ + +void sig_req(PLCI * plci, byte req, byte Id) +{ + if(!plci) return; + if(plci->adapter->adapter_disabled) return; + dbug(1,dprintf("sig_req(%x)",req)); + if (req == REMOVE) + plci->sig_remove_id = plci->Sig.Id; + if(plci->req_in==plci->req_in_start) { + plci->req_in +=2; + plci->RBuffer[plci->req_in++] = 0; + } + PUT_WORD(&plci->RBuffer[plci->req_in_start], plci->req_in-plci->req_in_start-2); + plci->RBuffer[plci->req_in++] = Id; /* sig/nl flag */ + plci->RBuffer[plci->req_in++] = req; /* request */ + plci->RBuffer[plci->req_in++] = 0; /* channel */ + plci->req_in_start = plci->req_in; +} + +/*------------------------------------------------------------------*/ +/* send a request for the network layer entity */ +/*------------------------------------------------------------------*/ + +void nl_req_ncci(PLCI * plci, byte req, byte ncci) +{ + if(!plci) return; + if(plci->adapter->adapter_disabled) return; + dbug(1,dprintf("nl_req %02x %02x %02x", plci->Id, req, ncci)); + if (req == REMOVE) + { + plci->nl_remove_id = plci->NL.Id; + ncci_remove (plci, 0, (byte)(ncci != 0)); + ncci = 0; + } + if(plci->req_in==plci->req_in_start) { + plci->req_in +=2; + plci->RBuffer[plci->req_in++] = 0; + } + PUT_WORD(&plci->RBuffer[plci->req_in_start], plci->req_in-plci->req_in_start-2); + plci->RBuffer[plci->req_in++] = 1; /* sig/nl flag */ + plci->RBuffer[plci->req_in++] = req; /* request */ + plci->RBuffer[plci->req_in++] = plci->adapter->ncci_ch[ncci]; /* channel */ + plci->req_in_start = plci->req_in; +} + +void send_req(PLCI * plci) +{ + ENTITY * e; + word l; +/* word i; */ + + if(!plci) return; + if(plci->adapter->adapter_disabled) return; + channel_xmit_xon (plci); + + /* if nothing to do, return */ + if(plci->req_in==plci->req_out) return; + dbug(1,dprintf("send_req(in=%d,out=%d)",plci->req_in,plci->req_out)); + + if(plci->nl_req || plci->sig_req) return; + + l = GET_WORD(&plci->RBuffer[plci->req_out]); + plci->req_out += 2; + plci->XData[0].P = &plci->RBuffer[plci->req_out]; + plci->req_out += l; + if(plci->RBuffer[plci->req_out]==1) + { + e = &plci->NL; + plci->req_out++; + e->Req = plci->nl_req = plci->RBuffer[plci->req_out++]; + e->ReqCh = plci->RBuffer[plci->req_out++]; + if(!(e->Id & 0x1f)) + { + e->Id = NL_ID; + plci->RBuffer[plci->req_out-4] = CAI; + plci->RBuffer[plci->req_out-3] = 1; + plci->RBuffer[plci->req_out-2] = (plci->Sig.Id==0xff) ? 0 : plci->Sig.Id; + plci->RBuffer[plci->req_out-1] = 0; + l+=3; + plci->nl_global_req = plci->nl_req; + } + dbug(1,dprintf("%x:NLREQ(%x:%x:%x)",plci->adapter->Id,e->Id,e->Req,e->ReqCh)); + } + else + { + e = &plci->Sig; + if(plci->RBuffer[plci->req_out]) + e->Id = plci->RBuffer[plci->req_out]; + plci->req_out++; + e->Req = plci->sig_req = plci->RBuffer[plci->req_out++]; + e->ReqCh = plci->RBuffer[plci->req_out++]; + if(!(e->Id & 0x1f)) + plci->sig_global_req = plci->sig_req; + dbug(1,dprintf("%x:SIGREQ(%x:%x:%x)",plci->adapter->Id,e->Id,e->Req,e->ReqCh)); + } + plci->XData[0].PLength = l; + e->X = plci->XData; + plci->adapter->request(e); + dbug(1,dprintf("send_ok")); +} + +void send_data(PLCI * plci) +{ + DIVA_CAPI_ADAPTER * a; + DATA_B3_DESC * data; + NCCI *ncci_ptr; + word ncci; + + if (!plci->nl_req && plci->ncci_ring_list) + { + a = plci->adapter; + ncci = plci->ncci_ring_list; + do + { + ncci = a->ncci_next[ncci]; + ncci_ptr = &(a->ncci[ncci]); + if (!(a->ncci_ch[ncci] + && (a->ch_flow_control[a->ncci_ch[ncci]] & N_OK_FC_PENDING))) + { + if (ncci_ptr->data_pending) + { + if ((a->ncci_state[ncci] == CONNECTED) + || (a->ncci_state[ncci] == INC_ACT_PENDING) + || (plci->send_disc == ncci)) + { + data = &(ncci_ptr->DBuffer[ncci_ptr->data_out]); + if ((plci->B2_prot == B2_V120_ASYNC) + || (plci->B2_prot == B2_V120_ASYNC_V42BIS) + || (plci->B2_prot == B2_V120_BIT_TRANSPARENT)) + { + plci->NData[1].P = TransmitBufferGet (plci->appl, data->P); + plci->NData[1].PLength = data->Length; + if (data->Flags & 0x10) + plci->NData[0].P = v120_break_header; + else + plci->NData[0].P = v120_default_header; + plci->NData[0].PLength = 1 ; + plci->NL.XNum = 2; + plci->NL.Req = plci->nl_req = (byte)((data->Flags&0x07)<<4 |N_DATA); + } + else + { + plci->NData[0].P = TransmitBufferGet (plci->appl, data->P); + plci->NData[0].PLength = data->Length; + if (data->Flags & 0x10) + plci->NL.Req = plci->nl_req = (byte)N_UDATA; + + else if ((plci->B3_prot == B3_RTP) && (data->Flags & 0x01)) + plci->NL.Req = plci->nl_req = (byte)N_BDATA; + + else + plci->NL.Req = plci->nl_req = (byte)((data->Flags&0x07)<<4 |N_DATA); + } + plci->NL.X = plci->NData; + plci->NL.ReqCh = a->ncci_ch[ncci]; + dbug(1,dprintf("%x:DREQ(%x:%x)",a->Id,plci->NL.Id,plci->NL.Req)); + plci->data_sent = TRUE; + plci->data_sent_ptr = data->P; + a->request(&plci->NL); + } + else { + cleanup_ncci_data (plci, ncci); + } + } + else if (plci->send_disc == ncci) + { + /* dprintf("N_DISC"); */ + plci->NData[0].PLength = 0; + plci->NL.ReqCh = a->ncci_ch[ncci]; + plci->NL.Req = plci->nl_req = N_DISC; + a->request(&plci->NL); + plci->command = _DISCONNECT_B3_R; + plci->send_disc = 0; + } + } + } while (!plci->nl_req && (ncci != plci->ncci_ring_list)); + plci->ncci_ring_list = ncci; + } +} + +void listen_check(DIVA_CAPI_ADAPTER * a) +{ + word i,j; + PLCI * plci; + byte activnotifiedcalls = 0; + + dbug(1,dprintf("listen_check(%d,%d)",a->listen_active,a->max_listen)); + if (!remove_started && !a->adapter_disabled) + { + for(i=0;i<a->max_plci;i++) + { + plci = &(a->plci[i]); + if(plci->notifiedcall) activnotifiedcalls++; + } + dbug(1,dprintf("listen_check(%d)",activnotifiedcalls)); + + for(i=a->listen_active; i < ((word)(a->max_listen+activnotifiedcalls)); i++) { + if((j=get_plci(a))) { + a->listen_active++; + plci = &a->plci[j-1]; + plci->State = LISTENING; + + add_p(plci,OAD,"\x01\xfd"); + + add_p(plci,KEY,"\x04\x43\x41\x32\x30"); + + add_p(plci,CAI,"\x01\xc0"); + add_p(plci,UID,"\x06\x43\x61\x70\x69\x32\x30"); + add_p(plci,LLI,"\x01\xc4"); /* support Dummy CR FAC + MWI + SpoofNotify */ + add_p(plci,SHIFT|6,NULL); + add_p(plci,SIN,"\x02\x00\x00"); + plci->internal_command = LISTEN_SIG_ASSIGN_PEND; /* do indicate_req if OK */ + sig_req(plci,ASSIGN,DSIG_ID); + send_req(plci); + } + } + } +} + +/*------------------------------------------------------------------*/ +/* functions for all parameters sent in INDs */ +/*------------------------------------------------------------------*/ + +void IndParse(PLCI * plci, word * parms_id, byte ** parms, byte multiIEsize) +{ + word ploc; /* points to current location within packet */ + byte w; + byte wlen; + byte codeset,lock; + byte * in; + word i; + word code; + word mIEindex = 0; + ploc = 0; + codeset = 0; + lock = 0; + + in = plci->Sig.RBuffer->P; + for(i=0; i<parms_id[0]; i++) /* multiIE parms_id contains just the 1st */ + { /* element but parms array is larger */ + parms[i] = (byte *)""; + } + for(i=0; i<multiIEsize; i++) + { + parms[i] = (byte *)""; + } + + while(ploc<plci->Sig.RBuffer->length-1) { + + /* read information element id and length */ + w = in[ploc]; + + if(w & 0x80) { +/* w &=0xf0; removed, cannot detect congestion levels */ +/* upper 4 bit masked with w==SHIFT now */ + wlen = 0; + } + else { + wlen = (byte)(in[ploc+1]+1); + } + /* check if length valid (not exceeding end of packet) */ + if((ploc+wlen) > 270) return ; + if(lock & 0x80) lock &=0x7f; + else codeset = lock; + + if((w&0xf0)==SHIFT) { + codeset = in[ploc]; + if(!(codeset & 0x08)) lock = (byte)(codeset & 7); + codeset &=7; + lock |=0x80; + } + else { + if(w==ESC && wlen>=3) code = in[ploc+2] |0x800; + else code = w; + code |= (codeset<<8); + + for(i=1; i<parms_id[0]+1 && parms_id[i]!=code; i++); + + if(i<parms_id[0]+1) { + if(!multiIEsize) { /* with multiIEs use next field index, */ + mIEindex = i-1; /* with normal IEs use same index like parms_id */ + } + + parms[mIEindex] = &in[ploc+1]; + dbug(1,dprintf("mIE[%d]=0x%x",*parms[mIEindex],in[ploc])); + if(parms_id[i]==OAD + || parms_id[i]==CONN_NR + || parms_id[i]==CAD) { + if(in[ploc+2] &0x80) { + in[ploc+0] = (byte)(in[ploc+1]+1); + in[ploc+1] = (byte)(in[ploc+2] &0x7f); + in[ploc+2] = 0x80; + parms[mIEindex] = &in[ploc]; + } + } + mIEindex++; /* effects multiIEs only */ + } + } + + ploc +=(wlen+1); + } + return ; +} + +/*------------------------------------------------------------------*/ +/* try to match a cip from received BC and HLC */ +/*------------------------------------------------------------------*/ + +byte ie_compare(byte * ie1, byte * ie2) +{ + word i; + if(!ie1 || ! ie2) return FALSE; + if(!ie1[0]) return FALSE; + for(i=0;i<(word)(ie1[0]+1);i++) if(ie1[i]!=ie2[i]) return FALSE; + return TRUE; +} + +word find_cip(DIVA_CAPI_ADAPTER * a, byte * bc, byte * hlc) +{ + word i; + word j; + + for(i=9;i && !ie_compare(bc,cip_bc[i][a->u_law]);i--); + + for(j=16;j<29 && + (!ie_compare(bc,cip_bc[j][a->u_law]) || !ie_compare(hlc,cip_hlc[j])); j++); + if(j==29) return i; + return j; +} + + +static byte AddInfo(byte **add_i, + byte **fty_i, + byte *esc_chi, + byte *facility) +{ + byte i; + byte j; + byte k; + byte flen; + byte len=0; + /* facility is a nested structure */ + /* FTY can be more than once */ + + if(esc_chi[0] && !(esc_chi[esc_chi[0]])&0x7f ) + { + add_i[0] = (byte *)"\x02\x02\x00"; /* use neither b nor d channel */ + } + + else + { + add_i[0] = (byte *)""; + } + if(!fty_i[0][0]) + { + add_i[3] = (byte *)""; + } + else + { /* facility array found */ + for(i=0,j=1;i<MAX_MULTI_IE && fty_i[i][0];i++) + { + dbug(1,dprintf("AddIFac[%d]",fty_i[i][0])); + len += fty_i[i][0]; + len += 2; + flen=fty_i[i][0]; + facility[j++]=0x1c; /* copy fac IE */ + for(k=0;k<=flen;k++,j++) + { + facility[j]=fty_i[i][k]; +/* dbug(1,dprintf("%x ",facility[j])); */ + } + } + facility[0] = len; + add_i[3] = facility; + } +/* dbug(1,dprintf("FacArrLen=%d ",len)); */ + len = add_i[0][0]+add_i[1][0]+add_i[2][0]+add_i[3][0]; + len += 4; /* calculate length of all */ + return(len); +} + +/*------------------------------------------------------------------*/ +/* voice and codec features */ +/*------------------------------------------------------------------*/ + +void SetVoiceChannel(PLCI *plci, byte *chi, DIVA_CAPI_ADAPTER * a) +{ + byte voice_chi[] = "\x02\x18\x01"; + byte channel; + + channel = chi[chi[0]]&0x3; + dbug(1,dprintf("ExtDevON(Ch=0x%x)",channel)); + voice_chi[2] = (channel) ? channel : 1; + add_p(plci,FTY,"\x02\x01\x07"); /* B On, default on 1 */ + add_p(plci,ESC,voice_chi); /* Channel */ + sig_req(plci,TEL_CTRL,0); + send_req(plci); + if(a->AdvSignalPLCI) + { + adv_voice_write_coefs (a->AdvSignalPLCI, ADV_VOICE_WRITE_ACTIVATION); + } +} + +void VoiceChannelOff(PLCI *plci) +{ + dbug(1,dprintf("ExtDevOFF")); + add_p(plci,FTY,"\x02\x01\x08"); /* B Off */ + sig_req(plci,TEL_CTRL,0); + send_req(plci); + if(plci->adapter->AdvSignalPLCI) + { + adv_voice_clear_config (plci->adapter->AdvSignalPLCI); + } +} + + +word AdvCodecSupport(DIVA_CAPI_ADAPTER *a, PLCI *plci, APPL *appl, byte hook_listen) +{ + word j; + PLCI *splci; + + /* check if hardware supports handset with hook states (adv.codec) */ + /* or if just a on board codec is supported */ + /* the advanced codec plci is just for internal use */ + + /* diva Pro with on-board codec: */ + if(a->profile.Global_Options & HANDSET) + { + /* new call, but hook states are already signalled */ + if(a->AdvCodecFLAG) + { + if(a->AdvSignalAppl!=appl || a->AdvSignalPLCI) + { + dbug(1,dprintf("AdvSigPlci=0x%x",a->AdvSignalPLCI)); + return 0x2001; /* codec in use by another application */ + } + if(plci!=0) + { + a->AdvSignalPLCI = plci; + plci->tel=ADV_VOICE; + } + return 0; /* adv codec still used */ + } + if((j=get_plci(a))) + { + splci = &a->plci[j-1]; + splci->tel = CODEC_PERMANENT; + /* hook_listen indicates if a facility_req with handset/hook support */ + /* was sent. Otherwise if just a call on an external device was made */ + /* the codec will be used but the hook info will be discarded (just */ + /* the external controller is in use */ + if(hook_listen) splci->State = ADVANCED_VOICE_SIG; + else + { + splci->State = ADVANCED_VOICE_NOSIG; + if(plci) + { + plci->spoofed_msg = SPOOFING_REQUIRED; + } + /* indicate D-ch connect if */ + } /* codec is connected OK */ + if(plci!=0) + { + a->AdvSignalPLCI = plci; + plci->tel=ADV_VOICE; + } + a->AdvSignalAppl = appl; + a->AdvCodecFLAG = TRUE; + a->AdvCodecPLCI = splci; + add_p(splci,CAI,"\x01\x15"); + add_p(splci,LLI,"\x01\x00"); + add_p(splci,ESC,"\x02\x18\x00"); + add_p(splci,UID,"\x06\x43\x61\x70\x69\x32\x30"); + splci->internal_command = PERM_COD_ASSIGN; + dbug(1,dprintf("Codec Assign")); + sig_req(splci,ASSIGN,DSIG_ID); + send_req(splci); + } + else + { + return 0x2001; /* wrong state, no more plcis */ + } + } + else if(a->profile.Global_Options & ON_BOARD_CODEC) + { + if(hook_listen) return 0x300B; /* Facility not supported */ + /* no hook with SCOM */ + if(plci!=0) plci->tel = CODEC; + dbug(1,dprintf("S/SCOM codec")); + /* first time we use the scom-s codec we must shut down the internal */ + /* handset application of the card. This can be done by an assign with */ + /* a cai with the 0x80 bit set. Assign return code is 'out of resource'*/ + if(!a->scom_appl_disable){ + if((j=get_plci(a))) { + splci = &a->plci[j-1]; + add_p(splci,CAI,"\x01\x80"); + add_p(splci,UID,"\x06\x43\x61\x70\x69\x32\x30"); + sig_req(splci,ASSIGN,0xC0); /* 0xc0 is the TEL_ID */ + send_req(splci); + a->scom_appl_disable = TRUE; + } + else{ + return 0x2001; /* wrong state, no more plcis */ + } + } + } + else return 0x300B; /* Facility not supported */ + + return 0; +} + + +void CodecIdCheck(DIVA_CAPI_ADAPTER *a, PLCI *plci) +{ + + dbug(1,dprintf("CodecIdCheck")); + + if(a->AdvSignalPLCI == plci) + { + dbug(1,dprintf("PLCI owns codec")); + VoiceChannelOff(a->AdvCodecPLCI); + if(a->AdvCodecPLCI->State == ADVANCED_VOICE_NOSIG) + { + dbug(1,dprintf("remove temp codec PLCI")); + plci_remove(a->AdvCodecPLCI); + a->AdvCodecFLAG = 0; + a->AdvCodecPLCI = NULL; + a->AdvSignalAppl = NULL; + } + a->AdvSignalPLCI = NULL; + } +} + +/* ------------------------------------------------------------------- + Ask for physical address of card on PCI bus + ------------------------------------------------------------------- */ +static void diva_ask_for_xdi_sdram_bar (DIVA_CAPI_ADAPTER * a, + IDI_SYNC_REQ * preq) { + a->sdram_bar = 0; + if (diva_xdi_extended_features & DIVA_CAPI_XDI_PROVIDES_SDRAM_BAR) { + ENTITY * e = (ENTITY *)preq; + + e->user[0] = a->Id - 1; + preq->xdi_sdram_bar.info.bar = 0; + preq->xdi_sdram_bar.Req = 0; + preq->xdi_sdram_bar.Rc = IDI_SYNC_REQ_XDI_GET_ADAPTER_SDRAM_BAR; + + (*(a->request))(e); + + a->sdram_bar = preq->xdi_sdram_bar.info.bar; + dbug(3,dprintf("A(%d) SDRAM BAR = %08x", a->Id, a->sdram_bar)); + } +} + +/* ------------------------------------------------------------------- + Ask XDI about extended features + ------------------------------------------------------------------- */ +static void diva_get_extended_adapter_features (DIVA_CAPI_ADAPTER * a) { + IDI_SYNC_REQ * preq; + char buffer[ ((sizeof(preq->xdi_extended_features)+4) > sizeof(ENTITY)) ? (sizeof(preq->xdi_extended_features)+4) : sizeof(ENTITY)]; + + char features[4]; + preq = (IDI_SYNC_REQ *)&buffer[0]; + + if (!diva_xdi_extended_features) { + ENTITY * e = (ENTITY *)preq; + diva_xdi_extended_features |= 0x80000000; + + e->user[0] = a->Id - 1; + preq->xdi_extended_features.Req = 0; + preq->xdi_extended_features.Rc = IDI_SYNC_REQ_XDI_GET_EXTENDED_FEATURES; + preq->xdi_extended_features.info.buffer_length_in_bytes = sizeof(features); + preq->xdi_extended_features.info.features = &features[0]; + + (*(a->request))(e); + + if (features[0] & DIVA_XDI_EXTENDED_FEATURES_VALID) { + /* + Check features located in the byte '0' + */ + if (features[0] & DIVA_XDI_EXTENDED_FEATURE_CMA) { + diva_xdi_extended_features |= DIVA_CAPI_USE_CMA; + } + if (features[0] & DIVA_XDI_EXTENDED_FEATURE_RX_DMA) { + diva_xdi_extended_features |= DIVA_CAPI_XDI_PROVIDES_RX_DMA; + dbug(1,dprintf("XDI provides RxDMA")); + } + if (features[0] & DIVA_XDI_EXTENDED_FEATURE_SDRAM_BAR) { + diva_xdi_extended_features |= DIVA_CAPI_XDI_PROVIDES_SDRAM_BAR; + } + if (features[0] & DIVA_XDI_EXTENDED_FEATURE_NO_CANCEL_RC) { + diva_xdi_extended_features |= DIVA_CAPI_XDI_PROVIDES_NO_CANCEL; + dbug(3,dprintf("XDI provides NO_CANCEL_RC feature")); + } + + } + } + + diva_ask_for_xdi_sdram_bar (a, preq); +} + +/*------------------------------------------------------------------*/ +/* automatic law */ +/*------------------------------------------------------------------*/ +/* called from OS specific part after init time to get the Law */ +/* a-law (Euro) and u-law (us,japan) use different BCs in the Setup message */ +void AutomaticLaw(DIVA_CAPI_ADAPTER *a) +{ + word j; + PLCI *splci; + + if(a->automatic_law) { + return; + } + if((j=get_plci(a))) { + diva_get_extended_adapter_features (a); + splci = &a->plci[j-1]; + a->automatic_lawPLCI = splci; + a->automatic_law = 1; + add_p(splci,CAI,"\x01\x80"); + add_p(splci,UID,"\x06\x43\x61\x70\x69\x32\x30"); + splci->internal_command = USELAW_REQ; + splci->command = 0; + splci->number = 0; + sig_req(splci,ASSIGN,DSIG_ID); + send_req(splci); + } +} + +/* called from OS specific part if an application sends an Capi20Release */ +word CapiRelease(word Id) +{ + word i, j, appls_found; + PLCI *plci; + APPL *this; + DIVA_CAPI_ADAPTER *a; + + if (!Id) + { + dbug(0,dprintf("A: CapiRelease(Id==0)")); + return (_WRONG_APPL_ID); + } + + this = &application[Id-1]; /* get application pointer */ + + for(i=0,appls_found=0; i<max_appl; i++) + { + if(application[i].Id) /* an application has been found */ + { + appls_found++; + } + } + + for(i=0; i<max_adapter; i++) /* scan all adapters... */ + { + a = &adapter[i]; + if (a->request) + { + a->Info_Mask[Id-1] = 0; + a->CIP_Mask[Id-1] = 0; + a->Notification_Mask[Id-1] = 0; + a->codec_listen[Id-1] = NULL; + a->requested_options_table[Id-1] = 0; + for(j=0; j<a->max_plci; j++) /* and all PLCIs connected */ + { /* with this application */ + plci = &a->plci[j]; + if(plci->Id) /* if plci owns no application */ + { /* it may be not jet connected */ + if(plci->State==INC_CON_PENDING + || plci->State==INC_CON_ALERT) + { + if(test_c_ind_mask_bit (plci, (word)(Id-1))) + { + clear_c_ind_mask_bit (plci, (word)(Id-1)); + if(c_ind_mask_empty (plci)) + { + sig_req(plci,HANGUP,0); + send_req(plci); + plci->State = OUTG_DIS_PENDING; + } + } + } + if(test_c_ind_mask_bit (plci, (word)(Id-1))) + { + clear_c_ind_mask_bit (plci, (word)(Id-1)); + if(c_ind_mask_empty (plci)) + { + if(!plci->appl) + { + plci_remove(plci); + plci->State = IDLE; + } + } + } + if(plci->appl==this) + { + plci->appl = NULL; + plci_remove(plci); + plci->State = IDLE; + } + } + } + listen_check(a); + + if(a->flag_dynamic_l1_down) + { + if(appls_found==1) /* last application does a capi release */ + { + if((j=get_plci(a))) + { + plci = &a->plci[j-1]; + plci->command = 0; + add_p(plci,OAD,"\x01\xfd"); + add_p(plci,CAI,"\x01\x80"); + add_p(plci,UID,"\x06\x43\x61\x70\x69\x32\x30"); + add_p(plci,SHIFT|6,NULL); + add_p(plci,SIN,"\x02\x00\x00"); + plci->internal_command = REM_L1_SIG_ASSIGN_PEND; + sig_req(plci,ASSIGN,DSIG_ID); + add_p(plci,FTY,"\x02\xff\x06"); /* l1 down */ + sig_req(plci,SIG_CTRL,0); + send_req(plci); + } + } + } + if(a->AdvSignalAppl==this) + { + this->NullCREnable = FALSE; + if (a->AdvCodecPLCI) + { + plci_remove(a->AdvCodecPLCI); + a->AdvCodecPLCI->tel = 0; + a->AdvCodecPLCI->adv_nl = 0; + } + a->AdvSignalAppl = NULL; + a->AdvSignalPLCI = NULL; + a->AdvCodecFLAG = 0; + a->AdvCodecPLCI = NULL; + } + } + } + + this->Id = 0; + + return GOOD; +} + +static word plci_remove_check(PLCI *plci) +{ + if(!plci) return TRUE; + if(!plci->NL.Id && c_ind_mask_empty (plci)) + { + if(plci->Sig.Id == 0xff) + plci->Sig.Id = 0; + if(!plci->Sig.Id) + { + dbug(1,dprintf("plci_remove_complete(%x)",plci->Id)); + dbug(1,dprintf("tel=0x%x,Sig=0x%x",plci->tel,plci->Sig.Id)); + if (plci->Id) + { + CodecIdCheck(plci->adapter, plci); + clear_b1_config (plci); + ncci_remove (plci, 0, FALSE); + plci_free_msg_in_queue (plci); + channel_flow_control_remove (plci); + plci->Id = 0; + plci->State = IDLE; + plci->channels = 0; + plci->appl = NULL; + plci->notifiedcall = 0; + } + listen_check(plci->adapter); + return TRUE; + } + } + return FALSE; +} + + +/*------------------------------------------------------------------*/ + +static byte plci_nl_busy (PLCI *plci) +{ + /* only applicable for non-multiplexed protocols */ + return (plci->nl_req + || (plci->ncci_ring_list + && plci->adapter->ncci_ch[plci->ncci_ring_list] + && (plci->adapter->ch_flow_control[plci->adapter->ncci_ch[plci->ncci_ring_list]] & N_OK_FC_PENDING))); +} + + +/*------------------------------------------------------------------*/ +/* DTMF facilities */ +/*------------------------------------------------------------------*/ + + +static struct +{ + byte send_mask; + byte listen_mask; + byte character; + byte code; +} dtmf_digit_map[] = +{ + { 0x01, 0x01, 0x23, DTMF_DIGIT_TONE_CODE_HASHMARK }, + { 0x01, 0x01, 0x2a, DTMF_DIGIT_TONE_CODE_STAR }, + { 0x01, 0x01, 0x30, DTMF_DIGIT_TONE_CODE_0 }, + { 0x01, 0x01, 0x31, DTMF_DIGIT_TONE_CODE_1 }, + { 0x01, 0x01, 0x32, DTMF_DIGIT_TONE_CODE_2 }, + { 0x01, 0x01, 0x33, DTMF_DIGIT_TONE_CODE_3 }, + { 0x01, 0x01, 0x34, DTMF_DIGIT_TONE_CODE_4 }, + { 0x01, 0x01, 0x35, DTMF_DIGIT_TONE_CODE_5 }, + { 0x01, 0x01, 0x36, DTMF_DIGIT_TONE_CODE_6 }, + { 0x01, 0x01, 0x37, DTMF_DIGIT_TONE_CODE_7 }, + { 0x01, 0x01, 0x38, DTMF_DIGIT_TONE_CODE_8 }, + { 0x01, 0x01, 0x39, DTMF_DIGIT_TONE_CODE_9 }, + { 0x01, 0x01, 0x41, DTMF_DIGIT_TONE_CODE_A }, + { 0x01, 0x01, 0x42, DTMF_DIGIT_TONE_CODE_B }, + { 0x01, 0x01, 0x43, DTMF_DIGIT_TONE_CODE_C }, + { 0x01, 0x01, 0x44, DTMF_DIGIT_TONE_CODE_D }, + { 0x01, 0x00, 0x61, DTMF_DIGIT_TONE_CODE_A }, + { 0x01, 0x00, 0x62, DTMF_DIGIT_TONE_CODE_B }, + { 0x01, 0x00, 0x63, DTMF_DIGIT_TONE_CODE_C }, + { 0x01, 0x00, 0x64, DTMF_DIGIT_TONE_CODE_D }, + + { 0x04, 0x04, 0x80, DTMF_SIGNAL_NO_TONE }, + { 0x00, 0x04, 0x81, DTMF_SIGNAL_UNIDENTIFIED_TONE }, + { 0x04, 0x04, 0x82, DTMF_SIGNAL_DIAL_TONE }, + { 0x04, 0x04, 0x83, DTMF_SIGNAL_PABX_INTERNAL_DIAL_TONE }, + { 0x04, 0x04, 0x84, DTMF_SIGNAL_SPECIAL_DIAL_TONE }, + { 0x04, 0x04, 0x85, DTMF_SIGNAL_SECOND_DIAL_TONE }, + { 0x04, 0x04, 0x86, DTMF_SIGNAL_RINGING_TONE }, + { 0x04, 0x04, 0x87, DTMF_SIGNAL_SPECIAL_RINGING_TONE }, + { 0x04, 0x04, 0x88, DTMF_SIGNAL_BUSY_TONE }, + { 0x04, 0x04, 0x89, DTMF_SIGNAL_CONGESTION_TONE }, + { 0x04, 0x04, 0x8a, DTMF_SIGNAL_SPECIAL_INFORMATION_TONE }, + { 0x04, 0x04, 0x8b, DTMF_SIGNAL_COMFORT_TONE }, + { 0x04, 0x04, 0x8c, DTMF_SIGNAL_HOLD_TONE }, + { 0x04, 0x04, 0x8d, DTMF_SIGNAL_RECORD_TONE }, + { 0x04, 0x04, 0x8e, DTMF_SIGNAL_CALLER_WAITING_TONE }, + { 0x04, 0x04, 0x8f, DTMF_SIGNAL_CALL_WAITING_TONE }, + { 0x04, 0x04, 0x90, DTMF_SIGNAL_PAY_TONE }, + { 0x04, 0x04, 0x91, DTMF_SIGNAL_POSITIVE_INDICATION_TONE }, + { 0x04, 0x04, 0x92, DTMF_SIGNAL_NEGATIVE_INDICATION_TONE }, + { 0x04, 0x04, 0x93, DTMF_SIGNAL_WARNING_TONE }, + { 0x04, 0x04, 0x94, DTMF_SIGNAL_INTRUSION_TONE }, + { 0x04, 0x04, 0x95, DTMF_SIGNAL_CALLING_CARD_SERVICE_TONE }, + { 0x04, 0x04, 0x96, DTMF_SIGNAL_PAYPHONE_RECOGNITION_TONE }, + { 0x04, 0x04, 0x97, DTMF_SIGNAL_CPE_ALERTING_SIGNAL }, + { 0x04, 0x04, 0x98, DTMF_SIGNAL_OFF_HOOK_WARNING_TONE }, + { 0x04, 0x04, 0xbf, DTMF_SIGNAL_INTERCEPT_TONE }, + { 0x04, 0x04, 0xc0, DTMF_SIGNAL_MODEM_CALLING_TONE }, + { 0x04, 0x04, 0xc1, DTMF_SIGNAL_FAX_CALLING_TONE }, + { 0x04, 0x04, 0xc2, DTMF_SIGNAL_ANSWER_TONE }, + { 0x04, 0x04, 0xc3, DTMF_SIGNAL_REVERSED_ANSWER_TONE }, + { 0x04, 0x04, 0xc4, DTMF_SIGNAL_ANSAM_TONE }, + { 0x04, 0x04, 0xc5, DTMF_SIGNAL_REVERSED_ANSAM_TONE }, + { 0x04, 0x04, 0xc6, DTMF_SIGNAL_BELL103_ANSWER_TONE }, + { 0x04, 0x04, 0xc7, DTMF_SIGNAL_FAX_FLAGS }, + { 0x04, 0x04, 0xc8, DTMF_SIGNAL_G2_FAX_GROUP_ID }, + { 0x00, 0x04, 0xc9, DTMF_SIGNAL_HUMAN_SPEECH }, + { 0x04, 0x04, 0xca, DTMF_SIGNAL_ANSWERING_MACHINE_390 }, + { 0x02, 0x02, 0xf1, DTMF_MF_DIGIT_TONE_CODE_1 }, + { 0x02, 0x02, 0xf2, DTMF_MF_DIGIT_TONE_CODE_2 }, + { 0x02, 0x02, 0xf3, DTMF_MF_DIGIT_TONE_CODE_3 }, + { 0x02, 0x02, 0xf4, DTMF_MF_DIGIT_TONE_CODE_4 }, + { 0x02, 0x02, 0xf5, DTMF_MF_DIGIT_TONE_CODE_5 }, + { 0x02, 0x02, 0xf6, DTMF_MF_DIGIT_TONE_CODE_6 }, + { 0x02, 0x02, 0xf7, DTMF_MF_DIGIT_TONE_CODE_7 }, + { 0x02, 0x02, 0xf8, DTMF_MF_DIGIT_TONE_CODE_8 }, + { 0x02, 0x02, 0xf9, DTMF_MF_DIGIT_TONE_CODE_9 }, + { 0x02, 0x02, 0xfa, DTMF_MF_DIGIT_TONE_CODE_0 }, + { 0x02, 0x02, 0xfb, DTMF_MF_DIGIT_TONE_CODE_K1 }, + { 0x02, 0x02, 0xfc, DTMF_MF_DIGIT_TONE_CODE_K2 }, + { 0x02, 0x02, 0xfd, DTMF_MF_DIGIT_TONE_CODE_KP }, + { 0x02, 0x02, 0xfe, DTMF_MF_DIGIT_TONE_CODE_S1 }, + { 0x02, 0x02, 0xff, DTMF_MF_DIGIT_TONE_CODE_ST }, + +}; + +#define DTMF_DIGIT_MAP_ENTRIES (sizeof(dtmf_digit_map) / sizeof(dtmf_digit_map[0])) + + +static void dtmf_enable_receiver (PLCI *plci, byte enable_mask) +{ + word min_digit_duration, min_gap_duration; + + dbug (1, dprintf ("[%06lx] %s,%d: dtmf_enable_receiver %02x", + (dword)((plci->Id << 8) | UnMapController (plci->adapter->Id)), + (char *)(FILE_), __LINE__, enable_mask)); + + if (enable_mask != 0) + { + min_digit_duration = (plci->dtmf_rec_pulse_ms == 0) ? 40 : plci->dtmf_rec_pulse_ms; + min_gap_duration = (plci->dtmf_rec_pause_ms == 0) ? 40 : plci->dtmf_rec_pause_ms; + plci->internal_req_buffer[0] = DTMF_UDATA_REQUEST_ENABLE_RECEIVER; + PUT_WORD (&plci->internal_req_buffer[1], min_digit_duration); + PUT_WORD (&plci->internal_req_buffer[3], min_gap_duration); + plci->NData[0].PLength = 5; + + PUT_WORD (&plci->internal_req_buffer[5], INTERNAL_IND_BUFFER_SIZE); + plci->NData[0].PLength += 2; + capidtmf_recv_enable (&(plci->capidtmf_state), min_digit_duration, min_gap_duration); + + } + else + { + plci->internal_req_buffer[0] = DTMF_UDATA_REQUEST_DISABLE_RECEIVER; + plci->NData[0].PLength = 1; + + capidtmf_recv_disable (&(plci->capidtmf_state)); + + } + plci->NData[0].P = plci->internal_req_buffer; + plci->NL.X = plci->NData; + plci->NL.ReqCh = 0; + plci->NL.Req = plci->nl_req = (byte) N_UDATA; + plci->adapter->request (&plci->NL); +} + + +static void dtmf_send_digits (PLCI *plci, byte *digit_buffer, word digit_count) +{ + word w, i; + + dbug (1, dprintf ("[%06lx] %s,%d: dtmf_send_digits %d", + (dword)((plci->Id << 8) | UnMapController (plci->adapter->Id)), + (char *)(FILE_), __LINE__, digit_count)); + + plci->internal_req_buffer[0] = DTMF_UDATA_REQUEST_SEND_DIGITS; + w = (plci->dtmf_send_pulse_ms == 0) ? 40 : plci->dtmf_send_pulse_ms; + PUT_WORD (&plci->internal_req_buffer[1], w); + w = (plci->dtmf_send_pause_ms == 0) ? 40 : plci->dtmf_send_pause_ms; + PUT_WORD (&plci->internal_req_buffer[3], w); + for (i = 0; i < digit_count; i++) + { + w = 0; + while ((w < DTMF_DIGIT_MAP_ENTRIES) + && (digit_buffer[i] != dtmf_digit_map[w].character)) + { + w++; + } + plci->internal_req_buffer[5+i] = (w < DTMF_DIGIT_MAP_ENTRIES) ? + dtmf_digit_map[w].code : DTMF_DIGIT_TONE_CODE_STAR; + } + plci->NData[0].PLength = 5 + digit_count; + plci->NData[0].P = plci->internal_req_buffer; + plci->NL.X = plci->NData; + plci->NL.ReqCh = 0; + plci->NL.Req = plci->nl_req = (byte) N_UDATA; + plci->adapter->request (&plci->NL); +} + + +static void dtmf_rec_clear_config (PLCI *plci) +{ + + dbug (1, dprintf ("[%06lx] %s,%d: dtmf_rec_clear_config", + (dword)((plci->Id << 8) | UnMapController (plci->adapter->Id)), + (char *)(FILE_), __LINE__)); + + plci->dtmf_rec_active = 0; + plci->dtmf_rec_pulse_ms = 0; + plci->dtmf_rec_pause_ms = 0; + + capidtmf_init (&(plci->capidtmf_state), plci->adapter->u_law); + +} + + +static void dtmf_send_clear_config (PLCI *plci) +{ + + dbug (1, dprintf ("[%06lx] %s,%d: dtmf_send_clear_config", + (dword)((plci->Id << 8) | UnMapController (plci->adapter->Id)), + (char *)(FILE_), __LINE__)); + + plci->dtmf_send_requests = 0; + plci->dtmf_send_pulse_ms = 0; + plci->dtmf_send_pause_ms = 0; +} + + +static void dtmf_prepare_switch (dword Id, PLCI *plci) +{ + + dbug (1, dprintf ("[%06lx] %s,%d: dtmf_prepare_switch", + UnMapId (Id), (char *)(FILE_), __LINE__)); + + while (plci->dtmf_send_requests != 0) + dtmf_confirmation (Id, plci); +} + + +static word dtmf_save_config (dword Id, PLCI *plci, byte Rc) +{ + + dbug (1, dprintf ("[%06lx] %s,%d: dtmf_save_config %02x %d", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc, plci->adjust_b_state)); + + return (GOOD); +} + + +static word dtmf_restore_config (dword Id, PLCI *plci, byte Rc) +{ + word Info; + + dbug (1, dprintf ("[%06lx] %s,%d: dtmf_restore_config %02x %d", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc, plci->adjust_b_state)); + + Info = GOOD; + if (plci->B1_facilities & B1_FACILITY_DTMFR) + { + switch (plci->adjust_b_state) + { + case ADJUST_B_RESTORE_DTMF_1: + plci->internal_command = plci->adjust_b_command; + if (plci_nl_busy (plci)) + { + plci->adjust_b_state = ADJUST_B_RESTORE_DTMF_1; + break; + } + dtmf_enable_receiver (plci, plci->dtmf_rec_active); + plci->adjust_b_state = ADJUST_B_RESTORE_DTMF_2; + break; + case ADJUST_B_RESTORE_DTMF_2: + if ((Rc != OK) && (Rc != OK_FC)) + { + dbug (1, dprintf ("[%06lx] %s,%d: Reenable DTMF receiver failed %02x", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc)); + Info = _WRONG_STATE; + break; + } + break; + } + } + return (Info); +} + + +static void dtmf_command (dword Id, PLCI *plci, byte Rc) +{ + word internal_command, Info; + byte mask; + byte result[4]; + + dbug (1, dprintf ("[%06lx] %s,%d: dtmf_command %02x %04x %04x %d %d %d %d", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc, plci->internal_command, + plci->dtmf_cmd, plci->dtmf_rec_pulse_ms, plci->dtmf_rec_pause_ms, + plci->dtmf_send_pulse_ms, plci->dtmf_send_pause_ms)); + + Info = GOOD; + result[0] = 2; + PUT_WORD (&result[1], DTMF_SUCCESS); + internal_command = plci->internal_command; + plci->internal_command = 0; + mask = 0x01; + switch (plci->dtmf_cmd) + { + + case DTMF_LISTEN_TONE_START: + mask <<= 1; + case DTMF_LISTEN_MF_START: + mask <<= 1; + + case DTMF_LISTEN_START: + switch (internal_command) + { + default: + adjust_b1_resource (Id, plci, NULL, (word)(plci->B1_facilities | + B1_FACILITY_DTMFR), DTMF_COMMAND_1); + case DTMF_COMMAND_1: + if (adjust_b_process (Id, plci, Rc) != GOOD) + { + dbug (1, dprintf ("[%06lx] %s,%d: Load DTMF failed", + UnMapId (Id), (char *)(FILE_), __LINE__)); + Info = _FACILITY_NOT_SUPPORTED; + break; + } + if (plci->internal_command) + return; + case DTMF_COMMAND_2: + if (plci_nl_busy (plci)) + { + plci->internal_command = DTMF_COMMAND_2; + return; + } + plci->internal_command = DTMF_COMMAND_3; + dtmf_enable_receiver (plci, (byte)(plci->dtmf_rec_active | mask)); + return; + case DTMF_COMMAND_3: + if ((Rc != OK) && (Rc != OK_FC)) + { + dbug (1, dprintf ("[%06lx] %s,%d: Enable DTMF receiver failed %02x", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc)); + Info = _FACILITY_NOT_SUPPORTED; + break; + } + + plci->tone_last_indication_code = DTMF_SIGNAL_NO_TONE; + + plci->dtmf_rec_active |= mask; + break; + } + break; + + + case DTMF_LISTEN_TONE_STOP: + mask <<= 1; + case DTMF_LISTEN_MF_STOP: + mask <<= 1; + + case DTMF_LISTEN_STOP: + switch (internal_command) + { + default: + plci->dtmf_rec_active &= ~mask; + if (plci->dtmf_rec_active) + break; +/* + case DTMF_COMMAND_1: + if (plci->dtmf_rec_active) + { + if (plci_nl_busy (plci)) + { + plci->internal_command = DTMF_COMMAND_1; + return; + } + plci->dtmf_rec_active &= ~mask; + plci->internal_command = DTMF_COMMAND_2; + dtmf_enable_receiver (plci, FALSE); + return; + } + Rc = OK; + case DTMF_COMMAND_2: + if ((Rc != OK) && (Rc != OK_FC)) + { + dbug (1, dprintf ("[%06lx] %s,%d: Disable DTMF receiver failed %02x", + UnMapId (Id), (char far *)(FILE_), __LINE__, Rc)); + Info = _FACILITY_NOT_SUPPORTED; + break; + } +*/ + adjust_b1_resource (Id, plci, NULL, (word)(plci->B1_facilities & + ~(B1_FACILITY_DTMFX | B1_FACILITY_DTMFR)), DTMF_COMMAND_3); + case DTMF_COMMAND_3: + if (adjust_b_process (Id, plci, Rc) != GOOD) + { + dbug (1, dprintf ("[%06lx] %s,%d: Unload DTMF failed", + UnMapId (Id), (char *)(FILE_), __LINE__)); + Info = _FACILITY_NOT_SUPPORTED; + break; + } + if (plci->internal_command) + return; + break; + } + break; + + + case DTMF_SEND_TONE: + mask <<= 1; + case DTMF_SEND_MF: + mask <<= 1; + + case DTMF_DIGITS_SEND: + switch (internal_command) + { + default: + adjust_b1_resource (Id, plci, NULL, (word)(plci->B1_facilities | + ((plci->dtmf_parameter_length != 0) ? B1_FACILITY_DTMFX | B1_FACILITY_DTMFR : B1_FACILITY_DTMFX)), + DTMF_COMMAND_1); + case DTMF_COMMAND_1: + if (adjust_b_process (Id, plci, Rc) != GOOD) + { + dbug (1, dprintf ("[%06lx] %s,%d: Load DTMF failed", + UnMapId (Id), (char *)(FILE_), __LINE__)); + Info = _FACILITY_NOT_SUPPORTED; + break; + } + if (plci->internal_command) + return; + case DTMF_COMMAND_2: + if (plci_nl_busy (plci)) + { + plci->internal_command = DTMF_COMMAND_2; + return; + } + plci->dtmf_msg_number_queue[(plci->dtmf_send_requests)++] = plci->number; + plci->internal_command = DTMF_COMMAND_3; + dtmf_send_digits (plci, &plci->saved_msg.parms[3].info[1], plci->saved_msg.parms[3].length); + return; + case DTMF_COMMAND_3: + if ((Rc != OK) && (Rc != OK_FC)) + { + dbug (1, dprintf ("[%06lx] %s,%d: Send DTMF digits failed %02x", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc)); + if (plci->dtmf_send_requests != 0) + (plci->dtmf_send_requests)--; + Info = _FACILITY_NOT_SUPPORTED; + break; + } + return; + } + break; + } + sendf (plci->appl, _FACILITY_R | CONFIRM, Id & 0xffffL, plci->number, + "wws", Info, SELECTOR_DTMF, result); +} + + +static byte dtmf_request (dword Id, word Number, DIVA_CAPI_ADAPTER *a, PLCI *plci, APPL *appl, API_PARSE *msg) +{ + word Info; + word i, j; + byte mask; + API_PARSE dtmf_parms[5]; + byte result[40]; + + dbug (1, dprintf ("[%06lx] %s,%d: dtmf_request", + UnMapId (Id), (char *)(FILE_), __LINE__)); + + Info = GOOD; + result[0] = 2; + PUT_WORD (&result[1], DTMF_SUCCESS); + if (!(a->profile.Global_Options & GL_DTMF_SUPPORTED)) + { + dbug (1, dprintf ("[%06lx] %s,%d: Facility not supported", + UnMapId (Id), (char *)(FILE_), __LINE__)); + Info = _FACILITY_NOT_SUPPORTED; + } + else if (api_parse (&msg[1].info[1], msg[1].length, "w", dtmf_parms)) + { + dbug (1, dprintf ("[%06lx] %s,%d: Wrong message format", + UnMapId (Id), (char *)(FILE_), __LINE__)); + Info = _WRONG_MESSAGE_FORMAT; + } + + else if ((GET_WORD (dtmf_parms[0].info) == DTMF_GET_SUPPORTED_DETECT_CODES) + || (GET_WORD (dtmf_parms[0].info) == DTMF_GET_SUPPORTED_SEND_CODES)) + { + if (!((a->requested_options_table[appl->Id-1]) + & (1L << PRIVATE_DTMF_TONE))) + { + dbug (1, dprintf ("[%06lx] %s,%d: DTMF unknown request %04x", + UnMapId (Id), (char *)(FILE_), __LINE__, GET_WORD (dtmf_parms[0].info))); + PUT_WORD (&result[1], DTMF_UNKNOWN_REQUEST); + } + else + { + for (i = 0; i < 32; i++) + result[4 + i] = 0; + if (GET_WORD (dtmf_parms[0].info) == DTMF_GET_SUPPORTED_DETECT_CODES) + { + for (i = 0; i < DTMF_DIGIT_MAP_ENTRIES; i++) + { + if (dtmf_digit_map[i].listen_mask != 0) + result[4 + (dtmf_digit_map[i].character >> 3)] |= (1 << (dtmf_digit_map[i].character & 0x7)); + } + } + else + { + for (i = 0; i < DTMF_DIGIT_MAP_ENTRIES; i++) + { + if (dtmf_digit_map[i].send_mask != 0) + result[4 + (dtmf_digit_map[i].character >> 3)] |= (1 << (dtmf_digit_map[i].character & 0x7)); + } + } + result[0] = 3 + 32; + result[3] = 32; + } + } + + else if (plci == NULL) + { + dbug (1, dprintf ("[%06lx] %s,%d: Wrong PLCI", + UnMapId (Id), (char *)(FILE_), __LINE__)); + Info = _WRONG_IDENTIFIER; + } + else + { + if (!plci->State + || !plci->NL.Id || plci->nl_remove_id) + { + dbug (1, dprintf ("[%06lx] %s,%d: Wrong state", + UnMapId (Id), (char *)(FILE_), __LINE__)); + Info = _WRONG_STATE; + } + else + { + plci->command = 0; + plci->dtmf_cmd = GET_WORD (dtmf_parms[0].info); + mask = 0x01; + switch (plci->dtmf_cmd) + { + + case DTMF_LISTEN_TONE_START: + case DTMF_LISTEN_TONE_STOP: + mask <<= 1; + case DTMF_LISTEN_MF_START: + case DTMF_LISTEN_MF_STOP: + mask <<= 1; + if (!((plci->requested_options_conn | plci->requested_options | plci->adapter->requested_options_table[appl->Id-1]) + & (1L << PRIVATE_DTMF_TONE))) + { + dbug (1, dprintf ("[%06lx] %s,%d: DTMF unknown request %04x", + UnMapId (Id), (char *)(FILE_), __LINE__, GET_WORD (dtmf_parms[0].info))); + PUT_WORD (&result[1], DTMF_UNKNOWN_REQUEST); + break; + } + + case DTMF_LISTEN_START: + case DTMF_LISTEN_STOP: + if (!(a->manufacturer_features & MANUFACTURER_FEATURE_HARDDTMF) + && !(a->manufacturer_features & MANUFACTURER_FEATURE_SOFTDTMF_RECEIVE)) + { + dbug (1, dprintf ("[%06lx] %s,%d: Facility not supported", + UnMapId (Id), (char *)(FILE_), __LINE__)); + Info = _FACILITY_NOT_SUPPORTED; + break; + } + if (mask & DTMF_LISTEN_ACTIVE_FLAG) + { + if (api_parse (&msg[1].info[1], msg[1].length, "wwws", dtmf_parms)) + { + plci->dtmf_rec_pulse_ms = 0; + plci->dtmf_rec_pause_ms = 0; + } + else + { + plci->dtmf_rec_pulse_ms = GET_WORD (dtmf_parms[1].info); + plci->dtmf_rec_pause_ms = GET_WORD (dtmf_parms[2].info); + } + } + start_internal_command (Id, plci, dtmf_command); + return (FALSE); + + + case DTMF_SEND_TONE: + mask <<= 1; + case DTMF_SEND_MF: + mask <<= 1; + if (!((plci->requested_options_conn | plci->requested_options | plci->adapter->requested_options_table[appl->Id-1]) + & (1L << PRIVATE_DTMF_TONE))) + { + dbug (1, dprintf ("[%06lx] %s,%d: DTMF unknown request %04x", + UnMapId (Id), (char *)(FILE_), __LINE__, GET_WORD (dtmf_parms[0].info))); + PUT_WORD (&result[1], DTMF_UNKNOWN_REQUEST); + break; + } + + case DTMF_DIGITS_SEND: + if (api_parse (&msg[1].info[1], msg[1].length, "wwws", dtmf_parms)) + { + dbug (1, dprintf ("[%06lx] %s,%d: Wrong message format", + UnMapId (Id), (char *)(FILE_), __LINE__)); + Info = _WRONG_MESSAGE_FORMAT; + break; + } + if (mask & DTMF_LISTEN_ACTIVE_FLAG) + { + plci->dtmf_send_pulse_ms = GET_WORD (dtmf_parms[1].info); + plci->dtmf_send_pause_ms = GET_WORD (dtmf_parms[2].info); + } + i = 0; + j = 0; + while ((i < dtmf_parms[3].length) && (j < DTMF_DIGIT_MAP_ENTRIES)) + { + j = 0; + while ((j < DTMF_DIGIT_MAP_ENTRIES) + && ((dtmf_parms[3].info[i+1] != dtmf_digit_map[j].character) + || ((dtmf_digit_map[j].send_mask & mask) == 0))) + { + j++; + } + i++; + } + if (j == DTMF_DIGIT_MAP_ENTRIES) + { + dbug (1, dprintf ("[%06lx] %s,%d: Incorrect DTMF digit %02x", + UnMapId (Id), (char *)(FILE_), __LINE__, dtmf_parms[3].info[i])); + PUT_WORD (&result[1], DTMF_INCORRECT_DIGIT); + break; + } + if (plci->dtmf_send_requests >= + sizeof(plci->dtmf_msg_number_queue) / sizeof(plci->dtmf_msg_number_queue[0])) + { + dbug (1, dprintf ("[%06lx] %s,%d: DTMF request overrun", + UnMapId (Id), (char *)(FILE_), __LINE__)); + Info = _WRONG_STATE; + break; + } + api_save_msg (dtmf_parms, "wwws", &plci->saved_msg); + start_internal_command (Id, plci, dtmf_command); + return (FALSE); + + default: + dbug (1, dprintf ("[%06lx] %s,%d: DTMF unknown request %04x", + UnMapId (Id), (char *)(FILE_), __LINE__, plci->dtmf_cmd)); + PUT_WORD (&result[1], DTMF_UNKNOWN_REQUEST); + } + } + } + sendf (appl, _FACILITY_R | CONFIRM, Id & 0xffffL, Number, + "wws", Info, SELECTOR_DTMF, result); + return (FALSE); +} + + +static void dtmf_confirmation (dword Id, PLCI *plci) +{ + word Info; + word i; + byte result[4]; + + dbug (1, dprintf ("[%06lx] %s,%d: dtmf_confirmation", + UnMapId (Id), (char *)(FILE_), __LINE__)); + + Info = GOOD; + result[0] = 2; + PUT_WORD (&result[1], DTMF_SUCCESS); + if (plci->dtmf_send_requests != 0) + { + sendf (plci->appl, _FACILITY_R | CONFIRM, Id & 0xffffL, plci->dtmf_msg_number_queue[0], + "wws", GOOD, SELECTOR_DTMF, result); + (plci->dtmf_send_requests)--; + for (i = 0; i < plci->dtmf_send_requests; i++) + plci->dtmf_msg_number_queue[i] = plci->dtmf_msg_number_queue[i+1]; + } +} + + +static void dtmf_indication (dword Id, PLCI *plci, byte *msg, word length) +{ + word i, j, n; + + dbug (1, dprintf ("[%06lx] %s,%d: dtmf_indication", + UnMapId (Id), (char *)(FILE_), __LINE__)); + + n = 0; + for (i = 1; i < length; i++) + { + j = 0; + while ((j < DTMF_DIGIT_MAP_ENTRIES) + && ((msg[i] != dtmf_digit_map[j].code) + || ((dtmf_digit_map[j].listen_mask & plci->dtmf_rec_active) == 0))) + { + j++; + } + if (j < DTMF_DIGIT_MAP_ENTRIES) + { + + if ((dtmf_digit_map[j].listen_mask & DTMF_TONE_LISTEN_ACTIVE_FLAG) + && (plci->tone_last_indication_code == DTMF_SIGNAL_NO_TONE) + && (dtmf_digit_map[j].character != DTMF_SIGNAL_UNIDENTIFIED_TONE)) + { + if (n + 1 == i) + { + for (i = length; i > n + 1; i--) + msg[i] = msg[i - 1]; + length++; + i++; + } + msg[++n] = DTMF_SIGNAL_UNIDENTIFIED_TONE; + } + plci->tone_last_indication_code = dtmf_digit_map[j].character; + + msg[++n] = dtmf_digit_map[j].character; + } + } + if (n != 0) + { + msg[0] = (byte) n; + sendf (plci->appl, _FACILITY_I, Id & 0xffffL, 0, "wS", SELECTOR_DTMF, msg); + } +} + + +/*------------------------------------------------------------------*/ +/* DTMF parameters */ +/*------------------------------------------------------------------*/ + +static void dtmf_parameter_write (PLCI *plci) +{ + word i; + byte parameter_buffer[DTMF_PARAMETER_BUFFER_SIZE + 2]; + + dbug (1, dprintf ("[%06lx] %s,%d: dtmf_parameter_write", + (dword)((plci->Id << 8) | UnMapController (plci->adapter->Id)), + (char *)(FILE_), __LINE__)); + + parameter_buffer[0] = plci->dtmf_parameter_length + 1; + parameter_buffer[1] = DSP_CTRL_SET_DTMF_PARAMETERS; + for (i = 0; i < plci->dtmf_parameter_length; i++) + parameter_buffer[2+i] = plci->dtmf_parameter_buffer[i]; + add_p (plci, FTY, parameter_buffer); + sig_req (plci, TEL_CTRL, 0); + send_req (plci); +} + + +static void dtmf_parameter_clear_config (PLCI *plci) +{ + + dbug (1, dprintf ("[%06lx] %s,%d: dtmf_parameter_clear_config", + (dword)((plci->Id << 8) | UnMapController (plci->adapter->Id)), + (char *)(FILE_), __LINE__)); + + plci->dtmf_parameter_length = 0; +} + + +static void dtmf_parameter_prepare_switch (dword Id, PLCI *plci) +{ + + dbug (1, dprintf ("[%06lx] %s,%d: dtmf_parameter_prepare_switch", + UnMapId (Id), (char *)(FILE_), __LINE__)); + +} + + +static word dtmf_parameter_save_config (dword Id, PLCI *plci, byte Rc) +{ + + dbug (1, dprintf ("[%06lx] %s,%d: dtmf_parameter_save_config %02x %d", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc, plci->adjust_b_state)); + + return (GOOD); +} + + +static word dtmf_parameter_restore_config (dword Id, PLCI *plci, byte Rc) +{ + word Info; + + dbug (1, dprintf ("[%06lx] %s,%d: dtmf_parameter_restore_config %02x %d", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc, plci->adjust_b_state)); + + Info = GOOD; + if ((plci->B1_facilities & B1_FACILITY_DTMFR) + && (plci->dtmf_parameter_length != 0)) + { + switch (plci->adjust_b_state) + { + case ADJUST_B_RESTORE_DTMF_PARAMETER_1: + plci->internal_command = plci->adjust_b_command; + if (plci->sig_req) + { + plci->adjust_b_state = ADJUST_B_RESTORE_DTMF_PARAMETER_1; + break; + } + dtmf_parameter_write (plci); + plci->adjust_b_state = ADJUST_B_RESTORE_DTMF_PARAMETER_2; + break; + case ADJUST_B_RESTORE_DTMF_PARAMETER_2: + if ((Rc != OK) && (Rc != OK_FC)) + { + dbug (1, dprintf ("[%06lx] %s,%d: Restore DTMF parameters failed %02x", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc)); + Info = _WRONG_STATE; + break; + } + break; + } + } + return (Info); +} + + +/*------------------------------------------------------------------*/ +/* Line interconnect facilities */ +/*------------------------------------------------------------------*/ + + +LI_CONFIG *li_config_table; +word li_total_channels; + + +/*------------------------------------------------------------------*/ +/* translate a CHI information element to a channel number */ +/* returns 0xff - any channel */ +/* 0xfe - chi wrong coding */ +/* 0xfd - D-channel */ +/* 0x00 - no channel */ +/* else channel number / PRI: timeslot */ +/* if channels is provided we accept more than one channel. */ +/*------------------------------------------------------------------*/ + +static byte chi_to_channel (byte *chi, dword *pchannelmap) +{ + int p; + int i; + dword map; + byte excl; + byte ofs; + byte ch; + + if (pchannelmap) *pchannelmap = 0; + if(!chi[0]) return 0xff; + excl = 0; + + if(chi[1] & 0x20) { + if(chi[0]==1 && chi[1]==0xac) return 0xfd; /* exclusive d-channel */ + for(i=1; i<chi[0] && !(chi[i] &0x80); i++); + if(i==chi[0] || !(chi[i] &0x80)) return 0xfe; + if((chi[1] |0xc8)!=0xe9) return 0xfe; + if(chi[1] &0x08) excl = 0x40; + + /* int. id present */ + if(chi[1] &0x40) { + p=i+1; + for(i=p; i<chi[0] && !(chi[i] &0x80); i++); + if(i==chi[0] || !(chi[i] &0x80)) return 0xfe; + } + + /* coding standard, Number/Map, Channel Type */ + p=i+1; + for(i=p; i<chi[0] && !(chi[i] &0x80); i++); + if(i==chi[0] || !(chi[i] &0x80)) return 0xfe; + if((chi[p]|0xd0)!=0xd3) return 0xfe; + + /* Number/Map */ + if(chi[p] &0x10) { + + /* map */ + if((chi[0]-p)==4) ofs = 0; + else if((chi[0]-p)==3) ofs = 1; + else return 0xfe; + ch = 0; + map = 0; + for(i=0; i<4 && p<chi[0]; i++) { + p++; + ch += 8; + map <<= 8; + if(chi[p]) { + for (ch=0; !(chi[p] & (1 << ch)); ch++); + map |= chi[p]; + } + } + ch += ofs; + map <<= ofs; + } + else { + + /* number */ + p=i+1; + ch = chi[p] &0x3f; + if(pchannelmap) { + if((byte)(chi[0]-p)>30) return 0xfe; + map = 0; + for(i=p; i<=chi[0]; i++) { + if ((chi[i] &0x7f) > 31) return 0xfe; + map |= (1L << (chi[i] &0x7f)); + } + } + else { + if(p!=chi[0]) return 0xfe; + if (ch > 31) return 0xfe; + map = (1L << ch); + } + if(chi[p] &0x40) return 0xfe; + } + if (pchannelmap) *pchannelmap = map; + else if (map != ((dword)(1L << ch))) return 0xfe; + return (byte)(excl | ch); + } + else { /* not PRI */ + for(i=1; i<chi[0] && !(chi[i] &0x80); i++); + if(i!=chi[0] || !(chi[i] &0x80)) return 0xfe; + if(chi[1] &0x08) excl = 0x40; + + switch(chi[1] |0x98) { + case 0x98: return 0; + case 0x99: + if (pchannelmap) *pchannelmap = 2; + return excl |1; + case 0x9a: + if (pchannelmap) *pchannelmap = 4; + return excl |2; + case 0x9b: return 0xff; + case 0x9c: return 0xfd; /* d-ch */ + default: return 0xfe; + } + } +} + + +static void mixer_set_bchannel_id_esc (PLCI *plci, byte bchannel_id) +{ + DIVA_CAPI_ADAPTER *a; + PLCI *splci; + byte old_id; + + a = plci->adapter; + old_id = plci->li_bchannel_id; + if (a->li_pri) + { + if ((old_id != 0) && (li_config_table[a->li_base + (old_id - 1)].plci == plci)) + li_config_table[a->li_base + (old_id - 1)].plci = NULL; + plci->li_bchannel_id = (bchannel_id & 0x1f) + 1; + if (li_config_table[a->li_base + (plci->li_bchannel_id - 1)].plci == NULL) + li_config_table[a->li_base + (plci->li_bchannel_id - 1)].plci = plci; + } + else + { + if (((bchannel_id & 0x03) == 1) || ((bchannel_id & 0x03) == 2)) + { + if ((old_id != 0) && (li_config_table[a->li_base + (old_id - 1)].plci == plci)) + li_config_table[a->li_base + (old_id - 1)].plci = NULL; + plci->li_bchannel_id = bchannel_id & 0x03; + if ((a->AdvSignalPLCI != NULL) && (a->AdvSignalPLCI != plci) && (a->AdvSignalPLCI->tel == ADV_VOICE)) + { + splci = a->AdvSignalPLCI; + if (li_config_table[a->li_base + (2 - plci->li_bchannel_id)].plci == NULL) + { + if ((splci->li_bchannel_id != 0) + && (li_config_table[a->li_base + (splci->li_bchannel_id - 1)].plci == splci)) + { + li_config_table[a->li_base + (splci->li_bchannel_id - 1)].plci = NULL; + } + splci->li_bchannel_id = 3 - plci->li_bchannel_id; + li_config_table[a->li_base + (2 - plci->li_bchannel_id)].plci = splci; + dbug (1, dprintf ("[%06lx] %s,%d: adv_voice_set_bchannel_id_esc %d", + (dword)((splci->Id << 8) | UnMapController (splci->adapter->Id)), + (char *)(FILE_), __LINE__, splci->li_bchannel_id)); + } + } + if (li_config_table[a->li_base + (plci->li_bchannel_id - 1)].plci == NULL) + li_config_table[a->li_base + (plci->li_bchannel_id - 1)].plci = plci; + } + } + if ((old_id == 0) && (plci->li_bchannel_id != 0) + && (li_config_table[a->li_base + (plci->li_bchannel_id - 1)].plci == plci)) + { + mixer_clear_config (plci); + } + dbug (1, dprintf ("[%06lx] %s,%d: mixer_set_bchannel_id_esc %d %d", + (dword)((plci->Id << 8) | UnMapController (plci->adapter->Id)), + (char *)(FILE_), __LINE__, bchannel_id, plci->li_bchannel_id)); +} + + +static void mixer_set_bchannel_id (PLCI *plci, byte *chi) +{ + DIVA_CAPI_ADAPTER *a; + PLCI *splci; + byte ch, old_id; + + a = plci->adapter; + old_id = plci->li_bchannel_id; + ch = chi_to_channel (chi, NULL); + if (!(ch & 0x80)) + { + if (a->li_pri) + { + if ((old_id != 0) && (li_config_table[a->li_base + (old_id - 1)].plci == plci)) + li_config_table[a->li_base + (old_id - 1)].plci = NULL; + plci->li_bchannel_id = (ch & 0x1f) + 1; + if (li_config_table[a->li_base + (plci->li_bchannel_id - 1)].plci == NULL) + li_config_table[a->li_base + (plci->li_bchannel_id - 1)].plci = plci; + } + else + { + if (((ch & 0x1f) == 1) || ((ch & 0x1f) == 2)) + { + if ((old_id != 0) && (li_config_table[a->li_base + (old_id - 1)].plci == plci)) + li_config_table[a->li_base + (old_id - 1)].plci = NULL; + plci->li_bchannel_id = ch & 0x1f; + if ((a->AdvSignalPLCI != NULL) && (a->AdvSignalPLCI != plci) && (a->AdvSignalPLCI->tel == ADV_VOICE)) + { + splci = a->AdvSignalPLCI; + if (li_config_table[a->li_base + (2 - plci->li_bchannel_id)].plci == NULL) + { + if ((splci->li_bchannel_id != 0) + && (li_config_table[a->li_base + (splci->li_bchannel_id - 1)].plci == splci)) + { + li_config_table[a->li_base + (splci->li_bchannel_id - 1)].plci = NULL; + } + splci->li_bchannel_id = 3 - plci->li_bchannel_id; + li_config_table[a->li_base + (2 - plci->li_bchannel_id)].plci = splci; + dbug (1, dprintf ("[%06lx] %s,%d: adv_voice_set_bchannel_id %d", + (dword)((splci->Id << 8) | UnMapController (splci->adapter->Id)), + (char *)(FILE_), __LINE__, splci->li_bchannel_id)); + } + } + if (li_config_table[a->li_base + (plci->li_bchannel_id - 1)].plci == NULL) + li_config_table[a->li_base + (plci->li_bchannel_id - 1)].plci = plci; + } + } + } + if ((old_id == 0) && (plci->li_bchannel_id != 0) + && (li_config_table[a->li_base + (plci->li_bchannel_id - 1)].plci == plci)) + { + mixer_clear_config (plci); + } + dbug (1, dprintf ("[%06lx] %s,%d: mixer_set_bchannel_id %02x %d", + (dword)((plci->Id << 8) | UnMapController (plci->adapter->Id)), + (char *)(FILE_), __LINE__, ch, plci->li_bchannel_id)); +} + + +#define MIXER_MAX_DUMP_CHANNELS 34 + +static void mixer_calculate_coefs (DIVA_CAPI_ADAPTER *a) +{ +static char hex_digit_table[0x10] = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'}; + word n, i, j; + char *p; + char hex_line[2 * MIXER_MAX_DUMP_CHANNELS + MIXER_MAX_DUMP_CHANNELS / 8 + 4]; + + dbug (1, dprintf ("[%06lx] %s,%d: mixer_calculate_coefs", + (dword)(UnMapController (a->Id)), (char *)(FILE_), __LINE__)); + + for (i = 0; i < li_total_channels; i++) + { + li_config_table[i].channel &= LI_CHANNEL_ADDRESSES_SET; + if (li_config_table[i].chflags != 0) + li_config_table[i].channel |= LI_CHANNEL_INVOLVED; + else + { + for (j = 0; j < li_total_channels; j++) + { + if (((li_config_table[i].flag_table[j]) != 0) + || ((li_config_table[j].flag_table[i]) != 0)) + { + li_config_table[i].channel |= LI_CHANNEL_INVOLVED; + } + if (((li_config_table[i].flag_table[j] & LI_FLAG_CONFERENCE) != 0) + || ((li_config_table[j].flag_table[i] & LI_FLAG_CONFERENCE) != 0)) + { + li_config_table[i].channel |= LI_CHANNEL_CONFERENCE; + } + } + } + } + for (i = 0; i < li_total_channels; i++) + { + for (j = 0; j < li_total_channels; j++) + { + li_config_table[i].coef_table[j] &= ~(LI_COEF_CH_CH | LI_COEF_CH_PC | LI_COEF_PC_CH | LI_COEF_PC_PC); + if (li_config_table[i].flag_table[j] & LI_FLAG_CONFERENCE) + li_config_table[i].coef_table[j] |= LI_COEF_CH_CH; + } + } + for (n = 0; n < li_total_channels; n++) + { + if (li_config_table[n].channel & LI_CHANNEL_CONFERENCE) + { + for (i = 0; i < li_total_channels; i++) + { + if (li_config_table[i].channel & LI_CHANNEL_CONFERENCE) + { + for (j = 0; j < li_total_channels; j++) + { + li_config_table[i].coef_table[j] |= + li_config_table[i].coef_table[n] & li_config_table[n].coef_table[j]; + } + } + } + } + } + for (i = 0; i < li_total_channels; i++) + { + if (li_config_table[i].channel & LI_CHANNEL_INVOLVED) + { + li_config_table[i].coef_table[i] &= ~LI_COEF_CH_CH; + for (j = 0; j < li_total_channels; j++) + { + if (li_config_table[i].coef_table[j] & LI_COEF_CH_CH) + li_config_table[i].flag_table[j] |= LI_FLAG_CONFERENCE; + } + if (li_config_table[i].flag_table[i] & LI_FLAG_CONFERENCE) + li_config_table[i].coef_table[i] |= LI_COEF_CH_CH; + } + } + for (i = 0; i < li_total_channels; i++) + { + if (li_config_table[i].channel & LI_CHANNEL_INVOLVED) + { + for (j = 0; j < li_total_channels; j++) + { + if (li_config_table[i].flag_table[j] & LI_FLAG_INTERCONNECT) + li_config_table[i].coef_table[j] |= LI_COEF_CH_CH; + if (li_config_table[i].flag_table[j] & LI_FLAG_MONITOR) + li_config_table[i].coef_table[j] |= LI_COEF_CH_PC; + if (li_config_table[i].flag_table[j] & LI_FLAG_MIX) + li_config_table[i].coef_table[j] |= LI_COEF_PC_CH; + if (li_config_table[i].flag_table[j] & LI_FLAG_PCCONNECT) + li_config_table[i].coef_table[j] |= LI_COEF_PC_PC; + } + if (li_config_table[i].chflags & LI_CHFLAG_MONITOR) + { + for (j = 0; j < li_total_channels; j++) + { + if (li_config_table[i].flag_table[j] & LI_FLAG_INTERCONNECT) + { + li_config_table[i].coef_table[j] |= LI_COEF_CH_PC; + if (li_config_table[j].chflags & LI_CHFLAG_MIX) + li_config_table[i].coef_table[j] |= LI_COEF_PC_CH | LI_COEF_PC_PC; + } + } + } + if (li_config_table[i].chflags & LI_CHFLAG_MIX) + { + for (j = 0; j < li_total_channels; j++) + { + if (li_config_table[j].flag_table[i] & LI_FLAG_INTERCONNECT) + li_config_table[j].coef_table[i] |= LI_COEF_PC_CH; + } + } + if (li_config_table[i].chflags & LI_CHFLAG_LOOP) + { + for (j = 0; j < li_total_channels; j++) + { + if (li_config_table[i].flag_table[j] & LI_FLAG_INTERCONNECT) + { + for (n = 0; n < li_total_channels; n++) + { + if (li_config_table[n].flag_table[i] & LI_FLAG_INTERCONNECT) + { + li_config_table[n].coef_table[j] |= LI_COEF_CH_CH; + if (li_config_table[j].chflags & LI_CHFLAG_MIX) + { + li_config_table[n].coef_table[j] |= LI_COEF_PC_CH; + if (li_config_table[n].chflags & LI_CHFLAG_MONITOR) + li_config_table[n].coef_table[j] |= LI_COEF_CH_PC | LI_COEF_PC_PC; + } + else if (li_config_table[n].chflags & LI_CHFLAG_MONITOR) + li_config_table[n].coef_table[j] |= LI_COEF_CH_PC; + } + } + } + } + } + } + } + for (i = 0; i < li_total_channels; i++) + { + if (li_config_table[i].channel & LI_CHANNEL_INVOLVED) + { + if (li_config_table[i].chflags & (LI_CHFLAG_MONITOR | LI_CHFLAG_MIX | LI_CHFLAG_LOOP)) + li_config_table[i].channel |= LI_CHANNEL_ACTIVE; + if (li_config_table[i].chflags & LI_CHFLAG_MONITOR) + li_config_table[i].channel |= LI_CHANNEL_RX_DATA; + if (li_config_table[i].chflags & LI_CHFLAG_MIX) + li_config_table[i].channel |= LI_CHANNEL_TX_DATA; + for (j = 0; j < li_total_channels; j++) + { + if ((li_config_table[i].flag_table[j] & + (LI_FLAG_INTERCONNECT | LI_FLAG_PCCONNECT | LI_FLAG_CONFERENCE | LI_FLAG_MONITOR)) + || (li_config_table[j].flag_table[i] & + (LI_FLAG_INTERCONNECT | LI_FLAG_PCCONNECT | LI_FLAG_CONFERENCE | LI_FLAG_ANNOUNCEMENT | LI_FLAG_MIX))) + { + li_config_table[i].channel |= LI_CHANNEL_ACTIVE; + } + if (li_config_table[i].flag_table[j] & (LI_FLAG_PCCONNECT | LI_FLAG_MONITOR)) + li_config_table[i].channel |= LI_CHANNEL_RX_DATA; + if (li_config_table[j].flag_table[i] & (LI_FLAG_PCCONNECT | LI_FLAG_ANNOUNCEMENT | LI_FLAG_MIX)) + li_config_table[i].channel |= LI_CHANNEL_TX_DATA; + } + if (!(li_config_table[i].channel & LI_CHANNEL_ACTIVE)) + { + li_config_table[i].coef_table[i] |= LI_COEF_PC_CH | LI_COEF_CH_PC; + li_config_table[i].channel |= LI_CHANNEL_TX_DATA | LI_CHANNEL_RX_DATA; + } + } + } + for (i = 0; i < li_total_channels; i++) + { + if (li_config_table[i].channel & LI_CHANNEL_INVOLVED) + { + j = 0; + while ((j < li_total_channels) && !(li_config_table[i].flag_table[j] & LI_FLAG_ANNOUNCEMENT)) + j++; + if (j < li_total_channels) + { + for (j = 0; j < li_total_channels; j++) + { + li_config_table[i].coef_table[j] &= ~(LI_COEF_CH_CH | LI_COEF_PC_CH); + if (li_config_table[i].flag_table[j] & LI_FLAG_ANNOUNCEMENT) + li_config_table[i].coef_table[j] |= LI_COEF_PC_CH; + } + } + } + } + n = li_total_channels; + if (n > MIXER_MAX_DUMP_CHANNELS) + n = MIXER_MAX_DUMP_CHANNELS; + p = hex_line; + for (j = 0; j < n; j++) + { + if ((j & 0x7) == 0) + *(p++) = ' '; + *(p++) = hex_digit_table[li_config_table[j].curchnl >> 4]; + *(p++) = hex_digit_table[li_config_table[j].curchnl & 0xf]; + } + *p = '\0'; + dbug (1, dprintf ("[%06lx] CURRENT %s", + (dword)(UnMapController (a->Id)), (char *) hex_line)); + p = hex_line; + for (j = 0; j < n; j++) + { + if ((j & 0x7) == 0) + *(p++) = ' '; + *(p++) = hex_digit_table[li_config_table[j].channel >> 4]; + *(p++) = hex_digit_table[li_config_table[j].channel & 0xf]; + } + *p = '\0'; + dbug (1, dprintf ("[%06lx] CHANNEL %s", + (dword)(UnMapController (a->Id)), (char *) hex_line)); + p = hex_line; + for (j = 0; j < n; j++) + { + if ((j & 0x7) == 0) + *(p++) = ' '; + *(p++) = hex_digit_table[li_config_table[j].chflags >> 4]; + *(p++) = hex_digit_table[li_config_table[j].chflags & 0xf]; + } + *p = '\0'; + dbug (1, dprintf ("[%06lx] CHFLAG %s", + (dword)(UnMapController (a->Id)), (char *) hex_line)); + for (i = 0; i < n; i++) + { + p = hex_line; + for (j = 0; j < n; j++) + { + if ((j & 0x7) == 0) + *(p++) = ' '; + *(p++) = hex_digit_table[li_config_table[i].flag_table[j] >> 4]; + *(p++) = hex_digit_table[li_config_table[i].flag_table[j] & 0xf]; + } + *p = '\0'; + dbug (1, dprintf ("[%06lx] FLAG[%02x]%s", + (dword)(UnMapController (a->Id)), i, (char *) hex_line)); + } + for (i = 0; i < n; i++) + { + p = hex_line; + for (j = 0; j < n; j++) + { + if ((j & 0x7) == 0) + *(p++) = ' '; + *(p++) = hex_digit_table[li_config_table[i].coef_table[j] >> 4]; + *(p++) = hex_digit_table[li_config_table[i].coef_table[j] & 0xf]; + } + *p = '\0'; + dbug (1, dprintf ("[%06lx] COEF[%02x]%s", + (dword)(UnMapController (a->Id)), i, (char *) hex_line)); + } +} + + +static struct +{ + byte mask; + byte line_flags; +} mixer_write_prog_pri[] = +{ + { LI_COEF_CH_CH, 0 }, + { LI_COEF_CH_PC, MIXER_COEF_LINE_TO_PC_FLAG }, + { LI_COEF_PC_CH, MIXER_COEF_LINE_FROM_PC_FLAG }, + { LI_COEF_PC_PC, MIXER_COEF_LINE_TO_PC_FLAG | MIXER_COEF_LINE_FROM_PC_FLAG } +}; + +static struct +{ + byte from_ch; + byte to_ch; + byte mask; + byte xconnect_override; +} mixer_write_prog_bri[] = +{ + { 0, 0, LI_COEF_CH_CH, 0x01 }, /* B to B */ + { 1, 0, LI_COEF_CH_CH, 0x01 }, /* Alt B to B */ + { 0, 0, LI_COEF_PC_CH, 0x80 }, /* PC to B */ + { 1, 0, LI_COEF_PC_CH, 0x01 }, /* Alt PC to B */ + { 2, 0, LI_COEF_CH_CH, 0x00 }, /* IC to B */ + { 3, 0, LI_COEF_CH_CH, 0x00 }, /* Alt IC to B */ + { 0, 0, LI_COEF_CH_PC, 0x80 }, /* B to PC */ + { 1, 0, LI_COEF_CH_PC, 0x01 }, /* Alt B to PC */ + { 0, 0, LI_COEF_PC_PC, 0x01 }, /* PC to PC */ + { 1, 0, LI_COEF_PC_PC, 0x01 }, /* Alt PC to PC */ + { 2, 0, LI_COEF_CH_PC, 0x00 }, /* IC to PC */ + { 3, 0, LI_COEF_CH_PC, 0x00 }, /* Alt IC to PC */ + { 0, 2, LI_COEF_CH_CH, 0x00 }, /* B to IC */ + { 1, 2, LI_COEF_CH_CH, 0x00 }, /* Alt B to IC */ + { 0, 2, LI_COEF_PC_CH, 0x00 }, /* PC to IC */ + { 1, 2, LI_COEF_PC_CH, 0x00 }, /* Alt PC to IC */ + { 2, 2, LI_COEF_CH_CH, 0x00 }, /* IC to IC */ + { 3, 2, LI_COEF_CH_CH, 0x00 }, /* Alt IC to IC */ + { 1, 1, LI_COEF_CH_CH, 0x01 }, /* Alt B to Alt B */ + { 0, 1, LI_COEF_CH_CH, 0x01 }, /* B to Alt B */ + { 1, 1, LI_COEF_PC_CH, 0x80 }, /* Alt PC to Alt B */ + { 0, 1, LI_COEF_PC_CH, 0x01 }, /* PC to Alt B */ + { 3, 1, LI_COEF_CH_CH, 0x00 }, /* Alt IC to Alt B */ + { 2, 1, LI_COEF_CH_CH, 0x00 }, /* IC to Alt B */ + { 1, 1, LI_COEF_CH_PC, 0x80 }, /* Alt B to Alt PC */ + { 0, 1, LI_COEF_CH_PC, 0x01 }, /* B to Alt PC */ + { 1, 1, LI_COEF_PC_PC, 0x01 }, /* Alt PC to Alt PC */ + { 0, 1, LI_COEF_PC_PC, 0x01 }, /* PC to Alt PC */ + { 3, 1, LI_COEF_CH_PC, 0x00 }, /* Alt IC to Alt PC */ + { 2, 1, LI_COEF_CH_PC, 0x00 }, /* IC to Alt PC */ + { 1, 3, LI_COEF_CH_CH, 0x00 }, /* Alt B to Alt IC */ + { 0, 3, LI_COEF_CH_CH, 0x00 }, /* B to Alt IC */ + { 1, 3, LI_COEF_PC_CH, 0x00 }, /* Alt PC to Alt IC */ + { 0, 3, LI_COEF_PC_CH, 0x00 }, /* PC to Alt IC */ + { 3, 3, LI_COEF_CH_CH, 0x00 }, /* Alt IC to Alt IC */ + { 2, 3, LI_COEF_CH_CH, 0x00 } /* IC to Alt IC */ +}; + +static byte mixer_swapped_index_bri[] = +{ + 18, /* B to B */ + 19, /* Alt B to B */ + 20, /* PC to B */ + 21, /* Alt PC to B */ + 22, /* IC to B */ + 23, /* Alt IC to B */ + 24, /* B to PC */ + 25, /* Alt B to PC */ + 26, /* PC to PC */ + 27, /* Alt PC to PC */ + 28, /* IC to PC */ + 29, /* Alt IC to PC */ + 30, /* B to IC */ + 31, /* Alt B to IC */ + 32, /* PC to IC */ + 33, /* Alt PC to IC */ + 34, /* IC to IC */ + 35, /* Alt IC to IC */ + 0, /* Alt B to Alt B */ + 1, /* B to Alt B */ + 2, /* Alt PC to Alt B */ + 3, /* PC to Alt B */ + 4, /* Alt IC to Alt B */ + 5, /* IC to Alt B */ + 6, /* Alt B to Alt PC */ + 7, /* B to Alt PC */ + 8, /* Alt PC to Alt PC */ + 9, /* PC to Alt PC */ + 10, /* Alt IC to Alt PC */ + 11, /* IC to Alt PC */ + 12, /* Alt B to Alt IC */ + 13, /* B to Alt IC */ + 14, /* Alt PC to Alt IC */ + 15, /* PC to Alt IC */ + 16, /* Alt IC to Alt IC */ + 17 /* IC to Alt IC */ +}; + +static struct +{ + byte mask; + byte from_pc; + byte to_pc; +} xconnect_write_prog[] = +{ + { LI_COEF_CH_CH, FALSE, FALSE }, + { LI_COEF_CH_PC, FALSE, TRUE }, + { LI_COEF_PC_CH, TRUE, FALSE }, + { LI_COEF_PC_PC, TRUE, TRUE } +}; + + +static void xconnect_query_addresses (PLCI *plci) +{ + DIVA_CAPI_ADAPTER *a; + word w, ch; + byte *p; + + dbug (1, dprintf ("[%06lx] %s,%d: xconnect_query_addresses", + (dword)((plci->Id << 8) | UnMapController (plci->adapter->Id)), + (char *)(FILE_), __LINE__)); + + a = plci->adapter; + if (a->li_pri && ((plci->li_bchannel_id == 0) + || (li_config_table[a->li_base + (plci->li_bchannel_id - 1)].plci != plci))) + { + dbug (1, dprintf ("[%06x] %s,%d: Channel id wiped out", + (dword)((plci->Id << 8) | UnMapController (plci->adapter->Id)), + (char *)(FILE_), __LINE__)); + return; + } + p = plci->internal_req_buffer; + ch = (a->li_pri) ? plci->li_bchannel_id - 1 : 0; + *(p++) = UDATA_REQUEST_XCONNECT_FROM; + w = ch; + *(p++) = (byte) w; + *(p++) = (byte)(w >> 8); + w = ch | XCONNECT_CHANNEL_PORT_PC; + *(p++) = (byte) w; + *(p++) = (byte)(w >> 8); + plci->NData[0].P = plci->internal_req_buffer; + plci->NData[0].PLength = p - plci->internal_req_buffer; + plci->NL.X = plci->NData; + plci->NL.ReqCh = 0; + plci->NL.Req = plci->nl_req = (byte) N_UDATA; + plci->adapter->request (&plci->NL); +} + + +static void xconnect_write_coefs (PLCI *plci, word internal_command) +{ + + dbug (1, dprintf ("[%06lx] %s,%d: xconnect_write_coefs %04x", + (dword)((plci->Id << 8) | UnMapController (plci->adapter->Id)), + (char *)(FILE_), __LINE__, internal_command)); + + plci->li_write_command = internal_command; + plci->li_write_channel = 0; +} + + +static byte xconnect_write_coefs_process (dword Id, PLCI *plci, byte Rc) +{ + DIVA_CAPI_ADAPTER *a; + word w, n, i, j, r, s, to_ch; + dword d; + byte *p; + struct xconnect_transfer_address_s *transfer_address; + byte ch_map[MIXER_CHANNELS_BRI]; + + dbug (1, dprintf ("[%06x] %s,%d: xconnect_write_coefs_process %02x %d", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc, plci->li_write_channel)); + + a = plci->adapter; + if ((plci->li_bchannel_id == 0) + || (li_config_table[a->li_base + (plci->li_bchannel_id - 1)].plci != plci)) + { + dbug (1, dprintf ("[%06x] %s,%d: Channel id wiped out", + UnMapId (Id), (char *)(FILE_), __LINE__)); + return (TRUE); + } + i = a->li_base + (plci->li_bchannel_id - 1); + j = plci->li_write_channel; + p = plci->internal_req_buffer; + if (j != 0) + { + if ((Rc != OK) && (Rc != OK_FC)) + { + dbug (1, dprintf ("[%06lx] %s,%d: LI write coefs failed %02x", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc)); + return (FALSE); + } + } + if (li_config_table[i].adapter->manufacturer_features & MANUFACTURER_FEATURE_XCONNECT) + { + r = 0; + s = 0; + if (j < li_total_channels) + { + if (li_config_table[i].channel & LI_CHANNEL_ADDRESSES_SET) + { + s = ((li_config_table[i].send_b.card_address.low | li_config_table[i].send_b.card_address.high) ? + (LI_COEF_CH_CH | LI_COEF_CH_PC | LI_COEF_PC_CH | LI_COEF_PC_PC) : (LI_COEF_CH_PC | LI_COEF_PC_PC)) & + ((li_config_table[i].send_pc.card_address.low | li_config_table[i].send_pc.card_address.high) ? + (LI_COEF_CH_CH | LI_COEF_CH_PC | LI_COEF_PC_CH | LI_COEF_PC_PC) : (LI_COEF_CH_CH | LI_COEF_PC_CH)); + } + r = ((li_config_table[i].coef_table[j] & 0xf) ^ (li_config_table[i].coef_table[j] >> 4)); + while ((j < li_total_channels) + && ((r == 0) + || (!(li_config_table[j].channel & LI_CHANNEL_ADDRESSES_SET)) + || (!li_config_table[j].adapter->li_pri + && (j >= li_config_table[j].adapter->li_base + MIXER_BCHANNELS_BRI)) + || (((li_config_table[j].send_b.card_address.low != li_config_table[i].send_b.card_address.low) + || (li_config_table[j].send_b.card_address.high != li_config_table[i].send_b.card_address.high)) + && (!(a->manufacturer_features & MANUFACTURER_FEATURE_DMACONNECT) + || !(li_config_table[j].adapter->manufacturer_features & MANUFACTURER_FEATURE_DMACONNECT))) + || ((li_config_table[j].adapter->li_base != a->li_base) + && !(r & s & + ((li_config_table[j].send_b.card_address.low | li_config_table[j].send_b.card_address.high) ? + (LI_COEF_CH_CH | LI_COEF_CH_PC | LI_COEF_PC_CH | LI_COEF_PC_PC) : (LI_COEF_PC_CH | LI_COEF_PC_PC)) & + ((li_config_table[j].send_pc.card_address.low | li_config_table[j].send_pc.card_address.high) ? + (LI_COEF_CH_CH | LI_COEF_CH_PC | LI_COEF_PC_CH | LI_COEF_PC_PC) : (LI_COEF_CH_CH | LI_COEF_CH_PC)))))) + { + j++; + if (j < li_total_channels) + r = ((li_config_table[i].coef_table[j] & 0xf) ^ (li_config_table[i].coef_table[j] >> 4)); + } + } + if (j < li_total_channels) + { + plci->internal_command = plci->li_write_command; + if (plci_nl_busy (plci)) + return (TRUE); + to_ch = (a->li_pri) ? plci->li_bchannel_id - 1 : 0; + *(p++) = UDATA_REQUEST_XCONNECT_TO; + do + { + if (li_config_table[j].adapter->li_base != a->li_base) + { + r &= s & + ((li_config_table[j].send_b.card_address.low | li_config_table[j].send_b.card_address.high) ? + (LI_COEF_CH_CH | LI_COEF_CH_PC | LI_COEF_PC_CH | LI_COEF_PC_PC) : (LI_COEF_PC_CH | LI_COEF_PC_PC)) & + ((li_config_table[j].send_pc.card_address.low | li_config_table[j].send_pc.card_address.high) ? + (LI_COEF_CH_CH | LI_COEF_CH_PC | LI_COEF_PC_CH | LI_COEF_PC_PC) : (LI_COEF_CH_CH | LI_COEF_CH_PC)); + } + n = 0; + do + { + if (r & xconnect_write_prog[n].mask) + { + if (xconnect_write_prog[n].from_pc) + transfer_address = &(li_config_table[j].send_pc); + else + transfer_address = &(li_config_table[j].send_b); + d = transfer_address->card_address.low; + *(p++) = (byte) d; + *(p++) = (byte)(d >> 8); + *(p++) = (byte)(d >> 16); + *(p++) = (byte)(d >> 24); + d = transfer_address->card_address.high; + *(p++) = (byte) d; + *(p++) = (byte)(d >> 8); + *(p++) = (byte)(d >> 16); + *(p++) = (byte)(d >> 24); + d = transfer_address->offset; + *(p++) = (byte) d; + *(p++) = (byte)(d >> 8); + *(p++) = (byte)(d >> 16); + *(p++) = (byte)(d >> 24); + w = xconnect_write_prog[n].to_pc ? to_ch | XCONNECT_CHANNEL_PORT_PC : to_ch; + *(p++) = (byte) w; + *(p++) = (byte)(w >> 8); + w = ((li_config_table[i].coef_table[j] & xconnect_write_prog[n].mask) == 0) ? 0x01 : + (li_config_table[i].adapter->u_law ? + (li_config_table[j].adapter->u_law ? 0x80 : 0x86) : + (li_config_table[j].adapter->u_law ? 0x7a : 0x80)); + *(p++) = (byte) w; + *(p++) = (byte) 0; + li_config_table[i].coef_table[j] ^= xconnect_write_prog[n].mask << 4; + } + n++; + } while ((n < sizeof(xconnect_write_prog) / sizeof(xconnect_write_prog[0])) + && ((p - plci->internal_req_buffer) + 16 < INTERNAL_REQ_BUFFER_SIZE)); + if (n == sizeof(xconnect_write_prog) / sizeof(xconnect_write_prog[0])) + { + do + { + j++; + if (j < li_total_channels) + r = ((li_config_table[i].coef_table[j] & 0xf) ^ (li_config_table[i].coef_table[j] >> 4)); + } while ((j < li_total_channels) + && ((r == 0) + || (!(li_config_table[j].channel & LI_CHANNEL_ADDRESSES_SET)) + || (!li_config_table[j].adapter->li_pri + && (j >= li_config_table[j].adapter->li_base + MIXER_BCHANNELS_BRI)) + || (((li_config_table[j].send_b.card_address.low != li_config_table[i].send_b.card_address.low) + || (li_config_table[j].send_b.card_address.high != li_config_table[i].send_b.card_address.high)) + && (!(a->manufacturer_features & MANUFACTURER_FEATURE_DMACONNECT) + || !(li_config_table[j].adapter->manufacturer_features & MANUFACTURER_FEATURE_DMACONNECT))) + || ((li_config_table[j].adapter->li_base != a->li_base) + && !(r & s & + ((li_config_table[j].send_b.card_address.low | li_config_table[j].send_b.card_address.high) ? + (LI_COEF_CH_CH | LI_COEF_CH_PC | LI_COEF_PC_CH | LI_COEF_PC_PC) : (LI_COEF_PC_CH | LI_COEF_PC_PC)) & + ((li_config_table[j].send_pc.card_address.low | li_config_table[j].send_pc.card_address.high) ? + (LI_COEF_CH_CH | LI_COEF_CH_PC | LI_COEF_PC_CH | LI_COEF_PC_PC) : (LI_COEF_CH_CH | LI_COEF_CH_PC)))))); + } + } while ((j < li_total_channels) + && ((p - plci->internal_req_buffer) + 16 < INTERNAL_REQ_BUFFER_SIZE)); + } + else if (j == li_total_channels) + { + plci->internal_command = plci->li_write_command; + if (plci_nl_busy (plci)) + return (TRUE); + if (a->li_pri) + { + *(p++) = UDATA_REQUEST_SET_MIXER_COEFS_PRI_SYNC; + w = 0; + if (li_config_table[i].channel & LI_CHANNEL_TX_DATA) + w |= MIXER_FEATURE_ENABLE_TX_DATA; + if (li_config_table[i].channel & LI_CHANNEL_RX_DATA) + w |= MIXER_FEATURE_ENABLE_RX_DATA; + *(p++) = (byte) w; + *(p++) = (byte)(w >> 8); + } + else + { + *(p++) = UDATA_REQUEST_SET_MIXER_COEFS_BRI; + w = 0; + if ((plci->tel == ADV_VOICE) && (plci == a->AdvSignalPLCI) + && (ADV_VOICE_NEW_COEF_BASE + sizeof(word) <= a->adv_voice_coef_length)) + { + w = GET_WORD (a->adv_voice_coef_buffer + ADV_VOICE_NEW_COEF_BASE); + } + if (li_config_table[i].channel & LI_CHANNEL_TX_DATA) + w |= MIXER_FEATURE_ENABLE_TX_DATA; + if (li_config_table[i].channel & LI_CHANNEL_RX_DATA) + w |= MIXER_FEATURE_ENABLE_RX_DATA; + *(p++) = (byte) w; + *(p++) = (byte)(w >> 8); + for (j = 0; j < sizeof(ch_map); j += 2) + { + if (plci->li_bchannel_id == 2) + { + ch_map[j] = (byte)(j+1); + ch_map[j+1] = (byte) j; + } + else + { + ch_map[j] = (byte) j; + ch_map[j+1] = (byte)(j+1); + } + } + for (n = 0; n < sizeof(mixer_write_prog_bri) / sizeof(mixer_write_prog_bri[0]); n++) + { + i = a->li_base + ch_map[mixer_write_prog_bri[n].to_ch]; + j = a->li_base + ch_map[mixer_write_prog_bri[n].from_ch]; + if (li_config_table[i].channel & li_config_table[j].channel & LI_CHANNEL_INVOLVED) + { + *p = (mixer_write_prog_bri[n].xconnect_override != 0) ? + mixer_write_prog_bri[n].xconnect_override : + ((li_config_table[i].coef_table[j] & mixer_write_prog_bri[n].mask) ? 0x80 : 0x01); + if ((i >= a->li_base + MIXER_BCHANNELS_BRI) || (j >= a->li_base + MIXER_BCHANNELS_BRI)) + { + w = ((li_config_table[i].coef_table[j] & 0xf) ^ (li_config_table[i].coef_table[j] >> 4)); + li_config_table[i].coef_table[j] ^= (w & mixer_write_prog_bri[n].mask) << 4; + } + } + else + { + *p = 0x00; + if ((a->AdvSignalPLCI != NULL) && (a->AdvSignalPLCI->tel == ADV_VOICE)) + { + w = (plci == a->AdvSignalPLCI) ? n : mixer_swapped_index_bri[n]; + if (ADV_VOICE_NEW_COEF_BASE + sizeof(word) + w < a->adv_voice_coef_length) + *p = a->adv_voice_coef_buffer[ADV_VOICE_NEW_COEF_BASE + sizeof(word) + w]; + } + } + p++; + } + } + j = li_total_channels + 1; + } + } + else + { + if (j <= li_total_channels) + { + plci->internal_command = plci->li_write_command; + if (plci_nl_busy (plci)) + return (TRUE); + if (j < a->li_base) + j = a->li_base; + if (a->li_pri) + { + *(p++) = UDATA_REQUEST_SET_MIXER_COEFS_PRI_SYNC; + w = 0; + if (li_config_table[i].channel & LI_CHANNEL_TX_DATA) + w |= MIXER_FEATURE_ENABLE_TX_DATA; + if (li_config_table[i].channel & LI_CHANNEL_RX_DATA) + w |= MIXER_FEATURE_ENABLE_RX_DATA; + *(p++) = (byte) w; + *(p++) = (byte)(w >> 8); + for (n = 0; n < sizeof(mixer_write_prog_pri) / sizeof(mixer_write_prog_pri[0]); n++) + { + *(p++) = (byte)((plci->li_bchannel_id - 1) | mixer_write_prog_pri[n].line_flags); + for (j = a->li_base; j < a->li_base + MIXER_CHANNELS_PRI; j++) + { + w = ((li_config_table[i].coef_table[j] & 0xf) ^ (li_config_table[i].coef_table[j] >> 4)); + if (w & mixer_write_prog_pri[n].mask) + { + *(p++) = (li_config_table[i].coef_table[j] & mixer_write_prog_pri[n].mask) ? 0x80 : 0x01; + li_config_table[i].coef_table[j] ^= mixer_write_prog_pri[n].mask << 4; + } + else + *(p++) = 0x00; + } + *(p++) = (byte)((plci->li_bchannel_id - 1) | MIXER_COEF_LINE_ROW_FLAG | mixer_write_prog_pri[n].line_flags); + for (j = a->li_base; j < a->li_base + MIXER_CHANNELS_PRI; j++) + { + w = ((li_config_table[j].coef_table[i] & 0xf) ^ (li_config_table[j].coef_table[i] >> 4)); + if (w & mixer_write_prog_pri[n].mask) + { + *(p++) = (li_config_table[j].coef_table[i] & mixer_write_prog_pri[n].mask) ? 0x80 : 0x01; + li_config_table[j].coef_table[i] ^= mixer_write_prog_pri[n].mask << 4; + } + else + *(p++) = 0x00; + } + } + } + else + { + *(p++) = UDATA_REQUEST_SET_MIXER_COEFS_BRI; + w = 0; + if ((plci->tel == ADV_VOICE) && (plci == a->AdvSignalPLCI) + && (ADV_VOICE_NEW_COEF_BASE + sizeof(word) <= a->adv_voice_coef_length)) + { + w = GET_WORD (a->adv_voice_coef_buffer + ADV_VOICE_NEW_COEF_BASE); + } + if (li_config_table[i].channel & LI_CHANNEL_TX_DATA) + w |= MIXER_FEATURE_ENABLE_TX_DATA; + if (li_config_table[i].channel & LI_CHANNEL_RX_DATA) + w |= MIXER_FEATURE_ENABLE_RX_DATA; + *(p++) = (byte) w; + *(p++) = (byte)(w >> 8); + for (j = 0; j < sizeof(ch_map); j += 2) + { + if (plci->li_bchannel_id == 2) + { + ch_map[j] = (byte)(j+1); + ch_map[j+1] = (byte) j; + } + else + { + ch_map[j] = (byte) j; + ch_map[j+1] = (byte)(j+1); + } + } + for (n = 0; n < sizeof(mixer_write_prog_bri) / sizeof(mixer_write_prog_bri[0]); n++) + { + i = a->li_base + ch_map[mixer_write_prog_bri[n].to_ch]; + j = a->li_base + ch_map[mixer_write_prog_bri[n].from_ch]; + if (li_config_table[i].channel & li_config_table[j].channel & LI_CHANNEL_INVOLVED) + { + *p = ((li_config_table[i].coef_table[j] & mixer_write_prog_bri[n].mask) ? 0x80 : 0x01); + w = ((li_config_table[i].coef_table[j] & 0xf) ^ (li_config_table[i].coef_table[j] >> 4)); + li_config_table[i].coef_table[j] ^= (w & mixer_write_prog_bri[n].mask) << 4; + } + else + { + *p = 0x00; + if ((a->AdvSignalPLCI != NULL) && (a->AdvSignalPLCI->tel == ADV_VOICE)) + { + w = (plci == a->AdvSignalPLCI) ? n : mixer_swapped_index_bri[n]; + if (ADV_VOICE_NEW_COEF_BASE + sizeof(word) + w < a->adv_voice_coef_length) + *p = a->adv_voice_coef_buffer[ADV_VOICE_NEW_COEF_BASE + sizeof(word) + w]; + } + } + p++; + } + } + j = li_total_channels + 1; + } + } + plci->li_write_channel = j; + if (p != plci->internal_req_buffer) + { + plci->NData[0].P = plci->internal_req_buffer; + plci->NData[0].PLength = p - plci->internal_req_buffer; + plci->NL.X = plci->NData; + plci->NL.ReqCh = 0; + plci->NL.Req = plci->nl_req = (byte) N_UDATA; + plci->adapter->request (&plci->NL); + } + return (TRUE); +} + + +static void mixer_notify_update (PLCI *plci, byte others) +{ + DIVA_CAPI_ADAPTER *a; + word i, w; + PLCI *notify_plci; + byte msg[sizeof(CAPI_MSG_HEADER) + 6]; + + dbug (1, dprintf ("[%06lx] %s,%d: mixer_notify_update %d", + (dword)((plci->Id << 8) | UnMapController (plci->adapter->Id)), + (char *)(FILE_), __LINE__, others)); + + a = plci->adapter; + if (a->profile.Global_Options & GL_LINE_INTERCONNECT_SUPPORTED) + { + if (others) + plci->li_notify_update = TRUE; + i = 0; + do + { + notify_plci = NULL; + if (others) + { + while ((i < li_total_channels) && (li_config_table[i].plci == NULL)) + i++; + if (i < li_total_channels) + notify_plci = li_config_table[i++].plci; + } + else + { + if ((plci->li_bchannel_id != 0) + && (li_config_table[a->li_base + (plci->li_bchannel_id - 1)].plci == plci)) + { + notify_plci = plci; + } + } + if ((notify_plci != NULL) + && !notify_plci->li_notify_update + && (notify_plci->appl != NULL) + && (notify_plci->State) + && notify_plci->NL.Id && !notify_plci->nl_remove_id) + { + notify_plci->li_notify_update = TRUE; + ((CAPI_MSG *) msg)->header.length = 18; + ((CAPI_MSG *) msg)->header.appl_id = notify_plci->appl->Id; + ((CAPI_MSG *) msg)->header.command = _FACILITY_R; + ((CAPI_MSG *) msg)->header.number = 0; + ((CAPI_MSG *) msg)->header.controller = notify_plci->adapter->Id; + ((CAPI_MSG *) msg)->header.plci = notify_plci->Id; + ((CAPI_MSG *) msg)->header.ncci = 0; + ((CAPI_MSG *) msg)->info.facility_req.Selector = SELECTOR_LINE_INTERCONNECT; + ((CAPI_MSG *) msg)->info.facility_req.structs[0] = 3; + PUT_WORD (&(((CAPI_MSG *) msg)->info.facility_req.structs[1]), LI_REQ_SILENT_UPDATE); + ((CAPI_MSG *) msg)->info.facility_req.structs[3] = 0; + w = api_put (notify_plci->appl, (CAPI_MSG *) msg); + if (w != _QUEUE_FULL) + { + if (w != 0) + { + dbug (1, dprintf ("[%06lx] %s,%d: Interconnect notify failed %06x %d", + (dword)((plci->Id << 8) | UnMapController (plci->adapter->Id)), + (char *)(FILE_), __LINE__, + (dword)((notify_plci->Id << 8) | UnMapController (notify_plci->adapter->Id)), w)); + } + notify_plci->li_notify_update = FALSE; + } + } + } while (others && (notify_plci != NULL)); + if (others) + plci->li_notify_update = FALSE; + } +} + + +static void mixer_clear_config (PLCI *plci) +{ + DIVA_CAPI_ADAPTER *a; + word i, j; + + dbug (1, dprintf ("[%06lx] %s,%d: mixer_clear_config", + (dword)((plci->Id << 8) | UnMapController (plci->adapter->Id)), + (char *)(FILE_), __LINE__)); + + plci->li_notify_update = FALSE; + plci->li_plci_b_write_pos = 0; + plci->li_plci_b_read_pos = 0; + plci->li_plci_b_req_pos = 0; + a = plci->adapter; + if ((plci->li_bchannel_id != 0) + && (li_config_table[a->li_base + (plci->li_bchannel_id - 1)].plci == plci)) + { + i = a->li_base + (plci->li_bchannel_id - 1); + li_config_table[i].curchnl = 0; + li_config_table[i].channel = 0; + li_config_table[i].chflags = 0; + for (j = 0; j < li_total_channels; j++) + { + li_config_table[j].flag_table[i] = 0; + li_config_table[i].flag_table[j] = 0; + li_config_table[i].coef_table[j] = 0; + li_config_table[j].coef_table[i] = 0; + } + if (!a->li_pri) + { + li_config_table[i].coef_table[i] |= LI_COEF_CH_PC_SET | LI_COEF_PC_CH_SET; + if ((plci->tel == ADV_VOICE) && (plci == a->AdvSignalPLCI)) + { + i = a->li_base + MIXER_IC_CHANNEL_BASE + (plci->li_bchannel_id - 1); + li_config_table[i].curchnl = 0; + li_config_table[i].channel = 0; + li_config_table[i].chflags = 0; + for (j = 0; j < li_total_channels; j++) + { + li_config_table[i].flag_table[j] = 0; + li_config_table[j].flag_table[i] = 0; + li_config_table[i].coef_table[j] = 0; + li_config_table[j].coef_table[i] = 0; + } + if (a->manufacturer_features & MANUFACTURER_FEATURE_SLAVE_CODEC) + { + i = a->li_base + MIXER_IC_CHANNEL_BASE + (2 - plci->li_bchannel_id); + li_config_table[i].curchnl = 0; + li_config_table[i].channel = 0; + li_config_table[i].chflags = 0; + for (j = 0; j < li_total_channels; j++) + { + li_config_table[i].flag_table[j] = 0; + li_config_table[j].flag_table[i] = 0; + li_config_table[i].coef_table[j] = 0; + li_config_table[j].coef_table[i] = 0; + } + } + } + } + } +} + + +static void mixer_prepare_switch (dword Id, PLCI *plci) +{ + + dbug (1, dprintf ("[%06lx] %s,%d: mixer_prepare_switch", + UnMapId (Id), (char *)(FILE_), __LINE__)); + + do + { + mixer_indication_coefs_set (Id, plci); + } while (plci->li_plci_b_read_pos != plci->li_plci_b_req_pos); +} + + +static word mixer_save_config (dword Id, PLCI *plci, byte Rc) +{ + DIVA_CAPI_ADAPTER *a; + word i, j; + + dbug (1, dprintf ("[%06lx] %s,%d: mixer_save_config %02x %d", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc, plci->adjust_b_state)); + + a = plci->adapter; + if ((plci->li_bchannel_id != 0) + && (li_config_table[a->li_base + (plci->li_bchannel_id - 1)].plci == plci)) + { + i = a->li_base + (plci->li_bchannel_id - 1); + for (j = 0; j < li_total_channels; j++) + { + li_config_table[i].coef_table[j] &= 0xf; + li_config_table[j].coef_table[i] &= 0xf; + } + if (!a->li_pri) + li_config_table[i].coef_table[i] |= LI_COEF_CH_PC_SET | LI_COEF_PC_CH_SET; + } + return (GOOD); +} + + +static word mixer_restore_config (dword Id, PLCI *plci, byte Rc) +{ + DIVA_CAPI_ADAPTER *a; + word Info; + + dbug (1, dprintf ("[%06lx] %s,%d: mixer_restore_config %02x %d", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc, plci->adjust_b_state)); + + Info = GOOD; + a = plci->adapter; + if ((plci->B1_facilities & B1_FACILITY_MIXER) + && (plci->li_bchannel_id != 0) + && (li_config_table[a->li_base + (plci->li_bchannel_id - 1)].plci == plci)) + { + switch (plci->adjust_b_state) + { + case ADJUST_B_RESTORE_MIXER_1: + if (a->manufacturer_features & MANUFACTURER_FEATURE_XCONNECT) + { + plci->internal_command = plci->adjust_b_command; + if (plci_nl_busy (plci)) + { + plci->adjust_b_state = ADJUST_B_RESTORE_MIXER_1; + break; + } + xconnect_query_addresses (plci); + plci->adjust_b_state = ADJUST_B_RESTORE_MIXER_2; + break; + } + plci->adjust_b_state = ADJUST_B_RESTORE_MIXER_5; + Rc = OK; + case ADJUST_B_RESTORE_MIXER_2: + case ADJUST_B_RESTORE_MIXER_3: + case ADJUST_B_RESTORE_MIXER_4: + if ((Rc != OK) && (Rc != OK_FC) && (Rc != 0)) + { + dbug (1, dprintf ("[%06lx] %s,%d: Adjust B query addresses failed %02x", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc)); + Info = _WRONG_STATE; + break; + } + if (Rc == OK) + { + if (plci->adjust_b_state == ADJUST_B_RESTORE_MIXER_2) + plci->adjust_b_state = ADJUST_B_RESTORE_MIXER_3; + else if (plci->adjust_b_state == ADJUST_B_RESTORE_MIXER_4) + plci->adjust_b_state = ADJUST_B_RESTORE_MIXER_5; + } + else if (Rc == 0) + { + if (plci->adjust_b_state == ADJUST_B_RESTORE_MIXER_2) + plci->adjust_b_state = ADJUST_B_RESTORE_MIXER_4; + else if (plci->adjust_b_state == ADJUST_B_RESTORE_MIXER_3) + plci->adjust_b_state = ADJUST_B_RESTORE_MIXER_5; + } + if (plci->adjust_b_state != ADJUST_B_RESTORE_MIXER_5) + { + plci->internal_command = plci->adjust_b_command; + break; + } + case ADJUST_B_RESTORE_MIXER_5: + xconnect_write_coefs (plci, plci->adjust_b_command); + plci->adjust_b_state = ADJUST_B_RESTORE_MIXER_6; + Rc = OK; + case ADJUST_B_RESTORE_MIXER_6: + if (!xconnect_write_coefs_process (Id, plci, Rc)) + { + dbug (1, dprintf ("[%06lx] %s,%d: Write mixer coefs failed", + UnMapId (Id), (char *)(FILE_), __LINE__)); + Info = _FACILITY_NOT_SUPPORTED; + break; + } + if (plci->internal_command) + break; + plci->adjust_b_state = ADJUST_B_RESTORE_MIXER_7; + case ADJUST_B_RESTORE_MIXER_7: + break; + } + } + return (Info); +} + + +static void mixer_command (dword Id, PLCI *plci, byte Rc) +{ + DIVA_CAPI_ADAPTER *a; + word i, internal_command, Info; + + dbug (1, dprintf ("[%06lx] %s,%d: mixer_command %02x %04x %04x", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc, plci->internal_command, + plci->li_cmd)); + + Info = GOOD; + a = plci->adapter; + internal_command = plci->internal_command; + plci->internal_command = 0; + switch (plci->li_cmd) + { + case LI_REQ_CONNECT: + case LI_REQ_DISCONNECT: + case LI_REQ_SILENT_UPDATE: + switch (internal_command) + { + default: + if (plci->li_channel_bits & LI_CHANNEL_INVOLVED) + { + adjust_b1_resource (Id, plci, NULL, (word)(plci->B1_facilities | + B1_FACILITY_MIXER), MIXER_COMMAND_1); + } + case MIXER_COMMAND_1: + if (plci->li_channel_bits & LI_CHANNEL_INVOLVED) + { + if (adjust_b_process (Id, plci, Rc) != GOOD) + { + dbug (1, dprintf ("[%06lx] %s,%d: Load mixer failed", + UnMapId (Id), (char *)(FILE_), __LINE__)); + Info = _FACILITY_NOT_SUPPORTED; + break; + } + if (plci->internal_command) + return; + } + plci->li_plci_b_req_pos = plci->li_plci_b_write_pos; + if ((plci->li_channel_bits & LI_CHANNEL_INVOLVED) + || ((get_b1_facilities (plci, plci->B1_resource) & B1_FACILITY_MIXER) + && (add_b1_facilities (plci, plci->B1_resource, (word)(plci->B1_facilities & + ~B1_FACILITY_MIXER)) == plci->B1_resource))) + { + xconnect_write_coefs (plci, MIXER_COMMAND_2); + } + else + { + do + { + mixer_indication_coefs_set (Id, plci); + } while (plci->li_plci_b_read_pos != plci->li_plci_b_req_pos); + } + case MIXER_COMMAND_2: + if ((plci->li_channel_bits & LI_CHANNEL_INVOLVED) + || ((get_b1_facilities (plci, plci->B1_resource) & B1_FACILITY_MIXER) + && (add_b1_facilities (plci, plci->B1_resource, (word)(plci->B1_facilities & + ~B1_FACILITY_MIXER)) == plci->B1_resource))) + { + if (!xconnect_write_coefs_process (Id, plci, Rc)) + { + dbug (1, dprintf ("[%06lx] %s,%d: Write mixer coefs failed", + UnMapId (Id), (char *)(FILE_), __LINE__)); + if (plci->li_plci_b_write_pos != plci->li_plci_b_req_pos) + { + do + { + plci->li_plci_b_write_pos = (plci->li_plci_b_write_pos == 0) ? + LI_PLCI_B_QUEUE_ENTRIES-1 : plci->li_plci_b_write_pos - 1; + i = (plci->li_plci_b_write_pos == 0) ? + LI_PLCI_B_QUEUE_ENTRIES-1 : plci->li_plci_b_write_pos - 1; + } while ((plci->li_plci_b_write_pos != plci->li_plci_b_req_pos) + && !(plci->li_plci_b_queue[i] & LI_PLCI_B_LAST_FLAG)); + } + Info = _FACILITY_NOT_SUPPORTED; + break; + } + if (plci->internal_command) + return; + } + if (!(plci->li_channel_bits & LI_CHANNEL_INVOLVED)) + { + adjust_b1_resource (Id, plci, NULL, (word)(plci->B1_facilities & + ~B1_FACILITY_MIXER), MIXER_COMMAND_3); + } + case MIXER_COMMAND_3: + if (!(plci->li_channel_bits & LI_CHANNEL_INVOLVED)) + { + if (adjust_b_process (Id, plci, Rc) != GOOD) + { + dbug (1, dprintf ("[%06lx] %s,%d: Unload mixer failed", + UnMapId (Id), (char *)(FILE_), __LINE__)); + Info = _FACILITY_NOT_SUPPORTED; + break; + } + if (plci->internal_command) + return; + } + break; + } + break; + } + if ((plci->li_bchannel_id == 0) + || (li_config_table[a->li_base + (plci->li_bchannel_id - 1)].plci != plci)) + { + dbug (1, dprintf ("[%06x] %s,%d: Channel id wiped out %d", + UnMapId (Id), (char *)(FILE_), __LINE__, (int)(plci->li_bchannel_id))); + } + else + { + i = a->li_base + (plci->li_bchannel_id - 1); + li_config_table[i].curchnl = plci->li_channel_bits; + if (!a->li_pri && (plci->tel == ADV_VOICE) && (plci == a->AdvSignalPLCI)) + { + i = a->li_base + MIXER_IC_CHANNEL_BASE + (plci->li_bchannel_id - 1); + li_config_table[i].curchnl = plci->li_channel_bits; + if (a->manufacturer_features & MANUFACTURER_FEATURE_SLAVE_CODEC) + { + i = a->li_base + MIXER_IC_CHANNEL_BASE + (2 - plci->li_bchannel_id); + li_config_table[i].curchnl = plci->li_channel_bits; + } + } + } +} + + +static void li_update_connect (dword Id, DIVA_CAPI_ADAPTER *a, PLCI *plci, + dword plci_b_id, byte connect, dword li_flags) +{ + word i, ch_a, ch_a_v, ch_a_s, ch_b, ch_b_v, ch_b_s; + PLCI *plci_b; + DIVA_CAPI_ADAPTER *a_b; + + a_b = &(adapter[MapController ((byte)(plci_b_id & 0x7f)) - 1]); + plci_b = &(a_b->plci[((plci_b_id >> 8) & 0xff) - 1]); + ch_a = a->li_base + (plci->li_bchannel_id - 1); + if (!a->li_pri && (plci->tel == ADV_VOICE) + && (plci == a->AdvSignalPLCI) && (Id & EXT_CONTROLLER)) + { + ch_a_v = ch_a + MIXER_IC_CHANNEL_BASE; + ch_a_s = (a->manufacturer_features & MANUFACTURER_FEATURE_SLAVE_CODEC) ? + a->li_base + MIXER_IC_CHANNEL_BASE + (2 - plci->li_bchannel_id) : ch_a_v; + } + else + { + ch_a_v = ch_a; + ch_a_s = ch_a; + } + ch_b = a_b->li_base + (plci_b->li_bchannel_id - 1); + if (!a_b->li_pri && (plci_b->tel == ADV_VOICE) + && (plci_b == a_b->AdvSignalPLCI) && (plci_b_id & EXT_CONTROLLER)) + { + ch_b_v = ch_b + MIXER_IC_CHANNEL_BASE; + ch_b_s = (a_b->manufacturer_features & MANUFACTURER_FEATURE_SLAVE_CODEC) ? + a_b->li_base + MIXER_IC_CHANNEL_BASE + (2 - plci_b->li_bchannel_id) : ch_b_v; + } + else + { + ch_b_v = ch_b; + ch_b_s = ch_b; + } + if (connect) + { + li_config_table[ch_a].flag_table[ch_a_v] &= ~LI_FLAG_MONITOR; + li_config_table[ch_a].flag_table[ch_a_s] &= ~LI_FLAG_MONITOR; + li_config_table[ch_a_v].flag_table[ch_a] &= ~(LI_FLAG_ANNOUNCEMENT | LI_FLAG_MIX); + li_config_table[ch_a_s].flag_table[ch_a] &= ~(LI_FLAG_ANNOUNCEMENT | LI_FLAG_MIX); + } + li_config_table[ch_a].flag_table[ch_b_v] &= ~LI_FLAG_MONITOR; + li_config_table[ch_a].flag_table[ch_b_s] &= ~LI_FLAG_MONITOR; + li_config_table[ch_b_v].flag_table[ch_a] &= ~(LI_FLAG_ANNOUNCEMENT | LI_FLAG_MIX); + li_config_table[ch_b_s].flag_table[ch_a] &= ~(LI_FLAG_ANNOUNCEMENT | LI_FLAG_MIX); + if (ch_a_v == ch_b_v) + { + li_config_table[ch_a_v].flag_table[ch_b_v] &= ~LI_FLAG_CONFERENCE; + li_config_table[ch_a_s].flag_table[ch_b_s] &= ~LI_FLAG_CONFERENCE; + } + else + { + if (li_config_table[ch_a_v].flag_table[ch_b_v] & LI_FLAG_CONFERENCE) + { + for (i = 0; i < li_total_channels; i++) + { + if (i != ch_a_v) + li_config_table[ch_a_v].flag_table[i] &= ~LI_FLAG_CONFERENCE; + } + } + if (li_config_table[ch_a_s].flag_table[ch_b_v] & LI_FLAG_CONFERENCE) + { + for (i = 0; i < li_total_channels; i++) + { + if (i != ch_a_s) + li_config_table[ch_a_s].flag_table[i] &= ~LI_FLAG_CONFERENCE; + } + } + if (li_config_table[ch_b_v].flag_table[ch_a_v] & LI_FLAG_CONFERENCE) + { + for (i = 0; i < li_total_channels; i++) + { + if (i != ch_a_v) + li_config_table[i].flag_table[ch_a_v] &= ~LI_FLAG_CONFERENCE; + } + } + if (li_config_table[ch_b_v].flag_table[ch_a_s] & LI_FLAG_CONFERENCE) + { + for (i = 0; i < li_total_channels; i++) + { + if (i != ch_a_s) + li_config_table[i].flag_table[ch_a_s] &= ~LI_FLAG_CONFERENCE; + } + } + } + if (li_flags & LI_FLAG_CONFERENCE_A_B) + { + li_config_table[ch_b_v].flag_table[ch_a_v] |= LI_FLAG_CONFERENCE; + li_config_table[ch_b_s].flag_table[ch_a_v] |= LI_FLAG_CONFERENCE; + li_config_table[ch_b_v].flag_table[ch_a_s] |= LI_FLAG_CONFERENCE; + li_config_table[ch_b_s].flag_table[ch_a_s] |= LI_FLAG_CONFERENCE; + } + if (li_flags & LI_FLAG_CONFERENCE_B_A) + { + li_config_table[ch_a_v].flag_table[ch_b_v] |= LI_FLAG_CONFERENCE; + li_config_table[ch_a_v].flag_table[ch_b_s] |= LI_FLAG_CONFERENCE; + li_config_table[ch_a_s].flag_table[ch_b_v] |= LI_FLAG_CONFERENCE; + li_config_table[ch_a_s].flag_table[ch_b_s] |= LI_FLAG_CONFERENCE; + } + if (li_flags & LI_FLAG_MONITOR_A) + { + li_config_table[ch_a].flag_table[ch_a_v] |= LI_FLAG_MONITOR; + li_config_table[ch_a].flag_table[ch_a_s] |= LI_FLAG_MONITOR; + } + if (li_flags & LI_FLAG_MONITOR_B) + { + li_config_table[ch_a].flag_table[ch_b_v] |= LI_FLAG_MONITOR; + li_config_table[ch_a].flag_table[ch_b_s] |= LI_FLAG_MONITOR; + } + if (li_flags & LI_FLAG_ANNOUNCEMENT_A) + { + li_config_table[ch_a_v].flag_table[ch_a] |= LI_FLAG_ANNOUNCEMENT; + li_config_table[ch_a_s].flag_table[ch_a] |= LI_FLAG_ANNOUNCEMENT; + } + if (li_flags & LI_FLAG_ANNOUNCEMENT_B) + { + li_config_table[ch_b_v].flag_table[ch_a] |= LI_FLAG_ANNOUNCEMENT; + li_config_table[ch_b_s].flag_table[ch_a] |= LI_FLAG_ANNOUNCEMENT; + } + if (li_flags & LI_FLAG_MIX_A) + { + li_config_table[ch_a_v].flag_table[ch_a] |= LI_FLAG_MIX; + li_config_table[ch_a_s].flag_table[ch_a] |= LI_FLAG_MIX; + } + if (li_flags & LI_FLAG_MIX_B) + { + li_config_table[ch_b_v].flag_table[ch_a] |= LI_FLAG_MIX; + li_config_table[ch_b_s].flag_table[ch_a] |= LI_FLAG_MIX; + } + if (ch_a_v != ch_a_s) + { + li_config_table[ch_a_v].flag_table[ch_a_s] |= LI_FLAG_CONFERENCE; + li_config_table[ch_a_s].flag_table[ch_a_v] |= LI_FLAG_CONFERENCE; + } + if (ch_b_v != ch_b_s) + { + li_config_table[ch_b_v].flag_table[ch_b_s] |= LI_FLAG_CONFERENCE; + li_config_table[ch_b_s].flag_table[ch_b_v] |= LI_FLAG_CONFERENCE; + } +} + + +static void li2_update_connect (dword Id, DIVA_CAPI_ADAPTER *a, PLCI *plci, + dword plci_b_id, byte connect, dword li_flags) +{ + word ch_a, ch_a_v, ch_a_s, ch_b, ch_b_v, ch_b_s; + PLCI *plci_b; + DIVA_CAPI_ADAPTER *a_b; + + a_b = &(adapter[MapController ((byte)(plci_b_id & 0x7f)) - 1]); + plci_b = &(a_b->plci[((plci_b_id >> 8) & 0xff) - 1]); + ch_a = a->li_base + (plci->li_bchannel_id - 1); + if (!a->li_pri && (plci->tel == ADV_VOICE) + && (plci == a->AdvSignalPLCI) && (Id & EXT_CONTROLLER)) + { + ch_a_v = ch_a + MIXER_IC_CHANNEL_BASE; + ch_a_s = (a->manufacturer_features & MANUFACTURER_FEATURE_SLAVE_CODEC) ? + a->li_base + MIXER_IC_CHANNEL_BASE + (2 - plci->li_bchannel_id) : ch_a_v; + } + else + { + ch_a_v = ch_a; + ch_a_s = ch_a; + } + ch_b = a_b->li_base + (plci_b->li_bchannel_id - 1); + if (!a_b->li_pri && (plci_b->tel == ADV_VOICE) + && (plci_b == a_b->AdvSignalPLCI) && (plci_b_id & EXT_CONTROLLER)) + { + ch_b_v = ch_b + MIXER_IC_CHANNEL_BASE; + ch_b_s = (a_b->manufacturer_features & MANUFACTURER_FEATURE_SLAVE_CODEC) ? + a_b->li_base + MIXER_IC_CHANNEL_BASE + (2 - plci_b->li_bchannel_id) : ch_b_v; + } + else + { + ch_b_v = ch_b; + ch_b_s = ch_b; + } + if (connect) + { + li_config_table[ch_b].flag_table[ch_b_v] &= ~LI_FLAG_MONITOR; + li_config_table[ch_b].flag_table[ch_b_s] &= ~LI_FLAG_MONITOR; + li_config_table[ch_b_v].flag_table[ch_b] &= ~LI_FLAG_MIX; + li_config_table[ch_b_s].flag_table[ch_b] &= ~LI_FLAG_MIX; + li_config_table[ch_b].flag_table[ch_b] &= ~LI_FLAG_PCCONNECT; + li_config_table[ch_b].chflags &= ~(LI_CHFLAG_MONITOR | LI_CHFLAG_MIX | LI_CHFLAG_LOOP); + } + li_config_table[ch_b_v].flag_table[ch_a_v] &= ~(LI_FLAG_INTERCONNECT | LI_FLAG_CONFERENCE); + li_config_table[ch_b_s].flag_table[ch_a_v] &= ~(LI_FLAG_INTERCONNECT | LI_FLAG_CONFERENCE); + li_config_table[ch_b_v].flag_table[ch_a_s] &= ~(LI_FLAG_INTERCONNECT | LI_FLAG_CONFERENCE); + li_config_table[ch_b_s].flag_table[ch_a_s] &= ~(LI_FLAG_INTERCONNECT | LI_FLAG_CONFERENCE); + li_config_table[ch_a_v].flag_table[ch_b_v] &= ~(LI_FLAG_INTERCONNECT | LI_FLAG_CONFERENCE); + li_config_table[ch_a_v].flag_table[ch_b_s] &= ~(LI_FLAG_INTERCONNECT | LI_FLAG_CONFERENCE); + li_config_table[ch_a_s].flag_table[ch_b_v] &= ~(LI_FLAG_INTERCONNECT | LI_FLAG_CONFERENCE); + li_config_table[ch_a_s].flag_table[ch_b_s] &= ~(LI_FLAG_INTERCONNECT | LI_FLAG_CONFERENCE); + if (li_flags & LI2_FLAG_INTERCONNECT_A_B) + { + li_config_table[ch_b_v].flag_table[ch_a_v] |= LI_FLAG_INTERCONNECT; + li_config_table[ch_b_s].flag_table[ch_a_v] |= LI_FLAG_INTERCONNECT; + li_config_table[ch_b_v].flag_table[ch_a_s] |= LI_FLAG_INTERCONNECT; + li_config_table[ch_b_s].flag_table[ch_a_s] |= LI_FLAG_INTERCONNECT; + } + if (li_flags & LI2_FLAG_INTERCONNECT_B_A) + { + li_config_table[ch_a_v].flag_table[ch_b_v] |= LI_FLAG_INTERCONNECT; + li_config_table[ch_a_v].flag_table[ch_b_s] |= LI_FLAG_INTERCONNECT; + li_config_table[ch_a_s].flag_table[ch_b_v] |= LI_FLAG_INTERCONNECT; + li_config_table[ch_a_s].flag_table[ch_b_s] |= LI_FLAG_INTERCONNECT; + } + if (li_flags & LI2_FLAG_MONITOR_B) + { + li_config_table[ch_b].flag_table[ch_b_v] |= LI_FLAG_MONITOR; + li_config_table[ch_b].flag_table[ch_b_s] |= LI_FLAG_MONITOR; + } + if (li_flags & LI2_FLAG_MIX_B) + { + li_config_table[ch_b_v].flag_table[ch_b] |= LI_FLAG_MIX; + li_config_table[ch_b_s].flag_table[ch_b] |= LI_FLAG_MIX; + } + if (li_flags & LI2_FLAG_MONITOR_X) + li_config_table[ch_b].chflags |= LI_CHFLAG_MONITOR; + if (li_flags & LI2_FLAG_MIX_X) + li_config_table[ch_b].chflags |= LI_CHFLAG_MIX; + if (li_flags & LI2_FLAG_LOOP_B) + { + li_config_table[ch_b_v].flag_table[ch_b_v] |= LI_FLAG_INTERCONNECT; + li_config_table[ch_b_s].flag_table[ch_b_v] |= LI_FLAG_INTERCONNECT; + li_config_table[ch_b_v].flag_table[ch_b_s] |= LI_FLAG_INTERCONNECT; + li_config_table[ch_b_s].flag_table[ch_b_s] |= LI_FLAG_INTERCONNECT; + } + if (li_flags & LI2_FLAG_LOOP_PC) + li_config_table[ch_b].flag_table[ch_b] |= LI_FLAG_PCCONNECT; + if (li_flags & LI2_FLAG_LOOP_X) + li_config_table[ch_b].chflags |= LI_CHFLAG_LOOP; + if (li_flags & LI2_FLAG_PCCONNECT_A_B) + li_config_table[ch_b_s].flag_table[ch_a_s] |= LI_FLAG_PCCONNECT; + if (li_flags & LI2_FLAG_PCCONNECT_B_A) + li_config_table[ch_a_s].flag_table[ch_b_s] |= LI_FLAG_PCCONNECT; + if (ch_a_v != ch_a_s) + { + li_config_table[ch_a_v].flag_table[ch_a_s] |= LI_FLAG_CONFERENCE; + li_config_table[ch_a_s].flag_table[ch_a_v] |= LI_FLAG_CONFERENCE; + } + if (ch_b_v != ch_b_s) + { + li_config_table[ch_b_v].flag_table[ch_b_s] |= LI_FLAG_CONFERENCE; + li_config_table[ch_b_s].flag_table[ch_b_v] |= LI_FLAG_CONFERENCE; + } +} + + +static word li_check_main_plci (dword Id, PLCI *plci) +{ + if (plci == NULL) + { + dbug (1, dprintf ("[%06lx] %s,%d: Wrong PLCI", + UnMapId (Id), (char *)(FILE_), __LINE__)); + return (_WRONG_IDENTIFIER); + } + if (!plci->State + || !plci->NL.Id || plci->nl_remove_id + || (plci->li_bchannel_id == 0)) + { + dbug (1, dprintf ("[%06lx] %s,%d: Wrong state", + UnMapId (Id), (char *)(FILE_), __LINE__)); + return (_WRONG_STATE); + } + li_config_table[plci->adapter->li_base + (plci->li_bchannel_id - 1)].plci = plci; + return (GOOD); +} + + +static PLCI *li_check_plci_b (dword Id, PLCI *plci, + dword plci_b_id, word plci_b_write_pos, byte *p_result) +{ + byte ctlr_b; + PLCI *plci_b; + + if (((plci->li_plci_b_read_pos > plci_b_write_pos) ? plci->li_plci_b_read_pos : + LI_PLCI_B_QUEUE_ENTRIES + plci->li_plci_b_read_pos) - plci_b_write_pos - 1 < 2) + { + dbug (1, dprintf ("[%06lx] %s,%d: LI request overrun", + UnMapId (Id), (char *)(FILE_), __LINE__)); + PUT_WORD (p_result, _REQUEST_NOT_ALLOWED_IN_THIS_STATE); + return (NULL); + } + ctlr_b = 0; + if ((plci_b_id & 0x7f) != 0) + { + ctlr_b = MapController ((byte)(plci_b_id & 0x7f)); + if ((ctlr_b > max_adapter) || ((ctlr_b != 0) && (adapter[ctlr_b - 1].request == NULL))) + ctlr_b = 0; + } + if ((ctlr_b == 0) + || (((plci_b_id >> 8) & 0xff) == 0) + || (((plci_b_id >> 8) & 0xff) > adapter[ctlr_b - 1].max_plci)) + { + dbug (1, dprintf ("[%06lx] %s,%d: LI invalid second PLCI %08lx", + UnMapId (Id), (char *)(FILE_), __LINE__, plci_b_id)); + PUT_WORD (p_result, _WRONG_IDENTIFIER); + return (NULL); + } + plci_b = &(adapter[ctlr_b - 1].plci[((plci_b_id >> 8) & 0xff) - 1]); + if (!plci_b->State + || !plci_b->NL.Id || plci_b->nl_remove_id + || (plci_b->li_bchannel_id == 0)) + { + dbug (1, dprintf ("[%06lx] %s,%d: LI peer in wrong state %08lx", + UnMapId (Id), (char *)(FILE_), __LINE__, plci_b_id)); + PUT_WORD (p_result, _REQUEST_NOT_ALLOWED_IN_THIS_STATE); + return (NULL); + } + li_config_table[plci_b->adapter->li_base + (plci_b->li_bchannel_id - 1)].plci = plci_b; + if (((byte)(plci_b_id & ~EXT_CONTROLLER)) != + ((byte)(UnMapController (plci->adapter->Id) & ~EXT_CONTROLLER)) + && (!(plci->adapter->manufacturer_features & MANUFACTURER_FEATURE_XCONNECT) + || !(plci_b->adapter->manufacturer_features & MANUFACTURER_FEATURE_XCONNECT))) + { + dbug (1, dprintf ("[%06lx] %s,%d: LI not on same ctrl %08lx", + UnMapId (Id), (char *)(FILE_), __LINE__, plci_b_id)); + PUT_WORD (p_result, _WRONG_IDENTIFIER); + return (NULL); + } + if (!(get_b1_facilities (plci_b, add_b1_facilities (plci_b, plci_b->B1_resource, + (word)(plci_b->B1_facilities | B1_FACILITY_MIXER))) & B1_FACILITY_MIXER)) + { + dbug (1, dprintf ("[%06lx] %s,%d: Interconnect peer cannot mix %d", + UnMapId (Id), (char *)(FILE_), __LINE__, plci_b->B1_resource)); + PUT_WORD (p_result, _REQUEST_NOT_ALLOWED_IN_THIS_STATE); + return (NULL); + } + return (plci_b); +} + + +static PLCI *li2_check_plci_b (dword Id, PLCI *plci, + dword plci_b_id, word plci_b_write_pos, byte *p_result) +{ + byte ctlr_b; + PLCI *plci_b; + + if (((plci->li_plci_b_read_pos > plci_b_write_pos) ? plci->li_plci_b_read_pos : + LI_PLCI_B_QUEUE_ENTRIES + plci->li_plci_b_read_pos) - plci_b_write_pos - 1 < 2) + { + dbug (1, dprintf ("[%06lx] %s,%d: LI request overrun", + UnMapId (Id), (char *)(FILE_), __LINE__)); + PUT_WORD (p_result, _WRONG_STATE); + return (NULL); + } + ctlr_b = 0; + if ((plci_b_id & 0x7f) != 0) + { + ctlr_b = MapController ((byte)(plci_b_id & 0x7f)); + if ((ctlr_b > max_adapter) || ((ctlr_b != 0) && (adapter[ctlr_b - 1].request == NULL))) + ctlr_b = 0; + } + if ((ctlr_b == 0) + || (((plci_b_id >> 8) & 0xff) == 0) + || (((plci_b_id >> 8) & 0xff) > adapter[ctlr_b - 1].max_plci)) + { + dbug (1, dprintf ("[%06lx] %s,%d: LI invalid second PLCI %08lx", + UnMapId (Id), (char *)(FILE_), __LINE__, plci_b_id)); + PUT_WORD (p_result, _WRONG_IDENTIFIER); + return (NULL); + } + plci_b = &(adapter[ctlr_b - 1].plci[((plci_b_id >> 8) & 0xff) - 1]); + if (!plci_b->State + || !plci_b->NL.Id || plci_b->nl_remove_id + || (plci_b->li_bchannel_id == 0) + || (li_config_table[plci_b->adapter->li_base + (plci_b->li_bchannel_id - 1)].plci != plci_b)) + { + dbug (1, dprintf ("[%06lx] %s,%d: LI peer in wrong state %08lx", + UnMapId (Id), (char *)(FILE_), __LINE__, plci_b_id)); + PUT_WORD (p_result, _WRONG_STATE); + return (NULL); + } + if (((byte)(plci_b_id & ~EXT_CONTROLLER)) != + ((byte)(UnMapController (plci->adapter->Id) & ~EXT_CONTROLLER)) + && (!(plci->adapter->manufacturer_features & MANUFACTURER_FEATURE_XCONNECT) + || !(plci_b->adapter->manufacturer_features & MANUFACTURER_FEATURE_XCONNECT))) + { + dbug (1, dprintf ("[%06lx] %s,%d: LI not on same ctrl %08lx", + UnMapId (Id), (char *)(FILE_), __LINE__, plci_b_id)); + PUT_WORD (p_result, _WRONG_IDENTIFIER); + return (NULL); + } + if (!(get_b1_facilities (plci_b, add_b1_facilities (plci_b, plci_b->B1_resource, + (word)(plci_b->B1_facilities | B1_FACILITY_MIXER))) & B1_FACILITY_MIXER)) + { + dbug (1, dprintf ("[%06lx] %s,%d: Interconnect peer cannot mix %d", + UnMapId (Id), (char *)(FILE_), __LINE__, plci_b->B1_resource)); + PUT_WORD (p_result, _WRONG_STATE); + return (NULL); + } + return (plci_b); +} + + +static byte mixer_request (dword Id, word Number, DIVA_CAPI_ADAPTER *a, PLCI *plci, APPL *appl, API_PARSE *msg) +{ + word Info; + word i; + dword d, li_flags, plci_b_id; + PLCI *plci_b; + API_PARSE li_parms[3]; + API_PARSE li_req_parms[3]; + API_PARSE li_participant_struct[2]; + API_PARSE li_participant_parms[3]; + word participant_parms_pos; + byte result_buffer[32]; + byte *result; + word result_pos; + word plci_b_write_pos; + + dbug (1, dprintf ("[%06lx] %s,%d: mixer_request", + UnMapId (Id), (char *)(FILE_), __LINE__)); + + Info = GOOD; + result = result_buffer; + result_buffer[0] = 0; + if (!(a->profile.Global_Options & GL_LINE_INTERCONNECT_SUPPORTED)) + { + dbug (1, dprintf ("[%06lx] %s,%d: Facility not supported", + UnMapId (Id), (char *)(FILE_), __LINE__)); + Info = _FACILITY_NOT_SUPPORTED; + } + else if (api_parse (&msg[1].info[1], msg[1].length, "ws", li_parms)) + { + dbug (1, dprintf ("[%06lx] %s,%d: Wrong message format", + UnMapId (Id), (char *)(FILE_), __LINE__)); + Info = _WRONG_MESSAGE_FORMAT; + } + else + { + result_buffer[0] = 3; + PUT_WORD (&result_buffer[1], GET_WORD (li_parms[0].info)); + result_buffer[3] = 0; + switch (GET_WORD (li_parms[0].info)) + { + case LI_GET_SUPPORTED_SERVICES: + if (appl->appl_flags & APPL_FLAG_OLD_LI_SPEC) + { + result_buffer[0] = 17; + result_buffer[3] = 14; + PUT_WORD (&result_buffer[4], GOOD); + d = 0; + if (a->manufacturer_features & MANUFACTURER_FEATURE_MIXER_CH_CH) + d |= LI_CONFERENCING_SUPPORTED; + if (a->manufacturer_features & MANUFACTURER_FEATURE_MIXER_CH_PC) + d |= LI_MONITORING_SUPPORTED; + if (a->manufacturer_features & MANUFACTURER_FEATURE_MIXER_PC_CH) + d |= LI_ANNOUNCEMENTS_SUPPORTED | LI_MIXING_SUPPORTED; + if (a->manufacturer_features & MANUFACTURER_FEATURE_XCONNECT) + d |= LI_CROSS_CONTROLLER_SUPPORTED; + PUT_DWORD (&result_buffer[6], d); + if (a->manufacturer_features & MANUFACTURER_FEATURE_XCONNECT) + { + d = 0; + for (i = 0; i < li_total_channels; i++) + { + if ((li_config_table[i].adapter->manufacturer_features & MANUFACTURER_FEATURE_XCONNECT) + && (li_config_table[i].adapter->li_pri + || (i < li_config_table[i].adapter->li_base + MIXER_BCHANNELS_BRI))) + { + d++; + } + } + } + else + { + d = a->li_pri ? a->li_channels : MIXER_BCHANNELS_BRI; + } + PUT_DWORD (&result_buffer[10], d / 2); + PUT_DWORD (&result_buffer[14], d); + } + else + { + result_buffer[0] = 25; + result_buffer[3] = 22; + PUT_WORD (&result_buffer[4], GOOD); + d = LI2_ASYMMETRIC_SUPPORTED | LI2_B_LOOPING_SUPPORTED | LI2_X_LOOPING_SUPPORTED; + if (a->manufacturer_features & MANUFACTURER_FEATURE_MIXER_CH_PC) + d |= LI2_MONITORING_SUPPORTED | LI2_REMOTE_MONITORING_SUPPORTED; + if (a->manufacturer_features & MANUFACTURER_FEATURE_MIXER_PC_CH) + d |= LI2_MIXING_SUPPORTED | LI2_REMOTE_MIXING_SUPPORTED; + if (a->manufacturer_features & MANUFACTURER_FEATURE_MIXER_PC_PC) + d |= LI2_PC_LOOPING_SUPPORTED; + if (a->manufacturer_features & MANUFACTURER_FEATURE_XCONNECT) + d |= LI2_CROSS_CONTROLLER_SUPPORTED; + PUT_DWORD (&result_buffer[6], d); + d = a->li_pri ? a->li_channels : MIXER_BCHANNELS_BRI; + PUT_DWORD (&result_buffer[10], d / 2); + PUT_DWORD (&result_buffer[14], d - 1); + if (a->manufacturer_features & MANUFACTURER_FEATURE_XCONNECT) + { + d = 0; + for (i = 0; i < li_total_channels; i++) + { + if ((li_config_table[i].adapter->manufacturer_features & MANUFACTURER_FEATURE_XCONNECT) + && (li_config_table[i].adapter->li_pri + || (i < li_config_table[i].adapter->li_base + MIXER_BCHANNELS_BRI))) + { + d++; + } + } + } + PUT_DWORD (&result_buffer[18], d / 2); + PUT_DWORD (&result_buffer[22], d - 1); + } + break; + + case LI_REQ_CONNECT: + if (li_parms[1].length == 8) + { + appl->appl_flags |= APPL_FLAG_OLD_LI_SPEC; + if (api_parse (&li_parms[1].info[1], li_parms[1].length, "dd", li_req_parms)) + { + dbug (1, dprintf ("[%06lx] %s,%d: Wrong message format", + UnMapId (Id), (char *)(FILE_), __LINE__)); + Info = _WRONG_MESSAGE_FORMAT; + break; + } + plci_b_id = GET_DWORD (li_req_parms[0].info) & 0xffff; + li_flags = GET_DWORD (li_req_parms[1].info); + Info = li_check_main_plci (Id, plci); + result_buffer[0] = 9; + result_buffer[3] = 6; + PUT_DWORD (&result_buffer[4], plci_b_id); + PUT_WORD (&result_buffer[8], GOOD); + if (Info != GOOD) + break; + result = plci->saved_msg.info; + for (i = 0; i <= result_buffer[0]; i++) + result[i] = result_buffer[i]; + plci_b_write_pos = plci->li_plci_b_write_pos; + plci_b = li_check_plci_b (Id, plci, plci_b_id, plci_b_write_pos, &result[8]); + if (plci_b == NULL) + break; + li_update_connect (Id, a, plci, plci_b_id, TRUE, li_flags); + plci->li_plci_b_queue[plci_b_write_pos] = plci_b_id | LI_PLCI_B_LAST_FLAG; + plci_b_write_pos = (plci_b_write_pos == LI_PLCI_B_QUEUE_ENTRIES-1) ? 0 : plci_b_write_pos + 1; + plci->li_plci_b_write_pos = plci_b_write_pos; + } + else + { + appl->appl_flags &= ~APPL_FLAG_OLD_LI_SPEC; + if (api_parse (&li_parms[1].info[1], li_parms[1].length, "ds", li_req_parms)) + { + dbug (1, dprintf ("[%06lx] %s,%d: Wrong message format", + UnMapId (Id), (char *)(FILE_), __LINE__)); + Info = _WRONG_MESSAGE_FORMAT; + break; + } + li_flags = GET_DWORD (li_req_parms[0].info) & ~(LI2_FLAG_INTERCONNECT_A_B | LI2_FLAG_INTERCONNECT_B_A); + Info = li_check_main_plci (Id, plci); + result_buffer[0] = 7; + result_buffer[3] = 4; + PUT_WORD (&result_buffer[4], Info); + result_buffer[6] = 0; + if (Info != GOOD) + break; + result = plci->saved_msg.info; + for (i = 0; i <= result_buffer[0]; i++) + result[i] = result_buffer[i]; + plci_b_write_pos = plci->li_plci_b_write_pos; + participant_parms_pos = 0; + result_pos = 7; + li2_update_connect (Id, a, plci, UnMapId (Id), TRUE, li_flags); + while (participant_parms_pos < li_req_parms[1].length) + { + result[result_pos] = 6; + result_pos += 7; + PUT_DWORD (&result[result_pos - 6], 0); + PUT_WORD (&result[result_pos - 2], GOOD); + if (api_parse (&li_req_parms[1].info[1 + participant_parms_pos], + (word)(li_parms[1].length - participant_parms_pos), "s", li_participant_struct)) + { + dbug (1, dprintf ("[%06lx] %s,%d: Wrong message format", + UnMapId (Id), (char *)(FILE_), __LINE__)); + PUT_WORD (&result[result_pos - 2], _WRONG_MESSAGE_FORMAT); + break; + } + if (api_parse (&li_participant_struct[0].info[1], + li_participant_struct[0].length, "dd", li_participant_parms)) + { + dbug (1, dprintf ("[%06lx] %s,%d: Wrong message format", + UnMapId (Id), (char *)(FILE_), __LINE__)); + PUT_WORD (&result[result_pos - 2], _WRONG_MESSAGE_FORMAT); + break; + } + plci_b_id = GET_DWORD (li_participant_parms[0].info) & 0xffff; + li_flags = GET_DWORD (li_participant_parms[1].info); + PUT_DWORD (&result[result_pos - 6], plci_b_id); + if (sizeof(result) - result_pos < 7) + { + dbug (1, dprintf ("[%06lx] %s,%d: LI result overrun", + UnMapId (Id), (char *)(FILE_), __LINE__)); + PUT_WORD (&result[result_pos - 2], _WRONG_STATE); + break; + } + plci_b = li2_check_plci_b (Id, plci, plci_b_id, plci_b_write_pos, &result[result_pos - 2]); + if (plci_b != NULL) + { + li2_update_connect (Id, a, plci, plci_b_id, TRUE, li_flags); + plci->li_plci_b_queue[plci_b_write_pos] = plci_b_id | + ((li_flags & (LI2_FLAG_INTERCONNECT_A_B | LI2_FLAG_INTERCONNECT_B_A | + LI2_FLAG_PCCONNECT_A_B | LI2_FLAG_PCCONNECT_B_A)) ? 0 : LI_PLCI_B_DISC_FLAG); + plci_b_write_pos = (plci_b_write_pos == LI_PLCI_B_QUEUE_ENTRIES-1) ? 0 : plci_b_write_pos + 1; + } + participant_parms_pos = (word)((&li_participant_struct[0].info[1 + li_participant_struct[0].length]) - + (&li_req_parms[1].info[1])); + } + result[0] = (byte)(result_pos - 1); + result[3] = (byte)(result_pos - 4); + result[6] = (byte)(result_pos - 7); + i = (plci_b_write_pos == 0) ? LI_PLCI_B_QUEUE_ENTRIES-1 : plci_b_write_pos - 1; + if ((plci_b_write_pos == plci->li_plci_b_read_pos) + || (plci->li_plci_b_queue[i] & LI_PLCI_B_LAST_FLAG)) + { + plci->li_plci_b_queue[plci_b_write_pos] = LI_PLCI_B_SKIP_FLAG | LI_PLCI_B_LAST_FLAG; + plci_b_write_pos = (plci_b_write_pos == LI_PLCI_B_QUEUE_ENTRIES-1) ? 0 : plci_b_write_pos + 1; + } + else + plci->li_plci_b_queue[i] |= LI_PLCI_B_LAST_FLAG; + plci->li_plci_b_write_pos = plci_b_write_pos; + } + mixer_calculate_coefs (a); + plci->li_channel_bits = li_config_table[a->li_base + (plci->li_bchannel_id - 1)].channel; + mixer_notify_update (plci, TRUE); + sendf (appl, _FACILITY_R | CONFIRM, Id & 0xffffL, Number, + "wwS", Info, SELECTOR_LINE_INTERCONNECT, result); + plci->command = 0; + plci->li_cmd = GET_WORD (li_parms[0].info); + start_internal_command (Id, plci, mixer_command); + return (FALSE); + + case LI_REQ_DISCONNECT: + if (li_parms[1].length == 4) + { + appl->appl_flags |= APPL_FLAG_OLD_LI_SPEC; + if (api_parse (&li_parms[1].info[1], li_parms[1].length, "d", li_req_parms)) + { + dbug (1, dprintf ("[%06lx] %s,%d: Wrong message format", + UnMapId (Id), (char *)(FILE_), __LINE__)); + Info = _WRONG_MESSAGE_FORMAT; + break; + } + plci_b_id = GET_DWORD (li_req_parms[0].info) & 0xffff; + Info = li_check_main_plci (Id, plci); + result_buffer[0] = 9; + result_buffer[3] = 6; + PUT_DWORD (&result_buffer[4], GET_DWORD (li_req_parms[0].info)); + PUT_WORD (&result_buffer[8], GOOD); + if (Info != GOOD) + break; + result = plci->saved_msg.info; + for (i = 0; i <= result_buffer[0]; i++) + result[i] = result_buffer[i]; + plci_b_write_pos = plci->li_plci_b_write_pos; + plci_b = li_check_plci_b (Id, plci, plci_b_id, plci_b_write_pos, &result[8]); + if (plci_b == NULL) + break; + li_update_connect (Id, a, plci, plci_b_id, FALSE, 0); + plci->li_plci_b_queue[plci_b_write_pos] = plci_b_id | LI_PLCI_B_DISC_FLAG | LI_PLCI_B_LAST_FLAG; + plci_b_write_pos = (plci_b_write_pos == LI_PLCI_B_QUEUE_ENTRIES-1) ? 0 : plci_b_write_pos + 1; + plci->li_plci_b_write_pos = plci_b_write_pos; + } + else + { + appl->appl_flags &= ~APPL_FLAG_OLD_LI_SPEC; + if (api_parse (&li_parms[1].info[1], li_parms[1].length, "s", li_req_parms)) + { + dbug (1, dprintf ("[%06lx] %s,%d: Wrong message format", + UnMapId (Id), (char *)(FILE_), __LINE__)); + Info = _WRONG_MESSAGE_FORMAT; + break; + } + Info = li_check_main_plci (Id, plci); + result_buffer[0] = 7; + result_buffer[3] = 4; + PUT_WORD (&result_buffer[4], Info); + result_buffer[6] = 0; + if (Info != GOOD) + break; + result = plci->saved_msg.info; + for (i = 0; i <= result_buffer[0]; i++) + result[i] = result_buffer[i]; + plci_b_write_pos = plci->li_plci_b_write_pos; + participant_parms_pos = 0; + result_pos = 7; + while (participant_parms_pos < li_req_parms[0].length) + { + result[result_pos] = 6; + result_pos += 7; + PUT_DWORD (&result[result_pos - 6], 0); + PUT_WORD (&result[result_pos - 2], GOOD); + if (api_parse (&li_req_parms[0].info[1 + participant_parms_pos], + (word)(li_parms[1].length - participant_parms_pos), "s", li_participant_struct)) + { + dbug (1, dprintf ("[%06lx] %s,%d: Wrong message format", + UnMapId (Id), (char *)(FILE_), __LINE__)); + PUT_WORD (&result[result_pos - 2], _WRONG_MESSAGE_FORMAT); + break; + } + if (api_parse (&li_participant_struct[0].info[1], + li_participant_struct[0].length, "d", li_participant_parms)) + { + dbug (1, dprintf ("[%06lx] %s,%d: Wrong message format", + UnMapId (Id), (char *)(FILE_), __LINE__)); + PUT_WORD (&result[result_pos - 2], _WRONG_MESSAGE_FORMAT); + break; + } + plci_b_id = GET_DWORD (li_participant_parms[0].info) & 0xffff; + PUT_DWORD (&result[result_pos - 6], plci_b_id); + if (sizeof(result) - result_pos < 7) + { + dbug (1, dprintf ("[%06lx] %s,%d: LI result overrun", + UnMapId (Id), (char *)(FILE_), __LINE__)); + PUT_WORD (&result[result_pos - 2], _WRONG_STATE); + break; + } + plci_b = li2_check_plci_b (Id, plci, plci_b_id, plci_b_write_pos, &result[result_pos - 2]); + if (plci_b != NULL) + { + li2_update_connect (Id, a, plci, plci_b_id, FALSE, 0); + plci->li_plci_b_queue[plci_b_write_pos] = plci_b_id | LI_PLCI_B_DISC_FLAG; + plci_b_write_pos = (plci_b_write_pos == LI_PLCI_B_QUEUE_ENTRIES-1) ? 0 : plci_b_write_pos + 1; + } + participant_parms_pos = (word)((&li_participant_struct[0].info[1 + li_participant_struct[0].length]) - + (&li_req_parms[0].info[1])); + } + result[0] = (byte)(result_pos - 1); + result[3] = (byte)(result_pos - 4); + result[6] = (byte)(result_pos - 7); + i = (plci_b_write_pos == 0) ? LI_PLCI_B_QUEUE_ENTRIES-1 : plci_b_write_pos - 1; + if ((plci_b_write_pos == plci->li_plci_b_read_pos) + || (plci->li_plci_b_queue[i] & LI_PLCI_B_LAST_FLAG)) + { + plci->li_plci_b_queue[plci_b_write_pos] = LI_PLCI_B_SKIP_FLAG | LI_PLCI_B_LAST_FLAG; + plci_b_write_pos = (plci_b_write_pos == LI_PLCI_B_QUEUE_ENTRIES-1) ? 0 : plci_b_write_pos + 1; + } + else + plci->li_plci_b_queue[i] |= LI_PLCI_B_LAST_FLAG; + plci->li_plci_b_write_pos = plci_b_write_pos; + } + mixer_calculate_coefs (a); + plci->li_channel_bits = li_config_table[a->li_base + (plci->li_bchannel_id - 1)].channel; + mixer_notify_update (plci, TRUE); + sendf (appl, _FACILITY_R | CONFIRM, Id & 0xffffL, Number, + "wwS", Info, SELECTOR_LINE_INTERCONNECT, result); + plci->command = 0; + plci->li_cmd = GET_WORD (li_parms[0].info); + start_internal_command (Id, plci, mixer_command); + return (FALSE); + + case LI_REQ_SILENT_UPDATE: + if (!plci || !plci->State + || !plci->NL.Id || plci->nl_remove_id + || (plci->li_bchannel_id == 0) + || (li_config_table[plci->adapter->li_base + (plci->li_bchannel_id - 1)].plci != plci)) + { + dbug (1, dprintf ("[%06lx] %s,%d: Wrong state", + UnMapId (Id), (char *)(FILE_), __LINE__)); + return (FALSE); + } + plci_b_write_pos = plci->li_plci_b_write_pos; + if (((plci->li_plci_b_read_pos > plci_b_write_pos) ? plci->li_plci_b_read_pos : + LI_PLCI_B_QUEUE_ENTRIES + plci->li_plci_b_read_pos) - plci_b_write_pos - 1 < 2) + { + dbug (1, dprintf ("[%06lx] %s,%d: LI request overrun", + UnMapId (Id), (char *)(FILE_), __LINE__)); + return (FALSE); + } + i = (plci_b_write_pos == 0) ? LI_PLCI_B_QUEUE_ENTRIES-1 : plci_b_write_pos - 1; + if ((plci_b_write_pos == plci->li_plci_b_read_pos) + || (plci->li_plci_b_queue[i] & LI_PLCI_B_LAST_FLAG)) + { + plci->li_plci_b_queue[plci_b_write_pos] = LI_PLCI_B_SKIP_FLAG | LI_PLCI_B_LAST_FLAG; + plci_b_write_pos = (plci_b_write_pos == LI_PLCI_B_QUEUE_ENTRIES-1) ? 0 : plci_b_write_pos + 1; + } + else + plci->li_plci_b_queue[i] |= LI_PLCI_B_LAST_FLAG; + plci->li_plci_b_write_pos = plci_b_write_pos; + plci->li_channel_bits = li_config_table[a->li_base + (plci->li_bchannel_id - 1)].channel; + plci->command = 0; + plci->li_cmd = GET_WORD (li_parms[0].info); + start_internal_command (Id, plci, mixer_command); + return (FALSE); + + default: + dbug (1, dprintf ("[%06lx] %s,%d: LI unknown request %04x", + UnMapId (Id), (char *)(FILE_), __LINE__, GET_WORD (li_parms[0].info))); + Info = _FACILITY_NOT_SUPPORTED; + } + } + sendf (appl, _FACILITY_R | CONFIRM, Id & 0xffffL, Number, + "wwS", Info, SELECTOR_LINE_INTERCONNECT, result); + return (FALSE); +} + + +static void mixer_indication_coefs_set (dword Id, PLCI *plci) +{ + dword d; + DIVA_CAPI_ADAPTER *a; + byte result[12]; + + dbug (1, dprintf ("[%06lx] %s,%d: mixer_indication_coefs_set", + UnMapId (Id), (char *)(FILE_), __LINE__)); + + a = plci->adapter; + if (plci->li_plci_b_read_pos != plci->li_plci_b_req_pos) + { + do + { + d = plci->li_plci_b_queue[plci->li_plci_b_read_pos]; + if (!(d & LI_PLCI_B_SKIP_FLAG)) + { + if (plci->appl->appl_flags & APPL_FLAG_OLD_LI_SPEC) + { + if (d & LI_PLCI_B_DISC_FLAG) + { + result[0] = 5; + PUT_WORD (&result[1], LI_IND_DISCONNECT); + result[3] = 2; + PUT_WORD (&result[4], _LI_USER_INITIATED); + } + else + { + result[0] = 7; + PUT_WORD (&result[1], LI_IND_CONNECT_ACTIVE); + result[3] = 4; + PUT_DWORD (&result[4], d & ~LI_PLCI_B_FLAG_MASK); + } + } + else + { + if (d & LI_PLCI_B_DISC_FLAG) + { + result[0] = 9; + PUT_WORD (&result[1], LI_IND_DISCONNECT); + result[3] = 6; + PUT_DWORD (&result[4], d & ~LI_PLCI_B_FLAG_MASK); + PUT_WORD (&result[8], _LI_USER_INITIATED); + } + else + { + result[0] = 7; + PUT_WORD (&result[1], LI_IND_CONNECT_ACTIVE); + result[3] = 4; + PUT_DWORD (&result[4], d & ~LI_PLCI_B_FLAG_MASK); + } + } + sendf (plci->appl, _FACILITY_I, Id & 0xffffL, 0, + "ws", SELECTOR_LINE_INTERCONNECT, result); + } + plci->li_plci_b_read_pos = (plci->li_plci_b_read_pos == LI_PLCI_B_QUEUE_ENTRIES-1) ? + 0 : plci->li_plci_b_read_pos + 1; + } while (!(d & LI_PLCI_B_LAST_FLAG) && (plci->li_plci_b_read_pos != plci->li_plci_b_req_pos)); + } +} + + +static void mixer_indication_xconnect_from (dword Id, PLCI *plci, byte *msg, word length) +{ + word i, j, ch; + struct xconnect_transfer_address_s s, *p; + DIVA_CAPI_ADAPTER *a; + + dbug (1, dprintf ("[%06lx] %s,%d: mixer_indication_xconnect_from %d", + UnMapId (Id), (char *)(FILE_), __LINE__, (int) length)); + + a = plci->adapter; + i = 1; + for (i = 1; i < length; i += 16) + { + s.card_address.low = msg[i] | (msg[i+1] << 8) | (((dword)(msg[i+2])) << 16) | (((dword)(msg[i+3])) << 24); + s.card_address.high = msg[i+4] | (msg[i+5] << 8) | (((dword)(msg[i+6])) << 16) | (((dword)(msg[i+7])) << 24); + s.offset = msg[i+8] | (msg[i+9] << 8) | (((dword)(msg[i+10])) << 16) | (((dword)(msg[i+11])) << 24); + ch = msg[i+12] | (msg[i+13] << 8); + j = ch & XCONNECT_CHANNEL_NUMBER_MASK; + if (!a->li_pri && (plci->li_bchannel_id == 2)) + j = 1 - j; + j += a->li_base; + if (ch & XCONNECT_CHANNEL_PORT_PC) + p = &(li_config_table[j].send_pc); + else + p = &(li_config_table[j].send_b); + p->card_address.low = s.card_address.low; + p->card_address.high = s.card_address.high; + p->offset = s.offset; + li_config_table[j].channel |= LI_CHANNEL_ADDRESSES_SET; + } + if (plci->internal_command_queue[0] + && ((plci->adjust_b_state == ADJUST_B_RESTORE_MIXER_2) + || (plci->adjust_b_state == ADJUST_B_RESTORE_MIXER_3) + || (plci->adjust_b_state == ADJUST_B_RESTORE_MIXER_4))) + { + (*(plci->internal_command_queue[0]))(Id, plci, 0); + if (!plci->internal_command) + next_internal_command (Id, plci); + } + mixer_notify_update (plci, TRUE); +} + + +static void mixer_indication_xconnect_to (dword Id, PLCI *plci, byte *msg, word length) +{ + + dbug (1, dprintf ("[%06lx] %s,%d: mixer_indication_xconnect_to %d", + UnMapId (Id), (char *)(FILE_), __LINE__, (int) length)); + +} + + +static byte mixer_notify_source_removed (PLCI *plci, dword plci_b_id) +{ + word plci_b_write_pos; + + plci_b_write_pos = plci->li_plci_b_write_pos; + if (((plci->li_plci_b_read_pos > plci_b_write_pos) ? plci->li_plci_b_read_pos : + LI_PLCI_B_QUEUE_ENTRIES + plci->li_plci_b_read_pos) - plci_b_write_pos - 1 < 1) + { + dbug (1, dprintf ("[%06lx] %s,%d: LI request overrun", + (dword)((plci->Id << 8) | UnMapController (plci->adapter->Id)), + (char *)(FILE_), __LINE__)); + return (FALSE); + } + plci->li_plci_b_queue[plci_b_write_pos] = plci_b_id | LI_PLCI_B_DISC_FLAG; + plci_b_write_pos = (plci_b_write_pos == LI_PLCI_B_QUEUE_ENTRIES-1) ? 0 : plci_b_write_pos + 1; + plci->li_plci_b_write_pos = plci_b_write_pos; + return (TRUE); +} + + +static void mixer_remove (PLCI *plci) +{ + DIVA_CAPI_ADAPTER *a; + PLCI *notify_plci; + dword plci_b_id; + word i, j; + + dbug (1, dprintf ("[%06lx] %s,%d: mixer_remove", + (dword)((plci->Id << 8) | UnMapController (plci->adapter->Id)), + (char *)(FILE_), __LINE__)); + + a = plci->adapter; + plci_b_id = (plci->Id << 8) | UnMapController (plci->adapter->Id); + if (a->profile.Global_Options & GL_LINE_INTERCONNECT_SUPPORTED) + { + if ((plci->li_bchannel_id != 0) + && (li_config_table[a->li_base + (plci->li_bchannel_id - 1)].plci == plci)) + { + i = a->li_base + (plci->li_bchannel_id - 1); + if ((li_config_table[i].curchnl | li_config_table[i].channel) & LI_CHANNEL_INVOLVED) + { + for (j = 0; j < li_total_channels; j++) + { + if ((li_config_table[i].flag_table[j] & LI_FLAG_INTERCONNECT) + || (li_config_table[j].flag_table[i] & LI_FLAG_INTERCONNECT)) + { + notify_plci = li_config_table[j].plci; + if ((notify_plci != NULL) + && (notify_plci != plci) + && (notify_plci->appl != NULL) + && !(notify_plci->appl->appl_flags & APPL_FLAG_OLD_LI_SPEC) + && (notify_plci->State) + && notify_plci->NL.Id && !notify_plci->nl_remove_id) + { + mixer_notify_source_removed (notify_plci, plci_b_id); + } + } + } + mixer_clear_config (plci); + mixer_calculate_coefs (a); + mixer_notify_update (plci, TRUE); + } + li_config_table[i].plci = NULL; + plci->li_bchannel_id = 0; + } + } +} + + +/*------------------------------------------------------------------*/ +/* Echo canceller facilities */ +/*------------------------------------------------------------------*/ + + +static void ec_write_parameters (PLCI *plci) +{ + word w; + byte parameter_buffer[6]; + + dbug (1, dprintf ("[%06lx] %s,%d: ec_write_parameters", + (dword)((plci->Id << 8) | UnMapController (plci->adapter->Id)), + (char *)(FILE_), __LINE__)); + + parameter_buffer[0] = 5; + parameter_buffer[1] = DSP_CTRL_SET_LEC_PARAMETERS; + PUT_WORD (¶meter_buffer[2], plci->ec_idi_options); + plci->ec_idi_options &= ~LEC_RESET_COEFFICIENTS; + w = (plci->ec_tail_length == 0) ? 128 : plci->ec_tail_length; + PUT_WORD (¶meter_buffer[4], w); + add_p (plci, FTY, parameter_buffer); + sig_req (plci, TEL_CTRL, 0); + send_req (plci); +} + + +static void ec_clear_config (PLCI *plci) +{ + + dbug (1, dprintf ("[%06lx] %s,%d: ec_clear_config", + (dword)((plci->Id << 8) | UnMapController (plci->adapter->Id)), + (char *)(FILE_), __LINE__)); + + plci->ec_idi_options = LEC_ENABLE_ECHO_CANCELLER | + LEC_MANUAL_DISABLE | LEC_ENABLE_NONLINEAR_PROCESSING; + plci->ec_tail_length = 0; +} + + +static void ec_prepare_switch (dword Id, PLCI *plci) +{ + + dbug (1, dprintf ("[%06lx] %s,%d: ec_prepare_switch", + UnMapId (Id), (char *)(FILE_), __LINE__)); + +} + + +static word ec_save_config (dword Id, PLCI *plci, byte Rc) +{ + + dbug (1, dprintf ("[%06lx] %s,%d: ec_save_config %02x %d", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc, plci->adjust_b_state)); + + return (GOOD); +} + + +static word ec_restore_config (dword Id, PLCI *plci, byte Rc) +{ + word Info; + + dbug (1, dprintf ("[%06lx] %s,%d: ec_restore_config %02x %d", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc, plci->adjust_b_state)); + + Info = GOOD; + if (plci->B1_facilities & B1_FACILITY_EC) + { + switch (plci->adjust_b_state) + { + case ADJUST_B_RESTORE_EC_1: + plci->internal_command = plci->adjust_b_command; + if (plci->sig_req) + { + plci->adjust_b_state = ADJUST_B_RESTORE_EC_1; + break; + } + ec_write_parameters (plci); + plci->adjust_b_state = ADJUST_B_RESTORE_EC_2; + break; + case ADJUST_B_RESTORE_EC_2: + if ((Rc != OK) && (Rc != OK_FC)) + { + dbug (1, dprintf ("[%06lx] %s,%d: Restore EC failed %02x", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc)); + Info = _WRONG_STATE; + break; + } + break; + } + } + return (Info); +} + + +static void ec_command (dword Id, PLCI *plci, byte Rc) +{ + word internal_command, Info; + byte result[8]; + + dbug (1, dprintf ("[%06lx] %s,%d: ec_command %02x %04x %04x %04x %d", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc, plci->internal_command, + plci->ec_cmd, plci->ec_idi_options, plci->ec_tail_length)); + + Info = GOOD; + if (plci->appl->appl_flags & APPL_FLAG_PRIV_EC_SPEC) + { + result[0] = 2; + PUT_WORD (&result[1], EC_SUCCESS); + } + else + { + result[0] = 5; + PUT_WORD (&result[1], plci->ec_cmd); + result[3] = 2; + PUT_WORD (&result[4], GOOD); + } + internal_command = plci->internal_command; + plci->internal_command = 0; + switch (plci->ec_cmd) + { + case EC_ENABLE_OPERATION: + case EC_FREEZE_COEFFICIENTS: + case EC_RESUME_COEFFICIENT_UPDATE: + case EC_RESET_COEFFICIENTS: + switch (internal_command) + { + default: + adjust_b1_resource (Id, plci, NULL, (word)(plci->B1_facilities | + B1_FACILITY_EC), EC_COMMAND_1); + case EC_COMMAND_1: + if (adjust_b_process (Id, plci, Rc) != GOOD) + { + dbug (1, dprintf ("[%06lx] %s,%d: Load EC failed", + UnMapId (Id), (char *)(FILE_), __LINE__)); + Info = _FACILITY_NOT_SUPPORTED; + break; + } + if (plci->internal_command) + return; + case EC_COMMAND_2: + if (plci->sig_req) + { + plci->internal_command = EC_COMMAND_2; + return; + } + plci->internal_command = EC_COMMAND_3; + ec_write_parameters (plci); + return; + case EC_COMMAND_3: + if ((Rc != OK) && (Rc != OK_FC)) + { + dbug (1, dprintf ("[%06lx] %s,%d: Enable EC failed %02x", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc)); + Info = _FACILITY_NOT_SUPPORTED; + break; + } + break; + } + break; + + case EC_DISABLE_OPERATION: + switch (internal_command) + { + default: + case EC_COMMAND_1: + if (plci->B1_facilities & B1_FACILITY_EC) + { + if (plci->sig_req) + { + plci->internal_command = EC_COMMAND_1; + return; + } + plci->internal_command = EC_COMMAND_2; + ec_write_parameters (plci); + return; + } + Rc = OK; + case EC_COMMAND_2: + if ((Rc != OK) && (Rc != OK_FC)) + { + dbug (1, dprintf ("[%06lx] %s,%d: Disable EC failed %02x", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc)); + Info = _FACILITY_NOT_SUPPORTED; + break; + } + adjust_b1_resource (Id, plci, NULL, (word)(plci->B1_facilities & + ~B1_FACILITY_EC), EC_COMMAND_3); + case EC_COMMAND_3: + if (adjust_b_process (Id, plci, Rc) != GOOD) + { + dbug (1, dprintf ("[%06lx] %s,%d: Unload EC failed", + UnMapId (Id), (char *)(FILE_), __LINE__)); + Info = _FACILITY_NOT_SUPPORTED; + break; + } + if (plci->internal_command) + return; + break; + } + break; + } + sendf (plci->appl, _FACILITY_R | CONFIRM, Id & 0xffffL, plci->number, + "wws", Info, (plci->appl->appl_flags & APPL_FLAG_PRIV_EC_SPEC) ? + PRIV_SELECTOR_ECHO_CANCELLER : SELECTOR_ECHO_CANCELLER, result); +} + + +static byte ec_request (dword Id, word Number, DIVA_CAPI_ADAPTER *a, PLCI *plci, APPL *appl, API_PARSE *msg) +{ + word Info; + word opt; + API_PARSE ec_parms[3]; + byte result[16]; + + dbug (1, dprintf ("[%06lx] %s,%d: ec_request", + UnMapId (Id), (char *)(FILE_), __LINE__)); + + Info = GOOD; + result[0] = 0; + if (!(a->man_profile.private_options & (1L << PRIVATE_ECHO_CANCELLER))) + { + dbug (1, dprintf ("[%06lx] %s,%d: Facility not supported", + UnMapId (Id), (char *)(FILE_), __LINE__)); + Info = _FACILITY_NOT_SUPPORTED; + } + else + { + if (appl->appl_flags & APPL_FLAG_PRIV_EC_SPEC) + { + if (api_parse (&msg[1].info[1], msg[1].length, "w", ec_parms)) + { + dbug (1, dprintf ("[%06lx] %s,%d: Wrong message format", + UnMapId (Id), (char *)(FILE_), __LINE__)); + Info = _WRONG_MESSAGE_FORMAT; + } + else + { + if (plci == NULL) + { + dbug (1, dprintf ("[%06lx] %s,%d: Wrong PLCI", + UnMapId (Id), (char *)(FILE_), __LINE__)); + Info = _WRONG_IDENTIFIER; + } + else if (!plci->State || !plci->NL.Id || plci->nl_remove_id) + { + dbug (1, dprintf ("[%06lx] %s,%d: Wrong state", + UnMapId (Id), (char *)(FILE_), __LINE__)); + Info = _WRONG_STATE; + } + else + { + plci->command = 0; + plci->ec_cmd = GET_WORD (ec_parms[0].info); + plci->ec_idi_options &= ~(LEC_MANUAL_DISABLE | LEC_RESET_COEFFICIENTS); + result[0] = 2; + PUT_WORD (&result[1], EC_SUCCESS); + if (msg[1].length >= 4) + { + opt = GET_WORD (&ec_parms[0].info[2]); + plci->ec_idi_options &= ~(LEC_ENABLE_NONLINEAR_PROCESSING | + LEC_ENABLE_2100HZ_DETECTOR | LEC_REQUIRE_2100HZ_REVERSALS); + if (!(opt & EC_DISABLE_NON_LINEAR_PROCESSING)) + plci->ec_idi_options |= LEC_ENABLE_NONLINEAR_PROCESSING; + if (opt & EC_DETECT_DISABLE_TONE) + plci->ec_idi_options |= LEC_ENABLE_2100HZ_DETECTOR; + if (!(opt & EC_DO_NOT_REQUIRE_REVERSALS)) + plci->ec_idi_options |= LEC_REQUIRE_2100HZ_REVERSALS; + if (msg[1].length >= 6) + { + plci->ec_tail_length = GET_WORD (&ec_parms[0].info[4]); + } + } + switch (plci->ec_cmd) + { + case EC_ENABLE_OPERATION: + plci->ec_idi_options &= ~LEC_FREEZE_COEFFICIENTS; + start_internal_command (Id, plci, ec_command); + return (FALSE); + + case EC_DISABLE_OPERATION: + plci->ec_idi_options = LEC_ENABLE_ECHO_CANCELLER | + LEC_MANUAL_DISABLE | LEC_ENABLE_NONLINEAR_PROCESSING | + LEC_RESET_COEFFICIENTS; + start_internal_command (Id, plci, ec_command); + return (FALSE); + + case EC_FREEZE_COEFFICIENTS: + plci->ec_idi_options |= LEC_FREEZE_COEFFICIENTS; + start_internal_command (Id, plci, ec_command); + return (FALSE); + + case EC_RESUME_COEFFICIENT_UPDATE: + plci->ec_idi_options &= ~LEC_FREEZE_COEFFICIENTS; + start_internal_command (Id, plci, ec_command); + return (FALSE); + + case EC_RESET_COEFFICIENTS: + plci->ec_idi_options |= LEC_RESET_COEFFICIENTS; + start_internal_command (Id, plci, ec_command); + return (FALSE); + + default: + dbug (1, dprintf ("[%06lx] %s,%d: EC unknown request %04x", + UnMapId (Id), (char *)(FILE_), __LINE__, plci->ec_cmd)); + PUT_WORD (&result[1], EC_UNSUPPORTED_OPERATION); + } + } + } + } + else + { + if (api_parse (&msg[1].info[1], msg[1].length, "ws", ec_parms)) + { + dbug (1, dprintf ("[%06lx] %s,%d: Wrong message format", + UnMapId (Id), (char *)(FILE_), __LINE__)); + Info = _WRONG_MESSAGE_FORMAT; + } + else + { + if (GET_WORD (ec_parms[0].info) == EC_GET_SUPPORTED_SERVICES) + { + result[0] = 11; + PUT_WORD (&result[1], EC_GET_SUPPORTED_SERVICES); + result[3] = 8; + PUT_WORD (&result[4], GOOD); + PUT_WORD (&result[6], 0x0007); + PUT_WORD (&result[8], LEC_MAX_SUPPORTED_TAIL_LENGTH); + PUT_WORD (&result[10], 0); + } + else if (plci == NULL) + { + dbug (1, dprintf ("[%06lx] %s,%d: Wrong PLCI", + UnMapId (Id), (char *)(FILE_), __LINE__)); + Info = _WRONG_IDENTIFIER; + } + else if (!plci->State || !plci->NL.Id || plci->nl_remove_id) + { + dbug (1, dprintf ("[%06lx] %s,%d: Wrong state", + UnMapId (Id), (char *)(FILE_), __LINE__)); + Info = _WRONG_STATE; + } + else + { + plci->command = 0; + plci->ec_cmd = GET_WORD (ec_parms[0].info); + plci->ec_idi_options &= ~(LEC_MANUAL_DISABLE | LEC_RESET_COEFFICIENTS); + result[0] = 5; + PUT_WORD (&result[1], plci->ec_cmd); + result[3] = 2; + PUT_WORD (&result[4], GOOD); + plci->ec_idi_options &= ~(LEC_ENABLE_NONLINEAR_PROCESSING | + LEC_ENABLE_2100HZ_DETECTOR | LEC_REQUIRE_2100HZ_REVERSALS); + plci->ec_tail_length = 0; + if (ec_parms[1].length >= 2) + { + opt = GET_WORD (&ec_parms[1].info[1]); + if (opt & EC_ENABLE_NON_LINEAR_PROCESSING) + plci->ec_idi_options |= LEC_ENABLE_NONLINEAR_PROCESSING; + if (opt & EC_DETECT_DISABLE_TONE) + plci->ec_idi_options |= LEC_ENABLE_2100HZ_DETECTOR; + if (!(opt & EC_DO_NOT_REQUIRE_REVERSALS)) + plci->ec_idi_options |= LEC_REQUIRE_2100HZ_REVERSALS; + if (ec_parms[1].length >= 4) + { + plci->ec_tail_length = GET_WORD (&ec_parms[1].info[3]); + } + } + switch (plci->ec_cmd) + { + case EC_ENABLE_OPERATION: + plci->ec_idi_options &= ~LEC_FREEZE_COEFFICIENTS; + start_internal_command (Id, plci, ec_command); + return (FALSE); + + case EC_DISABLE_OPERATION: + plci->ec_idi_options = LEC_ENABLE_ECHO_CANCELLER | + LEC_MANUAL_DISABLE | LEC_ENABLE_NONLINEAR_PROCESSING | + LEC_RESET_COEFFICIENTS; + start_internal_command (Id, plci, ec_command); + return (FALSE); + + default: + dbug (1, dprintf ("[%06lx] %s,%d: EC unknown request %04x", + UnMapId (Id), (char *)(FILE_), __LINE__, plci->ec_cmd)); + PUT_WORD (&result[4], _FACILITY_SPECIFIC_FUNCTION_NOT_SUPP); + } + } + } + } + } + sendf (appl, _FACILITY_R | CONFIRM, Id & 0xffffL, Number, + "wws", Info, (appl->appl_flags & APPL_FLAG_PRIV_EC_SPEC) ? + PRIV_SELECTOR_ECHO_CANCELLER : SELECTOR_ECHO_CANCELLER, result); + return (FALSE); +} + + +static void ec_indication (dword Id, PLCI *plci, byte *msg, word length) +{ + byte result[8]; + + dbug (1, dprintf ("[%06lx] %s,%d: ec_indication", + UnMapId (Id), (char *)(FILE_), __LINE__)); + + if (!(plci->ec_idi_options & LEC_MANUAL_DISABLE)) + { + if (plci->appl->appl_flags & APPL_FLAG_PRIV_EC_SPEC) + { + result[0] = 2; + PUT_WORD (&result[1], 0); + switch (msg[1]) + { + case LEC_DISABLE_TYPE_CONTIGNUOUS_2100HZ: + PUT_WORD (&result[1], EC_BYPASS_DUE_TO_CONTINUOUS_2100HZ); + break; + case LEC_DISABLE_TYPE_REVERSED_2100HZ: + PUT_WORD (&result[1], EC_BYPASS_DUE_TO_REVERSED_2100HZ); + break; + case LEC_DISABLE_RELEASED: + PUT_WORD (&result[1], EC_BYPASS_RELEASED); + break; + } + } + else + { + result[0] = 5; + PUT_WORD (&result[1], EC_BYPASS_INDICATION); + result[3] = 2; + PUT_WORD (&result[4], 0); + switch (msg[1]) + { + case LEC_DISABLE_TYPE_CONTIGNUOUS_2100HZ: + PUT_WORD (&result[4], EC_BYPASS_DUE_TO_CONTINUOUS_2100HZ); + break; + case LEC_DISABLE_TYPE_REVERSED_2100HZ: + PUT_WORD (&result[4], EC_BYPASS_DUE_TO_REVERSED_2100HZ); + break; + case LEC_DISABLE_RELEASED: + PUT_WORD (&result[4], EC_BYPASS_RELEASED); + break; + } + } + sendf (plci->appl, _FACILITY_I, Id & 0xffffL, 0, "ws", (plci->appl->appl_flags & APPL_FLAG_PRIV_EC_SPEC) ? + PRIV_SELECTOR_ECHO_CANCELLER : SELECTOR_ECHO_CANCELLER, result); + } +} + + + +/*------------------------------------------------------------------*/ +/* Advanced voice */ +/*------------------------------------------------------------------*/ + +static void adv_voice_write_coefs (PLCI *plci, word write_command) +{ + DIVA_CAPI_ADAPTER *a; + word i; + byte *p; + + word w, n, j, k; + byte ch_map[MIXER_CHANNELS_BRI]; + + byte coef_buffer[ADV_VOICE_COEF_BUFFER_SIZE + 2]; + + dbug (1, dprintf ("[%06lx] %s,%d: adv_voice_write_coefs %d", + (dword)((plci->Id << 8) | UnMapController (plci->adapter->Id)), + (char *)(FILE_), __LINE__, write_command)); + + a = plci->adapter; + p = coef_buffer + 1; + *(p++) = DSP_CTRL_OLD_SET_MIXER_COEFFICIENTS; + i = 0; + while (i + sizeof(word) <= a->adv_voice_coef_length) + { + PUT_WORD (p, GET_WORD (a->adv_voice_coef_buffer + i)); + p += 2; + i += 2; + } + while (i < ADV_VOICE_OLD_COEF_COUNT * sizeof(word)) + { + PUT_WORD (p, 0x8000); + p += 2; + i += 2; + } + + if (!a->li_pri && (plci->li_bchannel_id == 0)) + { + if ((li_config_table[a->li_base].plci == NULL) && (li_config_table[a->li_base + 1].plci != NULL)) + { + plci->li_bchannel_id = 1; + li_config_table[a->li_base].plci = plci; + dbug (1, dprintf ("[%06lx] %s,%d: adv_voice_set_bchannel_id %d", + (dword)((plci->Id << 8) | UnMapController (plci->adapter->Id)), + (char *)(FILE_), __LINE__, plci->li_bchannel_id)); + } + else if ((li_config_table[a->li_base].plci != NULL) && (li_config_table[a->li_base + 1].plci == NULL)) + { + plci->li_bchannel_id = 2; + li_config_table[a->li_base + 1].plci = plci; + dbug (1, dprintf ("[%06lx] %s,%d: adv_voice_set_bchannel_id %d", + (dword)((plci->Id << 8) | UnMapController (plci->adapter->Id)), + (char *)(FILE_), __LINE__, plci->li_bchannel_id)); + } + } + if (!a->li_pri && (plci->li_bchannel_id != 0) + && (li_config_table[a->li_base + (plci->li_bchannel_id - 1)].plci == plci)) + { + i = a->li_base + (plci->li_bchannel_id - 1); + switch (write_command) + { + case ADV_VOICE_WRITE_ACTIVATION: + j = a->li_base + MIXER_IC_CHANNEL_BASE + (plci->li_bchannel_id - 1); + k = a->li_base + MIXER_IC_CHANNEL_BASE + (2 - plci->li_bchannel_id); + if (!(plci->B1_facilities & B1_FACILITY_MIXER)) + { + li_config_table[j].flag_table[i] |= LI_FLAG_CONFERENCE | LI_FLAG_MIX; + li_config_table[i].flag_table[j] |= LI_FLAG_CONFERENCE | LI_FLAG_MONITOR; + } + if (a->manufacturer_features & MANUFACTURER_FEATURE_SLAVE_CODEC) + { + li_config_table[k].flag_table[i] |= LI_FLAG_CONFERENCE | LI_FLAG_MIX; + li_config_table[i].flag_table[k] |= LI_FLAG_CONFERENCE | LI_FLAG_MONITOR; + li_config_table[k].flag_table[j] |= LI_FLAG_CONFERENCE; + li_config_table[j].flag_table[k] |= LI_FLAG_CONFERENCE; + } + mixer_calculate_coefs (a); + li_config_table[i].curchnl = li_config_table[i].channel; + li_config_table[j].curchnl = li_config_table[j].channel; + if (a->manufacturer_features & MANUFACTURER_FEATURE_SLAVE_CODEC) + li_config_table[k].curchnl = li_config_table[k].channel; + break; + + case ADV_VOICE_WRITE_DEACTIVATION: + for (j = 0; j < li_total_channels; j++) + { + li_config_table[i].flag_table[j] = 0; + li_config_table[j].flag_table[i] = 0; + } + k = a->li_base + MIXER_IC_CHANNEL_BASE + (plci->li_bchannel_id - 1); + for (j = 0; j < li_total_channels; j++) + { + li_config_table[k].flag_table[j] = 0; + li_config_table[j].flag_table[k] = 0; + } + if (a->manufacturer_features & MANUFACTURER_FEATURE_SLAVE_CODEC) + { + k = a->li_base + MIXER_IC_CHANNEL_BASE + (2 - plci->li_bchannel_id); + for (j = 0; j < li_total_channels; j++) + { + li_config_table[k].flag_table[j] = 0; + li_config_table[j].flag_table[k] = 0; + } + } + mixer_calculate_coefs (a); + break; + } + if (plci->B1_facilities & B1_FACILITY_MIXER) + { + w = 0; + if (ADV_VOICE_NEW_COEF_BASE + sizeof(word) <= a->adv_voice_coef_length) + w = GET_WORD (a->adv_voice_coef_buffer + ADV_VOICE_NEW_COEF_BASE); + if (li_config_table[i].channel & LI_CHANNEL_TX_DATA) + w |= MIXER_FEATURE_ENABLE_TX_DATA; + if (li_config_table[i].channel & LI_CHANNEL_RX_DATA) + w |= MIXER_FEATURE_ENABLE_RX_DATA; + *(p++) = (byte) w; + *(p++) = (byte)(w >> 8); + for (j = 0; j < sizeof(ch_map); j += 2) + { + ch_map[j] = (byte)(j + (plci->li_bchannel_id - 1)); + ch_map[j+1] = (byte)(j + (2 - plci->li_bchannel_id)); + } + for (n = 0; n < sizeof(mixer_write_prog_bri) / sizeof(mixer_write_prog_bri[0]); n++) + { + i = a->li_base + ch_map[mixer_write_prog_bri[n].to_ch]; + j = a->li_base + ch_map[mixer_write_prog_bri[n].from_ch]; + if (li_config_table[i].channel & li_config_table[j].channel & LI_CHANNEL_INVOLVED) + { + *(p++) = ((li_config_table[i].coef_table[j] & mixer_write_prog_bri[n].mask) ? 0x80 : 0x01); + w = ((li_config_table[i].coef_table[j] & 0xf) ^ (li_config_table[i].coef_table[j] >> 4)); + li_config_table[i].coef_table[j] ^= (w & mixer_write_prog_bri[n].mask) << 4; + } + else + { + *(p++) = (ADV_VOICE_NEW_COEF_BASE + sizeof(word) + n < a->adv_voice_coef_length) ? + a->adv_voice_coef_buffer[ADV_VOICE_NEW_COEF_BASE + sizeof(word) + n] : 0x00; + } + } + } + else + { + for (i = ADV_VOICE_NEW_COEF_BASE; i < a->adv_voice_coef_length; i++) + *(p++) = a->adv_voice_coef_buffer[i]; + } + } + else + + { + for (i = ADV_VOICE_NEW_COEF_BASE; i < a->adv_voice_coef_length; i++) + *(p++) = a->adv_voice_coef_buffer[i]; + } + coef_buffer[0] = (p - coef_buffer) - 1; + add_p (plci, FTY, coef_buffer); + sig_req (plci, TEL_CTRL, 0); + send_req (plci); +} + + +static void adv_voice_clear_config (PLCI *plci) +{ + DIVA_CAPI_ADAPTER *a; + + word i, j; + + + dbug (1, dprintf ("[%06lx] %s,%d: adv_voice_clear_config", + (dword)((plci->Id << 8) | UnMapController (plci->adapter->Id)), + (char *)(FILE_), __LINE__)); + + a = plci->adapter; + if ((plci->tel == ADV_VOICE) && (plci == a->AdvSignalPLCI)) + { + a->adv_voice_coef_length = 0; + + if (!a->li_pri && (plci->li_bchannel_id != 0) + && (li_config_table[a->li_base + (plci->li_bchannel_id - 1)].plci == plci)) + { + i = a->li_base + (plci->li_bchannel_id - 1); + li_config_table[i].curchnl = 0; + li_config_table[i].channel = 0; + li_config_table[i].chflags = 0; + for (j = 0; j < li_total_channels; j++) + { + li_config_table[i].flag_table[j] = 0; + li_config_table[j].flag_table[i] = 0; + li_config_table[i].coef_table[j] = 0; + li_config_table[j].coef_table[i] = 0; + } + li_config_table[i].coef_table[i] |= LI_COEF_CH_PC_SET | LI_COEF_PC_CH_SET; + i = a->li_base + MIXER_IC_CHANNEL_BASE + (plci->li_bchannel_id - 1); + li_config_table[i].curchnl = 0; + li_config_table[i].channel = 0; + li_config_table[i].chflags = 0; + for (j = 0; j < li_total_channels; j++) + { + li_config_table[i].flag_table[j] = 0; + li_config_table[j].flag_table[i] = 0; + li_config_table[i].coef_table[j] = 0; + li_config_table[j].coef_table[i] = 0; + } + if (a->manufacturer_features & MANUFACTURER_FEATURE_SLAVE_CODEC) + { + i = a->li_base + MIXER_IC_CHANNEL_BASE + (2 - plci->li_bchannel_id); + li_config_table[i].curchnl = 0; + li_config_table[i].channel = 0; + li_config_table[i].chflags = 0; + for (j = 0; j < li_total_channels; j++) + { + li_config_table[i].flag_table[j] = 0; + li_config_table[j].flag_table[i] = 0; + li_config_table[i].coef_table[j] = 0; + li_config_table[j].coef_table[i] = 0; + } + } + } + + } +} + + +static void adv_voice_prepare_switch (dword Id, PLCI *plci) +{ + + dbug (1, dprintf ("[%06lx] %s,%d: adv_voice_prepare_switch", + UnMapId (Id), (char *)(FILE_), __LINE__)); + +} + + +static word adv_voice_save_config (dword Id, PLCI *plci, byte Rc) +{ + + dbug (1, dprintf ("[%06lx] %s,%d: adv_voice_save_config %02x %d", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc, plci->adjust_b_state)); + + return (GOOD); +} + + +static word adv_voice_restore_config (dword Id, PLCI *plci, byte Rc) +{ + DIVA_CAPI_ADAPTER *a; + word Info; + + dbug (1, dprintf ("[%06lx] %s,%d: adv_voice_restore_config %02x %d", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc, plci->adjust_b_state)); + + Info = GOOD; + a = plci->adapter; + if ((plci->B1_facilities & B1_FACILITY_VOICE) + && (plci->tel == ADV_VOICE) && (plci == a->AdvSignalPLCI)) + { + switch (plci->adjust_b_state) + { + case ADJUST_B_RESTORE_VOICE_1: + plci->internal_command = plci->adjust_b_command; + if (plci->sig_req) + { + plci->adjust_b_state = ADJUST_B_RESTORE_VOICE_1; + break; + } + adv_voice_write_coefs (plci, ADV_VOICE_WRITE_UPDATE); + plci->adjust_b_state = ADJUST_B_RESTORE_VOICE_2; + break; + case ADJUST_B_RESTORE_VOICE_2: + if ((Rc != OK) && (Rc != OK_FC)) + { + dbug (1, dprintf ("[%06lx] %s,%d: Restore voice config failed %02x", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc)); + Info = _WRONG_STATE; + break; + } + break; + } + } + return (Info); +} + + + + +/*------------------------------------------------------------------*/ +/* B1 resource switching */ +/*------------------------------------------------------------------*/ + +static byte b1_facilities_table[] = +{ + 0x00, /* 0 No bchannel resources */ + 0x00, /* 1 Codec (automatic law) */ + 0x00, /* 2 Codec (A-law) */ + 0x00, /* 3 Codec (y-law) */ + 0x00, /* 4 HDLC for X.21 */ + 0x00, /* 5 HDLC */ + 0x00, /* 6 External Device 0 */ + 0x00, /* 7 External Device 1 */ + 0x00, /* 8 HDLC 56k */ + 0x00, /* 9 Transparent */ + 0x00, /* 10 Loopback to network */ + 0x00, /* 11 Test pattern to net */ + 0x00, /* 12 Rate adaptation sync */ + 0x00, /* 13 Rate adaptation async */ + 0x00, /* 14 R-Interface */ + 0x00, /* 15 HDLC 128k leased line */ + 0x00, /* 16 FAX */ + 0x00, /* 17 Modem async */ + 0x00, /* 18 Modem sync HDLC */ + 0x00, /* 19 V.110 async HDLC */ + 0x12, /* 20 Adv voice (Trans,mixer) */ + 0x00, /* 21 Codec connected to IC */ + 0x0c, /* 22 Trans,DTMF */ + 0x1e, /* 23 Trans,DTMF+mixer */ + 0x1f, /* 24 Trans,DTMF+mixer+local */ + 0x13, /* 25 Trans,mixer+local */ + 0x12, /* 26 HDLC,mixer */ + 0x12, /* 27 HDLC 56k,mixer */ + 0x2c, /* 28 Trans,LEC+DTMF */ + 0x3e, /* 29 Trans,LEC+DTMF+mixer */ + 0x3f, /* 30 Trans,LEC+DTMF+mixer+local */ + 0x2c, /* 31 RTP,LEC+DTMF */ + 0x3e, /* 32 RTP,LEC+DTMF+mixer */ + 0x3f, /* 33 RTP,LEC+DTMF+mixer+local */ + 0x00, /* 34 Signaling task */ + 0x00, /* 35 PIAFS */ + 0x0c, /* 36 Trans,DTMF+TONE */ + 0x1e, /* 37 Trans,DTMF+TONE+mixer */ + 0x1f /* 38 Trans,DTMF+TONE+mixer+local*/ +}; + + +static word get_b1_facilities (PLCI * plci, byte b1_resource) +{ + word b1_facilities; + + b1_facilities = b1_facilities_table[b1_resource]; + if ((b1_resource == 9) || (b1_resource == 20) || (b1_resource == 25)) + { + + if (!(((plci->requested_options_conn | plci->requested_options) & (1L << PRIVATE_DTMF_TONE)) + || (plci->appl && (plci->adapter->requested_options_table[plci->appl->Id-1] & (1L << PRIVATE_DTMF_TONE))))) + + { + if (plci->adapter->manufacturer_features & MANUFACTURER_FEATURE_SOFTDTMF_SEND) + b1_facilities |= B1_FACILITY_DTMFX; + if (plci->adapter->manufacturer_features & MANUFACTURER_FEATURE_SOFTDTMF_RECEIVE) + b1_facilities |= B1_FACILITY_DTMFR; + } + } + if ((b1_resource == 17) || (b1_resource == 18)) + { + if (plci->adapter->manufacturer_features & (MANUFACTURER_FEATURE_V18 | MANUFACTURER_FEATURE_VOWN)) + b1_facilities |= B1_FACILITY_DTMFX | B1_FACILITY_DTMFR; + } +/* + dbug (1, dprintf ("[%06lx] %s,%d: get_b1_facilities %d %04x", + (dword)((plci->Id << 8) | UnMapController (plci->adapter->Id)), + (char far *)(FILE_), __LINE__, b1_resource, b1_facilites)); +*/ + return (b1_facilities); +} + + +static byte add_b1_facilities (PLCI * plci, byte b1_resource, word b1_facilities) +{ + byte b; + + switch (b1_resource) + { + case 5: + case 26: + if (b1_facilities & (B1_FACILITY_MIXER | B1_FACILITY_VOICE)) + b = 26; + else + b = 5; + break; + + case 8: + case 27: + if (b1_facilities & (B1_FACILITY_MIXER | B1_FACILITY_VOICE)) + b = 27; + else + b = 8; + break; + + case 9: + case 20: + case 22: + case 23: + case 24: + case 25: + case 28: + case 29: + case 30: + case 36: + case 37: + case 38: + if (b1_facilities & B1_FACILITY_EC) + { + if (b1_facilities & B1_FACILITY_LOCAL) + b = 30; + else if (b1_facilities & (B1_FACILITY_MIXER | B1_FACILITY_VOICE)) + b = 29; + else + b = 28; + } + + else if ((b1_facilities & (B1_FACILITY_DTMFX | B1_FACILITY_DTMFR | B1_FACILITY_MIXER)) + && (((plci->requested_options_conn | plci->requested_options) & (1L << PRIVATE_DTMF_TONE)) + || (plci->appl && (plci->adapter->requested_options_table[plci->appl->Id-1] & (1L << PRIVATE_DTMF_TONE))))) + { + if (b1_facilities & B1_FACILITY_LOCAL) + b = 38; + else if (b1_facilities & (B1_FACILITY_MIXER | B1_FACILITY_VOICE)) + b = 37; + else + b = 36; + } + + else if (((plci->adapter->manufacturer_features & MANUFACTURER_FEATURE_HARDDTMF) + && !(plci->adapter->manufacturer_features & MANUFACTURER_FEATURE_SOFTDTMF_RECEIVE)) + || ((b1_facilities & B1_FACILITY_DTMFR) + && ((b1_facilities & B1_FACILITY_MIXER) + || !(plci->adapter->manufacturer_features & MANUFACTURER_FEATURE_SOFTDTMF_RECEIVE))) + || ((b1_facilities & B1_FACILITY_DTMFX) + && ((b1_facilities & B1_FACILITY_MIXER) + || !(plci->adapter->manufacturer_features & MANUFACTURER_FEATURE_SOFTDTMF_SEND)))) + { + if (b1_facilities & B1_FACILITY_LOCAL) + b = 24; + else if (b1_facilities & (B1_FACILITY_MIXER | B1_FACILITY_VOICE)) + b = 23; + else + b = 22; + } + else + { + if (b1_facilities & B1_FACILITY_LOCAL) + b = 25; + else if (b1_facilities & (B1_FACILITY_MIXER | B1_FACILITY_VOICE)) + b = 20; + else + b = 9; + } + break; + + case 31: + case 32: + case 33: + if (b1_facilities & B1_FACILITY_LOCAL) + b = 33; + else if (b1_facilities & (B1_FACILITY_MIXER | B1_FACILITY_VOICE)) + b = 32; + else + b = 31; + break; + + default: + b = b1_resource; + } + dbug (1, dprintf ("[%06lx] %s,%d: add_b1_facilities %d %04x %d %04x", + (dword)((plci->Id << 8) | UnMapController (plci->adapter->Id)), + (char *)(FILE_), __LINE__, + b1_resource, b1_facilities, b, get_b1_facilities (plci, b))); + return (b); +} + + +static void adjust_b1_facilities (PLCI *plci, byte new_b1_resource, word new_b1_facilities) +{ + word removed_facilities; + + dbug (1, dprintf ("[%06lx] %s,%d: adjust_b1_facilities %d %04x %04x", + (dword)((plci->Id << 8) | UnMapController (plci->adapter->Id)), + (char *)(FILE_), __LINE__, new_b1_resource, new_b1_facilities, + new_b1_facilities & get_b1_facilities (plci, new_b1_resource))); + + new_b1_facilities &= get_b1_facilities (plci, new_b1_resource); + removed_facilities = plci->B1_facilities & ~new_b1_facilities; + + if (removed_facilities & B1_FACILITY_EC) + ec_clear_config (plci); + + + if (removed_facilities & B1_FACILITY_DTMFR) + { + dtmf_rec_clear_config (plci); + dtmf_parameter_clear_config (plci); + } + if (removed_facilities & B1_FACILITY_DTMFX) + dtmf_send_clear_config (plci); + + + if (removed_facilities & B1_FACILITY_MIXER) + mixer_clear_config (plci); + + if (removed_facilities & B1_FACILITY_VOICE) + adv_voice_clear_config (plci); + plci->B1_facilities = new_b1_facilities; +} + + +static void adjust_b_clear (PLCI *plci) +{ + + dbug (1, dprintf ("[%06lx] %s,%d: adjust_b_clear", + (dword)((plci->Id << 8) | UnMapController (plci->adapter->Id)), + (char *)(FILE_), __LINE__)); + + plci->adjust_b_restore = FALSE; +} + + +static word adjust_b_process (dword Id, PLCI *plci, byte Rc) +{ + word Info; + byte b1_resource; + NCCI * ncci_ptr; + API_PARSE bp[2]; + + dbug (1, dprintf ("[%06lx] %s,%d: adjust_b_process %02x %d", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc, plci->adjust_b_state)); + + Info = GOOD; + switch (plci->adjust_b_state) + { + case ADJUST_B_START: + if ((plci->adjust_b_parms_msg == NULL) + && (plci->adjust_b_mode & ADJUST_B_MODE_SWITCH_L1) + && ((plci->adjust_b_mode & ~(ADJUST_B_MODE_SAVE | ADJUST_B_MODE_SWITCH_L1 | + ADJUST_B_MODE_NO_RESOURCE | ADJUST_B_MODE_RESTORE)) == 0)) + { + b1_resource = (plci->adjust_b_mode == ADJUST_B_MODE_NO_RESOURCE) ? + 0 : add_b1_facilities (plci, plci->B1_resource, plci->adjust_b_facilities); + if (b1_resource == plci->B1_resource) + { + adjust_b1_facilities (plci, b1_resource, plci->adjust_b_facilities); + break; + } + if (plci->adjust_b_facilities & ~get_b1_facilities (plci, b1_resource)) + { + dbug (1, dprintf ("[%06lx] %s,%d: Adjust B nonsupported facilities %d %d %04x", + UnMapId (Id), (char *)(FILE_), __LINE__, + plci->B1_resource, b1_resource, plci->adjust_b_facilities)); + Info = _WRONG_STATE; + break; + } + } + if (plci->adjust_b_mode & ADJUST_B_MODE_SAVE) + { + + mixer_prepare_switch (Id, plci); + + + dtmf_prepare_switch (Id, plci); + dtmf_parameter_prepare_switch (Id, plci); + + + ec_prepare_switch (Id, plci); + + adv_voice_prepare_switch (Id, plci); + } + plci->adjust_b_state = ADJUST_B_SAVE_MIXER_1; + Rc = OK; + case ADJUST_B_SAVE_MIXER_1: + if (plci->adjust_b_mode & ADJUST_B_MODE_SAVE) + { + + Info = mixer_save_config (Id, plci, Rc); + if ((Info != GOOD) || plci->internal_command) + break; + + } + plci->adjust_b_state = ADJUST_B_SAVE_DTMF_1; + Rc = OK; + case ADJUST_B_SAVE_DTMF_1: + if (plci->adjust_b_mode & ADJUST_B_MODE_SAVE) + { + + Info = dtmf_save_config (Id, plci, Rc); + if ((Info != GOOD) || plci->internal_command) + break; + + } + plci->adjust_b_state = ADJUST_B_REMOVE_L23_1; + case ADJUST_B_REMOVE_L23_1: + if ((plci->adjust_b_mode & ADJUST_B_MODE_REMOVE_L23) + && plci->NL.Id && !plci->nl_remove_id) + { + plci->internal_command = plci->adjust_b_command; + if (plci->adjust_b_ncci != 0) + { + ncci_ptr = &(plci->adapter->ncci[plci->adjust_b_ncci]); + while (ncci_ptr->data_pending) + { + plci->data_sent_ptr = ncci_ptr->DBuffer[ncci_ptr->data_out].P; + data_rc (plci, plci->adapter->ncci_ch[plci->adjust_b_ncci]); + } + while (ncci_ptr->data_ack_pending) + data_ack (plci, plci->adapter->ncci_ch[plci->adjust_b_ncci]); + } + nl_req_ncci (plci, REMOVE, + (byte)((plci->adjust_b_mode & ADJUST_B_MODE_CONNECT) ? plci->adjust_b_ncci : 0)); + send_req (plci); + plci->adjust_b_state = ADJUST_B_REMOVE_L23_2; + break; + } + plci->adjust_b_state = ADJUST_B_REMOVE_L23_2; + Rc = OK; + case ADJUST_B_REMOVE_L23_2: + if ((Rc != OK) && (Rc != OK_FC)) + { + dbug (1, dprintf ("[%06lx] %s,%d: Adjust B remove failed %02x", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc)); + Info = _WRONG_STATE; + break; + } + if (plci->adjust_b_mode & ADJUST_B_MODE_REMOVE_L23) + { + if (plci_nl_busy (plci)) + { + plci->internal_command = plci->adjust_b_command; + break; + } + } + plci->adjust_b_state = ADJUST_B_SAVE_EC_1; + Rc = OK; + case ADJUST_B_SAVE_EC_1: + if (plci->adjust_b_mode & ADJUST_B_MODE_SAVE) + { + + Info = ec_save_config (Id, plci, Rc); + if ((Info != GOOD) || plci->internal_command) + break; + + } + plci->adjust_b_state = ADJUST_B_SAVE_DTMF_PARAMETER_1; + Rc = OK; + case ADJUST_B_SAVE_DTMF_PARAMETER_1: + if (plci->adjust_b_mode & ADJUST_B_MODE_SAVE) + { + + Info = dtmf_parameter_save_config (Id, plci, Rc); + if ((Info != GOOD) || plci->internal_command) + break; + + } + plci->adjust_b_state = ADJUST_B_SAVE_VOICE_1; + Rc = OK; + case ADJUST_B_SAVE_VOICE_1: + if (plci->adjust_b_mode & ADJUST_B_MODE_SAVE) + { + Info = adv_voice_save_config (Id, plci, Rc); + if ((Info != GOOD) || plci->internal_command) + break; + } + plci->adjust_b_state = ADJUST_B_SWITCH_L1_1; + case ADJUST_B_SWITCH_L1_1: + if (plci->adjust_b_mode & ADJUST_B_MODE_SWITCH_L1) + { + if (plci->sig_req) + { + plci->internal_command = plci->adjust_b_command; + break; + } + if (plci->adjust_b_parms_msg != NULL) + api_load_msg (plci->adjust_b_parms_msg, bp); + else + api_load_msg (&plci->B_protocol, bp); + Info = add_b1 (plci, bp, + (word)((plci->adjust_b_mode & ADJUST_B_MODE_NO_RESOURCE) ? 2 : 0), + plci->adjust_b_facilities); + if (Info != GOOD) + { + dbug (1, dprintf ("[%06lx] %s,%d: Adjust B invalid L1 parameters %d %04x", + UnMapId (Id), (char *)(FILE_), __LINE__, + plci->B1_resource, plci->adjust_b_facilities)); + break; + } + plci->internal_command = plci->adjust_b_command; + sig_req (plci, RESOURCES, 0); + send_req (plci); + plci->adjust_b_state = ADJUST_B_SWITCH_L1_2; + break; + } + plci->adjust_b_state = ADJUST_B_SWITCH_L1_2; + Rc = OK; + case ADJUST_B_SWITCH_L1_2: + if ((Rc != OK) && (Rc != OK_FC)) + { + dbug (1, dprintf ("[%06lx] %s,%d: Adjust B switch failed %02x %d %04x", + UnMapId (Id), (char *)(FILE_), __LINE__, + Rc, plci->B1_resource, plci->adjust_b_facilities)); + Info = _WRONG_STATE; + break; + } + plci->adjust_b_state = ADJUST_B_RESTORE_VOICE_1; + Rc = OK; + case ADJUST_B_RESTORE_VOICE_1: + case ADJUST_B_RESTORE_VOICE_2: + if (plci->adjust_b_mode & ADJUST_B_MODE_RESTORE) + { + Info = adv_voice_restore_config (Id, plci, Rc); + if ((Info != GOOD) || plci->internal_command) + break; + } + plci->adjust_b_state = ADJUST_B_RESTORE_DTMF_PARAMETER_1; + Rc = OK; + case ADJUST_B_RESTORE_DTMF_PARAMETER_1: + case ADJUST_B_RESTORE_DTMF_PARAMETER_2: + if (plci->adjust_b_mode & ADJUST_B_MODE_RESTORE) + { + + Info = dtmf_parameter_restore_config (Id, plci, Rc); + if ((Info != GOOD) || plci->internal_command) + break; + + } + plci->adjust_b_state = ADJUST_B_RESTORE_EC_1; + Rc = OK; + case ADJUST_B_RESTORE_EC_1: + case ADJUST_B_RESTORE_EC_2: + if (plci->adjust_b_mode & ADJUST_B_MODE_RESTORE) + { + + Info = ec_restore_config (Id, plci, Rc); + if ((Info != GOOD) || plci->internal_command) + break; + + } + plci->adjust_b_state = ADJUST_B_ASSIGN_L23_1; + case ADJUST_B_ASSIGN_L23_1: + if (plci->adjust_b_mode & ADJUST_B_MODE_ASSIGN_L23) + { + if (plci_nl_busy (plci)) + { + plci->internal_command = plci->adjust_b_command; + break; + } + if (plci->adjust_b_mode & ADJUST_B_MODE_CONNECT) + plci->call_dir |= CALL_DIR_FORCE_OUTG_NL; + if (plci->adjust_b_parms_msg != NULL) + api_load_msg (plci->adjust_b_parms_msg, bp); + else + api_load_msg (&plci->B_protocol, bp); + Info = add_b23 (plci, bp); + if (Info != GOOD) + { + dbug (1, dprintf ("[%06lx] %s,%d: Adjust B invalid L23 parameters %04x", + UnMapId (Id), (char *)(FILE_), __LINE__, Info)); + break; + } + plci->internal_command = plci->adjust_b_command; + nl_req_ncci (plci, ASSIGN, 0); + send_req (plci); + plci->adjust_b_state = ADJUST_B_ASSIGN_L23_2; + break; + } + plci->adjust_b_state = ADJUST_B_ASSIGN_L23_2; + Rc = ASSIGN_OK; + case ADJUST_B_ASSIGN_L23_2: + if ((Rc != OK) && (Rc != OK_FC) && (Rc != ASSIGN_OK)) + { + dbug (1, dprintf ("[%06lx] %s,%d: Adjust B assign failed %02x", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc)); + Info = _WRONG_STATE; + break; + } + if (plci->adjust_b_mode & ADJUST_B_MODE_ASSIGN_L23) + { + if (Rc != ASSIGN_OK) + { + plci->internal_command = plci->adjust_b_command; + break; + } + } + if (plci->adjust_b_mode & ADJUST_B_MODE_USER_CONNECT) + { + plci->adjust_b_restore = TRUE; + break; + } + plci->adjust_b_state = ADJUST_B_CONNECT_1; + case ADJUST_B_CONNECT_1: + if (plci->adjust_b_mode & ADJUST_B_MODE_CONNECT) + { + plci->internal_command = plci->adjust_b_command; + if (plci_nl_busy (plci)) + break; + nl_req_ncci (plci, N_CONNECT, 0); + send_req (plci); + plci->adjust_b_state = ADJUST_B_CONNECT_2; + break; + } + plci->adjust_b_state = ADJUST_B_RESTORE_DTMF_1; + Rc = OK; + case ADJUST_B_CONNECT_2: + case ADJUST_B_CONNECT_3: + case ADJUST_B_CONNECT_4: + if ((Rc != OK) && (Rc != OK_FC) && (Rc != 0)) + { + dbug (1, dprintf ("[%06lx] %s,%d: Adjust B connect failed %02x", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc)); + Info = _WRONG_STATE; + break; + } + if (Rc == OK) + { + if (plci->adjust_b_mode & ADJUST_B_MODE_CONNECT) + { + get_ncci (plci, (byte)(Id >> 16), plci->adjust_b_ncci); + Id = (Id & 0xffff) | (((dword)(plci->adjust_b_ncci)) << 16); + } + if (plci->adjust_b_state == ADJUST_B_CONNECT_2) + plci->adjust_b_state = ADJUST_B_CONNECT_3; + else if (plci->adjust_b_state == ADJUST_B_CONNECT_4) + plci->adjust_b_state = ADJUST_B_RESTORE_DTMF_1; + } + else if (Rc == 0) + { + if (plci->adjust_b_state == ADJUST_B_CONNECT_2) + plci->adjust_b_state = ADJUST_B_CONNECT_4; + else if (plci->adjust_b_state == ADJUST_B_CONNECT_3) + plci->adjust_b_state = ADJUST_B_RESTORE_DTMF_1; + } + if (plci->adjust_b_state != ADJUST_B_RESTORE_DTMF_1) + { + plci->internal_command = plci->adjust_b_command; + break; + } + Rc = OK; + case ADJUST_B_RESTORE_DTMF_1: + case ADJUST_B_RESTORE_DTMF_2: + if (plci->adjust_b_mode & ADJUST_B_MODE_RESTORE) + { + + Info = dtmf_restore_config (Id, plci, Rc); + if ((Info != GOOD) || plci->internal_command) + break; + + } + plci->adjust_b_state = ADJUST_B_RESTORE_MIXER_1; + Rc = OK; + case ADJUST_B_RESTORE_MIXER_1: + case ADJUST_B_RESTORE_MIXER_2: + case ADJUST_B_RESTORE_MIXER_3: + case ADJUST_B_RESTORE_MIXER_4: + case ADJUST_B_RESTORE_MIXER_5: + case ADJUST_B_RESTORE_MIXER_6: + case ADJUST_B_RESTORE_MIXER_7: + if (plci->adjust_b_mode & ADJUST_B_MODE_RESTORE) + { + + Info = mixer_restore_config (Id, plci, Rc); + if ((Info != GOOD) || plci->internal_command) + break; + + } + plci->adjust_b_state = ADJUST_B_END; + case ADJUST_B_END: + break; + } + return (Info); +} + + +static void adjust_b1_resource (dword Id, PLCI *plci, API_SAVE *bp_msg, word b1_facilities, word internal_command) +{ + + dbug (1, dprintf ("[%06lx] %s,%d: adjust_b1_resource %d %04x", + UnMapId (Id), (char *)(FILE_), __LINE__, + plci->B1_resource, b1_facilities)); + + plci->adjust_b_parms_msg = bp_msg; + plci->adjust_b_facilities = b1_facilities; + plci->adjust_b_command = internal_command; + plci->adjust_b_ncci = (word)(Id >> 16); + if ((bp_msg == NULL) && (plci->B1_resource == 0)) + plci->adjust_b_mode = ADJUST_B_MODE_SAVE | ADJUST_B_MODE_NO_RESOURCE | ADJUST_B_MODE_SWITCH_L1; + else + plci->adjust_b_mode = ADJUST_B_MODE_SAVE | ADJUST_B_MODE_SWITCH_L1 | ADJUST_B_MODE_RESTORE; + plci->adjust_b_state = ADJUST_B_START; + dbug (1, dprintf ("[%06lx] %s,%d: Adjust B1 resource %d %04x...", + UnMapId (Id), (char *)(FILE_), __LINE__, + plci->B1_resource, b1_facilities)); +} + + +static void adjust_b_restore (dword Id, PLCI *plci, byte Rc) +{ + word internal_command; + + dbug (1, dprintf ("[%06lx] %s,%d: adjust_b_restore %02x %04x", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc, plci->internal_command)); + + internal_command = plci->internal_command; + plci->internal_command = 0; + switch (internal_command) + { + default: + plci->command = 0; + if (plci->req_in != 0) + { + plci->internal_command = ADJUST_B_RESTORE_1; + break; + } + Rc = OK; + case ADJUST_B_RESTORE_1: + if ((Rc != OK) && (Rc != OK_FC)) + { + dbug (1, dprintf ("[%06lx] %s,%d: Adjust B enqueued failed %02x", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc)); + } + plci->adjust_b_parms_msg = NULL; + plci->adjust_b_facilities = plci->B1_facilities; + plci->adjust_b_command = ADJUST_B_RESTORE_2; + plci->adjust_b_ncci = (word)(Id >> 16); + plci->adjust_b_mode = ADJUST_B_MODE_RESTORE; + plci->adjust_b_state = ADJUST_B_START; + dbug (1, dprintf ("[%06lx] %s,%d: Adjust B restore...", + UnMapId (Id), (char *)(FILE_), __LINE__)); + case ADJUST_B_RESTORE_2: + if (adjust_b_process (Id, plci, Rc) != GOOD) + { + dbug (1, dprintf ("[%06lx] %s,%d: Adjust B restore failed", + UnMapId (Id), (char *)(FILE_), __LINE__)); + } + if (plci->internal_command) + break; + break; + } +} + + +static void reset_b3_command (dword Id, PLCI *plci, byte Rc) +{ + word Info; + word internal_command; + + dbug (1, dprintf ("[%06lx] %s,%d: reset_b3_command %02x %04x", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc, plci->internal_command)); + + Info = GOOD; + internal_command = plci->internal_command; + plci->internal_command = 0; + switch (internal_command) + { + default: + plci->command = 0; + plci->adjust_b_parms_msg = NULL; + plci->adjust_b_facilities = plci->B1_facilities; + plci->adjust_b_command = RESET_B3_COMMAND_1; + plci->adjust_b_ncci = (word)(Id >> 16); + plci->adjust_b_mode = ADJUST_B_MODE_REMOVE_L23 | ADJUST_B_MODE_ASSIGN_L23 | ADJUST_B_MODE_CONNECT; + plci->adjust_b_state = ADJUST_B_START; + dbug (1, dprintf ("[%06lx] %s,%d: Reset B3...", + UnMapId (Id), (char *)(FILE_), __LINE__)); + case RESET_B3_COMMAND_1: + Info = adjust_b_process (Id, plci, Rc); + if (Info != GOOD) + { + dbug (1, dprintf ("[%06lx] %s,%d: Reset failed", + UnMapId (Id), (char *)(FILE_), __LINE__)); + break; + } + if (plci->internal_command) + return; + break; + } +/* sendf (plci->appl, _RESET_B3_R | CONFIRM, Id, plci->number, "w", Info);*/ + sendf(plci->appl,_RESET_B3_I,Id,0,"s",""); +} + + +static void select_b_command (dword Id, PLCI *plci, byte Rc) +{ + word Info; + word internal_command; + byte esc_chi[3]; + + dbug (1, dprintf ("[%06lx] %s,%d: select_b_command %02x %04x", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc, plci->internal_command)); + + Info = GOOD; + internal_command = plci->internal_command; + plci->internal_command = 0; + switch (internal_command) + { + default: + plci->command = 0; + plci->adjust_b_parms_msg = &plci->saved_msg; + if ((plci->tel == ADV_VOICE) && (plci == plci->adapter->AdvSignalPLCI)) + plci->adjust_b_facilities = plci->B1_facilities | B1_FACILITY_VOICE; + else + plci->adjust_b_facilities = plci->B1_facilities & ~B1_FACILITY_VOICE; + plci->adjust_b_command = SELECT_B_COMMAND_1; + plci->adjust_b_ncci = (word)(Id >> 16); + if (plci->saved_msg.parms[0].length == 0) + { + plci->adjust_b_mode = ADJUST_B_MODE_SAVE | ADJUST_B_MODE_REMOVE_L23 | ADJUST_B_MODE_SWITCH_L1 | + ADJUST_B_MODE_NO_RESOURCE; + } + else + { + plci->adjust_b_mode = ADJUST_B_MODE_SAVE | ADJUST_B_MODE_REMOVE_L23 | ADJUST_B_MODE_SWITCH_L1 | + ADJUST_B_MODE_ASSIGN_L23 | ADJUST_B_MODE_USER_CONNECT | ADJUST_B_MODE_RESTORE; + } + plci->adjust_b_state = ADJUST_B_START; + dbug (1, dprintf ("[%06lx] %s,%d: Select B protocol...", + UnMapId (Id), (char *)(FILE_), __LINE__)); + case SELECT_B_COMMAND_1: + Info = adjust_b_process (Id, plci, Rc); + if (Info != GOOD) + { + dbug (1, dprintf ("[%06lx] %s,%d: Select B protocol failed", + UnMapId (Id), (char *)(FILE_), __LINE__)); + break; + } + if (plci->internal_command) + return; + if (plci->tel == ADV_VOICE) + { + esc_chi[0] = 0x02; + esc_chi[1] = 0x18; + esc_chi[2] = plci->b_channel; + SetVoiceChannel (plci->adapter->AdvCodecPLCI, esc_chi, plci->adapter); + } + break; + } + sendf (plci->appl, _SELECT_B_REQ | CONFIRM, Id, plci->number, "w", Info); +} + + +static void fax_connect_ack_command (dword Id, PLCI *plci, byte Rc) +{ + word Info; + word internal_command; + + dbug (1, dprintf ("[%06lx] %s,%d: fax_connect_ack_command %02x %04x", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc, plci->internal_command)); + + Info = GOOD; + internal_command = plci->internal_command; + plci->internal_command = 0; + switch (internal_command) + { + default: + plci->command = 0; + case FAX_CONNECT_ACK_COMMAND_1: + if (plci_nl_busy (plci)) + { + plci->internal_command = FAX_CONNECT_ACK_COMMAND_1; + return; + } + plci->internal_command = FAX_CONNECT_ACK_COMMAND_2; + plci->NData[0].P = plci->fax_connect_info_buffer; + plci->NData[0].PLength = plci->fax_connect_info_length; + plci->NL.X = plci->NData; + plci->NL.ReqCh = 0; + plci->NL.Req = plci->nl_req = (byte) N_CONNECT_ACK; + plci->adapter->request (&plci->NL); + return; + case FAX_CONNECT_ACK_COMMAND_2: + if ((Rc != OK) && (Rc != OK_FC)) + { + dbug (1, dprintf ("[%06lx] %s,%d: FAX issue CONNECT ACK failed %02x", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc)); + break; + } + } + if ((plci->ncpi_state & NCPI_VALID_CONNECT_B3_ACT) + && !(plci->ncpi_state & NCPI_CONNECT_B3_ACT_SENT)) + { + if (plci->B3_prot == 4) + sendf(plci->appl,_CONNECT_B3_ACTIVE_I,Id,0,"s",""); + else + sendf(plci->appl,_CONNECT_B3_ACTIVE_I,Id,0,"S",plci->ncpi_buffer); + plci->ncpi_state |= NCPI_CONNECT_B3_ACT_SENT; + } +} + + +static void fax_edata_ack_command (dword Id, PLCI *plci, byte Rc) +{ + word Info; + word internal_command; + + dbug (1, dprintf ("[%06lx] %s,%d: fax_edata_ack_command %02x %04x", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc, plci->internal_command)); + + Info = GOOD; + internal_command = plci->internal_command; + plci->internal_command = 0; + switch (internal_command) + { + default: + plci->command = 0; + case FAX_EDATA_ACK_COMMAND_1: + if (plci_nl_busy (plci)) + { + plci->internal_command = FAX_EDATA_ACK_COMMAND_1; + return; + } + plci->internal_command = FAX_EDATA_ACK_COMMAND_2; + plci->NData[0].P = plci->fax_connect_info_buffer; + plci->NData[0].PLength = plci->fax_edata_ack_length; + plci->NL.X = plci->NData; + plci->NL.ReqCh = 0; + plci->NL.Req = plci->nl_req = (byte) N_EDATA; + plci->adapter->request (&plci->NL); + return; + case FAX_EDATA_ACK_COMMAND_2: + if ((Rc != OK) && (Rc != OK_FC)) + { + dbug (1, dprintf ("[%06lx] %s,%d: FAX issue EDATA ACK failed %02x", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc)); + break; + } + } +} + + +static void fax_connect_info_command (dword Id, PLCI *plci, byte Rc) +{ + word Info; + word internal_command; + + dbug (1, dprintf ("[%06lx] %s,%d: fax_connect_info_command %02x %04x", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc, plci->internal_command)); + + Info = GOOD; + internal_command = plci->internal_command; + plci->internal_command = 0; + switch (internal_command) + { + default: + plci->command = 0; + case FAX_CONNECT_INFO_COMMAND_1: + if (plci_nl_busy (plci)) + { + plci->internal_command = FAX_CONNECT_INFO_COMMAND_1; + return; + } + plci->internal_command = FAX_CONNECT_INFO_COMMAND_2; + plci->NData[0].P = plci->fax_connect_info_buffer; + plci->NData[0].PLength = plci->fax_connect_info_length; + plci->NL.X = plci->NData; + plci->NL.ReqCh = 0; + plci->NL.Req = plci->nl_req = (byte) N_EDATA; + plci->adapter->request (&plci->NL); + return; + case FAX_CONNECT_INFO_COMMAND_2: + if ((Rc != OK) && (Rc != OK_FC)) + { + dbug (1, dprintf ("[%06lx] %s,%d: FAX setting connect info failed %02x", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc)); + Info = _WRONG_STATE; + break; + } + if (plci_nl_busy (plci)) + { + plci->internal_command = FAX_CONNECT_INFO_COMMAND_2; + return; + } + plci->command = _CONNECT_B3_R; + nl_req_ncci (plci, N_CONNECT, 0); + send_req (plci); + return; + } + sendf (plci->appl, _CONNECT_B3_R | CONFIRM, Id, plci->number, "w", Info); +} + + +static void fax_adjust_b23_command (dword Id, PLCI *plci, byte Rc) +{ + word Info; + word internal_command; + + dbug (1, dprintf ("[%06lx] %s,%d: fax_adjust_b23_command %02x %04x", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc, plci->internal_command)); + + Info = GOOD; + internal_command = plci->internal_command; + plci->internal_command = 0; + switch (internal_command) + { + default: + plci->command = 0; + plci->adjust_b_parms_msg = NULL; + plci->adjust_b_facilities = plci->B1_facilities; + plci->adjust_b_command = FAX_ADJUST_B23_COMMAND_1; + plci->adjust_b_ncci = (word)(Id >> 16); + plci->adjust_b_mode = ADJUST_B_MODE_REMOVE_L23 | ADJUST_B_MODE_ASSIGN_L23; + plci->adjust_b_state = ADJUST_B_START; + dbug (1, dprintf ("[%06lx] %s,%d: FAX adjust B23...", + UnMapId (Id), (char *)(FILE_), __LINE__)); + case FAX_ADJUST_B23_COMMAND_1: + Info = adjust_b_process (Id, plci, Rc); + if (Info != GOOD) + { + dbug (1, dprintf ("[%06lx] %s,%d: FAX adjust failed", + UnMapId (Id), (char *)(FILE_), __LINE__)); + break; + } + if (plci->internal_command) + return; + case FAX_ADJUST_B23_COMMAND_2: + if (plci_nl_busy (plci)) + { + plci->internal_command = FAX_ADJUST_B23_COMMAND_2; + return; + } + plci->command = _CONNECT_B3_R; + nl_req_ncci (plci, N_CONNECT, 0); + send_req (plci); + return; + } + sendf (plci->appl, _CONNECT_B3_R | CONFIRM, Id, plci->number, "w", Info); +} + + +static void fax_disconnect_command (dword Id, PLCI *plci, byte Rc) +{ + word internal_command; + + dbug (1, dprintf ("[%06lx] %s,%d: fax_disconnect_command %02x %04x", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc, plci->internal_command)); + + internal_command = plci->internal_command; + plci->internal_command = 0; + switch (internal_command) + { + default: + plci->command = 0; + plci->internal_command = FAX_DISCONNECT_COMMAND_1; + return; + case FAX_DISCONNECT_COMMAND_1: + case FAX_DISCONNECT_COMMAND_2: + case FAX_DISCONNECT_COMMAND_3: + if ((Rc != OK) && (Rc != OK_FC) && (Rc != 0)) + { + dbug (1, dprintf ("[%06lx] %s,%d: FAX disconnect EDATA failed %02x", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc)); + break; + } + if (Rc == OK) + { + if ((internal_command == FAX_DISCONNECT_COMMAND_1) + || (internal_command == FAX_DISCONNECT_COMMAND_2)) + { + plci->internal_command = FAX_DISCONNECT_COMMAND_2; + } + } + else if (Rc == 0) + { + if (internal_command == FAX_DISCONNECT_COMMAND_1) + plci->internal_command = FAX_DISCONNECT_COMMAND_3; + } + return; + } +} + + + +static void rtp_connect_b3_req_command (dword Id, PLCI *plci, byte Rc) +{ + word Info; + word internal_command; + + dbug (1, dprintf ("[%06lx] %s,%d: rtp_connect_b3_req_command %02x %04x", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc, plci->internal_command)); + + Info = GOOD; + internal_command = plci->internal_command; + plci->internal_command = 0; + switch (internal_command) + { + default: + plci->command = 0; + case RTP_CONNECT_B3_REQ_COMMAND_1: + if (plci_nl_busy (plci)) + { + plci->internal_command = RTP_CONNECT_B3_REQ_COMMAND_1; + return; + } + plci->internal_command = RTP_CONNECT_B3_REQ_COMMAND_2; + nl_req_ncci (plci, N_CONNECT, 0); + send_req (plci); + return; + case RTP_CONNECT_B3_REQ_COMMAND_2: + if ((Rc != OK) && (Rc != OK_FC)) + { + dbug (1, dprintf ("[%06lx] %s,%d: RTP setting connect info failed %02x", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc)); + Info = _WRONG_STATE; + break; + } + if (plci_nl_busy (plci)) + { + plci->internal_command = RTP_CONNECT_B3_REQ_COMMAND_2; + return; + } + plci->internal_command = RTP_CONNECT_B3_REQ_COMMAND_3; + plci->NData[0].PLength = plci->internal_req_buffer[0]; + plci->NData[0].P = plci->internal_req_buffer + 1; + plci->NL.X = plci->NData; + plci->NL.ReqCh = 0; + plci->NL.Req = plci->nl_req = (byte) N_UDATA; + plci->adapter->request (&plci->NL); + break; + case RTP_CONNECT_B3_REQ_COMMAND_3: + return; + } + sendf (plci->appl, _CONNECT_B3_R | CONFIRM, Id, plci->number, "w", Info); +} + + +static void rtp_connect_b3_res_command (dword Id, PLCI *plci, byte Rc) +{ + word Info; + word internal_command; + + dbug (1, dprintf ("[%06lx] %s,%d: rtp_connect_b3_res_command %02x %04x", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc, plci->internal_command)); + + Info = GOOD; + internal_command = plci->internal_command; + plci->internal_command = 0; + switch (internal_command) + { + default: + plci->command = 0; + case RTP_CONNECT_B3_RES_COMMAND_1: + if (plci_nl_busy (plci)) + { + plci->internal_command = RTP_CONNECT_B3_RES_COMMAND_1; + return; + } + plci->internal_command = RTP_CONNECT_B3_RES_COMMAND_2; + nl_req_ncci (plci, N_CONNECT_ACK, (byte)(Id >> 16)); + send_req (plci); + return; + case RTP_CONNECT_B3_RES_COMMAND_2: + if ((Rc != OK) && (Rc != OK_FC)) + { + dbug (1, dprintf ("[%06lx] %s,%d: RTP setting connect resp info failed %02x", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc)); + Info = _WRONG_STATE; + break; + } + if (plci_nl_busy (plci)) + { + plci->internal_command = RTP_CONNECT_B3_RES_COMMAND_2; + return; + } + sendf (plci->appl, _CONNECT_B3_ACTIVE_I, Id, 0, "s", ""); + plci->internal_command = RTP_CONNECT_B3_RES_COMMAND_3; + plci->NData[0].PLength = plci->internal_req_buffer[0]; + plci->NData[0].P = plci->internal_req_buffer + 1; + plci->NL.X = plci->NData; + plci->NL.ReqCh = 0; + plci->NL.Req = plci->nl_req = (byte) N_UDATA; + plci->adapter->request (&plci->NL); + return; + case RTP_CONNECT_B3_RES_COMMAND_3: + return; + } +} + + + +static void hold_save_command (dword Id, PLCI *plci, byte Rc) +{ + byte SS_Ind[] = "\x05\x02\x00\x02\x00\x00"; /* Hold_Ind struct*/ + word Info; + word internal_command; + + dbug (1, dprintf ("[%06lx] %s,%d: hold_save_command %02x %04x", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc, plci->internal_command)); + + Info = GOOD; + internal_command = plci->internal_command; + plci->internal_command = 0; + switch (internal_command) + { + default: + if (!plci->NL.Id) + break; + plci->command = 0; + plci->adjust_b_parms_msg = NULL; + plci->adjust_b_facilities = plci->B1_facilities; + plci->adjust_b_command = HOLD_SAVE_COMMAND_1; + plci->adjust_b_ncci = (word)(Id >> 16); + plci->adjust_b_mode = ADJUST_B_MODE_SAVE | ADJUST_B_MODE_REMOVE_L23; + plci->adjust_b_state = ADJUST_B_START; + dbug (1, dprintf ("[%06lx] %s,%d: HOLD save...", + UnMapId (Id), (char *)(FILE_), __LINE__)); + case HOLD_SAVE_COMMAND_1: + Info = adjust_b_process (Id, plci, Rc); + if (Info != GOOD) + { + dbug (1, dprintf ("[%06lx] %s,%d: HOLD save failed", + UnMapId (Id), (char *)(FILE_), __LINE__)); + break; + } + if (plci->internal_command) + return; + } + sendf (plci->appl, _FACILITY_I, Id & 0xffffL, 0, "ws", 3, SS_Ind); +} + + +static void retrieve_restore_command (dword Id, PLCI *plci, byte Rc) +{ + byte SS_Ind[] = "\x05\x03\x00\x02\x00\x00"; /* Retrieve_Ind struct*/ + word Info; + word internal_command; + + dbug (1, dprintf ("[%06lx] %s,%d: retrieve_restore_command %02x %04x", + UnMapId (Id), (char *)(FILE_), __LINE__, Rc, plci->internal_command)); + + Info = GOOD; + internal_command = plci->internal_command; + plci->internal_command = 0; + switch (internal_command) + { + default: + plci->command = 0; + plci->adjust_b_parms_msg = NULL; + plci->adjust_b_facilities = plci->B1_facilities; + plci->adjust_b_command = RETRIEVE_RESTORE_COMMAND_1; + plci->adjust_b_ncci = (word)(Id >> 16); + plci->adjust_b_mode = ADJUST_B_MODE_ASSIGN_L23 | ADJUST_B_MODE_USER_CONNECT | ADJUST_B_MODE_RESTORE; + plci->adjust_b_state = ADJUST_B_START; + dbug (1, dprintf ("[%06lx] %s,%d: RETRIEVE restore...", + UnMapId (Id), (char *)(FILE_), __LINE__)); + case RETRIEVE_RESTORE_COMMAND_1: + Info = adjust_b_process (Id, plci, Rc); + if (Info != GOOD) + { + dbug (1, dprintf ("[%06lx] %s,%d: RETRIEVE restore failed", + UnMapId (Id), (char *)(FILE_), __LINE__)); + break; + } + if (plci->internal_command) + return; + } + sendf (plci->appl, _FACILITY_I, Id & 0xffffL, 0, "ws", 3, SS_Ind); +} + + +static void init_b1_config (PLCI *plci) +{ + + dbug (1, dprintf ("[%06lx] %s,%d: init_b1_config", + (dword)((plci->Id << 8) | UnMapController (plci->adapter->Id)), + (char *)(FILE_), __LINE__)); + + plci->B1_resource = 0; + plci->B1_facilities = 0; + + plci->li_bchannel_id = 0; + mixer_clear_config (plci); + + + ec_clear_config (plci); + + + dtmf_rec_clear_config (plci); + dtmf_send_clear_config (plci); + dtmf_parameter_clear_config (plci); + + adv_voice_clear_config (plci); + adjust_b_clear (plci); +} + + +static void clear_b1_config (PLCI *plci) +{ + + dbug (1, dprintf ("[%06lx] %s,%d: clear_b1_config", + (dword)((plci->Id << 8) | UnMapController (plci->adapter->Id)), + (char *)(FILE_), __LINE__)); + + adv_voice_clear_config (plci); + adjust_b_clear (plci); + + ec_clear_config (plci); + + + dtmf_rec_clear_config (plci); + dtmf_send_clear_config (plci); + dtmf_parameter_clear_config (plci); + + + if ((plci->li_bchannel_id != 0) + && (li_config_table[plci->adapter->li_base + (plci->li_bchannel_id - 1)].plci == plci)) + { + mixer_clear_config (plci); + li_config_table[plci->adapter->li_base + (plci->li_bchannel_id - 1)].plci = NULL; + plci->li_bchannel_id = 0; + } + + plci->B1_resource = 0; + plci->B1_facilities = 0; +} + + +/* ----------------------------------------------------------------- + XON protocol local helpers + ----------------------------------------------------------------- */ +static void channel_flow_control_remove (PLCI * plci) { + DIVA_CAPI_ADAPTER * a = plci->adapter; + word i; + for(i=1;i<MAX_NL_CHANNEL+1;i++) { + if (a->ch_flow_plci[i] == plci->Id) { + a->ch_flow_plci[i] = 0; + a->ch_flow_control[i] = 0; + } + } +} + +static void channel_x_on (PLCI * plci, byte ch) { + DIVA_CAPI_ADAPTER * a = plci->adapter; + if (a->ch_flow_control[ch] & N_XON_SENT) { + a->ch_flow_control[ch] &= ~N_XON_SENT; + } +} + +static void channel_x_off (PLCI * plci, byte ch, byte flag) { + DIVA_CAPI_ADAPTER * a = plci->adapter; + if ((a->ch_flow_control[ch] & N_RX_FLOW_CONTROL_MASK) == 0) { + a->ch_flow_control[ch] |= (N_CH_XOFF | flag); + a->ch_flow_plci[ch] = plci->Id; + a->ch_flow_control_pending++; + } +} + +static void channel_request_xon (PLCI * plci, byte ch) { + DIVA_CAPI_ADAPTER * a = plci->adapter; + + if (a->ch_flow_control[ch] & N_CH_XOFF) { + a->ch_flow_control[ch] |= N_XON_REQ; + a->ch_flow_control[ch] &= ~N_CH_XOFF; + a->ch_flow_control[ch] &= ~N_XON_CONNECT_IND; + } +} + +static void channel_xmit_extended_xon (PLCI * plci) { + DIVA_CAPI_ADAPTER * a; + int max_ch = sizeof(a->ch_flow_control)/sizeof(a->ch_flow_control[0]); + int i, one_requested = 0; + + if ((!plci) || (!plci->Id) || ((a = plci->adapter) == 0)) { + return; + } + + for (i = 0; i < max_ch; i++) { + if ((a->ch_flow_control[i] & N_CH_XOFF) && + (a->ch_flow_control[i] & N_XON_CONNECT_IND) && + (plci->Id == a->ch_flow_plci[i])) { + channel_request_xon (plci, (byte)i); + one_requested = 1; + } + } + + if (one_requested) { + channel_xmit_xon (plci); + } +} + +/* + Try to xmit next X_ON + */ +static int find_channel_with_pending_x_on (DIVA_CAPI_ADAPTER * a, PLCI * plci) { + int max_ch = sizeof(a->ch_flow_control)/sizeof(a->ch_flow_control[0]); + int i; + + if (!(plci->adapter->manufacturer_features & MANUFACTURER_FEATURE_XONOFF_FLOW_CONTROL)) { + return (0); + } + + if (a->last_flow_control_ch >= max_ch) { + a->last_flow_control_ch = 1; + } + for (i=a->last_flow_control_ch; i < max_ch; i++) { + if ((a->ch_flow_control[i] & N_XON_REQ) && + (plci->Id == a->ch_flow_plci[i])) { + a->last_flow_control_ch = i+1; + return (i); + } + } + + for (i = 1; i < a->last_flow_control_ch; i++) { + if ((a->ch_flow_control[i] & N_XON_REQ) && + (plci->Id == a->ch_flow_plci[i])) { + a->last_flow_control_ch = i+1; + return (i); + } + } + + return (0); +} + +static void channel_xmit_xon (PLCI * plci) { + DIVA_CAPI_ADAPTER * a = plci->adapter; + byte ch; + + if (plci->nl_req || !plci->NL.Id || plci->nl_remove_id) { + return; + } + if ((ch = (byte)find_channel_with_pending_x_on (a, plci)) == 0) { + return; + } + a->ch_flow_control[ch] &= ~N_XON_REQ; + a->ch_flow_control[ch] |= N_XON_SENT; + + plci->NL.Req = plci->nl_req = (byte)N_XON; + plci->NL.ReqCh = ch; + plci->NL.X = plci->NData; + plci->NL.XNum = 1; + plci->NData[0].P = &plci->RBuffer[0]; + plci->NData[0].PLength = 0; + + plci->adapter->request(&plci->NL); +} + +static int channel_can_xon (PLCI * plci, byte ch) { + APPL * APPLptr; + DIVA_CAPI_ADAPTER * a; + word NCCIcode; + dword count; + word Num; + word i; + + APPLptr = plci->appl; + a = plci->adapter; + + if (!APPLptr) + return (0); + + NCCIcode = a->ch_ncci[ch] | (((word) a->Id) << 8); + + /* count all buffers within the Application pool */ + /* belonging to the same NCCI. XON if a first is */ + /* used. */ + count = 0; + Num = 0xffff; + for(i=0; i<APPLptr->MaxBuffer; i++) { + if(NCCIcode==APPLptr->DataNCCI[i]) count++; + if(!APPLptr->DataNCCI[i] && Num==0xffff) Num = i; + } + if ((count > 2) || (Num == 0xffff)) { + return (0); + } + return (1); +} + + +/*------------------------------------------------------------------*/ + +static word CPN_filter_ok(byte *cpn,DIVA_CAPI_ADAPTER * a,word offset) +{ + return 1; +} + + + +/**********************************************************************************/ +/* function groups the listening applications according to the CIP mask and the */ +/* Info_Mask. Each group gets just one Connect_Ind. Some application manufacturer */ +/* are not multi-instance capable, so they start e.g. 30 applications what causes */ +/* big problems on application level (one call, 30 Connect_Ind, ect). The */ +/* function must be enabled by setting "a->group_optimization_enabled" from the */ +/* OS specific part (per adapter). */ +/**********************************************************************************/ +static void group_optimization(DIVA_CAPI_ADAPTER * a, PLCI * plci) +{ + word i,j,k,busy,group_found; + dword info_mask_group[MAX_CIP_TYPES]; + dword cip_mask_group[MAX_CIP_TYPES]; + word appl_number_group_type[MAX_APPL]; + PLCI *auxplci; + + set_group_ind_mask (plci); /* all APPLs within this inc. call are allowed to dial in */ + + if(!a->group_optimization_enabled) + { + dbug(1,dprintf("No group optimization")); + return; + } + + dbug(1,dprintf("Group optimization = 0x%x...", a->group_optimization_enabled)); + + for(i=0;i<MAX_CIP_TYPES;i++) + { + info_mask_group[i] = 0; + cip_mask_group [i] = 0; + } + for(i=0;i<MAX_APPL;i++) + { + appl_number_group_type[i] = 0; + } + for(i=0; i<max_appl; i++) /* check if any multi instance capable application is present */ + { /* group_optimization set to 1 means not to optimize multi-instance capable applications (default) */ + if(application[i].Id && (application[i].MaxNCCI) > 1 && (a->CIP_Mask[i]) && (a->group_optimization_enabled ==1) ) + { + dbug(1,dprintf("Multi-Instance capable, no optimization required")); + return; /* allow good application unfiltered access */ + } + } + for(i=0; i<max_appl; i++) /* Build CIP Groups */ + { + if(application[i].Id && a->CIP_Mask[i] ) + { + for(k=0,busy=FALSE; k<a->max_plci; k++) + { + if(a->plci[k].Id) + { + auxplci = &a->plci[k]; + if(auxplci->appl == &application[i]) /* application has a busy PLCI */ + { + busy = TRUE; + dbug(1,dprintf("Appl 0x%x is busy",i+1)); + } + else if(test_c_ind_mask_bit (auxplci, i)) /* application has an incoming call pending */ + { + busy = TRUE; + dbug(1,dprintf("Appl 0x%x has inc. call pending",i+1)); + } + } + } + + for(j=0,group_found=0; j<=(MAX_CIP_TYPES) && !busy &&!group_found; j++) /* build groups with free applications only */ + { + if(j==MAX_CIP_TYPES) /* all groups are in use but group still not found */ + { /* the MAX_CIP_TYPES group enables all calls because of field overflow */ + appl_number_group_type[i] = MAX_CIP_TYPES; + group_found=TRUE; + dbug(1,dprintf("Field overflow appl 0x%x",i+1)); + } + else if( (info_mask_group[j]==a->CIP_Mask[i]) && (cip_mask_group[j]==a->Info_Mask[i]) ) + { /* is group already present ? */ + appl_number_group_type[i] = j|0x80; /* store the group number for each application */ + group_found=TRUE; + dbug(1,dprintf("Group 0x%x found with appl 0x%x, CIP=0x%lx",appl_number_group_type[i],i+1,info_mask_group[j])); + } + else if(!info_mask_group[j]) + { /* establish a new group */ + appl_number_group_type[i] = j|0x80; /* store the group number for each application */ + info_mask_group[j] = a->CIP_Mask[i]; /* store the new CIP mask for the new group */ + cip_mask_group[j] = a->Info_Mask[i]; /* store the new Info_Mask for this new group */ + group_found=TRUE; + dbug(1,dprintf("New Group 0x%x established with appl 0x%x, CIP=0x%lx",appl_number_group_type[i],i+1,info_mask_group[j])); + } + } + } + } + + for(i=0; i<max_appl; i++) /* Build group_optimization_mask_table */ + { + if(appl_number_group_type[i]) /* application is free, has listens and is member of a group */ + { + if(appl_number_group_type[i] == MAX_CIP_TYPES) + { + dbug(1,dprintf("OverflowGroup 0x%x, valid appl = 0x%x, call enabled",appl_number_group_type[i],i+1)); + } + else + { + dbug(1,dprintf("Group 0x%x, valid appl = 0x%x",appl_number_group_type[i],i+1)); + for(j=i+1; j<max_appl; j++) /* search other group members and mark them as busy */ + { + if(appl_number_group_type[i] == appl_number_group_type[j]) + { + dbug(1,dprintf("Appl 0x%x is member of group 0x%x, no call",j+1,appl_number_group_type[j])); + clear_group_ind_mask_bit (plci, j); /* disable call on other group members */ + appl_number_group_type[j] = 0; /* remove disabled group member from group list */ + } + } + } + } + else /* application should not get a call */ + { + clear_group_ind_mask_bit (plci, i); + } + } + +} + + + +/* OS notifies the driver about a application Capi_Register */ +word CapiRegister(word id) +{ + word i,j,appls_found; + + PLCI *plci; + DIVA_CAPI_ADAPTER *a; + + for(i=0,appls_found=0; i<max_appl; i++) + { + if( application[i].Id && (application[i].Id!=id) ) + { + appls_found++; /* an application has been found */ + } + } + + if(appls_found) return TRUE; + for(i=0; i<max_adapter; i++) /* scan all adapters... */ + { + a = &adapter[i]; + if(a->request) + { + if(a->flag_dynamic_l1_down) /* remove adapter from L1 tristate (Huntgroup) */ + { + if(!appls_found) /* first application does a capi register */ + { + if((j=get_plci(a))) /* activate L1 of all adapters */ + { + plci = &a->plci[j-1]; + plci->command = 0; + add_p(plci,OAD,"\x01\xfd"); + add_p(plci,CAI,"\x01\x80"); + add_p(plci,UID,"\x06\x43\x61\x70\x69\x32\x30"); + add_p(plci,SHIFT|6,NULL); + add_p(plci,SIN,"\x02\x00\x00"); + plci->internal_command = START_L1_SIG_ASSIGN_PEND; + sig_req(plci,ASSIGN,DSIG_ID); + add_p(plci,FTY,"\x02\xff\x07"); /* l1 start */ + sig_req(plci,SIG_CTRL,0); + send_req(plci); + } + } + } + } + } + return FALSE; +} + +/*------------------------------------------------------------------*/ + +/* Functions for virtual Switching e.g. Transfer by join, Conference */ + +static void VSwitchReqInd(PLCI *plci, dword Id, byte **parms) +{ + word i; + /* Format of vswitch_t: + 0 byte length + 1 byte VSWITCHIE + 2 byte VSWITCH_REQ/VSWITCH_IND + 3 byte reserved + 4 word VSwitchcommand + 6 word returnerror + 8... Params + */ + if(!plci || + !plci->appl || + !plci->State || + plci->Sig.Ind==NCR_FACILITY + ) + return; + + for(i=0;i<MAX_MULTI_IE;i++) + { + if(!parms[i][0]) continue; + if(parms[i][0]<7) + { + parms[i][0]=0; /* kill it */ + continue; + } + dbug(1,dprintf("VSwitchReqInd(%d)",parms[i][4])); + switch(parms[i][4]) + { + case VSJOIN: + if(!plci->relatedPTYPLCI || + (plci->ptyState!=S_ECT && plci->relatedPTYPLCI->ptyState!=S_ECT)) + { /* Error */ + break; + } + /* remember all necessary informations */ + if(parms[i][0]!=11 || parms[i][8]!=3) /* Length Test */ + { + break; + } + if(parms[i][2]==VSWITCH_IND && parms[i][9]==1) + { /* first indication after ECT-Request on Consultation Call */ + plci->vswitchstate=parms[i][9]; + parms[i][9]=2; /* State */ + /* now ask first Call to join */ + } + else if(parms[i][2]==VSWITCH_REQ && parms[i][9]==3) + { /* Answer of VSWITCH_REQ from first Call */ + plci->vswitchstate=parms[i][9]; + /* tell consultation call to join + and the protocol capabilities of the first call */ + } + else + { /* Error */ + break; + } + plci->vsprot=parms[i][10]; /* protocol */ + plci->vsprotdialect=parms[i][11]; /* protocoldialect */ + /* send join request to related PLCI */ + parms[i][1]=VSWITCHIE; + parms[i][2]=VSWITCH_REQ; + + plci->relatedPTYPLCI->command = 0; + plci->relatedPTYPLCI->internal_command = VSWITCH_REQ_PEND; + add_p(plci->relatedPTYPLCI,ESC,&parms[i][0]); + sig_req(plci->relatedPTYPLCI,VSWITCH_REQ,0); + send_req(plci->relatedPTYPLCI); + break; + case VSTRANSPORT: + default: + if(plci->relatedPTYPLCI && + plci->vswitchstate==3 && + plci->relatedPTYPLCI->vswitchstate==3) + { + add_p(plci->relatedPTYPLCI,ESC,&parms[i][0]); + sig_req(plci->relatedPTYPLCI,VSWITCH_REQ,0); + send_req(plci->relatedPTYPLCI); + } + break; + } + parms[i][0]=0; /* kill it */ + } +} + + +/*------------------------------------------------------------------*/ + +static int diva_get_dma_descriptor (PLCI *plci, dword *dma_magic) { + ENTITY e; + IDI_SYNC_REQ* pReq = (IDI_SYNC_REQ*)&e; + + if (!(diva_xdi_extended_features & DIVA_CAPI_XDI_PROVIDES_RX_DMA)) { + return (-1); + } + + pReq->xdi_dma_descriptor_operation.Req = 0; + pReq->xdi_dma_descriptor_operation.Rc = IDI_SYNC_REQ_DMA_DESCRIPTOR_OPERATION; + + pReq->xdi_dma_descriptor_operation.info.operation = IDI_SYNC_REQ_DMA_DESCRIPTOR_ALLOC; + pReq->xdi_dma_descriptor_operation.info.descriptor_number = -1; + pReq->xdi_dma_descriptor_operation.info.descriptor_address = NULL; + pReq->xdi_dma_descriptor_operation.info.descriptor_magic = 0; + + e.user[0] = plci->adapter->Id - 1; + plci->adapter->request((ENTITY*)pReq); + + if (!pReq->xdi_dma_descriptor_operation.info.operation && + (pReq->xdi_dma_descriptor_operation.info.descriptor_number >= 0) && + pReq->xdi_dma_descriptor_operation.info.descriptor_magic) { + *dma_magic = pReq->xdi_dma_descriptor_operation.info.descriptor_magic; + dbug(3,dprintf("dma_alloc, a:%d (%d-%08x)", + plci->adapter->Id, + pReq->xdi_dma_descriptor_operation.info.descriptor_number, + *dma_magic)); + return (pReq->xdi_dma_descriptor_operation.info.descriptor_number); + } else { + dbug(1,dprintf("dma_alloc failed")); + return (-1); + } +} + +static void diva_free_dma_descriptor (PLCI *plci, int nr) { + ENTITY e; + IDI_SYNC_REQ* pReq = (IDI_SYNC_REQ*)&e; + + if (nr < 0) { + return; + } + + pReq->xdi_dma_descriptor_operation.Req = 0; + pReq->xdi_dma_descriptor_operation.Rc = IDI_SYNC_REQ_DMA_DESCRIPTOR_OPERATION; + + pReq->xdi_dma_descriptor_operation.info.operation = IDI_SYNC_REQ_DMA_DESCRIPTOR_FREE; + pReq->xdi_dma_descriptor_operation.info.descriptor_number = nr; + pReq->xdi_dma_descriptor_operation.info.descriptor_address = NULL; + pReq->xdi_dma_descriptor_operation.info.descriptor_magic = 0; + + e.user[0] = plci->adapter->Id - 1; + plci->adapter->request((ENTITY*)pReq); + + if (!pReq->xdi_dma_descriptor_operation.info.operation) { + dbug(1,dprintf("dma_free(%d)", nr)); + } else { + dbug(1,dprintf("dma_free failed (%d)", nr)); + } +} + +/*------------------------------------------------------------------*/ diff --git a/drivers/isdn/hardware/eicon/mi_pc.h b/drivers/isdn/hardware/eicon/mi_pc.h new file mode 100644 index 000000000000..a861dac1f784 --- /dev/null +++ b/drivers/isdn/hardware/eicon/mi_pc.h @@ -0,0 +1,204 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +/*---------------------------------------------------------------------------- +// MAESTRA ISA PnP */ +#define BRI_MEMORY_BASE 0x1f700000 +#define BRI_MEMORY_SIZE 0x00100000 /* 1MB on the BRI */ +#define BRI_SHARED_RAM_SIZE 0x00010000 /* 64k shared RAM */ +#define BRI_RAY_TAYLOR_DSP_CODE_SIZE 0x00020000 /* max 128k DSP-Code (Ray Taylor's code) */ +#define BRI_ORG_MAX_DSP_CODE_SIZE 0x00050000 /* max 320k DSP-Code (Telindus) */ +#define BRI_V90D_MAX_DSP_CODE_SIZE 0x00060000 /* max 384k DSP-Code if V.90D included */ +#define BRI_CACHED_ADDR(x) (((x) & 0x1fffffffL) | 0x80000000L) +#define BRI_UNCACHED_ADDR(x) (((x) & 0x1fffffffL) | 0xa0000000L) +#define ADDR 4 +#define ADDRH 6 +#define DATA 0 +#define RESET 7 +#define DEFAULT_ADDRESS 0x240 +#define DEFAULT_IRQ 3 +#define M_PCI_ADDR 0x04 /* MAESTRA BRI PCI */ +#define M_PCI_ADDRH 0x0c /* MAESTRA BRI PCI */ +#define M_PCI_DATA 0x00 /* MAESTRA BRI PCI */ +#define M_PCI_RESET 0x10 /* MAESTRA BRI PCI */ +/*---------------------------------------------------------------------------- +// MAESTRA PRI PCI */ +#define MP_IRQ_RESET 0xc18 /* offset of isr in the CONFIG memory bar */ +#define MP_IRQ_RESET_VAL 0xfe /* value to clear an interrupt */ +#define MP_MEMORY_SIZE 0x00400000 /* 4MB on standard PRI */ +#define MP2_MEMORY_SIZE 0x00800000 /* 8MB on PRI Rev. 2 */ +#define MP_SHARED_RAM_OFFSET 0x00001000 /* offset of shared RAM base in the DRAM memory bar */ +#define MP_SHARED_RAM_SIZE 0x00010000 /* 64k shared RAM */ +#define MP_PROTOCOL_OFFSET (MP_SHARED_RAM_OFFSET + MP_SHARED_RAM_SIZE) +#define MP_RAY_TAYLOR_DSP_CODE_SIZE 0x00040000 /* max 256k DSP-Code (Ray Taylor's code) */ +#define MP_ORG_MAX_DSP_CODE_SIZE 0x00060000 /* max 384k DSP-Code (Telindus) */ +#define MP_V90D_MAX_DSP_CODE_SIZE 0x00070000 /* max 448k DSP-Code if V.90D included) */ +#define MP_VOIP_MAX_DSP_CODE_SIZE 0x00090000 /* max 576k DSP-Code if voice over IP included */ +#define MP_CACHED_ADDR(x) (((x) & 0x1fffffffL) | 0x80000000L) +#define MP_UNCACHED_ADDR(x) (((x) & 0x1fffffffL) | 0xa0000000L) +#define MP_RESET 0x20 /* offset of RESET register in the DEVICES memory bar */ +/* RESET register bits */ +#define _MP_S2M_RESET 0x10 /* active lo */ +#define _MP_LED2 0x08 /* 1 = on */ +#define _MP_LED1 0x04 /* 1 = on */ +#define _MP_DSP_RESET 0x02 /* active lo */ +#define _MP_RISC_RESET 0x81 /* active hi, bit 7 for compatibility with old boards */ +/* CPU exception context structure in MP shared ram after trap */ +typedef struct mp_xcptcontext_s MP_XCPTC; +struct mp_xcptcontext_s { + dword sr; + dword cr; + dword epc; + dword vaddr; + dword regs[32]; + dword mdlo; + dword mdhi; + dword reseverd; + dword xclass; +}; +/* boot interface structure for PRI */ +struct mp_load { + dword volatile cmd; + dword volatile addr; + dword volatile len; + dword volatile err; + dword volatile live; + dword volatile res1[0x1b]; + dword volatile TrapId; /* has value 0x999999XX on a CPU trap */ + dword volatile res2[0x03]; + MP_XCPTC volatile xcpt; /* contains register dump */ + dword volatile rest[((0x1020>>2)-6) - 0x1b - 1 - 0x03 - (sizeof(MP_XCPTC)>>2)]; + dword volatile signature; + dword data[60000]; /* real interface description */ +}; +/*----------------------------------------------------------------------------*/ +/* SERVER 4BRI (Quattro PCI) */ +#define MQ_BOARD_REG_OFFSET 0x800000 /* PC relative On board registers offset */ +#define MQ_BREG_RISC 0x1200 /* RISC Reset ect */ +#define MQ_RISC_COLD_RESET_MASK 0x0001 /* RISC Cold reset */ +#define MQ_RISC_WARM_RESET_MASK 0x0002 /* RISC Warm reset */ +#define MQ_BREG_IRQ_TEST 0x0608 /* Interrupt request, no CPU interaction */ +#define MQ_IRQ_REQ_ON 0x1 +#define MQ_IRQ_REQ_OFF 0x0 +#define MQ_BOARD_DSP_OFFSET 0xa00000 /* PC relative On board DSP regs offset */ +#define MQ_DSP1_ADDR_OFFSET 0x0008 /* Addr register offset DSP 1 subboard 1 */ +#define MQ_DSP2_ADDR_OFFSET 0x0208 /* Addr register offset DSP 2 subboard 1 */ +#define MQ_DSP1_DATA_OFFSET 0x0000 /* Data register offset DSP 1 subboard 1 */ +#define MQ_DSP2_DATA_OFFSET 0x0200 /* Data register offset DSP 2 subboard 1 */ +#define MQ_DSP_JUNK_OFFSET 0x0400 /* DSP Data/Addr regs subboard offset */ +#define MQ_ISAC_DSP_RESET 0x0028 /* ISAC and DSP reset address offset */ +#define MQ_BOARD_ISAC_DSP_RESET 0x800028 /* ISAC and DSP reset address offset */ +#define MQ_INSTANCE_COUNT 4 /* 4BRI consists of four instances */ +#define MQ_MEMORY_SIZE 0x00400000 /* 4MB on standard 4BRI */ +#define MQ_CTRL_SIZE 0x00002000 /* 8K memory mapped registers */ +#define MQ_SHARED_RAM_SIZE 0x00010000 /* 64k shared RAM */ +#define MQ_ORG_MAX_DSP_CODE_SIZE 0x00050000 /* max 320k DSP-Code (Telindus) */ +#define MQ_V90D_MAX_DSP_CODE_SIZE 0x00060000 /* max 384K DSP-Code if V.90D included */ +#define MQ_VOIP_MAX_DSP_CODE_SIZE 0x00028000 /* max 4*160k = 640K DSP-Code if voice over IP included */ +#define MQ_CACHED_ADDR(x) (((x) & 0x1fffffffL) | 0x80000000L) +#define MQ_UNCACHED_ADDR(x) (((x) & 0x1fffffffL) | 0xa0000000L) +/*--------------------------------------------------------------------------------------------*/ +/* Additional definitions reflecting the different address map of the SERVER 4BRI V2 */ +#define MQ2_BREG_RISC 0x0200 /* RISC Reset ect */ +#define MQ2_BREG_IRQ_TEST 0x0400 /* Interrupt request, no CPU interaction */ +#define MQ2_BOARD_DSP_OFFSET 0x800000 /* PC relative On board DSP regs offset */ +#define MQ2_DSP1_DATA_OFFSET 0x1800 /* Data register offset DSP 1 subboard 1 */ +#define MQ2_DSP1_ADDR_OFFSET 0x1808 /* Addr register offset DSP 1 subboard 1 */ +#define MQ2_DSP2_DATA_OFFSET 0x1810 /* Data register offset DSP 2 subboard 1 */ +#define MQ2_DSP2_ADDR_OFFSET 0x1818 /* Addr register offset DSP 2 subboard 1 */ +#define MQ2_DSP_JUNK_OFFSET 0x1000 /* DSP Data/Addr regs subboard offset */ +#define MQ2_ISAC_DSP_RESET 0x0000 /* ISAC and DSP reset address offset */ +#define MQ2_BOARD_ISAC_DSP_RESET 0x800000 /* ISAC and DSP reset address offset */ +#define MQ2_IPACX_CONFIG 0x0300 /* IPACX Configuration TE(0)/NT(1) */ +#define MQ2_BOARD_IPACX_CONFIG 0x800300 /* "" */ +#define MQ2_MEMORY_SIZE 0x01000000 /* 16MB code/data memory */ +#define MQ2_CTRL_SIZE 0x00008000 /* 32K memory mapped registers */ +/*----------------------------------------------------------------------------*/ +/* SERVER BRI 2M/2F as derived from 4BRI V2 */ +#define BRI2_MEMORY_SIZE 0x00800000 /* 8MB code/data memory */ +#define BRI2_PROTOCOL_MEMORY_SIZE (MQ2_MEMORY_SIZE >> 2) /* same as one 4BRI Rev.2 task */ +#define BRI2_CTRL_SIZE 0x00008000 /* 32K memory mapped registers */ +#define M_INSTANCE_COUNT 1 /* BRI consists of one instance */ +/* + * Some useful constants for proper initialization of the GT6401x + */ +#define ID_REG 0x0000 /*Pci reg-contain the Dev&Ven ID of the card*/ +#define RAS0_BASEREG 0x0010 /*Ras0 register - contain the base addr Ras0*/ +#define RAS2_BASEREG 0x0014 +#define CS_BASEREG 0x0018 +#define BOOT_BASEREG 0x001c +#define GTREGS_BASEREG 0x0024 /*GTRegsBase reg-contain the base addr where*/ + /*the GT64010 internal regs where mapped */ +/* + * GT64010 internal registers + */ + /* DRAM device coding */ +#define LOW_RAS0_DREG 0x0400 /*Ras0 low decode address*/ +#define HI_RAS0_DREG 0x0404 /*Ras0 high decode address*/ +#define LOW_RAS1_DREG 0x0408 /*Ras1 low decode address*/ +#define HI_RAS1_DREG 0x040c /*Ras1 high decode address*/ +#define LOW_RAS2_DREG 0x0410 /*Ras2 low decode address*/ +#define HI_RAS2_DREG 0x0414 /*Ras2 high decode address*/ +#define LOW_RAS3_DREG 0x0418 /*Ras3 low decode address*/ +#define HI_RAS3_DREG 0x041c /*Ras3 high decode address*/ + /* I/O CS device coding */ +#define LOW_CS0_DREG 0x0420 /* CS0* low decode register */ +#define HI_CS0_DREG 0x0424 /* CS0* high decode register */ +#define LOW_CS1_DREG 0x0428 /* CS1* low decode register */ +#define HI_CS1_DREG 0x042c /* CS1* high decode register */ +#define LOW_CS2_DREG 0x0430 /* CS2* low decode register */ +#define HI_CS2_DREG 0x0434 /* CS2* high decode register */ +#define LOW_CS3_DREG 0x0438 /* CS3* low decode register */ +#define HI_CS3_DREG 0x043c /* CS3* high decode register */ + /* Boot PROM device coding */ +#define LOW_BOOTCS_DREG 0x0440 /* Boot CS low decode register */ +#define HI_BOOTCS_DREG 0x0444 /* Boot CS High decode register */ + /* DRAM group coding (for CPU) */ +#define LO_RAS10_GREG 0x0008 /*Ras1..0 group low decode address*/ +#define HI_RAS10_GREG 0x0010 /*Ras1..0 group high decode address*/ +#define LO_RAS32_GREG 0x0018 /*Ras3..2 group low decode address */ +#define HI_RAS32_GREG 0x0020 /*Ras3..2 group high decode address */ + /* I/O CS group coding for (CPU) */ +#define LO_CS20_GREG 0x0028 /* CS2..0 group low decode register */ +#define HI_CS20_GREG 0x0030 /* CS2..0 group high decode register */ +#define LO_CS3B_GREG 0x0038 /* CS3 & PROM group low decode register */ +#define HI_CS3B_GREG 0x0040 /* CS3 & PROM group high decode register */ + /* Galileo specific PCI config. */ +#define PCI_TIMEOUT_RET 0x0c04 /* Time Out and retry register */ +#define RAS10_BANKSIZE 0x0c08 /* RAS 1..0 group PCI bank size */ +#define RAS32_BANKSIZE 0x0c0c /* RAS 3..2 group PCI bank size */ +#define CS20_BANKSIZE 0x0c10 /* CS 2..0 group PCI bank size */ +#define CS3B_BANKSIZE 0x0c14 /* CS 3 & Boot group PCI bank size */ +#define DRAM_SIZE 0x0001 /*Dram size in mega bytes*/ +#define PROM_SIZE 0x08000 /*Prom size in bytes*/ +/*--------------------------------------------------------------------------*/ +#define OFFS_DIVA_INIT_TASK_COUNT 0x68 +#define OFFS_DSP_CODE_BASE_ADDR 0x6c +#define OFFS_XLOG_BUF_ADDR 0x70 +#define OFFS_XLOG_COUNT_ADDR 0x74 +#define OFFS_XLOG_OUT_ADDR 0x78 +#define OFFS_PROTOCOL_END_ADDR 0x7c +#define OFFS_PROTOCOL_ID_STRING 0x80 +/*--------------------------------------------------------------------------*/ diff --git a/drivers/isdn/hardware/eicon/mntfunc.c b/drivers/isdn/hardware/eicon/mntfunc.c new file mode 100644 index 000000000000..a564b7560031 --- /dev/null +++ b/drivers/isdn/hardware/eicon/mntfunc.c @@ -0,0 +1,370 @@ +/* $Id: mntfunc.c,v 1.19.6.4 2005/01/31 12:22:20 armin Exp $ + * + * Driver for Eicon DIVA Server ISDN cards. + * Maint module + * + * Copyright 2000-2003 by Armin Schindler (mac@melware.de) + * Copyright 2000-2003 Cytronics & Melware (info@melware.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + */ + + +#include "platform.h" +#include "di_defs.h" +#include "divasync.h" +#include "debug_if.h" + +extern char *DRIVERRELEASE_MNT; + +#define DBG_MINIMUM (DL_LOG + DL_FTL + DL_ERR) +#define DBG_DEFAULT (DBG_MINIMUM + DL_XLOG + DL_REG) + +extern void DIVA_DIDD_Read(void *, int); + +static dword notify_handle; +static DESCRIPTOR DAdapter; +static DESCRIPTOR MAdapter; +static DESCRIPTOR MaintDescriptor = + { IDI_DIMAINT, 0, 0, (IDI_CALL) diva_maint_prtComp }; + +extern int diva_os_copy_to_user(void *os_handle, void __user *dst, + const void *src, int length); +extern int diva_os_copy_from_user(void *os_handle, void *dst, + const void __user *src, int length); + +static void no_printf(unsigned char *x, ...) +{ + /* dummy debug function */ +} + +#include "debuglib.c" + +/* + * DIDD callback function + */ +static void *didd_callback(void *context, DESCRIPTOR * adapter, + int removal) +{ + if (adapter->type == IDI_DADAPTER) { + DBG_ERR(("cb: Change in DAdapter ? Oops ?.")); + } else if (adapter->type == IDI_DIMAINT) { + if (removal) { + DbgDeregister(); + memset(&MAdapter, 0, sizeof(MAdapter)); + dprintf = no_printf; + } else { + memcpy(&MAdapter, adapter, sizeof(MAdapter)); + dprintf = (DIVA_DI_PRINTF) MAdapter.request; + DbgRegister("MAINT", DRIVERRELEASE_MNT, DBG_DEFAULT); + } + } else if ((adapter->type > 0) && (adapter->type < 16)) { + if (removal) { + diva_mnt_remove_xdi_adapter(adapter); + } else { + diva_mnt_add_xdi_adapter(adapter); + } + } + return (NULL); +} + +/* + * connect to didd + */ +static int DIVA_INIT_FUNCTION connect_didd(void) +{ + int x = 0; + int dadapter = 0; + IDI_SYNC_REQ req; + DESCRIPTOR DIDD_Table[MAX_DESCRIPTORS]; + + DIVA_DIDD_Read(DIDD_Table, sizeof(DIDD_Table)); + + for (x = 0; x < MAX_DESCRIPTORS; x++) { + if (DIDD_Table[x].type == IDI_DADAPTER) { /* DADAPTER found */ + dadapter = 1; + memcpy(&DAdapter, &DIDD_Table[x], sizeof(DAdapter)); + req.didd_notify.e.Req = 0; + req.didd_notify.e.Rc = + IDI_SYNC_REQ_DIDD_REGISTER_ADAPTER_NOTIFY; + req.didd_notify.info.callback = (void *)didd_callback; + req.didd_notify.info.context = NULL; + DAdapter.request((ENTITY *) & req); + if (req.didd_notify.e.Rc != 0xff) + return (0); + notify_handle = req.didd_notify.info.handle; + /* Register MAINT (me) */ + req.didd_add_adapter.e.Req = 0; + req.didd_add_adapter.e.Rc = + IDI_SYNC_REQ_DIDD_ADD_ADAPTER; + req.didd_add_adapter.info.descriptor = + (void *) &MaintDescriptor; + DAdapter.request((ENTITY *) & req); + if (req.didd_add_adapter.e.Rc != 0xff) + return (0); + } else if ((DIDD_Table[x].type > 0) + && (DIDD_Table[x].type < 16)) { + diva_mnt_add_xdi_adapter(&DIDD_Table[x]); + } + } + return (dadapter); +} + +/* + * disconnect from didd + */ +static void DIVA_EXIT_FUNCTION disconnect_didd(void) +{ + IDI_SYNC_REQ req; + + req.didd_notify.e.Req = 0; + req.didd_notify.e.Rc = IDI_SYNC_REQ_DIDD_REMOVE_ADAPTER_NOTIFY; + req.didd_notify.info.handle = notify_handle; + DAdapter.request((ENTITY *) & req); + + req.didd_remove_adapter.e.Req = 0; + req.didd_remove_adapter.e.Rc = IDI_SYNC_REQ_DIDD_REMOVE_ADAPTER; + req.didd_remove_adapter.info.p_request = + (IDI_CALL) MaintDescriptor.request; + DAdapter.request((ENTITY *) & req); +} + +/* + * read/write maint + */ +int maint_read_write(void __user *buf, int count) +{ + byte data[128]; + dword cmd, id, mask; + int ret = 0; + + if (count < (3 * sizeof(dword))) + return (-EFAULT); + + if (diva_os_copy_from_user(NULL, (void *) &data[0], + buf, 3 * sizeof(dword))) { + return (-EFAULT); + } + + cmd = *(dword *) & data[0]; /* command */ + id = *(dword *) & data[4]; /* driver id */ + mask = *(dword *) & data[8]; /* mask or size */ + + switch (cmd) { + case DITRACE_CMD_GET_DRIVER_INFO: + if ((ret = diva_get_driver_info(id, data, sizeof(data))) > 0) { + if ((count < ret) || diva_os_copy_to_user + (NULL, buf, (void *) &data[0], ret)) + ret = -EFAULT; + } else { + ret = -EINVAL; + } + break; + + case DITRACE_READ_DRIVER_DBG_MASK: + if ((ret = diva_get_driver_dbg_mask(id, (byte *) data)) > 0) { + if ((count < ret) || diva_os_copy_to_user + (NULL, buf, (void *) &data[0], ret)) + ret = -EFAULT; + } else { + ret = -ENODEV; + } + break; + + case DITRACE_WRITE_DRIVER_DBG_MASK: + if ((ret = diva_set_driver_dbg_mask(id, mask)) <= 0) { + ret = -ENODEV; + } + break; + + /* + Filter commands will ignore the ID due to fact that filtering affects + the B- channel and Audio Tap trace levels only. Also MAINT driver will + select the right trace ID by itself + */ + case DITRACE_WRITE_SELECTIVE_TRACE_FILTER: + if (!mask) { + ret = diva_set_trace_filter (1, "*"); + } else if (mask < sizeof(data)) { + if (diva_os_copy_from_user(NULL, data, (char __user *)buf+12, mask)) { + ret = -EFAULT; + } else { + ret = diva_set_trace_filter ((int)mask, data); + } + } else { + ret = -EINVAL; + } + break; + + case DITRACE_READ_SELECTIVE_TRACE_FILTER: + if ((ret = diva_get_trace_filter (sizeof(data), data)) > 0) { + if (diva_os_copy_to_user (NULL, buf, data, ret)) + ret = -EFAULT; + } else { + ret = -ENODEV; + } + break; + + case DITRACE_READ_TRACE_ENTRY:{ + diva_os_spin_lock_magic_t old_irql; + word size; + diva_dbg_entry_head_t *pmsg; + byte *pbuf; + + if (!(pbuf = diva_os_malloc(0, mask))) { + return (-ENOMEM); + } + + for(;;) { + if (!(pmsg = + diva_maint_get_message(&size, &old_irql))) { + break; + } + if (size > mask) { + diva_maint_ack_message(0, &old_irql); + ret = -EINVAL; + break; + } + ret = size; + memcpy(pbuf, pmsg, size); + diva_maint_ack_message(1, &old_irql); + if ((count < size) || + diva_os_copy_to_user (NULL, buf, (void *) pbuf, size)) + ret = -EFAULT; + break; + } + diva_os_free(0, pbuf); + } + break; + + case DITRACE_READ_TRACE_ENTRYS:{ + diva_os_spin_lock_magic_t old_irql; + word size; + diva_dbg_entry_head_t *pmsg; + byte *pbuf = NULL; + int written = 0; + + if (mask < 4096) { + ret = -EINVAL; + break; + } + if (!(pbuf = diva_os_malloc(0, mask))) { + return (-ENOMEM); + } + + for (;;) { + if (!(pmsg = + diva_maint_get_message(&size, &old_irql))) { + break; + } + if ((size + 8) > mask) { + diva_maint_ack_message(0, &old_irql); + break; + } + /* + Write entry length + */ + pbuf[written++] = (byte) size; + pbuf[written++] = (byte) (size >> 8); + pbuf[written++] = 0; + pbuf[written++] = 0; + /* + Write message + */ + memcpy(&pbuf[written], pmsg, size); + diva_maint_ack_message(1, &old_irql); + written += size; + mask -= (size + 4); + } + pbuf[written++] = 0; + pbuf[written++] = 0; + pbuf[written++] = 0; + pbuf[written++] = 0; + + if ((count < written) || diva_os_copy_to_user(NULL, buf, (void *) pbuf, written)) { + ret = -EFAULT; + } else { + ret = written; + } + diva_os_free(0, pbuf); + } + break; + + default: + ret = -EINVAL; + } + return (ret); +} + +/* + * init + */ +int DIVA_INIT_FUNCTION mntfunc_init(int *buffer_length, void **buffer, + unsigned long diva_dbg_mem) +{ + if (*buffer_length < 64) { + *buffer_length = 64; + } + if (*buffer_length > 512) { + *buffer_length = 512; + } + *buffer_length *= 1024; + + if (diva_dbg_mem) { + *buffer = (void *) diva_dbg_mem; + } else { + while ((*buffer_length >= (64 * 1024)) + && + (!(*buffer = diva_os_malloc (0, *buffer_length)))) { + *buffer_length -= 1024; + } + + if (!*buffer) { + DBG_ERR(("init: Can not alloc trace buffer")); + return (0); + } + } + + if (diva_maint_init(*buffer, *buffer_length, (diva_dbg_mem == 0))) { + if (!diva_dbg_mem) { + diva_os_free (0, *buffer); + } + DBG_ERR(("init: maint init failed")); + return (0); + } + + if (!connect_didd()) { + DBG_ERR(("init: failed to connect to DIDD.")); + diva_maint_finit(); + if (!diva_dbg_mem) { + diva_os_free (0, *buffer); + } + return (0); + } + return (1); +} + +/* + * exit + */ +void DIVA_EXIT_FUNCTION mntfunc_finit(void) +{ + void *buffer; + int i = 100; + + DbgDeregister(); + + while (diva_mnt_shutdown_xdi_adapters() && i--) { + diva_os_sleep(10); + } + + disconnect_didd(); + + if ((buffer = diva_maint_finit())) { + diva_os_free (0, buffer); + } + + memset(&MAdapter, 0, sizeof(MAdapter)); + dprintf = no_printf; +} diff --git a/drivers/isdn/hardware/eicon/os_4bri.c b/drivers/isdn/hardware/eicon/os_4bri.c new file mode 100644 index 000000000000..cccfabc1117d --- /dev/null +++ b/drivers/isdn/hardware/eicon/os_4bri.c @@ -0,0 +1,1131 @@ +/* $Id: os_4bri.c,v 1.28.4.4 2005/02/11 19:40:25 armin Exp $ */ + +#include "platform.h" +#include "debuglib.h" +#include "cardtype.h" +#include "pc.h" +#include "pr_pc.h" +#include "di_defs.h" +#include "dsp_defs.h" +#include "di.h" +#include "io.h" + +#include "xdi_msg.h" +#include "xdi_adapter.h" +#include "os_4bri.h" +#include "diva_pci.h" +#include "mi_pc.h" +#include "dsrv4bri.h" + +static void *diva_xdiLoadFileFile = NULL; +static dword diva_xdiLoadFileLength = 0; + +/* +** IMPORTS +*/ +extern void prepare_qBri_functions(PISDN_ADAPTER IoAdapter); +extern void prepare_qBri2_functions(PISDN_ADAPTER IoAdapter); +extern void diva_xdi_display_adapter_features(int card); +extern void diva_add_slave_adapter(diva_os_xdi_adapter_t * a); + +extern int qBri_FPGA_download(PISDN_ADAPTER IoAdapter); +extern void start_qBri_hardware(PISDN_ADAPTER IoAdapter); + +extern int diva_card_read_xlog(diva_os_xdi_adapter_t * a); + +/* +** LOCALS +*/ +static unsigned long _4bri_bar_length[4] = { + 0x100, + 0x100, /* I/O */ + MQ_MEMORY_SIZE, + 0x2000 +}; +static unsigned long _4bri_v2_bar_length[4] = { + 0x100, + 0x100, /* I/O */ + MQ2_MEMORY_SIZE, + 0x10000 +}; +static unsigned long _4bri_v2_bri_bar_length[4] = { + 0x100, + 0x100, /* I/O */ + BRI2_MEMORY_SIZE, + 0x10000 +}; + + +static int diva_4bri_cleanup_adapter(diva_os_xdi_adapter_t * a); +static int _4bri_get_serial_number(diva_os_xdi_adapter_t * a); +static int diva_4bri_cmd_card_proc(struct _diva_os_xdi_adapter *a, + diva_xdi_um_cfg_cmd_t * cmd, + int length); +static int diva_4bri_cleanup_slave_adapters(diva_os_xdi_adapter_t * a); +static int diva_4bri_write_fpga_image(diva_os_xdi_adapter_t * a, + byte * data, dword length); +static int diva_4bri_reset_adapter(PISDN_ADAPTER IoAdapter); +static int diva_4bri_write_sdram_block(PISDN_ADAPTER IoAdapter, + dword address, + const byte * data, + dword length, dword limit); +static int diva_4bri_start_adapter(PISDN_ADAPTER IoAdapter, + dword start_address, dword features); +static int check_qBri_interrupt(PISDN_ADAPTER IoAdapter); +static int diva_4bri_stop_adapter(diva_os_xdi_adapter_t * a); + +static int _4bri_is_rev_2_card(int card_ordinal) +{ + switch (card_ordinal) { + case CARDTYPE_DIVASRV_Q_8M_V2_PCI: + case CARDTYPE_DIVASRV_VOICE_Q_8M_V2_PCI: + case CARDTYPE_DIVASRV_B_2M_V2_PCI: + case CARDTYPE_DIVASRV_B_2F_PCI: + case CARDTYPE_DIVASRV_VOICE_B_2M_V2_PCI: + return (1); + } + return (0); +} + +static int _4bri_is_rev_2_bri_card(int card_ordinal) +{ + switch (card_ordinal) { + case CARDTYPE_DIVASRV_B_2M_V2_PCI: + case CARDTYPE_DIVASRV_B_2F_PCI: + case CARDTYPE_DIVASRV_VOICE_B_2M_V2_PCI: + return (1); + } + return (0); +} + +static void diva_4bri_set_addresses(diva_os_xdi_adapter_t *a) +{ + dword offset = a->resources.pci.qoffset; + dword c_offset = offset * a->xdi_adapter.ControllerNumber; + + a->resources.pci.mem_type_id[MEM_TYPE_RAM] = 2; + a->resources.pci.mem_type_id[MEM_TYPE_ADDRESS] = 2; + a->resources.pci.mem_type_id[MEM_TYPE_CONTROL] = 2; + a->resources.pci.mem_type_id[MEM_TYPE_RESET] = 0; + a->resources.pci.mem_type_id[MEM_TYPE_CTLREG] = 3; + a->resources.pci.mem_type_id[MEM_TYPE_PROM] = 0; + + /* + Set up hardware related pointers + */ + a->xdi_adapter.Address = a->resources.pci.addr[2]; /* BAR2 SDRAM */ + a->xdi_adapter.Address += c_offset; + + a->xdi_adapter.Control = a->resources.pci.addr[2]; /* BAR2 SDRAM */ + + a->xdi_adapter.ram = a->resources.pci.addr[2]; /* BAR2 SDRAM */ + a->xdi_adapter.ram += c_offset + (offset - MQ_SHARED_RAM_SIZE); + + a->xdi_adapter.reset = a->resources.pci.addr[0]; /* BAR0 CONFIG */ + /* + ctlReg contains the register address for the MIPS CPU reset control + */ + a->xdi_adapter.ctlReg = a->resources.pci.addr[3]; /* BAR3 CNTRL */ + /* + prom contains the register address for FPGA and EEPROM programming + */ + a->xdi_adapter.prom = &a->xdi_adapter.reset[0x6E]; +} + +/* +** BAR0 - MEM - 0x100 - CONFIG MEM +** BAR1 - I/O - 0x100 - UNUSED +** BAR2 - MEM - MQ_MEMORY_SIZE (MQ2_MEMORY_SIZE on Rev.2) - SDRAM +** BAR3 - MEM - 0x2000 (0x10000 on Rev.2) - CNTRL +** +** Called by master adapter, that will initialize and add slave adapters +*/ +int diva_4bri_init_card(diva_os_xdi_adapter_t * a) +{ + int bar, i; + byte __iomem *p; + PADAPTER_LIST_ENTRY quadro_list; + diva_os_xdi_adapter_t *diva_current; + diva_os_xdi_adapter_t *adapter_list[4]; + PISDN_ADAPTER Slave; + unsigned long bar_length[sizeof(_4bri_bar_length) / + sizeof(_4bri_bar_length[0])]; + int v2 = _4bri_is_rev_2_card(a->CardOrdinal); + int tasks = _4bri_is_rev_2_bri_card(a->CardOrdinal) ? 1 : MQ_INSTANCE_COUNT; + int factor = (tasks == 1) ? 1 : 2; + + if (v2) { + if (_4bri_is_rev_2_bri_card(a->CardOrdinal)) { + memcpy(bar_length, _4bri_v2_bri_bar_length, + sizeof(bar_length)); + } else { + memcpy(bar_length, _4bri_v2_bar_length, + sizeof(bar_length)); + } + } else { + memcpy(bar_length, _4bri_bar_length, sizeof(bar_length)); + } + DBG_TRC(("SDRAM_LENGTH=%08x, tasks=%d, factor=%d", + bar_length[2], tasks, factor)) + + /* + Get Serial Number + The serial number of 4BRI is accessible in accordance with PCI spec + via command register located in configuration space, also we do not + have to map any BAR before we can access it + */ + if (!_4bri_get_serial_number(a)) { + DBG_ERR(("A: 4BRI can't get Serial Number")) + diva_4bri_cleanup_adapter(a); + return (-1); + } + + /* + Set properties + */ + a->xdi_adapter.Properties = CardProperties[a->CardOrdinal]; + DBG_LOG(("Load %s, SN:%ld, bus:%02x, func:%02x", + a->xdi_adapter.Properties.Name, + a->xdi_adapter.serialNo, + a->resources.pci.bus, a->resources.pci.func)) + + /* + First initialization step: get and check hardware resoures. + Do not map resources and do not access card at this step + */ + for (bar = 0; bar < 4; bar++) { + a->resources.pci.bar[bar] = + divasa_get_pci_bar(a->resources.pci.bus, + a->resources.pci.func, bar, + a->resources.pci.hdev); + if (!a->resources.pci.bar[bar] + || (a->resources.pci.bar[bar] == 0xFFFFFFF0)) { + DBG_ERR( + ("A: invalid bar[%d]=%08x", bar, + a->resources.pci.bar[bar])) + return (-1); + } + } + a->resources.pci.irq = + (byte) divasa_get_pci_irq(a->resources.pci.bus, + a->resources.pci.func, + a->resources.pci.hdev); + if (!a->resources.pci.irq) { + DBG_ERR(("A: invalid irq")); + return (-1); + } + + a->xdi_adapter.sdram_bar = a->resources.pci.bar[2]; + + /* + Map all MEMORY BAR's + */ + for (bar = 0; bar < 4; bar++) { + if (bar != 1) { /* ignore I/O */ + a->resources.pci.addr[bar] = + divasa_remap_pci_bar(a, bar, a->resources.pci.bar[bar], + bar_length[bar]); + if (!a->resources.pci.addr[bar]) { + DBG_ERR(("A: 4BRI: can't map bar[%d]", bar)) + diva_4bri_cleanup_adapter(a); + return (-1); + } + } + } + + /* + Register I/O port + */ + sprintf(&a->port_name[0], "DIVA 4BRI %ld", (long) a->xdi_adapter.serialNo); + + if (diva_os_register_io_port(a, 1, a->resources.pci.bar[1], + bar_length[1], &a->port_name[0], 1)) { + DBG_ERR(("A: 4BRI: can't register bar[1]")) + diva_4bri_cleanup_adapter(a); + return (-1); + } + + a->resources.pci.addr[1] = + (void *) (unsigned long) a->resources.pci.bar[1]; + + /* + Set cleanup pointer for base adapter only, so slave adapter + will be unable to get cleanup + */ + a->interface.cleanup_adapter_proc = diva_4bri_cleanup_adapter; + + /* + Create slave adapters + */ + if (tasks > 1) { + if (!(a->slave_adapters[0] = + (diva_os_xdi_adapter_t *) diva_os_malloc(0, sizeof(*a)))) + { + diva_4bri_cleanup_adapter(a); + return (-1); + } + if (!(a->slave_adapters[1] = + (diva_os_xdi_adapter_t *) diva_os_malloc(0, sizeof(*a)))) + { + diva_os_free(0, a->slave_adapters[0]); + a->slave_adapters[0] = NULL; + diva_4bri_cleanup_adapter(a); + return (-1); + } + if (!(a->slave_adapters[2] = + (diva_os_xdi_adapter_t *) diva_os_malloc(0, sizeof(*a)))) + { + diva_os_free(0, a->slave_adapters[0]); + diva_os_free(0, a->slave_adapters[1]); + a->slave_adapters[0] = NULL; + a->slave_adapters[1] = NULL; + diva_4bri_cleanup_adapter(a); + return (-1); + } + memset(a->slave_adapters[0], 0x00, sizeof(*a)); + memset(a->slave_adapters[1], 0x00, sizeof(*a)); + memset(a->slave_adapters[2], 0x00, sizeof(*a)); + } + + adapter_list[0] = a; + adapter_list[1] = a->slave_adapters[0]; + adapter_list[2] = a->slave_adapters[1]; + adapter_list[3] = a->slave_adapters[2]; + + /* + Allocate slave list + */ + quadro_list = + (PADAPTER_LIST_ENTRY) diva_os_malloc(0, sizeof(*quadro_list)); + if (!(a->slave_list = quadro_list)) { + for (i = 0; i < (tasks - 1); i++) { + diva_os_free(0, a->slave_adapters[i]); + a->slave_adapters[i] = NULL; + } + diva_4bri_cleanup_adapter(a); + return (-1); + } + memset(quadro_list, 0x00, sizeof(*quadro_list)); + + /* + Set interfaces + */ + a->xdi_adapter.QuadroList = quadro_list; + for (i = 0; i < tasks; i++) { + adapter_list[i]->xdi_adapter.ControllerNumber = i; + adapter_list[i]->xdi_adapter.tasks = tasks; + quadro_list->QuadroAdapter[i] = + &adapter_list[i]->xdi_adapter; + } + + for (i = 0; i < tasks; i++) { + diva_current = adapter_list[i]; + + diva_current->dsp_mask = 0x00000003; + + diva_current->xdi_adapter.a.io = + &diva_current->xdi_adapter; + diva_current->xdi_adapter.DIRequest = request; + diva_current->interface.cmd_proc = diva_4bri_cmd_card_proc; + diva_current->xdi_adapter.Properties = + CardProperties[a->CardOrdinal]; + diva_current->CardOrdinal = a->CardOrdinal; + + diva_current->xdi_adapter.Channels = + CardProperties[a->CardOrdinal].Channels; + diva_current->xdi_adapter.e_max = + CardProperties[a->CardOrdinal].E_info; + diva_current->xdi_adapter.e_tbl = + diva_os_malloc(0, + diva_current->xdi_adapter.e_max * + sizeof(E_INFO)); + + if (!diva_current->xdi_adapter.e_tbl) { + diva_4bri_cleanup_slave_adapters(a); + diva_4bri_cleanup_adapter(a); + for (i = 1; i < (tasks - 1); i++) { + diva_os_free(0, adapter_list[i]); + } + return (-1); + } + memset(diva_current->xdi_adapter.e_tbl, 0x00, + diva_current->xdi_adapter.e_max * sizeof(E_INFO)); + + if (diva_os_initialize_spin_lock(&diva_current->xdi_adapter.isr_spin_lock, "isr")) { + diva_4bri_cleanup_slave_adapters(a); + diva_4bri_cleanup_adapter(a); + for (i = 1; i < (tasks - 1); i++) { + diva_os_free(0, adapter_list[i]); + } + return (-1); + } + if (diva_os_initialize_spin_lock(&diva_current->xdi_adapter.data_spin_lock, "data")) { + diva_4bri_cleanup_slave_adapters(a); + diva_4bri_cleanup_adapter(a); + for (i = 1; i < (tasks - 1); i++) { + diva_os_free(0, adapter_list[i]); + } + return (-1); + } + + strcpy(diva_current->xdi_adapter.req_soft_isr. dpc_thread_name, "kdivas4brid"); + + if (diva_os_initialize_soft_isr (&diva_current->xdi_adapter.req_soft_isr, DIDpcRoutine, + &diva_current->xdi_adapter)) { + diva_4bri_cleanup_slave_adapters(a); + diva_4bri_cleanup_adapter(a); + for (i = 1; i < (tasks - 1); i++) { + diva_os_free(0, adapter_list[i]); + } + return (-1); + } + + /* + Do not initialize second DPC - only one thread will be created + */ + diva_current->xdi_adapter.isr_soft_isr.object = + diva_current->xdi_adapter.req_soft_isr.object; + } + + if (v2) { + prepare_qBri2_functions(&a->xdi_adapter); + } else { + prepare_qBri_functions(&a->xdi_adapter); + } + + for (i = 0; i < tasks; i++) { + diva_current = adapter_list[i]; + if (i) + memcpy(&diva_current->resources, &a->resources, sizeof(divas_card_resources_t)); + diva_current->resources.pci.qoffset = (a->xdi_adapter.MemorySize >> factor); + } + + /* + Set up hardware related pointers + */ + a->xdi_adapter.cfg = (void *) (unsigned long) a->resources.pci.bar[0]; /* BAR0 CONFIG */ + a->xdi_adapter.port = (void *) (unsigned long) a->resources.pci.bar[1]; /* BAR1 */ + a->xdi_adapter.ctlReg = (void *) (unsigned long) a->resources.pci.bar[3]; /* BAR3 CNTRL */ + + for (i = 0; i < tasks; i++) { + diva_current = adapter_list[i]; + diva_4bri_set_addresses(diva_current); + Slave = a->xdi_adapter.QuadroList->QuadroAdapter[i]; + Slave->MultiMaster = &a->xdi_adapter; + Slave->sdram_bar = a->xdi_adapter.sdram_bar; + if (i) { + Slave->serialNo = ((dword) (Slave->ControllerNumber << 24)) | + a->xdi_adapter.serialNo; + Slave->cardType = a->xdi_adapter.cardType; + } + } + + /* + reset contains the base address for the PLX 9054 register set + */ + p = DIVA_OS_MEM_ATTACH_RESET(&a->xdi_adapter); + WRITE_BYTE(&p[PLX9054_INTCSR], 0x00); /* disable PCI interrupts */ + DIVA_OS_MEM_DETACH_RESET(&a->xdi_adapter, p); + + /* + Set IRQ handler + */ + a->xdi_adapter.irq_info.irq_nr = a->resources.pci.irq; + sprintf(a->xdi_adapter.irq_info.irq_name, "DIVA 4BRI %ld", + (long) a->xdi_adapter.serialNo); + + if (diva_os_register_irq(a, a->xdi_adapter.irq_info.irq_nr, + a->xdi_adapter.irq_info.irq_name)) { + diva_4bri_cleanup_slave_adapters(a); + diva_4bri_cleanup_adapter(a); + for (i = 1; i < (tasks - 1); i++) { + diva_os_free(0, adapter_list[i]); + } + return (-1); + } + + a->xdi_adapter.irq_info.registered = 1; + + /* + Add three slave adapters + */ + if (tasks > 1) { + diva_add_slave_adapter(adapter_list[1]); + diva_add_slave_adapter(adapter_list[2]); + diva_add_slave_adapter(adapter_list[3]); + } + + diva_log_info("%s IRQ:%d SerNo:%d", a->xdi_adapter.Properties.Name, + a->resources.pci.irq, a->xdi_adapter.serialNo); + + return (0); +} + +/* +** Cleanup function will be called for master adapter only +** this is garanteed by design: cleanup callback is set +** by master adapter only +*/ +static int diva_4bri_cleanup_adapter(diva_os_xdi_adapter_t * a) +{ + int bar; + + /* + Stop adapter if running + */ + if (a->xdi_adapter.Initialized) { + diva_4bri_stop_adapter(a); + } + + /* + Remove IRQ handler + */ + if (a->xdi_adapter.irq_info.registered) { + diva_os_remove_irq(a, a->xdi_adapter.irq_info.irq_nr); + } + a->xdi_adapter.irq_info.registered = 0; + + /* + Free DPC's and spin locks on all adapters + */ + diva_4bri_cleanup_slave_adapters(a); + + /* + Unmap all BARS + */ + for (bar = 0; bar < 4; bar++) { + if (bar != 1) { + if (a->resources.pci.bar[bar] + && a->resources.pci.addr[bar]) { + divasa_unmap_pci_bar(a->resources.pci.addr[bar]); + a->resources.pci.bar[bar] = 0; + a->resources.pci.addr[bar] = NULL; + } + } + } + + /* + Unregister I/O + */ + if (a->resources.pci.bar[1] && a->resources.pci.addr[1]) { + diva_os_register_io_port(a, 0, a->resources.pci.bar[1], + _4bri_is_rev_2_card(a-> + CardOrdinal) ? + _4bri_v2_bar_length[1] : + _4bri_bar_length[1], + &a->port_name[0], 1); + a->resources.pci.bar[1] = 0; + a->resources.pci.addr[1] = NULL; + } + + if (a->slave_list) { + diva_os_free(0, a->slave_list); + a->slave_list = NULL; + } + + return (0); +} + +static int _4bri_get_serial_number(diva_os_xdi_adapter_t * a) +{ + dword data[64]; + dword serNo; + word addr, status, i, j; + byte Bus, Slot; + void *hdev; + + Bus = a->resources.pci.bus; + Slot = a->resources.pci.func; + hdev = a->resources.pci.hdev; + + for (i = 0; i < 64; ++i) { + addr = i * 4; + for (j = 0; j < 5; ++j) { + PCIwrite(Bus, Slot, 0x4E, &addr, sizeof(addr), + hdev); + diva_os_wait(1); + PCIread(Bus, Slot, 0x4E, &status, sizeof(status), + hdev); + if (status & 0x8000) + break; + } + if (j >= 5) { + DBG_ERR(("EEPROM[%d] read failed (0x%x)", i * 4, addr)) + return (0); + } + PCIread(Bus, Slot, 0x50, &data[i], sizeof(data[i]), hdev); + } + DBG_BLK(((char *) &data[0], sizeof(data))) + + serNo = data[32]; + if (serNo == 0 || serNo == 0xffffffff) + serNo = data[63]; + + if (!serNo) { + DBG_LOG(("W: Serial Number == 0, create one serial number")); + serNo = a->resources.pci.bar[1] & 0xffff0000; + serNo |= a->resources.pci.bus << 8; + serNo |= a->resources.pci.func; + } + + a->xdi_adapter.serialNo = serNo; + + DBG_REG(("Serial No. : %ld", a->xdi_adapter.serialNo)) + + return (serNo); +} + +/* +** Release resources of slave adapters +*/ +static int diva_4bri_cleanup_slave_adapters(diva_os_xdi_adapter_t * a) +{ + diva_os_xdi_adapter_t *adapter_list[4]; + diva_os_xdi_adapter_t *diva_current; + int i; + + adapter_list[0] = a; + adapter_list[1] = a->slave_adapters[0]; + adapter_list[2] = a->slave_adapters[1]; + adapter_list[3] = a->slave_adapters[2]; + + for (i = 0; i < a->xdi_adapter.tasks; i++) { + diva_current = adapter_list[i]; + if (diva_current) { + diva_os_destroy_spin_lock(&diva_current-> + xdi_adapter. + isr_spin_lock, "unload"); + diva_os_destroy_spin_lock(&diva_current-> + xdi_adapter. + data_spin_lock, + "unload"); + + diva_os_cancel_soft_isr(&diva_current->xdi_adapter. + req_soft_isr); + diva_os_cancel_soft_isr(&diva_current->xdi_adapter. + isr_soft_isr); + + diva_os_remove_soft_isr(&diva_current->xdi_adapter. + req_soft_isr); + diva_current->xdi_adapter.isr_soft_isr.object = NULL; + + if (diva_current->xdi_adapter.e_tbl) { + diva_os_free(0, + diva_current->xdi_adapter. + e_tbl); + } + diva_current->xdi_adapter.e_tbl = NULL; + diva_current->xdi_adapter.e_max = 0; + diva_current->xdi_adapter.e_count = 0; + } + } + + return (0); +} + +static int +diva_4bri_cmd_card_proc(struct _diva_os_xdi_adapter *a, + diva_xdi_um_cfg_cmd_t * cmd, int length) +{ + int ret = -1; + + if (cmd->adapter != a->controller) { + DBG_ERR(("A: 4bri_cmd, invalid controller=%d != %d", + cmd->adapter, a->controller)) + return (-1); + } + + switch (cmd->command) { + case DIVA_XDI_UM_CMD_GET_CARD_ORDINAL: + a->xdi_mbox.data_length = sizeof(dword); + a->xdi_mbox.data = + diva_os_malloc(0, a->xdi_mbox.data_length); + if (a->xdi_mbox.data) { + *(dword *) a->xdi_mbox.data = + (dword) a->CardOrdinal; + a->xdi_mbox.status = DIVA_XDI_MBOX_BUSY; + ret = 0; + } + break; + + case DIVA_XDI_UM_CMD_GET_SERIAL_NR: + a->xdi_mbox.data_length = sizeof(dword); + a->xdi_mbox.data = + diva_os_malloc(0, a->xdi_mbox.data_length); + if (a->xdi_mbox.data) { + *(dword *) a->xdi_mbox.data = + (dword) a->xdi_adapter.serialNo; + a->xdi_mbox.status = DIVA_XDI_MBOX_BUSY; + ret = 0; + } + break; + + case DIVA_XDI_UM_CMD_GET_PCI_HW_CONFIG: + if (!a->xdi_adapter.ControllerNumber) { + /* + Only master adapter can access hardware config + */ + a->xdi_mbox.data_length = sizeof(dword) * 9; + a->xdi_mbox.data = + diva_os_malloc(0, a->xdi_mbox.data_length); + if (a->xdi_mbox.data) { + int i; + dword *data = (dword *) a->xdi_mbox.data; + + for (i = 0; i < 8; i++) { + *data++ = a->resources.pci.bar[i]; + } + *data++ = (dword) a->resources.pci.irq; + a->xdi_mbox.status = DIVA_XDI_MBOX_BUSY; + ret = 0; + } + } + break; + + case DIVA_XDI_UM_CMD_GET_CARD_STATE: + if (!a->xdi_adapter.ControllerNumber) { + a->xdi_mbox.data_length = sizeof(dword); + a->xdi_mbox.data = + diva_os_malloc(0, a->xdi_mbox.data_length); + if (a->xdi_mbox.data) { + dword *data = (dword *) a->xdi_mbox.data; + if (!a->xdi_adapter.ram + || !a->xdi_adapter.reset + || !a->xdi_adapter.cfg) { + *data = 3; + } else if (a->xdi_adapter.trapped) { + *data = 2; + } else if (a->xdi_adapter.Initialized) { + *data = 1; + } else { + *data = 0; + } + a->xdi_mbox.status = DIVA_XDI_MBOX_BUSY; + ret = 0; + } + } + break; + + case DIVA_XDI_UM_CMD_WRITE_FPGA: + if (!a->xdi_adapter.ControllerNumber) { + ret = + diva_4bri_write_fpga_image(a, + (byte *) & cmd[1], + cmd->command_data. + write_fpga. + image_length); + } + break; + + case DIVA_XDI_UM_CMD_RESET_ADAPTER: + if (!a->xdi_adapter.ControllerNumber) { + ret = diva_4bri_reset_adapter(&a->xdi_adapter); + } + break; + + case DIVA_XDI_UM_CMD_WRITE_SDRAM_BLOCK: + if (!a->xdi_adapter.ControllerNumber) { + ret = diva_4bri_write_sdram_block(&a->xdi_adapter, + cmd-> + command_data. + write_sdram. + offset, + (byte *) & + cmd[1], + cmd-> + command_data. + write_sdram. + length, + a->xdi_adapter. + MemorySize); + } + break; + + case DIVA_XDI_UM_CMD_START_ADAPTER: + if (!a->xdi_adapter.ControllerNumber) { + ret = diva_4bri_start_adapter(&a->xdi_adapter, + cmd->command_data. + start.offset, + cmd->command_data. + start.features); + } + break; + + case DIVA_XDI_UM_CMD_SET_PROTOCOL_FEATURES: + if (!a->xdi_adapter.ControllerNumber) { + a->xdi_adapter.features = + cmd->command_data.features.features; + a->xdi_adapter.a.protocol_capabilities = + a->xdi_adapter.features; + DBG_TRC(("Set raw protocol features (%08x)", + a->xdi_adapter.features)) + ret = 0; + } + break; + + case DIVA_XDI_UM_CMD_STOP_ADAPTER: + if (!a->xdi_adapter.ControllerNumber) { + ret = diva_4bri_stop_adapter(a); + } + break; + + case DIVA_XDI_UM_CMD_READ_XLOG_ENTRY: + ret = diva_card_read_xlog(a); + break; + + case DIVA_XDI_UM_CMD_READ_SDRAM: + if (!a->xdi_adapter.ControllerNumber + && a->xdi_adapter.Address) { + if ( + (a->xdi_mbox.data_length = + cmd->command_data.read_sdram.length)) { + if ( + (a->xdi_mbox.data_length + + cmd->command_data.read_sdram.offset) < + a->xdi_adapter.MemorySize) { + a->xdi_mbox.data = + diva_os_malloc(0, + a->xdi_mbox. + data_length); + if (a->xdi_mbox.data) { + byte __iomem *p = DIVA_OS_MEM_ATTACH_ADDRESS(&a->xdi_adapter); + byte __iomem *src = p; + byte *dst = a->xdi_mbox.data; + dword len = a->xdi_mbox.data_length; + + src += cmd->command_data.read_sdram.offset; + + while (len--) { + *dst++ = READ_BYTE(src++); + } + DIVA_OS_MEM_DETACH_ADDRESS(&a->xdi_adapter, p); + a->xdi_mbox.status = DIVA_XDI_MBOX_BUSY; + ret = 0; + } + } + } + } + break; + + default: + DBG_ERR(("A: A(%d) invalid cmd=%d", a->controller, + cmd->command)) + } + + return (ret); +} + +void *xdiLoadFile(char *FileName, unsigned long *FileLength, + unsigned long lim) +{ + void *ret = diva_xdiLoadFileFile; + + if (FileLength) { + *FileLength = diva_xdiLoadFileLength; + } + diva_xdiLoadFileFile = NULL; + diva_xdiLoadFileLength = 0; + + return (ret); +} + +void diva_os_set_qBri_functions(PISDN_ADAPTER IoAdapter) +{ +} + +void diva_os_set_qBri2_functions(PISDN_ADAPTER IoAdapter) +{ +} + +static int +diva_4bri_write_fpga_image(diva_os_xdi_adapter_t * a, byte * data, + dword length) +{ + int ret; + + diva_xdiLoadFileFile = data; + diva_xdiLoadFileLength = length; + + ret = qBri_FPGA_download(&a->xdi_adapter); + + diva_xdiLoadFileFile = NULL; + diva_xdiLoadFileLength = 0; + + return (ret ? 0 : -1); +} + +static int diva_4bri_reset_adapter(PISDN_ADAPTER IoAdapter) +{ + PISDN_ADAPTER Slave; + int i; + + if (!IoAdapter->Address || !IoAdapter->reset) { + return (-1); + } + if (IoAdapter->Initialized) { + DBG_ERR(("A: A(%d) can't reset 4BRI adapter - please stop first", + IoAdapter->ANum)) + return (-1); + } + + /* + Forget all entities on all adapters + */ + for (i = 0; ((i < IoAdapter->tasks) && IoAdapter->QuadroList); i++) { + Slave = IoAdapter->QuadroList->QuadroAdapter[i]; + Slave->e_count = 0; + if (Slave->e_tbl) { + memset(Slave->e_tbl, 0x00, + Slave->e_max * sizeof(E_INFO)); + } + Slave->head = 0; + Slave->tail = 0; + Slave->assign = 0; + Slave->trapped = 0; + + memset(&Slave->a.IdTable[0], 0x00, + sizeof(Slave->a.IdTable)); + memset(&Slave->a.IdTypeTable[0], 0x00, + sizeof(Slave->a.IdTypeTable)); + memset(&Slave->a.FlowControlIdTable[0], 0x00, + sizeof(Slave->a.FlowControlIdTable)); + memset(&Slave->a.FlowControlSkipTable[0], 0x00, + sizeof(Slave->a.FlowControlSkipTable)); + memset(&Slave->a.misc_flags_table[0], 0x00, + sizeof(Slave->a.misc_flags_table)); + memset(&Slave->a.rx_stream[0], 0x00, + sizeof(Slave->a.rx_stream)); + memset(&Slave->a.tx_stream[0], 0x00, + sizeof(Slave->a.tx_stream)); + memset(&Slave->a.tx_pos[0], 0x00, sizeof(Slave->a.tx_pos)); + memset(&Slave->a.rx_pos[0], 0x00, sizeof(Slave->a.rx_pos)); + } + + return (0); +} + + +static int +diva_4bri_write_sdram_block(PISDN_ADAPTER IoAdapter, + dword address, + const byte * data, dword length, dword limit) +{ + byte __iomem *p = DIVA_OS_MEM_ATTACH_ADDRESS(IoAdapter); + byte __iomem *mem = p; + + if (((address + length) >= limit) || !mem) { + DIVA_OS_MEM_DETACH_ADDRESS(IoAdapter, p); + DBG_ERR(("A: A(%d) write 4BRI address=0x%08lx", + IoAdapter->ANum, address + length)) + return (-1); + } + mem += address; + + while (length--) { + WRITE_BYTE(mem++, *data++); + } + + DIVA_OS_MEM_DETACH_ADDRESS(IoAdapter, p); + return (0); +} + +static int +diva_4bri_start_adapter(PISDN_ADAPTER IoAdapter, + dword start_address, dword features) +{ + volatile word __iomem *signature; + int started = 0; + int i; + byte __iomem *p; + + /* + start adapter + */ + start_qBri_hardware(IoAdapter); + + p = DIVA_OS_MEM_ATTACH_RAM(IoAdapter); + /* + wait for signature in shared memory (max. 3 seconds) + */ + signature = (volatile word __iomem *) (&p[0x1E]); + + for (i = 0; i < 300; ++i) { + diva_os_wait(10); + if (READ_WORD(&signature[0]) == 0x4447) { + DBG_TRC(("Protocol startup time %d.%02d seconds", + (i / 100), (i % 100))) + started = 1; + break; + } + } + + for (i = 1; i < IoAdapter->tasks; i++) { + IoAdapter->QuadroList->QuadroAdapter[i]->features = + IoAdapter->features; + IoAdapter->QuadroList->QuadroAdapter[i]->a. + protocol_capabilities = IoAdapter->features; + } + + if (!started) { + DBG_FTL(("%s: Adapter selftest failed, signature=%04x", + IoAdapter->Properties.Name, + READ_WORD(&signature[0]))) + DIVA_OS_MEM_DETACH_RAM(IoAdapter, p); + (*(IoAdapter->trapFnc)) (IoAdapter); + IoAdapter->stop(IoAdapter); + return (-1); + } + DIVA_OS_MEM_DETACH_RAM(IoAdapter, p); + + for (i = 0; i < IoAdapter->tasks; i++) { + IoAdapter->QuadroList->QuadroAdapter[i]->Initialized = 1; + IoAdapter->QuadroList->QuadroAdapter[i]->IrqCount = 0; + } + + if (check_qBri_interrupt(IoAdapter)) { + DBG_ERR(("A: A(%d) interrupt test failed", + IoAdapter->ANum)) + for (i = 0; i < IoAdapter->tasks; i++) { + IoAdapter->QuadroList->QuadroAdapter[i]->Initialized = 0; + } + IoAdapter->stop(IoAdapter); + return (-1); + } + + IoAdapter->Properties.Features = (word) features; + diva_xdi_display_adapter_features(IoAdapter->ANum); + + for (i = 0; i < IoAdapter->tasks; i++) { + DBG_LOG(("A(%d) %s adapter successfull started", + IoAdapter->QuadroList->QuadroAdapter[i]->ANum, + (IoAdapter->tasks == 1) ? "BRI 2.0" : "4BRI")) + diva_xdi_didd_register_adapter(IoAdapter->QuadroList->QuadroAdapter[i]->ANum); + IoAdapter->QuadroList->QuadroAdapter[i]->Properties.Features = (word) features; + } + + return (0); +} + +static int check_qBri_interrupt(PISDN_ADAPTER IoAdapter) +{ +#ifdef SUPPORT_INTERRUPT_TEST_ON_4BRI + int i; + ADAPTER *a = &IoAdapter->a; + byte __iomem *p; + + IoAdapter->IrqCount = 0; + + if (IoAdapter->ControllerNumber > 0) + return (-1); + + p = DIVA_OS_MEM_ATTACH_RESET(IoAdapter); + WRITE_BYTE(&p[PLX9054_INTCSR], PLX9054_INT_ENABLE); + DIVA_OS_MEM_DETACH_RESET(IoAdapter, p); + /* + interrupt test + */ + a->ReadyInt = 1; + a->ram_out(a, &PR_RAM->ReadyInt, 1); + + for (i = 100; !IoAdapter->IrqCount && (i-- > 0); diva_os_wait(10)); + + return ((IoAdapter->IrqCount > 0) ? 0 : -1); +#else + dword volatile __iomem *qBriIrq; + byte __iomem *p; + /* + Reset on-board interrupt register + */ + IoAdapter->IrqCount = 0; + p = DIVA_OS_MEM_ATTACH_CTLREG(IoAdapter); + qBriIrq = (dword volatile __iomem *) (&p[_4bri_is_rev_2_card + (IoAdapter-> + cardType) ? (MQ2_BREG_IRQ_TEST) + : (MQ_BREG_IRQ_TEST)]); + + WRITE_DWORD(qBriIrq, MQ_IRQ_REQ_OFF); + DIVA_OS_MEM_DETACH_CTLREG(IoAdapter, p); + + p = DIVA_OS_MEM_ATTACH_RESET(IoAdapter); + WRITE_BYTE(&p[PLX9054_INTCSR], PLX9054_INT_ENABLE); + DIVA_OS_MEM_DETACH_RESET(IoAdapter, p); + + diva_os_wait(100); + + return (0); +#endif /* SUPPORT_INTERRUPT_TEST_ON_4BRI */ +} + +static void diva_4bri_clear_interrupts(diva_os_xdi_adapter_t * a) +{ + PISDN_ADAPTER IoAdapter = &a->xdi_adapter; + + /* + clear any pending interrupt + */ + IoAdapter->disIrq(IoAdapter); + + IoAdapter->tst_irq(&IoAdapter->a); + IoAdapter->clr_irq(&IoAdapter->a); + IoAdapter->tst_irq(&IoAdapter->a); + + /* + kill pending dpcs + */ + diva_os_cancel_soft_isr(&IoAdapter->req_soft_isr); + diva_os_cancel_soft_isr(&IoAdapter->isr_soft_isr); +} + +static int diva_4bri_stop_adapter(diva_os_xdi_adapter_t * a) +{ + PISDN_ADAPTER IoAdapter = &a->xdi_adapter; + int i; + + if (!IoAdapter->ram) { + return (-1); + } + + if (!IoAdapter->Initialized) { + DBG_ERR(("A: A(%d) can't stop PRI adapter - not running", + IoAdapter->ANum)) + return (-1); /* nothing to stop */ + } + + for (i = 0; i < IoAdapter->tasks; i++) { + IoAdapter->QuadroList->QuadroAdapter[i]->Initialized = 0; + } + + /* + Disconnect Adapters from DIDD + */ + for (i = 0; i < IoAdapter->tasks; i++) { + diva_xdi_didd_remove_adapter(IoAdapter->QuadroList->QuadroAdapter[i]->ANum); + } + + i = 100; + + /* + Stop interrupts + */ + a->clear_interrupts_proc = diva_4bri_clear_interrupts; + IoAdapter->a.ReadyInt = 1; + IoAdapter->a.ram_inc(&IoAdapter->a, &PR_RAM->ReadyInt); + do { + diva_os_sleep(10); + } while (i-- && a->clear_interrupts_proc); + + if (a->clear_interrupts_proc) { + diva_4bri_clear_interrupts(a); + a->clear_interrupts_proc = NULL; + DBG_ERR(("A: A(%d) no final interrupt from 4BRI adapter", + IoAdapter->ANum)) + } + IoAdapter->a.ReadyInt = 0; + + /* + Stop and reset adapter + */ + IoAdapter->stop(IoAdapter); + + return (0); +} diff --git a/drivers/isdn/hardware/eicon/os_4bri.h b/drivers/isdn/hardware/eicon/os_4bri.h new file mode 100644 index 000000000000..665f0af27ce7 --- /dev/null +++ b/drivers/isdn/hardware/eicon/os_4bri.h @@ -0,0 +1,8 @@ +/* $Id: os_4bri.h,v 1.1.2.2 2001/02/08 12:25:44 armin Exp $ */ + +#ifndef __DIVA_OS_4_BRI_H__ +#define __DIVA_OS_4_BRI_H__ + +int diva_4bri_init_card(diva_os_xdi_adapter_t * a); + +#endif diff --git a/drivers/isdn/hardware/eicon/os_bri.c b/drivers/isdn/hardware/eicon/os_bri.c new file mode 100644 index 000000000000..4cc44a5dd1db --- /dev/null +++ b/drivers/isdn/hardware/eicon/os_bri.c @@ -0,0 +1,813 @@ +/* $Id: os_bri.c,v 1.21 2004/03/21 17:26:01 armin Exp $ */ + +#include "platform.h" +#include "debuglib.h" +#include "cardtype.h" +#include "pc.h" +#include "pr_pc.h" +#include "di_defs.h" +#include "dsp_defs.h" +#include "di.h" +#include "io.h" + +#include "xdi_msg.h" +#include "xdi_adapter.h" +#include "os_bri.h" +#include "diva_pci.h" +#include "mi_pc.h" +#include "pc_maint.h" + +/* +** IMPORTS +*/ +extern void prepare_maestra_functions(PISDN_ADAPTER IoAdapter); +extern void diva_xdi_display_adapter_features(int card); +extern int diva_card_read_xlog(diva_os_xdi_adapter_t * a); + +/* +** LOCALS +*/ +static int bri_bar_length[3] = { + 0x80, + 0x80, + 0x20 +}; +static int diva_bri_cleanup_adapter(diva_os_xdi_adapter_t * a); +static dword diva_bri_get_serial_number(diva_os_xdi_adapter_t * a); +static int diva_bri_cmd_card_proc(struct _diva_os_xdi_adapter *a, + diva_xdi_um_cfg_cmd_t * cmd, int length); +static int diva_bri_reregister_io(diva_os_xdi_adapter_t * a); +static int diva_bri_reset_adapter(PISDN_ADAPTER IoAdapter); +static int diva_bri_write_sdram_block(PISDN_ADAPTER IoAdapter, + dword address, + const byte * data, dword length); +static int diva_bri_start_adapter(PISDN_ADAPTER IoAdapter, + dword start_address, dword features); +static int diva_bri_stop_adapter(diva_os_xdi_adapter_t * a); + +static void diva_bri_set_addresses(diva_os_xdi_adapter_t * a) +{ + a->resources.pci.mem_type_id[MEM_TYPE_RAM] = 0; + a->resources.pci.mem_type_id[MEM_TYPE_CFG] = 1; + a->resources.pci.mem_type_id[MEM_TYPE_ADDRESS] = 2; + a->resources.pci.mem_type_id[MEM_TYPE_RESET] = 1; + a->resources.pci.mem_type_id[MEM_TYPE_PORT] = 2; + a->resources.pci.mem_type_id[MEM_TYPE_CTLREG] = 2; + + a->xdi_adapter.ram = a->resources.pci.addr[0]; + a->xdi_adapter.cfg = a->resources.pci.addr[1]; + a->xdi_adapter.Address = a->resources.pci.addr[2]; + + a->xdi_adapter.reset = a->xdi_adapter.cfg; + a->xdi_adapter.port = a->xdi_adapter.Address; + + a->xdi_adapter.ctlReg = a->xdi_adapter.port + M_PCI_RESET; + + a->xdi_adapter.reset += 0x4C; /* PLX 9050 !! */ +} + +/* +** BAR0 - MEM Addr - 0x80 - NOT USED +** BAR1 - I/O Addr - 0x80 +** BAR2 - I/O Addr - 0x20 +*/ +int diva_bri_init_card(diva_os_xdi_adapter_t * a) +{ + int bar; + dword bar2 = 0, bar2_length = 0xffffffff; + word cmd = 0, cmd_org; + byte Bus, Slot; + void *hdev; + byte __iomem *p; + + /* + Set properties + */ + a->xdi_adapter.Properties = CardProperties[a->CardOrdinal]; + DBG_LOG(("Load %s", a->xdi_adapter.Properties.Name)) + + /* + Get resources + */ + for (bar = 0; bar < 3; bar++) { + a->resources.pci.bar[bar] = + divasa_get_pci_bar(a->resources.pci.bus, + a->resources.pci.func, bar, + a->resources.pci.hdev); + if (!a->resources.pci.bar[bar]) { + DBG_ERR(("A: can't get BAR[%d]", bar)) + return (-1); + } + } + + a->resources.pci.irq = + (byte) divasa_get_pci_irq(a->resources.pci.bus, + a->resources.pci.func, + a->resources.pci.hdev); + if (!a->resources.pci.irq) { + DBG_ERR(("A: invalid irq")); + return (-1); + } + + /* + Get length of I/O bar 2 - it is different by older + EEPROM version + */ + Bus = a->resources.pci.bus; + Slot = a->resources.pci.func; + hdev = a->resources.pci.hdev; + + /* + Get plain original values of the BAR2 CDM registers + */ + PCIread(Bus, Slot, 0x18, &bar2, sizeof(bar2), hdev); + PCIread(Bus, Slot, 0x04, &cmd_org, sizeof(cmd_org), hdev); + /* + Disable device and get BAR2 length + */ + PCIwrite(Bus, Slot, 0x04, &cmd, sizeof(cmd), hdev); + PCIwrite(Bus, Slot, 0x18, &bar2_length, sizeof(bar2_length), hdev); + PCIread(Bus, Slot, 0x18, &bar2_length, sizeof(bar2_length), hdev); + /* + Restore BAR2 and CMD registers + */ + PCIwrite(Bus, Slot, 0x18, &bar2, sizeof(bar2), hdev); + PCIwrite(Bus, Slot, 0x04, &cmd_org, sizeof(cmd_org), hdev); + + /* + Calculate BAR2 length + */ + bar2_length = (~(bar2_length & ~7)) + 1; + DBG_LOG(("BAR[2] length=%lx", bar2_length)) + + /* + Map and register resources + */ + if (!(a->resources.pci.addr[0] = + divasa_remap_pci_bar(a, 0, a->resources.pci.bar[0], + bri_bar_length[0]))) { + DBG_ERR(("A: BRI, can't map BAR[0]")) + diva_bri_cleanup_adapter(a); + return (-1); + } + + sprintf(&a->port_name[0], "BRI %02x:%02x", + a->resources.pci.bus, a->resources.pci.func); + + if (diva_os_register_io_port(a, 1, a->resources.pci.bar[1], + bri_bar_length[1], &a->port_name[0], 1)) { + DBG_ERR(("A: BRI, can't register BAR[1]")) + diva_bri_cleanup_adapter(a); + return (-1); + } + a->resources.pci.addr[1] = (void *) (unsigned long) a->resources.pci.bar[1]; + a->resources.pci.length[1] = bri_bar_length[1]; + + if (diva_os_register_io_port(a, 1, a->resources.pci.bar[2], + bar2_length, &a->port_name[0], 2)) { + DBG_ERR(("A: BRI, can't register BAR[2]")) + diva_bri_cleanup_adapter(a); + return (-1); + } + a->resources.pci.addr[2] = (void *) (unsigned long) a->resources.pci.bar[2]; + a->resources.pci.length[2] = bar2_length; + + /* + Set all memory areas + */ + diva_bri_set_addresses(a); + + /* + Get Serial Number + */ + a->xdi_adapter.serialNo = diva_bri_get_serial_number(a); + + /* + Register I/O ports with correct name now + */ + if (diva_bri_reregister_io(a)) { + diva_bri_cleanup_adapter(a); + return (-1); + } + + /* + Initialize OS dependent objects + */ + if (diva_os_initialize_spin_lock + (&a->xdi_adapter.isr_spin_lock, "isr")) { + diva_bri_cleanup_adapter(a); + return (-1); + } + if (diva_os_initialize_spin_lock + (&a->xdi_adapter.data_spin_lock, "data")) { + diva_bri_cleanup_adapter(a); + return (-1); + } + + strcpy(a->xdi_adapter.req_soft_isr.dpc_thread_name, "kdivasbrid"); + + if (diva_os_initialize_soft_isr(&a->xdi_adapter.req_soft_isr, + DIDpcRoutine, &a->xdi_adapter)) { + diva_bri_cleanup_adapter(a); + return (-1); + } + /* + Do not initialize second DPC - only one thread will be created + */ + a->xdi_adapter.isr_soft_isr.object = a->xdi_adapter.req_soft_isr.object; + + /* + Create entity table + */ + a->xdi_adapter.Channels = CardProperties[a->CardOrdinal].Channels; + a->xdi_adapter.e_max = CardProperties[a->CardOrdinal].E_info; + a->xdi_adapter.e_tbl = diva_os_malloc(0, a->xdi_adapter.e_max * sizeof(E_INFO)); + if (!a->xdi_adapter.e_tbl) { + diva_bri_cleanup_adapter(a); + return (-1); + } + memset(a->xdi_adapter.e_tbl, 0x00, a->xdi_adapter.e_max * sizeof(E_INFO)); + + /* + Set up interface + */ + a->xdi_adapter.a.io = &a->xdi_adapter; + a->xdi_adapter.DIRequest = request; + a->interface.cleanup_adapter_proc = diva_bri_cleanup_adapter; + a->interface.cmd_proc = diva_bri_cmd_card_proc; + + p = DIVA_OS_MEM_ATTACH_RESET(&a->xdi_adapter); + outpp(p, 0x41); + DIVA_OS_MEM_DETACH_RESET(&a->xdi_adapter, p); + + prepare_maestra_functions(&a->xdi_adapter); + + a->dsp_mask = 0x00000003; + + /* + Set IRQ handler + */ + a->xdi_adapter.irq_info.irq_nr = a->resources.pci.irq; + sprintf(a->xdi_adapter.irq_info.irq_name, "DIVA BRI %ld", + (long) a->xdi_adapter.serialNo); + if (diva_os_register_irq(a, a->xdi_adapter.irq_info.irq_nr, + a->xdi_adapter.irq_info.irq_name)) { + diva_bri_cleanup_adapter(a); + return (-1); + } + a->xdi_adapter.irq_info.registered = 1; + + diva_log_info("%s IRQ:%d SerNo:%d", a->xdi_adapter.Properties.Name, + a->resources.pci.irq, a->xdi_adapter.serialNo); + + return (0); +} + + +static int diva_bri_cleanup_adapter(diva_os_xdi_adapter_t * a) +{ + int i; + + if (a->xdi_adapter.Initialized) { + diva_bri_stop_adapter(a); + } + + /* + Remove ISR Handler + */ + if (a->xdi_adapter.irq_info.registered) { + diva_os_remove_irq(a, a->xdi_adapter.irq_info.irq_nr); + } + a->xdi_adapter.irq_info.registered = 0; + + if (a->resources.pci.addr[0] && a->resources.pci.bar[0]) { + divasa_unmap_pci_bar(a->resources.pci.addr[0]); + a->resources.pci.addr[0] = NULL; + a->resources.pci.bar[0] = 0; + } + + for (i = 1; i < 3; i++) { + if (a->resources.pci.addr[i] && a->resources.pci.bar[i]) { + diva_os_register_io_port(a, 0, + a->resources.pci.bar[i], + a->resources.pci. + length[i], + &a->port_name[0], i); + a->resources.pci.addr[i] = NULL; + a->resources.pci.bar[i] = 0; + } + } + + /* + Free OS objects + */ + diva_os_cancel_soft_isr(&a->xdi_adapter.req_soft_isr); + diva_os_cancel_soft_isr(&a->xdi_adapter.isr_soft_isr); + + diva_os_remove_soft_isr(&a->xdi_adapter.req_soft_isr); + a->xdi_adapter.isr_soft_isr.object = NULL; + + diva_os_destroy_spin_lock(&a->xdi_adapter.isr_spin_lock, "rm"); + diva_os_destroy_spin_lock(&a->xdi_adapter.data_spin_lock, "rm"); + + /* + Free memory + */ + if (a->xdi_adapter.e_tbl) { + diva_os_free(0, a->xdi_adapter.e_tbl); + a->xdi_adapter.e_tbl = NULL; + } + + return (0); +} + +void diva_os_prepare_maestra_functions(PISDN_ADAPTER IoAdapter) +{ +} + +/* +** Get serial number +*/ +static dword diva_bri_get_serial_number(diva_os_xdi_adapter_t * a) +{ + dword serNo = 0; + byte __iomem *confIO; + word serHi, serLo; + word __iomem *confMem; + + confIO = DIVA_OS_MEM_ATTACH_CFG(&a->xdi_adapter); + serHi = (word) (inppw(&confIO[0x22]) & 0x0FFF); + serLo = (word) (inppw(&confIO[0x26]) & 0x0FFF); + serNo = ((dword) serHi << 16) | (dword) serLo; + DIVA_OS_MEM_DETACH_CFG(&a->xdi_adapter, confIO); + + if ((serNo == 0) || (serNo == 0xFFFFFFFF)) { + DBG_FTL(("W: BRI use BAR[0] to get card serial number")) + + confMem = (word __iomem *)DIVA_OS_MEM_ATTACH_RAM(&a->xdi_adapter); + serHi = (word) (READ_WORD(&confMem[0x11]) & 0x0FFF); + serLo = (word) (READ_WORD(&confMem[0x13]) & 0x0FFF); + serNo = (((dword) serHi) << 16) | ((dword) serLo); + DIVA_OS_MEM_DETACH_RAM(&a->xdi_adapter, confMem); + } + + DBG_LOG(("Serial Number=%ld", serNo)) + + return (serNo); +} + +/* +** Unregister I/O and register it with new name, +** based on Serial Number +*/ +static int diva_bri_reregister_io(diva_os_xdi_adapter_t * a) +{ + int i; + + for (i = 1; i < 3; i++) { + diva_os_register_io_port(a, 0, a->resources.pci.bar[i], + a->resources.pci.length[i], + &a->port_name[0], i); + a->resources.pci.addr[i] = NULL; + } + + sprintf(a->port_name, "DIVA BRI %ld", + (long) a->xdi_adapter.serialNo); + + for (i = 1; i < 3; i++) { + if (diva_os_register_io_port(a, 1, a->resources.pci.bar[i], + a->resources.pci.length[i], + &a->port_name[0], i)) { + DBG_ERR(("A: failed to reregister BAR[%d]", i)) + return (-1); + } + a->resources.pci.addr[i] = + (void *) (unsigned long) a->resources.pci.bar[i]; + } + + return (0); +} + +/* +** Process command from user mode +*/ +static int +diva_bri_cmd_card_proc(struct _diva_os_xdi_adapter *a, + diva_xdi_um_cfg_cmd_t * cmd, int length) +{ + int ret = -1; + + if (cmd->adapter != a->controller) { + DBG_ERR(("A: pri_cmd, invalid controller=%d != %d", + cmd->adapter, a->controller)) + return (-1); + } + + switch (cmd->command) { + case DIVA_XDI_UM_CMD_GET_CARD_ORDINAL: + a->xdi_mbox.data_length = sizeof(dword); + a->xdi_mbox.data = + diva_os_malloc(0, a->xdi_mbox.data_length); + if (a->xdi_mbox.data) { + *(dword *) a->xdi_mbox.data = + (dword) a->CardOrdinal; + a->xdi_mbox.status = DIVA_XDI_MBOX_BUSY; + ret = 0; + } + break; + + case DIVA_XDI_UM_CMD_GET_SERIAL_NR: + a->xdi_mbox.data_length = sizeof(dword); + a->xdi_mbox.data = + diva_os_malloc(0, a->xdi_mbox.data_length); + if (a->xdi_mbox.data) { + *(dword *) a->xdi_mbox.data = + (dword) a->xdi_adapter.serialNo; + a->xdi_mbox.status = DIVA_XDI_MBOX_BUSY; + ret = 0; + } + break; + + case DIVA_XDI_UM_CMD_GET_PCI_HW_CONFIG: + a->xdi_mbox.data_length = sizeof(dword) * 9; + a->xdi_mbox.data = + diva_os_malloc(0, a->xdi_mbox.data_length); + if (a->xdi_mbox.data) { + int i; + dword *data = (dword *) a->xdi_mbox.data; + + for (i = 0; i < 8; i++) { + *data++ = a->resources.pci.bar[i]; + } + *data++ = (dword) a->resources.pci.irq; + a->xdi_mbox.status = DIVA_XDI_MBOX_BUSY; + ret = 0; + } + break; + + case DIVA_XDI_UM_CMD_GET_CARD_STATE: + a->xdi_mbox.data_length = sizeof(dword); + a->xdi_mbox.data = + diva_os_malloc(0, a->xdi_mbox.data_length); + if (a->xdi_mbox.data) { + dword *data = (dword *) a->xdi_mbox.data; + if (!a->xdi_adapter.port) { + *data = 3; + } else if (a->xdi_adapter.trapped) { + *data = 2; + } else if (a->xdi_adapter.Initialized) { + *data = 1; + } else { + *data = 0; + } + a->xdi_mbox.status = DIVA_XDI_MBOX_BUSY; + ret = 0; + } + break; + + case DIVA_XDI_UM_CMD_RESET_ADAPTER: + ret = diva_bri_reset_adapter(&a->xdi_adapter); + break; + + case DIVA_XDI_UM_CMD_WRITE_SDRAM_BLOCK: + ret = diva_bri_write_sdram_block(&a->xdi_adapter, + cmd->command_data. + write_sdram.offset, + (byte *) & cmd[1], + cmd->command_data. + write_sdram.length); + break; + + case DIVA_XDI_UM_CMD_START_ADAPTER: + ret = diva_bri_start_adapter(&a->xdi_adapter, + cmd->command_data.start. + offset, + cmd->command_data.start. + features); + break; + + case DIVA_XDI_UM_CMD_SET_PROTOCOL_FEATURES: + a->xdi_adapter.features = + cmd->command_data.features.features; + a->xdi_adapter.a.protocol_capabilities = + a->xdi_adapter.features; + DBG_TRC( + ("Set raw protocol features (%08x)", + a->xdi_adapter.features)) ret = 0; + break; + + case DIVA_XDI_UM_CMD_STOP_ADAPTER: + ret = diva_bri_stop_adapter(a); + break; + + case DIVA_XDI_UM_CMD_READ_XLOG_ENTRY: + ret = diva_card_read_xlog(a); + break; + + default: + DBG_ERR( + ("A: A(%d) invalid cmd=%d", a->controller, + cmd->command))} + + return (ret); +} + +static int diva_bri_reset_adapter(PISDN_ADAPTER IoAdapter) +{ + byte __iomem *addrHi, *addrLo, *ioaddr; + dword i; + byte __iomem *Port; + + if (!IoAdapter->port) { + return (-1); + } + if (IoAdapter->Initialized) { + DBG_ERR(("A: A(%d) can't reset BRI adapter - please stop first", + IoAdapter->ANum)) return (-1); + } + (*(IoAdapter->rstFnc)) (IoAdapter); + diva_os_wait(100); + Port = DIVA_OS_MEM_ATTACH_PORT(IoAdapter); + addrHi = Port + + ((IoAdapter->Properties.Bus == BUS_PCI) ? M_PCI_ADDRH : ADDRH); + addrLo = Port + ADDR; + ioaddr = Port + DATA; + /* + recover + */ + outpp(addrHi, (byte) 0); + outppw(addrLo, (word) 0); + outppw(ioaddr, (word) 0); + /* + clear shared memory + */ + outpp(addrHi, + (byte) ( + (IoAdapter->MemoryBase + IoAdapter->MemorySize - + BRI_SHARED_RAM_SIZE) >> 16)); + outppw(addrLo, 0); + for (i = 0; i < 0x8000; outppw(ioaddr, 0), ++i); + diva_os_wait(100); + + /* + clear signature + */ + outpp(addrHi, + (byte) ( + (IoAdapter->MemoryBase + IoAdapter->MemorySize - + BRI_SHARED_RAM_SIZE) >> 16)); + outppw(addrLo, 0x1e); + outpp(ioaddr, 0); + outpp(ioaddr, 0); + + outpp(addrHi, (byte) 0); + outppw(addrLo, (word) 0); + outppw(ioaddr, (word) 0); + + DIVA_OS_MEM_DETACH_PORT(IoAdapter, Port); + + /* + Forget all outstanding entities + */ + IoAdapter->e_count = 0; + if (IoAdapter->e_tbl) { + memset(IoAdapter->e_tbl, 0x00, + IoAdapter->e_max * sizeof(E_INFO)); + } + IoAdapter->head = 0; + IoAdapter->tail = 0; + IoAdapter->assign = 0; + IoAdapter->trapped = 0; + + memset(&IoAdapter->a.IdTable[0], 0x00, + sizeof(IoAdapter->a.IdTable)); + memset(&IoAdapter->a.IdTypeTable[0], 0x00, + sizeof(IoAdapter->a.IdTypeTable)); + memset(&IoAdapter->a.FlowControlIdTable[0], 0x00, + sizeof(IoAdapter->a.FlowControlIdTable)); + memset(&IoAdapter->a.FlowControlSkipTable[0], 0x00, + sizeof(IoAdapter->a.FlowControlSkipTable)); + memset(&IoAdapter->a.misc_flags_table[0], 0x00, + sizeof(IoAdapter->a.misc_flags_table)); + memset(&IoAdapter->a.rx_stream[0], 0x00, + sizeof(IoAdapter->a.rx_stream)); + memset(&IoAdapter->a.tx_stream[0], 0x00, + sizeof(IoAdapter->a.tx_stream)); + memset(&IoAdapter->a.tx_pos[0], 0x00, sizeof(IoAdapter->a.tx_pos)); + memset(&IoAdapter->a.rx_pos[0], 0x00, sizeof(IoAdapter->a.rx_pos)); + + return (0); +} + +static int +diva_bri_write_sdram_block(PISDN_ADAPTER IoAdapter, + dword address, const byte * data, dword length) +{ + byte __iomem *addrHi, *addrLo, *ioaddr; + byte __iomem *Port; + + if (!IoAdapter->port) { + return (-1); + } + + Port = DIVA_OS_MEM_ATTACH_PORT(IoAdapter); + addrHi = Port + + ((IoAdapter->Properties.Bus == BUS_PCI) ? M_PCI_ADDRH : ADDRH); + addrLo = Port + ADDR; + ioaddr = Port + DATA; + + while (length--) { + outpp(addrHi, (word) (address >> 16)); + outppw(addrLo, (word) (address & 0x0000ffff)); + outpp(ioaddr, *data++); + address++; + } + + DIVA_OS_MEM_DETACH_PORT(IoAdapter, Port); + return (0); +} + +static int +diva_bri_start_adapter(PISDN_ADAPTER IoAdapter, + dword start_address, dword features) +{ + byte __iomem *Port; + dword i, test; + byte __iomem *addrHi, *addrLo, *ioaddr; + int started = 0; + ADAPTER *a = &IoAdapter->a; + + if (IoAdapter->Initialized) { + DBG_ERR( + ("A: A(%d) bri_start_adapter, adapter already running", + IoAdapter->ANum)) return (-1); + } + if (!IoAdapter->port) { + DBG_ERR(("A: A(%d) bri_start_adapter, adapter not mapped", + IoAdapter->ANum)) return (-1); + } + + sprintf(IoAdapter->Name, "A(%d)", (int) IoAdapter->ANum); + DBG_LOG(("A(%d) start BRI", IoAdapter->ANum)) + + Port = DIVA_OS_MEM_ATTACH_PORT(IoAdapter); + addrHi = Port + + ((IoAdapter->Properties.Bus == BUS_PCI) ? M_PCI_ADDRH : ADDRH); + addrLo = Port + ADDR; + ioaddr = Port + DATA; + + outpp(addrHi, + (byte) ( + (IoAdapter->MemoryBase + IoAdapter->MemorySize - + BRI_SHARED_RAM_SIZE) >> 16)); + outppw(addrLo, 0x1e); + outppw(ioaddr, 0x00); + DIVA_OS_MEM_DETACH_PORT(IoAdapter, Port); + + /* + start the protocol code + */ + Port = DIVA_OS_MEM_ATTACH_CTLREG(IoAdapter); + outpp(Port, 0x08); + DIVA_OS_MEM_DETACH_CTLREG(IoAdapter, Port); + + Port = DIVA_OS_MEM_ATTACH_PORT(IoAdapter); + addrHi = Port + + ((IoAdapter->Properties.Bus == BUS_PCI) ? M_PCI_ADDRH : ADDRH); + addrLo = Port + ADDR; + ioaddr = Port + DATA; + /* + wait for signature (max. 3 seconds) + */ + for (i = 0; i < 300; ++i) { + diva_os_wait(10); + outpp(addrHi, + (byte) ( + (IoAdapter->MemoryBase + + IoAdapter->MemorySize - + BRI_SHARED_RAM_SIZE) >> 16)); + outppw(addrLo, 0x1e); + test = (dword) inppw(ioaddr); + if (test == 0x4447) { + DBG_LOG( + ("Protocol startup time %d.%02d seconds", + (i / 100), (i % 100))) + started = 1; + break; + } + } + DIVA_OS_MEM_DETACH_PORT(IoAdapter, Port); + + if (!started) { + DBG_FTL(("A: A(%d) %s: Adapter selftest failed 0x%04X", + IoAdapter->ANum, IoAdapter->Properties.Name, + test)) + (*(IoAdapter->trapFnc)) (IoAdapter); + return (-1); + } + + IoAdapter->Initialized = 1; + + /* + Check Interrupt + */ + IoAdapter->IrqCount = 0; + a->ReadyInt = 1; + + if (IoAdapter->reset) { + Port = DIVA_OS_MEM_ATTACH_RESET(IoAdapter); + outpp(Port, 0x41); + DIVA_OS_MEM_DETACH_RESET(IoAdapter, Port); + } + + a->ram_out(a, &PR_RAM->ReadyInt, 1); + for (i = 0; ((!IoAdapter->IrqCount) && (i < 100)); i++) { + diva_os_wait(10); + } + if (!IoAdapter->IrqCount) { + DBG_ERR( + ("A: A(%d) interrupt test failed", + IoAdapter->ANum)) + IoAdapter->Initialized = 0; + IoAdapter->stop(IoAdapter); + return (-1); + } + + IoAdapter->Properties.Features = (word) features; + diva_xdi_display_adapter_features(IoAdapter->ANum); + DBG_LOG(("A(%d) BRI adapter successfull started", IoAdapter->ANum)) + /* + Register with DIDD + */ + diva_xdi_didd_register_adapter(IoAdapter->ANum); + + return (0); +} + +static void diva_bri_clear_interrupts(diva_os_xdi_adapter_t * a) +{ + PISDN_ADAPTER IoAdapter = &a->xdi_adapter; + + /* + clear any pending interrupt + */ + IoAdapter->disIrq(IoAdapter); + + IoAdapter->tst_irq(&IoAdapter->a); + IoAdapter->clr_irq(&IoAdapter->a); + IoAdapter->tst_irq(&IoAdapter->a); + + /* + kill pending dpcs + */ + diva_os_cancel_soft_isr(&IoAdapter->req_soft_isr); + diva_os_cancel_soft_isr(&IoAdapter->isr_soft_isr); +} + +/* +** Stop card +*/ +static int diva_bri_stop_adapter(diva_os_xdi_adapter_t * a) +{ + PISDN_ADAPTER IoAdapter = &a->xdi_adapter; + int i = 100; + + if (!IoAdapter->port) { + return (-1); + } + if (!IoAdapter->Initialized) { + DBG_ERR(("A: A(%d) can't stop BRI adapter - not running", + IoAdapter->ANum)) + return (-1); /* nothing to stop */ + } + IoAdapter->Initialized = 0; + + /* + Disconnect Adapter from DIDD + */ + diva_xdi_didd_remove_adapter(IoAdapter->ANum); + + /* + Stop interrupts + */ + a->clear_interrupts_proc = diva_bri_clear_interrupts; + IoAdapter->a.ReadyInt = 1; + IoAdapter->a.ram_inc(&IoAdapter->a, &PR_RAM->ReadyInt); + do { + diva_os_sleep(10); + } while (i-- && a->clear_interrupts_proc); + if (a->clear_interrupts_proc) { + diva_bri_clear_interrupts(a); + a->clear_interrupts_proc = NULL; + DBG_ERR(("A: A(%d) no final interrupt from BRI adapter", + IoAdapter->ANum)) + } + IoAdapter->a.ReadyInt = 0; + + /* + Stop and reset adapter + */ + IoAdapter->stop(IoAdapter); + + return (0); +} diff --git a/drivers/isdn/hardware/eicon/os_bri.h b/drivers/isdn/hardware/eicon/os_bri.h new file mode 100644 index 000000000000..a54f0ce58e13 --- /dev/null +++ b/drivers/isdn/hardware/eicon/os_bri.h @@ -0,0 +1,8 @@ +/* $Id: os_bri.h,v 1.1.2.2 2001/02/08 12:25:44 armin Exp $ */ + +#ifndef __DIVA_OS_BRI_REV_1_H__ +#define __DIVA_OS_BRI_REV_1_H__ + +int diva_bri_init_card(diva_os_xdi_adapter_t * a); + +#endif diff --git a/drivers/isdn/hardware/eicon/os_capi.h b/drivers/isdn/hardware/eicon/os_capi.h new file mode 100644 index 000000000000..726f915a09e5 --- /dev/null +++ b/drivers/isdn/hardware/eicon/os_capi.h @@ -0,0 +1,21 @@ +/* $Id: os_capi.h,v 1.7 2003/04/12 21:40:49 schindler Exp $ + * + * ISDN interface module for Eicon active cards DIVA. + * CAPI Interface OS include files + * + * Copyright 2000-2003 by Armin Schindler (mac@melware.de) + * Copyright 2000-2003 Cytronics & Melware (info@melware.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + */ + +#ifndef __OS_CAPI_H__ +#define __OS_CAPI_H__ + +#include <linux/capi.h> +#include <linux/kernelcapi.h> +#include <linux/isdn/capiutil.h> +#include <linux/isdn/capilli.h> + +#endif /* __OS_CAPI_H__ */ diff --git a/drivers/isdn/hardware/eicon/os_pri.c b/drivers/isdn/hardware/eicon/os_pri.c new file mode 100644 index 000000000000..8ac207f75e54 --- /dev/null +++ b/drivers/isdn/hardware/eicon/os_pri.c @@ -0,0 +1,1051 @@ +/* $Id: os_pri.c,v 1.32 2004/03/21 17:26:01 armin Exp $ */ + +#include "platform.h" +#include "debuglib.h" +#include "cardtype.h" +#include "pc.h" +#include "pr_pc.h" +#include "di_defs.h" +#include "dsp_defs.h" +#include "di.h" +#include "io.h" + +#include "xdi_msg.h" +#include "xdi_adapter.h" +#include "os_pri.h" +#include "diva_pci.h" +#include "mi_pc.h" +#include "pc_maint.h" +#include "dsp_tst.h" +#include "diva_dma.h" + +/* -------------------------------------------------------------------------- + OS Dependent part of XDI driver for DIVA PRI Adapter + + DSP detection/validation by Anthony Booth (Eicon Networks, www.eicon.com) +-------------------------------------------------------------------------- */ + +#define DIVA_PRI_NO_PCI_BIOS_WORKAROUND 1 + +extern int diva_card_read_xlog(diva_os_xdi_adapter_t * a); + +/* +** IMPORTS +*/ +extern void prepare_pri_functions(PISDN_ADAPTER IoAdapter); +extern void prepare_pri2_functions(PISDN_ADAPTER IoAdapter); +extern void diva_xdi_display_adapter_features(int card); + +static int diva_pri_cleanup_adapter(diva_os_xdi_adapter_t * a); +static int diva_pri_cmd_card_proc(struct _diva_os_xdi_adapter *a, + diva_xdi_um_cfg_cmd_t * cmd, int length); +static int pri_get_serial_number(diva_os_xdi_adapter_t * a); +static int diva_pri_stop_adapter(diva_os_xdi_adapter_t * a); +static dword diva_pri_detect_dsps(diva_os_xdi_adapter_t * a); + +/* +** Check card revision +*/ +static int pri_is_rev_2_card(int card_ordinal) +{ + switch (card_ordinal) { + case CARDTYPE_DIVASRV_P_30M_V2_PCI: + case CARDTYPE_DIVASRV_VOICE_P_30M_V2_PCI: + return (1); + } + return (0); +} + +static void diva_pri_set_addresses(diva_os_xdi_adapter_t * a) +{ + a->resources.pci.mem_type_id[MEM_TYPE_ADDRESS] = 0; + a->resources.pci.mem_type_id[MEM_TYPE_CONTROL] = 2; + a->resources.pci.mem_type_id[MEM_TYPE_CONFIG] = 4; + a->resources.pci.mem_type_id[MEM_TYPE_RAM] = 0; + a->resources.pci.mem_type_id[MEM_TYPE_RESET] = 2; + a->resources.pci.mem_type_id[MEM_TYPE_CFG] = 4; + a->resources.pci.mem_type_id[MEM_TYPE_PROM] = 3; + + a->xdi_adapter.Address = a->resources.pci.addr[0]; + a->xdi_adapter.Control = a->resources.pci.addr[2]; + a->xdi_adapter.Config = a->resources.pci.addr[4]; + + a->xdi_adapter.ram = a->resources.pci.addr[0]; + a->xdi_adapter.ram += MP_SHARED_RAM_OFFSET; + + a->xdi_adapter.reset = a->resources.pci.addr[2]; + a->xdi_adapter.reset += MP_RESET; + + a->xdi_adapter.cfg = a->resources.pci.addr[4]; + a->xdi_adapter.cfg += MP_IRQ_RESET; + + a->xdi_adapter.sdram_bar = a->resources.pci.bar[0]; + + a->xdi_adapter.prom = a->resources.pci.addr[3]; +} + +/* +** BAR0 - SDRAM, MP_MEMORY_SIZE, MP2_MEMORY_SIZE by Rev.2 +** BAR1 - DEVICES, 0x1000 +** BAR2 - CONTROL (REG), 0x2000 +** BAR3 - FLASH (REG), 0x8000 +** BAR4 - CONFIG (CFG), 0x1000 +*/ +int diva_pri_init_card(diva_os_xdi_adapter_t * a) +{ + int bar = 0; + int pri_rev_2; + unsigned long bar_length[5] = { + MP_MEMORY_SIZE, + 0x1000, + 0x2000, + 0x8000, + 0x1000 + }; + + pri_rev_2 = pri_is_rev_2_card(a->CardOrdinal); + + if (pri_rev_2) { + bar_length[0] = MP2_MEMORY_SIZE; + } + /* + Set properties + */ + a->xdi_adapter.Properties = CardProperties[a->CardOrdinal]; + DBG_LOG(("Load %s", a->xdi_adapter.Properties.Name)) + + /* + First initialization step: get and check hardware resoures. + Do not map resources and do not acecess card at this step + */ + for (bar = 0; bar < 5; bar++) { + a->resources.pci.bar[bar] = + divasa_get_pci_bar(a->resources.pci.bus, + a->resources.pci.func, bar, + a->resources.pci.hdev); + if (!a->resources.pci.bar[bar] + || (a->resources.pci.bar[bar] == 0xFFFFFFF0)) { + DBG_ERR(("A: invalid bar[%d]=%08x", bar, + a->resources.pci.bar[bar])) + return (-1); + } + } + a->resources.pci.irq = + (byte) divasa_get_pci_irq(a->resources.pci.bus, + a->resources.pci.func, + a->resources.pci.hdev); + if (!a->resources.pci.irq) { + DBG_ERR(("A: invalid irq")); + return (-1); + } + + /* + Map all BAR's + */ + for (bar = 0; bar < 5; bar++) { + a->resources.pci.addr[bar] = + divasa_remap_pci_bar(a, bar, a->resources.pci.bar[bar], + bar_length[bar]); + if (!a->resources.pci.addr[bar]) { + DBG_ERR(("A: A(%d), can't map bar[%d]", + a->controller, bar)) + diva_pri_cleanup_adapter(a); + return (-1); + } + } + + /* + Set all memory areas + */ + diva_pri_set_addresses(a); + + /* + Get Serial Number of this adapter + */ + if (pri_get_serial_number(a)) { + dword serNo; + serNo = a->resources.pci.bar[1] & 0xffff0000; + serNo |= ((dword) a->resources.pci.bus) << 8; + serNo += (a->resources.pci.func + a->controller + 1); + a->xdi_adapter.serialNo = serNo & ~0xFF000000; + DBG_ERR(("A: A(%d) can't get Serial Number, generated serNo=%ld", + a->controller, a->xdi_adapter.serialNo)) + } + + + /* + Initialize os objects + */ + if (diva_os_initialize_spin_lock(&a->xdi_adapter.isr_spin_lock, "isr")) { + diva_pri_cleanup_adapter(a); + return (-1); + } + if (diva_os_initialize_spin_lock + (&a->xdi_adapter.data_spin_lock, "data")) { + diva_pri_cleanup_adapter(a); + return (-1); + } + + strcpy(a->xdi_adapter.req_soft_isr.dpc_thread_name, "kdivasprid"); + + if (diva_os_initialize_soft_isr(&a->xdi_adapter.req_soft_isr, + DIDpcRoutine, &a->xdi_adapter)) { + diva_pri_cleanup_adapter(a); + return (-1); + } + + /* + Do not initialize second DPC - only one thread will be created + */ + a->xdi_adapter.isr_soft_isr.object = + a->xdi_adapter.req_soft_isr.object; + + /* + Next step of card initialization: + set up all interface pointers + */ + a->xdi_adapter.Channels = CardProperties[a->CardOrdinal].Channels; + a->xdi_adapter.e_max = CardProperties[a->CardOrdinal].E_info; + + a->xdi_adapter.e_tbl = + diva_os_malloc(0, a->xdi_adapter.e_max * sizeof(E_INFO)); + if (!a->xdi_adapter.e_tbl) { + diva_pri_cleanup_adapter(a); + return (-1); + } + memset(a->xdi_adapter.e_tbl, 0x00, a->xdi_adapter.e_max * sizeof(E_INFO)); + + a->xdi_adapter.a.io = &a->xdi_adapter; + a->xdi_adapter.DIRequest = request; + a->interface.cleanup_adapter_proc = diva_pri_cleanup_adapter; + a->interface.cmd_proc = diva_pri_cmd_card_proc; + + if (pri_rev_2) { + prepare_pri2_functions(&a->xdi_adapter); + } else { + prepare_pri_functions(&a->xdi_adapter); + } + + a->dsp_mask = diva_pri_detect_dsps(a); + + /* + Allocate DMA map + */ + if (pri_rev_2) { + diva_init_dma_map(a->resources.pci.hdev, + (struct _diva_dma_map_entry **) &a->xdi_adapter.dma_map, 32); + } + + /* + Set IRQ handler + */ + a->xdi_adapter.irq_info.irq_nr = a->resources.pci.irq; + sprintf(a->xdi_adapter.irq_info.irq_name, + "DIVA PRI %ld", (long) a->xdi_adapter.serialNo); + + if (diva_os_register_irq(a, a->xdi_adapter.irq_info.irq_nr, + a->xdi_adapter.irq_info.irq_name)) { + diva_pri_cleanup_adapter(a); + return (-1); + } + a->xdi_adapter.irq_info.registered = 1; + + diva_log_info("%s IRQ:%d SerNo:%d", a->xdi_adapter.Properties.Name, + a->resources.pci.irq, a->xdi_adapter.serialNo); + + return (0); +} + +static int diva_pri_cleanup_adapter(diva_os_xdi_adapter_t * a) +{ + int bar = 0; + + /* + Stop Adapter if adapter is running + */ + if (a->xdi_adapter.Initialized) { + diva_pri_stop_adapter(a); + } + + /* + Remove ISR Handler + */ + if (a->xdi_adapter.irq_info.registered) { + diva_os_remove_irq(a, a->xdi_adapter.irq_info.irq_nr); + } + a->xdi_adapter.irq_info.registered = 0; + + /* + Step 1: unmap all BAR's, if any was mapped + */ + for (bar = 0; bar < 5; bar++) { + if (a->resources.pci.bar[bar] + && a->resources.pci.addr[bar]) { + divasa_unmap_pci_bar(a->resources.pci.addr[bar]); + a->resources.pci.bar[bar] = 0; + a->resources.pci.addr[bar] = NULL; + } + } + + /* + Free OS objects + */ + diva_os_cancel_soft_isr(&a->xdi_adapter.isr_soft_isr); + diva_os_cancel_soft_isr(&a->xdi_adapter.req_soft_isr); + + diva_os_remove_soft_isr(&a->xdi_adapter.req_soft_isr); + a->xdi_adapter.isr_soft_isr.object = NULL; + + diva_os_destroy_spin_lock(&a->xdi_adapter.isr_spin_lock, "rm"); + diva_os_destroy_spin_lock(&a->xdi_adapter.data_spin_lock, "rm"); + + /* + Free memory accupied by XDI adapter + */ + if (a->xdi_adapter.e_tbl) { + diva_os_free(0, a->xdi_adapter.e_tbl); + a->xdi_adapter.e_tbl = NULL; + } + a->xdi_adapter.Channels = 0; + a->xdi_adapter.e_max = 0; + + + /* + Free adapter DMA map + */ + diva_free_dma_map(a->resources.pci.hdev, + (struct _diva_dma_map_entry *) a->xdi_adapter. + dma_map); + a->xdi_adapter.dma_map = NULL; + + + /* + Detach this adapter from debug driver + */ + + return (0); +} + +/* +** Activate On Board Boot Loader +*/ +static int diva_pri_reset_adapter(PISDN_ADAPTER IoAdapter) +{ + dword i; + struct mp_load __iomem *boot; + + if (!IoAdapter->Address || !IoAdapter->reset) { + return (-1); + } + if (IoAdapter->Initialized) { + DBG_ERR(("A: A(%d) can't reset PRI adapter - please stop first", + IoAdapter->ANum)) + return (-1); + } + + boot = (struct mp_load __iomem *) DIVA_OS_MEM_ATTACH_ADDRESS(IoAdapter); + WRITE_DWORD(&boot->err, 0); + DIVA_OS_MEM_DETACH_ADDRESS(IoAdapter, boot); + + IoAdapter->rstFnc(IoAdapter); + + diva_os_wait(10); + + boot = (struct mp_load __iomem *) DIVA_OS_MEM_ATTACH_ADDRESS(IoAdapter); + i = READ_DWORD(&boot->live); + + diva_os_wait(10); + if (i == READ_DWORD(&boot->live)) { + DIVA_OS_MEM_DETACH_ADDRESS(IoAdapter, boot); + DBG_ERR(("A: A(%d) CPU on PRI %ld is not alive!", + IoAdapter->ANum, IoAdapter->serialNo)) + return (-1); + } + if (READ_DWORD(&boot->err)) { + DBG_ERR(("A: A(%d) PRI %ld Board Selftest failed, error=%08lx", + IoAdapter->ANum, IoAdapter->serialNo, + READ_DWORD(&boot->err))) + DIVA_OS_MEM_DETACH_ADDRESS(IoAdapter, boot); + return (-1); + } + DIVA_OS_MEM_DETACH_ADDRESS(IoAdapter, boot); + + /* + Forget all outstanding entities + */ + IoAdapter->e_count = 0; + if (IoAdapter->e_tbl) { + memset(IoAdapter->e_tbl, 0x00, + IoAdapter->e_max * sizeof(E_INFO)); + } + IoAdapter->head = 0; + IoAdapter->tail = 0; + IoAdapter->assign = 0; + IoAdapter->trapped = 0; + + memset(&IoAdapter->a.IdTable[0], 0x00, + sizeof(IoAdapter->a.IdTable)); + memset(&IoAdapter->a.IdTypeTable[0], 0x00, + sizeof(IoAdapter->a.IdTypeTable)); + memset(&IoAdapter->a.FlowControlIdTable[0], 0x00, + sizeof(IoAdapter->a.FlowControlIdTable)); + memset(&IoAdapter->a.FlowControlSkipTable[0], 0x00, + sizeof(IoAdapter->a.FlowControlSkipTable)); + memset(&IoAdapter->a.misc_flags_table[0], 0x00, + sizeof(IoAdapter->a.misc_flags_table)); + memset(&IoAdapter->a.rx_stream[0], 0x00, + sizeof(IoAdapter->a.rx_stream)); + memset(&IoAdapter->a.tx_stream[0], 0x00, + sizeof(IoAdapter->a.tx_stream)); + memset(&IoAdapter->a.tx_pos[0], 0x00, sizeof(IoAdapter->a.tx_pos)); + memset(&IoAdapter->a.rx_pos[0], 0x00, sizeof(IoAdapter->a.rx_pos)); + + return (0); +} + +static int +diva_pri_write_sdram_block(PISDN_ADAPTER IoAdapter, + dword address, + const byte * data, dword length, dword limit) +{ + byte __iomem *p = DIVA_OS_MEM_ATTACH_ADDRESS(IoAdapter); + byte __iomem *mem = p; + + if (((address + length) >= limit) || !mem) { + DIVA_OS_MEM_DETACH_ADDRESS(IoAdapter, p); + DBG_ERR(("A: A(%d) write PRI address=0x%08lx", + IoAdapter->ANum, address + length)) + return (-1); + } + mem += address; + + /* memcpy_toio(), maybe? */ + while (length--) { + WRITE_BYTE(mem++, *data++); + } + + DIVA_OS_MEM_DETACH_ADDRESS(IoAdapter, p); + return (0); +} + +static int +diva_pri_start_adapter(PISDN_ADAPTER IoAdapter, + dword start_address, dword features) +{ + dword i; + int started = 0; + byte __iomem *p; + struct mp_load __iomem *boot = (struct mp_load __iomem *) DIVA_OS_MEM_ATTACH_ADDRESS(IoAdapter); + ADAPTER *a = &IoAdapter->a; + + if (IoAdapter->Initialized) { + DIVA_OS_MEM_DETACH_ADDRESS(IoAdapter, boot); + DBG_ERR(("A: A(%d) pri_start_adapter, adapter already running", + IoAdapter->ANum)) + return (-1); + } + if (!boot) { + DIVA_OS_MEM_DETACH_ADDRESS(IoAdapter, boot); + DBG_ERR(("A: PRI %ld can't start, adapter not mapped", + IoAdapter->serialNo)) + return (-1); + } + + sprintf(IoAdapter->Name, "A(%d)", (int) IoAdapter->ANum); + DBG_LOG(("A(%d) start PRI at 0x%08lx", IoAdapter->ANum, + start_address)) + + WRITE_DWORD(&boot->addr, start_address); + WRITE_DWORD(&boot->cmd, 3); + + for (i = 0; i < 300; ++i) { + diva_os_wait(10); + if ((READ_DWORD(&boot->signature) >> 16) == 0x4447) { + DBG_LOG(("A(%d) Protocol startup time %d.%02d seconds", + IoAdapter->ANum, (i / 100), (i % 100))) + started = 1; + break; + } + } + + if (!started) { + byte __iomem *p = (byte __iomem *)boot; + dword TrapId; + dword debug; + TrapId = READ_DWORD(&p[0x80]); + debug = READ_DWORD(&p[0x1c]); + DBG_ERR(("A(%d) Adapter start failed 0x%08lx, TrapId=%08lx, debug=%08lx", + IoAdapter->ANum, READ_DWORD(&boot->signature), + TrapId, debug)) + DIVA_OS_MEM_DETACH_ADDRESS(IoAdapter, boot); + if (IoAdapter->trapFnc) { + (*(IoAdapter->trapFnc)) (IoAdapter); + } + IoAdapter->stop(IoAdapter); + return (-1); + } + DIVA_OS_MEM_DETACH_ADDRESS(IoAdapter, boot); + + IoAdapter->Initialized = TRUE; + + /* + Check Interrupt + */ + IoAdapter->IrqCount = 0; + p = DIVA_OS_MEM_ATTACH_CFG(IoAdapter); + WRITE_DWORD(p, (dword) ~ 0x03E00000); + DIVA_OS_MEM_DETACH_CFG(IoAdapter, p); + a->ReadyInt = 1; + a->ram_out(a, &PR_RAM->ReadyInt, 1); + + for (i = 100; !IoAdapter->IrqCount && (i-- > 0); diva_os_wait(10)); + + if (!IoAdapter->IrqCount) { + DBG_ERR(("A: A(%d) interrupt test failed", + IoAdapter->ANum)) + IoAdapter->Initialized = FALSE; + IoAdapter->stop(IoAdapter); + return (-1); + } + + IoAdapter->Properties.Features = (word) features; + + diva_xdi_display_adapter_features(IoAdapter->ANum); + + DBG_LOG(("A(%d) PRI adapter successfull started", IoAdapter->ANum)) + /* + Register with DIDD + */ + diva_xdi_didd_register_adapter(IoAdapter->ANum); + + return (0); +} + +static void diva_pri_clear_interrupts(diva_os_xdi_adapter_t * a) +{ + PISDN_ADAPTER IoAdapter = &a->xdi_adapter; + + /* + clear any pending interrupt + */ + IoAdapter->disIrq(IoAdapter); + + IoAdapter->tst_irq(&IoAdapter->a); + IoAdapter->clr_irq(&IoAdapter->a); + IoAdapter->tst_irq(&IoAdapter->a); + + /* + kill pending dpcs + */ + diva_os_cancel_soft_isr(&IoAdapter->req_soft_isr); + diva_os_cancel_soft_isr(&IoAdapter->isr_soft_isr); +} + +/* +** Stop Adapter, but do not unmap/unregister - adapter +** will be restarted later +*/ +static int diva_pri_stop_adapter(diva_os_xdi_adapter_t * a) +{ + PISDN_ADAPTER IoAdapter = &a->xdi_adapter; + int i = 100; + + if (!IoAdapter->ram) { + return (-1); + } + if (!IoAdapter->Initialized) { + DBG_ERR(("A: A(%d) can't stop PRI adapter - not running", + IoAdapter->ANum)) + return (-1); /* nothing to stop */ + } + IoAdapter->Initialized = 0; + + /* + Disconnect Adapter from DIDD + */ + diva_xdi_didd_remove_adapter(IoAdapter->ANum); + + /* + Stop interrupts + */ + a->clear_interrupts_proc = diva_pri_clear_interrupts; + IoAdapter->a.ReadyInt = 1; + IoAdapter->a.ram_inc(&IoAdapter->a, &PR_RAM->ReadyInt); + do { + diva_os_sleep(10); + } while (i-- && a->clear_interrupts_proc); + + if (a->clear_interrupts_proc) { + diva_pri_clear_interrupts(a); + a->clear_interrupts_proc = NULL; + DBG_ERR(("A: A(%d) no final interrupt from PRI adapter", + IoAdapter->ANum)) + } + IoAdapter->a.ReadyInt = 0; + + /* + Stop and reset adapter + */ + IoAdapter->stop(IoAdapter); + + return (0); +} + +/* +** Process commands form configuration/download framework and from +** user mode +** +** return 0 on success +*/ +static int +diva_pri_cmd_card_proc(struct _diva_os_xdi_adapter *a, + diva_xdi_um_cfg_cmd_t * cmd, int length) +{ + int ret = -1; + + if (cmd->adapter != a->controller) { + DBG_ERR(("A: pri_cmd, invalid controller=%d != %d", + cmd->adapter, a->controller)) + return (-1); + } + + switch (cmd->command) { + case DIVA_XDI_UM_CMD_GET_CARD_ORDINAL: + a->xdi_mbox.data_length = sizeof(dword); + a->xdi_mbox.data = + diva_os_malloc(0, a->xdi_mbox.data_length); + if (a->xdi_mbox.data) { + *(dword *) a->xdi_mbox.data = + (dword) a->CardOrdinal; + a->xdi_mbox.status = DIVA_XDI_MBOX_BUSY; + ret = 0; + } + break; + + case DIVA_XDI_UM_CMD_GET_SERIAL_NR: + a->xdi_mbox.data_length = sizeof(dword); + a->xdi_mbox.data = + diva_os_malloc(0, a->xdi_mbox.data_length); + if (a->xdi_mbox.data) { + *(dword *) a->xdi_mbox.data = + (dword) a->xdi_adapter.serialNo; + a->xdi_mbox.status = DIVA_XDI_MBOX_BUSY; + ret = 0; + } + break; + + case DIVA_XDI_UM_CMD_GET_PCI_HW_CONFIG: + a->xdi_mbox.data_length = sizeof(dword) * 9; + a->xdi_mbox.data = + diva_os_malloc(0, a->xdi_mbox.data_length); + if (a->xdi_mbox.data) { + int i; + dword *data = (dword *) a->xdi_mbox.data; + + for (i = 0; i < 8; i++) { + *data++ = a->resources.pci.bar[i]; + } + *data++ = (dword) a->resources.pci.irq; + a->xdi_mbox.status = DIVA_XDI_MBOX_BUSY; + ret = 0; + } + break; + + case DIVA_XDI_UM_CMD_RESET_ADAPTER: + ret = diva_pri_reset_adapter(&a->xdi_adapter); + break; + + case DIVA_XDI_UM_CMD_WRITE_SDRAM_BLOCK: + ret = diva_pri_write_sdram_block(&a->xdi_adapter, + cmd->command_data. + write_sdram.offset, + (byte *) & cmd[1], + cmd->command_data. + write_sdram.length, + pri_is_rev_2_card(a-> + CardOrdinal) + ? MP2_MEMORY_SIZE : + MP_MEMORY_SIZE); + break; + + case DIVA_XDI_UM_CMD_STOP_ADAPTER: + ret = diva_pri_stop_adapter(a); + break; + + case DIVA_XDI_UM_CMD_START_ADAPTER: + ret = diva_pri_start_adapter(&a->xdi_adapter, + cmd->command_data.start. + offset, + cmd->command_data.start. + features); + break; + + case DIVA_XDI_UM_CMD_SET_PROTOCOL_FEATURES: + a->xdi_adapter.features = + cmd->command_data.features.features; + a->xdi_adapter.a.protocol_capabilities = + a->xdi_adapter.features; + DBG_TRC(("Set raw protocol features (%08x)", + a->xdi_adapter.features)) + ret = 0; + break; + + case DIVA_XDI_UM_CMD_GET_CARD_STATE: + a->xdi_mbox.data_length = sizeof(dword); + a->xdi_mbox.data = + diva_os_malloc(0, a->xdi_mbox.data_length); + if (a->xdi_mbox.data) { + dword *data = (dword *) a->xdi_mbox.data; + if (!a->xdi_adapter.ram || + !a->xdi_adapter.reset || + !a->xdi_adapter.cfg) { + *data = 3; + } else if (a->xdi_adapter.trapped) { + *data = 2; + } else if (a->xdi_adapter.Initialized) { + *data = 1; + } else { + *data = 0; + } + a->xdi_mbox.status = DIVA_XDI_MBOX_BUSY; + ret = 0; + } + break; + + case DIVA_XDI_UM_CMD_READ_XLOG_ENTRY: + ret = diva_card_read_xlog(a); + break; + + case DIVA_XDI_UM_CMD_READ_SDRAM: + if (a->xdi_adapter.Address) { + if ( + (a->xdi_mbox.data_length = + cmd->command_data.read_sdram.length)) { + if ( + (a->xdi_mbox.data_length + + cmd->command_data.read_sdram.offset) < + a->xdi_adapter.MemorySize) { + a->xdi_mbox.data = + diva_os_malloc(0, + a->xdi_mbox. + data_length); + if (a->xdi_mbox.data) { + byte __iomem *p = DIVA_OS_MEM_ATTACH_ADDRESS(&a->xdi_adapter); + byte __iomem *src = p; + byte *dst = a->xdi_mbox.data; + dword len = a->xdi_mbox.data_length; + + src += cmd->command_data.read_sdram.offset; + + while (len--) { + *dst++ = READ_BYTE(src++); + } + a->xdi_mbox.status = DIVA_XDI_MBOX_BUSY; + DIVA_OS_MEM_DETACH_ADDRESS(&a->xdi_adapter, p); + ret = 0; + } + } + } + } + break; + + default: + DBG_ERR(("A: A(%d) invalid cmd=%d", a->controller, + cmd->command)) + } + + return (ret); +} + +/* +** Get Serial Number +*/ +static int pri_get_serial_number(diva_os_xdi_adapter_t * a) +{ + byte data[64]; + int i; + dword len = sizeof(data); + volatile byte __iomem *config; + volatile byte __iomem *flash; + byte c; + +/* + * First set some GT6401x config registers before accessing the BOOT-ROM + */ + config = DIVA_OS_MEM_ATTACH_CONFIG(&a->xdi_adapter); + c = READ_BYTE(&config[0xc3c]); + if (!(c & 0x08)) { + WRITE_BYTE(&config[0xc3c], c); /* Base Address enable register */ + } + WRITE_BYTE(&config[LOW_BOOTCS_DREG], 0x00); + WRITE_BYTE(&config[HI_BOOTCS_DREG], 0xFF); + DIVA_OS_MEM_DETACH_CONFIG(&a->xdi_adapter, config); +/* + * Read only the last 64 bytes of manufacturing data + */ + memset(data, '\0', len); + flash = DIVA_OS_MEM_ATTACH_PROM(&a->xdi_adapter); + for (i = 0; i < len; i++) { + data[i] = READ_BYTE(&flash[0x8000 - len + i]); + } + DIVA_OS_MEM_DETACH_PROM(&a->xdi_adapter, flash); + + config = DIVA_OS_MEM_ATTACH_CONFIG(&a->xdi_adapter); + WRITE_BYTE(&config[LOW_BOOTCS_DREG], 0xFC); /* Disable FLASH EPROM access */ + WRITE_BYTE(&config[HI_BOOTCS_DREG], 0xFF); + DIVA_OS_MEM_DETACH_CONFIG(&a->xdi_adapter, config); + + if (memcmp(&data[48], "DIVAserverPR", 12)) { +#if !defined(DIVA_PRI_NO_PCI_BIOS_WORKAROUND) /* { */ + word cmd = 0, cmd_org; + void *addr; + dword addr1, addr3, addr4; + byte Bus, Slot; + void *hdev; + addr4 = a->resources.pci.bar[4]; + addr3 = a->resources.pci.bar[3]; /* flash */ + addr1 = a->resources.pci.bar[1]; /* unused */ + + DBG_ERR(("A: apply Compaq BIOS workaround")) + DBG_LOG(("%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", + data[0], data[1], data[2], data[3], + data[4], data[5], data[6], data[7])) + + Bus = a->resources.pci.bus; + Slot = a->resources.pci.func; + hdev = a->resources.pci.hdev; + PCIread(Bus, Slot, 0x04, &cmd_org, sizeof(cmd_org), hdev); + PCIwrite(Bus, Slot, 0x04, &cmd, sizeof(cmd), hdev); + + PCIwrite(Bus, Slot, 0x14, &addr4, sizeof(addr4), hdev); + PCIwrite(Bus, Slot, 0x20, &addr1, sizeof(addr1), hdev); + + PCIwrite(Bus, Slot, 0x04, &cmd_org, sizeof(cmd_org), hdev); + + addr = a->resources.pci.addr[1]; + a->resources.pci.addr[1] = a->resources.pci.addr[4]; + a->resources.pci.addr[4] = addr; + + addr1 = a->resources.pci.bar[1]; + a->resources.pci.bar[1] = a->resources.pci.bar[4]; + a->resources.pci.bar[4] = addr1; + + /* + Try to read Flash again + */ + len = sizeof(data); + + config = DIVA_OS_MEM_ATTACH_CONFIG(&a->xdi_adapter); + if (!(config[0xc3c] & 0x08)) { + config[0xc3c] |= 0x08; /* Base Address enable register */ + } + config[LOW_BOOTCS_DREG] = 0x00; + config[HI_BOOTCS_DREG] = 0xFF; + DIVA_OS_MEM_DETACH_CONFIG(&a->xdi_adapter, config); + + memset(data, '\0', len); + flash = DIVA_OS_MEM_ATTACH_PROM(&a->xdi_adapter); + for (i = 0; i < len; i++) { + data[i] = flash[0x8000 - len + i]; + } + DIVA_OS_MEM_ATTACH_PROM(&a->xdi_adapter, flash); + config = DIVA_OS_MEM_ATTACH_CONFIG(&a->xdi_adapter); + config[LOW_BOOTCS_DREG] = 0xFC; + config[HI_BOOTCS_DREG] = 0xFF; + DIVA_OS_MEM_DETACH_CONFIG(&a->xdi_adapter, config); + + if (memcmp(&data[48], "DIVAserverPR", 12)) { + DBG_ERR(("A: failed to read serial number")) + DBG_LOG(("%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", + data[0], data[1], data[2], data[3], + data[4], data[5], data[6], data[7])) + return (-1); + } +#else /* } { */ + DBG_ERR(("A: failed to read DIVA signature word")) + DBG_LOG(("%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", + data[0], data[1], data[2], data[3], + data[4], data[5], data[6], data[7])) + DBG_LOG(("%02x:%02x:%02x:%02x", data[47], data[46], + data[45], data[44])) +#endif /* } */ + } + + a->xdi_adapter.serialNo = + (data[47] << 24) | (data[46] << 16) | (data[45] << 8) | + data[44]; + if (!a->xdi_adapter.serialNo + || (a->xdi_adapter.serialNo == 0xffffffff)) { + a->xdi_adapter.serialNo = 0; + DBG_ERR(("A: failed to read serial number")) + return (-1); + } + + DBG_LOG(("Serial No. : %ld", a->xdi_adapter.serialNo)) + DBG_TRC(("Board Revision : %d.%02d", (int) data[41], + (int) data[40])) + DBG_TRC(("PLD revision : %d.%02d", (int) data[33], + (int) data[32])) + DBG_TRC(("Boot loader version : %d.%02d", (int) data[37], + (int) data[36])) + + DBG_TRC(("Manufacturing Date : %d/%02d/%02d (yyyy/mm/dd)", + (int) ((data[28] > 90) ? 1900 : 2000) + + (int) data[28], (int) data[29], (int) data[30])) + + return (0); +} + +void diva_os_prepare_pri2_functions(PISDN_ADAPTER IoAdapter) +{ +} + +void diva_os_prepare_pri_functions(PISDN_ADAPTER IoAdapter) +{ +} + +/* +** Checks presence of DSP on board +*/ +static int +dsp_check_presence(volatile byte __iomem * addr, volatile byte __iomem * data, int dsp) +{ + word pattern; + + WRITE_WORD(addr, 0x4000); + WRITE_WORD(data, DSP_SIGNATURE_PROBE_WORD); + + WRITE_WORD(addr, 0x4000); + pattern = READ_WORD(data); + + if (pattern != DSP_SIGNATURE_PROBE_WORD) { + DBG_TRC(("W: DSP[%d] %04x(is) != %04x(should)", + dsp, pattern, DSP_SIGNATURE_PROBE_WORD)) + return (-1); + } + + WRITE_WORD(addr, 0x4000); + WRITE_WORD(data, ~DSP_SIGNATURE_PROBE_WORD); + + WRITE_WORD(addr, 0x4000); + pattern = READ_WORD(data); + + if (pattern != (word) ~ DSP_SIGNATURE_PROBE_WORD) { + DBG_ERR(("A: DSP[%d] %04x(is) != %04x(should)", + dsp, pattern, (word) ~ DSP_SIGNATURE_PROBE_WORD)) + return (-2); + } + + DBG_TRC(("DSP[%d] present", dsp)) + + return (0); +} + + +/* +** Check if DSP's are present and operating +** Information about detected DSP's is returned as bit mask +** Bit 0 - DSP1 +** ... +** ... +** ... +** Bit 29 - DSP30 +*/ +static dword diva_pri_detect_dsps(diva_os_xdi_adapter_t * a) +{ + byte __iomem *base; + byte __iomem *p; + dword ret = 0; + dword row_offset[7] = { + 0x00000000, + 0x00000800, /* 1 - ROW 1 */ + 0x00000840, /* 2 - ROW 2 */ + 0x00001000, /* 3 - ROW 3 */ + 0x00001040, /* 4 - ROW 4 */ + 0x00000000 /* 5 - ROW 0 */ + }; + + byte __iomem *dsp_addr_port; + byte __iomem *dsp_data_port; + byte row_state; + int dsp_row = 0, dsp_index, dsp_num; + + if (!a->xdi_adapter.Control || !a->xdi_adapter.reset) { + return (0); + } + + p = DIVA_OS_MEM_ATTACH_RESET(&a->xdi_adapter); + WRITE_BYTE(p, _MP_RISC_RESET | _MP_DSP_RESET); + DIVA_OS_MEM_DETACH_RESET(&a->xdi_adapter, p); + diva_os_wait(5); + + base = DIVA_OS_MEM_ATTACH_CONTROL(&a->xdi_adapter); + + for (dsp_num = 0; dsp_num < 30; dsp_num++) { + dsp_row = dsp_num / 7 + 1; + dsp_index = dsp_num % 7; + + dsp_data_port = base; + dsp_addr_port = base; + + dsp_data_port += row_offset[dsp_row]; + dsp_addr_port += row_offset[dsp_row]; + + dsp_data_port += (dsp_index * 8); + dsp_addr_port += (dsp_index * 8) + 0x80; + + if (!dsp_check_presence + (dsp_addr_port, dsp_data_port, dsp_num + 1)) { + ret |= (1 << dsp_num); + } + } + DIVA_OS_MEM_DETACH_CONTROL(&a->xdi_adapter, base); + + p = DIVA_OS_MEM_ATTACH_RESET(&a->xdi_adapter); + WRITE_BYTE(p, _MP_RISC_RESET | _MP_LED1 | _MP_LED2); + DIVA_OS_MEM_DETACH_RESET(&a->xdi_adapter, p); + diva_os_wait(5); + + /* + Verify modules + */ + for (dsp_row = 0; dsp_row < 4; dsp_row++) { + row_state = ((ret >> (dsp_row * 7)) & 0x7F); + if (row_state && (row_state != 0x7F)) { + for (dsp_index = 0; dsp_index < 7; dsp_index++) { + if (!(row_state & (1 << dsp_index))) { + DBG_ERR(("A: MODULE[%d]-DSP[%d] failed", + dsp_row + 1, + dsp_index + 1)) + } + } + } + } + + if (!(ret & 0x10000000)) { + DBG_ERR(("A: ON BOARD-DSP[1] failed")) + } + if (!(ret & 0x20000000)) { + DBG_ERR(("A: ON BOARD-DSP[2] failed")) + } + + /* + Print module population now + */ + DBG_LOG(("+-----------------------+")) + DBG_LOG(("| DSP MODULE POPULATION |")) + DBG_LOG(("+-----------------------+")) + DBG_LOG(("| 1 | 2 | 3 | 4 |")) + DBG_LOG(("+-----------------------+")) + DBG_LOG(("| %s | %s | %s | %s |", + ((ret >> (0 * 7)) & 0x7F) ? "Y" : "N", + ((ret >> (1 * 7)) & 0x7F) ? "Y" : "N", + ((ret >> (2 * 7)) & 0x7F) ? "Y" : "N", + ((ret >> (3 * 7)) & 0x7F) ? "Y" : "N")) + DBG_LOG(("+-----------------------+")) + + DBG_LOG(("DSP's(present-absent):%08x-%08x", ret, + ~ret & 0x3fffffff)) + + return (ret); +} diff --git a/drivers/isdn/hardware/eicon/os_pri.h b/drivers/isdn/hardware/eicon/os_pri.h new file mode 100644 index 000000000000..a7c42f94d78a --- /dev/null +++ b/drivers/isdn/hardware/eicon/os_pri.h @@ -0,0 +1,8 @@ +/* $Id: os_pri.h,v 1.1.2.2 2001/02/08 12:25:44 armin Exp $ */ + +#ifndef __DIVA_OS_PRI_REV_1_H__ +#define __DIVA_OS_PRI_REV_1_H__ + +int diva_pri_init_card(diva_os_xdi_adapter_t * a); + +#endif diff --git a/drivers/isdn/hardware/eicon/pc.h b/drivers/isdn/hardware/eicon/pc.h new file mode 100644 index 000000000000..1c6945768a35 --- /dev/null +++ b/drivers/isdn/hardware/eicon/pc.h @@ -0,0 +1,738 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +#ifndef PC_H_INCLUDED /* { */ +#define PC_H_INCLUDED +/*------------------------------------------------------------------*/ +/* buffer definition */ +/*------------------------------------------------------------------*/ +typedef struct { + word length; /* length of data/parameter field */ + byte P[270]; /* data/parameter field */ +} PBUFFER; +/*------------------------------------------------------------------*/ +/* dual port ram structure */ +/*------------------------------------------------------------------*/ +struct dual +{ + byte Req; /* request register */ + byte ReqId; /* request task/entity identification */ + byte Rc; /* return code register */ + byte RcId; /* return code task/entity identification */ + byte Ind; /* Indication register */ + byte IndId; /* Indication task/entity identification */ + byte IMask; /* Interrupt Mask Flag */ + byte RNR; /* Receiver Not Ready (set by PC) */ + byte XLock; /* XBuffer locked Flag */ + byte Int; /* ISDN-S interrupt */ + byte ReqCh; /* Channel field for layer-3 Requests */ + byte RcCh; /* Channel field for layer-3 Returncodes */ + byte IndCh; /* Channel field for layer-3 Indications */ + byte MInd; /* more data indication field */ + word MLength; /* more data total packet length */ + byte ReadyInt; /* request field for ready interrupt */ + byte SWReg; /* Software register for special purposes */ + byte Reserved[11]; /* reserved space */ + byte InterfaceType; /* interface type 1=16K interface */ + word Signature; /* ISDN-S adapter Signature (GD) */ + PBUFFER XBuffer; /* Transmit Buffer */ + PBUFFER RBuffer; /* Receive Buffer */ +}; +/*------------------------------------------------------------------*/ +/* SWReg Values (0 means no command) */ +/*------------------------------------------------------------------*/ +#define SWREG_DIE_WITH_LEDON 0x01 +#define SWREG_HALT_CPU 0x02 /* Push CPU into a while(1) loop */ +/*------------------------------------------------------------------*/ +/* Id Fields Coding */ +/*------------------------------------------------------------------*/ +#define ID_MASK 0xe0 /* Mask for the ID field */ +#define GL_ERR_ID 0x1f /* ID for error reporting on global requests*/ +#define DSIG_ID 0x00 /* ID for D-channel signaling */ +#define NL_ID 0x20 /* ID for network-layer access (B or D) */ +#define BLLC_ID 0x60 /* ID for B-channel link level access */ +#define TASK_ID 0x80 /* ID for dynamic user tasks */ +#define TIMER_ID 0xa0 /* ID for timer task */ +#define TEL_ID 0xc0 /* ID for telephone support */ +#define MAN_ID 0xe0 /* ID for management */ +/*------------------------------------------------------------------*/ +/* ASSIGN and REMOVE requests are the same for all entities */ +/*------------------------------------------------------------------*/ +#define ASSIGN 0x01 +#define UREMOVE 0xfe /* without return code */ +#define REMOVE 0xff +/*------------------------------------------------------------------*/ +/* Timer Interrupt Task Interface */ +/*------------------------------------------------------------------*/ +#define ASSIGN_TIM 0x01 +#define REMOVE_TIM 0xff +/*------------------------------------------------------------------*/ +/* dynamic user task interface */ +/*------------------------------------------------------------------*/ +#define ASSIGN_TSK 0x01 +#define REMOVE_TSK 0xff +#define LOAD 0xf0 +#define RELOCATE 0xf1 +#define START 0xf2 +#define LOAD2 0xf3 +#define RELOCATE2 0xf4 +/*------------------------------------------------------------------*/ +/* dynamic user task messages */ +/*------------------------------------------------------------------*/ +#define TSK_B2 0x0000 +#define TSK_WAKEUP 0x2000 +#define TSK_TIMER 0x4000 +#define TSK_TSK 0x6000 +#define TSK_PC 0xe000 +/*------------------------------------------------------------------*/ +/* LL management primitives */ +/*------------------------------------------------------------------*/ +#define ASSIGN_LL 1 /* assign logical link */ +#define REMOVE_LL 0xff /* remove logical link */ +/*------------------------------------------------------------------*/ +/* LL service primitives */ +/*------------------------------------------------------------------*/ +#define LL_UDATA 1 /* link unit data request/indication */ +#define LL_ESTABLISH 2 /* link establish request/indication */ +#define LL_RELEASE 3 /* link release request/indication */ +#define LL_DATA 4 /* data request/indication */ +#define LL_LOCAL 5 /* switch to local operation (COM only) */ +#define LL_DATA_PEND 5 /* data pending indication (SDLC SHM only) */ +#define LL_REMOTE 6 /* switch to remote operation (COM only) */ +#define LL_TEST 8 /* link test request */ +#define LL_MDATA 9 /* more data request/indication */ +#define LL_BUDATA 10 /* broadcast unit data request/indication */ +#define LL_XID 12 /* XID command request/indication */ +#define LL_XID_R 13 /* XID response request/indication */ +/*------------------------------------------------------------------*/ +/* NL service primitives */ +/*------------------------------------------------------------------*/ +#define N_MDATA 1 /* more data to come REQ/IND */ +#define N_CONNECT 2 /* OSI N-CONNECT REQ/IND */ +#define N_CONNECT_ACK 3 /* OSI N-CONNECT CON/RES */ +#define N_DISC 4 /* OSI N-DISC REQ/IND */ +#define N_DISC_ACK 5 /* OSI N-DISC CON/RES */ +#define N_RESET 6 /* OSI N-RESET REQ/IND */ +#define N_RESET_ACK 7 /* OSI N-RESET CON/RES */ +#define N_DATA 8 /* OSI N-DATA REQ/IND */ +#define N_EDATA 9 /* OSI N-EXPEDITED DATA REQ/IND */ +#define N_UDATA 10 /* OSI D-UNIT-DATA REQ/IND */ +#define N_BDATA 11 /* BROADCAST-DATA REQ/IND */ +#define N_DATA_ACK 12 /* data ack ind for D-bit procedure */ +#define N_EDATA_ACK 13 /* data ack ind for INTERRUPT */ +#define N_XON 15 /* clear RNR state */ +#define N_COMBI_IND N_XON /* combined indication */ +#define N_Q_BIT 0x10 /* Q-bit for req/ind */ +#define N_M_BIT 0x20 /* M-bit for req/ind */ +#define N_D_BIT 0x40 /* D-bit for req/ind */ +/*------------------------------------------------------------------*/ +/* Signaling management primitives */ +/*------------------------------------------------------------------*/ +#define ASSIGN_SIG 1 /* assign signaling task */ +#define UREMOVE_SIG 0xfe /* remove signaling task without return code*/ +#define REMOVE_SIG 0xff /* remove signaling task */ +/*------------------------------------------------------------------*/ +/* Signaling service primitives */ +/*------------------------------------------------------------------*/ +#define CALL_REQ 1 /* call request */ +#define CALL_CON 1 /* call confirmation */ +#define CALL_IND 2 /* incoming call connected */ +#define LISTEN_REQ 2 /* listen request */ +#define HANGUP 3 /* hangup request/indication */ +#define SUSPEND 4 /* call suspend request/confirm */ +#define RESUME 5 /* call resume request/confirm */ +#define SUSPEND_REJ 6 /* suspend rejected indication */ +#define USER_DATA 8 /* user data for user to user signaling */ +#define CONGESTION 9 /* network congestion indication */ +#define INDICATE_REQ 10 /* request to indicate an incoming call */ +#define INDICATE_IND 10 /* indicates that there is an incoming call */ +#define CALL_RES 11 /* accept an incoming call */ +#define CALL_ALERT 12 /* send ALERT for incoming call */ +#define INFO_REQ 13 /* INFO request */ +#define INFO_IND 13 /* INFO indication */ +#define REJECT 14 /* reject an incoming call */ +#define RESOURCES 15 /* reserve B-Channel hardware resources */ +#define HW_CTRL 16 /* B-Channel hardware IOCTL req/ind */ +#define TEL_CTRL 16 /* Telephone control request/indication */ +#define STATUS_REQ 17 /* Request D-State (returned in INFO_IND) */ +#define FAC_REG_REQ 18 /* 1TR6 connection independent fac reg */ +#define FAC_REG_ACK 19 /* 1TR6 fac registration acknowledge */ +#define FAC_REG_REJ 20 /* 1TR6 fac registration reject */ +#define CALL_COMPLETE 21/* send a CALL_PROC for incoming call */ +#define SW_CTRL 22 /* extended software features */ +#define REGISTER_REQ 23 /* Q.931 connection independent reg req */ +#define REGISTER_IND 24 /* Q.931 connection independent reg ind */ +#define FACILITY_REQ 25 /* Q.931 connection independent fac req */ +#define FACILITY_IND 26 /* Q.931 connection independent fac ind */ +#define NCR_INFO_REQ 27 /* INFO_REQ with NULL CR */ +#define GCR_MIM_REQ 28 /* MANAGEMENT_INFO_REQ with global CR */ +#define SIG_CTRL 29 /* Control for Signalling Hardware */ +#define DSP_CTRL 30 /* Control for DSPs */ +#define LAW_REQ 31 /* Law config request for (returns info_i) */ +#define SPID_CTRL 32 /* Request/indication SPID related */ +#define NCR_FACILITY 33 /* Request/indication with NULL/DUMMY CR */ +#define CALL_HOLD 34 /* Request/indication to hold a CALL */ +#define CALL_RETRIEVE 35 /* Request/indication to retrieve a CALL */ +#define CALL_HOLD_ACK 36 /* OK of hold a CALL */ +#define CALL_RETRIEVE_ACK 37 /* OK of retrieve a CALL */ +#define CALL_HOLD_REJ 38 /* Reject of hold a CALL */ +#define CALL_RETRIEVE_REJ 39 /* Reject of retrieve a call */ +#define GCR_RESTART 40 /* Send/Receive Restart message */ +#define S_SERVICE 41 /* Send/Receive Supplementary Service */ +#define S_SERVICE_REJ 42 /* Reject Supplementary Service indication */ +#define S_SUPPORTED 43 /* Req/Ind to get Supported Services */ +#define STATUS_ENQ 44 /* Req to send the D-ch request if !state0 */ +#define CALL_GUARD 45 /* Req/Ind to use the FLAGS_CALL_OUTCHECK */ +#define CALL_GUARD_HP 46 /* Call Guard function to reject a call */ +#define CALL_GUARD_IF 47 /* Call Guard function, inform the appl */ +#define SSEXT_REQ 48 /* Supplem.Serv./QSIG specific request */ +#define SSEXT_IND 49 /* Supplem.Serv./QSIG specific indication */ +/* reserved commands for the US protocols */ +#define INT_3PTY_NIND 50 /* US specific indication */ +#define INT_CF_NIND 51 /* US specific indication */ +#define INT_3PTY_DROP 52 /* US specific indication */ +#define INT_MOVE_CONF 53 /* US specific indication */ +#define INT_MOVE_RC 54 /* US specific indication */ +#define INT_MOVE_FLIPPED_CONF 55 /* US specific indication */ +#define INT_X5NI_OK 56 /* internal transfer OK indication */ +#define INT_XDMS_START 57 /* internal transfer OK indication */ +#define INT_XDMS_STOP 58 /* internal transfer finish indication */ +#define INT_XDMS_STOP2 59 /* internal transfer send FA */ +#define INT_CUSTCONF_REJ 60 /* internal conference reject */ +#define INT_CUSTXFER 61 /* internal transfer request */ +#define INT_CUSTX_NIND 62 /* internal transfer ack */ +#define INT_CUSTXREJ_NIND 63 /* internal transfer rej */ +#define INT_X5NI_CF_XFER 64 /* internal transfer OK indication */ +#define VSWITCH_REQ 65 /* communication between protocol and */ +#define VSWITCH_IND 66 /* capifunctions for D-CH-switching */ +#define MWI_POLL 67 /* Message Waiting Status Request fkt */ +#define CALL_PEND_NOTIFY 68 /* notify capi to set new listen */ +#define DO_NOTHING 69 /* dont do somethin if you get this */ +#define INT_CT_REJ 70 /* ECT rejected internal command */ +#define CALL_HOLD_COMPLETE 71 /* In NT Mode indicate hold complete */ +#define CALL_RETRIEVE_COMPLETE 72 /* In NT Mode indicate retrieve complete */ +/*------------------------------------------------------------------*/ +/* management service primitives */ +/*------------------------------------------------------------------*/ +#define MAN_READ 2 +#define MAN_WRITE 3 +#define MAN_EXECUTE 4 +#define MAN_EVENT_ON 5 +#define MAN_EVENT_OFF 6 +#define MAN_LOCK 7 +#define MAN_UNLOCK 8 +#define MAN_INFO_IND 2 +#define MAN_EVENT_IND 3 +#define MAN_TRACE_IND 4 +#define MAN_COMBI_IND 9 +#define MAN_ESC 0x80 +/*------------------------------------------------------------------*/ +/* return code coding */ +/*------------------------------------------------------------------*/ +#define UNKNOWN_COMMAND 0x01 /* unknown command */ +#define WRONG_COMMAND 0x02 /* wrong command */ +#define WRONG_ID 0x03 /* unknown task/entity id */ +#define WRONG_CH 0x04 /* wrong task/entity id */ +#define UNKNOWN_IE 0x05 /* unknown information el. */ +#define WRONG_IE 0x06 /* wrong information el. */ +#define OUT_OF_RESOURCES 0x07 /* ISDN-S card out of res. */ +#define ISDN_GUARD_REJ 0x09 /* ISDN-Guard SuppServ rej */ +#define N_FLOW_CONTROL 0x10 /* Flow-Control, retry */ +#define ASSIGN_RC 0xe0 /* ASSIGN acknowledgement */ +#define ASSIGN_OK 0xef /* ASSIGN OK */ +#define OK_FC 0xfc /* Flow-Control RC */ +#define READY_INT 0xfd /* Ready interrupt */ +#define TIMER_INT 0xfe /* timer interrupt */ +#define OK 0xff /* command accepted */ +/*------------------------------------------------------------------*/ +/* information elements */ +/*------------------------------------------------------------------*/ +#define SHIFT 0x90 /* codeset shift */ +#define MORE 0xa0 /* more data */ +#define SDNCMPL 0xa1 /* sending complete */ +#define CL 0xb0 /* congestion level */ + /* codeset 0 */ +#define SMSG 0x00 /* segmented message */ +#define BC 0x04 /* Bearer Capability */ +#define CAU 0x08 /* cause */ +#define CAD 0x0c /* Connected address */ +#define CAI 0x10 /* call identity */ +#define CHI 0x18 /* channel identification */ +#define LLI 0x19 /* logical link id */ +#define CHA 0x1a /* charge advice */ +#define FTY 0x1c /* Facility */ +#define DT 0x29 /* ETSI date/time */ +#define KEY 0x2c /* keypad information element */ +#define UID 0x2d /* User id information element */ +#define DSP 0x28 /* display */ +#define SIG 0x34 /* signalling hardware control */ +#define OAD 0x6c /* origination address */ +#define OSA 0x6d /* origination sub-address */ +#define CPN 0x70 /* called party number */ +#define DSA 0x71 /* destination sub-address */ +#define RDX 0x73 /* redirecting number extended */ +#define RDN 0x74 /* redirecting number */ +#define RIN 0x76 /* redirection number */ +#define IUP 0x76 /* VN6 rerouter->PCS (codeset 6) */ +#define IPU 0x77 /* VN6 PCS->rerouter (codeset 6) */ +#define RI 0x79 /* restart indicator */ +#define MIE 0x7a /* management info element */ +#define LLC 0x7c /* low layer compatibility */ +#define HLC 0x7d /* high layer compatibility */ +#define UUI 0x7e /* user user information */ +#define ESC 0x7f /* escape extension */ +#define DLC 0x20 /* data link layer configuration */ +#define NLC 0x21 /* network layer configuration */ +#define REDIRECT_IE 0x22 /* redirection request/indication data */ +#define REDIRECT_NET_IE 0x23 /* redirection network override data */ + /* codeset 6 */ +#define SIN 0x01 /* service indicator */ +#define CIF 0x02 /* charging information */ +#define DATE 0x03 /* date */ +#define CPS 0x07 /* called party status */ +/*------------------------------------------------------------------*/ +/* ESC information elements */ +/*------------------------------------------------------------------*/ +#define MSGTYPEIE 0x7a /* Messagetype info element */ +#define CRIE 0x7b /* INFO info element */ +#define CODESET6IE 0xec /* Tunnel for Codeset 6 IEs */ +#define VSWITCHIE 0xed /* VSwitch info element */ +#define SSEXTIE 0xee /* Supplem. Service info element */ +#define PROFILEIE 0xef /* Profile info element */ +/*------------------------------------------------------------------*/ +/* TEL_CTRL contents */ +/*------------------------------------------------------------------*/ +#define RING_ON 0x01 +#define RING_OFF 0x02 +#define HANDS_FREE_ON 0x03 +#define HANDS_FREE_OFF 0x04 +#define ON_HOOK 0x80 +#define OFF_HOOK 0x90 +/* operation values used by ETSI supplementary services */ +#define THREE_PTY_BEGIN 0x04 +#define THREE_PTY_END 0x05 +#define ECT_EXECUTE 0x06 +#define ACTIVATION_DIVERSION 0x07 +#define DEACTIVATION_DIVERSION 0x08 +#define CALL_DEFLECTION 0x0D +#define INTERROGATION_DIVERSION 0x0B +#define INTERROGATION_SERV_USR_NR 0x11 +#define ACTIVATION_MWI 0x20 +#define DEACTIVATION_MWI 0x21 +#define MWI_INDICATION 0x22 +#define MWI_RESPONSE 0x23 +#define CONF_BEGIN 0x28 +#define CONF_ADD 0x29 +#define CONF_SPLIT 0x2a +#define CONF_DROP 0x2b +#define CONF_ISOLATE 0x2c +#define CONF_REATTACH 0x2d +#define CONF_PARTYDISC 0x2e +#define CCBS_INFO_RETAIN 0x2f +#define CCBS_ERASECALLLINKAGEID 0x30 +#define CCBS_STOP_ALERTING 0x31 +#define CCBS_REQUEST 0x32 +#define CCBS_DEACTIVATE 0x33 +#define CCBS_INTERROGATE 0x34 +#define CCBS_STATUS 0x35 +#define CCBS_ERASE 0x36 +#define CCBS_B_FREE 0x37 +#define CCNR_INFO_RETAIN 0x38 +#define CCBS_REMOTE_USER_FREE 0x39 +#define CCNR_REQUEST 0x3a +#define CCNR_INTERROGATE 0x3b +#define GET_SUPPORTED_SERVICES 0xff +#define DIVERSION_PROCEDURE_CFU 0x70 +#define DIVERSION_PROCEDURE_CFB 0x71 +#define DIVERSION_PROCEDURE_CFNR 0x72 +#define DIVERSION_DEACTIVATION_CFU 0x80 +#define DIVERSION_DEACTIVATION_CFB 0x81 +#define DIVERSION_DEACTIVATION_CFNR 0x82 +#define DIVERSION_INTERROGATE_NUM 0x11 +#define DIVERSION_INTERROGATE_CFU 0x60 +#define DIVERSION_INTERROGATE_CFB 0x61 +#define DIVERSION_INTERROGATE_CFNR 0x62 +/* Service Masks */ +#define SMASK_HOLD_RETRIEVE 0x00000001 +#define SMASK_TERMINAL_PORTABILITY 0x00000002 +#define SMASK_ECT 0x00000004 +#define SMASK_3PTY 0x00000008 +#define SMASK_CALL_FORWARDING 0x00000010 +#define SMASK_CALL_DEFLECTION 0x00000020 +#define SMASK_MCID 0x00000040 +#define SMASK_CCBS 0x00000080 +#define SMASK_MWI 0x00000100 +#define SMASK_CCNR 0x00000200 +#define SMASK_CONF 0x00000400 +/* ---------------------------------------------- + Types of transfers used to transfer the + information in the 'struct RC->Reserved2[8]' + The information is transferred as 2 dwords + (2 4Byte unsigned values) + First of them is the transfer type. + 2^32-1 possible messages are possible in this way. + The context of the second one had no meaning + ---------------------------------------------- */ +#define DIVA_RC_TYPE_NONE 0x00000000 +#define DIVA_RC_TYPE_REMOVE_COMPLETE 0x00000008 +#define DIVA_RC_TYPE_STREAM_PTR 0x00000009 +#define DIVA_RC_TYPE_CMA_PTR 0x0000000a +#define DIVA_RC_TYPE_OK_FC 0x0000000b +#define DIVA_RC_TYPE_RX_DMA 0x0000000c +/* ------------------------------------------------------ + IO Control codes for IN BAND SIGNALING + ------------------------------------------------------ */ +#define CTRL_L1_SET_SIG_ID 5 +#define CTRL_L1_SET_DAD 6 +#define CTRL_L1_RESOURCES 7 +/* ------------------------------------------------------ */ +/* ------------------------------------------------------ + Layer 2 types + ------------------------------------------------------ */ +#define X75T 1 /* x.75 for ttx */ +#define TRF 2 /* transparent with hdlc framing */ +#define TRF_IN 3 /* transparent with hdlc fr. inc. */ +#define SDLC 4 /* sdlc, sna layer-2 */ +#define X75 5 /* x.75 for btx */ +#define LAPD 6 /* lapd (Q.921) */ +#define X25_L2 7 /* x.25 layer-2 */ +#define V120_L2 8 /* V.120 layer-2 protocol */ +#define V42_IN 9 /* V.42 layer-2 protocol, incomming */ +#define V42 10 /* V.42 layer-2 protocol */ +#define MDM_ATP 11 /* AT Parser built in the L2 */ +#define X75_V42BIS 12 /* x.75 with V.42bis */ +#define RTPL2_IN 13 /* RTP layer-2 protocol, incomming */ +#define RTPL2 14 /* RTP layer-2 protocol */ +#define V120_V42BIS 15 /* V.120 asynchronous mode supporting V.42bis compression */ +#define LISTENER 27 /* Layer 2 to listen line */ +#define MTP2 28 /* MTP2 Layer 2 */ +#define PIAFS_CRC 29 /* PIAFS Layer 2 with CRC calculation at L2 */ +/* ------------------------------------------------------ + PIAFS DLC DEFINITIONS + ------------------------------------------------------ */ +#define PIAFS_64K 0x01 +#define PIAFS_VARIABLE_SPEED 0x02 +#define PIAFS_CHINESE_SPEED 0x04 +#define PIAFS_UDATA_ABILITY_ID 0x80 +#define PIAFS_UDATA_ABILITY_DCDON 0x01 +#define PIAFS_UDATA_ABILITY_DDI 0x80 +/* +DLC of PIAFS : +Byte | 8 7 6 5 4 3 2 1 +-----+-------------------------------------------------------- + 0 | 0 0 1 0 0 0 0 0 Data Link Configuration + 1 | X X X X X X X X Length of IE (at least 15 Bytes) + 2 | 0 0 0 0 0 0 0 0 max. information field, LOW byte (not used, fix 73 Bytes) + 3 | 0 0 0 0 0 0 0 0 max. information field, HIGH byte (not used, fix 73 Bytes) + 4 | 0 0 0 0 0 0 0 0 address A (not used) + 5 | 0 0 0 0 0 0 0 0 address B (not used) + 6 | 0 0 0 0 0 0 0 0 Mode (not used, fix 128) + 7 | 0 0 0 0 0 0 0 0 Window Size (not used, fix 127) + 8 | X X X X X X X X XID Length, Low Byte (at least 7 Bytes) + 9 | X X X X X X X X XID Length, High Byte + 10 | 0 0 0 0 0 C V S PIAFS Protocol Speed configuration -> Note(1) + | S = 0 -> Protocol Speed is 32K + | S = 1 -> Protocol Speed is 64K + | V = 0 -> Protocol Speed is fixed + | V = 1 -> Protocol Speed is variable + | C = 0 -> speed setting according to standard + | C = 1 -> speed setting for chinese implementation + 11 | 0 0 0 0 0 0 R T P0 - V42bis Compression enable/disable, Low Byte + | T = 0 -> Transmit Direction enable + | T = 1 -> Transmit Direction disable + | R = 0 -> Receive Direction enable + | R = 1 -> Receive Direction disable + 13 | 0 0 0 0 0 0 0 0 P0 - V42bis Compression enable/disable, High Byte + 14 | X X X X X X X X P1 - V42bis Dictionary Size, Low Byte + 15 | X X X X X X X X P1 - V42bis Dictionary Size, High Byte + 16 | X X X X X X X X P2 - V42bis String Length, Low Byte + 17 | X X X X X X X X P2 - V42bis String Length, High Byte + 18 | X X X X X X X X PIAFS extension length + 19 | 1 0 0 0 0 0 0 0 PIAFS extension Id (0x80) - UDATA abilities + 20 | U 0 0 0 0 0 0 D UDATA abilities -> Note (2) + | up to now the following Bits are defined: + | D - signal DCD ON + | U - use extensive UDATA control communication + | for DDI test application ++ Note (1): ----------+------+-----------------------------------------+ +| PIAFS Protocol | Bit | | +| Speed configuration | S | Bit 1 - Protocol Speed | +| | | 0 - 32K | +| | | 1 - 64K (default) | +| | V | Bit 2 - Variable Protocol Speed | +| | | 0 - Speed is fix | +| | | 1 - Speed is variable (default) | +| | | OVERWRITES 32k Bit 1 | +| | C | Bit 3 0 - Speed Settings according to | +| | | PIAFS specification | +| | | 1 - Speed setting for chinese | +| | | PIAFS implementation | +| | | Explanation for chinese speed settings: | +| | | if Bit 3 is set the following | +| | | rules apply: | +| | | Bit1=0 Bit2=0: 32k fix | +| | | Bit1=1 Bit2=0: 64k fix | +| | | Bit1=0 Bit2=1: PIAFS is trying | +| | | to negotiate 32k is that is | +| | | not possible it tries to | +| | | negotiate 64k | +| | | Bit1=1 Bit2=1: PIAFS is trying | +| | | to negotiate 64k is that is | +| | | not possible it tries to | +| | | negotiate 32k | ++ Note (2): ----------+------+-----------------------------------------+ +| PIAFS | Bit | this byte defines the usage of UDATA | +| Implementation | | control communication | +| UDATA usage | D | Bit 1 - DCD-ON signalling | +| | | 0 - no DCD-ON is signalled | +| | | (default) | +| | | 1 - DCD-ON will be signalled | +| | U | Bit 8 - DDI test application UDATA | +| | | control communication | +| | | 0 - no UDATA control | +| | | communication (default) | +| | | sets as well the DCD-ON | +| | | signalling | +| | | 1 - UDATA control communication | +| | | ATTENTION: Do not use these | +| | | setting if you | +| | | are not really | +| | | that you need it | +| | | and you know | +| | | exactly what you | +| | | are doing. | +| | | You can easily | +| | | disable any | +| | | data transfer. | ++---------------------+------+-----------------------------------------+ +*/ +/* ------------------------------------------------------ + LISTENER DLC DEFINITIONS + ------------------------------------------------------ */ +#define LISTENER_FEATURE_MASK_CUMMULATIVE 0x0001 +/* ------------------------------------------------------ + LISTENER META-FRAME CODE/PRIMITIVE DEFINITIONS + ------------------------------------------------------ */ +#define META_CODE_LL_UDATA_RX 0x01 +#define META_CODE_LL_UDATA_TX 0x02 +#define META_CODE_LL_DATA_RX 0x03 +#define META_CODE_LL_DATA_TX 0x04 +#define META_CODE_LL_MDATA_RX 0x05 +#define META_CODE_LL_MDATA_TX 0x06 +#define META_CODE_EMPTY 0x10 +#define META_CODE_LOST_FRAMES 0x11 +#define META_FLAG_TRUNCATED 0x0001 +/*------------------------------------------------------------------*/ +/* CAPI-like profile to indicate features on LAW_REQ */ +/*------------------------------------------------------------------*/ +#define GL_INTERNAL_CONTROLLER_SUPPORTED 0x00000001L +#define GL_EXTERNAL_EQUIPMENT_SUPPORTED 0x00000002L +#define GL_HANDSET_SUPPORTED 0x00000004L +#define GL_DTMF_SUPPORTED 0x00000008L +#define GL_SUPPLEMENTARY_SERVICES_SUPPORTED 0x00000010L +#define GL_CHANNEL_ALLOCATION_SUPPORTED 0x00000020L +#define GL_BCHANNEL_OPERATION_SUPPORTED 0x00000040L +#define GL_LINE_INTERCONNECT_SUPPORTED 0x00000080L +#define B1_HDLC_SUPPORTED 0x00000001L +#define B1_TRANSPARENT_SUPPORTED 0x00000002L +#define B1_V110_ASYNC_SUPPORTED 0x00000004L +#define B1_V110_SYNC_SUPPORTED 0x00000008L +#define B1_T30_SUPPORTED 0x00000010L +#define B1_HDLC_INVERTED_SUPPORTED 0x00000020L +#define B1_TRANSPARENT_R_SUPPORTED 0x00000040L +#define B1_MODEM_ALL_NEGOTIATE_SUPPORTED 0x00000080L +#define B1_MODEM_ASYNC_SUPPORTED 0x00000100L +#define B1_MODEM_SYNC_HDLC_SUPPORTED 0x00000200L +#define B2_X75_SUPPORTED 0x00000001L +#define B2_TRANSPARENT_SUPPORTED 0x00000002L +#define B2_SDLC_SUPPORTED 0x00000004L +#define B2_LAPD_SUPPORTED 0x00000008L +#define B2_T30_SUPPORTED 0x00000010L +#define B2_PPP_SUPPORTED 0x00000020L +#define B2_TRANSPARENT_NO_CRC_SUPPORTED 0x00000040L +#define B2_MODEM_EC_COMPRESSION_SUPPORTED 0x00000080L +#define B2_X75_V42BIS_SUPPORTED 0x00000100L +#define B2_V120_ASYNC_SUPPORTED 0x00000200L +#define B2_V120_ASYNC_V42BIS_SUPPORTED 0x00000400L +#define B2_V120_BIT_TRANSPARENT_SUPPORTED 0x00000800L +#define B2_LAPD_FREE_SAPI_SEL_SUPPORTED 0x00001000L +#define B3_TRANSPARENT_SUPPORTED 0x00000001L +#define B3_T90NL_SUPPORTED 0x00000002L +#define B3_ISO8208_SUPPORTED 0x00000004L +#define B3_X25_DCE_SUPPORTED 0x00000008L +#define B3_T30_SUPPORTED 0x00000010L +#define B3_T30_WITH_EXTENSIONS_SUPPORTED 0x00000020L +#define B3_RESERVED_SUPPORTED 0x00000040L +#define B3_MODEM_SUPPORTED 0x00000080L +#define MANUFACTURER_FEATURE_SLAVE_CODEC 0x00000001L +#define MANUFACTURER_FEATURE_FAX_MORE_DOCUMENTS 0x00000002L +#define MANUFACTURER_FEATURE_HARDDTMF 0x00000004L +#define MANUFACTURER_FEATURE_SOFTDTMF_SEND 0x00000008L +#define MANUFACTURER_FEATURE_DTMF_PARAMETERS 0x00000010L +#define MANUFACTURER_FEATURE_SOFTDTMF_RECEIVE 0x00000020L +#define MANUFACTURER_FEATURE_FAX_SUB_SEP_PWD 0x00000040L +#define MANUFACTURER_FEATURE_V18 0x00000080L +#define MANUFACTURER_FEATURE_MIXER_CH_CH 0x00000100L +#define MANUFACTURER_FEATURE_MIXER_CH_PC 0x00000200L +#define MANUFACTURER_FEATURE_MIXER_PC_CH 0x00000400L +#define MANUFACTURER_FEATURE_MIXER_PC_PC 0x00000800L +#define MANUFACTURER_FEATURE_ECHO_CANCELLER 0x00001000L +#define MANUFACTURER_FEATURE_RTP 0x00002000L +#define MANUFACTURER_FEATURE_T38 0x00004000L +#define MANUFACTURER_FEATURE_TRANSP_DELIVERY_CONF 0x00008000L +#define MANUFACTURER_FEATURE_XONOFF_FLOW_CONTROL 0x00010000L +#define MANUFACTURER_FEATURE_OOB_CHANNEL 0x00020000L +#define MANUFACTURER_FEATURE_IN_BAND_CHANNEL 0x00040000L +#define MANUFACTURER_FEATURE_IN_BAND_FEATURE 0x00080000L +#define MANUFACTURER_FEATURE_PIAFS 0x00100000L +#define MANUFACTURER_FEATURE_DTMF_TONE 0x00200000L +#define MANUFACTURER_FEATURE_FAX_PAPER_FORMATS 0x00400000L +#define MANUFACTURER_FEATURE_OK_FC_LABEL 0x00800000L +#define MANUFACTURER_FEATURE_VOWN 0x01000000L +#define MANUFACTURER_FEATURE_XCONNECT 0x02000000L +#define MANUFACTURER_FEATURE_DMACONNECT 0x04000000L +#define MANUFACTURER_FEATURE_AUDIO_TAP 0x08000000L +#define MANUFACTURER_FEATURE_FAX_NONSTANDARD 0x10000000L +#define MANUFACTURER_FEATURE_SS7 0x20000000L +#define MANUFACTURER_FEATURE_MADAPTER 0x40000000L +#define MANUFACTURER_FEATURE_MEASURE 0x80000000L +#define MANUFACTURER_FEATURE2_LISTENING 0x00000001L +#define MANUFACTURER_FEATURE2_SS_DIFFCONTPOSSIBLE 0x00000002L +#define MANUFACTURER_FEATURE2_GENERIC_TONE 0x00000004L +#define MANUFACTURER_FEATURE2_COLOR_FAX 0x00000008L +#define MANUFACTURER_FEATURE2_SS_ECT_DIFFCONTPOSSIBLE 0x00000010L +#define RTP_PRIM_PAYLOAD_PCMU_8000 0 +#define RTP_PRIM_PAYLOAD_1016_8000 1 +#define RTP_PRIM_PAYLOAD_G726_32_8000 2 +#define RTP_PRIM_PAYLOAD_GSM_8000 3 +#define RTP_PRIM_PAYLOAD_G723_8000 4 +#define RTP_PRIM_PAYLOAD_DVI4_8000 5 +#define RTP_PRIM_PAYLOAD_DVI4_16000 6 +#define RTP_PRIM_PAYLOAD_LPC_8000 7 +#define RTP_PRIM_PAYLOAD_PCMA_8000 8 +#define RTP_PRIM_PAYLOAD_G722_16000 9 +#define RTP_PRIM_PAYLOAD_QCELP_8000 12 +#define RTP_PRIM_PAYLOAD_G728_8000 14 +#define RTP_PRIM_PAYLOAD_G729_8000 18 +#define RTP_PRIM_PAYLOAD_GSM_HR_8000 30 +#define RTP_PRIM_PAYLOAD_GSM_EFR_8000 31 +#define RTP_ADD_PAYLOAD_BASE 32 +#define RTP_ADD_PAYLOAD_RED 32 +#define RTP_ADD_PAYLOAD_CN_8000 33 +#define RTP_ADD_PAYLOAD_DTMF 34 +#define RTP_PRIM_PAYLOAD_PCMU_8000_SUPPORTED (1L << RTP_PRIM_PAYLOAD_PCMU_8000) +#define RTP_PRIM_PAYLOAD_1016_8000_SUPPORTED (1L << RTP_PRIM_PAYLOAD_1016_8000) +#define RTP_PRIM_PAYLOAD_G726_32_8000_SUPPORTED (1L << RTP_PRIM_PAYLOAD_G726_32_8000) +#define RTP_PRIM_PAYLOAD_GSM_8000_SUPPORTED (1L << RTP_PRIM_PAYLOAD_GSM_8000) +#define RTP_PRIM_PAYLOAD_G723_8000_SUPPORTED (1L << RTP_PRIM_PAYLOAD_G723_8000) +#define RTP_PRIM_PAYLOAD_DVI4_8000_SUPPORTED (1L << RTP_PRIM_PAYLOAD_DVI4_8000) +#define RTP_PRIM_PAYLOAD_DVI4_16000_SUPPORTED (1L << RTP_PRIM_PAYLOAD_DVI4_16000) +#define RTP_PRIM_PAYLOAD_LPC_8000_SUPPORTED (1L << RTP_PRIM_PAYLOAD_LPC_8000) +#define RTP_PRIM_PAYLOAD_PCMA_8000_SUPPORTED (1L << RTP_PRIM_PAYLOAD_PCMA_8000) +#define RTP_PRIM_PAYLOAD_G722_16000_SUPPORTED (1L << RTP_PRIM_PAYLOAD_G722_16000) +#define RTP_PRIM_PAYLOAD_QCELP_8000_SUPPORTED (1L << RTP_PRIM_PAYLOAD_QCELP_8000) +#define RTP_PRIM_PAYLOAD_G728_8000_SUPPORTED (1L << RTP_PRIM_PAYLOAD_G728_8000) +#define RTP_PRIM_PAYLOAD_G729_8000_SUPPORTED (1L << RTP_PRIM_PAYLOAD_G729_8000) +#define RTP_PRIM_PAYLOAD_GSM_HR_8000_SUPPORTED (1L << RTP_PRIM_PAYLOAD_GSM_HR_8000) +#define RTP_PRIM_PAYLOAD_GSM_EFR_8000_SUPPORTED (1L << RTP_PRIM_PAYLOAD_GSM_EFR_8000) +#define RTP_ADD_PAYLOAD_RED_SUPPORTED (1L << (RTP_ADD_PAYLOAD_RED - RTP_ADD_PAYLOAD_BASE)) +#define RTP_ADD_PAYLOAD_CN_8000_SUPPORTED (1L << (RTP_ADD_PAYLOAD_CN_8000 - RTP_ADD_PAYLOAD_BASE)) +#define RTP_ADD_PAYLOAD_DTMF_SUPPORTED (1L << (RTP_ADD_PAYLOAD_DTMF - RTP_ADD_PAYLOAD_BASE)) +/* virtual switching definitions */ +#define VSJOIN 1 +#define VSTRANSPORT 2 +#define VSGETPARAMS 3 +#define VSCAD 1 +#define VSRXCPNAME 2 +#define VSCALLSTAT 3 +#define VSINVOKEID 4 +#define VSCLMRKS 5 +#define VSTBCTIDENT 6 +#define VSETSILINKID 7 +#define VSSAMECONTROLLER 8 +/* Errorcodes for VSETSILINKID begin */ +#define VSETSILINKIDRRWC 1 +#define VSETSILINKIDREJECT 2 +#define VSETSILINKIDTIMEOUT 3 +#define VSETSILINKIDFAILCOUNT 4 +#define VSETSILINKIDERROR 5 +/* Errorcodes for VSETSILINKID end */ +/* -----------------------------------------------------------** +** The PROTOCOL_FEATURE_STRING in feature.h (included ** +** in prstart.sx and astart.sx) defines capabilities and ** +** features of the actual protocol code. It's used as a bit ** +** mask. ** +** The following Bits are defined: ** +** -----------------------------------------------------------*/ +#define PROTCAP_TELINDUS 0x0001 /* Telindus Variant of protocol code */ +#define PROTCAP_MAN_IF 0x0002 /* Management interface implemented */ +#define PROTCAP_V_42 0x0004 /* V42 implemented */ +#define PROTCAP_V90D 0x0008 /* V.90D (implies up to 384k DSP code) */ +#define PROTCAP_EXTD_FAX 0x0010 /* Extended FAX (ECM, 2D, T6, Polling) */ +#define PROTCAP_EXTD_RXFC 0x0020 /* RxFC (Extd Flow Control), OOB Chnl */ +#define PROTCAP_VOIP 0x0040 /* VoIP (implies up to 512k DSP code) */ +#define PROTCAP_CMA_ALLPR 0x0080 /* CMA support for all NL primitives */ +#define PROTCAP_FREE8 0x0100 /* not used */ +#define PROTCAP_FREE9 0x0200 /* not used */ +#define PROTCAP_FREE10 0x0400 /* not used */ +#define PROTCAP_FREE11 0x0800 /* not used */ +#define PROTCAP_FREE12 0x1000 /* not used */ +#define PROTCAP_FREE13 0x2000 /* not used */ +#define PROTCAP_FREE14 0x4000 /* not used */ +#define PROTCAP_EXTENSION 0x8000 /* used for future extentions */ +/* -----------------------------------------------------------* */ +/* Onhook data transmission ETS30065901 */ +/* Message Type */ +/*#define RESERVED4 0x4*/ +#define CALL_SETUP 0x80 +#define MESSAGE_WAITING_INDICATOR 0x82 +/*#define RESERVED84 0x84*/ +/*#define RESERVED85 0x85*/ +#define ADVICE_OF_CHARGE 0x86 +/*1111 0001 +to +1111 1111 +F1H - Reserved for network operator use +to +FFH*/ +/* Parameter Types */ +#define DATE_AND_TIME 1 +#define CLI_PARAMETER_TYPE 2 +#define CALLED_DIRECTORY_NUMBER_PARAMETER_TYPE 3 +#define REASON_FOR_ABSENCE_OF_CLI_PARAMETER_TYPE 4 +#define NAME_PARAMETER_TYPE 7 +#define REASON_FOR_ABSENCE_OF_CALLING_PARTY_NAME_PARAMETER_TYPE 8 +#define VISUAL_INDICATOR_PARAMETER_TYPE 0xb +#define COMPLEMENTARY_CLI_PARAMETER_TYPE 0x10 +#define CALL_TYPE_PARAMETER_TYPE 0x11 +#define FIRST_CALLED_LINE_DIRECTORY_NUMBER_PARAMETER_TYPE 0x12 +#define NETWORK_MESSAGE_SYSTEM_STATUS_PARAMETER_TYPE 0x13 +#define FORWARDED_CALL_TYPE_PARAMETER_TYPE 0x15 +#define TYPE_OF_CALLING_USER_PARAMETER_TYPE 0x16 +#define REDIRECTING_NUMBER_PARAMETER_TYPE 0x1a +#define EXTENSION_FOR_NETWORK_OPERATOR_USE_PARAMETER_TYPE 0xe0 +/* -----------------------------------------------------------* */ +#else +#endif /* PC_H_INCLUDED } */ diff --git a/drivers/isdn/hardware/eicon/pc_init.h b/drivers/isdn/hardware/eicon/pc_init.h new file mode 100644 index 000000000000..a616fc9d32e9 --- /dev/null +++ b/drivers/isdn/hardware/eicon/pc_init.h @@ -0,0 +1,267 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +#ifndef PC_INIT_H_ +#define PC_INIT_H_ +/*------------------------------------------------------------------*/ +/* + Initialisation parameters for the card + 0x0008 <byte> TEI + 0x0009 <byte> NT2 flag + 0x000a <byte> Default DID length + 0x000b <byte> Disable watchdog flag + 0x000c <byte> Permanent connection flag + 0x000d <byte> Bit 3-8: L1 Hunt Group/Tristate + 0x000d <byte> Bit 1: QSig small CR length if set to 1 + 0x000d <byte> Bit 2: QSig small CHI length if set to 1 + 0x000e <byte> Bit 1-3: Stable L2, 0=OnDemand,1=NoDisc,2=permanent + 0x000e <byte> Bit 4: NT mode + 0x000e <byte> Bit 5: QSig Channel ID format + 0x000e <byte> Bit 6: QSig Call Forwarding Allowed Flag + 0x000e <byte> Bit 7: Disable AutoSPID Flag + 0x000f <byte> No order check flag + 0x0010 <byte> Force companding type:0=default,1=a-law,2=u-law + 0x0012 <byte> Low channel flag + 0x0013 <byte> Protocol version + 0x0014 <byte> CRC4 option:0=default,1=double_frm,2=multi_frm,3=auto + 0x0015 <byte> Bit 0: NoHscx30, Bit 1: Loopback flag, Bit 2: ForceHscx30 + 0x0016 <byte> DSP info + 0x0017-0x0019 Serial number + 0x001a <byte> Card type + 0x0020 <string> OAD 0 + 0x0040 <string> OSA 0 + 0x0060 <string> SPID 0 (if not T.1) + 0x0060 <struct> if T.1: Robbed Bit Configuration + 0x0060 length (8) + 0x0061 RBS Answer Delay + 0x0062 RBS Config Bit 3, 4: + 0 0 -> Wink Start + 1 0 -> Loop Start + 0 1 -> Ground Start + 1 1 -> reserved + Bit 5, 6: + 0 0 -> Pulse Dial -> Rotary + 1 0 -> DTMF + 0 1 -> MF + 1 1 -> reserved + 0x0063 RBS RX Digit Timeout + 0x0064 RBS Bearer Capability + 0x0065-0x0069 RBS Debug Mask + 0x0080 <string> OAD 1 + 0x00a0 <string> OSA 1 + 0x00c0 <string> SPID 1 + 0x00e0 <w-element list> Additional configuration +*/ +#define PCINIT_END_OF_LIST 0x00 +#define PCINIT_MODEM_GUARD_TONE 0x01 +#define PCINIT_MODEM_MIN_SPEED 0x02 +#define PCINIT_MODEM_MAX_SPEED 0x03 +#define PCINIT_MODEM_PROTOCOL_OPTIONS 0x04 +#define PCINIT_FAX_OPTIONS 0x05 +#define PCINIT_FAX_MAX_SPEED 0x06 +#define PCINIT_MODEM_OPTIONS 0x07 +#define PCINIT_MODEM_NEGOTIATION_MODE 0x08 +#define PCINIT_MODEM_MODULATIONS_MASK 0x09 +#define PCINIT_MODEM_TRANSMIT_LEVEL 0x0a +#define PCINIT_FAX_DISABLED_RESOLUTIONS 0x0b +#define PCINIT_FAX_MAX_RECORDING_WIDTH 0x0c +#define PCINIT_FAX_MAX_RECORDING_LENGTH 0x0d +#define PCINIT_FAX_MIN_SCANLINE_TIME 0x0e +#define PCINIT_US_EKTS_CACH_HANDLES 0x0f +#define PCINIT_US_EKTS_BEGIN_CONF 0x10 +#define PCINIT_US_EKTS_DROP_CONF 0x11 +#define PCINIT_US_EKTS_CALL_TRANSFER 0x12 +#define PCINIT_RINGERTONE_OPTION 0x13 +#define PCINIT_CARD_ADDRESS 0x14 +#define PCINIT_FPGA_FEATURES 0x15 +#define PCINIT_US_EKTS_MWI 0x16 +#define PCINIT_MODEM_SPEAKER_CONTROL 0x17 +#define PCINIT_MODEM_SPEAKER_VOLUME 0x18 +#define PCINIT_MODEM_CARRIER_WAIT_TIME 0x19 +#define PCINIT_MODEM_CARRIER_LOSS_TIME 0x1a +#define PCINIT_UNCHAN_B_MASK 0x1b +#define PCINIT_PART68_LIMITER 0x1c +#define PCINIT_XDI_FEATURES 0x1d +#define PCINIT_QSIG_DIALECT 0x1e +#define PCINIT_DISABLE_AUTOSPID_FLAG 0x1f +#define PCINIT_FORCE_VOICE_MAIL_ALERT 0x20 +#define PCINIT_PIAFS_TURNAROUND_FRAMES 0x21 +#define PCINIT_L2_COUNT 0x22 +#define PCINIT_QSIG_FEATURES 0x23 +#define PCINIT_NO_SIGNALLING 0x24 +#define PCINIT_CARD_SN 0x25 +#define PCINIT_CARD_PORT 0x26 +#define PCINIT_ALERTTO 0x27 +#define PCINIT_MODEM_EYE_SETUP 0x28 +#define PCINIT_FAX_V34_OPTIONS 0x29 +/*------------------------------------------------------------------*/ +#define PCINIT_MODEM_GUARD_TONE_NONE 0x00 +#define PCINIT_MODEM_GUARD_TONE_550HZ 0x01 +#define PCINIT_MODEM_GUARD_TONE_1800HZ 0x02 +#define PCINIT_MODEM_GUARD_TONE_CHOICES 0x03 +#define PCINIT_MODEMPROT_DISABLE_V42_V42BIS 0x0001 +#define PCINIT_MODEMPROT_DISABLE_MNP_MNP5 0x0002 +#define PCINIT_MODEMPROT_REQUIRE_PROTOCOL 0x0004 +#define PCINIT_MODEMPROT_DISABLE_V42_DETECT 0x0008 +#define PCINIT_MODEMPROT_DISABLE_COMPRESSION 0x0010 +#define PCINIT_MODEMPROT_REQUIRE_PROTOCOL_V34UP 0x0020 +#define PCINIT_MODEMPROT_NO_PROTOCOL_IF_1200 0x0100 +#define PCINIT_MODEMPROT_BUFFER_IN_V42_DETECT 0x0200 +#define PCINIT_MODEMPROT_DISABLE_V42_SREJ 0x0400 +#define PCINIT_MODEMPROT_DISABLE_MNP3 0x0800 +#define PCINIT_MODEMPROT_DISABLE_MNP4 0x1000 +#define PCINIT_MODEMPROT_DISABLE_MNP10 0x2000 +#define PCINIT_MODEMPROT_NO_PROTOCOL_IF_V22BIS 0x4000 +#define PCINIT_MODEMPROT_NO_PROTOCOL_IF_V32BIS 0x8000 +#define PCINIT_MODEMCONFIG_LEASED_LINE_MODE 0x00000001L +#define PCINIT_MODEMCONFIG_4_WIRE_OPERATION 0x00000002L +#define PCINIT_MODEMCONFIG_DISABLE_BUSY_DETECT 0x00000004L +#define PCINIT_MODEMCONFIG_DISABLE_CALLING_TONE 0x00000008L +#define PCINIT_MODEMCONFIG_DISABLE_ANSWER_TONE 0x00000010L +#define PCINIT_MODEMCONFIG_ENABLE_DIAL_TONE_DET 0x00000020L +#define PCINIT_MODEMCONFIG_USE_POTS_INTERFACE 0x00000040L +#define PCINIT_MODEMCONFIG_FORCE_RAY_TAYLOR_FAX 0x00000080L +#define PCINIT_MODEMCONFIG_DISABLE_RETRAIN 0x00000100L +#define PCINIT_MODEMCONFIG_DISABLE_STEPDOWN 0x00000200L +#define PCINIT_MODEMCONFIG_DISABLE_SPLIT_SPEED 0x00000400L +#define PCINIT_MODEMCONFIG_DISABLE_TRELLIS 0x00000800L +#define PCINIT_MODEMCONFIG_ALLOW_RDL_TEST_LOOP 0x00001000L +#define PCINIT_MODEMCONFIG_DISABLE_STEPUP 0x00002000L +#define PCINIT_MODEMCONFIG_DISABLE_FLUSH_TIMER 0x00004000L +#define PCINIT_MODEMCONFIG_REVERSE_DIRECTION 0x00008000L +#define PCINIT_MODEMCONFIG_DISABLE_TX_REDUCTION 0x00010000L +#define PCINIT_MODEMCONFIG_DISABLE_PRECODING 0x00020000L +#define PCINIT_MODEMCONFIG_DISABLE_PREEMPHASIS 0x00040000L +#define PCINIT_MODEMCONFIG_DISABLE_SHAPING 0x00080000L +#define PCINIT_MODEMCONFIG_DISABLE_NONLINEAR_EN 0x00100000L +#define PCINIT_MODEMCONFIG_DISABLE_MANUALREDUCT 0x00200000L +#define PCINIT_MODEMCONFIG_DISABLE_16_POINT_TRN 0x00400000L +#define PCINIT_MODEMCONFIG_DISABLE_2400_SYMBOLS 0x01000000L +#define PCINIT_MODEMCONFIG_DISABLE_2743_SYMBOLS 0x02000000L +#define PCINIT_MODEMCONFIG_DISABLE_2800_SYMBOLS 0x04000000L +#define PCINIT_MODEMCONFIG_DISABLE_3000_SYMBOLS 0x08000000L +#define PCINIT_MODEMCONFIG_DISABLE_3200_SYMBOLS 0x10000000L +#define PCINIT_MODEMCONFIG_DISABLE_3429_SYMBOLS 0x20000000L +#define PCINIT_MODEM_NEGOTIATE_HIGHEST 0x00 +#define PCINIT_MODEM_NEGOTIATE_DISABLED 0x01 +#define PCINIT_MODEM_NEGOTIATE_IN_CLASS 0x02 +#define PCINIT_MODEM_NEGOTIATE_V100 0x03 +#define PCINIT_MODEM_NEGOTIATE_V8 0x04 +#define PCINIT_MODEM_NEGOTIATE_V8BIS 0x05 +#define PCINIT_MODEM_NEGOTIATE_CHOICES 0x06 +#define PCINIT_MODEMMODULATION_DISABLE_V21 0x00000001L +#define PCINIT_MODEMMODULATION_DISABLE_V23 0x00000002L +#define PCINIT_MODEMMODULATION_DISABLE_V22 0x00000004L +#define PCINIT_MODEMMODULATION_DISABLE_V22BIS 0x00000008L +#define PCINIT_MODEMMODULATION_DISABLE_V32 0x00000010L +#define PCINIT_MODEMMODULATION_DISABLE_V32BIS 0x00000020L +#define PCINIT_MODEMMODULATION_DISABLE_V34 0x00000040L +#define PCINIT_MODEMMODULATION_DISABLE_V90 0x00000080L +#define PCINIT_MODEMMODULATION_DISABLE_BELL103 0x00000100L +#define PCINIT_MODEMMODULATION_DISABLE_BELL212A 0x00000200L +#define PCINIT_MODEMMODULATION_DISABLE_VFC 0x00000400L +#define PCINIT_MODEMMODULATION_DISABLE_K56FLEX 0x00000800L +#define PCINIT_MODEMMODULATION_DISABLE_X2 0x00001000L +#define PCINIT_MODEMMODULATION_ENABLE_V29FDX 0x00010000L +#define PCINIT_MODEMMODULATION_ENABLE_V33 0x00020000L +#define PCINIT_MODEMMODULATION_ENABLE_V90A 0x00040000L +#define PCINIT_MODEM_TRANSMIT_LEVEL_CHOICES 0x10 +#define PCINIT_MODEM_SPEAKER_OFF 0x00 +#define PCINIT_MODEM_SPEAKER_DURING_TRAIN 0x01 +#define PCINIT_MODEM_SPEAKER_TIL_CONNECT 0x02 +#define PCINIT_MODEM_SPEAKER_ALWAYS_ON 0x03 +#define PCINIT_MODEM_SPEAKER_CHOICES 0x04 +#define PCINIT_MODEM_SPEAKER_VOLUME_MIN 0x00 +#define PCINIT_MODEM_SPEAKER_VOLUME_LOW 0x01 +#define PCINIT_MODEM_SPEAKER_VOLUME_HIGH 0x02 +#define PCINIT_MODEM_SPEAKER_VOLUME_MAX 0x03 +#define PCINIT_MODEM_SPEAKER_VOLUME_CHOICES 0x04 +/*------------------------------------------------------------------*/ +#define PCINIT_FAXCONFIG_DISABLE_FINE 0x0001 +#define PCINIT_FAXCONFIG_DISABLE_ECM 0x0002 +#define PCINIT_FAXCONFIG_ECM_64_BYTES 0x0004 +#define PCINIT_FAXCONFIG_DISABLE_2D_CODING 0x0008 +#define PCINIT_FAXCONFIG_DISABLE_T6_CODING 0x0010 +#define PCINIT_FAXCONFIG_DISABLE_UNCOMPR 0x0020 +#define PCINIT_FAXCONFIG_REFUSE_POLLING 0x0040 +#define PCINIT_FAXCONFIG_HIDE_TOTAL_PAGES 0x0080 +#define PCINIT_FAXCONFIG_HIDE_ALL_HEADLINE 0x0100 +#define PCINIT_FAXCONFIG_HIDE_PAGE_INFO 0x0180 +#define PCINIT_FAXCONFIG_HEADLINE_OPTIONS_MASK 0x0180 +#define PCINIT_FAXCONFIG_DISABLE_FEATURE_FALLBACK 0x0200 +#define PCINIT_FAXCONFIG_V34FAX_CONTROL_RATE_1200 0x0800 +#define PCINIT_FAXCONFIG_DISABLE_V34FAX 0x1000 +#define PCINIT_FAXCONFIG_DISABLE_R8_0770_OR_200 0x01 +#define PCINIT_FAXCONFIG_DISABLE_R8_1540 0x02 +#define PCINIT_FAXCONFIG_DISABLE_R16_1540_OR_400 0x04 +#define PCINIT_FAXCONFIG_DISABLE_R4_0385_OR_100 0x08 +#define PCINIT_FAXCONFIG_DISABLE_300_300 0x10 +#define PCINIT_FAXCONFIG_DISABLE_INCH_BASED 0x40 +#define PCINIT_FAXCONFIG_DISABLE_METRIC_BASED 0x80 +#define PCINIT_FAXCONFIG_REC_WIDTH_ISO_A3 0 +#define PCINIT_FAXCONFIG_REC_WIDTH_ISO_B4 1 +#define PCINIT_FAXCONFIG_REC_WIDTH_ISO_A4 2 +#define PCINIT_FAXCONFIG_REC_WIDTH_COUNT 3 +#define PCINIT_FAXCONFIG_REC_LENGTH_UNLIMITED 0 +#define PCINIT_FAXCONFIG_REC_LENGTH_ISO_B4 1 +#define PCINIT_FAXCONFIG_REC_LENGTH_ISO_A4 2 +#define PCINIT_FAXCONFIG_REC_LENGTH_COUNT 3 +#define PCINIT_FAXCONFIG_SCANLINE_TIME_00_00_00 0 +#define PCINIT_FAXCONFIG_SCANLINE_TIME_05_05_05 1 +#define PCINIT_FAXCONFIG_SCANLINE_TIME_10_05_05 2 +#define PCINIT_FAXCONFIG_SCANLINE_TIME_10_10_10 3 +#define PCINIT_FAXCONFIG_SCANLINE_TIME_20_10_10 4 +#define PCINIT_FAXCONFIG_SCANLINE_TIME_20_20_20 5 +#define PCINIT_FAXCONFIG_SCANLINE_TIME_40_20_20 6 +#define PCINIT_FAXCONFIG_SCANLINE_TIME_40_40_40 7 +#define PCINIT_FAXCONFIG_SCANLINE_TIME_RES_8 8 +#define PCINIT_FAXCONFIG_SCANLINE_TIME_RES_9 9 +#define PCINIT_FAXCONFIG_SCANLINE_TIME_RES_10 10 +#define PCINIT_FAXCONFIG_SCANLINE_TIME_10_10_05 11 +#define PCINIT_FAXCONFIG_SCANLINE_TIME_20_10_05 12 +#define PCINIT_FAXCONFIG_SCANLINE_TIME_20_20_10 13 +#define PCINIT_FAXCONFIG_SCANLINE_TIME_40_20_10 14 +#define PCINIT_FAXCONFIG_SCANLINE_TIME_40_40_20 15 +#define PCINIT_FAXCONFIG_SCANLINE_TIME_COUNT 16 +#define PCINIT_FAXCONFIG_DISABLE_TX_REDUCTION 0x00010000L +#define PCINIT_FAXCONFIG_DISABLE_PRECODING 0x00020000L +#define PCINIT_FAXCONFIG_DISABLE_PREEMPHASIS 0x00040000L +#define PCINIT_FAXCONFIG_DISABLE_SHAPING 0x00080000L +#define PCINIT_FAXCONFIG_DISABLE_NONLINEAR_EN 0x00100000L +#define PCINIT_FAXCONFIG_DISABLE_MANUALREDUCT 0x00200000L +#define PCINIT_FAXCONFIG_DISABLE_16_POINT_TRN 0x00400000L +#define PCINIT_FAXCONFIG_DISABLE_2400_SYMBOLS 0x01000000L +#define PCINIT_FAXCONFIG_DISABLE_2743_SYMBOLS 0x02000000L +#define PCINIT_FAXCONFIG_DISABLE_2800_SYMBOLS 0x04000000L +#define PCINIT_FAXCONFIG_DISABLE_3000_SYMBOLS 0x08000000L +#define PCINIT_FAXCONFIG_DISABLE_3200_SYMBOLS 0x10000000L +#define PCINIT_FAXCONFIG_DISABLE_3429_SYMBOLS 0x20000000L +/*--------------------------------------------------------------------------*/ +#define PCINIT_XDI_CMA_FOR_ALL_NL_PRIMITIVES 0x01 +/*--------------------------------------------------------------------------*/ +#define PCINIT_FPGA_PLX_ACCESS_SUPPORTED 0x01 +/*--------------------------------------------------------------------------*/ +#endif +/*--------------------------------------------------------------------------*/ diff --git a/drivers/isdn/hardware/eicon/pc_maint.h b/drivers/isdn/hardware/eicon/pc_maint.h new file mode 100644 index 000000000000..352ab8dafb22 --- /dev/null +++ b/drivers/isdn/hardware/eicon/pc_maint.h @@ -0,0 +1,160 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +#ifdef PLATFORM_GT_32BIT +/* #define POINTER_32BIT byte * __ptr32 */ +#define POINTER_32BIT dword +#else +#define POINTER_32BIT byte * +#endif +#if !defined(MIPS_SCOM) +#define BUFFER_SZ 48 +#define MAINT_OFFS 0x380 +#else +#define BUFFER_SZ 128 +#if defined(PRI) +#define MAINT_OFFS 0xef00 +#else +#define MAINT_OFFS 0xff00 +#endif +#endif +#define MIPS_BUFFER_SZ 128 +#if defined(PRI) +#define MIPS_MAINT_OFFS 0xef00 +#else +#define MIPS_MAINT_OFFS 0xff00 +#endif +#define LOG 1 +#define MEMR 2 +#define MEMW 3 +#define IOR 4 +#define IOW 5 +#define B1TEST 6 +#define B2TEST 7 +#define BTESTOFF 8 +#define DSIG_STATS 9 +#define B_CH_STATS 10 +#define D_CH_STATS 11 +#define BL1_STATS 12 +#define BL1_STATS_C 13 +#define GET_VERSION 14 +#define OS_STATS 15 +#define XLOG_SET_MASK 16 +#define XLOG_GET_MASK 17 +#define DSP_READ 20 +#define DSP_WRITE 21 +#define OK 0xff +#define MORE_EVENTS 0xfe +#define NO_EVENT 1 +struct DSigStruc +{ + byte Id; + byte u; + byte listen; + byte active; + byte sin[3]; + byte bc[6]; + byte llc[6]; + byte hlc[6]; + byte oad[20]; +}; +struct BL1Struc { + dword cx_b1; + dword cx_b2; + dword cr_b1; + dword cr_b2; + dword px_b1; + dword px_b2; + dword pr_b1; + dword pr_b2; + word er_b1; + word er_b2; +}; +struct L2Struc { + dword XTotal; + dword RTotal; + word XError; + word RError; +}; +struct OSStruc { + dword free_n; +}; +typedef union +{ + struct DSigStruc DSigStats; + struct BL1Struc BL1Stats; + struct L2Struc L2Stats; + struct OSStruc OSStats; + byte b[BUFFER_SZ]; + word w[BUFFER_SZ>>1]; + word l[BUFFER_SZ>>2]; /* word is wrong, do not use! Use 'd' instead. */ + dword d[BUFFER_SZ>>2]; +} BUFFER; +typedef union +{ + struct DSigStruc DSigStats; + struct BL1Struc BL1Stats; + struct L2Struc L2Stats; + struct OSStruc OSStats; + byte b[MIPS_BUFFER_SZ]; + word w[MIPS_BUFFER_SZ>>1]; + word l[BUFFER_SZ>>2]; /* word is wrong, do not use! Use 'd' instead. */ + dword d[MIPS_BUFFER_SZ>>2]; +} MIPS_BUFFER; +#if !defined(MIPS_SCOM) +struct pc_maint +{ + byte req; + byte rc; + POINTER_32BIT mem; + short length; + word port; + byte fill[6]; + BUFFER data; +}; +#else +struct pc_maint +{ + byte req; + byte rc; + byte reserved[2]; /* R3000 alignment ... */ + POINTER_32BIT mem; + short length; + word port; + byte fill[4]; /* data at offset 16 */ + BUFFER data; +}; +#endif +struct mi_pc_maint +{ + byte req; + byte rc; + byte reserved[2]; /* R3000 alignment ... */ + POINTER_32BIT mem; + short length; + word port; + byte fill[4]; /* data at offset 16 */ + MIPS_BUFFER data; +}; diff --git a/drivers/isdn/hardware/eicon/pkmaint.h b/drivers/isdn/hardware/eicon/pkmaint.h new file mode 100644 index 000000000000..722f85fe42f6 --- /dev/null +++ b/drivers/isdn/hardware/eicon/pkmaint.h @@ -0,0 +1,44 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +#ifndef __DIVA_XDI_OS_DEPENDENT_PACK_MAIN_ON_BYTE_INC__ +#define __DIVA_XDI_OS_DEPENDENT_PACK_MAIN_ON_BYTE_INC__ + + +/* + Only one purpose of this compiler dependent file to pack + structures, described in pc_maint.h so that no padding + will be included. + + With microsoft compile it is done by "pshpack1.h" and + after is restored by "poppack.h" + */ + + +#include "pc_maint.h" + + +#endif + diff --git a/drivers/isdn/hardware/eicon/platform.h b/drivers/isdn/hardware/eicon/platform.h new file mode 100644 index 000000000000..12b8ff29e976 --- /dev/null +++ b/drivers/isdn/hardware/eicon/platform.h @@ -0,0 +1,394 @@ +/* $Id: platform.h,v 1.37.4.6 2005/01/31 12:22:20 armin Exp $ + * + * platform.h + * + * + * Copyright 2000-2003 by Armin Schindler (mac@melware.de) + * Copyright 2000 Eicon Networks + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + */ + + +#ifndef __PLATFORM_H__ +#define __PLATFORM_H__ + +#if !defined(DIVA_BUILD) +#define DIVA_BUILD "local" +#endif + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/skbuff.h> +#include <linux/vmalloc.h> +#include <linux/proc_fs.h> +#include <linux/interrupt.h> +#include <linux/smp_lock.h> +#include <linux/delay.h> +#include <linux/list.h> +#include <asm/types.h> +#include <asm/io.h> + +#include "cardtype.h" + +/* activate debuglib for modules only */ +#ifndef MODULE +#define DIVA_NO_DEBUGLIB +#endif + +#define DIVA_INIT_FUNCTION __init +#define DIVA_EXIT_FUNCTION __exit + +#define DIVA_USER_MODE_CARD_CONFIG 1 +#define USE_EXTENDED_DEBUGS 1 + +#define MAX_ADAPTER 32 + +#define DIVA_ISTREAM 1 + +#define MEMORY_SPACE_TYPE 0 +#define PORT_SPACE_TYPE 1 + + +#include <linux/string.h> + +#ifndef byte +#define byte u8 +#endif + +#ifndef word +#define word u16 +#endif + +#ifndef dword +#define dword u32 +#endif + +#ifndef qword +#define qword u64 +#endif + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef NULL +#define NULL ((void *) 0) +#endif + +#ifndef MIN +#define MIN(a,b) ((a)>(b) ? (b) : (a)) +#endif + +#ifndef MAX +#define MAX(a,b) ((a)>(b) ? (a) : (b)) +#endif + +#ifndef far +#define far +#endif + +#ifndef _pascal +#define _pascal +#endif + +#ifndef _loadds +#define _loadds +#endif + +#ifndef _cdecl +#define _cdecl +#endif + +#define MEM_TYPE_RAM 0 +#define MEM_TYPE_PORT 1 +#define MEM_TYPE_PROM 2 +#define MEM_TYPE_CTLREG 3 +#define MEM_TYPE_RESET 4 +#define MEM_TYPE_CFG 5 +#define MEM_TYPE_ADDRESS 6 +#define MEM_TYPE_CONFIG 7 +#define MEM_TYPE_CONTROL 8 + +#define MAX_MEM_TYPE 10 + +#define DIVA_OS_MEM_ATTACH_RAM(a) ((a)->ram) +#define DIVA_OS_MEM_ATTACH_PORT(a) ((a)->port) +#define DIVA_OS_MEM_ATTACH_PROM(a) ((a)->prom) +#define DIVA_OS_MEM_ATTACH_CTLREG(a) ((a)->ctlReg) +#define DIVA_OS_MEM_ATTACH_RESET(a) ((a)->reset) +#define DIVA_OS_MEM_ATTACH_CFG(a) ((a)->cfg) +#define DIVA_OS_MEM_ATTACH_ADDRESS(a) ((a)->Address) +#define DIVA_OS_MEM_ATTACH_CONFIG(a) ((a)->Config) +#define DIVA_OS_MEM_ATTACH_CONTROL(a) ((a)->Control) + +#define DIVA_OS_MEM_DETACH_RAM(a, x) do { } while(0) +#define DIVA_OS_MEM_DETACH_PORT(a, x) do { } while(0) +#define DIVA_OS_MEM_DETACH_PROM(a, x) do { } while(0) +#define DIVA_OS_MEM_DETACH_CTLREG(a, x) do { } while(0) +#define DIVA_OS_MEM_DETACH_RESET(a, x) do { } while(0) +#define DIVA_OS_MEM_DETACH_CFG(a, x) do { } while(0) +#define DIVA_OS_MEM_DETACH_ADDRESS(a, x) do { } while(0) +#define DIVA_OS_MEM_DETACH_CONFIG(a, x) do { } while(0) +#define DIVA_OS_MEM_DETACH_CONTROL(a, x) do { } while(0) + +#if !defined(DIM) +#define DIM(array) (sizeof (array)/sizeof ((array)[0])) +#endif + +#define DIVA_INVALID_FILE_HANDLE ((dword)(-1)) + +#define DIVAS_CONTAINING_RECORD(address, type, field) \ + ((type *)((char*)(address) - (char*)(&((type *)0)->field))) + +extern int sprintf(char *, const char*, ...); + +typedef void* LIST_ENTRY; + +typedef char DEVICE_NAME[64]; +typedef struct _ISDN_ADAPTER ISDN_ADAPTER; +typedef struct _ISDN_ADAPTER* PISDN_ADAPTER; + +typedef void (* DIVA_DI_PRINTF) (unsigned char *, ...); +#include "debuglib.h" + +#define dtrc(p) DBG_PRV0(p) +#define dbug(a,p) DBG_PRV1(p) + + +typedef struct e_info_s E_INFO ; + +typedef char diva_os_dependent_devica_name_t[64]; +typedef void* PDEVICE_OBJECT; + +struct _diva_os_soft_isr; +struct _diva_os_timer; +struct _ISDN_ADAPTER; + +void diva_log_info(unsigned char *, ...); + +/* +** XDI DIDD Interface +*/ +void diva_xdi_didd_register_adapter (int card); +void diva_xdi_didd_remove_adapter (int card); + +/* +** memory allocation +*/ +static __inline__ void* diva_os_malloc (unsigned long flags, unsigned long size) +{ + void *ret = NULL; + + if (size) { + ret = (void *) vmalloc((unsigned int) size); + } + return (ret); +} +static __inline__ void diva_os_free (unsigned long flags, void* ptr) +{ + vfree(ptr); +} + +/* +** use skbuffs for message buffer +*/ +typedef struct sk_buff diva_os_message_buffer_s; +diva_os_message_buffer_s *diva_os_alloc_message_buffer(unsigned long size, void **data_buf); +void diva_os_free_message_buffer(diva_os_message_buffer_s *dmb); +#define DIVA_MESSAGE_BUFFER_LEN(x) x->len +#define DIVA_MESSAGE_BUFFER_DATA(x) x->data + +/* +** mSeconds waiting +*/ +static __inline__ void diva_os_sleep(dword mSec) +{ + msleep(mSec); +} +static __inline__ void diva_os_wait(dword mSec) +{ + mdelay(mSec); +} + +/* +** PCI Configuration space access +*/ +void PCIwrite (byte bus, byte func, int offset, void* data, int length, void* pci_dev_handle); +void PCIread (byte bus, byte func, int offset, void* data, int length, void* pci_dev_handle); + +/* +** I/O Port utilities +*/ +int diva_os_register_io_port (void *adapter, int register, unsigned long port, + unsigned long length, const char* name, int id); +/* +** I/O port access abstraction +*/ +byte inpp (void __iomem *); +word inppw (void __iomem *); +void inppw_buffer (void __iomem *, void*, int); +void outppw (void __iomem *, word); +void outppw_buffer (void __iomem * , void*, int); +void outpp (void __iomem *, word); + +/* +** IRQ +*/ +typedef struct _diva_os_adapter_irq_info { + byte irq_nr; + int registered; + char irq_name[24]; +} diva_os_adapter_irq_info_t; +int diva_os_register_irq (void* context, byte irq, const char* name); +void diva_os_remove_irq (void* context, byte irq); + +#define diva_os_in_irq() in_irq() + +/* +** Spin Lock framework +*/ +typedef long diva_os_spin_lock_magic_t; +typedef spinlock_t diva_os_spin_lock_t; +static __inline__ int diva_os_initialize_spin_lock (spinlock_t *lock, void * unused) { \ + spin_lock_init (lock); return(0); } +static __inline__ void diva_os_enter_spin_lock (diva_os_spin_lock_t* a, \ + diva_os_spin_lock_magic_t* old_irql, \ + void* dbg) { spin_lock_bh(a); } +static __inline__ void diva_os_leave_spin_lock (diva_os_spin_lock_t* a, \ + diva_os_spin_lock_magic_t* old_irql, \ + void* dbg) { spin_unlock_bh(a); } + +#define diva_os_destroy_spin_lock(a,b) do { } while(0) + +/* +** Deffered processing framework +*/ +typedef int (*diva_os_isr_callback_t)(struct _ISDN_ADAPTER*); +typedef void (*diva_os_soft_isr_callback_t)(struct _diva_os_soft_isr* psoft_isr, void* context); + +typedef struct _diva_os_soft_isr { + void* object; + diva_os_soft_isr_callback_t callback; + void* callback_context; + char dpc_thread_name[24]; +} diva_os_soft_isr_t; + +int diva_os_initialize_soft_isr (diva_os_soft_isr_t* psoft_isr, diva_os_soft_isr_callback_t callback, void* callback_context); +int diva_os_schedule_soft_isr (diva_os_soft_isr_t* psoft_isr); +int diva_os_cancel_soft_isr (diva_os_soft_isr_t* psoft_isr); +void diva_os_remove_soft_isr (diva_os_soft_isr_t* psoft_isr); + +/* + Get time service + */ +void diva_os_get_time (dword* sec, dword* usec); + +/* +** atomic operation, fake because we use threads +*/ +typedef int diva_os_atomic_t; +static diva_os_atomic_t __inline__ +diva_os_atomic_increment(diva_os_atomic_t* pv) +{ + *pv += 1; + return (*pv); +} +static diva_os_atomic_t __inline__ +diva_os_atomic_decrement(diva_os_atomic_t* pv) +{ + *pv -= 1; + return (*pv); +} + +/* +** CAPI SECTION +*/ +#define NO_CORNETN +#define IMPLEMENT_DTMF 1 +#define IMPLEMENT_ECHO_CANCELLER 1 +#define IMPLEMENT_RTP 1 +#define IMPLEMENT_T38 1 +#define IMPLEMENT_FAX_SUB_SEP_PWD 1 +#define IMPLEMENT_V18 1 +#define IMPLEMENT_DTMF_TONE 1 +#define IMPLEMENT_PIAFS 1 +#define IMPLEMENT_FAX_PAPER_FORMATS 1 +#define IMPLEMENT_VOWN 1 +#define IMPLEMENT_CAPIDTMF 1 +#define IMPLEMENT_FAX_NONSTANDARD 1 +#define VSWITCH_SUPPORT 1 + +#define IMPLEMENT_MARKED_OK_AFTER_FC 1 + +#define DIVA_IDI_RX_DMA 1 + +/* +** endian macros +** +** If only... In some cases we did use them for endianness conversion; +** unfortunately, other uses were real iomem accesses. +*/ +#define READ_BYTE(addr) readb(addr) +#define READ_WORD(addr) readw(addr) +#define READ_DWORD(addr) readl(addr) + +#define WRITE_BYTE(addr,v) writeb(v,addr) +#define WRITE_WORD(addr,v) writew(v,addr) +#define WRITE_DWORD(addr,v) writel(v,addr) + +static inline __u16 GET_WORD(void *addr) +{ + return le16_to_cpu(*(__le16 *)addr); +} +static inline __u32 GET_DWORD(void *addr) +{ + return le32_to_cpu(*(__le32 *)addr); +} +static inline void PUT_WORD(void *addr, __u16 v) +{ + *(__le16 *)addr = cpu_to_le16(v); +} +static inline void PUT_DWORD(void *addr, __u32 v) +{ + *(__le32 *)addr = cpu_to_le32(v); +} + +/* +** 32/64 bit macors +*/ +#ifdef BITS_PER_LONG + #if BITS_PER_LONG > 32 + #define PLATFORM_GT_32BIT + #define ULongToPtr(x) (void *)(unsigned long)(x) + #endif +#endif + +/* +** undef os definitions of macros we use +*/ +#undef ID_MASK +#undef N_DATA +#undef ADDR + +/* +** dump file +*/ +#define diva_os_dump_file_t char +#define diva_os_board_trace_t char +#define diva_os_dump_file(__x__) do { } while(0) + +/* +** size of internal arrays +*/ +#define MAX_DESCRIPTORS 64 + +#endif /* __PLATFORM_H__ */ diff --git a/drivers/isdn/hardware/eicon/pr_pc.h b/drivers/isdn/hardware/eicon/pr_pc.h new file mode 100644 index 000000000000..bf49a5af567f --- /dev/null +++ b/drivers/isdn/hardware/eicon/pr_pc.h @@ -0,0 +1,76 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +struct pr_ram { + word NextReq; /* pointer to next Req Buffer */ + word NextRc; /* pointer to next Rc Buffer */ + word NextInd; /* pointer to next Ind Buffer */ + byte ReqInput; /* number of Req Buffers sent */ + byte ReqOutput; /* number of Req Buffers returned */ + byte ReqReserved; /* number of Req Buffers reserved */ + byte Int; /* ISDN-P interrupt */ + byte XLock; /* Lock field for arbitration */ + byte RcOutput; /* number of Rc buffers received */ + byte IndOutput; /* number of Ind buffers received */ + byte IMask; /* Interrupt Mask Flag */ + byte Reserved1[2]; /* reserved field, do not use */ + byte ReadyInt; /* request field for ready interrupt */ + byte Reserved2[12]; /* reserved field, do not use */ + byte InterfaceType; /* interface type 1=16K interface */ + word Signature; /* ISDN-P initialized indication */ + byte B[1]; /* buffer space for Req,Ind and Rc */ +}; +typedef struct { + word next; + byte Req; + byte ReqId; + byte ReqCh; + byte Reserved1; + word Reference; + byte Reserved[8]; + PBUFFER XBuffer; +} REQ; +typedef struct { + word next; + byte Rc; + byte RcId; + byte RcCh; + byte Reserved1; + word Reference; + byte Reserved2[8]; +} RC; +typedef struct { + word next; + byte Ind; + byte IndId; + byte IndCh; + byte MInd; + word MLength; + word Reference; + byte RNR; + byte Reserved; + dword Ack; + PBUFFER RBuffer; +} IND; diff --git a/drivers/isdn/hardware/eicon/s_4bri.c b/drivers/isdn/hardware/eicon/s_4bri.c new file mode 100644 index 000000000000..25c5d7feb838 --- /dev/null +++ b/drivers/isdn/hardware/eicon/s_4bri.c @@ -0,0 +1,510 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +#include "platform.h" +#include "di_defs.h" +#include "pc.h" +#include "pr_pc.h" +#include "di.h" +#include "mi_pc.h" +#include "pc_maint.h" +#include "divasync.h" +#include "pc_init.h" +#include "io.h" +#include "helpers.h" +#include "dsrv4bri.h" +#include "dsp_defs.h" +#include "sdp_hdr.h" + +/*****************************************************************************/ +#define MAX_XLOG_SIZE (64 * 1024) + +/* -------------------------------------------------------------------------- + Recovery XLOG from QBRI Card + -------------------------------------------------------------------------- */ +static void qBri_cpu_trapped (PISDN_ADAPTER IoAdapter) { + byte __iomem *base ; + word *Xlog ; + dword regs[4], TrapID, offset, size ; + Xdesc xlogDesc ; + int factor = (IoAdapter->tasks == 1) ? 1 : 2; + +/* + * check for trapped MIPS 46xx CPU, dump exception frame + */ + + base = DIVA_OS_MEM_ATTACH_CONTROL(IoAdapter); + offset = IoAdapter->ControllerNumber * (IoAdapter->MemorySize >> factor) ; + + TrapID = READ_DWORD(&base[0x80]) ; + + if ( (TrapID == 0x99999999) || (TrapID == 0x99999901) ) + { + dump_trap_frame (IoAdapter, &base[0x90]) ; + IoAdapter->trapped = 1 ; + } + + regs[0] = READ_DWORD((base + offset) + 0x70); + regs[1] = READ_DWORD((base + offset) + 0x74); + regs[2] = READ_DWORD((base + offset) + 0x78); + regs[3] = READ_DWORD((base + offset) + 0x7c); + regs[0] &= IoAdapter->MemorySize - 1 ; + + if ( (regs[0] >= offset) + && (regs[0] < offset + (IoAdapter->MemorySize >> factor) - 1) ) + { + if ( !(Xlog = (word *)diva_os_malloc (0, MAX_XLOG_SIZE)) ) { + DIVA_OS_MEM_DETACH_CONTROL(IoAdapter, base); + return ; + } + + size = offset + (IoAdapter->MemorySize >> factor) - regs[0] ; + if ( size > MAX_XLOG_SIZE ) + size = MAX_XLOG_SIZE ; + memcpy_fromio (Xlog, &base[regs[0]], size) ; + xlogDesc.buf = Xlog ; + xlogDesc.cnt = READ_WORD(&base[regs[1] & (IoAdapter->MemorySize - 1)]) ; + xlogDesc.out = READ_WORD(&base[regs[2] & (IoAdapter->MemorySize - 1)]) ; + dump_xlog_buffer (IoAdapter, &xlogDesc) ; + diva_os_free (0, Xlog) ; + IoAdapter->trapped = 2 ; + } + DIVA_OS_MEM_DETACH_CONTROL(IoAdapter, base); +} + +/* -------------------------------------------------------------------------- + Reset QBRI Hardware + -------------------------------------------------------------------------- */ +static void reset_qBri_hardware (PISDN_ADAPTER IoAdapter) { + word volatile __iomem *qBriReset ; + byte volatile __iomem *qBriCntrl ; + byte volatile __iomem *p ; + + qBriReset = (word volatile __iomem *)DIVA_OS_MEM_ATTACH_PROM(IoAdapter); + WRITE_WORD(qBriReset, READ_WORD(qBriReset) | PLX9054_SOFT_RESET) ; + diva_os_wait (1) ; + WRITE_WORD(qBriReset, READ_WORD(qBriReset) & ~PLX9054_SOFT_RESET) ; + diva_os_wait (1); + WRITE_WORD(qBriReset, READ_WORD(qBriReset) | PLX9054_RELOAD_EEPROM) ; + diva_os_wait (1) ; + WRITE_WORD(qBriReset, READ_WORD(qBriReset) & ~PLX9054_RELOAD_EEPROM) ; + diva_os_wait (1); + DIVA_OS_MEM_DETACH_PROM(IoAdapter, qBriReset); + + qBriCntrl = DIVA_OS_MEM_ATTACH_CTLREG(IoAdapter); + p = &qBriCntrl[DIVA_4BRI_REVISION(IoAdapter) ? (MQ2_BREG_RISC) : (MQ_BREG_RISC)]; + WRITE_DWORD(p, 0) ; + DIVA_OS_MEM_DETACH_CTLREG(IoAdapter, qBriCntrl); + + DBG_TRC(("resetted board @ reset addr 0x%08lx", qBriReset)) + DBG_TRC(("resetted board @ cntrl addr 0x%08lx", p)) +} + +/* -------------------------------------------------------------------------- + Start Card CPU + -------------------------------------------------------------------------- */ +void start_qBri_hardware (PISDN_ADAPTER IoAdapter) { + byte volatile __iomem *qBriReset ; + byte volatile __iomem *p ; + + p = DIVA_OS_MEM_ATTACH_CTLREG(IoAdapter); + qBriReset = &p[(DIVA_4BRI_REVISION(IoAdapter)) ? (MQ2_BREG_RISC) : (MQ_BREG_RISC)]; + WRITE_DWORD(qBriReset, MQ_RISC_COLD_RESET_MASK) ; + diva_os_wait (2) ; + WRITE_DWORD(qBriReset, MQ_RISC_WARM_RESET_MASK | MQ_RISC_COLD_RESET_MASK) ; + diva_os_wait (10) ; + DIVA_OS_MEM_DETACH_CTLREG(IoAdapter, p); + + DBG_TRC(("started processor @ addr 0x%08lx", qBriReset)) +} + +/* -------------------------------------------------------------------------- + Stop Card CPU + -------------------------------------------------------------------------- */ +static void stop_qBri_hardware (PISDN_ADAPTER IoAdapter) { + byte volatile __iomem *p ; + dword volatile __iomem *qBriReset ; + dword volatile __iomem *qBriIrq ; + dword volatile __iomem *qBriIsacDspReset ; + int rev2 = DIVA_4BRI_REVISION(IoAdapter); + int reset_offset = rev2 ? (MQ2_BREG_RISC) : (MQ_BREG_RISC); + int irq_offset = rev2 ? (MQ2_BREG_IRQ_TEST) : (MQ_BREG_IRQ_TEST); + int hw_offset = rev2 ? (MQ2_ISAC_DSP_RESET) : (MQ_ISAC_DSP_RESET); + + if ( IoAdapter->ControllerNumber > 0 ) + return ; + p = DIVA_OS_MEM_ATTACH_CTLREG(IoAdapter); + qBriReset = (dword volatile __iomem *)&p[reset_offset]; + qBriIsacDspReset = (dword volatile __iomem *)&p[hw_offset]; +/* + * clear interrupt line (reset Local Interrupt Test Register) + */ + WRITE_DWORD(qBriReset, 0) ; + WRITE_DWORD(qBriIsacDspReset, 0) ; + DIVA_OS_MEM_DETACH_CTLREG(IoAdapter, p); + + p = DIVA_OS_MEM_ATTACH_RESET(IoAdapter); + WRITE_BYTE(&p[PLX9054_INTCSR], 0x00); /* disable PCI interrupts */ + DIVA_OS_MEM_DETACH_RESET(IoAdapter, p); + + p = DIVA_OS_MEM_ATTACH_CTLREG(IoAdapter); + qBriIrq = (dword volatile __iomem *)&p[irq_offset]; + WRITE_DWORD(qBriIrq, MQ_IRQ_REQ_OFF) ; + DIVA_OS_MEM_DETACH_CTLREG(IoAdapter, p); + + DBG_TRC(("stopped processor @ addr 0x%08lx", qBriReset)) + +} + +/* -------------------------------------------------------------------------- + FPGA download + -------------------------------------------------------------------------- */ +#define FPGA_NAME_OFFSET 0x10 + +static byte * qBri_check_FPGAsrc (PISDN_ADAPTER IoAdapter, char *FileName, + dword *Length, dword *code) { + byte *File ; + char *fpgaFile, *fpgaType, *fpgaDate, *fpgaTime ; + dword fpgaFlen, fpgaTlen, fpgaDlen, cnt, year, i ; + + if (!(File = (byte *)xdiLoadFile (FileName, Length, 0))) { + return (NULL) ; + } +/* + * scan file until FF and put id string into buffer + */ + for ( i = 0 ; File[i] != 0xff ; ) + { + if ( ++i >= *Length ) + { + DBG_FTL(("FPGA download: start of data header not found")) + xdiFreeFile (File) ; + return (NULL) ; + } + } + *code = i++ ; + + if ( (File[i] & 0xF0) != 0x20 ) + { + DBG_FTL(("FPGA download: data header corrupted")) + xdiFreeFile (File) ; + return (NULL) ; + } + fpgaFlen = (dword) File[FPGA_NAME_OFFSET - 1] ; + if ( fpgaFlen == 0 ) + fpgaFlen = 12 ; + fpgaFile = (char *)&File[FPGA_NAME_OFFSET] ; + fpgaTlen = (dword) fpgaFile[fpgaFlen + 2] ; + if ( fpgaTlen == 0 ) + fpgaTlen = 10 ; + fpgaType = (char *)&fpgaFile[fpgaFlen + 3] ; + fpgaDlen = (dword) fpgaType[fpgaTlen + 2] ; + if ( fpgaDlen == 0 ) + fpgaDlen = 11 ; + fpgaDate = (char *)&fpgaType[fpgaTlen + 3] ; + fpgaTime = (char *)&fpgaDate[fpgaDlen + 3] ; + cnt = (dword)(((File[ i ] & 0x0F) << 20) + (File[i + 1] << 12) + + (File[i + 2] << 4) + (File[i + 3] >> 4)) ; + + if ( (dword)(i + (cnt / 8)) > *Length ) + { + DBG_FTL(("FPGA download: '%s' file too small (%ld < %ld)", + FileName, *Length, code + ((cnt + 7) / 8) )) + xdiFreeFile (File) ; + return (NULL) ; + } + i = 0 ; + do + { + while ( (fpgaDate[i] != '\0') + && ((fpgaDate[i] < '0') || (fpgaDate[i] > '9')) ) + { + i++; + } + year = 0 ; + while ( (fpgaDate[i] >= '0') && (fpgaDate[i] <= '9') ) + year = year * 10 + (fpgaDate[i++] - '0') ; + } while ( (year < 2000) && (fpgaDate[i] != '\0') ); + + switch (IoAdapter->cardType) { + case CARDTYPE_DIVASRV_B_2F_PCI: + break; + + default: + if ( year >= 2001 ) { + IoAdapter->fpga_features |= PCINIT_FPGA_PLX_ACCESS_SUPPORTED ; + } + } + + DBG_LOG(("FPGA[%s] file %s (%s %s) len %d", + fpgaType, fpgaFile, fpgaDate, fpgaTime, cnt)) + return (File) ; +} + +/******************************************************************************/ + +#define FPGA_PROG 0x0001 /* PROG enable low */ +#define FPGA_BUSY 0x0002 /* BUSY high, DONE low */ +#define FPGA_CS 0x000C /* Enable I/O pins */ +#define FPGA_CCLK 0x0100 +#define FPGA_DOUT 0x0400 +#define FPGA_DIN FPGA_DOUT /* bidirectional I/O */ + +int qBri_FPGA_download (PISDN_ADAPTER IoAdapter) { + int bit ; + byte *File ; + dword code, FileLength ; + word volatile __iomem *addr = (word volatile __iomem *)DIVA_OS_MEM_ATTACH_PROM(IoAdapter); + word val, baseval = FPGA_CS | FPGA_PROG ; + + + + if (DIVA_4BRI_REVISION(IoAdapter)) + { + char* name; + + switch (IoAdapter->cardType) { + case CARDTYPE_DIVASRV_B_2F_PCI: + name = "dsbri2f.bit"; + break; + + case CARDTYPE_DIVASRV_B_2M_V2_PCI: + case CARDTYPE_DIVASRV_VOICE_B_2M_V2_PCI: + name = "dsbri2m.bit"; + break; + + default: + name = "ds4bri2.bit"; + } + + File = qBri_check_FPGAsrc (IoAdapter, name, + &FileLength, &code); + } + else + { + File = qBri_check_FPGAsrc (IoAdapter, "ds4bri.bit", + &FileLength, &code) ; + } + if ( !File ) { + DIVA_OS_MEM_DETACH_PROM(IoAdapter, addr); + return (0) ; + } +/* + * prepare download, pulse PROGRAM pin down. + */ + WRITE_WORD(addr, baseval & ~FPGA_PROG) ; /* PROGRAM low pulse */ + WRITE_WORD(addr, baseval) ; /* release */ + diva_os_wait (50) ; /* wait until FPGA finished internal memory clear */ +/* + * check done pin, must be low + */ + if ( READ_WORD(addr) & FPGA_BUSY ) + { + DBG_FTL(("FPGA download: acknowledge for FPGA memory clear missing")) + xdiFreeFile (File) ; + DIVA_OS_MEM_DETACH_PROM(IoAdapter, addr); + return (0) ; + } +/* + * put data onto the FPGA + */ + while ( code < FileLength ) + { + val = ((word)File[code++]) << 3 ; + + for ( bit = 8 ; bit-- > 0 ; val <<= 1 ) /* put byte onto FPGA */ + { + baseval &= ~FPGA_DOUT ; /* clr data bit */ + baseval |= (val & FPGA_DOUT) ; /* copy data bit */ + WRITE_WORD(addr, baseval) ; + WRITE_WORD(addr, baseval | FPGA_CCLK) ; /* set CCLK hi */ + WRITE_WORD(addr, baseval | FPGA_CCLK) ; /* set CCLK hi */ + WRITE_WORD(addr, baseval) ; /* set CCLK lo */ + } + } + xdiFreeFile (File) ; + diva_os_wait (100) ; + val = READ_WORD(addr) ; + + DIVA_OS_MEM_DETACH_PROM(IoAdapter, addr); + + if ( !(val & FPGA_BUSY) ) + { + DBG_FTL(("FPGA download: chip remains in busy state (0x%04x)", val)) + return (0) ; + } + + return (1) ; +} + +static int load_qBri_hardware (PISDN_ADAPTER IoAdapter) { + return (0); +} + +/* -------------------------------------------------------------------------- + Card ISR + -------------------------------------------------------------------------- */ +static int qBri_ISR (struct _ISDN_ADAPTER* IoAdapter) { + dword volatile __iomem *qBriIrq ; + + PADAPTER_LIST_ENTRY QuadroList = IoAdapter->QuadroList ; + + word i ; + int serviced = 0 ; + byte __iomem *p; + + p = DIVA_OS_MEM_ATTACH_RESET(IoAdapter); + + if ( !(READ_BYTE(&p[PLX9054_INTCSR]) & 0x80) ) { + DIVA_OS_MEM_DETACH_RESET(IoAdapter, p); + return (0) ; + } + DIVA_OS_MEM_DETACH_RESET(IoAdapter, p); + +/* + * clear interrupt line (reset Local Interrupt Test Register) + */ + p = DIVA_OS_MEM_ATTACH_CTLREG(IoAdapter); + qBriIrq = (dword volatile __iomem *)(&p[DIVA_4BRI_REVISION(IoAdapter) ? (MQ2_BREG_IRQ_TEST) : (MQ_BREG_IRQ_TEST)]); + WRITE_DWORD(qBriIrq, MQ_IRQ_REQ_OFF) ; + DIVA_OS_MEM_DETACH_CTLREG(IoAdapter, p); + + for ( i = 0 ; i < IoAdapter->tasks; ++i ) + { + IoAdapter = QuadroList->QuadroAdapter[i] ; + + if ( IoAdapter && IoAdapter->Initialized + && IoAdapter->tst_irq (&IoAdapter->a) ) + { + IoAdapter->IrqCount++ ; + serviced = 1 ; + diva_os_schedule_soft_isr (&IoAdapter->isr_soft_isr); + } + } + + return (serviced) ; +} + +/* -------------------------------------------------------------------------- + Does disable the interrupt on the card + -------------------------------------------------------------------------- */ +static void disable_qBri_interrupt (PISDN_ADAPTER IoAdapter) { + dword volatile __iomem *qBriIrq ; + byte __iomem *p; + + if ( IoAdapter->ControllerNumber > 0 ) + return ; +/* + * clear interrupt line (reset Local Interrupt Test Register) + */ + p = DIVA_OS_MEM_ATTACH_RESET(IoAdapter); + WRITE_BYTE(&p[PLX9054_INTCSR], 0x00); /* disable PCI interrupts */ + DIVA_OS_MEM_DETACH_RESET(IoAdapter, p); + + p = DIVA_OS_MEM_ATTACH_CTLREG(IoAdapter); + qBriIrq = (dword volatile __iomem *)(&p[DIVA_4BRI_REVISION(IoAdapter) ? (MQ2_BREG_IRQ_TEST) : (MQ_BREG_IRQ_TEST)]); + WRITE_DWORD(qBriIrq, MQ_IRQ_REQ_OFF) ; + DIVA_OS_MEM_DETACH_CTLREG(IoAdapter, p); +} + +/* -------------------------------------------------------------------------- + Install Adapter Entry Points + -------------------------------------------------------------------------- */ +static void set_common_qBri_functions (PISDN_ADAPTER IoAdapter) { + ADAPTER *a; + + a = &IoAdapter->a ; + + a->ram_in = mem_in ; + a->ram_inw = mem_inw ; + a->ram_in_buffer = mem_in_buffer ; + a->ram_look_ahead = mem_look_ahead ; + a->ram_out = mem_out ; + a->ram_outw = mem_outw ; + a->ram_out_buffer = mem_out_buffer ; + a->ram_inc = mem_inc ; + + IoAdapter->out = pr_out ; + IoAdapter->dpc = pr_dpc ; + IoAdapter->tst_irq = scom_test_int ; + IoAdapter->clr_irq = scom_clear_int ; + IoAdapter->pcm = (struct pc_maint *)MIPS_MAINT_OFFS ; + + IoAdapter->load = load_qBri_hardware ; + + IoAdapter->disIrq = disable_qBri_interrupt ; + IoAdapter->rstFnc = reset_qBri_hardware ; + IoAdapter->stop = stop_qBri_hardware ; + IoAdapter->trapFnc = qBri_cpu_trapped ; + + IoAdapter->diva_isr_handler = qBri_ISR; + + IoAdapter->a.io = (void*)IoAdapter ; +} + +static void set_qBri_functions (PISDN_ADAPTER IoAdapter) { + if (!IoAdapter->tasks) { + IoAdapter->tasks = MQ_INSTANCE_COUNT; + } + IoAdapter->MemorySize = MQ_MEMORY_SIZE ; + set_common_qBri_functions (IoAdapter) ; + diva_os_set_qBri_functions (IoAdapter) ; +} + +static void set_qBri2_functions (PISDN_ADAPTER IoAdapter) { + if (!IoAdapter->tasks) { + IoAdapter->tasks = MQ_INSTANCE_COUNT; + } + IoAdapter->MemorySize = (IoAdapter->tasks == 1) ? BRI2_MEMORY_SIZE : MQ2_MEMORY_SIZE; + set_common_qBri_functions (IoAdapter) ; + diva_os_set_qBri2_functions (IoAdapter) ; +} + +/******************************************************************************/ + +void prepare_qBri_functions (PISDN_ADAPTER IoAdapter) { + + set_qBri_functions (IoAdapter->QuadroList->QuadroAdapter[0]) ; + set_qBri_functions (IoAdapter->QuadroList->QuadroAdapter[1]) ; + set_qBri_functions (IoAdapter->QuadroList->QuadroAdapter[2]) ; + set_qBri_functions (IoAdapter->QuadroList->QuadroAdapter[3]) ; + +} + +void prepare_qBri2_functions (PISDN_ADAPTER IoAdapter) { + if (!IoAdapter->tasks) { + IoAdapter->tasks = MQ_INSTANCE_COUNT; + } + + set_qBri2_functions (IoAdapter->QuadroList->QuadroAdapter[0]) ; + if (IoAdapter->tasks > 1) { + set_qBri2_functions (IoAdapter->QuadroList->QuadroAdapter[1]) ; + set_qBri2_functions (IoAdapter->QuadroList->QuadroAdapter[2]) ; + set_qBri2_functions (IoAdapter->QuadroList->QuadroAdapter[3]) ; + } + +} + +/* -------------------------------------------------------------------------- */ diff --git a/drivers/isdn/hardware/eicon/s_bri.c b/drivers/isdn/hardware/eicon/s_bri.c new file mode 100644 index 000000000000..5c87552e8c08 --- /dev/null +++ b/drivers/isdn/hardware/eicon/s_bri.c @@ -0,0 +1,191 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +#include "platform.h" +#include "di_defs.h" +#include "pc.h" +#include "pr_pc.h" +#include "di.h" +#include "mi_pc.h" +#include "pc_maint.h" +#include "divasync.h" +#include "io.h" +#include "helpers.h" +#include "dsrv_bri.h" +#include "dsp_defs.h" +/*****************************************************************************/ +#define MAX_XLOG_SIZE (64 * 1024) +/* -------------------------------------------------------------------------- + Investigate card state, recovery trace buffer + -------------------------------------------------------------------------- */ +static void bri_cpu_trapped (PISDN_ADAPTER IoAdapter) { + byte __iomem *addrHi, *addrLo, *ioaddr ; + word *Xlog ; + dword regs[4], i, size ; + Xdesc xlogDesc ; + byte __iomem *Port; +/* + * first read pointers and trap frame + */ + if ( !(Xlog = (word *)diva_os_malloc (0, MAX_XLOG_SIZE)) ) + return ; + Port = DIVA_OS_MEM_ATTACH_PORT(IoAdapter); + addrHi = Port + ((IoAdapter->Properties.Bus == BUS_PCI) ? M_PCI_ADDRH : ADDRH) ; + addrLo = Port + ADDR ; + ioaddr = Port + DATA ; + outpp (addrHi, 0) ; + outppw (addrLo, 0) ; + for ( i = 0 ; i < 0x100 ; Xlog[i++] = inppw(ioaddr) ) ; +/* + * check for trapped MIPS 3xxx CPU, dump only exception frame + */ + if ( GET_DWORD(&Xlog[0x80 / sizeof(Xlog[0])]) == 0x99999999 ) + { + dump_trap_frame (IoAdapter, &((byte *)Xlog)[0x90]) ; + IoAdapter->trapped = 1 ; + } + regs[0] = GET_DWORD(&((byte *)Xlog)[0x70]); + regs[1] = GET_DWORD(&((byte *)Xlog)[0x74]); + regs[2] = GET_DWORD(&((byte *)Xlog)[0x78]); + regs[3] = GET_DWORD(&((byte *)Xlog)[0x7c]); + outpp (addrHi, (regs[1] >> 16) & 0x7F) ; + outppw (addrLo, regs[1] & 0xFFFF) ; + xlogDesc.cnt = inppw(ioaddr) ; + outpp (addrHi, (regs[2] >> 16) & 0x7F) ; + outppw (addrLo, regs[2] & 0xFFFF) ; + xlogDesc.out = inppw(ioaddr) ; + xlogDesc.buf = Xlog ; + regs[0] &= IoAdapter->MemorySize - 1 ; + if ( (regs[0] < IoAdapter->MemorySize - 1) ) + { + size = IoAdapter->MemorySize - regs[0] ; + if ( size > MAX_XLOG_SIZE ) + size = MAX_XLOG_SIZE ; + for ( i = 0 ; i < (size / sizeof(*Xlog)) ; regs[0] += 2 ) + { + outpp (addrHi, (regs[0] >> 16) & 0x7F) ; + outppw (addrLo, regs[0] & 0xFFFF) ; + Xlog[i++] = inppw(ioaddr) ; + } + dump_xlog_buffer (IoAdapter, &xlogDesc) ; + diva_os_free (0, Xlog) ; + IoAdapter->trapped = 2 ; + } + outpp (addrHi, (byte)((BRI_UNCACHED_ADDR (IoAdapter->MemoryBase + IoAdapter->MemorySize - + BRI_SHARED_RAM_SIZE)) >> 16)) ; + outppw (addrLo, 0x00) ; + DIVA_OS_MEM_DETACH_PORT(IoAdapter, Port); +} +/* --------------------------------------------------------------------- + Reset hardware + --------------------------------------------------------------------- */ +static void reset_bri_hardware (PISDN_ADAPTER IoAdapter) { + byte __iomem *p = DIVA_OS_MEM_ATTACH_CTLREG(IoAdapter); + outpp (p, 0x00) ; + DIVA_OS_MEM_DETACH_CTLREG(IoAdapter, p); +} +/* --------------------------------------------------------------------- + Halt system + --------------------------------------------------------------------- */ +static void stop_bri_hardware (PISDN_ADAPTER IoAdapter) { + byte __iomem *p = DIVA_OS_MEM_ATTACH_RESET(IoAdapter); + if (p) { + outpp (p, 0x00) ; /* disable interrupts ! */ + } + DIVA_OS_MEM_DETACH_RESET(IoAdapter, p); + p = DIVA_OS_MEM_ATTACH_CTLREG(IoAdapter); + outpp (p, 0x00) ; /* clear int, halt cpu */ + DIVA_OS_MEM_DETACH_CTLREG(IoAdapter, p); +} +static int load_bri_hardware (PISDN_ADAPTER IoAdapter) { + return (0); +} +/******************************************************************************/ +static int bri_ISR (struct _ISDN_ADAPTER* IoAdapter) { + byte __iomem *p; + + p = DIVA_OS_MEM_ATTACH_CTLREG(IoAdapter); + if ( !(inpp (p) & 0x01) ) { + DIVA_OS_MEM_DETACH_CTLREG(IoAdapter, p); + return (0) ; + } + /* + clear interrupt line + */ + outpp (p, 0x08) ; + DIVA_OS_MEM_DETACH_CTLREG(IoAdapter, p); + IoAdapter->IrqCount++ ; + if ( IoAdapter->Initialized ) { + diva_os_schedule_soft_isr (&IoAdapter->isr_soft_isr); + } + return (1) ; +} +/* -------------------------------------------------------------------------- + Disable IRQ in the card hardware + -------------------------------------------------------------------------- */ +static void disable_bri_interrupt (PISDN_ADAPTER IoAdapter) { + byte __iomem *p; + p = DIVA_OS_MEM_ATTACH_RESET(IoAdapter); + if ( p ) + { + outpp (p, 0x00) ; /* disable interrupts ! */ + } + DIVA_OS_MEM_DETACH_RESET(IoAdapter, p); + p = DIVA_OS_MEM_ATTACH_CTLREG(IoAdapter); + outpp (p, 0x00) ; /* clear int, halt cpu */ + DIVA_OS_MEM_DETACH_CTLREG(IoAdapter, p); +} +/* ------------------------------------------------------------------------- + Fill card entry points + ------------------------------------------------------------------------- */ +void prepare_maestra_functions (PISDN_ADAPTER IoAdapter) { + ADAPTER *a = &IoAdapter->a ; + a->ram_in = io_in ; + a->ram_inw = io_inw ; + a->ram_in_buffer = io_in_buffer ; + a->ram_look_ahead = io_look_ahead ; + a->ram_out = io_out ; + a->ram_outw = io_outw ; + a->ram_out_buffer = io_out_buffer ; + a->ram_inc = io_inc ; + IoAdapter->MemoryBase = BRI_MEMORY_BASE ; + IoAdapter->MemorySize = BRI_MEMORY_SIZE ; + IoAdapter->out = pr_out ; + IoAdapter->dpc = pr_dpc ; + IoAdapter->tst_irq = scom_test_int ; + IoAdapter->clr_irq = scom_clear_int ; + IoAdapter->pcm = (struct pc_maint *)MIPS_MAINT_OFFS ; + IoAdapter->load = load_bri_hardware ; + IoAdapter->disIrq = disable_bri_interrupt ; + IoAdapter->rstFnc = reset_bri_hardware ; + IoAdapter->stop = stop_bri_hardware ; + IoAdapter->trapFnc = bri_cpu_trapped ; + IoAdapter->diva_isr_handler = bri_ISR; + /* + Prepare OS dependent functions + */ + diva_os_prepare_maestra_functions (IoAdapter); +} +/* -------------------------------------------------------------------------- */ diff --git a/drivers/isdn/hardware/eicon/s_pri.c b/drivers/isdn/hardware/eicon/s_pri.c new file mode 100644 index 000000000000..18f287888570 --- /dev/null +++ b/drivers/isdn/hardware/eicon/s_pri.c @@ -0,0 +1,205 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +#include "platform.h" +#include "di_defs.h" +#include "pc.h" +#include "pr_pc.h" +#include "di.h" +#include "mi_pc.h" +#include "pc_maint.h" +#include "divasync.h" +#include "io.h" +#include "helpers.h" +#include "dsrv_pri.h" +#include "dsp_defs.h" +/*****************************************************************************/ +#define MAX_XLOG_SIZE (64 * 1024) +/* ------------------------------------------------------------------------- + Does return offset between ADAPTER->ram and real begin of memory + ------------------------------------------------------------------------- */ +static dword pri_ram_offset (ADAPTER* a) { + return ((dword)MP_SHARED_RAM_OFFSET); +} +/* ------------------------------------------------------------------------- + Recovery XLOG buffer from the card + ------------------------------------------------------------------------- */ +static void pri_cpu_trapped (PISDN_ADAPTER IoAdapter) { + byte __iomem *base ; + word *Xlog ; + dword regs[4], TrapID, size ; + Xdesc xlogDesc ; +/* + * check for trapped MIPS 46xx CPU, dump exception frame + */ + base = DIVA_OS_MEM_ATTACH_ADDRESS(IoAdapter); + TrapID = READ_DWORD(&base[0x80]) ; + if ( (TrapID == 0x99999999) || (TrapID == 0x99999901) ) + { + dump_trap_frame (IoAdapter, &base[0x90]) ; + IoAdapter->trapped = 1 ; + } + regs[0] = READ_DWORD(&base[MP_PROTOCOL_OFFSET + 0x70]); + regs[1] = READ_DWORD(&base[MP_PROTOCOL_OFFSET + 0x74]); + regs[2] = READ_DWORD(&base[MP_PROTOCOL_OFFSET + 0x78]); + regs[3] = READ_DWORD(&base[MP_PROTOCOL_OFFSET + 0x7c]); + regs[0] &= IoAdapter->MemorySize - 1 ; + if ( (regs[0] < IoAdapter->MemorySize - 1) ) + { + if ( !(Xlog = (word *)diva_os_malloc (0, MAX_XLOG_SIZE)) ) { + DIVA_OS_MEM_DETACH_ADDRESS(IoAdapter, base); + return ; + } + size = IoAdapter->MemorySize - regs[0] ; + if ( size > MAX_XLOG_SIZE ) + size = MAX_XLOG_SIZE ; + memcpy_fromio(Xlog, &base[regs[0]], size) ; + xlogDesc.buf = Xlog ; + xlogDesc.cnt = READ_WORD(&base[regs[1] & (IoAdapter->MemorySize - 1)]) ; + xlogDesc.out = READ_WORD(&base[regs[2] & (IoAdapter->MemorySize - 1)]) ; + dump_xlog_buffer (IoAdapter, &xlogDesc) ; + diva_os_free (0, Xlog) ; + IoAdapter->trapped = 2 ; + } + DIVA_OS_MEM_DETACH_ADDRESS(IoAdapter, base); +} +/* ------------------------------------------------------------------------- + Hardware reset of PRI card + ------------------------------------------------------------------------- */ +static void reset_pri_hardware (PISDN_ADAPTER IoAdapter) { + byte __iomem *p = DIVA_OS_MEM_ATTACH_RESET(IoAdapter); + WRITE_BYTE(p, _MP_RISC_RESET | _MP_LED1 | _MP_LED2); + diva_os_wait (50) ; + WRITE_BYTE(p, 0x00); + diva_os_wait (50) ; + DIVA_OS_MEM_DETACH_RESET(IoAdapter, p); +} +/* ------------------------------------------------------------------------- + Stop Card Hardware + ------------------------------------------------------------------------- */ +static void stop_pri_hardware (PISDN_ADAPTER IoAdapter) { + dword i; + byte __iomem *p; + dword volatile __iomem *cfgReg = (void __iomem *)DIVA_OS_MEM_ATTACH_CFG(IoAdapter); + WRITE_DWORD(&cfgReg[3], 0); + WRITE_DWORD(&cfgReg[1], 0); + DIVA_OS_MEM_DETACH_CFG(IoAdapter, cfgReg); + IoAdapter->a.ram_out (&IoAdapter->a, &RAM->SWReg, SWREG_HALT_CPU) ; + i = 0 ; + while ( (i < 100) && (IoAdapter->a.ram_in (&IoAdapter->a, &RAM->SWReg) != 0) ) + { + diva_os_wait (1) ; + i++ ; + } + DBG_TRC(("%s: PRI stopped (%d)", IoAdapter->Name, i)) + cfgReg = (void __iomem *)DIVA_OS_MEM_ATTACH_CFG(IoAdapter); + WRITE_DWORD(&cfgReg[0],((dword)(~0x03E00000))); + DIVA_OS_MEM_DETACH_CFG(IoAdapter, cfgReg); + diva_os_wait (1) ; + p = DIVA_OS_MEM_ATTACH_RESET(IoAdapter); + WRITE_BYTE(p, _MP_RISC_RESET | _MP_LED1 | _MP_LED2); + DIVA_OS_MEM_DETACH_RESET(IoAdapter, p); +} +static int load_pri_hardware (PISDN_ADAPTER IoAdapter) { + return (0); +} +/* -------------------------------------------------------------------------- + PRI Adapter interrupt Service Routine + -------------------------------------------------------------------------- */ +static int pri_ISR (struct _ISDN_ADAPTER* IoAdapter) { + byte __iomem *cfg = DIVA_OS_MEM_ATTACH_CFG(IoAdapter); + if ( !(READ_DWORD(cfg) & 0x80000000) ) { + DIVA_OS_MEM_DETACH_CFG(IoAdapter, cfg); + return (0) ; + } + /* + clear interrupt line + */ + WRITE_DWORD(cfg, (dword)~0x03E00000) ; + DIVA_OS_MEM_DETACH_CFG(IoAdapter, cfg); + IoAdapter->IrqCount++ ; + if ( IoAdapter->Initialized ) + { + diva_os_schedule_soft_isr (&IoAdapter->isr_soft_isr); + } + return (1) ; +} +/* ------------------------------------------------------------------------- + Disable interrupt in the card hardware + ------------------------------------------------------------------------- */ +static void disable_pri_interrupt (PISDN_ADAPTER IoAdapter) { + dword volatile __iomem *cfgReg = (dword volatile __iomem *)DIVA_OS_MEM_ATTACH_CFG(IoAdapter) ; + WRITE_DWORD(&cfgReg[3], 0); + WRITE_DWORD(&cfgReg[1], 0); + WRITE_DWORD(&cfgReg[0], (dword)(~0x03E00000)) ; + DIVA_OS_MEM_DETACH_CFG(IoAdapter, cfgReg); +} +/* ------------------------------------------------------------------------- + Install entry points for PRI Adapter + ------------------------------------------------------------------------- */ +static void prepare_common_pri_functions (PISDN_ADAPTER IoAdapter) { + ADAPTER *a = &IoAdapter->a ; + a->ram_in = mem_in ; + a->ram_inw = mem_inw ; + a->ram_in_buffer = mem_in_buffer ; + a->ram_look_ahead = mem_look_ahead ; + a->ram_out = mem_out ; + a->ram_outw = mem_outw ; + a->ram_out_buffer = mem_out_buffer ; + a->ram_inc = mem_inc ; + a->ram_offset = pri_ram_offset ; + a->ram_out_dw = mem_out_dw; + a->ram_in_dw = mem_in_dw; + a->istream_wakeup = pr_stream; + IoAdapter->out = pr_out ; + IoAdapter->dpc = pr_dpc ; + IoAdapter->tst_irq = scom_test_int ; + IoAdapter->clr_irq = scom_clear_int ; + IoAdapter->pcm = (struct pc_maint *)(MIPS_MAINT_OFFS + - MP_SHARED_RAM_OFFSET) ; + IoAdapter->load = load_pri_hardware ; + IoAdapter->disIrq = disable_pri_interrupt ; + IoAdapter->rstFnc = reset_pri_hardware ; + IoAdapter->stop = stop_pri_hardware ; + IoAdapter->trapFnc = pri_cpu_trapped ; + IoAdapter->diva_isr_handler = pri_ISR; +} +/* ------------------------------------------------------------------------- + Install entry points for PRI Adapter + ------------------------------------------------------------------------- */ +void prepare_pri_functions (PISDN_ADAPTER IoAdapter) { + IoAdapter->MemorySize = MP_MEMORY_SIZE ; + prepare_common_pri_functions (IoAdapter) ; + diva_os_prepare_pri_functions (IoAdapter); +} +/* ------------------------------------------------------------------------- + Install entry points for PRI Rev.2 Adapter + ------------------------------------------------------------------------- */ +void prepare_pri2_functions (PISDN_ADAPTER IoAdapter) { + IoAdapter->MemorySize = MP2_MEMORY_SIZE ; + prepare_common_pri_functions (IoAdapter) ; + diva_os_prepare_pri2_functions (IoAdapter); +} +/* ------------------------------------------------------------------------- */ diff --git a/drivers/isdn/hardware/eicon/sdp_hdr.h b/drivers/isdn/hardware/eicon/sdp_hdr.h new file mode 100644 index 000000000000..8f61c696b9aa --- /dev/null +++ b/drivers/isdn/hardware/eicon/sdp_hdr.h @@ -0,0 +1,117 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +#ifndef __DIVA_SOFT_DSP_TASK_ENTRY_H__ +#define __DIVA_SOFT_DSP_TASK_ENTRY_H__ +/* + The soft DSP image is described by binary header contained on begin of this + image: +OFFSET FROM IMAGE START | VARIABLE +------------------------------------------------------------------------ + DIVA_MIPS_TASK_IMAGE_LINK_OFFS | link to the next image + ---------------------------------------------------------------------- + DIVA_MIPS_TASK_IMAGE_GP_OFFS | image gp register value, void* + ---------------------------------------------------------------------- + DIVA_MIPS_TASK_IMAGE_ENTRY_OFFS | diva_mips_sdp_task_entry_t* + ---------------------------------------------------------------------- + DIVA_MIPS_TASK_IMAGE_LOAD_ADDR_OFFS | image image start address (void*) + ---------------------------------------------------------------------- + DIVA_MIPS_TASK_IMAGE_END_ADDR_OFFS | image image end address (void*) + ---------------------------------------------------------------------- + DIVA_MIPS_TASK_IMAGE_ID_STRING_OFFS | image id string char[...]; + ---------------------------------------------------------------------- + */ +#define DIVA_MIPS_TASK_IMAGE_LINK_OFFS 0x6C +#define DIVA_MIPS_TASK_IMAGE_GP_OFFS 0x70 +#define DIVA_MIPS_TASK_IMAGE_ENTRY_OFFS 0x74 +#define DIVA_MIPS_TASK_IMAGE_LOAD_ADDR_OFFS 0x78 +#define DIVA_MIPS_TASK_IMAGE_END_ADDR_OFFS 0x7c +#define DIVA_MIPS_TASK_IMAGE_ID_STRING_OFFS 0x80 +/* + This function is called in order to set GP register of this task + This function should be always called before any function of the + task is called + */ +typedef void (*diva_task_set_prog_gp_proc_t)(void* new_gp); +/* + This function is called to clear .bss at task initialization step + */ +typedef void (*diva_task_sys_reset_proc_t)(void); +/* + This function is called in order to provide GP of master call to + task, that will be used by calls from the task to the master + */ +typedef void (*diva_task_set_main_gp_proc_t)(void* main_gp); +/* + This function is called to provide address of 'dprintf' function + to the task + */ +typedef word (*diva_prt_proc_t)(char *, ...); +typedef void (*diva_task_set_prt_proc_t)(diva_prt_proc_t fn); +/* + This function is called to set task PID + */ +typedef void (*diva_task_set_pid_proc_t)(dword id); +/* + This function is called for run-time task init + */ +typedef int (*diva_task_run_time_init_proc_t)(void*, dword); +/* + This function is called from system scheduler or from timer + */ +typedef void (*diva_task_callback_proc_t)(void); +/* + This callback is used by task to get current time im mS + */ +typedef dword (*diva_task_get_tick_count_proc_t)(void); +typedef void (*diva_task_set_get_time_proc_t)(\ + diva_task_get_tick_count_proc_t fn); +typedef struct _diva_mips_sdp_task_entry { + diva_task_set_prog_gp_proc_t set_gp_proc; + diva_task_sys_reset_proc_t sys_reset_proc; + diva_task_set_main_gp_proc_t set_main_gp_proc; + diva_task_set_prt_proc_t set_dprintf_proc; + diva_task_set_pid_proc_t set_pid_proc; + diva_task_run_time_init_proc_t run_time_init_proc; + diva_task_callback_proc_t task_callback_proc; + diva_task_callback_proc_t timer_callback_proc; + diva_task_set_get_time_proc_t set_get_time_proc; + void* last_entry_proc; +} diva_mips_sdp_task_entry_t; +/* + 'last_entry_proc' should be set to zero and is used for future extensuios + */ +typedef struct _diva_mips_sw_task { + diva_mips_sdp_task_entry_t sdp_entry; + void* sdp_gp_reg; + void* own_gp_reg; +} diva_mips_sw_task_t; +#if !defined(DIVA_BRI2F_SDP_1_NAME) +#define DIVA_BRI2F_SDP_1_NAME "sdp0.2q0" +#endif +#if !defined(DIVA_BRI2F_SDP_2_NAME) +#define DIVA_BRI2F_SDP_2_NAME "sdp1.2q0" +#endif +#endif diff --git a/drivers/isdn/hardware/eicon/um_idi.c b/drivers/isdn/hardware/eicon/um_idi.c new file mode 100644 index 000000000000..6563db998d06 --- /dev/null +++ b/drivers/isdn/hardware/eicon/um_idi.c @@ -0,0 +1,885 @@ +/* $Id: um_idi.c,v 1.14 2004/03/21 17:54:37 armin Exp $ */ + +#include "platform.h" +#include "di_defs.h" +#include "pc.h" +#include "dqueue.h" +#include "adapter.h" +#include "entity.h" +#include "um_xdi.h" +#include "um_idi.h" +#include "debuglib.h" +#include "divasync.h" + +#define DIVAS_MAX_XDI_ADAPTERS 64 + +/* -------------------------------------------------------------------------- + IMPORTS + -------------------------------------------------------------------------- */ +extern void diva_os_wakeup_read(void *os_context); +extern void diva_os_wakeup_close(void *os_context); +/* -------------------------------------------------------------------------- + LOCALS + -------------------------------------------------------------------------- */ +static LIST_HEAD(adapter_q); +static diva_os_spin_lock_t adapter_lock; + +static diva_um_idi_adapter_t *diva_um_idi_find_adapter(dword nr); +static void cleanup_adapter(diva_um_idi_adapter_t * a); +static void cleanup_entity(divas_um_idi_entity_t * e); +static int diva_user_mode_idi_adapter_features(diva_um_idi_adapter_t * a, + diva_um_idi_adapter_features_t + * features); +static int process_idi_request(divas_um_idi_entity_t * e, + const diva_um_idi_req_hdr_t * req); +static int process_idi_rc(divas_um_idi_entity_t * e, byte rc); +static int process_idi_ind(divas_um_idi_entity_t * e, byte ind); +static int write_return_code(divas_um_idi_entity_t * e, byte rc); + +/* -------------------------------------------------------------------------- + MAIN + -------------------------------------------------------------------------- */ +int diva_user_mode_idi_init(void) +{ + diva_os_initialize_spin_lock(&adapter_lock, "adapter"); + return (0); +} + +/* -------------------------------------------------------------------------- + Copy adapter features to user supplied buffer + -------------------------------------------------------------------------- */ +static int +diva_user_mode_idi_adapter_features(diva_um_idi_adapter_t * a, + diva_um_idi_adapter_features_t * + features) +{ + IDI_SYNC_REQ sync_req; + + if ((a) && (a->d.request)) { + features->type = a->d.type; + features->features = a->d.features; + features->channels = a->d.channels; + memset(features->name, 0, sizeof(features->name)); + + sync_req.GetName.Req = 0; + sync_req.GetName.Rc = IDI_SYNC_REQ_GET_NAME; + (*(a->d.request)) ((ENTITY *) & sync_req); + strlcpy(features->name, sync_req.GetName.name, + sizeof(features->name)); + + sync_req.GetSerial.Req = 0; + sync_req.GetSerial.Rc = IDI_SYNC_REQ_GET_SERIAL; + sync_req.GetSerial.serial = 0; + (*(a->d.request)) ((ENTITY *) & sync_req); + features->serial_number = sync_req.GetSerial.serial; + } + + return ((a) ? 0 : -1); +} + +/* -------------------------------------------------------------------------- + REMOVE ADAPTER + -------------------------------------------------------------------------- */ +void diva_user_mode_idi_remove_adapter(int adapter_nr) +{ + struct list_head *tmp; + diva_um_idi_adapter_t *a; + + list_for_each(tmp, &adapter_q) { + a = list_entry(tmp, diva_um_idi_adapter_t, link); + if (a->adapter_nr == adapter_nr) { + list_del(tmp); + cleanup_adapter(a); + DBG_LOG(("DIDD: del adapter(%d)", a->adapter_nr)); + diva_os_free(0, a); + break; + } + } +} + +/* -------------------------------------------------------------------------- + CALLED ON DRIVER EXIT (UNLOAD) + -------------------------------------------------------------------------- */ +void diva_user_mode_idi_finit(void) +{ + struct list_head *tmp, *safe; + diva_um_idi_adapter_t *a; + + list_for_each_safe(tmp, safe, &adapter_q) { + a = list_entry(tmp, diva_um_idi_adapter_t, link); + list_del(tmp); + cleanup_adapter(a); + DBG_LOG(("DIDD: del adapter(%d)", a->adapter_nr)); + diva_os_free(0, a); + } + diva_os_destroy_spin_lock(&adapter_lock, "adapter"); +} + +/* ------------------------------------------------------------------------- + CREATE AND INIT IDI ADAPTER + ------------------------------------------------------------------------- */ +int diva_user_mode_idi_create_adapter(const DESCRIPTOR * d, int adapter_nr) +{ + diva_os_spin_lock_magic_t old_irql; + diva_um_idi_adapter_t *a = + (diva_um_idi_adapter_t *) diva_os_malloc(0, + sizeof + (diva_um_idi_adapter_t)); + + if (!a) { + return (-1); + } + memset(a, 0x00, sizeof(*a)); + INIT_LIST_HEAD(&a->entity_q); + + a->d = *d; + a->adapter_nr = adapter_nr; + + DBG_LOG(("DIDD_ADD A(%d), type:%02x, features:%04x, channels:%d", + adapter_nr, a->d.type, a->d.features, a->d.channels)); + + diva_os_enter_spin_lock(&adapter_lock, &old_irql, "create_adapter"); + list_add_tail(&a->link, &adapter_q); + diva_os_leave_spin_lock(&adapter_lock, &old_irql, "create_adapter"); + return (0); +} + +/* ------------------------------------------------------------------------ + Find adapter by Adapter number + ------------------------------------------------------------------------ */ +static diva_um_idi_adapter_t *diva_um_idi_find_adapter(dword nr) +{ + diva_um_idi_adapter_t *a = NULL; + struct list_head *tmp; + + list_for_each(tmp, &adapter_q) { + a = list_entry(tmp, diva_um_idi_adapter_t, link); + DBG_TRC(("find_adapter: (%d)-(%d)", nr, a->adapter_nr)); + if (a->adapter_nr == (int)nr) + break; + a = NULL; + } + return(a); +} + +/* ------------------------------------------------------------------------ + Cleanup this adapter and cleanup/delete all entities assigned + to this adapter + ------------------------------------------------------------------------ */ +static void cleanup_adapter(diva_um_idi_adapter_t * a) +{ + struct list_head *tmp, *safe; + divas_um_idi_entity_t *e; + + list_for_each_safe(tmp, safe, &a->entity_q) { + e = list_entry(tmp, divas_um_idi_entity_t, link); + list_del(tmp); + cleanup_entity(e); + if (e->os_context) { + diva_os_wakeup_read(e->os_context); + diva_os_wakeup_close(e->os_context); + } + } + memset(&a->d, 0x00, sizeof(DESCRIPTOR)); +} + +/* ------------------------------------------------------------------------ + Cleanup, but NOT delete this entity + ------------------------------------------------------------------------ */ +static void cleanup_entity(divas_um_idi_entity_t * e) +{ + e->os_ref = NULL; + e->status = 0; + e->adapter = NULL; + e->e.Id = 0; + e->rc_count = 0; + + e->status |= DIVA_UM_IDI_REMOVED; + e->status |= DIVA_UM_IDI_REMOVE_PENDING; + + diva_data_q_finit(&e->data); + diva_data_q_finit(&e->rc); +} + + +/* ------------------------------------------------------------------------ + Create ENTITY, link it to the adapter and remove pointer to entity + ------------------------------------------------------------------------ */ +void *divas_um_idi_create_entity(dword adapter_nr, void *file) +{ + divas_um_idi_entity_t *e; + diva_um_idi_adapter_t *a; + diva_os_spin_lock_magic_t old_irql; + + if ((e = (divas_um_idi_entity_t *) diva_os_malloc(0, sizeof(*e)))) { + memset(e, 0x00, sizeof(*e)); + if (! + (e->os_context = + diva_os_malloc(0, diva_os_get_context_size()))) { + DBG_LOG(("E(%08x) no memory for os context", e)); + diva_os_free(0, e); + return NULL; + } + memset(e->os_context, 0x00, diva_os_get_context_size()); + + if ((diva_data_q_init(&e->data, 2048 + 512, 16))) { + diva_os_free(0, e->os_context); + diva_os_free(0, e); + return NULL; + } + if ((diva_data_q_init(&e->rc, sizeof(diva_um_idi_ind_hdr_t), 2))) { + diva_data_q_finit(&e->data); + diva_os_free(0, e->os_context); + diva_os_free(0, e); + return NULL; + } + + diva_os_enter_spin_lock(&adapter_lock, &old_irql, "create_entity"); + /* + Look for Adapter requested + */ + if (!(a = diva_um_idi_find_adapter(adapter_nr))) { + /* + No adapter was found, or this adapter was removed + */ + diva_os_leave_spin_lock(&adapter_lock, &old_irql, "create_entity"); + + DBG_LOG(("A: no adapter(%ld)", adapter_nr)); + + cleanup_entity(e); + diva_os_free(0, e->os_context); + diva_os_free(0, e); + + return NULL; + } + + e->os_ref = file; /* link to os handle */ + e->adapter = a; /* link to adapter */ + + list_add_tail(&e->link, &a->entity_q); /* link from adapter */ + + diva_os_leave_spin_lock(&adapter_lock, &old_irql, "create_entity"); + + DBG_LOG(("A(%ld), create E(%08x)", adapter_nr, e)); + } + + return (e); +} + +/* ------------------------------------------------------------------------ + Unlink entity and free memory + ------------------------------------------------------------------------ */ +int divas_um_idi_delete_entity(int adapter_nr, void *entity) +{ + divas_um_idi_entity_t *e; + diva_um_idi_adapter_t *a; + diva_os_spin_lock_magic_t old_irql; + + if (!(e = (divas_um_idi_entity_t *) entity)) + return (-1); + + diva_os_enter_spin_lock(&adapter_lock, &old_irql, "delete_entity"); + if ((a = e->adapter)) { + list_del(&e->link); + } + diva_os_leave_spin_lock(&adapter_lock, &old_irql, "delete_entity"); + + diva_um_idi_stop_wdog(entity); + cleanup_entity(e); + diva_os_free(0, e->os_context); + memset(e, 0x00, sizeof(*e)); + diva_os_free(0, e); + + DBG_LOG(("A(%d) remove E:%08x", adapter_nr, e)); + + return (0); +} + +/* -------------------------------------------------------------------------- + Called by application to read data from IDI + -------------------------------------------------------------------------- */ +int diva_um_idi_read(void *entity, + void *os_handle, + void *dst, + int max_length, divas_um_idi_copy_to_user_fn_t cp_fn) +{ + divas_um_idi_entity_t *e; + diva_um_idi_adapter_t *a; + const void *data; + int length, ret = 0; + diva_um_idi_data_queue_t *q; + diva_os_spin_lock_magic_t old_irql; + + diva_os_enter_spin_lock(&adapter_lock, &old_irql, "read"); + + e = (divas_um_idi_entity_t *) entity; + if (!e || (!(a = e->adapter)) || + (e->status & DIVA_UM_IDI_REMOVE_PENDING) || + (e->status & DIVA_UM_IDI_REMOVED) || + (a->status & DIVA_UM_IDI_ADAPTER_REMOVED)) { + diva_os_leave_spin_lock(&adapter_lock, &old_irql, "read"); + DBG_ERR(("E(%08x) read failed - adapter removed", e)) + return (-1); + } + + DBG_TRC(("A(%d) E(%08x) read(%d)", a->adapter_nr, e, max_length)); + + /* + Try to read return code first + */ + data = diva_data_q_get_segment4read(&e->rc); + q = &e->rc; + + /* + No return codes available, read indications now + */ + if (!data) { + if (!(e->status & DIVA_UM_IDI_RC_PENDING)) { + DBG_TRC(("A(%d) E(%08x) read data", a->adapter_nr, e)); + data = diva_data_q_get_segment4read(&e->data); + q = &e->data; + } + } else { + e->status &= ~DIVA_UM_IDI_RC_PENDING; + DBG_TRC(("A(%d) E(%08x) read rc", a->adapter_nr, e)); + } + + if (data) { + if ((length = diva_data_q_get_segment_length(q)) > + max_length) { + /* + Not enough space to read message + */ + DBG_ERR(("A: A(%d) E(%08x) read small buffer", + a->adapter_nr, e, ret)); + diva_os_leave_spin_lock(&adapter_lock, &old_irql, + "read"); + return (-2); + } + /* + Copy it to user, this function does access ONLY locked an verified + memory, also we can access it witch spin lock held + */ + + if ((ret = (*cp_fn) (os_handle, dst, data, length)) >= 0) { + /* + Acknowledge only if read was successfull + */ + diva_data_q_ack_segment4read(q); + } + } + + + DBG_TRC(("A(%d) E(%08x) read=%d", a->adapter_nr, e, ret)); + + diva_os_leave_spin_lock(&adapter_lock, &old_irql, "read"); + + return (ret); +} + + +int diva_um_idi_write(void *entity, + void *os_handle, + const void *src, + int length, divas_um_idi_copy_from_user_fn_t cp_fn) +{ + divas_um_idi_entity_t *e; + diva_um_idi_adapter_t *a; + diva_um_idi_req_hdr_t *req; + void *data; + int ret = 0; + diva_os_spin_lock_magic_t old_irql; + + diva_os_enter_spin_lock(&adapter_lock, &old_irql, "write"); + + e = (divas_um_idi_entity_t *) entity; + if (!e || (!(a = e->adapter)) || + (e->status & DIVA_UM_IDI_REMOVE_PENDING) || + (e->status & DIVA_UM_IDI_REMOVED) || + (a->status & DIVA_UM_IDI_ADAPTER_REMOVED)) { + diva_os_leave_spin_lock(&adapter_lock, &old_irql, "write"); + DBG_ERR(("E(%08x) write failed - adapter removed", e)) + return (-1); + } + + DBG_TRC(("A(%d) E(%08x) write(%d)", a->adapter_nr, e, length)); + + if ((length < sizeof(*req)) || (length > sizeof(e->buffer))) { + diva_os_leave_spin_lock(&adapter_lock, &old_irql, "write"); + return (-2); + } + + if (e->status & DIVA_UM_IDI_RC_PENDING) { + DBG_ERR(("A: A(%d) E(%08x) rc pending", a->adapter_nr, e)); + diva_os_leave_spin_lock(&adapter_lock, &old_irql, "write"); + return (-1); /* should wait for RC code first */ + } + + /* + Copy function does access only locked verified memory, + also it can be called with spin lock held + */ + if ((ret = (*cp_fn) (os_handle, e->buffer, src, length)) < 0) { + DBG_TRC(("A: A(%d) E(%08x) write error=%d", a->adapter_nr, + e, ret)); + diva_os_leave_spin_lock(&adapter_lock, &old_irql, "write"); + return (ret); + } + + req = (diva_um_idi_req_hdr_t *) & e->buffer[0]; + + switch (req->type) { + case DIVA_UM_IDI_GET_FEATURES:{ + DBG_LOG(("A(%d) get_features", a->adapter_nr)); + if (!(data = + diva_data_q_get_segment4write(&e->data))) { + DBG_ERR(("A(%d) get_features, no free buffer", + a->adapter_nr)); + diva_os_leave_spin_lock(&adapter_lock, + &old_irql, + "write"); + return (0); + } + diva_user_mode_idi_adapter_features(a, &(((diva_um_idi_ind_hdr_t + *) data)->hdr.features)); + ((diva_um_idi_ind_hdr_t *) data)->type = + DIVA_UM_IDI_IND_FEATURES; + ((diva_um_idi_ind_hdr_t *) data)->data_length = 0; + diva_data_q_ack_segment4write(&e->data, + sizeof(diva_um_idi_ind_hdr_t)); + + diva_os_leave_spin_lock(&adapter_lock, &old_irql, "write"); + + diva_os_wakeup_read(e->os_context); + } + break; + + case DIVA_UM_IDI_REQ: + case DIVA_UM_IDI_REQ_MAN: + case DIVA_UM_IDI_REQ_SIG: + case DIVA_UM_IDI_REQ_NET: + DBG_TRC(("A(%d) REQ(%02d)-(%02d)-(%08x)", a->adapter_nr, + req->Req, req->ReqCh, + req->type & DIVA_UM_IDI_REQ_TYPE_MASK)); + switch (process_idi_request(e, req)) { + case -1: + diva_os_leave_spin_lock(&adapter_lock, &old_irql, "write"); + return (-1); + case -2: + diva_os_leave_spin_lock(&adapter_lock, &old_irql, "write"); + diva_os_wakeup_read(e->os_context); + break; + default: + diva_os_leave_spin_lock(&adapter_lock, &old_irql, "write"); + break; + } + break; + + default: + diva_os_leave_spin_lock(&adapter_lock, &old_irql, "write"); + return (-1); + } + + DBG_TRC(("A(%d) E(%08x) write=%d", a->adapter_nr, e, ret)); + + return (ret); +} + +/* -------------------------------------------------------------------------- + CALLBACK FROM XDI + -------------------------------------------------------------------------- */ +static void diva_um_idi_xdi_callback(ENTITY * entity) +{ + divas_um_idi_entity_t *e = DIVAS_CONTAINING_RECORD(entity, + divas_um_idi_entity_t, + e); + diva_os_spin_lock_magic_t old_irql; + int call_wakeup = 0; + + diva_os_enter_spin_lock(&adapter_lock, &old_irql, "xdi_callback"); + + if (e->e.complete == 255) { + if (!(e->status & DIVA_UM_IDI_REMOVE_PENDING)) { + diva_um_idi_stop_wdog(e); + } + if ((call_wakeup = process_idi_rc(e, e->e.Rc))) { + if (e->rc_count) { + e->rc_count--; + } + } + e->e.Rc = 0; + diva_os_leave_spin_lock(&adapter_lock, &old_irql, "xdi_callback"); + + if (call_wakeup) { + diva_os_wakeup_read(e->os_context); + diva_os_wakeup_close(e->os_context); + } + } else { + if (e->status & DIVA_UM_IDI_REMOVE_PENDING) { + e->e.RNum = 0; + e->e.RNR = 2; + } else { + call_wakeup = process_idi_ind(e, e->e.Ind); + } + e->e.Ind = 0; + diva_os_leave_spin_lock(&adapter_lock, &old_irql, "xdi_callback"); + if (call_wakeup) { + diva_os_wakeup_read(e->os_context); + } + } +} + +static int process_idi_request(divas_um_idi_entity_t * e, + const diva_um_idi_req_hdr_t * req) +{ + int assign = 0; + byte Req = (byte) req->Req; + dword type = req->type & DIVA_UM_IDI_REQ_TYPE_MASK; + + if (!e->e.Id || !e->e.callback) { /* not assigned */ + if (Req != ASSIGN) { + DBG_ERR(("A: A(%d) E(%08x) not assigned", + e->adapter->adapter_nr, e)); + return (-1); /* NOT ASSIGNED */ + } else { + switch (type) { + case DIVA_UM_IDI_REQ_TYPE_MAN: + e->e.Id = MAN_ID; + DBG_TRC(("A(%d) E(%08x) assign MAN", + e->adapter->adapter_nr, e)); + break; + + case DIVA_UM_IDI_REQ_TYPE_SIG: + e->e.Id = DSIG_ID; + DBG_TRC(("A(%d) E(%08x) assign SIG", + e->adapter->adapter_nr, e)); + break; + + case DIVA_UM_IDI_REQ_TYPE_NET: + e->e.Id = NL_ID; + DBG_TRC(("A(%d) E(%08x) assign NET", + e->adapter->adapter_nr, e)); + break; + + default: + DBG_ERR(("A: A(%d) E(%08x) unknown type=%08x", + e->adapter->adapter_nr, e, + type)); + return (-1); + } + } + e->e.XNum = 1; + e->e.RNum = 1; + e->e.callback = diva_um_idi_xdi_callback; + e->e.X = &e->XData; + e->e.R = &e->RData; + assign = 1; + } + e->status |= DIVA_UM_IDI_RC_PENDING; + e->e.Req = Req; + e->e.ReqCh = (byte) req->ReqCh; + e->e.X->PLength = (word) req->data_length; + e->e.X->P = (byte *) & req[1]; /* Our buffer is safe */ + + DBG_TRC(("A(%d) E(%08x) request(%02x-%02x-%02x (%d))", + e->adapter->adapter_nr, e, e->e.Id, e->e.Req, + e->e.ReqCh, e->e.X->PLength)); + + e->rc_count++; + + if (e->adapter && e->adapter->d.request) { + diva_um_idi_start_wdog(e); + (*(e->adapter->d.request)) (&e->e); + } + + if (assign) { + if (e->e.Rc == OUT_OF_RESOURCES) { + /* + XDI has no entities more, call was not forwarded to the card, + no callback will be scheduled + */ + DBG_ERR(("A: A(%d) E(%08x) XDI out of entities", + e->adapter->adapter_nr, e)); + + e->e.Id = 0; + e->e.ReqCh = 0; + e->e.RcCh = 0; + e->e.Ind = 0; + e->e.IndCh = 0; + e->e.XNum = 0; + e->e.RNum = 0; + e->e.callback = NULL; + e->e.X = NULL; + e->e.R = NULL; + write_return_code(e, ASSIGN_RC | OUT_OF_RESOURCES); + return (-2); + } else { + e->status |= DIVA_UM_IDI_ASSIGN_PENDING; + } + } + + return (0); +} + +static int process_idi_rc(divas_um_idi_entity_t * e, byte rc) +{ + DBG_TRC(("A(%d) E(%08x) rc(%02x-%02x-%02x)", + e->adapter->adapter_nr, e, e->e.Id, rc, e->e.RcCh)); + + if (e->status & DIVA_UM_IDI_ASSIGN_PENDING) { + e->status &= ~DIVA_UM_IDI_ASSIGN_PENDING; + if (rc != ASSIGN_OK) { + DBG_ERR(("A: A(%d) E(%08x) ASSIGN failed", + e->adapter->adapter_nr, e)); + e->e.callback = NULL; + e->e.Id = 0; + e->e.Req = 0; + e->e.ReqCh = 0; + e->e.Rc = 0; + e->e.RcCh = 0; + e->e.Ind = 0; + e->e.IndCh = 0; + e->e.X = NULL; + e->e.R = NULL; + e->e.XNum = 0; + e->e.RNum = 0; + } + } + if ((e->e.Req == REMOVE) && e->e.Id && (rc == 0xff)) { + DBG_ERR(("A: A(%d) E(%08x) discard OK in REMOVE", + e->adapter->adapter_nr, e)); + return (0); /* let us do it in the driver */ + } + if ((e->e.Req == REMOVE) && (!e->e.Id)) { /* REMOVE COMPLETE */ + e->e.callback = NULL; + e->e.Id = 0; + e->e.Req = 0; + e->e.ReqCh = 0; + e->e.Rc = 0; + e->e.RcCh = 0; + e->e.Ind = 0; + e->e.IndCh = 0; + e->e.X = NULL; + e->e.R = NULL; + e->e.XNum = 0; + e->e.RNum = 0; + e->rc_count = 0; + } + if ((e->e.Req == REMOVE) && (rc != 0xff)) { /* REMOVE FAILED */ + DBG_ERR(("A: A(%d) E(%08x) REMOVE FAILED", + e->adapter->adapter_nr, e)); + } + write_return_code(e, rc); + + return (1); +} + +static int process_idi_ind(divas_um_idi_entity_t * e, byte ind) +{ + int do_wakeup = 0; + + if (e->e.complete != 0x02) { + diva_um_idi_ind_hdr_t *pind = + (diva_um_idi_ind_hdr_t *) + diva_data_q_get_segment4write(&e->data); + if (pind) { + e->e.RNum = 1; + e->e.R->P = (byte *) & pind[1]; + e->e.R->PLength = + (word) (diva_data_q_get_max_length(&e->data) - + sizeof(*pind)); + DBG_TRC(("A(%d) E(%08x) ind_1(%02x-%02x-%02x)-[%d-%d]", + e->adapter->adapter_nr, e, e->e.Id, ind, + e->e.IndCh, e->e.RLength, + e->e.R->PLength)); + + } else { + DBG_TRC(("A(%d) E(%08x) ind(%02x-%02x-%02x)-RNR", + e->adapter->adapter_nr, e, e->e.Id, ind, + e->e.IndCh)); + e->e.RNum = 0; + e->e.RNR = 1; + do_wakeup = 1; + } + } else { + diva_um_idi_ind_hdr_t *pind = + (diva_um_idi_ind_hdr_t *) (e->e.R->P); + + DBG_TRC(("A(%d) E(%08x) ind(%02x-%02x-%02x)-[%d]", + e->adapter->adapter_nr, e, e->e.Id, ind, + e->e.IndCh, e->e.R->PLength)); + + pind--; + pind->type = DIVA_UM_IDI_IND; + pind->hdr.ind.Ind = ind; + pind->hdr.ind.IndCh = e->e.IndCh; + pind->data_length = e->e.R->PLength; + diva_data_q_ack_segment4write(&e->data, + (int) (sizeof(*pind) + + e->e.R->PLength)); + do_wakeup = 1; + } + + if ((e->status & DIVA_UM_IDI_RC_PENDING) && !e->rc.count) { + do_wakeup = 0; + } + + return (do_wakeup); +} + +/* -------------------------------------------------------------------------- + Write return code to the return code queue of entity + -------------------------------------------------------------------------- */ +static int write_return_code(divas_um_idi_entity_t * e, byte rc) +{ + diva_um_idi_ind_hdr_t *prc; + + if (!(prc = + (diva_um_idi_ind_hdr_t *) diva_data_q_get_segment4write(&e->rc))) + { + DBG_ERR(("A: A(%d) E(%08x) rc(%02x) lost", + e->adapter->adapter_nr, e, rc)); + e->status &= ~DIVA_UM_IDI_RC_PENDING; + return (-1); + } + + prc->type = DIVA_UM_IDI_IND_RC; + prc->hdr.rc.Rc = rc; + prc->hdr.rc.RcCh = e->e.RcCh; + prc->data_length = 0; + diva_data_q_ack_segment4write(&e->rc, sizeof(*prc)); + + return (0); +} + +/* -------------------------------------------------------------------------- + Return amount of entries that can be bead from this entity or + -1 if adapter was removed + -------------------------------------------------------------------------- */ +int diva_user_mode_idi_ind_ready(void *entity, void *os_handle) +{ + divas_um_idi_entity_t *e; + diva_um_idi_adapter_t *a; + diva_os_spin_lock_magic_t old_irql; + int ret; + + if (!entity) + return (-1); + diva_os_enter_spin_lock(&adapter_lock, &old_irql, "ind_ready"); + e = (divas_um_idi_entity_t *) entity; + a = e->adapter; + + if ((!a) || (a->status & DIVA_UM_IDI_ADAPTER_REMOVED)) { + /* + Adapter was unloaded + */ + diva_os_leave_spin_lock(&adapter_lock, &old_irql, "ind_ready"); + return (-1); /* adapter was removed */ + } + if (e->status & DIVA_UM_IDI_REMOVED) { + /* + entity was removed as result of adapter removal + user should assign this entity again + */ + diva_os_leave_spin_lock(&adapter_lock, &old_irql, "ind_ready"); + return (-1); + } + + ret = e->rc.count + e->data.count; + + if ((e->status & DIVA_UM_IDI_RC_PENDING) && !e->rc.count) { + ret = 0; + } + + diva_os_leave_spin_lock(&adapter_lock, &old_irql, "ind_ready"); + + return (ret); +} + +void *diva_um_id_get_os_context(void *entity) +{ + return (((divas_um_idi_entity_t *) entity)->os_context); +} + +int divas_um_idi_entity_assigned(void *entity) +{ + divas_um_idi_entity_t *e; + diva_um_idi_adapter_t *a; + int ret; + diva_os_spin_lock_magic_t old_irql; + + diva_os_enter_spin_lock(&adapter_lock, &old_irql, "assigned?"); + + + e = (divas_um_idi_entity_t *) entity; + if (!e || (!(a = e->adapter)) || + (e->status & DIVA_UM_IDI_REMOVED) || + (a->status & DIVA_UM_IDI_ADAPTER_REMOVED)) { + diva_os_leave_spin_lock(&adapter_lock, &old_irql, "assigned?"); + return (0); + } + + e->status |= DIVA_UM_IDI_REMOVE_PENDING; + + ret = (e->e.Id || e->rc_count + || (e->status & DIVA_UM_IDI_ASSIGN_PENDING)); + + DBG_TRC(("Id:%02x, rc_count:%d, status:%08x", e->e.Id, e->rc_count, + e->status)) + + diva_os_leave_spin_lock(&adapter_lock, &old_irql, "assigned?"); + + return (ret); +} + +int divas_um_idi_entity_start_remove(void *entity) +{ + divas_um_idi_entity_t *e; + diva_um_idi_adapter_t *a; + diva_os_spin_lock_magic_t old_irql; + + diva_os_enter_spin_lock(&adapter_lock, &old_irql, "start_remove"); + + e = (divas_um_idi_entity_t *) entity; + if (!e || (!(a = e->adapter)) || + (e->status & DIVA_UM_IDI_REMOVED) || + (a->status & DIVA_UM_IDI_ADAPTER_REMOVED)) { + diva_os_leave_spin_lock(&adapter_lock, &old_irql, "start_remove"); + return (0); + } + + if (e->rc_count) { + /* + Entity BUSY + */ + diva_os_leave_spin_lock(&adapter_lock, &old_irql, "start_remove"); + return (1); + } + + if (!e->e.Id) { + /* + Remove request was already pending, and arrived now + */ + diva_os_leave_spin_lock(&adapter_lock, &old_irql, "start_remove"); + return (0); /* REMOVE was pending */ + } + + /* + Now send remove request + */ + e->e.Req = REMOVE; + e->e.ReqCh = 0; + + e->rc_count++; + + DBG_TRC(("A(%d) E(%08x) request(%02x-%02x-%02x (%d))", + e->adapter->adapter_nr, e, e->e.Id, e->e.Req, + e->e.ReqCh, e->e.X->PLength)); + + if (a->d.request) + (*(a->d.request)) (&e->e); + + diva_os_leave_spin_lock(&adapter_lock, &old_irql, "start_remove"); + + return (0); +} diff --git a/drivers/isdn/hardware/eicon/um_idi.h b/drivers/isdn/hardware/eicon/um_idi.h new file mode 100644 index 000000000000..141072f8881e --- /dev/null +++ b/drivers/isdn/hardware/eicon/um_idi.h @@ -0,0 +1,43 @@ +/* $Id: um_idi.h,v 1.6 2004/03/21 17:26:01 armin Exp $ */ + +#ifndef __DIVA_USER_MODE_IDI_CORE_H__ +#define __DIVA_USER_MODE_IDI_CORE_H__ + + +/* + interface between UM IDI core and OS dependent part + */ +int diva_user_mode_idi_init(void); +void diva_user_mode_idi_finit(void); +void *divas_um_idi_create_entity(dword adapter_nr, void *file); +int divas_um_idi_delete_entity(int adapter_nr, void *entity); + +typedef int (*divas_um_idi_copy_to_user_fn_t) (void *os_handle, + void *dst, + const void *src, + int length); +typedef int (*divas_um_idi_copy_from_user_fn_t) (void *os_handle, + void *dst, + const void *src, + int length); + +int diva_um_idi_read(void *entity, + void *os_handle, + void *dst, + int max_length, divas_um_idi_copy_to_user_fn_t cp_fn); + +int diva_um_idi_write(void *entity, + void *os_handle, + const void *src, + int length, divas_um_idi_copy_from_user_fn_t cp_fn); + +int diva_user_mode_idi_ind_ready(void *entity, void *os_handle); +void *diva_um_id_get_os_context(void *entity); +int diva_os_get_context_size(void); +int divas_um_idi_entity_assigned(void *entity); +int divas_um_idi_entity_start_remove(void *entity); + +void diva_um_idi_start_wdog(void *entity); +void diva_um_idi_stop_wdog(void *entity); + +#endif diff --git a/drivers/isdn/hardware/eicon/um_xdi.h b/drivers/isdn/hardware/eicon/um_xdi.h new file mode 100644 index 000000000000..b48fc042a5bc --- /dev/null +++ b/drivers/isdn/hardware/eicon/um_xdi.h @@ -0,0 +1,68 @@ +/* $Id: um_xdi.h,v 1.1.2.2 2002/10/02 14:38:38 armin Exp $ */ + +#ifndef __DIVA_USER_MODE_XDI_H__ +#define __DIVA_USER_MODE_XDI_H__ + +/* + Contains declaratiom of structures shared between application + and user mode idi driver +*/ + +typedef struct _diva_um_idi_adapter_features { + dword type; + dword features; + dword channels; + dword serial_number; + char name[128]; +} diva_um_idi_adapter_features_t; + +#define DIVA_UM_IDI_REQ_MASK 0x0000FFFF +#define DIVA_UM_IDI_REQ_TYPE_MASK (~(DIVA_UM_IDI_REQ_MASK)) +#define DIVA_UM_IDI_GET_FEATURES 1 /* trigger features indication */ +#define DIVA_UM_IDI_REQ 2 +#define DIVA_UM_IDI_REQ_TYPE_MAN 0x10000000 +#define DIVA_UM_IDI_REQ_TYPE_SIG 0x20000000 +#define DIVA_UM_IDI_REQ_TYPE_NET 0x30000000 +#define DIVA_UM_IDI_REQ_MAN (DIVA_UM_IDI_REQ | DIVA_UM_IDI_REQ_TYPE_MAN) +#define DIVA_UM_IDI_REQ_SIG (DIVA_UM_IDI_REQ | DIVA_UM_IDI_REQ_TYPE_SIG) +#define DIVA_UM_IDI_REQ_NET (DIVA_UM_IDI_REQ | DIVA_UM_IDI_REQ_TYPE_NET) +/* + data_length bytes will follow this structure +*/ +typedef struct _diva_um_idi_req_hdr { + dword type; + dword Req; + dword ReqCh; + dword data_length; +} diva_um_idi_req_hdr_t; + +typedef struct _diva_um_idi_ind_parameters { + dword Ind; + dword IndCh; +} diva_um_idi_ind_parameters_t; + +typedef struct _diva_um_idi_rc_parameters { + dword Rc; + dword RcCh; +} diva_um_idi_rc_parameters_t; + +typedef union _diva_um_idi_ind { + diva_um_idi_adapter_features_t features; + diva_um_idi_ind_parameters_t ind; + diva_um_idi_rc_parameters_t rc; +} diva_um_idi_ind_t; + +#define DIVA_UM_IDI_IND_FEATURES 1 /* features indication */ +#define DIVA_UM_IDI_IND 2 +#define DIVA_UM_IDI_IND_RC 3 +/* + data_length bytes of data follow + this structure +*/ +typedef struct _diva_um_idi_ind_hdr { + dword type; + diva_um_idi_ind_t hdr; + dword data_length; +} diva_um_idi_ind_hdr_t; + +#endif diff --git a/drivers/isdn/hardware/eicon/xdi_adapter.h b/drivers/isdn/hardware/eicon/xdi_adapter.h new file mode 100644 index 000000000000..a3bd163afb8f --- /dev/null +++ b/drivers/isdn/hardware/eicon/xdi_adapter.h @@ -0,0 +1,70 @@ +/* $Id: xdi_adapter.h,v 1.7 2004/03/21 17:26:01 armin Exp $ */ + +#ifndef __DIVA_OS_XDI_ADAPTER_H__ +#define __DIVA_OS_XDI_ADAPTER_H__ + +#define DIVAS_XDI_ADAPTER_BUS_PCI 0 +#define DIVAS_XDI_ADAPTER_BUS_ISA 1 + +typedef struct _divas_pci_card_resources { + byte bus; + byte func; + void *hdev; + + dword bar[8]; /* contains context of appropriate BAR Register */ + void __iomem *addr[8]; /* same bar, but mapped into memory */ + dword length[8]; /* bar length */ + int mem_type_id[MAX_MEM_TYPE]; + unsigned int qoffset; + byte irq; +} divas_pci_card_resources_t; + +typedef union _divas_card_resources { + divas_pci_card_resources_t pci; +} divas_card_resources_t; + +struct _diva_os_xdi_adapter; +typedef int (*diva_init_card_proc_t) (struct _diva_os_xdi_adapter * a); +typedef int (*diva_cmd_card_proc_t) (struct _diva_os_xdi_adapter * a, + diva_xdi_um_cfg_cmd_t * data, + int length); +typedef void (*diva_xdi_clear_interrupts_proc_t) (struct + _diva_os_xdi_adapter *); + +#define DIVA_XDI_MBOX_BUSY 1 +#define DIVA_XDI_MBOX_WAIT_XLOG 2 + +typedef struct _xdi_mbox_t { + dword status; + diva_xdi_um_cfg_cmd_data_t cmd_data; + dword data_length; + void *data; +} xdi_mbox_t; + +typedef struct _diva_os_idi_adapter_interface { + diva_init_card_proc_t cleanup_adapter_proc; + diva_cmd_card_proc_t cmd_proc; +} diva_os_idi_adapter_interface_t; + +typedef struct _diva_os_xdi_adapter { + struct list_head link; + int CardIndex; + int CardOrdinal; + int controller; /* number of this controller */ + int Bus; /* PCI, ISA, ... */ + divas_card_resources_t resources; + char port_name[24]; + ISDN_ADAPTER xdi_adapter; + xdi_mbox_t xdi_mbox; + diva_os_idi_adapter_interface_t interface; + struct _diva_os_xdi_adapter *slave_adapters[3]; + void *slave_list; + void *proc_adapter_dir; /* adapterX proc entry */ + void *proc_info; /* info proc entry */ + void *proc_grp_opt; /* group_optimization */ + void *proc_d_l1_down; /* dynamic_l1_down */ + volatile diva_xdi_clear_interrupts_proc_t clear_interrupts_proc; + dword dsp_mask; +} diva_os_xdi_adapter_t; + +#endif diff --git a/drivers/isdn/hardware/eicon/xdi_msg.h b/drivers/isdn/hardware/eicon/xdi_msg.h new file mode 100644 index 000000000000..3ade28f66698 --- /dev/null +++ b/drivers/isdn/hardware/eicon/xdi_msg.h @@ -0,0 +1,127 @@ +/* $Id: xdi_msg.h,v 1.1.2.2 2001/02/16 08:40:36 armin Exp $ */ + +#ifndef __DIVA_XDI_UM_CFG_MESSSGE_H__ +#define __DIVA_XDI_UM_CFG_MESSAGE_H__ + +/* + Definition of messages used to communicate between + XDI device driver and user mode configuration utility +*/ + +/* + As acknowledge one DWORD - card ordinal will be read from the card +*/ +#define DIVA_XDI_UM_CMD_GET_CARD_ORDINAL 0 + +/* + no acknowledge will be generated, memory block will be written in the + memory at given offset +*/ +#define DIVA_XDI_UM_CMD_WRITE_SDRAM_BLOCK 1 + +/* + no acknowledge will be genatated, FPGA will be programmed +*/ +#define DIVA_XDI_UM_CMD_WRITE_FPGA 2 + +/* + As acknowledge block of SDRAM will be read in the user buffer +*/ +#define DIVA_XDI_UM_CMD_READ_SDRAM 3 + +/* + As acknowledge dword with serial number will be read in the user buffer +*/ +#define DIVA_XDI_UM_CMD_GET_SERIAL_NR 4 + +/* + As acknowledge struct consisting from 9 dwords with PCI info. + dword[0...7] = 8 PCI BARS + dword[9] = IRQ +*/ +#define DIVA_XDI_UM_CMD_GET_PCI_HW_CONFIG 5 + +/* + Reset of the board + activation of primary + boot loader +*/ +#define DIVA_XDI_UM_CMD_RESET_ADAPTER 6 + +/* + Called after code download to start adapter + at specified address + Start does set new set of features due to fact that we not know + if protocol features have changed +*/ +#define DIVA_XDI_UM_CMD_START_ADAPTER 7 + +/* + Stop adapter, called if user + wishes to stop adapter without unload + of the driver, to reload adapter with + different protocol +*/ +#define DIVA_XDI_UM_CMD_STOP_ADAPTER 8 + +/* + Get state of current adapter + Acknowledge is one dword with following values: + 0 - adapter ready for download + 1 - adapter running + 2 - adapter dead + 3 - out of service, driver should be restarted or hardware problem +*/ +#define DIVA_XDI_UM_CMD_GET_CARD_STATE 9 + +/* + Reads XLOG entry from the card +*/ +#define DIVA_XDI_UM_CMD_READ_XLOG_ENTRY 10 + +/* + Set untranslated protocol code features + */ +#define DIVA_XDI_UM_CMD_SET_PROTOCOL_FEATURES 11 + +typedef struct _diva_xdi_um_cfg_cmd_data_set_features { + dword features; +} diva_xdi_um_cfg_cmd_data_set_features_t; + +typedef struct _diva_xdi_um_cfg_cmd_data_start { + dword offset; + dword features; +} diva_xdi_um_cfg_cmd_data_start_t; + +typedef struct _diva_xdi_um_cfg_cmd_data_write_sdram { + dword ram_number; + dword offset; + dword length; +} diva_xdi_um_cfg_cmd_data_write_sdram_t; + +typedef struct _diva_xdi_um_cfg_cmd_data_write_fpga { + dword fpga_number; + dword image_length; +} diva_xdi_um_cfg_cmd_data_write_fpga_t; + +typedef struct _diva_xdi_um_cfg_cmd_data_read_sdram { + dword ram_number; + dword offset; + dword length; +} diva_xdi_um_cfg_cmd_data_read_sdram_t; + +typedef union _diva_xdi_um_cfg_cmd_data { + diva_xdi_um_cfg_cmd_data_write_sdram_t write_sdram; + diva_xdi_um_cfg_cmd_data_write_fpga_t write_fpga; + diva_xdi_um_cfg_cmd_data_read_sdram_t read_sdram; + diva_xdi_um_cfg_cmd_data_start_t start; + diva_xdi_um_cfg_cmd_data_set_features_t features; +} diva_xdi_um_cfg_cmd_data_t; + +typedef struct _diva_xdi_um_cfg_cmd { + dword adapter; /* Adapter number 1...N */ + dword command; + diva_xdi_um_cfg_cmd_data_t command_data; + dword data_length; /* Plain binary data will follow */ +} diva_xdi_um_cfg_cmd_t; + +#endif diff --git a/drivers/isdn/hardware/eicon/xdi_vers.h b/drivers/isdn/hardware/eicon/xdi_vers.h new file mode 100644 index 000000000000..cf3494185b9d --- /dev/null +++ b/drivers/isdn/hardware/eicon/xdi_vers.h @@ -0,0 +1,26 @@ + +/* + * + Copyright (c) Eicon Networks, 2002. + * + This source file is supplied for the use with + Eicon Networks range of DIVA Server Adapters. + * + Eicon File Revision : 2.1 + * + 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 OF ANY KIND WHATSOEVER INCLUDING ANY + 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. + * + */ +static char diva_xdi_common_code_build[] = "102-52"; diff --git a/drivers/isdn/hisax/Kconfig b/drivers/isdn/hisax/Kconfig new file mode 100644 index 000000000000..6c7b8bffc6fd --- /dev/null +++ b/drivers/isdn/hisax/Kconfig @@ -0,0 +1,442 @@ + +menu "Passive cards" + depends on ISDN_I4L + +config ISDN_DRV_HISAX + tristate "HiSax SiemensChipSet driver support" + select CRC_CCITT + ---help--- + This is a driver supporting the Siemens chipset on various + ISDN-cards (like AVM A1, Elsa ISDN cards, Teles S0-16.0, Teles + S0-16.3, Teles S0-8, Teles/Creatix PnP, ITK micro ix1 and many + compatibles). + + HiSax is just the name of this driver, not the name of any hardware. + + If you have a card with such a chipset, you should say Y here and + also to the configuration option of the driver for your particular + card, below. + +if ISDN_DRV_HISAX!=n + +comment "D-channel protocol features" + +config HISAX_EURO + bool "HiSax Support for EURO/DSS1" + help + Say Y or N according to the D-channel protocol which your local + telephone service company provides. + + The call control protocol E-DSS1 is used in most European countries. + If unsure, say Y. + +config DE_AOC + bool "Support for german chargeinfo" + depends on HISAX_EURO + help + If you want that the HiSax hardware driver sends messages to the + upper level of the isdn code on each AOCD (Advice Of Charge, During + the call -- transmission of the fee information during a call) and + on each AOCE (Advice Of Charge, at the End of the call -- + transmission of fee information at the end of the call), say Y here. + This works only in Germany. + +config HISAX_NO_SENDCOMPLETE + bool "Disable sending complete" + depends on HISAX_EURO + help + If you have trouble with some ugly exchanges or you live in + Australia select this option. + +config HISAX_NO_LLC + bool "Disable sending low layer compatibility" + depends on HISAX_EURO + help + If you have trouble with some ugly exchanges try to select this + option. + +config HISAX_NO_KEYPAD + bool "Disable keypad protocol option" + depends on HISAX_EURO + help + If you like to send special dial strings including * or # without + using the keypad protocol, select this option. + +config HISAX_1TR6 + bool "HiSax Support for german 1TR6" + help + Say Y or N according to the D-channel protocol which your local + telephone service company provides. + + 1TR6 is an old call control protocol which was used in Germany + before E-DSS1 was established. Nowadays, all new lines in Germany + use E-DSS1. + +config HISAX_NI1 + bool "HiSax Support for US NI1" + help + Enable this if you like to use ISDN in US on a NI1 basic rate + interface. + +config HISAX_MAX_CARDS + int "Maximum number of cards supported by HiSax" + default "8" + help + This option allows you to specify the maximum number of cards which + the HiSax driver will be able to handle. + +comment "HiSax supported cards" + +config HISAX_16_0 + bool "Teles 16.0/8.0" + depends on ISA + help + This enables HiSax support for the Teles ISDN-cards S0-16.0, S0-8 + and many compatibles. + + See <file:Documentation/isdn/README.HiSax> on how to configure it + using the different cards, a different D-channel protocol, or + non-standard IRQ/port/shmem settings. + +config HISAX_16_3 + bool "Teles 16.3 or PNP or PCMCIA" + help + This enables HiSax support for the Teles ISDN-cards S0-16.3 the + Teles/Creatix PnP and the Teles PCMCIA. + + See <file:Documentation/isdn/README.HiSax> on how to configure it + using the different cards, a different D-channel protocol, or + non-standard IRQ/port settings. + +config HISAX_TELESPCI + bool "Teles PCI" + depends on PCI && (BROKEN || !(SPARC64 || PPC)) + help + This enables HiSax support for the Teles PCI. + See <file:Documentation/isdn/README.HiSax> on how to configure it. + +config HISAX_S0BOX + bool "Teles S0Box" + help + This enables HiSax support for the Teles/Creatix parallel port + S0BOX. See <file:Documentation/isdn/README.HiSax> on how to + configure it. + +config HISAX_AVM_A1 + bool "AVM A1 (Fritz)" + depends on ISA + help + This enables HiSax support for the AVM A1 (aka "Fritz"). + + See <file:Documentation/isdn/README.HiSax> on how to configure it + using the different cards, a different D-channel protocol, or + non-standard IRQ/port settings. + +config HISAX_FRITZPCI + bool "AVM PnP/PCI (Fritz!PnP/PCI)" + help + This enables HiSax support for the AVM "Fritz!PnP" and "Fritz!PCI". + See <file:Documentation/isdn/README.HiSax> on how to configure it. + +config HISAX_AVM_A1_PCMCIA + bool "AVM A1 PCMCIA (Fritz)" + help + This enables HiSax support for the AVM A1 "Fritz!PCMCIA"). + See <file:Documentation/isdn/README.HiSax> on how to configure it. + +config HISAX_ELSA + bool "Elsa cards" + help + This enables HiSax support for the Elsa Mircolink ISA cards, for the + Elsa Quickstep series cards and Elsa PCMCIA. + + See <file:Documentation/isdn/README.HiSax> on how to configure it + using the different cards, a different D-channel protocol, or + non-standard IRQ/port settings. + +config HISAX_IX1MICROR2 + bool "ITK ix1-micro Revision 2" + depends on ISA + help + This enables HiSax support for the ITK ix1-micro Revision 2 card. + + See <file:Documentation/isdn/README.HiSax> on how to configure it + using the different cards, a different D-channel protocol, or + non-standard IRQ/port settings. + +config HISAX_DIEHLDIVA + bool "Eicon.Diehl Diva cards" + help + This enables HiSax support for the Eicon.Diehl Diva none PRO + versions passive ISDN cards. + + See <file:Documentation/isdn/README.HiSax> on how to configure it + using the different cards, a different D-channel protocol, or + non-standard IRQ/port settings. + +config HISAX_ASUSCOM + bool "ASUSCOM ISA cards" + depends on ISA + help + This enables HiSax support for the AsusCom and their OEM versions + passive ISDN ISA cards. + + See <file:Documentation/isdn/README.HiSax> on how to configure it + using the different cards, a different D-channel protocol, or + non-standard IRQ/port settings. + +config HISAX_TELEINT + bool "TELEINT cards" + depends on ISA + help + This enables HiSax support for the TELEINT SA1 semiactiv ISDN card. + + See <file:Documentation/isdn/README.HiSax> on how to configure it + using the different cards, a different D-channel protocol, or + non-standard IRQ/port settings. + +config HISAX_HFCS + bool "HFC-S based cards" + depends on ISA + help + This enables HiSax support for the HFC-S 2BDS0 based cards, like + teles 16.3c. + + See <file:Documentation/isdn/README.HiSax> on how to configure it + using the different cards, a different D-channel protocol, or + non-standard IRQ/port settings. + +config HISAX_SEDLBAUER + bool "Sedlbauer cards" + help + This enables HiSax support for the Sedlbauer passive ISDN cards. + + See <file:Documentation/isdn/README.HiSax> on how to configure it + using the different cards, a different D-channel protocol, or + non-standard IRQ/port settings. + +config HISAX_SPORTSTER + bool "USR Sportster internal TA" + depends on ISA + help + This enables HiSax support for the USR Sportster internal TA card. + + See <file:Documentation/isdn/README.HiSax> on how to configure it + using a different D-channel protocol, or non-standard IRQ/port + settings. + +config HISAX_MIC + bool "MIC card" + depends on ISA + help + This enables HiSax support for the ITH MIC card. + + See <file:Documentation/isdn/README.HiSax> on how to configure it + using a different D-channel protocol, or non-standard IRQ/port + settings. + +config HISAX_NETJET + bool "NETjet card" + depends on PCI && (BROKEN || !(SPARC64 || PPC)) + help + This enables HiSax support for the NetJet from Traverse + Technologies. + + See <file:Documentation/isdn/README.HiSax> on how to configure it + using a different D-channel protocol, or non-standard IRQ/port + settings. + +config HISAX_NETJET_U + bool "NETspider U card" + depends on PCI && (BROKEN || !(SPARC64 || PPC)) + help + This enables HiSax support for the Netspider U interface ISDN card + from Traverse Technologies. + See <file:Documentation/isdn/README.HiSax> on how to configure it + using a different D-channel protocol, or non-standard IRQ/port + settings. + +config HISAX_NICCY + bool "Niccy PnP/PCI card" + help + This enables HiSax support for the Dr. Neuhaus Niccy PnP or PCI. + + See <file:Documentation/isdn/README.HiSax> on how to configure it + using a different D-channel protocol, or non-standard IRQ/port + settings. + +config HISAX_ISURF + bool "Siemens I-Surf card" + depends on ISA + help + This enables HiSax support for the Siemens I-Talk/I-Surf card with + ISAR chip. + See <file:Documentation/isdn/README.HiSax> on how to configure it + using a different D-channel protocol, or non-standard IRQ/port + settings. + +config HISAX_HSTSAPHIR + bool "HST Saphir card" + depends on ISA + help + This enables HiSax support for the HST Saphir card. + + See <file:Documentation/isdn/README.HiSax> on how to configure it + using a different D-channel protocol, or non-standard IRQ/port + settings. + +config HISAX_BKM_A4T + bool "Telekom A4T card" + depends on PCI + help + This enables HiSax support for the Telekom A4T card. + + See <file:Documentation/isdn/README.HiSax> on how to configure it + using a different D-channel protocol, or non-standard IRQ/port + settings. + +config HISAX_SCT_QUADRO + bool "Scitel Quadro card" + depends on PCI + help + This enables HiSax support for the Scitel Quadro card. + + See <file:Documentation/isdn/README.HiSax> on how to configure it + using a different D-channel protocol, or non-standard IRQ/port + settings. + +config HISAX_GAZEL + bool "Gazel cards" + help + This enables HiSax support for the Gazel cards. + + See <file:Documentation/isdn/README.HiSax> on how to configure it + using a different D-channel protocol, or non-standard IRQ/port + settings. + +config HISAX_HFC_PCI + bool "HFC PCI-Bus cards" + depends on PCI && (BROKEN || !(SPARC64 || PPC)) + help + This enables HiSax support for the HFC-S PCI 2BDS0 based cards. + + For more informations see under + <file:Documentation/isdn/README.hfc-pci>. + +config HISAX_W6692 + bool "Winbond W6692 based cards" + depends on PCI + help + This enables HiSax support for Winbond W6692 based PCI ISDN cards. + + See <file:Documentation/isdn/README.HiSax> on how to configure it + using a different D-channel protocol, or non-standard IRQ/port + settings. + +config HISAX_HFC_SX + bool "HFC-S+, HFC-SP, HFC-PCMCIA cards" + help + This enables HiSax support for the HFC-S+, HFC-SP and HFC-PCMCIA + cards. This code is not finished yet. + +# bool ' TESTEMULATOR (EXPERIMENTAL)' CONFIG_HISAX_TESTEMU + +config HISAX_ENTERNOW_PCI + bool "Formula-n enter:now PCI card" + depends on PCI && (BROKEN || !(SPARC64 || PPC)) + help + This enables HiSax support for the Formula-n enter:now PCI + ISDN card. + +config HISAX_AMD7930 + bool "Am7930 (EXPERIMENTAL)" + depends on EXPERIMENTAL && (SPARC32 || SPARC64) + help + This enables HiSax support for the AMD7930 chips on some SPARCs. + This code is not finished yet. + +endif + +if ISDN_DRV_HISAX + +config HISAX_DEBUG + bool "HiSax debugging" + help + This enables debugging code in the new-style HiSax drivers, i.e. + the ST5481 USB driver currently. + If in doubt, say yes. + +comment "HiSax PCMCIA card service modules" + +config HISAX_SEDLBAUER_CS + tristate "Sedlbauer PCMCIA cards" + depends on PCMCIA && HISAX_SEDLBAUER + help + This enables the PCMCIA client driver for the Sedlbauer Speed Star + and Speed Star II cards. + +config HISAX_ELSA_CS + tristate "ELSA PCMCIA MicroLink cards" + depends on PCMCIA && HISAX_ELSA + help + This enables the PCMCIA client driver for the Elsa PCMCIA MicroLink + card. + +config HISAX_AVM_A1_CS + tristate "AVM A1 PCMCIA cards" + depends on PCMCIA && ISDN_DRV_HISAX + help + This enables the PCMCIA client driver for the AVM A1 / Fritz!Card + PCMCIA cards. + +config HISAX_TELES_CS + tristate "TELES PCMCIA cards" + depends on PCMCIA && HISAX_16_3 + help + This enables the PCMCIA client driver for the Teles PCMCIA cards. + +comment "HiSax sub driver modules" + +config HISAX_ST5481 + tristate "ST5481 USB ISDN modem (EXPERIMENTAL)" + depends on USB && EXPERIMENTAL + select CRC_CCITT + help + This enables the driver for ST5481 based USB ISDN adapters, + e.g. the BeWan Gazel 128 USB + +config HISAX_HFCUSB + tristate "HFC USB based ISDN modems (EXPERIMENTAL)" + depends on USB && EXPERIMENTAL + help + This enables the driver for HFC USB based ISDN modems. + +config HISAX_HFC4S8S + tristate "HFC-4S/8S based ISDN cards (EXPERIMENTAL)" + depends on EXPERIMENTAL + help + This enables the driver for HFC-4S/8S based ISDN cards. + +config HISAX_FRITZ_PCIPNP + tristate "AVM Fritz!Card PCI/PCIv2/PnP support (EXPERIMENTAL)" + depends on PCI && EXPERIMENTAL + help + This enables the driver for the AVM Fritz!Card PCI, + Fritz!Card PCI v2 and Fritz!Card PnP. + (the latter also needs you to select "ISA Plug and Play support" + from the menu "Plug and Play configuration") + +config HISAX_HDLC + bool + depends on HISAX_ST5481 + default y + +config HISAX_AVM_A1_PCMCIA + bool + depends on HISAX_AVM_A1_CS + default y + +endif + +endmenu + diff --git a/drivers/isdn/hisax/Makefile b/drivers/isdn/hisax/Makefile new file mode 100644 index 000000000000..8d6bb56754b8 --- /dev/null +++ b/drivers/isdn/hisax/Makefile @@ -0,0 +1,64 @@ +# Makefile for the hisax ISDN device driver + +# The target object and module list name. + +# Define maximum number of cards + +EXTRA_CFLAGS += -DHISAX_MAX_CARDS=$(CONFIG_HISAX_MAX_CARDS) + +obj-$(CONFIG_ISDN_DRV_HISAX) += hisax.o +obj-$(CONFIG_HISAX_SEDLBAUER_CS) += sedlbauer_cs.o +obj-$(CONFIG_HISAX_ELSA_CS) += elsa_cs.o +obj-$(CONFIG_HISAX_AVM_A1_CS) += avma1_cs.o +obj-$(CONFIG_HISAX_TELES_CS) += teles_cs.o +obj-$(CONFIG_HISAX_ST5481) += hisax_st5481.o +obj-$(CONFIG_HISAX_HFCUSB) += hfc_usb.o +obj-$(CONFIG_HISAX_HFC4S8S) += hfc4s8s_l1.o +obj-$(CONFIG_HISAX_FRITZ_PCIPNP) += hisax_isac.o hisax_fcpcipnp.o + +ifdef CONFIG_HISAX_HDLC +obj-$(CONFIG_ISDN_DRV_HISAX) += isdnhdlc.o +endif + +# Multipart objects. + +hisax_st5481-y := st5481_init.o st5481_usb.o st5481_d.o \ + st5481_b.o st5481_hdlc.o + +hisax-y := config.o isdnl1.o tei.o isdnl2.o isdnl3.o \ + lmgr.o q931.o callc.o fsm.o +hisax-$(CONFIG_HISAX_EURO) += l3dss1.o +hisax-$(CONFIG_HISAX_NI1) += l3ni1.o +hisax-$(CONFIG_HISAX_1TR6) += l3_1tr6.o + +hisax-$(CONFIG_HISAX_16_0) += teles0.o isac.o arcofi.o hscx.o +hisax-$(CONFIG_HISAX_16_3) += teles3.o isac.o arcofi.o hscx.o +hisax-$(CONFIG_HISAX_TELESPCI) += telespci.o isac.o arcofi.o hscx.o +hisax-$(CONFIG_HISAX_S0BOX) += s0box.o isac.o arcofi.o hscx.o +hisax-$(CONFIG_HISAX_AVM_A1) += avm_a1.o isac.o arcofi.o hscx.o +hisax-$(CONFIG_HISAX_AVM_A1_PCMCIA) += avm_a1p.o isac.o arcofi.o hscx.o +hisax-$(CONFIG_HISAX_FRITZPCI) += avm_pci.o isac.o arcofi.o +hisax-$(CONFIG_HISAX_ELSA) += elsa.o isac.o arcofi.o hscx.o +hisax-$(CONFIG_HISAX_IX1MICROR2) += ix1_micro.o isac.o arcofi.o hscx.o +hisax-$(CONFIG_HISAX_DIEHLDIVA) += diva.o isac.o arcofi.o hscx.o ipacx.o +hisax-$(CONFIG_HISAX_ASUSCOM) += asuscom.o isac.o arcofi.o hscx.o +hisax-$(CONFIG_HISAX_TELEINT) += teleint.o isac.o arcofi.o hfc_2bs0.o +hisax-$(CONFIG_HISAX_SEDLBAUER) += sedlbauer.o isac.o arcofi.o hscx.o \ + isar.o +hisax-$(CONFIG_HISAX_SPORTSTER) += sportster.o isac.o arcofi.o hscx.o +hisax-$(CONFIG_HISAX_MIC) += mic.o isac.o arcofi.o hscx.o +hisax-$(CONFIG_HISAX_NETJET) += nj_s.o netjet.o isac.o arcofi.o +hisax-$(CONFIG_HISAX_NETJET_U) += nj_u.o netjet.o icc.o +hisax-$(CONFIG_HISAX_HFCS) += hfcscard.o hfc_2bds0.o +hisax-$(CONFIG_HISAX_HFC_PCI) += hfc_pci.o +hisax-$(CONFIG_HISAX_HFC_SX) += hfc_sx.o +hisax-$(CONFIG_HISAX_NICCY) += niccy.o isac.o arcofi.o hscx.o +hisax-$(CONFIG_HISAX_ISURF) += isurf.o isac.o arcofi.o isar.o +hisax-$(CONFIG_HISAX_HSTSAPHIR) += saphir.o isac.o arcofi.o hscx.o +hisax-$(CONFIG_HISAX_BKM_A4T) += bkm_a4t.o isac.o arcofi.o jade.o +hisax-$(CONFIG_HISAX_SCT_QUADRO) += bkm_a8.o isac.o arcofi.o hscx.o +hisax-$(CONFIG_HISAX_GAZEL) += gazel.o isac.o arcofi.o hscx.o +hisax-$(CONFIG_HISAX_W6692) += w6692.o +hisax-$(CONFIG_HISAX_ENTERNOW_PCI) += enternow_pci.o amd7930_fn.o +#hisax-$(CONFIG_HISAX_TESTEMU) += testemu.o + diff --git a/drivers/isdn/hisax/amd7930_fn.c b/drivers/isdn/hisax/amd7930_fn.c new file mode 100644 index 000000000000..c4f861a5db25 --- /dev/null +++ b/drivers/isdn/hisax/amd7930_fn.c @@ -0,0 +1,796 @@ +/* gerdes_amd7930.c,v 0.99 2001/10/02 + * + * gerdes_amd7930.c Amd 79C30A and 79C32A specific routines + * (based on HiSax driver by Karsten Keil) + * + * Author Christoph Ersfeld <info@formula-n.de> + * Formula-n Europe AG (www.formula-n.com) + * previously Gerdes AG + * + * + * This file is (c) under GNU PUBLIC LICENSE + * + * + * Notes: + * Version 0.99 is the first release of this driver and there are + * certainly a few bugs. + * + * Please don't report any malfunction to me without sending + * (compressed) debug-logs. + * It would be nearly impossible to retrace it. + * + * Log D-channel-processing as follows: + * + * 1. Load hisax with card-specific parameters, this example ist for + * Formula-n enter:now ISDN PCI and compatible + * (f.e. Gerdes Power ISDN PCI) + * + * modprobe hisax type=41 protocol=2 id=gerdes + * + * if you chose an other value for id, you need to modify the + * code below, too. + * + * 2. set debug-level + * + * hisaxctrl gerdes 1 0x3ff + * hisaxctrl gerdes 11 0x4f + * cat /dev/isdnctrl >> ~/log & + * + * Please take also a look into /var/log/messages if there is + * anything importand concerning HISAX. + * + * + * Credits: + * Programming the driver for Formula-n enter:now ISDN PCI and + * necessary this driver for the used Amd 7930 D-channel-controller + * was spnsored by Formula-n Europe AG. + * Thanks to Karsten Keil and Petr Novak, who gave me support in + * Hisax-specific questions. + * I want so say special thanks to Carl-Friedrich Braun, who had to + * answer a lot of questions about generally ISDN and about handling + * of the Amd-Chip. + * + */ + + +#include "hisax.h" +#include "isdnl1.h" +#include "isac.h" +#include "amd7930_fn.h" +#include <linux/interrupt.h> +#include <linux/init.h> + +static void Amd7930_new_ph(struct IsdnCardState *cs); + +static WORD initAMD[] = { + 0x0100, + + 0x00A5, 3, 0x01, 0x40, 0x58, // LPR, LMR1, LMR2 + 0x0086, 1, 0x0B, // DMR1 (D-Buffer TH-Interrupts on) + 0x0087, 1, 0xFF, // DMR2 + 0x0092, 1, 0x03, // EFCR (extended mode d-channel-fifo on) + 0x0090, 4, 0xFE, 0xFF, 0x02, 0x0F, // FRAR4, SRAR4, DMR3, DMR4 (address recognition ) + 0x0084, 2, 0x80, 0x00, // DRLR + 0x00C0, 1, 0x47, // PPCR1 + 0x00C8, 1, 0x01, // PPCR2 + + 0x0102, + 0x0107, + 0x01A1, 1, + 0x0121, 1, + 0x0189, 2, + + 0x0045, 4, 0x61, 0x72, 0x00, 0x00, // MCR1, MCR2, MCR3, MCR4 + 0x0063, 2, 0x08, 0x08, // GX + 0x0064, 2, 0x08, 0x08, // GR + 0x0065, 2, 0x99, 0x00, // GER + 0x0066, 2, 0x7C, 0x8B, // STG + 0x0067, 2, 0x00, 0x00, // FTGR1, FTGR2 + 0x0068, 2, 0x20, 0x20, // ATGR1, ATGR2 + 0x0069, 1, 0x4F, // MMR1 + 0x006A, 1, 0x00, // MMR2 + 0x006C, 1, 0x40, // MMR3 + 0x0021, 1, 0x02, // INIT + 0x00A3, 1, 0x40, // LMR1 + + 0xFFFF +}; + + +void /* macro wWordAMD */ +WriteWordAmd7930(struct IsdnCardState *cs, BYTE reg, WORD val) +{ + wByteAMD(cs, 0x00, reg); + wByteAMD(cs, 0x01, LOBYTE(val)); + wByteAMD(cs, 0x01, HIBYTE(val)); +} + +WORD /* macro rWordAMD */ +ReadWordAmd7930(struct IsdnCardState *cs, BYTE reg) +{ + WORD res; + /* direct access register */ + if(reg < 8) { + res = rByteAMD(cs, reg); + res += 256*rByteAMD(cs, reg); + } + /* indirect access register */ + else { + wByteAMD(cs, 0x00, reg); + res = rByteAMD(cs, 0x01); + res += 256*rByteAMD(cs, 0x01); + } + return (res); +} + + +static void +Amd7930_ph_command(struct IsdnCardState *cs, u_char command, char *s) +{ + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "AMD7930: %s: ph_command 0x%02X", s, command); + + cs->dc.amd7930.lmr1 = command; + wByteAMD(cs, 0xA3, command); +} + + + +static BYTE i430States[] = { +// to reset F3 F4 F5 F6 F7 F8 AR from + 0x01, 0x02, 0x00, 0x00, 0x00, 0x07, 0x05, 0x00, // init + 0x01, 0x02, 0x00, 0x00, 0x00, 0x07, 0x05, 0x00, // reset + 0x01, 0x02, 0x00, 0x00, 0x00, 0x09, 0x05, 0x04, // F3 + 0x01, 0x02, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, // F4 + 0x01, 0x02, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, // F5 + 0x01, 0x03, 0x00, 0x00, 0x00, 0x06, 0x05, 0x00, // F6 + 0x11, 0x13, 0x00, 0x00, 0x1B, 0x00, 0x15, 0x00, // F7 + 0x01, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, // F8 + 0x01, 0x03, 0x00, 0x00, 0x00, 0x09, 0x00, 0x0A}; // AR + + +/* Row init - reset F3 F4 F5 F6 F7 F8 AR */ +static BYTE stateHelper[] = { 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }; + + + + +static void +Amd7930_get_state(struct IsdnCardState *cs) { + BYTE lsr = rByteAMD(cs, 0xA1); + cs->dc.amd7930.ph_state = (lsr & 0x7) + 2; + Amd7930_new_ph(cs); +} + + + +static void +Amd7930_new_ph(struct IsdnCardState *cs) +{ + u_char index = stateHelper[cs->dc.amd7930.old_state]*8 + stateHelper[cs->dc.amd7930.ph_state]-1; + u_char message = i430States[index]; + + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "AMD7930: new_ph %d, old_ph %d, message %d, index %d", + cs->dc.amd7930.ph_state, cs->dc.amd7930.old_state, message & 0x0f, index); + + cs->dc.amd7930.old_state = cs->dc.amd7930.ph_state; + + /* abort transmit if nessesary */ + if ((message & 0xf0) && (cs->tx_skb)) { + wByteAMD(cs, 0x21, 0xC2); + wByteAMD(cs, 0x21, 0x02); + } + + switch (message & 0x0f) { + + case (1): + l1_msg(cs, HW_RESET | INDICATION, NULL); + Amd7930_get_state(cs); + break; + case (2): /* init, Card starts in F3 */ + l1_msg(cs, HW_DEACTIVATE | CONFIRM, NULL); + break; + case (3): + l1_msg(cs, HW_DEACTIVATE | INDICATION, NULL); + break; + case (4): + l1_msg(cs, HW_POWERUP | CONFIRM, NULL); + Amd7930_ph_command(cs, 0x50, "HW_ENABLE REQUEST"); + break; + case (5): + l1_msg(cs, HW_RSYNC | INDICATION, NULL); + break; + case (6): + l1_msg(cs, HW_INFO4_P8 | INDICATION, NULL); + break; + case (7): /* init, Card starts in F7 */ + l1_msg(cs, HW_RSYNC | INDICATION, NULL); + l1_msg(cs, HW_INFO4_P8 | INDICATION, NULL); + break; + case (8): + l1_msg(cs, HW_POWERUP | CONFIRM, NULL); + /* fall through */ + case (9): + Amd7930_ph_command(cs, 0x40, "HW_ENABLE REQ cleared if set"); + l1_msg(cs, HW_RSYNC | INDICATION, NULL); + l1_msg(cs, HW_INFO2 | INDICATION, NULL); + l1_msg(cs, HW_INFO4_P8 | INDICATION, NULL); + break; + case (10): + Amd7930_ph_command(cs, 0x40, "T3 expired, HW_ENABLE REQ cleared"); + cs->dc.amd7930.old_state = 3; + break; + case (11): + l1_msg(cs, HW_INFO2 | INDICATION, NULL); + break; + default: + break; + } +} + + + +static void +Amd7930_bh(struct IsdnCardState *cs) +{ + + struct PStack *stptr; + + if (!cs) + return; + if (test_and_clear_bit(D_CLEARBUSY, &cs->event)) { + if (cs->debug) + debugl1(cs, "Amd7930: bh, D-Channel Busy cleared"); + stptr = cs->stlist; + while (stptr != NULL) { + stptr->l1.l1l2(stptr, PH_PAUSE | CONFIRM, NULL); + stptr = stptr->next; + } + } + if (test_and_clear_bit(D_L1STATECHANGE, &cs->event)) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "AMD7930: bh, D_L1STATECHANGE"); + Amd7930_new_ph(cs); + } + + if (test_and_clear_bit(D_RCVBUFREADY, &cs->event)) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "AMD7930: bh, D_RCVBUFREADY"); + DChannel_proc_rcv(cs); + } + + if (test_and_clear_bit(D_XMTBUFREADY, &cs->event)) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "AMD7930: bh, D_XMTBUFREADY"); + DChannel_proc_xmt(cs); + } +} + +static void +Amd7930_empty_Dfifo(struct IsdnCardState *cs, int flag) +{ + + BYTE stat, der; + BYTE *ptr; + struct sk_buff *skb; + + + if ((cs->debug & L1_DEB_ISAC) && !(cs->debug & L1_DEB_ISAC_FIFO)) + debugl1(cs, "Amd7930: empty_Dfifo"); + + + ptr = cs->rcvbuf + cs->rcvidx; + + /* AMD interrupts off */ + AmdIrqOff(cs); + + /* read D-Channel-Fifo*/ + stat = rByteAMD(cs, 0x07); // DSR2 + + /* while Data in Fifo ... */ + while ( (stat & 2) && ((ptr-cs->rcvbuf) < MAX_DFRAME_LEN_L1) ) { + *ptr = rByteAMD(cs, 0x04); // DCRB + ptr++; + stat = rByteAMD(cs, 0x07); // DSR2 + cs->rcvidx = ptr - cs->rcvbuf; + + /* Paket ready? */ + if (stat & 1) { + + der = rWordAMD(cs, 0x03); + + /* no errors, packet ok */ + if(!der && !flag) { + rWordAMD(cs, 0x89); // clear DRCR + + if ((cs->rcvidx) > 0) { + if (!(skb = alloc_skb(cs->rcvidx, GFP_ATOMIC))) + printk(KERN_WARNING "HiSax: Amd7930: empty_Dfifo, D receive out of memory!\n"); + else { + /* Debugging */ + if (cs->debug & L1_DEB_ISAC_FIFO) { + char *t = cs->dlog; + + t += sprintf(t, "Amd7930: empty_Dfifo cnt: %d |", cs->rcvidx); + QuickHex(t, cs->rcvbuf, cs->rcvidx); + debugl1(cs, cs->dlog); + } + /* moves received data in sk-buffer */ + memcpy(skb_put(skb, cs->rcvidx), cs->rcvbuf, cs->rcvidx); + skb_queue_tail(&cs->rq, skb); + } + } + + } + /* throw damaged packets away, reset receive-buffer, indicate RX */ + ptr = cs->rcvbuf; + cs->rcvidx = 0; + schedule_event(cs, D_RCVBUFREADY); + } + } + /* Packet to long, overflow */ + if(cs->rcvidx >= MAX_DFRAME_LEN_L1) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "AMD7930: empty_Dfifo L2-Framelength overrun"); + cs->rcvidx = 0; + return; + } + /* AMD interrupts on */ + AmdIrqOn(cs); +} + + +static void +Amd7930_fill_Dfifo(struct IsdnCardState *cs) +{ + + WORD dtcrr, dtcrw, len, count; + BYTE txstat, dmr3; + BYTE *ptr, *deb_ptr; + + if ((cs->debug & L1_DEB_ISAC) && !(cs->debug & L1_DEB_ISAC_FIFO)) + debugl1(cs, "Amd7930: fill_Dfifo"); + + if ((!cs->tx_skb) || (cs->tx_skb->len <= 0)) + return; + + dtcrw = 0; + if(!cs->dc.amd7930.tx_xmtlen) + /* new Frame */ + len = dtcrw = cs->tx_skb->len; + /* continue frame */ + else len = cs->dc.amd7930.tx_xmtlen; + + + /* AMD interrupts off */ + AmdIrqOff(cs); + + deb_ptr = ptr = cs->tx_skb->data; + + /* while free place in tx-fifo available and data in sk-buffer */ + txstat = 0x10; + while((txstat & 0x10) && (cs->tx_cnt < len)) { + wByteAMD(cs, 0x04, *ptr); + ptr++; + cs->tx_cnt++; + txstat= rByteAMD(cs, 0x07); + } + count = ptr - cs->tx_skb->data; + skb_pull(cs->tx_skb, count); + + + dtcrr = rWordAMD(cs, 0x85); // DTCR + dmr3 = rByteAMD(cs, 0x8E); + + if (cs->debug & L1_DEB_ISAC) { + debugl1(cs, "Amd7930: fill_Dfifo, DMR3: 0x%02X, DTCR read: 0x%04X write: 0x%02X 0x%02X", dmr3, dtcrr, LOBYTE(dtcrw), HIBYTE(dtcrw)); + } + + /* writeing of dtcrw starts transmit */ + if(!cs->dc.amd7930.tx_xmtlen) { + wWordAMD(cs, 0x85, dtcrw); + cs->dc.amd7930.tx_xmtlen = dtcrw; + } + + if (test_and_set_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) { + debugl1(cs, "Amd7930: fill_Dfifo dbusytimer running"); + del_timer(&cs->dbusytimer); + } + init_timer(&cs->dbusytimer); + cs->dbusytimer.expires = jiffies + ((DBUSY_TIMER_VALUE * HZ) / 1000); + add_timer(&cs->dbusytimer); + + if (cs->debug & L1_DEB_ISAC_FIFO) { + char *t = cs->dlog; + + t += sprintf(t, "Amd7930: fill_Dfifo cnt: %d |", count); + QuickHex(t, deb_ptr, count); + debugl1(cs, cs->dlog); + } + /* AMD interrupts on */ + AmdIrqOn(cs); +} + + +void Amd7930_interrupt(struct IsdnCardState *cs, BYTE irflags) +{ + BYTE dsr1, dsr2, lsr; + WORD der; + + while (irflags) + { + + dsr1 = rByteAMD(cs, 0x02); + der = rWordAMD(cs, 0x03); + dsr2 = rByteAMD(cs, 0x07); + lsr = rByteAMD(cs, 0xA1); + + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "Amd7930: interrupt: flags: 0x%02X, DSR1: 0x%02X, DSR2: 0x%02X, LSR: 0x%02X, DER=0x%04X", irflags, dsr1, dsr2, lsr, der); + + /* D error -> read DER and DSR2 bit 2 */ + if (der || (dsr2 & 4)) { + + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "Amd7930: interrupt: D error DER=0x%04X", der); + + /* RX, TX abort if collision detected */ + if (der & 2) { + wByteAMD(cs, 0x21, 0xC2); + wByteAMD(cs, 0x21, 0x02); + if (test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) + del_timer(&cs->dbusytimer); + if (test_and_clear_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + schedule_event(cs, D_CLEARBUSY); + /* restart frame */ + if (cs->tx_skb) { + skb_push(cs->tx_skb, cs->tx_cnt); + cs->tx_cnt = 0; + cs->dc.amd7930.tx_xmtlen = 0; + Amd7930_fill_Dfifo(cs); + } else { + printk(KERN_WARNING "HiSax: Amd7930 D-Collision, no skb\n"); + debugl1(cs, "Amd7930: interrupt: D-Collision, no skb"); + } + } + /* remove damaged data from fifo */ + Amd7930_empty_Dfifo(cs, 1); + + if (test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) + del_timer(&cs->dbusytimer); + if (test_and_clear_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + schedule_event(cs, D_CLEARBUSY); + /* restart TX-Frame */ + if (cs->tx_skb) { + skb_push(cs->tx_skb, cs->tx_cnt); + cs->tx_cnt = 0; + cs->dc.amd7930.tx_xmtlen = 0; + Amd7930_fill_Dfifo(cs); + } + } + + /* D TX FIFO empty -> fill */ + if (irflags & 1) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "Amd7930: interrupt: clear Timer and fill D-TX-FIFO if data"); + + /* AMD interrupts off */ + AmdIrqOff(cs); + + if (test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) + del_timer(&cs->dbusytimer); + if (test_and_clear_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + schedule_event(cs, D_CLEARBUSY); + if (cs->tx_skb) { + if (cs->tx_skb->len) + Amd7930_fill_Dfifo(cs); + } + /* AMD interrupts on */ + AmdIrqOn(cs); + } + + + /* D RX FIFO full or tiny packet in Fifo -> empty */ + if ((irflags & 2) || (dsr1 & 2)) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "Amd7930: interrupt: empty D-FIFO"); + Amd7930_empty_Dfifo(cs, 0); + } + + + /* D-Frame transmit complete */ + if (dsr1 & 64) { + if (cs->debug & L1_DEB_ISAC) { + debugl1(cs, "Amd7930: interrupt: transmit packet ready"); + } + /* AMD interrupts off */ + AmdIrqOff(cs); + + if (test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) + del_timer(&cs->dbusytimer); + if (test_and_clear_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + schedule_event(cs, D_CLEARBUSY); + + if (cs->tx_skb) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "Amd7930: interrupt: TX-Packet ready, freeing skb"); + dev_kfree_skb_irq(cs->tx_skb); + cs->tx_cnt = 0; + cs->dc.amd7930.tx_xmtlen=0; + cs->tx_skb = NULL; + } + if ((cs->tx_skb = skb_dequeue(&cs->sq))) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "Amd7930: interrupt: TX-Packet ready, next packet dequeued"); + cs->tx_cnt = 0; + cs->dc.amd7930.tx_xmtlen=0; + Amd7930_fill_Dfifo(cs); + } + else + schedule_event(cs, D_XMTBUFREADY); + /* AMD interrupts on */ + AmdIrqOn(cs); + } + + /* LIU status interrupt -> read LSR, check statechanges */ + if (lsr & 0x38) { + /* AMD interrupts off */ + AmdIrqOff(cs); + + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "Amd: interrupt: LSR=0x%02X, LIU is in state %d", lsr, ((lsr & 0x7) +2)); + + cs->dc.amd7930.ph_state = (lsr & 0x7) + 2; + + schedule_event(cs, D_L1STATECHANGE); + /* AMD interrupts on */ + AmdIrqOn(cs); + } + + /* reads Interrupt-Register again. If there is a new interrupt-flag: restart handler */ + irflags = rByteAMD(cs, 0x00); + } + +} + +static void +Amd7930_l1hw(struct PStack *st, int pr, void *arg) +{ + struct IsdnCardState *cs = (struct IsdnCardState *) st->l1.hardware; + struct sk_buff *skb = arg; + u_long flags; + + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "Amd7930: l1hw called, pr: 0x%04X", pr); + + switch (pr) { + case (PH_DATA | REQUEST): + if (cs->debug & DEB_DLOG_HEX) + LogFrame(cs, skb->data, skb->len); + if (cs->debug & DEB_DLOG_VERBOSE) + dlogframe(cs, skb, 0); + spin_lock_irqsave(&cs->lock, flags); + if (cs->tx_skb) { + skb_queue_tail(&cs->sq, skb); +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "Amd7930: l1hw: PH_DATA Queued", 0); +#endif + } else { + cs->tx_skb = skb; + cs->tx_cnt = 0; + cs->dc.amd7930.tx_xmtlen=0; +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "Amd7930: l1hw: PH_DATA", 0); +#endif + Amd7930_fill_Dfifo(cs); + } + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (PH_PULL | INDICATION): + spin_lock_irqsave(&cs->lock, flags); + if (cs->tx_skb) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "Amd7930: l1hw: l2l1 tx_skb exist this shouldn't happen"); + skb_queue_tail(&cs->sq, skb); + break; + } + if (cs->debug & DEB_DLOG_HEX) + LogFrame(cs, skb->data, skb->len); + if (cs->debug & DEB_DLOG_VERBOSE) + dlogframe(cs, skb, 0); + cs->tx_skb = skb; + cs->tx_cnt = 0; + cs->dc.amd7930.tx_xmtlen=0; +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "Amd7930: l1hw: PH_DATA_PULLED", 0); +#endif + Amd7930_fill_Dfifo(cs); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (PH_PULL | REQUEST): +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + debugl1(cs, "Amd7930: l1hw: -> PH_REQUEST_PULL, skb: %s", (cs->tx_skb)? "yes":"no"); +#endif + if (!cs->tx_skb) { + test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } else + test_and_set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + case (HW_RESET | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + if ((cs->dc.amd7930.ph_state == 8)) { + /* b-channels off, PH-AR cleared + * change to F3 */ + Amd7930_ph_command(cs, 0x20, "HW_RESET REQEST"); //LMR1 bit 5 + spin_unlock_irqrestore(&cs->lock, flags); + } else { + Amd7930_ph_command(cs, 0x40, "HW_RESET REQUEST"); + cs->dc.amd7930.ph_state = 2; + spin_unlock_irqrestore(&cs->lock, flags); + Amd7930_new_ph(cs); + } + break; + case (HW_ENABLE | REQUEST): + cs->dc.amd7930.ph_state = 9; + Amd7930_new_ph(cs); + break; + case (HW_INFO3 | REQUEST): + // automatic + break; + case (HW_TESTLOOP | REQUEST): + /* not implemented yet */ + break; + case (HW_DEACTIVATE | RESPONSE): + skb_queue_purge(&cs->rq); + skb_queue_purge(&cs->sq); + if (cs->tx_skb) { + dev_kfree_skb(cs->tx_skb); + cs->tx_skb = NULL; + } + if (test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) + del_timer(&cs->dbusytimer); + if (test_and_clear_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + schedule_event(cs, D_CLEARBUSY); + break; + default: + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "Amd7930: l1hw: unknown %04x", pr); + break; + } +} + +void +setstack_Amd7930(struct PStack *st, struct IsdnCardState *cs) +{ + + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "Amd7930: setstack called"); + + st->l1.l1hw = Amd7930_l1hw; +} + + +void +DC_Close_Amd7930(struct IsdnCardState *cs) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "Amd7930: DC_Close called"); +} + + +static void +dbusy_timer_handler(struct IsdnCardState *cs) +{ + u_long flags; + struct PStack *stptr; + WORD dtcr, der; + BYTE dsr1, dsr2; + + + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "Amd7930: dbusy_timer expired!"); + + if (test_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) { + spin_lock_irqsave(&cs->lock, flags); + /* D Transmit Byte Count Register: + * Counts down packet's number of Bytes, 0 if packet ready */ + dtcr = rWordAMD(cs, 0x85); + dsr1 = rByteAMD(cs, 0x02); + dsr2 = rByteAMD(cs, 0x07); + der = rWordAMD(cs, 0x03); + + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "Amd7930: dbusy_timer_handler: DSR1=0x%02X, DSR2=0x%02X, DER=0x%04X, cs->tx_skb->len=%u, tx_stat=%u, dtcr=%u, cs->tx_cnt=%u", dsr1, dsr2, der, cs->tx_skb->len, cs->dc.amd7930.tx_xmtlen, dtcr, cs->tx_cnt); + + if ((cs->dc.amd7930.tx_xmtlen - dtcr) < cs->tx_cnt) { /* D-Channel Busy */ + test_and_set_bit(FLG_L1_DBUSY, &cs->HW_Flags); + stptr = cs->stlist; + spin_unlock_irqrestore(&cs->lock, flags); + while (stptr != NULL) { + stptr->l1.l1l2(stptr, PH_PAUSE | INDICATION, NULL); + stptr = stptr->next; + } + + } else { + /* discard frame; reset transceiver */ + test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags); + if (cs->tx_skb) { + dev_kfree_skb_any(cs->tx_skb); + cs->tx_cnt = 0; + cs->tx_skb = NULL; + cs->dc.amd7930.tx_xmtlen = 0; + } else { + printk(KERN_WARNING "HiSax: Amd7930: D-Channel Busy no skb\n"); + debugl1(cs, "Amd7930: D-Channel Busy no skb"); + + } + /* Transmitter reset, abort transmit */ + wByteAMD(cs, 0x21, 0x82); + wByteAMD(cs, 0x21, 0x02); + spin_unlock_irqrestore(&cs->lock, flags); + cs->irq_func(cs->irq, cs, NULL); + + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "Amd7930: dbusy_timer_handler: Transmitter reset"); + } + } +} + + + +void __devinit +Amd7930_init(struct IsdnCardState *cs) +{ + WORD *ptr; + BYTE cmd, cnt; + + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "Amd7930: initamd called"); + + cs->dc.amd7930.tx_xmtlen = 0; + cs->dc.amd7930.old_state = 0; + cs->dc.amd7930.lmr1 = 0x40; + cs->dc.amd7930.ph_command = Amd7930_ph_command; + cs->setstack_d = setstack_Amd7930; + cs->DC_Close = DC_Close_Amd7930; + + /* AMD Initialisation */ + for (ptr = initAMD; *ptr != 0xFFFF; ) { + cmd = LOBYTE(*ptr); + + /* read */ + if (*ptr++ >= 0x100) { + if (cmd < 8) + /* setzt Register zurück */ + rByteAMD(cs, cmd); + else { + wByteAMD(cs, 0x00, cmd); + for (cnt = *ptr++; cnt > 0; cnt--) + rByteAMD(cs, 0x01); + } + } + /* write */ + else if (cmd < 8) + wByteAMD(cs, cmd, LOBYTE(*ptr++)); + + else { + wByteAMD(cs, 0x00, cmd); + for (cnt = *ptr++; cnt > 0; cnt--) + wByteAMD(cs, 0x01, LOBYTE(*ptr++)); + } + } +} + +void __devinit +setup_Amd7930(struct IsdnCardState *cs) +{ + INIT_WORK(&cs->tqueue, (void *)(void *) Amd7930_bh, cs); + cs->dbusytimer.function = (void *) dbusy_timer_handler; + cs->dbusytimer.data = (long) cs; + init_timer(&cs->dbusytimer); +} diff --git a/drivers/isdn/hisax/amd7930_fn.h b/drivers/isdn/hisax/amd7930_fn.h new file mode 100644 index 000000000000..e039c3a0f2a2 --- /dev/null +++ b/drivers/isdn/hisax/amd7930_fn.h @@ -0,0 +1,37 @@ +/* 2001/10/02 + * + * gerdes_amd7930.h Header-file included by + * gerdes_amd7930.c + * + * Author Christoph Ersfeld <info@formula-n.de> + * Formula-n Europe AG (www.formula-n.com) + * previously Gerdes AG + * + * + * This file is (c) under GNU PUBLIC LICENSE + */ + + + + +#define BYTE unsigned char +#define WORD unsigned int +#define rByteAMD(cs, reg) cs->readisac(cs, reg) +#define wByteAMD(cs, reg, val) cs->writeisac(cs, reg, val) +#define rWordAMD(cs, reg) ReadWordAmd7930(cs, reg) +#define wWordAMD(cs, reg, val) WriteWordAmd7930(cs, reg, val) +#define HIBYTE(w) ((unsigned char)((w & 0xff00) / 256)) +#define LOBYTE(w) ((unsigned char)(w & 0x00ff)) + +#define AmdIrqOff(cs) cs->dc.amd7930.setIrqMask(cs, 0) +#define AmdIrqOn(cs) cs->dc.amd7930.setIrqMask(cs, 1) + +#define AMD_CR 0x00 +#define AMD_DR 0x01 + + +#define DBUSY_TIMER_VALUE 80 + +extern void Amd7930_interrupt(struct IsdnCardState *, unsigned char); +extern void Amd7930_init(struct IsdnCardState *); +extern void setup_Amd7930(struct IsdnCardState *); diff --git a/drivers/isdn/hisax/arcofi.c b/drivers/isdn/hisax/arcofi.c new file mode 100644 index 000000000000..d30ce5b978c2 --- /dev/null +++ b/drivers/isdn/hisax/arcofi.c @@ -0,0 +1,134 @@ +/* $Id: arcofi.c,v 1.14.2.3 2004/01/13 14:31:24 keil Exp $ + * + * Ansteuerung ARCOFI 2165 + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include "hisax.h" +#include "isdnl1.h" +#include "isac.h" +#include "arcofi.h" + +#define ARCOFI_TIMER_VALUE 20 + +static void +add_arcofi_timer(struct IsdnCardState *cs) { + if (test_and_set_bit(FLG_ARCOFI_TIMER, &cs->HW_Flags)) { + del_timer(&cs->dc.isac.arcofitimer); + } + init_timer(&cs->dc.isac.arcofitimer); + cs->dc.isac.arcofitimer.expires = jiffies + ((ARCOFI_TIMER_VALUE * HZ)/1000); + add_timer(&cs->dc.isac.arcofitimer); +} + +static void +send_arcofi(struct IsdnCardState *cs) { + u_char val; + + add_arcofi_timer(cs); + cs->dc.isac.mon_txp = 0; + cs->dc.isac.mon_txc = cs->dc.isac.arcofi_list->len; + memcpy(cs->dc.isac.mon_tx, cs->dc.isac.arcofi_list->msg, cs->dc.isac.mon_txc); + switch(cs->dc.isac.arcofi_bc) { + case 0: break; + case 1: cs->dc.isac.mon_tx[1] |= 0x40; + break; + default: break; + } + cs->dc.isac.mocr &= 0x0f; + cs->dc.isac.mocr |= 0xa0; + cs->writeisac(cs, ISAC_MOCR, cs->dc.isac.mocr); + val = cs->readisac(cs, ISAC_MOSR); + cs->writeisac(cs, ISAC_MOX1, cs->dc.isac.mon_tx[cs->dc.isac.mon_txp++]); + cs->dc.isac.mocr |= 0x10; + cs->writeisac(cs, ISAC_MOCR, cs->dc.isac.mocr); +} + +int +arcofi_fsm(struct IsdnCardState *cs, int event, void *data) { + if (cs->debug & L1_DEB_MONITOR) { + debugl1(cs, "arcofi state %d event %d", cs->dc.isac.arcofi_state, event); + } + if (event == ARCOFI_TIMEOUT) { + cs->dc.isac.arcofi_state = ARCOFI_NOP; + test_and_set_bit(FLG_ARCOFI_ERROR, &cs->HW_Flags); + wake_up(&cs->dc.isac.arcofi_wait); + return(1); + } + switch (cs->dc.isac.arcofi_state) { + case ARCOFI_NOP: + if (event == ARCOFI_START) { + cs->dc.isac.arcofi_list = data; + cs->dc.isac.arcofi_state = ARCOFI_TRANSMIT; + send_arcofi(cs); + } + break; + case ARCOFI_TRANSMIT: + if (event == ARCOFI_TX_END) { + if (cs->dc.isac.arcofi_list->receive) { + add_arcofi_timer(cs); + cs->dc.isac.arcofi_state = ARCOFI_RECEIVE; + } else { + if (cs->dc.isac.arcofi_list->next) { + cs->dc.isac.arcofi_list = + cs->dc.isac.arcofi_list->next; + send_arcofi(cs); + } else { + if (test_and_clear_bit(FLG_ARCOFI_TIMER, &cs->HW_Flags)) { + del_timer(&cs->dc.isac.arcofitimer); + } + cs->dc.isac.arcofi_state = ARCOFI_NOP; + wake_up(&cs->dc.isac.arcofi_wait); + } + } + } + break; + case ARCOFI_RECEIVE: + if (event == ARCOFI_RX_END) { + if (cs->dc.isac.arcofi_list->next) { + cs->dc.isac.arcofi_list = + cs->dc.isac.arcofi_list->next; + cs->dc.isac.arcofi_state = ARCOFI_TRANSMIT; + send_arcofi(cs); + } else { + if (test_and_clear_bit(FLG_ARCOFI_TIMER, &cs->HW_Flags)) { + del_timer(&cs->dc.isac.arcofitimer); + } + cs->dc.isac.arcofi_state = ARCOFI_NOP; + wake_up(&cs->dc.isac.arcofi_wait); + } + } + break; + default: + debugl1(cs, "Arcofi unknown state %x", cs->dc.isac.arcofi_state); + return(2); + } + return(0); +} + +static void +arcofi_timer(struct IsdnCardState *cs) { + arcofi_fsm(cs, ARCOFI_TIMEOUT, NULL); +} + +void +clear_arcofi(struct IsdnCardState *cs) { + if (test_and_clear_bit(FLG_ARCOFI_TIMER, &cs->HW_Flags)) { + del_timer(&cs->dc.isac.arcofitimer); + } +} + +void +init_arcofi(struct IsdnCardState *cs) { + cs->dc.isac.arcofitimer.function = (void *) arcofi_timer; + cs->dc.isac.arcofitimer.data = (long) cs; + init_timer(&cs->dc.isac.arcofitimer); + init_waitqueue_head(&cs->dc.isac.arcofi_wait); + test_and_set_bit(HW_ARCOFI, &cs->HW_Flags); +} diff --git a/drivers/isdn/hisax/arcofi.h b/drivers/isdn/hisax/arcofi.h new file mode 100644 index 000000000000..00c44d3ce972 --- /dev/null +++ b/drivers/isdn/hisax/arcofi.h @@ -0,0 +1,27 @@ +/* $Id: arcofi.h,v 1.6.6.2 2001/09/23 22:24:46 kai Exp $ + * + * Ansteuerung ARCOFI 2165 + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#define ARCOFI_USE 1 + +/* states */ +#define ARCOFI_NOP 0 +#define ARCOFI_TRANSMIT 1 +#define ARCOFI_RECEIVE 2 +/* events */ +#define ARCOFI_START 1 +#define ARCOFI_TX_END 2 +#define ARCOFI_RX_END 3 +#define ARCOFI_TIMEOUT 4 + +extern int arcofi_fsm(struct IsdnCardState *cs, int event, void *data); +extern void init_arcofi(struct IsdnCardState *cs); +extern void clear_arcofi(struct IsdnCardState *cs); diff --git a/drivers/isdn/hisax/asuscom.c b/drivers/isdn/hisax/asuscom.c new file mode 100644 index 000000000000..7546e2e4a94e --- /dev/null +++ b/drivers/isdn/hisax/asuscom.c @@ -0,0 +1,427 @@ +/* $Id: asuscom.c,v 1.14.2.4 2004/01/13 23:48:39 keil Exp $ + * + * low level stuff for ASUSCOM NETWORK INC. ISDNLink cards + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * Thanks to ASUSCOM NETWORK INC. Taiwan and Dynalink NL for information + * + */ + +#include <linux/init.h> +#include <linux/isapnp.h> +#include "hisax.h" +#include "isac.h" +#include "ipac.h" +#include "hscx.h" +#include "isdnl1.h" + +extern const char *CardType[]; + +const char *Asuscom_revision = "$Revision: 1.14.2.4 $"; + +#define byteout(addr,val) outb(val,addr) +#define bytein(addr) inb(addr) + +#define ASUS_ISAC 0 +#define ASUS_HSCX 1 +#define ASUS_ADR 2 +#define ASUS_CTRL_U7 3 +#define ASUS_CTRL_POTS 5 + +#define ASUS_IPAC_ALE 0 +#define ASUS_IPAC_DATA 1 + +#define ASUS_ISACHSCX 1 +#define ASUS_IPAC 2 + +/* CARD_ADR (Write) */ +#define ASUS_RESET 0x80 /* Bit 7 Reset-Leitung */ + +static inline u_char +readreg(unsigned int ale, unsigned int adr, u_char off) +{ + register u_char ret; + + byteout(ale, off); + ret = bytein(adr); + return (ret); +} + +static inline void +readfifo(unsigned int ale, unsigned int adr, u_char off, u_char * data, int size) +{ + byteout(ale, off); + insb(adr, data, size); +} + + +static inline void +writereg(unsigned int ale, unsigned int adr, u_char off, u_char data) +{ + byteout(ale, off); + byteout(adr, data); +} + +static inline void +writefifo(unsigned int ale, unsigned int adr, u_char off, u_char * data, int size) +{ + byteout(ale, off); + outsb(adr, data, size); +} + +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + return (readreg(cs->hw.asus.adr, cs->hw.asus.isac, offset)); +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writereg(cs->hw.asus.adr, cs->hw.asus.isac, offset, value); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + readfifo(cs->hw.asus.adr, cs->hw.asus.isac, 0, data, size); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + writefifo(cs->hw.asus.adr, cs->hw.asus.isac, 0, data, size); +} + +static u_char +ReadISAC_IPAC(struct IsdnCardState *cs, u_char offset) +{ + return (readreg(cs->hw.asus.adr, cs->hw.asus.isac, offset|0x80)); +} + +static void +WriteISAC_IPAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writereg(cs->hw.asus.adr, cs->hw.asus.isac, offset|0x80, value); +} + +static void +ReadISACfifo_IPAC(struct IsdnCardState *cs, u_char * data, int size) +{ + readfifo(cs->hw.asus.adr, cs->hw.asus.isac, 0x80, data, size); +} + +static void +WriteISACfifo_IPAC(struct IsdnCardState *cs, u_char * data, int size) +{ + writefifo(cs->hw.asus.adr, cs->hw.asus.isac, 0x80, data, size); +} + +static u_char +ReadHSCX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + return (readreg(cs->hw.asus.adr, + cs->hw.asus.hscx, offset + (hscx ? 0x40 : 0))); +} + +static void +WriteHSCX(struct IsdnCardState *cs, int hscx, u_char offset, u_char value) +{ + writereg(cs->hw.asus.adr, + cs->hw.asus.hscx, offset + (hscx ? 0x40 : 0), value); +} + +/* + * fast interrupt HSCX stuff goes here + */ + +#define READHSCX(cs, nr, reg) readreg(cs->hw.asus.adr, \ + cs->hw.asus.hscx, reg + (nr ? 0x40 : 0)) +#define WRITEHSCX(cs, nr, reg, data) writereg(cs->hw.asus.adr, \ + cs->hw.asus.hscx, reg + (nr ? 0x40 : 0), data) + +#define READHSCXFIFO(cs, nr, ptr, cnt) readfifo(cs->hw.asus.adr, \ + cs->hw.asus.hscx, (nr ? 0x40 : 0), ptr, cnt) + +#define WRITEHSCXFIFO(cs, nr, ptr, cnt) writefifo(cs->hw.asus.adr, \ + cs->hw.asus.hscx, (nr ? 0x40 : 0), ptr, cnt) + +#include "hscx_irq.c" + +static irqreturn_t +asuscom_interrupt(int intno, void *dev_id, struct pt_regs *regs) +{ + struct IsdnCardState *cs = dev_id; + u_char val; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + val = readreg(cs->hw.asus.adr, cs->hw.asus.hscx, HSCX_ISTA + 0x40); + Start_HSCX: + if (val) + hscx_int_main(cs, val); + val = readreg(cs->hw.asus.adr, cs->hw.asus.isac, ISAC_ISTA); + Start_ISAC: + if (val) + isac_interrupt(cs, val); + val = readreg(cs->hw.asus.adr, cs->hw.asus.hscx, HSCX_ISTA + 0x40); + if (val) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HSCX IntStat after IntRoutine"); + goto Start_HSCX; + } + val = readreg(cs->hw.asus.adr, cs->hw.asus.isac, ISAC_ISTA); + if (val) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ISAC IntStat after IntRoutine"); + goto Start_ISAC; + } + writereg(cs->hw.asus.adr, cs->hw.asus.hscx, HSCX_MASK, 0xFF); + writereg(cs->hw.asus.adr, cs->hw.asus.hscx, HSCX_MASK + 0x40, 0xFF); + writereg(cs->hw.asus.adr, cs->hw.asus.isac, ISAC_MASK, 0xFF); + writereg(cs->hw.asus.adr, cs->hw.asus.isac, ISAC_MASK, 0x0); + writereg(cs->hw.asus.adr, cs->hw.asus.hscx, HSCX_MASK, 0x0); + writereg(cs->hw.asus.adr, cs->hw.asus.hscx, HSCX_MASK + 0x40, 0x0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static irqreturn_t +asuscom_interrupt_ipac(int intno, void *dev_id, struct pt_regs *regs) +{ + struct IsdnCardState *cs = dev_id; + u_char ista, val, icnt = 5; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + ista = readreg(cs->hw.asus.adr, cs->hw.asus.isac, IPAC_ISTA); +Start_IPAC: + if (cs->debug & L1_DEB_IPAC) + debugl1(cs, "IPAC ISTA %02X", ista); + if (ista & 0x0f) { + val = readreg(cs->hw.asus.adr, cs->hw.asus.hscx, HSCX_ISTA + 0x40); + if (ista & 0x01) + val |= 0x01; + if (ista & 0x04) + val |= 0x02; + if (ista & 0x08) + val |= 0x04; + if (val) + hscx_int_main(cs, val); + } + if (ista & 0x20) { + val = 0xfe & readreg(cs->hw.asus.adr, cs->hw.asus.isac, ISAC_ISTA | 0x80); + if (val) { + isac_interrupt(cs, val); + } + } + if (ista & 0x10) { + val = 0x01; + isac_interrupt(cs, val); + } + ista = readreg(cs->hw.asus.adr, cs->hw.asus.isac, IPAC_ISTA); + if ((ista & 0x3f) && icnt) { + icnt--; + goto Start_IPAC; + } + if (!icnt) + printk(KERN_WARNING "ASUS IRQ LOOP\n"); + writereg(cs->hw.asus.adr, cs->hw.asus.isac, IPAC_MASK, 0xFF); + writereg(cs->hw.asus.adr, cs->hw.asus.isac, IPAC_MASK, 0xC0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +void +release_io_asuscom(struct IsdnCardState *cs) +{ + int bytecnt = 8; + + if (cs->hw.asus.cfg_reg) + release_region(cs->hw.asus.cfg_reg, bytecnt); +} + +static void +reset_asuscom(struct IsdnCardState *cs) +{ + if (cs->subtyp == ASUS_IPAC) + writereg(cs->hw.asus.adr, cs->hw.asus.isac, IPAC_POTA2, 0x20); + else + byteout(cs->hw.asus.adr, ASUS_RESET); /* Reset On */ + mdelay(10); + if (cs->subtyp == ASUS_IPAC) + writereg(cs->hw.asus.adr, cs->hw.asus.isac, IPAC_POTA2, 0x0); + else + byteout(cs->hw.asus.adr, 0); /* Reset Off */ + mdelay(10); + if (cs->subtyp == ASUS_IPAC) { + writereg(cs->hw.asus.adr, cs->hw.asus.isac, IPAC_CONF, 0x0); + writereg(cs->hw.asus.adr, cs->hw.asus.isac, IPAC_ACFG, 0xff); + writereg(cs->hw.asus.adr, cs->hw.asus.isac, IPAC_AOE, 0x0); + writereg(cs->hw.asus.adr, cs->hw.asus.isac, IPAC_MASK, 0xc0); + writereg(cs->hw.asus.adr, cs->hw.asus.isac, IPAC_PCFG, 0x12); + } +} + +static int +Asus_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + reset_asuscom(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return(0); + case CARD_RELEASE: + release_io_asuscom(cs); + return(0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + cs->debug |= L1_DEB_IPAC; + inithscxisac(cs, 3); + spin_unlock_irqrestore(&cs->lock, flags); + return(0); + case CARD_TEST: + return(0); + } + return(0); +} + +#ifdef __ISAPNP__ +static struct isapnp_device_id asus_ids[] __initdata = { + { ISAPNP_VENDOR('A', 'S', 'U'), ISAPNP_FUNCTION(0x1688), + ISAPNP_VENDOR('A', 'S', 'U'), ISAPNP_FUNCTION(0x1688), + (unsigned long) "Asus1688 PnP" }, + { ISAPNP_VENDOR('A', 'S', 'U'), ISAPNP_FUNCTION(0x1690), + ISAPNP_VENDOR('A', 'S', 'U'), ISAPNP_FUNCTION(0x1690), + (unsigned long) "Asus1690 PnP" }, + { ISAPNP_VENDOR('S', 'I', 'E'), ISAPNP_FUNCTION(0x0020), + ISAPNP_VENDOR('S', 'I', 'E'), ISAPNP_FUNCTION(0x0020), + (unsigned long) "Isurf2 PnP" }, + { ISAPNP_VENDOR('E', 'L', 'F'), ISAPNP_FUNCTION(0x0000), + ISAPNP_VENDOR('E', 'L', 'F'), ISAPNP_FUNCTION(0x0000), + (unsigned long) "Iscas TE320" }, + { 0, } +}; + +static struct isapnp_device_id *ipid __initdata = &asus_ids[0]; +static struct pnp_card *pnp_c __devinitdata = NULL; +#endif + +int __init +setup_asuscom(struct IsdnCard *card) +{ + int bytecnt; + struct IsdnCardState *cs = card->cs; + u_char val; + char tmp[64]; + + strcpy(tmp, Asuscom_revision); + printk(KERN_INFO "HiSax: Asuscom ISDNLink driver Rev. %s\n", HiSax_getrev(tmp)); + if (cs->typ != ISDN_CTYPE_ASUSCOM) + return (0); +#ifdef __ISAPNP__ + if (!card->para[1] && isapnp_present()) { + struct pnp_dev *pnp_d; + while(ipid->card_vendor) { + if ((pnp_c = pnp_find_card(ipid->card_vendor, + ipid->card_device, pnp_c))) { + pnp_d = NULL; + if ((pnp_d = pnp_find_dev(pnp_c, + ipid->vendor, ipid->function, pnp_d))) { + int err; + + printk(KERN_INFO "HiSax: %s detected\n", + (char *)ipid->driver_data); + pnp_disable_dev(pnp_d); + err = pnp_activate_dev(pnp_d); + if (err<0) { + printk(KERN_WARNING "%s: pnp_activate_dev ret(%d)\n", + __FUNCTION__, err); + return(0); + } + card->para[1] = pnp_port_start(pnp_d, 0); + card->para[0] = pnp_irq(pnp_d, 0); + if (!card->para[0] || !card->para[1]) { + printk(KERN_ERR "AsusPnP:some resources are missing %ld/%lx\n", + card->para[0], card->para[1]); + pnp_disable_dev(pnp_d); + return(0); + } + break; + } else { + printk(KERN_ERR "AsusPnP: PnP error card found, no device\n"); + } + } + ipid++; + pnp_c = NULL; + } + if (!ipid->card_vendor) { + printk(KERN_INFO "AsusPnP: no ISAPnP card found\n"); + return(0); + } + } +#endif + bytecnt = 8; + cs->hw.asus.cfg_reg = card->para[1]; + cs->irq = card->para[0]; + if (!request_region(cs->hw.asus.cfg_reg, bytecnt, "asuscom isdn")) { + printk(KERN_WARNING + "HiSax: %s config port %x-%x already in use\n", + CardType[card->typ], + cs->hw.asus.cfg_reg, + cs->hw.asus.cfg_reg + bytecnt); + return (0); + } + printk(KERN_INFO "ISDNLink: defined at 0x%x IRQ %d\n", + cs->hw.asus.cfg_reg, cs->irq); + setup_isac(cs); + cs->BC_Read_Reg = &ReadHSCX; + cs->BC_Write_Reg = &WriteHSCX; + cs->BC_Send_Data = &hscx_fill_fifo; + cs->cardmsg = &Asus_card_msg; + val = readreg(cs->hw.asus.cfg_reg + ASUS_IPAC_ALE, + cs->hw.asus.cfg_reg + ASUS_IPAC_DATA, IPAC_ID); + if ((val == 1) || (val == 2)) { + cs->subtyp = ASUS_IPAC; + cs->hw.asus.adr = cs->hw.asus.cfg_reg + ASUS_IPAC_ALE; + cs->hw.asus.isac = cs->hw.asus.cfg_reg + ASUS_IPAC_DATA; + cs->hw.asus.hscx = cs->hw.asus.cfg_reg + ASUS_IPAC_DATA; + test_and_set_bit(HW_IPAC, &cs->HW_Flags); + cs->readisac = &ReadISAC_IPAC; + cs->writeisac = &WriteISAC_IPAC; + cs->readisacfifo = &ReadISACfifo_IPAC; + cs->writeisacfifo = &WriteISACfifo_IPAC; + cs->irq_func = &asuscom_interrupt_ipac; + printk(KERN_INFO "Asus: IPAC version %x\n", val); + } else { + cs->subtyp = ASUS_ISACHSCX; + cs->hw.asus.adr = cs->hw.asus.cfg_reg + ASUS_ADR; + cs->hw.asus.isac = cs->hw.asus.cfg_reg + ASUS_ISAC; + cs->hw.asus.hscx = cs->hw.asus.cfg_reg + ASUS_HSCX; + cs->hw.asus.u7 = cs->hw.asus.cfg_reg + ASUS_CTRL_U7; + cs->hw.asus.pots = cs->hw.asus.cfg_reg + ASUS_CTRL_POTS; + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->irq_func = &asuscom_interrupt; + ISACVersion(cs, "ISDNLink:"); + if (HscxVersion(cs, "ISDNLink:")) { + printk(KERN_WARNING + "ISDNLink: wrong HSCX versions check IO address\n"); + release_io_asuscom(cs); + return (0); + } + } + return (1); +} diff --git a/drivers/isdn/hisax/avm_a1.c b/drivers/isdn/hisax/avm_a1.c new file mode 100644 index 000000000000..8f028d42fd2f --- /dev/null +++ b/drivers/isdn/hisax/avm_a1.c @@ -0,0 +1,317 @@ +/* $Id: avm_a1.c,v 2.15.2.4 2004/01/13 21:46:03 keil Exp $ + * + * low level stuff for AVM A1 (Fritz) isdn cards + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/init.h> +#include "hisax.h" +#include "isac.h" +#include "hscx.h" +#include "isdnl1.h" + +extern const char *CardType[]; +static const char *avm_revision = "$Revision: 2.15.2.4 $"; + +#define AVM_A1_STAT_ISAC 0x01 +#define AVM_A1_STAT_HSCX 0x02 +#define AVM_A1_STAT_TIMER 0x04 + +#define byteout(addr,val) outb(val,addr) +#define bytein(addr) inb(addr) + +static inline u_char +readreg(unsigned int adr, u_char off) +{ + return (bytein(adr + off)); +} + +static inline void +writereg(unsigned int adr, u_char off, u_char data) +{ + byteout(adr + off, data); +} + + +static inline void +read_fifo(unsigned int adr, u_char * data, int size) +{ + insb(adr, data, size); +} + +static void +write_fifo(unsigned int adr, u_char * data, int size) +{ + outsb(adr, data, size); +} + +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + return (readreg(cs->hw.avm.isac, offset)); +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writereg(cs->hw.avm.isac, offset, value); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + read_fifo(cs->hw.avm.isacfifo, data, size); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + write_fifo(cs->hw.avm.isacfifo, data, size); +} + +static u_char +ReadHSCX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + return (readreg(cs->hw.avm.hscx[hscx], offset)); +} + +static void +WriteHSCX(struct IsdnCardState *cs, int hscx, u_char offset, u_char value) +{ + writereg(cs->hw.avm.hscx[hscx], offset, value); +} + +/* + * fast interrupt HSCX stuff goes here + */ + +#define READHSCX(cs, nr, reg) readreg(cs->hw.avm.hscx[nr], reg) +#define WRITEHSCX(cs, nr, reg, data) writereg(cs->hw.avm.hscx[nr], reg, data) +#define READHSCXFIFO(cs, nr, ptr, cnt) read_fifo(cs->hw.avm.hscxfifo[nr], ptr, cnt) +#define WRITEHSCXFIFO(cs, nr, ptr, cnt) write_fifo(cs->hw.avm.hscxfifo[nr], ptr, cnt) + +#include "hscx_irq.c" + +static irqreturn_t +avm_a1_interrupt(int intno, void *dev_id, struct pt_regs *regs) +{ + struct IsdnCardState *cs = dev_id; + u_char val, sval; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + while (((sval = bytein(cs->hw.avm.cfg_reg)) & 0xf) != 0x7) { + if (!(sval & AVM_A1_STAT_TIMER)) { + byteout(cs->hw.avm.cfg_reg, 0x1E); + sval = bytein(cs->hw.avm.cfg_reg); + } else if (cs->debug & L1_DEB_INTSTAT) + debugl1(cs, "avm IntStatus %x", sval); + if (!(sval & AVM_A1_STAT_HSCX)) { + val = readreg(cs->hw.avm.hscx[1], HSCX_ISTA); + if (val) + hscx_int_main(cs, val); + } + if (!(sval & AVM_A1_STAT_ISAC)) { + val = readreg(cs->hw.avm.isac, ISAC_ISTA); + if (val) + isac_interrupt(cs, val); + } + } + writereg(cs->hw.avm.hscx[0], HSCX_MASK, 0xFF); + writereg(cs->hw.avm.hscx[1], HSCX_MASK, 0xFF); + writereg(cs->hw.avm.isac, ISAC_MASK, 0xFF); + writereg(cs->hw.avm.isac, ISAC_MASK, 0x0); + writereg(cs->hw.avm.hscx[0], HSCX_MASK, 0x0); + writereg(cs->hw.avm.hscx[1], HSCX_MASK, 0x0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +inline static void +release_ioregs(struct IsdnCardState *cs, int mask) +{ + release_region(cs->hw.avm.cfg_reg, 8); + if (mask & 1) + release_region(cs->hw.avm.isac + 32, 32); + if (mask & 2) + release_region(cs->hw.avm.isacfifo, 1); + if (mask & 4) + release_region(cs->hw.avm.hscx[0] + 32, 32); + if (mask & 8) + release_region(cs->hw.avm.hscxfifo[0], 1); + if (mask & 0x10) + release_region(cs->hw.avm.hscx[1] + 32, 32); + if (mask & 0x20) + release_region(cs->hw.avm.hscxfifo[1], 1); +} + +static int +AVM_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + switch (mt) { + case CARD_RESET: + return(0); + case CARD_RELEASE: + release_ioregs(cs, 0x3f); + return(0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + inithscxisac(cs, 1); + byteout(cs->hw.avm.cfg_reg, 0x16); + byteout(cs->hw.avm.cfg_reg, 0x1E); + inithscxisac(cs, 2); + spin_unlock_irqrestore(&cs->lock, flags); + return(0); + case CARD_TEST: + return(0); + } + return(0); +} + +int __init +setup_avm_a1(struct IsdnCard *card) +{ + u_char val; + struct IsdnCardState *cs = card->cs; + char tmp[64]; + + strcpy(tmp, avm_revision); + printk(KERN_INFO "HiSax: AVM driver Rev. %s\n", HiSax_getrev(tmp)); + if (cs->typ != ISDN_CTYPE_A1) + return (0); + + cs->hw.avm.cfg_reg = card->para[1] + 0x1800; + cs->hw.avm.isac = card->para[1] + 0x1400 - 0x20; + cs->hw.avm.hscx[0] = card->para[1] + 0x400 - 0x20; + cs->hw.avm.hscx[1] = card->para[1] + 0xc00 - 0x20; + cs->hw.avm.isacfifo = card->para[1] + 0x1000; + cs->hw.avm.hscxfifo[0] = card->para[1]; + cs->hw.avm.hscxfifo[1] = card->para[1] + 0x800; + cs->irq = card->para[0]; + if (!request_region(cs->hw.avm.cfg_reg, 8, "avm cfg")) { + printk(KERN_WARNING + "HiSax: %s config port %x-%x already in use\n", + CardType[card->typ], + cs->hw.avm.cfg_reg, + cs->hw.avm.cfg_reg + 8); + return (0); + } + if (!request_region(cs->hw.avm.isac + 32, 32, "HiSax isac")) { + printk(KERN_WARNING + "HiSax: %s isac ports %x-%x already in use\n", + CardType[cs->typ], + cs->hw.avm.isac + 32, + cs->hw.avm.isac + 64); + release_ioregs(cs, 0); + return (0); + } + if (!request_region(cs->hw.avm.isacfifo, 1, "HiSax isac fifo")) { + printk(KERN_WARNING + "HiSax: %s isac fifo port %x already in use\n", + CardType[cs->typ], + cs->hw.avm.isacfifo); + release_ioregs(cs, 1); + return (0); + } + if (!request_region(cs->hw.avm.hscx[0] + 32, 32, "HiSax hscx A")) { + printk(KERN_WARNING + "HiSax: %s hscx A ports %x-%x already in use\n", + CardType[cs->typ], + cs->hw.avm.hscx[0] + 32, + cs->hw.avm.hscx[0] + 64); + release_ioregs(cs, 3); + return (0); + } + if (!request_region(cs->hw.avm.hscxfifo[0], 1, "HiSax hscx A fifo")) { + printk(KERN_WARNING + "HiSax: %s hscx A fifo port %x already in use\n", + CardType[cs->typ], + cs->hw.avm.hscxfifo[0]); + release_ioregs(cs, 7); + return (0); + } + if (!request_region(cs->hw.avm.hscx[1] + 32, 32, "HiSax hscx B")) { + printk(KERN_WARNING + "HiSax: %s hscx B ports %x-%x already in use\n", + CardType[cs->typ], + cs->hw.avm.hscx[1] + 32, + cs->hw.avm.hscx[1] + 64); + release_ioregs(cs, 0xf); + return (0); + } + if (!request_region(cs->hw.avm.hscxfifo[1], 1, "HiSax hscx B fifo")) { + printk(KERN_WARNING + "HiSax: %s hscx B fifo port %x already in use\n", + CardType[cs->typ], + cs->hw.avm.hscxfifo[1]); + release_ioregs(cs, 0x1f); + return (0); + } + byteout(cs->hw.avm.cfg_reg, 0x0); + HZDELAY(HZ / 5 + 1); + byteout(cs->hw.avm.cfg_reg, 0x1); + HZDELAY(HZ / 5 + 1); + byteout(cs->hw.avm.cfg_reg, 0x0); + HZDELAY(HZ / 5 + 1); + val = cs->irq; + if (val == 9) + val = 2; + byteout(cs->hw.avm.cfg_reg + 1, val); + HZDELAY(HZ / 5 + 1); + byteout(cs->hw.avm.cfg_reg, 0x0); + HZDELAY(HZ / 5 + 1); + + val = bytein(cs->hw.avm.cfg_reg); + printk(KERN_INFO "AVM A1: Byte at %x is %x\n", + cs->hw.avm.cfg_reg, val); + val = bytein(cs->hw.avm.cfg_reg + 3); + printk(KERN_INFO "AVM A1: Byte at %x is %x\n", + cs->hw.avm.cfg_reg + 3, val); + val = bytein(cs->hw.avm.cfg_reg + 2); + printk(KERN_INFO "AVM A1: Byte at %x is %x\n", + cs->hw.avm.cfg_reg + 2, val); + val = bytein(cs->hw.avm.cfg_reg); + printk(KERN_INFO "AVM A1: Byte at %x is %x\n", + cs->hw.avm.cfg_reg, val); + + printk(KERN_INFO + "HiSax: %s config irq:%d cfg:0x%X\n", + CardType[cs->typ], cs->irq, + cs->hw.avm.cfg_reg); + printk(KERN_INFO + "HiSax: isac:0x%X/0x%X\n", + cs->hw.avm.isac + 32, cs->hw.avm.isacfifo); + printk(KERN_INFO + "HiSax: hscx A:0x%X/0x%X hscx B:0x%X/0x%X\n", + cs->hw.avm.hscx[0] + 32, cs->hw.avm.hscxfifo[0], + cs->hw.avm.hscx[1] + 32, cs->hw.avm.hscxfifo[1]); + + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->BC_Read_Reg = &ReadHSCX; + cs->BC_Write_Reg = &WriteHSCX; + cs->BC_Send_Data = &hscx_fill_fifo; + setup_isac(cs); + cs->cardmsg = &AVM_card_msg; + cs->irq_func = &avm_a1_interrupt; + ISACVersion(cs, "AVM A1:"); + if (HscxVersion(cs, "AVM A1:")) { + printk(KERN_WARNING + "AVM A1: wrong HSCX versions check IO address\n"); + release_ioregs(cs, 0x3f); + return (0); + } + return (1); +} diff --git a/drivers/isdn/hisax/avm_a1p.c b/drivers/isdn/hisax/avm_a1p.c new file mode 100644 index 000000000000..d643bb32ad09 --- /dev/null +++ b/drivers/isdn/hisax/avm_a1p.c @@ -0,0 +1,268 @@ +/* $Id: avm_a1p.c,v 2.9.2.5 2004/01/24 20:47:19 keil Exp $ + * + * low level stuff for the following AVM cards: + * A1 PCMCIA + * FRITZ!Card PCMCIA + * FRITZ!Card PCMCIA 2.0 + * + * Author Carsten Paeth + * Copyright by Carsten Paeth <calle@calle.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/init.h> +#include "hisax.h" +#include "isac.h" +#include "hscx.h" +#include "isdnl1.h" + +/* register offsets */ +#define ADDRREG_OFFSET 0x02 +#define DATAREG_OFFSET 0x03 +#define ASL0_OFFSET 0x04 +#define ASL1_OFFSET 0x05 +#define MODREG_OFFSET 0x06 +#define VERREG_OFFSET 0x07 + +/* address offsets */ +#define ISAC_FIFO_OFFSET 0x00 +#define ISAC_REG_OFFSET 0x20 +#define HSCX_CH_DIFF 0x40 +#define HSCX_FIFO_OFFSET 0x80 +#define HSCX_REG_OFFSET 0xa0 + +/* read bits ASL0 */ +#define ASL0_R_TIMER 0x10 /* active low */ +#define ASL0_R_ISAC 0x20 /* active low */ +#define ASL0_R_HSCX 0x40 /* active low */ +#define ASL0_R_TESTBIT 0x80 +#define ASL0_R_IRQPENDING (ASL0_R_ISAC|ASL0_R_HSCX|ASL0_R_TIMER) + +/* write bits ASL0 */ +#define ASL0_W_RESET 0x01 +#define ASL0_W_TDISABLE 0x02 +#define ASL0_W_TRESET 0x04 +#define ASL0_W_IRQENABLE 0x08 +#define ASL0_W_TESTBIT 0x80 + +/* write bits ASL1 */ +#define ASL1_W_LED0 0x10 +#define ASL1_W_LED1 0x20 +#define ASL1_W_ENABLE_S0 0xC0 + +#define byteout(addr,val) outb(val,addr) +#define bytein(addr) inb(addr) + +static const char *avm_revision = "$Revision: 2.9.2.5 $"; + +static inline u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + u_char ret; + + offset -= 0x20; + byteout(cs->hw.avm.cfg_reg+ADDRREG_OFFSET,ISAC_REG_OFFSET+offset); + ret = bytein(cs->hw.avm.cfg_reg+DATAREG_OFFSET); + return ret; +} + +static inline void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + offset -= 0x20; + byteout(cs->hw.avm.cfg_reg+ADDRREG_OFFSET,ISAC_REG_OFFSET+offset); + byteout(cs->hw.avm.cfg_reg+DATAREG_OFFSET, value); +} + +static inline void +ReadISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + byteout(cs->hw.avm.cfg_reg+ADDRREG_OFFSET,ISAC_FIFO_OFFSET); + insb(cs->hw.avm.cfg_reg+DATAREG_OFFSET, data, size); +} + +static inline void +WriteISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + byteout(cs->hw.avm.cfg_reg+ADDRREG_OFFSET,ISAC_FIFO_OFFSET); + outsb(cs->hw.avm.cfg_reg+DATAREG_OFFSET, data, size); +} + +static inline u_char +ReadHSCX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + u_char ret; + + offset -= 0x20; + byteout(cs->hw.avm.cfg_reg+ADDRREG_OFFSET, + HSCX_REG_OFFSET+hscx*HSCX_CH_DIFF+offset); + ret = bytein(cs->hw.avm.cfg_reg+DATAREG_OFFSET); + return ret; +} + +static inline void +WriteHSCX(struct IsdnCardState *cs, int hscx, u_char offset, u_char value) +{ + offset -= 0x20; + byteout(cs->hw.avm.cfg_reg+ADDRREG_OFFSET, + HSCX_REG_OFFSET+hscx*HSCX_CH_DIFF+offset); + byteout(cs->hw.avm.cfg_reg+DATAREG_OFFSET, value); +} + +static inline void +ReadHSCXfifo(struct IsdnCardState *cs, int hscx, u_char * data, int size) +{ + byteout(cs->hw.avm.cfg_reg+ADDRREG_OFFSET, + HSCX_FIFO_OFFSET+hscx*HSCX_CH_DIFF); + insb(cs->hw.avm.cfg_reg+DATAREG_OFFSET, data, size); +} + +static inline void +WriteHSCXfifo(struct IsdnCardState *cs, int hscx, u_char * data, int size) +{ + byteout(cs->hw.avm.cfg_reg+ADDRREG_OFFSET, + HSCX_FIFO_OFFSET+hscx*HSCX_CH_DIFF); + outsb(cs->hw.avm.cfg_reg+DATAREG_OFFSET, data, size); +} + +/* + * fast interrupt HSCX stuff goes here + */ + +#define READHSCX(cs, nr, reg) ReadHSCX(cs, nr, reg) +#define WRITEHSCX(cs, nr, reg, data) WriteHSCX(cs, nr, reg, data) +#define READHSCXFIFO(cs, nr, ptr, cnt) ReadHSCXfifo(cs, nr, ptr, cnt) +#define WRITEHSCXFIFO(cs, nr, ptr, cnt) WriteHSCXfifo(cs, nr, ptr, cnt) + +#include "hscx_irq.c" + +static irqreturn_t +avm_a1p_interrupt(int intno, void *dev_id, struct pt_regs *regs) +{ + struct IsdnCardState *cs = dev_id; + u_char val, sval; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + while ((sval = (~bytein(cs->hw.avm.cfg_reg+ASL0_OFFSET) & ASL0_R_IRQPENDING))) { + if (cs->debug & L1_DEB_INTSTAT) + debugl1(cs, "avm IntStatus %x", sval); + if (sval & ASL0_R_HSCX) { + val = ReadHSCX(cs, 1, HSCX_ISTA); + if (val) + hscx_int_main(cs, val); + } + if (sval & ASL0_R_ISAC) { + val = ReadISAC(cs, ISAC_ISTA); + if (val) + isac_interrupt(cs, val); + } + } + WriteHSCX(cs, 0, HSCX_MASK, 0xff); + WriteHSCX(cs, 1, HSCX_MASK, 0xff); + WriteISAC(cs, ISAC_MASK, 0xff); + WriteISAC(cs, ISAC_MASK, 0x00); + WriteHSCX(cs, 0, HSCX_MASK, 0x00); + WriteHSCX(cs, 1, HSCX_MASK, 0x00); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static int +AVM_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + byteout(cs->hw.avm.cfg_reg+ASL0_OFFSET,0x00); + HZDELAY(HZ / 5 + 1); + byteout(cs->hw.avm.cfg_reg+ASL0_OFFSET,ASL0_W_RESET); + HZDELAY(HZ / 5 + 1); + byteout(cs->hw.avm.cfg_reg+ASL0_OFFSET,0x00); + spin_unlock_irqrestore(&cs->lock, flags); + return 0; + + case CARD_RELEASE: + /* free_irq is done in HiSax_closecard(). */ + /* free_irq(cs->irq, cs); */ + return 0; + + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + byteout(cs->hw.avm.cfg_reg+ASL0_OFFSET,ASL0_W_TDISABLE|ASL0_W_TRESET|ASL0_W_IRQENABLE); + clear_pending_isac_ints(cs); + clear_pending_hscx_ints(cs); + inithscxisac(cs, 1); + inithscxisac(cs, 2); + spin_unlock_irqrestore(&cs->lock, flags); + return 0; + + case CARD_TEST: + /* we really don't need it for the PCMCIA Version */ + return 0; + + default: + /* all card drivers ignore others, so we do the same */ + return 0; + } + return 0; +} + +int +setup_avm_a1_pcmcia(struct IsdnCard *card) +{ + u_char model, vers; + struct IsdnCardState *cs = card->cs; + char tmp[64]; + + + strcpy(tmp, avm_revision); + printk(KERN_INFO "HiSax: AVM A1 PCMCIA driver Rev. %s\n", + HiSax_getrev(tmp)); + if (cs->typ != ISDN_CTYPE_A1_PCMCIA) + return (0); + + cs->hw.avm.cfg_reg = card->para[1]; + cs->irq = card->para[0]; + + + byteout(cs->hw.avm.cfg_reg+ASL1_OFFSET, ASL1_W_ENABLE_S0); + byteout(cs->hw.avm.cfg_reg+ASL0_OFFSET,0x00); + HZDELAY(HZ / 5 + 1); + byteout(cs->hw.avm.cfg_reg+ASL0_OFFSET,ASL0_W_RESET); + HZDELAY(HZ / 5 + 1); + byteout(cs->hw.avm.cfg_reg+ASL0_OFFSET,0x00); + + byteout(cs->hw.avm.cfg_reg+ASL0_OFFSET, ASL0_W_TDISABLE|ASL0_W_TRESET); + + model = bytein(cs->hw.avm.cfg_reg+MODREG_OFFSET); + vers = bytein(cs->hw.avm.cfg_reg+VERREG_OFFSET); + + printk(KERN_INFO "AVM A1 PCMCIA: io 0x%x irq %d model %d version %d\n", + cs->hw.avm.cfg_reg, cs->irq, model, vers); + + setup_isac(cs); + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->BC_Read_Reg = &ReadHSCX; + cs->BC_Write_Reg = &WriteHSCX; + cs->BC_Send_Data = &hscx_fill_fifo; + cs->cardmsg = &AVM_card_msg; + cs->irq_flags = SA_SHIRQ; + cs->irq_func = &avm_a1p_interrupt; + + ISACVersion(cs, "AVM A1 PCMCIA:"); + if (HscxVersion(cs, "AVM A1 PCMCIA:")) { + printk(KERN_WARNING + "AVM A1 PCMCIA: wrong HSCX versions check IO address\n"); + return (0); + } + return (1); +} diff --git a/drivers/isdn/hisax/avm_pci.c b/drivers/isdn/hisax/avm_pci.c new file mode 100644 index 000000000000..6fcb2cf7b0b6 --- /dev/null +++ b/drivers/isdn/hisax/avm_pci.c @@ -0,0 +1,865 @@ +/* $Id: avm_pci.c,v 1.29.2.4 2004/02/11 13:21:32 keil Exp $ + * + * low level stuff for AVM Fritz!PCI and ISA PnP isdn cards + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * Thanks to AVM, Berlin for information + * + */ + +#include <linux/config.h> +#include <linux/init.h> +#include "hisax.h" +#include "isac.h" +#include "isdnl1.h" +#include <linux/pci.h> +#include <linux/isapnp.h> +#include <linux/interrupt.h> + +extern const char *CardType[]; +static const char *avm_pci_rev = "$Revision: 1.29.2.4 $"; + +#define AVM_FRITZ_PCI 1 +#define AVM_FRITZ_PNP 2 + +#define HDLC_FIFO 0x0 +#define HDLC_STATUS 0x4 + +#define AVM_HDLC_1 0x00 +#define AVM_HDLC_2 0x01 +#define AVM_ISAC_FIFO 0x02 +#define AVM_ISAC_REG_LOW 0x04 +#define AVM_ISAC_REG_HIGH 0x06 + +#define AVM_STATUS0_IRQ_ISAC 0x01 +#define AVM_STATUS0_IRQ_HDLC 0x02 +#define AVM_STATUS0_IRQ_TIMER 0x04 +#define AVM_STATUS0_IRQ_MASK 0x07 + +#define AVM_STATUS0_RESET 0x01 +#define AVM_STATUS0_DIS_TIMER 0x02 +#define AVM_STATUS0_RES_TIMER 0x04 +#define AVM_STATUS0_ENA_IRQ 0x08 +#define AVM_STATUS0_TESTBIT 0x10 + +#define AVM_STATUS1_INT_SEL 0x0f +#define AVM_STATUS1_ENA_IOM 0x80 + +#define HDLC_MODE_ITF_FLG 0x01 +#define HDLC_MODE_TRANS 0x02 +#define HDLC_MODE_CCR_7 0x04 +#define HDLC_MODE_CCR_16 0x08 +#define HDLC_MODE_TESTLOOP 0x80 + +#define HDLC_INT_XPR 0x80 +#define HDLC_INT_XDU 0x40 +#define HDLC_INT_RPR 0x20 +#define HDLC_INT_MASK 0xE0 + +#define HDLC_STAT_RME 0x01 +#define HDLC_STAT_RDO 0x10 +#define HDLC_STAT_CRCVFRRAB 0x0E +#define HDLC_STAT_CRCVFR 0x06 +#define HDLC_STAT_RML_MASK 0x3f00 + +#define HDLC_CMD_XRS 0x80 +#define HDLC_CMD_XME 0x01 +#define HDLC_CMD_RRS 0x20 +#define HDLC_CMD_XML_MASK 0x3f00 + + +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + register u_char idx = (offset > 0x2f) ? AVM_ISAC_REG_HIGH : AVM_ISAC_REG_LOW; + register u_char val; + + outb(idx, cs->hw.avm.cfg_reg + 4); + val = inb(cs->hw.avm.isac + (offset & 0xf)); + return (val); +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + register u_char idx = (offset > 0x2f) ? AVM_ISAC_REG_HIGH : AVM_ISAC_REG_LOW; + + outb(idx, cs->hw.avm.cfg_reg + 4); + outb(value, cs->hw.avm.isac + (offset & 0xf)); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + outb(AVM_ISAC_FIFO, cs->hw.avm.cfg_reg + 4); + insb(cs->hw.avm.isac, data, size); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + outb(AVM_ISAC_FIFO, cs->hw.avm.cfg_reg + 4); + outsb(cs->hw.avm.isac, data, size); +} + +static inline u_int +ReadHDLCPCI(struct IsdnCardState *cs, int chan, u_char offset) +{ + register u_int idx = chan ? AVM_HDLC_2 : AVM_HDLC_1; + register u_int val; + + outl(idx, cs->hw.avm.cfg_reg + 4); + val = inl(cs->hw.avm.isac + offset); + return (val); +} + +static inline void +WriteHDLCPCI(struct IsdnCardState *cs, int chan, u_char offset, u_int value) +{ + register u_int idx = chan ? AVM_HDLC_2 : AVM_HDLC_1; + + outl(idx, cs->hw.avm.cfg_reg + 4); + outl(value, cs->hw.avm.isac + offset); +} + +static inline u_char +ReadHDLCPnP(struct IsdnCardState *cs, int chan, u_char offset) +{ + register u_char idx = chan ? AVM_HDLC_2 : AVM_HDLC_1; + register u_char val; + + outb(idx, cs->hw.avm.cfg_reg + 4); + val = inb(cs->hw.avm.isac + offset); + return (val); +} + +static inline void +WriteHDLCPnP(struct IsdnCardState *cs, int chan, u_char offset, u_char value) +{ + register u_char idx = chan ? AVM_HDLC_2 : AVM_HDLC_1; + + outb(idx, cs->hw.avm.cfg_reg + 4); + outb(value, cs->hw.avm.isac + offset); +} + +static u_char +ReadHDLC_s(struct IsdnCardState *cs, int chan, u_char offset) +{ + return(0xff & ReadHDLCPCI(cs, chan, offset)); +} + +static void +WriteHDLC_s(struct IsdnCardState *cs, int chan, u_char offset, u_char value) +{ + WriteHDLCPCI(cs, chan, offset, value); +} + +static inline +struct BCState *Sel_BCS(struct IsdnCardState *cs, int channel) +{ + if (cs->bcs[0].mode && (cs->bcs[0].channel == channel)) + return(&cs->bcs[0]); + else if (cs->bcs[1].mode && (cs->bcs[1].channel == channel)) + return(&cs->bcs[1]); + else + return(NULL); +} + +void +write_ctrl(struct BCState *bcs, int which) { + + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs, "hdlc %c wr%x ctrl %x", + 'A' + bcs->channel, which, bcs->hw.hdlc.ctrl.ctrl); + if (bcs->cs->subtyp == AVM_FRITZ_PCI) { + WriteHDLCPCI(bcs->cs, bcs->channel, HDLC_STATUS, bcs->hw.hdlc.ctrl.ctrl); + } else { + if (which & 4) + WriteHDLCPnP(bcs->cs, bcs->channel, HDLC_STATUS + 2, + bcs->hw.hdlc.ctrl.sr.mode); + if (which & 2) + WriteHDLCPnP(bcs->cs, bcs->channel, HDLC_STATUS + 1, + bcs->hw.hdlc.ctrl.sr.xml); + if (which & 1) + WriteHDLCPnP(bcs->cs, bcs->channel, HDLC_STATUS, + bcs->hw.hdlc.ctrl.sr.cmd); + } +} + +void +modehdlc(struct BCState *bcs, int mode, int bc) +{ + struct IsdnCardState *cs = bcs->cs; + int hdlc = bcs->channel; + + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hdlc %c mode %d --> %d ichan %d --> %d", + 'A' + hdlc, bcs->mode, mode, hdlc, bc); + bcs->hw.hdlc.ctrl.ctrl = 0; + switch (mode) { + case (-1): /* used for init */ + bcs->mode = 1; + bcs->channel = bc; + bc = 0; + case (L1_MODE_NULL): + if (bcs->mode == L1_MODE_NULL) + return; + bcs->hw.hdlc.ctrl.sr.cmd = HDLC_CMD_XRS | HDLC_CMD_RRS; + bcs->hw.hdlc.ctrl.sr.mode = HDLC_MODE_TRANS; + write_ctrl(bcs, 5); + bcs->mode = L1_MODE_NULL; + bcs->channel = bc; + break; + case (L1_MODE_TRANS): + bcs->mode = mode; + bcs->channel = bc; + bcs->hw.hdlc.ctrl.sr.cmd = HDLC_CMD_XRS | HDLC_CMD_RRS; + bcs->hw.hdlc.ctrl.sr.mode = HDLC_MODE_TRANS; + write_ctrl(bcs, 5); + bcs->hw.hdlc.ctrl.sr.cmd = HDLC_CMD_XRS; + write_ctrl(bcs, 1); + bcs->hw.hdlc.ctrl.sr.cmd = 0; + schedule_event(bcs, B_XMTBUFREADY); + break; + case (L1_MODE_HDLC): + bcs->mode = mode; + bcs->channel = bc; + bcs->hw.hdlc.ctrl.sr.cmd = HDLC_CMD_XRS | HDLC_CMD_RRS; + bcs->hw.hdlc.ctrl.sr.mode = HDLC_MODE_ITF_FLG; + write_ctrl(bcs, 5); + bcs->hw.hdlc.ctrl.sr.cmd = HDLC_CMD_XRS; + write_ctrl(bcs, 1); + bcs->hw.hdlc.ctrl.sr.cmd = 0; + schedule_event(bcs, B_XMTBUFREADY); + break; + } +} + +static inline void +hdlc_empty_fifo(struct BCState *bcs, int count) +{ + register u_int *ptr; + u_char *p; + u_char idx = bcs->channel ? AVM_HDLC_2 : AVM_HDLC_1; + int cnt=0; + struct IsdnCardState *cs = bcs->cs; + + if ((cs->debug & L1_DEB_HSCX) && !(cs->debug & L1_DEB_HSCX_FIFO)) + debugl1(cs, "hdlc_empty_fifo %d", count); + if (bcs->hw.hdlc.rcvidx + count > HSCX_BUFMAX) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "hdlc_empty_fifo: incoming packet too large"); + return; + } + p = bcs->hw.hdlc.rcvbuf + bcs->hw.hdlc.rcvidx; + ptr = (u_int *)p; + bcs->hw.hdlc.rcvidx += count; + if (cs->subtyp == AVM_FRITZ_PCI) { + outl(idx, cs->hw.avm.cfg_reg + 4); + while (cnt < count) { +#ifdef __powerpc__ +#ifdef CONFIG_APUS + *ptr++ = in_le32((unsigned *)(cs->hw.avm.isac +_IO_BASE)); +#else + *ptr++ = in_be32((unsigned *)(cs->hw.avm.isac +_IO_BASE)); +#endif /* CONFIG_APUS */ +#else + *ptr++ = inl(cs->hw.avm.isac); +#endif /* __powerpc__ */ + cnt += 4; + } + } else { + outb(idx, cs->hw.avm.cfg_reg + 4); + while (cnt < count) { + *p++ = inb(cs->hw.avm.isac); + cnt++; + } + } + if (cs->debug & L1_DEB_HSCX_FIFO) { + char *t = bcs->blog; + + if (cs->subtyp == AVM_FRITZ_PNP) + p = (u_char *) ptr; + t += sprintf(t, "hdlc_empty_fifo %c cnt %d", + bcs->channel ? 'B' : 'A', count); + QuickHex(t, p, count); + debugl1(cs, bcs->blog); + } +} + +static inline void +hdlc_fill_fifo(struct BCState *bcs) +{ + struct IsdnCardState *cs = bcs->cs; + int count, cnt =0; + int fifo_size = 32; + u_char *p; + u_int *ptr; + + if ((cs->debug & L1_DEB_HSCX) && !(cs->debug & L1_DEB_HSCX_FIFO)) + debugl1(cs, "hdlc_fill_fifo"); + if (!bcs->tx_skb) + return; + if (bcs->tx_skb->len <= 0) + return; + + bcs->hw.hdlc.ctrl.sr.cmd &= ~HDLC_CMD_XME; + if (bcs->tx_skb->len > fifo_size) { + count = fifo_size; + } else { + count = bcs->tx_skb->len; + if (bcs->mode != L1_MODE_TRANS) + bcs->hw.hdlc.ctrl.sr.cmd |= HDLC_CMD_XME; + } + if ((cs->debug & L1_DEB_HSCX) && !(cs->debug & L1_DEB_HSCX_FIFO)) + debugl1(cs, "hdlc_fill_fifo %d/%ld", count, bcs->tx_skb->len); + p = bcs->tx_skb->data; + ptr = (u_int *)p; + skb_pull(bcs->tx_skb, count); + bcs->tx_cnt -= count; + bcs->hw.hdlc.count += count; + bcs->hw.hdlc.ctrl.sr.xml = ((count == fifo_size) ? 0 : count); + write_ctrl(bcs, 3); /* sets the correct index too */ + if (cs->subtyp == AVM_FRITZ_PCI) { + while (cnt<count) { +#ifdef __powerpc__ +#ifdef CONFIG_APUS + out_le32((unsigned *)(cs->hw.avm.isac +_IO_BASE), *ptr++); +#else + out_be32((unsigned *)(cs->hw.avm.isac +_IO_BASE), *ptr++); +#endif /* CONFIG_APUS */ +#else + outl(*ptr++, cs->hw.avm.isac); +#endif /* __powerpc__ */ + cnt += 4; + } + } else { + while (cnt<count) { + outb(*p++, cs->hw.avm.isac); + cnt++; + } + } + if (cs->debug & L1_DEB_HSCX_FIFO) { + char *t = bcs->blog; + + if (cs->subtyp == AVM_FRITZ_PNP) + p = (u_char *) ptr; + t += sprintf(t, "hdlc_fill_fifo %c cnt %d", + bcs->channel ? 'B' : 'A', count); + QuickHex(t, p, count); + debugl1(cs, bcs->blog); + } +} + +static inline void +HDLC_irq(struct BCState *bcs, u_int stat) { + int len; + struct sk_buff *skb; + + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs, "ch%d stat %#x", bcs->channel, stat); + if (stat & HDLC_INT_RPR) { + if (stat & HDLC_STAT_RDO) { + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs, "RDO"); + else + debugl1(bcs->cs, "ch%d stat %#x", bcs->channel, stat); + bcs->hw.hdlc.ctrl.sr.xml = 0; + bcs->hw.hdlc.ctrl.sr.cmd |= HDLC_CMD_RRS; + write_ctrl(bcs, 1); + bcs->hw.hdlc.ctrl.sr.cmd &= ~HDLC_CMD_RRS; + write_ctrl(bcs, 1); + bcs->hw.hdlc.rcvidx = 0; + } else { + if (!(len = (stat & HDLC_STAT_RML_MASK)>>8)) + len = 32; + hdlc_empty_fifo(bcs, len); + if ((stat & HDLC_STAT_RME) || (bcs->mode == L1_MODE_TRANS)) { + if (((stat & HDLC_STAT_CRCVFRRAB)==HDLC_STAT_CRCVFR) || + (bcs->mode == L1_MODE_TRANS)) { + if (!(skb = dev_alloc_skb(bcs->hw.hdlc.rcvidx))) + printk(KERN_WARNING "HDLC: receive out of memory\n"); + else { + memcpy(skb_put(skb, bcs->hw.hdlc.rcvidx), + bcs->hw.hdlc.rcvbuf, bcs->hw.hdlc.rcvidx); + skb_queue_tail(&bcs->rqueue, skb); + } + bcs->hw.hdlc.rcvidx = 0; + schedule_event(bcs, B_RCVBUFREADY); + } else { + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs, "invalid frame"); + else + debugl1(bcs->cs, "ch%d invalid frame %#x", bcs->channel, stat); + bcs->hw.hdlc.rcvidx = 0; + } + } + } + } + if (stat & HDLC_INT_XDU) { + /* Here we lost an TX interrupt, so + * restart transmitting the whole frame. + */ + if (bcs->tx_skb) { + skb_push(bcs->tx_skb, bcs->hw.hdlc.count); + bcs->tx_cnt += bcs->hw.hdlc.count; + bcs->hw.hdlc.count = 0; + if (bcs->cs->debug & L1_DEB_WARN) + debugl1(bcs->cs, "ch%d XDU", bcs->channel); + } else if (bcs->cs->debug & L1_DEB_WARN) + debugl1(bcs->cs, "ch%d XDU without skb", bcs->channel); + bcs->hw.hdlc.ctrl.sr.xml = 0; + bcs->hw.hdlc.ctrl.sr.cmd |= HDLC_CMD_XRS; + write_ctrl(bcs, 1); + bcs->hw.hdlc.ctrl.sr.cmd &= ~HDLC_CMD_XRS; + write_ctrl(bcs, 1); + hdlc_fill_fifo(bcs); + } else if (stat & HDLC_INT_XPR) { + if (bcs->tx_skb) { + if (bcs->tx_skb->len) { + hdlc_fill_fifo(bcs); + return; + } else { + if (test_bit(FLG_LLI_L1WAKEUP,&bcs->st->lli.flag) && + (PACKET_NOACK != bcs->tx_skb->pkt_type)) { + u_long flags; + spin_lock_irqsave(&bcs->aclock, flags); + bcs->ackcnt += bcs->hw.hdlc.count; + spin_unlock_irqrestore(&bcs->aclock, flags); + schedule_event(bcs, B_ACKPENDING); + } + dev_kfree_skb_irq(bcs->tx_skb); + bcs->hw.hdlc.count = 0; + bcs->tx_skb = NULL; + } + } + if ((bcs->tx_skb = skb_dequeue(&bcs->squeue))) { + bcs->hw.hdlc.count = 0; + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + hdlc_fill_fifo(bcs); + } else { + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + schedule_event(bcs, B_XMTBUFREADY); + } + } +} + +inline void +HDLC_irq_main(struct IsdnCardState *cs) +{ + u_int stat; + struct BCState *bcs; + + if (cs->subtyp == AVM_FRITZ_PCI) { + stat = ReadHDLCPCI(cs, 0, HDLC_STATUS); + } else { + stat = ReadHDLCPnP(cs, 0, HDLC_STATUS); + if (stat & HDLC_INT_RPR) + stat |= (ReadHDLCPnP(cs, 0, HDLC_STATUS+1))<<8; + } + if (stat & HDLC_INT_MASK) { + if (!(bcs = Sel_BCS(cs, 0))) { + if (cs->debug) + debugl1(cs, "hdlc spurious channel 0 IRQ"); + } else + HDLC_irq(bcs, stat); + } + if (cs->subtyp == AVM_FRITZ_PCI) { + stat = ReadHDLCPCI(cs, 1, HDLC_STATUS); + } else { + stat = ReadHDLCPnP(cs, 1, HDLC_STATUS); + if (stat & HDLC_INT_RPR) + stat |= (ReadHDLCPnP(cs, 1, HDLC_STATUS+1))<<8; + } + if (stat & HDLC_INT_MASK) { + if (!(bcs = Sel_BCS(cs, 1))) { + if (cs->debug) + debugl1(cs, "hdlc spurious channel 1 IRQ"); + } else + HDLC_irq(bcs, stat); + } +} + +void +hdlc_l2l1(struct PStack *st, int pr, void *arg) +{ + struct BCState *bcs = st->l1.bcs; + struct sk_buff *skb = arg; + u_long flags; + + switch (pr) { + case (PH_DATA | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + skb_queue_tail(&bcs->squeue, skb); + } else { + bcs->tx_skb = skb; + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->hw.hdlc.count = 0; + bcs->cs->BC_Send_Data(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | INDICATION): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + printk(KERN_WARNING "hdlc_l2l1: this shouldn't happen\n"); + } else { + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->tx_skb = skb; + bcs->hw.hdlc.count = 0; + bcs->cs->BC_Send_Data(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | REQUEST): + if (!bcs->tx_skb) { + test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } else + test_and_set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + case (PH_ACTIVATE | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + test_and_set_bit(BC_FLG_ACTIV, &bcs->Flag); + modehdlc(bcs, st->l1.mode, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | REQUEST): + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | CONFIRM): + spin_lock_irqsave(&bcs->cs->lock, flags); + test_and_clear_bit(BC_FLG_ACTIV, &bcs->Flag); + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + modehdlc(bcs, 0, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + st->l1.l1l2(st, PH_DEACTIVATE | CONFIRM, NULL); + break; + } +} + +void +close_hdlcstate(struct BCState *bcs) +{ + modehdlc(bcs, 0, 0); + if (test_and_clear_bit(BC_FLG_INIT, &bcs->Flag)) { + if (bcs->hw.hdlc.rcvbuf) { + kfree(bcs->hw.hdlc.rcvbuf); + bcs->hw.hdlc.rcvbuf = NULL; + } + if (bcs->blog) { + kfree(bcs->blog); + bcs->blog = NULL; + } + skb_queue_purge(&bcs->rqueue); + skb_queue_purge(&bcs->squeue); + if (bcs->tx_skb) { + dev_kfree_skb_any(bcs->tx_skb); + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + } + } +} + +int +open_hdlcstate(struct IsdnCardState *cs, struct BCState *bcs) +{ + if (!test_and_set_bit(BC_FLG_INIT, &bcs->Flag)) { + if (!(bcs->hw.hdlc.rcvbuf = kmalloc(HSCX_BUFMAX, GFP_ATOMIC))) { + printk(KERN_WARNING + "HiSax: No memory for hdlc.rcvbuf\n"); + return (1); + } + if (!(bcs->blog = kmalloc(MAX_BLOG_SPACE, GFP_ATOMIC))) { + printk(KERN_WARNING + "HiSax: No memory for bcs->blog\n"); + test_and_clear_bit(BC_FLG_INIT, &bcs->Flag); + kfree(bcs->hw.hdlc.rcvbuf); + bcs->hw.hdlc.rcvbuf = NULL; + return (2); + } + skb_queue_head_init(&bcs->rqueue); + skb_queue_head_init(&bcs->squeue); + } + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->event = 0; + bcs->hw.hdlc.rcvidx = 0; + bcs->tx_cnt = 0; + return (0); +} + +int +setstack_hdlc(struct PStack *st, struct BCState *bcs) +{ + bcs->channel = st->l1.bc; + if (open_hdlcstate(st->l1.hardware, bcs)) + return (-1); + st->l1.bcs = bcs; + st->l2.l2l1 = hdlc_l2l1; + setstack_manager(st); + bcs->st = st; + setstack_l1_B(st); + return (0); +} + +void __init +clear_pending_hdlc_ints(struct IsdnCardState *cs) +{ + u_int val; + + if (cs->subtyp == AVM_FRITZ_PCI) { + val = ReadHDLCPCI(cs, 0, HDLC_STATUS); + debugl1(cs, "HDLC 1 STA %x", val); + val = ReadHDLCPCI(cs, 1, HDLC_STATUS); + debugl1(cs, "HDLC 2 STA %x", val); + } else { + val = ReadHDLCPnP(cs, 0, HDLC_STATUS); + debugl1(cs, "HDLC 1 STA %x", val); + val = ReadHDLCPnP(cs, 0, HDLC_STATUS + 1); + debugl1(cs, "HDLC 1 RML %x", val); + val = ReadHDLCPnP(cs, 0, HDLC_STATUS + 2); + debugl1(cs, "HDLC 1 MODE %x", val); + val = ReadHDLCPnP(cs, 0, HDLC_STATUS + 3); + debugl1(cs, "HDLC 1 VIN %x", val); + val = ReadHDLCPnP(cs, 1, HDLC_STATUS); + debugl1(cs, "HDLC 2 STA %x", val); + val = ReadHDLCPnP(cs, 1, HDLC_STATUS + 1); + debugl1(cs, "HDLC 2 RML %x", val); + val = ReadHDLCPnP(cs, 1, HDLC_STATUS + 2); + debugl1(cs, "HDLC 2 MODE %x", val); + val = ReadHDLCPnP(cs, 1, HDLC_STATUS + 3); + debugl1(cs, "HDLC 2 VIN %x", val); + } +} + +void __init +inithdlc(struct IsdnCardState *cs) +{ + cs->bcs[0].BC_SetStack = setstack_hdlc; + cs->bcs[1].BC_SetStack = setstack_hdlc; + cs->bcs[0].BC_Close = close_hdlcstate; + cs->bcs[1].BC_Close = close_hdlcstate; + modehdlc(cs->bcs, -1, 0); + modehdlc(cs->bcs + 1, -1, 1); +} + +static irqreturn_t +avm_pcipnp_interrupt(int intno, void *dev_id, struct pt_regs *regs) +{ + struct IsdnCardState *cs = dev_id; + u_long flags; + u_char val; + u_char sval; + + spin_lock_irqsave(&cs->lock, flags); + sval = inb(cs->hw.avm.cfg_reg + 2); + if ((sval & AVM_STATUS0_IRQ_MASK) == AVM_STATUS0_IRQ_MASK) { + /* possible a shared IRQ reqest */ + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_NONE; + } + if (!(sval & AVM_STATUS0_IRQ_ISAC)) { + val = ReadISAC(cs, ISAC_ISTA); + isac_interrupt(cs, val); + } + if (!(sval & AVM_STATUS0_IRQ_HDLC)) { + HDLC_irq_main(cs); + } + WriteISAC(cs, ISAC_MASK, 0xFF); + WriteISAC(cs, ISAC_MASK, 0x0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static void +reset_avmpcipnp(struct IsdnCardState *cs) +{ + printk(KERN_INFO "AVM PCI/PnP: reset\n"); + outb(AVM_STATUS0_RESET | AVM_STATUS0_DIS_TIMER, cs->hw.avm.cfg_reg + 2); + mdelay(10); + outb(AVM_STATUS0_DIS_TIMER | AVM_STATUS0_RES_TIMER | AVM_STATUS0_ENA_IRQ, cs->hw.avm.cfg_reg + 2); + outb(AVM_STATUS1_ENA_IOM | cs->irq, cs->hw.avm.cfg_reg + 3); + mdelay(10); + printk(KERN_INFO "AVM PCI/PnP: S1 %x\n", inb(cs->hw.avm.cfg_reg + 3)); +} + +static int +AVM_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + reset_avmpcipnp(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return(0); + case CARD_RELEASE: + outb(0, cs->hw.avm.cfg_reg + 2); + release_region(cs->hw.avm.cfg_reg, 32); + return(0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + reset_avmpcipnp(cs); + clear_pending_isac_ints(cs); + initisac(cs); + inithdlc(cs); + outb(AVM_STATUS0_DIS_TIMER | AVM_STATUS0_RES_TIMER, + cs->hw.avm.cfg_reg + 2); + WriteISAC(cs, ISAC_MASK, 0); + outb(AVM_STATUS0_DIS_TIMER | AVM_STATUS0_RES_TIMER | + AVM_STATUS0_ENA_IRQ, cs->hw.avm.cfg_reg + 2); + /* RESET Receiver and Transmitter */ + WriteISAC(cs, ISAC_CMDR, 0x41); + spin_unlock_irqrestore(&cs->lock, flags); + return(0); + case CARD_TEST: + return(0); + } + return(0); +} + +#ifdef CONFIG_PCI +static struct pci_dev *dev_avm __initdata = NULL; +#endif +#ifdef __ISAPNP__ +static struct pnp_card *pnp_avm_c __initdata = NULL; +#endif + +int __init +setup_avm_pcipnp(struct IsdnCard *card) +{ + u_int val, ver; + struct IsdnCardState *cs = card->cs; + char tmp[64]; + + strcpy(tmp, avm_pci_rev); + printk(KERN_INFO "HiSax: AVM PCI driver Rev. %s\n", HiSax_getrev(tmp)); + if (cs->typ != ISDN_CTYPE_FRITZPCI) + return (0); + if (card->para[1]) { + /* old manual method */ + cs->hw.avm.cfg_reg = card->para[1]; + cs->irq = card->para[0]; + cs->subtyp = AVM_FRITZ_PNP; + goto ready; + } +#ifdef __ISAPNP__ + if (isapnp_present()) { + struct pnp_dev *pnp_avm_d = NULL; + if ((pnp_avm_c = pnp_find_card( + ISAPNP_VENDOR('A', 'V', 'M'), + ISAPNP_FUNCTION(0x0900), pnp_avm_c))) { + if ((pnp_avm_d = pnp_find_dev(pnp_avm_c, + ISAPNP_VENDOR('A', 'V', 'M'), + ISAPNP_FUNCTION(0x0900), pnp_avm_d))) { + int err; + + pnp_disable_dev(pnp_avm_d); + err = pnp_activate_dev(pnp_avm_d); + if (err<0) { + printk(KERN_WARNING "%s: pnp_activate_dev ret(%d)\n", + __FUNCTION__, err); + return(0); + } + cs->hw.avm.cfg_reg = + pnp_port_start(pnp_avm_d, 0); + cs->irq = pnp_irq(pnp_avm_d, 0); + if (!cs->irq) { + printk(KERN_ERR "FritzPnP:No IRQ\n"); + return(0); + } + if (!cs->hw.avm.cfg_reg) { + printk(KERN_ERR "FritzPnP:No IO address\n"); + return(0); + } + cs->subtyp = AVM_FRITZ_PNP; + goto ready; + } + } + } else { + printk(KERN_INFO "FritzPnP: no ISA PnP present\n"); + } +#endif +#ifdef CONFIG_PCI + if ((dev_avm = pci_find_device(PCI_VENDOR_ID_AVM, + PCI_DEVICE_ID_AVM_A1, dev_avm))) { + if (pci_enable_device(dev_avm)) + return(0); + cs->irq = dev_avm->irq; + if (!cs->irq) { + printk(KERN_ERR "FritzPCI: No IRQ for PCI card found\n"); + return(0); + } + cs->hw.avm.cfg_reg = pci_resource_start(dev_avm, 1); + if (!cs->hw.avm.cfg_reg) { + printk(KERN_ERR "FritzPCI: No IO-Adr for PCI card found\n"); + return(0); + } + cs->subtyp = AVM_FRITZ_PCI; + } else { + printk(KERN_WARNING "FritzPCI: No PCI card found\n"); + return(0); + } + cs->irq_flags |= SA_SHIRQ; +#else + printk(KERN_WARNING "FritzPCI: NO_PCI_BIOS\n"); + return (0); +#endif /* CONFIG_PCI */ +ready: + cs->hw.avm.isac = cs->hw.avm.cfg_reg + 0x10; + if (!request_region(cs->hw.avm.cfg_reg, 32, + (cs->subtyp == AVM_FRITZ_PCI) ? "avm PCI" : "avm PnP")) { + printk(KERN_WARNING + "HiSax: %s config port %x-%x already in use\n", + CardType[card->typ], + cs->hw.avm.cfg_reg, + cs->hw.avm.cfg_reg + 31); + return (0); + } + switch (cs->subtyp) { + case AVM_FRITZ_PCI: + val = inl(cs->hw.avm.cfg_reg); + printk(KERN_INFO "AVM PCI: stat %#x\n", val); + printk(KERN_INFO "AVM PCI: Class %X Rev %d\n", + val & 0xff, (val>>8) & 0xff); + cs->BC_Read_Reg = &ReadHDLC_s; + cs->BC_Write_Reg = &WriteHDLC_s; + break; + case AVM_FRITZ_PNP: + val = inb(cs->hw.avm.cfg_reg); + ver = inb(cs->hw.avm.cfg_reg + 1); + printk(KERN_INFO "AVM PnP: Class %X Rev %d\n", val, ver); + cs->BC_Read_Reg = &ReadHDLCPnP; + cs->BC_Write_Reg = &WriteHDLCPnP; + break; + default: + printk(KERN_WARNING "AVM unknown subtype %d\n", cs->subtyp); + return(0); + } + printk(KERN_INFO "HiSax: %s config irq:%d base:0x%X\n", + (cs->subtyp == AVM_FRITZ_PCI) ? "AVM Fritz!PCI" : "AVM Fritz!PnP", + cs->irq, cs->hw.avm.cfg_reg); + + setup_isac(cs); + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->BC_Send_Data = &hdlc_fill_fifo; + cs->cardmsg = &AVM_card_msg; + cs->irq_func = &avm_pcipnp_interrupt; + cs->writeisac(cs, ISAC_MASK, 0xFF); + ISACVersion(cs, (cs->subtyp == AVM_FRITZ_PCI) ? "AVM PCI:" : "AVM PnP:"); + return (1); +} diff --git a/drivers/isdn/hisax/avma1_cs.c b/drivers/isdn/hisax/avma1_cs.c new file mode 100644 index 000000000000..663a0bf703b7 --- /dev/null +++ b/drivers/isdn/hisax/avma1_cs.c @@ -0,0 +1,527 @@ +/* + * PCMCIA client driver for AVM A1 / Fritz!PCMCIA + * + * Author Carsten Paeth + * Copyright 1998-2001 by Carsten Paeth <calle@calle.in-berlin.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/module.h> + + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/ptrace.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <asm/io.h> +#include <asm/system.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/ds.h> +#include "hisax_cfg.h" + +MODULE_DESCRIPTION("ISDN4Linux: PCMCIA client driver for AVM A1/Fritz!PCMCIA cards"); +MODULE_AUTHOR("Carsten Paeth"); +MODULE_LICENSE("GPL"); + +/* + All the PCMCIA modules use PCMCIA_DEBUG to control debugging. If + you do not define PCMCIA_DEBUG at all, all the debug code will be + left out. If you compile with PCMCIA_DEBUG=0, the debug code will + be present but disabled -- but it can then be enabled for specific + modules at load time with a 'pc_debug=#' option to insmod. +*/ +#ifdef PCMCIA_DEBUG +static int pc_debug = PCMCIA_DEBUG; +module_param(pc_debug, int, 0); +#define DEBUG(n, args...) if (pc_debug>(n)) printk(KERN_DEBUG args); +static char *version = +"avma1_cs.c 1.00 1998/01/23 10:00:00 (Carsten Paeth)"; +#else +#define DEBUG(n, args...) +#endif + +/*====================================================================*/ + +/* Parameters that can be set with 'insmod' */ + +static int isdnprot = 2; + +module_param(isdnprot, int, 0); + +/*====================================================================*/ + +/* + The event() function is this driver's Card Services event handler. + It will be called by Card Services when an appropriate card status + event is received. The config() and release() entry points are + used to configure or release a socket, in response to card insertion + and ejection events. They are invoked from the skeleton event + handler. +*/ + +static void avma1cs_config(dev_link_t *link); +static void avma1cs_release(dev_link_t *link); +static int avma1cs_event(event_t event, int priority, + event_callback_args_t *args); + +/* + The attach() and detach() entry points are used to create and destroy + "instances" of the driver, where each instance represents everything + needed to manage one actual PCMCIA card. +*/ + +static dev_link_t *avma1cs_attach(void); +static void avma1cs_detach(dev_link_t *); + +/* + The dev_info variable is the "key" that is used to match up this + device driver with appropriate cards, through the card configuration + database. +*/ + +static dev_info_t dev_info = "avma1_cs"; + +/* + A linked list of "instances" of the skeleton device. Each actual + PCMCIA card corresponds to one device instance, and is described + by one dev_link_t structure (defined in ds.h). + + You may not want to use a linked list for this -- for example, the + memory card driver uses an array of dev_link_t pointers, where minor + device numbers are used to derive the corresponding array index. +*/ + +static dev_link_t *dev_list = NULL; + +/* + A dev_link_t structure has fields for most things that are needed + to keep track of a socket, but there will usually be some device + specific information that also needs to be kept track of. The + 'priv' pointer in a dev_link_t structure can be used to point to + a device-specific private data structure, like this. + + A driver needs to provide a dev_node_t structure for each device + on a card. In some cases, there is only one device per card (for + example, ethernet cards, modems). In other cases, there may be + many actual or logical devices (SCSI adapters, memory cards with + multiple partitions). The dev_node_t structures need to be kept + in a linked list starting at the 'dev' field of a dev_link_t + structure. We allocate them in the card's private data structure, + because they generally can't be allocated dynamically. +*/ + +typedef struct local_info_t { + dev_node_t node; +} local_info_t; + +/*====================================================================== + + avma1cs_attach() creates an "instance" of the driver, allocating + local data structures for one device. The device is registered + with Card Services. + + The dev_link structure is initialized, but we don't actually + configure the card at this point -- we wait until we receive a + card insertion event. + +======================================================================*/ + +static dev_link_t *avma1cs_attach(void) +{ + client_reg_t client_reg; + dev_link_t *link; + local_info_t *local; + int ret; + + DEBUG(0, "avma1cs_attach()\n"); + + /* Initialize the dev_link_t structure */ + link = kmalloc(sizeof(struct dev_link_t), GFP_KERNEL); + if (!link) + return NULL; + memset(link, 0, sizeof(struct dev_link_t)); + + /* Allocate space for private device-specific data */ + local = kmalloc(sizeof(local_info_t), GFP_KERNEL); + if (!local) { + kfree(link); + return NULL; + } + memset(local, 0, sizeof(local_info_t)); + link->priv = local; + + /* The io structure describes IO port mapping */ + link->io.NumPorts1 = 16; + link->io.Attributes1 = IO_DATA_PATH_WIDTH_8; + link->io.NumPorts2 = 16; + link->io.Attributes2 = IO_DATA_PATH_WIDTH_16; + link->io.IOAddrLines = 5; + + /* Interrupt setup */ + link->irq.Attributes = IRQ_TYPE_EXCLUSIVE; + link->irq.Attributes = IRQ_TYPE_DYNAMIC_SHARING|IRQ_FIRST_SHARED; + + link->irq.IRQInfo1 = IRQ_LEVEL_ID; + + /* General socket configuration */ + link->conf.Attributes = CONF_ENABLE_IRQ; + link->conf.Vcc = 50; + link->conf.IntType = INT_MEMORY_AND_IO; + link->conf.ConfigIndex = 1; + link->conf.Present = PRESENT_OPTION; + + /* Register with Card Services */ + link->next = dev_list; + dev_list = link; + client_reg.dev_info = &dev_info; + client_reg.EventMask = + CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL | + CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET | + CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME; + client_reg.event_handler = &avma1cs_event; + client_reg.Version = 0x0210; + client_reg.event_callback_args.client_data = link; + ret = pcmcia_register_client(&link->handle, &client_reg); + if (ret != 0) { + cs_error(link->handle, RegisterClient, ret); + avma1cs_detach(link); + return NULL; + } + + return link; +} /* avma1cs_attach */ + +/*====================================================================== + + This deletes a driver "instance". The device is de-registered + with Card Services. If it has been released, all local data + structures are freed. Otherwise, the structures will be freed + when the device is released. + +======================================================================*/ + +static void avma1cs_detach(dev_link_t *link) +{ + dev_link_t **linkp; + + DEBUG(0, "avma1cs_detach(0x%p)\n", link); + + /* Locate device structure */ + for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) + if (*linkp == link) break; + if (*linkp == NULL) + return; + + /* + If the device is currently configured and active, we won't + actually delete it yet. Instead, it is marked so that when + the release() function is called, that will trigger a proper + detach(). + */ + if (link->state & DEV_CONFIG) { +#ifdef PCMCIA_DEBUG + printk(KERN_DEBUG "avma1_cs: detach postponed, '%s' " + "still locked\n", link->dev->dev_name); +#endif + link->state |= DEV_STALE_LINK; + return; + } + + /* Break the link with Card Services */ + if (link->handle) + pcmcia_deregister_client(link->handle); + + /* Unlink device structure, free pieces */ + *linkp = link->next; + if (link->priv) { + kfree(link->priv); + } + kfree(link); + +} /* avma1cs_detach */ + +/*====================================================================== + + avma1cs_config() is scheduled to run after a CARD_INSERTION event + is received, to configure the PCMCIA socket, and to make the + ethernet device available to the system. + +======================================================================*/ + +static int get_tuple(client_handle_t handle, tuple_t *tuple, + cisparse_t *parse) +{ + int i = pcmcia_get_tuple_data(handle, tuple); + if (i != CS_SUCCESS) return i; + return pcmcia_parse_tuple(handle, tuple, parse); +} + +static int first_tuple(client_handle_t handle, tuple_t *tuple, + cisparse_t *parse) +{ + int i = pcmcia_get_first_tuple(handle, tuple); + if (i != CS_SUCCESS) return i; + return get_tuple(handle, tuple, parse); +} + +static int next_tuple(client_handle_t handle, tuple_t *tuple, + cisparse_t *parse) +{ + int i = pcmcia_get_next_tuple(handle, tuple); + if (i != CS_SUCCESS) return i; + return get_tuple(handle, tuple, parse); +} + +static void avma1cs_config(dev_link_t *link) +{ + client_handle_t handle; + tuple_t tuple; + cisparse_t parse; + cistpl_cftable_entry_t *cf = &parse.cftable_entry; + local_info_t *dev; + int i; + u_char buf[64]; + char devname[128]; + IsdnCard_t icard; + int busy = 0; + + handle = link->handle; + dev = link->priv; + + DEBUG(0, "avma1cs_config(0x%p)\n", link); + + /* + This reads the card's CONFIG tuple to find its configuration + registers. + */ + do { + tuple.DesiredTuple = CISTPL_CONFIG; + i = pcmcia_get_first_tuple(handle, &tuple); + if (i != CS_SUCCESS) break; + tuple.TupleData = buf; + tuple.TupleDataMax = 64; + tuple.TupleOffset = 0; + i = pcmcia_get_tuple_data(handle, &tuple); + if (i != CS_SUCCESS) break; + i = pcmcia_parse_tuple(handle, &tuple, &parse); + if (i != CS_SUCCESS) break; + link->conf.ConfigBase = parse.config.base; + } while (0); + if (i != CS_SUCCESS) { + cs_error(link->handle, ParseTuple, i); + link->state &= ~DEV_CONFIG_PENDING; + return; + } + + /* Configure card */ + link->state |= DEV_CONFIG; + + do { + + tuple.Attributes = 0; + tuple.TupleData = buf; + tuple.TupleDataMax = 254; + tuple.TupleOffset = 0; + tuple.DesiredTuple = CISTPL_VERS_1; + + devname[0] = 0; + if( !first_tuple(handle, &tuple, &parse) && parse.version_1.ns > 1 ) { + strlcpy(devname,parse.version_1.str + parse.version_1.ofs[1], + sizeof(devname)); + } + /* + * find IO port + */ + tuple.TupleData = (cisdata_t *)buf; + tuple.TupleOffset = 0; tuple.TupleDataMax = 255; + tuple.Attributes = 0; + tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY; + i = first_tuple(handle, &tuple, &parse); + while (i == CS_SUCCESS) { + if (cf->io.nwin > 0) { + link->conf.ConfigIndex = cf->index; + link->io.BasePort1 = cf->io.win[0].base; + link->io.NumPorts1 = cf->io.win[0].len; + link->io.NumPorts2 = 0; + printk(KERN_INFO "avma1_cs: testing i/o %#x-%#x\n", + link->io.BasePort1, + link->io.BasePort1+link->io.NumPorts1 - 1); + i = pcmcia_request_io(link->handle, &link->io); + if (i == CS_SUCCESS) goto found_port; + } + i = next_tuple(handle, &tuple, &parse); + } + +found_port: + if (i != CS_SUCCESS) { + cs_error(link->handle, RequestIO, i); + break; + } + + /* + * allocate an interrupt line + */ + i = pcmcia_request_irq(link->handle, &link->irq); + if (i != CS_SUCCESS) { + cs_error(link->handle, RequestIRQ, i); + pcmcia_release_io(link->handle, &link->io); + break; + } + + /* + * configure the PCMCIA socket + */ + i = pcmcia_request_configuration(link->handle, &link->conf); + if (i != CS_SUCCESS) { + cs_error(link->handle, RequestConfiguration, i); + pcmcia_release_io(link->handle, &link->io); + pcmcia_release_irq(link->handle, &link->irq); + break; + } + + } while (0); + + /* At this point, the dev_node_t structure(s) should be + initialized and arranged in a linked list at link->dev. */ + + strcpy(dev->node.dev_name, "A1"); + dev->node.major = 45; + dev->node.minor = 0; + link->dev = &dev->node; + + link->state &= ~DEV_CONFIG_PENDING; + /* If any step failed, release any partially configured state */ + if (i != 0) { + avma1cs_release(link); + return; + } + + printk(KERN_NOTICE "avma1_cs: checking at i/o %#x, irq %d\n", + link->io.BasePort1, link->irq.AssignedIRQ); + + icard.para[0] = link->irq.AssignedIRQ; + icard.para[1] = link->io.BasePort1; + icard.protocol = isdnprot; + icard.typ = ISDN_CTYPE_A1_PCMCIA; + + i = hisax_init_pcmcia(link, &busy, &icard); + if (i < 0) { + printk(KERN_ERR "avma1_cs: failed to initialize AVM A1 PCMCIA %d at i/o %#x\n", i, link->io.BasePort1); + avma1cs_release(link); + return; + } + dev->node.minor = i; + +} /* avma1cs_config */ + +/*====================================================================== + + After a card is removed, avma1cs_release() will unregister the net + device, and release the PCMCIA configuration. If the device is + still open, this will be postponed until it is closed. + +======================================================================*/ + +static void avma1cs_release(dev_link_t *link) +{ + local_info_t *local = link->priv; + + DEBUG(0, "avma1cs_release(0x%p)\n", link); + + /* no unregister function with hisax */ + HiSax_closecard(local->node.minor); + + /* Unlink the device chain */ + link->dev = NULL; + + /* Don't bother checking to see if these succeed or not */ + pcmcia_release_configuration(link->handle); + pcmcia_release_io(link->handle, &link->io); + pcmcia_release_irq(link->handle, &link->irq); + link->state &= ~DEV_CONFIG; + + if (link->state & DEV_STALE_LINK) + avma1cs_detach(link); +} /* avma1cs_release */ + +/*====================================================================== + + The card status event handler. Mostly, this schedules other + stuff to run after an event is received. A CARD_REMOVAL event + also sets some flags to discourage the net drivers from trying + to talk to the card any more. + + When a CARD_REMOVAL event is received, we immediately set a flag + to block future accesses to this device. All the functions that + actually access the device should check this flag to make sure + the card is still present. + +======================================================================*/ + +static int avma1cs_event(event_t event, int priority, + event_callback_args_t *args) +{ + dev_link_t *link = args->client_data; + + DEBUG(1, "avma1cs_event(0x%06x)\n", event); + + switch (event) { + case CS_EVENT_CARD_REMOVAL: + if (link->state & DEV_CONFIG) + avma1cs_release(link); + break; + case CS_EVENT_CARD_INSERTION: + link->state |= DEV_PRESENT | DEV_CONFIG_PENDING; + avma1cs_config(link); + break; + case CS_EVENT_PM_SUSPEND: + link->state |= DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_RESET_PHYSICAL: + if (link->state & DEV_CONFIG) + pcmcia_release_configuration(link->handle); + break; + case CS_EVENT_PM_RESUME: + link->state &= ~DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_CARD_RESET: + if (link->state & DEV_CONFIG) + pcmcia_request_configuration(link->handle, &link->conf); + break; + } + return 0; +} /* avma1cs_event */ + +static struct pcmcia_driver avma1cs_driver = { + .owner = THIS_MODULE, + .drv = { + .name = "avma1_cs", + }, + .attach = avma1cs_attach, + .detach = avma1cs_detach, +}; + +/*====================================================================*/ + +static int __init init_avma1_cs(void) +{ + return(pcmcia_register_driver(&avma1cs_driver)); +} + +static void __exit exit_avma1_cs(void) +{ + pcmcia_unregister_driver(&avma1cs_driver); + BUG_ON(dev_list != NULL); +} + +module_init(init_avma1_cs); +module_exit(exit_avma1_cs); diff --git a/drivers/isdn/hisax/bkm_a4t.c b/drivers/isdn/hisax/bkm_a4t.c new file mode 100644 index 000000000000..f410f628a3e2 --- /dev/null +++ b/drivers/isdn/hisax/bkm_a4t.c @@ -0,0 +1,344 @@ +/* $Id: bkm_a4t.c,v 1.22.2.4 2004/01/14 16:04:48 keil Exp $ + * + * low level stuff for T-Berkom A4T + * + * Author Roland Klabunde + * Copyright by Roland Klabunde <R.Klabunde@Berkom.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + + +#include <linux/config.h> +#include <linux/init.h> +#include "hisax.h" +#include "isac.h" +#include "hscx.h" +#include "jade.h" +#include "isdnl1.h" +#include <linux/pci.h> +#include "bkm_ax.h" + +extern const char *CardType[]; + +const char *bkm_a4t_revision = "$Revision: 1.22.2.4 $"; + + +static inline u_char +readreg(unsigned int ale, unsigned long adr, u_char off) +{ + register u_int ret; + unsigned int *po = (unsigned int *) adr; /* Postoffice */ + + *po = (GCS_2 | PO_WRITE | off); + __WAITI20__(po); + *po = (ale | PO_READ); + __WAITI20__(po); + ret = *po; + return ((unsigned char) ret); +} + + +static inline void +readfifo(unsigned int ale, unsigned long adr, u_char off, u_char * data, int size) +{ + int i; + for (i = 0; i < size; i++) + *data++ = readreg(ale, adr, off); +} + + +static inline void +writereg(unsigned int ale, unsigned long adr, u_char off, u_char data) +{ + unsigned int *po = (unsigned int *) adr; /* Postoffice */ + *po = (GCS_2 | PO_WRITE | off); + __WAITI20__(po); + *po = (ale | PO_WRITE | data); + __WAITI20__(po); +} + + +static inline void +writefifo(unsigned int ale, unsigned long adr, u_char off, u_char * data, int size) +{ + int i; + + for (i = 0; i < size; i++) + writereg(ale, adr, off, *data++); +} + + +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + return (readreg(cs->hw.ax.isac_ale, cs->hw.ax.isac_adr, offset)); +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writereg(cs->hw.ax.isac_ale, cs->hw.ax.isac_adr, offset, value); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + readfifo(cs->hw.ax.isac_ale, cs->hw.ax.isac_adr, 0, data, size); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + writefifo(cs->hw.ax.isac_ale, cs->hw.ax.isac_adr, 0, data, size); +} + +static u_char +ReadJADE(struct IsdnCardState *cs, int jade, u_char offset) +{ + return (readreg(cs->hw.ax.jade_ale, cs->hw.ax.jade_adr, offset + (jade == -1 ? 0 : (jade ? 0xC0 : 0x80)))); +} + +static void +WriteJADE(struct IsdnCardState *cs, int jade, u_char offset, u_char value) +{ + writereg(cs->hw.ax.jade_ale, cs->hw.ax.jade_adr, offset + (jade == -1 ? 0 : (jade ? 0xC0 : 0x80)), value); +} + +/* + * fast interrupt JADE stuff goes here + */ + +#define READJADE(cs, nr, reg) readreg(cs->hw.ax.jade_ale,\ + cs->hw.ax.jade_adr, reg + (nr == -1 ? 0 : (nr ? 0xC0 : 0x80))) +#define WRITEJADE(cs, nr, reg, data) writereg(cs->hw.ax.jade_ale,\ + cs->hw.ax.jade_adr, reg + (nr == -1 ? 0 : (nr ? 0xC0 : 0x80)), data) + +#define READJADEFIFO(cs, nr, ptr, cnt) readfifo(cs->hw.ax.jade_ale,\ + cs->hw.ax.jade_adr, (nr == -1 ? 0 : (nr ? 0xC0 : 0x80)), ptr, cnt) +#define WRITEJADEFIFO(cs, nr, ptr, cnt) writefifo( cs->hw.ax.jade_ale,\ + cs->hw.ax.jade_adr, (nr == -1 ? 0 : (nr ? 0xC0 : 0x80)), ptr, cnt) + +#include "jade_irq.c" + +static irqreturn_t +bkm_interrupt(int intno, void *dev_id, struct pt_regs *regs) +{ + struct IsdnCardState *cs = dev_id; + u_char val = 0; + u_long flags; + I20_REGISTER_FILE *pI20_Regs; + + spin_lock_irqsave(&cs->lock, flags); + pI20_Regs = (I20_REGISTER_FILE *) (cs->hw.ax.base); + + /* ISDN interrupt pending? */ + if (pI20_Regs->i20IntStatus & intISDN) { + /* Reset the ISDN interrupt */ + pI20_Regs->i20IntStatus = intISDN; + /* Disable ISDN interrupt */ + pI20_Regs->i20IntCtrl &= ~intISDN; + /* Channel A first */ + val = readreg(cs->hw.ax.jade_ale, cs->hw.ax.jade_adr, jade_HDLC_ISR + 0x80); + if (val) { + jade_int_main(cs, val, 0); + } + /* Channel B */ + val = readreg(cs->hw.ax.jade_ale, cs->hw.ax.jade_adr, jade_HDLC_ISR + 0xC0); + if (val) { + jade_int_main(cs, val, 1); + } + /* D-Channel */ + val = readreg(cs->hw.ax.isac_ale, cs->hw.ax.isac_adr, ISAC_ISTA); + if (val) { + isac_interrupt(cs, val); + } + /* Reenable ISDN interrupt */ + pI20_Regs->i20IntCtrl |= intISDN; + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; + } else { + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_NONE; + } +} + +void +release_io_bkm(struct IsdnCardState *cs) +{ + if (cs->hw.ax.base) { + iounmap((void *) cs->hw.ax.base); + cs->hw.ax.base = 0; + } +} + +static void +enable_bkm_int(struct IsdnCardState *cs, unsigned bEnable) +{ + if (cs->typ == ISDN_CTYPE_BKM_A4T) { + I20_REGISTER_FILE *pI20_Regs = (I20_REGISTER_FILE *) (cs->hw.ax.base); + if (bEnable) + pI20_Regs->i20IntCtrl |= (intISDN | intPCI); + else + /* CAUTION: This disables the video capture driver too */ + pI20_Regs->i20IntCtrl &= ~(intISDN | intPCI); + } +} + +static void +reset_bkm(struct IsdnCardState *cs) +{ + if (cs->typ == ISDN_CTYPE_BKM_A4T) { + I20_REGISTER_FILE *pI20_Regs = (I20_REGISTER_FILE *) (cs->hw.ax.base); + /* Issue the I20 soft reset */ + pI20_Regs->i20SysControl = 0xFF; /* all in */ + mdelay(10); + /* Remove the soft reset */ + pI20_Regs->i20SysControl = sysRESET | 0xFF; + mdelay(10); + /* Set our configuration */ + pI20_Regs->i20SysControl = sysRESET | sysCFG; + /* Issue ISDN reset */ + pI20_Regs->i20GuestControl = guestWAIT_CFG | + g_A4T_JADE_RES | + g_A4T_ISAR_RES | + g_A4T_ISAC_RES | + g_A4T_JADE_BOOTR | + g_A4T_ISAR_BOOTR; + mdelay(10); + + /* Remove RESET state from ISDN */ + pI20_Regs->i20GuestControl &= ~(g_A4T_ISAC_RES | + g_A4T_JADE_RES | + g_A4T_ISAR_RES); + mdelay(10); + } +} + +static int +BKM_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + switch (mt) { + case CARD_RESET: + /* Disable ints */ + spin_lock_irqsave(&cs->lock, flags); + enable_bkm_int(cs, 0); + reset_bkm(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_RELEASE: + /* Sanity */ + spin_lock_irqsave(&cs->lock, flags); + enable_bkm_int(cs, 0); + reset_bkm(cs); + spin_unlock_irqrestore(&cs->lock, flags); + release_io_bkm(cs); + return (0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + clear_pending_isac_ints(cs); + clear_pending_jade_ints(cs); + initisac(cs); + initjade(cs); + /* Enable ints */ + enable_bkm_int(cs, 1); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_TEST: + return (0); + } + return (0); +} + +static struct pci_dev *dev_a4t __initdata = NULL; + +int __init +setup_bkm_a4t(struct IsdnCard *card) +{ + struct IsdnCardState *cs = card->cs; + char tmp[64]; + u_int pci_memaddr = 0, found = 0; + I20_REGISTER_FILE *pI20_Regs; +#ifdef CONFIG_PCI +#endif + + strcpy(tmp, bkm_a4t_revision); + printk(KERN_INFO "HiSax: T-Berkom driver Rev. %s\n", HiSax_getrev(tmp)); + if (cs->typ == ISDN_CTYPE_BKM_A4T) { + cs->subtyp = BKM_A4T; + } else + return (0); + +#ifdef CONFIG_PCI + while ((dev_a4t = pci_find_device(PCI_VENDOR_ID_ZORAN, + PCI_DEVICE_ID_ZORAN_36120, dev_a4t))) { + u16 sub_sys; + u16 sub_vendor; + + sub_vendor = dev_a4t->subsystem_vendor; + sub_sys = dev_a4t->subsystem_device; + if ((sub_sys == PCI_DEVICE_ID_BERKOM_A4T) && (sub_vendor == PCI_VENDOR_ID_BERKOM)) { + if (pci_enable_device(dev_a4t)) + return(0); + found = 1; + pci_memaddr = pci_resource_start(dev_a4t, 0); + cs->irq = dev_a4t->irq; + break; + } + } + if (!found) { + printk(KERN_WARNING "HiSax: %s: Card not found\n", CardType[card->typ]); + return (0); + } + if (!cs->irq) { /* IRQ range check ?? */ + printk(KERN_WARNING "HiSax: %s: No IRQ\n", CardType[card->typ]); + return (0); + } + if (!pci_memaddr) { + printk(KERN_WARNING "HiSax: %s: No Memory base address\n", CardType[card->typ]); + return (0); + } + cs->hw.ax.base = (long) ioremap(pci_memaddr, 4096); + /* Check suspecious address */ + pI20_Regs = (I20_REGISTER_FILE *) (cs->hw.ax.base); + if ((pI20_Regs->i20IntStatus & 0x8EFFFFFF) != 0) { + printk(KERN_WARNING "HiSax: %s address %lx-%lx suspecious\n", + CardType[card->typ], cs->hw.ax.base, cs->hw.ax.base + 4096); + iounmap((void *) cs->hw.ax.base); + cs->hw.ax.base = 0; + return (0); + } + cs->hw.ax.isac_adr = cs->hw.ax.base + PO_OFFSET; + cs->hw.ax.jade_adr = cs->hw.ax.base + PO_OFFSET; + cs->hw.ax.isac_ale = GCS_1; + cs->hw.ax.jade_ale = GCS_3; +#else + printk(KERN_WARNING "HiSax: %s: NO_PCI_BIOS\n", CardType[card->typ]); + printk(KERN_WARNING "HiSax: %s: unable to configure\n", CardType[card->typ]); + return (0); +#endif /* CONFIG_PCI */ + printk(KERN_INFO "HiSax: %s: Card configured at 0x%lX IRQ %d\n", + CardType[card->typ], cs->hw.ax.base, cs->irq); + + setup_isac(cs); + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->BC_Read_Reg = &ReadJADE; + cs->BC_Write_Reg = &WriteJADE; + cs->BC_Send_Data = &jade_fill_fifo; + cs->cardmsg = &BKM_card_msg; + cs->irq_func = &bkm_interrupt; + cs->irq_flags |= SA_SHIRQ; + ISACVersion(cs, "Telekom A4T:"); + /* Jade version */ + JadeVersion(cs, "Telekom A4T:"); + return (1); +} diff --git a/drivers/isdn/hisax/bkm_a8.c b/drivers/isdn/hisax/bkm_a8.c new file mode 100644 index 000000000000..94bb83ce7fd8 --- /dev/null +++ b/drivers/isdn/hisax/bkm_a8.c @@ -0,0 +1,451 @@ +/* $Id: bkm_a8.c,v 1.22.2.4 2004/01/15 14:02:34 keil Exp $ + * + * low level stuff for Scitel Quadro (4*S0, passive) + * + * Author Roland Klabunde + * Copyright by Roland Klabunde <R.Klabunde@Berkom.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + + +#include <linux/config.h> +#include <linux/init.h> +#include "hisax.h" +#include "isac.h" +#include "ipac.h" +#include "hscx.h" +#include "isdnl1.h" +#include <linux/pci.h> +#include "bkm_ax.h" + +#ifdef CONFIG_PCI + +#define ATTEMPT_PCI_REMAPPING /* Required for PLX rev 1 */ + +extern const char *CardType[]; + +const char sct_quadro_revision[] = "$Revision: 1.22.2.4 $"; + +static const char *sct_quadro_subtypes[] = +{ + "", + "#1", + "#2", + "#3", + "#4" +}; + + +#define wordout(addr,val) outw(val,addr) +#define wordin(addr) inw(addr) + +static inline u_char +readreg(unsigned int ale, unsigned int adr, u_char off) +{ + register u_char ret; + wordout(ale, off); + ret = wordin(adr) & 0xFF; + return (ret); +} + +static inline void +readfifo(unsigned int ale, unsigned int adr, u_char off, u_char * data, int size) +{ + int i; + wordout(ale, off); + for (i = 0; i < size; i++) + data[i] = wordin(adr) & 0xFF; +} + + +static inline void +writereg(unsigned int ale, unsigned int adr, u_char off, u_char data) +{ + wordout(ale, off); + wordout(adr, data); +} + +static inline void +writefifo(unsigned int ale, unsigned int adr, u_char off, u_char * data, int size) +{ + int i; + wordout(ale, off); + for (i = 0; i < size; i++) + wordout(adr, data[i]); +} + +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + return (readreg(cs->hw.ax.base, cs->hw.ax.data_adr, offset | 0x80)); +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writereg(cs->hw.ax.base, cs->hw.ax.data_adr, offset | 0x80, value); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + readfifo(cs->hw.ax.base, cs->hw.ax.data_adr, 0x80, data, size); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + writefifo(cs->hw.ax.base, cs->hw.ax.data_adr, 0x80, data, size); +} + + +static u_char +ReadHSCX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + return (readreg(cs->hw.ax.base, cs->hw.ax.data_adr, offset + (hscx ? 0x40 : 0))); +} + +static void +WriteHSCX(struct IsdnCardState *cs, int hscx, u_char offset, u_char value) +{ + writereg(cs->hw.ax.base, cs->hw.ax.data_adr, offset + (hscx ? 0x40 : 0), value); +} + +/* Set the specific ipac to active */ +static void +set_ipac_active(struct IsdnCardState *cs, u_int active) +{ + /* set irq mask */ + writereg(cs->hw.ax.base, cs->hw.ax.data_adr, IPAC_MASK, + active ? 0xc0 : 0xff); +} + +/* + * fast interrupt HSCX stuff goes here + */ + +#define READHSCX(cs, nr, reg) readreg(cs->hw.ax.base, \ + cs->hw.ax.data_adr, reg + (nr ? 0x40 : 0)) +#define WRITEHSCX(cs, nr, reg, data) writereg(cs->hw.ax.base, \ + cs->hw.ax.data_adr, reg + (nr ? 0x40 : 0), data) +#define READHSCXFIFO(cs, nr, ptr, cnt) readfifo(cs->hw.ax.base, \ + cs->hw.ax.data_adr, (nr ? 0x40 : 0), ptr, cnt) +#define WRITEHSCXFIFO(cs, nr, ptr, cnt) writefifo(cs->hw.ax.base, \ + cs->hw.ax.data_adr, (nr ? 0x40 : 0), ptr, cnt) + +#include "hscx_irq.c" + +static irqreturn_t +bkm_interrupt_ipac(int intno, void *dev_id, struct pt_regs *regs) +{ + struct IsdnCardState *cs = dev_id; + u_char ista, val, icnt = 5; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + ista = readreg(cs->hw.ax.base, cs->hw.ax.data_adr, IPAC_ISTA); + if (!(ista & 0x3f)) { /* not this IPAC */ + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_NONE; + } + Start_IPAC: + if (cs->debug & L1_DEB_IPAC) + debugl1(cs, "IPAC ISTA %02X", ista); + if (ista & 0x0f) { + val = readreg(cs->hw.ax.base, cs->hw.ax.data_adr, HSCX_ISTA + 0x40); + if (ista & 0x01) + val |= 0x01; + if (ista & 0x04) + val |= 0x02; + if (ista & 0x08) + val |= 0x04; + if (val) { + hscx_int_main(cs, val); + } + } + if (ista & 0x20) { + val = 0xfe & readreg(cs->hw.ax.base, cs->hw.ax.data_adr, ISAC_ISTA | 0x80); + if (val) { + isac_interrupt(cs, val); + } + } + if (ista & 0x10) { + val = 0x01; + isac_interrupt(cs, val); + } + ista = readreg(cs->hw.ax.base, cs->hw.ax.data_adr, IPAC_ISTA); + if ((ista & 0x3f) && icnt) { + icnt--; + goto Start_IPAC; + } + if (!icnt) + printk(KERN_WARNING "HiSax: %s (%s) IRQ LOOP\n", + CardType[cs->typ], + sct_quadro_subtypes[cs->subtyp]); + writereg(cs->hw.ax.base, cs->hw.ax.data_adr, IPAC_MASK, 0xFF); + writereg(cs->hw.ax.base, cs->hw.ax.data_adr, IPAC_MASK, 0xC0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +void +release_io_sct_quadro(struct IsdnCardState *cs) +{ + release_region(cs->hw.ax.base & 0xffffffc0, 128); + if (cs->subtyp == SCT_1) + release_region(cs->hw.ax.plx_adr, 64); +} + +static void +enable_bkm_int(struct IsdnCardState *cs, unsigned bEnable) +{ + if (cs->typ == ISDN_CTYPE_SCT_QUADRO) { + if (bEnable) + wordout(cs->hw.ax.plx_adr + 0x4C, (wordin(cs->hw.ax.plx_adr + 0x4C) | 0x41)); + else + wordout(cs->hw.ax.plx_adr + 0x4C, (wordin(cs->hw.ax.plx_adr + 0x4C) & ~0x41)); + } +} + +static void +reset_bkm(struct IsdnCardState *cs) +{ + if (cs->subtyp == SCT_1) { + wordout(cs->hw.ax.plx_adr + 0x50, (wordin(cs->hw.ax.plx_adr + 0x50) & ~4)); + mdelay(10); + /* Remove the soft reset */ + wordout(cs->hw.ax.plx_adr + 0x50, (wordin(cs->hw.ax.plx_adr + 0x50) | 4)); + mdelay(10); + } +} + +static int +BKM_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + /* Disable ints */ + set_ipac_active(cs, 0); + enable_bkm_int(cs, 0); + reset_bkm(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_RELEASE: + /* Sanity */ + spin_lock_irqsave(&cs->lock, flags); + set_ipac_active(cs, 0); + enable_bkm_int(cs, 0); + spin_unlock_irqrestore(&cs->lock, flags); + release_io_sct_quadro(cs); + return (0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + cs->debug |= L1_DEB_IPAC; + set_ipac_active(cs, 1); + inithscxisac(cs, 3); + /* Enable ints */ + enable_bkm_int(cs, 1); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_TEST: + return (0); + } + return (0); +} + +int __init +sct_alloc_io(u_int adr, u_int len) +{ + if (!request_region(adr, len, "scitel")) { + printk(KERN_WARNING + "HiSax: Scitel port %#x-%#x already in use\n", + adr, adr + len); + return (1); + } + return(0); +} + +static struct pci_dev *dev_a8 __initdata = NULL; +static u16 sub_vendor_id __initdata = 0; +static u16 sub_sys_id __initdata = 0; +static u_char pci_bus __initdata = 0; +static u_char pci_device_fn __initdata = 0; +static u_char pci_irq __initdata = 0; + +#endif /* CONFIG_PCI */ + +int __init +setup_sct_quadro(struct IsdnCard *card) +{ +#ifdef CONFIG_PCI + struct IsdnCardState *cs = card->cs; + char tmp[64]; + u_char pci_rev_id; + u_int found = 0; + u_int pci_ioaddr1, pci_ioaddr2, pci_ioaddr3, pci_ioaddr4, pci_ioaddr5; + + strcpy(tmp, sct_quadro_revision); + printk(KERN_INFO "HiSax: T-Berkom driver Rev. %s\n", HiSax_getrev(tmp)); + if (cs->typ == ISDN_CTYPE_SCT_QUADRO) { + cs->subtyp = SCT_1; /* Preset */ + } else + return (0); + + /* Identify subtype by para[0] */ + if (card->para[0] >= SCT_1 && card->para[0] <= SCT_4) + cs->subtyp = card->para[0]; + else { + printk(KERN_WARNING "HiSax: %s: Invalid subcontroller in configuration, default to 1\n", + CardType[card->typ]); + return (0); + } + if ((cs->subtyp != SCT_1) && ((sub_sys_id != PCI_DEVICE_ID_BERKOM_SCITEL_QUADRO) || + (sub_vendor_id != PCI_VENDOR_ID_BERKOM))) + return (0); + if (cs->subtyp == SCT_1) { + while ((dev_a8 = pci_find_device(PCI_VENDOR_ID_PLX, + PCI_DEVICE_ID_PLX_9050, dev_a8))) { + + sub_vendor_id = dev_a8->subsystem_vendor; + sub_sys_id = dev_a8->subsystem_device; + if ((sub_sys_id == PCI_DEVICE_ID_BERKOM_SCITEL_QUADRO) && + (sub_vendor_id == PCI_VENDOR_ID_BERKOM)) { + if (pci_enable_device(dev_a8)) + return(0); + pci_ioaddr1 = pci_resource_start(dev_a8, 1); + pci_irq = dev_a8->irq; + pci_bus = dev_a8->bus->number; + pci_device_fn = dev_a8->devfn; + found = 1; + break; + } + } + if (!found) { + printk(KERN_WARNING "HiSax: %s (%s): Card not found\n", + CardType[card->typ], + sct_quadro_subtypes[cs->subtyp]); + return (0); + } +#ifdef ATTEMPT_PCI_REMAPPING +/* HACK: PLX revision 1 bug: PLX address bit 7 must not be set */ + pci_read_config_byte(dev_a8, PCI_REVISION_ID, &pci_rev_id); + if ((pci_ioaddr1 & 0x80) && (pci_rev_id == 1)) { + printk(KERN_WARNING "HiSax: %s (%s): PLX rev 1, remapping required!\n", + CardType[card->typ], + sct_quadro_subtypes[cs->subtyp]); + /* Restart PCI negotiation */ + pci_write_config_dword(dev_a8, PCI_BASE_ADDRESS_1, (u_int) - 1); + /* Move up by 0x80 byte */ + pci_ioaddr1 += 0x80; + pci_ioaddr1 &= PCI_BASE_ADDRESS_IO_MASK; + pci_write_config_dword(dev_a8, PCI_BASE_ADDRESS_1, pci_ioaddr1); + dev_a8->resource[ 1].start = pci_ioaddr1; + } +#endif /* End HACK */ + } + if (!pci_irq) { /* IRQ range check ?? */ + printk(KERN_WARNING "HiSax: %s (%s): No IRQ\n", + CardType[card->typ], + sct_quadro_subtypes[cs->subtyp]); + return (0); + } + pci_read_config_dword(dev_a8, PCI_BASE_ADDRESS_1, &pci_ioaddr1); + pci_read_config_dword(dev_a8, PCI_BASE_ADDRESS_2, &pci_ioaddr2); + pci_read_config_dword(dev_a8, PCI_BASE_ADDRESS_3, &pci_ioaddr3); + pci_read_config_dword(dev_a8, PCI_BASE_ADDRESS_4, &pci_ioaddr4); + pci_read_config_dword(dev_a8, PCI_BASE_ADDRESS_5, &pci_ioaddr5); + if (!pci_ioaddr1 || !pci_ioaddr2 || !pci_ioaddr3 || !pci_ioaddr4 || !pci_ioaddr5) { + printk(KERN_WARNING "HiSax: %s (%s): No IO base address(es)\n", + CardType[card->typ], + sct_quadro_subtypes[cs->subtyp]); + return (0); + } + pci_ioaddr1 &= PCI_BASE_ADDRESS_IO_MASK; + pci_ioaddr2 &= PCI_BASE_ADDRESS_IO_MASK; + pci_ioaddr3 &= PCI_BASE_ADDRESS_IO_MASK; + pci_ioaddr4 &= PCI_BASE_ADDRESS_IO_MASK; + pci_ioaddr5 &= PCI_BASE_ADDRESS_IO_MASK; + /* Take over */ + cs->irq = pci_irq; + cs->irq_flags |= SA_SHIRQ; + /* pci_ioaddr1 is unique to all subdevices */ + /* pci_ioaddr2 is for the fourth subdevice only */ + /* pci_ioaddr3 is for the third subdevice only */ + /* pci_ioaddr4 is for the second subdevice only */ + /* pci_ioaddr5 is for the first subdevice only */ + cs->hw.ax.plx_adr = pci_ioaddr1; + /* Enter all ipac_base addresses */ + switch(cs->subtyp) { + case 1: + cs->hw.ax.base = pci_ioaddr5 + 0x00; + if (sct_alloc_io(pci_ioaddr1, 128)) + return(0); + if (sct_alloc_io(pci_ioaddr5, 64)) + return(0); + /* disable all IPAC */ + writereg(pci_ioaddr5, pci_ioaddr5 + 4, + IPAC_MASK, 0xFF); + writereg(pci_ioaddr4 + 0x08, pci_ioaddr4 + 0x0c, + IPAC_MASK, 0xFF); + writereg(pci_ioaddr3 + 0x10, pci_ioaddr3 + 0x14, + IPAC_MASK, 0xFF); + writereg(pci_ioaddr2 + 0x20, pci_ioaddr2 + 0x24, + IPAC_MASK, 0xFF); + break; + case 2: + cs->hw.ax.base = pci_ioaddr4 + 0x08; + if (sct_alloc_io(pci_ioaddr4, 64)) + return(0); + break; + case 3: + cs->hw.ax.base = pci_ioaddr3 + 0x10; + if (sct_alloc_io(pci_ioaddr3, 64)) + return(0); + break; + case 4: + cs->hw.ax.base = pci_ioaddr2 + 0x20; + if (sct_alloc_io(pci_ioaddr2, 64)) + return(0); + break; + } + /* For isac and hscx data path */ + cs->hw.ax.data_adr = cs->hw.ax.base + 4; + + printk(KERN_INFO "HiSax: %s (%s) configured at 0x%.4lX, 0x%.4lX, 0x%.4lX and IRQ %d\n", + CardType[card->typ], + sct_quadro_subtypes[cs->subtyp], + cs->hw.ax.plx_adr, + cs->hw.ax.base, + cs->hw.ax.data_adr, + cs->irq); + + test_and_set_bit(HW_IPAC, &cs->HW_Flags); + + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + + cs->BC_Read_Reg = &ReadHSCX; + cs->BC_Write_Reg = &WriteHSCX; + cs->BC_Send_Data = &hscx_fill_fifo; + cs->cardmsg = &BKM_card_msg; + cs->irq_func = &bkm_interrupt_ipac; + + printk(KERN_INFO "HiSax: %s (%s): IPAC Version %d\n", + CardType[card->typ], + sct_quadro_subtypes[cs->subtyp], + readreg(cs->hw.ax.base, cs->hw.ax.data_adr, IPAC_ID)); + return (1); +#else + printk(KERN_ERR "HiSax: bkm_a8 only supported on PCI Systems\n"); +#endif /* CONFIG_PCI */ +} diff --git a/drivers/isdn/hisax/bkm_ax.h b/drivers/isdn/hisax/bkm_ax.h new file mode 100644 index 000000000000..029e0a277661 --- /dev/null +++ b/drivers/isdn/hisax/bkm_ax.h @@ -0,0 +1,119 @@ +/* $Id: bkm_ax.h,v 1.5.6.3 2001/09/23 22:24:46 kai Exp $ + * + * low level decls for T-Berkom cards A4T and Scitel Quadro (4*S0, passive) + * + * Author Roland Klabunde + * Copyright by Roland Klabunde <R.Klabunde@Berkom.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#ifndef __BKM_AX_H__ +#define __BKM_AX_H__ + +/* Supported boards (subtypes) */ +#define SCT_1 1 +#define SCT_2 2 +#define SCT_3 3 +#define SCT_4 4 +#define BKM_A4T 5 + +#define PLX_ADDR_PLX 0x14 /* Addr PLX configuration */ +#define PLX_ADDR_ISAC 0x18 /* Addr ISAC */ +#define PLX_ADDR_HSCX 0x1C /* Addr HSCX */ +#define PLX_ADDR_ALE 0x20 /* Addr ALE */ +#define PLX_ADDR_ALEPLUS 0x24 /* Next Addr behind ALE */ + +#define PLX_SUBVEN 0x2C /* Offset SubVendor */ +#define PLX_SUBSYS 0x2E /* Offset SubSystem */ + + +/* Application specific registers I20 (Siemens SZB6120H) */ +typedef struct { + /* Video front end horizontal configuration register */ + volatile u_int i20VFEHorzCfg; /* Offset 00 */ + /* Video front end vertical configuration register */ + volatile u_int i20VFEVertCfg; /* Offset 04 */ + /* Video front end scaler and pixel format register */ + volatile u_int i20VFEScaler; /* Offset 08 */ + /* Video display top register */ + volatile u_int i20VDispTop; /* Offset 0C */ + /* Video display bottom register */ + volatile u_int i20VDispBottom; /* Offset 10 */ + /* Video stride, status and frame grab register */ + volatile u_int i20VidFrameGrab;/* Offset 14 */ + /* Video display configuration register */ + volatile u_int i20VDispCfg; /* Offset 18 */ + /* Video masking map top */ + volatile u_int i20VMaskTop; /* Offset 1C */ + /* Video masking map bottom */ + volatile u_int i20VMaskBottom; /* Offset 20 */ + /* Overlay control register */ + volatile u_int i20OvlyControl; /* Offset 24 */ + /* System, PCI and general purpose pins control register */ + volatile u_int i20SysControl; /* Offset 28 */ +#define sysRESET 0x01000000 /* bit 24:Softreset (Low) */ + /* GPIO 4...0: Output fixed for our cfg! */ +#define sysCFG 0x000000E0 /* GPIO 7,6,5: Input */ + /* General purpose pins and guest bus control register */ + volatile u_int i20GuestControl;/* Offset 2C */ +#define guestWAIT_CFG 0x00005555 /* 4 PCI waits for all */ +#define guestISDN_INT_E 0x01000000 /* ISDN Int en (low) */ +#define guestVID_INT_E 0x02000000 /* Video interrupt en (low) */ +#define guestADI1_INT_R 0x04000000 /* ADI #1 int req (low) */ +#define guestADI2_INT_R 0x08000000 /* ADI #2 int req (low) */ +#define guestISDN_RES 0x10000000 /* ISDN reset bit (high) */ +#define guestADI1_INT_S 0x20000000 /* ADI #1 int pending (low) */ +#define guestADI2_INT_S 0x40000000 /* ADI #2 int pending (low) */ +#define guestISDN_INT_S 0x80000000 /* ISAC int pending (low) */ + +#define g_A4T_JADE_RES 0x01000000 /* JADE Reset (High) */ +#define g_A4T_ISAR_RES 0x02000000 /* ISAR Reset (High) */ +#define g_A4T_ISAC_RES 0x04000000 /* ISAC Reset (High) */ +#define g_A4T_JADE_BOOTR 0x08000000 /* JADE enable boot SRAM (Low) NOT USED */ +#define g_A4T_ISAR_BOOTR 0x10000000 /* ISAR enable boot SRAM (Low) NOT USED */ +#define g_A4T_JADE_INT_S 0x20000000 /* JADE interrupt pnd (Low) */ +#define g_A4T_ISAR_INT_S 0x40000000 /* ISAR interrupt pnd (Low) */ +#define g_A4T_ISAC_INT_S 0x80000000 /* ISAC interrupt pnd (Low) */ + + volatile u_int i20CodeSource; /* Offset 30 */ + volatile u_int i20CodeXferCtrl;/* Offset 34 */ + volatile u_int i20CodeMemPtr; /* Offset 38 */ + + volatile u_int i20IntStatus; /* Offset 3C */ + volatile u_int i20IntCtrl; /* Offset 40 */ +#define intISDN 0x40000000 /* GIRQ1En (ISAC/ADI) (High) */ +#define intVID 0x20000000 /* GIRQ0En (VSYNC) (High) */ +#define intCOD 0x10000000 /* CodRepIrqEn (High) */ +#define intPCI 0x01000000 /* PCI IntA enable (High) */ + + volatile u_int i20I2CCtrl; /* Offset 44 */ +} I20_REGISTER_FILE, *PI20_REGISTER_FILE; + +/* + * Postoffice structure for A4T + * + */ +#define PO_OFFSET 0x00000200 /* Postoffice offset from base */ + +#define GCS_0 0x00000000 /* Guest bus chip selects */ +#define GCS_1 0x00100000 +#define GCS_2 0x00200000 +#define GCS_3 0x00300000 + +#define PO_READ 0x00000000 /* R/W from/to guest bus */ +#define PO_WRITE 0x00800000 + +#define PO_PEND 0x02000000 + +#define POSTOFFICE(postoffice) *(volatile unsigned int*)(postoffice) + +/* Wait unlimited (don't worry) */ +#define __WAITI20__(postoffice) \ +do { \ + while ((POSTOFFICE(postoffice) & PO_PEND)) ; \ +} while (0) + +#endif /* __BKM_AX_H__ */ diff --git a/drivers/isdn/hisax/callc.c b/drivers/isdn/hisax/callc.c new file mode 100644 index 000000000000..04065ab2610f --- /dev/null +++ b/drivers/isdn/hisax/callc.c @@ -0,0 +1,1793 @@ +/* $Id: callc.c,v 2.59.2.4 2004/02/11 13:21:32 keil Exp $ + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * For changes and modifications please read + * Documentation/isdn/HiSax.cert + * + * based on the teles driver from Jan den Ouden + * + * Thanks to Jan den Ouden + * Fritz Elfert + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include "hisax.h" +#include <linux/isdn/capicmd.h> + +const char *lli_revision = "$Revision: 2.59.2.4 $"; + +extern struct IsdnCard cards[]; +extern int nrcards; + +static int init_b_st(struct Channel *chanp, int incoming); +static void release_b_st(struct Channel *chanp); + +static struct Fsm callcfsm; +static int chancount; + +/* experimental REJECT after ALERTING for CALLBACK to beat the 4s delay */ +#define ALERT_REJECT 0 + +/* Value to delay the sending of the first B-channel paket after CONNECT + * here is no value given by ITU, but experience shows that 300 ms will + * work on many networks, if you or your other side is behind local exchanges + * a greater value may be recommented. If the delay is to short the first paket + * will be lost and autodetect on many comercial routers goes wrong ! + * You can adjust this value on runtime with + * hisaxctrl <id> 2 <value> + * value is in milliseconds + */ +#define DEFAULT_B_DELAY 300 + +/* Flags for remembering action done in lli */ + +#define FLG_START_B 0 + +/* + * Find card with given driverId + */ +static inline struct IsdnCardState * +hisax_findcard(int driverid) +{ + int i; + + for (i = 0; i < nrcards; i++) + if (cards[i].cs) + if (cards[i].cs->myid == driverid) + return (cards[i].cs); + return (struct IsdnCardState *) 0; +} + +static void +link_debug(struct Channel *chanp, int direction, char *fmt, ...) +{ + va_list args; + char tmp[16]; + + va_start(args, fmt); + sprintf(tmp, "Ch%d %s ", chanp->chan, + direction ? "LL->HL" : "HL->LL"); + VHiSax_putstatus(chanp->cs, tmp, fmt, args); + va_end(args); +} + +enum { + ST_NULL, /* 0 inactive */ + ST_OUT_DIAL, /* 1 outgoing, SETUP send; awaiting confirm */ + ST_IN_WAIT_LL, /* 2 incoming call received; wait for LL confirm */ + ST_IN_ALERT_SENT, /* 3 incoming call received; ALERT send */ + ST_IN_WAIT_CONN_ACK, /* 4 incoming CONNECT send; awaiting CONN_ACK */ + ST_WAIT_BCONN, /* 5 CONNECT/CONN_ACK received, awaiting b-channel prot. estbl. */ + ST_ACTIVE, /* 6 active, b channel prot. established */ + ST_WAIT_BRELEASE, /* 7 call clear. (initiator), awaiting b channel prot. rel. */ + ST_WAIT_BREL_DISC, /* 8 call clear. (receiver), DISCONNECT req. received */ + ST_WAIT_DCOMMAND, /* 9 call clear. (receiver), awaiting DCHANNEL message */ + ST_WAIT_DRELEASE, /* 10 DISCONNECT sent, awaiting RELEASE */ + ST_WAIT_D_REL_CNF, /* 11 RELEASE sent, awaiting RELEASE confirm */ + ST_IN_PROCEED_SEND, /* 12 incoming call, proceeding send */ +}; + + +#define STATE_COUNT (ST_IN_PROCEED_SEND + 1) + +static char *strState[] = +{ + "ST_NULL", + "ST_OUT_DIAL", + "ST_IN_WAIT_LL", + "ST_IN_ALERT_SENT", + "ST_IN_WAIT_CONN_ACK", + "ST_WAIT_BCONN", + "ST_ACTIVE", + "ST_WAIT_BRELEASE", + "ST_WAIT_BREL_DISC", + "ST_WAIT_DCOMMAND", + "ST_WAIT_DRELEASE", + "ST_WAIT_D_REL_CNF", + "ST_IN_PROCEED_SEND", +}; + +enum { + EV_DIAL, /* 0 */ + EV_SETUP_CNF, /* 1 */ + EV_ACCEPTB, /* 2 */ + EV_DISCONNECT_IND, /* 3 */ + EV_RELEASE, /* 4 */ + EV_LEASED, /* 5 */ + EV_LEASED_REL, /* 6 */ + EV_SETUP_IND, /* 7 */ + EV_ACCEPTD, /* 8 */ + EV_SETUP_CMPL_IND, /* 9 */ + EV_BC_EST, /* 10 */ + EV_WRITEBUF, /* 11 */ + EV_HANGUP, /* 12 */ + EV_BC_REL, /* 13 */ + EV_CINF, /* 14 */ + EV_SUSPEND, /* 15 */ + EV_RESUME, /* 16 */ + EV_NOSETUP_RSP, /* 17 */ + EV_SETUP_ERR, /* 18 */ + EV_CONNECT_ERR, /* 19 */ + EV_PROCEED, /* 20 */ + EV_ALERT, /* 21 */ + EV_REDIR, /* 22 */ +}; + +#define EVENT_COUNT (EV_REDIR + 1) + +static char *strEvent[] = +{ + "EV_DIAL", + "EV_SETUP_CNF", + "EV_ACCEPTB", + "EV_DISCONNECT_IND", + "EV_RELEASE", + "EV_LEASED", + "EV_LEASED_REL", + "EV_SETUP_IND", + "EV_ACCEPTD", + "EV_SETUP_CMPL_IND", + "EV_BC_EST", + "EV_WRITEBUF", + "EV_HANGUP", + "EV_BC_REL", + "EV_CINF", + "EV_SUSPEND", + "EV_RESUME", + "EV_NOSETUP_RSP", + "EV_SETUP_ERR", + "EV_CONNECT_ERR", + "EV_PROCEED", + "EV_ALERT", + "EV_REDIR", +}; + + +static inline void +HL_LL(struct Channel *chanp, int command) +{ + isdn_ctrl ic; + + ic.driver = chanp->cs->myid; + ic.command = command; + ic.arg = chanp->chan; + chanp->cs->iif.statcallb(&ic); +} + +static inline void +lli_deliver_cause(struct Channel *chanp) +{ + isdn_ctrl ic; + + if (!chanp->proc) + return; + if (chanp->proc->para.cause == NO_CAUSE) + return; + ic.driver = chanp->cs->myid; + ic.command = ISDN_STAT_CAUSE; + ic.arg = chanp->chan; + if (chanp->cs->protocol == ISDN_PTYPE_EURO) + sprintf(ic.parm.num, "E%02X%02X", chanp->proc->para.loc & 0x7f, + chanp->proc->para.cause & 0x7f); + else + sprintf(ic.parm.num, "%02X%02X", chanp->proc->para.loc & 0x7f, + chanp->proc->para.cause & 0x7f); + chanp->cs->iif.statcallb(&ic); +} + +static inline void +lli_close(struct FsmInst *fi) +{ + struct Channel *chanp = fi->userdata; + + FsmChangeState(fi, ST_NULL); + chanp->Flags = 0; + chanp->cs->cardmsg(chanp->cs, MDL_INFO_REL, (void *) (long)chanp->chan); +} + +static void +lli_leased_in(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + isdn_ctrl ic; + int ret; + + if (!chanp->leased) + return; + chanp->cs->cardmsg(chanp->cs, MDL_INFO_SETUP, (void *) (long)chanp->chan); + FsmChangeState(fi, ST_IN_WAIT_LL); + if (chanp->debug & 1) + link_debug(chanp, 0, "STAT_ICALL_LEASED"); + ic.driver = chanp->cs->myid; + ic.command = ((chanp->chan < 2) ? ISDN_STAT_ICALL : ISDN_STAT_ICALLW); + ic.arg = chanp->chan; + ic.parm.setup.si1 = 7; + ic.parm.setup.si2 = 0; + ic.parm.setup.plan = 0; + ic.parm.setup.screen = 0; + sprintf(ic.parm.setup.eazmsn,"%d", chanp->chan + 1); + sprintf(ic.parm.setup.phone,"LEASED%d", chanp->cs->myid); + ret = chanp->cs->iif.statcallb(&ic); + if (chanp->debug & 1) + link_debug(chanp, 1, "statcallb ret=%d", ret); + if (!ret) { + chanp->cs->cardmsg(chanp->cs, MDL_INFO_REL, (void *) (long)chanp->chan); + FsmChangeState(fi, ST_NULL); + } +} + + +/* + * Dial out + */ +static void +lli_init_bchan_out(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + FsmChangeState(fi, ST_WAIT_BCONN); + if (chanp->debug & 1) + link_debug(chanp, 0, "STAT_DCONN"); + HL_LL(chanp, ISDN_STAT_DCONN); + init_b_st(chanp, 0); + chanp->b_st->lli.l4l3(chanp->b_st, DL_ESTABLISH | REQUEST, NULL); +} + +static void +lli_prep_dialout(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + FsmDelTimer(&chanp->drel_timer, 60); + FsmDelTimer(&chanp->dial_timer, 73); + chanp->l2_active_protocol = chanp->l2_protocol; + chanp->incoming = 0; + chanp->cs->cardmsg(chanp->cs, MDL_INFO_SETUP, (void *) (long)chanp->chan); + if (chanp->leased) { + lli_init_bchan_out(fi, event, arg); + } else { + FsmChangeState(fi, ST_OUT_DIAL); + chanp->d_st->lli.l4l3(chanp->d_st, CC_SETUP | REQUEST, chanp); + } +} + +static void +lli_resume(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + FsmDelTimer(&chanp->drel_timer, 60); + FsmDelTimer(&chanp->dial_timer, 73); + chanp->l2_active_protocol = chanp->l2_protocol; + chanp->incoming = 0; + chanp->cs->cardmsg(chanp->cs, MDL_INFO_SETUP, (void *) (long)chanp->chan); + if (chanp->leased) { + lli_init_bchan_out(fi, event, arg); + } else { + FsmChangeState(fi, ST_OUT_DIAL); + chanp->d_st->lli.l4l3(chanp->d_st, CC_RESUME | REQUEST, chanp); + } +} + +static void +lli_go_active(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->bcs->conmsg) + strcpy(ic.parm.num, chanp->bcs->conmsg); + else + ic.parm.num[0] = 0; + if (chanp->debug & 1) + link_debug(chanp, 0, "STAT_BCONN %s", ic.parm.num); + ic.driver = chanp->cs->myid; + ic.command = ISDN_STAT_BCONN; + ic.arg = chanp->chan; + chanp->cs->iif.statcallb(&ic); + chanp->cs->cardmsg(chanp->cs, MDL_INFO_CONN, (void *) (long)chanp->chan); +} + + +/* + * RESUME + */ + +/* incoming call */ + +static void +lli_deliver_call(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + isdn_ctrl ic; + int ret; + + chanp->cs->cardmsg(chanp->cs, MDL_INFO_SETUP, (void *) (long)chanp->chan); + /* + * Report incoming calls only once to linklevel, use CallFlags + * which is set to 3 with each broadcast message in isdnl1.c + * and resetted if a interface answered the STAT_ICALL. + */ + if (1) { /* for only one TEI */ + FsmChangeState(fi, ST_IN_WAIT_LL); + if (chanp->debug & 1) + link_debug(chanp, 0, (chanp->chan < 2) ? "STAT_ICALL" : "STAT_ICALLW"); + ic.driver = chanp->cs->myid; + ic.command = ((chanp->chan < 2) ? ISDN_STAT_ICALL : ISDN_STAT_ICALLW); + + ic.arg = chanp->chan; + /* + * No need to return "unknown" for calls without OAD, + * cause that's handled in linklevel now (replaced by '0') + */ + memcpy(&ic.parm.setup, &chanp->proc->para.setup, sizeof(setup_parm)); + ret = chanp->cs->iif.statcallb(&ic); + if (chanp->debug & 1) + link_debug(chanp, 1, "statcallb ret=%d", ret); + + switch (ret) { + case 1: /* OK, someone likes this call */ + FsmDelTimer(&chanp->drel_timer, 61); + FsmChangeState(fi, ST_IN_ALERT_SENT); + chanp->d_st->lli.l4l3(chanp->d_st, CC_ALERTING | REQUEST, chanp->proc); + break; + case 5: /* direct redirect */ + case 4: /* Proceeding desired */ + FsmDelTimer(&chanp->drel_timer, 61); + FsmChangeState(fi, ST_IN_PROCEED_SEND); + chanp->d_st->lli.l4l3(chanp->d_st, CC_PROCEED_SEND | REQUEST, chanp->proc); + if (ret == 5) { + memcpy(&chanp->setup, &ic.parm.setup, sizeof(setup_parm)); + chanp->d_st->lli.l4l3(chanp->d_st, CC_REDIR | REQUEST, chanp->proc); + } + break; + case 2: /* Rejecting Call */ + break; + case 3: /* incomplete number */ + FsmDelTimer(&chanp->drel_timer, 61); + chanp->d_st->lli.l4l3(chanp->d_st, CC_MORE_INFO | REQUEST, chanp->proc); + break; + case 0: /* OK, nobody likes this call */ + default: /* statcallb problems */ + chanp->d_st->lli.l4l3(chanp->d_st, CC_IGNORE | REQUEST, chanp->proc); + chanp->cs->cardmsg(chanp->cs, MDL_INFO_REL, (void *) (long)chanp->chan); + FsmChangeState(fi, ST_NULL); + break; + } + } else { + chanp->d_st->lli.l4l3(chanp->d_st, CC_IGNORE | REQUEST, chanp->proc); + chanp->cs->cardmsg(chanp->cs, MDL_INFO_REL, (void *) (long)chanp->chan); + } +} + +static void +lli_send_dconnect(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + FsmChangeState(fi, ST_IN_WAIT_CONN_ACK); + chanp->d_st->lli.l4l3(chanp->d_st, CC_SETUP | RESPONSE, chanp->proc); +} + +static void +lli_send_alert(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + FsmChangeState(fi, ST_IN_ALERT_SENT); + chanp->d_st->lli.l4l3(chanp->d_st, CC_ALERTING | REQUEST, chanp->proc); +} + +static void +lli_send_redir(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + chanp->d_st->lli.l4l3(chanp->d_st, CC_REDIR | REQUEST, chanp->proc); +} + +static void +lli_init_bchan_in(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + FsmChangeState(fi, ST_WAIT_BCONN); + if (chanp->debug & 1) + link_debug(chanp, 0, "STAT_DCONN"); + HL_LL(chanp, ISDN_STAT_DCONN); + chanp->l2_active_protocol = chanp->l2_protocol; + chanp->incoming = !0; + init_b_st(chanp, !0); + chanp->b_st->lli.l4l3(chanp->b_st, DL_ESTABLISH | REQUEST, NULL); +} + +static void +lli_setup_rsp(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + if (chanp->leased) { + lli_init_bchan_in(fi, event, arg); + } else { + FsmChangeState(fi, ST_IN_WAIT_CONN_ACK); +#ifdef WANT_ALERT + chanp->d_st->lli.l4l3(chanp->d_st, CC_ALERTING | REQUEST, chanp->proc); +#endif + chanp->d_st->lli.l4l3(chanp->d_st, CC_SETUP | RESPONSE, chanp->proc); + } +} + +/* Call suspend */ + +static void +lli_suspend(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + chanp->d_st->lli.l4l3(chanp->d_st, CC_SUSPEND | REQUEST, chanp->proc); +} + +/* Call clearing */ + +static void +lli_leased_hup(struct FsmInst *fi, struct Channel *chanp) +{ + isdn_ctrl ic; + + ic.driver = chanp->cs->myid; + ic.command = ISDN_STAT_CAUSE; + ic.arg = chanp->chan; + sprintf(ic.parm.num, "L0010"); + chanp->cs->iif.statcallb(&ic); + if (chanp->debug & 1) + link_debug(chanp, 0, "STAT_DHUP"); + HL_LL(chanp, ISDN_STAT_DHUP); + lli_close(fi); +} + +static void +lli_disconnect_req(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + if (chanp->leased) { + lli_leased_hup(fi, chanp); + } else { + FsmChangeState(fi, ST_WAIT_DRELEASE); + if (chanp->proc) + chanp->proc->para.cause = 0x10; /* Normal Call Clearing */ + chanp->d_st->lli.l4l3(chanp->d_st, CC_DISCONNECT | REQUEST, + chanp->proc); + } +} + +static void +lli_disconnect_reject(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + if (chanp->leased) { + lli_leased_hup(fi, chanp); + } else { + FsmChangeState(fi, ST_WAIT_DRELEASE); + if (chanp->proc) + chanp->proc->para.cause = 0x15; /* Call Rejected */ + chanp->d_st->lli.l4l3(chanp->d_st, CC_DISCONNECT | REQUEST, + chanp->proc); + } +} + +static void +lli_dhup_close(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + if (chanp->leased) { + lli_leased_hup(fi, chanp); + } else { + if (chanp->debug & 1) + link_debug(chanp, 0, "STAT_DHUP"); + lli_deliver_cause(chanp); + HL_LL(chanp, ISDN_STAT_DHUP); + lli_close(fi); + } +} + +static void +lli_reject_req(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + if (chanp->leased) { + lli_leased_hup(fi, chanp); + return; + } +#ifndef ALERT_REJECT + if (chanp->proc) + chanp->proc->para.cause = 0x15; /* Call Rejected */ + chanp->d_st->lli.l4l3(chanp->d_st, CC_REJECT | REQUEST, chanp->proc); + lli_dhup_close(fi, event, arg); +#else + FsmRestartTimer(&chanp->drel_timer, 40, EV_HANGUP, NULL, 63); + FsmChangeState(fi, ST_IN_ALERT_SENT); + chanp->d_st->lli.l4l3(chanp->d_st, CC_ALERTING | REQUEST, chanp->proc); +#endif +} + +static void +lli_disconn_bchan(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + chanp->data_open = 0; + FsmChangeState(fi, ST_WAIT_BRELEASE); + chanp->b_st->lli.l4l3(chanp->b_st, DL_RELEASE | REQUEST, NULL); +} + +static void +lli_start_disc(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + if (chanp->leased) { + lli_leased_hup(fi, chanp); + } else { + lli_disconnect_req(fi, event, arg); + } +} + +static void +lli_rel_b_disc(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + release_b_st(chanp); + lli_start_disc(fi, event, arg); +} + +static void +lli_bhup_disc(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + if (chanp->debug & 1) + link_debug(chanp, 0, "STAT_BHUP"); + HL_LL(chanp, ISDN_STAT_BHUP); + lli_rel_b_disc(fi, event, arg); +} + +static void +lli_bhup_rel_b(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + FsmChangeState(fi, ST_WAIT_DCOMMAND); + chanp->data_open = 0; + if (chanp->debug & 1) + link_debug(chanp, 0, "STAT_BHUP"); + HL_LL(chanp, ISDN_STAT_BHUP); + release_b_st(chanp); +} + +static void +lli_release_bchan(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + chanp->data_open = 0; + FsmChangeState(fi, ST_WAIT_BREL_DISC); + chanp->b_st->lli.l4l3(chanp->b_st, DL_RELEASE | REQUEST, NULL); +} + + +static void +lli_rel_b_dhup(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + release_b_st(chanp); + lli_dhup_close(fi, event, arg); +} + +static void +lli_bhup_dhup(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + if (chanp->debug & 1) + link_debug(chanp, 0, "STAT_BHUP"); + HL_LL(chanp, ISDN_STAT_BHUP); + lli_rel_b_dhup(fi, event, arg); +} + +static void +lli_abort(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + chanp->data_open = 0; + chanp->b_st->lli.l4l3(chanp->b_st, DL_RELEASE | REQUEST, NULL); + lli_bhup_dhup(fi, event, arg); +} + +static void +lli_release_req(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + if (chanp->leased) { + lli_leased_hup(fi, chanp); + } else { + FsmChangeState(fi, ST_WAIT_D_REL_CNF); + chanp->d_st->lli.l4l3(chanp->d_st, CC_RELEASE | REQUEST, + chanp->proc); + } +} + +static void +lli_rel_b_release_req(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + release_b_st(chanp); + lli_release_req(fi, event, arg); +} + +static void +lli_bhup_release_req(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + if (chanp->debug & 1) + link_debug(chanp, 0, "STAT_BHUP"); + HL_LL(chanp, ISDN_STAT_BHUP); + lli_rel_b_release_req(fi, event, arg); +} + + +/* processing charge info */ +static void +lli_charge_info(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + isdn_ctrl ic; + + ic.driver = chanp->cs->myid; + ic.command = ISDN_STAT_CINF; + ic.arg = chanp->chan; + sprintf(ic.parm.num, "%d", chanp->proc->para.chargeinfo); + chanp->cs->iif.statcallb(&ic); +} + +/* error procedures */ + +static void +lli_dchan_not_ready(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + if (chanp->debug & 1) + link_debug(chanp, 0, "STAT_DHUP"); + HL_LL(chanp, ISDN_STAT_DHUP); +} + +static void +lli_no_setup_rsp(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + if (chanp->debug & 1) + link_debug(chanp, 0, "STAT_DHUP"); + HL_LL(chanp, ISDN_STAT_DHUP); + lli_close(fi); +} + +static void +lli_error(struct FsmInst *fi, int event, void *arg) +{ + FsmChangeState(fi, ST_WAIT_DRELEASE); +} + +static void +lli_failure_l(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + isdn_ctrl ic; + + FsmChangeState(fi, ST_NULL); + ic.driver = chanp->cs->myid; + ic.command = ISDN_STAT_CAUSE; + ic.arg = chanp->chan; + sprintf(ic.parm.num, "L%02X%02X", 0, 0x2f); + chanp->cs->iif.statcallb(&ic); + HL_LL(chanp, ISDN_STAT_DHUP); + chanp->Flags = 0; + chanp->cs->cardmsg(chanp->cs, MDL_INFO_REL, (void *) (long)chanp->chan); +} + +static void +lli_rel_b_fail(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + release_b_st(chanp); + lli_failure_l(fi, event, arg); +} + +static void +lli_bhup_fail(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + if (chanp->debug & 1) + link_debug(chanp, 0, "STAT_BHUP"); + HL_LL(chanp, ISDN_STAT_BHUP); + lli_rel_b_fail(fi, event, arg); +} + +static void +lli_failure_a(struct FsmInst *fi, int event, void *arg) +{ + struct Channel *chanp = fi->userdata; + + chanp->data_open = 0; + chanp->b_st->lli.l4l3(chanp->b_st, DL_RELEASE | REQUEST, NULL); + lli_bhup_fail(fi, event, arg); +} + +/* *INDENT-OFF* */ +static struct FsmNode fnlist[] __initdata = +{ + {ST_NULL, EV_DIAL, lli_prep_dialout}, + {ST_NULL, EV_RESUME, lli_resume}, + {ST_NULL, EV_SETUP_IND, lli_deliver_call}, + {ST_NULL, EV_LEASED, lli_leased_in}, + {ST_OUT_DIAL, EV_SETUP_CNF, lli_init_bchan_out}, + {ST_OUT_DIAL, EV_HANGUP, lli_disconnect_req}, + {ST_OUT_DIAL, EV_DISCONNECT_IND, lli_release_req}, + {ST_OUT_DIAL, EV_RELEASE, lli_dhup_close}, + {ST_OUT_DIAL, EV_NOSETUP_RSP, lli_no_setup_rsp}, + {ST_OUT_DIAL, EV_SETUP_ERR, lli_error}, + {ST_IN_WAIT_LL, EV_LEASED_REL, lli_failure_l}, + {ST_IN_WAIT_LL, EV_ACCEPTD, lli_setup_rsp}, + {ST_IN_WAIT_LL, EV_HANGUP, lli_reject_req}, + {ST_IN_WAIT_LL, EV_DISCONNECT_IND, lli_release_req}, + {ST_IN_WAIT_LL, EV_RELEASE, lli_dhup_close}, + {ST_IN_WAIT_LL, EV_SETUP_IND, lli_deliver_call}, + {ST_IN_WAIT_LL, EV_SETUP_ERR, lli_error}, + {ST_IN_ALERT_SENT, EV_SETUP_CMPL_IND, lli_init_bchan_in}, + {ST_IN_ALERT_SENT, EV_ACCEPTD, lli_send_dconnect}, + {ST_IN_ALERT_SENT, EV_HANGUP, lli_disconnect_reject}, + {ST_IN_ALERT_SENT, EV_DISCONNECT_IND, lli_release_req}, + {ST_IN_ALERT_SENT, EV_RELEASE, lli_dhup_close}, + {ST_IN_ALERT_SENT, EV_REDIR, lli_send_redir}, + {ST_IN_PROCEED_SEND, EV_REDIR, lli_send_redir}, + {ST_IN_PROCEED_SEND, EV_ALERT, lli_send_alert}, + {ST_IN_PROCEED_SEND, EV_ACCEPTD, lli_send_dconnect}, + {ST_IN_PROCEED_SEND, EV_HANGUP, lli_disconnect_reject}, + {ST_IN_PROCEED_SEND, EV_DISCONNECT_IND, lli_dhup_close}, + {ST_IN_ALERT_SENT, EV_RELEASE, lli_dhup_close}, + {ST_IN_WAIT_CONN_ACK, EV_SETUP_CMPL_IND, lli_init_bchan_in}, + {ST_IN_WAIT_CONN_ACK, EV_HANGUP, lli_disconnect_req}, + {ST_IN_WAIT_CONN_ACK, EV_DISCONNECT_IND, lli_release_req}, + {ST_IN_WAIT_CONN_ACK, EV_RELEASE, lli_dhup_close}, + {ST_IN_WAIT_CONN_ACK, EV_CONNECT_ERR, lli_error}, + {ST_WAIT_BCONN, EV_BC_EST, lli_go_active}, + {ST_WAIT_BCONN, EV_BC_REL, lli_rel_b_disc}, + {ST_WAIT_BCONN, EV_HANGUP, lli_rel_b_disc}, + {ST_WAIT_BCONN, EV_DISCONNECT_IND, lli_rel_b_release_req}, + {ST_WAIT_BCONN, EV_RELEASE, lli_rel_b_dhup}, + {ST_WAIT_BCONN, EV_LEASED_REL, lli_rel_b_fail}, + {ST_WAIT_BCONN, EV_CINF, lli_charge_info}, + {ST_ACTIVE, EV_CINF, lli_charge_info}, + {ST_ACTIVE, EV_BC_REL, lli_bhup_rel_b}, + {ST_ACTIVE, EV_SUSPEND, lli_suspend}, + {ST_ACTIVE, EV_HANGUP, lli_disconn_bchan}, + {ST_ACTIVE, EV_DISCONNECT_IND, lli_release_bchan}, + {ST_ACTIVE, EV_RELEASE, lli_abort}, + {ST_ACTIVE, EV_LEASED_REL, lli_failure_a}, + {ST_WAIT_BRELEASE, EV_BC_REL, lli_bhup_disc}, + {ST_WAIT_BRELEASE, EV_DISCONNECT_IND, lli_bhup_release_req}, + {ST_WAIT_BRELEASE, EV_RELEASE, lli_bhup_dhup}, + {ST_WAIT_BRELEASE, EV_LEASED_REL, lli_bhup_fail}, + {ST_WAIT_BREL_DISC, EV_BC_REL, lli_bhup_release_req}, + {ST_WAIT_BREL_DISC, EV_RELEASE, lli_bhup_dhup}, + {ST_WAIT_DCOMMAND, EV_HANGUP, lli_start_disc}, + {ST_WAIT_DCOMMAND, EV_DISCONNECT_IND, lli_release_req}, + {ST_WAIT_DCOMMAND, EV_RELEASE, lli_dhup_close}, + {ST_WAIT_DCOMMAND, EV_LEASED_REL, lli_failure_l}, + {ST_WAIT_DRELEASE, EV_RELEASE, lli_dhup_close}, + {ST_WAIT_DRELEASE, EV_DIAL, lli_dchan_not_ready}, + /* ETS 300-104 16.1 */ + {ST_WAIT_D_REL_CNF, EV_RELEASE, lli_dhup_close}, + {ST_WAIT_D_REL_CNF, EV_DIAL, lli_dchan_not_ready}, +}; +/* *INDENT-ON* */ + +#define FNCOUNT (sizeof(fnlist)/sizeof(struct FsmNode)) + +int __init +CallcNew(void) +{ + callcfsm.state_count = STATE_COUNT; + callcfsm.event_count = EVENT_COUNT; + callcfsm.strEvent = strEvent; + callcfsm.strState = strState; + return FsmNew(&callcfsm, fnlist, FNCOUNT); +} + +void +CallcFree(void) +{ + FsmFree(&callcfsm); +} + +static void +release_b_st(struct Channel *chanp) +{ + struct PStack *st = chanp->b_st; + + if(test_and_clear_bit(FLG_START_B, &chanp->Flags)) { + chanp->bcs->BC_Close(chanp->bcs); + switch (chanp->l2_active_protocol) { + case (ISDN_PROTO_L2_X75I): + releasestack_isdnl2(st); + break; + case (ISDN_PROTO_L2_HDLC): + case (ISDN_PROTO_L2_HDLC_56K): + case (ISDN_PROTO_L2_TRANS): + case (ISDN_PROTO_L2_MODEM): + case (ISDN_PROTO_L2_FAX): + releasestack_transl2(st); + break; + } + } +} + +struct Channel +*selectfreechannel(struct PStack *st, int bch) +{ + struct IsdnCardState *cs = st->l1.hardware; + struct Channel *chanp = st->lli.userdata; + int i; + + if (test_bit(FLG_TWO_DCHAN, &cs->HW_Flags)) + i=1; + else + i=0; + + if (!bch) { + i = 2; /* virtual channel */ + chanp += 2; + } + + while (i < ((bch) ? cs->chanlimit : (2 + MAX_WAITING_CALLS))) { + if (chanp->fi.state == ST_NULL) + return (chanp); + chanp++; + i++; + } + + if (bch) /* number of channels is limited */ { + i = 2; /* virtual channel */ + chanp = st->lli.userdata; + chanp += i; + while (i < (2 + MAX_WAITING_CALLS)) { + if (chanp->fi.state == ST_NULL) + return (chanp); + chanp++; + i++; + } + } + return (NULL); +} + +static void stat_redir_result(struct IsdnCardState *cs, int chan, ulong result) +{ isdn_ctrl ic; + + ic.driver = cs->myid; + ic.command = ISDN_STAT_REDIR; + ic.arg = chan; + ic.parm.num[0] = result; + cs->iif.statcallb(&ic); +} /* stat_redir_result */ + +static void +dchan_l3l4(struct PStack *st, int pr, void *arg) +{ + struct l3_process *pc = arg; + struct IsdnCardState *cs = st->l1.hardware; + struct Channel *chanp; + + if(!pc) + return; + + if (pr == (CC_SETUP | INDICATION)) { + if (!(chanp = selectfreechannel(pc->st, pc->para.bchannel))) { + pc->para.cause = 0x11; /* User busy */ + pc->st->lli.l4l3(pc->st, CC_REJECT | REQUEST, pc); + } else { + chanp->proc = pc; + pc->chan = chanp; + FsmEvent(&chanp->fi, EV_SETUP_IND, NULL); + } + return; + } + if (!(chanp = pc->chan)) + return; + + switch (pr) { + case (CC_MORE_INFO | INDICATION): + FsmEvent(&chanp->fi, EV_SETUP_IND, NULL); + break; + case (CC_DISCONNECT | INDICATION): + FsmEvent(&chanp->fi, EV_DISCONNECT_IND, NULL); + break; + case (CC_RELEASE | CONFIRM): + FsmEvent(&chanp->fi, EV_RELEASE, NULL); + break; + case (CC_SUSPEND | CONFIRM): + FsmEvent(&chanp->fi, EV_RELEASE, NULL); + break; + case (CC_RESUME | CONFIRM): + FsmEvent(&chanp->fi, EV_SETUP_CNF, NULL); + break; + case (CC_RESUME_ERR): + FsmEvent(&chanp->fi, EV_RELEASE, NULL); + break; + case (CC_RELEASE | INDICATION): + FsmEvent(&chanp->fi, EV_RELEASE, NULL); + break; + case (CC_SETUP_COMPL | INDICATION): + FsmEvent(&chanp->fi, EV_SETUP_CMPL_IND, NULL); + break; + case (CC_SETUP | CONFIRM): + FsmEvent(&chanp->fi, EV_SETUP_CNF, NULL); + break; + case (CC_CHARGE | INDICATION): + FsmEvent(&chanp->fi, EV_CINF, NULL); + break; + case (CC_NOSETUP_RSP): + FsmEvent(&chanp->fi, EV_NOSETUP_RSP, NULL); + break; + case (CC_SETUP_ERR): + FsmEvent(&chanp->fi, EV_SETUP_ERR, NULL); + break; + case (CC_CONNECT_ERR): + FsmEvent(&chanp->fi, EV_CONNECT_ERR, NULL); + break; + case (CC_RELEASE_ERR): + FsmEvent(&chanp->fi, EV_RELEASE, NULL); + break; + case (CC_PROCEED_SEND | INDICATION): + case (CC_PROCEEDING | INDICATION): + case (CC_ALERTING | INDICATION): + case (CC_PROGRESS | INDICATION): + case (CC_NOTIFY | INDICATION): + break; + case (CC_REDIR | INDICATION): + stat_redir_result(cs, chanp->chan, pc->redir_result); + break; + default: + if (chanp->debug & 0x800) { + HiSax_putstatus(chanp->cs, "Ch", + "%d L3->L4 unknown primitiv %#x", + chanp->chan, pr); + } + } +} + +static void +dummy_pstack(struct PStack *st, int pr, void *arg) { + printk(KERN_WARNING"call to dummy_pstack pr=%04x arg %lx\n", pr, (long)arg); +} + +static int +init_PStack(struct PStack **stp) { + *stp = kmalloc(sizeof(struct PStack), GFP_ATOMIC); + if (!*stp) + return -ENOMEM; + (*stp)->next = NULL; + (*stp)->l1.l1l2 = dummy_pstack; + (*stp)->l1.l1hw = dummy_pstack; + (*stp)->l1.l1tei = dummy_pstack; + (*stp)->l2.l2tei = dummy_pstack; + (*stp)->l2.l2l1 = dummy_pstack; + (*stp)->l2.l2l3 = dummy_pstack; + (*stp)->l3.l3l2 = dummy_pstack; + (*stp)->l3.l3ml3 = dummy_pstack; + (*stp)->l3.l3l4 = dummy_pstack; + (*stp)->lli.l4l3 = dummy_pstack; + (*stp)->ma.layer = dummy_pstack; + return 0; +} + +static int +init_d_st(struct Channel *chanp) +{ + struct PStack *st; + struct IsdnCardState *cs = chanp->cs; + char tmp[16]; + int err; + + err = init_PStack(&chanp->d_st); + if (err) + return err; + st = chanp->d_st; + st->next = NULL; + HiSax_addlist(cs, st); + setstack_HiSax(st, cs); + st->l2.sap = 0; + st->l2.tei = -1; + st->l2.flag = 0; + test_and_set_bit(FLG_MOD128, &st->l2.flag); + test_and_set_bit(FLG_LAPD, &st->l2.flag); + test_and_set_bit(FLG_ORIG, &st->l2.flag); + st->l2.maxlen = MAX_DFRAME_LEN; + st->l2.window = 1; + st->l2.T200 = 1000; /* 1000 milliseconds */ + st->l2.N200 = 3; /* try 3 times */ + st->l2.T203 = 10000; /* 10000 milliseconds */ + if (test_bit(FLG_TWO_DCHAN, &cs->HW_Flags)) + sprintf(tmp, "DCh%d Q.921 ", chanp->chan); + else + sprintf(tmp, "DCh Q.921 "); + setstack_isdnl2(st, tmp); + setstack_l3dc(st, chanp); + st->lli.userdata = chanp; + st->l3.l3l4 = dchan_l3l4; + + return 0; +} + +static void +callc_debug(struct FsmInst *fi, char *fmt, ...) +{ + va_list args; + struct Channel *chanp = fi->userdata; + char tmp[16]; + + va_start(args, fmt); + sprintf(tmp, "Ch%d callc ", chanp->chan); + VHiSax_putstatus(chanp->cs, tmp, fmt, args); + va_end(args); +} + +static int +init_chan(int chan, struct IsdnCardState *csta) +{ + struct Channel *chanp = csta->channel + chan; + int err; + + chanp->cs = csta; + chanp->bcs = csta->bcs + chan; + chanp->chan = chan; + chanp->incoming = 0; + chanp->debug = 0; + chanp->Flags = 0; + chanp->leased = 0; + err = init_PStack(&chanp->b_st); + if (err) + return err; + chanp->b_st->l1.delay = DEFAULT_B_DELAY; + chanp->fi.fsm = &callcfsm; + chanp->fi.state = ST_NULL; + chanp->fi.debug = 0; + chanp->fi.userdata = chanp; + chanp->fi.printdebug = callc_debug; + FsmInitTimer(&chanp->fi, &chanp->dial_timer); + FsmInitTimer(&chanp->fi, &chanp->drel_timer); + if (!chan || (test_bit(FLG_TWO_DCHAN, &csta->HW_Flags) && chan < 2)) { + err = init_d_st(chanp); + if (err) + return err; + } else { + chanp->d_st = csta->channel->d_st; + } + chanp->data_open = 0; + return 0; +} + +int +CallcNewChan(struct IsdnCardState *csta) { + int i, err; + + chancount += 2; + err = init_chan(0, csta); + if (err) + return err; + err = init_chan(1, csta); + if (err) + return err; + printk(KERN_INFO "HiSax: 2 channels added\n"); + + for (i = 0; i < MAX_WAITING_CALLS; i++) { + err = init_chan(i+2,csta); + if (err) + return err; + } + printk(KERN_INFO "HiSax: MAX_WAITING_CALLS added\n"); + if (test_bit(FLG_PTP, &csta->channel->d_st->l2.flag)) { + printk(KERN_INFO "LAYER2 WATCHING ESTABLISH\n"); + csta->channel->d_st->lli.l4l3(csta->channel->d_st, + DL_ESTABLISH | REQUEST, NULL); + } + return (0); +} + +static void +release_d_st(struct Channel *chanp) +{ + struct PStack *st = chanp->d_st; + + if (!st) + return; + releasestack_isdnl2(st); + releasestack_isdnl3(st); + HiSax_rmlist(st->l1.hardware, st); + kfree(st); + chanp->d_st = NULL; +} + +void +CallcFreeChan(struct IsdnCardState *csta) +{ + int i; + + for (i = 0; i < 2; i++) { + FsmDelTimer(&csta->channel[i].drel_timer, 74); + FsmDelTimer(&csta->channel[i].dial_timer, 75); + if (i || test_bit(FLG_TWO_DCHAN, &csta->HW_Flags)) + release_d_st(csta->channel + i); + if (csta->channel[i].b_st) { + release_b_st(csta->channel + i); + kfree(csta->channel[i].b_st); + csta->channel[i].b_st = NULL; + } else + printk(KERN_WARNING "CallcFreeChan b_st ch%d allready freed\n", i); + if (i || test_bit(FLG_TWO_DCHAN, &csta->HW_Flags)) { + release_d_st(csta->channel + i); + } else + csta->channel[i].d_st = NULL; + } +} + +static void +lldata_handler(struct PStack *st, int pr, void *arg) +{ + struct Channel *chanp = (struct Channel *) st->lli.userdata; + struct sk_buff *skb = arg; + + switch (pr) { + case (DL_DATA | INDICATION): + if (chanp->data_open) { + if (chanp->debug & 0x800) + link_debug(chanp, 0, "lldata: %d", skb->len); + chanp->cs->iif.rcvcallb_skb(chanp->cs->myid, chanp->chan, skb); + } else { + link_debug(chanp, 0, "lldata: channel not open"); + dev_kfree_skb(skb); + } + break; + case (DL_ESTABLISH | INDICATION): + case (DL_ESTABLISH | CONFIRM): + FsmEvent(&chanp->fi, EV_BC_EST, NULL); + break; + case (DL_RELEASE | INDICATION): + case (DL_RELEASE | CONFIRM): + FsmEvent(&chanp->fi, EV_BC_REL, NULL); + break; + default: + printk(KERN_WARNING "lldata_handler unknown primitive %#x\n", + pr); + break; + } +} + +static void +lltrans_handler(struct PStack *st, int pr, void *arg) +{ + struct Channel *chanp = (struct Channel *) st->lli.userdata; + struct sk_buff *skb = arg; + + switch (pr) { + case (PH_DATA | INDICATION): + if (chanp->data_open) { + if (chanp->debug & 0x800) + link_debug(chanp, 0, "lltrans: %d", skb->len); + chanp->cs->iif.rcvcallb_skb(chanp->cs->myid, chanp->chan, skb); + } else { + link_debug(chanp, 0, "lltrans: channel not open"); + dev_kfree_skb(skb); + } + break; + case (PH_ACTIVATE | INDICATION): + case (PH_ACTIVATE | CONFIRM): + FsmEvent(&chanp->fi, EV_BC_EST, NULL); + break; + case (PH_DEACTIVATE | INDICATION): + case (PH_DEACTIVATE | CONFIRM): + FsmEvent(&chanp->fi, EV_BC_REL, NULL); + break; + default: + printk(KERN_WARNING "lltrans_handler unknown primitive %#x\n", + pr); + break; + } +} + +void +lli_writewakeup(struct PStack *st, int len) +{ + struct Channel *chanp = st->lli.userdata; + isdn_ctrl ic; + + if (chanp->debug & 0x800) + link_debug(chanp, 0, "llwakeup: %d", len); + ic.driver = chanp->cs->myid; + ic.command = ISDN_STAT_BSENT; + ic.arg = chanp->chan; + ic.parm.length = len; + chanp->cs->iif.statcallb(&ic); +} + +static int +init_b_st(struct Channel *chanp, int incoming) +{ + struct PStack *st = chanp->b_st; + struct IsdnCardState *cs = chanp->cs; + char tmp[16]; + + st->l1.hardware = cs; + if (chanp->leased) + st->l1.bc = chanp->chan & 1; + else + st->l1.bc = chanp->proc->para.bchannel - 1; + switch (chanp->l2_active_protocol) { + case (ISDN_PROTO_L2_X75I): + case (ISDN_PROTO_L2_HDLC): + st->l1.mode = L1_MODE_HDLC; + break; + case (ISDN_PROTO_L2_HDLC_56K): + st->l1.mode = L1_MODE_HDLC_56K; + break; + case (ISDN_PROTO_L2_TRANS): + st->l1.mode = L1_MODE_TRANS; + break; + case (ISDN_PROTO_L2_MODEM): + st->l1.mode = L1_MODE_V32; + break; + case (ISDN_PROTO_L2_FAX): + st->l1.mode = L1_MODE_FAX; + break; + } + chanp->bcs->conmsg = NULL; + if (chanp->bcs->BC_SetStack(st, chanp->bcs)) + return (-1); + st->l2.flag = 0; + test_and_set_bit(FLG_LAPB, &st->l2.flag); + st->l2.maxlen = MAX_DATA_SIZE; + if (!incoming) + test_and_set_bit(FLG_ORIG, &st->l2.flag); + st->l2.T200 = 1000; /* 1000 milliseconds */ + st->l2.window = 7; + st->l2.N200 = 4; /* try 4 times */ + st->l2.T203 = 5000; /* 5000 milliseconds */ + st->l3.debug = 0; + switch (chanp->l2_active_protocol) { + case (ISDN_PROTO_L2_X75I): + sprintf(tmp, "Ch%d X.75", chanp->chan); + setstack_isdnl2(st, tmp); + setstack_l3bc(st, chanp); + st->l2.l2l3 = lldata_handler; + st->lli.userdata = chanp; + test_and_clear_bit(FLG_LLI_L1WAKEUP, &st->lli.flag); + test_and_set_bit(FLG_LLI_L2WAKEUP, &st->lli.flag); + st->l2.l2m.debug = chanp->debug & 16; + st->l2.debug = chanp->debug & 64; + break; + case (ISDN_PROTO_L2_HDLC): + case (ISDN_PROTO_L2_HDLC_56K): + case (ISDN_PROTO_L2_TRANS): + case (ISDN_PROTO_L2_MODEM): + case (ISDN_PROTO_L2_FAX): + st->l1.l1l2 = lltrans_handler; + st->lli.userdata = chanp; + test_and_set_bit(FLG_LLI_L1WAKEUP, &st->lli.flag); + test_and_clear_bit(FLG_LLI_L2WAKEUP, &st->lli.flag); + setstack_transl2(st); + setstack_l3bc(st, chanp); + break; + } + test_and_set_bit(FLG_START_B, &chanp->Flags); + return (0); +} + +static void +leased_l4l3(struct PStack *st, int pr, void *arg) +{ + struct Channel *chanp = (struct Channel *) st->lli.userdata; + struct sk_buff *skb = arg; + + switch (pr) { + case (DL_DATA | REQUEST): + link_debug(chanp, 0, "leased line d-channel DATA"); + dev_kfree_skb(skb); + break; + case (DL_ESTABLISH | REQUEST): + st->l2.l2l1(st, PH_ACTIVATE | REQUEST, NULL); + break; + case (DL_RELEASE | REQUEST): + break; + default: + printk(KERN_WARNING "transd_l4l3 unknown primitive %#x\n", + pr); + break; + } +} + +static void +leased_l1l2(struct PStack *st, int pr, void *arg) +{ + struct Channel *chanp = (struct Channel *) st->lli.userdata; + struct sk_buff *skb = arg; + int i,event = EV_LEASED_REL; + + switch (pr) { + case (PH_DATA | INDICATION): + link_debug(chanp, 0, "leased line d-channel DATA"); + dev_kfree_skb(skb); + break; + case (PH_ACTIVATE | INDICATION): + case (PH_ACTIVATE | CONFIRM): + event = EV_LEASED; + case (PH_DEACTIVATE | INDICATION): + case (PH_DEACTIVATE | CONFIRM): + if (test_bit(FLG_TWO_DCHAN, &chanp->cs->HW_Flags)) + i = 1; + else + i = 0; + while (i < 2) { + FsmEvent(&chanp->fi, event, NULL); + chanp++; + i++; + } + break; + default: + printk(KERN_WARNING + "transd_l1l2 unknown primitive %#x\n", pr); + break; + } +} + +static void +distr_debug(struct IsdnCardState *csta, int debugflags) +{ + int i; + struct Channel *chanp = csta->channel; + + for (i = 0; i < (2 + MAX_WAITING_CALLS) ; i++) { + chanp[i].debug = debugflags; + chanp[i].fi.debug = debugflags & 2; + chanp[i].d_st->l2.l2m.debug = debugflags & 8; + chanp[i].b_st->l2.l2m.debug = debugflags & 0x10; + chanp[i].d_st->l2.debug = debugflags & 0x20; + chanp[i].b_st->l2.debug = debugflags & 0x40; + chanp[i].d_st->l3.l3m.debug = debugflags & 0x80; + chanp[i].b_st->l3.l3m.debug = debugflags & 0x100; + chanp[i].b_st->ma.tei_m.debug = debugflags & 0x200; + chanp[i].b_st->ma.debug = debugflags & 0x200; + chanp[i].d_st->l1.l1m.debug = debugflags & 0x1000; + chanp[i].b_st->l1.l1m.debug = debugflags & 0x2000; + } + if (debugflags & 4) + csta->debug |= DEB_DLOG_HEX; + else + csta->debug &= ~DEB_DLOG_HEX; +} + +static char tmpbuf[256]; + +static void +capi_debug(struct Channel *chanp, capi_msg *cm) +{ + char *t = tmpbuf; + + t += QuickHex(t, (u_char *)cm, (cm->Length>50)? 50: cm->Length); + t--; + *t= 0; + HiSax_putstatus(chanp->cs, "Ch", "%d CAPIMSG %s", chanp->chan, tmpbuf); +} + +void +lli_got_fac_req(struct Channel *chanp, capi_msg *cm) { + if ((cm->para[0] != 3) || (cm->para[1] != 0)) + return; + if (cm->para[2]<3) + return; + if (cm->para[4] != 0) + return; + switch(cm->para[3]) { + case 4: /* Suspend */ + strncpy(chanp->setup.phone, &cm->para[5], cm->para[5] +1); + FsmEvent(&chanp->fi, EV_SUSPEND, cm); + break; + case 5: /* Resume */ + strncpy(chanp->setup.phone, &cm->para[5], cm->para[5] +1); + if (chanp->fi.state == ST_NULL) { + FsmEvent(&chanp->fi, EV_RESUME, cm); + } else { + FsmDelTimer(&chanp->dial_timer, 72); + FsmAddTimer(&chanp->dial_timer, 80, EV_RESUME, cm, 73); + } + break; + } +} + +void +lli_got_manufacturer(struct Channel *chanp, struct IsdnCardState *cs, capi_msg *cm) { + if ((cs->typ == ISDN_CTYPE_ELSA) || (cs->typ == ISDN_CTYPE_ELSA_PNP) || + (cs->typ == ISDN_CTYPE_ELSA_PCI)) { + if (cs->hw.elsa.MFlag) { + cs->cardmsg(cs, CARD_AUX_IND, cm->para); + } + } +} + + +/***************************************************************/ +/* Limit the available number of channels for the current card */ +/***************************************************************/ +static int +set_channel_limit(struct IsdnCardState *cs, int chanmax) +{ + isdn_ctrl ic; + int i, ii; + + if ((chanmax < 0) || (chanmax > 2)) + return(-EINVAL); + cs->chanlimit = 0; + for (ii = 0; ii < 2; ii++) { + ic.driver = cs->myid; + ic.command = ISDN_STAT_DISCH; + ic.arg = ii; + if (ii >= chanmax) + ic.parm.num[0] = 0; /* disabled */ + else + ic.parm.num[0] = 1; /* enabled */ + i = cs->iif.statcallb(&ic); + if (i) return(-EINVAL); + if (ii < chanmax) + cs->chanlimit++; + } + return(0); +} /* set_channel_limit */ + +int +HiSax_command(isdn_ctrl * ic) +{ + struct IsdnCardState *csta = hisax_findcard(ic->driver); + struct PStack *st; + struct Channel *chanp; + int i; + u_int num; + + if (!csta) { + printk(KERN_ERR + "HiSax: if_command %d called with invalid driverId %d!\n", + ic->command, ic->driver); + return -ENODEV; + } + switch (ic->command) { + case (ISDN_CMD_SETEAZ): + chanp = csta->channel + ic->arg; + break; + case (ISDN_CMD_SETL2): + chanp = csta->channel + (ic->arg & 0xff); + if (chanp->debug & 1) + link_debug(chanp, 1, "SETL2 card %d %ld", + csta->cardnr + 1, ic->arg >> 8); + chanp->l2_protocol = ic->arg >> 8; + break; + case (ISDN_CMD_SETL3): + chanp = csta->channel + (ic->arg & 0xff); + if (chanp->debug & 1) + link_debug(chanp, 1, "SETL3 card %d %ld", + csta->cardnr + 1, ic->arg >> 8); + chanp->l3_protocol = ic->arg >> 8; + break; + case (ISDN_CMD_DIAL): + chanp = csta->channel + (ic->arg & 0xff); + if (chanp->debug & 1) + link_debug(chanp, 1, "DIAL %s -> %s (%d,%d)", + ic->parm.setup.eazmsn, ic->parm.setup.phone, + ic->parm.setup.si1, ic->parm.setup.si2); + memcpy(&chanp->setup, &ic->parm.setup, sizeof(setup_parm)); + if (!strcmp(chanp->setup.eazmsn, "0")) + chanp->setup.eazmsn[0] = '\0'; + /* this solution is dirty and may be change, if + * we make a callreference based callmanager */ + if (chanp->fi.state == ST_NULL) { + FsmEvent(&chanp->fi, EV_DIAL, NULL); + } else { + FsmDelTimer(&chanp->dial_timer, 70); + FsmAddTimer(&chanp->dial_timer, 50, EV_DIAL, NULL, 71); + } + break; + case (ISDN_CMD_ACCEPTB): + chanp = csta->channel + ic->arg; + if (chanp->debug & 1) + link_debug(chanp, 1, "ACCEPTB"); + FsmEvent(&chanp->fi, EV_ACCEPTB, NULL); + break; + case (ISDN_CMD_ACCEPTD): + chanp = csta->channel + ic->arg; + memcpy(&chanp->setup, &ic->parm.setup, sizeof(setup_parm)); + if (chanp->debug & 1) + link_debug(chanp, 1, "ACCEPTD"); + FsmEvent(&chanp->fi, EV_ACCEPTD, NULL); + break; + case (ISDN_CMD_HANGUP): + chanp = csta->channel + ic->arg; + if (chanp->debug & 1) + link_debug(chanp, 1, "HANGUP"); + FsmEvent(&chanp->fi, EV_HANGUP, NULL); + break; + case (CAPI_PUT_MESSAGE): + chanp = csta->channel + ic->arg; + if (chanp->debug & 1) + capi_debug(chanp, &ic->parm.cmsg); + if (ic->parm.cmsg.Length < 8) + break; + switch(ic->parm.cmsg.Command) { + case CAPI_FACILITY: + if (ic->parm.cmsg.Subcommand == CAPI_REQ) + lli_got_fac_req(chanp, &ic->parm.cmsg); + break; + case CAPI_MANUFACTURER: + if (ic->parm.cmsg.Subcommand == CAPI_REQ) + lli_got_manufacturer(chanp, csta, &ic->parm.cmsg); + break; + default: + break; + } + break; + case (ISDN_CMD_IOCTL): + switch (ic->arg) { + case (0): + num = *(unsigned int *) ic->parm.num; + HiSax_reportcard(csta->cardnr, num); + break; + case (1): + num = *(unsigned int *) ic->parm.num; + distr_debug(csta, num); + printk(KERN_DEBUG "HiSax: debugging flags card %d set to %x\n", + csta->cardnr + 1, num); + HiSax_putstatus(csta, "debugging flags ", + "card %d set to %x", csta->cardnr + 1, num); + break; + case (2): + num = *(unsigned int *) ic->parm.num; + csta->channel[0].b_st->l1.delay = num; + csta->channel[1].b_st->l1.delay = num; + HiSax_putstatus(csta, "delay ", "card %d set to %d ms", + csta->cardnr + 1, num); + printk(KERN_DEBUG "HiSax: delay card %d set to %d ms\n", + csta->cardnr + 1, num); + break; + case (5): /* set card in leased mode */ + num = *(unsigned int *) ic->parm.num; + if ((num <1) || (num > 2)) { + HiSax_putstatus(csta, "Set LEASED ", + "wrong channel %d", num); + printk(KERN_WARNING "HiSax: Set LEASED wrong channel %d\n", + num); + } else { + num--; + chanp = csta->channel +num; + chanp->leased = 1; + HiSax_putstatus(csta, "Card", + "%d channel %d set leased mode\n", + csta->cardnr + 1, num + 1); + chanp->d_st->l1.l1l2 = leased_l1l2; + chanp->d_st->lli.l4l3 = leased_l4l3; + chanp->d_st->lli.l4l3(chanp->d_st, + DL_ESTABLISH | REQUEST, NULL); + } + break; + case (6): /* set B-channel test loop */ + num = *(unsigned int *) ic->parm.num; + if (csta->stlist) + csta->stlist->l2.l2l1(csta->stlist, + PH_TESTLOOP | REQUEST, (void *) (long)num); + break; + case (7): /* set card in PTP mode */ + num = *(unsigned int *) ic->parm.num; + if (test_bit(FLG_TWO_DCHAN, &csta->HW_Flags)) { + printk(KERN_ERR "HiSax PTP mode only with one TEI possible\n"); + } else if (num) { + test_and_set_bit(FLG_PTP, &csta->channel[0].d_st->l2.flag); + test_and_set_bit(FLG_FIXED_TEI, &csta->channel[0].d_st->l2.flag); + csta->channel[0].d_st->l2.tei = 0; + HiSax_putstatus(csta, "set card ", "in PTP mode"); + printk(KERN_DEBUG "HiSax: set card in PTP mode\n"); + printk(KERN_INFO "LAYER2 WATCHING ESTABLISH\n"); + csta->channel[0].d_st->lli.l4l3(csta->channel[0].d_st, + DL_ESTABLISH | REQUEST, NULL); + } else { + test_and_clear_bit(FLG_PTP, &csta->channel[0].d_st->l2.flag); + test_and_clear_bit(FLG_FIXED_TEI, &csta->channel[0].d_st->l2.flag); + HiSax_putstatus(csta, "set card ", "in PTMP mode"); + printk(KERN_DEBUG "HiSax: set card in PTMP mode\n"); + } + break; + case (8): /* set card in FIXED TEI mode */ + num = *(unsigned int *) ic->parm.num; + chanp = csta->channel + (num & 1); + num = num >>1; + if (num == 127) { + test_and_clear_bit(FLG_FIXED_TEI, &chanp->d_st->l2.flag); + chanp->d_st->l2.tei = -1; + HiSax_putstatus(csta, "set card ", "in VAR TEI mode"); + printk(KERN_DEBUG "HiSax: set card in VAR TEI mode\n"); + } else { + test_and_set_bit(FLG_FIXED_TEI, &chanp->d_st->l2.flag); + chanp->d_st->l2.tei = num; + HiSax_putstatus(csta, "set card ", "in FIXED TEI (%d) mode", num); + printk(KERN_DEBUG "HiSax: set card in FIXED TEI (%d) mode\n", + num); + } + chanp->d_st->lli.l4l3(chanp->d_st, + DL_ESTABLISH | REQUEST, NULL); + break; + case (11): + num = csta->debug & DEB_DLOG_HEX; + csta->debug = *(unsigned int *) ic->parm.num; + csta->debug |= num; + HiSax_putstatus(cards[0].cs, "l1 debugging ", + "flags card %d set to %x", + csta->cardnr + 1, csta->debug); + printk(KERN_DEBUG "HiSax: l1 debugging flags card %d set to %x\n", + csta->cardnr + 1, csta->debug); + break; + case (13): + csta->channel[0].d_st->l3.debug = *(unsigned int *) ic->parm.num; + csta->channel[1].d_st->l3.debug = *(unsigned int *) ic->parm.num; + HiSax_putstatus(cards[0].cs, "l3 debugging ", + "flags card %d set to %x\n", csta->cardnr + 1, + *(unsigned int *) ic->parm.num); + printk(KERN_DEBUG "HiSax: l3 debugging flags card %d set to %x\n", + csta->cardnr + 1, *(unsigned int *) ic->parm.num); + break; + case (10): + i = *(unsigned int *) ic->parm.num; + return(set_channel_limit(csta, i)); + default: + if (csta->auxcmd) + return(csta->auxcmd(csta, ic)); + printk(KERN_DEBUG "HiSax: invalid ioclt %d\n", + (int) ic->arg); + return (-EINVAL); + } + break; + + case (ISDN_CMD_PROCEED): + chanp = csta->channel + ic->arg; + if (chanp->debug & 1) + link_debug(chanp, 1, "PROCEED"); + FsmEvent(&chanp->fi, EV_PROCEED, NULL); + break; + + case (ISDN_CMD_ALERT): + chanp = csta->channel + ic->arg; + if (chanp->debug & 1) + link_debug(chanp, 1, "ALERT"); + FsmEvent(&chanp->fi, EV_ALERT, NULL); + break; + + case (ISDN_CMD_REDIR): + chanp = csta->channel + ic->arg; + if (chanp->debug & 1) + link_debug(chanp, 1, "REDIR"); + memcpy(&chanp->setup, &ic->parm.setup, sizeof(setup_parm)); + FsmEvent(&chanp->fi, EV_REDIR, NULL); + break; + + /* protocol specific io commands */ + case (ISDN_CMD_PROT_IO): + for (st = csta->stlist; st; st = st->next) + if (st->protocol == (ic->arg & 0xFF)) + return(st->lli.l4l3_proto(st, ic)); + return(-EINVAL); + break; + default: + if (csta->auxcmd) + return(csta->auxcmd(csta, ic)); + return(-EINVAL); + } + return (0); +} + +int +HiSax_writebuf_skb(int id, int chan, int ack, struct sk_buff *skb) +{ + struct IsdnCardState *csta = hisax_findcard(id); + struct Channel *chanp; + struct PStack *st; + int len = skb->len; + struct sk_buff *nskb; + + if (!csta) { + printk(KERN_ERR + "HiSax: if_sendbuf called with invalid driverId!\n"); + return -ENODEV; + } + chanp = csta->channel + chan; + st = chanp->b_st; + if (!chanp->data_open) { + link_debug(chanp, 1, "writebuf: channel not open"); + return -EIO; + } + if (len > MAX_DATA_SIZE) { + link_debug(chanp, 1, "writebuf: packet too large (%d bytes)", len); + printk(KERN_WARNING "HiSax_writebuf: packet too large (%d bytes) !\n", + len); + return -EINVAL; + } + if (len) { + if ((len + chanp->bcs->tx_cnt) > MAX_DATA_MEM) { + /* Must return 0 here, since this is not an error + * but a temporary lack of resources. + */ + if (chanp->debug & 0x800) + link_debug(chanp, 1, "writebuf: no buffers for %d bytes", len); + return 0; + } else if (chanp->debug & 0x800) + link_debug(chanp, 1, "writebuf %d/%d/%d", len, chanp->bcs->tx_cnt,MAX_DATA_MEM); + nskb = skb_clone(skb, GFP_ATOMIC); + if (nskb) { + nskb->truesize = nskb->len; + if (!ack) + nskb->pkt_type = PACKET_NOACK; + if (chanp->l2_active_protocol == ISDN_PROTO_L2_X75I) + st->l3.l3l2(st, DL_DATA | REQUEST, nskb); + else { + chanp->bcs->tx_cnt += len; + st->l2.l2l1(st, PH_DATA | REQUEST, nskb); + } + dev_kfree_skb(skb); + } else + len = 0; + } + return (len); +} diff --git a/drivers/isdn/hisax/config.c b/drivers/isdn/hisax/config.c new file mode 100644 index 000000000000..1663ee69d41d --- /dev/null +++ b/drivers/isdn/hisax/config.c @@ -0,0 +1,1958 @@ +/* $Id: config.c,v 2.84.2.5 2004/02/11 13:21:33 keil Exp $ + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * by Kai Germaschewski <kai.germaschewski@gmx.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * For changes and modifications please read + * Documentation/isdn/HiSax.cert + * + * based on the teles driver from Jan den Ouden + * + */ + +#include <linux/types.h> +#include <linux/stddef.h> +#include <linux/timer.h> +#include <linux/config.h> +#include <linux/init.h> +#include "hisax.h" +#include <linux/module.h> +#include <linux/kernel_stat.h> +#include <linux/workqueue.h> +#include <linux/interrupt.h> +#define HISAX_STATUS_BUFSIZE 4096 +#define INCLUDE_INLINE_FUNCS + +/* + * This structure array contains one entry per card. An entry looks + * like this: + * + * { type, protocol, p0, p1, p2, NULL } + * + * type + * 1 Teles 16.0 p0=irq p1=membase p2=iobase + * 2 Teles 8.0 p0=irq p1=membase + * 3 Teles 16.3 p0=irq p1=iobase + * 4 Creatix PNP p0=irq p1=IO0 (ISAC) p2=IO1 (HSCX) + * 5 AVM A1 (Fritz) p0=irq p1=iobase + * 6 ELSA PC [p0=iobase] or nothing (autodetect) + * 7 ELSA Quickstep p0=irq p1=iobase + * 8 Teles PCMCIA p0=irq p1=iobase + * 9 ITK ix1-micro p0=irq p1=iobase + * 10 ELSA PCMCIA p0=irq p1=iobase + * 11 Eicon.Diehl Diva p0=irq p1=iobase + * 12 Asuscom ISDNLink p0=irq p1=iobase + * 13 Teleint p0=irq p1=iobase + * 14 Teles 16.3c p0=irq p1=iobase + * 15 Sedlbauer speed p0=irq p1=iobase + * 15 Sedlbauer PC/104 p0=irq p1=iobase + * 15 Sedlbauer speed pci no parameter + * 16 USR Sportster internal p0=irq p1=iobase + * 17 MIC card p0=irq p1=iobase + * 18 ELSA Quickstep 1000PCI no parameter + * 19 Compaq ISDN S0 ISA card p0=irq p1=IO0 (HSCX) p2=IO1 (ISAC) p3=IO2 + * 20 Travers Technologies NETjet-S PCI card + * 21 TELES PCI no parameter + * 22 Sedlbauer Speed Star p0=irq p1=iobase + * 23 reserved + * 24 Dr Neuhaus Niccy PnP/PCI card p0=irq p1=IO0 p2=IO1 (PnP only) + * 25 Teles S0Box p0=irq p1=iobase (from isapnp setup) + * 26 AVM A1 PCMCIA (Fritz) p0=irq p1=iobase + * 27 AVM PnP/PCI p0=irq p1=iobase (PCI no parameter) + * 28 Sedlbauer Speed Fax+ p0=irq p1=iobase (from isapnp setup) + * 29 Siemens I-Surf p0=irq p1=iobase p2=memory (from isapnp setup) + * 30 ACER P10 p0=irq p1=iobase (from isapnp setup) + * 31 HST Saphir p0=irq p1=iobase + * 32 Telekom A4T none + * 33 Scitel Quadro p0=subcontroller (4*S0, subctrl 1...4) + * 34 Gazel ISDN cards + * 35 HFC 2BDS0 PCI none + * 36 Winbond 6692 PCI none + * 37 HFC 2BDS0 S+/SP p0=irq p1=iobase + * 38 Travers Technologies NETspider-U PCI card + * 39 HFC 2BDS0-SP PCMCIA p0=irq p1=iobase + * 40 hotplug interface + * 41 Formula-n enter:now ISDN PCI a/b none + * + * protocol can be either ISDN_PTYPE_EURO or ISDN_PTYPE_1TR6 or ISDN_PTYPE_NI1 + * + * + */ + +const char *CardType[] = { + "No Card", "Teles 16.0", "Teles 8.0", "Teles 16.3", + "Creatix/Teles PnP", "AVM A1", "Elsa ML", "Elsa Quickstep", + "Teles PCMCIA", "ITK ix1-micro Rev.2", "Elsa PCMCIA", + "Eicon.Diehl Diva", "ISDNLink", "TeleInt", "Teles 16.3c", + "Sedlbauer Speed Card", "USR Sportster", "ith mic Linux", + "Elsa PCI", "Compaq ISA", "NETjet-S", "Teles PCI", + "Sedlbauer Speed Star (PCMCIA)", "AMD 7930", "NICCY", "S0Box", + "AVM A1 (PCMCIA)", "AVM Fritz PnP/PCI", "Sedlbauer Speed Fax +", + "Siemens I-Surf", "Acer P10", "HST Saphir", "Telekom A4T", + "Scitel Quadro", "Gazel", "HFC 2BDS0 PCI", "Winbond 6692", + "HFC 2BDS0 SX", "NETspider-U", "HFC-2BDS0-SP PCMCIA", + "Hotplug", "Formula-n enter:now PCI a/b", +}; + +#ifdef CONFIG_HISAX_ELSA +#define DEFAULT_CARD ISDN_CTYPE_ELSA +#define DEFAULT_CFG {0,0,0,0} +#endif + +#ifdef CONFIG_HISAX_AVM_A1 +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_A1 +#define DEFAULT_CFG {10,0x340,0,0} +#endif + +#ifdef CONFIG_HISAX_AVM_A1_PCMCIA +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_A1_PCMCIA +#define DEFAULT_CFG {11,0x170,0,0} +#endif + +#ifdef CONFIG_HISAX_FRITZPCI +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_FRITZPCI +#define DEFAULT_CFG {0,0,0,0} +#endif + +#ifdef CONFIG_HISAX_16_3 +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_16_3 +#define DEFAULT_CFG {15,0x180,0,0} +#endif + +#ifdef CONFIG_HISAX_S0BOX +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_S0BOX +#define DEFAULT_CFG {7,0x378,0,0} +#endif + +#ifdef CONFIG_HISAX_16_0 +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_16_0 +#define DEFAULT_CFG {15,0xd0000,0xd80,0} +#endif + +#ifdef CONFIG_HISAX_TELESPCI +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_TELESPCI +#define DEFAULT_CFG {0,0,0,0} +#endif + +#ifdef CONFIG_HISAX_IX1MICROR2 +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_IX1MICROR2 +#define DEFAULT_CFG {5,0x390,0,0} +#endif + +#ifdef CONFIG_HISAX_DIEHLDIVA +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_DIEHLDIVA +#define DEFAULT_CFG {0,0x0,0,0} +#endif + +#ifdef CONFIG_HISAX_ASUSCOM +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_ASUSCOM +#define DEFAULT_CFG {5,0x200,0,0} +#endif + +#ifdef CONFIG_HISAX_TELEINT +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_TELEINT +#define DEFAULT_CFG {5,0x300,0,0} +#endif + +#ifdef CONFIG_HISAX_SEDLBAUER +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_SEDLBAUER +#define DEFAULT_CFG {11,0x270,0,0} +#endif + +#ifdef CONFIG_HISAX_SPORTSTER +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_SPORTSTER +#define DEFAULT_CFG {7,0x268,0,0} +#endif + +#ifdef CONFIG_HISAX_MIC +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_MIC +#define DEFAULT_CFG {12,0x3e0,0,0} +#endif + +#ifdef CONFIG_HISAX_NETJET +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_NETJET_S +#define DEFAULT_CFG {0,0,0,0} +#endif + +#ifdef CONFIG_HISAX_HFCS +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_TELES3C +#define DEFAULT_CFG {5,0x500,0,0} +#endif + +#ifdef CONFIG_HISAX_HFC_PCI +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_HFC_PCI +#define DEFAULT_CFG {0,0,0,0} +#endif + +#ifdef CONFIG_HISAX_HFC_SX +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_HFC_SX +#define DEFAULT_CFG {5,0x2E0,0,0} +#endif + + +#ifdef CONFIG_HISAX_AMD7930 +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_AMD7930 +#define DEFAULT_CFG {12,0x3e0,0,0} +#endif + +#ifdef CONFIG_HISAX_NICCY +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_NICCY +#define DEFAULT_CFG {0,0x0,0,0} +#endif + +#ifdef CONFIG_HISAX_ISURF +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_ISURF +#define DEFAULT_CFG {5,0x100,0xc8000,0} +#endif + +#ifdef CONFIG_HISAX_HSTSAPHIR +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_HSTSAPHIR +#define DEFAULT_CFG {5,0x250,0,0} +#endif + +#ifdef CONFIG_HISAX_BKM_A4T +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_BKM_A4T +#define DEFAULT_CFG {0,0x0,0,0} +#endif + +#ifdef CONFIG_HISAX_SCT_QUADRO +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_SCT_QUADRO +#define DEFAULT_CFG {1,0x0,0,0} +#endif + +#ifdef CONFIG_HISAX_GAZEL +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_GAZEL +#define DEFAULT_CFG {15,0x180,0,0} +#endif + +#ifdef CONFIG_HISAX_W6692 +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_W6692 +#define DEFAULT_CFG {0,0,0,0} +#endif + +#ifdef CONFIG_HISAX_NETJET_U +#undef DEFAULT_CARD +#undef DEFAULT_CFG +#define DEFAULT_CARD ISDN_CTYPE_NETJET_U +#define DEFAULT_CFG {0,0,0,0} +#endif + +#ifdef CONFIG_HISAX_1TR6 +#define DEFAULT_PROTO ISDN_PTYPE_1TR6 +#define DEFAULT_PROTO_NAME "1TR6" +#endif +#ifdef CONFIG_HISAX_NI1 +#undef DEFAULT_PROTO +#define DEFAULT_PROTO ISDN_PTYPE_NI1 +#undef DEFAULT_PROTO_NAME +#define DEFAULT_PROTO_NAME "NI1" +#endif +#ifdef CONFIG_HISAX_EURO +#undef DEFAULT_PROTO +#define DEFAULT_PROTO ISDN_PTYPE_EURO +#undef DEFAULT_PROTO_NAME +#define DEFAULT_PROTO_NAME "EURO" +#endif +#ifndef DEFAULT_PROTO +#define DEFAULT_PROTO ISDN_PTYPE_UNKNOWN +#define DEFAULT_PROTO_NAME "UNKNOWN" +#endif +#ifndef DEFAULT_CARD +#define DEFAULT_CARD 0 +#define DEFAULT_CFG {0,0,0,0} +#endif + +#define FIRST_CARD { \ + DEFAULT_CARD, \ + DEFAULT_PROTO, \ + DEFAULT_CFG, \ + NULL, \ +} + +struct IsdnCard cards[HISAX_MAX_CARDS] = { + FIRST_CARD, +}; + +#define HISAX_IDSIZE (HISAX_MAX_CARDS*8) +static char HiSaxID[HISAX_IDSIZE] = { 0, }; + +char *HiSax_id = HiSaxID; +#ifdef MODULE +/* Variables for insmod */ +static int type[HISAX_MAX_CARDS] = { 0, }; +static int protocol[HISAX_MAX_CARDS] = { 0, }; +static int io[HISAX_MAX_CARDS] = { 0, }; +#undef IO0_IO1 +#ifdef CONFIG_HISAX_16_3 +#define IO0_IO1 +#endif +#ifdef CONFIG_HISAX_NICCY +#undef IO0_IO1 +#define IO0_IO1 +#endif +#ifdef IO0_IO1 +static int io0[HISAX_MAX_CARDS] __devinitdata = { 0, }; +static int io1[HISAX_MAX_CARDS] __devinitdata = { 0, }; +#endif +static int irq[HISAX_MAX_CARDS] __devinitdata = { 0, }; +static int mem[HISAX_MAX_CARDS] __devinitdata = { 0, }; +static char *id = HiSaxID; + +MODULE_DESCRIPTION("ISDN4Linux: Driver for passive ISDN cards"); +MODULE_AUTHOR("Karsten Keil"); +MODULE_LICENSE("GPL"); +module_param_array(type, int, NULL, 0); +module_param_array(protocol, int, NULL, 0); +module_param_array(io, int, NULL, 0); +module_param_array(irq, int, NULL, 0); +module_param_array(mem, int, NULL, 0); +module_param(id, charp, 0); +#ifdef IO0_IO1 +module_param_array(io0, int, NULL, 0); +module_param_array(io1, int, NULL, 0); +#endif +#endif /* MODULE */ + +int nrcards; + +extern char *l1_revision; +extern char *l2_revision; +extern char *l3_revision; +extern char *lli_revision; +extern char *tei_revision; + +char *HiSax_getrev(const char *revision) +{ + char *rev; + char *p; + + if ((p = strchr(revision, ':'))) { + rev = p + 2; + p = strchr(rev, '$'); + *--p = 0; + } else + rev = "???"; + return rev; +} + +void __init HiSaxVersion(void) +{ + char tmp[64]; + + printk(KERN_INFO "HiSax: Linux Driver for passive ISDN cards\n"); +#ifdef MODULE + printk(KERN_INFO "HiSax: Version 3.5 (module)\n"); +#else + printk(KERN_INFO "HiSax: Version 3.5 (kernel)\n"); +#endif + strcpy(tmp, l1_revision); + printk(KERN_INFO "HiSax: Layer1 Revision %s\n", HiSax_getrev(tmp)); + strcpy(tmp, l2_revision); + printk(KERN_INFO "HiSax: Layer2 Revision %s\n", HiSax_getrev(tmp)); + strcpy(tmp, tei_revision); + printk(KERN_INFO "HiSax: TeiMgr Revision %s\n", HiSax_getrev(tmp)); + strcpy(tmp, l3_revision); + printk(KERN_INFO "HiSax: Layer3 Revision %s\n", HiSax_getrev(tmp)); + strcpy(tmp, lli_revision); + printk(KERN_INFO "HiSax: LinkLayer Revision %s\n", + HiSax_getrev(tmp)); +} + +#ifndef MODULE +#define MAX_ARG (HISAX_MAX_CARDS*5) +static int __init HiSax_setup(char *line) +{ + int i, j, argc; + int ints[MAX_ARG + 1]; + char *str; + + str = get_options(line, MAX_ARG, ints); + argc = ints[0]; + printk(KERN_DEBUG "HiSax_setup: argc(%d) str(%s)\n", argc, str); + i = 0; + j = 1; + while (argc && (i < HISAX_MAX_CARDS)) { + cards[i].protocol = DEFAULT_PROTO; + if (argc) { + cards[i].typ = ints[j]; + j++; + argc--; + } + if (argc) { + cards[i].protocol = ints[j]; + j++; + argc--; + } + if (argc) { + cards[i].para[0] = ints[j]; + j++; + argc--; + } + if (argc) { + cards[i].para[1] = ints[j]; + j++; + argc--; + } + if (argc) { + cards[i].para[2] = ints[j]; + j++; + argc--; + } + i++; + } + if (str && *str) { + if (strlen(str) < HISAX_IDSIZE) + strcpy(HiSaxID, str); + else + printk(KERN_WARNING "HiSax: ID too long!"); + } else + strcpy(HiSaxID, "HiSax"); + + HiSax_id = HiSaxID; + return 1; +} + +__setup("hisax=", HiSax_setup); +#endif /* MODULES */ + +#if CARD_TELES0 +extern int setup_teles0(struct IsdnCard *card); +#endif + +#if CARD_TELES3 +extern int setup_teles3(struct IsdnCard *card); +#endif + +#if CARD_S0BOX +extern int setup_s0box(struct IsdnCard *card); +#endif + +#if CARD_TELESPCI +extern int setup_telespci(struct IsdnCard *card); +#endif + +#if CARD_AVM_A1 +extern int setup_avm_a1(struct IsdnCard *card); +#endif + +#if CARD_AVM_A1_PCMCIA +extern int setup_avm_a1_pcmcia(struct IsdnCard *card); +#endif + +#if CARD_FRITZPCI +extern int setup_avm_pcipnp(struct IsdnCard *card); +#endif + +#if CARD_ELSA +extern int setup_elsa(struct IsdnCard *card); +#endif + +#if CARD_IX1MICROR2 +extern int setup_ix1micro(struct IsdnCard *card); +#endif + +#if CARD_DIEHLDIVA +extern int setup_diva(struct IsdnCard *card); +#endif + +#if CARD_ASUSCOM +extern int setup_asuscom(struct IsdnCard *card); +#endif + +#if CARD_TELEINT +extern int setup_TeleInt(struct IsdnCard *card); +#endif + +#if CARD_SEDLBAUER +extern int setup_sedlbauer(struct IsdnCard *card); +#endif + +#if CARD_SPORTSTER +extern int setup_sportster(struct IsdnCard *card); +#endif + +#if CARD_MIC +extern int setup_mic(struct IsdnCard *card); +#endif + +#if CARD_NETJET_S +extern int setup_netjet_s(struct IsdnCard *card); +#endif + +#if CARD_HFCS +extern int setup_hfcs(struct IsdnCard *card); +#endif + +#if CARD_HFC_PCI +extern int setup_hfcpci(struct IsdnCard *card); +#endif + +#if CARD_HFC_SX +extern int setup_hfcsx(struct IsdnCard *card); +#endif + +#if CARD_AMD7930 +extern int setup_amd7930(struct IsdnCard *card); +#endif + +#if CARD_NICCY +extern int setup_niccy(struct IsdnCard *card); +#endif + +#if CARD_ISURF +extern int setup_isurf(struct IsdnCard *card); +#endif + +#if CARD_HSTSAPHIR +extern int setup_saphir(struct IsdnCard *card); +#endif + +#if CARD_TESTEMU +extern int setup_testemu(struct IsdnCard *card); +#endif + +#if CARD_BKM_A4T +extern int setup_bkm_a4t(struct IsdnCard *card); +#endif + +#if CARD_SCT_QUADRO +extern int setup_sct_quadro(struct IsdnCard *card); +#endif + +#if CARD_GAZEL +extern int setup_gazel(struct IsdnCard *card); +#endif + +#if CARD_W6692 +extern int setup_w6692(struct IsdnCard *card); +#endif + +#if CARD_NETJET_U +extern int setup_netjet_u(struct IsdnCard *card); +#endif + +#if CARD_FN_ENTERNOW_PCI +extern int setup_enternow_pci(struct IsdnCard *card); +#endif + +/* + * Find card with given driverId + */ +static inline struct IsdnCardState *hisax_findcard(int driverid) +{ + int i; + + for (i = 0; i < nrcards; i++) + if (cards[i].cs) + if (cards[i].cs->myid == driverid) + return cards[i].cs; + return NULL; +} + +/* + * Find card with given card number + */ +struct IsdnCardState *hisax_get_card(int cardnr) +{ + if ((cardnr <= nrcards) && (cardnr > 0)) + if (cards[cardnr - 1].cs) + return cards[cardnr - 1].cs; + return NULL; +} + +int HiSax_readstatus(u_char __user *buf, int len, int id, int channel) +{ + int count, cnt; + u_char __user *p = buf; + struct IsdnCardState *cs = hisax_findcard(id); + + if (cs) { + if (len > HISAX_STATUS_BUFSIZE) { + printk(KERN_WARNING + "HiSax: status overflow readstat %d/%d\n", + len, HISAX_STATUS_BUFSIZE); + } + count = cs->status_end - cs->status_read + 1; + if (count >= len) + count = len; + copy_to_user(p, cs->status_read, count); + cs->status_read += count; + if (cs->status_read > cs->status_end) + cs->status_read = cs->status_buf; + p += count; + count = len - count; + while (count) { + if (count > HISAX_STATUS_BUFSIZE) + cnt = HISAX_STATUS_BUFSIZE; + else + cnt = count; + copy_to_user(p, cs->status_read, cnt); + p += cnt; + cs->status_read += cnt % HISAX_STATUS_BUFSIZE; + count -= cnt; + } + return len; + } else { + printk(KERN_ERR + "HiSax: if_readstatus called with invalid driverId!\n"); + return -ENODEV; + } +} + +int 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'; + return 8; +} + +static u_char tmpbuf[HISAX_STATUS_BUFSIZE]; + +void VHiSax_putstatus(struct IsdnCardState *cs, char *head, char *fmt, + va_list args) +{ + /* if head == NULL the fmt contains the full info */ + + u_long flags; + int count, i; + u_char *p; + isdn_ctrl ic; + int len; + + if (!cs) { + printk(KERN_WARNING "HiSax: No CardStatus for message"); + return; + } + spin_lock_irqsave(&cs->statlock, flags); + p = tmpbuf; + if (head) { + p += jiftime(p, jiffies); + p += sprintf(p, " %s", head); + p += vsprintf(p, fmt, args); + *p++ = '\n'; + *p = 0; + len = p - tmpbuf; + p = tmpbuf; + } else { + p = fmt; + len = strlen(fmt); + } + if (len > HISAX_STATUS_BUFSIZE) { + spin_unlock_irqrestore(&cs->statlock, flags); + printk(KERN_WARNING "HiSax: status overflow %d/%d\n", + len, HISAX_STATUS_BUFSIZE); + return; + } + count = len; + i = cs->status_end - cs->status_write + 1; + if (i >= len) + i = len; + len -= i; + memcpy(cs->status_write, p, i); + cs->status_write += i; + if (cs->status_write > cs->status_end) + cs->status_write = cs->status_buf; + p += i; + if (len) { + memcpy(cs->status_write, p, len); + cs->status_write += len; + } +#ifdef KERNELSTACK_DEBUG + i = (ulong) & len - current->kernel_stack_page; + sprintf(tmpbuf, "kstack %s %lx use %ld\n", current->comm, + current->kernel_stack_page, i); + len = strlen(tmpbuf); + for (p = tmpbuf, i = len; i > 0; i--, p++) { + *cs->status_write++ = *p; + if (cs->status_write > cs->status_end) + cs->status_write = cs->status_buf; + count++; + } +#endif + spin_unlock_irqrestore(&cs->statlock, flags); + if (count) { + ic.command = ISDN_STAT_STAVAIL; + ic.driver = cs->myid; + ic.arg = count; + cs->iif.statcallb(&ic); + } +} + +void HiSax_putstatus(struct IsdnCardState *cs, char *head, char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + VHiSax_putstatus(cs, head, fmt, args); + va_end(args); +} + +int ll_run(struct IsdnCardState *cs, int addfeatures) +{ + isdn_ctrl ic; + + ic.driver = cs->myid; + ic.command = ISDN_STAT_RUN; + cs->iif.features |= addfeatures; + cs->iif.statcallb(&ic); + return 0; +} + +void ll_stop(struct IsdnCardState *cs) +{ + isdn_ctrl ic; + + ic.command = ISDN_STAT_STOP; + ic.driver = cs->myid; + cs->iif.statcallb(&ic); + // CallcFreeChan(cs); +} + +static void ll_unload(struct IsdnCardState *cs) +{ + isdn_ctrl ic; + + ic.command = ISDN_STAT_UNLOAD; + ic.driver = cs->myid; + cs->iif.statcallb(&ic); + if (cs->status_buf) + kfree(cs->status_buf); + cs->status_read = NULL; + cs->status_write = NULL; + cs->status_end = NULL; + kfree(cs->dlog); + cs->dlog = NULL; +} + +static void closecard(int cardnr) +{ + struct IsdnCardState *csta = cards[cardnr].cs; + + if (csta->bcs->BC_Close != NULL) { + csta->bcs->BC_Close(csta->bcs + 1); + csta->bcs->BC_Close(csta->bcs); + } + + skb_queue_purge(&csta->rq); + skb_queue_purge(&csta->sq); + if (csta->rcvbuf) { + kfree(csta->rcvbuf); + csta->rcvbuf = NULL; + } + if (csta->tx_skb) { + dev_kfree_skb(csta->tx_skb); + csta->tx_skb = NULL; + } + if (csta->DC_Close != NULL) { + csta->DC_Close(csta); + } + if (csta->cardmsg) + csta->cardmsg(csta, CARD_RELEASE, NULL); + if (csta->dbusytimer.function != NULL) // FIXME? + del_timer(&csta->dbusytimer); + ll_unload(csta); +} + +static int init_card(struct IsdnCardState *cs) +{ + int irq_cnt, cnt = 3, ret; + + if (!cs->irq) { + ret = cs->cardmsg(cs, CARD_INIT, NULL); + return(ret); + } + irq_cnt = kstat_irqs(cs->irq); + printk(KERN_INFO "%s: IRQ %d count %d\n", CardType[cs->typ], + cs->irq, irq_cnt); + if (request_irq(cs->irq, cs->irq_func, cs->irq_flags, "HiSax", cs)) { + printk(KERN_WARNING "HiSax: couldn't get interrupt %d\n", + cs->irq); + return 1; + } + while (cnt) { + cs->cardmsg(cs, CARD_INIT, NULL); + /* Timeout 10ms */ + msleep(10); + printk(KERN_INFO "%s: IRQ %d count %d\n", + CardType[cs->typ], cs->irq, kstat_irqs(cs->irq)); + if (kstat_irqs(cs->irq) == irq_cnt) { + printk(KERN_WARNING + "%s: IRQ(%d) getting no interrupts during init %d\n", + CardType[cs->typ], cs->irq, 4 - cnt); + if (cnt == 1) { + free_irq(cs->irq, cs); + return 2; + } else { + cs->cardmsg(cs, CARD_RESET, NULL); + cnt--; + } + } else { + cs->cardmsg(cs, CARD_TEST, NULL); + return 0; + } + } + return 3; +} + +static int checkcard(int cardnr, char *id, int *busy_flag, struct module *lockowner) +{ + int ret = 0; + struct IsdnCard *card = cards + cardnr; + struct IsdnCardState *cs; + + cs = kmalloc(sizeof(struct IsdnCardState), GFP_ATOMIC); + if (!cs) { + printk(KERN_WARNING + "HiSax: No memory for IsdnCardState(card %d)\n", + cardnr + 1); + goto out; + } + memset(cs, 0, sizeof(struct IsdnCardState)); + card->cs = cs; + spin_lock_init(&cs->statlock); + spin_lock_init(&cs->lock); + cs->chanlimit = 2; /* maximum B-channel number */ + cs->logecho = 0; /* No echo logging */ + cs->cardnr = cardnr; + cs->debug = L1_DEB_WARN; + cs->HW_Flags = 0; + cs->busy_flag = busy_flag; + cs->irq_flags = I4L_IRQ_FLAG; +#if TEI_PER_CARD + if (card->protocol == ISDN_PTYPE_NI1) + test_and_set_bit(FLG_TWO_DCHAN, &cs->HW_Flags); +#else + test_and_set_bit(FLG_TWO_DCHAN, &cs->HW_Flags); +#endif + cs->protocol = card->protocol; + + if (card->typ <= 0 || card->typ > ISDN_CTYPE_COUNT) { + printk(KERN_WARNING + "HiSax: Card Type %d out of range\n", card->typ); + goto outf_cs; + } + if (!(cs->dlog = kmalloc(MAX_DLOG_SPACE, GFP_ATOMIC))) { + printk(KERN_WARNING + "HiSax: No memory for dlog(card %d)\n", cardnr + 1); + goto outf_cs; + } + if (!(cs->status_buf = kmalloc(HISAX_STATUS_BUFSIZE, GFP_ATOMIC))) { + printk(KERN_WARNING + "HiSax: No memory for status_buf(card %d)\n", + cardnr + 1); + goto outf_dlog; + } + cs->stlist = NULL; + cs->status_read = cs->status_buf; + cs->status_write = cs->status_buf; + cs->status_end = cs->status_buf + HISAX_STATUS_BUFSIZE - 1; + cs->typ = card->typ; +#ifdef MODULE + cs->iif.owner = lockowner; +#endif + strcpy(cs->iif.id, id); + cs->iif.channels = 2; + cs->iif.maxbufsize = MAX_DATA_SIZE; + cs->iif.hl_hdrlen = MAX_HEADER_LEN; + cs->iif.features = + ISDN_FEATURE_L2_X75I | + ISDN_FEATURE_L2_HDLC | + ISDN_FEATURE_L2_HDLC_56K | + ISDN_FEATURE_L2_TRANS | + ISDN_FEATURE_L3_TRANS | +#ifdef CONFIG_HISAX_1TR6 + ISDN_FEATURE_P_1TR6 | +#endif +#ifdef CONFIG_HISAX_EURO + ISDN_FEATURE_P_EURO | +#endif +#ifdef CONFIG_HISAX_NI1 + ISDN_FEATURE_P_NI1 | +#endif + 0; + + cs->iif.command = HiSax_command; + cs->iif.writecmd = NULL; + cs->iif.writebuf_skb = HiSax_writebuf_skb; + cs->iif.readstat = HiSax_readstatus; + register_isdn(&cs->iif); + cs->myid = cs->iif.channels; + printk(KERN_INFO + "HiSax: Card %d Protocol %s Id=%s (%d)\n", cardnr + 1, + (card->protocol == ISDN_PTYPE_1TR6) ? "1TR6" : + (card->protocol == ISDN_PTYPE_EURO) ? "EDSS1" : + (card->protocol == ISDN_PTYPE_LEASED) ? "LEASED" : + (card->protocol == ISDN_PTYPE_NI1) ? "NI1" : + "NONE", cs->iif.id, cs->myid); + switch (card->typ) { +#if CARD_TELES0 + case ISDN_CTYPE_16_0: + case ISDN_CTYPE_8_0: + ret = setup_teles0(card); + break; +#endif +#if CARD_TELES3 + case ISDN_CTYPE_16_3: + case ISDN_CTYPE_PNP: + case ISDN_CTYPE_TELESPCMCIA: + case ISDN_CTYPE_COMPAQ_ISA: + ret = setup_teles3(card); + break; +#endif +#if CARD_S0BOX + case ISDN_CTYPE_S0BOX: + ret = setup_s0box(card); + break; +#endif +#if CARD_TELESPCI + case ISDN_CTYPE_TELESPCI: + ret = setup_telespci(card); + break; +#endif +#if CARD_AVM_A1 + case ISDN_CTYPE_A1: + ret = setup_avm_a1(card); + break; +#endif +#if CARD_AVM_A1_PCMCIA + case ISDN_CTYPE_A1_PCMCIA: + ret = setup_avm_a1_pcmcia(card); + break; +#endif +#if CARD_FRITZPCI + case ISDN_CTYPE_FRITZPCI: + ret = setup_avm_pcipnp(card); + break; +#endif +#if CARD_ELSA + case ISDN_CTYPE_ELSA: + case ISDN_CTYPE_ELSA_PNP: + case ISDN_CTYPE_ELSA_PCMCIA: + case ISDN_CTYPE_ELSA_PCI: + ret = setup_elsa(card); + break; +#endif +#if CARD_IX1MICROR2 + case ISDN_CTYPE_IX1MICROR2: + ret = setup_ix1micro(card); + break; +#endif +#if CARD_DIEHLDIVA + case ISDN_CTYPE_DIEHLDIVA: + ret = setup_diva(card); + break; +#endif +#if CARD_ASUSCOM + case ISDN_CTYPE_ASUSCOM: + ret = setup_asuscom(card); + break; +#endif +#if CARD_TELEINT + case ISDN_CTYPE_TELEINT: + ret = setup_TeleInt(card); + break; +#endif +#if CARD_SEDLBAUER + case ISDN_CTYPE_SEDLBAUER: + case ISDN_CTYPE_SEDLBAUER_PCMCIA: + case ISDN_CTYPE_SEDLBAUER_FAX: + ret = setup_sedlbauer(card); + break; +#endif +#if CARD_SPORTSTER + case ISDN_CTYPE_SPORTSTER: + ret = setup_sportster(card); + break; +#endif +#if CARD_MIC + case ISDN_CTYPE_MIC: + ret = setup_mic(card); + break; +#endif +#if CARD_NETJET_S + case ISDN_CTYPE_NETJET_S: + ret = setup_netjet_s(card); + break; +#endif +#if CARD_HFCS + case ISDN_CTYPE_TELES3C: + case ISDN_CTYPE_ACERP10: + ret = setup_hfcs(card); + break; +#endif +#if CARD_HFC_PCI + case ISDN_CTYPE_HFC_PCI: + ret = setup_hfcpci(card); + break; +#endif +#if CARD_HFC_SX + case ISDN_CTYPE_HFC_SX: + ret = setup_hfcsx(card); + break; +#endif +#if CARD_NICCY + case ISDN_CTYPE_NICCY: + ret = setup_niccy(card); + break; +#endif +#if CARD_AMD7930 + case ISDN_CTYPE_AMD7930: + ret = setup_amd7930(card); + break; +#endif +#if CARD_ISURF + case ISDN_CTYPE_ISURF: + ret = setup_isurf(card); + break; +#endif +#if CARD_HSTSAPHIR + case ISDN_CTYPE_HSTSAPHIR: + ret = setup_saphir(card); + break; +#endif +#if CARD_TESTEMU + case ISDN_CTYPE_TESTEMU: + ret = setup_testemu(card); + break; +#endif +#if CARD_BKM_A4T + case ISDN_CTYPE_BKM_A4T: + ret = setup_bkm_a4t(card); + break; +#endif +#if CARD_SCT_QUADRO + case ISDN_CTYPE_SCT_QUADRO: + ret = setup_sct_quadro(card); + break; +#endif +#if CARD_GAZEL + case ISDN_CTYPE_GAZEL: + ret = setup_gazel(card); + break; +#endif +#if CARD_W6692 + case ISDN_CTYPE_W6692: + ret = setup_w6692(card); + break; +#endif +#if CARD_NETJET_U + case ISDN_CTYPE_NETJET_U: + ret = setup_netjet_u(card); + break; +#endif +#if CARD_FN_ENTERNOW_PCI + case ISDN_CTYPE_ENTERNOW: + ret = setup_enternow_pci(card); + break; +#endif + case ISDN_CTYPE_DYNAMIC: + ret = 2; + break; + default: + printk(KERN_WARNING + "HiSax: Support for %s Card not selected\n", + CardType[card->typ]); + ll_unload(cs); + goto outf_cs; + } + if (!ret) { + ll_unload(cs); + goto outf_cs; + } + if (!(cs->rcvbuf = kmalloc(MAX_DFRAME_LEN_L1, GFP_ATOMIC))) { + printk(KERN_WARNING "HiSax: No memory for isac rcvbuf\n"); + ll_unload(cs); + goto outf_cs; + } + cs->rcvidx = 0; + cs->tx_skb = NULL; + cs->tx_cnt = 0; + cs->event = 0; + cs->tqueue.data = cs; + + skb_queue_head_init(&cs->rq); + skb_queue_head_init(&cs->sq); + + init_bcstate(cs, 0); + init_bcstate(cs, 1); + + /* init_card only handles interrupts which are not */ + /* used here for the loadable driver */ + switch (card->typ) { + case ISDN_CTYPE_DYNAMIC: + ret = 0; + break; + default: + ret = init_card(cs); + break; + } + if (ret) { + closecard(cardnr); + ret = 0; + goto outf_cs; + } + init_tei(cs, cs->protocol); + ret = CallcNewChan(cs); + if (ret) { + closecard(cardnr); + ret = 0; + goto outf_cs; + } + /* ISAR needs firmware download first */ + if (!test_bit(HW_ISAR, &cs->HW_Flags)) + ll_run(cs, 0); + + ret = 1; + goto out; + + outf_dlog: + kfree(cs->dlog); + outf_cs: + kfree(cs); + card->cs = NULL; + out: + return ret; +} + +void HiSax_shiftcards(int idx) +{ + int i; + + for (i = idx; i < (HISAX_MAX_CARDS - 1); i++) + memcpy(&cards[i], &cards[i + 1], sizeof(cards[i])); +} + +int HiSax_inithardware(int *busy_flag) +{ + int foundcards = 0; + int i = 0; + int t = ','; + int flg = 0; + char *id; + char *next_id = HiSax_id; + char ids[20]; + + if (strchr(HiSax_id, ',')) + t = ','; + else if (strchr(HiSax_id, '%')) + t = '%'; + + while (i < nrcards) { + if (cards[i].typ < 1) + break; + id = next_id; + if ((next_id = strchr(id, t))) { + *next_id++ = 0; + strcpy(ids, id); + flg = i + 1; + } else { + next_id = id; + if (flg >= i) + strcpy(ids, id); + else + sprintf(ids, "%s%d", id, i); + } + if (checkcard(i, ids, busy_flag, THIS_MODULE)) { + foundcards++; + i++; + } else { + /* make sure we don't oops the module */ + if (cards[i].typ > 0 && cards[i].typ <= ISDN_CTYPE_COUNT) { + printk(KERN_WARNING + "HiSax: Card %s not installed !\n", + CardType[cards[i].typ]); + } + HiSax_shiftcards(i); + nrcards--; + } + } + return foundcards; +} + +void HiSax_closecard(int cardnr) +{ + int i, last = nrcards - 1; + + if (cardnr > last || cardnr < 0) + return; + if (cards[cardnr].cs) { + ll_stop(cards[cardnr].cs); + release_tei(cards[cardnr].cs); + CallcFreeChan(cards[cardnr].cs); + + closecard(cardnr); + if (cards[cardnr].cs->irq) + free_irq(cards[cardnr].cs->irq, cards[cardnr].cs); + kfree((void *) cards[cardnr].cs); + cards[cardnr].cs = NULL; + } + i = cardnr; + while (i <= last) { + cards[i] = cards[i + 1]; + i++; + } + nrcards--; +} + +void HiSax_reportcard(int cardnr, int sel) +{ + struct IsdnCardState *cs = cards[cardnr].cs; + + printk(KERN_DEBUG "HiSax: reportcard No %d\n", cardnr + 1); + printk(KERN_DEBUG "HiSax: Type %s\n", CardType[cs->typ]); + printk(KERN_DEBUG "HiSax: debuglevel %x\n", cs->debug); + printk(KERN_DEBUG "HiSax: HiSax_reportcard address 0x%lX\n", + (ulong) & HiSax_reportcard); + printk(KERN_DEBUG "HiSax: cs 0x%lX\n", (ulong) cs); + printk(KERN_DEBUG "HiSax: HW_Flags %lx bc0 flg %lx bc1 flg %lx\n", + cs->HW_Flags, cs->bcs[0].Flag, cs->bcs[1].Flag); + printk(KERN_DEBUG "HiSax: bcs 0 mode %d ch%d\n", + cs->bcs[0].mode, cs->bcs[0].channel); + printk(KERN_DEBUG "HiSax: bcs 1 mode %d ch%d\n", + cs->bcs[1].mode, cs->bcs[1].channel); +#ifdef ERROR_STATISTIC + printk(KERN_DEBUG "HiSax: dc errors(rx,crc,tx) %d,%d,%d\n", + cs->err_rx, cs->err_crc, cs->err_tx); + printk(KERN_DEBUG + "HiSax: bc0 errors(inv,rdo,crc,tx) %d,%d,%d,%d\n", + cs->bcs[0].err_inv, cs->bcs[0].err_rdo, cs->bcs[0].err_crc, + cs->bcs[0].err_tx); + printk(KERN_DEBUG + "HiSax: bc1 errors(inv,rdo,crc,tx) %d,%d,%d,%d\n", + cs->bcs[1].err_inv, cs->bcs[1].err_rdo, cs->bcs[1].err_crc, + cs->bcs[1].err_tx); + if (sel == 99) { + cs->err_rx = 0; + cs->err_crc = 0; + cs->err_tx = 0; + cs->bcs[0].err_inv = 0; + cs->bcs[0].err_rdo = 0; + cs->bcs[0].err_crc = 0; + cs->bcs[0].err_tx = 0; + cs->bcs[1].err_inv = 0; + cs->bcs[1].err_rdo = 0; + cs->bcs[1].err_crc = 0; + cs->bcs[1].err_tx = 0; + } +#endif +} + +static int __init HiSax_init(void) +{ + int i, retval; +#ifdef MODULE + int j; + int nzproto = 0; +#endif + + HiSaxVersion(); + retval = CallcNew(); + if (retval) + goto out; + retval = Isdnl3New(); + if (retval) + goto out_callc; + retval = Isdnl2New(); + if (retval) + goto out_isdnl3; + retval = TeiNew(); + if (retval) + goto out_isdnl2; + retval = Isdnl1New(); + if (retval) + goto out_tei; + +#ifdef MODULE + if (!type[0]) { + /* We 'll register drivers later, but init basic functions */ + for (i = 0; i < HISAX_MAX_CARDS; i++) + cards[i].typ = 0; + return 0; + } +#ifdef CONFIG_HISAX_ELSA + if (type[0] == ISDN_CTYPE_ELSA_PCMCIA) { + /* we have exported and return in this case */ + return 0; + } +#endif +#ifdef CONFIG_HISAX_SEDLBAUER + if (type[0] == ISDN_CTYPE_SEDLBAUER_PCMCIA) { + /* we have to export and return in this case */ + return 0; + } +#endif +#ifdef CONFIG_HISAX_AVM_A1_PCMCIA + if (type[0] == ISDN_CTYPE_A1_PCMCIA) { + /* we have to export and return in this case */ + return 0; + } +#endif +#ifdef CONFIG_HISAX_HFC_SX + if (type[0] == ISDN_CTYPE_HFC_SP_PCMCIA) { + /* we have to export and return in this case */ + return 0; + } +#endif +#endif + nrcards = 0; +#ifdef MODULE + if (id) /* If id= string used */ + HiSax_id = id; + for (i = j = 0; j < HISAX_MAX_CARDS; i++) { + cards[j].typ = type[i]; + if (protocol[i]) { + cards[j].protocol = protocol[i]; + nzproto++; + } else { + cards[j].protocol = DEFAULT_PROTO; + } + switch (type[i]) { + case ISDN_CTYPE_16_0: + cards[j].para[0] = irq[i]; + cards[j].para[1] = mem[i]; + cards[j].para[2] = io[i]; + break; + + case ISDN_CTYPE_8_0: + cards[j].para[0] = irq[i]; + cards[j].para[1] = mem[i]; + break; + +#ifdef IO0_IO1 + case ISDN_CTYPE_PNP: + case ISDN_CTYPE_NICCY: + cards[j].para[0] = irq[i]; + cards[j].para[1] = io0[i]; + cards[j].para[2] = io1[i]; + break; + case ISDN_CTYPE_COMPAQ_ISA: + cards[j].para[0] = irq[i]; + cards[j].para[1] = io0[i]; + cards[j].para[2] = io1[i]; + cards[j].para[3] = io[i]; + break; +#endif + case ISDN_CTYPE_ELSA: + case ISDN_CTYPE_HFC_PCI: + cards[j].para[0] = io[i]; + break; + case ISDN_CTYPE_16_3: + case ISDN_CTYPE_TELESPCMCIA: + case ISDN_CTYPE_A1: + case ISDN_CTYPE_A1_PCMCIA: + case ISDN_CTYPE_ELSA_PNP: + case ISDN_CTYPE_ELSA_PCMCIA: + case ISDN_CTYPE_IX1MICROR2: + case ISDN_CTYPE_DIEHLDIVA: + case ISDN_CTYPE_ASUSCOM: + case ISDN_CTYPE_TELEINT: + case ISDN_CTYPE_SEDLBAUER: + case ISDN_CTYPE_SEDLBAUER_PCMCIA: + case ISDN_CTYPE_SEDLBAUER_FAX: + case ISDN_CTYPE_SPORTSTER: + case ISDN_CTYPE_MIC: + case ISDN_CTYPE_TELES3C: + case ISDN_CTYPE_ACERP10: + case ISDN_CTYPE_S0BOX: + case ISDN_CTYPE_FRITZPCI: + case ISDN_CTYPE_HSTSAPHIR: + case ISDN_CTYPE_GAZEL: + case ISDN_CTYPE_HFC_SX: + case ISDN_CTYPE_HFC_SP_PCMCIA: + cards[j].para[0] = irq[i]; + cards[j].para[1] = io[i]; + break; + case ISDN_CTYPE_ISURF: + cards[j].para[0] = irq[i]; + cards[j].para[1] = io[i]; + cards[j].para[2] = mem[i]; + break; + case ISDN_CTYPE_ELSA_PCI: + case ISDN_CTYPE_NETJET_S: + case ISDN_CTYPE_AMD7930: + case ISDN_CTYPE_TELESPCI: + case ISDN_CTYPE_W6692: + case ISDN_CTYPE_NETJET_U: + break; + case ISDN_CTYPE_BKM_A4T: + break; + case ISDN_CTYPE_SCT_QUADRO: + if (irq[i]) { + cards[j].para[0] = irq[i]; + } else { + /* QUADRO is a 4 BRI card */ + cards[j++].para[0] = 1; + /* we need to check if further cards can be added */ + if (j < HISAX_MAX_CARDS) { + cards[j].typ = ISDN_CTYPE_SCT_QUADRO; + cards[j].protocol = protocol[i]; + cards[j++].para[0] = 2; + } + if (j < HISAX_MAX_CARDS) { + cards[j].typ = ISDN_CTYPE_SCT_QUADRO; + cards[j].protocol = protocol[i]; + cards[j++].para[0] = 3; + } + if (j < HISAX_MAX_CARDS) { + cards[j].typ = ISDN_CTYPE_SCT_QUADRO; + cards[j].protocol = protocol[i]; + cards[j].para[0] = 4; + } + } + break; + } + j++; + } + if (!nzproto) { + printk(KERN_WARNING + "HiSax: Warning - no protocol specified\n"); + printk(KERN_WARNING "HiSax: using protocol %s\n", + DEFAULT_PROTO_NAME); + } +#endif + if (!HiSax_id) + HiSax_id = HiSaxID; + if (!HiSaxID[0]) + strcpy(HiSaxID, "HiSax"); + for (i = 0; i < HISAX_MAX_CARDS; i++) + if (cards[i].typ > 0) + nrcards++; + printk(KERN_DEBUG "HiSax: Total %d card%s defined\n", + nrcards, (nrcards > 1) ? "s" : ""); + + /* Install only, if at least one card found */ + if (!HiSax_inithardware(NULL)) + return -ENODEV; + return 0; + + out_tei: + TeiFree(); + out_isdnl2: + Isdnl2Free(); + out_isdnl3: + Isdnl3Free(); + out_callc: + CallcFree(); + out: + return retval; +} + +static void __exit HiSax_exit(void) +{ + int cardnr = nrcards - 1; + + while (cardnr >= 0) + HiSax_closecard(cardnr--); + Isdnl1Free(); + TeiFree(); + Isdnl2Free(); + Isdnl3Free(); + CallcFree(); + printk(KERN_INFO "HiSax module removed\n"); +} + +int hisax_init_pcmcia(void *pcm_iob, int *busy_flag, struct IsdnCard *card) +{ + u_char ids[16]; + int ret = -1; + + cards[nrcards] = *card; + if (nrcards) + sprintf(ids, "HiSax%d", nrcards); + else + sprintf(ids, "HiSax"); + if (!checkcard(nrcards, ids, busy_flag, THIS_MODULE)) + goto error; + + ret = nrcards; + nrcards++; +error: + return ret; +} + +EXPORT_SYMBOL(hisax_init_pcmcia); +EXPORT_SYMBOL(HiSax_closecard); + +#include "hisax_if.h" + +EXPORT_SYMBOL(hisax_register); +EXPORT_SYMBOL(hisax_unregister); + +static void hisax_d_l1l2(struct hisax_if *ifc, int pr, void *arg); +static void hisax_b_l1l2(struct hisax_if *ifc, int pr, void *arg); +static void hisax_d_l2l1(struct PStack *st, int pr, void *arg); +static void hisax_b_l2l1(struct PStack *st, int pr, void *arg); +static int hisax_cardmsg(struct IsdnCardState *cs, int mt, void *arg); +static int hisax_bc_setstack(struct PStack *st, struct BCState *bcs); +static void hisax_bc_close(struct BCState *bcs); +static void hisax_bh(struct IsdnCardState *cs); +static void EChannel_proc_rcv(struct hisax_d_if *d_if); + +int hisax_register(struct hisax_d_if *hisax_d_if, struct hisax_b_if *b_if[], + char *name, int protocol) +{ + int i, retval; + char id[20]; + struct IsdnCardState *cs; + + for (i = 0; i < HISAX_MAX_CARDS; i++) { + if (!cards[i].typ) + break; + } + + if (i >= HISAX_MAX_CARDS) + return -EBUSY; + + cards[i].typ = ISDN_CTYPE_DYNAMIC; + cards[i].protocol = protocol; + sprintf(id, "%s%d", name, i); + nrcards++; + retval = checkcard(i, id, NULL, hisax_d_if->owner); + if (retval == 0) { // yuck + cards[i].typ = 0; + nrcards--; + return retval; + } + cs = cards[i].cs; + hisax_d_if->cs = cs; + cs->hw.hisax_d_if = hisax_d_if; + cs->cardmsg = hisax_cardmsg; + INIT_WORK(&cs->tqueue, (void *)(void *)hisax_bh, cs); + cs->channel[0].d_st->l2.l2l1 = hisax_d_l2l1; + for (i = 0; i < 2; i++) { + cs->bcs[i].BC_SetStack = hisax_bc_setstack; + cs->bcs[i].BC_Close = hisax_bc_close; + + b_if[i]->ifc.l1l2 = hisax_b_l1l2; + + hisax_d_if->b_if[i] = b_if[i]; + } + hisax_d_if->ifc.l1l2 = hisax_d_l1l2; + skb_queue_head_init(&hisax_d_if->erq); + clear_bit(0, &hisax_d_if->ph_state); + + return 0; +} + +void hisax_unregister(struct hisax_d_if *hisax_d_if) +{ + cards[hisax_d_if->cs->cardnr].typ = 0; + HiSax_closecard(hisax_d_if->cs->cardnr); + skb_queue_purge(&hisax_d_if->erq); +} + +#include "isdnl1.h" + +static void hisax_sched_event(struct IsdnCardState *cs, int event) +{ + test_and_set_bit(event, &cs->event); + schedule_work(&cs->tqueue); +} + +static void hisax_bh(struct IsdnCardState *cs) +{ + struct PStack *st; + int pr; + + if (test_and_clear_bit(D_RCVBUFREADY, &cs->event)) + DChannel_proc_rcv(cs); + if (test_and_clear_bit(E_RCVBUFREADY, &cs->event)) + EChannel_proc_rcv(cs->hw.hisax_d_if); + if (test_and_clear_bit(D_L1STATECHANGE, &cs->event)) { + if (test_bit(0, &cs->hw.hisax_d_if->ph_state)) + pr = PH_ACTIVATE | INDICATION; + else + pr = PH_DEACTIVATE | INDICATION; + for (st = cs->stlist; st; st = st->next) + st->l1.l1l2(st, pr, NULL); + + } +} + +static void hisax_b_sched_event(struct BCState *bcs, int event) +{ + test_and_set_bit(event, &bcs->event); + schedule_work(&bcs->tqueue); +} + +static inline void D_L2L1(struct hisax_d_if *d_if, int pr, void *arg) +{ + struct hisax_if *ifc = (struct hisax_if *) d_if; + ifc->l2l1(ifc, pr, arg); +} + +static inline void B_L2L1(struct hisax_b_if *b_if, int pr, void *arg) +{ + struct hisax_if *ifc = (struct hisax_if *) b_if; + ifc->l2l1(ifc, pr, arg); +} + +static void hisax_d_l1l2(struct hisax_if *ifc, int pr, void *arg) +{ + struct hisax_d_if *d_if = (struct hisax_d_if *) ifc; + struct IsdnCardState *cs = d_if->cs; + struct PStack *st; + struct sk_buff *skb; + + switch (pr) { + case PH_ACTIVATE | INDICATION: + set_bit(0, &d_if->ph_state); + hisax_sched_event(cs, D_L1STATECHANGE); + break; + case PH_DEACTIVATE | INDICATION: + clear_bit(0, &d_if->ph_state); + hisax_sched_event(cs, D_L1STATECHANGE); + break; + case PH_DATA | INDICATION: + skb_queue_tail(&cs->rq, arg); + hisax_sched_event(cs, D_RCVBUFREADY); + break; + case PH_DATA | CONFIRM: + skb = skb_dequeue(&cs->sq); + if (skb) { + D_L2L1(d_if, PH_DATA | REQUEST, skb); + break; + } + clear_bit(FLG_L1_DBUSY, &cs->HW_Flags); + for (st = cs->stlist; st; st = st->next) { + if (test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags)) { + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + break; + } + } + break; + case PH_DATA_E | INDICATION: + skb_queue_tail(&d_if->erq, arg); + hisax_sched_event(cs, E_RCVBUFREADY); + break; + default: + printk("pr %#x\n", pr); + break; + } +} + +static void hisax_b_l1l2(struct hisax_if *ifc, int pr, void *arg) +{ + struct hisax_b_if *b_if = (struct hisax_b_if *) ifc; + struct BCState *bcs = b_if->bcs; + struct PStack *st = bcs->st; + struct sk_buff *skb; + + // FIXME use isdnl1? + switch (pr) { + case PH_ACTIVATE | INDICATION: + st->l1.l1l2(st, pr, NULL); + break; + case PH_DEACTIVATE | INDICATION: + st->l1.l1l2(st, pr, NULL); + clear_bit(BC_FLG_BUSY, &bcs->Flag); + skb_queue_purge(&bcs->squeue); + bcs->hw.b_if = NULL; + break; + case PH_DATA | INDICATION: + skb_queue_tail(&bcs->rqueue, arg); + hisax_b_sched_event(bcs, B_RCVBUFREADY); + break; + case PH_DATA | CONFIRM: + bcs->tx_cnt -= (int) arg; + if (test_bit(FLG_LLI_L1WAKEUP,&bcs->st->lli.flag)) { + u_long flags; + spin_lock_irqsave(&bcs->aclock, flags); + bcs->ackcnt += (int) arg; + spin_unlock_irqrestore(&bcs->aclock, flags); + schedule_event(bcs, B_ACKPENDING); + } + skb = skb_dequeue(&bcs->squeue); + if (skb) { + B_L2L1(b_if, PH_DATA | REQUEST, skb); + break; + } + clear_bit(BC_FLG_BUSY, &bcs->Flag); + if (test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags)) { + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } + break; + default: + printk("hisax_b_l1l2 pr %#x\n", pr); + break; + } +} + +static void hisax_d_l2l1(struct PStack *st, int pr, void *arg) +{ + struct IsdnCardState *cs = st->l1.hardware; + struct hisax_d_if *hisax_d_if = cs->hw.hisax_d_if; + struct sk_buff *skb = arg; + + switch (pr) { + case PH_DATA | REQUEST: + case PH_PULL | INDICATION: + if (cs->debug & DEB_DLOG_HEX) + LogFrame(cs, skb->data, skb->len); + if (cs->debug & DEB_DLOG_VERBOSE) + dlogframe(cs, skb, 0); + Logl2Frame(cs, skb, "PH_DATA_REQ", 0); + // FIXME lock? + if (!test_and_set_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + D_L2L1(hisax_d_if, PH_DATA | REQUEST, skb); + else + skb_queue_tail(&cs->sq, skb); + break; + case PH_PULL | REQUEST: + if (!test_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + else + set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + default: + D_L2L1(hisax_d_if, pr, arg); + break; + } +} + +static int hisax_cardmsg(struct IsdnCardState *cs, int mt, void *arg) +{ + return 0; +} + +static void hisax_b_l2l1(struct PStack *st, int pr, void *arg) +{ + struct BCState *bcs = st->l1.bcs; + struct hisax_b_if *b_if = bcs->hw.b_if; + + switch (pr) { + case PH_ACTIVATE | REQUEST: + B_L2L1(b_if, pr, (void *) st->l1.mode); + break; + case PH_DATA | REQUEST: + case PH_PULL | INDICATION: + // FIXME lock? + if (!test_and_set_bit(BC_FLG_BUSY, &bcs->Flag)) { + B_L2L1(b_if, PH_DATA | REQUEST, arg); + } else { + skb_queue_tail(&bcs->squeue, arg); + } + break; + case PH_PULL | REQUEST: + if (!test_bit(BC_FLG_BUSY, &bcs->Flag)) + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + else + set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + case PH_DEACTIVATE | REQUEST: + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + skb_queue_purge(&bcs->squeue); + default: + B_L2L1(b_if, pr, arg); + break; + } +} + +static int hisax_bc_setstack(struct PStack *st, struct BCState *bcs) +{ + struct IsdnCardState *cs = st->l1.hardware; + struct hisax_d_if *hisax_d_if = cs->hw.hisax_d_if; + + bcs->channel = st->l1.bc; + + bcs->hw.b_if = hisax_d_if->b_if[st->l1.bc]; + hisax_d_if->b_if[st->l1.bc]->bcs = bcs; + + st->l1.bcs = bcs; + st->l2.l2l1 = hisax_b_l2l1; + setstack_manager(st); + bcs->st = st; + setstack_l1_B(st); + skb_queue_head_init(&bcs->rqueue); + skb_queue_head_init(&bcs->squeue); + return 0; +} + +static void hisax_bc_close(struct BCState *bcs) +{ + struct hisax_b_if *b_if = bcs->hw.b_if; + + if (b_if) + B_L2L1(b_if, PH_DEACTIVATE | REQUEST, NULL); +} + +static void EChannel_proc_rcv(struct hisax_d_if *d_if) +{ + struct IsdnCardState *cs = d_if->cs; + u_char *ptr; + struct sk_buff *skb; + + while ((skb = skb_dequeue(&d_if->erq)) != NULL) { + if (cs->debug & DEB_DLOG_HEX) { + ptr = cs->dlog; + if ((skb->len) < MAX_DLOG_SPACE / 3 - 10) { + *ptr++ = 'E'; + *ptr++ = 'C'; + *ptr++ = 'H'; + *ptr++ = 'O'; + *ptr++ = ':'; + ptr += QuickHex(ptr, skb->data, skb->len); + ptr--; + *ptr++ = '\n'; + *ptr = 0; + HiSax_putstatus(cs, NULL, cs->dlog); + } else + HiSax_putstatus(cs, "LogEcho: ", + "warning Frame too big (%d)", + skb->len); + } + dev_kfree_skb_any(skb); + } +} + +#ifdef CONFIG_PCI +#include <linux/pci.h> + +static struct pci_device_id hisax_pci_tbl[] __initdata = { +#ifdef CONFIG_HISAX_FRITZPCI + {PCI_VENDOR_ID_AVM, PCI_DEVICE_ID_AVM_A1, PCI_ANY_ID, PCI_ANY_ID}, +#endif +#ifdef CONFIG_HISAX_DIEHLDIVA + {PCI_VENDOR_ID_EICON, PCI_DEVICE_ID_EICON_DIVA20, PCI_ANY_ID, PCI_ANY_ID}, + {PCI_VENDOR_ID_EICON, PCI_DEVICE_ID_EICON_DIVA20_U, PCI_ANY_ID, PCI_ANY_ID}, + {PCI_VENDOR_ID_EICON, PCI_DEVICE_ID_EICON_DIVA201, PCI_ANY_ID, PCI_ANY_ID}, +//######################################################################################### + {PCI_VENDOR_ID_EICON, PCI_DEVICE_ID_EICON_DIVA202, PCI_ANY_ID, PCI_ANY_ID}, +//######################################################################################### +#endif +#ifdef CONFIG_HISAX_ELSA + {PCI_VENDOR_ID_ELSA, PCI_DEVICE_ID_ELSA_MICROLINK, PCI_ANY_ID, PCI_ANY_ID}, + {PCI_VENDOR_ID_ELSA, PCI_DEVICE_ID_ELSA_QS3000, PCI_ANY_ID, PCI_ANY_ID}, +#endif +#ifdef CONFIG_HISAX_GAZEL + {PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_R685, PCI_ANY_ID, PCI_ANY_ID}, + {PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_R753, PCI_ANY_ID, PCI_ANY_ID}, + {PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_DJINN_ITOO, PCI_ANY_ID, PCI_ANY_ID}, +#endif +#ifdef CONFIG_HISAX_QUADRO + {PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9050, PCI_ANY_ID, PCI_ANY_ID}, +#endif +#ifdef CONFIG_HISAX_NICCY + {PCI_VENDOR_ID_SATSAGEM, PCI_DEVICE_ID_SATSAGEM_NICCY, PCI_ANY_ID,PCI_ANY_ID}, +#endif +#ifdef CONFIG_HISAX_SEDLBAUER + {PCI_VENDOR_ID_TIGERJET, PCI_DEVICE_ID_TIGERJET_100, PCI_ANY_ID,PCI_ANY_ID}, +#endif +#if defined(CONFIG_HISAX_NETJET) || defined(CONFIG_HISAX_NETJET_U) + {PCI_VENDOR_ID_TIGERJET, PCI_DEVICE_ID_TIGERJET_300, PCI_ANY_ID,PCI_ANY_ID}, +#endif +#if defined(CONFIG_HISAX_TELESPCI) || defined(CONFIG_HISAX_SCT_QUADRO) + {PCI_VENDOR_ID_ZORAN, PCI_DEVICE_ID_ZORAN_36120, PCI_ANY_ID,PCI_ANY_ID}, +#endif +#ifdef CONFIG_HISAX_W6692 + {PCI_VENDOR_ID_DYNALINK, PCI_DEVICE_ID_DYNALINK_IS64PH, PCI_ANY_ID,PCI_ANY_ID}, + {PCI_VENDOR_ID_WINBOND2, PCI_DEVICE_ID_WINBOND2_6692, PCI_ANY_ID,PCI_ANY_ID}, +#endif +#ifdef CONFIG_HISAX_HFC_PCI + {PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_2BD0, PCI_ANY_ID, PCI_ANY_ID}, + {PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_B000, PCI_ANY_ID, PCI_ANY_ID}, + {PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_B006, PCI_ANY_ID, PCI_ANY_ID}, + {PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_B007, PCI_ANY_ID, PCI_ANY_ID}, + {PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_B008, PCI_ANY_ID, PCI_ANY_ID}, + {PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_B009, PCI_ANY_ID, PCI_ANY_ID}, + {PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_B00A, PCI_ANY_ID, PCI_ANY_ID}, + {PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_B00B, PCI_ANY_ID, PCI_ANY_ID}, + {PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_B00C, PCI_ANY_ID, PCI_ANY_ID}, + {PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_B100, PCI_ANY_ID, PCI_ANY_ID}, + {PCI_VENDOR_ID_ABOCOM, PCI_DEVICE_ID_ABOCOM_2BD1, PCI_ANY_ID, PCI_ANY_ID}, + {PCI_VENDOR_ID_ASUSTEK, PCI_DEVICE_ID_ASUSTEK_0675, PCI_ANY_ID, PCI_ANY_ID}, + {PCI_VENDOR_ID_BERKOM, PCI_DEVICE_ID_BERKOM_T_CONCEPT, PCI_ANY_ID, PCI_ANY_ID}, + {PCI_VENDOR_ID_BERKOM, PCI_DEVICE_ID_BERKOM_A1T, PCI_ANY_ID, PCI_ANY_ID}, + {PCI_VENDOR_ID_ANIGMA, PCI_DEVICE_ID_ANIGMA_MC145575, PCI_ANY_ID, PCI_ANY_ID}, + {PCI_VENDOR_ID_ZOLTRIX, PCI_DEVICE_ID_ZOLTRIX_2BD0, PCI_ANY_ID, PCI_ANY_ID}, + {PCI_VENDOR_ID_DIGI, PCI_DEVICE_ID_DIGI_DF_M_IOM2_E, PCI_ANY_ID, PCI_ANY_ID}, + {PCI_VENDOR_ID_DIGI, PCI_DEVICE_ID_DIGI_DF_M_E, PCI_ANY_ID, PCI_ANY_ID}, + {PCI_VENDOR_ID_DIGI, PCI_DEVICE_ID_DIGI_DF_M_IOM2_A, PCI_ANY_ID, PCI_ANY_ID}, + {PCI_VENDOR_ID_DIGI, PCI_DEVICE_ID_DIGI_DF_M_A, PCI_ANY_ID, PCI_ANY_ID}, +#endif + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(pci, hisax_pci_tbl); +#endif /* CONFIG_PCI */ + +module_init(HiSax_init); +module_exit(HiSax_exit); + +EXPORT_SYMBOL(FsmNew); +EXPORT_SYMBOL(FsmFree); +EXPORT_SYMBOL(FsmEvent); +EXPORT_SYMBOL(FsmChangeState); +EXPORT_SYMBOL(FsmInitTimer); +EXPORT_SYMBOL(FsmDelTimer); +EXPORT_SYMBOL(FsmRestartTimer); diff --git a/drivers/isdn/hisax/diva.c b/drivers/isdn/hisax/diva.c new file mode 100644 index 000000000000..394d481e093f --- /dev/null +++ b/drivers/isdn/hisax/diva.c @@ -0,0 +1,1183 @@ +/* $Id: diva.c,v 1.33.2.6 2004/02/11 13:21:33 keil Exp $ + * + * low level stuff for Eicon.Diehl Diva Family ISDN cards + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * For changes and modifications please read + * Documentation/isdn/HiSax.cert + * + * Thanks to Eicon Technology for documents and information + * + */ + +#include <linux/init.h> +#include <linux/config.h> +#include "hisax.h" +#include "isac.h" +#include "hscx.h" +#include "ipac.h" +#include "ipacx.h" +#include "isdnl1.h" +#include <linux/pci.h> +#include <linux/isapnp.h> + +extern const char *CardType[]; + +const char *Diva_revision = "$Revision: 1.33.2.6 $"; + +#define byteout(addr,val) outb(val,addr) +#define bytein(addr) inb(addr) + +#define DIVA_HSCX_DATA 0 +#define DIVA_HSCX_ADR 4 +#define DIVA_ISA_ISAC_DATA 2 +#define DIVA_ISA_ISAC_ADR 6 +#define DIVA_ISA_CTRL 7 +#define DIVA_IPAC_ADR 0 +#define DIVA_IPAC_DATA 1 + +#define DIVA_PCI_ISAC_DATA 8 +#define DIVA_PCI_ISAC_ADR 0xc +#define DIVA_PCI_CTRL 0x10 + +/* SUB Types */ +#define DIVA_ISA 1 +#define DIVA_PCI 2 +#define DIVA_IPAC_ISA 3 +#define DIVA_IPAC_PCI 4 +#define DIVA_IPACX_PCI 5 + +/* CTRL (Read) */ +#define DIVA_IRQ_STAT 0x01 +#define DIVA_EEPROM_SDA 0x02 + +/* CTRL (Write) */ +#define DIVA_IRQ_REQ 0x01 +#define DIVA_RESET 0x08 +#define DIVA_EEPROM_CLK 0x40 +#define DIVA_PCI_LED_A 0x10 +#define DIVA_PCI_LED_B 0x20 +#define DIVA_ISA_LED_A 0x20 +#define DIVA_ISA_LED_B 0x40 +#define DIVA_IRQ_CLR 0x80 + +/* Siemens PITA */ +#define PITA_MISC_REG 0x1c +#ifdef __BIG_ENDIAN +#define PITA_PARA_SOFTRESET 0x00000001 +#define PITA_SER_SOFTRESET 0x00000002 +#define PITA_PARA_MPX_MODE 0x00000004 +#define PITA_INT0_ENABLE 0x00000200 +#else +#define PITA_PARA_SOFTRESET 0x01000000 +#define PITA_SER_SOFTRESET 0x02000000 +#define PITA_PARA_MPX_MODE 0x04000000 +#define PITA_INT0_ENABLE 0x00020000 +#endif +#define PITA_INT0_STATUS 0x02 + +static inline u_char +readreg(unsigned int ale, unsigned int adr, u_char off) +{ + register u_char ret; + + byteout(ale, off); + ret = bytein(adr); + return (ret); +} + +static inline void +readfifo(unsigned int ale, unsigned int adr, u_char off, u_char * data, int size) +{ + byteout(ale, off); + insb(adr, data, size); +} + + +static inline void +writereg(unsigned int ale, unsigned int adr, u_char off, u_char data) +{ + byteout(ale, off); + byteout(adr, data); +} + +static inline void +writefifo(unsigned int ale, unsigned int adr, u_char off, u_char *data, int size) +{ + byteout(ale, off); + outsb(adr, data, size); +} + +static inline u_char +memreadreg(unsigned long adr, u_char off) +{ + return(*((unsigned char *) + (((unsigned int *)adr) + off))); +} + +static inline void +memwritereg(unsigned long adr, u_char off, u_char data) +{ + register u_char *p; + + p = (unsigned char *)(((unsigned int *)adr) + off); + *p = data; +} + +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + return(readreg(cs->hw.diva.isac_adr, cs->hw.diva.isac, offset)); +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writereg(cs->hw.diva.isac_adr, cs->hw.diva.isac, offset, value); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + readfifo(cs->hw.diva.isac_adr, cs->hw.diva.isac, 0, data, size); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + writefifo(cs->hw.diva.isac_adr, cs->hw.diva.isac, 0, data, size); +} + +static u_char +ReadISAC_IPAC(struct IsdnCardState *cs, u_char offset) +{ + return (readreg(cs->hw.diva.isac_adr, cs->hw.diva.isac, offset+0x80)); +} + +static void +WriteISAC_IPAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writereg(cs->hw.diva.isac_adr, cs->hw.diva.isac, offset|0x80, value); +} + +static void +ReadISACfifo_IPAC(struct IsdnCardState *cs, u_char * data, int size) +{ + readfifo(cs->hw.diva.isac_adr, cs->hw.diva.isac, 0x80, data, size); +} + +static void +WriteISACfifo_IPAC(struct IsdnCardState *cs, u_char * data, int size) +{ + writefifo(cs->hw.diva.isac_adr, cs->hw.diva.isac, 0x80, data, size); +} + +static u_char +ReadHSCX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + return(readreg(cs->hw.diva.hscx_adr, + cs->hw.diva.hscx, offset + (hscx ? 0x40 : 0))); +} + +static void +WriteHSCX(struct IsdnCardState *cs, int hscx, u_char offset, u_char value) +{ + writereg(cs->hw.diva.hscx_adr, + cs->hw.diva.hscx, offset + (hscx ? 0x40 : 0), value); +} + +static u_char +MemReadISAC_IPAC(struct IsdnCardState *cs, u_char offset) +{ + return (memreadreg(cs->hw.diva.cfg_reg, offset+0x80)); +} + +static void +MemWriteISAC_IPAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + memwritereg(cs->hw.diva.cfg_reg, offset|0x80, value); +} + +static void +MemReadISACfifo_IPAC(struct IsdnCardState *cs, u_char * data, int size) +{ + while(size--) + *data++ = memreadreg(cs->hw.diva.cfg_reg, 0x80); +} + +static void +MemWriteISACfifo_IPAC(struct IsdnCardState *cs, u_char * data, int size) +{ + while(size--) + memwritereg(cs->hw.diva.cfg_reg, 0x80, *data++); +} + +static u_char +MemReadHSCX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + return(memreadreg(cs->hw.diva.cfg_reg, offset + (hscx ? 0x40 : 0))); +} + +static void +MemWriteHSCX(struct IsdnCardState *cs, int hscx, u_char offset, u_char value) +{ + memwritereg(cs->hw.diva.cfg_reg, offset + (hscx ? 0x40 : 0), value); +} + +/* IO-Functions for IPACX type cards */ +static u_char +MemReadISAC_IPACX(struct IsdnCardState *cs, u_char offset) +{ + return (memreadreg(cs->hw.diva.cfg_reg, offset)); +} + +static void +MemWriteISAC_IPACX(struct IsdnCardState *cs, u_char offset, u_char value) +{ + memwritereg(cs->hw.diva.cfg_reg, offset, value); +} + +static void +MemReadISACfifo_IPACX(struct IsdnCardState *cs, u_char * data, int size) +{ + while(size--) + *data++ = memreadreg(cs->hw.diva.cfg_reg, 0); +} + +static void +MemWriteISACfifo_IPACX(struct IsdnCardState *cs, u_char * data, int size) +{ + while(size--) + memwritereg(cs->hw.diva.cfg_reg, 0, *data++); +} + +static u_char +MemReadHSCX_IPACX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + return(memreadreg(cs->hw.diva.cfg_reg, offset + + (hscx ? IPACX_OFF_B2 : IPACX_OFF_B1))); +} + +static void +MemWriteHSCX_IPACX(struct IsdnCardState *cs, int hscx, u_char offset, u_char value) +{ + memwritereg(cs->hw.diva.cfg_reg, offset + + (hscx ? IPACX_OFF_B2 : IPACX_OFF_B1), value); +} + +/* + * fast interrupt HSCX stuff goes here + */ + +#define READHSCX(cs, nr, reg) readreg(cs->hw.diva.hscx_adr, \ + cs->hw.diva.hscx, reg + (nr ? 0x40 : 0)) +#define WRITEHSCX(cs, nr, reg, data) writereg(cs->hw.diva.hscx_adr, \ + cs->hw.diva.hscx, reg + (nr ? 0x40 : 0), data) + +#define READHSCXFIFO(cs, nr, ptr, cnt) readfifo(cs->hw.diva.hscx_adr, \ + cs->hw.diva.hscx, (nr ? 0x40 : 0), ptr, cnt) + +#define WRITEHSCXFIFO(cs, nr, ptr, cnt) writefifo(cs->hw.diva.hscx_adr, \ + cs->hw.diva.hscx, (nr ? 0x40 : 0), ptr, cnt) + +#include "hscx_irq.c" + +static irqreturn_t +diva_interrupt(int intno, void *dev_id, struct pt_regs *regs) +{ + struct IsdnCardState *cs = dev_id; + u_char val, sval; + u_long flags; + int cnt=5; + + spin_lock_irqsave(&cs->lock, flags); + while (((sval = bytein(cs->hw.diva.ctrl)) & DIVA_IRQ_REQ) && cnt) { + val = readreg(cs->hw.diva.hscx_adr, cs->hw.diva.hscx, HSCX_ISTA + 0x40); + if (val) + hscx_int_main(cs, val); + val = readreg(cs->hw.diva.isac_adr, cs->hw.diva.isac, ISAC_ISTA); + if (val) + isac_interrupt(cs, val); + cnt--; + } + if (!cnt) + printk(KERN_WARNING "Diva: IRQ LOOP\n"); + writereg(cs->hw.diva.hscx_adr, cs->hw.diva.hscx, HSCX_MASK, 0xFF); + writereg(cs->hw.diva.hscx_adr, cs->hw.diva.hscx, HSCX_MASK + 0x40, 0xFF); + writereg(cs->hw.diva.isac_adr, cs->hw.diva.isac, ISAC_MASK, 0xFF); + writereg(cs->hw.diva.isac_adr, cs->hw.diva.isac, ISAC_MASK, 0x0); + writereg(cs->hw.diva.hscx_adr, cs->hw.diva.hscx, HSCX_MASK, 0x0); + writereg(cs->hw.diva.hscx_adr, cs->hw.diva.hscx, HSCX_MASK + 0x40, 0x0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static irqreturn_t +diva_irq_ipac_isa(int intno, void *dev_id, struct pt_regs *regs) +{ + struct IsdnCardState *cs = dev_id; + u_char ista,val; + u_long flags; + int icnt=5; + + spin_lock_irqsave(&cs->lock, flags); + ista = readreg(cs->hw.diva.isac_adr, cs->hw.diva.isac, IPAC_ISTA); +Start_IPACISA: + if (cs->debug & L1_DEB_IPAC) + debugl1(cs, "IPAC ISTA %02X", ista); + if (ista & 0x0f) { + val = readreg(cs->hw.diva.isac_adr, cs->hw.diva.isac, HSCX_ISTA + 0x40); + if (ista & 0x01) + val |= 0x01; + if (ista & 0x04) + val |= 0x02; + if (ista & 0x08) + val |= 0x04; + if (val) + hscx_int_main(cs, val); + } + if (ista & 0x20) { + val = 0xfe & readreg(cs->hw.diva.isac_adr, cs->hw.diva.isac, ISAC_ISTA + 0x80); + if (val) { + isac_interrupt(cs, val); + } + } + if (ista & 0x10) { + val = 0x01; + isac_interrupt(cs, val); + } + ista = readreg(cs->hw.diva.isac_adr, cs->hw.diva.isac, IPAC_ISTA); + if ((ista & 0x3f) && icnt) { + icnt--; + goto Start_IPACISA; + } + if (!icnt) + printk(KERN_WARNING "DIVA IPAC IRQ LOOP\n"); + writereg(cs->hw.diva.isac_adr, cs->hw.diva.isac, IPAC_MASK, 0xFF); + writereg(cs->hw.diva.isac_adr, cs->hw.diva.isac, IPAC_MASK, 0xC0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static inline void +MemwaitforCEC(struct IsdnCardState *cs, int hscx) +{ + int to = 50; + + while ((MemReadHSCX(cs, hscx, HSCX_STAR) & 0x04) && to) { + udelay(1); + to--; + } + if (!to) + printk(KERN_WARNING "HiSax: waitforCEC timeout\n"); +} + + +static inline void +MemwaitforXFW(struct IsdnCardState *cs, int hscx) +{ + int to = 50; + + while ((!(MemReadHSCX(cs, hscx, HSCX_STAR) & 0x44) == 0x40) && to) { + udelay(1); + to--; + } + if (!to) + printk(KERN_WARNING "HiSax: waitforXFW timeout\n"); +} + +static inline void +MemWriteHSCXCMDR(struct IsdnCardState *cs, int hscx, u_char data) +{ + MemwaitforCEC(cs, hscx); + MemWriteHSCX(cs, hscx, HSCX_CMDR, data); +} + +static void +Memhscx_empty_fifo(struct BCState *bcs, int count) +{ + u_char *ptr; + struct IsdnCardState *cs = bcs->cs; + int cnt; + + if ((cs->debug & L1_DEB_HSCX) && !(cs->debug & L1_DEB_HSCX_FIFO)) + debugl1(cs, "hscx_empty_fifo"); + + if (bcs->hw.hscx.rcvidx + count > HSCX_BUFMAX) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "hscx_empty_fifo: incoming packet too large"); + MemWriteHSCXCMDR(cs, bcs->hw.hscx.hscx, 0x80); + bcs->hw.hscx.rcvidx = 0; + return; + } + ptr = bcs->hw.hscx.rcvbuf + bcs->hw.hscx.rcvidx; + cnt = count; + while (cnt--) + *ptr++ = memreadreg(cs->hw.diva.cfg_reg, bcs->hw.hscx.hscx ? 0x40 : 0); + MemWriteHSCXCMDR(cs, bcs->hw.hscx.hscx, 0x80); + ptr = bcs->hw.hscx.rcvbuf + bcs->hw.hscx.rcvidx; + bcs->hw.hscx.rcvidx += count; + if (cs->debug & L1_DEB_HSCX_FIFO) { + char *t = bcs->blog; + + t += sprintf(t, "hscx_empty_fifo %c cnt %d", + bcs->hw.hscx.hscx ? 'B' : 'A', count); + QuickHex(t, ptr, count); + debugl1(cs, bcs->blog); + } +} + +static void +Memhscx_fill_fifo(struct BCState *bcs) +{ + struct IsdnCardState *cs = bcs->cs; + int more, count, cnt; + int fifo_size = test_bit(HW_IPAC, &cs->HW_Flags)? 64: 32; + u_char *ptr,*p; + + if ((cs->debug & L1_DEB_HSCX) && !(cs->debug & L1_DEB_HSCX_FIFO)) + debugl1(cs, "hscx_fill_fifo"); + + if (!bcs->tx_skb) + return; + if (bcs->tx_skb->len <= 0) + return; + + more = (bcs->mode == L1_MODE_TRANS) ? 1 : 0; + if (bcs->tx_skb->len > fifo_size) { + more = !0; + count = fifo_size; + } else + count = bcs->tx_skb->len; + cnt = count; + MemwaitforXFW(cs, bcs->hw.hscx.hscx); + p = ptr = bcs->tx_skb->data; + skb_pull(bcs->tx_skb, count); + bcs->tx_cnt -= count; + bcs->hw.hscx.count += count; + while(cnt--) + memwritereg(cs->hw.diva.cfg_reg, bcs->hw.hscx.hscx ? 0x40 : 0, + *p++); + MemWriteHSCXCMDR(cs, bcs->hw.hscx.hscx, more ? 0x8 : 0xa); + if (cs->debug & L1_DEB_HSCX_FIFO) { + char *t = bcs->blog; + + t += sprintf(t, "hscx_fill_fifo %c cnt %d", + bcs->hw.hscx.hscx ? 'B' : 'A', count); + QuickHex(t, ptr, count); + debugl1(cs, bcs->blog); + } +} + +static inline void +Memhscx_interrupt(struct IsdnCardState *cs, u_char val, u_char hscx) +{ + u_char r; + struct BCState *bcs = cs->bcs + hscx; + struct sk_buff *skb; + int fifo_size = test_bit(HW_IPAC, &cs->HW_Flags)? 64: 32; + int count; + + if (!test_bit(BC_FLG_INIT, &bcs->Flag)) + return; + + if (val & 0x80) { /* RME */ + r = MemReadHSCX(cs, hscx, HSCX_RSTA); + if ((r & 0xf0) != 0xa0) { + if (!(r & 0x80)) + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "HSCX invalid frame"); + if ((r & 0x40) && bcs->mode) + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "HSCX RDO mode=%d", + bcs->mode); + if (!(r & 0x20)) + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "HSCX CRC error"); + MemWriteHSCXCMDR(cs, hscx, 0x80); + } else { + count = MemReadHSCX(cs, hscx, HSCX_RBCL) & ( + test_bit(HW_IPAC, &cs->HW_Flags)? 0x3f: 0x1f); + if (count == 0) + count = fifo_size; + Memhscx_empty_fifo(bcs, count); + if ((count = bcs->hw.hscx.rcvidx - 1) > 0) { + if (cs->debug & L1_DEB_HSCX_FIFO) + debugl1(cs, "HX Frame %d", count); + if (!(skb = dev_alloc_skb(count))) + printk(KERN_WARNING "HSCX: receive out of memory\n"); + else { + memcpy(skb_put(skb, count), bcs->hw.hscx.rcvbuf, count); + skb_queue_tail(&bcs->rqueue, skb); + } + } + } + bcs->hw.hscx.rcvidx = 0; + schedule_event(bcs, B_RCVBUFREADY); + } + if (val & 0x40) { /* RPF */ + Memhscx_empty_fifo(bcs, fifo_size); + if (bcs->mode == L1_MODE_TRANS) { + /* receive audio data */ + if (!(skb = dev_alloc_skb(fifo_size))) + printk(KERN_WARNING "HiSax: receive out of memory\n"); + else { + memcpy(skb_put(skb, fifo_size), bcs->hw.hscx.rcvbuf, fifo_size); + skb_queue_tail(&bcs->rqueue, skb); + } + bcs->hw.hscx.rcvidx = 0; + schedule_event(bcs, B_RCVBUFREADY); + } + } + if (val & 0x10) { /* XPR */ + if (bcs->tx_skb) { + if (bcs->tx_skb->len) { + Memhscx_fill_fifo(bcs); + return; + } else { + if (test_bit(FLG_LLI_L1WAKEUP,&bcs->st->lli.flag) && + (PACKET_NOACK != bcs->tx_skb->pkt_type)) { + u_long flags; + spin_lock_irqsave(&bcs->aclock, flags); + bcs->ackcnt += bcs->hw.hscx.count; + spin_unlock_irqrestore(&bcs->aclock, flags); + schedule_event(bcs, B_ACKPENDING); + } + dev_kfree_skb_irq(bcs->tx_skb); + bcs->hw.hscx.count = 0; + bcs->tx_skb = NULL; + } + } + if ((bcs->tx_skb = skb_dequeue(&bcs->squeue))) { + bcs->hw.hscx.count = 0; + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + Memhscx_fill_fifo(bcs); + } else { + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + schedule_event(bcs, B_XMTBUFREADY); + } + } +} + +static inline void +Memhscx_int_main(struct IsdnCardState *cs, u_char val) +{ + + u_char exval; + struct BCState *bcs; + + if (val & 0x01) { // EXB + bcs = cs->bcs + 1; + exval = MemReadHSCX(cs, 1, HSCX_EXIR); + if (exval & 0x40) { + if (bcs->mode == 1) + Memhscx_fill_fifo(bcs); + else { + /* Here we lost an TX interrupt, so + * restart transmitting the whole frame. + */ + if (bcs->tx_skb) { + skb_push(bcs->tx_skb, bcs->hw.hscx.count); + bcs->tx_cnt += bcs->hw.hscx.count; + bcs->hw.hscx.count = 0; + } + MemWriteHSCXCMDR(cs, bcs->hw.hscx.hscx, 0x01); + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "HSCX B EXIR %x Lost TX", exval); + } + } else if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HSCX B EXIR %x", exval); + } + if (val & 0xf8) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HSCX B interrupt %x", val); + Memhscx_interrupt(cs, val, 1); + } + if (val & 0x02) { // EXA + bcs = cs->bcs; + exval = MemReadHSCX(cs, 0, HSCX_EXIR); + if (exval & 0x40) { + if (bcs->mode == L1_MODE_TRANS) + Memhscx_fill_fifo(bcs); + else { + /* Here we lost an TX interrupt, so + * restart transmitting the whole frame. + */ + if (bcs->tx_skb) { + skb_push(bcs->tx_skb, bcs->hw.hscx.count); + bcs->tx_cnt += bcs->hw.hscx.count; + bcs->hw.hscx.count = 0; + } + MemWriteHSCXCMDR(cs, bcs->hw.hscx.hscx, 0x01); + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "HSCX A EXIR %x Lost TX", exval); + } + } else if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HSCX A EXIR %x", exval); + } + if (val & 0x04) { // ICA + exval = MemReadHSCX(cs, 0, HSCX_ISTA); + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HSCX A interrupt %x", exval); + Memhscx_interrupt(cs, exval, 0); + } +} + +static irqreturn_t +diva_irq_ipac_pci(int intno, void *dev_id, struct pt_regs *regs) +{ + struct IsdnCardState *cs = dev_id; + u_char ista,val; + int icnt=5; + u_char *cfg; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + cfg = (u_char *) cs->hw.diva.pci_cfg; + val = *cfg; + if (!(val & PITA_INT0_STATUS)) { + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_NONE; /* other shared IRQ */ + } + *cfg = PITA_INT0_STATUS; /* Reset pending INT0 */ + ista = memreadreg(cs->hw.diva.cfg_reg, IPAC_ISTA); +Start_IPACPCI: + if (cs->debug & L1_DEB_IPAC) + debugl1(cs, "IPAC ISTA %02X", ista); + if (ista & 0x0f) { + val = memreadreg(cs->hw.diva.cfg_reg, HSCX_ISTA + 0x40); + if (ista & 0x01) + val |= 0x01; + if (ista & 0x04) + val |= 0x02; + if (ista & 0x08) + val |= 0x04; + if (val) + Memhscx_int_main(cs, val); + } + if (ista & 0x20) { + val = 0xfe & memreadreg(cs->hw.diva.cfg_reg, ISAC_ISTA + 0x80); + if (val) { + isac_interrupt(cs, val); + } + } + if (ista & 0x10) { + val = 0x01; + isac_interrupt(cs, val); + } + ista = memreadreg(cs->hw.diva.cfg_reg, IPAC_ISTA); + if ((ista & 0x3f) && icnt) { + icnt--; + goto Start_IPACPCI; + } + if (!icnt) + printk(KERN_WARNING "DIVA IPAC PCI IRQ LOOP\n"); + memwritereg(cs->hw.diva.cfg_reg, IPAC_MASK, 0xFF); + memwritereg(cs->hw.diva.cfg_reg, IPAC_MASK, 0xC0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static irqreturn_t +diva_irq_ipacx_pci(int intno, void *dev_id, struct pt_regs *regs) +{ + struct IsdnCardState *cs = dev_id; + u_char val; + u_char *cfg; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + cfg = (u_char *) cs->hw.diva.pci_cfg; + val = *cfg; + if (!(val &PITA_INT0_STATUS)) { + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_NONE; // other shared IRQ + } + interrupt_ipacx(cs); // handler for chip + *cfg = PITA_INT0_STATUS; // Reset PLX interrupt + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +void +release_io_diva(struct IsdnCardState *cs) +{ + int bytecnt; + + if ((cs->subtyp == DIVA_IPAC_PCI) || + (cs->subtyp == DIVA_IPACX_PCI) ) { + u_int *cfg = (unsigned int *)cs->hw.diva.pci_cfg; + + *cfg = 0; /* disable INT0/1 */ + *cfg = 2; /* reset pending INT0 */ + iounmap((void *)cs->hw.diva.cfg_reg); + iounmap((void *)cs->hw.diva.pci_cfg); + return; + } else if (cs->subtyp != DIVA_IPAC_ISA) { + del_timer(&cs->hw.diva.tl); + if (cs->hw.diva.cfg_reg) + byteout(cs->hw.diva.ctrl, 0); /* LED off, Reset */ + } + if ((cs->subtyp == DIVA_ISA) || (cs->subtyp == DIVA_IPAC_ISA)) + bytecnt = 8; + else + bytecnt = 32; + if (cs->hw.diva.cfg_reg) { + release_region(cs->hw.diva.cfg_reg, bytecnt); + } +} + +static void +reset_diva(struct IsdnCardState *cs) +{ + if (cs->subtyp == DIVA_IPAC_ISA) { + writereg(cs->hw.diva.isac_adr, cs->hw.diva.isac, IPAC_POTA2, 0x20); + mdelay(10); + writereg(cs->hw.diva.isac_adr, cs->hw.diva.isac, IPAC_POTA2, 0x00); + mdelay(10); + writereg(cs->hw.diva.isac_adr, cs->hw.diva.isac, IPAC_MASK, 0xc0); + } else if (cs->subtyp == DIVA_IPAC_PCI) { + unsigned int *ireg = (unsigned int *)(cs->hw.diva.pci_cfg + + PITA_MISC_REG); + *ireg = PITA_PARA_SOFTRESET | PITA_PARA_MPX_MODE; + mdelay(10); + *ireg = PITA_PARA_MPX_MODE; + mdelay(10); + memwritereg(cs->hw.diva.cfg_reg, IPAC_MASK, 0xc0); + } else if (cs->subtyp == DIVA_IPACX_PCI) { + unsigned int *ireg = (unsigned int *)(cs->hw.diva.pci_cfg + + PITA_MISC_REG); + *ireg = PITA_PARA_SOFTRESET | PITA_PARA_MPX_MODE; + mdelay(10); + *ireg = PITA_PARA_MPX_MODE | PITA_SER_SOFTRESET; + mdelay(10); + MemWriteISAC_IPACX(cs, IPACX_MASK, 0xff); // Interrupts off + } else { /* DIVA 2.0 */ + cs->hw.diva.ctrl_reg = 0; /* Reset On */ + byteout(cs->hw.diva.ctrl, cs->hw.diva.ctrl_reg); + mdelay(10); + cs->hw.diva.ctrl_reg |= DIVA_RESET; /* Reset Off */ + byteout(cs->hw.diva.ctrl, cs->hw.diva.ctrl_reg); + mdelay(10); + if (cs->subtyp == DIVA_ISA) + cs->hw.diva.ctrl_reg |= DIVA_ISA_LED_A; + else { + /* Workaround PCI9060 */ + byteout(cs->hw.diva.pci_cfg + 0x69, 9); + cs->hw.diva.ctrl_reg |= DIVA_PCI_LED_A; + } + byteout(cs->hw.diva.ctrl, cs->hw.diva.ctrl_reg); + } +} + +#define DIVA_ASSIGN 1 + +static void +diva_led_handler(struct IsdnCardState *cs) +{ + int blink = 0; + + if ((cs->subtyp == DIVA_IPAC_ISA) || + (cs->subtyp == DIVA_IPAC_PCI) || + (cs->subtyp == DIVA_IPACX_PCI) ) + return; + del_timer(&cs->hw.diva.tl); + if (cs->hw.diva.status & DIVA_ASSIGN) + cs->hw.diva.ctrl_reg |= (DIVA_ISA == cs->subtyp) ? + DIVA_ISA_LED_A : DIVA_PCI_LED_A; + else { + cs->hw.diva.ctrl_reg ^= (DIVA_ISA == cs->subtyp) ? + DIVA_ISA_LED_A : DIVA_PCI_LED_A; + blink = 250; + } + if (cs->hw.diva.status & 0xf000) + cs->hw.diva.ctrl_reg |= (DIVA_ISA == cs->subtyp) ? + DIVA_ISA_LED_B : DIVA_PCI_LED_B; + else if (cs->hw.diva.status & 0x0f00) { + cs->hw.diva.ctrl_reg ^= (DIVA_ISA == cs->subtyp) ? + DIVA_ISA_LED_B : DIVA_PCI_LED_B; + blink = 500; + } else + cs->hw.diva.ctrl_reg &= ~((DIVA_ISA == cs->subtyp) ? + DIVA_ISA_LED_B : DIVA_PCI_LED_B); + + byteout(cs->hw.diva.ctrl, cs->hw.diva.ctrl_reg); + if (blink) { + init_timer(&cs->hw.diva.tl); + cs->hw.diva.tl.expires = jiffies + ((blink * HZ) / 1000); + add_timer(&cs->hw.diva.tl); + } +} + +static int +Diva_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_int *ireg; + u_long flags; + + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + reset_diva(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return(0); + case CARD_RELEASE: + release_io_diva(cs); + return(0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + reset_diva(cs); + if (cs->subtyp == DIVA_IPACX_PCI) { + ireg = (unsigned int *)cs->hw.diva.pci_cfg; + *ireg = PITA_INT0_ENABLE; + init_ipacx(cs, 3); // init chip and enable interrupts + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + } + if (cs->subtyp == DIVA_IPAC_PCI) { + ireg = (unsigned int *)cs->hw.diva.pci_cfg; + *ireg = PITA_INT0_ENABLE; + } + inithscxisac(cs, 3); + spin_unlock_irqrestore(&cs->lock, flags); + return(0); + case CARD_TEST: + return(0); + case (MDL_REMOVE | REQUEST): + cs->hw.diva.status = 0; + break; + case (MDL_ASSIGN | REQUEST): + cs->hw.diva.status |= DIVA_ASSIGN; + break; + case MDL_INFO_SETUP: + if ((long)arg) + cs->hw.diva.status |= 0x0200; + else + cs->hw.diva.status |= 0x0100; + break; + case MDL_INFO_CONN: + if ((long)arg) + cs->hw.diva.status |= 0x2000; + else + cs->hw.diva.status |= 0x1000; + break; + case MDL_INFO_REL: + if ((long)arg) { + cs->hw.diva.status &= ~0x2000; + cs->hw.diva.status &= ~0x0200; + } else { + cs->hw.diva.status &= ~0x1000; + cs->hw.diva.status &= ~0x0100; + } + break; + } + if ((cs->subtyp != DIVA_IPAC_ISA) && + (cs->subtyp != DIVA_IPAC_PCI) && + (cs->subtyp != DIVA_IPACX_PCI)) { + spin_lock_irqsave(&cs->lock, flags); + diva_led_handler(cs); + spin_unlock_irqrestore(&cs->lock, flags); + } + return(0); +} + +static struct pci_dev *dev_diva __initdata = NULL; +static struct pci_dev *dev_diva_u __initdata = NULL; +static struct pci_dev *dev_diva201 __initdata = NULL; +static struct pci_dev *dev_diva202 __initdata = NULL; + +#ifdef __ISAPNP__ +static struct isapnp_device_id diva_ids[] __initdata = { + { ISAPNP_VENDOR('G', 'D', 'I'), ISAPNP_FUNCTION(0x51), + ISAPNP_VENDOR('G', 'D', 'I'), ISAPNP_FUNCTION(0x51), + (unsigned long) "Diva picola" }, + { ISAPNP_VENDOR('G', 'D', 'I'), ISAPNP_FUNCTION(0x51), + ISAPNP_VENDOR('E', 'I', 'C'), ISAPNP_FUNCTION(0x51), + (unsigned long) "Diva picola" }, + { ISAPNP_VENDOR('G', 'D', 'I'), ISAPNP_FUNCTION(0x71), + ISAPNP_VENDOR('G', 'D', 'I'), ISAPNP_FUNCTION(0x71), + (unsigned long) "Diva 2.0" }, + { ISAPNP_VENDOR('G', 'D', 'I'), ISAPNP_FUNCTION(0x71), + ISAPNP_VENDOR('E', 'I', 'C'), ISAPNP_FUNCTION(0x71), + (unsigned long) "Diva 2.0" }, + { ISAPNP_VENDOR('G', 'D', 'I'), ISAPNP_FUNCTION(0xA1), + ISAPNP_VENDOR('G', 'D', 'I'), ISAPNP_FUNCTION(0xA1), + (unsigned long) "Diva 2.01" }, + { ISAPNP_VENDOR('G', 'D', 'I'), ISAPNP_FUNCTION(0xA1), + ISAPNP_VENDOR('E', 'I', 'C'), ISAPNP_FUNCTION(0xA1), + (unsigned long) "Diva 2.01" }, + { 0, } +}; + +static struct isapnp_device_id *ipid __initdata = &diva_ids[0]; +static struct pnp_card *pnp_c __devinitdata = NULL; +#endif + + +int __init +setup_diva(struct IsdnCard *card) +{ + int bytecnt = 8; + u_char val; + struct IsdnCardState *cs = card->cs; + char tmp[64]; + + strcpy(tmp, Diva_revision); + printk(KERN_INFO "HiSax: Eicon.Diehl Diva driver Rev. %s\n", HiSax_getrev(tmp)); + if (cs->typ != ISDN_CTYPE_DIEHLDIVA) + return(0); + cs->hw.diva.status = 0; + if (card->para[1]) { + cs->hw.diva.ctrl_reg = 0; + cs->hw.diva.cfg_reg = card->para[1]; + val = readreg(cs->hw.diva.cfg_reg + DIVA_IPAC_ADR, + cs->hw.diva.cfg_reg + DIVA_IPAC_DATA, IPAC_ID); + printk(KERN_INFO "Diva: IPAC version %x\n", val); + if ((val == 1) || (val==2)) { + cs->subtyp = DIVA_IPAC_ISA; + cs->hw.diva.ctrl = 0; + cs->hw.diva.isac = card->para[1] + DIVA_IPAC_DATA; + cs->hw.diva.hscx = card->para[1] + DIVA_IPAC_DATA; + cs->hw.diva.isac_adr = card->para[1] + DIVA_IPAC_ADR; + cs->hw.diva.hscx_adr = card->para[1] + DIVA_IPAC_ADR; + test_and_set_bit(HW_IPAC, &cs->HW_Flags); + } else { + cs->subtyp = DIVA_ISA; + cs->hw.diva.ctrl = card->para[1] + DIVA_ISA_CTRL; + cs->hw.diva.isac = card->para[1] + DIVA_ISA_ISAC_DATA; + cs->hw.diva.hscx = card->para[1] + DIVA_HSCX_DATA; + cs->hw.diva.isac_adr = card->para[1] + DIVA_ISA_ISAC_ADR; + cs->hw.diva.hscx_adr = card->para[1] + DIVA_HSCX_ADR; + } + cs->irq = card->para[0]; + } else { +#ifdef __ISAPNP__ + if (isapnp_present()) { + struct pnp_dev *pnp_d; + while(ipid->card_vendor) { + if ((pnp_c = pnp_find_card(ipid->card_vendor, + ipid->card_device, pnp_c))) { + pnp_d = NULL; + if ((pnp_d = pnp_find_dev(pnp_c, + ipid->vendor, ipid->function, pnp_d))) { + int err; + + printk(KERN_INFO "HiSax: %s detected\n", + (char *)ipid->driver_data); + pnp_disable_dev(pnp_d); + err = pnp_activate_dev(pnp_d); + if (err<0) { + printk(KERN_WARNING "%s: pnp_activate_dev ret(%d)\n", + __FUNCTION__, err); + return(0); + } + card->para[1] = pnp_port_start(pnp_d, 0); + card->para[0] = pnp_irq(pnp_d, 0); + if (!card->para[0] || !card->para[1]) { + printk(KERN_ERR "Diva PnP:some resources are missing %ld/%lx\n", + card->para[0], card->para[1]); + pnp_disable_dev(pnp_d); + return(0); + } + cs->hw.diva.cfg_reg = card->para[1]; + cs->irq = card->para[0]; + if (ipid->function == ISAPNP_FUNCTION(0xA1)) { + cs->subtyp = DIVA_IPAC_ISA; + cs->hw.diva.ctrl = 0; + cs->hw.diva.isac = + card->para[1] + DIVA_IPAC_DATA; + cs->hw.diva.hscx = + card->para[1] + DIVA_IPAC_DATA; + cs->hw.diva.isac_adr = + card->para[1] + DIVA_IPAC_ADR; + cs->hw.diva.hscx_adr = + card->para[1] + DIVA_IPAC_ADR; + test_and_set_bit(HW_IPAC, &cs->HW_Flags); + } else { + cs->subtyp = DIVA_ISA; + cs->hw.diva.ctrl = + card->para[1] + DIVA_ISA_CTRL; + cs->hw.diva.isac = + card->para[1] + DIVA_ISA_ISAC_DATA; + cs->hw.diva.hscx = + card->para[1] + DIVA_HSCX_DATA; + cs->hw.diva.isac_adr = + card->para[1] + DIVA_ISA_ISAC_ADR; + cs->hw.diva.hscx_adr = + card->para[1] + DIVA_HSCX_ADR; + } + goto ready; + } else { + printk(KERN_ERR "Diva PnP: PnP error card found, no device\n"); + return(0); + } + } + ipid++; + pnp_c=NULL; + } + if (!ipid->card_vendor) { + printk(KERN_INFO "Diva PnP: no ISAPnP card found\n"); + } + } +#endif +#ifdef CONFIG_PCI + cs->subtyp = 0; + if ((dev_diva = pci_find_device(PCI_VENDOR_ID_EICON, + PCI_DEVICE_ID_EICON_DIVA20, dev_diva))) { + if (pci_enable_device(dev_diva)) + return(0); + cs->subtyp = DIVA_PCI; + cs->irq = dev_diva->irq; + cs->hw.diva.cfg_reg = pci_resource_start(dev_diva, 2); + } else if ((dev_diva_u = pci_find_device(PCI_VENDOR_ID_EICON, + PCI_DEVICE_ID_EICON_DIVA20_U, dev_diva_u))) { + if (pci_enable_device(dev_diva_u)) + return(0); + cs->subtyp = DIVA_PCI; + cs->irq = dev_diva_u->irq; + cs->hw.diva.cfg_reg = pci_resource_start(dev_diva_u, 2); + } else if ((dev_diva201 = pci_find_device(PCI_VENDOR_ID_EICON, + PCI_DEVICE_ID_EICON_DIVA201, dev_diva201))) { + if (pci_enable_device(dev_diva201)) + return(0); + cs->subtyp = DIVA_IPAC_PCI; + cs->irq = dev_diva201->irq; + cs->hw.diva.pci_cfg = + (ulong) ioremap(pci_resource_start(dev_diva201, 0), 4096); + cs->hw.diva.cfg_reg = + (ulong) ioremap(pci_resource_start(dev_diva201, 1), 4096); + } else if ((dev_diva202 = pci_find_device(PCI_VENDOR_ID_EICON, + PCI_DEVICE_ID_EICON_DIVA202, dev_diva202))) { + if (pci_enable_device(dev_diva202)) + return(0); + cs->subtyp = DIVA_IPACX_PCI; + cs->irq = dev_diva202->irq; + cs->hw.diva.pci_cfg = + (ulong) ioremap(pci_resource_start(dev_diva202, 0), 4096); + cs->hw.diva.cfg_reg = + (ulong) ioremap(pci_resource_start(dev_diva202, 1), 4096); + } else { + printk(KERN_WARNING "Diva: No PCI card found\n"); + return(0); + } + + if (!cs->irq) { + printk(KERN_WARNING "Diva: No IRQ for PCI card found\n"); + return(0); + } + + if (!cs->hw.diva.cfg_reg) { + printk(KERN_WARNING "Diva: No IO-Adr for PCI card found\n"); + return(0); + } + cs->irq_flags |= SA_SHIRQ; +#else + printk(KERN_WARNING "Diva: cfgreg 0 and NO_PCI_BIOS\n"); + printk(KERN_WARNING "Diva: unable to config DIVA PCI\n"); + return (0); +#endif /* CONFIG_PCI */ + if ((cs->subtyp == DIVA_IPAC_PCI) || + (cs->subtyp == DIVA_IPACX_PCI) ) { + cs->hw.diva.ctrl = 0; + cs->hw.diva.isac = 0; + cs->hw.diva.hscx = 0; + cs->hw.diva.isac_adr = 0; + cs->hw.diva.hscx_adr = 0; + test_and_set_bit(HW_IPAC, &cs->HW_Flags); + bytecnt = 0; + } else { + cs->hw.diva.ctrl = cs->hw.diva.cfg_reg + DIVA_PCI_CTRL; + cs->hw.diva.isac = cs->hw.diva.cfg_reg + DIVA_PCI_ISAC_DATA; + cs->hw.diva.hscx = cs->hw.diva.cfg_reg + DIVA_HSCX_DATA; + cs->hw.diva.isac_adr = cs->hw.diva.cfg_reg + DIVA_PCI_ISAC_ADR; + cs->hw.diva.hscx_adr = cs->hw.diva.cfg_reg + DIVA_HSCX_ADR; + bytecnt = 32; + } + } +ready: + printk(KERN_INFO + "Diva: %s card configured at %#lx IRQ %d\n", + (cs->subtyp == DIVA_PCI) ? "PCI" : + (cs->subtyp == DIVA_ISA) ? "ISA" : + (cs->subtyp == DIVA_IPAC_ISA) ? "IPAC ISA" : + (cs->subtyp == DIVA_IPAC_PCI) ? "IPAC PCI" : "IPACX PCI", + cs->hw.diva.cfg_reg, cs->irq); + if ((cs->subtyp == DIVA_IPAC_PCI) || + (cs->subtyp == DIVA_IPACX_PCI) || + (cs->subtyp == DIVA_PCI) ) + printk(KERN_INFO "Diva: %s space at %#lx\n", + (cs->subtyp == DIVA_PCI) ? "PCI" : + (cs->subtyp == DIVA_IPAC_PCI) ? "IPAC PCI" : "IPACX PCI", + cs->hw.diva.pci_cfg); + if ((cs->subtyp != DIVA_IPAC_PCI) && + (cs->subtyp != DIVA_IPACX_PCI) ) { + if (!request_region(cs->hw.diva.cfg_reg, bytecnt, "diva isdn")) { + printk(KERN_WARNING + "HiSax: %s config port %lx-%lx already in use\n", + CardType[card->typ], + cs->hw.diva.cfg_reg, + cs->hw.diva.cfg_reg + bytecnt); + return (0); + } + } + cs->BC_Read_Reg = &ReadHSCX; + cs->BC_Write_Reg = &WriteHSCX; + cs->BC_Send_Data = &hscx_fill_fifo; + cs->cardmsg = &Diva_card_msg; + setup_isac(cs); + if (cs->subtyp == DIVA_IPAC_ISA) { + cs->readisac = &ReadISAC_IPAC; + cs->writeisac = &WriteISAC_IPAC; + cs->readisacfifo = &ReadISACfifo_IPAC; + cs->writeisacfifo = &WriteISACfifo_IPAC; + cs->irq_func = &diva_irq_ipac_isa; + val = readreg(cs->hw.diva.isac_adr, cs->hw.diva.isac, IPAC_ID); + printk(KERN_INFO "Diva: IPAC version %x\n", val); + } else if (cs->subtyp == DIVA_IPAC_PCI) { + cs->readisac = &MemReadISAC_IPAC; + cs->writeisac = &MemWriteISAC_IPAC; + cs->readisacfifo = &MemReadISACfifo_IPAC; + cs->writeisacfifo = &MemWriteISACfifo_IPAC; + cs->BC_Read_Reg = &MemReadHSCX; + cs->BC_Write_Reg = &MemWriteHSCX; + cs->BC_Send_Data = &Memhscx_fill_fifo; + cs->irq_func = &diva_irq_ipac_pci; + val = memreadreg(cs->hw.diva.cfg_reg, IPAC_ID); + printk(KERN_INFO "Diva: IPAC version %x\n", val); + } else if (cs->subtyp == DIVA_IPACX_PCI) { + cs->readisac = &MemReadISAC_IPACX; + cs->writeisac = &MemWriteISAC_IPACX; + cs->readisacfifo = &MemReadISACfifo_IPACX; + cs->writeisacfifo = &MemWriteISACfifo_IPACX; + cs->BC_Read_Reg = &MemReadHSCX_IPACX; + cs->BC_Write_Reg = &MemWriteHSCX_IPACX; + cs->BC_Send_Data = NULL; // function located in ipacx module + cs->irq_func = &diva_irq_ipacx_pci; + printk(KERN_INFO "Diva: IPACX Design Id: %x\n", + MemReadISAC_IPACX(cs, IPACX_ID) &0x3F); + } else { /* DIVA 2.0 */ + cs->hw.diva.tl.function = (void *) diva_led_handler; + cs->hw.diva.tl.data = (long) cs; + init_timer(&cs->hw.diva.tl); + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->irq_func = &diva_interrupt; + ISACVersion(cs, "Diva:"); + if (HscxVersion(cs, "Diva:")) { + printk(KERN_WARNING + "Diva: wrong HSCX versions check IO address\n"); + release_io_diva(cs); + return (0); + } + } + return (1); +} diff --git a/drivers/isdn/hisax/elsa.c b/drivers/isdn/hisax/elsa.c new file mode 100644 index 000000000000..4d7a0250d7e2 --- /dev/null +++ b/drivers/isdn/hisax/elsa.c @@ -0,0 +1,1190 @@ +/* $Id: elsa.c,v 2.32.2.4 2004/01/24 20:47:21 keil Exp $ + * + * low level stuff for Elsa isdn cards + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * For changes and modifications please read + * Documentation/isdn/HiSax.cert + * + * Thanks to Elsa GmbH for documents and information + * + * Klaus Lichtenwalder (Klaus.Lichtenwalder@WebForum.DE) + * for ELSA PCMCIA support + * + */ + +#include <linux/init.h> +#include <linux/config.h> +#include "hisax.h" +#include "arcofi.h" +#include "isac.h" +#include "ipac.h" +#include "hscx.h" +#include "isdnl1.h" +#include <linux/pci.h> +#include <linux/isapnp.h> +#include <linux/serial.h> +#include <linux/serial_reg.h> + +extern const char *CardType[]; + +const char *Elsa_revision = "$Revision: 2.32.2.4 $"; +const char *Elsa_Types[] = +{"None", "PC", "PCC-8", "PCC-16", "PCF", "PCF-Pro", + "PCMCIA", "QS 1000", "QS 3000", "Microlink PCI", "QS 3000 PCI", + "PCMCIA-IPAC" }; + +const char *ITACVer[] = +{"?0?", "?1?", "?2?", "?3?", "?4?", "V2.2", + "B1", "A1"}; + +#define byteout(addr,val) outb(val,addr) +#define bytein(addr) inb(addr) + +#define ELSA_ISAC 0 +#define ELSA_ISAC_PCM 1 +#define ELSA_ITAC 1 +#define ELSA_HSCX 2 +#define ELSA_ALE 3 +#define ELSA_ALE_PCM 4 +#define ELSA_CONTROL 4 +#define ELSA_CONFIG 5 +#define ELSA_START_TIMER 6 +#define ELSA_TRIG_IRQ 7 + +#define ELSA_PC 1 +#define ELSA_PCC8 2 +#define ELSA_PCC16 3 +#define ELSA_PCF 4 +#define ELSA_PCFPRO 5 +#define ELSA_PCMCIA 6 +#define ELSA_QS1000 7 +#define ELSA_QS3000 8 +#define ELSA_QS1000PCI 9 +#define ELSA_QS3000PCI 10 +#define ELSA_PCMCIA_IPAC 11 + +/* PCI stuff */ +#define ELSA_PCI_IRQ_MASK 0x04 + +/* ITAC Registeradressen (only Microlink PC) */ +#define ITAC_SYS 0x34 +#define ITAC_ISEN 0x48 +#define ITAC_RFIE 0x4A +#define ITAC_XFIE 0x4C +#define ITAC_SCIE 0x4E +#define ITAC_STIE 0x46 + +/*** *** + *** Makros als Befehle fuer die Kartenregister *** + *** (mehrere Befehle werden durch Bit-Oderung kombiniert) *** + *** ***/ + +/* Config-Register (Read) */ +#define ELSA_TIMER_RUN 0x02 /* Bit 1 des Config-Reg */ +#define ELSA_TIMER_RUN_PCC8 0x01 /* Bit 0 des Config-Reg bei PCC */ +#define ELSA_IRQ_IDX 0x38 /* Bit 3,4,5 des Config-Reg */ +#define ELSA_IRQ_IDX_PCC8 0x30 /* Bit 4,5 des Config-Reg */ +#define ELSA_IRQ_IDX_PC 0x0c /* Bit 2,3 des Config-Reg */ + +/* Control-Register (Write) */ +#define ELSA_LINE_LED 0x02 /* Bit 1 Gelbe LED */ +#define ELSA_STAT_LED 0x08 /* Bit 3 Gruene LED */ +#define ELSA_ISDN_RESET 0x20 /* Bit 5 Reset-Leitung */ +#define ELSA_ENA_TIMER_INT 0x80 /* Bit 7 Freigabe Timer Interrupt */ + +/* ALE-Register (Read) */ +#define ELSA_HW_RELEASE 0x07 /* Bit 0-2 Hardwarerkennung */ +#define ELSA_S0_POWER_BAD 0x08 /* Bit 3 S0-Bus Spannung fehlt */ + +/* Status Flags */ +#define ELSA_TIMER_AKTIV 1 +#define ELSA_BAD_PWR 2 +#define ELSA_ASSIGN 4 + +#define RS_ISR_PASS_LIMIT 256 +#define _INLINE_ inline +#define FLG_MODEM_ACTIVE 1 +/* IPAC AUX */ +#define ELSA_IPAC_LINE_LED 0x40 /* Bit 6 Gelbe LED */ +#define ELSA_IPAC_STAT_LED 0x80 /* Bit 7 Gruene LED */ + +#if ARCOFI_USE +static struct arcofi_msg ARCOFI_XOP_F = + {NULL,0,2,{0xa1,0x3f,0,0,0,0,0,0,0,0}}; /* Normal OP */ +static struct arcofi_msg ARCOFI_XOP_1 = + {&ARCOFI_XOP_F,0,2,{0xa1,0x31,0,0,0,0,0,0,0,0}}; /* PWR UP */ +static struct arcofi_msg ARCOFI_SOP_F = + {&ARCOFI_XOP_1,0,10,{0xa1,0x1f,0x00,0x50,0x10,0x00,0x00,0x80,0x02,0x12}}; +static struct arcofi_msg ARCOFI_COP_9 = + {&ARCOFI_SOP_F,0,10,{0xa1,0x29,0x80,0xcb,0xe9,0x88,0x00,0xc8,0xd8,0x80}}; /* RX */ +static struct arcofi_msg ARCOFI_COP_8 = + {&ARCOFI_COP_9,0,10,{0xa1,0x28,0x49,0x31,0x8,0x13,0x6e,0x88,0x2a,0x61}}; /* TX */ +static struct arcofi_msg ARCOFI_COP_7 = + {&ARCOFI_COP_8,0,4,{0xa1,0x27,0x80,0x80,0,0,0,0,0,0}}; /* GZ */ +static struct arcofi_msg ARCOFI_COP_6 = + {&ARCOFI_COP_7,0,6,{0xa1,0x26,0,0,0x82,0x7c,0,0,0,0}}; /* GRL GRH */ +static struct arcofi_msg ARCOFI_COP_5 = + {&ARCOFI_COP_6,0,4,{0xa1,0x25,0xbb,0x4a,0,0,0,0,0,0}}; /* GTX */ +static struct arcofi_msg ARCOFI_VERSION = + {NULL,1,2,{0xa0,0,0,0,0,0,0,0,0,0}}; +static struct arcofi_msg ARCOFI_XOP_0 = + {NULL,0,2,{0xa1,0x30,0,0,0,0,0,0,0,0}}; /* PWR Down */ + +static void set_arcofi(struct IsdnCardState *cs, int bc); + +#include "elsa_ser.c" +#endif /* ARCOFI_USE */ + +static inline u_char +readreg(unsigned int ale, unsigned int adr, u_char off) +{ + register u_char ret; + + byteout(ale, off); + ret = bytein(adr); + return (ret); +} + +static inline void +readfifo(unsigned int ale, unsigned int adr, u_char off, u_char * data, int size) +{ + byteout(ale, off); + insb(adr, data, size); +} + + +static inline void +writereg(unsigned int ale, unsigned int adr, u_char off, u_char data) +{ + byteout(ale, off); + byteout(adr, data); +} + +static inline void +writefifo(unsigned int ale, unsigned int adr, u_char off, u_char * data, int size) +{ + byteout(ale, off); + outsb(adr, data, size); +} + +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + return (readreg(cs->hw.elsa.ale, cs->hw.elsa.isac, offset)); +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writereg(cs->hw.elsa.ale, cs->hw.elsa.isac, offset, value); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + readfifo(cs->hw.elsa.ale, cs->hw.elsa.isac, 0, data, size); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + writefifo(cs->hw.elsa.ale, cs->hw.elsa.isac, 0, data, size); +} + +static u_char +ReadISAC_IPAC(struct IsdnCardState *cs, u_char offset) +{ + return (readreg(cs->hw.elsa.ale, cs->hw.elsa.isac, offset+0x80)); +} + +static void +WriteISAC_IPAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writereg(cs->hw.elsa.ale, cs->hw.elsa.isac, offset|0x80, value); +} + +static void +ReadISACfifo_IPAC(struct IsdnCardState *cs, u_char * data, int size) +{ + readfifo(cs->hw.elsa.ale, cs->hw.elsa.isac, 0x80, data, size); +} + +static void +WriteISACfifo_IPAC(struct IsdnCardState *cs, u_char * data, int size) +{ + writefifo(cs->hw.elsa.ale, cs->hw.elsa.isac, 0x80, data, size); +} + +static u_char +ReadHSCX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + return (readreg(cs->hw.elsa.ale, + cs->hw.elsa.hscx, offset + (hscx ? 0x40 : 0))); +} + +static void +WriteHSCX(struct IsdnCardState *cs, int hscx, u_char offset, u_char value) +{ + writereg(cs->hw.elsa.ale, + cs->hw.elsa.hscx, offset + (hscx ? 0x40 : 0), value); +} + +static inline u_char +readitac(struct IsdnCardState *cs, u_char off) +{ + register u_char ret; + + byteout(cs->hw.elsa.ale, off); + ret = bytein(cs->hw.elsa.itac); + return (ret); +} + +static inline void +writeitac(struct IsdnCardState *cs, u_char off, u_char data) +{ + byteout(cs->hw.elsa.ale, off); + byteout(cs->hw.elsa.itac, data); +} + +static inline int +TimerRun(struct IsdnCardState *cs) +{ + register u_char v; + + v = bytein(cs->hw.elsa.cfg); + if ((cs->subtyp == ELSA_QS1000) || (cs->subtyp == ELSA_QS3000)) + return (0 == (v & ELSA_TIMER_RUN)); + else if (cs->subtyp == ELSA_PCC8) + return (v & ELSA_TIMER_RUN_PCC8); + return (v & ELSA_TIMER_RUN); +} +/* + * fast interrupt HSCX stuff goes here + */ + +#define READHSCX(cs, nr, reg) readreg(cs->hw.elsa.ale, \ + cs->hw.elsa.hscx, reg + (nr ? 0x40 : 0)) +#define WRITEHSCX(cs, nr, reg, data) writereg(cs->hw.elsa.ale, \ + cs->hw.elsa.hscx, reg + (nr ? 0x40 : 0), data) + +#define READHSCXFIFO(cs, nr, ptr, cnt) readfifo(cs->hw.elsa.ale, \ + cs->hw.elsa.hscx, (nr ? 0x40 : 0), ptr, cnt) + +#define WRITEHSCXFIFO(cs, nr, ptr, cnt) writefifo(cs->hw.elsa.ale, \ + cs->hw.elsa.hscx, (nr ? 0x40 : 0), ptr, cnt) + +#include "hscx_irq.c" + +static irqreturn_t +elsa_interrupt(int intno, void *dev_id, struct pt_regs *regs) +{ + struct IsdnCardState *cs = dev_id; + u_long flags; + u_char val; + int icnt=5; + + if ((cs->typ == ISDN_CTYPE_ELSA_PCMCIA) && (*cs->busy_flag == 1)) { + /* The card tends to generate interrupts while being removed + causing us to just crash the kernel. bad. */ + printk(KERN_WARNING "Elsa: card not available!\n"); + return IRQ_NONE; + } + spin_lock_irqsave(&cs->lock, flags); +#if ARCOFI_USE + if (cs->hw.elsa.MFlag) { + val = serial_inp(cs, UART_IIR); + if (!(val & UART_IIR_NO_INT)) { + debugl1(cs,"IIR %02x", val); + rs_interrupt_elsa(intno, cs); + } + } +#endif + val = readreg(cs->hw.elsa.ale, cs->hw.elsa.hscx, HSCX_ISTA + 0x40); + Start_HSCX: + if (val) { + hscx_int_main(cs, val); + } + val = readreg(cs->hw.elsa.ale, cs->hw.elsa.isac, ISAC_ISTA); + Start_ISAC: + if (val) { + isac_interrupt(cs, val); + } + val = readreg(cs->hw.elsa.ale, cs->hw.elsa.hscx, HSCX_ISTA + 0x40); + if (val && icnt) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HSCX IntStat after IntRoutine"); + icnt--; + goto Start_HSCX; + } + val = readreg(cs->hw.elsa.ale, cs->hw.elsa.isac, ISAC_ISTA); + if (val && icnt) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ISAC IntStat after IntRoutine"); + icnt--; + goto Start_ISAC; + } + if (!icnt) + printk(KERN_WARNING"ELSA IRQ LOOP\n"); + writereg(cs->hw.elsa.ale, cs->hw.elsa.hscx, HSCX_MASK, 0xFF); + writereg(cs->hw.elsa.ale, cs->hw.elsa.hscx, HSCX_MASK + 0x40, 0xFF); + writereg(cs->hw.elsa.ale, cs->hw.elsa.isac, ISAC_MASK, 0xFF); + if (cs->hw.elsa.status & ELSA_TIMER_AKTIV) { + if (!TimerRun(cs)) { + /* Timer Restart */ + byteout(cs->hw.elsa.timer, 0); + cs->hw.elsa.counter++; + } + } +#if ARCOFI_USE + if (cs->hw.elsa.MFlag) { + val = serial_inp(cs, UART_MCR); + val ^= 0x8; + serial_outp(cs, UART_MCR, val); + val = serial_inp(cs, UART_MCR); + val ^= 0x8; + serial_outp(cs, UART_MCR, val); + } +#endif + if (cs->hw.elsa.trig) + byteout(cs->hw.elsa.trig, 0x00); + writereg(cs->hw.elsa.ale, cs->hw.elsa.hscx, HSCX_MASK, 0x0); + writereg(cs->hw.elsa.ale, cs->hw.elsa.hscx, HSCX_MASK + 0x40, 0x0); + writereg(cs->hw.elsa.ale, cs->hw.elsa.isac, ISAC_MASK, 0x0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static irqreturn_t +elsa_interrupt_ipac(int intno, void *dev_id, struct pt_regs *regs) +{ + struct IsdnCardState *cs = dev_id; + u_long flags; + u_char ista,val; + int icnt=5; + + spin_lock_irqsave(&cs->lock, flags); + if (cs->subtyp == ELSA_QS1000PCI || cs->subtyp == ELSA_QS3000PCI) { + val = bytein(cs->hw.elsa.cfg + 0x4c); /* PCI IRQ */ + if (!(val & ELSA_PCI_IRQ_MASK)) { + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_NONE; + } + } +#if ARCOFI_USE + if (cs->hw.elsa.MFlag) { + val = serial_inp(cs, UART_IIR); + if (!(val & UART_IIR_NO_INT)) { + debugl1(cs,"IIR %02x", val); + rs_interrupt_elsa(intno, cs); + } + } +#endif + ista = readreg(cs->hw.elsa.ale, cs->hw.elsa.isac, IPAC_ISTA); +Start_IPAC: + if (cs->debug & L1_DEB_IPAC) + debugl1(cs, "IPAC ISTA %02X", ista); + if (ista & 0x0f) { + val = readreg(cs->hw.elsa.ale, cs->hw.elsa.hscx, HSCX_ISTA + 0x40); + if (ista & 0x01) + val |= 0x01; + if (ista & 0x04) + val |= 0x02; + if (ista & 0x08) + val |= 0x04; + if (val) + hscx_int_main(cs, val); + } + if (ista & 0x20) { + val = 0xfe & readreg(cs->hw.elsa.ale, cs->hw.elsa.isac, ISAC_ISTA + 0x80); + if (val) { + isac_interrupt(cs, val); + } + } + if (ista & 0x10) { + val = 0x01; + isac_interrupt(cs, val); + } + ista = readreg(cs->hw.elsa.ale, cs->hw.elsa.isac, IPAC_ISTA); + if ((ista & 0x3f) && icnt) { + icnt--; + goto Start_IPAC; + } + if (!icnt) + printk(KERN_WARNING "ELSA IRQ LOOP\n"); + writereg(cs->hw.elsa.ale, cs->hw.elsa.isac, IPAC_MASK, 0xFF); + writereg(cs->hw.elsa.ale, cs->hw.elsa.isac, IPAC_MASK, 0xC0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +void +release_io_elsa(struct IsdnCardState *cs) +{ + int bytecnt = 8; + + del_timer(&cs->hw.elsa.tl); +#if ARCOFI_USE + clear_arcofi(cs); +#endif + if (cs->hw.elsa.ctrl) + byteout(cs->hw.elsa.ctrl, 0); /* LEDs Out */ + if (cs->subtyp == ELSA_QS1000PCI) { + byteout(cs->hw.elsa.cfg + 0x4c, 0x01); /* disable IRQ */ + writereg(cs->hw.elsa.ale, cs->hw.elsa.isac, IPAC_ATX, 0xff); + bytecnt = 2; + release_region(cs->hw.elsa.cfg, 0x80); + } + if (cs->subtyp == ELSA_QS3000PCI) { + byteout(cs->hw.elsa.cfg + 0x4c, 0x03); /* disable ELSA PCI IRQ */ + writereg(cs->hw.elsa.ale, cs->hw.elsa.isac, IPAC_ATX, 0xff); + release_region(cs->hw.elsa.cfg, 0x80); + } + if (cs->subtyp == ELSA_PCMCIA_IPAC) { + writereg(cs->hw.elsa.ale, cs->hw.elsa.isac, IPAC_ATX, 0xff); + } + if ((cs->subtyp == ELSA_PCFPRO) || + (cs->subtyp == ELSA_QS3000) || + (cs->subtyp == ELSA_PCF) || + (cs->subtyp == ELSA_QS3000PCI)) { + bytecnt = 16; +#if ARCOFI_USE + release_modem(cs); +#endif + } + if (cs->hw.elsa.base) + release_region(cs->hw.elsa.base, bytecnt); +} + +static void +reset_elsa(struct IsdnCardState *cs) +{ + if (cs->hw.elsa.timer) { + /* Wait 1 Timer */ + byteout(cs->hw.elsa.timer, 0); + while (TimerRun(cs)); + cs->hw.elsa.ctrl_reg |= 0x50; + cs->hw.elsa.ctrl_reg &= ~ELSA_ISDN_RESET; /* Reset On */ + byteout(cs->hw.elsa.ctrl, cs->hw.elsa.ctrl_reg); + /* Wait 1 Timer */ + byteout(cs->hw.elsa.timer, 0); + while (TimerRun(cs)); + cs->hw.elsa.ctrl_reg |= ELSA_ISDN_RESET; /* Reset Off */ + byteout(cs->hw.elsa.ctrl, cs->hw.elsa.ctrl_reg); + /* Wait 1 Timer */ + byteout(cs->hw.elsa.timer, 0); + while (TimerRun(cs)); + if (cs->hw.elsa.trig) + byteout(cs->hw.elsa.trig, 0xff); + } + if ((cs->subtyp == ELSA_QS1000PCI) || (cs->subtyp == ELSA_QS3000PCI) || (cs->subtyp == ELSA_PCMCIA_IPAC)) { + writereg(cs->hw.elsa.ale, cs->hw.elsa.isac, IPAC_POTA2, 0x20); + mdelay(10); + writereg(cs->hw.elsa.ale, cs->hw.elsa.isac, IPAC_POTA2, 0x00); + writereg(cs->hw.elsa.ale, cs->hw.elsa.isac, IPAC_MASK, 0xc0); + mdelay(10); + if (cs->subtyp != ELSA_PCMCIA_IPAC) { + writereg(cs->hw.elsa.ale, cs->hw.elsa.isac, IPAC_ACFG, 0x0); + writereg(cs->hw.elsa.ale, cs->hw.elsa.isac, IPAC_AOE, 0x3c); + } else { + writereg(cs->hw.elsa.ale, cs->hw.elsa.isac, IPAC_PCFG, 0x10); + writereg(cs->hw.elsa.ale, cs->hw.elsa.isac, IPAC_ACFG, 0x4); + writereg(cs->hw.elsa.ale, cs->hw.elsa.isac, IPAC_AOE, 0xf8); + } + writereg(cs->hw.elsa.ale, cs->hw.elsa.isac, IPAC_ATX, 0xff); + if (cs->subtyp == ELSA_QS1000PCI) + byteout(cs->hw.elsa.cfg + 0x4c, 0x41); /* enable ELSA PCI IRQ */ + else if (cs->subtyp == ELSA_QS3000PCI) + byteout(cs->hw.elsa.cfg + 0x4c, 0x43); /* enable ELSA PCI IRQ */ + } +} + +#if ARCOFI_USE + +static void +set_arcofi(struct IsdnCardState *cs, int bc) { + cs->dc.isac.arcofi_bc = bc; + arcofi_fsm(cs, ARCOFI_START, &ARCOFI_COP_5); + interruptible_sleep_on(&cs->dc.isac.arcofi_wait); +} + +static int +check_arcofi(struct IsdnCardState *cs) +{ + int arcofi_present = 0; + char tmp[40]; + char *t; + u_char *p; + + if (!cs->dc.isac.mon_tx) + if (!(cs->dc.isac.mon_tx=kmalloc(MAX_MON_FRAME, GFP_ATOMIC))) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "ISAC MON TX out of buffers!"); + return(0); + } + cs->dc.isac.arcofi_bc = 0; + arcofi_fsm(cs, ARCOFI_START, &ARCOFI_VERSION); + interruptible_sleep_on(&cs->dc.isac.arcofi_wait); + if (!test_and_clear_bit(FLG_ARCOFI_ERROR, &cs->HW_Flags)) { + debugl1(cs, "Arcofi response received %d bytes", cs->dc.isac.mon_rxp); + p = cs->dc.isac.mon_rx; + t = tmp; + t += sprintf(tmp, "Arcofi data"); + QuickHex(t, p, cs->dc.isac.mon_rxp); + debugl1(cs, tmp); + if ((cs->dc.isac.mon_rxp == 2) && (cs->dc.isac.mon_rx[0] == 0xa0)) { + switch(cs->dc.isac.mon_rx[1]) { + case 0x80: + debugl1(cs, "Arcofi 2160 detected"); + arcofi_present = 1; + break; + case 0x82: + debugl1(cs, "Arcofi 2165 detected"); + arcofi_present = 2; + break; + case 0x84: + debugl1(cs, "Arcofi 2163 detected"); + arcofi_present = 3; + break; + default: + debugl1(cs, "unknown Arcofi response"); + break; + } + } else + debugl1(cs, "undefined Monitor response"); + cs->dc.isac.mon_rxp = 0; + } else if (cs->dc.isac.mon_tx) { + debugl1(cs, "Arcofi not detected"); + } + if (arcofi_present) { + if (cs->subtyp==ELSA_QS1000) { + cs->subtyp = ELSA_QS3000; + printk(KERN_INFO + "Elsa: %s detected modem at 0x%lx\n", + Elsa_Types[cs->subtyp], + cs->hw.elsa.base+8); + release_region(cs->hw.elsa.base, 8); + if (!request_region(cs->hw.elsa.base, 16, "elsa isdn modem")) { + printk(KERN_WARNING + "HiSax: %s config port %lx-%lx already in use\n", + Elsa_Types[cs->subtyp], + cs->hw.elsa.base + 8, + cs->hw.elsa.base + 16); + } + } else if (cs->subtyp==ELSA_PCC16) { + cs->subtyp = ELSA_PCF; + printk(KERN_INFO + "Elsa: %s detected modem at 0x%lx\n", + Elsa_Types[cs->subtyp], + cs->hw.elsa.base+8); + release_region(cs->hw.elsa.base, 8); + if (!request_region(cs->hw.elsa.base, 16, "elsa isdn modem")) { + printk(KERN_WARNING + "HiSax: %s config port %lx-%lx already in use\n", + Elsa_Types[cs->subtyp], + cs->hw.elsa.base + 8, + cs->hw.elsa.base + 16); + } + } else + printk(KERN_INFO + "Elsa: %s detected modem at 0x%lx\n", + Elsa_Types[cs->subtyp], + cs->hw.elsa.base+8); + arcofi_fsm(cs, ARCOFI_START, &ARCOFI_XOP_0); + interruptible_sleep_on(&cs->dc.isac.arcofi_wait); + return(1); + } + return(0); +} +#endif /* ARCOFI_USE */ + +static void +elsa_led_handler(struct IsdnCardState *cs) +{ + int blink = 0; + + if (cs->subtyp == ELSA_PCMCIA || cs->subtyp == ELSA_PCMCIA_IPAC) + return; + del_timer(&cs->hw.elsa.tl); + if (cs->hw.elsa.status & ELSA_ASSIGN) + cs->hw.elsa.ctrl_reg |= ELSA_STAT_LED; + else if (cs->hw.elsa.status & ELSA_BAD_PWR) + cs->hw.elsa.ctrl_reg &= ~ELSA_STAT_LED; + else { + cs->hw.elsa.ctrl_reg ^= ELSA_STAT_LED; + blink = 250; + } + if (cs->hw.elsa.status & 0xf000) + cs->hw.elsa.ctrl_reg |= ELSA_LINE_LED; + else if (cs->hw.elsa.status & 0x0f00) { + cs->hw.elsa.ctrl_reg ^= ELSA_LINE_LED; + blink = 500; + } else + cs->hw.elsa.ctrl_reg &= ~ELSA_LINE_LED; + + if ((cs->subtyp == ELSA_QS1000PCI) || + (cs->subtyp == ELSA_QS3000PCI)) { + u_char led = 0xff; + if (cs->hw.elsa.ctrl_reg & ELSA_LINE_LED) + led ^= ELSA_IPAC_LINE_LED; + if (cs->hw.elsa.ctrl_reg & ELSA_STAT_LED) + led ^= ELSA_IPAC_STAT_LED; + writereg(cs->hw.elsa.ale, cs->hw.elsa.isac, IPAC_ATX, led); + } else + byteout(cs->hw.elsa.ctrl, cs->hw.elsa.ctrl_reg); + if (blink) { + init_timer(&cs->hw.elsa.tl); + cs->hw.elsa.tl.expires = jiffies + ((blink * HZ) / 1000); + add_timer(&cs->hw.elsa.tl); + } +} + +static int +Elsa_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + int ret = 0; + u_long flags; + + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + reset_elsa(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return(0); + case CARD_RELEASE: + release_io_elsa(cs); + return(0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + cs->debug |= L1_DEB_IPAC; + reset_elsa(cs); + inithscxisac(cs, 1); + if ((cs->subtyp == ELSA_QS1000) || + (cs->subtyp == ELSA_QS3000)) + { + byteout(cs->hw.elsa.timer, 0); + } + if (cs->hw.elsa.trig) + byteout(cs->hw.elsa.trig, 0xff); + inithscxisac(cs, 2); + spin_unlock_irqrestore(&cs->lock, flags); + return(0); + case CARD_TEST: + if ((cs->subtyp == ELSA_PCMCIA) || + (cs->subtyp == ELSA_PCMCIA_IPAC) || + (cs->subtyp == ELSA_QS1000PCI)) { + return(0); + } else if (cs->subtyp == ELSA_QS3000PCI) { + ret = 0; + } else { + spin_lock_irqsave(&cs->lock, flags); + cs->hw.elsa.counter = 0; + cs->hw.elsa.ctrl_reg |= ELSA_ENA_TIMER_INT; + cs->hw.elsa.status |= ELSA_TIMER_AKTIV; + byteout(cs->hw.elsa.ctrl, cs->hw.elsa.ctrl_reg); + byteout(cs->hw.elsa.timer, 0); + spin_unlock_irqrestore(&cs->lock, flags); + msleep(110); + spin_lock_irqsave(&cs->lock, flags); + cs->hw.elsa.ctrl_reg &= ~ELSA_ENA_TIMER_INT; + byteout(cs->hw.elsa.ctrl, cs->hw.elsa.ctrl_reg); + cs->hw.elsa.status &= ~ELSA_TIMER_AKTIV; + spin_unlock_irqrestore(&cs->lock, flags); + printk(KERN_INFO "Elsa: %d timer tics in 110 msek\n", + cs->hw.elsa.counter); + if ((cs->hw.elsa.counter > 10) && + (cs->hw.elsa.counter < 16)) { + printk(KERN_INFO "Elsa: timer and irq OK\n"); + ret = 0; + } else { + printk(KERN_WARNING + "Elsa: timer tic problem (%d/12) maybe an IRQ(%d) conflict\n", + cs->hw.elsa.counter, cs->irq); + ret = 1; + } + } +#if ARCOFI_USE + if (check_arcofi(cs)) { + init_modem(cs); + } +#endif + elsa_led_handler(cs); + return(ret); + case (MDL_REMOVE | REQUEST): + cs->hw.elsa.status &= 0; + break; + case (MDL_ASSIGN | REQUEST): + cs->hw.elsa.status |= ELSA_ASSIGN; + break; + case MDL_INFO_SETUP: + if ((long) arg) + cs->hw.elsa.status |= 0x0200; + else + cs->hw.elsa.status |= 0x0100; + break; + case MDL_INFO_CONN: + if ((long) arg) + cs->hw.elsa.status |= 0x2000; + else + cs->hw.elsa.status |= 0x1000; + break; + case MDL_INFO_REL: + if ((long) arg) { + cs->hw.elsa.status &= ~0x2000; + cs->hw.elsa.status &= ~0x0200; + } else { + cs->hw.elsa.status &= ~0x1000; + cs->hw.elsa.status &= ~0x0100; + } + break; +#if ARCOFI_USE + case CARD_AUX_IND: + if (cs->hw.elsa.MFlag) { + int len; + u_char *msg; + + if (!arg) + return(0); + msg = arg; + len = *msg; + msg++; + modem_write_cmd(cs, msg, len); + } + break; +#endif + } + if (cs->typ == ISDN_CTYPE_ELSA) { + int pwr = bytein(cs->hw.elsa.ale); + if (pwr & 0x08) + cs->hw.elsa.status |= ELSA_BAD_PWR; + else + cs->hw.elsa.status &= ~ELSA_BAD_PWR; + } + elsa_led_handler(cs); + return(ret); +} + +static unsigned char +probe_elsa_adr(unsigned int adr, int typ) +{ + int i, in1, in2, p16_1 = 0, p16_2 = 0, p8_1 = 0, p8_2 = 0, pc_1 = 0, + pc_2 = 0, pfp_1 = 0, pfp_2 = 0; + + /* In case of the elsa pcmcia card, this region is in use, + reserved for us by the card manager. So we do not check it + here, it would fail. */ + if (typ != ISDN_CTYPE_ELSA_PCMCIA) { + if (request_region(adr, 8, "elsa card")) { + release_region(adr, 8); + } else { + printk(KERN_WARNING + "Elsa: Probing Port 0x%x: already in use\n", adr); + return (0); + } + } + for (i = 0; i < 16; i++) { + in1 = inb(adr + ELSA_CONFIG); /* 'toggelt' bei */ + in2 = inb(adr + ELSA_CONFIG); /* jedem Zugriff */ + p16_1 += 0x04 & in1; + p16_2 += 0x04 & in2; + p8_1 += 0x02 & in1; + p8_2 += 0x02 & in2; + pc_1 += 0x01 & in1; + pc_2 += 0x01 & in2; + pfp_1 += 0x40 & in1; + pfp_2 += 0x40 & in2; + } + printk(KERN_INFO "Elsa: Probing IO 0x%x", adr); + if (65 == ++p16_1 * ++p16_2) { + printk(" PCC-16/PCF found\n"); + return (ELSA_PCC16); + } else if (1025 == ++pfp_1 * ++pfp_2) { + printk(" PCF-Pro found\n"); + return (ELSA_PCFPRO); + } else if (33 == ++p8_1 * ++p8_2) { + printk(" PCC8 found\n"); + return (ELSA_PCC8); + } else if (17 == ++pc_1 * ++pc_2) { + printk(" PC found\n"); + return (ELSA_PC); + } else { + printk(" failed\n"); + return (0); + } +} + +static unsigned int +probe_elsa(struct IsdnCardState *cs) +{ + int i; + unsigned int CARD_portlist[] = + {0x160, 0x170, 0x260, 0x360, 0}; + + for (i = 0; CARD_portlist[i]; i++) { + if ((cs->subtyp = probe_elsa_adr(CARD_portlist[i], cs->typ))) + break; + } + return (CARD_portlist[i]); +} + +static struct pci_dev *dev_qs1000 __devinitdata = NULL; +static struct pci_dev *dev_qs3000 __devinitdata = NULL; + +#ifdef __ISAPNP__ +static struct isapnp_device_id elsa_ids[] __devinitdata = { + { ISAPNP_VENDOR('E', 'L', 'S'), ISAPNP_FUNCTION(0x0133), + ISAPNP_VENDOR('E', 'L', 'S'), ISAPNP_FUNCTION(0x0133), + (unsigned long) "Elsa QS1000" }, + { ISAPNP_VENDOR('E', 'L', 'S'), ISAPNP_FUNCTION(0x0134), + ISAPNP_VENDOR('E', 'L', 'S'), ISAPNP_FUNCTION(0x0134), + (unsigned long) "Elsa QS3000" }, + { 0, } +}; + +static struct isapnp_device_id *ipid __devinitdata = &elsa_ids[0]; +static struct pnp_card *pnp_c __devinitdata = NULL; +#endif + +int __devinit +setup_elsa(struct IsdnCard *card) +{ + int bytecnt; + u_char val; + struct IsdnCardState *cs = card->cs; + char tmp[64]; + + strcpy(tmp, Elsa_revision); + printk(KERN_INFO "HiSax: Elsa driver Rev. %s\n", HiSax_getrev(tmp)); + cs->hw.elsa.ctrl_reg = 0; + cs->hw.elsa.status = 0; + cs->hw.elsa.MFlag = 0; + cs->subtyp = 0; + if (cs->typ == ISDN_CTYPE_ELSA) { + cs->hw.elsa.base = card->para[0]; + printk(KERN_INFO "Elsa: Microlink IO probing\n"); + if (cs->hw.elsa.base) { + if (!(cs->subtyp = probe_elsa_adr(cs->hw.elsa.base, + cs->typ))) { + printk(KERN_WARNING + "Elsa: no Elsa Microlink at %#lx\n", + cs->hw.elsa.base); + return (0); + } + } else + cs->hw.elsa.base = probe_elsa(cs); + if (cs->hw.elsa.base) { + cs->hw.elsa.cfg = cs->hw.elsa.base + ELSA_CONFIG; + cs->hw.elsa.ctrl = cs->hw.elsa.base + ELSA_CONTROL; + cs->hw.elsa.ale = cs->hw.elsa.base + ELSA_ALE; + cs->hw.elsa.isac = cs->hw.elsa.base + ELSA_ISAC; + cs->hw.elsa.itac = cs->hw.elsa.base + ELSA_ITAC; + cs->hw.elsa.hscx = cs->hw.elsa.base + ELSA_HSCX; + cs->hw.elsa.trig = cs->hw.elsa.base + ELSA_TRIG_IRQ; + cs->hw.elsa.timer = cs->hw.elsa.base + ELSA_START_TIMER; + val = bytein(cs->hw.elsa.cfg); + if (cs->subtyp == ELSA_PC) { + const u_char CARD_IrqTab[8] = + {7, 3, 5, 9, 0, 0, 0, 0}; + cs->irq = CARD_IrqTab[(val & ELSA_IRQ_IDX_PC) >> 2]; + } else if (cs->subtyp == ELSA_PCC8) { + const u_char CARD_IrqTab[8] = + {7, 3, 5, 9, 0, 0, 0, 0}; + cs->irq = CARD_IrqTab[(val & ELSA_IRQ_IDX_PCC8) >> 4]; + } else { + const u_char CARD_IrqTab[8] = + {15, 10, 15, 3, 11, 5, 11, 9}; + cs->irq = CARD_IrqTab[(val & ELSA_IRQ_IDX) >> 3]; + } + val = bytein(cs->hw.elsa.ale) & ELSA_HW_RELEASE; + if (val < 3) + val |= 8; + val += 'A' - 3; + if (val == 'B' || val == 'C') + val ^= 1; + if ((cs->subtyp == ELSA_PCFPRO) && (val = 'G')) + val = 'C'; + printk(KERN_INFO + "Elsa: %s found at %#lx Rev.:%c IRQ %d\n", + Elsa_Types[cs->subtyp], + cs->hw.elsa.base, + val, cs->irq); + val = bytein(cs->hw.elsa.ale) & ELSA_S0_POWER_BAD; + if (val) { + printk(KERN_WARNING + "Elsa: Microlink S0 bus power bad\n"); + cs->hw.elsa.status |= ELSA_BAD_PWR; + } + } else { + printk(KERN_WARNING + "No Elsa Microlink found\n"); + return (0); + } + } else if (cs->typ == ISDN_CTYPE_ELSA_PNP) { +#ifdef __ISAPNP__ + if (!card->para[1] && isapnp_present()) { + struct pnp_dev *pnp_d; + while(ipid->card_vendor) { + if ((pnp_c = pnp_find_card(ipid->card_vendor, + ipid->card_device, pnp_c))) { + pnp_d = NULL; + if ((pnp_d = pnp_find_dev(pnp_c, + ipid->vendor, ipid->function, pnp_d))) { + int err; + + printk(KERN_INFO "HiSax: %s detected\n", + (char *)ipid->driver_data); + pnp_disable_dev(pnp_d); + err = pnp_activate_dev(pnp_d); + if (err<0) { + printk(KERN_WARNING "%s: pnp_activate_dev ret(%d)\n", + __FUNCTION__, err); + return(0); + } + card->para[1] = pnp_port_start(pnp_d, 0); + card->para[0] = pnp_irq(pnp_d, 0); + + if (!card->para[0] || !card->para[1]) { + printk(KERN_ERR "Elsa PnP:some resources are missing %ld/%lx\n", + card->para[0], card->para[1]); + pnp_disable_dev(pnp_d); + return(0); + } + if (ipid->function == ISAPNP_FUNCTION(0x133)) + cs->subtyp = ELSA_QS1000; + else + cs->subtyp = ELSA_QS3000; + break; + } else { + printk(KERN_ERR "Elsa PnP: PnP error card found, no device\n"); + return(0); + } + } + ipid++; + pnp_c=NULL; + } + if (!ipid->card_vendor) { + printk(KERN_INFO "Elsa PnP: no ISAPnP card found\n"); + return(0); + } + } +#endif + if (card->para[1] && card->para[0]) { + cs->hw.elsa.base = card->para[1]; + cs->irq = card->para[0]; + if (!cs->subtyp) + cs->subtyp = ELSA_QS1000; + } else { + printk(KERN_ERR "Elsa PnP: no parameter\n"); + } + cs->hw.elsa.cfg = cs->hw.elsa.base + ELSA_CONFIG; + cs->hw.elsa.ale = cs->hw.elsa.base + ELSA_ALE; + cs->hw.elsa.isac = cs->hw.elsa.base + ELSA_ISAC; + cs->hw.elsa.hscx = cs->hw.elsa.base + ELSA_HSCX; + cs->hw.elsa.trig = cs->hw.elsa.base + ELSA_TRIG_IRQ; + cs->hw.elsa.timer = cs->hw.elsa.base + ELSA_START_TIMER; + cs->hw.elsa.ctrl = cs->hw.elsa.base + ELSA_CONTROL; + printk(KERN_INFO + "Elsa: %s defined at %#lx IRQ %d\n", + Elsa_Types[cs->subtyp], + cs->hw.elsa.base, + cs->irq); + } else if (cs->typ == ISDN_CTYPE_ELSA_PCMCIA) { + cs->hw.elsa.base = card->para[1]; + cs->irq = card->para[0]; + val = readreg(cs->hw.elsa.base + 0, cs->hw.elsa.base + 2, IPAC_ID); + if ((val == 1) || (val == 2)) { /* IPAC version 1.1/1.2 */ + cs->subtyp = ELSA_PCMCIA_IPAC; + cs->hw.elsa.ale = cs->hw.elsa.base + 0; + cs->hw.elsa.isac = cs->hw.elsa.base + 2; + cs->hw.elsa.hscx = cs->hw.elsa.base + 2; + test_and_set_bit(HW_IPAC, &cs->HW_Flags); + } else { + cs->subtyp = ELSA_PCMCIA; + cs->hw.elsa.ale = cs->hw.elsa.base + ELSA_ALE_PCM; + cs->hw.elsa.isac = cs->hw.elsa.base + ELSA_ISAC_PCM; + cs->hw.elsa.hscx = cs->hw.elsa.base + ELSA_HSCX; + } + cs->hw.elsa.timer = 0; + cs->hw.elsa.trig = 0; + cs->hw.elsa.ctrl = 0; + cs->irq_flags |= SA_SHIRQ; + printk(KERN_INFO + "Elsa: %s defined at %#lx IRQ %d\n", + Elsa_Types[cs->subtyp], + cs->hw.elsa.base, + cs->irq); + } else if (cs->typ == ISDN_CTYPE_ELSA_PCI) { +#ifdef CONFIG_PCI + cs->subtyp = 0; + if ((dev_qs1000 = pci_find_device(PCI_VENDOR_ID_ELSA, + PCI_DEVICE_ID_ELSA_MICROLINK, dev_qs1000))) { + if (pci_enable_device(dev_qs1000)) + return(0); + cs->subtyp = ELSA_QS1000PCI; + cs->irq = dev_qs1000->irq; + cs->hw.elsa.cfg = pci_resource_start(dev_qs1000, 1); + cs->hw.elsa.base = pci_resource_start(dev_qs1000, 3); + } else if ((dev_qs3000 = pci_find_device(PCI_VENDOR_ID_ELSA, + PCI_DEVICE_ID_ELSA_QS3000, dev_qs3000))) { + if (pci_enable_device(dev_qs3000)) + return(0); + cs->subtyp = ELSA_QS3000PCI; + cs->irq = dev_qs3000->irq; + cs->hw.elsa.cfg = pci_resource_start(dev_qs3000, 1); + cs->hw.elsa.base = pci_resource_start(dev_qs3000, 3); + } else { + printk(KERN_WARNING "Elsa: No PCI card found\n"); + return(0); + } + if (!cs->irq) { + printk(KERN_WARNING "Elsa: No IRQ for PCI card found\n"); + return(0); + } + + if (!(cs->hw.elsa.base && cs->hw.elsa.cfg)) { + printk(KERN_WARNING "Elsa: No IO-Adr for PCI card found\n"); + return(0); + } + if ((cs->hw.elsa.cfg & 0xff) || (cs->hw.elsa.base & 0xf)) { + printk(KERN_WARNING "Elsa: You may have a wrong PCI bios\n"); + printk(KERN_WARNING "Elsa: If your system hangs now, read\n"); + printk(KERN_WARNING "Elsa: Documentation/isdn/README.HiSax\n"); + } + cs->hw.elsa.ale = cs->hw.elsa.base; + cs->hw.elsa.isac = cs->hw.elsa.base +1; + cs->hw.elsa.hscx = cs->hw.elsa.base +1; + test_and_set_bit(HW_IPAC, &cs->HW_Flags); + cs->hw.elsa.timer = 0; + cs->hw.elsa.trig = 0; + cs->irq_flags |= SA_SHIRQ; + printk(KERN_INFO + "Elsa: %s defined at %#lx/0x%x IRQ %d\n", + Elsa_Types[cs->subtyp], + cs->hw.elsa.base, + cs->hw.elsa.cfg, + cs->irq); +#else + printk(KERN_WARNING "Elsa: Elsa PCI and NO_PCI_BIOS\n"); + printk(KERN_WARNING "Elsa: unable to config Elsa PCI\n"); + return (0); +#endif /* CONFIG_PCI */ + } else + return (0); + + switch (cs->subtyp) { + case ELSA_PC: + case ELSA_PCC8: + case ELSA_PCC16: + case ELSA_QS1000: + case ELSA_PCMCIA: + case ELSA_PCMCIA_IPAC: + bytecnt = 8; + break; + case ELSA_PCFPRO: + case ELSA_PCF: + case ELSA_QS3000: + case ELSA_QS3000PCI: + bytecnt = 16; + break; + case ELSA_QS1000PCI: + bytecnt = 2; + break; + default: + printk(KERN_WARNING + "Unknown ELSA subtype %d\n", cs->subtyp); + return (0); + } + /* In case of the elsa pcmcia card, this region is in use, + reserved for us by the card manager. So we do not check it + here, it would fail. */ + if (cs->typ != ISDN_CTYPE_ELSA_PCMCIA && !request_region(cs->hw.elsa.base, bytecnt, "elsa isdn")) { + printk(KERN_WARNING + "HiSax: %s config port %#lx-%#lx already in use\n", + CardType[card->typ], + cs->hw.elsa.base, + cs->hw.elsa.base + bytecnt); + return (0); + } + if ((cs->subtyp == ELSA_QS1000PCI) || (cs->subtyp == ELSA_QS3000PCI)) { + if (!request_region(cs->hw.elsa.cfg, 0x80, "elsa isdn pci")) { + printk(KERN_WARNING + "HiSax: %s pci port %x-%x already in use\n", + CardType[card->typ], + cs->hw.elsa.cfg, + cs->hw.elsa.cfg + 0x80); + release_region(cs->hw.elsa.base, bytecnt); + return (0); + } + } +#if ARCOFI_USE + init_arcofi(cs); +#endif + setup_isac(cs); + cs->hw.elsa.tl.function = (void *) elsa_led_handler; + cs->hw.elsa.tl.data = (long) cs; + init_timer(&cs->hw.elsa.tl); + /* Teste Timer */ + if (cs->hw.elsa.timer) { + byteout(cs->hw.elsa.trig, 0xff); + byteout(cs->hw.elsa.timer, 0); + if (!TimerRun(cs)) { + byteout(cs->hw.elsa.timer, 0); /* 2. Versuch */ + if (!TimerRun(cs)) { + printk(KERN_WARNING + "Elsa: timer do not start\n"); + release_io_elsa(cs); + return (0); + } + } + HZDELAY((HZ/100) + 1); /* wait >=10 ms */ + if (TimerRun(cs)) { + printk(KERN_WARNING "Elsa: timer do not run down\n"); + release_io_elsa(cs); + return (0); + } + printk(KERN_INFO "Elsa: timer OK; resetting card\n"); + } + cs->BC_Read_Reg = &ReadHSCX; + cs->BC_Write_Reg = &WriteHSCX; + cs->BC_Send_Data = &hscx_fill_fifo; + cs->cardmsg = &Elsa_card_msg; + if ((cs->subtyp == ELSA_QS1000PCI) || (cs->subtyp == ELSA_QS3000PCI) || (cs->subtyp == ELSA_PCMCIA_IPAC)) { + cs->readisac = &ReadISAC_IPAC; + cs->writeisac = &WriteISAC_IPAC; + cs->readisacfifo = &ReadISACfifo_IPAC; + cs->writeisacfifo = &WriteISACfifo_IPAC; + cs->irq_func = &elsa_interrupt_ipac; + val = readreg(cs->hw.elsa.ale, cs->hw.elsa.isac, IPAC_ID); + printk(KERN_INFO "Elsa: IPAC version %x\n", val); + } else { + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->irq_func = &elsa_interrupt; + ISACVersion(cs, "Elsa:"); + if (HscxVersion(cs, "Elsa:")) { + printk(KERN_WARNING + "Elsa: wrong HSCX versions check IO address\n"); + release_io_elsa(cs); + return (0); + } + } + if (cs->subtyp == ELSA_PC) { + val = readitac(cs, ITAC_SYS); + printk(KERN_INFO "Elsa: ITAC version %s\n", ITACVer[val & 7]); + writeitac(cs, ITAC_ISEN, 0); + writeitac(cs, ITAC_RFIE, 0); + writeitac(cs, ITAC_XFIE, 0); + writeitac(cs, ITAC_SCIE, 0); + writeitac(cs, ITAC_STIE, 0); + } + return (1); +} diff --git a/drivers/isdn/hisax/elsa_cs.c b/drivers/isdn/hisax/elsa_cs.c new file mode 100644 index 000000000000..bfc013225f46 --- /dev/null +++ b/drivers/isdn/hisax/elsa_cs.c @@ -0,0 +1,532 @@ +/*====================================================================== + + An elsa_cs PCMCIA client driver + + This driver is for the Elsa PCM ISDN Cards, i.e. the MicroLink + + + The contents of this file are subject to the Mozilla Public + License Version 1.1 (the "License"); you may not use this file + except in compliance with the License. You may obtain a copy of + the License at http://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS + IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + implied. See the License for the specific language governing + rights and limitations under the License. + + The initial developer of the original code is David A. Hinds + <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + + Modifications from dummy_cs.c are Copyright (C) 1999-2001 Klaus + Lichtenwalder <Lichtenwalder@ACM.org>. All Rights Reserved. + + Alternatively, the contents of this file may be used under the + terms of the GNU General Public License version 2 (the "GPL"), in + which case the provisions of the GPL are applicable instead of the + above. If you wish to allow the use of your version of this file + only under the terms of the GPL and not to allow others to use + your version of this file under the MPL, indicate your decision + by deleting the provisions above and replace them with the notice + and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this + file under either the MPL or the GPL. + +======================================================================*/ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/ptrace.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/ioport.h> +#include <asm/io.h> +#include <asm/system.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/cisreg.h> +#include <pcmcia/ds.h> +#include "hisax_cfg.h" + +MODULE_DESCRIPTION("ISDN4Linux: PCMCIA client driver for Elsa PCM cards"); +MODULE_AUTHOR("Klaus Lichtenwalder"); +MODULE_LICENSE("Dual MPL/GPL"); + +/* + All the PCMCIA modules use PCMCIA_DEBUG to control debugging. If + you do not define PCMCIA_DEBUG at all, all the debug code will be + left out. If you compile with PCMCIA_DEBUG=0, the debug code will + be present but disabled -- but it can then be enabled for specific + modules at load time with a 'pc_debug=#' option to insmod. +*/ + +#ifdef PCMCIA_DEBUG +static int pc_debug = PCMCIA_DEBUG; +module_param(pc_debug, int, 0); +#define DEBUG(n, args...) if (pc_debug>(n)) printk(KERN_DEBUG args); +static char *version = +"elsa_cs.c $Revision: 1.2.2.4 $ $Date: 2004/01/25 15:07:06 $ (K.Lichtenwalder)"; +#else +#define DEBUG(n, args...) +#endif + +/*====================================================================*/ + +/* Parameters that can be set with 'insmod' */ + +static int protocol = 2; /* EURO-ISDN Default */ +module_param(protocol, int, 0); + +/*====================================================================*/ + +/* + The event() function is this driver's Card Services event handler. + It will be called by Card Services when an appropriate card status + event is received. The config() and release() entry points are + used to configure or release a socket, in response to card insertion + and ejection events. They are invoked from the elsa_cs event + handler. +*/ + +static void elsa_cs_config(dev_link_t *link); +static void elsa_cs_release(dev_link_t *link); +static int elsa_cs_event(event_t event, int priority, + event_callback_args_t *args); + +/* + The attach() and detach() entry points are used to create and destroy + "instances" of the driver, where each instance represents everything + needed to manage one actual PCMCIA card. +*/ + +static dev_link_t *elsa_cs_attach(void); +static void elsa_cs_detach(dev_link_t *); + +/* + The dev_info variable is the "key" that is used to match up this + device driver with appropriate cards, through the card configuration + database. +*/ + +static dev_info_t dev_info = "elsa_cs"; + +/* + A linked list of "instances" of the elsa_cs device. Each actual + PCMCIA card corresponds to one device instance, and is described + by one dev_link_t structure (defined in ds.h). + + You may not want to use a linked list for this -- for example, the + memory card driver uses an array of dev_link_t pointers, where minor + device numbers are used to derive the corresponding array index. +*/ + +static dev_link_t *dev_list = NULL; + +/* + A dev_link_t structure has fields for most things that are needed + to keep track of a socket, but there will usually be some device + specific information that also needs to be kept track of. The + 'priv' pointer in a dev_link_t structure can be used to point to + a device-specific private data structure, like this. + + To simplify the data structure handling, we actually include the + dev_link_t structure in the device's private data structure. + + A driver needs to provide a dev_node_t structure for each device + on a card. In some cases, there is only one device per card (for + example, ethernet cards, modems). In other cases, there may be + many actual or logical devices (SCSI adapters, memory cards with + multiple partitions). The dev_node_t structures need to be kept + in a linked list starting at the 'dev' field of a dev_link_t + structure. We allocate them in the card's private data structure, + because they generally shouldn't be allocated dynamically. + In this case, we also provide a flag to indicate if a device is + "stopped" due to a power management event, or card ejection. The + device IO routines can use a flag like this to throttle IO to a + card that is not ready to accept it. +*/ + +typedef struct local_info_t { + dev_link_t link; + dev_node_t node; + int busy; + int cardnr; +} local_info_t; + +/*====================================================================== + + elsa_cs_attach() creates an "instance" of the driver, allocatingx + local data structures for one device. The device is registered + with Card Services. + + The dev_link structure is initialized, but we don't actually + configure the card at this point -- we wait until we receive a + card insertion event. + +======================================================================*/ + +static dev_link_t *elsa_cs_attach(void) +{ + client_reg_t client_reg; + dev_link_t *link; + local_info_t *local; + int ret; + + DEBUG(0, "elsa_cs_attach()\n"); + + /* Allocate space for private device-specific data */ + local = kmalloc(sizeof(local_info_t), GFP_KERNEL); + if (!local) return NULL; + memset(local, 0, sizeof(local_info_t)); + local->cardnr = -1; + link = &local->link; link->priv = local; + + /* Interrupt setup */ + link->irq.Attributes = IRQ_TYPE_DYNAMIC_SHARING|IRQ_FIRST_SHARED; + link->irq.IRQInfo1 = IRQ_LEVEL_ID|IRQ_SHARE_ID; + link->irq.Handler = NULL; + + /* + General socket configuration defaults can go here. In this + client, we assume very little, and rely on the CIS for almost + everything. In most clients, many details (i.e., number, sizes, + and attributes of IO windows) are fixed by the nature of the + device, and can be hard-wired here. + */ + link->io.NumPorts1 = 8; + link->io.Attributes1 = IO_DATA_PATH_WIDTH_AUTO; + link->io.IOAddrLines = 3; + + link->conf.Attributes = CONF_ENABLE_IRQ; + link->conf.Vcc = 50; + link->conf.IntType = INT_MEMORY_AND_IO; + + /* Register with Card Services */ + link->next = dev_list; + dev_list = link; + client_reg.dev_info = &dev_info; + client_reg.EventMask = + CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL | + CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET | + CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME; + client_reg.event_handler = &elsa_cs_event; + client_reg.Version = 0x0210; + client_reg.event_callback_args.client_data = link; + ret = pcmcia_register_client(&link->handle, &client_reg); + if (ret != CS_SUCCESS) { + cs_error(link->handle, RegisterClient, ret); + elsa_cs_detach(link); + return NULL; + } + + return link; +} /* elsa_cs_attach */ + +/*====================================================================== + + This deletes a driver "instance". The device is de-registered + with Card Services. If it has been released, all local data + structures are freed. Otherwise, the structures will be freed + when the device is released. + +======================================================================*/ + +static void elsa_cs_detach(dev_link_t *link) +{ + dev_link_t **linkp; + local_info_t *info = link->priv; + int ret; + + DEBUG(0, "elsa_cs_detach(0x%p)\n", link); + + /* Locate device structure */ + for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) + if (*linkp == link) break; + if (*linkp == NULL) + return; + + if (link->state & DEV_CONFIG) + elsa_cs_release(link); + + /* Break the link with Card Services */ + if (link->handle) { + ret = pcmcia_deregister_client(link->handle); + if (ret != CS_SUCCESS) + cs_error(link->handle, DeregisterClient, ret); + } + + /* Unlink device structure and free it */ + *linkp = link->next; + kfree(info); + +} /* elsa_cs_detach */ + +/*====================================================================== + + elsa_cs_config() is scheduled to run after a CARD_INSERTION event + is received, to configure the PCMCIA socket, and to make the + device available to the system. + +======================================================================*/ +static int get_tuple(client_handle_t handle, tuple_t *tuple, + cisparse_t *parse) +{ + int i = pcmcia_get_tuple_data(handle, tuple); + if (i != CS_SUCCESS) return i; + return pcmcia_parse_tuple(handle, tuple, parse); +} + +static int first_tuple(client_handle_t handle, tuple_t *tuple, + cisparse_t *parse) +{ + int i = pcmcia_get_first_tuple(handle, tuple); + if (i != CS_SUCCESS) return i; + return get_tuple(handle, tuple, parse); +} + +static int next_tuple(client_handle_t handle, tuple_t *tuple, + cisparse_t *parse) +{ + int i = pcmcia_get_next_tuple(handle, tuple); + if (i != CS_SUCCESS) return i; + return get_tuple(handle, tuple, parse); +} + +static void elsa_cs_config(dev_link_t *link) +{ + client_handle_t handle; + tuple_t tuple; + cisparse_t parse; + local_info_t *dev; + int i, j, last_fn; + u_short buf[128]; + cistpl_cftable_entry_t *cf = &parse.cftable_entry; + IsdnCard_t icard; + + DEBUG(0, "elsa_config(0x%p)\n", link); + handle = link->handle; + dev = link->priv; + + /* + This reads the card's CONFIG tuple to find its configuration + registers. + */ + tuple.DesiredTuple = CISTPL_CONFIG; + tuple.TupleData = (cisdata_t *)buf; + tuple.TupleDataMax = 255; + tuple.TupleOffset = 0; + tuple.Attributes = 0; + i = first_tuple(handle, &tuple, &parse); + if (i != CS_SUCCESS) { + last_fn = ParseTuple; + goto cs_failed; + } + link->conf.ConfigBase = parse.config.base; + link->conf.Present = parse.config.rmask[0]; + + /* Configure card */ + link->state |= DEV_CONFIG; + + tuple.TupleData = (cisdata_t *)buf; + tuple.TupleOffset = 0; tuple.TupleDataMax = 255; + tuple.Attributes = 0; + tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY; + i = first_tuple(handle, &tuple, &parse); + while (i == CS_SUCCESS) { + if ( (cf->io.nwin > 0) && cf->io.win[0].base) { + printk(KERN_INFO "(elsa_cs: looks like the 96 model)\n"); + link->conf.ConfigIndex = cf->index; + link->io.BasePort1 = cf->io.win[0].base; + i = pcmcia_request_io(link->handle, &link->io); + if (i == CS_SUCCESS) break; + } else { + printk(KERN_INFO "(elsa_cs: looks like the 97 model)\n"); + link->conf.ConfigIndex = cf->index; + for (i = 0, j = 0x2f0; j > 0x100; j -= 0x10) { + link->io.BasePort1 = j; + i = pcmcia_request_io(link->handle, &link->io); + if (i == CS_SUCCESS) break; + } + break; + } + i = next_tuple(handle, &tuple, &parse); + } + + if (i != CS_SUCCESS) { + last_fn = RequestIO; + goto cs_failed; + } + + i = pcmcia_request_irq(link->handle, &link->irq); + if (i != CS_SUCCESS) { + link->irq.AssignedIRQ = 0; + last_fn = RequestIRQ; + goto cs_failed; + } + + i = pcmcia_request_configuration(link->handle, &link->conf); + if (i != CS_SUCCESS) { + last_fn = RequestConfiguration; + goto cs_failed; + } + + /* At this point, the dev_node_t structure(s) should be + initialized and arranged in a linked list at link->dev. *//* */ + sprintf(dev->node.dev_name, "elsa"); + dev->node.major = dev->node.minor = 0x0; + + link->dev = &dev->node; + + /* Finally, report what we've done */ + printk(KERN_INFO "%s: index 0x%02x: Vcc %d.%d", + dev->node.dev_name, link->conf.ConfigIndex, + link->conf.Vcc/10, link->conf.Vcc%10); + if (link->conf.Vpp1) + printk(", Vpp %d.%d", link->conf.Vpp1/10, link->conf.Vpp1%10); + if (link->conf.Attributes & CONF_ENABLE_IRQ) + printk(", irq %d", link->irq.AssignedIRQ); + if (link->io.NumPorts1) + printk(", io 0x%04x-0x%04x", link->io.BasePort1, + link->io.BasePort1+link->io.NumPorts1-1); + if (link->io.NumPorts2) + printk(" & 0x%04x-0x%04x", link->io.BasePort2, + link->io.BasePort2+link->io.NumPorts2-1); + printk("\n"); + + link->state &= ~DEV_CONFIG_PENDING; + + icard.para[0] = link->irq.AssignedIRQ; + icard.para[1] = link->io.BasePort1; + icard.protocol = protocol; + icard.typ = ISDN_CTYPE_ELSA_PCMCIA; + + i = hisax_init_pcmcia(link, &(((local_info_t*)link->priv)->busy), &icard); + if (i < 0) { + printk(KERN_ERR "elsa_cs: failed to initialize Elsa PCMCIA %d at i/o %#x\n", + i, link->io.BasePort1); + elsa_cs_release(link); + } else + ((local_info_t*)link->priv)->cardnr = i; + + return; +cs_failed: + cs_error(link->handle, last_fn, i); + elsa_cs_release(link); +} /* elsa_cs_config */ + +/*====================================================================== + + After a card is removed, elsa_cs_release() will unregister the net + device, and release the PCMCIA configuration. If the device is + still open, this will be postponed until it is closed. + +======================================================================*/ + +static void elsa_cs_release(dev_link_t *link) +{ + local_info_t *local = link->priv; + + DEBUG(0, "elsa_cs_release(0x%p)\n", link); + + if (local) { + if (local->cardnr >= 0) { + /* no unregister function with hisax */ + HiSax_closecard(local->cardnr); + } + } + /* Unlink the device chain */ + link->dev = NULL; + + /* Don't bother checking to see if these succeed or not */ + if (link->win) + pcmcia_release_window(link->win); + pcmcia_release_configuration(link->handle); + pcmcia_release_io(link->handle, &link->io); + pcmcia_release_irq(link->handle, &link->irq); + link->state &= ~DEV_CONFIG; +} /* elsa_cs_release */ + +/*====================================================================== + + The card status event handler. Mostly, this schedules other + stuff to run after an event is received. A CARD_REMOVAL event + also sets some flags to discourage the net drivers from trying + to talk to the card any more. + + When a CARD_REMOVAL event is received, we immediately set a flag + to block future accesses to this device. All the functions that + actually access the device should check this flag to make sure + the card is still present. + +======================================================================*/ + +static int elsa_cs_event(event_t event, int priority, + event_callback_args_t *args) +{ + dev_link_t *link = args->client_data; + local_info_t *dev = link->priv; + + DEBUG(1, "elsa_cs_event(%d)\n", event); + + switch (event) { + case CS_EVENT_CARD_REMOVAL: + link->state &= ~DEV_PRESENT; + if (link->state & DEV_CONFIG) { + ((local_info_t*)link->priv)->busy = 1; + elsa_cs_release(link); + } + break; + case CS_EVENT_CARD_INSERTION: + link->state |= DEV_PRESENT | DEV_CONFIG_PENDING; + elsa_cs_config(link); + break; + case CS_EVENT_PM_SUSPEND: + link->state |= DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_RESET_PHYSICAL: + /* Mark the device as stopped, to block IO until later */ + dev->busy = 1; + if (link->state & DEV_CONFIG) + pcmcia_release_configuration(link->handle); + break; + case CS_EVENT_PM_RESUME: + link->state &= ~DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_CARD_RESET: + if (link->state & DEV_CONFIG) + pcmcia_request_configuration(link->handle, &link->conf); + dev->busy = 0; + break; + } + return 0; +} /* elsa_cs_event */ + +static struct pcmcia_driver elsa_cs_driver = { + .owner = THIS_MODULE, + .drv = { + .name = "elsa_cs", + }, + .attach = elsa_cs_attach, + .detach = elsa_cs_detach, +}; + +static int __init init_elsa_cs(void) +{ + return pcmcia_register_driver(&elsa_cs_driver); +} + +static void __exit exit_elsa_cs(void) +{ + pcmcia_unregister_driver(&elsa_cs_driver); + BUG_ON(dev_list != NULL); +} + +module_init(init_elsa_cs); +module_exit(exit_elsa_cs); diff --git a/drivers/isdn/hisax/elsa_ser.c b/drivers/isdn/hisax/elsa_ser.c new file mode 100644 index 000000000000..689c83395693 --- /dev/null +++ b/drivers/isdn/hisax/elsa_ser.c @@ -0,0 +1,657 @@ +/* $Id: elsa_ser.c,v 2.14.2.3 2004/02/11 13:21:33 keil Exp $ + * + * stuff for the serial modem on ELSA cards + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/config.h> +#include <linux/serial.h> +#include <linux/serial_reg.h> + +#define MAX_MODEM_BUF 256 +#define WAKEUP_CHARS (MAX_MODEM_BUF/2) +#define RS_ISR_PASS_LIMIT 256 +#define BASE_BAUD ( 1843200 / 16 ) + +//#define SERIAL_DEBUG_OPEN 1 +//#define SERIAL_DEBUG_INTR 1 +//#define SERIAL_DEBUG_FLOW 1 +#undef SERIAL_DEBUG_OPEN +#undef SERIAL_DEBUG_INTR +#undef SERIAL_DEBUG_FLOW +#undef SERIAL_DEBUG_REG +//#define SERIAL_DEBUG_REG 1 + +#ifdef SERIAL_DEBUG_REG +static u_char deb[32]; +const char *ModemIn[] = {"RBR","IER","IIR","LCR","MCR","LSR","MSR","SCR"}; +const char *ModemOut[] = {"THR","IER","FCR","LCR","MCR","LSR","MSR","SCR"}; +#endif + +static char *MInit_1 = "AT&F&C1E0&D2\r\0"; +static char *MInit_2 = "ATL2M1S64=13\r\0"; +static char *MInit_3 = "AT+FCLASS=0\r\0"; +static char *MInit_4 = "ATV1S2=128X1\r\0"; +static char *MInit_5 = "AT\\V8\\N3\r\0"; +static char *MInit_6 = "ATL0M0&G0%E1\r\0"; +static char *MInit_7 = "AT%L1%M0%C3\r\0"; + +static char *MInit_speed28800 = "AT%G0%B28800\r\0"; + +static char *MInit_dialout = "ATs7=60 x1 d\r\0"; +static char *MInit_dialin = "ATs7=60 x1 a\r\0"; + + +static inline unsigned int serial_in(struct IsdnCardState *cs, int offset) +{ +#ifdef SERIAL_DEBUG_REG + u_int val = inb(cs->hw.elsa.base + 8 + offset); + debugl1(cs,"in %s %02x",ModemIn[offset], val); + return(val); +#else + return inb(cs->hw.elsa.base + 8 + offset); +#endif +} + +static inline unsigned int serial_inp(struct IsdnCardState *cs, int offset) +{ +#ifdef SERIAL_DEBUG_REG +#ifdef CONFIG_SERIAL_NOPAUSE_IO + u_int val = inb(cs->hw.elsa.base + 8 + offset); + debugl1(cs,"inp %s %02x",ModemIn[offset], val); +#else + u_int val = inb_p(cs->hw.elsa.base + 8 + offset); + debugl1(cs,"inP %s %02x",ModemIn[offset], val); +#endif + return(val); +#else +#ifdef CONFIG_SERIAL_NOPAUSE_IO + return inb(cs->hw.elsa.base + 8 + offset); +#else + return inb_p(cs->hw.elsa.base + 8 + offset); +#endif +#endif +} + +static inline void serial_out(struct IsdnCardState *cs, int offset, int value) +{ +#ifdef SERIAL_DEBUG_REG + debugl1(cs,"out %s %02x",ModemOut[offset], value); +#endif + outb(value, cs->hw.elsa.base + 8 + offset); +} + +static inline void serial_outp(struct IsdnCardState *cs, int offset, + int value) +{ +#ifdef SERIAL_DEBUG_REG +#ifdef CONFIG_SERIAL_NOPAUSE_IO + debugl1(cs,"outp %s %02x",ModemOut[offset], value); +#else + debugl1(cs,"outP %s %02x",ModemOut[offset], value); +#endif +#endif +#ifdef CONFIG_SERIAL_NOPAUSE_IO + outb(value, cs->hw.elsa.base + 8 + offset); +#else + outb_p(value, cs->hw.elsa.base + 8 + offset); +#endif +} + +/* + * This routine is called to set the UART divisor registers to match + * the specified baud rate for a serial port. + */ +static void change_speed(struct IsdnCardState *cs, int baud) +{ + int quot = 0, baud_base; + unsigned cval, fcr = 0; + int bits; + + + /* byte size and parity */ + cval = 0x03; bits = 10; + /* Determine divisor based on baud rate */ + baud_base = BASE_BAUD; + quot = baud_base / baud; + /* If the quotient is ever zero, default to 9600 bps */ + if (!quot) + quot = baud_base / 9600; + + /* Set up FIFO's */ + if ((baud_base / quot) < 2400) + fcr = UART_FCR_ENABLE_FIFO | UART_FCR_TRIGGER_1; + else + fcr = UART_FCR_ENABLE_FIFO | UART_FCR_TRIGGER_8; + serial_outp(cs, UART_FCR, fcr); + /* CTS flow control flag and modem status interrupts */ + cs->hw.elsa.IER &= ~UART_IER_MSI; + cs->hw.elsa.IER |= UART_IER_MSI; + serial_outp(cs, UART_IER, cs->hw.elsa.IER); + + debugl1(cs,"modem quot=0x%x", quot); + serial_outp(cs, UART_LCR, cval | UART_LCR_DLAB);/* set DLAB */ + serial_outp(cs, UART_DLL, quot & 0xff); /* LS of divisor */ + serial_outp(cs, UART_DLM, quot >> 8); /* MS of divisor */ + serial_outp(cs, UART_LCR, cval); /* reset DLAB */ + serial_inp(cs, UART_RX); +} + +static int mstartup(struct IsdnCardState *cs) +{ + int retval=0; + + /* + * Clear the FIFO buffers and disable them + * (they will be reenabled in change_speed()) + */ + serial_outp(cs, UART_FCR, (UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT)); + + /* + * At this point there's no way the LSR could still be 0xFF; + * if it is, then bail out, because there's likely no UART + * here. + */ + if (serial_inp(cs, UART_LSR) == 0xff) { + retval = -ENODEV; + goto errout; + } + + /* + * Clear the interrupt registers. + */ + (void) serial_inp(cs, UART_RX); + (void) serial_inp(cs, UART_IIR); + (void) serial_inp(cs, UART_MSR); + + /* + * Now, initialize the UART + */ + serial_outp(cs, UART_LCR, UART_LCR_WLEN8); /* reset DLAB */ + + cs->hw.elsa.MCR = 0; + cs->hw.elsa.MCR = UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2; + serial_outp(cs, UART_MCR, cs->hw.elsa.MCR); + + /* + * Finally, enable interrupts + */ + cs->hw.elsa.IER = UART_IER_MSI | UART_IER_RLSI | UART_IER_RDI; + serial_outp(cs, UART_IER, cs->hw.elsa.IER); /* enable interrupts */ + + /* + * And clear the interrupt registers again for luck. + */ + (void)serial_inp(cs, UART_LSR); + (void)serial_inp(cs, UART_RX); + (void)serial_inp(cs, UART_IIR); + (void)serial_inp(cs, UART_MSR); + + cs->hw.elsa.transcnt = cs->hw.elsa.transp = 0; + cs->hw.elsa.rcvcnt = cs->hw.elsa.rcvp =0; + + /* + * and set the speed of the serial port + */ + change_speed(cs, BASE_BAUD); + cs->hw.elsa.MFlag = 1; +errout: + return retval; +} + +/* + * 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 mshutdown(struct IsdnCardState *cs) +{ + +#ifdef SERIAL_DEBUG_OPEN + printk(KERN_DEBUG"Shutting down serial ...."); +#endif + + /* + * clear delta_msr_wait queue to avoid mem leaks: we may free the irq + * here so the queue might never be waken up + */ + + cs->hw.elsa.IER = 0; + serial_outp(cs, UART_IER, 0x00); /* disable all intrs */ + cs->hw.elsa.MCR &= ~UART_MCR_OUT2; + + /* disable break condition */ + serial_outp(cs, UART_LCR, serial_inp(cs, UART_LCR) & ~UART_LCR_SBC); + + cs->hw.elsa.MCR &= ~(UART_MCR_DTR|UART_MCR_RTS); + serial_outp(cs, UART_MCR, cs->hw.elsa.MCR); + + /* disable FIFO's */ + serial_outp(cs, UART_FCR, (UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT)); + serial_inp(cs, UART_RX); /* read data port to reset things */ + +#ifdef SERIAL_DEBUG_OPEN + printk(" done\n"); +#endif +} + +inline int +write_modem(struct BCState *bcs) { + int ret=0; + struct IsdnCardState *cs = bcs->cs; + int count, len, fp; + + if (!bcs->tx_skb) + return 0; + if (bcs->tx_skb->len <= 0) + return 0; + len = bcs->tx_skb->len; + if (len > MAX_MODEM_BUF - cs->hw.elsa.transcnt) + len = MAX_MODEM_BUF - cs->hw.elsa.transcnt; + fp = cs->hw.elsa.transcnt + cs->hw.elsa.transp; + fp &= (MAX_MODEM_BUF -1); + count = len; + if (count > MAX_MODEM_BUF - fp) { + count = MAX_MODEM_BUF - fp; + memcpy(cs->hw.elsa.transbuf + fp, bcs->tx_skb->data, count); + skb_pull(bcs->tx_skb, count); + cs->hw.elsa.transcnt += count; + ret = count; + count = len - count; + fp = 0; + } + memcpy((cs->hw.elsa.transbuf + fp), bcs->tx_skb->data, count); + skb_pull(bcs->tx_skb, count); + cs->hw.elsa.transcnt += count; + ret += count; + + if (cs->hw.elsa.transcnt && + !(cs->hw.elsa.IER & UART_IER_THRI)) { + cs->hw.elsa.IER |= UART_IER_THRI; + serial_outp(cs, UART_IER, cs->hw.elsa.IER); + } + return(ret); +} + +inline void +modem_fill(struct BCState *bcs) { + + if (bcs->tx_skb) { + if (bcs->tx_skb->len) { + write_modem(bcs); + return; + } else { + if (test_bit(FLG_LLI_L1WAKEUP,&bcs->st->lli.flag) && + (PACKET_NOACK != bcs->tx_skb->pkt_type)) { + u_long flags; + spin_lock_irqsave(&bcs->aclock, flags); + bcs->ackcnt += bcs->hw.hscx.count; + spin_unlock_irqrestore(&bcs->aclock, flags); + schedule_event(bcs, B_ACKPENDING); + } + dev_kfree_skb_any(bcs->tx_skb); + bcs->tx_skb = NULL; + } + } + if ((bcs->tx_skb = skb_dequeue(&bcs->squeue))) { + bcs->hw.hscx.count = 0; + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + write_modem(bcs); + } else { + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + schedule_event(bcs, B_XMTBUFREADY); + } +} + +static inline void receive_chars(struct IsdnCardState *cs, + int *status) +{ + unsigned char ch; + struct sk_buff *skb; + + do { + ch = serial_in(cs, UART_RX); + if (cs->hw.elsa.rcvcnt >= MAX_MODEM_BUF) + break; + cs->hw.elsa.rcvbuf[cs->hw.elsa.rcvcnt++] = ch; +#ifdef SERIAL_DEBUG_INTR + printk("DR%02x:%02x...", ch, *status); +#endif + if (*status & (UART_LSR_BI | UART_LSR_PE | + UART_LSR_FE | UART_LSR_OE)) { + +#ifdef SERIAL_DEBUG_INTR + printk("handling exept...."); +#endif + } + *status = serial_inp(cs, UART_LSR); + } while (*status & UART_LSR_DR); + if (cs->hw.elsa.MFlag == 2) { + if (!(skb = dev_alloc_skb(cs->hw.elsa.rcvcnt))) + printk(KERN_WARNING "ElsaSER: receive out of memory\n"); + else { + memcpy(skb_put(skb, cs->hw.elsa.rcvcnt), cs->hw.elsa.rcvbuf, + cs->hw.elsa.rcvcnt); + skb_queue_tail(& cs->hw.elsa.bcs->rqueue, skb); + } + schedule_event(cs->hw.elsa.bcs, B_RCVBUFREADY); + } else { + char tmp[128]; + char *t = tmp; + + t += sprintf(t, "modem read cnt %d", cs->hw.elsa.rcvcnt); + QuickHex(t, cs->hw.elsa.rcvbuf, cs->hw.elsa.rcvcnt); + debugl1(cs, tmp); + } + cs->hw.elsa.rcvcnt = 0; +} + +static inline void transmit_chars(struct IsdnCardState *cs, int *intr_done) +{ + int count; + + debugl1(cs, "transmit_chars: p(%x) cnt(%x)", cs->hw.elsa.transp, + cs->hw.elsa.transcnt); + + if (cs->hw.elsa.transcnt <= 0) { + cs->hw.elsa.IER &= ~UART_IER_THRI; + serial_out(cs, UART_IER, cs->hw.elsa.IER); + return; + } + count = 16; + do { + serial_outp(cs, UART_TX, cs->hw.elsa.transbuf[cs->hw.elsa.transp++]); + if (cs->hw.elsa.transp >= MAX_MODEM_BUF) + cs->hw.elsa.transp=0; + if (--cs->hw.elsa.transcnt <= 0) + break; + } while (--count > 0); + if ((cs->hw.elsa.transcnt < WAKEUP_CHARS) && (cs->hw.elsa.MFlag==2)) + modem_fill(cs->hw.elsa.bcs); + +#ifdef SERIAL_DEBUG_INTR + printk("THRE..."); +#endif + if (intr_done) + *intr_done = 0; + if (cs->hw.elsa.transcnt <= 0) { + cs->hw.elsa.IER &= ~UART_IER_THRI; + serial_outp(cs, UART_IER, cs->hw.elsa.IER); + } +} + + +static void rs_interrupt_elsa(int irq, struct IsdnCardState *cs) +{ + int status, iir, msr; + int pass_counter = 0; + +#ifdef SERIAL_DEBUG_INTR + printk("rs_interrupt_single(%d)...", irq); +#endif + + do { + status = serial_inp(cs, UART_LSR); + debugl1(cs,"rs LSR %02x", status); +#ifdef SERIAL_DEBUG_INTR + printk("status = %x...", status); +#endif + if (status & UART_LSR_DR) + receive_chars(cs, &status); + if (status & UART_LSR_THRE) + transmit_chars(cs, NULL); + if (pass_counter++ > RS_ISR_PASS_LIMIT) { + printk("rs_single loop break.\n"); + break; + } + iir = serial_inp(cs, UART_IIR); + debugl1(cs,"rs IIR %02x", iir); + if ((iir & 0xf) == 0) { + msr = serial_inp(cs, UART_MSR); + debugl1(cs,"rs MSR %02x", msr); + } + } while (!(iir & UART_IIR_NO_INT)); +#ifdef SERIAL_DEBUG_INTR + printk("end.\n"); +#endif +} + +extern int open_hscxstate(struct IsdnCardState *cs, struct BCState *bcs); +extern void modehscx(struct BCState *bcs, int mode, int bc); +extern void hscx_l2l1(struct PStack *st, int pr, void *arg); + +void +close_elsastate(struct BCState *bcs) +{ + modehscx(bcs, 0, bcs->channel); + if (test_and_clear_bit(BC_FLG_INIT, &bcs->Flag)) { + if (bcs->hw.hscx.rcvbuf) { + if (bcs->mode != L1_MODE_MODEM) + kfree(bcs->hw.hscx.rcvbuf); + bcs->hw.hscx.rcvbuf = NULL; + } + skb_queue_purge(&bcs->rqueue); + skb_queue_purge(&bcs->squeue); + if (bcs->tx_skb) { + dev_kfree_skb_any(bcs->tx_skb); + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + } + } +} + +void +modem_write_cmd(struct IsdnCardState *cs, u_char *buf, int len) { + int count, fp; + u_char *msg = buf; + + if (!len) + return; + if (len > (MAX_MODEM_BUF - cs->hw.elsa.transcnt)) { + return; + } + fp = cs->hw.elsa.transcnt + cs->hw.elsa.transp; + fp &= (MAX_MODEM_BUF -1); + count = len; + if (count > MAX_MODEM_BUF - fp) { + count = MAX_MODEM_BUF - fp; + memcpy(cs->hw.elsa.transbuf + fp, msg, count); + cs->hw.elsa.transcnt += count; + msg += count; + count = len - count; + fp = 0; + } + memcpy(cs->hw.elsa.transbuf + fp, msg, count); + cs->hw.elsa.transcnt += count; + if (cs->hw.elsa.transcnt && + !(cs->hw.elsa.IER & UART_IER_THRI)) { + cs->hw.elsa.IER |= UART_IER_THRI; + serial_outp(cs, UART_IER, cs->hw.elsa.IER); + } +} + +void +modem_set_init(struct IsdnCardState *cs) { + int timeout; + +#define RCV_DELAY 20000 + modem_write_cmd(cs, MInit_1, strlen(MInit_1)); + timeout = 1000; + while(timeout-- && cs->hw.elsa.transcnt) + udelay(1000); + debugl1(cs, "msi tout=%d", timeout); + udelay(RCV_DELAY); + modem_write_cmd(cs, MInit_2, strlen(MInit_2)); + timeout = 1000; + while(timeout-- && cs->hw.elsa.transcnt) + udelay(1000); + debugl1(cs, "msi tout=%d", timeout); + udelay(RCV_DELAY); + modem_write_cmd(cs, MInit_3, strlen(MInit_3)); + timeout = 1000; + while(timeout-- && cs->hw.elsa.transcnt) + udelay(1000); + debugl1(cs, "msi tout=%d", timeout); + udelay(RCV_DELAY); + modem_write_cmd(cs, MInit_4, strlen(MInit_4)); + timeout = 1000; + while(timeout-- && cs->hw.elsa.transcnt) + udelay(1000); + debugl1(cs, "msi tout=%d", timeout); + udelay(RCV_DELAY ); + modem_write_cmd(cs, MInit_5, strlen(MInit_5)); + timeout = 1000; + while(timeout-- && cs->hw.elsa.transcnt) + udelay(1000); + debugl1(cs, "msi tout=%d", timeout); + udelay(RCV_DELAY); + modem_write_cmd(cs, MInit_6, strlen(MInit_6)); + timeout = 1000; + while(timeout-- && cs->hw.elsa.transcnt) + udelay(1000); + debugl1(cs, "msi tout=%d", timeout); + udelay(RCV_DELAY); + modem_write_cmd(cs, MInit_7, strlen(MInit_7)); + timeout = 1000; + while(timeout-- && cs->hw.elsa.transcnt) + udelay(1000); + debugl1(cs, "msi tout=%d", timeout); + udelay(RCV_DELAY); +} + +void +modem_set_dial(struct IsdnCardState *cs, int outgoing) { + int timeout; +#define RCV_DELAY 20000 + + modem_write_cmd(cs, MInit_speed28800, strlen(MInit_speed28800)); + timeout = 1000; + while(timeout-- && cs->hw.elsa.transcnt) + udelay(1000); + debugl1(cs, "msi tout=%d", timeout); + udelay(RCV_DELAY); + if (outgoing) + modem_write_cmd(cs, MInit_dialout, strlen(MInit_dialout)); + else + modem_write_cmd(cs, MInit_dialin, strlen(MInit_dialin)); + timeout = 1000; + while(timeout-- && cs->hw.elsa.transcnt) + udelay(1000); + debugl1(cs, "msi tout=%d", timeout); + udelay(RCV_DELAY); +} + +void +modem_l2l1(struct PStack *st, int pr, void *arg) +{ + struct BCState *bcs = st->l1.bcs; + struct sk_buff *skb = arg; + u_long flags; + + if (pr == (PH_DATA | REQUEST)) { + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + skb_queue_tail(&bcs->squeue, skb); + } else { + bcs->tx_skb = skb; + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->hw.hscx.count = 0; + write_modem(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + } else if (pr == (PH_ACTIVATE | REQUEST)) { + test_and_set_bit(BC_FLG_ACTIV, &bcs->Flag); + st->l1.l1l2(st, PH_ACTIVATE | CONFIRM, NULL); + set_arcofi(bcs->cs, st->l1.bc); + mstartup(bcs->cs); + modem_set_dial(bcs->cs, test_bit(FLG_ORIG, &st->l2.flag)); + bcs->cs->hw.elsa.MFlag=2; + } else if (pr == (PH_DEACTIVATE | REQUEST)) { + test_and_clear_bit(BC_FLG_ACTIV, &bcs->Flag); + bcs->cs->dc.isac.arcofi_bc = st->l1.bc; + arcofi_fsm(bcs->cs, ARCOFI_START, &ARCOFI_XOP_0); + interruptible_sleep_on(&bcs->cs->dc.isac.arcofi_wait); + bcs->cs->hw.elsa.MFlag=1; + } else { + printk(KERN_WARNING"ElsaSer: unknown pr %x\n", pr); + } +} + +int +setstack_elsa(struct PStack *st, struct BCState *bcs) +{ + + bcs->channel = st->l1.bc; + switch (st->l1.mode) { + case L1_MODE_HDLC: + case L1_MODE_TRANS: + if (open_hscxstate(st->l1.hardware, bcs)) + return (-1); + st->l2.l2l1 = hscx_l2l1; + break; + case L1_MODE_MODEM: + bcs->mode = L1_MODE_MODEM; + if (!test_and_set_bit(BC_FLG_INIT, &bcs->Flag)) { + bcs->hw.hscx.rcvbuf = bcs->cs->hw.elsa.rcvbuf; + skb_queue_head_init(&bcs->rqueue); + skb_queue_head_init(&bcs->squeue); + } + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->event = 0; + bcs->hw.hscx.rcvidx = 0; + bcs->tx_cnt = 0; + bcs->cs->hw.elsa.bcs = bcs; + st->l2.l2l1 = modem_l2l1; + break; + } + st->l1.bcs = bcs; + setstack_manager(st); + bcs->st = st; + setstack_l1_B(st); + return (0); +} + +void +init_modem(struct IsdnCardState *cs) { + + cs->bcs[0].BC_SetStack = setstack_elsa; + cs->bcs[1].BC_SetStack = setstack_elsa; + cs->bcs[0].BC_Close = close_elsastate; + cs->bcs[1].BC_Close = close_elsastate; + if (!(cs->hw.elsa.rcvbuf = kmalloc(MAX_MODEM_BUF, + GFP_ATOMIC))) { + printk(KERN_WARNING + "Elsa: No modem mem hw.elsa.rcvbuf\n"); + return; + } + if (!(cs->hw.elsa.transbuf = kmalloc(MAX_MODEM_BUF, + GFP_ATOMIC))) { + printk(KERN_WARNING + "Elsa: No modem mem hw.elsa.transbuf\n"); + kfree(cs->hw.elsa.rcvbuf); + cs->hw.elsa.rcvbuf = NULL; + return; + } + if (mstartup(cs)) { + printk(KERN_WARNING "Elsa: problem startup modem\n"); + } + modem_set_init(cs); +} + +void +release_modem(struct IsdnCardState *cs) { + + cs->hw.elsa.MFlag = 0; + if (cs->hw.elsa.transbuf) { + if (cs->hw.elsa.rcvbuf) { + mshutdown(cs); + kfree(cs->hw.elsa.rcvbuf); + cs->hw.elsa.rcvbuf = NULL; + } + kfree(cs->hw.elsa.transbuf); + cs->hw.elsa.transbuf = NULL; + } +} diff --git a/drivers/isdn/hisax/enternow.h b/drivers/isdn/hisax/enternow.h new file mode 100644 index 000000000000..ed2eec5874c5 --- /dev/null +++ b/drivers/isdn/hisax/enternow.h @@ -0,0 +1,51 @@ +/* 2001/10/02 + * + * enternow.h Header-file included by + * enternow_pci.c + * + * Author Christoph Ersfeld <info@formula-n.de> + * Formula-n Europe AG (www.formula-n.com) + * previously Gerdes AG + * + * + * This file is (c) under GNU PUBLIC LICENSE + */ + + +/* ***************************************************************************************** * + * ****************************** datatypes and macros ************************************* * + * ***************************************************************************************** */ + +#define BYTE unsigned char +#define WORD unsigned int +#define HIBYTE(w) ((unsigned char)((w & 0xff00) / 256)) +#define LOBYTE(w) ((unsigned char)(w & 0x00ff)) +#define InByte(addr) inb(addr) +#define OutByte(addr,val) outb(val,addr) + + + +/* ***************************************************************************************** * + * *********************************** card-specific *************************************** * + * ***************************************************************************************** */ + +/* für PowerISDN PCI */ +#define TJ_AMD_IRQ 0x20 +#define TJ_LED1 0x40 +#define TJ_LED2 0x80 + + +/* Das Fenster zum AMD... + * Ab Adresse hw.njet.base + TJ_AMD_PORT werden vom AMD jeweils 8 Bit in + * den TigerJet i/o-Raum gemappt + * -> 0x01 des AMD bei hw.njet.base + 0C4 */ +#define TJ_AMD_PORT 0xC0 + + + +/* ***************************************************************************************** * + * *************************************** Prototypen ************************************** * + * ***************************************************************************************** */ + +BYTE ReadByteAmd7930(struct IsdnCardState *cs, BYTE offset); +void WriteByteAmd7930(struct IsdnCardState *cs, BYTE offset, BYTE value); diff --git a/drivers/isdn/hisax/enternow_pci.c b/drivers/isdn/hisax/enternow_pci.c new file mode 100644 index 000000000000..1cc4d11e007a --- /dev/null +++ b/drivers/isdn/hisax/enternow_pci.c @@ -0,0 +1,399 @@ +/* enternow_pci.c,v 0.99 2001/10/02 + * + * enternow_pci.c Card-specific routines for + * Formula-n enter:now ISDN PCI ab + * Gerdes AG Power ISDN PCI + * Woerltronic SA 16 PCI + * (based on HiSax driver by Karsten Keil) + * + * Author Christoph Ersfeld <info@formula-n.de> + * Formula-n Europe AG (www.formula-n.com) + * previously Gerdes AG + * + * + * This file is (c) under GNU PUBLIC LICENSE + * + * Notes: + * This driver interfaces to netjet.c which performs B-channel + * processing. + * + * Version 0.99 is the first release of this driver and there are + * certainly a few bugs. + * It isn't testet on linux 2.4 yet, so consider this code to be + * beta. + * + * Please don't report me any malfunction without sending + * (compressed) debug-logs. + * It would be nearly impossible to retrace it. + * + * Log D-channel-processing as follows: + * + * 1. Load hisax with card-specific parameters, this example ist for + * Formula-n enter:now ISDN PCI and compatible + * (f.e. Gerdes Power ISDN PCI) + * + * modprobe hisax type=41 protocol=2 id=gerdes + * + * if you chose an other value for id, you need to modify the + * code below, too. + * + * 2. set debug-level + * + * hisaxctrl gerdes 1 0x3ff + * hisaxctrl gerdes 11 0x4f + * cat /dev/isdnctrl >> ~/log & + * + * Please take also a look into /var/log/messages if there is + * anything importand concerning HISAX. + * + * + * Credits: + * Programming the driver for Formula-n enter:now ISDN PCI and + * necessary the driver for the used Amd 7930 D-channel-controller + * was spnsored by Formula-n Europe AG. + * Thanks to Karsten Keil and Petr Novak, who gave me support in + * Hisax-specific questions. + * I want so say special thanks to Carl-Friedrich Braun, who had to + * answer a lot of questions about generally ISDN and about handling + * of the Amd-Chip. + * + */ + + +#include <linux/config.h> +#include "hisax.h" +#include "isac.h" +#include "isdnl1.h" +#include "amd7930_fn.h" +#include "enternow.h" +#include <linux/interrupt.h> +#include <linux/ppp_defs.h> +#include <linux/pci.h> +#include <linux/init.h> +#include "netjet.h" + + + +const char *enternow_pci_rev = "$Revision: 1.1.4.5 $"; + + +/* *************************** I/O-Interface functions ************************************* */ + + +/* cs->readisac, macro rByteAMD */ +BYTE +ReadByteAmd7930(struct IsdnCardState *cs, BYTE offset) +{ + /* direktes Register */ + if(offset < 8) + return (InByte(cs->hw.njet.isac + 4*offset)); + + /* indirektes Register */ + else { + OutByte(cs->hw.njet.isac + 4*AMD_CR, offset); + return(InByte(cs->hw.njet.isac + 4*AMD_DR)); + } +} + +/* cs->writeisac, macro wByteAMD */ +void +WriteByteAmd7930(struct IsdnCardState *cs, BYTE offset, BYTE value) +{ + /* direktes Register */ + if(offset < 8) + OutByte(cs->hw.njet.isac + 4*offset, value); + + /* indirektes Register */ + else { + OutByte(cs->hw.njet.isac + 4*AMD_CR, offset); + OutByte(cs->hw.njet.isac + 4*AMD_DR, value); + } +} + + +void +enpci_setIrqMask(struct IsdnCardState *cs, BYTE val) { + if (!val) + OutByte(cs->hw.njet.base+NETJET_IRQMASK1, 0x00); + else + OutByte(cs->hw.njet.base+NETJET_IRQMASK1, TJ_AMD_IRQ); +} + + +static BYTE dummyrr(struct IsdnCardState *cs, int chan, BYTE off) +{ + return(5); +} + +static void dummywr(struct IsdnCardState *cs, int chan, BYTE off, BYTE value) +{ + +} + + +/* ******************************************************************************** */ + + +static void +reset_enpci(struct IsdnCardState *cs) +{ + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "enter:now PCI: reset"); + + /* Reset on, (also for AMD) */ + cs->hw.njet.ctrl_reg = 0x07; + OutByte(cs->hw.njet.base + NETJET_CTRL, cs->hw.njet.ctrl_reg); + mdelay(20); + /* Reset off */ + cs->hw.njet.ctrl_reg = 0x30; + OutByte(cs->hw.njet.base + NETJET_CTRL, cs->hw.njet.ctrl_reg); + /* 20ms delay */ + mdelay(20); + cs->hw.njet.auxd = 0; // LED-status + cs->hw.njet.dmactrl = 0; + OutByte(cs->hw.njet.base + NETJET_AUXCTRL, ~TJ_AMD_IRQ); + OutByte(cs->hw.njet.base + NETJET_IRQMASK1, TJ_AMD_IRQ); + OutByte(cs->hw.njet.auxa, cs->hw.njet.auxd); // LED off +} + + +static int +enpci_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + BYTE *chan; + + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "enter:now PCI: card_msg: 0x%04X", mt); + + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + reset_enpci(cs); + Amd7930_init(cs); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case CARD_RELEASE: + release_io_netjet(cs); + break; + case CARD_INIT: + reset_enpci(cs); + inittiger(cs); + /* irq must be on here */ + Amd7930_init(cs); + break; + case CARD_TEST: + break; + case MDL_ASSIGN: + /* TEI assigned, LED1 on */ + cs->hw.njet.auxd = TJ_AMD_IRQ << 1; + OutByte(cs->hw.njet.base + NETJET_AUXDATA, cs->hw.njet.auxd); + break; + case MDL_REMOVE: + /* TEI removed, LEDs off */ + cs->hw.njet.auxd = 0; + OutByte(cs->hw.njet.base + NETJET_AUXDATA, 0x00); + break; + case MDL_BC_ASSIGN: + /* activate B-channel */ + chan = (BYTE *)arg; + + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "enter:now PCI: assign phys. BC %d in AMD LMR1", *chan); + + cs->dc.amd7930.ph_command(cs, (cs->dc.amd7930.lmr1 | (*chan + 1)), "MDL_BC_ASSIGN"); + /* at least one b-channel in use, LED 2 on */ + cs->hw.njet.auxd |= TJ_AMD_IRQ << 2; + OutByte(cs->hw.njet.base + NETJET_AUXDATA, cs->hw.njet.auxd); + break; + case MDL_BC_RELEASE: + /* deactivate B-channel */ + chan = (BYTE *)arg; + + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "enter:now PCI: release phys. BC %d in Amd LMR1", *chan); + + cs->dc.amd7930.ph_command(cs, (cs->dc.amd7930.lmr1 & ~(*chan + 1)), "MDL_BC_RELEASE"); + /* no b-channel active -> LED2 off */ + if (!(cs->dc.amd7930.lmr1 & 3)) { + cs->hw.njet.auxd &= ~(TJ_AMD_IRQ << 2); + OutByte(cs->hw.njet.base + NETJET_AUXDATA, cs->hw.njet.auxd); + } + break; + default: + break; + + } + return(0); +} + +static irqreturn_t +enpci_interrupt(int intno, void *dev_id, struct pt_regs *regs) +{ + struct IsdnCardState *cs = dev_id; + BYTE s0val, s1val, ir; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + s1val = InByte(cs->hw.njet.base + NETJET_IRQSTAT1); + + /* AMD threw an interrupt */ + if (!(s1val & TJ_AMD_IRQ)) { + /* read and clear interrupt-register */ + ir = ReadByteAmd7930(cs, 0x00); + Amd7930_interrupt(cs, ir); + s1val = 1; + } else + s1val = 0; + s0val = InByte(cs->hw.njet.base + NETJET_IRQSTAT0); + if ((s0val | s1val)==0) { // shared IRQ + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_NONE; + } + if (s0val) + OutByte(cs->hw.njet.base + NETJET_IRQSTAT0, s0val); + + /* DMA-Interrupt: B-channel-stuff */ + /* set bits in sval to indicate which page is free */ + if (inl(cs->hw.njet.base + NETJET_DMA_WRITE_ADR) < + inl(cs->hw.njet.base + NETJET_DMA_WRITE_IRQ)) + /* the 2nd write page is free */ + s0val = 0x08; + else /* the 1st write page is free */ + s0val = 0x04; + if (inl(cs->hw.njet.base + NETJET_DMA_READ_ADR) < + inl(cs->hw.njet.base + NETJET_DMA_READ_IRQ)) + /* the 2nd read page is free */ + s0val = s0val | 0x02; + else /* the 1st read page is free */ + s0val = s0val | 0x01; + if (s0val != cs->hw.njet.last_is0) /* we have a DMA interrupt */ + { + if (test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; + } + cs->hw.njet.irqstat0 = s0val; + if ((cs->hw.njet.irqstat0 & NETJET_IRQM0_READ) != + (cs->hw.njet.last_is0 & NETJET_IRQM0_READ)) + /* we have a read dma int */ + read_tiger(cs); + if ((cs->hw.njet.irqstat0 & NETJET_IRQM0_WRITE) != + (cs->hw.njet.last_is0 & NETJET_IRQM0_WRITE)) + /* we have a write dma int */ + write_tiger(cs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + + +static struct pci_dev *dev_netjet __initdata = NULL; + +/* called by config.c */ +int __init +setup_enternow_pci(struct IsdnCard *card) +{ + int bytecnt; + struct IsdnCardState *cs = card->cs; + char tmp[64]; + +#ifdef CONFIG_PCI +#ifdef __BIG_ENDIAN +#error "not running on big endian machines now" +#endif + strcpy(tmp, enternow_pci_rev); + printk(KERN_INFO "HiSax: Formula-n Europe AG enter:now ISDN PCI driver Rev. %s\n", HiSax_getrev(tmp)); + if (cs->typ != ISDN_CTYPE_ENTERNOW) + return(0); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + + for ( ;; ) + { + if ((dev_netjet = pci_find_device(PCI_VENDOR_ID_TIGERJET, + PCI_DEVICE_ID_TIGERJET_300, dev_netjet))) { + if (pci_enable_device(dev_netjet)) + return(0); + cs->irq = dev_netjet->irq; + if (!cs->irq) { + printk(KERN_WARNING "enter:now PCI: No IRQ for PCI card found\n"); + return(0); + } + cs->hw.njet.base = pci_resource_start(dev_netjet, 0); + if (!cs->hw.njet.base) { + printk(KERN_WARNING "enter:now PCI: No IO-Adr for PCI card found\n"); + return(0); + } + /* checks Sub-Vendor ID because system crashes with Traverse-Card */ + if ((dev_netjet->subsystem_vendor != 0x55) || + (dev_netjet->subsystem_device != 0x02)) { + printk(KERN_WARNING "enter:now: You tried to load this driver with an incompatible TigerJet-card\n"); + printk(KERN_WARNING "Use type=20 for Traverse NetJet PCI Card.\n"); + return(0); + } + } else { + printk(KERN_WARNING "enter:now PCI: No PCI card found\n"); + return(0); + } + + cs->hw.njet.auxa = cs->hw.njet.base + NETJET_AUXDATA; + cs->hw.njet.isac = cs->hw.njet.base + 0xC0; // Fenster zum AMD + + /* Reset an */ + cs->hw.njet.ctrl_reg = 0x07; // geändert von 0xff + OutByte(cs->hw.njet.base + NETJET_CTRL, cs->hw.njet.ctrl_reg); + /* 20 ms Pause */ + mdelay(20); + + cs->hw.njet.ctrl_reg = 0x30; /* Reset Off and status read clear */ + OutByte(cs->hw.njet.base + NETJET_CTRL, cs->hw.njet.ctrl_reg); + mdelay(10); + + cs->hw.njet.auxd = 0x00; // war 0xc0 + cs->hw.njet.dmactrl = 0; + + OutByte(cs->hw.njet.base + NETJET_AUXCTRL, ~TJ_AMD_IRQ); + OutByte(cs->hw.njet.base + NETJET_IRQMASK1, TJ_AMD_IRQ); + OutByte(cs->hw.njet.auxa, cs->hw.njet.auxd); + + break; + } +#else + + printk(KERN_WARNING "enter:now PCI: NO_PCI_BIOS\n"); + printk(KERN_WARNING "enter:now PCI: unable to config Formula-n enter:now ISDN PCI ab\n"); + return (0); + +#endif /* CONFIG_PCI */ + + bytecnt = 256; + + printk(KERN_INFO + "enter:now PCI: PCI card configured at 0x%lx IRQ %d\n", + cs->hw.njet.base, cs->irq); + if (!request_region(cs->hw.njet.base, bytecnt, "Fn_ISDN")) { + printk(KERN_WARNING + "HiSax: %s config port %lx-%lx already in use\n", + CardType[card->typ], + cs->hw.njet.base, + cs->hw.njet.base + bytecnt); + return (0); + } + setup_Amd7930(cs); + cs->hw.njet.last_is0 = 0; + /* macro rByteAMD */ + cs->readisac = &ReadByteAmd7930; + /* macro wByteAMD */ + cs->writeisac = &WriteByteAmd7930; + cs->dc.amd7930.setIrqMask = &enpci_setIrqMask; + + cs->BC_Read_Reg = &dummyrr; + cs->BC_Write_Reg = &dummywr; + cs->BC_Send_Data = &netjet_fill_dma; + cs->cardmsg = &enpci_card_msg; + cs->irq_func = &enpci_interrupt; + cs->irq_flags |= SA_SHIRQ; + + return (1); +} diff --git a/drivers/isdn/hisax/fsm.c b/drivers/isdn/hisax/fsm.c new file mode 100644 index 000000000000..0d44a3f480ac --- /dev/null +++ b/drivers/isdn/hisax/fsm.c @@ -0,0 +1,163 @@ +/* $Id: fsm.c,v 1.14.6.4 2001/09/23 22:24:47 kai Exp $ + * + * Finite state machine + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * by Kai Germaschewski <kai.germaschewski@gmx.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * Thanks to Jan den Ouden + * Fritz Elfert + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include "hisax.h" + +#define FSM_TIMER_DEBUG 0 + +int +FsmNew(struct Fsm *fsm, struct FsmNode *fnlist, int fncount) +{ + int i; + + fsm->jumpmatrix = (FSMFNPTR *) + kmalloc(sizeof (FSMFNPTR) * fsm->state_count * fsm->event_count, GFP_KERNEL); + if (!fsm->jumpmatrix) + return -ENOMEM; + + memset(fsm->jumpmatrix, 0, sizeof (FSMFNPTR) * fsm->state_count * fsm->event_count); + + for (i = 0; i < fncount; i++) + if ((fnlist[i].state>=fsm->state_count) || (fnlist[i].event>=fsm->event_count)) { + printk(KERN_ERR "FsmNew Error line %d st(%ld/%ld) ev(%ld/%ld)\n", + i,(long)fnlist[i].state,(long)fsm->state_count, + (long)fnlist[i].event,(long)fsm->event_count); + } else + fsm->jumpmatrix[fsm->state_count * fnlist[i].event + + fnlist[i].state] = (FSMFNPTR) fnlist[i].routine; + return 0; +} + +void +FsmFree(struct Fsm *fsm) +{ + kfree((void *) fsm->jumpmatrix); +} + +int +FsmEvent(struct FsmInst *fi, int event, void *arg) +{ + FSMFNPTR r; + + if ((fi->state>=fi->fsm->state_count) || (event >= fi->fsm->event_count)) { + printk(KERN_ERR "FsmEvent Error st(%ld/%ld) ev(%d/%ld)\n", + (long)fi->state,(long)fi->fsm->state_count,event,(long)fi->fsm->event_count); + return(1); + } + r = fi->fsm->jumpmatrix[fi->fsm->state_count * event + fi->state]; + if (r) { + if (fi->debug) + fi->printdebug(fi, "State %s Event %s", + fi->fsm->strState[fi->state], + fi->fsm->strEvent[event]); + r(fi, event, arg); + return (0); + } else { + if (fi->debug) + fi->printdebug(fi, "State %s Event %s no routine", + fi->fsm->strState[fi->state], + fi->fsm->strEvent[event]); + return (!0); + } +} + +void +FsmChangeState(struct FsmInst *fi, int newstate) +{ + fi->state = newstate; + if (fi->debug) + fi->printdebug(fi, "ChangeState %s", + fi->fsm->strState[newstate]); +} + +static void +FsmExpireTimer(struct FsmTimer *ft) +{ +#if FSM_TIMER_DEBUG + if (ft->fi->debug) + ft->fi->printdebug(ft->fi, "FsmExpireTimer %lx", (long) ft); +#endif + 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; +#if FSM_TIMER_DEBUG + if (ft->fi->debug) + ft->fi->printdebug(ft->fi, "FsmInitTimer %lx", (long) ft); +#endif + init_timer(&ft->tl); +} + +void +FsmDelTimer(struct FsmTimer *ft, int where) +{ +#if FSM_TIMER_DEBUG + if (ft->fi->debug) + ft->fi->printdebug(ft->fi, "FsmDelTimer %lx %d", (long) ft, where); +#endif + del_timer(&ft->tl); +} + +int +FsmAddTimer(struct FsmTimer *ft, + int millisec, int event, void *arg, int where) +{ + +#if FSM_TIMER_DEBUG + if (ft->fi->debug) + ft->fi->printdebug(ft->fi, "FsmAddTimer %lx %d %d", + (long) ft, millisec, where); +#endif + + if (timer_pending(&ft->tl)) { + printk(KERN_WARNING "FsmAddTimer: timer already active!\n"); + ft->fi->printdebug(ft->fi, "FsmAddTimer already active!"); + 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; +} + +void +FsmRestartTimer(struct FsmTimer *ft, + int millisec, int event, void *arg, int where) +{ + +#if FSM_TIMER_DEBUG + if (ft->fi->debug) + ft->fi->printdebug(ft->fi, "FsmRestartTimer %lx %d %d", + (long) ft, millisec, where); +#endif + + if (timer_pending(&ft->tl)) + del_timer(&ft->tl); + init_timer(&ft->tl); + ft->event = event; + ft->arg = arg; + ft->tl.expires = jiffies + (millisec * HZ) / 1000; + add_timer(&ft->tl); +} diff --git a/drivers/isdn/hisax/fsm.h b/drivers/isdn/hisax/fsm.h new file mode 100644 index 000000000000..f02f7da1688d --- /dev/null +++ b/drivers/isdn/hisax/fsm.h @@ -0,0 +1,61 @@ +/* $Id: fsm.h,v 1.3.2.2 2001/09/23 22:24:47 kai Exp $ + * + * Finite state machine + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * by Kai Germaschewski <kai.germaschewski@gmx.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#ifndef __FSM_H__ +#define __FSM_H__ + +#include <linux/timer.h> + +struct FsmInst; + +typedef void (* FSMFNPTR)(struct FsmInst *, int, void *); + +struct Fsm { + FSMFNPTR *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; +}; + +int 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 FsmRestartTimer(struct FsmTimer *ft, int millisec, int event, + void *arg, int where); +void FsmDelTimer(struct FsmTimer *ft, int where); + +#endif diff --git a/drivers/isdn/hisax/gazel.c b/drivers/isdn/hisax/gazel.c new file mode 100644 index 000000000000..24a05a43f33e --- /dev/null +++ b/drivers/isdn/hisax/gazel.c @@ -0,0 +1,684 @@ +/* $Id: gazel.c,v 2.19.2.4 2004/01/14 16:04:48 keil Exp $ + * + * low level stuff for Gazel isdn cards + * + * Author BeWan Systems + * based on source code from Karsten Keil + * Copyright by BeWan Systems + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/config.h> +#include <linux/init.h> +#include "hisax.h" +#include "isac.h" +#include "hscx.h" +#include "isdnl1.h" +#include "ipac.h" +#include <linux/pci.h> + +extern const char *CardType[]; +const char *gazel_revision = "$Revision: 2.19.2.4 $"; + +#define R647 1 +#define R685 2 +#define R753 3 +#define R742 4 + +#define PLX_CNTRL 0x50 /* registre de controle PLX */ +#define RESET_GAZEL 0x4 +#define RESET_9050 0x40000000 +#define PLX_INCSR 0x4C /* registre d'IT du 9050 */ +#define INT_ISAC_EN 0x8 /* 1 = enable IT isac */ +#define INT_ISAC 0x20 /* 1 = IT isac en cours */ +#define INT_HSCX_EN 0x1 /* 1 = enable IT hscx */ +#define INT_HSCX 0x4 /* 1 = IT hscx en cours */ +#define INT_PCI_EN 0x40 /* 1 = enable IT PCI */ +#define INT_IPAC_EN 0x3 /* enable IT ipac */ + + +#define byteout(addr,val) outb(val,addr) +#define bytein(addr) inb(addr) + +static inline u_char +readreg(unsigned int adr, u_short off) +{ + return bytein(adr + off); +} + +static inline void +writereg(unsigned int adr, u_short off, u_char data) +{ + byteout(adr + off, data); +} + + +static inline void +read_fifo(unsigned int adr, u_char * data, int size) +{ + insb(adr, data, size); +} + +static void +write_fifo(unsigned int adr, u_char * data, int size) +{ + outsb(adr, data, size); +} + +static inline u_char +readreg_ipac(unsigned int adr, u_short off) +{ + register u_char ret; + + byteout(adr, off); + ret = bytein(adr + 4); + return ret; +} + +static inline void +writereg_ipac(unsigned int adr, u_short off, u_char data) +{ + byteout(adr, off); + byteout(adr + 4, data); +} + + +static inline void +read_fifo_ipac(unsigned int adr, u_short off, u_char * data, int size) +{ + byteout(adr, off); + insb(adr + 4, data, size); +} + +static void +write_fifo_ipac(unsigned int adr, u_short off, u_char * data, int size) +{ + byteout(adr, off); + outsb(adr + 4, data, size); +} + +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + u_short off2 = offset; + + switch (cs->subtyp) { + case R647: + off2 = ((off2 << 8 & 0xf000) | (off2 & 0xf)); + case R685: + return (readreg(cs->hw.gazel.isac, off2)); + case R753: + case R742: + return (readreg_ipac(cs->hw.gazel.ipac, 0x80 + off2)); + } + return 0; +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + u_short off2 = offset; + + switch (cs->subtyp) { + case R647: + off2 = ((off2 << 8 & 0xf000) | (off2 & 0xf)); + case R685: + writereg(cs->hw.gazel.isac, off2, value); + break; + case R753: + case R742: + writereg_ipac(cs->hw.gazel.ipac, 0x80 + off2, value); + break; + } +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + switch (cs->subtyp) { + case R647: + case R685: + read_fifo(cs->hw.gazel.isacfifo, data, size); + break; + case R753: + case R742: + read_fifo_ipac(cs->hw.gazel.ipac, 0x80, data, size); + break; + } +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + switch (cs->subtyp) { + case R647: + case R685: + write_fifo(cs->hw.gazel.isacfifo, data, size); + break; + case R753: + case R742: + write_fifo_ipac(cs->hw.gazel.ipac, 0x80, data, size); + break; + } +} + +static void +ReadHSCXfifo(struct IsdnCardState *cs, int hscx, u_char * data, int size) +{ + switch (cs->subtyp) { + case R647: + case R685: + read_fifo(cs->hw.gazel.hscxfifo[hscx], data, size); + break; + case R753: + case R742: + read_fifo_ipac(cs->hw.gazel.ipac, hscx * 0x40, data, size); + break; + } +} + +static void +WriteHSCXfifo(struct IsdnCardState *cs, int hscx, u_char * data, int size) +{ + switch (cs->subtyp) { + case R647: + case R685: + write_fifo(cs->hw.gazel.hscxfifo[hscx], data, size); + break; + case R753: + case R742: + write_fifo_ipac(cs->hw.gazel.ipac, hscx * 0x40, data, size); + break; + } +} + +static u_char +ReadHSCX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + u_short off2 = offset; + + switch (cs->subtyp) { + case R647: + off2 = ((off2 << 8 & 0xf000) | (off2 & 0xf)); + case R685: + return (readreg(cs->hw.gazel.hscx[hscx], off2)); + case R753: + case R742: + return (readreg_ipac(cs->hw.gazel.ipac, hscx * 0x40 + off2)); + } + return 0; +} + +static void +WriteHSCX(struct IsdnCardState *cs, int hscx, u_char offset, u_char value) +{ + u_short off2 = offset; + + switch (cs->subtyp) { + case R647: + off2 = ((off2 << 8 & 0xf000) | (off2 & 0xf)); + case R685: + writereg(cs->hw.gazel.hscx[hscx], off2, value); + break; + case R753: + case R742: + writereg_ipac(cs->hw.gazel.ipac, hscx * 0x40 + off2, value); + break; + } +} + +/* + * fast interrupt HSCX stuff goes here + */ + +#define READHSCX(cs, nr, reg) ReadHSCX(cs, nr, reg) +#define WRITEHSCX(cs, nr, reg, data) WriteHSCX(cs, nr, reg, data) +#define READHSCXFIFO(cs, nr, ptr, cnt) ReadHSCXfifo(cs, nr, ptr, cnt) +#define WRITEHSCXFIFO(cs, nr, ptr, cnt) WriteHSCXfifo(cs, nr, ptr, cnt) + +#include "hscx_irq.c" + +static irqreturn_t +gazel_interrupt(int intno, void *dev_id, struct pt_regs *regs) +{ +#define MAXCOUNT 5 + struct IsdnCardState *cs = dev_id; + u_char valisac, valhscx; + int count = 0; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + do { + valhscx = ReadHSCX(cs, 1, HSCX_ISTA); + if (valhscx) + hscx_int_main(cs, valhscx); + valisac = ReadISAC(cs, ISAC_ISTA); + if (valisac) + isac_interrupt(cs, valisac); + count++; + } while ((valhscx || valisac) && (count < MAXCOUNT)); + + WriteHSCX(cs, 0, HSCX_MASK, 0xFF); + WriteHSCX(cs, 1, HSCX_MASK, 0xFF); + WriteISAC(cs, ISAC_MASK, 0xFF); + WriteISAC(cs, ISAC_MASK, 0x0); + WriteHSCX(cs, 0, HSCX_MASK, 0x0); + WriteHSCX(cs, 1, HSCX_MASK, 0x0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + + +static irqreturn_t +gazel_interrupt_ipac(int intno, void *dev_id, struct pt_regs *regs) +{ + struct IsdnCardState *cs = dev_id; + u_char ista, val; + int count = 0; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + ista = ReadISAC(cs, IPAC_ISTA - 0x80); + do { + if (ista & 0x0f) { + val = ReadHSCX(cs, 1, HSCX_ISTA); + if (ista & 0x01) + val |= 0x01; + if (ista & 0x04) + val |= 0x02; + if (ista & 0x08) + val |= 0x04; + if (val) { + hscx_int_main(cs, val); + } + } + if (ista & 0x20) { + val = 0xfe & ReadISAC(cs, ISAC_ISTA); + if (val) { + isac_interrupt(cs, val); + } + } + if (ista & 0x10) { + val = 0x01; + isac_interrupt(cs, val); + } + ista = ReadISAC(cs, IPAC_ISTA - 0x80); + count++; + } + while ((ista & 0x3f) && (count < MAXCOUNT)); + + WriteISAC(cs, IPAC_MASK - 0x80, 0xFF); + WriteISAC(cs, IPAC_MASK - 0x80, 0xC0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} +void +release_io_gazel(struct IsdnCardState *cs) +{ + unsigned int i; + + switch (cs->subtyp) { + case R647: + for (i = 0x0000; i < 0xC000; i += 0x1000) + release_region(i + cs->hw.gazel.hscx[0], 16); + release_region(0xC000 + cs->hw.gazel.hscx[0], 1); + break; + + case R685: + release_region(cs->hw.gazel.hscx[0], 0x100); + release_region(cs->hw.gazel.cfg_reg, 0x80); + break; + + case R753: + release_region(cs->hw.gazel.ipac, 0x8); + release_region(cs->hw.gazel.cfg_reg, 0x80); + break; + + case R742: + release_region(cs->hw.gazel.ipac, 8); + break; + } +} + +static int +reset_gazel(struct IsdnCardState *cs) +{ + unsigned long plxcntrl, addr = cs->hw.gazel.cfg_reg; + + switch (cs->subtyp) { + case R647: + writereg(addr, 0, 0); + HZDELAY(10); + writereg(addr, 0, 1); + HZDELAY(2); + break; + case R685: + plxcntrl = inl(addr + PLX_CNTRL); + plxcntrl |= (RESET_9050 + RESET_GAZEL); + outl(plxcntrl, addr + PLX_CNTRL); + plxcntrl &= ~(RESET_9050 + RESET_GAZEL); + HZDELAY(4); + outl(plxcntrl, addr + PLX_CNTRL); + HZDELAY(10); + outb(INT_ISAC_EN + INT_HSCX_EN + INT_PCI_EN, addr + PLX_INCSR); + break; + case R753: + plxcntrl = inl(addr + PLX_CNTRL); + plxcntrl |= (RESET_9050 + RESET_GAZEL); + outl(plxcntrl, addr + PLX_CNTRL); + plxcntrl &= ~(RESET_9050 + RESET_GAZEL); + WriteISAC(cs, IPAC_POTA2 - 0x80, 0x20); + HZDELAY(4); + outl(plxcntrl, addr + PLX_CNTRL); + HZDELAY(10); + WriteISAC(cs, IPAC_POTA2 - 0x80, 0x00); + WriteISAC(cs, IPAC_ACFG - 0x80, 0xff); + WriteISAC(cs, IPAC_AOE - 0x80, 0x0); + WriteISAC(cs, IPAC_MASK - 0x80, 0xff); + WriteISAC(cs, IPAC_CONF - 0x80, 0x1); + outb(INT_IPAC_EN + INT_PCI_EN, addr + PLX_INCSR); + WriteISAC(cs, IPAC_MASK - 0x80, 0xc0); + break; + case R742: + WriteISAC(cs, IPAC_POTA2 - 0x80, 0x20); + HZDELAY(4); + WriteISAC(cs, IPAC_POTA2 - 0x80, 0x00); + WriteISAC(cs, IPAC_ACFG - 0x80, 0xff); + WriteISAC(cs, IPAC_AOE - 0x80, 0x0); + WriteISAC(cs, IPAC_MASK - 0x80, 0xff); + WriteISAC(cs, IPAC_CONF - 0x80, 0x1); + WriteISAC(cs, IPAC_MASK - 0x80, 0xc0); + break; + } + return (0); +} + +static int +Gazel_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + reset_gazel(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_RELEASE: + release_io_gazel(cs); + return (0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + inithscxisac(cs, 1); + if ((cs->subtyp==R647)||(cs->subtyp==R685)) { + int i; + for (i=0;i<(2+MAX_WAITING_CALLS);i++) { + cs->bcs[i].hw.hscx.tsaxr0 = 0x1f; + cs->bcs[i].hw.hscx.tsaxr1 = 0x23; + } + } + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_TEST: + return (0); + } + return (0); +} + +static int +reserve_regions(struct IsdnCard *card, struct IsdnCardState *cs) +{ + unsigned int i, j, base = 0, adr = 0, len = 0; + + switch (cs->subtyp) { + case R647: + base = cs->hw.gazel.hscx[0]; + if (!request_region(adr = (0xC000 + base), len = 1, "gazel")) + goto error; + for (i = 0x0000; i < 0xC000; i += 0x1000) { + if (!request_region(adr = (i + base), len = 16, "gazel")) + goto error; + } + if (i != 0xC000) { + for (j = 0; j < i; j+= 0x1000) + release_region(j + base, 16); + release_region(0xC000 + base, 1); + goto error; + } + break; + + case R685: + if (!request_region(adr = cs->hw.gazel.hscx[0], len = 0x100, "gazel")) + goto error; + if (!request_region(adr = cs->hw.gazel.cfg_reg, len = 0x80, "gazel")) { + release_region(cs->hw.gazel.hscx[0],0x100); + goto error; + } + break; + + case R753: + if (!request_region(adr = cs->hw.gazel.ipac, len = 0x8, "gazel")) + goto error; + if (!request_region(adr = cs->hw.gazel.cfg_reg, len = 0x80, "gazel")) { + release_region(cs->hw.gazel.ipac, 8); + goto error; + } + break; + + case R742: + if (!request_region(adr = cs->hw.gazel.ipac, len = 0x8, "gazel")) + goto error; + break; + } + + return 0; + + error: + printk(KERN_WARNING "Gazel: %s io ports 0x%x-0x%x already in use\n", + CardType[cs->typ], adr, adr + len); + return 1; +} + +static int __init +setup_gazelisa(struct IsdnCard *card, struct IsdnCardState *cs) +{ + printk(KERN_INFO "Gazel: ISA PnP card automatic recognition\n"); + // we got an irq parameter, assume it is an ISA card + // R742 decodes address even in not started... + // R647 returns FF if not present or not started + // eventually needs improvment + if (readreg_ipac(card->para[1], IPAC_ID) == 1) + cs->subtyp = R742; + else + cs->subtyp = R647; + + setup_isac(cs); + cs->hw.gazel.cfg_reg = card->para[1] + 0xC000; + cs->hw.gazel.ipac = card->para[1]; + cs->hw.gazel.isac = card->para[1] + 0x8000; + cs->hw.gazel.hscx[0] = card->para[1]; + cs->hw.gazel.hscx[1] = card->para[1] + 0x4000; + cs->irq = card->para[0]; + cs->hw.gazel.isacfifo = cs->hw.gazel.isac; + cs->hw.gazel.hscxfifo[0] = cs->hw.gazel.hscx[0]; + cs->hw.gazel.hscxfifo[1] = cs->hw.gazel.hscx[1]; + + switch (cs->subtyp) { + case R647: + printk(KERN_INFO "Gazel: Card ISA R647/R648 found\n"); + cs->dc.isac.adf2 = 0x87; + printk(KERN_INFO + "Gazel: config irq:%d isac:0x%X cfg:0x%X\n", + cs->irq, cs->hw.gazel.isac, cs->hw.gazel.cfg_reg); + printk(KERN_INFO + "Gazel: hscx A:0x%X hscx B:0x%X\n", + cs->hw.gazel.hscx[0], cs->hw.gazel.hscx[1]); + + break; + case R742: + printk(KERN_INFO "Gazel: Card ISA R742 found\n"); + test_and_set_bit(HW_IPAC, &cs->HW_Flags); + printk(KERN_INFO + "Gazel: config irq:%d ipac:0x%X\n", + cs->irq, cs->hw.gazel.ipac); + break; + } + + return (0); +} + +static struct pci_dev *dev_tel __initdata = NULL; + +static int __init +setup_gazelpci(struct IsdnCardState *cs) +{ + u_int pci_ioaddr0 = 0, pci_ioaddr1 = 0; + u_char pci_irq = 0, found; + u_int nbseek, seekcard; + + printk(KERN_WARNING "Gazel: PCI card automatic recognition\n"); + + found = 0; + seekcard = PCI_DEVICE_ID_PLX_R685; + for (nbseek = 0; nbseek < 3; nbseek++) { + if ((dev_tel = pci_find_device(PCI_VENDOR_ID_PLX, seekcard, dev_tel))) { + if (pci_enable_device(dev_tel)) + return 1; + pci_irq = dev_tel->irq; + pci_ioaddr0 = pci_resource_start(dev_tel, 1); + pci_ioaddr1 = pci_resource_start(dev_tel, 2); + found = 1; + } + if (found) + break; + else { + switch (seekcard) { + case PCI_DEVICE_ID_PLX_R685: + seekcard = PCI_DEVICE_ID_PLX_R753; + break; + case PCI_DEVICE_ID_PLX_R753: + seekcard = PCI_DEVICE_ID_PLX_DJINN_ITOO; + break; + } + } + } + if (!found) { + printk(KERN_WARNING "Gazel: No PCI card found\n"); + return (1); + } + if (!pci_irq) { + printk(KERN_WARNING "Gazel: No IRQ for PCI card found\n"); + return 1; + } + cs->hw.gazel.pciaddr[0] = pci_ioaddr0; + cs->hw.gazel.pciaddr[1] = pci_ioaddr1; + setup_isac(cs); + pci_ioaddr1 &= 0xfffe; + cs->hw.gazel.cfg_reg = pci_ioaddr0 & 0xfffe; + cs->hw.gazel.ipac = pci_ioaddr1; + cs->hw.gazel.isac = pci_ioaddr1 + 0x80; + cs->hw.gazel.hscx[0] = pci_ioaddr1; + cs->hw.gazel.hscx[1] = pci_ioaddr1 + 0x40; + cs->hw.gazel.isacfifo = cs->hw.gazel.isac; + cs->hw.gazel.hscxfifo[0] = cs->hw.gazel.hscx[0]; + cs->hw.gazel.hscxfifo[1] = cs->hw.gazel.hscx[1]; + cs->irq = pci_irq; + cs->irq_flags |= SA_SHIRQ; + + switch (seekcard) { + case PCI_DEVICE_ID_PLX_R685: + printk(KERN_INFO "Gazel: Card PCI R685 found\n"); + cs->subtyp = R685; + cs->dc.isac.adf2 = 0x87; + printk(KERN_INFO + "Gazel: config irq:%d isac:0x%X cfg:0x%X\n", + cs->irq, cs->hw.gazel.isac, cs->hw.gazel.cfg_reg); + printk(KERN_INFO + "Gazel: hscx A:0x%X hscx B:0x%X\n", + cs->hw.gazel.hscx[0], cs->hw.gazel.hscx[1]); + break; + case PCI_DEVICE_ID_PLX_R753: + case PCI_DEVICE_ID_PLX_DJINN_ITOO: + printk(KERN_INFO "Gazel: Card PCI R753 found\n"); + cs->subtyp = R753; + test_and_set_bit(HW_IPAC, &cs->HW_Flags); + printk(KERN_INFO + "Gazel: config irq:%d ipac:0x%X cfg:0x%X\n", + cs->irq, cs->hw.gazel.ipac, cs->hw.gazel.cfg_reg); + break; + } + + return (0); +} + +int __init +setup_gazel(struct IsdnCard *card) +{ + struct IsdnCardState *cs = card->cs; + char tmp[64]; + u_char val; + + strcpy(tmp, gazel_revision); + printk(KERN_INFO "Gazel: Driver Revision %s\n", HiSax_getrev(tmp)); + + if (cs->typ != ISDN_CTYPE_GAZEL) + return (0); + + if (card->para[0]) { + if (setup_gazelisa(card, cs)) + return (0); + } else { + +#ifdef CONFIG_PCI + if (setup_gazelpci(cs)) + return (0); +#else + printk(KERN_WARNING "Gazel: Card PCI requested and NO_PCI_BIOS, unable to config\n"); + return (0); +#endif /* CONFIG_PCI */ + } + + if (reserve_regions(card, cs)) { + return (0); + } + if (reset_gazel(cs)) { + printk(KERN_WARNING "Gazel: wrong IRQ\n"); + release_io_gazel(cs); + return (0); + } + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->BC_Read_Reg = &ReadHSCX; + cs->BC_Write_Reg = &WriteHSCX; + cs->BC_Send_Data = &hscx_fill_fifo; + cs->cardmsg = &Gazel_card_msg; + + switch (cs->subtyp) { + case R647: + case R685: + cs->irq_func = &gazel_interrupt; + ISACVersion(cs, "Gazel:"); + if (HscxVersion(cs, "Gazel:")) { + printk(KERN_WARNING + "Gazel: wrong HSCX versions check IO address\n"); + release_io_gazel(cs); + return (0); + } + break; + case R742: + case R753: + cs->irq_func = &gazel_interrupt_ipac; + val = ReadISAC(cs, IPAC_ID - 0x80); + printk(KERN_INFO "Gazel: IPAC version %x\n", val); + break; + } + + return (1); +} diff --git a/drivers/isdn/hisax/hfc4s8s_l1.c b/drivers/isdn/hisax/hfc4s8s_l1.c new file mode 100644 index 000000000000..1ac46c26b936 --- /dev/null +++ b/drivers/isdn/hisax/hfc4s8s_l1.c @@ -0,0 +1,1714 @@ +/*************************************************************************/ +/* $Id: hfc4s8s_l1.c,v 1.10 2005/02/09 16:31:09 martinb1 Exp $ */ +/* HFC-4S/8S low layer interface for Cologne Chip HFC-4S/8S isdn chips */ +/* The low layer (L1) is implemented as a loadable module for usage with */ +/* the HiSax isdn driver for passive cards. */ +/* */ +/* Author: Werner Cornelius */ +/* (C) 2003 Cornelius Consult (werner@cornelius-consult.de) */ +/* */ +/* Driver maintained by Cologne Chip */ +/* - Martin Bachem, support@colognechip.com */ +/* */ +/* This driver only works with chip revisions >= 1, older revision 0 */ +/* engineering samples (only first manufacturer sample cards) will not */ +/* work and are rejected by the driver. */ +/* */ +/* This file distributed under the GNU GPL. */ +/* */ +/* See Version History at the end of this file */ +/* */ +/*************************************************************************/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/config.h> +#include <linux/pci.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/timer.h> +#include <linux/skbuff.h> +#include <linux/wait.h> +#include "hisax_if.h" +#include "hfc4s8s_l1.h" + +static const char hfc4s8s_rev[] = "Revision: 1.10"; + +/***************************************************************/ +/* adjustable transparent mode fifo threshold */ +/* The value defines the used fifo threshold with the equation */ +/* */ +/* notify number of bytes = 2 * 2 ^ TRANS_FIFO_THRES */ +/* */ +/* The default value is 5 which results in a buffer size of 64 */ +/* and an interrupt rate of 8ms. */ +/* The maximum value is 7 due to fifo size restrictions. */ +/* Values below 3-4 are not recommended due to high interrupt */ +/* load of the processor. For non critical applications the */ +/* value should be raised to 7 to reduce any interrupt overhead*/ +/***************************************************************/ +#define TRANS_FIFO_THRES 5 + +/*************/ +/* constants */ +/*************/ +#define CLOCKMODE_0 0 /* ext. 24.576 MhZ clk freq, int. single clock mode */ +#define CLOCKMODE_1 1 /* ext. 49.576 MhZ clk freq, int. single clock mode */ +#define CHIP_ID_SHIFT 4 +#define HFC_MAX_ST 8 +#define MAX_D_FRAME_SIZE 270 +#define MAX_B_FRAME_SIZE 1536 +#define TRANS_TIMER_MODE (TRANS_FIFO_THRES & 0xf) +#define TRANS_FIFO_BYTES (2 << TRANS_FIFO_THRES) +#define MAX_F_CNT 0x0f + +#define CLKDEL_NT 0x6c +#define CLKDEL_TE 0xf +#define CTRL0_NT 4 +#define CTRL0_TE 0 + +#define L1_TIMER_T4 2 /* minimum in jiffies */ +#define L1_TIMER_T3 (7 * HZ) /* activation timeout */ +#define L1_TIMER_T1 ((120 * HZ) / 1000) /* NT mode deactivation timeout */ + + +/******************/ +/* types and vars */ +/******************/ +static int card_cnt; + +/* private driver_data */ +typedef struct { + int chip_id; + int clock_mode; + int max_st_ports; + char *device_name; +} hfc4s8s_param; + +static struct pci_device_id hfc4s8s_ids[] = { + {.vendor = PCI_VENDOR_ID_CCD, + .device = PCI_DEVICE_ID_4S, + .subvendor = 0x1397, + .subdevice = 0x08b4, + .driver_data = + (unsigned long) &((hfc4s8s_param) {CHIP_ID_4S, CLOCKMODE_0, 4, + "HFC-4S Evaluation Board"}), + }, + {.vendor = PCI_VENDOR_ID_CCD, + .device = PCI_DEVICE_ID_8S, + .subvendor = 0x1397, + .subdevice = 0x16b8, + .driver_data = + (unsigned long) &((hfc4s8s_param) {CHIP_ID_8S, CLOCKMODE_0, 8, + "HFC-8S Evaluation Board"}), + }, + {.vendor = PCI_VENDOR_ID_CCD, + .device = PCI_DEVICE_ID_4S, + .subvendor = 0x1397, + .subdevice = 0xb520, + .driver_data = + (unsigned long) &((hfc4s8s_param) {CHIP_ID_4S, CLOCKMODE_1, 4, + "IOB4ST"}), + }, + {.vendor = PCI_VENDOR_ID_CCD, + .device = PCI_DEVICE_ID_8S, + .subvendor = 0x1397, + .subdevice = 0xb522, + .driver_data = + (unsigned long) &((hfc4s8s_param) {CHIP_ID_8S, CLOCKMODE_1, 8, + "IOB8ST"}), + }, + {} +}; + +MODULE_DEVICE_TABLE(pci, hfc4s8s_ids); + +MODULE_AUTHOR("Werner Cornelius, werner@cornelius-consult.de"); +MODULE_DESCRIPTION("ISDN layer 1 for Cologne Chip HFC-4S/8S chips"); +MODULE_LICENSE("GPL"); + +/***********/ +/* layer 1 */ +/***********/ +struct hfc4s8s_btype { + spinlock_t lock; + struct hisax_b_if b_if; + struct hfc4s8s_l1 *l1p; + struct sk_buff_head tx_queue; + struct sk_buff *tx_skb; + struct sk_buff *rx_skb; + __u8 *rx_ptr; + int tx_cnt; + int bchan; + int mode; +}; + +struct _hfc4s8s_hw; + +struct hfc4s8s_l1 { + spinlock_t lock; + struct _hfc4s8s_hw *hw; /* pointer to hardware area */ + int l1_state; /* actual l1 state */ + struct timer_list l1_timer; /* layer 1 timer structure */ + int nt_mode; /* set to nt mode */ + int st_num; /* own index */ + int enabled; /* interface is enabled */ + struct sk_buff_head d_tx_queue; /* send queue */ + int tx_cnt; /* bytes to send */ + struct hisax_d_if d_if; /* D-channel interface */ + struct hfc4s8s_btype b_ch[2]; /* B-channel data */ + struct hisax_b_if *b_table[2]; +}; + +/**********************/ +/* hardware structure */ +/**********************/ +typedef struct _hfc4s8s_hw { + spinlock_t lock; + + int cardnum; + int ifnum; + int iobase; + int nt_mode; + u_char *membase; + u_char *hw_membase; + void *pdev; + int max_fifo; + hfc4s8s_param driver_data; + int irq; + int fifo_sched_cnt; + struct work_struct tqueue; + struct hfc4s8s_l1 l1[HFC_MAX_ST]; + char card_name[60]; + struct { + u_char r_irq_ctrl; + u_char r_ctrl0; + volatile u_char r_irq_statech; /* active isdn l1 status */ + u_char r_irqmsk_statchg; /* enabled isdn status ints */ + u_char r_irq_fifo_blx[8]; /* fifo status registers */ + u_char fifo_rx_trans_enables[8]; /* mask for enabled transparent rx fifos */ + u_char fifo_slow_timer_service[8]; /* mask for fifos needing slower timer service */ + volatile u_char r_irq_oview; /* contents of overview register */ + volatile u_char timer_irq; + int timer_usg_cnt; /* number of channels using timer */ + } mr; +} hfc4s8s_hw; + + + +/***************************/ +/* inline function defines */ +/***************************/ +#ifdef CONFIG_HISAX_HFC4S8S_PCIMEM /* inline functions mempry mapped */ + +/* memory write and dummy IO read to avoid PCI byte merge problems */ +#define Write_hfc8(a,b,c) {(*((volatile u_char *)(a->membase+b)) = c); inb(a->iobase+4);} +/* memory write without dummy IO access for fifo data access */ +#define fWrite_hfc8(a,b,c) (*((volatile u_char *)(a->membase+b)) = c) +#define Read_hfc8(a,b) (*((volatile u_char *)(a->membase+b))) +#define Write_hfc16(a,b,c) (*((volatile unsigned short *)(a->membase+b)) = c) +#define Read_hfc16(a,b) (*((volatile unsigned short *)(a->membase+b))) +#define Write_hfc32(a,b,c) (*((volatile unsigned long *)(a->membase+b)) = c) +#define Read_hfc32(a,b) (*((volatile unsigned long *)(a->membase+b))) +#define wait_busy(a) {while ((Read_hfc8(a, R_STATUS) & M_BUSY));} +#define PCI_ENA_MEMIO 0x03 + +#else + +/* inline functions io mapped */ +static inline void +SetRegAddr(hfc4s8s_hw * a, u_char b) +{ + outb(b, (a->iobase) + 4); +} + +static inline u_char +GetRegAddr(hfc4s8s_hw * a) +{ + return (inb((volatile u_int) (a->iobase + 4))); +} + + +static inline void +Write_hfc8(hfc4s8s_hw * a, u_char b, u_char c) +{ + SetRegAddr(a, b); + outb(c, a->iobase); +} + +static inline void +fWrite_hfc8(hfc4s8s_hw * a, u_char c) +{ + outb(c, a->iobase); +} + +static inline void +Write_hfc16(hfc4s8s_hw * a, u_char b, u_short c) +{ + SetRegAddr(a, b); + outw(c, a->iobase); +} + +static inline void +Write_hfc32(hfc4s8s_hw * a, u_char b, u_long c) +{ + SetRegAddr(a, b); + outl(c, a->iobase); +} + +static inline void +fWrite_hfc32(hfc4s8s_hw * a, u_long c) +{ + outl(c, a->iobase); +} + +static inline u_char +Read_hfc8(hfc4s8s_hw * a, u_char b) +{ + SetRegAddr(a, b); + return (inb((volatile u_int) a->iobase)); +} + +static inline u_char +fRead_hfc8(hfc4s8s_hw * a) +{ + return (inb((volatile u_int) a->iobase)); +} + + +static inline u_short +Read_hfc16(hfc4s8s_hw * a, u_char b) +{ + SetRegAddr(a, b); + return (inw((volatile u_int) a->iobase)); +} + +static inline u_long +Read_hfc32(hfc4s8s_hw * a, u_char b) +{ + SetRegAddr(a, b); + return (inl((volatile u_int) a->iobase)); +} + +static inline u_long +fRead_hfc32(hfc4s8s_hw * a) +{ + return (inl((volatile u_int) a->iobase)); +} + +static inline void +wait_busy(hfc4s8s_hw * a) +{ + SetRegAddr(a, R_STATUS); + while (inb((volatile u_int) a->iobase) & M_BUSY); +} + +#define PCI_ENA_REGIO 0x01 + +#endif /* CONFIG_HISAX_HFC4S8S_PCIMEM */ + +/******************************************************/ +/* function to read critical counter registers that */ +/* may be udpated by the chip during read */ +/******************************************************/ +static volatile u_char +Read_hfc8_stable(hfc4s8s_hw * hw, int reg) +{ + u_char ref8; + u_char in8; + ref8 = Read_hfc8(hw, reg); + while (((in8 = Read_hfc8(hw, reg)) != ref8)) { + ref8 = in8; + } + return in8; +} + +static volatile int +Read_hfc16_stable(hfc4s8s_hw * hw, int reg) +{ + int ref16; + int in16; + + ref16 = Read_hfc16(hw, reg); + while (((in16 = Read_hfc16(hw, reg)) != ref16)) { + ref16 = in16; + } + return in16; +} + +/*****************************/ +/* D-channel call from HiSax */ +/*****************************/ +static void +dch_l2l1(struct hisax_d_if *iface, int pr, void *arg) +{ + struct hfc4s8s_l1 *l1 = iface->ifc.priv; + struct sk_buff *skb = (struct sk_buff *) arg; + u_long flags; + + switch (pr) { + + case (PH_DATA | REQUEST): + if (!l1->enabled) { + dev_kfree_skb(skb); + break; + } + spin_lock_irqsave(&l1->lock, flags); + skb_queue_tail(&l1->d_tx_queue, skb); + if ((skb_queue_len(&l1->d_tx_queue) == 1) && + (l1->tx_cnt <= 0)) { + l1->hw->mr.r_irq_fifo_blx[l1->st_num] |= + 0x10; + spin_unlock_irqrestore(&l1->lock, flags); + schedule_work(&l1->hw->tqueue); + } else + spin_unlock_irqrestore(&l1->lock, flags); + break; + + case (PH_ACTIVATE | REQUEST): + if (!l1->enabled) + break; + if (!l1->nt_mode) { + if (l1->l1_state < 6) { + spin_lock_irqsave(&l1->lock, + flags); + + Write_hfc8(l1->hw, R_ST_SEL, + l1->st_num); + Write_hfc8(l1->hw, A_ST_WR_STA, + 0x60); + mod_timer(&l1->l1_timer, + jiffies + L1_TIMER_T3); + spin_unlock_irqrestore(&l1->lock, + flags); + } else if (l1->l1_state == 7) + l1->d_if.ifc.l1l2(&l1->d_if.ifc, + PH_ACTIVATE | + INDICATION, + NULL); + } else { + if (l1->l1_state != 3) { + spin_lock_irqsave(&l1->lock, + flags); + Write_hfc8(l1->hw, R_ST_SEL, + l1->st_num); + Write_hfc8(l1->hw, A_ST_WR_STA, + 0x60); + spin_unlock_irqrestore(&l1->lock, + flags); + } else if (l1->l1_state == 3) + l1->d_if.ifc.l1l2(&l1->d_if.ifc, + PH_ACTIVATE | + INDICATION, + NULL); + } + break; + + default: + printk(KERN_INFO + "HFC-4S/8S: Unknown D-chan cmd 0x%x received, ignored\n", + pr); + break; + } + if (!l1->enabled) + l1->d_if.ifc.l1l2(&l1->d_if.ifc, + PH_DEACTIVATE | INDICATION, NULL); +} /* dch_l2l1 */ + +/*****************************/ +/* B-channel call from HiSax */ +/*****************************/ +static void +bch_l2l1(struct hisax_if *ifc, int pr, void *arg) +{ + struct hfc4s8s_btype *bch = ifc->priv; + struct hfc4s8s_l1 *l1 = bch->l1p; + struct sk_buff *skb = (struct sk_buff *) arg; + int mode = (int) arg; + u_long flags; + + switch (pr) { + + case (PH_DATA | REQUEST): + if (!l1->enabled || (bch->mode == L1_MODE_NULL)) { + dev_kfree_skb(skb); + break; + } + spin_lock_irqsave(&l1->lock, flags); + skb_queue_tail(&bch->tx_queue, skb); + if (!bch->tx_skb && (bch->tx_cnt <= 0)) { + l1->hw->mr.r_irq_fifo_blx[l1->st_num] |= + ((bch->bchan == 1) ? 1 : 4); + spin_unlock_irqrestore(&l1->lock, flags); + schedule_work(&l1->hw->tqueue); + } else + spin_unlock_irqrestore(&l1->lock, flags); + break; + + case (PH_ACTIVATE | REQUEST): + case (PH_DEACTIVATE | REQUEST): + if (!l1->enabled) + break; + if (pr == (PH_DEACTIVATE | REQUEST)) + mode = L1_MODE_NULL; + + switch (mode) { + case L1_MODE_HDLC: + spin_lock_irqsave(&l1->lock, + flags); + l1->hw->mr.timer_usg_cnt++; + l1->hw->mr. + fifo_slow_timer_service[l1-> + st_num] + |= + ((bch->bchan == + 1) ? 0x2 : 0x8); + Write_hfc8(l1->hw, R_FIFO, + (l1->st_num * 8 + + ((bch->bchan == + 1) ? 0 : 2))); + wait_busy(l1->hw); + Write_hfc8(l1->hw, A_CON_HDLC, 0xc); /* HDLC mode, flag fill, connect ST */ + Write_hfc8(l1->hw, A_SUBCH_CFG, 0); /* 8 bits */ + Write_hfc8(l1->hw, A_IRQ_MSK, 1); /* enable TX interrupts for hdlc */ + Write_hfc8(l1->hw, A_INC_RES_FIFO, 2); /* reset fifo */ + wait_busy(l1->hw); + + Write_hfc8(l1->hw, R_FIFO, + (l1->st_num * 8 + + ((bch->bchan == + 1) ? 1 : 3))); + wait_busy(l1->hw); + Write_hfc8(l1->hw, A_CON_HDLC, 0xc); /* HDLC mode, flag fill, connect ST */ + Write_hfc8(l1->hw, A_SUBCH_CFG, 0); /* 8 bits */ + Write_hfc8(l1->hw, A_IRQ_MSK, 1); /* enable RX interrupts for hdlc */ + Write_hfc8(l1->hw, A_INC_RES_FIFO, 2); /* reset fifo */ + + Write_hfc8(l1->hw, R_ST_SEL, + l1->st_num); + l1->hw->mr.r_ctrl0 |= + (bch->bchan & 3); + Write_hfc8(l1->hw, A_ST_CTRL0, + l1->hw->mr.r_ctrl0); + bch->mode = L1_MODE_HDLC; + spin_unlock_irqrestore(&l1->lock, + flags); + + bch->b_if.ifc.l1l2(&bch->b_if.ifc, + PH_ACTIVATE | + INDICATION, + NULL); + break; + + case L1_MODE_TRANS: + spin_lock_irqsave(&l1->lock, + flags); + l1->hw->mr. + fifo_rx_trans_enables[l1-> + st_num] + |= + ((bch->bchan == + 1) ? 0x2 : 0x8); + l1->hw->mr.timer_usg_cnt++; + Write_hfc8(l1->hw, R_FIFO, + (l1->st_num * 8 + + ((bch->bchan == + 1) ? 0 : 2))); + wait_busy(l1->hw); + Write_hfc8(l1->hw, A_CON_HDLC, 0xf); /* Transparent mode, 1 fill, connect ST */ + Write_hfc8(l1->hw, A_SUBCH_CFG, 0); /* 8 bits */ + Write_hfc8(l1->hw, A_IRQ_MSK, 0); /* disable TX interrupts */ + Write_hfc8(l1->hw, A_INC_RES_FIFO, 2); /* reset fifo */ + wait_busy(l1->hw); + + Write_hfc8(l1->hw, R_FIFO, + (l1->st_num * 8 + + ((bch->bchan == + 1) ? 1 : 3))); + wait_busy(l1->hw); + Write_hfc8(l1->hw, A_CON_HDLC, 0xf); /* Transparent mode, 1 fill, connect ST */ + Write_hfc8(l1->hw, A_SUBCH_CFG, 0); /* 8 bits */ + Write_hfc8(l1->hw, A_IRQ_MSK, 0); /* disable RX interrupts */ + Write_hfc8(l1->hw, A_INC_RES_FIFO, 2); /* reset fifo */ + + Write_hfc8(l1->hw, R_ST_SEL, + l1->st_num); + l1->hw->mr.r_ctrl0 |= + (bch->bchan & 3); + Write_hfc8(l1->hw, A_ST_CTRL0, + l1->hw->mr.r_ctrl0); + bch->mode = L1_MODE_TRANS; + spin_unlock_irqrestore(&l1->lock, + flags); + + bch->b_if.ifc.l1l2(&bch->b_if.ifc, + PH_ACTIVATE | + INDICATION, + NULL); + break; + + default: + if (bch->mode == L1_MODE_NULL) + break; + spin_lock_irqsave(&l1->lock, + flags); + l1->hw->mr. + fifo_slow_timer_service[l1-> + st_num] + &= + ~((bch->bchan == + 1) ? 0x3 : 0xc); + l1->hw->mr. + fifo_rx_trans_enables[l1-> + st_num] + &= + ~((bch->bchan == + 1) ? 0x3 : 0xc); + l1->hw->mr.timer_usg_cnt--; + Write_hfc8(l1->hw, R_FIFO, + (l1->st_num * 8 + + ((bch->bchan == + 1) ? 0 : 2))); + wait_busy(l1->hw); + Write_hfc8(l1->hw, A_IRQ_MSK, 0); /* disable TX interrupts */ + wait_busy(l1->hw); + Write_hfc8(l1->hw, R_FIFO, + (l1->st_num * 8 + + ((bch->bchan == + 1) ? 1 : 3))); + wait_busy(l1->hw); + Write_hfc8(l1->hw, A_IRQ_MSK, 0); /* disable RX interrupts */ + Write_hfc8(l1->hw, R_ST_SEL, + l1->st_num); + l1->hw->mr.r_ctrl0 &= + ~(bch->bchan & 3); + Write_hfc8(l1->hw, A_ST_CTRL0, + l1->hw->mr.r_ctrl0); + spin_unlock_irqrestore(&l1->lock, + flags); + + bch->mode = L1_MODE_NULL; + bch->b_if.ifc.l1l2(&bch->b_if.ifc, + PH_DEACTIVATE | + INDICATION, + NULL); + if (bch->tx_skb) { + dev_kfree_skb(bch->tx_skb); + bch->tx_skb = NULL; + } + if (bch->rx_skb) { + dev_kfree_skb(bch->rx_skb); + bch->rx_skb = NULL; + } + skb_queue_purge(&bch->tx_queue); + bch->tx_cnt = 0; + bch->rx_ptr = NULL; + break; + } + + /* timer is only used when at least one b channel */ + /* is set up to transparent mode */ + if (l1->hw->mr.timer_usg_cnt) { + Write_hfc8(l1->hw, R_IRQMSK_MISC, + M_TI_IRQMSK); + } else { + Write_hfc8(l1->hw, R_IRQMSK_MISC, 0); + } + + break; + + default: + printk(KERN_INFO + "HFC-4S/8S: Unknown B-chan cmd 0x%x received, ignored\n", + pr); + break; + } + if (!l1->enabled) + bch->b_if.ifc.l1l2(&bch->b_if.ifc, + PH_DEACTIVATE | INDICATION, NULL); +} /* bch_l2l1 */ + +/**************************/ +/* layer 1 timer function */ +/**************************/ +static void +hfc_l1_timer(struct hfc4s8s_l1 *l1) +{ + u_long flags; + + if (!l1->enabled) + return; + + spin_lock_irqsave(&l1->lock, flags); + if (l1->nt_mode) { + l1->l1_state = 1; + Write_hfc8(l1->hw, R_ST_SEL, l1->st_num); + Write_hfc8(l1->hw, A_ST_WR_STA, 0x11); + spin_unlock_irqrestore(&l1->lock, flags); + l1->d_if.ifc.l1l2(&l1->d_if.ifc, + PH_DEACTIVATE | INDICATION, NULL); + spin_lock_irqsave(&l1->lock, flags); + l1->l1_state = 1; + Write_hfc8(l1->hw, A_ST_WR_STA, 0x1); + spin_unlock_irqrestore(&l1->lock, flags); + } else { + /* activation timed out */ + Write_hfc8(l1->hw, R_ST_SEL, l1->st_num); + Write_hfc8(l1->hw, A_ST_WR_STA, 0x13); + spin_unlock_irqrestore(&l1->lock, flags); + l1->d_if.ifc.l1l2(&l1->d_if.ifc, + PH_DEACTIVATE | INDICATION, NULL); + spin_lock_irqsave(&l1->lock, flags); + Write_hfc8(l1->hw, R_ST_SEL, l1->st_num); + Write_hfc8(l1->hw, A_ST_WR_STA, 0x3); + spin_unlock_irqrestore(&l1->lock, flags); + } +} /* hfc_l1_timer */ + +/****************************************/ +/* a complete D-frame has been received */ +/****************************************/ +static void +rx_d_frame(struct hfc4s8s_l1 *l1p, int ech) +{ + int z1, z2; + u_char f1, f2, df; + struct sk_buff *skb; + u_char *cp; + + + if (!l1p->enabled) + return; + do { + /* E/D RX fifo */ + Write_hfc8(l1p->hw, R_FIFO, + (l1p->st_num * 8 + ((ech) ? 7 : 5))); + wait_busy(l1p->hw); + + f1 = Read_hfc8_stable(l1p->hw, A_F1); + f2 = Read_hfc8(l1p->hw, A_F2); + df = f1 - f2; + if ((f1 - f2) < 0) + df = f1 - f2 + MAX_F_CNT + 1; + + + if (!df) { + return; /* no complete frame in fifo */ + } + + z1 = Read_hfc16_stable(l1p->hw, A_Z1); + z2 = Read_hfc16(l1p->hw, A_Z2); + + z1 = z1 - z2 + 1; + if (z1 < 0) + z1 += 384; + + if (!(skb = dev_alloc_skb(MAX_D_FRAME_SIZE))) { + printk(KERN_INFO + "HFC-4S/8S: Could not allocate D/E " + "channel receive buffer"); + Write_hfc8(l1p->hw, A_INC_RES_FIFO, 2); + wait_busy(l1p->hw); + return; + } + + if (((z1 < 4) || (z1 > MAX_D_FRAME_SIZE))) { + if (skb) + dev_kfree_skb(skb); + /* remove errornous D frame */ + if (df == 1) { + /* reset fifo */ + Write_hfc8(l1p->hw, A_INC_RES_FIFO, 2); + wait_busy(l1p->hw); + return; + } else { + /* read errornous D frame */ + +#ifndef CONFIG_HISAX_HFC4S8S_PCIMEM + SetRegAddr(l1p->hw, A_FIFO_DATA0); +#endif + + while (z1 >= 4) { +#ifdef CONFIG_HISAX_HFC4S8S_PCIMEM + Read_hfc32(l1p->hw, A_FIFO_DATA0); +#else + fRead_hfc32(l1p->hw); +#endif + z1 -= 4; + } + + while (z1--) +#ifdef CONFIG_HISAX_HFC4S8S_PCIMEM + Read_hfc8(l1p->hw, A_FIFO_DATA0); +#else + fRead_hfc8(l1p->hw); +#endif + + Write_hfc8(l1p->hw, A_INC_RES_FIFO, 1); + wait_busy(l1p->hw); + return; + } + } + + cp = skb->data; + +#ifndef CONFIG_HISAX_HFC4S8S_PCIMEM + SetRegAddr(l1p->hw, A_FIFO_DATA0); +#endif + + while (z1 >= 4) { +#ifdef CONFIG_HISAX_HFC4S8S_PCIMEM + *((unsigned long *) cp) = + Read_hfc32(l1p->hw, A_FIFO_DATA0); +#else + *((unsigned long *) cp) = fRead_hfc32(l1p->hw); +#endif + cp += 4; + z1 -= 4; + } + + while (z1--) +#ifdef CONFIG_HISAX_HFC4S8S_PCIMEM + *cp++ = Read_hfc8(l1p->hw, A_FIFO_DATA0); +#else + *cp++ = fRead_hfc8(l1p->hw); +#endif + + Write_hfc8(l1p->hw, A_INC_RES_FIFO, 1); /* increment f counter */ + wait_busy(l1p->hw); + + if (*(--cp)) { + dev_kfree_skb(skb); + } else { + skb->len = (cp - skb->data) - 2; + if (ech) + l1p->d_if.ifc.l1l2(&l1p->d_if.ifc, + PH_DATA_E | INDICATION, + skb); + else + l1p->d_if.ifc.l1l2(&l1p->d_if.ifc, + PH_DATA | INDICATION, + skb); + } + } while (1); +} /* rx_d_frame */ + +/*************************************************************/ +/* a B-frame has been received (perhaps not fully completed) */ +/*************************************************************/ +static void +rx_b_frame(struct hfc4s8s_btype *bch) +{ + int z1, z2, hdlc_complete; + u_char f1, f2; + struct hfc4s8s_l1 *l1 = bch->l1p; + struct sk_buff *skb; + + if (!l1->enabled || (bch->mode == L1_MODE_NULL)) + return; + + do { + /* RX Fifo */ + Write_hfc8(l1->hw, R_FIFO, + (l1->st_num * 8 + ((bch->bchan == 1) ? 1 : 3))); + wait_busy(l1->hw); + + if (bch->mode == L1_MODE_HDLC) { + f1 = Read_hfc8_stable(l1->hw, A_F1); + f2 = Read_hfc8(l1->hw, A_F2); + hdlc_complete = ((f1 ^ f2) & MAX_F_CNT); + } else + hdlc_complete = 0; + z1 = Read_hfc16_stable(l1->hw, A_Z1); + z2 = Read_hfc16(l1->hw, A_Z2); + z1 = (z1 - z2); + if (hdlc_complete) + z1++; + if (z1 < 0) + z1 += 384; + + if (!z1) + break; + + if (!(skb = bch->rx_skb)) { + if (! + (skb = + dev_alloc_skb((bch->mode == + L1_MODE_TRANS) ? z1 + : (MAX_B_FRAME_SIZE + 3)))) { + printk(KERN_ERR + "HFC-4S/8S: Could not allocate B " + "channel receive buffer"); + return; + } + bch->rx_ptr = skb->data; + bch->rx_skb = skb; + } + + skb->len = (bch->rx_ptr - skb->data) + z1; + + /* HDLC length check */ + if ((bch->mode == L1_MODE_HDLC) && + ((hdlc_complete && (skb->len < 4)) || + (skb->len > (MAX_B_FRAME_SIZE + 3)))) { + + skb->len = 0; + bch->rx_ptr = skb->data; + Write_hfc8(l1->hw, A_INC_RES_FIFO, 2); /* reset fifo */ + wait_busy(l1->hw); + return; + } +#ifndef CONFIG_HISAX_HFC4S8S_PCIMEM + SetRegAddr(l1->hw, A_FIFO_DATA0); +#endif + + while (z1 >= 4) { +#ifdef CONFIG_HISAX_HFC4S8S_PCIMEM + *((unsigned long *) bch->rx_ptr) = + Read_hfc32(l1->hw, A_FIFO_DATA0); +#else + *((unsigned long *) bch->rx_ptr) = + fRead_hfc32(l1->hw); +#endif + bch->rx_ptr += 4; + z1 -= 4; + } + + while (z1--) +#ifdef CONFIG_HISAX_HFC4S8S_PCIMEM + *(bch->rx_ptr++) = Read_hfc8(l1->hw, A_FIFO_DATA0); +#else + *(bch->rx_ptr++) = fRead_hfc8(l1->hw); +#endif + + if (hdlc_complete) { + /* increment f counter */ + Write_hfc8(l1->hw, A_INC_RES_FIFO, 1); + wait_busy(l1->hw); + + /* hdlc crc check */ + bch->rx_ptr--; + if (*bch->rx_ptr) { + skb->len = 0; + bch->rx_ptr = skb->data; + continue; + } + skb->len -= 3; + } + if (hdlc_complete || (bch->mode == L1_MODE_TRANS)) { + bch->rx_skb = NULL; + bch->rx_ptr = NULL; + bch->b_if.ifc.l1l2(&bch->b_if.ifc, + PH_DATA | INDICATION, skb); + } + + } while (1); +} /* rx_b_frame */ + +/********************************************/ +/* a D-frame has been/should be transmitted */ +/********************************************/ +static void +tx_d_frame(struct hfc4s8s_l1 *l1p) +{ + struct sk_buff *skb; + u_char f1, f2; + u_char *cp; + int cnt; + + if (l1p->l1_state != 7) + return; + + /* TX fifo */ + Write_hfc8(l1p->hw, R_FIFO, (l1p->st_num * 8 + 4)); + wait_busy(l1p->hw); + + f1 = Read_hfc8(l1p->hw, A_F1); + f2 = Read_hfc8_stable(l1p->hw, A_F2); + + if ((f1 ^ f2) & MAX_F_CNT) + return; /* fifo is still filled */ + + if (l1p->tx_cnt > 0) { + cnt = l1p->tx_cnt; + l1p->tx_cnt = 0; + l1p->d_if.ifc.l1l2(&l1p->d_if.ifc, PH_DATA | CONFIRM, + (void *) cnt); + } + + if ((skb = skb_dequeue(&l1p->d_tx_queue))) { + cp = skb->data; + cnt = skb->len; +#ifndef CONFIG_HISAX_HFC4S8S_PCIMEM + SetRegAddr(l1p->hw, A_FIFO_DATA0); +#endif + + while (cnt >= 4) { +#ifdef CONFIG_HISAX_HFC4S8S_PCIMEM + fWrite_hfc32(l1p->hw, A_FIFO_DATA0, + *(unsigned long *) cp); +#else + SetRegAddr(l1p->hw, A_FIFO_DATA0); + fWrite_hfc32(l1p->hw, *(unsigned long *) cp); +#endif + cp += 4; + cnt -= 4; + } + +#ifdef CONFIG_HISAX_HFC4S8S_PCIMEM + while (cnt--) + fWrite_hfc8(l1p->hw, A_FIFO_DATA0, *cp++); +#else + while (cnt--) + fWrite_hfc8(l1p->hw, *cp++); +#endif + + l1p->tx_cnt = skb->truesize; + Write_hfc8(l1p->hw, A_INC_RES_FIFO, 1); /* increment f counter */ + wait_busy(l1p->hw); + + dev_kfree_skb(skb); + } +} /* tx_d_frame */ + +/******************************************************/ +/* a B-frame may be transmitted (or is not completed) */ +/******************************************************/ +static void +tx_b_frame(struct hfc4s8s_btype *bch) +{ + struct sk_buff *skb; + struct hfc4s8s_l1 *l1 = bch->l1p; + u_char *cp; + int cnt, max, hdlc_num, ack_len = 0; + + if (!l1->enabled || (bch->mode == L1_MODE_NULL)) + return; + + /* TX fifo */ + Write_hfc8(l1->hw, R_FIFO, + (l1->st_num * 8 + ((bch->bchan == 1) ? 0 : 2))); + wait_busy(l1->hw); + do { + + if (bch->mode == L1_MODE_HDLC) { + hdlc_num = Read_hfc8(l1->hw, A_F1) & MAX_F_CNT; + hdlc_num -= + (Read_hfc8_stable(l1->hw, A_F2) & MAX_F_CNT); + if (hdlc_num < 0) + hdlc_num += 16; + if (hdlc_num >= 15) + break; /* fifo still filled up with hdlc frames */ + } else + hdlc_num = 0; + + if (!(skb = bch->tx_skb)) { + if (!(skb = skb_dequeue(&bch->tx_queue))) { + l1->hw->mr.fifo_slow_timer_service[l1-> + st_num] + &= ~((bch->bchan == 1) ? 1 : 4); + break; /* list empty */ + } + bch->tx_skb = skb; + bch->tx_cnt = 0; + } + + if (!hdlc_num) + l1->hw->mr.fifo_slow_timer_service[l1->st_num] |= + ((bch->bchan == 1) ? 1 : 4); + else + l1->hw->mr.fifo_slow_timer_service[l1->st_num] &= + ~((bch->bchan == 1) ? 1 : 4); + + max = Read_hfc16_stable(l1->hw, A_Z2); + max -= Read_hfc16(l1->hw, A_Z1); + if (max <= 0) + max += 384; + max--; + + if (max < 16) + break; /* don't write to small amounts of bytes */ + + cnt = skb->len - bch->tx_cnt; + if (cnt > max) + cnt = max; + cp = skb->data + bch->tx_cnt; + bch->tx_cnt += cnt; + +#ifndef CONFIG_HISAX_HFC4S8S_PCIMEM + SetRegAddr(l1->hw, A_FIFO_DATA0); +#endif + while (cnt >= 4) { +#ifdef CONFIG_HISAX_HFC4S8S_PCIMEM + fWrite_hfc32(l1->hw, A_FIFO_DATA0, + *(unsigned long *) cp); +#else + fWrite_hfc32(l1->hw, *(unsigned long *) cp); +#endif + cp += 4; + cnt -= 4; + } + + while (cnt--) +#ifdef CONFIG_HISAX_HFC4S8S_PCIMEM + fWrite_hfc8(l1->hw, A_FIFO_DATA0, *cp++); +#else + fWrite_hfc8(l1->hw, *cp++); +#endif + + if (bch->tx_cnt >= skb->len) { + if (bch->mode == L1_MODE_HDLC) { + /* increment f counter */ + Write_hfc8(l1->hw, A_INC_RES_FIFO, 1); + } + ack_len += skb->truesize; + bch->tx_skb = 0; + bch->tx_cnt = 0; + dev_kfree_skb(skb); + } else + /* Re-Select */ + Write_hfc8(l1->hw, R_FIFO, + (l1->st_num * 8 + + ((bch->bchan == 1) ? 0 : 2))); + wait_busy(l1->hw); + } while (1); + + if (ack_len) + bch->b_if.ifc.l1l2((struct hisax_if *) &bch->b_if, + PH_DATA | CONFIRM, (void *) ack_len); +} /* tx_b_frame */ + +/*************************************/ +/* bottom half handler for interrupt */ +/*************************************/ +static void +hfc4s8s_bh(hfc4s8s_hw * hw) +{ + u_char b; + struct hfc4s8s_l1 *l1p; + volatile u_char *fifo_stat; + int idx; + + /* handle layer 1 state changes */ + b = 1; + l1p = hw->l1; + while (b) { + if ((b & hw->mr.r_irq_statech)) { + /* reset l1 event */ + hw->mr.r_irq_statech &= ~b; + if (l1p->enabled) { + if (l1p->nt_mode) { + u_char oldstate = l1p->l1_state; + + Write_hfc8(l1p->hw, R_ST_SEL, + l1p->st_num); + l1p->l1_state = + Read_hfc8(l1p->hw, + A_ST_RD_STA) & 0xf; + + if ((oldstate == 3) + && (l1p->l1_state != 3)) + l1p->d_if.ifc.l1l2(&l1p-> + d_if. + ifc, + PH_DEACTIVATE + | + INDICATION, + NULL); + + if (l1p->l1_state != 2) { + del_timer(&l1p->l1_timer); + if (l1p->l1_state == 3) { + l1p->d_if.ifc. + l1l2(&l1p-> + d_if.ifc, + PH_ACTIVATE + | + INDICATION, + NULL); + } + } else { + /* allow transition */ + Write_hfc8(hw, A_ST_WR_STA, + M_SET_G2_G3); + mod_timer(&l1p->l1_timer, + jiffies + + L1_TIMER_T1); + } + printk(KERN_INFO + "HFC-4S/8S: NT ch %d l1 state %d -> %d\n", + l1p->st_num, oldstate, + l1p->l1_state); + } else { + u_char oldstate = l1p->l1_state; + + Write_hfc8(l1p->hw, R_ST_SEL, + l1p->st_num); + l1p->l1_state = + Read_hfc8(l1p->hw, + A_ST_RD_STA) & 0xf; + + if (((l1p->l1_state == 3) && + ((oldstate == 7) || + (oldstate == 8))) || + ((timer_pending + (&l1p->l1_timer)) + && (l1p->l1_state == 8))) { + mod_timer(&l1p->l1_timer, + L1_TIMER_T4 + + jiffies); + } else { + if (l1p->l1_state == 7) { + del_timer(&l1p-> + l1_timer); + l1p->d_if.ifc. + l1l2(&l1p-> + d_if.ifc, + PH_ACTIVATE + | + INDICATION, + NULL); + tx_d_frame(l1p); + } + if (l1p->l1_state == 3) { + if (oldstate != 3) + l1p->d_if. + ifc. + l1l2 + (&l1p-> + d_if. + ifc, + PH_DEACTIVATE + | + INDICATION, + NULL); + } + } + printk(KERN_INFO + "HFC-4S/8S: TE %d ch %d l1 state %d -> %d\n", + l1p->hw->cardnum, + l1p->st_num, oldstate, + l1p->l1_state); + } + } + } + b <<= 1; + l1p++; + } + + /* now handle the fifos */ + idx = 0; + fifo_stat = hw->mr.r_irq_fifo_blx; + l1p = hw->l1; + while (idx < hw->driver_data.max_st_ports) { + + if (hw->mr.timer_irq) { + *fifo_stat |= hw->mr.fifo_rx_trans_enables[idx]; + if (hw->fifo_sched_cnt <= 0) { + *fifo_stat |= + hw->mr.fifo_slow_timer_service[l1p-> + st_num]; + } + } + /* ignore fifo 6 (TX E fifo) */ + *fifo_stat &= 0xff - 0x40; + + while (*fifo_stat) { + + if (!l1p->nt_mode) { + /* RX Fifo has data to read */ + if ((*fifo_stat & 0x20)) { + *fifo_stat &= ~0x20; + rx_d_frame(l1p, 0); + } + /* E Fifo has data to read */ + if ((*fifo_stat & 0x80)) { + *fifo_stat &= ~0x80; + rx_d_frame(l1p, 1); + } + /* TX Fifo completed send */ + if ((*fifo_stat & 0x10)) { + *fifo_stat &= ~0x10; + tx_d_frame(l1p); + } + } + /* B1 RX Fifo has data to read */ + if ((*fifo_stat & 0x2)) { + *fifo_stat &= ~0x2; + rx_b_frame(l1p->b_ch); + } + /* B1 TX Fifo has send completed */ + if ((*fifo_stat & 0x1)) { + *fifo_stat &= ~0x1; + tx_b_frame(l1p->b_ch); + } + /* B2 RX Fifo has data to read */ + if ((*fifo_stat & 0x8)) { + *fifo_stat &= ~0x8; + rx_b_frame(l1p->b_ch + 1); + } + /* B2 TX Fifo has send completed */ + if ((*fifo_stat & 0x4)) { + *fifo_stat &= ~0x4; + tx_b_frame(l1p->b_ch + 1); + } + } + fifo_stat++; + l1p++; + idx++; + } + + if (hw->fifo_sched_cnt <= 0) + hw->fifo_sched_cnt += (1 << (7 - TRANS_TIMER_MODE)); + hw->mr.timer_irq = 0; /* clear requested timer irq */ +} /* hfc4s8s_bh */ + +/*********************/ +/* interrupt handler */ +/*********************/ +static irqreturn_t +hfc4s8s_interrupt(int intno, void *dev_id, struct pt_regs *regs) +{ + hfc4s8s_hw *hw = dev_id; + u_char b, ovr; + volatile u_char *ovp; + int idx; + u_char old_ioreg; + + if (!hw || !(hw->mr.r_irq_ctrl & M_GLOB_IRQ_EN)) + return IRQ_NONE; + +#ifndef CONFIG_HISAX_HFC4S8S_PCIMEM + /* read current selected regsister */ + old_ioreg = GetRegAddr(hw); +#endif + + /* Layer 1 State change */ + hw->mr.r_irq_statech |= + (Read_hfc8(hw, R_SCI) & hw->mr.r_irqmsk_statchg); + if (! + (b = (Read_hfc8(hw, R_STATUS) & (M_MISC_IRQSTA | M_FR_IRQSTA))) +&& !hw->mr.r_irq_statech) { +#ifndef CONFIG_HISAX_HFC4S8S_PCIMEM + SetRegAddr(hw, old_ioreg); +#endif + return IRQ_NONE; + } + + /* timer event */ + if (Read_hfc8(hw, R_IRQ_MISC) & M_TI_IRQ) { + hw->mr.timer_irq = 1; + hw->fifo_sched_cnt--; + } + + /* FIFO event */ + if ((ovr = Read_hfc8(hw, R_IRQ_OVIEW))) { + hw->mr.r_irq_oview |= ovr; + idx = R_IRQ_FIFO_BL0; + ovp = hw->mr.r_irq_fifo_blx; + while (ovr) { + if ((ovr & 1)) { + *ovp |= Read_hfc8(hw, idx); + } + ovp++; + idx++; + ovr >>= 1; + } + } + + /* queue the request to allow other cards to interrupt */ + schedule_work(&hw->tqueue); + +#ifndef CONFIG_HISAX_HFC4S8S_PCIMEM + SetRegAddr(hw, old_ioreg); +#endif + return IRQ_HANDLED; +} /* hfc4s8s_interrupt */ + +/***********************************************************************/ +/* reset the complete chip, don't release the chips irq but disable it */ +/***********************************************************************/ +static void +chipreset(hfc4s8s_hw * hw) +{ + u_long flags; + + spin_lock_irqsave(&hw->lock, flags); + Write_hfc8(hw, R_CTRL, 0); /* use internal RAM */ + Write_hfc8(hw, R_RAM_MISC, 0); /* 32k*8 RAM */ + Write_hfc8(hw, R_FIFO_MD, 0); /* fifo mode 386 byte/fifo simple mode */ + Write_hfc8(hw, R_CIRM, M_SRES); /* reset chip */ + hw->mr.r_irq_ctrl = 0; /* interrupt is inactive */ + spin_unlock_irqrestore(&hw->lock, flags); + + udelay(3); + Write_hfc8(hw, R_CIRM, 0); /* disable reset */ + wait_busy(hw); + + Write_hfc8(hw, R_PCM_MD0, M_PCM_MD); /* master mode */ + Write_hfc8(hw, R_RAM_MISC, M_FZ_MD); /* transmit fifo option */ + if (hw->driver_data.clock_mode == 1) + Write_hfc8(hw, R_BRG_PCM_CFG, M_PCM_CLK); /* PCM clk / 2 */ + Write_hfc8(hw, R_TI_WD, TRANS_TIMER_MODE); /* timer interval */ + + memset(&hw->mr, 0, sizeof(hw->mr)); +} /* chipreset */ + +/********************************************/ +/* disable/enable hardware in nt or te mode */ +/********************************************/ +void +hfc_hardware_enable(hfc4s8s_hw * hw, int enable, int nt_mode) +{ + u_long flags; + char if_name[40]; + int i; + + if (enable) { + /* save system vars */ + hw->nt_mode = nt_mode; + + /* enable fifo and state irqs, but not global irq enable */ + hw->mr.r_irq_ctrl = M_FIFO_IRQ; + Write_hfc8(hw, R_IRQ_CTRL, hw->mr.r_irq_ctrl); + hw->mr.r_irqmsk_statchg = 0; + Write_hfc8(hw, R_SCI_MSK, hw->mr.r_irqmsk_statchg); + Write_hfc8(hw, R_PWM_MD, 0x80); + Write_hfc8(hw, R_PWM1, 26); + if (!nt_mode) + Write_hfc8(hw, R_ST_SYNC, M_AUTO_SYNC); + + /* enable the line interfaces and fifos */ + for (i = 0; i < hw->driver_data.max_st_ports; i++) { + hw->mr.r_irqmsk_statchg |= (1 << i); + Write_hfc8(hw, R_SCI_MSK, hw->mr.r_irqmsk_statchg); + Write_hfc8(hw, R_ST_SEL, i); + Write_hfc8(hw, A_ST_CLK_DLY, + ((nt_mode) ? CLKDEL_NT : CLKDEL_TE)); + hw->mr.r_ctrl0 = ((nt_mode) ? CTRL0_NT : CTRL0_TE); + Write_hfc8(hw, A_ST_CTRL0, hw->mr.r_ctrl0); + Write_hfc8(hw, A_ST_CTRL2, 3); + Write_hfc8(hw, A_ST_WR_STA, 0); /* enable state machine */ + + hw->l1[i].enabled = 1; + hw->l1[i].nt_mode = nt_mode; + + if (!nt_mode) { + /* setup E-fifo */ + Write_hfc8(hw, R_FIFO, i * 8 + 7); /* E fifo */ + wait_busy(hw); + Write_hfc8(hw, A_CON_HDLC, 0x11); /* HDLC mode, 1 fill, connect ST */ + Write_hfc8(hw, A_SUBCH_CFG, 2); /* only 2 bits */ + Write_hfc8(hw, A_IRQ_MSK, 1); /* enable interrupt */ + Write_hfc8(hw, A_INC_RES_FIFO, 2); /* reset fifo */ + wait_busy(hw); + + /* setup D RX-fifo */ + Write_hfc8(hw, R_FIFO, i * 8 + 5); /* RX fifo */ + wait_busy(hw); + Write_hfc8(hw, A_CON_HDLC, 0x11); /* HDLC mode, 1 fill, connect ST */ + Write_hfc8(hw, A_SUBCH_CFG, 2); /* only 2 bits */ + Write_hfc8(hw, A_IRQ_MSK, 1); /* enable interrupt */ + Write_hfc8(hw, A_INC_RES_FIFO, 2); /* reset fifo */ + wait_busy(hw); + + /* setup D TX-fifo */ + Write_hfc8(hw, R_FIFO, i * 8 + 4); /* TX fifo */ + wait_busy(hw); + Write_hfc8(hw, A_CON_HDLC, 0x11); /* HDLC mode, 1 fill, connect ST */ + Write_hfc8(hw, A_SUBCH_CFG, 2); /* only 2 bits */ + Write_hfc8(hw, A_IRQ_MSK, 1); /* enable interrupt */ + Write_hfc8(hw, A_INC_RES_FIFO, 2); /* reset fifo */ + wait_busy(hw); + } + + sprintf(if_name, "hfc4s8s_%d%d_", hw->cardnum, i); + + if (hisax_register + (&hw->l1[i].d_if, hw->l1[i].b_table, if_name, + ((nt_mode) ? 3 : 2))) { + + hw->l1[i].enabled = 0; + hw->mr.r_irqmsk_statchg &= ~(1 << i); + Write_hfc8(hw, R_SCI_MSK, + hw->mr.r_irqmsk_statchg); + printk(KERN_INFO + "HFC-4S/8S: Unable to register S/T device %s, break\n", + if_name); + break; + } + } + spin_lock_irqsave(&hw->lock, flags); + hw->mr.r_irq_ctrl |= M_GLOB_IRQ_EN; + Write_hfc8(hw, R_IRQ_CTRL, hw->mr.r_irq_ctrl); + spin_unlock_irqrestore(&hw->lock, flags); + } else { + /* disable hardware */ + spin_lock_irqsave(&hw->lock, flags); + hw->mr.r_irq_ctrl &= ~M_GLOB_IRQ_EN; + Write_hfc8(hw, R_IRQ_CTRL, hw->mr.r_irq_ctrl); + spin_unlock_irqrestore(&hw->lock, flags); + + for (i = hw->driver_data.max_st_ports - 1; i >= 0; i--) { + hw->l1[i].enabled = 0; + hisax_unregister(&hw->l1[i].d_if); + del_timer(&hw->l1[i].l1_timer); + skb_queue_purge(&hw->l1[i].d_tx_queue); + skb_queue_purge(&hw->l1[i].b_ch[0].tx_queue); + skb_queue_purge(&hw->l1[i].b_ch[1].tx_queue); + } + chipreset(hw); + } +} /* hfc_hardware_enable */ + +/******************************************/ +/* disable memory mapped ports / io ports */ +/******************************************/ +void +release_pci_ports(hfc4s8s_hw * hw) +{ + pci_write_config_word(hw->pdev, PCI_COMMAND, 0); +#ifdef CONFIG_HISAX_HFC4S8S_PCIMEM + if (hw->membase) + iounmap((void *) hw->membase); +#else + if (hw->iobase) + release_region(hw->iobase, 8); +#endif +} + +/*****************************************/ +/* enable memory mapped ports / io ports */ +/*****************************************/ +void +enable_pci_ports(hfc4s8s_hw * hw) +{ +#ifdef CONFIG_HISAX_HFC4S8S_PCIMEM + pci_write_config_word(hw->pdev, PCI_COMMAND, PCI_ENA_MEMIO); +#else + pci_write_config_word(hw->pdev, PCI_COMMAND, PCI_ENA_REGIO); +#endif +} + +/*************************************/ +/* initialise the HFC-4s/8s hardware */ +/* return 0 on success. */ +/*************************************/ +static int __devinit +setup_instance(hfc4s8s_hw * hw) +{ + int err = -EIO; + int i; + + for (i = 0; i < HFC_MAX_ST; i++) { + struct hfc4s8s_l1 *l1p; + + l1p = hw->l1 + i; + spin_lock_init(&l1p->lock); + l1p->hw = hw; + l1p->l1_timer.function = (void *) hfc_l1_timer; + l1p->l1_timer.data = (long) (l1p); + init_timer(&l1p->l1_timer); + l1p->st_num = i; + skb_queue_head_init(&l1p->d_tx_queue); + l1p->d_if.ifc.priv = hw->l1 + i; + l1p->d_if.ifc.l2l1 = (void *) dch_l2l1; + + spin_lock_init(&l1p->b_ch[0].lock); + l1p->b_ch[0].b_if.ifc.l2l1 = (void *) bch_l2l1; + l1p->b_ch[0].b_if.ifc.priv = (void *) &l1p->b_ch[0]; + l1p->b_ch[0].l1p = hw->l1 + i; + l1p->b_ch[0].bchan = 1; + l1p->b_table[0] = &l1p->b_ch[0].b_if; + skb_queue_head_init(&l1p->b_ch[0].tx_queue); + + spin_lock_init(&l1p->b_ch[1].lock); + l1p->b_ch[1].b_if.ifc.l2l1 = (void *) bch_l2l1; + l1p->b_ch[1].b_if.ifc.priv = (void *) &l1p->b_ch[1]; + l1p->b_ch[1].l1p = hw->l1 + i; + l1p->b_ch[1].bchan = 2; + l1p->b_table[1] = &l1p->b_ch[1].b_if; + skb_queue_head_init(&l1p->b_ch[1].tx_queue); + } + + enable_pci_ports(hw); + chipreset(hw); + + i = Read_hfc8(hw, R_CHIP_ID) >> CHIP_ID_SHIFT; + if (i != hw->driver_data.chip_id) { + printk(KERN_INFO + "HFC-4S/8S: invalid chip id 0x%x instead of 0x%x, card ignored\n", + i, hw->driver_data.chip_id); + goto out; + } + + i = Read_hfc8(hw, R_CHIP_RV) & 0xf; + if (!i) { + printk(KERN_INFO + "HFC-4S/8S: chip revision 0 not supported, card ignored\n"); + goto out; + } + + INIT_WORK(&hw->tqueue, (void *) (void *) hfc4s8s_bh, hw); + + if (request_irq + (hw->irq, hfc4s8s_interrupt, SA_SHIRQ, hw->card_name, hw)) { + printk(KERN_INFO + "HFC-4S/8S: unable to alloc irq %d, card ignored\n", + hw->irq); + goto out; + } +#ifdef CONFIG_HISAX_HFC4S8S_PCIMEM + printk(KERN_INFO + "HFC-4S/8S: found PCI card at membase 0x%p, irq %d\n", + hw->hw_membase, hw->irq); +#else + printk(KERN_INFO + "HFC-4S/8S: found PCI card at iobase 0x%x, irq %d\n", + hw->iobase, hw->irq); +#endif + + hfc_hardware_enable(hw, 1, 0); + + return (0); + + out: + hw->irq = 0; + release_pci_ports(hw); + kfree(hw); + return (err); +} + +/*****************************************/ +/* PCI hotplug interface: probe new card */ +/*****************************************/ +static int __devinit +hfc4s8s_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + int err = -ENOMEM; + hfc4s8s_param *driver_data = (hfc4s8s_param *) ent->driver_data; + hfc4s8s_hw *hw; + + if (!(hw = kmalloc(sizeof(hfc4s8s_hw), GFP_ATOMIC))) { + printk(KERN_ERR "No kmem for HFC-4S/8S card\n"); + return (err); + } + memset(hw, 0, sizeof(hfc4s8s_hw)); + + hw->pdev = pdev; + err = pci_enable_device(pdev); + + if (err) + goto out; + + hw->cardnum = card_cnt; + sprintf(hw->card_name, "hfc4s8s_%d", hw->cardnum); + printk(KERN_INFO "HFC-4S/8S: found adapter %s (%s) at %s\n", + driver_data->device_name, hw->card_name, pci_name(pdev)); + + spin_lock_init(&hw->lock); + + hw->driver_data = *driver_data; + hw->irq = pdev->irq; + hw->iobase = pci_resource_start(pdev, 0); + +#ifdef CONFIG_HISAX_HFC4S8S_PCIMEM + hw->hw_membase = (u_char *) pci_resource_start(pdev, 1); + hw->membase = ioremap((ulong) hw->hw_membase, 256); +#else + if (!request_region(hw->iobase, 8, hw->card_name)) { + printk(KERN_INFO + "HFC-4S/8S: failed to rquest address space at 0x%04x\n", + hw->iobase); + goto out; + } +#endif + + pci_set_drvdata(pdev, hw); + err = setup_instance(hw); + if (!err) + card_cnt++; + return (err); + + out: + kfree(hw); + return (err); +} + +/**************************************/ +/* PCI hotplug interface: remove card */ +/**************************************/ +static void __devexit +hfc4s8s_remove(struct pci_dev *pdev) +{ + hfc4s8s_hw *hw = pci_get_drvdata(pdev); + + printk(KERN_INFO "HFC-4S/8S: removing card %d\n", hw->cardnum); + hfc_hardware_enable(hw, 0, 0); + + if (hw->irq) + free_irq(hw->irq, hw); + hw->irq = 0; + release_pci_ports(hw); + + card_cnt--; + pci_disable_device(pdev); + kfree(hw); + return; +} + +static struct pci_driver hfc4s8s_driver = { + name:"hfc4s8s_l1", + probe:hfc4s8s_probe, + remove:__devexit_p(hfc4s8s_remove), + id_table:hfc4s8s_ids, +}; + +/**********************/ +/* driver Module init */ +/**********************/ +static int __init +hfc4s8s_module_init(void) +{ + int err; + + printk(KERN_INFO + "HFC-4S/8S: Layer 1 driver module for HFC-4S/8S isdn chips, %s\n", + hfc4s8s_rev); + printk(KERN_INFO + "HFC-4S/8S: (C) 2003 Cornelius Consult, www.cornelius-consult.de\n"); + + card_cnt = 0; + + err = pci_register_driver(&hfc4s8s_driver); + if (err < 0) { + goto out; + } + printk(KERN_INFO "HFC-4S/8S: found %d cards\n", card_cnt); + +#if !defined(CONFIG_HOTPLUG) + if (err == 0) { + err = -ENODEV; + pci_unregister_driver(&hfc4s8s_driver); + goto out; + } +#endif + + return 0; + out: + return (err); +} /* hfc4s8s_init_hw */ + +/*************************************/ +/* driver module exit : */ +/* release the HFC-4s/8s hardware */ +/*************************************/ +static void +hfc4s8s_module_exit(void) +{ + pci_unregister_driver(&hfc4s8s_driver); + printk(KERN_INFO "HFC-4S/8S: module removed\n"); +} /* hfc4s8s_release_hw */ + +module_init(hfc4s8s_module_init); +module_exit(hfc4s8s_module_exit); diff --git a/drivers/isdn/hisax/hfc4s8s_l1.h b/drivers/isdn/hisax/hfc4s8s_l1.h new file mode 100644 index 000000000000..e8f9c077fa85 --- /dev/null +++ b/drivers/isdn/hisax/hfc4s8s_l1.h @@ -0,0 +1,88 @@ +/***************************************************************/ +/* $Id: hfc4s8s_l1.h,v 1.1 2005/02/02 17:28:55 martinb1 Exp $ */ +/* */ +/* This file is a minimal required extraction of hfc48scu.h */ +/* (Genero 3.2, HFC XML 1.7a for HFC-E1, HFC-4S and HFC-8S) */ +/* */ +/* To get this complete register description contact */ +/* Cologne Chip AG : */ +/* Internet: http://www.colognechip.com/ */ +/* E-Mail: info@colognechip.com */ +/***************************************************************/ + +#ifndef _HFC4S8S_L1_H_ +#define _HFC4S8S_L1_H_ + + +/* +* include Genero generated HFC-4S/8S header file hfc48scu.h +* for comlete register description. This will define _HFC48SCU_H_ +* to prevent redefinitions +*/ + +// #include "hfc48scu.h" + +#ifndef _HFC48SCU_H_ +#define _HFC48SCU_H_ + +#ifndef PCI_VENDOR_ID_CCD +#define PCI_VENDOR_ID_CCD 0x1397 +#endif + +#define CHIP_ID_4S 0x0C +#define CHIP_ID_8S 0x08 +#define PCI_DEVICE_ID_4S 0x08B4 +#define PCI_DEVICE_ID_8S 0x16B8 + +#define R_IRQ_MISC 0x11 +#define M_TI_IRQ 0x02 +#define A_ST_RD_STA 0x30 +#define A_ST_WR_STA 0x30 +#define M_SET_G2_G3 0x80 +#define A_ST_CTRL0 0x31 +#define A_ST_CTRL2 0x33 +#define A_ST_CLK_DLY 0x37 +#define A_Z1 0x04 +#define A_Z2 0x06 +#define R_CIRM 0x00 +#define M_SRES 0x08 +#define R_CTRL 0x01 +#define R_BRG_PCM_CFG 0x02 +#define M_PCM_CLK 0x20 +#define R_RAM_MISC 0x0C +#define M_FZ_MD 0x80 +#define R_FIFO_MD 0x0D +#define A_INC_RES_FIFO 0x0E +#define R_FIFO 0x0F +#define A_F1 0x0C +#define A_F2 0x0D +#define R_IRQ_OVIEW 0x10 +#define R_CHIP_ID 0x16 +#define R_STATUS 0x1C +#define M_BUSY 0x01 +#define M_MISC_IRQSTA 0x40 +#define M_FR_IRQSTA 0x80 +#define R_CHIP_RV 0x1F +#define R_IRQ_CTRL 0x13 +#define M_FIFO_IRQ 0x01 +#define M_GLOB_IRQ_EN 0x08 +#define R_PCM_MD0 0x14 +#define M_PCM_MD 0x01 +#define A_FIFO_DATA0 0x80 +#define R_TI_WD 0x1A +#define R_PWM1 0x39 +#define R_PWM_MD 0x46 +#define R_IRQ_FIFO_BL0 0xC8 +#define A_CON_HDLC 0xFA +#define A_SUBCH_CFG 0xFB +#define A_IRQ_MSK 0xFF +#define R_SCI_MSK 0x12 +#define R_ST_SEL 0x16 +#define R_ST_SYNC 0x17 +#define M_AUTO_SYNC 0x08 +#define R_SCI 0x12 +#define R_IRQMSK_MISC 0x11 +#define M_TI_IRQMSK 0x02 + +#endif /* _HFC4S8S_L1_H_ */ +#endif /* _HFC48SCU_H_ */ diff --git a/drivers/isdn/hisax/hfc_2bds0.c b/drivers/isdn/hisax/hfc_2bds0.c new file mode 100644 index 000000000000..ebea3feef003 --- /dev/null +++ b/drivers/isdn/hisax/hfc_2bds0.c @@ -0,0 +1,1082 @@ +/* $Id: hfc_2bds0.c,v 1.18.2.6 2004/02/11 13:21:33 keil Exp $ + * + * specific routines for CCD's HFC 2BDS0 + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/init.h> +#include "hisax.h" +#include "hfc_2bds0.h" +#include "isdnl1.h" +#include <linux/interrupt.h> +/* +#define KDEBUG_DEF +#include "kdebug.h" +*/ + +#define byteout(addr,val) outb(val,addr) +#define bytein(addr) inb(addr) + +static void +dummyf(struct IsdnCardState *cs, u_char * data, int size) +{ + printk(KERN_WARNING "HiSax: hfcd dummy fifo called\n"); +} + +static inline u_char +ReadReg(struct IsdnCardState *cs, int data, u_char reg) +{ + register u_char ret; + + if (data) { + if (cs->hw.hfcD.cip != reg) { + cs->hw.hfcD.cip = reg; + byteout(cs->hw.hfcD.addr | 1, reg); + } + ret = bytein(cs->hw.hfcD.addr); +#ifdef HFC_REG_DEBUG + if (cs->debug & L1_DEB_HSCX_FIFO && (data != 2)) + debugl1(cs, "t3c RD %02x %02x", reg, ret); +#endif + } else + ret = bytein(cs->hw.hfcD.addr | 1); + return (ret); +} + +static inline void +WriteReg(struct IsdnCardState *cs, int data, u_char reg, u_char value) +{ + if (cs->hw.hfcD.cip != reg) { + cs->hw.hfcD.cip = reg; + byteout(cs->hw.hfcD.addr | 1, reg); + } + if (data) + byteout(cs->hw.hfcD.addr, value); +#ifdef HFC_REG_DEBUG + if (cs->debug & L1_DEB_HSCX_FIFO && (data != HFCD_DATA_NODEB)) + debugl1(cs, "t3c W%c %02x %02x", data ? 'D' : 'C', reg, value); +#endif +} + +/* Interface functions */ + +static u_char +readreghfcd(struct IsdnCardState *cs, u_char offset) +{ + return(ReadReg(cs, HFCD_DATA, offset)); +} + +static void +writereghfcd(struct IsdnCardState *cs, u_char offset, u_char value) +{ + WriteReg(cs, HFCD_DATA, offset, value); +} + +static inline int +WaitForBusy(struct IsdnCardState *cs) +{ + int to = 130; + + while (!(ReadReg(cs, HFCD_DATA, HFCD_STAT) & HFCD_BUSY) && to) { + udelay(1); + to--; + } + if (!to) + printk(KERN_WARNING "HiSax: WaitForBusy timeout\n"); + return (to); +} + +static inline int +WaitNoBusy(struct IsdnCardState *cs) +{ + int to = 130; + + while ((ReadReg(cs, HFCD_STATUS, HFCD_STATUS) & HFCD_BUSY) && to) { + udelay(1); + to--; + } + if (!to) + printk(KERN_WARNING "HiSax: WaitNoBusy timeout\n"); + return (to); +} + +static int +SelFiFo(struct IsdnCardState *cs, u_char FiFo) +{ + u_char cip; + + if (cs->hw.hfcD.fifo == FiFo) + return(1); + switch(FiFo) { + case 0: cip = HFCB_FIFO | HFCB_Z1 | HFCB_SEND | HFCB_B1; + break; + case 1: cip = HFCB_FIFO | HFCB_Z1 | HFCB_REC | HFCB_B1; + break; + case 2: cip = HFCB_FIFO | HFCB_Z1 | HFCB_SEND | HFCB_B2; + break; + case 3: cip = HFCB_FIFO | HFCB_Z1 | HFCB_REC | HFCB_B2; + break; + case 4: cip = HFCD_FIFO | HFCD_Z1 | HFCD_SEND; + break; + case 5: cip = HFCD_FIFO | HFCD_Z1 | HFCD_REC; + break; + default: + debugl1(cs, "SelFiFo Error"); + return(0); + } + cs->hw.hfcD.fifo = FiFo; + WaitNoBusy(cs); + cs->BC_Write_Reg(cs, HFCD_DATA, cip, 0); + WaitForBusy(cs); + return(2); +} + +static int +GetFreeFifoBytes_B(struct BCState *bcs) +{ + int s; + + if (bcs->hw.hfc.f1 == bcs->hw.hfc.f2) + return (bcs->cs->hw.hfcD.bfifosize); + s = bcs->hw.hfc.send[bcs->hw.hfc.f1] - bcs->hw.hfc.send[bcs->hw.hfc.f2]; + if (s <= 0) + s += bcs->cs->hw.hfcD.bfifosize; + s = bcs->cs->hw.hfcD.bfifosize - s; + return (s); +} + +static int +GetFreeFifoBytes_D(struct IsdnCardState *cs) +{ + int s; + + if (cs->hw.hfcD.f1 == cs->hw.hfcD.f2) + return (cs->hw.hfcD.dfifosize); + s = cs->hw.hfcD.send[cs->hw.hfcD.f1] - cs->hw.hfcD.send[cs->hw.hfcD.f2]; + if (s <= 0) + s += cs->hw.hfcD.dfifosize; + s = cs->hw.hfcD.dfifosize - s; + return (s); +} + +static int +ReadZReg(struct IsdnCardState *cs, u_char reg) +{ + int val; + + WaitNoBusy(cs); + val = 256 * ReadReg(cs, HFCD_DATA, reg | HFCB_Z_HIGH); + WaitNoBusy(cs); + val += ReadReg(cs, HFCD_DATA, reg | HFCB_Z_LOW); + return (val); +} + +static struct sk_buff +*hfc_empty_fifo(struct BCState *bcs, int count) +{ + u_char *ptr; + struct sk_buff *skb; + struct IsdnCardState *cs = bcs->cs; + int idx; + int chksum; + u_char stat, cip; + + if ((cs->debug & L1_DEB_HSCX) && !(cs->debug & L1_DEB_HSCX_FIFO)) + debugl1(cs, "hfc_empty_fifo"); + idx = 0; + if (count > HSCX_BUFMAX + 3) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "hfc_empty_fifo: incoming packet too large"); + cip = HFCB_FIFO | HFCB_FIFO_OUT | HFCB_REC | HFCB_CHANNEL(bcs->channel); + while (idx++ < count) { + WaitNoBusy(cs); + ReadReg(cs, HFCD_DATA_NODEB, cip); + } + skb = NULL; + } else if (count < 4) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "hfc_empty_fifo: incoming packet too small"); + cip = HFCB_FIFO | HFCB_FIFO_OUT | HFCB_REC | HFCB_CHANNEL(bcs->channel); +#ifdef ERROR_STATISTIC + bcs->err_inv++; +#endif + while ((idx++ < count) && WaitNoBusy(cs)) + ReadReg(cs, HFCD_DATA_NODEB, cip); + skb = NULL; + } else if (!(skb = dev_alloc_skb(count - 3))) + printk(KERN_WARNING "HFC: receive out of memory\n"); + else { + ptr = skb_put(skb, count - 3); + idx = 0; + cip = HFCB_FIFO | HFCB_FIFO_OUT | HFCB_REC | HFCB_CHANNEL(bcs->channel); + while (idx < (count - 3)) { + if (!WaitNoBusy(cs)) + break; + *ptr = ReadReg(cs, HFCD_DATA_NODEB, cip); + ptr++; + idx++; + } + if (idx != count - 3) { + debugl1(cs, "RFIFO BUSY error"); + printk(KERN_WARNING "HFC FIFO channel %d BUSY Error\n", bcs->channel); + dev_kfree_skb_irq(skb); + skb = NULL; + } else { + WaitNoBusy(cs); + chksum = (ReadReg(cs, HFCD_DATA, cip) << 8); + WaitNoBusy(cs); + chksum += ReadReg(cs, HFCD_DATA, cip); + WaitNoBusy(cs); + stat = ReadReg(cs, HFCD_DATA, cip); + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfc_empty_fifo %d chksum %x stat %x", + bcs->channel, chksum, stat); + if (stat) { + debugl1(cs, "FIFO CRC error"); + dev_kfree_skb_irq(skb); + skb = NULL; +#ifdef ERROR_STATISTIC + bcs->err_crc++; +#endif + } + } + } + WaitForBusy(cs); + WaitNoBusy(cs); + stat = ReadReg(cs, HFCD_DATA, HFCB_FIFO | HFCB_F2_INC | + HFCB_REC | HFCB_CHANNEL(bcs->channel)); + WaitForBusy(cs); + return (skb); +} + +static void +hfc_fill_fifo(struct BCState *bcs) +{ + struct IsdnCardState *cs = bcs->cs; + int idx, fcnt; + int count; + u_char cip; + + if (!bcs->tx_skb) + return; + if (bcs->tx_skb->len <= 0) + return; + SelFiFo(cs, HFCB_SEND | HFCB_CHANNEL(bcs->channel)); + cip = HFCB_FIFO | HFCB_F1 | HFCB_SEND | HFCB_CHANNEL(bcs->channel); + WaitNoBusy(cs); + bcs->hw.hfc.f1 = ReadReg(cs, HFCD_DATA, cip); + WaitNoBusy(cs); + cip = HFCB_FIFO | HFCB_F2 | HFCB_SEND | HFCB_CHANNEL(bcs->channel); + WaitNoBusy(cs); + bcs->hw.hfc.f2 = ReadReg(cs, HFCD_DATA, cip); + bcs->hw.hfc.send[bcs->hw.hfc.f1] = ReadZReg(cs, HFCB_FIFO | HFCB_Z1 | HFCB_SEND | HFCB_CHANNEL(bcs->channel)); + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfc_fill_fifo %d f1(%d) f2(%d) z1(%x)", + bcs->channel, bcs->hw.hfc.f1, bcs->hw.hfc.f2, + bcs->hw.hfc.send[bcs->hw.hfc.f1]); + fcnt = bcs->hw.hfc.f1 - bcs->hw.hfc.f2; + if (fcnt < 0) + fcnt += 32; + if (fcnt > 30) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfc_fill_fifo more as 30 frames"); + return; + } + count = GetFreeFifoBytes_B(bcs); + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfc_fill_fifo %d count(%ld/%d),%lx", + bcs->channel, bcs->tx_skb->len, + count, current->state); + if (count < bcs->tx_skb->len) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfc_fill_fifo no fifo mem"); + return; + } + cip = HFCB_FIFO | HFCB_FIFO_IN | HFCB_SEND | HFCB_CHANNEL(bcs->channel); + idx = 0; + WaitForBusy(cs); + WaitNoBusy(cs); + WriteReg(cs, HFCD_DATA_NODEB, cip, bcs->tx_skb->data[idx++]); + while (idx < bcs->tx_skb->len) { + if (!WaitNoBusy(cs)) + break; + WriteReg(cs, HFCD_DATA_NODEB, cip, bcs->tx_skb->data[idx]); + idx++; + } + if (idx != bcs->tx_skb->len) { + debugl1(cs, "FIFO Send BUSY error"); + printk(KERN_WARNING "HFC S FIFO channel %d BUSY Error\n", bcs->channel); + } else { + bcs->tx_cnt -= bcs->tx_skb->len; + if (test_bit(FLG_LLI_L1WAKEUP,&bcs->st->lli.flag) && + (PACKET_NOACK != bcs->tx_skb->pkt_type)) { + u_long flags; + spin_lock_irqsave(&bcs->aclock, flags); + bcs->ackcnt += bcs->tx_skb->len; + spin_unlock_irqrestore(&bcs->aclock, flags); + schedule_event(bcs, B_ACKPENDING); + } + dev_kfree_skb_any(bcs->tx_skb); + bcs->tx_skb = NULL; + } + WaitForBusy(cs); + WaitNoBusy(cs); + ReadReg(cs, HFCD_DATA, HFCB_FIFO | HFCB_F1_INC | HFCB_SEND | HFCB_CHANNEL(bcs->channel)); + WaitForBusy(cs); + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + return; +} + +static void +hfc_send_data(struct BCState *bcs) +{ + struct IsdnCardState *cs = bcs->cs; + + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfc_fill_fifo(bcs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs,"send_data %d blocked", bcs->channel); +} + +void +main_rec_2bds0(struct BCState *bcs) +{ + struct IsdnCardState *cs = bcs->cs; + int z1, z2, rcnt; + u_char f1, f2, cip; + int receive, count = 5; + struct sk_buff *skb; + + Begin: + count--; + if (test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + debugl1(cs,"rec_data %d blocked", bcs->channel); + return; + } + SelFiFo(cs, HFCB_REC | HFCB_CHANNEL(bcs->channel)); + cip = HFCB_FIFO | HFCB_F1 | HFCB_REC | HFCB_CHANNEL(bcs->channel); + WaitNoBusy(cs); + f1 = ReadReg(cs, HFCD_DATA, cip); + cip = HFCB_FIFO | HFCB_F2 | HFCB_REC | HFCB_CHANNEL(bcs->channel); + WaitNoBusy(cs); + f2 = ReadReg(cs, HFCD_DATA, cip); + if (f1 != f2) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfc rec %d f1(%d) f2(%d)", + bcs->channel, f1, f2); + z1 = ReadZReg(cs, HFCB_FIFO | HFCB_Z1 | HFCB_REC | HFCB_CHANNEL(bcs->channel)); + z2 = ReadZReg(cs, HFCB_FIFO | HFCB_Z2 | HFCB_REC | HFCB_CHANNEL(bcs->channel)); + rcnt = z1 - z2; + if (rcnt < 0) + rcnt += cs->hw.hfcD.bfifosize; + rcnt++; + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfc rec %d z1(%x) z2(%x) cnt(%d)", + bcs->channel, z1, z2, rcnt); + if ((skb = hfc_empty_fifo(bcs, rcnt))) { + skb_queue_tail(&bcs->rqueue, skb); + schedule_event(bcs, B_RCVBUFREADY); + } + rcnt = f1 -f2; + if (rcnt<0) + rcnt += 32; + if (rcnt>1) + receive = 1; + else + receive = 0; + } else + receive = 0; + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + if (count && receive) + goto Begin; + return; +} + +void +mode_2bs0(struct BCState *bcs, int mode, int bc) +{ + struct IsdnCardState *cs = bcs->cs; + + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HFCD bchannel mode %d bchan %d/%d", + mode, bc, bcs->channel); + bcs->mode = mode; + bcs->channel = bc; + switch (mode) { + case (L1_MODE_NULL): + if (bc) { + cs->hw.hfcD.conn |= 0x18; + cs->hw.hfcD.sctrl &= ~SCTRL_B2_ENA; + } else { + cs->hw.hfcD.conn |= 0x3; + cs->hw.hfcD.sctrl &= ~SCTRL_B1_ENA; + } + break; + case (L1_MODE_TRANS): + if (bc) { + cs->hw.hfcD.ctmt |= 2; + cs->hw.hfcD.conn &= ~0x18; + cs->hw.hfcD.sctrl |= SCTRL_B2_ENA; + } else { + cs->hw.hfcD.ctmt |= 1; + cs->hw.hfcD.conn &= ~0x3; + cs->hw.hfcD.sctrl |= SCTRL_B1_ENA; + } + break; + case (L1_MODE_HDLC): + if (bc) { + cs->hw.hfcD.ctmt &= ~2; + cs->hw.hfcD.conn &= ~0x18; + cs->hw.hfcD.sctrl |= SCTRL_B2_ENA; + } else { + cs->hw.hfcD.ctmt &= ~1; + cs->hw.hfcD.conn &= ~0x3; + cs->hw.hfcD.sctrl |= SCTRL_B1_ENA; + } + break; + } + WriteReg(cs, HFCD_DATA, HFCD_SCTRL, cs->hw.hfcD.sctrl); + WriteReg(cs, HFCD_DATA, HFCD_CTMT, cs->hw.hfcD.ctmt); + WriteReg(cs, HFCD_DATA, HFCD_CONN, cs->hw.hfcD.conn); +} + +static void +hfc_l2l1(struct PStack *st, int pr, void *arg) +{ + struct BCState *bcs = st->l1.bcs; + struct sk_buff *skb = arg; + u_long flags; + + switch (pr) { + case (PH_DATA | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + skb_queue_tail(&bcs->squeue, skb); + } else { + bcs->tx_skb = skb; +// test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->cs->BC_Send_Data(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | INDICATION): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + printk(KERN_WARNING "hfc_l2l1: this shouldn't happen\n"); + } else { +// test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->tx_skb = skb; + bcs->cs->BC_Send_Data(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | REQUEST): + if (!bcs->tx_skb) { + test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } else + test_and_set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + case (PH_ACTIVATE | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + test_and_set_bit(BC_FLG_ACTIV, &bcs->Flag); + mode_2bs0(bcs, st->l1.mode, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | REQUEST): + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | CONFIRM): + spin_lock_irqsave(&bcs->cs->lock, flags); + test_and_clear_bit(BC_FLG_ACTIV, &bcs->Flag); + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + mode_2bs0(bcs, 0, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + st->l1.l1l2(st, PH_DEACTIVATE | CONFIRM, NULL); + break; + } +} + +void +close_2bs0(struct BCState *bcs) +{ + mode_2bs0(bcs, 0, bcs->channel); + if (test_and_clear_bit(BC_FLG_INIT, &bcs->Flag)) { + skb_queue_purge(&bcs->rqueue); + skb_queue_purge(&bcs->squeue); + if (bcs->tx_skb) { + dev_kfree_skb_any(bcs->tx_skb); + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + } + } +} + +static int +open_hfcstate(struct IsdnCardState *cs, struct BCState *bcs) +{ + if (!test_and_set_bit(BC_FLG_INIT, &bcs->Flag)) { + skb_queue_head_init(&bcs->rqueue); + skb_queue_head_init(&bcs->squeue); + } + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->event = 0; + bcs->tx_cnt = 0; + return (0); +} + +int +setstack_2b(struct PStack *st, struct BCState *bcs) +{ + bcs->channel = st->l1.bc; + if (open_hfcstate(st->l1.hardware, bcs)) + return (-1); + st->l1.bcs = bcs; + st->l2.l2l1 = hfc_l2l1; + setstack_manager(st); + bcs->st = st; + setstack_l1_B(st); + return (0); +} + +static void +hfcd_bh(struct IsdnCardState *cs) +{ + if (!cs) + return; + if (test_and_clear_bit(D_L1STATECHANGE, &cs->event)) { + switch (cs->dc.hfcd.ph_state) { + case (0): + l1_msg(cs, HW_RESET | INDICATION, NULL); + break; + case (3): + l1_msg(cs, HW_DEACTIVATE | INDICATION, NULL); + break; + case (8): + l1_msg(cs, HW_RSYNC | INDICATION, NULL); + break; + case (6): + l1_msg(cs, HW_INFO2 | INDICATION, NULL); + break; + case (7): + l1_msg(cs, HW_INFO4_P8 | INDICATION, NULL); + break; + default: + break; + } + } + if (test_and_clear_bit(D_RCVBUFREADY, &cs->event)) + DChannel_proc_rcv(cs); + if (test_and_clear_bit(D_XMTBUFREADY, &cs->event)) + DChannel_proc_xmt(cs); +} + +static +int receive_dmsg(struct IsdnCardState *cs) +{ + struct sk_buff *skb; + int idx; + int rcnt, z1, z2; + u_char stat, cip, f1, f2; + int chksum; + int count=5; + u_char *ptr; + + if (test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + debugl1(cs, "rec_dmsg blocked"); + return(1); + } + SelFiFo(cs, 4 | HFCD_REC); + cip = HFCD_FIFO | HFCD_F1 | HFCD_REC; + WaitNoBusy(cs); + f1 = cs->readisac(cs, cip) & 0xf; + cip = HFCD_FIFO | HFCD_F2 | HFCD_REC; + WaitNoBusy(cs); + f2 = cs->readisac(cs, cip) & 0xf; + while ((f1 != f2) && count--) { + z1 = ReadZReg(cs, HFCD_FIFO | HFCD_Z1 | HFCD_REC); + z2 = ReadZReg(cs, HFCD_FIFO | HFCD_Z2 | HFCD_REC); + rcnt = z1 - z2; + if (rcnt < 0) + rcnt += cs->hw.hfcD.dfifosize; + rcnt++; + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "hfcd recd f1(%d) f2(%d) z1(%x) z2(%x) cnt(%d)", + f1, f2, z1, z2, rcnt); + idx = 0; + cip = HFCD_FIFO | HFCD_FIFO_OUT | HFCD_REC; + if (rcnt > MAX_DFRAME_LEN + 3) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "empty_fifo d: incoming packet too large"); + while (idx < rcnt) { + if (!(WaitNoBusy(cs))) + break; + ReadReg(cs, HFCD_DATA_NODEB, cip); + idx++; + } + } else if (rcnt < 4) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "empty_fifo d: incoming packet too small"); + while ((idx++ < rcnt) && WaitNoBusy(cs)) + ReadReg(cs, HFCD_DATA_NODEB, cip); + } else if ((skb = dev_alloc_skb(rcnt - 3))) { + ptr = skb_put(skb, rcnt - 3); + while (idx < (rcnt - 3)) { + if (!(WaitNoBusy(cs))) + break; + *ptr = ReadReg(cs, HFCD_DATA_NODEB, cip); + idx++; + ptr++; + } + if (idx != (rcnt - 3)) { + debugl1(cs, "RFIFO D BUSY error"); + printk(KERN_WARNING "HFC DFIFO channel BUSY Error\n"); + dev_kfree_skb_irq(skb); + skb = NULL; +#ifdef ERROR_STATISTIC + cs->err_rx++; +#endif + } else { + WaitNoBusy(cs); + chksum = (ReadReg(cs, HFCD_DATA, cip) << 8); + WaitNoBusy(cs); + chksum += ReadReg(cs, HFCD_DATA, cip); + WaitNoBusy(cs); + stat = ReadReg(cs, HFCD_DATA, cip); + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "empty_dfifo chksum %x stat %x", + chksum, stat); + if (stat) { + debugl1(cs, "FIFO CRC error"); + dev_kfree_skb_irq(skb); + skb = NULL; +#ifdef ERROR_STATISTIC + cs->err_crc++; +#endif + } else { + skb_queue_tail(&cs->rq, skb); + schedule_event(cs, D_RCVBUFREADY); + } + } + } else + printk(KERN_WARNING "HFC: D receive out of memory\n"); + WaitForBusy(cs); + cip = HFCD_FIFO | HFCD_F2_INC | HFCD_REC; + WaitNoBusy(cs); + stat = ReadReg(cs, HFCD_DATA, cip); + WaitForBusy(cs); + cip = HFCD_FIFO | HFCD_F2 | HFCD_REC; + WaitNoBusy(cs); + f2 = cs->readisac(cs, cip) & 0xf; + } + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + return(1); +} + +static void +hfc_fill_dfifo(struct IsdnCardState *cs) +{ + int idx, fcnt; + int count; + u_char cip; + + if (!cs->tx_skb) + return; + if (cs->tx_skb->len <= 0) + return; + + SelFiFo(cs, 4 | HFCD_SEND); + cip = HFCD_FIFO | HFCD_F1 | HFCD_SEND; + WaitNoBusy(cs); + cs->hw.hfcD.f1 = ReadReg(cs, HFCD_DATA, cip) & 0xf; + WaitNoBusy(cs); + cip = HFCD_FIFO | HFCD_F2 | HFCD_SEND; + cs->hw.hfcD.f2 = ReadReg(cs, HFCD_DATA, cip) & 0xf; + cs->hw.hfcD.send[cs->hw.hfcD.f1] = ReadZReg(cs, HFCD_FIFO | HFCD_Z1 | HFCD_SEND); + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "hfc_fill_Dfifo f1(%d) f2(%d) z1(%x)", + cs->hw.hfcD.f1, cs->hw.hfcD.f2, + cs->hw.hfcD.send[cs->hw.hfcD.f1]); + fcnt = cs->hw.hfcD.f1 - cs->hw.hfcD.f2; + if (fcnt < 0) + fcnt += 16; + if (fcnt > 14) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfc_fill_Dfifo more as 14 frames"); + return; + } + count = GetFreeFifoBytes_D(cs); + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "hfc_fill_Dfifo count(%ld/%d)", + cs->tx_skb->len, count); + if (count < cs->tx_skb->len) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "hfc_fill_Dfifo no fifo mem"); + return; + } + cip = HFCD_FIFO | HFCD_FIFO_IN | HFCD_SEND; + idx = 0; + WaitForBusy(cs); + WaitNoBusy(cs); + WriteReg(cs, HFCD_DATA_NODEB, cip, cs->tx_skb->data[idx++]); + while (idx < cs->tx_skb->len) { + if (!(WaitNoBusy(cs))) + break; + WriteReg(cs, HFCD_DATA_NODEB, cip, cs->tx_skb->data[idx]); + idx++; + } + if (idx != cs->tx_skb->len) { + debugl1(cs, "DFIFO Send BUSY error"); + printk(KERN_WARNING "HFC S DFIFO channel BUSY Error\n"); + } + WaitForBusy(cs); + WaitNoBusy(cs); + ReadReg(cs, HFCD_DATA, HFCD_FIFO | HFCD_F1_INC | HFCD_SEND); + dev_kfree_skb_any(cs->tx_skb); + cs->tx_skb = NULL; + WaitForBusy(cs); + return; +} + +static +struct BCState *Sel_BCS(struct IsdnCardState *cs, int channel) +{ + if (cs->bcs[0].mode && (cs->bcs[0].channel == channel)) + return(&cs->bcs[0]); + else if (cs->bcs[1].mode && (cs->bcs[1].channel == channel)) + return(&cs->bcs[1]); + else + return(NULL); +} + +void +hfc2bds0_interrupt(struct IsdnCardState *cs, u_char val) +{ + u_char exval; + struct BCState *bcs; + int count=15; + + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "HFCD irq %x %s", val, + test_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags) ? + "locked" : "unlocked"); + val &= cs->hw.hfcD.int_m1; + if (val & 0x40) { /* TE state machine irq */ + exval = cs->readisac(cs, HFCD_STATES) & 0xf; + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ph_state chg %d->%d", cs->dc.hfcd.ph_state, + exval); + cs->dc.hfcd.ph_state = exval; + schedule_event(cs, D_L1STATECHANGE); + val &= ~0x40; + } + while (val) { + if (test_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + cs->hw.hfcD.int_s1 |= val; + return; + } + if (cs->hw.hfcD.int_s1 & 0x18) { + exval = val; + val = cs->hw.hfcD.int_s1; + cs->hw.hfcD.int_s1 = exval; + } + if (val & 0x08) { + if (!(bcs=Sel_BCS(cs, 0))) { + if (cs->debug) + debugl1(cs, "hfcd spurious 0x08 IRQ"); + } else + main_rec_2bds0(bcs); + } + if (val & 0x10) { + if (!(bcs=Sel_BCS(cs, 1))) { + if (cs->debug) + debugl1(cs, "hfcd spurious 0x10 IRQ"); + } else + main_rec_2bds0(bcs); + } + if (val & 0x01) { + if (!(bcs=Sel_BCS(cs, 0))) { + if (cs->debug) + debugl1(cs, "hfcd spurious 0x01 IRQ"); + } else { + if (bcs->tx_skb) { + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfc_fill_fifo(bcs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs,"fill_data %d blocked", bcs->channel); + } else { + if ((bcs->tx_skb = skb_dequeue(&bcs->squeue))) { + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfc_fill_fifo(bcs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs,"fill_data %d blocked", bcs->channel); + } else { + schedule_event(bcs, B_XMTBUFREADY); + } + } + } + } + if (val & 0x02) { + if (!(bcs=Sel_BCS(cs, 1))) { + if (cs->debug) + debugl1(cs, "hfcd spurious 0x02 IRQ"); + } else { + if (bcs->tx_skb) { + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfc_fill_fifo(bcs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs,"fill_data %d blocked", bcs->channel); + } else { + if ((bcs->tx_skb = skb_dequeue(&bcs->squeue))) { + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfc_fill_fifo(bcs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs,"fill_data %d blocked", bcs->channel); + } else { + schedule_event(bcs, B_XMTBUFREADY); + } + } + } + } + if (val & 0x20) { /* receive dframe */ + receive_dmsg(cs); + } + if (val & 0x04) { /* dframe transmitted */ + if (test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) + del_timer(&cs->dbusytimer); + if (test_and_clear_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + schedule_event(cs, D_CLEARBUSY); + if (cs->tx_skb) { + if (cs->tx_skb->len) { + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfc_fill_dfifo(cs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else { + debugl1(cs, "hfc_fill_dfifo irq blocked"); + } + goto afterXPR; + } else { + dev_kfree_skb_irq(cs->tx_skb); + cs->tx_cnt = 0; + cs->tx_skb = NULL; + } + } + if ((cs->tx_skb = skb_dequeue(&cs->sq))) { + cs->tx_cnt = 0; + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfc_fill_dfifo(cs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else { + debugl1(cs, "hfc_fill_dfifo irq blocked"); + } + } else + schedule_event(cs, D_XMTBUFREADY); + } + afterXPR: + if (cs->hw.hfcD.int_s1 && count--) { + val = cs->hw.hfcD.int_s1; + cs->hw.hfcD.int_s1 = 0; + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "HFCD irq %x loop %d", val, 15-count); + } else + val = 0; + } +} + +static void +HFCD_l1hw(struct PStack *st, int pr, void *arg) +{ + struct IsdnCardState *cs = (struct IsdnCardState *) st->l1.hardware; + struct sk_buff *skb = arg; + u_long flags; + + switch (pr) { + case (PH_DATA | REQUEST): + if (cs->debug & DEB_DLOG_HEX) + LogFrame(cs, skb->data, skb->len); + if (cs->debug & DEB_DLOG_VERBOSE) + dlogframe(cs, skb, 0); + spin_lock_irqsave(&cs->lock, flags); + if (cs->tx_skb) { + skb_queue_tail(&cs->sq, skb); +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "PH_DATA Queued", 0); +#endif + } else { + cs->tx_skb = skb; + cs->tx_cnt = 0; +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "PH_DATA", 0); +#endif + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfc_fill_dfifo(cs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs, "hfc_fill_dfifo blocked"); + + } + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (PH_PULL | INDICATION): + spin_lock_irqsave(&cs->lock, flags); + if (cs->tx_skb) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, " l2l1 tx_skb exist this shouldn't happen"); + skb_queue_tail(&cs->sq, skb); + spin_unlock_irqrestore(&cs->lock, flags); + break; + } + if (cs->debug & DEB_DLOG_HEX) + LogFrame(cs, skb->data, skb->len); + if (cs->debug & DEB_DLOG_VERBOSE) + dlogframe(cs, skb, 0); + cs->tx_skb = skb; + cs->tx_cnt = 0; +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "PH_DATA_PULLED", 0); +#endif + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfc_fill_dfifo(cs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs, "hfc_fill_dfifo blocked"); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (PH_PULL | REQUEST): +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + debugl1(cs, "-> PH_REQUEST_PULL"); +#endif + if (!cs->tx_skb) { + test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } else + test_and_set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + case (HW_RESET | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + cs->writeisac(cs, HFCD_STATES, HFCD_LOAD_STATE | 3); /* HFC ST 3 */ + udelay(6); + cs->writeisac(cs, HFCD_STATES, 3); /* HFC ST 2 */ + cs->hw.hfcD.mst_m |= HFCD_MASTER; + cs->writeisac(cs, HFCD_MST_MODE, cs->hw.hfcD.mst_m); + cs->writeisac(cs, HFCD_STATES, HFCD_ACTIVATE | HFCD_DO_ACTION); + spin_unlock_irqrestore(&cs->lock, flags); + l1_msg(cs, HW_POWERUP | CONFIRM, NULL); + break; + case (HW_ENABLE | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + cs->writeisac(cs, HFCD_STATES, HFCD_ACTIVATE | HFCD_DO_ACTION); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (HW_DEACTIVATE | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + cs->hw.hfcD.mst_m &= ~HFCD_MASTER; + cs->writeisac(cs, HFCD_MST_MODE, cs->hw.hfcD.mst_m); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (HW_INFO3 | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + cs->hw.hfcD.mst_m |= HFCD_MASTER; + cs->writeisac(cs, HFCD_MST_MODE, cs->hw.hfcD.mst_m); + spin_unlock_irqrestore(&cs->lock, flags); + break; + default: + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "hfcd_l1hw unknown pr %4x", pr); + break; + } +} + +void +setstack_hfcd(struct PStack *st, struct IsdnCardState *cs) +{ + st->l1.l1hw = HFCD_l1hw; +} + +static void +hfc_dbusy_timer(struct IsdnCardState *cs) +{ +} + +unsigned int __init +*init_send_hfcd(int cnt) +{ + int i, *send; + + if (!(send = kmalloc(cnt * sizeof(unsigned int), GFP_ATOMIC))) { + printk(KERN_WARNING + "HiSax: No memory for hfcd.send\n"); + return(NULL); + } + for (i = 0; i < cnt; i++) + send[i] = 0x1fff; + return(send); +} + +void __init +init2bds0(struct IsdnCardState *cs) +{ + cs->setstack_d = setstack_hfcd; + if (!cs->hw.hfcD.send) + cs->hw.hfcD.send = init_send_hfcd(16); + if (!cs->bcs[0].hw.hfc.send) + cs->bcs[0].hw.hfc.send = init_send_hfcd(32); + if (!cs->bcs[1].hw.hfc.send) + cs->bcs[1].hw.hfc.send = init_send_hfcd(32); + cs->BC_Send_Data = &hfc_send_data; + cs->bcs[0].BC_SetStack = setstack_2b; + cs->bcs[1].BC_SetStack = setstack_2b; + cs->bcs[0].BC_Close = close_2bs0; + cs->bcs[1].BC_Close = close_2bs0; + mode_2bs0(cs->bcs, 0, 0); + mode_2bs0(cs->bcs + 1, 0, 1); +} + +void +release2bds0(struct IsdnCardState *cs) +{ + if (cs->bcs[0].hw.hfc.send) { + kfree(cs->bcs[0].hw.hfc.send); + cs->bcs[0].hw.hfc.send = NULL; + } + if (cs->bcs[1].hw.hfc.send) { + kfree(cs->bcs[1].hw.hfc.send); + cs->bcs[1].hw.hfc.send = NULL; + } + if (cs->hw.hfcD.send) { + kfree(cs->hw.hfcD.send); + cs->hw.hfcD.send = NULL; + } +} + +void +set_cs_func(struct IsdnCardState *cs) +{ + cs->readisac = &readreghfcd; + cs->writeisac = &writereghfcd; + cs->readisacfifo = &dummyf; + cs->writeisacfifo = &dummyf; + cs->BC_Read_Reg = &ReadReg; + cs->BC_Write_Reg = &WriteReg; + cs->dbusytimer.function = (void *) hfc_dbusy_timer; + cs->dbusytimer.data = (long) cs; + init_timer(&cs->dbusytimer); + INIT_WORK(&cs->tqueue, (void *)(void *) hfcd_bh, cs); +} diff --git a/drivers/isdn/hisax/hfc_2bds0.h b/drivers/isdn/hisax/hfc_2bds0.h new file mode 100644 index 000000000000..30f1924db91c --- /dev/null +++ b/drivers/isdn/hisax/hfc_2bds0.h @@ -0,0 +1,128 @@ +/* $Id: hfc_2bds0.h,v 1.6.2.2 2004/01/12 22:52:26 keil Exp $ + * + * specific defines for CCD's HFC 2BDS0 + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#define HFCD_CIRM 0x18 +#define HFCD_CTMT 0x19 +#define HFCD_INT_M1 0x1A +#define HFCD_INT_M2 0x1B +#define HFCD_INT_S1 0x1E +#define HFCD_STAT 0x1C +#define HFCD_STAT_DISB 0x1D +#define HFCD_STATES 0x30 +#define HFCD_SCTRL 0x31 +#define HFCD_TEST 0x32 +#define HFCD_SQ 0x34 +#define HFCD_CLKDEL 0x37 +#define HFCD_MST_MODE 0x2E +#define HFCD_CONN 0x2F + +#define HFCD_FIFO 0x80 +#define HFCD_Z1 0x10 +#define HFCD_Z2 0x18 +#define HFCD_Z_LOW 0x00 +#define HFCD_Z_HIGH 0x04 +#define HFCD_F1_INC 0x12 +#define HFCD_FIFO_IN 0x16 +#define HFCD_F1 0x1a +#define HFCD_F2 0x1e +#define HFCD_F2_INC 0x22 +#define HFCD_FIFO_OUT 0x26 +#define HFCD_REC 0x01 +#define HFCD_SEND 0x00 + +#define HFCB_FIFO 0x80 +#define HFCB_Z1 0x00 +#define HFCB_Z2 0x08 +#define HFCB_Z_LOW 0x00 +#define HFCB_Z_HIGH 0x04 +#define HFCB_F1_INC 0x28 +#define HFCB_FIFO_IN 0x2c +#define HFCB_F1 0x30 +#define HFCB_F2 0x34 +#define HFCB_F2_INC 0x38 +#define HFCB_FIFO_OUT 0x3c +#define HFCB_REC 0x01 +#define HFCB_SEND 0x00 +#define HFCB_B1 0x00 +#define HFCB_B2 0x02 +#define HFCB_CHANNEL(ch) (ch ? HFCB_B2 : HFCB_B1) + +#define HFCD_STATUS 0 +#define HFCD_DATA 1 +#define HFCD_DATA_NODEB 2 + +/* Status (READ) */ +#define HFCD_BUSY 0x01 +#define HFCD_BUSY_NBUSY 0x04 +#define HFCD_TIMER_ELAP 0x10 +#define HFCD_STATINT 0x20 +#define HFCD_FRAMEINT 0x40 +#define HFCD_ANYINT 0x80 + +/* CTMT (Write) */ +#define HFCD_CLTIMER 0x80 +#define HFCD_TIM25 0x00 +#define HFCD_TIM50 0x08 +#define HFCD_TIM400 0x10 +#define HFCD_TIM800 0x18 +#define HFCD_AUTO_TIMER 0x20 +#define HFCD_TRANSB2 0x02 +#define HFCD_TRANSB1 0x01 + +/* CIRM (Write) */ +#define HFCD_RESET 0x08 +#define HFCD_MEM8K 0x10 +#define HFCD_INTA 0x01 +#define HFCD_INTB 0x02 +#define HFCD_INTC 0x03 +#define HFCD_INTD 0x04 +#define HFCD_INTE 0x05 +#define HFCD_INTF 0x06 + +/* INT_M1;INT_S1 */ +#define HFCD_INTS_B1TRANS 0x01 +#define HFCD_INTS_B2TRANS 0x02 +#define HFCD_INTS_DTRANS 0x04 +#define HFCD_INTS_B1REC 0x08 +#define HFCD_INTS_B2REC 0x10 +#define HFCD_INTS_DREC 0x20 +#define HFCD_INTS_L1STATE 0x40 +#define HFCD_INTS_TIMER 0x80 + +/* INT_M2 */ +#define HFCD_IRQ_ENABLE 0x08 + +/* STATES */ +#define HFCD_LOAD_STATE 0x10 +#define HFCD_ACTIVATE 0x20 +#define HFCD_DO_ACTION 0x40 + +/* HFCD_MST_MODE */ +#define HFCD_MASTER 0x01 + +/* HFCD_SCTRL */ +#define SCTRL_B1_ENA 0x01 +#define SCTRL_B2_ENA 0x02 +#define SCTRL_LOW_PRIO 0x08 +#define SCTRL_SQ_ENA 0x10 +#define SCTRL_TEST 0x20 +#define SCTRL_NONE_CAP 0x40 +#define SCTRL_PWR_DOWN 0x80 + +/* HFCD_TEST */ +#define HFCD_AUTO_AWAKE 0x01 + +extern void main_irq_2bds0(struct BCState *bcs); +extern void init2bds0(struct IsdnCardState *cs); +extern void release2bds0(struct IsdnCardState *cs); +extern void hfc2bds0_interrupt(struct IsdnCardState *cs, u_char val); +extern void set_cs_func(struct IsdnCardState *cs); diff --git a/drivers/isdn/hisax/hfc_2bs0.c b/drivers/isdn/hisax/hfc_2bs0.c new file mode 100644 index 000000000000..bb376f39ac89 --- /dev/null +++ b/drivers/isdn/hisax/hfc_2bs0.c @@ -0,0 +1,593 @@ +/* $Id: hfc_2bs0.c,v 1.20.2.6 2004/02/11 13:21:33 keil Exp $ + * + * specific routines for CCD's HFC 2BS0 + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/init.h> +#include "hisax.h" +#include "hfc_2bs0.h" +#include "isac.h" +#include "isdnl1.h" +#include <linux/interrupt.h> + +static inline int +WaitForBusy(struct IsdnCardState *cs) +{ + int to = 130; + u_char val; + + while (!(cs->BC_Read_Reg(cs, HFC_STATUS, 0) & HFC_BUSY) && to) { + val = cs->BC_Read_Reg(cs, HFC_DATA, HFC_CIP | HFC_F2 | + (cs->hw.hfc.cip & 3)); + udelay(1); + to--; + } + if (!to) { + printk(KERN_WARNING "HiSax: waitforBusy timeout\n"); + return (0); + } else + return (to); +} + +static inline int +WaitNoBusy(struct IsdnCardState *cs) +{ + int to = 125; + + while ((cs->BC_Read_Reg(cs, HFC_STATUS, 0) & HFC_BUSY) && to) { + udelay(1); + to--; + } + if (!to) { + printk(KERN_WARNING "HiSax: waitforBusy timeout\n"); + return (0); + } else + return (to); +} + +int +GetFreeFifoBytes(struct BCState *bcs) +{ + int s; + + if (bcs->hw.hfc.f1 == bcs->hw.hfc.f2) + return (bcs->cs->hw.hfc.fifosize); + s = bcs->hw.hfc.send[bcs->hw.hfc.f1] - bcs->hw.hfc.send[bcs->hw.hfc.f2]; + if (s <= 0) + s += bcs->cs->hw.hfc.fifosize; + s = bcs->cs->hw.hfc.fifosize - s; + return (s); +} + +int +ReadZReg(struct BCState *bcs, u_char reg) +{ + int val; + + WaitNoBusy(bcs->cs); + val = 256 * bcs->cs->BC_Read_Reg(bcs->cs, HFC_DATA, reg | HFC_CIP | HFC_Z_HIGH); + WaitNoBusy(bcs->cs); + val += bcs->cs->BC_Read_Reg(bcs->cs, HFC_DATA, reg | HFC_CIP | HFC_Z_LOW); + return (val); +} + +static void +hfc_clear_fifo(struct BCState *bcs) +{ + struct IsdnCardState *cs = bcs->cs; + int idx, cnt; + int rcnt, z1, z2; + u_char cip, f1, f2; + + if ((cs->debug & L1_DEB_HSCX) && !(cs->debug & L1_DEB_HSCX_FIFO)) + debugl1(cs, "hfc_clear_fifo"); + cip = HFC_CIP | HFC_F1 | HFC_REC | HFC_CHANNEL(bcs->channel); + if ((cip & 0xc3) != (cs->hw.hfc.cip & 0xc3)) { + cs->BC_Write_Reg(cs, HFC_STATUS, cip, cip); + WaitForBusy(cs); + } + WaitNoBusy(cs); + f1 = cs->BC_Read_Reg(cs, HFC_DATA, cip); + cip = HFC_CIP | HFC_F2 | HFC_REC | HFC_CHANNEL(bcs->channel); + WaitNoBusy(cs); + f2 = cs->BC_Read_Reg(cs, HFC_DATA, cip); + z1 = ReadZReg(bcs, HFC_Z1 | HFC_REC | HFC_CHANNEL(bcs->channel)); + z2 = ReadZReg(bcs, HFC_Z2 | HFC_REC | HFC_CHANNEL(bcs->channel)); + cnt = 32; + while (((f1 != f2) || (z1 != z2)) && cnt--) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfc clear %d f1(%d) f2(%d)", + bcs->channel, f1, f2); + rcnt = z1 - z2; + if (rcnt < 0) + rcnt += cs->hw.hfc.fifosize; + if (rcnt) + rcnt++; + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfc clear %d z1(%x) z2(%x) cnt(%d)", + bcs->channel, z1, z2, rcnt); + cip = HFC_CIP | HFC_FIFO_OUT | HFC_REC | HFC_CHANNEL(bcs->channel); + idx = 0; + while ((idx < rcnt) && WaitNoBusy(cs)) { + cs->BC_Read_Reg(cs, HFC_DATA_NODEB, cip); + idx++; + } + if (f1 != f2) { + WaitNoBusy(cs); + cs->BC_Read_Reg(cs, HFC_DATA, HFC_CIP | HFC_F2_INC | HFC_REC | + HFC_CHANNEL(bcs->channel)); + WaitForBusy(cs); + } + cip = HFC_CIP | HFC_F1 | HFC_REC | HFC_CHANNEL(bcs->channel); + WaitNoBusy(cs); + f1 = cs->BC_Read_Reg(cs, HFC_DATA, cip); + cip = HFC_CIP | HFC_F2 | HFC_REC | HFC_CHANNEL(bcs->channel); + WaitNoBusy(cs); + f2 = cs->BC_Read_Reg(cs, HFC_DATA, cip); + z1 = ReadZReg(bcs, HFC_Z1 | HFC_REC | HFC_CHANNEL(bcs->channel)); + z2 = ReadZReg(bcs, HFC_Z2 | HFC_REC | HFC_CHANNEL(bcs->channel)); + } + return; +} + + +static struct sk_buff +* +hfc_empty_fifo(struct BCState *bcs, int count) +{ + u_char *ptr; + struct sk_buff *skb; + struct IsdnCardState *cs = bcs->cs; + int idx; + int chksum; + u_char stat, cip; + + if ((cs->debug & L1_DEB_HSCX) && !(cs->debug & L1_DEB_HSCX_FIFO)) + debugl1(cs, "hfc_empty_fifo"); + idx = 0; + if (count > HSCX_BUFMAX + 3) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "hfc_empty_fifo: incoming packet too large"); + cip = HFC_CIP | HFC_FIFO_OUT | HFC_REC | HFC_CHANNEL(bcs->channel); + while ((idx++ < count) && WaitNoBusy(cs)) + cs->BC_Read_Reg(cs, HFC_DATA_NODEB, cip); + WaitNoBusy(cs); + stat = cs->BC_Read_Reg(cs, HFC_DATA, HFC_CIP | HFC_F2_INC | HFC_REC | + HFC_CHANNEL(bcs->channel)); + WaitForBusy(cs); + return (NULL); + } + if ((count < 4) && (bcs->mode != L1_MODE_TRANS)) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "hfc_empty_fifo: incoming packet too small"); + cip = HFC_CIP | HFC_FIFO_OUT | HFC_REC | HFC_CHANNEL(bcs->channel); + while ((idx++ < count) && WaitNoBusy(cs)) + cs->BC_Read_Reg(cs, HFC_DATA_NODEB, cip); + WaitNoBusy(cs); + stat = cs->BC_Read_Reg(cs, HFC_DATA, HFC_CIP | HFC_F2_INC | HFC_REC | + HFC_CHANNEL(bcs->channel)); + WaitForBusy(cs); +#ifdef ERROR_STATISTIC + bcs->err_inv++; +#endif + return (NULL); + } + if (bcs->mode == L1_MODE_TRANS) + count -= 1; + else + count -= 3; + if (!(skb = dev_alloc_skb(count))) + printk(KERN_WARNING "HFC: receive out of memory\n"); + else { + ptr = skb_put(skb, count); + idx = 0; + cip = HFC_CIP | HFC_FIFO_OUT | HFC_REC | HFC_CHANNEL(bcs->channel); + while ((idx < count) && WaitNoBusy(cs)) { + *ptr++ = cs->BC_Read_Reg(cs, HFC_DATA_NODEB, cip); + idx++; + } + if (idx != count) { + debugl1(cs, "RFIFO BUSY error"); + printk(KERN_WARNING "HFC FIFO channel %d BUSY Error\n", bcs->channel); + dev_kfree_skb_any(skb); + if (bcs->mode != L1_MODE_TRANS) { + WaitNoBusy(cs); + stat = cs->BC_Read_Reg(cs, HFC_DATA, HFC_CIP | HFC_F2_INC | HFC_REC | + HFC_CHANNEL(bcs->channel)); + WaitForBusy(cs); + } + return (NULL); + } + if (bcs->mode != L1_MODE_TRANS) { + WaitNoBusy(cs); + chksum = (cs->BC_Read_Reg(cs, HFC_DATA, cip) << 8); + WaitNoBusy(cs); + chksum += cs->BC_Read_Reg(cs, HFC_DATA, cip); + WaitNoBusy(cs); + stat = cs->BC_Read_Reg(cs, HFC_DATA, cip); + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfc_empty_fifo %d chksum %x stat %x", + bcs->channel, chksum, stat); + if (stat) { + debugl1(cs, "FIFO CRC error"); + dev_kfree_skb_any(skb); + skb = NULL; +#ifdef ERROR_STATISTIC + bcs->err_crc++; +#endif + } + WaitNoBusy(cs); + stat = cs->BC_Read_Reg(cs, HFC_DATA, HFC_CIP | HFC_F2_INC | HFC_REC | + HFC_CHANNEL(bcs->channel)); + WaitForBusy(cs); + } + } + return (skb); +} + +static void +hfc_fill_fifo(struct BCState *bcs) +{ + struct IsdnCardState *cs = bcs->cs; + int idx, fcnt; + int count; + int z1, z2; + u_char cip; + + if (!bcs->tx_skb) + return; + if (bcs->tx_skb->len <= 0) + return; + + cip = HFC_CIP | HFC_F1 | HFC_SEND | HFC_CHANNEL(bcs->channel); + if ((cip & 0xc3) != (cs->hw.hfc.cip & 0xc3)) { + cs->BC_Write_Reg(cs, HFC_STATUS, cip, cip); + WaitForBusy(cs); + } + WaitNoBusy(cs); + if (bcs->mode != L1_MODE_TRANS) { + bcs->hw.hfc.f1 = cs->BC_Read_Reg(cs, HFC_DATA, cip); + cip = HFC_CIP | HFC_F2 | HFC_SEND | HFC_CHANNEL(bcs->channel); + WaitNoBusy(cs); + bcs->hw.hfc.f2 = cs->BC_Read_Reg(cs, HFC_DATA, cip); + bcs->hw.hfc.send[bcs->hw.hfc.f1] = ReadZReg(bcs, HFC_Z1 | HFC_SEND | HFC_CHANNEL(bcs->channel)); + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfc_fill_fifo %d f1(%d) f2(%d) z1(%x)", + bcs->channel, bcs->hw.hfc.f1, bcs->hw.hfc.f2, + bcs->hw.hfc.send[bcs->hw.hfc.f1]); + fcnt = bcs->hw.hfc.f1 - bcs->hw.hfc.f2; + if (fcnt < 0) + fcnt += 32; + if (fcnt > 30) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfc_fill_fifo more as 30 frames"); + return; + } + count = GetFreeFifoBytes(bcs); + } + else { + WaitForBusy(cs); + z1 = ReadZReg(bcs, HFC_Z1 | HFC_REC | HFC_CHANNEL(bcs->channel)); + z2 = ReadZReg(bcs, HFC_Z2 | HFC_REC | HFC_CHANNEL(bcs->channel)); + count = z1 - z2; + if (count < 0) + count += cs->hw.hfc.fifosize; + } /* L1_MODE_TRANS */ + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfc_fill_fifo %d count(%ld/%d)", + bcs->channel, bcs->tx_skb->len, + count); + if (count < bcs->tx_skb->len) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfc_fill_fifo no fifo mem"); + return; + } + cip = HFC_CIP | HFC_FIFO_IN | HFC_SEND | HFC_CHANNEL(bcs->channel); + idx = 0; + while ((idx < bcs->tx_skb->len) && WaitNoBusy(cs)) + cs->BC_Write_Reg(cs, HFC_DATA_NODEB, cip, bcs->tx_skb->data[idx++]); + if (idx != bcs->tx_skb->len) { + debugl1(cs, "FIFO Send BUSY error"); + printk(KERN_WARNING "HFC S FIFO channel %d BUSY Error\n", bcs->channel); + } else { + count = bcs->tx_skb->len; + bcs->tx_cnt -= count; + if (PACKET_NOACK == bcs->tx_skb->pkt_type) + count = -1; + dev_kfree_skb_any(bcs->tx_skb); + bcs->tx_skb = NULL; + if (bcs->mode != L1_MODE_TRANS) { + WaitForBusy(cs); + WaitNoBusy(cs); + cs->BC_Read_Reg(cs, HFC_DATA, HFC_CIP | HFC_F1_INC | HFC_SEND | HFC_CHANNEL(bcs->channel)); + } + if (test_bit(FLG_LLI_L1WAKEUP,&bcs->st->lli.flag) && + (count >= 0)) { + u_long flags; + spin_lock_irqsave(&bcs->aclock, flags); + bcs->ackcnt += count; + spin_unlock_irqrestore(&bcs->aclock, flags); + schedule_event(bcs, B_ACKPENDING); + } + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + } + return; +} + +void +main_irq_hfc(struct BCState *bcs) +{ + struct IsdnCardState *cs = bcs->cs; + int z1, z2, rcnt; + u_char f1, f2, cip; + int receive, transmit, count = 5; + struct sk_buff *skb; + + Begin: + count--; + cip = HFC_CIP | HFC_F1 | HFC_REC | HFC_CHANNEL(bcs->channel); + if ((cip & 0xc3) != (cs->hw.hfc.cip & 0xc3)) { + cs->BC_Write_Reg(cs, HFC_STATUS, cip, cip); + WaitForBusy(cs); + } + WaitNoBusy(cs); + receive = 0; + if (bcs->mode == L1_MODE_HDLC) { + f1 = cs->BC_Read_Reg(cs, HFC_DATA, cip); + cip = HFC_CIP | HFC_F2 | HFC_REC | HFC_CHANNEL(bcs->channel); + WaitNoBusy(cs); + f2 = cs->BC_Read_Reg(cs, HFC_DATA, cip); + if (f1 != f2) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfc rec %d f1(%d) f2(%d)", + bcs->channel, f1, f2); + receive = 1; + } + } + if (receive || (bcs->mode == L1_MODE_TRANS)) { + WaitForBusy(cs); + z1 = ReadZReg(bcs, HFC_Z1 | HFC_REC | HFC_CHANNEL(bcs->channel)); + z2 = ReadZReg(bcs, HFC_Z2 | HFC_REC | HFC_CHANNEL(bcs->channel)); + rcnt = z1 - z2; + if (rcnt < 0) + rcnt += cs->hw.hfc.fifosize; + if ((bcs->mode == L1_MODE_HDLC) || (rcnt)) { + rcnt++; + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfc rec %d z1(%x) z2(%x) cnt(%d)", + bcs->channel, z1, z2, rcnt); + /* sti(); */ + if ((skb = hfc_empty_fifo(bcs, rcnt))) { + skb_queue_tail(&bcs->rqueue, skb); + schedule_event(bcs, B_RCVBUFREADY); + } + } + receive = 1; + } + if (bcs->tx_skb) { + transmit = 1; + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + hfc_fill_fifo(bcs); + if (test_bit(BC_FLG_BUSY, &bcs->Flag)) + transmit = 0; + } else { + if ((bcs->tx_skb = skb_dequeue(&bcs->squeue))) { + transmit = 1; + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + hfc_fill_fifo(bcs); + if (test_bit(BC_FLG_BUSY, &bcs->Flag)) + transmit = 0; + } else { + transmit = 0; + schedule_event(bcs, B_XMTBUFREADY); + } + } + if ((receive || transmit) && count) + goto Begin; + return; +} + +void +mode_hfc(struct BCState *bcs, int mode, int bc) +{ + struct IsdnCardState *cs = bcs->cs; + + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HFC 2BS0 mode %d bchan %d/%d", + mode, bc, bcs->channel); + bcs->mode = mode; + bcs->channel = bc; + + switch (mode) { + case (L1_MODE_NULL): + if (bc) { + cs->hw.hfc.ctmt &= ~1; + cs->hw.hfc.isac_spcr &= ~0x03; + } + else { + cs->hw.hfc.ctmt &= ~2; + cs->hw.hfc.isac_spcr &= ~0x0c; + } + break; + case (L1_MODE_TRANS): + cs->hw.hfc.ctmt &= ~(1 << bc); /* set HDLC mode */ + cs->BC_Write_Reg(cs, HFC_STATUS, cs->hw.hfc.ctmt, cs->hw.hfc.ctmt); + hfc_clear_fifo(bcs); /* complete fifo clear */ + if (bc) { + cs->hw.hfc.ctmt |= 1; + cs->hw.hfc.isac_spcr &= ~0x03; + cs->hw.hfc.isac_spcr |= 0x02; + } else { + cs->hw.hfc.ctmt |= 2; + cs->hw.hfc.isac_spcr &= ~0x0c; + cs->hw.hfc.isac_spcr |= 0x08; + } + break; + case (L1_MODE_HDLC): + if (bc) { + cs->hw.hfc.ctmt &= ~1; + cs->hw.hfc.isac_spcr &= ~0x03; + cs->hw.hfc.isac_spcr |= 0x02; + } else { + cs->hw.hfc.ctmt &= ~2; + cs->hw.hfc.isac_spcr &= ~0x0c; + cs->hw.hfc.isac_spcr |= 0x08; + } + break; + } + cs->BC_Write_Reg(cs, HFC_STATUS, cs->hw.hfc.ctmt, cs->hw.hfc.ctmt); + cs->writeisac(cs, ISAC_SPCR, cs->hw.hfc.isac_spcr); + if (mode == L1_MODE_HDLC) + hfc_clear_fifo(bcs); +} + +static void +hfc_l2l1(struct PStack *st, int pr, void *arg) +{ + struct BCState *bcs = st->l1.bcs; + struct sk_buff *skb = arg; + u_long flags; + + switch (pr) { + case (PH_DATA | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + skb_queue_tail(&bcs->squeue, skb); + } else { + bcs->tx_skb = skb; + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->cs->BC_Send_Data(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | INDICATION): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + printk(KERN_WARNING "hfc_l2l1: this shouldn't happen\n"); + } else { + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->tx_skb = skb; + bcs->cs->BC_Send_Data(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | REQUEST): + if (!bcs->tx_skb) { + test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } else + test_and_set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + case (PH_ACTIVATE | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + test_and_set_bit(BC_FLG_ACTIV, &bcs->Flag); + mode_hfc(bcs, st->l1.mode, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | REQUEST): + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | CONFIRM): + spin_lock_irqsave(&bcs->cs->lock, flags); + test_and_clear_bit(BC_FLG_ACTIV, &bcs->Flag); + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + mode_hfc(bcs, 0, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + st->l1.l1l2(st, PH_DEACTIVATE | CONFIRM, NULL); + break; + } +} + + +void +close_hfcstate(struct BCState *bcs) +{ + mode_hfc(bcs, 0, bcs->channel); + if (test_bit(BC_FLG_INIT, &bcs->Flag)) { + skb_queue_purge(&bcs->rqueue); + skb_queue_purge(&bcs->squeue); + if (bcs->tx_skb) { + dev_kfree_skb_any(bcs->tx_skb); + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + } + } + test_and_clear_bit(BC_FLG_INIT, &bcs->Flag); +} + +static int +open_hfcstate(struct IsdnCardState *cs, struct BCState *bcs) +{ + if (!test_and_set_bit(BC_FLG_INIT, &bcs->Flag)) { + skb_queue_head_init(&bcs->rqueue); + skb_queue_head_init(&bcs->squeue); + } + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->event = 0; + bcs->tx_cnt = 0; + return (0); +} + +int +setstack_hfc(struct PStack *st, struct BCState *bcs) +{ + bcs->channel = st->l1.bc; + if (open_hfcstate(st->l1.hardware, bcs)) + return (-1); + st->l1.bcs = bcs; + st->l2.l2l1 = hfc_l2l1; + setstack_manager(st); + bcs->st = st; + setstack_l1_B(st); + return (0); +} + +void __init +init_send(struct BCState *bcs) +{ + int i; + + if (!(bcs->hw.hfc.send = kmalloc(32 * sizeof(unsigned int), GFP_ATOMIC))) { + printk(KERN_WARNING + "HiSax: No memory for hfc.send\n"); + return; + } + for (i = 0; i < 32; i++) + bcs->hw.hfc.send[i] = 0x1fff; +} + +void __init +inithfc(struct IsdnCardState *cs) +{ + init_send(&cs->bcs[0]); + init_send(&cs->bcs[1]); + cs->BC_Send_Data = &hfc_fill_fifo; + cs->bcs[0].BC_SetStack = setstack_hfc; + cs->bcs[1].BC_SetStack = setstack_hfc; + cs->bcs[0].BC_Close = close_hfcstate; + cs->bcs[1].BC_Close = close_hfcstate; + mode_hfc(cs->bcs, 0, 0); + mode_hfc(cs->bcs + 1, 0, 0); +} + +void +releasehfc(struct IsdnCardState *cs) +{ + if (cs->bcs[0].hw.hfc.send) { + kfree(cs->bcs[0].hw.hfc.send); + cs->bcs[0].hw.hfc.send = NULL; + } + if (cs->bcs[1].hw.hfc.send) { + kfree(cs->bcs[1].hw.hfc.send); + cs->bcs[1].hw.hfc.send = NULL; + } +} diff --git a/drivers/isdn/hisax/hfc_2bs0.h b/drivers/isdn/hisax/hfc_2bs0.h new file mode 100644 index 000000000000..1a50d4a5c968 --- /dev/null +++ b/drivers/isdn/hisax/hfc_2bs0.h @@ -0,0 +1,60 @@ +/* $Id: hfc_2bs0.h,v 1.5.2.2 2004/01/12 22:52:26 keil Exp $ + * + * specific defines for CCD's HFC 2BS0 + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#define HFC_CTMT 0xe0 +#define HFC_CIRM 0xc0 +#define HFC_CIP 0x80 +#define HFC_Z1 0x00 +#define HFC_Z2 0x08 +#define HFC_Z_LOW 0x00 +#define HFC_Z_HIGH 0x04 +#define HFC_F1_INC 0x28 +#define HFC_FIFO_IN 0x2c +#define HFC_F1 0x30 +#define HFC_F2 0x34 +#define HFC_F2_INC 0x38 +#define HFC_FIFO_OUT 0x3c +#define HFC_B1 0x00 +#define HFC_B2 0x02 +#define HFC_REC 0x01 +#define HFC_SEND 0x00 +#define HFC_CHANNEL(ch) (ch ? HFC_B2 : HFC_B1) + +#define HFC_STATUS 0 +#define HFC_DATA 1 +#define HFC_DATA_NODEB 2 + +/* Status (READ) */ +#define HFC_BUSY 0x01 +#define HFC_TIMINT 0x02 +#define HFC_EXTINT 0x04 + +/* CTMT (Write) */ +#define HFC_CLTIMER 0x10 +#define HFC_TIM50MS 0x08 +#define HFC_TIMIRQE 0x04 +#define HFC_TRANSB2 0x02 +#define HFC_TRANSB1 0x01 + +/* CIRM (Write) */ +#define HFC_RESET 0x08 +#define HFC_MEM8K 0x10 +#define HFC_INTA 0x01 +#define HFC_INTB 0x02 +#define HFC_INTC 0x03 +#define HFC_INTD 0x04 +#define HFC_INTE 0x05 +#define HFC_INTF 0x06 + +extern void main_irq_hfc(struct BCState *bcs); +extern void inithfc(struct IsdnCardState *cs); +extern void releasehfc(struct IsdnCardState *cs); diff --git a/drivers/isdn/hisax/hfc_pci.c b/drivers/isdn/hisax/hfc_pci.c new file mode 100644 index 000000000000..c2db52696a86 --- /dev/null +++ b/drivers/isdn/hisax/hfc_pci.c @@ -0,0 +1,1747 @@ +/* $Id: hfc_pci.c,v 1.48.2.4 2004/02/11 13:21:33 keil Exp $ + * + * low level driver for CCD´s hfc-pci based cards + * + * Author Werner Cornelius + * based on existing driver for CCD hfc ISA cards + * Copyright by Werner Cornelius <werner@isdn4linux.de> + * by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * For changes and modifications please read + * Documentation/isdn/HiSax.cert + * + */ + +#include <linux/init.h> +#include <linux/config.h> +#include "hisax.h" +#include "hfc_pci.h" +#include "isdnl1.h" +#include <linux/pci.h> +#include <linux/interrupt.h> + +extern const char *CardType[]; + +static const char *hfcpci_revision = "$Revision: 1.48.2.4 $"; + +/* table entry in the PCI devices list */ +typedef struct { + int vendor_id; + int device_id; + char *vendor_name; + char *card_name; +} PCI_ENTRY; + +#define NT_T1_COUNT 20 /* number of 3.125ms interrupts for G2 timeout */ +#define CLKDEL_TE 0x0e /* CLKDEL in TE mode */ +#define CLKDEL_NT 0x6c /* CLKDEL in NT mode */ + +static const PCI_ENTRY id_list[] = +{ + {PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_2BD0, "CCD/Billion/Asuscom", "2BD0"}, + {PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_B000, "Billion", "B000"}, + {PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_B006, "Billion", "B006"}, + {PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_B007, "Billion", "B007"}, + {PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_B008, "Billion", "B008"}, + {PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_B009, "Billion", "B009"}, + {PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_B00A, "Billion", "B00A"}, + {PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_B00B, "Billion", "B00B"}, + {PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_B00C, "Billion", "B00C"}, + {PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_B100, "Seyeon", "B100"}, + {PCI_VENDOR_ID_ABOCOM, PCI_DEVICE_ID_ABOCOM_2BD1, "Abocom/Magitek", "2BD1"}, + {PCI_VENDOR_ID_ASUSTEK, PCI_DEVICE_ID_ASUSTEK_0675, "Asuscom/Askey", "675"}, + {PCI_VENDOR_ID_BERKOM, PCI_DEVICE_ID_BERKOM_T_CONCEPT, "German telekom", "T-Concept"}, + {PCI_VENDOR_ID_BERKOM, PCI_DEVICE_ID_BERKOM_A1T, "German telekom", "A1T"}, + {PCI_VENDOR_ID_ANIGMA, PCI_DEVICE_ID_ANIGMA_MC145575, "Motorola MC145575", "MC145575"}, + {PCI_VENDOR_ID_ZOLTRIX, PCI_DEVICE_ID_ZOLTRIX_2BD0, "Zoltrix", "2BD0"}, + {PCI_VENDOR_ID_DIGI, PCI_DEVICE_ID_DIGI_DF_M_IOM2_E,"Digi International", "Digi DataFire Micro V IOM2 (Europe)"}, + {PCI_VENDOR_ID_DIGI, PCI_DEVICE_ID_DIGI_DF_M_E,"Digi International", "Digi DataFire Micro V (Europe)"}, + {PCI_VENDOR_ID_DIGI, PCI_DEVICE_ID_DIGI_DF_M_IOM2_A,"Digi International", "Digi DataFire Micro V IOM2 (North America)"}, + {PCI_VENDOR_ID_DIGI, PCI_DEVICE_ID_DIGI_DF_M_A,"Digi International", "Digi DataFire Micro V (North America)"}, + {0, 0, NULL, NULL}, +}; + + +#ifdef CONFIG_PCI + +/******************************************/ +/* free hardware resources used by driver */ +/******************************************/ +void +release_io_hfcpci(struct IsdnCardState *cs) +{ + printk(KERN_INFO "HiSax: release hfcpci at %p\n", + cs->hw.hfcpci.pci_io); + cs->hw.hfcpci.int_m2 = 0; /* interrupt output off ! */ + Write_hfc(cs, HFCPCI_INT_M2, cs->hw.hfcpci.int_m2); + Write_hfc(cs, HFCPCI_CIRM, HFCPCI_RESET); /* Reset On */ + mdelay(10); + Write_hfc(cs, HFCPCI_CIRM, 0); /* Reset Off */ + mdelay(10); + Write_hfc(cs, HFCPCI_INT_M2, cs->hw.hfcpci.int_m2); + pci_write_config_word(cs->hw.hfcpci.dev, PCI_COMMAND, 0); /* disable memory mapped ports + busmaster */ + del_timer(&cs->hw.hfcpci.timer); + kfree(cs->hw.hfcpci.share_start); + cs->hw.hfcpci.share_start = NULL; + iounmap((void *)cs->hw.hfcpci.pci_io); +} + +/********************************************************************************/ +/* function called to reset the HFC PCI chip. A complete software reset of chip */ +/* and fifos is done. */ +/********************************************************************************/ +static void +reset_hfcpci(struct IsdnCardState *cs) +{ + pci_write_config_word(cs->hw.hfcpci.dev, PCI_COMMAND, PCI_ENA_MEMIO); /* enable memory mapped ports, disable busmaster */ + cs->hw.hfcpci.int_m2 = 0; /* interrupt output off ! */ + Write_hfc(cs, HFCPCI_INT_M2, cs->hw.hfcpci.int_m2); + + printk(KERN_INFO "HFC_PCI: resetting card\n"); + pci_write_config_word(cs->hw.hfcpci.dev, PCI_COMMAND, PCI_ENA_MEMIO + PCI_ENA_MASTER); /* enable memory ports + busmaster */ + Write_hfc(cs, HFCPCI_CIRM, HFCPCI_RESET); /* Reset On */ + mdelay(10); + Write_hfc(cs, HFCPCI_CIRM, 0); /* Reset Off */ + mdelay(10); + if (Read_hfc(cs, HFCPCI_STATUS) & 2) + printk(KERN_WARNING "HFC-PCI init bit busy\n"); + + cs->hw.hfcpci.fifo_en = 0x30; /* only D fifos enabled */ + Write_hfc(cs, HFCPCI_FIFO_EN, cs->hw.hfcpci.fifo_en); + + cs->hw.hfcpci.trm = 0 + HFCPCI_BTRANS_THRESMASK; /* no echo connect , threshold */ + Write_hfc(cs, HFCPCI_TRM, cs->hw.hfcpci.trm); + + Write_hfc(cs, HFCPCI_CLKDEL, CLKDEL_TE); /* ST-Bit delay for TE-Mode */ + cs->hw.hfcpci.sctrl_e = HFCPCI_AUTO_AWAKE; + Write_hfc(cs, HFCPCI_SCTRL_E, cs->hw.hfcpci.sctrl_e); /* S/T Auto awake */ + cs->hw.hfcpci.bswapped = 0; /* no exchange */ + cs->hw.hfcpci.nt_mode = 0; /* we are in TE mode */ + cs->hw.hfcpci.ctmt = HFCPCI_TIM3_125 | HFCPCI_AUTO_TIMER; + Write_hfc(cs, HFCPCI_CTMT, cs->hw.hfcpci.ctmt); + + cs->hw.hfcpci.int_m1 = HFCPCI_INTS_DTRANS | HFCPCI_INTS_DREC | + HFCPCI_INTS_L1STATE | HFCPCI_INTS_TIMER; + Write_hfc(cs, HFCPCI_INT_M1, cs->hw.hfcpci.int_m1); + + /* Clear already pending ints */ + if (Read_hfc(cs, HFCPCI_INT_S1)); + + Write_hfc(cs, HFCPCI_STATES, HFCPCI_LOAD_STATE | 2); /* HFC ST 2 */ + udelay(10); + Write_hfc(cs, HFCPCI_STATES, 2); /* HFC ST 2 */ + cs->hw.hfcpci.mst_m = HFCPCI_MASTER; /* HFC Master Mode */ + + Write_hfc(cs, HFCPCI_MST_MODE, cs->hw.hfcpci.mst_m); + cs->hw.hfcpci.sctrl = 0x40; /* set tx_lo mode, error in datasheet ! */ + Write_hfc(cs, HFCPCI_SCTRL, cs->hw.hfcpci.sctrl); + cs->hw.hfcpci.sctrl_r = 0; + Write_hfc(cs, HFCPCI_SCTRL_R, cs->hw.hfcpci.sctrl_r); + + /* Init GCI/IOM2 in master mode */ + /* Slots 0 and 1 are set for B-chan 1 and 2 */ + /* D- and monitor/CI channel are not enabled */ + /* STIO1 is used as output for data, B1+B2 from ST->IOM+HFC */ + /* STIO2 is used as data input, B1+B2 from IOM->ST */ + /* ST B-channel send disabled -> continous 1s */ + /* The IOM slots are always enabled */ + cs->hw.hfcpci.conn = 0x36; /* set data flow directions */ + Write_hfc(cs, HFCPCI_CONNECT, cs->hw.hfcpci.conn); + Write_hfc(cs, HFCPCI_B1_SSL, 0x80); /* B1-Slot 0 STIO1 out enabled */ + Write_hfc(cs, HFCPCI_B2_SSL, 0x81); /* B2-Slot 1 STIO1 out enabled */ + Write_hfc(cs, HFCPCI_B1_RSL, 0x80); /* B1-Slot 0 STIO2 in enabled */ + Write_hfc(cs, HFCPCI_B2_RSL, 0x81); /* B2-Slot 1 STIO2 in enabled */ + + /* Finally enable IRQ output */ + cs->hw.hfcpci.int_m2 = HFCPCI_IRQ_ENABLE; + Write_hfc(cs, HFCPCI_INT_M2, cs->hw.hfcpci.int_m2); + if (Read_hfc(cs, HFCPCI_INT_S1)); +} + +/***************************************************/ +/* Timer function called when kernel timer expires */ +/***************************************************/ +static void +hfcpci_Timer(struct IsdnCardState *cs) +{ + cs->hw.hfcpci.timer.expires = jiffies + 75; + /* WD RESET */ +/* WriteReg(cs, HFCD_DATA, HFCD_CTMT, cs->hw.hfcpci.ctmt | 0x80); + add_timer(&cs->hw.hfcpci.timer); + */ +} + + +/*********************************/ +/* schedule a new D-channel task */ +/*********************************/ +static void +sched_event_D_pci(struct IsdnCardState *cs, int event) +{ + test_and_set_bit(event, &cs->event); + schedule_work(&cs->tqueue); +} + +/*********************************/ +/* schedule a new b_channel task */ +/*********************************/ +static void +hfcpci_sched_event(struct BCState *bcs, int event) +{ + test_and_set_bit(event, &bcs->event); + schedule_work(&bcs->tqueue); +} + +/************************************************/ +/* select a b-channel entry matching and active */ +/************************************************/ +static +struct BCState * +Sel_BCS(struct IsdnCardState *cs, int channel) +{ + if (cs->bcs[0].mode && (cs->bcs[0].channel == channel)) + return (&cs->bcs[0]); + else if (cs->bcs[1].mode && (cs->bcs[1].channel == channel)) + return (&cs->bcs[1]); + else + return (NULL); +} + +/***************************************/ +/* clear the desired B-channel rx fifo */ +/***************************************/ +static void hfcpci_clear_fifo_rx(struct IsdnCardState *cs, int fifo) +{ u_char fifo_state; + bzfifo_type *bzr; + + if (fifo) { + bzr = &((fifo_area *) (cs->hw.hfcpci.fifos))->b_chans.rxbz_b2; + fifo_state = cs->hw.hfcpci.fifo_en & HFCPCI_FIFOEN_B2RX; + } else { + bzr = &((fifo_area *) (cs->hw.hfcpci.fifos))->b_chans.rxbz_b1; + fifo_state = cs->hw.hfcpci.fifo_en & HFCPCI_FIFOEN_B1RX; + } + if (fifo_state) + cs->hw.hfcpci.fifo_en ^= fifo_state; + Write_hfc(cs, HFCPCI_FIFO_EN, cs->hw.hfcpci.fifo_en); + cs->hw.hfcpci.last_bfifo_cnt[fifo] = 0; + bzr->za[MAX_B_FRAMES].z1 = B_FIFO_SIZE + B_SUB_VAL - 1; + bzr->za[MAX_B_FRAMES].z2 = bzr->za[MAX_B_FRAMES].z1; + bzr->f1 = MAX_B_FRAMES; + bzr->f2 = bzr->f1; /* init F pointers to remain constant */ + if (fifo_state) + cs->hw.hfcpci.fifo_en |= fifo_state; + Write_hfc(cs, HFCPCI_FIFO_EN, cs->hw.hfcpci.fifo_en); +} + +/***************************************/ +/* clear the desired B-channel tx fifo */ +/***************************************/ +static void hfcpci_clear_fifo_tx(struct IsdnCardState *cs, int fifo) +{ u_char fifo_state; + bzfifo_type *bzt; + + if (fifo) { + bzt = &((fifo_area *) (cs->hw.hfcpci.fifos))->b_chans.txbz_b2; + fifo_state = cs->hw.hfcpci.fifo_en & HFCPCI_FIFOEN_B2TX; + } else { + bzt = &((fifo_area *) (cs->hw.hfcpci.fifos))->b_chans.txbz_b1; + fifo_state = cs->hw.hfcpci.fifo_en & HFCPCI_FIFOEN_B1TX; + } + if (fifo_state) + cs->hw.hfcpci.fifo_en ^= fifo_state; + Write_hfc(cs, HFCPCI_FIFO_EN, cs->hw.hfcpci.fifo_en); + bzt->za[MAX_B_FRAMES].z1 = B_FIFO_SIZE + B_SUB_VAL - 1; + bzt->za[MAX_B_FRAMES].z2 = bzt->za[MAX_B_FRAMES].z1; + bzt->f1 = MAX_B_FRAMES; + bzt->f2 = bzt->f1; /* init F pointers to remain constant */ + if (fifo_state) + cs->hw.hfcpci.fifo_en |= fifo_state; + Write_hfc(cs, HFCPCI_FIFO_EN, cs->hw.hfcpci.fifo_en); +} + +/*********************************************/ +/* read a complete B-frame out of the buffer */ +/*********************************************/ +static struct sk_buff +* +hfcpci_empty_fifo(struct BCState *bcs, bzfifo_type * bz, u_char * bdata, int count) +{ + u_char *ptr, *ptr1, new_f2; + struct sk_buff *skb; + struct IsdnCardState *cs = bcs->cs; + int total, maxlen, new_z2; + z_type *zp; + + if ((cs->debug & L1_DEB_HSCX) && !(cs->debug & L1_DEB_HSCX_FIFO)) + debugl1(cs, "hfcpci_empty_fifo"); + zp = &bz->za[bz->f2]; /* point to Z-Regs */ + new_z2 = zp->z2 + count; /* new position in fifo */ + if (new_z2 >= (B_FIFO_SIZE + B_SUB_VAL)) + new_z2 -= B_FIFO_SIZE; /* buffer wrap */ + new_f2 = (bz->f2 + 1) & MAX_B_FRAMES; + if ((count > HSCX_BUFMAX + 3) || (count < 4) || + (*(bdata + (zp->z1 - B_SUB_VAL)))) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "hfcpci_empty_fifo: incoming packet invalid length %d or crc", count); +#ifdef ERROR_STATISTIC + bcs->err_inv++; +#endif + bz->za[new_f2].z2 = new_z2; + bz->f2 = new_f2; /* next buffer */ + skb = NULL; + } else if (!(skb = dev_alloc_skb(count - 3))) + printk(KERN_WARNING "HFCPCI: receive out of memory\n"); + else { + total = count; + count -= 3; + ptr = skb_put(skb, count); + + if (zp->z2 + count <= B_FIFO_SIZE + B_SUB_VAL) + maxlen = count; /* complete transfer */ + else + maxlen = B_FIFO_SIZE + B_SUB_VAL - zp->z2; /* maximum */ + + ptr1 = bdata + (zp->z2 - B_SUB_VAL); /* start of data */ + memcpy(ptr, ptr1, maxlen); /* copy data */ + count -= maxlen; + + if (count) { /* rest remaining */ + ptr += maxlen; + ptr1 = bdata; /* start of buffer */ + memcpy(ptr, ptr1, count); /* rest */ + } + bz->za[new_f2].z2 = new_z2; + bz->f2 = new_f2; /* next buffer */ + + } + return (skb); +} + +/*******************************/ +/* D-channel receive procedure */ +/*******************************/ +static +int +receive_dmsg(struct IsdnCardState *cs) +{ + struct sk_buff *skb; + int maxlen; + int rcnt, total; + int count = 5; + u_char *ptr, *ptr1; + dfifo_type *df; + z_type *zp; + + df = &((fifo_area *) (cs->hw.hfcpci.fifos))->d_chan.d_rx; + if (test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + debugl1(cs, "rec_dmsg blocked"); + return (1); + } + while (((df->f1 & D_FREG_MASK) != (df->f2 & D_FREG_MASK)) && count--) { + zp = &df->za[df->f2 & D_FREG_MASK]; + rcnt = zp->z1 - zp->z2; + if (rcnt < 0) + rcnt += D_FIFO_SIZE; + rcnt++; + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "hfcpci recd f1(%d) f2(%d) z1(%x) z2(%x) cnt(%d)", + df->f1, df->f2, zp->z1, zp->z2, rcnt); + + if ((rcnt > MAX_DFRAME_LEN + 3) || (rcnt < 4) || + (df->data[zp->z1])) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "empty_fifo hfcpci paket inv. len %d or crc %d", rcnt, df->data[zp->z1]); +#ifdef ERROR_STATISTIC + cs->err_rx++; +#endif + df->f2 = ((df->f2 + 1) & MAX_D_FRAMES) | (MAX_D_FRAMES + 1); /* next buffer */ + df->za[df->f2 & D_FREG_MASK].z2 = (zp->z2 + rcnt) & (D_FIFO_SIZE - 1); + } else if ((skb = dev_alloc_skb(rcnt - 3))) { + total = rcnt; + rcnt -= 3; + ptr = skb_put(skb, rcnt); + + if (zp->z2 + rcnt <= D_FIFO_SIZE) + maxlen = rcnt; /* complete transfer */ + else + maxlen = D_FIFO_SIZE - zp->z2; /* maximum */ + + ptr1 = df->data + zp->z2; /* start of data */ + memcpy(ptr, ptr1, maxlen); /* copy data */ + rcnt -= maxlen; + + if (rcnt) { /* rest remaining */ + ptr += maxlen; + ptr1 = df->data; /* start of buffer */ + memcpy(ptr, ptr1, rcnt); /* rest */ + } + df->f2 = ((df->f2 + 1) & MAX_D_FRAMES) | (MAX_D_FRAMES + 1); /* next buffer */ + df->za[df->f2 & D_FREG_MASK].z2 = (zp->z2 + total) & (D_FIFO_SIZE - 1); + + skb_queue_tail(&cs->rq, skb); + sched_event_D_pci(cs, D_RCVBUFREADY); + } else + printk(KERN_WARNING "HFC-PCI: D receive out of memory\n"); + } + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + return (1); +} + +/*******************************************************************************/ +/* check for transparent receive data and read max one threshold size if avail */ +/*******************************************************************************/ +int +hfcpci_empty_fifo_trans(struct BCState *bcs, bzfifo_type * bz, u_char * bdata) +{ + unsigned short *z1r, *z2r; + int new_z2, fcnt, maxlen; + struct sk_buff *skb; + u_char *ptr, *ptr1; + + z1r = &bz->za[MAX_B_FRAMES].z1; /* pointer to z reg */ + z2r = z1r + 1; + + if (!(fcnt = *z1r - *z2r)) + return (0); /* no data avail */ + + if (fcnt <= 0) + fcnt += B_FIFO_SIZE; /* bytes actually buffered */ + if (fcnt > HFCPCI_BTRANS_THRESHOLD) + fcnt = HFCPCI_BTRANS_THRESHOLD; /* limit size */ + + new_z2 = *z2r + fcnt; /* new position in fifo */ + if (new_z2 >= (B_FIFO_SIZE + B_SUB_VAL)) + new_z2 -= B_FIFO_SIZE; /* buffer wrap */ + + if (!(skb = dev_alloc_skb(fcnt))) + printk(KERN_WARNING "HFCPCI: receive out of memory\n"); + else { + ptr = skb_put(skb, fcnt); + if (*z2r + fcnt <= B_FIFO_SIZE + B_SUB_VAL) + maxlen = fcnt; /* complete transfer */ + else + maxlen = B_FIFO_SIZE + B_SUB_VAL - *z2r; /* maximum */ + + ptr1 = bdata + (*z2r - B_SUB_VAL); /* start of data */ + memcpy(ptr, ptr1, maxlen); /* copy data */ + fcnt -= maxlen; + + if (fcnt) { /* rest remaining */ + ptr += maxlen; + ptr1 = bdata; /* start of buffer */ + memcpy(ptr, ptr1, fcnt); /* rest */ + } + skb_queue_tail(&bcs->rqueue, skb); + hfcpci_sched_event(bcs, B_RCVBUFREADY); + } + + *z2r = new_z2; /* new position */ + return (1); +} /* hfcpci_empty_fifo_trans */ + +/**********************************/ +/* B-channel main receive routine */ +/**********************************/ +void +main_rec_hfcpci(struct BCState *bcs) +{ + struct IsdnCardState *cs = bcs->cs; + int rcnt, real_fifo; + int receive, count = 5; + struct sk_buff *skb; + bzfifo_type *bz; + u_char *bdata; + z_type *zp; + + + if ((bcs->channel) && (!cs->hw.hfcpci.bswapped)) { + bz = &((fifo_area *) (cs->hw.hfcpci.fifos))->b_chans.rxbz_b2; + bdata = ((fifo_area *) (cs->hw.hfcpci.fifos))->b_chans.rxdat_b2; + real_fifo = 1; + } else { + bz = &((fifo_area *) (cs->hw.hfcpci.fifos))->b_chans.rxbz_b1; + bdata = ((fifo_area *) (cs->hw.hfcpci.fifos))->b_chans.rxdat_b1; + real_fifo = 0; + } + Begin: + count--; + if (test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + debugl1(cs, "rec_data %d blocked", bcs->channel); + return; + } + if (bz->f1 != bz->f2) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfcpci rec %d f1(%d) f2(%d)", + bcs->channel, bz->f1, bz->f2); + zp = &bz->za[bz->f2]; + + rcnt = zp->z1 - zp->z2; + if (rcnt < 0) + rcnt += B_FIFO_SIZE; + rcnt++; + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfcpci rec %d z1(%x) z2(%x) cnt(%d)", + bcs->channel, zp->z1, zp->z2, rcnt); + if ((skb = hfcpci_empty_fifo(bcs, bz, bdata, rcnt))) { + skb_queue_tail(&bcs->rqueue, skb); + hfcpci_sched_event(bcs, B_RCVBUFREADY); + } + rcnt = bz->f1 - bz->f2; + if (rcnt < 0) + rcnt += MAX_B_FRAMES + 1; + if (cs->hw.hfcpci.last_bfifo_cnt[real_fifo] > rcnt + 1) { + rcnt = 0; + hfcpci_clear_fifo_rx(cs, real_fifo); + } + cs->hw.hfcpci.last_bfifo_cnt[real_fifo] = rcnt; + if (rcnt > 1) + receive = 1; + else + receive = 0; + } else if (bcs->mode == L1_MODE_TRANS) + receive = hfcpci_empty_fifo_trans(bcs, bz, bdata); + else + receive = 0; + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + if (count && receive) + goto Begin; + return; +} + +/**************************/ +/* D-channel send routine */ +/**************************/ +static void +hfcpci_fill_dfifo(struct IsdnCardState *cs) +{ + int fcnt; + int count, new_z1, maxlen; + dfifo_type *df; + u_char *src, *dst, new_f1; + + if (!cs->tx_skb) + return; + if (cs->tx_skb->len <= 0) + return; + + df = &((fifo_area *) (cs->hw.hfcpci.fifos))->d_chan.d_tx; + + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "hfcpci_fill_Dfifo f1(%d) f2(%d) z1(f1)(%x)", + df->f1, df->f2, + df->za[df->f1 & D_FREG_MASK].z1); + fcnt = df->f1 - df->f2; /* frame count actually buffered */ + if (fcnt < 0) + fcnt += (MAX_D_FRAMES + 1); /* if wrap around */ + if (fcnt > (MAX_D_FRAMES - 1)) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "hfcpci_fill_Dfifo more as 14 frames"); +#ifdef ERROR_STATISTIC + cs->err_tx++; +#endif + return; + } + /* now determine free bytes in FIFO buffer */ + count = df->za[df->f2 & D_FREG_MASK].z2 - df->za[df->f1 & D_FREG_MASK].z1 - 1; + if (count <= 0) + count += D_FIFO_SIZE; /* count now contains available bytes */ + + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "hfcpci_fill_Dfifo count(%ld/%d)", + cs->tx_skb->len, count); + if (count < cs->tx_skb->len) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "hfcpci_fill_Dfifo no fifo mem"); + return; + } + count = cs->tx_skb->len; /* get frame len */ + new_z1 = (df->za[df->f1 & D_FREG_MASK].z1 + count) & (D_FIFO_SIZE - 1); + new_f1 = ((df->f1 + 1) & D_FREG_MASK) | (D_FREG_MASK + 1); + src = cs->tx_skb->data; /* source pointer */ + dst = df->data + df->za[df->f1 & D_FREG_MASK].z1; + maxlen = D_FIFO_SIZE - df->za[df->f1 & D_FREG_MASK].z1; /* end fifo */ + if (maxlen > count) + maxlen = count; /* limit size */ + memcpy(dst, src, maxlen); /* first copy */ + + count -= maxlen; /* remaining bytes */ + if (count) { + dst = df->data; /* start of buffer */ + src += maxlen; /* new position */ + memcpy(dst, src, count); + } + df->za[new_f1 & D_FREG_MASK].z1 = new_z1; /* for next buffer */ + df->za[df->f1 & D_FREG_MASK].z1 = new_z1; /* new pos actual buffer */ + df->f1 = new_f1; /* next frame */ + + dev_kfree_skb_any(cs->tx_skb); + cs->tx_skb = NULL; + return; +} + +/**************************/ +/* B-channel send routine */ +/**************************/ +static void +hfcpci_fill_fifo(struct BCState *bcs) +{ + struct IsdnCardState *cs = bcs->cs; + int maxlen, fcnt; + int count, new_z1; + bzfifo_type *bz; + u_char *bdata; + u_char new_f1, *src, *dst; + unsigned short *z1t, *z2t; + + if (!bcs->tx_skb) + return; + if (bcs->tx_skb->len <= 0) + return; + + if ((bcs->channel) && (!cs->hw.hfcpci.bswapped)) { + bz = &((fifo_area *) (cs->hw.hfcpci.fifos))->b_chans.txbz_b2; + bdata = ((fifo_area *) (cs->hw.hfcpci.fifos))->b_chans.txdat_b2; + } else { + bz = &((fifo_area *) (cs->hw.hfcpci.fifos))->b_chans.txbz_b1; + bdata = ((fifo_area *) (cs->hw.hfcpci.fifos))->b_chans.txdat_b1; + } + + if (bcs->mode == L1_MODE_TRANS) { + z1t = &bz->za[MAX_B_FRAMES].z1; + z2t = z1t + 1; + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfcpci_fill_fifo_trans %d z1(%x) z2(%x)", + bcs->channel, *z1t, *z2t); + fcnt = *z2t - *z1t; + if (fcnt <= 0) + fcnt += B_FIFO_SIZE; /* fcnt contains available bytes in fifo */ + fcnt = B_FIFO_SIZE - fcnt; /* remaining bytes to send */ + + while ((fcnt < 2 * HFCPCI_BTRANS_THRESHOLD) && (bcs->tx_skb)) { + if (bcs->tx_skb->len < B_FIFO_SIZE - fcnt) { + /* data is suitable for fifo */ + count = bcs->tx_skb->len; + + new_z1 = *z1t + count; /* new buffer Position */ + if (new_z1 >= (B_FIFO_SIZE + B_SUB_VAL)) + new_z1 -= B_FIFO_SIZE; /* buffer wrap */ + src = bcs->tx_skb->data; /* source pointer */ + dst = bdata + (*z1t - B_SUB_VAL); + maxlen = (B_FIFO_SIZE + B_SUB_VAL) - *z1t; /* end of fifo */ + if (maxlen > count) + maxlen = count; /* limit size */ + memcpy(dst, src, maxlen); /* first copy */ + + count -= maxlen; /* remaining bytes */ + if (count) { + dst = bdata; /* start of buffer */ + src += maxlen; /* new position */ + memcpy(dst, src, count); + } + bcs->tx_cnt -= bcs->tx_skb->len; + fcnt += bcs->tx_skb->len; + *z1t = new_z1; /* now send data */ + } else if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfcpci_fill_fifo_trans %d frame length %d discarded", + bcs->channel, bcs->tx_skb->len); + + if (test_bit(FLG_LLI_L1WAKEUP,&bcs->st->lli.flag) && + (PACKET_NOACK != bcs->tx_skb->pkt_type)) { + u_long flags; + spin_lock_irqsave(&bcs->aclock, flags); + bcs->ackcnt += bcs->tx_skb->len; + spin_unlock_irqrestore(&bcs->aclock, flags); + schedule_event(bcs, B_ACKPENDING); + } + + dev_kfree_skb_any(bcs->tx_skb); + bcs->tx_skb = skb_dequeue(&bcs->squeue); /* fetch next data */ + } + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + return; + } + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfcpci_fill_fifo_hdlc %d f1(%d) f2(%d) z1(f1)(%x)", + bcs->channel, bz->f1, bz->f2, + bz->za[bz->f1].z1); + + fcnt = bz->f1 - bz->f2; /* frame count actually buffered */ + if (fcnt < 0) + fcnt += (MAX_B_FRAMES + 1); /* if wrap around */ + if (fcnt > (MAX_B_FRAMES - 1)) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfcpci_fill_Bfifo more as 14 frames"); + return; + } + /* now determine free bytes in FIFO buffer */ + count = bz->za[bz->f2].z2 - bz->za[bz->f1].z1 - 1; + if (count <= 0) + count += B_FIFO_SIZE; /* count now contains available bytes */ + + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfcpci_fill_fifo %d count(%ld/%d),%lx", + bcs->channel, bcs->tx_skb->len, + count, current->state); + + if (count < bcs->tx_skb->len) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hfcpci_fill_fifo no fifo mem"); + return; + } + count = bcs->tx_skb->len; /* get frame len */ + new_z1 = bz->za[bz->f1].z1 + count; /* new buffer Position */ + if (new_z1 >= (B_FIFO_SIZE + B_SUB_VAL)) + new_z1 -= B_FIFO_SIZE; /* buffer wrap */ + + new_f1 = ((bz->f1 + 1) & MAX_B_FRAMES); + src = bcs->tx_skb->data; /* source pointer */ + dst = bdata + (bz->za[bz->f1].z1 - B_SUB_VAL); + maxlen = (B_FIFO_SIZE + B_SUB_VAL) - bz->za[bz->f1].z1; /* end fifo */ + if (maxlen > count) + maxlen = count; /* limit size */ + memcpy(dst, src, maxlen); /* first copy */ + + count -= maxlen; /* remaining bytes */ + if (count) { + dst = bdata; /* start of buffer */ + src += maxlen; /* new position */ + memcpy(dst, src, count); + } + bcs->tx_cnt -= bcs->tx_skb->len; + if (test_bit(FLG_LLI_L1WAKEUP,&bcs->st->lli.flag) && + (PACKET_NOACK != bcs->tx_skb->pkt_type)) { + u_long flags; + spin_lock_irqsave(&bcs->aclock, flags); + bcs->ackcnt += bcs->tx_skb->len; + spin_unlock_irqrestore(&bcs->aclock, flags); + schedule_event(bcs, B_ACKPENDING); + } + + bz->za[new_f1].z1 = new_z1; /* for next buffer */ + bz->f1 = new_f1; /* next frame */ + + dev_kfree_skb_any(bcs->tx_skb); + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + return; +} + +/**********************************************/ +/* D-channel l1 state call for leased NT-mode */ +/**********************************************/ +static void +dch_nt_l2l1(struct PStack *st, int pr, void *arg) +{ + struct IsdnCardState *cs = (struct IsdnCardState *) st->l1.hardware; + + switch (pr) { + case (PH_DATA | REQUEST): + case (PH_PULL | REQUEST): + case (PH_PULL | INDICATION): + st->l1.l1hw(st, pr, arg); + break; + case (PH_ACTIVATE | REQUEST): + st->l1.l1l2(st, PH_ACTIVATE | CONFIRM, NULL); + break; + case (PH_TESTLOOP | REQUEST): + if (1 & (long) arg) + debugl1(cs, "PH_TEST_LOOP B1"); + if (2 & (long) arg) + debugl1(cs, "PH_TEST_LOOP B2"); + if (!(3 & (long) arg)) + debugl1(cs, "PH_TEST_LOOP DISABLED"); + st->l1.l1hw(st, HW_TESTLOOP | REQUEST, arg); + break; + default: + if (cs->debug) + debugl1(cs, "dch_nt_l2l1 msg %04X unhandled", pr); + break; + } +} + + + +/***********************/ +/* set/reset echo mode */ +/***********************/ +static int +hfcpci_auxcmd(struct IsdnCardState *cs, isdn_ctrl * ic) +{ + u_long flags; + int i = *(unsigned int *) ic->parm.num; + + if ((ic->arg == 98) && + (!(cs->hw.hfcpci.int_m1 & (HFCPCI_INTS_B2TRANS + HFCPCI_INTS_B2REC + HFCPCI_INTS_B1TRANS + HFCPCI_INTS_B1REC)))) { + spin_lock_irqsave(&cs->lock, flags); + Write_hfc(cs, HFCPCI_CLKDEL, CLKDEL_NT); /* ST-Bit delay for NT-Mode */ + Write_hfc(cs, HFCPCI_STATES, HFCPCI_LOAD_STATE | 0); /* HFC ST G0 */ + udelay(10); + cs->hw.hfcpci.sctrl |= SCTRL_MODE_NT; + Write_hfc(cs, HFCPCI_SCTRL, cs->hw.hfcpci.sctrl); /* set NT-mode */ + udelay(10); + Write_hfc(cs, HFCPCI_STATES, HFCPCI_LOAD_STATE | 1); /* HFC ST G1 */ + udelay(10); + Write_hfc(cs, HFCPCI_STATES, 1 | HFCPCI_ACTIVATE | HFCPCI_DO_ACTION); + cs->dc.hfcpci.ph_state = 1; + cs->hw.hfcpci.nt_mode = 1; + cs->hw.hfcpci.nt_timer = 0; + cs->stlist->l2.l2l1 = dch_nt_l2l1; + spin_unlock_irqrestore(&cs->lock, flags); + debugl1(cs, "NT mode activated"); + return (0); + } + if ((cs->chanlimit > 1) || (cs->hw.hfcpci.bswapped) || + (cs->hw.hfcpci.nt_mode) || (ic->arg != 12)) + return (-EINVAL); + + spin_lock_irqsave(&cs->lock, flags); + if (i) { + cs->logecho = 1; + cs->hw.hfcpci.trm |= 0x20; /* enable echo chan */ + cs->hw.hfcpci.int_m1 |= HFCPCI_INTS_B2REC; + cs->hw.hfcpci.fifo_en |= HFCPCI_FIFOEN_B2RX; + } else { + cs->logecho = 0; + cs->hw.hfcpci.trm &= ~0x20; /* disable echo chan */ + cs->hw.hfcpci.int_m1 &= ~HFCPCI_INTS_B2REC; + cs->hw.hfcpci.fifo_en &= ~HFCPCI_FIFOEN_B2RX; + } + cs->hw.hfcpci.sctrl_r &= ~SCTRL_B2_ENA; + cs->hw.hfcpci.sctrl &= ~SCTRL_B2_ENA; + cs->hw.hfcpci.conn |= 0x10; /* B2-IOM -> B2-ST */ + cs->hw.hfcpci.ctmt &= ~2; + Write_hfc(cs, HFCPCI_CTMT, cs->hw.hfcpci.ctmt); + Write_hfc(cs, HFCPCI_SCTRL_R, cs->hw.hfcpci.sctrl_r); + Write_hfc(cs, HFCPCI_SCTRL, cs->hw.hfcpci.sctrl); + Write_hfc(cs, HFCPCI_CONNECT, cs->hw.hfcpci.conn); + Write_hfc(cs, HFCPCI_TRM, cs->hw.hfcpci.trm); + Write_hfc(cs, HFCPCI_FIFO_EN, cs->hw.hfcpci.fifo_en); + Write_hfc(cs, HFCPCI_INT_M1, cs->hw.hfcpci.int_m1); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); +} /* hfcpci_auxcmd */ + +/*****************************/ +/* E-channel receive routine */ +/*****************************/ +static void +receive_emsg(struct IsdnCardState *cs) +{ + int rcnt; + int receive, count = 5; + bzfifo_type *bz; + u_char *bdata; + z_type *zp; + u_char *ptr, *ptr1, new_f2; + int total, maxlen, new_z2; + u_char e_buffer[256]; + + bz = &((fifo_area *) (cs->hw.hfcpci.fifos))->b_chans.rxbz_b2; + bdata = ((fifo_area *) (cs->hw.hfcpci.fifos))->b_chans.rxdat_b2; + Begin: + count--; + if (test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + debugl1(cs, "echo_rec_data blocked"); + return; + } + if (bz->f1 != bz->f2) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "hfcpci e_rec f1(%d) f2(%d)", + bz->f1, bz->f2); + zp = &bz->za[bz->f2]; + + rcnt = zp->z1 - zp->z2; + if (rcnt < 0) + rcnt += B_FIFO_SIZE; + rcnt++; + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "hfcpci e_rec z1(%x) z2(%x) cnt(%d)", + zp->z1, zp->z2, rcnt); + new_z2 = zp->z2 + rcnt; /* new position in fifo */ + if (new_z2 >= (B_FIFO_SIZE + B_SUB_VAL)) + new_z2 -= B_FIFO_SIZE; /* buffer wrap */ + new_f2 = (bz->f2 + 1) & MAX_B_FRAMES; + if ((rcnt > 256 + 3) || (count < 4) || + (*(bdata + (zp->z1 - B_SUB_VAL)))) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "hfcpci_empty_echan: incoming packet invalid length %d or crc", rcnt); + bz->za[new_f2].z2 = new_z2; + bz->f2 = new_f2; /* next buffer */ + } else { + total = rcnt; + rcnt -= 3; + ptr = e_buffer; + + if (zp->z2 <= B_FIFO_SIZE + B_SUB_VAL) + maxlen = rcnt; /* complete transfer */ + else + maxlen = B_FIFO_SIZE + B_SUB_VAL - zp->z2; /* maximum */ + + ptr1 = bdata + (zp->z2 - B_SUB_VAL); /* start of data */ + memcpy(ptr, ptr1, maxlen); /* copy data */ + rcnt -= maxlen; + + if (rcnt) { /* rest remaining */ + ptr += maxlen; + ptr1 = bdata; /* start of buffer */ + memcpy(ptr, ptr1, rcnt); /* rest */ + } + bz->za[new_f2].z2 = new_z2; + bz->f2 = new_f2; /* next buffer */ + if (cs->debug & DEB_DLOG_HEX) { + ptr = cs->dlog; + if ((total - 3) < MAX_DLOG_SPACE / 3 - 10) { + *ptr++ = 'E'; + *ptr++ = 'C'; + *ptr++ = 'H'; + *ptr++ = 'O'; + *ptr++ = ':'; + ptr += QuickHex(ptr, e_buffer, total - 3); + ptr--; + *ptr++ = '\n'; + *ptr = 0; + HiSax_putstatus(cs, NULL, cs->dlog); + } else + HiSax_putstatus(cs, "LogEcho: ", "warning Frame too big (%d)", total - 3); + } + } + + rcnt = bz->f1 - bz->f2; + if (rcnt < 0) + rcnt += MAX_B_FRAMES + 1; + if (rcnt > 1) + receive = 1; + else + receive = 0; + } else + receive = 0; + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + if (count && receive) + goto Begin; + return; +} /* receive_emsg */ + +/*********************/ +/* Interrupt handler */ +/*********************/ +static irqreturn_t +hfcpci_interrupt(int intno, void *dev_id, struct pt_regs *regs) +{ + u_long flags; + struct IsdnCardState *cs = dev_id; + u_char exval; + struct BCState *bcs; + int count = 15; + u_char val, stat; + + if (!(cs->hw.hfcpci.int_m2 & 0x08)) { + debugl1(cs, "HFC-PCI: int_m2 %x not initialised", cs->hw.hfcpci.int_m2); + return IRQ_NONE; /* not initialised */ + } + spin_lock_irqsave(&cs->lock, flags); + if (HFCPCI_ANYINT & (stat = Read_hfc(cs, HFCPCI_STATUS))) { + val = Read_hfc(cs, HFCPCI_INT_S1); + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "HFC-PCI: stat(%02x) s1(%02x)", stat, val); + } else { + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_NONE; + } + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "HFC-PCI irq %x %s", val, + test_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags) ? + "locked" : "unlocked"); + val &= cs->hw.hfcpci.int_m1; + if (val & 0x40) { /* state machine irq */ + exval = Read_hfc(cs, HFCPCI_STATES) & 0xf; + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ph_state chg %d->%d", cs->dc.hfcpci.ph_state, + exval); + cs->dc.hfcpci.ph_state = exval; + sched_event_D_pci(cs, D_L1STATECHANGE); + val &= ~0x40; + } + if (val & 0x80) { /* timer irq */ + if (cs->hw.hfcpci.nt_mode) { + if ((--cs->hw.hfcpci.nt_timer) < 0) + sched_event_D_pci(cs, D_L1STATECHANGE); + } + val &= ~0x80; + Write_hfc(cs, HFCPCI_CTMT, cs->hw.hfcpci.ctmt | HFCPCI_CLTIMER); + } + while (val) { + if (test_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + cs->hw.hfcpci.int_s1 |= val; + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; + } + if (cs->hw.hfcpci.int_s1 & 0x18) { + exval = val; + val = cs->hw.hfcpci.int_s1; + cs->hw.hfcpci.int_s1 = exval; + } + if (val & 0x08) { + if (!(bcs = Sel_BCS(cs, cs->hw.hfcpci.bswapped ? 1 : 0))) { + if (cs->debug) + debugl1(cs, "hfcpci spurious 0x08 IRQ"); + } else + main_rec_hfcpci(bcs); + } + if (val & 0x10) { + if (cs->logecho) + receive_emsg(cs); + else if (!(bcs = Sel_BCS(cs, 1))) { + if (cs->debug) + debugl1(cs, "hfcpci spurious 0x10 IRQ"); + } else + main_rec_hfcpci(bcs); + } + if (val & 0x01) { + if (!(bcs = Sel_BCS(cs, cs->hw.hfcpci.bswapped ? 1 : 0))) { + if (cs->debug) + debugl1(cs, "hfcpci spurious 0x01 IRQ"); + } else { + if (bcs->tx_skb) { + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfcpci_fill_fifo(bcs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs, "fill_data %d blocked", bcs->channel); + } else { + if ((bcs->tx_skb = skb_dequeue(&bcs->squeue))) { + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfcpci_fill_fifo(bcs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs, "fill_data %d blocked", bcs->channel); + } else { + hfcpci_sched_event(bcs, B_XMTBUFREADY); + } + } + } + } + if (val & 0x02) { + if (!(bcs = Sel_BCS(cs, 1))) { + if (cs->debug) + debugl1(cs, "hfcpci spurious 0x02 IRQ"); + } else { + if (bcs->tx_skb) { + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfcpci_fill_fifo(bcs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs, "fill_data %d blocked", bcs->channel); + } else { + if ((bcs->tx_skb = skb_dequeue(&bcs->squeue))) { + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfcpci_fill_fifo(bcs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs, "fill_data %d blocked", bcs->channel); + } else { + hfcpci_sched_event(bcs, B_XMTBUFREADY); + } + } + } + } + if (val & 0x20) { /* receive dframe */ + receive_dmsg(cs); + } + if (val & 0x04) { /* dframe transmitted */ + if (test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) + del_timer(&cs->dbusytimer); + if (test_and_clear_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + sched_event_D_pci(cs, D_CLEARBUSY); + if (cs->tx_skb) { + if (cs->tx_skb->len) { + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfcpci_fill_dfifo(cs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else { + debugl1(cs, "hfcpci_fill_dfifo irq blocked"); + } + goto afterXPR; + } else { + dev_kfree_skb_irq(cs->tx_skb); + cs->tx_cnt = 0; + cs->tx_skb = NULL; + } + } + if ((cs->tx_skb = skb_dequeue(&cs->sq))) { + cs->tx_cnt = 0; + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfcpci_fill_dfifo(cs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else { + debugl1(cs, "hfcpci_fill_dfifo irq blocked"); + } + } else + sched_event_D_pci(cs, D_XMTBUFREADY); + } + afterXPR: + if (cs->hw.hfcpci.int_s1 && count--) { + val = cs->hw.hfcpci.int_s1; + cs->hw.hfcpci.int_s1 = 0; + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "HFC-PCI irq %x loop %d", val, 15 - count); + } else + val = 0; + } + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +/********************************************************************/ +/* timer callback for D-chan busy resolution. Currently no function */ +/********************************************************************/ +static void +hfcpci_dbusy_timer(struct IsdnCardState *cs) +{ +} + +/*************************************/ +/* Layer 1 D-channel hardware access */ +/*************************************/ +static void +HFCPCI_l1hw(struct PStack *st, int pr, void *arg) +{ + u_long flags; + struct IsdnCardState *cs = (struct IsdnCardState *) st->l1.hardware; + struct sk_buff *skb = arg; + + switch (pr) { + case (PH_DATA | REQUEST): + if (cs->debug & DEB_DLOG_HEX) + LogFrame(cs, skb->data, skb->len); + if (cs->debug & DEB_DLOG_VERBOSE) + dlogframe(cs, skb, 0); + spin_lock_irqsave(&cs->lock, flags); + if (cs->tx_skb) { + skb_queue_tail(&cs->sq, skb); +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "PH_DATA Queued", 0); +#endif + } else { + cs->tx_skb = skb; + cs->tx_cnt = 0; +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "PH_DATA", 0); +#endif + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfcpci_fill_dfifo(cs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs, "hfcpci_fill_dfifo blocked"); + + } + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (PH_PULL | INDICATION): + spin_lock_irqsave(&cs->lock, flags); + if (cs->tx_skb) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, " l2l1 tx_skb exist this shouldn't happen"); + skb_queue_tail(&cs->sq, skb); + spin_unlock_irqrestore(&cs->lock, flags); + break; + } + if (cs->debug & DEB_DLOG_HEX) + LogFrame(cs, skb->data, skb->len); + if (cs->debug & DEB_DLOG_VERBOSE) + dlogframe(cs, skb, 0); + cs->tx_skb = skb; + cs->tx_cnt = 0; +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "PH_DATA_PULLED", 0); +#endif + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfcpci_fill_dfifo(cs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs, "hfcpci_fill_dfifo blocked"); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (PH_PULL | REQUEST): +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + debugl1(cs, "-> PH_REQUEST_PULL"); +#endif + if (!cs->tx_skb) { + test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } else + test_and_set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + case (HW_RESET | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + Write_hfc(cs, HFCPCI_STATES, HFCPCI_LOAD_STATE | 3); /* HFC ST 3 */ + udelay(6); + Write_hfc(cs, HFCPCI_STATES, 3); /* HFC ST 2 */ + cs->hw.hfcpci.mst_m |= HFCPCI_MASTER; + Write_hfc(cs, HFCPCI_MST_MODE, cs->hw.hfcpci.mst_m); + Write_hfc(cs, HFCPCI_STATES, HFCPCI_ACTIVATE | HFCPCI_DO_ACTION); + spin_unlock_irqrestore(&cs->lock, flags); + l1_msg(cs, HW_POWERUP | CONFIRM, NULL); + break; + case (HW_ENABLE | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + Write_hfc(cs, HFCPCI_STATES, HFCPCI_DO_ACTION); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (HW_DEACTIVATE | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + cs->hw.hfcpci.mst_m &= ~HFCPCI_MASTER; + Write_hfc(cs, HFCPCI_MST_MODE, cs->hw.hfcpci.mst_m); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (HW_INFO3 | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + cs->hw.hfcpci.mst_m |= HFCPCI_MASTER; + Write_hfc(cs, HFCPCI_MST_MODE, cs->hw.hfcpci.mst_m); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (HW_TESTLOOP | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + switch ((int) arg) { + case (1): + Write_hfc(cs, HFCPCI_B1_SSL, 0x80); /* tx slot */ + Write_hfc(cs, HFCPCI_B1_RSL, 0x80); /* rx slot */ + cs->hw.hfcpci.conn = (cs->hw.hfcpci.conn & ~7) | 1; + Write_hfc(cs, HFCPCI_CONNECT, cs->hw.hfcpci.conn); + break; + + case (2): + Write_hfc(cs, HFCPCI_B2_SSL, 0x81); /* tx slot */ + Write_hfc(cs, HFCPCI_B2_RSL, 0x81); /* rx slot */ + cs->hw.hfcpci.conn = (cs->hw.hfcpci.conn & ~0x38) | 0x08; + Write_hfc(cs, HFCPCI_CONNECT, cs->hw.hfcpci.conn); + break; + + default: + spin_unlock_irqrestore(&cs->lock, flags); + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "hfcpci_l1hw loop invalid %4x", (int) arg); + return; + } + cs->hw.hfcpci.trm |= 0x80; /* enable IOM-loop */ + Write_hfc(cs, HFCPCI_TRM, cs->hw.hfcpci.trm); + spin_unlock_irqrestore(&cs->lock, flags); + break; + default: + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "hfcpci_l1hw unknown pr %4x", pr); + break; + } +} + +/***********************************************/ +/* called during init setting l1 stack pointer */ +/***********************************************/ +void +setstack_hfcpci(struct PStack *st, struct IsdnCardState *cs) +{ + st->l1.l1hw = HFCPCI_l1hw; +} + +/**************************************/ +/* send B-channel data if not blocked */ +/**************************************/ +static void +hfcpci_send_data(struct BCState *bcs) +{ + struct IsdnCardState *cs = bcs->cs; + + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfcpci_fill_fifo(bcs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs, "send_data %d blocked", bcs->channel); +} + +/***************************************************************/ +/* activate/deactivate hardware for selected channels and mode */ +/***************************************************************/ +void +mode_hfcpci(struct BCState *bcs, int mode, int bc) +{ + struct IsdnCardState *cs = bcs->cs; + int fifo2; + + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HFCPCI bchannel mode %d bchan %d/%d", + mode, bc, bcs->channel); + bcs->mode = mode; + bcs->channel = bc; + fifo2 = bc; + if (cs->chanlimit > 1) { + cs->hw.hfcpci.bswapped = 0; /* B1 and B2 normal mode */ + cs->hw.hfcpci.sctrl_e &= ~0x80; + } else { + if (bc) { + if (mode != L1_MODE_NULL) { + cs->hw.hfcpci.bswapped = 1; /* B1 and B2 exchanged */ + cs->hw.hfcpci.sctrl_e |= 0x80; + } else { + cs->hw.hfcpci.bswapped = 0; /* B1 and B2 normal mode */ + cs->hw.hfcpci.sctrl_e &= ~0x80; + } + fifo2 = 0; + } else { + cs->hw.hfcpci.bswapped = 0; /* B1 and B2 normal mode */ + cs->hw.hfcpci.sctrl_e &= ~0x80; + } + } + switch (mode) { + case (L1_MODE_NULL): + if (bc) { + cs->hw.hfcpci.sctrl &= ~SCTRL_B2_ENA; + cs->hw.hfcpci.sctrl_r &= ~SCTRL_B2_ENA; + } else { + cs->hw.hfcpci.sctrl &= ~SCTRL_B1_ENA; + cs->hw.hfcpci.sctrl_r &= ~SCTRL_B1_ENA; + } + if (fifo2) { + cs->hw.hfcpci.fifo_en &= ~HFCPCI_FIFOEN_B2; + cs->hw.hfcpci.int_m1 &= ~(HFCPCI_INTS_B2TRANS + HFCPCI_INTS_B2REC); + } else { + cs->hw.hfcpci.fifo_en &= ~HFCPCI_FIFOEN_B1; + cs->hw.hfcpci.int_m1 &= ~(HFCPCI_INTS_B1TRANS + HFCPCI_INTS_B1REC); + } + break; + case (L1_MODE_TRANS): + hfcpci_clear_fifo_rx(cs, fifo2); + hfcpci_clear_fifo_tx(cs, fifo2); + if (bc) { + cs->hw.hfcpci.sctrl |= SCTRL_B2_ENA; + cs->hw.hfcpci.sctrl_r |= SCTRL_B2_ENA; + } else { + cs->hw.hfcpci.sctrl |= SCTRL_B1_ENA; + cs->hw.hfcpci.sctrl_r |= SCTRL_B1_ENA; + } + if (fifo2) { + cs->hw.hfcpci.fifo_en |= HFCPCI_FIFOEN_B2; + cs->hw.hfcpci.int_m1 |= (HFCPCI_INTS_B2TRANS + HFCPCI_INTS_B2REC); + cs->hw.hfcpci.ctmt |= 2; + cs->hw.hfcpci.conn &= ~0x18; + } else { + cs->hw.hfcpci.fifo_en |= HFCPCI_FIFOEN_B1; + cs->hw.hfcpci.int_m1 |= (HFCPCI_INTS_B1TRANS + HFCPCI_INTS_B1REC); + cs->hw.hfcpci.ctmt |= 1; + cs->hw.hfcpci.conn &= ~0x03; + } + break; + case (L1_MODE_HDLC): + hfcpci_clear_fifo_rx(cs, fifo2); + hfcpci_clear_fifo_tx(cs, fifo2); + if (bc) { + cs->hw.hfcpci.sctrl |= SCTRL_B2_ENA; + cs->hw.hfcpci.sctrl_r |= SCTRL_B2_ENA; + } else { + cs->hw.hfcpci.sctrl |= SCTRL_B1_ENA; + cs->hw.hfcpci.sctrl_r |= SCTRL_B1_ENA; + } + if (fifo2) { + cs->hw.hfcpci.last_bfifo_cnt[1] = 0; + cs->hw.hfcpci.fifo_en |= HFCPCI_FIFOEN_B2; + cs->hw.hfcpci.int_m1 |= (HFCPCI_INTS_B2TRANS + HFCPCI_INTS_B2REC); + cs->hw.hfcpci.ctmt &= ~2; + cs->hw.hfcpci.conn &= ~0x18; + } else { + cs->hw.hfcpci.last_bfifo_cnt[0] = 0; + cs->hw.hfcpci.fifo_en |= HFCPCI_FIFOEN_B1; + cs->hw.hfcpci.int_m1 |= (HFCPCI_INTS_B1TRANS + HFCPCI_INTS_B1REC); + cs->hw.hfcpci.ctmt &= ~1; + cs->hw.hfcpci.conn &= ~0x03; + } + break; + case (L1_MODE_EXTRN): + if (bc) { + cs->hw.hfcpci.conn |= 0x10; + cs->hw.hfcpci.sctrl |= SCTRL_B2_ENA; + cs->hw.hfcpci.sctrl_r |= SCTRL_B2_ENA; + cs->hw.hfcpci.fifo_en &= ~HFCPCI_FIFOEN_B2; + cs->hw.hfcpci.int_m1 &= ~(HFCPCI_INTS_B2TRANS + HFCPCI_INTS_B2REC); + } else { + cs->hw.hfcpci.conn |= 0x02; + cs->hw.hfcpci.sctrl |= SCTRL_B1_ENA; + cs->hw.hfcpci.sctrl_r |= SCTRL_B1_ENA; + cs->hw.hfcpci.fifo_en &= ~HFCPCI_FIFOEN_B1; + cs->hw.hfcpci.int_m1 &= ~(HFCPCI_INTS_B1TRANS + HFCPCI_INTS_B1REC); + } + break; + } + Write_hfc(cs, HFCPCI_SCTRL_E, cs->hw.hfcpci.sctrl_e); + Write_hfc(cs, HFCPCI_INT_M1, cs->hw.hfcpci.int_m1); + Write_hfc(cs, HFCPCI_FIFO_EN, cs->hw.hfcpci.fifo_en); + Write_hfc(cs, HFCPCI_SCTRL, cs->hw.hfcpci.sctrl); + Write_hfc(cs, HFCPCI_SCTRL_R, cs->hw.hfcpci.sctrl_r); + Write_hfc(cs, HFCPCI_CTMT, cs->hw.hfcpci.ctmt); + Write_hfc(cs, HFCPCI_CONNECT, cs->hw.hfcpci.conn); +} + +/******************************/ +/* Layer2 -> Layer 1 Transfer */ +/******************************/ +static void +hfcpci_l2l1(struct PStack *st, int pr, void *arg) +{ + struct BCState *bcs = st->l1.bcs; + u_long flags; + struct sk_buff *skb = arg; + + switch (pr) { + case (PH_DATA | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + skb_queue_tail(&bcs->squeue, skb); + } else { + bcs->tx_skb = skb; +// test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->cs->BC_Send_Data(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | INDICATION): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + spin_unlock_irqrestore(&bcs->cs->lock, flags); + printk(KERN_WARNING "hfc_l2l1: this shouldn't happen\n"); + break; + } +// test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->tx_skb = skb; + bcs->cs->BC_Send_Data(bcs); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | REQUEST): + if (!bcs->tx_skb) { + test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } else + test_and_set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + case (PH_ACTIVATE | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + test_and_set_bit(BC_FLG_ACTIV, &bcs->Flag); + mode_hfcpci(bcs, st->l1.mode, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | REQUEST): + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | CONFIRM): + spin_lock_irqsave(&bcs->cs->lock, flags); + test_and_clear_bit(BC_FLG_ACTIV, &bcs->Flag); + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + mode_hfcpci(bcs, 0, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + st->l1.l1l2(st, PH_DEACTIVATE | CONFIRM, NULL); + break; + } +} + +/******************************************/ +/* deactivate B-channel access and queues */ +/******************************************/ +static void +close_hfcpci(struct BCState *bcs) +{ + mode_hfcpci(bcs, 0, bcs->channel); + if (test_and_clear_bit(BC_FLG_INIT, &bcs->Flag)) { + skb_queue_purge(&bcs->rqueue); + skb_queue_purge(&bcs->squeue); + if (bcs->tx_skb) { + dev_kfree_skb_any(bcs->tx_skb); + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + } + } +} + +/*************************************/ +/* init B-channel queues and control */ +/*************************************/ +static int +open_hfcpcistate(struct IsdnCardState *cs, struct BCState *bcs) +{ + if (!test_and_set_bit(BC_FLG_INIT, &bcs->Flag)) { + skb_queue_head_init(&bcs->rqueue); + skb_queue_head_init(&bcs->squeue); + } + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->event = 0; + bcs->tx_cnt = 0; + return (0); +} + +/*********************************/ +/* inits the stack for B-channel */ +/*********************************/ +static int +setstack_2b(struct PStack *st, struct BCState *bcs) +{ + bcs->channel = st->l1.bc; + if (open_hfcpcistate(st->l1.hardware, bcs)) + return (-1); + st->l1.bcs = bcs; + st->l2.l2l1 = hfcpci_l2l1; + setstack_manager(st); + bcs->st = st; + setstack_l1_B(st); + return (0); +} + +/***************************/ +/* handle L1 state changes */ +/***************************/ +static void +hfcpci_bh(struct IsdnCardState *cs) +{ + u_long flags; +// struct PStack *stptr; + + if (!cs) + return; + if (test_and_clear_bit(D_L1STATECHANGE, &cs->event)) { + if (!cs->hw.hfcpci.nt_mode) + switch (cs->dc.hfcpci.ph_state) { + case (0): + l1_msg(cs, HW_RESET | INDICATION, NULL); + break; + case (3): + l1_msg(cs, HW_DEACTIVATE | INDICATION, NULL); + break; + case (8): + l1_msg(cs, HW_RSYNC | INDICATION, NULL); + break; + case (6): + l1_msg(cs, HW_INFO2 | INDICATION, NULL); + break; + case (7): + l1_msg(cs, HW_INFO4_P8 | INDICATION, NULL); + break; + default: + break; + } else { + spin_lock_irqsave(&cs->lock, flags); + switch (cs->dc.hfcpci.ph_state) { + case (2): + if (cs->hw.hfcpci.nt_timer < 0) { + cs->hw.hfcpci.nt_timer = 0; + cs->hw.hfcpci.int_m1 &= ~HFCPCI_INTS_TIMER; + Write_hfc(cs, HFCPCI_INT_M1, cs->hw.hfcpci.int_m1); + /* Clear already pending ints */ + if (Read_hfc(cs, HFCPCI_INT_S1)); + Write_hfc(cs, HFCPCI_STATES, 4 | HFCPCI_LOAD_STATE); + udelay(10); + Write_hfc(cs, HFCPCI_STATES, 4); + cs->dc.hfcpci.ph_state = 4; + } else { + cs->hw.hfcpci.int_m1 |= HFCPCI_INTS_TIMER; + Write_hfc(cs, HFCPCI_INT_M1, cs->hw.hfcpci.int_m1); + cs->hw.hfcpci.ctmt &= ~HFCPCI_AUTO_TIMER; + cs->hw.hfcpci.ctmt |= HFCPCI_TIM3_125; + Write_hfc(cs, HFCPCI_CTMT, cs->hw.hfcpci.ctmt | HFCPCI_CLTIMER); + Write_hfc(cs, HFCPCI_CTMT, cs->hw.hfcpci.ctmt | HFCPCI_CLTIMER); + cs->hw.hfcpci.nt_timer = NT_T1_COUNT; + Write_hfc(cs, HFCPCI_STATES, 2 | HFCPCI_NT_G2_G3); /* allow G2 -> G3 transition */ + } + break; + case (1): + case (3): + case (4): + cs->hw.hfcpci.nt_timer = 0; + cs->hw.hfcpci.int_m1 &= ~HFCPCI_INTS_TIMER; + Write_hfc(cs, HFCPCI_INT_M1, cs->hw.hfcpci.int_m1); + break; + default: + break; + } + spin_unlock_irqrestore(&cs->lock, flags); + } + } + if (test_and_clear_bit(D_RCVBUFREADY, &cs->event)) + DChannel_proc_rcv(cs); + if (test_and_clear_bit(D_XMTBUFREADY, &cs->event)) + DChannel_proc_xmt(cs); +} + + +/********************************/ +/* called for card init message */ +/********************************/ +void __init +inithfcpci(struct IsdnCardState *cs) +{ + cs->bcs[0].BC_SetStack = setstack_2b; + cs->bcs[1].BC_SetStack = setstack_2b; + cs->bcs[0].BC_Close = close_hfcpci; + cs->bcs[1].BC_Close = close_hfcpci; + cs->dbusytimer.function = (void *) hfcpci_dbusy_timer; + cs->dbusytimer.data = (long) cs; + init_timer(&cs->dbusytimer); + mode_hfcpci(cs->bcs, 0, 0); + mode_hfcpci(cs->bcs + 1, 0, 1); +} + + + +/*******************************************/ +/* handle card messages from control layer */ +/*******************************************/ +static int +hfcpci_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "HFCPCI: card_msg %x", mt); + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + reset_hfcpci(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_RELEASE: + release_io_hfcpci(cs); + return (0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + inithfcpci(cs); + reset_hfcpci(cs); + spin_unlock_irqrestore(&cs->lock, flags); + msleep(80); /* Timeout 80ms */ + /* now switch timer interrupt off */ + spin_lock_irqsave(&cs->lock, flags); + cs->hw.hfcpci.int_m1 &= ~HFCPCI_INTS_TIMER; + Write_hfc(cs, HFCPCI_INT_M1, cs->hw.hfcpci.int_m1); + /* reinit mode reg */ + Write_hfc(cs, HFCPCI_MST_MODE, cs->hw.hfcpci.mst_m); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_TEST: + return (0); + } + return (0); +} + + +/* this variable is used as card index when more than one cards are present */ +static struct pci_dev *dev_hfcpci __initdata = NULL; + +#endif /* CONFIG_PCI */ + +int __init +setup_hfcpci(struct IsdnCard *card) +{ + u_long flags; + struct IsdnCardState *cs = card->cs; + char tmp[64]; + int i; + struct pci_dev *tmp_hfcpci = NULL; + +#ifdef __BIG_ENDIAN +#error "not running on big endian machines now" +#endif + strcpy(tmp, hfcpci_revision); + printk(KERN_INFO "HiSax: HFC-PCI driver Rev. %s\n", HiSax_getrev(tmp)); +#ifdef CONFIG_PCI + cs->hw.hfcpci.int_s1 = 0; + cs->dc.hfcpci.ph_state = 0; + cs->hw.hfcpci.fifo = 255; + if (cs->typ == ISDN_CTYPE_HFC_PCI) { + i = 0; + while (id_list[i].vendor_id) { + tmp_hfcpci = pci_find_device(id_list[i].vendor_id, + id_list[i].device_id, + dev_hfcpci); + i++; + if (tmp_hfcpci) { + if (pci_enable_device(tmp_hfcpci)) + continue; + pci_set_master(tmp_hfcpci); + if ((card->para[0]) && (card->para[0] != (tmp_hfcpci->resource[ 0].start & PCI_BASE_ADDRESS_IO_MASK))) + continue; + else + break; + } + } + + if (tmp_hfcpci) { + i--; + dev_hfcpci = tmp_hfcpci; /* old device */ + cs->hw.hfcpci.dev = dev_hfcpci; + cs->irq = dev_hfcpci->irq; + if (!cs->irq) { + printk(KERN_WARNING "HFC-PCI: No IRQ for PCI card found\n"); + return (0); + } + cs->hw.hfcpci.pci_io = (char *) dev_hfcpci->resource[ 1].start; + printk(KERN_INFO "HiSax: HFC-PCI card manufacturer: %s card name: %s\n", id_list[i].vendor_name, id_list[i].card_name); + } else { + printk(KERN_WARNING "HFC-PCI: No PCI card found\n"); + return (0); + } + if (!cs->hw.hfcpci.pci_io) { + printk(KERN_WARNING "HFC-PCI: No IO-Mem for PCI card found\n"); + return (0); + } + /* Allocate memory for FIFOS */ + /* Because the HFC-PCI needs a 32K physical alignment, we */ + /* need to allocate the double mem and align the address */ + if (!(cs->hw.hfcpci.share_start = kmalloc(65536, GFP_KERNEL))) { + printk(KERN_WARNING "HFC-PCI: Error allocating memory for FIFO!\n"); + return 0; + } + cs->hw.hfcpci.fifos = (void *) + (((ulong) cs->hw.hfcpci.share_start) & ~0x7FFF) + 0x8000; + pci_write_config_dword(cs->hw.hfcpci.dev, 0x80, (u_int) virt_to_bus(cs->hw.hfcpci.fifos)); + cs->hw.hfcpci.pci_io = ioremap((ulong) cs->hw.hfcpci.pci_io, 256); + printk(KERN_INFO + "HFC-PCI: defined at mem %#x fifo %#x(%#x) IRQ %d HZ %d\n", + (u_int) cs->hw.hfcpci.pci_io, + (u_int) cs->hw.hfcpci.fifos, + (u_int) virt_to_bus(cs->hw.hfcpci.fifos), + cs->irq, HZ); + spin_lock_irqsave(&cs->lock, flags); + pci_write_config_word(cs->hw.hfcpci.dev, PCI_COMMAND, PCI_ENA_MEMIO); /* enable memory mapped ports, disable busmaster */ + cs->hw.hfcpci.int_m2 = 0; /* disable alle interrupts */ + cs->hw.hfcpci.int_m1 = 0; + Write_hfc(cs, HFCPCI_INT_M1, cs->hw.hfcpci.int_m1); + Write_hfc(cs, HFCPCI_INT_M2, cs->hw.hfcpci.int_m2); + /* At this point the needed PCI config is done */ + /* fifos are still not enabled */ + INIT_WORK(&cs->tqueue, (void *)(void *) hfcpci_bh, cs); + cs->setstack_d = setstack_hfcpci; + cs->BC_Send_Data = &hfcpci_send_data; + cs->readisac = NULL; + cs->writeisac = NULL; + cs->readisacfifo = NULL; + cs->writeisacfifo = NULL; + cs->BC_Read_Reg = NULL; + cs->BC_Write_Reg = NULL; + cs->irq_func = &hfcpci_interrupt; + cs->irq_flags |= SA_SHIRQ; + cs->hw.hfcpci.timer.function = (void *) hfcpci_Timer; + cs->hw.hfcpci.timer.data = (long) cs; + init_timer(&cs->hw.hfcpci.timer); + cs->cardmsg = &hfcpci_card_msg; + cs->auxcmd = &hfcpci_auxcmd; + spin_unlock_irqrestore(&cs->lock, flags); + return (1); + } else + return (0); /* no valid card type */ +#else + printk(KERN_WARNING "HFC-PCI: NO_PCI_BIOS\n"); + return (0); +#endif /* CONFIG_PCI */ +} diff --git a/drivers/isdn/hisax/hfc_pci.h b/drivers/isdn/hisax/hfc_pci.h new file mode 100644 index 000000000000..4df036ed1af0 --- /dev/null +++ b/drivers/isdn/hisax/hfc_pci.h @@ -0,0 +1,236 @@ +/* $Id: hfc_pci.h,v 1.10.2.2 2004/01/12 22:52:26 keil Exp $ + * + * specific defines for CCD's HFC 2BDS0 PCI chips + * + * Author Werner Cornelius + * Copyright by Werner Cornelius <werner@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +/*********************************************/ +/* thresholds for transparent B-channel mode */ +/* change mask and threshold simultaneously */ +/*********************************************/ +#define HFCPCI_BTRANS_THRESHOLD 128 +#define HFCPCI_BTRANS_THRESMASK 0x00 + + + +/* defines for PCI config */ + +#define PCI_ENA_MEMIO 0x02 +#define PCI_ENA_MASTER 0x04 + + +/* GCI/IOM bus monitor registers */ + +#define HCFPCI_C_I 0x08 +#define HFCPCI_TRxR 0x0C +#define HFCPCI_MON1_D 0x28 +#define HFCPCI_MON2_D 0x2C + + +/* GCI/IOM bus timeslot registers */ + +#define HFCPCI_B1_SSL 0x80 +#define HFCPCI_B2_SSL 0x84 +#define HFCPCI_AUX1_SSL 0x88 +#define HFCPCI_AUX2_SSL 0x8C +#define HFCPCI_B1_RSL 0x90 +#define HFCPCI_B2_RSL 0x94 +#define HFCPCI_AUX1_RSL 0x98 +#define HFCPCI_AUX2_RSL 0x9C + +/* GCI/IOM bus data registers */ + +#define HFCPCI_B1_D 0xA0 +#define HFCPCI_B2_D 0xA4 +#define HFCPCI_AUX1_D 0xA8 +#define HFCPCI_AUX2_D 0xAC + +/* GCI/IOM bus configuration registers */ + +#define HFCPCI_MST_EMOD 0xB4 +#define HFCPCI_MST_MODE 0xB8 +#define HFCPCI_CONNECT 0xBC + + +/* Interrupt and status registers */ + +#define HFCPCI_FIFO_EN 0x44 +#define HFCPCI_TRM 0x48 +#define HFCPCI_B_MODE 0x4C +#define HFCPCI_CHIP_ID 0x58 +#define HFCPCI_CIRM 0x60 +#define HFCPCI_CTMT 0x64 +#define HFCPCI_INT_M1 0x68 +#define HFCPCI_INT_M2 0x6C +#define HFCPCI_INT_S1 0x78 +#define HFCPCI_INT_S2 0x7C +#define HFCPCI_STATUS 0x70 + +/* S/T section registers */ + +#define HFCPCI_STATES 0xC0 +#define HFCPCI_SCTRL 0xC4 +#define HFCPCI_SCTRL_E 0xC8 +#define HFCPCI_SCTRL_R 0xCC +#define HFCPCI_SQ 0xD0 +#define HFCPCI_CLKDEL 0xDC +#define HFCPCI_B1_REC 0xF0 +#define HFCPCI_B1_SEND 0xF0 +#define HFCPCI_B2_REC 0xF4 +#define HFCPCI_B2_SEND 0xF4 +#define HFCPCI_D_REC 0xF8 +#define HFCPCI_D_SEND 0xF8 +#define HFCPCI_E_REC 0xFC + + +/* bits in status register (READ) */ +#define HFCPCI_PCI_PROC 0x02 +#define HFCPCI_NBUSY 0x04 +#define HFCPCI_TIMER_ELAP 0x10 +#define HFCPCI_STATINT 0x20 +#define HFCPCI_FRAMEINT 0x40 +#define HFCPCI_ANYINT 0x80 + +/* bits in CTMT (Write) */ +#define HFCPCI_CLTIMER 0x80 +#define HFCPCI_TIM3_125 0x04 +#define HFCPCI_TIM25 0x10 +#define HFCPCI_TIM50 0x14 +#define HFCPCI_TIM400 0x18 +#define HFCPCI_TIM800 0x1C +#define HFCPCI_AUTO_TIMER 0x20 +#define HFCPCI_TRANSB2 0x02 +#define HFCPCI_TRANSB1 0x01 + +/* bits in CIRM (Write) */ +#define HFCPCI_AUX_MSK 0x07 +#define HFCPCI_RESET 0x08 +#define HFCPCI_B1_REV 0x40 +#define HFCPCI_B2_REV 0x80 + +/* bits in INT_M1 and INT_S1 */ +#define HFCPCI_INTS_B1TRANS 0x01 +#define HFCPCI_INTS_B2TRANS 0x02 +#define HFCPCI_INTS_DTRANS 0x04 +#define HFCPCI_INTS_B1REC 0x08 +#define HFCPCI_INTS_B2REC 0x10 +#define HFCPCI_INTS_DREC 0x20 +#define HFCPCI_INTS_L1STATE 0x40 +#define HFCPCI_INTS_TIMER 0x80 + +/* bits in INT_M2 */ +#define HFCPCI_PROC_TRANS 0x01 +#define HFCPCI_GCI_I_CHG 0x02 +#define HFCPCI_GCI_MON_REC 0x04 +#define HFCPCI_IRQ_ENABLE 0x08 +#define HFCPCI_PMESEL 0x80 + +/* bits in STATES */ +#define HFCPCI_STATE_MSK 0x0F +#define HFCPCI_LOAD_STATE 0x10 +#define HFCPCI_ACTIVATE 0x20 +#define HFCPCI_DO_ACTION 0x40 +#define HFCPCI_NT_G2_G3 0x80 + +/* bits in HFCD_MST_MODE */ +#define HFCPCI_MASTER 0x01 +#define HFCPCI_SLAVE 0x00 +/* remaining bits are for codecs control */ + +/* bits in HFCD_SCTRL */ +#define SCTRL_B1_ENA 0x01 +#define SCTRL_B2_ENA 0x02 +#define SCTRL_MODE_TE 0x00 +#define SCTRL_MODE_NT 0x04 +#define SCTRL_LOW_PRIO 0x08 +#define SCTRL_SQ_ENA 0x10 +#define SCTRL_TEST 0x20 +#define SCTRL_NONE_CAP 0x40 +#define SCTRL_PWR_DOWN 0x80 + +/* bits in SCTRL_E */ +#define HFCPCI_AUTO_AWAKE 0x01 +#define HFCPCI_DBIT_1 0x04 +#define HFCPCI_IGNORE_COL 0x08 +#define HFCPCI_CHG_B1_B2 0x80 + +/****************************/ +/* bits in FIFO_EN register */ +/****************************/ +#define HFCPCI_FIFOEN_B1 0x03 +#define HFCPCI_FIFOEN_B2 0x0C +#define HFCPCI_FIFOEN_DTX 0x10 +#define HFCPCI_FIFOEN_B1TX 0x01 +#define HFCPCI_FIFOEN_B1RX 0x02 +#define HFCPCI_FIFOEN_B2TX 0x04 +#define HFCPCI_FIFOEN_B2RX 0x08 + + +/***********************************/ +/* definitions of fifo memory area */ +/***********************************/ +#define MAX_D_FRAMES 15 +#define MAX_B_FRAMES 31 +#define B_SUB_VAL 0x200 +#define B_FIFO_SIZE (0x2000 - B_SUB_VAL) +#define D_FIFO_SIZE 512 +#define D_FREG_MASK 0xF + +typedef struct { + unsigned short z1; /* Z1 pointer 16 Bit */ + unsigned short z2; /* Z2 pointer 16 Bit */ + } z_type; + +typedef struct { + u_char data[D_FIFO_SIZE]; /* FIFO data space */ + u_char fill1[0x20A0-D_FIFO_SIZE]; /* reserved, do not use */ + u_char f1,f2; /* f pointers */ + u_char fill2[0x20C0-0x20A2]; /* reserved, do not use */ + z_type za[MAX_D_FRAMES+1]; /* mask index with D_FREG_MASK for access */ + u_char fill3[0x4000-0x2100]; /* align 16K */ + } dfifo_type; + +typedef struct { + z_type za[MAX_B_FRAMES+1]; /* only range 0x0..0x1F allowed */ + u_char f1,f2; /* f pointers */ + u_char fill[0x2100-0x2082]; /* alignment */ + } bzfifo_type; + + +typedef union { + struct { + dfifo_type d_tx; /* D-send channel */ + dfifo_type d_rx; /* D-receive channel */ + } d_chan; + struct { + u_char fill1[0x200]; + u_char txdat_b1[B_FIFO_SIZE]; + bzfifo_type txbz_b1; + + bzfifo_type txbz_b2; + u_char txdat_b2[B_FIFO_SIZE]; + + u_char fill2[D_FIFO_SIZE]; + + u_char rxdat_b1[B_FIFO_SIZE]; + bzfifo_type rxbz_b1; + + bzfifo_type rxbz_b2; + u_char rxdat_b2[B_FIFO_SIZE]; + } b_chans; + u_char fill[32768]; + } fifo_area; + + +#define Write_hfc(a,b,c) (*(((u_char *)a->hw.hfcpci.pci_io)+b) = c) +#define Read_hfc(a,b) (*(((u_char *)a->hw.hfcpci.pci_io)+b)) + +extern void main_irq_hcpci(struct BCState *bcs); +extern void inithfcpci(struct IsdnCardState *cs); +extern void releasehfcpci(struct IsdnCardState *cs); diff --git a/drivers/isdn/hisax/hfc_sx.c b/drivers/isdn/hisax/hfc_sx.c new file mode 100644 index 000000000000..a307fcb6c634 --- /dev/null +++ b/drivers/isdn/hisax/hfc_sx.c @@ -0,0 +1,1521 @@ +/* $Id: hfc_sx.c,v 1.12.2.5 2004/02/11 13:21:33 keil Exp $ + * + * level driver for Cologne Chip Designs hfc-s+/sp based cards + * + * Author Werner Cornelius + * based on existing driver for CCD HFC PCI cards + * Copyright by Werner Cornelius <werner@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/init.h> +#include "hisax.h" +#include "hfc_sx.h" +#include "isdnl1.h" +#include <linux/interrupt.h> +#include <linux/isapnp.h> + +extern const char *CardType[]; + +static const char *hfcsx_revision = "$Revision: 1.12.2.5 $"; + +/***************************************/ +/* IRQ-table for CCDs demo board */ +/* IRQs 6,5,10,11,12,15 are supported */ +/***************************************/ + +/* Teles 16.3c Vendor Id TAG2620, Version 1.0, Vendor version 2.1 + * + * Thanks to Uwe Wisniewski + * + * ISA-SLOT Signal PIN + * B25 IRQ3 92 IRQ_G + * B23 IRQ5 94 IRQ_A + * B4 IRQ2/9 95 IRQ_B + * D3 IRQ10 96 IRQ_C + * D4 IRQ11 97 IRQ_D + * D5 IRQ12 98 IRQ_E + * D6 IRQ15 99 IRQ_F + */ + +#undef CCD_DEMO_BOARD +#ifdef CCD_DEMO_BOARD +static u_char ccd_sp_irqtab[16] = { + 0,0,0,0,0,2,1,0,0,0,3,4,5,0,0,6 +}; +#else /* Teles 16.3c */ +static u_char ccd_sp_irqtab[16] = { + 0,0,0,7,0,1,0,0,0,2,3,4,5,0,0,6 +}; +#endif +#define NT_T1_COUNT 20 /* number of 3.125ms interrupts for G2 timeout */ + +#define byteout(addr,val) outb(val,addr) +#define bytein(addr) inb(addr) + +/******************************/ +/* In/Out access to registers */ +/******************************/ +static inline void +Write_hfc(struct IsdnCardState *cs, u_char regnum, u_char val) +{ + byteout(cs->hw.hfcsx.base+1, regnum); + byteout(cs->hw.hfcsx.base, val); +} + +static inline u_char +Read_hfc(struct IsdnCardState *cs, u_char regnum) +{ + u_char ret; + + byteout(cs->hw.hfcsx.base+1, regnum); + ret = bytein(cs->hw.hfcsx.base); + return(ret); +} + + +/**************************************************/ +/* select a fifo and remember which one for reuse */ +/**************************************************/ +static void +fifo_select(struct IsdnCardState *cs, u_char fifo) +{ + if (fifo == cs->hw.hfcsx.last_fifo) + return; /* still valid */ + + byteout(cs->hw.hfcsx.base+1, HFCSX_FIF_SEL); + byteout(cs->hw.hfcsx.base, fifo); + while (bytein(cs->hw.hfcsx.base+1) & 1); /* wait for busy */ + udelay(4); + byteout(cs->hw.hfcsx.base, fifo); + while (bytein(cs->hw.hfcsx.base+1) & 1); /* wait for busy */ +} + +/******************************************/ +/* reset the specified fifo to defaults. */ +/* If its a send fifo init needed markers */ +/******************************************/ +static void +reset_fifo(struct IsdnCardState *cs, u_char fifo) +{ + fifo_select(cs, fifo); /* first select the fifo */ + byteout(cs->hw.hfcsx.base+1, HFCSX_CIRM); + byteout(cs->hw.hfcsx.base, cs->hw.hfcsx.cirm | 0x80); /* reset cmd */ + udelay(1); + while (bytein(cs->hw.hfcsx.base+1) & 1); /* wait for busy */ +} + + +/*************************************************************/ +/* write_fifo writes the skb contents to the desired fifo */ +/* if no space is available or an error occurs 0 is returned */ +/* the skb is not released in any way. */ +/*************************************************************/ +static int +write_fifo(struct IsdnCardState *cs, struct sk_buff *skb, u_char fifo, int trans_max) +{ + unsigned short *msp; + int fifo_size, count, z1, z2; + u_char f_msk, f1, f2, *src; + + if (skb->len <= 0) return(0); + if (fifo & 1) return(0); /* no write fifo */ + + fifo_select(cs, fifo); + if (fifo & 4) { + fifo_size = D_FIFO_SIZE; /* D-channel */ + f_msk = MAX_D_FRAMES; + if (trans_max) return(0); /* only HDLC */ + } + else { + fifo_size = cs->hw.hfcsx.b_fifo_size; /* B-channel */ + f_msk = MAX_B_FRAMES; + } + + z1 = Read_hfc(cs, HFCSX_FIF_Z1H); + z1 = ((z1 << 8) | Read_hfc(cs, HFCSX_FIF_Z1L)); + + /* Check for transparent mode */ + if (trans_max) { + z2 = Read_hfc(cs, HFCSX_FIF_Z2H); + z2 = ((z2 << 8) | Read_hfc(cs, HFCSX_FIF_Z2L)); + count = z2 - z1; + if (count <= 0) + count += fifo_size; /* free bytes */ + if (count < skb->len+1) return(0); /* no room */ + count = fifo_size - count; /* bytes still not send */ + if (count > 2 * trans_max) return(0); /* delay to long */ + count = skb->len; + src = skb->data; + while (count--) + Write_hfc(cs, HFCSX_FIF_DWR, *src++); + return(1); /* success */ + } + + msp = ((struct hfcsx_extra *)(cs->hw.hfcsx.extra))->marker; + msp += (((fifo >> 1) & 3) * (MAX_B_FRAMES+1)); + f1 = Read_hfc(cs, HFCSX_FIF_F1) & f_msk; + f2 = Read_hfc(cs, HFCSX_FIF_F2) & f_msk; + + count = f1 - f2; /* frame count actually buffered */ + if (count < 0) + count += (f_msk + 1); /* if wrap around */ + if (count > f_msk-1) { + if (cs->debug & L1_DEB_ISAC_FIFO) + debugl1(cs, "hfcsx_write_fifo %d more as %d frames",fifo,f_msk-1); + return(0); + } + + *(msp + f1) = z1; /* remember marker */ + + if (cs->debug & L1_DEB_ISAC_FIFO) + debugl1(cs, "hfcsx_write_fifo %d f1(%x) f2(%x) z1(f1)(%x)", + fifo, f1, f2, z1); + /* now determine free bytes in FIFO buffer */ + count = *(msp + f2) - z1; + if (count <= 0) + count += fifo_size; /* count now contains available bytes */ + + if (cs->debug & L1_DEB_ISAC_FIFO) + debugl1(cs, "hfcsx_write_fifo %d count(%ld/%d)", + fifo, skb->len, count); + if (count < skb->len) { + if (cs->debug & L1_DEB_ISAC_FIFO) + debugl1(cs, "hfcsx_write_fifo %d no fifo mem", fifo); + return(0); + } + + count = skb->len; /* get frame len */ + src = skb->data; /* source pointer */ + while (count--) + Write_hfc(cs, HFCSX_FIF_DWR, *src++); + + Read_hfc(cs, HFCSX_FIF_INCF1); /* increment F1 */ + udelay(1); + while (bytein(cs->hw.hfcsx.base+1) & 1); /* wait for busy */ + return(1); +} + +/***************************************************************/ +/* read_fifo reads data to an skb from the desired fifo */ +/* if no data is available or an error occurs NULL is returned */ +/* the skb is not released in any way. */ +/***************************************************************/ +static struct sk_buff * +read_fifo(struct IsdnCardState *cs, u_char fifo, int trans_max) +{ int fifo_size, count, z1, z2; + u_char f_msk, f1, f2, *dst; + struct sk_buff *skb; + + if (!(fifo & 1)) return(NULL); /* no read fifo */ + fifo_select(cs, fifo); + if (fifo & 4) { + fifo_size = D_FIFO_SIZE; /* D-channel */ + f_msk = MAX_D_FRAMES; + if (trans_max) return(NULL); /* only hdlc */ + } + else { + fifo_size = cs->hw.hfcsx.b_fifo_size; /* B-channel */ + f_msk = MAX_B_FRAMES; + } + + /* transparent mode */ + if (trans_max) { + z1 = Read_hfc(cs, HFCSX_FIF_Z1H); + z1 = ((z1 << 8) | Read_hfc(cs, HFCSX_FIF_Z1L)); + z2 = Read_hfc(cs, HFCSX_FIF_Z2H); + z2 = ((z2 << 8) | Read_hfc(cs, HFCSX_FIF_Z2L)); + /* now determine bytes in actual FIFO buffer */ + count = z1 - z2; + if (count <= 0) + count += fifo_size; /* count now contains buffered bytes */ + count++; + if (count > trans_max) + count = trans_max; /* limit length */ + if ((skb = dev_alloc_skb(count))) { + dst = skb_put(skb, count); + while (count--) + *dst++ = Read_hfc(cs, HFCSX_FIF_DRD); + return(skb); + } + else return(NULL); /* no memory */ + } + + do { + f1 = Read_hfc(cs, HFCSX_FIF_F1) & f_msk; + f2 = Read_hfc(cs, HFCSX_FIF_F2) & f_msk; + + if (f1 == f2) return(NULL); /* no frame available */ + + z1 = Read_hfc(cs, HFCSX_FIF_Z1H); + z1 = ((z1 << 8) | Read_hfc(cs, HFCSX_FIF_Z1L)); + z2 = Read_hfc(cs, HFCSX_FIF_Z2H); + z2 = ((z2 << 8) | Read_hfc(cs, HFCSX_FIF_Z2L)); + + if (cs->debug & L1_DEB_ISAC_FIFO) + debugl1(cs, "hfcsx_read_fifo %d f1(%x) f2(%x) z1(f2)(%x) z2(f2)(%x)", + fifo, f1, f2, z1, z2); + /* now determine bytes in actual FIFO buffer */ + count = z1 - z2; + if (count <= 0) + count += fifo_size; /* count now contains buffered bytes */ + count++; + + if (cs->debug & L1_DEB_ISAC_FIFO) + debugl1(cs, "hfcsx_read_fifo %d count %ld)", + fifo, count); + + if ((count > fifo_size) || (count < 4)) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "hfcsx_read_fifo %d paket inv. len %d ", fifo , count); + while (count) { + count--; /* empty fifo */ + Read_hfc(cs, HFCSX_FIF_DRD); + } + skb = NULL; + } else + if ((skb = dev_alloc_skb(count - 3))) { + count -= 3; + dst = skb_put(skb, count); + + while (count--) + *dst++ = Read_hfc(cs, HFCSX_FIF_DRD); + + Read_hfc(cs, HFCSX_FIF_DRD); /* CRC 1 */ + Read_hfc(cs, HFCSX_FIF_DRD); /* CRC 2 */ + if (Read_hfc(cs, HFCSX_FIF_DRD)) { + dev_kfree_skb_irq(skb); + if (cs->debug & L1_DEB_ISAC_FIFO) + debugl1(cs, "hfcsx_read_fifo %d crc error", fifo); + skb = NULL; + } + } else { + printk(KERN_WARNING "HFC-SX: receive out of memory\n"); + return(NULL); + } + + Read_hfc(cs, HFCSX_FIF_INCF2); /* increment F2 */ + udelay(1); + while (bytein(cs->hw.hfcsx.base+1) & 1); /* wait for busy */ + udelay(1); + } while (!skb); /* retry in case of crc error */ + return(skb); +} + +/******************************************/ +/* free hardware resources used by driver */ +/******************************************/ +void +release_io_hfcsx(struct IsdnCardState *cs) +{ + cs->hw.hfcsx.int_m2 = 0; /* interrupt output off ! */ + Write_hfc(cs, HFCSX_INT_M2, cs->hw.hfcsx.int_m2); + Write_hfc(cs, HFCSX_CIRM, HFCSX_RESET); /* Reset On */ + msleep(30); /* Timeout 30ms */ + Write_hfc(cs, HFCSX_CIRM, 0); /* Reset Off */ + del_timer(&cs->hw.hfcsx.timer); + release_region(cs->hw.hfcsx.base, 2); /* release IO-Block */ + kfree(cs->hw.hfcsx.extra); + cs->hw.hfcsx.extra = NULL; +} + +/**********************************************************/ +/* set_fifo_size determines the size of the RAM and FIFOs */ +/* returning 0 -> need to reset the chip again. */ +/**********************************************************/ +static int set_fifo_size(struct IsdnCardState *cs) +{ + + if (cs->hw.hfcsx.b_fifo_size) return(1); /* already determined */ + + if ((cs->hw.hfcsx.chip >> 4) == 9) { + cs->hw.hfcsx.b_fifo_size = B_FIFO_SIZE_32K; + return(1); + } + + cs->hw.hfcsx.b_fifo_size = B_FIFO_SIZE_8K; + cs->hw.hfcsx.cirm |= 0x10; /* only 8K of ram */ + return(0); + +} + +/********************************************************************************/ +/* function called to reset the HFC SX chip. A complete software reset of chip */ +/* and fifos is done. */ +/********************************************************************************/ +static void +reset_hfcsx(struct IsdnCardState *cs) +{ + cs->hw.hfcsx.int_m2 = 0; /* interrupt output off ! */ + Write_hfc(cs, HFCSX_INT_M2, cs->hw.hfcsx.int_m2); + + printk(KERN_INFO "HFC_SX: resetting card\n"); + while (1) { + Write_hfc(cs, HFCSX_CIRM, HFCSX_RESET | cs->hw.hfcsx.cirm ); /* Reset */ + mdelay(30); + Write_hfc(cs, HFCSX_CIRM, cs->hw.hfcsx.cirm); /* Reset Off */ + mdelay(20); + if (Read_hfc(cs, HFCSX_STATUS) & 2) + printk(KERN_WARNING "HFC-SX init bit busy\n"); + cs->hw.hfcsx.last_fifo = 0xff; /* invalidate */ + if (!set_fifo_size(cs)) continue; + break; + } + + cs->hw.hfcsx.trm = 0 + HFCSX_BTRANS_THRESMASK; /* no echo connect , threshold */ + Write_hfc(cs, HFCSX_TRM, cs->hw.hfcsx.trm); + + Write_hfc(cs, HFCSX_CLKDEL, 0x0e); /* ST-Bit delay for TE-Mode */ + cs->hw.hfcsx.sctrl_e = HFCSX_AUTO_AWAKE; + Write_hfc(cs, HFCSX_SCTRL_E, cs->hw.hfcsx.sctrl_e); /* S/T Auto awake */ + cs->hw.hfcsx.bswapped = 0; /* no exchange */ + cs->hw.hfcsx.nt_mode = 0; /* we are in TE mode */ + cs->hw.hfcsx.ctmt = HFCSX_TIM3_125 | HFCSX_AUTO_TIMER; + Write_hfc(cs, HFCSX_CTMT, cs->hw.hfcsx.ctmt); + + cs->hw.hfcsx.int_m1 = HFCSX_INTS_DTRANS | HFCSX_INTS_DREC | + HFCSX_INTS_L1STATE | HFCSX_INTS_TIMER; + Write_hfc(cs, HFCSX_INT_M1, cs->hw.hfcsx.int_m1); + + /* Clear already pending ints */ + if (Read_hfc(cs, HFCSX_INT_S1)); + + Write_hfc(cs, HFCSX_STATES, HFCSX_LOAD_STATE | 2); /* HFC ST 2 */ + udelay(10); + Write_hfc(cs, HFCSX_STATES, 2); /* HFC ST 2 */ + cs->hw.hfcsx.mst_m = HFCSX_MASTER; /* HFC Master Mode */ + + Write_hfc(cs, HFCSX_MST_MODE, cs->hw.hfcsx.mst_m); + cs->hw.hfcsx.sctrl = 0x40; /* set tx_lo mode, error in datasheet ! */ + Write_hfc(cs, HFCSX_SCTRL, cs->hw.hfcsx.sctrl); + cs->hw.hfcsx.sctrl_r = 0; + Write_hfc(cs, HFCSX_SCTRL_R, cs->hw.hfcsx.sctrl_r); + + /* Init GCI/IOM2 in master mode */ + /* Slots 0 and 1 are set for B-chan 1 and 2 */ + /* D- and monitor/CI channel are not enabled */ + /* STIO1 is used as output for data, B1+B2 from ST->IOM+HFC */ + /* STIO2 is used as data input, B1+B2 from IOM->ST */ + /* ST B-channel send disabled -> continous 1s */ + /* The IOM slots are always enabled */ + cs->hw.hfcsx.conn = 0x36; /* set data flow directions */ + Write_hfc(cs, HFCSX_CONNECT, cs->hw.hfcsx.conn); + Write_hfc(cs, HFCSX_B1_SSL, 0x80); /* B1-Slot 0 STIO1 out enabled */ + Write_hfc(cs, HFCSX_B2_SSL, 0x81); /* B2-Slot 1 STIO1 out enabled */ + Write_hfc(cs, HFCSX_B1_RSL, 0x80); /* B1-Slot 0 STIO2 in enabled */ + Write_hfc(cs, HFCSX_B2_RSL, 0x81); /* B2-Slot 1 STIO2 in enabled */ + + /* Finally enable IRQ output */ + cs->hw.hfcsx.int_m2 = HFCSX_IRQ_ENABLE; + Write_hfc(cs, HFCSX_INT_M2, cs->hw.hfcsx.int_m2); + if (Read_hfc(cs, HFCSX_INT_S2)); +} + +/***************************************************/ +/* Timer function called when kernel timer expires */ +/***************************************************/ +static void +hfcsx_Timer(struct IsdnCardState *cs) +{ + cs->hw.hfcsx.timer.expires = jiffies + 75; + /* WD RESET */ +/* WriteReg(cs, HFCD_DATA, HFCD_CTMT, cs->hw.hfcsx.ctmt | 0x80); + add_timer(&cs->hw.hfcsx.timer); + */ +} + +/************************************************/ +/* select a b-channel entry matching and active */ +/************************************************/ +static +struct BCState * +Sel_BCS(struct IsdnCardState *cs, int channel) +{ + if (cs->bcs[0].mode && (cs->bcs[0].channel == channel)) + return (&cs->bcs[0]); + else if (cs->bcs[1].mode && (cs->bcs[1].channel == channel)) + return (&cs->bcs[1]); + else + return (NULL); +} + +/*******************************/ +/* D-channel receive procedure */ +/*******************************/ +static +int +receive_dmsg(struct IsdnCardState *cs) +{ + struct sk_buff *skb; + int count = 5; + + if (test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + debugl1(cs, "rec_dmsg blocked"); + return (1); + } + + do { + skb = read_fifo(cs, HFCSX_SEL_D_RX, 0); + if (skb) { + skb_queue_tail(&cs->rq, skb); + schedule_event(cs, D_RCVBUFREADY); + } + } while (--count && skb); + + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + return (1); +} + +/**********************************/ +/* B-channel main receive routine */ +/**********************************/ +void +main_rec_hfcsx(struct BCState *bcs) +{ + struct IsdnCardState *cs = bcs->cs; + int count = 5; + struct sk_buff *skb; + + Begin: + count--; + if (test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + debugl1(cs, "rec_data %d blocked", bcs->channel); + return; + } + skb = read_fifo(cs, ((bcs->channel) && (!cs->hw.hfcsx.bswapped)) ? + HFCSX_SEL_B2_RX : HFCSX_SEL_B1_RX, + (bcs->mode == L1_MODE_TRANS) ? + HFCSX_BTRANS_THRESHOLD : 0); + + if (skb) { + skb_queue_tail(&bcs->rqueue, skb); + schedule_event(bcs, B_RCVBUFREADY); + } + + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + if (count && skb) + goto Begin; + return; +} + +/**************************/ +/* D-channel send routine */ +/**************************/ +static void +hfcsx_fill_dfifo(struct IsdnCardState *cs) +{ + if (!cs->tx_skb) + return; + if (cs->tx_skb->len <= 0) + return; + + if (write_fifo(cs, cs->tx_skb, HFCSX_SEL_D_TX, 0)) { + dev_kfree_skb_any(cs->tx_skb); + cs->tx_skb = NULL; + } + return; +} + +/**************************/ +/* B-channel send routine */ +/**************************/ +static void +hfcsx_fill_fifo(struct BCState *bcs) +{ + struct IsdnCardState *cs = bcs->cs; + + if (!bcs->tx_skb) + return; + if (bcs->tx_skb->len <= 0) + return; + + if (write_fifo(cs, bcs->tx_skb, + ((bcs->channel) && (!cs->hw.hfcsx.bswapped)) ? + HFCSX_SEL_B2_TX : HFCSX_SEL_B1_TX, + (bcs->mode == L1_MODE_TRANS) ? + HFCSX_BTRANS_THRESHOLD : 0)) { + + bcs->tx_cnt -= bcs->tx_skb->len; + if (test_bit(FLG_LLI_L1WAKEUP,&bcs->st->lli.flag) && + (PACKET_NOACK != bcs->tx_skb->pkt_type)) { + u_long flags; + spin_lock_irqsave(&bcs->aclock, flags); + bcs->ackcnt += bcs->tx_skb->len; + spin_unlock_irqrestore(&bcs->aclock, flags); + schedule_event(bcs, B_ACKPENDING); + } + dev_kfree_skb_any(bcs->tx_skb); + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + } +} + +/**********************************************/ +/* D-channel l1 state call for leased NT-mode */ +/**********************************************/ +static void +dch_nt_l2l1(struct PStack *st, int pr, void *arg) +{ + struct IsdnCardState *cs = (struct IsdnCardState *) st->l1.hardware; + + switch (pr) { + case (PH_DATA | REQUEST): + case (PH_PULL | REQUEST): + case (PH_PULL | INDICATION): + st->l1.l1hw(st, pr, arg); + break; + case (PH_ACTIVATE | REQUEST): + st->l1.l1l2(st, PH_ACTIVATE | CONFIRM, NULL); + break; + case (PH_TESTLOOP | REQUEST): + if (1 & (long) arg) + debugl1(cs, "PH_TEST_LOOP B1"); + if (2 & (long) arg) + debugl1(cs, "PH_TEST_LOOP B2"); + if (!(3 & (long) arg)) + debugl1(cs, "PH_TEST_LOOP DISABLED"); + st->l1.l1hw(st, HW_TESTLOOP | REQUEST, arg); + break; + default: + if (cs->debug) + debugl1(cs, "dch_nt_l2l1 msg %04X unhandled", pr); + break; + } +} + + + +/***********************/ +/* set/reset echo mode */ +/***********************/ +static int +hfcsx_auxcmd(struct IsdnCardState *cs, isdn_ctrl * ic) +{ + unsigned long flags; + int i = *(unsigned int *) ic->parm.num; + + if ((ic->arg == 98) && + (!(cs->hw.hfcsx.int_m1 & (HFCSX_INTS_B2TRANS + HFCSX_INTS_B2REC + HFCSX_INTS_B1TRANS + HFCSX_INTS_B1REC)))) { + spin_lock_irqsave(&cs->lock, flags); + Write_hfc(cs, HFCSX_STATES, HFCSX_LOAD_STATE | 0); /* HFC ST G0 */ + udelay(10); + cs->hw.hfcsx.sctrl |= SCTRL_MODE_NT; + Write_hfc(cs, HFCSX_SCTRL, cs->hw.hfcsx.sctrl); /* set NT-mode */ + udelay(10); + Write_hfc(cs, HFCSX_STATES, HFCSX_LOAD_STATE | 1); /* HFC ST G1 */ + udelay(10); + Write_hfc(cs, HFCSX_STATES, 1 | HFCSX_ACTIVATE | HFCSX_DO_ACTION); + cs->dc.hfcsx.ph_state = 1; + cs->hw.hfcsx.nt_mode = 1; + cs->hw.hfcsx.nt_timer = 0; + spin_unlock_irqrestore(&cs->lock, flags); + cs->stlist->l2.l2l1 = dch_nt_l2l1; + debugl1(cs, "NT mode activated"); + return (0); + } + if ((cs->chanlimit > 1) || (cs->hw.hfcsx.bswapped) || + (cs->hw.hfcsx.nt_mode) || (ic->arg != 12)) + return (-EINVAL); + + if (i) { + cs->logecho = 1; + cs->hw.hfcsx.trm |= 0x20; /* enable echo chan */ + cs->hw.hfcsx.int_m1 |= HFCSX_INTS_B2REC; + /* reset Channel !!!!! */ + } else { + cs->logecho = 0; + cs->hw.hfcsx.trm &= ~0x20; /* disable echo chan */ + cs->hw.hfcsx.int_m1 &= ~HFCSX_INTS_B2REC; + } + cs->hw.hfcsx.sctrl_r &= ~SCTRL_B2_ENA; + cs->hw.hfcsx.sctrl &= ~SCTRL_B2_ENA; + cs->hw.hfcsx.conn |= 0x10; /* B2-IOM -> B2-ST */ + cs->hw.hfcsx.ctmt &= ~2; + spin_lock_irqsave(&cs->lock, flags); + Write_hfc(cs, HFCSX_CTMT, cs->hw.hfcsx.ctmt); + Write_hfc(cs, HFCSX_SCTRL_R, cs->hw.hfcsx.sctrl_r); + Write_hfc(cs, HFCSX_SCTRL, cs->hw.hfcsx.sctrl); + Write_hfc(cs, HFCSX_CONNECT, cs->hw.hfcsx.conn); + Write_hfc(cs, HFCSX_TRM, cs->hw.hfcsx.trm); + Write_hfc(cs, HFCSX_INT_M1, cs->hw.hfcsx.int_m1); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); +} /* hfcsx_auxcmd */ + +/*****************************/ +/* E-channel receive routine */ +/*****************************/ +static void +receive_emsg(struct IsdnCardState *cs) +{ + int count = 5; + u_char *ptr; + struct sk_buff *skb; + + if (test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + debugl1(cs, "echo_rec_data blocked"); + return; + } + do { + skb = read_fifo(cs, HFCSX_SEL_B2_RX, 0); + if (skb) { + if (cs->debug & DEB_DLOG_HEX) { + ptr = cs->dlog; + if ((skb->len) < MAX_DLOG_SPACE / 3 - 10) { + *ptr++ = 'E'; + *ptr++ = 'C'; + *ptr++ = 'H'; + *ptr++ = 'O'; + *ptr++ = ':'; + ptr += QuickHex(ptr, skb->data, skb->len); + ptr--; + *ptr++ = '\n'; + *ptr = 0; + HiSax_putstatus(cs, NULL, cs->dlog); + } else + HiSax_putstatus(cs, "LogEcho: ", "warning Frame too big (%d)", skb->len); + } + dev_kfree_skb_any(skb); + } + } while (--count && skb); + + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + return; +} /* receive_emsg */ + + +/*********************/ +/* Interrupt handler */ +/*********************/ +static irqreturn_t +hfcsx_interrupt(int intno, void *dev_id, struct pt_regs *regs) +{ + struct IsdnCardState *cs = dev_id; + u_char exval; + struct BCState *bcs; + int count = 15; + u_long flags; + u_char val, stat; + + if (!(cs->hw.hfcsx.int_m2 & 0x08)) + return IRQ_NONE; /* not initialised */ + + spin_lock_irqsave(&cs->lock, flags); + if (HFCSX_ANYINT & (stat = Read_hfc(cs, HFCSX_STATUS))) { + val = Read_hfc(cs, HFCSX_INT_S1); + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "HFC-SX: stat(%02x) s1(%02x)", stat, val); + } else { + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_NONE; + } + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "HFC-SX irq %x %s", val, + test_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags) ? + "locked" : "unlocked"); + val &= cs->hw.hfcsx.int_m1; + if (val & 0x40) { /* state machine irq */ + exval = Read_hfc(cs, HFCSX_STATES) & 0xf; + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ph_state chg %d->%d", cs->dc.hfcsx.ph_state, + exval); + cs->dc.hfcsx.ph_state = exval; + schedule_event(cs, D_L1STATECHANGE); + val &= ~0x40; + } + if (val & 0x80) { /* timer irq */ + if (cs->hw.hfcsx.nt_mode) { + if ((--cs->hw.hfcsx.nt_timer) < 0) + schedule_event(cs, D_L1STATECHANGE); + } + val &= ~0x80; + Write_hfc(cs, HFCSX_CTMT, cs->hw.hfcsx.ctmt | HFCSX_CLTIMER); + } + while (val) { + if (test_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + cs->hw.hfcsx.int_s1 |= val; + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; + } + if (cs->hw.hfcsx.int_s1 & 0x18) { + exval = val; + val = cs->hw.hfcsx.int_s1; + cs->hw.hfcsx.int_s1 = exval; + } + if (val & 0x08) { + if (!(bcs = Sel_BCS(cs, cs->hw.hfcsx.bswapped ? 1 : 0))) { + if (cs->debug) + debugl1(cs, "hfcsx spurious 0x08 IRQ"); + } else + main_rec_hfcsx(bcs); + } + if (val & 0x10) { + if (cs->logecho) + receive_emsg(cs); + else if (!(bcs = Sel_BCS(cs, 1))) { + if (cs->debug) + debugl1(cs, "hfcsx spurious 0x10 IRQ"); + } else + main_rec_hfcsx(bcs); + } + if (val & 0x01) { + if (!(bcs = Sel_BCS(cs, cs->hw.hfcsx.bswapped ? 1 : 0))) { + if (cs->debug) + debugl1(cs, "hfcsx spurious 0x01 IRQ"); + } else { + if (bcs->tx_skb) { + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfcsx_fill_fifo(bcs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs, "fill_data %d blocked", bcs->channel); + } else { + if ((bcs->tx_skb = skb_dequeue(&bcs->squeue))) { + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfcsx_fill_fifo(bcs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs, "fill_data %d blocked", bcs->channel); + } else { + schedule_event(bcs, B_XMTBUFREADY); + } + } + } + } + if (val & 0x02) { + if (!(bcs = Sel_BCS(cs, 1))) { + if (cs->debug) + debugl1(cs, "hfcsx spurious 0x02 IRQ"); + } else { + if (bcs->tx_skb) { + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfcsx_fill_fifo(bcs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs, "fill_data %d blocked", bcs->channel); + } else { + if ((bcs->tx_skb = skb_dequeue(&bcs->squeue))) { + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfcsx_fill_fifo(bcs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs, "fill_data %d blocked", bcs->channel); + } else { + schedule_event(bcs, B_XMTBUFREADY); + } + } + } + } + if (val & 0x20) { /* receive dframe */ + receive_dmsg(cs); + } + if (val & 0x04) { /* dframe transmitted */ + if (test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) + del_timer(&cs->dbusytimer); + if (test_and_clear_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + schedule_event(cs, D_CLEARBUSY); + if (cs->tx_skb) { + if (cs->tx_skb->len) { + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfcsx_fill_dfifo(cs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else { + debugl1(cs, "hfcsx_fill_dfifo irq blocked"); + } + goto afterXPR; + } else { + dev_kfree_skb_irq(cs->tx_skb); + cs->tx_cnt = 0; + cs->tx_skb = NULL; + } + } + if ((cs->tx_skb = skb_dequeue(&cs->sq))) { + cs->tx_cnt = 0; + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfcsx_fill_dfifo(cs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else { + debugl1(cs, "hfcsx_fill_dfifo irq blocked"); + } + } else + schedule_event(cs, D_XMTBUFREADY); + } + afterXPR: + if (cs->hw.hfcsx.int_s1 && count--) { + val = cs->hw.hfcsx.int_s1; + cs->hw.hfcsx.int_s1 = 0; + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "HFC-SX irq %x loop %d", val, 15 - count); + } else + val = 0; + } + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +/********************************************************************/ +/* timer callback for D-chan busy resolution. Currently no function */ +/********************************************************************/ +static void +hfcsx_dbusy_timer(struct IsdnCardState *cs) +{ +} + +/*************************************/ +/* Layer 1 D-channel hardware access */ +/*************************************/ +static void +HFCSX_l1hw(struct PStack *st, int pr, void *arg) +{ + struct IsdnCardState *cs = (struct IsdnCardState *) st->l1.hardware; + struct sk_buff *skb = arg; + u_long flags; + + switch (pr) { + case (PH_DATA | REQUEST): + if (cs->debug & DEB_DLOG_HEX) + LogFrame(cs, skb->data, skb->len); + if (cs->debug & DEB_DLOG_VERBOSE) + dlogframe(cs, skb, 0); + spin_lock_irqsave(&cs->lock, flags); + if (cs->tx_skb) { + skb_queue_tail(&cs->sq, skb); +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "PH_DATA Queued", 0); +#endif + } else { + cs->tx_skb = skb; + cs->tx_cnt = 0; +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "PH_DATA", 0); +#endif + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfcsx_fill_dfifo(cs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs, "hfcsx_fill_dfifo blocked"); + + } + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (PH_PULL | INDICATION): + spin_lock_irqsave(&cs->lock, flags); + if (cs->tx_skb) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, " l2l1 tx_skb exist this shouldn't happen"); + skb_queue_tail(&cs->sq, skb); + spin_unlock_irqrestore(&cs->lock, flags); + break; + } + if (cs->debug & DEB_DLOG_HEX) + LogFrame(cs, skb->data, skb->len); + if (cs->debug & DEB_DLOG_VERBOSE) + dlogframe(cs, skb, 0); + cs->tx_skb = skb; + cs->tx_cnt = 0; +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "PH_DATA_PULLED", 0); +#endif + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfcsx_fill_dfifo(cs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs, "hfcsx_fill_dfifo blocked"); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (PH_PULL | REQUEST): +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + debugl1(cs, "-> PH_REQUEST_PULL"); +#endif + if (!cs->tx_skb) { + test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } else + test_and_set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + case (HW_RESET | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + Write_hfc(cs, HFCSX_STATES, HFCSX_LOAD_STATE | 3); /* HFC ST 3 */ + udelay(6); + Write_hfc(cs, HFCSX_STATES, 3); /* HFC ST 2 */ + cs->hw.hfcsx.mst_m |= HFCSX_MASTER; + Write_hfc(cs, HFCSX_MST_MODE, cs->hw.hfcsx.mst_m); + Write_hfc(cs, HFCSX_STATES, HFCSX_ACTIVATE | HFCSX_DO_ACTION); + spin_unlock_irqrestore(&cs->lock, flags); + l1_msg(cs, HW_POWERUP | CONFIRM, NULL); + break; + case (HW_ENABLE | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + Write_hfc(cs, HFCSX_STATES, HFCSX_ACTIVATE | HFCSX_DO_ACTION); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (HW_DEACTIVATE | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + cs->hw.hfcsx.mst_m &= ~HFCSX_MASTER; + Write_hfc(cs, HFCSX_MST_MODE, cs->hw.hfcsx.mst_m); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (HW_INFO3 | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + cs->hw.hfcsx.mst_m |= HFCSX_MASTER; + Write_hfc(cs, HFCSX_MST_MODE, cs->hw.hfcsx.mst_m); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (HW_TESTLOOP | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + switch ((int) arg) { + case (1): + Write_hfc(cs, HFCSX_B1_SSL, 0x80); /* tx slot */ + Write_hfc(cs, HFCSX_B1_RSL, 0x80); /* rx slot */ + cs->hw.hfcsx.conn = (cs->hw.hfcsx.conn & ~7) | 1; + Write_hfc(cs, HFCSX_CONNECT, cs->hw.hfcsx.conn); + break; + case (2): + Write_hfc(cs, HFCSX_B2_SSL, 0x81); /* tx slot */ + Write_hfc(cs, HFCSX_B2_RSL, 0x81); /* rx slot */ + cs->hw.hfcsx.conn = (cs->hw.hfcsx.conn & ~0x38) | 0x08; + Write_hfc(cs, HFCSX_CONNECT, cs->hw.hfcsx.conn); + break; + default: + spin_unlock_irqrestore(&cs->lock, flags); + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "hfcsx_l1hw loop invalid %4x", (int) arg); + return; + } + cs->hw.hfcsx.trm |= 0x80; /* enable IOM-loop */ + Write_hfc(cs, HFCSX_TRM, cs->hw.hfcsx.trm); + spin_unlock_irqrestore(&cs->lock, flags); + break; + default: + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "hfcsx_l1hw unknown pr %4x", pr); + break; + } +} + +/***********************************************/ +/* called during init setting l1 stack pointer */ +/***********************************************/ +void +setstack_hfcsx(struct PStack *st, struct IsdnCardState *cs) +{ + st->l1.l1hw = HFCSX_l1hw; +} + +/**************************************/ +/* send B-channel data if not blocked */ +/**************************************/ +static void +hfcsx_send_data(struct BCState *bcs) +{ + struct IsdnCardState *cs = bcs->cs; + + if (!test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + hfcsx_fill_fifo(bcs); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } else + debugl1(cs, "send_data %d blocked", bcs->channel); +} + +/***************************************************************/ +/* activate/deactivate hardware for selected channels and mode */ +/***************************************************************/ +void +mode_hfcsx(struct BCState *bcs, int mode, int bc) +{ + struct IsdnCardState *cs = bcs->cs; + int fifo2; + + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HFCSX bchannel mode %d bchan %d/%d", + mode, bc, bcs->channel); + bcs->mode = mode; + bcs->channel = bc; + fifo2 = bc; + if (cs->chanlimit > 1) { + cs->hw.hfcsx.bswapped = 0; /* B1 and B2 normal mode */ + cs->hw.hfcsx.sctrl_e &= ~0x80; + } else { + if (bc) { + if (mode != L1_MODE_NULL) { + cs->hw.hfcsx.bswapped = 1; /* B1 and B2 exchanged */ + cs->hw.hfcsx.sctrl_e |= 0x80; + } else { + cs->hw.hfcsx.bswapped = 0; /* B1 and B2 normal mode */ + cs->hw.hfcsx.sctrl_e &= ~0x80; + } + fifo2 = 0; + } else { + cs->hw.hfcsx.bswapped = 0; /* B1 and B2 normal mode */ + cs->hw.hfcsx.sctrl_e &= ~0x80; + } + } + switch (mode) { + case (L1_MODE_NULL): + if (bc) { + cs->hw.hfcsx.sctrl &= ~SCTRL_B2_ENA; + cs->hw.hfcsx.sctrl_r &= ~SCTRL_B2_ENA; + } else { + cs->hw.hfcsx.sctrl &= ~SCTRL_B1_ENA; + cs->hw.hfcsx.sctrl_r &= ~SCTRL_B1_ENA; + } + if (fifo2) { + cs->hw.hfcsx.int_m1 &= ~(HFCSX_INTS_B2TRANS + HFCSX_INTS_B2REC); + } else { + cs->hw.hfcsx.int_m1 &= ~(HFCSX_INTS_B1TRANS + HFCSX_INTS_B1REC); + } + break; + case (L1_MODE_TRANS): + if (bc) { + cs->hw.hfcsx.sctrl |= SCTRL_B2_ENA; + cs->hw.hfcsx.sctrl_r |= SCTRL_B2_ENA; + } else { + cs->hw.hfcsx.sctrl |= SCTRL_B1_ENA; + cs->hw.hfcsx.sctrl_r |= SCTRL_B1_ENA; + } + if (fifo2) { + cs->hw.hfcsx.int_m1 |= (HFCSX_INTS_B2TRANS + HFCSX_INTS_B2REC); + cs->hw.hfcsx.ctmt |= 2; + cs->hw.hfcsx.conn &= ~0x18; + } else { + cs->hw.hfcsx.int_m1 |= (HFCSX_INTS_B1TRANS + HFCSX_INTS_B1REC); + cs->hw.hfcsx.ctmt |= 1; + cs->hw.hfcsx.conn &= ~0x03; + } + break; + case (L1_MODE_HDLC): + if (bc) { + cs->hw.hfcsx.sctrl |= SCTRL_B2_ENA; + cs->hw.hfcsx.sctrl_r |= SCTRL_B2_ENA; + } else { + cs->hw.hfcsx.sctrl |= SCTRL_B1_ENA; + cs->hw.hfcsx.sctrl_r |= SCTRL_B1_ENA; + } + if (fifo2) { + cs->hw.hfcsx.int_m1 |= (HFCSX_INTS_B2TRANS + HFCSX_INTS_B2REC); + cs->hw.hfcsx.ctmt &= ~2; + cs->hw.hfcsx.conn &= ~0x18; + } else { + cs->hw.hfcsx.int_m1 |= (HFCSX_INTS_B1TRANS + HFCSX_INTS_B1REC); + cs->hw.hfcsx.ctmt &= ~1; + cs->hw.hfcsx.conn &= ~0x03; + } + break; + case (L1_MODE_EXTRN): + if (bc) { + cs->hw.hfcsx.conn |= 0x10; + cs->hw.hfcsx.sctrl |= SCTRL_B2_ENA; + cs->hw.hfcsx.sctrl_r |= SCTRL_B2_ENA; + cs->hw.hfcsx.int_m1 &= ~(HFCSX_INTS_B2TRANS + HFCSX_INTS_B2REC); + } else { + cs->hw.hfcsx.conn |= 0x02; + cs->hw.hfcsx.sctrl |= SCTRL_B1_ENA; + cs->hw.hfcsx.sctrl_r |= SCTRL_B1_ENA; + cs->hw.hfcsx.int_m1 &= ~(HFCSX_INTS_B1TRANS + HFCSX_INTS_B1REC); + } + break; + } + Write_hfc(cs, HFCSX_SCTRL_E, cs->hw.hfcsx.sctrl_e); + Write_hfc(cs, HFCSX_INT_M1, cs->hw.hfcsx.int_m1); + Write_hfc(cs, HFCSX_SCTRL, cs->hw.hfcsx.sctrl); + Write_hfc(cs, HFCSX_SCTRL_R, cs->hw.hfcsx.sctrl_r); + Write_hfc(cs, HFCSX_CTMT, cs->hw.hfcsx.ctmt); + Write_hfc(cs, HFCSX_CONNECT, cs->hw.hfcsx.conn); + if (mode != L1_MODE_EXTRN) { + reset_fifo(cs, fifo2 ? HFCSX_SEL_B2_RX : HFCSX_SEL_B1_RX); + reset_fifo(cs, fifo2 ? HFCSX_SEL_B2_TX : HFCSX_SEL_B1_TX); + } +} + +/******************************/ +/* Layer2 -> Layer 1 Transfer */ +/******************************/ +static void +hfcsx_l2l1(struct PStack *st, int pr, void *arg) +{ + struct BCState *bcs = st->l1.bcs; + struct sk_buff *skb = arg; + u_long flags; + + switch (pr) { + case (PH_DATA | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + skb_queue_tail(&bcs->squeue, skb); + } else { + bcs->tx_skb = skb; +// test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->cs->BC_Send_Data(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | INDICATION): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + printk(KERN_WARNING "hfc_l2l1: this shouldn't happen\n"); + } else { +// test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->tx_skb = skb; + bcs->cs->BC_Send_Data(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | REQUEST): + if (!bcs->tx_skb) { + test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } else + test_and_set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + case (PH_ACTIVATE | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + test_and_set_bit(BC_FLG_ACTIV, &bcs->Flag); + mode_hfcsx(bcs, st->l1.mode, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | REQUEST): + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | CONFIRM): + spin_lock_irqsave(&bcs->cs->lock, flags); + test_and_clear_bit(BC_FLG_ACTIV, &bcs->Flag); + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + mode_hfcsx(bcs, 0, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + st->l1.l1l2(st, PH_DEACTIVATE | CONFIRM, NULL); + break; + } +} + +/******************************************/ +/* deactivate B-channel access and queues */ +/******************************************/ +static void +close_hfcsx(struct BCState *bcs) +{ + mode_hfcsx(bcs, 0, bcs->channel); + if (test_and_clear_bit(BC_FLG_INIT, &bcs->Flag)) { + skb_queue_purge(&bcs->rqueue); + skb_queue_purge(&bcs->squeue); + if (bcs->tx_skb) { + dev_kfree_skb_any(bcs->tx_skb); + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + } + } +} + +/*************************************/ +/* init B-channel queues and control */ +/*************************************/ +static int +open_hfcsxstate(struct IsdnCardState *cs, struct BCState *bcs) +{ + if (!test_and_set_bit(BC_FLG_INIT, &bcs->Flag)) { + skb_queue_head_init(&bcs->rqueue); + skb_queue_head_init(&bcs->squeue); + } + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->event = 0; + bcs->tx_cnt = 0; + return (0); +} + +/*********************************/ +/* inits the stack for B-channel */ +/*********************************/ +static int +setstack_2b(struct PStack *st, struct BCState *bcs) +{ + bcs->channel = st->l1.bc; + if (open_hfcsxstate(st->l1.hardware, bcs)) + return (-1); + st->l1.bcs = bcs; + st->l2.l2l1 = hfcsx_l2l1; + setstack_manager(st); + bcs->st = st; + setstack_l1_B(st); + return (0); +} + +/***************************/ +/* handle L1 state changes */ +/***************************/ +static void +hfcsx_bh(struct IsdnCardState *cs) +{ + u_long flags; + + if (!cs) + return; + if (test_and_clear_bit(D_L1STATECHANGE, &cs->event)) { + if (!cs->hw.hfcsx.nt_mode) + switch (cs->dc.hfcsx.ph_state) { + case (0): + l1_msg(cs, HW_RESET | INDICATION, NULL); + break; + case (3): + l1_msg(cs, HW_DEACTIVATE | INDICATION, NULL); + break; + case (8): + l1_msg(cs, HW_RSYNC | INDICATION, NULL); + break; + case (6): + l1_msg(cs, HW_INFO2 | INDICATION, NULL); + break; + case (7): + l1_msg(cs, HW_INFO4_P8 | INDICATION, NULL); + break; + default: + break; + } else { + switch (cs->dc.hfcsx.ph_state) { + case (2): + spin_lock_irqsave(&cs->lock, flags); + if (cs->hw.hfcsx.nt_timer < 0) { + cs->hw.hfcsx.nt_timer = 0; + cs->hw.hfcsx.int_m1 &= ~HFCSX_INTS_TIMER; + Write_hfc(cs, HFCSX_INT_M1, cs->hw.hfcsx.int_m1); + /* Clear already pending ints */ + if (Read_hfc(cs, HFCSX_INT_S1)); + + Write_hfc(cs, HFCSX_STATES, 4 | HFCSX_LOAD_STATE); + udelay(10); + Write_hfc(cs, HFCSX_STATES, 4); + cs->dc.hfcsx.ph_state = 4; + } else { + cs->hw.hfcsx.int_m1 |= HFCSX_INTS_TIMER; + Write_hfc(cs, HFCSX_INT_M1, cs->hw.hfcsx.int_m1); + cs->hw.hfcsx.ctmt &= ~HFCSX_AUTO_TIMER; + cs->hw.hfcsx.ctmt |= HFCSX_TIM3_125; + Write_hfc(cs, HFCSX_CTMT, cs->hw.hfcsx.ctmt | HFCSX_CLTIMER); + Write_hfc(cs, HFCSX_CTMT, cs->hw.hfcsx.ctmt | HFCSX_CLTIMER); + cs->hw.hfcsx.nt_timer = NT_T1_COUNT; + Write_hfc(cs, HFCSX_STATES, 2 | HFCSX_NT_G2_G3); /* allow G2 -> G3 transition */ + } + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (1): + case (3): + case (4): + spin_lock_irqsave(&cs->lock, flags); + cs->hw.hfcsx.nt_timer = 0; + cs->hw.hfcsx.int_m1 &= ~HFCSX_INTS_TIMER; + Write_hfc(cs, HFCSX_INT_M1, cs->hw.hfcsx.int_m1); + spin_unlock_irqrestore(&cs->lock, flags); + break; + default: + break; + } + } + } + if (test_and_clear_bit(D_RCVBUFREADY, &cs->event)) + DChannel_proc_rcv(cs); + if (test_and_clear_bit(D_XMTBUFREADY, &cs->event)) + DChannel_proc_xmt(cs); +} + + +/********************************/ +/* called for card init message */ +/********************************/ +void __devinit +inithfcsx(struct IsdnCardState *cs) +{ + cs->setstack_d = setstack_hfcsx; + cs->BC_Send_Data = &hfcsx_send_data; + cs->bcs[0].BC_SetStack = setstack_2b; + cs->bcs[1].BC_SetStack = setstack_2b; + cs->bcs[0].BC_Close = close_hfcsx; + cs->bcs[1].BC_Close = close_hfcsx; + mode_hfcsx(cs->bcs, 0, 0); + mode_hfcsx(cs->bcs + 1, 0, 1); +} + + + +/*******************************************/ +/* handle card messages from control layer */ +/*******************************************/ +static int +hfcsx_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "HFCSX: card_msg %x", mt); + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + reset_hfcsx(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_RELEASE: + release_io_hfcsx(cs); + return (0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + inithfcsx(cs); + spin_unlock_irqrestore(&cs->lock, flags); + msleep(80); /* Timeout 80ms */ + /* now switch timer interrupt off */ + spin_lock_irqsave(&cs->lock, flags); + cs->hw.hfcsx.int_m1 &= ~HFCSX_INTS_TIMER; + Write_hfc(cs, HFCSX_INT_M1, cs->hw.hfcsx.int_m1); + /* reinit mode reg */ + Write_hfc(cs, HFCSX_MST_MODE, cs->hw.hfcsx.mst_m); + spin_unlock_irqrestore(&cs->lock, flags); + return (0); + case CARD_TEST: + return (0); + } + return (0); +} + +#ifdef __ISAPNP__ +static struct isapnp_device_id hfc_ids[] __devinitdata = { + { ISAPNP_VENDOR('T', 'A', 'G'), ISAPNP_FUNCTION(0x2620), + ISAPNP_VENDOR('T', 'A', 'G'), ISAPNP_FUNCTION(0x2620), + (unsigned long) "Teles 16.3c2" }, + { 0, } +}; + +static struct isapnp_device_id *ipid __devinitdata = &hfc_ids[0]; +static struct pnp_card *pnp_c __devinitdata = NULL; +#endif + +int __devinit +setup_hfcsx(struct IsdnCard *card) +{ + struct IsdnCardState *cs = card->cs; + char tmp[64]; + + strcpy(tmp, hfcsx_revision); + printk(KERN_INFO "HiSax: HFC-SX driver Rev. %s\n", HiSax_getrev(tmp)); +#ifdef __ISAPNP__ + if (!card->para[1] && isapnp_present()) { + struct pnp_dev *pnp_d; + while(ipid->card_vendor) { + if ((pnp_c = pnp_find_card(ipid->card_vendor, + ipid->card_device, pnp_c))) { + pnp_d = NULL; + if ((pnp_d = pnp_find_dev(pnp_c, + ipid->vendor, ipid->function, pnp_d))) { + int err; + + printk(KERN_INFO "HiSax: %s detected\n", + (char *)ipid->driver_data); + pnp_disable_dev(pnp_d); + err = pnp_activate_dev(pnp_d); + if (err<0) { + printk(KERN_WARNING "%s: pnp_activate_dev ret(%d)\n", + __FUNCTION__, err); + return(0); + } + card->para[1] = pnp_port_start(pnp_d, 0); + card->para[0] = pnp_irq(pnp_d, 0); + if (!card->para[0] || !card->para[1]) { + printk(KERN_ERR "HFC PnP:some resources are missing %ld/%lx\n", + card->para[0], card->para[1]); + pnp_disable_dev(pnp_d); + return(0); + } + break; + } else { + printk(KERN_ERR "HFC PnP: PnP error card found, no device\n"); + } + } + ipid++; + pnp_c = NULL; + } + if (!ipid->card_vendor) { + printk(KERN_INFO "HFC PnP: no ISAPnP card found\n"); + return(0); + } + } +#endif + cs->hw.hfcsx.base = card->para[1] & 0xfffe; + cs->irq = card->para[0]; + cs->hw.hfcsx.int_s1 = 0; + cs->dc.hfcsx.ph_state = 0; + cs->hw.hfcsx.fifo = 255; + if ((cs->typ == ISDN_CTYPE_HFC_SX) || + (cs->typ == ISDN_CTYPE_HFC_SP_PCMCIA)) { + if ((!cs->hw.hfcsx.base) || !request_region(cs->hw.hfcsx.base, 2, "HFCSX isdn")) { + printk(KERN_WARNING + "HiSax: HFC-SX io-base %#lx already in use\n", + cs->hw.hfcsx.base); + return(0); + } + byteout(cs->hw.hfcsx.base, cs->hw.hfcsx.base & 0xFF); + byteout(cs->hw.hfcsx.base + 1, + ((cs->hw.hfcsx.base >> 8) & 3) | 0x54); + udelay(10); + cs->hw.hfcsx.chip = Read_hfc(cs,HFCSX_CHIP_ID); + switch (cs->hw.hfcsx.chip >> 4) { + case 1: + tmp[0] ='+'; + break; + case 9: + tmp[0] ='P'; + break; + default: + printk(KERN_WARNING + "HFC-SX: invalid chip id 0x%x\n", + cs->hw.hfcsx.chip >> 4); + release_region(cs->hw.hfcsx.base, 2); + return(0); + } + if (!ccd_sp_irqtab[cs->irq & 0xF]) { + printk(KERN_WARNING + "HFC_SX: invalid irq %d specified\n",cs->irq & 0xF); + release_region(cs->hw.hfcsx.base, 2); + return(0); + } + if (!(cs->hw.hfcsx.extra = (void *) + kmalloc(sizeof(struct hfcsx_extra), GFP_ATOMIC))) { + release_region(cs->hw.hfcsx.base, 2); + printk(KERN_WARNING "HFC-SX: unable to allocate memory\n"); + return(0); + } + printk(KERN_INFO "HFC-S%c chip detected at base 0x%x IRQ %d HZ %d\n", + tmp[0], (u_int) cs->hw.hfcsx.base, cs->irq, HZ); + cs->hw.hfcsx.int_m2 = 0; /* disable alle interrupts */ + cs->hw.hfcsx.int_m1 = 0; + Write_hfc(cs, HFCSX_INT_M1, cs->hw.hfcsx.int_m1); + Write_hfc(cs, HFCSX_INT_M2, cs->hw.hfcsx.int_m2); + } else + return (0); /* no valid card type */ + + cs->dbusytimer.function = (void *) hfcsx_dbusy_timer; + cs->dbusytimer.data = (long) cs; + init_timer(&cs->dbusytimer); + INIT_WORK(&cs->tqueue, (void *)(void *) hfcsx_bh, cs); + cs->readisac = NULL; + cs->writeisac = NULL; + cs->readisacfifo = NULL; + cs->writeisacfifo = NULL; + cs->BC_Read_Reg = NULL; + cs->BC_Write_Reg = NULL; + cs->irq_func = &hfcsx_interrupt; + + cs->hw.hfcsx.timer.function = (void *) hfcsx_Timer; + cs->hw.hfcsx.timer.data = (long) cs; + cs->hw.hfcsx.b_fifo_size = 0; /* fifo size still unknown */ + cs->hw.hfcsx.cirm = ccd_sp_irqtab[cs->irq & 0xF]; /* RAM not evaluated */ + init_timer(&cs->hw.hfcsx.timer); + + reset_hfcsx(cs); + cs->cardmsg = &hfcsx_card_msg; + cs->auxcmd = &hfcsx_auxcmd; + return (1); +} diff --git a/drivers/isdn/hisax/hfc_sx.h b/drivers/isdn/hisax/hfc_sx.h new file mode 100644 index 000000000000..12f54159344a --- /dev/null +++ b/drivers/isdn/hisax/hfc_sx.h @@ -0,0 +1,197 @@ +/* $Id: hfc_sx.h,v 1.2.6.1 2001/09/23 22:24:48 kai Exp $ + * + * specific defines for CCD's HFC 2BDS0 S+,SP chips + * + * Author Werner Cornelius + * based on existing driver for CCD HFC PCI cards + * Copyright by Werner Cornelius <werner@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +/*********************************************/ +/* thresholds for transparent B-channel mode */ +/* change mask and threshold simultaneously */ +/*********************************************/ +#define HFCSX_BTRANS_THRESHOLD 128 +#define HFCSX_BTRANS_THRESMASK 0x00 + +/* GCI/IOM bus monitor registers */ + +#define HFCSX_C_I 0x02 +#define HFCSX_TRxR 0x03 +#define HFCSX_MON1_D 0x0A +#define HFCSX_MON2_D 0x0B + + +/* GCI/IOM bus timeslot registers */ + +#define HFCSX_B1_SSL 0x20 +#define HFCSX_B2_SSL 0x21 +#define HFCSX_AUX1_SSL 0x22 +#define HFCSX_AUX2_SSL 0x23 +#define HFCSX_B1_RSL 0x24 +#define HFCSX_B2_RSL 0x25 +#define HFCSX_AUX1_RSL 0x26 +#define HFCSX_AUX2_RSL 0x27 + +/* GCI/IOM bus data registers */ + +#define HFCSX_B1_D 0x28 +#define HFCSX_B2_D 0x29 +#define HFCSX_AUX1_D 0x2A +#define HFCSX_AUX2_D 0x2B + +/* GCI/IOM bus configuration registers */ + +#define HFCSX_MST_EMOD 0x2D +#define HFCSX_MST_MODE 0x2E +#define HFCSX_CONNECT 0x2F + + +/* Interrupt and status registers */ + +#define HFCSX_TRM 0x12 +#define HFCSX_B_MODE 0x13 +#define HFCSX_CHIP_ID 0x16 +#define HFCSX_CIRM 0x18 +#define HFCSX_CTMT 0x19 +#define HFCSX_INT_M1 0x1A +#define HFCSX_INT_M2 0x1B +#define HFCSX_INT_S1 0x1E +#define HFCSX_INT_S2 0x1F +#define HFCSX_STATUS 0x1C + +/* S/T section registers */ + +#define HFCSX_STATES 0x30 +#define HFCSX_SCTRL 0x31 +#define HFCSX_SCTRL_E 0x32 +#define HFCSX_SCTRL_R 0x33 +#define HFCSX_SQ 0x34 +#define HFCSX_CLKDEL 0x37 +#define HFCSX_B1_REC 0x3C +#define HFCSX_B1_SEND 0x3C +#define HFCSX_B2_REC 0x3D +#define HFCSX_B2_SEND 0x3D +#define HFCSX_D_REC 0x3E +#define HFCSX_D_SEND 0x3E +#define HFCSX_E_REC 0x3F + +/****************/ +/* FIFO section */ +/****************/ +#define HFCSX_FIF_SEL 0x10 +#define HFCSX_FIF_Z1L 0x80 +#define HFCSX_FIF_Z1H 0x84 +#define HFCSX_FIF_Z2L 0x88 +#define HFCSX_FIF_Z2H 0x8C +#define HFCSX_FIF_INCF1 0xA8 +#define HFCSX_FIF_DWR 0xAC +#define HFCSX_FIF_F1 0xB0 +#define HFCSX_FIF_F2 0xB4 +#define HFCSX_FIF_INCF2 0xB8 +#define HFCSX_FIF_DRD 0xBC + +/* bits in status register (READ) */ +#define HFCSX_SX_PROC 0x02 +#define HFCSX_NBUSY 0x04 +#define HFCSX_TIMER_ELAP 0x10 +#define HFCSX_STATINT 0x20 +#define HFCSX_FRAMEINT 0x40 +#define HFCSX_ANYINT 0x80 + +/* bits in CTMT (Write) */ +#define HFCSX_CLTIMER 0x80 +#define HFCSX_TIM3_125 0x04 +#define HFCSX_TIM25 0x10 +#define HFCSX_TIM50 0x14 +#define HFCSX_TIM400 0x18 +#define HFCSX_TIM800 0x1C +#define HFCSX_AUTO_TIMER 0x20 +#define HFCSX_TRANSB2 0x02 +#define HFCSX_TRANSB1 0x01 + +/* bits in CIRM (Write) */ +#define HFCSX_IRQ_SELMSK 0x07 +#define HFCSX_IRQ_SELDIS 0x00 +#define HFCSX_RESET 0x08 +#define HFCSX_FIFO_RESET 0x80 + + +/* bits in INT_M1 and INT_S1 */ +#define HFCSX_INTS_B1TRANS 0x01 +#define HFCSX_INTS_B2TRANS 0x02 +#define HFCSX_INTS_DTRANS 0x04 +#define HFCSX_INTS_B1REC 0x08 +#define HFCSX_INTS_B2REC 0x10 +#define HFCSX_INTS_DREC 0x20 +#define HFCSX_INTS_L1STATE 0x40 +#define HFCSX_INTS_TIMER 0x80 + +/* bits in INT_M2 */ +#define HFCSX_PROC_TRANS 0x01 +#define HFCSX_GCI_I_CHG 0x02 +#define HFCSX_GCI_MON_REC 0x04 +#define HFCSX_IRQ_ENABLE 0x08 + +/* bits in STATES */ +#define HFCSX_STATE_MSK 0x0F +#define HFCSX_LOAD_STATE 0x10 +#define HFCSX_ACTIVATE 0x20 +#define HFCSX_DO_ACTION 0x40 +#define HFCSX_NT_G2_G3 0x80 + +/* bits in HFCD_MST_MODE */ +#define HFCSX_MASTER 0x01 +#define HFCSX_SLAVE 0x00 +/* remaining bits are for codecs control */ + +/* bits in HFCD_SCTRL */ +#define SCTRL_B1_ENA 0x01 +#define SCTRL_B2_ENA 0x02 +#define SCTRL_MODE_TE 0x00 +#define SCTRL_MODE_NT 0x04 +#define SCTRL_LOW_PRIO 0x08 +#define SCTRL_SQ_ENA 0x10 +#define SCTRL_TEST 0x20 +#define SCTRL_NONE_CAP 0x40 +#define SCTRL_PWR_DOWN 0x80 + +/* bits in SCTRL_E */ +#define HFCSX_AUTO_AWAKE 0x01 +#define HFCSX_DBIT_1 0x04 +#define HFCSX_IGNORE_COL 0x08 +#define HFCSX_CHG_B1_B2 0x80 + +/**********************************/ +/* definitions for FIFO selection */ +/**********************************/ +#define HFCSX_SEL_D_RX 5 +#define HFCSX_SEL_D_TX 4 +#define HFCSX_SEL_B1_RX 1 +#define HFCSX_SEL_B1_TX 0 +#define HFCSX_SEL_B2_RX 3 +#define HFCSX_SEL_B2_TX 2 + +#define MAX_D_FRAMES 15 +#define MAX_B_FRAMES 31 +#define B_SUB_VAL_32K 0x0200 +#define B_FIFO_SIZE_32K (0x2000 - B_SUB_VAL_32K) +#define B_SUB_VAL_8K 0x1A00 +#define B_FIFO_SIZE_8K (0x2000 - B_SUB_VAL_8K) +#define D_FIFO_SIZE 512 +#define D_FREG_MASK 0xF + +/************************************************************/ +/* structure holding additional dynamic data -> send marker */ +/************************************************************/ +struct hfcsx_extra { + unsigned short marker[2*(MAX_B_FRAMES+1) + (MAX_D_FRAMES+1)]; +}; + +extern void main_irq_hfcsx(struct BCState *bcs); +extern void inithfcsx(struct IsdnCardState *cs); +extern void releasehfcsx(struct IsdnCardState *cs); diff --git a/drivers/isdn/hisax/hfc_usb.c b/drivers/isdn/hisax/hfc_usb.c new file mode 100644 index 000000000000..ffd74b84f502 --- /dev/null +++ b/drivers/isdn/hisax/hfc_usb.c @@ -0,0 +1,1828 @@ +/* + * hfc_usb.c + * + * $Id: hfc_usb.c,v 4.34 2005/01/26 17:25:53 martinb1 Exp $ + * + * modular HiSax ISDN driver for Colognechip HFC-S USB chip + * + * Authors : Peter Sprenger (sprenger@moving-bytes.de) + * Martin Bachem (info@colognechip.com) + * + * based on the first hfc_usb driver of + * Werner Cornelius (werner@isdn-development.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. + * + * See Version Histroy at the bottom of this file + * +*/ + +#include <linux/types.h> +#include <linux/stddef.h> +#include <linux/timer.h> +#include <linux/config.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kernel_stat.h> +#include <linux/usb.h> +#include <linux/kernel.h> +#include <linux/smp_lock.h> +#include <linux/sched.h> +#include "hisax.h" +#include "hisax_if.h" +#include "hfc_usb.h" + +/* +* Version Information +* (do not modify the CVS Makros $Revision: 4.34 $ and $Date: 2005/01/26 17:25:53 $ !) +*/ +static const char *hfcusb_revision = + "Revision: 4.34 $ Date: 2005/01/26 17:25:53 $ "; + +/* Hisax debug support +* use "modprobe debug=x" where x is bitfield of USB_DBG & ISDN_DBG +*/ +#ifdef CONFIG_HISAX_DEBUG +#include <linux/moduleparam.h> +#define __debug_variable hfc_debug +#include "hisax_debug.h" +static u_int debug; +module_param(debug, uint, 0); +int hfc_debug; +#endif + + +/****************************************/ +/* data defining the devices to be used */ +/****************************************/ +static struct usb_device_id hfc_usb_idtab[] = { + {USB_DEVICE(0x0959, 0x2bd0)}, /* Colognechip USB eval TA */ + {USB_DEVICE(0x0675, 0x1688)}, /* DrayTek miniVigor 128 USB ISDN TA */ + {USB_DEVICE(0x07b0, 0x0007)}, /* Billion USB TA 2 */ + {USB_DEVICE(0x0742, 0x2008)}, /* Stollmann USB TA */ + {USB_DEVICE(0x0742, 0x2009)}, /* Aceex USB ISDN TA */ + {USB_DEVICE(0x0742, 0x200A)}, /* OEM USB ISDN TA */ + {USB_DEVICE(0x08e3, 0x0301)}, /* OliTec ISDN USB */ + {USB_DEVICE(0x07fa, 0x0846)}, /* Bewan ISDN USB TA */ + {USB_DEVICE(0x07fa, 0x0847)}, /* Djinn Numeris USB */ + {USB_DEVICE(0x07b0, 0x0006)}, /* Twister ISDN USB TA */ + {} /* end with an all-zeroes entry */ +}; + +/* driver internal device specific data: +* VendorID, ProductID, Devicename, LED_SCHEME, +* LED's BitMask in HFCUSB_P_DATA Register : LED_USB, LED_S0, LED_B1, LED_B2 +*/ +vendor_data vdata[] = { + /* CologneChip Eval TA */ + {0x0959, 0x2bd0, "ISDN USB TA (Cologne Chip HFC-S USB based)", + LED_OFF, {4, 0, 2, 1} + } + , + /* DrayTek miniVigor 128 USB ISDN TA */ + {0x0675, 0x1688, "DrayTek miniVigor 128 USB ISDN TA", + LED_SCHEME1, {1, 2, 0, 0} + } + , + /* Billion TA */ + {0x07b0, 0x0007, "Billion tiny USB ISDN TA 128", + LED_SCHEME1, {0x80, -64, -32, -16} + } + , + /* Stollmann TA */ + {0x0742, 0x2008, "Stollmann USB TA", + LED_SCHEME1, {4, 0, 2, 1} + } + , + /* Aceex USB ISDN TA */ + {0x0742, 0x2009, "Aceex USB ISDN TA", + LED_SCHEME1, {4, 0, 2, 1} + } + , + /* OEM USB ISDN TA */ + {0x0742, 0x200A, "OEM USB ISDN TA", + LED_SCHEME1, {4, 0, 2, 1} + } + , + /* Olitec TA */ + {0x08e3, 0x0301, "Olitec USB RNIS", + LED_SCHEME1, {2, 0, 1, 4} + } + , + /* Bewan TA */ + {0x07fa, 0x0846, "Bewan Modem RNIS USB", + LED_SCHEME1, {0x80, -64, -32, -16} + } + , + /* Bewan TA */ + {0x07fa, 0x0847, "Djinn Numeris USB", + LED_SCHEME1, {0x80, -64, -32, -16} + } + , + /* Twister ISDN TA */ + {0x07b0, 0x0006, "Twister ISDN TA", + LED_SCHEME1, {0x80, -64, -32, -16} + } + , + {0, 0, 0} /* EOL element */ +}; + +/***************************************************************/ +/* structure defining input+output fifos (interrupt/bulk mode) */ +/***************************************************************/ +struct usb_fifo; /* forward definition */ +typedef struct iso_urb_struct { + struct urb *purb; + __u8 buffer[ISO_BUFFER_SIZE]; /* buffer incoming/outgoing data */ + struct usb_fifo *owner_fifo; /* pointer to owner fifo */ +} iso_urb_struct; + + +struct hfcusb_data; /* forward definition */ +typedef struct usb_fifo { + int fifonum; /* fifo index attached to this structure */ + int active; /* fifo is currently active */ + struct hfcusb_data *hfc; /* pointer to main structure */ + int pipe; /* address of endpoint */ + __u8 usb_packet_maxlen; /* maximum length for usb transfer */ + unsigned int max_size; /* maximum size of receive/send packet */ + __u8 intervall; /* interrupt interval */ + struct sk_buff *skbuff; /* actual used buffer */ + struct urb *urb; /* transfer structure for usb routines */ + __u8 buffer[128]; /* buffer incoming/outgoing data */ + int bit_line; /* how much bits are in the fifo? */ + + volatile __u8 usb_transfer_mode; /* switched between ISO and INT */ + iso_urb_struct iso[2]; /* need two urbs to have one always for pending */ + struct hisax_if *hif; /* hisax interface */ + int delete_flg; /* only delete skbuff once */ + int last_urblen; /* remember length of last packet */ + +} usb_fifo; + +/*********************************************/ +/* structure holding all data for one device */ +/*********************************************/ +typedef struct hfcusb_data { + /* HiSax Interface for loadable Layer1 drivers */ + struct hisax_d_if d_if; /* see hisax_if.h */ + struct hisax_b_if b_if[2]; /* see hisax_if.h */ + int protocol; + + struct usb_device *dev; /* our device */ + int if_used; /* used interface number */ + int alt_used; /* used alternate config */ + int ctrl_paksize; /* control pipe packet size */ + int ctrl_in_pipe, ctrl_out_pipe; /* handles for control pipe */ + int cfg_used; /* configuration index used */ + int vend_idx; /* vendor found */ + int b_mode[2]; /* B-channel mode */ + int l1_activated; /* layer 1 activated */ + int disc_flag; /* TRUE if device was disonnected to avoid some USB actions */ + int packet_size, iso_packet_size; + + /* control pipe background handling */ + ctrl_buft ctrl_buff[HFC_CTRL_BUFSIZE]; /* buffer holding queued data */ + volatile int ctrl_in_idx, ctrl_out_idx, ctrl_cnt; /* input/output pointer + count */ + struct urb *ctrl_urb; /* transfer structure for control channel */ + + struct usb_ctrlrequest ctrl_write; /* buffer for control write request */ + struct usb_ctrlrequest ctrl_read; /* same for read request */ + + __u8 old_led_state, led_state, led_new_data, led_b_active; + + volatile __u8 threshold_mask; /* threshold actually reported */ + volatile __u8 bch_enables; /* or mask for sctrl_r and sctrl register values */ + + usb_fifo fifos[HFCUSB_NUM_FIFOS]; /* structure holding all fifo data */ + + volatile __u8 l1_state; /* actual l1 state */ + struct timer_list t3_timer; /* timer 3 for activation/deactivation */ + struct timer_list t4_timer; /* timer 4 for activation/deactivation */ + struct timer_list led_timer; /* timer flashing leds */ + +} hfcusb_data; + + +static void collect_rx_frame(usb_fifo * fifo, __u8 * data, int len, + int finish); + + +static inline const char * +symbolic(struct hfcusb_symbolic_list list[], const int num) +{ + int i; + for (i = 0; list[i].name != NULL; i++) + if (list[i].num == num) + return (list[i].name); + return "<unkown>"; +} + + +/******************************************************/ +/* start next background transfer for control channel */ +/******************************************************/ +static void +ctrl_start_transfer(hfcusb_data * hfc) +{ + if (hfc->ctrl_cnt) { + hfc->ctrl_urb->pipe = hfc->ctrl_out_pipe; + hfc->ctrl_urb->setup_packet = (u_char *) & hfc->ctrl_write; + hfc->ctrl_urb->transfer_buffer = NULL; + hfc->ctrl_urb->transfer_buffer_length = 0; + hfc->ctrl_write.wIndex = + hfc->ctrl_buff[hfc->ctrl_out_idx].hfc_reg; + hfc->ctrl_write.wValue = + hfc->ctrl_buff[hfc->ctrl_out_idx].reg_val; + + usb_submit_urb(hfc->ctrl_urb, GFP_ATOMIC); /* start transfer */ + } +} /* ctrl_start_transfer */ + +/************************************/ +/* queue a control transfer request */ +/* return 0 on success. */ +/************************************/ +static int +queue_control_request(hfcusb_data * hfc, __u8 reg, __u8 val, int action) +{ + ctrl_buft *buf; + + if (hfc->ctrl_cnt >= HFC_CTRL_BUFSIZE) + return (1); /* no space left */ + buf = &hfc->ctrl_buff[hfc->ctrl_in_idx]; /* pointer to new index */ + buf->hfc_reg = reg; + buf->reg_val = val; + buf->action = action; + if (++hfc->ctrl_in_idx >= HFC_CTRL_BUFSIZE) + hfc->ctrl_in_idx = 0; /* pointer wrap */ + if (++hfc->ctrl_cnt == 1) + ctrl_start_transfer(hfc); + return (0); +} /* queue_control_request */ + +static int +control_action_handler(hfcusb_data * hfc, int reg, int val, int action) +{ + if (!action) + return (1); /* no action defined */ + return (0); +} + +/***************************************************************/ +/* control completion routine handling background control cmds */ +/***************************************************************/ +static void +ctrl_complete(struct urb *urb, struct pt_regs *regs) +{ + hfcusb_data *hfc = (hfcusb_data *) urb->context; + ctrl_buft *buf; + + urb->dev = hfc->dev; + if (hfc->ctrl_cnt) { + buf = &hfc->ctrl_buff[hfc->ctrl_out_idx]; + control_action_handler(hfc, buf->hfc_reg, buf->reg_val, + buf->action); + + hfc->ctrl_cnt--; /* decrement actual count */ + if (++hfc->ctrl_out_idx >= HFC_CTRL_BUFSIZE) + hfc->ctrl_out_idx = 0; /* pointer wrap */ + + ctrl_start_transfer(hfc); /* start next transfer */ + } +} /* ctrl_complete */ + +/***************************************************/ +/* write led data to auxport & invert if necessary */ +/***************************************************/ +static void +write_led(hfcusb_data * hfc, __u8 led_state) +{ + if (led_state != hfc->old_led_state) { + hfc->old_led_state = led_state; + queue_control_request(hfc, HFCUSB_P_DATA, led_state, 1); + } +} + +/**************************/ +/* handle LED bits */ +/**************************/ +static void +set_led_bit(hfcusb_data * hfc, signed short led_bits, int unset) +{ + if (unset) { + if (led_bits < 0) + hfc->led_state |= abs(led_bits); + else + hfc->led_state &= ~led_bits; + } else { + if (led_bits < 0) + hfc->led_state &= ~abs(led_bits); + else + hfc->led_state |= led_bits; + } +} + +/******************************************/ +/* invert B-channel LEDs if data is sent */ +/******************************************/ +static void +led_timer(hfcusb_data * hfc) +{ + static int cnt = 0; + + if (cnt) { + if (hfc->led_b_active & 1) + set_led_bit(hfc, vdata[hfc->vend_idx].led_bits[2], + 0); + if (hfc->led_b_active & 2) + set_led_bit(hfc, vdata[hfc->vend_idx].led_bits[3], + 0); + } else { + if (!(hfc->led_b_active & 1) || hfc->led_new_data & 1) + set_led_bit(hfc, vdata[hfc->vend_idx].led_bits[2], + 1); + if (!(hfc->led_b_active & 2) || hfc->led_new_data & 2) + set_led_bit(hfc, vdata[hfc->vend_idx].led_bits[3], + 1); + } + + write_led(hfc, hfc->led_state); + hfc->led_new_data = 0; + + cnt = !cnt; + + /* restart 4 hz timer */ + if (!timer_pending(&hfc->led_timer)) { + add_timer(&hfc->led_timer); + hfc->led_timer.expires = jiffies + (LED_TIME * HZ) / 1000; + } +} + +/**************************/ +/* handle LED requests */ +/**************************/ +static void +handle_led(hfcusb_data * hfc, int event) +{ + /* if no scheme -> no LED action */ + if (vdata[hfc->vend_idx].led_scheme == LED_OFF) + return; + + switch (event) { + case LED_POWER_ON: + set_led_bit(hfc, vdata[hfc->vend_idx].led_bits[0], + 0); + set_led_bit(hfc, vdata[hfc->vend_idx].led_bits[1], + 1); + set_led_bit(hfc, vdata[hfc->vend_idx].led_bits[2], + 1); + set_led_bit(hfc, vdata[hfc->vend_idx].led_bits[3], + 1); + break; + case LED_POWER_OFF: /* no Power off handling */ + break; + case LED_S0_ON: + set_led_bit(hfc, vdata[hfc->vend_idx].led_bits[1], + 0); + break; + case LED_S0_OFF: + set_led_bit(hfc, vdata[hfc->vend_idx].led_bits[1], + 1); + break; + case LED_B1_ON: + hfc->led_b_active |= 1; + break; + case LED_B1_OFF: + hfc->led_b_active &= ~1; + break; + case LED_B1_DATA: + hfc->led_new_data |= 1; + break; + case LED_B2_ON: + hfc->led_b_active |= 2; + break; + case LED_B2_OFF: + hfc->led_b_active &= ~2; + break; + case LED_B2_DATA: + hfc->led_new_data |= 2; + break; + } + + write_led(hfc, hfc->led_state); +} + +/********************************/ +/* called when timer t3 expires */ +/********************************/ +static void +l1_timer_expire_t3(hfcusb_data * hfc) +{ + hfc->d_if.ifc.l1l2(&hfc->d_if.ifc, PH_DEACTIVATE | INDICATION, + NULL); +#ifdef CONFIG_HISAX_DEBUG + DBG(ISDN_DBG, + "HFC-S USB: PH_DEACTIVATE | INDICATION sent (T3 expire)"); +#endif + hfc->l1_activated = FALSE; + handle_led(hfc, LED_S0_OFF); + /* deactivate : */ + queue_control_request(hfc, HFCUSB_STATES, 0x10, 1); + queue_control_request(hfc, HFCUSB_STATES, 3, 1); +} + +/********************************/ +/* called when timer t4 expires */ +/********************************/ +static void +l1_timer_expire_t4(hfcusb_data * hfc) +{ + hfc->d_if.ifc.l1l2(&hfc->d_if.ifc, PH_DEACTIVATE | INDICATION, + NULL); +#ifdef CONFIG_HISAX_DEBUG + DBG(ISDN_DBG, + "HFC-S USB: PH_DEACTIVATE | INDICATION sent (T4 expire)"); +#endif + hfc->l1_activated = FALSE; + handle_led(hfc, LED_S0_OFF); +} + +/*****************************/ +/* handle S0 state changes */ +/*****************************/ +static void +state_handler(hfcusb_data * hfc, __u8 state) +{ + __u8 old_state; + + old_state = hfc->l1_state; + if (state == old_state || state < 1 || state > 8) + return; + +#ifdef CONFIG_HISAX_DEBUG + DBG(ISDN_DBG, "HFC-S USB: new S0 state:%d old_state:%d", state, + old_state); +#endif + if (state < 4 || state == 7 || state == 8) { + if (timer_pending(&hfc->t3_timer)) + del_timer(&hfc->t3_timer); +#ifdef CONFIG_HISAX_DEBUG + DBG(ISDN_DBG, "HFC-S USB: T3 deactivated"); +#endif + } + if (state >= 7) { + if (timer_pending(&hfc->t4_timer)) + del_timer(&hfc->t4_timer); +#ifdef CONFIG_HISAX_DEBUG + DBG(ISDN_DBG, "HFC-S USB: T4 deactivated"); +#endif + } + + if (state == 7 && !hfc->l1_activated) { + hfc->d_if.ifc.l1l2(&hfc->d_if.ifc, + PH_ACTIVATE | INDICATION, NULL); +#ifdef CONFIG_HISAX_DEBUG + DBG(ISDN_DBG, "HFC-S USB: PH_ACTIVATE | INDICATION sent"); +#endif + hfc->l1_activated = TRUE; + handle_led(hfc, LED_S0_ON); + } else if (state <= 3 /* && activated */ ) { + if (old_state == 7 || old_state == 8) { +#ifdef CONFIG_HISAX_DEBUG + DBG(ISDN_DBG, "HFC-S USB: T4 activated"); +#endif + if (!timer_pending(&hfc->t4_timer)) { + hfc->t4_timer.expires = + jiffies + (HFC_TIMER_T4 * HZ) / 1000; + add_timer(&hfc->t4_timer); + } + } else { + hfc->d_if.ifc.l1l2(&hfc->d_if.ifc, + PH_DEACTIVATE | INDICATION, + NULL); +#ifdef CONFIG_HISAX_DEBUG + DBG(ISDN_DBG, + "HFC-S USB: PH_DEACTIVATE | INDICATION sent"); +#endif + hfc->l1_activated = FALSE; + handle_led(hfc, LED_S0_OFF); + } + } + hfc->l1_state = state; +} + +/* prepare iso urb */ +static void +fill_isoc_urb(struct urb *urb, struct usb_device *dev, unsigned int pipe, + void *buf, int num_packets, int packet_size, int interval, + usb_complete_t complete, void *context) +{ + int k; + + spin_lock_init(&urb->lock); + urb->dev = dev; + urb->pipe = pipe; + urb->complete = complete; + urb->number_of_packets = num_packets; + urb->transfer_buffer_length = packet_size * num_packets; + urb->context = context; + urb->transfer_buffer = buf; + urb->transfer_flags = URB_ISO_ASAP; + urb->actual_length = 0; + urb->interval = interval; + for (k = 0; k < num_packets; k++) { + urb->iso_frame_desc[k].offset = packet_size * k; + urb->iso_frame_desc[k].length = packet_size; + urb->iso_frame_desc[k].actual_length = 0; + } +} + +/* allocs urbs and start isoc transfer with two pending urbs to avoid + gaps in the transfer chain */ +static int +start_isoc_chain(usb_fifo * fifo, int num_packets_per_urb, + usb_complete_t complete, int packet_size) +{ + int i, k, errcode; + + printk(KERN_INFO "HFC-S USB: starting ISO-chain for Fifo %i\n", + fifo->fifonum); + + /* allocate Memory for Iso out Urbs */ + for (i = 0; i < 2; i++) { + if (!(fifo->iso[i].purb)) { + fifo->iso[i].purb = + usb_alloc_urb(num_packets_per_urb, GFP_KERNEL); + if (!(fifo->iso[i].purb)) { + printk(KERN_INFO + "alloc urb for fifo %i failed!!!", + fifo->fifonum); + } + fifo->iso[i].owner_fifo = (struct usb_fifo *) fifo; + + /* Init the first iso */ + if (ISO_BUFFER_SIZE >= + (fifo->usb_packet_maxlen * + num_packets_per_urb)) { + fill_isoc_urb(fifo->iso[i].purb, + fifo->hfc->dev, fifo->pipe, + fifo->iso[i].buffer, + num_packets_per_urb, + fifo->usb_packet_maxlen, + fifo->intervall, complete, + &fifo->iso[i]); + memset(fifo->iso[i].buffer, 0, + sizeof(fifo->iso[i].buffer)); + /* defining packet delimeters in fifo->buffer */ + for (k = 0; k < num_packets_per_urb; k++) { + fifo->iso[i].purb-> + iso_frame_desc[k].offset = + k * packet_size; + fifo->iso[i].purb-> + iso_frame_desc[k].length = + packet_size; + } + } else { + printk(KERN_INFO + "HFC-S USB: ISO Buffer size to small!\n"); + } + } + fifo->bit_line = BITLINE_INF; + + errcode = usb_submit_urb(fifo->iso[i].purb, GFP_KERNEL); + fifo->active = (errcode >= 0) ? 1 : 0; + if (errcode < 0) { + printk(KERN_INFO "HFC-S USB: %s URB nr:%d\n", + symbolic(urb_errlist, errcode), i); + }; + } + return (fifo->active); +} + +/* stops running iso chain and frees their pending urbs */ +static void +stop_isoc_chain(usb_fifo * fifo) +{ + int i; + + for (i = 0; i < 2; i++) { + if (fifo->iso[i].purb) { +#ifdef CONFIG_HISAX_DEBUG + DBG(USB_DBG, + "HFC-S USB: Stopping iso chain for fifo %i.%i", + fifo->fifonum, i); +#endif + usb_unlink_urb(fifo->iso[i].purb); + usb_free_urb(fifo->iso[i].purb); + fifo->iso[i].purb = NULL; + } + } + if (fifo->urb) { + usb_unlink_urb(fifo->urb); + usb_free_urb(fifo->urb); + fifo->urb = NULL; + } + fifo->active = 0; +} + +/* defines how much ISO packets are handled in one URB */ +static int iso_packets[8] = + { ISOC_PACKETS_B, ISOC_PACKETS_B, ISOC_PACKETS_B, ISOC_PACKETS_B, + ISOC_PACKETS_D, ISOC_PACKETS_D, ISOC_PACKETS_D, ISOC_PACKETS_D +}; + +/*****************************************************/ +/* transmit completion routine for all ISO tx fifos */ +/*****************************************************/ +static void +tx_iso_complete(struct urb *urb, struct pt_regs *regs) +{ + iso_urb_struct *context_iso_urb = (iso_urb_struct *) urb->context; + usb_fifo *fifo = context_iso_urb->owner_fifo; + hfcusb_data *hfc = fifo->hfc; + int k, tx_offset, num_isoc_packets, sink, len, current_len, + errcode; + int frame_complete, transp_mode, fifon, status; + __u8 threshbit; + __u8 threshtable[8] = { 1, 2, 4, 8, 0x10, 0x20, 0x40, 0x80 }; + + fifon = fifo->fifonum; + status = urb->status; + + tx_offset = 0; + + if (fifo->active && !status) { + transp_mode = 0; + if (fifon < 4 && hfc->b_mode[fifon / 2] == L1_MODE_TRANS) + transp_mode = TRUE; + + /* is FifoFull-threshold set for our channel? */ + threshbit = threshtable[fifon] & hfc->threshold_mask; + num_isoc_packets = iso_packets[fifon]; + + /* predict dataflow to avoid fifo overflow */ + if (fifon >= HFCUSB_D_TX) { + sink = (threshbit) ? SINK_DMIN : SINK_DMAX; + } else { + sink = (threshbit) ? SINK_MIN : SINK_MAX; + } + fill_isoc_urb(urb, fifo->hfc->dev, fifo->pipe, + context_iso_urb->buffer, num_isoc_packets, + fifo->usb_packet_maxlen, fifo->intervall, + tx_iso_complete, urb->context); + memset(context_iso_urb->buffer, 0, + sizeof(context_iso_urb->buffer)); + frame_complete = FALSE; + /* Generate next Iso Packets */ + for (k = 0; k < num_isoc_packets; ++k) { + if (fifo->skbuff) { + len = fifo->skbuff->len; + /* we lower data margin every msec */ + fifo->bit_line -= sink; + current_len = (0 - fifo->bit_line) / 8; + /* maximum 15 byte for every ISO packet makes our life easier */ + if (current_len > 14) + current_len = 14; + current_len = + (len <= + current_len) ? len : current_len; + /* how much bit do we put on the line? */ + fifo->bit_line += current_len * 8; + + context_iso_urb->buffer[tx_offset] = 0; + if (current_len == len) { + if (!transp_mode) { + /* here frame completion */ + context_iso_urb-> + buffer[tx_offset] = 1; + /* add 2 byte flags and 16bit CRC at end of ISDN frame */ + fifo->bit_line += 32; + } + frame_complete = TRUE; + } + + memcpy(context_iso_urb->buffer + + tx_offset + 1, fifo->skbuff->data, + current_len); + skb_pull(fifo->skbuff, current_len); + + /* define packet delimeters within the URB buffer */ + urb->iso_frame_desc[k].offset = tx_offset; + urb->iso_frame_desc[k].length = + current_len + 1; + + tx_offset += (current_len + 1); + if (!transp_mode) { + if (fifon == HFCUSB_B1_TX) + handle_led(hfc, + LED_B1_DATA); + if (fifon == HFCUSB_B2_TX) + handle_led(hfc, + LED_B2_DATA); + } + } else { + urb->iso_frame_desc[k].offset = + tx_offset++; + + urb->iso_frame_desc[k].length = 1; + fifo->bit_line -= sink; /* we lower data margin every msec */ + + if (fifo->bit_line < BITLINE_INF) { + fifo->bit_line = BITLINE_INF; + } + } + + if (frame_complete) { + fifo->delete_flg = TRUE; + fifo->hif->l1l2(fifo->hif, + PH_DATA | CONFIRM, + (void *) fifo->skbuff-> + truesize); + if (fifo->skbuff && fifo->delete_flg) { + dev_kfree_skb_any(fifo->skbuff); + fifo->skbuff = NULL; + fifo->delete_flg = FALSE; + } + frame_complete = FALSE; + } + } + errcode = usb_submit_urb(urb, GFP_ATOMIC); + if (errcode < 0) { + printk(KERN_INFO + "HFC-S USB: error submitting ISO URB: %d \n", + errcode); + } + } else { + if (status && !hfc->disc_flag) { + printk(KERN_INFO + "HFC-S USB: tx_iso_complete : urb->status %s (%i), fifonum=%d\n", + symbolic(urb_errlist, status), status, + fifon); + } + } +} /* tx_iso_complete */ + +/*****************************************************/ +/* receive completion routine for all ISO tx fifos */ +/*****************************************************/ +static void +rx_iso_complete(struct urb *urb, struct pt_regs *regs) +{ + iso_urb_struct *context_iso_urb = (iso_urb_struct *) urb->context; + usb_fifo *fifo = context_iso_urb->owner_fifo; + hfcusb_data *hfc = fifo->hfc; + int k, len, errcode, offset, num_isoc_packets, fifon, maxlen, + status; + unsigned int iso_status; + __u8 *buf; + static __u8 eof[8]; +#ifdef CONFIG_HISAX_DEBUG + __u8 i; +#endif + + fifon = fifo->fifonum; + status = urb->status; + + if (urb->status == -EOVERFLOW) { +#ifdef CONFIG_HISAX_DEBUG + DBG(USB_DBG, + "HFC-USB: ignoring USB DATAOVERRUN for fifo %i \n", + fifon); +#endif + status = 0; + } + if (fifo->active && !status) { + num_isoc_packets = iso_packets[fifon]; + maxlen = fifo->usb_packet_maxlen; + for (k = 0; k < num_isoc_packets; ++k) { + len = urb->iso_frame_desc[k].actual_length; + offset = urb->iso_frame_desc[k].offset; + buf = context_iso_urb->buffer + offset; + iso_status = urb->iso_frame_desc[k].status; +#ifdef CONFIG_HISAX_DEBUG + if (iso_status && !hfc->disc_flag) + DBG(USB_DBG, + "HFC-S USB: ISO packet failure - status:%x", + iso_status); + + if ((fifon == 5) && (debug > 1)) { + printk(KERN_INFO + "HFC-S USB: ISO-D-RX lst_urblen:%2d " + "act_urblen:%2d max-urblen:%2d " + "EOF:0x%0x DATA: ", + fifo->last_urblen, len, maxlen, + eof[5]); + for (i = 0; i < len; i++) + printk("%.2x ", buf[i]); + printk("\n"); + } +#endif + if (fifo->last_urblen != maxlen) { + /* the threshold mask is in the 2nd status byte */ + hfc->threshold_mask = buf[1]; + /* care for L1 state only for D-Channel + to avoid overlapped iso completions */ + if (fifon == 5) { + /* the S0 state is in the upper half + of the 1st status byte */ + state_handler(hfc, buf[0] >> 4); + } + eof[fifon] = buf[0] & 1; + if (len > 2) + collect_rx_frame(fifo, buf + 2, + len - 2, + (len < + maxlen) ? + eof[fifon] : 0); + } else { + collect_rx_frame(fifo, buf, len, + (len < + maxlen) ? eof[fifon] : + 0); + } + fifo->last_urblen = len; + } + + fill_isoc_urb(urb, fifo->hfc->dev, fifo->pipe, + context_iso_urb->buffer, num_isoc_packets, + fifo->usb_packet_maxlen, fifo->intervall, + rx_iso_complete, urb->context); + errcode = usb_submit_urb(urb, GFP_ATOMIC); + if (errcode < 0) { + printk(KERN_INFO + "HFC-S USB: error submitting ISO URB: %d \n", + errcode); + } + } else { + if (status && !hfc->disc_flag) { + printk(KERN_INFO + "HFC-S USB: rx_iso_complete : " + "urb->status %d, fifonum %d\n", + status, fifon); + } + } +} /* rx_iso_complete */ + +/*****************************************************/ +/* collect data from interrupt or isochron in */ +/*****************************************************/ +static void +collect_rx_frame(usb_fifo * fifo, __u8 * data, int len, int finish) +{ + hfcusb_data *hfc = fifo->hfc; + int transp_mode, fifon; +#ifdef CONFIG_HISAX_DEBUG + int i; +#endif + fifon = fifo->fifonum; + transp_mode = 0; + if (fifon < 4 && hfc->b_mode[fifon / 2] == L1_MODE_TRANS) + transp_mode = TRUE; + + if (!fifo->skbuff) { + fifo->skbuff = dev_alloc_skb(fifo->max_size + 3); + if (!fifo->skbuff) { + printk(KERN_INFO + "HFC-S USB: cannot allocate buffer (dev_alloc_skb) fifo:%d\n", + fifon); + return; + } + } + if (len) { + if (fifo->skbuff->len + len < fifo->max_size) { + memcpy(skb_put(fifo->skbuff, len), data, len); + } else { +#ifdef CONFIG_HISAX_DEBUG + printk(KERN_INFO "HFC-S USB: "); + for (i = 0; i < 15; i++) + printk("%.2x ", + fifo->skbuff->data[fifo->skbuff-> + len - 15 + i]); + printk("\n"); +#endif + printk(KERN_INFO + "HCF-USB: got frame exceeded fifo->max_size:%d on fifo:%d\n", + fifo->max_size, fifon); + } + } + if (transp_mode && fifo->skbuff->len >= 128) { + fifo->hif->l1l2(fifo->hif, PH_DATA | INDICATION, + fifo->skbuff); + fifo->skbuff = NULL; + return; + } + /* we have a complete hdlc packet */ + if (finish) { + if ((!fifo->skbuff->data[fifo->skbuff->len - 1]) + && (fifo->skbuff->len > 3)) { + /* remove CRC & status */ + skb_trim(fifo->skbuff, fifo->skbuff->len - 3); + if (fifon == HFCUSB_PCM_RX) { + fifo->hif->l1l2(fifo->hif, + PH_DATA_E | INDICATION, + fifo->skbuff); + } else + fifo->hif->l1l2(fifo->hif, + PH_DATA | INDICATION, + fifo->skbuff); + fifo->skbuff = NULL; /* buffer was freed from upper layer */ + } else { + if (fifo->skbuff->len > 3) { + printk(KERN_INFO + "HFC-S USB: got frame %d bytes but CRC ERROR on fifo:%d!!!\n", + fifo->skbuff->len, fifon); +#ifdef CONFIG_HISAX_DEBUG + if (debug > 1) { + printk(KERN_INFO "HFC-S USB: "); + for (i = 0; i < 15; i++) + printk("%.2x ", + fifo->skbuff-> + data[fifo->skbuff-> + len - 15 + i]); + printk("\n"); + } +#endif + } +#ifdef CONFIG_HISAX_DEBUG + else { + printk(KERN_INFO + "HFC-S USB: frame to small (%d bytes)!!!\n", + fifo->skbuff->len); + } +#endif + skb_trim(fifo->skbuff, 0); + } + } + + /* LED flashing only in HDLC mode */ + if (!transp_mode) { + if (fifon == HFCUSB_B1_RX) + handle_led(hfc, LED_B1_DATA); + if (fifon == HFCUSB_B2_RX) + handle_led(hfc, LED_B2_DATA); + } +} + +/***********************************************/ +/* receive completion routine for all rx fifos */ +/***********************************************/ +static void +rx_complete(struct urb *urb, struct pt_regs *regs) +{ + int len; + int status; + __u8 *buf, maxlen, fifon; + usb_fifo *fifo = (usb_fifo *) urb->context; + hfcusb_data *hfc = fifo->hfc; + static __u8 eof[8]; +#ifdef CONFIG_HISAX_DEBUG + __u8 i; +#endif + + urb->dev = hfc->dev; /* security init */ + + fifon = fifo->fifonum; + if ((!fifo->active) || (urb->status)) { +#ifdef CONFIG_HISAX_DEBUG + DBG(USB_DBG, "HFC-S USB: RX-Fifo %i is going down (%i)", + fifon, urb->status); +#endif + fifo->urb->interval = 0; /* cancel automatic rescheduling */ + if (fifo->skbuff) { + dev_kfree_skb_any(fifo->skbuff); + fifo->skbuff = NULL; + } + return; + } + len = urb->actual_length; + buf = fifo->buffer; + maxlen = fifo->usb_packet_maxlen; + +#ifdef CONFIG_HISAX_DEBUG + if ((fifon == 5) && (debug > 1)) { + printk(KERN_INFO + "HFC-S USB: INT-D-RX lst_urblen:%2d act_urblen:%2d max-urblen:%2d EOF:0x%0x DATA: ", + fifo->last_urblen, len, maxlen, eof[5]); + for (i = 0; i < len; i++) + printk("%.2x ", buf[i]); + printk("\n"); + } +#endif + + if (fifo->last_urblen != fifo->usb_packet_maxlen) { + /* the threshold mask is in the 2nd status byte */ + hfc->threshold_mask = buf[1]; + /* the S0 state is in the upper half of the 1st status byte */ + state_handler(hfc, buf[0] >> 4); + eof[fifon] = buf[0] & 1; + /* if we have more than the 2 status bytes -> collect data */ + if (len > 2) + collect_rx_frame(fifo, buf + 2, + urb->actual_length - 2, + (len < maxlen) ? eof[fifon] : 0); + } else { + collect_rx_frame(fifo, buf, urb->actual_length, + (len < maxlen) ? eof[fifon] : 0); + } + fifo->last_urblen = urb->actual_length; + status = usb_submit_urb(urb, GFP_ATOMIC); + if (status) { + printk(KERN_INFO + "HFC-S USB: error resubmitting URN at rx_complete...\n"); + } +} /* rx_complete */ + +/***************************************************/ +/* start the interrupt transfer for the given fifo */ +/***************************************************/ +static void +start_int_fifo(usb_fifo * fifo) +{ + int errcode; + + printk(KERN_INFO "HFC-S USB: starting intr IN fifo:%d\n", + fifo->fifonum); + + if (!fifo->urb) { + fifo->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!fifo->urb) + return; + } + usb_fill_int_urb(fifo->urb, fifo->hfc->dev, fifo->pipe, + fifo->buffer, fifo->usb_packet_maxlen, + rx_complete, fifo, fifo->intervall); + fifo->active = 1; /* must be marked active */ + errcode = usb_submit_urb(fifo->urb, GFP_KERNEL); + if (errcode) { + printk(KERN_INFO + "HFC-S USB: submit URB error(start_int_info): status:%i\n", + errcode); + fifo->active = 0; + fifo->skbuff = NULL; + } +} /* start_int_fifo */ + +/*****************************/ +/* set the B-channel mode */ +/*****************************/ +static void +set_hfcmode(hfcusb_data * hfc, int channel, int mode) +{ + __u8 val, idx_table[2] = { 0, 2 }; + + if (hfc->disc_flag) { + return; + } +#ifdef CONFIG_HISAX_DEBUG + DBG(ISDN_DBG, "HFC-S USB: setting channel %d to mode %d", channel, + mode); +#endif + hfc->b_mode[channel] = mode; + + /* setup CON_HDLC */ + val = 0; + if (mode != L1_MODE_NULL) + val = 8; /* enable fifo? */ + if (mode == L1_MODE_TRANS) + val |= 2; /* set transparent bit */ + + /* set FIFO to transmit register */ + queue_control_request(hfc, HFCUSB_FIFO, idx_table[channel], 1); + queue_control_request(hfc, HFCUSB_CON_HDLC, val, 1); + /* reset fifo */ + queue_control_request(hfc, HFCUSB_INC_RES_F, 2, 1); + /* set FIFO to receive register */ + queue_control_request(hfc, HFCUSB_FIFO, idx_table[channel] + 1, 1); + queue_control_request(hfc, HFCUSB_CON_HDLC, val, 1); + /* reset fifo */ + queue_control_request(hfc, HFCUSB_INC_RES_F, 2, 1); + + val = 0x40; + if (hfc->b_mode[0]) + val |= 1; + if (hfc->b_mode[1]) + val |= 2; + queue_control_request(hfc, HFCUSB_SCTRL, val, 1); + + val = 0; + if (hfc->b_mode[0]) + val |= 1; + if (hfc->b_mode[1]) + val |= 2; + queue_control_request(hfc, HFCUSB_SCTRL_R, val, 1); + + if (mode == L1_MODE_NULL) { + if (channel) + handle_led(hfc, LED_B2_OFF); + else + handle_led(hfc, LED_B1_OFF); + } else { + if (channel) + handle_led(hfc, LED_B2_ON); + else + handle_led(hfc, LED_B1_ON); + } +} + +void +hfc_usb_l2l1(struct hisax_if *my_hisax_if, int pr, void *arg) +{ + usb_fifo *fifo = my_hisax_if->priv; + hfcusb_data *hfc = fifo->hfc; + + switch (pr) { + case PH_ACTIVATE | REQUEST: + if (fifo->fifonum == HFCUSB_D_TX) { +#ifdef CONFIG_HISAX_DEBUG + DBG(ISDN_DBG, + "HFC_USB: hfc_usb_d_l2l1 D-chan: PH_ACTIVATE | REQUEST"); +#endif + if (hfc->l1_state != 3 + && hfc->l1_state != 7) { + hfc->d_if.ifc.l1l2(&hfc->d_if.ifc, + PH_DEACTIVATE | + INDICATION, + NULL); +#ifdef CONFIG_HISAX_DEBUG + DBG(ISDN_DBG, + "HFC-S USB: PH_DEACTIVATE | INDICATION sent (not state 3 or 7)"); +#endif + } else { + if (hfc->l1_state == 7) { /* l1 already active */ + hfc->d_if.ifc.l1l2(&hfc-> + d_if. + ifc, + PH_ACTIVATE + | + INDICATION, + NULL); +#ifdef CONFIG_HISAX_DEBUG + DBG(ISDN_DBG, + "HFC-S USB: PH_ACTIVATE | INDICATION sent again ;)"); +#endif + } else { + /* force sending sending INFO1 */ + queue_control_request(hfc, + HFCUSB_STATES, + 0x14, + 1); + mdelay(1); + /* start l1 activation */ + queue_control_request(hfc, + HFCUSB_STATES, + 0x04, + 1); + if (!timer_pending + (&hfc->t3_timer)) { + hfc->t3_timer. + expires = + jiffies + + (HFC_TIMER_T3 * + HZ) / 1000; + add_timer(&hfc-> + t3_timer); + } + } + } + } else { +#ifdef CONFIG_HISAX_DEBUG + DBG(ISDN_DBG, + "HFC_USB: hfc_usb_d_l2l1 Bx-chan: PH_ACTIVATE | REQUEST"); +#endif + set_hfcmode(hfc, + (fifo->fifonum == + HFCUSB_B1_TX) ? 0 : 1, + (int) arg); + fifo->hif->l1l2(fifo->hif, + PH_ACTIVATE | INDICATION, + NULL); + } + break; + case PH_DEACTIVATE | REQUEST: + if (fifo->fifonum == HFCUSB_D_TX) { +#ifdef CONFIG_HISAX_DEBUG + DBG(ISDN_DBG, + "HFC_USB: hfc_usb_d_l2l1 D-chan: PH_DEACTIVATE | REQUEST"); +#endif + printk(KERN_INFO + "HFC-S USB: ISDN TE device should not deativate...\n"); + } else { +#ifdef CONFIG_HISAX_DEBUG + DBG(ISDN_DBG, + "HFC_USB: hfc_usb_d_l2l1 Bx-chan: PH_DEACTIVATE | REQUEST"); +#endif + set_hfcmode(hfc, + (fifo->fifonum == + HFCUSB_B1_TX) ? 0 : 1, + (int) L1_MODE_NULL); + fifo->hif->l1l2(fifo->hif, + PH_DEACTIVATE | INDICATION, + NULL); + } + break; + case PH_DATA | REQUEST: + if (fifo->skbuff && fifo->delete_flg) { + dev_kfree_skb_any(fifo->skbuff); + fifo->skbuff = NULL; + fifo->delete_flg = FALSE; + } + fifo->skbuff = arg; /* we have a new buffer */ + break; + default: + printk(KERN_INFO + "HFC_USB: hfc_usb_d_l2l1: unkown state : %#x\n", + pr); + break; + } +} + +/***************************************************************************/ +/* usb_init is called once when a new matching device is detected to setup */ +/* main parameters. It registers the driver at the main hisax module. */ +/* on success 0 is returned. */ +/***************************************************************************/ +static int +usb_init(hfcusb_data * hfc) +{ + usb_fifo *fifo; + int i, err; + u_char b; + struct hisax_b_if *p_b_if[2]; + + /* check the chip id */ + if (read_usb(hfc, HFCUSB_CHIP_ID, &b) != 1) { + printk(KERN_INFO "HFC-USB: cannot read chip id\n"); + return (1); + } + if (b != HFCUSB_CHIPID) { + printk(KERN_INFO "HFC-S USB: Invalid chip id 0x%02x\n", b); + return (1); + } + + /* first set the needed config, interface and alternate */ + err = usb_set_interface(hfc->dev, hfc->if_used, hfc->alt_used); + + /* do Chip reset */ + write_usb(hfc, HFCUSB_CIRM, 8); + /* aux = output, reset off */ + write_usb(hfc, HFCUSB_CIRM, 0x10); + + /* set USB_SIZE to match the the wMaxPacketSize for INT or BULK transfers */ + write_usb(hfc, HFCUSB_USB_SIZE, + (hfc->packet_size / 8) | ((hfc->packet_size / 8) << 4)); + + /* set USB_SIZE_I to match the the wMaxPacketSize for ISO transfers */ + write_usb(hfc, HFCUSB_USB_SIZE_I, hfc->iso_packet_size); + + /* enable PCM/GCI master mode */ + write_usb(hfc, HFCUSB_MST_MODE1, 0); /* set default values */ + write_usb(hfc, HFCUSB_MST_MODE0, 1); /* enable master mode */ + + /* init the fifos */ + write_usb(hfc, HFCUSB_F_THRES, + (HFCUSB_TX_THRESHOLD / + 8) | ((HFCUSB_RX_THRESHOLD / 8) << 4)); + + fifo = hfc->fifos; + for (i = 0; i < HFCUSB_NUM_FIFOS; i++) { + write_usb(hfc, HFCUSB_FIFO, i); /* select the desired fifo */ + fifo[i].skbuff = NULL; /* init buffer pointer */ + fifo[i].max_size = + (i <= HFCUSB_B2_RX) ? MAX_BCH_SIZE : MAX_DFRAME_LEN; + fifo[i].last_urblen = 0; + /* set 2 bit for D- & E-channel */ + write_usb(hfc, HFCUSB_HDLC_PAR, + ((i <= HFCUSB_B2_RX) ? 0 : 2)); + /* rx hdlc, enable IFF for D-channel */ + write_usb(hfc, HFCUSB_CON_HDLC, + ((i == HFCUSB_D_TX) ? 0x09 : 0x08)); + write_usb(hfc, HFCUSB_INC_RES_F, 2); /* reset the fifo */ + } + + write_usb(hfc, HFCUSB_CLKDEL, 0x0f); /* clock delay value */ + write_usb(hfc, HFCUSB_STATES, 3 | 0x10); /* set deactivated mode */ + write_usb(hfc, HFCUSB_STATES, 3); /* enable state machine */ + + write_usb(hfc, HFCUSB_SCTRL_R, 0); /* disable both B receivers */ + write_usb(hfc, HFCUSB_SCTRL, 0x40); /* disable B transmitters + capacitive mode */ + + /* set both B-channel to not connected */ + hfc->b_mode[0] = L1_MODE_NULL; + hfc->b_mode[1] = L1_MODE_NULL; + + hfc->l1_activated = FALSE; + hfc->disc_flag = FALSE; + hfc->led_state = 0; + hfc->led_new_data = 0; + hfc->old_led_state = 0; + + /* init the t3 timer */ + init_timer(&hfc->t3_timer); + hfc->t3_timer.data = (long) hfc; + hfc->t3_timer.function = (void *) l1_timer_expire_t3; + + /* init the t4 timer */ + init_timer(&hfc->t4_timer); + hfc->t4_timer.data = (long) hfc; + hfc->t4_timer.function = (void *) l1_timer_expire_t4; + + /* init the led timer */ + init_timer(&hfc->led_timer); + hfc->led_timer.data = (long) hfc; + hfc->led_timer.function = (void *) led_timer; + + /* trigger 4 hz led timer */ + if (!timer_pending(&hfc->led_timer)) { + hfc->led_timer.expires = jiffies + (LED_TIME * HZ) / 1000; + add_timer(&hfc->led_timer); + } + + /* init the background machinery for control requests */ + hfc->ctrl_read.bRequestType = 0xc0; + hfc->ctrl_read.bRequest = 1; + hfc->ctrl_read.wLength = 1; + hfc->ctrl_write.bRequestType = 0x40; + hfc->ctrl_write.bRequest = 0; + hfc->ctrl_write.wLength = 0; + usb_fill_control_urb(hfc->ctrl_urb, + hfc->dev, + hfc->ctrl_out_pipe, + (u_char *) & hfc->ctrl_write, + NULL, 0, ctrl_complete, hfc); + /* Init All Fifos */ + for (i = 0; i < HFCUSB_NUM_FIFOS; i++) { + hfc->fifos[i].iso[0].purb = NULL; + hfc->fifos[i].iso[1].purb = NULL; + hfc->fifos[i].active = 0; + } + /* register Modul to upper Hisax Layers */ + hfc->d_if.owner = THIS_MODULE; + hfc->d_if.ifc.priv = &hfc->fifos[HFCUSB_D_TX]; + hfc->d_if.ifc.l2l1 = hfc_usb_l2l1; + for (i = 0; i < 2; i++) { + hfc->b_if[i].ifc.priv = &hfc->fifos[HFCUSB_B1_TX + i * 2]; + hfc->b_if[i].ifc.l2l1 = hfc_usb_l2l1; + p_b_if[i] = &hfc->b_if[i]; + } + /* default Prot: EURO ISDN, should be a module_param */ + hfc->protocol = 2; + hisax_register(&hfc->d_if, p_b_if, "hfc_usb", hfc->protocol); + +#ifdef CONFIG_HISAX_DEBUG + hfc_debug = debug; +#endif + + for (i = 0; i < 4; i++) + hfc->fifos[i].hif = &p_b_if[i / 2]->ifc; + for (i = 4; i < 8; i++) + hfc->fifos[i].hif = &hfc->d_if.ifc; + + /* 3 (+1) INT IN + 3 ISO OUT */ + if (hfc->cfg_used == CNF_3INT3ISO || hfc->cfg_used == CNF_4INT3ISO) { + start_int_fifo(hfc->fifos + HFCUSB_D_RX); + if (hfc->fifos[HFCUSB_PCM_RX].pipe) + start_int_fifo(hfc->fifos + HFCUSB_PCM_RX); + start_int_fifo(hfc->fifos + HFCUSB_B1_RX); + start_int_fifo(hfc->fifos + HFCUSB_B2_RX); + } + /* 3 (+1) ISO IN + 3 ISO OUT */ + if (hfc->cfg_used == CNF_3ISO3ISO || hfc->cfg_used == CNF_4ISO3ISO) { + start_isoc_chain(hfc->fifos + HFCUSB_D_RX, ISOC_PACKETS_D, + rx_iso_complete, 16); + if (hfc->fifos[HFCUSB_PCM_RX].pipe) + start_isoc_chain(hfc->fifos + HFCUSB_PCM_RX, + ISOC_PACKETS_D, rx_iso_complete, + 16); + start_isoc_chain(hfc->fifos + HFCUSB_B1_RX, ISOC_PACKETS_B, + rx_iso_complete, 16); + start_isoc_chain(hfc->fifos + HFCUSB_B2_RX, ISOC_PACKETS_B, + rx_iso_complete, 16); + } + + start_isoc_chain(hfc->fifos + HFCUSB_D_TX, ISOC_PACKETS_D, + tx_iso_complete, 1); + start_isoc_chain(hfc->fifos + HFCUSB_B1_TX, ISOC_PACKETS_B, + tx_iso_complete, 1); + start_isoc_chain(hfc->fifos + HFCUSB_B2_TX, ISOC_PACKETS_B, + tx_iso_complete, 1); + + handle_led(hfc, LED_POWER_ON); + + return (0); +} /* usb_init */ + +/*************************************************/ +/* function called to probe a new plugged device */ +/*************************************************/ +static int +hfc_usb_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *dev = interface_to_usbdev(intf); + hfcusb_data *context; + struct usb_host_interface *iface = intf->cur_altsetting; + struct usb_host_interface *iface_used = NULL; + struct usb_host_endpoint *ep; + int ifnum = iface->desc.bInterfaceNumber; + int i, idx, alt_idx, probe_alt_setting, vend_idx, cfg_used, *vcf, + attr, cfg_found, cidx, ep_addr; + int cmptbl[16], small_match, iso_packet_size, packet_size, + alt_used = 0; + + vend_idx = 0xffff; + for (i = 0; vdata[i].vendor; i++) { + if (dev->descriptor.idVendor == vdata[i].vendor + && dev->descriptor.idProduct == vdata[i].prod_id) + vend_idx = i; + } +#ifdef CONFIG_HISAX_DEBUG + DBG(USB_DBG, + "HFC-USB: probing interface(%d) actalt(%d) minor(%d)\n", ifnum, + iface->desc.bAlternateSetting, intf->minor); +#endif + printk(KERN_INFO + "HFC-S USB: probing interface(%d) actalt(%d) minor(%d)\n", + ifnum, iface->desc.bAlternateSetting, intf->minor); + + if (vend_idx != 0xffff) { +#ifdef CONFIG_HISAX_DEBUG + DBG(USB_DBG, "HFC-S USB: found vendor idx:%d name:%s", + vend_idx, vdata[vend_idx].vend_name); +#endif + /* if vendor and product ID is OK, start probing alternate settings */ + alt_idx = 0; + small_match = 0xffff; + + /* default settings */ + iso_packet_size = 16; + packet_size = 64; + + while (alt_idx < intf->num_altsetting) { + iface = intf->altsetting + alt_idx; + probe_alt_setting = iface->desc.bAlternateSetting; + cfg_used = 0; + + /* check for config EOL element */ + while (validconf[cfg_used][0]) { + cfg_found = TRUE; + vcf = validconf[cfg_used]; + /* first endpoint descriptor */ + ep = iface->endpoint; +#ifdef CONFIG_HISAX_DEBUG + DBG(USB_DBG, + "HFC-S USB: (if=%d alt=%d cfg_used=%d)\n", + ifnum, probe_alt_setting, cfg_used); +#endif + memcpy(cmptbl, vcf, 16 * sizeof(int)); + + /* check for all endpoints in this alternate setting */ + for (i = 0; i < iface->desc.bNumEndpoints; + i++) { + ep_addr = + ep->desc.bEndpointAddress; + /* get endpoint base */ + idx = ((ep_addr & 0x7f) - 1) * 2; + if (ep_addr & 0x80) + idx++; + attr = ep->desc.bmAttributes; + if (cmptbl[idx] == EP_NUL) { + cfg_found = FALSE; + } + if (attr == USB_ENDPOINT_XFER_INT + && cmptbl[idx] == EP_INT) + cmptbl[idx] = EP_NUL; + if (attr == USB_ENDPOINT_XFER_BULK + && cmptbl[idx] == EP_BLK) + cmptbl[idx] = EP_NUL; + if (attr == USB_ENDPOINT_XFER_ISOC + && cmptbl[idx] == EP_ISO) + cmptbl[idx] = EP_NUL; + + /* check if all INT endpoints match minimum interval */ + if (attr == USB_ENDPOINT_XFER_INT + && ep->desc.bInterval < + vcf[17]) { +#ifdef CONFIG_HISAX_DEBUG + if (cfg_found) + DBG(USB_DBG, + "HFC-S USB: Interrupt Endpoint interval < %d found - skipping config", + vcf[17]); +#endif + cfg_found = FALSE; + } + ep++; + } + for (i = 0; i < 16; i++) { + /* all entries must be EP_NOP or EP_NUL for a valid config */ + if (cmptbl[i] != EP_NOP + && cmptbl[i] != EP_NUL) + cfg_found = FALSE; + } + if (cfg_found) { + if (cfg_used < small_match) { + small_match = cfg_used; + alt_used = + probe_alt_setting; + iface_used = iface; + } +#ifdef CONFIG_HISAX_DEBUG + DBG(USB_DBG, + "HFC-USB: small_match=%x %x\n", + small_match, alt_used); +#endif + } + cfg_used++; + } + alt_idx++; + } /* (alt_idx < intf->num_altsetting) */ + + /* found a valid USB Ta Endpint config */ + if (small_match != 0xffff) { + iface = iface_used; + if (! + (context = + kmalloc(sizeof(hfcusb_data), GFP_KERNEL))) + return (-ENOMEM); /* got no mem */ + memset(context, 0, sizeof(hfcusb_data)); + + ep = iface->endpoint; + vcf = validconf[small_match]; + + for (i = 0; i < iface->desc.bNumEndpoints; i++) { + ep_addr = ep->desc.bEndpointAddress; + /* get endpoint base */ + idx = ((ep_addr & 0x7f) - 1) * 2; + if (ep_addr & 0x80) + idx++; + cidx = idx & 7; + attr = ep->desc.bmAttributes; + + /* init Endpoints */ + if (vcf[idx] != EP_NOP + && vcf[idx] != EP_NUL) { + switch (attr) { + case USB_ENDPOINT_XFER_INT: + context-> + fifos[cidx]. + pipe = + usb_rcvintpipe + (dev, + ep->desc. + bEndpointAddress); + context-> + fifos[cidx]. + usb_transfer_mode + = USB_INT; + packet_size = + ep->desc. + wMaxPacketSize; + break; + case USB_ENDPOINT_XFER_BULK: + if (ep_addr & 0x80) + context-> + fifos + [cidx]. + pipe = + usb_rcvbulkpipe + (dev, + ep-> + desc. + bEndpointAddress); + else + context-> + fifos + [cidx]. + pipe = + usb_sndbulkpipe + (dev, + ep-> + desc. + bEndpointAddress); + context-> + fifos[cidx]. + usb_transfer_mode + = USB_BULK; + packet_size = + ep->desc. + wMaxPacketSize; + break; + case USB_ENDPOINT_XFER_ISOC: + if (ep_addr & 0x80) + context-> + fifos + [cidx]. + pipe = + usb_rcvisocpipe + (dev, + ep-> + desc. + bEndpointAddress); + else + context-> + fifos + [cidx]. + pipe = + usb_sndisocpipe + (dev, + ep-> + desc. + bEndpointAddress); + context-> + fifos[cidx]. + usb_transfer_mode + = USB_ISOC; + iso_packet_size = + ep->desc. + wMaxPacketSize; + break; + default: + context-> + fifos[cidx]. + pipe = 0; + } /* switch attribute */ + + if (context->fifos[cidx].pipe) { + context->fifos[cidx]. + fifonum = cidx; + context->fifos[cidx].hfc = + context; + context->fifos[cidx]. + usb_packet_maxlen = + ep->desc. + wMaxPacketSize; + context->fifos[cidx]. + intervall = + ep->desc.bInterval; + context->fifos[cidx]. + skbuff = NULL; + } + } + ep++; + } + context->dev = dev; /* save device */ + context->if_used = ifnum; /* save used interface */ + context->alt_used = alt_used; /* and alternate config */ + context->ctrl_paksize = dev->descriptor.bMaxPacketSize0; /* control size */ + context->cfg_used = vcf[16]; /* store used config */ + context->vend_idx = vend_idx; /* store found vendor */ + context->packet_size = packet_size; + context->iso_packet_size = iso_packet_size; + + /* create the control pipes needed for register access */ + context->ctrl_in_pipe = + usb_rcvctrlpipe(context->dev, 0); + context->ctrl_out_pipe = + usb_sndctrlpipe(context->dev, 0); + context->ctrl_urb = usb_alloc_urb(0, GFP_KERNEL); + + printk(KERN_INFO + "HFC-S USB: detected \"%s\"\n", + vdata[vend_idx].vend_name); +#ifdef CONFIG_HISAX_DEBUG + DBG(USB_DBG, + "HFC-S USB: Endpoint-Config: %s (if=%d alt=%d)\n", + conf_str[small_match], context->if_used, + context->alt_used); + printk(KERN_INFO + "HFC-S USB: E-channel (\"ECHO:\") logging "); + if (validconf[small_match][18]) + printk(" possible\n"); + else + printk("NOT possible\n"); +#endif + /* init the chip and register the driver */ + if (usb_init(context)) { + if (context->ctrl_urb) { + usb_unlink_urb(context->ctrl_urb); + usb_free_urb(context->ctrl_urb); + context->ctrl_urb = NULL; + } + kfree(context); + return (-EIO); + } + usb_set_intfdata(intf, context); + return (0); + } + } else { + printk(KERN_INFO + "HFC-S USB: no valid vendor found in USB descriptor\n"); + } + return (-EIO); +} + +/****************************************************/ +/* function called when an active device is removed */ +/****************************************************/ +static void +hfc_usb_disconnect(struct usb_interface + *intf) +{ + hfcusb_data *context = usb_get_intfdata(intf); + int i; + printk(KERN_INFO "HFC-S USB: device disconnect\n"); + context->disc_flag = TRUE; + usb_set_intfdata(intf, NULL); + if (!context) + return; + if (timer_pending(&context->t3_timer)) + del_timer(&context->t3_timer); + if (timer_pending(&context->t4_timer)) + del_timer(&context->t4_timer); + if (timer_pending(&context->led_timer)) + del_timer(&context->led_timer); + /* tell all fifos to terminate */ + for (i = 0; i < HFCUSB_NUM_FIFOS; i++) { + if (context->fifos[i].usb_transfer_mode == USB_ISOC) { + if (context->fifos[i].active > 0) { + stop_isoc_chain(&context->fifos[i]); +#ifdef CONFIG_HISAX_DEBUG + DBG(USB_DBG, + "HFC-S USB: hfc_usb_disconnect: stopping ISOC chain Fifo no %i", + i); +#endif + } + } else { + if (context->fifos[i].active > 0) { + context->fifos[i].active = 0; +#ifdef CONFIG_HISAX_DEBUG + DBG(USB_DBG, + "HFC-S USB: hfc_usb_disconnect: unlinking URB for Fifo no %i", + i); +#endif + } + if (context->fifos[i].urb) { + usb_unlink_urb(context->fifos[i].urb); + usb_free_urb(context->fifos[i].urb); + context->fifos[i].urb = NULL; + } + } + context->fifos[i].active = 0; + } + /* wait for all URBS to terminate */ + mdelay(10); + if (context->ctrl_urb) { + usb_unlink_urb(context->ctrl_urb); + usb_free_urb(context->ctrl_urb); + context->ctrl_urb = NULL; + } + hisax_unregister(&context->d_if); + kfree(context); /* free our structure again */ +} /* hfc_usb_disconnect */ + +/************************************/ +/* our driver information structure */ +/************************************/ +static struct usb_driver hfc_drv = { + .owner = THIS_MODULE,.name = + "hfc_usb",.id_table = hfc_usb_idtab,.probe = + hfc_usb_probe,.disconnect = hfc_usb_disconnect, +}; +static void __exit +hfc_usb_exit(void) +{ +#ifdef CONFIG_HISAX_DEBUG + DBG(USB_DBG, "HFC-S USB: calling \"hfc_usb_exit\" ..."); +#endif + usb_deregister(&hfc_drv); /* release our driver */ + printk(KERN_INFO "HFC-S USB: module removed\n"); +} + +static int __init +hfc_usb_init(void) +{ +#ifndef CONFIG_HISAX_DEBUG + unsigned int debug = -1; +#endif + char revstr[30], datestr[30], dummy[30]; + sscanf(hfcusb_revision, + "%s %s $ %s %s %s $ ", dummy, revstr, + dummy, datestr, dummy); + printk(KERN_INFO + "HFC-S USB: driver module revision %s date %s loaded, (debug=%i)\n", + revstr, datestr, debug); + if (usb_register(&hfc_drv)) { + printk(KERN_INFO + "HFC-S USB: Unable to register HFC-S USB module at usb stack\n"); + return (-1); /* unable to register */ + } + return (0); +} + +module_init(hfc_usb_init); +module_exit(hfc_usb_exit); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_DEVICE_TABLE(usb, hfc_usb_idtab); diff --git a/drivers/isdn/hisax/hfc_usb.h b/drivers/isdn/hisax/hfc_usb.h new file mode 100644 index 000000000000..b171600cf641 --- /dev/null +++ b/drivers/isdn/hisax/hfc_usb.h @@ -0,0 +1,228 @@ +/* +* hfc_usb.h +* +* $Id: hfc_usb.h,v 4.1 2005/01/26 17:25:53 martinb1 Exp $ +*/ + +#ifndef __HFC_USB_H__ +#define __HFC_USB_H__ + +#define DRIVER_AUTHOR "Peter Sprenger (sprenger@moving-byters.de)" +#define DRIVER_DESC "HFC-S USB based HiSAX ISDN driver" + +#define VERBOSE_USB_DEBUG + +#define TRUE 1 +#define FALSE 0 + + +/***********/ +/* defines */ +/***********/ +#define HFC_CTRL_TIMEOUT 20 /* 5ms timeout writing/reading regs */ +#define HFC_TIMER_T3 8000 /* timeout for l1 activation timer */ +#define HFC_TIMER_T4 500 /* time for state change interval */ + +#define HFCUSB_L1_STATECHANGE 0 /* L1 state changed */ +#define HFCUSB_L1_DRX 1 /* D-frame received */ +#define HFCUSB_L1_ERX 2 /* E-frame received */ +#define HFCUSB_L1_DTX 4 /* D-frames completed */ + +#define MAX_BCH_SIZE 2048 /* allowed B-channel packet size */ + +#define HFCUSB_RX_THRESHOLD 64 /* threshold for fifo report bit rx */ +#define HFCUSB_TX_THRESHOLD 64 /* threshold for fifo report bit tx */ + +#define HFCUSB_CHIP_ID 0x16 /* Chip ID register index */ +#define HFCUSB_CIRM 0x00 /* cirm register index */ +#define HFCUSB_USB_SIZE 0x07 /* int length register */ +#define HFCUSB_USB_SIZE_I 0x06 /* iso length register */ +#define HFCUSB_F_CROSS 0x0b /* bit order register */ +#define HFCUSB_CLKDEL 0x37 /* bit delay register */ +#define HFCUSB_CON_HDLC 0xfa /* channel connect register */ +#define HFCUSB_HDLC_PAR 0xfb +#define HFCUSB_SCTRL 0x31 /* S-bus control register (tx) */ +#define HFCUSB_SCTRL_E 0x32 /* same for E and special funcs */ +#define HFCUSB_SCTRL_R 0x33 /* S-bus control register (rx) */ +#define HFCUSB_F_THRES 0x0c /* threshold register */ +#define HFCUSB_FIFO 0x0f /* fifo select register */ +#define HFCUSB_F_USAGE 0x1a /* fifo usage register */ +#define HFCUSB_MST_MODE0 0x14 +#define HFCUSB_MST_MODE1 0x15 +#define HFCUSB_P_DATA 0x1f +#define HFCUSB_INC_RES_F 0x0e +#define HFCUSB_STATES 0x30 + +#define HFCUSB_CHIPID 0x40 /* ID value of HFC-S USB */ + +/******************/ +/* fifo registers */ +/******************/ +#define HFCUSB_NUM_FIFOS 8 /* maximum number of fifos */ +#define HFCUSB_B1_TX 0 /* index for B1 transmit bulk/int */ +#define HFCUSB_B1_RX 1 /* index for B1 receive bulk/int */ +#define HFCUSB_B2_TX 2 +#define HFCUSB_B2_RX 3 +#define HFCUSB_D_TX 4 +#define HFCUSB_D_RX 5 +#define HFCUSB_PCM_TX 6 +#define HFCUSB_PCM_RX 7 + +/* +* used to switch snd_transfer_mode for different TA modes e.g. the Billion USB TA just +* supports ISO out, while the Cologne Chip EVAL TA just supports BULK out +*/ +#define USB_INT 0 +#define USB_BULK 1 +#define USB_ISOC 2 + +#define ISOC_PACKETS_D 8 +#define ISOC_PACKETS_B 8 +#define ISO_BUFFER_SIZE 128 + +// ISO send definitions +#define SINK_MAX 68 +#define SINK_MIN 48 +#define SINK_DMIN 12 +#define SINK_DMAX 18 +#define BITLINE_INF (-64*8) + + +/**********/ +/* macros */ +/**********/ +#define write_usb(a,b,c)usb_control_msg((a)->dev,(a)->ctrl_out_pipe,0,0x40,(c),(b),0,0,HFC_CTRL_TIMEOUT) +#define read_usb(a,b,c) usb_control_msg((a)->dev,(a)->ctrl_in_pipe,1,0xC0,0,(b),(c),1,HFC_CTRL_TIMEOUT) + + +/*******************/ +/* Debugging Flags */ +/*******************/ +#define USB_DBG 1 +#define ISDN_DBG 2 + + +/* *********************/ +/* USB related defines */ +/***********************/ +#define HFC_CTRL_BUFSIZE 32 + + + +/*************************************************/ +/* entry and size of output/input control buffer */ +/*************************************************/ +typedef struct { + __u8 hfc_reg; /* register number */ + __u8 reg_val; /* value to be written (or read) */ + int action; /* data for action handler */ +} ctrl_buft; + + +/********************/ +/* URB error codes: */ +/********************/ +/* Used to represent a list of values and their respective symbolic names */ +struct hfcusb_symbolic_list { + const int num; + const char *name; +}; + +static struct hfcusb_symbolic_list urb_errlist[] = { + {-ENOMEM, "No memory for allocation of internal structures"}, + {-ENOSPC, "The host controller's bandwidth is already consumed"}, + {-ENOENT, "URB was canceled by unlink_urb"}, + {-EXDEV, "ISO transfer only partially completed"}, + {-EAGAIN, "Too match scheduled for the future"}, + {-ENXIO, "URB already queued"}, + {-EFBIG, "Too much ISO frames requested"}, + {-ENOSR, "Buffer error (overrun)"}, + {-EPIPE, "Specified endpoint is stalled (device not responding)"}, + {-EOVERFLOW, "Babble (bad cable?)"}, + {-EPROTO, "Bit-stuff error (bad cable?)"}, + {-EILSEQ, "CRC/Timeout"}, + {-ETIMEDOUT, "NAK (device does not respond)"}, + {-ESHUTDOWN, "Device unplugged"}, + {-1, NULL} +}; + + +/*****************************************************/ +/* device dependant information to support different */ +/* ISDN Ta's using the HFC-S USB chip */ +/*****************************************************/ + +/* USB descriptor need to contain one of the following EndPoint combination: */ +#define CNF_4INT3ISO 1 // 4 INT IN, 3 ISO OUT +#define CNF_3INT3ISO 2 // 3 INT IN, 3 ISO OUT +#define CNF_4ISO3ISO 3 // 4 ISO IN, 3 ISO OUT +#define CNF_3ISO3ISO 4 // 3 ISO IN, 3 ISO OUT + +#define EP_NUL 1 // Endpoint at this position not allowed +#define EP_NOP 2 // all type of endpoints allowed at this position +#define EP_ISO 3 // Isochron endpoint mandatory at this position +#define EP_BLK 4 // Bulk endpoint mandatory at this position +#define EP_INT 5 // Interrupt endpoint mandatory at this position + +/* this array represents all endpoints possible in the HCF-USB the last +* 3 entries are the configuration number, the minimum interval for +* Interrupt endpoints & boolean if E-channel logging possible +*/ +int validconf[][19] = { + // INT in, ISO out config + {EP_NUL, EP_INT, EP_NUL, EP_INT, EP_NUL, EP_INT, EP_NOP, EP_INT, + EP_ISO, EP_NUL, EP_ISO, EP_NUL, EP_ISO, EP_NUL, EP_NUL, EP_NUL, + CNF_4INT3ISO, 2, 1}, + {EP_NUL, EP_INT, EP_NUL, EP_INT, EP_NUL, EP_INT, EP_NUL, EP_NUL, + EP_ISO, EP_NUL, EP_ISO, EP_NUL, EP_ISO, EP_NUL, EP_NUL, EP_NUL, + CNF_3INT3ISO, 2, 0}, + // ISO in, ISO out config + {EP_NUL, EP_NUL, EP_NUL, EP_NUL, EP_NUL, EP_NUL, EP_NUL, EP_NUL, + EP_ISO, EP_ISO, EP_ISO, EP_ISO, EP_ISO, EP_ISO, EP_NOP, EP_ISO, + CNF_4ISO3ISO, 2, 1}, + {EP_NUL, EP_NUL, EP_NUL, EP_NUL, EP_NUL, EP_NUL, EP_NUL, EP_NUL, + EP_ISO, EP_ISO, EP_ISO, EP_ISO, EP_ISO, EP_ISO, EP_NUL, EP_NUL, + CNF_3ISO3ISO, 2, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // EOL element +}; + +// string description of chosen config +char *conf_str[] = { + "4 Interrupt IN + 3 Isochron OUT", + "3 Interrupt IN + 3 Isochron OUT", + "4 Isochron IN + 3 Isochron OUT", + "3 Isochron IN + 3 Isochron OUT" +}; + + +typedef struct { + int vendor; // vendor id + int prod_id; // product id + char *vend_name; // vendor string + __u8 led_scheme; // led display scheme + signed short led_bits[8]; // array of 8 possible LED bitmask settings +} vendor_data; + +#define LED_OFF 0 // no LED support +#define LED_SCHEME1 1 // LED standard scheme +#define LED_SCHEME2 2 // not used yet... + +#define LED_POWER_ON 1 +#define LED_POWER_OFF 2 +#define LED_S0_ON 3 +#define LED_S0_OFF 4 +#define LED_B1_ON 5 +#define LED_B1_OFF 6 +#define LED_B1_DATA 7 +#define LED_B2_ON 8 +#define LED_B2_OFF 9 +#define LED_B2_DATA 10 + +#define LED_NORMAL 0 // LEDs are normal +#define LED_INVERTED 1 // LEDs are inverted + +/* time in ms to perform a Flashing LED when B-Channel has traffic */ +#define LED_TIME 250 + + +#endif // __HFC_USB_H__ diff --git a/drivers/isdn/hisax/hfcscard.c b/drivers/isdn/hisax/hfcscard.c new file mode 100644 index 000000000000..6fc55fea1702 --- /dev/null +++ b/drivers/isdn/hisax/hfcscard.c @@ -0,0 +1,266 @@ +/* $Id: hfcscard.c,v 1.10.2.4 2004/01/14 16:04:48 keil Exp $ + * + * low level stuff for hfcs based cards (Teles3c, ACER P10) + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/init.h> +#include <linux/isapnp.h> +#include "hisax.h" +#include "hfc_2bds0.h" +#include "isdnl1.h" + +extern const char *CardType[]; + +static const char *hfcs_revision = "$Revision: 1.10.2.4 $"; + +static irqreturn_t +hfcs_interrupt(int intno, void *dev_id, struct pt_regs *regs) +{ + struct IsdnCardState *cs = dev_id; + u_char val, stat; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + if ((HFCD_ANYINT | HFCD_BUSY_NBUSY) & + (stat = cs->BC_Read_Reg(cs, HFCD_DATA, HFCD_STAT))) { + val = cs->BC_Read_Reg(cs, HFCD_DATA, HFCD_INT_S1); + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "HFCS: stat(%02x) s1(%02x)", stat, val); + hfc2bds0_interrupt(cs, val); + } else { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "HFCS: irq_no_irq stat(%02x)", stat); + } + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static void +hfcs_Timer(struct IsdnCardState *cs) +{ + cs->hw.hfcD.timer.expires = jiffies + 75; + /* WD RESET */ +/* WriteReg(cs, HFCD_DATA, HFCD_CTMT, cs->hw.hfcD.ctmt | 0x80); + add_timer(&cs->hw.hfcD.timer); +*/ +} + +void +release_io_hfcs(struct IsdnCardState *cs) +{ + release2bds0(cs); + del_timer(&cs->hw.hfcD.timer); + if (cs->hw.hfcD.addr) + release_region(cs->hw.hfcD.addr, 2); +} + +static void +reset_hfcs(struct IsdnCardState *cs) +{ + printk(KERN_INFO "HFCS: resetting card\n"); + cs->hw.hfcD.cirm = HFCD_RESET; + if (cs->typ == ISDN_CTYPE_TELES3C) + cs->hw.hfcD.cirm |= HFCD_MEM8K; + cs->BC_Write_Reg(cs, HFCD_DATA, HFCD_CIRM, cs->hw.hfcD.cirm); /* Reset On */ + mdelay(10); + cs->hw.hfcD.cirm = 0; + if (cs->typ == ISDN_CTYPE_TELES3C) + cs->hw.hfcD.cirm |= HFCD_MEM8K; + cs->BC_Write_Reg(cs, HFCD_DATA, HFCD_CIRM, cs->hw.hfcD.cirm); /* Reset Off */ + mdelay(10); + if (cs->typ == ISDN_CTYPE_TELES3C) + cs->hw.hfcD.cirm |= HFCD_INTB; + else if (cs->typ == ISDN_CTYPE_ACERP10) + cs->hw.hfcD.cirm |= HFCD_INTA; + cs->BC_Write_Reg(cs, HFCD_DATA, HFCD_CIRM, cs->hw.hfcD.cirm); + cs->BC_Write_Reg(cs, HFCD_DATA, HFCD_CLKDEL, 0x0e); + cs->BC_Write_Reg(cs, HFCD_DATA, HFCD_TEST, HFCD_AUTO_AWAKE); /* S/T Auto awake */ + cs->hw.hfcD.ctmt = HFCD_TIM25 | HFCD_AUTO_TIMER; + cs->BC_Write_Reg(cs, HFCD_DATA, HFCD_CTMT, cs->hw.hfcD.ctmt); + cs->hw.hfcD.int_m2 = HFCD_IRQ_ENABLE; + cs->hw.hfcD.int_m1 = HFCD_INTS_B1TRANS | HFCD_INTS_B2TRANS | + HFCD_INTS_DTRANS | HFCD_INTS_B1REC | HFCD_INTS_B2REC | + HFCD_INTS_DREC | HFCD_INTS_L1STATE; + cs->BC_Write_Reg(cs, HFCD_DATA, HFCD_INT_M1, cs->hw.hfcD.int_m1); + cs->BC_Write_Reg(cs, HFCD_DATA, HFCD_INT_M2, cs->hw.hfcD.int_m2); + cs->BC_Write_Reg(cs, HFCD_DATA, HFCD_STATES, HFCD_LOAD_STATE | 2); /* HFC ST 2 */ + udelay(10); + cs->BC_Write_Reg(cs, HFCD_DATA, HFCD_STATES, 2); /* HFC ST 2 */ + cs->hw.hfcD.mst_m = HFCD_MASTER; + cs->BC_Write_Reg(cs, HFCD_DATA, HFCD_MST_MODE, cs->hw.hfcD.mst_m); /* HFC Master */ + cs->hw.hfcD.sctrl = 0; + cs->BC_Write_Reg(cs, HFCD_DATA, HFCD_SCTRL, cs->hw.hfcD.sctrl); +} + +static int +hfcs_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + int delay; + + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "HFCS: card_msg %x", mt); + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + reset_hfcs(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return(0); + case CARD_RELEASE: + release_io_hfcs(cs); + return(0); + case CARD_INIT: + delay = (75*HZ)/100 +1; + cs->hw.hfcD.timer.expires = jiffies + delay; + add_timer(&cs->hw.hfcD.timer); + spin_lock_irqsave(&cs->lock, flags); + reset_hfcs(cs); + init2bds0(cs); + spin_unlock_irqrestore(&cs->lock, flags); + delay = (80*HZ)/1000 +1; + msleep(80); + spin_lock_irqsave(&cs->lock, flags); + cs->hw.hfcD.ctmt |= HFCD_TIM800; + cs->BC_Write_Reg(cs, HFCD_DATA, HFCD_CTMT, cs->hw.hfcD.ctmt); + cs->BC_Write_Reg(cs, HFCD_DATA, HFCD_MST_MODE, cs->hw.hfcD.mst_m); + spin_unlock_irqrestore(&cs->lock, flags); + return(0); + case CARD_TEST: + return(0); + } + return(0); +} + +#ifdef __ISAPNP__ +static struct isapnp_device_id hfc_ids[] __initdata = { + { ISAPNP_VENDOR('A', 'N', 'X'), ISAPNP_FUNCTION(0x1114), + ISAPNP_VENDOR('A', 'N', 'X'), ISAPNP_FUNCTION(0x1114), + (unsigned long) "Acer P10" }, + { ISAPNP_VENDOR('B', 'I', 'L'), ISAPNP_FUNCTION(0x0002), + ISAPNP_VENDOR('B', 'I', 'L'), ISAPNP_FUNCTION(0x0002), + (unsigned long) "Billion 2" }, + { ISAPNP_VENDOR('B', 'I', 'L'), ISAPNP_FUNCTION(0x0001), + ISAPNP_VENDOR('B', 'I', 'L'), ISAPNP_FUNCTION(0x0001), + (unsigned long) "Billion 1" }, + { ISAPNP_VENDOR('T', 'A', 'G'), ISAPNP_FUNCTION(0x7410), + ISAPNP_VENDOR('T', 'A', 'G'), ISAPNP_FUNCTION(0x7410), + (unsigned long) "IStar PnP" }, + { ISAPNP_VENDOR('T', 'A', 'G'), ISAPNP_FUNCTION(0x2610), + ISAPNP_VENDOR('T', 'A', 'G'), ISAPNP_FUNCTION(0x2610), + (unsigned long) "Teles 16.3c" }, + { ISAPNP_VENDOR('S', 'F', 'M'), ISAPNP_FUNCTION(0x0001), + ISAPNP_VENDOR('S', 'F', 'M'), ISAPNP_FUNCTION(0x0001), + (unsigned long) "Tornado Tipa C" }, + { ISAPNP_VENDOR('K', 'Y', 'E'), ISAPNP_FUNCTION(0x0001), + ISAPNP_VENDOR('K', 'Y', 'E'), ISAPNP_FUNCTION(0x0001), + (unsigned long) "Genius Speed Surfer" }, + { 0, } +}; + +static struct isapnp_device_id *ipid __initdata = &hfc_ids[0]; +static struct pnp_card *pnp_c __devinitdata = NULL; +#endif + +int __init +setup_hfcs(struct IsdnCard *card) +{ + struct IsdnCardState *cs = card->cs; + char tmp[64]; + + strcpy(tmp, hfcs_revision); + printk(KERN_INFO "HiSax: HFC-S driver Rev. %s\n", HiSax_getrev(tmp)); + +#ifdef __ISAPNP__ + if (!card->para[1] && isapnp_present()) { + struct pnp_dev *pnp_d; + while(ipid->card_vendor) { + if ((pnp_c = pnp_find_card(ipid->card_vendor, + ipid->card_device, pnp_c))) { + pnp_d = NULL; + if ((pnp_d = pnp_find_dev(pnp_c, + ipid->vendor, ipid->function, pnp_d))) { + int err; + + printk(KERN_INFO "HiSax: %s detected\n", + (char *)ipid->driver_data); + pnp_disable_dev(pnp_d); + err = pnp_activate_dev(pnp_d); + if (err<0) { + printk(KERN_WARNING "%s: pnp_activate_dev ret(%d)\n", + __FUNCTION__, err); + return(0); + } + card->para[1] = pnp_port_start(pnp_d, 0); + card->para[0] = pnp_irq(pnp_d, 0); + if (!card->para[0] || !card->para[1]) { + printk(KERN_ERR "HFC PnP:some resources are missing %ld/%lx\n", + card->para[0], card->para[1]); + pnp_disable_dev(pnp_d); + return(0); + } + break; + } else { + printk(KERN_ERR "HFC PnP: PnP error card found, no device\n"); + } + } + ipid++; + pnp_c = NULL; + } + if (!ipid->card_vendor) { + printk(KERN_INFO "HFC PnP: no ISAPnP card found\n"); + return(0); + } + } +#endif + cs->hw.hfcD.addr = card->para[1] & 0xfffe; + cs->irq = card->para[0]; + cs->hw.hfcD.cip = 0; + cs->hw.hfcD.int_s1 = 0; + cs->hw.hfcD.send = NULL; + cs->bcs[0].hw.hfc.send = NULL; + cs->bcs[1].hw.hfc.send = NULL; + cs->hw.hfcD.dfifosize = 512; + cs->dc.hfcd.ph_state = 0; + cs->hw.hfcD.fifo = 255; + if (cs->typ == ISDN_CTYPE_TELES3C) { + cs->hw.hfcD.bfifosize = 1024 + 512; + } else if (cs->typ == ISDN_CTYPE_ACERP10) { + cs->hw.hfcD.bfifosize = 7*1024 + 512; + } else + return (0); + if (!request_region(cs->hw.hfcD.addr, 2, "HFCS isdn")) { + printk(KERN_WARNING + "HiSax: %s config port %x-%x already in use\n", + CardType[card->typ], + cs->hw.hfcD.addr, + cs->hw.hfcD.addr + 2); + return (0); + } + printk(KERN_INFO + "HFCS: defined at 0x%x IRQ %d HZ %d\n", + cs->hw.hfcD.addr, + cs->irq, HZ); + if (cs->typ == ISDN_CTYPE_TELES3C) { + /* Teles 16.3c IO ADR is 0x200 | YY0U (YY Bit 15/14 address) */ + outb(0x00, cs->hw.hfcD.addr); + outb(0x56, cs->hw.hfcD.addr | 1); + } else if (cs->typ == ISDN_CTYPE_ACERP10) { + /* Acer P10 IO ADR is 0x300 */ + outb(0x00, cs->hw.hfcD.addr); + outb(0x57, cs->hw.hfcD.addr | 1); + } + set_cs_func(cs); + cs->hw.hfcD.timer.function = (void *) hfcs_Timer; + cs->hw.hfcD.timer.data = (long) cs; + init_timer(&cs->hw.hfcD.timer); + cs->cardmsg = &hfcs_card_msg; + cs->irq_func = &hfcs_interrupt; + return (1); +} diff --git a/drivers/isdn/hisax/hisax.h b/drivers/isdn/hisax/hisax.h new file mode 100644 index 000000000000..dc5791728d53 --- /dev/null +++ b/drivers/isdn/hisax/hisax.h @@ -0,0 +1,1341 @@ +/* $Id: hisax.h,v 2.64.2.4 2004/02/11 13:21:33 keil Exp $ + * + * Basic declarations, defines and prototypes + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ +#include <linux/config.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/major.h> +#include <asm/segment.h> +#include <asm/io.h> +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/signal.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/mman.h> +#include <linux/ioport.h> +#include <linux/timer.h> +#include <linux/wait.h> +#include <linux/isdnif.h> +#include <linux/tty.h> +#include <linux/serial_reg.h> +#include <linux/netdevice.h> + +#define ERROR_STATISTIC + +#define REQUEST 0 +#define CONFIRM 1 +#define INDICATION 2 +#define RESPONSE 3 + +#define HW_ENABLE 0x0000 +#define HW_RESET 0x0004 +#define HW_POWERUP 0x0008 +#define HW_ACTIVATE 0x0010 +#define HW_DEACTIVATE 0x0018 + +#define HW_INFO1 0x0010 +#define HW_INFO2 0x0020 +#define HW_INFO3 0x0030 +#define HW_INFO4 0x0040 +#define HW_INFO4_P8 0x0040 +#define HW_INFO4_P10 0x0048 +#define HW_RSYNC 0x0060 +#define HW_TESTLOOP 0x0070 +#define CARD_RESET 0x00F0 +#define CARD_INIT 0x00F2 +#define CARD_RELEASE 0x00F3 +#define CARD_TEST 0x00F4 +#define CARD_AUX_IND 0x00F5 + +#define PH_ACTIVATE 0x0100 +#define PH_DEACTIVATE 0x0110 +#define PH_DATA 0x0120 +#define PH_PULL 0x0130 +#define PH_TESTLOOP 0x0140 +#define PH_PAUSE 0x0150 +#define MPH_ACTIVATE 0x0180 +#define MPH_DEACTIVATE 0x0190 +#define MPH_INFORMATION 0x01A0 + +#define DL_ESTABLISH 0x0200 +#define DL_RELEASE 0x0210 +#define DL_DATA 0x0220 +#define DL_FLUSH 0x0224 +#define DL_UNIT_DATA 0x0230 + +#define MDL_BC_RELEASE 0x0278 // Formula-n enter:now +#define MDL_BC_ASSIGN 0x027C // Formula-n enter:now +#define MDL_ASSIGN 0x0280 +#define MDL_REMOVE 0x0284 +#define MDL_ERROR 0x0288 +#define MDL_INFO_SETUP 0x02E0 +#define MDL_INFO_CONN 0x02E4 +#define MDL_INFO_REL 0x02E8 + +#define CC_SETUP 0x0300 +#define CC_RESUME 0x0304 +#define CC_MORE_INFO 0x0310 +#define CC_IGNORE 0x0320 +#define CC_REJECT 0x0324 +#define CC_SETUP_COMPL 0x0330 +#define CC_PROCEEDING 0x0340 +#define CC_ALERTING 0x0344 +#define CC_PROGRESS 0x0348 +#define CC_CONNECT 0x0350 +#define CC_CHARGE 0x0354 +#define CC_NOTIFY 0x0358 +#define CC_DISCONNECT 0x0360 +#define CC_RELEASE 0x0368 +#define CC_SUSPEND 0x0370 +#define CC_PROCEED_SEND 0x0374 +#define CC_REDIR 0x0378 +#define CC_T302 0x0382 +#define CC_T303 0x0383 +#define CC_T304 0x0384 +#define CC_T305 0x0385 +#define CC_T308_1 0x0388 +#define CC_T308_2 0x038A +#define CC_T309 0x0309 +#define CC_T310 0x0390 +#define CC_T313 0x0393 +#define CC_T318 0x0398 +#define CC_T319 0x0399 +#define CC_TSPID 0x03A0 +#define CC_NOSETUP_RSP 0x03E0 +#define CC_SETUP_ERR 0x03E1 +#define CC_SUSPEND_ERR 0x03E2 +#define CC_RESUME_ERR 0x03E3 +#define CC_CONNECT_ERR 0x03E4 +#define CC_RELEASE_ERR 0x03E5 +#define CC_RESTART 0x03F4 +#define CC_TDSS1_IO 0x13F4 /* DSS1 IO user timer */ +#define CC_TNI1_IO 0x13F5 /* NI1 IO user timer */ + +/* define maximum number of possible waiting incoming calls */ +#define MAX_WAITING_CALLS 2 + + +#ifdef __KERNEL__ + +/* include l3dss1 & ni1 specific process structures, but no other defines */ +#ifdef CONFIG_HISAX_EURO + #define l3dss1_process + #include "l3dss1.h" + #undef l3dss1_process +#endif /* CONFIG_HISAX_EURO */ + +#ifdef CONFIG_HISAX_NI1 + #define l3ni1_process + #include "l3ni1.h" + #undef l3ni1_process +#endif /* CONFIG_HISAX_NI1 */ + +#define MAX_DFRAME_LEN 260 +#define MAX_DFRAME_LEN_L1 300 +#define HSCX_BUFMAX 4096 +#define MAX_DATA_SIZE (HSCX_BUFMAX - 4) +#define MAX_DATA_MEM (HSCX_BUFMAX + 64) +#define RAW_BUFMAX (((HSCX_BUFMAX*6)/5) + 5) +#define MAX_HEADER_LEN 4 +#define MAX_WINDOW 8 +#define MAX_MON_FRAME 32 +#define MAX_DLOG_SPACE 2048 +#define MAX_BLOG_SPACE 256 + +/* #define I4L_IRQ_FLAG SA_INTERRUPT */ +#define I4L_IRQ_FLAG 0 + +/* + * Statemachine + */ + +struct FsmInst; + +typedef void (* FSMFNPTR)(struct FsmInst *, int, void *); + +struct Fsm { + FSMFNPTR *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 L3Timer { + struct l3_process *pc; + struct timer_list tl; + int event; +}; + +#define FLG_L1_ACTIVATING 1 +#define FLG_L1_ACTIVATED 2 +#define FLG_L1_DEACTTIMER 3 +#define FLG_L1_ACTTIMER 4 +#define FLG_L1_T3RUN 5 +#define FLG_L1_PULL_REQ 6 +#define FLG_L1_UINT 7 + +struct Layer1 { + void *hardware; + struct BCState *bcs; + struct PStack **stlistp; + long Flags; + struct FsmInst l1m; + struct FsmTimer timer; + void (*l1l2) (struct PStack *, int, void *); + void (*l1hw) (struct PStack *, int, void *); + void (*l1tei) (struct PStack *, int, void *); + int mode, bc; + int delay; +}; + +#define GROUP_TEI 127 +#define TEI_SAPI 63 +#define CTRL_SAPI 0 +#define PACKET_NOACK 250 + +/* Layer2 Flags */ + +#define FLG_LAPB 0 +#define FLG_LAPD 1 +#define FLG_ORIG 2 +#define FLG_MOD128 3 +#define FLG_PEND_REL 4 +#define FLG_L3_INIT 5 +#define FLG_T200_RUN 6 +#define FLG_ACK_PEND 7 +#define FLG_REJEXC 8 +#define FLG_OWN_BUSY 9 +#define FLG_PEER_BUSY 10 +#define FLG_DCHAN_BUSY 11 +#define FLG_L1_ACTIV 12 +#define FLG_ESTAB_PEND 13 +#define FLG_PTP 14 +#define FLG_FIXED_TEI 15 +#define FLG_L2BLOCK 16 + +struct Layer2 { + int tei; + int sap; + int maxlen; + u_long flag; + spinlock_t lock; + u_int vs, va, vr; + int rc; + unsigned int window; + unsigned int sow; + struct sk_buff *windowar[MAX_WINDOW]; + struct sk_buff_head i_queue; + struct sk_buff_head ui_queue; + void (*l2l1) (struct PStack *, int, void *); + void (*l2l3) (struct PStack *, int, void *); + void (*l2tei) (struct PStack *, int, void *); + struct FsmInst l2m; + struct FsmTimer t200, t203; + int T200, N200, T203; + int debug; + char debug_id[16]; +}; + +struct Layer3 { + void (*l3l4) (struct PStack *, int, void *); + void (*l3ml3) (struct PStack *, int, void *); + void (*l3l2) (struct PStack *, int, void *); + struct FsmInst l3m; + struct FsmTimer l3m_timer; + struct sk_buff_head squeue; + struct l3_process *proc; + struct l3_process *global; + int N303; + int debug; + char debug_id[8]; +}; + +struct LLInterface { + void (*l4l3) (struct PStack *, int, void *); + int (*l4l3_proto) (struct PStack *, isdn_ctrl *); + void *userdata; + u_long flag; +}; + +#define FLG_LLI_L1WAKEUP 1 +#define FLG_LLI_L2WAKEUP 2 + +struct Management { + int ri; + struct FsmInst tei_m; + struct FsmTimer t202; + int T202, N202, debug; + void (*layer) (struct PStack *, int, void *); +}; + +#define NO_CAUSE 254 + +struct Param { + u_char cause; + u_char loc; + u_char diag[6]; + int bchannel; + int chargeinfo; + int spv; /* SPV Flag */ + setup_parm setup; /* from isdnif.h numbers and Serviceindicator */ + u_char moderate; /* transfer mode and rate (bearer octet 4) */ +}; + + +struct PStack { + struct PStack *next; + struct Layer1 l1; + struct Layer2 l2; + struct Layer3 l3; + struct LLInterface lli; + struct Management ma; + int protocol; /* EDSS1, 1TR6 or NI1 */ + + /* protocol specific data fields */ + union + { u_char uuuu; /* only as dummy */ +#ifdef CONFIG_HISAX_EURO + dss1_stk_priv dss1; /* private dss1 data */ +#endif /* CONFIG_HISAX_EURO */ +#ifdef CONFIG_HISAX_NI1 + ni1_stk_priv ni1; /* private ni1 data */ +#endif /* CONFIG_HISAX_NI1 */ + } prot; +}; + +struct l3_process { + int callref; + int state; + struct L3Timer timer; + int N303; + int debug; + struct Param para; + struct Channel *chan; + struct PStack *st; + struct l3_process *next; + ulong redir_result; + + /* protocol specific data fields */ + union + { u_char uuuu; /* only when euro not defined, avoiding empty union */ +#ifdef CONFIG_HISAX_EURO + dss1_proc_priv dss1; /* private dss1 data */ +#endif /* CONFIG_HISAX_EURO */ +#ifdef CONFIG_HISAX_NI1 + ni1_proc_priv ni1; /* private ni1 data */ +#endif /* CONFIG_HISAX_NI1 */ + } prot; +}; + +struct hscx_hw { + int hscx; + int rcvidx; + int count; /* Current skb sent count */ + u_char *rcvbuf; /* B-Channel receive Buffer */ + u_char tsaxr0; + u_char tsaxr1; +}; + +struct w6692B_hw { + int bchan; + int rcvidx; + int count; /* Current skb sent count */ + u_char *rcvbuf; /* B-Channel receive Buffer */ +}; + +struct isar_reg { + unsigned long Flags; + volatile u_char bstat; + volatile u_char iis; + volatile u_char cmsb; + volatile u_char clsb; + volatile u_char par[8]; +}; + +struct isar_hw { + int dpath; + int rcvidx; + int txcnt; + int mml; + u_char state; + u_char cmd; + u_char mod; + u_char newcmd; + u_char newmod; + char try_mod; + struct timer_list ftimer; + u_char *rcvbuf; /* B-Channel receive Buffer */ + u_char conmsg[16]; + struct isar_reg *reg; +}; + +struct hdlc_stat_reg { +#ifdef __BIG_ENDIAN + u_char fill __attribute__((packed)); + u_char mode __attribute__((packed)); + u_char xml __attribute__((packed)); + u_char cmd __attribute__((packed)); +#else + u_char cmd __attribute__((packed)); + u_char xml __attribute__((packed)); + u_char mode __attribute__((packed)); + u_char fill __attribute__((packed)); +#endif +}; + +struct hdlc_hw { + union { + u_int ctrl; + struct hdlc_stat_reg sr; + } ctrl; + u_int stat; + int rcvidx; + int count; /* Current skb sent count */ + u_char *rcvbuf; /* B-Channel receive Buffer */ +}; + +struct hfcB_hw { + unsigned int *send; + int f1; + int f2; +}; + +struct tiger_hw { + u_int *send; + u_int *s_irq; + u_int *s_end; + u_int *sendp; + u_int *rec; + int free; + u_char *rcvbuf; + u_char *sendbuf; + u_char *sp; + int sendcnt; + u_int s_tot; + u_int r_bitcnt; + u_int r_tot; + u_int r_err; + u_int r_fcs; + u_char r_state; + u_char r_one; + u_char r_val; + u_char s_state; +}; + +struct amd7930_hw { + u_char *tx_buff; + u_char *rv_buff; + int rv_buff_in; + int rv_buff_out; + struct sk_buff *rv_skb; + struct hdlc_state *hdlc_state; + struct work_struct tq_rcv; + struct work_struct tq_xmt; +}; + +#define BC_FLG_INIT 1 +#define BC_FLG_ACTIV 2 +#define BC_FLG_BUSY 3 +#define BC_FLG_NOFRAME 4 +#define BC_FLG_HALF 5 +#define BC_FLG_EMPTY 6 +#define BC_FLG_ORIG 7 +#define BC_FLG_DLEETX 8 +#define BC_FLG_LASTDLE 9 +#define BC_FLG_FIRST 10 +#define BC_FLG_LASTDATA 11 +#define BC_FLG_NMD_DATA 12 +#define BC_FLG_FTI_RUN 13 +#define BC_FLG_LL_OK 14 +#define BC_FLG_LL_CONN 15 +#define BC_FLG_FTI_FTS 16 +#define BC_FLG_FRH_WAIT 17 + +#define L1_MODE_NULL 0 +#define L1_MODE_TRANS 1 +#define L1_MODE_HDLC 2 +#define L1_MODE_EXTRN 3 +#define L1_MODE_HDLC_56K 4 +#define L1_MODE_MODEM 7 +#define L1_MODE_V32 8 +#define L1_MODE_FAX 9 + +struct BCState { + int channel; + int mode; + u_long Flag; + struct IsdnCardState *cs; + int tx_cnt; /* B-Channel transmit counter */ + struct sk_buff *tx_skb; /* B-Channel transmit Buffer */ + struct sk_buff_head rqueue; /* B-Channel receive Queue */ + struct sk_buff_head squeue; /* B-Channel send Queue */ + int ackcnt; + spinlock_t aclock; + struct PStack *st; + u_char *blog; + u_char *conmsg; + struct timer_list transbusy; + struct work_struct tqueue; + u_long event; + int (*BC_SetStack) (struct PStack *, struct BCState *); + void (*BC_Close) (struct BCState *); +#ifdef ERROR_STATISTIC + int err_crc; + int err_tx; + int err_rdo; + int err_inv; +#endif + union { + struct hscx_hw hscx; + struct hdlc_hw hdlc; + struct isar_hw isar; + struct hfcB_hw hfc; + struct tiger_hw tiger; + struct amd7930_hw amd7930; + struct w6692B_hw w6692; + struct hisax_b_if *b_if; + } hw; +}; + +struct Channel { + struct PStack *b_st, *d_st; + struct IsdnCardState *cs; + struct BCState *bcs; + int chan; + int incoming; + struct FsmInst fi; + struct FsmTimer drel_timer, dial_timer; + int debug; + int l2_protocol, l2_active_protocol; + int l3_protocol; + int data_open; + struct l3_process *proc; + setup_parm setup; /* from isdnif.h numbers and Serviceindicator */ + u_long Flags; /* for remembering action done in l4 */ + int leased; +}; + +struct elsa_hw { + struct pci_dev *dev; + unsigned long base; + unsigned int cfg; + unsigned int ctrl; + unsigned int ale; + unsigned int isac; + unsigned int itac; + unsigned int hscx; + unsigned int trig; + unsigned int timer; + unsigned int counter; + unsigned int status; + struct timer_list tl; + unsigned int MFlag; + struct BCState *bcs; + u_char *transbuf; + u_char *rcvbuf; + unsigned int transp; + unsigned int rcvp; + unsigned int transcnt; + unsigned int rcvcnt; + u_char IER; + u_char FCR; + u_char LCR; + u_char MCR; + u_char ctrl_reg; +}; + +struct teles3_hw { + unsigned int cfg_reg; + signed int isac; + signed int hscx[2]; + signed int isacfifo; + signed int hscxfifo[2]; +}; + +struct teles0_hw { + unsigned int cfg_reg; + void __iomem *membase; + unsigned long phymem; +}; + +struct avm_hw { + unsigned int cfg_reg; + unsigned int isac; + unsigned int hscx[2]; + unsigned int isacfifo; + unsigned int hscxfifo[2]; + unsigned int counter; + struct pci_dev *dev; +}; + +struct ix1_hw { + unsigned int cfg_reg; + unsigned int isac_ale; + unsigned int isac; + unsigned int hscx_ale; + unsigned int hscx; +}; + +struct diva_hw { + unsigned long cfg_reg; + unsigned long pci_cfg; + unsigned int ctrl; + unsigned long isac_adr; + unsigned int isac; + unsigned long hscx_adr; + unsigned int hscx; + unsigned int status; + struct timer_list tl; + u_char ctrl_reg; + struct pci_dev *dev; +}; + +struct asus_hw { + unsigned int cfg_reg; + unsigned int adr; + unsigned int isac; + unsigned int hscx; + unsigned int u7; + unsigned int pots; +}; + + +struct hfc_hw { + unsigned int addr; + unsigned int fifosize; + unsigned char cirm; + unsigned char ctmt; + unsigned char cip; + u_char isac_spcr; + struct timer_list timer; +}; + +struct sedl_hw { + unsigned int cfg_reg; + unsigned int adr; + unsigned int isac; + unsigned int hscx; + unsigned int reset_on; + unsigned int reset_off; + struct isar_reg isar; + unsigned int chip; + unsigned int bus; + struct pci_dev *dev; +}; + +struct spt_hw { + unsigned int cfg_reg; + unsigned int isac; + unsigned int hscx[2]; + unsigned char res_irq; +}; + +struct mic_hw { + unsigned int cfg_reg; + unsigned int adr; + unsigned int isac; + unsigned int hscx; +}; + +struct njet_hw { + unsigned long base; + unsigned int isac; + unsigned int auxa; + unsigned char auxd; + unsigned char dmactrl; + unsigned char ctrl_reg; + unsigned char irqmask0; + unsigned char irqstat0; + unsigned char last_is0; + struct pci_dev *dev; +}; + +struct hfcPCI_hw { + unsigned char cirm; + unsigned char ctmt; + unsigned char conn; + unsigned char mst_m; + unsigned char int_m1; + unsigned char int_m2; + unsigned char int_s1; + unsigned char sctrl; + unsigned char sctrl_r; + unsigned char sctrl_e; + unsigned char trm; + unsigned char stat; + unsigned char fifo; + unsigned char fifo_en; + unsigned char bswapped; + unsigned char nt_mode; + int nt_timer; + struct pci_dev *dev; + unsigned char *pci_io; /* start of PCI IO memory */ + void *share_start; /* shared memory for Fifos start */ + void *fifos; /* FIFO memory */ + int last_bfifo_cnt[2]; /* marker saving last b-fifo frame count */ + struct timer_list timer; +}; + +struct hfcSX_hw { + unsigned long base; + unsigned char cirm; + unsigned char ctmt; + unsigned char conn; + unsigned char mst_m; + unsigned char int_m1; + unsigned char int_m2; + unsigned char int_s1; + unsigned char sctrl; + unsigned char sctrl_r; + unsigned char sctrl_e; + unsigned char trm; + unsigned char stat; + unsigned char fifo; + unsigned char bswapped; + unsigned char nt_mode; + unsigned char chip; + int b_fifo_size; + unsigned char last_fifo; + void *extra; + int nt_timer; + struct timer_list timer; +}; + +struct hfcD_hw { + unsigned int addr; + unsigned int bfifosize; + unsigned int dfifosize; + unsigned char cirm; + unsigned char ctmt; + unsigned char cip; + unsigned char conn; + unsigned char mst_m; + unsigned char int_m1; + unsigned char int_m2; + unsigned char int_s1; + unsigned char sctrl; + unsigned char stat; + unsigned char fifo; + unsigned char f1; + unsigned char f2; + unsigned int *send; + struct timer_list timer; +}; + +struct isurf_hw { + unsigned int reset; + unsigned long phymem; + void __iomem *isac; + void __iomem *isar; + struct isar_reg isar_r; +}; + +struct saphir_hw { + struct pci_dev *dev; + unsigned int cfg_reg; + unsigned int ale; + unsigned int isac; + unsigned int hscx; + struct timer_list timer; +}; + +struct bkm_hw { + struct pci_dev *dev; + unsigned long base; + /* A4T stuff */ + unsigned long isac_adr; + unsigned int isac_ale; + unsigned long jade_adr; + unsigned int jade_ale; + /* Scitel Quadro stuff */ + unsigned long plx_adr; + unsigned long data_adr; +}; + +struct gazel_hw { + struct pci_dev *dev; + unsigned int cfg_reg; + unsigned int pciaddr[2]; + signed int ipac; + signed int isac; + signed int hscx[2]; + signed int isacfifo; + signed int hscxfifo[2]; + unsigned char timeslot; + unsigned char iom2; +}; + +struct w6692_hw { + struct pci_dev *dev; + unsigned int iobase; + struct timer_list timer; +}; + +#ifdef CONFIG_HISAX_TESTEMU +struct te_hw { + unsigned char *sfifo; + unsigned char *sfifo_w; + unsigned char *sfifo_r; + unsigned char *sfifo_e; + int sfifo_cnt; + unsigned int stat; + wait_queue_head_t rwaitq; + wait_queue_head_t swaitq; +}; +#endif + +struct arcofi_msg { + struct arcofi_msg *next; + u_char receive; + u_char len; + u_char msg[10]; +}; + +struct isac_chip { + int ph_state; + u_char *mon_tx; + u_char *mon_rx; + int mon_txp; + int mon_txc; + int mon_rxp; + struct arcofi_msg *arcofi_list; + struct timer_list arcofitimer; + wait_queue_head_t arcofi_wait; + u_char arcofi_bc; + u_char arcofi_state; + u_char mocr; + u_char adf2; +}; + +struct hfcd_chip { + int ph_state; +}; + +struct hfcpci_chip { + int ph_state; +}; + +struct hfcsx_chip { + int ph_state; +}; + +struct w6692_chip { + int ph_state; +}; + +struct amd7930_chip { + u_char lmr1; + u_char ph_state; + u_char old_state; + u_char flg_t3; + unsigned int tx_xmtlen; + struct timer_list timer3; + void (*ph_command) (struct IsdnCardState *, u_char, char *); + void (*setIrqMask) (struct IsdnCardState *, u_char); +}; + +struct icc_chip { + int ph_state; + u_char *mon_tx; + u_char *mon_rx; + int mon_txp; + int mon_txc; + int mon_rxp; + struct arcofi_msg *arcofi_list; + struct timer_list arcofitimer; + wait_queue_head_t arcofi_wait; + u_char arcofi_bc; + u_char arcofi_state; + u_char mocr; + u_char adf2; +}; + +#define HW_IOM1 0 +#define HW_IPAC 1 +#define HW_ISAR 2 +#define HW_ARCOFI 3 +#define FLG_TWO_DCHAN 4 +#define FLG_L1_DBUSY 5 +#define FLG_DBUSY_TIMER 6 +#define FLG_LOCK_ATOMIC 7 +#define FLG_ARCOFI_TIMER 8 +#define FLG_ARCOFI_ERROR 9 +#define FLG_HW_L1_UINT 10 + +struct IsdnCardState { + spinlock_t lock; + u_char typ; + u_char subtyp; + int protocol; + u_int irq; + u_long irq_flags; + u_long HW_Flags; + int *busy_flag; + int chanlimit; /* limited number of B-chans to use */ + int logecho; /* log echo if supported by card */ + union { + struct elsa_hw elsa; + struct teles0_hw teles0; + struct teles3_hw teles3; + struct avm_hw avm; + struct ix1_hw ix1; + struct diva_hw diva; + struct asus_hw asus; + struct hfc_hw hfc; + struct sedl_hw sedl; + struct spt_hw spt; + struct mic_hw mic; + struct njet_hw njet; + struct hfcD_hw hfcD; + struct hfcPCI_hw hfcpci; + struct hfcSX_hw hfcsx; + struct ix1_hw niccy; + struct isurf_hw isurf; + struct saphir_hw saphir; +#ifdef CONFIG_HISAX_TESTEMU + struct te_hw te; +#endif + struct bkm_hw ax; + struct gazel_hw gazel; + struct w6692_hw w6692; + struct hisax_d_if *hisax_d_if; + } hw; + int myid; + isdn_if iif; + spinlock_t statlock; + u_char *status_buf; + u_char *status_read; + u_char *status_write; + u_char *status_end; + u_char (*readisac) (struct IsdnCardState *, u_char); + void (*writeisac) (struct IsdnCardState *, u_char, u_char); + void (*readisacfifo) (struct IsdnCardState *, u_char *, int); + void (*writeisacfifo) (struct IsdnCardState *, u_char *, int); + u_char (*BC_Read_Reg) (struct IsdnCardState *, int, u_char); + void (*BC_Write_Reg) (struct IsdnCardState *, int, u_char, u_char); + void (*BC_Send_Data) (struct BCState *); + int (*cardmsg) (struct IsdnCardState *, int, void *); + void (*setstack_d) (struct PStack *, struct IsdnCardState *); + void (*DC_Close) (struct IsdnCardState *); + int (*irq_func) (int, void *, struct pt_regs *); + int (*auxcmd) (struct IsdnCardState *, isdn_ctrl *); + struct Channel channel[2+MAX_WAITING_CALLS]; + struct BCState bcs[2+MAX_WAITING_CALLS]; + struct PStack *stlist; + struct sk_buff_head rq, sq; /* D-channel queues */ + int cardnr; + char *dlog; + int debug; + union { + struct isac_chip isac; + struct hfcd_chip hfcd; + struct hfcpci_chip hfcpci; + struct hfcsx_chip hfcsx; + struct w6692_chip w6692; + struct amd7930_chip amd7930; + struct icc_chip icc; + } dc; + u_char *rcvbuf; + int rcvidx; + struct sk_buff *tx_skb; + int tx_cnt; + u_long event; + struct work_struct tqueue; + struct timer_list dbusytimer; +#ifdef ERROR_STATISTIC + int err_crc; + int err_tx; + int err_rx; +#endif +}; + + +#define schedule_event(s, ev) do {test_and_set_bit(ev, &s->event);schedule_work(&s->tqueue); } while(0) + +#define MON0_RX 1 +#define MON1_RX 2 +#define MON0_TX 4 +#define MON1_TX 8 + + +#ifdef ISDN_CHIP_ISAC +#undef ISDN_CHIP_ISAC +#endif + +#ifdef CONFIG_HISAX_16_0 +#define CARD_TELES0 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_TELES0 0 +#endif + +#ifdef CONFIG_HISAX_16_3 +#define CARD_TELES3 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_TELES3 0 +#endif + +#ifdef CONFIG_HISAX_TELESPCI +#define CARD_TELESPCI 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_TELESPCI 0 +#endif + +#ifdef CONFIG_HISAX_AVM_A1 +#define CARD_AVM_A1 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_AVM_A1 0 +#endif + +#ifdef CONFIG_HISAX_AVM_A1_PCMCIA +#define CARD_AVM_A1_PCMCIA 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_AVM_A1_PCMCIA 0 +#endif + +#ifdef CONFIG_HISAX_FRITZPCI +#define CARD_FRITZPCI 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_FRITZPCI 0 +#endif + +#ifdef CONFIG_HISAX_ELSA +#define CARD_ELSA 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_ELSA 0 +#endif + +#ifdef CONFIG_HISAX_IX1MICROR2 +#define CARD_IX1MICROR2 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_IX1MICROR2 0 +#endif + +#ifdef CONFIG_HISAX_DIEHLDIVA +#define CARD_DIEHLDIVA 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_DIEHLDIVA 0 +#endif + +#ifdef CONFIG_HISAX_ASUSCOM +#define CARD_ASUSCOM 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_ASUSCOM 0 +#endif + +#ifdef CONFIG_HISAX_TELEINT +#define CARD_TELEINT 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_TELEINT 0 +#endif + +#ifdef CONFIG_HISAX_SEDLBAUER +#define CARD_SEDLBAUER 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_SEDLBAUER 0 +#endif + +#ifdef CONFIG_HISAX_SPORTSTER +#define CARD_SPORTSTER 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_SPORTSTER 0 +#endif + +#ifdef CONFIG_HISAX_MIC +#define CARD_MIC 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_MIC 0 +#endif + +#ifdef CONFIG_HISAX_NETJET +#define CARD_NETJET_S 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_NETJET_S 0 +#endif + +#ifdef CONFIG_HISAX_HFCS +#define CARD_HFCS 1 +#else +#define CARD_HFCS 0 +#endif + +#ifdef CONFIG_HISAX_HFC_PCI +#define CARD_HFC_PCI 1 +#else +#define CARD_HFC_PCI 0 +#endif + +#ifdef CONFIG_HISAX_HFC_SX +#define CARD_HFC_SX 1 +#else +#define CARD_HFC_SX 0 +#endif + +#ifdef CONFIG_HISAX_AMD7930 +#define CARD_AMD7930 1 +#else +#define CARD_AMD7930 0 +#endif + +#ifdef CONFIG_HISAX_NICCY +#define CARD_NICCY 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_NICCY 0 +#endif + +#ifdef CONFIG_HISAX_ISURF +#define CARD_ISURF 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_ISURF 0 +#endif + +#ifdef CONFIG_HISAX_S0BOX +#define CARD_S0BOX 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_S0BOX 0 +#endif + +#ifdef CONFIG_HISAX_HSTSAPHIR +#define CARD_HSTSAPHIR 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_HSTSAPHIR 0 +#endif + +#ifdef CONFIG_HISAX_TESTEMU +#define CARD_TESTEMU 1 +#define ISDN_CTYPE_TESTEMU 99 +#undef ISDN_CTYPE_COUNT +#define ISDN_CTYPE_COUNT ISDN_CTYPE_TESTEMU +#else +#define CARD_TESTEMU 0 +#endif + +#ifdef CONFIG_HISAX_BKM_A4T +#define CARD_BKM_A4T 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_BKM_A4T 0 +#endif + +#ifdef CONFIG_HISAX_SCT_QUADRO +#define CARD_SCT_QUADRO 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_SCT_QUADRO 0 +#endif + +#ifdef CONFIG_HISAX_GAZEL +#define CARD_GAZEL 1 +#ifndef ISDN_CHIP_ISAC +#define ISDN_CHIP_ISAC 1 +#endif +#else +#define CARD_GAZEL 0 +#endif + +#ifdef CONFIG_HISAX_W6692 +#define CARD_W6692 1 +#ifndef ISDN_CHIP_W6692 +#define ISDN_CHIP_W6692 1 +#endif +#else +#define CARD_W6692 0 +#endif + +#ifdef CONFIG_HISAX_NETJET_U +#define CARD_NETJET_U 1 +#ifndef ISDN_CHIP_ICC +#define ISDN_CHIP_ICC 1 +#endif +#ifndef HISAX_UINTERFACE +#define HISAX_UINTERFACE 1 +#endif +#else +#define CARD_NETJET_U 0 +#endif + +#ifdef CONFIG_HISAX_ENTERNOW_PCI +#define CARD_FN_ENTERNOW_PCI 1 +#endif + +#define TEI_PER_CARD 1 + +/* L1 Debug */ +#define L1_DEB_WARN 0x01 +#define L1_DEB_INTSTAT 0x02 +#define L1_DEB_ISAC 0x04 +#define L1_DEB_ISAC_FIFO 0x08 +#define L1_DEB_HSCX 0x10 +#define L1_DEB_HSCX_FIFO 0x20 +#define L1_DEB_LAPD 0x40 +#define L1_DEB_IPAC 0x80 +#define L1_DEB_RECEIVE_FRAME 0x100 +#define L1_DEB_MONITOR 0x200 +#define DEB_DLOG_HEX 0x400 +#define DEB_DLOG_VERBOSE 0x800 + +#define L2FRAME_DEBUG + +#ifdef L2FRAME_DEBUG +extern void Logl2Frame(struct IsdnCardState *cs, struct sk_buff *skb, char *buf, int dir); +#endif + +#include "hisax_cfg.h" + +void init_bcstate(struct IsdnCardState *cs, int bc); + +void setstack_HiSax(struct PStack *st, struct IsdnCardState *cs); +unsigned int random_ri(void); +void HiSax_addlist(struct IsdnCardState *sp, struct PStack *st); +void HiSax_rmlist(struct IsdnCardState *sp, struct PStack *st); + +void setstack_l1_B(struct PStack *st); + +void setstack_tei(struct PStack *st); +void setstack_manager(struct PStack *st); + +void setstack_isdnl2(struct PStack *st, char *debug_id); +void releasestack_isdnl2(struct PStack *st); +void setstack_transl2(struct PStack *st); +void releasestack_transl2(struct PStack *st); +void lli_writewakeup(struct PStack *st, int len); + +void setstack_l3dc(struct PStack *st, struct Channel *chanp); +void setstack_l3bc(struct PStack *st, struct Channel *chanp); +void releasestack_isdnl3(struct PStack *st); + +u_char *findie(u_char * p, int size, u_char ie, int wanted_set); +int getcallref(u_char * p); +int newcallref(void); + +int 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 FsmRestartTimer(struct FsmTimer *ft, int millisec, int event, + void *arg, int where); +void FsmDelTimer(struct FsmTimer *ft, int where); +int jiftime(char *s, long mark); + +int HiSax_command(isdn_ctrl * ic); +int HiSax_writebuf_skb(int id, int chan, int ack, struct sk_buff *skb); +void HiSax_putstatus(struct IsdnCardState *cs, char *head, char *fmt, ...); +void VHiSax_putstatus(struct IsdnCardState *cs, char *head, char *fmt, va_list args); +void HiSax_reportcard(int cardnr, int sel); +int QuickHex(char *txt, u_char * p, int cnt); +void LogFrame(struct IsdnCardState *cs, u_char * p, int size); +void dlogframe(struct IsdnCardState *cs, struct sk_buff *skb, int dir); +void iecpy(u_char * dest, u_char * iestart, int ieoffset); +#ifdef ISDN_CHIP_ISAC +void setstack_isac(struct PStack *st, struct IsdnCardState *cs); +#endif /* ISDN_CHIP_ISAC */ +#endif /* __KERNEL__ */ + +#define HZDELAY(jiffs) {int tout = jiffs; while (tout--) udelay(1000000/HZ);} + +int ll_run(struct IsdnCardState *cs, int addfeatures); +void ll_stop(struct IsdnCardState *cs); +int CallcNew(void); +void CallcFree(void); +int CallcNewChan(struct IsdnCardState *cs); +void CallcFreeChan(struct IsdnCardState *cs); +int Isdnl1New(void); +void Isdnl1Free(void); +int Isdnl2New(void); +void Isdnl2Free(void); +int Isdnl3New(void); +void Isdnl3Free(void); +void init_tei(struct IsdnCardState *cs, int protocol); +void release_tei(struct IsdnCardState *cs); +char *HiSax_getrev(const char *revision); +int TeiNew(void); +void TeiFree(void); diff --git a/drivers/isdn/hisax/hisax_cfg.h b/drivers/isdn/hisax/hisax_cfg.h new file mode 100644 index 000000000000..ca3fe6259bca --- /dev/null +++ b/drivers/isdn/hisax/hisax_cfg.h @@ -0,0 +1,64 @@ +/* $Id: hisax_cfg.h,v 1.1.2.1 2004/01/24 20:47:23 keil Exp $ + * define of the basic HiSax configuration structures + * and pcmcia interface + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#define ISDN_CTYPE_16_0 1 +#define ISDN_CTYPE_8_0 2 +#define ISDN_CTYPE_16_3 3 +#define ISDN_CTYPE_PNP 4 +#define ISDN_CTYPE_A1 5 +#define ISDN_CTYPE_ELSA 6 +#define ISDN_CTYPE_ELSA_PNP 7 +#define ISDN_CTYPE_TELESPCMCIA 8 +#define ISDN_CTYPE_IX1MICROR2 9 +#define ISDN_CTYPE_ELSA_PCMCIA 10 +#define ISDN_CTYPE_DIEHLDIVA 11 +#define ISDN_CTYPE_ASUSCOM 12 +#define ISDN_CTYPE_TELEINT 13 +#define ISDN_CTYPE_TELES3C 14 +#define ISDN_CTYPE_SEDLBAUER 15 +#define ISDN_CTYPE_SPORTSTER 16 +#define ISDN_CTYPE_MIC 17 +#define ISDN_CTYPE_ELSA_PCI 18 +#define ISDN_CTYPE_COMPAQ_ISA 19 +#define ISDN_CTYPE_NETJET_S 20 +#define ISDN_CTYPE_TELESPCI 21 +#define ISDN_CTYPE_SEDLBAUER_PCMCIA 22 +#define ISDN_CTYPE_AMD7930 23 +#define ISDN_CTYPE_NICCY 24 +#define ISDN_CTYPE_S0BOX 25 +#define ISDN_CTYPE_A1_PCMCIA 26 +#define ISDN_CTYPE_FRITZPCI 27 +#define ISDN_CTYPE_SEDLBAUER_FAX 28 +#define ISDN_CTYPE_ISURF 29 +#define ISDN_CTYPE_ACERP10 30 +#define ISDN_CTYPE_HSTSAPHIR 31 +#define ISDN_CTYPE_BKM_A4T 32 +#define ISDN_CTYPE_SCT_QUADRO 33 +#define ISDN_CTYPE_GAZEL 34 +#define ISDN_CTYPE_HFC_PCI 35 +#define ISDN_CTYPE_W6692 36 +#define ISDN_CTYPE_HFC_SX 37 +#define ISDN_CTYPE_NETJET_U 38 +#define ISDN_CTYPE_HFC_SP_PCMCIA 39 +#define ISDN_CTYPE_DYNAMIC 40 +#define ISDN_CTYPE_ENTERNOW 41 +#define ISDN_CTYPE_COUNT 41 + +typedef struct IsdnCardState IsdnCardState_t; +typedef struct IsdnCard IsdnCard_t; + +struct IsdnCard { + int typ; + int protocol; /* EDSS1, 1TR6 or NI1 */ + unsigned long para[4]; + IsdnCardState_t *cs; +}; + +extern void HiSax_closecard(int); +extern int hisax_init_pcmcia(void *, int *, IsdnCard_t *); diff --git a/drivers/isdn/hisax/hisax_debug.h b/drivers/isdn/hisax/hisax_debug.h new file mode 100644 index 000000000000..ba518a7a7fb7 --- /dev/null +++ b/drivers/isdn/hisax/hisax_debug.h @@ -0,0 +1,81 @@ +/* + * Common debugging macros for use with the hisax driver + * + * Author Frode Isaksen + * Copyright 2001 by Frode Isaksen <fisaksen@bewan.com> + * 2001 by Kai Germaschewski <kai.germaschewski@gmx.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * How to use: + * + * Before including this file, you need to + * #define __debug_variable my_debug + * where my_debug is a variable in your code which + * determines the debug bitmask. + * + * If CONFIG_HISAX_DEBUG is not set, all macros evaluate to nothing + * + */ + +#ifndef __HISAX_DEBUG_H__ +#define __HISAX_DEBUG_H__ + +#include <linux/config.h> + +#ifdef CONFIG_HISAX_DEBUG + +#define DBG(level, format, arg...) do { \ +if (level & __debug_variable) \ +printk(KERN_DEBUG "%s: " format "\n" , __FUNCTION__ , ## arg); \ +} while (0) + +#define DBG_PACKET(level,data,count) \ + if (level & __debug_variable) dump_packet(__FUNCTION__,data,count) + +#define DBG_SKB(level,skb) \ + if ((level & __debug_variable) && skb) dump_packet(__FUNCTION__,skb->data,skb->len) + + +static void __attribute__((unused)) +dump_packet(const char *name,const u_char *data,int pkt_len) +{ +#define DUMP_HDR_SIZE 20 +#define DUMP_TLR_SIZE 8 + if (pkt_len) { + int i,len1,len2; + + printk(KERN_DEBUG "%s: length=%d,data=",name,pkt_len); + + if (pkt_len > DUMP_HDR_SIZE+ DUMP_TLR_SIZE) { + len1 = DUMP_HDR_SIZE; + len2 = DUMP_TLR_SIZE; + } else { + len1 = pkt_len > DUMP_HDR_SIZE ? DUMP_HDR_SIZE : pkt_len; + len2 = 0; + } + for (i = 0; i < len1; ++i) { + printk ("%.2x", data[i]); + } + if (len2) { + printk (".."); + for (i = pkt_len-DUMP_TLR_SIZE; i < pkt_len; ++i) { + printk ("%.2x", data[i]); + } + } + printk ("\n"); + } +#undef DUMP_HDR_SIZE +#undef DUMP_TLR_SIZE +} + +#else + +#define DBG(level, format, arg...) do {} while (0) +#define DBG_PACKET(level,data,count) do {} while (0) +#define DBG_SKB(level,skb) do {} while (0) + +#endif + +#endif diff --git a/drivers/isdn/hisax/hisax_fcpcipnp.c b/drivers/isdn/hisax/hisax_fcpcipnp.c new file mode 100644 index 000000000000..b4d795d40154 --- /dev/null +++ b/drivers/isdn/hisax/hisax_fcpcipnp.c @@ -0,0 +1,1028 @@ +/* + * Driver for AVM Fritz!PCI, Fritz!PCI v2, Fritz!PnP ISDN cards + * + * Author Kai Germaschewski + * Copyright 2001 by Kai Germaschewski <kai.germaschewski@gmx.de> + * 2001 by Karsten Keil <keil@isdn4linux.de> + * + * based upon Karsten Keil's original avm_pci.c driver + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * Thanks to Wizard Computersysteme GmbH, Bremervoerde and + * SoHaNet Technology GmbH, Berlin + * for supporting the development of this driver + */ + + +/* TODO: + * + * o POWER PC + * o clean up debugging + * o tx_skb at PH_DEACTIVATE time + */ + +#include <linux/version.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/isapnp.h> +#include <linux/kmod.h> +#include <linux/slab.h> +#include <linux/skbuff.h> +#include <linux/netdevice.h> +#include <linux/delay.h> + +#include <asm/io.h> + +#include "hisax_fcpcipnp.h" + +// debugging cruft +#define __debug_variable debug +#include "hisax_debug.h" + +#ifdef CONFIG_HISAX_DEBUG +static int debug = 0; +/* static int hdlcfifosize = 32; */ +module_param(debug, int, 0); +/* module_param(hdlcfifosize, int, 0); */ +#endif + +MODULE_AUTHOR("Kai Germaschewski <kai.germaschewski@gmx.de>/Karsten Keil <kkeil@suse.de>"); +MODULE_DESCRIPTION("AVM Fritz!PCI/PnP ISDN driver"); + +static struct pci_device_id fcpci_ids[] = { + { .vendor = PCI_VENDOR_ID_AVM, + .device = PCI_DEVICE_ID_AVM_A1, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = (unsigned long) "Fritz!Card PCI", + }, + { .vendor = PCI_VENDOR_ID_AVM, + .device = PCI_DEVICE_ID_AVM_A1_V2, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = (unsigned long) "Fritz!Card PCI v2" }, + {} +}; + +MODULE_DEVICE_TABLE(pci, fcpci_ids); + +#ifdef __ISAPNP__ +static struct pnp_device_id fcpnp_ids[] __devinitdata = { + { + .id = "AVM0900", + .driver_data = (unsigned long) "Fritz!Card PnP", + }, +}; + +MODULE_DEVICE_TABLE(isapnp, fcpnp_ids); +#endif + +static int protocol = 2; /* EURO-ISDN Default */ +module_param(protocol, int, 0); +MODULE_LICENSE("GPL"); + +// ---------------------------------------------------------------------- + +#define AVM_INDEX 0x04 +#define AVM_DATA 0x10 + +#define AVM_IDX_HDLC_1 0x00 +#define AVM_IDX_HDLC_2 0x01 +#define AVM_IDX_ISAC_FIFO 0x02 +#define AVM_IDX_ISAC_REG_LOW 0x04 +#define AVM_IDX_ISAC_REG_HIGH 0x06 + +#define AVM_STATUS0 0x02 + +#define AVM_STATUS0_IRQ_ISAC 0x01 +#define AVM_STATUS0_IRQ_HDLC 0x02 +#define AVM_STATUS0_IRQ_TIMER 0x04 +#define AVM_STATUS0_IRQ_MASK 0x07 + +#define AVM_STATUS0_RESET 0x01 +#define AVM_STATUS0_DIS_TIMER 0x02 +#define AVM_STATUS0_RES_TIMER 0x04 +#define AVM_STATUS0_ENA_IRQ 0x08 +#define AVM_STATUS0_TESTBIT 0x10 + +#define AVM_STATUS1 0x03 +#define AVM_STATUS1_ENA_IOM 0x80 + +#define HDLC_FIFO 0x0 +#define HDLC_STATUS 0x4 +#define HDLC_CTRL 0x4 + +#define HDLC_MODE_ITF_FLG 0x01 +#define HDLC_MODE_TRANS 0x02 +#define HDLC_MODE_CCR_7 0x04 +#define HDLC_MODE_CCR_16 0x08 +#define HDLC_MODE_TESTLOOP 0x80 + +#define HDLC_INT_XPR 0x80 +#define HDLC_INT_XDU 0x40 +#define HDLC_INT_RPR 0x20 +#define HDLC_INT_MASK 0xE0 + +#define HDLC_STAT_RME 0x01 +#define HDLC_STAT_RDO 0x10 +#define HDLC_STAT_CRCVFRRAB 0x0E +#define HDLC_STAT_CRCVFR 0x06 +#define HDLC_STAT_RML_MASK 0xff00 + +#define HDLC_CMD_XRS 0x80 +#define HDLC_CMD_XME 0x01 +#define HDLC_CMD_RRS 0x20 +#define HDLC_CMD_XML_MASK 0xff00 + +#define AVM_HDLC_FIFO_1 0x10 +#define AVM_HDLC_FIFO_2 0x18 + +#define AVM_HDLC_STATUS_1 0x14 +#define AVM_HDLC_STATUS_2 0x1c + +#define AVM_ISACSX_INDEX 0x04 +#define AVM_ISACSX_DATA 0x08 + +// ---------------------------------------------------------------------- +// Fritz!PCI + +static unsigned char fcpci_read_isac(struct isac *isac, unsigned char offset) +{ + struct fritz_adapter *adapter = isac->priv; + unsigned char idx = (offset > 0x2f) ? + AVM_IDX_ISAC_REG_HIGH : AVM_IDX_ISAC_REG_LOW; + unsigned char val; + unsigned long flags; + + spin_lock_irqsave(&adapter->hw_lock, flags); + outb(idx, adapter->io + AVM_INDEX); + val = inb(adapter->io + AVM_DATA + (offset & 0xf)); + spin_unlock_irqrestore(&adapter->hw_lock, flags); + DBG(0x1000, " port %#x, value %#x", + offset, val); + return val; +} + +static void fcpci_write_isac(struct isac *isac, unsigned char offset, + unsigned char value) +{ + struct fritz_adapter *adapter = isac->priv; + unsigned char idx = (offset > 0x2f) ? + AVM_IDX_ISAC_REG_HIGH : AVM_IDX_ISAC_REG_LOW; + unsigned long flags; + + DBG(0x1000, " port %#x, value %#x", + offset, value); + spin_lock_irqsave(&adapter->hw_lock, flags); + outb(idx, adapter->io + AVM_INDEX); + outb(value, adapter->io + AVM_DATA + (offset & 0xf)); + spin_unlock_irqrestore(&adapter->hw_lock, flags); +} + +static void fcpci_read_isac_fifo(struct isac *isac, unsigned char * data, + int size) +{ + struct fritz_adapter *adapter = isac->priv; + unsigned long flags; + + spin_lock_irqsave(&adapter->hw_lock, flags); + outb(AVM_IDX_ISAC_FIFO, adapter->io + AVM_INDEX); + insb(adapter->io + AVM_DATA, data, size); + spin_unlock_irqrestore(&adapter->hw_lock, flags); +} + +static void fcpci_write_isac_fifo(struct isac *isac, unsigned char * data, + int size) +{ + struct fritz_adapter *adapter = isac->priv; + unsigned long flags; + + spin_lock_irqsave(&adapter->hw_lock, flags); + outb(AVM_IDX_ISAC_FIFO, adapter->io + AVM_INDEX); + outsb(adapter->io + AVM_DATA, data, size); + spin_unlock_irqrestore(&adapter->hw_lock, flags); +} + +static u32 fcpci_read_hdlc_status(struct fritz_adapter *adapter, int nr) +{ + u32 val; + int idx = nr ? AVM_IDX_HDLC_2 : AVM_IDX_HDLC_1; + unsigned long flags; + + spin_lock_irqsave(&adapter->hw_lock, flags); + outl(idx, adapter->io + AVM_INDEX); + val = inl(adapter->io + AVM_DATA + HDLC_STATUS); + spin_unlock_irqrestore(&adapter->hw_lock, flags); + return val; +} + +static void __fcpci_write_ctrl(struct fritz_bcs *bcs, int which) +{ + struct fritz_adapter *adapter = bcs->adapter; + int idx = bcs->channel ? AVM_IDX_HDLC_2 : AVM_IDX_HDLC_1; + + DBG(0x40, "hdlc %c wr%x ctrl %x", + 'A' + bcs->channel, which, bcs->ctrl.ctrl); + + outl(idx, adapter->io + AVM_INDEX); + outl(bcs->ctrl.ctrl, adapter->io + AVM_DATA + HDLC_CTRL); +} + +static void fcpci_write_ctrl(struct fritz_bcs *bcs, int which) +{ + struct fritz_adapter *adapter = bcs->adapter; + unsigned long flags; + + spin_lock_irqsave(&adapter->hw_lock, flags); + __fcpci_write_ctrl(bcs, which); + spin_unlock_irqrestore(&adapter->hw_lock, flags); +} + +// ---------------------------------------------------------------------- +// Fritz!PCI v2 + +static unsigned char fcpci2_read_isac(struct isac *isac, unsigned char offset) +{ + struct fritz_adapter *adapter = isac->priv; + unsigned char val; + unsigned long flags; + + spin_lock_irqsave(&adapter->hw_lock, flags); + outl(offset, adapter->io + AVM_ISACSX_INDEX); + val = inl(adapter->io + AVM_ISACSX_DATA); + spin_unlock_irqrestore(&adapter->hw_lock, flags); + DBG(0x1000, " port %#x, value %#x", + offset, val); + + return val; +} + +static void fcpci2_write_isac(struct isac *isac, unsigned char offset, + unsigned char value) +{ + struct fritz_adapter *adapter = isac->priv; + unsigned long flags; + + DBG(0x1000, " port %#x, value %#x", + offset, value); + spin_lock_irqsave(&adapter->hw_lock, flags); + outl(offset, adapter->io + AVM_ISACSX_INDEX); + outl(value, adapter->io + AVM_ISACSX_DATA); + spin_unlock_irqrestore(&adapter->hw_lock, flags); +} + +static void fcpci2_read_isac_fifo(struct isac *isac, unsigned char * data, + int size) +{ + struct fritz_adapter *adapter = isac->priv; + int i; + unsigned long flags; + + spin_lock_irqsave(&adapter->hw_lock, flags); + outl(0, adapter->io + AVM_ISACSX_INDEX); + for (i = 0; i < size; i++) + data[i] = inl(adapter->io + AVM_ISACSX_DATA); + spin_unlock_irqrestore(&adapter->hw_lock, flags); +} + +static void fcpci2_write_isac_fifo(struct isac *isac, unsigned char * data, + int size) +{ + struct fritz_adapter *adapter = isac->priv; + int i; + unsigned long flags; + + spin_lock_irqsave(&adapter->hw_lock, flags); + outl(0, adapter->io + AVM_ISACSX_INDEX); + for (i = 0; i < size; i++) + outl(data[i], adapter->io + AVM_ISACSX_DATA); + spin_unlock_irqrestore(&adapter->hw_lock, flags); +} + +static u32 fcpci2_read_hdlc_status(struct fritz_adapter *adapter, int nr) +{ + int offset = nr ? AVM_HDLC_STATUS_2 : AVM_HDLC_STATUS_1; + + return inl(adapter->io + offset); +} + +static void fcpci2_write_ctrl(struct fritz_bcs *bcs, int which) +{ + struct fritz_adapter *adapter = bcs->adapter; + int offset = bcs->channel ? AVM_HDLC_STATUS_2 : AVM_HDLC_STATUS_1; + + DBG(0x40, "hdlc %c wr%x ctrl %x", + 'A' + bcs->channel, which, bcs->ctrl.ctrl); + + outl(bcs->ctrl.ctrl, adapter->io + offset); +} + +// ---------------------------------------------------------------------- +// Fritz!PnP (ISAC access as for Fritz!PCI) + +static u32 fcpnp_read_hdlc_status(struct fritz_adapter *adapter, int nr) +{ + unsigned char idx = nr ? AVM_IDX_HDLC_2 : AVM_IDX_HDLC_1; + u32 val; + unsigned long flags; + + spin_lock_irqsave(&adapter->hw_lock, flags); + outb(idx, adapter->io + AVM_INDEX); + val = inb(adapter->io + AVM_DATA + HDLC_STATUS); + if (val & HDLC_INT_RPR) + val |= inb(adapter->io + AVM_DATA + HDLC_STATUS + 1) << 8; + spin_unlock_irqrestore(&adapter->hw_lock, flags); + return val; +} + +static void __fcpnp_write_ctrl(struct fritz_bcs *bcs, int which) +{ + struct fritz_adapter *adapter = bcs->adapter; + unsigned char idx = bcs->channel ? AVM_IDX_HDLC_2 : AVM_IDX_HDLC_1; + + DBG(0x40, "hdlc %c wr%x ctrl %x", + 'A' + bcs->channel, which, bcs->ctrl.ctrl); + + outb(idx, adapter->io + AVM_INDEX); + if (which & 4) + outb(bcs->ctrl.sr.mode, + adapter->io + AVM_DATA + HDLC_STATUS + 2); + if (which & 2) + outb(bcs->ctrl.sr.xml, + adapter->io + AVM_DATA + HDLC_STATUS + 1); + if (which & 1) + outb(bcs->ctrl.sr.cmd, + adapter->io + AVM_DATA + HDLC_STATUS + 0); +} + +static void fcpnp_write_ctrl(struct fritz_bcs *bcs, int which) +{ + struct fritz_adapter *adapter = bcs->adapter; + unsigned long flags; + + spin_lock_irqsave(&adapter->hw_lock, flags); + __fcpnp_write_ctrl(bcs, which); + spin_unlock_irqrestore(&adapter->hw_lock, flags); +} + +// ---------------------------------------------------------------------- + +static inline void B_L1L2(struct fritz_bcs *bcs, int pr, void *arg) +{ + struct hisax_if *ifc = (struct hisax_if *) &bcs->b_if; + + DBG(2, "pr %#x", pr); + ifc->l1l2(ifc, pr, arg); +} + +static void hdlc_fill_fifo(struct fritz_bcs *bcs) +{ + struct fritz_adapter *adapter = bcs->adapter; + struct sk_buff *skb = bcs->tx_skb; + int count; + unsigned long flags; + unsigned char *p; + + DBG(0x40, "hdlc_fill_fifo"); + + if (skb->len == 0) + BUG(); + + bcs->ctrl.sr.cmd &= ~HDLC_CMD_XME; + if (bcs->tx_skb->len > bcs->fifo_size) { + count = bcs->fifo_size; + } else { + count = bcs->tx_skb->len; + if (bcs->mode != L1_MODE_TRANS) + bcs->ctrl.sr.cmd |= HDLC_CMD_XME; + } + DBG(0x40, "hdlc_fill_fifo %d/%d", count, bcs->tx_skb->len); + p = bcs->tx_skb->data; + skb_pull(bcs->tx_skb, count); + bcs->tx_cnt += count; + bcs->ctrl.sr.xml = ((count == bcs->fifo_size) ? 0 : count); + + switch (adapter->type) { + case AVM_FRITZ_PCI: + spin_lock_irqsave(&adapter->hw_lock, flags); + // sets the correct AVM_INDEX, too + __fcpci_write_ctrl(bcs, 3); + outsl(adapter->io + AVM_DATA + HDLC_FIFO, + p, (count + 3) / 4); + spin_unlock_irqrestore(&adapter->hw_lock, flags); + break; + case AVM_FRITZ_PCIV2: + fcpci2_write_ctrl(bcs, 3); + outsl(adapter->io + + (bcs->channel ? AVM_HDLC_FIFO_2 : AVM_HDLC_FIFO_1), + p, (count + 3) / 4); + break; + case AVM_FRITZ_PNP: + spin_lock_irqsave(&adapter->hw_lock, flags); + // sets the correct AVM_INDEX, too + __fcpnp_write_ctrl(bcs, 3); + outsb(adapter->io + AVM_DATA, p, count); + spin_unlock_irqrestore(&adapter->hw_lock, flags); + break; + } +} + +static inline void hdlc_empty_fifo(struct fritz_bcs *bcs, int count) +{ + struct fritz_adapter *adapter = bcs->adapter; + unsigned char *p; + unsigned char idx = bcs->channel ? AVM_IDX_HDLC_2 : AVM_IDX_HDLC_1; + + DBG(0x10, "hdlc_empty_fifo %d", count); + if (bcs->rcvidx + count > HSCX_BUFMAX) { + DBG(0x10, "hdlc_empty_fifo: incoming packet too large"); + return; + } + p = bcs->rcvbuf + bcs->rcvidx; + bcs->rcvidx += count; + switch (adapter->type) { + case AVM_FRITZ_PCI: + spin_lock(&adapter->hw_lock); + outl(idx, adapter->io + AVM_INDEX); + insl(adapter->io + AVM_DATA + HDLC_FIFO, + p, (count + 3) / 4); + spin_unlock(&adapter->hw_lock); + break; + case AVM_FRITZ_PCIV2: + insl(adapter->io + + (bcs->channel ? AVM_HDLC_FIFO_2 : AVM_HDLC_FIFO_1), + p, (count + 3) / 4); + break; + case AVM_FRITZ_PNP: + spin_lock(&adapter->hw_lock); + outb(idx, adapter->io + AVM_INDEX); + insb(adapter->io + AVM_DATA, p, count); + spin_unlock(&adapter->hw_lock); + break; + } +} + +static inline void hdlc_rpr_irq(struct fritz_bcs *bcs, u32 stat) +{ + struct fritz_adapter *adapter = bcs->adapter; + struct sk_buff *skb; + int len; + + if (stat & HDLC_STAT_RDO) { + DBG(0x10, "RDO"); + bcs->ctrl.sr.xml = 0; + bcs->ctrl.sr.cmd |= HDLC_CMD_RRS; + adapter->write_ctrl(bcs, 1); + bcs->ctrl.sr.cmd &= ~HDLC_CMD_RRS; + adapter->write_ctrl(bcs, 1); + bcs->rcvidx = 0; + return; + } + + len = (stat & HDLC_STAT_RML_MASK) >> 8; + if (len == 0) + len = bcs->fifo_size; + + hdlc_empty_fifo(bcs, len); + + if ((stat & HDLC_STAT_RME) || (bcs->mode == L1_MODE_TRANS)) { + if (((stat & HDLC_STAT_CRCVFRRAB)== HDLC_STAT_CRCVFR) || + (bcs->mode == L1_MODE_TRANS)) { + skb = dev_alloc_skb(bcs->rcvidx); + if (!skb) { + printk(KERN_WARNING "HDLC: receive out of memory\n"); + } else { + memcpy(skb_put(skb, bcs->rcvidx), bcs->rcvbuf, + bcs->rcvidx); + DBG_SKB(1, skb); + B_L1L2(bcs, PH_DATA | INDICATION, skb); + } + bcs->rcvidx = 0; + } else { + DBG(0x10, "ch%d invalid frame %#x", + bcs->channel, stat); + bcs->rcvidx = 0; + } + } +} + +static inline void hdlc_xdu_irq(struct fritz_bcs *bcs) +{ + struct fritz_adapter *adapter = bcs->adapter; + + + /* Here we lost an TX interrupt, so + * restart transmitting the whole frame. + */ + bcs->ctrl.sr.xml = 0; + bcs->ctrl.sr.cmd |= HDLC_CMD_XRS; + adapter->write_ctrl(bcs, 1); + bcs->ctrl.sr.cmd &= ~HDLC_CMD_XRS; + + if (!bcs->tx_skb) { + DBG(0x10, "XDU without skb"); + adapter->write_ctrl(bcs, 1); + return; + } + /* only hdlc restarts the frame, transparent mode must continue */ + if (bcs->mode == L1_MODE_HDLC) { + skb_push(bcs->tx_skb, bcs->tx_cnt); + bcs->tx_cnt = 0; + } +} + +static inline void hdlc_xpr_irq(struct fritz_bcs *bcs) +{ + struct sk_buff *skb; + + skb = bcs->tx_skb; + if (!skb) + return; + + if (skb->len) { + hdlc_fill_fifo(bcs); + return; + } + bcs->tx_cnt = 0; + bcs->tx_skb = NULL; + B_L1L2(bcs, PH_DATA | CONFIRM, (void *) skb->truesize); + dev_kfree_skb_irq(skb); +} + +static void hdlc_irq_one(struct fritz_bcs *bcs, u32 stat) +{ + DBG(0x10, "ch%d stat %#x", bcs->channel, stat); + if (stat & HDLC_INT_RPR) { + DBG(0x10, "RPR"); + hdlc_rpr_irq(bcs, stat); + } + if (stat & HDLC_INT_XDU) { + DBG(0x10, "XDU"); + hdlc_xdu_irq(bcs); + hdlc_xpr_irq(bcs); + return; + } + if (stat & HDLC_INT_XPR) { + DBG(0x10, "XPR"); + hdlc_xpr_irq(bcs); + } +} + +static inline void hdlc_irq(struct fritz_adapter *adapter) +{ + int nr; + u32 stat; + + for (nr = 0; nr < 2; nr++) { + stat = adapter->read_hdlc_status(adapter, nr); + DBG(0x10, "HDLC %c stat %#x", 'A' + nr, stat); + if (stat & HDLC_INT_MASK) + hdlc_irq_one(&adapter->bcs[nr], stat); + } +} + +static void modehdlc(struct fritz_bcs *bcs, int mode) +{ + struct fritz_adapter *adapter = bcs->adapter; + + DBG(0x40, "hdlc %c mode %d --> %d", + 'A' + bcs->channel, bcs->mode, mode); + + if (bcs->mode == mode) + return; + + bcs->fifo_size = 32; + bcs->ctrl.ctrl = 0; + bcs->ctrl.sr.cmd = HDLC_CMD_XRS | HDLC_CMD_RRS; + switch (mode) { + case L1_MODE_NULL: + bcs->ctrl.sr.mode = HDLC_MODE_TRANS; + adapter->write_ctrl(bcs, 5); + break; + case L1_MODE_TRANS: + case L1_MODE_HDLC: + bcs->rcvidx = 0; + bcs->tx_cnt = 0; + bcs->tx_skb = NULL; + if (mode == L1_MODE_TRANS) { + bcs->ctrl.sr.mode = HDLC_MODE_TRANS; + } else { + bcs->ctrl.sr.mode = HDLC_MODE_ITF_FLG; + } + adapter->write_ctrl(bcs, 5); + bcs->ctrl.sr.cmd = HDLC_CMD_XRS; + adapter->write_ctrl(bcs, 1); + bcs->ctrl.sr.cmd = 0; + break; + } + bcs->mode = mode; +} + +static void fritz_b_l2l1(struct hisax_if *ifc, int pr, void *arg) +{ + struct fritz_bcs *bcs = ifc->priv; + struct sk_buff *skb = arg; + int mode; + + DBG(0x10, "pr %#x", pr); + + switch (pr) { + case PH_DATA | REQUEST: + if (bcs->tx_skb) + BUG(); + + bcs->tx_skb = skb; + DBG_SKB(1, skb); + hdlc_fill_fifo(bcs); + break; + case PH_ACTIVATE | REQUEST: + mode = (int) arg; + DBG(4,"B%d,PH_ACTIVATE_REQUEST %d", bcs->channel + 1, mode); + modehdlc(bcs, mode); + B_L1L2(bcs, PH_ACTIVATE | INDICATION, NULL); + break; + case PH_DEACTIVATE | REQUEST: + DBG(4,"B%d,PH_DEACTIVATE_REQUEST", bcs->channel + 1); + modehdlc(bcs, L1_MODE_NULL); + B_L1L2(bcs, PH_DEACTIVATE | INDICATION, NULL); + break; + } +} + +// ---------------------------------------------------------------------- + +static irqreturn_t +fcpci2_irq(int intno, void *dev, struct pt_regs *regs) +{ + struct fritz_adapter *adapter = dev; + unsigned char val; + + val = inb(adapter->io + AVM_STATUS0); + if (!(val & AVM_STATUS0_IRQ_MASK)) + /* hopefully a shared IRQ reqest */ + return IRQ_NONE; + DBG(2, "STATUS0 %#x", val); + if (val & AVM_STATUS0_IRQ_ISAC) + isacsx_irq(&adapter->isac); + if (val & AVM_STATUS0_IRQ_HDLC) + hdlc_irq(adapter); + if (val & AVM_STATUS0_IRQ_ISAC) + isacsx_irq(&adapter->isac); + return IRQ_HANDLED; +} + +static irqreturn_t +fcpci_irq(int intno, void *dev, struct pt_regs *regs) +{ + struct fritz_adapter *adapter = dev; + unsigned char sval; + + sval = inb(adapter->io + 2); + if ((sval & AVM_STATUS0_IRQ_MASK) == AVM_STATUS0_IRQ_MASK) + /* possibly a shared IRQ reqest */ + return IRQ_NONE; + DBG(2, "sval %#x", sval); + if (!(sval & AVM_STATUS0_IRQ_ISAC)) + isac_irq(&adapter->isac); + + if (!(sval & AVM_STATUS0_IRQ_HDLC)) + hdlc_irq(adapter); + return IRQ_HANDLED; +} + +// ---------------------------------------------------------------------- + +static inline void fcpci2_init(struct fritz_adapter *adapter) +{ + outb(AVM_STATUS0_RES_TIMER, adapter->io + AVM_STATUS0); + outb(AVM_STATUS0_ENA_IRQ, adapter->io + AVM_STATUS0); + +} + +static inline void fcpci_init(struct fritz_adapter *adapter) +{ + outb(AVM_STATUS0_DIS_TIMER | AVM_STATUS0_RES_TIMER | + AVM_STATUS0_ENA_IRQ, adapter->io + AVM_STATUS0); + + outb(AVM_STATUS1_ENA_IOM | adapter->irq, + adapter->io + AVM_STATUS1); + mdelay(10); +} + +// ---------------------------------------------------------------------- + +static int __devinit fcpcipnp_setup(struct fritz_adapter *adapter) +{ + u32 val = 0; + int retval; + + DBG(1,""); + + isac_init(&adapter->isac); // FIXME is this okay now + + retval = -EBUSY; + if (!request_region(adapter->io, 32, "fcpcipnp")) + goto err; + + switch (adapter->type) { + case AVM_FRITZ_PCIV2: + retval = request_irq(adapter->irq, fcpci2_irq, SA_SHIRQ, + "fcpcipnp", adapter); + break; + case AVM_FRITZ_PCI: + retval = request_irq(adapter->irq, fcpci_irq, SA_SHIRQ, + "fcpcipnp", adapter); + break; + case AVM_FRITZ_PNP: + retval = request_irq(adapter->irq, fcpci_irq, 0, + "fcpcipnp", adapter); + break; + } + if (retval) + goto err_region; + + switch (adapter->type) { + case AVM_FRITZ_PCIV2: + case AVM_FRITZ_PCI: + val = inl(adapter->io); + break; + case AVM_FRITZ_PNP: + val = inb(adapter->io); + val |= inb(adapter->io + 1) << 8; + break; + } + + DBG(1, "stat %#x Class %X Rev %d", + val, val & 0xff, (val>>8) & 0xff); + + spin_lock_init(&adapter->hw_lock); + adapter->isac.priv = adapter; + switch (adapter->type) { + case AVM_FRITZ_PCIV2: + adapter->isac.read_isac = &fcpci2_read_isac; + adapter->isac.write_isac = &fcpci2_write_isac; + adapter->isac.read_isac_fifo = &fcpci2_read_isac_fifo; + adapter->isac.write_isac_fifo = &fcpci2_write_isac_fifo; + + adapter->read_hdlc_status = &fcpci2_read_hdlc_status; + adapter->write_ctrl = &fcpci2_write_ctrl; + break; + case AVM_FRITZ_PCI: + adapter->isac.read_isac = &fcpci_read_isac; + adapter->isac.write_isac = &fcpci_write_isac; + adapter->isac.read_isac_fifo = &fcpci_read_isac_fifo; + adapter->isac.write_isac_fifo = &fcpci_write_isac_fifo; + + adapter->read_hdlc_status = &fcpci_read_hdlc_status; + adapter->write_ctrl = &fcpci_write_ctrl; + break; + case AVM_FRITZ_PNP: + adapter->isac.read_isac = &fcpci_read_isac; + adapter->isac.write_isac = &fcpci_write_isac; + adapter->isac.read_isac_fifo = &fcpci_read_isac_fifo; + adapter->isac.write_isac_fifo = &fcpci_write_isac_fifo; + + adapter->read_hdlc_status = &fcpnp_read_hdlc_status; + adapter->write_ctrl = &fcpnp_write_ctrl; + break; + } + + // Reset + outb(0, adapter->io + AVM_STATUS0); + mdelay(10); + outb(AVM_STATUS0_RESET, adapter->io + AVM_STATUS0); + mdelay(10); + outb(0, adapter->io + AVM_STATUS0); + mdelay(10); + + switch (adapter->type) { + case AVM_FRITZ_PCIV2: + fcpci2_init(adapter); + isacsx_setup(&adapter->isac); + break; + case AVM_FRITZ_PCI: + case AVM_FRITZ_PNP: + fcpci_init(adapter); + isac_setup(&adapter->isac); + break; + } + val = adapter->read_hdlc_status(adapter, 0); + DBG(0x20, "HDLC A STA %x", val); + val = adapter->read_hdlc_status(adapter, 1); + DBG(0x20, "HDLC B STA %x", val); + + adapter->bcs[0].mode = -1; + adapter->bcs[1].mode = -1; + modehdlc(&adapter->bcs[0], L1_MODE_NULL); + modehdlc(&adapter->bcs[1], L1_MODE_NULL); + + return 0; + + err_region: + release_region(adapter->io, 32); + err: + return retval; +} + +static void __devexit fcpcipnp_release(struct fritz_adapter *adapter) +{ + DBG(1,""); + + outb(0, adapter->io + AVM_STATUS0); + free_irq(adapter->irq, adapter); + release_region(adapter->io, 32); +} + +// ---------------------------------------------------------------------- + +static struct fritz_adapter * __devinit +new_adapter(void) +{ + struct fritz_adapter *adapter; + struct hisax_b_if *b_if[2]; + int i; + + adapter = kmalloc(sizeof(struct fritz_adapter), GFP_KERNEL); + if (!adapter) + return NULL; + + memset(adapter, 0, sizeof(struct fritz_adapter)); + + adapter->isac.hisax_d_if.owner = THIS_MODULE; + adapter->isac.hisax_d_if.ifc.priv = &adapter->isac; + adapter->isac.hisax_d_if.ifc.l2l1 = isac_d_l2l1; + + for (i = 0; i < 2; i++) { + adapter->bcs[i].adapter = adapter; + adapter->bcs[i].channel = i; + adapter->bcs[i].b_if.ifc.priv = &adapter->bcs[i]; + adapter->bcs[i].b_if.ifc.l2l1 = fritz_b_l2l1; + } + + for (i = 0; i < 2; i++) + b_if[i] = &adapter->bcs[i].b_if; + + hisax_register(&adapter->isac.hisax_d_if, b_if, "fcpcipnp", protocol); + + return adapter; +} + +static void delete_adapter(struct fritz_adapter *adapter) +{ + hisax_unregister(&adapter->isac.hisax_d_if); + kfree(adapter); +} + +static int __devinit fcpci_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + struct fritz_adapter *adapter; + int retval; + + retval = -ENOMEM; + adapter = new_adapter(); + if (!adapter) + goto err; + + pci_set_drvdata(pdev, adapter); + + if (pdev->device == PCI_DEVICE_ID_AVM_A1_V2) + adapter->type = AVM_FRITZ_PCIV2; + else + adapter->type = AVM_FRITZ_PCI; + + retval = pci_enable_device(pdev); + if (retval) + goto err_free; + + adapter->io = pci_resource_start(pdev, 1); + adapter->irq = pdev->irq; + + printk(KERN_INFO "hisax_fcpcipnp: found adapter %s at %s\n", + (char *) ent->driver_data, pci_name(pdev)); + + retval = fcpcipnp_setup(adapter); + if (retval) + goto err_free; + + return 0; + + err_free: + delete_adapter(adapter); + err: + return retval; +} + +#ifdef __ISAPNP__ +static int __devinit fcpnp_probe(struct pnp_dev *pdev, const struct pnp_device_id *dev_id) +{ + struct fritz_adapter *adapter; + int retval; + + if (!pdev) + return(-ENODEV); + + retval = -ENOMEM; + adapter = new_adapter(); + if (!adapter) + goto err; + + pnp_set_drvdata(pdev, adapter); + + adapter->type = AVM_FRITZ_PNP; + + pnp_disable_dev(pdev); + retval = pnp_activate_dev(pdev); + if (retval < 0) { + printk(KERN_WARNING "%s: pnp_activate_dev(%s) ret(%d)\n", __FUNCTION__, + (char *)dev_id->driver_data, retval); + goto err_free; + } + adapter->io = pnp_port_start(pdev, 0); + adapter->irq = pnp_irq(pdev, 0); + + printk(KERN_INFO "hisax_fcpcipnp: found adapter %s at IO %#x irq %d\n", + (char *) dev_id->driver_data, adapter->io, adapter->irq); + + retval = fcpcipnp_setup(adapter); + if (retval) + goto err_free; + + return 0; + + err_free: + delete_adapter(adapter); + err: + return retval; +} + +static void __devexit fcpnp_remove(struct pnp_dev *pdev) +{ + struct fritz_adapter *adapter = pnp_get_drvdata(pdev); + + if (adapter) { + fcpcipnp_release(adapter); + delete_adapter(adapter); + } + pnp_disable_dev(pdev); +} + +static struct pnp_driver fcpnp_driver = { + .name = "fcpnp", + .probe = fcpnp_probe, + .remove = __devexit_p(fcpnp_remove), + .id_table = fcpnp_ids, +}; +#endif + +static void __devexit fcpci_remove(struct pci_dev *pdev) +{ + struct fritz_adapter *adapter = pci_get_drvdata(pdev); + + fcpcipnp_release(adapter); + pci_disable_device(pdev); + delete_adapter(adapter); +} + +static struct pci_driver fcpci_driver = { + .name = "fcpci", + .probe = fcpci_probe, + .remove = __devexit_p(fcpci_remove), + .id_table = fcpci_ids, +}; + +static int __init hisax_fcpcipnp_init(void) +{ + int retval; + + printk(KERN_INFO "hisax_fcpcipnp: Fritz!Card PCI/PCIv2/PnP ISDN driver v0.0.1\n"); + + retval = pci_register_driver(&fcpci_driver); + if (retval) + goto out; +#ifdef __ISAPNP__ + retval = pnp_register_driver(&fcpnp_driver); + if (retval < 0) + goto out_unregister_pci; +#endif + return 0; + + out_unregister_pci: + pci_unregister_driver(&fcpci_driver); + out: + return retval; +} + +static void __exit hisax_fcpcipnp_exit(void) +{ +#ifdef __ISAPNP__ + pnp_unregister_driver(&fcpnp_driver); +#endif + pci_unregister_driver(&fcpci_driver); +} + +module_init(hisax_fcpcipnp_init); +module_exit(hisax_fcpcipnp_exit); diff --git a/drivers/isdn/hisax/hisax_fcpcipnp.h b/drivers/isdn/hisax/hisax_fcpcipnp.h new file mode 100644 index 000000000000..bd8a22e4d6a2 --- /dev/null +++ b/drivers/isdn/hisax/hisax_fcpcipnp.h @@ -0,0 +1,58 @@ +#include "hisax_if.h" +#include "hisax_isac.h" +#include <linux/pci.h> + +#define HSCX_BUFMAX 4096 + +enum { + AVM_FRITZ_PCI, + AVM_FRITZ_PNP, + AVM_FRITZ_PCIV2, +}; + +struct hdlc_stat_reg { +#ifdef __BIG_ENDIAN + u_char fill __attribute__((packed)); + u_char mode __attribute__((packed)); + u_char xml __attribute__((packed)); + u_char cmd __attribute__((packed)); +#else + u_char cmd __attribute__((packed)); + u_char xml __attribute__((packed)); + u_char mode __attribute__((packed)); + u_char fill __attribute__((packed)); +#endif +}; + +struct fritz_bcs { + struct hisax_b_if b_if; + struct fritz_adapter *adapter; + int mode; + int channel; + + union { + u_int ctrl; + struct hdlc_stat_reg sr; + } ctrl; + u_int stat; + int rcvidx; + int fifo_size; + u_char rcvbuf[HSCX_BUFMAX]; /* B-Channel receive Buffer */ + + int tx_cnt; /* B-Channel transmit counter */ + struct sk_buff *tx_skb; /* B-Channel transmit Buffer */ +}; + +struct fritz_adapter { + int type; + spinlock_t hw_lock; + unsigned int io; + unsigned int irq; + struct isac isac; + + struct fritz_bcs bcs[2]; + + u32 (*read_hdlc_status) (struct fritz_adapter *adapter, int nr); + void (*write_ctrl) (struct fritz_bcs *bcs, int which); +}; + diff --git a/drivers/isdn/hisax/hisax_if.h b/drivers/isdn/hisax/hisax_if.h new file mode 100644 index 000000000000..4898fce2d509 --- /dev/null +++ b/drivers/isdn/hisax/hisax_if.h @@ -0,0 +1,66 @@ +/* + * Interface between low level (hardware) drivers and + * HiSax protocol stack + * + * Author Kai Germaschewski + * Copyright 2001 by Kai Germaschewski <kai.germaschewski@gmx.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#ifndef __HISAX_IF_H__ +#define __HISAX_IF_H__ + +#include <linux/skbuff.h> + +#define REQUEST 0 +#define CONFIRM 1 +#define INDICATION 2 +#define RESPONSE 3 + +#define PH_ACTIVATE 0x0100 +#define PH_DEACTIVATE 0x0110 +#define PH_DATA 0x0120 +#define PH_PULL 0x0130 +#define PH_DATA_E 0x0140 + +#define L1_MODE_NULL 0 +#define L1_MODE_TRANS 1 +#define L1_MODE_HDLC 2 +#define L1_MODE_EXTRN 3 +#define L1_MODE_HDLC_56K 4 +#define L1_MODE_MODEM 7 +#define L1_MODE_V32 8 +#define L1_MODE_FAX 9 + +struct hisax_if { + void *priv; // private to driver + void (*l1l2)(struct hisax_if *, int pr, void *arg); + void (*l2l1)(struct hisax_if *, int pr, void *arg); +}; + +struct hisax_b_if { + struct hisax_if ifc; + + // private to hisax + struct BCState *bcs; +}; + +struct hisax_d_if { + struct hisax_if ifc; + + // private to hisax + struct module *owner; + struct IsdnCardState *cs; + struct hisax_b_if *b_if[2]; + struct sk_buff_head erq; + long ph_state; +}; + +int hisax_register(struct hisax_d_if *hisax_if, struct hisax_b_if *b_if[], + char *name, int protocol); +void hisax_unregister(struct hisax_d_if *hisax_if); + +#endif diff --git a/drivers/isdn/hisax/hisax_isac.c b/drivers/isdn/hisax/hisax_isac.c new file mode 100644 index 000000000000..f4972f6c1f5d --- /dev/null +++ b/drivers/isdn/hisax/hisax_isac.c @@ -0,0 +1,897 @@ +/* + * Driver for ISAC-S and ISAC-SX + * ISDN Subscriber Access Controller for Terminals + * + * Author Kai Germaschewski + * Copyright 2001 by Kai Germaschewski <kai.germaschewski@gmx.de> + * 2001 by Karsten Keil <keil@isdn4linux.de> + * + * based upon Karsten Keil's original isac.c driver + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * Thanks to Wizard Computersysteme GmbH, Bremervoerde and + * SoHaNet Technology GmbH, Berlin + * for supporting the development of this driver + */ + +/* TODO: + * specifically handle level vs edge triggered? + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/netdevice.h> +#include "hisax_isac.h" + +// debugging cruft + +#define __debug_variable debug +#include "hisax_debug.h" + +#ifdef CONFIG_HISAX_DEBUG +static int debug = 1; +module_param(debug, int, 0); + +static char *ISACVer[] = { + "2086/2186 V1.1", + "2085 B1", + "2085 B2", + "2085 V2.3" +}; +#endif + +MODULE_AUTHOR("Kai Germaschewski <kai.germaschewski@gmx.de>/Karsten Keil <kkeil@suse.de>"); +MODULE_DESCRIPTION("ISAC/ISAC-SX driver"); +MODULE_LICENSE("GPL"); + +#define DBG_WARN 0x0001 +#define DBG_IRQ 0x0002 +#define DBG_L1M 0x0004 +#define DBG_PR 0x0008 +#define DBG_RFIFO 0x0100 +#define DBG_RPACKET 0x0200 +#define DBG_XFIFO 0x1000 +#define DBG_XPACKET 0x2000 + +// we need to distinguish ISAC-S and ISAC-SX +#define TYPE_ISAC 0x00 +#define TYPE_ISACSX 0x01 + +// registers etc. +#define ISAC_MASK 0x20 +#define ISAC_ISTA 0x20 +#define ISAC_ISTA_EXI 0x01 +#define ISAC_ISTA_SIN 0x02 +#define ISAC_ISTA_CISQ 0x04 +#define ISAC_ISTA_XPR 0x10 +#define ISAC_ISTA_RSC 0x20 +#define ISAC_ISTA_RPF 0x40 +#define ISAC_ISTA_RME 0x80 + +#define ISAC_STAR 0x21 +#define ISAC_CMDR 0x21 +#define ISAC_CMDR_XRES 0x01 +#define ISAC_CMDR_XME 0x02 +#define ISAC_CMDR_XTF 0x08 +#define ISAC_CMDR_RRES 0x40 +#define ISAC_CMDR_RMC 0x80 + +#define ISAC_EXIR 0x24 +#define ISAC_EXIR_MOS 0x04 +#define ISAC_EXIR_XDU 0x40 +#define ISAC_EXIR_XMR 0x80 + +#define ISAC_ADF2 0x39 +#define ISAC_SPCR 0x30 +#define ISAC_ADF1 0x38 + +#define ISAC_CIR0 0x31 +#define ISAC_CIX0 0x31 +#define ISAC_CIR0_CIC0 0x02 +#define ISAC_CIR0_CIC1 0x01 + +#define ISAC_CIR1 0x33 +#define ISAC_CIX1 0x33 +#define ISAC_STCR 0x37 +#define ISAC_MODE 0x22 + +#define ISAC_RSTA 0x27 +#define ISAC_RSTA_RDO 0x40 +#define ISAC_RSTA_CRC 0x20 +#define ISAC_RSTA_RAB 0x10 + +#define ISAC_RBCL 0x25 +#define ISAC_RBCH 0x2A +#define ISAC_TIMR 0x23 +#define ISAC_SQXR 0x3b +#define ISAC_MOSR 0x3a +#define ISAC_MOCR 0x3a +#define ISAC_MOR0 0x32 +#define ISAC_MOX0 0x32 +#define ISAC_MOR1 0x34 +#define ISAC_MOX1 0x34 + +#define ISAC_RBCH_XAC 0x80 + +#define ISAC_CMD_TIM 0x0 +#define ISAC_CMD_RES 0x1 +#define ISAC_CMD_SSP 0x2 +#define ISAC_CMD_SCP 0x3 +#define ISAC_CMD_AR8 0x8 +#define ISAC_CMD_AR10 0x9 +#define ISAC_CMD_ARL 0xa +#define ISAC_CMD_DI 0xf + +#define ISACSX_MASK 0x60 +#define ISACSX_ISTA 0x60 +#define ISACSX_ISTA_ICD 0x01 +#define ISACSX_ISTA_CIC 0x10 + +#define ISACSX_MASKD 0x20 +#define ISACSX_ISTAD 0x20 +#define ISACSX_ISTAD_XDU 0x04 +#define ISACSX_ISTAD_XMR 0x08 +#define ISACSX_ISTAD_XPR 0x10 +#define ISACSX_ISTAD_RFO 0x20 +#define ISACSX_ISTAD_RPF 0x40 +#define ISACSX_ISTAD_RME 0x80 + +#define ISACSX_CMDRD 0x21 +#define ISACSX_CMDRD_XRES 0x01 +#define ISACSX_CMDRD_XME 0x02 +#define ISACSX_CMDRD_XTF 0x08 +#define ISACSX_CMDRD_RRES 0x40 +#define ISACSX_CMDRD_RMC 0x80 + +#define ISACSX_MODED 0x22 + +#define ISACSX_RBCLD 0x26 + +#define ISACSX_RSTAD 0x28 +#define ISACSX_RSTAD_RAB 0x10 +#define ISACSX_RSTAD_CRC 0x20 +#define ISACSX_RSTAD_RDO 0x40 +#define ISACSX_RSTAD_VFR 0x80 + +#define ISACSX_CIR0 0x2e +#define ISACSX_CIR0_CIC0 0x08 +#define ISACSX_CIX0 0x2e + +#define ISACSX_TR_CONF0 0x30 + +#define ISACSX_TR_CONF2 0x32 + +static struct Fsm l1fsm; + +enum { + ST_L1_RESET, + ST_L1_F3_PDOWN, + ST_L1_F3_PUP, + ST_L1_F3_PEND_DEACT, + ST_L1_F4, + ST_L1_F5, + ST_L1_F6, + ST_L1_F7, + ST_L1_F8, +}; + +#define L1_STATE_COUNT (ST_L1_F8+1) + +static char *strL1State[] = +{ + "ST_L1_RESET", + "ST_L1_F3_PDOWN", + "ST_L1_F3_PUP", + "ST_L1_F3_PEND_DEACT", + "ST_L1_F4", + "ST_L1_F5", + "ST_L1_F6", + "ST_L1_F7", + "ST_L1_F8", +}; + +enum { + EV_PH_DR, // 0000 + EV_PH_RES, // 0001 + EV_PH_TMA, // 0010 + EV_PH_SLD, // 0011 + EV_PH_RSY, // 0100 + EV_PH_DR6, // 0101 + EV_PH_EI, // 0110 + EV_PH_PU, // 0111 + EV_PH_AR, // 1000 + EV_PH_9, // 1001 + EV_PH_ARL, // 1010 + EV_PH_CVR, // 1011 + EV_PH_AI8, // 1100 + EV_PH_AI10, // 1101 + EV_PH_AIL, // 1110 + EV_PH_DC, // 1111 + EV_PH_ACTIVATE_REQ, + EV_PH_DEACTIVATE_REQ, + EV_TIMER3, +}; + +#define L1_EVENT_COUNT (EV_TIMER3 + 1) + +static char *strL1Event[] = +{ + "EV_PH_DR", // 0000 + "EV_PH_RES", // 0001 + "EV_PH_TMA", // 0010 + "EV_PH_SLD", // 0011 + "EV_PH_RSY", // 0100 + "EV_PH_DR6", // 0101 + "EV_PH_EI", // 0110 + "EV_PH_PU", // 0111 + "EV_PH_AR", // 1000 + "EV_PH_9", // 1001 + "EV_PH_ARL", // 1010 + "EV_PH_CVR", // 1011 + "EV_PH_AI8", // 1100 + "EV_PH_AI10", // 1101 + "EV_PH_AIL", // 1110 + "EV_PH_DC", // 1111 + "EV_PH_ACTIVATE_REQ", + "EV_PH_DEACTIVATE_REQ", + "EV_TIMER3", +}; + +static inline void D_L1L2(struct isac *isac, int pr, void *arg) +{ + struct hisax_if *ifc = (struct hisax_if *) &isac->hisax_d_if; + + DBG(DBG_PR, "pr %#x", pr); + ifc->l1l2(ifc, pr, arg); +} + +static void ph_command(struct isac *isac, unsigned int command) +{ + DBG(DBG_L1M, "ph_command %#x", command); + switch (isac->type) { + case TYPE_ISAC: + isac->write_isac(isac, ISAC_CIX0, (command << 2) | 3); + break; + case TYPE_ISACSX: + isac->write_isac(isac, ISACSX_CIX0, (command << 4) | (7 << 1)); + break; + } +} + +// ---------------------------------------------------------------------- + +static void l1_di(struct FsmInst *fi, int event, void *arg) +{ + struct isac *isac = fi->userdata; + + FsmChangeState(fi, ST_L1_RESET); + ph_command(isac, ISAC_CMD_DI); +} + +static void l1_di_deact_ind(struct FsmInst *fi, int event, void *arg) +{ + struct isac *isac = fi->userdata; + + FsmChangeState(fi, ST_L1_RESET); + D_L1L2(isac, PH_DEACTIVATE | INDICATION, NULL); + ph_command(isac, ISAC_CMD_DI); +} + +static void l1_go_f3pdown(struct FsmInst *fi, int event, void *arg) +{ + FsmChangeState(fi, ST_L1_F3_PDOWN); +} + +static void l1_go_f3pend_deact_ind(struct FsmInst *fi, int event, void *arg) +{ + struct isac *isac = fi->userdata; + + FsmChangeState(fi, ST_L1_F3_PEND_DEACT); + D_L1L2(isac, PH_DEACTIVATE | INDICATION, NULL); + ph_command(isac, ISAC_CMD_DI); +} + +static void l1_go_f3pend(struct FsmInst *fi, int event, void *arg) +{ + struct isac *isac = fi->userdata; + + FsmChangeState(fi, ST_L1_F3_PEND_DEACT); + ph_command(isac, ISAC_CMD_DI); +} + +static void l1_go_f4(struct FsmInst *fi, int event, void *arg) +{ + FsmChangeState(fi, ST_L1_F4); +} + +static void l1_go_f5(struct FsmInst *fi, int event, void *arg) +{ + FsmChangeState(fi, ST_L1_F5); +} + +static void l1_go_f6(struct FsmInst *fi, int event, void *arg) +{ + FsmChangeState(fi, ST_L1_F6); +} + +static void l1_go_f6_deact_ind(struct FsmInst *fi, int event, void *arg) +{ + struct isac *isac = fi->userdata; + + FsmChangeState(fi, ST_L1_F6); + D_L1L2(isac, PH_DEACTIVATE | INDICATION, NULL); +} + +static void l1_go_f7_act_ind(struct FsmInst *fi, int event, void *arg) +{ + struct isac *isac = fi->userdata; + + FsmDelTimer(&isac->timer, 0); + FsmChangeState(fi, ST_L1_F7); + ph_command(isac, ISAC_CMD_AR8); + D_L1L2(isac, PH_ACTIVATE | INDICATION, NULL); +} + +static void l1_go_f8(struct FsmInst *fi, int event, void *arg) +{ + FsmChangeState(fi, ST_L1_F8); +} + +static void l1_go_f8_deact_ind(struct FsmInst *fi, int event, void *arg) +{ + struct isac *isac = fi->userdata; + + FsmChangeState(fi, ST_L1_F8); + D_L1L2(isac, PH_DEACTIVATE | INDICATION, NULL); +} + +static void l1_ar8(struct FsmInst *fi, int event, void *arg) +{ + struct isac *isac = fi->userdata; + + FsmRestartTimer(&isac->timer, TIMER3_VALUE, EV_TIMER3, NULL, 2); + ph_command(isac, ISAC_CMD_AR8); +} + +static void l1_timer3(struct FsmInst *fi, int event, void *arg) +{ + struct isac *isac = fi->userdata; + + ph_command(isac, ISAC_CMD_DI); + D_L1L2(isac, PH_DEACTIVATE | INDICATION, NULL); +} + +// state machines according to data sheet PSB 2186 / 3186 + +static struct FsmNode L1FnList[] __initdata = +{ + {ST_L1_RESET, EV_PH_RES, l1_di}, + {ST_L1_RESET, EV_PH_EI, l1_di}, + {ST_L1_RESET, EV_PH_DC, l1_go_f3pdown}, + {ST_L1_RESET, EV_PH_AR, l1_go_f6}, + {ST_L1_RESET, EV_PH_AI8, l1_go_f7_act_ind}, + + {ST_L1_F3_PDOWN, EV_PH_RES, l1_di}, + {ST_L1_F3_PDOWN, EV_PH_EI, l1_di}, + {ST_L1_F3_PDOWN, EV_PH_AR, l1_go_f6}, + {ST_L1_F3_PDOWN, EV_PH_RSY, l1_go_f5}, + {ST_L1_F3_PDOWN, EV_PH_PU, l1_go_f4}, + {ST_L1_F3_PDOWN, EV_PH_AI8, l1_go_f7_act_ind}, + {ST_L1_F3_PDOWN, EV_PH_ACTIVATE_REQ, l1_ar8}, + {ST_L1_F3_PDOWN, EV_TIMER3, l1_timer3}, + + {ST_L1_F3_PEND_DEACT, EV_PH_RES, l1_di}, + {ST_L1_F3_PEND_DEACT, EV_PH_EI, l1_di}, + {ST_L1_F3_PEND_DEACT, EV_PH_DC, l1_go_f3pdown}, + {ST_L1_F3_PEND_DEACT, EV_PH_RSY, l1_go_f5}, + {ST_L1_F3_PEND_DEACT, EV_PH_AR, l1_go_f6}, + {ST_L1_F3_PEND_DEACT, EV_PH_AI8, l1_go_f7_act_ind}, + + {ST_L1_F4, EV_PH_RES, l1_di}, + {ST_L1_F4, EV_PH_EI, l1_di}, + {ST_L1_F4, EV_PH_RSY, l1_go_f5}, + {ST_L1_F4, EV_PH_AI8, l1_go_f7_act_ind}, + {ST_L1_F4, EV_TIMER3, l1_timer3}, + {ST_L1_F4, EV_PH_DC, l1_go_f3pdown}, + + {ST_L1_F5, EV_PH_RES, l1_di}, + {ST_L1_F5, EV_PH_EI, l1_di}, + {ST_L1_F5, EV_PH_AR, l1_go_f6}, + {ST_L1_F5, EV_PH_AI8, l1_go_f7_act_ind}, + {ST_L1_F5, EV_TIMER3, l1_timer3}, + {ST_L1_F5, EV_PH_DR, l1_go_f3pend}, + {ST_L1_F5, EV_PH_DC, l1_go_f3pdown}, + + {ST_L1_F6, EV_PH_RES, l1_di}, + {ST_L1_F6, EV_PH_EI, l1_di}, + {ST_L1_F6, EV_PH_RSY, l1_go_f8}, + {ST_L1_F6, EV_PH_AI8, l1_go_f7_act_ind}, + {ST_L1_F6, EV_PH_DR6, l1_go_f3pend}, + {ST_L1_F6, EV_TIMER3, l1_timer3}, + {ST_L1_F6, EV_PH_DC, l1_go_f3pdown}, + + {ST_L1_F7, EV_PH_RES, l1_di_deact_ind}, + {ST_L1_F7, EV_PH_EI, l1_di_deact_ind}, + {ST_L1_F7, EV_PH_AR, l1_go_f6_deact_ind}, + {ST_L1_F7, EV_PH_RSY, l1_go_f8_deact_ind}, + {ST_L1_F7, EV_PH_DR, l1_go_f3pend_deact_ind}, + + {ST_L1_F8, EV_PH_RES, l1_di}, + {ST_L1_F8, EV_PH_EI, l1_di}, + {ST_L1_F8, EV_PH_AR, l1_go_f6}, + {ST_L1_F8, EV_PH_DR, l1_go_f3pend}, + {ST_L1_F8, EV_PH_AI8, l1_go_f7_act_ind}, + {ST_L1_F8, EV_TIMER3, l1_timer3}, + {ST_L1_F8, EV_PH_DC, l1_go_f3pdown}, +}; + +static void l1m_debug(struct FsmInst *fi, char *fmt, ...) +{ + va_list args; + char buf[256]; + + va_start(args, fmt); + vsprintf(buf, fmt, args); + DBG(DBG_L1M, "%s", buf); + va_end(args); +} + +static void isac_version(struct isac *cs) +{ + int val; + + val = cs->read_isac(cs, ISAC_RBCH); + DBG(1, "ISAC version (%x): %s", val, ISACVer[(val >> 5) & 3]); +} + +static void isac_empty_fifo(struct isac *isac, int count) +{ + // this also works for isacsx, since + // CMDR(D) register works the same + u_char *ptr; + + DBG(DBG_IRQ, "count %d", count); + + if ((isac->rcvidx + count) >= MAX_DFRAME_LEN_L1) { + DBG(DBG_WARN, "overrun %d", isac->rcvidx + count); + isac->write_isac(isac, ISAC_CMDR, ISAC_CMDR_RMC); + isac->rcvidx = 0; + return; + } + ptr = isac->rcvbuf + isac->rcvidx; + isac->rcvidx += count; + isac->read_isac_fifo(isac, ptr, count); + isac->write_isac(isac, ISAC_CMDR, ISAC_CMDR_RMC); + DBG_PACKET(DBG_RFIFO, ptr, count); +} + +static void isac_fill_fifo(struct isac *isac) +{ + // this also works for isacsx, since + // CMDR(D) register works the same + + int count; + unsigned char cmd; + u_char *ptr; + + if (!isac->tx_skb) + BUG(); + + count = isac->tx_skb->len; + if (count <= 0) + BUG(); + + DBG(DBG_IRQ, "count %d", count); + + if (count > 0x20) { + count = 0x20; + cmd = ISAC_CMDR_XTF; + } else { + cmd = ISAC_CMDR_XTF | ISAC_CMDR_XME; + } + + ptr = isac->tx_skb->data; + skb_pull(isac->tx_skb, count); + isac->tx_cnt += count; + DBG_PACKET(DBG_XFIFO, ptr, count); + isac->write_isac_fifo(isac, ptr, count); + isac->write_isac(isac, ISAC_CMDR, cmd); +} + +static void isac_retransmit(struct isac *isac) +{ + if (!isac->tx_skb) { + DBG(DBG_WARN, "no skb"); + return; + } + skb_push(isac->tx_skb, isac->tx_cnt); + isac->tx_cnt = 0; +} + + +static inline void isac_cisq_interrupt(struct isac *isac) +{ + unsigned char val; + + val = isac->read_isac(isac, ISAC_CIR0); + DBG(DBG_IRQ, "CIR0 %#x", val); + if (val & ISAC_CIR0_CIC0) { + DBG(DBG_IRQ, "CODR0 %#x", (val >> 2) & 0xf); + FsmEvent(&isac->l1m, (val >> 2) & 0xf, NULL); + } + if (val & ISAC_CIR0_CIC1) { + val = isac->read_isac(isac, ISAC_CIR1); + DBG(DBG_WARN, "ISAC CIR1 %#x", val ); + } +} + +static inline void isac_rme_interrupt(struct isac *isac) +{ + unsigned char val; + int count; + struct sk_buff *skb; + + val = isac->read_isac(isac, ISAC_RSTA); + if ((val & (ISAC_RSTA_RDO | ISAC_RSTA_CRC | ISAC_RSTA_RAB) ) + != ISAC_RSTA_CRC) { + DBG(DBG_WARN, "RSTA %#x, dropped", val); + isac->write_isac(isac, ISAC_CMDR, ISAC_CMDR_RMC); + goto out; + } + + count = isac->read_isac(isac, ISAC_RBCL) & 0x1f; + DBG(DBG_IRQ, "RBCL %#x", count); + if (count == 0) + count = 0x20; + + isac_empty_fifo(isac, count); + count = isac->rcvidx; + if (count < 1) { + DBG(DBG_WARN, "count %d < 1", count); + goto out; + } + + skb = alloc_skb(count, GFP_ATOMIC); + if (!skb) { + DBG(DBG_WARN, "no memory, dropping\n"); + goto out; + } + memcpy(skb_put(skb, count), isac->rcvbuf, count); + DBG_SKB(DBG_RPACKET, skb); + D_L1L2(isac, PH_DATA | INDICATION, skb); + out: + isac->rcvidx = 0; +} + +static inline void isac_xpr_interrupt(struct isac *isac) +{ + if (!isac->tx_skb) + return; + + if (isac->tx_skb->len > 0) { + isac_fill_fifo(isac); + return; + } + dev_kfree_skb_irq(isac->tx_skb); + isac->tx_cnt = 0; + isac->tx_skb = NULL; + D_L1L2(isac, PH_DATA | CONFIRM, NULL); +} + +static inline void isac_exi_interrupt(struct isac *isac) +{ + unsigned char val; + + val = isac->read_isac(isac, ISAC_EXIR); + DBG(2, "EXIR %#x", val); + + if (val & ISAC_EXIR_XMR) { + DBG(DBG_WARN, "ISAC XMR"); + isac_retransmit(isac); + } + if (val & ISAC_EXIR_XDU) { + DBG(DBG_WARN, "ISAC XDU"); + isac_retransmit(isac); + } + if (val & ISAC_EXIR_MOS) { /* MOS */ + DBG(DBG_WARN, "MOS"); + val = isac->read_isac(isac, ISAC_MOSR); + DBG(2, "ISAC MOSR %#x", val); + } +} + +void isac_irq(struct isac *isac) +{ + unsigned char val; + + val = isac->read_isac(isac, ISAC_ISTA); + DBG(DBG_IRQ, "ISTA %#x", val); + + if (val & ISAC_ISTA_EXI) { + DBG(DBG_IRQ, "EXI"); + isac_exi_interrupt(isac); + } + if (val & ISAC_ISTA_XPR) { + DBG(DBG_IRQ, "XPR"); + isac_xpr_interrupt(isac); + } + if (val & ISAC_ISTA_RME) { + DBG(DBG_IRQ, "RME"); + isac_rme_interrupt(isac); + } + if (val & ISAC_ISTA_RPF) { + DBG(DBG_IRQ, "RPF"); + isac_empty_fifo(isac, 0x20); + } + if (val & ISAC_ISTA_CISQ) { + DBG(DBG_IRQ, "CISQ"); + isac_cisq_interrupt(isac); + } + if (val & ISAC_ISTA_RSC) { + DBG(DBG_WARN, "RSC"); + } + if (val & ISAC_ISTA_SIN) { + DBG(DBG_WARN, "SIN"); + } + isac->write_isac(isac, ISAC_MASK, 0xff); + isac->write_isac(isac, ISAC_MASK, 0x00); +} + +// ====================================================================== + +static inline void isacsx_cic_interrupt(struct isac *isac) +{ + unsigned char val; + + val = isac->read_isac(isac, ISACSX_CIR0); + DBG(DBG_IRQ, "CIR0 %#x", val); + if (val & ISACSX_CIR0_CIC0) { + DBG(DBG_IRQ, "CODR0 %#x", val >> 4); + FsmEvent(&isac->l1m, val >> 4, NULL); + } +} + +static inline void isacsx_rme_interrupt(struct isac *isac) +{ + int count; + struct sk_buff *skb; + unsigned char val; + + val = isac->read_isac(isac, ISACSX_RSTAD); + if ((val & (ISACSX_RSTAD_VFR | + ISACSX_RSTAD_RDO | + ISACSX_RSTAD_CRC | + ISACSX_RSTAD_RAB)) + != (ISACSX_RSTAD_VFR | ISACSX_RSTAD_CRC)) { + DBG(DBG_WARN, "RSTAD %#x, dropped", val); + isac->write_isac(isac, ISACSX_CMDRD, ISACSX_CMDRD_RMC); + goto out; + } + + count = isac->read_isac(isac, ISACSX_RBCLD) & 0x1f; + DBG(DBG_IRQ, "RBCLD %#x", count); + if (count == 0) + count = 0x20; + + isac_empty_fifo(isac, count); + // strip trailing status byte + count = isac->rcvidx - 1; + if (count < 1) { + DBG(DBG_WARN, "count %d < 1", count); + goto out; + } + + skb = dev_alloc_skb(count); + if (!skb) { + DBG(DBG_WARN, "no memory, dropping"); + goto out; + } + memcpy(skb_put(skb, count), isac->rcvbuf, count); + DBG_SKB(DBG_RPACKET, skb); + D_L1L2(isac, PH_DATA | INDICATION, skb); + out: + isac->rcvidx = 0; +} + +static inline void isacsx_xpr_interrupt(struct isac *isac) +{ + if (!isac->tx_skb) + return; + + if (isac->tx_skb->len > 0) { + isac_fill_fifo(isac); + return; + } + dev_kfree_skb_irq(isac->tx_skb); + isac->tx_skb = NULL; + isac->tx_cnt = 0; + D_L1L2(isac, PH_DATA | CONFIRM, NULL); +} + +static inline void isacsx_icd_interrupt(struct isac *isac) +{ + unsigned char val; + + val = isac->read_isac(isac, ISACSX_ISTAD); + DBG(DBG_IRQ, "ISTAD %#x", val); + if (val & ISACSX_ISTAD_XDU) { + DBG(DBG_WARN, "ISTAD XDU"); + isac_retransmit(isac); + } + if (val & ISACSX_ISTAD_XMR) { + DBG(DBG_WARN, "ISTAD XMR"); + isac_retransmit(isac); + } + if (val & ISACSX_ISTAD_XPR) { + DBG(DBG_IRQ, "ISTAD XPR"); + isacsx_xpr_interrupt(isac); + } + if (val & ISACSX_ISTAD_RFO) { + DBG(DBG_WARN, "ISTAD RFO"); + isac->write_isac(isac, ISACSX_CMDRD, ISACSX_CMDRD_RMC); + } + if (val & ISACSX_ISTAD_RME) { + DBG(DBG_IRQ, "ISTAD RME"); + isacsx_rme_interrupt(isac); + } + if (val & ISACSX_ISTAD_RPF) { + DBG(DBG_IRQ, "ISTAD RPF"); + isac_empty_fifo(isac, 0x20); + } +} + +void isacsx_irq(struct isac *isac) +{ + unsigned char val; + + val = isac->read_isac(isac, ISACSX_ISTA); + DBG(DBG_IRQ, "ISTA %#x", val); + + if (val & ISACSX_ISTA_ICD) + isacsx_icd_interrupt(isac); + if (val & ISACSX_ISTA_CIC) + isacsx_cic_interrupt(isac); +} + +void isac_init(struct isac *isac) +{ + isac->tx_skb = NULL; + isac->l1m.fsm = &l1fsm; + isac->l1m.state = ST_L1_RESET; +#ifdef CONFIG_HISAX_DEBUG + isac->l1m.debug = 1; +#else + isac->l1m.debug = 0; +#endif + isac->l1m.userdata = isac; + isac->l1m.printdebug = l1m_debug; + FsmInitTimer(&isac->l1m, &isac->timer); +} + +void isac_setup(struct isac *isac) +{ + int val, eval; + + isac->type = TYPE_ISAC; + isac_version(isac); + + ph_command(isac, ISAC_CMD_RES); + + isac->write_isac(isac, ISAC_MASK, 0xff); + isac->mocr = 0xaa; + if (test_bit(ISAC_IOM1, &isac->flags)) { + /* IOM 1 Mode */ + isac->write_isac(isac, ISAC_ADF2, 0x0); + isac->write_isac(isac, ISAC_SPCR, 0xa); + isac->write_isac(isac, ISAC_ADF1, 0x2); + isac->write_isac(isac, ISAC_STCR, 0x70); + isac->write_isac(isac, ISAC_MODE, 0xc9); + } else { + /* IOM 2 Mode */ + if (!isac->adf2) + isac->adf2 = 0x80; + isac->write_isac(isac, ISAC_ADF2, isac->adf2); + isac->write_isac(isac, ISAC_SQXR, 0x2f); + isac->write_isac(isac, ISAC_SPCR, 0x00); + isac->write_isac(isac, ISAC_STCR, 0x70); + isac->write_isac(isac, ISAC_MODE, 0xc9); + isac->write_isac(isac, ISAC_TIMR, 0x00); + isac->write_isac(isac, ISAC_ADF1, 0x00); + } + val = isac->read_isac(isac, ISAC_STAR); + DBG(2, "ISAC STAR %x", val); + val = isac->read_isac(isac, ISAC_MODE); + DBG(2, "ISAC MODE %x", val); + val = isac->read_isac(isac, ISAC_ADF2); + DBG(2, "ISAC ADF2 %x", val); + val = isac->read_isac(isac, ISAC_ISTA); + DBG(2, "ISAC ISTA %x", val); + if (val & 0x01) { + eval = isac->read_isac(isac, ISAC_EXIR); + DBG(2, "ISAC EXIR %x", eval); + } + val = isac->read_isac(isac, ISAC_CIR0); + DBG(2, "ISAC CIR0 %x", val); + FsmEvent(&isac->l1m, (val >> 2) & 0xf, NULL); + + isac->write_isac(isac, ISAC_MASK, 0x0); + // RESET Receiver and Transmitter + isac->write_isac(isac, ISAC_CMDR, ISAC_CMDR_XRES | ISAC_CMDR_RRES); +} + +void isacsx_setup(struct isac *isac) +{ + isac->type = TYPE_ISACSX; + // clear LDD + isac->write_isac(isac, ISACSX_TR_CONF0, 0x00); + // enable transmitter + isac->write_isac(isac, ISACSX_TR_CONF2, 0x00); + // transparent mode 0, RAC, stop/go + isac->write_isac(isac, ISACSX_MODED, 0xc9); + // all HDLC IRQ unmasked + isac->write_isac(isac, ISACSX_MASKD, 0x03); + // unmask ICD, CID IRQs + isac->write_isac(isac, ISACSX_MASK, + ~(ISACSX_ISTA_ICD | ISACSX_ISTA_CIC)); +} + +void isac_d_l2l1(struct hisax_if *hisax_d_if, int pr, void *arg) +{ + struct isac *isac = hisax_d_if->priv; + struct sk_buff *skb = arg; + + DBG(DBG_PR, "pr %#x", pr); + + switch (pr) { + case PH_ACTIVATE | REQUEST: + FsmEvent(&isac->l1m, EV_PH_ACTIVATE_REQ, NULL); + break; + case PH_DEACTIVATE | REQUEST: + FsmEvent(&isac->l1m, EV_PH_DEACTIVATE_REQ, NULL); + break; + case PH_DATA | REQUEST: + DBG(DBG_PR, "PH_DATA REQUEST len %d", skb->len); + DBG_SKB(DBG_XPACKET, skb); + if (isac->l1m.state != ST_L1_F7) { + DBG(1, "L1 wrong state %d\n", isac->l1m.state); + dev_kfree_skb(skb); + break; + } + if (isac->tx_skb) + BUG(); + + isac->tx_skb = skb; + isac_fill_fifo(isac); + break; + } +} + +static int __init hisax_isac_init(void) +{ + printk(KERN_INFO "hisax_isac: ISAC-S/ISAC-SX ISDN driver v0.1.0\n"); + + l1fsm.state_count = L1_STATE_COUNT; + l1fsm.event_count = L1_EVENT_COUNT; + l1fsm.strState = strL1State; + l1fsm.strEvent = strL1Event; + return FsmNew(&l1fsm, L1FnList, ARRAY_SIZE(L1FnList)); +} + +static void __exit hisax_isac_exit(void) +{ + FsmFree(&l1fsm); +} + +EXPORT_SYMBOL(isac_init); +EXPORT_SYMBOL(isac_d_l2l1); + +EXPORT_SYMBOL(isacsx_setup); +EXPORT_SYMBOL(isacsx_irq); + +EXPORT_SYMBOL(isac_setup); +EXPORT_SYMBOL(isac_irq); + +module_init(hisax_isac_init); +module_exit(hisax_isac_exit); diff --git a/drivers/isdn/hisax/hisax_isac.h b/drivers/isdn/hisax/hisax_isac.h new file mode 100644 index 000000000000..08890cf4d923 --- /dev/null +++ b/drivers/isdn/hisax/hisax_isac.h @@ -0,0 +1,45 @@ +#ifndef __HISAX_ISAC_H__ +#define __HISAX_ISAC_H__ + +#include <linux/kernel.h> +#include "fsm.h" +#include "hisax_if.h" + +#define TIMER3_VALUE 7000 +#define MAX_DFRAME_LEN_L1 300 + +#define ISAC_IOM1 0 + +struct isac { + void *priv; + + u_long flags; + struct hisax_d_if hisax_d_if; + struct FsmInst l1m; + struct FsmTimer timer; + u_char mocr; + u_char adf2; + int type; + + u_char rcvbuf[MAX_DFRAME_LEN_L1]; + int rcvidx; + + struct sk_buff *tx_skb; + int tx_cnt; + + u_char (*read_isac) (struct isac *, u_char); + void (*write_isac) (struct isac *, u_char, u_char); + void (*read_isac_fifo) (struct isac *, u_char *, int); + void (*write_isac_fifo)(struct isac *, u_char *, int); +}; + +void isac_init(struct isac *isac); +void isac_d_l2l1(struct hisax_if *hisax_d_if, int pr, void *arg); + +void isac_setup(struct isac *isac); +void isac_irq(struct isac *isac); + +void isacsx_setup(struct isac *isac); +void isacsx_irq(struct isac *isac); + +#endif diff --git a/drivers/isdn/hisax/hscx.c b/drivers/isdn/hisax/hscx.c new file mode 100644 index 000000000000..5bbbe3e95125 --- /dev/null +++ b/drivers/isdn/hisax/hscx.c @@ -0,0 +1,280 @@ +/* $Id: hscx.c,v 1.24.2.4 2004/01/24 20:47:23 keil Exp $ + * + * HSCX specific routines + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/init.h> +#include "hisax.h" +#include "hscx.h" +#include "isac.h" +#include "isdnl1.h" +#include <linux/interrupt.h> + +static char *HSCXVer[] = +{"A1", "?1", "A2", "?3", "A3", "V2.1", "?6", "?7", + "?8", "?9", "?10", "?11", "?12", "?13", "?14", "???"}; + +int +HscxVersion(struct IsdnCardState *cs, char *s) +{ + int verA, verB; + + verA = cs->BC_Read_Reg(cs, 0, HSCX_VSTR) & 0xf; + verB = cs->BC_Read_Reg(cs, 1, HSCX_VSTR) & 0xf; + printk(KERN_INFO "%s HSCX version A: %s B: %s\n", s, + HSCXVer[verA], HSCXVer[verB]); + if ((verA == 0) | (verA == 0xf) | (verB == 0) | (verB == 0xf)) + return (1); + else + return (0); +} + +void +modehscx(struct BCState *bcs, int mode, int bc) +{ + struct IsdnCardState *cs = bcs->cs; + int hscx = bcs->hw.hscx.hscx; + + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hscx %c mode %d ichan %d", + 'A' + hscx, mode, bc); + bcs->mode = mode; + bcs->channel = bc; + cs->BC_Write_Reg(cs, hscx, HSCX_XAD1, 0xFF); + cs->BC_Write_Reg(cs, hscx, HSCX_XAD2, 0xFF); + cs->BC_Write_Reg(cs, hscx, HSCX_RAH2, 0xFF); + cs->BC_Write_Reg(cs, hscx, HSCX_XBCH, 0x0); + cs->BC_Write_Reg(cs, hscx, HSCX_RLCR, 0x0); + cs->BC_Write_Reg(cs, hscx, HSCX_CCR1, + test_bit(HW_IPAC, &cs->HW_Flags) ? 0x82 : 0x85); + cs->BC_Write_Reg(cs, hscx, HSCX_CCR2, 0x30); + cs->BC_Write_Reg(cs, hscx, HSCX_XCCR, 7); + cs->BC_Write_Reg(cs, hscx, HSCX_RCCR, 7); + + /* Switch IOM 1 SSI */ + if (test_bit(HW_IOM1, &cs->HW_Flags) && (hscx == 0)) + bc = 1 - bc; + + if (bc == 0) { + cs->BC_Write_Reg(cs, hscx, HSCX_TSAX, + test_bit(HW_IOM1, &cs->HW_Flags) ? 0x7 : bcs->hw.hscx.tsaxr0); + cs->BC_Write_Reg(cs, hscx, HSCX_TSAR, + test_bit(HW_IOM1, &cs->HW_Flags) ? 0x7 : bcs->hw.hscx.tsaxr0); + } else { + cs->BC_Write_Reg(cs, hscx, HSCX_TSAX, bcs->hw.hscx.tsaxr1); + cs->BC_Write_Reg(cs, hscx, HSCX_TSAR, bcs->hw.hscx.tsaxr1); + } + switch (mode) { + case (L1_MODE_NULL): + cs->BC_Write_Reg(cs, hscx, HSCX_TSAX, 0x1f); + cs->BC_Write_Reg(cs, hscx, HSCX_TSAR, 0x1f); + cs->BC_Write_Reg(cs, hscx, HSCX_MODE, 0x84); + break; + case (L1_MODE_TRANS): + cs->BC_Write_Reg(cs, hscx, HSCX_MODE, 0xe4); + break; + case (L1_MODE_HDLC): + cs->BC_Write_Reg(cs, hscx, HSCX_CCR1, + test_bit(HW_IPAC, &cs->HW_Flags) ? 0x8a : 0x8d); + cs->BC_Write_Reg(cs, hscx, HSCX_MODE, 0x8c); + break; + } + if (mode) + cs->BC_Write_Reg(cs, hscx, HSCX_CMDR, 0x41); + cs->BC_Write_Reg(cs, hscx, HSCX_ISTA, 0x00); +} + +void +hscx_l2l1(struct PStack *st, int pr, void *arg) +{ + struct BCState *bcs = st->l1.bcs; + u_long flags; + struct sk_buff *skb = arg; + + switch (pr) { + case (PH_DATA | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + skb_queue_tail(&bcs->squeue, skb); + } else { + bcs->tx_skb = skb; + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->hw.hscx.count = 0; + bcs->cs->BC_Send_Data(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | INDICATION): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + printk(KERN_WARNING "hscx_l2l1: this shouldn't happen\n"); + } else { + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->tx_skb = skb; + bcs->hw.hscx.count = 0; + bcs->cs->BC_Send_Data(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | REQUEST): + if (!bcs->tx_skb) { + test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } else + test_and_set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + case (PH_ACTIVATE | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + test_and_set_bit(BC_FLG_ACTIV, &bcs->Flag); + modehscx(bcs, st->l1.mode, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | REQUEST): + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | CONFIRM): + spin_lock_irqsave(&bcs->cs->lock, flags); + test_and_clear_bit(BC_FLG_ACTIV, &bcs->Flag); + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + modehscx(bcs, 0, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + st->l1.l1l2(st, PH_DEACTIVATE | CONFIRM, NULL); + break; + } +} + +void +close_hscxstate(struct BCState *bcs) +{ + modehscx(bcs, 0, bcs->channel); + if (test_and_clear_bit(BC_FLG_INIT, &bcs->Flag)) { + if (bcs->hw.hscx.rcvbuf) { + kfree(bcs->hw.hscx.rcvbuf); + bcs->hw.hscx.rcvbuf = NULL; + } + if (bcs->blog) { + kfree(bcs->blog); + bcs->blog = NULL; + } + skb_queue_purge(&bcs->rqueue); + skb_queue_purge(&bcs->squeue); + if (bcs->tx_skb) { + dev_kfree_skb_any(bcs->tx_skb); + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + } + } +} + +int +open_hscxstate(struct IsdnCardState *cs, struct BCState *bcs) +{ + if (!test_and_set_bit(BC_FLG_INIT, &bcs->Flag)) { + if (!(bcs->hw.hscx.rcvbuf = kmalloc(HSCX_BUFMAX, GFP_ATOMIC))) { + printk(KERN_WARNING + "HiSax: No memory for hscx.rcvbuf\n"); + test_and_clear_bit(BC_FLG_INIT, &bcs->Flag); + return (1); + } + if (!(bcs->blog = kmalloc(MAX_BLOG_SPACE, GFP_ATOMIC))) { + printk(KERN_WARNING + "HiSax: No memory for bcs->blog\n"); + test_and_clear_bit(BC_FLG_INIT, &bcs->Flag); + kfree(bcs->hw.hscx.rcvbuf); + bcs->hw.hscx.rcvbuf = NULL; + return (2); + } + skb_queue_head_init(&bcs->rqueue); + skb_queue_head_init(&bcs->squeue); + } + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->event = 0; + bcs->hw.hscx.rcvidx = 0; + bcs->tx_cnt = 0; + return (0); +} + +int +setstack_hscx(struct PStack *st, struct BCState *bcs) +{ + bcs->channel = st->l1.bc; + if (open_hscxstate(st->l1.hardware, bcs)) + return (-1); + st->l1.bcs = bcs; + st->l2.l2l1 = hscx_l2l1; + setstack_manager(st); + bcs->st = st; + setstack_l1_B(st); + return (0); +} + +void +clear_pending_hscx_ints(struct IsdnCardState *cs) +{ + int val, eval; + + val = cs->BC_Read_Reg(cs, 1, HSCX_ISTA); + debugl1(cs, "HSCX B ISTA %x", val); + if (val & 0x01) { + eval = cs->BC_Read_Reg(cs, 1, HSCX_EXIR); + debugl1(cs, "HSCX B EXIR %x", eval); + } + if (val & 0x02) { + eval = cs->BC_Read_Reg(cs, 0, HSCX_EXIR); + debugl1(cs, "HSCX A EXIR %x", eval); + } + val = cs->BC_Read_Reg(cs, 0, HSCX_ISTA); + debugl1(cs, "HSCX A ISTA %x", val); + val = cs->BC_Read_Reg(cs, 1, HSCX_STAR); + debugl1(cs, "HSCX B STAR %x", val); + val = cs->BC_Read_Reg(cs, 0, HSCX_STAR); + debugl1(cs, "HSCX A STAR %x", val); + /* disable all IRQ */ + cs->BC_Write_Reg(cs, 0, HSCX_MASK, 0xFF); + cs->BC_Write_Reg(cs, 1, HSCX_MASK, 0xFF); +} + +void +inithscx(struct IsdnCardState *cs) +{ + cs->bcs[0].BC_SetStack = setstack_hscx; + cs->bcs[1].BC_SetStack = setstack_hscx; + cs->bcs[0].BC_Close = close_hscxstate; + cs->bcs[1].BC_Close = close_hscxstate; + cs->bcs[0].hw.hscx.hscx = 0; + cs->bcs[1].hw.hscx.hscx = 1; + cs->bcs[0].hw.hscx.tsaxr0 = 0x2f; + cs->bcs[0].hw.hscx.tsaxr1 = 3; + cs->bcs[1].hw.hscx.tsaxr0 = 0x2f; + cs->bcs[1].hw.hscx.tsaxr1 = 3; + modehscx(cs->bcs, 0, 0); + modehscx(cs->bcs + 1, 0, 0); +} + +void +inithscxisac(struct IsdnCardState *cs, int part) +{ + if (part & 1) { + clear_pending_isac_ints(cs); + clear_pending_hscx_ints(cs); + initisac(cs); + inithscx(cs); + } + if (part & 2) { + /* Reenable all IRQ */ + cs->writeisac(cs, ISAC_MASK, 0); + cs->BC_Write_Reg(cs, 0, HSCX_MASK, 0); + cs->BC_Write_Reg(cs, 1, HSCX_MASK, 0); + /* RESET Receiver and Transmitter */ + cs->writeisac(cs, ISAC_CMDR, 0x41); + } +} diff --git a/drivers/isdn/hisax/hscx.h b/drivers/isdn/hisax/hscx.h new file mode 100644 index 000000000000..268bfd3549b0 --- /dev/null +++ b/drivers/isdn/hisax/hscx.h @@ -0,0 +1,41 @@ +/* $Id: hscx.h,v 1.8.2.2 2004/01/12 22:52:26 keil Exp $ + * + * HSCX specific defines + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +/* All Registers original Siemens Spec */ + +#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 + +extern int HscxVersion(struct IsdnCardState *cs, char *s); +extern void modehscx(struct BCState *bcs, int mode, int bc); +extern void clear_pending_hscx_ints(struct IsdnCardState *cs); +extern void inithscx(struct IsdnCardState *cs); +extern void inithscxisac(struct IsdnCardState *cs, int part); diff --git a/drivers/isdn/hisax/hscx_irq.c b/drivers/isdn/hisax/hscx_irq.c new file mode 100644 index 000000000000..5fe9d42d03a3 --- /dev/null +++ b/drivers/isdn/hisax/hscx_irq.c @@ -0,0 +1,292 @@ +/* $Id: hscx_irq.c,v 1.18.2.3 2004/02/11 13:21:34 keil Exp $ + * + * low level b-channel stuff for Siemens HSCX + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * This is an include file for fast inline IRQ stuff + * + */ + + +static inline void +waitforCEC(struct IsdnCardState *cs, int hscx) +{ + int to = 50; + + while ((READHSCX(cs, hscx, HSCX_STAR) & 0x04) && to) { + udelay(1); + to--; + } + if (!to) + printk(KERN_WARNING "HiSax: waitforCEC timeout\n"); +} + + +static inline void +waitforXFW(struct IsdnCardState *cs, int hscx) +{ + int to = 50; + + while ((!(READHSCX(cs, hscx, HSCX_STAR) & 0x44) == 0x40) && to) { + udelay(1); + to--; + } + if (!to) + printk(KERN_WARNING "HiSax: waitforXFW timeout\n"); +} + +static inline void +WriteHSCXCMDR(struct IsdnCardState *cs, int hscx, u_char data) +{ + waitforCEC(cs, hscx); + WRITEHSCX(cs, hscx, HSCX_CMDR, data); +} + + + +static void +hscx_empty_fifo(struct BCState *bcs, int count) +{ + u_char *ptr; + struct IsdnCardState *cs = bcs->cs; + + if ((cs->debug & L1_DEB_HSCX) && !(cs->debug & L1_DEB_HSCX_FIFO)) + debugl1(cs, "hscx_empty_fifo"); + + if (bcs->hw.hscx.rcvidx + count > HSCX_BUFMAX) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "hscx_empty_fifo: incoming packet too large"); + WriteHSCXCMDR(cs, bcs->hw.hscx.hscx, 0x80); + bcs->hw.hscx.rcvidx = 0; + return; + } + ptr = bcs->hw.hscx.rcvbuf + bcs->hw.hscx.rcvidx; + bcs->hw.hscx.rcvidx += count; + READHSCXFIFO(cs, bcs->hw.hscx.hscx, ptr, count); + WriteHSCXCMDR(cs, bcs->hw.hscx.hscx, 0x80); + if (cs->debug & L1_DEB_HSCX_FIFO) { + char *t = bcs->blog; + + t += sprintf(t, "hscx_empty_fifo %c cnt %d", + bcs->hw.hscx.hscx ? 'B' : 'A', count); + QuickHex(t, ptr, count); + debugl1(cs, bcs->blog); + } +} + +static void +hscx_fill_fifo(struct BCState *bcs) +{ + struct IsdnCardState *cs = bcs->cs; + int more, count; + int fifo_size = test_bit(HW_IPAC, &cs->HW_Flags)? 64: 32; + u_char *ptr; + + if ((cs->debug & L1_DEB_HSCX) && !(cs->debug & L1_DEB_HSCX_FIFO)) + debugl1(cs, "hscx_fill_fifo"); + + if (!bcs->tx_skb) + return; + if (bcs->tx_skb->len <= 0) + return; + + more = (bcs->mode == L1_MODE_TRANS) ? 1 : 0; + if (bcs->tx_skb->len > fifo_size) { + more = !0; + count = fifo_size; + } else + count = bcs->tx_skb->len; + + waitforXFW(cs, bcs->hw.hscx.hscx); + ptr = bcs->tx_skb->data; + skb_pull(bcs->tx_skb, count); + bcs->tx_cnt -= count; + bcs->hw.hscx.count += count; + WRITEHSCXFIFO(cs, bcs->hw.hscx.hscx, ptr, count); + WriteHSCXCMDR(cs, bcs->hw.hscx.hscx, more ? 0x8 : 0xa); + if (cs->debug & L1_DEB_HSCX_FIFO) { + char *t = bcs->blog; + + t += sprintf(t, "hscx_fill_fifo %c cnt %d", + bcs->hw.hscx.hscx ? 'B' : 'A', count); + QuickHex(t, ptr, count); + debugl1(cs, bcs->blog); + } +} + +static inline void +hscx_interrupt(struct IsdnCardState *cs, u_char val, u_char hscx) +{ + u_char r; + struct BCState *bcs = cs->bcs + hscx; + struct sk_buff *skb; + int fifo_size = test_bit(HW_IPAC, &cs->HW_Flags)? 64: 32; + int count; + + if (!test_bit(BC_FLG_INIT, &bcs->Flag)) + return; + + if (val & 0x80) { /* RME */ + r = READHSCX(cs, hscx, HSCX_RSTA); + if ((r & 0xf0) != 0xa0) { + if (!(r & 0x80)) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "HSCX invalid frame"); +#ifdef ERROR_STATISTIC + bcs->err_inv++; +#endif + } + if ((r & 0x40) && bcs->mode) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "HSCX RDO mode=%d", + bcs->mode); +#ifdef ERROR_STATISTIC + bcs->err_rdo++; +#endif + } + if (!(r & 0x20)) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "HSCX CRC error"); +#ifdef ERROR_STATISTIC + bcs->err_crc++; +#endif + } + WriteHSCXCMDR(cs, hscx, 0x80); + } else { + count = READHSCX(cs, hscx, HSCX_RBCL) & ( + test_bit(HW_IPAC, &cs->HW_Flags)? 0x3f: 0x1f); + if (count == 0) + count = fifo_size; + hscx_empty_fifo(bcs, count); + if ((count = bcs->hw.hscx.rcvidx - 1) > 0) { + if (cs->debug & L1_DEB_HSCX_FIFO) + debugl1(cs, "HX Frame %d", count); + if (!(skb = dev_alloc_skb(count))) + printk(KERN_WARNING "HSCX: receive out of memory\n"); + else { + memcpy(skb_put(skb, count), bcs->hw.hscx.rcvbuf, count); + skb_queue_tail(&bcs->rqueue, skb); + } + } + } + bcs->hw.hscx.rcvidx = 0; + schedule_event(bcs, B_RCVBUFREADY); + } + if (val & 0x40) { /* RPF */ + hscx_empty_fifo(bcs, fifo_size); + if (bcs->mode == L1_MODE_TRANS) { + /* receive audio data */ + if (!(skb = dev_alloc_skb(fifo_size))) + printk(KERN_WARNING "HiSax: receive out of memory\n"); + else { + memcpy(skb_put(skb, fifo_size), bcs->hw.hscx.rcvbuf, fifo_size); + skb_queue_tail(&bcs->rqueue, skb); + } + bcs->hw.hscx.rcvidx = 0; + schedule_event(bcs, B_RCVBUFREADY); + } + } + if (val & 0x10) { /* XPR */ + if (bcs->tx_skb) { + if (bcs->tx_skb->len) { + hscx_fill_fifo(bcs); + return; + } else { + if (test_bit(FLG_LLI_L1WAKEUP,&bcs->st->lli.flag) && + (PACKET_NOACK != bcs->tx_skb->pkt_type)) { + u_long flags; + spin_lock_irqsave(&bcs->aclock, flags); + bcs->ackcnt += bcs->hw.hscx.count; + spin_unlock_irqrestore(&bcs->aclock, flags); + schedule_event(bcs, B_ACKPENDING); + } + dev_kfree_skb_irq(bcs->tx_skb); + bcs->hw.hscx.count = 0; + bcs->tx_skb = NULL; + } + } + if ((bcs->tx_skb = skb_dequeue(&bcs->squeue))) { + bcs->hw.hscx.count = 0; + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + hscx_fill_fifo(bcs); + } else { + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + schedule_event(bcs, B_XMTBUFREADY); + } + } +} + +static inline void +hscx_int_main(struct IsdnCardState *cs, u_char val) +{ + + u_char exval; + struct BCState *bcs; + + if (val & 0x01) { + bcs = cs->bcs + 1; + exval = READHSCX(cs, 1, HSCX_EXIR); + if (exval & 0x40) { + if (bcs->mode == 1) + hscx_fill_fifo(bcs); + else { +#ifdef ERROR_STATISTIC + bcs->err_tx++; +#endif + /* Here we lost an TX interrupt, so + * restart transmitting the whole frame. + */ + if (bcs->tx_skb) { + skb_push(bcs->tx_skb, bcs->hw.hscx.count); + bcs->tx_cnt += bcs->hw.hscx.count; + bcs->hw.hscx.count = 0; + } + WriteHSCXCMDR(cs, bcs->hw.hscx.hscx, 0x01); + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "HSCX B EXIR %x Lost TX", exval); + } + } else if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HSCX B EXIR %x", exval); + } + if (val & 0xf8) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HSCX B interrupt %x", val); + hscx_interrupt(cs, val, 1); + } + if (val & 0x02) { + bcs = cs->bcs; + exval = READHSCX(cs, 0, HSCX_EXIR); + if (exval & 0x40) { + if (bcs->mode == L1_MODE_TRANS) + hscx_fill_fifo(bcs); + else { + /* Here we lost an TX interrupt, so + * restart transmitting the whole frame. + */ +#ifdef ERROR_STATISTIC + bcs->err_tx++; +#endif + if (bcs->tx_skb) { + skb_push(bcs->tx_skb, bcs->hw.hscx.count); + bcs->tx_cnt += bcs->hw.hscx.count; + bcs->hw.hscx.count = 0; + } + WriteHSCXCMDR(cs, bcs->hw.hscx.hscx, 0x01); + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "HSCX A EXIR %x Lost TX", exval); + } + } else if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HSCX A EXIR %x", exval); + } + if (val & 0x04) { + exval = READHSCX(cs, 0, HSCX_ISTA); + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HSCX A interrupt %x", exval); + hscx_interrupt(cs, exval, 0); + } +} diff --git a/drivers/isdn/hisax/icc.c b/drivers/isdn/hisax/icc.c new file mode 100644 index 000000000000..dcf31f83c600 --- /dev/null +++ b/drivers/isdn/hisax/icc.c @@ -0,0 +1,685 @@ +/* $Id: icc.c,v 1.8.2.3 2004/01/13 14:31:25 keil Exp $ + * + * ICC specific routines + * + * Author Matt Henderson & Guy Ellis + * Copyright by Traverse Technologies Pty Ltd, www.travers.com.au + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * 1999.6.25 Initial implementation of routines for Siemens ISDN + * Communication Controller PEB 2070 based on the ISAC routines + * written by Karsten Keil. + * + */ + +#include <linux/init.h> +#include "hisax.h" +#include "icc.h" +// #include "arcofi.h" +#include "isdnl1.h" +#include <linux/interrupt.h> + +#define DBUSY_TIMER_VALUE 80 +#define ARCOFI_USE 0 + +static char *ICCVer[] __initdata = +{"2070 A1/A3", "2070 B1", "2070 B2/B3", "2070 V2.4"}; + +void __init +ICCVersion(struct IsdnCardState *cs, char *s) +{ + int val; + + val = cs->readisac(cs, ICC_RBCH); + printk(KERN_INFO "%s ICC version (%x): %s\n", s, val, ICCVer[(val >> 5) & 3]); +} + +static void +ph_command(struct IsdnCardState *cs, unsigned int command) +{ + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ph_command %x", command); + cs->writeisac(cs, ICC_CIX0, (command << 2) | 3); +} + + +static void +icc_new_ph(struct IsdnCardState *cs) +{ + switch (cs->dc.icc.ph_state) { + case (ICC_IND_EI1): + ph_command(cs, ICC_CMD_DI); + l1_msg(cs, HW_RESET | INDICATION, NULL); + break; + case (ICC_IND_DC): + l1_msg(cs, HW_DEACTIVATE | CONFIRM, NULL); + break; + case (ICC_IND_DR): + l1_msg(cs, HW_DEACTIVATE | INDICATION, NULL); + break; + case (ICC_IND_PU): + l1_msg(cs, HW_POWERUP | CONFIRM, NULL); + break; + case (ICC_IND_FJ): + l1_msg(cs, HW_RSYNC | INDICATION, NULL); + break; + case (ICC_IND_AR): + l1_msg(cs, HW_INFO2 | INDICATION, NULL); + break; + case (ICC_IND_AI): + l1_msg(cs, HW_INFO4 | INDICATION, NULL); + break; + default: + break; + } +} + +static void +icc_bh(struct IsdnCardState *cs) +{ + struct PStack *stptr; + + if (!cs) + return; + if (test_and_clear_bit(D_CLEARBUSY, &cs->event)) { + if (cs->debug) + debugl1(cs, "D-Channel Busy cleared"); + stptr = cs->stlist; + while (stptr != NULL) { + stptr->l1.l1l2(stptr, PH_PAUSE | CONFIRM, NULL); + stptr = stptr->next; + } + } + if (test_and_clear_bit(D_L1STATECHANGE, &cs->event)) + icc_new_ph(cs); + if (test_and_clear_bit(D_RCVBUFREADY, &cs->event)) + DChannel_proc_rcv(cs); + if (test_and_clear_bit(D_XMTBUFREADY, &cs->event)) + DChannel_proc_xmt(cs); +#if ARCOFI_USE + if (!test_bit(HW_ARCOFI, &cs->HW_Flags)) + return; + if (test_and_clear_bit(D_RX_MON1, &cs->event)) + arcofi_fsm(cs, ARCOFI_RX_END, NULL); + if (test_and_clear_bit(D_TX_MON1, &cs->event)) + arcofi_fsm(cs, ARCOFI_TX_END, NULL); +#endif +} + +void +icc_empty_fifo(struct IsdnCardState *cs, int count) +{ + u_char *ptr; + + if ((cs->debug & L1_DEB_ISAC) && !(cs->debug & L1_DEB_ISAC_FIFO)) + debugl1(cs, "icc_empty_fifo"); + + if ((cs->rcvidx + count) >= MAX_DFRAME_LEN_L1) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "icc_empty_fifo overrun %d", + cs->rcvidx + count); + cs->writeisac(cs, ICC_CMDR, 0x80); + cs->rcvidx = 0; + return; + } + ptr = cs->rcvbuf + cs->rcvidx; + cs->rcvidx += count; + cs->readisacfifo(cs, ptr, count); + cs->writeisac(cs, ICC_CMDR, 0x80); + if (cs->debug & L1_DEB_ISAC_FIFO) { + char *t = cs->dlog; + + t += sprintf(t, "icc_empty_fifo cnt %d", count); + QuickHex(t, ptr, count); + debugl1(cs, cs->dlog); + } +} + +static void +icc_fill_fifo(struct IsdnCardState *cs) +{ + int count, more; + u_char *ptr; + + if ((cs->debug & L1_DEB_ISAC) && !(cs->debug & L1_DEB_ISAC_FIFO)) + debugl1(cs, "icc_fill_fifo"); + + if (!cs->tx_skb) + return; + + count = cs->tx_skb->len; + if (count <= 0) + return; + + more = 0; + if (count > 32) { + more = !0; + count = 32; + } + ptr = cs->tx_skb->data; + skb_pull(cs->tx_skb, count); + cs->tx_cnt += count; + cs->writeisacfifo(cs, ptr, count); + cs->writeisac(cs, ICC_CMDR, more ? 0x8 : 0xa); + if (test_and_set_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) { + debugl1(cs, "icc_fill_fifo dbusytimer running"); + del_timer(&cs->dbusytimer); + } + init_timer(&cs->dbusytimer); + cs->dbusytimer.expires = jiffies + ((DBUSY_TIMER_VALUE * HZ)/1000); + add_timer(&cs->dbusytimer); + if (cs->debug & L1_DEB_ISAC_FIFO) { + char *t = cs->dlog; + + t += sprintf(t, "icc_fill_fifo cnt %d", count); + QuickHex(t, ptr, count); + debugl1(cs, cs->dlog); + } +} + +void +icc_interrupt(struct IsdnCardState *cs, u_char val) +{ + u_char exval, v1; + struct sk_buff *skb; + unsigned int count; + + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ICC interrupt %x", val); + if (val & 0x80) { /* RME */ + exval = cs->readisac(cs, ICC_RSTA); + if ((exval & 0x70) != 0x20) { + if (exval & 0x40) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "ICC RDO"); +#ifdef ERROR_STATISTIC + cs->err_rx++; +#endif + } + if (!(exval & 0x20)) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "ICC CRC error"); +#ifdef ERROR_STATISTIC + cs->err_crc++; +#endif + } + cs->writeisac(cs, ICC_CMDR, 0x80); + } else { + count = cs->readisac(cs, ICC_RBCL) & 0x1f; + if (count == 0) + count = 32; + icc_empty_fifo(cs, count); + if ((count = cs->rcvidx) > 0) { + cs->rcvidx = 0; + if (!(skb = alloc_skb(count, GFP_ATOMIC))) + printk(KERN_WARNING "HiSax: D receive out of memory\n"); + else { + memcpy(skb_put(skb, count), cs->rcvbuf, count); + skb_queue_tail(&cs->rq, skb); + } + } + } + cs->rcvidx = 0; + schedule_event(cs, D_RCVBUFREADY); + } + if (val & 0x40) { /* RPF */ + icc_empty_fifo(cs, 32); + } + if (val & 0x20) { /* RSC */ + /* never */ + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "ICC RSC interrupt"); + } + if (val & 0x10) { /* XPR */ + if (test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) + del_timer(&cs->dbusytimer); + if (test_and_clear_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + schedule_event(cs, D_CLEARBUSY); + if (cs->tx_skb) { + if (cs->tx_skb->len) { + icc_fill_fifo(cs); + goto afterXPR; + } else { + dev_kfree_skb_irq(cs->tx_skb); + cs->tx_cnt = 0; + cs->tx_skb = NULL; + } + } + if ((cs->tx_skb = skb_dequeue(&cs->sq))) { + cs->tx_cnt = 0; + icc_fill_fifo(cs); + } else + schedule_event(cs, D_XMTBUFREADY); + } + afterXPR: + if (val & 0x04) { /* CISQ */ + exval = cs->readisac(cs, ICC_CIR0); + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ICC CIR0 %02X", exval ); + if (exval & 2) { + cs->dc.icc.ph_state = (exval >> 2) & 0xf; + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ph_state change %x", cs->dc.icc.ph_state); + schedule_event(cs, D_L1STATECHANGE); + } + if (exval & 1) { + exval = cs->readisac(cs, ICC_CIR1); + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ICC CIR1 %02X", exval ); + } + } + if (val & 0x02) { /* SIN */ + /* never */ + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "ICC SIN interrupt"); + } + if (val & 0x01) { /* EXI */ + exval = cs->readisac(cs, ICC_EXIR); + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "ICC EXIR %02x", exval); + if (exval & 0x80) { /* XMR */ + debugl1(cs, "ICC XMR"); + printk(KERN_WARNING "HiSax: ICC XMR\n"); + } + if (exval & 0x40) { /* XDU */ + debugl1(cs, "ICC XDU"); + printk(KERN_WARNING "HiSax: ICC XDU\n"); +#ifdef ERROR_STATISTIC + cs->err_tx++; +#endif + if (test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) + del_timer(&cs->dbusytimer); + if (test_and_clear_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + schedule_event(cs, D_CLEARBUSY); + if (cs->tx_skb) { /* Restart frame */ + skb_push(cs->tx_skb, cs->tx_cnt); + cs->tx_cnt = 0; + icc_fill_fifo(cs); + } else { + printk(KERN_WARNING "HiSax: ICC XDU no skb\n"); + debugl1(cs, "ICC XDU no skb"); + } + } + if (exval & 0x04) { /* MOS */ + v1 = cs->readisac(cs, ICC_MOSR); + if (cs->debug & L1_DEB_MONITOR) + debugl1(cs, "ICC MOSR %02x", v1); +#if ARCOFI_USE + if (v1 & 0x08) { + if (!cs->dc.icc.mon_rx) { + if (!(cs->dc.icc.mon_rx = kmalloc(MAX_MON_FRAME, GFP_ATOMIC))) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "ICC MON RX out of memory!"); + cs->dc.icc.mocr &= 0xf0; + cs->dc.icc.mocr |= 0x0a; + cs->writeisac(cs, ICC_MOCR, cs->dc.icc.mocr); + goto afterMONR0; + } else + cs->dc.icc.mon_rxp = 0; + } + if (cs->dc.icc.mon_rxp >= MAX_MON_FRAME) { + cs->dc.icc.mocr &= 0xf0; + cs->dc.icc.mocr |= 0x0a; + cs->writeisac(cs, ICC_MOCR, cs->dc.icc.mocr); + cs->dc.icc.mon_rxp = 0; + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "ICC MON RX overflow!"); + goto afterMONR0; + } + cs->dc.icc.mon_rx[cs->dc.icc.mon_rxp++] = cs->readisac(cs, ICC_MOR0); + if (cs->debug & L1_DEB_MONITOR) + debugl1(cs, "ICC MOR0 %02x", cs->dc.icc.mon_rx[cs->dc.icc.mon_rxp -1]); + if (cs->dc.icc.mon_rxp == 1) { + cs->dc.icc.mocr |= 0x04; + cs->writeisac(cs, ICC_MOCR, cs->dc.icc.mocr); + } + } + afterMONR0: + if (v1 & 0x80) { + if (!cs->dc.icc.mon_rx) { + if (!(cs->dc.icc.mon_rx = kmalloc(MAX_MON_FRAME, GFP_ATOMIC))) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "ICC MON RX out of memory!"); + cs->dc.icc.mocr &= 0x0f; + cs->dc.icc.mocr |= 0xa0; + cs->writeisac(cs, ICC_MOCR, cs->dc.icc.mocr); + goto afterMONR1; + } else + cs->dc.icc.mon_rxp = 0; + } + if (cs->dc.icc.mon_rxp >= MAX_MON_FRAME) { + cs->dc.icc.mocr &= 0x0f; + cs->dc.icc.mocr |= 0xa0; + cs->writeisac(cs, ICC_MOCR, cs->dc.icc.mocr); + cs->dc.icc.mon_rxp = 0; + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "ICC MON RX overflow!"); + goto afterMONR1; + } + cs->dc.icc.mon_rx[cs->dc.icc.mon_rxp++] = cs->readisac(cs, ICC_MOR1); + if (cs->debug & L1_DEB_MONITOR) + debugl1(cs, "ICC MOR1 %02x", cs->dc.icc.mon_rx[cs->dc.icc.mon_rxp -1]); + cs->dc.icc.mocr |= 0x40; + cs->writeisac(cs, ICC_MOCR, cs->dc.icc.mocr); + } + afterMONR1: + if (v1 & 0x04) { + cs->dc.icc.mocr &= 0xf0; + cs->writeisac(cs, ICC_MOCR, cs->dc.icc.mocr); + cs->dc.icc.mocr |= 0x0a; + cs->writeisac(cs, ICC_MOCR, cs->dc.icc.mocr); + schedule_event(cs, D_RX_MON0); + } + if (v1 & 0x40) { + cs->dc.icc.mocr &= 0x0f; + cs->writeisac(cs, ICC_MOCR, cs->dc.icc.mocr); + cs->dc.icc.mocr |= 0xa0; + cs->writeisac(cs, ICC_MOCR, cs->dc.icc.mocr); + schedule_event(cs, D_RX_MON1); + } + if (v1 & 0x02) { + if ((!cs->dc.icc.mon_tx) || (cs->dc.icc.mon_txc && + (cs->dc.icc.mon_txp >= cs->dc.icc.mon_txc) && + !(v1 & 0x08))) { + cs->dc.icc.mocr &= 0xf0; + cs->writeisac(cs, ICC_MOCR, cs->dc.icc.mocr); + cs->dc.icc.mocr |= 0x0a; + cs->writeisac(cs, ICC_MOCR, cs->dc.icc.mocr); + if (cs->dc.icc.mon_txc && + (cs->dc.icc.mon_txp >= cs->dc.icc.mon_txc)) + schedule_event(cs, D_TX_MON0); + goto AfterMOX0; + } + if (cs->dc.icc.mon_txc && (cs->dc.icc.mon_txp >= cs->dc.icc.mon_txc)) { + schedule_event(cs, D_TX_MON0); + goto AfterMOX0; + } + cs->writeisac(cs, ICC_MOX0, + cs->dc.icc.mon_tx[cs->dc.icc.mon_txp++]); + if (cs->debug & L1_DEB_MONITOR) + debugl1(cs, "ICC %02x -> MOX0", cs->dc.icc.mon_tx[cs->dc.icc.mon_txp -1]); + } + AfterMOX0: + if (v1 & 0x20) { + if ((!cs->dc.icc.mon_tx) || (cs->dc.icc.mon_txc && + (cs->dc.icc.mon_txp >= cs->dc.icc.mon_txc) && + !(v1 & 0x80))) { + cs->dc.icc.mocr &= 0x0f; + cs->writeisac(cs, ICC_MOCR, cs->dc.icc.mocr); + cs->dc.icc.mocr |= 0xa0; + cs->writeisac(cs, ICC_MOCR, cs->dc.icc.mocr); + if (cs->dc.icc.mon_txc && + (cs->dc.icc.mon_txp >= cs->dc.icc.mon_txc)) + schedule_event(cs, D_TX_MON1); + goto AfterMOX1; + } + if (cs->dc.icc.mon_txc && (cs->dc.icc.mon_txp >= cs->dc.icc.mon_txc)) { + schedule_event(cs, D_TX_MON1); + goto AfterMOX1; + } + cs->writeisac(cs, ICC_MOX1, + cs->dc.icc.mon_tx[cs->dc.icc.mon_txp++]); + if (cs->debug & L1_DEB_MONITOR) + debugl1(cs, "ICC %02x -> MOX1", cs->dc.icc.mon_tx[cs->dc.icc.mon_txp -1]); + } + AfterMOX1: +#endif + } + } +} + +static void +ICC_l1hw(struct PStack *st, int pr, void *arg) +{ + struct IsdnCardState *cs = (struct IsdnCardState *) st->l1.hardware; + struct sk_buff *skb = arg; + u_long flags; + int val; + + switch (pr) { + case (PH_DATA |REQUEST): + if (cs->debug & DEB_DLOG_HEX) + LogFrame(cs, skb->data, skb->len); + if (cs->debug & DEB_DLOG_VERBOSE) + dlogframe(cs, skb, 0); + spin_lock_irqsave(&cs->lock, flags); + if (cs->tx_skb) { + skb_queue_tail(&cs->sq, skb); +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "PH_DATA Queued", 0); +#endif + } else { + cs->tx_skb = skb; + cs->tx_cnt = 0; +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "PH_DATA", 0); +#endif + icc_fill_fifo(cs); + } + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (PH_PULL |INDICATION): + spin_lock_irqsave(&cs->lock, flags); + if (cs->tx_skb) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, " l2l1 tx_skb exist this shouldn't happen"); + skb_queue_tail(&cs->sq, skb); + break; + } + if (cs->debug & DEB_DLOG_HEX) + LogFrame(cs, skb->data, skb->len); + if (cs->debug & DEB_DLOG_VERBOSE) + dlogframe(cs, skb, 0); + cs->tx_skb = skb; + cs->tx_cnt = 0; +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "PH_DATA_PULLED", 0); +#endif + icc_fill_fifo(cs); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (PH_PULL | REQUEST): +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + debugl1(cs, "-> PH_REQUEST_PULL"); +#endif + if (!cs->tx_skb) { + test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } else + test_and_set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + case (HW_RESET | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + if ((cs->dc.icc.ph_state == ICC_IND_EI1) || + (cs->dc.icc.ph_state == ICC_IND_DR)) + ph_command(cs, ICC_CMD_DI); + else + ph_command(cs, ICC_CMD_RES); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (HW_ENABLE | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + ph_command(cs, ICC_CMD_DI); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (HW_INFO1 | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + ph_command(cs, ICC_CMD_AR); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (HW_INFO3 | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + ph_command(cs, ICC_CMD_AI); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (HW_TESTLOOP | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + val = 0; + if (1 & (long) arg) + val |= 0x0c; + if (2 & (long) arg) + val |= 0x3; + if (test_bit(HW_IOM1, &cs->HW_Flags)) { + /* IOM 1 Mode */ + if (!val) { + cs->writeisac(cs, ICC_SPCR, 0xa); + cs->writeisac(cs, ICC_ADF1, 0x2); + } else { + cs->writeisac(cs, ICC_SPCR, val); + cs->writeisac(cs, ICC_ADF1, 0xa); + } + } else { + /* IOM 2 Mode */ + cs->writeisac(cs, ICC_SPCR, val); + if (val) + cs->writeisac(cs, ICC_ADF1, 0x8); + else + cs->writeisac(cs, ICC_ADF1, 0x0); + } + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (HW_DEACTIVATE | RESPONSE): + skb_queue_purge(&cs->rq); + skb_queue_purge(&cs->sq); + if (cs->tx_skb) { + dev_kfree_skb_any(cs->tx_skb); + cs->tx_skb = NULL; + } + if (test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) + del_timer(&cs->dbusytimer); + if (test_and_clear_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + schedule_event(cs, D_CLEARBUSY); + break; + default: + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "icc_l1hw unknown %04x", pr); + break; + } +} + +void +setstack_icc(struct PStack *st, struct IsdnCardState *cs) +{ + st->l1.l1hw = ICC_l1hw; +} + +void +DC_Close_icc(struct IsdnCardState *cs) { + if (cs->dc.icc.mon_rx) { + kfree(cs->dc.icc.mon_rx); + cs->dc.icc.mon_rx = NULL; + } + if (cs->dc.icc.mon_tx) { + kfree(cs->dc.icc.mon_tx); + cs->dc.icc.mon_tx = NULL; + } +} + +static void +dbusy_timer_handler(struct IsdnCardState *cs) +{ + struct PStack *stptr; + int rbch, star; + + if (test_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) { + rbch = cs->readisac(cs, ICC_RBCH); + star = cs->readisac(cs, ICC_STAR); + if (cs->debug) + debugl1(cs, "D-Channel Busy RBCH %02x STAR %02x", + rbch, star); + if (rbch & ICC_RBCH_XAC) { /* D-Channel Busy */ + test_and_set_bit(FLG_L1_DBUSY, &cs->HW_Flags); + stptr = cs->stlist; + while (stptr != NULL) { + stptr->l1.l1l2(stptr, PH_PAUSE | INDICATION, NULL); + stptr = stptr->next; + } + } else { + /* discard frame; reset transceiver */ + test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags); + if (cs->tx_skb) { + dev_kfree_skb_any(cs->tx_skb); + cs->tx_cnt = 0; + cs->tx_skb = NULL; + } else { + printk(KERN_WARNING "HiSax: ICC D-Channel Busy no skb\n"); + debugl1(cs, "D-Channel Busy no skb"); + } + cs->writeisac(cs, ICC_CMDR, 0x01); /* Transmitter reset */ + cs->irq_func(cs->irq, cs, NULL); + } + } +} + +void __init +initicc(struct IsdnCardState *cs) +{ + cs->setstack_d = setstack_icc; + cs->DC_Close = DC_Close_icc; + cs->dc.icc.mon_tx = NULL; + cs->dc.icc.mon_rx = NULL; + cs->writeisac(cs, ICC_MASK, 0xff); + cs->dc.icc.mocr = 0xaa; + if (test_bit(HW_IOM1, &cs->HW_Flags)) { + /* IOM 1 Mode */ + cs->writeisac(cs, ICC_ADF2, 0x0); + cs->writeisac(cs, ICC_SPCR, 0xa); + cs->writeisac(cs, ICC_ADF1, 0x2); + cs->writeisac(cs, ICC_STCR, 0x70); + cs->writeisac(cs, ICC_MODE, 0xc9); + } else { + /* IOM 2 Mode */ + if (!cs->dc.icc.adf2) + cs->dc.icc.adf2 = 0x80; + cs->writeisac(cs, ICC_ADF2, cs->dc.icc.adf2); + cs->writeisac(cs, ICC_SQXR, 0xa0); + cs->writeisac(cs, ICC_SPCR, 0x20); + cs->writeisac(cs, ICC_STCR, 0x70); + cs->writeisac(cs, ICC_MODE, 0xca); + cs->writeisac(cs, ICC_TIMR, 0x00); + cs->writeisac(cs, ICC_ADF1, 0x20); + } + ph_command(cs, ICC_CMD_RES); + cs->writeisac(cs, ICC_MASK, 0x0); + ph_command(cs, ICC_CMD_DI); +} + +void __init +clear_pending_icc_ints(struct IsdnCardState *cs) +{ + int val, eval; + + val = cs->readisac(cs, ICC_STAR); + debugl1(cs, "ICC STAR %x", val); + val = cs->readisac(cs, ICC_MODE); + debugl1(cs, "ICC MODE %x", val); + val = cs->readisac(cs, ICC_ADF2); + debugl1(cs, "ICC ADF2 %x", val); + val = cs->readisac(cs, ICC_ISTA); + debugl1(cs, "ICC ISTA %x", val); + if (val & 0x01) { + eval = cs->readisac(cs, ICC_EXIR); + debugl1(cs, "ICC EXIR %x", eval); + } + val = cs->readisac(cs, ICC_CIR0); + debugl1(cs, "ICC CIR0 %x", val); + cs->dc.icc.ph_state = (val >> 2) & 0xf; + schedule_event(cs, D_L1STATECHANGE); + /* Disable all IRQ */ + cs->writeisac(cs, ICC_MASK, 0xFF); +} + +void __devinit +setup_icc(struct IsdnCardState *cs) +{ + INIT_WORK(&cs->tqueue, (void *)(void *) icc_bh, cs); + cs->dbusytimer.function = (void *) dbusy_timer_handler; + cs->dbusytimer.data = (long) cs; + init_timer(&cs->dbusytimer); +} diff --git a/drivers/isdn/hisax/icc.h b/drivers/isdn/hisax/icc.h new file mode 100644 index 000000000000..b3bb3d5de532 --- /dev/null +++ b/drivers/isdn/hisax/icc.h @@ -0,0 +1,72 @@ +/* $Id: icc.h,v 1.4.2.2 2004/01/12 22:52:26 keil Exp $ + * + * ICC specific routines + * + * Author Matt Henderson & Guy Ellis + * Copyright by Traverse Technologies Pty Ltd, www.travers.com.au + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * 1999.7.14 Initial implementation of routines for Siemens ISDN + * Communication Controller PEB 2070 based on the ISAC routines + * written by Karsten Keil. + */ + +/* All Registers original Siemens Spec */ + +#define ICC_MASK 0x20 +#define ICC_ISTA 0x20 +#define ICC_STAR 0x21 +#define ICC_CMDR 0x21 +#define ICC_EXIR 0x24 +#define ICC_ADF2 0x39 +#define ICC_SPCR 0x30 +#define ICC_ADF1 0x38 +#define ICC_CIR0 0x31 +#define ICC_CIX0 0x31 +#define ICC_CIR1 0x33 +#define ICC_CIX1 0x33 +#define ICC_STCR 0x37 +#define ICC_MODE 0x22 +#define ICC_RSTA 0x27 +#define ICC_RBCL 0x25 +#define ICC_RBCH 0x2A +#define ICC_TIMR 0x23 +#define ICC_SQXR 0x3b +#define ICC_MOSR 0x3a +#define ICC_MOCR 0x3a +#define ICC_MOR0 0x32 +#define ICC_MOX0 0x32 +#define ICC_MOR1 0x34 +#define ICC_MOX1 0x34 + +#define ICC_RBCH_XAC 0x80 + +#define ICC_CMD_TIM 0x0 +#define ICC_CMD_RES 0x1 +#define ICC_CMD_DU 0x3 +#define ICC_CMD_EI1 0x4 +#define ICC_CMD_SSP 0x5 +#define ICC_CMD_DT 0x6 +#define ICC_CMD_AR 0x8 +#define ICC_CMD_ARL 0xA +#define ICC_CMD_AI 0xC +#define ICC_CMD_DI 0xF + +#define ICC_IND_DR 0x0 +#define ICC_IND_FJ 0x2 +#define ICC_IND_EI1 0x4 +#define ICC_IND_INT 0x6 +#define ICC_IND_PU 0x7 +#define ICC_IND_AR 0x8 +#define ICC_IND_ARL 0xA +#define ICC_IND_AI 0xC +#define ICC_IND_AIL 0xE +#define ICC_IND_DC 0xF + +extern void __init ICCVersion(struct IsdnCardState *cs, char *s); +extern void initicc(struct IsdnCardState *cs); +extern void icc_interrupt(struct IsdnCardState *cs, u_char val); +extern void clear_pending_icc_ints(struct IsdnCardState *cs); +extern void setup_icc(struct IsdnCardState *); diff --git a/drivers/isdn/hisax/ipac.h b/drivers/isdn/hisax/ipac.h new file mode 100644 index 000000000000..f92a04a92826 --- /dev/null +++ b/drivers/isdn/hisax/ipac.h @@ -0,0 +1,29 @@ +/* $Id: ipac.h,v 1.7.2.2 2004/01/12 22:52:26 keil Exp $ + * + * IPAC specific defines + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +/* All Registers original Siemens Spec */ + +#define IPAC_CONF 0xC0 +#define IPAC_MASK 0xC1 +#define IPAC_ISTA 0xC1 +#define IPAC_ID 0xC2 +#define IPAC_ACFG 0xC3 +#define IPAC_AOE 0xC4 +#define IPAC_ARX 0xC5 +#define IPAC_ATX 0xC5 +#define IPAC_PITA1 0xC6 +#define IPAC_PITA2 0xC7 +#define IPAC_POTA1 0xC8 +#define IPAC_POTA2 0xC9 +#define IPAC_PCFG 0xCA +#define IPAC_SCFG 0xCB +#define IPAC_TIMR2 0xCC diff --git a/drivers/isdn/hisax/ipacx.c b/drivers/isdn/hisax/ipacx.c new file mode 100644 index 000000000000..6485e232d869 --- /dev/null +++ b/drivers/isdn/hisax/ipacx.c @@ -0,0 +1,1004 @@ +/* + * + * IPACX specific routines + * + * Author Joerg Petersohn + * Derived from hisax_isac.c, isac.c, hscx.c and others + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ +#include <linux/kernel.h> +#include <linux/config.h> +#include <linux/init.h> +#include "hisax_if.h" +#include "hisax.h" +#include "isdnl1.h" +#include "ipacx.h" + +#define DBUSY_TIMER_VALUE 80 +#define TIMER3_VALUE 7000 +#define MAX_DFRAME_LEN_L1 300 +#define B_FIFO_SIZE 64 +#define D_FIFO_SIZE 32 + + +// ipacx interrupt mask values +#define _MASK_IMASK 0x2E // global mask +#define _MASKB_IMASK 0x0B +#define _MASKD_IMASK 0x03 // all on + +//---------------------------------------------------------- +// local function declarations +//---------------------------------------------------------- +static void ph_command(struct IsdnCardState *cs, unsigned int command); +static inline void cic_int(struct IsdnCardState *cs); +static void dch_l2l1(struct PStack *st, int pr, void *arg); +static void dbusy_timer_handler(struct IsdnCardState *cs); +static void ipacx_new_ph(struct IsdnCardState *cs); +static void dch_bh(struct IsdnCardState *cs); +static void dch_empty_fifo(struct IsdnCardState *cs, int count); +static void dch_fill_fifo(struct IsdnCardState *cs); +static inline void dch_int(struct IsdnCardState *cs); +static void __devinit dch_setstack(struct PStack *st, struct IsdnCardState *cs); +static void __devinit dch_init(struct IsdnCardState *cs); +static void bch_l2l1(struct PStack *st, int pr, void *arg); +static void bch_empty_fifo(struct BCState *bcs, int count); +static void bch_fill_fifo(struct BCState *bcs); +static void bch_int(struct IsdnCardState *cs, u_char hscx); +static void bch_mode(struct BCState *bcs, int mode, int bc); +static void bch_close_state(struct BCState *bcs); +static int bch_open_state(struct IsdnCardState *cs, struct BCState *bcs); +static int bch_setstack(struct PStack *st, struct BCState *bcs); +static void __devinit bch_init(struct IsdnCardState *cs, int hscx); +static void __init clear_pending_ints(struct IsdnCardState *cs); + +//---------------------------------------------------------- +// Issue Layer 1 command to chip +//---------------------------------------------------------- +static void +ph_command(struct IsdnCardState *cs, unsigned int command) +{ + if (cs->debug &L1_DEB_ISAC) + debugl1(cs, "ph_command (%#x) in (%#x)", command, + cs->dc.isac.ph_state); +//################################### +// printk(KERN_INFO "ph_command (%#x)\n", command); +//################################### + cs->writeisac(cs, IPACX_CIX0, (command << 4) | 0x0E); +} + +//---------------------------------------------------------- +// Transceiver interrupt handler +//---------------------------------------------------------- +static inline void +cic_int(struct IsdnCardState *cs) +{ + u_char event; + + event = cs->readisac(cs, IPACX_CIR0) >> 4; + if (cs->debug &L1_DEB_ISAC) debugl1(cs, "cic_int(event=%#x)", event); +//######################################### +// printk(KERN_INFO "cic_int(%x)\n", event); +//######################################### + cs->dc.isac.ph_state = event; + schedule_event(cs, D_L1STATECHANGE); +} + +//========================================================== +// D channel functions +//========================================================== + +//---------------------------------------------------------- +// Command entry point +//---------------------------------------------------------- +static void +dch_l2l1(struct PStack *st, int pr, void *arg) +{ + struct IsdnCardState *cs = (struct IsdnCardState *) st->l1.hardware; + struct sk_buff *skb = arg; + u_char cda1_cr, cda2_cr; + + switch (pr) { + case (PH_DATA |REQUEST): + if (cs->debug &DEB_DLOG_HEX) LogFrame(cs, skb->data, skb->len); + if (cs->debug &DEB_DLOG_VERBOSE) dlogframe(cs, skb, 0); + if (cs->tx_skb) { + skb_queue_tail(&cs->sq, skb); +#ifdef L2FRAME_DEBUG + if (cs->debug &L1_DEB_LAPD) Logl2Frame(cs, skb, "PH_DATA Queued", 0); +#endif + } else { + cs->tx_skb = skb; + cs->tx_cnt = 0; +#ifdef L2FRAME_DEBUG + if (cs->debug &L1_DEB_LAPD) Logl2Frame(cs, skb, "PH_DATA", 0); +#endif + dch_fill_fifo(cs); + } + break; + + case (PH_PULL |INDICATION): + if (cs->tx_skb) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, " l2l1 tx_skb exist this shouldn't happen"); + skb_queue_tail(&cs->sq, skb); + break; + } + if (cs->debug & DEB_DLOG_HEX) LogFrame(cs, skb->data, skb->len); + if (cs->debug & DEB_DLOG_VERBOSE) dlogframe(cs, skb, 0); + cs->tx_skb = skb; + cs->tx_cnt = 0; +#ifdef L2FRAME_DEBUG + if (cs->debug & L1_DEB_LAPD) Logl2Frame(cs, skb, "PH_DATA_PULLED", 0); +#endif + dch_fill_fifo(cs); + break; + + case (PH_PULL | REQUEST): +#ifdef L2FRAME_DEBUG + if (cs->debug & L1_DEB_LAPD) debugl1(cs, "-> PH_REQUEST_PULL"); +#endif + if (!cs->tx_skb) { + clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } else + set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + + case (HW_RESET | REQUEST): + case (HW_ENABLE | REQUEST): + if ((cs->dc.isac.ph_state == IPACX_IND_RES) || + (cs->dc.isac.ph_state == IPACX_IND_DR) || + (cs->dc.isac.ph_state == IPACX_IND_DC)) + ph_command(cs, IPACX_CMD_TIM); + else + ph_command(cs, IPACX_CMD_RES); + break; + + case (HW_INFO3 | REQUEST): + ph_command(cs, IPACX_CMD_AR8); + break; + + case (HW_TESTLOOP | REQUEST): + cs->writeisac(cs, IPACX_CDA_TSDP10, 0x80); // Timeslot 0 is B1 + cs->writeisac(cs, IPACX_CDA_TSDP11, 0x81); // Timeslot 0 is B1 + cda1_cr = cs->readisac(cs, IPACX_CDA1_CR); + cda2_cr = cs->readisac(cs, IPACX_CDA2_CR); + if ((long)arg &1) { // loop B1 + cs->writeisac(cs, IPACX_CDA1_CR, cda1_cr |0x0a); + } + else { // B1 off + cs->writeisac(cs, IPACX_CDA1_CR, cda1_cr &~0x0a); + } + if ((long)arg &2) { // loop B2 + cs->writeisac(cs, IPACX_CDA1_CR, cda1_cr |0x14); + } + else { // B2 off + cs->writeisac(cs, IPACX_CDA1_CR, cda1_cr &~0x14); + } + break; + + case (HW_DEACTIVATE | RESPONSE): + skb_queue_purge(&cs->rq); + skb_queue_purge(&cs->sq); + if (cs->tx_skb) { + dev_kfree_skb_any(cs->tx_skb); + cs->tx_skb = NULL; + } + if (test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) + del_timer(&cs->dbusytimer); + break; + + default: + if (cs->debug &L1_DEB_WARN) debugl1(cs, "dch_l2l1 unknown %04x", pr); + break; + } +} + +//---------------------------------------------------------- +//---------------------------------------------------------- +static void +dbusy_timer_handler(struct IsdnCardState *cs) +{ + struct PStack *st; + int rbchd, stard; + + if (test_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) { + rbchd = cs->readisac(cs, IPACX_RBCHD); + stard = cs->readisac(cs, IPACX_STARD); + if (cs->debug) + debugl1(cs, "D-Channel Busy RBCHD %02x STARD %02x", rbchd, stard); + if (!(stard &0x40)) { // D-Channel Busy + set_bit(FLG_L1_DBUSY, &cs->HW_Flags); + for (st = cs->stlist; st; st = st->next) { + st->l1.l1l2(st, PH_PAUSE | INDICATION, NULL); // flow control on + } + } else { + // seems we lost an interrupt; reset transceiver */ + clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags); + if (cs->tx_skb) { + dev_kfree_skb_any(cs->tx_skb); + cs->tx_cnt = 0; + cs->tx_skb = NULL; + } else { + printk(KERN_WARNING "HiSax: ISAC D-Channel Busy no skb\n"); + debugl1(cs, "D-Channel Busy no skb"); + } + cs->writeisac(cs, IPACX_CMDRD, 0x01); // Tx reset, generates XPR + } + } +} + +//---------------------------------------------------------- +// L1 state machine intermediate layer to isdnl1 module +//---------------------------------------------------------- +static void +ipacx_new_ph(struct IsdnCardState *cs) +{ + switch (cs->dc.isac.ph_state) { + case (IPACX_IND_RES): + ph_command(cs, IPACX_CMD_DI); + l1_msg(cs, HW_RESET | INDICATION, NULL); + break; + + case (IPACX_IND_DC): + l1_msg(cs, HW_DEACTIVATE | CONFIRM, NULL); + break; + + case (IPACX_IND_DR): + l1_msg(cs, HW_DEACTIVATE | INDICATION, NULL); + break; + + case (IPACX_IND_PU): + l1_msg(cs, HW_POWERUP | CONFIRM, NULL); + break; + + case (IPACX_IND_RSY): + l1_msg(cs, HW_RSYNC | INDICATION, NULL); + break; + + case (IPACX_IND_AR): + l1_msg(cs, HW_INFO2 | INDICATION, NULL); + break; + + case (IPACX_IND_AI8): + l1_msg(cs, HW_INFO4_P8 | INDICATION, NULL); + break; + + case (IPACX_IND_AI10): + l1_msg(cs, HW_INFO4_P10 | INDICATION, NULL); + break; + + default: + break; + } +} + +//---------------------------------------------------------- +// bottom half handler for D channel +//---------------------------------------------------------- +static void +dch_bh(struct IsdnCardState *cs) +{ + struct PStack *st; + + if (!cs) return; + + if (test_and_clear_bit(D_CLEARBUSY, &cs->event)) { + if (cs->debug) debugl1(cs, "D-Channel Busy cleared"); + for (st = cs->stlist; st; st = st->next) { + st->l1.l1l2(st, PH_PAUSE | CONFIRM, NULL); + } + } + + if (test_and_clear_bit(D_RCVBUFREADY, &cs->event)) { + DChannel_proc_rcv(cs); + } + + if (test_and_clear_bit(D_XMTBUFREADY, &cs->event)) { + DChannel_proc_xmt(cs); + } + + if (test_and_clear_bit(D_L1STATECHANGE, &cs->event)) { + ipacx_new_ph(cs); + } +} + +//---------------------------------------------------------- +// Fill buffer from receive FIFO +//---------------------------------------------------------- +static void +dch_empty_fifo(struct IsdnCardState *cs, int count) +{ + u_char *ptr; + + if ((cs->debug &L1_DEB_ISAC) && !(cs->debug &L1_DEB_ISAC_FIFO)) + debugl1(cs, "dch_empty_fifo()"); + + // message too large, remove + if ((cs->rcvidx + count) >= MAX_DFRAME_LEN_L1) { + if (cs->debug &L1_DEB_WARN) + debugl1(cs, "dch_empty_fifo() incoming message too large"); + cs->writeisac(cs, IPACX_CMDRD, 0x80); // RMC + cs->rcvidx = 0; + return; + } + + ptr = cs->rcvbuf + cs->rcvidx; + cs->rcvidx += count; + + cs->readisacfifo(cs, ptr, count); + cs->writeisac(cs, IPACX_CMDRD, 0x80); // RMC + + if (cs->debug &L1_DEB_ISAC_FIFO) { + char *t = cs->dlog; + + t += sprintf(t, "dch_empty_fifo() cnt %d", count); + QuickHex(t, ptr, count); + debugl1(cs, cs->dlog); + } +} + +//---------------------------------------------------------- +// Fill transmit FIFO +//---------------------------------------------------------- +static void +dch_fill_fifo(struct IsdnCardState *cs) +{ + int count; + u_char cmd, *ptr; + + if ((cs->debug &L1_DEB_ISAC) && !(cs->debug &L1_DEB_ISAC_FIFO)) + debugl1(cs, "dch_fill_fifo()"); + + if (!cs->tx_skb) return; + count = cs->tx_skb->len; + if (count <= 0) return; + + if (count > D_FIFO_SIZE) { + count = D_FIFO_SIZE; + cmd = 0x08; // XTF + } else { + cmd = 0x0A; // XTF | XME + } + + ptr = cs->tx_skb->data; + skb_pull(cs->tx_skb, count); + cs->tx_cnt += count; + cs->writeisacfifo(cs, ptr, count); + cs->writeisac(cs, IPACX_CMDRD, cmd); + + // set timeout for transmission contol + if (test_and_set_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) { + debugl1(cs, "dch_fill_fifo dbusytimer running"); + del_timer(&cs->dbusytimer); + } + init_timer(&cs->dbusytimer); + cs->dbusytimer.expires = jiffies + ((DBUSY_TIMER_VALUE * HZ)/1000); + add_timer(&cs->dbusytimer); + + if (cs->debug &L1_DEB_ISAC_FIFO) { + char *t = cs->dlog; + + t += sprintf(t, "dch_fill_fifo() cnt %d", count); + QuickHex(t, ptr, count); + debugl1(cs, cs->dlog); + } +} + +//---------------------------------------------------------- +// D channel interrupt handler +//---------------------------------------------------------- +static inline void +dch_int(struct IsdnCardState *cs) +{ + struct sk_buff *skb; + u_char istad, rstad; + int count; + + istad = cs->readisac(cs, IPACX_ISTAD); +//############################################## +// printk(KERN_WARNING "dch_int(istad=%02x)\n", istad); +//############################################## + + if (istad &0x80) { // RME + rstad = cs->readisac(cs, IPACX_RSTAD); + if ((rstad &0xf0) != 0xa0) { // !(VFR && !RDO && CRC && !RAB) + if (!(rstad &0x80)) + if (cs->debug &L1_DEB_WARN) + debugl1(cs, "dch_int(): invalid frame"); + if ((rstad &0x40)) + if (cs->debug &L1_DEB_WARN) + debugl1(cs, "dch_int(): RDO"); + if (!(rstad &0x20)) + if (cs->debug &L1_DEB_WARN) + debugl1(cs, "dch_int(): CRC error"); + cs->writeisac(cs, IPACX_CMDRD, 0x80); // RMC + } else { // received frame ok + count = cs->readisac(cs, IPACX_RBCLD); + if (count) count--; // RSTAB is last byte + count &= D_FIFO_SIZE-1; + if (count == 0) count = D_FIFO_SIZE; + dch_empty_fifo(cs, count); + if ((count = cs->rcvidx) > 0) { + cs->rcvidx = 0; + if (!(skb = dev_alloc_skb(count))) + printk(KERN_WARNING "HiSax dch_int(): receive out of memory\n"); + else { + memcpy(skb_put(skb, count), cs->rcvbuf, count); + skb_queue_tail(&cs->rq, skb); + } + } + } + cs->rcvidx = 0; + schedule_event(cs, D_RCVBUFREADY); + } + + if (istad &0x40) { // RPF + dch_empty_fifo(cs, D_FIFO_SIZE); + } + + if (istad &0x20) { // RFO + if (cs->debug &L1_DEB_WARN) debugl1(cs, "dch_int(): RFO"); + cs->writeisac(cs, IPACX_CMDRD, 0x40); //RRES + } + + if (istad &0x10) { // XPR + if (test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) + del_timer(&cs->dbusytimer); + if (test_and_clear_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + schedule_event(cs, D_CLEARBUSY); + if (cs->tx_skb) { + if (cs->tx_skb->len) { + dch_fill_fifo(cs); + goto afterXPR; + } + else { + dev_kfree_skb_irq(cs->tx_skb); + cs->tx_skb = NULL; + cs->tx_cnt = 0; + } + } + if ((cs->tx_skb = skb_dequeue(&cs->sq))) { + cs->tx_cnt = 0; + dch_fill_fifo(cs); + } + else { + schedule_event(cs, D_XMTBUFREADY); + } + } + afterXPR: + + if (istad &0x0C) { // XDU or XMR + if (cs->debug &L1_DEB_WARN) debugl1(cs, "dch_int(): XDU"); + if (cs->tx_skb) { + skb_push(cs->tx_skb, cs->tx_cnt); // retransmit + cs->tx_cnt = 0; + dch_fill_fifo(cs); + } else { + printk(KERN_WARNING "HiSax: ISAC XDU no skb\n"); + debugl1(cs, "ISAC XDU no skb"); + } + } +} + +//---------------------------------------------------------- +//---------------------------------------------------------- +static void __devinit +dch_setstack(struct PStack *st, struct IsdnCardState *cs) +{ + st->l1.l1hw = dch_l2l1; +} + +//---------------------------------------------------------- +//---------------------------------------------------------- +static void __devinit +dch_init(struct IsdnCardState *cs) +{ + printk(KERN_INFO "HiSax: IPACX ISDN driver v0.1.0\n"); + + cs->setstack_d = dch_setstack; + + cs->dbusytimer.function = (void *) dbusy_timer_handler; + cs->dbusytimer.data = (long) cs; + init_timer(&cs->dbusytimer); + + cs->writeisac(cs, IPACX_TR_CONF0, 0x00); // clear LDD + cs->writeisac(cs, IPACX_TR_CONF2, 0x00); // enable transmitter + cs->writeisac(cs, IPACX_MODED, 0xC9); // transparent mode 0, RAC, stop/go + cs->writeisac(cs, IPACX_MON_CR, 0x00); // disable monitor channel +} + + +//========================================================== +// B channel functions +//========================================================== + +//---------------------------------------------------------- +// Entry point for commands +//---------------------------------------------------------- +static void +bch_l2l1(struct PStack *st, int pr, void *arg) +{ + struct BCState *bcs = st->l1.bcs; + struct sk_buff *skb = arg; + u_long flags; + + switch (pr) { + case (PH_DATA | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + skb_queue_tail(&bcs->squeue, skb); + } else { + bcs->tx_skb = skb; + set_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->hw.hscx.count = 0; + bch_fill_fifo(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | INDICATION): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + printk(KERN_WARNING "HiSax bch_l2l1(): this shouldn't happen\n"); + } else { + set_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->tx_skb = skb; + bcs->hw.hscx.count = 0; + bch_fill_fifo(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | REQUEST): + if (!bcs->tx_skb) { + clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } else + set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + case (PH_ACTIVATE | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + set_bit(BC_FLG_ACTIV, &bcs->Flag); + bch_mode(bcs, st->l1.mode, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | REQUEST): + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | CONFIRM): + spin_lock_irqsave(&bcs->cs->lock, flags); + clear_bit(BC_FLG_ACTIV, &bcs->Flag); + clear_bit(BC_FLG_BUSY, &bcs->Flag); + bch_mode(bcs, 0, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + st->l1.l1l2(st, PH_DEACTIVATE | CONFIRM, NULL); + break; + } +} + +//---------------------------------------------------------- +// Read B channel fifo to receive buffer +//---------------------------------------------------------- +static void +bch_empty_fifo(struct BCState *bcs, int count) +{ + u_char *ptr, hscx; + struct IsdnCardState *cs; + int cnt; + + cs = bcs->cs; + hscx = bcs->hw.hscx.hscx; + if ((cs->debug &L1_DEB_HSCX) && !(cs->debug &L1_DEB_HSCX_FIFO)) + debugl1(cs, "bch_empty_fifo()"); + + // message too large, remove + if (bcs->hw.hscx.rcvidx + count > HSCX_BUFMAX) { + if (cs->debug &L1_DEB_WARN) + debugl1(cs, "bch_empty_fifo() incoming packet too large"); + cs->BC_Write_Reg(cs, hscx, IPACX_CMDRB, 0x80); // RMC + bcs->hw.hscx.rcvidx = 0; + return; + } + + ptr = bcs->hw.hscx.rcvbuf + bcs->hw.hscx.rcvidx; + cnt = count; + while (cnt--) *ptr++ = cs->BC_Read_Reg(cs, hscx, IPACX_RFIFOB); + cs->BC_Write_Reg(cs, hscx, IPACX_CMDRB, 0x80); // RMC + + ptr = bcs->hw.hscx.rcvbuf + bcs->hw.hscx.rcvidx; + bcs->hw.hscx.rcvidx += count; + + if (cs->debug &L1_DEB_HSCX_FIFO) { + char *t = bcs->blog; + + t += sprintf(t, "bch_empty_fifo() B-%d cnt %d", hscx, count); + QuickHex(t, ptr, count); + debugl1(cs, bcs->blog); + } +} + +//---------------------------------------------------------- +// Fill buffer to transmit FIFO +//---------------------------------------------------------- +static void +bch_fill_fifo(struct BCState *bcs) +{ + struct IsdnCardState *cs; + int more, count, cnt; + u_char *ptr, *p, hscx; + + cs = bcs->cs; + if ((cs->debug &L1_DEB_HSCX) && !(cs->debug &L1_DEB_HSCX_FIFO)) + debugl1(cs, "bch_fill_fifo()"); + + if (!bcs->tx_skb) return; + if (bcs->tx_skb->len <= 0) return; + + hscx = bcs->hw.hscx.hscx; + more = (bcs->mode == L1_MODE_TRANS) ? 1 : 0; + if (bcs->tx_skb->len > B_FIFO_SIZE) { + more = 1; + count = B_FIFO_SIZE; + } else { + count = bcs->tx_skb->len; + } + cnt = count; + + p = ptr = bcs->tx_skb->data; + skb_pull(bcs->tx_skb, count); + bcs->tx_cnt -= count; + bcs->hw.hscx.count += count; + while (cnt--) cs->BC_Write_Reg(cs, hscx, IPACX_XFIFOB, *p++); + cs->BC_Write_Reg(cs, hscx, IPACX_CMDRB, (more ? 0x08 : 0x0a)); + + if (cs->debug &L1_DEB_HSCX_FIFO) { + char *t = bcs->blog; + + t += sprintf(t, "chb_fill_fifo() B-%d cnt %d", hscx, count); + QuickHex(t, ptr, count); + debugl1(cs, bcs->blog); + } +} + +//---------------------------------------------------------- +// B channel interrupt handler +//---------------------------------------------------------- +static void +bch_int(struct IsdnCardState *cs, u_char hscx) +{ + u_char istab; + struct BCState *bcs; + struct sk_buff *skb; + int count; + u_char rstab; + + bcs = cs->bcs + hscx; + istab = cs->BC_Read_Reg(cs, hscx, IPACX_ISTAB); +//############################################## +// printk(KERN_WARNING "bch_int(istab=%02x)\n", istab); +//############################################## + if (!test_bit(BC_FLG_INIT, &bcs->Flag)) return; + + if (istab &0x80) { // RME + rstab = cs->BC_Read_Reg(cs, hscx, IPACX_RSTAB); + if ((rstab &0xf0) != 0xa0) { // !(VFR && !RDO && CRC && !RAB) + if (!(rstab &0x80)) + if (cs->debug &L1_DEB_WARN) + debugl1(cs, "bch_int() B-%d: invalid frame", hscx); + if ((rstab &0x40) && (bcs->mode != L1_MODE_NULL)) + if (cs->debug &L1_DEB_WARN) + debugl1(cs, "bch_int() B-%d: RDO mode=%d", hscx, bcs->mode); + if (!(rstab &0x20)) + if (cs->debug &L1_DEB_WARN) + debugl1(cs, "bch_int() B-%d: CRC error", hscx); + cs->BC_Write_Reg(cs, hscx, IPACX_CMDRB, 0x80); // RMC + } + else { // received frame ok + count = cs->BC_Read_Reg(cs, hscx, IPACX_RBCLB) &(B_FIFO_SIZE-1); + if (count == 0) count = B_FIFO_SIZE; + bch_empty_fifo(bcs, count); + if ((count = bcs->hw.hscx.rcvidx - 1) > 0) { + if (cs->debug &L1_DEB_HSCX_FIFO) + debugl1(cs, "bch_int Frame %d", count); + if (!(skb = dev_alloc_skb(count))) + printk(KERN_WARNING "HiSax bch_int(): receive frame out of memory\n"); + else { + memcpy(skb_put(skb, count), bcs->hw.hscx.rcvbuf, count); + skb_queue_tail(&bcs->rqueue, skb); + } + } + } + bcs->hw.hscx.rcvidx = 0; + schedule_event(bcs, B_RCVBUFREADY); + } + + if (istab &0x40) { // RPF + bch_empty_fifo(bcs, B_FIFO_SIZE); + + if (bcs->mode == L1_MODE_TRANS) { // queue every chunk + // receive transparent audio data + if (!(skb = dev_alloc_skb(B_FIFO_SIZE))) + printk(KERN_WARNING "HiSax bch_int(): receive transparent out of memory\n"); + else { + memcpy(skb_put(skb, B_FIFO_SIZE), bcs->hw.hscx.rcvbuf, B_FIFO_SIZE); + skb_queue_tail(&bcs->rqueue, skb); + } + bcs->hw.hscx.rcvidx = 0; + schedule_event(bcs, B_RCVBUFREADY); + } + } + + if (istab &0x20) { // RFO + if (cs->debug &L1_DEB_WARN) + debugl1(cs, "bch_int() B-%d: RFO error", hscx); + cs->BC_Write_Reg(cs, hscx, IPACX_CMDRB, 0x40); // RRES + } + + if (istab &0x10) { // XPR + if (bcs->tx_skb) { + if (bcs->tx_skb->len) { + bch_fill_fifo(bcs); + goto afterXPR; + } else { + if (test_bit(FLG_LLI_L1WAKEUP,&bcs->st->lli.flag) && + (PACKET_NOACK != bcs->tx_skb->pkt_type)) { + u_long flags; + spin_lock_irqsave(&bcs->aclock, flags); + bcs->ackcnt += bcs->hw.hscx.count; + spin_unlock_irqrestore(&bcs->aclock, flags); + schedule_event(bcs, B_ACKPENDING); + } + } + dev_kfree_skb_irq(bcs->tx_skb); + bcs->hw.hscx.count = 0; + bcs->tx_skb = NULL; + } + if ((bcs->tx_skb = skb_dequeue(&bcs->squeue))) { + bcs->hw.hscx.count = 0; + set_bit(BC_FLG_BUSY, &bcs->Flag); + bch_fill_fifo(bcs); + } else { + clear_bit(BC_FLG_BUSY, &bcs->Flag); + schedule_event(bcs, B_XMTBUFREADY); + } + } + afterXPR: + + if (istab &0x04) { // XDU + if (bcs->mode == L1_MODE_TRANS) { + bch_fill_fifo(bcs); + } + else { + if (bcs->tx_skb) { // restart transmitting the whole frame + skb_push(bcs->tx_skb, bcs->hw.hscx.count); + bcs->tx_cnt += bcs->hw.hscx.count; + bcs->hw.hscx.count = 0; + } + cs->BC_Write_Reg(cs, hscx, IPACX_CMDRB, 0x01); // XRES + if (cs->debug &L1_DEB_WARN) + debugl1(cs, "bch_int() B-%d XDU error", hscx); + } + } +} + +//---------------------------------------------------------- +//---------------------------------------------------------- +static void +bch_mode(struct BCState *bcs, int mode, int bc) +{ + struct IsdnCardState *cs = bcs->cs; + int hscx = bcs->hw.hscx.hscx; + + bc = bc ? 1 : 0; // in case bc is greater than 1 + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "mode_bch() switch B-% mode %d chan %d", hscx, mode, bc); + bcs->mode = mode; + bcs->channel = bc; + + // map controller to according timeslot + if (!hscx) + { + cs->writeisac(cs, IPACX_BCHA_TSDP_BC1, 0x80 | bc); + cs->writeisac(cs, IPACX_BCHA_CR, 0x88); + } + else + { + cs->writeisac(cs, IPACX_BCHB_TSDP_BC1, 0x80 | bc); + cs->writeisac(cs, IPACX_BCHB_CR, 0x88); + } + + switch (mode) { + case (L1_MODE_NULL): + cs->BC_Write_Reg(cs, hscx, IPACX_MODEB, 0xC0); // rec off + cs->BC_Write_Reg(cs, hscx, IPACX_EXMB, 0x30); // std adj. + cs->BC_Write_Reg(cs, hscx, IPACX_MASKB, 0xFF); // ints off + cs->BC_Write_Reg(cs, hscx, IPACX_CMDRB, 0x41); // validate adjustments + break; + case (L1_MODE_TRANS): + cs->BC_Write_Reg(cs, hscx, IPACX_MODEB, 0x88); // ext transp mode + cs->BC_Write_Reg(cs, hscx, IPACX_EXMB, 0x00); // xxx00000 + cs->BC_Write_Reg(cs, hscx, IPACX_CMDRB, 0x41); // validate adjustments + cs->BC_Write_Reg(cs, hscx, IPACX_MASKB, _MASKB_IMASK); + break; + case (L1_MODE_HDLC): + cs->BC_Write_Reg(cs, hscx, IPACX_MODEB, 0xC8); // transp mode 0 + cs->BC_Write_Reg(cs, hscx, IPACX_EXMB, 0x01); // idle=hdlc flags crc enabled + cs->BC_Write_Reg(cs, hscx, IPACX_CMDRB, 0x41); // validate adjustments + cs->BC_Write_Reg(cs, hscx, IPACX_MASKB, _MASKB_IMASK); + break; + } +} + +//---------------------------------------------------------- +//---------------------------------------------------------- +static void +bch_close_state(struct BCState *bcs) +{ + bch_mode(bcs, 0, bcs->channel); + if (test_and_clear_bit(BC_FLG_INIT, &bcs->Flag)) { + if (bcs->hw.hscx.rcvbuf) { + kfree(bcs->hw.hscx.rcvbuf); + bcs->hw.hscx.rcvbuf = NULL; + } + if (bcs->blog) { + kfree(bcs->blog); + bcs->blog = NULL; + } + skb_queue_purge(&bcs->rqueue); + skb_queue_purge(&bcs->squeue); + if (bcs->tx_skb) { + dev_kfree_skb_any(bcs->tx_skb); + bcs->tx_skb = NULL; + clear_bit(BC_FLG_BUSY, &bcs->Flag); + } + } +} + +//---------------------------------------------------------- +//---------------------------------------------------------- +static int +bch_open_state(struct IsdnCardState *cs, struct BCState *bcs) +{ + if (!test_and_set_bit(BC_FLG_INIT, &bcs->Flag)) { + if (!(bcs->hw.hscx.rcvbuf = kmalloc(HSCX_BUFMAX, GFP_ATOMIC))) { + printk(KERN_WARNING + "HiSax open_bchstate(): No memory for hscx.rcvbuf\n"); + clear_bit(BC_FLG_INIT, &bcs->Flag); + return (1); + } + if (!(bcs->blog = kmalloc(MAX_BLOG_SPACE, GFP_ATOMIC))) { + printk(KERN_WARNING + "HiSax open_bchstate: No memory for bcs->blog\n"); + clear_bit(BC_FLG_INIT, &bcs->Flag); + kfree(bcs->hw.hscx.rcvbuf); + bcs->hw.hscx.rcvbuf = NULL; + return (2); + } + skb_queue_head_init(&bcs->rqueue); + skb_queue_head_init(&bcs->squeue); + } + bcs->tx_skb = NULL; + clear_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->event = 0; + bcs->hw.hscx.rcvidx = 0; + bcs->tx_cnt = 0; + return (0); +} + +//---------------------------------------------------------- +//---------------------------------------------------------- +static int +bch_setstack(struct PStack *st, struct BCState *bcs) +{ + bcs->channel = st->l1.bc; + if (bch_open_state(st->l1.hardware, bcs)) return (-1); + st->l1.bcs = bcs; + st->l2.l2l1 = bch_l2l1; + setstack_manager(st); + bcs->st = st; + setstack_l1_B(st); + return (0); +} + +//---------------------------------------------------------- +//---------------------------------------------------------- +static void __devinit +bch_init(struct IsdnCardState *cs, int hscx) +{ + cs->bcs[hscx].BC_SetStack = bch_setstack; + cs->bcs[hscx].BC_Close = bch_close_state; + cs->bcs[hscx].hw.hscx.hscx = hscx; + cs->bcs[hscx].cs = cs; + bch_mode(cs->bcs + hscx, 0, hscx); +} + + +//========================================================== +// Shared functions +//========================================================== + +//---------------------------------------------------------- +// Main interrupt handler +//---------------------------------------------------------- +void +interrupt_ipacx(struct IsdnCardState *cs) +{ + u_char ista; + + while ((ista = cs->readisac(cs, IPACX_ISTA))) { +//################################################# +// printk(KERN_WARNING "interrupt_ipacx(ista=%02x)\n", ista); +//################################################# + if (ista &0x80) bch_int(cs, 0); // B channel interrupts + if (ista &0x40) bch_int(cs, 1); + + if (ista &0x01) dch_int(cs); // D channel + if (ista &0x10) cic_int(cs); // Layer 1 state + } +} + +//---------------------------------------------------------- +// Clears chip interrupt status +//---------------------------------------------------------- +static void __init +clear_pending_ints(struct IsdnCardState *cs) +{ + int ista; + + // all interrupts off + cs->writeisac(cs, IPACX_MASK, 0xff); + cs->writeisac(cs, IPACX_MASKD, 0xff); + cs->BC_Write_Reg(cs, 0, IPACX_MASKB, 0xff); + cs->BC_Write_Reg(cs, 1, IPACX_MASKB, 0xff); + + ista = cs->readisac(cs, IPACX_ISTA); + if (ista &0x80) cs->BC_Read_Reg(cs, 0, IPACX_ISTAB); + if (ista &0x40) cs->BC_Read_Reg(cs, 1, IPACX_ISTAB); + if (ista &0x10) cs->readisac(cs, IPACX_CIR0); + if (ista &0x01) cs->readisac(cs, IPACX_ISTAD); +} + +//---------------------------------------------------------- +// Does chip configuration work +// Work to do depends on bit mask in part +//---------------------------------------------------------- +void __init +init_ipacx(struct IsdnCardState *cs, int part) +{ + if (part &1) { // initialise chip +//################################################## +// printk(KERN_INFO "init_ipacx(%x)\n", part); +//################################################## + clear_pending_ints(cs); + bch_init(cs, 0); + bch_init(cs, 1); + dch_init(cs); + } + if (part &2) { // reenable all interrupts and start chip + cs->BC_Write_Reg(cs, 0, IPACX_MASKB, _MASKB_IMASK); + cs->BC_Write_Reg(cs, 1, IPACX_MASKB, _MASKB_IMASK); + cs->writeisac(cs, IPACX_MASKD, _MASKD_IMASK); + cs->writeisac(cs, IPACX_MASK, _MASK_IMASK); // global mask register + + // reset HDLC Transmitters/receivers + cs->writeisac(cs, IPACX_CMDRD, 0x41); + cs->BC_Write_Reg(cs, 0, IPACX_CMDRB, 0x41); + cs->BC_Write_Reg(cs, 1, IPACX_CMDRB, 0x41); + ph_command(cs, IPACX_CMD_RES); + } +} + + +void __devinit +setup_ipacx(struct IsdnCardState *cs) +{ + INIT_WORK(&cs->tqueue, (void *)(void *) dch_bh, cs); + cs->dbusytimer.function = (void *) dbusy_timer_handler; + cs->dbusytimer.data = (long) cs; + init_timer(&cs->dbusytimer); +} +//----------------- end of file ----------------------- + diff --git a/drivers/isdn/hisax/ipacx.h b/drivers/isdn/hisax/ipacx.h new file mode 100644 index 000000000000..e8a22e8f34b6 --- /dev/null +++ b/drivers/isdn/hisax/ipacx.h @@ -0,0 +1,162 @@ +/* + * + * IPACX specific defines + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +/* All Registers original Siemens Spec */ + +#ifndef INCLUDE_IPACX_H +#define INCLUDE_IPACX_H + +/* D-channel registers */ +#define IPACX_RFIFOD 0x00 /* RD */ +#define IPACX_XFIFOD 0x00 /* WR */ +#define IPACX_ISTAD 0x20 /* RD */ +#define IPACX_MASKD 0x20 /* WR */ +#define IPACX_STARD 0x21 /* RD */ +#define IPACX_CMDRD 0x21 /* WR */ +#define IPACX_MODED 0x22 /* RD/WR */ +#define IPACX_EXMD1 0x23 /* RD/WR */ +#define IPACX_TIMR1 0x24 /* RD/WR */ +#define IPACX_SAP1 0x25 /* WR */ +#define IPACX_SAP2 0x26 /* WR */ +#define IPACX_RBCLD 0x26 /* RD */ +#define IPACX_RBCHD 0x27 /* RD */ +#define IPACX_TEI1 0x27 /* WR */ +#define IPACX_TEI2 0x28 /* WR */ +#define IPACX_RSTAD 0x28 /* RD */ +#define IPACX_TMD 0x29 /* RD/WR */ +#define IPACX_CIR0 0x2E /* RD */ +#define IPACX_CIX0 0x2E /* WR */ +#define IPACX_CIR1 0x2F /* RD */ +#define IPACX_CIX1 0x2F /* WR */ + +/* Transceiver registers */ +#define IPACX_TR_CONF0 0x30 /* RD/WR */ +#define IPACX_TR_CONF1 0x31 /* RD/WR */ +#define IPACX_TR_CONF2 0x32 /* RD/WR */ +#define IPACX_TR_STA 0x33 /* RD */ +#define IPACX_TR_CMD 0x34 /* RD/WR */ +#define IPACX_SQRR1 0x35 /* RD */ +#define IPACX_SQXR1 0x35 /* WR */ +#define IPACX_SQRR2 0x36 /* RD */ +#define IPACX_SQXR2 0x36 /* WR */ +#define IPACX_SQRR3 0x37 /* RD */ +#define IPACX_SQXR3 0x37 /* WR */ +#define IPACX_ISTATR 0x38 /* RD */ +#define IPACX_MASKTR 0x39 /* RD/WR */ +#define IPACX_TR_MODE 0x3A /* RD/WR */ +#define IPACX_ACFG1 0x3C /* RD/WR */ +#define IPACX_ACFG2 0x3D /* RD/WR */ +#define IPACX_AOE 0x3E /* RD/WR */ +#define IPACX_ARX 0x3F /* RD */ +#define IPACX_ATX 0x3F /* WR */ + +/* IOM: Timeslot, DPS, CDA */ +#define IPACX_CDA10 0x40 /* RD/WR */ +#define IPACX_CDA11 0x41 /* RD/WR */ +#define IPACX_CDA20 0x42 /* RD/WR */ +#define IPACX_CDA21 0x43 /* RD/WR */ +#define IPACX_CDA_TSDP10 0x44 /* RD/WR */ +#define IPACX_CDA_TSDP11 0x45 /* RD/WR */ +#define IPACX_CDA_TSDP20 0x46 /* RD/WR */ +#define IPACX_CDA_TSDP21 0x47 /* RD/WR */ +#define IPACX_BCHA_TSDP_BC1 0x48 /* RD/WR */ +#define IPACX_BCHA_TSDP_BC2 0x49 /* RD/WR */ +#define IPACX_BCHB_TSDP_BC1 0x4A /* RD/WR */ +#define IPACX_BCHB_TSDP_BC2 0x4B /* RD/WR */ +#define IPACX_TR_TSDP_BC1 0x4C /* RD/WR */ +#define IPACX_TR_TSDP_BC2 0x4D /* RD/WR */ +#define IPACX_CDA1_CR 0x4E /* RD/WR */ +#define IPACX_CDA2_CR 0x4F /* RD/WR */ + +/* IOM: Contol, Sync transfer, Monitor */ +#define IPACX_TR_CR 0x50 /* RD/WR */ +#define IPACX_TRC_CR 0x50 /* RD/WR */ +#define IPACX_BCHA_CR 0x51 /* RD/WR */ +#define IPACX_BCHB_CR 0x52 /* RD/WR */ +#define IPACX_DCI_CR 0x53 /* RD/WR */ +#define IPACX_DCIC_CR 0x53 /* RD/WR */ +#define IPACX_MON_CR 0x54 /* RD/WR */ +#define IPACX_SDS1_CR 0x55 /* RD/WR */ +#define IPACX_SDS2_CR 0x56 /* RD/WR */ +#define IPACX_IOM_CR 0x57 /* RD/WR */ +#define IPACX_STI 0x58 /* RD */ +#define IPACX_ASTI 0x58 /* WR */ +#define IPACX_MSTI 0x59 /* RD/WR */ +#define IPACX_SDS_CONF 0x5A /* RD/WR */ +#define IPACX_MCDA 0x5B /* RD */ +#define IPACX_MOR 0x5C /* RD */ +#define IPACX_MOX 0x5C /* WR */ +#define IPACX_MOSR 0x5D /* RD */ +#define IPACX_MOCR 0x5E /* RD/WR */ +#define IPACX_MSTA 0x5F /* RD */ +#define IPACX_MCONF 0x5F /* WR */ + +/* Interrupt and general registers */ +#define IPACX_ISTA 0x60 /* RD */ +#define IPACX_MASK 0x60 /* WR */ +#define IPACX_AUXI 0x61 /* RD */ +#define IPACX_AUXM 0x61 /* WR */ +#define IPACX_MODE1 0x62 /* RD/WR */ +#define IPACX_MODE2 0x63 /* RD/WR */ +#define IPACX_ID 0x64 /* RD */ +#define IPACX_SRES 0x64 /* WR */ +#define IPACX_TIMR2 0x65 /* RD/WR */ + +/* B-channel registers */ +#define IPACX_OFF_B1 0x70 +#define IPACX_OFF_B2 0x80 + +#define IPACX_ISTAB 0x00 /* RD */ +#define IPACX_MASKB 0x00 /* WR */ +#define IPACX_STARB 0x01 /* RD */ +#define IPACX_CMDRB 0x01 /* WR */ +#define IPACX_MODEB 0x02 /* RD/WR */ +#define IPACX_EXMB 0x03 /* RD/WR */ +#define IPACX_RAH1 0x05 /* WR */ +#define IPACX_RAH2 0x06 /* WR */ +#define IPACX_RBCLB 0x06 /* RD */ +#define IPACX_RBCHB 0x07 /* RD */ +#define IPACX_RAL1 0x07 /* WR */ +#define IPACX_RAL2 0x08 /* WR */ +#define IPACX_RSTAB 0x08 /* RD */ +#define IPACX_TMB 0x09 /* RD/WR */ +#define IPACX_RFIFOB 0x0A /*- RD */ +#define IPACX_XFIFOB 0x0A /*- WR */ + +/* Layer 1 Commands */ +#define IPACX_CMD_TIM 0x0 +#define IPACX_CMD_RES 0x1 +#define IPACX_CMD_SSP 0x2 +#define IPACX_CMD_SCP 0x3 +#define IPACX_CMD_AR8 0x8 +#define IPACX_CMD_AR10 0x9 +#define IPACX_CMD_ARL 0xa +#define IPACX_CMD_DI 0xf + +/* Layer 1 Indications */ +#define IPACX_IND_DR 0x0 +#define IPACX_IND_RES 0x1 +#define IPACX_IND_TMA 0x2 +#define IPACX_IND_SLD 0x3 +#define IPACX_IND_RSY 0x4 +#define IPACX_IND_DR6 0x5 +#define IPACX_IND_PU 0x7 +#define IPACX_IND_AR 0x8 +#define IPACX_IND_ARL 0xa +#define IPACX_IND_CVR 0xb +#define IPACX_IND_AI8 0xc +#define IPACX_IND_AI10 0xd +#define IPACX_IND_AIL 0xe +#define IPACX_IND_DC 0xf + +extern void init_ipacx(struct IsdnCardState *, int); +extern void interrupt_ipacx(struct IsdnCardState *); +extern void setup_isac(struct IsdnCardState *); + +#endif diff --git a/drivers/isdn/hisax/isac.c b/drivers/isdn/hisax/isac.c new file mode 100644 index 000000000000..20b949952952 --- /dev/null +++ b/drivers/isdn/hisax/isac.c @@ -0,0 +1,684 @@ +/* $Id: isac.c,v 1.31.2.3 2004/01/13 14:31:25 keil Exp $ + * + * ISAC specific routines + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * For changes and modifications please read + * Documentation/isdn/HiSax.cert + * + */ + +#include "hisax.h" +#include "isac.h" +#include "arcofi.h" +#include "isdnl1.h" +#include <linux/interrupt.h> +#include <linux/init.h> + +#define DBUSY_TIMER_VALUE 80 +#define ARCOFI_USE 1 + +static char *ISACVer[] __devinitdata = +{"2086/2186 V1.1", "2085 B1", "2085 B2", + "2085 V2.3"}; + +void +ISACVersion(struct IsdnCardState *cs, char *s) +{ + int val; + + val = cs->readisac(cs, ISAC_RBCH); + printk(KERN_INFO "%s ISAC version (%x): %s\n", s, val, ISACVer[(val >> 5) & 3]); +} + +static void +ph_command(struct IsdnCardState *cs, unsigned int command) +{ + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ph_command %x", command); + cs->writeisac(cs, ISAC_CIX0, (command << 2) | 3); +} + + +static void +isac_new_ph(struct IsdnCardState *cs) +{ + switch (cs->dc.isac.ph_state) { + case (ISAC_IND_RS): + case (ISAC_IND_EI): + ph_command(cs, ISAC_CMD_DUI); + l1_msg(cs, HW_RESET | INDICATION, NULL); + break; + case (ISAC_IND_DID): + l1_msg(cs, HW_DEACTIVATE | CONFIRM, NULL); + break; + case (ISAC_IND_DR): + l1_msg(cs, HW_DEACTIVATE | INDICATION, NULL); + break; + case (ISAC_IND_PU): + l1_msg(cs, HW_POWERUP | CONFIRM, NULL); + break; + case (ISAC_IND_RSY): + l1_msg(cs, HW_RSYNC | INDICATION, NULL); + break; + case (ISAC_IND_ARD): + l1_msg(cs, HW_INFO2 | INDICATION, NULL); + break; + case (ISAC_IND_AI8): + l1_msg(cs, HW_INFO4_P8 | INDICATION, NULL); + break; + case (ISAC_IND_AI10): + l1_msg(cs, HW_INFO4_P10 | INDICATION, NULL); + break; + default: + break; + } +} + +static void +isac_bh(struct IsdnCardState *cs) +{ + struct PStack *stptr; + + if (!cs) + return; + if (test_and_clear_bit(D_CLEARBUSY, &cs->event)) { + if (cs->debug) + debugl1(cs, "D-Channel Busy cleared"); + stptr = cs->stlist; + while (stptr != NULL) { + stptr->l1.l1l2(stptr, PH_PAUSE | CONFIRM, NULL); + stptr = stptr->next; + } + } + if (test_and_clear_bit(D_L1STATECHANGE, &cs->event)) + isac_new_ph(cs); + if (test_and_clear_bit(D_RCVBUFREADY, &cs->event)) + DChannel_proc_rcv(cs); + if (test_and_clear_bit(D_XMTBUFREADY, &cs->event)) + DChannel_proc_xmt(cs); +#if ARCOFI_USE + if (!test_bit(HW_ARCOFI, &cs->HW_Flags)) + return; + if (test_and_clear_bit(D_RX_MON1, &cs->event)) + arcofi_fsm(cs, ARCOFI_RX_END, NULL); + if (test_and_clear_bit(D_TX_MON1, &cs->event)) + arcofi_fsm(cs, ARCOFI_TX_END, NULL); +#endif +} + +void +isac_empty_fifo(struct IsdnCardState *cs, int count) +{ + u_char *ptr; + + if ((cs->debug & L1_DEB_ISAC) && !(cs->debug & L1_DEB_ISAC_FIFO)) + debugl1(cs, "isac_empty_fifo"); + + if ((cs->rcvidx + count) >= MAX_DFRAME_LEN_L1) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "isac_empty_fifo overrun %d", + cs->rcvidx + count); + cs->writeisac(cs, ISAC_CMDR, 0x80); + cs->rcvidx = 0; + return; + } + ptr = cs->rcvbuf + cs->rcvidx; + cs->rcvidx += count; + cs->readisacfifo(cs, ptr, count); + cs->writeisac(cs, ISAC_CMDR, 0x80); + if (cs->debug & L1_DEB_ISAC_FIFO) { + char *t = cs->dlog; + + t += sprintf(t, "isac_empty_fifo cnt %d", count); + QuickHex(t, ptr, count); + debugl1(cs, cs->dlog); + } +} + +static void +isac_fill_fifo(struct IsdnCardState *cs) +{ + int count, more; + u_char *ptr; + + if ((cs->debug & L1_DEB_ISAC) && !(cs->debug & L1_DEB_ISAC_FIFO)) + debugl1(cs, "isac_fill_fifo"); + + if (!cs->tx_skb) + return; + + count = cs->tx_skb->len; + if (count <= 0) + return; + + more = 0; + if (count > 32) { + more = !0; + count = 32; + } + ptr = cs->tx_skb->data; + skb_pull(cs->tx_skb, count); + cs->tx_cnt += count; + cs->writeisacfifo(cs, ptr, count); + cs->writeisac(cs, ISAC_CMDR, more ? 0x8 : 0xa); + if (test_and_set_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) { + debugl1(cs, "isac_fill_fifo dbusytimer running"); + del_timer(&cs->dbusytimer); + } + init_timer(&cs->dbusytimer); + cs->dbusytimer.expires = jiffies + ((DBUSY_TIMER_VALUE * HZ)/1000); + add_timer(&cs->dbusytimer); + if (cs->debug & L1_DEB_ISAC_FIFO) { + char *t = cs->dlog; + + t += sprintf(t, "isac_fill_fifo cnt %d", count); + QuickHex(t, ptr, count); + debugl1(cs, cs->dlog); + } +} + +void +isac_interrupt(struct IsdnCardState *cs, u_char val) +{ + u_char exval, v1; + struct sk_buff *skb; + unsigned int count; + + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ISAC interrupt %x", val); + if (val & 0x80) { /* RME */ + exval = cs->readisac(cs, ISAC_RSTA); + if ((exval & 0x70) != 0x20) { + if (exval & 0x40) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "ISAC RDO"); +#ifdef ERROR_STATISTIC + cs->err_rx++; +#endif + } + if (!(exval & 0x20)) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "ISAC CRC error"); +#ifdef ERROR_STATISTIC + cs->err_crc++; +#endif + } + cs->writeisac(cs, ISAC_CMDR, 0x80); + } else { + count = cs->readisac(cs, ISAC_RBCL) & 0x1f; + if (count == 0) + count = 32; + isac_empty_fifo(cs, count); + if ((count = cs->rcvidx) > 0) { + cs->rcvidx = 0; + if (!(skb = alloc_skb(count, GFP_ATOMIC))) + printk(KERN_WARNING "HiSax: D receive out of memory\n"); + else { + memcpy(skb_put(skb, count), cs->rcvbuf, count); + skb_queue_tail(&cs->rq, skb); + } + } + } + cs->rcvidx = 0; + schedule_event(cs, D_RCVBUFREADY); + } + if (val & 0x40) { /* RPF */ + isac_empty_fifo(cs, 32); + } + if (val & 0x20) { /* RSC */ + /* never */ + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "ISAC RSC interrupt"); + } + if (val & 0x10) { /* XPR */ + if (test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) + del_timer(&cs->dbusytimer); + if (test_and_clear_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + schedule_event(cs, D_CLEARBUSY); + if (cs->tx_skb) { + if (cs->tx_skb->len) { + isac_fill_fifo(cs); + goto afterXPR; + } else { + dev_kfree_skb_irq(cs->tx_skb); + cs->tx_cnt = 0; + cs->tx_skb = NULL; + } + } + if ((cs->tx_skb = skb_dequeue(&cs->sq))) { + cs->tx_cnt = 0; + isac_fill_fifo(cs); + } else + schedule_event(cs, D_XMTBUFREADY); + } + afterXPR: + if (val & 0x04) { /* CISQ */ + exval = cs->readisac(cs, ISAC_CIR0); + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ISAC CIR0 %02X", exval ); + if (exval & 2) { + cs->dc.isac.ph_state = (exval >> 2) & 0xf; + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ph_state change %x", cs->dc.isac.ph_state); + schedule_event(cs, D_L1STATECHANGE); + } + if (exval & 1) { + exval = cs->readisac(cs, ISAC_CIR1); + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ISAC CIR1 %02X", exval ); + } + } + if (val & 0x02) { /* SIN */ + /* never */ + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "ISAC SIN interrupt"); + } + if (val & 0x01) { /* EXI */ + exval = cs->readisac(cs, ISAC_EXIR); + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "ISAC EXIR %02x", exval); + if (exval & 0x80) { /* XMR */ + debugl1(cs, "ISAC XMR"); + printk(KERN_WARNING "HiSax: ISAC XMR\n"); + } + if (exval & 0x40) { /* XDU */ + debugl1(cs, "ISAC XDU"); + printk(KERN_WARNING "HiSax: ISAC XDU\n"); +#ifdef ERROR_STATISTIC + cs->err_tx++; +#endif + if (test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) + del_timer(&cs->dbusytimer); + if (test_and_clear_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + schedule_event(cs, D_CLEARBUSY); + if (cs->tx_skb) { /* Restart frame */ + skb_push(cs->tx_skb, cs->tx_cnt); + cs->tx_cnt = 0; + isac_fill_fifo(cs); + } else { + printk(KERN_WARNING "HiSax: ISAC XDU no skb\n"); + debugl1(cs, "ISAC XDU no skb"); + } + } + if (exval & 0x04) { /* MOS */ + v1 = cs->readisac(cs, ISAC_MOSR); + if (cs->debug & L1_DEB_MONITOR) + debugl1(cs, "ISAC MOSR %02x", v1); +#if ARCOFI_USE + if (v1 & 0x08) { + if (!cs->dc.isac.mon_rx) { + if (!(cs->dc.isac.mon_rx = kmalloc(MAX_MON_FRAME, GFP_ATOMIC))) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "ISAC MON RX out of memory!"); + cs->dc.isac.mocr &= 0xf0; + cs->dc.isac.mocr |= 0x0a; + cs->writeisac(cs, ISAC_MOCR, cs->dc.isac.mocr); + goto afterMONR0; + } else + cs->dc.isac.mon_rxp = 0; + } + if (cs->dc.isac.mon_rxp >= MAX_MON_FRAME) { + cs->dc.isac.mocr &= 0xf0; + cs->dc.isac.mocr |= 0x0a; + cs->writeisac(cs, ISAC_MOCR, cs->dc.isac.mocr); + cs->dc.isac.mon_rxp = 0; + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "ISAC MON RX overflow!"); + goto afterMONR0; + } + cs->dc.isac.mon_rx[cs->dc.isac.mon_rxp++] = cs->readisac(cs, ISAC_MOR0); + if (cs->debug & L1_DEB_MONITOR) + debugl1(cs, "ISAC MOR0 %02x", cs->dc.isac.mon_rx[cs->dc.isac.mon_rxp -1]); + if (cs->dc.isac.mon_rxp == 1) { + cs->dc.isac.mocr |= 0x04; + cs->writeisac(cs, ISAC_MOCR, cs->dc.isac.mocr); + } + } + afterMONR0: + if (v1 & 0x80) { + if (!cs->dc.isac.mon_rx) { + if (!(cs->dc.isac.mon_rx = kmalloc(MAX_MON_FRAME, GFP_ATOMIC))) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "ISAC MON RX out of memory!"); + cs->dc.isac.mocr &= 0x0f; + cs->dc.isac.mocr |= 0xa0; + cs->writeisac(cs, ISAC_MOCR, cs->dc.isac.mocr); + goto afterMONR1; + } else + cs->dc.isac.mon_rxp = 0; + } + if (cs->dc.isac.mon_rxp >= MAX_MON_FRAME) { + cs->dc.isac.mocr &= 0x0f; + cs->dc.isac.mocr |= 0xa0; + cs->writeisac(cs, ISAC_MOCR, cs->dc.isac.mocr); + cs->dc.isac.mon_rxp = 0; + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "ISAC MON RX overflow!"); + goto afterMONR1; + } + cs->dc.isac.mon_rx[cs->dc.isac.mon_rxp++] = cs->readisac(cs, ISAC_MOR1); + if (cs->debug & L1_DEB_MONITOR) + debugl1(cs, "ISAC MOR1 %02x", cs->dc.isac.mon_rx[cs->dc.isac.mon_rxp -1]); + cs->dc.isac.mocr |= 0x40; + cs->writeisac(cs, ISAC_MOCR, cs->dc.isac.mocr); + } + afterMONR1: + if (v1 & 0x04) { + cs->dc.isac.mocr &= 0xf0; + cs->writeisac(cs, ISAC_MOCR, cs->dc.isac.mocr); + cs->dc.isac.mocr |= 0x0a; + cs->writeisac(cs, ISAC_MOCR, cs->dc.isac.mocr); + schedule_event(cs, D_RX_MON0); + } + if (v1 & 0x40) { + cs->dc.isac.mocr &= 0x0f; + cs->writeisac(cs, ISAC_MOCR, cs->dc.isac.mocr); + cs->dc.isac.mocr |= 0xa0; + cs->writeisac(cs, ISAC_MOCR, cs->dc.isac.mocr); + schedule_event(cs, D_RX_MON1); + } + if (v1 & 0x02) { + if ((!cs->dc.isac.mon_tx) || (cs->dc.isac.mon_txc && + (cs->dc.isac.mon_txp >= cs->dc.isac.mon_txc) && + !(v1 & 0x08))) { + cs->dc.isac.mocr &= 0xf0; + cs->writeisac(cs, ISAC_MOCR, cs->dc.isac.mocr); + cs->dc.isac.mocr |= 0x0a; + cs->writeisac(cs, ISAC_MOCR, cs->dc.isac.mocr); + if (cs->dc.isac.mon_txc && + (cs->dc.isac.mon_txp >= cs->dc.isac.mon_txc)) + schedule_event(cs, D_TX_MON0); + goto AfterMOX0; + } + if (cs->dc.isac.mon_txc && (cs->dc.isac.mon_txp >= cs->dc.isac.mon_txc)) { + schedule_event(cs, D_TX_MON0); + goto AfterMOX0; + } + cs->writeisac(cs, ISAC_MOX0, + cs->dc.isac.mon_tx[cs->dc.isac.mon_txp++]); + if (cs->debug & L1_DEB_MONITOR) + debugl1(cs, "ISAC %02x -> MOX0", cs->dc.isac.mon_tx[cs->dc.isac.mon_txp -1]); + } + AfterMOX0: + if (v1 & 0x20) { + if ((!cs->dc.isac.mon_tx) || (cs->dc.isac.mon_txc && + (cs->dc.isac.mon_txp >= cs->dc.isac.mon_txc) && + !(v1 & 0x80))) { + cs->dc.isac.mocr &= 0x0f; + cs->writeisac(cs, ISAC_MOCR, cs->dc.isac.mocr); + cs->dc.isac.mocr |= 0xa0; + cs->writeisac(cs, ISAC_MOCR, cs->dc.isac.mocr); + if (cs->dc.isac.mon_txc && + (cs->dc.isac.mon_txp >= cs->dc.isac.mon_txc)) + schedule_event(cs, D_TX_MON1); + goto AfterMOX1; + } + if (cs->dc.isac.mon_txc && (cs->dc.isac.mon_txp >= cs->dc.isac.mon_txc)) { + schedule_event(cs, D_TX_MON1); + goto AfterMOX1; + } + cs->writeisac(cs, ISAC_MOX1, + cs->dc.isac.mon_tx[cs->dc.isac.mon_txp++]); + if (cs->debug & L1_DEB_MONITOR) + debugl1(cs, "ISAC %02x -> MOX1", cs->dc.isac.mon_tx[cs->dc.isac.mon_txp -1]); + } + AfterMOX1:; +#endif + } + } +} + +static void +ISAC_l1hw(struct PStack *st, int pr, void *arg) +{ + struct IsdnCardState *cs = (struct IsdnCardState *) st->l1.hardware; + struct sk_buff *skb = arg; + u_long flags; + int val; + + switch (pr) { + case (PH_DATA |REQUEST): + if (cs->debug & DEB_DLOG_HEX) + LogFrame(cs, skb->data, skb->len); + if (cs->debug & DEB_DLOG_VERBOSE) + dlogframe(cs, skb, 0); + spin_lock_irqsave(&cs->lock, flags); + if (cs->tx_skb) { + skb_queue_tail(&cs->sq, skb); +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "PH_DATA Queued", 0); +#endif + } else { + cs->tx_skb = skb; + cs->tx_cnt = 0; +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "PH_DATA", 0); +#endif + isac_fill_fifo(cs); + } + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (PH_PULL |INDICATION): + spin_lock_irqsave(&cs->lock, flags); + if (cs->tx_skb) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, " l2l1 tx_skb exist this shouldn't happen"); + skb_queue_tail(&cs->sq, skb); + } else { + if (cs->debug & DEB_DLOG_HEX) + LogFrame(cs, skb->data, skb->len); + if (cs->debug & DEB_DLOG_VERBOSE) + dlogframe(cs, skb, 0); + cs->tx_skb = skb; + cs->tx_cnt = 0; +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "PH_DATA_PULLED", 0); +#endif + isac_fill_fifo(cs); + } + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (PH_PULL | REQUEST): +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + debugl1(cs, "-> PH_REQUEST_PULL"); +#endif + if (!cs->tx_skb) { + test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } else + test_and_set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + case (HW_RESET | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + if ((cs->dc.isac.ph_state == ISAC_IND_EI) || + (cs->dc.isac.ph_state == ISAC_IND_DR) || + (cs->dc.isac.ph_state == ISAC_IND_RS)) + ph_command(cs, ISAC_CMD_TIM); + else + ph_command(cs, ISAC_CMD_RS); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (HW_ENABLE | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + ph_command(cs, ISAC_CMD_TIM); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (HW_INFO3 | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + ph_command(cs, ISAC_CMD_AR8); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (HW_TESTLOOP | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + val = 0; + if (1 & (long) arg) + val |= 0x0c; + if (2 & (long) arg) + val |= 0x3; + if (test_bit(HW_IOM1, &cs->HW_Flags)) { + /* IOM 1 Mode */ + if (!val) { + cs->writeisac(cs, ISAC_SPCR, 0xa); + cs->writeisac(cs, ISAC_ADF1, 0x2); + } else { + cs->writeisac(cs, ISAC_SPCR, val); + cs->writeisac(cs, ISAC_ADF1, 0xa); + } + } else { + /* IOM 2 Mode */ + cs->writeisac(cs, ISAC_SPCR, val); + if (val) + cs->writeisac(cs, ISAC_ADF1, 0x8); + else + cs->writeisac(cs, ISAC_ADF1, 0x0); + } + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (HW_DEACTIVATE | RESPONSE): + skb_queue_purge(&cs->rq); + skb_queue_purge(&cs->sq); + if (cs->tx_skb) { + dev_kfree_skb_any(cs->tx_skb); + cs->tx_skb = NULL; + } + if (test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) + del_timer(&cs->dbusytimer); + if (test_and_clear_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + schedule_event(cs, D_CLEARBUSY); + break; + default: + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "isac_l1hw unknown %04x", pr); + break; + } +} + +void +setstack_isac(struct PStack *st, struct IsdnCardState *cs) +{ + st->l1.l1hw = ISAC_l1hw; +} + +void +DC_Close_isac(struct IsdnCardState *cs) { + if (cs->dc.isac.mon_rx) { + kfree(cs->dc.isac.mon_rx); + cs->dc.isac.mon_rx = NULL; + } + if (cs->dc.isac.mon_tx) { + kfree(cs->dc.isac.mon_tx); + cs->dc.isac.mon_tx = NULL; + } +} + +static void +dbusy_timer_handler(struct IsdnCardState *cs) +{ + struct PStack *stptr; + int rbch, star; + + if (test_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) { + rbch = cs->readisac(cs, ISAC_RBCH); + star = cs->readisac(cs, ISAC_STAR); + if (cs->debug) + debugl1(cs, "D-Channel Busy RBCH %02x STAR %02x", + rbch, star); + if (rbch & ISAC_RBCH_XAC) { /* D-Channel Busy */ + test_and_set_bit(FLG_L1_DBUSY, &cs->HW_Flags); + stptr = cs->stlist; + while (stptr != NULL) { + stptr->l1.l1l2(stptr, PH_PAUSE | INDICATION, NULL); + stptr = stptr->next; + } + } else { + /* discard frame; reset transceiver */ + test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags); + if (cs->tx_skb) { + dev_kfree_skb_any(cs->tx_skb); + cs->tx_cnt = 0; + cs->tx_skb = NULL; + } else { + printk(KERN_WARNING "HiSax: ISAC D-Channel Busy no skb\n"); + debugl1(cs, "D-Channel Busy no skb"); + } + cs->writeisac(cs, ISAC_CMDR, 0x01); /* Transmitter reset */ + cs->irq_func(cs->irq, cs, NULL); + } + } +} + +void __devinit +initisac(struct IsdnCardState *cs) +{ + cs->setstack_d = setstack_isac; + cs->DC_Close = DC_Close_isac; + cs->dc.isac.mon_tx = NULL; + cs->dc.isac.mon_rx = NULL; + cs->writeisac(cs, ISAC_MASK, 0xff); + cs->dc.isac.mocr = 0xaa; + if (test_bit(HW_IOM1, &cs->HW_Flags)) { + /* IOM 1 Mode */ + cs->writeisac(cs, ISAC_ADF2, 0x0); + cs->writeisac(cs, ISAC_SPCR, 0xa); + cs->writeisac(cs, ISAC_ADF1, 0x2); + cs->writeisac(cs, ISAC_STCR, 0x70); + cs->writeisac(cs, ISAC_MODE, 0xc9); + } else { + /* IOM 2 Mode */ + if (!cs->dc.isac.adf2) + cs->dc.isac.adf2 = 0x80; + cs->writeisac(cs, ISAC_ADF2, cs->dc.isac.adf2); + cs->writeisac(cs, ISAC_SQXR, 0x2f); + cs->writeisac(cs, ISAC_SPCR, 0x00); + cs->writeisac(cs, ISAC_STCR, 0x70); + cs->writeisac(cs, ISAC_MODE, 0xc9); + cs->writeisac(cs, ISAC_TIMR, 0x00); + cs->writeisac(cs, ISAC_ADF1, 0x00); + } + ph_command(cs, ISAC_CMD_RS); + cs->writeisac(cs, ISAC_MASK, 0x0); +} + +void __devinit +clear_pending_isac_ints(struct IsdnCardState *cs) +{ + int val, eval; + + val = cs->readisac(cs, ISAC_STAR); + debugl1(cs, "ISAC STAR %x", val); + val = cs->readisac(cs, ISAC_MODE); + debugl1(cs, "ISAC MODE %x", val); + val = cs->readisac(cs, ISAC_ADF2); + debugl1(cs, "ISAC ADF2 %x", val); + val = cs->readisac(cs, ISAC_ISTA); + debugl1(cs, "ISAC ISTA %x", val); + if (val & 0x01) { + eval = cs->readisac(cs, ISAC_EXIR); + debugl1(cs, "ISAC EXIR %x", eval); + } + val = cs->readisac(cs, ISAC_CIR0); + debugl1(cs, "ISAC CIR0 %x", val); + cs->dc.isac.ph_state = (val >> 2) & 0xf; + schedule_event(cs, D_L1STATECHANGE); + /* Disable all IRQ */ + cs->writeisac(cs, ISAC_MASK, 0xFF); +} + +void __devinit +setup_isac(struct IsdnCardState *cs) +{ + INIT_WORK(&cs->tqueue, (void *)(void *) isac_bh, cs); + cs->dbusytimer.function = (void *) dbusy_timer_handler; + cs->dbusytimer.data = (long) cs; + init_timer(&cs->dbusytimer); +} diff --git a/drivers/isdn/hisax/isac.h b/drivers/isdn/hisax/isac.h new file mode 100644 index 000000000000..8f8331e44866 --- /dev/null +++ b/drivers/isdn/hisax/isac.h @@ -0,0 +1,70 @@ +/* $Id: isac.h,v 1.9.2.2 2004/01/12 22:52:27 keil Exp $ + * + * ISAC specific defines + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +/* All Registers original Siemens Spec */ + +#define ISAC_MASK 0x20 +#define ISAC_ISTA 0x20 +#define ISAC_STAR 0x21 +#define ISAC_CMDR 0x21 +#define ISAC_EXIR 0x24 +#define ISAC_ADF2 0x39 +#define ISAC_SPCR 0x30 +#define ISAC_ADF1 0x38 +#define ISAC_CIR0 0x31 +#define ISAC_CIX0 0x31 +#define ISAC_CIR1 0x33 +#define ISAC_CIX1 0x33 +#define ISAC_STCR 0x37 +#define ISAC_MODE 0x22 +#define ISAC_RSTA 0x27 +#define ISAC_RBCL 0x25 +#define ISAC_RBCH 0x2A +#define ISAC_TIMR 0x23 +#define ISAC_SQXR 0x3b +#define ISAC_MOSR 0x3a +#define ISAC_MOCR 0x3a +#define ISAC_MOR0 0x32 +#define ISAC_MOX0 0x32 +#define ISAC_MOR1 0x34 +#define ISAC_MOX1 0x34 + +#define ISAC_RBCH_XAC 0x80 + +#define ISAC_CMD_TIM 0x0 +#define ISAC_CMD_RS 0x1 +#define ISAC_CMD_SCZ 0x4 +#define ISAC_CMD_SSZ 0x2 +#define ISAC_CMD_AR8 0x8 +#define ISAC_CMD_AR10 0x9 +#define ISAC_CMD_ARL 0xA +#define ISAC_CMD_DUI 0xF + +#define ISAC_IND_RS 0x1 +#define ISAC_IND_PU 0x7 +#define ISAC_IND_DR 0x0 +#define ISAC_IND_SD 0x2 +#define ISAC_IND_DIS 0x3 +#define ISAC_IND_EI 0x6 +#define ISAC_IND_RSY 0x4 +#define ISAC_IND_ARD 0x8 +#define ISAC_IND_TI 0xA +#define ISAC_IND_ATI 0xB +#define ISAC_IND_AI8 0xC +#define ISAC_IND_AI10 0xD +#define ISAC_IND_DID 0xF + +extern void ISACVersion(struct IsdnCardState *, char *); +extern void setup_isac(struct IsdnCardState *); +extern void initisac(struct IsdnCardState *); +extern void isac_interrupt(struct IsdnCardState *, u_char); +extern void clear_pending_isac_ints(struct IsdnCardState *); diff --git a/drivers/isdn/hisax/isar.c b/drivers/isdn/hisax/isar.c new file mode 100644 index 000000000000..ee081321efb2 --- /dev/null +++ b/drivers/isdn/hisax/isar.c @@ -0,0 +1,1909 @@ +/* $Id: isar.c,v 1.22.2.6 2004/02/11 13:21:34 keil Exp $ + * + * isar.c ISAR (Siemens PSB 7110) specific routines + * + * Author Karsten Keil (keil@isdn4linux.de) + * + * This file is (c) under GNU General Public License + * + */ + +#include <linux/init.h> +#include "hisax.h" +#include "isar.h" +#include "isdnl1.h" +#include <linux/interrupt.h> + +#define DBG_LOADFIRM 0 +#define DUMP_MBOXFRAME 2 + +#define DLE 0x10 +#define ETX 0x03 + +#define FAXMODCNT 13 +const u_char faxmodulation[] = {3,24,48,72,73,74,96,97,98,121,122,145,146}; +static u_int modmask = 0x1fff; +static int frm_extra_delay = 2; +static int para_TOA = 6; +const u_char *FC1_CMD[] = {"FAE", "FTS", "FRS", "FTM", "FRM", "FTH", "FRH", "CTRL" }; + +void isar_setup(struct IsdnCardState *cs); +static void isar_pump_cmd(struct BCState *bcs, u_char cmd, u_char para); +static void ll_deliver_faxstat(struct BCState *bcs, u_char status); + +static inline int +waitforHIA(struct IsdnCardState *cs, int timeout) +{ + + while ((cs->BC_Read_Reg(cs, 0, ISAR_HIA) & 1) && timeout) { + udelay(1); + timeout--; + } + if (!timeout) + printk(KERN_WARNING "HiSax: ISAR waitforHIA timeout\n"); + return(timeout); +} + + +int +sendmsg(struct IsdnCardState *cs, u_char his, u_char creg, u_char len, + u_char *msg) +{ + int i; + + if (!waitforHIA(cs, 4000)) + return(0); +#if DUMP_MBOXFRAME + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "sendmsg(%02x,%02x,%d)", his, creg, len); +#endif + cs->BC_Write_Reg(cs, 0, ISAR_CTRL_H, creg); + cs->BC_Write_Reg(cs, 0, ISAR_CTRL_L, len); + cs->BC_Write_Reg(cs, 0, ISAR_WADR, 0); + if (msg && len) { + cs->BC_Write_Reg(cs, 1, ISAR_MBOX, msg[0]); + for (i=1; i<len; i++) + cs->BC_Write_Reg(cs, 2, ISAR_MBOX, msg[i]); +#if DUMP_MBOXFRAME>1 + if (cs->debug & L1_DEB_HSCX_FIFO) { + char tmp[256], *t; + + i = len; + while (i>0) { + t = tmp; + t += sprintf(t, "sendmbox cnt %d", len); + QuickHex(t, &msg[len-i], (i>64) ? 64:i); + debugl1(cs, tmp); + i -= 64; + } + } +#endif + } + cs->BC_Write_Reg(cs, 1, ISAR_HIS, his); + waitforHIA(cs, 10000); + return(1); +} + +/* Call only with IRQ disabled !!! */ +inline void +rcv_mbox(struct IsdnCardState *cs, struct isar_reg *ireg, u_char *msg) +{ + int i; + + cs->BC_Write_Reg(cs, 1, ISAR_RADR, 0); + if (msg && ireg->clsb) { + msg[0] = cs->BC_Read_Reg(cs, 1, ISAR_MBOX); + for (i=1; i < ireg->clsb; i++) + msg[i] = cs->BC_Read_Reg(cs, 2, ISAR_MBOX); +#if DUMP_MBOXFRAME>1 + if (cs->debug & L1_DEB_HSCX_FIFO) { + char tmp[256], *t; + + i = ireg->clsb; + while (i>0) { + t = tmp; + t += sprintf(t, "rcv_mbox cnt %d", ireg->clsb); + QuickHex(t, &msg[ireg->clsb-i], (i>64) ? 64:i); + debugl1(cs, tmp); + i -= 64; + } + } +#endif + } + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); +} + +/* Call only with IRQ disabled !!! */ +inline void +get_irq_infos(struct IsdnCardState *cs, struct isar_reg *ireg) +{ + ireg->iis = cs->BC_Read_Reg(cs, 1, ISAR_IIS); + ireg->cmsb = cs->BC_Read_Reg(cs, 1, ISAR_CTRL_H); + ireg->clsb = cs->BC_Read_Reg(cs, 1, ISAR_CTRL_L); +#if DUMP_MBOXFRAME + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "irq_stat(%02x,%02x,%d)", ireg->iis, ireg->cmsb, + ireg->clsb); +#endif +} + +int +waitrecmsg(struct IsdnCardState *cs, u_char *len, + u_char *msg, int maxdelay) +{ + int timeout = 0; + struct isar_reg *ir = cs->bcs[0].hw.isar.reg; + + + while((!(cs->BC_Read_Reg(cs, 0, ISAR_IRQBIT) & ISAR_IRQSTA)) && + (timeout++ < maxdelay)) + udelay(1); + if (timeout >= maxdelay) { + printk(KERN_WARNING"isar recmsg IRQSTA timeout\n"); + return(0); + } + get_irq_infos(cs, ir); + rcv_mbox(cs, ir, msg); + *len = ir->clsb; + return(1); +} + +int +ISARVersion(struct IsdnCardState *cs, char *s) +{ + int ver; + u_char msg[] = ISAR_MSG_HWVER; + u_char tmp[64]; + u_char len; + u_long flags; + int debug; + + cs->cardmsg(cs, CARD_RESET, NULL); + spin_lock_irqsave(&cs->lock, flags); + /* disable ISAR IRQ */ + cs->BC_Write_Reg(cs, 0, ISAR_IRQBIT, 0); + debug = cs->debug; + cs->debug &= ~(L1_DEB_HSCX | L1_DEB_HSCX_FIFO); + if (!sendmsg(cs, ISAR_HIS_VNR, 0, 3, msg)) { + spin_unlock_irqrestore(&cs->lock, flags); + return(-1); + } + if (!waitrecmsg(cs, &len, tmp, 100000)) { + spin_unlock_irqrestore(&cs->lock, flags); + return(-2); + } + cs->debug = debug; + if (cs->bcs[0].hw.isar.reg->iis == ISAR_IIS_VNR) { + if (len == 1) { + ver = tmp[0] & 0xf; + printk(KERN_INFO "%s ISAR version %d\n", s, ver); + } else + ver = -3; + } else + ver = -4; + spin_unlock_irqrestore(&cs->lock, flags); + return(ver); +} + +int +isar_load_firmware(struct IsdnCardState *cs, u_char __user *buf) +{ + int ret, size, cnt, debug; + u_char len, nom, noc; + u_short sadr, left, *sp; + u_char __user *p = buf; + u_char *msg, *tmpmsg, *mp, tmp[64]; + u_long flags; + struct isar_reg *ireg = cs->bcs[0].hw.isar.reg; + + struct {u_short sadr; + u_short len; + u_short d_key; + } blk_head; + +#define BLK_HEAD_SIZE 6 + if (1 != (ret = ISARVersion(cs, "Testing"))) { + printk(KERN_ERR"isar_load_firmware wrong isar version %d\n", ret); + return(1); + } + debug = cs->debug; +#if DBG_LOADFIRM<2 + cs->debug &= ~(L1_DEB_HSCX | L1_DEB_HSCX_FIFO); +#endif + + if ((ret = copy_from_user(&size, p, sizeof(int)))) { + printk(KERN_ERR"isar_load_firmware copy_from_user ret %d\n", ret); + return ret; + } + p += sizeof(int); + printk(KERN_DEBUG"isar_load_firmware size: %d\n", size); + cnt = 0; + /* disable ISAR IRQ */ + cs->BC_Write_Reg(cs, 0, ISAR_IRQBIT, 0); + if (!(msg = kmalloc(256, GFP_KERNEL))) { + printk(KERN_ERR"isar_load_firmware no buffer\n"); + return (1); + } + if (!(tmpmsg = kmalloc(256, GFP_KERNEL))) { + printk(KERN_ERR"isar_load_firmware no tmp buffer\n"); + kfree(msg); + return (1); + } + spin_lock_irqsave(&cs->lock, flags); + /* disable ISAR IRQ */ + cs->BC_Write_Reg(cs, 0, ISAR_IRQBIT, 0); + spin_unlock_irqrestore(&cs->lock, flags); + while (cnt < size) { + if ((ret = copy_from_user(&blk_head, p, BLK_HEAD_SIZE))) { + printk(KERN_ERR"isar_load_firmware copy_from_user ret %d\n", ret); + goto reterror; + } +#ifdef __BIG_ENDIAN + sadr = (blk_head.sadr & 0xff)*256 + blk_head.sadr/256; + blk_head.sadr = sadr; + sadr = (blk_head.len & 0xff)*256 + blk_head.len/256; + blk_head.len = sadr; + sadr = (blk_head.d_key & 0xff)*256 + blk_head.d_key/256; + blk_head.d_key = sadr; +#endif /* __BIG_ENDIAN */ + cnt += BLK_HEAD_SIZE; + p += BLK_HEAD_SIZE; + printk(KERN_DEBUG"isar firmware block (%#x,%5d,%#x)\n", + blk_head.sadr, blk_head.len, blk_head.d_key & 0xff); + sadr = blk_head.sadr; + left = blk_head.len; + spin_lock_irqsave(&cs->lock, flags); + if (!sendmsg(cs, ISAR_HIS_DKEY, blk_head.d_key & 0xff, 0, NULL)) { + printk(KERN_ERR"isar sendmsg dkey failed\n"); + ret = 1;goto reterr_unlock; + } + if (!waitrecmsg(cs, &len, tmp, 100000)) { + printk(KERN_ERR"isar waitrecmsg dkey failed\n"); + ret = 1;goto reterr_unlock; + } + if ((ireg->iis != ISAR_IIS_DKEY) || ireg->cmsb || len) { + printk(KERN_ERR"isar wrong dkey response (%x,%x,%x)\n", + ireg->iis, ireg->cmsb, len); + ret = 1;goto reterr_unlock; + } + spin_unlock_irqrestore(&cs->lock, flags); + while (left>0) { + if (left > 126) + noc = 126; + else + noc = left; + nom = 2*noc; + mp = msg; + *mp++ = sadr / 256; + *mp++ = sadr % 256; + left -= noc; + *mp++ = noc; + if ((ret = copy_from_user(tmpmsg, p, nom))) { + printk(KERN_ERR"isar_load_firmware copy_from_user ret %d\n", ret); + goto reterror; + } + p += nom; + cnt += nom; + nom += 3; + sp = (u_short *)tmpmsg; +#if DBG_LOADFIRM + printk(KERN_DEBUG"isar: load %3d words at %04x left %d\n", + noc, sadr, left); +#endif + sadr += noc; + while(noc) { +#ifdef __BIG_ENDIAN + *mp++ = *sp % 256; + *mp++ = *sp / 256; +#else + *mp++ = *sp / 256; + *mp++ = *sp % 256; +#endif /* __BIG_ENDIAN */ + sp++; + noc--; + } + spin_lock_irqsave(&cs->lock, flags); + if (!sendmsg(cs, ISAR_HIS_FIRM, 0, nom, msg)) { + printk(KERN_ERR"isar sendmsg prog failed\n"); + ret = 1;goto reterr_unlock; + } + if (!waitrecmsg(cs, &len, tmp, 100000)) { + printk(KERN_ERR"isar waitrecmsg prog failed\n"); + ret = 1;goto reterr_unlock; + } + if ((ireg->iis != ISAR_IIS_FIRM) || ireg->cmsb || len) { + printk(KERN_ERR"isar wrong prog response (%x,%x,%x)\n", + ireg->iis, ireg->cmsb, len); + ret = 1;goto reterr_unlock; + } + spin_unlock_irqrestore(&cs->lock, flags); + } + printk(KERN_DEBUG"isar firmware block %5d words loaded\n", + blk_head.len); + } + /* 10ms delay */ + cnt = 10; + while (cnt--) + udelay(1000); + msg[0] = 0xff; + msg[1] = 0xfe; + ireg->bstat = 0; + spin_lock_irqsave(&cs->lock, flags); + if (!sendmsg(cs, ISAR_HIS_STDSP, 0, 2, msg)) { + printk(KERN_ERR"isar sendmsg start dsp failed\n"); + ret = 1;goto reterr_unlock; + } + if (!waitrecmsg(cs, &len, tmp, 100000)) { + printk(KERN_ERR"isar waitrecmsg start dsp failed\n"); + ret = 1;goto reterr_unlock; + } + if ((ireg->iis != ISAR_IIS_STDSP) || ireg->cmsb || len) { + printk(KERN_ERR"isar wrong start dsp response (%x,%x,%x)\n", + ireg->iis, ireg->cmsb, len); + ret = 1;goto reterr_unlock; + } else + printk(KERN_DEBUG"isar start dsp success\n"); + /* NORMAL mode entered */ + /* Enable IRQs of ISAR */ + cs->BC_Write_Reg(cs, 0, ISAR_IRQBIT, ISAR_IRQSTA); + spin_unlock_irqrestore(&cs->lock, flags); + cnt = 1000; /* max 1s */ + while ((!ireg->bstat) && cnt) { + udelay(1000); + cnt--; + } + if (!cnt) { + printk(KERN_ERR"isar no general status event received\n"); + ret = 1;goto reterror; + } else { + printk(KERN_DEBUG"isar general status event %x\n", + ireg->bstat); + } + /* 10ms delay */ + cnt = 10; + while (cnt--) + udelay(1000); + spin_lock_irqsave(&cs->lock, flags); + ireg->iis = 0; + if (!sendmsg(cs, ISAR_HIS_DIAG, ISAR_CTRL_STST, 0, NULL)) { + printk(KERN_ERR"isar sendmsg self tst failed\n"); + ret = 1;goto reterr_unlock; + } + cnt = 10000; /* max 100 ms */ + spin_unlock_irqrestore(&cs->lock, flags); + while ((ireg->iis != ISAR_IIS_DIAG) && cnt) { + udelay(10); + cnt--; + } + udelay(1000); + if (!cnt) { + printk(KERN_ERR"isar no self tst response\n"); + ret = 1;goto reterror; + } + if ((ireg->cmsb == ISAR_CTRL_STST) && (ireg->clsb == 1) + && (ireg->par[0] == 0)) { + printk(KERN_DEBUG"isar selftest OK\n"); + } else { + printk(KERN_DEBUG"isar selftest not OK %x/%x/%x\n", + ireg->cmsb, ireg->clsb, ireg->par[0]); + ret = 1;goto reterror; + } + spin_lock_irqsave(&cs->lock, flags); + ireg->iis = 0; + if (!sendmsg(cs, ISAR_HIS_DIAG, ISAR_CTRL_SWVER, 0, NULL)) { + printk(KERN_ERR"isar RQST SVN failed\n"); + ret = 1;goto reterr_unlock; + } + spin_unlock_irqrestore(&cs->lock, flags); + cnt = 30000; /* max 300 ms */ + while ((ireg->iis != ISAR_IIS_DIAG) && cnt) { + udelay(10); + cnt--; + } + udelay(1000); + if (!cnt) { + printk(KERN_ERR"isar no SVN response\n"); + ret = 1;goto reterror; + } else { + if ((ireg->cmsb == ISAR_CTRL_SWVER) && (ireg->clsb == 1)) + printk(KERN_DEBUG"isar software version %#x\n", + ireg->par[0]); + else { + printk(KERN_ERR"isar wrong swver response (%x,%x) cnt(%d)\n", + ireg->cmsb, ireg->clsb, cnt); + ret = 1;goto reterror; + } + } + spin_lock_irqsave(&cs->lock, flags); + cs->debug = debug; + isar_setup(cs); + + ret = 0; +reterr_unlock: + spin_unlock_irqrestore(&cs->lock, flags); +reterror: + cs->debug = debug; + if (ret) + /* disable ISAR IRQ */ + cs->BC_Write_Reg(cs, 0, ISAR_IRQBIT, 0); + kfree(msg); + kfree(tmpmsg); + return(ret); +} + +extern void BChannel_bh(struct BCState *); +#define B_LL_NOCARRIER 8 +#define B_LL_CONNECT 9 +#define B_LL_OK 10 + +static void +isar_bh(struct BCState *bcs) +{ + BChannel_bh(bcs); + if (test_and_clear_bit(B_LL_NOCARRIER, &bcs->event)) + ll_deliver_faxstat(bcs, ISDN_FAX_CLASS1_NOCARR); + if (test_and_clear_bit(B_LL_CONNECT, &bcs->event)) + ll_deliver_faxstat(bcs, ISDN_FAX_CLASS1_CONNECT); + if (test_and_clear_bit(B_LL_OK, &bcs->event)) + ll_deliver_faxstat(bcs, ISDN_FAX_CLASS1_OK); +} + +static void +send_DLE_ETX(struct BCState *bcs) +{ + u_char dleetx[2] = {DLE,ETX}; + struct sk_buff *skb; + + if ((skb = dev_alloc_skb(2))) { + memcpy(skb_put(skb, 2), dleetx, 2); + skb_queue_tail(&bcs->rqueue, skb); + schedule_event(bcs, B_RCVBUFREADY); + } else { + printk(KERN_WARNING "HiSax: skb out of memory\n"); + } +} + +static inline int +dle_count(unsigned char *buf, int len) +{ + int count = 0; + + while (len--) + if (*buf++ == DLE) + count++; + return count; +} + +static inline void +insert_dle(unsigned char *dest, unsigned char *src, int count) { + /* <DLE> in input stream have to be flagged as <DLE><DLE> */ + while (count--) { + *dest++ = *src; + if (*src++ == DLE) + *dest++ = DLE; + } +} + +static void +isar_rcv_frame(struct IsdnCardState *cs, struct BCState *bcs) +{ + u_char *ptr; + struct sk_buff *skb; + struct isar_reg *ireg = bcs->hw.isar.reg; + + if (!ireg->clsb) { + debugl1(cs, "isar zero len frame"); + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); + return; + } + switch (bcs->mode) { + case L1_MODE_NULL: + debugl1(cs, "isar mode 0 spurious IIS_RDATA %x/%x/%x", + ireg->iis, ireg->cmsb, ireg->clsb); + printk(KERN_WARNING"isar mode 0 spurious IIS_RDATA %x/%x/%x\n", + ireg->iis, ireg->cmsb, ireg->clsb); + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); + break; + case L1_MODE_TRANS: + case L1_MODE_V32: + if ((skb = dev_alloc_skb(ireg->clsb))) { + rcv_mbox(cs, ireg, (u_char *)skb_put(skb, ireg->clsb)); + skb_queue_tail(&bcs->rqueue, skb); + schedule_event(bcs, B_RCVBUFREADY); + } else { + printk(KERN_WARNING "HiSax: skb out of memory\n"); + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); + } + break; + case L1_MODE_HDLC: + if ((bcs->hw.isar.rcvidx + ireg->clsb) > HSCX_BUFMAX) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "isar_rcv_frame: incoming packet too large"); + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); + bcs->hw.isar.rcvidx = 0; + } else if (ireg->cmsb & HDLC_ERROR) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "isar frame error %x len %d", + ireg->cmsb, ireg->clsb); +#ifdef ERROR_STATISTIC + if (ireg->cmsb & HDLC_ERR_RER) + bcs->err_inv++; + if (ireg->cmsb & HDLC_ERR_CER) + bcs->err_crc++; +#endif + bcs->hw.isar.rcvidx = 0; + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); + } else { + if (ireg->cmsb & HDLC_FSD) + bcs->hw.isar.rcvidx = 0; + ptr = bcs->hw.isar.rcvbuf + bcs->hw.isar.rcvidx; + bcs->hw.isar.rcvidx += ireg->clsb; + rcv_mbox(cs, ireg, ptr); + if (ireg->cmsb & HDLC_FED) { + if (bcs->hw.isar.rcvidx < 3) { /* last 2 bytes are the FCS */ + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "isar frame to short %d", + bcs->hw.isar.rcvidx); + } else if (!(skb = dev_alloc_skb(bcs->hw.isar.rcvidx-2))) { + printk(KERN_WARNING "ISAR: receive out of memory\n"); + } else { + memcpy(skb_put(skb, bcs->hw.isar.rcvidx-2), + bcs->hw.isar.rcvbuf, bcs->hw.isar.rcvidx-2); + skb_queue_tail(&bcs->rqueue, skb); + schedule_event(bcs, B_RCVBUFREADY); + } + bcs->hw.isar.rcvidx = 0; + } + } + break; + case L1_MODE_FAX: + if (bcs->hw.isar.state != STFAX_ACTIV) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "isar_rcv_frame: not ACTIV"); + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); + bcs->hw.isar.rcvidx = 0; + break; + } + if (bcs->hw.isar.cmd == PCTRL_CMD_FRM) { + rcv_mbox(cs, ireg, bcs->hw.isar.rcvbuf); + bcs->hw.isar.rcvidx = ireg->clsb + + dle_count(bcs->hw.isar.rcvbuf, ireg->clsb); + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "isar_rcv_frame: raw(%d) dle(%d)", + ireg->clsb, bcs->hw.isar.rcvidx); + if ((skb = dev_alloc_skb(bcs->hw.isar.rcvidx))) { + insert_dle((u_char *)skb_put(skb, bcs->hw.isar.rcvidx), + bcs->hw.isar.rcvbuf, ireg->clsb); + skb_queue_tail(&bcs->rqueue, skb); + schedule_event(bcs, B_RCVBUFREADY); + if (ireg->cmsb & SART_NMD) { /* ABORT */ + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "isar_rcv_frame: no more data"); + bcs->hw.isar.rcvidx = 0; + send_DLE_ETX(bcs); + sendmsg(cs, SET_DPS(bcs->hw.isar.dpath) | + ISAR_HIS_PUMPCTRL, PCTRL_CMD_ESC, + 0, NULL); + bcs->hw.isar.state = STFAX_ESCAPE; + schedule_event(bcs, B_LL_NOCARRIER); + } + } else { + printk(KERN_WARNING "HiSax: skb out of memory\n"); + } + break; + } + if (bcs->hw.isar.cmd != PCTRL_CMD_FRH) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "isar_rcv_frame: unknown fax mode %x", + bcs->hw.isar.cmd); + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); + bcs->hw.isar.rcvidx = 0; + break; + } + /* PCTRL_CMD_FRH */ + if ((bcs->hw.isar.rcvidx + ireg->clsb) > HSCX_BUFMAX) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "isar_rcv_frame: incoming packet too large"); + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); + bcs->hw.isar.rcvidx = 0; + } else if (ireg->cmsb & HDLC_ERROR) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "isar frame error %x len %d", + ireg->cmsb, ireg->clsb); + bcs->hw.isar.rcvidx = 0; + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); + } else { + if (ireg->cmsb & HDLC_FSD) { + bcs->hw.isar.rcvidx = 0; + } + ptr = bcs->hw.isar.rcvbuf + bcs->hw.isar.rcvidx; + bcs->hw.isar.rcvidx += ireg->clsb; + rcv_mbox(cs, ireg, ptr); + if (ireg->cmsb & HDLC_FED) { + int len = bcs->hw.isar.rcvidx + + dle_count(bcs->hw.isar.rcvbuf, bcs->hw.isar.rcvidx); + if (bcs->hw.isar.rcvidx < 3) { /* last 2 bytes are the FCS */ + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "isar frame to short %d", + bcs->hw.isar.rcvidx); + printk(KERN_WARNING "ISAR: frame to short %d\n", + bcs->hw.isar.rcvidx); + } else if (!(skb = dev_alloc_skb(len))) { + printk(KERN_WARNING "ISAR: receive out of memory\n"); + } else { + insert_dle((u_char *)skb_put(skb, len), + bcs->hw.isar.rcvbuf, + bcs->hw.isar.rcvidx); + skb_queue_tail(&bcs->rqueue, skb); + schedule_event(bcs, B_RCVBUFREADY); + send_DLE_ETX(bcs); + schedule_event(bcs, B_LL_OK); + test_and_clear_bit(BC_FLG_FRH_WAIT, &bcs->Flag); + } + bcs->hw.isar.rcvidx = 0; + } + } + if (ireg->cmsb & SART_NMD) { /* ABORT */ + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "isar_rcv_frame: no more data"); + bcs->hw.isar.rcvidx = 0; + sendmsg(cs, SET_DPS(bcs->hw.isar.dpath) | + ISAR_HIS_PUMPCTRL, PCTRL_CMD_ESC, 0, NULL); + bcs->hw.isar.state = STFAX_ESCAPE; + if (test_and_clear_bit(BC_FLG_FRH_WAIT, &bcs->Flag)) { + send_DLE_ETX(bcs); + schedule_event(bcs, B_LL_NOCARRIER); + } + } + break; + default: + printk(KERN_ERR"isar_rcv_frame mode (%x)error\n", bcs->mode); + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); + break; + } +} + +void +isar_fill_fifo(struct BCState *bcs) +{ + struct IsdnCardState *cs = bcs->cs; + int count; + u_char msb; + u_char *ptr; + + if ((cs->debug & L1_DEB_HSCX) && !(cs->debug & L1_DEB_HSCX_FIFO)) + debugl1(cs, "isar_fill_fifo"); + if (!bcs->tx_skb) + return; + if (bcs->tx_skb->len <= 0) + return; + if (!(bcs->hw.isar.reg->bstat & + (bcs->hw.isar.dpath == 1 ? BSTAT_RDM1 : BSTAT_RDM2))) + return; + if (bcs->tx_skb->len > bcs->hw.isar.mml) { + msb = 0; + count = bcs->hw.isar.mml; + } else { + count = bcs->tx_skb->len; + msb = HDLC_FED; + } + ptr = bcs->tx_skb->data; + if (!bcs->hw.isar.txcnt) { + msb |= HDLC_FST; + if ((bcs->mode == L1_MODE_FAX) && + (bcs->hw.isar.cmd == PCTRL_CMD_FTH)) { + if (bcs->tx_skb->len > 1) { + if ((ptr[0]== 0xff) && (ptr[1] == 0x13)) + /* last frame */ + test_and_set_bit(BC_FLG_LASTDATA, + &bcs->Flag); + } + } + } + skb_pull(bcs->tx_skb, count); + bcs->tx_cnt -= count; + bcs->hw.isar.txcnt += count; + switch (bcs->mode) { + case L1_MODE_NULL: + printk(KERN_ERR"isar_fill_fifo wrong mode 0\n"); + break; + case L1_MODE_TRANS: + case L1_MODE_V32: + sendmsg(cs, SET_DPS(bcs->hw.isar.dpath) | ISAR_HIS_SDATA, + 0, count, ptr); + break; + case L1_MODE_HDLC: + sendmsg(cs, SET_DPS(bcs->hw.isar.dpath) | ISAR_HIS_SDATA, + msb, count, ptr); + break; + case L1_MODE_FAX: + if (bcs->hw.isar.state != STFAX_ACTIV) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "isar_fill_fifo: not ACTIV"); + } else if (bcs->hw.isar.cmd == PCTRL_CMD_FTH) { + sendmsg(cs, SET_DPS(bcs->hw.isar.dpath) | ISAR_HIS_SDATA, + msb, count, ptr); + } else if (bcs->hw.isar.cmd == PCTRL_CMD_FTM) { + sendmsg(cs, SET_DPS(bcs->hw.isar.dpath) | ISAR_HIS_SDATA, + 0, count, ptr); + } else { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "isar_fill_fifo: not FTH/FTM"); + } + break; + default: + if (cs->debug) + debugl1(cs, "isar_fill_fifo mode(%x) error", bcs->mode); + printk(KERN_ERR"isar_fill_fifo mode(%x) error\n", bcs->mode); + break; + } +} + +inline +struct BCState *sel_bcs_isar(struct IsdnCardState *cs, u_char dpath) +{ + if ((!dpath) || (dpath == 3)) + return(NULL); + if (cs->bcs[0].hw.isar.dpath == dpath) + return(&cs->bcs[0]); + if (cs->bcs[1].hw.isar.dpath == dpath) + return(&cs->bcs[1]); + return(NULL); +} + +void +send_frames(struct BCState *bcs) +{ + if (bcs->tx_skb) { + if (bcs->tx_skb->len) { + isar_fill_fifo(bcs); + return; + } else { + if (test_bit(FLG_LLI_L1WAKEUP,&bcs->st->lli.flag) && + (PACKET_NOACK != bcs->tx_skb->pkt_type)) { + u_long flags; + spin_lock_irqsave(&bcs->aclock, flags); + bcs->ackcnt += bcs->hw.isar.txcnt; + spin_unlock_irqrestore(&bcs->aclock, flags); + schedule_event(bcs, B_ACKPENDING); + } + if (bcs->mode == L1_MODE_FAX) { + if (bcs->hw.isar.cmd == PCTRL_CMD_FTH) { + if (test_bit(BC_FLG_LASTDATA, &bcs->Flag)) { + test_and_set_bit(BC_FLG_NMD_DATA, &bcs->Flag); + } + } else if (bcs->hw.isar.cmd == PCTRL_CMD_FTM) { + if (test_bit(BC_FLG_DLEETX, &bcs->Flag)) { + test_and_set_bit(BC_FLG_LASTDATA, &bcs->Flag); + test_and_set_bit(BC_FLG_NMD_DATA, &bcs->Flag); + } + } + } + dev_kfree_skb_any(bcs->tx_skb); + bcs->hw.isar.txcnt = 0; + bcs->tx_skb = NULL; + } + } + if ((bcs->tx_skb = skb_dequeue(&bcs->squeue))) { + bcs->hw.isar.txcnt = 0; + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + isar_fill_fifo(bcs); + } else { + if (test_and_clear_bit(BC_FLG_DLEETX, &bcs->Flag)) { + if (test_and_clear_bit(BC_FLG_LASTDATA, &bcs->Flag)) { + if (test_and_clear_bit(BC_FLG_NMD_DATA, &bcs->Flag)) { + u_char dummy = 0; + sendmsg(bcs->cs, SET_DPS(bcs->hw.isar.dpath) | + ISAR_HIS_SDATA, 0x01, 1, &dummy); + } + test_and_set_bit(BC_FLG_LL_OK, &bcs->Flag); + } else { + schedule_event(bcs, B_LL_CONNECT); + } + } + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + schedule_event(bcs, B_XMTBUFREADY); + } +} + +inline void +check_send(struct IsdnCardState *cs, u_char rdm) +{ + struct BCState *bcs; + + if (rdm & BSTAT_RDM1) { + if ((bcs = sel_bcs_isar(cs, 1))) { + if (bcs->mode) { + send_frames(bcs); + } + } + } + if (rdm & BSTAT_RDM2) { + if ((bcs = sel_bcs_isar(cs, 2))) { + if (bcs->mode) { + send_frames(bcs); + } + } + } + +} + +const char *dmril[] = {"NO SPEED", "1200/75", "NODEF2", "75/1200", "NODEF4", + "300", "600", "1200", "2400", "4800", "7200", + "9600nt", "9600t", "12000", "14400", "WRONG"}; +const char *dmrim[] = {"NO MOD", "NO DEF", "V32/V32b", "V22", "V21", + "Bell103", "V23", "Bell202", "V17", "V29", "V27ter"}; + +static void +isar_pump_status_rsp(struct BCState *bcs, struct isar_reg *ireg) { + struct IsdnCardState *cs = bcs->cs; + u_char ril = ireg->par[0]; + u_char rim; + + if (!test_and_clear_bit(ISAR_RATE_REQ, &bcs->hw.isar.reg->Flags)) + return; + if (ril > 14) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "wrong pstrsp ril=%d",ril); + ril = 15; + } + switch(ireg->par[1]) { + case 0: + rim = 0; + break; + case 0x20: + rim = 2; + break; + case 0x40: + rim = 3; + break; + case 0x41: + rim = 4; + break; + case 0x51: + rim = 5; + break; + case 0x61: + rim = 6; + break; + case 0x71: + rim = 7; + break; + case 0x82: + rim = 8; + break; + case 0x92: + rim = 9; + break; + case 0xa2: + rim = 10; + break; + default: + rim = 1; + break; + } + sprintf(bcs->hw.isar.conmsg,"%s %s", dmril[ril], dmrim[rim]); + bcs->conmsg = bcs->hw.isar.conmsg; + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump strsp %s", bcs->conmsg); +} + +static void +isar_pump_statev_modem(struct BCState *bcs, u_char devt) { + struct IsdnCardState *cs = bcs->cs; + u_char dps = SET_DPS(bcs->hw.isar.dpath); + + switch(devt) { + case PSEV_10MS_TIMER: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev TIMER"); + break; + case PSEV_CON_ON: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev CONNECT"); + l1_msg_b(bcs->st, PH_ACTIVATE | REQUEST, NULL); + break; + case PSEV_CON_OFF: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev NO CONNECT"); + sendmsg(cs, dps | ISAR_HIS_PSTREQ, 0, 0, NULL); + l1_msg_b(bcs->st, PH_DEACTIVATE | REQUEST, NULL); + break; + case PSEV_V24_OFF: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev V24 OFF"); + break; + case PSEV_CTS_ON: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev CTS ON"); + break; + case PSEV_CTS_OFF: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev CTS OFF"); + break; + case PSEV_DCD_ON: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev CARRIER ON"); + test_and_set_bit(ISAR_RATE_REQ, &bcs->hw.isar.reg->Flags); + sendmsg(cs, dps | ISAR_HIS_PSTREQ, 0, 0, NULL); + break; + case PSEV_DCD_OFF: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev CARRIER OFF"); + break; + case PSEV_DSR_ON: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev DSR ON"); + break; + case PSEV_DSR_OFF: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev DSR_OFF"); + break; + case PSEV_REM_RET: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev REMOTE RETRAIN"); + break; + case PSEV_REM_REN: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev REMOTE RENEGOTIATE"); + break; + case PSEV_GSTN_CLR: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev GSTN CLEAR", devt); + break; + default: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "unknown pump stev %x", devt); + break; + } +} + +static void +ll_deliver_faxstat(struct BCState *bcs, u_char status) +{ + isdn_ctrl ic; + struct Channel *chanp = (struct Channel *) bcs->st->lli.userdata; + + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs, "HL->LL FAXIND %x", status); + ic.driver = bcs->cs->myid; + ic.command = ISDN_STAT_FAXIND; + ic.arg = chanp->chan; + ic.parm.aux.cmd = status; + bcs->cs->iif.statcallb(&ic); +} + +static void +isar_pump_statev_fax(struct BCState *bcs, u_char devt) { + struct IsdnCardState *cs = bcs->cs; + u_char dps = SET_DPS(bcs->hw.isar.dpath); + u_char p1; + + switch(devt) { + case PSEV_10MS_TIMER: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev TIMER"); + break; + case PSEV_RSP_READY: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev RSP_READY"); + bcs->hw.isar.state = STFAX_READY; + l1_msg_b(bcs->st, PH_ACTIVATE | REQUEST, NULL); + if (test_bit(BC_FLG_ORIG, &bcs->Flag)) { + isar_pump_cmd(bcs, ISDN_FAX_CLASS1_FRH, 3); + } else { + isar_pump_cmd(bcs, ISDN_FAX_CLASS1_FTH, 3); + } + break; + case PSEV_LINE_TX_H: + if (bcs->hw.isar.state == STFAX_LINE) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev LINE_TX_H"); + bcs->hw.isar.state = STFAX_CONT; + sendmsg(cs, dps | ISAR_HIS_PUMPCTRL, PCTRL_CMD_CONT, 0, NULL); + } else { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "pump stev LINE_TX_H wrong st %x", + bcs->hw.isar.state); + } + break; + case PSEV_LINE_RX_H: + if (bcs->hw.isar.state == STFAX_LINE) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev LINE_RX_H"); + bcs->hw.isar.state = STFAX_CONT; + sendmsg(cs, dps | ISAR_HIS_PUMPCTRL, PCTRL_CMD_CONT, 0, NULL); + } else { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "pump stev LINE_RX_H wrong st %x", + bcs->hw.isar.state); + } + break; + case PSEV_LINE_TX_B: + if (bcs->hw.isar.state == STFAX_LINE) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev LINE_TX_B"); + bcs->hw.isar.state = STFAX_CONT; + sendmsg(cs, dps | ISAR_HIS_PUMPCTRL, PCTRL_CMD_CONT, 0, NULL); + } else { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "pump stev LINE_TX_B wrong st %x", + bcs->hw.isar.state); + } + break; + case PSEV_LINE_RX_B: + if (bcs->hw.isar.state == STFAX_LINE) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev LINE_RX_B"); + bcs->hw.isar.state = STFAX_CONT; + sendmsg(cs, dps | ISAR_HIS_PUMPCTRL, PCTRL_CMD_CONT, 0, NULL); + } else { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "pump stev LINE_RX_B wrong st %x", + bcs->hw.isar.state); + } + break; + case PSEV_RSP_CONN: + if (bcs->hw.isar.state == STFAX_CONT) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev RSP_CONN"); + bcs->hw.isar.state = STFAX_ACTIV; + test_and_set_bit(ISAR_RATE_REQ, &bcs->hw.isar.reg->Flags); + sendmsg(cs, dps | ISAR_HIS_PSTREQ, 0, 0, NULL); + if (bcs->hw.isar.cmd == PCTRL_CMD_FTH) { + /* 1s Flags before data */ + if (test_and_set_bit(BC_FLG_FTI_RUN, &bcs->Flag)) + del_timer(&bcs->hw.isar.ftimer); + /* 1000 ms */ + bcs->hw.isar.ftimer.expires = + jiffies + ((1000 * HZ)/1000); + test_and_set_bit(BC_FLG_LL_CONN, + &bcs->Flag); + add_timer(&bcs->hw.isar.ftimer); + } else { + schedule_event(bcs, B_LL_CONNECT); + } + } else { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "pump stev RSP_CONN wrong st %x", + bcs->hw.isar.state); + } + break; + case PSEV_FLAGS_DET: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev FLAGS_DET"); + break; + case PSEV_RSP_DISC: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev RSP_DISC"); + if (bcs->hw.isar.state == STFAX_ESCAPE) { + p1 = 5; + switch(bcs->hw.isar.newcmd) { + case 0: + bcs->hw.isar.state = STFAX_READY; + break; + case PCTRL_CMD_FTM: + p1 = 2; + case PCTRL_CMD_FTH: + sendmsg(cs, dps | ISAR_HIS_PUMPCTRL, + PCTRL_CMD_SILON, 1, &p1); + bcs->hw.isar.state = STFAX_SILDET; + break; + case PCTRL_CMD_FRM: + if (frm_extra_delay) + mdelay(frm_extra_delay); + case PCTRL_CMD_FRH: + p1 = bcs->hw.isar.mod = bcs->hw.isar.newmod; + bcs->hw.isar.newmod = 0; + bcs->hw.isar.cmd = bcs->hw.isar.newcmd; + bcs->hw.isar.newcmd = 0; + sendmsg(cs, dps | ISAR_HIS_PUMPCTRL, + bcs->hw.isar.cmd, 1, &p1); + bcs->hw.isar.state = STFAX_LINE; + bcs->hw.isar.try_mod = 3; + break; + default: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "RSP_DISC unknown newcmd %x", bcs->hw.isar.newcmd); + break; + } + } else if (bcs->hw.isar.state == STFAX_ACTIV) { + if (test_and_clear_bit(BC_FLG_LL_OK, &bcs->Flag)) { + schedule_event(bcs, B_LL_OK); + } else if (bcs->hw.isar.cmd == PCTRL_CMD_FRM) { + send_DLE_ETX(bcs); + schedule_event(bcs, B_LL_NOCARRIER); + } else { + ll_deliver_faxstat(bcs, ISDN_FAX_CLASS1_FCERROR); + } + bcs->hw.isar.state = STFAX_READY; + } else { + bcs->hw.isar.state = STFAX_READY; + ll_deliver_faxstat(bcs, ISDN_FAX_CLASS1_FCERROR); + } + break; + case PSEV_RSP_SILDET: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev RSP_SILDET"); + if (bcs->hw.isar.state == STFAX_SILDET) { + p1 = bcs->hw.isar.mod = bcs->hw.isar.newmod; + bcs->hw.isar.newmod = 0; + bcs->hw.isar.cmd = bcs->hw.isar.newcmd; + bcs->hw.isar.newcmd = 0; + sendmsg(cs, dps | ISAR_HIS_PUMPCTRL, + bcs->hw.isar.cmd, 1, &p1); + bcs->hw.isar.state = STFAX_LINE; + bcs->hw.isar.try_mod = 3; + } + break; + case PSEV_RSP_SILOFF: + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev RSP_SILOFF"); + break; + case PSEV_RSP_FCERR: + if (bcs->hw.isar.state == STFAX_LINE) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev RSP_FCERR try %d", + bcs->hw.isar.try_mod); + if (bcs->hw.isar.try_mod--) { + sendmsg(cs, dps | ISAR_HIS_PUMPCTRL, + bcs->hw.isar.cmd, 1, + &bcs->hw.isar.mod); + break; + } + } + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev RSP_FCERR"); + bcs->hw.isar.state = STFAX_ESCAPE; + sendmsg(cs, dps | ISAR_HIS_PUMPCTRL, PCTRL_CMD_ESC, 0, NULL); + ll_deliver_faxstat(bcs, ISDN_FAX_CLASS1_FCERROR); + break; + default: + break; + } +} + +static char debbuf[128]; + +void +isar_int_main(struct IsdnCardState *cs) +{ + struct isar_reg *ireg = cs->bcs[0].hw.isar.reg; + struct BCState *bcs; + + get_irq_infos(cs, ireg); + switch (ireg->iis & ISAR_IIS_MSCMSD) { + case ISAR_IIS_RDATA: + if ((bcs = sel_bcs_isar(cs, ireg->iis >> 6))) { + isar_rcv_frame(cs, bcs); + } else { + debugl1(cs, "isar spurious IIS_RDATA %x/%x/%x", + ireg->iis, ireg->cmsb, ireg->clsb); + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); + } + break; + case ISAR_IIS_GSTEV: + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); + ireg->bstat |= ireg->cmsb; + check_send(cs, ireg->cmsb); + break; + case ISAR_IIS_BSTEV: +#ifdef ERROR_STATISTIC + if ((bcs = sel_bcs_isar(cs, ireg->iis >> 6))) { + if (ireg->cmsb == BSTEV_TBO) + bcs->err_tx++; + if (ireg->cmsb == BSTEV_RBO) + bcs->err_rdo++; + } +#endif + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "Buffer STEV dpath%d msb(%x)", + ireg->iis>>6, ireg->cmsb); + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); + break; + case ISAR_IIS_PSTEV: + if ((bcs = sel_bcs_isar(cs, ireg->iis >> 6))) { + rcv_mbox(cs, ireg, (u_char *)ireg->par); + if (bcs->mode == L1_MODE_V32) { + isar_pump_statev_modem(bcs, ireg->cmsb); + } else if (bcs->mode == L1_MODE_FAX) { + isar_pump_statev_fax(bcs, ireg->cmsb); + } else if (ireg->cmsb == PSEV_10MS_TIMER) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "pump stev TIMER"); + } else { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "isar IIS_PSTEV pmode %d stat %x", + bcs->mode, ireg->cmsb); + } + } else { + debugl1(cs, "isar spurious IIS_PSTEV %x/%x/%x", + ireg->iis, ireg->cmsb, ireg->clsb); + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); + } + break; + case ISAR_IIS_PSTRSP: + if ((bcs = sel_bcs_isar(cs, ireg->iis >> 6))) { + rcv_mbox(cs, ireg, (u_char *)ireg->par); + isar_pump_status_rsp(bcs, ireg); + } else { + debugl1(cs, "isar spurious IIS_PSTRSP %x/%x/%x", + ireg->iis, ireg->cmsb, ireg->clsb); + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); + } + break; + case ISAR_IIS_DIAG: + case ISAR_IIS_BSTRSP: + case ISAR_IIS_IOM2RSP: + rcv_mbox(cs, ireg, (u_char *)ireg->par); + if ((cs->debug & (L1_DEB_HSCX | L1_DEB_HSCX_FIFO)) + == L1_DEB_HSCX) { + u_char *tp=debbuf; + + tp += sprintf(debbuf, "msg iis(%x) msb(%x)", + ireg->iis, ireg->cmsb); + QuickHex(tp, (u_char *)ireg->par, ireg->clsb); + debugl1(cs, debbuf); + } + break; + case ISAR_IIS_INVMSG: + rcv_mbox(cs, ireg, debbuf); + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "invalid msg his:%x", + ireg->cmsb); + break; + default: + rcv_mbox(cs, ireg, debbuf); + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "unhandled msg iis(%x) ctrl(%x/%x)", + ireg->iis, ireg->cmsb, ireg->clsb); + break; + } +} + +static void +ftimer_handler(struct BCState *bcs) { + if (bcs->cs->debug) + debugl1(bcs->cs, "ftimer flags %04x", + bcs->Flag); + test_and_clear_bit(BC_FLG_FTI_RUN, &bcs->Flag); + if (test_and_clear_bit(BC_FLG_LL_CONN, &bcs->Flag)) { + schedule_event(bcs, B_LL_CONNECT); + } + if (test_and_clear_bit(BC_FLG_FTI_FTS, &bcs->Flag)) { + schedule_event(bcs, B_LL_OK); + } +} + +static void +setup_pump(struct BCState *bcs) { + struct IsdnCardState *cs = bcs->cs; + u_char dps = SET_DPS(bcs->hw.isar.dpath); + u_char ctrl, param[6]; + + switch (bcs->mode) { + case L1_MODE_NULL: + case L1_MODE_TRANS: + case L1_MODE_HDLC: + sendmsg(cs, dps | ISAR_HIS_PUMPCFG, PMOD_BYPASS, 0, NULL); + break; + case L1_MODE_V32: + ctrl = PMOD_DATAMODEM; + if (test_bit(BC_FLG_ORIG, &bcs->Flag)) { + ctrl |= PCTRL_ORIG; + param[5] = PV32P6_CTN; + } else { + param[5] = PV32P6_ATN; + } + param[0] = para_TOA; /* 6 db */ + param[1] = PV32P2_V23R | PV32P2_V22A | PV32P2_V22B | + PV32P2_V22C | PV32P2_V21 | PV32P2_BEL; + param[2] = PV32P3_AMOD | PV32P3_V32B | PV32P3_V23B; + param[3] = PV32P4_UT144; + param[4] = PV32P5_UT144; + sendmsg(cs, dps | ISAR_HIS_PUMPCFG, ctrl, 6, param); + break; + case L1_MODE_FAX: + ctrl = PMOD_FAX; + if (test_bit(BC_FLG_ORIG, &bcs->Flag)) { + ctrl |= PCTRL_ORIG; + param[1] = PFAXP2_CTN; + } else { + param[1] = PFAXP2_ATN; + } + param[0] = para_TOA; /* 6 db */ + sendmsg(cs, dps | ISAR_HIS_PUMPCFG, ctrl, 2, param); + bcs->hw.isar.state = STFAX_NULL; + bcs->hw.isar.newcmd = 0; + bcs->hw.isar.newmod = 0; + test_and_set_bit(BC_FLG_FTI_RUN, &bcs->Flag); + break; + } + udelay(1000); + sendmsg(cs, dps | ISAR_HIS_PSTREQ, 0, 0, NULL); + udelay(1000); +} + +static void +setup_sart(struct BCState *bcs) { + struct IsdnCardState *cs = bcs->cs; + u_char dps = SET_DPS(bcs->hw.isar.dpath); + u_char ctrl, param[2]; + + switch (bcs->mode) { + case L1_MODE_NULL: + sendmsg(cs, dps | ISAR_HIS_SARTCFG, SMODE_DISABLE, 0, + NULL); + break; + case L1_MODE_TRANS: + sendmsg(cs, dps | ISAR_HIS_SARTCFG, SMODE_BINARY, 2, + "\0\0"); + break; + case L1_MODE_HDLC: + param[0] = 0; + sendmsg(cs, dps | ISAR_HIS_SARTCFG, SMODE_HDLC, 1, + param); + break; + case L1_MODE_V32: + ctrl = SMODE_V14 | SCTRL_HDMC_BOTH; + param[0] = S_P1_CHS_8; + param[1] = S_P2_BFT_DEF; + sendmsg(cs, dps | ISAR_HIS_SARTCFG, ctrl, 2, + param); + break; + case L1_MODE_FAX: + /* SART must not configured with FAX */ + break; + } + udelay(1000); + sendmsg(cs, dps | ISAR_HIS_BSTREQ, 0, 0, NULL); + udelay(1000); +} + +static void +setup_iom2(struct BCState *bcs) { + struct IsdnCardState *cs = bcs->cs; + u_char dps = SET_DPS(bcs->hw.isar.dpath); + u_char cmsb = IOM_CTRL_ENA, msg[5] = {IOM_P1_TXD,0,0,0,0}; + + if (bcs->channel) + msg[1] = msg[3] = 1; + switch (bcs->mode) { + case L1_MODE_NULL: + cmsb = 0; + /* dummy slot */ + msg[1] = msg[3] = bcs->hw.isar.dpath + 2; + break; + case L1_MODE_TRANS: + case L1_MODE_HDLC: + break; + case L1_MODE_V32: + case L1_MODE_FAX: + cmsb |= IOM_CTRL_ALAW | IOM_CTRL_RCV; + break; + } + sendmsg(cs, dps | ISAR_HIS_IOM2CFG, cmsb, 5, msg); + udelay(1000); + sendmsg(cs, dps | ISAR_HIS_IOM2REQ, 0, 0, NULL); + udelay(1000); +} + +int +modeisar(struct BCState *bcs, int mode, int bc) +{ + struct IsdnCardState *cs = bcs->cs; + + /* Here we are selecting the best datapath for requested mode */ + if(bcs->mode == L1_MODE_NULL) { /* New Setup */ + bcs->channel = bc; + switch (mode) { + case L1_MODE_NULL: /* init */ + if (!bcs->hw.isar.dpath) + /* no init for dpath 0 */ + return(0); + break; + case L1_MODE_TRANS: + case L1_MODE_HDLC: + /* best is datapath 2 */ + if (!test_and_set_bit(ISAR_DP2_USE, + &bcs->hw.isar.reg->Flags)) + bcs->hw.isar.dpath = 2; + else if (!test_and_set_bit(ISAR_DP1_USE, + &bcs->hw.isar.reg->Flags)) + bcs->hw.isar.dpath = 1; + else { + printk(KERN_WARNING"isar modeisar both pathes in use\n"); + return(1); + } + break; + case L1_MODE_V32: + case L1_MODE_FAX: + /* only datapath 1 */ + if (!test_and_set_bit(ISAR_DP1_USE, + &bcs->hw.isar.reg->Flags)) + bcs->hw.isar.dpath = 1; + else { + printk(KERN_WARNING"isar modeisar analog funktions only with DP1\n"); + debugl1(cs, "isar modeisar analog funktions only with DP1"); + return(1); + } + break; + } + } + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "isar dp%d mode %d->%d ichan %d", + bcs->hw.isar.dpath, bcs->mode, mode, bc); + bcs->mode = mode; + setup_pump(bcs); + setup_iom2(bcs); + setup_sart(bcs); + if (bcs->mode == L1_MODE_NULL) { + /* Clear resources */ + if (bcs->hw.isar.dpath == 1) + test_and_clear_bit(ISAR_DP1_USE, &bcs->hw.isar.reg->Flags); + else if (bcs->hw.isar.dpath == 2) + test_and_clear_bit(ISAR_DP2_USE, &bcs->hw.isar.reg->Flags); + bcs->hw.isar.dpath = 0; + } + return(0); +} + +static void +isar_pump_cmd(struct BCState *bcs, u_char cmd, u_char para) +{ + struct IsdnCardState *cs = bcs->cs; + u_char dps = SET_DPS(bcs->hw.isar.dpath); + u_char ctrl = 0, nom = 0, p1 = 0; + + switch(cmd) { + case ISDN_FAX_CLASS1_FTM: + test_and_clear_bit(BC_FLG_FRH_WAIT, &bcs->Flag); + if (bcs->hw.isar.state == STFAX_READY) { + p1 = para; + ctrl = PCTRL_CMD_FTM; + nom = 1; + bcs->hw.isar.state = STFAX_LINE; + bcs->hw.isar.cmd = ctrl; + bcs->hw.isar.mod = para; + bcs->hw.isar.newmod = 0; + bcs->hw.isar.newcmd = 0; + bcs->hw.isar.try_mod = 3; + } else if ((bcs->hw.isar.state == STFAX_ACTIV) && + (bcs->hw.isar.cmd == PCTRL_CMD_FTM) && + (bcs->hw.isar.mod == para)) { + ll_deliver_faxstat(bcs, ISDN_FAX_CLASS1_CONNECT); + } else { + bcs->hw.isar.newmod = para; + bcs->hw.isar.newcmd = PCTRL_CMD_FTM; + nom = 0; + ctrl = PCTRL_CMD_ESC; + bcs->hw.isar.state = STFAX_ESCAPE; + } + break; + case ISDN_FAX_CLASS1_FTH: + test_and_clear_bit(BC_FLG_FRH_WAIT, &bcs->Flag); + if (bcs->hw.isar.state == STFAX_READY) { + p1 = para; + ctrl = PCTRL_CMD_FTH; + nom = 1; + bcs->hw.isar.state = STFAX_LINE; + bcs->hw.isar.cmd = ctrl; + bcs->hw.isar.mod = para; + bcs->hw.isar.newmod = 0; + bcs->hw.isar.newcmd = 0; + bcs->hw.isar.try_mod = 3; + } else if ((bcs->hw.isar.state == STFAX_ACTIV) && + (bcs->hw.isar.cmd == PCTRL_CMD_FTH) && + (bcs->hw.isar.mod == para)) { + ll_deliver_faxstat(bcs, ISDN_FAX_CLASS1_CONNECT); + } else { + bcs->hw.isar.newmod = para; + bcs->hw.isar.newcmd = PCTRL_CMD_FTH; + nom = 0; + ctrl = PCTRL_CMD_ESC; + bcs->hw.isar.state = STFAX_ESCAPE; + } + break; + case ISDN_FAX_CLASS1_FRM: + test_and_clear_bit(BC_FLG_FRH_WAIT, &bcs->Flag); + if (bcs->hw.isar.state == STFAX_READY) { + p1 = para; + ctrl = PCTRL_CMD_FRM; + nom = 1; + bcs->hw.isar.state = STFAX_LINE; + bcs->hw.isar.cmd = ctrl; + bcs->hw.isar.mod = para; + bcs->hw.isar.newmod = 0; + bcs->hw.isar.newcmd = 0; + bcs->hw.isar.try_mod = 3; + } else if ((bcs->hw.isar.state == STFAX_ACTIV) && + (bcs->hw.isar.cmd == PCTRL_CMD_FRM) && + (bcs->hw.isar.mod == para)) { + ll_deliver_faxstat(bcs, ISDN_FAX_CLASS1_CONNECT); + } else { + bcs->hw.isar.newmod = para; + bcs->hw.isar.newcmd = PCTRL_CMD_FRM; + nom = 0; + ctrl = PCTRL_CMD_ESC; + bcs->hw.isar.state = STFAX_ESCAPE; + } + break; + case ISDN_FAX_CLASS1_FRH: + test_and_set_bit(BC_FLG_FRH_WAIT, &bcs->Flag); + if (bcs->hw.isar.state == STFAX_READY) { + p1 = para; + ctrl = PCTRL_CMD_FRH; + nom = 1; + bcs->hw.isar.state = STFAX_LINE; + bcs->hw.isar.cmd = ctrl; + bcs->hw.isar.mod = para; + bcs->hw.isar.newmod = 0; + bcs->hw.isar.newcmd = 0; + bcs->hw.isar.try_mod = 3; + } else if ((bcs->hw.isar.state == STFAX_ACTIV) && + (bcs->hw.isar.cmd == PCTRL_CMD_FRH) && + (bcs->hw.isar.mod == para)) { + ll_deliver_faxstat(bcs, ISDN_FAX_CLASS1_CONNECT); + } else { + bcs->hw.isar.newmod = para; + bcs->hw.isar.newcmd = PCTRL_CMD_FRH; + nom = 0; + ctrl = PCTRL_CMD_ESC; + bcs->hw.isar.state = STFAX_ESCAPE; + } + break; + case ISDN_FAXPUMP_HALT: + bcs->hw.isar.state = STFAX_NULL; + nom = 0; + ctrl = PCTRL_CMD_HALT; + break; + } + if (ctrl) + sendmsg(cs, dps | ISAR_HIS_PUMPCTRL, ctrl, nom, &p1); +} + +void +isar_setup(struct IsdnCardState *cs) +{ + u_char msg; + int i; + + /* Dpath 1, 2 */ + msg = 61; + for (i=0; i<2; i++) { + /* Buffer Config */ + sendmsg(cs, (i ? ISAR_HIS_DPS2 : ISAR_HIS_DPS1) | + ISAR_HIS_P12CFG, 4, 1, &msg); + cs->bcs[i].hw.isar.mml = msg; + cs->bcs[i].mode = 0; + cs->bcs[i].hw.isar.dpath = i + 1; + modeisar(&cs->bcs[i], 0, 0); + INIT_WORK(&cs->bcs[i].tqueue, (void *)(void *) isar_bh, &cs->bcs[i]); + } +} + +void +isar_l2l1(struct PStack *st, int pr, void *arg) +{ + struct BCState *bcs = st->l1.bcs; + struct sk_buff *skb = arg; + int ret; + u_long flags; + + switch (pr) { + case (PH_DATA | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + skb_queue_tail(&bcs->squeue, skb); + } else { + bcs->tx_skb = skb; + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs, "DRQ set BC_FLG_BUSY"); + bcs->hw.isar.txcnt = 0; + bcs->cs->BC_Send_Data(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | INDICATION): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + printk(KERN_WARNING "isar_l2l1: this shouldn't happen\n"); + } else { + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs, "PUI set BC_FLG_BUSY"); + bcs->tx_skb = skb; + bcs->hw.isar.txcnt = 0; + bcs->cs->BC_Send_Data(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | REQUEST): + if (!bcs->tx_skb) { + test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } else + test_and_set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + case (PH_ACTIVATE | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + test_and_set_bit(BC_FLG_ACTIV, &bcs->Flag); + bcs->hw.isar.conmsg[0] = 0; + if (test_bit(FLG_ORIG, &st->l2.flag)) + test_and_set_bit(BC_FLG_ORIG, &bcs->Flag); + else + test_and_clear_bit(BC_FLG_ORIG, &bcs->Flag); + switch(st->l1.mode) { + case L1_MODE_TRANS: + case L1_MODE_HDLC: + ret = modeisar(bcs, st->l1.mode, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + if (ret) + l1_msg_b(st, PH_DEACTIVATE | REQUEST, arg); + else + l1_msg_b(st, PH_ACTIVATE | REQUEST, arg); + break; + case L1_MODE_V32: + case L1_MODE_FAX: + ret = modeisar(bcs, st->l1.mode, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + if (ret) + l1_msg_b(st, PH_DEACTIVATE | REQUEST, arg); + break; + default: + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + } + break; + case (PH_DEACTIVATE | REQUEST): + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | CONFIRM): + spin_lock_irqsave(&bcs->cs->lock, flags); + switch(st->l1.mode) { + case L1_MODE_TRANS: + case L1_MODE_HDLC: + case L1_MODE_V32: + break; + case L1_MODE_FAX: + isar_pump_cmd(bcs, ISDN_FAXPUMP_HALT, 0); + break; + } + test_and_clear_bit(BC_FLG_ACTIV, &bcs->Flag); + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs, "PDAC clear BC_FLG_BUSY"); + modeisar(bcs, 0, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + st->l1.l1l2(st, PH_DEACTIVATE | CONFIRM, NULL); + break; + } +} + +void +close_isarstate(struct BCState *bcs) +{ + modeisar(bcs, 0, bcs->channel); + if (test_and_clear_bit(BC_FLG_INIT, &bcs->Flag)) { + if (bcs->hw.isar.rcvbuf) { + kfree(bcs->hw.isar.rcvbuf); + bcs->hw.isar.rcvbuf = NULL; + } + skb_queue_purge(&bcs->rqueue); + skb_queue_purge(&bcs->squeue); + if (bcs->tx_skb) { + dev_kfree_skb_any(bcs->tx_skb); + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs, "closeisar clear BC_FLG_BUSY"); + } + } + del_timer(&bcs->hw.isar.ftimer); +} + +int +open_isarstate(struct IsdnCardState *cs, struct BCState *bcs) +{ + if (!test_and_set_bit(BC_FLG_INIT, &bcs->Flag)) { + if (!(bcs->hw.isar.rcvbuf = kmalloc(HSCX_BUFMAX, GFP_ATOMIC))) { + printk(KERN_WARNING + "HiSax: No memory for isar.rcvbuf\n"); + return (1); + } + skb_queue_head_init(&bcs->rqueue); + skb_queue_head_init(&bcs->squeue); + } + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "openisar clear BC_FLG_BUSY"); + bcs->event = 0; + bcs->hw.isar.rcvidx = 0; + bcs->tx_cnt = 0; + return (0); +} + +int +setstack_isar(struct PStack *st, struct BCState *bcs) +{ + bcs->channel = st->l1.bc; + if (open_isarstate(st->l1.hardware, bcs)) + return (-1); + st->l1.bcs = bcs; + st->l2.l2l1 = isar_l2l1; + setstack_manager(st); + bcs->st = st; + setstack_l1_B(st); + return (0); +} + +int +isar_auxcmd(struct IsdnCardState *cs, isdn_ctrl *ic) { + u_long adr; + int features, i; + struct BCState *bcs; + + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "isar_auxcmd cmd/ch %x/%d", ic->command, ic->arg); + switch (ic->command) { + case (ISDN_CMD_FAXCMD): + bcs = cs->channel[ic->arg].bcs; + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "isar_auxcmd cmd/subcmd %d/%d", + ic->parm.aux.cmd, ic->parm.aux.subcmd); + switch(ic->parm.aux.cmd) { + case ISDN_FAX_CLASS1_CTRL: + if (ic->parm.aux.subcmd == ETX) + test_and_set_bit(BC_FLG_DLEETX, + &bcs->Flag); + break; + case ISDN_FAX_CLASS1_FTS: + if (ic->parm.aux.subcmd == AT_QUERY) { + ic->command = ISDN_STAT_FAXIND; + ic->parm.aux.cmd = ISDN_FAX_CLASS1_OK; + cs->iif.statcallb(ic); + return(0); + } else if (ic->parm.aux.subcmd == AT_EQ_QUERY) { + strcpy(ic->parm.aux.para, "0-255"); + ic->command = ISDN_STAT_FAXIND; + ic->parm.aux.cmd = ISDN_FAX_CLASS1_QUERY; + cs->iif.statcallb(ic); + return(0); + } else if (ic->parm.aux.subcmd == AT_EQ_VALUE) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "isar_auxcmd %s=%d", + FC1_CMD[ic->parm.aux.cmd], ic->parm.aux.para[0]); + if (bcs->hw.isar.state == STFAX_READY) { + if (! ic->parm.aux.para[0]) { + ic->command = ISDN_STAT_FAXIND; + ic->parm.aux.cmd = ISDN_FAX_CLASS1_OK; + cs->iif.statcallb(ic); + return(0); + } + if (! test_and_set_bit(BC_FLG_FTI_RUN, &bcs->Flag)) { + /* n*10 ms */ + bcs->hw.isar.ftimer.expires = + jiffies + ((ic->parm.aux.para[0] * 10 * HZ)/1000); + test_and_set_bit(BC_FLG_FTI_FTS, &bcs->Flag); + add_timer(&bcs->hw.isar.ftimer); + return(0); + } else { + if (cs->debug) + debugl1(cs, "isar FTS=%d and FTI busy", + ic->parm.aux.para[0]); + } + } else { + if (cs->debug) + debugl1(cs, "isar FTS=%d and isar.state not ready(%x)", + ic->parm.aux.para[0],bcs->hw.isar.state); + } + ic->command = ISDN_STAT_FAXIND; + ic->parm.aux.cmd = ISDN_FAX_CLASS1_ERROR; + cs->iif.statcallb(ic); + } + break; + case ISDN_FAX_CLASS1_FRM: + case ISDN_FAX_CLASS1_FRH: + case ISDN_FAX_CLASS1_FTM: + case ISDN_FAX_CLASS1_FTH: + if (ic->parm.aux.subcmd == AT_QUERY) { + sprintf(ic->parm.aux.para, + "%d", bcs->hw.isar.mod); + ic->command = ISDN_STAT_FAXIND; + ic->parm.aux.cmd = ISDN_FAX_CLASS1_QUERY; + cs->iif.statcallb(ic); + return(0); + } else if (ic->parm.aux.subcmd == AT_EQ_QUERY) { + char *p = ic->parm.aux.para; + for(i=0;i<FAXMODCNT;i++) + if ((1<<i) & modmask) + p += sprintf(p, "%d,", faxmodulation[i]); + p--; + *p=0; + ic->command = ISDN_STAT_FAXIND; + ic->parm.aux.cmd = ISDN_FAX_CLASS1_QUERY; + cs->iif.statcallb(ic); + return(0); + } else if (ic->parm.aux.subcmd == AT_EQ_VALUE) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "isar_auxcmd %s=%d", + FC1_CMD[ic->parm.aux.cmd], ic->parm.aux.para[0]); + for(i=0;i<FAXMODCNT;i++) + if (faxmodulation[i]==ic->parm.aux.para[0]) + break; + if ((i < FAXMODCNT) && ((1<<i) & modmask) && + test_bit(BC_FLG_INIT, &bcs->Flag)) { + isar_pump_cmd(bcs, + ic->parm.aux.cmd, + ic->parm.aux.para[0]); + return(0); + } + } + /* wrong modulation or not activ */ + /* fall through */ + default: + ic->command = ISDN_STAT_FAXIND; + ic->parm.aux.cmd = ISDN_FAX_CLASS1_ERROR; + cs->iif.statcallb(ic); + } + break; + case (ISDN_CMD_IOCTL): + switch (ic->arg) { + case 9: /* load firmware */ + features = ISDN_FEATURE_L2_MODEM | + ISDN_FEATURE_L2_FAX | + ISDN_FEATURE_L3_FCLASS1; + memcpy(&adr, ic->parm.num, sizeof(ulong)); + if (isar_load_firmware(cs, (u_char __user *)adr)) + return(1); + else + ll_run(cs, features); + break; + case 20: + features = *(unsigned int *) ic->parm.num; + printk(KERN_DEBUG "HiSax: max modulation old(%04x) new(%04x)\n", + modmask, features); + modmask = features; + break; + case 21: + features = *(unsigned int *) ic->parm.num; + printk(KERN_DEBUG "HiSax: FRM extra delay old(%d) new(%d) ms\n", + frm_extra_delay, features); + if (features >= 0) + frm_extra_delay = features; + break; + case 22: + features = *(unsigned int *) ic->parm.num; + printk(KERN_DEBUG "HiSax: TOA old(%d) new(%d) db\n", + para_TOA, features); + if (features >= 0 && features < 32) + para_TOA = features; + break; + default: + printk(KERN_DEBUG "HiSax: invalid ioctl %d\n", + (int) ic->arg); + return(-EINVAL); + } + break; + default: + return(-EINVAL); + } + return(0); +} + +void __devinit +initisar(struct IsdnCardState *cs) +{ + cs->bcs[0].BC_SetStack = setstack_isar; + cs->bcs[1].BC_SetStack = setstack_isar; + cs->bcs[0].BC_Close = close_isarstate; + cs->bcs[1].BC_Close = close_isarstate; + cs->bcs[0].hw.isar.ftimer.function = (void *) ftimer_handler; + cs->bcs[0].hw.isar.ftimer.data = (long) &cs->bcs[0]; + init_timer(&cs->bcs[0].hw.isar.ftimer); + cs->bcs[1].hw.isar.ftimer.function = (void *) ftimer_handler; + cs->bcs[1].hw.isar.ftimer.data = (long) &cs->bcs[1]; + init_timer(&cs->bcs[1].hw.isar.ftimer); +} diff --git a/drivers/isdn/hisax/isar.h b/drivers/isdn/hisax/isar.h new file mode 100644 index 000000000000..bf7676586392 --- /dev/null +++ b/drivers/isdn/hisax/isar.h @@ -0,0 +1,222 @@ +/* $Id: isar.h,v 1.11.2.2 2004/01/12 22:52:27 keil Exp $ + * + * ISAR (Siemens PSB 7110) specific defines + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#define ISAR_IRQMSK 0x04 +#define ISAR_IRQSTA 0x04 +#define ISAR_IRQBIT 0x75 +#define ISAR_CTRL_H 0x61 +#define ISAR_CTRL_L 0x60 +#define ISAR_IIS 0x58 +#define ISAR_IIA 0x58 +#define ISAR_HIS 0x50 +#define ISAR_HIA 0x50 +#define ISAR_MBOX 0x4c +#define ISAR_WADR 0x4a +#define ISAR_RADR 0x48 + +#define ISAR_HIS_VNR 0x14 +#define ISAR_HIS_DKEY 0x02 +#define ISAR_HIS_FIRM 0x1e +#define ISAR_HIS_STDSP 0x08 +#define ISAR_HIS_DIAG 0x05 +#define ISAR_HIS_WAITSTATE 0x27 +#define ISAR_HIS_TIMERIRQ 0x25 +#define ISAR_HIS_P0CFG 0x3c +#define ISAR_HIS_P12CFG 0x24 +#define ISAR_HIS_SARTCFG 0x25 +#define ISAR_HIS_PUMPCFG 0x26 +#define ISAR_HIS_PUMPCTRL 0x2a +#define ISAR_HIS_IOM2CFG 0x27 +#define ISAR_HIS_IOM2REQ 0x07 +#define ISAR_HIS_IOM2CTRL 0x2b +#define ISAR_HIS_BSTREQ 0x0c +#define ISAR_HIS_PSTREQ 0x0e +#define ISAR_HIS_SDATA 0x20 +#define ISAR_HIS_DPS1 0x40 +#define ISAR_HIS_DPS2 0x80 +#define SET_DPS(x) ((x<<6) & 0xc0) + +#define ISAR_CMD_TIMERIRQ_OFF 0x20 +#define ISAR_CMD_TIMERIRQ_ON 0x21 + + +#define ISAR_IIS_MSCMSD 0x3f +#define ISAR_IIS_VNR 0x15 +#define ISAR_IIS_DKEY 0x03 +#define ISAR_IIS_FIRM 0x1f +#define ISAR_IIS_STDSP 0x09 +#define ISAR_IIS_DIAG 0x25 +#define ISAR_IIS_GSTEV 0x00 +#define ISAR_IIS_BSTEV 0x28 +#define ISAR_IIS_BSTRSP 0x2c +#define ISAR_IIS_PSTRSP 0x2e +#define ISAR_IIS_PSTEV 0x2a +#define ISAR_IIS_IOM2RSP 0x27 +#define ISAR_IIS_RDATA 0x20 +#define ISAR_IIS_INVMSG 0x3f + +#define ISAR_CTRL_SWVER 0x10 +#define ISAR_CTRL_STST 0x40 + +#define ISAR_MSG_HWVER {0x20, 0, 1} + +#define ISAR_DP1_USE 1 +#define ISAR_DP2_USE 2 +#define ISAR_RATE_REQ 3 + +#define PMOD_DISABLE 0 +#define PMOD_FAX 1 +#define PMOD_DATAMODEM 2 +#define PMOD_HALFDUPLEX 3 +#define PMOD_V110 4 +#define PMOD_DTMF 5 +#define PMOD_DTMF_TRANS 6 +#define PMOD_BYPASS 7 + +#define PCTRL_ORIG 0x80 +#define PV32P2_V23R 0x40 +#define PV32P2_V22A 0x20 +#define PV32P2_V22B 0x10 +#define PV32P2_V22C 0x08 +#define PV32P2_V21 0x02 +#define PV32P2_BEL 0x01 + +// LSB MSB in ISAR doc wrong !!! Arghhh +#define PV32P3_AMOD 0x80 +#define PV32P3_V32B 0x02 +#define PV32P3_V23B 0x01 +#define PV32P4_48 0x11 +#define PV32P5_48 0x05 +#define PV32P4_UT48 0x11 +#define PV32P5_UT48 0x0d +#define PV32P4_96 0x11 +#define PV32P5_96 0x03 +#define PV32P4_UT96 0x11 +#define PV32P5_UT96 0x0f +#define PV32P4_B96 0x91 +#define PV32P5_B96 0x0b +#define PV32P4_UTB96 0xd1 +#define PV32P5_UTB96 0x0f +#define PV32P4_120 0xb1 +#define PV32P5_120 0x09 +#define PV32P4_UT120 0xf1 +#define PV32P5_UT120 0x0f +#define PV32P4_144 0x99 +#define PV32P5_144 0x09 +#define PV32P4_UT144 0xf9 +#define PV32P5_UT144 0x0f +#define PV32P6_CTN 0x01 +#define PV32P6_ATN 0x02 + +#define PFAXP2_CTN 0x01 +#define PFAXP2_ATN 0x04 + +#define PSEV_10MS_TIMER 0x02 +#define PSEV_CON_ON 0x18 +#define PSEV_CON_OFF 0x19 +#define PSEV_V24_OFF 0x20 +#define PSEV_CTS_ON 0x21 +#define PSEV_CTS_OFF 0x22 +#define PSEV_DCD_ON 0x23 +#define PSEV_DCD_OFF 0x24 +#define PSEV_DSR_ON 0x25 +#define PSEV_DSR_OFF 0x26 +#define PSEV_REM_RET 0xcc +#define PSEV_REM_REN 0xcd +#define PSEV_GSTN_CLR 0xd4 + +#define PSEV_RSP_READY 0xbc +#define PSEV_LINE_TX_H 0xb3 +#define PSEV_LINE_TX_B 0xb2 +#define PSEV_LINE_RX_H 0xb1 +#define PSEV_LINE_RX_B 0xb0 +#define PSEV_RSP_CONN 0xb5 +#define PSEV_RSP_DISC 0xb7 +#define PSEV_RSP_FCERR 0xb9 +#define PSEV_RSP_SILDET 0xbe +#define PSEV_RSP_SILOFF 0xab +#define PSEV_FLAGS_DET 0xba + +#define PCTRL_CMD_FTH 0xa7 +#define PCTRL_CMD_FRH 0xa5 +#define PCTRL_CMD_FTM 0xa8 +#define PCTRL_CMD_FRM 0xa6 +#define PCTRL_CMD_SILON 0xac +#define PCTRL_CMD_CONT 0xa2 +#define PCTRL_CMD_ESC 0xa4 +#define PCTRL_CMD_SILOFF 0xab +#define PCTRL_CMD_HALT 0xa9 + +#define PCTRL_LOC_RET 0xcf +#define PCTRL_LOC_REN 0xce + +#define SMODE_DISABLE 0 +#define SMODE_V14 2 +#define SMODE_HDLC 3 +#define SMODE_BINARY 4 +#define SMODE_FSK_V14 5 + +#define SCTRL_HDMC_BOTH 0x00 +#define SCTRL_HDMC_DTX 0x80 +#define SCTRL_HDMC_DRX 0x40 +#define S_P1_OVSP 0x40 +#define S_P1_SNP 0x20 +#define S_P1_EOP 0x10 +#define S_P1_EDP 0x08 +#define S_P1_NSB 0x04 +#define S_P1_CHS_8 0x03 +#define S_P1_CHS_7 0x02 +#define S_P1_CHS_6 0x01 +#define S_P1_CHS_5 0x00 + +#define S_P2_BFT_DEF 0x10 + +#define IOM_CTRL_ENA 0x80 +#define IOM_CTRL_NOPCM 0x00 +#define IOM_CTRL_ALAW 0x02 +#define IOM_CTRL_ULAW 0x04 +#define IOM_CTRL_RCV 0x01 + +#define IOM_P1_TXD 0x10 + +#define HDLC_FED 0x40 +#define HDLC_FSD 0x20 +#define HDLC_FST 0x20 +#define HDLC_ERROR 0x1c +#define HDLC_ERR_FAD 0x10 +#define HDLC_ERR_RER 0x08 +#define HDLC_ERR_CER 0x04 +#define SART_NMD 0x01 + +#define BSTAT_RDM0 0x1 +#define BSTAT_RDM1 0x2 +#define BSTAT_RDM2 0x4 +#define BSTAT_RDM3 0x8 +#define BSTEV_TBO 0x1f +#define BSTEV_RBO 0x2f + +/* FAX State Machine */ +#define STFAX_NULL 0 +#define STFAX_READY 1 +#define STFAX_LINE 2 +#define STFAX_CONT 3 +#define STFAX_ACTIV 4 +#define STFAX_ESCAPE 5 +#define STFAX_SILDET 6 + +#define ISDN_FAXPUMP_HALT 100 + +extern int ISARVersion(struct IsdnCardState *cs, char *s); +extern void isar_int_main(struct IsdnCardState *cs); +extern void initisar(struct IsdnCardState *cs); +extern void isar_fill_fifo(struct BCState *bcs); +extern int isar_auxcmd(struct IsdnCardState *cs, isdn_ctrl *ic); diff --git a/drivers/isdn/hisax/isdnhdlc.c b/drivers/isdn/hisax/isdnhdlc.c new file mode 100644 index 000000000000..cbdf54c5af84 --- /dev/null +++ b/drivers/isdn/hisax/isdnhdlc.c @@ -0,0 +1,628 @@ +/* + * isdnhdlc.c -- General purpose ISDN HDLC decoder. + * + *Copyright (C) 2002 Wolfgang Mües <wolfgang@iksw-muees.de> + * 2001 Frode Isaksen <fisaksen@bewan.com> + * 2001 Kai Germaschewski <kai.germaschewski@gmx.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 of the License, 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. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/crc-ccitt.h> +#include "isdnhdlc.h" + +/*-------------------------------------------------------------------*/ + +MODULE_AUTHOR("Wolfgang Mües <wolfgang@iksw-muees.de>, " + "Frode Isaksen <fisaksen@bewan.com>, " + "Kai Germaschewski <kai.germaschewski@gmx.de>"); +MODULE_DESCRIPTION("General purpose ISDN HDLC decoder"); +MODULE_LICENSE("GPL"); + +/*-------------------------------------------------------------------*/ + +/* bit swap table. + * Very handy for devices with different bit order, + * and neccessary for each transparent B-channel access for all + * devices which works with this HDLC decoder without bit reversal. + */ +const unsigned char isdnhdlc_bit_rev_tab[256] = { + 0x00,0x80,0x40,0xC0,0x20,0xA0,0x60,0xE0,0x10,0x90,0x50,0xD0,0x30,0xB0,0x70,0xF0, + 0x08,0x88,0x48,0xC8,0x28,0xA8,0x68,0xE8,0x18,0x98,0x58,0xD8,0x38,0xB8,0x78,0xF8, + 0x04,0x84,0x44,0xC4,0x24,0xA4,0x64,0xE4,0x14,0x94,0x54,0xD4,0x34,0xB4,0x74,0xF4, + 0x0C,0x8C,0x4C,0xCC,0x2C,0xAC,0x6C,0xEC,0x1C,0x9C,0x5C,0xDC,0x3C,0xBC,0x7C,0xFC, + 0x02,0x82,0x42,0xC2,0x22,0xA2,0x62,0xE2,0x12,0x92,0x52,0xD2,0x32,0xB2,0x72,0xF2, + 0x0A,0x8A,0x4A,0xCA,0x2A,0xAA,0x6A,0xEA,0x1A,0x9A,0x5A,0xDA,0x3A,0xBA,0x7A,0xFA, + 0x06,0x86,0x46,0xC6,0x26,0xA6,0x66,0xE6,0x16,0x96,0x56,0xD6,0x36,0xB6,0x76,0xF6, + 0x0E,0x8E,0x4E,0xCE,0x2E,0xAE,0x6E,0xEE,0x1E,0x9E,0x5E,0xDE,0x3E,0xBE,0x7E,0xFE, + 0x01,0x81,0x41,0xC1,0x21,0xA1,0x61,0xE1,0x11,0x91,0x51,0xD1,0x31,0xB1,0x71,0xF1, + 0x09,0x89,0x49,0xC9,0x29,0xA9,0x69,0xE9,0x19,0x99,0x59,0xD9,0x39,0xB9,0x79,0xF9, + 0x05,0x85,0x45,0xC5,0x25,0xA5,0x65,0xE5,0x15,0x95,0x55,0xD5,0x35,0xB5,0x75,0xF5, + 0x0D,0x8D,0x4D,0xCD,0x2D,0xAD,0x6D,0xED,0x1D,0x9D,0x5D,0xDD,0x3D,0xBD,0x7D,0xFD, + 0x03,0x83,0x43,0xC3,0x23,0xA3,0x63,0xE3,0x13,0x93,0x53,0xD3,0x33,0xB3,0x73,0xF3, + 0x0B,0x8B,0x4B,0xCB,0x2B,0xAB,0x6B,0xEB,0x1B,0x9B,0x5B,0xDB,0x3B,0xBB,0x7B,0xFB, + 0x07,0x87,0x47,0xC7,0x27,0xA7,0x67,0xE7,0x17,0x97,0x57,0xD7,0x37,0xB7,0x77,0xF7, + 0x0F,0x8F,0x4F,0xCF,0x2F,0xAF,0x6F,0xEF,0x1F,0x9F,0x5F,0xDF,0x3F,0xBF,0x7F,0xFF +}; + +enum { + HDLC_FAST_IDLE,HDLC_GET_FLAG_B0,HDLC_GETFLAG_B1A6,HDLC_GETFLAG_B7, + HDLC_GET_DATA,HDLC_FAST_FLAG +}; + +enum { + HDLC_SEND_DATA,HDLC_SEND_CRC1,HDLC_SEND_FAST_FLAG, + HDLC_SEND_FIRST_FLAG,HDLC_SEND_CRC2,HDLC_SEND_CLOSING_FLAG, + HDLC_SEND_IDLE1,HDLC_SEND_FAST_IDLE,HDLC_SENDFLAG_B0, + HDLC_SENDFLAG_B1A6,HDLC_SENDFLAG_B7,STOPPED +}; + +void isdnhdlc_rcv_init (struct isdnhdlc_vars *hdlc, int do_adapt56) +{ + hdlc->bit_shift = 0; + hdlc->hdlc_bits1 = 0; + hdlc->data_bits = 0; + hdlc->ffbit_shift = 0; + hdlc->data_received = 0; + hdlc->state = HDLC_GET_DATA; + hdlc->do_adapt56 = do_adapt56; + hdlc->dchannel = 0; + hdlc->crc = 0; + hdlc->cbin = 0; + hdlc->shift_reg = 0; + hdlc->ffvalue = 0; + hdlc->dstpos = 0; +} + +void isdnhdlc_out_init (struct isdnhdlc_vars *hdlc, int is_d_channel, int do_adapt56) +{ + hdlc->bit_shift = 0; + hdlc->hdlc_bits1 = 0; + hdlc->data_bits = 0; + hdlc->ffbit_shift = 0; + hdlc->data_received = 0; + hdlc->do_closing = 0; + hdlc->ffvalue = 0; + if (is_d_channel) { + hdlc->dchannel = 1; + hdlc->state = HDLC_SEND_FIRST_FLAG; + } else { + hdlc->dchannel = 0; + hdlc->state = HDLC_SEND_FAST_FLAG; + hdlc->ffvalue = 0x7e; + } + hdlc->cbin = 0x7e; + hdlc->bit_shift = 0; + if(do_adapt56){ + hdlc->do_adapt56 = 1; + hdlc->data_bits = 0; + hdlc->state = HDLC_SENDFLAG_B0; + } else { + hdlc->do_adapt56 = 0; + hdlc->data_bits = 8; + } + hdlc->shift_reg = 0; +} + +/* + isdnhdlc_decode - decodes HDLC frames from a transparent bit stream. + + The source buffer is scanned for valid HDLC frames looking for + flags (01111110) to indicate the start of a frame. If the start of + the frame is found, the bit stuffing is removed (0 after 5 1's). + When a new flag is found, the complete frame has been received + and the CRC is checked. + If a valid frame is found, the function returns the frame length + excluding the CRC with the bit HDLC_END_OF_FRAME set. + If the beginning of a valid frame is found, the function returns + the length. + If a framing error is found (too many 1s and not a flag) the function + returns the length with the bit HDLC_FRAMING_ERROR set. + If a CRC error is found the function returns the length with the + bit HDLC_CRC_ERROR set. + If the frame length exceeds the destination buffer size, the function + returns the length with the bit HDLC_LENGTH_ERROR set. + + src - source buffer + slen - source buffer length + count - number of bytes removed (decoded) from the source buffer + dst _ destination buffer + dsize - destination buffer size + returns - number of decoded bytes in the destination buffer and status + flag. + */ +int isdnhdlc_decode (struct isdnhdlc_vars *hdlc, const unsigned char *src, + int slen, int *count, unsigned char *dst, int dsize) +{ + int status=0; + + static const unsigned char fast_flag[]={ + 0x00,0x00,0x00,0x20,0x30,0x38,0x3c,0x3e,0x3f + }; + + static const unsigned char fast_flag_value[]={ + 0x00,0x7e,0xfc,0xf9,0xf3,0xe7,0xcf,0x9f,0x3f + }; + + static const unsigned char fast_abort[]={ + 0x00,0x00,0x80,0xc0,0xe0,0xf0,0xf8,0xfc,0xfe,0xff + }; + + *count = slen; + + while(slen > 0){ + if(hdlc->bit_shift==0){ + hdlc->cbin = *src++; + slen--; + hdlc->bit_shift = 8; + if(hdlc->do_adapt56){ + hdlc->bit_shift --; + } + } + + switch(hdlc->state){ + case STOPPED: + return 0; + case HDLC_FAST_IDLE: + if(hdlc->cbin == 0xff){ + hdlc->bit_shift = 0; + break; + } + hdlc->state = HDLC_GET_FLAG_B0; + hdlc->hdlc_bits1 = 0; + hdlc->bit_shift = 8; + break; + case HDLC_GET_FLAG_B0: + if(!(hdlc->cbin & 0x80)) { + hdlc->state = HDLC_GETFLAG_B1A6; + hdlc->hdlc_bits1 = 0; + } else { + if(!hdlc->do_adapt56){ + if(++hdlc->hdlc_bits1 >=8 ) if(hdlc->bit_shift==1) + hdlc->state = HDLC_FAST_IDLE; + } + } + hdlc->cbin<<=1; + hdlc->bit_shift --; + break; + case HDLC_GETFLAG_B1A6: + if(hdlc->cbin & 0x80){ + hdlc->hdlc_bits1++; + if(hdlc->hdlc_bits1==6){ + hdlc->state = HDLC_GETFLAG_B7; + } + } else { + hdlc->hdlc_bits1 = 0; + } + hdlc->cbin<<=1; + hdlc->bit_shift --; + break; + case HDLC_GETFLAG_B7: + if(hdlc->cbin & 0x80) { + hdlc->state = HDLC_GET_FLAG_B0; + } else { + hdlc->state = HDLC_GET_DATA; + hdlc->crc = 0xffff; + hdlc->shift_reg = 0; + hdlc->hdlc_bits1 = 0; + hdlc->data_bits = 0; + hdlc->data_received = 0; + } + hdlc->cbin<<=1; + hdlc->bit_shift --; + break; + case HDLC_GET_DATA: + if(hdlc->cbin & 0x80){ + hdlc->hdlc_bits1++; + switch(hdlc->hdlc_bits1){ + case 6: + break; + case 7: + if(hdlc->data_received) { + // bad frame + status = -HDLC_FRAMING_ERROR; + } + if(!hdlc->do_adapt56){ + if(hdlc->cbin==fast_abort[hdlc->bit_shift+1]){ + hdlc->state = HDLC_FAST_IDLE; + hdlc->bit_shift=1; + break; + } + } else { + hdlc->state = HDLC_GET_FLAG_B0; + } + break; + default: + hdlc->shift_reg>>=1; + hdlc->shift_reg |= 0x80; + hdlc->data_bits++; + break; + } + } else { + switch(hdlc->hdlc_bits1){ + case 5: + break; + case 6: + if(hdlc->data_received){ + if (hdlc->dstpos < 2) { + status = -HDLC_FRAMING_ERROR; + } else if (hdlc->crc != 0xf0b8){ + // crc error + status = -HDLC_CRC_ERROR; + } else { + // remove CRC + hdlc->dstpos -= 2; + // good frame + status = hdlc->dstpos; + } + } + hdlc->crc = 0xffff; + hdlc->shift_reg = 0; + hdlc->data_bits = 0; + if(!hdlc->do_adapt56){ + if(hdlc->cbin==fast_flag[hdlc->bit_shift]){ + hdlc->ffvalue = fast_flag_value[hdlc->bit_shift]; + hdlc->state = HDLC_FAST_FLAG; + hdlc->ffbit_shift = hdlc->bit_shift; + hdlc->bit_shift = 1; + } else { + hdlc->state = HDLC_GET_DATA; + hdlc->data_received = 0; + } + } else { + hdlc->state = HDLC_GET_DATA; + hdlc->data_received = 0; + } + break; + default: + hdlc->shift_reg>>=1; + hdlc->data_bits++; + break; + } + hdlc->hdlc_bits1 = 0; + } + if (status) { + hdlc->dstpos = 0; + *count -= slen; + hdlc->cbin <<= 1; + hdlc->bit_shift--; + return status; + } + if(hdlc->data_bits==8){ + hdlc->data_bits = 0; + hdlc->data_received = 1; + hdlc->crc = crc_ccitt_byte(hdlc->crc, hdlc->shift_reg); + + // good byte received + if (hdlc->dstpos < dsize) { + dst[hdlc->dstpos++] = hdlc->shift_reg; + } else { + // frame too long + status = -HDLC_LENGTH_ERROR; + hdlc->dstpos = 0; + } + } + hdlc->cbin <<= 1; + hdlc->bit_shift--; + break; + case HDLC_FAST_FLAG: + if(hdlc->cbin==hdlc->ffvalue){ + hdlc->bit_shift = 0; + break; + } else { + if(hdlc->cbin == 0xff){ + hdlc->state = HDLC_FAST_IDLE; + hdlc->bit_shift=0; + } else if(hdlc->ffbit_shift==8){ + hdlc->state = HDLC_GETFLAG_B7; + break; + } else { + hdlc->shift_reg = fast_abort[hdlc->ffbit_shift-1]; + hdlc->hdlc_bits1 = hdlc->ffbit_shift-2; + if(hdlc->hdlc_bits1<0)hdlc->hdlc_bits1 = 0; + hdlc->data_bits = hdlc->ffbit_shift-1; + hdlc->state = HDLC_GET_DATA; + hdlc->data_received = 0; + } + } + break; + default: + break; + } + } + *count -= slen; + return 0; +} + +/* + isdnhdlc_encode - encodes HDLC frames to a transparent bit stream. + + The bit stream starts with a beginning flag (01111110). After + that each byte is added to the bit stream with bit stuffing added + (0 after 5 1's). + When the last byte has been removed from the source buffer, the + CRC (2 bytes is added) and the frame terminates with the ending flag. + For the dchannel, the idle character (all 1's) is also added at the end. + If this function is called with empty source buffer (slen=0), flags or + idle character will be generated. + + src - source buffer + slen - source buffer length + count - number of bytes removed (encoded) from source buffer + dst _ destination buffer + dsize - destination buffer size + returns - number of encoded bytes in the destination buffer +*/ +int isdnhdlc_encode(struct isdnhdlc_vars *hdlc, const unsigned char *src, + unsigned short slen, int *count, + unsigned char *dst, int dsize) +{ + static const unsigned char xfast_flag_value[] = { + 0x7e,0x3f,0x9f,0xcf,0xe7,0xf3,0xf9,0xfc,0x7e + }; + + int len = 0; + + *count = slen; + + while (dsize > 0) { + if(hdlc->bit_shift==0){ + if(slen && !hdlc->do_closing){ + hdlc->shift_reg = *src++; + slen--; + if (slen == 0) + hdlc->do_closing = 1; /* closing sequence, CRC + flag(s) */ + hdlc->bit_shift = 8; + } else { + if(hdlc->state == HDLC_SEND_DATA){ + if(hdlc->data_received){ + hdlc->state = HDLC_SEND_CRC1; + hdlc->crc ^= 0xffff; + hdlc->bit_shift = 8; + hdlc->shift_reg = hdlc->crc & 0xff; + } else if(!hdlc->do_adapt56){ + hdlc->state = HDLC_SEND_FAST_FLAG; + } else { + hdlc->state = HDLC_SENDFLAG_B0; + } + } + + } + } + + switch(hdlc->state){ + case STOPPED: + while (dsize--) + *dst++ = 0xff; + + return dsize; + case HDLC_SEND_FAST_FLAG: + hdlc->do_closing = 0; + if(slen == 0){ + *dst++ = hdlc->ffvalue; + len++; + dsize--; + break; + } + if(hdlc->bit_shift==8){ + hdlc->cbin = hdlc->ffvalue>>(8-hdlc->data_bits); + hdlc->state = HDLC_SEND_DATA; + hdlc->crc = 0xffff; + hdlc->hdlc_bits1 = 0; + hdlc->data_received = 1; + } + break; + case HDLC_SENDFLAG_B0: + hdlc->do_closing = 0; + hdlc->cbin <<= 1; + hdlc->data_bits++; + hdlc->hdlc_bits1 = 0; + hdlc->state = HDLC_SENDFLAG_B1A6; + break; + case HDLC_SENDFLAG_B1A6: + hdlc->cbin <<= 1; + hdlc->data_bits++; + hdlc->cbin++; + if(++hdlc->hdlc_bits1 == 6) + hdlc->state = HDLC_SENDFLAG_B7; + break; + case HDLC_SENDFLAG_B7: + hdlc->cbin <<= 1; + hdlc->data_bits++; + if(slen == 0){ + hdlc->state = HDLC_SENDFLAG_B0; + break; + } + if(hdlc->bit_shift==8){ + hdlc->state = HDLC_SEND_DATA; + hdlc->crc = 0xffff; + hdlc->hdlc_bits1 = 0; + hdlc->data_received = 1; + } + break; + case HDLC_SEND_FIRST_FLAG: + hdlc->data_received = 1; + if(hdlc->data_bits==8){ + hdlc->state = HDLC_SEND_DATA; + hdlc->crc = 0xffff; + hdlc->hdlc_bits1 = 0; + break; + } + hdlc->cbin <<= 1; + hdlc->data_bits++; + if(hdlc->shift_reg & 0x01) + hdlc->cbin++; + hdlc->shift_reg >>= 1; + hdlc->bit_shift--; + if(hdlc->bit_shift==0){ + hdlc->state = HDLC_SEND_DATA; + hdlc->crc = 0xffff; + hdlc->hdlc_bits1 = 0; + } + break; + case HDLC_SEND_DATA: + hdlc->cbin <<= 1; + hdlc->data_bits++; + if(hdlc->hdlc_bits1 == 5){ + hdlc->hdlc_bits1 = 0; + break; + } + if(hdlc->bit_shift==8){ + hdlc->crc = crc_ccitt_byte(hdlc->crc, hdlc->shift_reg); + } + if(hdlc->shift_reg & 0x01){ + hdlc->hdlc_bits1++; + hdlc->cbin++; + hdlc->shift_reg >>= 1; + hdlc->bit_shift--; + } else { + hdlc->hdlc_bits1 = 0; + hdlc->shift_reg >>= 1; + hdlc->bit_shift--; + } + break; + case HDLC_SEND_CRC1: + hdlc->cbin <<= 1; + hdlc->data_bits++; + if(hdlc->hdlc_bits1 == 5){ + hdlc->hdlc_bits1 = 0; + break; + } + if(hdlc->shift_reg & 0x01){ + hdlc->hdlc_bits1++; + hdlc->cbin++; + hdlc->shift_reg >>= 1; + hdlc->bit_shift--; + } else { + hdlc->hdlc_bits1 = 0; + hdlc->shift_reg >>= 1; + hdlc->bit_shift--; + } + if(hdlc->bit_shift==0){ + hdlc->shift_reg = (hdlc->crc >> 8); + hdlc->state = HDLC_SEND_CRC2; + hdlc->bit_shift = 8; + } + break; + case HDLC_SEND_CRC2: + hdlc->cbin <<= 1; + hdlc->data_bits++; + if(hdlc->hdlc_bits1 == 5){ + hdlc->hdlc_bits1 = 0; + break; + } + if(hdlc->shift_reg & 0x01){ + hdlc->hdlc_bits1++; + hdlc->cbin++; + hdlc->shift_reg >>= 1; + hdlc->bit_shift--; + } else { + hdlc->hdlc_bits1 = 0; + hdlc->shift_reg >>= 1; + hdlc->bit_shift--; + } + if(hdlc->bit_shift==0){ + hdlc->shift_reg = 0x7e; + hdlc->state = HDLC_SEND_CLOSING_FLAG; + hdlc->bit_shift = 8; + } + break; + case HDLC_SEND_CLOSING_FLAG: + hdlc->cbin <<= 1; + hdlc->data_bits++; + if(hdlc->hdlc_bits1 == 5){ + hdlc->hdlc_bits1 = 0; + break; + } + if(hdlc->shift_reg & 0x01){ + hdlc->cbin++; + } + hdlc->shift_reg >>= 1; + hdlc->bit_shift--; + if(hdlc->bit_shift==0){ + hdlc->ffvalue = xfast_flag_value[hdlc->data_bits]; + if(hdlc->dchannel){ + hdlc->ffvalue = 0x7e; + hdlc->state = HDLC_SEND_IDLE1; + hdlc->bit_shift = 8-hdlc->data_bits; + if(hdlc->bit_shift==0) + hdlc->state = HDLC_SEND_FAST_IDLE; + } else { + if(!hdlc->do_adapt56){ + hdlc->state = HDLC_SEND_FAST_FLAG; + hdlc->data_received = 0; + } else { + hdlc->state = HDLC_SENDFLAG_B0; + hdlc->data_received = 0; + } + // Finished with this frame, send flags + if (dsize > 1) dsize = 1; + } + } + break; + case HDLC_SEND_IDLE1: + hdlc->do_closing = 0; + hdlc->cbin <<= 1; + hdlc->cbin++; + hdlc->data_bits++; + hdlc->bit_shift--; + if(hdlc->bit_shift==0){ + hdlc->state = HDLC_SEND_FAST_IDLE; + hdlc->bit_shift = 0; + } + break; + case HDLC_SEND_FAST_IDLE: + hdlc->do_closing = 0; + hdlc->cbin = 0xff; + hdlc->data_bits = 8; + if(hdlc->bit_shift == 8){ + hdlc->cbin = 0x7e; + hdlc->state = HDLC_SEND_FIRST_FLAG; + } else { + *dst++ = hdlc->cbin; + hdlc->bit_shift = hdlc->data_bits = 0; + len++; + dsize = 0; + } + break; + default: + break; + } + if(hdlc->do_adapt56){ + if(hdlc->data_bits==7){ + hdlc->cbin <<= 1; + hdlc->cbin++; + hdlc->data_bits++; + } + } + if(hdlc->data_bits==8){ + *dst++ = hdlc->cbin; + hdlc->data_bits = 0; + len++; + dsize--; + } + } + *count -= slen; + + return len; +} + +EXPORT_SYMBOL(isdnhdlc_bit_rev_tab); +EXPORT_SYMBOL(isdnhdlc_rcv_init); +EXPORT_SYMBOL(isdnhdlc_decode); +EXPORT_SYMBOL(isdnhdlc_out_init); +EXPORT_SYMBOL(isdnhdlc_encode); diff --git a/drivers/isdn/hisax/isdnhdlc.h b/drivers/isdn/hisax/isdnhdlc.h new file mode 100644 index 000000000000..269315988dc8 --- /dev/null +++ b/drivers/isdn/hisax/isdnhdlc.h @@ -0,0 +1,72 @@ +/* + * isdnhdlc.h -- General purpose ISDN HDLC decoder. + * + * Implementation of a HDLC decoder/encoder in software. + * Neccessary because some ISDN devices don't have HDLC + * controllers. Also included: a bit reversal table. + * + *Copyright (C) 2002 Wolfgang Mües <wolfgang@iksw-muees.de> + * 2001 Frode Isaksen <fisaksen@bewan.com> + * 2001 Kai Germaschewski <kai.germaschewski@gmx.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 of the License, 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. + */ + +#ifndef __ISDNHDLC_H__ +#define __ISDNHDLC_H__ + +struct isdnhdlc_vars { + int bit_shift; + int hdlc_bits1; + int data_bits; + int ffbit_shift; // encoding only + int state; + int dstpos; + + unsigned short crc; + + unsigned char cbin; + unsigned char shift_reg; + unsigned char ffvalue; + + int data_received:1; // set if transferring data + int dchannel:1; // set if D channel (send idle instead of flags) + int do_adapt56:1; // set if 56K adaptation + int do_closing:1; // set if in closing phase (need to send CRC + flag +}; + + +/* + The return value from isdnhdlc_decode is + the frame length, 0 if no complete frame was decoded, + or a negative error number +*/ +#define HDLC_FRAMING_ERROR 1 +#define HDLC_CRC_ERROR 2 +#define HDLC_LENGTH_ERROR 3 + +extern const unsigned char isdnhdlc_bit_rev_tab[256]; + +extern void isdnhdlc_rcv_init (struct isdnhdlc_vars *hdlc, int do_adapt56); + +extern int isdnhdlc_decode (struct isdnhdlc_vars *hdlc, const unsigned char *src, int slen,int *count, + unsigned char *dst, int dsize); + +extern void isdnhdlc_out_init (struct isdnhdlc_vars *hdlc,int is_d_channel,int do_adapt56); + +extern int isdnhdlc_encode (struct isdnhdlc_vars *hdlc,const unsigned char *src,unsigned short slen,int *count, + unsigned char *dst,int dsize); + +#endif /* __ISDNHDLC_H__ */ diff --git a/drivers/isdn/hisax/isdnl1.c b/drivers/isdn/hisax/isdnl1.c new file mode 100644 index 000000000000..4d08d27f1499 --- /dev/null +++ b/drivers/isdn/hisax/isdnl1.c @@ -0,0 +1,932 @@ +/* $Id: isdnl1.c,v 2.46.2.5 2004/02/11 13:21:34 keil Exp $ + * + * common low level stuff for Siemens Chipsetbased isdn cards + * + * Author Karsten Keil + * based on the teles driver from Jan den Ouden + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * For changes and modifications please read + * Documentation/isdn/HiSax.cert + * + * Thanks to Jan den Ouden + * Fritz Elfert + * Beat Doebeli + * + */ + +const char *l1_revision = "$Revision: 2.46.2.5 $"; + +#include <linux/init.h> +#include "hisax.h" +#include "isdnl1.h" + +#define TIMER3_VALUE 7000 + +static struct Fsm l1fsm_b; +static struct Fsm l1fsm_s; + +enum { + ST_L1_F2, + ST_L1_F3, + ST_L1_F4, + ST_L1_F5, + ST_L1_F6, + ST_L1_F7, + ST_L1_F8, +}; + +#define L1S_STATE_COUNT (ST_L1_F8+1) + +static char *strL1SState[] = +{ + "ST_L1_F2", + "ST_L1_F3", + "ST_L1_F4", + "ST_L1_F5", + "ST_L1_F6", + "ST_L1_F7", + "ST_L1_F8", +}; + +#ifdef HISAX_UINTERFACE +static +struct Fsm l1fsm_u = +{NULL, 0, 0, NULL, NULL}; + +enum { + ST_L1_RESET, + ST_L1_DEACT, + ST_L1_SYNC2, + ST_L1_TRANS, +}; + +#define L1U_STATE_COUNT (ST_L1_TRANS+1) + +static char *strL1UState[] = +{ + "ST_L1_RESET", + "ST_L1_DEACT", + "ST_L1_SYNC2", + "ST_L1_TRANS", +}; +#endif + +enum { + ST_L1_NULL, + ST_L1_WAIT_ACT, + ST_L1_WAIT_DEACT, + ST_L1_ACTIV, +}; + +#define L1B_STATE_COUNT (ST_L1_ACTIV+1) + +static char *strL1BState[] = +{ + "ST_L1_NULL", + "ST_L1_WAIT_ACT", + "ST_L1_WAIT_DEACT", + "ST_L1_ACTIV", +}; + +enum { + EV_PH_ACTIVATE, + EV_PH_DEACTIVATE, + EV_RESET_IND, + EV_DEACT_CNF, + EV_DEACT_IND, + EV_POWER_UP, + EV_RSYNC_IND, + EV_INFO2_IND, + EV_INFO4_IND, + EV_TIMER_DEACT, + EV_TIMER_ACT, + EV_TIMER3, +}; + +#define L1_EVENT_COUNT (EV_TIMER3 + 1) + +static char *strL1Event[] = +{ + "EV_PH_ACTIVATE", + "EV_PH_DEACTIVATE", + "EV_RESET_IND", + "EV_DEACT_CNF", + "EV_DEACT_IND", + "EV_POWER_UP", + "EV_RSYNC_IND", + "EV_INFO2_IND", + "EV_INFO4_IND", + "EV_TIMER_DEACT", + "EV_TIMER_ACT", + "EV_TIMER3", +}; + +void +debugl1(struct IsdnCardState *cs, char *fmt, ...) +{ + va_list args; + char tmp[8]; + + va_start(args, fmt); + sprintf(tmp, "Card%d ", cs->cardnr + 1); + VHiSax_putstatus(cs, tmp, fmt, args); + va_end(args); +} + +static void +l1m_debug(struct FsmInst *fi, char *fmt, ...) +{ + va_list args; + struct PStack *st = fi->userdata; + struct IsdnCardState *cs = st->l1.hardware; + char tmp[8]; + + va_start(args, fmt); + sprintf(tmp, "Card%d ", cs->cardnr + 1); + VHiSax_putstatus(cs, tmp, fmt, args); + va_end(args); +} + +void +L1activated(struct IsdnCardState *cs) +{ + struct PStack *st; + + st = cs->stlist; + while (st) { + if (test_and_clear_bit(FLG_L1_ACTIVATING, &st->l1.Flags)) + st->l1.l1l2(st, PH_ACTIVATE | CONFIRM, NULL); + else + st->l1.l1l2(st, PH_ACTIVATE | INDICATION, NULL); + st = st->next; + } +} + +void +L1deactivated(struct IsdnCardState *cs) +{ + struct PStack *st; + + st = cs->stlist; + while (st) { + if (test_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + st->l1.l1l2(st, PH_PAUSE | CONFIRM, NULL); + st->l1.l1l2(st, PH_DEACTIVATE | INDICATION, NULL); + st = st->next; + } + test_and_clear_bit(FLG_L1_DBUSY, &cs->HW_Flags); +} + +void +DChannel_proc_xmt(struct IsdnCardState *cs) +{ + struct PStack *stptr; + + if (cs->tx_skb) + return; + + stptr = cs->stlist; + while (stptr != NULL) { + if (test_and_clear_bit(FLG_L1_PULL_REQ, &stptr->l1.Flags)) { + stptr->l1.l1l2(stptr, PH_PULL | CONFIRM, NULL); + break; + } else + stptr = stptr->next; + } +} + +void +DChannel_proc_rcv(struct IsdnCardState *cs) +{ + struct sk_buff *skb, *nskb; + struct PStack *stptr = cs->stlist; + int found, tei, sapi; + + if (stptr) + if (test_bit(FLG_L1_ACTTIMER, &stptr->l1.Flags)) + FsmEvent(&stptr->l1.l1m, EV_TIMER_ACT, NULL); + while ((skb = skb_dequeue(&cs->rq))) { +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "PH_DATA", 1); +#endif + stptr = cs->stlist; + if (skb->len<3) { + debugl1(cs, "D-channel frame too short(%d)",skb->len); + dev_kfree_skb(skb); + return; + } + if ((skb->data[0] & 1) || !(skb->data[1] &1)) { + debugl1(cs, "D-channel frame wrong EA0/EA1"); + dev_kfree_skb(skb); + return; + } + sapi = skb->data[0] >> 2; + tei = skb->data[1] >> 1; + if (cs->debug & DEB_DLOG_HEX) + LogFrame(cs, skb->data, skb->len); + if (cs->debug & DEB_DLOG_VERBOSE) + dlogframe(cs, skb, 1); + if (tei == GROUP_TEI) { + if (sapi == CTRL_SAPI) { /* sapi 0 */ + while (stptr != NULL) { + if ((nskb = skb_clone(skb, GFP_ATOMIC))) + stptr->l1.l1l2(stptr, PH_DATA | INDICATION, nskb); + else + printk(KERN_WARNING "HiSax: isdn broadcast buffer shortage\n"); + stptr = stptr->next; + } + } else if (sapi == TEI_SAPI) { + while (stptr != NULL) { + if ((nskb = skb_clone(skb, GFP_ATOMIC))) + stptr->l1.l1tei(stptr, PH_DATA | INDICATION, nskb); + else + printk(KERN_WARNING "HiSax: tei broadcast buffer shortage\n"); + stptr = stptr->next; + } + } + dev_kfree_skb(skb); + } else if (sapi == CTRL_SAPI) { /* sapi 0 */ + found = 0; + while (stptr != NULL) + if (tei == stptr->l2.tei) { + stptr->l1.l1l2(stptr, PH_DATA | INDICATION, skb); + found = !0; + break; + } else + stptr = stptr->next; + if (!found) + dev_kfree_skb(skb); + } else + dev_kfree_skb(skb); + } +} + +static void +BChannel_proc_xmt(struct BCState *bcs) +{ + struct PStack *st = bcs->st; + + if (test_bit(BC_FLG_BUSY, &bcs->Flag)) { + debugl1(bcs->cs, "BC_BUSY Error"); + return; + } + + if (test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags)) + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + if (!test_bit(BC_FLG_ACTIV, &bcs->Flag)) { + if (!test_bit(BC_FLG_BUSY, &bcs->Flag) && (!skb_queue_len(&bcs->squeue))) { + st->l2.l2l1(st, PH_DEACTIVATE | CONFIRM, NULL); + } + } +} + +static void +BChannel_proc_rcv(struct BCState *bcs) +{ + struct sk_buff *skb; + + if (bcs->st->l1.l1m.state == ST_L1_WAIT_ACT) { + FsmDelTimer(&bcs->st->l1.timer, 4); + FsmEvent(&bcs->st->l1.l1m, EV_TIMER_ACT, NULL); + } + while ((skb = skb_dequeue(&bcs->rqueue))) { + bcs->st->l1.l1l2(bcs->st, PH_DATA | INDICATION, skb); + } +} + +static void +BChannel_proc_ack(struct BCState *bcs) +{ + u_long flags; + int ack; + + spin_lock_irqsave(&bcs->aclock, flags); + ack = bcs->ackcnt; + bcs->ackcnt = 0; + spin_unlock_irqrestore(&bcs->aclock, flags); + if (ack) + lli_writewakeup(bcs->st, ack); +} + +void +BChannel_bh(struct BCState *bcs) +{ + if (!bcs) + return; + if (test_and_clear_bit(B_RCVBUFREADY, &bcs->event)) + BChannel_proc_rcv(bcs); + if (test_and_clear_bit(B_XMTBUFREADY, &bcs->event)) + BChannel_proc_xmt(bcs); + if (test_and_clear_bit(B_ACKPENDING, &bcs->event)) + BChannel_proc_ack(bcs); +} + +void +HiSax_addlist(struct IsdnCardState *cs, + struct PStack *st) +{ + st->next = cs->stlist; + cs->stlist = st; +} + +void +HiSax_rmlist(struct IsdnCardState *cs, + struct PStack *st) +{ + struct PStack *p; + + FsmDelTimer(&st->l1.timer, 0); + if (cs->stlist == st) + cs->stlist = st->next; + else { + p = cs->stlist; + while (p) + if (p->next == st) { + p->next = st->next; + return; + } else + p = p->next; + } +} + +void +init_bcstate(struct IsdnCardState *cs, int bc) +{ + struct BCState *bcs = cs->bcs + bc; + + bcs->cs = cs; + bcs->channel = bc; + INIT_WORK(&bcs->tqueue, (void *)(void *) BChannel_bh, bcs); + spin_lock_init(&bcs->aclock); + bcs->BC_SetStack = NULL; + bcs->BC_Close = NULL; + bcs->Flag = 0; +} + +#ifdef L2FRAME_DEBUG /* psa */ + +char * +l2cmd(u_char cmd) +{ + switch (cmd & ~0x10) { + case 1: + return "RR"; + case 5: + return "RNR"; + case 9: + return "REJ"; + case 0x6f: + return "SABME"; + case 0x0f: + return "DM"; + case 3: + return "UI"; + case 0x43: + return "DISC"; + case 0x63: + return "UA"; + case 0x87: + return "FRMR"; + case 0xaf: + return "XID"; + default: + if (!(cmd & 1)) + return "I"; + else + return "invalid command"; + } +} + +static char tmpdeb[32]; + +char * +l2frames(u_char * ptr) +{ + switch (ptr[2] & ~0x10) { + case 1: + case 5: + case 9: + sprintf(tmpdeb, "%s[%d](nr %d)", l2cmd(ptr[2]), ptr[3] & 1, ptr[3] >> 1); + break; + case 0x6f: + case 0x0f: + case 3: + case 0x43: + case 0x63: + case 0x87: + case 0xaf: + sprintf(tmpdeb, "%s[%d]", l2cmd(ptr[2]), (ptr[2] & 0x10) >> 4); + break; + default: + if (!(ptr[2] & 1)) { + sprintf(tmpdeb, "I[%d](ns %d, nr %d)", ptr[3] & 1, ptr[2] >> 1, ptr[3] >> 1); + break; + } else + return "invalid command"; + } + + + return tmpdeb; +} + +void +Logl2Frame(struct IsdnCardState *cs, struct sk_buff *skb, char *buf, int dir) +{ + u_char *ptr; + + ptr = skb->data; + + if (ptr[0] & 1 || !(ptr[1] & 1)) + debugl1(cs, "Address not LAPD"); + else + debugl1(cs, "%s %s: %s%c (sapi %d, tei %d)", + (dir ? "<-" : "->"), buf, l2frames(ptr), + ((ptr[0] & 2) >> 1) == dir ? 'C' : 'R', ptr[0] >> 2, ptr[1] >> 1); +} +#endif + +static void +l1_reset(struct FsmInst *fi, int event, void *arg) +{ + FsmChangeState(fi, ST_L1_F3); +} + +static void +l1_deact_cnf(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + FsmChangeState(fi, ST_L1_F3); + if (test_bit(FLG_L1_ACTIVATING, &st->l1.Flags)) + st->l1.l1hw(st, HW_ENABLE | REQUEST, NULL); +} + +static void +l1_deact_req_s(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + FsmChangeState(fi, ST_L1_F3); + FsmRestartTimer(&st->l1.timer, 550, EV_TIMER_DEACT, NULL, 2); + test_and_set_bit(FLG_L1_DEACTTIMER, &st->l1.Flags); +} + +static void +l1_power_up_s(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + if (test_bit(FLG_L1_ACTIVATING, &st->l1.Flags)) { + FsmChangeState(fi, ST_L1_F4); + st->l1.l1hw(st, HW_INFO3 | REQUEST, NULL); + FsmRestartTimer(&st->l1.timer, TIMER3_VALUE, EV_TIMER3, NULL, 2); + test_and_set_bit(FLG_L1_T3RUN, &st->l1.Flags); + } else + FsmChangeState(fi, ST_L1_F3); +} + +static void +l1_go_F5(struct FsmInst *fi, int event, void *arg) +{ + FsmChangeState(fi, ST_L1_F5); +} + +static void +l1_go_F8(struct FsmInst *fi, int event, void *arg) +{ + FsmChangeState(fi, ST_L1_F8); +} + +static void +l1_info2_ind(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + +#ifdef HISAX_UINTERFACE + if (test_bit(FLG_L1_UINT, &st->l1.Flags)) + FsmChangeState(fi, ST_L1_SYNC2); + else +#endif + FsmChangeState(fi, ST_L1_F6); + st->l1.l1hw(st, HW_INFO3 | REQUEST, NULL); +} + +static void +l1_info4_ind(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + +#ifdef HISAX_UINTERFACE + if (test_bit(FLG_L1_UINT, &st->l1.Flags)) + FsmChangeState(fi, ST_L1_TRANS); + else +#endif + FsmChangeState(fi, ST_L1_F7); + st->l1.l1hw(st, HW_INFO3 | REQUEST, NULL); + if (test_and_clear_bit(FLG_L1_DEACTTIMER, &st->l1.Flags)) + FsmDelTimer(&st->l1.timer, 4); + if (!test_bit(FLG_L1_ACTIVATED, &st->l1.Flags)) { + if (test_and_clear_bit(FLG_L1_T3RUN, &st->l1.Flags)) + FsmDelTimer(&st->l1.timer, 3); + FsmRestartTimer(&st->l1.timer, 110, EV_TIMER_ACT, NULL, 2); + test_and_set_bit(FLG_L1_ACTTIMER, &st->l1.Flags); + } +} + +static void +l1_timer3(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + test_and_clear_bit(FLG_L1_T3RUN, &st->l1.Flags); + if (test_and_clear_bit(FLG_L1_ACTIVATING, &st->l1.Flags)) + L1deactivated(st->l1.hardware); + +#ifdef HISAX_UINTERFACE + if (!test_bit(FLG_L1_UINT, &st->l1.Flags)) +#endif + if (st->l1.l1m.state != ST_L1_F6) { + FsmChangeState(fi, ST_L1_F3); + st->l1.l1hw(st, HW_ENABLE | REQUEST, NULL); + } +} + +static void +l1_timer_act(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + test_and_clear_bit(FLG_L1_ACTTIMER, &st->l1.Flags); + test_and_set_bit(FLG_L1_ACTIVATED, &st->l1.Flags); + L1activated(st->l1.hardware); +} + +static void +l1_timer_deact(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + test_and_clear_bit(FLG_L1_DEACTTIMER, &st->l1.Flags); + test_and_clear_bit(FLG_L1_ACTIVATED, &st->l1.Flags); + L1deactivated(st->l1.hardware); + st->l1.l1hw(st, HW_DEACTIVATE | RESPONSE, NULL); +} + +static void +l1_activate_s(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + st->l1.l1hw(st, HW_RESET | REQUEST, NULL); +} + +static void +l1_activate_no(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + if ((!test_bit(FLG_L1_DEACTTIMER, &st->l1.Flags)) && (!test_bit(FLG_L1_T3RUN, &st->l1.Flags))) { + test_and_clear_bit(FLG_L1_ACTIVATING, &st->l1.Flags); + L1deactivated(st->l1.hardware); + } +} + +static struct FsmNode L1SFnList[] __initdata = +{ + {ST_L1_F3, EV_PH_ACTIVATE, l1_activate_s}, + {ST_L1_F6, EV_PH_ACTIVATE, l1_activate_no}, + {ST_L1_F8, EV_PH_ACTIVATE, l1_activate_no}, + {ST_L1_F3, EV_RESET_IND, l1_reset}, + {ST_L1_F4, EV_RESET_IND, l1_reset}, + {ST_L1_F5, EV_RESET_IND, l1_reset}, + {ST_L1_F6, EV_RESET_IND, l1_reset}, + {ST_L1_F7, EV_RESET_IND, l1_reset}, + {ST_L1_F8, EV_RESET_IND, l1_reset}, + {ST_L1_F3, EV_DEACT_CNF, l1_deact_cnf}, + {ST_L1_F4, EV_DEACT_CNF, l1_deact_cnf}, + {ST_L1_F5, EV_DEACT_CNF, l1_deact_cnf}, + {ST_L1_F6, EV_DEACT_CNF, l1_deact_cnf}, + {ST_L1_F7, EV_DEACT_CNF, l1_deact_cnf}, + {ST_L1_F8, EV_DEACT_CNF, l1_deact_cnf}, + {ST_L1_F6, EV_DEACT_IND, l1_deact_req_s}, + {ST_L1_F7, EV_DEACT_IND, l1_deact_req_s}, + {ST_L1_F8, EV_DEACT_IND, l1_deact_req_s}, + {ST_L1_F3, EV_POWER_UP, l1_power_up_s}, + {ST_L1_F4, EV_RSYNC_IND, l1_go_F5}, + {ST_L1_F6, EV_RSYNC_IND, l1_go_F8}, + {ST_L1_F7, EV_RSYNC_IND, l1_go_F8}, + {ST_L1_F3, EV_INFO2_IND, l1_info2_ind}, + {ST_L1_F4, EV_INFO2_IND, l1_info2_ind}, + {ST_L1_F5, EV_INFO2_IND, l1_info2_ind}, + {ST_L1_F7, EV_INFO2_IND, l1_info2_ind}, + {ST_L1_F8, EV_INFO2_IND, l1_info2_ind}, + {ST_L1_F3, EV_INFO4_IND, l1_info4_ind}, + {ST_L1_F4, EV_INFO4_IND, l1_info4_ind}, + {ST_L1_F5, EV_INFO4_IND, l1_info4_ind}, + {ST_L1_F6, EV_INFO4_IND, l1_info4_ind}, + {ST_L1_F8, EV_INFO4_IND, l1_info4_ind}, + {ST_L1_F3, EV_TIMER3, l1_timer3}, + {ST_L1_F4, EV_TIMER3, l1_timer3}, + {ST_L1_F5, EV_TIMER3, l1_timer3}, + {ST_L1_F6, EV_TIMER3, l1_timer3}, + {ST_L1_F8, EV_TIMER3, l1_timer3}, + {ST_L1_F7, EV_TIMER_ACT, l1_timer_act}, + {ST_L1_F3, EV_TIMER_DEACT, l1_timer_deact}, + {ST_L1_F4, EV_TIMER_DEACT, l1_timer_deact}, + {ST_L1_F5, EV_TIMER_DEACT, l1_timer_deact}, + {ST_L1_F6, EV_TIMER_DEACT, l1_timer_deact}, + {ST_L1_F7, EV_TIMER_DEACT, l1_timer_deact}, + {ST_L1_F8, EV_TIMER_DEACT, l1_timer_deact}, +}; + +#define L1S_FN_COUNT (sizeof(L1SFnList)/sizeof(struct FsmNode)) + +#ifdef HISAX_UINTERFACE +static void +l1_deact_req_u(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + FsmChangeState(fi, ST_L1_RESET); + FsmRestartTimer(&st->l1.timer, 550, EV_TIMER_DEACT, NULL, 2); + test_and_set_bit(FLG_L1_DEACTTIMER, &st->l1.Flags); + st->l1.l1hw(st, HW_ENABLE | REQUEST, NULL); +} + +static void +l1_power_up_u(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + FsmRestartTimer(&st->l1.timer, TIMER3_VALUE, EV_TIMER3, NULL, 2); + test_and_set_bit(FLG_L1_T3RUN, &st->l1.Flags); +} + +static void +l1_info0_ind(struct FsmInst *fi, int event, void *arg) +{ + FsmChangeState(fi, ST_L1_DEACT); +} + +static void +l1_activate_u(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + st->l1.l1hw(st, HW_INFO1 | REQUEST, NULL); +} + +static struct FsmNode L1UFnList[] __initdata = +{ + {ST_L1_RESET, EV_DEACT_IND, l1_deact_req_u}, + {ST_L1_DEACT, EV_DEACT_IND, l1_deact_req_u}, + {ST_L1_SYNC2, EV_DEACT_IND, l1_deact_req_u}, + {ST_L1_TRANS, EV_DEACT_IND, l1_deact_req_u}, + {ST_L1_DEACT, EV_PH_ACTIVATE, l1_activate_u}, + {ST_L1_DEACT, EV_POWER_UP, l1_power_up_u}, + {ST_L1_DEACT, EV_INFO2_IND, l1_info2_ind}, + {ST_L1_TRANS, EV_INFO2_IND, l1_info2_ind}, + {ST_L1_RESET, EV_DEACT_CNF, l1_info0_ind}, + {ST_L1_DEACT, EV_INFO4_IND, l1_info4_ind}, + {ST_L1_SYNC2, EV_INFO4_IND, l1_info4_ind}, + {ST_L1_RESET, EV_INFO4_IND, l1_info4_ind}, + {ST_L1_DEACT, EV_TIMER3, l1_timer3}, + {ST_L1_SYNC2, EV_TIMER3, l1_timer3}, + {ST_L1_TRANS, EV_TIMER_ACT, l1_timer_act}, + {ST_L1_DEACT, EV_TIMER_DEACT, l1_timer_deact}, + {ST_L1_SYNC2, EV_TIMER_DEACT, l1_timer_deact}, + {ST_L1_RESET, EV_TIMER_DEACT, l1_timer_deact}, +}; + +#define L1U_FN_COUNT (sizeof(L1UFnList)/sizeof(struct FsmNode)) + +#endif + +static void +l1b_activate(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + FsmChangeState(fi, ST_L1_WAIT_ACT); + FsmRestartTimer(&st->l1.timer, st->l1.delay, EV_TIMER_ACT, NULL, 2); +} + +static void +l1b_deactivate(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + FsmChangeState(fi, ST_L1_WAIT_DEACT); + FsmRestartTimer(&st->l1.timer, 10, EV_TIMER_DEACT, NULL, 2); +} + +static void +l1b_timer_act(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + FsmChangeState(fi, ST_L1_ACTIV); + st->l1.l1l2(st, PH_ACTIVATE | CONFIRM, NULL); +} + +static void +l1b_timer_deact(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + FsmChangeState(fi, ST_L1_NULL); + st->l2.l2l1(st, PH_DEACTIVATE | CONFIRM, NULL); +} + +static struct FsmNode L1BFnList[] __initdata = +{ + {ST_L1_NULL, EV_PH_ACTIVATE, l1b_activate}, + {ST_L1_WAIT_ACT, EV_TIMER_ACT, l1b_timer_act}, + {ST_L1_ACTIV, EV_PH_DEACTIVATE, l1b_deactivate}, + {ST_L1_WAIT_DEACT, EV_TIMER_DEACT, l1b_timer_deact}, +}; + +#define L1B_FN_COUNT (sizeof(L1BFnList)/sizeof(struct FsmNode)) + +int __init +Isdnl1New(void) +{ + int retval; + + l1fsm_s.state_count = L1S_STATE_COUNT; + l1fsm_s.event_count = L1_EVENT_COUNT; + l1fsm_s.strEvent = strL1Event; + l1fsm_s.strState = strL1SState; + retval = FsmNew(&l1fsm_s, L1SFnList, L1S_FN_COUNT); + if (retval) + return retval; + + l1fsm_b.state_count = L1B_STATE_COUNT; + l1fsm_b.event_count = L1_EVENT_COUNT; + l1fsm_b.strEvent = strL1Event; + l1fsm_b.strState = strL1BState; + retval = FsmNew(&l1fsm_b, L1BFnList, L1B_FN_COUNT); + if (retval) { + FsmFree(&l1fsm_s); + return retval; + } +#ifdef HISAX_UINTERFACE + l1fsm_u.state_count = L1U_STATE_COUNT; + l1fsm_u.event_count = L1_EVENT_COUNT; + l1fsm_u.strEvent = strL1Event; + l1fsm_u.strState = strL1UState; + retval = FsmNew(&l1fsm_u, L1UFnList, L1U_FN_COUNT); + if (retval) { + FsmFree(&l1fsm_s); + FsmFree(&l1fsm_b); + return retval; + } +#endif + return 0; +} + +void Isdnl1Free(void) +{ +#ifdef HISAX_UINTERFACE + FsmFree(&l1fsm_u); +#endif + FsmFree(&l1fsm_s); + FsmFree(&l1fsm_b); +} + +static void +dch_l2l1(struct PStack *st, int pr, void *arg) +{ + struct IsdnCardState *cs = (struct IsdnCardState *) st->l1.hardware; + + switch (pr) { + case (PH_DATA | REQUEST): + case (PH_PULL | REQUEST): + case (PH_PULL |INDICATION): + st->l1.l1hw(st, pr, arg); + break; + case (PH_ACTIVATE | REQUEST): + if (cs->debug) + debugl1(cs, "PH_ACTIVATE_REQ %s", + st->l1.l1m.fsm->strState[st->l1.l1m.state]); + if (test_bit(FLG_L1_ACTIVATED, &st->l1.Flags)) + st->l1.l1l2(st, PH_ACTIVATE | CONFIRM, NULL); + else { + test_and_set_bit(FLG_L1_ACTIVATING, &st->l1.Flags); + FsmEvent(&st->l1.l1m, EV_PH_ACTIVATE, arg); + } + break; + case (PH_TESTLOOP | REQUEST): + if (1 & (long) arg) + debugl1(cs, "PH_TEST_LOOP B1"); + if (2 & (long) arg) + debugl1(cs, "PH_TEST_LOOP B2"); + if (!(3 & (long) arg)) + debugl1(cs, "PH_TEST_LOOP DISABLED"); + st->l1.l1hw(st, HW_TESTLOOP | REQUEST, arg); + break; + default: + if (cs->debug) + debugl1(cs, "dch_l2l1 msg %04X unhandled", pr); + break; + } +} + +void +l1_msg(struct IsdnCardState *cs, int pr, void *arg) { + struct PStack *st; + + st = cs->stlist; + + while (st) { + switch(pr) { + case (HW_RESET | INDICATION): + FsmEvent(&st->l1.l1m, EV_RESET_IND, arg); + break; + case (HW_DEACTIVATE | CONFIRM): + FsmEvent(&st->l1.l1m, EV_DEACT_CNF, arg); + break; + case (HW_DEACTIVATE | INDICATION): + FsmEvent(&st->l1.l1m, EV_DEACT_IND, arg); + break; + case (HW_POWERUP | CONFIRM): + FsmEvent(&st->l1.l1m, EV_POWER_UP, arg); + break; + case (HW_RSYNC | INDICATION): + FsmEvent(&st->l1.l1m, EV_RSYNC_IND, arg); + break; + case (HW_INFO2 | INDICATION): + FsmEvent(&st->l1.l1m, EV_INFO2_IND, arg); + break; + case (HW_INFO4_P8 | INDICATION): + case (HW_INFO4_P10 | INDICATION): + FsmEvent(&st->l1.l1m, EV_INFO4_IND, arg); + break; + default: + if (cs->debug) + debugl1(cs, "l1msg %04X unhandled", pr); + break; + } + st = st->next; + } +} + +void +l1_msg_b(struct PStack *st, int pr, void *arg) { + switch(pr) { + case (PH_ACTIVATE | REQUEST): + FsmEvent(&st->l1.l1m, EV_PH_ACTIVATE, NULL); + break; + case (PH_DEACTIVATE | REQUEST): + FsmEvent(&st->l1.l1m, EV_PH_DEACTIVATE, NULL); + break; + } +} + +void +setstack_HiSax(struct PStack *st, struct IsdnCardState *cs) +{ + st->l1.hardware = cs; + st->protocol = cs->protocol; + st->l1.l1m.fsm = &l1fsm_s; + st->l1.l1m.state = ST_L1_F3; + st->l1.Flags = 0; +#ifdef HISAX_UINTERFACE + if (test_bit(FLG_HW_L1_UINT, &cs->HW_Flags)) { + st->l1.l1m.fsm = &l1fsm_u; + st->l1.l1m.state = ST_L1_RESET; + st->l1.Flags = FLG_L1_UINT; + } +#endif + st->l1.l1m.debug = cs->debug; + st->l1.l1m.userdata = st; + st->l1.l1m.userint = 0; + st->l1.l1m.printdebug = l1m_debug; + FsmInitTimer(&st->l1.l1m, &st->l1.timer); + setstack_tei(st); + setstack_manager(st); + st->l1.stlistp = &(cs->stlist); + st->l2.l2l1 = dch_l2l1; + if (cs->setstack_d) + cs->setstack_d(st, cs); +} + +void +setstack_l1_B(struct PStack *st) +{ + struct IsdnCardState *cs = st->l1.hardware; + + st->l1.l1m.fsm = &l1fsm_b; + st->l1.l1m.state = ST_L1_NULL; + st->l1.l1m.debug = cs->debug; + st->l1.l1m.userdata = st; + st->l1.l1m.userint = 0; + st->l1.l1m.printdebug = l1m_debug; + st->l1.Flags = 0; + FsmInitTimer(&st->l1.l1m, &st->l1.timer); +} diff --git a/drivers/isdn/hisax/isdnl1.h b/drivers/isdn/hisax/isdnl1.h new file mode 100644 index 000000000000..0e88cfabdf10 --- /dev/null +++ b/drivers/isdn/hisax/isdnl1.h @@ -0,0 +1,32 @@ +/* $Id: isdnl1.h,v 2.12.2.3 2004/02/11 13:21:34 keil Exp $ + * + * Layer 1 defines + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#define D_RCVBUFREADY 0 +#define D_XMTBUFREADY 1 +#define D_L1STATECHANGE 2 +#define D_CLEARBUSY 3 +#define D_RX_MON0 4 +#define D_RX_MON1 5 +#define D_TX_MON0 6 +#define D_TX_MON1 7 +#define E_RCVBUFREADY 8 + +#define B_RCVBUFREADY 0 +#define B_XMTBUFREADY 1 +#define B_ACKPENDING 2 + +extern void debugl1(struct IsdnCardState *cs, char *fmt, ...); +extern void DChannel_proc_xmt(struct IsdnCardState *cs); +extern void DChannel_proc_rcv(struct IsdnCardState *cs); +extern void l1_msg(struct IsdnCardState *cs, int pr, void *arg); +extern void l1_msg_b(struct PStack *st, int pr, void *arg); + +#ifdef L2FRAME_DEBUG +extern void Logl2Frame(struct IsdnCardState *cs, struct sk_buff *skb, char *buf, int dir); +#endif diff --git a/drivers/isdn/hisax/isdnl2.c b/drivers/isdn/hisax/isdnl2.c new file mode 100644 index 000000000000..d311b5fbf895 --- /dev/null +++ b/drivers/isdn/hisax/isdnl2.c @@ -0,0 +1,1860 @@ +/* $Id: isdnl2.c,v 2.30.2.4 2004/02/11 13:21:34 keil Exp $ + * + * Author Karsten Keil + * based on the teles driver from Jan den Ouden + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * For changes and modifications please read + * Documentation/isdn/HiSax.cert + * + * Thanks to Jan den Ouden + * Fritz Elfert + * + */ + +#include <linux/init.h> +#include "hisax.h" +#include "isdnl2.h" + +const char *l2_revision = "$Revision: 2.30.2.4 $"; + +static void l2m_debug(struct FsmInst *fi, char *fmt, ...); + +static struct Fsm l2fsm; + +enum { + ST_L2_1, + ST_L2_2, + 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_2", + "ST_L2_3", + "ST_L2_4", + "ST_L2_5", + "ST_L2_6", + "ST_L2_7", + "ST_L2_8", +}; + +enum { + EV_L2_UI, + EV_L2_SABME, + EV_L2_DISC, + EV_L2_DM, + EV_L2_UA, + EV_L2_FRMR, + EV_L2_SUPER, + EV_L2_I, + EV_L2_DL_DATA, + EV_L2_ACK_PULL, + EV_L2_DL_UNIT_DATA, + EV_L2_DL_ESTABLISH_REQ, + EV_L2_DL_RELEASE_REQ, + EV_L2_MDL_ASSIGN, + EV_L2_MDL_REMOVE, + EV_L2_MDL_ERROR, + EV_L1_DEACTIVATE, + EV_L2_T200, + EV_L2_T203, + EV_L2_SET_OWN_BUSY, + EV_L2_CLEAR_OWN_BUSY, + EV_L2_FRAME_ERROR, +}; + +#define L2_EVENT_COUNT (EV_L2_FRAME_ERROR+1) + +static char *strL2Event[] = +{ + "EV_L2_UI", + "EV_L2_SABME", + "EV_L2_DISC", + "EV_L2_DM", + "EV_L2_UA", + "EV_L2_FRMR", + "EV_L2_SUPER", + "EV_L2_I", + "EV_L2_DL_DATA", + "EV_L2_ACK_PULL", + "EV_L2_DL_UNIT_DATA", + "EV_L2_DL_ESTABLISH_REQ", + "EV_L2_DL_RELEASE_REQ", + "EV_L2_MDL_ASSIGN", + "EV_L2_MDL_REMOVE", + "EV_L2_MDL_ERROR", + "EV_L1_DEACTIVATE", + "EV_L2_T200", + "EV_L2_T203", + "EV_L2_SET_OWN_BUSY", + "EV_L2_CLEAR_OWN_BUSY", + "EV_L2_FRAME_ERROR", +}; + +static int l2addrsize(struct Layer2 *l2); + +static void +set_peer_busy(struct Layer2 *l2) { + test_and_set_bit(FLG_PEER_BUSY, &l2->flag); + if (skb_queue_len(&l2->i_queue) || skb_queue_len(&l2->ui_queue)) + test_and_set_bit(FLG_L2BLOCK, &l2->flag); +} + +static void +clear_peer_busy(struct Layer2 *l2) { + if (test_and_clear_bit(FLG_PEER_BUSY, &l2->flag)) + test_and_clear_bit(FLG_L2BLOCK, &l2->flag); +} + +static void +InitWin(struct Layer2 *l2) +{ + int i; + + for (i = 0; i < MAX_WINDOW; i++) + l2->windowar[i] = NULL; +} + +static int +freewin1(struct Layer2 *l2) +{ + int i, cnt = 0; + + for (i = 0; i < MAX_WINDOW; i++) { + if (l2->windowar[i]) { + cnt++; + dev_kfree_skb(l2->windowar[i]); + l2->windowar[i] = NULL; + } + } + return cnt; +} + +inline void +freewin(struct PStack *st) +{ + freewin1(&st->l2); +} + +static void +ReleaseWin(struct Layer2 *l2) +{ + int cnt; + + if((cnt = freewin1(l2))) + printk(KERN_WARNING "isdl2 freed %d skbuffs in release\n", cnt); +} + +inline unsigned int +cansend(struct PStack *st) +{ + unsigned int p1; + + if(test_bit(FLG_MOD128, &st->l2.flag)) + p1 = (st->l2.vs - st->l2.va) % 128; + else + p1 = (st->l2.vs - st->l2.va) % 8; + return ((p1 < st->l2.window) && !test_bit(FLG_PEER_BUSY, &st->l2.flag)); +} + +inline void +clear_exception(struct Layer2 *l2) +{ + test_and_clear_bit(FLG_ACK_PEND, &l2->flag); + test_and_clear_bit(FLG_REJEXC, &l2->flag); + test_and_clear_bit(FLG_OWN_BUSY, &l2->flag); + clear_peer_busy(l2); +} + +inline int +l2headersize(struct Layer2 *l2, int ui) +{ + return (((test_bit(FLG_MOD128, &l2->flag) && (!ui)) ? 2 : 1) + + (test_bit(FLG_LAPD, &l2->flag) ? 2 : 1)); +} + +inline int +l2addrsize(struct Layer2 *l2) +{ + return (test_bit(FLG_LAPD, &l2->flag) ? 2 : 1); +} + +static int +sethdraddr(struct Layer2 *l2, u_char * header, int rsp) +{ + u_char *ptr = header; + int crbit = rsp; + + if (test_bit(FLG_LAPD, &l2->flag)) { + *ptr++ = (l2->sap << 2) | (rsp ? 2 : 0); + *ptr++ = (l2->tei << 1) | 1; + return (2); + } else { + if (test_bit(FLG_ORIG, &l2->flag)) + crbit = !crbit; + if (crbit) + *ptr++ = 1; + else + *ptr++ = 3; + return (1); + } +} + +inline static void +enqueue_super(struct PStack *st, + struct sk_buff *skb) +{ + if (test_bit(FLG_LAPB, &st->l2.flag)) + st->l1.bcs->tx_cnt += skb->len; + st->l2.l2l1(st, PH_DATA | REQUEST, skb); +} + +#define enqueue_ui(a, b) enqueue_super(a, b) + +inline int +IsUI(u_char * data) +{ + return ((data[0] & 0xef) == UI); +} + +inline int +IsUA(u_char * data) +{ + return ((data[0] & 0xef) == UA); +} + +inline int +IsDM(u_char * data) +{ + return ((data[0] & 0xef) == DM); +} + +inline int +IsDISC(u_char * data) +{ + return ((data[0] & 0xef) == DISC); +} + +inline int +IsRR(u_char * data, struct PStack *st) +{ + if (test_bit(FLG_MOD128, &st->l2.flag)) + return (data[0] == RR); + else + return ((data[0] & 0xf) == 1); +} + +inline int +IsSFrame(u_char * data, struct PStack *st) +{ + register u_char d = *data; + + if (!test_bit(FLG_MOD128, &st->l2.flag)) + d &= 0xf; + return(((d & 0xf3) == 1) && ((d & 0x0c) != 0x0c)); +} + +inline int +IsSABME(u_char * data, struct PStack *st) +{ + u_char d = data[0] & ~0x10; + + return (test_bit(FLG_MOD128, &st->l2.flag) ? d == SABME : d == SABM); +} + +inline int +IsREJ(u_char * data, struct PStack *st) +{ + return (test_bit(FLG_MOD128, &st->l2.flag) ? data[0] == REJ : (data[0] & 0xf) == REJ); +} + +inline int +IsFRMR(u_char * data) +{ + return ((data[0] & 0xef) == FRMR); +} + +inline int +IsRNR(u_char * data, struct PStack *st) +{ + return (test_bit(FLG_MOD128, &st->l2.flag) ? data[0] == RNR : (data[0] & 0xf) == RNR); +} + +int +iframe_error(struct PStack *st, struct sk_buff *skb) +{ + int i = l2addrsize(&st->l2) + (test_bit(FLG_MOD128, &st->l2.flag) ? 2 : 1); + int rsp = *skb->data & 0x2; + + if (test_bit(FLG_ORIG, &st->l2.flag)) + rsp = !rsp; + + if (rsp) + return 'L'; + + + if (skb->len < i) + return 'N'; + + if ((skb->len - i) > st->l2.maxlen) + return 'O'; + + + return 0; +} + +int +super_error(struct PStack *st, struct sk_buff *skb) +{ + if (skb->len != l2addrsize(&st->l2) + + (test_bit(FLG_MOD128, &st->l2.flag) ? 2 : 1)) + return 'N'; + + return 0; +} + +int +unnum_error(struct PStack *st, struct sk_buff *skb, int wantrsp) +{ + int rsp = (*skb->data & 0x2) >> 1; + if (test_bit(FLG_ORIG, &st->l2.flag)) + rsp = !rsp; + + if (rsp != wantrsp) + return 'L'; + + if (skb->len != l2addrsize(&st->l2) + 1) + return 'N'; + + return 0; +} + +int +UI_error(struct PStack *st, struct sk_buff *skb) +{ + int rsp = *skb->data & 0x2; + if (test_bit(FLG_ORIG, &st->l2.flag)) + rsp = !rsp; + + if (rsp) + return 'L'; + + if (skb->len > st->l2.maxlen + l2addrsize(&st->l2) + 1) + return 'O'; + + return 0; +} + +int +FRMR_error(struct PStack *st, struct sk_buff *skb) +{ + int headers = l2addrsize(&st->l2) + 1; + u_char *datap = skb->data + headers; + int rsp = *skb->data & 0x2; + + if (test_bit(FLG_ORIG, &st->l2.flag)) + rsp = !rsp; + + if (!rsp) + return 'L'; + + if (test_bit(FLG_MOD128, &st->l2.flag)) { + if (skb->len < headers + 5) + return 'N'; + else + l2m_debug(&st->l2.l2m, "FRMR information %2x %2x %2x %2x %2x", + datap[0], datap[1], datap[2], + datap[3], datap[4]); + } else { + if (skb->len < headers + 3) + return 'N'; + else + l2m_debug(&st->l2.l2m, "FRMR information %2x %2x %2x", + datap[0], datap[1], datap[2]); + } + + return 0; +} + +static unsigned int +legalnr(struct PStack *st, unsigned int nr) +{ + struct Layer2 *l2 = &st->l2; + + if(test_bit(FLG_MOD128, &l2->flag)) + return ((nr - l2->va) % 128) <= ((l2->vs - l2->va) % 128); + else + return ((nr - l2->va) % 8) <= ((l2->vs - l2->va) % 8); +} + +static void +setva(struct PStack *st, unsigned int nr) +{ + struct Layer2 *l2 = &st->l2; + int len; + u_long flags; + + spin_lock_irqsave(&l2->lock, flags); + while (l2->va != nr) { + (l2->va)++; + if(test_bit(FLG_MOD128, &l2->flag)) + l2->va %= 128; + else + l2->va %= 8; + len = l2->windowar[l2->sow]->len; + if (PACKET_NOACK == l2->windowar[l2->sow]->pkt_type) + len = -1; + dev_kfree_skb(l2->windowar[l2->sow]); + l2->windowar[l2->sow] = NULL; + l2->sow = (l2->sow + 1) % l2->window; + spin_unlock_irqrestore(&l2->lock, flags); + if (test_bit(FLG_LLI_L2WAKEUP, &st->lli.flag) && (len >=0)) + lli_writewakeup(st, len); + spin_lock_irqsave(&l2->lock, flags); + } + spin_unlock_irqrestore(&l2->lock, flags); +} + +static void +send_uframe(struct PStack *st, u_char cmd, u_char cr) +{ + struct sk_buff *skb; + u_char tmp[MAX_HEADER_LEN]; + int i; + + i = sethdraddr(&st->l2, tmp, cr); + tmp[i++] = cmd; + if (!(skb = alloc_skb(i, GFP_ATOMIC))) { + printk(KERN_WARNING "isdl2 can't alloc sbbuff for send_uframe\n"); + return; + } + memcpy(skb_put(skb, i), tmp, i); + enqueue_super(st, skb); +} + +inline u_char +get_PollFlag(struct PStack * st, struct sk_buff * skb) +{ + return (skb->data[l2addrsize(&(st->l2))] & 0x10); +} + +inline void +FreeSkb(struct sk_buff *skb) +{ + dev_kfree_skb(skb); +} + + +inline u_char +get_PollFlagFree(struct PStack *st, struct sk_buff *skb) +{ + u_char PF; + + PF = get_PollFlag(st, skb); + FreeSkb(skb); + return (PF); +} + +inline void +start_t200(struct PStack *st, int i) +{ + FsmAddTimer(&st->l2.t200, st->l2.T200, EV_L2_T200, NULL, i); + test_and_set_bit(FLG_T200_RUN, &st->l2.flag); +} + +inline void +restart_t200(struct PStack *st, int i) +{ + FsmRestartTimer(&st->l2.t200, st->l2.T200, EV_L2_T200, NULL, i); + test_and_set_bit(FLG_T200_RUN, &st->l2.flag); +} + +inline void +stop_t200(struct PStack *st, int i) +{ + if(test_and_clear_bit(FLG_T200_RUN, &st->l2.flag)) + FsmDelTimer(&st->l2.t200, i); +} + +inline void +st5_dl_release_l2l3(struct PStack *st) +{ + int pr; + + if(test_and_clear_bit(FLG_PEND_REL, &st->l2.flag)) + pr = DL_RELEASE | CONFIRM; + else + pr = DL_RELEASE | INDICATION; + + st->l2.l2l3(st, pr, NULL); +} + +inline void +lapb_dl_release_l2l3(struct PStack *st, int f) +{ + if (test_bit(FLG_LAPB, &st->l2.flag)) + st->l2.l2l1(st, PH_DEACTIVATE | REQUEST, NULL); + st->l2.l2l3(st, DL_RELEASE | f, NULL); +} + +static void +establishlink(struct FsmInst *fi) +{ + struct PStack *st = fi->userdata; + u_char cmd; + + clear_exception(&st->l2); + st->l2.rc = 0; + cmd = (test_bit(FLG_MOD128, &st->l2.flag) ? SABME : SABM) | 0x10; + send_uframe(st, cmd, CMD); + FsmDelTimer(&st->l2.t203, 1); + restart_t200(st, 1); + test_and_clear_bit(FLG_PEND_REL, &st->l2.flag); + freewin(st); + FsmChangeState(fi, ST_L2_5); +} + +static void +l2_mdl_error_ua(struct FsmInst *fi, int event, void *arg) +{ + struct sk_buff *skb = arg; + struct PStack *st = fi->userdata; + + if (get_PollFlagFree(st, skb)) + st->ma.layer(st, MDL_ERROR | INDICATION, (void *) 'C'); + else + st->ma.layer(st, MDL_ERROR | INDICATION, (void *) 'D'); +} + +static void +l2_mdl_error_dm(struct FsmInst *fi, int event, void *arg) +{ + struct sk_buff *skb = arg; + struct PStack *st = fi->userdata; + + if (get_PollFlagFree(st, skb)) + st->ma.layer(st, MDL_ERROR | INDICATION, (void *) 'B'); + else { + st->ma.layer(st, MDL_ERROR | INDICATION, (void *) 'E'); + establishlink(fi); + test_and_clear_bit(FLG_L3_INIT, &st->l2.flag); + } +} + +static void +l2_st8_mdl_error_dm(struct FsmInst *fi, int event, void *arg) +{ + struct sk_buff *skb = arg; + struct PStack *st = fi->userdata; + + if (get_PollFlagFree(st, skb)) + st->ma.layer(st, MDL_ERROR | INDICATION, (void *) 'B'); + else { + st->ma.layer(st, MDL_ERROR | INDICATION, (void *) 'E'); + } + establishlink(fi); + test_and_clear_bit(FLG_L3_INIT, &st->l2.flag); +} + +static void +l2_go_st3(struct FsmInst *fi, int event, void *arg) +{ + FsmChangeState(fi, ST_L2_3); +} + +static void +l2_mdl_assign(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + FsmChangeState(fi, ST_L2_3); + st->l2.l2tei(st, MDL_ASSIGN | INDICATION, NULL); +} + +static void +l2_queue_ui_assign(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + + skb_queue_tail(&st->l2.ui_queue, skb); + FsmChangeState(fi, ST_L2_2); + st->l2.l2tei(st, MDL_ASSIGN | INDICATION, NULL); +} + +static void +l2_queue_ui(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + + skb_queue_tail(&st->l2.ui_queue, skb); +} + +static void +tx_ui(struct PStack *st) +{ + struct sk_buff *skb; + u_char header[MAX_HEADER_LEN]; + int i; + + i = sethdraddr(&(st->l2), header, CMD); + header[i++] = UI; + while ((skb = skb_dequeue(&st->l2.ui_queue))) { + memcpy(skb_push(skb, i), header, i); + enqueue_ui(st, skb); + } +} + +static void +l2_send_ui(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + + skb_queue_tail(&st->l2.ui_queue, skb); + tx_ui(st); +} + +static void +l2_got_ui(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + + skb_pull(skb, l2headersize(&st->l2, 1)); + st->l2.l2l3(st, DL_UNIT_DATA | INDICATION, skb); +/* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + * in states 1-3 for broadcast + */ + + +} + +static void +l2_establish(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + establishlink(fi); + test_and_set_bit(FLG_L3_INIT, &st->l2.flag); +} + +static void +l2_discard_i_setl3(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + skb_queue_purge(&st->l2.i_queue); + test_and_set_bit(FLG_L3_INIT, &st->l2.flag); + test_and_clear_bit(FLG_PEND_REL, &st->l2.flag); +} + +static void +l2_l3_reestablish(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + skb_queue_purge(&st->l2.i_queue); + establishlink(fi); + test_and_set_bit(FLG_L3_INIT, &st->l2.flag); +} + +static void +l2_release(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + st->l2.l2l3(st, DL_RELEASE | CONFIRM, NULL); +} + +static void +l2_pend_rel(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + test_and_set_bit(FLG_PEND_REL, &st->l2.flag); +} + +static void +l2_disconnect(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + skb_queue_purge(&st->l2.i_queue); + freewin(st); + FsmChangeState(fi, ST_L2_6); + st->l2.rc = 0; + send_uframe(st, DISC | 0x10, CMD); + FsmDelTimer(&st->l2.t203, 1); + restart_t200(st, 2); +} + +static void +l2_start_multi(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + + send_uframe(st, UA | get_PollFlagFree(st, skb), RSP); + + clear_exception(&st->l2); + st->l2.vs = 0; + st->l2.va = 0; + st->l2.vr = 0; + st->l2.sow = 0; + FsmChangeState(fi, ST_L2_7); + FsmAddTimer(&st->l2.t203, st->l2.T203, EV_L2_T203, NULL, 3); + + st->l2.l2l3(st, DL_ESTABLISH | INDICATION, NULL); +} + +static void +l2_send_UA(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + + send_uframe(st, UA | get_PollFlagFree(st, skb), RSP); +} + +static void +l2_send_DM(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + + send_uframe(st, DM | get_PollFlagFree(st, skb), RSP); +} + +static void +l2_restart_multi(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + int est = 0, state; + + state = fi->state; + + send_uframe(st, UA | get_PollFlagFree(st, skb), RSP); + + st->ma.layer(st, MDL_ERROR | INDICATION, (void *) 'F'); + + if (st->l2.vs != st->l2.va) { + skb_queue_purge(&st->l2.i_queue); + est = 1; + } + + clear_exception(&st->l2); + st->l2.vs = 0; + st->l2.va = 0; + st->l2.vr = 0; + st->l2.sow = 0; + FsmChangeState(fi, ST_L2_7); + stop_t200(st, 3); + FsmRestartTimer(&st->l2.t203, st->l2.T203, EV_L2_T203, NULL, 3); + + if (est) + st->l2.l2l3(st, DL_ESTABLISH | INDICATION, NULL); + + if ((ST_L2_7==state) || (ST_L2_8 == state)) + if (skb_queue_len(&st->l2.i_queue) && cansend(st)) + st->l2.l2l1(st, PH_PULL | REQUEST, NULL); +} + +static void +l2_stop_multi(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + + FsmChangeState(fi, ST_L2_4); + FsmDelTimer(&st->l2.t203, 3); + stop_t200(st, 4); + + send_uframe(st, UA | get_PollFlagFree(st, skb), RSP); + + skb_queue_purge(&st->l2.i_queue); + freewin(st); + lapb_dl_release_l2l3(st, INDICATION); +} + +static void +l2_connected(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + int pr=-1; + + if (!get_PollFlag(st, skb)) { + l2_mdl_error_ua(fi, event, arg); + return; + } + FreeSkb(skb); + + if (test_and_clear_bit(FLG_PEND_REL, &st->l2.flag)) + l2_disconnect(fi, event, arg); + + if (test_and_clear_bit(FLG_L3_INIT, &st->l2.flag)) { + pr = DL_ESTABLISH | CONFIRM; + } else if (st->l2.vs != st->l2.va) { + skb_queue_purge(&st->l2.i_queue); + pr = DL_ESTABLISH | INDICATION; + } + + stop_t200(st, 5); + + st->l2.vr = 0; + st->l2.vs = 0; + st->l2.va = 0; + st->l2.sow = 0; + FsmChangeState(fi, ST_L2_7); + FsmAddTimer(&st->l2.t203, st->l2.T203, EV_L2_T203, NULL, 4); + + if (pr != -1) + st->l2.l2l3(st, pr, NULL); + + if (skb_queue_len(&st->l2.i_queue) && cansend(st)) + st->l2.l2l1(st, PH_PULL | REQUEST, NULL); +} + +static void +l2_released(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + + if (!get_PollFlag(st, skb)) { + l2_mdl_error_ua(fi, event, arg); + return; + } + FreeSkb(skb); + + stop_t200(st, 6); + lapb_dl_release_l2l3(st, CONFIRM); + FsmChangeState(fi, ST_L2_4); +} + +static void +l2_reestablish(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + + if (!get_PollFlagFree(st, skb)) { + establishlink(fi); + test_and_set_bit(FLG_L3_INIT, &st->l2.flag); + } +} + +static void +l2_st5_dm_release(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + + if (get_PollFlagFree(st, skb)) { + stop_t200(st, 7); + if (!test_bit(FLG_L3_INIT, &st->l2.flag)) + skb_queue_purge(&st->l2.i_queue); + if (test_bit(FLG_LAPB, &st->l2.flag)) + st->l2.l2l1(st, PH_DEACTIVATE | REQUEST, NULL); + st5_dl_release_l2l3(st); + FsmChangeState(fi, ST_L2_4); + } +} + +static void +l2_st6_dm_release(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + + if (get_PollFlagFree(st, skb)) { + stop_t200(st, 8); + lapb_dl_release_l2l3(st, CONFIRM); + FsmChangeState(fi, ST_L2_4); + } +} + +inline void +enquiry_cr(struct PStack *st, u_char typ, u_char cr, u_char pf) +{ + struct sk_buff *skb; + struct Layer2 *l2; + u_char tmp[MAX_HEADER_LEN]; + int i; + + l2 = &st->l2; + i = sethdraddr(l2, tmp, cr); + if (test_bit(FLG_MOD128, &l2->flag)) { + tmp[i++] = typ; + tmp[i++] = (l2->vr << 1) | (pf ? 1 : 0); + } else + tmp[i++] = (l2->vr << 5) | typ | (pf ? 0x10 : 0); + if (!(skb = alloc_skb(i, GFP_ATOMIC))) { + printk(KERN_WARNING "isdl2 can't alloc sbbuff for enquiry_cr\n"); + return; + } + memcpy(skb_put(skb, i), tmp, i); + enqueue_super(st, skb); +} + +inline void +enquiry_response(struct PStack *st) +{ + if (test_bit(FLG_OWN_BUSY, &st->l2.flag)) + enquiry_cr(st, RNR, RSP, 1); + else + enquiry_cr(st, RR, RSP, 1); + test_and_clear_bit(FLG_ACK_PEND, &st->l2.flag); +} + +inline void +transmit_enquiry(struct PStack *st) +{ + if (test_bit(FLG_OWN_BUSY, &st->l2.flag)) + enquiry_cr(st, RNR, CMD, 1); + else + enquiry_cr(st, RR, CMD, 1); + test_and_clear_bit(FLG_ACK_PEND, &st->l2.flag); + start_t200(st, 9); +} + + +static void +nrerrorrecovery(struct FsmInst *fi) +{ + struct PStack *st = fi->userdata; + + st->ma.layer(st, MDL_ERROR | INDICATION, (void *) 'J'); + establishlink(fi); + test_and_clear_bit(FLG_L3_INIT, &st->l2.flag); +} + +static void +invoke_retransmission(struct PStack *st, unsigned int nr) +{ + struct Layer2 *l2 = &st->l2; + u_int p1; + u_long flags; + + spin_lock_irqsave(&l2->lock, flags); + if (l2->vs != nr) { + while (l2->vs != nr) { + (l2->vs)--; + if(test_bit(FLG_MOD128, &l2->flag)) { + l2->vs %= 128; + p1 = (l2->vs - l2->va) % 128; + } else { + l2->vs %= 8; + p1 = (l2->vs - l2->va) % 8; + } + p1 = (p1 + l2->sow) % l2->window; + if (test_bit(FLG_LAPB, &l2->flag)) + st->l1.bcs->tx_cnt += l2->windowar[p1]->len + l2headersize(l2, 0); + skb_queue_head(&l2->i_queue, l2->windowar[p1]); + l2->windowar[p1] = NULL; + } + spin_unlock_irqrestore(&l2->lock, flags); + st->l2.l2l1(st, PH_PULL | REQUEST, NULL); + return; + } + spin_unlock_irqrestore(&l2->lock, flags); +} + +static void +l2_st7_got_super(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + int PollFlag, rsp, typ = RR; + unsigned int nr; + struct Layer2 *l2 = &st->l2; + + rsp = *skb->data & 0x2; + if (test_bit(FLG_ORIG, &l2->flag)) + rsp = !rsp; + + skb_pull(skb, l2addrsize(l2)); + if (IsRNR(skb->data, st)) { + set_peer_busy(l2); + typ = RNR; + } else + clear_peer_busy(l2); + if (IsREJ(skb->data, st)) + typ = REJ; + + if (test_bit(FLG_MOD128, &l2->flag)) { + PollFlag = (skb->data[1] & 0x1) == 0x1; + nr = skb->data[1] >> 1; + } else { + PollFlag = (skb->data[0] & 0x10); + nr = (skb->data[0] >> 5) & 0x7; + } + FreeSkb(skb); + + if (PollFlag) { + if (rsp) + st->ma.layer(st, MDL_ERROR | INDICATION, (void *) 'A'); + else + enquiry_response(st); + } + if (legalnr(st, nr)) { + if (typ == REJ) { + setva(st, nr); + invoke_retransmission(st, nr); + stop_t200(st, 10); + if (FsmAddTimer(&st->l2.t203, st->l2.T203, + EV_L2_T203, NULL, 6)) + l2m_debug(&st->l2.l2m, "Restart T203 ST7 REJ"); + } else if ((nr == l2->vs) && (typ == RR)) { + setva(st, nr); + stop_t200(st, 11); + FsmRestartTimer(&st->l2.t203, st->l2.T203, + EV_L2_T203, NULL, 7); + } else if ((l2->va != nr) || (typ == RNR)) { + setva(st, nr); + if(typ != RR) FsmDelTimer(&st->l2.t203, 9); + restart_t200(st, 12); + } + if (skb_queue_len(&st->l2.i_queue) && (typ == RR)) + st->l2.l2l1(st, PH_PULL | REQUEST, NULL); + } else + nrerrorrecovery(fi); +} + +static void +l2_feed_i_if_reest(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + + if (test_bit(FLG_LAPB, &st->l2.flag)) + st->l1.bcs->tx_cnt += skb->len + l2headersize(&st->l2, 0); + if (!test_bit(FLG_L3_INIT, &st->l2.flag)) + skb_queue_tail(&st->l2.i_queue, skb); + else + FreeSkb(skb); +} + +static void +l2_feed_i_pull(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + + if (test_bit(FLG_LAPB, &st->l2.flag)) + st->l1.bcs->tx_cnt += skb->len + l2headersize(&st->l2, 0); + skb_queue_tail(&st->l2.i_queue, skb); + st->l2.l2l1(st, PH_PULL | REQUEST, NULL); +} + +static void +l2_feed_iqueue(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + + if (test_bit(FLG_LAPB, &st->l2.flag)) + st->l1.bcs->tx_cnt += skb->len + l2headersize(&st->l2, 0); + skb_queue_tail(&st->l2.i_queue, skb); +} + +static void +l2_got_iframe(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + struct Layer2 *l2 = &(st->l2); + int PollFlag, ns, i; + unsigned int nr; + + i = l2addrsize(l2); + if (test_bit(FLG_MOD128, &l2->flag)) { + PollFlag = ((skb->data[i + 1] & 0x1) == 0x1); + ns = skb->data[i] >> 1; + nr = (skb->data[i + 1] >> 1) & 0x7f; + } else { + PollFlag = (skb->data[i] & 0x10); + ns = (skb->data[i] >> 1) & 0x7; + nr = (skb->data[i] >> 5) & 0x7; + } + if (test_bit(FLG_OWN_BUSY, &l2->flag)) { + FreeSkb(skb); + if(PollFlag) enquiry_response(st); + } else if (l2->vr == ns) { + (l2->vr)++; + if(test_bit(FLG_MOD128, &l2->flag)) + l2->vr %= 128; + else + l2->vr %= 8; + test_and_clear_bit(FLG_REJEXC, &l2->flag); + + if (PollFlag) + enquiry_response(st); + else + test_and_set_bit(FLG_ACK_PEND, &l2->flag); + skb_pull(skb, l2headersize(l2, 0)); + st->l2.l2l3(st, DL_DATA | INDICATION, skb); + } else { + /* n(s)!=v(r) */ + FreeSkb(skb); + if (test_and_set_bit(FLG_REJEXC, &l2->flag)) { + if (PollFlag) + enquiry_response(st); + } else { + enquiry_cr(st, REJ, RSP, PollFlag); + test_and_clear_bit(FLG_ACK_PEND, &l2->flag); + } + } + + if (legalnr(st, nr)) { + if (!test_bit(FLG_PEER_BUSY, &st->l2.flag) && (fi->state == ST_L2_7)) { + if (nr == st->l2.vs) { + stop_t200(st, 13); + FsmRestartTimer(&st->l2.t203, st->l2.T203, + EV_L2_T203, NULL, 7); + } else if (nr != st->l2.va) + restart_t200(st, 14); + } + setva(st, nr); + } else { + nrerrorrecovery(fi); + return; + } + + if (skb_queue_len(&st->l2.i_queue) && (fi->state == ST_L2_7)) + st->l2.l2l1(st, PH_PULL | REQUEST, NULL); + if (test_and_clear_bit(FLG_ACK_PEND, &st->l2.flag)) + enquiry_cr(st, RR, RSP, 0); +} + +static void +l2_got_tei(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + st->l2.tei = (long) arg; + + if (fi->state == ST_L2_3) { + establishlink(fi); + test_and_set_bit(FLG_L3_INIT, &st->l2.flag); + } else + FsmChangeState(fi, ST_L2_4); + if (skb_queue_len(&st->l2.ui_queue)) + tx_ui(st); +} + +static void +l2_st5_tout_200(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + if (test_bit(FLG_LAPD, &st->l2.flag) && + test_bit(FLG_DCHAN_BUSY, &st->l2.flag)) { + FsmAddTimer(&st->l2.t200, st->l2.T200, EV_L2_T200, NULL, 9); + } else if (st->l2.rc == st->l2.N200) { + FsmChangeState(fi, ST_L2_4); + test_and_clear_bit(FLG_T200_RUN, &st->l2.flag); + skb_queue_purge(&st->l2.i_queue); + st->ma.layer(st, MDL_ERROR | INDICATION, (void *) 'G'); + if (test_bit(FLG_LAPB, &st->l2.flag)) + st->l2.l2l1(st, PH_DEACTIVATE | REQUEST, NULL); + st5_dl_release_l2l3(st); + } else { + st->l2.rc++; + FsmAddTimer(&st->l2.t200, st->l2.T200, EV_L2_T200, NULL, 9); + send_uframe(st, (test_bit(FLG_MOD128, &st->l2.flag) ? SABME : SABM) + | 0x10, CMD); + } +} + +static void +l2_st6_tout_200(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + if (test_bit(FLG_LAPD, &st->l2.flag) && + test_bit(FLG_DCHAN_BUSY, &st->l2.flag)) { + FsmAddTimer(&st->l2.t200, st->l2.T200, EV_L2_T200, NULL, 9); + } else if (st->l2.rc == st->l2.N200) { + FsmChangeState(fi, ST_L2_4); + test_and_clear_bit(FLG_T200_RUN, &st->l2.flag); + st->ma.layer(st, MDL_ERROR | INDICATION, (void *) 'H'); + lapb_dl_release_l2l3(st, CONFIRM); + } else { + st->l2.rc++; + FsmAddTimer(&st->l2.t200, st->l2.T200, EV_L2_T200, + NULL, 9); + send_uframe(st, DISC | 0x10, CMD); + } +} + +static void +l2_st7_tout_200(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + if (test_bit(FLG_LAPD, &st->l2.flag) && + test_bit(FLG_DCHAN_BUSY, &st->l2.flag)) { + FsmAddTimer(&st->l2.t200, st->l2.T200, EV_L2_T200, NULL, 9); + return; + } + test_and_clear_bit(FLG_T200_RUN, &st->l2.flag); + st->l2.rc = 0; + FsmChangeState(fi, ST_L2_8); + + transmit_enquiry(st); + st->l2.rc++; +} + +static void +l2_st8_tout_200(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + if (test_bit(FLG_LAPD, &st->l2.flag) && + test_bit(FLG_DCHAN_BUSY, &st->l2.flag)) { + FsmAddTimer(&st->l2.t200, st->l2.T200, EV_L2_T200, NULL, 9); + return; + } + test_and_clear_bit(FLG_T200_RUN, &st->l2.flag); + if (st->l2.rc == st->l2.N200) { + st->ma.layer(st, MDL_ERROR | INDICATION, (void *) 'I'); + establishlink(fi); + test_and_clear_bit(FLG_L3_INIT, &st->l2.flag); + } else { + transmit_enquiry(st); + st->l2.rc++; + } +} + +static void +l2_st7_tout_203(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + if (test_bit(FLG_LAPD, &st->l2.flag) && + test_bit(FLG_DCHAN_BUSY, &st->l2.flag)) { + FsmAddTimer(&st->l2.t203, st->l2.T203, EV_L2_T203, NULL, 9); + return; + } + FsmChangeState(fi, ST_L2_8); + transmit_enquiry(st); + st->l2.rc = 0; +} + +static void +l2_pull_iqueue(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb, *oskb; + struct Layer2 *l2 = &st->l2; + u_char header[MAX_HEADER_LEN]; + int i; + int unsigned p1; + u_long flags; + + if (!cansend(st)) + return; + + skb = skb_dequeue(&l2->i_queue); + if (!skb) + return; + + spin_lock_irqsave(&l2->lock, flags); + if(test_bit(FLG_MOD128, &l2->flag)) + p1 = (l2->vs - l2->va) % 128; + else + p1 = (l2->vs - l2->va) % 8; + p1 = (p1 + l2->sow) % l2->window; + if (l2->windowar[p1]) { + printk(KERN_WARNING "isdnl2 try overwrite ack queue entry %d\n", + p1); + dev_kfree_skb(l2->windowar[p1]); + } + l2->windowar[p1] = skb_clone(skb, GFP_ATOMIC); + + i = sethdraddr(&st->l2, header, CMD); + + if (test_bit(FLG_MOD128, &l2->flag)) { + header[i++] = l2->vs << 1; + header[i++] = l2->vr << 1; + l2->vs = (l2->vs + 1) % 128; + } else { + header[i++] = (l2->vr << 5) | (l2->vs << 1); + l2->vs = (l2->vs + 1) % 8; + } + spin_unlock_irqrestore(&l2->lock, flags); + p1 = skb->data - skb->head; + if (p1 >= i) + memcpy(skb_push(skb, i), header, i); + else { + printk(KERN_WARNING + "isdl2 pull_iqueue skb header(%d/%d) too short\n", i, p1); + oskb = skb; + skb = alloc_skb(oskb->len + i, GFP_ATOMIC); + memcpy(skb_put(skb, i), header, i); + memcpy(skb_put(skb, oskb->len), oskb->data, oskb->len); + FreeSkb(oskb); + } + st->l2.l2l1(st, PH_PULL | INDICATION, skb); + test_and_clear_bit(FLG_ACK_PEND, &st->l2.flag); + if (!test_and_set_bit(FLG_T200_RUN, &st->l2.flag)) { + FsmDelTimer(&st->l2.t203, 13); + FsmAddTimer(&st->l2.t200, st->l2.T200, EV_L2_T200, NULL, 11); + } + if (skb_queue_len(&l2->i_queue) && cansend(st)) + st->l2.l2l1(st, PH_PULL | REQUEST, NULL); +} + +static void +l2_st8_got_super(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + int PollFlag, rsp, rnr = 0; + unsigned int nr; + struct Layer2 *l2 = &st->l2; + + rsp = *skb->data & 0x2; + if (test_bit(FLG_ORIG, &l2->flag)) + rsp = !rsp; + + skb_pull(skb, l2addrsize(l2)); + + if (IsRNR(skb->data, st)) { + set_peer_busy(l2); + rnr = 1; + } else + clear_peer_busy(l2); + + if (test_bit(FLG_MOD128, &l2->flag)) { + PollFlag = (skb->data[1] & 0x1) == 0x1; + nr = skb->data[1] >> 1; + } else { + PollFlag = (skb->data[0] & 0x10); + nr = (skb->data[0] >> 5) & 0x7; + } + FreeSkb(skb); + + if (rsp && PollFlag) { + if (legalnr(st, nr)) { + if (rnr) { + restart_t200(st, 15); + } else { + stop_t200(st, 16); + FsmAddTimer(&l2->t203, l2->T203, + EV_L2_T203, NULL, 5); + setva(st, nr); + } + invoke_retransmission(st, nr); + FsmChangeState(fi, ST_L2_7); + if (skb_queue_len(&l2->i_queue) && cansend(st)) + st->l2.l2l1(st, PH_PULL | REQUEST, NULL); + } else + nrerrorrecovery(fi); + } else { + if (!rsp && PollFlag) + enquiry_response(st); + if (legalnr(st, nr)) { + setva(st, nr); + } else + nrerrorrecovery(fi); + } +} + +static void +l2_got_FRMR(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + + skb_pull(skb, l2addrsize(&st->l2) + 1); + + if (!(skb->data[0] & 1) || ((skb->data[0] & 3) == 1) || /* I or S */ + (IsUA(skb->data) && (fi->state == ST_L2_7))) { + st->ma.layer(st, MDL_ERROR | INDICATION, (void *) 'K'); + establishlink(fi); + test_and_clear_bit(FLG_L3_INIT, &st->l2.flag); + } + FreeSkb(skb); +} + +static void +l2_st24_tei_remove(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + skb_queue_purge(&st->l2.ui_queue); + st->l2.tei = -1; + FsmChangeState(fi, ST_L2_1); +} + +static void +l2_st3_tei_remove(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + skb_queue_purge(&st->l2.ui_queue); + st->l2.tei = -1; + st->l2.l2l3(st, DL_RELEASE | INDICATION, NULL); + FsmChangeState(fi, ST_L2_1); +} + +static void +l2_st5_tei_remove(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + skb_queue_purge(&st->l2.i_queue); + skb_queue_purge(&st->l2.ui_queue); + freewin(st); + st->l2.tei = -1; + stop_t200(st, 17); + st5_dl_release_l2l3(st); + FsmChangeState(fi, ST_L2_1); +} + +static void +l2_st6_tei_remove(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + skb_queue_purge(&st->l2.ui_queue); + st->l2.tei = -1; + stop_t200(st, 18); + st->l2.l2l3(st, DL_RELEASE | CONFIRM, NULL); + FsmChangeState(fi, ST_L2_1); +} + +static void +l2_tei_remove(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + skb_queue_purge(&st->l2.i_queue); + skb_queue_purge(&st->l2.ui_queue); + freewin(st); + st->l2.tei = -1; + stop_t200(st, 17); + FsmDelTimer(&st->l2.t203, 19); + st->l2.l2l3(st, DL_RELEASE | INDICATION, NULL); + FsmChangeState(fi, ST_L2_1); +} + +static void +l2_st14_persistant_da(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + skb_queue_purge(&st->l2.i_queue); + skb_queue_purge(&st->l2.ui_queue); + if (test_and_clear_bit(FLG_ESTAB_PEND, &st->l2.flag)) + st->l2.l2l3(st, DL_RELEASE | INDICATION, NULL); +} + +static void +l2_st5_persistant_da(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + skb_queue_purge(&st->l2.i_queue); + skb_queue_purge(&st->l2.ui_queue); + freewin(st); + stop_t200(st, 19); + st5_dl_release_l2l3(st); + FsmChangeState(fi, ST_L2_4); +} + +static void +l2_st6_persistant_da(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + skb_queue_purge(&st->l2.ui_queue); + stop_t200(st, 20); + st->l2.l2l3(st, DL_RELEASE | CONFIRM, NULL); + FsmChangeState(fi, ST_L2_4); +} + +static void +l2_persistant_da(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + skb_queue_purge(&st->l2.i_queue); + skb_queue_purge(&st->l2.ui_queue); + freewin(st); + stop_t200(st, 19); + FsmDelTimer(&st->l2.t203, 19); + st->l2.l2l3(st, DL_RELEASE | INDICATION, NULL); + FsmChangeState(fi, ST_L2_4); +} + +static void +l2_set_own_busy(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + if(!test_and_set_bit(FLG_OWN_BUSY, &st->l2.flag)) { + enquiry_cr(st, RNR, RSP, 0); + test_and_clear_bit(FLG_ACK_PEND, &st->l2.flag); + } +} + +static void +l2_clear_own_busy(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + if(!test_and_clear_bit(FLG_OWN_BUSY, &st->l2.flag)) { + enquiry_cr(st, RR, RSP, 0); + test_and_clear_bit(FLG_ACK_PEND, &st->l2.flag); + } +} + +static void +l2_frame_error(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + st->ma.layer(st, MDL_ERROR | INDICATION, arg); +} + +static void +l2_frame_error_reest(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + st->ma.layer(st, MDL_ERROR | INDICATION, arg); + establishlink(fi); + test_and_clear_bit(FLG_L3_INIT, &st->l2.flag); +} + +static struct FsmNode L2FnList[] __initdata = +{ + {ST_L2_1, EV_L2_DL_ESTABLISH_REQ, l2_mdl_assign}, + {ST_L2_2, EV_L2_DL_ESTABLISH_REQ, l2_go_st3}, + {ST_L2_4, EV_L2_DL_ESTABLISH_REQ, l2_establish}, + {ST_L2_5, EV_L2_DL_ESTABLISH_REQ, l2_discard_i_setl3}, + {ST_L2_7, EV_L2_DL_ESTABLISH_REQ, l2_l3_reestablish}, + {ST_L2_8, EV_L2_DL_ESTABLISH_REQ, l2_l3_reestablish}, + {ST_L2_4, EV_L2_DL_RELEASE_REQ, l2_release}, + {ST_L2_5, EV_L2_DL_RELEASE_REQ, l2_pend_rel}, + {ST_L2_7, EV_L2_DL_RELEASE_REQ, l2_disconnect}, + {ST_L2_8, EV_L2_DL_RELEASE_REQ, l2_disconnect}, + {ST_L2_5, EV_L2_DL_DATA, l2_feed_i_if_reest}, + {ST_L2_7, EV_L2_DL_DATA, l2_feed_i_pull}, + {ST_L2_8, EV_L2_DL_DATA, l2_feed_iqueue}, + {ST_L2_1, EV_L2_DL_UNIT_DATA, l2_queue_ui_assign}, + {ST_L2_2, EV_L2_DL_UNIT_DATA, l2_queue_ui}, + {ST_L2_3, EV_L2_DL_UNIT_DATA, l2_queue_ui}, + {ST_L2_4, EV_L2_DL_UNIT_DATA, l2_send_ui}, + {ST_L2_5, EV_L2_DL_UNIT_DATA, l2_send_ui}, + {ST_L2_6, EV_L2_DL_UNIT_DATA, l2_send_ui}, + {ST_L2_7, EV_L2_DL_UNIT_DATA, l2_send_ui}, + {ST_L2_8, EV_L2_DL_UNIT_DATA, l2_send_ui}, + {ST_L2_1, EV_L2_MDL_ASSIGN, l2_got_tei}, + {ST_L2_2, EV_L2_MDL_ASSIGN, l2_got_tei}, + {ST_L2_3, EV_L2_MDL_ASSIGN, l2_got_tei}, + {ST_L2_2, EV_L2_MDL_ERROR, l2_st24_tei_remove}, + {ST_L2_3, EV_L2_MDL_ERROR, l2_st3_tei_remove}, + {ST_L2_4, EV_L2_MDL_REMOVE, l2_st24_tei_remove}, + {ST_L2_5, EV_L2_MDL_REMOVE, l2_st5_tei_remove}, + {ST_L2_6, EV_L2_MDL_REMOVE, l2_st6_tei_remove}, + {ST_L2_7, EV_L2_MDL_REMOVE, l2_tei_remove}, + {ST_L2_8, EV_L2_MDL_REMOVE, l2_tei_remove}, + {ST_L2_4, EV_L2_SABME, l2_start_multi}, + {ST_L2_5, EV_L2_SABME, l2_send_UA}, + {ST_L2_6, EV_L2_SABME, l2_send_DM}, + {ST_L2_7, EV_L2_SABME, l2_restart_multi}, + {ST_L2_8, EV_L2_SABME, l2_restart_multi}, + {ST_L2_4, EV_L2_DISC, l2_send_DM}, + {ST_L2_5, EV_L2_DISC, l2_send_DM}, + {ST_L2_6, EV_L2_DISC, l2_send_UA}, + {ST_L2_7, EV_L2_DISC, l2_stop_multi}, + {ST_L2_8, EV_L2_DISC, l2_stop_multi}, + {ST_L2_4, EV_L2_UA, l2_mdl_error_ua}, + {ST_L2_5, EV_L2_UA, l2_connected}, + {ST_L2_6, EV_L2_UA, l2_released}, + {ST_L2_7, EV_L2_UA, l2_mdl_error_ua}, + {ST_L2_8, EV_L2_UA, l2_mdl_error_ua}, + {ST_L2_4, EV_L2_DM, l2_reestablish}, + {ST_L2_5, EV_L2_DM, l2_st5_dm_release}, + {ST_L2_6, EV_L2_DM, l2_st6_dm_release}, + {ST_L2_7, EV_L2_DM, l2_mdl_error_dm}, + {ST_L2_8, EV_L2_DM, l2_st8_mdl_error_dm}, + {ST_L2_1, EV_L2_UI, l2_got_ui}, + {ST_L2_2, EV_L2_UI, l2_got_ui}, + {ST_L2_3, EV_L2_UI, l2_got_ui}, + {ST_L2_4, EV_L2_UI, l2_got_ui}, + {ST_L2_5, EV_L2_UI, l2_got_ui}, + {ST_L2_6, EV_L2_UI, l2_got_ui}, + {ST_L2_7, EV_L2_UI, l2_got_ui}, + {ST_L2_8, EV_L2_UI, l2_got_ui}, + {ST_L2_7, EV_L2_FRMR, l2_got_FRMR}, + {ST_L2_8, EV_L2_FRMR, l2_got_FRMR}, + {ST_L2_7, EV_L2_SUPER, l2_st7_got_super}, + {ST_L2_8, EV_L2_SUPER, l2_st8_got_super}, + {ST_L2_7, EV_L2_I, l2_got_iframe}, + {ST_L2_8, EV_L2_I, l2_got_iframe}, + {ST_L2_5, EV_L2_T200, l2_st5_tout_200}, + {ST_L2_6, EV_L2_T200, l2_st6_tout_200}, + {ST_L2_7, EV_L2_T200, l2_st7_tout_200}, + {ST_L2_8, EV_L2_T200, l2_st8_tout_200}, + {ST_L2_7, EV_L2_T203, l2_st7_tout_203}, + {ST_L2_7, EV_L2_ACK_PULL, l2_pull_iqueue}, + {ST_L2_7, EV_L2_SET_OWN_BUSY, l2_set_own_busy}, + {ST_L2_8, EV_L2_SET_OWN_BUSY, l2_set_own_busy}, + {ST_L2_7, EV_L2_CLEAR_OWN_BUSY, l2_clear_own_busy}, + {ST_L2_8, EV_L2_CLEAR_OWN_BUSY, l2_clear_own_busy}, + {ST_L2_4, EV_L2_FRAME_ERROR, l2_frame_error}, + {ST_L2_5, EV_L2_FRAME_ERROR, l2_frame_error}, + {ST_L2_6, EV_L2_FRAME_ERROR, l2_frame_error}, + {ST_L2_7, EV_L2_FRAME_ERROR, l2_frame_error_reest}, + {ST_L2_8, EV_L2_FRAME_ERROR, l2_frame_error_reest}, + {ST_L2_1, EV_L1_DEACTIVATE, l2_st14_persistant_da}, + {ST_L2_2, EV_L1_DEACTIVATE, l2_st24_tei_remove}, + {ST_L2_3, EV_L1_DEACTIVATE, l2_st3_tei_remove}, + {ST_L2_4, EV_L1_DEACTIVATE, l2_st14_persistant_da}, + {ST_L2_5, EV_L1_DEACTIVATE, l2_st5_persistant_da}, + {ST_L2_6, EV_L1_DEACTIVATE, l2_st6_persistant_da}, + {ST_L2_7, EV_L1_DEACTIVATE, l2_persistant_da}, + {ST_L2_8, EV_L1_DEACTIVATE, l2_persistant_da}, +}; + +#define L2_FN_COUNT (sizeof(L2FnList)/sizeof(struct FsmNode)) + +static void +isdnl2_l1l2(struct PStack *st, int pr, void *arg) +{ + struct sk_buff *skb = arg; + u_char *datap; + int ret = 1, len; + int c = 0; + + switch (pr) { + case (PH_DATA | INDICATION): + datap = skb->data; + len = l2addrsize(&st->l2); + if (skb->len > len) + datap += len; + else { + FsmEvent(&st->l2.l2m, EV_L2_FRAME_ERROR, (void *) 'N'); + FreeSkb(skb); + return; + } + if (!(*datap & 1)) { /* I-Frame */ + if(!(c = iframe_error(st, skb))) + ret = FsmEvent(&st->l2.l2m, EV_L2_I, skb); + } else if (IsSFrame(datap, st)) { /* S-Frame */ + if(!(c = super_error(st, skb))) + ret = FsmEvent(&st->l2.l2m, EV_L2_SUPER, skb); + } else if (IsUI(datap)) { + if(!(c = UI_error(st, skb))) + ret = FsmEvent(&st->l2.l2m, EV_L2_UI, skb); + } else if (IsSABME(datap, st)) { + if(!(c = unnum_error(st, skb, CMD))) + ret = FsmEvent(&st->l2.l2m, EV_L2_SABME, skb); + } else if (IsUA(datap)) { + if(!(c = unnum_error(st, skb, RSP))) + ret = FsmEvent(&st->l2.l2m, EV_L2_UA, skb); + } else if (IsDISC(datap)) { + if(!(c = unnum_error(st, skb, CMD))) + ret = FsmEvent(&st->l2.l2m, EV_L2_DISC, skb); + } else if (IsDM(datap)) { + if(!(c = unnum_error(st, skb, RSP))) + ret = FsmEvent(&st->l2.l2m, EV_L2_DM, skb); + } else if (IsFRMR(datap)) { + if(!(c = FRMR_error(st,skb))) + ret = FsmEvent(&st->l2.l2m, EV_L2_FRMR, skb); + } else { + FsmEvent(&st->l2.l2m, EV_L2_FRAME_ERROR, (void *) 'L'); + FreeSkb(skb); + ret = 0; + } + if(c) { + FreeSkb(skb); + FsmEvent(&st->l2.l2m, EV_L2_FRAME_ERROR, (void *)(long)c); + ret = 0; + } + if (ret) + FreeSkb(skb); + break; + case (PH_PULL | CONFIRM): + FsmEvent(&st->l2.l2m, EV_L2_ACK_PULL, arg); + break; + case (PH_PAUSE | INDICATION): + test_and_set_bit(FLG_DCHAN_BUSY, &st->l2.flag); + break; + case (PH_PAUSE | CONFIRM): + test_and_clear_bit(FLG_DCHAN_BUSY, &st->l2.flag); + break; + case (PH_ACTIVATE | CONFIRM): + case (PH_ACTIVATE | INDICATION): + test_and_set_bit(FLG_L1_ACTIV, &st->l2.flag); + if (test_and_clear_bit(FLG_ESTAB_PEND, &st->l2.flag)) + FsmEvent(&st->l2.l2m, EV_L2_DL_ESTABLISH_REQ, arg); + break; + case (PH_DEACTIVATE | INDICATION): + case (PH_DEACTIVATE | CONFIRM): + test_and_clear_bit(FLG_L1_ACTIV, &st->l2.flag); + FsmEvent(&st->l2.l2m, EV_L1_DEACTIVATE, arg); + break; + default: + l2m_debug(&st->l2.l2m, "l2 unknown pr %04x", pr); + break; + } +} + +static void +isdnl2_l3l2(struct PStack *st, int pr, void *arg) +{ + switch (pr) { + case (DL_DATA | REQUEST): + if (FsmEvent(&st->l2.l2m, EV_L2_DL_DATA, arg)) { + dev_kfree_skb((struct sk_buff *) arg); + } + break; + case (DL_UNIT_DATA | REQUEST): + if (FsmEvent(&st->l2.l2m, EV_L2_DL_UNIT_DATA, arg)) { + dev_kfree_skb((struct sk_buff *) arg); + } + break; + case (DL_ESTABLISH | REQUEST): + if (test_bit(FLG_L1_ACTIV, &st->l2.flag)) { + if (test_bit(FLG_LAPD, &st->l2.flag) || + test_bit(FLG_ORIG, &st->l2.flag)) { + FsmEvent(&st->l2.l2m, EV_L2_DL_ESTABLISH_REQ, arg); + } + } else { + if (test_bit(FLG_LAPD, &st->l2.flag) || + test_bit(FLG_ORIG, &st->l2.flag)) { + test_and_set_bit(FLG_ESTAB_PEND, &st->l2.flag); + } + st->l2.l2l1(st, PH_ACTIVATE, NULL); + } + break; + case (DL_RELEASE | REQUEST): + if (test_bit(FLG_LAPB, &st->l2.flag)) { + st->l2.l2l1(st, PH_DEACTIVATE, NULL); + } + FsmEvent(&st->l2.l2m, EV_L2_DL_RELEASE_REQ, arg); + break; + case (MDL_ASSIGN | REQUEST): + FsmEvent(&st->l2.l2m, EV_L2_MDL_ASSIGN, arg); + break; + case (MDL_REMOVE | REQUEST): + FsmEvent(&st->l2.l2m, EV_L2_MDL_REMOVE, arg); + break; + case (MDL_ERROR | RESPONSE): + FsmEvent(&st->l2.l2m, EV_L2_MDL_ERROR, arg); + break; + } +} + +void +releasestack_isdnl2(struct PStack *st) +{ + FsmDelTimer(&st->l2.t200, 21); + FsmDelTimer(&st->l2.t203, 16); + skb_queue_purge(&st->l2.i_queue); + skb_queue_purge(&st->l2.ui_queue); + ReleaseWin(&st->l2); +} + +static void +l2m_debug(struct FsmInst *fi, char *fmt, ...) +{ + va_list args; + struct PStack *st = fi->userdata; + + va_start(args, fmt); + VHiSax_putstatus(st->l1.hardware, st->l2.debug_id, fmt, args); + va_end(args); +} + +void +setstack_isdnl2(struct PStack *st, char *debug_id) +{ + spin_lock_init(&st->l2.lock); + st->l1.l1l2 = isdnl2_l1l2; + st->l3.l3l2 = isdnl2_l3l2; + + skb_queue_head_init(&st->l2.i_queue); + skb_queue_head_init(&st->l2.ui_queue); + InitWin(&st->l2); + st->l2.debug = 0; + + st->l2.l2m.fsm = &l2fsm; + if (test_bit(FLG_LAPB, &st->l2.flag)) + st->l2.l2m.state = ST_L2_4; + else + st->l2.l2m.state = ST_L2_1; + st->l2.l2m.debug = 0; + st->l2.l2m.userdata = st; + st->l2.l2m.userint = 0; + st->l2.l2m.printdebug = l2m_debug; + strcpy(st->l2.debug_id, debug_id); + + FsmInitTimer(&st->l2.l2m, &st->l2.t200); + FsmInitTimer(&st->l2.l2m, &st->l2.t203); +} + +static void +transl2_l3l2(struct PStack *st, int pr, void *arg) +{ + switch (pr) { + case (DL_DATA | REQUEST): + case (DL_UNIT_DATA | REQUEST): + st->l2.l2l1(st, PH_DATA | REQUEST, arg); + break; + case (DL_ESTABLISH | REQUEST): + st->l2.l2l1(st, PH_ACTIVATE | REQUEST, NULL); + break; + case (DL_RELEASE | REQUEST): + st->l2.l2l1(st, PH_DEACTIVATE | REQUEST, NULL); + break; + } +} + +void +setstack_transl2(struct PStack *st) +{ + st->l3.l3l2 = transl2_l3l2; +} + +void +releasestack_transl2(struct PStack *st) +{ +} + +int __init +Isdnl2New(void) +{ + l2fsm.state_count = L2_STATE_COUNT; + l2fsm.event_count = L2_EVENT_COUNT; + l2fsm.strEvent = strL2Event; + l2fsm.strState = strL2State; + return FsmNew(&l2fsm, L2FnList, L2_FN_COUNT); +} + +void +Isdnl2Free(void) +{ + FsmFree(&l2fsm); +} diff --git a/drivers/isdn/hisax/isdnl2.h b/drivers/isdn/hisax/isdnl2.h new file mode 100644 index 000000000000..0cdab1b73fac --- /dev/null +++ b/drivers/isdn/hisax/isdnl2.h @@ -0,0 +1,26 @@ +/* $Id: isdnl2.h,v 1.3.6.2 2001/09/23 22:24:49 kai Exp $ + * + * Layer 2 defines + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#define RR 0x01 +#define RNR 0x05 +#define REJ 0x09 +#define SABME 0x6f +#define SABM 0x2f +#define DM 0x0f +#define UI 0x03 +#define DISC 0x43 +#define UA 0x63 +#define FRMR 0x87 +#define XID 0xaf + +#define CMD 0 +#define RSP 1 + +#define LC_FLUSH_WAIT 1 + diff --git a/drivers/isdn/hisax/isdnl3.c b/drivers/isdn/hisax/isdnl3.c new file mode 100644 index 000000000000..f571b5d18e91 --- /dev/null +++ b/drivers/isdn/hisax/isdnl3.c @@ -0,0 +1,610 @@ +/* $Id: isdnl3.c,v 2.22.2.3 2004/01/13 14:31:25 keil Exp $ + * + * Author Karsten Keil + * based on the teles driver from Jan den Ouden + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * For changes and modifications please read + * Documentation/isdn/HiSax.cert + * + * Thanks to Jan den Ouden + * Fritz Elfert + * + */ + +#include <linux/init.h> +#include "hisax.h" +#include "isdnl3.h" +#include <linux/config.h> + +const char *l3_revision = "$Revision: 2.22.2.3 $"; + +static struct Fsm l3fsm; + +enum { + ST_L3_LC_REL, + ST_L3_LC_ESTAB_WAIT, + ST_L3_LC_REL_DELAY, + ST_L3_LC_REL_WAIT, + ST_L3_LC_ESTAB, +}; + +#define L3_STATE_COUNT (ST_L3_LC_ESTAB+1) + +static char *strL3State[] = +{ + "ST_L3_LC_REL", + "ST_L3_LC_ESTAB_WAIT", + "ST_L3_LC_REL_DELAY", + "ST_L3_LC_REL_WAIT", + "ST_L3_LC_ESTAB", +}; + +enum { + EV_ESTABLISH_REQ, + EV_ESTABLISH_IND, + EV_ESTABLISH_CNF, + EV_RELEASE_REQ, + EV_RELEASE_CNF, + EV_RELEASE_IND, + EV_TIMEOUT, +}; + +#define L3_EVENT_COUNT (EV_TIMEOUT+1) + +static char *strL3Event[] = +{ + "EV_ESTABLISH_REQ", + "EV_ESTABLISH_IND", + "EV_ESTABLISH_CNF", + "EV_RELEASE_REQ", + "EV_RELEASE_CNF", + "EV_RELEASE_IND", + "EV_TIMEOUT", +}; + +static void +l3m_debug(struct FsmInst *fi, char *fmt, ...) +{ + va_list args; + struct PStack *st = fi->userdata; + + va_start(args, fmt); + VHiSax_putstatus(st->l1.hardware, st->l3.debug_id, fmt, args); + va_end(args); +} + +u_char * +findie(u_char * p, int size, u_char ie, int wanted_set) +{ + int l, codeset, maincodeset; + u_char *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) + { /* improved length check (Werner Cornelius) */ + if ((pend - p) < 2) + return(NULL); + if (*(p+1) > (pend - (p+2))) + return(NULL); + return (p); + } + + if (*p > ie) + return (NULL); + } + p++; + l = *p++; + p += l; + codeset = maincodeset; + } + } + return (NULL); +} + +int +getcallref(u_char * p) +{ + int l, cr = 0; + + p++; /* prot discr */ + if (*p & 0xfe) /* wrong callref BRI only 1 octet*/ + return(-2); + l = 0xf & *p++; /* callref length */ + if (!l) /* dummy CallRef */ + return(-1); + cr = *p++; + return (cr); +} + +static int OrigCallRef = 0; + +int +newcallref(void) +{ + if (OrigCallRef == 127) + OrigCallRef = 1; + else + OrigCallRef++; + return (OrigCallRef); +} + +void +newl3state(struct l3_process *pc, int state) +{ + if (pc->debug & L3_DEB_STATE) + l3_debug(pc->st, "newstate cr %d %d --> %d", + pc->callref & 0x7F, + pc->state, state); + pc->state = state; +} + +static void +L3ExpireTimer(struct L3Timer *t) +{ + t->pc->st->lli.l4l3(t->pc->st, t->event, t->pc); +} + +void +L3InitTimer(struct l3_process *pc, struct L3Timer *t) +{ + t->pc = pc; + t->tl.function = (void *) L3ExpireTimer; + t->tl.data = (long) t; + init_timer(&t->tl); +} + +void +L3DelTimer(struct L3Timer *t) +{ + del_timer(&t->tl); +} + +int +L3AddTimer(struct L3Timer *t, + int millisec, int event) +{ + if (timer_pending(&t->tl)) { + printk(KERN_WARNING "L3AddTimer: timer already active!\n"); + return -1; + } + init_timer(&t->tl); + t->event = event; + t->tl.expires = jiffies + (millisec * HZ) / 1000; + add_timer(&t->tl); + return 0; +} + +void +StopAllL3Timer(struct l3_process *pc) +{ + L3DelTimer(&pc->timer); +} + +struct sk_buff * +l3_alloc_skb(int len) +{ + struct sk_buff *skb; + + if (!(skb = alloc_skb(len + MAX_HEADER_LEN, GFP_ATOMIC))) { + printk(KERN_WARNING "HiSax: No skb for D-channel\n"); + return (NULL); + } + skb_reserve(skb, MAX_HEADER_LEN); + return (skb); +} + +static void +no_l3_proto(struct PStack *st, int pr, void *arg) +{ + struct sk_buff *skb = arg; + + HiSax_putstatus(st->l1.hardware, "L3", "no D protocol"); + if (skb) { + dev_kfree_skb(skb); + } +} + +static int +no_l3_proto_spec(struct PStack *st, isdn_ctrl *ic) +{ + printk(KERN_WARNING "HiSax: no specific protocol handler for proto %lu\n",ic->arg & 0xFF); + return(-1); +} + +#ifdef CONFIG_HISAX_EURO +extern void setstack_dss1(struct PStack *st); +#endif + +#ifdef CONFIG_HISAX_NI1 +extern void setstack_ni1(struct PStack *st); +#endif + +#ifdef CONFIG_HISAX_1TR6 +extern void setstack_1tr6(struct PStack *st); +#endif + +struct l3_process +*getl3proc(struct PStack *st, int cr) +{ + struct l3_process *p = st->l3.proc; + + while (p) + if (p->callref == cr) + return (p); + else + p = p->next; + return (NULL); +} + +struct l3_process +*new_l3_process(struct PStack *st, int cr) +{ + struct l3_process *p, *np; + + if (!(p = kmalloc(sizeof(struct l3_process), GFP_ATOMIC))) { + printk(KERN_ERR "HiSax can't get memory for cr %d\n", cr); + return (NULL); + } + if (!st->l3.proc) + st->l3.proc = p; + else { + np = st->l3.proc; + while (np->next) + np = np->next; + np->next = p; + } + p->next = NULL; + p->debug = st->l3.debug; + p->callref = cr; + p->state = 0; + p->chan = NULL; + p->st = st; + p->N303 = st->l3.N303; + L3InitTimer(p, &p->timer); + return (p); +}; + +void +release_l3_process(struct l3_process *p) +{ + struct l3_process *np, *pp = NULL; + + if (!p) + return; + np = p->st->l3.proc; + while (np) { + if (np == p) { + StopAllL3Timer(p); + if (pp) + pp->next = np->next; + else if (!(p->st->l3.proc = np->next) && + !test_bit(FLG_PTP, &p->st->l2.flag)) { + if (p->debug) + l3_debug(p->st, "release_l3_process: last process"); + if (!skb_queue_len(&p->st->l3.squeue)) { + if (p->debug) + l3_debug(p->st, "release_l3_process: release link"); + if (p->st->protocol != ISDN_PTYPE_NI1) + FsmEvent(&p->st->l3.l3m, EV_RELEASE_REQ, NULL); + else + FsmEvent(&p->st->l3.l3m, EV_RELEASE_IND, NULL); + } else { + if (p->debug) + l3_debug(p->st, "release_l3_process: not release link"); + } + } + kfree(p); + return; + } + pp = np; + np = np->next; + } + printk(KERN_ERR "HiSax internal L3 error CR(%d) not in list\n", p->callref); + l3_debug(p->st, "HiSax internal L3 error CR(%d) not in list", p->callref); +}; + +static void +l3ml3p(struct PStack *st, int pr) +{ + struct l3_process *p = st->l3.proc; + struct l3_process *np; + + while (p) { + /* p might be kfreed under us, so we need to save where we want to go on */ + np = p->next; + st->l3.l3ml3(st, pr, p); + p = np; + } +} + +void +setstack_l3dc(struct PStack *st, struct Channel *chanp) +{ + char tmp[64]; + + st->l3.proc = NULL; + st->l3.global = NULL; + skb_queue_head_init(&st->l3.squeue); + st->l3.l3m.fsm = &l3fsm; + st->l3.l3m.state = ST_L3_LC_REL; + st->l3.l3m.debug = 1; + st->l3.l3m.userdata = st; + st->l3.l3m.userint = 0; + st->l3.l3m.printdebug = l3m_debug; + FsmInitTimer(&st->l3.l3m, &st->l3.l3m_timer); + strcpy(st->l3.debug_id, "L3DC "); + st->lli.l4l3_proto = no_l3_proto_spec; + +#ifdef CONFIG_HISAX_EURO + if (st->protocol == ISDN_PTYPE_EURO) { + setstack_dss1(st); + } else +#endif +#ifdef CONFIG_HISAX_NI1 + if (st->protocol == ISDN_PTYPE_NI1) { + setstack_ni1(st); + } else +#endif +#ifdef CONFIG_HISAX_1TR6 + if (st->protocol == ISDN_PTYPE_1TR6) { + setstack_1tr6(st); + } else +#endif + if (st->protocol == ISDN_PTYPE_LEASED) { + st->lli.l4l3 = no_l3_proto; + st->l2.l2l3 = no_l3_proto; + st->l3.l3ml3 = no_l3_proto; + printk(KERN_INFO "HiSax: Leased line mode\n"); + } else { + st->lli.l4l3 = no_l3_proto; + st->l2.l2l3 = no_l3_proto; + st->l3.l3ml3 = no_l3_proto; + sprintf(tmp, "protocol %s not supported", + (st->protocol == ISDN_PTYPE_1TR6) ? "1tr6" : + (st->protocol == ISDN_PTYPE_EURO) ? "euro" : + (st->protocol == ISDN_PTYPE_NI1) ? "ni1" : + "unknown"); + printk(KERN_WARNING "HiSax: %s\n", tmp); + st->protocol = -1; + } +} + +void +isdnl3_trans(struct PStack *st, int pr, void *arg) { + st->l3.l3l2(st, pr, arg); +} + +void +releasestack_isdnl3(struct PStack *st) +{ + while (st->l3.proc) + release_l3_process(st->l3.proc); + if (st->l3.global) { + StopAllL3Timer(st->l3.global); + kfree(st->l3.global); + st->l3.global = NULL; + } + FsmDelTimer(&st->l3.l3m_timer, 54); + skb_queue_purge(&st->l3.squeue); +} + +void +setstack_l3bc(struct PStack *st, struct Channel *chanp) +{ + + st->l3.proc = NULL; + st->l3.global = NULL; + skb_queue_head_init(&st->l3.squeue); + st->l3.l3m.fsm = &l3fsm; + st->l3.l3m.state = ST_L3_LC_REL; + st->l3.l3m.debug = 1; + st->l3.l3m.userdata = st; + st->l3.l3m.userint = 0; + st->l3.l3m.printdebug = l3m_debug; + strcpy(st->l3.debug_id, "L3BC "); + st->lli.l4l3 = isdnl3_trans; +} + +#define DREL_TIMER_VALUE 40000 + +static void +lc_activate(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + FsmChangeState(fi, ST_L3_LC_ESTAB_WAIT); + st->l3.l3l2(st, DL_ESTABLISH | REQUEST, NULL); +} + +static void +lc_connect(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + int dequeued = 0; + + FsmChangeState(fi, ST_L3_LC_ESTAB); + while ((skb = skb_dequeue(&st->l3.squeue))) { + st->l3.l3l2(st, DL_DATA | REQUEST, skb); + dequeued++; + } + if ((!st->l3.proc) && dequeued) { + if (st->l3.debug) + l3_debug(st, "lc_connect: release link"); + FsmEvent(&st->l3.l3m, EV_RELEASE_REQ, NULL); + } else + l3ml3p(st, DL_ESTABLISH | INDICATION); +} + +static void +lc_connected(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + int dequeued = 0; + + FsmDelTimer(&st->l3.l3m_timer, 51); + FsmChangeState(fi, ST_L3_LC_ESTAB); + while ((skb = skb_dequeue(&st->l3.squeue))) { + st->l3.l3l2(st, DL_DATA | REQUEST, skb); + dequeued++; + } + if ((!st->l3.proc) && dequeued) { + if (st->l3.debug) + l3_debug(st, "lc_connected: release link"); + FsmEvent(&st->l3.l3m, EV_RELEASE_REQ, NULL); + } else + l3ml3p(st, DL_ESTABLISH | CONFIRM); +} + +static void +lc_start_delay(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + FsmChangeState(fi, ST_L3_LC_REL_DELAY); + FsmAddTimer(&st->l3.l3m_timer, DREL_TIMER_VALUE, EV_TIMEOUT, NULL, 50); +} + +static void +lc_start_delay_check(struct FsmInst *fi, int event, void *arg) +/* 20/09/00 - GE timer not user for NI-1 as layer 2 should stay up */ +{ + struct PStack *st = fi->userdata; + + FsmChangeState(fi, ST_L3_LC_REL_DELAY); + /* 19/09/00 - GE timer not user for NI-1 */ + if (st->protocol != ISDN_PTYPE_NI1) + FsmAddTimer(&st->l3.l3m_timer, DREL_TIMER_VALUE, EV_TIMEOUT, NULL, 50); +} + +static void +lc_release_req(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + if (test_bit(FLG_L2BLOCK, &st->l2.flag)) { + if (st->l3.debug) + l3_debug(st, "lc_release_req: l2 blocked"); + /* restart release timer */ + FsmAddTimer(&st->l3.l3m_timer, DREL_TIMER_VALUE, EV_TIMEOUT, NULL, 51); + } else { + FsmChangeState(fi, ST_L3_LC_REL_WAIT); + st->l3.l3l2(st, DL_RELEASE | REQUEST, NULL); + } +} + +static void +lc_release_ind(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + FsmDelTimer(&st->l3.l3m_timer, 52); + FsmChangeState(fi, ST_L3_LC_REL); + skb_queue_purge(&st->l3.squeue); + l3ml3p(st, DL_RELEASE | INDICATION); +} + +static void +lc_release_cnf(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + FsmChangeState(fi, ST_L3_LC_REL); + skb_queue_purge(&st->l3.squeue); + l3ml3p(st, DL_RELEASE | CONFIRM); +} + + +/* *INDENT-OFF* */ +static struct FsmNode L3FnList[] __initdata = +{ + {ST_L3_LC_REL, EV_ESTABLISH_REQ, lc_activate}, + {ST_L3_LC_REL, EV_ESTABLISH_IND, lc_connect}, + {ST_L3_LC_REL, EV_ESTABLISH_CNF, lc_connect}, + {ST_L3_LC_ESTAB_WAIT, EV_ESTABLISH_CNF, lc_connected}, + {ST_L3_LC_ESTAB_WAIT, EV_RELEASE_REQ, lc_start_delay}, + {ST_L3_LC_ESTAB_WAIT, EV_RELEASE_IND, lc_release_ind}, + {ST_L3_LC_ESTAB, EV_RELEASE_IND, lc_release_ind}, + {ST_L3_LC_ESTAB, EV_RELEASE_REQ, lc_start_delay_check}, + {ST_L3_LC_REL_DELAY, EV_RELEASE_IND, lc_release_ind}, + {ST_L3_LC_REL_DELAY, EV_ESTABLISH_REQ, lc_connected}, + {ST_L3_LC_REL_DELAY, EV_TIMEOUT, lc_release_req}, + {ST_L3_LC_REL_WAIT, EV_RELEASE_CNF, lc_release_cnf}, + {ST_L3_LC_REL_WAIT, EV_ESTABLISH_REQ, lc_activate}, +}; +/* *INDENT-ON* */ + +#define L3_FN_COUNT (sizeof(L3FnList)/sizeof(struct FsmNode)) + +void +l3_msg(struct PStack *st, int pr, void *arg) +{ + switch (pr) { + case (DL_DATA | REQUEST): + if (st->l3.l3m.state == ST_L3_LC_ESTAB) { + st->l3.l3l2(st, pr, arg); + } else { + struct sk_buff *skb = arg; + + skb_queue_tail(&st->l3.squeue, skb); + FsmEvent(&st->l3.l3m, EV_ESTABLISH_REQ, NULL); + } + break; + case (DL_ESTABLISH | REQUEST): + FsmEvent(&st->l3.l3m, EV_ESTABLISH_REQ, NULL); + break; + case (DL_ESTABLISH | CONFIRM): + FsmEvent(&st->l3.l3m, EV_ESTABLISH_CNF, NULL); + break; + case (DL_ESTABLISH | INDICATION): + FsmEvent(&st->l3.l3m, EV_ESTABLISH_IND, NULL); + break; + case (DL_RELEASE | INDICATION): + FsmEvent(&st->l3.l3m, EV_RELEASE_IND, NULL); + break; + case (DL_RELEASE | CONFIRM): + FsmEvent(&st->l3.l3m, EV_RELEASE_CNF, NULL); + break; + case (DL_RELEASE | REQUEST): + FsmEvent(&st->l3.l3m, EV_RELEASE_REQ, NULL); + break; + } +} + +int __init +Isdnl3New(void) +{ + l3fsm.state_count = L3_STATE_COUNT; + l3fsm.event_count = L3_EVENT_COUNT; + l3fsm.strEvent = strL3Event; + l3fsm.strState = strL3State; + return FsmNew(&l3fsm, L3FnList, L3_FN_COUNT); +} + +void +Isdnl3Free(void) +{ + FsmFree(&l3fsm); +} diff --git a/drivers/isdn/hisax/isdnl3.h b/drivers/isdn/hisax/isdnl3.h new file mode 100644 index 000000000000..1dbe0297a506 --- /dev/null +++ b/drivers/isdn/hisax/isdnl3.h @@ -0,0 +1,37 @@ +/* $Id: isdnl3.h,v 2.6.6.2 2001/09/23 22:24:49 kai Exp $ + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#define SBIT(state) (1<<state) +#define ALL_STATES 0x03ffffff + +#define PROTO_DIS_EURO 0x08 + +#define L3_DEB_WARN 0x01 +#define L3_DEB_PROTERR 0x02 +#define L3_DEB_STATE 0x04 +#define L3_DEB_CHARGE 0x08 +#define L3_DEB_CHECK 0x10 +#define L3_DEB_SI 0x20 + +struct stateentry { + int state; + int primitive; + void (*rout) (struct l3_process *, u8, void *); +}; + +#define l3_debug(st, fmt, args...) HiSax_putstatus(st->l1.hardware, "l3 ", fmt, ## args) + +extern void newl3state(struct l3_process *pc, int state); +extern void L3InitTimer(struct l3_process *pc, struct L3Timer *t); +extern void L3DelTimer(struct L3Timer *t); +extern int L3AddTimer(struct L3Timer *t, int millisec, int event); +extern void StopAllL3Timer(struct l3_process *pc); +extern struct sk_buff *l3_alloc_skb(int len); +extern struct l3_process *new_l3_process(struct PStack *st, int cr); +extern void release_l3_process(struct l3_process *p); +extern struct l3_process *getl3proc(struct PStack *st, int cr); +extern void l3_msg(struct PStack *st, int pr, void *arg); diff --git a/drivers/isdn/hisax/isurf.c b/drivers/isdn/hisax/isurf.c new file mode 100644 index 000000000000..af5171da7345 --- /dev/null +++ b/drivers/isdn/hisax/isurf.c @@ -0,0 +1,306 @@ +/* $Id: isurf.c,v 1.12.2.4 2004/01/13 21:46:03 keil Exp $ + * + * low level stuff for Siemens I-Surf/I-Talk cards + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/init.h> +#include "hisax.h" +#include "isac.h" +#include "isar.h" +#include "isdnl1.h" +#include <linux/isapnp.h> + +extern const char *CardType[]; + +static const char *ISurf_revision = "$Revision: 1.12.2.4 $"; + +#define byteout(addr,val) outb(val,addr) +#define bytein(addr) inb(addr) + +#define ISURF_ISAR_RESET 1 +#define ISURF_ISAC_RESET 2 +#define ISURF_ISAR_EA 4 +#define ISURF_ARCOFI_RESET 8 +#define ISURF_RESET (ISURF_ISAR_RESET | ISURF_ISAC_RESET | ISURF_ARCOFI_RESET) + +#define ISURF_ISAR_OFFSET 0 +#define ISURF_ISAC_OFFSET 0x100 +#define ISURF_IOMEM_SIZE 0x400 +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + return (readb(cs->hw.isurf.isac + offset)); +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writeb(value, cs->hw.isurf.isac + offset); mb(); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + register int i; + for (i = 0; i < size; i++) + data[i] = readb(cs->hw.isurf.isac); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + register int i; + for (i = 0; i < size; i++){ + writeb(data[i], cs->hw.isurf.isac);mb(); + } +} + +/* ISAR access routines + * mode = 0 access with IRQ on + * mode = 1 access with IRQ off + * mode = 2 access with IRQ off and using last offset + */ + +static u_char +ReadISAR(struct IsdnCardState *cs, int mode, u_char offset) +{ + return(readb(cs->hw.isurf.isar + offset)); +} + +static void +WriteISAR(struct IsdnCardState *cs, int mode, u_char offset, u_char value) +{ + writeb(value, cs->hw.isurf.isar + offset);mb(); +} + +static irqreturn_t +isurf_interrupt(int intno, void *dev_id, struct pt_regs *regs) +{ + struct IsdnCardState *cs = dev_id; + u_char val; + int cnt = 5; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + val = readb(cs->hw.isurf.isar + ISAR_IRQBIT); + Start_ISAR: + if (val & ISAR_IRQSTA) + isar_int_main(cs); + val = readb(cs->hw.isurf.isac + ISAC_ISTA); + Start_ISAC: + if (val) + isac_interrupt(cs, val); + val = readb(cs->hw.isurf.isar + ISAR_IRQBIT); + if ((val & ISAR_IRQSTA) && --cnt) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "ISAR IntStat after IntRoutine"); + goto Start_ISAR; + } + val = readb(cs->hw.isurf.isac + ISAC_ISTA); + if (val && --cnt) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ISAC IntStat after IntRoutine"); + goto Start_ISAC; + } + if (!cnt) + printk(KERN_WARNING "ISurf IRQ LOOP\n"); + + writeb(0, cs->hw.isurf.isar + ISAR_IRQBIT); mb(); + writeb(0xFF, cs->hw.isurf.isac + ISAC_MASK);mb(); + writeb(0, cs->hw.isurf.isac + ISAC_MASK);mb(); + writeb(ISAR_IRQMSK, cs->hw.isurf.isar + ISAR_IRQBIT); mb(); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +void +release_io_isurf(struct IsdnCardState *cs) +{ + release_region(cs->hw.isurf.reset, 1); + iounmap(cs->hw.isurf.isar); + release_mem_region(cs->hw.isurf.phymem, ISURF_IOMEM_SIZE); +} + +static void +reset_isurf(struct IsdnCardState *cs, u_char chips) +{ + printk(KERN_INFO "ISurf: resetting card\n"); + + byteout(cs->hw.isurf.reset, chips); /* Reset On */ + mdelay(10); + byteout(cs->hw.isurf.reset, ISURF_ISAR_EA); /* Reset Off */ + mdelay(10); +} + +static int +ISurf_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + reset_isurf(cs, ISURF_RESET); + spin_unlock_irqrestore(&cs->lock, flags); + return(0); + case CARD_RELEASE: + release_io_isurf(cs); + return(0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + reset_isurf(cs, ISURF_RESET); + clear_pending_isac_ints(cs); + writeb(0, cs->hw.isurf.isar+ISAR_IRQBIT);mb(); + initisac(cs); + initisar(cs); + /* Reenable ISAC IRQ */ + cs->writeisac(cs, ISAC_MASK, 0); + /* RESET Receiver and Transmitter */ + cs->writeisac(cs, ISAC_CMDR, 0x41); + spin_unlock_irqrestore(&cs->lock, flags); + return(0); + case CARD_TEST: + return(0); + } + return(0); +} + +static int +isurf_auxcmd(struct IsdnCardState *cs, isdn_ctrl *ic) { + int ret; + u_long flags; + + if ((ic->command == ISDN_CMD_IOCTL) && (ic->arg == 9)) { + ret = isar_auxcmd(cs, ic); + spin_lock_irqsave(&cs->lock, flags); + if (!ret) { + reset_isurf(cs, ISURF_ISAR_EA | ISURF_ISAC_RESET | + ISURF_ARCOFI_RESET); + initisac(cs); + cs->writeisac(cs, ISAC_MASK, 0); + cs->writeisac(cs, ISAC_CMDR, 0x41); + } + spin_unlock_irqrestore(&cs->lock, flags); + return(ret); + } + return(isar_auxcmd(cs, ic)); +} + +#ifdef __ISAPNP__ +static struct pnp_card *pnp_c __initdata = NULL; +#endif + +int __init +setup_isurf(struct IsdnCard *card) +{ + int ver; + struct IsdnCardState *cs = card->cs; + char tmp[64]; + + strcpy(tmp, ISurf_revision); + printk(KERN_INFO "HiSax: ISurf driver Rev. %s\n", HiSax_getrev(tmp)); + + if (cs->typ != ISDN_CTYPE_ISURF) + return(0); + if (card->para[1] && card->para[2]) { + cs->hw.isurf.reset = card->para[1]; + cs->hw.isurf.phymem = card->para[2]; + cs->irq = card->para[0]; + } else { +#ifdef __ISAPNP__ + if (isapnp_present()) { + struct pnp_dev *pnp_d = NULL; + int err; + + cs->subtyp = 0; + if ((pnp_c = pnp_find_card( + ISAPNP_VENDOR('S', 'I', 'E'), + ISAPNP_FUNCTION(0x0010), pnp_c))) { + if (!(pnp_d = pnp_find_dev(pnp_c, + ISAPNP_VENDOR('S', 'I', 'E'), + ISAPNP_FUNCTION(0x0010), pnp_d))) { + printk(KERN_ERR "ISurfPnP: PnP error card found, no device\n"); + return (0); + } + pnp_disable_dev(pnp_d); + err = pnp_activate_dev(pnp_d); + cs->hw.isurf.reset = pnp_port_start(pnp_d, 0); + cs->hw.isurf.phymem = pnp_mem_start(pnp_d, 1); + cs->irq = pnp_irq(pnp_d, 0); + if (!cs->irq || !cs->hw.isurf.reset || !cs->hw.isurf.phymem) { + printk(KERN_ERR "ISurfPnP:some resources are missing %d/%x/%lx\n", + cs->irq, cs->hw.isurf.reset, cs->hw.isurf.phymem); + pnp_disable_dev(pnp_d); + return(0); + } + } else { + printk(KERN_INFO "ISurfPnP: no ISAPnP card found\n"); + return(0); + } + } else { + printk(KERN_INFO "ISurfPnP: no ISAPnP bus found\n"); + return(0); + } +#else + printk(KERN_WARNING "HiSax: %s port/mem not set\n", + CardType[card->typ]); + return (0); +#endif + } + if (!request_region(cs->hw.isurf.reset, 1, "isurf isdn")) { + printk(KERN_WARNING + "HiSax: %s config port %x already in use\n", + CardType[card->typ], + cs->hw.isurf.reset); + return (0); + } + if (!request_region(cs->hw.isurf.phymem, ISURF_IOMEM_SIZE, "isurf iomem")) { + printk(KERN_WARNING + "HiSax: %s memory region %lx-%lx already in use\n", + CardType[card->typ], + cs->hw.isurf.phymem, + cs->hw.isurf.phymem + ISURF_IOMEM_SIZE); + release_region(cs->hw.isurf.reset, 1); + return (0); + } + cs->hw.isurf.isar = ioremap(cs->hw.isurf.phymem, ISURF_IOMEM_SIZE); + cs->hw.isurf.isac = cs->hw.isurf.isar + ISURF_ISAC_OFFSET; + printk(KERN_INFO + "ISurf: defined at 0x%x 0x%lx IRQ %d\n", + cs->hw.isurf.reset, + cs->hw.isurf.phymem, + cs->irq); + + setup_isac(cs); + cs->cardmsg = &ISurf_card_msg; + cs->irq_func = &isurf_interrupt; + cs->auxcmd = &isurf_auxcmd; + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->bcs[0].hw.isar.reg = &cs->hw.isurf.isar_r; + cs->bcs[1].hw.isar.reg = &cs->hw.isurf.isar_r; + test_and_set_bit(HW_ISAR, &cs->HW_Flags); + ISACVersion(cs, "ISurf:"); + cs->BC_Read_Reg = &ReadISAR; + cs->BC_Write_Reg = &WriteISAR; + cs->BC_Send_Data = &isar_fill_fifo; + ver = ISARVersion(cs, "ISurf:"); + if (ver < 0) { + printk(KERN_WARNING + "ISurf: wrong ISAR version (ret = %d)\n", ver); + release_io_isurf(cs); + return (0); + } + return (1); +} diff --git a/drivers/isdn/hisax/ix1_micro.c b/drivers/isdn/hisax/ix1_micro.c new file mode 100644 index 000000000000..b843b7509ae2 --- /dev/null +++ b/drivers/isdn/hisax/ix1_micro.c @@ -0,0 +1,318 @@ +/* $Id: ix1_micro.c,v 2.12.2.4 2004/01/13 23:48:39 keil Exp $ + * + * low level stuff for ITK ix1-micro Rev.2 isdn cards + * derived from the original file teles3.c from Karsten Keil + * + * Author Klaus-Peter Nischke + * Copyright by Klaus-Peter Nischke, ITK AG + * <klaus@nischke.do.eunet.de> + * by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * Klaus-Peter Nischke + * Deusener Str. 287 + * 44369 Dortmund + * Germany + */ + +#include <linux/init.h> +#include <linux/isapnp.h> +#include "hisax.h" +#include "isac.h" +#include "hscx.h" +#include "isdnl1.h" + +extern const char *CardType[]; +const char *ix1_revision = "$Revision: 2.12.2.4 $"; + +#define byteout(addr,val) outb(val,addr) +#define bytein(addr) inb(addr) + +#define SPECIAL_PORT_OFFSET 3 + +#define ISAC_COMMAND_OFFSET 2 +#define ISAC_DATA_OFFSET 0 +#define HSCX_COMMAND_OFFSET 2 +#define HSCX_DATA_OFFSET 1 + +#define TIMEOUT 50 + +static inline u_char +readreg(unsigned int ale, unsigned int adr, u_char off) +{ + register u_char ret; + + byteout(ale, off); + ret = bytein(adr); + return (ret); +} + +static inline void +readfifo(unsigned int ale, unsigned int adr, u_char off, u_char * data, int size) +{ + byteout(ale, off); + insb(adr, data, size); +} + + +static inline void +writereg(unsigned int ale, unsigned int adr, u_char off, u_char data) +{ + byteout(ale, off); + byteout(adr, data); +} + +static inline void +writefifo(unsigned int ale, unsigned int adr, u_char off, u_char * data, int size) +{ + byteout(ale, off); + outsb(adr, data, size); +} + +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + return (readreg(cs->hw.ix1.isac_ale, cs->hw.ix1.isac, offset)); +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writereg(cs->hw.ix1.isac_ale, cs->hw.ix1.isac, offset, value); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + readfifo(cs->hw.ix1.isac_ale, cs->hw.ix1.isac, 0, data, size); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + writefifo(cs->hw.ix1.isac_ale, cs->hw.ix1.isac, 0, data, size); +} + +static u_char +ReadHSCX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + return (readreg(cs->hw.ix1.hscx_ale, + cs->hw.ix1.hscx, offset + (hscx ? 0x40 : 0))); +} + +static void +WriteHSCX(struct IsdnCardState *cs, int hscx, u_char offset, u_char value) +{ + writereg(cs->hw.ix1.hscx_ale, + cs->hw.ix1.hscx, offset + (hscx ? 0x40 : 0), value); +} + +#define READHSCX(cs, nr, reg) readreg(cs->hw.ix1.hscx_ale, \ + cs->hw.ix1.hscx, reg + (nr ? 0x40 : 0)) +#define WRITEHSCX(cs, nr, reg, data) writereg(cs->hw.ix1.hscx_ale, \ + cs->hw.ix1.hscx, reg + (nr ? 0x40 : 0), data) + +#define READHSCXFIFO(cs, nr, ptr, cnt) readfifo(cs->hw.ix1.hscx_ale, \ + cs->hw.ix1.hscx, (nr ? 0x40 : 0), ptr, cnt) + +#define WRITEHSCXFIFO(cs, nr, ptr, cnt) writefifo(cs->hw.ix1.hscx_ale, \ + cs->hw.ix1.hscx, (nr ? 0x40 : 0), ptr, cnt) + +#include "hscx_irq.c" + +static irqreturn_t +ix1micro_interrupt(int intno, void *dev_id, struct pt_regs *regs) +{ + struct IsdnCardState *cs = dev_id; + u_char val; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + val = readreg(cs->hw.ix1.hscx_ale, cs->hw.ix1.hscx, HSCX_ISTA + 0x40); + Start_HSCX: + if (val) + hscx_int_main(cs, val); + val = readreg(cs->hw.ix1.isac_ale, cs->hw.ix1.isac, ISAC_ISTA); + Start_ISAC: + if (val) + isac_interrupt(cs, val); + val = readreg(cs->hw.ix1.hscx_ale, cs->hw.ix1.hscx, HSCX_ISTA + 0x40); + if (val) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HSCX IntStat after IntRoutine"); + goto Start_HSCX; + } + val = readreg(cs->hw.ix1.isac_ale, cs->hw.ix1.isac, ISAC_ISTA); + if (val) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ISAC IntStat after IntRoutine"); + goto Start_ISAC; + } + writereg(cs->hw.ix1.hscx_ale, cs->hw.ix1.hscx, HSCX_MASK, 0xFF); + writereg(cs->hw.ix1.hscx_ale, cs->hw.ix1.hscx, HSCX_MASK + 0x40, 0xFF); + writereg(cs->hw.ix1.isac_ale, cs->hw.ix1.isac, ISAC_MASK, 0xFF); + writereg(cs->hw.ix1.isac_ale, cs->hw.ix1.isac, ISAC_MASK, 0); + writereg(cs->hw.ix1.hscx_ale, cs->hw.ix1.hscx, HSCX_MASK, 0); + writereg(cs->hw.ix1.hscx_ale, cs->hw.ix1.hscx, HSCX_MASK + 0x40, 0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +void +release_io_ix1micro(struct IsdnCardState *cs) +{ + if (cs->hw.ix1.cfg_reg) + release_region(cs->hw.ix1.cfg_reg, 4); +} + +static void +ix1_reset(struct IsdnCardState *cs) +{ + int cnt; + + /* reset isac */ + cnt = 3 * (HZ / 10) + 1; + while (cnt--) { + byteout(cs->hw.ix1.cfg_reg + SPECIAL_PORT_OFFSET, 1); + HZDELAY(1); /* wait >=10 ms */ + } + byteout(cs->hw.ix1.cfg_reg + SPECIAL_PORT_OFFSET, 0); +} + +static int +ix1_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + ix1_reset(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return(0); + case CARD_RELEASE: + release_io_ix1micro(cs); + return(0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + ix1_reset(cs); + inithscxisac(cs, 3); + spin_unlock_irqrestore(&cs->lock, flags); + return(0); + case CARD_TEST: + return(0); + } + return(0); +} + +#ifdef __ISAPNP__ +static struct isapnp_device_id itk_ids[] __initdata = { + { ISAPNP_VENDOR('I', 'T', 'K'), ISAPNP_FUNCTION(0x25), + ISAPNP_VENDOR('I', 'T', 'K'), ISAPNP_FUNCTION(0x25), + (unsigned long) "ITK micro 2" }, + { ISAPNP_VENDOR('I', 'T', 'K'), ISAPNP_FUNCTION(0x29), + ISAPNP_VENDOR('I', 'T', 'K'), ISAPNP_FUNCTION(0x29), + (unsigned long) "ITK micro 2." }, + { 0, } +}; + +static struct isapnp_device_id *ipid __initdata = &itk_ids[0]; +static struct pnp_card *pnp_c __devinitdata = NULL; +#endif + + +int __init +setup_ix1micro(struct IsdnCard *card) +{ + struct IsdnCardState *cs = card->cs; + char tmp[64]; + + strcpy(tmp, ix1_revision); + printk(KERN_INFO "HiSax: ITK IX1 driver Rev. %s\n", HiSax_getrev(tmp)); + if (cs->typ != ISDN_CTYPE_IX1MICROR2) + return (0); + +#ifdef __ISAPNP__ + if (!card->para[1] && isapnp_present()) { + struct pnp_dev *pnp_d; + while(ipid->card_vendor) { + if ((pnp_c = pnp_find_card(ipid->card_vendor, + ipid->card_device, pnp_c))) { + pnp_d = NULL; + if ((pnp_d = pnp_find_dev(pnp_c, + ipid->vendor, ipid->function, pnp_d))) { + int err; + + printk(KERN_INFO "HiSax: %s detected\n", + (char *)ipid->driver_data); + pnp_disable_dev(pnp_d); + err = pnp_activate_dev(pnp_d); + if (err<0) { + printk(KERN_WARNING "%s: pnp_activate_dev ret(%d)\n", + __FUNCTION__, err); + return(0); + } + card->para[1] = pnp_port_start(pnp_d, 0); + card->para[0] = pnp_irq(pnp_d, 0); + if (!card->para[0] || !card->para[1]) { + printk(KERN_ERR "ITK PnP:some resources are missing %ld/%lx\n", + card->para[0], card->para[1]); + pnp_disable_dev(pnp_d); + return(0); + } + break; + } else { + printk(KERN_ERR "ITK PnP: PnP error card found, no device\n"); + } + } + ipid++; + pnp_c = NULL; + } + if (!ipid->card_vendor) { + printk(KERN_INFO "ITK PnP: no ISAPnP card found\n"); + return(0); + } + } +#endif + /* IO-Ports */ + cs->hw.ix1.isac_ale = card->para[1] + ISAC_COMMAND_OFFSET; + cs->hw.ix1.hscx_ale = card->para[1] + HSCX_COMMAND_OFFSET; + cs->hw.ix1.isac = card->para[1] + ISAC_DATA_OFFSET; + cs->hw.ix1.hscx = card->para[1] + HSCX_DATA_OFFSET; + cs->hw.ix1.cfg_reg = card->para[1]; + cs->irq = card->para[0]; + if (cs->hw.ix1.cfg_reg) { + if (!request_region(cs->hw.ix1.cfg_reg, 4, "ix1micro cfg")) { + printk(KERN_WARNING + "HiSax: %s config port %x-%x already in use\n", + CardType[card->typ], + cs->hw.ix1.cfg_reg, + cs->hw.ix1.cfg_reg + 4); + return (0); + } + } + printk(KERN_INFO "HiSax: %s config irq:%d io:0x%X\n", + CardType[cs->typ], cs->irq, cs->hw.ix1.cfg_reg); + setup_isac(cs); + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->BC_Read_Reg = &ReadHSCX; + cs->BC_Write_Reg = &WriteHSCX; + cs->BC_Send_Data = &hscx_fill_fifo; + cs->cardmsg = &ix1_card_msg; + cs->irq_func = &ix1micro_interrupt; + ISACVersion(cs, "ix1-Micro:"); + if (HscxVersion(cs, "ix1-Micro:")) { + printk(KERN_WARNING + "ix1-Micro: wrong HSCX versions check IO address\n"); + release_io_ix1micro(cs); + return (0); + } + return (1); +} diff --git a/drivers/isdn/hisax/jade.c b/drivers/isdn/hisax/jade.c new file mode 100644 index 000000000000..f05d52757557 --- /dev/null +++ b/drivers/isdn/hisax/jade.c @@ -0,0 +1,318 @@ +/* $Id: jade.c,v 1.9.2.4 2004/01/14 16:04:48 keil Exp $ + * + * JADE stuff (derived from original hscx.c) + * + * Author Roland Klabunde + * Copyright by Roland Klabunde <R.Klabunde@Berkom.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + + +#include <linux/init.h> +#include "hisax.h" +#include "hscx.h" +#include "jade.h" +#include "isdnl1.h" +#include <linux/interrupt.h> + + +int __init +JadeVersion(struct IsdnCardState *cs, char *s) +{ + int ver,i; + int to = 50; + cs->BC_Write_Reg(cs, -1, 0x50, 0x19); + i=0; + while (to) { + udelay(1); + ver = cs->BC_Read_Reg(cs, -1, 0x60); + to--; + if (ver) + break; + if (!to) { + printk(KERN_INFO "%s JADE version not obtainable\n", s); + return (0); + } + } + /* Wait for the JADE */ + udelay(10); + /* Read version */ + ver = cs->BC_Read_Reg(cs, -1, 0x60); + printk(KERN_INFO "%s JADE version: %d\n", s, ver); + return (1); +} + +/* Write to indirect accessible jade register set */ +static void +jade_write_indirect(struct IsdnCardState *cs, u_char reg, u_char value) +{ + int to = 50; + u_char ret; + + /* Write the data */ + cs->BC_Write_Reg(cs, -1, COMM_JADE+1, value); + /* Say JADE we wanna write indirect reg 'reg' */ + cs->BC_Write_Reg(cs, -1, COMM_JADE, reg); + to = 50; + /* Wait for RDY goes high */ + while (to) { + udelay(1); + ret = cs->BC_Read_Reg(cs, -1, COMM_JADE); + to--; + if (ret & 1) + /* Got acknowledge */ + break; + if (!to) { + printk(KERN_INFO "Can not see ready bit from JADE DSP (reg=0x%X, value=0x%X)\n", reg, value); + return; + } + } +} + + + +void +modejade(struct BCState *bcs, int mode, int bc) +{ + struct IsdnCardState *cs = bcs->cs; + int jade = bcs->hw.hscx.hscx; + + if (cs->debug & L1_DEB_HSCX) { + char tmp[40]; + sprintf(tmp, "jade %c mode %d ichan %d", + 'A' + jade, mode, bc); + debugl1(cs, tmp); + } + bcs->mode = mode; + bcs->channel = bc; + + cs->BC_Write_Reg(cs, jade, jade_HDLC_MODE, (mode == L1_MODE_TRANS ? jadeMODE_TMO:0x00)); + cs->BC_Write_Reg(cs, jade, jade_HDLC_CCR0, (jadeCCR0_PU|jadeCCR0_ITF)); + cs->BC_Write_Reg(cs, jade, jade_HDLC_CCR1, 0x00); + + jade_write_indirect(cs, jade_HDLC1SERRXPATH, 0x08); + jade_write_indirect(cs, jade_HDLC2SERRXPATH, 0x08); + jade_write_indirect(cs, jade_HDLC1SERTXPATH, 0x00); + jade_write_indirect(cs, jade_HDLC2SERTXPATH, 0x00); + + cs->BC_Write_Reg(cs, jade, jade_HDLC_XCCR, 0x07); + cs->BC_Write_Reg(cs, jade, jade_HDLC_RCCR, 0x07); + + if (bc == 0) { + cs->BC_Write_Reg(cs, jade, jade_HDLC_TSAX, 0x00); + cs->BC_Write_Reg(cs, jade, jade_HDLC_TSAR, 0x00); + } else { + cs->BC_Write_Reg(cs, jade, jade_HDLC_TSAX, 0x04); + cs->BC_Write_Reg(cs, jade, jade_HDLC_TSAR, 0x04); + } + switch (mode) { + case (L1_MODE_NULL): + cs->BC_Write_Reg(cs, jade, jade_HDLC_MODE, jadeMODE_TMO); + break; + case (L1_MODE_TRANS): + cs->BC_Write_Reg(cs, jade, jade_HDLC_MODE, (jadeMODE_TMO|jadeMODE_RAC|jadeMODE_XAC)); + break; + case (L1_MODE_HDLC): + cs->BC_Write_Reg(cs, jade, jade_HDLC_MODE, (jadeMODE_RAC|jadeMODE_XAC)); + break; + } + if (mode) { + cs->BC_Write_Reg(cs, jade, jade_HDLC_RCMD, (jadeRCMD_RRES|jadeRCMD_RMC)); + cs->BC_Write_Reg(cs, jade, jade_HDLC_XCMD, jadeXCMD_XRES); + /* Unmask ints */ + cs->BC_Write_Reg(cs, jade, jade_HDLC_IMR, 0xF8); + } + else + /* Mask ints */ + cs->BC_Write_Reg(cs, jade, jade_HDLC_IMR, 0x00); +} + +static void +jade_l2l1(struct PStack *st, int pr, void *arg) +{ + struct BCState *bcs = st->l1.bcs; + struct sk_buff *skb = arg; + u_long flags; + + switch (pr) { + case (PH_DATA | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + skb_queue_tail(&bcs->squeue, skb); + } else { + bcs->tx_skb = skb; + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->hw.hscx.count = 0; + bcs->cs->BC_Send_Data(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | INDICATION): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + printk(KERN_WARNING "jade_l2l1: this shouldn't happen\n"); + } else { + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->tx_skb = skb; + bcs->hw.hscx.count = 0; + bcs->cs->BC_Send_Data(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | REQUEST): + if (!bcs->tx_skb) { + test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } else + test_and_set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + case (PH_ACTIVATE | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + test_and_set_bit(BC_FLG_ACTIV, &bcs->Flag); + modejade(bcs, st->l1.mode, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | REQUEST): + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | CONFIRM): + spin_lock_irqsave(&bcs->cs->lock, flags); + test_and_clear_bit(BC_FLG_ACTIV, &bcs->Flag); + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + modejade(bcs, 0, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + st->l1.l1l2(st, PH_DEACTIVATE | CONFIRM, NULL); + break; + } +} + +void +close_jadestate(struct BCState *bcs) +{ + modejade(bcs, 0, bcs->channel); + if (test_and_clear_bit(BC_FLG_INIT, &bcs->Flag)) { + if (bcs->hw.hscx.rcvbuf) { + kfree(bcs->hw.hscx.rcvbuf); + bcs->hw.hscx.rcvbuf = NULL; + } + if (bcs->blog) { + kfree(bcs->blog); + bcs->blog = NULL; + } + skb_queue_purge(&bcs->rqueue); + skb_queue_purge(&bcs->squeue); + if (bcs->tx_skb) { + dev_kfree_skb_any(bcs->tx_skb); + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + } + } +} + +static int +open_jadestate(struct IsdnCardState *cs, struct BCState *bcs) +{ + if (!test_and_set_bit(BC_FLG_INIT, &bcs->Flag)) { + if (!(bcs->hw.hscx.rcvbuf = kmalloc(HSCX_BUFMAX, GFP_ATOMIC))) { + printk(KERN_WARNING + "HiSax: No memory for hscx.rcvbuf\n"); + test_and_clear_bit(BC_FLG_INIT, &bcs->Flag); + return (1); + } + if (!(bcs->blog = kmalloc(MAX_BLOG_SPACE, GFP_ATOMIC))) { + printk(KERN_WARNING + "HiSax: No memory for bcs->blog\n"); + test_and_clear_bit(BC_FLG_INIT, &bcs->Flag); + kfree(bcs->hw.hscx.rcvbuf); + bcs->hw.hscx.rcvbuf = NULL; + return (2); + } + skb_queue_head_init(&bcs->rqueue); + skb_queue_head_init(&bcs->squeue); + } + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->event = 0; + bcs->hw.hscx.rcvidx = 0; + bcs->tx_cnt = 0; + return (0); +} + + +int +setstack_jade(struct PStack *st, struct BCState *bcs) +{ + bcs->channel = st->l1.bc; + if (open_jadestate(st->l1.hardware, bcs)) + return (-1); + st->l1.bcs = bcs; + st->l2.l2l1 = jade_l2l1; + setstack_manager(st); + bcs->st = st; + setstack_l1_B(st); + return (0); +} + +void __init +clear_pending_jade_ints(struct IsdnCardState *cs) +{ + int val; + char tmp[64]; + + cs->BC_Write_Reg(cs, 0, jade_HDLC_IMR, 0x00); + cs->BC_Write_Reg(cs, 1, jade_HDLC_IMR, 0x00); + + val = cs->BC_Read_Reg(cs, 1, jade_HDLC_ISR); + sprintf(tmp, "jade B ISTA %x", val); + debugl1(cs, tmp); + val = cs->BC_Read_Reg(cs, 0, jade_HDLC_ISR); + sprintf(tmp, "jade A ISTA %x", val); + debugl1(cs, tmp); + val = cs->BC_Read_Reg(cs, 1, jade_HDLC_STAR); + sprintf(tmp, "jade B STAR %x", val); + debugl1(cs, tmp); + val = cs->BC_Read_Reg(cs, 0, jade_HDLC_STAR); + sprintf(tmp, "jade A STAR %x", val); + debugl1(cs, tmp); + /* Unmask ints */ + cs->BC_Write_Reg(cs, 0, jade_HDLC_IMR, 0xF8); + cs->BC_Write_Reg(cs, 1, jade_HDLC_IMR, 0xF8); +} + +void __init +initjade(struct IsdnCardState *cs) +{ + cs->bcs[0].BC_SetStack = setstack_jade; + cs->bcs[1].BC_SetStack = setstack_jade; + cs->bcs[0].BC_Close = close_jadestate; + cs->bcs[1].BC_Close = close_jadestate; + cs->bcs[0].hw.hscx.hscx = 0; + cs->bcs[1].hw.hscx.hscx = 1; + + /* Stop DSP audio tx/rx */ + jade_write_indirect(cs, 0x11, 0x0f); + jade_write_indirect(cs, 0x17, 0x2f); + + /* Transparent Mode, RxTx inactive, No Test, No RFS/TFS */ + cs->BC_Write_Reg(cs, 0, jade_HDLC_MODE, jadeMODE_TMO); + cs->BC_Write_Reg(cs, 1, jade_HDLC_MODE, jadeMODE_TMO); + /* Power down, 1-Idle, RxTx least significant bit first */ + cs->BC_Write_Reg(cs, 0, jade_HDLC_CCR0, 0x00); + cs->BC_Write_Reg(cs, 1, jade_HDLC_CCR0, 0x00); + /* Mask all interrupts */ + cs->BC_Write_Reg(cs, 0, jade_HDLC_IMR, 0x00); + cs->BC_Write_Reg(cs, 1, jade_HDLC_IMR, 0x00); + /* Setup host access to hdlc controller */ + jade_write_indirect(cs, jade_HDLCCNTRACCESS, (jadeINDIRECT_HAH1|jadeINDIRECT_HAH2)); + /* Unmask HDLC int (don´t forget DSP int later on)*/ + cs->BC_Write_Reg(cs, -1,jade_INT, (jadeINT_HDLC1|jadeINT_HDLC2)); + + /* once again TRANSPARENT */ + modejade(cs->bcs, 0, 0); + modejade(cs->bcs + 1, 0, 0); +} + diff --git a/drivers/isdn/hisax/jade.h b/drivers/isdn/hisax/jade.h new file mode 100644 index 000000000000..fa2944485994 --- /dev/null +++ b/drivers/isdn/hisax/jade.h @@ -0,0 +1,135 @@ +/* $Id: jade.h,v 1.5.2.3 2004/01/14 16:04:48 keil Exp $ + * + * JADE specific defines + * + * Author Roland Klabunde + * Copyright by Roland Klabunde <R.Klabunde@Berkom.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +/* All Registers original Siemens Spec */ +#ifndef __JADE_H__ +#define __JADE_H__ + +/* Special registers for access to indirect accessible JADE regs */ +#define DIRECT_IO_JADE 0x0000 /* Jade direct io access area */ +#define COMM_JADE 0x0040 /* Jade communication area */ + +/********************************************************************/ +/* JADE-HDLC registers */ +/********************************************************************/ +#define jade_HDLC_RFIFO 0x00 /* R */ +#define jade_HDLC_XFIFO 0x00 /* W */ + +#define jade_HDLC_STAR 0x20 /* R */ + #define jadeSTAR_XDOV 0x80 + #define jadeSTAR_XFW 0x40 /* Does not work*/ + #define jadeSTAR_XCEC 0x20 + #define jadeSTAR_RCEC 0x10 + #define jadeSTAR_BSY 0x08 + #define jadeSTAR_RNA 0x04 + #define jadeSTAR_STR 0x02 + #define jadeSTAR_STX 0x01 + +#define jade_HDLC_XCMD 0x20 /* W */ + #define jadeXCMD_XF 0x80 + #define jadeXCMD_XME 0x40 + #define jadeXCMD_XRES 0x20 + #define jadeXCMD_STX 0x01 + +#define jade_HDLC_RSTA 0x21 /* R */ + #define jadeRSTA_VFR 0x80 + #define jadeRSTA_RDO 0x40 + #define jadeRSTA_CRC 0x20 + #define jadeRSTA_RAB 0x10 + #define jadeRSTA_MASK 0xF0 + +#define jade_HDLC_MODE 0x22 /* RW*/ + #define jadeMODE_TMO 0x80 + #define jadeMODE_RAC 0x40 + #define jadeMODE_XAC 0x20 + #define jadeMODE_TLP 0x10 + #define jadeMODE_ERFS 0x02 + #define jadeMODE_ETFS 0x01 + +#define jade_HDLC_RBCH 0x24 /* R */ + +#define jade_HDLC_RBCL 0x25 /* R */ +#define jade_HDLC_RCMD 0x25 /* W */ + #define jadeRCMD_RMC 0x80 + #define jadeRCMD_RRES 0x40 + #define jadeRCMD_RMD 0x20 + #define jadeRCMD_STR 0x02 + +#define jade_HDLC_CCR0 0x26 /* RW*/ + #define jadeCCR0_PU 0x80 + #define jadeCCR0_ITF 0x40 + #define jadeCCR0_C32 0x20 + #define jadeCCR0_CRL 0x10 + #define jadeCCR0_RCRC 0x08 + #define jadeCCR0_XCRC 0x04 + #define jadeCCR0_RMSB 0x02 + #define jadeCCR0_XMSB 0x01 + +#define jade_HDLC_CCR1 0x27 /* RW*/ + #define jadeCCR1_RCS0 0x80 + #define jadeCCR1_RCONT 0x40 + #define jadeCCR1_RFDIS 0x20 + #define jadeCCR1_XCS0 0x10 + #define jadeCCR1_XCONT 0x08 + #define jadeCCR1_XFDIS 0x04 + +#define jade_HDLC_TSAR 0x28 /* RW*/ +#define jade_HDLC_TSAX 0x29 /* RW*/ +#define jade_HDLC_RCCR 0x2A /* RW*/ +#define jade_HDLC_XCCR 0x2B /* RW*/ + +#define jade_HDLC_ISR 0x2C /* R */ +#define jade_HDLC_IMR 0x2C /* W */ + #define jadeISR_RME 0x80 + #define jadeISR_RPF 0x40 + #define jadeISR_RFO 0x20 + #define jadeISR_XPR 0x10 + #define jadeISR_XDU 0x08 + #define jadeISR_ALLS 0x04 + +#define jade_INT 0x75 + #define jadeINT_HDLC1 0x02 + #define jadeINT_HDLC2 0x01 + #define jadeINT_DSP 0x04 +#define jade_INTR 0x70 + +/********************************************************************/ +/* Indirect accessible JADE registers of common interest */ +/********************************************************************/ +#define jade_CHIPVERSIONNR 0x00 /* Does not work*/ + +#define jade_HDLCCNTRACCESS 0x10 + #define jadeINDIRECT_HAH1 0x02 + #define jadeINDIRECT_HAH2 0x01 + +#define jade_HDLC1SERRXPATH 0x1D +#define jade_HDLC1SERTXPATH 0x1E +#define jade_HDLC2SERRXPATH 0x1F +#define jade_HDLC2SERTXPATH 0x20 + #define jadeINDIRECT_SLIN1 0x10 + #define jadeINDIRECT_SLIN0 0x08 + #define jadeINDIRECT_LMOD1 0x04 + #define jadeINDIRECT_LMOD0 0x02 + #define jadeINDIRECT_HHR 0x01 + #define jadeINDIRECT_HHX 0x01 + +#define jade_RXAUDIOCH1CFG 0x11 +#define jade_RXAUDIOCH2CFG 0x14 +#define jade_TXAUDIOCH1CFG 0x17 +#define jade_TXAUDIOCH2CFG 0x1A + +extern int JadeVersion(struct IsdnCardState *cs, char *s); +extern void modejade(struct BCState *bcs, int mode, int bc); +extern void clear_pending_jade_ints(struct IsdnCardState *cs); +extern void initjade(struct IsdnCardState *cs); + +#endif /* __JADE_H__ */ diff --git a/drivers/isdn/hisax/jade_irq.c b/drivers/isdn/hisax/jade_irq.c new file mode 100644 index 000000000000..08563400e4fd --- /dev/null +++ b/drivers/isdn/hisax/jade_irq.c @@ -0,0 +1,236 @@ +/* $Id: jade_irq.c,v 1.7.2.4 2004/02/11 13:21:34 keil Exp $ + * + * Low level JADE IRQ stuff (derived from original hscx_irq.c) + * + * Author Roland Klabunde + * Copyright by Roland Klabunde <R.Klabunde@Berkom.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +static inline void +waitforCEC(struct IsdnCardState *cs, int jade, int reg) +{ + int to = 50; + int mask = (reg == jade_HDLC_XCMD ? jadeSTAR_XCEC : jadeSTAR_RCEC); + while ((READJADE(cs, jade, jade_HDLC_STAR) & mask) && to) { + udelay(1); + to--; + } + if (!to) + printk(KERN_WARNING "HiSax: waitforCEC (jade) timeout\n"); +} + + +static inline void +waitforXFW(struct IsdnCardState *cs, int jade) +{ + /* Does not work on older jade versions, don't care */ +} + +static inline void +WriteJADECMDR(struct IsdnCardState *cs, int jade, int reg, u_char data) +{ + waitforCEC(cs, jade, reg); + WRITEJADE(cs, jade, reg, data); +} + + + +static void +jade_empty_fifo(struct BCState *bcs, int count) +{ + u_char *ptr; + struct IsdnCardState *cs = bcs->cs; + + if ((cs->debug & L1_DEB_HSCX) && !(cs->debug & L1_DEB_HSCX_FIFO)) + debugl1(cs, "jade_empty_fifo"); + + if (bcs->hw.hscx.rcvidx + count > HSCX_BUFMAX) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "jade_empty_fifo: incoming packet too large"); + WriteJADECMDR(cs, bcs->hw.hscx.hscx, jade_HDLC_RCMD, jadeRCMD_RMC); + bcs->hw.hscx.rcvidx = 0; + return; + } + ptr = bcs->hw.hscx.rcvbuf + bcs->hw.hscx.rcvidx; + bcs->hw.hscx.rcvidx += count; + READJADEFIFO(cs, bcs->hw.hscx.hscx, ptr, count); + WriteJADECMDR(cs, bcs->hw.hscx.hscx, jade_HDLC_RCMD, jadeRCMD_RMC); + if (cs->debug & L1_DEB_HSCX_FIFO) { + char *t = bcs->blog; + + t += sprintf(t, "jade_empty_fifo %c cnt %d", + bcs->hw.hscx.hscx ? 'B' : 'A', count); + QuickHex(t, ptr, count); + debugl1(cs, bcs->blog); + } +} + +static void +jade_fill_fifo(struct BCState *bcs) +{ + struct IsdnCardState *cs = bcs->cs; + int more, count; + int fifo_size = 32; + u_char *ptr; + + if ((cs->debug & L1_DEB_HSCX) && !(cs->debug & L1_DEB_HSCX_FIFO)) + debugl1(cs, "jade_fill_fifo"); + + if (!bcs->tx_skb) + return; + if (bcs->tx_skb->len <= 0) + return; + + more = (bcs->mode == L1_MODE_TRANS) ? 1 : 0; + if (bcs->tx_skb->len > fifo_size) { + more = !0; + count = fifo_size; + } else + count = bcs->tx_skb->len; + + waitforXFW(cs, bcs->hw.hscx.hscx); + ptr = bcs->tx_skb->data; + skb_pull(bcs->tx_skb, count); + bcs->tx_cnt -= count; + bcs->hw.hscx.count += count; + WRITEJADEFIFO(cs, bcs->hw.hscx.hscx, ptr, count); + WriteJADECMDR(cs, bcs->hw.hscx.hscx, jade_HDLC_XCMD, more ? jadeXCMD_XF : (jadeXCMD_XF|jadeXCMD_XME)); + if (cs->debug & L1_DEB_HSCX_FIFO) { + char *t = bcs->blog; + + t += sprintf(t, "jade_fill_fifo %c cnt %d", + bcs->hw.hscx.hscx ? 'B' : 'A', count); + QuickHex(t, ptr, count); + debugl1(cs, bcs->blog); + } +} + + +static inline void +jade_interrupt(struct IsdnCardState *cs, u_char val, u_char jade) +{ + u_char r; + struct BCState *bcs = cs->bcs + jade; + struct sk_buff *skb; + int fifo_size = 32; + int count; + int i_jade = (int) jade; /* To satisfy the compiler */ + + if (!test_bit(BC_FLG_INIT, &bcs->Flag)) + return; + + if (val & 0x80) { /* RME */ + r = READJADE(cs, i_jade, jade_HDLC_RSTA); + if ((r & 0xf0) != 0xa0) { + if (!(r & 0x80)) + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "JADE %s invalid frame", (jade ? "B":"A")); + if ((r & 0x40) && bcs->mode) + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "JADE %c RDO mode=%d", 'A'+jade, bcs->mode); + if (!(r & 0x20)) + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "JADE %c CRC error", 'A'+jade); + WriteJADECMDR(cs, jade, jade_HDLC_RCMD, jadeRCMD_RMC); + } else { + count = READJADE(cs, i_jade, jade_HDLC_RBCL) & 0x1F; + if (count == 0) + count = fifo_size; + jade_empty_fifo(bcs, count); + if ((count = bcs->hw.hscx.rcvidx - 1) > 0) { + if (cs->debug & L1_DEB_HSCX_FIFO) + debugl1(cs, "HX Frame %d", count); + if (!(skb = dev_alloc_skb(count))) + printk(KERN_WARNING "JADE %s receive out of memory\n", (jade ? "B":"A")); + else { + memcpy(skb_put(skb, count), bcs->hw.hscx.rcvbuf, count); + skb_queue_tail(&bcs->rqueue, skb); + } + } + } + bcs->hw.hscx.rcvidx = 0; + schedule_event(bcs, B_RCVBUFREADY); + } + if (val & 0x40) { /* RPF */ + jade_empty_fifo(bcs, fifo_size); + if (bcs->mode == L1_MODE_TRANS) { + /* receive audio data */ + if (!(skb = dev_alloc_skb(fifo_size))) + printk(KERN_WARNING "HiSax: receive out of memory\n"); + else { + memcpy(skb_put(skb, fifo_size), bcs->hw.hscx.rcvbuf, fifo_size); + skb_queue_tail(&bcs->rqueue, skb); + } + bcs->hw.hscx.rcvidx = 0; + schedule_event(bcs, B_RCVBUFREADY); + } + } + if (val & 0x10) { /* XPR */ + if (bcs->tx_skb) { + if (bcs->tx_skb->len) { + jade_fill_fifo(bcs); + return; + } else { + if (test_bit(FLG_LLI_L1WAKEUP,&bcs->st->lli.flag) && + (PACKET_NOACK != bcs->tx_skb->pkt_type)) { + u_long flags; + spin_lock_irqsave(&bcs->aclock, flags); + bcs->ackcnt += bcs->hw.hscx.count; + spin_unlock_irqrestore(&bcs->aclock, flags); + schedule_event(bcs, B_ACKPENDING); + } + dev_kfree_skb_irq(bcs->tx_skb); + bcs->hw.hscx.count = 0; + bcs->tx_skb = NULL; + } + } + if ((bcs->tx_skb = skb_dequeue(&bcs->squeue))) { + bcs->hw.hscx.count = 0; + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + jade_fill_fifo(bcs); + } else { + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + schedule_event(bcs, B_XMTBUFREADY); + } + } +} + +static inline void +jade_int_main(struct IsdnCardState *cs, u_char val, int jade) +{ + struct BCState *bcs; + bcs = cs->bcs + jade; + + if (val & jadeISR_RFO) { + /* handled with RDO */ + val &= ~jadeISR_RFO; + } + if (val & jadeISR_XDU) { + /* relevant in HDLC mode only */ + /* don't reset XPR here */ + if (bcs->mode == 1) + jade_fill_fifo(bcs); + else { + /* Here we lost an TX interrupt, so + * restart transmitting the whole frame. + */ + if (bcs->tx_skb) { + skb_push(bcs->tx_skb, bcs->hw.hscx.count); + bcs->tx_cnt += bcs->hw.hscx.count; + bcs->hw.hscx.count = 0; + } + WriteJADECMDR(cs, bcs->hw.hscx.hscx, jade_HDLC_XCMD, jadeXCMD_XRES); + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "JADE %c EXIR %x Lost TX", 'A'+jade, val); + } + } + if (val & (jadeISR_RME|jadeISR_RPF|jadeISR_XPR)) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "JADE %c interrupt %x", 'A'+jade, val); + jade_interrupt(cs, val, jade); + } +} diff --git a/drivers/isdn/hisax/l3_1tr6.c b/drivers/isdn/hisax/l3_1tr6.c new file mode 100644 index 000000000000..d6c1c8f8329d --- /dev/null +++ b/drivers/isdn/hisax/l3_1tr6.c @@ -0,0 +1,955 @@ +/* $Id: l3_1tr6.c,v 2.15.2.3 2004/01/13 14:31:25 keil Exp $ + * + * German 1TR6 D-channel protocol + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * For changes and modifications please read + * Documentation/isdn/HiSax.cert + * + */ + +#include "hisax.h" +#include "l3_1tr6.h" +#include "isdnl3.h" +#include <linux/ctype.h> + +extern char *HiSax_getrev(const char *revision); +const char *l3_1tr6_revision = "$Revision: 2.15.2.3 $"; + +#define MsgHead(ptr, cref, mty, dis) \ + *ptr++ = dis; \ + *ptr++ = 0x1; \ + *ptr++ = cref ^ 0x80; \ + *ptr++ = mty + +static void +l3_1TR6_message(struct l3_process *pc, u_char mt, u_char pd) +{ + struct sk_buff *skb; + u_char *p; + + if (!(skb = l3_alloc_skb(4))) + return; + p = skb_put(skb, 4); + MsgHead(p, pc->callref, mt, pd); + l3_msg(pc->st, DL_DATA | REQUEST, skb); +} + +static void +l3_1tr6_release_req(struct l3_process *pc, u_char pr, void *arg) +{ + StopAllL3Timer(pc); + newl3state(pc, 19); + l3_1TR6_message(pc, MT_N1_REL, PROTO_DIS_N1); + L3AddTimer(&pc->timer, T308, CC_T308_1); +} + +static void +l3_1tr6_invalid(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + + dev_kfree_skb(skb); + l3_1tr6_release_req(pc, 0, NULL); +} + +static void +l3_1tr6_error(struct l3_process *pc, u_char *msg, struct sk_buff *skb) +{ + dev_kfree_skb(skb); + if (pc->st->l3.debug & L3_DEB_WARN) + l3_debug(pc->st, msg); + l3_1tr6_release_req(pc, 0, NULL); +} + +static void +l3_1tr6_setup_req(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb; + u_char tmp[128]; + u_char *p = tmp; + u_char *teln; + u_char *eaz; + u_char channel = 0; + int l; + + MsgHead(p, pc->callref, MT_N1_SETUP, PROTO_DIS_N1); + teln = pc->para.setup.phone; + pc->para.spv = 0; + if (!isdigit(*teln)) { + switch (0x5f & *teln) { + case 'S': + pc->para.spv = 1; + break; + case 'C': + channel = 0x08; + case 'P': + channel |= 0x80; + teln++; + if (*teln == '1') + channel |= 0x01; + else + channel |= 0x02; + break; + default: + if (pc->st->l3.debug & L3_DEB_WARN) + l3_debug(pc->st, "Wrong MSN Code"); + break; + } + teln++; + } + if (channel) { + *p++ = 0x18; /* channel indicator */ + *p++ = 1; + *p++ = channel; + } + if (pc->para.spv) { /* SPV ? */ + /* NSF SPV */ + *p++ = WE0_netSpecFac; + *p++ = 4; /* Laenge */ + *p++ = 0; + *p++ = FAC_SPV; /* SPV */ + *p++ = pc->para.setup.si1; /* 0 for all Services */ + *p++ = pc->para.setup.si2; /* 0 for all Services */ + *p++ = WE0_netSpecFac; + *p++ = 4; /* Laenge */ + *p++ = 0; + *p++ = FAC_Activate; /* aktiviere SPV (default) */ + *p++ = pc->para.setup.si1; /* 0 for all Services */ + *p++ = pc->para.setup.si2; /* 0 for all Services */ + } + eaz = pc->para.setup.eazmsn; + if (*eaz) { + *p++ = WE0_origAddr; + *p++ = strlen(eaz) + 1; + /* Classify as AnyPref. */ + *p++ = 0x81; /* Ext = '1'B, Type = '000'B, Plan = '0001'B. */ + while (*eaz) + *p++ = *eaz++ & 0x7f; + } + *p++ = WE0_destAddr; + *p++ = strlen(teln) + 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++ = pc->para.setup.si1; + *p++ = pc->para.setup.si2; + + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + memcpy(skb_put(skb, l), tmp, l); + L3DelTimer(&pc->timer); + L3AddTimer(&pc->timer, T303, CC_T303); + newl3state(pc, 1); + l3_msg(pc->st, DL_DATA | REQUEST, skb); +} + +static void +l3_1tr6_setup(struct l3_process *pc, u_char pr, void *arg) +{ + u_char *p; + int bcfound = 0; + char tmp[80]; + struct sk_buff *skb = arg; + + p = skb->data; + + /* Channel Identification */ + p = skb->data; + if ((p = findie(p, skb->len, WE0_chanID, 0))) { + if (p[1] != 1) { + l3_1tr6_error(pc, "setup wrong chanID len", skb); + return; + } + if ((p[2] & 0xf4) != 0x80) { + l3_1tr6_error(pc, "setup wrong WE0_chanID", skb); + return; + } + if ((pc->para.bchannel = p[2] & 0x3)) + bcfound++; + } else { + l3_1tr6_error(pc, "missing setup chanID", skb); + return; + } + + p = skb->data; + if ((p = findie(p, skb->len, WE6_serviceInd, 6))) { + pc->para.setup.si1 = p[2]; + pc->para.setup.si2 = p[3]; + } else { + l3_1tr6_error(pc, "missing setup SI", skb); + return; + } + + p = skb->data; + if ((p = findie(p, skb->len, WE0_destAddr, 0))) + iecpy(pc->para.setup.eazmsn, p, 1); + else + pc->para.setup.eazmsn[0] = 0; + + p = skb->data; + if ((p = findie(p, skb->len, WE0_origAddr, 0))) { + iecpy(pc->para.setup.phone, p, 1); + } else + pc->para.setup.phone[0] = 0; + + p = skb->data; + pc->para.spv = 0; + if ((p = findie(p, skb->len, WE0_netSpecFac, 0))) { + if ((FAC_SPV == p[3]) || (FAC_Activate == p[3])) + pc->para.spv = 1; + } + dev_kfree_skb(skb); + + /* Signal all services, linklevel takes care of Service-Indicator */ + if (bcfound) { + if ((pc->para.setup.si1 != 7) && (pc->st->l3.debug & L3_DEB_WARN)) { + sprintf(tmp, "non-digital call: %s -> %s", + pc->para.setup.phone, + pc->para.setup.eazmsn); + l3_debug(pc->st, tmp); + } + newl3state(pc, 6); + pc->st->l3.l3l4(pc->st, CC_SETUP | INDICATION, pc); + } else + release_l3_process(pc); +} + +static void +l3_1tr6_setup_ack(struct l3_process *pc, u_char pr, void *arg) +{ + u_char *p; + struct sk_buff *skb = arg; + + L3DelTimer(&pc->timer); + p = skb->data; + newl3state(pc, 2); + if ((p = findie(p, skb->len, WE0_chanID, 0))) { + if (p[1] != 1) { + l3_1tr6_error(pc, "setup_ack wrong chanID len", skb); + return; + } + if ((p[2] & 0xf4) != 0x80) { + l3_1tr6_error(pc, "setup_ack wrong WE0_chanID", skb); + return; + } + pc->para.bchannel = p[2] & 0x3; + } else { + l3_1tr6_error(pc, "missing setup_ack WE0_chanID", skb); + return; + } + dev_kfree_skb(skb); + L3AddTimer(&pc->timer, T304, CC_T304); + pc->st->l3.l3l4(pc->st, CC_MORE_INFO | INDICATION, pc); +} + +static void +l3_1tr6_call_sent(struct l3_process *pc, u_char pr, void *arg) +{ + u_char *p; + struct sk_buff *skb = arg; + + L3DelTimer(&pc->timer); + p = skb->data; + if ((p = findie(p, skb->len, WE0_chanID, 0))) { + if (p[1] != 1) { + l3_1tr6_error(pc, "call sent wrong chanID len", skb); + return; + } + if ((p[2] & 0xf4) != 0x80) { + l3_1tr6_error(pc, "call sent wrong WE0_chanID", skb); + return; + } + if ((pc->state == 2) && (pc->para.bchannel != (p[2] & 0x3))) { + l3_1tr6_error(pc, "call sent wrong chanID value", skb); + return; + } + pc->para.bchannel = p[2] & 0x3; + } else { + l3_1tr6_error(pc, "missing call sent WE0_chanID", skb); + return; + } + dev_kfree_skb(skb); + L3AddTimer(&pc->timer, T310, CC_T310); + newl3state(pc, 3); + pc->st->l3.l3l4(pc->st, CC_PROCEEDING | INDICATION, pc); +} + +static void +l3_1tr6_alert(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + + dev_kfree_skb(skb); + L3DelTimer(&pc->timer); /* T304 */ + newl3state(pc, 4); + pc->st->l3.l3l4(pc->st, CC_ALERTING | INDICATION, pc); +} + +static void +l3_1tr6_info(struct l3_process *pc, u_char pr, void *arg) +{ + u_char *p; + int i, tmpcharge = 0; + char a_charge[8], tmp[32]; + struct sk_buff *skb = arg; + + p = skb->data; + if ((p = findie(p, skb->len, 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 > pc->para.chargeinfo) { + pc->para.chargeinfo = tmpcharge; + pc->st->l3.l3l4(pc->st, CC_CHARGE | INDICATION, pc); + } + if (pc->st->l3.debug & L3_DEB_CHARGE) { + sprintf(tmp, "charging info %d", pc->para.chargeinfo); + l3_debug(pc->st, tmp); + } + } else if (pc->st->l3.debug & L3_DEB_CHARGE) + l3_debug(pc->st, "charging info not found"); + dev_kfree_skb(skb); + +} + +static void +l3_1tr6_info_s2(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + + dev_kfree_skb(skb); +} + +static void +l3_1tr6_connect(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + + L3DelTimer(&pc->timer); /* T310 */ + if (!findie(skb->data, skb->len, WE6_date, 6)) { + l3_1tr6_error(pc, "missing connect date", skb); + return; + } + newl3state(pc, 10); + dev_kfree_skb(skb); + pc->para.chargeinfo = 0; + pc->st->l3.l3l4(pc->st, CC_SETUP | CONFIRM, pc); +} + +static void +l3_1tr6_rel(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + u_char *p; + + p = skb->data; + if ((p = findie(p, skb->len, WE0_cause, 0))) { + if (p[1] > 0) { + pc->para.cause = p[2]; + if (p[1] > 1) + pc->para.loc = p[3]; + else + pc->para.loc = 0; + } else { + pc->para.cause = 0; + pc->para.loc = 0; + } + } else { + pc->para.cause = NO_CAUSE; + l3_1tr6_error(pc, "missing REL cause", skb); + return; + } + dev_kfree_skb(skb); + StopAllL3Timer(pc); + newl3state(pc, 0); + l3_1TR6_message(pc, MT_N1_REL_ACK, PROTO_DIS_N1); + pc->st->l3.l3l4(pc->st, CC_RELEASE | INDICATION, pc); + release_l3_process(pc); +} + +static void +l3_1tr6_rel_ack(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + + dev_kfree_skb(skb); + StopAllL3Timer(pc); + newl3state(pc, 0); + pc->para.cause = NO_CAUSE; + pc->st->l3.l3l4(pc->st, CC_RELEASE | CONFIRM, pc); + release_l3_process(pc); +} + +static void +l3_1tr6_disc(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + u_char *p; + int i, tmpcharge = 0; + char a_charge[8], tmp[32]; + + StopAllL3Timer(pc); + p = skb->data; + if ((p = findie(p, skb->len, 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 > pc->para.chargeinfo) { + pc->para.chargeinfo = tmpcharge; + pc->st->l3.l3l4(pc->st, CC_CHARGE | INDICATION, pc); + } + if (pc->st->l3.debug & L3_DEB_CHARGE) { + sprintf(tmp, "charging info %d", pc->para.chargeinfo); + l3_debug(pc->st, tmp); + } + } else if (pc->st->l3.debug & L3_DEB_CHARGE) + l3_debug(pc->st, "charging info not found"); + + + p = skb->data; + if ((p = findie(p, skb->len, WE0_cause, 0))) { + if (p[1] > 0) { + pc->para.cause = p[2]; + if (p[1] > 1) + pc->para.loc = p[3]; + else + pc->para.loc = 0; + } else { + pc->para.cause = 0; + pc->para.loc = 0; + } + } else { + if (pc->st->l3.debug & L3_DEB_WARN) + l3_debug(pc->st, "cause not found"); + pc->para.cause = NO_CAUSE; + } + if (!findie(skb->data, skb->len, WE6_date, 6)) { + l3_1tr6_error(pc, "missing connack date", skb); + return; + } + dev_kfree_skb(skb); + newl3state(pc, 12); + pc->st->l3.l3l4(pc->st, CC_DISCONNECT | INDICATION, pc); +} + + +static void +l3_1tr6_connect_ack(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + + if (!findie(skb->data, skb->len, WE6_date, 6)) { + l3_1tr6_error(pc, "missing connack date", skb); + return; + } + dev_kfree_skb(skb); + newl3state(pc, 10); + pc->para.chargeinfo = 0; + L3DelTimer(&pc->timer); + pc->st->l3.l3l4(pc->st, CC_SETUP_COMPL | INDICATION, pc); +} + +static void +l3_1tr6_alert_req(struct l3_process *pc, u_char pr, void *arg) +{ + newl3state(pc, 7); + l3_1TR6_message(pc, MT_N1_ALERT, PROTO_DIS_N1); +} + +static void +l3_1tr6_setup_rsp(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb; + u_char tmp[24]; + u_char *p = tmp; + int l; + + MsgHead(p, pc->callref, MT_N1_CONN, PROTO_DIS_N1); + if (pc->para.spv) { /* SPV ? */ + /* NSF SPV */ + *p++ = WE0_netSpecFac; + *p++ = 4; /* Laenge */ + *p++ = 0; + *p++ = FAC_SPV; /* SPV */ + *p++ = pc->para.setup.si1; + *p++ = pc->para.setup.si2; + *p++ = WE0_netSpecFac; + *p++ = 4; /* Laenge */ + *p++ = 0; + *p++ = FAC_Activate; /* aktiviere SPV */ + *p++ = pc->para.setup.si1; + *p++ = pc->para.setup.si2; + } + newl3state(pc, 8); + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + memcpy(skb_put(skb, l), tmp, l); + l3_msg(pc->st, DL_DATA | REQUEST, skb); + L3DelTimer(&pc->timer); + L3AddTimer(&pc->timer, T313, CC_T313); +} + +static void +l3_1tr6_reset(struct l3_process *pc, u_char pr, void *arg) +{ + release_l3_process(pc); +} + +static void +l3_1tr6_disconnect_req(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb; + u_char tmp[16]; + u_char *p = tmp; + int l; + u_char cause = 0x10; + u_char clen = 1; + + if (pc->para.cause > 0) + cause = pc->para.cause; + /* Map DSS1 causes */ + switch (cause & 0x7f) { + case 0x10: + clen = 0; + break; + case 0x11: + cause = CAUSE_UserBusy; + break; + case 0x15: + cause = CAUSE_CallRejected; + break; + } + StopAllL3Timer(pc); + MsgHead(p, pc->callref, MT_N1_DISC, PROTO_DIS_N1); + *p++ = WE0_cause; + *p++ = clen; /* Laenge */ + if (clen) + *p++ = cause | 0x80; + newl3state(pc, 11); + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + memcpy(skb_put(skb, l), tmp, l); + l3_msg(pc->st, DL_DATA | REQUEST, skb); + L3AddTimer(&pc->timer, T305, CC_T305); +} + +static void +l3_1tr6_t303(struct l3_process *pc, u_char pr, void *arg) +{ + if (pc->N303 > 0) { + pc->N303--; + L3DelTimer(&pc->timer); + l3_1tr6_setup_req(pc, pr, arg); + } else { + L3DelTimer(&pc->timer); + pc->para.cause = 0; + l3_1tr6_disconnect_req(pc, 0, NULL); + } +} + +static void +l3_1tr6_t304(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + pc->para.cause = 0xE6; + l3_1tr6_disconnect_req(pc, pr, NULL); + pc->st->l3.l3l4(pc->st, CC_SETUP_ERR, pc); +} + +static void +l3_1tr6_t305(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb; + u_char tmp[16]; + u_char *p = tmp; + int l; + u_char cause = 0x90; + u_char clen = 1; + + L3DelTimer(&pc->timer); + if (pc->para.cause != NO_CAUSE) + cause = pc->para.cause; + /* Map DSS1 causes */ + switch (cause & 0x7f) { + case 0x10: + clen = 0; + break; + case 0x15: + cause = CAUSE_CallRejected; + break; + } + MsgHead(p, pc->callref, MT_N1_REL, PROTO_DIS_N1); + *p++ = WE0_cause; + *p++ = clen; /* Laenge */ + if (clen) + *p++ = cause; + newl3state(pc, 19); + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + memcpy(skb_put(skb, l), tmp, l); + l3_msg(pc->st, DL_DATA | REQUEST, skb); + L3AddTimer(&pc->timer, T308, CC_T308_1); +} + +static void +l3_1tr6_t310(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + pc->para.cause = 0xE6; + l3_1tr6_disconnect_req(pc, pr, NULL); + pc->st->l3.l3l4(pc->st, CC_SETUP_ERR, pc); +} + +static void +l3_1tr6_t313(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + pc->para.cause = 0xE6; + l3_1tr6_disconnect_req(pc, pr, NULL); + pc->st->l3.l3l4(pc->st, CC_CONNECT_ERR, pc); +} + +static void +l3_1tr6_t308_1(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + l3_1TR6_message(pc, MT_N1_REL, PROTO_DIS_N1); + L3AddTimer(&pc->timer, T308, CC_T308_2); + newl3state(pc, 19); +} + +static void +l3_1tr6_t308_2(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + pc->st->l3.l3l4(pc->st, CC_RELEASE_ERR, pc); + release_l3_process(pc); +} + +static void +l3_1tr6_dl_reset(struct l3_process *pc, u_char pr, void *arg) +{ + pc->para.cause = CAUSE_LocalProcErr; + l3_1tr6_disconnect_req(pc, pr, NULL); + pc->st->l3.l3l4(pc->st, CC_SETUP_ERR, pc); +} + +static void +l3_1tr6_dl_release(struct l3_process *pc, u_char pr, void *arg) +{ + newl3state(pc, 0); + pc->para.cause = 0x1b; /* Destination out of order */ + pc->para.loc = 0; + pc->st->l3.l3l4(pc->st, CC_RELEASE | INDICATION, pc); + release_l3_process(pc); +} + +/* *INDENT-OFF* */ +static struct stateentry downstl[] = +{ + {SBIT(0), + CC_SETUP | REQUEST, l3_1tr6_setup_req}, + {SBIT(1) | SBIT(2) | SBIT(3) | SBIT(4) | SBIT(6) | SBIT(7) | SBIT(8) | + SBIT(10), + CC_DISCONNECT | REQUEST, l3_1tr6_disconnect_req}, + {SBIT(12), + CC_RELEASE | REQUEST, l3_1tr6_release_req}, + {SBIT(6), + CC_IGNORE | REQUEST, l3_1tr6_reset}, + {SBIT(6), + CC_REJECT | REQUEST, l3_1tr6_disconnect_req}, + {SBIT(6), + CC_ALERTING | REQUEST, l3_1tr6_alert_req}, + {SBIT(6) | SBIT(7), + CC_SETUP | RESPONSE, l3_1tr6_setup_rsp}, + {SBIT(1), + CC_T303, l3_1tr6_t303}, + {SBIT(2), + CC_T304, l3_1tr6_t304}, + {SBIT(3), + CC_T310, l3_1tr6_t310}, + {SBIT(8), + CC_T313, l3_1tr6_t313}, + {SBIT(11), + CC_T305, l3_1tr6_t305}, + {SBIT(19), + CC_T308_1, l3_1tr6_t308_1}, + {SBIT(19), + CC_T308_2, l3_1tr6_t308_2}, +}; + +#define DOWNSTL_LEN \ + (sizeof(downstl) / sizeof(struct stateentry)) + +static struct stateentry datastln1[] = +{ + {SBIT(0), + MT_N1_INVALID, l3_1tr6_invalid}, + {SBIT(0), + MT_N1_SETUP, l3_1tr6_setup}, + {SBIT(1), + MT_N1_SETUP_ACK, l3_1tr6_setup_ack}, + {SBIT(1) | SBIT(2), + MT_N1_CALL_SENT, l3_1tr6_call_sent}, + {SBIT(1) | SBIT(2) | SBIT(3) | SBIT(4) | SBIT(7) | SBIT(8) | SBIT(10), + MT_N1_DISC, l3_1tr6_disc}, + {SBIT(2) | SBIT(3) | SBIT(4), + MT_N1_ALERT, l3_1tr6_alert}, + {SBIT(2) | SBIT(3) | SBIT(4), + MT_N1_CONN, l3_1tr6_connect}, + {SBIT(2), + MT_N1_INFO, l3_1tr6_info_s2}, + {SBIT(8), + MT_N1_CONN_ACK, l3_1tr6_connect_ack}, + {SBIT(10), + MT_N1_INFO, l3_1tr6_info}, + {SBIT(0) | SBIT(1) | SBIT(2) | SBIT(3) | SBIT(4) | SBIT(7) | SBIT(8) | + SBIT(10) | SBIT(11) | SBIT(12) | SBIT(15) | SBIT(17), + MT_N1_REL, l3_1tr6_rel}, + {SBIT(19), + MT_N1_REL, l3_1tr6_rel_ack}, + {SBIT(0) | SBIT(1) | SBIT(2) | SBIT(3) | SBIT(4) | SBIT(7) | SBIT(8) | + SBIT(10) | SBIT(11) | SBIT(12) | SBIT(15) | SBIT(17), + MT_N1_REL_ACK, l3_1tr6_invalid}, + {SBIT(19), + MT_N1_REL_ACK, l3_1tr6_rel_ack} +}; + +#define DATASTLN1_LEN \ + (sizeof(datastln1) / sizeof(struct stateentry)) + +static struct stateentry manstatelist[] = +{ + {SBIT(2), + DL_ESTABLISH | INDICATION, l3_1tr6_dl_reset}, + {ALL_STATES, + DL_RELEASE | INDICATION, l3_1tr6_dl_release}, +}; + +#define MANSLLEN \ + (sizeof(manstatelist) / sizeof(struct stateentry)) +/* *INDENT-ON* */ + +static void +up1tr6(struct PStack *st, int pr, void *arg) +{ + int i, mt, cr; + struct l3_process *proc; + struct sk_buff *skb = arg; + char tmp[80]; + + switch (pr) { + case (DL_DATA | INDICATION): + case (DL_UNIT_DATA | INDICATION): + break; + case (DL_ESTABLISH | CONFIRM): + case (DL_ESTABLISH | INDICATION): + case (DL_RELEASE | INDICATION): + case (DL_RELEASE | CONFIRM): + l3_msg(st, pr, arg); + return; + break; + } + if (skb->len < 4) { + if (st->l3.debug & L3_DEB_PROTERR) { + sprintf(tmp, "up1tr6 len only %d", skb->len); + l3_debug(st, tmp); + } + dev_kfree_skb(skb); + return; + } + if ((skb->data[0] & 0xfe) != PROTO_DIS_N0) { + if (st->l3.debug & L3_DEB_PROTERR) { + sprintf(tmp, "up1tr6%sunexpected discriminator %x message len %d", + (pr == (DL_DATA | INDICATION)) ? " " : "(broadcast) ", + skb->data[0], skb->len); + l3_debug(st, tmp); + } + dev_kfree_skb(skb); + return; + } + if (skb->data[1] != 1) { + if (st->l3.debug & L3_DEB_PROTERR) { + sprintf(tmp, "up1tr6 CR len not 1"); + l3_debug(st, tmp); + } + dev_kfree_skb(skb); + return; + } + cr = skb->data[2]; + mt = skb->data[3]; + if (skb->data[0] == PROTO_DIS_N0) { + dev_kfree_skb(skb); + if (st->l3.debug & L3_DEB_STATE) { + sprintf(tmp, "up1tr6%s N0 mt %x unhandled", + (pr == (DL_DATA | INDICATION)) ? " " : "(broadcast) ", mt); + l3_debug(st, tmp); + } + } else if (skb->data[0] == PROTO_DIS_N1) { + if (!(proc = getl3proc(st, cr))) { + if (mt == MT_N1_SETUP) { + if (cr < 128) { + if (!(proc = new_l3_process(st, cr))) { + if (st->l3.debug & L3_DEB_PROTERR) { + sprintf(tmp, "up1tr6 no roc mem"); + l3_debug(st, tmp); + } + dev_kfree_skb(skb); + return; + } + } else { + dev_kfree_skb(skb); + return; + } + } else if ((mt == MT_N1_REL) || (mt == MT_N1_REL_ACK) || + (mt == MT_N1_CANC_ACK) || (mt == MT_N1_CANC_REJ) || + (mt == MT_N1_REG_ACK) || (mt == MT_N1_REG_REJ) || + (mt == MT_N1_SUSP_ACK) || (mt == MT_N1_RES_REJ) || + (mt == MT_N1_INFO)) { + dev_kfree_skb(skb); + return; + } else { + if (!(proc = new_l3_process(st, cr))) { + if (st->l3.debug & L3_DEB_PROTERR) { + sprintf(tmp, "up1tr6 no roc mem"); + l3_debug(st, tmp); + } + dev_kfree_skb(skb); + return; + } + mt = MT_N1_INVALID; + } + } + for (i = 0; i < DATASTLN1_LEN; i++) + if ((mt == datastln1[i].primitive) && + ((1 << proc->state) & datastln1[i].state)) + break; + if (i == DATASTLN1_LEN) { + dev_kfree_skb(skb); + if (st->l3.debug & L3_DEB_STATE) { + sprintf(tmp, "up1tr6%sstate %d mt %x unhandled", + (pr == (DL_DATA | INDICATION)) ? " " : "(broadcast) ", + proc->state, mt); + l3_debug(st, tmp); + } + return; + } else { + if (st->l3.debug & L3_DEB_STATE) { + sprintf(tmp, "up1tr6%sstate %d mt %x", + (pr == (DL_DATA | INDICATION)) ? " " : "(broadcast) ", + proc->state, mt); + l3_debug(st, tmp); + } + datastln1[i].rout(proc, pr, skb); + } + } +} + +static void +down1tr6(struct PStack *st, int pr, void *arg) +{ + int i, cr; + struct l3_process *proc; + struct Channel *chan; + char tmp[80]; + + if ((DL_ESTABLISH | REQUEST)== pr) { + l3_msg(st, pr, NULL); + return; + } else if ((CC_SETUP | REQUEST) == pr) { + chan = arg; + cr = newcallref(); + cr |= 0x80; + if (!(proc = new_l3_process(st, cr))) { + return; + } else { + proc->chan = chan; + chan->proc = proc; + memcpy(&proc->para.setup, &chan->setup, sizeof(setup_parm)); + proc->callref = cr; + } + } else { + proc = arg; + } + + for (i = 0; i < DOWNSTL_LEN; i++) + if ((pr == downstl[i].primitive) && + ((1 << proc->state) & downstl[i].state)) + break; + if (i == DOWNSTL_LEN) { + if (st->l3.debug & L3_DEB_STATE) { + sprintf(tmp, "down1tr6 state %d prim %d unhandled", + proc->state, pr); + l3_debug(st, tmp); + } + } else { + if (st->l3.debug & L3_DEB_STATE) { + sprintf(tmp, "down1tr6 state %d prim %d", + proc->state, pr); + l3_debug(st, tmp); + } + downstl[i].rout(proc, pr, arg); + } +} + +static void +man1tr6(struct PStack *st, int pr, void *arg) +{ + int i; + struct l3_process *proc = arg; + + if (!proc) { + printk(KERN_ERR "HiSax man1tr6 without proc pr=%04x\n", pr); + return; + } + for (i = 0; i < MANSLLEN; i++) + if ((pr == manstatelist[i].primitive) && + ((1 << proc->state) & manstatelist[i].state)) + break; + if (i == MANSLLEN) { + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "cr %d man1tr6 state %d prim %d unhandled", + proc->callref & 0x7f, proc->state, pr); + } + } else { + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "cr %d man1tr6 state %d prim %d", + proc->callref & 0x7f, proc->state, pr); + } + manstatelist[i].rout(proc, pr, arg); + } +} + +void +setstack_1tr6(struct PStack *st) +{ + char tmp[64]; + + st->lli.l4l3 = down1tr6; + st->l2.l2l3 = up1tr6; + st->l3.l3ml3 = man1tr6; + st->l3.N303 = 0; + + strcpy(tmp, l3_1tr6_revision); + printk(KERN_INFO "HiSax: 1TR6 Rev. %s\n", HiSax_getrev(tmp)); +} diff --git a/drivers/isdn/hisax/l3_1tr6.h b/drivers/isdn/hisax/l3_1tr6.h new file mode 100644 index 000000000000..43215c00cada --- /dev/null +++ b/drivers/isdn/hisax/l3_1tr6.h @@ -0,0 +1,164 @@ +/* $Id: l3_1tr6.h,v 2.2.6.2 2001/09/23 22:24:49 kai Exp $ + * + * German 1TR6 D-channel protocol defines + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#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 +#define MT_N1_INVALID 0 + +/* + * 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 + +#define T303 4000 +#define T304 20000 +#define T305 4000 +#define T308 4000 +#define T310 120000 +#define T313 4000 +#define T318 4000 +#define T319 4000 + +#endif diff --git a/drivers/isdn/hisax/l3dss1.c b/drivers/isdn/hisax/l3dss1.c new file mode 100644 index 000000000000..ec92308c1efc --- /dev/null +++ b/drivers/isdn/hisax/l3dss1.c @@ -0,0 +1,3238 @@ +/* $Id: l3dss1.c,v 2.32.2.3 2004/01/13 14:31:25 keil Exp $ + * + * EURO/DSS1 D-channel protocol + * + * German 1TR6 D-channel protocol + * + * Author Karsten Keil + * based on the teles driver from Jan den Ouden + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * For changes and modifications please read + * Documentation/isdn/HiSax.cert + * + * Thanks to Jan den Ouden + * Fritz Elfert + * + */ + +#include "hisax.h" +#include "isdnl3.h" +#include "l3dss1.h" +#include <linux/ctype.h> +#include <linux/config.h> + +extern char *HiSax_getrev(const char *revision); +const char *dss1_revision = "$Revision: 2.32.2.3 $"; + +#define EXT_BEARER_CAPS 1 + +#define MsgHead(ptr, cref, mty) \ + *ptr++ = 0x8; \ + if (cref == -1) { \ + *ptr++ = 0x0; \ + } else { \ + *ptr++ = 0x1; \ + *ptr++ = cref^0x80; \ + } \ + *ptr++ = mty + + +/**********************************************/ +/* get a new invoke id for remote operations. */ +/* Only a return value != 0 is valid */ +/**********************************************/ +static unsigned char new_invoke_id(struct PStack *p) +{ + unsigned char retval; + int i; + + i = 32; /* maximum search depth */ + + retval = p->prot.dss1.last_invoke_id + 1; /* try new id */ + while ((i) && (p->prot.dss1.invoke_used[retval >> 3] == 0xFF)) { + p->prot.dss1.last_invoke_id = (retval & 0xF8) + 8; + i--; + } + if (i) { + while (p->prot.dss1.invoke_used[retval >> 3] & (1 << (retval & 7))) + retval++; + } else + retval = 0; + p->prot.dss1.last_invoke_id = retval; + p->prot.dss1.invoke_used[retval >> 3] |= (1 << (retval & 7)); + return(retval); +} /* new_invoke_id */ + +/*************************/ +/* free a used invoke id */ +/*************************/ +static void free_invoke_id(struct PStack *p, unsigned char id) +{ + + if (!id) return; /* 0 = invalid value */ + + p->prot.dss1.invoke_used[id >> 3] &= ~(1 << (id & 7)); +} /* free_invoke_id */ + + +/**********************************************************/ +/* create a new l3 process and fill in dss1 specific data */ +/**********************************************************/ +static struct l3_process +*dss1_new_l3_process(struct PStack *st, int cr) +{ struct l3_process *proc; + + if (!(proc = new_l3_process(st, cr))) + return(NULL); + + proc->prot.dss1.invoke_id = 0; + proc->prot.dss1.remote_operation = 0; + proc->prot.dss1.uus1_data[0] = '\0'; + + return(proc); +} /* dss1_new_l3_process */ + +/************************************************/ +/* free a l3 process and all dss1 specific data */ +/************************************************/ +static void +dss1_release_l3_process(struct l3_process *p) +{ + free_invoke_id(p->st,p->prot.dss1.invoke_id); + release_l3_process(p); +} /* dss1_release_l3_process */ + +/********************************************************/ +/* search a process with invoke id id and dummy callref */ +/********************************************************/ +static struct l3_process * +l3dss1_search_dummy_proc(struct PStack *st, int id) +{ struct l3_process *pc = st->l3.proc; /* start of processes */ + + if (!id) return(NULL); + + while (pc) + { if ((pc->callref == -1) && (pc->prot.dss1.invoke_id == id)) + return(pc); + pc = pc->next; + } + return(NULL); +} /* l3dss1_search_dummy_proc */ + +/*******************************************************************/ +/* called when a facility message with a dummy callref is received */ +/* and a return result is delivered. id specifies the invoke id. */ +/*******************************************************************/ +static void +l3dss1_dummy_return_result(struct PStack *st, int id, u_char *p, u_char nlen) +{ isdn_ctrl ic; + struct IsdnCardState *cs; + struct l3_process *pc = NULL; + + if ((pc = l3dss1_search_dummy_proc(st, id))) + { L3DelTimer(&pc->timer); /* remove timer */ + + cs = pc->st->l1.hardware; + ic.driver = cs->myid; + ic.command = ISDN_STAT_PROT; + ic.arg = DSS1_STAT_INVOKE_RES; + ic.parm.dss1_io.hl_id = pc->prot.dss1.invoke_id; + ic.parm.dss1_io.ll_id = pc->prot.dss1.ll_id; + ic.parm.dss1_io.proc = pc->prot.dss1.proc; + ic.parm.dss1_io.timeout= 0; + ic.parm.dss1_io.datalen = nlen; + ic.parm.dss1_io.data = p; + free_invoke_id(pc->st, pc->prot.dss1.invoke_id); + pc->prot.dss1.invoke_id = 0; /* reset id */ + + cs->iif.statcallb(&ic); + dss1_release_l3_process(pc); + } + else + l3_debug(st, "dummy return result id=0x%x result len=%d",id,nlen); +} /* l3dss1_dummy_return_result */ + +/*******************************************************************/ +/* called when a facility message with a dummy callref is received */ +/* and a return error is delivered. id specifies the invoke id. */ +/*******************************************************************/ +static void +l3dss1_dummy_error_return(struct PStack *st, int id, ulong error) +{ isdn_ctrl ic; + struct IsdnCardState *cs; + struct l3_process *pc = NULL; + + if ((pc = l3dss1_search_dummy_proc(st, id))) + { L3DelTimer(&pc->timer); /* remove timer */ + + cs = pc->st->l1.hardware; + ic.driver = cs->myid; + ic.command = ISDN_STAT_PROT; + ic.arg = DSS1_STAT_INVOKE_ERR; + ic.parm.dss1_io.hl_id = pc->prot.dss1.invoke_id; + ic.parm.dss1_io.ll_id = pc->prot.dss1.ll_id; + ic.parm.dss1_io.proc = pc->prot.dss1.proc; + ic.parm.dss1_io.timeout= error; + ic.parm.dss1_io.datalen = 0; + ic.parm.dss1_io.data = NULL; + free_invoke_id(pc->st, pc->prot.dss1.invoke_id); + pc->prot.dss1.invoke_id = 0; /* reset id */ + + cs->iif.statcallb(&ic); + dss1_release_l3_process(pc); + } + else + l3_debug(st, "dummy return error id=0x%x error=0x%lx",id,error); +} /* l3dss1_error_return */ + +/*******************************************************************/ +/* called when a facility message with a dummy callref is received */ +/* and a invoke is delivered. id specifies the invoke id. */ +/*******************************************************************/ +static void +l3dss1_dummy_invoke(struct PStack *st, int cr, int id, + int ident, u_char *p, u_char nlen) +{ isdn_ctrl ic; + struct IsdnCardState *cs; + + l3_debug(st, "dummy invoke %s id=0x%x ident=0x%x datalen=%d", + (cr == -1) ? "local" : "broadcast",id,ident,nlen); + if (cr >= -1) return; /* ignore local data */ + + cs = st->l1.hardware; + ic.driver = cs->myid; + ic.command = ISDN_STAT_PROT; + ic.arg = DSS1_STAT_INVOKE_BRD; + ic.parm.dss1_io.hl_id = id; + ic.parm.dss1_io.ll_id = 0; + ic.parm.dss1_io.proc = ident; + ic.parm.dss1_io.timeout= 0; + ic.parm.dss1_io.datalen = nlen; + ic.parm.dss1_io.data = p; + + cs->iif.statcallb(&ic); +} /* l3dss1_dummy_invoke */ + +static void +l3dss1_parse_facility(struct PStack *st, struct l3_process *pc, + int cr, u_char * p) +{ + int qd_len = 0; + unsigned char nlen = 0, ilen, cp_tag; + int ident, id; + ulong err_ret; + + if (pc) + st = pc->st; /* valid Stack */ + else + if ((!st) || (cr >= 0)) return; /* neither pc nor st specified */ + + p++; + qd_len = *p++; + if (qd_len == 0) { + l3_debug(st, "qd_len == 0"); + return; + } + if ((*p & 0x1F) != 0x11) { /* Service discriminator, supplementary service */ + l3_debug(st, "supplementary service != 0x11"); + return; + } + while (qd_len > 0 && !(*p & 0x80)) { /* extension ? */ + p++; + qd_len--; + } + if (qd_len < 2) { + l3_debug(st, "qd_len < 2"); + return; + } + p++; + qd_len--; + if ((*p & 0xE0) != 0xA0) { /* class and form */ + l3_debug(st, "class and form != 0xA0"); + return; + } + + cp_tag = *p & 0x1F; /* remember tag value */ + + p++; + qd_len--; + if (qd_len < 1) + { l3_debug(st, "qd_len < 1"); + return; + } + if (*p & 0x80) + { /* length format indefinite or limited */ + nlen = *p++ & 0x7F; /* number of len bytes or indefinite */ + if ((qd_len-- < ((!nlen) ? 3 : (1 + nlen))) || + (nlen > 1)) + { l3_debug(st, "length format error or not implemented"); + return; + } + if (nlen == 1) + { nlen = *p++; /* complete length */ + qd_len--; + } + else + { qd_len -= 2; /* trailing null bytes */ + if ((*(p+qd_len)) || (*(p+qd_len+1))) + { l3_debug(st,"length format indefinite error"); + return; + } + nlen = qd_len; + } + } + else + { nlen = *p++; + qd_len--; + } + if (qd_len < nlen) + { l3_debug(st, "qd_len < nlen"); + return; + } + qd_len -= nlen; + + if (nlen < 2) + { l3_debug(st, "nlen < 2"); + return; + } + if (*p != 0x02) + { /* invoke identifier tag */ + l3_debug(st, "invoke identifier tag !=0x02"); + return; + } + p++; + nlen--; + if (*p & 0x80) + { /* length format */ + l3_debug(st, "invoke id length format 2"); + return; + } + ilen = *p++; + nlen--; + if (ilen > nlen || ilen == 0) + { l3_debug(st, "ilen > nlen || ilen == 0"); + return; + } + nlen -= ilen; + id = 0; + while (ilen > 0) + { id = (id << 8) | (*p++ & 0xFF); /* invoke identifier */ + ilen--; + } + + switch (cp_tag) { /* component tag */ + case 1: /* invoke */ + if (nlen < 2) { + l3_debug(st, "nlen < 2 22"); + return; + } + if (*p != 0x02) { /* operation value */ + l3_debug(st, "operation value !=0x02"); + return; + } + p++; + nlen--; + ilen = *p++; + nlen--; + if (ilen > nlen || ilen == 0) { + l3_debug(st, "ilen > nlen || ilen == 0 22"); + return; + } + nlen -= ilen; + ident = 0; + while (ilen > 0) { + ident = (ident << 8) | (*p++ & 0xFF); + ilen--; + } + + if (!pc) + { l3dss1_dummy_invoke(st, cr, id, ident, p, nlen); + return; + } +#if HISAX_DE_AOC + { + +#define FOO1(s,a,b) \ + while(nlen > 1) { \ + int ilen = p[1]; \ + if(nlen < ilen+2) { \ + l3_debug(st, "FOO1 nlen < ilen+2"); \ + return; \ + } \ + nlen -= ilen+2; \ + if((*p & 0xFF) == (a)) { \ + int nlen = ilen; \ + p += 2; \ + b; \ + } else { \ + p += ilen+2; \ + } \ + } + + switch (ident) { + case 0x22: /* during */ + FOO1("1A", 0x30, FOO1("1C", 0xA1, FOO1("1D", 0x30, FOO1("1E", 0x02, ( { + ident = 0; + nlen = (nlen)?nlen:0; /* Make gcc happy */ + while (ilen > 0) { + ident = (ident << 8) | *p++; + ilen--; + } + if (ident > pc->para.chargeinfo) { + pc->para.chargeinfo = ident; + st->l3.l3l4(st, CC_CHARGE | INDICATION, pc); + } + if (st->l3.debug & L3_DEB_CHARGE) { + if (*(p + 2) == 0) { + l3_debug(st, "charging info during %d", pc->para.chargeinfo); + } + else { + l3_debug(st, "charging info final %d", pc->para.chargeinfo); + } + } + } + ))))) + break; + case 0x24: /* final */ + FOO1("2A", 0x30, FOO1("2B", 0x30, FOO1("2C", 0xA1, FOO1("2D", 0x30, FOO1("2E", 0x02, ( { + ident = 0; + nlen = (nlen)?nlen:0; /* Make gcc happy */ + while (ilen > 0) { + ident = (ident << 8) | *p++; + ilen--; + } + if (ident > pc->para.chargeinfo) { + pc->para.chargeinfo = ident; + st->l3.l3l4(st, CC_CHARGE | INDICATION, pc); + } + if (st->l3.debug & L3_DEB_CHARGE) { + l3_debug(st, "charging info final %d", pc->para.chargeinfo); + } + } + )))))) + break; + default: + l3_debug(st, "invoke break invalid ident %02x",ident); + break; + } +#undef FOO1 + + } +#else /* not HISAX_DE_AOC */ + l3_debug(st, "invoke break"); +#endif /* not HISAX_DE_AOC */ + break; + case 2: /* return result */ + /* if no process available handle separately */ + if (!pc) + { if (cr == -1) + l3dss1_dummy_return_result(st, id, p, nlen); + return; + } + if ((pc->prot.dss1.invoke_id) && (pc->prot.dss1.invoke_id == id)) + { /* Diversion successful */ + free_invoke_id(st,pc->prot.dss1.invoke_id); + pc->prot.dss1.remote_result = 0; /* success */ + pc->prot.dss1.invoke_id = 0; + pc->redir_result = pc->prot.dss1.remote_result; + st->l3.l3l4(st, CC_REDIR | INDICATION, pc); } /* Diversion successful */ + else + l3_debug(st,"return error unknown identifier"); + break; + case 3: /* return error */ + err_ret = 0; + if (nlen < 2) + { l3_debug(st, "return error nlen < 2"); + return; + } + if (*p != 0x02) + { /* result tag */ + l3_debug(st, "invoke error tag !=0x02"); + return; + } + p++; + nlen--; + if (*p > 4) + { /* length format */ + l3_debug(st, "invoke return errlen > 4 "); + return; + } + ilen = *p++; + nlen--; + if (ilen > nlen || ilen == 0) + { l3_debug(st, "error return ilen > nlen || ilen == 0"); + return; + } + nlen -= ilen; + while (ilen > 0) + { err_ret = (err_ret << 8) | (*p++ & 0xFF); /* error value */ + ilen--; + } + /* if no process available handle separately */ + if (!pc) + { if (cr == -1) + l3dss1_dummy_error_return(st, id, err_ret); + return; + } + if ((pc->prot.dss1.invoke_id) && (pc->prot.dss1.invoke_id == id)) + { /* Deflection error */ + free_invoke_id(st,pc->prot.dss1.invoke_id); + pc->prot.dss1.remote_result = err_ret; /* result */ + pc->prot.dss1.invoke_id = 0; + pc->redir_result = pc->prot.dss1.remote_result; + st->l3.l3l4(st, CC_REDIR | INDICATION, pc); + } /* Deflection error */ + else + l3_debug(st,"return result unknown identifier"); + break; + default: + l3_debug(st, "facility default break tag=0x%02x",cp_tag); + break; + } +} + +static void +l3dss1_message(struct l3_process *pc, u_char mt) +{ + struct sk_buff *skb; + u_char *p; + + if (!(skb = l3_alloc_skb(4))) + return; + p = skb_put(skb, 4); + MsgHead(p, pc->callref, mt); + l3_msg(pc->st, DL_DATA | REQUEST, skb); +} + +static void +l3dss1_message_cause(struct l3_process *pc, u_char mt, u_char cause) +{ + struct sk_buff *skb; + u_char tmp[16]; + u_char *p = tmp; + int l; + + MsgHead(p, pc->callref, mt); + *p++ = IE_CAUSE; + *p++ = 0x2; + *p++ = 0x80; + *p++ = cause | 0x80; + + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + memcpy(skb_put(skb, l), tmp, l); + l3_msg(pc->st, DL_DATA | REQUEST, skb); +} + +static void +l3dss1_status_send(struct l3_process *pc, u_char pr, void *arg) +{ + u_char tmp[16]; + u_char *p = tmp; + int l; + struct sk_buff *skb; + + MsgHead(p, pc->callref, MT_STATUS); + + *p++ = IE_CAUSE; + *p++ = 0x2; + *p++ = 0x80; + *p++ = pc->para.cause | 0x80; + + *p++ = IE_CALL_STATE; + *p++ = 0x1; + *p++ = pc->state & 0x3f; + + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + memcpy(skb_put(skb, l), tmp, l); + l3_msg(pc->st, DL_DATA | REQUEST, skb); +} + +static void +l3dss1_msg_without_setup(struct l3_process *pc, u_char pr, void *arg) +{ + /* This routine is called if here was no SETUP made (checks in dss1up and in + * l3dss1_setup) and a RELEASE_COMPLETE have to be sent with an error code + * MT_STATUS_ENQUIRE in the NULL state is handled too + */ + u_char tmp[16]; + u_char *p = tmp; + int l; + struct sk_buff *skb; + + switch (pc->para.cause) { + case 81: /* invalid callreference */ + case 88: /* incomp destination */ + case 96: /* mandory IE missing */ + case 100: /* invalid IE contents */ + case 101: /* incompatible Callstate */ + MsgHead(p, pc->callref, MT_RELEASE_COMPLETE); + *p++ = IE_CAUSE; + *p++ = 0x2; + *p++ = 0x80; + *p++ = pc->para.cause | 0x80; + break; + default: + printk(KERN_ERR "HiSax l3dss1_msg_without_setup wrong cause %d\n", + pc->para.cause); + return; + } + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + memcpy(skb_put(skb, l), tmp, l); + l3_msg(pc->st, DL_DATA | REQUEST, skb); + dss1_release_l3_process(pc); +} + +static int ie_ALERTING[] = {IE_BEARER, IE_CHANNEL_ID | IE_MANDATORY_1, + IE_FACILITY, IE_PROGRESS, IE_DISPLAY, IE_SIGNAL, IE_HLC, + IE_USER_USER, -1}; +static int ie_CALL_PROCEEDING[] = {IE_BEARER, IE_CHANNEL_ID | IE_MANDATORY_1, + IE_FACILITY, IE_PROGRESS, IE_DISPLAY, IE_HLC, -1}; +static int ie_CONNECT[] = {IE_BEARER, IE_CHANNEL_ID | IE_MANDATORY_1, + IE_FACILITY, IE_PROGRESS, IE_DISPLAY, IE_DATE, IE_SIGNAL, + IE_CONNECT_PN, IE_CONNECT_SUB, IE_LLC, IE_HLC, IE_USER_USER, -1}; +static int ie_CONNECT_ACKNOWLEDGE[] = {IE_CHANNEL_ID, IE_DISPLAY, IE_SIGNAL, -1}; +static int ie_DISCONNECT[] = {IE_CAUSE | IE_MANDATORY, IE_FACILITY, + IE_PROGRESS, IE_DISPLAY, IE_SIGNAL, IE_USER_USER, -1}; +static int ie_INFORMATION[] = {IE_COMPLETE, IE_DISPLAY, IE_KEYPAD, IE_SIGNAL, + IE_CALLED_PN, -1}; +static int ie_NOTIFY[] = {IE_BEARER, IE_NOTIFY | IE_MANDATORY, IE_DISPLAY, -1}; +static int ie_PROGRESS[] = {IE_BEARER, IE_CAUSE, IE_FACILITY, IE_PROGRESS | + IE_MANDATORY, IE_DISPLAY, IE_HLC, IE_USER_USER, -1}; +static int ie_RELEASE[] = {IE_CAUSE | IE_MANDATORY_1, IE_FACILITY, IE_DISPLAY, + IE_SIGNAL, IE_USER_USER, -1}; +/* a RELEASE_COMPLETE with errors don't require special actions +static int ie_RELEASE_COMPLETE[] = {IE_CAUSE | IE_MANDATORY_1, IE_DISPLAY, IE_SIGNAL, IE_USER_USER, -1}; +*/ +static int ie_RESUME_ACKNOWLEDGE[] = {IE_CHANNEL_ID| IE_MANDATORY, IE_FACILITY, + IE_DISPLAY, -1}; +static int ie_RESUME_REJECT[] = {IE_CAUSE | IE_MANDATORY, IE_DISPLAY, -1}; +static int ie_SETUP[] = {IE_COMPLETE, IE_BEARER | IE_MANDATORY, + IE_CHANNEL_ID| IE_MANDATORY, IE_FACILITY, IE_PROGRESS, + IE_NET_FAC, IE_DISPLAY, IE_KEYPAD, IE_SIGNAL, IE_CALLING_PN, + IE_CALLING_SUB, IE_CALLED_PN, IE_CALLED_SUB, IE_REDIR_NR, + IE_LLC, IE_HLC, IE_USER_USER, -1}; +static int ie_SETUP_ACKNOWLEDGE[] = {IE_CHANNEL_ID | IE_MANDATORY, IE_FACILITY, + IE_PROGRESS, IE_DISPLAY, IE_SIGNAL, -1}; +static int ie_STATUS[] = {IE_CAUSE | IE_MANDATORY, IE_CALL_STATE | + IE_MANDATORY, IE_DISPLAY, -1}; +static int ie_STATUS_ENQUIRY[] = {IE_DISPLAY, -1}; +static int ie_SUSPEND_ACKNOWLEDGE[] = {IE_DISPLAY, IE_FACILITY, -1}; +static int ie_SUSPEND_REJECT[] = {IE_CAUSE | IE_MANDATORY, IE_DISPLAY, -1}; +/* not used + * static int ie_CONGESTION_CONTROL[] = {IE_CONGESTION | IE_MANDATORY, + * IE_CAUSE | IE_MANDATORY, IE_DISPLAY, -1}; + * static int ie_USER_INFORMATION[] = {IE_MORE_DATA, IE_USER_USER | IE_MANDATORY, -1}; + * static int ie_RESTART[] = {IE_CHANNEL_ID, IE_DISPLAY, IE_RESTART_IND | + * IE_MANDATORY, -1}; + */ +static int ie_FACILITY[] = {IE_FACILITY | IE_MANDATORY, IE_DISPLAY, -1}; +static int comp_required[] = {1,2,3,5,6,7,9,10,11,14,15,-1}; +static int l3_valid_states[] = {0,1,2,3,4,6,7,8,9,10,11,12,15,17,19,25,-1}; + +struct ie_len { + int ie; + int len; +}; + +static +struct ie_len max_ie_len[] = { + {IE_SEGMENT, 4}, + {IE_BEARER, 12}, + {IE_CAUSE, 32}, + {IE_CALL_ID, 10}, + {IE_CALL_STATE, 3}, + {IE_CHANNEL_ID, 34}, + {IE_FACILITY, 255}, + {IE_PROGRESS, 4}, + {IE_NET_FAC, 255}, + {IE_NOTIFY, 3}, + {IE_DISPLAY, 82}, + {IE_DATE, 8}, + {IE_KEYPAD, 34}, + {IE_SIGNAL, 3}, + {IE_INFORATE, 6}, + {IE_E2E_TDELAY, 11}, + {IE_TDELAY_SEL, 5}, + {IE_PACK_BINPARA, 3}, + {IE_PACK_WINSIZE, 4}, + {IE_PACK_SIZE, 4}, + {IE_CUG, 7}, + {IE_REV_CHARGE, 3}, + {IE_CALLING_PN, 24}, + {IE_CALLING_SUB, 23}, + {IE_CALLED_PN, 24}, + {IE_CALLED_SUB, 23}, + {IE_REDIR_NR, 255}, + {IE_TRANS_SEL, 255}, + {IE_RESTART_IND, 3}, + {IE_LLC, 18}, + {IE_HLC, 5}, + {IE_USER_USER, 131}, + {-1,0}, +}; + +static int +getmax_ie_len(u_char ie) { + int i = 0; + while (max_ie_len[i].ie != -1) { + if (max_ie_len[i].ie == ie) + return(max_ie_len[i].len); + i++; + } + return(255); +} + +static int +ie_in_set(struct l3_process *pc, u_char ie, int *checklist) { + int ret = 1; + + while (*checklist != -1) { + if ((*checklist & 0xff) == ie) { + if (ie & 0x80) + return(-ret); + else + return(ret); + } + ret++; + checklist++; + } + return(0); +} + +static int +check_infoelements(struct l3_process *pc, struct sk_buff *skb, int *checklist) +{ + int *cl = checklist; + u_char mt; + u_char *p, ie; + int l, newpos, oldpos; + int err_seq = 0, err_len = 0, err_compr = 0, err_ureg = 0; + u_char codeset = 0; + u_char old_codeset = 0; + u_char codelock = 1; + + p = skb->data; + /* skip cr */ + p++; + l = (*p++) & 0xf; + p += l; + mt = *p++; + oldpos = 0; + while ((p - skb->data) < skb->len) { + if ((*p & 0xf0) == 0x90) { /* shift codeset */ + old_codeset = codeset; + codeset = *p & 7; + if (*p & 0x08) + codelock = 0; + else + codelock = 1; + if (pc->debug & L3_DEB_CHECK) + l3_debug(pc->st, "check IE shift%scodeset %d->%d", + codelock ? " locking ": " ", old_codeset, codeset); + p++; + continue; + } + if (!codeset) { /* only codeset 0 */ + if ((newpos = ie_in_set(pc, *p, cl))) { + if (newpos > 0) { + if (newpos < oldpos) + err_seq++; + else + oldpos = newpos; + } + } else { + if (ie_in_set(pc, *p, comp_required)) + err_compr++; + else + err_ureg++; + } + } + ie = *p++; + if (ie & 0x80) { + l = 1; + } else { + l = *p++; + p += l; + l += 2; + } + if (!codeset && (l > getmax_ie_len(ie))) + err_len++; + if (!codelock) { + if (pc->debug & L3_DEB_CHECK) + l3_debug(pc->st, "check IE shift back codeset %d->%d", + codeset, old_codeset); + codeset = old_codeset; + codelock = 1; + } + } + if (err_compr | err_ureg | err_len | err_seq) { + if (pc->debug & L3_DEB_CHECK) + l3_debug(pc->st, "check IE MT(%x) %d/%d/%d/%d", + mt, err_compr, err_ureg, err_len, err_seq); + if (err_compr) + return(ERR_IE_COMPREHENSION); + if (err_ureg) + return(ERR_IE_UNRECOGNIZED); + if (err_len) + return(ERR_IE_LENGTH); + if (err_seq) + return(ERR_IE_SEQUENCE); + } + return(0); +} + +/* verify if a message type exists and contain no IE error */ +static int +l3dss1_check_messagetype_validity(struct l3_process *pc, int mt, void *arg) +{ + switch (mt) { + case MT_ALERTING: + case MT_CALL_PROCEEDING: + case MT_CONNECT: + case MT_CONNECT_ACKNOWLEDGE: + case MT_DISCONNECT: + case MT_INFORMATION: + case MT_FACILITY: + case MT_NOTIFY: + case MT_PROGRESS: + case MT_RELEASE: + case MT_RELEASE_COMPLETE: + case MT_SETUP: + case MT_SETUP_ACKNOWLEDGE: + case MT_RESUME_ACKNOWLEDGE: + case MT_RESUME_REJECT: + case MT_SUSPEND_ACKNOWLEDGE: + case MT_SUSPEND_REJECT: + case MT_USER_INFORMATION: + case MT_RESTART: + case MT_RESTART_ACKNOWLEDGE: + case MT_CONGESTION_CONTROL: + case MT_STATUS: + case MT_STATUS_ENQUIRY: + if (pc->debug & L3_DEB_CHECK) + l3_debug(pc->st, "l3dss1_check_messagetype_validity mt(%x) OK", mt); + break; + case MT_RESUME: /* RESUME only in user->net */ + case MT_SUSPEND: /* SUSPEND only in user->net */ + default: + if (pc->debug & (L3_DEB_CHECK | L3_DEB_WARN)) + l3_debug(pc->st, "l3dss1_check_messagetype_validity mt(%x) fail", mt); + pc->para.cause = 97; + l3dss1_status_send(pc, 0, NULL); + return(1); + } + return(0); +} + +static void +l3dss1_std_ie_err(struct l3_process *pc, int ret) { + + if (pc->debug & L3_DEB_CHECK) + l3_debug(pc->st, "check_infoelements ret %d", ret); + switch(ret) { + case 0: + break; + case ERR_IE_COMPREHENSION: + pc->para.cause = 96; + l3dss1_status_send(pc, 0, NULL); + break; + case ERR_IE_UNRECOGNIZED: + pc->para.cause = 99; + l3dss1_status_send(pc, 0, NULL); + break; + case ERR_IE_LENGTH: + pc->para.cause = 100; + l3dss1_status_send(pc, 0, NULL); + break; + case ERR_IE_SEQUENCE: + default: + break; + } +} + +static int +l3dss1_get_channel_id(struct l3_process *pc, struct sk_buff *skb) { + u_char *p; + + p = skb->data; + if ((p = findie(p, skb->len, IE_CHANNEL_ID, 0))) { + p++; + if (*p != 1) { /* len for BRI = 1 */ + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "wrong chid len %d", *p); + return (-2); + } + p++; + if (*p & 0x60) { /* only base rate interface */ + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "wrong chid %x", *p); + return (-3); + } + return(*p & 0x3); + } else + return(-1); +} + +static int +l3dss1_get_cause(struct l3_process *pc, struct sk_buff *skb) { + u_char l, i=0; + u_char *p; + + p = skb->data; + pc->para.cause = 31; + pc->para.loc = 0; + if ((p = findie(p, skb->len, IE_CAUSE, 0))) { + p++; + l = *p++; + if (l>30) + return(1); + if (l) { + pc->para.loc = *p++; + l--; + } else { + return(2); + } + if (l && !(pc->para.loc & 0x80)) { + l--; + p++; /* skip recommendation */ + } + if (l) { + pc->para.cause = *p++; + l--; + if (!(pc->para.cause & 0x80)) + return(3); + } else + return(4); + while (l && (i<6)) { + pc->para.diag[i++] = *p++; + l--; + } + } else + return(-1); + return(0); +} + +static void +l3dss1_msg_with_uus(struct l3_process *pc, u_char cmd) +{ + struct sk_buff *skb; + u_char tmp[16+40]; + u_char *p = tmp; + int l; + + MsgHead(p, pc->callref, cmd); + + if (pc->prot.dss1.uus1_data[0]) + { *p++ = IE_USER_USER; /* UUS info element */ + *p++ = strlen(pc->prot.dss1.uus1_data) + 1; + *p++ = 0x04; /* IA5 chars */ + strcpy(p,pc->prot.dss1.uus1_data); + p += strlen(pc->prot.dss1.uus1_data); + pc->prot.dss1.uus1_data[0] = '\0'; + } + + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + memcpy(skb_put(skb, l), tmp, l); + l3_msg(pc->st, DL_DATA | REQUEST, skb); +} /* l3dss1_msg_with_uus */ + +static void +l3dss1_release_req(struct l3_process *pc, u_char pr, void *arg) +{ + StopAllL3Timer(pc); + newl3state(pc, 19); + if (!pc->prot.dss1.uus1_data[0]) + l3dss1_message(pc, MT_RELEASE); + else + l3dss1_msg_with_uus(pc, MT_RELEASE); + L3AddTimer(&pc->timer, T308, CC_T308_1); +} + +static void +l3dss1_release_cmpl(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int ret; + + if ((ret = l3dss1_get_cause(pc, skb))>0) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "RELCMPL get_cause ret(%d)",ret); + } else if (ret < 0) + pc->para.cause = NO_CAUSE; + StopAllL3Timer(pc); + newl3state(pc, 0); + pc->st->l3.l3l4(pc->st, CC_RELEASE | CONFIRM, pc); + dss1_release_l3_process(pc); +} + +#if EXT_BEARER_CAPS + +static u_char * +EncodeASyncParams(u_char * p, u_char si2) +{ // 7c 06 88 90 21 42 00 bb + + p[0] = 0; + p[1] = 0x40; // Intermediate rate: 16 kbit/s jj 2000.02.19 + p[2] = 0x80; + if (si2 & 32) // 7 data bits + + p[2] += 16; + else // 8 data bits + + p[2] += 24; + + if (si2 & 16) // 2 stop bits + + p[2] += 96; + else // 1 stop bit + + p[2] += 32; + + if (si2 & 8) // even parity + + p[2] += 2; + else // no parity + + p[2] += 3; + + switch (si2 & 0x07) { + case 0: + p[0] = 66; // 1200 bit/s + + break; + case 1: + p[0] = 88; // 1200/75 bit/s + + break; + case 2: + p[0] = 87; // 75/1200 bit/s + + break; + case 3: + p[0] = 67; // 2400 bit/s + + break; + case 4: + p[0] = 69; // 4800 bit/s + + break; + case 5: + p[0] = 72; // 9600 bit/s + + break; + case 6: + p[0] = 73; // 14400 bit/s + + break; + case 7: + p[0] = 75; // 19200 bit/s + + break; + } + return p + 3; +} + +static u_char +EncodeSyncParams(u_char si2, u_char ai) +{ + + switch (si2) { + case 0: + return ai + 2; // 1200 bit/s + + case 1: + return ai + 24; // 1200/75 bit/s + + case 2: + return ai + 23; // 75/1200 bit/s + + case 3: + return ai + 3; // 2400 bit/s + + case 4: + return ai + 5; // 4800 bit/s + + case 5: + return ai + 8; // 9600 bit/s + + case 6: + return ai + 9; // 14400 bit/s + + case 7: + return ai + 11; // 19200 bit/s + + case 8: + return ai + 14; // 48000 bit/s + + case 9: + return ai + 15; // 56000 bit/s + + case 15: + return ai + 40; // negotiate bit/s + + default: + break; + } + return ai; +} + + +static u_char +DecodeASyncParams(u_char si2, u_char * p) +{ + u_char info; + + switch (p[5]) { + case 66: // 1200 bit/s + + break; // si2 don't change + + case 88: // 1200/75 bit/s + + si2 += 1; + break; + case 87: // 75/1200 bit/s + + si2 += 2; + break; + case 67: // 2400 bit/s + + si2 += 3; + break; + case 69: // 4800 bit/s + + si2 += 4; + break; + case 72: // 9600 bit/s + + si2 += 5; + break; + case 73: // 14400 bit/s + + si2 += 6; + break; + case 75: // 19200 bit/s + + si2 += 7; + break; + } + + info = p[7] & 0x7f; + if ((info & 16) && (!(info & 8))) // 7 data bits + + si2 += 32; // else 8 data bits + + if ((info & 96) == 96) // 2 stop bits + + si2 += 16; // else 1 stop bit + + if ((info & 2) && (!(info & 1))) // even parity + + si2 += 8; // else no parity + + return si2; +} + + +static u_char +DecodeSyncParams(u_char si2, u_char info) +{ + info &= 0x7f; + switch (info) { + case 40: // bit/s negotiation failed ai := 165 not 175! + + return si2 + 15; + case 15: // 56000 bit/s failed, ai := 0 not 169 ! + + return si2 + 9; + case 14: // 48000 bit/s + + return si2 + 8; + case 11: // 19200 bit/s + + return si2 + 7; + case 9: // 14400 bit/s + + return si2 + 6; + case 8: // 9600 bit/s + + return si2 + 5; + case 5: // 4800 bit/s + + return si2 + 4; + case 3: // 2400 bit/s + + return si2 + 3; + case 23: // 75/1200 bit/s + + return si2 + 2; + case 24: // 1200/75 bit/s + + return si2 + 1; + default: // 1200 bit/s + + return si2; + } +} + +static u_char +DecodeSI2(struct sk_buff *skb) +{ + u_char *p; //, *pend=skb->data + skb->len; + + if ((p = findie(skb->data, skb->len, 0x7c, 0))) { + switch (p[4] & 0x0f) { + case 0x01: + if (p[1] == 0x04) // sync. Bitratenadaption + + return DecodeSyncParams(160, p[5]); // V.110/X.30 + + else if (p[1] == 0x06) // async. Bitratenadaption + + return DecodeASyncParams(192, p); // V.110/X.30 + + break; + case 0x08: // if (p[5] == 0x02) // sync. Bitratenadaption + if (p[1] > 3) + return DecodeSyncParams(176, p[5]); // V.120 + break; + } + } + return 0; +} + +#endif + + +static void +l3dss1_setup_req(struct l3_process *pc, u_char pr, + void *arg) +{ + struct sk_buff *skb; + u_char tmp[128]; + u_char *p = tmp; + u_char channel = 0; + + u_char send_keypad; + u_char screen = 0x80; + u_char *teln; + u_char *msn; + u_char *sub; + u_char *sp; + int l; + + MsgHead(p, pc->callref, MT_SETUP); + + teln = pc->para.setup.phone; +#ifndef CONFIG_HISAX_NO_KEYPAD + send_keypad = (strchr(teln,'*') || strchr(teln,'#')) ? 1 : 0; +#else + send_keypad = 0; +#endif +#ifndef CONFIG_HISAX_NO_SENDCOMPLETE + if (!send_keypad) + *p++ = 0xa1; /* complete indicator */ +#endif + /* + * Set Bearer Capability, Map info from 1TR6-convention to EDSS1 + */ + switch (pc->para.setup.si1) { + case 1: /* Telephony */ + *p++ = IE_BEARER; + *p++ = 0x3; /* Length */ + *p++ = 0x90; /* Coding Std. CCITT, 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++ = IE_BEARER; + *p++ = 0x2; /* Length */ + *p++ = 0x88; /* Coding Std. CCITT, unrestr. dig. Inform. */ + *p++ = 0x90; /* Circuit-Mode 64kbps */ + break; + } + + if (send_keypad) { + *p++ = IE_KEYPAD; + *p++ = strlen(teln); + while (*teln) + *p++ = (*teln++) & 0x7F; + } + + /* + * What about info2? Mapping to High-Layer-Compatibility? + */ + if ((*teln) && (!send_keypad)) { + /* parse number for special things */ + if (!isdigit(*teln)) { + switch (0x5f & *teln) { + case 'C': + channel = 0x08; + case 'P': + channel |= 0x80; + teln++; + if (*teln == '1') + channel |= 0x01; + else + channel |= 0x02; + break; + case 'R': + screen = 0xA0; + break; + case 'D': + screen = 0x80; + break; + + default: + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "Wrong MSN Code"); + break; + } + teln++; + } + } + if (channel) { + *p++ = IE_CHANNEL_ID; + *p++ = 1; + *p++ = channel; + } + msn = pc->para.setup.eazmsn; + sub = NULL; + sp = msn; + while (*sp) { + if ('.' == *sp) { + sub = sp; + *sp = 0; + } else + sp++; + } + if (*msn) { + *p++ = IE_CALLING_PN; + *p++ = strlen(msn) + (screen ? 2 : 1); + /* Classify as AnyPref. */ + if (screen) { + *p++ = 0x01; /* Ext = '0'B, Type = '000'B, Plan = '0001'B. */ + *p++ = screen; + } else + *p++ = 0x81; /* Ext = '1'B, Type = '000'B, Plan = '0001'B. */ + while (*msn) + *p++ = *msn++ & 0x7f; + } + if (sub) { + *sub++ = '.'; + *p++ = IE_CALLING_SUB; + *p++ = strlen(sub) + 2; + *p++ = 0x80; /* NSAP coded */ + *p++ = 0x50; /* local IDI format */ + while (*sub) + *p++ = *sub++ & 0x7f; + } + sub = NULL; + sp = teln; + while (*sp) { + if ('.' == *sp) { + sub = sp; + *sp = 0; + } else + sp++; + } + + if (!send_keypad) { + *p++ = IE_CALLED_PN; + *p++ = strlen(teln) + 1; + /* Classify as AnyPref. */ + *p++ = 0x81; /* Ext = '1'B, Type = '000'B, Plan = '0001'B. */ + while (*teln) + *p++ = *teln++ & 0x7f; + + if (sub) { + *sub++ = '.'; + *p++ = IE_CALLED_SUB; + *p++ = strlen(sub) + 2; + *p++ = 0x80; /* NSAP coded */ + *p++ = 0x50; /* local IDI format */ + while (*sub) + *p++ = *sub++ & 0x7f; + } + } +#if EXT_BEARER_CAPS + if ((pc->para.setup.si2 >= 160) && (pc->para.setup.si2 <= 175)) { // sync. Bitratenadaption, V.110/X.30 + + *p++ = IE_LLC; + *p++ = 0x04; + *p++ = 0x88; + *p++ = 0x90; + *p++ = 0x21; + *p++ = EncodeSyncParams(pc->para.setup.si2 - 160, 0x80); + } else if ((pc->para.setup.si2 >= 176) && (pc->para.setup.si2 <= 191)) { // sync. Bitratenadaption, V.120 + + *p++ = IE_LLC; + *p++ = 0x05; + *p++ = 0x88; + *p++ = 0x90; + *p++ = 0x28; + *p++ = EncodeSyncParams(pc->para.setup.si2 - 176, 0); + *p++ = 0x82; + } else if (pc->para.setup.si2 >= 192) { // async. Bitratenadaption, V.110/X.30 + + *p++ = IE_LLC; + *p++ = 0x06; + *p++ = 0x88; + *p++ = 0x90; + *p++ = 0x21; + p = EncodeASyncParams(p, pc->para.setup.si2 - 192); +#ifndef CONFIG_HISAX_NO_LLC + } else { + switch (pc->para.setup.si1) { + case 1: /* Telephony */ + *p++ = IE_LLC; + *p++ = 0x3; /* Length */ + *p++ = 0x90; /* Coding Std. CCITT, 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++ = IE_LLC; + *p++ = 0x2; /* Length */ + *p++ = 0x88; /* Coding Std. CCITT, unrestr. dig. Inform. */ + *p++ = 0x90; /* Circuit-Mode 64kbps */ + break; + } +#endif + } +#endif + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + memcpy(skb_put(skb, l), tmp, l); + L3DelTimer(&pc->timer); + L3AddTimer(&pc->timer, T303, CC_T303); + newl3state(pc, 1); + l3_msg(pc->st, DL_DATA | REQUEST, skb); +} + +static void +l3dss1_call_proc(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int id, ret; + + if ((id = l3dss1_get_channel_id(pc, skb)) >= 0) { + if ((0 == id) || ((3 == id) && (0x10 == pc->para.moderate))) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "setup answer with wrong chid %x", id); + pc->para.cause = 100; + l3dss1_status_send(pc, pr, NULL); + return; + } + pc->para.bchannel = id; + } else if (1 == pc->state) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "setup answer wrong chid (ret %d)", id); + if (id == -1) + pc->para.cause = 96; + else + pc->para.cause = 100; + l3dss1_status_send(pc, pr, NULL); + return; + } + /* Now we are on none mandatory IEs */ + ret = check_infoelements(pc, skb, ie_CALL_PROCEEDING); + if (ERR_IE_COMPREHENSION == ret) { + l3dss1_std_ie_err(pc, ret); + return; + } + L3DelTimer(&pc->timer); + newl3state(pc, 3); + L3AddTimer(&pc->timer, T310, CC_T310); + if (ret) /* STATUS for none mandatory IE errors after actions are taken */ + l3dss1_std_ie_err(pc, ret); + pc->st->l3.l3l4(pc->st, CC_PROCEEDING | INDICATION, pc); +} + +static void +l3dss1_setup_ack(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int id, ret; + + if ((id = l3dss1_get_channel_id(pc, skb)) >= 0) { + if ((0 == id) || ((3 == id) && (0x10 == pc->para.moderate))) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "setup answer with wrong chid %x", id); + pc->para.cause = 100; + l3dss1_status_send(pc, pr, NULL); + return; + } + pc->para.bchannel = id; + } else { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "setup answer wrong chid (ret %d)", id); + if (id == -1) + pc->para.cause = 96; + else + pc->para.cause = 100; + l3dss1_status_send(pc, pr, NULL); + return; + } + /* Now we are on none mandatory IEs */ + ret = check_infoelements(pc, skb, ie_SETUP_ACKNOWLEDGE); + if (ERR_IE_COMPREHENSION == ret) { + l3dss1_std_ie_err(pc, ret); + return; + } + L3DelTimer(&pc->timer); + newl3state(pc, 2); + L3AddTimer(&pc->timer, T304, CC_T304); + if (ret) /* STATUS for none mandatory IE errors after actions are taken */ + l3dss1_std_ie_err(pc, ret); + pc->st->l3.l3l4(pc->st, CC_MORE_INFO | INDICATION, pc); +} + +static void +l3dss1_disconnect(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + u_char *p; + int ret; + u_char cause = 0; + + StopAllL3Timer(pc); + if ((ret = l3dss1_get_cause(pc, skb))) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "DISC get_cause ret(%d)", ret); + if (ret < 0) + cause = 96; + else if (ret > 0) + cause = 100; + } + if ((p = findie(skb->data, skb->len, IE_FACILITY, 0))) + l3dss1_parse_facility(pc->st, pc, pc->callref, p); + ret = check_infoelements(pc, skb, ie_DISCONNECT); + if (ERR_IE_COMPREHENSION == ret) + cause = 96; + else if ((!cause) && (ERR_IE_UNRECOGNIZED == ret)) + cause = 99; + ret = pc->state; + newl3state(pc, 12); + if (cause) + newl3state(pc, 19); + if (11 != ret) + pc->st->l3.l3l4(pc->st, CC_DISCONNECT | INDICATION, pc); + else if (!cause) + l3dss1_release_req(pc, pr, NULL); + if (cause) { + l3dss1_message_cause(pc, MT_RELEASE, cause); + L3AddTimer(&pc->timer, T308, CC_T308_1); + } +} + +static void +l3dss1_connect(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int ret; + + ret = check_infoelements(pc, skb, ie_CONNECT); + if (ERR_IE_COMPREHENSION == ret) { + l3dss1_std_ie_err(pc, ret); + return; + } + L3DelTimer(&pc->timer); /* T310 */ + newl3state(pc, 10); + pc->para.chargeinfo = 0; + /* here should inserted COLP handling KKe */ + if (ret) + l3dss1_std_ie_err(pc, ret); + pc->st->l3.l3l4(pc->st, CC_SETUP | CONFIRM, pc); +} + +static void +l3dss1_alerting(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int ret; + + ret = check_infoelements(pc, skb, ie_ALERTING); + if (ERR_IE_COMPREHENSION == ret) { + l3dss1_std_ie_err(pc, ret); + return; + } + L3DelTimer(&pc->timer); /* T304 */ + newl3state(pc, 4); + if (ret) + l3dss1_std_ie_err(pc, ret); + pc->st->l3.l3l4(pc->st, CC_ALERTING | INDICATION, pc); +} + +static void +l3dss1_setup(struct l3_process *pc, u_char pr, void *arg) +{ + u_char *p; + int bcfound = 0; + char tmp[80]; + struct sk_buff *skb = arg; + int id; + int err = 0; + + /* + * Bearer Capabilities + */ + p = skb->data; + /* only the first occurence 'll be detected ! */ + if ((p = findie(p, skb->len, 0x04, 0))) { + if ((p[1] < 2) || (p[1] > 11)) + err = 1; + else { + pc->para.setup.si2 = 0; + switch (p[2] & 0x7f) { + case 0x00: /* Speech */ + case 0x10: /* 3.1 Khz audio */ + pc->para.setup.si1 = 1; + break; + case 0x08: /* Unrestricted digital information */ + pc->para.setup.si1 = 7; +/* JIM, 05.11.97 I wanna set service indicator 2 */ +#if EXT_BEARER_CAPS + pc->para.setup.si2 = DecodeSI2(skb); +#endif + break; + case 0x09: /* Restricted digital information */ + pc->para.setup.si1 = 2; + break; + case 0x11: + /* Unrestr. digital information with + * tones/announcements ( or 7 kHz audio + */ + pc->para.setup.si1 = 3; + break; + case 0x18: /* Video */ + pc->para.setup.si1 = 4; + break; + default: + err = 2; + break; + } + switch (p[3] & 0x7f) { + case 0x40: /* packed mode */ + pc->para.setup.si1 = 8; + break; + case 0x10: /* 64 kbit */ + case 0x11: /* 2*64 kbit */ + case 0x13: /* 384 kbit */ + case 0x15: /* 1536 kbit */ + case 0x17: /* 1920 kbit */ + pc->para.moderate = p[3] & 0x7f; + break; + default: + err = 3; + break; + } + } + if (pc->debug & L3_DEB_SI) + l3_debug(pc->st, "SI=%d, AI=%d", + pc->para.setup.si1, pc->para.setup.si2); + if (err) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "setup with wrong bearer(l=%d:%x,%x)", + p[1], p[2], p[3]); + pc->para.cause = 100; + l3dss1_msg_without_setup(pc, pr, NULL); + return; + } + } else { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "setup without bearer capabilities"); + /* ETS 300-104 1.3.3 */ + pc->para.cause = 96; + l3dss1_msg_without_setup(pc, pr, NULL); + return; + } + /* + * Channel Identification + */ + if ((id = l3dss1_get_channel_id(pc, skb)) >= 0) { + if ((pc->para.bchannel = id)) { + if ((3 == id) && (0x10 == pc->para.moderate)) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "setup with wrong chid %x", + id); + pc->para.cause = 100; + l3dss1_msg_without_setup(pc, pr, NULL); + return; + } + bcfound++; + } else + { if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "setup without bchannel, call waiting"); + bcfound++; + } + } else { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "setup with wrong chid ret %d", id); + if (id == -1) + pc->para.cause = 96; + else + pc->para.cause = 100; + l3dss1_msg_without_setup(pc, pr, NULL); + return; + } + /* Now we are on none mandatory IEs */ + err = check_infoelements(pc, skb, ie_SETUP); + if (ERR_IE_COMPREHENSION == err) { + pc->para.cause = 96; + l3dss1_msg_without_setup(pc, pr, NULL); + return; + } + p = skb->data; + if ((p = findie(p, skb->len, 0x70, 0))) + iecpy(pc->para.setup.eazmsn, p, 1); + else + pc->para.setup.eazmsn[0] = 0; + + p = skb->data; + if ((p = findie(p, skb->len, 0x71, 0))) { + /* Called party subaddress */ + if ((p[1] >= 2) && (p[2] == 0x80) && (p[3] == 0x50)) { + tmp[0] = '.'; + iecpy(&tmp[1], p, 2); + strcat(pc->para.setup.eazmsn, tmp); + } else if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "wrong called subaddress"); + } + p = skb->data; + if ((p = findie(p, skb->len, 0x6c, 0))) { + pc->para.setup.plan = p[2]; + if (p[2] & 0x80) { + iecpy(pc->para.setup.phone, p, 1); + pc->para.setup.screen = 0; + } else { + iecpy(pc->para.setup.phone, p, 2); + pc->para.setup.screen = p[3]; + } + } else { + pc->para.setup.phone[0] = 0; + pc->para.setup.plan = 0; + pc->para.setup.screen = 0; + } + p = skb->data; + if ((p = findie(p, skb->len, 0x6d, 0))) { + /* Calling party subaddress */ + if ((p[1] >= 2) && (p[2] == 0x80) && (p[3] == 0x50)) { + tmp[0] = '.'; + iecpy(&tmp[1], p, 2); + strcat(pc->para.setup.phone, tmp); + } else if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "wrong calling subaddress"); + } + newl3state(pc, 6); + if (err) /* STATUS for none mandatory IE errors after actions are taken */ + l3dss1_std_ie_err(pc, err); + pc->st->l3.l3l4(pc->st, CC_SETUP | INDICATION, pc); +} + +static void +l3dss1_reset(struct l3_process *pc, u_char pr, void *arg) +{ + dss1_release_l3_process(pc); +} + +static void +l3dss1_disconnect_req(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb; + u_char tmp[16+40]; + u_char *p = tmp; + int l; + u_char cause = 16; + + if (pc->para.cause != NO_CAUSE) + cause = pc->para.cause; + + StopAllL3Timer(pc); + + MsgHead(p, pc->callref, MT_DISCONNECT); + + *p++ = IE_CAUSE; + *p++ = 0x2; + *p++ = 0x80; + *p++ = cause | 0x80; + + if (pc->prot.dss1.uus1_data[0]) + { *p++ = IE_USER_USER; /* UUS info element */ + *p++ = strlen(pc->prot.dss1.uus1_data) + 1; + *p++ = 0x04; /* IA5 chars */ + strcpy(p,pc->prot.dss1.uus1_data); + p += strlen(pc->prot.dss1.uus1_data); + pc->prot.dss1.uus1_data[0] = '\0'; + } + + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + memcpy(skb_put(skb, l), tmp, l); + newl3state(pc, 11); + l3_msg(pc->st, DL_DATA | REQUEST, skb); + L3AddTimer(&pc->timer, T305, CC_T305); +} + +static void +l3dss1_setup_rsp(struct l3_process *pc, u_char pr, + void *arg) +{ + if (!pc->para.bchannel) + { if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "D-chan connect for waiting call"); + l3dss1_disconnect_req(pc, pr, arg); + return; + } + newl3state(pc, 8); + l3dss1_message(pc, MT_CONNECT); + L3DelTimer(&pc->timer); + L3AddTimer(&pc->timer, T313, CC_T313); +} + +static void +l3dss1_connect_ack(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int ret; + + ret = check_infoelements(pc, skb, ie_CONNECT_ACKNOWLEDGE); + if (ERR_IE_COMPREHENSION == ret) { + l3dss1_std_ie_err(pc, ret); + return; + } + newl3state(pc, 10); + L3DelTimer(&pc->timer); + if (ret) + l3dss1_std_ie_err(pc, ret); + pc->st->l3.l3l4(pc->st, CC_SETUP_COMPL | INDICATION, pc); +} + +static void +l3dss1_reject_req(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb; + u_char tmp[16]; + u_char *p = tmp; + int l; + u_char cause = 21; + + if (pc->para.cause != NO_CAUSE) + cause = pc->para.cause; + + MsgHead(p, pc->callref, MT_RELEASE_COMPLETE); + + *p++ = IE_CAUSE; + *p++ = 0x2; + *p++ = 0x80; + *p++ = cause | 0x80; + + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + memcpy(skb_put(skb, l), tmp, l); + l3_msg(pc->st, DL_DATA | REQUEST, skb); + pc->st->l3.l3l4(pc->st, CC_RELEASE | INDICATION, pc); + newl3state(pc, 0); + dss1_release_l3_process(pc); +} + +static void +l3dss1_release(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + u_char *p; + int ret, cause=0; + + StopAllL3Timer(pc); + if ((ret = l3dss1_get_cause(pc, skb))>0) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "REL get_cause ret(%d)", ret); + } else if (ret<0) + pc->para.cause = NO_CAUSE; + if ((p = findie(skb->data, skb->len, IE_FACILITY, 0))) { + l3dss1_parse_facility(pc->st, pc, pc->callref, p); + } + if ((ret<0) && (pc->state != 11)) + cause = 96; + else if (ret>0) + cause = 100; + ret = check_infoelements(pc, skb, ie_RELEASE); + if (ERR_IE_COMPREHENSION == ret) + cause = 96; + else if ((ERR_IE_UNRECOGNIZED == ret) && (!cause)) + cause = 99; + if (cause) + l3dss1_message_cause(pc, MT_RELEASE_COMPLETE, cause); + else + l3dss1_message(pc, MT_RELEASE_COMPLETE); + pc->st->l3.l3l4(pc->st, CC_RELEASE | INDICATION, pc); + newl3state(pc, 0); + dss1_release_l3_process(pc); +} + +static void +l3dss1_alert_req(struct l3_process *pc, u_char pr, + void *arg) +{ + newl3state(pc, 7); + if (!pc->prot.dss1.uus1_data[0]) + l3dss1_message(pc, MT_ALERTING); + else + l3dss1_msg_with_uus(pc, MT_ALERTING); +} + +static void +l3dss1_proceed_req(struct l3_process *pc, u_char pr, + void *arg) +{ + newl3state(pc, 9); + l3dss1_message(pc, MT_CALL_PROCEEDING); + pc->st->l3.l3l4(pc->st, CC_PROCEED_SEND | INDICATION, pc); +} + +static void +l3dss1_setup_ack_req(struct l3_process *pc, u_char pr, + void *arg) +{ + newl3state(pc, 25); + L3DelTimer(&pc->timer); + L3AddTimer(&pc->timer, T302, CC_T302); + l3dss1_message(pc, MT_SETUP_ACKNOWLEDGE); +} + +/********************************************/ +/* deliver a incoming display message to HL */ +/********************************************/ +static void +l3dss1_deliver_display(struct l3_process *pc, int pr, u_char *infp) +{ u_char len; + isdn_ctrl ic; + struct IsdnCardState *cs; + char *p; + + if (*infp++ != IE_DISPLAY) return; + if ((len = *infp++) > 80) return; /* total length <= 82 */ + if (!pc->chan) return; + + p = ic.parm.display; + while (len--) + *p++ = *infp++; + *p = '\0'; + ic.command = ISDN_STAT_DISPLAY; + cs = pc->st->l1.hardware; + ic.driver = cs->myid; + ic.arg = pc->chan->chan; + cs->iif.statcallb(&ic); +} /* l3dss1_deliver_display */ + + +static void +l3dss1_progress(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int err = 0; + u_char *p; + + if ((p = findie(skb->data, skb->len, IE_PROGRESS, 0))) { + if (p[1] != 2) { + err = 1; + pc->para.cause = 100; + } else if (!(p[2] & 0x70)) { + switch (p[2]) { + case 0x80: + case 0x81: + case 0x82: + case 0x84: + case 0x85: + case 0x87: + case 0x8a: + switch (p[3]) { + case 0x81: + case 0x82: + case 0x83: + case 0x84: + case 0x88: + break; + default: + err = 2; + pc->para.cause = 100; + break; + } + break; + default: + err = 3; + pc->para.cause = 100; + break; + } + } + } else { + pc->para.cause = 96; + err = 4; + } + if (err) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "progress error %d", err); + l3dss1_status_send(pc, pr, NULL); + return; + } + /* Now we are on none mandatory IEs */ + err = check_infoelements(pc, skb, ie_PROGRESS); + if (err) + l3dss1_std_ie_err(pc, err); + if (ERR_IE_COMPREHENSION != err) + pc->st->l3.l3l4(pc->st, CC_PROGRESS | INDICATION, pc); +} + +static void +l3dss1_notify(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int err = 0; + u_char *p; + + if ((p = findie(skb->data, skb->len, IE_NOTIFY, 0))) { + if (p[1] != 1) { + err = 1; + pc->para.cause = 100; + } else { + switch (p[2]) { + case 0x80: + case 0x81: + case 0x82: + break; + default: + pc->para.cause = 100; + err = 2; + break; + } + } + } else { + pc->para.cause = 96; + err = 3; + } + if (err) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "notify error %d", err); + l3dss1_status_send(pc, pr, NULL); + return; + } + /* Now we are on none mandatory IEs */ + err = check_infoelements(pc, skb, ie_NOTIFY); + if (err) + l3dss1_std_ie_err(pc, err); + if (ERR_IE_COMPREHENSION != err) + pc->st->l3.l3l4(pc->st, CC_NOTIFY | INDICATION, pc); +} + +static void +l3dss1_status_enq(struct l3_process *pc, u_char pr, void *arg) +{ + int ret; + struct sk_buff *skb = arg; + + ret = check_infoelements(pc, skb, ie_STATUS_ENQUIRY); + l3dss1_std_ie_err(pc, ret); + pc->para.cause = 30; /* response to STATUS_ENQUIRY */ + l3dss1_status_send(pc, pr, NULL); +} + +static void +l3dss1_information(struct l3_process *pc, u_char pr, void *arg) +{ + int ret; + struct sk_buff *skb = arg; + u_char *p; + char tmp[32]; + + ret = check_infoelements(pc, skb, ie_INFORMATION); + if (ret) + l3dss1_std_ie_err(pc, ret); + if (pc->state == 25) { /* overlap receiving */ + L3DelTimer(&pc->timer); + p = skb->data; + if ((p = findie(p, skb->len, 0x70, 0))) { + iecpy(tmp, p, 1); + strcat(pc->para.setup.eazmsn, tmp); + pc->st->l3.l3l4(pc->st, CC_MORE_INFO | INDICATION, pc); + } + L3AddTimer(&pc->timer, T302, CC_T302); + } +} + +/******************************/ +/* handle deflection requests */ +/******************************/ +static void l3dss1_redir_req(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb; + u_char tmp[128]; + u_char *p = tmp; + u_char *subp; + u_char len_phone = 0; + u_char len_sub = 0; + int l; + + + strcpy(pc->prot.dss1.uus1_data,pc->chan->setup.eazmsn); /* copy uus element if available */ + if (!pc->chan->setup.phone[0]) + { pc->para.cause = -1; + l3dss1_disconnect_req(pc,pr,arg); /* disconnect immediately */ + return; + } /* only uus */ + + if (pc->prot.dss1.invoke_id) + free_invoke_id(pc->st,pc->prot.dss1.invoke_id); + + if (!(pc->prot.dss1.invoke_id = new_invoke_id(pc->st))) + return; + + MsgHead(p, pc->callref, MT_FACILITY); + + for (subp = pc->chan->setup.phone; (*subp) && (*subp != '.'); subp++) len_phone++; /* len of phone number */ + if (*subp++ == '.') len_sub = strlen(subp) + 2; /* length including info subaddress element */ + + *p++ = 0x1c; /* Facility info element */ + *p++ = len_phone + len_sub + 2 + 2 + 8 + 3 + 3; /* length of element */ + *p++ = 0x91; /* remote operations protocol */ + *p++ = 0xa1; /* invoke component */ + + *p++ = len_phone + len_sub + 2 + 2 + 8 + 3; /* length of data */ + *p++ = 0x02; /* invoke id tag, integer */ + *p++ = 0x01; /* length */ + *p++ = pc->prot.dss1.invoke_id; /* invoke id */ + *p++ = 0x02; /* operation value tag, integer */ + *p++ = 0x01; /* length */ + *p++ = 0x0D; /* Call Deflect */ + + *p++ = 0x30; /* sequence phone number */ + *p++ = len_phone + 2 + 2 + 3 + len_sub; /* length */ + + *p++ = 0x30; /* Deflected to UserNumber */ + *p++ = len_phone+2+len_sub; /* length */ + *p++ = 0x80; /* NumberDigits */ + *p++ = len_phone; /* length */ + for (l = 0; l < len_phone; l++) + *p++ = pc->chan->setup.phone[l]; + + if (len_sub) + { *p++ = 0x04; /* called party subaddress */ + *p++ = len_sub - 2; + while (*subp) *p++ = *subp++; + } + + *p++ = 0x01; /* screening identifier */ + *p++ = 0x01; + *p++ = pc->chan->setup.screen; + + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) return; + memcpy(skb_put(skb, l), tmp, l); + + l3_msg(pc->st, DL_DATA | REQUEST, skb); +} /* l3dss1_redir_req */ + +/********************************************/ +/* handle deflection request in early state */ +/********************************************/ +static void l3dss1_redir_req_early(struct l3_process *pc, u_char pr, void *arg) +{ + l3dss1_proceed_req(pc,pr,arg); + l3dss1_redir_req(pc,pr,arg); +} /* l3dss1_redir_req_early */ + +/***********************************************/ +/* handle special commands for this protocol. */ +/* Examples are call independant services like */ +/* remote operations with dummy callref. */ +/***********************************************/ +static int l3dss1_cmd_global(struct PStack *st, isdn_ctrl *ic) +{ u_char id; + u_char temp[265]; + u_char *p = temp; + int i, l, proc_len; + struct sk_buff *skb; + struct l3_process *pc = NULL; + + switch (ic->arg) + { case DSS1_CMD_INVOKE: + if (ic->parm.dss1_io.datalen < 0) return(-2); /* invalid parameter */ + + for (proc_len = 1, i = ic->parm.dss1_io.proc >> 8; i; i++) + i = i >> 8; /* add one byte */ + l = ic->parm.dss1_io.datalen + proc_len + 8; /* length excluding ie header */ + if (l > 255) + return(-2); /* too long */ + + if (!(id = new_invoke_id(st))) + return(0); /* first get a invoke id -> return if no available */ + + i = -1; + MsgHead(p, i, MT_FACILITY); /* build message head */ + *p++ = 0x1C; /* Facility IE */ + *p++ = l; /* length of ie */ + *p++ = 0x91; /* remote operations */ + *p++ = 0xA1; /* invoke */ + *p++ = l - 3; /* length of invoke */ + *p++ = 0x02; /* invoke id tag */ + *p++ = 0x01; /* length is 1 */ + *p++ = id; /* invoke id */ + *p++ = 0x02; /* operation */ + *p++ = proc_len; /* length of operation */ + + for (i = proc_len; i; i--) + *p++ = (ic->parm.dss1_io.proc >> (i-1)) & 0xFF; + memcpy(p, ic->parm.dss1_io.data, ic->parm.dss1_io.datalen); /* copy data */ + l = (p - temp) + ic->parm.dss1_io.datalen; /* total length */ + + if (ic->parm.dss1_io.timeout > 0) + if (!(pc = dss1_new_l3_process(st, -1))) + { free_invoke_id(st, id); + return(-2); + } + pc->prot.dss1.ll_id = ic->parm.dss1_io.ll_id; /* remember id */ + pc->prot.dss1.proc = ic->parm.dss1_io.proc; /* and procedure */ + + if (!(skb = l3_alloc_skb(l))) + { free_invoke_id(st, id); + if (pc) dss1_release_l3_process(pc); + return(-2); + } + memcpy(skb_put(skb, l), temp, l); + + if (pc) + { pc->prot.dss1.invoke_id = id; /* remember id */ + L3AddTimer(&pc->timer, ic->parm.dss1_io.timeout, CC_TDSS1_IO | REQUEST); + } + + l3_msg(st, DL_DATA | REQUEST, skb); + ic->parm.dss1_io.hl_id = id; /* return id */ + return(0); + + case DSS1_CMD_INVOKE_ABORT: + if ((pc = l3dss1_search_dummy_proc(st, ic->parm.dss1_io.hl_id))) + { L3DelTimer(&pc->timer); /* remove timer */ + dss1_release_l3_process(pc); + return(0); + } + else + { l3_debug(st, "l3dss1_cmd_global abort unknown id"); + return(-2); + } + break; + + default: + l3_debug(st, "l3dss1_cmd_global unknown cmd 0x%lx", ic->arg); + return(-1); + } /* switch ic-> arg */ + return(-1); +} /* l3dss1_cmd_global */ + +static void +l3dss1_io_timer(struct l3_process *pc) +{ isdn_ctrl ic; + struct IsdnCardState *cs = pc->st->l1.hardware; + + L3DelTimer(&pc->timer); /* remove timer */ + + ic.driver = cs->myid; + ic.command = ISDN_STAT_PROT; + ic.arg = DSS1_STAT_INVOKE_ERR; + ic.parm.dss1_io.hl_id = pc->prot.dss1.invoke_id; + ic.parm.dss1_io.ll_id = pc->prot.dss1.ll_id; + ic.parm.dss1_io.proc = pc->prot.dss1.proc; + ic.parm.dss1_io.timeout= -1; + ic.parm.dss1_io.datalen = 0; + ic.parm.dss1_io.data = NULL; + free_invoke_id(pc->st, pc->prot.dss1.invoke_id); + pc->prot.dss1.invoke_id = 0; /* reset id */ + + cs->iif.statcallb(&ic); + + dss1_release_l3_process(pc); +} /* l3dss1_io_timer */ + +static void +l3dss1_release_ind(struct l3_process *pc, u_char pr, void *arg) +{ + u_char *p; + struct sk_buff *skb = arg; + int callState = 0; + p = skb->data; + + if ((p = findie(p, skb->len, IE_CALL_STATE, 0))) { + p++; + if (1 == *p++) + callState = *p; + } + if (callState == 0) { + /* ETS 300-104 7.6.1, 8.6.1, 10.6.1... and 16.1 + * set down layer 3 without sending any message + */ + pc->st->l3.l3l4(pc->st, CC_RELEASE | INDICATION, pc); + newl3state(pc, 0); + dss1_release_l3_process(pc); + } else { + pc->st->l3.l3l4(pc->st, CC_IGNORE | INDICATION, pc); + } +} + +static void +l3dss1_dummy(struct l3_process *pc, u_char pr, void *arg) +{ +} + +static void +l3dss1_t302(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + pc->para.loc = 0; + pc->para.cause = 28; /* invalid number */ + l3dss1_disconnect_req(pc, pr, NULL); + pc->st->l3.l3l4(pc->st, CC_SETUP_ERR, pc); +} + +static void +l3dss1_t303(struct l3_process *pc, u_char pr, void *arg) +{ + if (pc->N303 > 0) { + pc->N303--; + L3DelTimer(&pc->timer); + l3dss1_setup_req(pc, pr, arg); + } else { + L3DelTimer(&pc->timer); + l3dss1_message_cause(pc, MT_RELEASE_COMPLETE, 102); + pc->st->l3.l3l4(pc->st, CC_NOSETUP_RSP, pc); + dss1_release_l3_process(pc); + } +} + +static void +l3dss1_t304(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + pc->para.loc = 0; + pc->para.cause = 102; + l3dss1_disconnect_req(pc, pr, NULL); + pc->st->l3.l3l4(pc->st, CC_SETUP_ERR, pc); + +} + +static void +l3dss1_t305(struct l3_process *pc, u_char pr, void *arg) +{ + u_char tmp[16]; + u_char *p = tmp; + int l; + struct sk_buff *skb; + u_char cause = 16; + + L3DelTimer(&pc->timer); + if (pc->para.cause != NO_CAUSE) + cause = pc->para.cause; + + MsgHead(p, pc->callref, MT_RELEASE); + + *p++ = IE_CAUSE; + *p++ = 0x2; + *p++ = 0x80; + *p++ = cause | 0x80; + + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + memcpy(skb_put(skb, l), tmp, l); + newl3state(pc, 19); + l3_msg(pc->st, DL_DATA | REQUEST, skb); + L3AddTimer(&pc->timer, T308, CC_T308_1); +} + +static void +l3dss1_t310(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + pc->para.loc = 0; + pc->para.cause = 102; + l3dss1_disconnect_req(pc, pr, NULL); + pc->st->l3.l3l4(pc->st, CC_SETUP_ERR, pc); +} + +static void +l3dss1_t313(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + pc->para.loc = 0; + pc->para.cause = 102; + l3dss1_disconnect_req(pc, pr, NULL); + pc->st->l3.l3l4(pc->st, CC_CONNECT_ERR, pc); +} + +static void +l3dss1_t308_1(struct l3_process *pc, u_char pr, void *arg) +{ + newl3state(pc, 19); + L3DelTimer(&pc->timer); + l3dss1_message(pc, MT_RELEASE); + L3AddTimer(&pc->timer, T308, CC_T308_2); +} + +static void +l3dss1_t308_2(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + pc->st->l3.l3l4(pc->st, CC_RELEASE_ERR, pc); + dss1_release_l3_process(pc); +} + +static void +l3dss1_t318(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + pc->para.cause = 102; /* Timer expiry */ + pc->para.loc = 0; /* local */ + pc->st->l3.l3l4(pc->st, CC_RESUME_ERR, pc); + newl3state(pc, 19); + l3dss1_message(pc, MT_RELEASE); + L3AddTimer(&pc->timer, T308, CC_T308_1); +} + +static void +l3dss1_t319(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + pc->para.cause = 102; /* Timer expiry */ + pc->para.loc = 0; /* local */ + pc->st->l3.l3l4(pc->st, CC_SUSPEND_ERR, pc); + newl3state(pc, 10); +} + +static void +l3dss1_restart(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + pc->st->l3.l3l4(pc->st, CC_RELEASE | INDICATION, pc); + dss1_release_l3_process(pc); +} + +static void +l3dss1_status(struct l3_process *pc, u_char pr, void *arg) +{ + u_char *p; + struct sk_buff *skb = arg; + int ret; + u_char cause = 0, callState = 0; + + if ((ret = l3dss1_get_cause(pc, skb))) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "STATUS get_cause ret(%d)",ret); + if (ret < 0) + cause = 96; + else if (ret > 0) + cause = 100; + } + if ((p = findie(skb->data, skb->len, IE_CALL_STATE, 0))) { + p++; + if (1 == *p++) { + callState = *p; + if (!ie_in_set(pc, *p, l3_valid_states)) + cause = 100; + } else + cause = 100; + } else + cause = 96; + if (!cause) { /* no error before */ + ret = check_infoelements(pc, skb, ie_STATUS); + if (ERR_IE_COMPREHENSION == ret) + cause = 96; + else if (ERR_IE_UNRECOGNIZED == ret) + cause = 99; + } + if (cause) { + u_char tmp; + + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "STATUS error(%d/%d)",ret,cause); + tmp = pc->para.cause; + pc->para.cause = cause; + l3dss1_status_send(pc, 0, NULL); + if (cause == 99) + pc->para.cause = tmp; + else + return; + } + cause = pc->para.cause; + if (((cause & 0x7f) == 111) && (callState == 0)) { + /* ETS 300-104 7.6.1, 8.6.1, 10.6.1... + * if received MT_STATUS with cause == 111 and call + * state == 0, then we must set down layer 3 + */ + pc->st->l3.l3l4(pc->st, CC_RELEASE | INDICATION, pc); + newl3state(pc, 0); + dss1_release_l3_process(pc); + } +} + +static void +l3dss1_facility(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int ret; + + ret = check_infoelements(pc, skb, ie_FACILITY); + l3dss1_std_ie_err(pc, ret); + { + u_char *p; + if ((p = findie(skb->data, skb->len, IE_FACILITY, 0))) + l3dss1_parse_facility(pc->st, pc, pc->callref, p); + } +} + +static void +l3dss1_suspend_req(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb; + u_char tmp[32]; + u_char *p = tmp; + u_char i, l; + u_char *msg = pc->chan->setup.phone; + + MsgHead(p, pc->callref, MT_SUSPEND); + l = *msg++; + if (l && (l <= 10)) { /* Max length 10 octets */ + *p++ = IE_CALL_ID; + *p++ = l; + for (i = 0; i < l; i++) + *p++ = *msg++; + } else if (l) { + l3_debug(pc->st, "SUS wrong CALL_ID len %d", l); + return; + } + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + memcpy(skb_put(skb, l), tmp, l); + l3_msg(pc->st, DL_DATA | REQUEST, skb); + newl3state(pc, 15); + L3AddTimer(&pc->timer, T319, CC_T319); +} + +static void +l3dss1_suspend_ack(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int ret; + + L3DelTimer(&pc->timer); + newl3state(pc, 0); + pc->para.cause = NO_CAUSE; + pc->st->l3.l3l4(pc->st, CC_SUSPEND | CONFIRM, pc); + /* We don't handle suspend_ack for IE errors now */ + if ((ret = check_infoelements(pc, skb, ie_SUSPEND_ACKNOWLEDGE))) + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "SUSPACK check ie(%d)",ret); + dss1_release_l3_process(pc); +} + +static void +l3dss1_suspend_rej(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int ret; + + if ((ret = l3dss1_get_cause(pc, skb))) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "SUSP_REJ get_cause ret(%d)",ret); + if (ret < 0) + pc->para.cause = 96; + else + pc->para.cause = 100; + l3dss1_status_send(pc, pr, NULL); + return; + } + ret = check_infoelements(pc, skb, ie_SUSPEND_REJECT); + if (ERR_IE_COMPREHENSION == ret) { + l3dss1_std_ie_err(pc, ret); + return; + } + L3DelTimer(&pc->timer); + pc->st->l3.l3l4(pc->st, CC_SUSPEND_ERR, pc); + newl3state(pc, 10); + if (ret) /* STATUS for none mandatory IE errors after actions are taken */ + l3dss1_std_ie_err(pc, ret); +} + +static void +l3dss1_resume_req(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb; + u_char tmp[32]; + u_char *p = tmp; + u_char i, l; + u_char *msg = pc->para.setup.phone; + + MsgHead(p, pc->callref, MT_RESUME); + + l = *msg++; + if (l && (l <= 10)) { /* Max length 10 octets */ + *p++ = IE_CALL_ID; + *p++ = l; + for (i = 0; i < l; i++) + *p++ = *msg++; + } else if (l) { + l3_debug(pc->st, "RES wrong CALL_ID len %d", l); + return; + } + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + memcpy(skb_put(skb, l), tmp, l); + l3_msg(pc->st, DL_DATA | REQUEST, skb); + newl3state(pc, 17); + L3AddTimer(&pc->timer, T318, CC_T318); +} + +static void +l3dss1_resume_ack(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int id, ret; + + if ((id = l3dss1_get_channel_id(pc, skb)) > 0) { + if ((0 == id) || ((3 == id) && (0x10 == pc->para.moderate))) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "resume ack with wrong chid %x", id); + pc->para.cause = 100; + l3dss1_status_send(pc, pr, NULL); + return; + } + pc->para.bchannel = id; + } else if (1 == pc->state) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "resume ack without chid (ret %d)", id); + pc->para.cause = 96; + l3dss1_status_send(pc, pr, NULL); + return; + } + ret = check_infoelements(pc, skb, ie_RESUME_ACKNOWLEDGE); + if (ERR_IE_COMPREHENSION == ret) { + l3dss1_std_ie_err(pc, ret); + return; + } + L3DelTimer(&pc->timer); + pc->st->l3.l3l4(pc->st, CC_RESUME | CONFIRM, pc); + newl3state(pc, 10); + if (ret) /* STATUS for none mandatory IE errors after actions are taken */ + l3dss1_std_ie_err(pc, ret); +} + +static void +l3dss1_resume_rej(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int ret; + + if ((ret = l3dss1_get_cause(pc, skb))) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "RES_REJ get_cause ret(%d)",ret); + if (ret < 0) + pc->para.cause = 96; + else + pc->para.cause = 100; + l3dss1_status_send(pc, pr, NULL); + return; + } + ret = check_infoelements(pc, skb, ie_RESUME_REJECT); + if (ERR_IE_COMPREHENSION == ret) { + l3dss1_std_ie_err(pc, ret); + return; + } + L3DelTimer(&pc->timer); + pc->st->l3.l3l4(pc->st, CC_RESUME_ERR, pc); + newl3state(pc, 0); + if (ret) /* STATUS for none mandatory IE errors after actions are taken */ + l3dss1_std_ie_err(pc, ret); + dss1_release_l3_process(pc); +} + +static void +l3dss1_global_restart(struct l3_process *pc, u_char pr, void *arg) +{ + u_char tmp[32]; + u_char *p; + u_char ri, ch = 0, chan = 0; + int l; + struct sk_buff *skb = arg; + struct l3_process *up; + + newl3state(pc, 2); + L3DelTimer(&pc->timer); + p = skb->data; + if ((p = findie(p, skb->len, IE_RESTART_IND, 0))) { + ri = p[2]; + l3_debug(pc->st, "Restart %x", ri); + } else { + l3_debug(pc->st, "Restart without restart IE"); + ri = 0x86; + } + p = skb->data; + if ((p = findie(p, skb->len, IE_CHANNEL_ID, 0))) { + chan = p[2] & 3; + ch = p[2]; + if (pc->st->l3.debug) + l3_debug(pc->st, "Restart for channel %d", chan); + } + newl3state(pc, 2); + up = pc->st->l3.proc; + while (up) { + if ((ri & 7) == 7) + up->st->lli.l4l3(up->st, CC_RESTART | REQUEST, up); + else if (up->para.bchannel == chan) + up->st->lli.l4l3(up->st, CC_RESTART | REQUEST, up); + up = up->next; + } + p = tmp; + MsgHead(p, pc->callref, MT_RESTART_ACKNOWLEDGE); + if (chan) { + *p++ = IE_CHANNEL_ID; + *p++ = 1; + *p++ = ch | 0x80; + } + *p++ = 0x79; /* RESTART Ind */ + *p++ = 1; + *p++ = ri; + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + memcpy(skb_put(skb, l), tmp, l); + newl3state(pc, 0); + l3_msg(pc->st, DL_DATA | REQUEST, skb); +} + +static void +l3dss1_dl_reset(struct l3_process *pc, u_char pr, void *arg) +{ + pc->para.cause = 0x29; /* Temporary failure */ + pc->para.loc = 0; + l3dss1_disconnect_req(pc, pr, NULL); + pc->st->l3.l3l4(pc->st, CC_SETUP_ERR, pc); +} + +static void +l3dss1_dl_release(struct l3_process *pc, u_char pr, void *arg) +{ + newl3state(pc, 0); + pc->para.cause = 0x1b; /* Destination out of order */ + pc->para.loc = 0; + pc->st->l3.l3l4(pc->st, CC_RELEASE | INDICATION, pc); + release_l3_process(pc); +} + +static void +l3dss1_dl_reestablish(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + L3AddTimer(&pc->timer, T309, CC_T309); + l3_msg(pc->st, DL_ESTABLISH | REQUEST, NULL); +} + +static void +l3dss1_dl_reest_status(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + + pc->para.cause = 0x1F; /* normal, unspecified */ + l3dss1_status_send(pc, 0, NULL); +} + +/* *INDENT-OFF* */ +static struct stateentry downstatelist[] = +{ + {SBIT(0), + CC_SETUP | REQUEST, l3dss1_setup_req}, + {SBIT(0), + CC_RESUME | REQUEST, l3dss1_resume_req}, + {SBIT(1) | SBIT(2) | SBIT(3) | SBIT(4) | SBIT(6) | SBIT(7) | SBIT(8) | SBIT(9) | SBIT(10) | SBIT(25), + CC_DISCONNECT | REQUEST, l3dss1_disconnect_req}, + {SBIT(12), + CC_RELEASE | REQUEST, l3dss1_release_req}, + {ALL_STATES, + CC_RESTART | REQUEST, l3dss1_restart}, + {SBIT(6) | SBIT(25), + CC_IGNORE | REQUEST, l3dss1_reset}, + {SBIT(6) | SBIT(25), + CC_REJECT | REQUEST, l3dss1_reject_req}, + {SBIT(6) | SBIT(25), + CC_PROCEED_SEND | REQUEST, l3dss1_proceed_req}, + {SBIT(6), + CC_MORE_INFO | REQUEST, l3dss1_setup_ack_req}, + {SBIT(25), + CC_MORE_INFO | REQUEST, l3dss1_dummy}, + {SBIT(6) | SBIT(9) | SBIT(25), + CC_ALERTING | REQUEST, l3dss1_alert_req}, + {SBIT(6) | SBIT(7) | SBIT(9) | SBIT(25), + CC_SETUP | RESPONSE, l3dss1_setup_rsp}, + {SBIT(10), + CC_SUSPEND | REQUEST, l3dss1_suspend_req}, + {SBIT(7) | SBIT(9) | SBIT(25), + CC_REDIR | REQUEST, l3dss1_redir_req}, + {SBIT(6), + CC_REDIR | REQUEST, l3dss1_redir_req_early}, + {SBIT(9) | SBIT(25), + CC_DISCONNECT | REQUEST, l3dss1_disconnect_req}, + {SBIT(25), + CC_T302, l3dss1_t302}, + {SBIT(1), + CC_T303, l3dss1_t303}, + {SBIT(2), + CC_T304, l3dss1_t304}, + {SBIT(3), + CC_T310, l3dss1_t310}, + {SBIT(8), + CC_T313, l3dss1_t313}, + {SBIT(11), + CC_T305, l3dss1_t305}, + {SBIT(15), + CC_T319, l3dss1_t319}, + {SBIT(17), + CC_T318, l3dss1_t318}, + {SBIT(19), + CC_T308_1, l3dss1_t308_1}, + {SBIT(19), + CC_T308_2, l3dss1_t308_2}, + {SBIT(10), + CC_T309, l3dss1_dl_release}, +}; + +#define DOWNSLLEN \ + (sizeof(downstatelist) / sizeof(struct stateentry)) + +static struct stateentry datastatelist[] = +{ + {ALL_STATES, + MT_STATUS_ENQUIRY, l3dss1_status_enq}, + {ALL_STATES, + MT_FACILITY, l3dss1_facility}, + {SBIT(19), + MT_STATUS, l3dss1_release_ind}, + {ALL_STATES, + MT_STATUS, l3dss1_status}, + {SBIT(0), + MT_SETUP, l3dss1_setup}, + {SBIT(6) | SBIT(7) | SBIT(8) | SBIT(9) | SBIT(10) | SBIT(11) | SBIT(12) | + SBIT(15) | SBIT(17) | SBIT(19) | SBIT(25), + MT_SETUP, l3dss1_dummy}, + {SBIT(1) | SBIT(2), + MT_CALL_PROCEEDING, l3dss1_call_proc}, + {SBIT(1), + MT_SETUP_ACKNOWLEDGE, l3dss1_setup_ack}, + {SBIT(2) | SBIT(3), + MT_ALERTING, l3dss1_alerting}, + {SBIT(2) | SBIT(3), + MT_PROGRESS, l3dss1_progress}, + {SBIT(2) | SBIT(3) | SBIT(4) | SBIT(7) | SBIT(8) | SBIT(9) | SBIT(10) | + SBIT(11) | SBIT(12) | SBIT(15) | SBIT(17) | SBIT(19) | SBIT(25), + MT_INFORMATION, l3dss1_information}, + {SBIT(10) | SBIT(11) | SBIT(15), + MT_NOTIFY, l3dss1_notify}, + {SBIT(0) | SBIT(1) | SBIT(2) | SBIT(3) | SBIT(4) | SBIT(7) | SBIT(8) | SBIT(10) | + SBIT(11) | SBIT(12) | SBIT(15) | SBIT(17) | SBIT(19) | SBIT(25), + MT_RELEASE_COMPLETE, l3dss1_release_cmpl}, + {SBIT(1) | SBIT(2) | SBIT(3) | SBIT(4) | SBIT(7) | SBIT(8) | SBIT(9) | SBIT(10) | SBIT(11) | SBIT(12) | SBIT(15) | SBIT(17) | SBIT(25), + MT_RELEASE, l3dss1_release}, + {SBIT(19), MT_RELEASE, l3dss1_release_ind}, + {SBIT(1) | SBIT(2) | SBIT(3) | SBIT(4) | SBIT(7) | SBIT(8) | SBIT(9) | SBIT(10) | SBIT(11) | SBIT(15) | SBIT(17) | SBIT(25), + MT_DISCONNECT, l3dss1_disconnect}, + {SBIT(19), + MT_DISCONNECT, l3dss1_dummy}, + {SBIT(1) | SBIT(2) | SBIT(3) | SBIT(4), + MT_CONNECT, l3dss1_connect}, + {SBIT(8), + MT_CONNECT_ACKNOWLEDGE, l3dss1_connect_ack}, + {SBIT(15), + MT_SUSPEND_ACKNOWLEDGE, l3dss1_suspend_ack}, + {SBIT(15), + MT_SUSPEND_REJECT, l3dss1_suspend_rej}, + {SBIT(17), + MT_RESUME_ACKNOWLEDGE, l3dss1_resume_ack}, + {SBIT(17), + MT_RESUME_REJECT, l3dss1_resume_rej}, +}; + +#define DATASLLEN \ + (sizeof(datastatelist) / sizeof(struct stateentry)) + +static struct stateentry globalmes_list[] = +{ + {ALL_STATES, + MT_STATUS, l3dss1_status}, + {SBIT(0), + MT_RESTART, l3dss1_global_restart}, +/* {SBIT(1), + MT_RESTART_ACKNOWLEDGE, l3dss1_restart_ack}, +*/ +}; +#define GLOBALM_LEN \ + (sizeof(globalmes_list) / sizeof(struct stateentry)) + +static struct stateentry manstatelist[] = +{ + {SBIT(2), + DL_ESTABLISH | INDICATION, l3dss1_dl_reset}, + {SBIT(10), + DL_ESTABLISH | CONFIRM, l3dss1_dl_reest_status}, + {SBIT(10), + DL_RELEASE | INDICATION, l3dss1_dl_reestablish}, + {ALL_STATES, + DL_RELEASE | INDICATION, l3dss1_dl_release}, +}; + +#define MANSLLEN \ + (sizeof(manstatelist) / sizeof(struct stateentry)) +/* *INDENT-ON* */ + + +static void +global_handler(struct PStack *st, int mt, struct sk_buff *skb) +{ + u_char tmp[16]; + u_char *p = tmp; + int l; + int i; + struct l3_process *proc = st->l3.global; + + proc->callref = skb->data[2]; /* cr flag */ + for (i = 0; i < GLOBALM_LEN; i++) + if ((mt == globalmes_list[i].primitive) && + ((1 << proc->state) & globalmes_list[i].state)) + break; + if (i == GLOBALM_LEN) { + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "dss1 global state %d mt %x unhandled", + proc->state, mt); + } + MsgHead(p, proc->callref, MT_STATUS); + *p++ = IE_CAUSE; + *p++ = 0x2; + *p++ = 0x80; + *p++ = 81 |0x80; /* invalid cr */ + *p++ = 0x14; /* CallState */ + *p++ = 0x1; + *p++ = proc->state & 0x3f; + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + memcpy(skb_put(skb, l), tmp, l); + l3_msg(proc->st, DL_DATA | REQUEST, skb); + } else { + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "dss1 global %d mt %x", + proc->state, mt); + } + globalmes_list[i].rout(proc, mt, skb); + } +} + +static void +dss1up(struct PStack *st, int pr, void *arg) +{ + int i, mt, cr, cause, callState; + char *ptr; + u_char *p; + struct sk_buff *skb = arg; + struct l3_process *proc; + + switch (pr) { + case (DL_DATA | INDICATION): + case (DL_UNIT_DATA | INDICATION): + break; + case (DL_ESTABLISH | CONFIRM): + case (DL_ESTABLISH | INDICATION): + case (DL_RELEASE | INDICATION): + case (DL_RELEASE | CONFIRM): + l3_msg(st, pr, arg); + return; + break; + default: + printk(KERN_ERR "HiSax dss1up unknown pr=%04x\n", pr); + return; + } + if (skb->len < 3) { + l3_debug(st, "dss1up frame too short(%d)", skb->len); + dev_kfree_skb(skb); + return; + } + + if (skb->data[0] != PROTO_DIS_EURO) { + if (st->l3.debug & L3_DEB_PROTERR) { + l3_debug(st, "dss1up%sunexpected discriminator %x message len %d", + (pr == (DL_DATA | INDICATION)) ? " " : "(broadcast) ", + skb->data[0], skb->len); + } + dev_kfree_skb(skb); + return; + } + cr = getcallref(skb->data); + if (skb->len < ((skb->data[1] & 0x0f) + 3)) { + l3_debug(st, "dss1up frame too short(%d)", skb->len); + dev_kfree_skb(skb); + return; + } + mt = skb->data[skb->data[1] + 2]; + if (st->l3.debug & L3_DEB_STATE) + l3_debug(st, "dss1up cr %d", cr); + if (cr == -2) { /* wrong Callref */ + if (st->l3.debug & L3_DEB_WARN) + l3_debug(st, "dss1up wrong Callref"); + dev_kfree_skb(skb); + return; + } else if (cr == -1) { /* Dummy Callref */ + if (mt == MT_FACILITY) + if ((p = findie(skb->data, skb->len, IE_FACILITY, 0))) { + l3dss1_parse_facility(st, NULL, + (pr == (DL_DATA | INDICATION)) ? -1 : -2, p); + dev_kfree_skb(skb); + return; + } + if (st->l3.debug & L3_DEB_WARN) + l3_debug(st, "dss1up dummy Callref (no facility msg or ie)"); + dev_kfree_skb(skb); + return; + } else if ((((skb->data[1] & 0x0f) == 1) && (0==(cr & 0x7f))) || + (((skb->data[1] & 0x0f) == 2) && (0==(cr & 0x7fff)))) { /* Global CallRef */ + if (st->l3.debug & L3_DEB_STATE) + l3_debug(st, "dss1up Global CallRef"); + global_handler(st, mt, skb); + dev_kfree_skb(skb); + return; + } else if (!(proc = getl3proc(st, cr))) { + /* No transaction process exist, that means no call with + * this callreference is active + */ + if (mt == MT_SETUP) { + /* Setup creates a new transaction process */ + if (skb->data[2] & 0x80) { + /* Setup with wrong CREF flag */ + if (st->l3.debug & L3_DEB_STATE) + l3_debug(st, "dss1up wrong CRef flag"); + dev_kfree_skb(skb); + return; + } + if (!(proc = dss1_new_l3_process(st, cr))) { + /* May be to answer with RELEASE_COMPLETE and + * CAUSE 0x2f "Resource unavailable", but this + * need a new_l3_process too ... arghh + */ + dev_kfree_skb(skb); + return; + } + } else if (mt == MT_STATUS) { + cause = 0; + if ((ptr = findie(skb->data, skb->len, IE_CAUSE, 0)) != NULL) { + ptr++; + if (*ptr++ == 2) + ptr++; + cause = *ptr & 0x7f; + } + callState = 0; + if ((ptr = findie(skb->data, skb->len, IE_CALL_STATE, 0)) != NULL) { + ptr++; + if (*ptr++ == 2) + ptr++; + callState = *ptr; + } + /* ETS 300-104 part 2.4.1 + * if setup has not been made and a message type + * MT_STATUS is received with call state == 0, + * we must send nothing + */ + if (callState != 0) { + /* ETS 300-104 part 2.4.2 + * if setup has not been made and a message type + * MT_STATUS is received with call state != 0, + * we must send MT_RELEASE_COMPLETE cause 101 + */ + if ((proc = dss1_new_l3_process(st, cr))) { + proc->para.cause = 101; + l3dss1_msg_without_setup(proc, 0, NULL); + } + } + dev_kfree_skb(skb); + return; + } else if (mt == MT_RELEASE_COMPLETE) { + dev_kfree_skb(skb); + return; + } else { + /* ETS 300-104 part 2 + * if setup has not been made and a message type + * (except MT_SETUP and RELEASE_COMPLETE) is received, + * we must send MT_RELEASE_COMPLETE cause 81 */ + dev_kfree_skb(skb); + if ((proc = dss1_new_l3_process(st, cr))) { + proc->para.cause = 81; + l3dss1_msg_without_setup(proc, 0, NULL); + } + return; + } + } + if (l3dss1_check_messagetype_validity(proc, mt, skb)) { + dev_kfree_skb(skb); + return; + } + if ((p = findie(skb->data, skb->len, IE_DISPLAY, 0)) != NULL) + l3dss1_deliver_display(proc, pr, p); /* Display IE included */ + for (i = 0; i < DATASLLEN; i++) + if ((mt == datastatelist[i].primitive) && + ((1 << proc->state) & datastatelist[i].state)) + break; + if (i == DATASLLEN) { + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "dss1up%sstate %d mt %#x unhandled", + (pr == (DL_DATA | INDICATION)) ? " " : "(broadcast) ", + proc->state, mt); + } + if ((MT_RELEASE_COMPLETE != mt) && (MT_RELEASE != mt)) { + proc->para.cause = 101; + l3dss1_status_send(proc, pr, skb); + } + } else { + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "dss1up%sstate %d mt %x", + (pr == (DL_DATA | INDICATION)) ? " " : "(broadcast) ", + proc->state, mt); + } + datastatelist[i].rout(proc, pr, skb); + } + dev_kfree_skb(skb); + return; +} + +static void +dss1down(struct PStack *st, int pr, void *arg) +{ + int i, cr; + struct l3_process *proc; + struct Channel *chan; + + if ((DL_ESTABLISH | REQUEST) == pr) { + l3_msg(st, pr, NULL); + return; + } else if (((CC_SETUP | REQUEST) == pr) || ((CC_RESUME | REQUEST) == pr)) { + chan = arg; + cr = newcallref(); + cr |= 0x80; + if ((proc = dss1_new_l3_process(st, cr))) { + proc->chan = chan; + chan->proc = proc; + memcpy(&proc->para.setup, &chan->setup, sizeof(setup_parm)); + proc->callref = cr; + } + } else { + proc = arg; + } + if (!proc) { + printk(KERN_ERR "HiSax dss1down without proc pr=%04x\n", pr); + return; + } + + if ( pr == (CC_TDSS1_IO | REQUEST)) { + l3dss1_io_timer(proc); /* timer expires */ + return; + } + + for (i = 0; i < DOWNSLLEN; i++) + if ((pr == downstatelist[i].primitive) && + ((1 << proc->state) & downstatelist[i].state)) + break; + if (i == DOWNSLLEN) { + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "dss1down state %d prim %#x unhandled", + proc->state, pr); + } + } else { + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "dss1down state %d prim %#x", + proc->state, pr); + } + downstatelist[i].rout(proc, pr, arg); + } +} + +static void +dss1man(struct PStack *st, int pr, void *arg) +{ + int i; + struct l3_process *proc = arg; + + if (!proc) { + printk(KERN_ERR "HiSax dss1man without proc pr=%04x\n", pr); + return; + } + for (i = 0; i < MANSLLEN; i++) + if ((pr == manstatelist[i].primitive) && + ((1 << proc->state) & manstatelist[i].state)) + break; + if (i == MANSLLEN) { + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "cr %d dss1man state %d prim %#x unhandled", + proc->callref & 0x7f, proc->state, pr); + } + } else { + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "cr %d dss1man state %d prim %#x", + proc->callref & 0x7f, proc->state, pr); + } + manstatelist[i].rout(proc, pr, arg); + } +} + +void +setstack_dss1(struct PStack *st) +{ + char tmp[64]; + int i; + + st->lli.l4l3 = dss1down; + st->lli.l4l3_proto = l3dss1_cmd_global; + st->l2.l2l3 = dss1up; + st->l3.l3ml3 = dss1man; + st->l3.N303 = 1; + st->prot.dss1.last_invoke_id = 0; + st->prot.dss1.invoke_used[0] = 1; /* Bit 0 must always be set to 1 */ + i = 1; + while (i < 32) + st->prot.dss1.invoke_used[i++] = 0; + + if (!(st->l3.global = kmalloc(sizeof(struct l3_process), GFP_ATOMIC))) { + printk(KERN_ERR "HiSax can't get memory for dss1 global CR\n"); + } else { + st->l3.global->state = 0; + st->l3.global->callref = 0; + st->l3.global->next = NULL; + st->l3.global->debug = L3_DEB_WARN; + st->l3.global->st = st; + st->l3.global->N303 = 1; + st->l3.global->prot.dss1.invoke_id = 0; + + L3InitTimer(st->l3.global, &st->l3.global->timer); + } + strcpy(tmp, dss1_revision); + printk(KERN_INFO "HiSax: DSS1 Rev. %s\n", HiSax_getrev(tmp)); +} diff --git a/drivers/isdn/hisax/l3dss1.h b/drivers/isdn/hisax/l3dss1.h new file mode 100644 index 000000000000..6da47f05ef2a --- /dev/null +++ b/drivers/isdn/hisax/l3dss1.h @@ -0,0 +1,124 @@ +/* $Id: l3dss1.h,v 1.10.6.2 2001/09/23 22:24:50 kai Exp $ + * + * DSS1 (Euro) D-channel protocol defines + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#ifndef l3dss1_process + +#define T302 15000 +#define T303 4000 +#define T304 30000 +#define T305 30000 +#define T308 4000 +/* for layer 1 certification T309 < layer1 T3 (e.g. 4000) */ +/* This makes some tests easier and quicker */ +#define T309 40000 +#define T310 30000 +#define T313 4000 +#define T318 4000 +#define T319 4000 + +/* + * 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_SEGMENT 0x00 +#define IE_BEARER 0x04 +#define IE_CAUSE 0x08 +#define IE_CALL_ID 0x10 +#define IE_CALL_STATE 0x14 +#define IE_CHANNEL_ID 0x18 +#define IE_FACILITY 0x1c +#define IE_PROGRESS 0x1e +#define IE_NET_FAC 0x20 +#define IE_NOTIFY 0x27 +#define IE_DISPLAY 0x28 +#define IE_DATE 0x29 +#define IE_KEYPAD 0x2c +#define IE_SIGNAL 0x34 +#define IE_INFORATE 0x40 +#define IE_E2E_TDELAY 0x42 +#define IE_TDELAY_SEL 0x43 +#define IE_PACK_BINPARA 0x44 +#define IE_PACK_WINSIZE 0x45 +#define IE_PACK_SIZE 0x46 +#define IE_CUG 0x47 +#define IE_REV_CHARGE 0x4a +#define IE_CONNECT_PN 0x4c +#define IE_CONNECT_SUB 0x4d +#define IE_CALLING_PN 0x6c +#define IE_CALLING_SUB 0x6d +#define IE_CALLED_PN 0x70 +#define IE_CALLED_SUB 0x71 +#define IE_REDIR_NR 0x74 +#define IE_TRANS_SEL 0x78 +#define IE_RESTART_IND 0x79 +#define IE_LLC 0x7c +#define IE_HLC 0x7d +#define IE_USER_USER 0x7e +#define IE_ESCAPE 0x7f +#define IE_SHIFT 0x90 +#define IE_MORE_DATA 0xa0 +#define IE_COMPLETE 0xa1 +#define IE_CONGESTION 0xb0 +#define IE_REPEAT 0xd0 + +#define IE_MANDATORY 0x0100 +/* mandatory not in every case */ +#define IE_MANDATORY_1 0x0200 + +#define ERR_IE_COMPREHENSION 1 +#define ERR_IE_UNRECOGNIZED -1 +#define ERR_IE_LENGTH -2 +#define ERR_IE_SEQUENCE -3 + +#else /* only l3dss1_process */ + +/* l3dss1 specific data in l3 process */ +typedef struct + { unsigned char invoke_id; /* used invoke id in remote ops, 0 = not active */ + ulong ll_id; /* remebered ll id */ + u8 remote_operation; /* handled remote operation, 0 = not active */ + int proc; /* rememered procedure */ + ulong remote_result; /* result of remote operation for statcallb */ + char uus1_data[35]; /* data send during alerting or disconnect */ + } dss1_proc_priv; + +/* l3dss1 specific data in protocol stack */ +typedef struct + { unsigned char last_invoke_id; /* last used value for invoking */ + unsigned char invoke_used[32]; /* 256 bits for 256 values */ + } dss1_stk_priv; + +#endif /* only l3dss1_process */ diff --git a/drivers/isdn/hisax/l3ni1.c b/drivers/isdn/hisax/l3ni1.c new file mode 100644 index 000000000000..3ab3a54daac1 --- /dev/null +++ b/drivers/isdn/hisax/l3ni1.c @@ -0,0 +1,3189 @@ +/* $Id: l3ni1.c,v 2.8.2.3 2004/01/13 14:31:25 keil Exp $ + * + * NI1 D-channel protocol + * + * Author Matt Henderson & Guy Ellis + * Copyright by Traverse Technologies Pty Ltd, www.travers.com.au + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * 2000.6.6 Initial implementation of routines for US NI1 + * Layer 3 protocol based on the EURO/DSS1 D-channel protocol + * driver written by Karsten Keil et al. + * NI-1 Hall of Fame - Thanks to.... + * Ragnar Paulson - for some handy code fragments + * Will Scales - beta tester extraordinaire + * Brett Whittacre - beta tester and remote devel system in Vegas + * + */ + +#include "hisax.h" +#include "isdnl3.h" +#include "l3ni1.h" +#include <linux/ctype.h> + +extern char *HiSax_getrev(const char *revision); +const char *ni1_revision = "$Revision: 2.8.2.3 $"; + +#define EXT_BEARER_CAPS 1 + +#define MsgHead(ptr, cref, mty) \ + *ptr++ = 0x8; \ + if (cref == -1) { \ + *ptr++ = 0x0; \ + } else { \ + *ptr++ = 0x1; \ + *ptr++ = cref^0x80; \ + } \ + *ptr++ = mty + + +/**********************************************/ +/* get a new invoke id for remote operations. */ +/* Only a return value != 0 is valid */ +/**********************************************/ +static unsigned char new_invoke_id(struct PStack *p) +{ + unsigned char retval; + int i; + + i = 32; /* maximum search depth */ + + retval = p->prot.ni1.last_invoke_id + 1; /* try new id */ + while ((i) && (p->prot.ni1.invoke_used[retval >> 3] == 0xFF)) { + p->prot.ni1.last_invoke_id = (retval & 0xF8) + 8; + i--; + } + if (i) { + while (p->prot.ni1.invoke_used[retval >> 3] & (1 << (retval & 7))) + retval++; + } else + retval = 0; + p->prot.ni1.last_invoke_id = retval; + p->prot.ni1.invoke_used[retval >> 3] |= (1 << (retval & 7)); + return(retval); +} /* new_invoke_id */ + +/*************************/ +/* free a used invoke id */ +/*************************/ +static void free_invoke_id(struct PStack *p, unsigned char id) +{ + + if (!id) return; /* 0 = invalid value */ + + p->prot.ni1.invoke_used[id >> 3] &= ~(1 << (id & 7)); +} /* free_invoke_id */ + + +/**********************************************************/ +/* create a new l3 process and fill in ni1 specific data */ +/**********************************************************/ +static struct l3_process +*ni1_new_l3_process(struct PStack *st, int cr) +{ struct l3_process *proc; + + if (!(proc = new_l3_process(st, cr))) + return(NULL); + + proc->prot.ni1.invoke_id = 0; + proc->prot.ni1.remote_operation = 0; + proc->prot.ni1.uus1_data[0] = '\0'; + + return(proc); +} /* ni1_new_l3_process */ + +/************************************************/ +/* free a l3 process and all ni1 specific data */ +/************************************************/ +static void +ni1_release_l3_process(struct l3_process *p) +{ + free_invoke_id(p->st,p->prot.ni1.invoke_id); + release_l3_process(p); +} /* ni1_release_l3_process */ + +/********************************************************/ +/* search a process with invoke id id and dummy callref */ +/********************************************************/ +static struct l3_process * +l3ni1_search_dummy_proc(struct PStack *st, int id) +{ struct l3_process *pc = st->l3.proc; /* start of processes */ + + if (!id) return(NULL); + + while (pc) + { if ((pc->callref == -1) && (pc->prot.ni1.invoke_id == id)) + return(pc); + pc = pc->next; + } + return(NULL); +} /* l3ni1_search_dummy_proc */ + +/*******************************************************************/ +/* called when a facility message with a dummy callref is received */ +/* and a return result is delivered. id specifies the invoke id. */ +/*******************************************************************/ +static void +l3ni1_dummy_return_result(struct PStack *st, int id, u_char *p, u_char nlen) +{ isdn_ctrl ic; + struct IsdnCardState *cs; + struct l3_process *pc = NULL; + + if ((pc = l3ni1_search_dummy_proc(st, id))) + { L3DelTimer(&pc->timer); /* remove timer */ + + cs = pc->st->l1.hardware; + ic.driver = cs->myid; + ic.command = ISDN_STAT_PROT; + ic.arg = NI1_STAT_INVOKE_RES; + ic.parm.ni1_io.hl_id = pc->prot.ni1.invoke_id; + ic.parm.ni1_io.ll_id = pc->prot.ni1.ll_id; + ic.parm.ni1_io.proc = pc->prot.ni1.proc; + ic.parm.ni1_io.timeout= 0; + ic.parm.ni1_io.datalen = nlen; + ic.parm.ni1_io.data = p; + free_invoke_id(pc->st, pc->prot.ni1.invoke_id); + pc->prot.ni1.invoke_id = 0; /* reset id */ + + cs->iif.statcallb(&ic); + ni1_release_l3_process(pc); + } + else + l3_debug(st, "dummy return result id=0x%x result len=%d",id,nlen); +} /* l3ni1_dummy_return_result */ + +/*******************************************************************/ +/* called when a facility message with a dummy callref is received */ +/* and a return error is delivered. id specifies the invoke id. */ +/*******************************************************************/ +static void +l3ni1_dummy_error_return(struct PStack *st, int id, ulong error) +{ isdn_ctrl ic; + struct IsdnCardState *cs; + struct l3_process *pc = NULL; + + if ((pc = l3ni1_search_dummy_proc(st, id))) + { L3DelTimer(&pc->timer); /* remove timer */ + + cs = pc->st->l1.hardware; + ic.driver = cs->myid; + ic.command = ISDN_STAT_PROT; + ic.arg = NI1_STAT_INVOKE_ERR; + ic.parm.ni1_io.hl_id = pc->prot.ni1.invoke_id; + ic.parm.ni1_io.ll_id = pc->prot.ni1.ll_id; + ic.parm.ni1_io.proc = pc->prot.ni1.proc; + ic.parm.ni1_io.timeout= error; + ic.parm.ni1_io.datalen = 0; + ic.parm.ni1_io.data = NULL; + free_invoke_id(pc->st, pc->prot.ni1.invoke_id); + pc->prot.ni1.invoke_id = 0; /* reset id */ + + cs->iif.statcallb(&ic); + ni1_release_l3_process(pc); + } + else + l3_debug(st, "dummy return error id=0x%x error=0x%lx",id,error); +} /* l3ni1_error_return */ + +/*******************************************************************/ +/* called when a facility message with a dummy callref is received */ +/* and a invoke is delivered. id specifies the invoke id. */ +/*******************************************************************/ +static void +l3ni1_dummy_invoke(struct PStack *st, int cr, int id, + int ident, u_char *p, u_char nlen) +{ isdn_ctrl ic; + struct IsdnCardState *cs; + + l3_debug(st, "dummy invoke %s id=0x%x ident=0x%x datalen=%d", + (cr == -1) ? "local" : "broadcast",id,ident,nlen); + if (cr >= -1) return; /* ignore local data */ + + cs = st->l1.hardware; + ic.driver = cs->myid; + ic.command = ISDN_STAT_PROT; + ic.arg = NI1_STAT_INVOKE_BRD; + ic.parm.ni1_io.hl_id = id; + ic.parm.ni1_io.ll_id = 0; + ic.parm.ni1_io.proc = ident; + ic.parm.ni1_io.timeout= 0; + ic.parm.ni1_io.datalen = nlen; + ic.parm.ni1_io.data = p; + + cs->iif.statcallb(&ic); +} /* l3ni1_dummy_invoke */ + +static void +l3ni1_parse_facility(struct PStack *st, struct l3_process *pc, + int cr, u_char * p) +{ + int qd_len = 0; + unsigned char nlen = 0, ilen, cp_tag; + int ident, id; + ulong err_ret; + + if (pc) + st = pc->st; /* valid Stack */ + else + if ((!st) || (cr >= 0)) return; /* neither pc nor st specified */ + + p++; + qd_len = *p++; + if (qd_len == 0) { + l3_debug(st, "qd_len == 0"); + return; + } + if ((*p & 0x1F) != 0x11) { /* Service discriminator, supplementary service */ + l3_debug(st, "supplementary service != 0x11"); + return; + } + while (qd_len > 0 && !(*p & 0x80)) { /* extension ? */ + p++; + qd_len--; + } + if (qd_len < 2) { + l3_debug(st, "qd_len < 2"); + return; + } + p++; + qd_len--; + if ((*p & 0xE0) != 0xA0) { /* class and form */ + l3_debug(st, "class and form != 0xA0"); + return; + } + + cp_tag = *p & 0x1F; /* remember tag value */ + + p++; + qd_len--; + if (qd_len < 1) + { l3_debug(st, "qd_len < 1"); + return; + } + if (*p & 0x80) + { /* length format indefinite or limited */ + nlen = *p++ & 0x7F; /* number of len bytes or indefinite */ + if ((qd_len-- < ((!nlen) ? 3 : (1 + nlen))) || + (nlen > 1)) + { l3_debug(st, "length format error or not implemented"); + return; + } + if (nlen == 1) + { nlen = *p++; /* complete length */ + qd_len--; + } + else + { qd_len -= 2; /* trailing null bytes */ + if ((*(p+qd_len)) || (*(p+qd_len+1))) + { l3_debug(st,"length format indefinite error"); + return; + } + nlen = qd_len; + } + } + else + { nlen = *p++; + qd_len--; + } + if (qd_len < nlen) + { l3_debug(st, "qd_len < nlen"); + return; + } + qd_len -= nlen; + + if (nlen < 2) + { l3_debug(st, "nlen < 2"); + return; + } + if (*p != 0x02) + { /* invoke identifier tag */ + l3_debug(st, "invoke identifier tag !=0x02"); + return; + } + p++; + nlen--; + if (*p & 0x80) + { /* length format */ + l3_debug(st, "invoke id length format 2"); + return; + } + ilen = *p++; + nlen--; + if (ilen > nlen || ilen == 0) + { l3_debug(st, "ilen > nlen || ilen == 0"); + return; + } + nlen -= ilen; + id = 0; + while (ilen > 0) + { id = (id << 8) | (*p++ & 0xFF); /* invoke identifier */ + ilen--; + } + + switch (cp_tag) { /* component tag */ + case 1: /* invoke */ + if (nlen < 2) { + l3_debug(st, "nlen < 2 22"); + return; + } + if (*p != 0x02) { /* operation value */ + l3_debug(st, "operation value !=0x02"); + return; + } + p++; + nlen--; + ilen = *p++; + nlen--; + if (ilen > nlen || ilen == 0) { + l3_debug(st, "ilen > nlen || ilen == 0 22"); + return; + } + nlen -= ilen; + ident = 0; + while (ilen > 0) { + ident = (ident << 8) | (*p++ & 0xFF); + ilen--; + } + + if (!pc) + { + l3ni1_dummy_invoke(st, cr, id, ident, p, nlen); + return; + } + l3_debug(st, "invoke break"); + break; + case 2: /* return result */ + /* if no process available handle separately */ + if (!pc) + { if (cr == -1) + l3ni1_dummy_return_result(st, id, p, nlen); + return; + } + if ((pc->prot.ni1.invoke_id) && (pc->prot.ni1.invoke_id == id)) + { /* Diversion successful */ + free_invoke_id(st,pc->prot.ni1.invoke_id); + pc->prot.ni1.remote_result = 0; /* success */ + pc->prot.ni1.invoke_id = 0; + pc->redir_result = pc->prot.ni1.remote_result; + st->l3.l3l4(st, CC_REDIR | INDICATION, pc); } /* Diversion successful */ + else + l3_debug(st,"return error unknown identifier"); + break; + case 3: /* return error */ + err_ret = 0; + if (nlen < 2) + { l3_debug(st, "return error nlen < 2"); + return; + } + if (*p != 0x02) + { /* result tag */ + l3_debug(st, "invoke error tag !=0x02"); + return; + } + p++; + nlen--; + if (*p > 4) + { /* length format */ + l3_debug(st, "invoke return errlen > 4 "); + return; + } + ilen = *p++; + nlen--; + if (ilen > nlen || ilen == 0) + { l3_debug(st, "error return ilen > nlen || ilen == 0"); + return; + } + nlen -= ilen; + while (ilen > 0) + { err_ret = (err_ret << 8) | (*p++ & 0xFF); /* error value */ + ilen--; + } + /* if no process available handle separately */ + if (!pc) + { if (cr == -1) + l3ni1_dummy_error_return(st, id, err_ret); + return; + } + if ((pc->prot.ni1.invoke_id) && (pc->prot.ni1.invoke_id == id)) + { /* Deflection error */ + free_invoke_id(st,pc->prot.ni1.invoke_id); + pc->prot.ni1.remote_result = err_ret; /* result */ + pc->prot.ni1.invoke_id = 0; + pc->redir_result = pc->prot.ni1.remote_result; + st->l3.l3l4(st, CC_REDIR | INDICATION, pc); + } /* Deflection error */ + else + l3_debug(st,"return result unknown identifier"); + break; + default: + l3_debug(st, "facility default break tag=0x%02x",cp_tag); + break; + } +} + +static void +l3ni1_message(struct l3_process *pc, u_char mt) +{ + struct sk_buff *skb; + u_char *p; + + if (!(skb = l3_alloc_skb(4))) + return; + p = skb_put(skb, 4); + MsgHead(p, pc->callref, mt); + l3_msg(pc->st, DL_DATA | REQUEST, skb); +} + +static void +l3ni1_message_plus_chid(struct l3_process *pc, u_char mt) +/* sends an l3 messages plus channel id - added GE 05/09/00 */ +{ + struct sk_buff *skb; + u_char tmp[16]; + u_char *p = tmp; + u_char chid; + + chid = (u_char)(pc->para.bchannel & 0x03) | 0x88; + MsgHead(p, pc->callref, mt); + *p++ = IE_CHANNEL_ID; + *p++ = 0x01; + *p++ = chid; + + if (!(skb = l3_alloc_skb(7))) + return; + memcpy(skb_put(skb, 7), tmp, 7); + l3_msg(pc->st, DL_DATA | REQUEST, skb); +} + +static void +l3ni1_message_cause(struct l3_process *pc, u_char mt, u_char cause) +{ + struct sk_buff *skb; + u_char tmp[16]; + u_char *p = tmp; + int l; + + MsgHead(p, pc->callref, mt); + *p++ = IE_CAUSE; + *p++ = 0x2; + *p++ = 0x80; + *p++ = cause | 0x80; + + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + memcpy(skb_put(skb, l), tmp, l); + l3_msg(pc->st, DL_DATA | REQUEST, skb); +} + +static void +l3ni1_status_send(struct l3_process *pc, u_char pr, void *arg) +{ + u_char tmp[16]; + u_char *p = tmp; + int l; + struct sk_buff *skb; + + MsgHead(p, pc->callref, MT_STATUS); + + *p++ = IE_CAUSE; + *p++ = 0x2; + *p++ = 0x80; + *p++ = pc->para.cause | 0x80; + + *p++ = IE_CALL_STATE; + *p++ = 0x1; + *p++ = pc->state & 0x3f; + + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + memcpy(skb_put(skb, l), tmp, l); + l3_msg(pc->st, DL_DATA | REQUEST, skb); +} + +static void +l3ni1_msg_without_setup(struct l3_process *pc, u_char pr, void *arg) +{ + /* This routine is called if here was no SETUP made (checks in ni1up and in + * l3ni1_setup) and a RELEASE_COMPLETE have to be sent with an error code + * MT_STATUS_ENQUIRE in the NULL state is handled too + */ + u_char tmp[16]; + u_char *p = tmp; + int l; + struct sk_buff *skb; + + switch (pc->para.cause) { + case 81: /* invalid callreference */ + case 88: /* incomp destination */ + case 96: /* mandory IE missing */ + case 100: /* invalid IE contents */ + case 101: /* incompatible Callstate */ + MsgHead(p, pc->callref, MT_RELEASE_COMPLETE); + *p++ = IE_CAUSE; + *p++ = 0x2; + *p++ = 0x80; + *p++ = pc->para.cause | 0x80; + break; + default: + printk(KERN_ERR "HiSax l3ni1_msg_without_setup wrong cause %d\n", + pc->para.cause); + return; + } + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + memcpy(skb_put(skb, l), tmp, l); + l3_msg(pc->st, DL_DATA | REQUEST, skb); + ni1_release_l3_process(pc); +} + +static int ie_ALERTING[] = {IE_BEARER, IE_CHANNEL_ID | IE_MANDATORY_1, + IE_FACILITY, IE_PROGRESS, IE_DISPLAY, IE_SIGNAL, IE_HLC, + IE_USER_USER, -1}; +static int ie_CALL_PROCEEDING[] = {IE_BEARER, IE_CHANNEL_ID | IE_MANDATORY_1, + IE_FACILITY, IE_PROGRESS, IE_DISPLAY, IE_HLC, -1}; +static int ie_CONNECT[] = {IE_BEARER, IE_CHANNEL_ID | IE_MANDATORY_1, + IE_FACILITY, IE_PROGRESS, IE_DISPLAY, IE_DATE, IE_SIGNAL, + IE_CONNECT_PN, IE_CONNECT_SUB, IE_LLC, IE_HLC, IE_USER_USER, -1}; +static int ie_CONNECT_ACKNOWLEDGE[] = {IE_CHANNEL_ID, IE_DISPLAY, IE_SIGNAL, -1}; +static int ie_DISCONNECT[] = {IE_CAUSE | IE_MANDATORY, IE_FACILITY, + IE_PROGRESS, IE_DISPLAY, IE_SIGNAL, IE_USER_USER, -1}; +static int ie_INFORMATION[] = {IE_COMPLETE, IE_DISPLAY, IE_KEYPAD, IE_SIGNAL, + IE_CALLED_PN, -1}; +static int ie_NOTIFY[] = {IE_BEARER, IE_NOTIFY | IE_MANDATORY, IE_DISPLAY, -1}; +static int ie_PROGRESS[] = {IE_BEARER, IE_CAUSE, IE_FACILITY, IE_PROGRESS | + IE_MANDATORY, IE_DISPLAY, IE_HLC, IE_USER_USER, -1}; +static int ie_RELEASE[] = {IE_CAUSE | IE_MANDATORY_1, IE_FACILITY, IE_DISPLAY, + IE_SIGNAL, IE_USER_USER, -1}; +/* a RELEASE_COMPLETE with errors don't require special actions +static int ie_RELEASE_COMPLETE[] = {IE_CAUSE | IE_MANDATORY_1, IE_DISPLAY, IE_SIGNAL, IE_USER_USER, -1}; +*/ +static int ie_RESUME_ACKNOWLEDGE[] = {IE_CHANNEL_ID| IE_MANDATORY, IE_FACILITY, + IE_DISPLAY, -1}; +static int ie_RESUME_REJECT[] = {IE_CAUSE | IE_MANDATORY, IE_DISPLAY, -1}; +static int ie_SETUP[] = {IE_COMPLETE, IE_BEARER | IE_MANDATORY, + IE_CHANNEL_ID| IE_MANDATORY, IE_FACILITY, IE_PROGRESS, + IE_NET_FAC, IE_DISPLAY, IE_KEYPAD, IE_SIGNAL, IE_CALLING_PN, + IE_CALLING_SUB, IE_CALLED_PN, IE_CALLED_SUB, IE_REDIR_NR, + IE_LLC, IE_HLC, IE_USER_USER, -1}; +static int ie_SETUP_ACKNOWLEDGE[] = {IE_CHANNEL_ID | IE_MANDATORY, IE_FACILITY, + IE_PROGRESS, IE_DISPLAY, IE_SIGNAL, -1}; +static int ie_STATUS[] = {IE_CAUSE | IE_MANDATORY, IE_CALL_STATE | + IE_MANDATORY, IE_DISPLAY, -1}; +static int ie_STATUS_ENQUIRY[] = {IE_DISPLAY, -1}; +static int ie_SUSPEND_ACKNOWLEDGE[] = {IE_DISPLAY, IE_FACILITY, -1}; +static int ie_SUSPEND_REJECT[] = {IE_CAUSE | IE_MANDATORY, IE_DISPLAY, -1}; +/* not used + * static int ie_CONGESTION_CONTROL[] = {IE_CONGESTION | IE_MANDATORY, + * IE_CAUSE | IE_MANDATORY, IE_DISPLAY, -1}; + * static int ie_USER_INFORMATION[] = {IE_MORE_DATA, IE_USER_USER | IE_MANDATORY, -1}; + * static int ie_RESTART[] = {IE_CHANNEL_ID, IE_DISPLAY, IE_RESTART_IND | + * IE_MANDATORY, -1}; + */ +static int ie_FACILITY[] = {IE_FACILITY | IE_MANDATORY, IE_DISPLAY, -1}; +static int comp_required[] = {1,2,3,5,6,7,9,10,11,14,15,-1}; +static int l3_valid_states[] = {0,1,2,3,4,6,7,8,9,10,11,12,15,17,19,25,-1}; + +struct ie_len { + int ie; + int len; +}; + +static +struct ie_len max_ie_len[] = { + {IE_SEGMENT, 4}, + {IE_BEARER, 12}, + {IE_CAUSE, 32}, + {IE_CALL_ID, 10}, + {IE_CALL_STATE, 3}, + {IE_CHANNEL_ID, 34}, + {IE_FACILITY, 255}, + {IE_PROGRESS, 4}, + {IE_NET_FAC, 255}, + {IE_NOTIFY, 3}, + {IE_DISPLAY, 82}, + {IE_DATE, 8}, + {IE_KEYPAD, 34}, + {IE_SIGNAL, 3}, + {IE_INFORATE, 6}, + {IE_E2E_TDELAY, 11}, + {IE_TDELAY_SEL, 5}, + {IE_PACK_BINPARA, 3}, + {IE_PACK_WINSIZE, 4}, + {IE_PACK_SIZE, 4}, + {IE_CUG, 7}, + {IE_REV_CHARGE, 3}, + {IE_CALLING_PN, 24}, + {IE_CALLING_SUB, 23}, + {IE_CALLED_PN, 24}, + {IE_CALLED_SUB, 23}, + {IE_REDIR_NR, 255}, + {IE_TRANS_SEL, 255}, + {IE_RESTART_IND, 3}, + {IE_LLC, 18}, + {IE_HLC, 5}, + {IE_USER_USER, 131}, + {-1,0}, +}; + +static int +getmax_ie_len(u_char ie) { + int i = 0; + while (max_ie_len[i].ie != -1) { + if (max_ie_len[i].ie == ie) + return(max_ie_len[i].len); + i++; + } + return(255); +} + +static int +ie_in_set(struct l3_process *pc, u_char ie, int *checklist) { + int ret = 1; + + while (*checklist != -1) { + if ((*checklist & 0xff) == ie) { + if (ie & 0x80) + return(-ret); + else + return(ret); + } + ret++; + checklist++; + } + return(0); +} + +static int +check_infoelements(struct l3_process *pc, struct sk_buff *skb, int *checklist) +{ + int *cl = checklist; + u_char mt; + u_char *p, ie; + int l, newpos, oldpos; + int err_seq = 0, err_len = 0, err_compr = 0, err_ureg = 0; + u_char codeset = 0; + u_char old_codeset = 0; + u_char codelock = 1; + + p = skb->data; + /* skip cr */ + p++; + l = (*p++) & 0xf; + p += l; + mt = *p++; + oldpos = 0; + while ((p - skb->data) < skb->len) { + if ((*p & 0xf0) == 0x90) { /* shift codeset */ + old_codeset = codeset; + codeset = *p & 7; + if (*p & 0x08) + codelock = 0; + else + codelock = 1; + if (pc->debug & L3_DEB_CHECK) + l3_debug(pc->st, "check IE shift%scodeset %d->%d", + codelock ? " locking ": " ", old_codeset, codeset); + p++; + continue; + } + if (!codeset) { /* only codeset 0 */ + if ((newpos = ie_in_set(pc, *p, cl))) { + if (newpos > 0) { + if (newpos < oldpos) + err_seq++; + else + oldpos = newpos; + } + } else { + if (ie_in_set(pc, *p, comp_required)) + err_compr++; + else + err_ureg++; + } + } + ie = *p++; + if (ie & 0x80) { + l = 1; + } else { + l = *p++; + p += l; + l += 2; + } + if (!codeset && (l > getmax_ie_len(ie))) + err_len++; + if (!codelock) { + if (pc->debug & L3_DEB_CHECK) + l3_debug(pc->st, "check IE shift back codeset %d->%d", + codeset, old_codeset); + codeset = old_codeset; + codelock = 1; + } + } + if (err_compr | err_ureg | err_len | err_seq) { + if (pc->debug & L3_DEB_CHECK) + l3_debug(pc->st, "check IE MT(%x) %d/%d/%d/%d", + mt, err_compr, err_ureg, err_len, err_seq); + if (err_compr) + return(ERR_IE_COMPREHENSION); + if (err_ureg) + return(ERR_IE_UNRECOGNIZED); + if (err_len) + return(ERR_IE_LENGTH); + if (err_seq) + return(ERR_IE_SEQUENCE); + } + return(0); +} + +/* verify if a message type exists and contain no IE error */ +static int +l3ni1_check_messagetype_validity(struct l3_process *pc, int mt, void *arg) +{ + switch (mt) { + case MT_ALERTING: + case MT_CALL_PROCEEDING: + case MT_CONNECT: + case MT_CONNECT_ACKNOWLEDGE: + case MT_DISCONNECT: + case MT_INFORMATION: + case MT_FACILITY: + case MT_NOTIFY: + case MT_PROGRESS: + case MT_RELEASE: + case MT_RELEASE_COMPLETE: + case MT_SETUP: + case MT_SETUP_ACKNOWLEDGE: + case MT_RESUME_ACKNOWLEDGE: + case MT_RESUME_REJECT: + case MT_SUSPEND_ACKNOWLEDGE: + case MT_SUSPEND_REJECT: + case MT_USER_INFORMATION: + case MT_RESTART: + case MT_RESTART_ACKNOWLEDGE: + case MT_CONGESTION_CONTROL: + case MT_STATUS: + case MT_STATUS_ENQUIRY: + if (pc->debug & L3_DEB_CHECK) + l3_debug(pc->st, "l3ni1_check_messagetype_validity mt(%x) OK", mt); + break; + case MT_RESUME: /* RESUME only in user->net */ + case MT_SUSPEND: /* SUSPEND only in user->net */ + default: + if (pc->debug & (L3_DEB_CHECK | L3_DEB_WARN)) + l3_debug(pc->st, "l3ni1_check_messagetype_validity mt(%x) fail", mt); + pc->para.cause = 97; + l3ni1_status_send(pc, 0, NULL); + return(1); + } + return(0); +} + +static void +l3ni1_std_ie_err(struct l3_process *pc, int ret) { + + if (pc->debug & L3_DEB_CHECK) + l3_debug(pc->st, "check_infoelements ret %d", ret); + switch(ret) { + case 0: + break; + case ERR_IE_COMPREHENSION: + pc->para.cause = 96; + l3ni1_status_send(pc, 0, NULL); + break; + case ERR_IE_UNRECOGNIZED: + pc->para.cause = 99; + l3ni1_status_send(pc, 0, NULL); + break; + case ERR_IE_LENGTH: + pc->para.cause = 100; + l3ni1_status_send(pc, 0, NULL); + break; + case ERR_IE_SEQUENCE: + default: + break; + } +} + +static int +l3ni1_get_channel_id(struct l3_process *pc, struct sk_buff *skb) { + u_char *p; + + p = skb->data; + if ((p = findie(p, skb->len, IE_CHANNEL_ID, 0))) { + p++; + if (*p != 1) { /* len for BRI = 1 */ + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "wrong chid len %d", *p); + return (-2); + } + p++; + if (*p & 0x60) { /* only base rate interface */ + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "wrong chid %x", *p); + return (-3); + } + return(*p & 0x3); + } else + return(-1); +} + +static int +l3ni1_get_cause(struct l3_process *pc, struct sk_buff *skb) { + u_char l, i=0; + u_char *p; + + p = skb->data; + pc->para.cause = 31; + pc->para.loc = 0; + if ((p = findie(p, skb->len, IE_CAUSE, 0))) { + p++; + l = *p++; + if (l>30) + return(1); + if (l) { + pc->para.loc = *p++; + l--; + } else { + return(2); + } + if (l && !(pc->para.loc & 0x80)) { + l--; + p++; /* skip recommendation */ + } + if (l) { + pc->para.cause = *p++; + l--; + if (!(pc->para.cause & 0x80)) + return(3); + } else + return(4); + while (l && (i<6)) { + pc->para.diag[i++] = *p++; + l--; + } + } else + return(-1); + return(0); +} + +static void +l3ni1_msg_with_uus(struct l3_process *pc, u_char cmd) +{ + struct sk_buff *skb; + u_char tmp[16+40]; + u_char *p = tmp; + int l; + + MsgHead(p, pc->callref, cmd); + + if (pc->prot.ni1.uus1_data[0]) + { *p++ = IE_USER_USER; /* UUS info element */ + *p++ = strlen(pc->prot.ni1.uus1_data) + 1; + *p++ = 0x04; /* IA5 chars */ + strcpy(p,pc->prot.ni1.uus1_data); + p += strlen(pc->prot.ni1.uus1_data); + pc->prot.ni1.uus1_data[0] = '\0'; + } + + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + memcpy(skb_put(skb, l), tmp, l); + l3_msg(pc->st, DL_DATA | REQUEST, skb); +} /* l3ni1_msg_with_uus */ + +static void +l3ni1_release_req(struct l3_process *pc, u_char pr, void *arg) +{ + StopAllL3Timer(pc); + newl3state(pc, 19); + if (!pc->prot.ni1.uus1_data[0]) + l3ni1_message(pc, MT_RELEASE); + else + l3ni1_msg_with_uus(pc, MT_RELEASE); + L3AddTimer(&pc->timer, T308, CC_T308_1); +} + +static void +l3ni1_release_cmpl(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int ret; + + if ((ret = l3ni1_get_cause(pc, skb))>0) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "RELCMPL get_cause ret(%d)",ret); + } else if (ret < 0) + pc->para.cause = NO_CAUSE; + StopAllL3Timer(pc); + newl3state(pc, 0); + pc->st->l3.l3l4(pc->st, CC_RELEASE | CONFIRM, pc); + ni1_release_l3_process(pc); +} + +#if EXT_BEARER_CAPS + +static u_char * +EncodeASyncParams(u_char * p, u_char si2) +{ // 7c 06 88 90 21 42 00 bb + + p[0] = 0; + p[1] = 0x40; // Intermediate rate: 16 kbit/s jj 2000.02.19 + p[2] = 0x80; + if (si2 & 32) // 7 data bits + + p[2] += 16; + else // 8 data bits + + p[2] += 24; + + if (si2 & 16) // 2 stop bits + + p[2] += 96; + else // 1 stop bit + + p[2] += 32; + + if (si2 & 8) // even parity + + p[2] += 2; + else // no parity + + p[2] += 3; + + switch (si2 & 0x07) { + case 0: + p[0] = 66; // 1200 bit/s + + break; + case 1: + p[0] = 88; // 1200/75 bit/s + + break; + case 2: + p[0] = 87; // 75/1200 bit/s + + break; + case 3: + p[0] = 67; // 2400 bit/s + + break; + case 4: + p[0] = 69; // 4800 bit/s + + break; + case 5: + p[0] = 72; // 9600 bit/s + + break; + case 6: + p[0] = 73; // 14400 bit/s + + break; + case 7: + p[0] = 75; // 19200 bit/s + + break; + } + return p + 3; +} + +static u_char +EncodeSyncParams(u_char si2, u_char ai) +{ + + switch (si2) { + case 0: + return ai + 2; // 1200 bit/s + + case 1: + return ai + 24; // 1200/75 bit/s + + case 2: + return ai + 23; // 75/1200 bit/s + + case 3: + return ai + 3; // 2400 bit/s + + case 4: + return ai + 5; // 4800 bit/s + + case 5: + return ai + 8; // 9600 bit/s + + case 6: + return ai + 9; // 14400 bit/s + + case 7: + return ai + 11; // 19200 bit/s + + case 8: + return ai + 14; // 48000 bit/s + + case 9: + return ai + 15; // 56000 bit/s + + case 15: + return ai + 40; // negotiate bit/s + + default: + break; + } + return ai; +} + + +static u_char +DecodeASyncParams(u_char si2, u_char * p) +{ + u_char info; + + switch (p[5]) { + case 66: // 1200 bit/s + + break; // si2 don't change + + case 88: // 1200/75 bit/s + + si2 += 1; + break; + case 87: // 75/1200 bit/s + + si2 += 2; + break; + case 67: // 2400 bit/s + + si2 += 3; + break; + case 69: // 4800 bit/s + + si2 += 4; + break; + case 72: // 9600 bit/s + + si2 += 5; + break; + case 73: // 14400 bit/s + + si2 += 6; + break; + case 75: // 19200 bit/s + + si2 += 7; + break; + } + + info = p[7] & 0x7f; + if ((info & 16) && (!(info & 8))) // 7 data bits + + si2 += 32; // else 8 data bits + + if ((info & 96) == 96) // 2 stop bits + + si2 += 16; // else 1 stop bit + + if ((info & 2) && (!(info & 1))) // even parity + + si2 += 8; // else no parity + + return si2; +} + + +static u_char +DecodeSyncParams(u_char si2, u_char info) +{ + info &= 0x7f; + switch (info) { + case 40: // bit/s negotiation failed ai := 165 not 175! + + return si2 + 15; + case 15: // 56000 bit/s failed, ai := 0 not 169 ! + + return si2 + 9; + case 14: // 48000 bit/s + + return si2 + 8; + case 11: // 19200 bit/s + + return si2 + 7; + case 9: // 14400 bit/s + + return si2 + 6; + case 8: // 9600 bit/s + + return si2 + 5; + case 5: // 4800 bit/s + + return si2 + 4; + case 3: // 2400 bit/s + + return si2 + 3; + case 23: // 75/1200 bit/s + + return si2 + 2; + case 24: // 1200/75 bit/s + + return si2 + 1; + default: // 1200 bit/s + + return si2; + } +} + +static u_char +DecodeSI2(struct sk_buff *skb) +{ + u_char *p; //, *pend=skb->data + skb->len; + + if ((p = findie(skb->data, skb->len, 0x7c, 0))) { + switch (p[4] & 0x0f) { + case 0x01: + if (p[1] == 0x04) // sync. Bitratenadaption + + return DecodeSyncParams(160, p[5]); // V.110/X.30 + + else if (p[1] == 0x06) // async. Bitratenadaption + + return DecodeASyncParams(192, p); // V.110/X.30 + + break; + case 0x08: // if (p[5] == 0x02) // sync. Bitratenadaption + if (p[1] > 3) + return DecodeSyncParams(176, p[5]); // V.120 + break; + } + } + return 0; +} + +#endif + + +static void +l3ni1_setup_req(struct l3_process *pc, u_char pr, + void *arg) +{ + struct sk_buff *skb; + u_char tmp[128]; + u_char *p = tmp; + + u_char *teln; + u_char *sub; + u_char *sp; + int l; + + MsgHead(p, pc->callref, MT_SETUP); + + teln = pc->para.setup.phone; + + *p++ = 0xa1; /* complete indicator */ + /* + * Set Bearer Capability, Map info from 1TR6-convention to NI1 + */ + switch (pc->para.setup.si1) { + case 1: /* Telephony */ + *p++ = IE_BEARER; + *p++ = 0x3; /* Length */ + *p++ = 0x90; /* 3.1khz Audio */ + *p++ = 0x90; /* Circuit-Mode 64kbps */ + *p++ = 0xa2; /* u-Law Audio */ + break; + case 5: /* Datatransmission 64k, BTX */ + case 7: /* Datatransmission 64k */ + default: + *p++ = IE_BEARER; + *p++ = 0x2; /* Length */ + *p++ = 0x88; /* Coding Std. CCITT, unrestr. dig. Inform. */ + *p++ = 0x90; /* Circuit-Mode 64kbps */ + break; + } + + sub = NULL; + sp = teln; + while (*sp) { + if ('.' == *sp) { + sub = sp; + *sp = 0; + } else + sp++; + } + + *p++ = IE_KEYPAD; + *p++ = strlen(teln); + while (*teln) + *p++ = (*teln++) & 0x7F; + + if (sub) + *sub++ = '.'; + +#if EXT_BEARER_CAPS + if ((pc->para.setup.si2 >= 160) && (pc->para.setup.si2 <= 175)) { // sync. Bitratenadaption, V.110/X.30 + + *p++ = IE_LLC; + *p++ = 0x04; + *p++ = 0x88; + *p++ = 0x90; + *p++ = 0x21; + *p++ = EncodeSyncParams(pc->para.setup.si2 - 160, 0x80); + } else if ((pc->para.setup.si2 >= 176) && (pc->para.setup.si2 <= 191)) { // sync. Bitratenadaption, V.120 + + *p++ = IE_LLC; + *p++ = 0x05; + *p++ = 0x88; + *p++ = 0x90; + *p++ = 0x28; + *p++ = EncodeSyncParams(pc->para.setup.si2 - 176, 0); + *p++ = 0x82; + } else if (pc->para.setup.si2 >= 192) { // async. Bitratenadaption, V.110/X.30 + + *p++ = IE_LLC; + *p++ = 0x06; + *p++ = 0x88; + *p++ = 0x90; + *p++ = 0x21; + p = EncodeASyncParams(p, pc->para.setup.si2 - 192); + } else { + switch (pc->para.setup.si1) { + case 1: /* Telephony */ + *p++ = IE_LLC; + *p++ = 0x3; /* Length */ + *p++ = 0x90; /* Coding Std. CCITT, 3.1 kHz audio */ + *p++ = 0x90; /* Circuit-Mode 64kbps */ + *p++ = 0xa2; /* u-Law Audio */ + break; + case 5: /* Datatransmission 64k, BTX */ + case 7: /* Datatransmission 64k */ + default: + *p++ = IE_LLC; + *p++ = 0x2; /* Length */ + *p++ = 0x88; /* Coding Std. CCITT, unrestr. dig. Inform. */ + *p++ = 0x90; /* Circuit-Mode 64kbps */ + break; + } + } +#endif + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) +{ + return; +} + memcpy(skb_put(skb, l), tmp, l); + L3DelTimer(&pc->timer); + L3AddTimer(&pc->timer, T303, CC_T303); + newl3state(pc, 1); + l3_msg(pc->st, DL_DATA | REQUEST, skb); +} + +static void +l3ni1_call_proc(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int id, ret; + + if ((id = l3ni1_get_channel_id(pc, skb)) >= 0) { + if ((0 == id) || ((3 == id) && (0x10 == pc->para.moderate))) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "setup answer with wrong chid %x", id); + pc->para.cause = 100; + l3ni1_status_send(pc, pr, NULL); + return; + } + pc->para.bchannel = id; + } else if (1 == pc->state) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "setup answer wrong chid (ret %d)", id); + if (id == -1) + pc->para.cause = 96; + else + pc->para.cause = 100; + l3ni1_status_send(pc, pr, NULL); + return; + } + /* Now we are on none mandatory IEs */ + ret = check_infoelements(pc, skb, ie_CALL_PROCEEDING); + if (ERR_IE_COMPREHENSION == ret) { + l3ni1_std_ie_err(pc, ret); + return; + } + L3DelTimer(&pc->timer); + newl3state(pc, 3); + L3AddTimer(&pc->timer, T310, CC_T310); + if (ret) /* STATUS for none mandatory IE errors after actions are taken */ + l3ni1_std_ie_err(pc, ret); + pc->st->l3.l3l4(pc->st, CC_PROCEEDING | INDICATION, pc); +} + +static void +l3ni1_setup_ack(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int id, ret; + + if ((id = l3ni1_get_channel_id(pc, skb)) >= 0) { + if ((0 == id) || ((3 == id) && (0x10 == pc->para.moderate))) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "setup answer with wrong chid %x", id); + pc->para.cause = 100; + l3ni1_status_send(pc, pr, NULL); + return; + } + pc->para.bchannel = id; + } else { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "setup answer wrong chid (ret %d)", id); + if (id == -1) + pc->para.cause = 96; + else + pc->para.cause = 100; + l3ni1_status_send(pc, pr, NULL); + return; + } + /* Now we are on none mandatory IEs */ + ret = check_infoelements(pc, skb, ie_SETUP_ACKNOWLEDGE); + if (ERR_IE_COMPREHENSION == ret) { + l3ni1_std_ie_err(pc, ret); + return; + } + L3DelTimer(&pc->timer); + newl3state(pc, 2); + L3AddTimer(&pc->timer, T304, CC_T304); + if (ret) /* STATUS for none mandatory IE errors after actions are taken */ + l3ni1_std_ie_err(pc, ret); + pc->st->l3.l3l4(pc->st, CC_MORE_INFO | INDICATION, pc); +} + +static void +l3ni1_disconnect(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + u_char *p; + int ret; + u_char cause = 0; + + StopAllL3Timer(pc); + if ((ret = l3ni1_get_cause(pc, skb))) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "DISC get_cause ret(%d)", ret); + if (ret < 0) + cause = 96; + else if (ret > 0) + cause = 100; + } + if ((p = findie(skb->data, skb->len, IE_FACILITY, 0))) + l3ni1_parse_facility(pc->st, pc, pc->callref, p); + ret = check_infoelements(pc, skb, ie_DISCONNECT); + if (ERR_IE_COMPREHENSION == ret) + cause = 96; + else if ((!cause) && (ERR_IE_UNRECOGNIZED == ret)) + cause = 99; + ret = pc->state; + newl3state(pc, 12); + if (cause) + newl3state(pc, 19); + if (11 != ret) + pc->st->l3.l3l4(pc->st, CC_DISCONNECT | INDICATION, pc); + else if (!cause) + l3ni1_release_req(pc, pr, NULL); + if (cause) { + l3ni1_message_cause(pc, MT_RELEASE, cause); + L3AddTimer(&pc->timer, T308, CC_T308_1); + } +} + +static void +l3ni1_connect(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int ret; + + ret = check_infoelements(pc, skb, ie_CONNECT); + if (ERR_IE_COMPREHENSION == ret) { + l3ni1_std_ie_err(pc, ret); + return; + } + L3DelTimer(&pc->timer); /* T310 */ + newl3state(pc, 10); + pc->para.chargeinfo = 0; + /* here should inserted COLP handling KKe */ + if (ret) + l3ni1_std_ie_err(pc, ret); + pc->st->l3.l3l4(pc->st, CC_SETUP | CONFIRM, pc); +} + +static void +l3ni1_alerting(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int ret; + + ret = check_infoelements(pc, skb, ie_ALERTING); + if (ERR_IE_COMPREHENSION == ret) { + l3ni1_std_ie_err(pc, ret); + return; + } + L3DelTimer(&pc->timer); /* T304 */ + newl3state(pc, 4); + if (ret) + l3ni1_std_ie_err(pc, ret); + pc->st->l3.l3l4(pc->st, CC_ALERTING | INDICATION, pc); +} + +static void +l3ni1_setup(struct l3_process *pc, u_char pr, void *arg) +{ + u_char *p; + int bcfound = 0; + char tmp[80]; + struct sk_buff *skb = arg; + int id; + int err = 0; + + /* + * Bearer Capabilities + */ + p = skb->data; + /* only the first occurence 'll be detected ! */ + if ((p = findie(p, skb->len, 0x04, 0))) { + if ((p[1] < 2) || (p[1] > 11)) + err = 1; + else { + pc->para.setup.si2 = 0; + switch (p[2] & 0x7f) { + case 0x00: /* Speech */ + case 0x10: /* 3.1 Khz audio */ + pc->para.setup.si1 = 1; + break; + case 0x08: /* Unrestricted digital information */ + pc->para.setup.si1 = 7; +/* JIM, 05.11.97 I wanna set service indicator 2 */ +#if EXT_BEARER_CAPS + pc->para.setup.si2 = DecodeSI2(skb); +#endif + break; + case 0x09: /* Restricted digital information */ + pc->para.setup.si1 = 2; + break; + case 0x11: + /* Unrestr. digital information with + * tones/announcements ( or 7 kHz audio + */ + pc->para.setup.si1 = 3; + break; + case 0x18: /* Video */ + pc->para.setup.si1 = 4; + break; + default: + err = 2; + break; + } + switch (p[3] & 0x7f) { + case 0x40: /* packed mode */ + pc->para.setup.si1 = 8; + break; + case 0x10: /* 64 kbit */ + case 0x11: /* 2*64 kbit */ + case 0x13: /* 384 kbit */ + case 0x15: /* 1536 kbit */ + case 0x17: /* 1920 kbit */ + pc->para.moderate = p[3] & 0x7f; + break; + default: + err = 3; + break; + } + } + if (pc->debug & L3_DEB_SI) + l3_debug(pc->st, "SI=%d, AI=%d", + pc->para.setup.si1, pc->para.setup.si2); + if (err) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "setup with wrong bearer(l=%d:%x,%x)", + p[1], p[2], p[3]); + pc->para.cause = 100; + l3ni1_msg_without_setup(pc, pr, NULL); + return; + } + } else { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "setup without bearer capabilities"); + /* ETS 300-104 1.3.3 */ + pc->para.cause = 96; + l3ni1_msg_without_setup(pc, pr, NULL); + return; + } + /* + * Channel Identification + */ + if ((id = l3ni1_get_channel_id(pc, skb)) >= 0) { + if ((pc->para.bchannel = id)) { + if ((3 == id) && (0x10 == pc->para.moderate)) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "setup with wrong chid %x", + id); + pc->para.cause = 100; + l3ni1_msg_without_setup(pc, pr, NULL); + return; + } + bcfound++; + } else + { if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "setup without bchannel, call waiting"); + bcfound++; + } + } else { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "setup with wrong chid ret %d", id); + if (id == -1) + pc->para.cause = 96; + else + pc->para.cause = 100; + l3ni1_msg_without_setup(pc, pr, NULL); + return; + } + /* Now we are on none mandatory IEs */ + err = check_infoelements(pc, skb, ie_SETUP); + if (ERR_IE_COMPREHENSION == err) { + pc->para.cause = 96; + l3ni1_msg_without_setup(pc, pr, NULL); + return; + } + p = skb->data; + if ((p = findie(p, skb->len, 0x70, 0))) + iecpy(pc->para.setup.eazmsn, p, 1); + else + pc->para.setup.eazmsn[0] = 0; + + p = skb->data; + if ((p = findie(p, skb->len, 0x71, 0))) { + /* Called party subaddress */ + if ((p[1] >= 2) && (p[2] == 0x80) && (p[3] == 0x50)) { + tmp[0] = '.'; + iecpy(&tmp[1], p, 2); + strcat(pc->para.setup.eazmsn, tmp); + } else if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "wrong called subaddress"); + } + p = skb->data; + if ((p = findie(p, skb->len, 0x6c, 0))) { + pc->para.setup.plan = p[2]; + if (p[2] & 0x80) { + iecpy(pc->para.setup.phone, p, 1); + pc->para.setup.screen = 0; + } else { + iecpy(pc->para.setup.phone, p, 2); + pc->para.setup.screen = p[3]; + } + } else { + pc->para.setup.phone[0] = 0; + pc->para.setup.plan = 0; + pc->para.setup.screen = 0; + } + p = skb->data; + if ((p = findie(p, skb->len, 0x6d, 0))) { + /* Calling party subaddress */ + if ((p[1] >= 2) && (p[2] == 0x80) && (p[3] == 0x50)) { + tmp[0] = '.'; + iecpy(&tmp[1], p, 2); + strcat(pc->para.setup.phone, tmp); + } else if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "wrong calling subaddress"); + } + newl3state(pc, 6); + if (err) /* STATUS for none mandatory IE errors after actions are taken */ + l3ni1_std_ie_err(pc, err); + pc->st->l3.l3l4(pc->st, CC_SETUP | INDICATION, pc); +} + +static void +l3ni1_reset(struct l3_process *pc, u_char pr, void *arg) +{ + ni1_release_l3_process(pc); +} + +static void +l3ni1_disconnect_req(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb; + u_char tmp[16+40]; + u_char *p = tmp; + int l; + u_char cause = 16; + + if (pc->para.cause != NO_CAUSE) + cause = pc->para.cause; + + StopAllL3Timer(pc); + + MsgHead(p, pc->callref, MT_DISCONNECT); + + *p++ = IE_CAUSE; + *p++ = 0x2; + *p++ = 0x80; + *p++ = cause | 0x80; + + if (pc->prot.ni1.uus1_data[0]) + { *p++ = IE_USER_USER; /* UUS info element */ + *p++ = strlen(pc->prot.ni1.uus1_data) + 1; + *p++ = 0x04; /* IA5 chars */ + strcpy(p,pc->prot.ni1.uus1_data); + p += strlen(pc->prot.ni1.uus1_data); + pc->prot.ni1.uus1_data[0] = '\0'; + } + + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + memcpy(skb_put(skb, l), tmp, l); + newl3state(pc, 11); + l3_msg(pc->st, DL_DATA | REQUEST, skb); + L3AddTimer(&pc->timer, T305, CC_T305); +} + +static void +l3ni1_setup_rsp(struct l3_process *pc, u_char pr, + void *arg) +{ + if (!pc->para.bchannel) + { if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "D-chan connect for waiting call"); + l3ni1_disconnect_req(pc, pr, arg); + return; + } + newl3state(pc, 8); + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "D-chan connect for waiting call"); + l3ni1_message_plus_chid(pc, MT_CONNECT); /* GE 05/09/00 */ + L3DelTimer(&pc->timer); + L3AddTimer(&pc->timer, T313, CC_T313); +} + +static void +l3ni1_connect_ack(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int ret; + + ret = check_infoelements(pc, skb, ie_CONNECT_ACKNOWLEDGE); + if (ERR_IE_COMPREHENSION == ret) { + l3ni1_std_ie_err(pc, ret); + return; + } + newl3state(pc, 10); + L3DelTimer(&pc->timer); + if (ret) + l3ni1_std_ie_err(pc, ret); + pc->st->l3.l3l4(pc->st, CC_SETUP_COMPL | INDICATION, pc); +} + +static void +l3ni1_reject_req(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb; + u_char tmp[16]; + u_char *p = tmp; + int l; + u_char cause = 21; + + if (pc->para.cause != NO_CAUSE) + cause = pc->para.cause; + + MsgHead(p, pc->callref, MT_RELEASE_COMPLETE); + + *p++ = IE_CAUSE; + *p++ = 0x2; + *p++ = 0x80; + *p++ = cause | 0x80; + + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + memcpy(skb_put(skb, l), tmp, l); + l3_msg(pc->st, DL_DATA | REQUEST, skb); + pc->st->l3.l3l4(pc->st, CC_RELEASE | INDICATION, pc); + newl3state(pc, 0); + ni1_release_l3_process(pc); +} + +static void +l3ni1_release(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + u_char *p; + int ret, cause=0; + + StopAllL3Timer(pc); + if ((ret = l3ni1_get_cause(pc, skb))>0) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "REL get_cause ret(%d)", ret); + } else if (ret<0) + pc->para.cause = NO_CAUSE; + if ((p = findie(skb->data, skb->len, IE_FACILITY, 0))) { + l3ni1_parse_facility(pc->st, pc, pc->callref, p); + } + if ((ret<0) && (pc->state != 11)) + cause = 96; + else if (ret>0) + cause = 100; + ret = check_infoelements(pc, skb, ie_RELEASE); + if (ERR_IE_COMPREHENSION == ret) + cause = 96; + else if ((ERR_IE_UNRECOGNIZED == ret) && (!cause)) + cause = 99; + if (cause) + l3ni1_message_cause(pc, MT_RELEASE_COMPLETE, cause); + else + l3ni1_message(pc, MT_RELEASE_COMPLETE); + pc->st->l3.l3l4(pc->st, CC_RELEASE | INDICATION, pc); + newl3state(pc, 0); + ni1_release_l3_process(pc); +} + +static void +l3ni1_alert_req(struct l3_process *pc, u_char pr, + void *arg) +{ + newl3state(pc, 7); + if (!pc->prot.ni1.uus1_data[0]) + l3ni1_message(pc, MT_ALERTING); + else + l3ni1_msg_with_uus(pc, MT_ALERTING); +} + +static void +l3ni1_proceed_req(struct l3_process *pc, u_char pr, + void *arg) +{ + newl3state(pc, 9); + l3ni1_message(pc, MT_CALL_PROCEEDING); + pc->st->l3.l3l4(pc->st, CC_PROCEED_SEND | INDICATION, pc); +} + +static void +l3ni1_setup_ack_req(struct l3_process *pc, u_char pr, + void *arg) +{ + newl3state(pc, 25); + L3DelTimer(&pc->timer); + L3AddTimer(&pc->timer, T302, CC_T302); + l3ni1_message(pc, MT_SETUP_ACKNOWLEDGE); +} + +/********************************************/ +/* deliver a incoming display message to HL */ +/********************************************/ +static void +l3ni1_deliver_display(struct l3_process *pc, int pr, u_char *infp) +{ u_char len; + isdn_ctrl ic; + struct IsdnCardState *cs; + char *p; + + if (*infp++ != IE_DISPLAY) return; + if ((len = *infp++) > 80) return; /* total length <= 82 */ + if (!pc->chan) return; + + p = ic.parm.display; + while (len--) + *p++ = *infp++; + *p = '\0'; + ic.command = ISDN_STAT_DISPLAY; + cs = pc->st->l1.hardware; + ic.driver = cs->myid; + ic.arg = pc->chan->chan; + cs->iif.statcallb(&ic); +} /* l3ni1_deliver_display */ + + +static void +l3ni1_progress(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int err = 0; + u_char *p; + + if ((p = findie(skb->data, skb->len, IE_PROGRESS, 0))) { + if (p[1] != 2) { + err = 1; + pc->para.cause = 100; + } else if (!(p[2] & 0x70)) { + switch (p[2]) { + case 0x80: + case 0x81: + case 0x82: + case 0x84: + case 0x85: + case 0x87: + case 0x8a: + switch (p[3]) { + case 0x81: + case 0x82: + case 0x83: + case 0x84: + case 0x88: + break; + default: + err = 2; + pc->para.cause = 100; + break; + } + break; + default: + err = 3; + pc->para.cause = 100; + break; + } + } + } else { + pc->para.cause = 96; + err = 4; + } + if (err) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "progress error %d", err); + l3ni1_status_send(pc, pr, NULL); + return; + } + /* Now we are on none mandatory IEs */ + err = check_infoelements(pc, skb, ie_PROGRESS); + if (err) + l3ni1_std_ie_err(pc, err); + if (ERR_IE_COMPREHENSION != err) + pc->st->l3.l3l4(pc->st, CC_PROGRESS | INDICATION, pc); +} + +static void +l3ni1_notify(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int err = 0; + u_char *p; + + if ((p = findie(skb->data, skb->len, IE_NOTIFY, 0))) { + if (p[1] != 1) { + err = 1; + pc->para.cause = 100; + } else { + switch (p[2]) { + case 0x80: + case 0x81: + case 0x82: + break; + default: + pc->para.cause = 100; + err = 2; + break; + } + } + } else { + pc->para.cause = 96; + err = 3; + } + if (err) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "notify error %d", err); + l3ni1_status_send(pc, pr, NULL); + return; + } + /* Now we are on none mandatory IEs */ + err = check_infoelements(pc, skb, ie_NOTIFY); + if (err) + l3ni1_std_ie_err(pc, err); + if (ERR_IE_COMPREHENSION != err) + pc->st->l3.l3l4(pc->st, CC_NOTIFY | INDICATION, pc); +} + +static void +l3ni1_status_enq(struct l3_process *pc, u_char pr, void *arg) +{ + int ret; + struct sk_buff *skb = arg; + + ret = check_infoelements(pc, skb, ie_STATUS_ENQUIRY); + l3ni1_std_ie_err(pc, ret); + pc->para.cause = 30; /* response to STATUS_ENQUIRY */ + l3ni1_status_send(pc, pr, NULL); +} + +static void +l3ni1_information(struct l3_process *pc, u_char pr, void *arg) +{ + int ret; + struct sk_buff *skb = arg; + u_char *p; + char tmp[32]; + + ret = check_infoelements(pc, skb, ie_INFORMATION); + if (ret) + l3ni1_std_ie_err(pc, ret); + if (pc->state == 25) { /* overlap receiving */ + L3DelTimer(&pc->timer); + p = skb->data; + if ((p = findie(p, skb->len, 0x70, 0))) { + iecpy(tmp, p, 1); + strcat(pc->para.setup.eazmsn, tmp); + pc->st->l3.l3l4(pc->st, CC_MORE_INFO | INDICATION, pc); + } + L3AddTimer(&pc->timer, T302, CC_T302); + } +} + +/******************************/ +/* handle deflection requests */ +/******************************/ +static void l3ni1_redir_req(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb; + u_char tmp[128]; + u_char *p = tmp; + u_char *subp; + u_char len_phone = 0; + u_char len_sub = 0; + int l; + + + strcpy(pc->prot.ni1.uus1_data,pc->chan->setup.eazmsn); /* copy uus element if available */ + if (!pc->chan->setup.phone[0]) + { pc->para.cause = -1; + l3ni1_disconnect_req(pc,pr,arg); /* disconnect immediately */ + return; + } /* only uus */ + + if (pc->prot.ni1.invoke_id) + free_invoke_id(pc->st,pc->prot.ni1.invoke_id); + + if (!(pc->prot.ni1.invoke_id = new_invoke_id(pc->st))) + return; + + MsgHead(p, pc->callref, MT_FACILITY); + + for (subp = pc->chan->setup.phone; (*subp) && (*subp != '.'); subp++) len_phone++; /* len of phone number */ + if (*subp++ == '.') len_sub = strlen(subp) + 2; /* length including info subaddress element */ + + *p++ = 0x1c; /* Facility info element */ + *p++ = len_phone + len_sub + 2 + 2 + 8 + 3 + 3; /* length of element */ + *p++ = 0x91; /* remote operations protocol */ + *p++ = 0xa1; /* invoke component */ + + *p++ = len_phone + len_sub + 2 + 2 + 8 + 3; /* length of data */ + *p++ = 0x02; /* invoke id tag, integer */ + *p++ = 0x01; /* length */ + *p++ = pc->prot.ni1.invoke_id; /* invoke id */ + *p++ = 0x02; /* operation value tag, integer */ + *p++ = 0x01; /* length */ + *p++ = 0x0D; /* Call Deflect */ + + *p++ = 0x30; /* sequence phone number */ + *p++ = len_phone + 2 + 2 + 3 + len_sub; /* length */ + + *p++ = 0x30; /* Deflected to UserNumber */ + *p++ = len_phone+2+len_sub; /* length */ + *p++ = 0x80; /* NumberDigits */ + *p++ = len_phone; /* length */ + for (l = 0; l < len_phone; l++) + *p++ = pc->chan->setup.phone[l]; + + if (len_sub) + { *p++ = 0x04; /* called party subaddress */ + *p++ = len_sub - 2; + while (*subp) *p++ = *subp++; + } + + *p++ = 0x01; /* screening identifier */ + *p++ = 0x01; + *p++ = pc->chan->setup.screen; + + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) return; + memcpy(skb_put(skb, l), tmp, l); + + l3_msg(pc->st, DL_DATA | REQUEST, skb); +} /* l3ni1_redir_req */ + +/********************************************/ +/* handle deflection request in early state */ +/********************************************/ +static void l3ni1_redir_req_early(struct l3_process *pc, u_char pr, void *arg) +{ + l3ni1_proceed_req(pc,pr,arg); + l3ni1_redir_req(pc,pr,arg); +} /* l3ni1_redir_req_early */ + +/***********************************************/ +/* handle special commands for this protocol. */ +/* Examples are call independant services like */ +/* remote operations with dummy callref. */ +/***********************************************/ +static int l3ni1_cmd_global(struct PStack *st, isdn_ctrl *ic) +{ u_char id; + u_char temp[265]; + u_char *p = temp; + int i, l, proc_len; + struct sk_buff *skb; + struct l3_process *pc = NULL; + + switch (ic->arg) + { case NI1_CMD_INVOKE: + if (ic->parm.ni1_io.datalen < 0) return(-2); /* invalid parameter */ + + for (proc_len = 1, i = ic->parm.ni1_io.proc >> 8; i; i++) + i = i >> 8; /* add one byte */ + l = ic->parm.ni1_io.datalen + proc_len + 8; /* length excluding ie header */ + if (l > 255) + return(-2); /* too long */ + + if (!(id = new_invoke_id(st))) + return(0); /* first get a invoke id -> return if no available */ + + i = -1; + MsgHead(p, i, MT_FACILITY); /* build message head */ + *p++ = 0x1C; /* Facility IE */ + *p++ = l; /* length of ie */ + *p++ = 0x91; /* remote operations */ + *p++ = 0xA1; /* invoke */ + *p++ = l - 3; /* length of invoke */ + *p++ = 0x02; /* invoke id tag */ + *p++ = 0x01; /* length is 1 */ + *p++ = id; /* invoke id */ + *p++ = 0x02; /* operation */ + *p++ = proc_len; /* length of operation */ + + for (i = proc_len; i; i--) + *p++ = (ic->parm.ni1_io.proc >> (i-1)) & 0xFF; + memcpy(p, ic->parm.ni1_io.data, ic->parm.ni1_io.datalen); /* copy data */ + l = (p - temp) + ic->parm.ni1_io.datalen; /* total length */ + + if (ic->parm.ni1_io.timeout > 0) + if (!(pc = ni1_new_l3_process(st, -1))) + { free_invoke_id(st, id); + return(-2); + } + pc->prot.ni1.ll_id = ic->parm.ni1_io.ll_id; /* remember id */ + pc->prot.ni1.proc = ic->parm.ni1_io.proc; /* and procedure */ + + if (!(skb = l3_alloc_skb(l))) + { free_invoke_id(st, id); + if (pc) ni1_release_l3_process(pc); + return(-2); + } + memcpy(skb_put(skb, l), temp, l); + + if (pc) + { pc->prot.ni1.invoke_id = id; /* remember id */ + L3AddTimer(&pc->timer, ic->parm.ni1_io.timeout, CC_TNI1_IO | REQUEST); + } + + l3_msg(st, DL_DATA | REQUEST, skb); + ic->parm.ni1_io.hl_id = id; /* return id */ + return(0); + + case NI1_CMD_INVOKE_ABORT: + if ((pc = l3ni1_search_dummy_proc(st, ic->parm.ni1_io.hl_id))) + { L3DelTimer(&pc->timer); /* remove timer */ + ni1_release_l3_process(pc); + return(0); + } + else + { l3_debug(st, "l3ni1_cmd_global abort unknown id"); + return(-2); + } + break; + + default: + l3_debug(st, "l3ni1_cmd_global unknown cmd 0x%lx", ic->arg); + return(-1); + } /* switch ic-> arg */ + return(-1); +} /* l3ni1_cmd_global */ + +static void +l3ni1_io_timer(struct l3_process *pc) +{ isdn_ctrl ic; + struct IsdnCardState *cs = pc->st->l1.hardware; + + L3DelTimer(&pc->timer); /* remove timer */ + + ic.driver = cs->myid; + ic.command = ISDN_STAT_PROT; + ic.arg = NI1_STAT_INVOKE_ERR; + ic.parm.ni1_io.hl_id = pc->prot.ni1.invoke_id; + ic.parm.ni1_io.ll_id = pc->prot.ni1.ll_id; + ic.parm.ni1_io.proc = pc->prot.ni1.proc; + ic.parm.ni1_io.timeout= -1; + ic.parm.ni1_io.datalen = 0; + ic.parm.ni1_io.data = NULL; + free_invoke_id(pc->st, pc->prot.ni1.invoke_id); + pc->prot.ni1.invoke_id = 0; /* reset id */ + + cs->iif.statcallb(&ic); + + ni1_release_l3_process(pc); +} /* l3ni1_io_timer */ + +static void +l3ni1_release_ind(struct l3_process *pc, u_char pr, void *arg) +{ + u_char *p; + struct sk_buff *skb = arg; + int callState = 0; + p = skb->data; + + if ((p = findie(p, skb->len, IE_CALL_STATE, 0))) { + p++; + if (1 == *p++) + callState = *p; + } + if (callState == 0) { + /* ETS 300-104 7.6.1, 8.6.1, 10.6.1... and 16.1 + * set down layer 3 without sending any message + */ + pc->st->l3.l3l4(pc->st, CC_RELEASE | INDICATION, pc); + newl3state(pc, 0); + ni1_release_l3_process(pc); + } else { + pc->st->l3.l3l4(pc->st, CC_IGNORE | INDICATION, pc); + } +} + +static void +l3ni1_dummy(struct l3_process *pc, u_char pr, void *arg) +{ +} + +static void +l3ni1_t302(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + pc->para.loc = 0; + pc->para.cause = 28; /* invalid number */ + l3ni1_disconnect_req(pc, pr, NULL); + pc->st->l3.l3l4(pc->st, CC_SETUP_ERR, pc); +} + +static void +l3ni1_t303(struct l3_process *pc, u_char pr, void *arg) +{ + if (pc->N303 > 0) { + pc->N303--; + L3DelTimer(&pc->timer); + l3ni1_setup_req(pc, pr, arg); + } else { + L3DelTimer(&pc->timer); + l3ni1_message_cause(pc, MT_RELEASE_COMPLETE, 102); + pc->st->l3.l3l4(pc->st, CC_NOSETUP_RSP, pc); + ni1_release_l3_process(pc); + } +} + +static void +l3ni1_t304(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + pc->para.loc = 0; + pc->para.cause = 102; + l3ni1_disconnect_req(pc, pr, NULL); + pc->st->l3.l3l4(pc->st, CC_SETUP_ERR, pc); + +} + +static void +l3ni1_t305(struct l3_process *pc, u_char pr, void *arg) +{ + u_char tmp[16]; + u_char *p = tmp; + int l; + struct sk_buff *skb; + u_char cause = 16; + + L3DelTimer(&pc->timer); + if (pc->para.cause != NO_CAUSE) + cause = pc->para.cause; + + MsgHead(p, pc->callref, MT_RELEASE); + + *p++ = IE_CAUSE; + *p++ = 0x2; + *p++ = 0x80; + *p++ = cause | 0x80; + + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + memcpy(skb_put(skb, l), tmp, l); + newl3state(pc, 19); + l3_msg(pc->st, DL_DATA | REQUEST, skb); + L3AddTimer(&pc->timer, T308, CC_T308_1); +} + +static void +l3ni1_t310(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + pc->para.loc = 0; + pc->para.cause = 102; + l3ni1_disconnect_req(pc, pr, NULL); + pc->st->l3.l3l4(pc->st, CC_SETUP_ERR, pc); +} + +static void +l3ni1_t313(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + pc->para.loc = 0; + pc->para.cause = 102; + l3ni1_disconnect_req(pc, pr, NULL); + pc->st->l3.l3l4(pc->st, CC_CONNECT_ERR, pc); +} + +static void +l3ni1_t308_1(struct l3_process *pc, u_char pr, void *arg) +{ + newl3state(pc, 19); + L3DelTimer(&pc->timer); + l3ni1_message(pc, MT_RELEASE); + L3AddTimer(&pc->timer, T308, CC_T308_2); +} + +static void +l3ni1_t308_2(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + pc->st->l3.l3l4(pc->st, CC_RELEASE_ERR, pc); + ni1_release_l3_process(pc); +} + +static void +l3ni1_t318(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + pc->para.cause = 102; /* Timer expiry */ + pc->para.loc = 0; /* local */ + pc->st->l3.l3l4(pc->st, CC_RESUME_ERR, pc); + newl3state(pc, 19); + l3ni1_message(pc, MT_RELEASE); + L3AddTimer(&pc->timer, T308, CC_T308_1); +} + +static void +l3ni1_t319(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + pc->para.cause = 102; /* Timer expiry */ + pc->para.loc = 0; /* local */ + pc->st->l3.l3l4(pc->st, CC_SUSPEND_ERR, pc); + newl3state(pc, 10); +} + +static void +l3ni1_restart(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + pc->st->l3.l3l4(pc->st, CC_RELEASE | INDICATION, pc); + ni1_release_l3_process(pc); +} + +static void +l3ni1_status(struct l3_process *pc, u_char pr, void *arg) +{ + u_char *p; + struct sk_buff *skb = arg; + int ret; + u_char cause = 0, callState = 0; + + if ((ret = l3ni1_get_cause(pc, skb))) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "STATUS get_cause ret(%d)",ret); + if (ret < 0) + cause = 96; + else if (ret > 0) + cause = 100; + } + if ((p = findie(skb->data, skb->len, IE_CALL_STATE, 0))) { + p++; + if (1 == *p++) { + callState = *p; + if (!ie_in_set(pc, *p, l3_valid_states)) + cause = 100; + } else + cause = 100; + } else + cause = 96; + if (!cause) { /* no error before */ + ret = check_infoelements(pc, skb, ie_STATUS); + if (ERR_IE_COMPREHENSION == ret) + cause = 96; + else if (ERR_IE_UNRECOGNIZED == ret) + cause = 99; + } + if (cause) { + u_char tmp; + + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "STATUS error(%d/%d)",ret,cause); + tmp = pc->para.cause; + pc->para.cause = cause; + l3ni1_status_send(pc, 0, NULL); + if (cause == 99) + pc->para.cause = tmp; + else + return; + } + cause = pc->para.cause; + if (((cause & 0x7f) == 111) && (callState == 0)) { + /* ETS 300-104 7.6.1, 8.6.1, 10.6.1... + * if received MT_STATUS with cause == 111 and call + * state == 0, then we must set down layer 3 + */ + pc->st->l3.l3l4(pc->st, CC_RELEASE | INDICATION, pc); + newl3state(pc, 0); + ni1_release_l3_process(pc); + } +} + +static void +l3ni1_facility(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int ret; + + ret = check_infoelements(pc, skb, ie_FACILITY); + l3ni1_std_ie_err(pc, ret); + { + u_char *p; + if ((p = findie(skb->data, skb->len, IE_FACILITY, 0))) + l3ni1_parse_facility(pc->st, pc, pc->callref, p); + } +} + +static void +l3ni1_suspend_req(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb; + u_char tmp[32]; + u_char *p = tmp; + u_char i, l; + u_char *msg = pc->chan->setup.phone; + + MsgHead(p, pc->callref, MT_SUSPEND); + l = *msg++; + if (l && (l <= 10)) { /* Max length 10 octets */ + *p++ = IE_CALL_ID; + *p++ = l; + for (i = 0; i < l; i++) + *p++ = *msg++; + } else if (l) { + l3_debug(pc->st, "SUS wrong CALL_ID len %d", l); + return; + } + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + memcpy(skb_put(skb, l), tmp, l); + l3_msg(pc->st, DL_DATA | REQUEST, skb); + newl3state(pc, 15); + L3AddTimer(&pc->timer, T319, CC_T319); +} + +static void +l3ni1_suspend_ack(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int ret; + + L3DelTimer(&pc->timer); + newl3state(pc, 0); + pc->para.cause = NO_CAUSE; + pc->st->l3.l3l4(pc->st, CC_SUSPEND | CONFIRM, pc); + /* We don't handle suspend_ack for IE errors now */ + if ((ret = check_infoelements(pc, skb, ie_SUSPEND_ACKNOWLEDGE))) + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "SUSPACK check ie(%d)",ret); + ni1_release_l3_process(pc); +} + +static void +l3ni1_suspend_rej(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int ret; + + if ((ret = l3ni1_get_cause(pc, skb))) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "SUSP_REJ get_cause ret(%d)",ret); + if (ret < 0) + pc->para.cause = 96; + else + pc->para.cause = 100; + l3ni1_status_send(pc, pr, NULL); + return; + } + ret = check_infoelements(pc, skb, ie_SUSPEND_REJECT); + if (ERR_IE_COMPREHENSION == ret) { + l3ni1_std_ie_err(pc, ret); + return; + } + L3DelTimer(&pc->timer); + pc->st->l3.l3l4(pc->st, CC_SUSPEND_ERR, pc); + newl3state(pc, 10); + if (ret) /* STATUS for none mandatory IE errors after actions are taken */ + l3ni1_std_ie_err(pc, ret); +} + +static void +l3ni1_resume_req(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb; + u_char tmp[32]; + u_char *p = tmp; + u_char i, l; + u_char *msg = pc->para.setup.phone; + + MsgHead(p, pc->callref, MT_RESUME); + + l = *msg++; + if (l && (l <= 10)) { /* Max length 10 octets */ + *p++ = IE_CALL_ID; + *p++ = l; + for (i = 0; i < l; i++) + *p++ = *msg++; + } else if (l) { + l3_debug(pc->st, "RES wrong CALL_ID len %d", l); + return; + } + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + memcpy(skb_put(skb, l), tmp, l); + l3_msg(pc->st, DL_DATA | REQUEST, skb); + newl3state(pc, 17); + L3AddTimer(&pc->timer, T318, CC_T318); +} + +static void +l3ni1_resume_ack(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int id, ret; + + if ((id = l3ni1_get_channel_id(pc, skb)) > 0) { + if ((0 == id) || ((3 == id) && (0x10 == pc->para.moderate))) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "resume ack with wrong chid %x", id); + pc->para.cause = 100; + l3ni1_status_send(pc, pr, NULL); + return; + } + pc->para.bchannel = id; + } else if (1 == pc->state) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "resume ack without chid (ret %d)", id); + pc->para.cause = 96; + l3ni1_status_send(pc, pr, NULL); + return; + } + ret = check_infoelements(pc, skb, ie_RESUME_ACKNOWLEDGE); + if (ERR_IE_COMPREHENSION == ret) { + l3ni1_std_ie_err(pc, ret); + return; + } + L3DelTimer(&pc->timer); + pc->st->l3.l3l4(pc->st, CC_RESUME | CONFIRM, pc); + newl3state(pc, 10); + if (ret) /* STATUS for none mandatory IE errors after actions are taken */ + l3ni1_std_ie_err(pc, ret); +} + +static void +l3ni1_resume_rej(struct l3_process *pc, u_char pr, void *arg) +{ + struct sk_buff *skb = arg; + int ret; + + if ((ret = l3ni1_get_cause(pc, skb))) { + if (pc->debug & L3_DEB_WARN) + l3_debug(pc->st, "RES_REJ get_cause ret(%d)",ret); + if (ret < 0) + pc->para.cause = 96; + else + pc->para.cause = 100; + l3ni1_status_send(pc, pr, NULL); + return; + } + ret = check_infoelements(pc, skb, ie_RESUME_REJECT); + if (ERR_IE_COMPREHENSION == ret) { + l3ni1_std_ie_err(pc, ret); + return; + } + L3DelTimer(&pc->timer); + pc->st->l3.l3l4(pc->st, CC_RESUME_ERR, pc); + newl3state(pc, 0); + if (ret) /* STATUS for none mandatory IE errors after actions are taken */ + l3ni1_std_ie_err(pc, ret); + ni1_release_l3_process(pc); +} + +static void +l3ni1_global_restart(struct l3_process *pc, u_char pr, void *arg) +{ + u_char tmp[32]; + u_char *p; + u_char ri, ch = 0, chan = 0; + int l; + struct sk_buff *skb = arg; + struct l3_process *up; + + newl3state(pc, 2); + L3DelTimer(&pc->timer); + p = skb->data; + if ((p = findie(p, skb->len, IE_RESTART_IND, 0))) { + ri = p[2]; + l3_debug(pc->st, "Restart %x", ri); + } else { + l3_debug(pc->st, "Restart without restart IE"); + ri = 0x86; + } + p = skb->data; + if ((p = findie(p, skb->len, IE_CHANNEL_ID, 0))) { + chan = p[2] & 3; + ch = p[2]; + if (pc->st->l3.debug) + l3_debug(pc->st, "Restart for channel %d", chan); + } + newl3state(pc, 2); + up = pc->st->l3.proc; + while (up) { + if ((ri & 7) == 7) + up->st->lli.l4l3(up->st, CC_RESTART | REQUEST, up); + else if (up->para.bchannel == chan) + up->st->lli.l4l3(up->st, CC_RESTART | REQUEST, up); + + up = up->next; + } + p = tmp; + MsgHead(p, pc->callref, MT_RESTART_ACKNOWLEDGE); + if (chan) { + *p++ = IE_CHANNEL_ID; + *p++ = 1; + *p++ = ch | 0x80; + } + *p++ = 0x79; /* RESTART Ind */ + *p++ = 1; + *p++ = ri; + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + memcpy(skb_put(skb, l), tmp, l); + newl3state(pc, 0); + l3_msg(pc->st, DL_DATA | REQUEST, skb); +} + +static void +l3ni1_dl_reset(struct l3_process *pc, u_char pr, void *arg) +{ + pc->para.cause = 0x29; /* Temporary failure */ + pc->para.loc = 0; + l3ni1_disconnect_req(pc, pr, NULL); + pc->st->l3.l3l4(pc->st, CC_SETUP_ERR, pc); +} + +static void +l3ni1_dl_release(struct l3_process *pc, u_char pr, void *arg) +{ + newl3state(pc, 0); + pc->para.cause = 0x1b; /* Destination out of order */ + pc->para.loc = 0; + pc->st->l3.l3l4(pc->st, CC_RELEASE | INDICATION, pc); + release_l3_process(pc); +} + +static void +l3ni1_dl_reestablish(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + L3AddTimer(&pc->timer, T309, CC_T309); + l3_msg(pc->st, DL_ESTABLISH | REQUEST, NULL); +} + +static void +l3ni1_dl_reest_status(struct l3_process *pc, u_char pr, void *arg) +{ + L3DelTimer(&pc->timer); + + pc->para.cause = 0x1F; /* normal, unspecified */ + l3ni1_status_send(pc, 0, NULL); +} + +static void l3ni1_SendSpid( struct l3_process *pc, u_char pr, struct sk_buff *skb, int iNewState ) +{ + u_char * p; + char * pSPID; + struct Channel * pChan = pc->st->lli.userdata; + int l; + + if ( skb ) + dev_kfree_skb( skb); + + if ( !( pSPID = strchr( pChan->setup.eazmsn, ':' ) ) ) + { + printk( KERN_ERR "SPID not supplied in EAZMSN %s\n", pChan->setup.eazmsn ); + newl3state( pc, 0 ); + pc->st->l3.l3l2( pc->st, DL_RELEASE | REQUEST, NULL ); + return; + } + + l = strlen( ++pSPID ); + if ( !( skb = l3_alloc_skb( 5+l ) ) ) + { + printk( KERN_ERR "HiSax can't get memory to send SPID\n" ); + return; + } + + p = skb_put( skb, 5 ); + *p++ = PROTO_DIS_EURO; + *p++ = 0; + *p++ = MT_INFORMATION; + *p++ = IE_SPID; + *p++ = l; + + memcpy( skb_put( skb, l ), pSPID, l ); + + newl3state( pc, iNewState ); + + L3DelTimer( &pc->timer ); + L3AddTimer( &pc->timer, TSPID, CC_TSPID ); + + pc->st->l3.l3l2( pc->st, DL_DATA | REQUEST, skb ); +} + +static void l3ni1_spid_send( struct l3_process *pc, u_char pr, void *arg ) +{ + l3ni1_SendSpid( pc, pr, arg, 20 ); +} + +void l3ni1_spid_epid( struct l3_process *pc, u_char pr, void *arg ) +{ + struct sk_buff *skb = arg; + + if ( skb->data[ 1 ] == 0 ) + if ( skb->data[ 3 ] == IE_ENDPOINT_ID ) + { + L3DelTimer( &pc->timer ); + newl3state( pc, 0 ); + l3_msg( pc->st, DL_ESTABLISH | CONFIRM, NULL ); + } + dev_kfree_skb( skb); +} + +static void l3ni1_spid_tout( struct l3_process *pc, u_char pr, void *arg ) +{ + if ( pc->state < 22 ) + l3ni1_SendSpid( pc, pr, arg, pc->state+1 ); + else + { + L3DelTimer( &pc->timer ); + dev_kfree_skb( arg); + + printk( KERN_ERR "SPID not accepted\n" ); + newl3state( pc, 0 ); + pc->st->l3.l3l2( pc->st, DL_RELEASE | REQUEST, NULL ); + } +} + +/* *INDENT-OFF* */ +static struct stateentry downstatelist[] = +{ + {SBIT(0), + CC_SETUP | REQUEST, l3ni1_setup_req}, + {SBIT(0), + CC_RESUME | REQUEST, l3ni1_resume_req}, + {SBIT(1) | SBIT(2) | SBIT(3) | SBIT(4) | SBIT(6) | SBIT(7) | SBIT(8) | SBIT(9) | SBIT(10) | SBIT(25), + CC_DISCONNECT | REQUEST, l3ni1_disconnect_req}, + {SBIT(12), + CC_RELEASE | REQUEST, l3ni1_release_req}, + {ALL_STATES, + CC_RESTART | REQUEST, l3ni1_restart}, + {SBIT(6) | SBIT(25), + CC_IGNORE | REQUEST, l3ni1_reset}, + {SBIT(6) | SBIT(25), + CC_REJECT | REQUEST, l3ni1_reject_req}, + {SBIT(6) | SBIT(25), + CC_PROCEED_SEND | REQUEST, l3ni1_proceed_req}, + {SBIT(6), + CC_MORE_INFO | REQUEST, l3ni1_setup_ack_req}, + {SBIT(25), + CC_MORE_INFO | REQUEST, l3ni1_dummy}, + {SBIT(6) | SBIT(9) | SBIT(25), + CC_ALERTING | REQUEST, l3ni1_alert_req}, + {SBIT(6) | SBIT(7) | SBIT(9) | SBIT(25), + CC_SETUP | RESPONSE, l3ni1_setup_rsp}, + {SBIT(10), + CC_SUSPEND | REQUEST, l3ni1_suspend_req}, + {SBIT(7) | SBIT(9) | SBIT(25), + CC_REDIR | REQUEST, l3ni1_redir_req}, + {SBIT(6), + CC_REDIR | REQUEST, l3ni1_redir_req_early}, + {SBIT(9) | SBIT(25), + CC_DISCONNECT | REQUEST, l3ni1_disconnect_req}, + {SBIT(25), + CC_T302, l3ni1_t302}, + {SBIT(1), + CC_T303, l3ni1_t303}, + {SBIT(2), + CC_T304, l3ni1_t304}, + {SBIT(3), + CC_T310, l3ni1_t310}, + {SBIT(8), + CC_T313, l3ni1_t313}, + {SBIT(11), + CC_T305, l3ni1_t305}, + {SBIT(15), + CC_T319, l3ni1_t319}, + {SBIT(17), + CC_T318, l3ni1_t318}, + {SBIT(19), + CC_T308_1, l3ni1_t308_1}, + {SBIT(19), + CC_T308_2, l3ni1_t308_2}, + {SBIT(10), + CC_T309, l3ni1_dl_release}, + { SBIT( 20 ) | SBIT( 21 ) | SBIT( 22 ), + CC_TSPID, l3ni1_spid_tout }, +}; + +#define DOWNSLLEN \ + (sizeof(downstatelist) / sizeof(struct stateentry)) + +static struct stateentry datastatelist[] = +{ + {ALL_STATES, + MT_STATUS_ENQUIRY, l3ni1_status_enq}, + {ALL_STATES, + MT_FACILITY, l3ni1_facility}, + {SBIT(19), + MT_STATUS, l3ni1_release_ind}, + {ALL_STATES, + MT_STATUS, l3ni1_status}, + {SBIT(0), + MT_SETUP, l3ni1_setup}, + {SBIT(6) | SBIT(7) | SBIT(8) | SBIT(9) | SBIT(10) | SBIT(11) | SBIT(12) | + SBIT(15) | SBIT(17) | SBIT(19) | SBIT(25), + MT_SETUP, l3ni1_dummy}, + {SBIT(1) | SBIT(2), + MT_CALL_PROCEEDING, l3ni1_call_proc}, + {SBIT(1), + MT_SETUP_ACKNOWLEDGE, l3ni1_setup_ack}, + {SBIT(2) | SBIT(3), + MT_ALERTING, l3ni1_alerting}, + {SBIT(2) | SBIT(3), + MT_PROGRESS, l3ni1_progress}, + {SBIT(2) | SBIT(3) | SBIT(4) | SBIT(7) | SBIT(8) | SBIT(9) | SBIT(10) | + SBIT(11) | SBIT(12) | SBIT(15) | SBIT(17) | SBIT(19) | SBIT(25), + MT_INFORMATION, l3ni1_information}, + {SBIT(10) | SBIT(11) | SBIT(15), + MT_NOTIFY, l3ni1_notify}, + {SBIT(0) | SBIT(1) | SBIT(2) | SBIT(3) | SBIT(4) | SBIT(7) | SBIT(8) | SBIT(10) | + SBIT(11) | SBIT(12) | SBIT(15) | SBIT(17) | SBIT(19) | SBIT(25), + MT_RELEASE_COMPLETE, l3ni1_release_cmpl}, + {SBIT(1) | SBIT(2) | SBIT(3) | SBIT(4) | SBIT(7) | SBIT(8) | SBIT(9) | SBIT(10) | SBIT(11) | SBIT(12) | SBIT(15) | SBIT(17) | SBIT(25), + MT_RELEASE, l3ni1_release}, + {SBIT(19), MT_RELEASE, l3ni1_release_ind}, + {SBIT(1) | SBIT(2) | SBIT(3) | SBIT(4) | SBIT(7) | SBIT(8) | SBIT(9) | SBIT(10) | SBIT(11) | SBIT(15) | SBIT(17) | SBIT(25), + MT_DISCONNECT, l3ni1_disconnect}, + {SBIT(19), + MT_DISCONNECT, l3ni1_dummy}, + {SBIT(1) | SBIT(2) | SBIT(3) | SBIT(4), + MT_CONNECT, l3ni1_connect}, + {SBIT(8), + MT_CONNECT_ACKNOWLEDGE, l3ni1_connect_ack}, + {SBIT(15), + MT_SUSPEND_ACKNOWLEDGE, l3ni1_suspend_ack}, + {SBIT(15), + MT_SUSPEND_REJECT, l3ni1_suspend_rej}, + {SBIT(17), + MT_RESUME_ACKNOWLEDGE, l3ni1_resume_ack}, + {SBIT(17), + MT_RESUME_REJECT, l3ni1_resume_rej}, +}; + +#define DATASLLEN \ + (sizeof(datastatelist) / sizeof(struct stateentry)) + +static struct stateentry globalmes_list[] = +{ + {ALL_STATES, + MT_STATUS, l3ni1_status}, + {SBIT(0), + MT_RESTART, l3ni1_global_restart}, +/* {SBIT(1), + MT_RESTART_ACKNOWLEDGE, l3ni1_restart_ack}, +*/ + { SBIT( 0 ), MT_DL_ESTABLISHED, l3ni1_spid_send }, + { SBIT( 20 ) | SBIT( 21 ) | SBIT( 22 ), MT_INFORMATION, l3ni1_spid_epid }, +}; +#define GLOBALM_LEN \ + (sizeof(globalmes_list) / sizeof(struct stateentry)) + +static struct stateentry manstatelist[] = +{ + {SBIT(2), + DL_ESTABLISH | INDICATION, l3ni1_dl_reset}, + {SBIT(10), + DL_ESTABLISH | CONFIRM, l3ni1_dl_reest_status}, + {SBIT(10), + DL_RELEASE | INDICATION, l3ni1_dl_reestablish}, + {ALL_STATES, + DL_RELEASE | INDICATION, l3ni1_dl_release}, +}; + +#define MANSLLEN \ + (sizeof(manstatelist) / sizeof(struct stateentry)) +/* *INDENT-ON* */ + + +static void +global_handler(struct PStack *st, int mt, struct sk_buff *skb) +{ + u_char tmp[16]; + u_char *p = tmp; + int l; + int i; + struct l3_process *proc = st->l3.global; + + if ( skb ) + proc->callref = skb->data[2]; /* cr flag */ + else + proc->callref = 0; + for (i = 0; i < GLOBALM_LEN; i++) + if ((mt == globalmes_list[i].primitive) && + ((1 << proc->state) & globalmes_list[i].state)) + break; + if (i == GLOBALM_LEN) { + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "ni1 global state %d mt %x unhandled", + proc->state, mt); + } + MsgHead(p, proc->callref, MT_STATUS); + *p++ = IE_CAUSE; + *p++ = 0x2; + *p++ = 0x80; + *p++ = 81 |0x80; /* invalid cr */ + *p++ = 0x14; /* CallState */ + *p++ = 0x1; + *p++ = proc->state & 0x3f; + l = p - tmp; + if (!(skb = l3_alloc_skb(l))) + return; + memcpy(skb_put(skb, l), tmp, l); + l3_msg(proc->st, DL_DATA | REQUEST, skb); + } else { + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "ni1 global %d mt %x", + proc->state, mt); + } + globalmes_list[i].rout(proc, mt, skb); + } +} + +static void +ni1up(struct PStack *st, int pr, void *arg) +{ + int i, mt, cr, cause, callState; + char *ptr; + u_char *p; + struct sk_buff *skb = arg; + struct l3_process *proc; + + switch (pr) { + case (DL_DATA | INDICATION): + case (DL_UNIT_DATA | INDICATION): + break; + case (DL_ESTABLISH | INDICATION): + case (DL_RELEASE | INDICATION): + case (DL_RELEASE | CONFIRM): + l3_msg(st, pr, arg); + return; + break; + + case (DL_ESTABLISH | CONFIRM): + global_handler( st, MT_DL_ESTABLISHED, NULL ); + return; + + default: + printk(KERN_ERR "HiSax ni1up unknown pr=%04x\n", pr); + return; + } + if (skb->len < 3) { + l3_debug(st, "ni1up frame too short(%d)", skb->len); + dev_kfree_skb(skb); + return; + } + + if (skb->data[0] != PROTO_DIS_EURO) { + if (st->l3.debug & L3_DEB_PROTERR) { + l3_debug(st, "ni1up%sunexpected discriminator %x message len %d", + (pr == (DL_DATA | INDICATION)) ? " " : "(broadcast) ", + skb->data[0], skb->len); + } + dev_kfree_skb(skb); + return; + } + cr = getcallref(skb->data); + if (skb->len < ((skb->data[1] & 0x0f) + 3)) { + l3_debug(st, "ni1up frame too short(%d)", skb->len); + dev_kfree_skb(skb); + return; + } + mt = skb->data[skb->data[1] + 2]; + if (st->l3.debug & L3_DEB_STATE) + l3_debug(st, "ni1up cr %d", cr); + if (cr == -2) { /* wrong Callref */ + if (st->l3.debug & L3_DEB_WARN) + l3_debug(st, "ni1up wrong Callref"); + dev_kfree_skb(skb); + return; + } else if (cr == -1) { /* Dummy Callref */ + if (mt == MT_FACILITY) + { + if ((p = findie(skb->data, skb->len, IE_FACILITY, 0))) { + l3ni1_parse_facility(st, NULL, + (pr == (DL_DATA | INDICATION)) ? -1 : -2, p); + dev_kfree_skb(skb); + return; + } + } + else + { + global_handler(st, mt, skb); + return; + } + + if (st->l3.debug & L3_DEB_WARN) + l3_debug(st, "ni1up dummy Callref (no facility msg or ie)"); + dev_kfree_skb(skb); + return; + } else if ((((skb->data[1] & 0x0f) == 1) && (0==(cr & 0x7f))) || + (((skb->data[1] & 0x0f) == 2) && (0==(cr & 0x7fff)))) { /* Global CallRef */ + if (st->l3.debug & L3_DEB_STATE) + l3_debug(st, "ni1up Global CallRef"); + global_handler(st, mt, skb); + dev_kfree_skb(skb); + return; + } else if (!(proc = getl3proc(st, cr))) { + /* No transaction process exist, that means no call with + * this callreference is active + */ + if (mt == MT_SETUP) { + /* Setup creates a new transaction process */ + if (skb->data[2] & 0x80) { + /* Setup with wrong CREF flag */ + if (st->l3.debug & L3_DEB_STATE) + l3_debug(st, "ni1up wrong CRef flag"); + dev_kfree_skb(skb); + return; + } + if (!(proc = ni1_new_l3_process(st, cr))) { + /* May be to answer with RELEASE_COMPLETE and + * CAUSE 0x2f "Resource unavailable", but this + * need a new_l3_process too ... arghh + */ + dev_kfree_skb(skb); + return; + } + } else if (mt == MT_STATUS) { + cause = 0; + if ((ptr = findie(skb->data, skb->len, IE_CAUSE, 0)) != NULL) { + ptr++; + if (*ptr++ == 2) + ptr++; + cause = *ptr & 0x7f; + } + callState = 0; + if ((ptr = findie(skb->data, skb->len, IE_CALL_STATE, 0)) != NULL) { + ptr++; + if (*ptr++ == 2) + ptr++; + callState = *ptr; + } + /* ETS 300-104 part 2.4.1 + * if setup has not been made and a message type + * MT_STATUS is received with call state == 0, + * we must send nothing + */ + if (callState != 0) { + /* ETS 300-104 part 2.4.2 + * if setup has not been made and a message type + * MT_STATUS is received with call state != 0, + * we must send MT_RELEASE_COMPLETE cause 101 + */ + if ((proc = ni1_new_l3_process(st, cr))) { + proc->para.cause = 101; + l3ni1_msg_without_setup(proc, 0, NULL); + } + } + dev_kfree_skb(skb); + return; + } else if (mt == MT_RELEASE_COMPLETE) { + dev_kfree_skb(skb); + return; + } else { + /* ETS 300-104 part 2 + * if setup has not been made and a message type + * (except MT_SETUP and RELEASE_COMPLETE) is received, + * we must send MT_RELEASE_COMPLETE cause 81 */ + dev_kfree_skb(skb); + if ((proc = ni1_new_l3_process(st, cr))) { + proc->para.cause = 81; + l3ni1_msg_without_setup(proc, 0, NULL); + } + return; + } + } + if (l3ni1_check_messagetype_validity(proc, mt, skb)) { + dev_kfree_skb(skb); + return; + } + if ((p = findie(skb->data, skb->len, IE_DISPLAY, 0)) != NULL) + l3ni1_deliver_display(proc, pr, p); /* Display IE included */ + for (i = 0; i < DATASLLEN; i++) + if ((mt == datastatelist[i].primitive) && + ((1 << proc->state) & datastatelist[i].state)) + break; + if (i == DATASLLEN) { + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "ni1up%sstate %d mt %#x unhandled", + (pr == (DL_DATA | INDICATION)) ? " " : "(broadcast) ", + proc->state, mt); + } + if ((MT_RELEASE_COMPLETE != mt) && (MT_RELEASE != mt)) { + proc->para.cause = 101; + l3ni1_status_send(proc, pr, skb); + } + } else { + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "ni1up%sstate %d mt %x", + (pr == (DL_DATA | INDICATION)) ? " " : "(broadcast) ", + proc->state, mt); + } + datastatelist[i].rout(proc, pr, skb); + } + dev_kfree_skb(skb); + return; +} + +static void +ni1down(struct PStack *st, int pr, void *arg) +{ + int i, cr; + struct l3_process *proc; + struct Channel *chan; + + if ((DL_ESTABLISH | REQUEST) == pr) { + l3_msg(st, pr, NULL); + return; + } else if (((CC_SETUP | REQUEST) == pr) || ((CC_RESUME | REQUEST) == pr)) { + chan = arg; + cr = newcallref(); + cr |= 0x80; + if ((proc = ni1_new_l3_process(st, cr))) { + proc->chan = chan; + chan->proc = proc; + memcpy(&proc->para.setup, &chan->setup, sizeof(setup_parm)); + proc->callref = cr; + } + } else { + proc = arg; + } + if (!proc) { + printk(KERN_ERR "HiSax ni1down without proc pr=%04x\n", pr); + return; + } + + if ( pr == (CC_TNI1_IO | REQUEST)) { + l3ni1_io_timer(proc); /* timer expires */ + return; + } + + for (i = 0; i < DOWNSLLEN; i++) + if ((pr == downstatelist[i].primitive) && + ((1 << proc->state) & downstatelist[i].state)) + break; + if (i == DOWNSLLEN) { + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "ni1down state %d prim %#x unhandled", + proc->state, pr); + } + } else { + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "ni1down state %d prim %#x", + proc->state, pr); + } + downstatelist[i].rout(proc, pr, arg); + } +} + +static void +ni1man(struct PStack *st, int pr, void *arg) +{ + int i; + struct l3_process *proc = arg; + + if (!proc) { + printk(KERN_ERR "HiSax ni1man without proc pr=%04x\n", pr); + return; + } + for (i = 0; i < MANSLLEN; i++) + if ((pr == manstatelist[i].primitive) && + ((1 << proc->state) & manstatelist[i].state)) + break; + if (i == MANSLLEN) { + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "cr %d ni1man state %d prim %#x unhandled", + proc->callref & 0x7f, proc->state, pr); + } + } else { + if (st->l3.debug & L3_DEB_STATE) { + l3_debug(st, "cr %d ni1man state %d prim %#x", + proc->callref & 0x7f, proc->state, pr); + } + manstatelist[i].rout(proc, pr, arg); + } +} + +void +setstack_ni1(struct PStack *st) +{ + char tmp[64]; + int i; + + st->lli.l4l3 = ni1down; + st->lli.l4l3_proto = l3ni1_cmd_global; + st->l2.l2l3 = ni1up; + st->l3.l3ml3 = ni1man; + st->l3.N303 = 1; + st->prot.ni1.last_invoke_id = 0; + st->prot.ni1.invoke_used[0] = 1; /* Bit 0 must always be set to 1 */ + i = 1; + while (i < 32) + st->prot.ni1.invoke_used[i++] = 0; + + if (!(st->l3.global = kmalloc(sizeof(struct l3_process), GFP_ATOMIC))) { + printk(KERN_ERR "HiSax can't get memory for ni1 global CR\n"); + } else { + st->l3.global->state = 0; + st->l3.global->callref = 0; + st->l3.global->next = NULL; + st->l3.global->debug = L3_DEB_WARN; + st->l3.global->st = st; + st->l3.global->N303 = 1; + st->l3.global->prot.ni1.invoke_id = 0; + + L3InitTimer(st->l3.global, &st->l3.global->timer); + } + strcpy(tmp, ni1_revision); + printk(KERN_INFO "HiSax: National ISDN-1 Rev. %s\n", HiSax_getrev(tmp)); +} diff --git a/drivers/isdn/hisax/l3ni1.h b/drivers/isdn/hisax/l3ni1.h new file mode 100644 index 000000000000..4066da2fe5a2 --- /dev/null +++ b/drivers/isdn/hisax/l3ni1.h @@ -0,0 +1,136 @@ +/* $Id: l3ni1.h,v 2.3.6.2 2001/09/23 22:24:50 kai Exp $ + * + * NI1 D-channel protocol + * + * Author Matt Henderson & Guy Ellis + * Copyright by Traverse Technologies Pty Ltd, www.travers.com.au + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * 2000.6.6 Initial implementation of routines for US NI1 + * Layer 3 protocol based on the EURO/DSS1 D-channel protocol + * driver written by Karsten Keil et al. Thanks also for the + * code provided by Ragnar Paulson. + * + */ + +#ifndef l3ni1_process + +#define T302 15000 +#define T303 4000 +#define T304 30000 +#define T305 30000 +#define T308 4000 +/* for layer 1 certification T309 < layer1 T3 (e.g. 4000) */ +/* This makes some tests easier and quicker */ +#define T309 40000 +#define T310 30000 +#define T313 4000 +#define T318 4000 +#define T319 4000 +#define TSPID 5000 /* was 2000 - Guy Ellis */ + +/* + * 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 MT_DL_ESTABLISHED 0xfe + +#define IE_SEGMENT 0x00 +#define IE_BEARER 0x04 +#define IE_CAUSE 0x08 +#define IE_CALL_ID 0x10 +#define IE_CALL_STATE 0x14 +#define IE_CHANNEL_ID 0x18 +#define IE_FACILITY 0x1c +#define IE_PROGRESS 0x1e +#define IE_NET_FAC 0x20 +#define IE_NOTIFY 0x27 +#define IE_DISPLAY 0x28 +#define IE_DATE 0x29 +#define IE_KEYPAD 0x2c +#define IE_SIGNAL 0x34 +#define IE_SPID 0x3a +#define IE_ENDPOINT_ID 0x3b +#define IE_INFORATE 0x40 +#define IE_E2E_TDELAY 0x42 +#define IE_TDELAY_SEL 0x43 +#define IE_PACK_BINPARA 0x44 +#define IE_PACK_WINSIZE 0x45 +#define IE_PACK_SIZE 0x46 +#define IE_CUG 0x47 +#define IE_REV_CHARGE 0x4a +#define IE_CONNECT_PN 0x4c +#define IE_CONNECT_SUB 0x4d +#define IE_CALLING_PN 0x6c +#define IE_CALLING_SUB 0x6d +#define IE_CALLED_PN 0x70 +#define IE_CALLED_SUB 0x71 +#define IE_REDIR_NR 0x74 +#define IE_TRANS_SEL 0x78 +#define IE_RESTART_IND 0x79 +#define IE_LLC 0x7c +#define IE_HLC 0x7d +#define IE_USER_USER 0x7e +#define IE_ESCAPE 0x7f +#define IE_SHIFT 0x90 +#define IE_MORE_DATA 0xa0 +#define IE_COMPLETE 0xa1 +#define IE_CONGESTION 0xb0 +#define IE_REPEAT 0xd0 + +#define IE_MANDATORY 0x0100 +/* mandatory not in every case */ +#define IE_MANDATORY_1 0x0200 + +#define ERR_IE_COMPREHENSION 1 +#define ERR_IE_UNRECOGNIZED -1 +#define ERR_IE_LENGTH -2 +#define ERR_IE_SEQUENCE -3 + +#else /* only l3ni1_process */ + +/* l3ni1 specific data in l3 process */ +typedef struct + { unsigned char invoke_id; /* used invoke id in remote ops, 0 = not active */ + ulong ll_id; /* remebered ll id */ + u8 remote_operation; /* handled remote operation, 0 = not active */ + int proc; /* rememered procedure */ + ulong remote_result; /* result of remote operation for statcallb */ + char uus1_data[35]; /* data send during alerting or disconnect */ + } ni1_proc_priv; + +/* l3dni1 specific data in protocol stack */ +typedef struct + { unsigned char last_invoke_id; /* last used value for invoking */ + unsigned char invoke_used[32]; /* 256 bits for 256 values */ + } ni1_stk_priv; + +#endif /* only l3dni1_process */ diff --git a/drivers/isdn/hisax/lmgr.c b/drivers/isdn/hisax/lmgr.c new file mode 100644 index 000000000000..d4f86d654de0 --- /dev/null +++ b/drivers/isdn/hisax/lmgr.c @@ -0,0 +1,50 @@ +/* $Id: lmgr.c,v 1.7.6.2 2001/09/23 22:24:50 kai Exp $ + * + * Layermanagement module + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include "hisax.h" + +static void +error_handling_dchan(struct PStack *st, int Error) +{ + switch (Error) { + case 'C': + case 'D': + case 'G': + case 'H': + st->l2.l2tei(st, MDL_ERROR | REQUEST, NULL); + break; + } +} + +static void +hisax_manager(struct PStack *st, int pr, void *arg) +{ + long Code; + + switch (pr) { + case (MDL_ERROR | INDICATION): + Code = (long) arg; + HiSax_putstatus(st->l1.hardware, "manager: MDL_ERROR", + " %c %s", (char)Code, + test_bit(FLG_LAPD, &st->l2.flag) ? + "D-channel" : "B-channel"); + if (test_bit(FLG_LAPD, &st->l2.flag)) + error_handling_dchan(st, Code); + break; + } +} + +void +setstack_manager(struct PStack *st) +{ + st->ma.layer = hisax_manager; +} diff --git a/drivers/isdn/hisax/mic.c b/drivers/isdn/hisax/mic.c new file mode 100644 index 000000000000..3ac4484a4886 --- /dev/null +++ b/drivers/isdn/hisax/mic.c @@ -0,0 +1,239 @@ +/* $Id: mic.c,v 1.12.2.4 2004/01/13 23:48:39 keil Exp $ + * + * low level stuff for mic cards + * + * Author Stephan von Krawczynski + * Copyright by Stephan von Krawczynski <skraw@ithnet.com> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/init.h> +#include "hisax.h" +#include "isac.h" +#include "hscx.h" +#include "isdnl1.h" + +extern const char *CardType[]; + +const char *mic_revision = "$Revision: 1.12.2.4 $"; + +#define byteout(addr,val) outb(val,addr) +#define bytein(addr) inb(addr) + +#define MIC_ISAC 2 +#define MIC_HSCX 1 +#define MIC_ADR 7 + +/* CARD_ADR (Write) */ +#define MIC_RESET 0x3 /* same as DOS driver */ + +static inline u_char +readreg(unsigned int ale, unsigned int adr, u_char off) +{ + register u_char ret; + + byteout(ale, off); + ret = bytein(adr); + return (ret); +} + +static inline void +readfifo(unsigned int ale, unsigned int adr, u_char off, u_char * data, int size) +{ + byteout(ale, off); + insb(adr, data, size); +} + + +static inline void +writereg(unsigned int ale, unsigned int adr, u_char off, u_char data) +{ + byteout(ale, off); + byteout(adr, data); +} + +static inline void +writefifo(unsigned int ale, unsigned int adr, u_char off, u_char * data, int size) +{ + byteout(ale, off); + outsb(adr, data, size); +} + +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + return (readreg(cs->hw.mic.adr, cs->hw.mic.isac, offset)); +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writereg(cs->hw.mic.adr, cs->hw.mic.isac, offset, value); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + readfifo(cs->hw.mic.adr, cs->hw.mic.isac, 0, data, size); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + writefifo(cs->hw.mic.adr, cs->hw.mic.isac, 0, data, size); +} + +static u_char +ReadHSCX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + return (readreg(cs->hw.mic.adr, + cs->hw.mic.hscx, offset + (hscx ? 0x40 : 0))); +} + +static void +WriteHSCX(struct IsdnCardState *cs, int hscx, u_char offset, u_char value) +{ + writereg(cs->hw.mic.adr, + cs->hw.mic.hscx, offset + (hscx ? 0x40 : 0), value); +} + +/* + * fast interrupt HSCX stuff goes here + */ + +#define READHSCX(cs, nr, reg) readreg(cs->hw.mic.adr, \ + cs->hw.mic.hscx, reg + (nr ? 0x40 : 0)) +#define WRITEHSCX(cs, nr, reg, data) writereg(cs->hw.mic.adr, \ + cs->hw.mic.hscx, reg + (nr ? 0x40 : 0), data) + +#define READHSCXFIFO(cs, nr, ptr, cnt) readfifo(cs->hw.mic.adr, \ + cs->hw.mic.hscx, (nr ? 0x40 : 0), ptr, cnt) + +#define WRITEHSCXFIFO(cs, nr, ptr, cnt) writefifo(cs->hw.mic.adr, \ + cs->hw.mic.hscx, (nr ? 0x40 : 0), ptr, cnt) + +#include "hscx_irq.c" + +static irqreturn_t +mic_interrupt(int intno, void *dev_id, struct pt_regs *regs) +{ + struct IsdnCardState *cs = dev_id; + u_char val; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + val = readreg(cs->hw.mic.adr, cs->hw.mic.hscx, HSCX_ISTA + 0x40); + Start_HSCX: + if (val) + hscx_int_main(cs, val); + val = readreg(cs->hw.mic.adr, cs->hw.mic.isac, ISAC_ISTA); + Start_ISAC: + if (val) + isac_interrupt(cs, val); + val = readreg(cs->hw.mic.adr, cs->hw.mic.hscx, HSCX_ISTA + 0x40); + if (val) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HSCX IntStat after IntRoutine"); + goto Start_HSCX; + } + val = readreg(cs->hw.mic.adr, cs->hw.mic.isac, ISAC_ISTA); + if (val) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ISAC IntStat after IntRoutine"); + goto Start_ISAC; + } + writereg(cs->hw.mic.adr, cs->hw.mic.hscx, HSCX_MASK, 0xFF); + writereg(cs->hw.mic.adr, cs->hw.mic.hscx, HSCX_MASK + 0x40, 0xFF); + writereg(cs->hw.mic.adr, cs->hw.mic.isac, ISAC_MASK, 0xFF); + writereg(cs->hw.mic.adr, cs->hw.mic.isac, ISAC_MASK, 0x0); + writereg(cs->hw.mic.adr, cs->hw.mic.hscx, HSCX_MASK, 0x0); + writereg(cs->hw.mic.adr, cs->hw.mic.hscx, HSCX_MASK + 0x40, 0x0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +void +release_io_mic(struct IsdnCardState *cs) +{ + int bytecnt = 8; + + if (cs->hw.mic.cfg_reg) + release_region(cs->hw.mic.cfg_reg, bytecnt); +} + +static int +mic_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + switch (mt) { + case CARD_RESET: + return(0); + case CARD_RELEASE: + release_io_mic(cs); + return(0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + inithscx(cs); /* /RTSA := ISAC RST */ + inithscxisac(cs, 3); + spin_unlock_irqrestore(&cs->lock, flags); + return(0); + case CARD_TEST: + return(0); + } + return(0); +} + +int __init +setup_mic(struct IsdnCard *card) +{ + int bytecnt; + struct IsdnCardState *cs = card->cs; + char tmp[64]; + + strcpy(tmp, mic_revision); + printk(KERN_INFO "HiSax: mic driver Rev. %s\n", HiSax_getrev(tmp)); + if (cs->typ != ISDN_CTYPE_MIC) + return (0); + + bytecnt = 8; + cs->hw.mic.cfg_reg = card->para[1]; + cs->irq = card->para[0]; + cs->hw.mic.adr = cs->hw.mic.cfg_reg + MIC_ADR; + cs->hw.mic.isac = cs->hw.mic.cfg_reg + MIC_ISAC; + cs->hw.mic.hscx = cs->hw.mic.cfg_reg + MIC_HSCX; + + if (!request_region(cs->hw.mic.cfg_reg, bytecnt, "mic isdn")) { + printk(KERN_WARNING + "HiSax: %s config port %x-%x already in use\n", + CardType[card->typ], + cs->hw.mic.cfg_reg, + cs->hw.mic.cfg_reg + bytecnt); + return (0); + } + printk(KERN_INFO "mic: defined at 0x%x IRQ %d\n", + cs->hw.mic.cfg_reg, cs->irq); + setup_isac(cs); + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->BC_Read_Reg = &ReadHSCX; + cs->BC_Write_Reg = &WriteHSCX; + cs->BC_Send_Data = &hscx_fill_fifo; + cs->cardmsg = &mic_card_msg; + cs->irq_func = &mic_interrupt; + ISACVersion(cs, "mic:"); + if (HscxVersion(cs, "mic:")) { + printk(KERN_WARNING + "mic: wrong HSCX versions check IO address\n"); + release_io_mic(cs); + return (0); + } + return (1); +} diff --git a/drivers/isdn/hisax/netjet.c b/drivers/isdn/hisax/netjet.c new file mode 100644 index 000000000000..fe61d26365d3 --- /dev/null +++ b/drivers/isdn/hisax/netjet.c @@ -0,0 +1,996 @@ +/* $Id: netjet.c,v 1.29.2.4 2004/02/11 13:21:34 keil Exp $ + * + * low level stuff for Traverse Technologie NETJet ISDN cards + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * Thanks to Traverse Technologies Australia for documents and information + * + * 16-Apr-2002 - led code added - Guy Ellis (guy@traverse.com.au) + * + */ + +#include <linux/init.h> +#include "hisax.h" +#include "isac.h" +#include "hscx.h" +#include "isdnl1.h" +#include <linux/pci.h> +#include <linux/interrupt.h> +#include <linux/ppp_defs.h> +#include <asm/io.h> +#include "netjet.h" + +const char *NETjet_revision = "$Revision: 1.29.2.4 $"; + +/* Interface functions */ + +u_char +NETjet_ReadIC(struct IsdnCardState *cs, u_char offset) +{ + u_char ret; + + cs->hw.njet.auxd &= 0xfc; + cs->hw.njet.auxd |= (offset>>4) & 3; + byteout(cs->hw.njet.auxa, cs->hw.njet.auxd); + ret = bytein(cs->hw.njet.isac + ((offset & 0xf)<<2)); + return(ret); +} + +void +NETjet_WriteIC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + cs->hw.njet.auxd &= 0xfc; + cs->hw.njet.auxd |= (offset>>4) & 3; + byteout(cs->hw.njet.auxa, cs->hw.njet.auxd); + byteout(cs->hw.njet.isac + ((offset & 0xf)<<2), value); +} + +void +NETjet_ReadICfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + cs->hw.njet.auxd &= 0xfc; + byteout(cs->hw.njet.auxa, cs->hw.njet.auxd); + insb(cs->hw.njet.isac, data, size); +} + +void +NETjet_WriteICfifo(struct IsdnCardState *cs, u_char *data, int size) +{ + cs->hw.njet.auxd &= 0xfc; + byteout(cs->hw.njet.auxa, cs->hw.njet.auxd); + outsb(cs->hw.njet.isac, data, size); +} + +void fill_mem(struct BCState *bcs, u_int *pos, u_int cnt, int chan, u_char fill) +{ + u_int mask=0x000000ff, val = 0, *p=pos; + u_int i; + + val |= fill; + if (chan) { + val <<= 8; + mask <<= 8; + } + mask ^= 0xffffffff; + for (i=0; i<cnt; i++) { + *p &= mask; + *p++ |= val; + if (p > bcs->hw.tiger.s_end) + p = bcs->hw.tiger.send; + } +} + +void +mode_tiger(struct BCState *bcs, int mode, int bc) +{ + struct IsdnCardState *cs = bcs->cs; + u_char led; + + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "Tiger mode %d bchan %d/%d", + mode, bc, bcs->channel); + bcs->mode = mode; + bcs->channel = bc; + switch (mode) { + case (L1_MODE_NULL): + fill_mem(bcs, bcs->hw.tiger.send, + NETJET_DMA_TXSIZE, bc, 0xff); + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "Tiger stat rec %d/%d send %d", + bcs->hw.tiger.r_tot, bcs->hw.tiger.r_err, + bcs->hw.tiger.s_tot); + if ((cs->bcs[0].mode == L1_MODE_NULL) && + (cs->bcs[1].mode == L1_MODE_NULL)) { + cs->hw.njet.dmactrl = 0; + byteout(cs->hw.njet.base + NETJET_DMACTRL, + cs->hw.njet.dmactrl); + byteout(cs->hw.njet.base + NETJET_IRQMASK0, 0); + } + if (cs->typ == ISDN_CTYPE_NETJET_S) + { + // led off + led = bc & 0x01; + led = 0x01 << (6 + led); // convert to mask + led = ~led; + cs->hw.njet.auxd &= led; + byteout(cs->hw.njet.auxa, cs->hw.njet.auxd); + } + break; + case (L1_MODE_TRANS): + break; + case (L1_MODE_HDLC_56K): + case (L1_MODE_HDLC): + fill_mem(bcs, bcs->hw.tiger.send, + NETJET_DMA_TXSIZE, bc, 0xff); + bcs->hw.tiger.r_state = HDLC_ZERO_SEARCH; + bcs->hw.tiger.r_tot = 0; + bcs->hw.tiger.r_bitcnt = 0; + bcs->hw.tiger.r_one = 0; + bcs->hw.tiger.r_err = 0; + bcs->hw.tiger.s_tot = 0; + if (! cs->hw.njet.dmactrl) { + fill_mem(bcs, bcs->hw.tiger.send, + NETJET_DMA_TXSIZE, !bc, 0xff); + cs->hw.njet.dmactrl = 1; + byteout(cs->hw.njet.base + NETJET_DMACTRL, + cs->hw.njet.dmactrl); + byteout(cs->hw.njet.base + NETJET_IRQMASK0, 0x0f); + /* was 0x3f now 0x0f for TJ300 and TJ320 GE 13/07/00 */ + } + bcs->hw.tiger.sendp = bcs->hw.tiger.send; + bcs->hw.tiger.free = NETJET_DMA_TXSIZE; + test_and_set_bit(BC_FLG_EMPTY, &bcs->Flag); + if (cs->typ == ISDN_CTYPE_NETJET_S) + { + // led on + led = bc & 0x01; + led = 0x01 << (6 + led); // convert to mask + cs->hw.njet.auxd |= led; + byteout(cs->hw.njet.auxa, cs->hw.njet.auxd); + } + break; + } + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "tiger: set %x %x %x %x/%x pulse=%d", + bytein(cs->hw.njet.base + NETJET_DMACTRL), + bytein(cs->hw.njet.base + NETJET_IRQMASK0), + bytein(cs->hw.njet.base + NETJET_IRQSTAT0), + inl(cs->hw.njet.base + NETJET_DMA_READ_ADR), + inl(cs->hw.njet.base + NETJET_DMA_WRITE_ADR), + bytein(cs->hw.njet.base + NETJET_PULSE_CNT)); +} + +static void printframe(struct IsdnCardState *cs, u_char *buf, int count, char *s) { + char tmp[128]; + char *t = tmp; + int i=count,j; + u_char *p = buf; + + t += sprintf(t, "tiger %s(%4d)", s, count); + while (i>0) { + if (i>16) + j=16; + else + j=i; + QuickHex(t, p, j); + debugl1(cs, tmp); + p += j; + i -= j; + t = tmp; + t += sprintf(t, "tiger %s ", s); + } +} + +// macro for 64k + +#define MAKE_RAW_BYTE for (j=0; j<8; j++) { \ + bitcnt++;\ + s_val >>= 1;\ + if (val & 1) {\ + s_one++;\ + s_val |= 0x80;\ + } else {\ + s_one = 0;\ + s_val &= 0x7f;\ + }\ + if (bitcnt==8) {\ + bcs->hw.tiger.sendbuf[s_cnt++] = s_val;\ + bitcnt = 0;\ + }\ + if (s_one == 5) {\ + s_val >>= 1;\ + s_val &= 0x7f;\ + bitcnt++;\ + s_one = 0;\ + }\ + if (bitcnt==8) {\ + bcs->hw.tiger.sendbuf[s_cnt++] = s_val;\ + bitcnt = 0;\ + }\ + val >>= 1;\ + } + +static int make_raw_data(struct BCState *bcs) { +// this make_raw is for 64k + register u_int i,s_cnt=0; + register u_char j; + register u_char val; + register u_char s_one = 0; + register u_char s_val = 0; + register u_char bitcnt = 0; + u_int fcs; + + if (!bcs->tx_skb) { + debugl1(bcs->cs, "tiger make_raw: NULL skb"); + return(1); + } + bcs->hw.tiger.sendbuf[s_cnt++] = HDLC_FLAG_VALUE; + fcs = PPP_INITFCS; + for (i=0; i<bcs->tx_skb->len; i++) { + val = bcs->tx_skb->data[i]; + fcs = PPP_FCS (fcs, val); + MAKE_RAW_BYTE; + } + fcs ^= 0xffff; + val = fcs & 0xff; + MAKE_RAW_BYTE; + val = (fcs>>8) & 0xff; + MAKE_RAW_BYTE; + val = HDLC_FLAG_VALUE; + for (j=0; j<8; j++) { + bitcnt++; + s_val >>= 1; + if (val & 1) + s_val |= 0x80; + else + s_val &= 0x7f; + if (bitcnt==8) { + bcs->hw.tiger.sendbuf[s_cnt++] = s_val; + bitcnt = 0; + } + val >>= 1; + } + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs,"tiger make_raw: in %ld out %d.%d", + bcs->tx_skb->len, s_cnt, bitcnt); + if (bitcnt) { + while (8>bitcnt++) { + s_val >>= 1; + s_val |= 0x80; + } + bcs->hw.tiger.sendbuf[s_cnt++] = s_val; + bcs->hw.tiger.sendbuf[s_cnt++] = 0xff; // NJ<->NJ thoughput bug fix + } + bcs->hw.tiger.sendcnt = s_cnt; + bcs->tx_cnt -= bcs->tx_skb->len; + bcs->hw.tiger.sp = bcs->hw.tiger.sendbuf; + return(0); +} + +// macro for 56k + +#define MAKE_RAW_BYTE_56K for (j=0; j<8; j++) { \ + bitcnt++;\ + s_val >>= 1;\ + if (val & 1) {\ + s_one++;\ + s_val |= 0x80;\ + } else {\ + s_one = 0;\ + s_val &= 0x7f;\ + }\ + if (bitcnt==7) {\ + s_val >>= 1;\ + s_val |= 0x80;\ + bcs->hw.tiger.sendbuf[s_cnt++] = s_val;\ + bitcnt = 0;\ + }\ + if (s_one == 5) {\ + s_val >>= 1;\ + s_val &= 0x7f;\ + bitcnt++;\ + s_one = 0;\ + }\ + if (bitcnt==7) {\ + s_val >>= 1;\ + s_val |= 0x80;\ + bcs->hw.tiger.sendbuf[s_cnt++] = s_val;\ + bitcnt = 0;\ + }\ + val >>= 1;\ + } + +static int make_raw_data_56k(struct BCState *bcs) { +// this make_raw is for 56k + register u_int i,s_cnt=0; + register u_char j; + register u_char val; + register u_char s_one = 0; + register u_char s_val = 0; + register u_char bitcnt = 0; + u_int fcs; + + if (!bcs->tx_skb) { + debugl1(bcs->cs, "tiger make_raw_56k: NULL skb"); + return(1); + } + val = HDLC_FLAG_VALUE; + for (j=0; j<8; j++) { + bitcnt++; + s_val >>= 1; + if (val & 1) + s_val |= 0x80; + else + s_val &= 0x7f; + if (bitcnt==7) { + s_val >>= 1; + s_val |= 0x80; + bcs->hw.tiger.sendbuf[s_cnt++] = s_val; + bitcnt = 0; + } + val >>= 1; + }; + fcs = PPP_INITFCS; + for (i=0; i<bcs->tx_skb->len; i++) { + val = bcs->tx_skb->data[i]; + fcs = PPP_FCS (fcs, val); + MAKE_RAW_BYTE_56K; + } + fcs ^= 0xffff; + val = fcs & 0xff; + MAKE_RAW_BYTE_56K; + val = (fcs>>8) & 0xff; + MAKE_RAW_BYTE_56K; + val = HDLC_FLAG_VALUE; + for (j=0; j<8; j++) { + bitcnt++; + s_val >>= 1; + if (val & 1) + s_val |= 0x80; + else + s_val &= 0x7f; + if (bitcnt==7) { + s_val >>= 1; + s_val |= 0x80; + bcs->hw.tiger.sendbuf[s_cnt++] = s_val; + bitcnt = 0; + } + val >>= 1; + } + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs,"tiger make_raw_56k: in %ld out %d.%d", + bcs->tx_skb->len, s_cnt, bitcnt); + if (bitcnt) { + while (8>bitcnt++) { + s_val >>= 1; + s_val |= 0x80; + } + bcs->hw.tiger.sendbuf[s_cnt++] = s_val; + bcs->hw.tiger.sendbuf[s_cnt++] = 0xff; // NJ<->NJ thoughput bug fix + } + bcs->hw.tiger.sendcnt = s_cnt; + bcs->tx_cnt -= bcs->tx_skb->len; + bcs->hw.tiger.sp = bcs->hw.tiger.sendbuf; + return(0); +} + +static void got_frame(struct BCState *bcs, int count) { + struct sk_buff *skb; + + if (!(skb = dev_alloc_skb(count))) + printk(KERN_WARNING "TIGER: receive out of memory\n"); + else { + memcpy(skb_put(skb, count), bcs->hw.tiger.rcvbuf, count); + skb_queue_tail(&bcs->rqueue, skb); + } + test_and_set_bit(B_RCVBUFREADY, &bcs->event); + schedule_work(&bcs->tqueue); + + if (bcs->cs->debug & L1_DEB_RECEIVE_FRAME) + printframe(bcs->cs, bcs->hw.tiger.rcvbuf, count, "rec"); +} + + + +static void read_raw(struct BCState *bcs, u_int *buf, int cnt){ + int i; + register u_char j; + register u_char val; + u_int *pend = bcs->hw.tiger.rec +NETJET_DMA_RXSIZE -1; + register u_char state = bcs->hw.tiger.r_state; + register u_char r_one = bcs->hw.tiger.r_one; + register u_char r_val = bcs->hw.tiger.r_val; + register u_int bitcnt = bcs->hw.tiger.r_bitcnt; + u_int *p = buf; + int bits; + u_char mask; + + if (bcs->mode == L1_MODE_HDLC) { // it's 64k + mask = 0xff; + bits = 8; + } + else { // it's 56K + mask = 0x7f; + bits = 7; + }; + for (i=0;i<cnt;i++) { + val = bcs->channel ? ((*p>>8) & 0xff) : (*p & 0xff); + p++; + if (p > pend) + p = bcs->hw.tiger.rec; + if ((val & mask) == mask) { + state = HDLC_ZERO_SEARCH; + bcs->hw.tiger.r_tot++; + bitcnt = 0; + r_one = 0; + continue; + } + for (j=0;j<bits;j++) { + if (state == HDLC_ZERO_SEARCH) { + if (val & 1) { + r_one++; + } else { + r_one=0; + state= HDLC_FLAG_SEARCH; + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs,"tiger read_raw: zBit(%d,%d,%d) %x", + bcs->hw.tiger.r_tot,i,j,val); + } + } else if (state == HDLC_FLAG_SEARCH) { + if (val & 1) { + r_one++; + if (r_one>6) { + state=HDLC_ZERO_SEARCH; + } + } else { + if (r_one==6) { + bitcnt=0; + r_val=0; + state=HDLC_FLAG_FOUND; + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs,"tiger read_raw: flag(%d,%d,%d) %x", + bcs->hw.tiger.r_tot,i,j,val); + } + r_one=0; + } + } else if (state == HDLC_FLAG_FOUND) { + if (val & 1) { + r_one++; + if (r_one>6) { + state=HDLC_ZERO_SEARCH; + } else { + r_val >>= 1; + r_val |= 0x80; + bitcnt++; + } + } else { + if (r_one==6) { + bitcnt=0; + r_val=0; + r_one=0; + val >>= 1; + continue; + } else if (r_one!=5) { + r_val >>= 1; + r_val &= 0x7f; + bitcnt++; + } + r_one=0; + } + if ((state != HDLC_ZERO_SEARCH) && + !(bitcnt & 7)) { + state=HDLC_FRAME_FOUND; + bcs->hw.tiger.r_fcs = PPP_INITFCS; + bcs->hw.tiger.rcvbuf[0] = r_val; + bcs->hw.tiger.r_fcs = PPP_FCS (bcs->hw.tiger.r_fcs, r_val); + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs,"tiger read_raw: byte1(%d,%d,%d) rval %x val %x i %x", + bcs->hw.tiger.r_tot,i,j,r_val,val, + bcs->cs->hw.njet.irqstat0); + } + } else if (state == HDLC_FRAME_FOUND) { + if (val & 1) { + r_one++; + if (r_one>6) { + state=HDLC_ZERO_SEARCH; + bitcnt=0; + } else { + r_val >>= 1; + r_val |= 0x80; + bitcnt++; + } + } else { + if (r_one==6) { + r_val=0; + r_one=0; + bitcnt++; + if (bitcnt & 7) { + debugl1(bcs->cs, "tiger: frame not byte aligned"); + state=HDLC_FLAG_SEARCH; + bcs->hw.tiger.r_err++; +#ifdef ERROR_STATISTIC + bcs->err_inv++; +#endif + } else { + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs,"tiger frame end(%d,%d): fcs(%x) i %x", + i,j,bcs->hw.tiger.r_fcs, bcs->cs->hw.njet.irqstat0); + if (bcs->hw.tiger.r_fcs == PPP_GOODFCS) { + got_frame(bcs, (bitcnt>>3)-3); + } else { + if (bcs->cs->debug) { + debugl1(bcs->cs, "tiger FCS error"); + printframe(bcs->cs, bcs->hw.tiger.rcvbuf, + (bitcnt>>3)-1, "rec"); + bcs->hw.tiger.r_err++; + } +#ifdef ERROR_STATISTIC + bcs->err_crc++; +#endif + } + state=HDLC_FLAG_FOUND; + } + bitcnt=0; + } else if (r_one==5) { + val >>= 1; + r_one=0; + continue; + } else { + r_val >>= 1; + r_val &= 0x7f; + bitcnt++; + } + r_one=0; + } + if ((state == HDLC_FRAME_FOUND) && + !(bitcnt & 7)) { + if ((bitcnt>>3)>=HSCX_BUFMAX) { + debugl1(bcs->cs, "tiger: frame too big"); + r_val=0; + state=HDLC_FLAG_SEARCH; + bcs->hw.tiger.r_err++; +#ifdef ERROR_STATISTIC + bcs->err_inv++; +#endif + } else { + bcs->hw.tiger.rcvbuf[(bitcnt>>3)-1] = r_val; + bcs->hw.tiger.r_fcs = + PPP_FCS (bcs->hw.tiger.r_fcs, r_val); + } + } + } + val >>= 1; + } + bcs->hw.tiger.r_tot++; + } + bcs->hw.tiger.r_state = state; + bcs->hw.tiger.r_one = r_one; + bcs->hw.tiger.r_val = r_val; + bcs->hw.tiger.r_bitcnt = bitcnt; +} + +void read_tiger(struct IsdnCardState *cs) { + u_int *p; + int cnt = NETJET_DMA_RXSIZE/2; + + if ((cs->hw.njet.irqstat0 & cs->hw.njet.last_is0) & NETJET_IRQM0_READ) { + debugl1(cs,"tiger warn read double dma %x/%x", + cs->hw.njet.irqstat0, cs->hw.njet.last_is0); +#ifdef ERROR_STATISTIC + if (cs->bcs[0].mode) + cs->bcs[0].err_rdo++; + if (cs->bcs[1].mode) + cs->bcs[1].err_rdo++; +#endif + return; + } else { + cs->hw.njet.last_is0 &= ~NETJET_IRQM0_READ; + cs->hw.njet.last_is0 |= (cs->hw.njet.irqstat0 & NETJET_IRQM0_READ); + } + if (cs->hw.njet.irqstat0 & NETJET_IRQM0_READ_1) + p = cs->bcs[0].hw.tiger.rec + NETJET_DMA_RXSIZE - 1; + else + p = cs->bcs[0].hw.tiger.rec + cnt - 1; + if ((cs->bcs[0].mode == L1_MODE_HDLC) || (cs->bcs[0].mode == L1_MODE_HDLC_56K)) + read_raw(cs->bcs, p, cnt); + + if ((cs->bcs[1].mode == L1_MODE_HDLC) || (cs->bcs[1].mode == L1_MODE_HDLC_56K)) + read_raw(cs->bcs + 1, p, cnt); + cs->hw.njet.irqstat0 &= ~NETJET_IRQM0_READ; +} + +static void write_raw(struct BCState *bcs, u_int *buf, int cnt); + +void netjet_fill_dma(struct BCState *bcs) +{ + register u_int *p, *sp; + register int cnt; + + if (!bcs->tx_skb) + return; + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs,"tiger fill_dma1: c%d %4x", bcs->channel, + bcs->Flag); + if (test_and_set_bit(BC_FLG_BUSY, &bcs->Flag)) + return; + if (bcs->mode == L1_MODE_HDLC) { // it's 64k + if (make_raw_data(bcs)) + return; + } + else { // it's 56k + if (make_raw_data_56k(bcs)) + return; + }; + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs,"tiger fill_dma2: c%d %4x", bcs->channel, + bcs->Flag); + if (test_and_clear_bit(BC_FLG_NOFRAME, &bcs->Flag)) { + write_raw(bcs, bcs->hw.tiger.sendp, bcs->hw.tiger.free); + } else if (test_and_clear_bit(BC_FLG_HALF, &bcs->Flag)) { + p = bus_to_virt(inl(bcs->cs->hw.njet.base + NETJET_DMA_READ_ADR)); + sp = bcs->hw.tiger.sendp; + if (p == bcs->hw.tiger.s_end) + p = bcs->hw.tiger.send -1; + if (sp == bcs->hw.tiger.s_end) + sp = bcs->hw.tiger.send -1; + cnt = p - sp; + if (cnt <0) { + write_raw(bcs, bcs->hw.tiger.sendp, bcs->hw.tiger.free); + } else { + p++; + cnt++; + if (p > bcs->hw.tiger.s_end) + p = bcs->hw.tiger.send; + p++; + cnt++; + if (p > bcs->hw.tiger.s_end) + p = bcs->hw.tiger.send; + write_raw(bcs, p, bcs->hw.tiger.free - cnt); + } + } else if (test_and_clear_bit(BC_FLG_EMPTY, &bcs->Flag)) { + p = bus_to_virt(inl(bcs->cs->hw.njet.base + NETJET_DMA_READ_ADR)); + cnt = bcs->hw.tiger.s_end - p; + if (cnt < 2) { + p = bcs->hw.tiger.send + 1; + cnt = NETJET_DMA_TXSIZE/2 - 2; + } else { + p++; + p++; + if (cnt <= (NETJET_DMA_TXSIZE/2)) + cnt += NETJET_DMA_TXSIZE/2; + cnt--; + cnt--; + } + write_raw(bcs, p, cnt); + } + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs,"tiger fill_dma3: c%d %4x", bcs->channel, + bcs->Flag); +} + +static void write_raw(struct BCState *bcs, u_int *buf, int cnt) { + u_int mask, val, *p=buf; + u_int i, s_cnt; + + if (cnt <= 0) + return; + if (test_bit(BC_FLG_BUSY, &bcs->Flag)) { + if (bcs->hw.tiger.sendcnt> cnt) { + s_cnt = cnt; + bcs->hw.tiger.sendcnt -= cnt; + } else { + s_cnt = bcs->hw.tiger.sendcnt; + bcs->hw.tiger.sendcnt = 0; + } + if (bcs->channel) + mask = 0xffff00ff; + else + mask = 0xffffff00; + for (i=0; i<s_cnt; i++) { + val = bcs->channel ? ((bcs->hw.tiger.sp[i] <<8) & 0xff00) : + (bcs->hw.tiger.sp[i]); + *p &= mask; + *p++ |= val; + if (p>bcs->hw.tiger.s_end) + p = bcs->hw.tiger.send; + } + bcs->hw.tiger.s_tot += s_cnt; + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs,"tiger write_raw: c%d %p-%p %d/%d %d %x", bcs->channel, + buf, p, s_cnt, cnt, + bcs->hw.tiger.sendcnt, bcs->cs->hw.njet.irqstat0); + if (bcs->cs->debug & L1_DEB_HSCX_FIFO) + printframe(bcs->cs, bcs->hw.tiger.sp, s_cnt, "snd"); + bcs->hw.tiger.sp += s_cnt; + bcs->hw.tiger.sendp = p; + if (!bcs->hw.tiger.sendcnt) { + if (!bcs->tx_skb) { + debugl1(bcs->cs,"tiger write_raw: NULL skb s_cnt %d", s_cnt); + } else { + if (test_bit(FLG_LLI_L1WAKEUP,&bcs->st->lli.flag) && + (PACKET_NOACK != bcs->tx_skb->pkt_type)) { + u_long flags; + spin_lock_irqsave(&bcs->aclock, flags); + bcs->ackcnt += bcs->tx_skb->len; + spin_unlock_irqrestore(&bcs->aclock, flags); + schedule_event(bcs, B_ACKPENDING); + } + dev_kfree_skb_any(bcs->tx_skb); + bcs->tx_skb = NULL; + } + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->hw.tiger.free = cnt - s_cnt; + if (bcs->hw.tiger.free > (NETJET_DMA_TXSIZE/2)) + test_and_set_bit(BC_FLG_HALF, &bcs->Flag); + else { + test_and_clear_bit(BC_FLG_HALF, &bcs->Flag); + test_and_set_bit(BC_FLG_NOFRAME, &bcs->Flag); + } + if ((bcs->tx_skb = skb_dequeue(&bcs->squeue))) { + netjet_fill_dma(bcs); + } else { + mask ^= 0xffffffff; + if (s_cnt < cnt) { + for (i=s_cnt; i<cnt;i++) { + *p++ |= mask; + if (p>bcs->hw.tiger.s_end) + p = bcs->hw.tiger.send; + } + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs, "tiger write_raw: fill rest %d", + cnt - s_cnt); + } + test_and_set_bit(B_XMTBUFREADY, &bcs->event); + schedule_work(&bcs->tqueue); + } + } + } else if (test_and_clear_bit(BC_FLG_NOFRAME, &bcs->Flag)) { + test_and_set_bit(BC_FLG_HALF, &bcs->Flag); + fill_mem(bcs, buf, cnt, bcs->channel, 0xff); + bcs->hw.tiger.free += cnt; + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs,"tiger write_raw: fill half"); + } else if (test_and_clear_bit(BC_FLG_HALF, &bcs->Flag)) { + test_and_set_bit(BC_FLG_EMPTY, &bcs->Flag); + fill_mem(bcs, buf, cnt, bcs->channel, 0xff); + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs,"tiger write_raw: fill full"); + } +} + +void write_tiger(struct IsdnCardState *cs) { + u_int *p, cnt = NETJET_DMA_TXSIZE/2; + + if ((cs->hw.njet.irqstat0 & cs->hw.njet.last_is0) & NETJET_IRQM0_WRITE) { + debugl1(cs,"tiger warn write double dma %x/%x", + cs->hw.njet.irqstat0, cs->hw.njet.last_is0); +#ifdef ERROR_STATISTIC + if (cs->bcs[0].mode) + cs->bcs[0].err_tx++; + if (cs->bcs[1].mode) + cs->bcs[1].err_tx++; +#endif + return; + } else { + cs->hw.njet.last_is0 &= ~NETJET_IRQM0_WRITE; + cs->hw.njet.last_is0 |= (cs->hw.njet.irqstat0 & NETJET_IRQM0_WRITE); + } + if (cs->hw.njet.irqstat0 & NETJET_IRQM0_WRITE_1) + p = cs->bcs[0].hw.tiger.send + NETJET_DMA_TXSIZE - 1; + else + p = cs->bcs[0].hw.tiger.send + cnt - 1; + if ((cs->bcs[0].mode == L1_MODE_HDLC) || (cs->bcs[0].mode == L1_MODE_HDLC_56K)) + write_raw(cs->bcs, p, cnt); + if ((cs->bcs[1].mode == L1_MODE_HDLC) || (cs->bcs[1].mode == L1_MODE_HDLC_56K)) + write_raw(cs->bcs + 1, p, cnt); + cs->hw.njet.irqstat0 &= ~NETJET_IRQM0_WRITE; +} + +static void +tiger_l2l1(struct PStack *st, int pr, void *arg) +{ + struct BCState *bcs = st->l1.bcs; + struct sk_buff *skb = arg; + u_long flags; + + switch (pr) { + case (PH_DATA | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + skb_queue_tail(&bcs->squeue, skb); + } else { + bcs->tx_skb = skb; + bcs->cs->BC_Send_Data(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | INDICATION): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + printk(KERN_WARNING "tiger_l2l1: this shouldn't happen\n"); + } else { + bcs->tx_skb = skb; + bcs->cs->BC_Send_Data(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | REQUEST): + if (!bcs->tx_skb) { + test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } else + test_and_set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + case (PH_ACTIVATE | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + test_and_set_bit(BC_FLG_ACTIV, &bcs->Flag); + mode_tiger(bcs, st->l1.mode, st->l1.bc); + /* 2001/10/04 Christoph Ersfeld, Formula-n Europe AG */ + spin_unlock_irqrestore(&bcs->cs->lock, flags); + bcs->cs->cardmsg(bcs->cs, MDL_BC_ASSIGN, (void *)(&st->l1.bc)); + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | REQUEST): + /* 2001/10/04 Christoph Ersfeld, Formula-n Europe AG */ + bcs->cs->cardmsg(bcs->cs, MDL_BC_RELEASE, (void *)(&st->l1.bc)); + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | CONFIRM): + spin_lock_irqsave(&bcs->cs->lock, flags); + test_and_clear_bit(BC_FLG_ACTIV, &bcs->Flag); + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + mode_tiger(bcs, 0, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + st->l1.l1l2(st, PH_DEACTIVATE | CONFIRM, NULL); + break; + } +} + + +void +close_tigerstate(struct BCState *bcs) +{ + mode_tiger(bcs, 0, bcs->channel); + if (test_and_clear_bit(BC_FLG_INIT, &bcs->Flag)) { + if (bcs->hw.tiger.rcvbuf) { + kfree(bcs->hw.tiger.rcvbuf); + bcs->hw.tiger.rcvbuf = NULL; + } + if (bcs->hw.tiger.sendbuf) { + kfree(bcs->hw.tiger.sendbuf); + bcs->hw.tiger.sendbuf = NULL; + } + skb_queue_purge(&bcs->rqueue); + skb_queue_purge(&bcs->squeue); + if (bcs->tx_skb) { + dev_kfree_skb_any(bcs->tx_skb); + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + } + } +} + +static int +open_tigerstate(struct IsdnCardState *cs, struct BCState *bcs) +{ + if (!test_and_set_bit(BC_FLG_INIT, &bcs->Flag)) { + if (!(bcs->hw.tiger.rcvbuf = kmalloc(HSCX_BUFMAX, GFP_ATOMIC))) { + printk(KERN_WARNING + "HiSax: No memory for tiger.rcvbuf\n"); + return (1); + } + if (!(bcs->hw.tiger.sendbuf = kmalloc(RAW_BUFMAX, GFP_ATOMIC))) { + printk(KERN_WARNING + "HiSax: No memory for tiger.sendbuf\n"); + return (1); + } + skb_queue_head_init(&bcs->rqueue); + skb_queue_head_init(&bcs->squeue); + } + bcs->tx_skb = NULL; + bcs->hw.tiger.sendcnt = 0; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->event = 0; + bcs->tx_cnt = 0; + return (0); +} + +int +setstack_tiger(struct PStack *st, struct BCState *bcs) +{ + bcs->channel = st->l1.bc; + if (open_tigerstate(st->l1.hardware, bcs)) + return (-1); + st->l1.bcs = bcs; + st->l2.l2l1 = tiger_l2l1; + setstack_manager(st); + bcs->st = st; + setstack_l1_B(st); + return (0); +} + + +void __init +inittiger(struct IsdnCardState *cs) +{ + if (!(cs->bcs[0].hw.tiger.send = kmalloc(NETJET_DMA_TXSIZE * sizeof(unsigned int), + GFP_KERNEL | GFP_DMA))) { + printk(KERN_WARNING + "HiSax: No memory for tiger.send\n"); + return; + } + cs->bcs[0].hw.tiger.s_irq = cs->bcs[0].hw.tiger.send + NETJET_DMA_TXSIZE/2 - 1; + cs->bcs[0].hw.tiger.s_end = cs->bcs[0].hw.tiger.send + NETJET_DMA_TXSIZE - 1; + cs->bcs[1].hw.tiger.send = cs->bcs[0].hw.tiger.send; + cs->bcs[1].hw.tiger.s_irq = cs->bcs[0].hw.tiger.s_irq; + cs->bcs[1].hw.tiger.s_end = cs->bcs[0].hw.tiger.s_end; + + memset(cs->bcs[0].hw.tiger.send, 0xff, NETJET_DMA_TXSIZE * sizeof(unsigned int)); + debugl1(cs, "tiger: send buf %p - %p", cs->bcs[0].hw.tiger.send, + cs->bcs[0].hw.tiger.send + NETJET_DMA_TXSIZE - 1); + outl(virt_to_bus(cs->bcs[0].hw.tiger.send), + cs->hw.njet.base + NETJET_DMA_READ_START); + outl(virt_to_bus(cs->bcs[0].hw.tiger.s_irq), + cs->hw.njet.base + NETJET_DMA_READ_IRQ); + outl(virt_to_bus(cs->bcs[0].hw.tiger.s_end), + cs->hw.njet.base + NETJET_DMA_READ_END); + if (!(cs->bcs[0].hw.tiger.rec = kmalloc(NETJET_DMA_RXSIZE * sizeof(unsigned int), + GFP_KERNEL | GFP_DMA))) { + printk(KERN_WARNING + "HiSax: No memory for tiger.rec\n"); + return; + } + debugl1(cs, "tiger: rec buf %p - %p", cs->bcs[0].hw.tiger.rec, + cs->bcs[0].hw.tiger.rec + NETJET_DMA_RXSIZE - 1); + cs->bcs[1].hw.tiger.rec = cs->bcs[0].hw.tiger.rec; + memset(cs->bcs[0].hw.tiger.rec, 0xff, NETJET_DMA_RXSIZE * sizeof(unsigned int)); + outl(virt_to_bus(cs->bcs[0].hw.tiger.rec), + cs->hw.njet.base + NETJET_DMA_WRITE_START); + outl(virt_to_bus(cs->bcs[0].hw.tiger.rec + NETJET_DMA_RXSIZE/2 - 1), + cs->hw.njet.base + NETJET_DMA_WRITE_IRQ); + outl(virt_to_bus(cs->bcs[0].hw.tiger.rec + NETJET_DMA_RXSIZE - 1), + cs->hw.njet.base + NETJET_DMA_WRITE_END); + debugl1(cs, "tiger: dmacfg %x/%x pulse=%d", + inl(cs->hw.njet.base + NETJET_DMA_WRITE_ADR), + inl(cs->hw.njet.base + NETJET_DMA_READ_ADR), + bytein(cs->hw.njet.base + NETJET_PULSE_CNT)); + cs->hw.njet.last_is0 = 0; + cs->bcs[0].BC_SetStack = setstack_tiger; + cs->bcs[1].BC_SetStack = setstack_tiger; + cs->bcs[0].BC_Close = close_tigerstate; + cs->bcs[1].BC_Close = close_tigerstate; +} + +void +releasetiger(struct IsdnCardState *cs) +{ + if (cs->bcs[0].hw.tiger.send) { + kfree(cs->bcs[0].hw.tiger.send); + cs->bcs[0].hw.tiger.send = NULL; + } + if (cs->bcs[1].hw.tiger.send) { + cs->bcs[1].hw.tiger.send = NULL; + } + if (cs->bcs[0].hw.tiger.rec) { + kfree(cs->bcs[0].hw.tiger.rec); + cs->bcs[0].hw.tiger.rec = NULL; + } + if (cs->bcs[1].hw.tiger.rec) { + cs->bcs[1].hw.tiger.rec = NULL; + } +} + +void +release_io_netjet(struct IsdnCardState *cs) +{ + byteout(cs->hw.njet.base + NETJET_IRQMASK0, 0); + byteout(cs->hw.njet.base + NETJET_IRQMASK1, 0); + releasetiger(cs); + release_region(cs->hw.njet.base, 256); +} + diff --git a/drivers/isdn/hisax/netjet.h b/drivers/isdn/hisax/netjet.h new file mode 100644 index 000000000000..1080508f3c6a --- /dev/null +++ b/drivers/isdn/hisax/netjet.h @@ -0,0 +1,72 @@ +/* $Id: netjet.h,v 2.8.2.2 2004/01/12 22:52:28 keil Exp $ + * + * NETjet common header file + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * by Matt Henderson, + * Traverse Technologies P/L www.traverse.com.au + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +extern const char *CardType[]; + +#define byteout(addr,val) outb(val,addr) +#define bytein(addr) inb(addr) + +#define NETJET_CTRL 0x00 +#define NETJET_DMACTRL 0x01 +#define NETJET_AUXCTRL 0x02 +#define NETJET_AUXDATA 0x03 +#define NETJET_IRQMASK0 0x04 +#define NETJET_IRQMASK1 0x05 +#define NETJET_IRQSTAT0 0x06 +#define NETJET_IRQSTAT1 0x07 +#define NETJET_DMA_READ_START 0x08 +#define NETJET_DMA_READ_IRQ 0x0c +#define NETJET_DMA_READ_END 0x10 +#define NETJET_DMA_READ_ADR 0x14 +#define NETJET_DMA_WRITE_START 0x18 +#define NETJET_DMA_WRITE_IRQ 0x1c +#define NETJET_DMA_WRITE_END 0x20 +#define NETJET_DMA_WRITE_ADR 0x24 +#define NETJET_PULSE_CNT 0x28 + +#define NETJET_ISAC_OFF 0xc0 +#define NETJET_ISACIRQ 0x10 +#define NETJET_IRQM0_READ 0x0c +#define NETJET_IRQM0_READ_1 0x04 +#define NETJET_IRQM0_READ_2 0x08 +#define NETJET_IRQM0_WRITE 0x03 +#define NETJET_IRQM0_WRITE_1 0x01 +#define NETJET_IRQM0_WRITE_2 0x02 + +#define NETJET_DMA_TXSIZE 512 +#define NETJET_DMA_RXSIZE 128 + +#define HDLC_ZERO_SEARCH 0 +#define HDLC_FLAG_SEARCH 1 +#define HDLC_FLAG_FOUND 2 +#define HDLC_FRAME_FOUND 3 +#define HDLC_NULL 4 +#define HDLC_PART 5 +#define HDLC_FULL 6 + +#define HDLC_FLAG_VALUE 0x7e + +u_char NETjet_ReadIC(struct IsdnCardState *cs, u_char offset); +void NETjet_WriteIC(struct IsdnCardState *cs, u_char offset, u_char value); +void NETjet_ReadICfifo(struct IsdnCardState *cs, u_char *data, int size); +void NETjet_WriteICfifo(struct IsdnCardState *cs, u_char *data, int size); + +void read_tiger(struct IsdnCardState *cs); +void write_tiger(struct IsdnCardState *cs); + +void netjet_fill_dma(struct BCState *bcs); +void netjet_interrupt(int intno, void *dev_id, struct pt_regs *regs); +void inittiger(struct IsdnCardState *cs); +void release_io_netjet(struct IsdnCardState *cs); + diff --git a/drivers/isdn/hisax/niccy.c b/drivers/isdn/hisax/niccy.c new file mode 100644 index 000000000000..cf77d8360975 --- /dev/null +++ b/drivers/isdn/hisax/niccy.c @@ -0,0 +1,389 @@ +/* $Id: niccy.c,v 1.21.2.4 2004/01/13 23:48:39 keil Exp $ + * + * low level stuff for Dr. Neuhaus NICCY PnP and NICCY PCI and + * compatible (SAGEM cybermodem) + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * Thanks to Dr. Neuhaus and SAGEM for information + * + */ + + +#include <linux/config.h> +#include <linux/init.h> +#include "hisax.h" +#include "isac.h" +#include "hscx.h" +#include "isdnl1.h" +#include <linux/pci.h> +#include <linux/isapnp.h> + +extern const char *CardType[]; +const char *niccy_revision = "$Revision: 1.21.2.4 $"; + +#define byteout(addr,val) outb(val,addr) +#define bytein(addr) inb(addr) + +#define ISAC_PCI_DATA 0 +#define HSCX_PCI_DATA 1 +#define ISAC_PCI_ADDR 2 +#define HSCX_PCI_ADDR 3 +#define ISAC_PNP 0 +#define HSCX_PNP 1 + +/* SUB Types */ +#define NICCY_PNP 1 +#define NICCY_PCI 2 + +/* PCI stuff */ +#define PCI_IRQ_CTRL_REG 0x38 +#define PCI_IRQ_ENABLE 0x1f00 +#define PCI_IRQ_DISABLE 0xff0000 +#define PCI_IRQ_ASSERT 0x800000 + +static inline u_char +readreg(unsigned int ale, unsigned int adr, u_char off) +{ + register u_char ret; + + byteout(ale, off); + ret = bytein(adr); + return (ret); +} + +static inline void +readfifo(unsigned int ale, unsigned int adr, u_char off, u_char * data, int size) +{ + byteout(ale, off); + insb(adr, data, size); +} + + +static inline void +writereg(unsigned int ale, unsigned int adr, u_char off, u_char data) +{ + byteout(ale, off); + byteout(adr, data); +} + +static inline void +writefifo(unsigned int ale, unsigned int adr, u_char off, u_char * data, int size) +{ + byteout(ale, off); + outsb(adr, data, size); +} + +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + return (readreg(cs->hw.niccy.isac_ale, cs->hw.niccy.isac, offset)); +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writereg(cs->hw.niccy.isac_ale, cs->hw.niccy.isac, offset, value); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + readfifo(cs->hw.niccy.isac_ale, cs->hw.niccy.isac, 0, data, size); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + writefifo(cs->hw.niccy.isac_ale, cs->hw.niccy.isac, 0, data, size); +} + +static u_char +ReadHSCX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + return (readreg(cs->hw.niccy.hscx_ale, + cs->hw.niccy.hscx, offset + (hscx ? 0x40 : 0))); +} + +static void +WriteHSCX(struct IsdnCardState *cs, int hscx, u_char offset, u_char value) +{ + writereg(cs->hw.niccy.hscx_ale, + cs->hw.niccy.hscx, offset + (hscx ? 0x40 : 0), value); +} + +#define READHSCX(cs, nr, reg) readreg(cs->hw.niccy.hscx_ale, \ + cs->hw.niccy.hscx, reg + (nr ? 0x40 : 0)) +#define WRITEHSCX(cs, nr, reg, data) writereg(cs->hw.niccy.hscx_ale, \ + cs->hw.niccy.hscx, reg + (nr ? 0x40 : 0), data) + +#define READHSCXFIFO(cs, nr, ptr, cnt) readfifo(cs->hw.niccy.hscx_ale, \ + cs->hw.niccy.hscx, (nr ? 0x40 : 0), ptr, cnt) + +#define WRITEHSCXFIFO(cs, nr, ptr, cnt) writefifo(cs->hw.niccy.hscx_ale, \ + cs->hw.niccy.hscx, (nr ? 0x40 : 0), ptr, cnt) + +#include "hscx_irq.c" + +static irqreturn_t +niccy_interrupt(int intno, void *dev_id, struct pt_regs *regs) +{ + struct IsdnCardState *cs = dev_id; + u_char val; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + if (cs->subtyp == NICCY_PCI) { + int ival; + ival = inl(cs->hw.niccy.cfg_reg + PCI_IRQ_CTRL_REG); + if (!(ival & PCI_IRQ_ASSERT)) { /* IRQ not for us (shared) */ + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_NONE; + } + outl(ival, cs->hw.niccy.cfg_reg + PCI_IRQ_CTRL_REG); + } + val = readreg(cs->hw.niccy.hscx_ale, cs->hw.niccy.hscx, HSCX_ISTA + 0x40); + Start_HSCX: + if (val) + hscx_int_main(cs, val); + val = readreg(cs->hw.niccy.isac_ale, cs->hw.niccy.isac, ISAC_ISTA); + Start_ISAC: + if (val) + isac_interrupt(cs, val); + val = readreg(cs->hw.niccy.hscx_ale, cs->hw.niccy.hscx, HSCX_ISTA + 0x40); + if (val) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HSCX IntStat after IntRoutine"); + goto Start_HSCX; + } + val = readreg(cs->hw.niccy.isac_ale, cs->hw.niccy.isac, ISAC_ISTA); + if (val) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ISAC IntStat after IntRoutine"); + goto Start_ISAC; + } + writereg(cs->hw.niccy.hscx_ale, cs->hw.niccy.hscx, HSCX_MASK, 0xFF); + writereg(cs->hw.niccy.hscx_ale, cs->hw.niccy.hscx, HSCX_MASK + 0x40, 0xFF); + writereg(cs->hw.niccy.isac_ale, cs->hw.niccy.isac, ISAC_MASK, 0xFF); + writereg(cs->hw.niccy.isac_ale, cs->hw.niccy.isac, ISAC_MASK, 0); + writereg(cs->hw.niccy.hscx_ale, cs->hw.niccy.hscx, HSCX_MASK, 0); + writereg(cs->hw.niccy.hscx_ale, cs->hw.niccy.hscx, HSCX_MASK + 0x40, 0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +void +release_io_niccy(struct IsdnCardState *cs) +{ + if (cs->subtyp == NICCY_PCI) { + int val; + + val = inl(cs->hw.niccy.cfg_reg + PCI_IRQ_CTRL_REG); + val &= PCI_IRQ_DISABLE; + outl(val, cs->hw.niccy.cfg_reg + PCI_IRQ_CTRL_REG); + release_region(cs->hw.niccy.cfg_reg, 0x40); + release_region(cs->hw.niccy.isac, 4); + } else { + release_region(cs->hw.niccy.isac, 2); + release_region(cs->hw.niccy.isac_ale, 2); + } +} + +static void +niccy_reset(struct IsdnCardState *cs) +{ + if (cs->subtyp == NICCY_PCI) { + int val; + + val = inl(cs->hw.niccy.cfg_reg + PCI_IRQ_CTRL_REG); + val |= PCI_IRQ_ENABLE; + outl(val, cs->hw.niccy.cfg_reg + PCI_IRQ_CTRL_REG); + } + inithscxisac(cs, 3); +} + +static int +niccy_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + niccy_reset(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return(0); + case CARD_RELEASE: + release_io_niccy(cs); + return(0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + niccy_reset(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return(0); + case CARD_TEST: + return(0); + } + return(0); +} + +static struct pci_dev *niccy_dev __initdata = NULL; +#ifdef __ISAPNP__ +static struct pnp_card *pnp_c __devinitdata = NULL; +#endif + +int __init +setup_niccy(struct IsdnCard *card) +{ + struct IsdnCardState *cs = card->cs; + char tmp[64]; + + strcpy(tmp, niccy_revision); + printk(KERN_INFO "HiSax: Niccy driver Rev. %s\n", HiSax_getrev(tmp)); + if (cs->typ != ISDN_CTYPE_NICCY) + return (0); +#ifdef __ISAPNP__ + if (!card->para[1] && isapnp_present()) { + struct pnp_dev *pnp_d = NULL; + int err; + + if ((pnp_c = pnp_find_card( + ISAPNP_VENDOR('S', 'D', 'A'), + ISAPNP_FUNCTION(0x0150), pnp_c))) { + if (!(pnp_d = pnp_find_dev(pnp_c, + ISAPNP_VENDOR('S', 'D', 'A'), + ISAPNP_FUNCTION(0x0150), pnp_d))) { + printk(KERN_ERR "NiccyPnP: PnP error card found, no device\n"); + return (0); + } + pnp_disable_dev(pnp_d); + err = pnp_activate_dev(pnp_d); + if (err<0) { + printk(KERN_WARNING "%s: pnp_activate_dev ret(%d)\n", + __FUNCTION__, err); + return(0); + } + card->para[1] = pnp_port_start(pnp_d, 0); + card->para[2] = pnp_port_start(pnp_d, 1); + card->para[0] = pnp_irq(pnp_d, 0); + if (!card->para[0] || !card->para[1] || !card->para[2]) { + printk(KERN_ERR "NiccyPnP:some resources are missing %ld/%lx/%lx\n", + card->para[0], card->para[1], card->para[2]); + pnp_disable_dev(pnp_d); + return(0); + } + } else { + printk(KERN_INFO "NiccyPnP: no ISAPnP card found\n"); + } + } +#endif + if (card->para[1]) { + cs->hw.niccy.isac = card->para[1] + ISAC_PNP; + cs->hw.niccy.hscx = card->para[1] + HSCX_PNP; + cs->hw.niccy.isac_ale = card->para[2] + ISAC_PNP; + cs->hw.niccy.hscx_ale = card->para[2] + HSCX_PNP; + cs->hw.niccy.cfg_reg = 0; + cs->subtyp = NICCY_PNP; + cs->irq = card->para[0]; + if (!request_region(cs->hw.niccy.isac, 2, "niccy data")) { + printk(KERN_WARNING + "HiSax: %s data port %x-%x already in use\n", + CardType[card->typ], + cs->hw.niccy.isac, + cs->hw.niccy.isac + 1); + return (0); + } + if (!request_region(cs->hw.niccy.isac_ale, 2, "niccy addr")) { + printk(KERN_WARNING + "HiSax: %s address port %x-%x already in use\n", + CardType[card->typ], + cs->hw.niccy.isac_ale, + cs->hw.niccy.isac_ale + 1); + release_region(cs->hw.niccy.isac, 2); + return (0); + } + } else { +#ifdef CONFIG_PCI + u_int pci_ioaddr; + cs->subtyp = 0; + if ((niccy_dev = pci_find_device(PCI_VENDOR_ID_SATSAGEM, + PCI_DEVICE_ID_SATSAGEM_NICCY, niccy_dev))) { + if (pci_enable_device(niccy_dev)) + return(0); + /* get IRQ */ + if (!niccy_dev->irq) { + printk(KERN_WARNING "Niccy: No IRQ for PCI card found\n"); + return(0); + } + cs->irq = niccy_dev->irq; + cs->hw.niccy.cfg_reg = pci_resource_start(niccy_dev, 0); + if (!cs->hw.niccy.cfg_reg) { + printk(KERN_WARNING "Niccy: No IO-Adr for PCI cfg found\n"); + return(0); + } + pci_ioaddr = pci_resource_start(niccy_dev, 1); + if (!pci_ioaddr) { + printk(KERN_WARNING "Niccy: No IO-Adr for PCI card found\n"); + return(0); + } + cs->subtyp = NICCY_PCI; + } else { + printk(KERN_WARNING "Niccy: No PCI card found\n"); + return(0); + } + cs->irq_flags |= SA_SHIRQ; + cs->hw.niccy.isac = pci_ioaddr + ISAC_PCI_DATA; + cs->hw.niccy.isac_ale = pci_ioaddr + ISAC_PCI_ADDR; + cs->hw.niccy.hscx = pci_ioaddr + HSCX_PCI_DATA; + cs->hw.niccy.hscx_ale = pci_ioaddr + HSCX_PCI_ADDR; + if (!request_region(cs->hw.niccy.isac, 4, "niccy")) { + printk(KERN_WARNING + "HiSax: %s data port %x-%x already in use\n", + CardType[card->typ], + cs->hw.niccy.isac, + cs->hw.niccy.isac + 4); + return (0); + } + if (!request_region(cs->hw.niccy.cfg_reg, 0x40, "niccy pci")) { + printk(KERN_WARNING + "HiSax: %s pci port %x-%x already in use\n", + CardType[card->typ], + cs->hw.niccy.cfg_reg, + cs->hw.niccy.cfg_reg + 0x40); + release_region(cs->hw.niccy.isac, 4); + return (0); + } +#else + printk(KERN_WARNING "Niccy: io0 0 and NO_PCI_BIOS\n"); + printk(KERN_WARNING "Niccy: unable to config NICCY PCI\n"); + return (0); +#endif /* CONFIG_PCI */ + } + printk(KERN_INFO "HiSax: %s %s config irq:%d data:0x%X ale:0x%X\n", + CardType[cs->typ], (cs->subtyp==1) ? "PnP":"PCI", + cs->irq, cs->hw.niccy.isac, cs->hw.niccy.isac_ale); + setup_isac(cs); + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->BC_Read_Reg = &ReadHSCX; + cs->BC_Write_Reg = &WriteHSCX; + cs->BC_Send_Data = &hscx_fill_fifo; + cs->cardmsg = &niccy_card_msg; + cs->irq_func = &niccy_interrupt; + ISACVersion(cs, "Niccy:"); + if (HscxVersion(cs, "Niccy:")) { + printk(KERN_WARNING + "Niccy: wrong HSCX versions check IO address\n"); + release_io_niccy(cs); + return (0); + } + return (1); +} diff --git a/drivers/isdn/hisax/nj_s.c b/drivers/isdn/hisax/nj_s.c new file mode 100644 index 000000000000..fd664697f821 --- /dev/null +++ b/drivers/isdn/hisax/nj_s.c @@ -0,0 +1,278 @@ +/* $Id: nj_s.c,v 2.13.2.4 2004/01/16 01:53:48 keil Exp $ + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/config.h> +#include <linux/init.h> +#include "hisax.h" +#include "isac.h" +#include "isdnl1.h" +#include <linux/pci.h> +#include <linux/interrupt.h> +#include <linux/ppp_defs.h> +#include "netjet.h" + +const char *NETjet_S_revision = "$Revision: 2.13.2.4 $"; + +static u_char dummyrr(struct IsdnCardState *cs, int chan, u_char off) +{ + return(5); +} + +static void dummywr(struct IsdnCardState *cs, int chan, u_char off, u_char value) +{ +} + +static irqreturn_t +netjet_s_interrupt(int intno, void *dev_id, struct pt_regs *regs) +{ + struct IsdnCardState *cs = dev_id; + u_char val, s1val, s0val; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + s1val = bytein(cs->hw.njet.base + NETJET_IRQSTAT1); + if (!(s1val & NETJET_ISACIRQ)) { + val = NETjet_ReadIC(cs, ISAC_ISTA); + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "tiger: i1 %x %x", s1val, val); + if (val) { + isac_interrupt(cs, val); + NETjet_WriteIC(cs, ISAC_MASK, 0xFF); + NETjet_WriteIC(cs, ISAC_MASK, 0x0); + } + s1val = 1; + } else + s1val = 0; + /* + * read/write stat0 is better, because lower IRQ rate + * Note the IRQ is on for 125 us if a condition match + * thats long on modern CPU and so the IRQ is reentered + * all the time. + */ + s0val = bytein(cs->hw.njet.base + NETJET_IRQSTAT0); + if ((s0val | s1val)==0) { // shared IRQ + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_NONE; + } + if (s0val) + byteout(cs->hw.njet.base + NETJET_IRQSTAT0, s0val); + /* start new code 13/07/00 GE */ + /* set bits in sval to indicate which page is free */ + if (inl(cs->hw.njet.base + NETJET_DMA_WRITE_ADR) < + inl(cs->hw.njet.base + NETJET_DMA_WRITE_IRQ)) + /* the 2nd write page is free */ + s0val = 0x08; + else /* the 1st write page is free */ + s0val = 0x04; + if (inl(cs->hw.njet.base + NETJET_DMA_READ_ADR) < + inl(cs->hw.njet.base + NETJET_DMA_READ_IRQ)) + /* the 2nd read page is free */ + s0val |= 0x02; + else /* the 1st read page is free */ + s0val |= 0x01; + if (s0val != cs->hw.njet.last_is0) /* we have a DMA interrupt */ + { + if (test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + printk(KERN_WARNING "nj LOCK_ATOMIC s0val %x->%x\n", + cs->hw.njet.last_is0, s0val); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; + } + cs->hw.njet.irqstat0 = s0val; + if ((cs->hw.njet.irqstat0 & NETJET_IRQM0_READ) != + (cs->hw.njet.last_is0 & NETJET_IRQM0_READ)) + /* we have a read dma int */ + read_tiger(cs); + if ((cs->hw.njet.irqstat0 & NETJET_IRQM0_WRITE) != + (cs->hw.njet.last_is0 & NETJET_IRQM0_WRITE)) + /* we have a write dma int */ + write_tiger(cs); + /* end new code 13/07/00 GE */ + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static void +reset_netjet_s(struct IsdnCardState *cs) +{ + cs->hw.njet.ctrl_reg = 0xff; /* Reset On */ + byteout(cs->hw.njet.base + NETJET_CTRL, cs->hw.njet.ctrl_reg); + mdelay(10); + /* now edge triggered for TJ320 GE 13/07/00 */ + /* see comment in IRQ function */ + if (cs->subtyp) /* TJ320 */ + cs->hw.njet.ctrl_reg = 0x40; /* Reset Off and status read clear */ + else + cs->hw.njet.ctrl_reg = 0x00; /* Reset Off and status read clear */ + byteout(cs->hw.njet.base + NETJET_CTRL, cs->hw.njet.ctrl_reg); + mdelay(10); + cs->hw.njet.auxd = 0; + cs->hw.njet.dmactrl = 0; + byteout(cs->hw.njet.base + NETJET_AUXCTRL, ~NETJET_ISACIRQ); + byteout(cs->hw.njet.base + NETJET_IRQMASK1, NETJET_ISACIRQ); + byteout(cs->hw.njet.auxa, cs->hw.njet.auxd); +} + +static int +NETjet_S_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + reset_netjet_s(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return(0); + case CARD_RELEASE: + release_io_netjet(cs); + return(0); + case CARD_INIT: + reset_netjet_s(cs); + inittiger(cs); + spin_lock_irqsave(&cs->lock, flags); + clear_pending_isac_ints(cs); + initisac(cs); + /* Reenable all IRQ */ + cs->writeisac(cs, ISAC_MASK, 0); + spin_unlock_irqrestore(&cs->lock, flags); + return(0); + case CARD_TEST: + return(0); + } + return(0); +} + +static struct pci_dev *dev_netjet __initdata = NULL; + +int __init +setup_netjet_s(struct IsdnCard *card) +{ + int bytecnt,cfg; + struct IsdnCardState *cs = card->cs; + char tmp[64]; + +#ifdef __BIG_ENDIAN +#error "not running on big endian machines now" +#endif + strcpy(tmp, NETjet_S_revision); + printk(KERN_INFO "HiSax: Traverse Tech. NETjet-S driver Rev. %s\n", HiSax_getrev(tmp)); + if (cs->typ != ISDN_CTYPE_NETJET_S) + return(0); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + +#ifdef CONFIG_PCI + + for ( ;; ) + { + if ((dev_netjet = pci_find_device(PCI_VENDOR_ID_TIGERJET, + PCI_DEVICE_ID_TIGERJET_300, dev_netjet))) { + if (pci_enable_device(dev_netjet)) + return(0); + pci_set_master(dev_netjet); + cs->irq = dev_netjet->irq; + if (!cs->irq) { + printk(KERN_WARNING "NETjet-S: No IRQ for PCI card found\n"); + return(0); + } + cs->hw.njet.base = pci_resource_start(dev_netjet, 0); + if (!cs->hw.njet.base) { + printk(KERN_WARNING "NETjet-S: No IO-Adr for PCI card found\n"); + return(0); + } + /* the TJ300 and TJ320 must be detected, the IRQ handling is different + * unfortunatly the chips use the same device ID, but the TJ320 has + * the bit20 in status PCI cfg register set + */ + pci_read_config_dword(dev_netjet, 0x04, &cfg); + if (cfg & 0x00100000) + cs->subtyp = 1; /* TJ320 */ + else + cs->subtyp = 0; /* TJ300 */ + /* 2001/10/04 Christoph Ersfeld, Formula-n Europe AG www.formula-n.com */ + if ((dev_netjet->subsystem_vendor == 0x55) && + (dev_netjet->subsystem_device == 0x02)) { + printk(KERN_WARNING "Netjet: You tried to load this driver with an incompatible TigerJet-card\n"); + printk(KERN_WARNING "Use type=41 for Formula-n enter:now ISDN PCI and compatible\n"); + return(0); + } + /* end new code */ + } else { + printk(KERN_WARNING "NETjet-S: No PCI card found\n"); + return(0); + } + + cs->hw.njet.auxa = cs->hw.njet.base + NETJET_AUXDATA; + cs->hw.njet.isac = cs->hw.njet.base | NETJET_ISAC_OFF; + + cs->hw.njet.ctrl_reg = 0xff; /* Reset On */ + byteout(cs->hw.njet.base + NETJET_CTRL, cs->hw.njet.ctrl_reg); + mdelay(10); + + cs->hw.njet.ctrl_reg = 0x00; /* Reset Off and status read clear */ + byteout(cs->hw.njet.base + NETJET_CTRL, cs->hw.njet.ctrl_reg); + mdelay(10); + + cs->hw.njet.auxd = 0xC0; + cs->hw.njet.dmactrl = 0; + + byteout(cs->hw.njet.base + NETJET_AUXCTRL, ~NETJET_ISACIRQ); + byteout(cs->hw.njet.base + NETJET_IRQMASK1, NETJET_ISACIRQ); + byteout(cs->hw.njet.auxa, cs->hw.njet.auxd); + + switch ( ( ( NETjet_ReadIC( cs, ISAC_RBCH ) >> 5 ) & 3 ) ) + { + case 0 : + break; + + case 3 : + printk( KERN_WARNING "NETjet-S: NETspider-U PCI card found\n" ); + continue; + + default : + printk( KERN_WARNING "NETjet-S: No PCI card found\n" ); + return 0; + } + break; + } +#else + + printk(KERN_WARNING "NETjet-S: NO_PCI_BIOS\n"); + printk(KERN_WARNING "NETjet-S: unable to config NETJET-S PCI\n"); + return (0); + +#endif /* CONFIG_PCI */ + + bytecnt = 256; + + printk(KERN_INFO + "NETjet-S: %s card configured at %#lx IRQ %d\n", + cs->subtyp ? "TJ320" : "TJ300", cs->hw.njet.base, cs->irq); + if (!request_region(cs->hw.njet.base, bytecnt, "netjet-s isdn")) { + printk(KERN_WARNING + "HiSax: %s config port %#lx-%#lx already in use\n", + CardType[card->typ], + cs->hw.njet.base, + cs->hw.njet.base + bytecnt); + return (0); + } + cs->readisac = &NETjet_ReadIC; + cs->writeisac = &NETjet_WriteIC; + cs->readisacfifo = &NETjet_ReadICfifo; + cs->writeisacfifo = &NETjet_WriteICfifo; + cs->BC_Read_Reg = &dummyrr; + cs->BC_Write_Reg = &dummywr; + cs->BC_Send_Data = &netjet_fill_dma; + setup_isac(cs); + cs->cardmsg = &NETjet_S_card_msg; + cs->irq_func = &netjet_s_interrupt; + cs->irq_flags |= SA_SHIRQ; + ISACVersion(cs, "NETjet-S:"); + return (1); +} diff --git a/drivers/isdn/hisax/nj_u.c b/drivers/isdn/hisax/nj_u.c new file mode 100644 index 000000000000..3d6441e9633c --- /dev/null +++ b/drivers/isdn/hisax/nj_u.c @@ -0,0 +1,244 @@ +/* $Id: nj_u.c,v 2.14.2.3 2004/01/13 14:31:26 keil Exp $ + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/config.h> +#include <linux/init.h> +#include "hisax.h" +#include "icc.h" +#include "isdnl1.h" +#include <linux/pci.h> +#include <linux/interrupt.h> +#include <linux/ppp_defs.h> +#include "netjet.h" + +const char *NETjet_U_revision = "$Revision: 2.14.2.3 $"; + +static u_char dummyrr(struct IsdnCardState *cs, int chan, u_char off) +{ + return(5); +} + +static void dummywr(struct IsdnCardState *cs, int chan, u_char off, u_char value) +{ +} + +static irqreturn_t +netjet_u_interrupt(int intno, void *dev_id, struct pt_regs *regs) +{ + struct IsdnCardState *cs = dev_id; + u_char val, sval; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + if (!((sval = bytein(cs->hw.njet.base + NETJET_IRQSTAT1)) & + NETJET_ISACIRQ)) { + val = NETjet_ReadIC(cs, ICC_ISTA); + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "tiger: i1 %x %x", sval, val); + if (val) { + icc_interrupt(cs, val); + NETjet_WriteIC(cs, ICC_MASK, 0xFF); + NETjet_WriteIC(cs, ICC_MASK, 0x0); + } + } + /* start new code 13/07/00 GE */ + /* set bits in sval to indicate which page is free */ + if (inl(cs->hw.njet.base + NETJET_DMA_WRITE_ADR) < + inl(cs->hw.njet.base + NETJET_DMA_WRITE_IRQ)) + /* the 2nd write page is free */ + sval = 0x08; + else /* the 1st write page is free */ + sval = 0x04; + if (inl(cs->hw.njet.base + NETJET_DMA_READ_ADR) < + inl(cs->hw.njet.base + NETJET_DMA_READ_IRQ)) + /* the 2nd read page is free */ + sval = sval | 0x02; + else /* the 1st read page is free */ + sval = sval | 0x01; + if (sval != cs->hw.njet.last_is0) /* we have a DMA interrupt */ + { + if (test_and_set_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags)) { + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; + } + cs->hw.njet.irqstat0 = sval; + if ((cs->hw.njet.irqstat0 & NETJET_IRQM0_READ) != + (cs->hw.njet.last_is0 & NETJET_IRQM0_READ)) + /* we have a read dma int */ + read_tiger(cs); + if ((cs->hw.njet.irqstat0 & NETJET_IRQM0_WRITE) != + (cs->hw.njet.last_is0 & NETJET_IRQM0_WRITE)) + /* we have a write dma int */ + write_tiger(cs); + /* end new code 13/07/00 GE */ + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + } + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static void +reset_netjet_u(struct IsdnCardState *cs) +{ + cs->hw.njet.ctrl_reg = 0xff; /* Reset On */ + byteout(cs->hw.njet.base + NETJET_CTRL, cs->hw.njet.ctrl_reg); + mdelay(10); + cs->hw.njet.ctrl_reg = 0x40; /* Reset Off and status read clear */ + /* now edge triggered for TJ320 GE 13/07/00 */ + byteout(cs->hw.njet.base + NETJET_CTRL, cs->hw.njet.ctrl_reg); + mdelay(10); + cs->hw.njet.auxd = 0xC0; + cs->hw.njet.dmactrl = 0; + byteout(cs->hw.njet.auxa, 0); + byteout(cs->hw.njet.base + NETJET_AUXCTRL, ~NETJET_ISACIRQ); + byteout(cs->hw.njet.base + NETJET_IRQMASK1, NETJET_ISACIRQ); + byteout(cs->hw.njet.auxa, cs->hw.njet.auxd); +} + +static int +NETjet_U_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + reset_netjet_u(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return(0); + case CARD_RELEASE: + release_io_netjet(cs); + return(0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + inittiger(cs); + reset_netjet_u(cs); + clear_pending_icc_ints(cs); + initicc(cs); + /* Reenable all IRQ */ + cs->writeisac(cs, ICC_MASK, 0); + spin_unlock_irqrestore(&cs->lock, flags); + return(0); + case CARD_TEST: + return(0); + } + return(0); +} + +static struct pci_dev *dev_netjet __initdata = NULL; + +int __init +setup_netjet_u(struct IsdnCard *card) +{ + int bytecnt; + struct IsdnCardState *cs = card->cs; + char tmp[64]; +#ifdef CONFIG_PCI +#endif +#ifdef __BIG_ENDIAN +#error "not running on big endian machines now" +#endif + strcpy(tmp, NETjet_U_revision); + printk(KERN_INFO "HiSax: Traverse Tech. NETspider-U driver Rev. %s\n", HiSax_getrev(tmp)); + if (cs->typ != ISDN_CTYPE_NETJET_U) + return(0); + test_and_clear_bit(FLG_LOCK_ATOMIC, &cs->HW_Flags); + +#ifdef CONFIG_PCI + + for ( ;; ) + { + if ((dev_netjet = pci_find_device(PCI_VENDOR_ID_TIGERJET, + PCI_DEVICE_ID_TIGERJET_300, dev_netjet))) { + if (pci_enable_device(dev_netjet)) + return(0); + pci_set_master(dev_netjet); + cs->irq = dev_netjet->irq; + if (!cs->irq) { + printk(KERN_WARNING "NETspider-U: No IRQ for PCI card found\n"); + return(0); + } + cs->hw.njet.base = pci_resource_start(dev_netjet, 0); + if (!cs->hw.njet.base) { + printk(KERN_WARNING "NETspider-U: No IO-Adr for PCI card found\n"); + return(0); + } + } else { + printk(KERN_WARNING "NETspider-U: No PCI card found\n"); + return(0); + } + + cs->hw.njet.auxa = cs->hw.njet.base + NETJET_AUXDATA; + cs->hw.njet.isac = cs->hw.njet.base | NETJET_ISAC_OFF; + mdelay(10); + + cs->hw.njet.ctrl_reg = 0xff; /* Reset On */ + byteout(cs->hw.njet.base + NETJET_CTRL, cs->hw.njet.ctrl_reg); + mdelay(10); + + cs->hw.njet.ctrl_reg = 0x00; /* Reset Off and status read clear */ + byteout(cs->hw.njet.base + NETJET_CTRL, cs->hw.njet.ctrl_reg); + mdelay(10); + + cs->hw.njet.auxd = 0xC0; + cs->hw.njet.dmactrl = 0; + + byteout(cs->hw.njet.auxa, 0); + byteout(cs->hw.njet.base + NETJET_AUXCTRL, ~NETJET_ISACIRQ); + byteout(cs->hw.njet.base + NETJET_IRQMASK1, NETJET_ISACIRQ); + byteout(cs->hw.njet.auxa, cs->hw.njet.auxd); + + switch ( ( ( NETjet_ReadIC( cs, ICC_RBCH ) >> 5 ) & 3 ) ) + { + case 3 : + break; + + case 0 : + printk( KERN_WARNING "NETspider-U: NETjet-S PCI card found\n" ); + continue; + + default : + printk( KERN_WARNING "NETspider-U: No PCI card found\n" ); + return 0; + } + break; + } +#else + + printk(KERN_WARNING "NETspider-U: NO_PCI_BIOS\n"); + printk(KERN_WARNING "NETspider-U: unable to config NETspider-U PCI\n"); + return (0); + +#endif /* CONFIG_PCI */ + + bytecnt = 256; + + printk(KERN_INFO + "NETspider-U: PCI card configured at %#lx IRQ %d\n", + cs->hw.njet.base, cs->irq); + if (!request_region(cs->hw.njet.base, bytecnt, "netspider-u isdn")) { + printk(KERN_WARNING + "HiSax: %s config port %#lx-%#lx already in use\n", + CardType[card->typ], + cs->hw.njet.base, + cs->hw.njet.base + bytecnt); + return (0); + } + setup_icc(cs); + cs->readisac = &NETjet_ReadIC; + cs->writeisac = &NETjet_WriteIC; + cs->readisacfifo = &NETjet_ReadICfifo; + cs->writeisacfifo = &NETjet_WriteICfifo; + cs->BC_Read_Reg = &dummyrr; + cs->BC_Write_Reg = &dummywr; + cs->BC_Send_Data = &netjet_fill_dma; + cs->cardmsg = &NETjet_U_card_msg; + cs->irq_func = &netjet_u_interrupt; + cs->irq_flags |= SA_SHIRQ; + ICCVersion(cs, "NETspider-U:"); + return (1); +} diff --git a/drivers/isdn/hisax/q931.c b/drivers/isdn/hisax/q931.c new file mode 100644 index 000000000000..170fcd4a3984 --- /dev/null +++ b/drivers/isdn/hisax/q931.c @@ -0,0 +1,1522 @@ +/* $Id: q931.c,v 1.12.2.3 2004/01/13 14:31:26 keil Exp $ + * + * code to decode ITU Q.931 call control messages + * + * Author Jan den Ouden + * Copyright by Jan den Ouden + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * Changelog: + * + * Pauline Middelink general improvements + * Beat Doebeli cause texts, display information element + * Karsten Keil cause texts, display information element for 1TR6 + * + */ + + +#include "hisax.h" +#include "l3_1tr6.h" + +void +iecpy(u_char * dest, u_char * iestart, int ieoffset) +{ + u_char *p; + int l; + + p = iestart + ieoffset + 2; + l = iestart[1] - ieoffset; + while (l--) + *dest++ = *p++; + *dest++ = '\0'; +} + +/* + * According to Table 4-2/Q.931 + */ +static +struct MessageType { + u_char nr; + char *descr; +} mtlist[] = { + + { + 0x1, "ALERTING" + }, + { + 0x2, "CALL PROCEEDING" + }, + { + 0x7, "CONNECT" + }, + { + 0xf, "CONNECT ACKNOWLEDGE" + }, + { + 0x3, "PROGRESS" + }, + { + 0x5, "SETUP" + }, + { + 0xd, "SETUP ACKNOWLEDGE" + }, + { + 0x24, "HOLD" + }, + { + 0x28, "HOLD ACKNOWLEDGE" + }, + { + 0x30, "HOLD REJECT" + }, + { + 0x31, "RETRIEVE" + }, + { + 0x33, "RETRIEVE ACKNOWLEDGE" + }, + { + 0x37, "RETRIEVE REJECT" + }, + { + 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"} +}; + +#define 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"} +}; + +#define MT_N1_LEN (sizeof(mt_n1) / sizeof(struct MessageType)) + + +static int +prbits(char *dest, u_char 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 +u_char * +skipext(u_char * 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 { + u_char 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, u_char * p) +{ + u_char *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, u_char * 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, u_char * 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, u_char * 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, u_char * 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, u_char * 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 +prbearer_ni1(char *dest, u_char * p) +{ + char *dp = dest; + u_char len; + + p++; + len = *p++; + dp += sprintf(dp, " octet 3 "); + dp += prbits(dp, *p, 8, 8); + switch (*p++) { + case 0x80: + dp += sprintf(dp, " Speech"); + break; + case 0x88: + dp += sprintf(dp, " Unrestricted digital information"); + break; + case 0x90: + dp += sprintf(dp, " 3.1 kHz audio"); + break; + default: + dp += sprintf(dp, " Unknown information-transfer capability"); + } + *dp++ = '\n'; + dp += sprintf(dp, " octet 4 "); + dp += prbits(dp, *p, 8, 8); + switch (*p++) { + case 0x90: + dp += sprintf(dp, " 64 kbps, circuit mode"); + break; + case 0xc0: + dp += sprintf(dp, " Packet mode"); + break; + default: + dp += sprintf(dp, " Unknown transfer mode"); + } + *dp++ = '\n'; + if (len > 2) { + dp += sprintf(dp, " octet 5 "); + dp += prbits(dp, *p, 8, 8); + switch (*p++) { + case 0x21: + dp += sprintf(dp, " Rate adaption\n"); + dp += sprintf(dp, " octet 5a "); + dp += prbits(dp, *p, 8, 8); + break; + case 0xa2: + dp += sprintf(dp, " u-law"); + break; + default: + dp += sprintf(dp, " Unknown UI layer 1 protocol"); + } + *dp++ = '\n'; + } + return (dp - dest); +} + +static int +general(char *dest, u_char * 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 +general_ni1(char *dest, u_char * 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, u_char * 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, u_char * 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 +prfeatureind(char *dest, u_char * p) +{ + char *dp = dest; + + p += 2; /* skip id, len */ + dp += sprintf(dp, " octet 3 "); + dp += prbits(dp, *p, 8, 8); + *dp++ = '\n'; + if (!(*p++ & 80)) { + dp += sprintf(dp, " octet 4 "); + dp += prbits(dp, *p++, 8, 8); + *dp++ = '\n'; + } + dp += sprintf(dp, " Status: "); + switch (*p) { + case 0: + dp += sprintf(dp, "Idle"); + break; + case 1: + dp += sprintf(dp, "Active"); + break; + case 2: + dp += sprintf(dp, "Prompt"); + break; + case 3: + dp += sprintf(dp, "Pending"); + break; + default: + dp += sprintf(dp, "(Reserved)"); + break; + } + *dp++ = '\n'; + return (dp - dest); +} + +static +struct DTag { /* Display tags */ + u_char nr; + char *descr; +} dtaglist[] = { + { 0x82, "Continuation" }, + { 0x83, "Called address" }, + { 0x84, "Cause" }, + { 0x85, "Progress indicator" }, + { 0x86, "Notification indicator" }, + { 0x87, "Prompt" }, + { 0x88, "Accumlated digits" }, + { 0x89, "Status" }, + { 0x8a, "Inband" }, + { 0x8b, "Calling address" }, + { 0x8c, "Reason" }, + { 0x8d, "Calling party name" }, + { 0x8e, "Called party name" }, + { 0x8f, "Orignal called name" }, + { 0x90, "Redirecting name" }, + { 0x91, "Connected name" }, + { 0x92, "Originating restrictions" }, + { 0x93, "Date & time of day" }, + { 0x94, "Call Appearance ID" }, + { 0x95, "Feature address" }, + { 0x96, "Redirection name" }, + { 0x9e, "Text" }, +}; +#define DTAGSIZE sizeof(dtaglist)/sizeof(struct DTag) + +static int +disptext_ni1(char *dest, u_char * p) +{ + char *dp = dest; + int l, tag, len, i; + + p++; + l = *p++ - 1; + if (*p++ != 0x80) { + dp += sprintf(dp, " Unknown display type\n"); + return (dp - dest); + } + /* Iterate over all tag,length,text fields */ + while (l > 0) { + tag = *p++; + len = *p++; + l -= len + 2; + /* Don't space or skip */ + if ((tag == 0x80) || (tag == 0x81)) p++; + else { + for (i = 0; i < DTAGSIZE; i++) + if (tag == dtaglist[i].nr) + break; + + /* When not found, give appropriate msg */ + if (i != DTAGSIZE) { + dp += sprintf(dp, " %s: ", dtaglist[i].descr); + while (len--) + *dp++ = *p++; + } else { + dp += sprintf(dp, " (unknown display tag %2x): ", tag); + while (len--) + *dp++ = *p++; + } + dp += sprintf(dp, "\n"); + } + } + return (dp - dest); +} +static int +display(char *dest, u_char * 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, u_char * 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 { + u_char nr; + char *descr; + int (*f) (char *, u_char *); +} 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 ielist_ni1[] = { + { 0x04, "Bearer Capability", prbearer_ni1 }, + { 0x08, "Cause", prcause }, + { 0x14, "Call State", general_ni1 }, + { 0x18, "Channel Identification", prchident }, + { 0x1e, "Progress Indicator", general_ni1 }, + { 0x27, "Notification Indicator", general_ni1 }, + { 0x2c, "Keypad Facility", prtext }, + { 0x32, "Information Request", general_ni1 }, + { 0x34, "Signal", general_ni1 }, + { 0x38, "Feature Activation", general_ni1 }, + { 0x39, "Feature Indication", prfeatureind }, + { 0x3a, "Service Profile Identification (SPID)", prtext }, + { 0x3b, "Endpoint Identifier", general_ni1 }, + { 0x6c, "Calling Party Number", prcalling }, + { 0x6d, "Calling Party Subaddress", general_ni1 }, + { 0x70, "Called Party Number", prcalled }, + { 0x71, "Called Party Subaddress", general_ni1 }, + { 0x74, "Redirecting Number", general_ni1 }, + { 0x78, "Transit Network Selection", general_ni1 }, + { 0x7c, "Low Layer Compatibility", general_ni1 }, + { 0x7d, "High Layer Compatibility", general_ni1 }, +}; + + +#define IESIZE_NI1 sizeof(ielist_ni1)/sizeof(struct InformationElement) + +static +struct InformationElement ielist_ni1_cs5[] = { + { 0x1d, "Operator system access", general_ni1 }, + { 0x2a, "Display text", disptext_ni1 }, +}; + +#define IESIZE_NI1_CS5 sizeof(ielist_ni1_cs5)/sizeof(struct InformationElement) + +static +struct InformationElement ielist_ni1_cs6[] = { + { 0x7b, "Call appearance", general_ni1 }, +}; + +#define IESIZE_NI1_CS6 sizeof(ielist_ni1_cs6)/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} +}; + +#define 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} +}; +#define WE_6_LEN (sizeof(we_6) / sizeof(struct InformationElement)) + +int +QuickHex(char *txt, u_char * p, int cnt) +{ + register int i; + register char *t = txt; + register u_char w; + + for (i = 0; i < cnt; i++) { + *t++ = ' '; + w = (p[i] >> 4) & 0x0f; + if (w < 10) + *t++ = '0' + w; + else + *t++ = 'A' - 10 + w; + w = p[i] & 0x0f; + if (w < 10) + *t++ = '0' + w; + else + *t++ = 'A' - 10 + w; + } + *t++ = 0; + return (t - txt); +} + +void +LogFrame(struct IsdnCardState *cs, u_char * buf, int size) +{ + char *dp; + + if (size < 1) + return; + dp = cs->dlog; + if (size < MAX_DLOG_SPACE / 3 - 10) { + *dp++ = 'H'; + *dp++ = 'E'; + *dp++ = 'X'; + *dp++ = ':'; + dp += QuickHex(dp, buf, size); + dp--; + *dp++ = '\n'; + *dp = 0; + HiSax_putstatus(cs, NULL, cs->dlog); + } else + HiSax_putstatus(cs, "LogFrame: ", "warning Frame too big (%d)", size); +} + +void +dlogframe(struct IsdnCardState *cs, struct sk_buff *skb, int dir) +{ + u_char *bend, *buf; + char *dp; + unsigned char pd, cr_l, cr, mt; + unsigned char sapi, tei, ftyp; + int i, cset = 0, cs_old = 0, cs_fest = 0; + int size, finish = 0; + + if (skb->len < 3) + return; + /* display header */ + dp = cs->dlog; + dp += jiftime(dp, jiffies); + *dp++ = ' '; + sapi = skb->data[0] >> 2; + tei = skb->data[1] >> 1; + ftyp = skb->data[2]; + buf = skb->data; + dp += sprintf(dp, "frame %s ", dir ? "network->user" : "user->network"); + size = skb->len; + + if (tei == GROUP_TEI) { + if (sapi == CTRL_SAPI) { /* sapi 0 */ + if (ftyp == 3) { + dp += sprintf(dp, "broadcast\n"); + buf += 3; + size -= 3; + } else { + dp += sprintf(dp, "no UI broadcast\n"); + finish = 1; + } + } else if (sapi == TEI_SAPI) { + dp += sprintf(dp, "tei management\n"); + finish = 1; + } else { + dp += sprintf(dp, "unknown sapi %d broadcast\n", sapi); + finish = 1; + } + } else { + if (sapi == CTRL_SAPI) { + if (!(ftyp & 1)) { /* IFrame */ + dp += sprintf(dp, "with tei %d\n", tei); + buf += 4; + size -= 4; + } else { + dp += sprintf(dp, "SFrame with tei %d\n", tei); + finish = 1; + } + } else { + dp += sprintf(dp, "unknown sapi %d tei %d\n", sapi, tei); + finish = 1; + } + } + bend = skb->data + skb->len; + if (buf >= bend) { + dp += sprintf(dp, "frame too short\n"); + finish = 1; + } + if (finish) { + *dp = 0; + HiSax_putstatus(cs, NULL, cs->dlog); + return; + } + if ((0xfe & buf[0]) == PROTO_DIS_N0) { /* 1TR6 */ + /* locate message type */ + pd = *buf++; + cr_l = *buf++; + if (cr_l) + cr = *buf++; + else + cr = 0; + mt = *buf++; + if (pd == PROTO_DIS_N0) { /* N0 */ + for (i = 0; i < MT_N0_LEN; i++) + if (mt_n0[i].nr == mt) + break; + /* display message type if it exists */ + if (i == MT_N0_LEN) + dp += sprintf(dp, "callref %d %s size %d unknown message type N0 %x!\n", + cr & 0x7f, (cr & 0x80) ? "called" : "caller", + size, mt); + else + dp += sprintf(dp, "callref %d %s size %d message type %s\n", + cr & 0x7f, (cr & 0x80) ? "called" : "caller", + size, mt_n0[i].descr); + } else { /* N1 */ + for (i = 0; i < MT_N1_LEN; i++) + if (mt_n1[i].nr == mt) + break; + /* display message type if it exists */ + if (i == MT_N1_LEN) + dp += sprintf(dp, "callref %d %s size %d unknown message type N1 %x!\n", + cr & 0x7f, (cr & 0x80) ? "called" : "caller", + size, mt); + else + dp += sprintf(dp, "callref %d %s size %d message type %s\n", + cr & 0x7f, (cr & 0x80) ? "called" : "caller", + size, mt_n1[i].descr); + } + + /* display each information element */ + 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 = cset; + cset = *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 (cset == 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", cset, *buf, buf[1]); + } else if (cset == 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", cset, *buf, buf[1]); + } else + dp += sprintf(dp, " Unknown Codeset %d attribute %x attribute size %d\n", cset, *buf, buf[1]); + /* Skip to next element */ + if (cs_fest == 8) { + cset = cs_old; + cs_old = 0; + cs_fest = 0; + } + buf += buf[1] + 2; + } + } else if ((buf[0] == 8) && (cs->protocol == ISDN_PTYPE_NI1)) { /* NI-1 */ + /* locate message type */ + buf++; + cr_l = *buf++; + if (cr_l) + cr = *buf++; + else + cr = 0; + mt = *buf++; + for (i = 0; i < MTSIZE; i++) + if (mtlist[i].nr == mt) + break; + + /* display message type if it exists */ + if (i == MTSIZE) + dp += sprintf(dp, "callref %d %s size %d unknown message type %x!\n", + cr & 0x7f, (cr & 0x80) ? "called" : "caller", + size, mt); + else + dp += sprintf(dp, "callref %d %s size %d message type %s\n", + cr & 0x7f, (cr & 0x80) ? "called" : "caller", + size, mtlist[i].descr); + + /* display each information element */ + 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 = cset; + cset = *buf & 7; + cs_fest = *buf & 8; + break; + default: + dp += sprintf(dp, " Unknown single-octet IE %x\n", *buf); + break; + } + buf++; + continue; + } + /* No, locate it in the table */ + if (cset == 0) { + for (i = 0; i < IESIZE; i++) + if (*buf == ielist_ni1[i].nr) + break; + + /* When not found, give appropriate msg */ + if (i != IESIZE) { + dp += sprintf(dp, " %s\n", ielist_ni1[i].descr); + dp += ielist_ni1[i].f(dp, buf); + } else + dp += sprintf(dp, " attribute %x attribute size %d\n", *buf, buf[1]); + } else if (cset == 5) { + for (i = 0; i < IESIZE_NI1_CS5; i++) + if (*buf == ielist_ni1_cs5[i].nr) + break; + + /* When not found, give appropriate msg */ + if (i != IESIZE_NI1_CS5) { + dp += sprintf(dp, " %s\n", ielist_ni1_cs5[i].descr); + dp += ielist_ni1_cs5[i].f(dp, buf); + } else + dp += sprintf(dp, " attribute %x attribute size %d\n", *buf, buf[1]); + } else if (cset == 6) { + for (i = 0; i < IESIZE_NI1_CS6; i++) + if (*buf == ielist_ni1_cs6[i].nr) + break; + + /* When not found, give appropriate msg */ + if (i != IESIZE_NI1_CS6) { + dp += sprintf(dp, " %s\n", ielist_ni1_cs6[i].descr); + dp += ielist_ni1_cs6[i].f(dp, buf); + } else + dp += sprintf(dp, " attribute %x attribute size %d\n", *buf, buf[1]); + } else + dp += sprintf(dp, " Unknown Codeset %d attribute %x attribute size %d\n", cset, *buf, buf[1]); + + /* Skip to next element */ + if (cs_fest == 8) { + cset = cs_old; + cs_old = 0; + cs_fest = 0; + } + buf += buf[1] + 2; + } + } else if ((buf[0] == 8) && (cs->protocol == ISDN_PTYPE_EURO)) { /* EURO */ + /* locate message type */ + buf++; + cr_l = *buf++; + if (cr_l) + cr = *buf++; + else + cr = 0; + mt = *buf++; + for (i = 0; i < MTSIZE; i++) + if (mtlist[i].nr == mt) + break; + + /* display message type if it exists */ + if (i == MTSIZE) + dp += sprintf(dp, "callref %d %s size %d unknown message type %x!\n", + cr & 0x7f, (cr & 0x80) ? "called" : "caller", + size, mt); + else + dp += sprintf(dp, "callref %d %s size %d message type %s\n", + cr & 0x7f, (cr & 0x80) ? "called" : "caller", + size, mtlist[i].descr); + + /* display each information element */ + 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; + } + } else { + dp += sprintf(dp, "Unknown protocol %x!", buf[0]); + } + *dp = 0; + HiSax_putstatus(cs, NULL, cs->dlog); +} diff --git a/drivers/isdn/hisax/s0box.c b/drivers/isdn/hisax/s0box.c new file mode 100644 index 000000000000..f3c481384a4e --- /dev/null +++ b/drivers/isdn/hisax/s0box.c @@ -0,0 +1,266 @@ +/* $Id: s0box.c,v 2.6.2.4 2004/01/13 23:48:39 keil Exp $ + * + * low level stuff for Creatix S0BOX + * + * Author Enrik Berkhan + * Copyright by Enrik Berkhan <enrik@starfleet.inka.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/init.h> +#include "hisax.h" +#include "isac.h" +#include "hscx.h" +#include "isdnl1.h" + +extern const char *CardType[]; +const char *s0box_revision = "$Revision: 2.6.2.4 $"; + +static inline void +writereg(unsigned int padr, signed int addr, u_char off, u_char val) { + outb_p(0x1c,padr+2); + outb_p(0x14,padr+2); + outb_p((addr+off)&0x7f,padr); + outb_p(0x16,padr+2); + outb_p(val,padr); + outb_p(0x17,padr+2); + outb_p(0x14,padr+2); + outb_p(0x1c,padr+2); +} + +static u_char nibtab[] = { 1, 9, 5, 0xd, 3, 0xb, 7, 0xf, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 8, 4, 0xc, 2, 0xa, 6, 0xe } ; + +static inline u_char +readreg(unsigned int padr, signed int addr, u_char off) { + register u_char n1, n2; + + outb_p(0x1c,padr+2); + outb_p(0x14,padr+2); + outb_p((addr+off)|0x80,padr); + outb_p(0x16,padr+2); + outb_p(0x17,padr+2); + n1 = (inb_p(padr+1) >> 3) & 0x17; + outb_p(0x16,padr+2); + n2 = (inb_p(padr+1) >> 3) & 0x17; + outb_p(0x14,padr+2); + outb_p(0x1c,padr+2); + return nibtab[n1] | (nibtab[n2] << 4); +} + +static inline void +read_fifo(unsigned int padr, signed int adr, u_char * data, int size) +{ + int i; + register u_char n1, n2; + + outb_p(0x1c, padr+2); + outb_p(0x14, padr+2); + outb_p(adr|0x80, padr); + outb_p(0x16, padr+2); + for (i=0; i<size; i++) { + outb_p(0x17, padr+2); + n1 = (inb_p(padr+1) >> 3) & 0x17; + outb_p(0x16,padr+2); + n2 = (inb_p(padr+1) >> 3) & 0x17; + *(data++)=nibtab[n1] | (nibtab[n2] << 4); + } + outb_p(0x14,padr+2); + outb_p(0x1c,padr+2); + return; +} + +static inline void +write_fifo(unsigned int padr, signed int adr, u_char * data, int size) +{ + int i; + outb_p(0x1c, padr+2); + outb_p(0x14, padr+2); + outb_p(adr&0x7f, padr); + for (i=0; i<size; i++) { + outb_p(0x16, padr+2); + outb_p(*(data++), padr); + outb_p(0x17, padr+2); + } + outb_p(0x14,padr+2); + outb_p(0x1c,padr+2); + return; +} + +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + return (readreg(cs->hw.teles3.cfg_reg, cs->hw.teles3.isac, offset)); +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writereg(cs->hw.teles3.cfg_reg, cs->hw.teles3.isac, offset, value); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + read_fifo(cs->hw.teles3.cfg_reg, cs->hw.teles3.isacfifo, data, size); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + write_fifo(cs->hw.teles3.cfg_reg, cs->hw.teles3.isacfifo, data, size); +} + +static u_char +ReadHSCX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + return (readreg(cs->hw.teles3.cfg_reg, cs->hw.teles3.hscx[hscx], offset)); +} + +static void +WriteHSCX(struct IsdnCardState *cs, int hscx, u_char offset, u_char value) +{ + writereg(cs->hw.teles3.cfg_reg, cs->hw.teles3.hscx[hscx], offset, value); +} + +/* + * fast interrupt HSCX stuff goes here + */ + +#define READHSCX(cs, nr, reg) readreg(cs->hw.teles3.cfg_reg, cs->hw.teles3.hscx[nr], reg) +#define WRITEHSCX(cs, nr, reg, data) writereg(cs->hw.teles3.cfg_reg, cs->hw.teles3.hscx[nr], reg, data) +#define READHSCXFIFO(cs, nr, ptr, cnt) read_fifo(cs->hw.teles3.cfg_reg, cs->hw.teles3.hscxfifo[nr], ptr, cnt) +#define WRITEHSCXFIFO(cs, nr, ptr, cnt) write_fifo(cs->hw.teles3.cfg_reg, cs->hw.teles3.hscxfifo[nr], ptr, cnt) + +#include "hscx_irq.c" + +static irqreturn_t +s0box_interrupt(int intno, void *dev_id, struct pt_regs *regs) +{ +#define MAXCOUNT 5 + struct IsdnCardState *cs = dev_id; + u_char val; + u_long flags; + int count = 0; + + spin_lock_irqsave(&cs->lock, flags); + val = readreg(cs->hw.teles3.cfg_reg, cs->hw.teles3.hscx[1], HSCX_ISTA); + Start_HSCX: + if (val) + hscx_int_main(cs, val); + val = readreg(cs->hw.teles3.cfg_reg, cs->hw.teles3.isac, ISAC_ISTA); + Start_ISAC: + if (val) + isac_interrupt(cs, val); + count++; + val = readreg(cs->hw.teles3.cfg_reg, cs->hw.teles3.hscx[1], HSCX_ISTA); + if (val && count < MAXCOUNT) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HSCX IntStat after IntRoutine"); + goto Start_HSCX; + } + val = readreg(cs->hw.teles3.cfg_reg, cs->hw.teles3.isac, ISAC_ISTA); + if (val && count < MAXCOUNT) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ISAC IntStat after IntRoutine"); + goto Start_ISAC; + } + if (count >= MAXCOUNT) + printk(KERN_WARNING "S0Box: more than %d loops in s0box_interrupt\n", count); + writereg(cs->hw.teles3.cfg_reg, cs->hw.teles3.hscx[0], HSCX_MASK, 0xFF); + writereg(cs->hw.teles3.cfg_reg, cs->hw.teles3.hscx[1], HSCX_MASK, 0xFF); + writereg(cs->hw.teles3.cfg_reg, cs->hw.teles3.isac, ISAC_MASK, 0xFF); + writereg(cs->hw.teles3.cfg_reg, cs->hw.teles3.isac, ISAC_MASK, 0x0); + writereg(cs->hw.teles3.cfg_reg, cs->hw.teles3.hscx[0], HSCX_MASK, 0x0); + writereg(cs->hw.teles3.cfg_reg, cs->hw.teles3.hscx[1], HSCX_MASK, 0x0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +void +release_io_s0box(struct IsdnCardState *cs) +{ + release_region(cs->hw.teles3.cfg_reg, 8); +} + +static int +S0Box_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + switch (mt) { + case CARD_RESET: + break; + case CARD_RELEASE: + release_io_s0box(cs); + break; + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + inithscxisac(cs, 3); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case CARD_TEST: + break; + } + return(0); +} + +int __init +setup_s0box(struct IsdnCard *card) +{ + struct IsdnCardState *cs = card->cs; + char tmp[64]; + + strcpy(tmp, s0box_revision); + printk(KERN_INFO "HiSax: S0Box IO driver Rev. %s\n", HiSax_getrev(tmp)); + if (cs->typ != ISDN_CTYPE_S0BOX) + return (0); + + cs->hw.teles3.cfg_reg = card->para[1]; + cs->hw.teles3.hscx[0] = -0x20; + cs->hw.teles3.hscx[1] = 0x0; + cs->hw.teles3.isac = 0x20; + cs->hw.teles3.isacfifo = cs->hw.teles3.isac + 0x3e; + cs->hw.teles3.hscxfifo[0] = cs->hw.teles3.hscx[0] + 0x3e; + cs->hw.teles3.hscxfifo[1] = cs->hw.teles3.hscx[1] + 0x3e; + cs->irq = card->para[0]; + if (!request_region(cs->hw.teles3.cfg_reg,8, "S0Box parallel I/O")) { + printk(KERN_WARNING + "HiSax: %s ports %x-%x already in use\n", + CardType[cs->typ], + cs->hw.teles3.cfg_reg, + cs->hw.teles3.cfg_reg + 7); + return 0; + } + printk(KERN_INFO + "HiSax: %s config irq:%d isac:0x%x cfg:0x%x\n", + CardType[cs->typ], cs->irq, + cs->hw.teles3.isac, cs->hw.teles3.cfg_reg); + printk(KERN_INFO + "HiSax: hscx A:0x%x hscx B:0x%x\n", + cs->hw.teles3.hscx[0], cs->hw.teles3.hscx[1]); + setup_isac(cs); + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->BC_Read_Reg = &ReadHSCX; + cs->BC_Write_Reg = &WriteHSCX; + cs->BC_Send_Data = &hscx_fill_fifo; + cs->cardmsg = &S0Box_card_msg; + cs->irq_func = &s0box_interrupt; + ISACVersion(cs, "S0Box:"); + if (HscxVersion(cs, "S0Box:")) { + printk(KERN_WARNING + "S0Box: wrong HSCX versions check IO address\n"); + release_io_s0box(cs); + return (0); + } + return (1); +} diff --git a/drivers/isdn/hisax/saphir.c b/drivers/isdn/hisax/saphir.c new file mode 100644 index 000000000000..9e6d3d686cce --- /dev/null +++ b/drivers/isdn/hisax/saphir.c @@ -0,0 +1,300 @@ +/* $Id: saphir.c,v 1.10.2.4 2004/01/13 23:48:39 keil Exp $ + * + * low level stuff for HST Saphir 1 + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * Thanks to HST High Soft Tech GmbH + * + */ + +#include <linux/init.h> +#include "hisax.h" +#include "isac.h" +#include "hscx.h" +#include "isdnl1.h" + +extern const char *CardType[]; +static char *saphir_rev = "$Revision: 1.10.2.4 $"; + +#define byteout(addr,val) outb(val,addr) +#define bytein(addr) inb(addr) + +#define ISAC_DATA 0 +#define HSCX_DATA 1 +#define ADDRESS_REG 2 +#define IRQ_REG 3 +#define SPARE_REG 4 +#define RESET_REG 5 + +static inline u_char +readreg(unsigned int ale, unsigned int adr, u_char off) +{ + register u_char ret; + + byteout(ale, off); + ret = bytein(adr); + return (ret); +} + +static inline void +readfifo(unsigned int ale, unsigned int adr, u_char off, u_char * data, int size) +{ + byteout(ale, off); + insb(adr, data, size); +} + + +static inline void +writereg(unsigned int ale, unsigned int adr, u_char off, u_char data) +{ + byteout(ale, off); + byteout(adr, data); +} + +static inline void +writefifo(unsigned int ale, unsigned int adr, u_char off, u_char * data, int size) +{ + byteout(ale, off); + outsb(adr, data, size); +} + +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + return (readreg(cs->hw.saphir.ale, cs->hw.saphir.isac, offset)); +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writereg(cs->hw.saphir.ale, cs->hw.saphir.isac, offset, value); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + readfifo(cs->hw.saphir.ale, cs->hw.saphir.isac, 0, data, size); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + writefifo(cs->hw.saphir.ale, cs->hw.saphir.isac, 0, data, size); +} + +static u_char +ReadHSCX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + return (readreg(cs->hw.saphir.ale, cs->hw.saphir.hscx, + offset + (hscx ? 0x40 : 0))); +} + +static void +WriteHSCX(struct IsdnCardState *cs, int hscx, u_char offset, u_char value) +{ + writereg(cs->hw.saphir.ale, cs->hw.saphir.hscx, + offset + (hscx ? 0x40 : 0), value); +} + +#define READHSCX(cs, nr, reg) readreg(cs->hw.saphir.ale, \ + cs->hw.saphir.hscx, reg + (nr ? 0x40 : 0)) +#define WRITEHSCX(cs, nr, reg, data) writereg(cs->hw.saphir.ale, \ + cs->hw.saphir.hscx, reg + (nr ? 0x40 : 0), data) + +#define READHSCXFIFO(cs, nr, ptr, cnt) readfifo(cs->hw.saphir.ale, \ + cs->hw.saphir.hscx, (nr ? 0x40 : 0), ptr, cnt) + +#define WRITEHSCXFIFO(cs, nr, ptr, cnt) writefifo(cs->hw.saphir.ale, \ + cs->hw.saphir.hscx, (nr ? 0x40 : 0), ptr, cnt) + +#include "hscx_irq.c" + +static irqreturn_t +saphir_interrupt(int intno, void *dev_id, struct pt_regs *regs) +{ + struct IsdnCardState *cs = dev_id; + u_char val; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + val = readreg(cs->hw.saphir.ale, cs->hw.saphir.hscx, HSCX_ISTA + 0x40); + Start_HSCX: + if (val) + hscx_int_main(cs, val); + val = readreg(cs->hw.saphir.ale, cs->hw.saphir.isac, ISAC_ISTA); + Start_ISAC: + if (val) + isac_interrupt(cs, val); + val = readreg(cs->hw.saphir.ale, cs->hw.saphir.hscx, HSCX_ISTA + 0x40); + if (val) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HSCX IntStat after IntRoutine"); + goto Start_HSCX; + } + val = readreg(cs->hw.saphir.ale, cs->hw.saphir.isac, ISAC_ISTA); + if (val) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ISAC IntStat after IntRoutine"); + goto Start_ISAC; + } + /* Watchdog */ + if (cs->hw.saphir.timer.function) + mod_timer(&cs->hw.saphir.timer, jiffies+1*HZ); + else + printk(KERN_WARNING "saphir: Spurious timer!\n"); + writereg(cs->hw.saphir.ale, cs->hw.saphir.hscx, HSCX_MASK, 0xFF); + writereg(cs->hw.saphir.ale, cs->hw.saphir.hscx, HSCX_MASK + 0x40, 0xFF); + writereg(cs->hw.saphir.ale, cs->hw.saphir.isac, ISAC_MASK, 0xFF); + writereg(cs->hw.saphir.ale, cs->hw.saphir.isac, ISAC_MASK, 0); + writereg(cs->hw.saphir.ale, cs->hw.saphir.hscx, HSCX_MASK, 0); + writereg(cs->hw.saphir.ale, cs->hw.saphir.hscx, HSCX_MASK + 0x40, 0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static void +SaphirWatchDog(struct IsdnCardState *cs) +{ + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + /* 5 sec WatchDog, so read at least every 4 sec */ + cs->readisac(cs, ISAC_RBCH); + spin_unlock_irqrestore(&cs->lock, flags); + mod_timer(&cs->hw.saphir.timer, jiffies+1*HZ); +} + +void +release_io_saphir(struct IsdnCardState *cs) +{ + byteout(cs->hw.saphir.cfg_reg + IRQ_REG, 0xff); + del_timer(&cs->hw.saphir.timer); + cs->hw.saphir.timer.function = NULL; + if (cs->hw.saphir.cfg_reg) + release_region(cs->hw.saphir.cfg_reg, 6); +} + +static int +saphir_reset(struct IsdnCardState *cs) +{ + u_char irq_val; + + switch(cs->irq) { + case 5: irq_val = 0; + break; + case 3: irq_val = 1; + break; + case 11: + irq_val = 2; + break; + case 12: + irq_val = 3; + break; + case 15: + irq_val = 4; + break; + default: + printk(KERN_WARNING "HiSax: saphir wrong IRQ %d\n", + cs->irq); + return (1); + } + byteout(cs->hw.saphir.cfg_reg + IRQ_REG, irq_val); + byteout(cs->hw.saphir.cfg_reg + RESET_REG, 1); + mdelay(10); + byteout(cs->hw.saphir.cfg_reg + RESET_REG, 0); + mdelay(10); + byteout(cs->hw.saphir.cfg_reg + IRQ_REG, irq_val); + byteout(cs->hw.saphir.cfg_reg + SPARE_REG, 0x02); + return (0); +} + +static int +saphir_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + saphir_reset(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return(0); + case CARD_RELEASE: + release_io_saphir(cs); + return(0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + inithscxisac(cs, 3); + spin_unlock_irqrestore(&cs->lock, flags); + return(0); + case CARD_TEST: + return(0); + } + return(0); +} + + +int __init +setup_saphir(struct IsdnCard *card) +{ + struct IsdnCardState *cs = card->cs; + char tmp[64]; + + strcpy(tmp, saphir_rev); + printk(KERN_INFO "HiSax: HST Saphir driver Rev. %s\n", HiSax_getrev(tmp)); + if (cs->typ != ISDN_CTYPE_HSTSAPHIR) + return (0); + + /* IO-Ports */ + cs->hw.saphir.cfg_reg = card->para[1]; + cs->hw.saphir.isac = card->para[1] + ISAC_DATA; + cs->hw.saphir.hscx = card->para[1] + HSCX_DATA; + cs->hw.saphir.ale = card->para[1] + ADDRESS_REG; + cs->irq = card->para[0]; + if (!request_region(cs->hw.saphir.cfg_reg, 6, "saphir")) { + printk(KERN_WARNING + "HiSax: %s config port %x-%x already in use\n", + CardType[card->typ], + cs->hw.saphir.cfg_reg, + cs->hw.saphir.cfg_reg + 5); + return (0); + } + + printk(KERN_INFO "HiSax: %s config irq:%d io:0x%X\n", + CardType[cs->typ], cs->irq, cs->hw.saphir.cfg_reg); + + setup_isac(cs); + cs->hw.saphir.timer.function = (void *) SaphirWatchDog; + cs->hw.saphir.timer.data = (long) cs; + init_timer(&cs->hw.saphir.timer); + cs->hw.saphir.timer.expires = jiffies + 4*HZ; + add_timer(&cs->hw.saphir.timer); + if (saphir_reset(cs)) { + release_io_saphir(cs); + return (0); + } + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->BC_Read_Reg = &ReadHSCX; + cs->BC_Write_Reg = &WriteHSCX; + cs->BC_Send_Data = &hscx_fill_fifo; + cs->cardmsg = &saphir_card_msg; + cs->irq_func = &saphir_interrupt; + ISACVersion(cs, "saphir:"); + if (HscxVersion(cs, "saphir:")) { + printk(KERN_WARNING + "saphir: wrong HSCX versions check IO address\n"); + release_io_saphir(cs); + return (0); + } + return (1); +} diff --git a/drivers/isdn/hisax/sedlbauer.c b/drivers/isdn/hisax/sedlbauer.c new file mode 100644 index 000000000000..8390f1606853 --- /dev/null +++ b/drivers/isdn/hisax/sedlbauer.c @@ -0,0 +1,833 @@ +/* $Id: sedlbauer.c,v 1.34.2.6 2004/01/24 20:47:24 keil Exp $ + * + * low level stuff for Sedlbauer cards + * includes support for the Sedlbauer speed star (speed star II), + * support for the Sedlbauer speed fax+, + * support for the Sedlbauer ISDN-Controller PC/104 and + * support for the Sedlbauer speed pci + * derived from the original file asuscom.c from Karsten Keil + * + * Author Marcus Niemann + * Copyright by Marcus Niemann <niemann@www-bib.fh-bielefeld.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * Thanks to Karsten Keil + * Sedlbauer AG for informations + * Edgar Toernig + * + */ + +/* Supported cards: + * Card: Chip: Configuration: Comment: + * --------------------------------------------------------------------- + * Speed Card ISAC_HSCX DIP-SWITCH + * Speed Win ISAC_HSCX ISAPNP + * Speed Fax+ ISAC_ISAR ISAPNP Full analog support + * Speed Star ISAC_HSCX CARDMGR + * Speed Win2 IPAC ISAPNP + * ISDN PC/104 IPAC DIP-SWITCH + * Speed Star2 IPAC CARDMGR + * Speed PCI IPAC PCI PNP + * Speed Fax+ ISAC_ISAR PCI PNP Full analog support + * + * Important: + * For the sedlbauer speed fax+ to work properly you have to download + * the firmware onto the card. + * For example: hisaxctrl <DriverID> 9 ISAR.BIN +*/ + +#include <linux/init.h> +#include <linux/config.h> +#include "hisax.h" +#include "isac.h" +#include "ipac.h" +#include "hscx.h" +#include "isar.h" +#include "isdnl1.h" +#include <linux/pci.h> +#include <linux/isapnp.h> + +extern const char *CardType[]; + +const char *Sedlbauer_revision = "$Revision: 1.34.2.6 $"; + +const char *Sedlbauer_Types[] = + {"None", "speed card/win", "speed star", "speed fax+", + "speed win II / ISDN PC/104", "speed star II", "speed pci", + "speed fax+ pyramid", "speed fax+ pci", "HST Saphir III"}; + +#define PCI_SUBVENDOR_SPEEDFAX_PYRAMID 0x51 +#define PCI_SUBVENDOR_HST_SAPHIR3 0x52 +#define PCI_SUBVENDOR_SEDLBAUER_PCI 0x53 +#define PCI_SUBVENDOR_SPEEDFAX_PCI 0x54 +#define PCI_SUB_ID_SEDLBAUER 0x01 + +#define SEDL_SPEED_CARD_WIN 1 +#define SEDL_SPEED_STAR 2 +#define SEDL_SPEED_FAX 3 +#define SEDL_SPEED_WIN2_PC104 4 +#define SEDL_SPEED_STAR2 5 +#define SEDL_SPEED_PCI 6 +#define SEDL_SPEEDFAX_PYRAMID 7 +#define SEDL_SPEEDFAX_PCI 8 +#define HST_SAPHIR3 9 + +#define SEDL_CHIP_TEST 0 +#define SEDL_CHIP_ISAC_HSCX 1 +#define SEDL_CHIP_ISAC_ISAR 2 +#define SEDL_CHIP_IPAC 3 + +#define SEDL_BUS_ISA 1 +#define SEDL_BUS_PCI 2 +#define SEDL_BUS_PCMCIA 3 + +#define byteout(addr,val) outb(val,addr) +#define bytein(addr) inb(addr) + +#define SEDL_HSCX_ISA_RESET_ON 0 +#define SEDL_HSCX_ISA_RESET_OFF 1 +#define SEDL_HSCX_ISA_ISAC 2 +#define SEDL_HSCX_ISA_HSCX 3 +#define SEDL_HSCX_ISA_ADR 4 + +#define SEDL_HSCX_PCMCIA_RESET 0 +#define SEDL_HSCX_PCMCIA_ISAC 1 +#define SEDL_HSCX_PCMCIA_HSCX 2 +#define SEDL_HSCX_PCMCIA_ADR 4 + +#define SEDL_ISAR_ISA_ISAC 4 +#define SEDL_ISAR_ISA_ISAR 6 +#define SEDL_ISAR_ISA_ADR 8 +#define SEDL_ISAR_ISA_ISAR_RESET_ON 10 +#define SEDL_ISAR_ISA_ISAR_RESET_OFF 12 + +#define SEDL_IPAC_ANY_ADR 0 +#define SEDL_IPAC_ANY_IPAC 2 + +#define SEDL_IPAC_PCI_BASE 0 +#define SEDL_IPAC_PCI_ADR 0xc0 +#define SEDL_IPAC_PCI_IPAC 0xc8 +#define SEDL_ISAR_PCI_ADR 0xc8 +#define SEDL_ISAR_PCI_ISAC 0xd0 +#define SEDL_ISAR_PCI_ISAR 0xe0 +#define SEDL_ISAR_PCI_ISAR_RESET_ON 0x01 +#define SEDL_ISAR_PCI_ISAR_RESET_OFF 0x18 +#define SEDL_ISAR_PCI_LED1 0x08 +#define SEDL_ISAR_PCI_LED2 0x10 + +#define SEDL_RESET 0x3 /* same as DOS driver */ + +static inline u_char +readreg(unsigned int ale, unsigned int adr, u_char off) +{ + register u_char ret; + + byteout(ale, off); + ret = bytein(adr); + return (ret); +} + +static inline void +readfifo(unsigned int ale, unsigned int adr, u_char off, u_char * data, int size) +{ + byteout(ale, off); + insb(adr, data, size); +} + + +static inline void +writereg(unsigned int ale, unsigned int adr, u_char off, u_char data) +{ + byteout(ale, off); + byteout(adr, data); +} + +static inline void +writefifo(unsigned int ale, unsigned int adr, u_char off, u_char * data, int size) +{ + byteout(ale, off); + outsb(adr, data, size); +} + +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + return (readreg(cs->hw.sedl.adr, cs->hw.sedl.isac, offset)); +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writereg(cs->hw.sedl.adr, cs->hw.sedl.isac, offset, value); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + readfifo(cs->hw.sedl.adr, cs->hw.sedl.isac, 0, data, size); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + writefifo(cs->hw.sedl.adr, cs->hw.sedl.isac, 0, data, size); +} + +static u_char +ReadISAC_IPAC(struct IsdnCardState *cs, u_char offset) +{ + return (readreg(cs->hw.sedl.adr, cs->hw.sedl.isac, offset|0x80)); +} + +static void +WriteISAC_IPAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writereg(cs->hw.sedl.adr, cs->hw.sedl.isac, offset|0x80, value); +} + +static void +ReadISACfifo_IPAC(struct IsdnCardState *cs, u_char * data, int size) +{ + readfifo(cs->hw.sedl.adr, cs->hw.sedl.isac, 0x80, data, size); +} + +static void +WriteISACfifo_IPAC(struct IsdnCardState *cs, u_char * data, int size) +{ + writefifo(cs->hw.sedl.adr, cs->hw.sedl.isac, 0x80, data, size); +} + +static u_char +ReadHSCX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + return (readreg(cs->hw.sedl.adr, + cs->hw.sedl.hscx, offset + (hscx ? 0x40 : 0))); +} + +static void +WriteHSCX(struct IsdnCardState *cs, int hscx, u_char offset, u_char value) +{ + writereg(cs->hw.sedl.adr, + cs->hw.sedl.hscx, offset + (hscx ? 0x40 : 0), value); +} + +/* ISAR access routines + * mode = 0 access with IRQ on + * mode = 1 access with IRQ off + * mode = 2 access with IRQ off and using last offset + */ + +static u_char +ReadISAR(struct IsdnCardState *cs, int mode, u_char offset) +{ + if (mode == 0) + return (readreg(cs->hw.sedl.adr, cs->hw.sedl.hscx, offset)); + else if (mode == 1) + byteout(cs->hw.sedl.adr, offset); + return(bytein(cs->hw.sedl.hscx)); +} + +static void +WriteISAR(struct IsdnCardState *cs, int mode, u_char offset, u_char value) +{ + if (mode == 0) + writereg(cs->hw.sedl.adr, cs->hw.sedl.hscx, offset, value); + else { + if (mode == 1) + byteout(cs->hw.sedl.adr, offset); + byteout(cs->hw.sedl.hscx, value); + } +} + +/* + * fast interrupt HSCX stuff goes here + */ + +#define READHSCX(cs, nr, reg) readreg(cs->hw.sedl.adr, \ + cs->hw.sedl.hscx, reg + (nr ? 0x40 : 0)) +#define WRITEHSCX(cs, nr, reg, data) writereg(cs->hw.sedl.adr, \ + cs->hw.sedl.hscx, reg + (nr ? 0x40 : 0), data) + +#define READHSCXFIFO(cs, nr, ptr, cnt) readfifo(cs->hw.sedl.adr, \ + cs->hw.sedl.hscx, (nr ? 0x40 : 0), ptr, cnt) + +#define WRITEHSCXFIFO(cs, nr, ptr, cnt) writefifo(cs->hw.sedl.adr, \ + cs->hw.sedl.hscx, (nr ? 0x40 : 0), ptr, cnt) + +#include "hscx_irq.c" + +static irqreturn_t +sedlbauer_interrupt(int intno, void *dev_id, struct pt_regs *regs) +{ + struct IsdnCardState *cs = dev_id; + u_char val; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + if ((cs->hw.sedl.bus == SEDL_BUS_PCMCIA) && (*cs->busy_flag == 1)) { + /* The card tends to generate interrupts while being removed + causing us to just crash the kernel. bad. */ + spin_unlock_irqrestore(&cs->lock, flags); + printk(KERN_WARNING "Sedlbauer: card not available!\n"); + return IRQ_NONE; + } + + val = readreg(cs->hw.sedl.adr, cs->hw.sedl.hscx, HSCX_ISTA + 0x40); + Start_HSCX: + if (val) + hscx_int_main(cs, val); + val = readreg(cs->hw.sedl.adr, cs->hw.sedl.isac, ISAC_ISTA); + Start_ISAC: + if (val) + isac_interrupt(cs, val); + val = readreg(cs->hw.sedl.adr, cs->hw.sedl.hscx, HSCX_ISTA + 0x40); + if (val) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HSCX IntStat after IntRoutine"); + goto Start_HSCX; + } + val = readreg(cs->hw.sedl.adr, cs->hw.sedl.isac, ISAC_ISTA); + if (val) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ISAC IntStat after IntRoutine"); + goto Start_ISAC; + } + writereg(cs->hw.sedl.adr, cs->hw.sedl.hscx, HSCX_MASK, 0xFF); + writereg(cs->hw.sedl.adr, cs->hw.sedl.hscx, HSCX_MASK + 0x40, 0xFF); + writereg(cs->hw.sedl.adr, cs->hw.sedl.isac, ISAC_MASK, 0xFF); + writereg(cs->hw.sedl.adr, cs->hw.sedl.isac, ISAC_MASK, 0x0); + writereg(cs->hw.sedl.adr, cs->hw.sedl.hscx, HSCX_MASK, 0x0); + writereg(cs->hw.sedl.adr, cs->hw.sedl.hscx, HSCX_MASK + 0x40, 0x0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static irqreturn_t +sedlbauer_interrupt_ipac(int intno, void *dev_id, struct pt_regs *regs) +{ + struct IsdnCardState *cs = dev_id; + u_char ista, val, icnt = 5; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + ista = readreg(cs->hw.sedl.adr, cs->hw.sedl.isac, IPAC_ISTA); +Start_IPAC: + if (cs->debug & L1_DEB_IPAC) + debugl1(cs, "IPAC ISTA %02X", ista); + if (ista & 0x0f) { + val = readreg(cs->hw.sedl.adr, cs->hw.sedl.hscx, HSCX_ISTA + 0x40); + if (ista & 0x01) + val |= 0x01; + if (ista & 0x04) + val |= 0x02; + if (ista & 0x08) + val |= 0x04; + if (val) + hscx_int_main(cs, val); + } + if (ista & 0x20) { + val = 0xfe & readreg(cs->hw.sedl.adr, cs->hw.sedl.isac, ISAC_ISTA | 0x80); + if (val) { + isac_interrupt(cs, val); + } + } + if (ista & 0x10) { + val = 0x01; + isac_interrupt(cs, val); + } + ista = readreg(cs->hw.sedl.adr, cs->hw.sedl.isac, IPAC_ISTA); + if ((ista & 0x3f) && icnt) { + icnt--; + goto Start_IPAC; + } + if (!icnt) + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "Sedlbauer IRQ LOOP"); + writereg(cs->hw.sedl.adr, cs->hw.sedl.isac, IPAC_MASK, 0xFF); + writereg(cs->hw.sedl.adr, cs->hw.sedl.isac, IPAC_MASK, 0xC0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static irqreturn_t +sedlbauer_interrupt_isar(int intno, void *dev_id, struct pt_regs *regs) +{ + struct IsdnCardState *cs = dev_id; + u_char val; + int cnt = 5; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + val = readreg(cs->hw.sedl.adr, cs->hw.sedl.hscx, ISAR_IRQBIT); + Start_ISAR: + if (val & ISAR_IRQSTA) + isar_int_main(cs); + val = readreg(cs->hw.sedl.adr, cs->hw.sedl.isac, ISAC_ISTA); + Start_ISAC: + if (val) + isac_interrupt(cs, val); + val = readreg(cs->hw.sedl.adr, cs->hw.sedl.hscx, ISAR_IRQBIT); + if ((val & ISAR_IRQSTA) && --cnt) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "ISAR IntStat after IntRoutine"); + goto Start_ISAR; + } + val = readreg(cs->hw.sedl.adr, cs->hw.sedl.isac, ISAC_ISTA); + if (val && --cnt) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ISAC IntStat after IntRoutine"); + goto Start_ISAC; + } + if (!cnt) + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "Sedlbauer IRQ LOOP"); + + writereg(cs->hw.sedl.adr, cs->hw.sedl.hscx, ISAR_IRQBIT, 0); + writereg(cs->hw.sedl.adr, cs->hw.sedl.isac, ISAC_MASK, 0xFF); + writereg(cs->hw.sedl.adr, cs->hw.sedl.isac, ISAC_MASK, 0x0); + writereg(cs->hw.sedl.adr, cs->hw.sedl.hscx, ISAR_IRQBIT, ISAR_IRQMSK); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +void +release_io_sedlbauer(struct IsdnCardState *cs) +{ + int bytecnt = 8; + + if (cs->subtyp == SEDL_SPEED_FAX) { + bytecnt = 16; + } else if (cs->hw.sedl.bus == SEDL_BUS_PCI) { + bytecnt = 256; + } + if (cs->hw.sedl.cfg_reg) + release_region(cs->hw.sedl.cfg_reg, bytecnt); +} + +static void +reset_sedlbauer(struct IsdnCardState *cs) +{ + printk(KERN_INFO "Sedlbauer: resetting card\n"); + + if (!((cs->hw.sedl.bus == SEDL_BUS_PCMCIA) && + (cs->hw.sedl.chip == SEDL_CHIP_ISAC_HSCX))) { + if (cs->hw.sedl.chip == SEDL_CHIP_IPAC) { + writereg(cs->hw.sedl.adr, cs->hw.sedl.isac, IPAC_POTA2, 0x20); + mdelay(2); + writereg(cs->hw.sedl.adr, cs->hw.sedl.isac, IPAC_POTA2, 0x0); + mdelay(10); + writereg(cs->hw.sedl.adr, cs->hw.sedl.isac, IPAC_CONF, 0x0); + writereg(cs->hw.sedl.adr, cs->hw.sedl.isac, IPAC_ACFG, 0xff); + writereg(cs->hw.sedl.adr, cs->hw.sedl.isac, IPAC_AOE, 0x0); + writereg(cs->hw.sedl.adr, cs->hw.sedl.isac, IPAC_MASK, 0xc0); + writereg(cs->hw.sedl.adr, cs->hw.sedl.isac, IPAC_PCFG, 0x12); + } else if ((cs->hw.sedl.chip == SEDL_CHIP_ISAC_ISAR) && + (cs->hw.sedl.bus == SEDL_BUS_PCI)) { + byteout(cs->hw.sedl.cfg_reg +3, cs->hw.sedl.reset_on); + mdelay(2); + byteout(cs->hw.sedl.cfg_reg +3, cs->hw.sedl.reset_off); + mdelay(10); + } else { + byteout(cs->hw.sedl.reset_on, SEDL_RESET); /* Reset On */ + mdelay(2); + byteout(cs->hw.sedl.reset_off, 0); /* Reset Off */ + mdelay(10); + } + } +} + +static int +Sedl_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + reset_sedlbauer(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return(0); + case CARD_RELEASE: + if (cs->hw.sedl.chip == SEDL_CHIP_ISAC_ISAR) { + spin_lock_irqsave(&cs->lock, flags); + writereg(cs->hw.sedl.adr, cs->hw.sedl.hscx, + ISAR_IRQBIT, 0); + writereg(cs->hw.sedl.adr, cs->hw.sedl.isac, + ISAC_MASK, 0xFF); + reset_sedlbauer(cs); + writereg(cs->hw.sedl.adr, cs->hw.sedl.hscx, + ISAR_IRQBIT, 0); + writereg(cs->hw.sedl.adr, cs->hw.sedl.isac, + ISAC_MASK, 0xFF); + spin_unlock_irqrestore(&cs->lock, flags); + } + release_io_sedlbauer(cs); + return(0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + reset_sedlbauer(cs); + if (cs->hw.sedl.chip == SEDL_CHIP_ISAC_ISAR) { + clear_pending_isac_ints(cs); + writereg(cs->hw.sedl.adr, cs->hw.sedl.hscx, + ISAR_IRQBIT, 0); + initisac(cs); + initisar(cs); + /* Reenable all IRQ */ + cs->writeisac(cs, ISAC_MASK, 0); + /* RESET Receiver and Transmitter */ + cs->writeisac(cs, ISAC_CMDR, 0x41); + } else { + inithscxisac(cs, 3); + } + spin_unlock_irqrestore(&cs->lock, flags); + return(0); + case CARD_TEST: + return(0); + case MDL_INFO_CONN: + if (cs->subtyp != SEDL_SPEEDFAX_PYRAMID) + return(0); + spin_lock_irqsave(&cs->lock, flags); + if ((long) arg) + cs->hw.sedl.reset_off &= ~SEDL_ISAR_PCI_LED2; + else + cs->hw.sedl.reset_off &= ~SEDL_ISAR_PCI_LED1; + byteout(cs->hw.sedl.cfg_reg +3, cs->hw.sedl.reset_off); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case MDL_INFO_REL: + if (cs->subtyp != SEDL_SPEEDFAX_PYRAMID) + return(0); + spin_lock_irqsave(&cs->lock, flags); + if ((long) arg) + cs->hw.sedl.reset_off |= SEDL_ISAR_PCI_LED2; + else + cs->hw.sedl.reset_off |= SEDL_ISAR_PCI_LED1; + byteout(cs->hw.sedl.cfg_reg +3, cs->hw.sedl.reset_off); + spin_unlock_irqrestore(&cs->lock, flags); + break; + } + return(0); +} + +static struct pci_dev *dev_sedl __devinitdata = NULL; + +#ifdef __ISAPNP__ +static struct isapnp_device_id sedl_ids[] __devinitdata = { + { ISAPNP_VENDOR('S', 'A', 'G'), ISAPNP_FUNCTION(0x01), + ISAPNP_VENDOR('S', 'A', 'G'), ISAPNP_FUNCTION(0x01), + (unsigned long) "Speed win" }, + { ISAPNP_VENDOR('S', 'A', 'G'), ISAPNP_FUNCTION(0x02), + ISAPNP_VENDOR('S', 'A', 'G'), ISAPNP_FUNCTION(0x02), + (unsigned long) "Speed Fax+" }, + { 0, } +}; + +static struct isapnp_device_id *ipid __devinitdata = &sedl_ids[0]; +static struct pnp_card *pnp_c __devinitdata = NULL; +#endif + +int __devinit +setup_sedlbauer(struct IsdnCard *card) +{ + int bytecnt, ver, val; + struct IsdnCardState *cs = card->cs; + char tmp[64]; + u16 sub_vendor_id, sub_id; + + strcpy(tmp, Sedlbauer_revision); + printk(KERN_INFO "HiSax: Sedlbauer driver Rev. %s\n", HiSax_getrev(tmp)); + + if (cs->typ == ISDN_CTYPE_SEDLBAUER) { + cs->subtyp = SEDL_SPEED_CARD_WIN; + cs->hw.sedl.bus = SEDL_BUS_ISA; + cs->hw.sedl.chip = SEDL_CHIP_TEST; + } else if (cs->typ == ISDN_CTYPE_SEDLBAUER_PCMCIA) { + cs->subtyp = SEDL_SPEED_STAR; + cs->hw.sedl.bus = SEDL_BUS_PCMCIA; + cs->hw.sedl.chip = SEDL_CHIP_TEST; + } else if (cs->typ == ISDN_CTYPE_SEDLBAUER_FAX) { + cs->subtyp = SEDL_SPEED_FAX; + cs->hw.sedl.bus = SEDL_BUS_ISA; + cs->hw.sedl.chip = SEDL_CHIP_ISAC_ISAR; + } else + return (0); + + bytecnt = 8; + if (card->para[1]) { + cs->hw.sedl.cfg_reg = card->para[1]; + cs->irq = card->para[0]; + if (cs->hw.sedl.chip == SEDL_CHIP_ISAC_ISAR) { + bytecnt = 16; + } + } else { +#ifdef __ISAPNP__ + if (isapnp_present()) { + struct pnp_dev *pnp_d; + while(ipid->card_vendor) { + if ((pnp_c = pnp_find_card(ipid->card_vendor, + ipid->card_device, pnp_c))) { + pnp_d = NULL; + if ((pnp_d = pnp_find_dev(pnp_c, + ipid->vendor, ipid->function, pnp_d))) { + int err; + + printk(KERN_INFO "HiSax: %s detected\n", + (char *)ipid->driver_data); + pnp_disable_dev(pnp_d); + err = pnp_activate_dev(pnp_d); + if (err<0) { + printk(KERN_WARNING "%s: pnp_activate_dev ret(%d)\n", + __FUNCTION__, err); + return(0); + } + card->para[1] = pnp_port_start(pnp_d, 0); + card->para[0] = pnp_irq(pnp_d, 0); + + if (!card->para[0] || !card->para[1]) { + printk(KERN_ERR "Sedlbauer PnP:some resources are missing %ld/%lx\n", + card->para[0], card->para[1]); + pnp_disable_dev(pnp_d); + return(0); + } + cs->hw.sedl.cfg_reg = card->para[1]; + cs->irq = card->para[0]; + if (ipid->function == ISAPNP_FUNCTION(0x2)) { + cs->subtyp = SEDL_SPEED_FAX; + cs->hw.sedl.chip = SEDL_CHIP_ISAC_ISAR; + bytecnt = 16; + } else { + cs->subtyp = SEDL_SPEED_CARD_WIN; + cs->hw.sedl.chip = SEDL_CHIP_TEST; + } + goto ready; + } else { + printk(KERN_ERR "Sedlbauer PnP: PnP error card found, no device\n"); + return(0); + } + } + ipid++; + pnp_c = NULL; + } + if (!ipid->card_vendor) { + printk(KERN_INFO "Sedlbauer PnP: no ISAPnP card found\n"); + } + } +#endif +/* Probe for Sedlbauer speed pci */ +#ifdef CONFIG_PCI + if ((dev_sedl = pci_find_device(PCI_VENDOR_ID_TIGERJET, + PCI_DEVICE_ID_TIGERJET_100, dev_sedl))) { + if (pci_enable_device(dev_sedl)) + return(0); + cs->irq = dev_sedl->irq; + if (!cs->irq) { + printk(KERN_WARNING "Sedlbauer: No IRQ for PCI card found\n"); + return(0); + } + cs->hw.sedl.cfg_reg = pci_resource_start(dev_sedl, 0); + } else { + printk(KERN_WARNING "Sedlbauer: No PCI card found\n"); + return(0); + } + cs->irq_flags |= SA_SHIRQ; + cs->hw.sedl.bus = SEDL_BUS_PCI; + sub_vendor_id = dev_sedl->subsystem_vendor; + sub_id = dev_sedl->subsystem_device; + printk(KERN_INFO "Sedlbauer: PCI subvendor:%x subid %x\n", + sub_vendor_id, sub_id); + printk(KERN_INFO "Sedlbauer: PCI base adr %#x\n", + cs->hw.sedl.cfg_reg); + if (sub_id != PCI_SUB_ID_SEDLBAUER) { + printk(KERN_ERR "Sedlbauer: unknown sub id %#x\n", sub_id); + return(0); + } + if (sub_vendor_id == PCI_SUBVENDOR_SPEEDFAX_PYRAMID) { + cs->hw.sedl.chip = SEDL_CHIP_ISAC_ISAR; + cs->subtyp = SEDL_SPEEDFAX_PYRAMID; + } else if (sub_vendor_id == PCI_SUBVENDOR_SPEEDFAX_PCI) { + cs->hw.sedl.chip = SEDL_CHIP_ISAC_ISAR; + cs->subtyp = SEDL_SPEEDFAX_PCI; + } else if (sub_vendor_id == PCI_SUBVENDOR_HST_SAPHIR3) { + cs->hw.sedl.chip = SEDL_CHIP_IPAC; + cs->subtyp = HST_SAPHIR3; + } else if (sub_vendor_id == PCI_SUBVENDOR_SEDLBAUER_PCI) { + cs->hw.sedl.chip = SEDL_CHIP_IPAC; + cs->subtyp = SEDL_SPEED_PCI; + } else { + printk(KERN_ERR "Sedlbauer: unknown sub vendor id %#x\n", + sub_vendor_id); + return(0); + } + bytecnt = 256; + cs->hw.sedl.reset_on = SEDL_ISAR_PCI_ISAR_RESET_ON; + cs->hw.sedl.reset_off = SEDL_ISAR_PCI_ISAR_RESET_OFF; + byteout(cs->hw.sedl.cfg_reg, 0xff); + byteout(cs->hw.sedl.cfg_reg, 0x00); + byteout(cs->hw.sedl.cfg_reg+ 2, 0xdd); + byteout(cs->hw.sedl.cfg_reg+ 5, 0x02); + byteout(cs->hw.sedl.cfg_reg +3, cs->hw.sedl.reset_on); + mdelay(2); + byteout(cs->hw.sedl.cfg_reg +3, cs->hw.sedl.reset_off); + mdelay(10); +#else + printk(KERN_WARNING "Sedlbauer: NO_PCI_BIOS\n"); + return (0); +#endif /* CONFIG_PCI */ + } +ready: + /* In case of the sedlbauer pcmcia card, this region is in use, + * reserved for us by the card manager. So we do not check it + * here, it would fail. + */ + if (cs->hw.sedl.bus != SEDL_BUS_PCMCIA && + !request_region(cs->hw.sedl.cfg_reg, bytecnt, "sedlbauer isdn")) { + printk(KERN_WARNING + "HiSax: %s config port %x-%x already in use\n", + CardType[card->typ], + cs->hw.sedl.cfg_reg, + cs->hw.sedl.cfg_reg + bytecnt); + return (0); + } + + printk(KERN_INFO + "Sedlbauer: defined at 0x%x-0x%x IRQ %d\n", + cs->hw.sedl.cfg_reg, + cs->hw.sedl.cfg_reg + bytecnt, + cs->irq); + + cs->BC_Read_Reg = &ReadHSCX; + cs->BC_Write_Reg = &WriteHSCX; + cs->BC_Send_Data = &hscx_fill_fifo; + cs->cardmsg = &Sedl_card_msg; + +/* + * testing ISA and PCMCIA Cards for IPAC, default is ISAC + * do not test for PCI card, because ports are different + * and PCI card uses only IPAC (for the moment) + */ + if (cs->hw.sedl.bus != SEDL_BUS_PCI) { + val = readreg(cs->hw.sedl.cfg_reg + SEDL_IPAC_ANY_ADR, + cs->hw.sedl.cfg_reg + SEDL_IPAC_ANY_IPAC, IPAC_ID); + printk(KERN_DEBUG "Sedlbauer: testing IPAC version %x\n", val); + if ((val == 1) || (val == 2)) { + /* IPAC */ + cs->subtyp = SEDL_SPEED_WIN2_PC104; + if (cs->hw.sedl.bus == SEDL_BUS_PCMCIA) { + cs->subtyp = SEDL_SPEED_STAR2; + } + cs->hw.sedl.chip = SEDL_CHIP_IPAC; + } else { + /* ISAC_HSCX oder ISAC_ISAR */ + if (cs->hw.sedl.chip == SEDL_CHIP_TEST) { + cs->hw.sedl.chip = SEDL_CHIP_ISAC_HSCX; + } + } + } + +/* + * hw.sedl.chip is now properly set + */ + printk(KERN_INFO "Sedlbauer: %s detected\n", + Sedlbauer_Types[cs->subtyp]); + + setup_isac(cs); + if (cs->hw.sedl.chip == SEDL_CHIP_IPAC) { + if (cs->hw.sedl.bus == SEDL_BUS_PCI) { + cs->hw.sedl.adr = cs->hw.sedl.cfg_reg + SEDL_IPAC_PCI_ADR; + cs->hw.sedl.isac = cs->hw.sedl.cfg_reg + SEDL_IPAC_PCI_IPAC; + cs->hw.sedl.hscx = cs->hw.sedl.cfg_reg + SEDL_IPAC_PCI_IPAC; + } else { + cs->hw.sedl.adr = cs->hw.sedl.cfg_reg + SEDL_IPAC_ANY_ADR; + cs->hw.sedl.isac = cs->hw.sedl.cfg_reg + SEDL_IPAC_ANY_IPAC; + cs->hw.sedl.hscx = cs->hw.sedl.cfg_reg + SEDL_IPAC_ANY_IPAC; + } + test_and_set_bit(HW_IPAC, &cs->HW_Flags); + cs->readisac = &ReadISAC_IPAC; + cs->writeisac = &WriteISAC_IPAC; + cs->readisacfifo = &ReadISACfifo_IPAC; + cs->writeisacfifo = &WriteISACfifo_IPAC; + cs->irq_func = &sedlbauer_interrupt_ipac; + val = readreg(cs->hw.sedl.adr, cs->hw.sedl.isac, IPAC_ID); + printk(KERN_INFO "Sedlbauer: IPAC version %x\n", val); + } else { + /* ISAC_HSCX oder ISAC_ISAR */ + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + if (cs->hw.sedl.chip == SEDL_CHIP_ISAC_ISAR) { + if (cs->hw.sedl.bus == SEDL_BUS_PCI) { + cs->hw.sedl.adr = cs->hw.sedl.cfg_reg + + SEDL_ISAR_PCI_ADR; + cs->hw.sedl.isac = cs->hw.sedl.cfg_reg + + SEDL_ISAR_PCI_ISAC; + cs->hw.sedl.hscx = cs->hw.sedl.cfg_reg + + SEDL_ISAR_PCI_ISAR; + } else { + cs->hw.sedl.adr = cs->hw.sedl.cfg_reg + + SEDL_ISAR_ISA_ADR; + cs->hw.sedl.isac = cs->hw.sedl.cfg_reg + + SEDL_ISAR_ISA_ISAC; + cs->hw.sedl.hscx = cs->hw.sedl.cfg_reg + + SEDL_ISAR_ISA_ISAR; + cs->hw.sedl.reset_on = cs->hw.sedl.cfg_reg + + SEDL_ISAR_ISA_ISAR_RESET_ON; + cs->hw.sedl.reset_off = cs->hw.sedl.cfg_reg + + SEDL_ISAR_ISA_ISAR_RESET_OFF; + } + cs->bcs[0].hw.isar.reg = &cs->hw.sedl.isar; + cs->bcs[1].hw.isar.reg = &cs->hw.sedl.isar; + test_and_set_bit(HW_ISAR, &cs->HW_Flags); + cs->irq_func = &sedlbauer_interrupt_isar; + cs->auxcmd = &isar_auxcmd; + ISACVersion(cs, "Sedlbauer:"); + cs->BC_Read_Reg = &ReadISAR; + cs->BC_Write_Reg = &WriteISAR; + cs->BC_Send_Data = &isar_fill_fifo; + bytecnt = 3; + while (bytecnt) { + ver = ISARVersion(cs, "Sedlbauer:"); + if (ver < 0) + printk(KERN_WARNING + "Sedlbauer: wrong ISAR version (ret = %d)\n", ver); + else + break; + reset_sedlbauer(cs); + bytecnt--; + } + if (!bytecnt) { + release_io_sedlbauer(cs); + return (0); + } + } else { + if (cs->hw.sedl.bus == SEDL_BUS_PCMCIA) { + cs->hw.sedl.adr = cs->hw.sedl.cfg_reg + SEDL_HSCX_PCMCIA_ADR; + cs->hw.sedl.isac = cs->hw.sedl.cfg_reg + SEDL_HSCX_PCMCIA_ISAC; + cs->hw.sedl.hscx = cs->hw.sedl.cfg_reg + SEDL_HSCX_PCMCIA_HSCX; + cs->hw.sedl.reset_on = cs->hw.sedl.cfg_reg + SEDL_HSCX_PCMCIA_RESET; + cs->hw.sedl.reset_off = cs->hw.sedl.cfg_reg + SEDL_HSCX_PCMCIA_RESET; + cs->irq_flags |= SA_SHIRQ; + } else { + cs->hw.sedl.adr = cs->hw.sedl.cfg_reg + SEDL_HSCX_ISA_ADR; + cs->hw.sedl.isac = cs->hw.sedl.cfg_reg + SEDL_HSCX_ISA_ISAC; + cs->hw.sedl.hscx = cs->hw.sedl.cfg_reg + SEDL_HSCX_ISA_HSCX; + cs->hw.sedl.reset_on = cs->hw.sedl.cfg_reg + SEDL_HSCX_ISA_RESET_ON; + cs->hw.sedl.reset_off = cs->hw.sedl.cfg_reg + SEDL_HSCX_ISA_RESET_OFF; + } + cs->irq_func = &sedlbauer_interrupt; + ISACVersion(cs, "Sedlbauer:"); + + if (HscxVersion(cs, "Sedlbauer:")) { + printk(KERN_WARNING + "Sedlbauer: wrong HSCX versions check IO address\n"); + release_io_sedlbauer(cs); + return (0); + } + } + } + return (1); +} diff --git a/drivers/isdn/hisax/sedlbauer_cs.c b/drivers/isdn/hisax/sedlbauer_cs.c new file mode 100644 index 000000000000..449651241477 --- /dev/null +++ b/drivers/isdn/hisax/sedlbauer_cs.c @@ -0,0 +1,640 @@ +/*====================================================================== + + A Sedlbauer PCMCIA client driver + + This driver is for the Sedlbauer Speed Star and Speed Star II, + which are ISDN PCMCIA Cards. + + The contents of this file are subject to the Mozilla Public + License Version 1.1 (the "License"); you may not use this file + except in compliance with the License. You may obtain a copy of + the License at http://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS + IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + implied. See the License for the specific language governing + rights and limitations under the License. + + The initial developer of the original code is David A. Hinds + <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + + Modifications from dummy_cs.c are Copyright (C) 1999-2001 Marcus Niemann + <maniemann@users.sourceforge.net>. All Rights Reserved. + + Alternatively, the contents of this file may be used under the + terms of the GNU General Public License version 2 (the "GPL"), in + which case the provisions of the GPL are applicable instead of the + above. If you wish to allow the use of your version of this file + only under the terms of the GPL and not to allow others to use + your version of this file under the MPL, indicate your decision + by deleting the provisions above and replace them with the notice + and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this + file under either the MPL or the GPL. + +======================================================================*/ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/ptrace.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/ioport.h> +#include <asm/io.h> +#include <asm/system.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/cisreg.h> +#include <pcmcia/ds.h> +#include "hisax_cfg.h" + +MODULE_DESCRIPTION("ISDN4Linux: PCMCIA client driver for Sedlbauer cards"); +MODULE_AUTHOR("Marcus Niemann"); +MODULE_LICENSE("Dual MPL/GPL"); + +/* + All the PCMCIA modules use PCMCIA_DEBUG to control debugging. If + you do not define PCMCIA_DEBUG at all, all the debug code will be + left out. If you compile with PCMCIA_DEBUG=0, the debug code will + be present but disabled -- but it can then be enabled for specific + modules at load time with a 'pc_debug=#' option to insmod. +*/ + +#ifdef PCMCIA_DEBUG +static int pc_debug = PCMCIA_DEBUG; +module_param(pc_debug, int, 0); +#define DEBUG(n, args...) if (pc_debug>(n)) printk(KERN_DEBUG args); +static char *version = +"sedlbauer_cs.c 1.1a 2001/01/28 15:04:04 (M.Niemann)"; +#else +#define DEBUG(n, args...) +#endif + + +/*====================================================================*/ + +/* Parameters that can be set with 'insmod' */ + +static int protocol = 2; /* EURO-ISDN Default */ +module_param(protocol, int, 0); + +/*====================================================================*/ + +/* + The event() function is this driver's Card Services event handler. + It will be called by Card Services when an appropriate card status + event is received. The config() and release() entry points are + used to configure or release a socket, in response to card + insertion and ejection events. They are invoked from the sedlbauer + event handler. +*/ + +static void sedlbauer_config(dev_link_t *link); +static void sedlbauer_release(dev_link_t *link); +static int sedlbauer_event(event_t event, int priority, + event_callback_args_t *args); + +/* + The attach() and detach() entry points are used to create and destroy + "instances" of the driver, where each instance represents everything + needed to manage one actual PCMCIA card. +*/ + +static dev_link_t *sedlbauer_attach(void); +static void sedlbauer_detach(dev_link_t *); + +/* + You'll also need to prototype all the functions that will actually + be used to talk to your device. See 'memory_cs' for a good example + of a fully self-sufficient driver; the other drivers rely more or + less on other parts of the kernel. +*/ + +/* + The dev_info variable is the "key" that is used to match up this + device driver with appropriate cards, through the card configuration + database. +*/ + +static dev_info_t dev_info = "sedlbauer_cs"; + +/* + A linked list of "instances" of the sedlbauer device. Each actual + PCMCIA card corresponds to one device instance, and is described + by one dev_link_t structure (defined in ds.h). + + You may not want to use a linked list for this -- for example, the + memory card driver uses an array of dev_link_t pointers, where minor + device numbers are used to derive the corresponding array index. +*/ + +static dev_link_t *dev_list = NULL; + +/* + A dev_link_t structure has fields for most things that are needed + to keep track of a socket, but there will usually be some device + specific information that also needs to be kept track of. The + 'priv' pointer in a dev_link_t structure can be used to point to + a device-specific private data structure, like this. + + To simplify the data structure handling, we actually include the + dev_link_t structure in the device's private data structure. + + A driver needs to provide a dev_node_t structure for each device + on a card. In some cases, there is only one device per card (for + example, ethernet cards, modems). In other cases, there may be + many actual or logical devices (SCSI adapters, memory cards with + multiple partitions). The dev_node_t structures need to be kept + in a linked list starting at the 'dev' field of a dev_link_t + structure. We allocate them in the card's private data structure, + because they generally shouldn't be allocated dynamically. + + In this case, we also provide a flag to indicate if a device is + "stopped" due to a power management event, or card ejection. The + device IO routines can use a flag like this to throttle IO to a + card that is not ready to accept it. +*/ + +typedef struct local_info_t { + dev_link_t link; + dev_node_t node; + int stop; + int cardnr; +} local_info_t; + +/*====================================================================== + + sedlbauer_attach() creates an "instance" of the driver, allocating + local data structures for one device. The device is registered + with Card Services. + + The dev_link structure is initialized, but we don't actually + configure the card at this point -- we wait until we receive a + card insertion event. + +======================================================================*/ + +static dev_link_t *sedlbauer_attach(void) +{ + local_info_t *local; + dev_link_t *link; + client_reg_t client_reg; + int ret; + + DEBUG(0, "sedlbauer_attach()\n"); + + /* Allocate space for private device-specific data */ + local = kmalloc(sizeof(local_info_t), GFP_KERNEL); + if (!local) return NULL; + memset(local, 0, sizeof(local_info_t)); + local->cardnr = -1; + link = &local->link; link->priv = local; + + /* Interrupt setup */ + link->irq.Attributes = IRQ_TYPE_EXCLUSIVE; + link->irq.IRQInfo1 = IRQ_LEVEL_ID; + link->irq.Handler = NULL; + + /* + General socket configuration defaults can go here. In this + client, we assume very little, and rely on the CIS for almost + everything. In most clients, many details (i.e., number, sizes, + and attributes of IO windows) are fixed by the nature of the + device, and can be hard-wired here. + */ + + /* from old sedl_cs + */ + /* The io structure describes IO port mapping */ + link->io.NumPorts1 = 8; + link->io.Attributes1 = IO_DATA_PATH_WIDTH_8; + link->io.IOAddrLines = 3; + + + link->conf.Attributes = 0; + link->conf.Vcc = 50; + link->conf.IntType = INT_MEMORY_AND_IO; + + /* Register with Card Services */ + link->next = dev_list; + dev_list = link; + client_reg.dev_info = &dev_info; + client_reg.EventMask = + CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL | + CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET | + CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME; + client_reg.event_handler = &sedlbauer_event; + client_reg.Version = 0x0210; + client_reg.event_callback_args.client_data = link; + ret = pcmcia_register_client(&link->handle, &client_reg); + if (ret != CS_SUCCESS) { + cs_error(link->handle, RegisterClient, ret); + sedlbauer_detach(link); + return NULL; + } + + return link; +} /* sedlbauer_attach */ + +/*====================================================================== + + This deletes a driver "instance". The device is de-registered + with Card Services. If it has been released, all local data + structures are freed. Otherwise, the structures will be freed + when the device is released. + +======================================================================*/ + +static void sedlbauer_detach(dev_link_t *link) +{ + dev_link_t **linkp; + + DEBUG(0, "sedlbauer_detach(0x%p)\n", link); + + /* Locate device structure */ + for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) + if (*linkp == link) break; + if (*linkp == NULL) + return; + + /* + If the device is currently configured and active, we won't + actually delete it yet. Instead, it is marked so that when + the release() function is called, that will trigger a proper + detach(). + */ + if (link->state & DEV_CONFIG) { +#ifdef PCMCIA_DEBUG + printk(KERN_DEBUG "sedlbauer_cs: detach postponed, '%s' " + "still locked\n", link->dev->dev_name); +#endif + link->state |= DEV_STALE_LINK; + return; + } + + /* Break the link with Card Services */ + if (link->handle) + pcmcia_deregister_client(link->handle); + + /* Unlink device structure, and free it */ + *linkp = link->next; + /* This points to the parent local_info_t struct */ + kfree(link->priv); +} /* sedlbauer_detach */ + +/*====================================================================== + + sedlbauer_config() is scheduled to run after a CARD_INSERTION event + is received, to configure the PCMCIA socket, and to make the + device available to the system. + +======================================================================*/ +#define CS_CHECK(fn, ret) \ +do { last_fn = (fn); if ((last_ret = (ret)) != 0) goto cs_failed; } while (0) + +static void sedlbauer_config(dev_link_t *link) +{ + client_handle_t handle = link->handle; + local_info_t *dev = link->priv; + tuple_t tuple; + cisparse_t parse; + int last_fn, last_ret; + u8 buf[64]; + config_info_t conf; + win_req_t req; + memreq_t map; + IsdnCard_t icard; + + DEBUG(0, "sedlbauer_config(0x%p)\n", link); + + /* + This reads the card's CONFIG tuple to find its configuration + registers. + */ + tuple.DesiredTuple = CISTPL_CONFIG; + tuple.Attributes = 0; + tuple.TupleData = buf; + tuple.TupleDataMax = sizeof(buf); + tuple.TupleOffset = 0; + CS_CHECK(GetFirstTuple, pcmcia_get_first_tuple(handle, &tuple)); + CS_CHECK(GetTupleData, pcmcia_get_tuple_data(handle, &tuple)); + CS_CHECK(ParseTuple, pcmcia_parse_tuple(handle, &tuple, &parse)); + link->conf.ConfigBase = parse.config.base; + link->conf.Present = parse.config.rmask[0]; + + /* Configure card */ + link->state |= DEV_CONFIG; + + /* Look up the current Vcc */ + CS_CHECK(GetConfigurationInfo, pcmcia_get_configuration_info(handle, &conf)); + link->conf.Vcc = conf.Vcc; + + /* + In this loop, we scan the CIS for configuration table entries, + each of which describes a valid card configuration, including + voltage, IO window, memory window, and interrupt settings. + + We make no assumptions about the card to be configured: we use + just the information available in the CIS. In an ideal world, + this would work for any PCMCIA card, but it requires a complete + and accurate CIS. In practice, a driver usually "knows" most of + these things without consulting the CIS, and most client drivers + will only use the CIS to fill in implementation-defined details. + */ + tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY; + CS_CHECK(GetFirstTuple, pcmcia_get_first_tuple(handle, &tuple)); + while (1) { + cistpl_cftable_entry_t dflt = { 0 }; + cistpl_cftable_entry_t *cfg = &(parse.cftable_entry); + if (pcmcia_get_tuple_data(handle, &tuple) != 0 || + pcmcia_parse_tuple(handle, &tuple, &parse) != 0) + goto next_entry; + + if (cfg->flags & CISTPL_CFTABLE_DEFAULT) dflt = *cfg; + if (cfg->index == 0) goto next_entry; + link->conf.ConfigIndex = cfg->index; + + /* Does this card need audio output? */ + if (cfg->flags & CISTPL_CFTABLE_AUDIO) { + link->conf.Attributes |= CONF_ENABLE_SPKR; + link->conf.Status = CCSR_AUDIO_ENA; + } + + /* Use power settings for Vcc and Vpp if present */ + /* Note that the CIS values need to be rescaled */ + if (cfg->vcc.present & (1<<CISTPL_POWER_VNOM)) { + if (conf.Vcc != cfg->vcc.param[CISTPL_POWER_VNOM]/10000) + goto next_entry; + } else if (dflt.vcc.present & (1<<CISTPL_POWER_VNOM)) { + if (conf.Vcc != dflt.vcc.param[CISTPL_POWER_VNOM]/10000) + goto next_entry; + } + + if (cfg->vpp1.present & (1<<CISTPL_POWER_VNOM)) + link->conf.Vpp1 = link->conf.Vpp2 = + cfg->vpp1.param[CISTPL_POWER_VNOM]/10000; + else if (dflt.vpp1.present & (1<<CISTPL_POWER_VNOM)) + link->conf.Vpp1 = link->conf.Vpp2 = + dflt.vpp1.param[CISTPL_POWER_VNOM]/10000; + + /* Do we need to allocate an interrupt? */ + if (cfg->irq.IRQInfo1 || dflt.irq.IRQInfo1) + link->conf.Attributes |= CONF_ENABLE_IRQ; + + /* IO window settings */ + link->io.NumPorts1 = link->io.NumPorts2 = 0; + if ((cfg->io.nwin > 0) || (dflt.io.nwin > 0)) { + cistpl_io_t *io = (cfg->io.nwin) ? &cfg->io : &dflt.io; + link->io.Attributes1 = IO_DATA_PATH_WIDTH_AUTO; + if (!(io->flags & CISTPL_IO_8BIT)) + link->io.Attributes1 = IO_DATA_PATH_WIDTH_16; + if (!(io->flags & CISTPL_IO_16BIT)) + link->io.Attributes1 = IO_DATA_PATH_WIDTH_8; +/* new in dummy.cs 2001/01/28 MN + link->io.IOAddrLines = io->flags & CISTPL_IO_LINES_MASK; +*/ + link->io.BasePort1 = io->win[0].base; + link->io.NumPorts1 = io->win[0].len; + if (io->nwin > 1) { + link->io.Attributes2 = link->io.Attributes1; + link->io.BasePort2 = io->win[1].base; + link->io.NumPorts2 = io->win[1].len; + } + /* This reserves IO space but doesn't actually enable it */ + if (pcmcia_request_io(link->handle, &link->io) != 0) + goto next_entry; + } + + /* + Now set up a common memory window, if needed. There is room + in the dev_link_t structure for one memory window handle, + but if the base addresses need to be saved, or if multiple + windows are needed, the info should go in the private data + structure for this device. + + Note that the memory window base is a physical address, and + needs to be mapped to virtual space with ioremap() before it + is used. + */ + if ((cfg->mem.nwin > 0) || (dflt.mem.nwin > 0)) { + cistpl_mem_t *mem = + (cfg->mem.nwin) ? &cfg->mem : &dflt.mem; + req.Attributes = WIN_DATA_WIDTH_16|WIN_MEMORY_TYPE_CM; + req.Attributes |= WIN_ENABLE; + req.Base = mem->win[0].host_addr; + req.Size = mem->win[0].len; +/* new in dummy.cs 2001/01/28 MN + if (req.Size < 0x1000) + req.Size = 0x1000; +*/ + req.AccessSpeed = 0; + if (pcmcia_request_window(&link->handle, &req, &link->win) != 0) + goto next_entry; + map.Page = 0; map.CardOffset = mem->win[0].card_addr; + if (pcmcia_map_mem_page(link->win, &map) != 0) + goto next_entry; + } + /* If we got this far, we're cool! */ + break; + + next_entry: +/* new in dummy.cs 2001/01/28 MN + if (link->io.NumPorts1) + pcmcia_release_io(link->handle, &link->io); +*/ + CS_CHECK(GetNextTuple, pcmcia_get_next_tuple(handle, &tuple)); + } + + /* + Allocate an interrupt line. Note that this does not assign a + handler to the interrupt, unless the 'Handler' member of the + irq structure is initialized. + */ + if (link->conf.Attributes & CONF_ENABLE_IRQ) + CS_CHECK(RequestIRQ, pcmcia_request_irq(link->handle, &link->irq)); + + /* + This actually configures the PCMCIA socket -- setting up + the I/O windows and the interrupt mapping, and putting the + card and host interface into "Memory and IO" mode. + */ + CS_CHECK(RequestConfiguration, pcmcia_request_configuration(link->handle, &link->conf)); + + /* + At this point, the dev_node_t structure(s) need to be + initialized and arranged in a linked list at link->dev. + */ + sprintf(dev->node.dev_name, "sedlbauer"); + dev->node.major = dev->node.minor = 0; + link->dev = &dev->node; + + /* Finally, report what we've done */ + printk(KERN_INFO "%s: index 0x%02x: Vcc %d.%d", + dev->node.dev_name, link->conf.ConfigIndex, + link->conf.Vcc/10, link->conf.Vcc%10); + if (link->conf.Vpp1) + printk(", Vpp %d.%d", link->conf.Vpp1/10, link->conf.Vpp1%10); + if (link->conf.Attributes & CONF_ENABLE_IRQ) + printk(", irq %d", link->irq.AssignedIRQ); + if (link->io.NumPorts1) + printk(", io 0x%04x-0x%04x", link->io.BasePort1, + link->io.BasePort1+link->io.NumPorts1-1); + if (link->io.NumPorts2) + printk(" & 0x%04x-0x%04x", link->io.BasePort2, + link->io.BasePort2+link->io.NumPorts2-1); + if (link->win) + printk(", mem 0x%06lx-0x%06lx", req.Base, + req.Base+req.Size-1); + printk("\n"); + + link->state &= ~DEV_CONFIG_PENDING; + + icard.para[0] = link->irq.AssignedIRQ; + icard.para[1] = link->io.BasePort1; + icard.protocol = protocol; + icard.typ = ISDN_CTYPE_SEDLBAUER_PCMCIA; + + last_ret = hisax_init_pcmcia(link, &(((local_info_t*)link->priv)->stop), &icard); + if (last_ret < 0) { + printk(KERN_ERR "sedlbauer_cs: failed to initialize SEDLBAUER PCMCIA %d at i/o %#x\n", + last_ret, link->io.BasePort1); + sedlbauer_release(link); + } else + ((local_info_t*)link->priv)->cardnr = last_ret; + + return; + +cs_failed: + cs_error(link->handle, last_fn, last_ret); + sedlbauer_release(link); + +} /* sedlbauer_config */ + +/*====================================================================== + + After a card is removed, sedlbauer_release() will unregister the + device, and release the PCMCIA configuration. If the device is + still open, this will be postponed until it is closed. + +======================================================================*/ + +static void sedlbauer_release(dev_link_t *link) +{ + local_info_t *local = link->priv; + DEBUG(0, "sedlbauer_release(0x%p)\n", link); + + if (local) { + if (local->cardnr >= 0) { + /* no unregister function with hisax */ + HiSax_closecard(local->cardnr); + } + } + /* Unlink the device chain */ + link->dev = NULL; + + /* + In a normal driver, additional code may be needed to release + other kernel data structures associated with this device. + */ + + /* Don't bother checking to see if these succeed or not */ + if (link->win) + pcmcia_release_window(link->win); + pcmcia_release_configuration(link->handle); + if (link->io.NumPorts1) + pcmcia_release_io(link->handle, &link->io); + if (link->irq.AssignedIRQ) + pcmcia_release_irq(link->handle, &link->irq); + link->state &= ~DEV_CONFIG; + + if (link->state & DEV_STALE_LINK) + sedlbauer_detach(link); + +} /* sedlbauer_release */ + +/*====================================================================== + + The card status event handler. Mostly, this schedules other + stuff to run after an event is received. + + When a CARD_REMOVAL event is received, we immediately set a + private flag to block future accesses to this device. All the + functions that actually access the device should check this flag + to make sure the card is still present. + +======================================================================*/ + +static int sedlbauer_event(event_t event, int priority, + event_callback_args_t *args) +{ + dev_link_t *link = args->client_data; + local_info_t *dev = link->priv; + + DEBUG(1, "sedlbauer_event(0x%06x)\n", event); + + switch (event) { + case CS_EVENT_CARD_REMOVAL: + link->state &= ~DEV_PRESENT; + if (link->state & DEV_CONFIG) { + ((local_info_t *)link->priv)->stop = 1; + sedlbauer_release(link); + } + break; + case CS_EVENT_CARD_INSERTION: + link->state |= DEV_PRESENT | DEV_CONFIG_PENDING; + sedlbauer_config(link); + break; + case CS_EVENT_PM_SUSPEND: + link->state |= DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_RESET_PHYSICAL: + /* Mark the device as stopped, to block IO until later */ + dev->stop = 1; + if (link->state & DEV_CONFIG) + pcmcia_release_configuration(link->handle); + break; + case CS_EVENT_PM_RESUME: + link->state &= ~DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_CARD_RESET: + if (link->state & DEV_CONFIG) + pcmcia_request_configuration(link->handle, &link->conf); + dev->stop = 0; + /* + In a normal driver, additional code may go here to restore + the device state and restart IO. + */ + break; + } + return 0; +} /* sedlbauer_event */ + +static struct pcmcia_driver sedlbauer_driver = { + .owner = THIS_MODULE, + .drv = { + .name = "sedlbauer_cs", + }, + .attach = sedlbauer_attach, + .detach = sedlbauer_detach, +}; + +static int __init init_sedlbauer_cs(void) +{ + return pcmcia_register_driver(&sedlbauer_driver); +} + +static void __exit exit_sedlbauer_cs(void) +{ + pcmcia_unregister_driver(&sedlbauer_driver); + BUG_ON(dev_list != NULL); +} + +module_init(init_sedlbauer_cs); +module_exit(exit_sedlbauer_cs); diff --git a/drivers/isdn/hisax/sportster.c b/drivers/isdn/hisax/sportster.c new file mode 100644 index 000000000000..132840b750ce --- /dev/null +++ b/drivers/isdn/hisax/sportster.c @@ -0,0 +1,270 @@ +/* $Id: sportster.c,v 1.16.2.4 2004/01/13 23:48:39 keil Exp $ + * + * low level stuff for USR Sportster internal TA + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * Thanks to Christian "naddy" Weisgerber (3Com, US Robotics) for documentation + * + * + */ +#include <linux/init.h> +#include "hisax.h" +#include "isac.h" +#include "hscx.h" +#include "isdnl1.h" + +extern const char *CardType[]; +const char *sportster_revision = "$Revision: 1.16.2.4 $"; + +#define byteout(addr,val) outb(val,addr) +#define bytein(addr) inb(addr) + +#define SPORTSTER_ISAC 0xC000 +#define SPORTSTER_HSCXA 0x0000 +#define SPORTSTER_HSCXB 0x4000 +#define SPORTSTER_RES_IRQ 0x8000 +#define SPORTSTER_RESET 0x80 +#define SPORTSTER_INTE 0x40 + +static inline int +calc_off(unsigned int base, unsigned int off) +{ + return(base + ((off & 0xfc)<<8) + ((off & 3)<<1)); +} + +static inline void +read_fifo(unsigned int adr, u_char * data, int size) +{ + insb(adr, data, size); +} + +static void +write_fifo(unsigned int adr, u_char * data, int size) +{ + outsb(adr, data, size); +} + +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + return (bytein(calc_off(cs->hw.spt.isac, offset))); +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + byteout(calc_off(cs->hw.spt.isac, offset), value); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + read_fifo(cs->hw.spt.isac, data, size); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + write_fifo(cs->hw.spt.isac, data, size); +} + +static u_char +ReadHSCX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + return (bytein(calc_off(cs->hw.spt.hscx[hscx], offset))); +} + +static void +WriteHSCX(struct IsdnCardState *cs, int hscx, u_char offset, u_char value) +{ + byteout(calc_off(cs->hw.spt.hscx[hscx], offset), value); +} + +/* + * fast interrupt HSCX stuff goes here + */ + +#define READHSCX(cs, nr, reg) bytein(calc_off(cs->hw.spt.hscx[nr], reg)) +#define WRITEHSCX(cs, nr, reg, data) byteout(calc_off(cs->hw.spt.hscx[nr], reg), data) +#define READHSCXFIFO(cs, nr, ptr, cnt) read_fifo(cs->hw.spt.hscx[nr], ptr, cnt) +#define WRITEHSCXFIFO(cs, nr, ptr, cnt) write_fifo(cs->hw.spt.hscx[nr], ptr, cnt) + +#include "hscx_irq.c" + +static irqreturn_t +sportster_interrupt(int intno, void *dev_id, struct pt_regs *regs) +{ + struct IsdnCardState *cs = dev_id; + u_char val; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + val = READHSCX(cs, 1, HSCX_ISTA); + Start_HSCX: + if (val) + hscx_int_main(cs, val); + val = ReadISAC(cs, ISAC_ISTA); + Start_ISAC: + if (val) + isac_interrupt(cs, val); + val = READHSCX(cs, 1, HSCX_ISTA); + if (val) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HSCX IntStat after IntRoutine"); + goto Start_HSCX; + } + val = ReadISAC(cs, ISAC_ISTA); + if (val) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ISAC IntStat after IntRoutine"); + goto Start_ISAC; + } + /* get a new irq impulse if there any pending */ + bytein(cs->hw.spt.cfg_reg + SPORTSTER_RES_IRQ +1); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +void +release_io_sportster(struct IsdnCardState *cs) +{ + int i, adr; + + byteout(cs->hw.spt.cfg_reg + SPORTSTER_RES_IRQ, 0); + for (i=0; i<64; i++) { + adr = cs->hw.spt.cfg_reg + i *1024; + release_region(adr, 8); + } +} + +void +reset_sportster(struct IsdnCardState *cs) +{ + cs->hw.spt.res_irq |= SPORTSTER_RESET; /* Reset On */ + byteout(cs->hw.spt.cfg_reg + SPORTSTER_RES_IRQ, cs->hw.spt.res_irq); + mdelay(10); + cs->hw.spt.res_irq &= ~SPORTSTER_RESET; /* Reset Off */ + byteout(cs->hw.spt.cfg_reg + SPORTSTER_RES_IRQ, cs->hw.spt.res_irq); + mdelay(10); +} + +static int +Sportster_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + reset_sportster(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return(0); + case CARD_RELEASE: + release_io_sportster(cs); + return(0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + reset_sportster(cs); + inithscxisac(cs, 1); + cs->hw.spt.res_irq |= SPORTSTER_INTE; /* IRQ On */ + byteout(cs->hw.spt.cfg_reg + SPORTSTER_RES_IRQ, cs->hw.spt.res_irq); + inithscxisac(cs, 2); + spin_unlock_irqrestore(&cs->lock, flags); + return(0); + case CARD_TEST: + return(0); + } + return(0); +} + +static int __init +get_io_range(struct IsdnCardState *cs) +{ + int i, j, adr; + + for (i=0;i<64;i++) { + adr = cs->hw.spt.cfg_reg + i *1024; + if (!request_region(adr, 8, "sportster")) { + printk(KERN_WARNING + "HiSax: %s config port %x-%x already in use\n", + CardType[cs->typ], adr, adr + 8); + break; + } + } + if (i==64) + return(1); + else { + for (j=0; j<i; j++) { + adr = cs->hw.spt.cfg_reg + j *1024; + release_region(adr, 8); + } + return(0); + } +} + +int __init +setup_sportster(struct IsdnCard *card) +{ + struct IsdnCardState *cs = card->cs; + char tmp[64]; + + strcpy(tmp, sportster_revision); + printk(KERN_INFO "HiSax: USR Sportster driver Rev. %s\n", HiSax_getrev(tmp)); + if (cs->typ != ISDN_CTYPE_SPORTSTER) + return (0); + + cs->hw.spt.cfg_reg = card->para[1]; + cs->irq = card->para[0]; + if (!get_io_range(cs)) + return (0); + cs->hw.spt.isac = cs->hw.spt.cfg_reg + SPORTSTER_ISAC; + cs->hw.spt.hscx[0] = cs->hw.spt.cfg_reg + SPORTSTER_HSCXA; + cs->hw.spt.hscx[1] = cs->hw.spt.cfg_reg + SPORTSTER_HSCXB; + + switch(cs->irq) { + case 5: cs->hw.spt.res_irq = 1; + break; + case 7: cs->hw.spt.res_irq = 2; + break; + case 10:cs->hw.spt.res_irq = 3; + break; + case 11:cs->hw.spt.res_irq = 4; + break; + case 12:cs->hw.spt.res_irq = 5; + break; + case 14:cs->hw.spt.res_irq = 6; + break; + case 15:cs->hw.spt.res_irq = 7; + break; + default:release_io_sportster(cs); + printk(KERN_WARNING "Sportster: wrong IRQ\n"); + return(0); + } + printk(KERN_INFO "HiSax: %s config irq:%d cfg:0x%X\n", + CardType[cs->typ], cs->irq, cs->hw.spt.cfg_reg); + setup_isac(cs); + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->BC_Read_Reg = &ReadHSCX; + cs->BC_Write_Reg = &WriteHSCX; + cs->BC_Send_Data = &hscx_fill_fifo; + cs->cardmsg = &Sportster_card_msg; + cs->irq_func = &sportster_interrupt; + ISACVersion(cs, "Sportster:"); + if (HscxVersion(cs, "Sportster:")) { + printk(KERN_WARNING + "Sportster: wrong HSCX versions check IO address\n"); + release_io_sportster(cs); + return (0); + } + return (1); +} diff --git a/drivers/isdn/hisax/st5481.h b/drivers/isdn/hisax/st5481.h new file mode 100644 index 000000000000..e8177b017b1d --- /dev/null +++ b/drivers/isdn/hisax/st5481.h @@ -0,0 +1,535 @@ +/* + * Driver for ST5481 USB ISDN modem + * + * Author Frode Isaksen + * Copyright 2001 by Frode Isaksen <fisaksen@bewan.com> + * 2001 by Kai Germaschewski <kai.germaschewski@gmx.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#ifndef _ST5481_H_ +#define _ST5481_H_ + +#include <linux/config.h> + +// USB IDs, the Product Id is in the range 0x4810-0x481F + +#define ST_VENDOR_ID 0x0483 +#define ST5481_PRODUCT_ID 0x4810 +#define ST5481_PRODUCT_ID_MASK 0xFFF0 + +// ST5481 endpoints when using alternative setting 3 (2B+D). +// To get the endpoint address, OR with 0x80 for IN endpoints. + +#define EP_CTRL 0x00U /* Control endpoint */ +#define EP_INT 0x01U /* Interrupt endpoint */ +#define EP_B1_OUT 0x02U /* B1 channel out */ +#define EP_B1_IN 0x03U /* B1 channel in */ +#define EP_B2_OUT 0x04U /* B2 channel out */ +#define EP_B2_IN 0x05U /* B2 channel in */ +#define EP_D_OUT 0x06U /* D channel out */ +#define EP_D_IN 0x07U /* D channel in */ + +// Number of isochronous packets. With 20 packets we get +// 50 interrupts/sec for each endpoint. + +#define NUM_ISO_PACKETS_D 20 +#define NUM_ISO_PACKETS_B 20 + +// Size of each isochronous packet. +// In outgoing direction we need to match ISDN data rates: +// D: 2 bytes / msec -> 16 kbit / s +// B: 16 bytes / msec -> 64 kbit / s +#define SIZE_ISO_PACKETS_D_IN 16 +#define SIZE_ISO_PACKETS_D_OUT 2 +#define SIZE_ISO_PACKETS_B_IN 32 +#define SIZE_ISO_PACKETS_B_OUT 8 + +// If we overrun/underrun, we send one packet with +/- 2 bytes +#define B_FLOW_ADJUST 2 + +// Registers that are written using vendor specific device request +// on endpoint 0. + +#define LBA 0x02 /* S loopback */ +#define SET_DEFAULT 0x06 /* Soft reset */ +#define LBB 0x1D /* S maintenance loopback */ +#define STT 0x1e /* S force transmission signals */ +#define SDA_MIN 0x20 /* SDA-sin minimal value */ +#define SDA_MAX 0x21 /* SDA-sin maximal value */ +#define SDELAY_VALUE 0x22 /* Delay between Tx and Rx clock */ +#define IN_D_COUNTER 0x36 /* D receive channel fifo counter */ +#define OUT_D_COUNTER 0x37 /* D transmit channel fifo counter */ +#define IN_B1_COUNTER 0x38 /* B1 receive channel fifo counter */ +#define OUT_B1_COUNTER 0x39 /* B1 transmit channel fifo counter */ +#define IN_B2_COUNTER 0x3a /* B2 receive channel fifo counter */ +#define OUT_B2_COUNTER 0x3b /* B2 transmit channel fifo counter */ +#define FFCTRL_IN_D 0x3C /* D receive channel fifo threshold low */ +#define FFCTRH_IN_D 0x3D /* D receive channel fifo threshold high */ +#define FFCTRL_OUT_D 0x3E /* D transmit channel fifo threshold low */ +#define FFCTRH_OUT_D 0x3F /* D transmit channel fifo threshold high */ +#define FFCTRL_IN_B1 0x40 /* B1 receive channel fifo threshold low */ +#define FFCTRH_IN_B1 0x41 /* B1 receive channel fifo threshold high */ +#define FFCTRL_OUT_B1 0x42 /* B1 transmit channel fifo threshold low */ +#define FFCTRH_OUT_B1 0x43 /* B1 transmit channel fifo threshold high */ +#define FFCTRL_IN_B2 0x44 /* B2 receive channel fifo threshold low */ +#define FFCTRH_IN_B2 0x45 /* B2 receive channel fifo threshold high */ +#define FFCTRL_OUT_B2 0x46 /* B2 transmit channel fifo threshold low */ +#define FFCTRH_OUT_B2 0x47 /* B2 transmit channel fifo threshold high */ +#define MPMSK 0x4A /* Multi purpose interrupt MASK register */ +#define FFMSK_D 0x4c /* D fifo interrupt MASK register */ +#define FFMSK_B1 0x4e /* B1 fifo interrupt MASK register */ +#define FFMSK_B2 0x50 /* B2 fifo interrupt MASK register */ +#define GPIO_DIR 0x52 /* GPIO pins direction registers */ +#define GPIO_OUT 0x53 /* GPIO pins output register */ +#define GPIO_IN 0x54 /* GPIO pins input register */ +#define TXCI 0x56 /* CI command to be transmitted */ + + +// Format of the interrupt packet received on endpoint 1: +// +// +--------+--------+--------+--------+--------+--------+ +// !MPINT !FFINT_D !FFINT_B1!FFINT_B2!CCIST !GPIO_INT! +// +--------+--------+--------+--------+--------+--------+ + +// Offsets in the interrupt packet + +#define MPINT 0 +#define FFINT_D 1 +#define FFINT_B1 2 +#define FFINT_B2 3 +#define CCIST 4 +#define GPIO_INT 5 +#define INT_PKT_SIZE 6 + +// MPINT +#define LSD_INT 0x80 /* S line activity detected */ +#define RXCI_INT 0x40 /* Indicate primitive arrived */ +#define DEN_INT 0x20 /* Signal enabling data out of D Tx fifo */ +#define DCOLL_INT 0x10 /* D channel collision */ +#define AMIVN_INT 0x04 /* AMI violation number reached 2 */ +#define INFOI_INT 0x04 /* INFOi changed */ +#define DRXON_INT 0x02 /* Reception channel active */ +#define GPCHG_INT 0x01 /* GPIO pin value changed */ + +// FFINT_x +#define IN_OVERRUN 0x80 /* In fifo overrun */ +#define OUT_UNDERRUN 0x40 /* Out fifo underrun */ +#define IN_UP 0x20 /* In fifo thresholdh up-crossed */ +#define IN_DOWN 0x10 /* In fifo thresholdl down-crossed */ +#define OUT_UP 0x08 /* Out fifo thresholdh up-crossed */ +#define OUT_DOWN 0x04 /* Out fifo thresholdl down-crossed */ +#define IN_COUNTER_ZEROED 0x02 /* In down-counter reached 0 */ +#define OUT_COUNTER_ZEROED 0x01 /* Out down-counter reached 0 */ + +#define ANY_REC_INT (IN_OVERRUN+IN_UP+IN_DOWN+IN_COUNTER_ZEROED) +#define ANY_XMIT_INT (OUT_UNDERRUN+OUT_UP+OUT_DOWN+OUT_COUNTER_ZEROED) + + +// Level 1 commands that are sent using the TXCI device request +#define ST5481_CMD_DR 0x0 /* Deactivation Request */ +#define ST5481_CMD_RES 0x1 /* state machine RESet */ +#define ST5481_CMD_TM1 0x2 /* Test Mode 1 */ +#define ST5481_CMD_TM2 0x3 /* Test Mode 2 */ +#define ST5481_CMD_PUP 0x7 /* Power UP */ +#define ST5481_CMD_AR8 0x8 /* Activation Request class 1 */ +#define ST5481_CMD_AR10 0x9 /* Activation Request class 2 */ +#define ST5481_CMD_ARL 0xA /* Activation Request Loopback */ +#define ST5481_CMD_PDN 0xF /* Power DoWn */ + +// Turn on/off the LEDs using the GPIO device request. +// To use the B LEDs, number_of_leds must be set to 4 +#define B1_LED 0x10U +#define B2_LED 0x20U +#define GREEN_LED 0x40U +#define RED_LED 0x80U + +// D channel out states +enum { + ST_DOUT_NONE, + + ST_DOUT_SHORT_INIT, + ST_DOUT_SHORT_WAIT_DEN, + + ST_DOUT_LONG_INIT, + ST_DOUT_LONG_WAIT_DEN, + ST_DOUT_NORMAL, + + ST_DOUT_WAIT_FOR_UNDERRUN, + ST_DOUT_WAIT_FOR_NOT_BUSY, + ST_DOUT_WAIT_FOR_STOP, + ST_DOUT_WAIT_FOR_RESET, +}; + +#define DOUT_STATE_COUNT (ST_DOUT_WAIT_FOR_RESET + 1) + +// D channel out events +enum { + EV_DOUT_START_XMIT, + EV_DOUT_COMPLETE, + EV_DOUT_DEN, + EV_DOUT_RESETED, + EV_DOUT_STOPPED, + EV_DOUT_COLL, + EV_DOUT_UNDERRUN, +}; + +#define DOUT_EVENT_COUNT (EV_DOUT_UNDERRUN + 1) + +// ---------------------------------------------------------------------- + +enum { + ST_L1_F3, + ST_L1_F4, + ST_L1_F6, + ST_L1_F7, + ST_L1_F8, +}; + +#define L1_STATE_COUNT (ST_L1_F8+1) + +// The first 16 entries match the Level 1 indications that +// are found at offset 4 (CCIST) in the interrupt packet + +enum { + EV_IND_DP, // 0000 Deactivation Pending + EV_IND_1, // 0001 + EV_IND_2, // 0010 + EV_IND_3, // 0011 + EV_IND_RSY, // 0100 ReSYnchronizing + EV_IND_5, // 0101 + EV_IND_6, // 0110 + EV_IND_7, // 0111 + EV_IND_AP, // 1000 Activation Pending + EV_IND_9, // 1001 + EV_IND_10, // 1010 + EV_IND_11, // 1011 + EV_IND_AI8, // 1100 Activation Indication class 8 + EV_IND_AI10,// 1101 Activation Indication class 10 + EV_IND_AIL, // 1110 Activation Indication Loopback + EV_IND_DI, // 1111 Deactivation Indication + EV_PH_ACTIVATE_REQ, + EV_PH_DEACTIVATE_REQ, + EV_TIMER3, +}; + +#define L1_EVENT_COUNT (EV_TIMER3 + 1) + +#define ERR(format, arg...) \ +printk(KERN_ERR "%s:%s: " format "\n" , __FILE__, __FUNCTION__ , ## arg) + +#define WARN(format, arg...) \ +printk(KERN_WARNING "%s:%s: " format "\n" , __FILE__, __FUNCTION__ , ## arg) + +#define INFO(format, arg...) \ +printk(KERN_INFO "%s:%s: " format "\n" , __FILE__, __FUNCTION__ , ## arg) + +#include "isdnhdlc.h" +#include "fsm.h" +#include "hisax_if.h" +#include <linux/skbuff.h> + +/* ====================================================================== + * FIFO handling + */ + +/* Generic FIFO structure */ +struct fifo { + u_char r,w,count,size; + spinlock_t lock; +}; + +/* + * Init an FIFO + */ +static inline void fifo_init(struct fifo *fifo, int size) +{ + fifo->r = fifo->w = fifo->count = 0; + fifo->size = size; + spin_lock_init(&fifo->lock); +} + +/* + * Add an entry to the FIFO + */ +static inline int fifo_add(struct fifo *fifo) +{ + unsigned long flags; + int index; + + if (!fifo) { + return -1; + } + + spin_lock_irqsave(&fifo->lock, flags); + if (fifo->count == fifo->size) { + // FIFO full + index = -1; + } else { + // Return index where to get the next data to add to the FIFO + index = fifo->w++ & (fifo->size-1); + fifo->count++; + } + spin_unlock_irqrestore(&fifo->lock, flags); + return index; +} + +/* + * Remove an entry from the FIFO with the index returned. + */ +static inline int fifo_remove(struct fifo *fifo) +{ + unsigned long flags; + int index; + + if (!fifo) { + return -1; + } + + spin_lock_irqsave(&fifo->lock, flags); + if (!fifo->count) { + // FIFO empty + index = -1; + } else { + // Return index where to get the next data from the FIFO + index = fifo->r++ & (fifo->size-1); + fifo->count--; + } + spin_unlock_irqrestore(&fifo->lock, flags); + + return index; +} + +/* ====================================================================== + * control pipe + */ +typedef void (*ctrl_complete_t)(void *); + +typedef struct ctrl_msg { + struct usb_ctrlrequest dr; + ctrl_complete_t complete; + void *context; +} ctrl_msg; + +/* FIFO of ctrl messages waiting to be sent */ +#define MAX_EP0_MSG 16 +struct ctrl_msg_fifo { + struct fifo f; + struct ctrl_msg data[MAX_EP0_MSG]; +}; + +#define MAX_DFRAME_LEN_L1 300 +#define HSCX_BUFMAX 4096 + +struct st5481_ctrl { + struct ctrl_msg_fifo msg_fifo; + unsigned long busy; + struct urb *urb; +}; + +struct st5481_intr { + // struct evt_fifo evt_fifo; + struct urb *urb; +}; + +struct st5481_d_out { + struct isdnhdlc_vars hdlc_state; + struct urb *urb[2]; /* double buffering */ + unsigned long busy; + struct sk_buff *tx_skb; + struct FsmInst fsm; +}; + +struct st5481_b_out { + struct isdnhdlc_vars hdlc_state; + struct urb *urb[2]; /* double buffering */ + u_char flow_event; + u_long busy; + struct sk_buff *tx_skb; +}; + +struct st5481_in { + struct isdnhdlc_vars hdlc_state; + struct urb *urb[2]; /* double buffering */ + int mode; + int bufsize; + unsigned int num_packets; + unsigned int packet_size; + unsigned char ep, counter; + unsigned char *rcvbuf; + struct st5481_adapter *adapter; + struct hisax_if *hisax_if; +}; + +int st5481_setup_in(struct st5481_in *in); +void st5481_release_in(struct st5481_in *in); +void st5481_in_mode(struct st5481_in *in, int mode); + +struct st5481_bcs { + struct hisax_b_if b_if; + struct st5481_adapter *adapter; + struct st5481_in b_in; + struct st5481_b_out b_out; + int channel; + int mode; +}; + +struct st5481_adapter { + struct list_head list; + int number_of_leds; + struct usb_device *usb_dev; + struct hisax_d_if hisax_d_if; + + struct st5481_ctrl ctrl; + struct st5481_intr intr; + struct st5481_in d_in; + struct st5481_d_out d_out; + + unsigned char leds; + unsigned int led_counter; + + unsigned long event; + + struct FsmInst l1m; + struct FsmTimer timer; + + struct st5481_bcs bcs[2]; +}; + +#define TIMER3_VALUE 7000 + +/* ====================================================================== + * + */ + +/* + * Submit an URB with error reporting. This is a macro so + * the __FUNCTION__ returns the caller function name. + */ +#define SUBMIT_URB(urb, mem_flags) \ +({ \ + int status; \ + if ((status = usb_submit_urb(urb, mem_flags)) < 0) { \ + WARN("usb_submit_urb failed,status=%d", status); \ + } \ + status; \ +}) + +/* + * USB double buffering, return the URB index (0 or 1). + */ +static inline int get_buf_nr(struct urb *urbs[], struct urb *urb) +{ + return (urbs[0]==urb ? 0 : 1); +} + +/* ---------------------------------------------------------------------- */ + +/* B Channel */ + +int st5481_setup_b(struct st5481_bcs *bcs); +void st5481_release_b(struct st5481_bcs *bcs); +void st5481_d_l2l1(struct hisax_if *hisax_d_if, int pr, void *arg); + +/* D Channel */ + +int st5481_setup_d(struct st5481_adapter *adapter); +void st5481_release_d(struct st5481_adapter *adapter); +void st5481_b_l2l1(struct hisax_if *b_if, int pr, void *arg); +int st5481_d_init(void); +void st5481_d_exit(void); + +/* USB */ +void st5481_ph_command(struct st5481_adapter *adapter, unsigned int command); +int st5481_setup_isocpipes(struct urb* urb[2], struct usb_device *dev, + unsigned int pipe, int num_packets, + int packet_size, int buf_size, + usb_complete_t complete, void *context); +void st5481_release_isocpipes(struct urb* urb[2]); + +int st5481_isoc_flatten(struct urb *urb); +void st5481_usb_pipe_reset(struct st5481_adapter *adapter, + u_char pipe, ctrl_complete_t complete, void *context); +void st5481_usb_ctrl_msg(struct st5481_adapter *adapter, + u8 request, u8 requesttype, u16 value, u16 index, + ctrl_complete_t complete, void *context); +void st5481_usb_device_ctrl_msg(struct st5481_adapter *adapter, + u8 request, u16 value, + ctrl_complete_t complete, void *context); +int st5481_setup_usb(struct st5481_adapter *adapter); +void st5481_release_usb(struct st5481_adapter *adapter); +void st5481_start(struct st5481_adapter *adapter); +void st5481_stop(struct st5481_adapter *adapter); + +// ---------------------------------------------------------------------- +// debugging macros + +#define __debug_variable st5481_debug +#include "hisax_debug.h" + +#ifdef CONFIG_HISAX_DEBUG + +extern int st5481_debug; + +#define DBG_ISO_PACKET(level,urb) \ + if (level & __debug_variable) dump_iso_packet(__FUNCTION__,urb) + +static void __attribute__((unused)) +dump_iso_packet(const char *name, struct urb *urb) +{ + int i,j; + int len,ofs; + u_char *data; + + printk(KERN_DEBUG "%s: packets=%d,errors=%d\n", + name,urb->number_of_packets,urb->error_count); + for (i = 0; i < urb->number_of_packets; ++i) { + if (urb->pipe & USB_DIR_IN) { + len = urb->iso_frame_desc[i].actual_length; + } else { + len = urb->iso_frame_desc[i].length; + } + ofs = urb->iso_frame_desc[i].offset; + printk(KERN_DEBUG "len=%.2d,ofs=%.3d ",len,ofs); + if (len) { + data = urb->transfer_buffer+ofs; + for (j=0; j < len; j++) { + printk ("%.2x", data[j]); + } + } + printk("\n"); + } +} + +static inline const char *ST5481_CMD_string(int evt) +{ + static char s[16]; + + switch (evt) { + case ST5481_CMD_DR: return "DR"; + case ST5481_CMD_RES: return "RES"; + case ST5481_CMD_TM1: return "TM1"; + case ST5481_CMD_TM2: return "TM2"; + case ST5481_CMD_PUP: return "PUP"; + case ST5481_CMD_AR8: return "AR8"; + case ST5481_CMD_AR10: return "AR10"; + case ST5481_CMD_ARL: return "ARL"; + case ST5481_CMD_PDN: return "PDN"; + }; + + sprintf(s,"0x%x",evt); + return s; +} + +#else + +#define DBG_ISO_PACKET(level,urb) do {} while (0) + +#endif + + + +#endif diff --git a/drivers/isdn/hisax/st5481_b.c b/drivers/isdn/hisax/st5481_b.c new file mode 100644 index 000000000000..2fcd093921d8 --- /dev/null +++ b/drivers/isdn/hisax/st5481_b.c @@ -0,0 +1,374 @@ +/* + * Driver for ST5481 USB ISDN modem + * + * Author Frode Isaksen + * Copyright 2001 by Frode Isaksen <fisaksen@bewan.com> + * 2001 by Kai Germaschewski <kai.germaschewski@gmx.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/init.h> +#include <linux/usb.h> +#include <linux/slab.h> +#include <linux/netdevice.h> +#include "st5481.h" + +static inline void B_L1L2(struct st5481_bcs *bcs, int pr, void *arg) +{ + struct hisax_if *ifc = (struct hisax_if *) &bcs->b_if; + + ifc->l1l2(ifc, pr, arg); +} + +/* + * Encode and transmit next frame. + */ +static void usb_b_out(struct st5481_bcs *bcs,int buf_nr) +{ + struct st5481_b_out *b_out = &bcs->b_out; + struct st5481_adapter *adapter = bcs->adapter; + struct urb *urb; + unsigned int packet_size,offset; + int len,buf_size,bytes_sent; + int i; + struct sk_buff *skb; + + if (test_and_set_bit(buf_nr, &b_out->busy)) { + DBG(4,"ep %d urb %d busy",(bcs->channel+1)*2,buf_nr); + return; + } + urb = b_out->urb[buf_nr]; + + // Adjust isoc buffer size according to flow state + if(b_out->flow_event & (OUT_DOWN | OUT_UNDERRUN)) { + buf_size = NUM_ISO_PACKETS_B*SIZE_ISO_PACKETS_B_OUT + B_FLOW_ADJUST; + packet_size = SIZE_ISO_PACKETS_B_OUT + B_FLOW_ADJUST; + DBG(4,"B%d,adjust flow,add %d bytes",bcs->channel+1,B_FLOW_ADJUST); + } else if(b_out->flow_event & OUT_UP){ + buf_size = NUM_ISO_PACKETS_B*SIZE_ISO_PACKETS_B_OUT - B_FLOW_ADJUST; + packet_size = SIZE_ISO_PACKETS_B_OUT - B_FLOW_ADJUST; + DBG(4,"B%d,adjust flow,remove %d bytes",bcs->channel+1,B_FLOW_ADJUST); + } else { + buf_size = NUM_ISO_PACKETS_B*SIZE_ISO_PACKETS_B_OUT; + packet_size = 8; + } + b_out->flow_event = 0; + + len = 0; + while (len < buf_size) { + if ((skb = b_out->tx_skb)) { + DBG_SKB(0x100, skb); + DBG(4,"B%d,len=%d",bcs->channel+1,skb->len); + + if (bcs->mode == L1_MODE_TRANS) { + bytes_sent = buf_size - len; + if (skb->len < bytes_sent) + bytes_sent = skb->len; + { /* swap tx bytes to get hearable audio data */ + register unsigned char *src = skb->data; + register unsigned char *dest = urb->transfer_buffer+len; + register unsigned int count; + for (count = 0; count < bytes_sent; count++) + *dest++ = isdnhdlc_bit_rev_tab[*src++]; + } + len += bytes_sent; + } else { + len += isdnhdlc_encode(&b_out->hdlc_state, + skb->data, skb->len, &bytes_sent, + urb->transfer_buffer+len, buf_size-len); + } + + skb_pull(skb, bytes_sent); + + if (!skb->len) { + // Frame sent + b_out->tx_skb = NULL; + B_L1L2(bcs, PH_DATA | CONFIRM, (void *) skb->truesize); + dev_kfree_skb_any(skb); + +/* if (!(bcs->tx_skb = skb_dequeue(&bcs->sq))) { */ +/* st5481B_sched_event(bcs, B_XMTBUFREADY); */ +/* } */ + } + } else { + if (bcs->mode == L1_MODE_TRANS) { + memset(urb->transfer_buffer+len, 0xff, buf_size-len); + len = buf_size; + } else { + // Send flags + len += isdnhdlc_encode(&b_out->hdlc_state, + NULL, 0, &bytes_sent, + urb->transfer_buffer+len, buf_size-len); + } + } + } + + // Prepare the URB + for (i = 0, offset = 0; offset < len; i++) { + urb->iso_frame_desc[i].offset = offset; + urb->iso_frame_desc[i].length = packet_size; + offset += packet_size; + packet_size = SIZE_ISO_PACKETS_B_OUT; + } + urb->transfer_buffer_length = len; + urb->number_of_packets = i; + urb->dev = adapter->usb_dev; + + DBG_ISO_PACKET(0x200,urb); + + SUBMIT_URB(urb, GFP_NOIO); +} + +/* + * Start transfering (flags or data) on the B channel, since + * FIFO counters has been set to a non-zero value. + */ +static void st5481B_start_xfer(void *context) +{ + struct st5481_bcs *bcs = context; + + DBG(4,"B%d",bcs->channel+1); + + // Start transmitting (flags or data) on B channel + + usb_b_out(bcs,0); + usb_b_out(bcs,1); +} + +/* + * If the adapter has only 2 LEDs, the green + * LED will blink with a rate depending + * on the number of channels opened. + */ +static void led_blink(struct st5481_adapter *adapter) +{ + u_char leds = adapter->leds; + + // 50 frames/sec for each channel + if (++adapter->led_counter % 50) { + return; + } + + if (adapter->led_counter % 100) { + leds |= GREEN_LED; + } else { + leds &= ~GREEN_LED; + } + + st5481_usb_device_ctrl_msg(adapter, GPIO_OUT, leds, NULL, NULL); +} + +static void usb_b_out_complete(struct urb *urb, struct pt_regs *regs) +{ + struct st5481_bcs *bcs = urb->context; + struct st5481_b_out *b_out = &bcs->b_out; + struct st5481_adapter *adapter = bcs->adapter; + int buf_nr; + + buf_nr = get_buf_nr(b_out->urb, urb); + test_and_clear_bit(buf_nr, &b_out->busy); + + if (unlikely(urb->status < 0)) { + if (urb->status != -ENOENT && urb->status != -ESHUTDOWN) { + WARN("urb status %d",urb->status); + if (b_out->busy == 0) { + st5481_usb_pipe_reset(adapter, (bcs->channel+1)*2 | USB_DIR_OUT, NULL, NULL); + } + } else { + DBG(1,"urb killed"); + return; // Give up + } + } + + usb_b_out(bcs,buf_nr); + + if (adapter->number_of_leds == 2) + led_blink(adapter); +} + +/* + * Start or stop the transfer on the B channel. + */ +static void st5481B_mode(struct st5481_bcs *bcs, int mode) +{ + struct st5481_b_out *b_out = &bcs->b_out; + struct st5481_adapter *adapter = bcs->adapter; + + DBG(4,"B%d,mode=%d", bcs->channel + 1, mode); + + if (bcs->mode == mode) + return; + + bcs->mode = mode; + + // Cancel all USB transfers on this B channel + usb_unlink_urb(b_out->urb[0]); + usb_unlink_urb(b_out->urb[1]); + b_out->busy = 0; + + st5481_in_mode(&bcs->b_in, mode); + if (bcs->mode != L1_MODE_NULL) { + // Open the B channel + if (bcs->mode != L1_MODE_TRANS) { + isdnhdlc_out_init(&b_out->hdlc_state, 0, bcs->mode == L1_MODE_HDLC_56K); + } + st5481_usb_pipe_reset(adapter, (bcs->channel+1)*2, NULL, NULL); + + // Enable B channel interrupts + st5481_usb_device_ctrl_msg(adapter, FFMSK_B1+(bcs->channel*2), + OUT_UP+OUT_DOWN+OUT_UNDERRUN, NULL, NULL); + + // Enable B channel FIFOs + st5481_usb_device_ctrl_msg(adapter, OUT_B1_COUNTER+(bcs->channel*2), 32, st5481B_start_xfer, bcs); + if (adapter->number_of_leds == 4) { + if (bcs->channel == 0) { + adapter->leds |= B1_LED; + } else { + adapter->leds |= B2_LED; + } + } + } else { + // Disble B channel interrupts + st5481_usb_device_ctrl_msg(adapter, FFMSK_B1+(bcs->channel*2), 0, NULL, NULL); + + // Disable B channel FIFOs + st5481_usb_device_ctrl_msg(adapter, OUT_B1_COUNTER+(bcs->channel*2), 0, NULL, NULL); + + if (adapter->number_of_leds == 4) { + if (bcs->channel == 0) { + adapter->leds &= ~B1_LED; + } else { + adapter->leds &= ~B2_LED; + } + } else { + st5481_usb_device_ctrl_msg(adapter, GPIO_OUT, adapter->leds, NULL, NULL); + } + if (b_out->tx_skb) { + dev_kfree_skb_any(b_out->tx_skb); + b_out->tx_skb = NULL; + } + + } +} + +static int st5481_setup_b_out(struct st5481_bcs *bcs) +{ + struct usb_device *dev = bcs->adapter->usb_dev; + struct usb_interface *intf; + struct usb_host_interface *altsetting = NULL; + struct usb_host_endpoint *endpoint; + struct st5481_b_out *b_out = &bcs->b_out; + + DBG(4,""); + + intf = usb_ifnum_to_if(dev, 0); + if (intf) + altsetting = usb_altnum_to_altsetting(intf, 3); + if (!altsetting) + return -ENXIO; + + // Allocate URBs and buffers for the B channel out + endpoint = &altsetting->endpoint[EP_B1_OUT - 1 + bcs->channel * 2]; + + DBG(4,"endpoint address=%02x,packet size=%d", + endpoint->desc.bEndpointAddress, le16_to_cpu(endpoint->desc.wMaxPacketSize)); + + // Allocate memory for 8000bytes/sec + extra bytes if underrun + return st5481_setup_isocpipes(b_out->urb, dev, + usb_sndisocpipe(dev, endpoint->desc.bEndpointAddress), + NUM_ISO_PACKETS_B, SIZE_ISO_PACKETS_B_OUT, + NUM_ISO_PACKETS_B * SIZE_ISO_PACKETS_B_OUT + B_FLOW_ADJUST, + usb_b_out_complete, bcs); +} + +static void st5481_release_b_out(struct st5481_bcs *bcs) +{ + struct st5481_b_out *b_out = &bcs->b_out; + + DBG(4,""); + + st5481_release_isocpipes(b_out->urb); +} + +int st5481_setup_b(struct st5481_bcs *bcs) +{ + int retval; + + DBG(4,""); + + retval = st5481_setup_b_out(bcs); + if (retval) + goto err; + bcs->b_in.bufsize = HSCX_BUFMAX; + bcs->b_in.num_packets = NUM_ISO_PACKETS_B; + bcs->b_in.packet_size = SIZE_ISO_PACKETS_B_IN; + bcs->b_in.ep = (bcs->channel ? EP_B2_IN : EP_B1_IN) | USB_DIR_IN; + bcs->b_in.counter = bcs->channel ? IN_B2_COUNTER : IN_B1_COUNTER; + bcs->b_in.adapter = bcs->adapter; + bcs->b_in.hisax_if = &bcs->b_if.ifc; + retval = st5481_setup_in(&bcs->b_in); + if (retval) + goto err_b_out; + + + return 0; + + err_b_out: + st5481_release_b_out(bcs); + err: + return retval; +} + +/* + * Release buffers and URBs for the B channels + */ +void st5481_release_b(struct st5481_bcs *bcs) +{ + DBG(4,""); + + st5481_release_in(&bcs->b_in); + st5481_release_b_out(bcs); +} + +/* + * st5481_b_l2l1 is the entry point for upper layer routines that want to + * transmit on the B channel. PH_DATA | REQUEST is a normal packet that + * we either start transmitting (if idle) or queue (if busy). + * PH_PULL | REQUEST can be called to request a callback message + * (PH_PULL | CONFIRM) + * once the link is idle. After a "pull" callback, the upper layer + * routines can use PH_PULL | INDICATION to send data. + */ +void st5481_b_l2l1(struct hisax_if *ifc, int pr, void *arg) +{ + struct st5481_bcs *bcs = ifc->priv; + struct sk_buff *skb = arg; + int mode; + + DBG(4, ""); + + switch (pr) { + case PH_DATA | REQUEST: + if (bcs->b_out.tx_skb) + BUG(); + + bcs->b_out.tx_skb = skb; + break; + case PH_ACTIVATE | REQUEST: + mode = (int) arg; + DBG(4,"B%d,PH_ACTIVATE_REQUEST %d", bcs->channel + 1, mode); + st5481B_mode(bcs, mode); + B_L1L2(bcs, PH_ACTIVATE | INDICATION, NULL); + break; + case PH_DEACTIVATE | REQUEST: + DBG(4,"B%d,PH_DEACTIVATE_REQUEST", bcs->channel + 1); + st5481B_mode(bcs, L1_MODE_NULL); + B_L1L2(bcs, PH_DEACTIVATE | INDICATION, NULL); + break; + default: + WARN("pr %#x\n", pr); + } +} diff --git a/drivers/isdn/hisax/st5481_d.c b/drivers/isdn/hisax/st5481_d.c new file mode 100644 index 000000000000..071b1d31999f --- /dev/null +++ b/drivers/isdn/hisax/st5481_d.c @@ -0,0 +1,776 @@ +/* + * Driver for ST5481 USB ISDN modem + * + * Author Frode Isaksen + * Copyright 2001 by Frode Isaksen <fisaksen@bewan.com> + * 2001 by Kai Germaschewski <kai.germaschewski@gmx.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/init.h> +#include <linux/usb.h> +#include <linux/slab.h> +#include <linux/netdevice.h> +#include "st5481.h" + +static void ph_connect(struct st5481_adapter *adapter); +static void ph_disconnect(struct st5481_adapter *adapter); + +static struct Fsm l1fsm; + +static char *strL1State[] = +{ + "ST_L1_F3", + "ST_L1_F4", + "ST_L1_F6", + "ST_L1_F7", + "ST_L1_F8", +}; + +static char *strL1Event[] = +{ + "EV_IND_DP", + "EV_IND_1", + "EV_IND_2", + "EV_IND_3", + "EV_IND_RSY", + "EV_IND_5", + "EV_IND_6", + "EV_IND_7", + "EV_IND_AP", + "EV_IND_9", + "EV_IND_10", + "EV_IND_11", + "EV_IND_AI8", + "EV_IND_AI10", + "EV_IND_AIL", + "EV_IND_DI", + "EV_PH_ACTIVATE_REQ", + "EV_PH_DEACTIVATE_REQ", + "EV_TIMER3", +}; + +static inline void D_L1L2(struct st5481_adapter *adapter, int pr, void *arg) +{ + struct hisax_if *ifc = (struct hisax_if *) &adapter->hisax_d_if; + + ifc->l1l2(ifc, pr, arg); +} + +static void +l1_go_f3(struct FsmInst *fi, int event, void *arg) +{ + struct st5481_adapter *adapter = fi->userdata; + + if (fi->state == ST_L1_F7) + ph_disconnect(adapter); + + FsmChangeState(fi, ST_L1_F3); + D_L1L2(adapter, PH_DEACTIVATE | INDICATION, NULL); +} + +static void +l1_go_f6(struct FsmInst *fi, int event, void *arg) +{ + struct st5481_adapter *adapter = fi->userdata; + + if (fi->state == ST_L1_F7) + ph_disconnect(adapter); + + FsmChangeState(fi, ST_L1_F6); +} + +static void +l1_go_f7(struct FsmInst *fi, int event, void *arg) +{ + struct st5481_adapter *adapter = fi->userdata; + + FsmDelTimer(&adapter->timer, 0); + ph_connect(adapter); + FsmChangeState(fi, ST_L1_F7); + D_L1L2(adapter, PH_ACTIVATE | INDICATION, NULL); +} + +static void +l1_go_f8(struct FsmInst *fi, int event, void *arg) +{ + struct st5481_adapter *adapter = fi->userdata; + + if (fi->state == ST_L1_F7) + ph_disconnect(adapter); + + FsmChangeState(fi, ST_L1_F8); +} + +static void +l1_timer3(struct FsmInst *fi, int event, void *arg) +{ + struct st5481_adapter *adapter = fi->userdata; + + st5481_ph_command(adapter, ST5481_CMD_DR); + FsmChangeState(fi, ST_L1_F3); + D_L1L2(adapter, PH_DEACTIVATE | INDICATION, NULL); +} + +static void +l1_ignore(struct FsmInst *fi, int event, void *arg) +{ +} + +static void +l1_activate(struct FsmInst *fi, int event, void *arg) +{ + struct st5481_adapter *adapter = fi->userdata; + + st5481_ph_command(adapter, ST5481_CMD_DR); + st5481_ph_command(adapter, ST5481_CMD_PUP); + FsmRestartTimer(&adapter->timer, TIMER3_VALUE, EV_TIMER3, NULL, 2); + st5481_ph_command(adapter, ST5481_CMD_AR8); + FsmChangeState(fi, ST_L1_F4); +} + +static struct FsmNode L1FnList[] __initdata = +{ + {ST_L1_F3, EV_IND_DP, l1_ignore}, + {ST_L1_F3, EV_IND_AP, l1_go_f6}, + {ST_L1_F3, EV_IND_AI8, l1_go_f7}, + {ST_L1_F3, EV_IND_AI10, l1_go_f7}, + {ST_L1_F3, EV_PH_ACTIVATE_REQ, l1_activate}, + + {ST_L1_F4, EV_TIMER3, l1_timer3}, + {ST_L1_F4, EV_IND_DP, l1_go_f3}, + {ST_L1_F4, EV_IND_AP, l1_go_f6}, + {ST_L1_F4, EV_IND_AI8, l1_go_f7}, + {ST_L1_F4, EV_IND_AI10, l1_go_f7}, + + {ST_L1_F6, EV_TIMER3, l1_timer3}, + {ST_L1_F6, EV_IND_DP, l1_go_f3}, + {ST_L1_F6, EV_IND_AP, l1_ignore}, + {ST_L1_F6, EV_IND_AI8, l1_go_f7}, + {ST_L1_F6, EV_IND_AI10, l1_go_f7}, + {ST_L1_F7, EV_IND_RSY, l1_go_f8}, + + {ST_L1_F7, EV_IND_DP, l1_go_f3}, + {ST_L1_F7, EV_IND_AP, l1_go_f6}, + {ST_L1_F7, EV_IND_AI8, l1_ignore}, + {ST_L1_F7, EV_IND_AI10, l1_ignore}, + {ST_L1_F7, EV_IND_RSY, l1_go_f8}, + + {ST_L1_F8, EV_TIMER3, l1_timer3}, + {ST_L1_F8, EV_IND_DP, l1_go_f3}, + {ST_L1_F8, EV_IND_AP, l1_go_f6}, + {ST_L1_F8, EV_IND_AI8, l1_go_f8}, + {ST_L1_F8, EV_IND_AI10, l1_go_f8}, + {ST_L1_F8, EV_IND_RSY, l1_ignore}, +}; + +static void l1m_debug(struct FsmInst *fi, char *fmt, ...) +{ + va_list args; + char buf[256]; + + va_start(args, fmt); + vsprintf(buf, fmt, args); + DBG(8, "%s", buf); + va_end(args); +} + +/* ====================================================================== + * D-Channel out + */ + +/* + D OUT state machine: + ==================== + + Transmit short frame (< 16 bytes of encoded data): + + L1 FRAME D_OUT_STATE USB D CHANNEL + -------- ----------- --- --------- + + FIXME + + -> [xx..xx] SHORT_INIT -> [7Exx..xxC1C27EFF] + SHORT_WAIT_DEN <> OUT_D_COUNTER=16 + + END_OF_SHORT <- DEN_EVENT -> 7Exx + xxxx + xxxx + xxxx + xxxx + xxxx + C1C1 + 7EFF + WAIT_FOR_RESET_IDLE <- D_UNDERRUN <- (8ms) + IDLE <> Reset pipe + + + + Transmit long frame (>= 16 bytes of encoded data): + + L1 FRAME D_OUT_STATE USB D CHANNEL + -------- ----------- --- --------- + + -> [xx...xx] IDLE + WAIT_FOR_STOP <> OUT_D_COUNTER=0 + WAIT_FOR_RESET <> Reset pipe + STOP + INIT_LONG_FRAME -> [7Exx..xx] + WAIT_DEN <> OUT_D_COUNTER=16 + OUT_NORMAL <- DEN_EVENT -> 7Exx + END_OF_FRAME_BUSY -> [xxxx] xxxx + END_OF_FRAME_NOT_BUSY -> [xxxx] xxxx + -> [xxxx] xxxx + -> [C1C2] xxxx + -> [7EFF] xxxx + xxxx + xxxx + .... + xxxx + C1C2 + 7EFF + <- D_UNDERRUN <- (> 8ms) + WAIT_FOR_STOP <> OUT_D_COUNTER=0 + WAIT_FOR_RESET <> Reset pipe + STOP + +*/ + +static struct Fsm dout_fsm; + +static char *strDoutState[] = +{ + "ST_DOUT_NONE", + + "ST_DOUT_SHORT_INIT", + "ST_DOUT_SHORT_WAIT_DEN", + + "ST_DOUT_LONG_INIT", + "ST_DOUT_LONG_WAIT_DEN", + "ST_DOUT_NORMAL", + + "ST_DOUT_WAIT_FOR_UNDERRUN", + "ST_DOUT_WAIT_FOR_NOT_BUSY", + "ST_DOUT_WAIT_FOR_STOP", + "ST_DOUT_WAIT_FOR_RESET", +}; + +static char *strDoutEvent[] = +{ + "EV_DOUT_START_XMIT", + "EV_DOUT_COMPLETE", + "EV_DOUT_DEN", + "EV_DOUT_RESETED", + "EV_DOUT_STOPPED", + "EV_DOUT_COLL", + "EV_DOUT_UNDERRUN", +}; + +static void dout_debug(struct FsmInst *fi, char *fmt, ...) +{ + va_list args; + char buf[256]; + + va_start(args, fmt); + vsprintf(buf, fmt, args); + DBG(0x2, "%s", buf); + va_end(args); +} + +static void dout_stop_event(void *context) +{ + struct st5481_adapter *adapter = context; + + FsmEvent(&adapter->d_out.fsm, EV_DOUT_STOPPED, NULL); +} + +/* + * Start the transfer of a D channel frame. + */ +static void usb_d_out(struct st5481_adapter *adapter, int buf_nr) +{ + struct st5481_d_out *d_out = &adapter->d_out; + struct urb *urb; + unsigned int num_packets, packet_offset; + int len, buf_size, bytes_sent; + struct sk_buff *skb; + struct usb_iso_packet_descriptor *desc; + + if (d_out->fsm.state != ST_DOUT_NORMAL) + return; + + if (test_and_set_bit(buf_nr, &d_out->busy)) { + DBG(2, "ep %d urb %d busy %#lx", EP_D_OUT, buf_nr, d_out->busy); + return; + } + urb = d_out->urb[buf_nr]; + + skb = d_out->tx_skb; + + buf_size = NUM_ISO_PACKETS_D * SIZE_ISO_PACKETS_D_OUT; + + if (skb) { + len = isdnhdlc_encode(&d_out->hdlc_state, + skb->data, skb->len, &bytes_sent, + urb->transfer_buffer, buf_size); + skb_pull(skb,bytes_sent); + } else { + // Send flags or idle + len = isdnhdlc_encode(&d_out->hdlc_state, + NULL, 0, &bytes_sent, + urb->transfer_buffer, buf_size); + } + + if (len < buf_size) { + FsmChangeState(&d_out->fsm, ST_DOUT_WAIT_FOR_UNDERRUN); + } + if (skb && !skb->len) { + d_out->tx_skb = NULL; + D_L1L2(adapter, PH_DATA | CONFIRM, NULL); + dev_kfree_skb_any(skb); + } + + // Prepare the URB + urb->transfer_buffer_length = len; + num_packets = 0; + packet_offset = 0; + while (packet_offset < len) { + desc = &urb->iso_frame_desc[num_packets]; + desc->offset = packet_offset; + desc->length = SIZE_ISO_PACKETS_D_OUT; + if (len - packet_offset < desc->length) + desc->length = len - packet_offset; + num_packets++; + packet_offset += desc->length; + } + urb->number_of_packets = num_packets; + + // Prepare the URB + urb->dev = adapter->usb_dev; + // Need to transmit the next buffer 2ms after the DEN_EVENT + urb->transfer_flags = 0; + urb->start_frame = usb_get_current_frame_number(adapter->usb_dev)+2; + + DBG_ISO_PACKET(0x20,urb); + + if (usb_submit_urb(urb, GFP_KERNEL) < 0) { + // There is another URB queued up + urb->transfer_flags = URB_ISO_ASAP; + SUBMIT_URB(urb, GFP_KERNEL); + } +} + +static void fifo_reseted(void *context) +{ + struct st5481_adapter *adapter = context; + + FsmEvent(&adapter->d_out.fsm, EV_DOUT_RESETED, NULL); +} + +static void usb_d_out_complete(struct urb *urb, struct pt_regs *regs) +{ + struct st5481_adapter *adapter = urb->context; + struct st5481_d_out *d_out = &adapter->d_out; + int buf_nr; + + DBG(2, ""); + + buf_nr = get_buf_nr(d_out->urb, urb); + test_and_clear_bit(buf_nr, &d_out->busy); + + if (unlikely(urb->status < 0)) { + if (urb->status != -ENOENT && urb->status != -ESHUTDOWN) { + WARN("urb status %d",urb->status); + if (d_out->busy == 0) { + st5481_usb_pipe_reset(adapter, EP_D_OUT | USB_DIR_OUT, fifo_reseted, adapter); + } + return; + } else { + DBG(1,"urb killed"); + return; // Give up + } + } + + FsmEvent(&adapter->d_out.fsm, EV_DOUT_COMPLETE, (void *) buf_nr); +} + +/* ====================================================================== */ + +static void dout_start_xmit(struct FsmInst *fsm, int event, void *arg) +{ + // FIXME unify? + struct st5481_adapter *adapter = fsm->userdata; + struct st5481_d_out *d_out = &adapter->d_out; + struct urb *urb; + int len, bytes_sent; + struct sk_buff *skb; + int buf_nr = 0; + + skb = d_out->tx_skb; + + DBG(2,"len=%d",skb->len); + + isdnhdlc_out_init(&d_out->hdlc_state, 1, 0); + + if (test_and_set_bit(buf_nr, &d_out->busy)) { + WARN("ep %d urb %d busy %#lx", EP_D_OUT, buf_nr, d_out->busy); + return; + } + urb = d_out->urb[buf_nr]; + + DBG_SKB(0x10, skb); + len = isdnhdlc_encode(&d_out->hdlc_state, + skb->data, skb->len, &bytes_sent, + urb->transfer_buffer, 16); + skb_pull(skb, bytes_sent); + + if(len < 16) + FsmChangeState(&d_out->fsm, ST_DOUT_SHORT_INIT); + else + FsmChangeState(&d_out->fsm, ST_DOUT_LONG_INIT); + + if (skb->len == 0) { + d_out->tx_skb = NULL; + D_L1L2(adapter, PH_DATA | CONFIRM, NULL); + dev_kfree_skb_any(skb); + } + +// Prepare the URB + urb->transfer_buffer_length = len; + + urb->iso_frame_desc[0].offset = 0; + urb->iso_frame_desc[0].length = len; + urb->number_of_packets = 1; + + // Prepare the URB + urb->dev = adapter->usb_dev; + urb->transfer_flags = URB_ISO_ASAP; + + DBG_ISO_PACKET(0x20,urb); + SUBMIT_URB(urb, GFP_KERNEL); +} + +static void dout_short_fifo(struct FsmInst *fsm, int event, void *arg) +{ + struct st5481_adapter *adapter = fsm->userdata; + struct st5481_d_out *d_out = &adapter->d_out; + + FsmChangeState(&d_out->fsm, ST_DOUT_SHORT_WAIT_DEN); + st5481_usb_device_ctrl_msg(adapter, OUT_D_COUNTER, 16, NULL, NULL); +} + +static void dout_end_short_frame(struct FsmInst *fsm, int event, void *arg) +{ + struct st5481_adapter *adapter = fsm->userdata; + struct st5481_d_out *d_out = &adapter->d_out; + + FsmChangeState(&d_out->fsm, ST_DOUT_WAIT_FOR_UNDERRUN); +} + +static void dout_long_enable_fifo(struct FsmInst *fsm, int event, void *arg) +{ + struct st5481_adapter *adapter = fsm->userdata; + struct st5481_d_out *d_out = &adapter->d_out; + + st5481_usb_device_ctrl_msg(adapter, OUT_D_COUNTER, 16, NULL, NULL); + FsmChangeState(&d_out->fsm, ST_DOUT_LONG_WAIT_DEN); +} + +static void dout_long_den(struct FsmInst *fsm, int event, void *arg) +{ + struct st5481_adapter *adapter = fsm->userdata; + struct st5481_d_out *d_out = &adapter->d_out; + + FsmChangeState(&d_out->fsm, ST_DOUT_NORMAL); + usb_d_out(adapter, 0); + usb_d_out(adapter, 1); +} + +static void dout_reset(struct FsmInst *fsm, int event, void *arg) +{ + struct st5481_adapter *adapter = fsm->userdata; + struct st5481_d_out *d_out = &adapter->d_out; + + FsmChangeState(&d_out->fsm, ST_DOUT_WAIT_FOR_RESET); + st5481_usb_pipe_reset(adapter, EP_D_OUT | USB_DIR_OUT, fifo_reseted, adapter); +} + +static void dout_stop(struct FsmInst *fsm, int event, void *arg) +{ + struct st5481_adapter *adapter = fsm->userdata; + struct st5481_d_out *d_out = &adapter->d_out; + + FsmChangeState(&d_out->fsm, ST_DOUT_WAIT_FOR_STOP); + st5481_usb_device_ctrl_msg(adapter, OUT_D_COUNTER, 0, dout_stop_event, adapter); +} + +static void dout_underrun(struct FsmInst *fsm, int event, void *arg) +{ + struct st5481_adapter *adapter = fsm->userdata; + struct st5481_d_out *d_out = &adapter->d_out; + + if (test_bit(0, &d_out->busy) || test_bit(1, &d_out->busy)) { + FsmChangeState(&d_out->fsm, ST_DOUT_WAIT_FOR_NOT_BUSY); + } else { + dout_stop(fsm, event, arg); + } +} + +static void dout_check_busy(struct FsmInst *fsm, int event, void *arg) +{ + struct st5481_adapter *adapter = fsm->userdata; + struct st5481_d_out *d_out = &adapter->d_out; + + if (!test_bit(0, &d_out->busy) && !test_bit(1, &d_out->busy)) + dout_stop(fsm, event, arg); +} + +static void dout_reseted(struct FsmInst *fsm, int event, void *arg) +{ + struct st5481_adapter *adapter = fsm->userdata; + struct st5481_d_out *d_out = &adapter->d_out; + + FsmChangeState(&d_out->fsm, ST_DOUT_NONE); + // FIXME locking + if (d_out->tx_skb) + FsmEvent(&d_out->fsm, EV_DOUT_START_XMIT, NULL); +} + +static void dout_complete(struct FsmInst *fsm, int event, void *arg) +{ + struct st5481_adapter *adapter = fsm->userdata; + int buf_nr = (int) arg; + + usb_d_out(adapter, buf_nr); +} + +static void dout_ignore(struct FsmInst *fsm, int event, void *arg) +{ +} + +static struct FsmNode DoutFnList[] __initdata = +{ + {ST_DOUT_NONE, EV_DOUT_START_XMIT, dout_start_xmit}, + + {ST_DOUT_SHORT_INIT, EV_DOUT_COMPLETE, dout_short_fifo}, + + {ST_DOUT_SHORT_WAIT_DEN, EV_DOUT_DEN, dout_end_short_frame}, + {ST_DOUT_SHORT_WAIT_DEN, EV_DOUT_UNDERRUN, dout_underrun}, + + {ST_DOUT_LONG_INIT, EV_DOUT_COMPLETE, dout_long_enable_fifo}, + + {ST_DOUT_LONG_WAIT_DEN, EV_DOUT_DEN, dout_long_den}, + {ST_DOUT_LONG_WAIT_DEN, EV_DOUT_UNDERRUN, dout_underrun}, + + {ST_DOUT_NORMAL, EV_DOUT_UNDERRUN, dout_underrun}, + {ST_DOUT_NORMAL, EV_DOUT_COMPLETE, dout_complete}, + + {ST_DOUT_WAIT_FOR_UNDERRUN, EV_DOUT_UNDERRUN, dout_underrun}, + {ST_DOUT_WAIT_FOR_UNDERRUN, EV_DOUT_COMPLETE, dout_ignore}, + + {ST_DOUT_WAIT_FOR_NOT_BUSY, EV_DOUT_COMPLETE, dout_check_busy}, + + {ST_DOUT_WAIT_FOR_STOP, EV_DOUT_STOPPED, dout_reset}, + + {ST_DOUT_WAIT_FOR_RESET, EV_DOUT_RESETED, dout_reseted}, +}; + +void st5481_d_l2l1(struct hisax_if *hisax_d_if, int pr, void *arg) +{ + struct st5481_adapter *adapter = hisax_d_if->priv; + struct sk_buff *skb = arg; + + switch (pr) { + case PH_ACTIVATE | REQUEST: + FsmEvent(&adapter->l1m, EV_PH_ACTIVATE_REQ, NULL); + break; + case PH_DEACTIVATE | REQUEST: + FsmEvent(&adapter->l1m, EV_PH_DEACTIVATE_REQ, NULL); + break; + case PH_DATA | REQUEST: + DBG(2, "PH_DATA REQUEST len %d", skb->len); + if (adapter->d_out.tx_skb) + BUG(); + + adapter->d_out.tx_skb = skb; + FsmEvent(&adapter->d_out.fsm, EV_DOUT_START_XMIT, NULL); + break; + default: + WARN("pr %#x\n", pr); + break; + } +} + +/* ====================================================================== + */ + +/* + * Start receiving on the D channel since entered state F7. + */ +static void ph_connect(struct st5481_adapter *adapter) +{ + struct st5481_d_out *d_out = &adapter->d_out; + struct st5481_in *d_in = &adapter->d_in; + + DBG(8,""); + + FsmChangeState(&d_out->fsm, ST_DOUT_NONE); + + // st5481_usb_device_ctrl_msg(adapter, FFMSK_D, OUT_UNDERRUN, NULL, NULL); + st5481_usb_device_ctrl_msg(adapter, FFMSK_D, 0xfc, NULL, NULL); + st5481_in_mode(d_in, L1_MODE_HDLC); + +#ifdef LOOPBACK + // Turn loopback on (data sent on B and D looped back) + st5481_usb_device_ctrl_msg(cs, LBB, 0x04, NULL, NULL); +#endif + + st5481_usb_pipe_reset(adapter, EP_D_OUT | USB_DIR_OUT, NULL, NULL); + + // Turn on the green LED to tell that we are in state F7 + adapter->leds |= GREEN_LED; + st5481_usb_device_ctrl_msg(adapter, GPIO_OUT, adapter->leds, NULL, NULL); +} + +/* + * Stop receiving on the D channel since not in state F7. + */ +static void ph_disconnect(struct st5481_adapter *adapter) +{ + DBG(8,""); + + st5481_in_mode(&adapter->d_in, L1_MODE_NULL); + + // Turn off the green LED to tell that we left state F7 + adapter->leds &= ~GREEN_LED; + st5481_usb_device_ctrl_msg(adapter, GPIO_OUT, adapter->leds, NULL, NULL); +} + +static int st5481_setup_d_out(struct st5481_adapter *adapter) +{ + struct usb_device *dev = adapter->usb_dev; + struct usb_interface *intf; + struct usb_host_interface *altsetting = NULL; + struct usb_host_endpoint *endpoint; + struct st5481_d_out *d_out = &adapter->d_out; + + DBG(2,""); + + intf = usb_ifnum_to_if(dev, 0); + if (intf) + altsetting = usb_altnum_to_altsetting(intf, 3); + if (!altsetting) + return -ENXIO; + + // Allocate URBs and buffers for the D channel out + endpoint = &altsetting->endpoint[EP_D_OUT-1]; + + DBG(2,"endpoint address=%02x,packet size=%d", + endpoint->desc.bEndpointAddress, le16_to_cpu(endpoint->desc.wMaxPacketSize)); + + return st5481_setup_isocpipes(d_out->urb, dev, + usb_sndisocpipe(dev, endpoint->desc.bEndpointAddress), + NUM_ISO_PACKETS_D, SIZE_ISO_PACKETS_D_OUT, + NUM_ISO_PACKETS_D * SIZE_ISO_PACKETS_D_OUT, + usb_d_out_complete, adapter); +} + +static void st5481_release_d_out(struct st5481_adapter *adapter) +{ + struct st5481_d_out *d_out = &adapter->d_out; + + DBG(2,""); + + st5481_release_isocpipes(d_out->urb); +} + +int st5481_setup_d(struct st5481_adapter *adapter) +{ + int retval; + + DBG(2,""); + + retval = st5481_setup_d_out(adapter); + if (retval) + goto err; + adapter->d_in.bufsize = MAX_DFRAME_LEN_L1; + adapter->d_in.num_packets = NUM_ISO_PACKETS_D; + adapter->d_in.packet_size = SIZE_ISO_PACKETS_D_IN; + adapter->d_in.ep = EP_D_IN | USB_DIR_IN; + adapter->d_in.counter = IN_D_COUNTER; + adapter->d_in.adapter = adapter; + adapter->d_in.hisax_if = &adapter->hisax_d_if.ifc; + retval = st5481_setup_in(&adapter->d_in); + if (retval) + goto err_d_out; + + adapter->l1m.fsm = &l1fsm; + adapter->l1m.state = ST_L1_F3; + adapter->l1m.debug = 1; + adapter->l1m.userdata = adapter; + adapter->l1m.printdebug = l1m_debug; + FsmInitTimer(&adapter->l1m, &adapter->timer); + + adapter->d_out.fsm.fsm = &dout_fsm; + adapter->d_out.fsm.state = ST_DOUT_NONE; + adapter->d_out.fsm.debug = 1; + adapter->d_out.fsm.userdata = adapter; + adapter->d_out.fsm.printdebug = dout_debug; + + return 0; + + err_d_out: + st5481_release_d_out(adapter); + err: + return retval; +} + +void st5481_release_d(struct st5481_adapter *adapter) +{ + DBG(2,""); + + st5481_release_in(&adapter->d_in); + st5481_release_d_out(adapter); +} + +/* ====================================================================== + * init / exit + */ + +int __init st5481_d_init(void) +{ + int retval; + + l1fsm.state_count = L1_STATE_COUNT; + l1fsm.event_count = L1_EVENT_COUNT; + l1fsm.strEvent = strL1Event; + l1fsm.strState = strL1State; + retval = FsmNew(&l1fsm, L1FnList, ARRAY_SIZE(L1FnList)); + if (retval) + goto err; + + dout_fsm.state_count = DOUT_STATE_COUNT; + dout_fsm.event_count = DOUT_EVENT_COUNT; + dout_fsm.strEvent = strDoutEvent; + dout_fsm.strState = strDoutState; + retval = FsmNew(&dout_fsm, DoutFnList, ARRAY_SIZE(DoutFnList)); + if (retval) + goto err_l1; + + return 0; + + err_l1: + FsmFree(&l1fsm); + err: + return retval; +} + +// can't be __exit +void st5481_d_exit(void) +{ + FsmFree(&l1fsm); + FsmFree(&dout_fsm); +} diff --git a/drivers/isdn/hisax/st5481_hdlc.c b/drivers/isdn/hisax/st5481_hdlc.c new file mode 100644 index 000000000000..680f42e9a993 --- /dev/null +++ b/drivers/isdn/hisax/st5481_hdlc.c @@ -0,0 +1,580 @@ +/* + * Driver for ST5481 USB ISDN modem + * + * Author Frode Isaksen + * Copyright 2001 by Frode Isaksen <fisaksen@bewan.com> + * 2001 by Kai Germaschewski <kai.germaschewski@gmx.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/crc-ccitt.h> +#include "st5481_hdlc.h" + + +enum { + HDLC_FAST_IDLE,HDLC_GET_FLAG_B0,HDLC_GETFLAG_B1A6,HDLC_GETFLAG_B7, + HDLC_GET_DATA,HDLC_FAST_FLAG +}; + +enum { + HDLC_SEND_DATA,HDLC_SEND_CRC1,HDLC_SEND_FAST_FLAG, + HDLC_SEND_FIRST_FLAG,HDLC_SEND_CRC2,HDLC_SEND_CLOSING_FLAG, + HDLC_SEND_IDLE1,HDLC_SEND_FAST_IDLE,HDLC_SENDFLAG_B0, + HDLC_SENDFLAG_B1A6,HDLC_SENDFLAG_B7,STOPPED +}; + +void +hdlc_rcv_init(struct hdlc_vars *hdlc, int do_adapt56) +{ + hdlc->bit_shift = 0; + hdlc->hdlc_bits1 = 0; + hdlc->data_bits = 0; + hdlc->ffbit_shift = 0; + hdlc->data_received = 0; + hdlc->state = HDLC_GET_DATA; + hdlc->do_adapt56 = do_adapt56; + hdlc->dchannel = 0; + hdlc->crc = 0; + hdlc->cbin = 0; + hdlc->shift_reg = 0; + hdlc->ffvalue = 0; + hdlc->dstpos = 0; +} + +void +hdlc_out_init(struct hdlc_vars *hdlc, int is_d_channel, int do_adapt56) +{ + hdlc->bit_shift = 0; + hdlc->hdlc_bits1 = 0; + hdlc->data_bits = 0; + hdlc->ffbit_shift = 0; + hdlc->data_received = 0; + hdlc->do_closing = 0; + hdlc->ffvalue = 0; + if (is_d_channel) { + hdlc->dchannel = 1; + hdlc->state = HDLC_SEND_FIRST_FLAG; + } else { + hdlc->dchannel = 0; + hdlc->state = HDLC_SEND_FAST_FLAG; + hdlc->ffvalue = 0x7e; + } + hdlc->cbin = 0x7e; + hdlc->bit_shift = 0; + if(do_adapt56){ + hdlc->do_adapt56 = 1; + hdlc->data_bits = 0; + hdlc->state = HDLC_SENDFLAG_B0; + } else { + hdlc->do_adapt56 = 0; + hdlc->data_bits = 8; + } + hdlc->shift_reg = 0; +} + +/* + hdlc_decode - decodes HDLC frames from a transparent bit stream. + + The source buffer is scanned for valid HDLC frames looking for + flags (01111110) to indicate the start of a frame. If the start of + the frame is found, the bit stuffing is removed (0 after 5 1's). + When a new flag is found, the complete frame has been received + and the CRC is checked. + If a valid frame is found, the function returns the frame length + excluding the CRC with the bit HDLC_END_OF_FRAME set. + If the beginning of a valid frame is found, the function returns + the length. + If a framing error is found (too many 1s and not a flag) the function + returns the length with the bit HDLC_FRAMING_ERROR set. + If a CRC error is found the function returns the length with the + bit HDLC_CRC_ERROR set. + If the frame length exceeds the destination buffer size, the function + returns the length with the bit HDLC_LENGTH_ERROR set. + + src - source buffer + slen - source buffer length + count - number of bytes removed (decoded) from the source buffer + dst _ destination buffer + dsize - destination buffer size + returns - number of decoded bytes in the destination buffer and status + flag. + */ +int hdlc_decode(struct hdlc_vars *hdlc, const unsigned char *src, + int slen, int *count, unsigned char *dst, int dsize) +{ + int status=0; + + static const unsigned char fast_flag[]={ + 0x00,0x00,0x00,0x20,0x30,0x38,0x3c,0x3e,0x3f + }; + + static const unsigned char fast_flag_value[]={ + 0x00,0x7e,0xfc,0xf9,0xf3,0xe7,0xcf,0x9f,0x3f + }; + + static const unsigned char fast_abort[]={ + 0x00,0x00,0x80,0xc0,0xe0,0xf0,0xf8,0xfc,0xfe,0xff + }; + + *count = slen; + + while(slen > 0){ + if(hdlc->bit_shift==0){ + hdlc->cbin = *src++; + slen--; + hdlc->bit_shift = 8; + if(hdlc->do_adapt56){ + hdlc->bit_shift --; + } + } + + switch(hdlc->state){ + case STOPPED: + return 0; + case HDLC_FAST_IDLE: + if(hdlc->cbin == 0xff){ + hdlc->bit_shift = 0; + break; + } + hdlc->state = HDLC_GET_FLAG_B0; + hdlc->hdlc_bits1 = 0; + hdlc->bit_shift = 8; + break; + case HDLC_GET_FLAG_B0: + if(!(hdlc->cbin & 0x80)) { + hdlc->state = HDLC_GETFLAG_B1A6; + hdlc->hdlc_bits1 = 0; + } else { + if(!hdlc->do_adapt56){ + if(++hdlc->hdlc_bits1 >=8 ) if(hdlc->bit_shift==1) + hdlc->state = HDLC_FAST_IDLE; + } + } + hdlc->cbin<<=1; + hdlc->bit_shift --; + break; + case HDLC_GETFLAG_B1A6: + if(hdlc->cbin & 0x80){ + hdlc->hdlc_bits1++; + if(hdlc->hdlc_bits1==6){ + hdlc->state = HDLC_GETFLAG_B7; + } + } else { + hdlc->hdlc_bits1 = 0; + } + hdlc->cbin<<=1; + hdlc->bit_shift --; + break; + case HDLC_GETFLAG_B7: + if(hdlc->cbin & 0x80) { + hdlc->state = HDLC_GET_FLAG_B0; + } else { + hdlc->state = HDLC_GET_DATA; + hdlc->crc = 0xffff; + hdlc->shift_reg = 0; + hdlc->hdlc_bits1 = 0; + hdlc->data_bits = 0; + hdlc->data_received = 0; + } + hdlc->cbin<<=1; + hdlc->bit_shift --; + break; + case HDLC_GET_DATA: + if(hdlc->cbin & 0x80){ + hdlc->hdlc_bits1++; + switch(hdlc->hdlc_bits1){ + case 6: + break; + case 7: + if(hdlc->data_received) { + // bad frame + status = -HDLC_FRAMING_ERROR; + } + if(!hdlc->do_adapt56){ + if(hdlc->cbin==fast_abort[hdlc->bit_shift+1]){ + hdlc->state = HDLC_FAST_IDLE; + hdlc->bit_shift=1; + break; + } + } else { + hdlc->state = HDLC_GET_FLAG_B0; + } + break; + default: + hdlc->shift_reg>>=1; + hdlc->shift_reg |= 0x80; + hdlc->data_bits++; + break; + } + } else { + switch(hdlc->hdlc_bits1){ + case 5: + break; + case 6: + if(hdlc->data_received){ + if (hdlc->dstpos < 2) { + status = -HDLC_FRAMING_ERROR; + } else if (hdlc->crc != 0xf0b8){ + // crc error + status = -HDLC_CRC_ERROR; + } else { + // remove CRC + hdlc->dstpos -= 2; + // good frame + status = hdlc->dstpos; + } + } + hdlc->crc = 0xffff; + hdlc->shift_reg = 0; + hdlc->data_bits = 0; + if(!hdlc->do_adapt56){ + if(hdlc->cbin==fast_flag[hdlc->bit_shift]){ + hdlc->ffvalue = fast_flag_value[hdlc->bit_shift]; + hdlc->state = HDLC_FAST_FLAG; + hdlc->ffbit_shift = hdlc->bit_shift; + hdlc->bit_shift = 1; + } else { + hdlc->state = HDLC_GET_DATA; + hdlc->data_received = 0; + } + } else { + hdlc->state = HDLC_GET_DATA; + hdlc->data_received = 0; + } + break; + default: + hdlc->shift_reg>>=1; + hdlc->data_bits++; + break; + } + hdlc->hdlc_bits1 = 0; + } + if (status) { + hdlc->dstpos = 0; + *count -= slen; + hdlc->cbin <<= 1; + hdlc->bit_shift--; + return status; + } + if(hdlc->data_bits==8){ + hdlc->data_bits = 0; + hdlc->data_received = 1; + hdlc->crc = crc_ccitt_byte(hdlc->crc, hdlc->shift_reg); + + // good byte received + if (dsize--) { + dst[hdlc->dstpos++] = hdlc->shift_reg; + } else { + // frame too long + status = -HDLC_LENGTH_ERROR; + hdlc->dstpos = 0; + } + } + hdlc->cbin <<= 1; + hdlc->bit_shift--; + break; + case HDLC_FAST_FLAG: + if(hdlc->cbin==hdlc->ffvalue){ + hdlc->bit_shift = 0; + break; + } else { + if(hdlc->cbin == 0xff){ + hdlc->state = HDLC_FAST_IDLE; + hdlc->bit_shift=0; + } else if(hdlc->ffbit_shift==8){ + hdlc->state = HDLC_GETFLAG_B7; + break; + } else { + hdlc->shift_reg = fast_abort[hdlc->ffbit_shift-1]; + hdlc->hdlc_bits1 = hdlc->ffbit_shift-2; + if(hdlc->hdlc_bits1<0)hdlc->hdlc_bits1 = 0; + hdlc->data_bits = hdlc->ffbit_shift-1; + hdlc->state = HDLC_GET_DATA; + hdlc->data_received = 0; + } + } + break; + default: + break; + } + } + *count -= slen; + return 0; +} + +/* + hdlc_encode - encodes HDLC frames to a transparent bit stream. + + The bit stream starts with a beginning flag (01111110). After + that each byte is added to the bit stream with bit stuffing added + (0 after 5 1's). + When the last byte has been removed from the source buffer, the + CRC (2 bytes is added) and the frame terminates with the ending flag. + For the dchannel, the idle character (all 1's) is also added at the end. + If this function is called with empty source buffer (slen=0), flags or + idle character will be generated. + + src - source buffer + slen - source buffer length + count - number of bytes removed (encoded) from source buffer + dst _ destination buffer + dsize - destination buffer size + returns - number of encoded bytes in the destination buffer +*/ +int hdlc_encode(struct hdlc_vars *hdlc, const unsigned char *src, + unsigned short slen, int *count, + unsigned char *dst, int dsize) +{ + static const unsigned char xfast_flag_value[] = { + 0x7e,0x3f,0x9f,0xcf,0xe7,0xf3,0xf9,0xfc,0x7e + }; + + int len = 0; + + *count = slen; + + while (dsize > 0) { + if(hdlc->bit_shift==0){ + if(slen && !hdlc->do_closing){ + hdlc->shift_reg = *src++; + slen--; + if (slen == 0) + hdlc->do_closing = 1; /* closing sequence, CRC + flag(s) */ + hdlc->bit_shift = 8; + } else { + if(hdlc->state == HDLC_SEND_DATA){ + if(hdlc->data_received){ + hdlc->state = HDLC_SEND_CRC1; + hdlc->crc ^= 0xffff; + hdlc->bit_shift = 8; + hdlc->shift_reg = hdlc->crc & 0xff; + } else if(!hdlc->do_adapt56){ + hdlc->state = HDLC_SEND_FAST_FLAG; + } else { + hdlc->state = HDLC_SENDFLAG_B0; + } + } + + } + } + + switch(hdlc->state){ + case STOPPED: + while (dsize--) + *dst++ = 0xff; + + return dsize; + case HDLC_SEND_FAST_FLAG: + hdlc->do_closing = 0; + if(slen == 0){ + *dst++ = hdlc->ffvalue; + len++; + dsize--; + break; + } + if(hdlc->bit_shift==8){ + hdlc->cbin = hdlc->ffvalue>>(8-hdlc->data_bits); + hdlc->state = HDLC_SEND_DATA; + hdlc->crc = 0xffff; + hdlc->hdlc_bits1 = 0; + hdlc->data_received = 1; + } + break; + case HDLC_SENDFLAG_B0: + hdlc->do_closing = 0; + hdlc->cbin <<= 1; + hdlc->data_bits++; + hdlc->hdlc_bits1 = 0; + hdlc->state = HDLC_SENDFLAG_B1A6; + break; + case HDLC_SENDFLAG_B1A6: + hdlc->cbin <<= 1; + hdlc->data_bits++; + hdlc->cbin++; + if(++hdlc->hdlc_bits1 == 6) + hdlc->state = HDLC_SENDFLAG_B7; + break; + case HDLC_SENDFLAG_B7: + hdlc->cbin <<= 1; + hdlc->data_bits++; + if(slen == 0){ + hdlc->state = HDLC_SENDFLAG_B0; + break; + } + if(hdlc->bit_shift==8){ + hdlc->state = HDLC_SEND_DATA; + hdlc->crc = 0xffff; + hdlc->hdlc_bits1 = 0; + hdlc->data_received = 1; + } + break; + case HDLC_SEND_FIRST_FLAG: + hdlc->data_received = 1; + if(hdlc->data_bits==8){ + hdlc->state = HDLC_SEND_DATA; + hdlc->crc = 0xffff; + hdlc->hdlc_bits1 = 0; + break; + } + hdlc->cbin <<= 1; + hdlc->data_bits++; + if(hdlc->shift_reg & 0x01) + hdlc->cbin++; + hdlc->shift_reg >>= 1; + hdlc->bit_shift--; + if(hdlc->bit_shift==0){ + hdlc->state = HDLC_SEND_DATA; + hdlc->crc = 0xffff; + hdlc->hdlc_bits1 = 0; + } + break; + case HDLC_SEND_DATA: + hdlc->cbin <<= 1; + hdlc->data_bits++; + if(hdlc->hdlc_bits1 == 5){ + hdlc->hdlc_bits1 = 0; + break; + } + if(hdlc->bit_shift==8){ + hdlc->crc = crc_ccitt_byte(hdlc->crc, hdlc->shift_reg); + } + if(hdlc->shift_reg & 0x01){ + hdlc->hdlc_bits1++; + hdlc->cbin++; + hdlc->shift_reg >>= 1; + hdlc->bit_shift--; + } else { + hdlc->hdlc_bits1 = 0; + hdlc->shift_reg >>= 1; + hdlc->bit_shift--; + } + break; + case HDLC_SEND_CRC1: + hdlc->cbin <<= 1; + hdlc->data_bits++; + if(hdlc->hdlc_bits1 == 5){ + hdlc->hdlc_bits1 = 0; + break; + } + if(hdlc->shift_reg & 0x01){ + hdlc->hdlc_bits1++; + hdlc->cbin++; + hdlc->shift_reg >>= 1; + hdlc->bit_shift--; + } else { + hdlc->hdlc_bits1 = 0; + hdlc->shift_reg >>= 1; + hdlc->bit_shift--; + } + if(hdlc->bit_shift==0){ + hdlc->shift_reg = (hdlc->crc >> 8); + hdlc->state = HDLC_SEND_CRC2; + hdlc->bit_shift = 8; + } + break; + case HDLC_SEND_CRC2: + hdlc->cbin <<= 1; + hdlc->data_bits++; + if(hdlc->hdlc_bits1 == 5){ + hdlc->hdlc_bits1 = 0; + break; + } + if(hdlc->shift_reg & 0x01){ + hdlc->hdlc_bits1++; + hdlc->cbin++; + hdlc->shift_reg >>= 1; + hdlc->bit_shift--; + } else { + hdlc->hdlc_bits1 = 0; + hdlc->shift_reg >>= 1; + hdlc->bit_shift--; + } + if(hdlc->bit_shift==0){ + hdlc->shift_reg = 0x7e; + hdlc->state = HDLC_SEND_CLOSING_FLAG; + hdlc->bit_shift = 8; + } + break; + case HDLC_SEND_CLOSING_FLAG: + hdlc->cbin <<= 1; + hdlc->data_bits++; + if(hdlc->hdlc_bits1 == 5){ + hdlc->hdlc_bits1 = 0; + break; + } + if(hdlc->shift_reg & 0x01){ + hdlc->cbin++; + } + hdlc->shift_reg >>= 1; + hdlc->bit_shift--; + if(hdlc->bit_shift==0){ + hdlc->ffvalue = xfast_flag_value[hdlc->data_bits]; + if(hdlc->dchannel){ + hdlc->ffvalue = 0x7e; + hdlc->state = HDLC_SEND_IDLE1; + hdlc->bit_shift = 8-hdlc->data_bits; + if(hdlc->bit_shift==0) + hdlc->state = HDLC_SEND_FAST_IDLE; + } else { + if(!hdlc->do_adapt56){ + hdlc->state = HDLC_SEND_FAST_FLAG; + hdlc->data_received = 0; + } else { + hdlc->state = HDLC_SENDFLAG_B0; + hdlc->data_received = 0; + } + // Finished with this frame, send flags + if (dsize > 1) dsize = 1; + } + } + break; + case HDLC_SEND_IDLE1: + hdlc->do_closing = 0; + hdlc->cbin <<= 1; + hdlc->cbin++; + hdlc->data_bits++; + hdlc->bit_shift--; + if(hdlc->bit_shift==0){ + hdlc->state = HDLC_SEND_FAST_IDLE; + hdlc->bit_shift = 0; + } + break; + case HDLC_SEND_FAST_IDLE: + hdlc->do_closing = 0; + hdlc->cbin = 0xff; + hdlc->data_bits = 8; + if(hdlc->bit_shift == 8){ + hdlc->cbin = 0x7e; + hdlc->state = HDLC_SEND_FIRST_FLAG; + } else { + *dst++ = hdlc->cbin; + hdlc->bit_shift = hdlc->data_bits = 0; + len++; + dsize = 0; + } + break; + default: + break; + } + if(hdlc->do_adapt56){ + if(hdlc->data_bits==7){ + hdlc->cbin <<= 1; + hdlc->cbin++; + hdlc->data_bits++; + } + } + if(hdlc->data_bits==8){ + *dst++ = hdlc->cbin; + hdlc->data_bits = 0; + len++; + dsize--; + } + } + *count -= slen; + + return len; +} + diff --git a/drivers/isdn/hisax/st5481_hdlc.h b/drivers/isdn/hisax/st5481_hdlc.h new file mode 100644 index 000000000000..495432f0f6ba --- /dev/null +++ b/drivers/isdn/hisax/st5481_hdlc.h @@ -0,0 +1,62 @@ +/* + * Driver for ST5481 USB ISDN modem + * + * Author Frode Isaksen + * Copyright 2001 by Frode Isaksen <fisaksen@bewan.com> + * 2001 by Kai Germaschewski <kai.germaschewski@gmx.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#ifndef __ST5481_HDLC_H__ +#define __ST5481_HDLC_H__ + +struct hdlc_vars { + int bit_shift; + int hdlc_bits1; + int data_bits; + int ffbit_shift; // encoding only + int state; + int dstpos; + + int data_received:1; // set if transferring data + int dchannel:1; // set if D channel (send idle instead of flags) + int do_adapt56:1; // set if 56K adaptation + int do_closing:1; // set if in closing phase (need to send CRC + flag + + unsigned short crc; + + unsigned char cbin; + unsigned char shift_reg; + unsigned char ffvalue; + +}; + + +/* + The return value from hdlc_decode is + the frame length, 0 if no complete frame was decoded, + or a negative error number +*/ + +#define HDLC_FRAMING_ERROR 1 +#define HDLC_CRC_ERROR 2 +#define HDLC_LENGTH_ERROR 3 + +void +hdlc_rcv_init(struct hdlc_vars *hdlc, int do_adapt56); + +int +hdlc_decode(struct hdlc_vars *hdlc, const unsigned char *src, int slen,int *count, + unsigned char *dst, int dsize); + +void +hdlc_out_init(struct hdlc_vars *hdlc,int is_d_channel,int do_adapt56); + +int +hdlc_encode(struct hdlc_vars *hdlc,const unsigned char *src,unsigned short slen,int *count, + unsigned char *dst,int dsize); + +#endif diff --git a/drivers/isdn/hisax/st5481_init.c b/drivers/isdn/hisax/st5481_init.c new file mode 100644 index 000000000000..7aa810d5d333 --- /dev/null +++ b/drivers/isdn/hisax/st5481_init.c @@ -0,0 +1,224 @@ +/* + * Driver for ST5481 USB ISDN modem + * + * Author Frode Isaksen + * Copyright 2001 by Frode Isaksen <fisaksen@bewan.com> + * 2001 by Kai Germaschewski <kai.germaschewski@gmx.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +/* + * TODO: + * + * b layer1 delay? + * hotplug / unregister issues + * mod_inc/dec_use_count + * unify parts of d/b channel usb handling + * file header + * avoid copy to isoc buffer? + * improve usb delay? + * merge l1 state machines? + * clean up debug + */ + +#include <linux/config.h> +#include <linux/version.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/usb.h> +#include <linux/slab.h> +#include "st5481.h" + +MODULE_DESCRIPTION("ISDN4Linux: driver for ST5481 USB ISDN adapter"); +MODULE_AUTHOR("Frode Isaksen"); +MODULE_LICENSE("GPL"); + +static int protocol = 2; /* EURO-ISDN Default */ +module_param(protocol, int, 0); + +static int number_of_leds = 2; /* 2 LEDs on the adpater default */ +module_param(number_of_leds, int, 0); + +#ifdef CONFIG_HISAX_DEBUG +static int debug = 0x1; +module_param(debug, int, 0); +int st5481_debug; +#endif + +static LIST_HEAD(adapter_list); + +/* ====================================================================== + * registration/deregistration with the USB layer + */ + +/* + * This function will be called when the adapter is plugged + * into the USB bus. + */ +static int probe_st5481(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *dev = interface_to_usbdev(intf); + struct st5481_adapter *adapter; + struct hisax_b_if *b_if[2]; + int retval, i; + + printk(KERN_INFO "st541: found adapter VendorId %04x, ProductId %04x, LEDs %d\n", + le16_to_cpu(dev->descriptor.idVendor), + le16_to_cpu(dev->descriptor.idProduct), + number_of_leds); + + adapter = kmalloc(sizeof(struct st5481_adapter), GFP_KERNEL); + if (!adapter) + return -ENOMEM; + + memset(adapter, 0, sizeof(struct st5481_adapter)); + + adapter->number_of_leds = number_of_leds; + adapter->usb_dev = dev; + + adapter->hisax_d_if.owner = THIS_MODULE; + adapter->hisax_d_if.ifc.priv = adapter; + adapter->hisax_d_if.ifc.l2l1 = st5481_d_l2l1; + + for (i = 0; i < 2; i++) { + adapter->bcs[i].adapter = adapter; + adapter->bcs[i].channel = i; + adapter->bcs[i].b_if.ifc.priv = &adapter->bcs[i]; + adapter->bcs[i].b_if.ifc.l2l1 = st5481_b_l2l1; + } + list_add(&adapter->list, &adapter_list); + + retval = st5481_setup_usb(adapter); + if (retval < 0) + goto err; + + retval = st5481_setup_d(adapter); + if (retval < 0) + goto err_usb; + + retval = st5481_setup_b(&adapter->bcs[0]); + if (retval < 0) + goto err_d; + + retval = st5481_setup_b(&adapter->bcs[1]); + if (retval < 0) + goto err_b; + + for (i = 0; i < 2; i++) + b_if[i] = &adapter->bcs[i].b_if; + + hisax_register(&adapter->hisax_d_if, b_if, "st5481_usb", protocol); + st5481_start(adapter); + + usb_set_intfdata(intf, adapter); + return 0; + + err_b: + st5481_release_b(&adapter->bcs[0]); + err_d: + st5481_release_d(adapter); + err_usb: + st5481_release_usb(adapter); + err: + return -EIO; +} + +/* + * This function will be called when the adapter is removed + * from the USB bus. + */ +static void disconnect_st5481(struct usb_interface *intf) +{ + struct st5481_adapter *adapter = usb_get_intfdata(intf); + + DBG(1,""); + + usb_set_intfdata(intf, NULL); + if (!adapter) + return; + + list_del(&adapter->list); + + st5481_stop(adapter); + st5481_release_b(&adapter->bcs[1]); + st5481_release_b(&adapter->bcs[0]); + st5481_release_d(adapter); + // we would actually better wait for completion of outstanding urbs + mdelay(2); + st5481_release_usb(adapter); + + hisax_unregister(&adapter->hisax_d_if); + + kfree(adapter); +} + +/* + * The last 4 bits in the Product Id is set with 4 pins on the chip. + */ +static struct usb_device_id st5481_ids[] = { + { USB_DEVICE(ST_VENDOR_ID, ST5481_PRODUCT_ID+0x0) }, + { USB_DEVICE(ST_VENDOR_ID, ST5481_PRODUCT_ID+0x1) }, + { USB_DEVICE(ST_VENDOR_ID, ST5481_PRODUCT_ID+0x2) }, + { USB_DEVICE(ST_VENDOR_ID, ST5481_PRODUCT_ID+0x3) }, + { USB_DEVICE(ST_VENDOR_ID, ST5481_PRODUCT_ID+0x4) }, + { USB_DEVICE(ST_VENDOR_ID, ST5481_PRODUCT_ID+0x5) }, + { USB_DEVICE(ST_VENDOR_ID, ST5481_PRODUCT_ID+0x6) }, + { USB_DEVICE(ST_VENDOR_ID, ST5481_PRODUCT_ID+0x7) }, + { USB_DEVICE(ST_VENDOR_ID, ST5481_PRODUCT_ID+0x8) }, + { USB_DEVICE(ST_VENDOR_ID, ST5481_PRODUCT_ID+0x9) }, + { USB_DEVICE(ST_VENDOR_ID, ST5481_PRODUCT_ID+0xA) }, + { USB_DEVICE(ST_VENDOR_ID, ST5481_PRODUCT_ID+0xB) }, + { USB_DEVICE(ST_VENDOR_ID, ST5481_PRODUCT_ID+0xC) }, + { USB_DEVICE(ST_VENDOR_ID, ST5481_PRODUCT_ID+0xD) }, + { USB_DEVICE(ST_VENDOR_ID, ST5481_PRODUCT_ID+0xE) }, + { USB_DEVICE(ST_VENDOR_ID, ST5481_PRODUCT_ID+0xF) }, + { } +}; +MODULE_DEVICE_TABLE (usb, st5481_ids); + +static struct usb_driver st5481_usb_driver = { + .owner = THIS_MODULE, + .name = "st5481_usb", + .probe = probe_st5481, + .disconnect = disconnect_st5481, + .id_table = st5481_ids, +}; + +static int __init st5481_usb_init(void) +{ + int retval; + +#ifdef CONFIG_HISAX_DEBUG + st5481_debug = debug; +#endif + + printk(KERN_INFO "hisax_st5481: ST5481 USB ISDN driver $Revision: 2.4.2.3 $\n"); + + retval = st5481_d_init(); + if (retval < 0) + goto out; + + retval = usb_register(&st5481_usb_driver); + if (retval < 0) + goto out_d_exit; + + return 0; + + out_d_exit: + st5481_d_exit(); + out: + return retval; +} + +static void __exit st5481_usb_exit(void) +{ + usb_deregister(&st5481_usb_driver); + st5481_d_exit(); +} + +module_init(st5481_usb_init); +module_exit(st5481_usb_exit); diff --git a/drivers/isdn/hisax/st5481_usb.c b/drivers/isdn/hisax/st5481_usb.c new file mode 100644 index 000000000000..2369180b1cb1 --- /dev/null +++ b/drivers/isdn/hisax/st5481_usb.c @@ -0,0 +1,650 @@ +/* + * Driver for ST5481 USB ISDN modem + * + * Author Frode Isaksen + * Copyright 2001 by Frode Isaksen <fisaksen@bewan.com> + * 2001 by Kai Germaschewski <kai.germaschewski@gmx.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/init.h> +#include <linux/usb.h> +#include <linux/slab.h> +#include "st5481.h" + +/* ====================================================================== + * control pipe + */ + +/* + * Send the next endpoint 0 request stored in the FIFO. + * Called either by the completion or by usb_ctrl_msg. + */ +static void usb_next_ctrl_msg(struct urb *urb, + struct st5481_adapter *adapter) +{ + struct st5481_ctrl *ctrl = &adapter->ctrl; + int r_index; + + if (test_and_set_bit(0, &ctrl->busy)) { + return; + } + + if ((r_index = fifo_remove(&ctrl->msg_fifo.f)) < 0) { + test_and_clear_bit(0,&ctrl->busy); + return; + } + urb->setup_packet = + (unsigned char *)&ctrl->msg_fifo.data[r_index]; + + DBG(1,"request=0x%02x,value=0x%04x,index=%x", + ((struct ctrl_msg *)urb->setup_packet)->dr.bRequest, + ((struct ctrl_msg *)urb->setup_packet)->dr.wValue, + ((struct ctrl_msg *)urb->setup_packet)->dr.wIndex); + + // Prepare the URB + urb->dev = adapter->usb_dev; + + SUBMIT_URB(urb, GFP_ATOMIC); +} + +/* + * Asynchronous endpoint 0 request (async version of usb_control_msg). + * The request will be queued up in a FIFO if the endpoint is busy. + */ +void usb_ctrl_msg(struct st5481_adapter *adapter, + u8 request, u8 requesttype, u16 value, u16 index, + ctrl_complete_t complete, void *context) +{ + struct st5481_ctrl *ctrl = &adapter->ctrl; + int w_index; + struct ctrl_msg *ctrl_msg; + + if ((w_index = fifo_add(&ctrl->msg_fifo.f)) < 0) { + WARN("control msg FIFO full"); + return; + } + ctrl_msg = &ctrl->msg_fifo.data[w_index]; + + ctrl_msg->dr.bRequestType = requesttype; + ctrl_msg->dr.bRequest = request; + ctrl_msg->dr.wValue = cpu_to_le16p(&value); + ctrl_msg->dr.wIndex = cpu_to_le16p(&index); + ctrl_msg->dr.wLength = 0; + ctrl_msg->complete = complete; + ctrl_msg->context = context; + + usb_next_ctrl_msg(ctrl->urb, adapter); +} + +/* + * Asynchronous endpoint 0 device request. + */ +void st5481_usb_device_ctrl_msg(struct st5481_adapter *adapter, + u8 request, u16 value, + ctrl_complete_t complete, void *context) +{ + usb_ctrl_msg(adapter, request, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + value, 0, complete, context); +} + +/* + * Asynchronous pipe reset (async version of usb_clear_halt). + */ +void st5481_usb_pipe_reset(struct st5481_adapter *adapter, + u_char pipe, + ctrl_complete_t complete, void *context) +{ + DBG(1,"pipe=%02x",pipe); + + usb_ctrl_msg(adapter, + USB_REQ_CLEAR_FEATURE, USB_DIR_OUT | USB_RECIP_ENDPOINT, + 0, pipe, complete, context); +} + + +/* + Physical level functions +*/ + +void st5481_ph_command(struct st5481_adapter *adapter, unsigned int command) +{ + DBG(8,"command=%s", ST5481_CMD_string(command)); + + st5481_usb_device_ctrl_msg(adapter, TXCI, command, NULL, NULL); +} + +/* + * The request on endpoint 0 has completed. + * Call the user provided completion routine and try + * to send the next request. + */ +static void usb_ctrl_complete(struct urb *urb, struct pt_regs *regs) +{ + struct st5481_adapter *adapter = urb->context; + struct st5481_ctrl *ctrl = &adapter->ctrl; + struct ctrl_msg *ctrl_msg; + + if (unlikely(urb->status < 0)) { + if (urb->status != -ENOENT && urb->status != -ESHUTDOWN) { + WARN("urb status %d",urb->status); + } else { + DBG(1,"urb killed"); + return; // Give up + } + } + + ctrl_msg = (struct ctrl_msg *)urb->setup_packet; + + if (ctrl_msg->dr.bRequest == USB_REQ_CLEAR_FEATURE) { + /* Special case handling for pipe reset */ + le16_to_cpus(&ctrl_msg->dr.wIndex); + + /* toggle is reset on clear */ + usb_settoggle(adapter->usb_dev, + ctrl_msg->dr.wIndex & ~USB_DIR_IN, + (ctrl_msg->dr.wIndex & USB_DIR_IN) == 0, + 0); + + + } + + if (ctrl_msg->complete) + ctrl_msg->complete(ctrl_msg->context); + + clear_bit(0, &ctrl->busy); + + // Try to send next control message + usb_next_ctrl_msg(urb, adapter); + return; +} + +/* ====================================================================== + * interrupt pipe + */ + +/* + * The interrupt endpoint will be called when any + * of the 6 registers changes state (depending on masks). + * Decode the register values and schedule a private event. + * Called at interrupt. + */ +static void usb_int_complete(struct urb *urb, struct pt_regs *regs) +{ + u8 *data = urb->transfer_buffer; + u8 irqbyte; + struct st5481_adapter *adapter = urb->context; + int j; + int status; + + switch (urb->status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + DBG(1, "urb shutting down with status: %d", urb->status); + return; + default: + WARN("nonzero urb status received: %d", urb->status); + goto exit; + } + + + DBG_PACKET(1, data, INT_PKT_SIZE); + + if (urb->actual_length == 0) { + goto exit; + } + + irqbyte = data[MPINT]; + if (irqbyte & DEN_INT) + FsmEvent(&adapter->d_out.fsm, EV_DOUT_DEN, NULL); + + if (irqbyte & DCOLL_INT) + FsmEvent(&adapter->d_out.fsm, EV_DOUT_COLL, NULL); + + irqbyte = data[FFINT_D]; + if (irqbyte & OUT_UNDERRUN) + FsmEvent(&adapter->d_out.fsm, EV_DOUT_UNDERRUN, NULL); + + if (irqbyte & OUT_DOWN) +;// printk("OUT_DOWN\n"); + + irqbyte = data[MPINT]; + if (irqbyte & RXCI_INT) + FsmEvent(&adapter->l1m, data[CCIST] & 0x0f, NULL); + + for (j = 0; j < 2; j++) + adapter->bcs[j].b_out.flow_event |= data[FFINT_B1 + j]; + + urb->actual_length = 0; + +exit: + status = usb_submit_urb (urb, GFP_ATOMIC); + if (status) + WARN("usb_submit_urb failed with result %d", status); +} + +/* ====================================================================== + * initialization + */ + +int st5481_setup_usb(struct st5481_adapter *adapter) +{ + struct usb_device *dev = adapter->usb_dev; + struct st5481_ctrl *ctrl = &adapter->ctrl; + struct st5481_intr *intr = &adapter->intr; + struct usb_interface *intf; + struct usb_host_interface *altsetting = NULL; + struct usb_host_endpoint *endpoint; + int status; + struct urb *urb; + u8 *buf; + + DBG(1,""); + + if ((status = usb_reset_configuration (dev)) < 0) { + WARN("reset_configuration failed,status=%d",status); + return status; + } + + intf = usb_ifnum_to_if(dev, 0); + if (intf) + altsetting = usb_altnum_to_altsetting(intf, 3); + if (!altsetting) + return -ENXIO; + + // Check if the config is sane + if ( altsetting->desc.bNumEndpoints != 7 ) { + WARN("expecting 7 got %d endpoints!", altsetting->desc.bNumEndpoints); + return -EINVAL; + } + + // The descriptor is wrong for some early samples of the ST5481 chip + altsetting->endpoint[3].desc.wMaxPacketSize = __constant_cpu_to_le16(32); + altsetting->endpoint[4].desc.wMaxPacketSize = __constant_cpu_to_le16(32); + + // Use alternative setting 3 on interface 0 to have 2B+D + if ((status = usb_set_interface (dev, 0, 3)) < 0) { + WARN("usb_set_interface failed,status=%d",status); + return status; + } + + // Allocate URB for control endpoint + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + return -ENOMEM; + } + ctrl->urb = urb; + + // Fill the control URB + usb_fill_control_urb (urb, dev, + usb_sndctrlpipe(dev, 0), + NULL, NULL, 0, usb_ctrl_complete, adapter); + + + fifo_init(&ctrl->msg_fifo.f, ARRAY_SIZE(ctrl->msg_fifo.data)); + + // Allocate URBs and buffers for interrupt endpoint + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + return -ENOMEM; + } + intr->urb = urb; + + buf = kmalloc(INT_PKT_SIZE, GFP_KERNEL); + if (!buf) { + return -ENOMEM; + } + + endpoint = &altsetting->endpoint[EP_INT-1]; + + // Fill the interrupt URB + usb_fill_int_urb(urb, dev, + usb_rcvintpipe(dev, endpoint->desc.bEndpointAddress), + buf, INT_PKT_SIZE, + usb_int_complete, adapter, + endpoint->desc.bInterval); + + return 0; +} + +/* + * Release buffers and URBs for the interrupt and control + * endpoint. + */ +void st5481_release_usb(struct st5481_adapter *adapter) +{ + struct st5481_intr *intr = &adapter->intr; + struct st5481_ctrl *ctrl = &adapter->ctrl; + + DBG(1,""); + + // Stop and free Control and Interrupt URBs + usb_unlink_urb(ctrl->urb); + if (ctrl->urb->transfer_buffer) + kfree(ctrl->urb->transfer_buffer); + usb_free_urb(ctrl->urb); + + usb_unlink_urb(intr->urb); + if (intr->urb->transfer_buffer) + kfree(intr->urb->transfer_buffer); + usb_free_urb(intr->urb); +} + +/* + * Initialize the adapter. + */ +void st5481_start(struct st5481_adapter *adapter) +{ + static const u8 init_cmd_table[]={ + SET_DEFAULT,0, + STT,0, + SDA_MIN,0x0d, + SDA_MAX,0x29, + SDELAY_VALUE,0x14, + GPIO_DIR,0x01, + GPIO_OUT,RED_LED, +// FFCTRL_OUT_D,4, +// FFCTRH_OUT_D,12, + FFCTRL_OUT_B1,6, + FFCTRH_OUT_B1,20, + FFCTRL_OUT_B2,6, + FFCTRH_OUT_B2,20, + MPMSK,RXCI_INT+DEN_INT+DCOLL_INT, + 0 + }; + struct st5481_intr *intr = &adapter->intr; + int i = 0; + u8 request,value; + + DBG(8,""); + + adapter->leds = RED_LED; + + // Start receiving on the interrupt endpoint + SUBMIT_URB(intr->urb, GFP_KERNEL); + + while ((request = init_cmd_table[i++])) { + value = init_cmd_table[i++]; + st5481_usb_device_ctrl_msg(adapter, request, value, NULL, NULL); + } + st5481_ph_command(adapter, ST5481_CMD_PUP); +} + +/* + * Reset the adapter to default values. + */ +void st5481_stop(struct st5481_adapter *adapter) +{ + DBG(8,""); + + st5481_usb_device_ctrl_msg(adapter, SET_DEFAULT, 0, NULL, NULL); +} + +/* ====================================================================== + * isochronous USB helpers + */ + +static void +fill_isoc_urb(struct urb *urb, struct usb_device *dev, + unsigned int pipe, void *buf, int num_packets, + int packet_size, usb_complete_t complete, + void *context) +{ + int k; + + spin_lock_init(&urb->lock); + urb->dev=dev; + urb->pipe=pipe; + urb->transfer_buffer=buf; + urb->number_of_packets = num_packets; + urb->transfer_buffer_length=num_packets*packet_size; + urb->actual_length = 0; + urb->complete=complete; + urb->context=context; + urb->transfer_flags=URB_ISO_ASAP; + for (k = 0; k < num_packets; k++) { + urb->iso_frame_desc[k].offset = packet_size * k; + urb->iso_frame_desc[k].length = packet_size; + urb->iso_frame_desc[k].actual_length = 0; + } +} + +int +st5481_setup_isocpipes(struct urb* urb[2], struct usb_device *dev, + unsigned int pipe, int num_packets, + int packet_size, int buf_size, + usb_complete_t complete, void *context) +{ + int j, retval; + unsigned char *buf; + + for (j = 0; j < 2; j++) { + retval = -ENOMEM; + urb[j] = usb_alloc_urb(num_packets, GFP_KERNEL); + if (!urb[j]) + goto err; + + // Allocate memory for 2000bytes/sec (16Kb/s) + buf = kmalloc(buf_size, GFP_KERNEL); + if (!buf) + goto err; + + // Fill the isochronous URB + fill_isoc_urb(urb[j], dev, pipe, buf, + num_packets, packet_size, complete, + context); + } + return 0; + + err: + for (j = 0; j < 2; j++) { + if (urb[j]) { + if (urb[j]->transfer_buffer) + kfree(urb[j]->transfer_buffer); + usb_free_urb(urb[j]); + } + } + return retval; +} + +void st5481_release_isocpipes(struct urb* urb[2]) +{ + int j; + + for (j = 0; j < 2; j++) { + usb_unlink_urb(urb[j]); + if (urb[j]->transfer_buffer) + kfree(urb[j]->transfer_buffer); + usb_free_urb(urb[j]); + } +} + +/* + * Decode frames received on the B/D channel. + * Note that this function will be called continously + * with 64Kbit/s / 16Kbit/s of data and hence it will be + * called 50 times per second with 20 ISOC descriptors. + * Called at interrupt. + */ +static void usb_in_complete(struct urb *urb, struct pt_regs *regs) +{ + struct st5481_in *in = urb->context; + unsigned char *ptr; + struct sk_buff *skb; + int len, count, status; + + if (unlikely(urb->status < 0)) { + if (urb->status != -ENOENT && urb->status != -ESHUTDOWN) { + WARN("urb status %d",urb->status); + } else { + DBG(1,"urb killed"); + return; // Give up + } + } + + DBG_ISO_PACKET(0x80,urb); + + len = st5481_isoc_flatten(urb); + ptr = urb->transfer_buffer; + while (len > 0) { + if (in->mode == L1_MODE_TRANS) { + memcpy(in->rcvbuf, ptr, len); + status = len; + len = 0; + } else { + status = isdnhdlc_decode(&in->hdlc_state, ptr, len, &count, + in->rcvbuf, in->bufsize); + ptr += count; + len -= count; + } + + if (status > 0) { + // Good frame received + DBG(4,"count=%d",status); + DBG_PACKET(0x400, in->rcvbuf, status); + if (!(skb = dev_alloc_skb(status))) { + WARN("receive out of memory\n"); + break; + } + memcpy(skb_put(skb, status), in->rcvbuf, status); + in->hisax_if->l1l2(in->hisax_if, PH_DATA | INDICATION, skb); + } else if (status == -HDLC_CRC_ERROR) { + INFO("CRC error"); + } else if (status == -HDLC_FRAMING_ERROR) { + INFO("framing error"); + } else if (status == -HDLC_LENGTH_ERROR) { + INFO("length error"); + } + } + + // Prepare URB for next transfer + urb->dev = in->adapter->usb_dev; + urb->actual_length = 0; + + SUBMIT_URB(urb, GFP_ATOMIC); +} + +int st5481_setup_in(struct st5481_in *in) +{ + struct usb_device *dev = in->adapter->usb_dev; + int retval; + + DBG(4,""); + + in->rcvbuf = kmalloc(in->bufsize, GFP_KERNEL); + retval = -ENOMEM; + if (!in->rcvbuf) + goto err; + + retval = st5481_setup_isocpipes(in->urb, dev, + usb_rcvisocpipe(dev, in->ep), + in->num_packets, in->packet_size, + in->num_packets * in->packet_size, + usb_in_complete, in); + if (retval) + goto err_free; + return 0; + + err_free: + kfree(in->rcvbuf); + err: + return retval; +} + +void st5481_release_in(struct st5481_in *in) +{ + DBG(2,""); + + st5481_release_isocpipes(in->urb); +} + +/* + * Make the transfer_buffer contiguous by + * copying from the iso descriptors if necessary. + */ +int st5481_isoc_flatten(struct urb *urb) +{ + struct usb_iso_packet_descriptor *pipd,*pend; + unsigned char *src,*dst; + unsigned int len; + + if (urb->status < 0) { + return urb->status; + } + for (pipd = &urb->iso_frame_desc[0], + pend = &urb->iso_frame_desc[urb->number_of_packets], + dst = urb->transfer_buffer; + pipd < pend; + pipd++) { + + if (pipd->status < 0) { + return (pipd->status); + } + + len = pipd->actual_length; + pipd->actual_length = 0; + src = urb->transfer_buffer+pipd->offset; + + if (src != dst) { + // Need to copy since isoc buffers not full + while (len--) { + *dst++ = *src++; + } + } else { + // No need to copy, just update destination buffer + dst += len; + } + } + // Return size of flattened buffer + return (dst - (unsigned char *)urb->transfer_buffer); +} + +static void st5481_start_rcv(void *context) +{ + struct st5481_in *in = context; + struct st5481_adapter *adapter = in->adapter; + + DBG(4,""); + + in->urb[0]->dev = adapter->usb_dev; + SUBMIT_URB(in->urb[0], GFP_KERNEL); + + in->urb[1]->dev = adapter->usb_dev; + SUBMIT_URB(in->urb[1], GFP_KERNEL); +} + +void st5481_in_mode(struct st5481_in *in, int mode) +{ + if (in->mode == mode) + return; + + in->mode = mode; + + usb_unlink_urb(in->urb[0]); + usb_unlink_urb(in->urb[1]); + + if (in->mode != L1_MODE_NULL) { + if (in->mode != L1_MODE_TRANS) + isdnhdlc_rcv_init(&in->hdlc_state, + in->mode == L1_MODE_HDLC_56K); + + st5481_usb_pipe_reset(in->adapter, in->ep, NULL, NULL); + st5481_usb_device_ctrl_msg(in->adapter, in->counter, + in->packet_size, + NULL, NULL); + st5481_start_rcv(in); + } else { + st5481_usb_device_ctrl_msg(in->adapter, in->counter, + 0, NULL, NULL); + } +} + diff --git a/drivers/isdn/hisax/tei.c b/drivers/isdn/hisax/tei.c new file mode 100644 index 000000000000..082726db3985 --- /dev/null +++ b/drivers/isdn/hisax/tei.c @@ -0,0 +1,466 @@ +/* $Id: tei.c,v 2.20.2.3 2004/01/13 14:31:26 keil Exp $ + * + * Author Karsten Keil + * based on the teles driver from Jan den Ouden + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * For changes and modifications please read + * Documentation/isdn/HiSax.cert + * + * Thanks to Jan den Ouden + * Fritz Elfert + * + */ + +#include "hisax.h" +#include "isdnl2.h" +#include <linux/init.h> +#include <linux/random.h> + +const char *tei_revision = "$Revision: 2.20.2.3 $"; + +#define ID_REQUEST 1 +#define ID_ASSIGNED 2 +#define ID_DENIED 3 +#define ID_CHK_REQ 4 +#define ID_CHK_RES 5 +#define ID_REMOVE 6 +#define ID_VERIFY 7 + +#define TEI_ENTITY_ID 0xf + +static struct Fsm teifsm; + +void tei_handler(struct PStack *st, u_char pr, struct sk_buff *skb); + +enum { + ST_TEI_NOP, + ST_TEI_IDREQ, + ST_TEI_IDVERIFY, +}; + +#define TEI_STATE_COUNT (ST_TEI_IDVERIFY+1) + +static char *strTeiState[] = +{ + "ST_TEI_NOP", + "ST_TEI_IDREQ", + "ST_TEI_IDVERIFY", +}; + +enum { + EV_IDREQ, + EV_ASSIGN, + EV_DENIED, + EV_CHKREQ, + EV_REMOVE, + EV_VERIFY, + EV_T202, +}; + +#define TEI_EVENT_COUNT (EV_T202+1) + +static char *strTeiEvent[] = +{ + "EV_IDREQ", + "EV_ASSIGN", + "EV_DENIED", + "EV_CHKREQ", + "EV_REMOVE", + "EV_VERIFY", + "EV_T202", +}; + +unsigned int +random_ri(void) +{ + unsigned int x; + + get_random_bytes(&x, sizeof(x)); + return (x & 0xffff); +} + +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); +} + +static void +put_tei_msg(struct PStack *st, u_char m_id, unsigned int ri, u_char tei) +{ + struct sk_buff *skb; + u_char *bp; + + if (!(skb = alloc_skb(8, GFP_ATOMIC))) { + printk(KERN_WARNING "HiSax: No skb for TEI manager\n"); + return; + } + bp = skb_put(skb, 3); + bp[0] = (TEI_SAPI << 2); + bp[1] = (GROUP_TEI << 1) | 0x1; + bp[2] = UI; + bp = skb_put(skb, 5); + bp[0] = TEI_ENTITY_ID; + bp[1] = ri >> 8; + bp[2] = ri & 0xff; + bp[3] = m_id; + bp[4] = (tei << 1) | 1; + st->l2.l2l1(st, PH_DATA | REQUEST, skb); +} + +static void +tei_id_request(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + if (st->l2.tei != -1) { + st->ma.tei_m.printdebug(&st->ma.tei_m, + "assign request for allready asigned tei %d", + st->l2.tei); + return; + } + st->ma.ri = random_ri(); + if (st->ma.debug) + st->ma.tei_m.printdebug(&st->ma.tei_m, + "assign request ri %d", st->ma.ri); + put_tei_msg(st, ID_REQUEST, st->ma.ri, 127); + FsmChangeState(&st->ma.tei_m, ST_TEI_IDREQ); + FsmAddTimer(&st->ma.t202, st->ma.T202, EV_T202, NULL, 1); + st->ma.N202 = 3; +} + +static void +tei_id_assign(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *ost, *st = fi->userdata; + struct sk_buff *skb = arg; + struct IsdnCardState *cs; + int ri, tei; + + ri = ((unsigned int) skb->data[1] << 8) + skb->data[2]; + tei = skb->data[4] >> 1; + if (st->ma.debug) + st->ma.tei_m.printdebug(&st->ma.tei_m, + "identity assign ri %d tei %d", ri, tei); + if ((ost = findtei(st, tei))) { /* same tei is in use */ + if (ri != ost->ma.ri) { + st->ma.tei_m.printdebug(&st->ma.tei_m, + "possible duplicate assignment tei %d", tei); + ost->l2.l2tei(ost, MDL_ERROR | RESPONSE, NULL); + } + } else if (ri == st->ma.ri) { + FsmDelTimer(&st->ma.t202, 1); + FsmChangeState(&st->ma.tei_m, ST_TEI_NOP); + st->l3.l3l2(st, MDL_ASSIGN | REQUEST, (void *) (long) tei); + cs = (struct IsdnCardState *) st->l1.hardware; + cs->cardmsg(cs, MDL_ASSIGN | REQUEST, NULL); + } +} + +static void +tei_id_test_dup(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *ost, *st = fi->userdata; + struct sk_buff *skb = arg; + int tei, ri; + + ri = ((unsigned int) skb->data[1] << 8) + skb->data[2]; + tei = skb->data[4] >> 1; + if (st->ma.debug) + st->ma.tei_m.printdebug(&st->ma.tei_m, + "foreign identity assign ri %d tei %d", ri, tei); + if ((ost = findtei(st, tei))) { /* same tei is in use */ + if (ri != ost->ma.ri) { /* and it wasn't our request */ + st->ma.tei_m.printdebug(&st->ma.tei_m, + "possible duplicate assignment tei %d", tei); + FsmEvent(&ost->ma.tei_m, EV_VERIFY, NULL); + } + } +} + +static void +tei_id_denied(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + int ri, tei; + + ri = ((unsigned int) skb->data[1] << 8) + skb->data[2]; + tei = skb->data[4] >> 1; + if (st->ma.debug) + st->ma.tei_m.printdebug(&st->ma.tei_m, + "identity denied ri %d tei %d", ri, tei); +} + +static void +tei_id_chk_req(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + int tei; + + tei = skb->data[4] >> 1; + if (st->ma.debug) + st->ma.tei_m.printdebug(&st->ma.tei_m, + "identity check req tei %d", tei); + if ((st->l2.tei != -1) && ((tei == GROUP_TEI) || (tei == st->l2.tei))) { + FsmDelTimer(&st->ma.t202, 4); + FsmChangeState(&st->ma.tei_m, ST_TEI_NOP); + put_tei_msg(st, ID_CHK_RES, random_ri(), st->l2.tei); + } +} + +static void +tei_id_remove(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct sk_buff *skb = arg; + struct IsdnCardState *cs; + int tei; + + tei = skb->data[4] >> 1; + if (st->ma.debug) + st->ma.tei_m.printdebug(&st->ma.tei_m, + "identity remove tei %d", tei); + if ((st->l2.tei != -1) && ((tei == GROUP_TEI) || (tei == st->l2.tei))) { + FsmDelTimer(&st->ma.t202, 5); + FsmChangeState(&st->ma.tei_m, ST_TEI_NOP); + st->l3.l3l2(st, MDL_REMOVE | REQUEST, NULL); + cs = (struct IsdnCardState *) st->l1.hardware; + cs->cardmsg(cs, MDL_REMOVE | REQUEST, NULL); + } +} + +static void +tei_id_verify(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + + if (st->ma.debug) + st->ma.tei_m.printdebug(&st->ma.tei_m, + "id verify request for tei %d", st->l2.tei); + put_tei_msg(st, ID_VERIFY, 0, st->l2.tei); + FsmChangeState(&st->ma.tei_m, ST_TEI_IDVERIFY); + FsmAddTimer(&st->ma.t202, st->ma.T202, EV_T202, NULL, 2); + st->ma.N202 = 2; +} + +static void +tei_id_req_tout(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct IsdnCardState *cs; + + if (--st->ma.N202) { + st->ma.ri = random_ri(); + if (st->ma.debug) + st->ma.tei_m.printdebug(&st->ma.tei_m, + "assign req(%d) ri %d", 4 - st->ma.N202, + st->ma.ri); + put_tei_msg(st, ID_REQUEST, st->ma.ri, 127); + FsmAddTimer(&st->ma.t202, st->ma.T202, EV_T202, NULL, 3); + } else { + st->ma.tei_m.printdebug(&st->ma.tei_m, "assign req failed"); + st->l3.l3l2(st, MDL_ERROR | RESPONSE, NULL); + cs = (struct IsdnCardState *) st->l1.hardware; + cs->cardmsg(cs, MDL_REMOVE | REQUEST, NULL); + FsmChangeState(fi, ST_TEI_NOP); + } +} + +static void +tei_id_ver_tout(struct FsmInst *fi, int event, void *arg) +{ + struct PStack *st = fi->userdata; + struct IsdnCardState *cs; + + if (--st->ma.N202) { + if (st->ma.debug) + st->ma.tei_m.printdebug(&st->ma.tei_m, + "id verify req(%d) for tei %d", + 3 - st->ma.N202, st->l2.tei); + put_tei_msg(st, ID_VERIFY, 0, st->l2.tei); + FsmAddTimer(&st->ma.t202, st->ma.T202, EV_T202, NULL, 4); + } else { + st->ma.tei_m.printdebug(&st->ma.tei_m, + "verify req for tei %d failed", st->l2.tei); + st->l3.l3l2(st, MDL_REMOVE | REQUEST, NULL); + cs = (struct IsdnCardState *) st->l1.hardware; + cs->cardmsg(cs, MDL_REMOVE | REQUEST, NULL); + FsmChangeState(fi, ST_TEI_NOP); + } +} + +static void +tei_l1l2(struct PStack *st, int pr, void *arg) +{ + struct sk_buff *skb = arg; + int mt; + + if (test_bit(FLG_FIXED_TEI, &st->l2.flag)) { + dev_kfree_skb(skb); + return; + } + + if (pr == (PH_DATA | INDICATION)) { + if (skb->len < 3) { + st->ma.tei_m.printdebug(&st->ma.tei_m, + "short mgr frame %ld/3", skb->len); + } else if ((skb->data[0] != ((TEI_SAPI << 2) | 2)) || + (skb->data[1] != ((GROUP_TEI << 1) | 1))) { + st->ma.tei_m.printdebug(&st->ma.tei_m, + "wrong mgr sapi/tei %x/%x", + skb->data[0], skb->data[1]); + } else if ((skb->data[2] & 0xef) != UI) { + st->ma.tei_m.printdebug(&st->ma.tei_m, + "mgr frame is not ui %x", skb->data[2]); + } else { + skb_pull(skb, 3); + if (skb->len < 5) { + st->ma.tei_m.printdebug(&st->ma.tei_m, + "short mgr frame %ld/5", skb->len); + } else if (skb->data[0] != TEI_ENTITY_ID) { + /* wrong management entity identifier, ignore */ + st->ma.tei_m.printdebug(&st->ma.tei_m, + "tei handler wrong entity id %x", + skb->data[0]); + } else { + mt = skb->data[3]; + if (mt == ID_ASSIGNED) + FsmEvent(&st->ma.tei_m, EV_ASSIGN, skb); + else if (mt == ID_DENIED) + FsmEvent(&st->ma.tei_m, EV_DENIED, skb); + else if (mt == ID_CHK_REQ) + FsmEvent(&st->ma.tei_m, EV_CHKREQ, skb); + else if (mt == ID_REMOVE) + FsmEvent(&st->ma.tei_m, EV_REMOVE, skb); + else { + st->ma.tei_m.printdebug(&st->ma.tei_m, + "tei handler wrong mt %x\n", mt); + } + } + } + } else { + st->ma.tei_m.printdebug(&st->ma.tei_m, + "tei handler wrong pr %x\n", pr); + } + dev_kfree_skb(skb); +} + +static void +tei_l2tei(struct PStack *st, int pr, void *arg) +{ + struct IsdnCardState *cs; + + if (test_bit(FLG_FIXED_TEI, &st->l2.flag)) { + if (pr == (MDL_ASSIGN | INDICATION)) { + if (st->ma.debug) + st->ma.tei_m.printdebug(&st->ma.tei_m, + "fixed assign tei %d", st->l2.tei); + st->l3.l3l2(st, MDL_ASSIGN | REQUEST, (void *) (long) st->l2.tei); + cs = (struct IsdnCardState *) st->l1.hardware; + cs->cardmsg(cs, MDL_ASSIGN | REQUEST, NULL); + } + return; + } + switch (pr) { + case (MDL_ASSIGN | INDICATION): + FsmEvent(&st->ma.tei_m, EV_IDREQ, arg); + break; + case (MDL_ERROR | REQUEST): + FsmEvent(&st->ma.tei_m, EV_VERIFY, arg); + break; + default: + break; + } +} + +static void +tei_debug(struct FsmInst *fi, char *fmt, ...) +{ + va_list args; + struct PStack *st = fi->userdata; + + va_start(args, fmt); + VHiSax_putstatus(st->l1.hardware, "tei ", fmt, args); + va_end(args); +} + +void +setstack_tei(struct PStack *st) +{ + st->l2.l2tei = tei_l2tei; + st->ma.T202 = 2000; /* T202 2000 milliseconds */ + st->l1.l1tei = tei_l1l2; + st->ma.debug = 1; + st->ma.tei_m.fsm = &teifsm; + st->ma.tei_m.state = ST_TEI_NOP; + st->ma.tei_m.debug = 1; + st->ma.tei_m.userdata = st; + st->ma.tei_m.userint = 0; + st->ma.tei_m.printdebug = tei_debug; + FsmInitTimer(&st->ma.tei_m, &st->ma.t202); +} + +void +init_tei(struct IsdnCardState *cs, int protocol) +{ +} + +void +release_tei(struct IsdnCardState *cs) +{ + struct PStack *st = cs->stlist; + + while (st) { + FsmDelTimer(&st->ma.t202, 1); + st = st->next; + } +} + +static struct FsmNode TeiFnList[] __initdata = +{ + {ST_TEI_NOP, EV_IDREQ, tei_id_request}, + {ST_TEI_NOP, EV_ASSIGN, tei_id_test_dup}, + {ST_TEI_NOP, EV_VERIFY, tei_id_verify}, + {ST_TEI_NOP, EV_REMOVE, tei_id_remove}, + {ST_TEI_NOP, EV_CHKREQ, tei_id_chk_req}, + {ST_TEI_IDREQ, EV_T202, tei_id_req_tout}, + {ST_TEI_IDREQ, EV_ASSIGN, tei_id_assign}, + {ST_TEI_IDREQ, EV_DENIED, tei_id_denied}, + {ST_TEI_IDVERIFY, EV_T202, tei_id_ver_tout}, + {ST_TEI_IDVERIFY, EV_REMOVE, tei_id_remove}, + {ST_TEI_IDVERIFY, EV_CHKREQ, tei_id_chk_req}, +}; + +#define TEI_FN_COUNT (sizeof(TeiFnList)/sizeof(struct FsmNode)) + +int __init +TeiNew(void) +{ + teifsm.state_count = TEI_STATE_COUNT; + teifsm.event_count = TEI_EVENT_COUNT; + teifsm.strEvent = strTeiEvent; + teifsm.strState = strTeiState; + return FsmNew(&teifsm, TeiFnList, TEI_FN_COUNT); +} + +void +TeiFree(void) +{ + FsmFree(&teifsm); +} diff --git a/drivers/isdn/hisax/teleint.c b/drivers/isdn/hisax/teleint.c new file mode 100644 index 000000000000..ef8984c5f1f7 --- /dev/null +++ b/drivers/isdn/hisax/teleint.c @@ -0,0 +1,339 @@ +/* $Id: teleint.c,v 1.16.2.5 2004/01/19 15:31:50 keil Exp $ + * + * low level stuff for TeleInt isdn cards + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/init.h> +#include "hisax.h" +#include "isac.h" +#include "hfc_2bs0.h" +#include "isdnl1.h" + +extern const char *CardType[]; + +const char *TeleInt_revision = "$Revision: 1.16.2.5 $"; + +#define byteout(addr,val) outb(val,addr) +#define bytein(addr) inb(addr) + +static inline u_char +readreg(unsigned int ale, unsigned int adr, u_char off) +{ + register u_char ret; + int max_delay = 2000; + + byteout(ale, off); + ret = HFC_BUSY & bytein(ale); + while (ret && --max_delay) + ret = HFC_BUSY & bytein(ale); + if (!max_delay) { + printk(KERN_WARNING "TeleInt Busy not inactive\n"); + return (0); + } + ret = bytein(adr); + return (ret); +} + +static inline void +readfifo(unsigned int ale, unsigned int adr, u_char off, u_char * data, int size) +{ + register u_char ret; + register int max_delay = 20000; + register int i; + + byteout(ale, off); + for (i = 0; i<size; i++) { + ret = HFC_BUSY & bytein(ale); + while (ret && --max_delay) + ret = HFC_BUSY & bytein(ale); + if (!max_delay) { + printk(KERN_WARNING "TeleInt Busy not inactive\n"); + return; + } + data[i] = bytein(adr); + } +} + + +static inline void +writereg(unsigned int ale, unsigned int adr, u_char off, u_char data) +{ + register u_char ret; + int max_delay = 2000; + + byteout(ale, off); + ret = HFC_BUSY & bytein(ale); + while (ret && --max_delay) + ret = HFC_BUSY & bytein(ale); + if (!max_delay) { + printk(KERN_WARNING "TeleInt Busy not inactive\n"); + return; + } + byteout(adr, data); +} + +static inline void +writefifo(unsigned int ale, unsigned int adr, u_char off, u_char * data, int size) +{ + register u_char ret; + register int max_delay = 20000; + register int i; + + byteout(ale, off); + for (i = 0; i<size; i++) { + ret = HFC_BUSY & bytein(ale); + while (ret && --max_delay) + ret = HFC_BUSY & bytein(ale); + if (!max_delay) { + printk(KERN_WARNING "TeleInt Busy not inactive\n"); + return; + } + byteout(adr, data[i]); + } +} + +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + cs->hw.hfc.cip = offset; + return (readreg(cs->hw.hfc.addr | 1, cs->hw.hfc.addr, offset)); +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + cs->hw.hfc.cip = offset; + writereg(cs->hw.hfc.addr | 1, cs->hw.hfc.addr, offset, value); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + cs->hw.hfc.cip = 0; + readfifo(cs->hw.hfc.addr | 1, cs->hw.hfc.addr, 0, data, size); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + cs->hw.hfc.cip = 0; + writefifo(cs->hw.hfc.addr | 1, cs->hw.hfc.addr, 0, data, size); +} + +static u_char +ReadHFC(struct IsdnCardState *cs, int data, u_char reg) +{ + register u_char ret; + + if (data) { + cs->hw.hfc.cip = reg; + byteout(cs->hw.hfc.addr | 1, reg); + ret = bytein(cs->hw.hfc.addr); + if (cs->debug & L1_DEB_HSCX_FIFO && (data != 2)) + debugl1(cs, "hfc RD %02x %02x", reg, ret); + } else + ret = bytein(cs->hw.hfc.addr | 1); + return (ret); +} + +static void +WriteHFC(struct IsdnCardState *cs, int data, u_char reg, u_char value) +{ + byteout(cs->hw.hfc.addr | 1, reg); + cs->hw.hfc.cip = reg; + if (data) + byteout(cs->hw.hfc.addr, value); + if (cs->debug & L1_DEB_HSCX_FIFO && (data != 2)) + debugl1(cs, "hfc W%c %02x %02x", data ? 'D' : 'C', reg, value); +} + +static irqreturn_t +TeleInt_interrupt(int intno, void *dev_id, struct pt_regs *regs) +{ + struct IsdnCardState *cs = dev_id; + u_char val; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + val = readreg(cs->hw.hfc.addr | 1, cs->hw.hfc.addr, ISAC_ISTA); + Start_ISAC: + if (val) + isac_interrupt(cs, val); + val = readreg(cs->hw.hfc.addr | 1, cs->hw.hfc.addr, ISAC_ISTA); + if (val) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ISAC IntStat after IntRoutine"); + goto Start_ISAC; + } + writereg(cs->hw.hfc.addr | 1, cs->hw.hfc.addr, ISAC_MASK, 0xFF); + writereg(cs->hw.hfc.addr | 1, cs->hw.hfc.addr, ISAC_MASK, 0x0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static void +TeleInt_Timer(struct IsdnCardState *cs) +{ + int stat = 0; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + if (cs->bcs[0].mode) { + stat |= 1; + main_irq_hfc(&cs->bcs[0]); + } + if (cs->bcs[1].mode) { + stat |= 2; + main_irq_hfc(&cs->bcs[1]); + } + spin_unlock_irqrestore(&cs->lock, flags); + stat = HZ/100; + if (!stat) + stat = 1; + cs->hw.hfc.timer.expires = jiffies + stat; + add_timer(&cs->hw.hfc.timer); +} + +void +release_io_TeleInt(struct IsdnCardState *cs) +{ + del_timer(&cs->hw.hfc.timer); + releasehfc(cs); + if (cs->hw.hfc.addr) + release_region(cs->hw.hfc.addr, 2); +} + +static void +reset_TeleInt(struct IsdnCardState *cs) +{ + printk(KERN_INFO "TeleInt: resetting card\n"); + cs->hw.hfc.cirm |= HFC_RESET; + byteout(cs->hw.hfc.addr | 1, cs->hw.hfc.cirm); /* Reset On */ + mdelay(10); + cs->hw.hfc.cirm &= ~HFC_RESET; + byteout(cs->hw.hfc.addr | 1, cs->hw.hfc.cirm); /* Reset Off */ + mdelay(10); +} + +static int +TeleInt_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + int delay; + + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + reset_TeleInt(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return(0); + case CARD_RELEASE: + release_io_TeleInt(cs); + return(0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + reset_TeleInt(cs); + inithfc(cs); + clear_pending_isac_ints(cs); + initisac(cs); + /* Reenable all IRQ */ + cs->writeisac(cs, ISAC_MASK, 0); + cs->writeisac(cs, ISAC_CMDR, 0x41); + spin_unlock_irqrestore(&cs->lock, flags); + delay = HZ/100; + if (!delay) + delay = 1; + cs->hw.hfc.timer.expires = jiffies + delay; + add_timer(&cs->hw.hfc.timer); + return(0); + case CARD_TEST: + return(0); + } + return(0); +} + +int __init +setup_TeleInt(struct IsdnCard *card) +{ + struct IsdnCardState *cs = card->cs; + char tmp[64]; + + strcpy(tmp, TeleInt_revision); + printk(KERN_INFO "HiSax: TeleInt driver Rev. %s\n", HiSax_getrev(tmp)); + if (cs->typ != ISDN_CTYPE_TELEINT) + return (0); + + cs->hw.hfc.addr = card->para[1] & 0x3fe; + cs->irq = card->para[0]; + cs->hw.hfc.cirm = HFC_CIRM; + cs->hw.hfc.isac_spcr = 0x00; + cs->hw.hfc.cip = 0; + cs->hw.hfc.ctmt = HFC_CTMT | HFC_CLTIMER; + cs->bcs[0].hw.hfc.send = NULL; + cs->bcs[1].hw.hfc.send = NULL; + cs->hw.hfc.fifosize = 7 * 1024 + 512; + cs->hw.hfc.timer.function = (void *) TeleInt_Timer; + cs->hw.hfc.timer.data = (long) cs; + init_timer(&cs->hw.hfc.timer); + if (!request_region(cs->hw.hfc.addr, 2, "TeleInt isdn")) { + printk(KERN_WARNING + "HiSax: %s config port %x-%x already in use\n", + CardType[card->typ], + cs->hw.hfc.addr, + cs->hw.hfc.addr + 2); + return (0); + } + /* HW IO = IO */ + byteout(cs->hw.hfc.addr, cs->hw.hfc.addr & 0xff); + byteout(cs->hw.hfc.addr | 1, ((cs->hw.hfc.addr & 0x300) >> 8) | 0x54); + switch (cs->irq) { + case 3: + cs->hw.hfc.cirm |= HFC_INTA; + break; + case 4: + cs->hw.hfc.cirm |= HFC_INTB; + break; + case 5: + cs->hw.hfc.cirm |= HFC_INTC; + break; + case 7: + cs->hw.hfc.cirm |= HFC_INTD; + break; + case 10: + cs->hw.hfc.cirm |= HFC_INTE; + break; + case 11: + cs->hw.hfc.cirm |= HFC_INTF; + break; + default: + printk(KERN_WARNING "TeleInt: wrong IRQ\n"); + release_io_TeleInt(cs); + return (0); + } + byteout(cs->hw.hfc.addr | 1, cs->hw.hfc.cirm); + byteout(cs->hw.hfc.addr | 1, cs->hw.hfc.ctmt); + + printk(KERN_INFO "TeleInt: defined at 0x%x IRQ %d\n", + cs->hw.hfc.addr, cs->irq); + + setup_isac(cs); + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->BC_Read_Reg = &ReadHFC; + cs->BC_Write_Reg = &WriteHFC; + cs->cardmsg = &TeleInt_card_msg; + cs->irq_func = &TeleInt_interrupt; + ISACVersion(cs, "TeleInt:"); + return (1); +} diff --git a/drivers/isdn/hisax/teles0.c b/drivers/isdn/hisax/teles0.c new file mode 100644 index 000000000000..5ec5ec3e1eab --- /dev/null +++ b/drivers/isdn/hisax/teles0.c @@ -0,0 +1,367 @@ +/* $Id: teles0.c,v 2.15.2.4 2004/01/13 23:48:39 keil Exp $ + * + * low level stuff for Teles Memory IO isdn cards + * + * Author Karsten Keil + * based on the teles driver from Jan den Ouden + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * Thanks to Jan den Ouden + * Fritz Elfert + * Beat Doebeli + * + */ + +#include <linux/init.h> +#include "hisax.h" +#include "isdnl1.h" +#include "isac.h" +#include "hscx.h" + +extern const char *CardType[]; + +const char *teles0_revision = "$Revision: 2.15.2.4 $"; + +#define TELES_IOMEM_SIZE 0x400 +#define byteout(addr,val) outb(val,addr) +#define bytein(addr) inb(addr) + +static inline u_char +readisac(void __iomem *adr, u_char off) +{ + return readb(adr + ((off & 1) ? 0x2ff : 0x100) + off); +} + +static inline void +writeisac(void __iomem *adr, u_char off, u_char data) +{ + writeb(data, adr + ((off & 1) ? 0x2ff : 0x100) + off); mb(); +} + + +static inline u_char +readhscx(void __iomem *adr, int hscx, u_char off) +{ + return readb(adr + (hscx ? 0x1c0 : 0x180) + + ((off & 1) ? 0x1ff : 0) + off); +} + +static inline void +writehscx(void __iomem *adr, int hscx, u_char off, u_char data) +{ + writeb(data, adr + (hscx ? 0x1c0 : 0x180) + + ((off & 1) ? 0x1ff : 0) + off); mb(); +} + +static inline void +read_fifo_isac(void __iomem *adr, u_char * data, int size) +{ + register int i; + register u_char __iomem *ad = adr + 0x100; + for (i = 0; i < size; i++) + data[i] = readb(ad); +} + +static inline void +write_fifo_isac(void __iomem *adr, u_char * data, int size) +{ + register int i; + register u_char __iomem *ad = adr + 0x100; + for (i = 0; i < size; i++) { + writeb(data[i], ad); mb(); + } +} + +static inline void +read_fifo_hscx(void __iomem *adr, int hscx, u_char * data, int size) +{ + register int i; + register u_char __iomem *ad = adr + (hscx ? 0x1c0 : 0x180); + for (i = 0; i < size; i++) + data[i] = readb(ad); +} + +static inline void +write_fifo_hscx(void __iomem *adr, int hscx, u_char * data, int size) +{ + int i; + register u_char __iomem *ad = adr + (hscx ? 0x1c0 : 0x180); + for (i = 0; i < size; i++) { + writeb(data[i], ad); mb(); + } +} + +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + return (readisac(cs->hw.teles0.membase, offset)); +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writeisac(cs->hw.teles0.membase, offset, value); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + read_fifo_isac(cs->hw.teles0.membase, data, size); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + write_fifo_isac(cs->hw.teles0.membase, data, size); +} + +static u_char +ReadHSCX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + return (readhscx(cs->hw.teles0.membase, hscx, offset)); +} + +static void +WriteHSCX(struct IsdnCardState *cs, int hscx, u_char offset, u_char value) +{ + writehscx(cs->hw.teles0.membase, hscx, offset, value); +} + +/* + * fast interrupt HSCX stuff goes here + */ + +#define READHSCX(cs, nr, reg) readhscx(cs->hw.teles0.membase, nr, reg) +#define WRITEHSCX(cs, nr, reg, data) writehscx(cs->hw.teles0.membase, nr, reg, data) +#define READHSCXFIFO(cs, nr, ptr, cnt) read_fifo_hscx(cs->hw.teles0.membase, nr, ptr, cnt) +#define WRITEHSCXFIFO(cs, nr, ptr, cnt) write_fifo_hscx(cs->hw.teles0.membase, nr, ptr, cnt) + +#include "hscx_irq.c" + +static irqreturn_t +teles0_interrupt(int intno, void *dev_id, struct pt_regs *regs) +{ + struct IsdnCardState *cs = dev_id; + u_char val; + u_long flags; + int count = 0; + + spin_lock_irqsave(&cs->lock, flags); + val = readhscx(cs->hw.teles0.membase, 1, HSCX_ISTA); + Start_HSCX: + if (val) + hscx_int_main(cs, val); + val = readisac(cs->hw.teles0.membase, ISAC_ISTA); + Start_ISAC: + if (val) + isac_interrupt(cs, val); + count++; + val = readhscx(cs->hw.teles0.membase, 1, HSCX_ISTA); + if (val && count < 5) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HSCX IntStat after IntRoutine"); + goto Start_HSCX; + } + val = readisac(cs->hw.teles0.membase, ISAC_ISTA); + if (val && count < 5) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ISAC IntStat after IntRoutine"); + goto Start_ISAC; + } + writehscx(cs->hw.teles0.membase, 0, HSCX_MASK, 0xFF); + writehscx(cs->hw.teles0.membase, 1, HSCX_MASK, 0xFF); + writeisac(cs->hw.teles0.membase, ISAC_MASK, 0xFF); + writeisac(cs->hw.teles0.membase, ISAC_MASK, 0x0); + writehscx(cs->hw.teles0.membase, 0, HSCX_MASK, 0x0); + writehscx(cs->hw.teles0.membase, 1, HSCX_MASK, 0x0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +void +release_io_teles0(struct IsdnCardState *cs) +{ + if (cs->hw.teles0.cfg_reg) + release_region(cs->hw.teles0.cfg_reg, 8); + iounmap(cs->hw.teles0.membase); + release_mem_region(cs->hw.teles0.phymem, TELES_IOMEM_SIZE); +} + +static int +reset_teles0(struct IsdnCardState *cs) +{ + u_char cfval; + + if (cs->hw.teles0.cfg_reg) { + switch (cs->irq) { + case 2: + case 9: + 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: + return(1); + } + cfval |= ((cs->hw.teles0.phymem >> 9) & 0xF0); + byteout(cs->hw.teles0.cfg_reg + 4, cfval); + HZDELAY(HZ / 10 + 1); + byteout(cs->hw.teles0.cfg_reg + 4, cfval | 1); + HZDELAY(HZ / 10 + 1); + } + writeb(0, cs->hw.teles0.membase + 0x80); mb(); + HZDELAY(HZ / 5 + 1); + writeb(1, cs->hw.teles0.membase + 0x80); mb(); + HZDELAY(HZ / 5 + 1); + return(0); +} + +static int +Teles_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + reset_teles0(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return(0); + case CARD_RELEASE: + release_io_teles0(cs); + return(0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + inithscxisac(cs, 3); + spin_unlock_irqrestore(&cs->lock, flags); + return(0); + case CARD_TEST: + return(0); + } + return(0); +} + +int __init +setup_teles0(struct IsdnCard *card) +{ + u_char val; + struct IsdnCardState *cs = card->cs; + char tmp[64]; + + strcpy(tmp, teles0_revision); + printk(KERN_INFO "HiSax: Teles 8.0/16.0 driver Rev. %s\n", HiSax_getrev(tmp)); + if ((cs->typ != ISDN_CTYPE_16_0) && (cs->typ != ISDN_CTYPE_8_0)) + return (0); + + if (cs->typ == ISDN_CTYPE_16_0) + cs->hw.teles0.cfg_reg = card->para[2]; + else /* 8.0 */ + cs->hw.teles0.cfg_reg = 0; + + if (card->para[1] < 0x10000) { + card->para[1] <<= 4; + printk(KERN_INFO + "Teles0: membase configured DOSish, assuming 0x%lx\n", + (unsigned long) card->para[1]); + } + cs->irq = card->para[0]; + if (cs->hw.teles0.cfg_reg) { + if (!request_region(cs->hw.teles0.cfg_reg, 8, "teles cfg")) { + printk(KERN_WARNING + "HiSax: %s config port %x-%x already in use\n", + CardType[card->typ], + cs->hw.teles0.cfg_reg, + cs->hw.teles0.cfg_reg + 8); + return (0); + } + } + if (cs->hw.teles0.cfg_reg) { + if ((val = bytein(cs->hw.teles0.cfg_reg + 0)) != 0x51) { + printk(KERN_WARNING "Teles0: 16.0 Byte at %x is %x\n", + cs->hw.teles0.cfg_reg + 0, val); + release_region(cs->hw.teles0.cfg_reg, 8); + return (0); + } + if ((val = bytein(cs->hw.teles0.cfg_reg + 1)) != 0x93) { + printk(KERN_WARNING "Teles0: 16.0 Byte at %x is %x\n", + cs->hw.teles0.cfg_reg + 1, val); + release_region(cs->hw.teles0.cfg_reg, 8); + return (0); + } + val = bytein(cs->hw.teles0.cfg_reg + 2); /* 0x1e=without AB + * 0x1f=with AB + * 0x1c 16.3 ??? + */ + if (val != 0x1e && val != 0x1f) { + printk(KERN_WARNING "Teles0: 16.0 Byte at %x is %x\n", + cs->hw.teles0.cfg_reg + 2, val); + release_region(cs->hw.teles0.cfg_reg, 8); + return (0); + } + } + /* 16.0 and 8.0 designed for IOM1 */ + test_and_set_bit(HW_IOM1, &cs->HW_Flags); + cs->hw.teles0.phymem = card->para[1]; + if (!request_mem_region(cs->hw.teles0.phymem, TELES_IOMEM_SIZE, "teles iomem")) { + printk(KERN_WARNING + "HiSax: %s memory region %lx-%lx already in use\n", + CardType[card->typ], + cs->hw.teles0.phymem, + cs->hw.teles0.phymem + TELES_IOMEM_SIZE); + if (cs->hw.teles0.cfg_reg) + release_region(cs->hw.teles0.cfg_reg, 8); + return (0); + } + cs->hw.teles0.membase = ioremap(cs->hw.teles0.phymem, TELES_IOMEM_SIZE); + printk(KERN_INFO + "HiSax: %s config irq:%d mem:%p cfg:0x%X\n", + CardType[cs->typ], cs->irq, + cs->hw.teles0.membase, cs->hw.teles0.cfg_reg); + if (reset_teles0(cs)) { + printk(KERN_WARNING "Teles0: wrong IRQ\n"); + release_io_teles0(cs); + return (0); + } + setup_isac(cs); + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->BC_Read_Reg = &ReadHSCX; + cs->BC_Write_Reg = &WriteHSCX; + cs->BC_Send_Data = &hscx_fill_fifo; + cs->cardmsg = &Teles_card_msg; + cs->irq_func = &teles0_interrupt; + ISACVersion(cs, "Teles0:"); + if (HscxVersion(cs, "Teles0:")) { + printk(KERN_WARNING + "Teles0: wrong HSCX versions check IO/MEM addresses\n"); + release_io_teles0(cs); + return (0); + } + return (1); +} diff --git a/drivers/isdn/hisax/teles3.c b/drivers/isdn/hisax/teles3.c new file mode 100644 index 000000000000..c5b1f65f7275 --- /dev/null +++ b/drivers/isdn/hisax/teles3.c @@ -0,0 +1,499 @@ +/* $Id: teles3.c,v 2.19.2.4 2004/01/13 23:48:39 keil Exp $ + * + * low level stuff for Teles 16.3 & PNP isdn cards + * + * Author Karsten Keil + * Copyright by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * Thanks to Jan den Ouden + * Fritz Elfert + * Beat Doebeli + * + */ +#include <linux/init.h> +#include <linux/isapnp.h> +#include "hisax.h" +#include "isac.h" +#include "hscx.h" +#include "isdnl1.h" + +extern const char *CardType[]; +const char *teles3_revision = "$Revision: 2.19.2.4 $"; + +#define byteout(addr,val) outb(val,addr) +#define bytein(addr) inb(addr) + +static inline u_char +readreg(unsigned int adr, u_char off) +{ + return (bytein(adr + off)); +} + +static inline void +writereg(unsigned int adr, u_char off, u_char data) +{ + byteout(adr + off, data); +} + + +static inline void +read_fifo(unsigned int adr, u_char * data, int size) +{ + insb(adr, data, size); +} + +static void +write_fifo(unsigned int adr, u_char * data, int size) +{ + outsb(adr, data, size); +} + +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + return (readreg(cs->hw.teles3.isac, offset)); +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writereg(cs->hw.teles3.isac, offset, value); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + read_fifo(cs->hw.teles3.isacfifo, data, size); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + write_fifo(cs->hw.teles3.isacfifo, data, size); +} + +static u_char +ReadHSCX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + return (readreg(cs->hw.teles3.hscx[hscx], offset)); +} + +static void +WriteHSCX(struct IsdnCardState *cs, int hscx, u_char offset, u_char value) +{ + writereg(cs->hw.teles3.hscx[hscx], offset, value); +} + +/* + * fast interrupt HSCX stuff goes here + */ + +#define READHSCX(cs, nr, reg) readreg(cs->hw.teles3.hscx[nr], reg) +#define WRITEHSCX(cs, nr, reg, data) writereg(cs->hw.teles3.hscx[nr], reg, data) +#define READHSCXFIFO(cs, nr, ptr, cnt) read_fifo(cs->hw.teles3.hscxfifo[nr], ptr, cnt) +#define WRITEHSCXFIFO(cs, nr, ptr, cnt) write_fifo(cs->hw.teles3.hscxfifo[nr], ptr, cnt) + +#include "hscx_irq.c" + +static irqreturn_t +teles3_interrupt(int intno, void *dev_id, struct pt_regs *regs) +{ +#define MAXCOUNT 5 + struct IsdnCardState *cs = dev_id; + u_char val; + u_long flags; + int count = 0; + + spin_lock_irqsave(&cs->lock, flags); + val = readreg(cs->hw.teles3.hscx[1], HSCX_ISTA); + Start_HSCX: + if (val) + hscx_int_main(cs, val); + val = readreg(cs->hw.teles3.isac, ISAC_ISTA); + Start_ISAC: + if (val) + isac_interrupt(cs, val); + count++; + val = readreg(cs->hw.teles3.hscx[1], HSCX_ISTA); + if (val && count < MAXCOUNT) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HSCX IntStat after IntRoutine"); + goto Start_HSCX; + } + val = readreg(cs->hw.teles3.isac, ISAC_ISTA); + if (val && count < MAXCOUNT) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ISAC IntStat after IntRoutine"); + goto Start_ISAC; + } + if (count >= MAXCOUNT) + printk(KERN_WARNING "Teles3: more than %d loops in teles3_interrupt\n", count); + writereg(cs->hw.teles3.hscx[0], HSCX_MASK, 0xFF); + writereg(cs->hw.teles3.hscx[1], HSCX_MASK, 0xFF); + writereg(cs->hw.teles3.isac, ISAC_MASK, 0xFF); + writereg(cs->hw.teles3.isac, ISAC_MASK, 0x0); + writereg(cs->hw.teles3.hscx[0], HSCX_MASK, 0x0); + writereg(cs->hw.teles3.hscx[1], HSCX_MASK, 0x0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +inline static void +release_ioregs(struct IsdnCardState *cs, int mask) +{ + if (mask & 1) + release_region(cs->hw.teles3.isac + 32, 32); + if (mask & 2) + release_region(cs->hw.teles3.hscx[0] + 32, 32); + if (mask & 4) + release_region(cs->hw.teles3.hscx[1] + 32, 32); +} + +void +release_io_teles3(struct IsdnCardState *cs) +{ + if (cs->typ == ISDN_CTYPE_TELESPCMCIA) { + release_region(cs->hw.teles3.hscx[1], 96); + } else { + if (cs->hw.teles3.cfg_reg) { + if (cs->typ == ISDN_CTYPE_COMPAQ_ISA) { + release_region(cs->hw.teles3.cfg_reg, 1); + } else { + release_region(cs->hw.teles3.cfg_reg, 8); + } + } + release_ioregs(cs, 0x7); + } +} + +static int +reset_teles3(struct IsdnCardState *cs) +{ + u_char irqcfg; + + if (cs->typ != ISDN_CTYPE_TELESPCMCIA) { + if ((cs->hw.teles3.cfg_reg) && (cs->typ != ISDN_CTYPE_COMPAQ_ISA)) { + switch (cs->irq) { + case 2: + case 9: + irqcfg = 0x00; + break; + case 3: + irqcfg = 0x02; + break; + case 4: + irqcfg = 0x04; + break; + case 5: + irqcfg = 0x06; + break; + case 10: + irqcfg = 0x08; + break; + case 11: + irqcfg = 0x0A; + break; + case 12: + irqcfg = 0x0C; + break; + case 15: + irqcfg = 0x0E; + break; + default: + return(1); + } + byteout(cs->hw.teles3.cfg_reg + 4, irqcfg); + HZDELAY(HZ / 10 + 1); + byteout(cs->hw.teles3.cfg_reg + 4, irqcfg | 1); + HZDELAY(HZ / 10 + 1); + } else if (cs->typ == ISDN_CTYPE_COMPAQ_ISA) { + byteout(cs->hw.teles3.cfg_reg, 0xff); + HZDELAY(2); + byteout(cs->hw.teles3.cfg_reg, 0x00); + HZDELAY(2); + } else { + /* Reset off for 16.3 PnP , thanks to Georg Acher */ + byteout(cs->hw.teles3.isac + 0x3c, 0); + HZDELAY(2); + byteout(cs->hw.teles3.isac + 0x3c, 1); + HZDELAY(2); + } + } + return(0); +} + +static int +Teles_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + switch (mt) { + case CARD_RESET: + spin_lock_irqsave(&cs->lock, flags); + reset_teles3(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return(0); + case CARD_RELEASE: + release_io_teles3(cs); + return(0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + inithscxisac(cs, 3); + spin_unlock_irqrestore(&cs->lock, flags); + return(0); + case CARD_TEST: + return(0); + } + return(0); +} + +#ifdef __ISAPNP__ + +static struct isapnp_device_id teles_ids[] __devinitdata = { + { ISAPNP_VENDOR('T', 'A', 'G'), ISAPNP_FUNCTION(0x2110), + ISAPNP_VENDOR('T', 'A', 'G'), ISAPNP_FUNCTION(0x2110), + (unsigned long) "Teles 16.3 PnP" }, + { ISAPNP_VENDOR('C', 'T', 'X'), ISAPNP_FUNCTION(0x0), + ISAPNP_VENDOR('C', 'T', 'X'), ISAPNP_FUNCTION(0x0), + (unsigned long) "Creatix 16.3 PnP" }, + { ISAPNP_VENDOR('C', 'P', 'Q'), ISAPNP_FUNCTION(0x1002), + ISAPNP_VENDOR('C', 'P', 'Q'), ISAPNP_FUNCTION(0x1002), + (unsigned long) "Compaq ISDN S0" }, + { 0, } +}; + +static struct isapnp_device_id *ipid __devinitdata = &teles_ids[0]; +static struct pnp_card *pnp_c __devinitdata = NULL; +#endif + +int __devinit +setup_teles3(struct IsdnCard *card) +{ + u_char val; + struct IsdnCardState *cs = card->cs; + char tmp[64]; + + strcpy(tmp, teles3_revision); + printk(KERN_INFO "HiSax: Teles IO driver Rev. %s\n", HiSax_getrev(tmp)); + if ((cs->typ != ISDN_CTYPE_16_3) && (cs->typ != ISDN_CTYPE_PNP) + && (cs->typ != ISDN_CTYPE_TELESPCMCIA) && (cs->typ != ISDN_CTYPE_COMPAQ_ISA)) + return (0); + +#ifdef __ISAPNP__ + if (!card->para[1] && isapnp_present()) { + struct pnp_dev *pnp_d; + while(ipid->card_vendor) { + if ((pnp_c = pnp_find_card(ipid->card_vendor, + ipid->card_device, pnp_c))) { + pnp_d = NULL; + if ((pnp_d = pnp_find_dev(pnp_c, + ipid->vendor, ipid->function, pnp_d))) { + int err; + + printk(KERN_INFO "HiSax: %s detected\n", + (char *)ipid->driver_data); + pnp_disable_dev(pnp_d); + err = pnp_activate_dev(pnp_d); + if (err<0) { + printk(KERN_WARNING "%s: pnp_activate_dev ret(%d)\n", + __FUNCTION__, err); + return(0); + } + card->para[3] = pnp_port_start(pnp_d, 2); + card->para[2] = pnp_port_start(pnp_d, 1); + card->para[1] = pnp_port_start(pnp_d, 0); + card->para[0] = pnp_irq(pnp_d, 0); + if (!card->para[0] || !card->para[1] || !card->para[2]) { + printk(KERN_ERR "Teles PnP:some resources are missing %ld/%lx/%lx\n", + card->para[0], card->para[1], card->para[2]); + pnp_disable_dev(pnp_d); + return(0); + } + break; + } else { + printk(KERN_ERR "Teles PnP: PnP error card found, no device\n"); + } + } + ipid++; + pnp_c = NULL; + } + if (!ipid->card_vendor) { + printk(KERN_INFO "Teles PnP: no ISAPnP card found\n"); + return(0); + } + } +#endif + if (cs->typ == ISDN_CTYPE_16_3) { + cs->hw.teles3.cfg_reg = card->para[1]; + switch (cs->hw.teles3.cfg_reg) { + case 0x180: + case 0x280: + case 0x380: + cs->hw.teles3.cfg_reg |= 0xc00; + break; + } + cs->hw.teles3.isac = cs->hw.teles3.cfg_reg - 0x420; + cs->hw.teles3.hscx[0] = cs->hw.teles3.cfg_reg - 0xc20; + cs->hw.teles3.hscx[1] = cs->hw.teles3.cfg_reg - 0x820; + } else if (cs->typ == ISDN_CTYPE_TELESPCMCIA) { + cs->hw.teles3.cfg_reg = 0; + cs->hw.teles3.hscx[0] = card->para[1] - 0x20; + cs->hw.teles3.hscx[1] = card->para[1]; + cs->hw.teles3.isac = card->para[1] + 0x20; + } else if (cs->typ == ISDN_CTYPE_COMPAQ_ISA) { + cs->hw.teles3.cfg_reg = card->para[3]; + cs->hw.teles3.isac = card->para[2] - 32; + cs->hw.teles3.hscx[0] = card->para[1] - 32; + cs->hw.teles3.hscx[1] = card->para[1]; + } else { /* PNP */ + cs->hw.teles3.cfg_reg = 0; + cs->hw.teles3.isac = card->para[1] - 32; + cs->hw.teles3.hscx[0] = card->para[2] - 32; + cs->hw.teles3.hscx[1] = card->para[2]; + } + cs->irq = card->para[0]; + cs->hw.teles3.isacfifo = cs->hw.teles3.isac + 0x3e; + cs->hw.teles3.hscxfifo[0] = cs->hw.teles3.hscx[0] + 0x3e; + cs->hw.teles3.hscxfifo[1] = cs->hw.teles3.hscx[1] + 0x3e; + if (cs->typ == ISDN_CTYPE_TELESPCMCIA) { + if (!request_region(cs->hw.teles3.hscx[1], 96, "HiSax Teles PCMCIA")) { + printk(KERN_WARNING + "HiSax: %s ports %x-%x already in use\n", + CardType[cs->typ], + cs->hw.teles3.hscx[1], + cs->hw.teles3.hscx[1] + 96); + return (0); + } + } else { + if (cs->hw.teles3.cfg_reg) { + if (cs->typ == ISDN_CTYPE_COMPAQ_ISA) { + if (!request_region(cs->hw.teles3.cfg_reg, 1, "teles3 cfg")) { + printk(KERN_WARNING + "HiSax: %s config port %x already in use\n", + CardType[card->typ], + cs->hw.teles3.cfg_reg); + return (0); + } + } else { + if (!request_region(cs->hw.teles3.cfg_reg, 8, "teles3 cfg")) { + printk(KERN_WARNING + "HiSax: %s config port %x-%x already in use\n", + CardType[card->typ], + cs->hw.teles3.cfg_reg, + cs->hw.teles3.cfg_reg + 8); + return (0); + } + } + } + if (!request_region(cs->hw.teles3.isac + 32, 32, "HiSax isac")) { + printk(KERN_WARNING + "HiSax: %s isac ports %x-%x already in use\n", + CardType[cs->typ], + cs->hw.teles3.isac + 32, + cs->hw.teles3.isac + 64); + if (cs->hw.teles3.cfg_reg) { + if (cs->typ == ISDN_CTYPE_COMPAQ_ISA) { + release_region(cs->hw.teles3.cfg_reg, 1); + } else { + release_region(cs->hw.teles3.cfg_reg, 8); + } + } + return (0); + } + if (!request_region(cs->hw.teles3.hscx[0] + 32, 32, "HiSax hscx A")) { + printk(KERN_WARNING + "HiSax: %s hscx A ports %x-%x already in use\n", + CardType[cs->typ], + cs->hw.teles3.hscx[0] + 32, + cs->hw.teles3.hscx[0] + 64); + if (cs->hw.teles3.cfg_reg) { + if (cs->typ == ISDN_CTYPE_COMPAQ_ISA) { + release_region(cs->hw.teles3.cfg_reg, 1); + } else { + release_region(cs->hw.teles3.cfg_reg, 8); + } + } + release_ioregs(cs, 1); + return (0); + } + if (!request_region(cs->hw.teles3.hscx[1] + 32, 32, "HiSax hscx B")) { + printk(KERN_WARNING + "HiSax: %s hscx B ports %x-%x already in use\n", + CardType[cs->typ], + cs->hw.teles3.hscx[1] + 32, + cs->hw.teles3.hscx[1] + 64); + if (cs->hw.teles3.cfg_reg) { + if (cs->typ == ISDN_CTYPE_COMPAQ_ISA) { + release_region(cs->hw.teles3.cfg_reg, 1); + } else { + release_region(cs->hw.teles3.cfg_reg, 8); + } + } + release_ioregs(cs, 3); + return (0); + } + } + if ((cs->hw.teles3.cfg_reg) && (cs->typ != ISDN_CTYPE_COMPAQ_ISA)) { + if ((val = bytein(cs->hw.teles3.cfg_reg + 0)) != 0x51) { + printk(KERN_WARNING "Teles: 16.3 Byte at %x is %x\n", + cs->hw.teles3.cfg_reg + 0, val); + release_io_teles3(cs); + return (0); + } + if ((val = bytein(cs->hw.teles3.cfg_reg + 1)) != 0x93) { + printk(KERN_WARNING "Teles: 16.3 Byte at %x is %x\n", + cs->hw.teles3.cfg_reg + 1, val); + release_io_teles3(cs); + return (0); + } + val = bytein(cs->hw.teles3.cfg_reg + 2);/* 0x1e=without AB + * 0x1f=with AB + * 0x1c 16.3 ??? + * 0x39 16.3 1.1 + * 0x38 16.3 1.3 + * 0x46 16.3 with AB + Video (Teles-Vision) + */ + if (val != 0x46 && val != 0x39 && val != 0x38 && val != 0x1c && val != 0x1e && val != 0x1f) { + printk(KERN_WARNING "Teles: 16.3 Byte at %x is %x\n", + cs->hw.teles3.cfg_reg + 2, val); + release_io_teles3(cs); + return (0); + } + } + printk(KERN_INFO + "HiSax: %s config irq:%d isac:0x%X cfg:0x%X\n", + CardType[cs->typ], cs->irq, + cs->hw.teles3.isac + 32, cs->hw.teles3.cfg_reg); + printk(KERN_INFO + "HiSax: hscx A:0x%X hscx B:0x%X\n", + cs->hw.teles3.hscx[0] + 32, cs->hw.teles3.hscx[1] + 32); + + setup_isac(cs); + if (reset_teles3(cs)) { + printk(KERN_WARNING "Teles3: wrong IRQ\n"); + release_io_teles3(cs); + return (0); + } + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->BC_Read_Reg = &ReadHSCX; + cs->BC_Write_Reg = &WriteHSCX; + cs->BC_Send_Data = &hscx_fill_fifo; + cs->cardmsg = &Teles_card_msg; + cs->irq_func = &teles3_interrupt; + ISACVersion(cs, "Teles3:"); + if (HscxVersion(cs, "Teles3:")) { + printk(KERN_WARNING + "Teles3: wrong HSCX versions check IO address\n"); + release_io_teles3(cs); + return (0); + } + return (1); +} diff --git a/drivers/isdn/hisax/teles_cs.c b/drivers/isdn/hisax/teles_cs.c new file mode 100644 index 000000000000..63e8e20c17a8 --- /dev/null +++ b/drivers/isdn/hisax/teles_cs.c @@ -0,0 +1,513 @@ +/* $Id: teles_cs.c,v 1.1.2.2 2004/01/25 15:07:06 keil Exp $ */ +/*====================================================================== + + A teles S0 PCMCIA client driver + + Based on skeleton by David Hinds, dhinds@allegro.stanford.edu + Written by Christof Petig, christof.petig@wtal.de + + Also inspired by ELSA PCMCIA driver + by Klaus Lichtenwalder <Lichtenwalder@ACM.org> + + Extentions to new hisax_pcmcia by Karsten Keil + + minor changes to be compatible with kernel 2.4.x + by Jan.Schubert@GMX.li + +======================================================================*/ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/ptrace.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/ioport.h> +#include <asm/io.h> +#include <asm/system.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/cisreg.h> +#include <pcmcia/ds.h> +#include "hisax_cfg.h" + +MODULE_DESCRIPTION("ISDN4Linux: PCMCIA client driver for Teles PCMCIA cards"); +MODULE_AUTHOR("Christof Petig, christof.petig@wtal.de, Karsten Keil, kkeil@suse.de"); +MODULE_LICENSE("GPL"); + +/* + All the PCMCIA modules use PCMCIA_DEBUG to control debugging. If + you do not define PCMCIA_DEBUG at all, all the debug code will be + left out. If you compile with PCMCIA_DEBUG=0, the debug code will + be present but disabled -- but it can then be enabled for specific + modules at load time with a 'pc_debug=#' option to insmod. +*/ + +#ifdef PCMCIA_DEBUG +static int pc_debug = PCMCIA_DEBUG; +module_param(pc_debug, int, 0); +#define DEBUG(n, args...) if (pc_debug>(n)) printk(KERN_DEBUG args); +static char *version = +"teles_cs.c 2.10 2002/07/30 22:23:34 kkeil"; +#else +#define DEBUG(n, args...) +#endif + +/*====================================================================*/ + +/* Parameters that can be set with 'insmod' */ + +static int protocol = 2; /* EURO-ISDN Default */ +module_param(protocol, int, 0); + +/*====================================================================*/ + +/* + The event() function is this driver's Card Services event handler. + It will be called by Card Services when an appropriate card status + event is received. The config() and release() entry points are + used to configure or release a socket, in response to card insertion + and ejection events. They are invoked from the teles_cs event + handler. +*/ + +static void teles_cs_config(dev_link_t *link); +static void teles_cs_release(dev_link_t *link); +static int teles_cs_event(event_t event, int priority, + event_callback_args_t *args); + +/* + The attach() and detach() entry points are used to create and destroy + "instances" of the driver, where each instance represents everything + needed to manage one actual PCMCIA card. +*/ + +static dev_link_t *teles_attach(void); +static void teles_detach(dev_link_t *); + +/* + The dev_info variable is the "key" that is used to match up this + device driver with appropriate cards, through the card configuration + database. +*/ + +static dev_info_t dev_info = "teles_cs"; + +/* + A linked list of "instances" of the teles_cs device. Each actual + PCMCIA card corresponds to one device instance, and is described + by one dev_link_t structure (defined in ds.h). + + You may not want to use a linked list for this -- for example, the + memory card driver uses an array of dev_link_t pointers, where minor + device numbers are used to derive the corresponding array index. +*/ + +static dev_link_t *dev_list = NULL; + +/* + A dev_link_t structure has fields for most things that are needed + to keep track of a socket, but there will usually be some device + specific information that also needs to be kept track of. The + 'priv' pointer in a dev_link_t structure can be used to point to + a device-specific private data structure, like this. + + To simplify the data structure handling, we actually include the + dev_link_t structure in the device's private data structure. + + A driver needs to provide a dev_node_t structure for each device + on a card. In some cases, there is only one device per card (for + example, ethernet cards, modems). In other cases, there may be + many actual or logical devices (SCSI adapters, memory cards with + multiple partitions). The dev_node_t structures need to be kept + in a linked list starting at the 'dev' field of a dev_link_t + structure. We allocate them in the card's private data structure, + because they generally shouldn't be allocated dynamically. + In this case, we also provide a flag to indicate if a device is + "stopped" due to a power management event, or card ejection. The + device IO routines can use a flag like this to throttle IO to a + card that is not ready to accept it. +*/ + +typedef struct local_info_t { + dev_link_t link; + dev_node_t node; + int busy; + int cardnr; +} local_info_t; + +/*====================================================================== + + teles_attach() creates an "instance" of the driver, allocatingx + local data structures for one device. The device is registered + with Card Services. + + The dev_link structure is initialized, but we don't actually + configure the card at this point -- we wait until we receive a + card insertion event. + +======================================================================*/ + +static dev_link_t *teles_attach(void) +{ + client_reg_t client_reg; + dev_link_t *link; + local_info_t *local; + int ret; + + DEBUG(0, "teles_attach()\n"); + + /* Allocate space for private device-specific data */ + local = kmalloc(sizeof(local_info_t), GFP_KERNEL); + if (!local) return NULL; + memset(local, 0, sizeof(local_info_t)); + local->cardnr = -1; + link = &local->link; link->priv = local; + + /* Interrupt setup */ + link->irq.Attributes = IRQ_TYPE_DYNAMIC_SHARING|IRQ_FIRST_SHARED; + link->irq.IRQInfo1 = IRQ_LEVEL_ID|IRQ_SHARE_ID; + link->irq.Handler = NULL; + + /* + General socket configuration defaults can go here. In this + client, we assume very little, and rely on the CIS for almost + everything. In most clients, many details (i.e., number, sizes, + and attributes of IO windows) are fixed by the nature of the + device, and can be hard-wired here. + */ + link->io.NumPorts1 = 96; + link->io.Attributes1 = IO_DATA_PATH_WIDTH_AUTO; + link->io.IOAddrLines = 5; + + link->conf.Attributes = CONF_ENABLE_IRQ; + link->conf.Vcc = 50; + link->conf.IntType = INT_MEMORY_AND_IO; + + /* Register with Card Services */ + link->next = dev_list; + dev_list = link; + client_reg.dev_info = &dev_info; + client_reg.EventMask = + CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL | + CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET | + CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME; + client_reg.event_handler = &teles_cs_event; + client_reg.Version = 0x0210; + client_reg.event_callback_args.client_data = link; + ret = pcmcia_register_client(&link->handle, &client_reg); + if (ret != CS_SUCCESS) { + cs_error(link->handle, RegisterClient, ret); + teles_detach(link); + return NULL; + } + + return link; +} /* teles_attach */ + +/*====================================================================== + + This deletes a driver "instance". The device is de-registered + with Card Services. If it has been released, all local data + structures are freed. Otherwise, the structures will be freed + when the device is released. + +======================================================================*/ + +static void teles_detach(dev_link_t *link) +{ + dev_link_t **linkp; + local_info_t *info = link->priv; + int ret; + + DEBUG(0, "teles_detach(0x%p)\n", link); + + /* Locate device structure */ + for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) + if (*linkp == link) break; + if (*linkp == NULL) + return; + + if (link->state & DEV_CONFIG) + teles_cs_release(link); + + /* Break the link with Card Services */ + if (link->handle) { + ret = pcmcia_deregister_client(link->handle); + if (ret != CS_SUCCESS) + cs_error(link->handle, DeregisterClient, ret); + } + + /* Unlink device structure and free it */ + *linkp = link->next; + kfree(info); + +} /* teles_detach */ + +/*====================================================================== + + teles_cs_config() is scheduled to run after a CARD_INSERTION event + is received, to configure the PCMCIA socket, and to make the + device available to the system. + +======================================================================*/ +static int get_tuple(client_handle_t handle, tuple_t *tuple, + cisparse_t *parse) +{ + int i = pcmcia_get_tuple_data(handle, tuple); + if (i != CS_SUCCESS) return i; + return pcmcia_parse_tuple(handle, tuple, parse); +} + +static int first_tuple(client_handle_t handle, tuple_t *tuple, + cisparse_t *parse) +{ + int i = pcmcia_get_first_tuple(handle, tuple); + if (i != CS_SUCCESS) return i; + return get_tuple(handle, tuple, parse); +} + +static int next_tuple(client_handle_t handle, tuple_t *tuple, + cisparse_t *parse) +{ + int i = pcmcia_get_next_tuple(handle, tuple); + if (i != CS_SUCCESS) return i; + return get_tuple(handle, tuple, parse); +} + +static void teles_cs_config(dev_link_t *link) +{ + client_handle_t handle; + tuple_t tuple; + cisparse_t parse; + local_info_t *dev; + int i, j, last_fn; + u_short buf[128]; + cistpl_cftable_entry_t *cf = &parse.cftable_entry; + IsdnCard_t icard; + + DEBUG(0, "teles_config(0x%p)\n", link); + handle = link->handle; + dev = link->priv; + + /* + This reads the card's CONFIG tuple to find its configuration + registers. + */ + tuple.DesiredTuple = CISTPL_CONFIG; + tuple.TupleData = (cisdata_t *)buf; + tuple.TupleDataMax = 255; + tuple.TupleOffset = 0; + tuple.Attributes = 0; + i = first_tuple(handle, &tuple, &parse); + if (i != CS_SUCCESS) { + last_fn = ParseTuple; + goto cs_failed; + } + link->conf.ConfigBase = parse.config.base; + link->conf.Present = parse.config.rmask[0]; + + /* Configure card */ + link->state |= DEV_CONFIG; + + tuple.TupleData = (cisdata_t *)buf; + tuple.TupleOffset = 0; tuple.TupleDataMax = 255; + tuple.Attributes = 0; + tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY; + i = first_tuple(handle, &tuple, &parse); + while (i == CS_SUCCESS) { + if ( (cf->io.nwin > 0) && cf->io.win[0].base) { + printk(KERN_INFO "(teles_cs: looks like the 96 model)\n"); + link->conf.ConfigIndex = cf->index; + link->io.BasePort1 = cf->io.win[0].base; + i = pcmcia_request_io(link->handle, &link->io); + if (i == CS_SUCCESS) break; + } else { + printk(KERN_INFO "(teles_cs: looks like the 97 model)\n"); + link->conf.ConfigIndex = cf->index; + for (i = 0, j = 0x2f0; j > 0x100; j -= 0x10) { + link->io.BasePort1 = j; + i = pcmcia_request_io(link->handle, &link->io); + if (i == CS_SUCCESS) break; + } + break; + } + i = next_tuple(handle, &tuple, &parse); + } + + if (i != CS_SUCCESS) { + last_fn = RequestIO; + goto cs_failed; + } + + i = pcmcia_request_irq(link->handle, &link->irq); + if (i != CS_SUCCESS) { + link->irq.AssignedIRQ = 0; + last_fn = RequestIRQ; + goto cs_failed; + } + + i = pcmcia_request_configuration(link->handle, &link->conf); + if (i != CS_SUCCESS) { + last_fn = RequestConfiguration; + goto cs_failed; + } + + /* At this point, the dev_node_t structure(s) should be + initialized and arranged in a linked list at link->dev. *//* */ + sprintf(dev->node.dev_name, "teles"); + dev->node.major = dev->node.minor = 0x0; + + link->dev = &dev->node; + + /* Finally, report what we've done */ + printk(KERN_INFO "%s: index 0x%02x: Vcc %d.%d", + dev->node.dev_name, link->conf.ConfigIndex, + link->conf.Vcc/10, link->conf.Vcc%10); + if (link->conf.Vpp1) + printk(", Vpp %d.%d", link->conf.Vpp1/10, link->conf.Vpp1%10); + if (link->conf.Attributes & CONF_ENABLE_IRQ) + printk(", irq %d", link->irq.AssignedIRQ); + if (link->io.NumPorts1) + printk(", io 0x%04x-0x%04x", link->io.BasePort1, + link->io.BasePort1+link->io.NumPorts1-1); + if (link->io.NumPorts2) + printk(" & 0x%04x-0x%04x", link->io.BasePort2, + link->io.BasePort2+link->io.NumPorts2-1); + printk("\n"); + + link->state &= ~DEV_CONFIG_PENDING; + + icard.para[0] = link->irq.AssignedIRQ; + icard.para[1] = link->io.BasePort1; + icard.protocol = protocol; + icard.typ = ISDN_CTYPE_TELESPCMCIA; + + i = hisax_init_pcmcia(link, &(((local_info_t*)link->priv)->busy), &icard); + if (i < 0) { + printk(KERN_ERR "teles_cs: failed to initialize Teles PCMCIA %d at i/o %#x\n", + i, link->io.BasePort1); + teles_cs_release(link); + } else + ((local_info_t*)link->priv)->cardnr = i; + + return; +cs_failed: + cs_error(link->handle, last_fn, i); + teles_cs_release(link); +} /* teles_cs_config */ + +/*====================================================================== + + After a card is removed, teles_cs_release() will unregister the net + device, and release the PCMCIA configuration. If the device is + still open, this will be postponed until it is closed. + +======================================================================*/ + +static void teles_cs_release(dev_link_t *link) +{ + local_info_t *local = link->priv; + + DEBUG(0, "teles_cs_release(0x%p)\n", link); + + if (local) { + if (local->cardnr >= 0) { + /* no unregister function with hisax */ + HiSax_closecard(local->cardnr); + } + } + /* Unlink the device chain */ + link->dev = NULL; + + /* Don't bother checking to see if these succeed or not */ + if (link->win) + pcmcia_release_window(link->win); + pcmcia_release_configuration(link->handle); + pcmcia_release_io(link->handle, &link->io); + pcmcia_release_irq(link->handle, &link->irq); + link->state &= ~DEV_CONFIG; +} /* teles_cs_release */ + +/*====================================================================== + + The card status event handler. Mostly, this schedules other + stuff to run after an event is received. A CARD_REMOVAL event + also sets some flags to discourage the net drivers from trying + to talk to the card any more. + + When a CARD_REMOVAL event is received, we immediately set a flag + to block future accesses to this device. All the functions that + actually access the device should check this flag to make sure + the card is still present. + +======================================================================*/ + +static int teles_cs_event(event_t event, int priority, + event_callback_args_t *args) +{ + dev_link_t *link = args->client_data; + local_info_t *dev = link->priv; + + DEBUG(1, "teles_cs_event(%d)\n", event); + + switch (event) { + case CS_EVENT_CARD_REMOVAL: + link->state &= ~DEV_PRESENT; + if (link->state & DEV_CONFIG) { + ((local_info_t*)link->priv)->busy = 1; + teles_cs_release(link); + } + break; + case CS_EVENT_CARD_INSERTION: + link->state |= DEV_PRESENT | DEV_CONFIG_PENDING; + teles_cs_config(link); + break; + case CS_EVENT_PM_SUSPEND: + link->state |= DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_RESET_PHYSICAL: + /* Mark the device as stopped, to block IO until later */ + dev->busy = 1; + if (link->state & DEV_CONFIG) + pcmcia_release_configuration(link->handle); + break; + case CS_EVENT_PM_RESUME: + link->state &= ~DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_CARD_RESET: + if (link->state & DEV_CONFIG) + pcmcia_request_configuration(link->handle, &link->conf); + dev->busy = 0; + break; + } + return 0; +} /* teles_cs_event */ + +static struct pcmcia_driver teles_cs_driver = { + .owner = THIS_MODULE, + .drv = { + .name = "teles_cs", + }, + .attach = teles_attach, + .detach = teles_detach, +}; + +static int __init init_teles_cs(void) +{ + return pcmcia_register_driver(&teles_cs_driver); +} + +static void __exit exit_teles_cs(void) +{ + pcmcia_unregister_driver(&teles_cs_driver); + BUG_ON(dev_list != NULL); +} + +module_init(init_teles_cs); +module_exit(exit_teles_cs); diff --git a/drivers/isdn/hisax/telespci.c b/drivers/isdn/hisax/telespci.c new file mode 100644 index 000000000000..0661c6c31ad0 --- /dev/null +++ b/drivers/isdn/hisax/telespci.c @@ -0,0 +1,359 @@ +/* $Id: telespci.c,v 2.23.2.3 2004/01/13 14:31:26 keil Exp $ + * + * low level stuff for Teles PCI isdn cards + * + * Author Ton van Rosmalen + * Karsten Keil + * Copyright by Ton van Rosmalen + * by Karsten Keil <keil@isdn4linux.de> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/init.h> +#include <linux/config.h> +#include "hisax.h" +#include "isac.h" +#include "hscx.h" +#include "isdnl1.h" +#include <linux/pci.h> + +extern const char *CardType[]; +const char *telespci_revision = "$Revision: 2.23.2.3 $"; + +#define ZORAN_PO_RQ_PEN 0x02000000 +#define ZORAN_PO_WR 0x00800000 +#define ZORAN_PO_GID0 0x00000000 +#define ZORAN_PO_GID1 0x00100000 +#define ZORAN_PO_GREG0 0x00000000 +#define ZORAN_PO_GREG1 0x00010000 +#define ZORAN_PO_DMASK 0xFF + +#define WRITE_ADDR_ISAC (ZORAN_PO_WR | ZORAN_PO_GID0 | ZORAN_PO_GREG0) +#define READ_DATA_ISAC (ZORAN_PO_GID0 | ZORAN_PO_GREG1) +#define WRITE_DATA_ISAC (ZORAN_PO_WR | ZORAN_PO_GID0 | ZORAN_PO_GREG1) +#define WRITE_ADDR_HSCX (ZORAN_PO_WR | ZORAN_PO_GID1 | ZORAN_PO_GREG0) +#define READ_DATA_HSCX (ZORAN_PO_GID1 | ZORAN_PO_GREG1) +#define WRITE_DATA_HSCX (ZORAN_PO_WR | ZORAN_PO_GID1 | ZORAN_PO_GREG1) + +#define ZORAN_WAIT_NOBUSY do { \ + portdata = readl(adr + 0x200); \ + } while (portdata & ZORAN_PO_RQ_PEN) + +static inline u_char +readisac(void __iomem *adr, u_char off) +{ + register unsigned int portdata; + + ZORAN_WAIT_NOBUSY; + + /* set address for ISAC */ + writel(WRITE_ADDR_ISAC | off, adr + 0x200); + ZORAN_WAIT_NOBUSY; + + /* read data from ISAC */ + writel(READ_DATA_ISAC, adr + 0x200); + ZORAN_WAIT_NOBUSY; + return((u_char)(portdata & ZORAN_PO_DMASK)); +} + +static inline void +writeisac(void __iomem *adr, u_char off, u_char data) +{ + register unsigned int portdata; + + ZORAN_WAIT_NOBUSY; + + /* set address for ISAC */ + writel(WRITE_ADDR_ISAC | off, adr + 0x200); + ZORAN_WAIT_NOBUSY; + + /* write data to ISAC */ + writel(WRITE_DATA_ISAC | data, adr + 0x200); + ZORAN_WAIT_NOBUSY; +} + +static inline u_char +readhscx(void __iomem *adr, int hscx, u_char off) +{ + register unsigned int portdata; + + ZORAN_WAIT_NOBUSY; + /* set address for HSCX */ + writel(WRITE_ADDR_HSCX | ((hscx ? 0x40:0) + off), adr + 0x200); + ZORAN_WAIT_NOBUSY; + + /* read data from HSCX */ + writel(READ_DATA_HSCX, adr + 0x200); + ZORAN_WAIT_NOBUSY; + return ((u_char)(portdata & ZORAN_PO_DMASK)); +} + +static inline void +writehscx(void __iomem *adr, int hscx, u_char off, u_char data) +{ + register unsigned int portdata; + + ZORAN_WAIT_NOBUSY; + /* set address for HSCX */ + writel(WRITE_ADDR_HSCX | ((hscx ? 0x40:0) + off), adr + 0x200); + ZORAN_WAIT_NOBUSY; + + /* write data to HSCX */ + writel(WRITE_DATA_HSCX | data, adr + 0x200); + ZORAN_WAIT_NOBUSY; +} + +static inline void +read_fifo_isac(void __iomem *adr, u_char * data, int size) +{ + register unsigned int portdata; + register int i; + + ZORAN_WAIT_NOBUSY; + /* read data from ISAC */ + for (i = 0; i < size; i++) { + /* set address for ISAC fifo */ + writel(WRITE_ADDR_ISAC | 0x1E, adr + 0x200); + ZORAN_WAIT_NOBUSY; + writel(READ_DATA_ISAC, adr + 0x200); + ZORAN_WAIT_NOBUSY; + data[i] = (u_char)(portdata & ZORAN_PO_DMASK); + } +} + +static void +write_fifo_isac(void __iomem *adr, u_char * data, int size) +{ + register unsigned int portdata; + register int i; + + ZORAN_WAIT_NOBUSY; + /* write data to ISAC */ + for (i = 0; i < size; i++) { + /* set address for ISAC fifo */ + writel(WRITE_ADDR_ISAC | 0x1E, adr + 0x200); + ZORAN_WAIT_NOBUSY; + writel(WRITE_DATA_ISAC | data[i], adr + 0x200); + ZORAN_WAIT_NOBUSY; + } +} + +static inline void +read_fifo_hscx(void __iomem *adr, int hscx, u_char * data, int size) +{ + register unsigned int portdata; + register int i; + + ZORAN_WAIT_NOBUSY; + /* read data from HSCX */ + for (i = 0; i < size; i++) { + /* set address for HSCX fifo */ + writel(WRITE_ADDR_HSCX |(hscx ? 0x5F:0x1F), adr + 0x200); + ZORAN_WAIT_NOBUSY; + writel(READ_DATA_HSCX, adr + 0x200); + ZORAN_WAIT_NOBUSY; + data[i] = (u_char) (portdata & ZORAN_PO_DMASK); + } +} + +static inline void +write_fifo_hscx(void __iomem *adr, int hscx, u_char * data, int size) +{ + unsigned int portdata; + register int i; + + ZORAN_WAIT_NOBUSY; + /* write data to HSCX */ + for (i = 0; i < size; i++) { + /* set address for HSCX fifo */ + writel(WRITE_ADDR_HSCX |(hscx ? 0x5F:0x1F), adr + 0x200); + ZORAN_WAIT_NOBUSY; + writel(WRITE_DATA_HSCX | data[i], adr + 0x200); + ZORAN_WAIT_NOBUSY; + udelay(10); + } +} + +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + return (readisac(cs->hw.teles0.membase, offset)); +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writeisac(cs->hw.teles0.membase, offset, value); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + read_fifo_isac(cs->hw.teles0.membase, data, size); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + write_fifo_isac(cs->hw.teles0.membase, data, size); +} + +static u_char +ReadHSCX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + return (readhscx(cs->hw.teles0.membase, hscx, offset)); +} + +static void +WriteHSCX(struct IsdnCardState *cs, int hscx, u_char offset, u_char value) +{ + writehscx(cs->hw.teles0.membase, hscx, offset, value); +} + +/* + * fast interrupt HSCX stuff goes here + */ + +#define READHSCX(cs, nr, reg) readhscx(cs->hw.teles0.membase, nr, reg) +#define WRITEHSCX(cs, nr, reg, data) writehscx(cs->hw.teles0.membase, nr, reg, data) +#define READHSCXFIFO(cs, nr, ptr, cnt) read_fifo_hscx(cs->hw.teles0.membase, nr, ptr, cnt) +#define WRITEHSCXFIFO(cs, nr, ptr, cnt) write_fifo_hscx(cs->hw.teles0.membase, nr, ptr, cnt) + +#include "hscx_irq.c" + +static irqreturn_t +telespci_interrupt(int intno, void *dev_id, struct pt_regs *regs) +{ + struct IsdnCardState *cs = dev_id; + u_char hval, ival; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + hval = readhscx(cs->hw.teles0.membase, 1, HSCX_ISTA); + if (hval) + hscx_int_main(cs, hval); + ival = readisac(cs->hw.teles0.membase, ISAC_ISTA); + if ((hval | ival) == 0) { + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_NONE; + } + if (ival) + isac_interrupt(cs, ival); + /* Clear interrupt register for Zoran PCI controller */ + writel(0x70000000, cs->hw.teles0.membase + 0x3C); + + writehscx(cs->hw.teles0.membase, 0, HSCX_MASK, 0xFF); + writehscx(cs->hw.teles0.membase, 1, HSCX_MASK, 0xFF); + writeisac(cs->hw.teles0.membase, ISAC_MASK, 0xFF); + writeisac(cs->hw.teles0.membase, ISAC_MASK, 0x0); + writehscx(cs->hw.teles0.membase, 0, HSCX_MASK, 0x0); + writehscx(cs->hw.teles0.membase, 1, HSCX_MASK, 0x0); + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +void +release_io_telespci(struct IsdnCardState *cs) +{ + iounmap(cs->hw.teles0.membase); +} + +static int +TelesPCI_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_long flags; + + switch (mt) { + case CARD_RESET: + return(0); + case CARD_RELEASE: + release_io_telespci(cs); + return(0); + case CARD_INIT: + spin_lock_irqsave(&cs->lock, flags); + inithscxisac(cs, 3); + spin_unlock_irqrestore(&cs->lock, flags); + return(0); + case CARD_TEST: + return(0); + } + return(0); +} + +static struct pci_dev *dev_tel __initdata = NULL; + +int __init +setup_telespci(struct IsdnCard *card) +{ + struct IsdnCardState *cs = card->cs; + char tmp[64]; + +#ifdef __BIG_ENDIAN +#error "not running on big endian machines now" +#endif + strcpy(tmp, telespci_revision); + printk(KERN_INFO "HiSax: Teles/PCI driver Rev. %s\n", HiSax_getrev(tmp)); + if (cs->typ != ISDN_CTYPE_TELESPCI) + return (0); +#ifdef CONFIG_PCI + if ((dev_tel = pci_find_device (PCI_VENDOR_ID_ZORAN, PCI_DEVICE_ID_ZORAN_36120, dev_tel))) { + if (pci_enable_device(dev_tel)) + return(0); + cs->irq = dev_tel->irq; + if (!cs->irq) { + printk(KERN_WARNING "Teles: No IRQ for PCI card found\n"); + return(0); + } + cs->hw.teles0.membase = ioremap(pci_resource_start(dev_tel, 0), + PAGE_SIZE); + printk(KERN_INFO "Found: Zoran, base-address: 0x%lx, irq: 0x%x\n", + pci_resource_start(dev_tel, 0), dev_tel->irq); + } else { + printk(KERN_WARNING "TelesPCI: No PCI card found\n"); + return(0); + } +#else + printk(KERN_WARNING "HiSax: Teles/PCI and NO_PCI_BIOS\n"); + printk(KERN_WARNING "HiSax: Teles/PCI unable to config\n"); + return (0); +#endif /* CONFIG_PCI */ + + /* Initialize Zoran PCI controller */ + writel(0x00000000, cs->hw.teles0.membase + 0x28); + writel(0x01000000, cs->hw.teles0.membase + 0x28); + writel(0x01000000, cs->hw.teles0.membase + 0x28); + writel(0x7BFFFFFF, cs->hw.teles0.membase + 0x2C); + writel(0x70000000, cs->hw.teles0.membase + 0x3C); + writel(0x61000000, cs->hw.teles0.membase + 0x40); + /* writel(0x00800000, cs->hw.teles0.membase + 0x200); */ + + printk(KERN_INFO + "HiSax: %s config irq:%d mem:%p\n", + CardType[cs->typ], cs->irq, + cs->hw.teles0.membase); + + setup_isac(cs); + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->BC_Read_Reg = &ReadHSCX; + cs->BC_Write_Reg = &WriteHSCX; + cs->BC_Send_Data = &hscx_fill_fifo; + cs->cardmsg = &TelesPCI_card_msg; + cs->irq_func = &telespci_interrupt; + cs->irq_flags |= SA_SHIRQ; + ISACVersion(cs, "TelesPCI:"); + if (HscxVersion(cs, "TelesPCI:")) { + printk(KERN_WARNING + "TelesPCI: wrong HSCX versions check IO/MEM addresses\n"); + release_io_telespci(cs); + return (0); + } + return (1); +} diff --git a/drivers/isdn/hisax/w6692.c b/drivers/isdn/hisax/w6692.c new file mode 100644 index 000000000000..d2b6b8e72980 --- /dev/null +++ b/drivers/isdn/hisax/w6692.c @@ -0,0 +1,1096 @@ +/* $Id: w6692.c,v 1.18.2.4 2004/02/11 13:21:34 keil Exp $ + * + * Winbond W6692 specific routines + * + * Author Petr Novak + * Copyright by Petr Novak <petr.novak@i.cz> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/config.h> +#include <linux/init.h> +#include "hisax.h" +#include "w6692.h" +#include "isdnl1.h" +#include <linux/interrupt.h> +#include <linux/pci.h> + +/* table entry in the PCI devices list */ +typedef struct { + int vendor_id; + int device_id; + char *vendor_name; + char *card_name; +} PCI_ENTRY; + +static const PCI_ENTRY id_list[] = +{ + {PCI_VENDOR_ID_WINBOND2, PCI_DEVICE_ID_WINBOND2_6692, "Winbond", "W6692"}, + {PCI_VENDOR_ID_DYNALINK, PCI_DEVICE_ID_DYNALINK_IS64PH, "Dynalink/AsusCom", "IS64PH"}, + {0, 0, "U.S.Robotics", "ISDN PCI Card TA"} +}; + +#define W6692_SV_USR 0x16ec +#define W6692_SD_USR 0x3409 +#define W6692_WINBOND 0 +#define W6692_DYNALINK 1 +#define W6692_USR 2 + +extern const char *CardType[]; + +const char *w6692_revision = "$Revision: 1.18.2.4 $"; + +#define DBUSY_TIMER_VALUE 80 + +static char *W6692Ver[] __initdata = +{"W6692 V00", "W6692 V01", "W6692 V10", + "W6692 V11"}; + +static void __init +W6692Version(struct IsdnCardState *cs, char *s) +{ + int val; + + val = cs->readW6692(cs, W_D_RBCH); + printk(KERN_INFO "%s Winbond W6692 version (%x): %s\n", s, val, W6692Ver[(val >> 6) & 3]); +} + +static void +ph_command(struct IsdnCardState *cs, unsigned int command) +{ + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ph_command %x", command); + cs->writeisac(cs, W_CIX, command); +} + + +static void +W6692_new_ph(struct IsdnCardState *cs) +{ + switch (cs->dc.w6692.ph_state) { + case (W_L1CMD_RST): + ph_command(cs, W_L1CMD_DRC); + l1_msg(cs, HW_RESET | INDICATION, NULL); + /* fallthru */ + case (W_L1IND_CD): + l1_msg(cs, HW_DEACTIVATE | CONFIRM, NULL); + break; + case (W_L1IND_DRD): + l1_msg(cs, HW_DEACTIVATE | INDICATION, NULL); + break; + case (W_L1IND_CE): + l1_msg(cs, HW_POWERUP | CONFIRM, NULL); + break; + case (W_L1IND_LD): + l1_msg(cs, HW_RSYNC | INDICATION, NULL); + break; + case (W_L1IND_ARD): + l1_msg(cs, HW_INFO2 | INDICATION, NULL); + break; + case (W_L1IND_AI8): + l1_msg(cs, HW_INFO4_P8 | INDICATION, NULL); + break; + case (W_L1IND_AI10): + l1_msg(cs, HW_INFO4_P10 | INDICATION, NULL); + break; + default: + break; + } +} + +static void +W6692_bh(struct IsdnCardState *cs) +{ + struct PStack *stptr; + + if (!cs) + return; + if (test_and_clear_bit(D_CLEARBUSY, &cs->event)) { + if (cs->debug) + debugl1(cs, "D-Channel Busy cleared"); + stptr = cs->stlist; + while (stptr != NULL) { + stptr->l1.l1l2(stptr, PH_PAUSE | CONFIRM, NULL); + stptr = stptr->next; + } + } + if (test_and_clear_bit(D_L1STATECHANGE, &cs->event)) + W6692_new_ph(cs); + if (test_and_clear_bit(D_RCVBUFREADY, &cs->event)) + DChannel_proc_rcv(cs); + if (test_and_clear_bit(D_XMTBUFREADY, &cs->event)) + DChannel_proc_xmt(cs); +/* + if (test_and_clear_bit(D_RX_MON1, &cs->event)) + arcofi_fsm(cs, ARCOFI_RX_END, NULL); + if (test_and_clear_bit(D_TX_MON1, &cs->event)) + arcofi_fsm(cs, ARCOFI_TX_END, NULL); + */ +} + +static void +W6692_empty_fifo(struct IsdnCardState *cs, int count) +{ + u_char *ptr; + + if ((cs->debug & L1_DEB_ISAC) && !(cs->debug & L1_DEB_ISAC_FIFO)) + debugl1(cs, "W6692_empty_fifo"); + + if ((cs->rcvidx + count) >= MAX_DFRAME_LEN_L1) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "W6692_empty_fifo overrun %d", + cs->rcvidx + count); + cs->writeW6692(cs, W_D_CMDR, W_D_CMDR_RACK); + cs->rcvidx = 0; + return; + } + ptr = cs->rcvbuf + cs->rcvidx; + cs->rcvidx += count; + cs->readW6692fifo(cs, ptr, count); + cs->writeW6692(cs, W_D_CMDR, W_D_CMDR_RACK); + if (cs->debug & L1_DEB_ISAC_FIFO) { + char *t = cs->dlog; + + t += sprintf(t, "W6692_empty_fifo cnt %d", count); + QuickHex(t, ptr, count); + debugl1(cs, cs->dlog); + } +} + +static void +W6692_fill_fifo(struct IsdnCardState *cs) +{ + int count, more; + u_char *ptr; + + if ((cs->debug & L1_DEB_ISAC) && !(cs->debug & L1_DEB_ISAC_FIFO)) + debugl1(cs, "W6692_fill_fifo"); + + if (!cs->tx_skb) + return; + + count = cs->tx_skb->len; + if (count <= 0) + return; + + more = 0; + if (count > W_D_FIFO_THRESH) { + more = !0; + count = W_D_FIFO_THRESH; + } + ptr = cs->tx_skb->data; + skb_pull(cs->tx_skb, count); + cs->tx_cnt += count; + cs->writeW6692fifo(cs, ptr, count); + cs->writeW6692(cs, W_D_CMDR, more ? W_D_CMDR_XMS : (W_D_CMDR_XMS | W_D_CMDR_XME)); + if (test_and_set_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) { + debugl1(cs, "W6692_fill_fifo dbusytimer running"); + del_timer(&cs->dbusytimer); + } + init_timer(&cs->dbusytimer); + cs->dbusytimer.expires = jiffies + ((DBUSY_TIMER_VALUE * HZ) / 1000); + add_timer(&cs->dbusytimer); + if (cs->debug & L1_DEB_ISAC_FIFO) { + char *t = cs->dlog; + + t += sprintf(t, "W6692_fill_fifo cnt %d", count); + QuickHex(t, ptr, count); + debugl1(cs, cs->dlog); + } +} + +static void +W6692B_empty_fifo(struct BCState *bcs, int count) +{ + u_char *ptr; + struct IsdnCardState *cs = bcs->cs; + + if ((cs->debug & L1_DEB_HSCX) && !(cs->debug & L1_DEB_HSCX_FIFO)) + debugl1(cs, "W6692B_empty_fifo"); + + if (bcs->hw.w6692.rcvidx + count > HSCX_BUFMAX) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "W6692B_empty_fifo: incoming packet too large"); + cs->BC_Write_Reg(cs, bcs->channel, W_B_CMDR, W_B_CMDR_RACK | W_B_CMDR_RACT); + bcs->hw.w6692.rcvidx = 0; + return; + } + ptr = bcs->hw.w6692.rcvbuf + bcs->hw.w6692.rcvidx; + bcs->hw.w6692.rcvidx += count; + READW6692BFIFO(cs, bcs->channel, ptr, count); + cs->BC_Write_Reg(cs, bcs->channel, W_B_CMDR, W_B_CMDR_RACK | W_B_CMDR_RACT); + if (cs->debug & L1_DEB_HSCX_FIFO) { + char *t = bcs->blog; + + t += sprintf(t, "W6692B_empty_fifo %c cnt %d", + bcs->channel + '1', count); + QuickHex(t, ptr, count); + debugl1(cs, bcs->blog); + } +} + +static void +W6692B_fill_fifo(struct BCState *bcs) +{ + struct IsdnCardState *cs = bcs->cs; + int more, count; + u_char *ptr; + + if (!bcs->tx_skb) + return; + if (bcs->tx_skb->len <= 0) + return; + + more = (bcs->mode == L1_MODE_TRANS) ? 1 : 0; + if (bcs->tx_skb->len > W_B_FIFO_THRESH) { + more = 1; + count = W_B_FIFO_THRESH; + } else + count = bcs->tx_skb->len; + + if ((cs->debug & L1_DEB_HSCX) && !(cs->debug & L1_DEB_HSCX_FIFO)) + debugl1(cs, "W6692B_fill_fifo%s%d", (more ? " ": " last "), count); + + ptr = bcs->tx_skb->data; + skb_pull(bcs->tx_skb, count); + bcs->tx_cnt -= count; + bcs->hw.w6692.count += count; + WRITEW6692BFIFO(cs, bcs->channel, ptr, count); + cs->BC_Write_Reg(cs, bcs->channel, W_B_CMDR, W_B_CMDR_RACT | W_B_CMDR_XMS | (more ? 0 : W_B_CMDR_XME)); + if (cs->debug & L1_DEB_HSCX_FIFO) { + char *t = bcs->blog; + + t += sprintf(t, "W6692B_fill_fifo %c cnt %d", + bcs->channel + '1', count); + QuickHex(t, ptr, count); + debugl1(cs, bcs->blog); + } +} + +static void +W6692B_interrupt(struct IsdnCardState *cs, u_char bchan) +{ + u_char val; + u_char r; + struct BCState *bcs; + struct sk_buff *skb; + int count; + + bcs = (cs->bcs->channel == bchan) ? cs->bcs : (cs->bcs+1); + val = cs->BC_Read_Reg(cs, bchan, W_B_EXIR); + debugl1(cs, "W6692B chan %d B_EXIR 0x%02X", bchan, val); + + if (!test_bit(BC_FLG_INIT, &bcs->Flag)) { + debugl1(cs, "W6692B not INIT yet"); + return; + } + if (val & W_B_EXI_RME) { /* RME */ + r = cs->BC_Read_Reg(cs, bchan, W_B_STAR); + if (r & (W_B_STAR_RDOV | W_B_STAR_CRCE | W_B_STAR_RMB)) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "W6692 B STAR %x", r); + if ((r & W_B_STAR_RDOV) && bcs->mode) + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "W6692 B RDOV mode=%d", + bcs->mode); + if (r & W_B_STAR_CRCE) + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "W6692 B CRC error"); + cs->BC_Write_Reg(cs, bchan, W_B_CMDR, W_B_CMDR_RACK | W_B_CMDR_RRST | W_B_CMDR_RACT); + } else { + count = cs->BC_Read_Reg(cs, bchan, W_B_RBCL) & (W_B_FIFO_THRESH - 1); + if (count == 0) + count = W_B_FIFO_THRESH; + W6692B_empty_fifo(bcs, count); + if ((count = bcs->hw.w6692.rcvidx) > 0) { + if (cs->debug & L1_DEB_HSCX_FIFO) + debugl1(cs, "W6692 Bchan Frame %d", count); + if (!(skb = dev_alloc_skb(count))) + printk(KERN_WARNING "W6692: Bchan receive out of memory\n"); + else { + memcpy(skb_put(skb, count), bcs->hw.w6692.rcvbuf, count); + skb_queue_tail(&bcs->rqueue, skb); + } + } + } + bcs->hw.w6692.rcvidx = 0; + schedule_event(bcs, B_RCVBUFREADY); + } + if (val & W_B_EXI_RMR) { /* RMR */ + W6692B_empty_fifo(bcs, W_B_FIFO_THRESH); + r = cs->BC_Read_Reg(cs, bchan, W_B_STAR); + if (r & W_B_STAR_RDOV) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "W6692 B RDOV(RMR) mode=%d",bcs->mode); + cs->BC_Write_Reg(cs, bchan, W_B_CMDR, W_B_CMDR_RACK | W_B_CMDR_RRST | W_B_CMDR_RACT); + if (bcs->mode != L1_MODE_TRANS) + bcs->hw.w6692.rcvidx = 0; + } + if (bcs->mode == L1_MODE_TRANS) { + /* receive audio data */ + if (!(skb = dev_alloc_skb(W_B_FIFO_THRESH))) + printk(KERN_WARNING "HiSax: receive out of memory\n"); + else { + memcpy(skb_put(skb, W_B_FIFO_THRESH), bcs->hw.w6692.rcvbuf, W_B_FIFO_THRESH); + skb_queue_tail(&bcs->rqueue, skb); + } + bcs->hw.w6692.rcvidx = 0; + schedule_event(bcs, B_RCVBUFREADY); + } + } + if (val & W_B_EXI_XDUN) { /* XDUN */ + cs->BC_Write_Reg(cs, bchan, W_B_CMDR, W_B_CMDR_XRST | W_B_CMDR_RACT); + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "W6692 B EXIR %x Lost TX", val); + if (bcs->mode == 1) + W6692B_fill_fifo(bcs); + else { + /* Here we lost an TX interrupt, so + * restart transmitting the whole frame. + */ + if (bcs->tx_skb) { + skb_push(bcs->tx_skb, bcs->hw.w6692.count); + bcs->tx_cnt += bcs->hw.w6692.count; + bcs->hw.w6692.count = 0; + } + } + return; + } + if (val & W_B_EXI_XFR) { /* XFR */ + r = cs->BC_Read_Reg(cs, bchan, W_B_STAR); + if (r & W_B_STAR_XDOW) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "W6692 B STAR %x XDOW", r); + cs->BC_Write_Reg(cs, bchan, W_B_CMDR, W_B_CMDR_XRST | W_B_CMDR_RACT); + if (bcs->tx_skb && (bcs->mode != 1)) { + skb_push(bcs->tx_skb, bcs->hw.w6692.count); + bcs->tx_cnt += bcs->hw.w6692.count; + bcs->hw.w6692.count = 0; + } + } + if (bcs->tx_skb) { + if (bcs->tx_skb->len) { + W6692B_fill_fifo(bcs); + return; + } else { + if (test_bit(FLG_LLI_L1WAKEUP,&bcs->st->lli.flag) && + (PACKET_NOACK != bcs->tx_skb->pkt_type)) { + u_long flags; + spin_lock_irqsave(&bcs->aclock, flags); + bcs->ackcnt += bcs->hw.w6692.count; + spin_unlock_irqrestore(&bcs->aclock, flags); + schedule_event(bcs, B_ACKPENDING); + } + dev_kfree_skb_irq(bcs->tx_skb); + bcs->hw.w6692.count = 0; + bcs->tx_skb = NULL; + } + } + if ((bcs->tx_skb = skb_dequeue(&bcs->squeue))) { + bcs->hw.w6692.count = 0; + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + W6692B_fill_fifo(bcs); + } else { + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + schedule_event(bcs, B_XMTBUFREADY); + } + } +} + +static irqreturn_t +W6692_interrupt(int intno, void *dev_id, struct pt_regs *regs) +{ + struct IsdnCardState *cs = dev_id; + u_char val, exval, v1; + struct sk_buff *skb; + u_int count; + u_long flags; + int icnt = 5; + + spin_lock_irqsave(&cs->lock, flags); + val = cs->readW6692(cs, W_ISTA); + if (!val) { + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_NONE; + } + StartW6692: + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "W6692 ISTA %x", val); + + if (val & W_INT_D_RME) { /* RME */ + exval = cs->readW6692(cs, W_D_RSTA); + if (exval & (W_D_RSTA_RDOV | W_D_RSTA_CRCE | W_D_RSTA_RMB)) { + if (exval & W_D_RSTA_RDOV) + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "W6692 RDOV"); + if (exval & W_D_RSTA_CRCE) + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "W6692 D-channel CRC error"); + if (exval & W_D_RSTA_RMB) + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "W6692 D-channel ABORT"); + cs->writeW6692(cs, W_D_CMDR, W_D_CMDR_RACK | W_D_CMDR_RRST); + } else { + count = cs->readW6692(cs, W_D_RBCL) & (W_D_FIFO_THRESH - 1); + if (count == 0) + count = W_D_FIFO_THRESH; + W6692_empty_fifo(cs, count); + if ((count = cs->rcvidx) > 0) { + cs->rcvidx = 0; + if (!(skb = alloc_skb(count, GFP_ATOMIC))) + printk(KERN_WARNING "HiSax: D receive out of memory\n"); + else { + memcpy(skb_put(skb, count), cs->rcvbuf, count); + skb_queue_tail(&cs->rq, skb); + } + } + } + cs->rcvidx = 0; + schedule_event(cs, D_RCVBUFREADY); + } + if (val & W_INT_D_RMR) { /* RMR */ + W6692_empty_fifo(cs, W_D_FIFO_THRESH); + } + if (val & W_INT_D_XFR) { /* XFR */ + if (test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) + del_timer(&cs->dbusytimer); + if (test_and_clear_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + schedule_event(cs, D_CLEARBUSY); + if (cs->tx_skb) { + if (cs->tx_skb->len) { + W6692_fill_fifo(cs); + goto afterXFR; + } else { + dev_kfree_skb_irq(cs->tx_skb); + cs->tx_cnt = 0; + cs->tx_skb = NULL; + } + } + if ((cs->tx_skb = skb_dequeue(&cs->sq))) { + cs->tx_cnt = 0; + W6692_fill_fifo(cs); + } else + schedule_event(cs, D_XMTBUFREADY); + } + afterXFR: + if (val & (W_INT_XINT0 | W_INT_XINT1)) { /* XINT0/1 - never */ + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "W6692 spurious XINT!"); + } + if (val & W_INT_D_EXI) { /* EXI */ + exval = cs->readW6692(cs, W_D_EXIR); + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "W6692 D_EXIR %02x", exval); + if (exval & (W_D_EXI_XDUN | W_D_EXI_XCOL)) { /* Transmit underrun/collision */ + debugl1(cs, "W6692 D-chan underrun/collision"); + printk(KERN_WARNING "HiSax: W6692 XDUN/XCOL\n"); + if (test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) + del_timer(&cs->dbusytimer); + if (test_and_clear_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + schedule_event(cs, D_CLEARBUSY); + if (cs->tx_skb) { /* Restart frame */ + skb_push(cs->tx_skb, cs->tx_cnt); + cs->tx_cnt = 0; + W6692_fill_fifo(cs); + } else { + printk(KERN_WARNING "HiSax: W6692 XDUN/XCOL no skb\n"); + debugl1(cs, "W6692 XDUN/XCOL no skb"); + cs->writeW6692(cs, W_D_CMDR, W_D_CMDR_XRST); + } + } + if (exval & W_D_EXI_RDOV) { /* RDOV */ + debugl1(cs, "W6692 D-channel RDOV"); + printk(KERN_WARNING "HiSax: W6692 D-RDOV\n"); + cs->writeW6692(cs, W_D_CMDR, W_D_CMDR_RRST); + } + if (exval & W_D_EXI_TIN2) { /* TIN2 - never */ + debugl1(cs, "W6692 spurious TIN2 interrupt"); + } + if (exval & W_D_EXI_MOC) { /* MOC - not supported */ + debugl1(cs, "W6692 spurious MOC interrupt"); + v1 = cs->readW6692(cs, W_MOSR); + debugl1(cs, "W6692 MOSR %02x", v1); + } + if (exval & W_D_EXI_ISC) { /* ISC - Level1 change */ + v1 = cs->readW6692(cs, W_CIR); + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "W6692 ISC CIR=0x%02X", v1); + if (v1 & W_CIR_ICC) { + cs->dc.w6692.ph_state = v1 & W_CIR_COD_MASK; + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ph_state_change %x", cs->dc.w6692.ph_state); + schedule_event(cs, D_L1STATECHANGE); + } + if (v1 & W_CIR_SCC) { + v1 = cs->readW6692(cs, W_SQR); + debugl1(cs, "W6692 SCC SQR=0x%02X", v1); + } + } + if (exval & W_D_EXI_WEXP) { + debugl1(cs, "W6692 spurious WEXP interrupt!"); + } + if (exval & W_D_EXI_TEXP) { + debugl1(cs, "W6692 spurious TEXP interrupt!"); + } + } + if (val & W_INT_B1_EXI) { + debugl1(cs, "W6692 B channel 1 interrupt"); + W6692B_interrupt(cs, 0); + } + if (val & W_INT_B2_EXI) { + debugl1(cs, "W6692 B channel 2 interrupt"); + W6692B_interrupt(cs, 1); + } + val = cs->readW6692(cs, W_ISTA); + if (val && icnt) { + icnt--; + goto StartW6692; + } + if (!icnt) { + printk(KERN_WARNING "W6692 IRQ LOOP\n"); + cs->writeW6692(cs, W_IMASK, 0xff); + } + spin_unlock_irqrestore(&cs->lock, flags); + return IRQ_HANDLED; +} + +static void +W6692_l1hw(struct PStack *st, int pr, void *arg) +{ + struct IsdnCardState *cs = (struct IsdnCardState *) st->l1.hardware; + struct sk_buff *skb = arg; + u_long flags; + int val; + + switch (pr) { + case (PH_DATA | REQUEST): + if (cs->debug & DEB_DLOG_HEX) + LogFrame(cs, skb->data, skb->len); + if (cs->debug & DEB_DLOG_VERBOSE) + dlogframe(cs, skb, 0); + spin_lock_irqsave(&cs->lock, flags); + if (cs->tx_skb) { + skb_queue_tail(&cs->sq, skb); +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "PH_DATA Queued", 0); +#endif + } else { + cs->tx_skb = skb; + cs->tx_cnt = 0; +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "PH_DATA", 0); +#endif + W6692_fill_fifo(cs); + } + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (PH_PULL | INDICATION): + spin_lock_irqsave(&cs->lock, flags); + if (cs->tx_skb) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, " l2l1 tx_skb exist this shouldn't happen"); + skb_queue_tail(&cs->sq, skb); + spin_unlock_irqrestore(&cs->lock, flags); + break; + } + if (cs->debug & DEB_DLOG_HEX) + LogFrame(cs, skb->data, skb->len); + if (cs->debug & DEB_DLOG_VERBOSE) + dlogframe(cs, skb, 0); + cs->tx_skb = skb; + cs->tx_cnt = 0; +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + Logl2Frame(cs, skb, "PH_DATA_PULLED", 0); +#endif + W6692_fill_fifo(cs); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (PH_PULL | REQUEST): +#ifdef L2FRAME_DEBUG /* psa */ + if (cs->debug & L1_DEB_LAPD) + debugl1(cs, "-> PH_REQUEST_PULL"); +#endif + if (!cs->tx_skb) { + test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } else + test_and_set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + case (HW_RESET | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + if ((cs->dc.w6692.ph_state == W_L1IND_DRD)) { + ph_command(cs, W_L1CMD_ECK); + spin_unlock_irqrestore(&cs->lock, flags); + } else { + ph_command(cs, W_L1CMD_RST); + cs->dc.w6692.ph_state = W_L1CMD_RST; + spin_unlock_irqrestore(&cs->lock, flags); + W6692_new_ph(cs); + } + break; + case (HW_ENABLE | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + ph_command(cs, W_L1CMD_ECK); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (HW_INFO3 | REQUEST): + spin_lock_irqsave(&cs->lock, flags); + ph_command(cs, W_L1CMD_AR8); + spin_unlock_irqrestore(&cs->lock, flags); + break; + case (HW_TESTLOOP | REQUEST): + val = 0; + if (1 & (long) arg) + val |= 0x0c; + if (2 & (long) arg) + val |= 0x3; + /* !!! not implemented yet */ + break; + case (HW_DEACTIVATE | RESPONSE): + skb_queue_purge(&cs->rq); + skb_queue_purge(&cs->sq); + if (cs->tx_skb) { + dev_kfree_skb_any(cs->tx_skb); + cs->tx_skb = NULL; + } + if (test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) + del_timer(&cs->dbusytimer); + if (test_and_clear_bit(FLG_L1_DBUSY, &cs->HW_Flags)) + schedule_event(cs, D_CLEARBUSY); + break; + default: + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "W6692_l1hw unknown %04x", pr); + break; + } +} + +static void +setstack_W6692(struct PStack *st, struct IsdnCardState *cs) +{ + st->l1.l1hw = W6692_l1hw; +} + +static void +DC_Close_W6692(struct IsdnCardState *cs) +{ +} + +static void +dbusy_timer_handler(struct IsdnCardState *cs) +{ + struct PStack *stptr; + int rbch, star; + u_long flags; + + spin_lock_irqsave(&cs->lock, flags); + if (test_bit(FLG_DBUSY_TIMER, &cs->HW_Flags)) { + rbch = cs->readW6692(cs, W_D_RBCH); + star = cs->readW6692(cs, W_D_STAR); + if (cs->debug) + debugl1(cs, "D-Channel Busy D_RBCH %02x D_STAR %02x", + rbch, star); + if (star & W_D_STAR_XBZ) { /* D-Channel Busy */ + test_and_set_bit(FLG_L1_DBUSY, &cs->HW_Flags); + stptr = cs->stlist; + while (stptr != NULL) { + stptr->l1.l1l2(stptr, PH_PAUSE | INDICATION, NULL); + stptr = stptr->next; + } + } else { + /* discard frame; reset transceiver */ + test_and_clear_bit(FLG_DBUSY_TIMER, &cs->HW_Flags); + if (cs->tx_skb) { + dev_kfree_skb_any(cs->tx_skb); + cs->tx_cnt = 0; + cs->tx_skb = NULL; + } else { + printk(KERN_WARNING "HiSax: W6692 D-Channel Busy no skb\n"); + debugl1(cs, "D-Channel Busy no skb"); + } + cs->writeW6692(cs, W_D_CMDR, W_D_CMDR_XRST); /* Transmitter reset */ + spin_unlock_irqrestore(&cs->lock, flags); + cs->irq_func(cs->irq, cs, NULL); + return; + } + } + spin_unlock_irqrestore(&cs->lock, flags); +} + +static void +W6692Bmode(struct BCState *bcs, int mode, int bchan) +{ + struct IsdnCardState *cs = bcs->cs; + + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "w6692 %c mode %d ichan %d", + '1' + bchan, mode, bchan); + bcs->mode = mode; + bcs->channel = bchan; + bcs->hw.w6692.bchan = bchan; + + switch (mode) { + case (L1_MODE_NULL): + cs->BC_Write_Reg(cs, bchan, W_B_MODE, 0); + break; + case (L1_MODE_TRANS): + cs->BC_Write_Reg(cs, bchan, W_B_MODE, W_B_MODE_MMS); + break; + case (L1_MODE_HDLC): + cs->BC_Write_Reg(cs, bchan, W_B_MODE, W_B_MODE_ITF); + cs->BC_Write_Reg(cs, bchan, W_B_ADM1, 0xff); + cs->BC_Write_Reg(cs, bchan, W_B_ADM2, 0xff); + break; + } + if (mode) + cs->BC_Write_Reg(cs, bchan, W_B_CMDR, W_B_CMDR_RRST | + W_B_CMDR_RACT | W_B_CMDR_XRST); + cs->BC_Write_Reg(cs, bchan, W_B_EXIM, 0x00); +} + +static void +W6692_l2l1(struct PStack *st, int pr, void *arg) +{ + struct sk_buff *skb = arg; + struct BCState *bcs = st->l1.bcs; + u_long flags; + + switch (pr) { + case (PH_DATA | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->tx_skb) { + skb_queue_tail(&bcs->squeue, skb); + } else { + bcs->tx_skb = skb; + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->hw.w6692.count = 0; + bcs->cs->BC_Send_Data(bcs); + } + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | INDICATION): + if (bcs->tx_skb) { + printk(KERN_WARNING "W6692_l2l1: this shouldn't happen\n"); + break; + } + spin_lock_irqsave(&bcs->cs->lock, flags); + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->tx_skb = skb; + bcs->hw.w6692.count = 0; + bcs->cs->BC_Send_Data(bcs); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + break; + case (PH_PULL | REQUEST): + if (!bcs->tx_skb) { + test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } else + test_and_set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + case (PH_ACTIVATE | REQUEST): + spin_lock_irqsave(&bcs->cs->lock, flags); + test_and_set_bit(BC_FLG_ACTIV, &bcs->Flag); + W6692Bmode(bcs, st->l1.mode, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | REQUEST): + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | CONFIRM): + spin_lock_irqsave(&bcs->cs->lock, flags); + test_and_clear_bit(BC_FLG_ACTIV, &bcs->Flag); + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + W6692Bmode(bcs, 0, st->l1.bc); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + st->l1.l1l2(st, PH_DEACTIVATE | CONFIRM, NULL); + break; + } +} + +static void +close_w6692state(struct BCState *bcs) +{ + W6692Bmode(bcs, 0, bcs->channel); + if (test_and_clear_bit(BC_FLG_INIT, &bcs->Flag)) { + if (bcs->hw.w6692.rcvbuf) { + kfree(bcs->hw.w6692.rcvbuf); + bcs->hw.w6692.rcvbuf = NULL; + } + if (bcs->blog) { + kfree(bcs->blog); + bcs->blog = NULL; + } + skb_queue_purge(&bcs->rqueue); + skb_queue_purge(&bcs->squeue); + if (bcs->tx_skb) { + dev_kfree_skb_any(bcs->tx_skb); + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + } + } +} + +static int +open_w6692state(struct IsdnCardState *cs, struct BCState *bcs) +{ + if (!test_and_set_bit(BC_FLG_INIT, &bcs->Flag)) { + if (!(bcs->hw.w6692.rcvbuf = kmalloc(HSCX_BUFMAX, GFP_ATOMIC))) { + printk(KERN_WARNING + "HiSax: No memory for w6692.rcvbuf\n"); + test_and_clear_bit(BC_FLG_INIT, &bcs->Flag); + return (1); + } + if (!(bcs->blog = kmalloc(MAX_BLOG_SPACE, GFP_ATOMIC))) { + printk(KERN_WARNING + "HiSax: No memory for bcs->blog\n"); + test_and_clear_bit(BC_FLG_INIT, &bcs->Flag); + kfree(bcs->hw.w6692.rcvbuf); + bcs->hw.w6692.rcvbuf = NULL; + return (2); + } + skb_queue_head_init(&bcs->rqueue); + skb_queue_head_init(&bcs->squeue); + } + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->event = 0; + bcs->hw.w6692.rcvidx = 0; + bcs->tx_cnt = 0; + return (0); +} + +static int +setstack_w6692(struct PStack *st, struct BCState *bcs) +{ + bcs->channel = st->l1.bc; + if (open_w6692state(st->l1.hardware, bcs)) + return (-1); + st->l1.bcs = bcs; + st->l2.l2l1 = W6692_l2l1; + setstack_manager(st); + bcs->st = st; + setstack_l1_B(st); + return (0); +} + +void resetW6692(struct IsdnCardState *cs) +{ + cs->writeW6692(cs, W_D_CTL, W_D_CTL_SRST); + mdelay(10); + cs->writeW6692(cs, W_D_CTL, 0x00); + mdelay(10); + cs->writeW6692(cs, W_IMASK, 0xff); + cs->writeW6692(cs, W_D_SAM, 0xff); + cs->writeW6692(cs, W_D_TAM, 0xff); + cs->writeW6692(cs, W_D_EXIM, 0x00); + cs->writeW6692(cs, W_D_MODE, W_D_MODE_RACT); + cs->writeW6692(cs, W_IMASK, 0x18); + if (cs->subtyp == W6692_USR) { + /* seems that USR implemented some power control features + * Pin 79 is connected to the oscilator circuit so we + * have to handle it here + */ + cs->writeW6692(cs, W_PCTL, 0x80); + cs->writeW6692(cs, W_XDATA, 0x00); + } +} + +void __init initW6692(struct IsdnCardState *cs, int part) +{ + if (part & 1) { + cs->setstack_d = setstack_W6692; + cs->DC_Close = DC_Close_W6692; + cs->dbusytimer.function = (void *) dbusy_timer_handler; + cs->dbusytimer.data = (long) cs; + init_timer(&cs->dbusytimer); + resetW6692(cs); + ph_command(cs, W_L1CMD_RST); + cs->dc.w6692.ph_state = W_L1CMD_RST; + W6692_new_ph(cs); + ph_command(cs, W_L1CMD_ECK); + + cs->bcs[0].BC_SetStack = setstack_w6692; + cs->bcs[1].BC_SetStack = setstack_w6692; + cs->bcs[0].BC_Close = close_w6692state; + cs->bcs[1].BC_Close = close_w6692state; + W6692Bmode(cs->bcs, 0, 0); + W6692Bmode(cs->bcs + 1, 0, 0); + } + if (part & 2) { + /* Reenable all IRQ */ + cs->writeW6692(cs, W_IMASK, 0x18); + cs->writeW6692(cs, W_D_EXIM, 0x00); + cs->BC_Write_Reg(cs, 0, W_B_EXIM, 0x00); + cs->BC_Write_Reg(cs, 1, W_B_EXIM, 0x00); + /* Reset D-chan receiver and transmitter */ + cs->writeW6692(cs, W_D_CMDR, W_D_CMDR_RRST | W_D_CMDR_XRST); + } +} + +/* Interface functions */ + +static u_char +ReadW6692(struct IsdnCardState *cs, u_char offset) +{ + return (inb(cs->hw.w6692.iobase + offset)); +} + +static void +WriteW6692(struct IsdnCardState *cs, u_char offset, u_char value) +{ + outb(value, cs->hw.w6692.iobase + offset); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + insb(cs->hw.w6692.iobase + W_D_RFIFO, data, size); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + outsb(cs->hw.w6692.iobase + W_D_XFIFO, data, size); +} + +static u_char +ReadW6692B(struct IsdnCardState *cs, int bchan, u_char offset) +{ + return (inb(cs->hw.w6692.iobase + (bchan ? 0x40 : 0) + offset)); +} + +static void +WriteW6692B(struct IsdnCardState *cs, int bchan, u_char offset, u_char value) +{ + outb(value, cs->hw.w6692.iobase + (bchan ? 0x40 : 0) + offset); +} + +static int +w6692_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + switch (mt) { + case CARD_RESET: + resetW6692(cs); + return (0); + case CARD_RELEASE: + cs->writeW6692(cs, W_IMASK, 0xff); + release_region(cs->hw.w6692.iobase, 256); + if (cs->subtyp == W6692_USR) { + cs->writeW6692(cs, W_XDATA, 0x04); + } + return (0); + case CARD_INIT: + initW6692(cs, 3); + return (0); + case CARD_TEST: + return (0); + } + return (0); +} + +static int id_idx ; + +static struct pci_dev *dev_w6692 __initdata = NULL; + +int __init +setup_w6692(struct IsdnCard *card) +{ + struct IsdnCardState *cs = card->cs; + char tmp[64]; + u_char found = 0; + u_char pci_irq = 0; + u_int pci_ioaddr = 0; + + strcpy(tmp, w6692_revision); + printk(KERN_INFO "HiSax: W6692 driver Rev. %s\n", HiSax_getrev(tmp)); + if (cs->typ != ISDN_CTYPE_W6692) + return (0); +#ifdef CONFIG_PCI + while (id_list[id_idx].vendor_id) { + dev_w6692 = pci_find_device(id_list[id_idx].vendor_id, + id_list[id_idx].device_id, + dev_w6692); + if (dev_w6692) { + if (pci_enable_device(dev_w6692)) + continue; + cs->subtyp = id_idx; + break; + } + id_idx++; + } + if (dev_w6692) { + found = 1; + pci_irq = dev_w6692->irq; + /* I think address 0 is allways the configuration area */ + /* and address 1 is the real IO space KKe 03.09.99 */ + pci_ioaddr = pci_resource_start(dev_w6692, 1); + /* USR ISDN PCI card TA need some special handling */ + if (cs->subtyp == W6692_WINBOND) { + if ((W6692_SV_USR == dev_w6692->subsystem_vendor) && + (W6692_SD_USR == dev_w6692->subsystem_device)) { + cs->subtyp = W6692_USR; + } + } + } + if (!found) { + printk(KERN_WARNING "W6692: No PCI card found\n"); + return (0); + } + cs->irq = pci_irq; + if (!cs->irq) { + printk(KERN_WARNING "W6692: No IRQ for PCI card found\n"); + return (0); + } + if (!pci_ioaddr) { + printk(KERN_WARNING "W6692: NO I/O Base Address found\n"); + return (0); + } + cs->hw.w6692.iobase = pci_ioaddr; + printk(KERN_INFO "Found: %s %s, I/O base: 0x%x, irq: %d\n", + id_list[cs->subtyp].vendor_name, id_list[cs->subtyp].card_name, + pci_ioaddr, pci_irq); + if (!request_region(cs->hw.w6692.iobase, 256, id_list[cs->subtyp].card_name)) { + printk(KERN_WARNING + "HiSax: %s I/O ports %x-%x already in use\n", + id_list[cs->subtyp].card_name, + cs->hw.w6692.iobase, + cs->hw.w6692.iobase + 255); + return (0); + } +#else + printk(KERN_WARNING "HiSax: W6692 and NO_PCI_BIOS\n"); + printk(KERN_WARNING "HiSax: W6692 unable to config\n"); + return (0); +#endif /* CONFIG_PCI */ + + printk(KERN_INFO + "HiSax: %s config irq:%d I/O:%x\n", + id_list[cs->subtyp].card_name, cs->irq, + cs->hw.w6692.iobase); + + INIT_WORK(&cs->tqueue, (void *)(void *) W6692_bh, cs); + cs->readW6692 = &ReadW6692; + cs->writeW6692 = &WriteW6692; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->BC_Read_Reg = &ReadW6692B; + cs->BC_Write_Reg = &WriteW6692B; + cs->BC_Send_Data = &W6692B_fill_fifo; + cs->cardmsg = &w6692_card_msg; + cs->irq_func = &W6692_interrupt; + cs->irq_flags |= SA_SHIRQ; + W6692Version(cs, "W6692:"); + printk(KERN_INFO "W6692 ISTA=0x%X\n", ReadW6692(cs, W_ISTA)); + printk(KERN_INFO "W6692 IMASK=0x%X\n", ReadW6692(cs, W_IMASK)); + printk(KERN_INFO "W6692 D_EXIR=0x%X\n", ReadW6692(cs, W_D_EXIR)); + printk(KERN_INFO "W6692 D_EXIM=0x%X\n", ReadW6692(cs, W_D_EXIM)); + printk(KERN_INFO "W6692 D_RSTA=0x%X\n", ReadW6692(cs, W_D_RSTA)); + return (1); +} diff --git a/drivers/isdn/hisax/w6692.h b/drivers/isdn/hisax/w6692.h new file mode 100644 index 000000000000..c79c81e0401f --- /dev/null +++ b/drivers/isdn/hisax/w6692.h @@ -0,0 +1,184 @@ +/* $Id: w6692.h,v 1.4.2.2 2004/01/12 22:52:29 keil Exp $ + * + * Winbond W6692 specific defines + * + * Author Petr Novak + * Copyright by Petr Novak <petr.novak@i.cz> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +/* map W6692 functions to ISAC functions */ +#define readW6692 readisac +#define writeW6692 writeisac +#define readW6692fifo readisacfifo +#define writeW6692fifo writeisacfifo + +/* B-channel FIFO read/write routines */ + +#define READW6692BFIFO(cs,bchan,ptr,count) \ + insb(cs->hw.w6692.iobase+W_B_RFIFO+(bchan?0x40:0),ptr,count) + +#define WRITEW6692BFIFO(cs,bchan,ptr,count) \ + outsb(cs->hw.w6692.iobase+W_B_XFIFO+(bchan?0x40:0),ptr,count) + +/* Specifications of W6692 registers */ + +#define W_D_RFIFO 0x00 /* R */ +#define W_D_XFIFO 0x04 /* W */ +#define W_D_CMDR 0x08 /* W */ +#define W_D_MODE 0x0c /* R/W */ +#define W_D_TIMR 0x10 /* R/W */ +#define W_ISTA 0x14 /* R_clr */ +#define W_IMASK 0x18 /* R/W */ +#define W_D_EXIR 0x1c /* R_clr */ +#define W_D_EXIM 0x20 /* R/W */ +#define W_D_STAR 0x24 /* R */ +#define W_D_RSTA 0x28 /* R */ +#define W_D_SAM 0x2c /* R/W */ +#define W_D_SAP1 0x30 /* R/W */ +#define W_D_SAP2 0x34 /* R/W */ +#define W_D_TAM 0x38 /* R/W */ +#define W_D_TEI1 0x3c /* R/W */ +#define W_D_TEI2 0x40 /* R/W */ +#define W_D_RBCH 0x44 /* R */ +#define W_D_RBCL 0x48 /* R */ +#define W_TIMR2 0x4c /* W */ +#define W_L1_RC 0x50 /* R/W */ +#define W_D_CTL 0x54 /* R/W */ +#define W_CIR 0x58 /* R */ +#define W_CIX 0x5c /* W */ +#define W_SQR 0x60 /* R */ +#define W_SQX 0x64 /* W */ +#define W_PCTL 0x68 /* R/W */ +#define W_MOR 0x6c /* R */ +#define W_MOX 0x70 /* R/W */ +#define W_MOSR 0x74 /* R_clr */ +#define W_MOCR 0x78 /* R/W */ +#define W_GCR 0x7c /* R/W */ + +#define W_B_RFIFO 0x80 /* R */ +#define W_B_XFIFO 0x84 /* W */ +#define W_B_CMDR 0x88 /* W */ +#define W_B_MODE 0x8c /* R/W */ +#define W_B_EXIR 0x90 /* R_clr */ +#define W_B_EXIM 0x94 /* R/W */ +#define W_B_STAR 0x98 /* R */ +#define W_B_ADM1 0x9c /* R/W */ +#define W_B_ADM2 0xa0 /* R/W */ +#define W_B_ADR1 0xa4 /* R/W */ +#define W_B_ADR2 0xa8 /* R/W */ +#define W_B_RBCL 0xac /* R */ +#define W_B_RBCH 0xb0 /* R */ + +#define W_XADDR 0xf4 /* R/W */ +#define W_XDATA 0xf8 /* R/W */ +#define W_EPCTL 0xfc /* W */ + +/* W6692 register bits */ + +#define W_D_CMDR_XRST 0x01 +#define W_D_CMDR_XME 0x02 +#define W_D_CMDR_XMS 0x08 +#define W_D_CMDR_STT 0x10 +#define W_D_CMDR_RRST 0x40 +#define W_D_CMDR_RACK 0x80 + +#define W_D_MODE_RLP 0x01 +#define W_D_MODE_DLP 0x02 +#define W_D_MODE_MFD 0x04 +#define W_D_MODE_TEE 0x08 +#define W_D_MODE_TMS 0x10 +#define W_D_MODE_RACT 0x40 +#define W_D_MODE_MMS 0x80 + +#define W_INT_B2_EXI 0x01 +#define W_INT_B1_EXI 0x02 +#define W_INT_D_EXI 0x04 +#define W_INT_XINT0 0x08 +#define W_INT_XINT1 0x10 +#define W_INT_D_XFR 0x20 +#define W_INT_D_RME 0x40 +#define W_INT_D_RMR 0x80 + +#define W_D_EXI_WEXP 0x01 +#define W_D_EXI_TEXP 0x02 +#define W_D_EXI_ISC 0x04 +#define W_D_EXI_MOC 0x08 +#define W_D_EXI_TIN2 0x10 +#define W_D_EXI_XCOL 0x20 +#define W_D_EXI_XDUN 0x40 +#define W_D_EXI_RDOV 0x80 + +#define W_D_STAR_DRDY 0x10 +#define W_D_STAR_XBZ 0x20 +#define W_D_STAR_XDOW 0x80 + +#define W_D_RSTA_RMB 0x10 +#define W_D_RSTA_CRCE 0x20 +#define W_D_RSTA_RDOV 0x40 + +#define W_D_CTL_SRST 0x20 + +#define W_CIR_SCC 0x80 +#define W_CIR_ICC 0x40 +#define W_CIR_COD_MASK 0x0f + +#define W_B_CMDR_XRST 0x01 +#define W_B_CMDR_XME 0x02 +#define W_B_CMDR_XMS 0x04 +#define W_B_CMDR_RACT 0x20 +#define W_B_CMDR_RRST 0x40 +#define W_B_CMDR_RACK 0x80 + +#define W_B_MODE_FTS0 0x01 +#define W_B_MODE_FTS1 0x02 +#define W_B_MODE_SW56 0x04 +#define W_B_MODE_BSW0 0x08 +#define W_B_MODE_BSW1 0x10 +#define W_B_MODE_EPCM 0x20 +#define W_B_MODE_ITF 0x40 +#define W_B_MODE_MMS 0x80 + +#define W_B_EXI_XDUN 0x01 +#define W_B_EXI_XFR 0x02 +#define W_B_EXI_RDOV 0x10 +#define W_B_EXI_RME 0x20 +#define W_B_EXI_RMR 0x40 + +#define W_B_STAR_XBZ 0x01 +#define W_B_STAR_XDOW 0x04 +#define W_B_STAR_RMB 0x10 +#define W_B_STAR_CRCE 0x20 +#define W_B_STAR_RDOV 0x40 + +#define W_B_RBCH_LOV 0x20 + +/* W6692 Layer1 commands */ + +#define W_L1CMD_ECK 0x00 +#define W_L1CMD_RST 0x01 +#define W_L1CMD_SCP 0x04 +#define W_L1CMD_SSP 0x02 +#define W_L1CMD_AR8 0x08 +#define W_L1CMD_AR10 0x09 +#define W_L1CMD_EAL 0x0a +#define W_L1CMD_DRC 0x0f + +/* W6692 Layer1 indications */ + +#define W_L1IND_CE 0x07 +#define W_L1IND_DRD 0x00 +#define W_L1IND_LD 0x04 +#define W_L1IND_ARD 0x08 +#define W_L1IND_TI 0x0a +#define W_L1IND_ATI 0x0b +#define W_L1IND_AI8 0x0c +#define W_L1IND_AI10 0x0d +#define W_L1IND_CD 0x0f + +/* FIFO thresholds */ +#define W_D_FIFO_THRESH 64 +#define W_B_FIFO_THRESH 64 diff --git a/drivers/isdn/hysdn/Kconfig b/drivers/isdn/hysdn/Kconfig new file mode 100644 index 000000000000..c6d8a7042988 --- /dev/null +++ b/drivers/isdn/hysdn/Kconfig @@ -0,0 +1,18 @@ +# +# Config.in for HYSDN ISDN driver +# +config HYSDN + tristate "Hypercope HYSDN cards (Champ, Ergo, Metro) support (module only)" + depends on m && PROC_FS && PCI && BROKEN_ON_SMP + help + Say Y here if you have one of Hypercope's active PCI ISDN cards + Champ, Ergo and Metro. You will then get a module called hysdn. + Please read the file <file:Documentation/isdn/README.hysdn> for more + information. + +config HYSDN_CAPI + bool "HYSDN CAPI 2.0 support" + depends on HYSDN && ISDN_CAPI + help + Say Y here if you like to use Hypercope's CAPI 2.0 interface. + diff --git a/drivers/isdn/hysdn/Makefile b/drivers/isdn/hysdn/Makefile new file mode 100644 index 000000000000..da63b636267d --- /dev/null +++ b/drivers/isdn/hysdn/Makefile @@ -0,0 +1,11 @@ +# Makefile for the hysdn ISDN device driver + +# Each configuration option enables a list of files. + +obj-$(CONFIG_HYSDN) += hysdn.o + +# Multipart objects. + +hysdn-y := hysdn_procconf.o hysdn_proclog.o boardergo.o \ + hysdn_boot.o hysdn_sched.o hysdn_net.o hysdn_init.o +hysdn-$(CONFIG_HYSDN_CAPI) += hycapi.o diff --git a/drivers/isdn/hysdn/boardergo.c b/drivers/isdn/hysdn/boardergo.c new file mode 100644 index 000000000000..e19a01a305a9 --- /dev/null +++ b/drivers/isdn/hysdn/boardergo.c @@ -0,0 +1,453 @@ +/* $Id: boardergo.c,v 1.5.6.7 2001/11/06 21:58:19 kai Exp $ + * + * Linux driver for HYSDN cards, specific routines for ergo type boards. + * + * Author Werner Cornelius (werner@titro.de) for Hypercope GmbH + * Copyright 1999 by Werner Cornelius (werner@titro.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * As all Linux supported cards Champ2, Ergo and Metro2/4 use the same + * DPRAM interface and layout with only minor differences all related + * stuff is done here, not in separate modules. + * + */ + +#include <linux/config.h> +#include <linux/sched.h> +#include <linux/signal.h> +#include <linux/kernel.h> +#include <linux/ioport.h> +#include <linux/interrupt.h> +#include <linux/vmalloc.h> +#include <linux/delay.h> +#include <asm/io.h> + +#include "hysdn_defs.h" +#include "boardergo.h" + +#define byteout(addr,val) outb(val,addr) +#define bytein(addr) inb(addr) + +/***************************************************/ +/* The cards interrupt handler. Called from system */ +/***************************************************/ +static irqreturn_t +ergo_interrupt(int intno, void *dev_id, struct pt_regs *regs) +{ + hysdn_card *card = dev_id; /* parameter from irq */ + tErgDpram *dpr; + ulong flags; + uchar volatile b; + + if (!card) + return IRQ_NONE; /* error -> spurious interrupt */ + if (!card->irq_enabled) + return IRQ_NONE; /* other device interrupting or irq switched off */ + + save_flags(flags); + cli(); /* no further irqs allowed */ + + if (!(bytein(card->iobase + PCI9050_INTR_REG) & PCI9050_INTR_REG_STAT1)) { + restore_flags(flags); /* restore old state */ + return IRQ_NONE; /* no interrupt requested by E1 */ + } + /* clear any pending ints on the board */ + dpr = card->dpram; + b = dpr->ToPcInt; /* clear for ergo */ + b |= dpr->ToPcIntMetro; /* same for metro */ + b |= dpr->ToHyInt; /* and for champ */ + + /* start kernel task immediately after leaving all interrupts */ + if (!card->hw_lock) + schedule_work(&card->irq_queue); + restore_flags(flags); + return IRQ_HANDLED; +} /* ergo_interrupt */ + +/******************************************************************************/ +/* ergo_irq_bh is the function called by the immediate kernel task list after */ +/* being activated with queue_task and no interrupts active. This task is the */ +/* only one handling data transfer from or to the card after booting. The task */ +/* may be queued from everywhere (interrupts included). */ +/******************************************************************************/ +static void +ergo_irq_bh(hysdn_card * card) +{ + tErgDpram *dpr; + int again; + ulong flags; + + if (card->state != CARD_STATE_RUN) + return; /* invalid call */ + + dpr = card->dpram; /* point to DPRAM */ + + save_flags(flags); + cli(); + if (card->hw_lock) { + restore_flags(flags); /* hardware currently unavailable */ + return; + } + card->hw_lock = 1; /* we now lock the hardware */ + + do { + sti(); /* reenable other ints */ + again = 0; /* assume loop not to be repeated */ + + if (!dpr->ToHyFlag) { + /* we are able to send a buffer */ + + if (hysdn_sched_tx(card, dpr->ToHyBuf, &dpr->ToHySize, &dpr->ToHyChannel, + ERG_TO_HY_BUF_SIZE)) { + dpr->ToHyFlag = 1; /* enable tx */ + again = 1; /* restart loop */ + } + } /* we are able to send a buffer */ + if (dpr->ToPcFlag) { + /* a message has arrived for us, handle it */ + + if (hysdn_sched_rx(card, dpr->ToPcBuf, dpr->ToPcSize, dpr->ToPcChannel)) { + dpr->ToPcFlag = 0; /* we worked the data */ + again = 1; /* restart loop */ + } + } /* a message has arrived for us */ + cli(); /* no further ints */ + if (again) { + dpr->ToHyInt = 1; + dpr->ToPcInt = 1; /* interrupt to E1 for all cards */ + } else + card->hw_lock = 0; /* free hardware again */ + } while (again); /* until nothing more to do */ + + restore_flags(flags); +} /* ergo_irq_bh */ + + +/*********************************************************/ +/* stop the card (hardware reset) and disable interrupts */ +/*********************************************************/ +static void +ergo_stopcard(hysdn_card * card) +{ + ulong flags; + uchar val; + + hysdn_net_release(card); /* first release the net device if existing */ +#ifdef CONFIG_HYSDN_CAPI + hycapi_capi_stop(card); +#endif /* CONFIG_HYSDN_CAPI */ + save_flags(flags); + cli(); + val = bytein(card->iobase + PCI9050_INTR_REG); /* get actual value */ + val &= ~(PCI9050_INTR_REG_ENPCI | PCI9050_INTR_REG_EN1); /* mask irq */ + byteout(card->iobase + PCI9050_INTR_REG, val); + card->irq_enabled = 0; + byteout(card->iobase + PCI9050_USER_IO, PCI9050_E1_RESET); /* reset E1 processor */ + card->state = CARD_STATE_UNUSED; + card->err_log_state = ERRLOG_STATE_OFF; /* currently no log active */ + + restore_flags(flags); +} /* ergo_stopcard */ + +/**************************************************************************/ +/* enable or disable the cards error log. The event is queued if possible */ +/**************************************************************************/ +static void +ergo_set_errlog_state(hysdn_card * card, int on) +{ + ulong flags; + + if (card->state != CARD_STATE_RUN) { + card->err_log_state = ERRLOG_STATE_OFF; /* must be off */ + return; + } + save_flags(flags); + cli(); + + if (((card->err_log_state == ERRLOG_STATE_OFF) && !on) || + ((card->err_log_state == ERRLOG_STATE_ON) && on)) { + restore_flags(flags); + return; /* nothing to do */ + } + if (on) + card->err_log_state = ERRLOG_STATE_START; /* request start */ + else + card->err_log_state = ERRLOG_STATE_STOP; /* request stop */ + + restore_flags(flags); + schedule_work(&card->irq_queue); +} /* ergo_set_errlog_state */ + +/******************************************/ +/* test the cards RAM and return 0 if ok. */ +/******************************************/ +static const char TestText[36] = "This Message is filler, why read it"; + +static int +ergo_testram(hysdn_card * card) +{ + tErgDpram *dpr = card->dpram; + + memset(dpr->TrapTable, 0, sizeof(dpr->TrapTable)); /* clear all Traps */ + dpr->ToHyInt = 1; /* E1 INTR state forced */ + + memcpy(&dpr->ToHyBuf[ERG_TO_HY_BUF_SIZE - sizeof(TestText)], TestText, + sizeof(TestText)); + if (memcmp(&dpr->ToHyBuf[ERG_TO_HY_BUF_SIZE - sizeof(TestText)], TestText, + sizeof(TestText))) + return (-1); + + memcpy(&dpr->ToPcBuf[ERG_TO_PC_BUF_SIZE - sizeof(TestText)], TestText, + sizeof(TestText)); + if (memcmp(&dpr->ToPcBuf[ERG_TO_PC_BUF_SIZE - sizeof(TestText)], TestText, + sizeof(TestText))) + return (-1); + + return (0); +} /* ergo_testram */ + +/*****************************************************************************/ +/* this function is intended to write stage 1 boot image to the cards buffer */ +/* this is done in two steps. First the 1024 hi-words are written (offs=0), */ +/* then the 1024 lo-bytes are written. The remaining DPRAM is cleared, the */ +/* PCI-write-buffers flushed and the card is taken out of reset. */ +/* The function then waits for a reaction of the E1 processor or a timeout. */ +/* Negative return values are interpreted as errors. */ +/*****************************************************************************/ +static int +ergo_writebootimg(struct HYSDN_CARD *card, uchar * buf, ulong offs) +{ + uchar *dst; + tErgDpram *dpram; + int cnt = (BOOT_IMG_SIZE >> 2); /* number of words to move and swap (byte order!) */ + + if (card->debug_flags & LOG_POF_CARD) + hysdn_addlog(card, "ERGO: write bootldr offs=0x%lx ", offs); + + dst = card->dpram; /* pointer to start of DPRAM */ + dst += (offs + ERG_DPRAM_FILL_SIZE); /* offset in the DPRAM */ + while (cnt--) { + *dst++ = *(buf + 1); /* high byte */ + *dst++ = *buf; /* low byte */ + dst += 2; /* point to next longword */ + buf += 2; /* buffer only filled with words */ + } + + /* if low words (offs = 2) have been written, clear the rest of the DPRAM, */ + /* flush the PCI-write-buffer and take the E1 out of reset */ + if (offs) { + memset(card->dpram, 0, ERG_DPRAM_FILL_SIZE); /* fill the DPRAM still not cleared */ + dpram = card->dpram; /* get pointer to dpram structure */ + dpram->ToHyNoDpramErrLog = 0xFF; /* write a dpram register */ + while (!dpram->ToHyNoDpramErrLog); /* reread volatile register to flush PCI */ + + byteout(card->iobase + PCI9050_USER_IO, PCI9050_E1_RUN); /* start E1 processor */ + /* the interrupts are still masked */ + + sti(); + msleep_interruptible(20); /* Timeout 20ms */ + + if (((tDpramBootSpooler *) card->dpram)->Len != DPRAM_SPOOLER_DATA_SIZE) { + if (card->debug_flags & LOG_POF_CARD) + hysdn_addlog(card, "ERGO: write bootldr no answer"); + return (-ERR_BOOTIMG_FAIL); + } + } /* start_boot_img */ + return (0); /* successful */ +} /* ergo_writebootimg */ + +/********************************************************************************/ +/* ergo_writebootseq writes the buffer containing len bytes to the E1 processor */ +/* using the boot spool mechanism. If everything works fine 0 is returned. In */ +/* case of errors a negative error value is returned. */ +/********************************************************************************/ +static int +ergo_writebootseq(struct HYSDN_CARD *card, uchar * buf, int len) +{ + tDpramBootSpooler *sp = (tDpramBootSpooler *) card->dpram; + uchar *dst; + uchar buflen; + int nr_write; + uchar tmp_rdptr; + uchar wr_mirror; + int i; + + if (card->debug_flags & LOG_POF_CARD) + hysdn_addlog(card, "ERGO: write boot seq len=%d ", len); + + dst = sp->Data; /* point to data in spool structure */ + buflen = sp->Len; /* maximum len of spooled data */ + wr_mirror = sp->WrPtr; /* only once read */ + sti(); + + /* try until all bytes written or error */ + i = 0x1000; /* timeout value */ + while (len) { + + /* first determine the number of bytes that may be buffered */ + do { + tmp_rdptr = sp->RdPtr; /* first read the pointer */ + i--; /* decrement timeout */ + } while (i && (tmp_rdptr != sp->RdPtr)); /* wait for stable pointer */ + + if (!i) { + if (card->debug_flags & LOG_POF_CARD) + hysdn_addlog(card, "ERGO: write boot seq timeout"); + return (-ERR_BOOTSEQ_FAIL); /* value not stable -> timeout */ + } + if ((nr_write = tmp_rdptr - wr_mirror - 1) < 0) + nr_write += buflen; /* now we got number of free bytes - 1 in buffer */ + + if (!nr_write) + continue; /* no free bytes in buffer */ + + if (nr_write > len) + nr_write = len; /* limit if last few bytes */ + i = 0x1000; /* reset timeout value */ + + /* now we know how much bytes we may put in the puffer */ + len -= nr_write; /* we savely could adjust len before output */ + while (nr_write--) { + *(dst + wr_mirror) = *buf++; /* output one byte */ + if (++wr_mirror >= buflen) + wr_mirror = 0; + sp->WrPtr = wr_mirror; /* announce the next byte to E1 */ + } /* while (nr_write) */ + + } /* while (len) */ + return (0); +} /* ergo_writebootseq */ + +/***********************************************************************************/ +/* ergo_waitpofready waits for a maximum of 10 seconds for the completition of the */ +/* boot process. If the process has been successful 0 is returned otherwise a */ +/* negative error code is returned. */ +/***********************************************************************************/ +static int +ergo_waitpofready(struct HYSDN_CARD *card) +{ + tErgDpram *dpr = card->dpram; /* pointer to DPRAM structure */ + int timecnt = 10000 / 50; /* timeout is 10 secs max. */ + ulong flags; + int msg_size; + int i; + + if (card->debug_flags & LOG_POF_CARD) + hysdn_addlog(card, "ERGO: waiting for pof ready"); + while (timecnt--) { + /* wait until timeout */ + + if (dpr->ToPcFlag) { + /* data has arrived */ + + if ((dpr->ToPcChannel != CHAN_SYSTEM) || + (dpr->ToPcSize < MIN_RDY_MSG_SIZE) || + (dpr->ToPcSize > MAX_RDY_MSG_SIZE) || + ((*(ulong *) dpr->ToPcBuf) != RDY_MAGIC)) + break; /* an error occurred */ + + /* Check for additional data delivered during SysReady */ + msg_size = dpr->ToPcSize - RDY_MAGIC_SIZE; + if (msg_size > 0) + if (EvalSysrTokData(card, dpr->ToPcBuf + RDY_MAGIC_SIZE, msg_size)) + break; + + if (card->debug_flags & LOG_POF_RECORD) + hysdn_addlog(card, "ERGO: pof boot success"); + save_flags(flags); + cli(); + + card->state = CARD_STATE_RUN; /* now card is running */ + /* enable the cards interrupt */ + byteout(card->iobase + PCI9050_INTR_REG, + bytein(card->iobase + PCI9050_INTR_REG) | + (PCI9050_INTR_REG_ENPCI | PCI9050_INTR_REG_EN1)); + card->irq_enabled = 1; /* we are ready to receive interrupts */ + + dpr->ToPcFlag = 0; /* reset data indicator */ + dpr->ToHyInt = 1; + dpr->ToPcInt = 1; /* interrupt to E1 for all cards */ + + restore_flags(flags); + if ((hynet_enable & (1 << card->myid)) + && (i = hysdn_net_create(card))) + { + ergo_stopcard(card); + card->state = CARD_STATE_BOOTERR; + return (i); + } +#ifdef CONFIG_HYSDN_CAPI + if((i = hycapi_capi_create(card))) { + printk(KERN_WARNING "HYSDN: failed to create capi-interface.\n"); + } +#endif /* CONFIG_HYSDN_CAPI */ + return (0); /* success */ + } /* data has arrived */ + sti(); + msleep_interruptible(50); /* Timeout 50ms */ + } /* wait until timeout */ + + if (card->debug_flags & LOG_POF_CARD) + hysdn_addlog(card, "ERGO: pof boot ready timeout"); + return (-ERR_POF_TIMEOUT); +} /* ergo_waitpofready */ + + + +/************************************************************************************/ +/* release the cards hardware. Before releasing do a interrupt disable and hardware */ +/* reset. Also unmap dpram. */ +/* Use only during module release. */ +/************************************************************************************/ +static void +ergo_releasehardware(hysdn_card * card) +{ + ergo_stopcard(card); /* first stop the card if not already done */ + free_irq(card->irq, card); /* release interrupt */ + release_region(card->iobase + PCI9050_INTR_REG, 1); /* release all io ports */ + release_region(card->iobase + PCI9050_USER_IO, 1); + vfree(card->dpram); + card->dpram = NULL; /* release shared mem */ +} /* ergo_releasehardware */ + + +/*********************************************************************************/ +/* acquire the needed hardware ports and map dpram. If an error occurs a nonzero */ +/* value is returned. */ +/* Use only during module init. */ +/*********************************************************************************/ +int +ergo_inithardware(hysdn_card * card) +{ + if (!request_region(card->iobase + PCI9050_INTR_REG, 1, "HYSDN")) + return (-1); + if (!request_region(card->iobase + PCI9050_USER_IO, 1, "HYSDN")) { + release_region(card->iobase + PCI9050_INTR_REG, 1); + return (-1); /* ports already in use */ + } + card->memend = card->membase + ERG_DPRAM_PAGE_SIZE - 1; + if (!(card->dpram = ioremap(card->membase, ERG_DPRAM_PAGE_SIZE))) { + release_region(card->iobase + PCI9050_INTR_REG, 1); + release_region(card->iobase + PCI9050_USER_IO, 1); + return (-1); + } + + ergo_stopcard(card); /* disable interrupts */ + if (request_irq(card->irq, ergo_interrupt, SA_SHIRQ, "HYSDN", card)) { + ergo_releasehardware(card); /* return the acquired hardware */ + return (-1); + } + /* success, now setup the function pointers */ + card->stopcard = ergo_stopcard; + card->releasehardware = ergo_releasehardware; + card->testram = ergo_testram; + card->writebootimg = ergo_writebootimg; + card->writebootseq = ergo_writebootseq; + card->waitpofready = ergo_waitpofready; + card->set_errlog_state = ergo_set_errlog_state; + INIT_WORK(&card->irq_queue, (void *) (void *) ergo_irq_bh, card); + + return (0); +} /* ergo_inithardware */ diff --git a/drivers/isdn/hysdn/boardergo.h b/drivers/isdn/hysdn/boardergo.h new file mode 100644 index 000000000000..b56ff0889ead --- /dev/null +++ b/drivers/isdn/hysdn/boardergo.h @@ -0,0 +1,100 @@ +/* $Id: boardergo.h,v 1.2.6.1 2001/09/23 22:24:54 kai Exp $ + * + * Linux driver for HYSDN cards, definitions for ergo type boards (buffers..). + * + * Author Werner Cornelius (werner@titro.de) for Hypercope GmbH + * Copyright 1999 by Werner Cornelius (werner@titro.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + + +/************************************************/ +/* defines for the dual port memory of the card */ +/************************************************/ +#define ERG_DPRAM_PAGE_SIZE 0x2000 /* DPRAM occupies a 8K page */ +#define BOOT_IMG_SIZE 4096 +#define ERG_DPRAM_FILL_SIZE (ERG_DPRAM_PAGE_SIZE - BOOT_IMG_SIZE) + +#define ERG_TO_HY_BUF_SIZE 0x0E00 /* 3072 bytes buffer size to card */ +#define ERG_TO_PC_BUF_SIZE 0x0E00 /* 3072 bytes to PC, too */ + +/* following DPRAM layout copied from OS2-driver boarderg.h */ +typedef struct ErgDpram_tag { +/*0000 */ uchar ToHyBuf[ERG_TO_HY_BUF_SIZE]; +/*0E00 */ uchar ToPcBuf[ERG_TO_PC_BUF_SIZE]; + + /*1C00 */ uchar bSoftUart[SIZE_RSV_SOFT_UART]; + /* size 0x1B0 */ + + /*1DB0 *//* tErrLogEntry */ uchar volatile ErrLogMsg[64]; + /* size 64 bytes */ + /*1DB0 ulong ulErrType; */ + /*1DB4 ulong ulErrSubtype; */ + /*1DB8 ulong ucTextSize; */ + /*1DB9 ulong ucText[ERRLOG_TEXT_SIZE]; *//* ASCIIZ of len ucTextSize-1 */ + /*1DF0 */ + +/*1DF0 */ word volatile ToHyChannel; +/*1DF2 */ word volatile ToHySize; + /*1DF4 */ uchar volatile ToHyFlag; + /* !=0: msg for Hy waiting */ + /*1DF5 */ uchar volatile ToPcFlag; + /* !=0: msg for PC waiting */ +/*1DF6 */ word volatile ToPcChannel; +/*1DF8 */ word volatile ToPcSize; + /*1DFA */ uchar bRes1DBA[0x1E00 - 0x1DFA]; + /* 6 bytes */ + +/*1E00 */ uchar bRestOfEntryTbl[0x1F00 - 0x1E00]; +/*1F00 */ ulong TrapTable[62]; + /*1FF8 */ uchar bRes1FF8[0x1FFB - 0x1FF8]; + /* low part of reset vetor */ +/*1FFB */ uchar ToPcIntMetro; + /* notes: + * - metro has 32-bit boot ram - accessing + * ToPcInt and ToHyInt would be the same; + * so we moved ToPcInt to 1FFB. + * Because on the PC side both vars are + * readonly (reseting on int from E1 to PC), + * we can read both vars on both cards + * without destroying anything. + * - 1FFB is the high byte of the reset vector, + * so E1 side should NOT change this byte + * when writing! + */ +/*1FFC */ uchar volatile ToHyNoDpramErrLog; + /* note: ToHyNoDpramErrLog is used to inform + * boot loader, not to use DPRAM based + * ErrLog; when DOS driver is rewritten + * this becomes obsolete + */ +/*1FFD */ uchar bRes1FFD; + /*1FFE */ uchar ToPcInt; + /* E1_intclear; on CHAMP2: E1_intset */ + /*1FFF */ uchar ToHyInt; + /* E1_intset; on CHAMP2: E1_intclear */ +} tErgDpram; + +/**********************************************/ +/* PCI9050 controller local register offsets: */ +/* copied from boarderg.c */ +/**********************************************/ +#define PCI9050_INTR_REG 0x4C /* Interrupt register */ +#define PCI9050_USER_IO 0x51 /* User I/O register */ + + /* bitmask for PCI9050_INTR_REG: */ +#define PCI9050_INTR_REG_EN1 0x01 /* 1= enable (def.), 0= disable */ +#define PCI9050_INTR_REG_POL1 0x02 /* 1= active high (def.), 0= active low */ +#define PCI9050_INTR_REG_STAT1 0x04 /* 1= intr. active, 0= intr. not active (def.) */ +#define PCI9050_INTR_REG_ENPCI 0x40 /* 1= PCI interrupts enable (def.) */ + + /* bitmask for PCI9050_USER_IO: */ +#define PCI9050_USER_IO_EN3 0x02 /* 1= disable , 0= enable (def.) */ +#define PCI9050_USER_IO_DIR3 0x04 /* 1= output (def.), 0= input */ +#define PCI9050_USER_IO_DAT3 0x08 /* 1= high (def.) , 0= low */ + +#define PCI9050_E1_RESET ( PCI9050_USER_IO_DIR3) /* 0x04 */ +#define PCI9050_E1_RUN (PCI9050_USER_IO_DAT3|PCI9050_USER_IO_DIR3) /* 0x0C */ diff --git a/drivers/isdn/hysdn/hycapi.c b/drivers/isdn/hysdn/hycapi.c new file mode 100644 index 000000000000..8ee25b2ccce1 --- /dev/null +++ b/drivers/isdn/hysdn/hycapi.c @@ -0,0 +1,797 @@ +/* $Id: hycapi.c,v 1.8.6.4 2001/09/23 22:24:54 kai Exp $ + * + * Linux driver for HYSDN cards, CAPI2.0-Interface. + * + * Author Ulrich Albrecht <u.albrecht@hypercope.de> for Hypercope GmbH + * Copyright 2000 by Hypercope GmbH + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/module.h> +#include <linux/version.h> +#include <linux/signal.h> +#include <linux/kernel.h> +#include <linux/skbuff.h> +#include <linux/netdevice.h> + +#define VER_DRIVER 0 +#define VER_CARDTYPE 1 +#define VER_HWID 2 +#define VER_SERIAL 3 +#define VER_OPTION 4 +#define VER_PROTO 5 +#define VER_PROFILE 6 +#define VER_CAPI 7 + +#include "hysdn_defs.h" +#include <linux/kernelcapi.h> + +static char hycapi_revision[]="$Revision: 1.8.6.4 $"; + +unsigned int hycapi_enable = 0xffffffff; +MODULE_PARM(hycapi_enable, "i"); + +typedef struct _hycapi_appl { + unsigned int ctrl_mask; + capi_register_params rp; + struct sk_buff *listen_req[CAPI_MAXCONTR]; +} hycapi_appl; + +static hycapi_appl hycapi_applications[CAPI_MAXAPPL]; + +static inline int _hycapi_appCheck(int app_id, int ctrl_no) +{ + if((ctrl_no <= 0) || (ctrl_no > CAPI_MAXCONTR) || (app_id <= 0) || + (app_id > CAPI_MAXAPPL)) + { + printk(KERN_ERR "HYCAPI: Invalid request app_id %d for controller %d", app_id, ctrl_no); + return -1; + } + return ((hycapi_applications[app_id-1].ctrl_mask & (1 << (ctrl_no-1))) != 0); +} + +/****************************** +Kernel-Capi callback reset_ctr +******************************/ + +void +hycapi_reset_ctr(struct capi_ctr *ctrl) +{ + hycapictrl_info *cinfo = ctrl->driverdata; + +#ifdef HYCAPI_PRINTFNAMES + printk(KERN_NOTICE "HYCAPI hycapi_reset_ctr\n"); +#endif + capilib_release(&cinfo->ncci_head); + capi_ctr_reseted(ctrl); +} + +/****************************** +Kernel-Capi callback remove_ctr +******************************/ + +void +hycapi_remove_ctr(struct capi_ctr *ctrl) +{ + int i; + hycapictrl_info *cinfo = NULL; + hysdn_card *card = NULL; +#ifdef HYCAPI_PRINTFNAMES + printk(KERN_NOTICE "HYCAPI hycapi_remove_ctr\n"); +#endif + cinfo = (hycapictrl_info *)(ctrl->driverdata); + if(!cinfo) { + printk(KERN_ERR "No hycapictrl_info set!"); + return; + } + card = cinfo->card; + capi_ctr_suspend_output(ctrl); + for(i=0; i<CAPI_MAXAPPL;i++) { + if(hycapi_applications[i].listen_req[ctrl->cnr-1]) { + kfree_skb(hycapi_applications[i].listen_req[ctrl->cnr-1]); + hycapi_applications[i].listen_req[ctrl->cnr-1] = NULL; + } + } + detach_capi_ctr(ctrl); + ctrl->driverdata = NULL; + kfree(card->hyctrlinfo); + + + card->hyctrlinfo = NULL; +} + +/*********************************************************** + +Queue a CAPI-message to the controller. + +***********************************************************/ + +static void +hycapi_sendmsg_internal(struct capi_ctr *ctrl, struct sk_buff *skb) +{ + hycapictrl_info *cinfo = (hycapictrl_info *)(ctrl->driverdata); + hysdn_card *card = cinfo->card; + + spin_lock_irq(&cinfo->lock); +#ifdef HYCAPI_PRINTFNAMES + printk(KERN_NOTICE "hycapi_send_message\n"); +#endif + cinfo->skbs[cinfo->in_idx++] = skb; /* add to buffer list */ + if (cinfo->in_idx >= HYSDN_MAX_CAPI_SKB) + cinfo->in_idx = 0; /* wrap around */ + cinfo->sk_count++; /* adjust counter */ + if (cinfo->sk_count >= HYSDN_MAX_CAPI_SKB) { + /* inform upper layers we're full */ + printk(KERN_ERR "HYSDN Card%d: CAPI-buffer overrun!\n", + card->myid); + capi_ctr_suspend_output(ctrl); + } + cinfo->tx_skb = skb; + spin_unlock_irq(&cinfo->lock); + schedule_work(&card->irq_queue); +} + +/*********************************************************** +hycapi_register_internal + +Send down the CAPI_REGISTER-Command to the controller. +This functions will also be used if the adapter has been rebooted to +re-register any applications in the private list. + +************************************************************/ + +static void +hycapi_register_internal(struct capi_ctr *ctrl, __u16 appl, + capi_register_params *rp) +{ + char ExtFeatureDefaults[] = "49 /0/0/0/0,*/1,*/2,*/3,*/4,*/5,*/6,*/7,*/8,*/9,*"; + hycapictrl_info *cinfo = (hycapictrl_info *)(ctrl->driverdata); + hysdn_card *card = cinfo->card; + struct sk_buff *skb; + __u16 len; + __u8 _command = 0xa0, _subcommand = 0x80; + __u16 MessageNumber = 0x0000; + __u16 MessageBufferSize = 0; + int slen = strlen(ExtFeatureDefaults); +#ifdef HYCAPI_PRINTFNAMES + printk(KERN_NOTICE "hycapi_register_appl\n"); +#endif + MessageBufferSize = rp->level3cnt * rp->datablkcnt * rp->datablklen; + + len = CAPI_MSG_BASELEN + 8 + slen + 1; + if (!(skb = alloc_skb(len, GFP_ATOMIC))) { + printk(KERN_ERR "HYSDN card%d: memory squeeze in hycapi_register_appl\n", + card->myid); + return; + } + memcpy(skb_put(skb,sizeof(__u16)), &len, sizeof(__u16)); + memcpy(skb_put(skb,sizeof(__u16)), &appl, sizeof(__u16)); + memcpy(skb_put(skb,sizeof(__u8)), &_command, sizeof(_command)); + memcpy(skb_put(skb,sizeof(__u8)), &_subcommand, sizeof(_subcommand)); + memcpy(skb_put(skb,sizeof(__u16)), &MessageNumber, sizeof(__u16)); + memcpy(skb_put(skb,sizeof(__u16)), &MessageBufferSize, sizeof(__u16)); + memcpy(skb_put(skb,sizeof(__u16)), &(rp->level3cnt), sizeof(__u16)); + memcpy(skb_put(skb,sizeof(__u16)), &(rp->datablkcnt), sizeof(__u16)); + memcpy(skb_put(skb,sizeof(__u16)), &(rp->datablklen), sizeof(__u16)); + memcpy(skb_put(skb,slen), ExtFeatureDefaults, slen); + hycapi_applications[appl-1].ctrl_mask |= (1 << (ctrl->cnr-1)); + hycapi_send_message(ctrl, skb); +} + +/************************************************************ +hycapi_restart_internal + +After an adapter has been rebootet, re-register all applications and +send a LISTEN_REQ (if there has been such a thing ) + +*************************************************************/ + +static void hycapi_restart_internal(struct capi_ctr *ctrl) +{ + int i; + struct sk_buff *skb; +#ifdef HYCAPI_PRINTFNAMES + printk(KERN_WARNING "HYSDN: hycapi_restart_internal"); +#endif + for(i=0; i<CAPI_MAXAPPL; i++) { + if(_hycapi_appCheck(i+1, ctrl->cnr) == 1) { + hycapi_register_internal(ctrl, i+1, + &hycapi_applications[i].rp); + if(hycapi_applications[i].listen_req[ctrl->cnr-1]) { + skb = skb_copy(hycapi_applications[i].listen_req[ctrl->cnr-1], GFP_ATOMIC); + hycapi_sendmsg_internal(ctrl, skb); + } + } + } +} + +/************************************************************* +Register an application. +Error-checking is done for CAPI-compliance. + +The application is recorded in the internal list. +*************************************************************/ + +void +hycapi_register_appl(struct capi_ctr *ctrl, __u16 appl, + capi_register_params *rp) +{ + int MaxLogicalConnections = 0, MaxBDataBlocks = 0, MaxBDataLen = 0; + hycapictrl_info *cinfo = (hycapictrl_info *)(ctrl->driverdata); + hysdn_card *card = cinfo->card; + int chk = _hycapi_appCheck(appl, ctrl->cnr); + if(chk < 0) { + return; + } + if(chk == 1) { + printk(KERN_INFO "HYSDN: apl %d already registered\n", appl); + return; + } + MaxBDataBlocks = rp->datablkcnt > CAPI_MAXDATAWINDOW ? CAPI_MAXDATAWINDOW : rp->datablkcnt; + rp->datablkcnt = MaxBDataBlocks; + MaxBDataLen = rp->datablklen < 1024 ? 1024 : rp->datablklen ; + rp->datablklen = MaxBDataLen; + + MaxLogicalConnections = rp->level3cnt; + if (MaxLogicalConnections < 0) { + MaxLogicalConnections = card->bchans * -MaxLogicalConnections; + } + if (MaxLogicalConnections == 0) { + MaxLogicalConnections = card->bchans; + } + + rp->level3cnt = MaxLogicalConnections; + memcpy(&hycapi_applications[appl-1].rp, + rp, sizeof(capi_register_params)); +} + +/********************************************************************* + +hycapi_release_internal + +Send down a CAPI_RELEASE to the controller. +*********************************************************************/ + +static void hycapi_release_internal(struct capi_ctr *ctrl, __u16 appl) +{ + hycapictrl_info *cinfo = (hycapictrl_info *)(ctrl->driverdata); + hysdn_card *card = cinfo->card; + struct sk_buff *skb; + __u16 len; + __u8 _command = 0xa1, _subcommand = 0x80; + __u16 MessageNumber = 0x0000; + + capilib_release_appl(&cinfo->ncci_head, appl); + +#ifdef HYCAPI_PRINTFNAMES + printk(KERN_NOTICE "hycapi_release_appl\n"); +#endif + len = CAPI_MSG_BASELEN; + if (!(skb = alloc_skb(len, GFP_ATOMIC))) { + printk(KERN_ERR "HYSDN card%d: memory squeeze in hycapi_register_appl\n", + card->myid); + return; + } + memcpy(skb_put(skb,sizeof(__u16)), &len, sizeof(__u16)); + memcpy(skb_put(skb,sizeof(__u16)), &appl, sizeof(__u16)); + memcpy(skb_put(skb,sizeof(__u8)), &_command, sizeof(_command)); + memcpy(skb_put(skb,sizeof(__u8)), &_subcommand, sizeof(_subcommand)); + memcpy(skb_put(skb,sizeof(__u16)), &MessageNumber, sizeof(__u16)); + hycapi_send_message(ctrl, skb); + hycapi_applications[appl-1].ctrl_mask &= ~(1 << (ctrl->cnr-1)); +} + +/****************************************************************** +hycapi_release_appl + +Release the application from the internal list an remove it's +registration at controller-level +******************************************************************/ + +void +hycapi_release_appl(struct capi_ctr *ctrl, __u16 appl) +{ + int chk; + + chk = _hycapi_appCheck(appl, ctrl->cnr); + if(chk<0) { + printk(KERN_ERR "HYCAPI: Releasing invalid appl %d on controller %d\n", appl, ctrl->cnr); + return; + } + if(hycapi_applications[appl-1].listen_req[ctrl->cnr-1]) { + kfree_skb(hycapi_applications[appl-1].listen_req[ctrl->cnr-1]); + hycapi_applications[appl-1].listen_req[ctrl->cnr-1] = NULL; + } + if(chk == 1) + { + hycapi_release_internal(ctrl, appl); + } +} + + +/************************************************************** +Kill a single controller. +**************************************************************/ + +int hycapi_capi_release(hysdn_card *card) +{ + hycapictrl_info *cinfo = card->hyctrlinfo; + struct capi_ctr *ctrl; +#ifdef HYCAPI_PRINTFNAMES + printk(KERN_NOTICE "hycapi_capi_release\n"); +#endif + if(cinfo) { + ctrl = &cinfo->capi_ctrl; + hycapi_remove_ctr(ctrl); + } + return 0; +} + +/************************************************************** +hycapi_capi_stop + +Stop CAPI-Output on a card. (e.g. during reboot) +***************************************************************/ + +int hycapi_capi_stop(hysdn_card *card) +{ + hycapictrl_info *cinfo = card->hyctrlinfo; + struct capi_ctr *ctrl; +#ifdef HYCAPI_PRINTFNAMES + printk(KERN_NOTICE "hycapi_capi_stop\n"); +#endif + if(cinfo) { + ctrl = &cinfo->capi_ctrl; +/* ctrl->suspend_output(ctrl); */ + capi_ctr_reseted(ctrl); + } + return 0; +} + +/*************************************************************** +hycapi_send_message + +Send a message to the controller. + +Messages are parsed for their Command/Subcommand-type, and appropriate +action's are performed. + +Note that we have to muck around with a 64Bit-DATA_REQ as there are +firmware-releases that do not check the MsgLen-Indication! + +***************************************************************/ + +u16 hycapi_send_message(struct capi_ctr *ctrl, struct sk_buff *skb) +{ + __u16 appl_id; + int _len, _len2; + __u8 msghead[64]; + hycapictrl_info *cinfo = ctrl->driverdata; + u16 retval = CAPI_NOERROR; + + appl_id = CAPIMSG_APPID(skb->data); + switch(_hycapi_appCheck(appl_id, ctrl->cnr)) + { + case 0: +/* printk(KERN_INFO "Need to register\n"); */ + hycapi_register_internal(ctrl, + appl_id, + &(hycapi_applications[appl_id-1].rp)); + break; + case 1: + break; + default: + printk(KERN_ERR "HYCAPI: Controller mixup!\n"); + retval = CAPI_ILLAPPNR; + goto out; + } + switch(CAPIMSG_CMD(skb->data)) { + case CAPI_DISCONNECT_B3_RESP: + capilib_free_ncci(&cinfo->ncci_head, appl_id, + CAPIMSG_NCCI(skb->data)); + break; + case CAPI_DATA_B3_REQ: + _len = CAPIMSG_LEN(skb->data); + if (_len > 22) { + _len2 = _len - 22; + memcpy(msghead, skb->data, 22); + memcpy(skb->data + _len2, msghead, 22); + skb_pull(skb, _len2); + CAPIMSG_SETLEN(skb->data, 22); + retval = capilib_data_b3_req(&cinfo->ncci_head, + CAPIMSG_APPID(skb->data), + CAPIMSG_NCCI(skb->data), + CAPIMSG_MSGID(skb->data)); + } + break; + case CAPI_LISTEN_REQ: + if(hycapi_applications[appl_id-1].listen_req[ctrl->cnr-1]) + { + kfree_skb(hycapi_applications[appl_id-1].listen_req[ctrl->cnr-1]); + hycapi_applications[appl_id-1].listen_req[ctrl->cnr-1] = NULL; + } + if (!(hycapi_applications[appl_id-1].listen_req[ctrl->cnr-1] = skb_copy(skb, GFP_ATOMIC))) + { + printk(KERN_ERR "HYSDN: memory squeeze in private_listen\n"); + } + break; + default: + break; + } + out: + if (retval == CAPI_NOERROR) + hycapi_sendmsg_internal(ctrl, skb); + else + dev_kfree_skb_any(skb); + + return retval; +} + +/********************************************************************* +hycapi_read_proc + +Informations provided in the /proc/capi-entries. + +*********************************************************************/ + +int hycapi_read_proc(char *page, char **start, off_t off, + int count, int *eof, struct capi_ctr *ctrl) +{ + hycapictrl_info *cinfo = (hycapictrl_info *)(ctrl->driverdata); + hysdn_card *card = cinfo->card; + int len = 0; + char *s; +#ifdef HYCAPI_PRINTFNAMES + printk(KERN_NOTICE "hycapi_read_proc\n"); +#endif + len += sprintf(page+len, "%-16s %s\n", "name", cinfo->cardname); + len += sprintf(page+len, "%-16s 0x%x\n", "io", card->iobase); + len += sprintf(page+len, "%-16s %d\n", "irq", card->irq); + + switch (card->brdtype) { + case BD_PCCARD: s = "HYSDN Hycard"; break; + case BD_ERGO: s = "HYSDN Ergo2"; break; + case BD_METRO: s = "HYSDN Metro4"; break; + case BD_CHAMP2: s = "HYSDN Champ2"; break; + case BD_PLEXUS: s = "HYSDN Plexus30"; break; + default: s = "???"; break; + } + len += sprintf(page+len, "%-16s %s\n", "type", s); + if ((s = cinfo->version[VER_DRIVER]) != 0) + len += sprintf(page+len, "%-16s %s\n", "ver_driver", s); + if ((s = cinfo->version[VER_CARDTYPE]) != 0) + len += sprintf(page+len, "%-16s %s\n", "ver_cardtype", s); + if ((s = cinfo->version[VER_SERIAL]) != 0) + len += sprintf(page+len, "%-16s %s\n", "ver_serial", s); + + len += sprintf(page+len, "%-16s %s\n", "cardname", cinfo->cardname); + + if (off+count >= len) + *eof = 1; + if (len < off) + return 0; + *start = page + off; + return ((count < len-off) ? count : len-off); +} + +/************************************************************** +hycapi_load_firmware + +This does NOT load any firmware, but the callback somehow is needed +on capi-interface registration. + +**************************************************************/ + +int hycapi_load_firmware(struct capi_ctr *ctrl, capiloaddata *data) +{ +#ifdef HYCAPI_PRINTFNAMES + printk(KERN_NOTICE "hycapi_load_firmware\n"); +#endif + return 0; +} + + +char *hycapi_procinfo(struct capi_ctr *ctrl) +{ + hycapictrl_info *cinfo = (hycapictrl_info *)(ctrl->driverdata); +#ifdef HYCAPI_PRINTFNAMES + printk(KERN_NOTICE "hycapi_proc_info\n"); +#endif + if (!cinfo) + return ""; + sprintf(cinfo->infobuf, "%s %s 0x%x %d %s", + cinfo->cardname[0] ? cinfo->cardname : "-", + cinfo->version[VER_DRIVER] ? cinfo->version[VER_DRIVER] : "-", + cinfo->card ? cinfo->card->iobase : 0x0, + cinfo->card ? cinfo->card->irq : 0, + hycapi_revision + ); + return cinfo->infobuf; +} + +/****************************************************************** +hycapi_rx_capipkt + +Receive a capi-message. + +All B3_DATA_IND are converted to 64K-extension compatible format. +New nccis are created if necessary. +*******************************************************************/ + +void +hycapi_rx_capipkt(hysdn_card * card, uchar * buf, word len) +{ + struct sk_buff *skb; + hycapictrl_info *cinfo = card->hyctrlinfo; + struct capi_ctr *ctrl; + __u16 ApplId; + __u16 MsgLen, info; + __u16 len2, CapiCmd; + __u32 CP64[2] = {0,0}; +#ifdef HYCAPI_PRINTFNAMES + printk(KERN_NOTICE "hycapi_rx_capipkt\n"); +#endif + if(!cinfo) { + return; + } + ctrl = &cinfo->capi_ctrl; + if(len < CAPI_MSG_BASELEN) { + printk(KERN_ERR "HYSDN Card%d: invalid CAPI-message, lenght %d!\n", + card->myid, len); + return; + } + MsgLen = CAPIMSG_LEN(buf); + ApplId = CAPIMSG_APPID(buf); + CapiCmd = CAPIMSG_CMD(buf); + + if((CapiCmd == CAPI_DATA_B3_IND) && (MsgLen < 30)) { + len2 = len + (30 - MsgLen); + if (!(skb = alloc_skb(len2, GFP_ATOMIC))) { + printk(KERN_ERR "HYSDN Card%d: incoming packet dropped\n", + card->myid); + return; + } + memcpy(skb_put(skb, MsgLen), buf, MsgLen); + memcpy(skb_put(skb, 2*sizeof(__u32)), CP64, 2* sizeof(__u32)); + memcpy(skb_put(skb, len - MsgLen), buf + MsgLen, + len - MsgLen); + CAPIMSG_SETLEN(skb->data, 30); + } else { + if (!(skb = alloc_skb(len, GFP_ATOMIC))) { + printk(KERN_ERR "HYSDN Card%d: incoming packet dropped\n", + card->myid); + return; + } + memcpy(skb_put(skb, len), buf, len); + } + switch(CAPIMSG_CMD(skb->data)) + { + case CAPI_CONNECT_B3_CONF: +/* Check info-field for error-indication: */ + info = CAPIMSG_U16(skb->data, 12); + switch(info) + { + case 0: + capilib_new_ncci(&cinfo->ncci_head, ApplId, CAPIMSG_NCCI(skb->data), + hycapi_applications[ApplId-1].rp.datablkcnt); + + break; + case 0x0001: + printk(KERN_ERR "HYSDN Card%d: NCPI not supported by current " + "protocol. NCPI ignored.\n", card->myid); + break; + case 0x2001: + printk(KERN_ERR "HYSDN Card%d: Message not supported in" + " current state\n", card->myid); + break; + case 0x2002: + printk(KERN_ERR "HYSDN Card%d: invalid PLCI\n", card->myid); + break; + case 0x2004: + printk(KERN_ERR "HYSDN Card%d: out of NCCI\n", card->myid); + break; + case 0x3008: + printk(KERN_ERR "HYSDN Card%d: NCPI not supported\n", + card->myid); + break; + default: + printk(KERN_ERR "HYSDN Card%d: Info in CONNECT_B3_CONF: %d\n", + card->myid, info); + break; + } + break; + case CAPI_CONNECT_B3_IND: + capilib_new_ncci(&cinfo->ncci_head, ApplId, + CAPIMSG_NCCI(skb->data), + hycapi_applications[ApplId-1].rp.datablkcnt); + break; + case CAPI_DATA_B3_CONF: + capilib_data_b3_conf(&cinfo->ncci_head, ApplId, + CAPIMSG_NCCI(skb->data), + CAPIMSG_MSGID(skb->data)); + break; + default: + break; + } + capi_ctr_handle_message(ctrl, ApplId, skb); +} + +/****************************************************************** +hycapi_tx_capiack + +Internally acknowledge a msg sent. This will remove the msg from the +internal queue. + +*******************************************************************/ + +void hycapi_tx_capiack(hysdn_card * card) +{ + hycapictrl_info *cinfo = card->hyctrlinfo; +#ifdef HYCAPI_PRINTFNAMES + printk(KERN_NOTICE "hycapi_tx_capiack\n"); +#endif + if(!cinfo) { + return; + } + spin_lock_irq(&cinfo->lock); + kfree_skb(cinfo->skbs[cinfo->out_idx]); /* free skb */ + cinfo->skbs[cinfo->out_idx++] = NULL; + if (cinfo->out_idx >= HYSDN_MAX_CAPI_SKB) + cinfo->out_idx = 0; /* wrap around */ + + if (cinfo->sk_count-- == HYSDN_MAX_CAPI_SKB) /* dec usage count */ + capi_ctr_resume_output(&cinfo->capi_ctrl); + spin_unlock_irq(&cinfo->lock); +} + +/*************************************************************** +hycapi_tx_capiget(hysdn_card *card) + +This is called when polling for messages to SEND. + +****************************************************************/ + +struct sk_buff * +hycapi_tx_capiget(hysdn_card *card) +{ + hycapictrl_info *cinfo = card->hyctrlinfo; + if(!cinfo) { + return (struct sk_buff *)NULL; + } + if (!cinfo->sk_count) + return (struct sk_buff *)NULL; /* nothing available */ + + return (cinfo->skbs[cinfo->out_idx]); /* next packet to send */ +} + + +/********************************************************** +int hycapi_init() + +attach the capi-driver to the kernel-capi. + +***********************************************************/ + +int hycapi_init(void) +{ + int i; + for(i=0;i<CAPI_MAXAPPL;i++) { + memset(&(hycapi_applications[i]), 0, sizeof(hycapi_appl)); + } + return(0); +} + +/************************************************************** +hycapi_cleanup(void) + +detach the capi-driver to the kernel-capi. Actually this should +free some more ressources. Do that later. +**************************************************************/ + +void +hycapi_cleanup(void) +{ +} + +/******************************************************************** +hycapi_capi_create(hysdn_card *card) + +Attach the card with its capi-ctrl. +*********************************************************************/ + +static void hycapi_fill_profile(hysdn_card *card) +{ + hycapictrl_info *cinfo = NULL; + struct capi_ctr *ctrl = NULL; + cinfo = card->hyctrlinfo; + if(!cinfo) return; + ctrl = &cinfo->capi_ctrl; + strcpy(ctrl->manu, "Hypercope"); + ctrl->version.majorversion = 2; + ctrl->version.minorversion = 0; + ctrl->version.majormanuversion = 3; + ctrl->version.minormanuversion = 2; + ctrl->profile.ncontroller = card->myid; + ctrl->profile.nbchannel = card->bchans; + ctrl->profile.goptions = GLOBAL_OPTION_INTERNAL_CONTROLLER | + GLOBAL_OPTION_B_CHANNEL_OPERATION; + ctrl->profile.support1 = B1_PROT_64KBIT_HDLC | + (card->faxchans ? B1_PROT_T30 : 0) | + B1_PROT_64KBIT_TRANSPARENT; + ctrl->profile.support2 = B2_PROT_ISO7776 | + (card->faxchans ? B2_PROT_T30 : 0) | + B2_PROT_TRANSPARENT; + ctrl->profile.support3 = B3_PROT_TRANSPARENT | + B3_PROT_T90NL | + (card->faxchans ? B3_PROT_T30 : 0) | + (card->faxchans ? B3_PROT_T30EXT : 0) | + B3_PROT_ISO8208; +} + +int +hycapi_capi_create(hysdn_card *card) +{ + hycapictrl_info *cinfo = NULL; + struct capi_ctr *ctrl = NULL; + int retval; +#ifdef HYCAPI_PRINTFNAMES + printk(KERN_NOTICE "hycapi_capi_create\n"); +#endif + if((hycapi_enable & (1 << card->myid)) == 0) { + return 1; + } + if (!card->hyctrlinfo) { + cinfo = (hycapictrl_info *) kmalloc(sizeof(hycapictrl_info), GFP_ATOMIC); + if (!cinfo) { + printk(KERN_WARNING "HYSDN: no memory for capi-ctrl.\n"); + return -ENOMEM; + } + memset(cinfo, 0, sizeof(hycapictrl_info)); + card->hyctrlinfo = cinfo; + cinfo->card = card; + spin_lock_init(&cinfo->lock); + INIT_LIST_HEAD(&cinfo->ncci_head); + + switch (card->brdtype) { + case BD_PCCARD: strcpy(cinfo->cardname,"HYSDN Hycard"); break; + case BD_ERGO: strcpy(cinfo->cardname,"HYSDN Ergo2"); break; + case BD_METRO: strcpy(cinfo->cardname,"HYSDN Metro4"); break; + case BD_CHAMP2: strcpy(cinfo->cardname,"HYSDN Champ2"); break; + case BD_PLEXUS: strcpy(cinfo->cardname,"HYSDN Plexus30"); break; + default: strcpy(cinfo->cardname,"HYSDN ???"); break; + } + + ctrl = &cinfo->capi_ctrl; + ctrl->driver_name = "hycapi"; + ctrl->driverdata = cinfo; + ctrl->register_appl = hycapi_register_appl; + ctrl->release_appl = hycapi_release_appl; + ctrl->send_message = hycapi_send_message; + ctrl->load_firmware = hycapi_load_firmware; + ctrl->reset_ctr = hycapi_reset_ctr; + ctrl->procinfo = hycapi_procinfo; + ctrl->ctr_read_proc = hycapi_read_proc; + strcpy(ctrl->name, cinfo->cardname); + ctrl->owner = THIS_MODULE; + + retval = attach_capi_ctr(ctrl); + if (retval) { + printk(KERN_ERR "hycapi: attach controller failed.\n"); + return -EBUSY; + } + /* fill in the blanks: */ + hycapi_fill_profile(card); + capi_ctr_ready(ctrl); + } else { + /* resume output on stopped ctrl */ + ctrl = &card->hyctrlinfo->capi_ctrl; + hycapi_fill_profile(card); + capi_ctr_ready(ctrl); + hycapi_restart_internal(ctrl); +/* ctrl->resume_output(ctrl); */ + } + return 0; +} diff --git a/drivers/isdn/hysdn/hysdn_boot.c b/drivers/isdn/hysdn/hysdn_boot.c new file mode 100644 index 000000000000..6c04281e57b8 --- /dev/null +++ b/drivers/isdn/hysdn/hysdn_boot.c @@ -0,0 +1,399 @@ +/* $Id: hysdn_boot.c,v 1.4.6.4 2001/09/23 22:24:54 kai Exp $ + * + * Linux driver for HYSDN cards + * specific routines for booting and pof handling + * + * Author Werner Cornelius (werner@titro.de) for Hypercope GmbH + * Copyright 1999 by Werner Cornelius (werner@titro.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/vmalloc.h> +#include <linux/slab.h> +#include <asm/uaccess.h> + +#include "hysdn_defs.h" +#include "hysdn_pof.h" + +/********************************/ +/* defines for pof read handler */ +/********************************/ +#define POF_READ_FILE_HEAD 0 +#define POF_READ_TAG_HEAD 1 +#define POF_READ_TAG_DATA 2 + +/************************************************************/ +/* definition of boot specific data area. This data is only */ +/* needed during boot and so allocated dynamically. */ +/************************************************************/ +struct boot_data { + word Cryptor; /* for use with Decrypt function */ + word Nrecs; /* records remaining in file */ + uchar pof_state; /* actual state of read handler */ + uchar is_crypted; /* card data is crypted */ + int BufSize; /* actual number of bytes bufferd */ + int last_error; /* last occurred error */ + word pof_recid; /* actual pof recid */ + ulong pof_reclen; /* total length of pof record data */ + ulong pof_recoffset; /* actual offset inside pof record */ + union { + uchar BootBuf[BOOT_BUF_SIZE]; /* buffer as byte count */ + tPofRecHdr PofRecHdr; /* header for actual record/chunk */ + tPofFileHdr PofFileHdr; /* header from POF file */ + tPofTimeStamp PofTime; /* time information */ + } buf; +}; + +/*****************************************************/ +/* start decryption of successive POF file chuncks. */ +/* */ +/* to be called at start of POF file reading, */ +/* before starting any decryption on any POF record. */ +/*****************************************************/ +void +StartDecryption(struct boot_data *boot) +{ + boot->Cryptor = CRYPT_STARTTERM; +} /* StartDecryption */ + + +/***************************************************************/ +/* decrypt complete BootBuf */ +/* NOTE: decryption must be applied to all or none boot tags - */ +/* to HI and LO boot loader and (all) seq tags, because */ +/* global Cryptor is started for whole POF. */ +/***************************************************************/ +void +DecryptBuf(struct boot_data *boot, int cnt) +{ + uchar *bufp = boot->buf.BootBuf; + + while (cnt--) { + boot->Cryptor = (boot->Cryptor >> 1) ^ ((boot->Cryptor & 1U) ? CRYPT_FEEDTERM : 0); + *bufp++ ^= (uchar) boot->Cryptor; + } +} /* DecryptBuf */ + +/********************************************************************************/ +/* pof_handle_data executes the required actions dependent on the active record */ +/* id. If successful 0 is returned, a negative value shows an error. */ +/********************************************************************************/ +static int +pof_handle_data(hysdn_card * card, int datlen) +{ + struct boot_data *boot = card->boot; /* pointer to boot specific data */ + long l; + uchar *imgp; + int img_len; + + /* handle the different record types */ + switch (boot->pof_recid) { + + case TAG_TIMESTMP: + if (card->debug_flags & LOG_POF_RECORD) + hysdn_addlog(card, "POF created %s", boot->buf.PofTime.DateTimeText); + break; + + case TAG_CBOOTDTA: + DecryptBuf(boot, datlen); /* we need to encrypt the buffer */ + case TAG_BOOTDTA: + if (card->debug_flags & LOG_POF_RECORD) + hysdn_addlog(card, "POF got %s len=%d offs=0x%lx", + (boot->pof_recid == TAG_CBOOTDTA) ? "CBOOTDATA" : "BOOTDTA", + datlen, boot->pof_recoffset); + + if (boot->pof_reclen != POF_BOOT_LOADER_TOTAL_SIZE) { + boot->last_error = EPOF_BAD_IMG_SIZE; /* invalid length */ + return (boot->last_error); + } + imgp = boot->buf.BootBuf; /* start of buffer */ + img_len = datlen; /* maximum length to transfer */ + + l = POF_BOOT_LOADER_OFF_IN_PAGE - + (boot->pof_recoffset & (POF_BOOT_LOADER_PAGE_SIZE - 1)); + if (l > 0) { + /* buffer needs to be truncated */ + imgp += l; /* advance pointer */ + img_len -= l; /* adjust len */ + } + /* at this point no special handling for data wrapping over buffer */ + /* is necessary, because the boot image always will be adjusted to */ + /* match a page boundary inside the buffer. */ + /* The buffer for the boot image on the card is filled in 2 cycles */ + /* first the 1024 hi-words are put in the buffer, then the low 1024 */ + /* word are handled in the same way with different offset. */ + + if (img_len > 0) { + /* data available for copy */ + if ((boot->last_error = + card->writebootimg(card, imgp, + (boot->pof_recoffset > POF_BOOT_LOADER_PAGE_SIZE) ? 2 : 0)) < 0) + return (boot->last_error); + } + break; /* end of case boot image hi/lo */ + + case TAG_CABSDATA: + DecryptBuf(boot, datlen); /* we need to encrypt the buffer */ + case TAG_ABSDATA: + if (card->debug_flags & LOG_POF_RECORD) + hysdn_addlog(card, "POF got %s len=%d offs=0x%lx", + (boot->pof_recid == TAG_CABSDATA) ? "CABSDATA" : "ABSDATA", + datlen, boot->pof_recoffset); + + if ((boot->last_error = card->writebootseq(card, boot->buf.BootBuf, datlen) < 0)) + return (boot->last_error); /* error writing data */ + + if (boot->pof_recoffset + datlen >= boot->pof_reclen) + return (card->waitpofready(card)); /* data completely spooled, wait for ready */ + + break; /* end of case boot seq data */ + + default: + if (card->debug_flags & LOG_POF_RECORD) + hysdn_addlog(card, "POF got data(id=0x%lx) len=%d offs=0x%lx", boot->pof_recid, + datlen, boot->pof_recoffset); + + break; /* simply skip record */ + } /* switch boot->pof_recid */ + + return (0); +} /* pof_handle_data */ + + +/******************************************************************************/ +/* pof_write_buffer is called when the buffer has been filled with the needed */ +/* number of data bytes. The number delivered is additionally supplied for */ +/* verification. The functions handles the data and returns the needed number */ +/* of bytes for the next action. If the returned value is 0 or less an error */ +/* occurred and booting must be aborted. */ +/******************************************************************************/ +int +pof_write_buffer(hysdn_card * card, int datlen) +{ + struct boot_data *boot = card->boot; /* pointer to boot specific data */ + + if (!boot) + return (-EFAULT); /* invalid call */ + if (boot->last_error < 0) + return (boot->last_error); /* repeated error */ + + if (card->debug_flags & LOG_POF_WRITE) + hysdn_addlog(card, "POF write: got %d bytes ", datlen); + + switch (boot->pof_state) { + case POF_READ_FILE_HEAD: + if (card->debug_flags & LOG_POF_WRITE) + hysdn_addlog(card, "POF write: checking file header"); + + if (datlen != sizeof(tPofFileHdr)) { + boot->last_error = -EPOF_INTERNAL; + break; + } + if (boot->buf.PofFileHdr.Magic != TAGFILEMAGIC) { + boot->last_error = -EPOF_BAD_MAGIC; + break; + } + /* Setup the new state and vars */ + boot->Nrecs = (word) (boot->buf.PofFileHdr.N_PofRecs); /* limited to 65535 */ + boot->pof_state = POF_READ_TAG_HEAD; /* now start with single tags */ + boot->last_error = sizeof(tPofRecHdr); /* new length */ + break; + + case POF_READ_TAG_HEAD: + if (card->debug_flags & LOG_POF_WRITE) + hysdn_addlog(card, "POF write: checking tag header"); + + if (datlen != sizeof(tPofRecHdr)) { + boot->last_error = -EPOF_INTERNAL; + break; + } + boot->pof_recid = boot->buf.PofRecHdr.PofRecId; /* actual pof recid */ + boot->pof_reclen = boot->buf.PofRecHdr.PofRecDataLen; /* total length */ + boot->pof_recoffset = 0; /* no starting offset */ + + if (card->debug_flags & LOG_POF_RECORD) + hysdn_addlog(card, "POF: got record id=0x%lx length=%ld ", + boot->pof_recid, boot->pof_reclen); + + boot->pof_state = POF_READ_TAG_DATA; /* now start with tag data */ + if (boot->pof_reclen < BOOT_BUF_SIZE) + boot->last_error = boot->pof_reclen; /* limit size */ + else + boot->last_error = BOOT_BUF_SIZE; /* maximum */ + + if (!boot->last_error) { /* no data inside record */ + boot->pof_state = POF_READ_TAG_HEAD; /* now start with single tags */ + boot->last_error = sizeof(tPofRecHdr); /* new length */ + } + break; + + case POF_READ_TAG_DATA: + if (card->debug_flags & LOG_POF_WRITE) + hysdn_addlog(card, "POF write: getting tag data"); + + if (datlen != boot->last_error) { + boot->last_error = -EPOF_INTERNAL; + break; + } + if ((boot->last_error = pof_handle_data(card, datlen)) < 0) + return (boot->last_error); /* an error occurred */ + boot->pof_recoffset += datlen; + if (boot->pof_recoffset >= boot->pof_reclen) { + boot->pof_state = POF_READ_TAG_HEAD; /* now start with single tags */ + boot->last_error = sizeof(tPofRecHdr); /* new length */ + } else { + if (boot->pof_reclen - boot->pof_recoffset < BOOT_BUF_SIZE) + boot->last_error = boot->pof_reclen - boot->pof_recoffset; /* limit size */ + else + boot->last_error = BOOT_BUF_SIZE; /* maximum */ + } + break; + + default: + boot->last_error = -EPOF_INTERNAL; /* unknown state */ + break; + } /* switch (boot->pof_state) */ + + return (boot->last_error); +} /* pof_write_buffer */ + + +/*******************************************************************************/ +/* pof_write_open is called when an open for boot on the cardlog device occurs. */ +/* The function returns the needed number of bytes for the next operation. If */ +/* the returned number is less or equal 0 an error specified by this code */ +/* occurred. Additionally the pointer to the buffer data area is set on success */ +/*******************************************************************************/ +int +pof_write_open(hysdn_card * card, uchar ** bufp) +{ + struct boot_data *boot; /* pointer to boot specific data */ + + if (card->boot) { + if (card->debug_flags & LOG_POF_OPEN) + hysdn_addlog(card, "POF open: already opened for boot"); + return (-ERR_ALREADY_BOOT); /* boot already active */ + } + /* error no mem available */ + if (!(boot = kmalloc(sizeof(struct boot_data), GFP_KERNEL))) { + if (card->debug_flags & LOG_MEM_ERR) + hysdn_addlog(card, "POF open: unable to allocate mem"); + return (-EFAULT); + } + card->boot = boot; + card->state = CARD_STATE_BOOTING; + memset(boot, 0, sizeof(struct boot_data)); + + card->stopcard(card); /* first stop the card */ + if (card->testram(card)) { + if (card->debug_flags & LOG_POF_OPEN) + hysdn_addlog(card, "POF open: DPRAM test failure"); + boot->last_error = -ERR_BOARD_DPRAM; + card->state = CARD_STATE_BOOTERR; /* show boot error */ + return (boot->last_error); + } + boot->BufSize = 0; /* Buffer is empty */ + boot->pof_state = POF_READ_FILE_HEAD; /* read file header */ + StartDecryption(boot); /* if POF File should be encrypted */ + + if (card->debug_flags & LOG_POF_OPEN) + hysdn_addlog(card, "POF open: success"); + + *bufp = boot->buf.BootBuf; /* point to buffer */ + return (sizeof(tPofFileHdr)); +} /* pof_write_open */ + +/********************************************************************************/ +/* pof_write_close is called when an close of boot on the cardlog device occurs. */ +/* The return value must be 0 if everything has happened as desired. */ +/********************************************************************************/ +int +pof_write_close(hysdn_card * card) +{ + struct boot_data *boot = card->boot; /* pointer to boot specific data */ + + if (!boot) + return (-EFAULT); /* invalid call */ + + card->boot = NULL; /* no boot active */ + kfree(boot); + + if (card->state == CARD_STATE_RUN) + card->set_errlog_state(card, 1); /* activate error log */ + + if (card->debug_flags & LOG_POF_OPEN) + hysdn_addlog(card, "POF close: success"); + + return (0); +} /* pof_write_close */ + +/*********************************************************************************/ +/* EvalSysrTokData checks additional records delivered with the Sysready Message */ +/* when POF has been booted. A return value of 0 is used if no error occurred. */ +/*********************************************************************************/ +int +EvalSysrTokData(hysdn_card * card, uchar * cp, int len) +{ + u_char *p; + u_char crc; + + if (card->debug_flags & LOG_POF_RECORD) + hysdn_addlog(card, "SysReady Token data length %d", len); + + if (len < 2) { + hysdn_addlog(card, "SysReady Token Data to short"); + return (1); + } + for (p = cp, crc = 0; p < (cp + len - 2); p++) + if ((crc & 0x80)) + crc = (((u_char) (crc << 1)) + 1) + *p; + else + crc = ((u_char) (crc << 1)) + *p; + crc = ~crc; + if (crc != *(cp + len - 1)) { + hysdn_addlog(card, "SysReady Token Data invalid CRC"); + return (1); + } + len--; /* don't check CRC byte */ + while (len > 0) { + + if (*cp == SYSR_TOK_END) + return (0); /* End of Token stream */ + + if (len < (*(cp + 1) + 2)) { + hysdn_addlog(card, "token 0x%x invalid length %d", *cp, *(cp + 1)); + return (1); + } + switch (*cp) { + case SYSR_TOK_B_CHAN: /* 1 */ + if (*(cp + 1) != 1) + return (1); /* length invalid */ + card->bchans = *(cp + 2); + break; + + case SYSR_TOK_FAX_CHAN: /* 2 */ + if (*(cp + 1) != 1) + return (1); /* length invalid */ + card->faxchans = *(cp + 2); + break; + + case SYSR_TOK_MAC_ADDR: /* 3 */ + if (*(cp + 1) != 6) + return (1); /* length invalid */ + memcpy(card->mac_addr, cp + 2, 6); + break; + + default: + hysdn_addlog(card, "unknown token 0x%02x length %d", *cp, *(cp + 1)); + break; + } + len -= (*(cp + 1) + 2); /* adjust len */ + cp += (*(cp + 1) + 2); /* and pointer */ + } + + hysdn_addlog(card, "no end token found"); + return (1); +} /* EvalSysrTokData */ diff --git a/drivers/isdn/hysdn/hysdn_defs.h b/drivers/isdn/hysdn/hysdn_defs.h new file mode 100644 index 000000000000..4cee26e558ee --- /dev/null +++ b/drivers/isdn/hysdn/hysdn_defs.h @@ -0,0 +1,298 @@ +/* $Id: hysdn_defs.h,v 1.5.6.3 2001/09/23 22:24:54 kai Exp $ + * + * Linux driver for HYSDN cards + * global definitions and exported vars and functions. + * + * Author Werner Cornelius (werner@titro.de) for Hypercope GmbH + * Copyright 1999 by Werner Cornelius (werner@titro.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#ifndef HYSDN_DEFS_H +#define HYSDN_DEFS_H + +#include <linux/config.h> +#include <linux/hysdn_if.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/skbuff.h> + +/****************************/ +/* storage type definitions */ +/****************************/ +#define uchar unsigned char +#define uint unsigned int +#define ulong unsigned long +#define word unsigned short + +#include "ince1pc.h" + +#ifdef CONFIG_HYSDN_CAPI +#include <linux/capi.h> +#include <linux/isdn/capicmd.h> +#include <linux/isdn/capiutil.h> +#include <linux/isdn/capilli.h> + +/***************************/ +/* CAPI-Profile values. */ +/***************************/ + +#define GLOBAL_OPTION_INTERNAL_CONTROLLER 0x0001 +#define GLOBAL_OPTION_EXTERNAL_CONTROLLER 0x0002 +#define GLOBAL_OPTION_HANDSET 0x0004 +#define GLOBAL_OPTION_DTMF 0x0008 +#define GLOBAL_OPTION_SUPPL_SERVICES 0x0010 +#define GLOBAL_OPTION_CHANNEL_ALLOCATION 0x0020 +#define GLOBAL_OPTION_B_CHANNEL_OPERATION 0x0040 + +#define B1_PROT_64KBIT_HDLC 0x0001 +#define B1_PROT_64KBIT_TRANSPARENT 0x0002 +#define B1_PROT_V110_ASYNCH 0x0004 +#define B1_PROT_V110_SYNCH 0x0008 +#define B1_PROT_T30 0x0010 +#define B1_PROT_64KBIT_INV_HDLC 0x0020 +#define B1_PROT_56KBIT_TRANSPARENT 0x0040 + +#define B2_PROT_ISO7776 0x0001 +#define B2_PROT_TRANSPARENT 0x0002 +#define B2_PROT_SDLC 0x0004 +#define B2_PROT_LAPD 0x0008 +#define B2_PROT_T30 0x0010 +#define B2_PROT_PPP 0x0020 +#define B2_PROT_TRANSPARENT_IGNORE_B1_FRAMING_ERRORS 0x0040 + +#define B3_PROT_TRANSPARENT 0x0001 +#define B3_PROT_T90NL 0x0002 +#define B3_PROT_ISO8208 0x0004 +#define B3_PROT_X25_DCE 0x0008 +#define B3_PROT_T30 0x0010 +#define B3_PROT_T30EXT 0x0020 + +#define HYSDN_MAXVERSION 8 + +/* Number of sendbuffers in CAPI-queue */ +#define HYSDN_MAX_CAPI_SKB 20 + +#endif /* CONFIG_HYSDN_CAPI*/ + +/************************************************/ +/* constants and bits for debugging/log outputs */ +/************************************************/ +#define LOG_MAX_LINELEN 120 +#define DEB_OUT_SYSLOG 0x80000000 /* output to syslog instead of proc fs */ +#define LOG_MEM_ERR 0x00000001 /* log memory errors like kmalloc failure */ +#define LOG_POF_OPEN 0x00000010 /* log pof open and close activities */ +#define LOG_POF_RECORD 0x00000020 /* log pof record parser */ +#define LOG_POF_WRITE 0x00000040 /* log detailed pof write operation */ +#define LOG_POF_CARD 0x00000080 /* log pof related card functions */ +#define LOG_CNF_LINE 0x00000100 /* all conf lines are put to procfs */ +#define LOG_CNF_DATA 0x00000200 /* non comment conf lines are shown with channel */ +#define LOG_CNF_MISC 0x00000400 /* additional conf line debug outputs */ +#define LOG_SCHED_ASYN 0x00001000 /* debug schedulers async tx routines */ +#define LOG_PROC_OPEN 0x00100000 /* open and close from procfs are logged */ +#define LOG_PROC_ALL 0x00200000 /* all actions from procfs are logged */ +#define LOG_NET_INIT 0x00010000 /* network init and deinit logging */ + +#define DEF_DEB_FLAGS 0x7fff000f /* everything is logged to procfs */ + +/**********************************/ +/* proc filesystem name constants */ +/**********************************/ +#define PROC_SUBDIR_NAME "hysdn" +#define PROC_CONF_BASENAME "cardconf" +#define PROC_LOG_BASENAME "cardlog" + +/***********************************/ +/* PCI 32 bit parms for IO and MEM */ +/***********************************/ +#define PCI_REG_PLX_MEM_BASE 0 +#define PCI_REG_PLX_IO_BASE 1 +#define PCI_REG_MEMORY_BASE 3 + +/**************/ +/* card types */ +/**************/ +#define BD_NONE 0U +#define BD_PERFORMANCE 1U +#define BD_VALUE 2U +#define BD_PCCARD 3U +#define BD_ERGO 4U +#define BD_METRO 5U +#define BD_CHAMP2 6U +#define BD_PLEXUS 7U + +/******************************************************/ +/* defined states for cards shown by reading cardconf */ +/******************************************************/ +#define CARD_STATE_UNUSED 0 /* never been used or booted */ +#define CARD_STATE_BOOTING 1 /* booting is in progress */ +#define CARD_STATE_BOOTERR 2 /* a previous boot was aborted */ +#define CARD_STATE_RUN 3 /* card is active */ + +/*******************************/ +/* defines for error_log_state */ +/*******************************/ +#define ERRLOG_STATE_OFF 0 /* error log is switched off, nothing to do */ +#define ERRLOG_STATE_ON 1 /* error log is switched on, wait for data */ +#define ERRLOG_STATE_START 2 /* start error logging */ +#define ERRLOG_STATE_STOP 3 /* stop error logging */ + +/*******************************/ +/* data structure for one card */ +/*******************************/ +typedef struct HYSDN_CARD { + + /* general variables for the cards */ + int myid; /* own driver card id */ + uchar bus; /* pci bus the card is connected to */ + uchar devfn; /* slot+function bit encoded */ + word subsysid; /* PCI subsystem id */ + uchar brdtype; /* type of card */ + uint bchans; /* number of available B-channels */ + uint faxchans; /* number of available fax-channels */ + uchar mac_addr[6]; /* MAC Address read from card */ + uint irq; /* interrupt number */ + uint iobase; /* IO-port base address */ + ulong plxbase; /* PLX memory base */ + ulong membase; /* DPRAM memory base */ + ulong memend; /* DPRAM memory end */ + void *dpram; /* mapped dpram */ + int state; /* actual state of card -> CARD_STATE_** */ + struct HYSDN_CARD *next; /* pointer to next card */ + + /* data areas for the /proc file system */ + void *proclog; /* pointer to proclog filesystem specific data */ + void *procconf; /* pointer to procconf filesystem specific data */ + + /* debugging and logging */ + uchar err_log_state; /* actual error log state of the card */ + ulong debug_flags; /* tells what should be debugged and where */ + void (*set_errlog_state) (struct HYSDN_CARD *, int); + + /* interrupt handler + interrupt synchronisation */ + struct work_struct irq_queue; /* interrupt task queue */ + uchar volatile irq_enabled; /* interrupt enabled if != 0 */ + uchar volatile hw_lock; /* hardware is currently locked -> no access */ + + /* boot process */ + void *boot; /* pointer to boot private data */ + int (*writebootimg) (struct HYSDN_CARD *, uchar *, ulong); + int (*writebootseq) (struct HYSDN_CARD *, uchar *, int); + int (*waitpofready) (struct HYSDN_CARD *); + int (*testram) (struct HYSDN_CARD *); + + /* scheduler for data transfer (only async parts) */ + uchar async_data[256]; /* async data to be sent (normally for config) */ + word volatile async_len; /* length of data to sent */ + word volatile async_channel; /* channel number for async transfer */ + int volatile async_busy; /* flag != 0 sending in progress */ + int volatile net_tx_busy; /* a network packet tx is in progress */ + + /* network interface */ + void *netif; /* pointer to network structure */ + + /* init and deinit stopcard for booting, too */ + void (*stopcard) (struct HYSDN_CARD *); + void (*releasehardware) (struct HYSDN_CARD *); +#ifdef CONFIG_HYSDN_CAPI + struct hycapictrl_info { + char cardname[32]; + spinlock_t lock; + int versionlen; + char versionbuf[1024]; + char *version[HYSDN_MAXVERSION]; + + char infobuf[128]; /* for function procinfo */ + + struct HYSDN_CARD *card; + struct capi_ctr capi_ctrl; + struct sk_buff *skbs[HYSDN_MAX_CAPI_SKB]; + int in_idx, out_idx; /* indexes to buffer ring */ + int sk_count; /* number of buffers currently in ring */ + struct sk_buff *tx_skb; /* buffer for tx operation */ + + struct list_head ncci_head; + } *hyctrlinfo; +#endif /* CONFIG_HYSDN_CAPI */ +} hysdn_card; + +#ifdef CONFIG_HYSDN_CAPI +typedef struct hycapictrl_info hycapictrl_info; +#endif /* CONFIG_HYSDN_CAPI */ + + +/*****************/ +/* exported vars */ +/*****************/ +extern int cardmax; /* number of found cards */ +extern hysdn_card *card_root; /* pointer to first card */ + + + +/*************************/ +/* im/exported functions */ +/*************************/ +extern char *hysdn_getrev(const char *); + +/* hysdn_procconf.c */ +extern int hysdn_procconf_init(void); /* init proc config filesys */ +extern void hysdn_procconf_release(void); /* deinit proc config filesys */ + +/* hysdn_proclog.c */ +extern int hysdn_proclog_init(hysdn_card *); /* init proc log entry */ +extern void hysdn_proclog_release(hysdn_card *); /* deinit proc log entry */ +extern void put_log_buffer(hysdn_card *, char *); /* output log data */ +extern void hysdn_addlog(hysdn_card *, char *,...); /* output data to log */ +extern void hysdn_card_errlog(hysdn_card *, tErrLogEntry *, int); /* output card log */ + +/* boardergo.c */ +extern int ergo_inithardware(hysdn_card * card); /* get hardware -> module init */ + +/* hysdn_boot.c */ +extern int pof_write_close(hysdn_card *); /* close proc file after writing pof */ +extern int pof_write_open(hysdn_card *, uchar **); /* open proc file for writing pof */ +extern int pof_write_buffer(hysdn_card *, int); /* write boot data to card */ +extern int EvalSysrTokData(hysdn_card *, uchar *, int); /* Check Sysready Token Data */ + +/* hysdn_sched.c */ +extern int hysdn_sched_tx(hysdn_card *, uchar *, word volatile *, word volatile *, + word); +extern int hysdn_sched_rx(hysdn_card *, uchar *, word, word); +extern int hysdn_tx_cfgline(hysdn_card *, uchar *, word); /* send one cfg line */ + +/* hysdn_net.c */ +extern unsigned int hynet_enable; +extern char *hysdn_net_revision; +extern int hysdn_net_create(hysdn_card *); /* create a new net device */ +extern int hysdn_net_release(hysdn_card *); /* delete the device */ +extern char *hysdn_net_getname(hysdn_card *); /* get name of net interface */ +extern void hysdn_tx_netack(hysdn_card *); /* acknowledge a packet tx */ +extern struct sk_buff *hysdn_tx_netget(hysdn_card *); /* get next network packet */ +extern void hysdn_rx_netpkt(hysdn_card *, uchar *, word); /* rxed packet from network */ + +#ifdef CONFIG_HYSDN_CAPI +extern unsigned int hycapi_enable; +extern int hycapi_capi_create(hysdn_card *); /* create a new capi device */ +extern int hycapi_capi_release(hysdn_card *); /* delete the device */ +extern int hycapi_capi_stop(hysdn_card *card); /* suspend */ +extern int hycapi_load_firmware(struct capi_ctr *, capiloaddata *); +extern void hycapi_reset_ctr(struct capi_ctr *); +extern void hycapi_remove_ctr(struct capi_ctr *); +extern void hycapi_register_appl(struct capi_ctr *, __u16 appl, + capi_register_params *); +extern void hycapi_release_appl(struct capi_ctr *, __u16 appl); +extern u16 hycapi_send_message(struct capi_ctr *, struct sk_buff *skb); +extern char *hycapi_procinfo(struct capi_ctr *); +extern int hycapi_read_proc(char *page, char **start, off_t off, + int count, int *eof, struct capi_ctr *card); +extern void hycapi_rx_capipkt(hysdn_card * card, uchar * buf, word len); +extern void hycapi_tx_capiack(hysdn_card * card); +extern struct sk_buff *hycapi_tx_capiget(hysdn_card *card); +extern int hycapi_init(void); +extern void hycapi_cleanup(void); +#endif /* CONFIG_HYSDN_CAPI */ + +#endif /* HYSDN_DEFS_H */ diff --git a/drivers/isdn/hysdn/hysdn_init.c b/drivers/isdn/hysdn/hysdn_init.c new file mode 100644 index 000000000000..5cac2bf5f4b0 --- /dev/null +++ b/drivers/isdn/hysdn/hysdn_init.c @@ -0,0 +1,254 @@ +/* $Id: hysdn_init.c,v 1.6.6.6 2001/09/23 22:24:54 kai Exp $ + * + * Linux driver for HYSDN cards, init functions. + * + * Author Werner Cornelius (werner@titro.de) for Hypercope GmbH + * Copyright 1999 by Werner Cornelius (werner@titro.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/version.h> +#include <linux/poll.h> +#include <linux/vmalloc.h> +#include <linux/slab.h> +#include <linux/pci.h> + +#include "hysdn_defs.h" + +static struct pci_device_id hysdn_pci_tbl[] = { + {PCI_VENDOR_ID_HYPERCOPE, PCI_DEVICE_ID_HYPERCOPE_PLX, PCI_ANY_ID, PCI_SUBDEVICE_ID_HYPERCOPE_METRO}, + {PCI_VENDOR_ID_HYPERCOPE, PCI_DEVICE_ID_HYPERCOPE_PLX, PCI_ANY_ID, PCI_SUBDEVICE_ID_HYPERCOPE_CHAMP2}, + {PCI_VENDOR_ID_HYPERCOPE, PCI_DEVICE_ID_HYPERCOPE_PLX, PCI_ANY_ID, PCI_SUBDEVICE_ID_HYPERCOPE_ERGO}, + {PCI_VENDOR_ID_HYPERCOPE, PCI_DEVICE_ID_HYPERCOPE_PLX, PCI_ANY_ID, PCI_SUBDEVICE_ID_HYPERCOPE_OLD_ERGO}, + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(pci, hysdn_pci_tbl); +MODULE_DESCRIPTION("ISDN4Linux: Driver for HYSDN cards"); +MODULE_AUTHOR("Werner Cornelius"); +MODULE_LICENSE("GPL"); + +static char *hysdn_init_revision = "$Revision: 1.6.6.6 $"; +int cardmax; /* number of found cards */ +hysdn_card *card_root = NULL; /* pointer to first card */ + +/**********************************************/ +/* table assigning PCI-sub ids to board types */ +/* the last entry contains all 0 */ +/**********************************************/ +static struct { + word subid; /* PCI sub id */ + uchar cardtyp; /* card type assigned */ +} pci_subid_map[] = { + + { + PCI_SUBDEVICE_ID_HYPERCOPE_METRO, BD_METRO + }, + { + PCI_SUBDEVICE_ID_HYPERCOPE_CHAMP2, BD_CHAMP2 + }, + { + PCI_SUBDEVICE_ID_HYPERCOPE_ERGO, BD_ERGO + }, + { + PCI_SUBDEVICE_ID_HYPERCOPE_OLD_ERGO, BD_ERGO + }, + { + 0, 0 + } /* terminating entry */ +}; + + +/*********************************************************************/ +/* search_cards searches for available cards in the pci config data. */ +/* If a card is found, the card structure is allocated and the cards */ +/* ressources are reserved. cardmax is incremented. */ +/*********************************************************************/ +static void +search_cards(void) +{ + struct pci_dev *akt_pcidev = NULL; + hysdn_card *card, *card_last; + int i; + + card_root = NULL; + card_last = NULL; + while ((akt_pcidev = pci_find_device(PCI_VENDOR_ID_HYPERCOPE, PCI_DEVICE_ID_HYPERCOPE_PLX, + akt_pcidev)) != NULL) { + if (pci_enable_device(akt_pcidev)) + continue; + + if (!(card = kmalloc(sizeof(hysdn_card), GFP_KERNEL))) { + printk(KERN_ERR "HYSDN: unable to alloc device mem \n"); + return; + } + memset(card, 0, sizeof(hysdn_card)); + card->myid = cardmax; /* set own id */ + card->bus = akt_pcidev->bus->number; + card->devfn = akt_pcidev->devfn; /* slot + function */ + card->subsysid = akt_pcidev->subsystem_device; + card->irq = akt_pcidev->irq; + card->iobase = pci_resource_start(akt_pcidev, PCI_REG_PLX_IO_BASE); + card->plxbase = pci_resource_start(akt_pcidev, PCI_REG_PLX_MEM_BASE); + card->membase = pci_resource_start(akt_pcidev, PCI_REG_MEMORY_BASE); + card->brdtype = BD_NONE; /* unknown */ + card->debug_flags = DEF_DEB_FLAGS; /* set default debug */ + card->faxchans = 0; /* default no fax channels */ + card->bchans = 2; /* and 2 b-channels */ + for (i = 0; pci_subid_map[i].subid; i++) + if (pci_subid_map[i].subid == card->subsysid) { + card->brdtype = pci_subid_map[i].cardtyp; + break; + } + if (card->brdtype != BD_NONE) { + if (ergo_inithardware(card)) { + printk(KERN_WARNING "HYSDN: card at io 0x%04x already in use\n", card->iobase); + kfree(card); + continue; + } + } else { + printk(KERN_WARNING "HYSDN: unknown card id 0x%04x\n", card->subsysid); + kfree(card); /* release mem */ + continue; + } + cardmax++; + card->next = NULL; /*end of chain */ + if (card_last) + card_last->next = card; /* pointer to next card */ + else + card_root = card; + card_last = card; /* new chain end */ + } /* device found */ +} /* search_cards */ + +/************************************************************************************/ +/* free_resources frees the acquired PCI resources and returns the allocated memory */ +/************************************************************************************/ +static void +free_resources(void) +{ + hysdn_card *card; + + while (card_root) { + card = card_root; + if (card->releasehardware) + card->releasehardware(card); /* free all hardware resources */ + card_root = card_root->next; /* remove card from chain */ + kfree(card); /* return mem */ + + } /* while card_root */ +} /* free_resources */ + +/**************************************************************************/ +/* stop_cards disables (hardware resets) all cards and disables interrupt */ +/**************************************************************************/ +static void +stop_cards(void) +{ + hysdn_card *card; + + card = card_root; /* first in chain */ + while (card) { + if (card->stopcard) + card->stopcard(card); + card = card->next; /* remove card from chain */ + } /* while card */ +} /* stop_cards */ + + +/****************************************************************************/ +/* The module startup and shutdown code. Only compiled when used as module. */ +/* Using the driver as module is always advisable, because the booting */ +/* image becomes smaller and the driver code is only loaded when needed. */ +/* Additionally newer versions may be activated without rebooting. */ +/****************************************************************************/ + +/******************************************************/ +/* extract revision number from string for log output */ +/******************************************************/ +char * +hysdn_getrev(const char *revision) +{ + char *rev; + char *p; + + if ((p = strchr(revision, ':'))) { + rev = p + 2; + p = strchr(rev, '$'); + *--p = 0; + } else + rev = "???"; + return rev; +} + + +/****************************************************************************/ +/* init_module is called once when the module is loaded to do all necessary */ +/* things like autodetect... */ +/* If the return value of this function is 0 the init has been successful */ +/* and the module is added to the list in /proc/modules, otherwise an error */ +/* is assumed and the module will not be kept in memory. */ +/****************************************************************************/ +static int __init +hysdn_init(void) +{ + char tmp[50]; + + strcpy(tmp, hysdn_init_revision); + printk(KERN_NOTICE "HYSDN: module Rev: %s loaded\n", hysdn_getrev(tmp)); + strcpy(tmp, hysdn_net_revision); + printk(KERN_NOTICE "HYSDN: network interface Rev: %s \n", hysdn_getrev(tmp)); + search_cards(); + printk(KERN_INFO "HYSDN: %d card(s) found.\n", cardmax); + + if (hysdn_procconf_init()) { + free_resources(); /* proc file_sys not created */ + return (-1); + } +#ifdef CONFIG_HYSDN_CAPI + if(cardmax > 0) { + if(hycapi_init()) { + printk(KERN_ERR "HYCAPI: init failed\n"); + return(-1); + } + } +#endif /* CONFIG_HYSDN_CAPI */ + return (0); /* no error */ +} /* init_module */ + + +/***********************************************************************/ +/* cleanup_module is called when the module is released by the kernel. */ +/* The routine is only called if init_module has been successful and */ +/* the module counter has a value of 0. Otherwise this function will */ +/* not be called. This function must release all resources still allo- */ +/* cated as after the return from this function the module code will */ +/* be removed from memory. */ +/***********************************************************************/ +static void __exit +hysdn_exit(void) +{ +#ifdef CONFIG_HYSDN_CAPI + hysdn_card *card; +#endif /* CONFIG_HYSDN_CAPI */ + stop_cards(); +#ifdef CONFIG_HYSDN_CAPI + card = card_root; /* first in chain */ + while (card) { + hycapi_capi_release(card); + card = card->next; /* remove card from chain */ + } /* while card */ + hycapi_cleanup(); +#endif /* CONFIG_HYSDN_CAPI */ + hysdn_procconf_release(); + free_resources(); + printk(KERN_NOTICE "HYSDN: module unloaded\n"); +} /* cleanup_module */ + +module_init(hysdn_init); +module_exit(hysdn_exit); diff --git a/drivers/isdn/hysdn/hysdn_net.c b/drivers/isdn/hysdn/hysdn_net.c new file mode 100644 index 000000000000..babec8157ae6 --- /dev/null +++ b/drivers/isdn/hysdn/hysdn_net.c @@ -0,0 +1,348 @@ +/* $Id: hysdn_net.c,v 1.8.6.4 2001/09/23 22:24:54 kai Exp $ + * + * Linux driver for HYSDN cards, net (ethernet type) handling routines. + * + * Author Werner Cornelius (werner@titro.de) for Hypercope GmbH + * Copyright 1999 by Werner Cornelius (werner@titro.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * This net module has been inspired by the skeleton driver from + * Donald Becker (becker@CESDIS.gsfc.nasa.gov) + * + */ + +#include <linux/module.h> +#include <linux/version.h> +#include <linux/signal.h> +#include <linux/kernel.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/inetdevice.h> + +#include "hysdn_defs.h" + +unsigned int hynet_enable = 0xffffffff; +MODULE_PARM(hynet_enable, "i"); + +/* store the actual version for log reporting */ +char *hysdn_net_revision = "$Revision: 1.8.6.4 $"; + +#define MAX_SKB_BUFFERS 20 /* number of buffers for keeping TX-data */ + +/****************************************************************************/ +/* structure containing the complete network data. The structure is aligned */ +/* in a way that both, the device and statistics are kept inside it. */ +/* for proper access, the device structure MUST be the first var/struct */ +/* inside the definition. */ +/****************************************************************************/ +struct net_local { + struct net_device netdev; /* the network device */ + struct net_device_stats stats; + /* additional vars may be added here */ + char dev_name[9]; /* our own device name */ + + /* Tx control lock. This protects the transmit buffer ring + * state along with the "tx full" state of the driver. This + * means all netif_queue flow control actions are protected + * by this lock as well. + */ + spinlock_t lock; + struct sk_buff *skbs[MAX_SKB_BUFFERS]; /* pointers to tx-skbs */ + int in_idx, out_idx; /* indexes to buffer ring */ + int sk_count; /* number of buffers currently in ring */ +}; /* net_local */ + + +/*****************************************************/ +/* Get the current statistics for this card. */ +/* This may be called with the card open or closed ! */ +/*****************************************************/ +static struct net_device_stats * +net_get_stats(struct net_device *dev) +{ + return (&((struct net_local *) dev)->stats); +} /* net_device_stats */ + +/*********************************************************************/ +/* Open/initialize the board. This is called (in the current kernel) */ +/* sometime after booting when the 'ifconfig' program is run. */ +/* This routine should set everything up anew at each open, even */ +/* registers that "should" only need to be set once at boot, so that */ +/* there is non-reboot way to recover if something goes wrong. */ +/*********************************************************************/ +static int +net_open(struct net_device *dev) +{ + struct in_device *in_dev; + hysdn_card *card = dev->priv; + int i; + + netif_start_queue(dev); /* start tx-queueing */ + + /* Fill in the MAC-level header (if not already set) */ + if (!card->mac_addr[0]) { + for (i = 0; i < ETH_ALEN - sizeof(ulong); i++) + dev->dev_addr[i] = 0xfc; + if ((in_dev = dev->ip_ptr) != NULL) { + struct in_ifaddr *ifa = in_dev->ifa_list; + if (ifa != NULL) + memcpy(dev->dev_addr + (ETH_ALEN - sizeof(ulong)), &ifa->ifa_local, sizeof(ulong)); + } + } else + memcpy(dev->dev_addr, card->mac_addr, ETH_ALEN); + + return (0); +} /* net_open */ + +/*******************************************/ +/* flush the currently occupied tx-buffers */ +/* must only be called when device closed */ +/*******************************************/ +static void +flush_tx_buffers(struct net_local *nl) +{ + + while (nl->sk_count) { + dev_kfree_skb(nl->skbs[nl->out_idx++]); /* free skb */ + if (nl->out_idx >= MAX_SKB_BUFFERS) + nl->out_idx = 0; /* wrap around */ + nl->sk_count--; + } +} /* flush_tx_buffers */ + + +/*********************************************************************/ +/* close/decativate the device. The device is not removed, but only */ +/* deactivated. */ +/*********************************************************************/ +static int +net_close(struct net_device *dev) +{ + + netif_stop_queue(dev); /* disable queueing */ + + flush_tx_buffers((struct net_local *) dev); + + return (0); /* success */ +} /* net_close */ + +/************************************/ +/* send a packet on this interface. */ +/* new style for kernel >= 2.3.33 */ +/************************************/ +static int +net_send_packet(struct sk_buff *skb, struct net_device *dev) +{ + struct net_local *lp = (struct net_local *) dev; + + spin_lock_irq(&lp->lock); + + lp->skbs[lp->in_idx++] = skb; /* add to buffer list */ + if (lp->in_idx >= MAX_SKB_BUFFERS) + lp->in_idx = 0; /* wrap around */ + lp->sk_count++; /* adjust counter */ + dev->trans_start = jiffies; + + /* If we just used up the very last entry in the + * TX ring on this device, tell the queueing + * layer to send no more. + */ + if (lp->sk_count >= MAX_SKB_BUFFERS) + netif_stop_queue(dev); + + /* When the TX completion hw interrupt arrives, this + * is when the transmit statistics are updated. + */ + + spin_unlock_irq(&lp->lock); + + if (lp->sk_count <= 3) { + schedule_work(&((hysdn_card *) dev->priv)->irq_queue); + } + return (0); /* success */ +} /* net_send_packet */ + + + +/***********************************************************************/ +/* acknowlegde a packet send. The network layer will be informed about */ +/* completion */ +/***********************************************************************/ +void +hysdn_tx_netack(hysdn_card * card) +{ + struct net_local *lp = card->netif; + + if (!lp) + return; /* non existing device */ + + + if (!lp->sk_count) + return; /* error condition */ + + lp->stats.tx_packets++; + lp->stats.tx_bytes += lp->skbs[lp->out_idx]->len; + + dev_kfree_skb(lp->skbs[lp->out_idx++]); /* free skb */ + if (lp->out_idx >= MAX_SKB_BUFFERS) + lp->out_idx = 0; /* wrap around */ + + if (lp->sk_count-- == MAX_SKB_BUFFERS) /* dec usage count */ + netif_start_queue((struct net_device *) lp); +} /* hysdn_tx_netack */ + +/*****************************************************/ +/* we got a packet from the network, go and queue it */ +/*****************************************************/ +void +hysdn_rx_netpkt(hysdn_card * card, uchar * buf, word len) +{ + struct net_local *lp = card->netif; + struct sk_buff *skb; + + if (!lp) + return; /* non existing device */ + + lp->stats.rx_bytes += len; + + skb = dev_alloc_skb(len); + if (skb == NULL) { + printk(KERN_NOTICE "%s: Memory squeeze, dropping packet.\n", + lp->netdev.name); + lp->stats.rx_dropped++; + return; + } + skb->dev = &lp->netdev; + + /* copy the data */ + memcpy(skb_put(skb, len), buf, len); + + /* determine the used protocol */ + skb->protocol = eth_type_trans(skb, &lp->netdev); + + netif_rx(skb); + lp->stats.rx_packets++; /* adjust packet count */ + +} /* hysdn_rx_netpkt */ + +/*****************************************************/ +/* return the pointer to a network packet to be send */ +/*****************************************************/ +struct sk_buff * +hysdn_tx_netget(hysdn_card * card) +{ + struct net_local *lp = card->netif; + + if (!lp) + return (NULL); /* non existing device */ + + if (!lp->sk_count) + return (NULL); /* nothing available */ + + return (lp->skbs[lp->out_idx]); /* next packet to send */ +} /* hysdn_tx_netget */ + + +/*******************************************/ +/* init function called by register device */ +/*******************************************/ +static int +net_init(struct net_device *dev) +{ + /* setup the function table */ + dev->open = net_open; + dev->stop = net_close; + dev->hard_start_xmit = net_send_packet; + dev->get_stats = net_get_stats; + + /* Fill in the fields of the device structure with ethernet values. */ + ether_setup(dev); + + return (0); /* success */ +} /* net_init */ + +/*****************************************************************************/ +/* hysdn_net_create creates a new net device for the given card. If a device */ +/* already exists, it will be deleted and created a new one. The return value */ +/* 0 announces success, else a negative error code will be returned. */ +/*****************************************************************************/ +int +hysdn_net_create(hysdn_card * card) +{ + struct net_device *dev; + int i; + if(!card) { + printk(KERN_WARNING "No card-pt in hysdn_net_create!\n"); + return (-ENOMEM); + } + hysdn_net_release(card); /* release an existing net device */ + if ((dev = kmalloc(sizeof(struct net_local), GFP_KERNEL)) == NULL) { + printk(KERN_WARNING "HYSDN: unable to allocate mem\n"); + return (-ENOMEM); + } + memset(dev, 0, sizeof(struct net_local)); /* clean the structure */ + + spin_lock_init(&((struct net_local *) dev)->lock); + + /* initialise necessary or informing fields */ + dev->base_addr = card->iobase; /* IO address */ + dev->irq = card->irq; /* irq */ + dev->init = net_init; /* the init function of the device */ + if(dev->name) { + strcpy(dev->name, ((struct net_local *) dev)->dev_name); + } + if ((i = register_netdev(dev))) { + printk(KERN_WARNING "HYSDN: unable to create network device\n"); + kfree(dev); + return (i); + } + dev->priv = card; /* remember pointer to own data structure */ + card->netif = dev; /* setup the local pointer */ + + if (card->debug_flags & LOG_NET_INIT) + hysdn_addlog(card, "network device created"); + return (0); /* and return success */ +} /* hysdn_net_create */ + +/***************************************************************************/ +/* hysdn_net_release deletes the net device for the given card. The return */ +/* value 0 announces success, else a negative error code will be returned. */ +/***************************************************************************/ +int +hysdn_net_release(hysdn_card * card) +{ + struct net_device *dev = card->netif; + + if (!dev) + return (0); /* non existing */ + + card->netif = NULL; /* clear out pointer */ + dev->stop(dev); /* close the device */ + + flush_tx_buffers((struct net_local *) dev); /* empty buffers */ + + unregister_netdev(dev); /* release the device */ + free_netdev(dev); /* release the memory allocated */ + if (card->debug_flags & LOG_NET_INIT) + hysdn_addlog(card, "network device deleted"); + + return (0); /* always successful */ +} /* hysdn_net_release */ + +/*****************************************************************************/ +/* hysdn_net_getname returns a pointer to the name of the network interface. */ +/* if the interface is not existing, a "-" is returned. */ +/*****************************************************************************/ +char * +hysdn_net_getname(hysdn_card * card) +{ + struct net_device *dev = card->netif; + + if (!dev) + return ("-"); /* non existing */ + + return (dev->name); +} /* hysdn_net_getname */ diff --git a/drivers/isdn/hysdn/hysdn_pof.h b/drivers/isdn/hysdn/hysdn_pof.h new file mode 100644 index 000000000000..6cd81b9b08bc --- /dev/null +++ b/drivers/isdn/hysdn/hysdn_pof.h @@ -0,0 +1,78 @@ +/* $Id: hysdn_pof.h,v 1.2.6.1 2001/09/23 22:24:54 kai Exp $ + * + * Linux driver for HYSDN cards, definitions used for handling pof-files. + * + * Author Werner Cornelius (werner@titro.de) for Hypercope GmbH + * Copyright 1999 by Werner Cornelius (werner@titro.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +/************************/ +/* POF specific defines */ +/************************/ +#define BOOT_BUF_SIZE 0x1000 /* =4096, maybe moved to other h file */ +#define CRYPT_FEEDTERM 0x8142 +#define CRYPT_STARTTERM 0x81a5 + /* max. timeout time in seconds + * from end of booting to POF is ready + */ +#define POF_READY_TIME_OUT_SEC 10 + +/**********************************/ +/* defines for 1.stage boot image */ +/**********************************/ + +/* the POF file record containing the boot loader image + * has 2 pages a 16KB: + * 1. page contains the high 16-bit part of the 32-bit E1 words + * 2. page contains the low 16-bit part of the 32-bit E1 words + * + * In each 16KB page we assume the start of the boot loader code + * in the highest 2KB part (at offset 0x3800); + * the rest (0x0000..0x37FF) is assumed to contain 0 bytes. + */ + +#define POF_BOOT_LOADER_PAGE_SIZE 0x4000 /* =16384U */ +#define POF_BOOT_LOADER_TOTAL_SIZE (2U*POF_BOOT_LOADER_PAGE_SIZE) + +#define POF_BOOT_LOADER_CODE_SIZE 0x0800 /* =2KB =2048U */ + + /* offset in boot page, where loader code may start */ + /* =0x3800= 14336U */ +#define POF_BOOT_LOADER_OFF_IN_PAGE (POF_BOOT_LOADER_PAGE_SIZE-POF_BOOT_LOADER_CODE_SIZE) + + +/*--------------------------------------POF file record structs------------*/ +typedef struct PofFileHdr_tag { /* Pof file header */ +/*00 */ ulong Magic __attribute__((packed)); +/*04 */ ulong N_PofRecs __attribute__((packed)); +/*08 */ +} tPofFileHdr; + +typedef struct PofRecHdr_tag { /* Pof record header */ +/*00 */ word PofRecId __attribute__((packed)); +/*02 */ ulong PofRecDataLen __attribute__((packed)); +/*06 */ +} tPofRecHdr; + +typedef struct PofTimeStamp_tag { +/*00 */ ulong UnixTime __attribute__((packed)); + /*04 */ uchar DateTimeText[0x28] __attribute__((packed)); + /* =40 */ +/*2C */ +} tPofTimeStamp; + + /* tPofFileHdr.Magic value: */ +#define TAGFILEMAGIC 0x464F501AUL + /* tPofRecHdr.PofRecId values: */ +#define TAG_ABSDATA 0x1000 /* abs. data */ +#define TAG_BOOTDTA 0x1001 /* boot data */ +#define TAG_COMMENT 0x0020 +#define TAG_SYSCALL 0x0021 +#define TAG_FLOWCTRL 0x0022 +#define TAG_TIMESTMP 0x0010 /* date/time stamp of version */ +#define TAG_CABSDATA 0x1100 /* crypted abs. data */ +#define TAG_CBOOTDTA 0x1101 /* crypted boot data */ diff --git a/drivers/isdn/hysdn/hysdn_procconf.c b/drivers/isdn/hysdn/hysdn_procconf.c new file mode 100644 index 000000000000..5da507e532fc --- /dev/null +++ b/drivers/isdn/hysdn/hysdn_procconf.c @@ -0,0 +1,443 @@ +/* $Id: hysdn_procconf.c,v 1.8.6.4 2001/09/23 22:24:54 kai Exp $ + * + * Linux driver for HYSDN cards, /proc/net filesystem dir and conf functions. + * + * written by Werner Cornelius (werner@titro.de) for Hypercope GmbH + * + * Copyright 1999 by Werner Cornelius (werner@titro.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/module.h> +#include <linux/version.h> +#include <linux/poll.h> +#include <linux/proc_fs.h> +#include <linux/pci.h> +#include <linux/smp_lock.h> + +#include "hysdn_defs.h" + +static char *hysdn_procconf_revision = "$Revision: 1.8.6.4 $"; + +#define INFO_OUT_LEN 80 /* length of info line including lf */ + +/********************************************************/ +/* defines and data structure for conf write operations */ +/********************************************************/ +#define CONF_STATE_DETECT 0 /* waiting for detect */ +#define CONF_STATE_CONF 1 /* writing config data */ +#define CONF_STATE_POF 2 /* writing pof data */ +#define CONF_LINE_LEN 255 /* 255 chars max */ + +struct conf_writedata { + hysdn_card *card; /* card the device is connected to */ + int buf_size; /* actual number of bytes in the buffer */ + int needed_size; /* needed size when reading pof */ + int state; /* actual interface states from above constants */ + uchar conf_line[CONF_LINE_LEN]; /* buffered conf line */ + word channel; /* active channel number */ + uchar *pof_buffer; /* buffer when writing pof */ +}; + +/***********************************************************************/ +/* process_line parses one config line and transfers it to the card if */ +/* necessary. */ +/* if the return value is negative an error occurred. */ +/***********************************************************************/ +static int +process_line(struct conf_writedata *cnf) +{ + uchar *cp = cnf->conf_line; + int i; + + if (cnf->card->debug_flags & LOG_CNF_LINE) + hysdn_addlog(cnf->card, "conf line: %s", cp); + + if (*cp == '-') { /* option */ + cp++; /* point to option char */ + + if (*cp++ != 'c') + return (0); /* option unknown or used */ + i = 0; /* start value for channel */ + while ((*cp <= '9') && (*cp >= '0')) + i = i * 10 + *cp++ - '0'; /* get decimal number */ + if (i > 65535) { + if (cnf->card->debug_flags & LOG_CNF_MISC) + hysdn_addlog(cnf->card, "conf channel invalid %d", i); + return (-ERR_INV_CHAN); /* invalid channel */ + } + cnf->channel = i & 0xFFFF; /* set new channel number */ + return (0); /* success */ + } /* option */ + if (*cp == '*') { /* line to send */ + if (cnf->card->debug_flags & LOG_CNF_DATA) + hysdn_addlog(cnf->card, "conf chan=%d %s", cnf->channel, cp); + return (hysdn_tx_cfgline(cnf->card, cnf->conf_line + 1, + cnf->channel)); /* send the line without * */ + } /* line to send */ + return (0); +} /* process_line */ + +/***********************************/ +/* conf file operations and tables */ +/***********************************/ + +/****************************************************/ +/* write conf file -> boot or send cfg line to card */ +/****************************************************/ +static ssize_t +hysdn_conf_write(struct file *file, const char __user *buf, size_t count, loff_t * off) +{ + struct conf_writedata *cnf; + int i; + uchar ch, *cp; + + if (!count) + return (0); /* nothing to handle */ + + if (!(cnf = file->private_data)) + return (-EFAULT); /* should never happen */ + + if (cnf->state == CONF_STATE_DETECT) { /* auto detect cnf or pof data */ + if (copy_from_user(&ch, buf, 1)) /* get first char for detect */ + return (-EFAULT); + + if (ch == 0x1A) { + /* we detected a pof file */ + if ((cnf->needed_size = pof_write_open(cnf->card, &cnf->pof_buffer)) <= 0) + return (cnf->needed_size); /* an error occurred -> exit */ + cnf->buf_size = 0; /* buffer is empty */ + cnf->state = CONF_STATE_POF; /* new state */ + } else { + /* conf data has been detected */ + cnf->buf_size = 0; /* buffer is empty */ + cnf->state = CONF_STATE_CONF; /* requested conf data write */ + if (cnf->card->state != CARD_STATE_RUN) + return (-ERR_NOT_BOOTED); + cnf->conf_line[CONF_LINE_LEN - 1] = 0; /* limit string length */ + cnf->channel = 4098; /* default channel for output */ + } + } /* state was auto detect */ + if (cnf->state == CONF_STATE_POF) { /* pof write active */ + i = cnf->needed_size - cnf->buf_size; /* bytes still missing for write */ + if (i <= 0) + return (-EINVAL); /* size error handling pof */ + + if (i < count) + count = i; /* limit requested number of bytes */ + if (copy_from_user(cnf->pof_buffer + cnf->buf_size, buf, count)) + return (-EFAULT); /* error while copying */ + cnf->buf_size += count; + + if (cnf->needed_size == cnf->buf_size) { + cnf->needed_size = pof_write_buffer(cnf->card, cnf->buf_size); /* write data */ + if (cnf->needed_size <= 0) { + cnf->card->state = CARD_STATE_BOOTERR; /* show boot error */ + return (cnf->needed_size); /* an error occurred */ + } + cnf->buf_size = 0; /* buffer is empty again */ + } + } + /* pof write active */ + else { /* conf write active */ + + if (cnf->card->state != CARD_STATE_RUN) { + if (cnf->card->debug_flags & LOG_CNF_MISC) + hysdn_addlog(cnf->card, "cnf write denied -> not booted"); + return (-ERR_NOT_BOOTED); + } + i = (CONF_LINE_LEN - 1) - cnf->buf_size; /* bytes available in buffer */ + if (i > 0) { + /* copy remaining bytes into buffer */ + + if (count > i) + count = i; /* limit transfer */ + if (copy_from_user(cnf->conf_line + cnf->buf_size, buf, count)) + return (-EFAULT); /* error while copying */ + + i = count; /* number of chars in buffer */ + cp = cnf->conf_line + cnf->buf_size; + while (i) { + /* search for end of line */ + if ((*cp < ' ') && (*cp != 9)) + break; /* end of line found */ + cp++; + i--; + } /* search for end of line */ + + if (i) { + /* delimiter found */ + *cp++ = 0; /* string termination */ + count -= (i - 1); /* subtract remaining bytes from count */ + while ((i) && (*cp < ' ') && (*cp != 9)) { + i--; /* discard next char */ + count++; /* mark as read */ + cp++; /* next char */ + } + cnf->buf_size = 0; /* buffer is empty after transfer */ + if ((i = process_line(cnf)) < 0) /* handle the line */ + count = i; /* return the error */ + } + /* delimiter found */ + else { + cnf->buf_size += count; /* add chars to string */ + if (cnf->buf_size >= CONF_LINE_LEN - 1) { + if (cnf->card->debug_flags & LOG_CNF_MISC) + hysdn_addlog(cnf->card, "cnf line too long %d chars pos %d", cnf->buf_size, count); + return (-ERR_CONF_LONG); + } + } /* not delimited */ + + } + /* copy remaining bytes into buffer */ + else { + if (cnf->card->debug_flags & LOG_CNF_MISC) + hysdn_addlog(cnf->card, "cnf line too long"); + return (-ERR_CONF_LONG); + } + } /* conf write active */ + + return (count); +} /* hysdn_conf_write */ + +/*******************************************/ +/* read conf file -> output card info data */ +/*******************************************/ +static ssize_t +hysdn_conf_read(struct file *file, char __user *buf, size_t count, loff_t * off) +{ + char *cp; + int i; + + if (file->f_mode & FMODE_READ) { + if (!(cp = file->private_data)) + return (-EFAULT); /* should never happen */ + i = strlen(cp); /* get total string length */ + if (*off < i) { + /* still bytes to transfer */ + cp += *off; /* point to desired data offset */ + i -= *off; /* remaining length */ + if (i > count) + i = count; /* limit length to transfer */ + if (copy_to_user(buf, cp, i)) + return (-EFAULT); /* copy error */ + *off += i; /* adjust offset */ + } else + return (0); + } else + return (-EPERM); /* no permission to read */ + + return (i); +} /* hysdn_conf_read */ + +/******************/ +/* open conf file */ +/******************/ +static int +hysdn_conf_open(struct inode *ino, struct file *filep) +{ + hysdn_card *card; + struct proc_dir_entry *pd; + struct conf_writedata *cnf; + char *cp, *tmp; + + /* now search the addressed card */ + lock_kernel(); + card = card_root; + while (card) { + pd = card->procconf; + if (pd == PDE(ino)) + break; + card = card->next; /* search next entry */ + } + if (!card) { + unlock_kernel(); + return (-ENODEV); /* device is unknown/invalid */ + } + if (card->debug_flags & (LOG_PROC_OPEN | LOG_PROC_ALL)) + hysdn_addlog(card, "config open for uid=%d gid=%d mode=0x%x", + filep->f_uid, filep->f_gid, filep->f_mode); + + if ((filep->f_mode & (FMODE_READ | FMODE_WRITE)) == FMODE_WRITE) { + /* write only access -> write boot file or conf line */ + + if (!(cnf = kmalloc(sizeof(struct conf_writedata), GFP_KERNEL))) { + unlock_kernel(); + return (-EFAULT); + } + cnf->card = card; + cnf->buf_size = 0; /* nothing buffered */ + cnf->state = CONF_STATE_DETECT; /* start auto detect */ + filep->private_data = cnf; + + } else if ((filep->f_mode & (FMODE_READ | FMODE_WRITE)) == FMODE_READ) { + /* read access -> output card info data */ + + if (!(tmp = (char *) kmalloc(INFO_OUT_LEN * 2 + 2, GFP_KERNEL))) { + unlock_kernel(); + return (-EFAULT); /* out of memory */ + } + filep->private_data = tmp; /* start of string */ + + /* first output a headline */ + sprintf(tmp, "id bus slot type irq iobase dp-mem b-chans fax-chans state device"); + cp = tmp; /* start of string */ + while (*cp) + cp++; + while (((cp - tmp) % (INFO_OUT_LEN + 1)) != INFO_OUT_LEN) + *cp++ = ' '; + *cp++ = '\n'; + + /* and now the data */ + sprintf(cp, "%d %3d %4d %4d %3d 0x%04x 0x%08lx %7d %9d %3d %s", + card->myid, + card->bus, + PCI_SLOT(card->devfn), + card->brdtype, + card->irq, + card->iobase, + card->membase, + card->bchans, + card->faxchans, + card->state, + hysdn_net_getname(card)); + while (*cp) + cp++; + while (((cp - tmp) % (INFO_OUT_LEN + 1)) != INFO_OUT_LEN) + *cp++ = ' '; + *cp++ = '\n'; + *cp = 0; /* end of string */ + } else { /* simultaneous read/write access forbidden ! */ + unlock_kernel(); + return (-EPERM); /* no permission this time */ + } + unlock_kernel(); + return nonseekable_open(ino, filep); +} /* hysdn_conf_open */ + +/***************************/ +/* close a config file. */ +/***************************/ +static int +hysdn_conf_close(struct inode *ino, struct file *filep) +{ + hysdn_card *card; + struct conf_writedata *cnf; + int retval = 0; + struct proc_dir_entry *pd; + + lock_kernel(); + /* search the addressed card */ + card = card_root; + while (card) { + pd = card->procconf; + if (pd == PDE(ino)) + break; + card = card->next; /* search next entry */ + } + if (!card) { + unlock_kernel(); + return (-ENODEV); /* device is unknown/invalid */ + } + if (card->debug_flags & (LOG_PROC_OPEN | LOG_PROC_ALL)) + hysdn_addlog(card, "config close for uid=%d gid=%d mode=0x%x", + filep->f_uid, filep->f_gid, filep->f_mode); + + if ((filep->f_mode & (FMODE_READ | FMODE_WRITE)) == FMODE_WRITE) { + /* write only access -> write boot file or conf line */ + if (filep->private_data) { + cnf = filep->private_data; + + if (cnf->state == CONF_STATE_POF) + retval = pof_write_close(cnf->card); /* close the pof write */ + kfree(filep->private_data); /* free allocated memory for buffer */ + + } /* handle write private data */ + } else if ((filep->f_mode & (FMODE_READ | FMODE_WRITE)) == FMODE_READ) { + /* read access -> output card info data */ + + if (filep->private_data) + kfree(filep->private_data); /* release memory */ + } + unlock_kernel(); + return (retval); +} /* hysdn_conf_close */ + +/******************************************************/ +/* table for conf filesystem functions defined above. */ +/******************************************************/ +static struct file_operations conf_fops = +{ + .llseek = no_llseek, + .read = hysdn_conf_read, + .write = hysdn_conf_write, + .open = hysdn_conf_open, + .release = hysdn_conf_close, +}; + +/*****************************/ +/* hysdn subdir in /proc/net */ +/*****************************/ +struct proc_dir_entry *hysdn_proc_entry = NULL; + +/*******************************************************************************/ +/* hysdn_procconf_init is called when the module is loaded and after the cards */ +/* have been detected. The needed proc dir and card config files are created. */ +/* The log init is called at last. */ +/*******************************************************************************/ +int +hysdn_procconf_init(void) +{ + hysdn_card *card; + uchar conf_name[20]; + + hysdn_proc_entry = create_proc_entry(PROC_SUBDIR_NAME, S_IFDIR | S_IRUGO | S_IXUGO, proc_net); + if (!hysdn_proc_entry) { + printk(KERN_ERR "HYSDN: unable to create hysdn subdir\n"); + return (-1); + } + card = card_root; /* point to first card */ + while (card) { + + sprintf(conf_name, "%s%d", PROC_CONF_BASENAME, card->myid); + if ((card->procconf = (void *) create_proc_entry(conf_name, + S_IFREG | S_IRUGO | S_IWUSR, + hysdn_proc_entry)) != NULL) { + ((struct proc_dir_entry *) card->procconf)->proc_fops = &conf_fops; + ((struct proc_dir_entry *) card->procconf)->owner = THIS_MODULE; + hysdn_proclog_init(card); /* init the log file entry */ + } + card = card->next; /* next entry */ + } + + printk(KERN_NOTICE "HYSDN: procfs Rev. %s initialised\n", hysdn_getrev(hysdn_procconf_revision)); + return (0); +} /* hysdn_procconf_init */ + +/*************************************************************************************/ +/* hysdn_procconf_release is called when the module is unloaded and before the cards */ +/* resources are released. The module counter is assumed to be 0 ! */ +/*************************************************************************************/ +void +hysdn_procconf_release(void) +{ + hysdn_card *card; + uchar conf_name[20]; + + card = card_root; /* start with first card */ + while (card) { + + sprintf(conf_name, "%s%d", PROC_CONF_BASENAME, card->myid); + if (card->procconf) + remove_proc_entry(conf_name, hysdn_proc_entry); + + hysdn_proclog_release(card); /* init the log file entry */ + + card = card->next; /* point to next card */ + } + + remove_proc_entry(PROC_SUBDIR_NAME, proc_net); +} diff --git a/drivers/isdn/hysdn/hysdn_proclog.c b/drivers/isdn/hysdn/hysdn_proclog.c new file mode 100644 index 000000000000..8ef2b7c952a6 --- /dev/null +++ b/drivers/isdn/hysdn/hysdn_proclog.c @@ -0,0 +1,441 @@ +/* $Id: hysdn_proclog.c,v 1.9.6.3 2001/09/23 22:24:54 kai Exp $ + * + * Linux driver for HYSDN cards, /proc/net filesystem log functions. + * + * Author Werner Cornelius (werner@titro.de) for Hypercope GmbH + * Copyright 1999 by Werner Cornelius (werner@titro.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/module.h> +#include <linux/version.h> +#include <linux/poll.h> +#include <linux/proc_fs.h> +#include <linux/pci.h> +#include <linux/smp_lock.h> + +#include "hysdn_defs.h" + +/* the proc subdir for the interface is defined in the procconf module */ +extern struct proc_dir_entry *hysdn_proc_entry; + +/*************************************************/ +/* structure keeping ascii log for device output */ +/*************************************************/ +struct log_data { + struct log_data *next; + ulong usage_cnt; /* number of files still to work */ + void *proc_ctrl; /* pointer to own control procdata structure */ + char log_start[2]; /* log string start (final len aligned by size) */ +}; + +/**********************************************/ +/* structure holding proc entrys for one card */ +/**********************************************/ +struct procdata { + struct proc_dir_entry *log; /* log entry */ + char log_name[15]; /* log filename */ + struct log_data *log_head, *log_tail; /* head and tail for queue */ + int if_used; /* open count for interface */ + int volatile del_lock; /* lock for delete operations */ + uchar logtmp[LOG_MAX_LINELEN]; + wait_queue_head_t rd_queue; +}; + + +/**********************************************/ +/* log function for cards error log interface */ +/**********************************************/ +void +hysdn_card_errlog(hysdn_card * card, tErrLogEntry * logp, int maxsize) +{ + char buf[ERRLOG_TEXT_SIZE + 40]; + + sprintf(buf, "LOG 0x%08lX 0x%08lX : %s\n", logp->ulErrType, logp->ulErrSubtype, logp->ucText); + put_log_buffer(card, buf); /* output the string */ +} /* hysdn_card_errlog */ + +/***************************************************/ +/* Log function using format specifiers for output */ +/***************************************************/ +void +hysdn_addlog(hysdn_card * card, char *fmt,...) +{ + struct procdata *pd = card->proclog; + char *cp; + va_list args; + + if (!pd) + return; /* log structure non existent */ + + cp = pd->logtmp; + cp += sprintf(cp, "HYSDN: card %d ", card->myid); + + va_start(args, fmt); + cp += vsprintf(cp, fmt, args); + va_end(args); + *cp++ = '\n'; + *cp = 0; + + if (card->debug_flags & DEB_OUT_SYSLOG) + printk(KERN_INFO "%s", pd->logtmp); + else + put_log_buffer(card, pd->logtmp); + +} /* hysdn_addlog */ + +/********************************************/ +/* put an log buffer into the log queue. */ +/* This buffer will be kept until all files */ +/* opened for read got the contents. */ +/* Flushes buffers not longer in use. */ +/********************************************/ +void +put_log_buffer(hysdn_card * card, char *cp) +{ + struct log_data *ib; + struct procdata *pd = card->proclog; + int i; + unsigned long flags; + + if (!pd) + return; + if (!cp) + return; + if (!*cp) + return; + if (pd->if_used <= 0) + return; /* no open file for read */ + + if (!(ib = (struct log_data *) kmalloc(sizeof(struct log_data) + strlen(cp), GFP_ATOMIC))) + return; /* no memory */ + strcpy(ib->log_start, cp); /* set output string */ + ib->next = NULL; + ib->proc_ctrl = pd; /* point to own control structure */ + save_flags(flags); + cli(); + ib->usage_cnt = pd->if_used; + if (!pd->log_head) + pd->log_head = ib; /* new head */ + else + pd->log_tail->next = ib; /* follows existing messages */ + pd->log_tail = ib; /* new tail */ + i = pd->del_lock++; /* get lock state */ + restore_flags(flags); + + /* delete old entrys */ + if (!i) + while (pd->log_head->next) { + if ((pd->log_head->usage_cnt <= 0) && + (pd->log_head->next->usage_cnt <= 0)) { + ib = pd->log_head; + pd->log_head = pd->log_head->next; + kfree(ib); + } else + break; + } /* pd->log_head->next */ + pd->del_lock--; /* release lock level */ + wake_up_interruptible(&(pd->rd_queue)); /* announce new entry */ +} /* put_log_buffer */ + + +/******************************/ +/* file operations and tables */ +/******************************/ + +/****************************************/ +/* write log file -> set log level bits */ +/****************************************/ +static ssize_t +hysdn_log_write(struct file *file, const char __user *buf, size_t count, loff_t * off) +{ + ulong u = 0; + int found = 0; + uchar *cp, valbuf[128]; + long base = 10; + hysdn_card *card = (hysdn_card *) file->private_data; + + if (count > (sizeof(valbuf) - 1)) + count = sizeof(valbuf) - 1; /* limit length */ + if (copy_from_user(valbuf, buf, count)) + return (-EFAULT); /* copy failed */ + + valbuf[count] = 0; /* terminating 0 */ + cp = valbuf; + if ((count > 2) && (valbuf[0] == '0') && (valbuf[1] == 'x')) { + cp += 2; /* pointer after hex modifier */ + base = 16; + } + /* scan the input for debug flags */ + while (*cp) { + if ((*cp >= '0') && (*cp <= '9')) { + found = 1; + u *= base; /* adjust to next digit */ + u += *cp++ - '0'; + continue; + } + if (base != 16) + break; /* end of number */ + + if ((*cp >= 'a') && (*cp <= 'f')) { + found = 1; + u *= base; /* adjust to next digit */ + u += *cp++ - 'a' + 10; + continue; + } + break; /* terminated */ + } + + if (found) { + card->debug_flags = u; /* remember debug flags */ + hysdn_addlog(card, "debug set to 0x%lx", card->debug_flags); + } + return (count); +} /* hysdn_log_write */ + +/******************/ +/* read log file */ +/******************/ +static ssize_t +hysdn_log_read(struct file *file, char __user *buf, size_t count, loff_t * off) +{ + struct log_data *inf; + int len; + struct proc_dir_entry *pde = PDE(file->f_dentry->d_inode); + struct procdata *pd = NULL; + hysdn_card *card; + + if (!*((struct log_data **) file->private_data)) { + if (file->f_flags & O_NONBLOCK) + return (-EAGAIN); + + /* sorry, but we need to search the card */ + card = card_root; + while (card) { + pd = card->proclog; + if (pd->log == pde) + break; + card = card->next; /* search next entry */ + } + if (card) + interruptible_sleep_on(&(pd->rd_queue)); + else + return (-EAGAIN); + + } + if (!(inf = *((struct log_data **) file->private_data))) + return (0); + + inf->usage_cnt--; /* new usage count */ + file->private_data = &inf->next; /* next structure */ + if ((len = strlen(inf->log_start)) <= count) { + if (copy_to_user(buf, inf->log_start, len)) + return -EFAULT; + *off += len; + return (len); + } + return (0); +} /* hysdn_log_read */ + +/******************/ +/* open log file */ +/******************/ +static int +hysdn_log_open(struct inode *ino, struct file *filep) +{ + hysdn_card *card; + struct procdata *pd = NULL; + ulong flags; + + lock_kernel(); + card = card_root; + while (card) { + pd = card->proclog; + if (pd->log == PDE(ino)) + break; + card = card->next; /* search next entry */ + } + if (!card) { + unlock_kernel(); + return (-ENODEV); /* device is unknown/invalid */ + } + filep->private_data = card; /* remember our own card */ + + if ((filep->f_mode & (FMODE_READ | FMODE_WRITE)) == FMODE_WRITE) { + /* write only access -> write log level only */ + } else if ((filep->f_mode & (FMODE_READ | FMODE_WRITE)) == FMODE_READ) { + + /* read access -> log/debug read */ + save_flags(flags); + cli(); + pd->if_used++; + if (pd->log_head) + filep->private_data = &pd->log_tail->next; + else + filep->private_data = &pd->log_head; + restore_flags(flags); + } else { /* simultaneous read/write access forbidden ! */ + unlock_kernel(); + return (-EPERM); /* no permission this time */ + } + unlock_kernel(); + return nonseekable_open(ino, filep); +} /* hysdn_log_open */ + +/*******************************************************************************/ +/* close a cardlog file. If the file has been opened for exclusive write it is */ +/* assumed as pof data input and the pof loader is noticed about. */ +/* Otherwise file is handled as log output. In this case the interface usage */ +/* count is decremented and all buffers are noticed of closing. If this file */ +/* was the last one to be closed, all buffers are freed. */ +/*******************************************************************************/ +static int +hysdn_log_close(struct inode *ino, struct file *filep) +{ + struct log_data *inf; + struct procdata *pd; + hysdn_card *card; + int retval = 0; + unsigned long flags; + + + lock_kernel(); + if ((filep->f_mode & (FMODE_READ | FMODE_WRITE)) == FMODE_WRITE) { + /* write only access -> write debug level written */ + retval = 0; /* success */ + } else { + /* read access -> log/debug read, mark one further file as closed */ + + pd = NULL; + save_flags(flags); + cli(); + inf = *((struct log_data **) filep->private_data); /* get first log entry */ + if (inf) + pd = (struct procdata *) inf->proc_ctrl; /* still entries there */ + else { + /* no info available -> search card */ + card = card_root; + while (card) { + pd = card->proclog; + if (pd->log == PDE(ino)) + break; + card = card->next; /* search next entry */ + } + if (card) + pd = card->proclog; /* pointer to procfs log */ + } + if (pd) + pd->if_used--; /* decrement interface usage count by one */ + + while (inf) { + inf->usage_cnt--; /* decrement usage count for buffers */ + inf = inf->next; + } + restore_flags(flags); + + if (pd) + if (pd->if_used <= 0) /* delete buffers if last file closed */ + while (pd->log_head) { + inf = pd->log_head; + pd->log_head = pd->log_head->next; + kfree(inf); + } + } /* read access */ + unlock_kernel(); + + return (retval); +} /* hysdn_log_close */ + +/*************************************************/ +/* select/poll routine to be able using select() */ +/*************************************************/ +static unsigned int +hysdn_log_poll(struct file *file, poll_table * wait) +{ + unsigned int mask = 0; + struct proc_dir_entry *pde = PDE(file->f_dentry->d_inode); + hysdn_card *card; + struct procdata *pd = NULL; + + if ((file->f_mode & (FMODE_READ | FMODE_WRITE)) == FMODE_WRITE) + return (mask); /* no polling for write supported */ + + /* we need to search the card */ + card = card_root; + while (card) { + pd = card->proclog; + if (pd->log == pde) + break; + card = card->next; /* search next entry */ + } + if (!card) + return (mask); /* card not found */ + + poll_wait(file, &(pd->rd_queue), wait); + + if (*((struct log_data **) file->private_data)) + mask |= POLLIN | POLLRDNORM; + + return mask; +} /* hysdn_log_poll */ + +/**************************************************/ +/* table for log filesystem functions defined above. */ +/**************************************************/ +static struct file_operations log_fops = +{ + .llseek = no_llseek, + .read = hysdn_log_read, + .write = hysdn_log_write, + .poll = hysdn_log_poll, + .open = hysdn_log_open, + .release = hysdn_log_close, +}; + + +/***********************************************************************************/ +/* hysdn_proclog_init is called when the module is loaded after creating the cards */ +/* conf files. */ +/***********************************************************************************/ +int +hysdn_proclog_init(hysdn_card * card) +{ + struct procdata *pd; + + /* create a cardlog proc entry */ + + if ((pd = (struct procdata *) kmalloc(sizeof(struct procdata), GFP_KERNEL)) != NULL) { + memset(pd, 0, sizeof(struct procdata)); + sprintf(pd->log_name, "%s%d", PROC_LOG_BASENAME, card->myid); + if ((pd->log = create_proc_entry(pd->log_name, S_IFREG | S_IRUGO | S_IWUSR, hysdn_proc_entry)) != NULL) { + pd->log->proc_fops = &log_fops; + pd->log->owner = THIS_MODULE; + } + + init_waitqueue_head(&(pd->rd_queue)); + + card->proclog = (void *) pd; /* remember procfs structure */ + } + return (0); +} /* hysdn_proclog_init */ + +/************************************************************************************/ +/* hysdn_proclog_release is called when the module is unloaded and before the cards */ +/* conf file is released */ +/* The module counter is assumed to be 0 ! */ +/************************************************************************************/ +void +hysdn_proclog_release(hysdn_card * card) +{ + struct procdata *pd; + + if ((pd = (struct procdata *) card->proclog) != NULL) { + if (pd->log) + remove_proc_entry(pd->log_name, hysdn_proc_entry); + kfree(pd); /* release memory */ + card->proclog = NULL; + } +} /* hysdn_proclog_release */ diff --git a/drivers/isdn/hysdn/hysdn_sched.c b/drivers/isdn/hysdn/hysdn_sched.c new file mode 100644 index 000000000000..4fa3b01707cd --- /dev/null +++ b/drivers/isdn/hysdn/hysdn_sched.c @@ -0,0 +1,207 @@ +/* $Id: hysdn_sched.c,v 1.5.6.4 2001/11/06 21:58:19 kai Exp $ + * + * Linux driver for HYSDN cards + * scheduler routines for handling exchange card <-> pc. + * + * Author Werner Cornelius (werner@titro.de) for Hypercope GmbH + * Copyright 1999 by Werner Cornelius (werner@titro.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/config.h> +#include <linux/sched.h> +#include <linux/signal.h> +#include <linux/kernel.h> +#include <linux/ioport.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <asm/io.h> + +#include "hysdn_defs.h" + +/*****************************************************************************/ +/* hysdn_sched_rx is called from the cards handler to announce new data is */ +/* available from the card. The routine has to handle the data and return */ +/* with a nonzero code if the data could be worked (or even thrown away), if */ +/* no room to buffer the data is available a zero return tells the card */ +/* to keep the data until later. */ +/*****************************************************************************/ +int +hysdn_sched_rx(hysdn_card * card, uchar * buf, word len, word chan) +{ + + switch (chan) { + case CHAN_NDIS_DATA: + if (hynet_enable & (1 << card->myid)) { + /* give packet to network handler */ + hysdn_rx_netpkt(card, buf, len); + } + break; + + case CHAN_ERRLOG: + hysdn_card_errlog(card, (tErrLogEntry *) buf, len); + if (card->err_log_state == ERRLOG_STATE_ON) + card->err_log_state = ERRLOG_STATE_START; /* start new fetch */ + break; +#ifdef CONFIG_HYSDN_CAPI + case CHAN_CAPI: +/* give packet to CAPI handler */ + if (hycapi_enable & (1 << card->myid)) { + hycapi_rx_capipkt(card, buf, len); + } + break; +#endif /* CONFIG_HYSDN_CAPI */ + default: + printk(KERN_INFO "irq message channel %d len %d unhandled \n", chan, len); + break; + + } /* switch rx channel */ + + return (1); /* always handled */ +} /* hysdn_sched_rx */ + +/*****************************************************************************/ +/* hysdn_sched_tx is called from the cards handler to announce that there is */ +/* room in the tx-buffer to the card and data may be sent if needed. */ +/* If the routine wants to send data it must fill buf, len and chan with the */ +/* appropriate data and return a nonzero value. With a zero return no new */ +/* data to send is assumed. maxlen specifies the buffer size available for */ +/* sending. */ +/*****************************************************************************/ +int +hysdn_sched_tx(hysdn_card * card, uchar * buf, word volatile *len, word volatile *chan, word maxlen) +{ + struct sk_buff *skb; + + if (card->net_tx_busy) { + card->net_tx_busy = 0; /* reset flag */ + hysdn_tx_netack(card); /* acknowledge packet send */ + } /* a network packet has completely been transferred */ + /* first of all async requests are handled */ + if (card->async_busy) { + if (card->async_len <= maxlen) { + memcpy(buf, card->async_data, card->async_len); + *len = card->async_len; + *chan = card->async_channel; + card->async_busy = 0; /* reset request */ + return (1); + } + card->async_busy = 0; /* in case of length error */ + } /* async request */ + if ((card->err_log_state == ERRLOG_STATE_START) && + (maxlen >= ERRLOG_CMD_REQ_SIZE)) { + strcpy(buf, ERRLOG_CMD_REQ); /* copy the command */ + *len = ERRLOG_CMD_REQ_SIZE; /* buffer length */ + *chan = CHAN_ERRLOG; /* and channel */ + card->err_log_state = ERRLOG_STATE_ON; /* new state is on */ + return (1); /* tell that data should be send */ + } /* error log start and able to send */ + if ((card->err_log_state == ERRLOG_STATE_STOP) && + (maxlen >= ERRLOG_CMD_STOP_SIZE)) { + strcpy(buf, ERRLOG_CMD_STOP); /* copy the command */ + *len = ERRLOG_CMD_STOP_SIZE; /* buffer length */ + *chan = CHAN_ERRLOG; /* and channel */ + card->err_log_state = ERRLOG_STATE_OFF; /* new state is off */ + return (1); /* tell that data should be send */ + } /* error log start and able to send */ + /* now handle network interface packets */ + if ((hynet_enable & (1 << card->myid)) && + (skb = hysdn_tx_netget(card)) != NULL) + { + if (skb->len <= maxlen) { + memcpy(buf, skb->data, skb->len); /* copy the packet to the buffer */ + *len = skb->len; + *chan = CHAN_NDIS_DATA; + card->net_tx_busy = 1; /* we are busy sending network data */ + return (1); /* go and send the data */ + } else + hysdn_tx_netack(card); /* aknowledge packet -> throw away */ + } /* send a network packet if available */ +#ifdef CONFIG_HYSDN_CAPI + if( ((hycapi_enable & (1 << card->myid))) && + ((skb = hycapi_tx_capiget(card)) != NULL) ) + { + if (skb->len <= maxlen) { + memcpy(buf, skb->data, skb->len); + *len = skb->len; + *chan = CHAN_CAPI; + hycapi_tx_capiack(card); + return (1); /* go and send the data */ + } + } +#endif /* CONFIG_HYSDN_CAPI */ + return (0); /* nothing to send */ +} /* hysdn_sched_tx */ + + +/*****************************************************************************/ +/* send one config line to the card and return 0 if successful, otherwise a */ +/* negative error code. */ +/* The function works with timeouts perhaps not giving the greatest speed */ +/* sending the line, but this should be meaningless beacuse only some lines */ +/* are to be sent and this happens very seldom. */ +/*****************************************************************************/ +int +hysdn_tx_cfgline(hysdn_card * card, uchar * line, word chan) +{ + int cnt = 50; /* timeout intervalls */ + ulong flags; + + if (card->debug_flags & LOG_SCHED_ASYN) + hysdn_addlog(card, "async tx-cfg chan=%d len=%d", chan, strlen(line) + 1); + + save_flags(flags); + cli(); + while (card->async_busy) { + sti(); + + if (card->debug_flags & LOG_SCHED_ASYN) + hysdn_addlog(card, "async tx-cfg delayed"); + + msleep_interruptible(20); /* Timeout 20ms */ + if (!--cnt) { + restore_flags(flags); + return (-ERR_ASYNC_TIME); /* timed out */ + } + cli(); + } /* wait for buffer to become free */ + + strcpy(card->async_data, line); + card->async_len = strlen(line) + 1; + card->async_channel = chan; + card->async_busy = 1; /* request transfer */ + + /* now queue the task */ + schedule_work(&card->irq_queue); + sti(); + + if (card->debug_flags & LOG_SCHED_ASYN) + hysdn_addlog(card, "async tx-cfg data queued"); + + cnt++; /* short delay */ + cli(); + + while (card->async_busy) { + sti(); + + if (card->debug_flags & LOG_SCHED_ASYN) + hysdn_addlog(card, "async tx-cfg waiting for tx-ready"); + + msleep_interruptible(20); /* Timeout 20ms */ + if (!--cnt) { + restore_flags(flags); + return (-ERR_ASYNC_TIME); /* timed out */ + } + cli(); + } /* wait for buffer to become free again */ + + restore_flags(flags); + + if (card->debug_flags & LOG_SCHED_ASYN) + hysdn_addlog(card, "async tx-cfg data send"); + + return (0); /* line send correctly */ +} /* hysdn_tx_cfgline */ diff --git a/drivers/isdn/hysdn/ince1pc.h b/drivers/isdn/hysdn/ince1pc.h new file mode 100644 index 000000000000..4a115a87c782 --- /dev/null +++ b/drivers/isdn/hysdn/ince1pc.h @@ -0,0 +1,134 @@ +/* + * Linux driver for HYSDN cards + * common definitions for both sides of the bus: + * - conventions both spoolers must know + * - channel numbers agreed upon + * + * Author M. Steinkopf + * Copyright 1999 by M. Steinkopf + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#ifndef __INCE1PC_H__ +#define __INCE1PC_H__ + +/* basic scalar definitions have same meanning, + * but their declaration location depends on environment + */ + +/*--------------------------------------channel numbers---------------------*/ +#define CHAN_SYSTEM 0x0001 /* system channel (spooler to spooler) */ +#define CHAN_ERRLOG 0x0005 /* error logger */ +#define CHAN_CAPI 0x0064 /* CAPI interface */ +#define CHAN_NDIS_DATA 0x1001 /* NDIS data transfer */ + +/*--------------------------------------POF ready msg-----------------------*/ + /* NOTE: after booting POF sends system ready message to PC: */ +#define RDY_MAGIC 0x52535953UL /* 'SYSR' reversed */ +#define RDY_MAGIC_SIZE 4 /* size in bytes */ + +#define MAX_N_TOK_BYTES 255 + +#define MIN_RDY_MSG_SIZE RDY_MAGIC_SIZE +#define MAX_RDY_MSG_SIZE (RDY_MAGIC_SIZE+MAX_N_TOK_BYTES) + +#define SYSR_TOK_END 0 +#define SYSR_TOK_B_CHAN 1 /* nr. of B-Channels; DataLen=1; def: 2 */ +#define SYSR_TOK_FAX_CHAN 2 /* nr. of FAX Channels; DataLen=1; def: 0 */ +#define SYSR_TOK_MAC_ADDR 3 /* MAC-Address; DataLen=6; def: auto */ +#define SYSR_TOK_ESC 255 /* undefined data size yet */ + /* default values, if not corrected by token: */ +#define SYSR_TOK_B_CHAN_DEF 2 /* assume 2 B-Channels */ +#define SYSR_TOK_FAX_CHAN_DEF 1 /* assume 1 FAX Channel */ + +/* syntax of new SYSR token stream: + * channel: CHAN_SYSTEM + * msgsize: MIN_RDY_MSG_SIZE <= x <= MAX_RDY_MSG_SIZE + * RDY_MAGIC_SIZE <= x <= (RDY_MAGIC_SIZE+MAX_N_TOK_BYTES) + * msg : 0 1 2 3 {4 5 6 ..} + * S Y S R MAX_N_TOK_BYTES bytes of TokenStream + * + * TokenStream := empty + * | {NonEndTokenChunk} EndToken RotlCRC + * NonEndTokenChunk:= NonEndTokenId DataLen [Data] + * NonEndTokenId := 0x01 .. 0xFE 1 BYTE + * DataLen := 0x00 .. 0xFF 1 BYTE + * Data := DataLen bytes + * EndToken := 0x00 + * RotlCRC := special 1 byte CRC over all NonEndTokenChunk bytes + * s. RotlCRC algorithm + * + * RotlCRC algorithm: + * ucSum= 0 1 uchar + * for all NonEndTokenChunk bytes: + * ROTL(ucSum,1) rotate left by 1 + * ucSum += Char; add current byte with swap around + * RotlCRC= ~ucSum; invert all bits for result + * + * note: + * - for 16-bit FIFO add padding 0 byte to achieve even token data bytes! + */ + +/*--------------------------------------error logger------------------------*/ + /* note: pof needs final 0 ! */ +#define ERRLOG_CMD_REQ "ERRLOG ON" +#define ERRLOG_CMD_REQ_SIZE 10 /* with final 0 byte ! */ +#define ERRLOG_CMD_STOP "ERRLOG OFF" +#define ERRLOG_CMD_STOP_SIZE 11 /* with final 0 byte ! */ + +#define ERRLOG_ENTRY_SIZE 64 /* sizeof(tErrLogEntry) */ + /* remaining text size = 55 */ +#define ERRLOG_TEXT_SIZE (ERRLOG_ENTRY_SIZE-2*4-1) + +typedef struct ErrLogEntry_tag { + +/*00 */ ulong ulErrType; + +/*04 */ ulong ulErrSubtype; + +/*08 */ uchar ucTextSize; + + /*09 */ uchar ucText[ERRLOG_TEXT_SIZE]; + /* ASCIIZ of len ucTextSize-1 */ + +/*40 */ +} tErrLogEntry; + + +#if defined(__TURBOC__) +#if sizeof(tErrLogEntry) != ERRLOG_ENTRY_SIZE +#error size of tErrLogEntry != ERRLOG_ENTRY_SIZE +#endif /* */ +#endif /* */ + +/*--------------------------------------DPRAM boot spooler------------------*/ + /* this is the struture used between pc and + * hyperstone to exchange boot data + */ +#define DPRAM_SPOOLER_DATA_SIZE 0x20 +typedef struct DpramBootSpooler_tag { + +/*00 */ uchar Len; + +/*01 */ volatile uchar RdPtr; + +/*02 */ uchar WrPtr; + +/*03 */ uchar Data[DPRAM_SPOOLER_DATA_SIZE]; + +/*23 */ +} tDpramBootSpooler; + + +#define DPRAM_SPOOLER_MIN_SIZE 5 /* Len+RdPtr+Wrptr+2*data */ +#define DPRAM_SPOOLER_DEF_SIZE 0x23 /* current default size */ + +/*--------------------------------------HYCARD/ERGO DPRAM SoftUart----------*/ + /* at DPRAM offset 0x1C00: */ +#define SIZE_RSV_SOFT_UART 0x1B0 /* 432 bytes reserved for SoftUart */ + + +#endif /* __INCE1PC_H__ */ diff --git a/drivers/isdn/i4l/Kconfig b/drivers/isdn/i4l/Kconfig new file mode 100644 index 000000000000..1789b607f090 --- /dev/null +++ b/drivers/isdn/i4l/Kconfig @@ -0,0 +1,141 @@ +# +# Old ISDN4Linux config +# + +config ISDN_PPP + bool "Support synchronous PPP" + depends on INET + help + Over digital connections such as ISDN, there is no need to + synchronize sender and recipient's clocks with start and stop bits + as is done over analog telephone lines. Instead, one can use + "synchronous PPP". Saying Y here will include this protocol. This + protocol is used by Cisco and Sun for example. So you want to say Y + here if the other end of your ISDN connection supports it. You will + need a special version of pppd (called ipppd) for using this + feature. See <file:Documentation/isdn/README.syncppp> and + <file:Documentation/isdn/syncPPP.FAQ> for more information. + +config ISDN_PPP_VJ + bool "Use VJ-compression with synchronous PPP" + depends on ISDN_PPP + help + This enables Van Jacobson header compression for synchronous PPP. + Say Y if the other end of the connection supports it. + +config ISDN_MPP + bool "Support generic MP (RFC 1717)" + depends on ISDN_PPP + help + With synchronous PPP enabled, it is possible to increase throughput + by bundling several ISDN-connections, using this protocol. See + <file:Documentation/isdn/README.syncppp> for more information. + +config IPPP_FILTER + bool "Filtering for synchronous PPP" + depends on ISDN_PPP + help + Say Y here if you want to be able to filter the packets passing over + IPPP interfaces. This allows you to control which packets count as + activity (i.e. which packets will reset the idle timer or bring up + a demand-dialled link) and which packets are to be dropped entirely. + You need to say Y here if you wish to use the pass-filter and + active-filter options to ipppd. + +config ISDN_PPP_BSDCOMP + tristate "Support BSD compression" + depends on ISDN_PPP + help + Support for the BSD-Compress compression method for PPP, which uses + the LZW compression method to compress each PPP packet before it is + sent over the wire. The machine at the other end of the PPP link + (usually your ISP) has to support the BSD-Compress compression + method as well for this to be useful. Even if they don't support it, + it is safe to say Y here. + +config ISDN_AUDIO + bool "Support audio via ISDN" + help + If you say Y here, the modem-emulator will support a subset of the + EIA Class 8 Voice commands. Using a getty with voice-support + (mgetty+sendfax by <gert@greenie.muc.de> with an extension, available + with the ISDN utility package for example), you will be able to use + your Linux box as an ISDN-answering machine. Of course, this must be + supported by the lowlevel driver also. Currently, the HiSax driver + is the only voice-supporting driver. See + <file:Documentation/isdn/README.audio> for more information. + +config ISDN_TTY_FAX + bool "Support AT-Fax Class 1 and 2 commands" + depends on ISDN_AUDIO + help + If you say Y here, the modem-emulator will support a subset of the + Fax Class 1 and 2 commands. Using a getty with fax-support + (mgetty+sendfax, hylafax), you will be able to use your Linux box as + an ISDN-fax-machine. This must be supported by the lowlevel driver + also. See <file:Documentation/isdn/README.fax> for more information. + +config ISDN_X25 + bool "X.25 PLP on top of ISDN" + depends on X25 + help + This feature provides the X.25 protocol over ISDN connections. + See <file:Documentation/isdn/README.x25> for more information + if you are thinking about using this. + + +menu "ISDN feature submodules" + depends on ISDN + +config ISDN_DRV_LOOP + tristate "isdnloop support" + depends on BROKEN_ON_SMP + help + This driver provides a virtual ISDN card. Its primary purpose is + testing of linklevel features or configuration without getting + charged by your service-provider for lots of phone calls. + You need will need the loopctrl utility from the latest isdn4k-utils + package to set up this driver. + +config ISDN_DIVERSION + tristate "Support isdn diversion services" + depends on ISDN && ISDN_I4L + help + This option allows you to use some supplementary diversion + services in conjunction with the HiSax driver on an EURO/DSS1 + line. + + Supported options are CD (call deflection), CFU (Call forward + unconditional), CFB (Call forward when busy) and CFNR (call forward + not reachable). Additionally the actual CFU, CFB and CFNR state may + be interrogated. + + The use of CFU, CFB, CFNR and interrogation may be limited to some + countries. The keypad protocol is still not implemented. CD should + work in all countries if the service has been subscribed to. + + Please read the file <file:Documentation/isdn/README.diversion>. + +endmenu + +comment "ISDN4Linux hardware drivers" + depends on NET && ISDN && ISDN_I4L + +source "drivers/isdn/hisax/Kconfig" + + +menu "Active cards" + depends on NET && ISDN && ISDN_I4L!=n + +source "drivers/isdn/icn/Kconfig" + +source "drivers/isdn/pcbit/Kconfig" + +source "drivers/isdn/sc/Kconfig" + +source "drivers/isdn/act2000/Kconfig" + +source "drivers/isdn/hysdn/Kconfig" + +endmenu + diff --git a/drivers/isdn/i4l/Makefile b/drivers/isdn/i4l/Makefile new file mode 100644 index 000000000000..49a06c0005dd --- /dev/null +++ b/drivers/isdn/i4l/Makefile @@ -0,0 +1,18 @@ +# Makefile for the kernel ISDN subsystem and device drivers. + +# Each configuration option enables a list of files. + +obj-$(CONFIG_ISDN_I4L) += isdn.o +obj-$(CONFIG_ISDN_PPP_BSDCOMP) += isdn_bsdcomp.o + +# Multipart objects. + +isdn-y := isdn_net.o isdn_tty.o isdn_v110.o isdn_common.o + +# Optional parts of multipart objects. + +isdn-$(CONFIG_ISDN_PPP) += isdn_ppp.o +isdn-$(CONFIG_ISDN_X25) += isdn_concap.o isdn_x25iface.o +isdn-$(CONFIG_ISDN_AUDIO) += isdn_audio.o +isdn-$(CONFIG_ISDN_TTY_FAX) += isdn_ttyfax.o + diff --git a/drivers/isdn/i4l/isdn_audio.c b/drivers/isdn/i4l/isdn_audio.c new file mode 100644 index 000000000000..5350836a4f98 --- /dev/null +++ b/drivers/isdn/i4l/isdn_audio.c @@ -0,0 +1,720 @@ +/* $Id: isdn_audio.c,v 1.1.2.2 2004/01/12 22:37:18 keil Exp $ + * + * Linux ISDN subsystem, audio conversion and compression (linklevel). + * + * Copyright 1994-1999 by Fritz Elfert (fritz@isdn4linux.de) + * DTMF code (c) 1996 by Christian Mock (cm@kukuruz.ping.at) + * Silence detection (c) 1998 by Armin Schindler (mac@gismo.telekom.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/isdn.h> +#include "isdn_audio.h" +#include "isdn_common.h" + +char *isdn_audio_revision = "$Revision: 1.1.2.2 $"; + +/* + * 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 8 /* number of frequencies to be analyzed */ +#define DTMF_TRESH 4000 /* above this is dtmf */ +#define SILENCE_TRESH 200 /* below this is silence */ +#define AMP_BITS 9 /* bits per sample, reduced to avoid overflow */ +#define LOGRP 0 +#define HIGRP 1 + +/* For DTMF recognition: + * 2 * cos(2 * PI * k / N) precalculated for all k + */ +static int cos2pik[NCOEFF] = +{ + 55813, 53604, 51193, 48591, 38114, 33057, 25889, 18332 +}; + +static char dtmf_matrix[4][4] = +{ + {'1', '2', '3', 'A'}, + {'4', '5', '6', 'B'}, + {'7', '8', '9', 'C'}, + {'*', '0', '#', 'D'} +}; + +static inline void +isdn_audio_tlookup(const u_char *table, u_char *buff, unsigned long n) +{ +#ifdef __i386__ + unsigned long d0, d1, d2, d3; + __asm__ __volatile__( + "cld\n" + "1:\tlodsb\n\t" + "xlatb\n\t" + "stosb\n\t" + "loop 1b\n\t" + : "=&b"(d0), "=&c"(d1), "=&D"(d2), "=&S"(d3) + : "0"((long) table), "1"(n), "2"((long) buff), "3"((long) buff) + : "memory", "ax"); +#else + while (n--) + *buff = table[*(unsigned char *)buff], 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. + */ +static 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); + 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; + } + /* Avoid overflows */ + sk >>= 1; + sk2 >>= 1; + /* compute |X(k)|**2 */ + /* report overflows. This should not happen. */ + /* Comment this out if desired */ + if (sk < -32768 || sk > 32767) + printk(KERN_DEBUG + "isdn_audio: dtmf goertzel overflow, sk=%d\n", sk); + if (sk2 < -32768 || sk2 > 32767) + printk(KERN_DEBUG + "isdn_audio: dtmf goertzel overflow, sk2=%d\n", sk2); + 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; + int grp[2]; + char what; + char *p; + int thresh; + + while ((skb = skb_dequeue(&info->dtmf_queue))) { + result = (int *) skb->data; + s = info->dtmf_state; + grp[LOGRP] = grp[HIGRP] = -1; + silence = 0; + thresh = 0; + for (i = 0; i < NCOEFF; i++) { + if (result[i] > DTMF_TRESH) { + if (result[i] > thresh) + thresh = result[i]; + } + else if (result[i] < SILENCE_TRESH) + silence++; + } + if (silence == NCOEFF) + what = ' '; + else { + if (thresh > 0) { + thresh = thresh >> 4; /* touchtones must match within 12 dB */ + for (i = 0; i < NCOEFF; i++) { + if (result[i] < thresh) + continue; /* ignore */ + /* good level found. This is allowed only one time per group */ + if (i < NCOEFF / 2) { + /* lowgroup*/ + if (grp[LOGRP] >= 0) { + // Bad. Another tone found. */ + grp[LOGRP] = -1; + break; + } + else + grp[LOGRP] = i; + } + else { /* higroup */ + if (grp[HIGRP] >= 0) { // Bad. Another tone found. */ + grp[HIGRP] = -1; + break; + } + else + grp[HIGRP] = i - NCOEFF/2; + } + } + if ((grp[LOGRP] >= 0) && (grp[HIGRP] >= 0)) { + what = dtmf_matrix[grp[LOGRP]][grp[HIGRP]]; + if (s->last != ' ' && s->last != '.') + s->last = what; /* min. 1 non-DTMF between DTMF */ + } else + what = '.'; + } + 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); + ISDN_AUDIO_SKB_DLECOUNT(skb) = 0; + ISDN_AUDIO_SKB_LOCK(skb) = 0; + di = info->isdn_driver; + ch = info->isdn_channel; + __skb_queue_tail(&dev->drv[di]->rpqueue[ch], skb); + dev->drv[di]->rcvcount[ch] += 2; + /* 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); + 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 = DTMF_NPOINTS - s->idx; + if (c > len) + c = len; + 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; + } +} + +silence_state * +isdn_audio_silence_init(silence_state * s) +{ + if (!s) + s = (silence_state *) kmalloc(sizeof(silence_state), GFP_ATOMIC); + if (s) { + s->idx = 0; + s->state = 0; + } + return s; +} + +void +isdn_audio_calc_silence(modem_info * info, unsigned char *buf, int len, int fmt) +{ + silence_state *s = info->silence_state; + int i; + signed char c; + + if (!info->emu.vpar[1]) return; + + for (i = 0; i < len; i++) { + if (fmt) + c = isdn_audio_alaw_to_ulaw[*buf++]; + else + c = *buf++; + + if (c > 0) c -= 128; + c = abs(c); + + if (c > (info->emu.vpar[1] * 4)) { + s->idx = 0; + s->state = 1; + } else { + if (s->idx < 210000) s->idx++; + } + } +} + +void +isdn_audio_put_dle_code(modem_info * info, u_char code) +{ + struct sk_buff *skb; + int di; + int ch; + char *p; + + skb = dev_alloc_skb(2); + if (!skb) { + printk(KERN_WARNING + "isdn_audio: Could not alloc skb for ttyI%d\n", + info->line); + return; + } + p = (char *) skb_put(skb, 2); + p[0] = 0x10; + p[1] = code; + ISDN_AUDIO_SKB_DLECOUNT(skb) = 0; + ISDN_AUDIO_SKB_LOCK(skb) = 0; + di = info->isdn_driver; + ch = info->isdn_channel; + __skb_queue_tail(&dev->drv[di]->rpqueue[ch], skb); + dev->drv[di]->rcvcount[ch] += 2; + /* Schedule dequeuing */ + if ((dev->modempoll) && (info->rcvsched)) + isdn_timer_ctrl(ISDN_TIMER_MODEMREAD, 1); + wake_up_interruptible(&dev->drv[di]->rcv_waitq[ch]); +} + +void +isdn_audio_eval_silence(modem_info * info) +{ + silence_state *s = info->silence_state; + char what; + + what = ' '; + + if (s->idx > (info->emu.vpar[2] * 800)) { + s->idx = 0; + if (!s->state) { /* silence from beginning of rec */ + what = 's'; + } else { + what = 'q'; + } + } + if ((what == 's') || (what == 'q')) { + printk(KERN_DEBUG "ttyI%d: %s\n", info->line, + (what=='s') ? "silence":"quiet"); + isdn_audio_put_dle_code(info, what); + } +} diff --git a/drivers/isdn/i4l/isdn_audio.h b/drivers/isdn/i4l/isdn_audio.h new file mode 100644 index 000000000000..5a977b21dcfa --- /dev/null +++ b/drivers/isdn/i4l/isdn_audio.h @@ -0,0 +1,45 @@ +/* $Id: isdn_audio.h,v 1.1.2.2 2004/01/12 22:37:18 keil Exp $ + * + * Linux ISDN subsystem, audio conversion and compression (linklevel). + * + * Copyright 1994-1999 by Fritz Elfert (fritz@isdn4linux.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#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; + char llast; + int idx; + int buf[DTMF_NPOINTS]; +} dtmf_state; + +typedef struct silence_state { + int state; + unsigned int idx; +} silence_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 *); +extern void isdn_audio_calc_silence(modem_info *, unsigned char *, int, int); +extern void isdn_audio_eval_silence(modem_info *); +silence_state *isdn_audio_silence_init(silence_state *); +extern void isdn_audio_put_dle_code(modem_info *, u_char); diff --git a/drivers/isdn/i4l/isdn_bsdcomp.c b/drivers/isdn/i4l/isdn_bsdcomp.c new file mode 100644 index 000000000000..baf4bcad9bf9 --- /dev/null +++ b/drivers/isdn/i4l/isdn_bsdcomp.c @@ -0,0 +1,937 @@ +/* + * BSD compression module + * + * Patched version for ISDN syncPPP written 1997/1998 by Michael Hipp + * The whole module is now SKB based. + * + */ + +/* + * Update: The Berkeley copyright was changed, and the change + * is retroactive to all "true" BSD software (ie everything + * from UCB as opposed to other peoples code that just carried + * the same license). The new copyright doesn't clash with the + * GPL, so the module-only restriction has been removed.. + */ + +/* + * Original copyright notice: + * + * Copyright (c) 1985, 1986 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * James A. Woods, derived from original work by Spencer Thomas + * and Joseph Orost. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/interrupt.h> +#include <linux/ptrace.h> +#include <linux/ioport.h> +#include <linux/in.h> +#include <linux/slab.h> +#include <linux/tty.h> +#include <linux/errno.h> +#include <linux/string.h> /* used in new tty drivers */ +#include <linux/signal.h> /* used in new tty drivers */ +#include <linux/bitops.h> + +#include <asm/system.h> +#include <asm/byteorder.h> +#include <asm/types.h> + +#include <linux/if.h> + +#include <linux/if_ether.h> +#include <linux/netdevice.h> +#include <linux/skbuff.h> +#include <linux/inet.h> +#include <linux/ioctl.h> +#include <linux/vmalloc.h> + +#include <linux/ppp_defs.h> + +#include <linux/isdn.h> +#include <linux/isdn_ppp.h> +#include <linux/ip.h> +#include <linux/tcp.h> +#include <linux/if_arp.h> +#include <linux/ppp-comp.h> + +#include "isdn_ppp.h" + +MODULE_DESCRIPTION("ISDN4Linux: BSD Compression for PPP over ISDN"); +MODULE_LICENSE("Dual BSD/GPL"); + +#define BSD_VERSION(x) ((x) >> 5) +#define BSD_NBITS(x) ((x) & 0x1F) + +#define BSD_CURRENT_VERSION 1 + +#define DEBUG 1 + +/* + * A dictionary for doing BSD compress. + */ + +struct bsd_dict { + u32 fcode; + u16 codem1; /* output of hash table -1 */ + u16 cptr; /* map code to hash table entry */ +}; + +struct bsd_db { + int totlen; /* length of this structure */ + unsigned int hsize; /* size of the hash table */ + unsigned char hshift; /* used in hash function */ + unsigned char n_bits; /* current bits/code */ + unsigned char maxbits; /* maximum bits/code */ + unsigned char debug; /* non-zero if debug desired */ + unsigned char unit; /* ppp unit number */ + u16 seqno; /* sequence # of next packet */ + unsigned int mru; /* size of receive (decompress) bufr */ + unsigned int maxmaxcode; /* largest valid code */ + unsigned int max_ent; /* largest code in use */ + unsigned int in_count; /* uncompressed bytes, aged */ + unsigned int bytes_out; /* compressed bytes, aged */ + unsigned int ratio; /* recent compression ratio */ + unsigned int checkpoint; /* when to next check the ratio */ + unsigned int clear_count; /* times dictionary cleared */ + unsigned int incomp_count; /* incompressible packets */ + unsigned int incomp_bytes; /* incompressible bytes */ + unsigned int uncomp_count; /* uncompressed packets */ + unsigned int uncomp_bytes; /* uncompressed bytes */ + unsigned int comp_count; /* compressed packets */ + unsigned int comp_bytes; /* compressed bytes */ + unsigned short *lens; /* array of lengths of codes */ + struct bsd_dict *dict; /* dictionary */ + int xmit; +}; + +#define BSD_OVHD 2 /* BSD compress overhead/packet */ +#define MIN_BSD_BITS 9 +#define BSD_INIT_BITS MIN_BSD_BITS +#define MAX_BSD_BITS 15 + +/* + * the next two codes should not be changed lightly, as they must not + * lie within the contiguous general code space. + */ +#define CLEAR 256 /* table clear output code */ +#define FIRST 257 /* first free entry */ +#define LAST 255 + +#define MAXCODE(b) ((1 << (b)) - 1) +#define BADCODEM1 MAXCODE(MAX_BSD_BITS); + +#define BSD_HASH(prefix,suffix,hshift) ((((unsigned long)(suffix))<<(hshift)) \ + ^ (unsigned long)(prefix)) +#define BSD_KEY(prefix,suffix) ((((unsigned long)(suffix)) << 16) \ + + (unsigned long)(prefix)) + +#define CHECK_GAP 10000 /* Ratio check interval */ + +#define RATIO_SCALE_LOG 8 +#define RATIO_SCALE (1<<RATIO_SCALE_LOG) +#define RATIO_MAX (0x7fffffff>>RATIO_SCALE_LOG) + +/* + * clear the dictionary + */ + +static void bsd_clear(struct bsd_db *db) +{ + db->clear_count++; + db->max_ent = FIRST-1; + db->n_bits = BSD_INIT_BITS; + db->bytes_out = 0; + db->in_count = 0; + db->incomp_count = 0; + db->ratio = 0; + db->checkpoint = CHECK_GAP; +} + +/* + * If the dictionary is full, then see if it is time to reset it. + * + * Compute the compression ratio using fixed-point arithmetic + * with 8 fractional bits. + * + * Since we have an infinite stream instead of a single file, + * watch only the local compression ratio. + * + * Since both peers must reset the dictionary at the same time even in + * the absence of CLEAR codes (while packets are incompressible), they + * must compute the same ratio. + */ +static int bsd_check (struct bsd_db *db) /* 1=output CLEAR */ +{ + unsigned int new_ratio; + + if (db->in_count >= db->checkpoint) + { + /* age the ratio by limiting the size of the counts */ + if (db->in_count >= RATIO_MAX || db->bytes_out >= RATIO_MAX) + { + db->in_count -= (db->in_count >> 2); + db->bytes_out -= (db->bytes_out >> 2); + } + + db->checkpoint = db->in_count + CHECK_GAP; + + if (db->max_ent >= db->maxmaxcode) + { + /* Reset the dictionary only if the ratio is worse, + * or if it looks as if it has been poisoned + * by incompressible data. + * + * This does not overflow, because + * db->in_count <= RATIO_MAX. + */ + + new_ratio = db->in_count << RATIO_SCALE_LOG; + if (db->bytes_out != 0) + { + new_ratio /= db->bytes_out; + } + + if (new_ratio < db->ratio || new_ratio < 1 * RATIO_SCALE) + { + bsd_clear (db); + return 1; + } + db->ratio = new_ratio; + } + } + return 0; +} + +/* + * Return statistics. + */ + +static void bsd_stats (void *state, struct compstat *stats) +{ + struct bsd_db *db = (struct bsd_db *) state; + + stats->unc_bytes = db->uncomp_bytes; + stats->unc_packets = db->uncomp_count; + stats->comp_bytes = db->comp_bytes; + stats->comp_packets = db->comp_count; + stats->inc_bytes = db->incomp_bytes; + stats->inc_packets = db->incomp_count; + stats->in_count = db->in_count; + stats->bytes_out = db->bytes_out; +} + +/* + * Reset state, as on a CCP ResetReq. + */ +static void bsd_reset (void *state,unsigned char code, unsigned char id, + unsigned char *data, unsigned len, + struct isdn_ppp_resetparams *rsparm) +{ + struct bsd_db *db = (struct bsd_db *) state; + + bsd_clear(db); + db->seqno = 0; + db->clear_count = 0; +} + +/* + * Release the compression structure + */ +static void bsd_free (void *state) +{ + struct bsd_db *db = (struct bsd_db *) state; + + if (db) { + /* + * Release the dictionary + */ + if (db->dict) { + vfree (db->dict); + db->dict = NULL; + } + + /* + * Release the string buffer + */ + if (db->lens) { + vfree (db->lens); + db->lens = NULL; + } + + /* + * Finally release the structure itself. + */ + kfree (db); + } +} + + +/* + * Allocate space for a (de) compressor. + */ +static void *bsd_alloc (struct isdn_ppp_comp_data *data) +{ + int bits; + unsigned int hsize, hshift, maxmaxcode; + struct bsd_db *db; + int decomp; + + static unsigned int htab[][2] = { + { 5003 , 4 } , { 5003 , 4 } , { 5003 , 4 } , { 5003 , 4 } , + { 9001 , 5 } , { 18013 , 6 } , { 35023 , 7 } , { 69001 , 8 } + }; + + if (data->optlen != 1 || data->num != CI_BSD_COMPRESS + || BSD_VERSION(data->options[0]) != BSD_CURRENT_VERSION) + return NULL; + + bits = BSD_NBITS(data->options[0]); + + if(bits < 9 || bits > 15) + return NULL; + + hsize = htab[bits-9][0]; + hshift = htab[bits-9][1]; + + /* + * Allocate the main control structure for this instance. + */ + maxmaxcode = MAXCODE(bits); + db = (struct bsd_db *) kmalloc (sizeof (struct bsd_db),GFP_KERNEL); + if (!db) + return NULL; + + memset (db, 0, sizeof(struct bsd_db)); + + db->xmit = data->flags & IPPP_COMP_FLAG_XMIT; + decomp = db->xmit ? 0 : 1; + + /* + * Allocate space for the dictionary. This may be more than one page in + * length. + */ + db->dict = (struct bsd_dict *) vmalloc (hsize * sizeof (struct bsd_dict)); + if (!db->dict) { + bsd_free (db); + return NULL; + } + + /* + * If this is the compression buffer then there is no length data. + * For decompression, the length information is needed as well. + */ + if (!decomp) + db->lens = NULL; + else { + db->lens = (unsigned short *) vmalloc ((maxmaxcode + 1) * + sizeof (db->lens[0])); + if (!db->lens) { + bsd_free (db); + return (NULL); + } + } + + /* + * Initialize the data information for the compression code + */ + db->totlen = sizeof (struct bsd_db) + (sizeof (struct bsd_dict) * hsize); + db->hsize = hsize; + db->hshift = hshift; + db->maxmaxcode = maxmaxcode; + db->maxbits = bits; + + return (void *) db; +} + +/* + * Initialize the database. + */ +static int bsd_init (void *state, struct isdn_ppp_comp_data *data, int unit, int debug) +{ + struct bsd_db *db = state; + int indx; + int decomp; + + if(!state || !data) { + printk(KERN_ERR "isdn_bsd_init: [%d] ERR, state %lx data %lx\n",unit,(long)state,(long)data); + return 0; + } + + decomp = db->xmit ? 0 : 1; + + if (data->optlen != 1 || data->num != CI_BSD_COMPRESS + || (BSD_VERSION(data->options[0]) != BSD_CURRENT_VERSION) + || (BSD_NBITS(data->options[0]) != db->maxbits) + || (decomp && db->lens == NULL)) { + printk(KERN_ERR "isdn_bsd: %d %d %d %d %lx\n",data->optlen,data->num,data->options[0],decomp,(unsigned long)db->lens); + return 0; + } + + if (decomp) + for(indx=LAST;indx>=0;indx--) + db->lens[indx] = 1; + + indx = db->hsize; + while (indx-- != 0) { + db->dict[indx].codem1 = BADCODEM1; + db->dict[indx].cptr = 0; + } + + db->unit = unit; + db->mru = 0; + + db->debug = 1; + + bsd_reset(db,0,0,NULL,0,NULL); + + return 1; +} + +/* + * Obtain pointers to the various structures in the compression tables + */ + +#define dict_ptrx(p,idx) &(p->dict[idx]) +#define lens_ptrx(p,idx) &(p->lens[idx]) + +#ifdef DEBUG +static unsigned short *lens_ptr(struct bsd_db *db, int idx) +{ + if ((unsigned int) idx > (unsigned int) db->maxmaxcode) { + printk (KERN_DEBUG "<9>ppp: lens_ptr(%d) > max\n", idx); + idx = 0; + } + return lens_ptrx (db, idx); +} + +static struct bsd_dict *dict_ptr(struct bsd_db *db, int idx) +{ + if ((unsigned int) idx >= (unsigned int) db->hsize) { + printk (KERN_DEBUG "<9>ppp: dict_ptr(%d) > max\n", idx); + idx = 0; + } + return dict_ptrx (db, idx); +} + +#else +#define lens_ptr(db,idx) lens_ptrx(db,idx) +#define dict_ptr(db,idx) dict_ptrx(db,idx) +#endif + +/* + * compress a packet + */ +static int bsd_compress (void *state, struct sk_buff *skb_in, struct sk_buff *skb_out,int proto) +{ + struct bsd_db *db; + int hshift; + unsigned int max_ent; + unsigned int n_bits; + unsigned int bitno; + unsigned long accm; + int ent; + unsigned long fcode; + struct bsd_dict *dictp; + unsigned char c; + int hval,disp,ilen,mxcode; + unsigned char *rptr = skb_in->data; + int isize = skb_in->len; + +#define OUTPUT(ent) \ + { \ + bitno -= n_bits; \ + accm |= ((ent) << bitno); \ + do { \ + if(skb_out && skb_tailroom(skb_out) > 0) \ + *(skb_put(skb_out,1)) = (unsigned char) (accm>>24); \ + accm <<= 8; \ + bitno += 8; \ + } while (bitno <= 24); \ + } + + /* + * If the protocol is not in the range we're interested in, + * just return without compressing the packet. If it is, + * the protocol becomes the first byte to compress. + */ + printk(KERN_DEBUG "bsd_compress called with %x\n",proto); + + ent = proto; + if (proto < 0x21 || proto > 0xf9 || !(proto & 0x1) ) + return 0; + + db = (struct bsd_db *) state; + hshift = db->hshift; + max_ent = db->max_ent; + n_bits = db->n_bits; + bitno = 32; + accm = 0; + mxcode = MAXCODE (n_bits); + + /* This is the PPP header information */ + if(skb_out && skb_tailroom(skb_out) >= 2) { + char *v = skb_put(skb_out,2); + /* we only push our own data on the header, + AC,PC and protos is pushed by caller */ + v[0] = db->seqno >> 8; + v[1] = db->seqno; + } + + ilen = ++isize; /* This is off by one, but that is what is in draft! */ + + while (--ilen > 0) { + c = *rptr++; + fcode = BSD_KEY (ent, c); + hval = BSD_HASH (ent, c, hshift); + dictp = dict_ptr (db, hval); + + /* Validate and then check the entry. */ + if (dictp->codem1 >= max_ent) + goto nomatch; + + if (dictp->fcode == fcode) { + ent = dictp->codem1 + 1; + continue; /* found (prefix,suffix) */ + } + + /* continue probing until a match or invalid entry */ + disp = (hval == 0) ? 1 : hval; + + do { + hval += disp; + if (hval >= db->hsize) + hval -= db->hsize; + dictp = dict_ptr (db, hval); + if (dictp->codem1 >= max_ent) + goto nomatch; + } while (dictp->fcode != fcode); + + ent = dictp->codem1 + 1; /* finally found (prefix,suffix) */ + continue; + +nomatch: + OUTPUT(ent); /* output the prefix */ + + /* code -> hashtable */ + if (max_ent < db->maxmaxcode) { + struct bsd_dict *dictp2; + struct bsd_dict *dictp3; + int indx; + + /* expand code size if needed */ + if (max_ent >= mxcode) { + db->n_bits = ++n_bits; + mxcode = MAXCODE (n_bits); + } + + /* + * Invalidate old hash table entry using + * this code, and then take it over. + */ + dictp2 = dict_ptr (db, max_ent + 1); + indx = dictp2->cptr; + dictp3 = dict_ptr (db, indx); + + if (dictp3->codem1 == max_ent) + dictp3->codem1 = BADCODEM1; + + dictp2->cptr = hval; + dictp->codem1 = max_ent; + dictp->fcode = fcode; + db->max_ent = ++max_ent; + + if (db->lens) { + unsigned short *len1 = lens_ptr (db, max_ent); + unsigned short *len2 = lens_ptr (db, ent); + *len1 = *len2 + 1; + } + } + ent = c; + } + + OUTPUT(ent); /* output the last code */ + + if(skb_out) + db->bytes_out += skb_out->len; /* Do not count bytes from here */ + db->uncomp_bytes += isize; + db->in_count += isize; + ++db->uncomp_count; + ++db->seqno; + + if (bitno < 32) + ++db->bytes_out; /* must be set before calling bsd_check */ + + /* + * Generate the clear command if needed + */ + + if (bsd_check(db)) + OUTPUT (CLEAR); + + /* + * Pad dribble bits of last code with ones. + * Do not emit a completely useless byte of ones. + */ + if (bitno < 32 && skb_out && skb_tailroom(skb_out) > 0) + *(skb_put(skb_out,1)) = (unsigned char) ((accm | (0xff << (bitno-8))) >> 24); + + /* + * Increase code size if we would have without the packet + * boundary because the decompressor will do so. + */ + if (max_ent >= mxcode && max_ent < db->maxmaxcode) + db->n_bits++; + + /* If output length is too large then this is an incompressible frame. */ + if (!skb_out || (skb_out && skb_out->len >= skb_in->len) ) { + ++db->incomp_count; + db->incomp_bytes += isize; + return 0; + } + + /* Count the number of compressed frames */ + ++db->comp_count; + db->comp_bytes += skb_out->len; + return skb_out->len; + +#undef OUTPUT +} + +/* + * Update the "BSD Compress" dictionary on the receiver for + * incompressible data by pretending to compress the incoming data. + */ +static void bsd_incomp (void *state, struct sk_buff *skb_in,int proto) +{ + bsd_compress (state, skb_in, NULL, proto); +} + +/* + * Decompress "BSD Compress". + */ +static int bsd_decompress (void *state, struct sk_buff *skb_in, struct sk_buff *skb_out, + struct isdn_ppp_resetparams *rsparm) +{ + struct bsd_db *db; + unsigned int max_ent; + unsigned long accm; + unsigned int bitno; /* 1st valid bit in accm */ + unsigned int n_bits; + unsigned int tgtbitno; /* bitno when we have a code */ + struct bsd_dict *dictp; + int seq; + unsigned int incode; + unsigned int oldcode; + unsigned int finchar; + unsigned char *p,*ibuf; + int ilen; + int codelen; + int extra; + + db = (struct bsd_db *) state; + max_ent = db->max_ent; + accm = 0; + bitno = 32; /* 1st valid bit in accm */ + n_bits = db->n_bits; + tgtbitno = 32 - n_bits; /* bitno when we have a code */ + + printk(KERN_DEBUG "bsd_decompress called\n"); + + if(!skb_in || !skb_out) { + printk(KERN_ERR "bsd_decompress called with NULL parameter\n"); + return DECOMP_ERROR; + } + + /* + * Get the sequence number. + */ + if( (p = skb_pull(skb_in,2)) == NULL) { + return DECOMP_ERROR; + } + p-=2; + seq = (p[0] << 8) + p[1]; + ilen = skb_in->len; + ibuf = skb_in->data; + + /* + * Check the sequence number and give up if it differs from + * the value we're expecting. + */ + if (seq != db->seqno) { + if (db->debug) { + printk(KERN_DEBUG "bsd_decomp%d: bad sequence # %d, expected %d\n", + db->unit, seq, db->seqno - 1); + } + return DECOMP_ERROR; + } + + ++db->seqno; + db->bytes_out += ilen; + + if(skb_tailroom(skb_out) > 0) + *(skb_put(skb_out,1)) = 0; + else + return DECOMP_ERR_NOMEM; + + oldcode = CLEAR; + + /* + * Keep the checkpoint correctly so that incompressible packets + * clear the dictionary at the proper times. + */ + + for (;;) { + if (ilen-- <= 0) { + db->in_count += (skb_out->len - 1); /* don't count the header */ + break; + } + + /* + * Accumulate bytes until we have a complete code. + * Then get the next code, relying on the 32-bit, + * unsigned accm to mask the result. + */ + + bitno -= 8; + accm |= *ibuf++ << bitno; + if (tgtbitno < bitno) + continue; + + incode = accm >> tgtbitno; + accm <<= n_bits; + bitno += n_bits; + + /* + * The dictionary must only be cleared at the end of a packet. + */ + + if (incode == CLEAR) { + if (ilen > 0) { + if (db->debug) + printk(KERN_DEBUG "bsd_decomp%d: bad CLEAR\n", db->unit); + return DECOMP_FATALERROR; /* probably a bug */ + } + bsd_clear(db); + break; + } + + if ((incode > max_ent + 2) || (incode > db->maxmaxcode) + || (incode > max_ent && oldcode == CLEAR)) { + if (db->debug) { + printk(KERN_DEBUG "bsd_decomp%d: bad code 0x%x oldcode=0x%x ", + db->unit, incode, oldcode); + printk(KERN_DEBUG "max_ent=0x%x skb->Len=%d seqno=%d\n", + max_ent, skb_out->len, db->seqno); + } + return DECOMP_FATALERROR; /* probably a bug */ + } + + /* Special case for KwKwK string. */ + if (incode > max_ent) { + finchar = oldcode; + extra = 1; + } else { + finchar = incode; + extra = 0; + } + + codelen = *(lens_ptr (db, finchar)); + if( skb_tailroom(skb_out) < codelen + extra) { + if (db->debug) { + printk(KERN_DEBUG "bsd_decomp%d: ran out of mru\n", db->unit); +#ifdef DEBUG + printk(KERN_DEBUG " len=%d, finchar=0x%x, codelen=%d,skblen=%d\n", + ilen, finchar, codelen, skb_out->len); +#endif + } + return DECOMP_FATALERROR; + } + + /* + * Decode this code and install it in the decompressed buffer. + */ + + p = skb_put(skb_out,codelen); + p += codelen; + while (finchar > LAST) { + struct bsd_dict *dictp2 = dict_ptr (db, finchar); + + dictp = dict_ptr (db, dictp2->cptr); + +#ifdef DEBUG + if (--codelen <= 0 || dictp->codem1 != finchar-1) { + if (codelen <= 0) { + printk(KERN_ERR "bsd_decomp%d: fell off end of chain ", db->unit); + printk(KERN_ERR "0x%x at 0x%x by 0x%x, max_ent=0x%x\n", incode, finchar, dictp2->cptr, max_ent); + } else { + if (dictp->codem1 != finchar-1) { + printk(KERN_ERR "bsd_decomp%d: bad code chain 0x%x finchar=0x%x ",db->unit, incode, finchar); + printk(KERN_ERR "oldcode=0x%x cptr=0x%x codem1=0x%x\n", oldcode, dictp2->cptr, dictp->codem1); + } + } + return DECOMP_FATALERROR; + } +#endif + + { + u32 fcode = dictp->fcode; + *--p = (fcode >> 16) & 0xff; + finchar = fcode & 0xffff; + } + } + *--p = finchar; + +#ifdef DEBUG + if (--codelen != 0) + printk(KERN_ERR "bsd_decomp%d: short by %d after code 0x%x, max_ent=0x%x\n", db->unit, codelen, incode, max_ent); +#endif + + if (extra) /* the KwKwK case again */ + *(skb_put(skb_out,1)) = finchar; + + /* + * If not first code in a packet, and + * if not out of code space, then allocate a new code. + * + * Keep the hash table correct so it can be used + * with uncompressed packets. + */ + if (oldcode != CLEAR && max_ent < db->maxmaxcode) { + struct bsd_dict *dictp2, *dictp3; + u16 *lens1, *lens2; + unsigned long fcode; + int hval, disp, indx; + + fcode = BSD_KEY(oldcode,finchar); + hval = BSD_HASH(oldcode,finchar,db->hshift); + dictp = dict_ptr (db, hval); + + /* look for a free hash table entry */ + if (dictp->codem1 < max_ent) { + disp = (hval == 0) ? 1 : hval; + do { + hval += disp; + if (hval >= db->hsize) + hval -= db->hsize; + dictp = dict_ptr (db, hval); + } while (dictp->codem1 < max_ent); + } + + /* + * Invalidate previous hash table entry + * assigned this code, and then take it over + */ + + dictp2 = dict_ptr (db, max_ent + 1); + indx = dictp2->cptr; + dictp3 = dict_ptr (db, indx); + + if (dictp3->codem1 == max_ent) + dictp3->codem1 = BADCODEM1; + + dictp2->cptr = hval; + dictp->codem1 = max_ent; + dictp->fcode = fcode; + db->max_ent = ++max_ent; + + /* Update the length of this string. */ + lens1 = lens_ptr (db, max_ent); + lens2 = lens_ptr (db, oldcode); + *lens1 = *lens2 + 1; + + /* Expand code size if needed. */ + if (max_ent >= MAXCODE(n_bits) && max_ent < db->maxmaxcode) { + db->n_bits = ++n_bits; + tgtbitno = 32-n_bits; + } + } + oldcode = incode; + } + + ++db->comp_count; + ++db->uncomp_count; + db->comp_bytes += skb_in->len - BSD_OVHD; + db->uncomp_bytes += skb_out->len; + + if (bsd_check(db)) { + if (db->debug) + printk(KERN_DEBUG "bsd_decomp%d: peer should have cleared dictionary on %d\n", + db->unit, db->seqno - 1); + } + return skb_out->len; +} + +/************************************************************* + * Table of addresses for the BSD compression module + *************************************************************/ + +static struct isdn_ppp_compressor ippp_bsd_compress = { + .owner = THIS_MODULE, + .num = CI_BSD_COMPRESS, + .alloc = bsd_alloc, + .free = bsd_free, + .init = bsd_init, + .reset = bsd_reset, + .compress = bsd_compress, + .decompress = bsd_decompress, + .incomp = bsd_incomp, + .stat = bsd_stats, +}; + +/************************************************************* + * Module support routines + *************************************************************/ + +static int __init isdn_bsdcomp_init(void) +{ + int answer = isdn_ppp_register_compressor (&ippp_bsd_compress); + if (answer == 0) + printk (KERN_INFO "PPP BSD Compression module registered\n"); + return answer; +} + +static void __exit isdn_bsdcomp_exit(void) +{ + isdn_ppp_unregister_compressor (&ippp_bsd_compress); +} + +module_init(isdn_bsdcomp_init); +module_exit(isdn_bsdcomp_exit); diff --git a/drivers/isdn/i4l/isdn_common.c b/drivers/isdn/i4l/isdn_common.c new file mode 100644 index 000000000000..c406df6f268a --- /dev/null +++ b/drivers/isdn/i4l/isdn_common.c @@ -0,0 +1,2253 @@ +/* $Id: isdn_common.c,v 1.1.2.3 2004/02/10 01:07:13 keil Exp $ + * + * Linux ISDN subsystem, common used functions (linklevel). + * + * Copyright 1994-1999 by Fritz Elfert (fritz@isdn4linux.de) + * Copyright 1995,96 Thinking Objects Software GmbH Wuerzburg + * Copyright 1995,96 by Michael Hipp (Michael.Hipp@student.uni-tuebingen.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/version.h> +#include <linux/poll.h> +#include <linux/vmalloc.h> +#include <linux/isdn.h> +#include <linux/smp_lock.h> +#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 +#ifdef CONFIG_ISDN_DIVERSION_MODULE +#define CONFIG_ISDN_DIVERSION +#endif +#ifdef CONFIG_ISDN_DIVERSION +#include <linux/isdn_divertif.h> +#endif /* CONFIG_ISDN_DIVERSION */ +#include "isdn_v110.h" + +/* Debugflags */ +#undef ISDN_DEBUG_STATCALLB + +MODULE_DESCRIPTION("ISDN4Linux: link layer"); +MODULE_AUTHOR("Fritz Elfert"); +MODULE_LICENSE("GPL"); + +isdn_dev *dev; + +static char *isdn_revision = "$Revision: 1.1.2.3 $"; + +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 +extern char *isdn_v110_revision; + +#ifdef CONFIG_ISDN_DIVERSION +static isdn_divert_if *divert_if; /* = NULL */ +#endif /* CONFIG_ISDN_DIVERSION */ + + +static int isdn_writebuf_stub(int, int, const u_char __user *, int); +static void set_global_features(void); +static int isdn_wildmat(char *s, char *p); + + +static inline void +isdn_lock_driver(isdn_driver_t *drv) +{ + try_module_get(drv->interface->owner); + drv->locks++; +} + +void +isdn_lock_drivers(void) +{ + int i; + + for (i = 0; i < ISDN_MAX_DRIVERS; i++) { + if (!dev->drv[i]) + continue; + isdn_lock_driver(dev->drv[i]); + } +} + +static inline void +isdn_unlock_driver(isdn_driver_t *drv) +{ + if (drv->locks > 0) { + drv->locks--; + module_put(drv->interface->owner); + } +} + +void +isdn_unlock_drivers(void) +{ + int i; + + for (i = 0; i < ISDN_MAX_DRIVERS; i++) { + if (!dev->drv[i]) + continue; + isdn_unlock_driver(dev->drv[i]); + } +} + +#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 + +/* + * 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_star(char *s, char *p) +{ + while (isdn_wildmat(s, p)) { + if (*++s == '\0') + return (2); + } + return (0); +} + +/* + * 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. + * + * Return: + * 0 = match. + * 1 = no match. + * 2 = no match. Would eventually match, if s would be longer. + * + * 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_wildmat(char *s, char *p) +{ + register int last; + register int matched; + register int reverse; + register int nostar = 1; + + if (!(*s) && !(*p)) + return(1); + for (; *p; s++, p++) + switch (*p) { + case '\\': + /* + * Literal match with following character, + * fall through. + */ + p++; + default: + if (*s != *p) + return (*s == '\0')?2:1; + continue; + case '?': + /* Match anything. */ + if (*s == '\0') + return (2); + continue; + case '*': + nostar = 0; + /* Trailing star matches everything. */ + return (*++p ? isdn_star(s, p) : 0); + 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 (1); + continue; + } + return (*s == '\0')?0:nostar; +} + +int isdn_msncmp( const char * msn1, const char * msn2 ) +{ + char TmpMsn1[ ISDN_MSNLEN ]; + char TmpMsn2[ ISDN_MSNLEN ]; + char *p; + + for ( p = TmpMsn1; *msn1 && *msn1 != ':'; ) // Strip off a SPID + *p++ = *msn1++; + *p = '\0'; + + for ( p = TmpMsn2; *msn2 && *msn2 != ':'; ) // Strip off a SPID + *p++ = *msn2++; + *p = '\0'; + + return isdn_wildmat( TmpMsn1, TmpMsn2 ); +} + +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 int isdn_timer_cnt3 = 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 (++isdn_timer_cnt3 >= ISDN_TIMER_RINGING) { + isdn_timer_cnt3 = 0; + if (tf & ISDN_TIMER_MODEMRING) + isdn_tty_modem_ring(); + } + if (tf & ISDN_TIMER_CARRIER) + isdn_tty_carrier_timeout(); + } + } + if (tf) + mod_timer(&dev->timer, jiffies+ISDN_TIMER_RES); +} + +void +isdn_timer_ctrl(int tf, int onoff) +{ + unsigned long flags; + int old_tflags; + + spin_lock_irqsave(&dev->timerlock, flags); + 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; + } + old_tflags = dev->tflags; + if (onoff) + dev->tflags |= tf; + else + dev->tflags &= ~tf; + if (dev->tflags && !old_tflags) + mod_timer(&dev->timer, jiffies+ISDN_TIMER_RES); + spin_unlock_irqrestore(&dev->timerlock, 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) +{ + int i; + + if ((i = isdn_dc2minor(di, channel)) == -1) { + dev_kfree_skb(skb); + return; + } + /* Update statistics */ + dev->ibytes[i] += skb->len; + + /* First, try to deliver data to network-device */ + if (isdn_net_rcv_skb(i, skb)) + return; + + /* V.110 handling + * makes sense for async streams only, so it is + * called after possible net-device delivery. + */ + if (dev->v110[i]) { + atomic_inc(&dev->v110use[i]); + skb = isdn_v110_decode(dev->v110[i], skb); + atomic_dec(&dev->v110use[i]); + if (!skb) + return; + } + + /* No network-device found, deliver to tty or raw-channel */ + if (skb->len) { + if (isdn_tty_rcv_skb(i, di, channel, skb)) + return; + wake_up_interruptible(&dev->drv[di]->rcv_waitq[channel]); + } else + dev_kfree_skb(skb); +} + +/* + * Intercept command from Linklevel to Lowlevel. + * If layer 2 protocol is V.110 and this is not supported by current + * lowlevel-driver, use driver's transparent mode and handle V.110 in + * linklevel instead. + */ +int +isdn_command(isdn_ctrl *cmd) +{ + if (cmd->driver == -1) { + printk(KERN_WARNING "isdn_command command(%x) driver -1\n", cmd->command); + return(1); + } + if (cmd->command == ISDN_CMD_SETL2) { + int idx = isdn_dc2minor(cmd->driver, cmd->arg & 255); + unsigned long l2prot = (cmd->arg >> 8) & 255; + unsigned long features = (dev->drv[cmd->driver]->interface->features + >> ISDN_FEATURE_L2_SHIFT) & + ISDN_FEATURE_L2_MASK; + unsigned long l2_feature = (1 << l2prot); + + switch (l2prot) { + case ISDN_PROTO_L2_V11096: + case ISDN_PROTO_L2_V11019: + case ISDN_PROTO_L2_V11038: + /* If V.110 requested, but not supported by + * HL-driver, set emulator-flag and change + * Layer-2 to transparent + */ + if (!(features & l2_feature)) { + dev->v110emu[idx] = l2prot; + cmd->arg = (cmd->arg & 255) | + (ISDN_PROTO_L2_TRANS << 8); + } else + dev->v110emu[idx] = 0; + } + } + return dev->drv[cmd->driver]->interface->command(cmd); +} + +void +isdn_all_eaz(int di, int ch) +{ + isdn_ctrl cmd; + + if (di < 0) + return; + cmd.driver = di; + cmd.arg = ch; + cmd.command = ISDN_CMD_SETEAZ; + cmd.parm.num[0] = '\0'; + isdn_command(&cmd); +} + +/* + * Begin of a CAPI like LL<->HL interface, currently used only for + * supplementary service (CAPI 2.0 part III) + */ +#include <linux/isdn/capicmd.h> + +int +isdn_capi_rec_hl_msg(capi_msg *cm) { + + int di; + int ch; + + di = (cm->adr.Controller & 0x7f) -1; + ch = isdn_dc2minor(di, (cm->adr.Controller>>8)& 0x7f); + switch(cm->Command) { + case CAPI_FACILITY: + /* in the moment only handled in tty */ + return(isdn_tty_capi_facility(cm)); + default: + return(-1); + } +} + +static int +isdn_status_callback(isdn_ctrl * c) +{ + int di; + u_long flags; + int i; + int r; + int retval = 0; + isdn_ctrl cmd; + isdn_net_dev *p; + + 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)) + return 0; + if (isdn_v110_stat_callback(i, c)) + return 0; + if (isdn_tty_stat_callback(i, c)) + return 0; + wake_up_interruptible(&dev->drv[di]->snd_waitq[c->arg]); + break; + case ISDN_STAT_STAVAIL: + dev->drv[di]->stavail += c->arg; + wake_up_interruptible(&dev->drv[di]->st_waitq); + break; + case ISDN_STAT_RUN: + dev->drv[di]->flags |= DRV_FLAG_RUNNING; + for (i = 0; i < ISDN_MAX_CHANNELS; i++) + if (dev->drvmap[i] == di) + isdn_all_eaz(di, dev->chanmap[i]); + set_global_features(); + break; + case ISDN_STAT_STOP: + dev->drv[di]->flags &= ~DRV_FLAG_RUNNING; + 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->parm.num); +#endif + if (dev->global_flags & ISDN_GLOBAL_STOPPED) { + cmd.driver = di; + cmd.arg = c->arg; + cmd.command = ISDN_CMD_HANGUP; + isdn_command(&cmd); + return 0; + } + /* Try to find a network-interface which will accept incoming call */ + r = ((c->command == ISDN_STAT_ICALLW) ? 0 : isdn_net_find_icall(di, c->arg, i, &c->parm.setup)); + switch (r) { + case 0: + /* No network-device replies. + * Try ttyI's. + * These return 0 on no match, 1 on match and + * 3 on eventually match, if CID is longer. + */ + if (c->command == ISDN_STAT_ICALL) + if ((retval = isdn_tty_find_icall(di, c->arg, &c->parm.setup))) return(retval); +#ifdef CONFIG_ISDN_DIVERSION + if (divert_if) + if ((retval = divert_if->stat_callback(c))) + return(retval); /* processed */ +#endif /* CONFIG_ISDN_DIVERSION */ + if ((!retval) && (dev->drv[di]->flags & DRV_FLAG_REJBUS)) { + /* No tty responding */ + cmd.driver = di; + cmd.arg = c->arg; + cmd.command = ISDN_CMD_HANGUP; + isdn_command(&cmd); + retval = 2; + } + break; + case 1: + /* Schedule connection-setup */ + isdn_net_dial(); + cmd.driver = di; + cmd.arg = c->arg; + cmd.command = ISDN_CMD_ACCEPTD; + for ( p = dev->netdev; p; p = p->next ) + if ( p->local->isdn_channel == cmd.arg ) + { + strcpy( cmd.parm.setup.eazmsn, p->local->msn ); + isdn_command(&cmd); + retval = 1; + break; + } + break; + + case 2: /* For calling back, first reject incoming call ... */ + case 3: /* Interface found, but down, reject call actively */ + retval = 2; + printk(KERN_INFO "isdn: Rejecting Call\n"); + cmd.driver = di; + cmd.arg = c->arg; + cmd.command = ISDN_CMD_HANGUP; + isdn_command(&cmd); + if (r == 3) + break; + /* Fall through */ + case 4: + /* ... then start callback. */ + isdn_net_dial(); + break; + case 5: + /* Number would eventually match, if longer */ + retval = 3; + break; + } +#ifdef ISDN_DEBUG_STATCALLB + printk(KERN_DEBUG "ICALL: ret=%d\n", retval); +#endif + return retval; + break; + case ISDN_STAT_CINF: + if (i < 0) + return -1; +#ifdef ISDN_DEBUG_STATCALLB + printk(KERN_DEBUG "CINF: %ld %s\n", c->arg, c->parm.num); +#endif + if (dev->global_flags & ISDN_GLOBAL_STOPPED) + return 0; + if (strcmp(c->parm.num, "0")) + isdn_net_stat_callback(i, c); + isdn_tty_stat_callback(i, c); + break; + case ISDN_STAT_CAUSE: +#ifdef ISDN_DEBUG_STATCALLB + printk(KERN_DEBUG "CAUSE: %ld %s\n", c->arg, c->parm.num); +#endif + printk(KERN_INFO "isdn: %s,ch%ld cause: %s\n", + dev->drvid[di], c->arg, c->parm.num); + isdn_tty_stat_callback(i, c); +#ifdef CONFIG_ISDN_DIVERSION + if (divert_if) + divert_if->stat_callback(c); +#endif /* CONFIG_ISDN_DIVERSION */ + break; + case ISDN_STAT_DISPLAY: +#ifdef ISDN_DEBUG_STATCALLB + printk(KERN_DEBUG "DISPLAY: %ld %s\n", c->arg, c->parm.display); +#endif + isdn_tty_stat_callback(i, c); +#ifdef CONFIG_ISDN_DIVERSION + if (divert_if) + divert_if->stat_callback(c); +#endif /* CONFIG_ISDN_DIVERSION */ + 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 net-device, waiting for D-channel setup */ + if (isdn_net_stat_callback(i, c)) + break; + isdn_v110_stat_callback(i, c); + /* Find any ttyI, waiting for D-channel setup */ + if (isdn_tty_stat_callback(i, c)) { + cmd.driver = di; + cmd.arg = c->arg; + cmd.command = ISDN_CMD_ACCEPTB; + isdn_command(&cmd); + break; + } + 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]->online &= ~(1 << (c->arg)); + isdn_info_update(); + /* Signal hangup to network-devices */ + if (isdn_net_stat_callback(i, c)) + break; + isdn_v110_stat_callback(i, c); + if (isdn_tty_stat_callback(i, c)) + break; +#ifdef CONFIG_ISDN_DIVERSION + if (divert_if) + divert_if->stat_callback(c); +#endif /* CONFIG_ISDN_DIVERSION */ + break; + 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]->online |= (1 << (c->arg)); + isdn_info_update(); + if (isdn_net_stat_callback(i, c)) + break; + isdn_v110_stat_callback(i, c); + if (isdn_tty_stat_callback(i, c)) + break; + 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]->online &= ~(1 << (c->arg)); + isdn_info_update(); +#ifdef CONFIG_ISDN_X25 + /* Signal hangup to network-devices */ + if (isdn_net_stat_callback(i, c)) + break; +#endif + isdn_v110_stat_callback(i, c); + if (isdn_tty_stat_callback(i, c)) + break; + 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)) + break; + if (isdn_tty_stat_callback(i, c)) + break; + break; + case ISDN_STAT_ADDCH: + spin_lock_irqsave(&dev->lock, flags); + if (isdn_add_channels(dev->drv[di], di, c->arg, 1)) { + spin_unlock_irqrestore(&dev->lock, flags); + return -1; + } + spin_unlock_irqrestore(&dev->lock, flags); + isdn_info_update(); + break; + case ISDN_STAT_DISCH: + spin_lock_irqsave(&dev->lock, flags); + for (i = 0; i < ISDN_MAX_CHANNELS; i++) + if ((dev->drvmap[i] == di) && + (dev->chanmap[i] == c->arg)) { + if (c->parm.num[0]) + dev->usage[i] &= ~ISDN_USAGE_DISABLED; + else + if (USG_NONE(dev->usage[i])) { + dev->usage[i] |= ISDN_USAGE_DISABLED; + } + else + retval = -1; + break; + } + spin_unlock_irqrestore(&dev->lock, flags); + isdn_info_update(); + break; + case ISDN_STAT_UNLOAD: + while (dev->drv[di]->locks > 0) { + isdn_unlock_driver(dev->drv[di]); + } + spin_lock_irqsave(&dev->lock, flags); + isdn_tty_stat_callback(i, c); + for (i = 0; i < ISDN_MAX_CHANNELS; i++) + if (dev->drvmap[i] == di) { + dev->drvmap[i] = -1; + dev->chanmap[i] = -1; + dev->usage[i] &= ~ISDN_USAGE_DISABLED; + } + 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++) + skb_queue_purge(&dev->drv[di]->rpqueue[i]); + kfree(dev->drv[di]->rpqueue); + kfree(dev->drv[di]->rcv_waitq); + kfree(dev->drv[di]); + dev->drv[di] = NULL; + dev->drvid[di][0] = '\0'; + isdn_info_update(); + set_global_features(); + spin_unlock_irqrestore(&dev->lock, flags); + return 0; + case ISDN_STAT_L1ERR: + break; + case CAPI_PUT_MESSAGE: + return(isdn_capi_rec_hl_msg(&c->parm.cmsg)); +#ifdef CONFIG_ISDN_TTY_FAX + case ISDN_STAT_FAXIND: + isdn_tty_stat_callback(i, c); + break; +#endif +#ifdef CONFIG_ISDN_AUDIO + case ISDN_STAT_AUDIO: + isdn_tty_stat_callback(i, c); + break; +#endif +#ifdef CONFIG_ISDN_DIVERSION + case ISDN_STAT_PROT: + case ISDN_STAT_REDIR: + if (divert_if) + return(divert_if->stat_callback(c)); +#endif /* CONFIG_ISDN_DIVERSION */ + 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. + * + * Be aware that this is not an atomic operation when sleep != 0, even though + * interrupts are turned off! Well, like that we are currently only called + * on behalf of a read system call on raw device files (which are documented + * to be dangerous and for for debugging purpose only). The inode semaphore + * takes care that this is not called for the same minor device number while + * we are sleeping, but access is not serialized against simultaneous read() + * from the corresponding ttyI device. Can other ugly events, like changes + * of the mapping (di,ch)<->minor, happen during the sleep? --he + */ +int +isdn_readbchan(int di, int channel, u_char * buf, u_char * fp, int len, wait_queue_head_t *sleep) +{ + int count; + int count_pull; + int count_put; + int dflag; + struct sk_buff *skb; + u_char *cp; + + if (!dev->drv[di]) + return 0; + if (skb_queue_empty(&dev->drv[di]->rpqueue[channel])) { + if (sleep) + interruptible_sleep_on(sleep); + else + return 0; + } + if (len > dev->drv[di]->rcvcount[channel]) + len = dev->drv[di]->rcvcount[channel]; + cp = buf; + count = 0; + while (len) { + if (!(skb = skb_peek(&dev->drv[di]->rpqueue[channel]))) + break; +#ifdef CONFIG_ISDN_AUDIO + if (ISDN_AUDIO_SKB_LOCK(skb)) + break; + ISDN_AUDIO_SKB_LOCK(skb) = 1; + if ((ISDN_AUDIO_SKB_DLECOUNT(skb)) || (dev->drv[di]->DLEflag & (1 << channel))) { + char *p = skb->data; + unsigned long DLEmask = (1 << channel); + + dflag = 0; + count_pull = count_put = 0; + while ((count_pull < skb->len) && (len > 0)) { + len--; + if (dev->drv[di]->DLEflag & DLEmask) { + *cp++ = DLE; + dev->drv[di]->DLEflag &= ~DLEmask; + } else { + *cp++ = *p; + if (*p == DLE) { + dev->drv[di]->DLEflag |= DLEmask; + (ISDN_AUDIO_SKB_DLECOUNT(skb))--; + } + p++; + count_pull++; + } + count_put++; + } + if (count_pull >= skb->len) + dflag = 1; + } else { +#endif + /* No DLE's in buff, so simply copy it */ + dflag = 1; + if ((count_pull = skb->len) > len) { + count_pull = len; + dflag = 0; + } + count_put = count_pull; + memcpy(cp, skb->data, count_put); + cp += count_put; + len -= count_put; +#ifdef CONFIG_ISDN_AUDIO + } +#endif + 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; +#ifdef CONFIG_ISDN_AUDIO + ISDN_AUDIO_SKB_LOCK(skb) = 0; +#endif + skb = skb_dequeue(&dev->drv[di]->rpqueue[channel]); + dev_kfree_skb(skb); + } 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); +#ifdef CONFIG_ISDN_AUDIO + ISDN_AUDIO_SKB_LOCK(skb) = 0; +#endif + } + dev->drv[di]->rcvcount[channel] -= count_put; + } + return count; +} + +static __inline int +isdn_minor2drv(int minor) +{ + return (dev->drvmap[minor]); +} + +static __inline int +isdn_minor2chan(int minor) +{ + return (dev->chanmap[minor]); +} + +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]->online); + 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 ssize_t +isdn_read(struct file *file, char __user *buf, size_t count, loff_t * off) +{ + uint minor = MINOR(file->f_dentry->d_inode->i_rdev); + int len = 0; + int drvidx; + int chidx; + int retval; + char *p; + + lock_kernel(); + if (minor == ISDN_MINOR_STATUS) { + if (!file->private_data) { + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + goto out; + } + interruptible_sleep_on(&(dev->info_waitq)); + } + p = isdn_statstr(); + file->private_data = NULL; + if ((len = strlen(p)) <= count) { + if (copy_to_user(buf, p, len)) { + retval = -EFAULT; + goto out; + } + *off += len; + retval = len; + goto out; + } + retval = 0; + goto out; + } + if (!dev->drivers) { + retval = -ENODEV; + goto out; + } + if (minor <= ISDN_MINOR_BMAX) { + printk(KERN_WARNING "isdn_read minor %d obsolete!\n", minor); + drvidx = isdn_minor2drv(minor); + if (drvidx < 0) { + retval = -ENODEV; + goto out; + } + if (!(dev->drv[drvidx]->flags & DRV_FLAG_RUNNING)) { + retval = -ENODEV; + goto out; + } + chidx = isdn_minor2chan(minor); + if (!(p = kmalloc(count, GFP_KERNEL))) { + retval = -ENOMEM; + goto out; + } + len = isdn_readbchan(drvidx, chidx, p, NULL, count, + &dev->drv[drvidx]->rcv_waitq[chidx]); + *off += len; + if (copy_to_user(buf,p,len)) + len = -EFAULT; + kfree(p); + retval = len; + goto out; + } + if (minor <= ISDN_MINOR_CTRLMAX) { + drvidx = isdn_minor2drv(minor - ISDN_MINOR_CTRL); + if (drvidx < 0) { + retval = -ENODEV; + goto out; + } + if (!dev->drv[drvidx]->stavail) { + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + goto out; + } + interruptible_sleep_on(&(dev->drv[drvidx]->st_waitq)); + } + if (dev->drv[drvidx]->interface->readstat) { + if (count > dev->drv[drvidx]->stavail) + count = dev->drv[drvidx]->stavail; + len = dev->drv[drvidx]->interface-> + readstat(buf, count, drvidx, + isdn_minor2chan(minor)); + } else { + len = 0; + } + if (len) + dev->drv[drvidx]->stavail -= len; + else + dev->drv[drvidx]->stavail = 0; + *off += len; + retval = len; + goto out; + } +#ifdef CONFIG_ISDN_PPP + if (minor <= ISDN_MINOR_PPPMAX) { + retval = isdn_ppp_read(minor - ISDN_MINOR_PPP, file, buf, count); + goto out; + } +#endif + retval = -ENODEV; + out: + unlock_kernel(); + return retval; +} + +static ssize_t +isdn_write(struct file *file, const char __user *buf, size_t count, loff_t * off) +{ + uint minor = MINOR(file->f_dentry->d_inode->i_rdev); + int drvidx; + int chidx; + int retval; + + if (minor == ISDN_MINOR_STATUS) + return -EPERM; + if (!dev->drivers) + return -ENODEV; + + lock_kernel(); + if (minor <= ISDN_MINOR_BMAX) { + printk(KERN_WARNING "isdn_write minor %d obsolete!\n", minor); + drvidx = isdn_minor2drv(minor); + if (drvidx < 0) { + retval = -ENODEV; + goto out; + } + if (!(dev->drv[drvidx]->flags & DRV_FLAG_RUNNING)) { + retval = -ENODEV; + goto out; + } + chidx = isdn_minor2chan(minor); + while (isdn_writebuf_stub(drvidx, chidx, buf, count) != count) + interruptible_sleep_on(&dev->drv[drvidx]->snd_waitq[chidx]); + retval = count; + goto out; + } + if (minor <= ISDN_MINOR_CTRLMAX) { + drvidx = isdn_minor2drv(minor - ISDN_MINOR_CTRL); + if (drvidx < 0) { + retval = -ENODEV; + goto out; + } + /* + * We want to use the isdnctrl device to load the firmware + * + if (!(dev->drv[drvidx]->flags & DRV_FLAG_RUNNING)) + return -ENODEV; + */ + if (dev->drv[drvidx]->interface->writecmd) + retval = dev->drv[drvidx]->interface-> + writecmd(buf, count, drvidx, isdn_minor2chan(minor)); + else + retval = count; + goto out; + } +#ifdef CONFIG_ISDN_PPP + if (minor <= ISDN_MINOR_PPPMAX) { + retval = isdn_ppp_write(minor - ISDN_MINOR_PPP, file, buf, count); + goto out; + } +#endif + retval = -ENODEV; + out: + unlock_kernel(); + return retval; +} + +static unsigned int +isdn_poll(struct file *file, poll_table * wait) +{ + unsigned int mask = 0; + unsigned int minor = MINOR(file->f_dentry->d_inode->i_rdev); + int drvidx = isdn_minor2drv(minor - ISDN_MINOR_CTRL); + + lock_kernel(); + if (minor == ISDN_MINOR_STATUS) { + poll_wait(file, &(dev->info_waitq), wait); + /* mask = POLLOUT | POLLWRNORM; */ + if (file->private_data) { + mask |= POLLIN | POLLRDNORM; + } + goto out; + } + if (minor >= ISDN_MINOR_CTRL && minor <= ISDN_MINOR_CTRLMAX) { + if (drvidx < 0) { + /* driver deregistered while file open */ + mask = POLLHUP; + goto out; + } + poll_wait(file, &(dev->drv[drvidx]->st_waitq), wait); + mask = POLLOUT | POLLWRNORM; + if (dev->drv[drvidx]->stavail) { + mask |= POLLIN | POLLRDNORM; + } + goto out; + } +#ifdef CONFIG_ISDN_PPP + if (minor <= ISDN_MINOR_PPPMAX) { + mask = isdn_ppp_poll(file, wait); + goto out; + } +#endif + mask = POLLERR; + out: + unlock_kernel(); + return mask; +} + + +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; + int i; + char __user *p; + char *s; + union iocpar { + char name[10]; + char bname[22]; + isdn_ioctl_struct iocts; + isdn_net_ioctl_phone phone; + isdn_net_ioctl_cfg cfg; + } iocpar; + void __user *argp = (void __user *)arg; + +#define name iocpar.name +#define bname iocpar.bname +#define iocts iocpar.iocts +#define phone iocpar.phone +#define cfg iocpar.cfg + + if (minor == ISDN_MINOR_STATUS) { + switch (cmd) { + case IIOCGETDVR: + return (TTY_DV + + (NET_DV << 8) + + (INF_DV << 16)); + case IIOCGETCPS: + if (arg) { + ulong __user *p = argp; + int i; + if (!access_ok(VERIFY_WRITE, p, + sizeof(ulong) * ISDN_MAX_CHANNELS * 2)) + return -EFAULT; + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + put_user(dev->ibytes[i], p++); + put_user(dev->obytes[i], p++); + } + return 0; + } else + return -EINVAL; + break; +#ifdef CONFIG_NETDEVICES + case IIOCNETGPN: + /* Get peer phone number of a connected + * isdn network interface */ + if (arg) { + if (copy_from_user(&phone, argp, sizeof(phone))) + return -EFAULT; + return isdn_net_getpeer(&phone, argp); + } else + return -EINVAL; +#endif + default: + return -EINVAL; + } + } + if (!dev->drivers) + return -ENODEV; + if (minor <= ISDN_MINOR_BMAX) { + drvidx = isdn_minor2drv(minor); + if (drvidx < 0) + return -ENODEV; + chidx = isdn_minor2chan(minor); + if (!(dev->drv[drvidx]->flags & DRV_FLAG_RUNNING)) + return -ENODEV; + return 0; + } + if (minor <= ISDN_MINOR_CTRLMAX) { +/* + * isdn net devices manage lots of configuration variables as linked lists. + * Those lists must only be manipulated from user space. Some of the ioctl's + * service routines access user space and are not atomic. Therefor, ioctl's + * manipulating the lists and ioctl's sleeping while accessing the lists + * are serialized by means of a semaphore. + */ + switch (cmd) { + case IIOCNETDWRSET: + printk(KERN_INFO "INFO: ISDN_DW_ABC_EXTENSION not enabled\n"); + return(-EINVAL); + case IIOCNETLCR: + printk(KERN_INFO "INFO: ISDN_ABC_LCR_SUPPORT not enabled\n"); + return -ENODEV; +#ifdef CONFIG_NETDEVICES + case IIOCNETAIF: + /* Add a network-interface */ + if (arg) { + if (copy_from_user(name, argp, sizeof(name))) + return -EFAULT; + s = name; + } else { + s = NULL; + } + ret = down_interruptible(&dev->sem); + if( ret ) return ret; + if ((s = isdn_net_new(s, NULL))) { + if (copy_to_user(argp, s, strlen(s) + 1)){ + ret = -EFAULT; + } else { + ret = 0; + } + } else + ret = -ENODEV; + up(&dev->sem); + return ret; + case IIOCNETASL: + /* Add a slave to a network-interface */ + if (arg) { + if (copy_from_user(bname, argp, sizeof(bname) - 1)) + return -EFAULT; + } else + return -EINVAL; + ret = down_interruptible(&dev->sem); + if( ret ) return ret; + if ((s = isdn_net_newslave(bname))) { + if (copy_to_user(argp, s, strlen(s) + 1)){ + ret = -EFAULT; + } else { + ret = 0; + } + } else + ret = -ENODEV; + up(&dev->sem); + return ret; + case IIOCNETDIF: + /* Delete a network-interface */ + if (arg) { + if (copy_from_user(name, argp, sizeof(name))) + return -EFAULT; + ret = down_interruptible(&dev->sem); + if( ret ) return ret; + ret = isdn_net_rm(name); + up(&dev->sem); + return ret; + } else + return -EINVAL; + case IIOCNETSCF: + /* Set configurable parameters of a network-interface */ + if (arg) { + if (copy_from_user(&cfg, argp, sizeof(cfg))) + return -EFAULT; + return isdn_net_setcfg(&cfg); + } else + return -EINVAL; + case IIOCNETGCF: + /* Get configurable parameters of a network-interface */ + if (arg) { + if (copy_from_user(&cfg, argp, sizeof(cfg))) + return -EFAULT; + if (!(ret = isdn_net_getcfg(&cfg))) { + if (copy_to_user(argp, &cfg, sizeof(cfg))) + return -EFAULT; + } + return ret; + } else + return -EINVAL; + case IIOCNETANM: + /* Add a phone-number to a network-interface */ + if (arg) { + if (copy_from_user(&phone, argp, sizeof(phone))) + return -EFAULT; + ret = down_interruptible(&dev->sem); + if( ret ) return ret; + ret = isdn_net_addphone(&phone); + up(&dev->sem); + return ret; + } else + return -EINVAL; + case IIOCNETGNM: + /* Get list of phone-numbers of a network-interface */ + if (arg) { + if (copy_from_user(&phone, argp, sizeof(phone))) + return -EFAULT; + ret = down_interruptible(&dev->sem); + if( ret ) return ret; + ret = isdn_net_getphones(&phone, argp); + up(&dev->sem); + return ret; + } else + return -EINVAL; + case IIOCNETDNM: + /* Delete a phone-number of a network-interface */ + if (arg) { + if (copy_from_user(&phone, argp, sizeof(phone))) + return -EFAULT; + ret = down_interruptible(&dev->sem); + if( ret ) return ret; + ret = isdn_net_delphone(&phone); + up(&dev->sem); + return ret; + } else + return -EINVAL; + case IIOCNETDIL: + /* Force dialing of a network-interface */ + if (arg) { + if (copy_from_user(name, argp, sizeof(name))) + return -EFAULT; + return isdn_net_force_dial(name); + } else + return -EINVAL; +#ifdef CONFIG_ISDN_PPP + case IIOCNETALN: + if (!arg) + return -EINVAL; + if (copy_from_user(name, argp, sizeof(name))) + return -EFAULT; + return isdn_ppp_dial_slave(name); + case IIOCNETDLN: + if (!arg) + return -EINVAL; + if (copy_from_user(name, argp, sizeof(name))) + return -EFAULT; + return isdn_ppp_hangup_slave(name); +#endif + case IIOCNETHUP: + /* Force hangup of a network-interface */ + if (!arg) + return -EINVAL; + if (copy_from_user(name, argp, sizeof(name))) + return -EFAULT; + return isdn_net_force_hangup(name); + 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(&iocts, argp, + 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; + if (iocts.arg) + dev->drv[drvidx]->flags |= DRV_FLAG_REJBUS; + else + dev->drv[drvidx]->flags &= ~DRV_FLAG_REJBUS; + return 0; + case IIOCSIGPRF: + dev->profd = current; + return 0; + break; + case IIOCGETPRF: + /* Get all Modem-Profiles */ + if (arg) { + char __user *p = argp; + int i; + + if (!access_ok(VERIFY_WRITE, argp, + (ISDN_MODEM_NUMREG + ISDN_MSNLEN + ISDN_LMSNLEN) + * ISDN_MAX_CHANNELS)) + return -EFAULT; + + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + if (copy_to_user(p, dev->mdm.info[i].emu.profile, + ISDN_MODEM_NUMREG)) + return -EFAULT; + p += ISDN_MODEM_NUMREG; + if (copy_to_user(p, dev->mdm.info[i].emu.pmsn, ISDN_MSNLEN)) + return -EFAULT; + p += ISDN_MSNLEN; + if (copy_to_user(p, dev->mdm.info[i].emu.plmsn, ISDN_LMSNLEN)) + return -EFAULT; + p += ISDN_LMSNLEN; + } + return (ISDN_MODEM_NUMREG + ISDN_MSNLEN + ISDN_LMSNLEN) * ISDN_MAX_CHANNELS; + } else + return -EINVAL; + break; + case IIOCSETPRF: + /* Set all Modem-Profiles */ + if (arg) { + char __user *p = argp; + int i; + + if (!access_ok(VERIFY_READ, argp, + (ISDN_MODEM_NUMREG + ISDN_MSNLEN + ISDN_LMSNLEN) + * ISDN_MAX_CHANNELS)) + return -EFAULT; + + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + if (copy_from_user(dev->mdm.info[i].emu.profile, p, + ISDN_MODEM_NUMREG)) + return -EFAULT; + p += ISDN_MODEM_NUMREG; + if (copy_from_user(dev->mdm.info[i].emu.plmsn, p, ISDN_LMSNLEN)) + return -EFAULT; + p += ISDN_LMSNLEN; + 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) { + + if (copy_from_user(&iocts, argp, + sizeof(isdn_ioctl_struct))) + 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) { + int loop = 1; + + p = (char __user *) iocts.arg; + i = 0; + while (loop) { + int j = 0; + + while (1) { + if (!access_ok(VERIFY_READ, p, 1)) + return -EFAULT; + get_user(bname[j], p++); + switch (bname[j]) { + case '\0': + loop = 0; + /* Fall through */ + case ',': + bname[j] = '\0'; + strcpy(dev->drv[drvidx]->msn2eaz[i], bname); + j = ISDN_MSNLEN; + break; + default: + j++; + } + if (j >= ISDN_MSNLEN) + break; + } + if (++i > 9) + break; + } + } else { + p = (char __user *) iocts.arg; + for (i = 0; i < 10; i++) { + sprintf(bname, "%s%s", + strlen(dev->drv[drvidx]->msn2eaz[i]) ? + dev->drv[drvidx]->msn2eaz[i] : "_", + (i < 9) ? "," : "\0"); + if (copy_to_user(p, bname, strlen(bname) + 1)) + return -EFAULT; + p += strlen(bname); + } + } + return 0; + } else + return -EINVAL; + case IIOCDBGVAR: + if (arg) { + if (copy_to_user(argp, &dev, sizeof(ulong))) + return -EFAULT; + return 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; + if (copy_from_user(&iocts, argp, 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; + } + } else + drvidx = 0; + if (drvidx == -1) + return -ENODEV; + if (!access_ok(VERIFY_WRITE, argp, + sizeof(isdn_ioctl_struct))) + return -EFAULT; + c.driver = drvidx; + c.command = ISDN_CMD_IOCTL; + c.arg = cmd; + memcpy(c.parm.num, &iocts.arg, sizeof(ulong)); + ret = isdn_command(&c); + memcpy(&iocts.arg, c.parm.num, sizeof(ulong)); + if (copy_to_user(argp, &iocts, sizeof(isdn_ioctl_struct))) + return -EFAULT; + return ret; + } 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; + +#undef name +#undef bname +#undef iocts +#undef phone +#undef cfg +} + +/* + * Open the device code. + */ +static int +isdn_open(struct inode *ino, struct file *filep) +{ + uint minor = MINOR(ino->i_rdev); + int drvidx; + int chidx; + int retval = -ENODEV; + + + if (minor == ISDN_MINOR_STATUS) { + infostruct *p; + + if ((p = kmalloc(sizeof(infostruct), GFP_KERNEL))) { + 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; + retval = 0; + goto out; + } else { + retval = -ENOMEM; + goto out; + } + } + if (!dev->channels) + goto out; + if (minor <= ISDN_MINOR_BMAX) { + printk(KERN_WARNING "isdn_open minor %d obsolete!\n", minor); + drvidx = isdn_minor2drv(minor); + if (drvidx < 0) + goto out; + chidx = isdn_minor2chan(minor); + if (!(dev->drv[drvidx]->flags & DRV_FLAG_RUNNING)) + goto out; + if (!(dev->drv[drvidx]->online & (1 << chidx))) + goto out; + isdn_lock_drivers(); + retval = 0; + goto out; + } + if (minor <= ISDN_MINOR_CTRLMAX) { + drvidx = isdn_minor2drv(minor - ISDN_MINOR_CTRL); + if (drvidx < 0) + goto out; + isdn_lock_drivers(); + retval = 0; + goto out; + } +#ifdef CONFIG_ISDN_PPP + if (minor <= ISDN_MINOR_PPPMAX) { + retval = isdn_ppp_open(minor - ISDN_MINOR_PPP, filep); + if (retval == 0) + isdn_lock_drivers(); + goto out; + } +#endif + out: + nonseekable_open(ino, filep); + return retval; +} + +static int +isdn_close(struct inode *ino, struct file *filep) +{ + uint minor = MINOR(ino->i_rdev); + + lock_kernel(); + 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); + kfree(p); + goto out; + } + q = p; + p = (infostruct *) (p->next); + } + printk(KERN_WARNING "isdn: No private data while closing isdnctrl\n"); + goto out; + } + isdn_unlock_drivers(); + if (minor <= ISDN_MINOR_BMAX) + goto out; + if (minor <= ISDN_MINOR_CTRLMAX) { + if (dev->profd == current) + dev->profd = NULL; + goto out; + } +#ifdef CONFIG_ISDN_PPP + if (minor <= ISDN_MINOR_PPPMAX) + isdn_ppp_release(minor - ISDN_MINOR_PPP, filep); +#endif + + out: + unlock_kernel(); + return 0; +} + +static struct file_operations isdn_fops = +{ + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = isdn_read, + .write = isdn_write, + .poll = isdn_poll, + .ioctl = isdn_ioctl, + .open = isdn_open, + .release = isdn_close, +}; + +char * +isdn_map_eaz2msn(char *msn, int di) +{ + isdn_driver_t *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. + */ +#define L2V (~(ISDN_FEATURE_L2_V11096|ISDN_FEATURE_L2_V11019|ISDN_FEATURE_L2_V11038)) + +/* + * This function must be called with holding the dev->lock. + */ +int +isdn_get_free_channel(int usage, int l2_proto, int l3_proto, int pre_dev + ,int pre_chan, char *msn) +{ + int i; + ulong features; + ulong vfeatures; + + features = ((1 << l2_proto) | (0x10000 << l3_proto)); + vfeatures = (((1 << l2_proto) | (0x10000 << l3_proto)) & + ~(ISDN_FEATURE_L2_V11096|ISDN_FEATURE_L2_V11019|ISDN_FEATURE_L2_V11038)); + /* If Layer-2 protocol is V.110, accept drivers with + * transparent feature even if these don't support V.110 + * because we can emulate this in linklevel. + */ + 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 (!strcmp(isdn_map_eaz2msn(msn, d), "-")) + continue; + if (dev->usage[i] & ISDN_USAGE_DISABLED) + continue; /* usage not allowed */ + if (dev->drv[d]->flags & DRV_FLAG_RUNNING) { + if (((dev->drv[d]->interface->features & features) == features) || + (((dev->drv[d]->interface->features & vfeatures) == vfeatures) && + (dev->drv[d]->interface->features & ISDN_FEATURE_L2_TRANS))) { + if ((pre_dev < 0) || (pre_chan < 0)) { + dev->usage[i] &= ISDN_USAGE_EXCLUSIVE; + dev->usage[i] |= usage; + isdn_info_update(); + 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(); + return i; + } + } + } + } + } + return -1; +} + +/* + * Set state of ISDN-channel to 'unused' + */ +void +isdn_free_channel(int di, int ch, int usage) +{ + int i; + + for (i = 0; i < ISDN_MAX_CHANNELS; i++) + if (((!usage) || ((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; +// 20.10.99 JIM, try to reinitialize v110 ! + dev->v110emu[i] = 0; + atomic_set(&(dev->v110use[i]), 0); + isdn_v110_close(dev->v110[i]); + dev->v110[i] = NULL; +// 20.10.99 JIM, try to reinitialize v110 ! + isdn_info_update(); + skb_queue_purge(&dev->drv[di]->rpqueue[ch]); + } +} + +/* + * Cancel Exclusive-Flag for ISDN-channel + */ +void +isdn_unexclusive_channel(int di, int ch) +{ + int i; + + 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(); + return; + } +} + +/* + * writebuf replacement for SKB_ABLE drivers + */ +static int +isdn_writebuf_stub(int drvidx, int chan, const u_char __user * buf, int len) +{ + int ret; + int hl = dev->drv[drvidx]->interface->hl_hdrlen; + struct sk_buff *skb = alloc_skb(hl + len, GFP_ATOMIC); + + if (!skb) + return 0; + skb_reserve(skb, hl); + copy_from_user(skb_put(skb, len), buf, len); + ret = dev->drv[drvidx]->interface->writebuf_skb(drvidx, chan, 1, skb); + if (ret <= 0) + dev_kfree_skb(skb); + if (ret > 0) + dev->obytes[isdn_dc2minor(drvidx, chan)] += ret; + return ret; +} + +/* + * Return: length of data on success, -ERRcode on failure. + */ +int +isdn_writebuf_skb_stub(int drvidx, int chan, int ack, struct sk_buff *skb) +{ + int ret; + struct sk_buff *nskb = NULL; + int v110_ret = skb->len; + int idx = isdn_dc2minor(drvidx, chan); + + if (dev->v110[idx]) { + atomic_inc(&dev->v110use[idx]); + nskb = isdn_v110_encode(dev->v110[idx], skb); + atomic_dec(&dev->v110use[idx]); + if (!nskb) + return 0; + v110_ret = *((int *)nskb->data); + skb_pull(nskb, sizeof(int)); + if (!nskb->len) { + dev_kfree_skb(nskb); + return v110_ret; + } + /* V.110 must always be acknowledged */ + ack = 1; + ret = dev->drv[drvidx]->interface->writebuf_skb(drvidx, chan, ack, nskb); + } else { + int hl = dev->drv[drvidx]->interface->hl_hdrlen; + + if( skb_headroom(skb) < hl ){ + /* + * This should only occur when new HL driver with + * increased hl_hdrlen was loaded after netdevice + * was created and connected to the new driver. + * + * The V.110 branch (re-allocates on its own) does + * not need this + */ + struct sk_buff * skb_tmp; + + skb_tmp = skb_realloc_headroom(skb, hl); + printk(KERN_DEBUG "isdn_writebuf_skb_stub: reallocating headroom%s\n", skb_tmp ? "" : " failed"); + if (!skb_tmp) return -ENOMEM; /* 0 better? */ + ret = dev->drv[drvidx]->interface->writebuf_skb(drvidx, chan, ack, skb_tmp); + if( ret > 0 ){ + dev_kfree_skb(skb); + } else { + dev_kfree_skb(skb_tmp); + } + } else { + ret = dev->drv[drvidx]->interface->writebuf_skb(drvidx, chan, ack, skb); + } + } + if (ret > 0) { + dev->obytes[idx] += ret; + if (dev->v110[idx]) { + atomic_inc(&dev->v110use[idx]); + dev->v110[idx]->skbuser++; + atomic_dec(&dev->v110use[idx]); + /* For V.110 return unencoded data length */ + ret = v110_ret; + /* if the complete frame was send we free the skb; + if not upper function will requeue the skb */ + if (ret == skb->len) + dev_kfree_skb(skb); + } + } else + if (dev->v110[idx]) + dev_kfree_skb(nskb); + return ret; +} + +int +isdn_add_channels(isdn_driver_t *d, int drvidx, int n, int adding) +{ + int j, k, m; + + init_waitqueue_head(&d->st_waitq); + if (d->flags & DRV_FLAG_RUNNING) + return -1; + if (n < 1) return 0; + + m = (adding) ? d->channels + n : n; + + if (dev->channels + n > ISDN_MAX_CHANNELS) { + printk(KERN_WARNING "register_isdn: Max. %d channels supported\n", + ISDN_MAX_CHANNELS); + return -1; + } + + if ((adding) && (d->rcverr)) + kfree(d->rcverr); + if (!(d->rcverr = kmalloc(sizeof(int) * m, GFP_ATOMIC))) { + printk(KERN_WARNING "register_isdn: Could not alloc rcverr\n"); + return -1; + } + memset((char *) d->rcverr, 0, sizeof(int) * m); + + if ((adding) && (d->rcvcount)) + kfree(d->rcvcount); + if (!(d->rcvcount = kmalloc(sizeof(int) * m, GFP_ATOMIC))) { + printk(KERN_WARNING "register_isdn: Could not alloc rcvcount\n"); + if (!adding) kfree(d->rcverr); + return -1; + } + memset((char *) d->rcvcount, 0, sizeof(int) * m); + + if ((adding) && (d->rpqueue)) { + for (j = 0; j < d->channels; j++) + skb_queue_purge(&d->rpqueue[j]); + kfree(d->rpqueue); + } + if (!(d->rpqueue = kmalloc(sizeof(struct sk_buff_head) * m, GFP_ATOMIC))) { + printk(KERN_WARNING "register_isdn: Could not alloc rpqueue\n"); + if (!adding) { + kfree(d->rcvcount); + kfree(d->rcverr); + } + return -1; + } + for (j = 0; j < m; j++) { + skb_queue_head_init(&d->rpqueue[j]); + } + + if ((adding) && (d->rcv_waitq)) + kfree(d->rcv_waitq); + d->rcv_waitq = kmalloc(sizeof(wait_queue_head_t) * 2 * m, GFP_ATOMIC); + if (!d->rcv_waitq) { + printk(KERN_WARNING "register_isdn: Could not alloc rcv_waitq\n"); + if (!adding) { + kfree(d->rpqueue); + kfree(d->rcvcount); + kfree(d->rcverr); + } + return -1; + } + d->snd_waitq = d->rcv_waitq + m; + for (j = 0; j < m; j++) { + init_waitqueue_head(&d->rcv_waitq[j]); + init_waitqueue_head(&d->snd_waitq[j]); + } + + dev->channels += n; + for (j = d->channels; j < m; j++) + for (k = 0; k < ISDN_MAX_CHANNELS; k++) + if (dev->chanmap[k] < 0) { + dev->chanmap[k] = j; + dev->drvmap[k] = drvidx; + break; + } + d->channels = m; + return 0; +} + +/* + * Low-level-driver registration + */ + +static void +set_global_features(void) +{ + int drvidx; + + dev->global_features = 0; + for (drvidx = 0; drvidx < ISDN_MAX_DRIVERS; drvidx++) { + if (!dev->drv[drvidx]) + continue; + if (dev->drv[drvidx]->interface) + dev->global_features |= dev->drv[drvidx]->interface->features; + } +} + +#ifdef CONFIG_ISDN_DIVERSION + +static char *map_drvname(int di) +{ + if ((di < 0) || (di >= ISDN_MAX_DRIVERS)) + return(NULL); + return(dev->drvid[di]); /* driver name */ +} /* map_drvname */ + +static int map_namedrv(char *id) +{ int i; + + for (i = 0; i < ISDN_MAX_DRIVERS; i++) + { if (!strcmp(dev->drvid[i],id)) + return(i); + } + return(-1); +} /* map_namedrv */ + +int DIVERT_REG_NAME(isdn_divert_if *i_div) +{ + if (i_div->if_magic != DIVERT_IF_MAGIC) + return(DIVERT_VER_ERR); + switch (i_div->cmd) + { + case DIVERT_CMD_REL: + if (divert_if != i_div) + return(DIVERT_REL_ERR); + divert_if = NULL; /* free interface */ + return(DIVERT_NO_ERR); + + case DIVERT_CMD_REG: + if (divert_if) + return(DIVERT_REG_ERR); + i_div->ll_cmd = isdn_command; /* set command function */ + i_div->drv_to_name = map_drvname; + i_div->name_to_drv = map_namedrv; + divert_if = i_div; /* remember interface */ + return(DIVERT_NO_ERR); + + default: + return(DIVERT_CMD_ERR); + } +} /* DIVERT_REG_NAME */ + +EXPORT_SYMBOL(DIVERT_REG_NAME); + +#endif /* CONFIG_ISDN_DIVERSION */ + + +EXPORT_SYMBOL(register_isdn); +#ifdef CONFIG_ISDN_PPP +EXPORT_SYMBOL(isdn_ppp_register_compressor); +EXPORT_SYMBOL(isdn_ppp_unregister_compressor); +#endif + +int +register_isdn(isdn_if * i) +{ + isdn_driver_t *d; + int j; + 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; + } + if (!i->writebuf_skb) { + printk(KERN_WARNING "register_isdn: No write routine given.\n"); + return 0; + } + if (!(d = kmalloc(sizeof(isdn_driver_t), GFP_KERNEL))) { + printk(KERN_WARNING "register_isdn: Could not alloc driver-struct\n"); + return 0; + } + memset((char *) d, 0, sizeof(isdn_driver_t)); + + d->maxbufsize = i->maxbufsize; + d->pktcount = 0; + d->stavail = 0; + d->flags = DRV_FLAG_LOADED; + d->online = 0; + d->interface = i; + d->channels = 0; + spin_lock_irqsave(&dev->lock, flags); + for (drvidx = 0; drvidx < ISDN_MAX_DRIVERS; drvidx++) + if (!dev->drv[drvidx]) + break; + if (isdn_add_channels(d, drvidx, i->channels, 0)) { + spin_unlock_irqrestore(&dev->lock, flags); + kfree(d); + return 0; + } + i->channels = drvidx; + i->rcvcallb_skb = isdn_receive_skb_callback; + i->statcallb = isdn_status_callback; + if (!strlen(i->id)) + sprintf(i->id, "line%d", drvidx); + for (j = 0; j < drvidx; j++) + if (!strcmp(i->id, dev->drvid[j])) + sprintf(i->id, "line%d", drvidx); + dev->drv[drvidx] = d; + strcpy(dev->drvid[drvidx], i->id); + isdn_info_update(); + dev->drivers++; + set_global_features(); + spin_unlock_irqrestore(&dev->lock, flags); + return 1; +} + +/* + ***************************************************************************** + * And now the modules code. + ***************************************************************************** + */ + +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; +} + +/* + * Allocate and initialize all data, register modem-devices + */ +static int __init isdn_init(void) +{ + int i; + char tmprev[50]; + + if (!(dev = (isdn_dev *) vmalloc(sizeof(isdn_dev)))) { + printk(KERN_WARNING "isdn: Could not allocate device-struct.\n"); + return -EIO; + } + memset((char *) dev, 0, sizeof(isdn_dev)); + init_timer(&dev->timer); + dev->timer.function = isdn_timer_funct; + spin_lock_init(&dev->lock); + spin_lock_init(&dev->timerlock); +#ifdef MODULE + dev->owner = THIS_MODULE; +#endif + init_MUTEX(&dev->sem); + init_waitqueue_head(&dev->info_waitq); + 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], "???"); + init_waitqueue_head(&dev->mdm.info[i].open_wait); + init_waitqueue_head(&dev->mdm.info[i].close_wait); + } + if (register_chrdev(ISDN_MAJOR, "isdn", &isdn_fops)) { + printk(KERN_WARNING "isdn: Could not register control devices\n"); + vfree(dev); + return -EIO; + } + if ((isdn_tty_modem_init()) < 0) { + printk(KERN_WARNING "isdn: Could not register tty devices\n"); + vfree(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"); + isdn_tty_exit(); + unregister_chrdev(ISDN_MAJOR, "isdn"); + vfree(dev); + return -EIO; + } +#endif /* CONFIG_ISDN_PPP */ + + strcpy(tmprev, isdn_revision); + printk(KERN_NOTICE "ISDN subsystem Rev: %s/", isdn_getrev(tmprev)); + strcpy(tmprev, isdn_tty_revision); + printk("%s/", isdn_getrev(tmprev)); + strcpy(tmprev, isdn_net_revision); + printk("%s/", isdn_getrev(tmprev)); + strcpy(tmprev, isdn_ppp_revision); + printk("%s/", isdn_getrev(tmprev)); + strcpy(tmprev, isdn_audio_revision); + printk("%s/", isdn_getrev(tmprev)); + strcpy(tmprev, isdn_v110_revision); + printk("%s", isdn_getrev(tmprev)); + +#ifdef MODULE + printk(" loaded\n"); +#else + printk("\n"); +#endif + isdn_info_update(); + return 0; +} + +/* + * Unload module + */ +static void __exit isdn_exit(void) +{ +#ifdef CONFIG_ISDN_PPP + isdn_ppp_cleanup(); +#endif + if (isdn_net_rmall() < 0) { + printk(KERN_WARNING "isdn: net-device busy, remove cancelled\n"); + return; + } + isdn_tty_exit(); + unregister_chrdev(ISDN_MAJOR, "isdn"); + del_timer(&dev->timer); + /* call vfree with interrupts enabled, else it will hang */ + vfree(dev); + printk(KERN_NOTICE "ISDN-subsystem unloaded\n"); +} + +module_init(isdn_init); +module_exit(isdn_exit); diff --git a/drivers/isdn/i4l/isdn_common.h b/drivers/isdn/i4l/isdn_common.h new file mode 100644 index 000000000000..808135c427ad --- /dev/null +++ b/drivers/isdn/i4l/isdn_common.h @@ -0,0 +1,47 @@ +/* $Id: isdn_common.h,v 1.1.2.2 2004/01/12 22:37:19 keil Exp $ + * + * header for Linux ISDN subsystem + * common used functions and debugging-switches (linklevel). + * + * Copyright 1994-1999 by Fritz Elfert (fritz@isdn4linux.de) + * Copyright 1995,96 by Thinking Objects Software GmbH Wuerzburg + * Copyright 1995,96 by Michael Hipp (Michael.Hipp@student.uni-tuebingen.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#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_MODEM_VOICE +#undef ISDN_DEBUG_AT +#undef ISDN_DEBUG_NET_DUMP +#undef ISDN_DEBUG_NET_DIAL +#undef ISDN_DEBUG_NET_ICALL + +/* Prototypes */ +extern void isdn_lock_drivers(void); +extern void isdn_unlock_drivers(void); +extern void isdn_free_channel(int di, int ch, int usage); +extern void isdn_all_eaz(int di, int ch); +extern int isdn_command(isdn_ctrl *); +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, wait_queue_head_t *); +extern int isdn_get_free_channel(int, int, int, int, int, char *); +extern int isdn_writebuf_skb_stub(int, int, int, struct sk_buff *); +extern int register_isdn(isdn_if * i); +extern int isdn_msncmp( const char *, const char *); +extern int isdn_add_channels(isdn_driver_t *, int, int, int); +#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/i4l/isdn_concap.c b/drivers/isdn/i4l/isdn_concap.c new file mode 100644 index 000000000000..83a4f5382bc2 --- /dev/null +++ b/drivers/isdn/i4l/isdn_concap.c @@ -0,0 +1,108 @@ +/* $Id: isdn_concap.c,v 1.1.2.2 2004/01/12 22:37:19 keil Exp $ + * + * Linux ISDN subsystem, protocol encapsulation + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +/* Stuff to support the concap_proto by isdn4linux. isdn4linux - specific + * stuff goes here. Stuff that depends only on the concap protocol goes to + * another -- protocol specific -- source file. + * + */ + + +#include <linux/isdn.h> +#include "isdn_x25iface.h" +#include "isdn_net.h" +#include <linux/concap.h> +#include "isdn_concap.h" + + +/* The following set of device service operations are for encapsulation + protocols that require for reliable datalink semantics. That means: + + - before any data is to be submitted the connection must explicitly + be set up. + - after the successful set up of the connection is signalled the + connection is considered to be reliably up. + + Auto-dialing ist not compatible with this requirements. Thus, auto-dialing + is completely bypassed. + + It might be possible to implement a (non standardized) datalink protocol + that provides a reliable data link service while using some auto dialing + mechanism. Such a protocol would need an auxiliary channel (i.e. user-user- + signaling on the D-channel) while the B-channel is down. + */ + + +int isdn_concap_dl_data_req(struct concap_proto *concap, struct sk_buff *skb) +{ + struct net_device *ndev = concap -> net_dev; + isdn_net_dev *nd = ((isdn_net_local *) ndev->priv)->netdev; + isdn_net_local *lp = isdn_net_get_locked_lp(nd); + + IX25DEBUG( "isdn_concap_dl_data_req: %s \n", concap->net_dev->name); + if (!lp) { + IX25DEBUG( "isdn_concap_dl_data_req: %s : isdn_net_send_skb returned %d\n", concap -> net_dev -> name, 1); + return 1; + } + lp->huptimer = 0; + isdn_net_writebuf_skb(lp, skb); + spin_unlock_bh(&lp->xmit_lock); + IX25DEBUG( "isdn_concap_dl_data_req: %s : isdn_net_send_skb returned %d\n", concap -> net_dev -> name, 0); + return 0; +} + + +int isdn_concap_dl_connect_req(struct concap_proto *concap) +{ + struct net_device *ndev = concap -> net_dev; + isdn_net_local *lp = (isdn_net_local *) ndev->priv; + int ret; + IX25DEBUG( "isdn_concap_dl_connect_req: %s \n", ndev -> name); + + /* dial ... */ + ret = isdn_net_dial_req( lp ); + if ( ret ) IX25DEBUG("dialing failed\n"); + return ret; +} + +int isdn_concap_dl_disconn_req(struct concap_proto *concap) +{ + IX25DEBUG( "isdn_concap_dl_disconn_req: %s \n", concap -> net_dev -> name); + + isdn_net_hangup( concap -> net_dev ); + return 0; +} + +struct concap_device_ops isdn_concap_reliable_dl_dops = { + &isdn_concap_dl_data_req, + &isdn_concap_dl_connect_req, + &isdn_concap_dl_disconn_req +}; + +struct concap_device_ops isdn_concap_demand_dial_dops = { + NULL, /* set this first entry to something like &isdn_net_start_xmit, + but the entry part of the current isdn_net_start_xmit must be + separated first. */ + /* no connection control for demand dial semantics */ + NULL, + NULL, +}; + +/* The following should better go into a dedicated source file such that + this sourcefile does not need to include any protocol specific header + files. For now: + */ +struct concap_proto * isdn_concap_new( int encap ) +{ + switch ( encap ) { + case ISDN_NET_ENCAP_X25IFACE: + return isdn_x25iface_proto_new(); + } + return NULL; +} diff --git a/drivers/isdn/i4l/isdn_concap.h b/drivers/isdn/i4l/isdn_concap.h new file mode 100644 index 000000000000..306eb180438f --- /dev/null +++ b/drivers/isdn/i4l/isdn_concap.h @@ -0,0 +1,14 @@ +/* $Id: isdn_concap.h,v 1.1.2.2 2004/01/12 22:37:19 keil Exp $ + * + * Linux ISDN subsystem, protocol encapsulation + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +extern struct concap_device_ops isdn_concap_reliable_dl_dops; +extern struct concap_device_ops isdn_concap_demand_dial_dops; +extern struct concap_proto * isdn_concap_new( int ); + + diff --git a/drivers/isdn/i4l/isdn_net.c b/drivers/isdn/i4l/isdn_net.c new file mode 100644 index 000000000000..e2b790e34510 --- /dev/null +++ b/drivers/isdn/i4l/isdn_net.c @@ -0,0 +1,3222 @@ +/* $Id: isdn_net.c,v 1.1.2.2 2004/01/12 22:37:19 keil Exp $ + * + * Linux ISDN subsystem, network interfaces and related functions (linklevel). + * + * Copyright 1994-1998 by Fritz Elfert (fritz@isdn4linux.de) + * Copyright 1995,96 by Thinking Objects Software GmbH Wuerzburg + * Copyright 1995,96 by Michael Hipp (Michael.Hipp@student.uni-tuebingen.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * Data Over Voice (DOV) support added - Guy Ellis 23-Mar-02 + * guy@traverse.com.au + * Outgoing calls - looks for a 'V' in first char of dialed number + * Incoming calls - checks first character of eaz as follows: + * Numeric - accept DATA only - original functionality + * 'V' - accept VOICE (DOV) only + * 'B' - accept BOTH DATA and DOV types + * + * Jan 2001: fix CISCO HDLC Bjoern A. Zeeb <i4l@zabbadoz.net> + * for info on the protocol, see + * http://i4l.zabbadoz.net/i4l/cisco-hdlc.txt + */ + +#include <linux/config.h> +#include <linux/isdn.h> +#include <net/arp.h> +#include <net/dst.h> +#include <net/pkt_sched.h> +#include <linux/inetdevice.h> +#include "isdn_common.h" +#include "isdn_net.h" +#ifdef CONFIG_ISDN_PPP +#include "isdn_ppp.h" +#endif +#ifdef CONFIG_ISDN_X25 +#include <linux/concap.h> +#include "isdn_concap.h" +#endif + + +/* + * Outline of new tbusy handling: + * + * Old method, roughly spoken, consisted of setting tbusy when entering + * isdn_net_start_xmit() and at several other locations and clearing + * it from isdn_net_start_xmit() thread when sending was successful. + * + * With 2.3.x multithreaded network core, to prevent problems, tbusy should + * only be set by the isdn_net_start_xmit() thread and only when a tx-busy + * condition is detected. Other threads (in particular isdn_net_stat_callb()) + * are only allowed to clear tbusy. + * + * -HE + */ + +/* + * About SOFTNET: + * Most of the changes were pretty obvious and basically done by HE already. + * + * One problem of the isdn net device code is that is uses struct net_device + * for masters and slaves. However, only master interface are registered to + * the network layer, and therefore, it only makes sense to call netif_* + * functions on them. + * + * --KG + */ + +/* + * Find out if the netdevice has been ifup-ed yet. + * For slaves, look at the corresponding master. + */ +static __inline__ int isdn_net_device_started(isdn_net_dev *n) +{ + isdn_net_local *lp = n->local; + struct net_device *dev; + + if (lp->master) + dev = lp->master; + else + dev = &n->dev; + return netif_running(dev); +} + +/* + * wake up the network -> net_device queue. + * For slaves, wake the corresponding master interface. + */ +static __inline__ void isdn_net_device_wake_queue(isdn_net_local *lp) +{ + if (lp->master) + netif_wake_queue(lp->master); + else + netif_wake_queue(&lp->netdev->dev); +} + +/* + * stop the network -> net_device queue. + * For slaves, stop the corresponding master interface. + */ +static __inline__ void isdn_net_device_stop_queue(isdn_net_local *lp) +{ + if (lp->master) + netif_stop_queue(lp->master); + else + netif_stop_queue(&lp->netdev->dev); +} + +/* + * find out if the net_device which this lp belongs to (lp can be + * master or slave) is busy. It's busy iff all (master and slave) + * queues are busy + */ +static __inline__ int isdn_net_device_busy(isdn_net_local *lp) +{ + isdn_net_local *nlp; + isdn_net_dev *nd; + unsigned long flags; + + if (!isdn_net_lp_busy(lp)) + return 0; + + if (lp->master) + nd = ((isdn_net_local *) lp->master->priv)->netdev; + else + nd = lp->netdev; + + spin_lock_irqsave(&nd->queue_lock, flags); + nlp = lp->next; + while (nlp != lp) { + if (!isdn_net_lp_busy(nlp)) { + spin_unlock_irqrestore(&nd->queue_lock, flags); + return 0; + } + nlp = nlp->next; + } + spin_unlock_irqrestore(&nd->queue_lock, flags); + return 1; +} + +static __inline__ void isdn_net_inc_frame_cnt(isdn_net_local *lp) +{ + atomic_inc(&lp->frame_cnt); + if (isdn_net_device_busy(lp)) + isdn_net_device_stop_queue(lp); +} + +static __inline__ void isdn_net_dec_frame_cnt(isdn_net_local *lp) +{ + atomic_dec(&lp->frame_cnt); + + if (!(isdn_net_device_busy(lp))) { + if (!skb_queue_empty(&lp->super_tx_queue)) { + schedule_work(&lp->tqueue); + } else { + isdn_net_device_wake_queue(lp); + } + } +} + +static __inline__ void isdn_net_zero_frame_cnt(isdn_net_local *lp) +{ + atomic_set(&lp->frame_cnt, 0); +} + +/* For 2.2.x we leave the transmitter busy timeout at 2 secs, just + * to be safe. + * For 2.3.x we push it up to 20 secs, because call establishment + * (in particular callback) may take such a long time, and we + * don't want confusing messages in the log. However, there is a slight + * possibility that this large timeout will break other things like MPPP, + * which might rely on the tx timeout. If so, we'll find out this way... + */ + +#define ISDN_NET_TX_TIMEOUT (20*HZ) + +/* Prototypes */ + +int isdn_net_force_dial_lp(isdn_net_local *); +static int isdn_net_start_xmit(struct sk_buff *, struct net_device *); + +static void isdn_net_ciscohdlck_connected(isdn_net_local *lp); +static void isdn_net_ciscohdlck_disconnected(isdn_net_local *lp); + +char *isdn_net_revision = "$Revision: 1.1.2.2 $"; + + /* + * Code for raw-networking over ISDN + */ + +static void +isdn_net_unreachable(struct net_device *dev, struct sk_buff *skb, char *reason) +{ + if(skb) { + + u_short proto = ntohs(skb->protocol); + + printk(KERN_DEBUG "isdn_net: %s: %s, signalling dst_link_failure %s\n", + dev->name, + (reason != NULL) ? reason : "unknown", + (proto != ETH_P_IP) ? "Protocol != ETH_P_IP" : ""); + + dst_link_failure(skb); + } + else { /* dial not triggered by rawIP packet */ + printk(KERN_DEBUG "isdn_net: %s: %s\n", + dev->name, + (reason != NULL) ? reason : "reason unknown"); + } +} + +static void +isdn_net_reset(struct net_device *dev) +{ +#ifdef CONFIG_ISDN_X25 + struct concap_device_ops * dops = + ( (isdn_net_local *) dev->priv ) -> dops; + struct concap_proto * cprot = + ( (isdn_net_local *) dev->priv ) -> netdev -> cprot; +#endif +#ifdef CONFIG_ISDN_X25 + if( cprot && cprot -> pops && dops ) + cprot -> pops -> restart ( cprot, dev, dops ); +#endif +} + +/* Open/initialize the board. */ +static int +isdn_net_open(struct net_device *dev) +{ + int i; + struct net_device *p; + struct in_device *in_dev; + + /* moved here from isdn_net_reset, because only the master has an + interface associated which is supposed to be started. BTW: + we need to call netif_start_queue, not netif_wake_queue here */ + netif_start_queue(dev); + + isdn_net_reset(dev); + /* Fill in the MAC-level header (not needed, but for compatibility... */ + for (i = 0; i < ETH_ALEN - sizeof(u32); i++) + dev->dev_addr[i] = 0xfc; + if ((in_dev = dev->ip_ptr) != NULL) { + /* + * Any address will do - we take the first + */ + struct in_ifaddr *ifa = in_dev->ifa_list; + if (ifa != NULL) + memcpy(dev->dev_addr+2, &ifa->ifa_local, 4); + } + + /* If this interface has slaves, start them also */ + + if ((p = (((isdn_net_local *) dev->priv)->slave))) { + while (p) { + isdn_net_reset(p); + p = (((isdn_net_local *) p->priv)->slave); + } + } + isdn_lock_drivers(); + return 0; +} + +/* + * Assign an ISDN-channel to a net-interface + */ +static void +isdn_net_bind_channel(isdn_net_local * lp, int idx) +{ + lp->flags |= ISDN_NET_CONNECTED; + lp->isdn_device = dev->drvmap[idx]; + lp->isdn_channel = dev->chanmap[idx]; + dev->rx_netdev[idx] = lp->netdev; + dev->st_netdev[idx] = lp->netdev; +} + +/* + * unbind a net-interface (resets interface after an error) + */ +static void +isdn_net_unbind_channel(isdn_net_local * lp) +{ + skb_queue_purge(&lp->super_tx_queue); + + if (!lp->master) { /* reset only master device */ + /* Moral equivalent of dev_purge_queues(): + BEWARE! This chunk of code cannot be called from hardware + interrupt handler. I hope it is true. --ANK + */ + qdisc_reset(lp->netdev->dev.qdisc); + } + 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; +} + +/* + * 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. + */ +unsigned long last_jiffies = -HZ; + +void +isdn_net_autohup(void) +{ + isdn_net_dev *p = dev->netdev; + int anymore; + + anymore = 0; + while (p) { + isdn_net_local *l = p->local; + if (jiffies == last_jiffies) + l->cps = l->transcount; + else + l->cps = (l->transcount * HZ) / (jiffies - last_jiffies); + 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 there is some dialmode where timeout-hangup + * should _not_ be done, check for that here + */ + if ((l->onhtime) && + (l->huptimer > l->onhtime)) + { + if (l->hupflags & ISDN_MANCHARGE && + l->hupflags & ISDN_CHARGEHUP) { + while (time_after(jiffies, l->chargetime + l->chargeint)) + l->chargetime += l->chargeint; + if (time_after(jiffies, l->chargetime + l->chargeint - 2 * HZ)) + if (l->outgoing || l->hupflags & ISDN_INHUP) + isdn_net_hangup(&p->dev); + } else if (l->outgoing) { + if (l->hupflags & ISDN_CHARGEHUP) { + if (l->hupflags & ISDN_WAITCHARGE) { + printk(KERN_DEBUG "isdn_net: Hupflags of %s are %X\n", + l->name, l->hupflags); + isdn_net_hangup(&p->dev); + } else if (time_after(jiffies, l->chargetime + l->chargeint)) { + printk(KERN_DEBUG + "isdn_net: %s: chtime = %lu, chint = %d\n", + l->name, l->chargetime, l->chargeint); + isdn_net_hangup(&p->dev); + } + } else + isdn_net_hangup(&p->dev); + } else if (l->hupflags & ISDN_INHUP) + isdn_net_hangup(&p->dev); + } + + if(dev->global_flags & ISDN_GLOBAL_STOPPED || (ISDN_NET_DIALMODE(*l) == ISDN_NET_DM_OFF)) { + isdn_net_hangup(&p->dev); + break; + } + } + p = (isdn_net_dev *) p->next; + } + last_jiffies = jiffies; + isdn_timer_ctrl(ISDN_TIMER_NETHANGUP, anymore); +} + +static void isdn_net_lp_disconnected(isdn_net_local *lp) +{ + isdn_net_rm_from_bundle(lp); +} + +/* + * 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, isdn_ctrl *c) +{ + isdn_net_dev *p = dev->st_netdev[idx]; + int cmd = c->command; + + if (p) { + isdn_net_local *lp = p->local; +#ifdef CONFIG_ISDN_X25 + struct concap_proto *cprot = lp->netdev->cprot; + struct concap_proto_ops *pops = cprot ? cprot->pops : NULL; +#endif + switch (cmd) { + case ISDN_STAT_BSENT: + /* A packet has successfully been sent out */ + if ((lp->flags & ISDN_NET_CONNECTED) && + (!lp->dialstate)) { + isdn_net_dec_frame_cnt(lp); + lp->stats.tx_packets++; + lp->stats.tx_bytes += c->parm.length; + } + 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 */ +#ifdef CONFIG_ISDN_X25 + /* If we are not connencted then dialing had + failed. If there are generic encap protocol + receiver routines signal the closure of + the link*/ + + if( !(lp->flags & ISDN_NET_CONNECTED) + && pops && pops -> disconn_ind ) + pops -> disconn_ind(cprot); +#endif /* CONFIG_ISDN_X25 */ + if ((!lp->dialstate) && (lp->flags & ISDN_NET_CONNECTED)) { + if (lp->p_encap == ISDN_NET_ENCAP_CISCOHDLCK) + isdn_net_ciscohdlck_disconnected(lp); +#ifdef CONFIG_ISDN_PPP + if (lp->p_encap == ISDN_NET_ENCAP_SYNCPPP) + isdn_ppp_free(lp); +#endif + isdn_net_lp_disconnected(lp); + 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); + isdn_net_unbind_channel(lp); + return 1; + } + break; +#ifdef CONFIG_ISDN_X25 + case ISDN_STAT_BHUP: + /* B-Channel-hangup */ + /* try if there are generic encap protocol + receiver routines and signal the closure of + the link */ + if( pops && pops -> disconn_ind ){ + pops -> disconn_ind(cprot); + return 1; + } + break; +#endif /* CONFIG_ISDN_X25 */ + case ISDN_STAT_BCONN: + /* B-Channel is up */ + isdn_net_zero_frame_cnt(lp); + 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); + if (lp->p_encap == ISDN_NET_ENCAP_CISCOHDLCK) + isdn_net_ciscohdlck_connected(lp); + if (lp->p_encap != ISDN_NET_ENCAP_SYNCPPP) { + if (lp->master) { /* is lp a slave? */ + isdn_net_dev *nd = ((isdn_net_local *)lp->master->priv)->netdev; + isdn_net_add_to_bundle(nd, lp); + } + } + 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; + + /* reset dial-timeout */ + lp->dialstarted = 0; + lp->dialwait_timer = 0; + +#ifdef CONFIG_ISDN_PPP + if (lp->p_encap == ISDN_NET_ENCAP_SYNCPPP) + isdn_ppp_wakeup_daemon(lp); +#endif +#ifdef CONFIG_ISDN_X25 + /* try if there are generic concap receiver routines */ + if( pops ) + if( pops->connect_ind) + pops->connect_ind(cprot); +#endif /* CONFIG_ISDN_X25 */ + /* ppp needs to do negotiations first */ + if (lp->p_encap != ISDN_NET_ENCAP_SYNCPPP) + isdn_net_device_wake_queue(lp); + 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 & ISDN_HAVECHARGE) { + lp->hupflags &= ~ISDN_WAITCHARGE; + lp->chargeint = jiffies - lp->chargetime - (2 * HZ); + } + if (lp->hupflags & ISDN_WAITCHARGE) + lp->hupflags |= ISDN_HAVECHARGE; + lp->chargetime = jiffies; + printk(KERN_DEBUG "isdn_net: Got CINF chargetime of %s now %lu\n", + lp->name, lp->chargetime); + return 1; + } + } + return 0; +} + +/* + * 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; + u_char *phone_number; + + while (p) { + isdn_net_local *lp = p->local; + +#ifdef ISDN_DEBUG_NET_DIAL + if (lp->dialstate) + printk(KERN_DEBUG "%s: dialstate=%d\n", lp->name, lp->dialstate); +#endif + switch (lp->dialstate) { + case 0: + /* Nothing to do for this interface */ + break; + case 1: + /* Initiate dialout. Set phone-number-pointer to first number + * of interface. + */ + lp->dial = lp->phone[1]; + if (!lp->dial) { + printk(KERN_WARNING "%s: phone number deleted?\n", + lp->name); + isdn_net_hangup(&p->dev); + break; + } + anymore = 1; + + if(lp->dialtimeout > 0) + if(lp->dialstarted == 0 || time_after(jiffies, lp->dialstarted + lp->dialtimeout + lp->dialwait)) { + lp->dialstarted = jiffies; + lp->dialwait_timer = 0; + } + + lp->dialstate++; + /* Fall through */ + case 2: + /* Prepare dialing. Clear EAZ, then set EAZ. */ + cmd.driver = lp->isdn_device; + cmd.arg = lp->isdn_channel; + cmd.command = ISDN_CMD_CLREAZ; + isdn_command(&cmd); + sprintf(cmd.parm.num, "%s", isdn_map_eaz2msn(lp->msn, cmd.driver)); + cmd.command = ISDN_CMD_SETEAZ; + isdn_command(&cmd); + lp->dialretry = 0; + anymore = 1; + lp->dialstate++; + /* Fall through */ + case 3: + /* Setup interface, dial current phone-number, switch to next number. + * If list of phone-numbers is exhausted, increment + * retry-counter. + */ + if(dev->global_flags & ISDN_GLOBAL_STOPPED || (ISDN_NET_DIALMODE(*lp) == ISDN_NET_DM_OFF)) { + char *s; + if (dev->global_flags & ISDN_GLOBAL_STOPPED) + s = "dial suppressed: isdn system stopped"; + else + s = "dial suppressed: dialmode `off'"; + isdn_net_unreachable(&p->dev, NULL, s); + isdn_net_hangup(&p->dev); + break; + } + cmd.driver = lp->isdn_device; + cmd.command = ISDN_CMD_SETL2; + cmd.arg = lp->isdn_channel + (lp->l2_proto << 8); + isdn_command(&cmd); + cmd.driver = lp->isdn_device; + cmd.command = ISDN_CMD_SETL3; + cmd.arg = lp->isdn_channel + (lp->l3_proto << 8); + isdn_command(&cmd); + cmd.driver = lp->isdn_device; + cmd.arg = lp->isdn_channel; + if (!lp->dial) { + printk(KERN_WARNING "%s: phone number deleted?\n", + lp->name); + isdn_net_hangup(&p->dev); + break; + } + if (!strncmp(lp->dial->num, "LEASED", strlen("LEASED"))) { + lp->dialstate = 4; + printk(KERN_INFO "%s: Open leased line ...\n", lp->name); + } else { + if(lp->dialtimeout > 0) + if (time_after(jiffies, lp->dialstarted + lp->dialtimeout)) { + lp->dialwait_timer = jiffies + lp->dialwait; + lp->dialstarted = 0; + isdn_net_unreachable(&p->dev, NULL, "dial: timed out"); + isdn_net_hangup(&p->dev); + break; + } + + cmd.driver = lp->isdn_device; + cmd.command = ISDN_CMD_DIAL; + cmd.parm.setup.si2 = 0; + + /* check for DOV */ + phone_number = lp->dial->num; + if ((*phone_number == 'v') || + (*phone_number == 'V')) { /* DOV call */ + cmd.parm.setup.si1 = 1; + } else { /* DATA call */ + cmd.parm.setup.si1 = 7; + } + + strcpy(cmd.parm.setup.phone, phone_number); + /* + * Switch to next number or back to start if at end of list. + */ + if (!(lp->dial = (isdn_net_phone *) lp->dial->next)) { + lp->dial = lp->phone[1]; + lp->dialretry++; + + if (lp->dialretry > lp->dialmax) { + if (lp->dialtimeout == 0) { + lp->dialwait_timer = jiffies + lp->dialwait; + lp->dialstarted = 0; + isdn_net_unreachable(&p->dev, NULL, "dial: tried all numbers dialmax times"); + } + isdn_net_hangup(&p->dev); + break; + } + } + sprintf(cmd.parm.setup.eazmsn, "%s", + isdn_map_eaz2msn(lp->msn, cmd.driver)); + i = isdn_dc2minor(lp->isdn_device, lp->isdn_channel); + if (i >= 0) { + strcpy(dev->num[i], cmd.parm.setup.phone); + dev->usage[i] |= ISDN_USAGE_OUTGOING; + isdn_info_update(); + } + printk(KERN_INFO "%s: dialing %d %s... %s\n", lp->name, + lp->dialretry, cmd.parm.setup.phone, + (cmd.parm.setup.si1 == 1) ? "DOV" : ""); + lp->dtimer = 0; +#ifdef ISDN_DEBUG_NET_DIAL + printk(KERN_DEBUG "dial: d=%d c=%d\n", lp->isdn_device, + lp->isdn_channel); +#endif + isdn_command(&cmd); + } + lp->huptimer = 0; + lp->outgoing = 1; + if (lp->chargeint) { + lp->hupflags |= ISDN_HAVECHARGE; + lp->hupflags &= ~ISDN_WAITCHARGE; + } else { + lp->hupflags |= ISDN_WAITCHARGE; + lp->hupflags &= ~ISDN_HAVECHARGE; + } + anymore = 1; + lp->dialstate = + (lp->cbdelay && + (lp->flags & ISDN_NET_CBOUT)) ? 12 : 4; + break; + case 4: + /* Wait for D-Channel-connect. + * If timeout, switch back to state 3. + * Dialmax-handling moved to state 3. + */ + if (lp->dtimer++ > ISDN_TIMER_DTIMEOUT10) + lp->dialstate = 3; + anymore = 1; + break; + case 5: + /* Got D-Channel-Connect, send B-Channel-request */ + cmd.driver = lp->isdn_device; + cmd.arg = lp->isdn_channel; + cmd.command = ISDN_CMD_ACCEPTB; + anymore = 1; + lp->dtimer = 0; + lp->dialstate++; + isdn_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", lp->dtimer); +#endif + if (lp->dtimer++ > ISDN_TIMER_DTIMEOUT10) + lp->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", lp->dtimer); +#endif + cmd.driver = lp->isdn_device; + cmd.command = ISDN_CMD_SETL2; + cmd.arg = lp->isdn_channel + (lp->l2_proto << 8); + isdn_command(&cmd); + cmd.driver = lp->isdn_device; + cmd.command = ISDN_CMD_SETL3; + cmd.arg = lp->isdn_channel + (lp->l3_proto << 8); + isdn_command(&cmd); + if (lp->dtimer++ > ISDN_TIMER_DTIMEOUT15) + isdn_net_hangup(&p->dev); + else { + anymore = 1; + lp->dialstate++; + } + break; + case 9: + /* Got incoming D-Channel-Connect, send B-Channel-request */ + cmd.driver = lp->isdn_device; + cmd.arg = lp->isdn_channel; + cmd.command = ISDN_CMD_ACCEPTB; + isdn_command(&cmd); + anymore = 1; + lp->dtimer = 0; + lp->dialstate++; + break; + case 8: + case 10: + /* Wait for B- or D-channel-connect */ +#ifdef ISDN_DEBUG_NET_DIAL + printk(KERN_DEBUG "dialtimer4: %d\n", lp->dtimer); +#endif + if (lp->dtimer++ > ISDN_TIMER_DTIMEOUT10) + isdn_net_hangup(&p->dev); + else + anymore = 1; + break; + case 11: + /* Callback Delay */ + if (lp->dtimer++ > lp->cbdelay) + lp->dialstate = 1; + anymore = 1; + break; + case 12: + /* Remote does callback. Hangup after cbdelay, then wait for incoming + * call (in state 4). + */ + if (lp->dtimer++ > lp->cbdelay) + { + printk(KERN_INFO "%s: hangup waiting for callback ...\n", lp->name); + lp->dtimer = 0; + lp->dialstate = 4; + cmd.driver = lp->isdn_device; + cmd.command = ISDN_CMD_HANGUP; + cmd.arg = lp->isdn_channel; + isdn_command(&cmd); + isdn_all_eaz(lp->isdn_device, lp->isdn_channel); + } + anymore = 1; + break; + default: + printk(KERN_WARNING "isdn_net: Illegal dialstate %d for device %s\n", + lp->dialstate, lp->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 net_device *d) +{ + isdn_net_local *lp = (isdn_net_local *) d->priv; + isdn_ctrl cmd; +#ifdef CONFIG_ISDN_X25 + struct concap_proto *cprot = lp->netdev->cprot; + struct concap_proto_ops *pops = cprot ? cprot->pops : NULL; +#endif + + if (lp->flags & ISDN_NET_CONNECTED) { + if (lp->slave != NULL) { + isdn_net_local *slp = (isdn_net_local *)lp->slave->priv; + if (slp->flags & ISDN_NET_CONNECTED) { + printk(KERN_INFO + "isdn_net: hang up slave %s before %s\n", + slp->name, lp->name); + isdn_net_hangup(lp->slave); + } + } + printk(KERN_INFO "isdn_net: local hangup %s\n", lp->name); +#ifdef CONFIG_ISDN_PPP + if (lp->p_encap == ISDN_NET_ENCAP_SYNCPPP) + isdn_ppp_free(lp); +#endif + isdn_net_lp_disconnected(lp); +#ifdef CONFIG_ISDN_X25 + /* try if there are generic encap protocol + receiver routines and signal the closure of + the link */ + if( pops && pops -> disconn_ind ) + pops -> disconn_ind(cprot); +#endif /* CONFIG_ISDN_X25 */ + + cmd.driver = lp->isdn_device; + cmd.command = ISDN_CMD_HANGUP; + cmd.arg = lp->isdn_channel; + isdn_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_skb(struct sk_buff * skb, isdn_net_local * lp) +{ + u_char *p = skb->nh.raw; /* hopefully, this was set correctly */ + unsigned short proto = ntohs(skb->protocol); + int data_ofs; + ip_ports *ipp; + char addinfo[100]; + + addinfo[0] = '\0'; + /* This check stolen from 2.1.72 dev_queue_xmit_nit() */ + if (skb->nh.raw < skb->data || skb->nh.raw >= skb->tail) { + /* fall back to old isdn_net_log_packet method() */ + char * buf = skb->data; + + printk(KERN_DEBUG "isdn_net: protocol %04x is buggy, dev %s\n", skb->protocol, lp->name); + p = buf; + proto = ETH_P_IP; + 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; +#ifdef CONFIG_ISDN_PPP + case ISDN_NET_ENCAP_SYNCPPP: + proto = ntohs(skb->protocol); + p = &buf[IPPP_MAX_HEADER]; + break; +#endif + } + } + 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; + } +} + +/* + * this function is used to send supervisory data, i.e. data which was + * not received from the network layer, but e.g. frames from ipppd, CCP + * reset frames etc. + */ +void isdn_net_write_super(isdn_net_local *lp, struct sk_buff *skb) +{ + if (in_irq()) { + // we can't grab the lock from irq context, + // so we just queue the packet + skb_queue_tail(&lp->super_tx_queue, skb); + schedule_work(&lp->tqueue); + return; + } + + spin_lock_bh(&lp->xmit_lock); + if (!isdn_net_lp_busy(lp)) { + isdn_net_writebuf_skb(lp, skb); + } else { + skb_queue_tail(&lp->super_tx_queue, skb); + } + spin_unlock_bh(&lp->xmit_lock); +} + +/* + * called from tq_immediate + */ +static void isdn_net_softint(void *private) +{ + isdn_net_local *lp = private; + struct sk_buff *skb; + + spin_lock_bh(&lp->xmit_lock); + while (!isdn_net_lp_busy(lp)) { + skb = skb_dequeue(&lp->super_tx_queue); + if (!skb) + break; + isdn_net_writebuf_skb(lp, skb); + } + spin_unlock_bh(&lp->xmit_lock); +} + +/* + * all frames sent from the (net) LL to a HL driver should go via this function + * it's serialized by the caller holding the lp->xmit_lock spinlock + */ +void isdn_net_writebuf_skb(isdn_net_local *lp, struct sk_buff *skb) +{ + int ret; + int len = skb->len; /* save len */ + + /* before obtaining the lock the caller should have checked that + the lp isn't busy */ + if (isdn_net_lp_busy(lp)) { + printk("isdn BUG at %s:%d!\n", __FILE__, __LINE__); + goto error; + } + + if (!(lp->flags & ISDN_NET_CONNECTED)) { + printk("isdn BUG at %s:%d!\n", __FILE__, __LINE__); + goto error; + } + ret = isdn_writebuf_skb_stub(lp->isdn_device, lp->isdn_channel, 1, skb); + if (ret != len) { + /* we should never get here */ + printk(KERN_WARNING "%s: HL driver queue full\n", lp->name); + goto error; + } + + lp->transcount += len; + isdn_net_inc_frame_cnt(lp); + return; + + error: + dev_kfree_skb(skb); + lp->stats.tx_errors++; + +} + + +/* + * 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 net_device *ndev, struct sk_buff *skb) +{ + isdn_net_dev *nd; + isdn_net_local *slp; + isdn_net_local *lp = (isdn_net_local *) ndev->priv; + int retv = 0; + + if (((isdn_net_local *) (ndev->priv))->master) { + printk("isdn BUG at %s:%d!\n", __FILE__, __LINE__); + dev_kfree_skb(skb); + return 0; + } + + /* 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 + nd = ((isdn_net_local *) ndev->priv)->netdev; + lp = isdn_net_get_locked_lp(nd); + if (!lp) { + printk(KERN_WARNING "%s: all channels busy - requeuing!\n", ndev->name); + return 1; + } + /* we have our lp locked from now on */ + + /* Reset hangup-timeout */ + lp->huptimer = 0; // FIXME? + isdn_net_writebuf_skb(lp, skb); + spin_unlock_bh(&lp->xmit_lock); + + /* the following stuff is here for backwards compatibility. + * in future, start-up and hangup of slaves (based on current load) + * should move to userspace and get based on an overall cps + * calculation + */ + if (lp->cps > lp->triggercps) { + 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 (time_after(jiffies, lp->sqfull_stamp + lp->slavedelay)) { + slp = lp->slave->priv; + if (!(slp->flags & ISDN_NET_CONNECTED)) { + isdn_net_force_dial_lp((isdn_net_local *) lp->slave->priv); + } + } + } + } + } else { + if (lp->sqfull && time_after(jiffies, lp->sqfull_stamp + lp->slavedelay + (10 * HZ))) { + lp->sqfull = 0; + } + /* this is a hack to allow auto-hangup for slaves on moderate loads */ + nd->queue = nd->local; + } + + return retv; + +} + +static void +isdn_net_adjust_hdr(struct sk_buff *skb, struct net_device *dev) +{ + isdn_net_local *lp = (isdn_net_local *) dev->priv; + if (!skb) + return; + if (lp->p_encap == ISDN_NET_ENCAP_ETHER) { + int pullsize = (ulong)skb->nh.raw - (ulong)skb->data - ETH_HLEN; + if (pullsize > 0) { + printk(KERN_DEBUG "isdn_net: Pull junk %d\n", pullsize); + skb_pull(skb, pullsize); + } + } +} + + +void isdn_net_tx_timeout(struct net_device * ndev) +{ + isdn_net_local *lp = (isdn_net_local *) ndev->priv; + + printk(KERN_WARNING "isdn_tx_timeout dev %s dialstate %d\n", ndev->name, lp->dialstate); + if (!lp->dialstate){ + lp->stats.tx_errors++; + /* + * There is a certain probability that this currently + * works at all because if we always wake up the interface, + * then upper layer will try to send the next packet + * immediately. And then, the old clean_up logic in the + * driver will hopefully continue to work as it used to do. + * + * This is rather primitive right know, we better should + * clean internal queues here, in particular for multilink and + * ppp, and reset HL driver's channel, too. --HE + * + * actually, this may not matter at all, because ISDN hardware + * should not see transmitter hangs at all IMO + * changed KERN_DEBUG to KERN_WARNING to find out if this is + * ever called --KG + */ + } + ndev->trans_start = jiffies; + netif_wake_queue(ndev); +} + +/* + * Try sending a packet. + * If this interface isn't connected to a ISDN-Channel, find a free channel, + * and start dialing. + */ +static int +isdn_net_start_xmit(struct sk_buff *skb, struct net_device *ndev) +{ + isdn_net_local *lp = (isdn_net_local *) ndev->priv; +#ifdef CONFIG_ISDN_X25 + struct concap_proto * cprot = lp -> netdev -> cprot; +/* At this point hard_start_xmit() passes control to the encapsulation + protocol (if present). + For X.25 auto-dialing is completly bypassed because: + - It does not conform with the semantics of a reliable datalink + service as needed by X.25 PLP. + - I don't want that the interface starts dialing when the network layer + sends a message which requests to disconnect the lapb link (or if it + sends any other message not resulting in data transmission). + Instead, dialing will be initiated by the encapsulation protocol entity + when a dl_establish request is received from the upper layer. +*/ + if (cprot && cprot -> pops) { + int ret = cprot -> pops -> encap_and_xmit ( cprot , skb); + + if (ret) + netif_stop_queue(ndev); + return ret; + } else +#endif + /* auto-dialing xmit function */ + { +#ifdef ISDN_DEBUG_NET_DUMP + u_char *buf; +#endif + isdn_net_adjust_hdr(skb, ndev); +#ifdef ISDN_DEBUG_NET_DUMP + buf = skb->data; + isdn_dumppkt("S:", buf, skb->len, 40); +#endif + + if (!(lp->flags & ISDN_NET_CONNECTED)) { + int chi; + /* only do autodial if allowed by config */ + if (!(ISDN_NET_DIALMODE(*lp) == ISDN_NET_DM_AUTO)) { + isdn_net_unreachable(ndev, skb, "dial rejected: interface not in dialmode `auto'"); + dev_kfree_skb(skb); + return 0; + } + if (lp->phone[1]) { + ulong flags; + + if(lp->dialwait_timer <= 0) + if(lp->dialstarted > 0 && lp->dialtimeout > 0 && time_before(jiffies, lp->dialstarted + lp->dialtimeout + lp->dialwait)) + lp->dialwait_timer = lp->dialstarted + lp->dialtimeout + lp->dialwait; + + if(lp->dialwait_timer > 0) { + if(time_before(jiffies, lp->dialwait_timer)) { + isdn_net_unreachable(ndev, skb, "dial rejected: retry-time not reached"); + dev_kfree_skb(skb); + return 0; + } else + lp->dialwait_timer = 0; + } + /* Grab a free ISDN-Channel */ + spin_lock_irqsave(&dev->lock, flags); + if (((chi = + isdn_get_free_channel( + ISDN_USAGE_NET, + lp->l2_proto, + lp->l3_proto, + lp->pre_device, + lp->pre_channel, + lp->msn) + ) < 0) && + ((chi = + isdn_get_free_channel( + ISDN_USAGE_NET, + lp->l2_proto, + lp->l3_proto, + lp->pre_device, + lp->pre_channel^1, + lp->msn) + ) < 0)) { + spin_unlock_irqrestore(&dev->lock, flags); + isdn_net_unreachable(ndev, skb, + "No channel"); + dev_kfree_skb(skb); + return 0; + } + /* Log packet, which triggered dialing */ + if (dev->net_verbose) + isdn_net_log_skb(skb, lp); + lp->dialstate = 1; + /* 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); + isdn_net_unbind_channel(lp); + spin_unlock_irqrestore(&dev->lock, flags); + return 0; /* STN (skb to nirvana) ;) */ + } +#ifdef CONFIG_IPPP_FILTER + if (isdn_ppp_autodial_filter(skb, lp)) { + isdn_ppp_free(lp); + isdn_net_unbind_channel(lp); + spin_unlock_irqrestore(&dev->lock, flags); + isdn_net_unreachable(ndev, skb, "dial rejected: packet filtered"); + dev_kfree_skb(skb); + return 0; + } +#endif + spin_unlock_irqrestore(&dev->lock, flags); + isdn_net_dial(); /* Initiate dialing */ + netif_stop_queue(ndev); + return 1; /* let upper layer requeue skb packet */ + } +#endif + /* Initiate dialing */ + spin_unlock_irqrestore(&dev->lock, flags); + isdn_net_dial(); + isdn_net_device_stop_queue(lp); + return 1; + } else { + isdn_net_unreachable(ndev, skb, + "No phone number"); + dev_kfree_skb(skb); + return 0; + } + } else { + /* Device is connected to an ISDN channel */ + ndev->trans_start = jiffies; + if (!lp->dialstate) { + /* ISDN connection is established, try sending */ + int ret; + ret = (isdn_net_xmit(ndev, skb)); + if(ret) netif_stop_queue(ndev); + return ret; + } else + netif_stop_queue(ndev); + } + } + return 1; +} + +/* + * Shutdown a net-interface. + */ +static int +isdn_net_close(struct net_device *dev) +{ + struct net_device *p; +#ifdef CONFIG_ISDN_X25 + struct concap_proto * cprot = + ( (isdn_net_local *) dev->priv ) -> netdev -> cprot; + /* printk(KERN_DEBUG "isdn_net_close %s\n" , dev-> name ); */ +#endif + +#ifdef CONFIG_ISDN_X25 + if( cprot && cprot -> pops ) cprot -> pops -> close( cprot ); +#endif + netif_stop_queue(dev); + if ((p = (((isdn_net_local *) dev->priv)->slave))) { + /* If this interface has slaves, stop them also */ + while (p) { +#ifdef CONFIG_ISDN_X25 + cprot = ( (isdn_net_local *) p->priv ) + -> netdev -> cprot; + if( cprot && cprot -> pops ) + cprot -> pops -> close( cprot ); +#endif + isdn_net_hangup(p); + p = (((isdn_net_local *) p->priv)->slave); + } + } + isdn_net_hangup(dev); + isdn_unlock_drivers(); + return 0; +} + +/* + * Get statistics + */ +static struct net_device_stats * +isdn_net_get_stats(struct net_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. + */ + +static unsigned short +isdn_net_type_trans(struct sk_buff *skb, struct net_device *dev) +{ + struct ethhdr *eth; + unsigned char *rawp; + + skb->mac.raw = skb->data; + skb_pull(skb, ETH_HLEN); + eth = eth_hdr(skb); + + 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); +} + + +/* + * CISCO HDLC keepalive specific stuff + */ +static struct sk_buff* +isdn_net_ciscohdlck_alloc_skb(isdn_net_local *lp, int len) +{ + unsigned short hl = dev->drv[lp->isdn_device]->interface->hl_hdrlen; + struct sk_buff *skb; + + skb = alloc_skb(hl + len, GFP_ATOMIC); + if (skb) + skb_reserve(skb, hl); + else + printk("isdn out of mem at %s:%d!\n", __FILE__, __LINE__); + return skb; +} + +/* cisco hdlck device private ioctls */ +int +isdn_ciscohdlck_dev_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) +{ + isdn_net_local *lp = (isdn_net_local *) dev->priv; + unsigned long len = 0; + unsigned long expires = 0; + int tmp = 0; + int period = lp->cisco_keepalive_period; + s8 debserint = lp->cisco_debserint; + int rc = 0; + + if (lp->p_encap != ISDN_NET_ENCAP_CISCOHDLCK) + return -EINVAL; + + switch (cmd) { + /* get/set keepalive period */ + case SIOCGKEEPPERIOD: + len = (unsigned long)sizeof(lp->cisco_keepalive_period); + if (copy_to_user(ifr->ifr_data, + &lp->cisco_keepalive_period, len)) + rc = -EFAULT; + break; + case SIOCSKEEPPERIOD: + tmp = lp->cisco_keepalive_period; + len = (unsigned long)sizeof(lp->cisco_keepalive_period); + if (copy_from_user(&period, ifr->ifr_data, len)) + rc = -EFAULT; + if ((period > 0) && (period <= 32767)) + lp->cisco_keepalive_period = period; + else + rc = -EINVAL; + if (!rc && (tmp != lp->cisco_keepalive_period)) { + expires = (unsigned long)(jiffies + + lp->cisco_keepalive_period * HZ); + mod_timer(&lp->cisco_timer, expires); + printk(KERN_INFO "%s: Keepalive period set " + "to %d seconds.\n", + lp->name, lp->cisco_keepalive_period); + } + break; + + /* get/set debugging */ + case SIOCGDEBSERINT: + len = (unsigned long)sizeof(lp->cisco_debserint); + if (copy_to_user(ifr->ifr_data, + &lp->cisco_debserint, len)) + rc = -EFAULT; + break; + case SIOCSDEBSERINT: + len = (unsigned long)sizeof(lp->cisco_debserint); + if (copy_from_user(&debserint, + ifr->ifr_data, len)) + rc = -EFAULT; + if ((debserint >= 0) && (debserint <= 64)) + lp->cisco_debserint = debserint; + else + rc = -EINVAL; + break; + + default: + rc = -EINVAL; + break; + } + return (rc); +} + +/* called via cisco_timer.function */ +static void +isdn_net_ciscohdlck_slarp_send_keepalive(unsigned long data) +{ + isdn_net_local *lp = (isdn_net_local *) data; + struct sk_buff *skb; + unsigned char *p; + unsigned long last_cisco_myseq = lp->cisco_myseq; + int myseq_diff = 0; + + if (!(lp->flags & ISDN_NET_CONNECTED) || lp->dialstate) { + printk("isdn BUG at %s:%d!\n", __FILE__, __LINE__); + return; + } + lp->cisco_myseq++; + + myseq_diff = (lp->cisco_myseq - lp->cisco_mineseen); + if ((lp->cisco_line_state) && ((myseq_diff >= 3)||(myseq_diff <= -3))) { + /* line up -> down */ + lp->cisco_line_state = 0; + printk (KERN_WARNING + "UPDOWN: Line protocol on Interface %s," + " changed state to down\n", lp->name); + /* should stop routing higher-level data accross */ + } else if ((!lp->cisco_line_state) && + (myseq_diff >= 0) && (myseq_diff <= 2)) { + /* line down -> up */ + lp->cisco_line_state = 1; + printk (KERN_WARNING + "UPDOWN: Line protocol on Interface %s," + " changed state to up\n", lp->name); + /* restart routing higher-level data accross */ + } + + if (lp->cisco_debserint) + printk (KERN_DEBUG "%s: HDLC " + "myseq %lu, mineseen %lu%c, yourseen %lu, %s\n", + lp->name, last_cisco_myseq, lp->cisco_mineseen, + ((last_cisco_myseq == lp->cisco_mineseen) ? '*' : 040), + lp->cisco_yourseq, + ((lp->cisco_line_state) ? "line up" : "line down")); + + skb = isdn_net_ciscohdlck_alloc_skb(lp, 4 + 14); + if (!skb) + return; + + p = skb_put(skb, 4 + 14); + + /* cisco header */ + p += put_u8 (p, CISCO_ADDR_UNICAST); + p += put_u8 (p, CISCO_CTRL); + p += put_u16(p, CISCO_TYPE_SLARP); + + /* slarp keepalive */ + p += put_u32(p, CISCO_SLARP_KEEPALIVE); + p += put_u32(p, lp->cisco_myseq); + p += put_u32(p, lp->cisco_yourseq); + p += put_u16(p, 0xffff); // reliablity, always 0xffff + + isdn_net_write_super(lp, skb); + + lp->cisco_timer.expires = jiffies + lp->cisco_keepalive_period * HZ; + + add_timer(&lp->cisco_timer); +} + +static void +isdn_net_ciscohdlck_slarp_send_request(isdn_net_local *lp) +{ + struct sk_buff *skb; + unsigned char *p; + + skb = isdn_net_ciscohdlck_alloc_skb(lp, 4 + 14); + if (!skb) + return; + + p = skb_put(skb, 4 + 14); + + /* cisco header */ + p += put_u8 (p, CISCO_ADDR_UNICAST); + p += put_u8 (p, CISCO_CTRL); + p += put_u16(p, CISCO_TYPE_SLARP); + + /* slarp request */ + p += put_u32(p, CISCO_SLARP_REQUEST); + p += put_u32(p, 0); // address + p += put_u32(p, 0); // netmask + p += put_u16(p, 0); // unused + + isdn_net_write_super(lp, skb); +} + +static void +isdn_net_ciscohdlck_connected(isdn_net_local *lp) +{ + lp->cisco_myseq = 0; + lp->cisco_mineseen = 0; + lp->cisco_yourseq = 0; + lp->cisco_keepalive_period = ISDN_TIMER_KEEPINT; + lp->cisco_last_slarp_in = 0; + lp->cisco_line_state = 0; + lp->cisco_debserint = 0; + + /* send slarp request because interface/seq.no.s reset */ + isdn_net_ciscohdlck_slarp_send_request(lp); + + init_timer(&lp->cisco_timer); + lp->cisco_timer.data = (unsigned long) lp; + lp->cisco_timer.function = isdn_net_ciscohdlck_slarp_send_keepalive; + lp->cisco_timer.expires = jiffies + lp->cisco_keepalive_period * HZ; + add_timer(&lp->cisco_timer); +} + +static void +isdn_net_ciscohdlck_disconnected(isdn_net_local *lp) +{ + del_timer(&lp->cisco_timer); +} + +static void +isdn_net_ciscohdlck_slarp_send_reply(isdn_net_local *lp) +{ + struct sk_buff *skb; + unsigned char *p; + struct in_device *in_dev = NULL; + u32 addr = 0; /* local ipv4 address */ + u32 mask = 0; /* local netmask */ + + if ((in_dev = lp->netdev->dev.ip_ptr) != NULL) { + /* take primary(first) address of interface */ + struct in_ifaddr *ifa = in_dev->ifa_list; + if (ifa != NULL) { + addr = ifa->ifa_local; + mask = ifa->ifa_mask; + } + } + + skb = isdn_net_ciscohdlck_alloc_skb(lp, 4 + 14); + if (!skb) + return; + + p = skb_put(skb, 4 + 14); + + /* cisco header */ + p += put_u8 (p, CISCO_ADDR_UNICAST); + p += put_u8 (p, CISCO_CTRL); + p += put_u16(p, CISCO_TYPE_SLARP); + + /* slarp reply, send own ip/netmask; if values are nonsense remote + * should think we are unable to provide it with an address via SLARP */ + p += put_u32(p, CISCO_SLARP_REPLY); + p += put_u32(p, addr); // address + p += put_u32(p, mask); // netmask + p += put_u16(p, 0); // unused + + isdn_net_write_super(lp, skb); +} + +static void +isdn_net_ciscohdlck_slarp_in(isdn_net_local *lp, struct sk_buff *skb) +{ + unsigned char *p; + int period; + u32 code; + u32 my_seq, addr; + u32 your_seq, mask; + u32 local; + u16 unused; + + if (skb->len < 14) + return; + + p = skb->data; + p += get_u32(p, &code); + + switch (code) { + case CISCO_SLARP_REQUEST: + lp->cisco_yourseq = 0; + isdn_net_ciscohdlck_slarp_send_reply(lp); + break; + case CISCO_SLARP_REPLY: + addr = ntohl(*(u32 *)p); + mask = ntohl(*(u32 *)(p+4)); + if (mask != 0xfffffffc) + goto slarp_reply_out; + if ((addr & 3) == 0 || (addr & 3) == 3) + goto slarp_reply_out; + local = addr ^ 3; + printk(KERN_INFO "%s: got slarp reply: " + "remote ip: %d.%d.%d.%d, " + "local ip: %d.%d.%d.%d " + "mask: %d.%d.%d.%d\n", + lp->name, + HIPQUAD(addr), + HIPQUAD(local), + HIPQUAD(mask)); + break; + slarp_reply_out: + printk(KERN_INFO "%s: got invalid slarp " + "reply (%d.%d.%d.%d/%d.%d.%d.%d) " + "- ignored\n", lp->name, + HIPQUAD(addr), HIPQUAD(mask)); + break; + case CISCO_SLARP_KEEPALIVE: + period = (int)((jiffies - lp->cisco_last_slarp_in + + HZ/2 - 1) / HZ); + if (lp->cisco_debserint && + (period != lp->cisco_keepalive_period) && + lp->cisco_last_slarp_in) { + printk(KERN_DEBUG "%s: Keepalive period mismatch - " + "is %d but should be %d.\n", + lp->name, period, lp->cisco_keepalive_period); + } + lp->cisco_last_slarp_in = jiffies; + p += get_u32(p, &my_seq); + p += get_u32(p, &your_seq); + p += get_u16(p, &unused); + lp->cisco_yourseq = my_seq; + lp->cisco_mineseen = your_seq; + break; + } +} + +static void +isdn_net_ciscohdlck_receive(isdn_net_local *lp, struct sk_buff *skb) +{ + unsigned char *p; + u8 addr; + u8 ctrl; + u16 type; + + if (skb->len < 4) + goto out_free; + + p = skb->data; + p += get_u8 (p, &addr); + p += get_u8 (p, &ctrl); + p += get_u16(p, &type); + skb_pull(skb, 4); + + if (addr != CISCO_ADDR_UNICAST && addr != CISCO_ADDR_BROADCAST) { + printk(KERN_WARNING "%s: Unknown Cisco addr 0x%02x\n", + lp->name, addr); + goto out_free; + } + if (ctrl != CISCO_CTRL) { + printk(KERN_WARNING "%s: Unknown Cisco ctrl 0x%02x\n", + lp->name, ctrl); + goto out_free; + } + + switch (type) { + case CISCO_TYPE_SLARP: + isdn_net_ciscohdlck_slarp_in(lp, skb); + goto out_free; + case CISCO_TYPE_CDP: + if (lp->cisco_debserint) + printk(KERN_DEBUG "%s: Received CDP packet. use " + "\"no cdp enable\" on cisco.\n", lp->name); + goto out_free; + default: + /* no special cisco protocol */ + skb->protocol = htons(type); + netif_rx(skb); + return; + } + + out_free: + kfree_skb(skb); +} + +/* + * Got a packet from ISDN-Channel. + */ +static void +isdn_net_receive(struct net_device *ndev, struct sk_buff *skb) +{ + isdn_net_local *lp = (isdn_net_local *) ndev->priv; + isdn_net_local *olp = lp; /* original 'lp' */ +#ifdef CONFIG_ISDN_X25 + struct concap_proto *cprot = lp -> netdev -> cprot; +#endif + lp->transcount += skb->len; + + lp->stats.rx_packets++; + lp->stats.rx_bytes += skb->len; + 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->stats.rx_bytes += skb->len; + } + skb->dev = ndev; + skb->input_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 */ + olp->huptimer = 0; + lp->huptimer = 0; + skb->protocol = isdn_net_type_trans(skb, ndev); + break; + case ISDN_NET_ENCAP_UIHDLC: + /* HDLC with UI-frame (for ispa with -h1 option) */ + olp->huptimer = 0; + lp->huptimer = 0; + skb_pull(skb, 2); + /* Fall through */ + case ISDN_NET_ENCAP_RAWIP: + /* RAW-IP without MAC-Header */ + olp->huptimer = 0; + lp->huptimer = 0; + skb->protocol = htons(ETH_P_IP); + break; + case ISDN_NET_ENCAP_CISCOHDLCK: + isdn_net_ciscohdlck_receive(lp, skb); + return; + 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 */ + olp->huptimer = 0; + lp->huptimer = 0; + 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: + /* huptimer is done in isdn_ppp_push_higher */ + isdn_ppp_receive(lp->netdev, olp, skb); + return; +#endif + + default: +#ifdef CONFIG_ISDN_X25 + /* try if there are generic sync_device receiver routines */ + if(cprot) if(cprot -> pops) + if( cprot -> pops -> data_ind){ + cprot -> pops -> data_ind(cprot,skb); + return; + }; +#endif /* CONFIG_ISDN_X25 */ + printk(KERN_WARNING "%s: unknown encapsulation, dropping\n", + lp->name); + kfree_skb(skb); + 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_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 net_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 | IFF_NOARP)) { + memset(eth->h_dest, 0, dev->addr_len); + return ETH_HLEN /*(dev->hard_header_len)*/; + } + if (daddr) { + memcpy(eth->h_dest, daddr, dev->addr_len); + return ETH_HLEN /*dev->hard_header_len*/; + } + return -ETH_HLEN /*dev->hard_header_len*/; +} + +/* + * build an header + * depends on encaps that is being used. + */ + +static int +isdn_net_header(struct sk_buff *skb, struct net_device *dev, unsigned short type, + void *daddr, void *saddr, unsigned plen) +{ + isdn_net_local *lp = dev->priv; + unsigned char *p; + ushort len = 0; + + switch (lp->p_encap) { + case ISDN_NET_ENCAP_ETHER: + len = my_eth_header(skb, dev, type, daddr, saddr, plen); + break; +#ifdef CONFIG_ISDN_PPP + case ISDN_NET_ENCAP_SYNCPPP: + /* stick on a fake header to keep fragmentation code happy. */ + len = IPPP_MAX_HEADER; + skb_push(skb,len); + break; +#endif + 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: + case ISDN_NET_ENCAP_CISCOHDLCK: + p = skb_push(skb, 4); + p += put_u8 (p, CISCO_ADDR_UNICAST); + p += put_u8 (p, CISCO_CTRL); + p += put_u16(p, type); + len = 4; + break; +#ifdef CONFIG_ISDN_X25 + default: + /* try if there are generic concap protocol routines */ + if( lp-> netdev -> cprot ){ + printk(KERN_WARNING "isdn_net_header called with concap_proto!\n"); + len = 0; + break; + } + break; +#endif /* CONFIG_ISDN_X25 */ + } + return len; +} + +/* We don't need to send arp, because we have point-to-point connections. */ +static int +isdn_net_rebuild_header(struct sk_buff *skb) +{ + struct net_device *dev = skb->dev; + isdn_net_local *lp = dev->priv; + int ret = 0; + + if (lp->p_encap == ISDN_NET_ENCAP_ETHER) { + struct ethhdr *eth = (struct ethhdr *) skb->data; + + /* + * 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, skb); +#endif + } + return ret; +} + +/* + * Interface-setup. (just after registering a new interface) + */ +static int +isdn_net_init(struct net_device *ndev) +{ + ushort max_hlhdr_len = 0; + isdn_net_local *lp = (isdn_net_local *) ndev->priv; + int drvidx, i; + + ether_setup(ndev); + lp->org_hhc = ndev->hard_header_cache; + lp->org_hcu = ndev->header_cache_update; + + /* Setup the generic properties */ + + ndev->hard_header = NULL; + ndev->hard_header_cache = NULL; + ndev->header_cache_update = NULL; + ndev->mtu = 1500; + ndev->flags = IFF_NOARP|IFF_POINTOPOINT; + ndev->type = ARPHRD_ETHER; + ndev->addr_len = ETH_ALEN; + + /* for clients with MPPP maybe higher values better */ + ndev->tx_queue_len = 30; + + for (i = 0; i < ETH_ALEN; i++) + ndev->broadcast[i] = 0xff; + + /* 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; + ndev->do_ioctl = NULL; + return 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 + * 5 = No appropriate interface for this call, + * would eventually match if CID was longer. + */ + +int +isdn_net_find_icall(int di, int ch, int idx, setup_parm *setup) +{ + char *eaz; + int si1; + int si2; + int ematch; + int wret; + int swapped; + int sidx = 0; + u_long flags; + isdn_net_dev *p; + isdn_net_phone *n; + char nr[32]; + char *my_eaz; + + /* Search name in netdev-chain */ + if (!setup->phone[0]) { + nr[0] = '0'; + nr[1] = '\0'; + printk(KERN_INFO "isdn_net: Incoming call without OAD, assuming '0'\n"); + } else + strcpy(nr, setup->phone); + si1 = (int) setup->si1; + si2 = (int) setup->si2; + if (!setup->eazmsn[0]) { + printk(KERN_WARNING "isdn_net: Incoming call without CPN, assuming '0'\n"); + eaz = "0"; + } else + eaz = setup->eazmsn; + if (dev->net_verbose > 1) + printk(KERN_INFO "isdn_net: call from %s,%d,%d -> %s\n", nr, si1, si2, eaz); + /* Accept DATA and VOICE calls at this stage + * local eaz is checked later for allowed call types + */ + if ((si1 != 7) && (si1 != 1)) { + if (dev->net_verbose > 1) + printk(KERN_INFO "isdn_net: Service-Indicator not 1 or 7, ignored\n"); + return 0; + } + n = (isdn_net_phone *) 0; + p = dev->netdev; + ematch = wret = swapped = 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 + while (p) { + int matchret; + isdn_net_local *lp = p->local; + + /* 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; + /* check acceptable call types for DOV */ + my_eaz = isdn_map_eaz2msn(lp->msn, di); + if (si1 == 1) { /* it's a DOV call, check if we allow it */ + if (*my_eaz == 'v' || *my_eaz == 'V' || + *my_eaz == 'b' || *my_eaz == 'B') + my_eaz++; /* skip to allow a match */ + else + my_eaz = NULL; /* force non match */ + } else { /* it's a DATA call, check if we allow it */ + if (*my_eaz == 'b' || *my_eaz == 'B') + my_eaz++; /* skip to allow a match */ + } + if (my_eaz) + matchret = isdn_msncmp(eaz, my_eaz); + else + matchret = 1; + if (!matchret) + ematch = 1; + + /* Remember if more numbers eventually can match */ + if (matchret > wret) + wret = matchret; +#ifdef ISDN_DEBUG_NET_ICALL + printk(KERN_DEBUG "n_fi: if='%s', l.msn=%s, l.flags=%d, l.dstate=%d\n", + lp->name, lp->msn, lp->flags, lp->dialstate); +#endif + if ((!matchret) && /* EAZ is matching */ + (((!(lp->flags & ISDN_NET_CONNECTED)) && /* but not connected */ + (USG_NONE(dev->usage[idx]))) || /* and ch. unused or */ + ((((lp->dialstate == 4) || (lp->dialstate == 12)) && /* if dialing */ + (!(lp->flags & ISDN_NET_CALLBACK))) /* but no callback */ + ))) + { +#ifdef ISDN_DEBUG_NET_ICALL + printk(KERN_DEBUG "n_fi: match1, pdev=%d pch=%d\n", + lp->pre_device, lp->pre_channel); +#endif + if (dev->usage[idx] & ISDN_USAGE_EXCLUSIVE) { + if ((lp->pre_channel != ch) || + (lp->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 ((lp->pre_device == di) && + (lp->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) && + ((lp->pre_channel != ch) || + (lp->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 + } + } + } +#ifdef ISDN_DEBUG_NET_ICALL + printk(KERN_DEBUG "n_fi: match2\n"); +#endif + n = lp->phone[0]; + if (lp->flags & ISDN_NET_SECURE) { + while (n) { + if (!isdn_msncmp(nr, n->num)) + break; + n = (isdn_net_phone *) n->next; + } + } + if (n || (!(lp->flags & ISDN_NET_SECURE))) { +#ifdef ISDN_DEBUG_NET_ICALL + printk(KERN_DEBUG "n_fi: match3\n"); +#endif + /* matching interface found */ + + /* + * Is the state STOPPED? + * If so, no dialin is allowed, + * so reject actively. + * */ + if (ISDN_NET_DIALMODE(*lp) == ISDN_NET_DM_OFF) { + printk(KERN_INFO "incoming call, interface %s `stopped' -> rejected\n", + lp->name); + return 3; + } + /* + * Is the interface up? + * If not, reject the call actively. + */ + if (!isdn_net_device_started(p)) { + printk(KERN_INFO "%s: incoming call, interface 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; + /* + * Is the state MANUAL? + * If so, no callback can be made, + * so reject actively. + * */ + if (ISDN_NET_DIALMODE(*lp) == ISDN_NET_DM_OFF) { + printk(KERN_INFO "incoming call for callback, interface %s `off' -> rejected\n", + lp->name); + return 3; + } + printk(KERN_DEBUG "%s: call from %s -> %s, start callback\n", + lp->name, nr, eaz); + if (lp->phone[1]) { + /* Grab a free ISDN-Channel */ + spin_lock_irqsave(&dev->lock, flags); + if ((chi = + isdn_get_free_channel( + ISDN_USAGE_NET, + lp->l2_proto, + lp->l3_proto, + lp->pre_device, + lp->pre_channel, + lp->msn) + ) < 0) { + + printk(KERN_WARNING "isdn_net_find_icall: No channel for %s\n", lp->name); + spin_unlock_irqrestore(&dev->lock, flags); + return 0; + } + /* Setup dialstate. */ + lp->dtimer = 0; + lp->dialstate = 11; + /* 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) { + spin_unlock_irqrestore(&dev->lock, flags); + isdn_net_unbind_channel(lp); + return 0; + } +#endif + spin_unlock_irqrestore(&dev->lock, flags); + /* Initiate dialing by returning 2 or 4 */ + return (lp->flags & ISDN_NET_CBHUP) ? 2 : 4; + } else + printk(KERN_WARNING "isdn_net: %s: No phone number\n", lp->name); + 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 ((lp->dialstate == 4) || (lp->dialstate == 12)) { +#ifdef CONFIG_ISDN_PPP + if (lp->p_encap == ISDN_NET_ENCAP_SYNCPPP) + isdn_ppp_free(lp); +#endif + isdn_net_lp_disconnected(lp); + isdn_free_channel(lp->isdn_device, lp->isdn_channel, + ISDN_USAGE_NET); + } + spin_lock_irqsave(&dev->lock, flags); + 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; + lp->isdn_device = di; + lp->isdn_channel = ch; + lp->ppp_slot = -1; + lp->flags |= ISDN_NET_CONNECTED; + lp->dialstate = 7; + lp->dtimer = 0; + lp->outgoing = 0; + lp->huptimer = 0; + lp->hupflags |= ISDN_WAITCHARGE; + lp->hupflags &= ~ISDN_HAVECHARGE; +#ifdef CONFIG_ISDN_PPP + if (lp->p_encap == ISDN_NET_ENCAP_SYNCPPP) { + if (isdn_ppp_bind(lp) < 0) { + isdn_net_unbind_channel(lp); + spin_unlock_irqrestore(&dev->lock, flags); + return 0; + } + } +#endif + spin_unlock_irqrestore(&dev->lock, 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); + return (wret == 2)?5: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; + + /* Grab a free ISDN-Channel */ + spin_lock_irqsave(&dev->lock, flags); + if ((chi = isdn_get_free_channel( + ISDN_USAGE_NET, + lp->l2_proto, + lp->l3_proto, + lp->pre_device, + lp->pre_channel, + lp->msn)) < 0) { + printk(KERN_WARNING "isdn_net_force_dial: No channel for %s\n", lp->name); + spin_unlock_irqrestore(&dev->lock, flags); + return -EAGAIN; + } + lp->dialstate = 1; + /* 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); + spin_unlock_irqrestore(&dev->lock, flags); + return -EAGAIN; + } +#endif + /* Initiate dialing */ + spin_unlock_irqrestore(&dev->lock, flags); + isdn_net_dial(); + return 0; + } else + return -EINVAL; + } else + return -EBUSY; +} + +/* + * This is called from certain upper protocol layers (multilink ppp + * and x25iface encapsulation module) that want to initiate dialing + * themselves. + */ +int +isdn_net_dial_req(isdn_net_local * lp) +{ + /* is there a better error code? */ + if (!(ISDN_NET_DIALMODE(*lp) == ISDN_NET_DM_AUTO)) return -EBUSY; + + return isdn_net_force_dial_lp(lp); +} + +/* + * 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 net_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 (!(netdev->local = (isdn_net_local *) kmalloc(sizeof(isdn_net_local), GFP_KERNEL))) { + printk(KERN_WARNING "isdn_net: Could not allocate device locals\n"); + kfree(netdev); + return NULL; + } + memset(netdev->local, 0, sizeof(isdn_net_local)); + if (name == NULL) + strcpy(netdev->local->name, " "); + else + strcpy(netdev->local->name, name); + strcpy(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 net_device *p = (((isdn_net_local *) master->priv)->slave); + struct net_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); + } else { + /* Device shall be a master */ + /* + * Watchdog timer (currently) for master only. + */ + netdev->dev.tx_timeout = isdn_net_tx_timeout; + netdev->dev.watchdog_timeo = ISDN_NET_TX_TIMEOUT; + if (register_netdev(&netdev->dev) != 0) { + printk(KERN_WARNING "isdn_net: Could not register net-device\n"); + kfree(netdev->local); + kfree(netdev); + return NULL; + } + } + netdev->local->magic = ISDN_NET_MAGIC; + + netdev->queue = netdev->local; + spin_lock_init(&netdev->queue_lock); + + netdev->local->last = netdev->local; + netdev->local->netdev = netdev; + netdev->local->next = netdev->local; + + INIT_WORK(&netdev->local->tqueue, (void *)(void *) isdn_net_softint, netdev->local); + spin_lock_init(&netdev->local->xmit_lock); + + 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; + skb_queue_head_init(&netdev->local->super_tx_queue); + netdev->local->l2_proto = ISDN_PROTO_L2_X75I; + netdev->local->l3_proto = ISDN_PROTO_L3_TRANS; + netdev->local->triggercps = 6000; + netdev->local->slavedelay = 10 * HZ; + netdev->local->hupflags = ISDN_INHUP; /* 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 | ISDN_NET_DM_MANUAL; /* Hangup before Callback, manual dial */ + netdev->local->cbdelay = 25; /* Wait 5 secs before Callback */ + netdev->local->dialtimeout = -1; /* Infinite Dial-Timeout */ + netdev->local->dialwait = 5 * HZ; /* Wait 5 sec. after failed dial */ + netdev->local->dialstarted = 0; /* Jiffies of last dial-start */ + netdev->local->dialwait_timer = 0; /* Jiffies of earliest next dial-start */ + + /* 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; + /* Master must not be started yet */ + if (isdn_net_device_started(n)) + 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) { + isdn_net_local *lp = p->local; + + /* See if any registered driver supports the features we want */ + features = ((1 << cfg->l2_proto) << ISDN_FEATURE_L2_SHIFT) | + ((1 << cfg->l3_proto) << ISDN_FEATURE_L3_SHIFT); + 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 (lp->p_encap != cfg->p_encap){ +#ifdef CONFIG_ISDN_X25 + struct concap_proto * cprot = p -> cprot; +#endif + if (isdn_net_device_started(p)) { + printk(KERN_WARNING "%s: cannot change encap when if is up\n", + lp->name); + return -EBUSY; + } +#ifdef CONFIG_ISDN_X25 + if( cprot && cprot -> pops ) + cprot -> pops -> proto_del ( cprot ); + p -> cprot = NULL; + lp -> dops = NULL; + /* ... , prepare for configuration of new one ... */ + switch ( cfg -> p_encap ){ + case ISDN_NET_ENCAP_X25IFACE: + lp -> dops = &isdn_concap_reliable_dl_dops; + } + /* ... and allocate new one ... */ + p -> cprot = isdn_concap_new( cfg -> p_encap ); + /* p -> cprot == NULL now if p_encap is not supported + by means of the concap_proto mechanism */ + /* the protocol is not configured yet; this will + happen later when isdn_net_reset() is called */ +#endif + } + switch ( cfg->p_encap ) { + case ISDN_NET_ENCAP_SYNCPPP: +#ifndef CONFIG_ISDN_PPP + printk(KERN_WARNING "%s: SyncPPP support not configured\n", + lp->name); + return -EINVAL; +#else + p->dev.type = ARPHRD_PPP; /* change ARP type */ + p->dev.addr_len = 0; + p->dev.do_ioctl = isdn_ppp_dev_ioctl; +#endif + break; + case ISDN_NET_ENCAP_X25IFACE: +#ifndef CONFIG_ISDN_X25 + printk(KERN_WARNING "%s: isdn-x25 support not configured\n", + p->local->name); + return -EINVAL; +#else + p->dev.type = ARPHRD_X25; /* change ARP type */ + p->dev.addr_len = 0; +#endif + break; + case ISDN_NET_ENCAP_CISCOHDLCK: + p->dev.do_ioctl = isdn_ciscohdlck_dev_ioctl; + break; + default: + if( cfg->p_encap >= 0 && + cfg->p_encap <= ISDN_NET_ENCAP_MAX_ENCAP ) + break; + printk(KERN_WARNING + "%s: encapsulation protocol %d not supported\n", + p->local->name, cfg->p_encap); + return -EINVAL; + } + 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 = lp->pre_device; + chidx = lp->pre_channel; + } + if (cfg->exclusive > 0) { + unsigned long flags; + + /* If binding is exclusive, try to grab the channel */ + spin_lock_irqsave(&dev->lock, flags); + if ((i = isdn_get_free_channel(ISDN_USAGE_NET, + lp->l2_proto, lp->l3_proto, drvidx, + chidx, lp->msn)) < 0) { + /* Grab failed, because desired channel is in use */ + lp->exclusive = -1; + spin_unlock_irqrestore(&dev->lock, flags); + return -EBUSY; + } + /* All went ok, so update isdninfo */ + dev->usage[i] = ISDN_USAGE_EXCLUSIVE; + isdn_info_update(); + spin_unlock_irqrestore(&dev->lock, flags); + lp->exclusive = i; + } else { + /* Non-exclusive binding or unbind. */ + lp->exclusive = -1; + if ((lp->pre_device != -1) && (cfg->exclusive == -1)) { + isdn_unexclusive_channel(lp->pre_device, lp->pre_channel); + isdn_free_channel(lp->pre_device, lp->pre_channel, ISDN_USAGE_NET); + drvidx = -1; + chidx = -1; + } + } + strcpy(lp->msn, cfg->eaz); + lp->pre_device = drvidx; + lp->pre_channel = chidx; + lp->onhtime = cfg->onhtime; + lp->charge = cfg->charge; + lp->l2_proto = cfg->l2_proto; + lp->l3_proto = cfg->l3_proto; + lp->cbdelay = cfg->cbdelay; + lp->dialmax = cfg->dialmax; + lp->triggercps = cfg->triggercps; + lp->slavedelay = cfg->slavedelay * HZ; + lp->pppbind = cfg->pppbind; + lp->dialtimeout = cfg->dialtimeout >= 0 ? cfg->dialtimeout * HZ : -1; + lp->dialwait = cfg->dialwait * HZ; + if (cfg->secure) + lp->flags |= ISDN_NET_SECURE; + else + lp->flags &= ~ISDN_NET_SECURE; + if (cfg->cbhup) + lp->flags |= ISDN_NET_CBHUP; + else + lp->flags &= ~ISDN_NET_CBHUP; + switch (cfg->callback) { + case 0: + lp->flags &= ~(ISDN_NET_CALLBACK | ISDN_NET_CBOUT); + break; + case 1: + lp->flags |= ISDN_NET_CALLBACK; + lp->flags &= ~ISDN_NET_CBOUT; + break; + case 2: + lp->flags |= ISDN_NET_CBOUT; + lp->flags &= ~ISDN_NET_CALLBACK; + break; + } + lp->flags &= ~ISDN_NET_DIALMODE_MASK; /* first all bits off */ + if (cfg->dialmode && !(cfg->dialmode & ISDN_NET_DIALMODE_MASK)) { + /* old isdnctrl version, where only 0 or 1 is given */ + printk(KERN_WARNING + "Old isdnctrl version detected! Please update.\n"); + lp->flags |= ISDN_NET_DM_OFF; /* turn on `off' bit */ + } + else { + lp->flags |= cfg->dialmode; /* turn on selected bits */ + } + if (cfg->chargehup) + lp->hupflags |= ISDN_CHARGEHUP; + else + lp->hupflags &= ~ISDN_CHARGEHUP; + if (cfg->ihup) + lp->hupflags |= ISDN_INHUP; + else + lp->hupflags &= ~ISDN_INHUP; + if (cfg->chargeint > 10) { + lp->hupflags |= ISDN_CHARGEHUP | ISDN_HAVECHARGE | ISDN_MANCHARGE; + lp->chargeint = cfg->chargeint * HZ; + } + if (cfg->p_encap != lp->p_encap) { + if (cfg->p_encap == ISDN_NET_ENCAP_RAWIP) { + p->dev.hard_header = NULL; + p->dev.hard_header_cache = NULL; + p->dev.header_cache_update = NULL; + p->dev.flags = IFF_NOARP|IFF_POINTOPOINT; + } else { + p->dev.hard_header = isdn_net_header; + if (cfg->p_encap == ISDN_NET_ENCAP_ETHER) { + p->dev.hard_header_cache = lp->org_hhc; + p->dev.header_cache_update = lp->org_hcu; + p->dev.flags = IFF_BROADCAST | IFF_MULTICAST; + } else { + p->dev.hard_header_cache = NULL; + p->dev.header_cache_update = NULL; + p->dev.flags = IFF_NOARP|IFF_POINTOPOINT; + } + } + } + lp->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) { + isdn_net_local *lp = p->local; + + strcpy(cfg->eaz, lp->msn); + cfg->exclusive = lp->exclusive; + if (lp->pre_device >= 0) { + sprintf(cfg->drvid, "%s,%d", dev->drvid[lp->pre_device], + lp->pre_channel); + } else + cfg->drvid[0] = '\0'; + cfg->onhtime = lp->onhtime; + cfg->charge = lp->charge; + cfg->l2_proto = lp->l2_proto; + cfg->l3_proto = lp->l3_proto; + cfg->p_encap = lp->p_encap; + cfg->secure = (lp->flags & ISDN_NET_SECURE) ? 1 : 0; + cfg->callback = 0; + if (lp->flags & ISDN_NET_CALLBACK) + cfg->callback = 1; + if (lp->flags & ISDN_NET_CBOUT) + cfg->callback = 2; + cfg->cbhup = (lp->flags & ISDN_NET_CBHUP) ? 1 : 0; + cfg->dialmode = lp->flags & ISDN_NET_DIALMODE_MASK; + cfg->chargehup = (lp->hupflags & 4) ? 1 : 0; + cfg->ihup = (lp->hupflags & 8) ? 1 : 0; + cfg->cbdelay = lp->cbdelay; + cfg->dialmax = lp->dialmax; + cfg->triggercps = lp->triggercps; + cfg->slavedelay = lp->slavedelay / HZ; + cfg->chargeint = (lp->hupflags & ISDN_CHARGEHUP) ? + (lp->chargeint / HZ) : 0; + cfg->pppbind = lp->pppbind; + cfg->dialtimeout = lp->dialtimeout >= 0 ? lp->dialtimeout / HZ : -1; + cfg->dialwait = lp->dialwait / HZ; + if (lp->slave) + strcpy(cfg->slave, ((isdn_net_local *) lp->slave->priv)->name); + else + cfg->slave[0] = '\0'; + if (lp->master) + strcpy(cfg->master, ((isdn_net_local *) lp->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 (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; +} + +/* + * Copy a string of all phone-numbers of an interface to user space. + * This might sleep and must be called with the isdn semaphore down. + */ +int +isdn_net_getphones(isdn_net_ioctl_phone * phone, char __user *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; + + if (!p) + return -ENODEV; + inout &= 1; + for (n = p->local->phone[inout]; n; n = n->next) { + if (more) { + put_user(' ', phones++); + count++; + } + if (copy_to_user(phones, n->num, strlen(n->num) + 1)) { + return -EFAULT; + } + phones += strlen(n->num); + count += strlen(n->num); + more = 1; + } + put_user(0, phones); + count++; + return count; +} + +/* + * Copy a string containing the peer's phone number of a connected interface + * to user space. + */ +int +isdn_net_getpeer(isdn_net_ioctl_phone *phone, isdn_net_ioctl_phone __user *peer) +{ + isdn_net_dev *p = isdn_net_findif(phone->name); + int ch, dv, idx; + + if (!p) return -ENODEV; + /* + * Theoretical race: while this executes, the remote number might + * become invalid (hang up) or change (new connection), resulting + * in (partially) wrong number copied to user. This race + * currently ignored. + */ + ch = p->local->isdn_channel; + dv = p->local->isdn_device; + if(ch<0 && dv<0) return -ENOTCONN; + idx = isdn_dc2minor(dv, ch); + if (idx<0) return -ENODEV; + /* for pre-bound channels, we need this extra check */ + if ( strncmp(dev->num[idx],"???",3) == 0 ) return -ENOTCONN; + strncpy(phone->phone,dev->num[idx],ISDN_MSNLEN); + phone->outgoing=USG_OUTGOING(dev->usage[idx]); + if ( copy_to_user(peer,phone,sizeof(*peer)) ) return -EFAULT; + return 0; +} +/* + * 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 (p->local->dial == n) + p->local->dial = n->next; + 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 i; + + 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; + } + p->local->dial = NULL; + return 0; +} + +/* + * Force a hangup of a network-interface. + */ +int +isdn_net_force_hangup(char *name) +{ + isdn_net_dev *p = isdn_net_findif(name); + struct net_device *q; + + if (p) { + if (p->local->isdn_device < 0) + return 1; + 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); + } + isdn_net_hangup(&p->dev); + 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) +{ + u_long flags; + + if (isdn_net_device_started(p)) { + return -EBUSY; + } +#ifdef CONFIG_ISDN_X25 + if( p -> cprot && p -> cprot -> pops ) + p -> cprot -> pops -> proto_del ( p -> cprot ); +#endif + /* 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 */ + p->dev.hard_header_cache = p->local->org_hhc; + p->dev.header_cache_update = p->local->org_hcu; + unregister_netdev(&p->dev); + } + /* Unlink device from chain */ + spin_lock_irqsave(&dev->lock, flags); + 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)) { + spin_unlock_irqrestore(&dev->lock, flags); + isdn_net_realrm(n, q); + spin_lock_irqsave(&dev->lock, flags); + break; + } + q = n; + n = (isdn_net_dev *) n->next; + } + } + spin_unlock_irqrestore(&dev->lock, flags); + /* If no more net-devices remain, disable auto-hangup timer */ + if (dev->netdev == NULL) + isdn_timer_ctrl(ISDN_TIMER_NETHANGUP, 0); + kfree(p->local); + kfree(p); + + return 0; +} + +/* + * Remove a single network-interface. + */ +int +isdn_net_rm(char *name) +{ + u_long flags; + isdn_net_dev *p; + isdn_net_dev *q; + + /* Search name in netdev-chain */ + spin_lock_irqsave(&dev->lock, flags); + p = dev->netdev; + q = NULL; + while (p) { + if (!strcmp(p->local->name, name)) { + spin_unlock_irqrestore(&dev->lock, flags); + return (isdn_net_realrm(p, q)); + } + q = p; + p = (isdn_net_dev *) p->next; + } + spin_unlock_irqrestore(&dev->lock, flags); + /* 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) +{ + u_long flags; + int ret; + + /* Walk through netdev-chain */ + spin_lock_irqsave(&dev->lock, flags); + while (dev->netdev) { + if (!dev->netdev->local->master) { + /* Remove master-devices only, slaves get removed with their master */ + spin_unlock_irqrestore(&dev->lock, flags); + if ((ret = isdn_net_realrm(dev->netdev, NULL))) { + return ret; + } + spin_lock_irqsave(&dev->lock, flags); + } + } + dev->netdev = NULL; + spin_unlock_irqrestore(&dev->lock, flags); + return 0; +} diff --git a/drivers/isdn/i4l/isdn_net.h b/drivers/isdn/i4l/isdn_net.h new file mode 100644 index 000000000000..bc2f0dd962ea --- /dev/null +++ b/drivers/isdn/i4l/isdn_net.h @@ -0,0 +1,190 @@ +/* $Id: isdn_net.h,v 1.1.2.2 2004/01/12 22:37:19 keil Exp $ + * + * header for Linux ISDN subsystem, network related functions (linklevel). + * + * Copyright 1994-1999 by Fritz Elfert (fritz@isdn4linux.de) + * Copyright 1995,96 by Thinking Objects Software GmbH Wuerzburg + * Copyright 1995,96 by Michael Hipp (Michael.Hipp@student.uni-tuebingen.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + + /* Definitions for hupflags: */ +#define ISDN_WAITCHARGE 1 /* did not get a charge info yet */ +#define ISDN_HAVECHARGE 2 /* We know a charge info */ +#define ISDN_CHARGEHUP 4 /* We want to use the charge mechanism */ +#define ISDN_INHUP 8 /* Even if incoming, close after huptimeout */ +#define ISDN_MANCHARGE 16 /* Charge Interval manually set */ + +/* + * Definitions for Cisco-HDLC header. + */ + +#define CISCO_ADDR_UNICAST 0x0f +#define CISCO_ADDR_BROADCAST 0x8f +#define CISCO_CTRL 0x00 +#define CISCO_TYPE_CDP 0x2000 +#define CISCO_TYPE_SLARP 0x8035 +#define CISCO_SLARP_REQUEST 0 +#define CISCO_SLARP_REPLY 1 +#define CISCO_SLARP_KEEPALIVE 2 + +extern char *isdn_net_new(char *, struct net_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, isdn_ctrl *); +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 __user *); +extern int isdn_net_getpeer(isdn_net_ioctl_phone *, isdn_net_ioctl_phone __user *); +extern int isdn_net_delphone(isdn_net_ioctl_phone *); +extern int isdn_net_find_icall(int, int, int, setup_parm *); +extern void isdn_net_hangup(struct net_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_rcv_skb(int, struct sk_buff *); +extern int isdn_net_dial_req(isdn_net_local *); +extern void isdn_net_writebuf_skb(isdn_net_local *lp, struct sk_buff *skb); +extern void isdn_net_write_super(isdn_net_local *lp, struct sk_buff *skb); + +#define ISDN_NET_MAX_QUEUE_LENGTH 2 + +/* + * is this particular channel busy? + */ +static __inline__ int isdn_net_lp_busy(isdn_net_local *lp) +{ + if (atomic_read(&lp->frame_cnt) < ISDN_NET_MAX_QUEUE_LENGTH) + return 0; + else + return 1; +} + +/* + * For the given net device, this will get a non-busy channel out of the + * corresponding bundle. The returned channel is locked. + */ +static __inline__ isdn_net_local * isdn_net_get_locked_lp(isdn_net_dev *nd) +{ + unsigned long flags; + isdn_net_local *lp; + + spin_lock_irqsave(&nd->queue_lock, flags); + lp = nd->queue; /* get lp on top of queue */ + spin_lock(&nd->queue->xmit_lock); + while (isdn_net_lp_busy(nd->queue)) { + spin_unlock(&nd->queue->xmit_lock); + nd->queue = nd->queue->next; + if (nd->queue == lp) { /* not found -- should never happen */ + lp = NULL; + goto errout; + } + spin_lock(&nd->queue->xmit_lock); + } + lp = nd->queue; + nd->queue = nd->queue->next; + local_bh_disable(); +errout: + spin_unlock_irqrestore(&nd->queue_lock, flags); + return lp; +} + +/* + * add a channel to a bundle + */ +static __inline__ void isdn_net_add_to_bundle(isdn_net_dev *nd, isdn_net_local *nlp) +{ + isdn_net_local *lp; + unsigned long flags; + + spin_lock_irqsave(&nd->queue_lock, flags); + + lp = nd->queue; +// printk(KERN_DEBUG "%s: lp:%s(%p) nlp:%s(%p) last(%p)\n", +// __FUNCTION__, lp->name, lp, nlp->name, nlp, lp->last); + nlp->last = lp->last; + lp->last->next = nlp; + lp->last = nlp; + nlp->next = lp; + nd->queue = nlp; + + spin_unlock_irqrestore(&nd->queue_lock, flags); +} +/* + * remove a channel from the bundle it belongs to + */ +static __inline__ void isdn_net_rm_from_bundle(isdn_net_local *lp) +{ + isdn_net_local *master_lp = lp; + unsigned long flags; + + if (lp->master) + master_lp = (isdn_net_local *) lp->master->priv; + +// printk(KERN_DEBUG "%s: lp:%s(%p) mlp:%s(%p) last(%p) next(%p) mndq(%p)\n", +// __FUNCTION__, lp->name, lp, master_lp->name, master_lp, lp->last, lp->next, master_lp->netdev->queue); + spin_lock_irqsave(&master_lp->netdev->queue_lock, flags); + 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 in queue */ + master_lp->netdev->queue = master_lp->netdev->local; + } + } + lp->next = lp->last = lp; /* (re)set own pointers */ +// printk(KERN_DEBUG "%s: mndq(%p)\n", +// __FUNCTION__, master_lp->netdev->queue); + spin_unlock_irqrestore(&master_lp->netdev->queue_lock, flags); +} + +static inline int +put_u8(unsigned char *p, u8 x) +{ + *p = x; + return 1; +} + +static inline int +put_u16(unsigned char *p, u16 x) +{ + *((u16 *)p) = htons(x); + return 2; +} + +static inline int +put_u32(unsigned char *p, u32 x) +{ + *((u32 *)p) = htonl(x); + return 4; +} + +static inline int +get_u8(unsigned char *p, u8 *x) +{ + *x = *p; + return 1; +} + +static inline int +get_u16(unsigned char *p, u16 *x) +{ + *x = ntohs(*((u16 *)p)); + return 2; +} + +static inline int +get_u32(unsigned char *p, u32 *x) +{ + *x = ntohl(*((u32 *)p)); + return 4; +} + + diff --git a/drivers/isdn/i4l/isdn_ppp.c b/drivers/isdn/i4l/isdn_ppp.c new file mode 100644 index 000000000000..3c092117a8ea --- /dev/null +++ b/drivers/isdn/i4l/isdn_ppp.c @@ -0,0 +1,3020 @@ +/* $Id: isdn_ppp.c,v 1.1.2.3 2004/02/10 01:07:13 keil Exp $ + * + * Linux ISDN subsystem, functions for synchronous PPP (linklevel). + * + * Copyright 1995,96 by Michael Hipp (Michael.Hipp@student.uni-tuebingen.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/config.h> +#include <linux/isdn.h> +#include <linux/poll.h> +#include <linux/ppp-comp.h> +#ifdef CONFIG_IPPP_FILTER +#include <linux/filter.h> +#endif + +#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); +static int isdn_ppp_set_compressor(struct ippp_struct *is,struct isdn_ppp_comp_data *); +static struct sk_buff *isdn_ppp_decompress(struct sk_buff *, + struct ippp_struct *,struct ippp_struct *,int *proto); +static void isdn_ppp_receive_ccp(isdn_net_dev * net_dev, isdn_net_local * lp, + struct sk_buff *skb,int proto); +static struct sk_buff *isdn_ppp_compress(struct sk_buff *skb_in,int *proto, + struct ippp_struct *is,struct ippp_struct *master,int type); +static void isdn_ppp_send_ccp(isdn_net_dev *net_dev, isdn_net_local *lp, + struct sk_buff *skb); + +/* New CCP stuff */ +static void isdn_ppp_ccp_kickup(struct ippp_struct *is); +static void isdn_ppp_ccp_xmit_reset(struct ippp_struct *is, int proto, + unsigned char code, unsigned char id, + unsigned char *data, int len); +static struct ippp_ccp_reset *isdn_ppp_ccp_reset_alloc(struct ippp_struct *is); +static void isdn_ppp_ccp_reset_free(struct ippp_struct *is); +static void isdn_ppp_ccp_reset_free_state(struct ippp_struct *is, + unsigned char id); +static void isdn_ppp_ccp_timer_callback(unsigned long closure); +static struct ippp_ccp_reset_state *isdn_ppp_ccp_reset_alloc_state(struct ippp_struct *is, + unsigned char id); +static void isdn_ppp_ccp_reset_trans(struct ippp_struct *is, + struct isdn_ppp_resetparams *rp); +static void isdn_ppp_ccp_reset_ack_rcvd(struct ippp_struct *is, + unsigned char id); + + + +#ifdef CONFIG_ISDN_MPP +static ippp_bundle * isdn_ppp_bundle_arr = NULL; + +static int isdn_ppp_mp_bundle_array_init(void); +static int isdn_ppp_mp_init( isdn_net_local * lp, ippp_bundle * add_to ); +static void isdn_ppp_mp_receive(isdn_net_dev * net_dev, isdn_net_local * lp, + struct sk_buff *skb); +static void isdn_ppp_mp_cleanup( isdn_net_local * lp ); + +static int isdn_ppp_bundle(struct ippp_struct *, int unit); +#endif /* CONFIG_ISDN_MPP */ + +char *isdn_ppp_revision = "$Revision: 1.1.2.3 $"; + +static struct ippp_struct *ippp_table[ISDN_MAX_CHANNELS]; + +static struct isdn_ppp_compressor *ipc_head = NULL; + +/* + * frame log (debug) + */ +static void +isdn_ppp_frame_log(char *info, char *data, int len, int maxlen,int unit,int slot) +{ + int cnt, + j, + i; + char buf[80]; + + if (len < maxlen) + maxlen = len; + + for (i = 0, cnt = 0; cnt < maxlen; i++) { + for (j = 0; j < 16 && cnt < maxlen; j++, cnt++) + sprintf(buf + j * 3, "%02x ", (unsigned char) data[cnt]); + printk(KERN_DEBUG "[%d/%d].%s[%d]: %s\n",unit,slot, info, i, buf); + } +} + +/* + * unbind isdn_net_local <=> ippp-device + * note: it can happen, that we hangup/free the master before the slaves + * in this case we bind another lp to the master device + */ +int +isdn_ppp_free(isdn_net_local * lp) +{ + struct ippp_struct *is; + + if (lp->ppp_slot < 0 || lp->ppp_slot > ISDN_MAX_CHANNELS) { + printk(KERN_ERR "%s: ppp_slot(%d) out of range\n", + __FUNCTION__, lp->ppp_slot); + return 0; + } + +#ifdef CONFIG_ISDN_MPP + spin_lock(&lp->netdev->pb->lock); +#endif + isdn_net_rm_from_bundle(lp); +#ifdef CONFIG_ISDN_MPP + if (lp->netdev->pb->ref_ct == 1) /* last link in queue? */ + isdn_ppp_mp_cleanup(lp); + + lp->netdev->pb->ref_ct--; + spin_unlock(&lp->netdev->pb->lock); +#endif /* CONFIG_ISDN_MPP */ + if (lp->ppp_slot < 0 || lp->ppp_slot > ISDN_MAX_CHANNELS) { + printk(KERN_ERR "%s: ppp_slot(%d) now invalid\n", + __FUNCTION__, lp->ppp_slot); + return 0; + } + is = ippp_table[lp->ppp_slot]; + if ((is->state & IPPP_CONNECT)) + isdn_ppp_closewait(lp->ppp_slot); /* force wakeup on ippp device */ + else if (is->state & IPPP_ASSIGNED) + is->state = IPPP_OPEN; /* fallback to 'OPEN but not ASSIGNED' state */ + + 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 ?? */ + + return 0; +} + +/* + * bind isdn_net_local <=> ippp-device + * + * This function is allways called with holding dev->lock so + * no additional lock is needed + */ +int +isdn_ppp_bind(isdn_net_local * lp) +{ + int i; + int unit = 0; + struct ippp_struct *is; + int retval; + + 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) == IPPP_OPEN) + break; + } + } + + if (i >= ISDN_MAX_CHANNELS) { + printk(KERN_WARNING "isdn_ppp_bind: Can't find a (free) connection to the ipppd daemon.\n"); + retval = -1; + goto out; + } + 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); + retval = -1; + goto out; + } + + lp->ppp_slot = i; + is = ippp_table[i]; + is->lp = lp; + is->unit = unit; + is->state = IPPP_OPEN | IPPP_ASSIGNED; /* assigned to a netdevice but not connected */ +#ifdef CONFIG_ISDN_MPP + retval = isdn_ppp_mp_init(lp, NULL); + if (retval < 0) + goto out; +#endif /* CONFIG_ISDN_MPP */ + + retval = lp->ppp_slot; + + out: + return retval; +} + +/* + * kick the ipppd on the device + * (wakes up daemon after B-channel connect) + */ + +void +isdn_ppp_wakeup_daemon(isdn_net_local * lp) +{ + if (lp->ppp_slot < 0 || lp->ppp_slot >= ISDN_MAX_CHANNELS) { + printk(KERN_ERR "%s: ppp_slot(%d) out of range\n", + __FUNCTION__, lp->ppp_slot); + return; + } + ippp_table[lp->ppp_slot]->state = IPPP_OPEN | IPPP_CONNECT | IPPP_NOBLOCK; + 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) { + printk(KERN_ERR "%s: slot(%d) out of range\n", + __FUNCTION__, slot); + return 0; + } + is = ippp_table[slot]; + if (is->state) + 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; + + if (min < 0 || min > ISDN_MAX_CHANNELS) + return -ENODEV; + + slot = isdn_ppp_get_slot(); + if (slot < 0) { + return -EBUSY; + } + is = file->private_data = ippp_table[slot]; + + printk(KERN_DEBUG "ippp, open, slot: %d, minor: %d, state: %04x\n", + slot, min, is->state); + + /* compression stuff */ + is->link_compressor = is->compressor = NULL; + is->link_decompressor = is->decompressor = NULL; + is->link_comp_stat = is->comp_stat = NULL; + is->link_decomp_stat = is->decomp_stat = NULL; + is->compflags = 0; + + is->reset = isdn_ppp_ccp_reset_alloc(is); + + is->lp = NULL; + is->mp_seqno = 0; /* MP sequence number */ + is->pppcfg = 0; /* ppp configuration */ + is->mpppcfg = 0; /* mppp configuration */ + 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; + init_waitqueue_head(&is->wq); + 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 +#ifdef CONFIG_IPPP_FILTER + is->pass_filter = NULL; + is->active_filter = NULL; +#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) { + printk(KERN_ERR "%s: no file->private_data\n", __FUNCTION__); + return; + } + 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 = is->lp->netdev; + + if (!p) { + printk(KERN_ERR "%s: no lp->netdev\n", __FUNCTION__); + return; + } + is->state &= ~IPPP_CONNECT; /* -> effect: no call of wakeup */ + /* + * isdn_net_hangup() calls isdn_ppp_free() + * isdn_ppp_free() sets is->lp to NULL and lp->ppp_slot to -1 + * removing the IPPP_CONNECT flag omits calling of isdn_ppp_wakeup_daemon() + */ + isdn_net_hangup(&p->dev); + } + 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 +/* TODO: if this was the previous master: link the slcomp to the new master */ + slhc_free(is->slcomp); + is->slcomp = NULL; +#endif +#ifdef CONFIG_IPPP_FILTER + if (is->pass_filter) { + kfree(is->pass_filter); + is->pass_filter = NULL; + } + if (is->active_filter) { + kfree(is->active_filter); + is->active_filter = NULL; + } +#endif + +/* TODO: if this was the previous master: link the stuff to the new master */ + if(is->comp_stat) + is->compressor->free(is->comp_stat); + if(is->link_comp_stat) + is->link_compressor->free(is->link_comp_stat); + if(is->link_decomp_stat) + is->link_decompressor->free(is->link_decomp_stat); + if(is->decomp_stat) + is->decompressor->free(is->decomp_stat); + is->compressor = is->link_compressor = NULL; + is->decompressor = is->link_decompressor = NULL; + is->comp_stat = is->link_comp_stat = NULL; + is->decomp_stat = is->link_decomp_stat = NULL; + + /* Clean up if necessary */ + if(is->reset) + isdn_ppp_ccp_reset_free(is); + + /* this slot is ready for new connections */ + is->state = 0; +} + +/* + * get_arg .. ioctl helper + */ +static int +get_arg(void __user *b, void *val, int len) +{ + if (len <= 0) + len = sizeof(void *); + if (copy_from_user(val, b, len)) + return -EFAULT; + return 0; +} + +/* + * set arg .. ioctl helper + */ +static int +set_arg(void __user *b, void *val,int len) +{ + if(len <= 0) + len = sizeof(void *); + if (copy_to_user(b, val, len)) + return -EFAULT; + return 0; +} + +static int get_filter(void __user *arg, struct sock_filter **p) +{ + struct sock_fprog uprog; + struct sock_filter *code = NULL; + int len, err; + + if (copy_from_user(&uprog, arg, sizeof(uprog))) + return -EFAULT; + + if (!uprog.len) { + *p = NULL; + return 0; + } + + /* uprog.len is unsigned short, so no overflow here */ + len = uprog.len * sizeof(struct sock_filter); + code = kmalloc(len, GFP_KERNEL); + if (code == NULL) + return -ENOMEM; + + if (copy_from_user(code, uprog.filter, len)) { + kfree(code); + return -EFAULT; + } + + err = sk_chk_filter(code, uprog.len); + if (err) { + kfree(code); + return err; + } + + *p = code; + return uprog.len; +} + +/* + * ippp device ioctl + */ +int +isdn_ppp_ioctl(int min, struct file *file, unsigned int cmd, unsigned long arg) +{ + unsigned long val; + int r,i,j; + struct ippp_struct *is; + isdn_net_local *lp; + struct isdn_ppp_comp_data data; + void __user *argp = (void __user *)arg; + + is = (struct ippp_struct *) file->private_data; + lp = is->lp; + + 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 (!(is->state & IPPP_CONNECT)) + return -EINVAL; + if ((r = get_arg(argp, &val, sizeof(val) ))) + 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 = set_arg(argp, &is->unit, sizeof(is->unit) ))) + return r; + break; + case PPPIOCGIFNAME: + if(!lp) + return -EINVAL; + if ((r = set_arg(argp, lp->name, strlen(lp->name)))) + return r; + break; + case PPPIOCGMPFLAGS: /* get configuration flags */ + if ((r = set_arg(argp, &is->mpppcfg, sizeof(is->mpppcfg) ))) + return r; + break; + case PPPIOCSMPFLAGS: /* set configuration flags */ + if ((r = get_arg(argp, &val, sizeof(val) ))) + return r; + is->mpppcfg = val; + break; + case PPPIOCGFLAGS: /* get configuration flags */ + if ((r = set_arg(argp, &is->pppcfg,sizeof(is->pppcfg) ))) + return r; + break; + case PPPIOCSFLAGS: /* set configuration flags */ + if ((r = get_arg(argp, &val, sizeof(val) ))) { + return r; + } + if (val & SC_ENABLE_IP && !(is->pppcfg & SC_ENABLE_IP) && (is->state & IPPP_CONNECT)) { + if (lp) { + /* OK .. we are ready to send buffers */ + is->pppcfg = val; /* isdn_ppp_xmit test for SC_ENABLE_IP !!! */ + netif_wake_queue(&lp->netdev->dev); + break; + } + } + is->pppcfg = val; + break; + case PPPIOCGIDLE: /* get idle time information */ + if (lp) { + struct ppp_idle pidle; + pidle.xmit_idle = pidle.recv_idle = lp->huptimer; + if ((r = set_arg(argp, &pidle,sizeof(struct ppp_idle)))) + return r; + } + break; + case PPPIOCSMRU: /* set receive unit size for PPP */ + if ((r = get_arg(argp, &val, sizeof(val) ))) + return r; + is->mru = val; + break; + case PPPIOCSMPMRU: + break; + case PPPIOCSMPMTU: + break; + case PPPIOCSMAXCID: /* set the maximum compression slot id */ + if ((r = get_arg(argp, &val, sizeof(val) ))) + 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 = set_arg(argp, &is->debug, sizeof(is->debug) ))) + return r; + break; + case PPPIOCSDEBUG: + if ((r = get_arg(argp, &val, sizeof(val) ))) + return r; + is->debug = val; + break; + case PPPIOCGCOMPRESSORS: + { + unsigned long protos[8] = {0,}; + struct isdn_ppp_compressor *ipc = ipc_head; + while(ipc) { + j = ipc->num / (sizeof(long)*8); + i = ipc->num % (sizeof(long)*8); + if(j < 8) + protos[j] |= (0x1<<i); + ipc = ipc->next; + } + if ((r = set_arg(argp,protos,8*sizeof(long) ))) + return r; + } + break; + case PPPIOCSCOMPRESSOR: + if ((r = get_arg(argp, &data, sizeof(struct isdn_ppp_comp_data)))) + return r; + return isdn_ppp_set_compressor(is, &data); + case PPPIOCGCALLINFO: + { + struct pppcallinfo pci; + memset((char *) &pci,0,sizeof(struct pppcallinfo)); + if(lp) + { + strncpy(pci.local_num,lp->msn,63); + if(lp->dial) { + strncpy(pci.remote_num,lp->dial->num,63); + } + pci.charge_units = lp->charge; + if(lp->outgoing) + pci.calltype = CALLTYPE_OUTGOING; + else + pci.calltype = CALLTYPE_INCOMING; + if(lp->flags & ISDN_NET_CALLBACK) + pci.calltype |= CALLTYPE_CALLBACK; + } + return set_arg(argp,&pci,sizeof(struct pppcallinfo)); + } +#ifdef CONFIG_IPPP_FILTER + case PPPIOCSPASS: + { + struct sock_filter *code; + int len = get_filter(argp, &code); + if (len < 0) + return len; + kfree(is->pass_filter); + is->pass_filter = code; + is->pass_len = len; + break; + } + case PPPIOCSACTIVE: + { + struct sock_filter *code; + int len = get_filter(argp, &code); + if (len < 0) + return len; + kfree(is->active_filter); + is->active_filter = code; + is->active_len = len; + break; + } +#endif /* CONFIG_IPPP_FILTER */ + default: + break; + } + return 0; +} + +unsigned int +isdn_ppp_poll(struct file *file, poll_table * wait) +{ + u_int mask; + struct ippp_buf_queue *bf, *bl; + u_long flags; + struct ippp_struct *is; + + is = file->private_data; + + if (is->debug & 0x2) + printk(KERN_DEBUG "isdn_ppp_poll: minor: %d\n", + MINOR(file->f_dentry->d_inode->i_rdev)); + + /* just registers wait_queue hook. This doesn't really wait. */ + poll_wait(file, &is->wq, wait); + + if (!(is->state & IPPP_OPEN)) { + if(is->state == IPPP_CLOSEWAIT) + return POLLHUP; + printk(KERN_DEBUG "isdn_ppp: device not open\n"); + return POLLERR; + } + /* we're always ready to send .. */ + mask = POLLOUT | POLLWRNORM; + + spin_lock_irqsave(&is->buflock, flags); + 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)) { + is->state &= ~IPPP_NOBLOCK; + mask |= POLLIN | POLLRDNORM; + } + spin_unlock_irqrestore(&is->buflock, flags); + return mask; +} + +/* + * 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; + u_long flags; + u_char *nbuf; + struct ippp_struct *is; + + if (slot < 0 || slot >= ISDN_MAX_CHANNELS) { + printk(KERN_WARNING "ippp: illegal slot(%d).\n", slot); + 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); + + spin_lock_irqsave(&is->buflock, flags); + 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; + spin_unlock_irqrestore(&is->buflock, flags); + 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 __user *buf, int count) +{ + struct ippp_struct *is; + struct ippp_buf_queue *b; + u_long flags; + u_char *save_buf; + + is = file->private_data; + + if (!(is->state & IPPP_OPEN)) + return 0; + + if (!access_ok(VERIFY_WRITE, buf, count)) + return -EFAULT; + + spin_lock_irqsave(&is->buflock, flags); + b = is->first->next; + save_buf = b->buf; + if (!save_buf) { + spin_unlock_irqrestore(&is->buflock, flags); + return -EAGAIN; + } + if (b->len < count) + count = b->len; + b->buf = NULL; + is->first = b; + + spin_unlock_irqrestore(&is->buflock, flags); + copy_to_user(buf, save_buf, count); + kfree(save_buf); + + return count; +} + +/* + * ipppd wanna write a packet to the card .. non-blocking + */ + +int +isdn_ppp_write(int min, struct file *file, const char __user *buf, int count) +{ + isdn_net_local *lp; + struct ippp_struct *is; + int proto; + unsigned char protobuf[4]; + + 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 { + /* + * Don't reset huptimer for + * LCP packets. (Echo requests). + */ + if (copy_from_user(protobuf, buf, 4)) + return -EFAULT; + proto = PPP_PROTOCOL(protobuf); + if (proto != PPP_LCP) + lp->huptimer = 0; + + if (lp->isdn_device < 0 || lp->isdn_channel < 0) + return 0; + + if ((dev->drv[lp->isdn_device]->flags & DRV_FLAG_RUNNING) && + lp->dialstate == 0 && + (lp->flags & ISDN_NET_CONNECTED)) { + unsigned short hl; + struct sk_buff *skb; + /* + * we need to reserve enought space in front of + * sk_buff. old call to dev_alloc_skb only reserved + * 16 bytes, now we are looking what the driver want + */ + hl = dev->drv[lp->isdn_device]->interface->hl_hdrlen; + skb = alloc_skb(hl+count, GFP_ATOMIC); + if (!skb) { + printk(KERN_WARNING "isdn_ppp_write: out of memory!\n"); + return count; + } + skb_reserve(skb, hl); + if (copy_from_user(skb_put(skb, count), buf, count)) + { + kfree_skb(skb); + return -EFAULT; + } + if (is->debug & 0x40) { + printk(KERN_DEBUG "ppp xmit: len %d\n", (int) skb->len); + isdn_ppp_frame_log("xmit", skb->data, skb->len, 32,is->unit,lp->ppp_slot); + } + + isdn_ppp_send_ccp(lp->netdev,lp,skb); /* keeps CCP/compression states in sync */ + + isdn_net_write_super(lp, skb); + } + } + return count; +} + +/* + * init memory, structures etc. + */ + +int +isdn_ppp_init(void) +{ + int i, + j; + +#ifdef CONFIG_ISDN_MPP + if( isdn_ppp_mp_bundle_array_init() < 0 ) + return -ENOMEM; +#endif /* CONFIG_ISDN_MPP */ + + 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[j]); + return -1; + } + memset((char *) ippp_table[i], 0, sizeof(struct ippp_struct)); + spin_lock_init(&ippp_table[i]->buflock); + 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]); + +#ifdef CONFIG_ISDN_MPP + if (isdn_ppp_bundle_arr) + kfree(isdn_ppp_bundle_arr); +#endif /* CONFIG_ISDN_MPP */ + +} + +/* + * check for address/control field and skip if allowed + * retval != 0 -> discard packet silently + */ +static int isdn_ppp_skip_ac(struct ippp_struct *is, struct sk_buff *skb) +{ + if (skb->len < 1) + return -1; + + if (skb->data[0] == 0xff) { + if (skb->len < 2) + return -1; + + if (skb->data[1] != 0x03) + return -1; + + // skip address/control (AC) field + skb_pull(skb, 2); + } else { + if (is->pppcfg & SC_REJ_COMP_AC) + // if AC compression was not negotiated, but used, discard packet + return -1; + } + return 0; +} + +/* + * get the PPP protocol header and pull skb + * retval < 0 -> discard packet silently + */ +static int isdn_ppp_strip_proto(struct sk_buff *skb) +{ + int proto; + + if (skb->len < 1) + return -1; + + if (skb->data[0] & 0x1) { + // protocol field is compressed + proto = skb->data[0]; + skb_pull(skb, 1); + } else { + if (skb->len < 2) + return -1; + proto = ((int) skb->data[0] << 8) + skb->data[1]; + skb_pull(skb, 2); + } + return proto; +} + + +/* + * 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; + int slot; + int proto; + + if (net_dev->local->master) + BUG(); // we're called with the master device always + + slot = lp->ppp_slot; + if (slot < 0 || slot >= ISDN_MAX_CHANNELS) { + printk(KERN_ERR "isdn_ppp_receive: lp->ppp_slot(%d)\n", + lp->ppp_slot); + kfree_skb(skb); + return; + } + is = ippp_table[slot]; + + if (is->debug & 0x4) { + printk(KERN_DEBUG "ippp_receive: is:%08lx lp:%08lx slot:%d unit:%d len:%d\n", + (long)is,(long)lp,lp->ppp_slot,is->unit,(int) skb->len); + isdn_ppp_frame_log("receive", skb->data, skb->len, 32,is->unit,lp->ppp_slot); + } + + if (isdn_ppp_skip_ac(is, skb) < 0) { + kfree_skb(skb); + return; + } + proto = isdn_ppp_strip_proto(skb); + if (proto < 0) { + kfree_skb(skb); + return; + } + +#ifdef CONFIG_ISDN_MPP + if (is->compflags & SC_LINK_DECOMP_ON) { + skb = isdn_ppp_decompress(skb, is, NULL, &proto); + if (!skb) // decompression error + return; + } + + if (!(is->mpppcfg & SC_REJ_MP_PROT)) { // we agreed to receive MPPP + if (proto == PPP_MP) { + isdn_ppp_mp_receive(net_dev, lp, skb); + return; + } + } +#endif + isdn_ppp_push_higher(net_dev, lp, skb, proto); +} + +/* + * we receive a reassembled frame, MPPP has been taken care of before. + * address/control and protocol have been stripped from the skb + * 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 net_device *dev = &net_dev->dev; + struct ippp_struct *is, *mis; + isdn_net_local *mlp = NULL; + int slot; + + slot = lp->ppp_slot; + if (slot < 0 || slot >= ISDN_MAX_CHANNELS) { + printk(KERN_ERR "isdn_ppp_push_higher: lp->ppp_slot(%d)\n", + lp->ppp_slot); + goto drop_packet; + } + is = ippp_table[slot]; + + if (lp->master) { // FIXME? + mlp = (isdn_net_local *) lp->master->priv; + slot = mlp->ppp_slot; + if (slot < 0 || slot > ISDN_MAX_CHANNELS) { + printk(KERN_ERR "isdn_ppp_push_higher: master->ppp_slot(%d)\n", + lp->ppp_slot); + goto drop_packet; + } + } + mis = ippp_table[slot]; + + if (is->debug & 0x10) { + printk(KERN_DEBUG "push, skb %d %04x\n", (int) skb->len, proto); + isdn_ppp_frame_log("rpush", skb->data, skb->len, 32,is->unit,lp->ppp_slot); + } + if (mis->compflags & SC_DECOMP_ON) { + skb = isdn_ppp_decompress(skb, is, mis, &proto); + if (!skb) // decompression error + return; + } + switch (proto) { + case PPP_IPX: /* untested */ + if (is->debug & 0x20) + printk(KERN_DEBUG "isdn_ppp: IPX\n"); + skb->protocol = htons(ETH_P_IPX); + break; + case PPP_IP: + if (is->debug & 0x20) + printk(KERN_DEBUG "isdn_ppp: IP\n"); + skb->protocol = htons(ETH_P_IP); + break; + case PPP_COMP: + case PPP_COMPFRAG: + printk(KERN_INFO "isdn_ppp: unexpected compressed frame dropped\n"); + goto drop_packet; +#ifdef CONFIG_ISDN_PPP_VJ + case PPP_VJC_UNCOMP: + if (is->debug & 0x20) + printk(KERN_DEBUG "isdn_ppp: VJC_UNCOMP\n"); + if (net_dev->local->ppp_slot < 0) { + printk(KERN_ERR "%s: net_dev->local->ppp_slot(%d) out of range\n", + __FUNCTION__, net_dev->local->ppp_slot); + goto drop_packet; + } + 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"); + goto drop_packet; + } + skb->protocol = htons(ETH_P_IP); + break; + case PPP_VJC_COMP: + if (is->debug & 0x20) + printk(KERN_DEBUG "isdn_ppp: VJC_COMP\n"); + { + struct sk_buff *skb_old = skb; + int pkt_len; + skb = dev_alloc_skb(skb_old->len + 128); + + if (!skb) { + printk(KERN_WARNING "%s: Memory squeeze, dropping packet.\n", dev->name); + skb = skb_old; + goto drop_packet; + } + skb_put(skb, skb_old->len + 128); + memcpy(skb->data, skb_old->data, skb_old->len); + if (net_dev->local->ppp_slot < 0) { + printk(KERN_ERR "%s: net_dev->local->ppp_slot(%d) out of range\n", + __FUNCTION__, net_dev->local->ppp_slot); + goto drop_packet; + } + pkt_len = slhc_uncompress(ippp_table[net_dev->local->ppp_slot]->slcomp, + skb->data, skb_old->len); + kfree_skb(skb_old); + if (pkt_len < 0) + goto drop_packet; + + skb_trim(skb, pkt_len); + skb->protocol = htons(ETH_P_IP); + } + break; +#endif + case PPP_CCP: + case PPP_CCPFRAG: + isdn_ppp_receive_ccp(net_dev,lp,skb,proto); + /* Dont pop up ResetReq/Ack stuff to the daemon any + longer - the job is done already */ + if(skb->data[0] == CCP_RESETREQ || + skb->data[0] == CCP_RESETACK) + break; + /* fall through */ + default: + isdn_ppp_fill_rq(skb->data, skb->len, proto, lp->ppp_slot); /* push data to pppd device */ + kfree_skb(skb); + return; + } + +#ifdef CONFIG_IPPP_FILTER + /* check if the packet passes the pass and active filters + * the filter instructions are constructed assuming + * a four-byte PPP header on each packet (which is still present) */ + skb_push(skb, 4); + + { + u_int16_t *p = (u_int16_t *) skb->data; + + *p = 0; /* indicate inbound in DLT_LINUX_SLL */ + } + + if (is->pass_filter + && sk_run_filter(skb, is->pass_filter, is->pass_len) == 0) { + if (is->debug & 0x2) + printk(KERN_DEBUG "IPPP: inbound frame filtered.\n"); + kfree_skb(skb); + return; + } + if (!(is->active_filter + && sk_run_filter(skb, is->active_filter, + is->active_len) == 0)) { + if (is->debug & 0x2) + printk(KERN_DEBUG "IPPP: link-active filter: reseting huptimer.\n"); + lp->huptimer = 0; + if (mlp) + mlp->huptimer = 0; + } + skb_pull(skb, 4); +#else /* CONFIG_IPPP_FILTER */ + lp->huptimer = 0; + if (mlp) + mlp->huptimer = 0; +#endif /* CONFIG_IPPP_FILTER */ + skb->dev = dev; + skb->input_dev = dev; + skb->mac.raw = skb->data; + netif_rx(skb); + /* net_dev->local->stats.rx_packets++; done in isdn_net.c */ + return; + + drop_packet: + net_dev->local->stats.rx_dropped++; + kfree_skb(skb); +} + +/* + * isdn_ppp_skb_push .. + * checks whether we have enough space at the beginning of the skb + * and allocs a new SKB if necessary + */ +static unsigned char *isdn_ppp_skb_push(struct sk_buff **skb_p,int len) +{ + struct sk_buff *skb = *skb_p; + + if(skb_headroom(skb) < len) { + struct sk_buff *nskb = skb_realloc_headroom(skb, len); + + if (!nskb) { + printk(KERN_ERR "isdn_ppp_skb_push: can't realloc headroom!\n"); + dev_kfree_skb(skb); + return NULL; + } + printk(KERN_DEBUG "isdn_ppp_skb_push:under %d %d\n",skb_headroom(skb),len); + dev_kfree_skb(skb); + *skb_p = nskb; + return skb_push(nskb, len); + } + return skb_push(skb,len); +} + +/* + * 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!! + */ + +int +isdn_ppp_xmit(struct sk_buff *skb, struct net_device *netdev) +{ + isdn_net_local *lp,*mlp; + isdn_net_dev *nd; + unsigned int proto = PPP_IP; /* 0x21 */ + struct ippp_struct *ipt,*ipts; + int slot, retval = 0; + + mlp = (isdn_net_local *) (netdev->priv); + nd = mlp->netdev; /* get master lp */ + + slot = mlp->ppp_slot; + if (slot < 0 || slot >= ISDN_MAX_CHANNELS) { + printk(KERN_ERR "isdn_ppp_xmit: lp->ppp_slot(%d)\n", + mlp->ppp_slot); + kfree_skb(skb); + goto out; + } + ipts = ippp_table[slot]; + + if (!(ipts->pppcfg & SC_ENABLE_IP)) { /* PPP connected ? */ + if (ipts->debug & 0x1) + printk(KERN_INFO "%s: IP frame delayed.\n", netdev->name); + retval = 1; + goto out; + } + + switch (ntohs(skb->protocol)) { + case ETH_P_IP: + proto = PPP_IP; + break; + case ETH_P_IPX: + proto = PPP_IPX; /* untested */ + break; + default: + printk(KERN_ERR "isdn_ppp: skipped unsupported protocol: %#x.\n", + skb->protocol); + dev_kfree_skb(skb); + goto out; + } + + lp = isdn_net_get_locked_lp(nd); + if (!lp) { + printk(KERN_WARNING "%s: all channels busy - requeuing!\n", netdev->name); + retval = 1; + goto out; + } + /* we have our lp locked from now on */ + + slot = lp->ppp_slot; + if (slot < 0 || slot > ISDN_MAX_CHANNELS) { + printk(KERN_ERR "isdn_ppp_xmit: lp->ppp_slot(%d)\n", + lp->ppp_slot); + kfree_skb(skb); + goto unlock; + } + ipt = ippp_table[slot]; + + /* + * after this line .. requeueing in the device queue is no longer allowed!!! + */ + + /* Pull off the fake header we stuck on earlier to keep + * the fragmentation code happy. + */ + skb_pull(skb,IPPP_MAX_HEADER); + +#ifdef CONFIG_IPPP_FILTER + /* check if we should pass this packet + * the filter instructions are constructed assuming + * a four-byte PPP header on each packet */ + skb_push(skb, 4); + + { + u_int16_t *p = (u_int16_t *) skb->data; + + *p++ = htons(4); /* indicate outbound in DLT_LINUX_SLL */ + *p = htons(proto); + } + + if (ipt->pass_filter + && sk_run_filter(skb, ipt->pass_filter, ipt->pass_len) == 0) { + if (ipt->debug & 0x4) + printk(KERN_DEBUG "IPPP: outbound frame filtered.\n"); + kfree_skb(skb); + goto unlock; + } + if (!(ipt->active_filter + && sk_run_filter(skb, ipt->active_filter, + ipt->active_len) == 0)) { + if (ipt->debug & 0x4) + printk(KERN_DEBUG "IPPP: link-active filter: reseting huptimer.\n"); + lp->huptimer = 0; + } + skb_pull(skb, 4); +#else /* CONFIG_IPPP_FILTER */ + lp->huptimer = 0; +#endif /* CONFIG_IPPP_FILTER */ + + if (ipt->debug & 0x4) + printk(KERN_DEBUG "xmit skb, len %d\n", (int) skb->len); + if (ipts->debug & 0x40) + isdn_ppp_frame_log("xmit0", skb->data, skb->len, 32,ipts->unit,lp->ppp_slot); + +#ifdef CONFIG_ISDN_PPP_VJ + if (proto == PPP_IP && ipts->pppcfg & SC_COMP_TCP) { /* ipts here? probably yes, but check this again */ + struct sk_buff *new_skb; + unsigned short hl; + /* + * we need to reserve enought space in front of + * sk_buff. old call to dev_alloc_skb only reserved + * 16 bytes, now we are looking what the driver want. + */ + hl = dev->drv[lp->isdn_device]->interface->hl_hdrlen + IPPP_MAX_HEADER; + /* + * Note: hl might still be insufficient because the method + * above does not account for a possibible MPPP slave channel + * which had larger HL header space requirements than the + * master. + */ + new_skb = alloc_skb(hl+skb->len, GFP_ATOMIC); + if (new_skb) { + u_char *buf; + int pktlen; + + skb_reserve(new_skb, hl); + new_skb->dev = skb->dev; + 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) { + if (new_skb->data != buf) + printk(KERN_ERR "isdn_ppp: FATAL error after slhc_compress!!\n"); + dev_kfree_skb(skb); + skb = new_skb; + } else { + dev_kfree_skb(new_skb); + } + + 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 + + /* + * normal (single link) or bundle compression + */ + if(ipts->compflags & SC_COMP_ON) { + /* We send compressed only if both down- und upstream + compression is negotiated, that means, CCP is up */ + if(ipts->compflags & SC_DECOMP_ON) { + skb = isdn_ppp_compress(skb,&proto,ipt,ipts,0); + } else { + printk(KERN_DEBUG "isdn_ppp: CCP not yet up - sending as-is\n"); + } + } + + if (ipt->debug & 0x24) + printk(KERN_DEBUG "xmit2 skb, len %d, proto %04x\n", (int) 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++; + if (ipt->mpppcfg & SC_OUT_SHORT_SEQ) { + unsigned char *data = isdn_ppp_skb_push(&skb, 3); + if(!data) + goto unlock; + mp_seqno &= 0xfff; + data[0] = MP_BEGIN_FRAG | MP_END_FRAG | ((mp_seqno >> 8) & 0xf); /* (B)egin & (E)ndbit .. */ + data[1] = mp_seqno & 0xff; + data[2] = proto; /* PID compression */ + } else { + unsigned char *data = isdn_ppp_skb_push(&skb, 5); + if(!data) + goto unlock; + data[0] = MP_BEGIN_FRAG | MP_END_FRAG; /* (B)egin & (E)ndbit .. */ + data[1] = (mp_seqno >> 16) & 0xff; /* sequence number: 24bit */ + data[2] = (mp_seqno >> 8) & 0xff; + data[3] = (mp_seqno >> 0) & 0xff; + data[4] = proto; /* PID compression */ + } + proto = PPP_MP; /* MP Protocol, 0x003d */ + } +#endif + + /* + * 'link in bundle' compression ... + */ + if(ipt->compflags & SC_LINK_COMP_ON) + skb = isdn_ppp_compress(skb,&proto,ipt,ipts,1); + + if( (ipt->pppcfg & SC_COMP_PROT) && (proto <= 0xff) ) { + unsigned char *data = isdn_ppp_skb_push(&skb,1); + if(!data) + goto unlock; + data[0] = proto & 0xff; + } + else { + unsigned char *data = isdn_ppp_skb_push(&skb,2); + if(!data) + goto unlock; + data[0] = (proto >> 8) & 0xff; + data[1] = proto & 0xff; + } + if(!(ipt->pppcfg & SC_COMP_AC)) { + unsigned char *data = isdn_ppp_skb_push(&skb,2); + if(!data) + goto unlock; + data[0] = 0xff; /* All Stations */ + data[1] = 0x03; /* Unnumbered information */ + } + + /* tx-stats are now updated via BSENT-callback */ + + if (ipts->debug & 0x40) { + printk(KERN_DEBUG "skb xmit: len: %d\n", (int) skb->len); + isdn_ppp_frame_log("xmit", skb->data, skb->len, 32,ipt->unit,lp->ppp_slot); + } + + isdn_net_writebuf_skb(lp, skb); + + unlock: + spin_unlock_bh(&lp->xmit_lock); + out: + return retval; +} + +#ifdef CONFIG_IPPP_FILTER +/* + * check if this packet may trigger auto-dial. + */ + +int isdn_ppp_autodial_filter(struct sk_buff *skb, isdn_net_local *lp) +{ + struct ippp_struct *is = ippp_table[lp->ppp_slot]; + u_int16_t proto; + int drop = 0; + + switch (ntohs(skb->protocol)) { + case ETH_P_IP: + proto = PPP_IP; + break; + case ETH_P_IPX: + proto = PPP_IPX; + break; + default: + printk(KERN_ERR "isdn_ppp_autodial_filter: unsupported protocol 0x%x.\n", + skb->protocol); + return 1; + } + + /* the filter instructions are constructed assuming + * a four-byte PPP header on each packet. we have to + * temporarily remove part of the fake header stuck on + * earlier. + */ + skb_pull(skb, IPPP_MAX_HEADER - 4); + + { + u_int16_t *p = (u_int16_t *) skb->data; + + *p++ = htons(4); /* indicate outbound in DLT_LINUX_SLL */ + *p = htons(proto); + } + + drop |= is->pass_filter + && sk_run_filter(skb, is->pass_filter, is->pass_len) == 0; + drop |= is->active_filter + && sk_run_filter(skb, is->active_filter, is->active_len) == 0; + + skb_push(skb, IPPP_MAX_HEADER - 4); + return drop; +} +#endif +#ifdef CONFIG_ISDN_MPP + +/* this is _not_ rfc1990 header, but something we convert both short and long + * headers to for convinience's sake: + * byte 0 is flags as in rfc1990 + * bytes 1...4 is 24-bit seqence number converted to host byte order + */ +#define MP_HEADER_LEN 5 + +#define MP_LONGSEQ_MASK 0x00ffffff +#define MP_SHORTSEQ_MASK 0x00000fff +#define MP_LONGSEQ_MAX MP_LONGSEQ_MASK +#define MP_SHORTSEQ_MAX MP_SHORTSEQ_MASK +#define MP_LONGSEQ_MAXBIT ((MP_LONGSEQ_MASK+1)>>1) +#define MP_SHORTSEQ_MAXBIT ((MP_SHORTSEQ_MASK+1)>>1) + +/* sequence-wrap safe comparisions (for long sequence)*/ +#define MP_LT(a,b) ((a-b)&MP_LONGSEQ_MAXBIT) +#define MP_LE(a,b) !((b-a)&MP_LONGSEQ_MAXBIT) +#define MP_GT(a,b) ((b-a)&MP_LONGSEQ_MAXBIT) +#define MP_GE(a,b) !((a-b)&MP_LONGSEQ_MAXBIT) + +#define MP_SEQ(f) ((*(u32*)(f->data+1))) +#define MP_FLAGS(f) (f->data[0]) + +static int isdn_ppp_mp_bundle_array_init(void) +{ + int i; + int sz = ISDN_MAX_CHANNELS*sizeof(ippp_bundle); + if( (isdn_ppp_bundle_arr = (ippp_bundle*)kmalloc(sz, + GFP_KERNEL)) == NULL ) + return -ENOMEM; + memset(isdn_ppp_bundle_arr, 0, sz); + for( i = 0; i < ISDN_MAX_CHANNELS; i++ ) + spin_lock_init(&isdn_ppp_bundle_arr[i].lock); + return 0; +} + +static ippp_bundle * isdn_ppp_mp_bundle_alloc(void) +{ + int i; + for( i = 0; i < ISDN_MAX_CHANNELS; i++ ) + if (isdn_ppp_bundle_arr[i].ref_ct <= 0) + return (isdn_ppp_bundle_arr + i); + return NULL; +} + +static int isdn_ppp_mp_init( isdn_net_local * lp, ippp_bundle * add_to ) +{ + struct ippp_struct * is; + + if (lp->ppp_slot < 0) { + printk(KERN_ERR "%s: lp->ppp_slot(%d) out of range\n", + __FUNCTION__, lp->ppp_slot); + return(-EINVAL); + } + + is = ippp_table[lp->ppp_slot]; + if (add_to) { + if( lp->netdev->pb ) + lp->netdev->pb->ref_ct--; + lp->netdev->pb = add_to; + } else { /* first link in a bundle */ + is->mp_seqno = 0; + if ((lp->netdev->pb = isdn_ppp_mp_bundle_alloc()) == NULL) + return -ENOMEM; + lp->next = lp->last = lp; /* nobody else in a queue */ + lp->netdev->pb->frags = NULL; + lp->netdev->pb->frames = 0; + lp->netdev->pb->seq = UINT_MAX; + } + lp->netdev->pb->ref_ct++; + + is->last_link_seqno = 0; + return 0; +} + +static u32 isdn_ppp_mp_get_seq( int short_seq, + struct sk_buff * skb, u32 last_seq ); +static struct sk_buff * isdn_ppp_mp_discard( ippp_bundle * mp, + struct sk_buff * from, struct sk_buff * to ); +static void isdn_ppp_mp_reassembly( isdn_net_dev * net_dev, isdn_net_local * lp, + struct sk_buff * from, struct sk_buff * to ); +static void isdn_ppp_mp_free_skb( ippp_bundle * mp, struct sk_buff * skb ); +static void isdn_ppp_mp_print_recv_pkt( int slot, struct sk_buff * skb ); + +static void isdn_ppp_mp_receive(isdn_net_dev * net_dev, isdn_net_local * lp, + struct sk_buff *skb) +{ + struct ippp_struct *is; + isdn_net_local * lpq; + ippp_bundle * mp; + isdn_mppp_stats * stats; + struct sk_buff * newfrag, * frag, * start, *nextf; + u32 newseq, minseq, thisseq; + unsigned long flags; + int slot; + + spin_lock_irqsave(&net_dev->pb->lock, flags); + mp = net_dev->pb; + stats = &mp->stats; + slot = lp->ppp_slot; + if (slot < 0 || slot > ISDN_MAX_CHANNELS) { + printk(KERN_ERR "%s: lp->ppp_slot(%d)\n", + __FUNCTION__, lp->ppp_slot); + stats->frame_drops++; + dev_kfree_skb(skb); + spin_unlock_irqrestore(&mp->lock, flags); + return; + } + is = ippp_table[slot]; + if( ++mp->frames > stats->max_queue_len ) + stats->max_queue_len = mp->frames; + + if (is->debug & 0x8) + isdn_ppp_mp_print_recv_pkt(lp->ppp_slot, skb); + + newseq = isdn_ppp_mp_get_seq(is->mpppcfg & SC_IN_SHORT_SEQ, + skb, is->last_link_seqno); + + + /* if this packet seq # is less than last already processed one, + * toss it right away, but check for sequence start case first + */ + if( mp->seq > MP_LONGSEQ_MAX && (newseq & MP_LONGSEQ_MAXBIT) ) { + mp->seq = newseq; /* the first packet: required for + * rfc1990 non-compliant clients -- + * prevents constant packet toss */ + } else if( MP_LT(newseq, mp->seq) ) { + stats->frame_drops++; + isdn_ppp_mp_free_skb(mp, skb); + spin_unlock_irqrestore(&mp->lock, flags); + return; + } + + /* find the minimum received sequence number over all links */ + is->last_link_seqno = minseq = newseq; + for (lpq = net_dev->queue;;) { + slot = lpq->ppp_slot; + if (slot < 0 || slot > ISDN_MAX_CHANNELS) { + printk(KERN_ERR "%s: lpq->ppp_slot(%d)\n", + __FUNCTION__, lpq->ppp_slot); + } else { + u32 lls = ippp_table[slot]->last_link_seqno; + if (MP_LT(lls, minseq)) + minseq = lls; + } + if ((lpq = lpq->next) == net_dev->queue) + break; + } + if (MP_LT(minseq, mp->seq)) + minseq = mp->seq; /* can't go beyond already processed + * packets */ + newfrag = skb; + + /* if this new fragment is before the first one, then enqueue it now. */ + if ((frag = mp->frags) == NULL || MP_LT(newseq, MP_SEQ(frag))) { + newfrag->next = frag; + mp->frags = frag = newfrag; + newfrag = NULL; + } + + start = MP_FLAGS(frag) & MP_BEGIN_FRAG && + MP_SEQ(frag) == mp->seq ? frag : NULL; + + /* + * main fragment traversing loop + * + * try to accomplish several tasks: + * - insert new fragment into the proper sequence slot (once that's done + * newfrag will be set to NULL) + * - reassemble any complete fragment sequence (non-null 'start' + * indicates there is a continguous sequence present) + * - discard any incomplete sequences that are below minseq -- due + * to the fact that sender always increment sequence number, if there + * is an incomplete sequence below minseq, no new fragments would + * come to complete such sequence and it should be discarded + * + * loop completes when we accomplished the following tasks: + * - new fragment is inserted in the proper sequence ('newfrag' is + * set to NULL) + * - we hit a gap in the sequence, so no reassembly/processing is + * possible ('start' would be set to NULL) + * + * algorightm for this code is derived from code in the book + * 'PPP Design And Debugging' by James Carlson (Addison-Wesley) + */ + while (start != NULL || newfrag != NULL) { + + thisseq = MP_SEQ(frag); + nextf = frag->next; + + /* drop any duplicate fragments */ + if (newfrag != NULL && thisseq == newseq) { + isdn_ppp_mp_free_skb(mp, newfrag); + newfrag = NULL; + } + + /* insert new fragment before next element if possible. */ + if (newfrag != NULL && (nextf == NULL || + MP_LT(newseq, MP_SEQ(nextf)))) { + newfrag->next = nextf; + frag->next = nextf = newfrag; + newfrag = NULL; + } + + if (start != NULL) { + /* check for misplaced start */ + if (start != frag && (MP_FLAGS(frag) & MP_BEGIN_FRAG)) { + printk(KERN_WARNING"isdn_mppp(seq %d): new " + "BEGIN flag with no prior END", thisseq); + stats->seqerrs++; + stats->frame_drops++; + start = isdn_ppp_mp_discard(mp, start,frag); + nextf = frag->next; + } + } else if (MP_LE(thisseq, minseq)) { + if (MP_FLAGS(frag) & MP_BEGIN_FRAG) + start = frag; + else { + if (MP_FLAGS(frag) & MP_END_FRAG) + stats->frame_drops++; + if( mp->frags == frag ) + mp->frags = nextf; + isdn_ppp_mp_free_skb(mp, frag); + frag = nextf; + continue; + } + } + + /* if start is non-null and we have end fragment, then + * we have full reassembly sequence -- reassemble + * and process packet now + */ + if (start != NULL && (MP_FLAGS(frag) & MP_END_FRAG)) { + minseq = mp->seq = (thisseq+1) & MP_LONGSEQ_MASK; + /* Reassemble the packet then dispatch it */ + isdn_ppp_mp_reassembly(net_dev, lp, start, nextf); + + start = NULL; + frag = NULL; + + mp->frags = nextf; + } + + /* check if need to update start pointer: if we just + * reassembled the packet and sequence is contiguous + * then next fragment should be the start of new reassembly + * if sequence is contiguous, but we haven't reassembled yet, + * keep going. + * if sequence is not contiguous, either clear everyting + * below low watermark and set start to the next frag or + * clear start ptr. + */ + if (nextf != NULL && + ((thisseq+1) & MP_LONGSEQ_MASK) == MP_SEQ(nextf)) { + /* if we just reassembled and the next one is here, + * then start another reassembly. */ + + if (frag == NULL) { + if (MP_FLAGS(nextf) & MP_BEGIN_FRAG) + start = nextf; + else + { + printk(KERN_WARNING"isdn_mppp(seq %d):" + " END flag with no following " + "BEGIN", thisseq); + stats->seqerrs++; + } + } + + } else { + if ( nextf != NULL && frag != NULL && + MP_LT(thisseq, minseq)) { + /* we've got a break in the sequence + * and we not at the end yet + * and we did not just reassembled + *(if we did, there wouldn't be anything before) + * and we below the low watermark + * discard all the frames below low watermark + * and start over */ + stats->frame_drops++; + mp->frags = isdn_ppp_mp_discard(mp,start,nextf); + } + /* break in the sequence, no reassembly */ + start = NULL; + } + + frag = nextf; + } /* while -- main loop */ + + if (mp->frags == NULL) + mp->frags = frag; + + /* rather straighforward way to deal with (not very) possible + * queue overflow */ + if (mp->frames > MP_MAX_QUEUE_LEN) { + stats->overflows++; + while (mp->frames > MP_MAX_QUEUE_LEN) { + frag = mp->frags->next; + isdn_ppp_mp_free_skb(mp, mp->frags); + mp->frags = frag; + } + } + spin_unlock_irqrestore(&mp->lock, flags); +} + +static void isdn_ppp_mp_cleanup( isdn_net_local * lp ) +{ + struct sk_buff * frag = lp->netdev->pb->frags; + struct sk_buff * nextfrag; + while( frag ) { + nextfrag = frag->next; + isdn_ppp_mp_free_skb(lp->netdev->pb, frag); + frag = nextfrag; + } + lp->netdev->pb->frags = NULL; +} + +static u32 isdn_ppp_mp_get_seq( int short_seq, + struct sk_buff * skb, u32 last_seq ) +{ + u32 seq; + int flags = skb->data[0] & (MP_BEGIN_FRAG | MP_END_FRAG); + + if( !short_seq ) + { + seq = ntohl(*(u32*)skb->data) & MP_LONGSEQ_MASK; + skb_push(skb,1); + } + else + { + /* convert 12-bit short seq number to 24-bit long one + */ + seq = ntohs(*(u16*)skb->data) & MP_SHORTSEQ_MASK; + + /* check for seqence wrap */ + if( !(seq & MP_SHORTSEQ_MAXBIT) && + (last_seq & MP_SHORTSEQ_MAXBIT) && + (unsigned long)last_seq <= MP_LONGSEQ_MAX ) + seq |= (last_seq + MP_SHORTSEQ_MAX+1) & + (~MP_SHORTSEQ_MASK & MP_LONGSEQ_MASK); + else + seq |= last_seq & (~MP_SHORTSEQ_MASK & MP_LONGSEQ_MASK); + + skb_push(skb, 3); /* put converted seqence back in skb */ + } + *(u32*)(skb->data+1) = seq; /* put seqence back in _host_ byte + * order */ + skb->data[0] = flags; /* restore flags */ + return seq; +} + +struct sk_buff * isdn_ppp_mp_discard( ippp_bundle * mp, + struct sk_buff * from, struct sk_buff * to ) +{ + if( from ) + while (from != to) { + struct sk_buff * next = from->next; + isdn_ppp_mp_free_skb(mp, from); + from = next; + } + return from; +} + +void isdn_ppp_mp_reassembly( isdn_net_dev * net_dev, isdn_net_local * lp, + struct sk_buff * from, struct sk_buff * to ) +{ + ippp_bundle * mp = net_dev->pb; + int proto; + struct sk_buff * skb; + unsigned int tot_len; + + if (lp->ppp_slot < 0 || lp->ppp_slot >= ISDN_MAX_CHANNELS) { + printk(KERN_ERR "%s: lp->ppp_slot(%d) out of range\n", + __FUNCTION__, lp->ppp_slot); + return; + } + if( MP_FLAGS(from) == (MP_BEGIN_FRAG | MP_END_FRAG) ) { + if( ippp_table[lp->ppp_slot]->debug & 0x40 ) + printk(KERN_DEBUG "isdn_mppp: reassembly: frame %d, " + "len %d\n", MP_SEQ(from), from->len ); + skb = from; + skb_pull(skb, MP_HEADER_LEN); + mp->frames--; + } else { + struct sk_buff * frag; + int n; + + for(tot_len=n=0, frag=from; frag != to; frag=frag->next, n++) + tot_len += frag->len - MP_HEADER_LEN; + + if( ippp_table[lp->ppp_slot]->debug & 0x40 ) + printk(KERN_DEBUG"isdn_mppp: reassembling frames %d " + "to %d, len %d\n", MP_SEQ(from), + (MP_SEQ(from)+n-1) & MP_LONGSEQ_MASK, tot_len ); + if( (skb = dev_alloc_skb(tot_len)) == NULL ) { + printk(KERN_ERR "isdn_mppp: cannot allocate sk buff " + "of size %d\n", tot_len); + isdn_ppp_mp_discard(mp, from, to); + return; + } + + while( from != to ) { + unsigned int len = from->len - MP_HEADER_LEN; + + memcpy(skb_put(skb,len), from->data+MP_HEADER_LEN, len); + frag = from->next; + isdn_ppp_mp_free_skb(mp, from); + from = frag; + } + } + proto = isdn_ppp_strip_proto(skb); + isdn_ppp_push_higher(net_dev, lp, skb, proto); +} + +static void isdn_ppp_mp_free_skb(ippp_bundle * mp, struct sk_buff * skb) +{ + dev_kfree_skb(skb); + mp->frames--; +} + +static void isdn_ppp_mp_print_recv_pkt( int slot, struct sk_buff * skb ) +{ + printk(KERN_DEBUG "mp_recv: %d/%d -> %02x %02x %02x %02x %02x %02x\n", + slot, (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]); +} + +static int +isdn_ppp_bundle(struct ippp_struct *is, int unit) +{ + char ifn[IFNAMSIZ + 1]; + isdn_net_dev *p; + isdn_net_local *lp, *nlp; + int rc; + unsigned long flags; + + sprintf(ifn, "ippp%d", unit); + p = isdn_net_findif(ifn); + if (!p) { + printk(KERN_ERR "ippp_bundle: cannot find %s\n", ifn); + return -EINVAL; + } + + spin_lock_irqsave(&p->pb->lock, flags); + + nlp = is->lp; + lp = p->queue; + if( nlp->ppp_slot < 0 || nlp->ppp_slot >= ISDN_MAX_CHANNELS || + lp->ppp_slot < 0 || lp->ppp_slot >= ISDN_MAX_CHANNELS ) { + printk(KERN_ERR "ippp_bundle: binding to invalid slot %d\n", + nlp->ppp_slot < 0 || nlp->ppp_slot >= ISDN_MAX_CHANNELS ? + nlp->ppp_slot : lp->ppp_slot ); + rc = -EINVAL; + goto out; + } + + isdn_net_add_to_bundle(p, 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); + rc = isdn_ppp_mp_init(nlp, p->pb); +out: + spin_unlock_irqrestore(&p->pb->lock, flags); + return rc; +} + +#endif /* CONFIG_ISDN_MPP */ + +/* + * network device ioctl handlers + */ + +static int +isdn_ppp_dev_ioctl_stats(int slot, struct ifreq *ifr, struct net_device *dev) +{ + struct ppp_stats __user *res = ifr->ifr_data; + struct ppp_stats t; + isdn_net_local *lp = (isdn_net_local *) dev->priv; + + if (!access_ok(VERIFY_WRITE, res, sizeof(struct ppp_stats))) + return -EFAULT; + + /* 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_ibytes = lp->stats.rx_bytes; + t.p.ppp_ierrors = lp->stats.rx_errors; + t.p.ppp_opackets = lp->stats.tx_packets; + t.p.ppp_obytes = lp->stats.tx_bytes; + 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 + } + if (copy_to_user(res, &t, sizeof(struct ppp_stats))) + return -EFAULT; + return 0; +} + +int +isdn_ppp_dev_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) +{ + int error=0; + int len; + isdn_net_local *lp = (isdn_net_local *) dev->priv; + + + if (lp->p_encap != ISDN_NET_ENCAP_SYNCPPP) + return -EINVAL; + + switch (cmd) { +#define PPP_VERSION "2.3.7" + case SIOCGPPPVER: + len = strlen(PPP_VERSION) + 1; + if (copy_to_user(ifr->ifr_data, 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 net_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_dial_req((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 net_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->slave) { /* find last connected link in chain */ + isdn_net_local *nlp = (isdn_net_local *) mlp->slave->priv; + + if (!(nlp->flags & ISDN_NET_CONNECTED)) + break; + } else if (mlp->flags & ISDN_NET_CONNECTED) + break; + + sdev = mlp->slave; + } + if (!sdev) + return 2; + + isdn_net_hangup(sdev); + return 0; +#else + return -1; +#endif +} + +/* + * PPP compression stuff + */ + + +/* Push an empty CCP Data Frame up to the daemon to wake it up and let it + generate a CCP Reset-Request or tear down CCP altogether */ + +static void isdn_ppp_ccp_kickup(struct ippp_struct *is) +{ + isdn_ppp_fill_rq(NULL, 0, PPP_COMP, is->lp->ppp_slot); +} + +/* In-kernel handling of CCP Reset-Request and Reset-Ack is necessary, + but absolutely nontrivial. The most abstruse problem we are facing is + that the generation, reception and all the handling of timeouts and + resends including proper request id management should be entirely left + to the (de)compressor, but indeed is not covered by the current API to + the (de)compressor. The API is a prototype version from PPP where only + some (de)compressors have yet been implemented and all of them are + rather simple in their reset handling. Especially, their is only one + outstanding ResetAck at a time with all of them and ResetReq/-Acks do + not have parameters. For this very special case it was sufficient to + just return an error code from the decompressor and have a single + reset() entry to communicate all the necessary information between + the framework and the (de)compressor. Bad enough, LZS is different + (and any other compressor may be different, too). It has multiple + histories (eventually) and needs to Reset each of them independently + and thus uses multiple outstanding Acks and history numbers as an + additional parameter to Reqs/Acks. + All that makes it harder to port the reset state engine into the + kernel because it is not just the same simple one as in (i)pppd but + it must be able to pass additional parameters and have multiple out- + standing Acks. We are trying to achieve the impossible by handling + reset transactions independent by their id. The id MUST change when + the data portion changes, thus any (de)compressor who uses more than + one resettable state must provide and recognize individual ids for + each individual reset transaction. The framework itself does _only_ + differentiate them by id, because it has no other semantics like the + (de)compressor might. + This looks like a major redesign of the interface would be nice, + but I don't have an idea how to do it better. */ + +/* Send a CCP Reset-Request or Reset-Ack directly from the kernel. This is + getting that lengthy because there is no simple "send-this-frame-out" + function above but every wrapper does a bit different. Hope I guess + correct in this hack... */ + +static void isdn_ppp_ccp_xmit_reset(struct ippp_struct *is, int proto, + unsigned char code, unsigned char id, + unsigned char *data, int len) +{ + struct sk_buff *skb; + unsigned char *p; + int hl; + int cnt = 0; + isdn_net_local *lp = is->lp; + + /* Alloc large enough skb */ + hl = dev->drv[lp->isdn_device]->interface->hl_hdrlen; + skb = alloc_skb(len + hl + 16,GFP_ATOMIC); + if(!skb) { + printk(KERN_WARNING + "ippp: CCP cannot send reset - out of memory\n"); + return; + } + skb_reserve(skb, hl); + + /* We may need to stuff an address and control field first */ + if(!(is->pppcfg & SC_COMP_AC)) { + p = skb_put(skb, 2); + *p++ = 0xff; + *p++ = 0x03; + } + + /* Stuff proto, code, id and length */ + p = skb_put(skb, 6); + *p++ = (proto >> 8); + *p++ = (proto & 0xff); + *p++ = code; + *p++ = id; + cnt = 4 + len; + *p++ = (cnt >> 8); + *p++ = (cnt & 0xff); + + /* Now stuff remaining bytes */ + if(len) { + p = skb_put(skb, len); + memcpy(p, data, len); + } + + /* skb is now ready for xmit */ + printk(KERN_DEBUG "Sending CCP Frame:\n"); + isdn_ppp_frame_log("ccp-xmit", skb->data, skb->len, 32, is->unit,lp->ppp_slot); + + isdn_net_write_super(lp, skb); +} + +/* Allocate the reset state vector */ +static struct ippp_ccp_reset *isdn_ppp_ccp_reset_alloc(struct ippp_struct *is) +{ + struct ippp_ccp_reset *r; + r = kmalloc(sizeof(struct ippp_ccp_reset), GFP_KERNEL); + if(!r) { + printk(KERN_ERR "ippp_ccp: failed to allocate reset data" + " structure - no mem\n"); + return NULL; + } + memset(r, 0, sizeof(struct ippp_ccp_reset)); + printk(KERN_DEBUG "ippp_ccp: allocated reset data structure %p\n", r); + is->reset = r; + return r; +} + +/* Destroy the reset state vector. Kill all pending timers first. */ +static void isdn_ppp_ccp_reset_free(struct ippp_struct *is) +{ + unsigned int id; + + printk(KERN_DEBUG "ippp_ccp: freeing reset data structure %p\n", + is->reset); + for(id = 0; id < 256; id++) { + if(is->reset->rs[id]) { + isdn_ppp_ccp_reset_free_state(is, (unsigned char)id); + } + } + kfree(is->reset); + is->reset = NULL; +} + +/* Free a given state and clear everything up for later reallocation */ +static void isdn_ppp_ccp_reset_free_state(struct ippp_struct *is, + unsigned char id) +{ + struct ippp_ccp_reset_state *rs; + + if(is->reset->rs[id]) { + printk(KERN_DEBUG "ippp_ccp: freeing state for id %d\n", id); + rs = is->reset->rs[id]; + /* Make sure the kernel will not call back later */ + if(rs->ta) + del_timer(&rs->timer); + is->reset->rs[id] = NULL; + kfree(rs); + } else { + printk(KERN_WARNING "ippp_ccp: id %d is not allocated\n", id); + } +} + +/* The timer callback function which is called when a ResetReq has timed out, + aka has never been answered by a ResetAck */ +static void isdn_ppp_ccp_timer_callback(unsigned long closure) +{ + struct ippp_ccp_reset_state *rs = + (struct ippp_ccp_reset_state *)closure; + + if(!rs) { + printk(KERN_ERR "ippp_ccp: timer cb with zero closure.\n"); + return; + } + if(rs->ta && rs->state == CCPResetSentReq) { + /* We are correct here */ + if(!rs->expra) { + /* Hmm, there is no Ack really expected. We can clean + up the state now, it will be reallocated if the + decompressor insists on another reset */ + rs->ta = 0; + isdn_ppp_ccp_reset_free_state(rs->is, rs->id); + return; + } + printk(KERN_DEBUG "ippp_ccp: CCP Reset timed out for id %d\n", + rs->id); + /* Push it again */ + isdn_ppp_ccp_xmit_reset(rs->is, PPP_CCP, CCP_RESETREQ, rs->id, + rs->data, rs->dlen); + /* Restart timer */ + rs->timer.expires = jiffies + HZ*5; + add_timer(&rs->timer); + } else { + printk(KERN_WARNING "ippp_ccp: timer cb in wrong state %d\n", + rs->state); + } +} + +/* Allocate a new reset transaction state */ +static struct ippp_ccp_reset_state *isdn_ppp_ccp_reset_alloc_state(struct ippp_struct *is, + unsigned char id) +{ + struct ippp_ccp_reset_state *rs; + if(is->reset->rs[id]) { + printk(KERN_WARNING "ippp_ccp: old state exists for id %d\n", + id); + return NULL; + } else { + rs = kmalloc(sizeof(struct ippp_ccp_reset_state), GFP_KERNEL); + if(!rs) + return NULL; + memset(rs, 0, sizeof(struct ippp_ccp_reset_state)); + rs->state = CCPResetIdle; + rs->is = is; + rs->id = id; + rs->timer.data = (unsigned long)rs; + rs->timer.function = isdn_ppp_ccp_timer_callback; + is->reset->rs[id] = rs; + } + return rs; +} + + +/* A decompressor wants a reset with a set of parameters - do what is + necessary to fulfill it */ +static void isdn_ppp_ccp_reset_trans(struct ippp_struct *is, + struct isdn_ppp_resetparams *rp) +{ + struct ippp_ccp_reset_state *rs; + + if(rp->valid) { + /* The decompressor defines parameters by itself */ + if(rp->rsend) { + /* And he wants us to send a request */ + if(!(rp->idval)) { + printk(KERN_ERR "ippp_ccp: decompressor must" + " specify reset id\n"); + return; + } + if(is->reset->rs[rp->id]) { + /* There is already a transaction in existence + for this id. May be still waiting for a + Ack or may be wrong. */ + rs = is->reset->rs[rp->id]; + if(rs->state == CCPResetSentReq && rs->ta) { + printk(KERN_DEBUG "ippp_ccp: reset" + " trans still in progress" + " for id %d\n", rp->id); + } else { + printk(KERN_WARNING "ippp_ccp: reset" + " trans in wrong state %d for" + " id %d\n", rs->state, rp->id); + } + } else { + /* Ok, this is a new transaction */ + printk(KERN_DEBUG "ippp_ccp: new trans for id" + " %d to be started\n", rp->id); + rs = isdn_ppp_ccp_reset_alloc_state(is, rp->id); + if(!rs) { + printk(KERN_ERR "ippp_ccp: out of mem" + " allocing ccp trans\n"); + return; + } + rs->state = CCPResetSentReq; + rs->expra = rp->expra; + if(rp->dtval) { + rs->dlen = rp->dlen; + memcpy(rs->data, rp->data, rp->dlen); + } + /* HACK TODO - add link comp here */ + isdn_ppp_ccp_xmit_reset(is, PPP_CCP, + CCP_RESETREQ, rs->id, + rs->data, rs->dlen); + /* Start the timer */ + rs->timer.expires = jiffies + 5*HZ; + add_timer(&rs->timer); + rs->ta = 1; + } + } else { + printk(KERN_DEBUG "ippp_ccp: no reset sent\n"); + } + } else { + /* The reset params are invalid. The decompressor does not + care about them, so we just send the minimal requests + and increase ids only when an Ack is received for a + given id */ + if(is->reset->rs[is->reset->lastid]) { + /* There is already a transaction in existence + for this id. May be still waiting for a + Ack or may be wrong. */ + rs = is->reset->rs[is->reset->lastid]; + if(rs->state == CCPResetSentReq && rs->ta) { + printk(KERN_DEBUG "ippp_ccp: reset" + " trans still in progress" + " for id %d\n", rp->id); + } else { + printk(KERN_WARNING "ippp_ccp: reset" + " trans in wrong state %d for" + " id %d\n", rs->state, rp->id); + } + } else { + printk(KERN_DEBUG "ippp_ccp: new trans for id" + " %d to be started\n", is->reset->lastid); + rs = isdn_ppp_ccp_reset_alloc_state(is, + is->reset->lastid); + if(!rs) { + printk(KERN_ERR "ippp_ccp: out of mem" + " allocing ccp trans\n"); + return; + } + rs->state = CCPResetSentReq; + /* We always expect an Ack if the decompressor doesn't + know better */ + rs->expra = 1; + rs->dlen = 0; + /* HACK TODO - add link comp here */ + isdn_ppp_ccp_xmit_reset(is, PPP_CCP, CCP_RESETREQ, + rs->id, NULL, 0); + /* Start the timer */ + rs->timer.expires = jiffies + 5*HZ; + add_timer(&rs->timer); + rs->ta = 1; + } + } +} + +/* An Ack was received for this id. This means we stop the timer and clean + up the state prior to calling the decompressors reset routine. */ +static void isdn_ppp_ccp_reset_ack_rcvd(struct ippp_struct *is, + unsigned char id) +{ + struct ippp_ccp_reset_state *rs = is->reset->rs[id]; + + if(rs) { + if(rs->ta && rs->state == CCPResetSentReq) { + /* Great, we are correct */ + if(!rs->expra) + printk(KERN_DEBUG "ippp_ccp: ResetAck received" + " for id %d but not expected\n", id); + } else { + printk(KERN_INFO "ippp_ccp: ResetAck received out of" + "sync for id %d\n", id); + } + if(rs->ta) { + rs->ta = 0; + del_timer(&rs->timer); + } + isdn_ppp_ccp_reset_free_state(is, id); + } else { + printk(KERN_INFO "ippp_ccp: ResetAck received for unknown id" + " %d\n", id); + } + /* Make sure the simple reset stuff uses a new id next time */ + is->reset->lastid++; +} + +/* + * decompress packet + * + * if master = 0, we're trying to uncompress an per-link compressed packet, + * as opposed to an compressed reconstructed-from-MPPP packet. + * proto is updated to protocol field of uncompressed packet. + * + * retval: decompressed packet, + * same packet if uncompressed, + * NULL if decompression error + */ + +static struct sk_buff *isdn_ppp_decompress(struct sk_buff *skb,struct ippp_struct *is,struct ippp_struct *master, + int *proto) +{ + void *stat = NULL; + struct isdn_ppp_compressor *ipc = NULL; + struct sk_buff *skb_out; + int len; + struct ippp_struct *ri; + struct isdn_ppp_resetparams rsparm; + unsigned char rsdata[IPPP_RESET_MAXDATABYTES]; + + if(!master) { + // per-link decompression + stat = is->link_decomp_stat; + ipc = is->link_decompressor; + ri = is; + } else { + stat = master->decomp_stat; + ipc = master->decompressor; + ri = master; + } + + if (!ipc) { + // no decompressor -> we can't decompress. + printk(KERN_DEBUG "ippp: no decompressor defined!\n"); + return skb; + } + if (!stat) // if we have a compressor, stat has been set as well + BUG(); + + if((master && *proto == PPP_COMP) || (!master && *proto == PPP_COMPFRAG) ) { + // compressed packets are compressed by their protocol type + + // Set up reset params for the decompressor + memset(&rsparm, 0, sizeof(rsparm)); + rsparm.data = rsdata; + rsparm.maxdlen = IPPP_RESET_MAXDATABYTES; + + skb_out = dev_alloc_skb(is->mru + PPP_HDRLEN); + len = ipc->decompress(stat, skb, skb_out, &rsparm); + kfree_skb(skb); + if (len <= 0) { + switch(len) { + case DECOMP_ERROR: + printk(KERN_INFO "ippp: decomp wants reset %s params\n", + rsparm.valid ? "with" : "without"); + + isdn_ppp_ccp_reset_trans(ri, &rsparm); + break; + case DECOMP_FATALERROR: + ri->pppcfg |= SC_DC_FERROR; + /* Kick ipppd to recognize the error */ + isdn_ppp_ccp_kickup(ri); + break; + } + kfree_skb(skb_out); + return NULL; + } + *proto = isdn_ppp_strip_proto(skb_out); + if (*proto < 0) { + kfree_skb(skb_out); + return NULL; + } + return skb_out; + } else { + // uncompressed packets are fed through the decompressor to + // update the decompressor state + ipc->incomp(stat, skb, *proto); + return skb; + } +} + +/* + * compress a frame + * type=0: normal/bundle compression + * =1: link compression + * returns original skb if we haven't compressed the frame + * and a new skb pointer if we've done it + */ +static struct sk_buff *isdn_ppp_compress(struct sk_buff *skb_in,int *proto, + struct ippp_struct *is,struct ippp_struct *master,int type) +{ + int ret; + int new_proto; + struct isdn_ppp_compressor *compressor; + void *stat; + struct sk_buff *skb_out; + + /* we do not compress control protocols */ + if(*proto < 0 || *proto > 0x3fff) { + return skb_in; + } + + if(type) { /* type=1 => Link compression */ + return skb_in; + } + else { + if(!master) { + compressor = is->compressor; + stat = is->comp_stat; + } + else { + compressor = master->compressor; + stat = master->comp_stat; + } + new_proto = PPP_COMP; + } + + if(!compressor) { + printk(KERN_ERR "isdn_ppp: No compressor set!\n"); + return skb_in; + } + if(!stat) { + printk(KERN_ERR "isdn_ppp: Compressor not initialized?\n"); + return skb_in; + } + + /* Allow for at least 150 % expansion (for now) */ + skb_out = alloc_skb(skb_in->len + skb_in->len/2 + 32 + + skb_headroom(skb_in), GFP_ATOMIC); + if(!skb_out) + return skb_in; + skb_reserve(skb_out, skb_headroom(skb_in)); + + ret = (compressor->compress)(stat,skb_in,skb_out,*proto); + if(!ret) { + dev_kfree_skb(skb_out); + return skb_in; + } + + dev_kfree_skb(skb_in); + *proto = new_proto; + return skb_out; +} + +/* + * we received a CCP frame .. + * not a clean solution, but we MUST handle a few cases in the kernel + */ +static void isdn_ppp_receive_ccp(isdn_net_dev *net_dev, isdn_net_local *lp, + struct sk_buff *skb,int proto) +{ + struct ippp_struct *is; + struct ippp_struct *mis; + int len; + struct isdn_ppp_resetparams rsparm; + unsigned char rsdata[IPPP_RESET_MAXDATABYTES]; + + printk(KERN_DEBUG "Received CCP frame from peer slot(%d)\n", + lp->ppp_slot); + if (lp->ppp_slot < 0 || lp->ppp_slot > ISDN_MAX_CHANNELS) { + printk(KERN_ERR "%s: lp->ppp_slot(%d) out of range\n", + __FUNCTION__, lp->ppp_slot); + return; + } + is = ippp_table[lp->ppp_slot]; + isdn_ppp_frame_log("ccp-rcv", skb->data, skb->len, 32, is->unit,lp->ppp_slot); + + if(lp->master) { + int slot = ((isdn_net_local *) (lp->master->priv))->ppp_slot; + if (slot < 0 || slot > ISDN_MAX_CHANNELS) { + printk(KERN_ERR "%s: slot(%d) out of range\n", + __FUNCTION__, slot); + return; + } + mis = ippp_table[slot]; + } else + mis = is; + + switch(skb->data[0]) { + case CCP_CONFREQ: + if(is->debug & 0x10) + printk(KERN_DEBUG "Disable compression here!\n"); + if(proto == PPP_CCP) + mis->compflags &= ~SC_COMP_ON; + else + is->compflags &= ~SC_LINK_COMP_ON; + break; + case CCP_TERMREQ: + case CCP_TERMACK: + if(is->debug & 0x10) + printk(KERN_DEBUG "Disable (de)compression here!\n"); + if(proto == PPP_CCP) + mis->compflags &= ~(SC_DECOMP_ON|SC_COMP_ON); + else + is->compflags &= ~(SC_LINK_DECOMP_ON|SC_LINK_COMP_ON); + break; + case CCP_CONFACK: + /* if we RECEIVE an ackowledge we enable the decompressor */ + if(is->debug & 0x10) + printk(KERN_DEBUG "Enable decompression here!\n"); + if(proto == PPP_CCP) { + if (!mis->decompressor) + break; + mis->compflags |= SC_DECOMP_ON; + } else { + if (!is->decompressor) + break; + is->compflags |= SC_LINK_DECOMP_ON; + } + break; + + case CCP_RESETACK: + printk(KERN_DEBUG "Received ResetAck from peer\n"); + len = (skb->data[2] << 8) | skb->data[3]; + len -= 4; + + if(proto == PPP_CCP) { + /* If a reset Ack was outstanding for this id, then + clean up the state engine */ + isdn_ppp_ccp_reset_ack_rcvd(mis, skb->data[1]); + if(mis->decompressor && mis->decomp_stat) + mis->decompressor-> + reset(mis->decomp_stat, + skb->data[0], + skb->data[1], + len ? &skb->data[4] : NULL, + len, NULL); + /* TODO: This is not easy to decide here */ + mis->compflags &= ~SC_DECOMP_DISCARD; + } + else { + isdn_ppp_ccp_reset_ack_rcvd(is, skb->data[1]); + if(is->link_decompressor && is->link_decomp_stat) + is->link_decompressor-> + reset(is->link_decomp_stat, + skb->data[0], + skb->data[1], + len ? &skb->data[4] : NULL, + len, NULL); + /* TODO: neither here */ + is->compflags &= ~SC_LINK_DECOMP_DISCARD; + } + break; + + case CCP_RESETREQ: + printk(KERN_DEBUG "Received ResetReq from peer\n"); + /* Receiving a ResetReq means we must reset our compressor */ + /* Set up reset params for the reset entry */ + memset(&rsparm, 0, sizeof(rsparm)); + rsparm.data = rsdata; + rsparm.maxdlen = IPPP_RESET_MAXDATABYTES; + /* Isolate data length */ + len = (skb->data[2] << 8) | skb->data[3]; + len -= 4; + if(proto == PPP_CCP) { + if(mis->compressor && mis->comp_stat) + mis->compressor-> + reset(mis->comp_stat, + skb->data[0], + skb->data[1], + len ? &skb->data[4] : NULL, + len, &rsparm); + } + else { + if(is->link_compressor && is->link_comp_stat) + is->link_compressor-> + reset(is->link_comp_stat, + skb->data[0], + skb->data[1], + len ? &skb->data[4] : NULL, + len, &rsparm); + } + /* Ack the Req as specified by rsparm */ + if(rsparm.valid) { + /* Compressor reset handler decided how to answer */ + if(rsparm.rsend) { + /* We should send a Frame */ + isdn_ppp_ccp_xmit_reset(is, proto, CCP_RESETACK, + rsparm.idval ? rsparm.id + : skb->data[1], + rsparm.dtval ? + rsparm.data : NULL, + rsparm.dtval ? + rsparm.dlen : 0); + } else { + printk(KERN_DEBUG "ResetAck suppressed\n"); + } + } else { + /* We answer with a straight reflected Ack */ + isdn_ppp_ccp_xmit_reset(is, proto, CCP_RESETACK, + skb->data[1], + len ? &skb->data[4] : NULL, + len); + } + break; + } +} + + +/* + * Daemon sends a CCP frame ... + */ + +/* TODO: Clean this up with new Reset semantics */ + +/* I believe the CCP handling as-is is done wrong. Compressed frames + * should only be sent/received after CCP reaches UP state, which means + * both sides have sent CONF_ACK. Currently, we handle both directions + * independently, which means we may accept compressed frames too early + * (supposedly not a problem), but may also mean we send compressed frames + * too early, which may turn out to be a problem. + * This part of state machine should actually be handled by (i)pppd, but + * that's too big of a change now. --kai + */ + +/* Actually, we might turn this into an advantage: deal with the RFC in + * the old tradition of beeing generous on what we accept, but beeing + * strict on what we send. Thus we should just + * - accept compressed frames as soon as decompression is negotiated + * - send compressed frames only when decomp *and* comp are negotiated + * - drop rx compressed frames if we cannot decomp (instead of pushing them + * up to ipppd) + * and I tried to modify this file according to that. --abp + */ + +static void isdn_ppp_send_ccp(isdn_net_dev *net_dev, isdn_net_local *lp, struct sk_buff *skb) +{ + struct ippp_struct *mis,*is; + int proto, slot = lp->ppp_slot; + unsigned char *data; + + if(!skb || skb->len < 3) + return; + if (slot < 0 || slot >= ISDN_MAX_CHANNELS) { + printk(KERN_ERR "%s: lp->ppp_slot(%d) out of range\n", + __FUNCTION__, slot); + return; + } + is = ippp_table[slot]; + /* Daemon may send with or without address and control field comp */ + data = skb->data; + if(!(is->pppcfg & SC_COMP_AC) && data[0] == 0xff && data[1] == 0x03) { + data += 2; + if(skb->len < 5) + return; + } + + proto = ((int)data[0]<<8)+data[1]; + if(proto != PPP_CCP && proto != PPP_CCPFRAG) + return; + + printk(KERN_DEBUG "Received CCP frame from daemon:\n"); + isdn_ppp_frame_log("ccp-xmit", skb->data, skb->len, 32, is->unit,lp->ppp_slot); + + if (lp->master) { + slot = ((isdn_net_local *) (lp->master->priv))->ppp_slot; + if (slot < 0 || slot > ISDN_MAX_CHANNELS) { + printk(KERN_ERR "%s: slot(%d) out of range\n", + __FUNCTION__, slot); + return; + } + mis = ippp_table[slot]; + } else + mis = is; + if (mis != is) + printk(KERN_DEBUG "isdn_ppp: Ouch! Master CCP sends on slave slot!\n"); + + switch(data[2]) { + case CCP_CONFREQ: + if(is->debug & 0x10) + printk(KERN_DEBUG "Disable decompression here!\n"); + if(proto == PPP_CCP) + is->compflags &= ~SC_DECOMP_ON; + else + is->compflags &= ~SC_LINK_DECOMP_ON; + break; + case CCP_TERMREQ: + case CCP_TERMACK: + if(is->debug & 0x10) + printk(KERN_DEBUG "Disable (de)compression here!\n"); + if(proto == PPP_CCP) + is->compflags &= ~(SC_DECOMP_ON|SC_COMP_ON); + else + is->compflags &= ~(SC_LINK_DECOMP_ON|SC_LINK_COMP_ON); + break; + case CCP_CONFACK: + /* if we SEND an ackowledge we can/must enable the compressor */ + if(is->debug & 0x10) + printk(KERN_DEBUG "Enable compression here!\n"); + if(proto == PPP_CCP) { + if (!is->compressor) + break; + is->compflags |= SC_COMP_ON; + } else { + if (!is->compressor) + break; + is->compflags |= SC_LINK_COMP_ON; + } + break; + case CCP_RESETACK: + /* If we send a ACK we should reset our compressor */ + if(is->debug & 0x10) + printk(KERN_DEBUG "Reset decompression state here!\n"); + printk(KERN_DEBUG "ResetAck from daemon passed by\n"); + if(proto == PPP_CCP) { + /* link to master? */ + if(is->compressor && is->comp_stat) + is->compressor->reset(is->comp_stat, 0, 0, + NULL, 0, NULL); + is->compflags &= ~SC_COMP_DISCARD; + } + else { + if(is->link_compressor && is->link_comp_stat) + is->link_compressor->reset(is->link_comp_stat, + 0, 0, NULL, 0, NULL); + is->compflags &= ~SC_LINK_COMP_DISCARD; + } + break; + case CCP_RESETREQ: + /* Just let it pass by */ + printk(KERN_DEBUG "ResetReq from daemon passed by\n"); + break; + } +} + +int isdn_ppp_register_compressor(struct isdn_ppp_compressor *ipc) +{ + ipc->next = ipc_head; + ipc->prev = NULL; + if(ipc_head) { + ipc_head->prev = ipc; + } + ipc_head = ipc; + return 0; +} + +int isdn_ppp_unregister_compressor(struct isdn_ppp_compressor *ipc) +{ + if(ipc->prev) + ipc->prev->next = ipc->next; + else + ipc_head = ipc->next; + if(ipc->next) + ipc->next->prev = ipc->prev; + ipc->prev = ipc->next = NULL; + return 0; +} + +static int isdn_ppp_set_compressor(struct ippp_struct *is, struct isdn_ppp_comp_data *data) +{ + struct isdn_ppp_compressor *ipc = ipc_head; + int ret; + void *stat; + int num = data->num; + + if(is->debug & 0x10) + printk(KERN_DEBUG "[%d] Set %s type %d\n",is->unit, + (data->flags&IPPP_COMP_FLAG_XMIT)?"compressor":"decompressor",num); + + /* If is has no valid reset state vector, we cannot allocate a + decompressor. The decompressor would cause reset transactions + sooner or later, and they need that vector. */ + + if(!(data->flags & IPPP_COMP_FLAG_XMIT) && !is->reset) { + printk(KERN_ERR "ippp_ccp: no reset data structure - can't" + " allow decompression.\n"); + return -ENOMEM; + } + + while(ipc) { + if(ipc->num == num) { + stat = ipc->alloc(data); + if(stat) { + ret = ipc->init(stat,data,is->unit,0); + if(!ret) { + printk(KERN_ERR "Can't init (de)compression!\n"); + ipc->free(stat); + stat = NULL; + break; + } + } + else { + printk(KERN_ERR "Can't alloc (de)compression!\n"); + break; + } + + if(data->flags & IPPP_COMP_FLAG_XMIT) { + if(data->flags & IPPP_COMP_FLAG_LINK) { + if(is->link_comp_stat) + is->link_compressor->free(is->link_comp_stat); + is->link_comp_stat = stat; + is->link_compressor = ipc; + } + else { + if(is->comp_stat) + is->compressor->free(is->comp_stat); + is->comp_stat = stat; + is->compressor = ipc; + } + } + else { + if(data->flags & IPPP_COMP_FLAG_LINK) { + if(is->link_decomp_stat) + is->link_decompressor->free(is->link_decomp_stat); + is->link_decomp_stat = stat; + is->link_decompressor = ipc; + } + else { + if(is->decomp_stat) + is->decompressor->free(is->decomp_stat); + is->decomp_stat = stat; + is->decompressor = ipc; + } + } + return 0; + } + ipc = ipc->next; + } + return -EINVAL; +} diff --git a/drivers/isdn/i4l/isdn_ppp.h b/drivers/isdn/i4l/isdn_ppp.h new file mode 100644 index 000000000000..8cc05c7ccf78 --- /dev/null +++ b/drivers/isdn/i4l/isdn_ppp.h @@ -0,0 +1,43 @@ +/* $Id: isdn_ppp.h,v 1.1.2.2 2004/01/12 22:37:19 keil Exp $ + * + * header for Linux ISDN subsystem, functions for synchronous PPP (linklevel). + * + * Copyright 1995,96 by Michael Hipp (Michael.Hipp@student.uni-tuebingen.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/ppp_defs.h> /* for PPP_PROTOCOL */ +#include <linux/isdn_ppp.h> /* for isdn_ppp info */ + +extern int isdn_ppp_read(int, struct file *, char __user *, int); +extern int isdn_ppp_write(int, struct file *, const char __user *, 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_autodial_filter(struct sk_buff *, isdn_net_local *); +extern int isdn_ppp_xmit(struct sk_buff *, struct net_device *); +extern void isdn_ppp_receive(isdn_net_dev *, isdn_net_local *, struct sk_buff *); +extern int isdn_ppp_dev_ioctl(struct net_device *, struct ifreq *, int); +extern unsigned int isdn_ppp_poll(struct file *, struct poll_table_struct *); +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 int isdn_ppp_register_compressor(struct isdn_ppp_compressor *ipc); +extern int isdn_ppp_unregister_compressor(struct isdn_ppp_compressor *ipc); + +#define IPPP_OPEN 0x01 +#define IPPP_CONNECT 0x02 +#define IPPP_CLOSEWAIT 0x04 +#define IPPP_NOBLOCK 0x08 +#define IPPP_ASSIGNED 0x10 + +#define IPPP_MAX_HEADER 10 + + diff --git a/drivers/isdn/i4l/isdn_tty.c b/drivers/isdn/i4l/isdn_tty.c new file mode 100644 index 000000000000..e21007eca0f0 --- /dev/null +++ b/drivers/isdn/i4l/isdn_tty.c @@ -0,0 +1,3911 @@ +/* $Id: isdn_tty.c,v 1.1.2.3 2004/02/10 01:07:13 keil Exp $ + * + * Linux ISDN subsystem, tty functions and AT-command emulator (linklevel). + * + * Copyright 1994-1999 by Fritz Elfert (fritz@isdn4linux.de) + * Copyright 1995,96 by Thinking Objects Software GmbH Wuerzburg + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ +#undef ISDN_TTY_STAT_DEBUG + +#include <linux/config.h> +#include <linux/isdn.h> +#include <linux/delay.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 + +#define FIX_FILE_TRANSFER +#define DUMMY_HAYES_AT + +/* Prototypes */ + +static int isdn_tty_edit_at(const char *, int, modem_info *); +static void isdn_tty_check_esc(const u_char *, u_char, int, int *, u_long *); +static void isdn_tty_modem_reset_regs(modem_info *, int); +static void isdn_tty_cmd_ATA(modem_info *); +static void isdn_tty_flush_buffer(struct tty_struct *); +static void isdn_tty_modem_result(int, modem_info *); +#ifdef CONFIG_ISDN_AUDIO +static int isdn_tty_countDLE(unsigned char *, int); +#endif + +/* Leave this unchanged unless you know what you do! */ +#define MODEM_PARANOIA_CHECK +#define MODEM_DO_RESTART + +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.1.2.3 $"; + + +/* isdn_tty_try_read() is called from within isdn_tty_rcv_skb() + * 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(). + */ +static 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 +#ifdef CONFIG_ISDN_AUDIO + + ISDN_AUDIO_SKB_DLECOUNT(skb) +#endif + ; + if (c >= len) { +#ifdef CONFIG_ISDN_AUDIO + if (ISDN_AUDIO_SKB_DLECOUNT(skb)) + while (skb->len--) { + if (*skb->data == DLE) + tty_insert_flip_char(tty, DLE, 0); + tty_insert_flip_char(tty, *skb->data++, 0); + } else { +#endif + 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; +#ifdef CONFIG_ISDN_AUDIO + } +#endif + if (info->emu.mdmreg[REG_CPPP] & BIT_CPPP) + tty->flip.flag_buf_ptr[len - 1] = 0xff; + schedule_delayed_work(&tty->flip.work, 1); + kfree_skb(skb); + 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; + 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); + if ((info->vonline & 1) && (info->emu.vpar[1])) + isdn_audio_eval_silence(info); +#endif + if ((tty = info->tty)) { + if (info->mcr & UART_MCR_RTS) { + c = TTY_FLIPBUF_SIZE - tty->flip.count; + if (c > 0) { + r = isdn_readbchan(info->isdn_driver, info->isdn_channel, + tty->flip.char_buf_ptr, + tty->flip.flag_buf_ptr, c, NULL); + /* CISCO AsyncPPP Hack */ + if (!(info->emu.mdmreg[REG_CPPP] & BIT_CPPP)) + 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) + schedule_delayed_work(&tty->flip.work, 1); + } + } 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); +} + +int +isdn_tty_rcv_skb(int i, int di, int channel, struct sk_buff *skb) +{ + ulong flags; + int midx; +#ifdef CONFIG_ISDN_AUDIO + int ifmt; +#endif + modem_info *info; + + if ((midx = dev->m_idx[i]) < 0) { + /* if midx is invalid, packet is not for tty */ + return 0; + } + info = &dev->mdm.info[midx]; +#ifdef CONFIG_ISDN_AUDIO + ifmt = 1; + + if ((info->vonline) && (!info->emu.vpar[4])) + isdn_audio_calc_dtmf(info, skb->data, skb->len, ifmt); + if ((info->vonline & 1) && (info->emu.vpar[1])) + isdn_audio_calc_silence(info, skb->data, skb->len, ifmt); +#endif + if ((info->online < 2) +#ifdef CONFIG_ISDN_AUDIO + && (!(info->vonline & 1)) +#endif + ) { + /* If Modem not listening, drop data */ + kfree_skb(skb); + return 1; + } + if (info->emu.mdmreg[REG_T70] & BIT_T70) { + if (info->emu.mdmreg[REG_T70] & BIT_T70_EXT) { + /* T.70 decoding: throw away the T.70 header (2 or 4 bytes) */ + if (skb->data[0] == 3) /* pure data packet -> 4 byte headers */ + skb_pull(skb, 4); + else + if (skb->data[0] == 1) /* keepalive packet -> 2 byte hdr */ + skb_pull(skb, 2); + } else + /* 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); + } +#ifdef CONFIG_ISDN_AUDIO + ISDN_AUDIO_SKB_DLECOUNT(skb) = 0; + ISDN_AUDIO_SKB_LOCK(skb) = 0; + 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; + } + ISDN_AUDIO_SKB_DLECOUNT(skb) = + isdn_tty_countDLE(skb->data, skb->len); + } +#ifdef CONFIG_ISDN_TTY_FAX + else { + if (info->faxonline & 2) { + isdn_tty_fax_bitorder(info, skb); + ISDN_AUDIO_SKB_DLECOUNT(skb) = + isdn_tty_countDLE(skb->data, skb->len); + } + } +#endif +#endif + /* Try to deliver directly via tty-flip-buf if queue is empty */ + spin_lock_irqsave(&info->readlock, flags); + if (skb_queue_empty(&dev->drv[di]->rpqueue[channel])) + if (isdn_tty_try_read(info, skb)) { + spin_unlock_irqrestore(&info->readlock, flags); + return 1; + } + /* 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 +#ifdef CONFIG_ISDN_AUDIO + + ISDN_AUDIO_SKB_DLECOUNT(skb) +#endif + ); + spin_unlock_irqrestore(&info->readlock, flags); + /* Schedule dequeuing */ + if ((dev->modempoll) && (info->rcvsched)) + isdn_timer_ctrl(ISDN_TIMER_MODEMREAD, 1); + return 1; +} + +void +isdn_tty_cleanup_xmit(modem_info * info) +{ + skb_queue_purge(&info->xmit_queue); +#ifdef CONFIG_ISDN_AUDIO + skb_queue_purge(&info->dtmf_queue); +#endif +} + +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, 1, skb)) == len) { + struct tty_struct *tty = info->tty; + info->send_outstanding++; + info->msr &= ~UART_MSR_CTS; + info->lsr &= ~UART_LSR_TEMT; + tty_wakeup(tty); + return; + } + if (slen < 0) { + /* Error: no channel, already shutdown, or wrong parameter */ + dev_kfree_skb(skb); + return; + } + skb_queue_head(&info->xmit_queue, skb); +} + +#ifdef CONFIG_ISDN_AUDIO +static 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; +#ifdef ISDN_DEBUG_MODEM_VOICE + printk(KERN_DEBUG + "DLEdown: got DLE-DC4, send DLE-ETX on ttyI%d\n", + info->line); +#endif + isdn_tty_at_cout("\020\003", info); + if (!info->vonline) { +#ifdef ISDN_DEBUG_MODEM_VOICE + printk(KERN_DEBUG + "DLEdown: send VCON on ttyI%d\n", + info->line); +#endif + 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) +{ + char ch; + + while (c--) { + ch = *buf; + if ((ch != 0x11) && (ch != 0x13)) + return 1; + buf++; + } + return 0; +} + +static int voice_cf[7] = +{0, 0, 4, 3, 2, 0, 0}; + +#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) +{ + int buflen; + int skb_res; +#ifdef CONFIG_ISDN_AUDIO + int audio_len; +#endif + struct sk_buff *skb; + +#ifdef CONFIG_ISDN_AUDIO + if (info->vonline & 4) { + info->vonline &= ~6; + if (!info->vonline) { +#ifdef ISDN_DEBUG_MODEM_VOICE + printk(KERN_DEBUG + "senddown: send VCON on ttyI%d\n", + info->line); +#endif + isdn_tty_at_cout("\r\nVCON\r\n", info); + } + } +#endif + if (!(buflen = info->xmit_count)) + return; + if ((info->emu.mdmreg[REG_CTS] & BIT_CTS) != 0) + info->msr &= ~UART_MSR_CTS; + info->lsr &= ~UART_LSR_TEMT; + /* info->xmit_count is modified here and in isdn_tty_write(). + * So we return here if isdn_tty_write() is in the + * critical section. + */ + atomic_inc(&info->xmit_lock); + if (!(atomic_dec_and_test(&info->xmit_lock))) + return; + if (info->isdn_driver < 0) { + info->xmit_count = 0; + return; + } + skb_res = dev->drv[info->isdn_driver]->interface->hl_hdrlen + 4; +#ifdef CONFIG_ISDN_AUDIO + if (info->vonline & 2) + audio_len = buflen * voice_cf[info->emu.vpar[3]]; + else + audio_len = 0; + skb = dev_alloc_skb(skb_res + buflen + audio_len); +#else + skb = dev_alloc_skb(skb_res + buflen); +#endif + if (!skb) { + printk(KERN_WARNING + "isdn_tty: Out of memory in ttyI%d senddown\n", + info->line); + return; + } + skb_reserve(skb, skb_res); + memcpy(skb_put(skb, buflen), info->xmit_buf, buflen); + info->xmit_count = 0; +#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; + + /* voice conversion/decompression */ + switch (info->emu.vpar[3]) { + case 2: + case 3: + case 4: + /* adpcm, compatible to ZyXel 1496 modem + * with ROM revision 6.01 + */ + audio_len = isdn_audio_adpcm2xlaw(info->adpcms, + ifmt, + skb->data, + skb_put(skb, audio_len), + buflen); + skb_pull(skb, buflen); + skb_trim(skb, audio_len); + break; + case 5: + /* a-law */ + if (!ifmt) + isdn_audio_alaw2ulaw(skb->data, + buflen); + break; + case 6: + /* u-law */ + if (ifmt) + isdn_audio_ulaw2alaw(skb->data, + buflen); + break; + } + } +#endif /* CONFIG_ISDN_AUDIO */ + if (info->emu.mdmreg[REG_T70] & BIT_T70) { + /* Add T.70 simplified header */ + if (info->emu.mdmreg[REG_T70] & BIT_T70_EXT) + memcpy(skb_push(skb, 2), "\1\0", 2); + else + memcpy(skb_push(skb, 4), "\1\0\1\0", 4); + } + skb_queue_tail(&info->xmit_queue, skb); +} + +/************************************************************ + * + * 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(RESULT_NO_CARRIER, 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->nc_timer.expires = jiffies + HZ; + add_timer(&info->nc_timer); + } +} + +/* + * return the usage calculated by si and layer 2 protocol + */ +int +isdn_calc_usage(int si, int l2) +{ + int usg = ISDN_USAGE_MODEM; + +#ifdef CONFIG_ISDN_AUDIO + if (si == 1) { + switch(l2) { + case ISDN_PROTO_L2_MODEM: + usg = ISDN_USAGE_MODEM; + break; +#ifdef CONFIG_ISDN_TTY_FAX + case ISDN_PROTO_L2_FAX: + usg = ISDN_USAGE_FAX; + break; +#endif + case ISDN_PROTO_L2_TRANS: + default: + usg = ISDN_USAGE_VOICE; + break; + } + } +#endif + return(usg); +} + +/* 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[REG_L2PROT]; + u_long flags; + isdn_ctrl cmd; + int i; + int j; + + for (j = 7; j >= 0; j--) + if (m->mdmreg[REG_SI1] & (1 << j)) { + si = bit2si[j]; + break; + } + usg = isdn_calc_usage(si, l2); +#ifdef CONFIG_ISDN_AUDIO + if ((si == 1) && + (l2 != ISDN_PROTO_L2_MODEM) +#ifdef CONFIG_ISDN_TTY_FAX + && (l2 != ISDN_PROTO_L2_FAX) +#endif + ) { + l2 = ISDN_PROTO_L2_TRANS; + usg = ISDN_USAGE_VOICE; + } +#endif + m->mdmreg[REG_SI1I] = si2bit[si]; + spin_lock_irqsave(&dev->lock, flags); + i = isdn_get_free_channel(usg, l2, m->mdmreg[REG_L3PROT], -1, -1, m->msn); + if (i < 0) { + spin_unlock_irqrestore(&dev->lock, flags); + isdn_tty_modem_result(RESULT_NO_DIALTONE, 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; + info->last_dir = 1; + strcpy(info->last_num, n); + isdn_info_update(); + spin_unlock_irqrestore(&dev->lock, flags); + cmd.driver = info->isdn_driver; + cmd.arg = info->isdn_channel; + cmd.command = ISDN_CMD_CLREAZ; + isdn_command(&cmd); + strcpy(cmd.parm.num, isdn_map_eaz2msn(m->msn, info->isdn_driver)); + cmd.driver = info->isdn_driver; + cmd.command = ISDN_CMD_SETEAZ; + isdn_command(&cmd); + cmd.driver = info->isdn_driver; + cmd.command = ISDN_CMD_SETL2; + info->last_l2 = l2; + cmd.arg = info->isdn_channel + (l2 << 8); + isdn_command(&cmd); + cmd.driver = info->isdn_driver; + cmd.command = ISDN_CMD_SETL3; + cmd.arg = info->isdn_channel + (m->mdmreg[REG_L3PROT] << 8); +#ifdef CONFIG_ISDN_TTY_FAX + if (l2 == ISDN_PROTO_L2_FAX) { + cmd.parm.fax = info->fax; + info->fax->direction = ISDN_TTY_FAX_CONN_OUT; + } +#endif + isdn_command(&cmd); + cmd.driver = info->isdn_driver; + cmd.arg = info->isdn_channel; + sprintf(cmd.parm.setup.phone, "%s", n); + sprintf(cmd.parm.setup.eazmsn, "%s", + isdn_map_eaz2msn(m->msn, info->isdn_driver)); + cmd.parm.setup.si1 = si; + cmd.parm.setup.si2 = m->mdmreg[REG_SI2]; + cmd.command = ISDN_CMD_DIAL; + info->dialing = 1; + info->emu.carrierwait = 0; + strcpy(dev->num[i], n); + isdn_info_update(); + isdn_command(&cmd); + isdn_timer_ctrl(ISDN_TIMER_CARRIER, 1); + } +} + +/* 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, int local) +{ + isdn_ctrl cmd; + int di, ch; + + if (!info) + return; + + di = info->isdn_driver; + ch = info->isdn_channel; + if (di < 0 || ch < 0) + return; + + info->isdn_driver = -1; + info->isdn_channel = -1; + +#ifdef ISDN_DEBUG_MODEM_HUP + printk(KERN_DEBUG "Mhup ttyI%d\n", info->line); +#endif + info->rcvsched = 0; + isdn_tty_flush_buffer(info->tty); + if (info->online) { + info->last_lhup = local; + info->online = 0; + isdn_tty_modem_result(RESULT_NO_CARRIER, info); + } +#ifdef CONFIG_ISDN_AUDIO + info->vonline = 0; +#ifdef CONFIG_ISDN_TTY_FAX + info->faxonline = 0; + info->fax->phase = ISDN_FAX_PHASE_IDLE; +#endif + info->emu.vpar[4] = 0; + info->emu.vpar[5] = 8; + if (info->dtmf_state) { + kfree(info->dtmf_state); + info->dtmf_state = NULL; + } + if (info->silence_state) { + kfree(info->silence_state); + info->silence_state = NULL; + } + if (info->adpcms) { + kfree(info->adpcms); + info->adpcms = NULL; + } + if (info->adpcmr) { + kfree(info->adpcmr); + info->adpcmr = NULL; + } +#endif + if ((info->msr & UART_MSR_RI) && + (info->emu.mdmreg[REG_RUNG] & BIT_RUNG)) + isdn_tty_modem_result(RESULT_RUNG, info); + info->msr &= ~(UART_MSR_DCD | UART_MSR_RI); + info->lsr |= UART_LSR_TEMT; + + if (local) { + cmd.driver = di; + cmd.command = ISDN_CMD_HANGUP; + cmd.arg = ch; + isdn_command(&cmd); + } + + isdn_all_eaz(di, ch); + info->emu.mdmreg[REG_RINGCNT] = 0; + isdn_free_channel(di, ch, 0); + + if (info->drv_index >= 0) { + dev->m_idx[info->drv_index] = -1; + info->drv_index = -1; + } +} + +/* + * Begin of a CAPI like interface, currently used only for + * supplementary service (CAPI 2.0 part III) + */ +#include <linux/isdn/capicmd.h> + +int +isdn_tty_capi_facility(capi_msg *cm) { + return(-1); /* dummy */ +} + +/* isdn_tty_suspend() tries to suspend the current tty connection + */ +static void +isdn_tty_suspend(char *id, modem_info * info, atemu * m) +{ + isdn_ctrl cmd; + + int l; + + if (!info) + return; + +#ifdef ISDN_DEBUG_MODEM_SERVICES + printk(KERN_DEBUG "Msusp ttyI%d\n", info->line); +#endif + l = strlen(id); + if ((info->isdn_driver >= 0)) { + cmd.parm.cmsg.Length = l+18; + cmd.parm.cmsg.Command = CAPI_FACILITY; + cmd.parm.cmsg.Subcommand = CAPI_REQ; + cmd.parm.cmsg.adr.Controller = info->isdn_driver + 1; + cmd.parm.cmsg.para[0] = 3; /* 16 bit 0x0003 suplementary service */ + cmd.parm.cmsg.para[1] = 0; + cmd.parm.cmsg.para[2] = l + 3; + cmd.parm.cmsg.para[3] = 4; /* 16 bit 0x0004 Suspend */ + cmd.parm.cmsg.para[4] = 0; + cmd.parm.cmsg.para[5] = l; + strncpy(&cmd.parm.cmsg.para[6], id, l); + cmd.command = CAPI_PUT_MESSAGE; + cmd.driver = info->isdn_driver; + cmd.arg = info->isdn_channel; + isdn_command(&cmd); + } +} + +/* isdn_tty_resume() tries to resume a suspended call + * setup of the lower levels before that. unfortunatly here is no + * checking for compatibility of used protocols implemented by Q931 + * It does the same things like isdn_tty_dial, the last command + * is different, may be we can merge it. + */ + +static void +isdn_tty_resume(char *id, modem_info * info, atemu * m) +{ + int usg = ISDN_USAGE_MODEM; + int si = 7; + int l2 = m->mdmreg[REG_L2PROT]; + isdn_ctrl cmd; + ulong flags; + int i; + int j; + int l; + + l = strlen(id); + for (j = 7; j >= 0; j--) + if (m->mdmreg[REG_SI1] & (1 << j)) { + si = bit2si[j]; + break; + } + usg = isdn_calc_usage(si, l2); +#ifdef CONFIG_ISDN_AUDIO + if ((si == 1) && + (l2 != ISDN_PROTO_L2_MODEM) +#ifdef CONFIG_ISDN_TTY_FAX + && (l2 != ISDN_PROTO_L2_FAX) +#endif + ) { + l2 = ISDN_PROTO_L2_TRANS; + usg = ISDN_USAGE_VOICE; + } +#endif + m->mdmreg[REG_SI1I] = si2bit[si]; + spin_lock_irqsave(&dev->lock, flags); + i = isdn_get_free_channel(usg, l2, m->mdmreg[REG_L3PROT], -1, -1, m->msn); + if (i < 0) { + spin_unlock_irqrestore(&dev->lock, flags); + isdn_tty_modem_result(RESULT_NO_DIALTONE, 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; + info->last_dir = 1; +// strcpy(info->last_num, n); + isdn_info_update(); + spin_unlock_irqrestore(&dev->lock, flags); + cmd.driver = info->isdn_driver; + cmd.arg = info->isdn_channel; + cmd.command = ISDN_CMD_CLREAZ; + isdn_command(&cmd); + strcpy(cmd.parm.num, isdn_map_eaz2msn(m->msn, info->isdn_driver)); + cmd.driver = info->isdn_driver; + cmd.command = ISDN_CMD_SETEAZ; + isdn_command(&cmd); + cmd.driver = info->isdn_driver; + cmd.command = ISDN_CMD_SETL2; + info->last_l2 = l2; + cmd.arg = info->isdn_channel + (l2 << 8); + isdn_command(&cmd); + cmd.driver = info->isdn_driver; + cmd.command = ISDN_CMD_SETL3; + cmd.arg = info->isdn_channel + (m->mdmreg[REG_L3PROT] << 8); + isdn_command(&cmd); + cmd.driver = info->isdn_driver; + cmd.arg = info->isdn_channel; + cmd.parm.cmsg.Length = l+18; + cmd.parm.cmsg.Command = CAPI_FACILITY; + cmd.parm.cmsg.Subcommand = CAPI_REQ; + cmd.parm.cmsg.adr.Controller = info->isdn_driver + 1; + cmd.parm.cmsg.para[0] = 3; /* 16 bit 0x0003 suplementary service */ + cmd.parm.cmsg.para[1] = 0; + cmd.parm.cmsg.para[2] = l+3; + cmd.parm.cmsg.para[3] = 5; /* 16 bit 0x0005 Resume */ + cmd.parm.cmsg.para[4] = 0; + cmd.parm.cmsg.para[5] = l; + strncpy(&cmd.parm.cmsg.para[6], id, l); + cmd.command =CAPI_PUT_MESSAGE; + info->dialing = 1; +// strcpy(dev->num[i], n); + isdn_info_update(); + isdn_command(&cmd); + isdn_timer_ctrl(ISDN_TIMER_CARRIER, 1); + } +} + +/* isdn_tty_send_msg() sends a message to a HL driver + * This is used for hybrid modem cards to send AT commands to it + */ + +static void +isdn_tty_send_msg(modem_info * info, atemu * m, char *msg) +{ + int usg = ISDN_USAGE_MODEM; + int si = 7; + int l2 = m->mdmreg[REG_L2PROT]; + isdn_ctrl cmd; + ulong flags; + int i; + int j; + int l; + + l = strlen(msg); + if (!l) { + isdn_tty_modem_result(RESULT_ERROR, info); + return; + } + for (j = 7; j >= 0; j--) + if (m->mdmreg[REG_SI1] & (1 << j)) { + si = bit2si[j]; + break; + } + usg = isdn_calc_usage(si, l2); +#ifdef CONFIG_ISDN_AUDIO + if ((si == 1) && + (l2 != ISDN_PROTO_L2_MODEM) +#ifdef CONFIG_ISDN_TTY_FAX + && (l2 != ISDN_PROTO_L2_FAX) +#endif + ) { + l2 = ISDN_PROTO_L2_TRANS; + usg = ISDN_USAGE_VOICE; + } +#endif + m->mdmreg[REG_SI1I] = si2bit[si]; + spin_lock_irqsave(&dev->lock, flags); + i = isdn_get_free_channel(usg, l2, m->mdmreg[REG_L3PROT], -1, -1, m->msn); + if (i < 0) { + spin_unlock_irqrestore(&dev->lock, flags); + isdn_tty_modem_result(RESULT_NO_DIALTONE, 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; + info->last_dir = 1; + isdn_info_update(); + spin_unlock_irqrestore(&dev->lock, flags); + cmd.driver = info->isdn_driver; + cmd.arg = info->isdn_channel; + cmd.command = ISDN_CMD_CLREAZ; + isdn_command(&cmd); + strcpy(cmd.parm.num, isdn_map_eaz2msn(m->msn, info->isdn_driver)); + cmd.driver = info->isdn_driver; + cmd.command = ISDN_CMD_SETEAZ; + isdn_command(&cmd); + cmd.driver = info->isdn_driver; + cmd.command = ISDN_CMD_SETL2; + info->last_l2 = l2; + cmd.arg = info->isdn_channel + (l2 << 8); + isdn_command(&cmd); + cmd.driver = info->isdn_driver; + cmd.command = ISDN_CMD_SETL3; + cmd.arg = info->isdn_channel + (m->mdmreg[REG_L3PROT] << 8); + isdn_command(&cmd); + cmd.driver = info->isdn_driver; + cmd.arg = info->isdn_channel; + cmd.parm.cmsg.Length = l+14; + cmd.parm.cmsg.Command = CAPI_MANUFACTURER; + cmd.parm.cmsg.Subcommand = CAPI_REQ; + cmd.parm.cmsg.adr.Controller = info->isdn_driver + 1; + cmd.parm.cmsg.para[0] = l+1; + strncpy(&cmd.parm.cmsg.para[1], msg, l); + cmd.parm.cmsg.para[l+1] = 0xd; + cmd.command =CAPI_PUT_MESSAGE; +/* info->dialing = 1; + strcpy(dev->num[i], n); + isdn_info_update(); +*/ + isdn_command(&cmd); + } +} + +static inline int +isdn_tty_paranoia_check(modem_info *info, char *name, const char *routine) +{ +#ifdef MODEM_PARANOIA_CHECK + if (!info) { + printk(KERN_WARNING "isdn_tty: null info_struct for %s in %s\n", + name, routine); + return 1; + } + if (info->magic != ISDN_ASYNC_MAGIC) { + printk(KERN_WARNING "isdn_tty: bad magic for modem struct %s in %s\n", + name, 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[REG_DTRHUP] & BIT_DTRHUP) { +#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, 0); + isdn_tty_modem_hup(info, 1); + } + 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) +{ + if (info->flags & ISDN_ASYNC_INITIALIZED) + return 0; + isdn_lock_drivers(); +#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; + 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) +{ + if (!(info->flags & ISDN_ASYNC_INITIALIZED)) + return; +#ifdef ISDN_DEBUG_MODEM_OPEN + printk(KERN_DEBUG "Shutting down isdnmodem port %d ....\n", info->line); +#endif + isdn_unlock_drivers(); + info->msr &= ~UART_MSR_RI; + if (!info->tty || (info->tty->termios->c_cflag & HUPCL)) { + info->mcr &= ~(UART_MCR_DTR | UART_MCR_RTS); + if (info->emu.mdmreg[REG_DTRHUP] & BIT_DTRHUP) { + isdn_tty_modem_reset_regs(info, 0); +#ifdef ISDN_DEBUG_MODEM_HUP + printk(KERN_DEBUG "Mhup in isdn_tty_shutdown\n"); +#endif + isdn_tty_modem_hup(info, 1); + } + } + if (info->tty) + set_bit(TTY_IO_ERROR, &info->tty->flags); + + info->flags &= ~ISDN_ASYNC_INITIALIZED; +} + +/* 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, const u_char * buf, int count) +{ + int c; + int total = 0; + modem_info *info = (modem_info *) tty->driver_data; + atemu *m = &info->emu; + + if (isdn_tty_paranoia_check(info, tty->name, "isdn_tty_write")) + return 0; + /* See isdn_tty_senddown() */ + atomic_inc(&info->xmit_lock); + while (1) { + c = count; + if (c > info->xmit_size - info->xmit_count) + c = info->xmit_size - info->xmit_count; + if (info->isdn_driver >= 0 && c > dev->drv[info->isdn_driver]->maxbufsize) + c = dev->drv[info->isdn_driver]->maxbufsize; + if (c <= 0) + break; + if ((info->online > 1) +#ifdef CONFIG_ISDN_AUDIO + || (info->vonline & 3) +#endif + ) { +#ifdef CONFIG_ISDN_AUDIO + if (!info->vonline) +#endif + isdn_tty_check_esc(buf, m->mdmreg[REG_ESC], c, + &(m->pluscount), + &(m->lastplus)); + memcpy(&(info->xmit_buf[info->xmit_count]), buf, c); +#ifdef CONFIG_ISDN_AUDIO + if (info->vonline) { + int cc = isdn_tty_handleDLEdown(info, m, c); + if (info->vonline & 2) { + if (!cc) { + /* If DLE decoding results in zero-transmit, but + * c originally was non-zero, do a wakeup. + */ + tty_wakeup(tty); + info->msr |= UART_MSR_CTS; + info->lsr |= UART_LSR_TEMT; + } + info->xmit_count += cc; + } + if ((info->vonline & 3) == 1) { + /* Do NOT handle Ctrl-Q or Ctrl-S + * when in full-duplex audio mode. + */ + if (isdn_tty_end_vrx(buf, c)) { + info->vonline &= ~1; +#ifdef ISDN_DEBUG_MODEM_VOICE + printk(KERN_DEBUG + "got !^Q/^S, send DLE-ETX,VCON on ttyI%d\n", + info->line); +#endif + isdn_tty_at_cout("\020\003\r\nVCON\r\n", info); + } + } + } else + if (TTY_IS_FCLASS1(info)) { + int cc = isdn_tty_handleDLEdown(info, m, c); + + if (info->vonline & 4) { /* ETX seen */ + isdn_ctrl c; + + c.command = ISDN_CMD_FAXCMD; + c.driver = info->isdn_driver; + c.arg = info->isdn_channel; + c.parm.aux.cmd = ISDN_FAX_CLASS1_CTRL; + c.parm.aux.subcmd = ETX; + isdn_command(&c); + } + info->vonline = 0; +#ifdef ISDN_DEBUG_MODEM_VOICE + printk(KERN_DEBUG "fax dle cc/c %d/%d\n", cc, c); +#endif + info->xmit_count += cc; + } else +#endif + info->xmit_count += c; + } else { + info->msr |= UART_MSR_CTS; + info->lsr |= UART_LSR_TEMT; + 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(RESULT_NO_CARRIER, info); + isdn_tty_modem_hup(info, 1); + } else + c = isdn_tty_edit_at(buf, c, info); + } + buf += c; + count -= c; + total += c; + } + atomic_dec(&info->xmit_lock); + if ((info->xmit_count) || (skb_queue_len(&info->xmit_queue))) { + if (m->mdmreg[REG_DXMT] & BIT_DXMT) { + isdn_tty_senddown(info); + isdn_tty_tint(info); + } + isdn_timer_ctrl(ISDN_TIMER_MODEMXMIT, 1); + } + 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->name, "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->name, "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; + + if (!tty) { + return; + } + info = (modem_info *) tty->driver_data; + if (isdn_tty_paranoia_check(info, tty->name, "isdn_tty_flush_buffer")) { + return; + } + isdn_tty_cleanup_xmit(info); + info->xmit_count = 0; + wake_up_interruptible(&tty->write_wait); + tty_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->name, "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->name, "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->name, "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 __user * value) +{ + u_char status; + uint result; + + status = info->lsr; + result = ((status & UART_LSR_TEMT) ? TIOCSER_TEMT : 0); + return put_user(result, value); +} + + +static int +isdn_tty_tiocmget(struct tty_struct *tty, struct file *file) +{ + modem_info *info = (modem_info *) tty->driver_data; + u_char control, status; + + if (isdn_tty_paranoia_check(info, tty->name, __FUNCTION__)) + return -ENODEV; + if (tty->flags & (1 << TTY_IO_ERROR)) + return -EIO; + +#ifdef ISDN_DEBUG_MODEM_IOCTL + printk(KERN_DEBUG "ttyI%d ioctl TIOCMGET\n", info->line); +#endif + + control = info->mcr; + status = info->msr; + return ((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); +} + +static int +isdn_tty_tiocmset(struct tty_struct *tty, struct file *file, + unsigned int set, unsigned int clear) +{ + modem_info *info = (modem_info *) tty->driver_data; + + if (isdn_tty_paranoia_check(info, tty->name, __FUNCTION__)) + return -ENODEV; + if (tty->flags & (1 << TTY_IO_ERROR)) + return -EIO; + +#ifdef ISDN_DEBUG_MODEM_IOCTL + printk(KERN_DEBUG "ttyI%d ioctl TIOCMxxx: %x %x\n", info->line, set, clear); +#endif + + if (set & TIOCM_RTS) + info->mcr |= UART_MCR_RTS; + if (set & TIOCM_DTR) { + info->mcr |= UART_MCR_DTR; + isdn_tty_modem_ncarrier(info); + } + + if (clear & TIOCM_RTS) + info->mcr &= ~UART_MCR_RTS; + if (clear & TIOCM_DTR) { + info->mcr &= ~UART_MCR_DTR; + if (info->emu.mdmreg[REG_DTRHUP] & BIT_DTRHUP) { + isdn_tty_modem_reset_regs(info, 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, 1); + } + } + 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 retval; + + if (isdn_tty_paranoia_check(info, tty->name, "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, (ulong __user *) arg); + case TIOCSSOFTCAR: +#ifdef ISDN_DEBUG_MODEM_IOCTL + printk(KERN_DEBUG "ttyI%d ioctl TIOCSSOFTCAR\n", info->line); +#endif + if (get_user(arg, (ulong __user *) arg)) + return -EFAULT; + tty->termios->c_cflag = + ((tty->termios->c_cflag & ~CLOCAL) | + (arg ? CLOCAL : 0)); + return 0; + 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 __user *) 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) +{ + DECLARE_WAITQUEUE(wait, NULL); + int do_clocal = 0; + 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 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 + if (!(tty_hung_up_p(filp))) + info->count--; + info->blocked_open++; + while (1) { + set_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 (signal_pending(current)) { + 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 = tty->index; + if (line < 0 || line > ISDN_MAX_CHANNELS) + return -ENODEV; + info = &dev->mdm.info[line]; + if (isdn_tty_paranoia_check(info, tty->name, "isdn_tty_open")) + return -ENODEV; + if (!try_module_get(info->owner)) { + printk(KERN_WARNING "%s: cannot reserve module\n", __FUNCTION__); + return -ENODEV; + } +#ifdef ISDN_DEBUG_MODEM_OPEN + printk(KERN_DEBUG "isdn_tty_open %s, count = %d\n", tty->name, + 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 + module_put(info->owner); + 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 + module_put(info->owner); + return retval; + } +#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 timeout; + + if (!info || isdn_tty_paranoia_check(info, tty->name, "isdn_tty_close")) + return; + if (tty_hung_up_p(filp)) { +#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) { +#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)) { + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(20); + if (time_after(jiffies,timeout)) + break; + } + } + dev->modempoll--; + isdn_tty_shutdown(info); + + if (tty->driver->flush_buffer) + tty->driver->flush_buffer(tty); + tty_ldisc_flush(tty); + info->tty = NULL; + info->ncarrier = 0; + tty->closing = 0; + module_put(info->owner); + if (info->blocked_open) { + msleep_interruptible(500); + wake_up_interruptible(&info->open_wait); + } + info->flags &= ~(ISDN_ASYNC_NORMAL_ACTIVE | ISDN_ASYNC_CLOSING); + wake_up_interruptible(&info->close_wait); +#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->name, "isdn_tty_hangup")) + return; + isdn_tty_shutdown(info); + info->count = 0; + info->flags &= ~(ISDN_ASYNC_NORMAL_ACTIVE | ISDN_ASYNC_CALLOUT_ACTIVE); + info->tty = NULL; + 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->profile[23] = 0; + m->pmsn[0] = '\0'; + m->plmsn[0] = '\0'; +} + +#ifdef CONFIG_ISDN_AUDIO +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 ) */ + m->vpar[4] = 0; /* DTMF detection level (0 = softcode ) */ + m->vpar[5] = 8; /* DTMF interval (8 * 5 ms. ) */ +} +#endif + +#ifdef CONFIG_ISDN_TTY_FAX +static void +isdn_tty_modem_reset_faxpar(modem_info * info) +{ + T30_s *f = info->fax; + + f->code = 0; + f->phase = ISDN_FAX_PHASE_IDLE; + f->direction = 0; + f->resolution = 1; /* fine */ + f->rate = 5; /* 14400 bit/s */ + f->width = 0; + f->length = 0; + f->compression = 0; + f->ecm = 0; + f->binary = 0; + f->scantime = 0; + memset(&f->id[0], 32, FAXIDLEN - 1); + f->id[FAXIDLEN - 1] = 0; + f->badlin = 0; + f->badmul = 0; + f->bor = 0; + f->nbc = 0; + f->cq = 0; + f->cr = 0; + f->ctcrty = 0; + f->minsp = 0; + f->phcto = 30; + f->rel = 0; + memset(&f->pollid[0], 32, FAXIDLEN - 1); + f->pollid[FAXIDLEN - 1] = 0; +} +#endif + +static void +isdn_tty_modem_reset_regs(modem_info * info, int force) +{ + atemu *m = &info->emu; + if ((m->mdmreg[REG_DTRR] & BIT_DTRR) || force) { + memcpy(m->mdmreg, m->profile, ISDN_MODEM_NUMREG); + memcpy(m->msn, m->pmsn, ISDN_MSNLEN); + memcpy(m->lmsn, m->plmsn, ISDN_LMSNLEN); + info->xmit_size = m->mdmreg[REG_PSIZE] * 16; + } +#ifdef CONFIG_ISDN_AUDIO + isdn_tty_modem_reset_vpar(m); +#endif +#ifdef CONFIG_ISDN_TTY_FAX + isdn_tty_modem_reset_faxpar(info); +#endif + m->mdmcmdl = 0; +} + +static void +modem_write_profile(atemu * m) +{ + memcpy(m->profile, m->mdmreg, ISDN_MODEM_NUMREG); + memcpy(m->pmsn, m->msn, ISDN_MSNLEN); + memcpy(m->plmsn, m->lmsn, ISDN_LMSNLEN); + if (dev->profd) + send_sig(SIGIO, dev->profd, 1); +} + +static struct tty_operations modem_ops = { + .open = isdn_tty_open, + .close = isdn_tty_close, + .write = isdn_tty_write, + .flush_chars = isdn_tty_flush_chars, + .write_room = isdn_tty_write_room, + .chars_in_buffer = isdn_tty_chars_in_buffer, + .flush_buffer = isdn_tty_flush_buffer, + .ioctl = isdn_tty_ioctl, + .throttle = isdn_tty_throttle, + .unthrottle = isdn_tty_unthrottle, + .set_termios = isdn_tty_set_termios, + .hangup = isdn_tty_hangup, + .tiocmget = isdn_tty_tiocmget, + .tiocmset = isdn_tty_tiocmset, +}; + +int +isdn_tty_modem_init(void) +{ + isdn_modem_t *m; + int i, retval; + modem_info *info; + + m = &dev->mdm; + m->tty_modem = alloc_tty_driver(ISDN_MAX_CHANNELS); + if (!m->tty_modem) + return -ENOMEM; + m->tty_modem->name = "ttyI"; + m->tty_modem->devfs_name = "isdn/ttyI"; + m->tty_modem->major = ISDN_TTY_MAJOR; + m->tty_modem->minor_start = 0; + m->tty_modem->type = TTY_DRIVER_TYPE_SERIAL; + m->tty_modem->subtype = 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 | TTY_DRIVER_NO_DEVFS; + m->tty_modem->driver_name = "isdn_tty"; + tty_set_operations(m->tty_modem, &modem_ops); + retval = tty_register_driver(m->tty_modem); + if (retval) { + printk(KERN_WARNING "isdn_tty: Couldn't register modem-device\n"); + goto err; + } + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + info = &m->info[i]; +#ifdef CONFIG_ISDN_TTY_FAX + if (!(info->fax = kmalloc(sizeof(T30_s), GFP_KERNEL))) { + printk(KERN_ERR "Could not allocate fax t30-buffer\n"); + retval = -ENOMEM; + goto err_unregister; + } +#endif +#ifdef MODULE + info->owner = THIS_MODULE; +#endif + spin_lock_init(&info->readlock); + init_MUTEX(&info->write_sem); + sprintf(info->last_cause, "0000"); + sprintf(info->last_num, "none"); + info->last_dir = 0; + info->last_lhup = 1; + info->last_l2 = -1; + info->last_si = 0; + isdn_tty_reset_profile(&info->emu); + isdn_tty_modem_reset_regs(info, 1); + info->magic = ISDN_ASYNC_MAGIC; + info->line = i; + info->tty = NULL; + info->x_char = 0; + info->count = 0; + info->blocked_open = 0; + init_waitqueue_head(&info->open_wait); + init_waitqueue_head(&info->close_wait); + info->isdn_driver = -1; + info->isdn_channel = -1; + info->drv_index = -1; + info->xmit_size = ISDN_SERIAL_XMIT_SIZE; + init_timer(&info->nc_timer); + info->nc_timer.function = isdn_tty_modem_do_ncarrier; + info->nc_timer.data = (unsigned long) info; + skb_queue_head_init(&info->xmit_queue); +#ifdef CONFIG_ISDN_AUDIO + skb_queue_head_init(&info->dtmf_queue); +#endif + if (!(info->xmit_buf = kmalloc(ISDN_SERIAL_XMIT_MAX + 5, GFP_KERNEL))) { + printk(KERN_ERR "Could not allocate modem xmit-buffer\n"); + retval = -ENOMEM; + goto err_unregister; + } + /* Make room for T.70 header */ + info->xmit_buf += 4; + } + return 0; +err_unregister: + for (i--; i >= 0; i--) { + info = &m->info[i]; +#ifdef CONFIG_ISDN_TTY_FAX + kfree(info->fax); +#endif + kfree(info->xmit_buf - 4); + } + tty_unregister_driver(m->tty_modem); + err: + put_tty_driver(m->tty_modem); + m->tty_modem = NULL; + return retval; +} + +void +isdn_tty_exit(void) +{ + modem_info *info; + int i; + + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + info = &dev->mdm.info[i]; + isdn_tty_cleanup_xmit(info); +#ifdef CONFIG_ISDN_TTY_FAX + kfree(info->fax); +#endif + kfree(info->xmit_buf - 4); + } + tty_unregister_driver(dev->mdm.tty_modem); + put_tty_driver(dev->mdm.tty_modem); + dev->mdm.tty_modem = NULL; +} + + +/* + * isdn_tty_match_icall(char *MSN, atemu *tty_emulator, int dev_idx) + * match the MSN against the MSNs (glob patterns) defined for tty_emulator, + * and return 0 for match, 1 for no match, 2 if MSN could match if longer. + */ + +static int +isdn_tty_match_icall(char *cid, atemu *emu, int di) +{ +#ifdef ISDN_DEBUG_MODEM_ICALL + printk(KERN_DEBUG "m_fi: msn=%s lmsn=%s mmsn=%s mreg[SI1]=%d mreg[SI2]=%d\n", + emu->msn, emu->lmsn, isdn_map_eaz2msn(emu->msn, di), + emu->mdmreg[REG_SI1], emu->mdmreg[REG_SI2]); +#endif + if (strlen(emu->lmsn)) { + char *p = emu->lmsn; + char *q; + int tmp; + int ret = 0; + + while (1) { + if ((q = strchr(p, ';'))) + *q = '\0'; + if ((tmp = isdn_msncmp(cid, isdn_map_eaz2msn(p, di))) > ret) + ret = tmp; +#ifdef ISDN_DEBUG_MODEM_ICALL + printk(KERN_DEBUG "m_fi: lmsnX=%s mmsn=%s -> tmp=%d\n", + p, isdn_map_eaz2msn(emu->msn, di), tmp); +#endif + if (q) { + *q = ';'; + p = q; + p++; + } + if (!tmp) + return 0; + if (!q) + break; + } + return ret; + } else { + int tmp; + tmp = isdn_msncmp(cid, isdn_map_eaz2msn(emu->msn, di)); +#ifdef ISDN_DEBUG_MODEM_ICALL + printk(KERN_DEBUG "m_fi: mmsn=%s -> tmp=%d\n", + isdn_map_eaz2msn(emu->msn, di), tmp); +#endif + return tmp; + } +} + +/* + * An incoming call-request has arrived. + * Search the tty-devices for an appropriate device and bind + * it to the ISDN-Channel. + * Return: + * + * 0 = No matching device found. + * 1 = A matching device found. + * 3 = No match found, but eventually would match, if + * CID is longer. + */ +int +isdn_tty_find_icall(int di, int ch, setup_parm *setup) +{ + char *eaz; + int i; + int wret; + int idx; + int si1; + int si2; + char *nr; + ulong flags; + + if (!setup->phone[0]) { + nr = "0"; + printk(KERN_INFO "isdn_tty: Incoming call without OAD, assuming '0'\n"); + } else + nr = setup->phone; + si1 = (int) setup->si1; + si2 = (int) setup->si2; + if (!setup->eazmsn[0]) { + printk(KERN_WARNING "isdn_tty: Incoming call without CPN, assuming '0'\n"); + eaz = "0"; + } else + eaz = setup->eazmsn; +#ifdef ISDN_DEBUG_MODEM_ICALL + printk(KERN_DEBUG "m_fi: eaz=%s si1=%d si2=%d\n", eaz, si1, si2); +#endif + wret = 0; + spin_lock_irqsave(&dev->lock, flags); + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + modem_info *info = &dev->mdm.info[i]; + + if (info->count == 0) + continue; + if ((info->emu.mdmreg[REG_SI1] & si2bit[si1]) && /* SI1 is matching */ + (info->emu.mdmreg[REG_SI2] == si2)) { /* SI2 is matching */ + idx = isdn_dc2minor(di, ch); +#ifdef ISDN_DEBUG_MODEM_ICALL + printk(KERN_DEBUG "m_fi: match1 wret=%d\n", wret); + 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 ( +#ifndef FIX_FILE_TRANSFER + (info->flags & ISDN_ASYNC_NORMAL_ACTIVE) && +#endif + (info->isdn_driver == -1) && + (info->isdn_channel == -1) && + (USG_NONE(dev->usage[idx]))) { + int matchret; + + if ((matchret = isdn_tty_match_icall(eaz, &info->emu, di)) > wret) + wret = matchret; + if (!matchret) { /* EAZ is matching */ + 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] |= isdn_calc_usage(si1, info->emu.mdmreg[REG_L2PROT]); + strcpy(dev->num[idx], nr); + strcpy(info->emu.cpn, eaz); + info->emu.mdmreg[REG_SI1I] = si2bit[si1]; + info->emu.mdmreg[REG_PLAN] = setup->plan; + info->emu.mdmreg[REG_SCREEN] = setup->screen; + isdn_info_update(); + spin_unlock_irqrestore(&dev->lock, flags); + printk(KERN_INFO "isdn_tty: call from %s, -> RING on ttyI%d\n", nr, + info->line); + info->msr |= UART_MSR_RI; + isdn_tty_modem_result(RESULT_RING, info); + isdn_timer_ctrl(ISDN_TIMER_MODEMRING, 1); + return 1; + } + } + } + } + spin_unlock_irqrestore(&dev->lock, flags); + printk(KERN_INFO "isdn_tty: call from %s -> %s %s\n", nr, eaz, + ((dev->drv[di]->flags & DRV_FLAG_REJBUS) && (wret != 2))? "rejected" : "ignored"); + return (wret == 2)?3:0; +} + +#define TTY_IS_ACTIVE(info) \ + (info->flags & (ISDN_ASYNC_NORMAL_ACTIVE | ISDN_ASYNC_CALLOUT_ACTIVE)) + +int +isdn_tty_stat_callback(int i, isdn_ctrl *c) +{ + int mi; + modem_info *info; + char *e; + + if (i < 0) + return 0; + if ((mi = dev->m_idx[i]) >= 0) { + info = &dev->mdm.info[mi]; + switch (c->command) { + case ISDN_STAT_CINF: + printk(KERN_DEBUG "CHARGEINFO on ttyI%d: %ld %s\n", info->line, c->arg, c->parm.num); + info->emu.charge = (unsigned) simple_strtoul(c->parm.num, &e, 10); + if (e == (char *)c->parm.num) + info->emu.charge = 0; + + break; + case ISDN_STAT_BSENT: +#ifdef ISDN_TTY_STAT_DEBUG + printk(KERN_DEBUG "tty_STAT_BSENT ttyI%d\n", info->line); +#endif + if ((info->isdn_driver == c->driver) && + (info->isdn_channel == c->arg)) { + info->msr |= UART_MSR_CTS; + if (info->send_outstanding) + if (!(--info->send_outstanding)) + info->lsr |= UART_LSR_TEMT; + isdn_tty_tint(info); + return 1; + } + break; + case ISDN_STAT_CAUSE: +#ifdef ISDN_TTY_STAT_DEBUG + printk(KERN_DEBUG "tty_STAT_CAUSE ttyI%d\n", info->line); +#endif + /* Signal cause to tty-device */ + strncpy(info->last_cause, c->parm.num, 5); + return 1; + case ISDN_STAT_DISPLAY: +#ifdef ISDN_TTY_STAT_DEBUG + printk(KERN_DEBUG "tty_STAT_DISPLAY ttyI%d\n", info->line); +#endif + /* Signal display to tty-device */ + if ((info->emu.mdmreg[REG_DISPLAY] & BIT_DISPLAY) && + !(info->emu.mdmreg[REG_RESPNUM] & BIT_RESPNUM)) { + isdn_tty_at_cout("\r\n", info); + isdn_tty_at_cout("DISPLAY: ", info); + isdn_tty_at_cout(c->parm.display, info); + isdn_tty_at_cout("\r\n", info); + } + return 1; + case ISDN_STAT_DCONN: +#ifdef ISDN_TTY_STAT_DEBUG + printk(KERN_DEBUG "tty_STAT_DCONN ttyI%d\n", info->line); +#endif + if (TTY_IS_ACTIVE(info)) { + if (info->dialing == 1) { + info->dialing = 2; + return 1; + } + } + break; + case ISDN_STAT_DHUP: +#ifdef ISDN_TTY_STAT_DEBUG + printk(KERN_DEBUG "tty_STAT_DHUP ttyI%d\n", info->line); +#endif + if (TTY_IS_ACTIVE(info)) { + if (info->dialing == 1) + isdn_tty_modem_result(RESULT_BUSY, info); + if (info->dialing > 1) + isdn_tty_modem_result(RESULT_NO_CARRIER, info); + info->dialing = 0; +#ifdef ISDN_DEBUG_MODEM_HUP + printk(KERN_DEBUG "Mhup in ISDN_STAT_DHUP\n"); +#endif + isdn_tty_modem_hup(info, 0); + return 1; + } + break; + case ISDN_STAT_BCONN: +#ifdef ISDN_TTY_STAT_DEBUG + printk(KERN_DEBUG "tty_STAT_BCONN ttyI%d\n", info->line); +#endif + /* Wake up any processes waiting + * for incoming call of this device when + * DCD follow the state of incoming carrier + */ + if (info->blocked_open && + (info->emu.mdmreg[REG_DCD] & BIT_DCD)) { + wake_up_interruptible(&info->open_wait); + } + + /* Schedule CONNECT-Message to any tty + * waiting for it and + * set DCD-bit of its modem-status. + */ + if (TTY_IS_ACTIVE(info) || + (info->blocked_open && (info->emu.mdmreg[REG_DCD] & BIT_DCD))) { + info->msr |= UART_MSR_DCD; + info->emu.charge = 0; + if (info->dialing & 0xf) + info->last_dir = 1; + else + info->last_dir = 0; + info->dialing = 0; + info->rcvsched = 1; + if (USG_MODEM(dev->usage[i])) { + if (info->emu.mdmreg[REG_L2PROT] == ISDN_PROTO_L2_MODEM) { + strcpy(info->emu.connmsg, c->parm.num); + isdn_tty_modem_result(RESULT_CONNECT, info); + } else + isdn_tty_modem_result(RESULT_CONNECT64000, info); + } + if (USG_VOICE(dev->usage[i])) + isdn_tty_modem_result(RESULT_VCON, info); + return 1; + } + break; + case ISDN_STAT_BHUP: +#ifdef ISDN_TTY_STAT_DEBUG + printk(KERN_DEBUG "tty_STAT_BHUP ttyI%d\n", info->line); +#endif + if (TTY_IS_ACTIVE(info)) { +#ifdef ISDN_DEBUG_MODEM_HUP + printk(KERN_DEBUG "Mhup in ISDN_STAT_BHUP\n"); +#endif + isdn_tty_modem_hup(info, 0); + return 1; + } + break; + case ISDN_STAT_NODCH: +#ifdef ISDN_TTY_STAT_DEBUG + printk(KERN_DEBUG "tty_STAT_NODCH ttyI%d\n", info->line); +#endif + if (TTY_IS_ACTIVE(info)) { + if (info->dialing) { + info->dialing = 0; + info->last_l2 = -1; + info->last_si = 0; + sprintf(info->last_cause, "0000"); + isdn_tty_modem_result(RESULT_NO_DIALTONE, info); + } + isdn_tty_modem_hup(info, 0); + return 1; + } + break; + case ISDN_STAT_UNLOAD: +#ifdef ISDN_TTY_STAT_DEBUG + printk(KERN_DEBUG "tty_STAT_UNLOAD ttyI%d\n", info->line); +#endif + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + info = &dev->mdm.info[i]; + if (info->isdn_driver == c->driver) { + if (info->online) + isdn_tty_modem_hup(info, 1); + } + } + return 1; +#ifdef CONFIG_ISDN_TTY_FAX + case ISDN_STAT_FAXIND: + if (TTY_IS_ACTIVE(info)) { + isdn_tty_fax_command(info, c); + } + break; +#endif +#ifdef CONFIG_ISDN_AUDIO + case ISDN_STAT_AUDIO: + if (TTY_IS_ACTIVE(info)) { + switch(c->parm.num[0]) { + case ISDN_AUDIO_DTMF: + if (info->vonline) { + isdn_audio_put_dle_code(info, + c->parm.num[1]); + } + break; + } + } + break; +#endif + } + } + return 0; +} + +/********************************************************************* + 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. + */ +void +isdn_tty_at_cout(char *msg, modem_info * info) +{ + struct tty_struct *tty; + atemu *m = &info->emu; + char *p; + char c; + u_long flags; + struct sk_buff *skb = NULL; + char *sp = NULL; + + if (!msg) { + printk(KERN_WARNING "isdn_tty: Null-Message in isdn_tty_at_cout\n"); + return; + } + spin_lock_irqsave(&info->readlock, flags); + tty = info->tty; + if ((info->flags & ISDN_ASYNC_CLOSING) || (!tty)) { + spin_unlock_irqrestore(&info->readlock, flags); + return; + } + + /* use queue instead of direct flip, if online and */ + /* data is in queue or flip buffer is full */ + if ((info->online) && (((tty->flip.count + strlen(msg)) >= TTY_FLIPBUF_SIZE) || + (!skb_queue_empty(&dev->drv[info->isdn_driver]->rpqueue[info->isdn_channel])))) { + skb = alloc_skb(strlen(msg), GFP_ATOMIC); + if (!skb) { + spin_unlock_irqrestore(&info->readlock, flags); + return; + } + sp = skb_put(skb, strlen(msg)); +#ifdef CONFIG_ISDN_AUDIO + ISDN_AUDIO_SKB_DLECOUNT(skb) = 0; + ISDN_AUDIO_SKB_LOCK(skb) = 0; +#endif + } + + for (p = msg; *p; p++) { + switch (*p) { + case '\r': + c = m->mdmreg[REG_CR]; + break; + case '\n': + c = m->mdmreg[REG_LF]; + break; + case '\b': + c = m->mdmreg[REG_BS]; + break; + default: + c = *p; + } + if (skb) { + *sp++ = c; + } else { + if (tty->flip.count >= TTY_FLIPBUF_SIZE) + break; + tty_insert_flip_char(tty, c, 0); + } + } + if (skb) { + __skb_queue_tail(&dev->drv[info->isdn_driver]->rpqueue[info->isdn_channel], skb); + dev->drv[info->isdn_driver]->rcvcount[info->isdn_channel] += skb->len; + spin_unlock_irqrestore(&info->readlock, flags); + /* Schedule dequeuing */ + if ((dev->modempoll) && (info->rcvsched)) + isdn_timer_ctrl(ISDN_TIMER_MODEMREAD, 1); + + } else { + spin_unlock_irqrestore(&info->readlock, flags); + schedule_delayed_work(&tty->flip.work, 1); + } +} + +/* + * 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_hup(info, 1); + } +} + +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, + u_long *lastplus) +{ + if (plus > 127) + return; + if (count > 3) { + p += count - 3; + count = 3; + *pluscount = 0; + } + while (count > 0) { + if (*(p++) == plus) { + if ((*pluscount)++) { + /* Time since last '+' > 0.5 sec. ? */ + if (time_after(jiffies, *lastplus + PLUSWAIT1)) + *pluscount = 1; + } else { + /* Time since last non-'+' < 1.5 sec. ? */ + if (time_before(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 + */ + +static 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", "RUNG"}; + char s[ISDN_MSNLEN+10]; + + switch (code) { + case RESULT_RING: + m->mdmreg[REG_RINGCNT]++; + if (m->mdmreg[REG_RINGCNT] == m->mdmreg[REG_RINGATA]) + /* Automatically accept incoming call */ + isdn_tty_cmd_ATA(info); + break; + case RESULT_NO_CARRIER: +#ifdef ISDN_DEBUG_MODEM_HUP + printk(KERN_DEBUG "modem_result: NO CARRIER %d %d\n", + (info->flags & ISDN_ASYNC_CLOSING), + (!info->tty)); +#endif + m->mdmreg[REG_RINGCNT] = 0; + del_timer(&info->nc_timer); + info->ncarrier = 0; + if ((info->flags & ISDN_ASYNC_CLOSING) || (!info->tty)) { + return; + } +#ifdef CONFIG_ISDN_AUDIO + if (info->vonline & 1) { +#ifdef ISDN_DEBUG_MODEM_VOICE + printk(KERN_DEBUG "res3: send DLE-ETX on ttyI%d\n", + info->line); +#endif + /* voice-recording, add DLE-ETX */ + isdn_tty_at_cout("\020\003", info); + } + if (info->vonline & 2) { +#ifdef ISDN_DEBUG_MODEM_VOICE + printk(KERN_DEBUG "res3: send DLE-DC4 on ttyI%d\n", + info->line); +#endif + /* voice-playing, add DLE-DC4 */ + isdn_tty_at_cout("\020\024", info); + } +#endif + break; + case RESULT_CONNECT: + case RESULT_CONNECT64000: + sprintf(info->last_cause, "0000"); + if (!info->online) + info->online = 2; + break; + case RESULT_VCON: +#ifdef ISDN_DEBUG_MODEM_VOICE + printk(KERN_DEBUG "res3: send VCON on ttyI%d\n", + info->line); +#endif + sprintf(info->last_cause, "0000"); + if (!info->online) + info->online = 1; + break; + } /* switch(code) */ + + if (m->mdmreg[REG_RESP] & BIT_RESP) { + /* Show results */ + if (m->mdmreg[REG_RESPNUM] & BIT_RESPNUM) { + /* Show numeric results only */ + sprintf(s, "\r\n%d\r\n", code); + isdn_tty_at_cout(s, info); + } else { + if (code == RESULT_RING) { + /* return if "show RUNG" and ringcounter>1 */ + if ((m->mdmreg[REG_RUNG] & BIT_RUNG) && + (m->mdmreg[REG_RINGCNT] > 1)) + return; + /* print CID, _before_ _every_ ring */ + if (!(m->mdmreg[REG_CIDONCE] & BIT_CIDONCE)) { + isdn_tty_at_cout("\r\nCALLER NUMBER: ", info); + isdn_tty_at_cout(dev->num[info->drv_index], info); + if (m->mdmreg[REG_CDN] & BIT_CDN) { + isdn_tty_at_cout("\r\nCALLED NUMBER: ", info); + isdn_tty_at_cout(info->emu.cpn, info); + } + } + } + isdn_tty_at_cout("\r\n", info); + isdn_tty_at_cout(msg[code], info); + switch (code) { + case RESULT_CONNECT: + switch (m->mdmreg[REG_L2PROT]) { + case ISDN_PROTO_L2_MODEM: + isdn_tty_at_cout(" ", info); + isdn_tty_at_cout(m->connmsg, info); + break; + } + break; + case RESULT_RING: + /* Append CPN, if enabled */ + if ((m->mdmreg[REG_CPN] & BIT_CPN)) { + sprintf(s, "/%s", m->cpn); + isdn_tty_at_cout(s, info); + } + /* Print CID only once, _after_ 1st RING */ + if ((m->mdmreg[REG_CIDONCE] & BIT_CIDONCE) && + (m->mdmreg[REG_RINGCNT] == 1)) { + isdn_tty_at_cout("\r\n", info); + isdn_tty_at_cout("CALLER NUMBER: ", info); + isdn_tty_at_cout(dev->num[info->drv_index], info); + if (m->mdmreg[REG_CDN] & BIT_CDN) { + isdn_tty_at_cout("\r\nCALLED NUMBER: ", info); + isdn_tty_at_cout(info->emu.cpn, info); + } + } + break; + case RESULT_NO_CARRIER: + case RESULT_NO_DIALTONE: + case RESULT_BUSY: + case RESULT_NO_ANSWER: + m->mdmreg[REG_RINGCNT] = 0; + /* Append Cause-Message if enabled */ + if (m->mdmreg[REG_RESPXT] & BIT_RESPXT) { + sprintf(s, "/%s", info->last_cause); + isdn_tty_at_cout(s, info); + } + break; + case RESULT_CONNECT64000: + /* Append Protocol to CONNECT message */ + switch (m->mdmreg[REG_L2PROT]) { + case ISDN_PROTO_L2_X75I: + case ISDN_PROTO_L2_X75UI: + case ISDN_PROTO_L2_X75BUI: + isdn_tty_at_cout("/X.75", info); + break; + case ISDN_PROTO_L2_HDLC: + isdn_tty_at_cout("/HDLC", info); + break; + case ISDN_PROTO_L2_V11096: + isdn_tty_at_cout("/V110/9600", info); + break; + case ISDN_PROTO_L2_V11019: + isdn_tty_at_cout("/V110/19200", info); + break; + case ISDN_PROTO_L2_V11038: + isdn_tty_at_cout("/V110/38400", info); + break; + } + if (m->mdmreg[REG_T70] & BIT_T70) { + isdn_tty_at_cout("/T.70", info); + if (m->mdmreg[REG_T70] & BIT_T70_EXT) + isdn_tty_at_cout("+", info); + } + break; + } + isdn_tty_at_cout("\r\n", info); + } + } + if (code == RESULT_NO_CARRIER) { + if ((info->flags & ISDN_ASYNC_CLOSING) || (!info->tty)) { + return; + } + tty_ldisc_flush(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); + } + } +} + + +/* + * 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) +{ + int limit = ISDN_MSNLEN - 1; + + while (((*p[0] >= '0' && *p[0] <= '9') || + /* Why a comma ??? */ + (*p[0] == ',') || (*p[0] == ':')) && + (limit--)) + *n++ = *p[0]++; + *n = '\0'; +} + +/* + * Get phone-number from modem-commandbuffer + */ +static void +isdn_tty_getdial(char *p, char *q,int cnt) +{ + int first = 1; + int limit = ISDN_MSNLEN - 1; /* MUST match the size of interface var to avoid + buffer overflow */ + + while (strchr(" 0123456789,#.*WPTS-", *p) && *p && --cnt>0) { + if ((*p >= '0' && *p <= '9') || ((*p == 'S') && first) || + (*p == '*') || (*p == '#')) { + *q++ = *p; + limit--; + } + if(!limit) + break; + p++; + first = 0; + } + *q = 0; +} + +#define PARSE_ERROR { isdn_tty_modem_result(RESULT_ERROR, info); return; } +#define PARSE_ERROR1 { isdn_tty_modem_result(RESULT_ERROR, info); return 1; } + +static void +isdn_tty_report(modem_info * info) +{ + atemu *m = &info->emu; + char s[80]; + + isdn_tty_at_cout("\r\nStatistics of last connection:\r\n\r\n", info); + sprintf(s, " Remote Number: %s\r\n", info->last_num); + isdn_tty_at_cout(s, info); + sprintf(s, " Direction: %s\r\n", info->last_dir ? "outgoing" : "incoming"); + isdn_tty_at_cout(s, info); + isdn_tty_at_cout(" Layer-2 Protocol: ", info); + switch (info->last_l2) { + case ISDN_PROTO_L2_X75I: + isdn_tty_at_cout("X.75i", info); + break; + case ISDN_PROTO_L2_X75UI: + isdn_tty_at_cout("X.75ui", info); + break; + case ISDN_PROTO_L2_X75BUI: + isdn_tty_at_cout("X.75bui", info); + break; + case ISDN_PROTO_L2_HDLC: + isdn_tty_at_cout("HDLC", info); + break; + case ISDN_PROTO_L2_V11096: + isdn_tty_at_cout("V.110 9600 Baud", info); + break; + case ISDN_PROTO_L2_V11019: + isdn_tty_at_cout("V.110 19200 Baud", info); + break; + case ISDN_PROTO_L2_V11038: + isdn_tty_at_cout("V.110 38400 Baud", info); + break; + case ISDN_PROTO_L2_TRANS: + isdn_tty_at_cout("transparent", info); + break; + case ISDN_PROTO_L2_MODEM: + isdn_tty_at_cout("modem", info); + break; + case ISDN_PROTO_L2_FAX: + isdn_tty_at_cout("fax", info); + break; + default: + isdn_tty_at_cout("unknown", info); + break; + } + if (m->mdmreg[REG_T70] & BIT_T70) { + isdn_tty_at_cout("/T.70", info); + if (m->mdmreg[REG_T70] & BIT_T70_EXT) + isdn_tty_at_cout("+", info); + } + isdn_tty_at_cout("\r\n", info); + isdn_tty_at_cout(" Service: ", info); + switch (info->last_si) { + case 1: + isdn_tty_at_cout("audio\r\n", info); + break; + case 5: + isdn_tty_at_cout("btx\r\n", info); + break; + case 7: + isdn_tty_at_cout("data\r\n", info); + break; + default: + sprintf(s, "%d\r\n", info->last_si); + isdn_tty_at_cout(s, info); + break; + } + sprintf(s, " Hangup location: %s\r\n", info->last_lhup ? "local" : "remote"); + isdn_tty_at_cout(s, info); + sprintf(s, " Last cause: %s\r\n", info->last_cause); + isdn_tty_at_cout(s, info); +} + +/* + * Parse AT&.. commands. + */ +static int +isdn_tty_cmd_ATand(char **p, modem_info * info) +{ + atemu *m = &info->emu; + int i; + char rb[100]; + +#define MAXRB (sizeof(rb) - 1) + + switch (*p[0]) { + case 'B': + /* &B - Set Buffersize */ + p[0]++; + i = isdn_getnum(p); + if ((i < 0) || (i > ISDN_SERIAL_XMIT_MAX)) + PARSE_ERROR1; +#ifdef CONFIG_ISDN_AUDIO + if ((m->mdmreg[REG_SI1] & 1) && (i > VBUF)) + PARSE_ERROR1; +#endif + m->mdmreg[REG_PSIZE] = i / 16; + info->xmit_size = m->mdmreg[REG_PSIZE] * 16; + switch (m->mdmreg[REG_L2PROT]) { + case ISDN_PROTO_L2_V11096: + case ISDN_PROTO_L2_V11019: + case ISDN_PROTO_L2_V11038: + info->xmit_size /= 10; + } + break; + case 'C': + /* &C - DCD Status */ + p[0]++; + switch (isdn_getnum(p)) { + case 0: + m->mdmreg[REG_DCD] &= ~BIT_DCD; + break; + case 1: + m->mdmreg[REG_DCD] |= BIT_DCD; + break; + default: + PARSE_ERROR1 + } + break; + case 'D': + /* &D - Set DTR-Low-behavior */ + p[0]++; + switch (isdn_getnum(p)) { + case 0: + m->mdmreg[REG_DTRHUP] &= ~BIT_DTRHUP; + m->mdmreg[REG_DTRR] &= ~BIT_DTRR; + break; + case 2: + m->mdmreg[REG_DTRHUP] |= BIT_DTRHUP; + m->mdmreg[REG_DTRR] &= ~BIT_DTRR; + break; + case 3: + m->mdmreg[REG_DTRHUP] |= BIT_DTRHUP; + m->mdmreg[REG_DTRR] |= BIT_DTRR; + 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]++; + if (info->msr & UART_MSR_DCD) + PARSE_ERROR1; + isdn_tty_reset_profile(m); + isdn_tty_modem_reset_regs(info, 1); + break; +#ifdef DUMMY_HAYES_AT + case 'K': + /* only for be compilant with common scripts */ + /* &K Flowcontrol - no function */ + p[0]++; + isdn_getnum(p); + break; +#endif + case 'L': + /* &L -Set Numbers to listen on */ + p[0]++; + i = 0; + while (*p[0] && (strchr("0123456789,-*[]?;", *p[0])) && + (i < ISDN_LMSNLEN)) + m->lmsn[i++] = *p[0]++; + m->lmsn[i] = '\0'; + break; + case 'R': + /* &R - Set V.110 bitrate adaption */ + p[0]++; + i = isdn_getnum(p); + switch (i) { + case 0: + /* Switch off V.110, back to X.75 */ + m->mdmreg[REG_L2PROT] = ISDN_PROTO_L2_X75I; + m->mdmreg[REG_SI2] = 0; + info->xmit_size = m->mdmreg[REG_PSIZE] * 16; + break; + case 9600: + m->mdmreg[REG_L2PROT] = ISDN_PROTO_L2_V11096; + m->mdmreg[REG_SI2] = 197; + info->xmit_size = m->mdmreg[REG_PSIZE] * 16 / 10; + break; + case 19200: + m->mdmreg[REG_L2PROT] = ISDN_PROTO_L2_V11019; + m->mdmreg[REG_SI2] = 199; + info->xmit_size = m->mdmreg[REG_PSIZE] * 16 / 10; + break; + case 38400: + m->mdmreg[REG_L2PROT] = ISDN_PROTO_L2_V11038; + m->mdmreg[REG_SI2] = 198; /* no existing standard for this */ + info->xmit_size = m->mdmreg[REG_PSIZE] * 16 / 10; + break; + default: + PARSE_ERROR1; + } + /* Switch off T.70 */ + m->mdmreg[REG_T70] &= ~(BIT_T70 | BIT_T70_EXT); + /* Set Service 7 */ + m->mdmreg[REG_SI1] |= 4; + break; + case 'S': + /* &S - Set Windowsize */ + p[0]++; + i = isdn_getnum(p); + if ((i > 0) && (i < 9)) + m->mdmreg[REG_WSIZE] = i; + else + PARSE_ERROR1; + break; + case 'V': + /* &V - Show registers */ + p[0]++; + isdn_tty_at_cout("\r\n", info); + for (i = 0; i < ISDN_MODEM_NUMREG; i++) { + sprintf(rb, "S%02d=%03d%s", i, + m->mdmreg[i], ((i + 1) % 10) ? " " : "\r\n"); + isdn_tty_at_cout(rb, info); + } + sprintf(rb, "\r\nEAZ/MSN: %.50s\r\n", + strlen(m->msn) ? m->msn : "None"); + isdn_tty_at_cout(rb, info); + if (strlen(m->lmsn)) { + isdn_tty_at_cout("\r\nListen: ", info); + isdn_tty_at_cout(m->lmsn, info); + isdn_tty_at_cout("\r\n", 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 and T.70 */ + p[0]++; + switch (isdn_getnum(p)) { + case 0: + m->mdmreg[REG_T70] &= ~(BIT_T70 | BIT_T70_EXT); + info->xmit_size = m->mdmreg[REG_PSIZE] * 16; + break; + case 1: + m->mdmreg[REG_T70] |= BIT_T70; + m->mdmreg[REG_T70] &= ~BIT_T70_EXT; + m->mdmreg[REG_L2PROT] = ISDN_PROTO_L2_X75I; + info->xmit_size = 112; + m->mdmreg[REG_SI1] = 4; + m->mdmreg[REG_SI2] = 0; + break; + case 2: + m->mdmreg[REG_T70] |= (BIT_T70 | BIT_T70_EXT); + m->mdmreg[REG_L2PROT] = ISDN_PROTO_L2_X75I; + info->xmit_size = 112; + m->mdmreg[REG_SI1] = 4; + m->mdmreg[REG_SI2] = 0; + break; + default: + PARSE_ERROR1; + } + break; + default: + PARSE_ERROR1; + } + return 0; +} + +static int +isdn_tty_check_ats(int mreg, int mval, modem_info * info, atemu * m) +{ + /* Some plausibility checks */ + switch (mreg) { + case REG_L2PROT: + if (mval > ISDN_PROTO_L2_MAX) + return 1; + break; + case REG_PSIZE: + if ((mval * 16) > ISDN_SERIAL_XMIT_MAX) + return 1; +#ifdef CONFIG_ISDN_AUDIO + if ((m->mdmreg[REG_SI1] & 1) && (mval > VBUFX)) + return 1; +#endif + info->xmit_size = mval * 16; + switch (m->mdmreg[REG_L2PROT]) { + case ISDN_PROTO_L2_V11096: + case ISDN_PROTO_L2_V11019: + case ISDN_PROTO_L2_V11038: + info->xmit_size /= 10; + } + break; + case REG_SI1I: + case REG_PLAN: + case REG_SCREEN: + /* readonly registers */ + return 1; + } + return 0; +} + +/* + * Perform ATS command + */ +static int +isdn_tty_cmd_ATS(char **p, modem_info * info) +{ + atemu *m = &info->emu; + int bitpos; + int mreg; + int mval; + int bval; + + mreg = isdn_getnum(p); + if (mreg < 0 || mreg >= ISDN_MODEM_NUMREG) + PARSE_ERROR1; + switch (*p[0]) { + case '=': + p[0]++; + mval = isdn_getnum(p); + if (mval < 0 || mval > 255) + PARSE_ERROR1; + if (isdn_tty_check_ats(mreg, mval, info, m)) + PARSE_ERROR1; + m->mdmreg[mreg] = mval; + break; + case '.': + /* Set/Clear a single bit */ + p[0]++; + bitpos = isdn_getnum(p); + if ((bitpos < 0) || (bitpos > 7)) + PARSE_ERROR1; + switch (*p[0]) { + case '=': + p[0]++; + bval = isdn_getnum(p); + if (bval < 0 || bval > 1) + PARSE_ERROR1; + if (bval) + mval = m->mdmreg[mreg] | (1 << bitpos); + else + mval = m->mdmreg[mreg] & ~(1 << bitpos); + if (isdn_tty_check_ats(mreg, mval, info, m)) + PARSE_ERROR1; + m->mdmreg[mreg] = mval; + break; + case '?': + p[0]++; + isdn_tty_at_cout("\r\n", info); + isdn_tty_at_cout((m->mdmreg[mreg] & (1 << bitpos)) ? "1" : "0", + info); + break; + default: + PARSE_ERROR1; + } + 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 */ + info->last_dir = 0; + strcpy(info->last_num, dev->num[info->drv_index]); + m->mdmreg[REG_RINGCNT] = 0; + info->msr &= ~UART_MSR_RI; + l2 = m->mdmreg[REG_L2PROT]; +#ifdef CONFIG_ISDN_AUDIO + /* If more than one bit set in reg18, autoselect Layer2 */ + if ((m->mdmreg[REG_SI1] & m->mdmreg[REG_SI1I]) != m->mdmreg[REG_SI1]) { + if (m->mdmreg[REG_SI1I] == 1) { + if ((l2 != ISDN_PROTO_L2_MODEM) && (l2 != ISDN_PROTO_L2_FAX)) + l2 = ISDN_PROTO_L2_TRANS; + } else + l2 = ISDN_PROTO_L2_X75I; + } +#endif + cmd.driver = info->isdn_driver; + cmd.command = ISDN_CMD_SETL2; + cmd.arg = info->isdn_channel + (l2 << 8); + info->last_l2 = l2; + isdn_command(&cmd); + cmd.driver = info->isdn_driver; + cmd.command = ISDN_CMD_SETL3; + cmd.arg = info->isdn_channel + (m->mdmreg[REG_L3PROT] << 8); +#ifdef CONFIG_ISDN_TTY_FAX + if (l2 == ISDN_PROTO_L2_FAX) { + cmd.parm.fax = info->fax; + info->fax->direction = ISDN_TTY_FAX_CONN_IN; + } +#endif + isdn_command(&cmd); + cmd.driver = info->isdn_driver; + cmd.arg = info->isdn_channel; + cmd.command = ISDN_CMD_ACCEPTD; + info->dialing = 16; + info->emu.carrierwait = 0; + isdn_command(&cmd); + isdn_timer_ctrl(ISDN_TIMER_CARRIER, 1); + } else + isdn_tty_modem_result(RESULT_NO_ANSWER, info); +} + +#ifdef CONFIG_ISDN_AUDIO +/* + * Parse AT+F.. commands + */ +static int +isdn_tty_cmd_PLUSF(char **p, modem_info * info) +{ + atemu *m = &info->emu; + 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[REG_SI1] & 1) ? 8 : 0); +#ifdef CONFIG_ISDN_TTY_FAX + if (TTY_IS_FCLASS2(info)) + sprintf(rs, "\r\n2"); + else if (TTY_IS_FCLASS1(info)) + sprintf(rs, "\r\n1"); +#endif + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + switch (*p[0]) { + case '0': + p[0]++; + m->mdmreg[REG_L2PROT] = ISDN_PROTO_L2_X75I; + m->mdmreg[REG_L3PROT] = ISDN_PROTO_L3_TRANS; + m->mdmreg[REG_SI1] = 4; + info->xmit_size = + m->mdmreg[REG_PSIZE] * 16; + break; +#ifdef CONFIG_ISDN_TTY_FAX + case '1': + p[0]++; + if (!(dev->global_features & + ISDN_FEATURE_L3_FCLASS1)) + PARSE_ERROR1; + m->mdmreg[REG_SI1] = 1; + m->mdmreg[REG_L2PROT] = ISDN_PROTO_L2_FAX; + m->mdmreg[REG_L3PROT] = ISDN_PROTO_L3_FCLASS1; + info->xmit_size = + m->mdmreg[REG_PSIZE] * 16; + break; + case '2': + p[0]++; + if (!(dev->global_features & + ISDN_FEATURE_L3_FCLASS2)) + PARSE_ERROR1; + m->mdmreg[REG_SI1] = 1; + m->mdmreg[REG_L2PROT] = ISDN_PROTO_L2_FAX; + m->mdmreg[REG_L3PROT] = ISDN_PROTO_L3_FCLASS2; + info->xmit_size = + m->mdmreg[REG_PSIZE] * 16; + break; +#endif + case '8': + p[0]++; + /* L2 will change on dialout with si=1 */ + m->mdmreg[REG_L2PROT] = ISDN_PROTO_L2_X75I; + m->mdmreg[REG_L3PROT] = ISDN_PROTO_L3_TRANS; + m->mdmreg[REG_SI1] = 5; + info->xmit_size = VBUF; + break; + case '?': + p[0]++; + strcpy(rs, "\r\n0,"); +#ifdef CONFIG_ISDN_TTY_FAX + if (dev->global_features & + ISDN_FEATURE_L3_FCLASS1) + strcat(rs, "1,"); + if (dev->global_features & + ISDN_FEATURE_L3_FCLASS2) + strcat(rs, "2,"); +#endif + strcat(rs, "8"); + isdn_tty_at_cout(rs, info); + break; + default: + PARSE_ERROR1; + } + break; + default: + PARSE_ERROR1; + } + return 0; + } +#ifdef CONFIG_ISDN_TTY_FAX + return (isdn_tty_cmd_PLUSF_FAX(p, info)); +#else + PARSE_ERROR1; +#endif +} + +/* + * Parse AT+V.. commands + */ +static int +isdn_tty_cmd_PLUSV(char **p, modem_info * info) +{ + atemu *m = &info->emu; + isdn_ctrl cmd; + static char *vcmd[] = + {"NH", "IP", "LS", "RX", "SD", "SM", "TX", "DD", 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(RESULT_NO_ANSWER, 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; + } + info->silence_state = isdn_audio_silence_init(info->silence_state); + if (!info->silence_state) { + printk(KERN_WARNING "isdn_tty: Couldn't malloc silence 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; + } + } +#ifdef ISDN_DEBUG_AT + printk(KERN_DEBUG "AT: +VRX\n"); +#endif + info->vonline |= 1; + isdn_tty_modem_result(RESULT_CONNECT, info); + return 0; + 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]++; + if ((*p[0]>='0') && (*p[0]<='9')) { + 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; + } else + if (*p[0] == '?') { + p[0]++; + isdn_tty_at_cout("\r\n<0-31>,<0-255>", + info); + break; + } else + 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)\r\n", + info); + isdn_tty_at_cout("6;ULAW;8;0;(8000)\r\n", + 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(RESULT_NO_ANSWER, 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; + } + } +#ifdef ISDN_DEBUG_AT + printk(KERN_DEBUG "AT: +VTX\n"); +#endif + m->lastDLE = 0; + info->vonline |= 2; + isdn_tty_modem_result(RESULT_CONNECT, info); + return 0; + break; + case 7: + /* AT+VDD - DTMF detection */ + switch (*p[0]) { + case '?': + p[0]++; + sprintf(rs, "\r\n<%d>,<%d>", + m->vpar[4], + m->vpar[5]); + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + if ((*p[0]>='0') && (*p[0]<='9')) { + if (info->online != 1) + PARSE_ERROR1; + par1 = isdn_getnum(p); + if ((par1 < 0) || (par1 > 15)) + PARSE_ERROR1; + if (*p[0] != ',') + PARSE_ERROR1; + p[0]++; + par2 = isdn_getnum(p); + if ((par2 < 0) || (par2 > 255)) + PARSE_ERROR1; + m->vpar[4] = par1; + m->vpar[5] = par2; + cmd.driver = info->isdn_driver; + cmd.command = ISDN_CMD_AUDIO; + cmd.arg = info->isdn_channel + (ISDN_AUDIO_SETDD << 8); + cmd.parm.num[0] = par1; + cmd.parm.num[1] = par2; + isdn_command(&cmd); + break; + } else + if (*p[0] == '?') { + p[0]++; + isdn_tty_at_cout("\r\n<0-15>,<0-255>", + info); + break; + } else + PARSE_ERROR1; + break; + default: + PARSE_ERROR1; + } + 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 ' ': + p++; + break; + case 'A': + /* A - Accept incoming call */ + p++; + isdn_tty_cmd_ATA(info); + return; + break; + case 'D': + /* D - Dial */ + if (info->msr & UART_MSR_DCD) + PARSE_ERROR; + if (info->msr & UART_MSR_RI) { + isdn_tty_modem_result(RESULT_NO_CARRIER, info); + return; + } + isdn_tty_getdial(++p, ds, sizeof ds); + p += strlen(p); + if (!strlen(m->msn)) + isdn_tty_modem_result(RESULT_NO_MSN_EAZ, info); + else if (strlen(ds)) + isdn_tty_dial(ds, info, m); + else + PARSE_ERROR; + return; + case 'E': + /* E - Turn Echo on/off */ + p++; + switch (isdn_getnum(&p)) { + case 0: + m->mdmreg[REG_ECHO] &= ~BIT_ECHO; + break; + case 1: + m->mdmreg[REG_ECHO] |= BIT_ECHO; + 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; + case '2': + p++; + isdn_tty_report(info); + break; + case '3': + p++; + sprintf(ds, "\r\n%d", info->emu.charge); + isdn_tty_at_cout(ds, info); + break; + default:; + } + break; +#ifdef DUMMY_HAYES_AT + case 'L': + case 'M': + /* only for be compilant with common scripts */ + /* no function */ + p++; + isdn_getnum(&p); + break; +#endif + case 'O': + /* O - Go online */ + p++; + if (info->msr & UART_MSR_DCD) + /* if B-Channel is up */ + isdn_tty_modem_result((m->mdmreg[REG_L2PROT] == ISDN_PROTO_L2_MODEM) ? RESULT_CONNECT:RESULT_CONNECT64000, info); + else + isdn_tty_modem_result(RESULT_NO_CARRIER, info); + return; + case 'Q': + /* Q - Turn Emulator messages on/off */ + p++; + switch (isdn_getnum(&p)) { + case 0: + m->mdmreg[REG_RESP] |= BIT_RESP; + break; + case 1: + m->mdmreg[REG_RESP] &= ~BIT_RESP; + 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[REG_RESP] |= BIT_RESPNUM; + break; + case 1: + m->mdmreg[REG_RESP] &= ~BIT_RESPNUM; + break; + default: + PARSE_ERROR; + } + break; + case 'Z': + /* Z - Load Registers from Profile */ + p++; + if (info->msr & UART_MSR_DCD) { + info->online = 0; + isdn_tty_on_hook(info); + } + isdn_tty_modem_reset_regs(info, 1); + break; + case '+': + p++; + switch (*p) { +#ifdef CONFIG_ISDN_AUDIO + case 'F': + p++; + if (isdn_tty_cmd_PLUSF(&p, info)) + return; + break; + case 'V': + if ((!(m->mdmreg[REG_SI1] & 1)) || + (m->mdmreg[REG_L2PROT] == ISDN_PROTO_L2_MODEM)) + PARSE_ERROR; + p++; + if (isdn_tty_cmd_PLUSV(&p, info)) + return; + break; +#endif /* CONFIG_ISDN_AUDIO */ + case 'S': /* SUSPEND */ + p++; + isdn_tty_get_msnstr(ds, &p); + isdn_tty_suspend(ds, info, m); + break; + case 'R': /* RESUME */ + p++; + isdn_tty_get_msnstr(ds, &p); + isdn_tty_resume(ds, info, m); + break; + case 'M': /* MESSAGE */ + p++; + isdn_tty_send_msg(info, m, p); + break; + default: + PARSE_ERROR; + } + break; + case '&': + p++; + if (isdn_tty_cmd_ATand(&p, info)) + return; + break; + default: + PARSE_ERROR; + } + } +#ifdef CONFIG_ISDN_AUDIO + if (!info->vonline) +#endif + isdn_tty_modem_result(RESULT_OK, 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) + */ +static int +isdn_tty_edit_at(const char *p, int count, modem_info * info) +{ + atemu *m = &info->emu; + int total = 0; + u_char c; + char eb[2]; + int cnt; + + for (cnt = count; cnt > 0; p++, cnt--) { + c = *p; + total++; + if (c == m->mdmreg[REG_CR] || c == m->mdmreg[REG_LF]) { + /* Separator (CR or LF) */ + m->mdmcmd[m->mdmcmdl] = 0; + if (m->mdmreg[REG_ECHO] & BIT_ECHO) { + eb[0] = c; + eb[1] = 0; + isdn_tty_at_cout(eb, info); + } + if ((m->mdmcmdl >= 2) && (!(strncmp(m->mdmcmd, "AT", 2)))) + isdn_tty_parse_at(info); + m->mdmcmdl = 0; + continue; + } + if (c == m->mdmreg[REG_BS] && m->mdmreg[REG_BS] < 128) { + /* Backspace-Function */ + if ((m->mdmcmdl > 2) || (!m->mdmcmdl)) { + if (m->mdmcmdl) + m->mdmcmdl--; + if (m->mdmreg[REG_ECHO] & BIT_ECHO) + isdn_tty_at_cout("\b", info); + } + continue; + } + if (cmdchar(c)) { + if (m->mdmreg[REG_ECHO] & BIT_ECHO) { + eb[0] = c; + eb[1] = 0; + isdn_tty_at_cout(eb, info); + } + if (m->mdmcmdl < 255) { + c = my_toupper(c); + switch (m->mdmcmdl) { + case 1: + if (c == 'T') { + m->mdmcmd[m->mdmcmdl] = c; + m->mdmcmd[++m->mdmcmdl] = 0; + break; + } else + m->mdmcmdl = 0; + /* Fall through, check for 'A' */ + case 0: + if (c == 'A') { + m->mdmcmd[m->mdmcmdl] = c; + m->mdmcmd[++m->mdmcmdl] = 0; + } + break; + default: + m->mdmcmd[m->mdmcmdl] = c; + m->mdmcmd[++m->mdmcmdl] = 0; + } + } + } + } + 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) && + time_after(jiffies , info->emu.lastplus + PLUSWAIT2)) { + info->emu.pluscount = 0; + info->online = 0; + isdn_tty_modem_result(RESULT_OK, 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(RESULT_RING, 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); +} + +/* + * Check all channels if we have a 'no carrier' timeout. + * Timeout value is set by Register S7. + */ +void +isdn_tty_carrier_timeout(void) +{ + int ton = 0; + int i; + + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + modem_info *info = &dev->mdm.info[i]; + if (info->dialing) { + if (info->emu.carrierwait++ > info->emu.mdmreg[REG_WAITC]) { + info->dialing = 0; + isdn_tty_modem_result(RESULT_NO_CARRIER, info); + isdn_tty_modem_hup(info, 1); + } + else + ton = 1; + } + } + isdn_timer_ctrl(ISDN_TIMER_CARRIER, ton); +} diff --git a/drivers/isdn/i4l/isdn_tty.h b/drivers/isdn/i4l/isdn_tty.h new file mode 100644 index 000000000000..2423a7ff0cc3 --- /dev/null +++ b/drivers/isdn/i4l/isdn_tty.h @@ -0,0 +1,122 @@ +/* $Id: isdn_tty.h,v 1.1.2.2 2004/01/12 22:37:19 keil Exp $ + * + * header for Linux ISDN subsystem, tty related functions (linklevel). + * + * Copyright 1994-1999 by Fritz Elfert (fritz@isdn4linux.de) + * Copyright 1995,96 by Thinking Objects Software GmbH Wuerzburg + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/config.h> + +#define DLE 0x10 +#define ETX 0x03 +#define DC4 0x14 + + +/* + * Definition of some special Registers of AT-Emulator + */ +#define REG_RINGATA 0 +#define REG_RINGCNT 1 /* ring counter register */ +#define REG_ESC 2 +#define REG_CR 3 +#define REG_LF 4 +#define REG_BS 5 + +#define REG_WAITC 7 + +#define REG_RESP 12 /* show response messages register */ +#define BIT_RESP 1 /* show response messages bit */ +#define REG_RESPNUM 12 /* show numeric responses register */ +#define BIT_RESPNUM 2 /* show numeric responses bit */ +#define REG_ECHO 12 +#define BIT_ECHO 4 +#define REG_DCD 12 +#define BIT_DCD 8 +#define REG_CTS 12 +#define BIT_CTS 16 +#define REG_DTRR 12 +#define BIT_DTRR 32 +#define REG_DSR 12 +#define BIT_DSR 64 +#define REG_CPPP 12 +#define BIT_CPPP 128 + +#define REG_DXMT 13 +#define BIT_DXMT 1 +#define REG_T70 13 +#define BIT_T70 2 +#define BIT_T70_EXT 32 +#define REG_DTRHUP 13 +#define BIT_DTRHUP 4 +#define REG_RESPXT 13 +#define BIT_RESPXT 8 +#define REG_CIDONCE 13 +#define BIT_CIDONCE 16 +#define REG_RUNG 13 /* show RUNG message register */ +#define BIT_RUNG 64 /* show RUNG message bit */ +#define REG_DISPLAY 13 +#define BIT_DISPLAY 128 + +#define REG_L2PROT 14 +#define REG_L3PROT 15 +#define REG_PSIZE 16 +#define REG_WSIZE 17 +#define REG_SI1 18 +#define REG_SI2 19 +#define REG_SI1I 20 +#define REG_PLAN 21 +#define REG_SCREEN 22 + +#define REG_CPN 23 +#define BIT_CPN 1 +#define REG_CPNFCON 23 +#define BIT_CPNFCON 2 +#define REG_CDN 23 +#define BIT_CDN 4 + +/* defines for result codes */ +#define RESULT_OK 0 +#define RESULT_CONNECT 1 +#define RESULT_RING 2 +#define RESULT_NO_CARRIER 3 +#define RESULT_ERROR 4 +#define RESULT_CONNECT64000 5 +#define RESULT_NO_DIALTONE 6 +#define RESULT_BUSY 7 +#define RESULT_NO_ANSWER 8 +#define RESULT_RINGING 9 +#define RESULT_NO_MSN_EAZ 10 +#define RESULT_VCON 11 +#define RESULT_RUNG 12 + +#define TTY_IS_FCLASS1(info) \ + ((info->emu.mdmreg[REG_L2PROT] == ISDN_PROTO_L2_FAX) && \ + (info->emu.mdmreg[REG_L3PROT] == ISDN_PROTO_L3_FCLASS1)) +#define TTY_IS_FCLASS2(info) \ + ((info->emu.mdmreg[REG_L2PROT] == ISDN_PROTO_L2_FAX) && \ + (info->emu.mdmreg[REG_L3PROT] == ISDN_PROTO_L3_FCLASS2)) + +extern void isdn_tty_modem_escape(void); +extern void isdn_tty_modem_ring(void); +extern void isdn_tty_carrier_timeout(void); +extern void isdn_tty_modem_xmit(void); +extern int isdn_tty_modem_init(void); +extern void isdn_tty_exit(void); +extern void isdn_tty_readmodem(void); +extern int isdn_tty_find_icall(int, int, setup_parm *); +extern void isdn_tty_cleanup_xmit(modem_info *); +extern int isdn_tty_stat_callback(int, isdn_ctrl *); +extern int isdn_tty_rcv_skb(int, int, int, struct sk_buff *); +extern int isdn_tty_capi_facility(capi_msg *cm); +extern void isdn_tty_at_cout(char *, modem_info *); +extern void isdn_tty_modem_hup(modem_info *, int); +#ifdef CONFIG_ISDN_TTY_FAX +extern int isdn_tty_cmd_PLUSF_FAX(char **, modem_info *); +extern int isdn_tty_fax_command(modem_info *, isdn_ctrl *); +extern void isdn_tty_fax_bitorder(modem_info *, struct sk_buff *); +#endif diff --git a/drivers/isdn/i4l/isdn_ttyfax.c b/drivers/isdn/i4l/isdn_ttyfax.c new file mode 100644 index 000000000000..ce2c3ef92e46 --- /dev/null +++ b/drivers/isdn/i4l/isdn_ttyfax.c @@ -0,0 +1,1122 @@ +/* $Id: isdn_ttyfax.c,v 1.1.2.2 2004/01/12 22:37:19 keil Exp $ + * + * Linux ISDN subsystem, tty_fax AT-command emulator (linklevel). + * + * Copyright 1999 by Armin Schindler (mac@melware.de) + * Copyright 1999 by Ralf Spachmann (mel@melware.de) + * Copyright 1999 by Cytronics & Melware + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#undef ISDN_TTY_FAX_STAT_DEBUG +#undef ISDN_TTY_FAX_CMD_DEBUG + +#include <linux/isdn.h> +#include "isdn_common.h" +#include "isdn_tty.h" +#include "isdn_ttyfax.h" + + +static char *isdn_tty_fax_revision = "$Revision: 1.1.2.2 $"; + +#define PARSE_ERROR1 { isdn_tty_fax_modem_result(1, info); return 1; } + +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; +} + +/* + * Fax Class 2 Modem results + * + */ + +static void +isdn_tty_fax_modem_result(int code, modem_info * info) +{ + atemu *m = &info->emu; + T30_s *f = info->fax; + char rs[50]; + char rss[50]; + char *rp; + int i; + static char *msg[] = + {"OK", "ERROR", "+FCON", "+FCSI:", "+FDIS:", + "+FHNG:", "+FDCS:", "CONNECT", "+FTSI:", + "+FCFR", "+FPTS:", "+FET:"}; + + + isdn_tty_at_cout("\r\n", info); + isdn_tty_at_cout(msg[code], info); + +#ifdef ISDN_TTY_FAX_CMD_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax send %s on ttyI%d\n", + msg[code], info->line); +#endif + switch (code) { + case 0: /* OK */ + break; + case 1: /* ERROR */ + break; + case 2: /* +FCON */ + /* Append CPN, if enabled */ + if ((m->mdmreg[REG_CPNFCON] & BIT_CPNFCON) && + (!(dev->usage[info->isdn_channel] & ISDN_USAGE_OUTGOING))) { + sprintf(rs, "/%s", m->cpn); + isdn_tty_at_cout(rs, info); + } + info->online = 1; + f->fet = 0; + if (f->phase == ISDN_FAX_PHASE_A) + f->phase = ISDN_FAX_PHASE_B; + break; + case 3: /* +FCSI */ + case 8: /* +FTSI */ + sprintf(rs, "\"%s\"", f->r_id); + isdn_tty_at_cout(rs, info); + break; + case 4: /* +FDIS */ + rs[0] = 0; + rp = &f->r_resolution; + for (i = 0; i < 8; i++) { + sprintf(rss, "%c%s", rp[i] + 48, + (i < 7) ? "," : ""); + strcat(rs, rss); + } + isdn_tty_at_cout(rs, info); +#ifdef ISDN_TTY_FAX_CMD_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax DIS=%s on ttyI%d\n", + rs, info->line); +#endif + break; + case 5: /* +FHNG */ + sprintf(rs, "%d", f->code); + isdn_tty_at_cout(rs, info); + info->faxonline = 0; + break; + case 6: /* +FDCS */ + rs[0] = 0; + rp = &f->r_resolution; + for (i = 0; i < 8; i++) { + sprintf(rss, "%c%s", rp[i] + 48, + (i < 7) ? "," : ""); + strcat(rs, rss); + } + isdn_tty_at_cout(rs, info); +#ifdef ISDN_TTY_FAX_CMD_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax DCS=%s on ttyI%d\n", + rs, info->line); +#endif + break; + case 7: /* CONNECT */ + info->faxonline |= 2; + break; + case 9: /* FCFR */ + break; + case 10: /* FPTS */ + isdn_tty_at_cout("1", info); + break; + case 11: /* FET */ + sprintf(rs, "%d", f->fet); + isdn_tty_at_cout(rs, info); + break; + } + + isdn_tty_at_cout("\r\n", info); + + switch (code) { + case 7: /* CONNECT */ + info->online = 2; + if (info->faxonline & 1) { + sprintf(rs, "%c", XON); + isdn_tty_at_cout(rs, info); + } + break; + } +} + +int +isdn_tty_fax_command1(modem_info * info, isdn_ctrl * c) +{ + static char *msg[] = + {"OK", "CONNECT", "NO CARRIER", "ERROR", "FCERROR"}; + +#ifdef ISDN_TTY_FAX_CMD_DEBUG + printk(KERN_DEBUG "isdn_tty: FCLASS1 cmd(%d)\n", c->parm.aux.cmd); +#endif + if (c->parm.aux.cmd < ISDN_FAX_CLASS1_QUERY) { + if (info->online) + info->online = 1; + isdn_tty_at_cout("\r\n", info); + isdn_tty_at_cout(msg[c->parm.aux.cmd], info); + isdn_tty_at_cout("\r\n", info); + } + switch (c->parm.aux.cmd) { + case ISDN_FAX_CLASS1_CONNECT: + info->online = 2; + break; + case ISDN_FAX_CLASS1_OK: + case ISDN_FAX_CLASS1_FCERROR: + case ISDN_FAX_CLASS1_ERROR: + case ISDN_FAX_CLASS1_NOCARR: + break; + case ISDN_FAX_CLASS1_QUERY: + isdn_tty_at_cout("\r\n", info); + if (!c->parm.aux.para[0]) { + isdn_tty_at_cout(msg[ISDN_FAX_CLASS1_ERROR], info); + isdn_tty_at_cout("\r\n", info); + } else { + isdn_tty_at_cout(c->parm.aux.para, info); + isdn_tty_at_cout("\r\nOK\r\n", info); + } + break; + } + return (0); +} + +int +isdn_tty_fax_command(modem_info * info, isdn_ctrl * c) +{ + T30_s *f = info->fax; + char rs[10]; + + if (TTY_IS_FCLASS1(info)) + return (isdn_tty_fax_command1(info, c)); + +#ifdef ISDN_TTY_FAX_CMD_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax cmd %d on ttyI%d\n", + f->r_code, info->line); +#endif + switch (f->r_code) { + case ISDN_TTY_FAX_FCON: + info->faxonline = 1; + isdn_tty_fax_modem_result(2, info); /* +FCON */ + return (0); + case ISDN_TTY_FAX_FCON_I: + info->faxonline = 16; + isdn_tty_fax_modem_result(2, info); /* +FCON */ + return (0); + case ISDN_TTY_FAX_RID: + if (info->faxonline & 1) + isdn_tty_fax_modem_result(3, info); /* +FCSI */ + if (info->faxonline & 16) + isdn_tty_fax_modem_result(8, info); /* +FTSI */ + return (0); + case ISDN_TTY_FAX_DIS: + isdn_tty_fax_modem_result(4, info); /* +FDIS */ + return (0); + case ISDN_TTY_FAX_HNG: + if (f->phase == ISDN_FAX_PHASE_C) { + if (f->direction == ISDN_TTY_FAX_CONN_IN) { + sprintf(rs, "%c%c", DLE, ETX); + isdn_tty_at_cout(rs, info); + } else { + sprintf(rs, "%c", 0x18); + isdn_tty_at_cout(rs, info); + } + info->faxonline &= ~2; /* leave data mode */ + info->online = 1; + } + f->phase = ISDN_FAX_PHASE_E; + isdn_tty_fax_modem_result(5, info); /* +FHNG */ + isdn_tty_fax_modem_result(0, info); /* OK */ + return (0); + case ISDN_TTY_FAX_DCS: + isdn_tty_fax_modem_result(6, info); /* +FDCS */ + isdn_tty_fax_modem_result(7, info); /* CONNECT */ + f->phase = ISDN_FAX_PHASE_C; + return (0); + case ISDN_TTY_FAX_TRAIN_OK: + isdn_tty_fax_modem_result(6, info); /* +FDCS */ + isdn_tty_fax_modem_result(0, info); /* OK */ + return (0); + case ISDN_TTY_FAX_SENT: + isdn_tty_fax_modem_result(0, info); /* OK */ + return (0); + case ISDN_TTY_FAX_CFR: + isdn_tty_fax_modem_result(9, info); /* +FCFR */ + return (0); + case ISDN_TTY_FAX_ET: + sprintf(rs, "%c%c", DLE, ETX); + isdn_tty_at_cout(rs, info); + isdn_tty_fax_modem_result(10, info); /* +FPTS */ + isdn_tty_fax_modem_result(11, info); /* +FET */ + isdn_tty_fax_modem_result(0, info); /* OK */ + info->faxonline &= ~2; /* leave data mode */ + info->online = 1; + f->phase = ISDN_FAX_PHASE_D; + return (0); + case ISDN_TTY_FAX_PTS: + isdn_tty_fax_modem_result(10, info); /* +FPTS */ + if (f->direction == ISDN_TTY_FAX_CONN_OUT) { + if (f->fet == 1) + f->phase = ISDN_FAX_PHASE_B; + if (f->fet == 0) + isdn_tty_fax_modem_result(0, info); /* OK */ + } + return (0); + case ISDN_TTY_FAX_EOP: + info->faxonline &= ~2; /* leave data mode */ + info->online = 1; + f->phase = ISDN_FAX_PHASE_D; + return (0); + + } + return (-1); +} + + +void +isdn_tty_fax_bitorder(modem_info * info, struct sk_buff *skb) +{ + __u8 LeftMask; + __u8 RightMask; + __u8 fBit; + __u8 Data; + int i; + + if (!info->fax->bor) { + for (i = 0; i < skb->len; i++) { + Data = skb->data[i]; + for ( + LeftMask = 0x80, RightMask = 0x01; + LeftMask > RightMask; + LeftMask >>= 1, RightMask <<= 1 + ) { + fBit = (Data & LeftMask); + if (Data & RightMask) + Data |= LeftMask; + else + Data &= ~LeftMask; + if (fBit) + Data |= RightMask; + else + Data &= ~RightMask; + + } + skb->data[i] = Data; + } + } +} + +/* + * Parse AT+F.. FAX class 1 commands + */ + +int +isdn_tty_cmd_FCLASS1(char **p, modem_info * info) +{ + static char *cmd[] = + {"AE", "TS", "RS", "TM", "RM", "TH", "RH"}; + isdn_ctrl c; + int par, i; + u_long flags; + + for (c.parm.aux.cmd = 0; c.parm.aux.cmd < 7; c.parm.aux.cmd++) + if (!strncmp(p[0], cmd[c.parm.aux.cmd], 2)) + break; + +#ifdef ISDN_TTY_FAX_CMD_DEBUG + printk(KERN_DEBUG "isdn_tty_cmd_FCLASS1 (%s,%d)\n", p[0], c.parm.aux.cmd); +#endif + if (c.parm.aux.cmd == 7) + PARSE_ERROR1; + + p[0] += 2; + switch (*p[0]) { + case '?': + p[0]++; + c.parm.aux.subcmd = AT_QUERY; + break; + case '=': + p[0]++; + if (*p[0] == '?') { + p[0]++; + c.parm.aux.subcmd = AT_EQ_QUERY; + } else { + par = isdn_getnum(p); + if ((par < 0) || (par > 255)) + PARSE_ERROR1; + c.parm.aux.subcmd = AT_EQ_VALUE; + c.parm.aux.para[0] = par; + } + break; + case 0: + c.parm.aux.subcmd = AT_COMMAND; + break; + default: + PARSE_ERROR1; + } + c.command = ISDN_CMD_FAXCMD; +#ifdef ISDN_TTY_FAX_CMD_DEBUG + printk(KERN_DEBUG "isdn_tty_cmd_FCLASS1 %d/%d/%d)\n", + c.parm.aux.cmd, c.parm.aux.subcmd, c.parm.aux.para[0]); +#endif + if (info->isdn_driver < 0) { + if ((c.parm.aux.subcmd == AT_EQ_VALUE) || + (c.parm.aux.subcmd == AT_COMMAND)) { + PARSE_ERROR1; + } + spin_lock_irqsave(&dev->lock, flags); + /* get a temporary connection to the first free fax driver */ + i = isdn_get_free_channel(ISDN_USAGE_FAX, ISDN_PROTO_L2_FAX, + ISDN_PROTO_L3_FCLASS1, -1, -1, "00"); + if (i < 0) { + spin_unlock_irqrestore(&dev->lock, flags); + PARSE_ERROR1; + } + info->isdn_driver = dev->drvmap[i]; + info->isdn_channel = dev->chanmap[i]; + info->drv_index = i; + dev->m_idx[i] = info->line; + spin_unlock_irqrestore(&dev->lock, flags); + c.driver = info->isdn_driver; + c.arg = info->isdn_channel; + isdn_command(&c); + spin_lock_irqsave(&dev->lock, flags); + isdn_free_channel(info->isdn_driver, info->isdn_channel, + ISDN_USAGE_FAX); + info->isdn_driver = -1; + info->isdn_channel = -1; + if (info->drv_index >= 0) { + dev->m_idx[info->drv_index] = -1; + info->drv_index = -1; + } + spin_unlock_irqrestore(&dev->lock, flags); + } else { + c.driver = info->isdn_driver; + c.arg = info->isdn_channel; + isdn_command(&c); + } + return 1; +} + +/* + * Parse AT+F.. FAX class 2 commands + */ + +int +isdn_tty_cmd_FCLASS2(char **p, modem_info * info) +{ + atemu *m = &info->emu; + T30_s *f = info->fax; + isdn_ctrl cmd; + int par; + char rs[50]; + char rss[50]; + int maxdccval[] = + {1, 5, 2, 2, 3, 2, 0, 7}; + + /* FAA still unchanged */ + if (!strncmp(p[0], "AA", 2)) { /* TODO */ + p[0] += 2; + switch (*p[0]) { + case '?': + p[0]++; + sprintf(rs, "\r\n%d", 0); + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + par = isdn_getnum(p); + if ((par < 0) || (par > 255)) + PARSE_ERROR1; + break; + default: + PARSE_ERROR1; + } + return 0; + } + /* BADLIN=value - dummy 0=disable errorchk disabled, 1-255 nr. of lines for making page bad */ + if (!strncmp(p[0], "BADLIN", 6)) { + p[0] += 6; + switch (*p[0]) { + case '?': + p[0]++; + sprintf(rs, "\r\n%d", f->badlin); + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + if (*p[0] == '?') { + p[0]++; + sprintf(rs, "\r\n0-255"); + isdn_tty_at_cout(rs, info); + } else { + par = isdn_getnum(p); + if ((par < 0) || (par > 255)) + PARSE_ERROR1; + f->badlin = par; +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax FBADLIN=%d\n", par); +#endif + } + break; + default: + PARSE_ERROR1; + } + return 0; + } + /* BADMUL=value - dummy 0=disable errorchk disabled (treshold multiplier) */ + if (!strncmp(p[0], "BADMUL", 6)) { + p[0] += 6; + switch (*p[0]) { + case '?': + p[0]++; + sprintf(rs, "\r\n%d", f->badmul); + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + if (*p[0] == '?') { + p[0]++; + sprintf(rs, "\r\n0-255"); + isdn_tty_at_cout(rs, info); + } else { + par = isdn_getnum(p); + if ((par < 0) || (par > 255)) + PARSE_ERROR1; + f->badmul = par; +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax FBADMUL=%d\n", par); +#endif + } + break; + default: + PARSE_ERROR1; + } + return 0; + } + /* BOR=n - Phase C bit order, 0=direct, 1=reverse */ + if (!strncmp(p[0], "BOR", 3)) { + p[0] += 3; + switch (*p[0]) { + case '?': + p[0]++; + sprintf(rs, "\r\n%d", f->bor); + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + if (*p[0] == '?') { + p[0]++; + sprintf(rs, "\r\n0,1"); + isdn_tty_at_cout(rs, info); + } else { + par = isdn_getnum(p); + if ((par < 0) || (par > 1)) + PARSE_ERROR1; + f->bor = par; +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax FBOR=%d\n", par); +#endif + } + break; + default: + PARSE_ERROR1; + } + return 0; + } + /* NBC=n - No Best Capabilities */ + if (!strncmp(p[0], "NBC", 3)) { + p[0] += 3; + switch (*p[0]) { + case '?': + p[0]++; + sprintf(rs, "\r\n%d", f->nbc); + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + if (*p[0] == '?') { + p[0]++; + sprintf(rs, "\r\n0,1"); + isdn_tty_at_cout(rs, info); + } else { + par = isdn_getnum(p); + if ((par < 0) || (par > 1)) + PARSE_ERROR1; + f->nbc = par; +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax FNBC=%d\n", par); +#endif + } + break; + default: + PARSE_ERROR1; + } + return 0; + } + /* BUF? - Readonly buffersize readout */ + if (!strncmp(p[0], "BUF?", 4)) { + p[0] += 4; +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax FBUF? (%d) \n", (16 * m->mdmreg[REG_PSIZE])); +#endif + p[0]++; + sprintf(rs, "\r\n %d ", (16 * m->mdmreg[REG_PSIZE])); + isdn_tty_at_cout(rs, info); + return 0; + } + /* CIG=string - local fax station id string for polling rx */ + if (!strncmp(p[0], "CIG", 3)) { + int i, r; + p[0] += 3; + switch (*p[0]) { + case '?': + p[0]++; + sprintf(rs, "\r\n\"%s\"", f->pollid); + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + if (*p[0] == '?') { + p[0]++; + sprintf(rs, "\r\n\"STRING\""); + isdn_tty_at_cout(rs, info); + } else { + if (*p[0] == '"') + p[0]++; + for (i = 0; (*p[0]) && i < (FAXIDLEN - 1) && (*p[0] != '"'); i++) { + f->pollid[i] = *p[0]++; + } + if (*p[0] == '"') + p[0]++; + for (r = i; r < FAXIDLEN; r++) { + f->pollid[r] = 32; + } + f->pollid[FAXIDLEN - 1] = 0; +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax local poll ID rx \"%s\"\n", f->pollid); +#endif + } + break; + default: + PARSE_ERROR1; + } + return 0; + } + /* CQ=n - copy qlty chk, 0= no chk, 1=only 1D chk, 2=1D+2D chk */ + if (!strncmp(p[0], "CQ", 2)) { + p[0] += 2; + switch (*p[0]) { + case '?': + p[0]++; + sprintf(rs, "\r\n%d", f->cq); + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + if (*p[0] == '?') { + p[0]++; + sprintf(rs, "\r\n0,1,2"); + isdn_tty_at_cout(rs, info); + } else { + par = isdn_getnum(p); + if ((par < 0) || (par > 2)) + PARSE_ERROR1; + f->cq = par; +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax FCQ=%d\n", par); +#endif + } + break; + default: + PARSE_ERROR1; + } + return 0; + } + /* CR=n - can receive? 0= no data rx or poll remote dev, 1=do receive data or poll remote dev */ + if (!strncmp(p[0], "CR", 2)) { + p[0] += 2; + switch (*p[0]) { + case '?': + p[0]++; + sprintf(rs, "\r\n%d", f->cr); /* read actual value from struct and print */ + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + if (*p[0] == '?') { + p[0]++; + sprintf(rs, "\r\n0,1"); /* display online help */ + isdn_tty_at_cout(rs, info); + } else { + par = isdn_getnum(p); + if ((par < 0) || (par > 1)) + PARSE_ERROR1; + f->cr = par; +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax FCR=%d\n", par); +#endif + } + break; + default: + PARSE_ERROR1; + } + return 0; + } + /* CTCRTY=value - ECM retry count */ + if (!strncmp(p[0], "CTCRTY", 6)) { + p[0] += 6; + switch (*p[0]) { + case '?': + p[0]++; + sprintf(rs, "\r\n%d", f->ctcrty); + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + if (*p[0] == '?') { + p[0]++; + sprintf(rs, "\r\n0-255"); + isdn_tty_at_cout(rs, info); + } else { + par = isdn_getnum(p); + if ((par < 0) || (par > 255)) + PARSE_ERROR1; + f->ctcrty = par; +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax FCTCRTY=%d\n", par); +#endif + } + break; + default: + PARSE_ERROR1; + } + return 0; + } + /* DCC=vr,br,wd,ln,df,ec,bf,st - DCE capabilities parms */ + if (!strncmp(p[0], "DCC", 3)) { + char *rp = &f->resolution; + int i; + + p[0] += 3; + switch (*p[0]) { + case '?': + p[0]++; + strcpy(rs, "\r\n"); + for (i = 0; i < 8; i++) { + sprintf(rss, "%c%s", rp[i] + 48, + (i < 7) ? "," : ""); + strcat(rs, rss); + } + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + if (*p[0] == '?') { + isdn_tty_at_cout("\r\n(0,1),(0-5),(0-2),(0-2),(0-3),(0-2),(0),(0-7)", info); + p[0]++; + } else { + for (i = 0; (((*p[0] >= '0') && (*p[0] <= '9')) || (*p[0] == ',')) && (i < 8); i++) { + if (*p[0] != ',') { + if ((*p[0] - 48) > maxdccval[i]) { + PARSE_ERROR1; + } + rp[i] = *p[0] - 48; + p[0]++; + if (*p[0] == ',') + p[0]++; + } else + p[0]++; + } +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax FDCC capabilities DCE=%d,%d,%d,%d,%d,%d,%d,%d\n", + rp[0], rp[1], rp[2], rp[3], rp[4], rp[5], rp[6], rp[7]); +#endif + } + break; + default: + PARSE_ERROR1; + } + return 0; + } + /* DIS=vr,br,wd,ln,df,ec,bf,st - current session parms */ + if (!strncmp(p[0], "DIS", 3)) { + char *rp = &f->resolution; + int i; + + p[0] += 3; + switch (*p[0]) { + case '?': + p[0]++; + strcpy(rs, "\r\n"); + for (i = 0; i < 8; i++) { + sprintf(rss, "%c%s", rp[i] + 48, + (i < 7) ? "," : ""); + strcat(rs, rss); + } + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + if (*p[0] == '?') { + isdn_tty_at_cout("\r\n(0,1),(0-5),(0-2),(0-2),(0-3),(0-2),(0),(0-7)", info); + p[0]++; + } else { + for (i = 0; (((*p[0] >= '0') && (*p[0] <= '9')) || (*p[0] == ',')) && (i < 8); i++) { + if (*p[0] != ',') { + if ((*p[0] - 48) > maxdccval[i]) { + PARSE_ERROR1; + } + rp[i] = *p[0] - 48; + p[0]++; + if (*p[0] == ',') + p[0]++; + } else + p[0]++; + } +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax FDIS session parms=%d,%d,%d,%d,%d,%d,%d,%d\n", + rp[0], rp[1], rp[2], rp[3], rp[4], rp[5], rp[6], rp[7]); +#endif + } + break; + default: + PARSE_ERROR1; + } + return 0; + } + /* DR - Receive Phase C data command, initiates document reception */ + if (!strncmp(p[0], "DR", 2)) { + p[0] += 2; + if ((info->faxonline & 16) && /* incoming connection */ + ((f->phase == ISDN_FAX_PHASE_B) || (f->phase == ISDN_FAX_PHASE_D))) { +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax FDR\n"); +#endif + f->code = ISDN_TTY_FAX_DR; + cmd.driver = info->isdn_driver; + cmd.arg = info->isdn_channel; + cmd.command = ISDN_CMD_FAXCMD; + isdn_command(&cmd); + if (f->phase == ISDN_FAX_PHASE_B) { + f->phase = ISDN_FAX_PHASE_C; + } else if (f->phase == ISDN_FAX_PHASE_D) { + switch (f->fet) { + case 0: /* next page will be received */ + f->phase = ISDN_FAX_PHASE_C; + isdn_tty_fax_modem_result(7, info); /* CONNECT */ + break; + case 1: /* next doc will be received */ + f->phase = ISDN_FAX_PHASE_B; + break; + case 2: /* fax session is terminating */ + f->phase = ISDN_FAX_PHASE_E; + break; + default: + PARSE_ERROR1; + } + } + } else { + PARSE_ERROR1; + } + return 1; + } + /* DT=df,vr,wd,ln - TX phase C data command (release DCE to proceed with negotiation) */ + if (!strncmp(p[0], "DT", 2)) { + int i, val[] = + {4, 0, 2, 3}; + char *rp = &f->resolution; + + p[0] += 2; + if (!info->faxonline & 1) /* not outgoing connection */ + PARSE_ERROR1; + + for (i = 0; (((*p[0] >= '0') && (*p[0] <= '9')) || (*p[0] == ',')) && (i < 4); i++) { + if (*p[0] != ',') { + if ((*p[0] - 48) > maxdccval[val[i]]) { + PARSE_ERROR1; + } + rp[val[i]] = *p[0] - 48; + p[0]++; + if (*p[0] == ',') + p[0]++; + } else + p[0]++; + } +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax FDT tx data command parms=%d,%d,%d,%d\n", + rp[4], rp[0], rp[2], rp[3]); +#endif + if ((f->phase == ISDN_FAX_PHASE_B) || (f->phase == ISDN_FAX_PHASE_D)) { + f->code = ISDN_TTY_FAX_DT; + cmd.driver = info->isdn_driver; + cmd.arg = info->isdn_channel; + cmd.command = ISDN_CMD_FAXCMD; + isdn_command(&cmd); + if (f->phase == ISDN_FAX_PHASE_D) { + f->phase = ISDN_FAX_PHASE_C; + isdn_tty_fax_modem_result(7, info); /* CONNECT */ + } + } else { + PARSE_ERROR1; + } + return 1; + } + /* ECM=n - Error mode control 0=disabled, 2=enabled, handled by DCE alone incl. buff of partial pages */ + if (!strncmp(p[0], "ECM", 3)) { + p[0] += 3; + switch (*p[0]) { + case '?': + p[0]++; + sprintf(rs, "\r\n%d", f->ecm); + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + if (*p[0] == '?') { + p[0]++; + sprintf(rs, "\r\n0,2"); + isdn_tty_at_cout(rs, info); + } else { + par = isdn_getnum(p); + if ((par != 0) && (par != 2)) + PARSE_ERROR1; + f->ecm = par; +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax FECM=%d\n", par); +#endif + } + break; + default: + PARSE_ERROR1; + } + return 0; + } + /* ET=n - End of page or document */ + if (!strncmp(p[0], "ET=", 3)) { + p[0] += 3; + if (*p[0] == '?') { + p[0]++; + sprintf(rs, "\r\n0-2"); + isdn_tty_at_cout(rs, info); + } else { + if ((f->phase != ISDN_FAX_PHASE_D) || (!info->faxonline & 1)) + PARSE_ERROR1; + par = isdn_getnum(p); + if ((par < 0) || (par > 2)) + PARSE_ERROR1; + f->fet = par; + f->code = ISDN_TTY_FAX_ET; + cmd.driver = info->isdn_driver; + cmd.arg = info->isdn_channel; + cmd.command = ISDN_CMD_FAXCMD; + isdn_command(&cmd); +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax FET=%d\n", par); +#endif + return 1; + } + return 0; + } + /* K - terminate */ + if (!strncmp(p[0], "K", 1)) { + p[0] += 1; + if ((f->phase == ISDN_FAX_PHASE_IDLE) || (f->phase == ISDN_FAX_PHASE_E)) + PARSE_ERROR1; + isdn_tty_modem_hup(info, 1); + return 1; + } + /* LID=string - local fax ID */ + if (!strncmp(p[0], "LID", 3)) { + int i, r; + p[0] += 3; + switch (*p[0]) { + case '?': + p[0]++; + sprintf(rs, "\r\n\"%s\"", f->id); + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + if (*p[0] == '?') { + p[0]++; + sprintf(rs, "\r\n\"STRING\""); + isdn_tty_at_cout(rs, info); + } else { + if (*p[0] == '"') + p[0]++; + for (i = 0; (*p[0]) && i < (FAXIDLEN - 1) && (*p[0] != '"'); i++) { + f->id[i] = *p[0]++; + } + if (*p[0] == '"') + p[0]++; + for (r = i; r < FAXIDLEN; r++) { + f->id[r] = 32; + } + f->id[FAXIDLEN - 1] = 0; +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax local ID \"%s\"\n", f->id); +#endif + } + break; + default: + PARSE_ERROR1; + } + return 0; + } + + /* MDL? - DCE Model */ + if (!strncmp(p[0], "MDL?", 4)) { + p[0] += 4; +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: FMDL?\n"); +#endif + isdn_tty_at_cout("\r\nisdn4linux", info); + return 0; + } + /* MFR? - DCE Manufacturer */ + if (!strncmp(p[0], "MFR?", 4)) { + p[0] += 4; +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: FMFR?\n"); +#endif + isdn_tty_at_cout("\r\nisdn4linux", info); + return 0; + } + /* MINSP=n - Minimum Speed for Phase C */ + if (!strncmp(p[0], "MINSP", 5)) { + p[0] += 5; + switch (*p[0]) { + case '?': + p[0]++; + sprintf(rs, "\r\n%d", f->minsp); + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + if (*p[0] == '?') { + p[0]++; + sprintf(rs, "\r\n0-5"); + isdn_tty_at_cout(rs, info); + } else { + par = isdn_getnum(p); + if ((par < 0) || (par > 5)) + PARSE_ERROR1; + f->minsp = par; +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax FMINSP=%d\n", par); +#endif + } + break; + default: + PARSE_ERROR1; + } + return 0; + } + /* PHCTO=value - DTE phase C timeout */ + if (!strncmp(p[0], "PHCTO", 5)) { + p[0] += 5; + switch (*p[0]) { + case '?': + p[0]++; + sprintf(rs, "\r\n%d", f->phcto); + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + if (*p[0] == '?') { + p[0]++; + sprintf(rs, "\r\n0-255"); + isdn_tty_at_cout(rs, info); + } else { + par = isdn_getnum(p); + if ((par < 0) || (par > 255)) + PARSE_ERROR1; + f->phcto = par; +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax FPHCTO=%d\n", par); +#endif + } + break; + default: + PARSE_ERROR1; + } + return 0; + } + + /* REL=n - Phase C received EOL alignment */ + if (!strncmp(p[0], "REL", 3)) { + p[0] += 3; + switch (*p[0]) { + case '?': + p[0]++; + sprintf(rs, "\r\n%d", f->rel); + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + if (*p[0] == '?') { + p[0]++; + sprintf(rs, "\r\n0,1"); + isdn_tty_at_cout(rs, info); + } else { + par = isdn_getnum(p); + if ((par < 0) || (par > 1)) + PARSE_ERROR1; + f->rel = par; +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax FREL=%d\n", par); +#endif + } + break; + default: + PARSE_ERROR1; + } + return 0; + } + /* REV? - DCE Revision */ + if (!strncmp(p[0], "REV?", 4)) { + p[0] += 4; +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: FREV?\n"); +#endif + strcpy(rss, isdn_tty_fax_revision); + sprintf(rs, "\r\nRev: %s", isdn_getrev(rss)); + isdn_tty_at_cout(rs, info); + return 0; + } + + /* Phase C Transmit Data Block Size */ + if (!strncmp(p[0], "TBC=", 4)) { /* dummy, not used */ + p[0] += 4; +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax FTBC=%c\n", *p[0]); +#endif + switch (*p[0]) { + case '0': + p[0]++; + break; + default: + PARSE_ERROR1; + } + return 0; + } + printk(KERN_DEBUG "isdn_tty: unknown token=>AT+F%s<\n", p[0]); + PARSE_ERROR1; +} + +int +isdn_tty_cmd_PLUSF_FAX(char **p, modem_info * info) +{ + if (TTY_IS_FCLASS2(info)) + return (isdn_tty_cmd_FCLASS2(p, info)); + else if (TTY_IS_FCLASS1(info)) + return (isdn_tty_cmd_FCLASS1(p, info)); + PARSE_ERROR1; +} diff --git a/drivers/isdn/i4l/isdn_ttyfax.h b/drivers/isdn/i4l/isdn_ttyfax.h new file mode 100644 index 000000000000..757a89010020 --- /dev/null +++ b/drivers/isdn/i4l/isdn_ttyfax.h @@ -0,0 +1,18 @@ +/* $Id: isdn_ttyfax.h,v 1.1.2.2 2004/01/12 22:37:19 keil Exp $ + * + * header for Linux ISDN subsystem, tty_fax related functions (linklevel). + * + * Copyright 1999 by Armin Schindler (mac@melware.de) + * Copyright 1999 by Ralf Spachmann (mel@melware.de) + * Copyright 1999 by Cytronics & Melware + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + + +#define XON 0x11 +#define XOFF 0x13 +#define DC2 0x12 + diff --git a/drivers/isdn/i4l/isdn_v110.c b/drivers/isdn/i4l/isdn_v110.c new file mode 100644 index 000000000000..f47f2b9846d8 --- /dev/null +++ b/drivers/isdn/i4l/isdn_v110.c @@ -0,0 +1,617 @@ +/* $Id: isdn_v110.c,v 1.1.2.2 2004/01/12 22:37:19 keil Exp $ + * + * Linux ISDN subsystem, V.110 related functions (linklevel). + * + * Copyright by Thomas Pfeiffer (pfeiffer@pds.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/string.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/delay.h> + +#include <linux/isdn.h> +#include "isdn_v110.h" + +#undef ISDN_V110_DEBUG + +char *isdn_v110_revision = "$Revision: 1.1.2.2 $"; + +#define V110_38400 255 +#define V110_19200 15 +#define V110_9600 3 + +/* + * The following data are precoded matrices, online and offline matrix + * for 9600, 19200 und 38400, respectively + */ +static unsigned char V110_OnMatrix_9600[] = +{0xfc, 0xfc, 0xfc, 0xfc, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xff, + 0xff, 0xfd, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xfd, + 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xff, + 0xff, 0xfd, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xfd}; + +static unsigned char V110_OffMatrix_9600[] = +{0xfc, 0xfc, 0xfc, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + +static unsigned char V110_OnMatrix_19200[] = +{0xf0, 0xf0, 0xff, 0xf7, 0xff, 0xf7, 0xff, 0xf7, 0xff, 0xf7, + 0xfd, 0xff, 0xff, 0xf7, 0xff, 0xf7, 0xff, 0xf7, 0xff, 0xf7}; + +static unsigned char V110_OffMatrix_19200[] = +{0xf0, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + +static unsigned char V110_OnMatrix_38400[] = +{0x00, 0x7f, 0x7f, 0x7f, 0x7f, 0xfd, 0x7f, 0x7f, 0x7f, 0x7f}; + +static unsigned char V110_OffMatrix_38400[] = +{0x00, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xff}; + +/* + * FlipBits reorders sequences of keylen bits in one byte. + * E.g. source order 7654321 will be converted to 45670123 when keylen = 4, + * and to 67452301 when keylen = 2. This is necessary because ordering on + * the isdn line is the other way. + */ +static __inline unsigned char +FlipBits(unsigned char c, int keylen) +{ + unsigned char b = c; + unsigned char bit = 128; + int i; + int j; + int hunks = (8 / keylen); + + c = 0; + for (i = 0; i < hunks; i++) { + for (j = 0; j < keylen; j++) { + if (b & (bit >> j)) + c |= bit >> (keylen - j - 1); + } + bit >>= keylen; + } + return c; +} + + +/* isdn_v110_open allocates and initializes private V.110 data + * structures and returns a pointer to these. + */ +static isdn_v110_stream * +isdn_v110_open(unsigned char key, int hdrlen, int maxsize) +{ + int i; + isdn_v110_stream *v; + + if ((v = kmalloc(sizeof(isdn_v110_stream), GFP_ATOMIC)) == NULL) + return NULL; + memset(v, 0, sizeof(isdn_v110_stream)); + v->key = key; + v->nbits = 0; + for (i = 0; key & (1 << i); i++) + v->nbits++; + + v->nbytes = 8 / v->nbits; + v->decodelen = 0; + + switch (key) { + case V110_38400: + v->OnlineFrame = V110_OnMatrix_38400; + v->OfflineFrame = V110_OffMatrix_38400; + break; + case V110_19200: + v->OnlineFrame = V110_OnMatrix_19200; + v->OfflineFrame = V110_OffMatrix_19200; + break; + default: + v->OnlineFrame = V110_OnMatrix_9600; + v->OfflineFrame = V110_OffMatrix_9600; + break; + } + v->framelen = v->nbytes * 10; + v->SyncInit = 5; + v->introducer = 0; + v->dbit = 1; + v->b = 0; + v->skbres = hdrlen; + v->maxsize = maxsize - hdrlen; + if ((v->encodebuf = kmalloc(maxsize, GFP_ATOMIC)) == NULL) { + kfree(v); + return NULL; + } + return v; +} + +/* isdn_v110_close frees private V.110 data structures */ +void +isdn_v110_close(isdn_v110_stream * v) +{ + if (v == NULL) + return; +#ifdef ISDN_V110_DEBUG + printk(KERN_DEBUG "v110 close\n"); +#endif + kfree(v->encodebuf); + kfree(v); +} + + +/* + * ValidHeaderBytes return the number of valid bytes in v->decodebuf + */ +static int +ValidHeaderBytes(isdn_v110_stream * v) +{ + int i; + for (i = 0; (i < v->decodelen) && (i < v->nbytes); i++) + if ((v->decodebuf[i] & v->key) != 0) + break; + return i; +} + +/* + * SyncHeader moves the decodebuf ptr to the next valid header + */ +static void +SyncHeader(isdn_v110_stream * v) +{ + unsigned char *rbuf = v->decodebuf; + int len = v->decodelen; + + if (len == 0) + return; + for (rbuf++, len--; len > 0; len--, rbuf++) /* such den SyncHeader in buf ! */ + if ((*rbuf & v->key) == 0) /* erstes byte gefunden ? */ + break; /* jupp! */ + if (len) + memcpy(v->decodebuf, rbuf, len); + + v->decodelen = len; +#ifdef ISDN_V110_DEBUG + printk(KERN_DEBUG "isdn_v110: Header resync\n"); +#endif +} + +/* DecodeMatrix takes n (n>=1) matrices (v110 frames, 10 bytes) where + len is the number of matrix-lines. len must be a multiple of 10, i.e. + only complete matices must be given. + From these, netto data is extracted and returned in buf. The return-value + is the bytecount of the decoded data. + */ +static int +DecodeMatrix(isdn_v110_stream * v, unsigned char *m, int len, unsigned char *buf) +{ + int line = 0; + int buflen = 0; + int mbit = 64; + int introducer = v->introducer; + int dbit = v->dbit; + unsigned char b = v->b; + + while (line < len) { /* Are we done with all lines of the matrix? */ + if ((line % 10) == 0) { /* the 0. line of the matrix is always 0 ! */ + if (m[line] != 0x00) { /* not 0 ? -> error! */ +#ifdef ISDN_V110_DEBUG + printk(KERN_DEBUG "isdn_v110: DecodeMatrix, V110 Bad Header\n"); + /* returning now is not the right thing, though :-( */ +#endif + } + line++; /* next line of matrix */ + continue; + } else if ((line % 10) == 5) { /* in line 5 there's only e-bits ! */ + if ((m[line] & 0x70) != 0x30) { /* 011 has to be at the beginning! */ +#ifdef ISDN_V110_DEBUG + printk(KERN_DEBUG "isdn_v110: DecodeMatrix, V110 Bad 5th line\n"); + /* returning now is not the right thing, though :-( */ +#endif + } + line++; /* next line */ + continue; + } else if (!introducer) { /* every byte starts with 10 (stopbit, startbit) */ + introducer = (m[line] & mbit) ? 0 : 1; /* current bit of the matrix */ + next_byte: + if (mbit > 2) { /* was it the last bit in this line ? */ + mbit >>= 1; /* no -> take next */ + continue; + } /* otherwise start with leftmost bit in the next line */ + mbit = 64; + line++; + continue; + } else { /* otherwise we need to set a data bit */ + if (m[line] & mbit) /* was that bit set in the matrix ? */ + b |= dbit; /* yes -> set it in the data byte */ + else + b &= dbit - 1; /* no -> clear it in the data byte */ + if (dbit < 128) /* is that data byte done ? */ + dbit <<= 1; /* no, got the next bit */ + else { /* data byte is done */ + buf[buflen++] = b; /* copy byte into the output buffer */ + introducer = b = 0; /* init of the intro sequence and of the data byte */ + dbit = 1; /* next we look for the 0th bit */ + } + goto next_byte; /* look for next bit in the matrix */ + } + } + v->introducer = introducer; + v->dbit = dbit; + v->b = b; + return buflen; /* return number of bytes in the output buffer */ +} + +/* + * DecodeStream receives V.110 coded data from the input stream. It recovers the + * original frames. + * The input stream doesn't need to be framed + */ +struct sk_buff * +isdn_v110_decode(isdn_v110_stream * v, struct sk_buff *skb) +{ + int i; + int j; + int len; + unsigned char *v110_buf; + unsigned char *rbuf; + + if (!skb) { + printk(KERN_WARNING "isdn_v110_decode called with NULL skb!\n"); + return NULL; + } + rbuf = skb->data; + len = skb->len; + if (v == NULL) { + /* invalid handle, no chance to proceed */ + printk(KERN_WARNING "isdn_v110_decode called with NULL stream!\n"); + dev_kfree_skb(skb); + return NULL; + } + if (v->decodelen == 0) /* cache empty? */ + for (; len > 0; len--, rbuf++) /* scan for SyncHeader in buf */ + if ((*rbuf & v->key) == 0) + break; /* found first byte */ + if (len == 0) { + dev_kfree_skb(skb); + return NULL; + } + /* copy new data to decode-buffer */ + memcpy(&(v->decodebuf[v->decodelen]), rbuf, len); + v->decodelen += len; + ReSync: + if (v->decodelen < v->nbytes) { /* got a new header ? */ + dev_kfree_skb(skb); + return NULL; /* no, try later */ + } + if (ValidHeaderBytes(v) != v->nbytes) { /* is that a valid header? */ + SyncHeader(v); /* no -> look for header */ + goto ReSync; + } + len = (v->decodelen - (v->decodelen % (10 * v->nbytes))) / v->nbytes; + if ((v110_buf = kmalloc(len, GFP_ATOMIC)) == NULL) { + printk(KERN_WARNING "isdn_v110_decode: Couldn't allocate v110_buf\n"); + dev_kfree_skb(skb); + return NULL; + } + for (i = 0; i < len; i++) { + v110_buf[i] = 0; + for (j = 0; j < v->nbytes; j++) + v110_buf[i] |= (v->decodebuf[(i * v->nbytes) + j] & v->key) << (8 - ((j + 1) * v->nbits)); + v110_buf[i] = FlipBits(v110_buf[i], v->nbits); + } + v->decodelen = (v->decodelen % (10 * v->nbytes)); + memcpy(v->decodebuf, &(v->decodebuf[len * v->nbytes]), v->decodelen); + + skb_trim(skb, DecodeMatrix(v, v110_buf, len, skb->data)); + kfree(v110_buf); + if (skb->len) + return skb; + else { + kfree_skb(skb); + return NULL; + } +} + +/* EncodeMatrix takes input data in buf, len is the bytecount. + Data is encoded into v110 frames in m. Return value is the number of + matrix-lines generated. + */ +static int +EncodeMatrix(unsigned char *buf, int len, unsigned char *m, int mlen) +{ + int line = 0; + int i = 0; + int mbit = 128; + int dbit = 1; + int introducer = 3; + int ibit[] = {0, 1, 1}; + + while ((i < len) && (line < mlen)) { /* while we still have input data */ + switch (line % 10) { /* in which line of the matrix are we? */ + case 0: + m[line++] = 0x00; /* line 0 is always 0 */ + mbit = 128; /* go on with the 7th bit */ + break; + case 5: + m[line++] = 0xbf; /* line 5 is always 10111111 */ + mbit = 128; /* go on with the 7th bit */ + break; + } + if (line >= mlen) { + printk(KERN_WARNING "isdn_v110 (EncodeMatrix): buffer full!\n"); + return line; + } + next_bit: + switch (mbit) { /* leftmost or rightmost bit ? */ + case 1: + line++; /* rightmost -> go to next line */ + if (line >= mlen) { + printk(KERN_WARNING "isdn_v110 (EncodeMatrix): buffer full!\n"); + return line; + } + case 128: + m[line] = 128; /* leftmost -> set byte to 1000000 */ + mbit = 64; /* current bit in the matrix line */ + continue; + } + if (introducer) { /* set 110 sequence ? */ + introducer--; /* set on digit less */ + m[line] |= ibit[introducer] ? mbit : 0; /* set corresponding bit */ + mbit >>= 1; /* bit of matrix line >> 1 */ + goto next_bit; /* and go on there */ + } /* else push data bits into the matrix! */ + m[line] |= (buf[i] & dbit) ? mbit : 0; /* set data bit in matrix */ + if (dbit == 128) { /* was it the last one? */ + dbit = 1; /* then go on with first bit of */ + i++; /* next byte in input buffer */ + if (i < len) /* input buffer done ? */ + introducer = 3; /* no, write introducer 110 */ + else { /* input buffer done ! */ + m[line] |= (mbit - 1) & 0xfe; /* set remaining bits in line to 1 */ + break; + } + } else /* not the last data bit */ + dbit <<= 1; /* then go to next data bit */ + mbit >>= 1; /* go to next bit of matrix */ + goto next_bit; + + } + /* if necessary, generate remaining lines of the matrix... */ + if ((line) && ((line + 10) < mlen)) + switch (++line % 10) { + case 1: + m[line++] = 0xfe; + case 2: + m[line++] = 0xfe; + case 3: + m[line++] = 0xfe; + case 4: + m[line++] = 0xfe; + case 5: + m[line++] = 0xbf; + case 6: + m[line++] = 0xfe; + case 7: + m[line++] = 0xfe; + case 8: + m[line++] = 0xfe; + case 9: + m[line++] = 0xfe; + } + return line; /* that's how many lines we have */ +} + +/* + * Build a sync frame. + */ +static struct sk_buff * +isdn_v110_sync(isdn_v110_stream *v) +{ + struct sk_buff *skb; + + if (v == NULL) { + /* invalid handle, no chance to proceed */ + printk(KERN_WARNING "isdn_v110_sync called with NULL stream!\n"); + return NULL; + } + if ((skb = dev_alloc_skb(v->framelen + v->skbres))) { + skb_reserve(skb, v->skbres); + memcpy(skb_put(skb, v->framelen), v->OfflineFrame, v->framelen); + } + return skb; +} + +/* + * Build an idle frame. + */ +static struct sk_buff * +isdn_v110_idle(isdn_v110_stream *v) +{ + struct sk_buff *skb; + + if (v == NULL) { + /* invalid handle, no chance to proceed */ + printk(KERN_WARNING "isdn_v110_sync called with NULL stream!\n"); + return NULL; + } + if ((skb = dev_alloc_skb(v->framelen + v->skbres))) { + skb_reserve(skb, v->skbres); + memcpy(skb_put(skb, v->framelen), v->OnlineFrame, v->framelen); + } + return skb; +} + +struct sk_buff * +isdn_v110_encode(isdn_v110_stream * v, struct sk_buff *skb) +{ + int i; + int j; + int rlen; + int mlen; + int olen; + int size; + int sval1; + int sval2; + int nframes; + unsigned char *v110buf; + unsigned char *rbuf; + struct sk_buff *nskb; + + if (v == NULL) { + /* invalid handle, no chance to proceed */ + printk(KERN_WARNING "isdn_v110_encode called with NULL stream!\n"); + return NULL; + } + if (!skb) { + /* invalid skb, no chance to proceed */ + printk(KERN_WARNING "isdn_v110_encode called with NULL skb!\n"); + return NULL; + } + rlen = skb->len; + nframes = (rlen + 3) / 4; + v110buf = v->encodebuf; + if ((nframes * 40) > v->maxsize) { + size = v->maxsize; + rlen = v->maxsize / 40; + } else + size = nframes * 40; + if (!(nskb = dev_alloc_skb(size + v->skbres + sizeof(int)))) { + printk(KERN_WARNING "isdn_v110_encode: Couldn't alloc skb\n"); + return NULL; + } + skb_reserve(nskb, v->skbres + sizeof(int)); + if (skb->len == 0) { + memcpy(skb_put(nskb, v->framelen), v->OnlineFrame, v->framelen); + *((int *)skb_push(nskb, sizeof(int))) = 0; + return nskb; + } + mlen = EncodeMatrix(skb->data, rlen, v110buf, size); + /* now distribute 2 or 4 bits each to the output stream! */ + rbuf = skb_put(nskb, size); + olen = 0; + sval1 = 8 - v->nbits; + sval2 = v->key << sval1; + for (i = 0; i < mlen; i++) { + v110buf[i] = FlipBits(v110buf[i], v->nbits); + for (j = 0; j < v->nbytes; j++) { + if (size--) + *rbuf++ = ~v->key | (((v110buf[i] << (j * v->nbits)) & sval2) >> sval1); + else { + printk(KERN_WARNING "isdn_v110_encode: buffers full!\n"); + goto buffer_full; + } + olen++; + } + } +buffer_full: + skb_trim(nskb, olen); + *((int *)skb_push(nskb, sizeof(int))) = rlen; + return nskb; +} + +int +isdn_v110_stat_callback(int idx, isdn_ctrl * c) +{ + isdn_v110_stream *v = NULL; + int i; + int ret; + + if (idx < 0) + return 0; + switch (c->command) { + case ISDN_STAT_BSENT: + /* Keep the send-queue of the driver filled + * with frames: + * If number of outstanding frames < 3, + * send down an Idle-Frame (or an Sync-Frame, if + * v->SyncInit != 0). + */ + if (!(v = dev->v110[idx])) + return 0; + atomic_inc(&dev->v110use[idx]); + for (i=0; i * v->framelen < c->parm.length; i++) { + if (v->skbidle > 0) { + v->skbidle--; + ret = 1; + } else { + if (v->skbuser > 0) + v->skbuser--; + ret = 0; + } + } + for (i = v->skbuser + v->skbidle; i < 2; i++) { + struct sk_buff *skb; + if (v->SyncInit > 0) + skb = isdn_v110_sync(v); + else + skb = isdn_v110_idle(v); + if (skb) { + if (dev->drv[c->driver]->interface->writebuf_skb(c->driver, c->arg, 1, skb) <= 0) { + dev_kfree_skb(skb); + break; + } else { + if (v->SyncInit) + v->SyncInit--; + v->skbidle++; + } + } else + break; + } + atomic_dec(&dev->v110use[idx]); + return ret; + case ISDN_STAT_DHUP: + case ISDN_STAT_BHUP: + while (1) { + atomic_inc(&dev->v110use[idx]); + if (atomic_dec_and_test(&dev->v110use[idx])) { + isdn_v110_close(dev->v110[idx]); + dev->v110[idx] = NULL; + break; + } + mdelay(1); + } + break; + case ISDN_STAT_BCONN: + if (dev->v110emu[idx] && (dev->v110[idx] == NULL)) { + int hdrlen = dev->drv[c->driver]->interface->hl_hdrlen; + int maxsize = dev->drv[c->driver]->interface->maxbufsize; + atomic_inc(&dev->v110use[idx]); + switch (dev->v110emu[idx]) { + case ISDN_PROTO_L2_V11096: + dev->v110[idx] = isdn_v110_open(V110_9600, hdrlen, maxsize); + break; + case ISDN_PROTO_L2_V11019: + dev->v110[idx] = isdn_v110_open(V110_19200, hdrlen, maxsize); + break; + case ISDN_PROTO_L2_V11038: + dev->v110[idx] = isdn_v110_open(V110_38400, hdrlen, maxsize); + break; + default:; + } + if ((v = dev->v110[idx])) { + while (v->SyncInit) { + struct sk_buff *skb = isdn_v110_sync(v); + if (dev->drv[c->driver]->interface->writebuf_skb(c->driver, c->arg, 1, skb) <= 0) { + dev_kfree_skb(skb); + /* Unable to send, try later */ + break; + } + v->SyncInit--; + v->skbidle++; + } + } else + printk(KERN_WARNING "isdn_v110: Couldn't open stream for chan %d\n", idx); + atomic_dec(&dev->v110use[idx]); + } + break; + default: + return 0; + } + return 0; +} diff --git a/drivers/isdn/i4l/isdn_v110.h b/drivers/isdn/i4l/isdn_v110.h new file mode 100644 index 000000000000..08f274bbc438 --- /dev/null +++ b/drivers/isdn/i4l/isdn_v110.h @@ -0,0 +1,29 @@ +/* $Id: isdn_v110.h,v 1.1.2.2 2004/01/12 22:37:19 keil Exp $ + * + * Linux ISDN subsystem, V.110 related functions (linklevel). + * + * Copyright by Thomas Pfeiffer (pfeiffer@pds.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#ifndef _isdn_v110_h_ +#define _isdn_v110_h_ + +/* + * isdn_v110_encode will take raw data and encode it using V.110 + */ +extern struct sk_buff *isdn_v110_encode(isdn_v110_stream *, struct sk_buff *); + +/* + * isdn_v110_decode receives V.110 coded data from the stream and rebuilds + * frames from them. The source stream doesn't need to be framed. + */ +extern struct sk_buff *isdn_v110_decode(isdn_v110_stream *, struct sk_buff *); + +extern int isdn_v110_stat_callback(int, isdn_ctrl *); +extern void isdn_v110_close(isdn_v110_stream * v); + +#endif diff --git a/drivers/isdn/i4l/isdn_x25iface.c b/drivers/isdn/i4l/isdn_x25iface.c new file mode 100644 index 000000000000..4ab7600cf9c1 --- /dev/null +++ b/drivers/isdn/i4l/isdn_x25iface.c @@ -0,0 +1,328 @@ +/* $Id: isdn_x25iface.c,v 1.1.2.2 2004/01/12 22:37:19 keil Exp $ + * + * Linux ISDN subsystem, X.25 related functions + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * stuff needed to support the Linux X.25 PLP code on top of devices that + * can provide a lab_b service using the concap_proto mechanism. + * This module supports a network interface wich provides lapb_sematics + * -- as defined in Documentation/networking/x25-iface.txt -- to + * the upper layer and assumes that the lower layer provides a reliable + * data link service by means of the concap_device_ops callbacks. + * + * Only protocol specific stuff goes here. Device specific stuff + * goes to another -- device related -- concap_proto support source file. + * + */ + +/* #include <linux/isdn.h> */ +#include <linux/netdevice.h> +#include <linux/concap.h> +#include <linux/wanrouter.h> +#include <net/x25device.h> +#include "isdn_x25iface.h" + +/* for debugging messages not to cause an oops when device pointer is NULL*/ +#define MY_DEVNAME(dev) ( (dev) ? (dev)->name : "DEVICE UNSPECIFIED" ) + + +typedef struct isdn_x25iface_proto_data { + int magic; + enum wan_states state; + /* Private stuff, not to be accessed via proto_data. We provide the + other storage for the concap_proto instance here as well, + enabling us to allocate both with just one kmalloc(): */ + struct concap_proto priv; +} ix25_pdata_t; + + + +/* is now in header file (extern): struct concap_proto * isdn_x25iface_proto_new(void); */ +void isdn_x25iface_proto_del( struct concap_proto * ); +int isdn_x25iface_proto_close( struct concap_proto * ); +int isdn_x25iface_proto_restart( struct concap_proto *, + struct net_device *, + struct concap_device_ops *); +int isdn_x25iface_xmit( struct concap_proto *, struct sk_buff * ); +int isdn_x25iface_receive( struct concap_proto *, struct sk_buff * ); +int isdn_x25iface_connect_ind( struct concap_proto * ); +int isdn_x25iface_disconn_ind( struct concap_proto * ); + + +static struct concap_proto_ops ix25_pops = { + &isdn_x25iface_proto_new, + &isdn_x25iface_proto_del, + &isdn_x25iface_proto_restart, + &isdn_x25iface_proto_close, + &isdn_x25iface_xmit, + &isdn_x25iface_receive, + &isdn_x25iface_connect_ind, + &isdn_x25iface_disconn_ind +}; + +/* error message helper function */ +static void illegal_state_warn( unsigned state, unsigned char firstbyte) +{ + printk( KERN_WARNING "isdn_x25iface: firstbyte %x illegal in" + "current state %d\n",firstbyte, state ); +} + +/* check protocol data field for consistency */ +static int pdata_is_bad( ix25_pdata_t * pda ){ + + if( pda && pda -> magic == ISDN_X25IFACE_MAGIC ) return 0; + printk( KERN_WARNING + "isdn_x25iface_xxx: illegal pointer to proto data\n" ); + return 1; +} + +/* create a new x25 interface protocol instance + */ +struct concap_proto * isdn_x25iface_proto_new(void) +{ + ix25_pdata_t * tmp = kmalloc(sizeof(ix25_pdata_t),GFP_KERNEL); + IX25DEBUG("isdn_x25iface_proto_new\n"); + if( tmp ){ + tmp -> magic = ISDN_X25IFACE_MAGIC; + tmp -> state = WAN_UNCONFIGURED; + /* private data space used to hold the concap_proto data. + Only to be accessed via the returned pointer */ + spin_lock_init(&tmp->priv.lock); + tmp -> priv.dops = NULL; + tmp -> priv.net_dev = NULL; + tmp -> priv.pops = &ix25_pops; + tmp -> priv.flags = 0; + tmp -> priv.proto_data = tmp; + return( &(tmp -> priv) ); + } + return NULL; +}; + +/* close the x25iface encapsulation protocol + */ +int isdn_x25iface_proto_close(struct concap_proto *cprot){ + + ix25_pdata_t *tmp; + int ret = 0; + ulong flags; + + if( ! cprot ){ + printk( KERN_ERR "isdn_x25iface_proto_close: " + "invalid concap_proto pointer\n" ); + return -1; + } + IX25DEBUG( "isdn_x25iface_proto_close %s \n", MY_DEVNAME(cprot -> net_dev) ); + spin_lock_irqsave(&cprot->lock, flags); + cprot -> dops = NULL; + cprot -> net_dev = NULL; + tmp = cprot -> proto_data; + if( pdata_is_bad( tmp ) ){ + ret = -1; + } else { + tmp -> state = WAN_UNCONFIGURED; + } + spin_unlock_irqrestore(&cprot->lock, flags); + return ret; +} + +/* Delete the x25iface encapsulation protocol instance + */ +void isdn_x25iface_proto_del(struct concap_proto *cprot){ + + ix25_pdata_t * tmp; + + IX25DEBUG( "isdn_x25iface_proto_del \n" ); + if( ! cprot ){ + printk( KERN_ERR "isdn_x25iface_proto_del: " + "concap_proto pointer is NULL\n" ); + return; + } + tmp = cprot -> proto_data; + if( tmp == NULL ){ + printk( KERN_ERR "isdn_x25iface_proto_del: inconsistent " + "proto_data pointer (maybe already deleted?)\n"); + return; + } + /* close if the protocol is still open */ + if( cprot -> dops ) isdn_x25iface_proto_close(cprot); + /* freeing the storage should be sufficient now. But some additional + settings might help to catch wild pointer bugs */ + tmp -> magic = 0; + cprot -> proto_data = NULL; + + kfree( tmp ); + return; +} + +/* (re-)initialize the data structures for x25iface encapsulation + */ +int isdn_x25iface_proto_restart(struct concap_proto *cprot, + struct net_device *ndev, + struct concap_device_ops *dops) +{ + ix25_pdata_t * pda = cprot -> proto_data ; + ulong flags; + + IX25DEBUG( "isdn_x25iface_proto_restart %s \n", MY_DEVNAME(ndev) ); + + if ( pdata_is_bad( pda ) ) return -1; + + if( !( dops && dops -> data_req && dops -> connect_req + && dops -> disconn_req ) ){ + printk( KERN_WARNING "isdn_x25iface_restart: required dops" + " missing\n" ); + isdn_x25iface_proto_close(cprot); + return -1; + } + spin_lock_irqsave(&cprot->lock, flags); + cprot -> net_dev = ndev; + cprot -> pops = &ix25_pops; + cprot -> dops = dops; + pda -> state = WAN_DISCONNECTED; + spin_unlock_irqrestore(&cprot->lock, flags); + return 0; +} + +/* deliver a dl_data frame received from i4l HL driver to the network layer + */ +int isdn_x25iface_receive(struct concap_proto *cprot, struct sk_buff *skb) +{ + IX25DEBUG( "isdn_x25iface_receive %s \n", MY_DEVNAME(cprot->net_dev) ); + if ( ( (ix25_pdata_t*) (cprot->proto_data) ) + -> state == WAN_CONNECTED ){ + if( skb_push(skb, 1)){ + skb -> data[0]=0x00; + skb->protocol = x25_type_trans(skb, cprot->net_dev); + netif_rx(skb); + return 0; + } + } + printk(KERN_WARNING "isdn_x25iface_receive %s: not connected, skb dropped\n", MY_DEVNAME(cprot->net_dev) ); + dev_kfree_skb(skb); + return -1; +} + +/* a connection set up is indicated by lower layer + */ +int isdn_x25iface_connect_ind(struct concap_proto *cprot) +{ + struct sk_buff * skb = dev_alloc_skb(1); + enum wan_states *state_p + = &( ( (ix25_pdata_t*) (cprot->proto_data) ) -> state); + IX25DEBUG( "isdn_x25iface_connect_ind %s \n" + , MY_DEVNAME(cprot->net_dev) ); + if( *state_p == WAN_UNCONFIGURED ){ + printk(KERN_WARNING + "isdn_x25iface_connect_ind while unconfigured %s\n" + , MY_DEVNAME(cprot->net_dev) ); + return -1; + } + *state_p = WAN_CONNECTED; + if( skb ){ + *( skb_put(skb, 1) ) = 0x01; + skb->protocol = x25_type_trans(skb, cprot->net_dev); + netif_rx(skb); + return 0; + } else { + printk(KERN_WARNING "isdn_x25iface_connect_ind: " + " out of memory -- disconnecting\n"); + cprot -> dops -> disconn_req(cprot); + return -1; + } +} + +/* a disconnect is indicated by lower layer + */ +int isdn_x25iface_disconn_ind(struct concap_proto *cprot) +{ + struct sk_buff *skb; + enum wan_states *state_p + = &( ( (ix25_pdata_t*) (cprot->proto_data) ) -> state); + IX25DEBUG( "isdn_x25iface_disconn_ind %s \n", MY_DEVNAME(cprot -> net_dev) ); + if( *state_p == WAN_UNCONFIGURED ){ + printk(KERN_WARNING + "isdn_x25iface_disconn_ind while unconfigured\n"); + return -1; + } + if(! cprot -> net_dev) return -1; + *state_p = WAN_DISCONNECTED; + skb = dev_alloc_skb(1); + if( skb ){ + *( skb_put(skb, 1) ) = 0x02; + skb->protocol = x25_type_trans(skb, cprot->net_dev); + netif_rx(skb); + return 0; + } else { + printk(KERN_WARNING "isdn_x25iface_disconn_ind:" + " out of memory\n"); + return -1; + } +} + +/* process a frame handed over to us from linux network layer. First byte + semantics as defined in Documentation/networking/x25-iface.txt + */ +int isdn_x25iface_xmit(struct concap_proto *cprot, struct sk_buff *skb) +{ + unsigned char firstbyte = skb->data[0]; + enum wan_states *state = &((ix25_pdata_t*)cprot->proto_data)->state; + int ret = 0; + IX25DEBUG( "isdn_x25iface_xmit: %s first=%x state=%d \n", MY_DEVNAME(cprot -> net_dev), firstbyte, *state ); + switch ( firstbyte ){ + case 0x00: /* dl_data request */ + if( *state == WAN_CONNECTED ){ + skb_pull(skb, 1); + cprot -> net_dev -> trans_start = jiffies; + ret = ( cprot -> dops -> data_req(cprot, skb) ); + /* prepare for future retransmissions */ + if( ret ) skb_push(skb,1); + return ret; + } + illegal_state_warn( *state, firstbyte ); + break; + case 0x01: /* dl_connect request */ + if( *state == WAN_DISCONNECTED ){ + *state = WAN_CONNECTING; + ret = cprot -> dops -> connect_req(cprot); + if(ret){ + /* reset state and notify upper layer about + * immidiatly failed attempts */ + isdn_x25iface_disconn_ind(cprot); + } + } else { + illegal_state_warn( *state, firstbyte ); + } + break; + case 0x02: /* dl_disconnect request */ + switch ( *state ){ + case WAN_DISCONNECTED: + /* Should not happen. However, give upper layer a + chance to recover from inconstistency but don't + trust the lower layer sending the disconn_confirm + when already disconnected */ + printk(KERN_WARNING "isdn_x25iface_xmit: disconnect " + " requested while disconnected\n" ); + isdn_x25iface_disconn_ind(cprot); + break; /* prevent infinite loops */ + case WAN_CONNECTING: + case WAN_CONNECTED: + *state = WAN_DISCONNECTED; + cprot -> dops -> disconn_req(cprot); + break; + default: + illegal_state_warn( *state, firstbyte ); + } + break; + case 0x03: /* changing lapb parameters requested */ + printk(KERN_WARNING "isdn_x25iface_xmit: setting of lapb" + " options not yet supported\n"); + break; + default: + printk(KERN_WARNING "isdn_x25iface_xmit: frame with illegal" + " first byte %x ignored:\n", firstbyte); + } + dev_kfree_skb(skb); + return 0; +} diff --git a/drivers/isdn/i4l/isdn_x25iface.h b/drivers/isdn/i4l/isdn_x25iface.h new file mode 100644 index 000000000000..41a3d4977466 --- /dev/null +++ b/drivers/isdn/i4l/isdn_x25iface.h @@ -0,0 +1,39 @@ +/* $Id: isdn_x25iface.h,v 1.1.2.2 2004/01/12 22:37:19 keil Exp $ + * + * header for Linux ISDN subsystem, x.25 related functions + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#ifndef _LINUX_ISDN_X25IFACE_H +#define _LINUX_ISDN_X25IFACE_H + +#define ISDN_X25IFACE_MAGIC 0x1e75a2b9 +/* #define DEBUG_ISDN_X25 if you want isdn_x25 debugging messages */ +#ifdef DEBUG_ISDN_X25 +# define IX25DEBUG(fmt,args...) printk(KERN_DEBUG fmt , ## args) +#else +# define IX25DEBUG(fmt,args...) +#endif + +#include <linux/skbuff.h> +#include <linux/wanrouter.h> +#include <linux/isdn.h> +#include <linux/concap.h> + +extern struct concap_proto_ops * isdn_x25iface_concap_proto_ops_pt; +extern struct concap_proto * isdn_x25iface_proto_new(void); + + + +#endif + + + + + + + + diff --git a/drivers/isdn/icn/Kconfig b/drivers/isdn/icn/Kconfig new file mode 100644 index 000000000000..fcb99f5f0b26 --- /dev/null +++ b/drivers/isdn/icn/Kconfig @@ -0,0 +1,16 @@ +# +# Config.in for ICN ISDN driver +# +config ISDN_DRV_ICN + tristate "ICN 2B and 4B support" + depends on ISDN_I4L && ISA + help + This enables support for two kinds of ISDN-cards made by a German + company called ICN. 2B is the standard version for a single ISDN + line with two B-channels, 4B supports two ISDN lines. For running + this card, additional firmware is necessary, which has to be + downloaded into the card using a utility which is distributed + separately. See <file:Documentation/isdn/README> and + <file:Documentation/isdn/README.icn> for more + information. + diff --git a/drivers/isdn/icn/Makefile b/drivers/isdn/icn/Makefile new file mode 100644 index 000000000000..d9b476fcf384 --- /dev/null +++ b/drivers/isdn/icn/Makefile @@ -0,0 +1,5 @@ +# Makefile for the icn ISDN device driver + +# Each configuration option enables a list of files. + +obj-$(CONFIG_ISDN_DRV_ICN) += icn.o diff --git a/drivers/isdn/icn/icn.c b/drivers/isdn/icn/icn.c new file mode 100644 index 000000000000..9fc0c1e03732 --- /dev/null +++ b/drivers/isdn/icn/icn.c @@ -0,0 +1,1691 @@ +/* $Id: icn.c,v 1.65.6.8 2001/09/23 22:24:55 kai Exp $ + * + * ISDN low-level module for the ICN active ISDN-Card. + * + * Copyright 1994,95,96 by Fritz Elfert (fritz@isdn4linux.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include "icn.h" +#include <linux/module.h> +#include <linux/init.h> + +static int portbase = ICN_BASEADDR; +static unsigned long membase = ICN_MEMADDR; +static char *icn_id = "\0"; +static char *icn_id2 = "\0"; + +MODULE_DESCRIPTION("ISDN4Linux: Driver for ICN active ISDN card"); +MODULE_AUTHOR("Fritz Elfert"); +MODULE_LICENSE("GPL"); +module_param(portbase, int, 0); +MODULE_PARM_DESC(portbase, "Port address of first card"); +module_param(membase, ulong, 0); +MODULE_PARM_DESC(membase, "Shared memory address of all cards"); +module_param(icn_id, charp, 0); +MODULE_PARM_DESC(icn_id, "ID-String of first card"); +module_param(icn_id2, charp, 0); +MODULE_PARM_DESC(icn_id2, "ID-String of first card, second S0 (4B only)"); + +/* + * Verbose bootcode- and protocol-downloading. + */ +#undef BOOT_DEBUG + +/* + * Verbose Shmem-Mapping. + */ +#undef MAP_DEBUG + +static char +*revision = "$Revision: 1.65.6.8 $"; + +static int icn_addcard(int, char *, char *); + +/* + * Free send-queue completely. + * Parameter: + * card = pointer to card struct + * channel = channel number + */ +static void +icn_free_queue(icn_card * card, int channel) +{ + struct sk_buff_head *queue = &card->spqueue[channel]; + struct sk_buff *skb; + + skb_queue_purge(queue); + card->xlen[channel] = 0; + card->sndcount[channel] = 0; + if ((skb = card->xskb[channel])) { + card->xskb[channel] = NULL; + dev_kfree_skb(skb); + } +} + +/* Put a value into a shift-register, highest bit first. + * Parameters: + * port = port for output (bit 0 is significant) + * val = value to be output + * firstbit = Bit-Number of highest bit + * bitcount = Number of bits to output + */ +static inline void +icn_shiftout(unsigned short port, + unsigned long val, + int firstbit, + int bitcount) +{ + + register u_char s; + register u_char c; + + for (s = firstbit, c = bitcount; c > 0; s--, c--) + OUTB_P((u_char) ((val >> s) & 1) ? 0xff : 0, port); +} + +/* + * disable a cards shared memory + */ +static inline void +icn_disable_ram(icn_card * card) +{ + OUTB_P(0, ICN_MAPRAM); +} + +/* + * enable a cards shared memory + */ +static inline void +icn_enable_ram(icn_card * card) +{ + OUTB_P(0xff, ICN_MAPRAM); +} + +/* + * Map a cards channel0 (Bank0/Bank8) or channel1 (Bank4/Bank12) + * + * must called with holding the devlock + */ +static inline void +icn_map_channel(icn_card * card, int channel) +{ +#ifdef MAP_DEBUG + printk(KERN_DEBUG "icn_map_channel %d %d\n", dev.channel, channel); +#endif + if ((channel == dev.channel) && (card == dev.mcard)) + return; + if (dev.mcard) + icn_disable_ram(dev.mcard); + icn_shiftout(ICN_BANK, chan2bank[channel], 3, 4); /* Select Bank */ + icn_enable_ram(card); + dev.mcard = card; + dev.channel = channel; +#ifdef MAP_DEBUG + printk(KERN_DEBUG "icn_map_channel done\n"); +#endif +} + +/* + * Lock a cards channel. + * Return 0 if requested card/channel is unmapped (failure). + * Return 1 on success. + * + * must called with holding the devlock + */ +static inline int +icn_lock_channel(icn_card * card, int channel) +{ + register int retval; + +#ifdef MAP_DEBUG + printk(KERN_DEBUG "icn_lock_channel %d\n", channel); +#endif + if ((dev.channel == channel) && (card == dev.mcard)) { + dev.chanlock++; + retval = 1; +#ifdef MAP_DEBUG + printk(KERN_DEBUG "icn_lock_channel %d OK\n", channel); +#endif + } else { + retval = 0; +#ifdef MAP_DEBUG + printk(KERN_DEBUG "icn_lock_channel %d FAILED, dc=%d\n", channel, dev.channel); +#endif + } + return retval; +} + +/* + * Release current card/channel lock + * + * must called with holding the devlock + */ +static inline void +__icn_release_channel(void) +{ +#ifdef MAP_DEBUG + printk(KERN_DEBUG "icn_release_channel l=%d\n", dev.chanlock); +#endif + if (dev.chanlock > 0) + dev.chanlock--; +} + +/* + * Release current card/channel lock + */ +static inline void +icn_release_channel(void) +{ + ulong flags; + + spin_lock_irqsave(&dev.devlock, flags); + __icn_release_channel(); + spin_unlock_irqrestore(&dev.devlock, flags); +} + +/* + * Try to map and lock a cards channel. + * Return 1 on success, 0 on failure. + */ +static inline int +icn_trymaplock_channel(icn_card * card, int channel) +{ + ulong flags; + +#ifdef MAP_DEBUG + printk(KERN_DEBUG "trymaplock c=%d dc=%d l=%d\n", channel, dev.channel, + dev.chanlock); +#endif + spin_lock_irqsave(&dev.devlock, flags); + if ((!dev.chanlock) || + ((dev.channel == channel) && (dev.mcard == card))) { + dev.chanlock++; + icn_map_channel(card, channel); + spin_unlock_irqrestore(&dev.devlock, flags); +#ifdef MAP_DEBUG + printk(KERN_DEBUG "trymaplock %d OK\n", channel); +#endif + return 1; + } + spin_unlock_irqrestore(&dev.devlock, flags); +#ifdef MAP_DEBUG + printk(KERN_DEBUG "trymaplock %d FAILED\n", channel); +#endif + return 0; +} + +/* + * Release current card/channel lock, + * then map same or other channel without locking. + */ +static inline void +icn_maprelease_channel(icn_card * card, int channel) +{ + ulong flags; + +#ifdef MAP_DEBUG + printk(KERN_DEBUG "map_release c=%d l=%d\n", channel, dev.chanlock); +#endif + spin_lock_irqsave(&dev.devlock, flags); + if (dev.chanlock > 0) + dev.chanlock--; + if (!dev.chanlock) + icn_map_channel(card, channel); + spin_unlock_irqrestore(&dev.devlock, flags); +} + +/* Get Data from the B-Channel, assemble fragmented packets and put them + * into receive-queue. Wake up any B-Channel-reading processes. + * This routine is called via timer-callback from icn_pollbchan(). + */ + +static void +icn_pollbchan_receive(int channel, icn_card * card) +{ + int mch = channel + ((card->secondhalf) ? 2 : 0); + int eflag; + int cnt; + struct sk_buff *skb; + + if (icn_trymaplock_channel(card, mch)) { + while (rbavl) { + cnt = readb(&rbuf_l); + if ((card->rcvidx[channel] + cnt) > 4000) { + printk(KERN_WARNING + "icn: (%s) bogus packet on ch%d, dropping.\n", + CID, + channel + 1); + card->rcvidx[channel] = 0; + eflag = 0; + } else { + memcpy_fromio(&card->rcvbuf[channel][card->rcvidx[channel]], + &rbuf_d, cnt); + card->rcvidx[channel] += cnt; + eflag = readb(&rbuf_f); + } + rbnext; + icn_maprelease_channel(card, mch & 2); + if (!eflag) { + if ((cnt = card->rcvidx[channel])) { + if (!(skb = dev_alloc_skb(cnt))) { + printk(KERN_WARNING "icn: receive out of memory\n"); + break; + } + memcpy(skb_put(skb, cnt), card->rcvbuf[channel], cnt); + card->rcvidx[channel] = 0; + card->interface.rcvcallb_skb(card->myid, channel, skb); + } + } + if (!icn_trymaplock_channel(card, mch)) + break; + } + icn_maprelease_channel(card, mch & 2); + } +} + +/* Send data-packet to B-Channel, split it up into fragments of + * ICN_FRAGSIZE length. If last fragment is sent out, signal + * success to upper layers via statcallb with ISDN_STAT_BSENT argument. + * This routine is called via timer-callback from icn_pollbchan() or + * directly from icn_sendbuf(). + */ + +static void +icn_pollbchan_send(int channel, icn_card * card) +{ + int mch = channel + ((card->secondhalf) ? 2 : 0); + int cnt; + unsigned long flags; + struct sk_buff *skb; + isdn_ctrl cmd; + + if (!(card->sndcount[channel] || card->xskb[channel] || + skb_queue_len(&card->spqueue[channel]))) + return; + if (icn_trymaplock_channel(card, mch)) { + while (sbfree && + (card->sndcount[channel] || + skb_queue_len(&card->spqueue[channel]) || + card->xskb[channel])) { + spin_lock_irqsave(&card->lock, flags); + if (card->xmit_lock[channel]) { + spin_unlock_irqrestore(&card->lock, flags); + break; + } + card->xmit_lock[channel]++; + spin_unlock_irqrestore(&card->lock, flags); + skb = card->xskb[channel]; + if (!skb) { + skb = skb_dequeue(&card->spqueue[channel]); + if (skb) { + /* Pop ACK-flag off skb. + * Store length to xlen. + */ + if (*(skb_pull(skb,1))) + card->xlen[channel] = skb->len; + else + card->xlen[channel] = 0; + } + } + if (!skb) + break; + if (skb->len > ICN_FRAGSIZE) { + writeb(0xff, &sbuf_f); + cnt = ICN_FRAGSIZE; + } else { + writeb(0x0, &sbuf_f); + cnt = skb->len; + } + writeb(cnt, &sbuf_l); + memcpy_toio(&sbuf_d, skb->data, cnt); + skb_pull(skb, cnt); + sbnext; /* switch to next buffer */ + icn_maprelease_channel(card, mch & 2); + spin_lock_irqsave(&card->lock, flags); + card->sndcount[channel] -= cnt; + if (!skb->len) { + if (card->xskb[channel]) + card->xskb[channel] = NULL; + card->xmit_lock[channel] = 0; + spin_unlock_irqrestore(&card->lock, flags); + dev_kfree_skb(skb); + if (card->xlen[channel]) { + cmd.command = ISDN_STAT_BSENT; + cmd.driver = card->myid; + cmd.arg = channel; + cmd.parm.length = card->xlen[channel]; + card->interface.statcallb(&cmd); + } + } else { + card->xskb[channel] = skb; + card->xmit_lock[channel] = 0; + spin_unlock_irqrestore(&card->lock, flags); + } + if (!icn_trymaplock_channel(card, mch)) + break; + } + icn_maprelease_channel(card, mch & 2); + } +} + +/* Send/Receive Data to/from the B-Channel. + * This routine is called via timer-callback. + * It schedules itself while any B-Channel is open. + */ + +static void +icn_pollbchan(unsigned long data) +{ + icn_card *card = (icn_card *) data; + unsigned long flags; + + if (card->flags & ICN_FLAGS_B1ACTIVE) { + icn_pollbchan_receive(0, card); + icn_pollbchan_send(0, card); + } + if (card->flags & ICN_FLAGS_B2ACTIVE) { + icn_pollbchan_receive(1, card); + icn_pollbchan_send(1, card); + } + if (card->flags & (ICN_FLAGS_B1ACTIVE | ICN_FLAGS_B2ACTIVE)) { + /* schedule b-channel polling again */ + spin_lock_irqsave(&card->lock, flags); + mod_timer(&card->rb_timer, jiffies+ICN_TIMER_BCREAD); + card->flags |= ICN_FLAGS_RBTIMER; + spin_unlock_irqrestore(&card->lock, flags); + } else + card->flags &= ~ICN_FLAGS_RBTIMER; +} + +typedef struct icn_stat { + char *statstr; + int command; + int action; +} icn_stat; +/* *INDENT-OFF* */ +static icn_stat icn_stat_table[] = +{ + {"BCON_", ISDN_STAT_BCONN, 1}, /* B-Channel connected */ + {"BDIS_", ISDN_STAT_BHUP, 2}, /* B-Channel disconnected */ + /* + ** add d-channel connect and disconnect support to link-level + */ + {"DCON_", ISDN_STAT_DCONN, 10}, /* D-Channel connected */ + {"DDIS_", ISDN_STAT_DHUP, 11}, /* D-Channel disconnected */ + {"DCAL_I", ISDN_STAT_ICALL, 3}, /* Incoming call dialup-line */ + {"DSCA_I", ISDN_STAT_ICALL, 3}, /* Incoming call 1TR6-SPV */ + {"FCALL", ISDN_STAT_ICALL, 4}, /* Leased line connection up */ + {"CIF", ISDN_STAT_CINF, 5}, /* Charge-info, 1TR6-type */ + {"AOC", ISDN_STAT_CINF, 6}, /* Charge-info, DSS1-type */ + {"CAU", ISDN_STAT_CAUSE, 7}, /* Cause code */ + {"TEI OK", ISDN_STAT_RUN, 0}, /* Card connected to wallplug */ + {"E_L1: ACT FAIL", ISDN_STAT_BHUP, 8}, /* Layer-1 activation failed */ + {"E_L2: DATA LIN", ISDN_STAT_BHUP, 8}, /* Layer-2 data link lost */ + {"E_L1: ACTIVATION FAILED", + ISDN_STAT_BHUP, 8}, /* Layer-1 activation failed */ + {NULL, 0, -1} +}; +/* *INDENT-ON* */ + + +/* + * Check Statusqueue-Pointer from isdn-cards. + * If there are new status-replies from the interface, check + * them against B-Channel-connects/disconnects and set flags accordingly. + * Wake-Up any processes, who are reading the status-device. + * If there are B-Channels open, initiate a timer-callback to + * icn_pollbchan(). + * This routine is called periodically via timer. + */ + +static void +icn_parse_status(u_char * status, int channel, icn_card * card) +{ + icn_stat *s = icn_stat_table; + int action = -1; + unsigned long flags; + isdn_ctrl cmd; + + while (s->statstr) { + if (!strncmp(status, s->statstr, strlen(s->statstr))) { + cmd.command = s->command; + action = s->action; + break; + } + s++; + } + if (action == -1) + return; + cmd.driver = card->myid; + cmd.arg = channel; + switch (action) { + case 11: + spin_lock_irqsave(&card->lock, flags); + icn_free_queue(card,channel); + card->rcvidx[channel] = 0; + + if (card->flags & + ((channel)?ICN_FLAGS_B2ACTIVE:ICN_FLAGS_B1ACTIVE)) { + + isdn_ctrl ncmd; + + card->flags &= ~((channel)? + ICN_FLAGS_B2ACTIVE:ICN_FLAGS_B1ACTIVE); + + memset(&ncmd, 0, sizeof(ncmd)); + + ncmd.driver = card->myid; + ncmd.arg = channel; + ncmd.command = ISDN_STAT_BHUP; + spin_unlock_irqrestore(&card->lock, flags); + card->interface.statcallb(&cmd); + } else + spin_unlock_irqrestore(&card->lock, flags); + break; + case 1: + spin_lock_irqsave(&card->lock, flags); + icn_free_queue(card,channel); + card->flags |= (channel) ? + ICN_FLAGS_B2ACTIVE : ICN_FLAGS_B1ACTIVE; + spin_unlock_irqrestore(&card->lock, flags); + break; + case 2: + spin_lock_irqsave(&card->lock, flags); + card->flags &= ~((channel) ? + ICN_FLAGS_B2ACTIVE : ICN_FLAGS_B1ACTIVE); + icn_free_queue(card, channel); + card->rcvidx[channel] = 0; + spin_unlock_irqrestore(&card->lock, flags); + break; + case 3: + { + char *t = status + 6; + char *s = strchr(t, ','); + + *s++ = '\0'; + strlcpy(cmd.parm.setup.phone, t, + sizeof(cmd.parm.setup.phone)); + s = strchr(t = s, ','); + *s++ = '\0'; + if (!strlen(t)) + cmd.parm.setup.si1 = 0; + else + cmd.parm.setup.si1 = + simple_strtoul(t, NULL, 10); + s = strchr(t = s, ','); + *s++ = '\0'; + if (!strlen(t)) + cmd.parm.setup.si2 = 0; + else + cmd.parm.setup.si2 = + simple_strtoul(t, NULL, 10); + strlcpy(cmd.parm.setup.eazmsn, s, + sizeof(cmd.parm.setup.eazmsn)); + } + cmd.parm.setup.plan = 0; + cmd.parm.setup.screen = 0; + break; + case 4: + sprintf(cmd.parm.setup.phone, "LEASED%d", card->myid); + sprintf(cmd.parm.setup.eazmsn, "%d", channel + 1); + cmd.parm.setup.si1 = 7; + cmd.parm.setup.si2 = 0; + cmd.parm.setup.plan = 0; + cmd.parm.setup.screen = 0; + break; + case 5: + strlcpy(cmd.parm.num, status + 3, sizeof(cmd.parm.num)); + break; + case 6: + snprintf(cmd.parm.num, sizeof(cmd.parm.num), "%d", + (int) simple_strtoul(status + 7, NULL, 16)); + break; + case 7: + status += 3; + if (strlen(status) == 4) + snprintf(cmd.parm.num, sizeof(cmd.parm.num), "%s%c%c", + status + 2, *status, *(status + 1)); + else + strlcpy(cmd.parm.num, status + 1, sizeof(cmd.parm.num)); + break; + case 8: + spin_lock_irqsave(&card->lock, flags); + card->flags &= ~ICN_FLAGS_B1ACTIVE; + icn_free_queue(card, 0); + card->rcvidx[0] = 0; + spin_unlock_irqrestore(&card->lock, flags); + cmd.arg = 0; + cmd.driver = card->myid; + card->interface.statcallb(&cmd); + cmd.command = ISDN_STAT_DHUP; + cmd.arg = 0; + cmd.driver = card->myid; + card->interface.statcallb(&cmd); + cmd.command = ISDN_STAT_BHUP; + spin_lock_irqsave(&card->lock, flags); + card->flags &= ~ICN_FLAGS_B2ACTIVE; + icn_free_queue(card, 1); + card->rcvidx[1] = 0; + spin_unlock_irqrestore(&card->lock, flags); + cmd.arg = 1; + cmd.driver = card->myid; + card->interface.statcallb(&cmd); + cmd.command = ISDN_STAT_DHUP; + cmd.arg = 1; + cmd.driver = card->myid; + break; + } + card->interface.statcallb(&cmd); + return; +} + +static void +icn_putmsg(icn_card * card, unsigned char c) +{ + ulong flags; + + spin_lock_irqsave(&card->lock, flags); + *card->msg_buf_write++ = (c == 0xff) ? '\n' : c; + if (card->msg_buf_write == card->msg_buf_read) { + if (++card->msg_buf_read > card->msg_buf_end) + card->msg_buf_read = card->msg_buf; + } + if (card->msg_buf_write > card->msg_buf_end) + card->msg_buf_write = card->msg_buf; + spin_unlock_irqrestore(&card->lock, flags); +} + +static void +icn_polldchan(unsigned long data) +{ + icn_card *card = (icn_card *) data; + int mch = card->secondhalf ? 2 : 0; + int avail = 0; + int left; + u_char c; + int ch; + unsigned long flags; + int i; + u_char *p; + isdn_ctrl cmd; + + if (icn_trymaplock_channel(card, mch)) { + avail = msg_avail; + for (left = avail, i = readb(&msg_o); left > 0; i++, left--) { + c = readb(&dev.shmem->comm_buffers.iopc_buf[i & 0xff]); + icn_putmsg(card, c); + if (c == 0xff) { + card->imsg[card->iptr] = 0; + card->iptr = 0; + if (card->imsg[0] == '0' && card->imsg[1] >= '0' && + card->imsg[1] <= '2' && card->imsg[2] == ';') { + ch = (card->imsg[1] - '0') - 1; + p = &card->imsg[3]; + icn_parse_status(p, ch, card); + } else { + p = card->imsg; + if (!strncmp(p, "DRV1.", 5)) { + u_char vstr[10]; + u_char *q = vstr; + + printk(KERN_INFO "icn: (%s) %s\n", CID, p); + if (!strncmp(p + 7, "TC", 2)) { + card->ptype = ISDN_PTYPE_1TR6; + card->interface.features |= ISDN_FEATURE_P_1TR6; + printk(KERN_INFO + "icn: (%s) 1TR6-Protocol loaded and running\n", CID); + } + if (!strncmp(p + 7, "EC", 2)) { + card->ptype = ISDN_PTYPE_EURO; + card->interface.features |= ISDN_FEATURE_P_EURO; + printk(KERN_INFO + "icn: (%s) Euro-Protocol loaded and running\n", CID); + } + p = strstr(card->imsg, "BRV") + 3; + while (*p) { + if (*p >= '0' && *p <= '9') + *q++ = *p; + p++; + } + *q = '\0'; + strcat(vstr, "000"); + vstr[3] = '\0'; + card->fw_rev = (int) simple_strtoul(vstr, NULL, 10); + continue; + + } + } + } else { + card->imsg[card->iptr] = c; + if (card->iptr < 59) + card->iptr++; + } + } + writeb((readb(&msg_o) + avail) & 0xff, &msg_o); + icn_release_channel(); + } + if (avail) { + cmd.command = ISDN_STAT_STAVAIL; + cmd.driver = card->myid; + cmd.arg = avail; + card->interface.statcallb(&cmd); + } + spin_lock_irqsave(&card->lock, flags); + if (card->flags & (ICN_FLAGS_B1ACTIVE | ICN_FLAGS_B2ACTIVE)) + if (!(card->flags & ICN_FLAGS_RBTIMER)) { + /* schedule b-channel polling */ + card->flags |= ICN_FLAGS_RBTIMER; + del_timer(&card->rb_timer); + card->rb_timer.function = icn_pollbchan; + card->rb_timer.data = (unsigned long) card; + card->rb_timer.expires = jiffies + ICN_TIMER_BCREAD; + add_timer(&card->rb_timer); + } + /* schedule again */ + mod_timer(&card->st_timer, jiffies+ICN_TIMER_DCREAD); + spin_unlock_irqrestore(&card->lock, flags); +} + +/* Append a packet to the transmit buffer-queue. + * Parameters: + * channel = Number of B-channel + * skb = pointer to sk_buff + * card = pointer to card-struct + * Return: + * Number of bytes transferred, -E??? on error + */ + +static int +icn_sendbuf(int channel, int ack, struct sk_buff *skb, icn_card * card) +{ + int len = skb->len; + unsigned long flags; + struct sk_buff *nskb; + + if (len > 4000) { + printk(KERN_WARNING + "icn: Send packet too large\n"); + return -EINVAL; + } + if (len) { + if (!(card->flags & (channel) ? ICN_FLAGS_B2ACTIVE : ICN_FLAGS_B1ACTIVE)) + return 0; + if (card->sndcount[channel] > ICN_MAX_SQUEUE) + return 0; + #warning TODO test headroom or use skb->nb to flag ACK + nskb = skb_clone(skb, GFP_ATOMIC); + if (nskb) { + /* Push ACK flag as one + * byte in front of data. + */ + *(skb_push(nskb, 1)) = ack?1:0; + skb_queue_tail(&card->spqueue[channel], nskb); + dev_kfree_skb(skb); + } else + len = 0; + spin_lock_irqsave(&card->lock, flags); + card->sndcount[channel] += len; + spin_unlock_irqrestore(&card->lock, flags); + } + return len; +} + +/* + * Check card's status after starting the bootstrap loader. + * On entry, the card's shared memory has already to be mapped. + * Return: + * 0 on success (Boot loader ready) + * -EIO on failure (timeout) + */ +static int +icn_check_loader(int cardnumber) +{ + int timer = 0; + + while (1) { +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "Loader %d ?\n", cardnumber); +#endif + if (readb(&dev.shmem->data_control.scns) || + readb(&dev.shmem->data_control.scnr)) { + if (timer++ > 5) { + printk(KERN_WARNING + "icn: Boot-Loader %d timed out.\n", + cardnumber); + icn_release_channel(); + return -EIO; + } +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "Loader %d TO?\n", cardnumber); +#endif + msleep_interruptible(ICN_BOOT_TIMEOUT1); + } else { +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "Loader %d OK\n", cardnumber); +#endif + icn_release_channel(); + return 0; + } + } +} + +/* Load the boot-code into the interface-card's memory and start it. + * Always called from user-process. + * + * Parameters: + * buffer = pointer to packet + * Return: + * 0 if successfully loaded + */ + +#ifdef BOOT_DEBUG +#define SLEEP(sec) { \ +int slsec = sec; \ + printk(KERN_DEBUG "SLEEP(%d)\n",slsec); \ + while (slsec) { \ + msleep_interruptible(1000); \ + slsec--; \ + } \ +} +#else +#define SLEEP(sec) +#endif + +static int +icn_loadboot(u_char __user * buffer, icn_card * card) +{ + int ret; + u_char *codebuf; + unsigned long flags; + +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "icn_loadboot called, buffaddr=%08lx\n", (ulong) buffer); +#endif + if (!(codebuf = kmalloc(ICN_CODE_STAGE1, GFP_KERNEL))) { + printk(KERN_WARNING "icn: Could not allocate code buffer\n"); + ret = -ENOMEM; + goto out; + } + if (copy_from_user(codebuf, buffer, ICN_CODE_STAGE1)) { + ret = -EFAULT; + goto out_kfree; + } + if (!card->rvalid) { + if (!request_region(card->port, ICN_PORTLEN, card->regname)) { + printk(KERN_WARNING + "icn: (%s) ports 0x%03x-0x%03x in use.\n", + CID, + card->port, + card->port + ICN_PORTLEN); + ret = -EBUSY; + goto out_kfree; + } + card->rvalid = 1; + if (card->doubleS0) + card->other->rvalid = 1; + } + if (!dev.mvalid) { + if (!request_mem_region(dev.memaddr, 0x4000, "icn-isdn (all cards)")) { + printk(KERN_WARNING + "icn: memory at 0x%08lx in use.\n", dev.memaddr); + ret = -EBUSY; + goto out_kfree; + } + dev.shmem = ioremap(dev.memaddr, 0x4000); + dev.mvalid = 1; + } + OUTB_P(0, ICN_RUN); /* Reset Controller */ + OUTB_P(0, ICN_MAPRAM); /* Disable RAM */ + icn_shiftout(ICN_CFG, 0x0f, 3, 4); /* Windowsize= 16k */ + icn_shiftout(ICN_CFG, dev.memaddr, 23, 10); /* Set RAM-Addr. */ +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "shmem=%08lx\n", dev.memaddr); +#endif + SLEEP(1); +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "Map Bank 0\n"); +#endif + spin_lock_irqsave(&dev.devlock, flags); + icn_map_channel(card, 0); /* Select Bank 0 */ + icn_lock_channel(card, 0); /* Lock Bank 0 */ + spin_unlock_irqrestore(&dev.devlock, flags); + SLEEP(1); + memcpy_toio(dev.shmem, codebuf, ICN_CODE_STAGE1); /* Copy code */ +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "Bootloader transferred\n"); +#endif + if (card->doubleS0) { + SLEEP(1); +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "Map Bank 8\n"); +#endif + spin_lock_irqsave(&dev.devlock, flags); + __icn_release_channel(); + icn_map_channel(card, 2); /* Select Bank 8 */ + icn_lock_channel(card, 2); /* Lock Bank 8 */ + spin_unlock_irqrestore(&dev.devlock, flags); + SLEEP(1); + memcpy_toio(dev.shmem, codebuf, ICN_CODE_STAGE1); /* Copy code */ +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "Bootloader transferred\n"); +#endif + } + SLEEP(1); + OUTB_P(0xff, ICN_RUN); /* Start Boot-Code */ + if ((ret = icn_check_loader(card->doubleS0 ? 2 : 1))) { + goto out_kfree; + } + if (!card->doubleS0) { + ret = 0; + goto out_kfree; + } + /* reached only, if we have a Double-S0-Card */ +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "Map Bank 0\n"); +#endif + spin_lock_irqsave(&dev.devlock, flags); + icn_map_channel(card, 0); /* Select Bank 0 */ + icn_lock_channel(card, 0); /* Lock Bank 0 */ + spin_unlock_irqrestore(&dev.devlock, flags); + SLEEP(1); + ret = (icn_check_loader(1)); + + out_kfree: + kfree(codebuf); + out: + return ret; +} + +static int +icn_loadproto(u_char __user * buffer, icn_card * card) +{ + register u_char __user *p = buffer; + u_char codebuf[256]; + uint left = ICN_CODE_STAGE2; + uint cnt; + int timer; + unsigned long flags; + +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "icn_loadproto called\n"); +#endif + if (!access_ok(VERIFY_READ, buffer, ICN_CODE_STAGE2)) + return -EFAULT; + timer = 0; + spin_lock_irqsave(&dev.devlock, flags); + if (card->secondhalf) { + icn_map_channel(card, 2); + icn_lock_channel(card, 2); + } else { + icn_map_channel(card, 0); + icn_lock_channel(card, 0); + } + spin_unlock_irqrestore(&dev.devlock, flags); + while (left) { + if (sbfree) { /* If there is a free buffer... */ + cnt = left; + if (cnt > 256) + cnt = 256; + if (copy_from_user(codebuf, p, cnt)) { + icn_maprelease_channel(card, 0); + return -EFAULT; + } + memcpy_toio(&sbuf_l, codebuf, cnt); /* copy data */ + sbnext; /* switch to next buffer */ + p += cnt; + left -= cnt; + timer = 0; + } else { +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "boot 2 !sbfree\n"); +#endif + if (timer++ > 5) { + icn_maprelease_channel(card, 0); + return -EIO; + } + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(10); + } + } + writeb(0x20, &sbuf_n); + timer = 0; + while (1) { + if (readb(&cmd_o) || readb(&cmd_i)) { +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "Proto?\n"); +#endif + if (timer++ > 5) { + printk(KERN_WARNING + "icn: (%s) Protocol timed out.\n", + CID); +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "Proto TO!\n"); +#endif + icn_maprelease_channel(card, 0); + return -EIO; + } +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "Proto TO?\n"); +#endif + msleep_interruptible(ICN_BOOT_TIMEOUT1); + } else { + if ((card->secondhalf) || (!card->doubleS0)) { +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "Proto loaded, install poll-timer %d\n", + card->secondhalf); +#endif + spin_lock_irqsave(&card->lock, flags); + init_timer(&card->st_timer); + card->st_timer.expires = jiffies + ICN_TIMER_DCREAD; + card->st_timer.function = icn_polldchan; + card->st_timer.data = (unsigned long) card; + add_timer(&card->st_timer); + card->flags |= ICN_FLAGS_RUNNING; + if (card->doubleS0) { + init_timer(&card->other->st_timer); + card->other->st_timer.expires = jiffies + ICN_TIMER_DCREAD; + card->other->st_timer.function = icn_polldchan; + card->other->st_timer.data = (unsigned long) card->other; + add_timer(&card->other->st_timer); + card->other->flags |= ICN_FLAGS_RUNNING; + } + spin_unlock_irqrestore(&card->lock, flags); + } + icn_maprelease_channel(card, 0); + return 0; + } + } +} + +/* Read the Status-replies from the Interface */ +static int +icn_readstatus(u_char __user *buf, int len, icn_card * card) +{ + int count; + u_char __user *p; + + for (p = buf, count = 0; count < len; p++, count++) { + if (card->msg_buf_read == card->msg_buf_write) + return count; + put_user(*card->msg_buf_read++, p); + if (card->msg_buf_read > card->msg_buf_end) + card->msg_buf_read = card->msg_buf; + } + return count; +} + +/* Put command-strings into the command-queue of the Interface */ +static int +icn_writecmd(const u_char * buf, int len, int user, icn_card * card) +{ + int mch = card->secondhalf ? 2 : 0; + int pp; + int i; + int count; + int xcount; + int ocount; + int loop; + unsigned long flags; + int lastmap_channel; + struct icn_card *lastmap_card; + u_char *p; + isdn_ctrl cmd; + u_char msg[0x100]; + + ocount = 1; + xcount = loop = 0; + while (len) { + count = cmd_free; + if (count > len) + count = len; + if (user) { + if (copy_from_user(msg, buf, count)) + return -EFAULT; + } else + memcpy(msg, buf, count); + + spin_lock_irqsave(&dev.devlock, flags); + lastmap_card = dev.mcard; + lastmap_channel = dev.channel; + icn_map_channel(card, mch); + + icn_putmsg(card, '>'); + for (p = msg, pp = readb(&cmd_i), i = count; i > 0; i--, p++, pp + ++) { + writeb((*p == '\n') ? 0xff : *p, + &dev.shmem->comm_buffers.pcio_buf[pp & 0xff]); + len--; + xcount++; + icn_putmsg(card, *p); + if ((*p == '\n') && (i > 1)) { + icn_putmsg(card, '>'); + ocount++; + } + ocount++; + } + writeb((readb(&cmd_i) + count) & 0xff, &cmd_i); + if (lastmap_card) + icn_map_channel(lastmap_card, lastmap_channel); + spin_unlock_irqrestore(&dev.devlock, flags); + if (len) { + mdelay(1); + if (loop++ > 20) + break; + } else + break; + } + if (len && (!user)) + printk(KERN_WARNING "icn: writemsg incomplete!\n"); + cmd.command = ISDN_STAT_STAVAIL; + cmd.driver = card->myid; + cmd.arg = ocount; + card->interface.statcallb(&cmd); + return xcount; +} + +/* + * Delete card's pending timers, send STOP to linklevel + */ +static void +icn_stopcard(icn_card * card) +{ + unsigned long flags; + isdn_ctrl cmd; + + spin_lock_irqsave(&card->lock, flags); + if (card->flags & ICN_FLAGS_RUNNING) { + card->flags &= ~ICN_FLAGS_RUNNING; + del_timer(&card->st_timer); + del_timer(&card->rb_timer); + spin_unlock_irqrestore(&card->lock, flags); + cmd.command = ISDN_STAT_STOP; + cmd.driver = card->myid; + card->interface.statcallb(&cmd); + if (card->doubleS0) + icn_stopcard(card->other); + } else + spin_unlock_irqrestore(&card->lock, flags); +} + +static void +icn_stopallcards(void) +{ + icn_card *p = cards; + + while (p) { + icn_stopcard(p); + p = p->next; + } +} + +/* + * Unmap all cards, because some of them may be mapped accidetly during + * autoprobing of some network drivers (SMC-driver?) + */ +static void +icn_disable_cards(void) +{ + icn_card *card = cards; + + while (card) { + if (!request_region(card->port, ICN_PORTLEN, "icn-isdn")) { + printk(KERN_WARNING + "icn: (%s) ports 0x%03x-0x%03x in use.\n", + CID, + card->port, + card->port + ICN_PORTLEN); + } else { + OUTB_P(0, ICN_RUN); /* Reset Controller */ + OUTB_P(0, ICN_MAPRAM); /* Disable RAM */ + release_region(card->port, ICN_PORTLEN); + } + card = card->next; + } +} + +static int +icn_command(isdn_ctrl * c, icn_card * card) +{ + ulong a; + ulong flags; + int i; + char cbuf[60]; + isdn_ctrl cmd; + icn_cdef cdef; + char __user *arg; + + switch (c->command) { + case ISDN_CMD_IOCTL: + memcpy(&a, c->parm.num, sizeof(ulong)); + arg = (char __user *)a; + switch (c->arg) { + case ICN_IOCTL_SETMMIO: + if (dev.memaddr != (a & 0x0ffc000)) { + if (!request_mem_region(a & 0x0ffc000, 0x4000, "icn-isdn (all cards)")) { + printk(KERN_WARNING + "icn: memory at 0x%08lx in use.\n", + a & 0x0ffc000); + return -EINVAL; + } + release_mem_region(a & 0x0ffc000, 0x4000); + icn_stopallcards(); + spin_lock_irqsave(&card->lock, flags); + if (dev.mvalid) { + iounmap(dev.shmem); + release_mem_region(dev.memaddr, 0x4000); + } + dev.mvalid = 0; + dev.memaddr = a & 0x0ffc000; + spin_unlock_irqrestore(&card->lock, flags); + printk(KERN_INFO + "icn: (%s) mmio set to 0x%08lx\n", + CID, + dev.memaddr); + } + break; + case ICN_IOCTL_GETMMIO: + return (long) dev.memaddr; + case ICN_IOCTL_SETPORT: + if (a == 0x300 || a == 0x310 || a == 0x320 || a == 0x330 + || a == 0x340 || a == 0x350 || a == 0x360 || + a == 0x308 || a == 0x318 || a == 0x328 || a == 0x338 + || a == 0x348 || a == 0x358 || a == 0x368) { + if (card->port != (unsigned short) a) { + if (!request_region((unsigned short) a, ICN_PORTLEN, "icn-isdn")) { + printk(KERN_WARNING + "icn: (%s) ports 0x%03x-0x%03x in use.\n", + CID, (int) a, (int) a + ICN_PORTLEN); + return -EINVAL; + } + release_region((unsigned short) a, ICN_PORTLEN); + icn_stopcard(card); + spin_lock_irqsave(&card->lock, flags); + if (card->rvalid) + release_region(card->port, ICN_PORTLEN); + card->port = (unsigned short) a; + card->rvalid = 0; + if (card->doubleS0) { + card->other->port = (unsigned short) a; + card->other->rvalid = 0; + } + spin_unlock_irqrestore(&card->lock, flags); + printk(KERN_INFO + "icn: (%s) port set to 0x%03x\n", + CID, card->port); + } + } else + return -EINVAL; + break; + case ICN_IOCTL_GETPORT: + return (int) card->port; + case ICN_IOCTL_GETDOUBLE: + return (int) card->doubleS0; + case ICN_IOCTL_DEBUGVAR: + if (copy_to_user(arg, + &card, + sizeof(ulong))) + return -EFAULT; + a += sizeof(ulong); + { + ulong l = (ulong) & dev; + if (copy_to_user(arg, + &l, + sizeof(ulong))) + return -EFAULT; + } + return 0; + case ICN_IOCTL_LOADBOOT: + if (dev.firstload) { + icn_disable_cards(); + dev.firstload = 0; + } + icn_stopcard(card); + return (icn_loadboot(arg, card)); + case ICN_IOCTL_LOADPROTO: + icn_stopcard(card); + if ((i = (icn_loadproto(arg, card)))) + return i; + if (card->doubleS0) + i = icn_loadproto(arg + ICN_CODE_STAGE2, card->other); + return i; + break; + case ICN_IOCTL_ADDCARD: + if (!dev.firstload) + return -EBUSY; + if (copy_from_user(&cdef, + arg, + sizeof(cdef))) + return -EFAULT; + return (icn_addcard(cdef.port, cdef.id1, cdef.id2)); + break; + case ICN_IOCTL_LEASEDCFG: + if (a) { + if (!card->leased) { + card->leased = 1; + while (card->ptype == ISDN_PTYPE_UNKNOWN) { + msleep_interruptible(ICN_BOOT_TIMEOUT1); + } + msleep_interruptible(ICN_BOOT_TIMEOUT1); + sprintf(cbuf, "00;FV2ON\n01;EAZ%c\n02;EAZ%c\n", + (a & 1)?'1':'C', (a & 2)?'2':'C'); + i = icn_writecmd(cbuf, strlen(cbuf), 0, card); + printk(KERN_INFO + "icn: (%s) Leased-line mode enabled\n", + CID); + cmd.command = ISDN_STAT_RUN; + cmd.driver = card->myid; + cmd.arg = 0; + card->interface.statcallb(&cmd); + } + } else { + if (card->leased) { + card->leased = 0; + sprintf(cbuf, "00;FV2OFF\n"); + i = icn_writecmd(cbuf, strlen(cbuf), 0, card); + printk(KERN_INFO + "icn: (%s) Leased-line mode disabled\n", + CID); + cmd.command = ISDN_STAT_RUN; + cmd.driver = card->myid; + cmd.arg = 0; + card->interface.statcallb(&cmd); + } + } + return 0; + default: + return -EINVAL; + } + break; + case ISDN_CMD_DIAL: + if (!card->flags & ICN_FLAGS_RUNNING) + return -ENODEV; + if (card->leased) + break; + if ((c->arg & 255) < ICN_BCH) { + char *p; + char dial[50]; + char dcode[4]; + + a = c->arg; + p = c->parm.setup.phone; + if (*p == 's' || *p == 'S') { + /* Dial for SPV */ + p++; + strcpy(dcode, "SCA"); + } else + /* Normal Dial */ + strcpy(dcode, "CAL"); + strcpy(dial, p); + sprintf(cbuf, "%02d;D%s_R%s,%02d,%02d,%s\n", (int) (a + 1), + dcode, dial, c->parm.setup.si1, + c->parm.setup.si2, c->parm.setup.eazmsn); + i = icn_writecmd(cbuf, strlen(cbuf), 0, card); + } + break; + case ISDN_CMD_ACCEPTD: + if (!card->flags & ICN_FLAGS_RUNNING) + return -ENODEV; + if (c->arg < ICN_BCH) { + a = c->arg + 1; + if (card->fw_rev >= 300) { + switch (card->l2_proto[a - 1]) { + case ISDN_PROTO_L2_X75I: + sprintf(cbuf, "%02d;BX75\n", (int) a); + break; + case ISDN_PROTO_L2_HDLC: + sprintf(cbuf, "%02d;BTRA\n", (int) a); + break; + } + i = icn_writecmd(cbuf, strlen(cbuf), 0, card); + } + sprintf(cbuf, "%02d;DCON_R\n", (int) a); + i = icn_writecmd(cbuf, strlen(cbuf), 0, card); + } + break; + case ISDN_CMD_ACCEPTB: + if (!card->flags & ICN_FLAGS_RUNNING) + return -ENODEV; + if (c->arg < ICN_BCH) { + a = c->arg + 1; + if (card->fw_rev >= 300) + switch (card->l2_proto[a - 1]) { + case ISDN_PROTO_L2_X75I: + sprintf(cbuf, "%02d;BCON_R,BX75\n", (int) a); + break; + case ISDN_PROTO_L2_HDLC: + sprintf(cbuf, "%02d;BCON_R,BTRA\n", (int) a); + break; + } else + sprintf(cbuf, "%02d;BCON_R\n", (int) a); + i = icn_writecmd(cbuf, strlen(cbuf), 0, card); + } + break; + case ISDN_CMD_HANGUP: + if (!card->flags & ICN_FLAGS_RUNNING) + return -ENODEV; + if (c->arg < ICN_BCH) { + a = c->arg + 1; + sprintf(cbuf, "%02d;BDIS_R\n%02d;DDIS_R\n", (int) a, (int) a); + i = icn_writecmd(cbuf, strlen(cbuf), 0, card); + } + break; + case ISDN_CMD_SETEAZ: + if (!card->flags & ICN_FLAGS_RUNNING) + return -ENODEV; + if (card->leased) + break; + if (c->arg < ICN_BCH) { + a = c->arg + 1; + if (card->ptype == ISDN_PTYPE_EURO) { + sprintf(cbuf, "%02d;MS%s%s\n", (int) a, + c->parm.num[0] ? "N" : "ALL", c->parm.num); + } else + sprintf(cbuf, "%02d;EAZ%s\n", (int) a, + c->parm.num[0] ? (char *)(c->parm.num) : "0123456789"); + i = icn_writecmd(cbuf, strlen(cbuf), 0, card); + } + break; + case ISDN_CMD_CLREAZ: + if (!card->flags & ICN_FLAGS_RUNNING) + return -ENODEV; + if (card->leased) + break; + if (c->arg < ICN_BCH) { + a = c->arg + 1; + if (card->ptype == ISDN_PTYPE_EURO) + sprintf(cbuf, "%02d;MSNC\n", (int) a); + else + sprintf(cbuf, "%02d;EAZC\n", (int) a); + i = icn_writecmd(cbuf, strlen(cbuf), 0, card); + } + break; + case ISDN_CMD_SETL2: + if (!card->flags & ICN_FLAGS_RUNNING) + return -ENODEV; + if ((c->arg & 255) < ICN_BCH) { + a = c->arg; + switch (a >> 8) { + case ISDN_PROTO_L2_X75I: + sprintf(cbuf, "%02d;BX75\n", (int) (a & 255) + 1); + break; + case ISDN_PROTO_L2_HDLC: + sprintf(cbuf, "%02d;BTRA\n", (int) (a & 255) + 1); + break; + default: + return -EINVAL; + } + i = icn_writecmd(cbuf, strlen(cbuf), 0, card); + card->l2_proto[a & 255] = (a >> 8); + } + break; + case ISDN_CMD_SETL3: + if (!card->flags & ICN_FLAGS_RUNNING) + return -ENODEV; + return 0; + default: + return -EINVAL; + } + return 0; +} + +/* + * Find card with given driverId + */ +static inline icn_card * +icn_findcard(int driverid) +{ + icn_card *p = cards; + + while (p) { + if (p->myid == driverid) + return p; + p = p->next; + } + return (icn_card *) 0; +} + +/* + * Wrapper functions for interface to linklevel + */ +static int +if_command(isdn_ctrl * c) +{ + icn_card *card = icn_findcard(c->driver); + + if (card) + return (icn_command(c, card)); + printk(KERN_ERR + "icn: if_command %d called with invalid driverId %d!\n", + c->command, c->driver); + return -ENODEV; +} + +static int +if_writecmd(const u_char __user *buf, int len, int id, int channel) +{ + icn_card *card = icn_findcard(id); + + if (card) { + if (!card->flags & ICN_FLAGS_RUNNING) + return -ENODEV; + return (icn_writecmd(buf, len, 1, card)); + } + printk(KERN_ERR + "icn: if_writecmd called with invalid driverId!\n"); + return -ENODEV; +} + +static int +if_readstatus(u_char __user *buf, int len, int id, int channel) +{ + icn_card *card = icn_findcard(id); + + if (card) { + if (!card->flags & ICN_FLAGS_RUNNING) + return -ENODEV; + return (icn_readstatus(buf, len, card)); + } + printk(KERN_ERR + "icn: if_readstatus called with invalid driverId!\n"); + return -ENODEV; +} + +static int +if_sendbuf(int id, int channel, int ack, struct sk_buff *skb) +{ + icn_card *card = icn_findcard(id); + + if (card) { + if (!card->flags & ICN_FLAGS_RUNNING) + return -ENODEV; + return (icn_sendbuf(channel, ack, skb, card)); + } + printk(KERN_ERR + "icn: if_sendbuf called with invalid driverId!\n"); + return -ENODEV; +} + +/* + * Allocate a new card-struct, initialize it + * link it into cards-list and register it at linklevel. + */ +static icn_card * +icn_initcard(int port, char *id) +{ + icn_card *card; + int i; + + if (!(card = (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)); + spin_lock_init(&card->lock); + card->port = port; + card->interface.owner = THIS_MODULE; + card->interface.hl_hdrlen = 1; + card->interface.channels = ICN_BCH; + card->interface.maxbufsize = 4000; + card->interface.command = if_command; + card->interface.writebuf_skb = if_sendbuf; + card->interface.writecmd = if_writecmd; + card->interface.readstat = if_readstatus; + card->interface.features = ISDN_FEATURE_L2_X75I | + ISDN_FEATURE_L2_HDLC | + ISDN_FEATURE_L3_TRANS | + ISDN_FEATURE_P_UNKNOWN; + card->ptype = ISDN_PTYPE_UNKNOWN; + strlcpy(card->interface.id, id, sizeof(card->interface.id)); + card->msg_buf_write = card->msg_buf; + card->msg_buf_read = card->msg_buf; + card->msg_buf_end = &card->msg_buf[sizeof(card->msg_buf) - 1]; + for (i = 0; i < ICN_BCH; i++) { + card->l2_proto[i] = ISDN_PROTO_L2_X75I; + skb_queue_head_init(&card->spqueue[i]); + } + card->next = cards; + cards = card; + if (!register_isdn(&card->interface)) { + cards = cards->next; + printk(KERN_WARNING + "icn: Unable to register %s\n", id); + kfree(card); + return (icn_card *) 0; + } + card->myid = card->interface.channels; + sprintf(card->regname, "icn-isdn (%s)", card->interface.id); + return card; +} + +static int +icn_addcard(int port, char *id1, char *id2) +{ + icn_card *card; + icn_card *card2; + + if (!(card = icn_initcard(port, id1))) { + return -EIO; + } + if (!strlen(id2)) { + printk(KERN_INFO + "icn: (%s) ICN-2B, port 0x%x added\n", + card->interface.id, port); + return 0; + } + if (!(card2 = icn_initcard(port, id2))) { + printk(KERN_INFO + "icn: (%s) half ICN-4B, port 0x%x added\n", + card2->interface.id, port); + return 0; + } + card->doubleS0 = 1; + card->secondhalf = 0; + card->other = card2; + card2->doubleS0 = 1; + card2->secondhalf = 1; + card2->other = card; + printk(KERN_INFO + "icn: (%s and %s) ICN-4B, port 0x%x added\n", + card->interface.id, card2->interface.id, port); + return 0; +} + +#ifndef MODULE +static int __init +icn_setup(char *line) +{ + char *p, *str; + int ints[3]; + static char sid[20]; + static char sid2[20]; + + str = get_options(line, 2, ints); + if (ints[0]) + portbase = ints[1]; + if (ints[0] > 1) + membase = (unsigned long)ints[2]; + if (str && *str) { + strcpy(sid, str); + icn_id = sid; + if ((p = strchr(sid, ','))) { + *p++ = 0; + strcpy(sid2, p); + icn_id2 = sid2; + } + } + return(1); +} +__setup("icn=", icn_setup); +#endif /* MODULE */ + +static int __init icn_init(void) +{ + char *p; + char rev[10]; + + memset(&dev, 0, sizeof(icn_dev)); + dev.memaddr = (membase & 0x0ffc000); + dev.channel = -1; + dev.mcard = NULL; + dev.firstload = 1; + spin_lock_init(&dev.devlock); + + if ((p = strchr(revision, ':'))) { + strcpy(rev, p + 1); + p = strchr(rev, '$'); + *p = 0; + } else + strcpy(rev, " ??? "); + printk(KERN_NOTICE "ICN-ISDN-driver Rev%smem=0x%08lx\n", rev, + dev.memaddr); + return (icn_addcard(portbase, icn_id, icn_id2)); +} + +static void __exit icn_exit(void) +{ + isdn_ctrl cmd; + icn_card *card = cards; + icn_card *last; + int i; + unsigned long flags; + + icn_stopallcards(); + while (card) { + cmd.command = ISDN_STAT_UNLOAD; + cmd.driver = card->myid; + card->interface.statcallb(&cmd); + spin_lock_irqsave(&card->lock, flags); + if (card->rvalid) { + OUTB_P(0, ICN_RUN); /* Reset Controller */ + OUTB_P(0, ICN_MAPRAM); /* Disable RAM */ + if (card->secondhalf || (!card->doubleS0)) { + release_region(card->port, ICN_PORTLEN); + card->rvalid = 0; + } + for (i = 0; i < ICN_BCH; i++) + icn_free_queue(card, i); + } + card = card->next; + spin_unlock_irqrestore(&card->lock, flags); + } + card = cards; + cards = NULL; + while (card) { + last = card; + card = card->next; + kfree(last); + } + if (dev.mvalid) { + iounmap(dev.shmem); + release_mem_region(dev.memaddr, 0x4000); + } + printk(KERN_NOTICE "ICN-ISDN-driver unloaded\n"); +} + +module_init(icn_init); +module_exit(icn_exit); diff --git a/drivers/isdn/icn/icn.h b/drivers/isdn/icn/icn.h new file mode 100644 index 000000000000..9028cc3b5071 --- /dev/null +++ b/drivers/isdn/icn/icn.h @@ -0,0 +1,254 @@ +/* $Id: icn.h,v 1.30.6.5 2001/09/23 22:24:55 kai Exp $ + * + * ISDN lowlevel-module for the ICN active ISDN-Card. + * + * Copyright 1994 by Fritz Elfert (fritz@isdn4linux.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#ifndef icn_h +#define icn_h + +#define ICN_IOCTL_SETMMIO 0 +#define ICN_IOCTL_GETMMIO 1 +#define ICN_IOCTL_SETPORT 2 +#define ICN_IOCTL_GETPORT 3 +#define ICN_IOCTL_LOADBOOT 4 +#define ICN_IOCTL_LOADPROTO 5 +#define ICN_IOCTL_LEASEDCFG 6 +#define ICN_IOCTL_GETDOUBLE 7 +#define ICN_IOCTL_DEBUGVAR 8 +#define ICN_IOCTL_ADDCARD 9 + +/* Struct for adding new cards */ +typedef struct icn_cdef { + int port; + char id1[10]; + char id2[10]; +} icn_cdef; + +#if defined(__KERNEL__) || defined(__DEBUGVAR__) + +#ifdef __KERNEL__ +/* Kernel includes */ + +#include <linux/version.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/major.h> +#include <asm/io.h> +#include <linux/kernel.h> +#include <linux/signal.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/mman.h> +#include <linux/ioport.h> +#include <linux/timer.h> +#include <linux/wait.h> +#include <linux/delay.h> +#include <linux/isdnif.h> + +#endif /* __KERNEL__ */ + +/* some useful macros for debugging */ +#ifdef ICN_DEBUG_PORT +#define OUTB_P(v,p) {printk(KERN_DEBUG "icn: outb_p(0x%02x,0x%03x)\n",v,p); outb_p(v,p);} +#else +#define OUTB_P outb +#endif + +/* Defaults for Port-Address and shared-memory */ +#define ICN_BASEADDR 0x320 +#define ICN_PORTLEN (0x04) +#define ICN_MEMADDR 0x0d0000 + +#define ICN_FLAGS_B1ACTIVE 1 /* B-Channel-1 is open */ +#define ICN_FLAGS_B2ACTIVE 2 /* B-Channel-2 is open */ +#define ICN_FLAGS_RUNNING 4 /* Cards driver activated */ +#define ICN_FLAGS_RBTIMER 8 /* cyclic scheduling of B-Channel-poll */ + +#define ICN_BOOT_TIMEOUT1 1000 /* Delay for Boot-download (msecs) */ + +#define ICN_TIMER_BCREAD (HZ/100) /* B-Channel poll-cycle */ +#define ICN_TIMER_DCREAD (HZ/2) /* D-Channel poll-cycle */ + +#define ICN_CODE_STAGE1 4096 /* Size of bootcode */ +#define ICN_CODE_STAGE2 65536 /* Size of protocol-code */ + +#define ICN_MAX_SQUEUE 8000 /* Max. outstanding send-data (2* hw-buf.) */ +#define ICN_FRAGSIZE (250) /* Max. size of send-fragments */ +#define ICN_BCH 2 /* Number of supported channels per card */ + +/* type-definitions for accessing the mmap-io-areas */ + +#define SHM_DCTL_OFFSET (0) /* Offset to data-controlstructures in shm */ +#define SHM_CCTL_OFFSET (0x1d2) /* Offset to comm-controlstructures in shm */ +#define SHM_CBUF_OFFSET (0x200) /* Offset to comm-buffers in shm */ +#define SHM_DBUF_OFFSET (0x2000) /* Offset to data-buffers in shm */ + +/* + * Layout of card's data buffers + */ +typedef struct { + unsigned char length; /* Bytecount of fragment (max 250) */ + unsigned char endflag; /* 0=last frag., 0xff=frag. continued */ + unsigned char data[ICN_FRAGSIZE]; /* The data */ + /* Fill to 256 bytes */ + char unused[0x100 - ICN_FRAGSIZE - 2]; +} frag_buf; + +/* + * Layout of card's shared memory + */ +typedef union { + struct { + unsigned char scns; /* Index to free SendFrag. */ + unsigned char scnr; /* Index to active SendFrag READONLY */ + unsigned char ecns; /* Index to free RcvFrag. READONLY */ + unsigned char ecnr; /* Index to valid RcvFrag */ + char unused[6]; + unsigned short fuell1; /* Internal Buf Bytecount */ + } data_control; + struct { + char unused[SHM_CCTL_OFFSET]; + unsigned char iopc_i; /* Read-Ptr Status-Queue READONLY */ + unsigned char iopc_o; /* Write-Ptr Status-Queue */ + unsigned char pcio_i; /* Write-Ptr Command-Queue */ + unsigned char pcio_o; /* Read-Ptr Command Queue READONLY */ + } comm_control; + struct { + char unused[SHM_CBUF_OFFSET]; + unsigned char pcio_buf[0x100]; /* Ring-Buffer Command-Queue */ + unsigned char iopc_buf[0x100]; /* Ring-Buffer Status-Queue */ + } comm_buffers; + struct { + char unused[SHM_DBUF_OFFSET]; + frag_buf receive_buf[0x10]; + frag_buf send_buf[0x10]; + } data_buffers; +} icn_shmem; + +/* + * Per card driver data + */ +typedef struct icn_card { + struct icn_card *next; /* Pointer to next device struct */ + struct icn_card *other; /* Pointer to other card for ICN4B */ + unsigned short port; /* Base-port-address */ + int myid; /* Driver-Nr. assigned by linklevel */ + int rvalid; /* IO-portregion has been requested */ + int leased; /* Flag: This Adapter is connected */ + /* to a leased line */ + unsigned short flags; /* Statusflags */ + int doubleS0; /* Flag: ICN4B */ + int secondhalf; /* Flag: Second half of a doubleS0 */ + int fw_rev; /* Firmware revision loaded */ + int ptype; /* Protocol type (1TR6 or Euro) */ + struct timer_list st_timer; /* Timer for Status-Polls */ + struct timer_list rb_timer; /* Timer for B-Channel-Polls */ + u_char rcvbuf[ICN_BCH][4096]; /* B-Channel-Receive-Buffers */ + int rcvidx[ICN_BCH]; /* Index for above buffers */ + int l2_proto[ICN_BCH]; /* Current layer-2-protocol */ + isdn_if interface; /* Interface to upper layer */ + int iptr; /* Index to imsg-buffer */ + char imsg[60]; /* Internal buf for status-parsing */ + char msg_buf[2048]; /* Buffer for status-messages */ + char *msg_buf_write; /* Writepointer for statusbuffer */ + char *msg_buf_read; /* Readpointer for statusbuffer */ + char *msg_buf_end; /* Pointer to end of statusbuffer */ + int sndcount[ICN_BCH]; /* Byte-counters for B-Ch.-send */ + int xlen[ICN_BCH]; /* Byte-counters/Flags for sent-ACK */ + struct sk_buff *xskb[ICN_BCH]; /* Current transmitted skb */ + struct sk_buff_head spqueue[ICN_BCH]; /* Sendqueue */ + char regname[35]; /* Name used for request_region */ + u_char xmit_lock[ICN_BCH]; /* Semaphore for pollbchan_send()*/ + spinlock_t lock; /* protect critical operations */ +} icn_card; + +/* + * Main driver data + */ +typedef struct icn_dev { + spinlock_t devlock; /* spinlock to protect this struct */ + unsigned long memaddr; /* Address of memory mapped buffers */ + icn_shmem __iomem *shmem; /* Pointer to memory-mapped-buffers */ + int mvalid; /* IO-shmem has been requested */ + int channel; /* Currently mapped channel */ + struct icn_card *mcard; /* Currently mapped card */ + int chanlock; /* Semaphore for channel-mapping */ + int firstload; /* Flag: firmware never loaded */ +} icn_dev; + +typedef icn_dev *icn_devptr; + +#ifdef __KERNEL__ + +static icn_card *cards = (icn_card *) 0; +static u_char chan2bank[] = +{0, 4, 8, 12}; /* for icn_map_channel() */ + +static icn_dev dev; + +#endif /* __KERNEL__ */ + +/* Utility-Macros */ + +/* Macros for accessing ports */ +#define ICN_CFG (card->port) +#define ICN_MAPRAM (card->port+1) +#define ICN_RUN (card->port+2) +#define ICN_BANK (card->port+3) + +/* Return true, if there is a free transmit-buffer */ +#define sbfree (((readb(&dev.shmem->data_control.scns)+1) & 0xf) != \ + readb(&dev.shmem->data_control.scnr)) + +/* Switch to next transmit-buffer */ +#define sbnext (writeb((readb(&dev.shmem->data_control.scns)+1) & 0xf, \ + &dev.shmem->data_control.scns)) + +/* Shortcuts for transmit-buffer-access */ +#define sbuf_n dev.shmem->data_control.scns +#define sbuf_d dev.shmem->data_buffers.send_buf[readb(&sbuf_n)].data +#define sbuf_l dev.shmem->data_buffers.send_buf[readb(&sbuf_n)].length +#define sbuf_f dev.shmem->data_buffers.send_buf[readb(&sbuf_n)].endflag + +/* Return true, if there is receive-data is available */ +#define rbavl (readb(&dev.shmem->data_control.ecnr) != \ + readb(&dev.shmem->data_control.ecns)) + +/* Switch to next receive-buffer */ +#define rbnext (writeb((readb(&dev.shmem->data_control.ecnr)+1) & 0xf, \ + &dev.shmem->data_control.ecnr)) + +/* Shortcuts for receive-buffer-access */ +#define rbuf_n dev.shmem->data_control.ecnr +#define rbuf_d dev.shmem->data_buffers.receive_buf[readb(&rbuf_n)].data +#define rbuf_l dev.shmem->data_buffers.receive_buf[readb(&rbuf_n)].length +#define rbuf_f dev.shmem->data_buffers.receive_buf[readb(&rbuf_n)].endflag + +/* Shortcuts for command-buffer-access */ +#define cmd_o (dev.shmem->comm_control.pcio_o) +#define cmd_i (dev.shmem->comm_control.pcio_i) + +/* Return free space in command-buffer */ +#define cmd_free ((readb(&cmd_i)>=readb(&cmd_o))? \ + 0x100-readb(&cmd_i)+readb(&cmd_o): \ + readb(&cmd_o)-readb(&cmd_i)) + +/* Shortcuts for message-buffer-access */ +#define msg_o (dev.shmem->comm_control.iopc_o) +#define msg_i (dev.shmem->comm_control.iopc_i) + +/* Return length of Message, if avail. */ +#define msg_avail ((readb(&msg_o)>readb(&msg_i))? \ + 0x100-readb(&msg_o)+readb(&msg_i): \ + readb(&msg_i)-readb(&msg_o)) + +#define CID (card->interface.id) + +#endif /* defined(__KERNEL__) || defined(__DEBUGVAR__) */ +#endif /* icn_h */ diff --git a/drivers/isdn/isdnloop/Makefile b/drivers/isdn/isdnloop/Makefile new file mode 100644 index 000000000000..317cd3c5b8ee --- /dev/null +++ b/drivers/isdn/isdnloop/Makefile @@ -0,0 +1,5 @@ +# Makefile for the isdnloop ISDN device driver + +# Each configuration option enables a list of files. + +obj-$(CONFIG_ISDN_DRV_LOOP) += isdnloop.o diff --git a/drivers/isdn/isdnloop/isdnloop.c b/drivers/isdn/isdnloop/isdnloop.c new file mode 100644 index 000000000000..14e1f8fbc61f --- /dev/null +++ b/drivers/isdn/isdnloop/isdnloop.c @@ -0,0 +1,1554 @@ +/* $Id: isdnloop.c,v 1.11.6.7 2001/11/11 19:54:31 kai Exp $ + * + * ISDN low-level module implementing a dummy loop driver. + * + * Copyright 1997 by Fritz Elfert (fritz@isdn4linux.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/sched.h> +#include "isdnloop.h" + +static char *revision = "$Revision: 1.11.6.7 $"; +static char *isdnloop_id = "loop0"; + +MODULE_DESCRIPTION("ISDN4Linux: Pseudo Driver that simulates an ISDN card"); +MODULE_AUTHOR("Fritz Elfert"); +MODULE_LICENSE("GPL"); +MODULE_PARM(isdnloop_id, "s"); +MODULE_PARM_DESC(isdnloop_id, "ID-String of first card"); + +static int isdnloop_addcard(char *); + +/* + * Free queue completely. + * + * Parameter: + * card = pointer to card struct + * channel = channel number + */ +static void +isdnloop_free_queue(isdnloop_card * card, int channel) +{ + struct sk_buff_head *queue = &card->bqueue[channel]; + + skb_queue_purge(queue); + card->sndcount[channel] = 0; +} + +/* + * Send B-Channel data to another virtual card. + * This routine is called via timer-callback from isdnloop_pollbchan(). + * + * Parameter: + * card = pointer to card struct. + * ch = channel number (0-based) + */ +static void +isdnloop_bchan_send(isdnloop_card * card, int ch) +{ + isdnloop_card *rcard = card->rcard[ch]; + int rch = card->rch[ch], len, ack; + struct sk_buff *skb; + isdn_ctrl cmd; + + while (card->sndcount[ch]) { + if ((skb = skb_dequeue(&card->bqueue[ch]))) { + len = skb->len; + card->sndcount[ch] -= len; + ack = *(skb->head); /* used as scratch area */ + cmd.driver = card->myid; + cmd.arg = ch; + if (rcard){ + rcard->interface.rcvcallb_skb(rcard->myid, rch, skb); + } else { + printk(KERN_WARNING "isdnloop: no rcard, skb dropped\n"); + dev_kfree_skb(skb); + + }; + cmd.command = ISDN_STAT_BSENT; + cmd.parm.length = len; + card->interface.statcallb(&cmd); + } else + card->sndcount[ch] = 0; + } +} + +/* + * Send/Receive Data to/from the B-Channel. + * This routine is called via timer-callback. + * It schedules itself while any B-Channel is open. + * + * Parameter: + * data = pointer to card struct, set by kernel timer.data + */ +static void +isdnloop_pollbchan(unsigned long data) +{ + isdnloop_card *card = (isdnloop_card *) data; + unsigned long flags; + + if (card->flags & ISDNLOOP_FLAGS_B1ACTIVE) + isdnloop_bchan_send(card, 0); + if (card->flags & ISDNLOOP_FLAGS_B2ACTIVE) + isdnloop_bchan_send(card, 1); + if (card->flags & (ISDNLOOP_FLAGS_B1ACTIVE | ISDNLOOP_FLAGS_B2ACTIVE)) { + /* schedule b-channel polling again */ + save_flags(flags); + cli(); + card->rb_timer.expires = jiffies + ISDNLOOP_TIMER_BCREAD; + add_timer(&card->rb_timer); + card->flags |= ISDNLOOP_FLAGS_RBTIMER; + restore_flags(flags); + } else + card->flags &= ~ISDNLOOP_FLAGS_RBTIMER; +} + +/* + * Parse ICN-type setup string and fill fields of setup-struct + * with parsed data. + * + * Parameter: + * setup = setup string, format: [caller-id],si1,si2,[called-id] + * cmd = pointer to struct to be filled. + */ +static void +isdnloop_parse_setup(char *setup, isdn_ctrl * cmd) +{ + char *t = setup; + char *s = strchr(t, ','); + + *s++ = '\0'; + strlcpy(cmd->parm.setup.phone, t, sizeof(cmd->parm.setup.phone)); + s = strchr(t = s, ','); + *s++ = '\0'; + if (!strlen(t)) + cmd->parm.setup.si1 = 0; + else + cmd->parm.setup.si1 = simple_strtoul(t, NULL, 10); + s = strchr(t = s, ','); + *s++ = '\0'; + if (!strlen(t)) + cmd->parm.setup.si2 = 0; + else + cmd->parm.setup.si2 = + simple_strtoul(t, NULL, 10); + strlcpy(cmd->parm.setup.eazmsn, s, sizeof(cmd->parm.setup.eazmsn)); + cmd->parm.setup.plan = 0; + cmd->parm.setup.screen = 0; +} + +typedef struct isdnloop_stat { + char *statstr; + int command; + int action; +} isdnloop_stat; +/* *INDENT-OFF* */ +static isdnloop_stat isdnloop_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 */ + {"E_L1: ACT FAIL", ISDN_STAT_BHUP, 8}, /* Layer-1 activation failed */ + {"E_L2: DATA LIN", ISDN_STAT_BHUP, 8}, /* Layer-2 data link lost */ + {"E_L1: ACTIVATION FAILED", + ISDN_STAT_BHUP, 8}, /* Layer-1 activation failed */ + {NULL, 0, -1} +}; +/* *INDENT-ON* */ + + +/* + * Parse Status message-strings from virtual card. + * Depending on status, call statcallb for sending messages to upper + * levels. Also set/reset B-Channel active-flags. + * + * Parameter: + * status = status string to parse. + * channel = channel where message comes from. + * card = card where message comes from. + */ +static void +isdnloop_parse_status(u_char * status, int channel, isdnloop_card * card) +{ + isdnloop_stat *s = isdnloop_stat_table; + int action = -1; + isdn_ctrl cmd; + + while (s->statstr) { + if (!strncmp(status, s->statstr, strlen(s->statstr))) { + cmd.command = s->command; + action = s->action; + break; + } + s++; + } + if (action == -1) + return; + cmd.driver = card->myid; + cmd.arg = channel; + switch (action) { + case 1: + /* BCON_x */ + card->flags |= (channel) ? + ISDNLOOP_FLAGS_B2ACTIVE : ISDNLOOP_FLAGS_B1ACTIVE; + break; + case 2: + /* BDIS_x */ + card->flags &= ~((channel) ? + ISDNLOOP_FLAGS_B2ACTIVE : ISDNLOOP_FLAGS_B1ACTIVE); + isdnloop_free_queue(card, channel); + break; + case 3: + /* DCAL_I and DSCA_I */ + isdnloop_parse_setup(status + 6, &cmd); + break; + case 4: + /* FCALL */ + sprintf(cmd.parm.setup.phone, "LEASED%d", card->myid); + sprintf(cmd.parm.setup.eazmsn, "%d", channel + 1); + cmd.parm.setup.si1 = 7; + cmd.parm.setup.si2 = 0; + cmd.parm.setup.plan = 0; + cmd.parm.setup.screen = 0; + break; + case 5: + /* CIF */ + strlcpy(cmd.parm.num, status + 3, sizeof(cmd.parm.num)); + break; + case 6: + /* AOC */ + snprintf(cmd.parm.num, sizeof(cmd.parm.num), "%d", + (int) simple_strtoul(status + 7, NULL, 16)); + break; + case 7: + /* CAU */ + status += 3; + if (strlen(status) == 4) + snprintf(cmd.parm.num, sizeof(cmd.parm.num), "%s%c%c", + status + 2, *status, *(status + 1)); + else + strlcpy(cmd.parm.num, status + 1, sizeof(cmd.parm.num)); + break; + case 8: + /* Misc Errors on L1 and L2 */ + card->flags &= ~ISDNLOOP_FLAGS_B1ACTIVE; + isdnloop_free_queue(card, 0); + 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; + card->flags &= ~ISDNLOOP_FLAGS_B2ACTIVE; + isdnloop_free_queue(card, 1); + 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); +} + +/* + * Store a cwcharacter into ringbuffer for reading from /dev/isdnctrl + * + * Parameter: + * card = pointer to card struct. + * c = char to store. + */ +static void +isdnloop_putmsg(isdnloop_card * card, unsigned char c) +{ + ulong flags; + + 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); +} + +/* + * Poll a virtual cards message queue. + * If there are new status-replies from the card, copy them to + * ringbuffer for reading on /dev/isdnctrl and call + * isdnloop_parse_status() for processing them. Watch for special + * Firmware bootmessage and parse it, to get the D-Channel protocol. + * If there are B-Channels open, initiate a timer-callback to + * isdnloop_pollbchan(). + * This routine is called periodically via timer interrupt. + * + * Parameter: + * data = pointer to card struct + */ +static void +isdnloop_polldchan(unsigned long data) +{ + isdnloop_card *card = (isdnloop_card *) data; + struct sk_buff *skb; + int avail; + int left; + u_char c; + int ch; + unsigned long flags; + u_char *p; + isdn_ctrl cmd; + + if ((skb = skb_dequeue(&card->dqueue))) + avail = skb->len; + else + avail = 0; + for (left = avail; left > 0; left--) { + c = *skb->data; + skb_pull(skb, 1); + isdnloop_putmsg(card, c); + card->imsg[card->iptr] = c; + if (card->iptr < 59) + card->iptr++; + if (!skb->len) { + avail++; + isdnloop_putmsg(card, '\n'); + 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]; + isdnloop_parse_status(p, ch, card); + } else { + p = card->imsg; + if (!strncmp(p, "DRV1.", 5)) { + printk(KERN_INFO "isdnloop: (%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 + "isdnloop: (%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 + "isdnloop: (%s) Euro-Protocol loaded and running\n", CID); + } + continue; + + } + } + } + } + if (avail) { + cmd.command = ISDN_STAT_STAVAIL; + cmd.driver = card->myid; + cmd.arg = avail; + card->interface.statcallb(&cmd); + } + if (card->flags & (ISDNLOOP_FLAGS_B1ACTIVE | ISDNLOOP_FLAGS_B2ACTIVE)) + if (!(card->flags & ISDNLOOP_FLAGS_RBTIMER)) { + /* schedule b-channel polling */ + card->flags |= ISDNLOOP_FLAGS_RBTIMER; + save_flags(flags); + cli(); + del_timer(&card->rb_timer); + card->rb_timer.function = isdnloop_pollbchan; + card->rb_timer.data = (unsigned long) card; + card->rb_timer.expires = jiffies + ISDNLOOP_TIMER_BCREAD; + add_timer(&card->rb_timer); + restore_flags(flags); + } + /* schedule again */ + save_flags(flags); + cli(); + card->st_timer.expires = jiffies + ISDNLOOP_TIMER_DCREAD; + add_timer(&card->st_timer); + restore_flags(flags); +} + +/* + * Append a packet to the transmit buffer-queue. + * + * Parameter: + * channel = Number of B-channel + * skb = packet to send. + * card = pointer to card-struct + * Return: + * Number of bytes transferred, -E??? on error + */ +static int +isdnloop_sendbuf(int channel, struct sk_buff *skb, isdnloop_card * card) +{ + int len = skb->len; + unsigned long flags; + struct sk_buff *nskb; + + if (len > 4000) { + printk(KERN_WARNING + "isdnloop: Send packet too large\n"); + return -EINVAL; + } + if (len) { + if (!(card->flags & (channel) ? ISDNLOOP_FLAGS_B2ACTIVE : ISDNLOOP_FLAGS_B1ACTIVE)) + return 0; + if (card->sndcount[channel] > ISDNLOOP_MAX_SQUEUE) + return 0; + save_flags(flags); + cli(); + nskb = dev_alloc_skb(skb->len); + if (nskb) { + memcpy(skb_put(nskb, len), skb->data, len); + skb_queue_tail(&card->bqueue[channel], nskb); + dev_kfree_skb(skb); + } else + len = 0; + card->sndcount[channel] += len; + restore_flags(flags); + } + return len; +} + +/* + * Read the messages from the card's ringbuffer + * + * Parameter: + * buf = pointer to buffer. + * len = number of bytes to read. + * user = flag, 1: called from userlevel 0: called from kernel. + * card = pointer to card struct. + * Return: + * number of bytes actually transferred. + */ +static int +isdnloop_readstatus(u_char __user *buf, int len, isdnloop_card * card) +{ + int count; + u_char __user *p; + + for (p = buf, count = 0; count < len; p++, count++) { + if (card->msg_buf_read == card->msg_buf_write) + return count; + put_user(*card->msg_buf_read++, p); + if (card->msg_buf_read > card->msg_buf_end) + card->msg_buf_read = card->msg_buf; + } + return count; +} + +/* + * Simulate a card's response by appending it to the cards + * message queue. + * + * Parameter: + * card = pointer to card struct. + * s = pointer to message-string. + * ch = channel: 0 = generic messages, 1 and 2 = D-channel messages. + * Return: + * 0 on success, 1 on memory squeeze. + */ +static int +isdnloop_fake(isdnloop_card * card, char *s, int ch) +{ + struct sk_buff *skb; + int len = strlen(s) + ((ch >= 0) ? 3 : 0); + + if (!(skb = dev_alloc_skb(len))) { + printk(KERN_WARNING "isdnloop: Out of memory in isdnloop_fake\n"); + return 1; + } + if (ch >= 0) + sprintf(skb_put(skb, 3), "%02d;", ch); + memcpy(skb_put(skb, strlen(s)), s, strlen(s)); + skb_queue_tail(&card->dqueue, skb); + return 0; +} +/* *INDENT-OFF* */ +static isdnloop_stat isdnloop_cmd_table[] = +{ + {"BCON_R", 0, 1}, /* B-Channel connect */ + {"BCON_I", 0, 17}, /* B-Channel connect ind */ + {"BDIS_R", 0, 2}, /* B-Channel disconnect */ + {"DDIS_R", 0, 3}, /* D-Channel disconnect */ + {"DCON_R", 0, 16}, /* D-Channel connect */ + {"DSCA_R", 0, 4}, /* Dial 1TR6-SPV */ + {"DCAL_R", 0, 5}, /* Dial */ + {"EAZC", 0, 6}, /* Clear EAZ listener */ + {"EAZ", 0, 7}, /* Set EAZ listener */ + {"SEEAZ", 0, 8}, /* Get EAZ listener */ + {"MSN", 0, 9}, /* Set/Clear MSN listener */ + {"MSALL", 0, 10}, /* Set multi MSN listeners */ + {"SETSIL", 0, 11}, /* Set SI list */ + {"SEESIL", 0, 12}, /* Get SI list */ + {"SILC", 0, 13}, /* Clear SI list */ + {"LOCK", 0, -1}, /* LOCK channel */ + {"UNLOCK", 0, -1}, /* UNLOCK channel */ + {"FV2ON", 1, 14}, /* Leased mode on */ + {"FV2OFF", 1, 15}, /* Leased mode off */ + {NULL, 0, -1} +}; +/* *INDENT-ON* */ + + +/* + * Simulate an error-response from a card. + * + * Parameter: + * card = pointer to card struct. + */ +static void +isdnloop_fake_err(isdnloop_card * card) +{ + char buf[60]; + + sprintf(buf, "E%s", card->omsg); + isdnloop_fake(card, buf, -1); + isdnloop_fake(card, "NAK", -1); +} + +static u_char ctable_eu[] = +{0x00, 0x11, 0x01, 0x12}; +static u_char ctable_1t[] = +{0x00, 0x3b, 0x01, 0x3a}; + +/* + * Assemble a simplified cause message depending on the + * D-channel protocol used. + * + * Parameter: + * card = pointer to card struct. + * loc = location: 0 = local, 1 = remote. + * cau = cause: 1 = busy, 2 = nonexistent callerid, 3 = no user responding. + * Return: + * Pointer to buffer containing the assembled message. + */ +static char * +isdnloop_unicause(isdnloop_card * card, int loc, int cau) +{ + static char buf[6]; + + switch (card->ptype) { + case ISDN_PTYPE_EURO: + sprintf(buf, "E%02X%02X", (loc) ? 4 : 2, ctable_eu[cau]); + break; + case ISDN_PTYPE_1TR6: + sprintf(buf, "%02X44", ctable_1t[cau]); + break; + default: + return ("0000"); + } + return (buf); +} + +/* + * Release a virtual connection. Called from timer interrupt, when + * called party did not respond. + * + * Parameter: + * card = pointer to card struct. + * ch = channel (0-based) + */ +static void +isdnloop_atimeout(isdnloop_card * card, int ch) +{ + unsigned long flags; + char buf[60]; + + save_flags(flags); + cli(); + if (card->rcard) { + isdnloop_fake(card->rcard[ch], "DDIS_I", card->rch[ch] + 1); + card->rcard[ch]->rcard[card->rch[ch]] = NULL; + card->rcard[ch] = NULL; + } + isdnloop_fake(card, "DDIS_I", ch + 1); + /* No user responding */ + sprintf(buf, "CAU%s", isdnloop_unicause(card, 1, 3)); + isdnloop_fake(card, buf, ch + 1); + restore_flags(flags); +} + +/* + * Wrapper for isdnloop_atimeout(). + */ +static void +isdnloop_atimeout0(unsigned long data) +{ + isdnloop_card *card = (isdnloop_card *) data; + isdnloop_atimeout(card, 0); +} + +/* + * Wrapper for isdnloop_atimeout(). + */ +static void +isdnloop_atimeout1(unsigned long data) +{ + isdnloop_card *card = (isdnloop_card *) data; + isdnloop_atimeout(card, 1); +} + +/* + * Install a watchdog for a user, not responding. + * + * Parameter: + * card = pointer to card struct. + * ch = channel to watch for. + */ +static void +isdnloop_start_ctimer(isdnloop_card * card, int ch) +{ + unsigned long flags; + + save_flags(flags); + cli(); + init_timer(&card->c_timer[ch]); + card->c_timer[ch].expires = jiffies + ISDNLOOP_TIMER_ALERTWAIT; + if (ch) + card->c_timer[ch].function = isdnloop_atimeout1; + else + card->c_timer[ch].function = isdnloop_atimeout0; + card->c_timer[ch].data = (unsigned long) card; + add_timer(&card->c_timer[ch]); + restore_flags(flags); +} + +/* + * Kill a pending channel watchdog. + * + * Parameter: + * card = pointer to card struct. + * ch = channel (0-based). + */ +static void +isdnloop_kill_ctimer(isdnloop_card * card, int ch) +{ + unsigned long flags; + + save_flags(flags); + cli(); + del_timer(&card->c_timer[ch]); + restore_flags(flags); +} + +static u_char si2bit[] = +{0, 1, 0, 0, 0, 2, 0, 4, 0, 0}; +static u_char bit2si[] = +{1, 5, 7}; + +/* + * Try finding a listener for an outgoing call. + * + * Parameter: + * card = pointer to calling card. + * p = pointer to ICN-type setup-string. + * lch = channel of calling card. + * cmd = pointer to struct to be filled when parsing setup. + * Return: + * 0 = found match, alerting should happen. + * 1 = found matching number but it is busy. + * 2 = no matching listener. + * 3 = found matching number but SI does not match. + */ +static int +isdnloop_try_call(isdnloop_card * card, char *p, int lch, isdn_ctrl * cmd) +{ + isdnloop_card *cc = cards; + unsigned long flags; + int ch; + int num_match; + int i; + char *e; + char nbuf[32]; + + isdnloop_parse_setup(p, cmd); + while (cc) { + for (ch = 0; ch < 2; ch++) { + /* Exclude ourself */ + if ((cc == card) && (ch == lch)) + continue; + num_match = 0; + switch (cc->ptype) { + case ISDN_PTYPE_EURO: + for (i = 0; i < 3; i++) + if (!(strcmp(cc->s0num[i], cmd->parm.setup.phone))) + num_match = 1; + break; + case ISDN_PTYPE_1TR6: + e = cc->eazlist[ch]; + while (*e) { + sprintf(nbuf, "%s%c", cc->s0num[0], *e); + if (!(strcmp(nbuf, cmd->parm.setup.phone))) + num_match = 1; + e++; + } + } + if (num_match) { + save_flags(flags); + cli(); + /* channel idle? */ + if (!(cc->rcard[ch])) { + /* Check SI */ + if (!(si2bit[cmd->parm.setup.si1] & cc->sil[ch])) { + restore_flags(flags); + return 3; + } + /* ch is idle, si and number matches */ + cc->rcard[ch] = card; + cc->rch[ch] = lch; + card->rcard[lch] = cc; + card->rch[lch] = ch; + restore_flags(flags); + return 0; + } else { + restore_flags(flags); + /* num matches, but busy */ + if (ch == 1) + return 1; + } + } + } + cc = cc->next; + } + return 2; +} + +/* + * Depending on D-channel protocol and caller/called, modify + * phone number. + * + * Parameter: + * card = pointer to card struct. + * phone = pointer phone number. + * caller = flag: 1 = caller, 0 = called. + * Return: + * pointer to new phone number. + */ +static char * +isdnloop_vstphone(isdnloop_card * card, char *phone, int caller) +{ + int i; + static char nphone[30]; + + if (!card) { + printk("BUG!!!\n"); + return ""; + } + switch (card->ptype) { + case ISDN_PTYPE_EURO: + if (caller) { + for (i = 0; i < 2; i++) + if (!(strcmp(card->s0num[i], phone))) + return (phone); + return (card->s0num[0]); + } + return (phone); + break; + case ISDN_PTYPE_1TR6: + if (caller) { + sprintf(nphone, "%s%c", card->s0num[0], phone[0]); + return (nphone); + } else + return (&phone[strlen(phone) - 1]); + break; + } + return ""; +} + +/* + * Parse an ICN-type command string sent to the 'card'. + * Perform misc. actions depending on the command. + * + * Parameter: + * card = pointer to card struct. + */ +static void +isdnloop_parse_cmd(isdnloop_card * card) +{ + char *p = card->omsg; + isdn_ctrl cmd; + char buf[60]; + isdnloop_stat *s = isdnloop_cmd_table; + int action = -1; + int i; + int ch; + + if ((card->omsg[0] != '0') && (card->omsg[2] != ';')) { + isdnloop_fake_err(card); + return; + } + ch = card->omsg[1] - '0'; + if ((ch < 0) || (ch > 2)) { + isdnloop_fake_err(card); + return; + } + p += 3; + while (s->statstr) { + if (!strncmp(p, s->statstr, strlen(s->statstr))) { + action = s->action; + if (s->command && (ch != 0)) { + isdnloop_fake_err(card); + return; + } + break; + } + s++; + } + if (action == -1) + return; + switch (action) { + case 1: + /* 0x;BCON_R */ + if (card->rcard[ch - 1]) { + isdnloop_fake(card->rcard[ch - 1], "BCON_I", + card->rch[ch - 1] + 1); + isdnloop_fake(card, "BCON_C", ch); + } + break; + case 17: + /* 0x;BCON_I */ + if (card->rcard[ch - 1]) { + isdnloop_fake(card->rcard[ch - 1], "BCON_C", + card->rch[ch - 1] + 1); + } + break; + case 2: + /* 0x;BDIS_R */ + isdnloop_fake(card, "BDIS_C", ch); + if (card->rcard[ch - 1]) { + isdnloop_fake(card->rcard[ch - 1], "BDIS_I", + card->rch[ch - 1] + 1); + } + break; + case 16: + /* 0x;DCON_R */ + isdnloop_kill_ctimer(card, ch - 1); + if (card->rcard[ch - 1]) { + isdnloop_kill_ctimer(card->rcard[ch - 1], card->rch[ch - 1]); + isdnloop_fake(card->rcard[ch - 1], "DCON_C", + card->rch[ch - 1] + 1); + isdnloop_fake(card, "DCON_C", ch); + } + break; + case 3: + /* 0x;DDIS_R */ + isdnloop_kill_ctimer(card, ch - 1); + if (card->rcard[ch - 1]) { + isdnloop_kill_ctimer(card->rcard[ch - 1], card->rch[ch - 1]); + isdnloop_fake(card->rcard[ch - 1], "DDIS_I", + card->rch[ch - 1] + 1); + card->rcard[ch - 1] = NULL; + } + isdnloop_fake(card, "DDIS_C", ch); + break; + case 4: + /* 0x;DSCA_Rdd,yy,zz,oo */ + if (card->ptype != ISDN_PTYPE_1TR6) { + isdnloop_fake_err(card); + return; + } + /* Fall through */ + case 5: + /* 0x;DCAL_Rdd,yy,zz,oo */ + p += 6; + switch (isdnloop_try_call(card, p, ch - 1, &cmd)) { + case 0: + /* Alerting */ + sprintf(buf, "D%s_I%s,%02d,%02d,%s", + (action == 4) ? "SCA" : "CAL", + isdnloop_vstphone(card, cmd.parm.setup.eazmsn, 1), + cmd.parm.setup.si1, + cmd.parm.setup.si2, + isdnloop_vstphone(card->rcard[ch - 1], + cmd.parm.setup.phone, 0)); + isdnloop_fake(card->rcard[ch - 1], buf, card->rch[ch - 1] + 1); + /* Fall through */ + case 3: + /* si1 does not match, don't alert but start timer */ + isdnloop_start_ctimer(card, ch - 1); + break; + case 1: + /* Remote busy */ + isdnloop_fake(card, "DDIS_I", ch); + sprintf(buf, "CAU%s", isdnloop_unicause(card, 1, 1)); + isdnloop_fake(card, buf, ch); + break; + case 2: + /* No such user */ + isdnloop_fake(card, "DDIS_I", ch); + sprintf(buf, "CAU%s", isdnloop_unicause(card, 1, 2)); + isdnloop_fake(card, buf, ch); + break; + } + break; + case 6: + /* 0x;EAZC */ + card->eazlist[ch - 1][0] = '\0'; + break; + case 7: + /* 0x;EAZ */ + p += 3; + strcpy(card->eazlist[ch - 1], p); + break; + case 8: + /* 0x;SEEAZ */ + sprintf(buf, "EAZ-LIST: %s", card->eazlist[ch - 1]); + isdnloop_fake(card, buf, ch + 1); + break; + case 9: + /* 0x;MSN */ + break; + case 10: + /* 0x;MSNALL */ + break; + case 11: + /* 0x;SETSIL */ + p += 6; + i = 0; + while (strchr("0157", *p)) { + if (i) + card->sil[ch - 1] |= si2bit[*p - '0']; + i = (*p++ == '0'); + } + if (*p) + isdnloop_fake_err(card); + break; + case 12: + /* 0x;SEESIL */ + sprintf(buf, "SIN-LIST: "); + p = buf + 10; + for (i = 0; i < 3; i++) + if (card->sil[ch - 1] & (1 << i)) + p += sprintf(p, "%02d", bit2si[i]); + isdnloop_fake(card, buf, ch + 1); + break; + case 13: + /* 0x;SILC */ + card->sil[ch - 1] = 0; + break; + case 14: + /* 00;FV2ON */ + break; + case 15: + /* 00;FV2OFF */ + break; + } +} + +/* + * Put command-strings into the of the 'card'. In reality, execute them + * right in place by calling isdnloop_parse_cmd(). Also copy every + * command to the read message ringbuffer, preceeding it with a '>'. + * These mesagges can be read at /dev/isdnctrl. + * + * Parameter: + * buf = pointer to command buffer. + * len = length of buffer data. + * user = flag: 1 = called form userlevel, 0 called from kernel. + * card = pointer to card struct. + * Return: + * number of bytes transferred (currently always equals len). + */ +static int +isdnloop_writecmd(const u_char * buf, int len, int user, isdnloop_card * card) +{ + int xcount = 0; + int ocount = 1; + isdn_ctrl cmd; + + while (len) { + int count = len; + u_char *p; + u_char msg[0x100]; + + if (count > 255) + count = 255; + if (user) { + if (copy_from_user(msg, buf, count)) + return -EFAULT; + } else + memcpy(msg, buf, count); + isdnloop_putmsg(card, '>'); + for (p = msg; count > 0; count--, p++) { + len--; + xcount++; + isdnloop_putmsg(card, *p); + card->omsg[card->optr] = *p; + if (*p == '\n') { + card->omsg[card->optr] = '\0'; + card->optr = 0; + isdnloop_parse_cmd(card); + if (len) { + isdnloop_putmsg(card, '>'); + ocount++; + } + } else { + if (card->optr < 59) + card->optr++; + } + ocount++; + } + } + cmd.command = ISDN_STAT_STAVAIL; + cmd.driver = card->myid; + cmd.arg = ocount; + card->interface.statcallb(&cmd); + return xcount; +} + +/* + * Delete card's pending timers, send STOP to linklevel + */ +static void +isdnloop_stopcard(isdnloop_card * card) +{ + unsigned long flags; + isdn_ctrl cmd; + + save_flags(flags); + cli(); + if (card->flags & ISDNLOOP_FLAGS_RUNNING) { + card->flags &= ~ISDNLOOP_FLAGS_RUNNING; + del_timer(&card->st_timer); + del_timer(&card->rb_timer); + del_timer(&card->c_timer[0]); + del_timer(&card->c_timer[1]); + cmd.command = ISDN_STAT_STOP; + cmd.driver = card->myid; + card->interface.statcallb(&cmd); + } + restore_flags(flags); +} + +/* + * Stop all cards before unload. + */ +static void +isdnloop_stopallcards(void) +{ + isdnloop_card *p = cards; + + while (p) { + isdnloop_stopcard(p); + p = p->next; + } +} + +/* + * Start a 'card'. Simulate card's boot message and set the phone + * number(s) of the virtual 'S0-Interface'. Install D-channel + * poll timer. + * + * Parameter: + * card = pointer to card struct. + * sdefp = pointer to struct holding ioctl parameters. + * Return: + * 0 on success, -E??? otherwise. + */ +static int +isdnloop_start(isdnloop_card * card, isdnloop_sdef * sdefp) +{ + unsigned long flags; + isdnloop_sdef sdef; + int i; + + if (card->flags & ISDNLOOP_FLAGS_RUNNING) + return -EBUSY; + if (copy_from_user((char *) &sdef, (char *) sdefp, sizeof(sdef))) + return -EFAULT; + save_flags(flags); + cli(); + switch (sdef.ptype) { + case ISDN_PTYPE_EURO: + if (isdnloop_fake(card, "DRV1.23EC-Q.931-CAPI-CNS-BASIS-20.02.96", + -1)) { + restore_flags(flags); + return -ENOMEM; + } + card->sil[0] = card->sil[1] = 4; + if (isdnloop_fake(card, "TEI OK", 0)) { + restore_flags(flags); + return -ENOMEM; + } + for (i = 0; i < 3; i++) + strcpy(card->s0num[i], sdef.num[i]); + break; + case ISDN_PTYPE_1TR6: + if (isdnloop_fake(card, "DRV1.04TC-1TR6-CAPI-CNS-BASIS-29.11.95", + -1)) { + restore_flags(flags); + return -ENOMEM; + } + card->sil[0] = card->sil[1] = 4; + if (isdnloop_fake(card, "TEI OK", 0)) { + restore_flags(flags); + return -ENOMEM; + } + strcpy(card->s0num[0], sdef.num[0]); + card->s0num[1][0] = '\0'; + card->s0num[2][0] = '\0'; + break; + default: + restore_flags(flags); + printk(KERN_WARNING "isdnloop: Illegal D-channel protocol %d\n", + sdef.ptype); + return -EINVAL; + } + init_timer(&card->st_timer); + card->st_timer.expires = jiffies + ISDNLOOP_TIMER_DCREAD; + card->st_timer.function = isdnloop_polldchan; + card->st_timer.data = (unsigned long) card; + add_timer(&card->st_timer); + card->flags |= ISDNLOOP_FLAGS_RUNNING; + restore_flags(flags); + return 0; +} + +/* + * Main handler for commands sent by linklevel. + */ +static int +isdnloop_command(isdn_ctrl * c, isdnloop_card * card) +{ + ulong a; + int i; + char cbuf[60]; + isdn_ctrl cmd; + isdnloop_cdef cdef; + + switch (c->command) { + case ISDN_CMD_IOCTL: + memcpy(&a, c->parm.num, sizeof(ulong)); + switch (c->arg) { + case ISDNLOOP_IOCTL_DEBUGVAR: + return (ulong) card; + case ISDNLOOP_IOCTL_STARTUP: + if (!access_ok(VERIFY_READ, (void *) a, sizeof(isdnloop_sdef))) + return -EFAULT; + return (isdnloop_start(card, (isdnloop_sdef *) a)); + break; + case ISDNLOOP_IOCTL_ADDCARD: + if (copy_from_user((char *)&cdef, + (char *)a, + sizeof(cdef))) + return -EFAULT; + return (isdnloop_addcard(cdef.id1)); + break; + case ISDNLOOP_IOCTL_LEASEDCFG: + if (a) { + if (!card->leased) { + card->leased = 1; + while (card->ptype == ISDN_PTYPE_UNKNOWN) { + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(10); + } + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(10); + sprintf(cbuf, "00;FV2ON\n01;EAZ1\n02;EAZ2\n"); + i = isdnloop_writecmd(cbuf, strlen(cbuf), 0, card); + printk(KERN_INFO + "isdnloop: (%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 = isdnloop_writecmd(cbuf, strlen(cbuf), 0, card); + printk(KERN_INFO + "isdnloop: (%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 & ISDNLOOP_FLAGS_RUNNING) + return -ENODEV; + if (card->leased) + break; + if ((c->arg & 255) < ISDNLOOP_BCH) { + char *p; + char dial[50]; + char dcode[4]; + + a = c->arg; + p = c->parm.setup.phone; + if (*p == 's' || *p == 'S') { + /* Dial for SPV */ + p++; + strcpy(dcode, "SCA"); + } else + /* Normal Dial */ + strcpy(dcode, "CAL"); + strcpy(dial, p); + sprintf(cbuf, "%02d;D%s_R%s,%02d,%02d,%s\n", (int) (a + 1), + dcode, dial, c->parm.setup.si1, + c->parm.setup.si2, c->parm.setup.eazmsn); + i = isdnloop_writecmd(cbuf, strlen(cbuf), 0, card); + } + break; + case ISDN_CMD_ACCEPTD: + if (!card->flags & ISDNLOOP_FLAGS_RUNNING) + return -ENODEV; + if (c->arg < ISDNLOOP_BCH) { + a = c->arg + 1; + cbuf[0] = 0; + switch (card->l2_proto[a - 1]) { + case ISDN_PROTO_L2_X75I: + sprintf(cbuf, "%02d;BX75\n", (int) a); + break; +#ifdef CONFIG_ISDN_X25 + case ISDN_PROTO_L2_X25DTE: + sprintf(cbuf, "%02d;BX2T\n", (int) a); + break; + case ISDN_PROTO_L2_X25DCE: + sprintf(cbuf, "%02d;BX2C\n", (int) a); + break; +#endif + case ISDN_PROTO_L2_HDLC: + sprintf(cbuf, "%02d;BTRA\n", (int) a); + break; + } + if (strlen(cbuf)) + i = isdnloop_writecmd(cbuf, strlen(cbuf), 0, card); + sprintf(cbuf, "%02d;DCON_R\n", (int) a); + i = isdnloop_writecmd(cbuf, strlen(cbuf), 0, card); + } + break; + case ISDN_CMD_ACCEPTB: + if (!card->flags & ISDNLOOP_FLAGS_RUNNING) + return -ENODEV; + if (c->arg < ISDNLOOP_BCH) { + a = c->arg + 1; + switch (card->l2_proto[a - 1]) { + case ISDN_PROTO_L2_X75I: + sprintf(cbuf, "%02d;BCON_R,BX75\n", (int) a); + break; +#ifdef CONFIG_ISDN_X25 + case ISDN_PROTO_L2_X25DTE: + sprintf(cbuf, "%02d;BCON_R,BX2T\n", (int) a); + break; + case ISDN_PROTO_L2_X25DCE: + sprintf(cbuf, "%02d;BCON_R,BX2C\n", (int) a); + break; +#endif + case ISDN_PROTO_L2_HDLC: + sprintf(cbuf, "%02d;BCON_R,BTRA\n", (int) a); + break; + default: + sprintf(cbuf, "%02d;BCON_R\n", (int) a); + } + printk(KERN_DEBUG "isdnloop writecmd '%s'\n", cbuf); + i = isdnloop_writecmd(cbuf, strlen(cbuf), 0, card); + break; + case ISDN_CMD_HANGUP: + if (!card->flags & ISDNLOOP_FLAGS_RUNNING) + return -ENODEV; + if (c->arg < ISDNLOOP_BCH) { + a = c->arg + 1; + sprintf(cbuf, "%02d;BDIS_R\n%02d;DDIS_R\n", (int) a, (int) a); + i = isdnloop_writecmd(cbuf, strlen(cbuf), 0, card); + } + break; + case ISDN_CMD_SETEAZ: + if (!card->flags & ISDNLOOP_FLAGS_RUNNING) + return -ENODEV; + if (card->leased) + break; + if (c->arg < ISDNLOOP_BCH) { + a = c->arg + 1; + if (card->ptype == ISDN_PTYPE_EURO) { + sprintf(cbuf, "%02d;MS%s%s\n", (int) a, + c->parm.num[0] ? "N" : "ALL", c->parm.num); + } else + sprintf(cbuf, "%02d;EAZ%s\n", (int) a, + c->parm.num[0] ? c->parm.num : (u_char *) "0123456789"); + i = isdnloop_writecmd(cbuf, strlen(cbuf), 0, card); + } + break; + case ISDN_CMD_CLREAZ: + if (!card->flags & ISDNLOOP_FLAGS_RUNNING) + return -ENODEV; + if (card->leased) + break; + if (c->arg < ISDNLOOP_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 = isdnloop_writecmd(cbuf, strlen(cbuf), 0, card); + } + break; + case ISDN_CMD_SETL2: + if (!card->flags & ISDNLOOP_FLAGS_RUNNING) + return -ENODEV; + if ((c->arg & 255) < ISDNLOOP_BCH) { + a = c->arg; + switch (a >> 8) { + case ISDN_PROTO_L2_X75I: + sprintf(cbuf, "%02d;BX75\n", (int) (a & 255) + 1); + break; +#ifdef CONFIG_ISDN_X25 + case ISDN_PROTO_L2_X25DTE: + sprintf(cbuf, "%02d;BX2T\n", (int) (a & 255) + 1); + break; + case ISDN_PROTO_L2_X25DCE: + sprintf(cbuf, "%02d;BX2C\n", (int) (a & 255) + 1); + break; +#endif + case ISDN_PROTO_L2_HDLC: + sprintf(cbuf, "%02d;BTRA\n", (int) (a & 255) + 1); + break; + case ISDN_PROTO_L2_TRANS: + sprintf(cbuf, "%02d;BTRA\n", (int) (a & 255) + 1); + break; + default: + return -EINVAL; + } + i = isdnloop_writecmd(cbuf, strlen(cbuf), 0, card); + card->l2_proto[a & 255] = (a >> 8); + } + break; + case ISDN_CMD_SETL3: + if (!card->flags & ISDNLOOP_FLAGS_RUNNING) + return -ENODEV; + return 0; + default: + return -EINVAL; + } + } + return 0; +} + +/* + * Find card with given driverId + */ +static inline isdnloop_card * +isdnloop_findcard(int driverid) +{ + isdnloop_card *p = cards; + + while (p) { + if (p->myid == driverid) + return p; + p = p->next; + } + return (isdnloop_card *) 0; +} + +/* + * Wrapper functions for interface to linklevel + */ +static int +if_command(isdn_ctrl * c) +{ + isdnloop_card *card = isdnloop_findcard(c->driver); + + if (card) + return (isdnloop_command(c, card)); + printk(KERN_ERR + "isdnloop: if_command called with invalid driverId!\n"); + return -ENODEV; +} + +static int +if_writecmd(const u_char __user *buf, int len, int id, int channel) +{ + isdnloop_card *card = isdnloop_findcard(id); + + if (card) { + if (!card->flags & ISDNLOOP_FLAGS_RUNNING) + return -ENODEV; + return (isdnloop_writecmd(buf, len, 1, card)); + } + printk(KERN_ERR + "isdnloop: if_writecmd called with invalid driverId!\n"); + return -ENODEV; +} + +static int +if_readstatus(u_char __user *buf, int len, int id, int channel) +{ + isdnloop_card *card = isdnloop_findcard(id); + + if (card) { + if (!card->flags & ISDNLOOP_FLAGS_RUNNING) + return -ENODEV; + return (isdnloop_readstatus(buf, len, card)); + } + printk(KERN_ERR + "isdnloop: if_readstatus called with invalid driverId!\n"); + return -ENODEV; +} + +static int +if_sendbuf(int id, int channel, int ack, struct sk_buff *skb) +{ + isdnloop_card *card = isdnloop_findcard(id); + + if (card) { + if (!card->flags & ISDNLOOP_FLAGS_RUNNING) + return -ENODEV; + /* ack request stored in skb scratch area */ + *(skb->head) = ack; + return (isdnloop_sendbuf(channel, skb, card)); + } + printk(KERN_ERR + "isdnloop: 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 isdnloop_card * +isdnloop_initcard(char *id) +{ + isdnloop_card *card; + int i; + + if (!(card = (isdnloop_card *) kmalloc(sizeof(isdnloop_card), GFP_KERNEL))) { + printk(KERN_WARNING + "isdnloop: (%s) Could not allocate card-struct.\n", id); + return (isdnloop_card *) 0; + } + memset((char *) card, 0, sizeof(isdnloop_card)); + card->interface.owner = THIS_MODULE; + card->interface.channels = ISDNLOOP_BCH; + card->interface.hl_hdrlen = 1; /* scratch area for storing ack flag*/ + 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 | +#ifdef CONFIG_ISDN_X25 + ISDN_FEATURE_L2_X25DTE | + ISDN_FEATURE_L2_X25DCE | +#endif + ISDN_FEATURE_L2_HDLC | + ISDN_FEATURE_L3_TRANS | + ISDN_FEATURE_P_UNKNOWN; + card->ptype = ISDN_PTYPE_UNKNOWN; + strlcpy(card->interface.id, id, sizeof(card->interface.id)); + card->msg_buf_write = card->msg_buf; + card->msg_buf_read = card->msg_buf; + card->msg_buf_end = &card->msg_buf[sizeof(card->msg_buf) - 1]; + for (i = 0; i < ISDNLOOP_BCH; i++) { + card->l2_proto[i] = ISDN_PROTO_L2_X75I; + skb_queue_head_init(&card->bqueue[i]); + } + skb_queue_head_init(&card->dqueue); + card->next = cards; + cards = card; + if (!register_isdn(&card->interface)) { + cards = cards->next; + printk(KERN_WARNING + "isdnloop: Unable to register %s\n", id); + kfree(card); + return (isdnloop_card *) 0; + } + card->myid = card->interface.channels; + return card; +} + +static int +isdnloop_addcard(char *id1) +{ + isdnloop_card *card; + + if (!(card = isdnloop_initcard(id1))) { + return -EIO; + } + printk(KERN_INFO + "isdnloop: (%s) virtual card added\n", + card->interface.id); + return 0; +} + +static int __init +isdnloop_init(void) +{ + char *p; + char rev[10]; + + if ((p = strchr(revision, ':'))) { + strcpy(rev, p + 1); + p = strchr(rev, '$'); + *p = 0; + } else + strcpy(rev, " ??? "); + printk(KERN_NOTICE "isdnloop-ISDN-driver Rev%s\n", rev); + + if (isdnloop_id) + return (isdnloop_addcard(isdnloop_id)); + + return 0; +} + +static void __exit +isdnloop_exit(void) +{ + isdn_ctrl cmd; + isdnloop_card *card = cards; + isdnloop_card *last; + int i; + + isdnloop_stopallcards(); + while (card) { + cmd.command = ISDN_STAT_UNLOAD; + cmd.driver = card->myid; + card->interface.statcallb(&cmd); + for (i = 0; i < ISDNLOOP_BCH; i++) + isdnloop_free_queue(card, i); + card = card->next; + } + card = cards; + while (card) { + last = card; + skb_queue_purge(&card->dqueue); + card = card->next; + kfree(last); + } + printk(KERN_NOTICE "isdnloop-ISDN-driver unloaded\n"); +} + +module_init(isdnloop_init); +module_exit(isdnloop_exit); diff --git a/drivers/isdn/isdnloop/isdnloop.h b/drivers/isdn/isdnloop/isdnloop.h new file mode 100644 index 000000000000..8fb7bc1bfe0f --- /dev/null +++ b/drivers/isdn/isdnloop/isdnloop.h @@ -0,0 +1,112 @@ +/* $Id: isdnloop.h,v 1.5.6.3 2001/09/23 22:24:56 kai Exp $ + * + * Loopback lowlevel module for testing of linklevel. + * + * Copyright 1997 by Fritz Elfert (fritz@isdn4linux.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#ifndef isdnloop_h +#define isdnloop_h + +#define ISDNLOOP_IOCTL_DEBUGVAR 0 +#define ISDNLOOP_IOCTL_ADDCARD 1 +#define ISDNLOOP_IOCTL_LEASEDCFG 2 +#define ISDNLOOP_IOCTL_STARTUP 3 + +/* Struct for adding new cards */ +typedef struct isdnloop_cdef { + char id1[10]; +} isdnloop_cdef; + +/* Struct for configuring cards */ +typedef struct isdnloop_sdef { + int ptype; + char num[3][20]; +} isdnloop_sdef; + +#if defined(__KERNEL__) || defined(__DEBUGVAR__) + +#ifdef __KERNEL__ +/* Kernel includes */ + +#include <linux/version.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/major.h> +#include <asm/io.h> +#include <linux/kernel.h> +#include <linux/signal.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/mman.h> +#include <linux/ioport.h> +#include <linux/timer.h> +#include <linux/wait.h> +#include <linux/isdnif.h> + +#endif /* __KERNEL__ */ + +#define ISDNLOOP_FLAGS_B1ACTIVE 1 /* B-Channel-1 is open */ +#define ISDNLOOP_FLAGS_B2ACTIVE 2 /* B-Channel-2 is open */ +#define ISDNLOOP_FLAGS_RUNNING 4 /* Cards driver activated */ +#define ISDNLOOP_FLAGS_RBTIMER 8 /* scheduling of B-Channel-poll */ +#define ISDNLOOP_TIMER_BCREAD 1 /* B-Channel poll-cycle */ +#define ISDNLOOP_TIMER_DCREAD (HZ/2) /* D-Channel poll-cycle */ +#define ISDNLOOP_TIMER_ALERTWAIT (10*HZ) /* Alert timeout */ +#define ISDNLOOP_MAX_SQUEUE 65536 /* Max. outstanding send-data */ +#define ISDNLOOP_BCH 2 /* channels per card */ + +/* + * Per card driver data + */ +typedef struct isdnloop_card { + struct isdnloop_card *next; /* Pointer to next device struct */ + struct isdnloop_card + *rcard[ISDNLOOP_BCH]; /* Pointer to 'remote' card */ + int rch[ISDNLOOP_BCH]; /* 'remote' channel */ + int myid; /* Driver-Nr. assigned by linklevel */ + int leased; /* Flag: This Adapter is connected */ + /* to a leased line */ + int sil[ISDNLOOP_BCH]; /* SI's to listen for */ + char eazlist[ISDNLOOP_BCH][11]; + /* EAZ's to listen for */ + char s0num[3][20]; /* 1TR6 base-number or MSN's */ + unsigned short flags; /* Statusflags */ + 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 */ + struct timer_list + c_timer[ISDNLOOP_BCH]; /* Timer for Alerting */ + int l2_proto[ISDNLOOP_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 */ + int optr; /* Index to omsg-buffer */ + char omsg[60]; /* Internal buf for cmd-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[ISDNLOOP_BCH]; /* Byte-counters for B-Ch.-send */ + struct sk_buff_head + bqueue[ISDNLOOP_BCH]; /* B-Channel queues */ + struct sk_buff_head dqueue; /* D-Channel queue */ +} isdnloop_card; + +/* + * Main driver data + */ +#ifdef __KERNEL__ +static isdnloop_card *cards = (isdnloop_card *) 0; +#endif /* __KERNEL__ */ + +/* Utility-Macros */ + +#define CID (card->interface.id) + +#endif /* defined(__KERNEL__) || defined(__DEBUGVAR__) */ +#endif /* isdnloop_h */ diff --git a/drivers/isdn/pcbit/Kconfig b/drivers/isdn/pcbit/Kconfig new file mode 100644 index 000000000000..f06997faef16 --- /dev/null +++ b/drivers/isdn/pcbit/Kconfig @@ -0,0 +1,14 @@ +# +# Config.in for PCBIT ISDN driver +# +config ISDN_DRV_PCBIT + tristate "PCBIT-D support" + depends on ISDN_I4L && ISA && (BROKEN || !PPC) + help + This enables support for the PCBIT ISDN-card. This card is + manufactured in Portugal by Octal. For running this card, + additional firmware is necessary, which has to be downloaded into + the card using a utility which is distributed separately. See + <file:Documentation/isdn/README> and + <file:Documentation/isdn/README.pcbit> for more information. + diff --git a/drivers/isdn/pcbit/Makefile b/drivers/isdn/pcbit/Makefile new file mode 100644 index 000000000000..2d026c3242e8 --- /dev/null +++ b/drivers/isdn/pcbit/Makefile @@ -0,0 +1,9 @@ +# Makefile for the pcbit ISDN device driver + +# Each configuration option enables a list of files. + +obj-$(CONFIG_ISDN_DRV_PCBIT) += pcbit.o + +# Multipart objects. + +pcbit-y := module.o edss1.o drv.o layer2.o capi.o callbacks.o diff --git a/drivers/isdn/pcbit/callbacks.c b/drivers/isdn/pcbit/callbacks.c new file mode 100644 index 000000000000..692ec72d1ee8 --- /dev/null +++ b/drivers/isdn/pcbit/callbacks.c @@ -0,0 +1,367 @@ +/* + * Callbacks for the FSM + * + * 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 General Public License, incorporated herein by reference. + */ + +/* + * Fix: 19981230 - Carlos Morgado <chbm@techie.com> + * Port of Nelson Escravana's <nelson.escravana@usa.net> fix to CalledPN + * NULL pointer dereference in cb_in_1 (originally fixed in 2.0) + */ + +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/kernel.h> + +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/mm.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.CallingPN == NULL) { + printk(KERN_DEBUG "NULL CallingPN to phone; using 0\n"); + strcpy(ictl.parm.setup.phone, "0"); + } + else { + strcpy(ictl.parm.setup.phone, cbdata->data.setup.CallingPN); + } + if (cbdata->data.setup.CalledPN == NULL) { + printk(KERN_DEBUG "NULL CalledPN to eazmsn; using 0\n"); + strcpy(ictl.parm.setup.eazmsn, "0"); + } + else { + strcpy(ictl.parm.setup.eazmsn, cbdata->data.setup.CalledPN); + } + ictl.parm.setup.si1 = 7; + ictl.parm.setup.si2 = 0; + ictl.parm.setup.plan = 0; + ictl.parm.setup.screen = 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 000000000000..f510dc56b57e --- /dev/null +++ b/drivers/isdn/pcbit/callbacks.h @@ -0,0 +1,49 @@ +/* + * Callbacks prototypes for FSM + * + * 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 General Public License, incorporated herein by reference. + */ + +#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 000000000000..29eb03a8c29d --- /dev/null +++ b/drivers/isdn/pcbit/capi.c @@ -0,0 +1,663 @@ +/* + * CAPI encoder/decoder for + * Portugal Telecom CAPI 2.0 + * + * 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 General Public License, incorporated herein by reference. + * + * 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 + */ + +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/kernel.h> + +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/mm.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; + } + + *((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; + } + + *((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; + } + + *((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; + } + + *((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; + } + + *((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; + + if(skb_headroom(skb) < 10) + { + printk(KERN_CRIT "No headspace (%u) on headroom %p for capi header\n", skb_headroom(skb), skb); + } + else + { + 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; + } + + *((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; + } + + *((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; + } + + *((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 000000000000..18e6aa360a8f --- /dev/null +++ b/drivers/isdn/pcbit/capi.h @@ -0,0 +1,88 @@ +/* + * CAPI encode/decode prototypes and defines + * + * 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 General Public License, incorporated herein by reference. + */ + +#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 + +static 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 000000000000..e98f9c48c184 --- /dev/null +++ b/drivers/isdn/pcbit/drv.c @@ -0,0 +1,1088 @@ +/* + * PCBIT-D interface with isdn4linux + * + * 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 General Public License, incorporated herein by reference. + */ + +/* + * Fixes: + * + * Nuno Grilo <l38486@alfa.ist.utl.pt> + * fixed msn_list NULL pointer dereference. + * + */ + +#include <linux/module.h> + +#include <linux/sched.h> + +#include <linux/kernel.h> + +#include <linux/types.h> +#include <linux/slab.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 <linux/ioport.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 __user * buf, int len, int, int); +int pcbit_xmit(int driver, int chan, int ack, struct sk_buff *skb); +int pcbit_writecmd(const u_char __user *, 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)); + init_waitqueue_head(&dev->set_running_wq); + spin_lock_init(&dev->lock); + + if (mem_base >= 0xA0000 && mem_base <= 0xFFFFF ) { + dev->ph_mem = mem_base; + if (!request_mem_region(dev->ph_mem, 4096, "PCBIT mem")) { + printk(KERN_WARNING + "PCBIT: memory region %lx-%lx already in use\n", + dev->ph_mem, dev->ph_mem + 4096); + kfree(dev); + dev_pcbit[board] = NULL; + return -EACCES; + } + dev->sh_mem = ioremap(dev->ph_mem, 4096); + } + 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"); + iounmap(dev->sh_mem); + release_mem_region(dev->ph_mem, 4096); + 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); + iounmap(dev->sh_mem); + release_mem_region(dev->ph_mem, 4096); + kfree(dev); + return -ENOMEM; + } + + memset(dev->b1, 0, sizeof(struct pcbit_chan)); + memset(dev->b2, 0, sizeof(struct pcbit_chan)); + dev->b2->id = 1; + + INIT_WORK(&dev->qdelivery, pcbit_deliver, dev); + + /* + * interrupts + */ + + if (request_irq(irq, &pcbit_irq_handler, 0, pcbit_devname[board], dev) != 0) + { + kfree(dev->b1); + kfree(dev->b2); + iounmap(dev->sh_mem); + release_mem_region(dev->ph_mem, 4096); + 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 = 16; + + dev_if = kmalloc(sizeof(isdn_if), GFP_KERNEL); + + if (!dev_if) { + free_irq(irq, dev); + kfree(dev->b1); + kfree(dev->b2); + iounmap(dev->sh_mem); + release_mem_region(dev->ph_mem, 4096); + kfree(dev); + dev_pcbit[board] = NULL; + return -EIO; + } + + dev->dev_if = dev_if; + + dev_if->owner = THIS_MODULE; + + 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->hl_hdrlen = 16; + + 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); + iounmap(dev->sh_mem); + release_mem_region(dev->ph_mem, 4096); + 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); + iounmap(dev->sh_mem); + release_mem_region(dev->ph_mem, 4096); + kfree(dev); + } +} +#endif + +int pcbit_command(isdn_ctrl* ctl) +{ + struct pcbit_dev *dev; + struct pcbit_chan *chan; + struct callb_data info; + + 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->parm.setup.phone; + 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_CLREAZ: + pcbit_clear_msn(dev); + break; + case ISDN_CMD_SETEAZ: + pcbit_set_msn(dev, ctl->parm.num); + break; + case ISDN_CMD_SETL3: + if ((ctl->arg >> 8) != ISDN_PROTO_L3_TRANS) + printk(KERN_DEBUG "L3 protocol unknown\n"); + 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, int ack, 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 __user *buf, int len, int driver, int channel) +{ + struct pcbit_dev * dev; + int i, j; + const u_char * loadbuf; + u_char * ptr = NULL; + u_char *cbuf; + + 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 < 0 || len > BANK4 + 1 || len > 1024) + { + printk("pcbit_writecmd: invalid length %d\n", len); + return -EINVAL; + } + + cbuf = kmalloc(len, GFP_KERNEL); + if (!cbuf) + return -ENOMEM; + + if (copy_from_user(cbuf, buf, len)) { + kfree(cbuf); + return -EFAULT; + } + memcpy_toio(dev->sh_mem, cbuf, len); + kfree(cbuf); + return len; + case L2_FWMODE: + /* this is the hard part */ + /* dumb board */ + /* 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; + + errstat = 0; + + for (i=0; i < len; i++) + { + for(j=0; j < LOAD_RETRY; j++) + if (!(readb(dev->sh_mem + dev->loadptr))) + 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; + } + kfree(ptr); + + return errstat ? errstat : len; + default: + return -EBUSY; + } +} + +/* + * 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; + + 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 + /* 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; + } + } + 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); + break; +#endif + } + + kfree_skb(skb); + +} + +/* + * Single statbuf + * should be a statbuf per device + */ + +static char statbuf[STATBUF_LEN]; +static int stat_st = 0; +static int stat_end = 0; + +int pcbit_stat(u_char __user *buf, int len, 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) + { + copy_to_user(buf, statbuf + stat_st, len); + stat_st += len; + } + else + { + if (len > STATBUF_LEN - stat_st) + { + copy_to_user(buf, statbuf + stat_st, + STATBUF_LEN - stat_st); + copy_to_user(buf, statbuf, + len - (STATBUF_LEN - stat_st)); + + stat_st = len - (STATBUF_LEN - stat_st); + } + else + { + copy_to_user(buf, statbuf + stat_st, len); + + 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 + writeb(0x40, dev->sh_mem + BANK4); + + /* 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->parm.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"); + kfree(ptr); + 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 000000000000..93ca7de5670b --- /dev/null +++ b/drivers/isdn/pcbit/edss1.c @@ -0,0 +1,325 @@ +/* + * DSS.1 Finite State Machine + * base: ITU-T Rec Q.931 + * + * 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 General Public License, incorporated herein by reference. + */ + +/* + * TODO: complete the FSM + * move state/event descriptions to a user space logger + */ + +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/kernel.h> + +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/mm.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; + + spin_lock_irqsave(&dev->lock, flags); + + for (action = fsm_table; action->init != 0xff; action++) + if (action->init == chan->fsm_state && action->event == event) + break; + + if (action->init == 0xff) { + + spin_unlock_irqrestore(&dev->lock, flags); + printk(KERN_DEBUG "fsm error: event %x on state %x\n", + event, chan->fsm_state); + 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); + } + + spin_unlock_irqrestore(&dev->lock, 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 000000000000..6bb587005b86 --- /dev/null +++ b/drivers/isdn/pcbit/edss1.h @@ -0,0 +1,99 @@ +/* + * DSS.1 module definitions + * + * 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 General Public License, incorporated herein by reference. + */ + +#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 000000000000..ba766930f088 --- /dev/null +++ b/drivers/isdn/pcbit/layer2.c @@ -0,0 +1,732 @@ +/* + * PCBIT-D low-layer interface + * + * 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 General Public License, incorporated herein by reference. + */ + +/* + * 19991203 - Fernando Carvalho - takion@superbofh.org + * Hacked to compile with egcs and run with current version of isdn modules +*/ + +/* + * 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 + */ + +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/workqueue.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) +{ + schedule_work(&dev->qdelivery); +} + + +/* + * 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); + 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); + 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; + else + frame->dt_len = 0; + + frame->skb = skb; + + frame->next = NULL; + + spin_lock_irqsave(&dev->lock, flags); + + if (dev->write_queue == NULL) { + dev->write_queue = frame; + spin_unlock_irqrestore(&dev->lock, flags); + pcbit_transmit(dev); + } else { + for (ptr = dev->write_queue; ptr->next; ptr = ptr->next); + ptr->next = frame; + + spin_unlock_irqrestore(&dev->lock, 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 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; + + spin_lock_irqsave(&dev->lock, flags); + + if (dev->free > 16 && dev->write_queue && unacked < 7) { + + if (!dev->w_busy) + dev->w_busy = 1; + else { + spin_unlock_irqrestore(&dev->lock, flags); + return; + } + + + frame = dev->write_queue; + free = dev->free; + + spin_unlock_irqrestore(&dev->lock, flags); + + if (frame->copied == 0) { + + /* Type 0 frame */ + + ulong msg; + + if (frame->skb) + flen = FRAME_HDR_LEN + PREHDR_LEN + frame->skb->len; + else + flen = FRAME_HDR_LEN + PREHDR_LEN; + + if (flen > free) + flen = free; + + msg = frame->msg; + + /* + * Board level 2 header + */ + + pcbit_writew(dev, flen - FRAME_HDR_LEN); + + pcbit_writeb(dev, GET_MSG_CPU(msg)); + + pcbit_writeb(dev, GET_MSG_PROC(msg)); + + /* 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, GET_MSG_CMD(msg)); + pcbit_writeb(dev, GET_MSG_SCMD(msg)); + + /* NUM */ + pcbit_writew(dev, frame->refnum); + + count = FRAME_HDR_LEN + PREHDR_LEN; + } else { + /* Type 1 frame */ + + flen = 2 + (frame->skb->len - frame->copied); + + if (flen > free) + flen = free; + + /* TT */ + tt = ((ushort) (flen - 2)) | 0x8000U; /* Type 1 */ + pcbit_writew(dev, tt); + + count = 2; + } + + if (frame->skb) { + cp_len = frame->skb->len - frame->copied; + if (cp_len > flen - count) + cp_len = 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); + + spin_lock_irqsave(&dev->lock, flags); + + 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); + } + kfree(frame); + } + dev->w_busy = 0; + spin_unlock_irqrestore(&dev->lock, flags); + } else { + spin_unlock_irqrestore(&dev->lock, 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, msg; + struct pcbit_dev *dev = (struct pcbit_dev *) data; + + spin_lock_irqsave(&dev->lock, flags); + + while ((frame = dev->read_queue)) { + dev->read_queue = frame->next; + spin_unlock_irqrestore(&dev->lock, flags); + + SET_MSG_CPU(msg, 0); + SET_MSG_PROC(msg, 0); + SET_MSG_CMD(msg, frame->skb->data[2]); + SET_MSG_SCMD(msg, 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); + + spin_lock_irqsave(&dev->lock, flags); + } + + spin_unlock_irqrestore(&dev->lock, 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"); + /* discard previous queued frame */ + if (dev->read_frame->skb) + kfree_skb(dev->read_frame->skb); + kfree(dev->read_frame); + dev->read_frame = NULL; + } + 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"); + /* usually after an error: toss frame */ + dev->readptr += tt; + if (dev->readptr > dev->sh_mem + BANK2 + BANKLEN) + dev->readptr -= BANKLEN; + return; + + } + } + + memcpy_frompcbit(dev, skb_put(frame->skb, tt), tt); + + frame->copied += tt; + spin_lock_irqsave(&dev->lock, flags); + if (frame->copied == frame->hdr_len + frame->dt_len) { + + 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; + + } else { + dev->read_frame = frame; + } + spin_unlock_irqrestore(&dev->lock, 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); + } +} + +irqreturn_t +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 IRQ_NONE; + } + if (dev->interrupt) { + printk(KERN_DEBUG "pcbit: reentering interrupt hander\n"); + return IRQ_HANDLED; + } + 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 IRQ_HANDLED; + } + 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 IRQ_HANDLED; + } + if (dev->l2_state != L2_RUNNING && dev->l2_state != L2_LOADING) { + dev->interrupt = 0; + return IRQ_HANDLED; + } + 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); + return IRQ_HANDLED; +} + + +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) + kfree_skb(dev->read_frame->skb); + 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); + } + 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 000000000000..0d99da3a3e2b --- /dev/null +++ b/drivers/isdn/pcbit/layer2.h @@ -0,0 +1,288 @@ +/* + * PCBIT-D low-layer interface definitions + * + * 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 General Public License, incorporated herein by reference. + */ + +/* + * 19991203 - Fernando Carvalho - takion@superbofh.org + * Hacked to compile with egcs and run with current version of isdn modules +*/ + +#ifndef LAYER2_H +#define LAYER2_H + +#include <linux/interrupt.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 +*/ + +#ifdef __LITTLE_ENDIAN +#define SET_MSG_SCMD(msg, ch) (msg = (msg & 0xffffff00) | (((ch) & 0xff))) +#define SET_MSG_CMD(msg, ch) (msg = (msg & 0xffff00ff) | (((ch) & 0xff) << 8)) +#define SET_MSG_PROC(msg, ch) (msg = (msg & 0xff00ffff) | (((ch) & 0xff) << 16)) +#define SET_MSG_CPU(msg, ch) (msg = (msg & 0x00ffffff) | (((ch) & 0xff) << 24)) + +#define GET_MSG_SCMD(msg) ((msg) & 0xFF) +#define GET_MSG_CMD(msg) ((msg) >> 8 & 0xFF) +#define GET_MSG_PROC(msg) ((msg) >> 16 & 0xFF) +#define GET_MSG_CPU(msg) ((msg) >> 24) + +#else +#error "Non-Intel CPU" +#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 int refnum; + unsigned int dt_len; + unsigned int hdr_len; + struct sk_buff *skb; + unsigned int copied; + struct frame_buf * next; +}; + +extern int pcbit_l2_write(struct pcbit_dev * dev, ulong msg, ushort refnum, + struct sk_buff *skb, unsigned short hdr_len); + +extern irqreturn_t 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 000000000000..282073a35d6a --- /dev/null +++ b/drivers/isdn/pcbit/module.c @@ -0,0 +1,130 @@ +/* + * PCBIT-D module support + * + * 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 General Public License, incorporated herein by reference. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/kernel.h> +#include <linux/skbuff.h> + +#include <linux/isdnif.h> +#include "pcbit.h" + +MODULE_DESCRIPTION("ISDN4Linux: Driver for PCBIT-T card"); +MODULE_AUTHOR("Pedro Roque Marques"); +MODULE_LICENSE("GPL"); + +static int mem[MAX_PCBIT_CARDS]; +static int irq[MAX_PCBIT_CARDS]; + +module_param_array(mem, int, NULL, 0); +module_param_array(irq, int, NULL, 0); + +static int num_boards; +struct pcbit_dev * dev_pcbit[MAX_PCBIT_CARDS]; + +extern void pcbit_terminate(int board); +extern int pcbit_init_dev(int board, int mem_base, int irq); + +static int __init pcbit_init(void) +{ + int board; + + num_boards = 0; + + printk(KERN_NOTICE + "PCBIT-D device driver v 0.5-fjpc0 19991204 - " + "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; + } + return 0; +} + +static void __exit pcbit_exit(void) +{ +#ifdef MODULE + int board; + + for (board = 0; board < num_boards; board++) + pcbit_terminate(board); + printk(KERN_NOTICE + "PCBIT-D module unloaded\n"); +#endif +} + +#ifndef MODULE +#define MAX_PARA (MAX_PCBIT_CARDS * 2) +static int __init pcbit_setup(char *line) +{ + int i, j, argc; + char *str; + int ints[MAX_PARA+1]; + + str = get_options(line, MAX_PARA, ints); + 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++; + } + return(1); +} +__setup("pcbit=", pcbit_setup); +#endif + +module_init(pcbit_init); +module_exit(pcbit_exit); + diff --git a/drivers/isdn/pcbit/pcbit.h b/drivers/isdn/pcbit/pcbit.h new file mode 100644 index 000000000000..388bacefd23a --- /dev/null +++ b/drivers/isdn/pcbit/pcbit.h @@ -0,0 +1,169 @@ +/* + * PCBIT-D device driver definitions + * + * 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 General Public License, incorporated herein by reference. + */ + +#ifndef PCBIT_H +#define PCBIT_H + +#include <linux/workqueue.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 __iomem *sh_mem; /* RDP address */ + unsigned long ph_mem; + unsigned int irq; + unsigned int id; + unsigned int interrupt; /* set during interrupt + processing */ + spinlock_t lock; + /* 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 */ + wait_queue_head_t set_running_wq; + struct timer_list set_running_timer; + + struct timer_list error_recover_timer; + + struct work_struct qdelivery; + + u_char w_busy; + u_char r_busy; + + volatile unsigned char __iomem *readptr; + volatile unsigned char __iomem *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 (HZ/10) + +/* 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/sc/Kconfig b/drivers/isdn/sc/Kconfig new file mode 100644 index 000000000000..5346e33d816c --- /dev/null +++ b/drivers/isdn/sc/Kconfig @@ -0,0 +1,12 @@ +# +# Config.in for Spellcaster ISDN driver +# +config ISDN_DRV_SC + tristate "Spellcaster support" + depends on ISDN_I4L && ISA + help + This enables support for the Spellcaster BRI ISDN boards. This + driver currently builds only in a modularized version. + To build it, choose M here: the module will be called sc. + See <file:Documentation/isdn/README.sc> for more information. + diff --git a/drivers/isdn/sc/Makefile b/drivers/isdn/sc/Makefile new file mode 100644 index 000000000000..9cc474cd0c44 --- /dev/null +++ b/drivers/isdn/sc/Makefile @@ -0,0 +1,10 @@ +# Makefile for the sc ISDN device driver + +# Each configuration option enables a list of files. + +obj-$(CONFIG_ISDN_DRV_SC) += sc.o + +# Multipart objects. + +sc-y := shmem.o init.o debug.o packet.o command.o event.o \ + ioctl.o interrupt.o message.o timer.o diff --git a/drivers/isdn/sc/card.h b/drivers/isdn/sc/card.h new file mode 100644 index 000000000000..8e44928cdf1c --- /dev/null +++ b/drivers/isdn/sc/card.h @@ -0,0 +1,101 @@ +/* $Id: card.h,v 1.1.10.1 2001/09/23 22:24:59 kai Exp $ + * + * Driver parameters for SpellCaster ISA ISDN adapters + * + * Copyright (C) 1996 SpellCaster Telecommunications Inc. + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * For more information, please contact gpl-info@spellcast.com or write: + * + * SpellCaster Telecommunications Inc. + * 5621 Finch Avenue East, Unit #3 + * Scarborough, Ontario Canada + * M1B 2T9 + * +1 (416) 297-8565 + * +1 (416) 297-6433 Facsimile + */ + +#ifndef CARD_H +#define CARD_H + +/* + * We need these if they're not already included + */ +#include <linux/timer.h> +#include <linux/time.h> +#include <linux/isdnif.h> +#include "message.h" + +/* + * Amount of time to wait for a reset to complete + */ +#define CHECKRESET_TIME msecs_to_jiffies(4000) + +/* + * Amount of time between line status checks + */ +#define CHECKSTAT_TIME msecs_to_jiffies(8000) + +/* + * The maximum amount of time to wait for a message response + * to arrive. Use exclusively by send_and_receive + */ +#define SAR_TIMEOUT msecs_to_jiffies(10000) + +/* + * Macro to determine is a card id is valid + */ +#define IS_VALID_CARD(x) ((x >= 0) && (x <= cinst)) + +/* + * Per channel status and configuration + */ +typedef struct { + int l2_proto; + int l3_proto; + char dn[50]; + unsigned long first_sendbuf; /* Offset of first send buffer */ + unsigned int num_sendbufs; /* Number of send buffers */ + unsigned int free_sendbufs; /* Number of free sendbufs */ + unsigned int next_sendbuf; /* Next sequential buffer */ + char eazlist[50]; /* Set with SETEAZ */ + char sillist[50]; /* Set with SETSIL */ + int eazclear; /* Don't accept calls if TRUE */ +} bchan; + +/* + * Everything you want to know about the adapter ... + */ +typedef struct { + int model; + int driverId; /* LL Id */ + char devicename[20]; /* The device name */ + isdn_if *card; /* ISDN4Linux structure */ + bchan *channel; /* status of the B channels */ + char nChannels; /* Number of channels */ + unsigned int interrupt; /* Interrupt number */ + int iobase; /* I/O Base address */ + int ioport[MAX_IO_REGS]; /* Index to I/O ports */ + int shmem_pgport; /* port for the exp mem page reg. */ + int shmem_magic; /* adapter magic number */ + unsigned int rambase; /* Shared RAM base address */ + unsigned int ramsize; /* Size of shared memory */ + RspMessage async_msg; /* Async response message */ + int want_async_messages; /* Snoop the Q ? */ + unsigned char seq_no; /* Next send seq. number */ + struct timer_list reset_timer; /* Check reset timer */ + struct timer_list stat_timer; /* Check startproc timer */ + unsigned char nphystat; /* Latest PhyStat info */ + unsigned char phystat; /* Last PhyStat info */ + HWConfig_pl hwconfig; /* Hardware config info */ + char load_ver[11]; /* CommManage Version string */ + char proc_ver[11]; /* CommEngine Version */ + int StartOnReset; /* Indicates startproc after reset */ + int EngineUp; /* Indicates CommEngine Up */ + int trace_mode; /* Indicate if tracing is on */ + spinlock_t lock; /* local lock */ +} board; + +#endif /* CARD_H */ diff --git a/drivers/isdn/sc/command.c b/drivers/isdn/sc/command.c new file mode 100644 index 000000000000..b2c4eac7cef5 --- /dev/null +++ b/drivers/isdn/sc/command.c @@ -0,0 +1,441 @@ +/* $Id: command.c,v 1.4.10.1 2001/09/23 22:24:59 kai Exp $ + * + * Copyright (C) 1996 SpellCaster Telecommunications Inc. + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * For more information, please contact gpl-info@spellcast.com or write: + * + * SpellCaster Telecommunications Inc. + * 5621 Finch Avenue East, Unit #3 + * Scarborough, Ontario Canada + * M1B 2T9 + * +1 (416) 297-8565 + * +1 (416) 297-6433 Facsimile + */ + +#include <linux/module.h> +#include "includes.h" /* This must be first */ +#include "hardware.h" +#include "message.h" +#include "card.h" +#include "scioc.h" + +int dial(int card, unsigned long channel, setup_parm setup); +int hangup(int card, unsigned long channel); +int answer(int card, unsigned long channel); +int clreaz(int card, unsigned long channel); +int seteaz(int card, unsigned long channel, char *); +int setl2(int card, unsigned long arg); +int setl3(int card, unsigned long arg); +int acceptb(int card, unsigned long channel); + +extern int cinst; +extern board *sc_adapter[]; + +extern int sc_ioctl(int, scs_ioctl *); +extern int setup_buffers(int, int, unsigned int); +extern int indicate_status(int, int,ulong,char*); +extern void check_reset(unsigned long); +extern int send_and_receive(int, unsigned int, unsigned char, unsigned char, + unsigned char, unsigned char, unsigned char, unsigned char *, + RspMessage *, int); +extern int sendmessage(int, unsigned int, unsigned int, unsigned int, + unsigned int, unsigned int, unsigned int, unsigned int *); +extern inline void pullphone(char *, char *); + +#ifdef DEBUG +/* + * Translate command codes to strings + */ +static char *commands[] = { "ISDN_CMD_IOCTL", + "ISDN_CMD_DIAL", + "ISDN_CMD_ACCEPTB", + "ISDN_CMD_ACCEPTB", + "ISDN_CMD_HANGUP", + "ISDN_CMD_CLREAZ", + "ISDN_CMD_SETEAZ", + NULL, + NULL, + NULL, + "ISDN_CMD_SETL2", + NULL, + "ISDN_CMD_SETL3", + NULL, + NULL, + NULL, + NULL, + NULL, }; + +/* + * Translates ISDN4Linux protocol codes to strings for debug messages + */ +static char *l3protos[] = { "ISDN_PROTO_L3_TRANS" }; +static char *l2protos[] = { "ISDN_PROTO_L2_X75I", + "ISDN_PROTO_L2_X75UI", + "ISDN_PROTO_L2_X75BUI", + "ISDN_PROTO_L2_HDLC", + "ISDN_PROTO_L2_TRANS" }; +#endif + +int get_card_from_id(int driver) +{ + int i; + + for(i = 0 ; i < cinst ; i++) { + if(sc_adapter[i]->driverId == driver) + return i; + } + return -ENODEV; +} + +/* + * command + */ + +int command(isdn_ctrl *cmd) +{ + int card; + + card = get_card_from_id(cmd->driver); + if(!IS_VALID_CARD(card)) { + pr_debug("Invalid param: %d is not a valid card id\n", card); + return -ENODEV; + } + + pr_debug("%s: Received %s command from Link Layer\n", + sc_adapter[card]->devicename, commands[cmd->command]); + + /* + * Dispatch the command + */ + switch(cmd->command) { + case ISDN_CMD_IOCTL: + { + unsigned long cmdptr; + scs_ioctl ioc; + + memcpy(&cmdptr, cmd->parm.num, sizeof(unsigned long)); + if (copy_from_user(&ioc, (scs_ioctl __user *)cmdptr, + sizeof(scs_ioctl))) { + pr_debug("%s: Failed to verify user space 0x%x\n", + sc_adapter[card]->devicename, cmdptr); + return -EFAULT; + } + return sc_ioctl(card, &ioc); + } + case ISDN_CMD_DIAL: + return dial(card, cmd->arg, cmd->parm.setup); + case ISDN_CMD_HANGUP: + return hangup(card, cmd->arg); + case ISDN_CMD_ACCEPTD: + return answer(card, cmd->arg); + case ISDN_CMD_ACCEPTB: + return acceptb(card, cmd->arg); + case ISDN_CMD_CLREAZ: + return clreaz(card, cmd->arg); + case ISDN_CMD_SETEAZ: + return seteaz(card, cmd->arg, cmd->parm.num); + case ISDN_CMD_SETL2: + return setl2(card, cmd->arg); + case ISDN_CMD_SETL3: + return setl3(card, cmd->arg); + default: + return -EINVAL; + } + return 0; +} + +/* + * Confirm our ability to communicate with the board. This test assumes no + * other message activity is present + */ +int loopback(int card) +{ + + int status; + static char testmsg[] = "Test Message"; + RspMessage rspmsg; + + if(!IS_VALID_CARD(card)) { + pr_debug("Invalid param: %d is not a valid card id\n", card); + return -ENODEV; + } + + pr_debug("%s: Sending loopback message\n", + sc_adapter[card]->devicename); + + /* + * Send the loopback message to confirm that memory transfer is + * operational + */ + status = send_and_receive(card, CMPID, cmReqType1, + cmReqClass0, + cmReqMsgLpbk, + 0, + (unsigned char) strlen(testmsg), + (unsigned char *)testmsg, + &rspmsg, SAR_TIMEOUT); + + + if (!status) { + pr_debug("%s: Loopback message successfully sent\n", + sc_adapter[card]->devicename); + if(strcmp(rspmsg.msg_data.byte_array, testmsg)) { + pr_debug("%s: Loopback return != sent\n", + sc_adapter[card]->devicename); + return -EIO; + } + return 0; + } + else { + pr_debug("%s: Send loopback message failed\n", + sc_adapter[card]->devicename); + return -EIO; + } + +} + +/* + * start the onboard firmware + */ +int startproc(int card) +{ + int status; + + if(!IS_VALID_CARD(card)) { + pr_debug("Invalid param: %d is not a valid card id\n", card); + return -ENODEV; + } + + /* + * send start msg + */ + status = sendmessage(card, CMPID,cmReqType2, + cmReqClass0, + cmReqStartProc, + 0,0,NULL); + pr_debug("%s: Sent startProc\n", sc_adapter[card]->devicename); + + return status; +} + + +int loadproc(int card, char *data) +{ + return -1; +} + + +/* + * Dials the number passed in + */ +int dial(int card, unsigned long channel, setup_parm setup) +{ + int status; + char Phone[48]; + + if(!IS_VALID_CARD(card)) { + pr_debug("Invalid param: %d is not a valid card id\n", card); + return -ENODEV; + } + + /*extract ISDN number to dial from eaz/msn string*/ + strcpy(Phone,setup.phone); + + /*send the connection message*/ + status = sendmessage(card, CEPID,ceReqTypePhy, + ceReqClass1, + ceReqPhyConnect, + (unsigned char) channel+1, + strlen(Phone), + (unsigned int *) Phone); + + pr_debug("%s: Dialing %s on channel %d\n", + sc_adapter[card]->devicename, Phone, channel+1); + + return status; +} + +/* + * Answer an incoming call + */ +int answer(int card, unsigned long channel) +{ + if(!IS_VALID_CARD(card)) { + pr_debug("Invalid param: %d is not a valid card id\n", card); + return -ENODEV; + } + + if(setup_buffers(card, channel+1, BUFFER_SIZE)) { + hangup(card, channel+1); + return -ENOBUFS; + } + + indicate_status(card, ISDN_STAT_BCONN,channel,NULL); + pr_debug("%s: Answered incoming call on channel %s\n", + sc_adapter[card]->devicename, channel+1); + return 0; +} + +/* + * Hangup up the call on specified channel + */ +int hangup(int card, unsigned long channel) +{ + int status; + + if(!IS_VALID_CARD(card)) { + pr_debug("Invalid param: %d is not a valid card id\n", card); + return -ENODEV; + } + + status = sendmessage(card, CEPID, ceReqTypePhy, + ceReqClass1, + ceReqPhyDisconnect, + (unsigned char) channel+1, + 0, + NULL); + pr_debug("%s: Sent HANGUP message to channel %d\n", + sc_adapter[card]->devicename, channel+1); + return status; +} + +/* + * Set the layer 2 protocol (X.25, HDLC, Raw) + */ +int setl2(int card, unsigned long arg) +{ + int status =0; + int protocol,channel; + + if(!IS_VALID_CARD(card)) { + pr_debug("Invalid param: %d is not a valid card id\n", card); + return -ENODEV; + } + protocol = arg >> 8; + channel = arg & 0xff; + sc_adapter[card]->channel[channel].l2_proto = protocol; + pr_debug("%s: Level 2 protocol for channel %d set to %s from %d\n", + sc_adapter[card]->devicename, channel+1, + l2protos[sc_adapter[card]->channel[channel].l2_proto],protocol); + + /* + * check that the adapter is also set to the correct protocol + */ + pr_debug("%s: Sending GetFrameFormat for channel %d\n", + sc_adapter[card]->devicename, channel+1); + status = sendmessage(card, CEPID, ceReqTypeCall, + ceReqClass0, + ceReqCallGetFrameFormat, + (unsigned char)channel+1, + 1, + (unsigned int *) protocol); + if(status) + return status; + return 0; +} + +/* + * Set the layer 3 protocol + */ +int setl3(int card, unsigned long channel) +{ + int protocol = channel >> 8; + + if(!IS_VALID_CARD(card)) { + pr_debug("Invalid param: %d is not a valid card id\n", card); + return -ENODEV; + } + + sc_adapter[card]->channel[channel].l3_proto = protocol; + pr_debug("%s: Level 3 protocol for channel %d set to %s\n", + sc_adapter[card]->devicename, channel+1, l3protos[protocol]); + return 0; +} + +int acceptb(int card, unsigned long channel) +{ + if(!IS_VALID_CARD(card)) { + pr_debug("Invalid param: %d is not a valid card id\n", card); + return -ENODEV; + } + + if(setup_buffers(card, channel+1, BUFFER_SIZE)) + { + hangup(card, channel+1); + return -ENOBUFS; + } + + pr_debug("%s: B-Channel connection accepted on channel %d\n", + sc_adapter[card]->devicename, channel+1); + indicate_status(card, ISDN_STAT_BCONN, channel, NULL); + return 0; +} + +int clreaz(int card, unsigned long arg) +{ + if(!IS_VALID_CARD(card)) { + pr_debug("Invalid param: %d is not a valid card id\n", card); + return -ENODEV; + } + + strcpy(sc_adapter[card]->channel[arg].eazlist, ""); + sc_adapter[card]->channel[arg].eazclear = 1; + pr_debug("%s: EAZ List cleared for channel %d\n", + sc_adapter[card]->devicename, arg+1); + return 0; +} + +int seteaz(int card, unsigned long arg, char *num) +{ + if(!IS_VALID_CARD(card)) { + pr_debug("Invalid param: %d is not a valid card id\n", card); + return -ENODEV; + } + + strcpy(sc_adapter[card]->channel[arg].eazlist, num); + sc_adapter[card]->channel[arg].eazclear = 0; + pr_debug("%s: EAZ list for channel %d set to: %s\n", + sc_adapter[card]->devicename, arg+1, + sc_adapter[card]->channel[arg].eazlist); + return 0; +} + +int reset(int card) +{ + unsigned long flags; + + if(!IS_VALID_CARD(card)) { + pr_debug("Invalid param: %d is not a valid card id\n", card); + return -ENODEV; + } + + indicate_status(card, ISDN_STAT_STOP, 0, NULL); + + if(sc_adapter[card]->EngineUp) { + del_timer(&sc_adapter[card]->stat_timer); + } + + sc_adapter[card]->EngineUp = 0; + + spin_lock_irqsave(&sc_adapter[card]->lock, flags); + init_timer(&sc_adapter[card]->reset_timer); + sc_adapter[card]->reset_timer.function = check_reset; + sc_adapter[card]->reset_timer.data = card; + sc_adapter[card]->reset_timer.expires = jiffies + CHECKRESET_TIME; + add_timer(&sc_adapter[card]->reset_timer); + spin_unlock_irqrestore(&sc_adapter[card]->lock, flags); + + outb(0x1,sc_adapter[card]->ioport[SFT_RESET]); + + pr_debug("%s: Adapter Reset\n", sc_adapter[card]->devicename); + return 0; +} + +void flushreadfifo (int card) +{ + while(inb(sc_adapter[card]->ioport[FIFO_STATUS]) & RF_HAS_DATA) + inb(sc_adapter[card]->ioport[FIFO_READ]); +} diff --git a/drivers/isdn/sc/debug.c b/drivers/isdn/sc/debug.c new file mode 100644 index 000000000000..1a992a75868b --- /dev/null +++ b/drivers/isdn/sc/debug.c @@ -0,0 +1,46 @@ +/* $Id: debug.c,v 1.5.6.1 2001/09/23 22:24:59 kai Exp $ + * + * Copyright (C) 1996 SpellCaster Telecommunications Inc. + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * For more information, please contact gpl-info@spellcast.com or write: + * + * SpellCaster Telecommunications Inc. + * 5621 Finch Avenue East, Unit #3 + * Scarborough, Ontario Canada + * M1B 2T9 + * +1 (416) 297-8565 + * +1 (416) 297-6433 Facsimile + */ + +#include <linux/kernel.h> +#include <linux/string.h> + +int dbg_level = 0; +static char dbg_funcname[255]; + +void dbg_endfunc(void) +{ + if (dbg_level) { + printk("<-- Leaving function %s\n", dbg_funcname); + strcpy(dbg_funcname, ""); + } +} + +void dbg_func(char *func) +{ + strcpy(dbg_funcname, func); + if(dbg_level) + printk("--> Entering function %s\n", dbg_funcname); +} + +inline void pullphone(char *dn, char *str) +{ + int i = 0; + + while(dn[i] != ',') + str[i] = dn[i], i++; + str[i] = 0x0; +} diff --git a/drivers/isdn/sc/debug.h b/drivers/isdn/sc/debug.h new file mode 100644 index 000000000000..e9db96ede4b2 --- /dev/null +++ b/drivers/isdn/sc/debug.h @@ -0,0 +1,19 @@ +/* $Id: debug.h,v 1.2.8.1 2001/09/23 22:24:59 kai Exp $ + * + * Copyright (C) 1996 SpellCaster Telecommunications Inc. + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * For more information, please contact gpl-info@spellcast.com or write: + * + * SpellCaster Telecommunications Inc. + * 5621 Finch Avenue East, Unit #3 + * Scarborough, Ontario Canada + * M1B 2T9 + * +1 (416) 297-8565 + * +1 (416) 297-6433 Facsimile + */ + +#define REQUEST_IRQ(a,b,c,d,e) request_irq(a,b,c,d,e) +#define FREE_IRQ(a,b) free_irq(a,b) diff --git a/drivers/isdn/sc/event.c b/drivers/isdn/sc/event.c new file mode 100644 index 000000000000..5b8c7c1a7663 --- /dev/null +++ b/drivers/isdn/sc/event.c @@ -0,0 +1,69 @@ +/* $Id: event.c,v 1.4.8.1 2001/09/23 22:24:59 kai Exp $ + * + * Copyright (C) 1996 SpellCaster Telecommunications Inc. + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * For more information, please contact gpl-info@spellcast.com or write: + * + * SpellCaster Telecommunications Inc. + * 5621 Finch Avenue East, Unit #3 + * Scarborough, Ontario Canada + * M1B 2T9 + * +1 (416) 297-8565 + * +1 (416) 297-6433 Facsimile + */ + +#include "includes.h" +#include "hardware.h" +#include "message.h" +#include "card.h" + +extern int cinst; +extern board *sc_adapter[]; + +#ifdef DEBUG +static char *events[] = { "ISDN_STAT_STAVAIL", + "ISDN_STAT_ICALL", + "ISDN_STAT_RUN", + "ISDN_STAT_STOP", + "ISDN_STAT_DCONN", + "ISDN_STAT_BCONN", + "ISDN_STAT_DHUP", + "ISDN_STAT_BHUP", + "ISDN_STAT_CINF", + "ISDN_STAT_LOAD", + "ISDN_STAT_UNLOAD", + "ISDN_STAT_BSENT", + "ISDN_STAT_NODCH", + "ISDN_STAT_ADDCH", + "ISDN_STAT_CAUSE" }; +#endif + +int indicate_status(int card, int event,ulong Channel,char *Data) +{ + isdn_ctrl cmd; + + pr_debug("%s: Indicating event %s on Channel %d\n", + sc_adapter[card]->devicename, events[event-256], Channel); + if (Data != NULL){ + pr_debug("%s: Event data: %s\n", sc_adapter[card]->devicename, + Data); + switch (event) { + case ISDN_STAT_BSENT: + memcpy(&cmd.parm.length, Data, sizeof(cmd.parm.length)); + break; + case ISDN_STAT_ICALL: + memcpy(&cmd.parm.setup, Data, sizeof(cmd.parm.setup)); + break; + default: + strcpy(cmd.parm.num, Data); + } + } + + cmd.command = event; + cmd.driver = sc_adapter[card]->driverId; + cmd.arg = Channel; + return sc_adapter[card]->card->statcallb(&cmd); +} diff --git a/drivers/isdn/sc/hardware.h b/drivers/isdn/sc/hardware.h new file mode 100644 index 000000000000..9e6d5302bf8e --- /dev/null +++ b/drivers/isdn/sc/hardware.h @@ -0,0 +1,110 @@ +/* + * Hardware specific macros, defines and structures + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#ifndef HARDWARE_H +#define HARDWARE_H + +#include <asm/param.h> /* For HZ */ + +/* + * General hardware parameters common to all ISA adapters + */ + +#define MAX_CARDS 4 /* The maximum number of cards to + control or probe for. */ + +#define SIGNATURE 0x87654321 /* Board reset signature */ +#define SIG_OFFSET 0x1004 /* Where to find signature in shared RAM */ +#define TRACE_OFFSET 0x1008 /* Trace enable word offset in shared RAM */ +#define BUFFER_OFFSET 0x1800 /* Beginning of buffers */ + +/* I/O Port parameters */ +#define IOBASE_MIN 0x180 /* Lowest I/O port address */ +#define IOBASE_MAX 0x3C0 /* Highest I/O port address */ +#define IOBASE_OFFSET 0x20 /* Inter-board I/O port gap used during + probing */ +#define FIFORD_OFFSET 0x0 +#define FIFOWR_OFFSET 0x400 +#define FIFOSTAT_OFFSET 0x1000 +#define RESET_OFFSET 0x2800 +#define PG0_OFFSET 0x3000 /* Offset from I/O Base for Page 0 register */ +#define PG1_OFFSET 0x3400 /* Offset from I/O Base for Page 1 register */ +#define PG2_OFFSET 0x3800 /* Offset from I/O Base for Page 2 register */ +#define PG3_OFFSET 0x3C00 /* Offset from I/O Base for Page 3 register */ + +#define FIFO_READ 0 /* FIFO Read register */ +#define FIFO_WRITE 1 /* FIFO Write rgister */ +#define LO_ADDR_PTR 2 /* Extended RAM Low Addr Pointer */ +#define HI_ADDR_PTR 3 /* Extended RAM High Addr Pointer */ +#define NOT_USED_1 4 +#define FIFO_STATUS 5 /* FIFO Status Register */ +#define NOT_USED_2 6 +#define MEM_OFFSET 7 +#define SFT_RESET 10 /* Reset Register */ +#define EXP_BASE 11 /* Shared RAM Base address */ +#define EXP_PAGE0 12 /* Shared RAM Page0 register */ +#define EXP_PAGE1 13 /* Shared RAM Page1 register */ +#define EXP_PAGE2 14 /* Shared RAM Page2 register */ +#define EXP_PAGE3 15 /* Shared RAM Page3 register */ +#define IRQ_SELECT 16 /* IRQ selection register */ +#define MAX_IO_REGS 17 /* Total number of I/O ports */ + +/* FIFO register values */ +#define RF_HAS_DATA 0x01 /* fifo has data */ +#define RF_QUART_FULL 0x02 /* fifo quarter full */ +#define RF_HALF_FULL 0x04 /* fifo half full */ +#define RF_NOT_FULL 0x08 /* fifo not full */ +#define WF_HAS_DATA 0x10 /* fifo has data */ +#define WF_QUART_FULL 0x20 /* fifo quarter full */ +#define WF_HALF_FULL 0x40 /* fifo half full */ +#define WF_NOT_FULL 0x80 /* fifo not full */ + +/* Shared RAM parameters */ +#define SRAM_MIN 0xC0000 /* Lowest host shared RAM address */ +#define SRAM_MAX 0xEFFFF /* Highest host shared RAM address */ +#define SRAM_PAGESIZE 0x4000 /* Size of one RAM page (16K) */ + +/* Shared RAM buffer parameters */ +#define BUFFER_SIZE 0x800 /* The size of a buffer in bytes */ +#define BUFFER_BASE BUFFER_OFFSET /* Offset from start of shared RAM + where buffer start */ +#define BUFFERS_MAX 16 /* Maximum number of send/receive + buffers per channel */ +#define HDLC_PROTO 0x01 /* Frame Format for Layer 2 */ + +#define BRI_BOARD 0 +#define POTS_BOARD 1 +#define PRI_BOARD 2 + +/* + * Specific hardware parameters for the DataCommute/BRI + */ +#define BRI_CHANNELS 2 /* Number of B channels */ +#define BRI_BASEPG_VAL 0x98 +#define BRI_MAGIC 0x60000 /* Magic Number */ +#define BRI_MEMSIZE 0x10000 /* Ammount of RAM (64K) */ +#define BRI_PARTNO "72-029" +#define BRI_FEATURES ISDN_FEATURE_L2_HDLC | ISDN_FEATURE_L3_TRANS; +/* + * Specific hardware parameters for the DataCommute/PRI + */ +#define PRI_CHANNELS 23 /* Number of B channels */ +#define PRI_BASEPG_VAL 0x88 +#define PRI_MAGIC 0x20000 /* Magic Number */ +#define PRI_MEMSIZE 0x100000 /* Amount of RAM (1M) */ +#define PRI_PARTNO "72-030" +#define PRI_FEATURES ISDN_FEATURE_L2_HDLC | ISDN_FEATURE_L3_TRANS; + +/* + * Some handy macros + */ + +/* Determine if a channel number is valid for the adapter */ +#define IS_VALID_CHANNEL(y,x) ((x>0) && (x <= sc_adapter[y]->channels)) + +#endif diff --git a/drivers/isdn/sc/includes.h b/drivers/isdn/sc/includes.h new file mode 100644 index 000000000000..4611da6e9231 --- /dev/null +++ b/drivers/isdn/sc/includes.h @@ -0,0 +1,18 @@ +/* + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/version.h> +#include <linux/errno.h> +#include <asm/io.h> +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/ioport.h> +#include <linux/timer.h> +#include <linux/wait.h> +#include <linux/isdnif.h> +#include "debug.h" diff --git a/drivers/isdn/sc/init.c b/drivers/isdn/sc/init.c new file mode 100644 index 000000000000..efefedea37b9 --- /dev/null +++ b/drivers/isdn/sc/init.c @@ -0,0 +1,571 @@ +/* + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include "includes.h" +#include "hardware.h" +#include "card.h" + +MODULE_DESCRIPTION("ISDN4Linux: Driver for Spellcaster card"); +MODULE_AUTHOR("Spellcaster Telecommunications Inc."); +MODULE_LICENSE("GPL"); + +board *sc_adapter[MAX_CARDS]; +int cinst; + +static char devname[] = "scX"; +const char version[] = "2.0b1"; + +const char *boardname[] = { "DataCommute/BRI", "DataCommute/PRI", "TeleCommute/BRI" }; + +/* insmod set parameters */ +static unsigned int io[] = {0,0,0,0}; +static unsigned char irq[] = {0,0,0,0}; +static unsigned long ram[] = {0,0,0,0}; +static int do_reset = 0; + +module_param_array(io, int, NULL, 0); +module_param_array(irq, int, NULL, 0); +module_param_array(ram, int, NULL, 0); +module_param(do_reset, bool, 0); + +static int sup_irq[] = { 11, 10, 9, 5, 12, 14, 7, 3, 4, 6 }; +#define MAX_IRQS 10 + +extern irqreturn_t interrupt_handler(int, void *, struct pt_regs *); +extern int sndpkt(int, int, int, struct sk_buff *); +extern int command(isdn_ctrl *); +extern int indicate_status(int, int, ulong, char*); +extern int reset(int); + +int identify_board(unsigned long, unsigned int); + +int irq_supported(int irq_x) +{ + int i; + for(i=0 ; i < MAX_IRQS ; i++) { + if(sup_irq[i] == irq_x) + return 1; + } + return 0; +} + +static int __init sc_init(void) +{ + int b = -1; + int i, j; + int status = -ENODEV; + + unsigned long memsize = 0; + unsigned long features = 0; + isdn_if *interface; + unsigned char channels; + unsigned char pgport; + unsigned long magic; + int model; + int last_base = IOBASE_MIN; + int probe_exhasted = 0; + +#ifdef MODULE + pr_info("SpellCaster ISA ISDN Adapter Driver rev. %s Loaded\n", version); +#else + pr_info("SpellCaster ISA ISDN Adapter Driver rev. %s\n", version); +#endif + pr_info("Copyright (C) 1996 SpellCaster Telecommunications Inc.\n"); + + while(b++ < MAX_CARDS - 1) { + pr_debug("Probing for adapter #%d\n", b); + /* + * Initialize reusable variables + */ + model = -1; + magic = 0; + channels = 0; + pgport = 0; + + /* + * See if we should probe for IO base + */ + pr_debug("I/O Base for board %d is 0x%x, %s probe\n", b, io[b], + io[b] == 0 ? "will" : "won't"); + if(io[b]) { + /* + * No, I/O Base has been provided + */ + for (i = 0 ; i < MAX_IO_REGS - 1 ; i++) { + if(!request_region(io[b] + i * 0x400, 1, "sc test")) { + pr_debug("check_region for 0x%x failed\n", io[b] + i * 0x400); + io[b] = 0; + break; + } else + release_region(io[b] + i * 0x400, 1); + } + + /* + * Confirm the I/O Address with a test + */ + if(io[b] == 0) { + pr_debug("I/O Address 0x%x is in use.\n"); + continue; + } + + outb(0x18, io[b] + 0x400 * EXP_PAGE0); + if(inb(io[b] + 0x400 * EXP_PAGE0) != 0x18) { + pr_debug("I/O Base 0x%x fails test\n"); + continue; + } + } + else { + /* + * Yes, probe for I/O Base + */ + if(probe_exhasted) { + pr_debug("All probe addresses exhasted, skipping\n"); + continue; + } + pr_debug("Probing for I/O...\n"); + for (i = last_base ; i <= IOBASE_MAX ; i += IOBASE_OFFSET) { + int found_io = 1; + if (i == IOBASE_MAX) { + probe_exhasted = 1; /* No more addresses to probe */ + pr_debug("End of Probes\n"); + } + last_base = i + IOBASE_OFFSET; + pr_debug(" checking 0x%x...", i); + for ( j = 0 ; j < MAX_IO_REGS - 1 ; j++) { + if(!request_region(i + j * 0x400, 1, "sc test")) { + pr_debug("Failed\n"); + found_io = 0; + break; + } else + release_region(i + j * 0x400, 1); + } + + if(found_io) { + io[b] = i; + outb(0x18, io[b] + 0x400 * EXP_PAGE0); + if(inb(io[b] + 0x400 * EXP_PAGE0) != 0x18) { + pr_debug("Failed by test\n"); + continue; + } + pr_debug("Passed\n"); + break; + } + } + if(probe_exhasted) { + continue; + } + } + + /* + * See if we should probe for shared RAM + */ + if(do_reset) { + pr_debug("Doing a SAFE probe reset\n"); + outb(0xFF, io[b] + RESET_OFFSET); + msleep_interruptible(10000); + } + pr_debug("RAM Base for board %d is 0x%x, %s probe\n", b, ram[b], + ram[b] == 0 ? "will" : "won't"); + + if(ram[b]) { + /* + * No, the RAM base has been provided + * Just look for a signature and ID the + * board model + */ + if(request_region(ram[b], SRAM_PAGESIZE, "sc test")) { + pr_debug("request_region for RAM base 0x%x succeeded\n", ram[b]); + model = identify_board(ram[b], io[b]); + release_region(ram[b], SRAM_PAGESIZE); + } + } + else { + /* + * Yes, probe for free RAM and look for + * a signature and id the board model + */ + for (i = SRAM_MIN ; i < SRAM_MAX ; i += SRAM_PAGESIZE) { + pr_debug("Checking RAM address 0x%x...\n", i); + if(request_region(i, SRAM_PAGESIZE, "sc test")) { + pr_debug(" check_region succeeded\n"); + model = identify_board(i, io[b]); + release_region(i, SRAM_PAGESIZE); + if (model >= 0) { + pr_debug(" Identified a %s\n", + boardname[model]); + ram[b] = i; + break; + } + pr_debug(" Unidentifed or inaccessible\n"); + continue; + } + pr_debug(" request failed\n"); + } + } + /* + * See if we found free RAM and the board model + */ + if(!ram[b] || model < 0) { + /* + * Nope, there was no place in RAM for the + * board, or it couldn't be identified + */ + pr_debug("Failed to find an adapter at 0x%x\n", ram[b]); + continue; + } + + /* + * Set the board's magic number, memory size and page register + */ + switch(model) { + case PRI_BOARD: + channels = 23; + magic = 0x20000; + memsize = 0x100000; + features = PRI_FEATURES; + break; + + case BRI_BOARD: + case POTS_BOARD: + channels = 2; + magic = 0x60000; + memsize = 0x10000; + features = BRI_FEATURES; + break; + } + switch(ram[b] >> 12 & 0x0F) { + case 0x0: + pr_debug("RAM Page register set to EXP_PAGE0\n"); + pgport = EXP_PAGE0; + break; + + case 0x4: + pr_debug("RAM Page register set to EXP_PAGE1\n"); + pgport = EXP_PAGE1; + break; + + case 0x8: + pr_debug("RAM Page register set to EXP_PAGE2\n"); + pgport = EXP_PAGE2; + break; + + case 0xC: + pr_debug("RAM Page register set to EXP_PAGE3\n"); + pgport = EXP_PAGE3; + break; + + default: + pr_debug("RAM base address doesn't fall on 16K boundary\n"); + continue; + } + + pr_debug("current IRQ: %d b: %d\n",irq[b],b); + + /* + * Make sure we got an IRQ + */ + if(!irq[b]) { + /* + * No interrupt could be used + */ + pr_debug("Failed to acquire an IRQ line\n"); + continue; + } + + /* + * Horray! We found a board, Make sure we can register + * it with ISDN4Linux + */ + interface = kmalloc(sizeof(isdn_if), GFP_KERNEL); + if (interface == NULL) { + /* + * Oops, can't malloc isdn_if + */ + continue; + } + memset(interface, 0, sizeof(isdn_if)); + + interface->owner = THIS_MODULE; + interface->hl_hdrlen = 0; + interface->channels = channels; + interface->maxbufsize = BUFFER_SIZE; + interface->features = features; + interface->writebuf_skb = sndpkt; + interface->writecmd = NULL; + interface->command = command; + strcpy(interface->id, devname); + interface->id[2] = '0' + cinst; + + /* + * Allocate the board structure + */ + sc_adapter[cinst] = kmalloc(sizeof(board), GFP_KERNEL); + if (sc_adapter[cinst] == NULL) { + /* + * Oops, can't alloc memory for the board + */ + kfree(interface); + continue; + } + memset(sc_adapter[cinst], 0, sizeof(board)); + spin_lock_init(&sc_adapter[cinst]->lock); + + if(!register_isdn(interface)) { + /* + * Oops, couldn't register for some reason + */ + kfree(interface); + kfree(sc_adapter[cinst]); + continue; + } + + sc_adapter[cinst]->card = interface; + sc_adapter[cinst]->driverId = interface->channels; + strcpy(sc_adapter[cinst]->devicename, interface->id); + sc_adapter[cinst]->nChannels = channels; + sc_adapter[cinst]->ramsize = memsize; + sc_adapter[cinst]->shmem_magic = magic; + sc_adapter[cinst]->shmem_pgport = pgport; + sc_adapter[cinst]->StartOnReset = 1; + + /* + * Allocate channels status structures + */ + sc_adapter[cinst]->channel = kmalloc(sizeof(bchan) * channels, GFP_KERNEL); + if (sc_adapter[cinst]->channel == NULL) { + /* + * Oops, can't alloc memory for the channels + */ + indicate_status(cinst, ISDN_STAT_UNLOAD, 0, NULL); /* Fix me */ + kfree(interface); + kfree(sc_adapter[cinst]); + continue; + } + memset(sc_adapter[cinst]->channel, 0, sizeof(bchan) * channels); + + /* + * Lock down the hardware resources + */ + sc_adapter[cinst]->interrupt = irq[b]; + if (request_irq(sc_adapter[cinst]->interrupt, interrupt_handler, + SA_INTERRUPT, interface->id, NULL)) + { + kfree(sc_adapter[cinst]->channel); + indicate_status(cinst, ISDN_STAT_UNLOAD, 0, NULL); /* Fix me */ + kfree(interface); + kfree(sc_adapter[cinst]); + continue; + + } + sc_adapter[cinst]->iobase = io[b]; + for(i = 0 ; i < MAX_IO_REGS - 1 ; i++) { + sc_adapter[cinst]->ioport[i] = io[b] + i * 0x400; + request_region(sc_adapter[cinst]->ioport[i], 1, + interface->id); + pr_debug("Requesting I/O Port %#x\n", + sc_adapter[cinst]->ioport[i]); + } + sc_adapter[cinst]->ioport[IRQ_SELECT] = io[b] + 0x2; + request_region(sc_adapter[cinst]->ioport[IRQ_SELECT], 1, + interface->id); + pr_debug("Requesting I/O Port %#x\n", + sc_adapter[cinst]->ioport[IRQ_SELECT]); + sc_adapter[cinst]->rambase = ram[b]; + request_region(sc_adapter[cinst]->rambase, SRAM_PAGESIZE, + interface->id); + + pr_info(" %s (%d) - %s %d channels IRQ %d, I/O Base 0x%x, RAM Base 0x%lx\n", + sc_adapter[cinst]->devicename, + sc_adapter[cinst]->driverId, + boardname[model], channels, irq[b], io[b], ram[b]); + + /* + * reset the adapter to put things in motion + */ + reset(cinst); + + cinst++; + status = 0; + } + if (status) + pr_info("Failed to find any adapters, driver unloaded\n"); + return status; +} + +static void __exit sc_exit(void) +{ + int i, j; + + for(i = 0 ; i < cinst ; i++) { + pr_debug("Cleaning up after adapter %d\n", i); + /* + * kill the timers + */ + del_timer(&(sc_adapter[i]->reset_timer)); + del_timer(&(sc_adapter[i]->stat_timer)); + + /* + * Tell I4L we're toast + */ + indicate_status(i, ISDN_STAT_STOP, 0, NULL); + indicate_status(i, ISDN_STAT_UNLOAD, 0, NULL); + + /* + * Release shared RAM + */ + release_region(sc_adapter[i]->rambase, SRAM_PAGESIZE); + + /* + * Release the IRQ + */ + FREE_IRQ(sc_adapter[i]->interrupt, NULL); + + /* + * Reset for a clean start + */ + outb(0xFF, sc_adapter[i]->ioport[SFT_RESET]); + + /* + * Release the I/O Port regions + */ + for(j = 0 ; j < MAX_IO_REGS - 1; j++) { + release_region(sc_adapter[i]->ioport[j], 1); + pr_debug("Releasing I/O Port %#x\n", + sc_adapter[i]->ioport[j]); + } + release_region(sc_adapter[i]->ioport[IRQ_SELECT], 1); + pr_debug("Releasing I/O Port %#x\n", + sc_adapter[i]->ioport[IRQ_SELECT]); + + /* + * Release any memory we alloced + */ + kfree(sc_adapter[i]->channel); + kfree(sc_adapter[i]->card); + kfree(sc_adapter[i]); + } + pr_info("SpellCaster ISA ISDN Adapter Driver Unloaded.\n"); +} + +int identify_board(unsigned long rambase, unsigned int iobase) +{ + unsigned int pgport; + unsigned long sig; + DualPortMemory *dpm; + RspMessage rcvmsg; + ReqMessage sndmsg; + HWConfig_pl hwci; + int x; + + pr_debug("Attempting to identify adapter @ 0x%x io 0x%x\n", + rambase, iobase); + + /* + * Enable the base pointer + */ + outb(rambase >> 12, iobase + 0x2c00); + + switch(rambase >> 12 & 0x0F) { + case 0x0: + pgport = iobase + PG0_OFFSET; + pr_debug("Page Register offset is 0x%x\n", PG0_OFFSET); + break; + + case 0x4: + pgport = iobase + PG1_OFFSET; + pr_debug("Page Register offset is 0x%x\n", PG1_OFFSET); + break; + + case 0x8: + pgport = iobase + PG2_OFFSET; + pr_debug("Page Register offset is 0x%x\n", PG2_OFFSET); + break; + + case 0xC: + pgport = iobase + PG3_OFFSET; + pr_debug("Page Register offset is 0x%x\n", PG3_OFFSET); + break; + default: + pr_debug("Invalid rambase 0x%lx\n", rambase); + return -1; + } + + /* + * Try to identify a PRI card + */ + outb(PRI_BASEPG_VAL, pgport); + msleep_interruptible(1000); + sig = readl(rambase + SIG_OFFSET); + pr_debug("Looking for a signature, got 0x%x\n", sig); + if(sig == SIGNATURE) + return PRI_BOARD; + + /* + * Try to identify a PRI card + */ + outb(BRI_BASEPG_VAL, pgport); + msleep_interruptible(1000); + sig = readl(rambase + SIG_OFFSET); + pr_debug("Looking for a signature, got 0x%x\n", sig); + if(sig == SIGNATURE) + return BRI_BOARD; + + return -1; + + /* + * Try to spot a card + */ + sig = readl(rambase + SIG_OFFSET); + pr_debug("Looking for a signature, got 0x%x\n", sig); + if(sig != SIGNATURE) + return -1; + + dpm = (DualPortMemory *) rambase; + + memset(&sndmsg, 0, MSG_LEN); + sndmsg.msg_byte_cnt = 3; + sndmsg.type = cmReqType1; + sndmsg.class = cmReqClass0; + sndmsg.code = cmReqHWConfig; + memcpy_toio(&(dpm->req_queue[dpm->req_head++]), &sndmsg, MSG_LEN); + outb(0, iobase + 0x400); + pr_debug("Sent HWConfig message\n"); + /* + * Wait for the response + */ + x = 0; + while((inb(iobase + FIFOSTAT_OFFSET) & RF_HAS_DATA) && x < 100) { + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(1); + x++; + } + if(x == 100) { + pr_debug("Timeout waiting for response\n"); + return -1; + } + + memcpy_fromio(&rcvmsg, &(dpm->rsp_queue[dpm->rsp_tail]), MSG_LEN); + pr_debug("Got HWConfig response, status = 0x%x\n", rcvmsg.rsp_status); + memcpy(&hwci, &(rcvmsg.msg_data.HWCresponse), sizeof(HWConfig_pl)); + pr_debug("Hardware Config: Interface: %s, RAM Size: %d, Serial: %s\n" + " Part: %s, Rev: %s\n", + hwci.st_u_sense ? "S/T" : "U", hwci.ram_size, + hwci.serial_no, hwci.part_no, hwci.rev_no); + + if(!strncmp(PRI_PARTNO, hwci.part_no, 6)) + return PRI_BOARD; + if(!strncmp(BRI_PARTNO, hwci.part_no, 6)) + return BRI_BOARD; + + return -1; +} + +module_init(sc_init); +module_exit(sc_exit); diff --git a/drivers/isdn/sc/interrupt.c b/drivers/isdn/sc/interrupt.c new file mode 100644 index 000000000000..e5e164aca7fa --- /dev/null +++ b/drivers/isdn/sc/interrupt.c @@ -0,0 +1,260 @@ +/* $Id: interrupt.c,v 1.4.8.3 2001/09/23 22:24:59 kai Exp $ + * + * Copyright (C) 1996 SpellCaster Telecommunications Inc. + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * For more information, please contact gpl-info@spellcast.com or write: + * + * SpellCaster Telecommunications Inc. + * 5621 Finch Avenue East, Unit #3 + * Scarborough, Ontario Canada + * M1B 2T9 + * +1 (416) 297-8565 + * +1 (416) 297-6433 Facsimile + */ + +#include "includes.h" +#include "hardware.h" +#include "message.h" +#include "card.h" +#include <linux/interrupt.h> + +extern int indicate_status(int, int, ulong, char *); +extern void check_phystat(unsigned long); +extern int receivemessage(int, RspMessage *); +extern int sendmessage(int, unsigned int, unsigned int, unsigned int, + unsigned int, unsigned int, unsigned int, unsigned int *); +extern void rcvpkt(int, RspMessage *); + +extern int cinst; +extern board *sc_adapter[]; + +int get_card_from_irq(int irq) +{ + int i; + + for(i = 0 ; i < cinst ; i++) { + if(sc_adapter[i]->interrupt == irq) + return i; + } + return -1; +} + +/* + * + */ +irqreturn_t interrupt_handler(int interrupt, void *cardptr, struct pt_regs *regs) +{ + + RspMessage rcvmsg; + int channel; + int card; + + card = get_card_from_irq(interrupt); + + if(!IS_VALID_CARD(card)) { + pr_debug("Invalid param: %d is not a valid card id\n", card); + return IRQ_NONE; + } + + pr_debug("%s: Entered Interrupt handler\n", + sc_adapter[card]->devicename); + + /* + * Pull all of the waiting messages off the response queue + */ + while (!receivemessage(card, &rcvmsg)) { + /* + * Push the message to the adapter structure for + * send_and_receive to snoop + */ + if(sc_adapter[card]->want_async_messages) + memcpy(&(sc_adapter[card]->async_msg), + &rcvmsg, sizeof(RspMessage)); + + channel = (unsigned int) rcvmsg.phy_link_no; + + /* + * Trap Invalid request messages + */ + if(IS_CM_MESSAGE(rcvmsg, 0, 0, Invalid)) { + pr_debug("%s: Invalid request Message, rsp_status = %d\n", + sc_adapter[card]->devicename, + rcvmsg.rsp_status); + break; + } + + /* + * Check for a linkRead message + */ + if (IS_CE_MESSAGE(rcvmsg, Lnk, 1, Read)) + { + pr_debug("%s: Received packet 0x%x bytes long at 0x%x\n", + sc_adapter[card]->devicename, + rcvmsg.msg_data.response.msg_len, + rcvmsg.msg_data.response.buff_offset); + rcvpkt(card, &rcvmsg); + continue; + + } + + /* + * Handle a write acknoledgement + */ + if(IS_CE_MESSAGE(rcvmsg, Lnk, 1, Write)) { + pr_debug("%s: Packet Send ACK on channel %d\n", + sc_adapter[card]->devicename, + rcvmsg.phy_link_no); + sc_adapter[card]->channel[rcvmsg.phy_link_no-1].free_sendbufs++; + continue; + } + + /* + * Handle a connection message + */ + if (IS_CE_MESSAGE(rcvmsg, Phy, 1, Connect)) + { + unsigned int callid; + setup_parm setup; + pr_debug("%s: Connect message: line %d: status %d: cause 0x%x\n", + sc_adapter[card]->devicename, + rcvmsg.phy_link_no, + rcvmsg.rsp_status, + rcvmsg.msg_data.byte_array[2]); + + memcpy(&callid,rcvmsg.msg_data.byte_array,sizeof(int)); + if(callid>=0x8000 && callid<=0xFFFF) + { + pr_debug("%s: Got Dial-Out Rsp\n", + sc_adapter[card]->devicename); + indicate_status(card, ISDN_STAT_DCONN, + (unsigned long)rcvmsg.phy_link_no-1,NULL); + + } + else if(callid>=0x0000 && callid<=0x7FFF) + { + pr_debug("%s: Got Incoming Call\n", + sc_adapter[card]->devicename); + strcpy(setup.phone,&(rcvmsg.msg_data.byte_array[4])); + strcpy(setup.eazmsn, + sc_adapter[card]->channel[rcvmsg.phy_link_no-1].dn); + setup.si1 = 7; + setup.si2 = 0; + setup.plan = 0; + setup.screen = 0; + + indicate_status(card, ISDN_STAT_ICALL,(unsigned long)rcvmsg.phy_link_no-1,(char *)&setup); + indicate_status(card, ISDN_STAT_DCONN,(unsigned long)rcvmsg.phy_link_no-1,NULL); + } + continue; + } + + /* + * Handle a disconnection message + */ + if (IS_CE_MESSAGE(rcvmsg, Phy, 1, Disconnect)) + { + pr_debug("%s: disconnect message: line %d: status %d: cause 0x%x\n", + sc_adapter[card]->devicename, + rcvmsg.phy_link_no, + rcvmsg.rsp_status, + rcvmsg.msg_data.byte_array[2]); + + indicate_status(card, ISDN_STAT_BHUP,(unsigned long)rcvmsg.phy_link_no-1,NULL); + indicate_status(card, ISDN_STAT_DHUP,(unsigned long)rcvmsg.phy_link_no-1,NULL); + continue; + + } + + /* + * Handle a startProc engine up message + */ + if (IS_CM_MESSAGE(rcvmsg, 5, 0, MiscEngineUp)) { + pr_debug("%s: Received EngineUp message\n", + sc_adapter[card]->devicename); + sc_adapter[card]->EngineUp = 1; + sendmessage(card, CEPID,ceReqTypeCall,ceReqClass0,ceReqCallGetMyNumber,1,0,NULL); + sendmessage(card, CEPID,ceReqTypeCall,ceReqClass0,ceReqCallGetMyNumber,2,0,NULL); + init_timer(&sc_adapter[card]->stat_timer); + sc_adapter[card]->stat_timer.function = check_phystat; + sc_adapter[card]->stat_timer.data = card; + sc_adapter[card]->stat_timer.expires = jiffies + CHECKSTAT_TIME; + add_timer(&sc_adapter[card]->stat_timer); + continue; + } + + /* + * Start proc response + */ + if (IS_CM_MESSAGE(rcvmsg, 2, 0, StartProc)) { + pr_debug("%s: StartProc Response Status %d\n", + sc_adapter[card]->devicename, + rcvmsg.rsp_status); + continue; + } + + /* + * Handle a GetMyNumber Rsp + */ + if (IS_CE_MESSAGE(rcvmsg,Call,0,GetMyNumber)){ + strcpy(sc_adapter[card]->channel[rcvmsg.phy_link_no-1].dn,rcvmsg.msg_data.byte_array); + continue; + } + + /* + * PhyStatus response + */ + if(IS_CE_MESSAGE(rcvmsg, Phy, 2, Status)) { + unsigned int b1stat, b2stat; + + /* + * Covert the message data to the adapter->phystat code + */ + b1stat = (unsigned int) rcvmsg.msg_data.byte_array[0]; + b2stat = (unsigned int) rcvmsg.msg_data.byte_array[1]; + + sc_adapter[card]->nphystat = (b2stat >> 8) | b1stat; /* endian?? */ + pr_debug("%s: PhyStat is 0x%2x\n", + sc_adapter[card]->devicename, + sc_adapter[card]->nphystat); + continue; + } + + + /* + * Handle a GetFramFormat + */ + if(IS_CE_MESSAGE(rcvmsg, Call, 0, GetFrameFormat)) { + if(rcvmsg.msg_data.byte_array[0] != HDLC_PROTO) { + unsigned int proto = HDLC_PROTO; + /* + * Set board format to HDLC if it wasn't already + */ + pr_debug("%s: current frame format: 0x%x, will change to HDLC\n", + sc_adapter[card]->devicename, + rcvmsg.msg_data.byte_array[0]); + sendmessage(card, CEPID, ceReqTypeCall, + ceReqClass0, + ceReqCallSetFrameFormat, + (unsigned char) channel +1, + 1,&proto); + } + continue; + } + + /* + * Hmm... + */ + pr_debug("%s: Received unhandled message (%d,%d,%d) link %d\n", + sc_adapter[card]->devicename, + rcvmsg.type, rcvmsg.class, rcvmsg.code, + rcvmsg.phy_link_no); + + } /* while */ + + pr_debug("%s: Exiting Interrupt Handler\n", + sc_adapter[card]->devicename); + return IRQ_HANDLED; +} diff --git a/drivers/isdn/sc/ioctl.c b/drivers/isdn/sc/ioctl.c new file mode 100644 index 000000000000..1371a990416a --- /dev/null +++ b/drivers/isdn/sc/ioctl.c @@ -0,0 +1,601 @@ +/* + * Copyright (C) 1996 SpellCaster Telecommunications Inc. + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include "includes.h" +#include "hardware.h" +#include "message.h" +#include "card.h" +#include "scioc.h" + +extern int indicate_status(int, int, unsigned long, char *); +extern int startproc(int); +extern int loadproc(int, char *record); +extern int reset(int); +extern int send_and_receive(int, unsigned int, unsigned char,unsigned char, + unsigned char,unsigned char, + unsigned char, unsigned char *, RspMessage *, int); + +extern board *sc_adapter[]; + + +int GetStatus(int card, boardInfo *); + +/* + * Process private IOCTL messages (typically from scctrl) + */ +int sc_ioctl(int card, scs_ioctl *data) +{ + int status; + RspMessage *rcvmsg; + char *spid; + char *dn; + char switchtype; + char speed; + + rcvmsg = kmalloc(sizeof(RspMessage), GFP_KERNEL); + if (!rcvmsg) + return -ENOMEM; + + switch(data->command) { + case SCIOCRESET: /* Perform a hard reset of the adapter */ + { + pr_debug("%s: SCIOCRESET: ioctl received\n", + sc_adapter[card]->devicename); + sc_adapter[card]->StartOnReset = 0; + return (reset(card)); + } + + case SCIOCLOAD: + { + char *srec; + + srec = kmalloc(SCIOC_SRECSIZE, GFP_KERNEL); + if (!srec) { + kfree(rcvmsg); + return -ENOMEM; + } + pr_debug("%s: SCIOLOAD: ioctl received\n", + sc_adapter[card]->devicename); + if(sc_adapter[card]->EngineUp) { + pr_debug("%s: SCIOCLOAD: command failed, LoadProc while engine running.\n", + sc_adapter[card]->devicename); + kfree(rcvmsg); + kfree(srec); + return -1; + } + + /* + * Get the SRec from user space + */ + if (copy_from_user(srec, data->dataptr, sizeof(srec))) { + kfree(rcvmsg); + kfree(srec); + return -EFAULT; + } + + status = send_and_receive(card, CMPID, cmReqType2, cmReqClass0, cmReqLoadProc, + 0, sizeof(srec), srec, rcvmsg, SAR_TIMEOUT); + kfree(rcvmsg); + kfree(srec); + + if(status) { + pr_debug("%s: SCIOCLOAD: command failed, status = %d\n", + sc_adapter[card]->devicename, status); + return -1; + } + else { + pr_debug("%s: SCIOCLOAD: command successful\n", + sc_adapter[card]->devicename); + return 0; + } + } + + case SCIOCSTART: + { + pr_debug("%s: SCIOSTART: ioctl received\n", + sc_adapter[card]->devicename); + if(sc_adapter[card]->EngineUp) { + pr_debug("%s: SCIOCSTART: command failed, engine already running.\n", + sc_adapter[card]->devicename); + return -1; + } + + sc_adapter[card]->StartOnReset = 1; + startproc(card); + return 0; + } + + case SCIOCSETSWITCH: + { + pr_debug("%s: SCIOSETSWITCH: ioctl received\n", + sc_adapter[card]->devicename); + + /* + * Get the switch type from user space + */ + if (copy_from_user(&switchtype, data->dataptr, sizeof(char))) { + kfree(rcvmsg); + return -EFAULT; + } + + pr_debug("%s: SCIOCSETSWITCH: setting switch type to %d\n", + sc_adapter[card]->devicename, + switchtype); + status = send_and_receive(card, CEPID, ceReqTypeCall, ceReqClass0, ceReqCallSetSwitchType, + 0, sizeof(char),&switchtype, rcvmsg, SAR_TIMEOUT); + if(!status && !(rcvmsg->rsp_status)) { + pr_debug("%s: SCIOCSETSWITCH: command successful\n", + sc_adapter[card]->devicename); + kfree(rcvmsg); + return 0; + } + else { + pr_debug("%s: SCIOCSETSWITCH: command failed (status = %d)\n", + sc_adapter[card]->devicename, status); + kfree(rcvmsg); + return status; + } + } + + case SCIOCGETSWITCH: + { + pr_debug("%s: SCIOGETSWITCH: ioctl received\n", + sc_adapter[card]->devicename); + + /* + * Get the switch type from the board + */ + status = send_and_receive(card, CEPID, ceReqTypeCall, ceReqClass0, + ceReqCallGetSwitchType, 0, 0, NULL, rcvmsg, SAR_TIMEOUT); + if (!status && !(rcvmsg->rsp_status)) { + pr_debug("%s: SCIOCGETSWITCH: command successful\n", + sc_adapter[card]->devicename); + } + else { + pr_debug("%s: SCIOCGETSWITCH: command failed (status = %d)\n", + sc_adapter[card]->devicename, status); + kfree(rcvmsg); + return status; + } + + switchtype = rcvmsg->msg_data.byte_array[0]; + + /* + * Package the switch type and send to user space + */ + if (copy_to_user(data->dataptr, &switchtype, + sizeof(char))) { + kfree(rcvmsg); + return -EFAULT; + } + + kfree(rcvmsg); + return 0; + } + + case SCIOCGETSPID: + { + pr_debug("%s: SCIOGETSPID: ioctl received\n", + sc_adapter[card]->devicename); + + spid = kmalloc(SCIOC_SPIDSIZE, GFP_KERNEL); + if(!spid) { + kfree(rcvmsg); + return -ENOMEM; + } + /* + * Get the spid from the board + */ + status = send_and_receive(card, CEPID, ceReqTypeCall, ceReqClass0, ceReqCallGetSPID, + data->channel, 0, NULL, rcvmsg, SAR_TIMEOUT); + if (!status) { + pr_debug("%s: SCIOCGETSPID: command successful\n", + sc_adapter[card]->devicename); + } + else { + pr_debug("%s: SCIOCGETSPID: command failed (status = %d)\n", + sc_adapter[card]->devicename, status); + kfree(rcvmsg); + return status; + } + strcpy(spid, rcvmsg->msg_data.byte_array); + + /* + * Package the switch type and send to user space + */ + if (copy_to_user(data->dataptr, spid, SCIOC_SPIDSIZE)) { + kfree(spid); + kfree(rcvmsg); + return -EFAULT; + } + + kfree(spid); + kfree(rcvmsg); + return 0; + } + + case SCIOCSETSPID: + { + pr_debug("%s: DCBIOSETSPID: ioctl received\n", + sc_adapter[card]->devicename); + + spid = kmalloc(SCIOC_SPIDSIZE, GFP_KERNEL); + if(!spid) { + kfree(rcvmsg); + return -ENOMEM; + } + + /* + * Get the spid from user space + */ + if (copy_from_user(spid, data->dataptr, SCIOC_SPIDSIZE)) { + kfree(rcvmsg); + return -EFAULT; + } + + pr_debug("%s: SCIOCSETSPID: setting channel %d spid to %s\n", + sc_adapter[card]->devicename, data->channel, spid); + status = send_and_receive(card, CEPID, ceReqTypeCall, + ceReqClass0, ceReqCallSetSPID, data->channel, + strlen(spid), spid, rcvmsg, SAR_TIMEOUT); + if(!status && !(rcvmsg->rsp_status)) { + pr_debug("%s: SCIOCSETSPID: command successful\n", + sc_adapter[card]->devicename); + kfree(rcvmsg); + kfree(spid); + return 0; + } + else { + pr_debug("%s: SCIOCSETSPID: command failed (status = %d)\n", + sc_adapter[card]->devicename, status); + kfree(rcvmsg); + kfree(spid); + return status; + } + } + + case SCIOCGETDN: + { + pr_debug("%s: SCIOGETDN: ioctl received\n", + sc_adapter[card]->devicename); + + /* + * Get the dn from the board + */ + status = send_and_receive(card, CEPID, ceReqTypeCall, ceReqClass0, ceReqCallGetMyNumber, + data->channel, 0, NULL, rcvmsg, SAR_TIMEOUT); + if (!status) { + pr_debug("%s: SCIOCGETDN: command successful\n", + sc_adapter[card]->devicename); + } + else { + pr_debug("%s: SCIOCGETDN: command failed (status = %d)\n", + sc_adapter[card]->devicename, status); + kfree(rcvmsg); + return status; + } + + dn = kmalloc(SCIOC_DNSIZE, GFP_KERNEL); + if (!dn) { + kfree(rcvmsg); + return -ENOMEM; + } + strcpy(dn, rcvmsg->msg_data.byte_array); + kfree(rcvmsg); + + /* + * Package the dn and send to user space + */ + if (copy_to_user(data->dataptr, dn, SCIOC_DNSIZE)) { + kfree(dn); + return -EFAULT; + } + kfree(dn); + return 0; + } + + case SCIOCSETDN: + { + pr_debug("%s: SCIOSETDN: ioctl received\n", + sc_adapter[card]->devicename); + + dn = kmalloc(SCIOC_DNSIZE, GFP_KERNEL); + if (!dn) { + kfree(rcvmsg); + return -ENOMEM; + } + /* + * Get the spid from user space + */ + if (copy_from_user(dn, data->dataptr, SCIOC_DNSIZE)) { + kfree(rcvmsg); + kfree(dn); + return -EFAULT; + } + + pr_debug("%s: SCIOCSETDN: setting channel %d dn to %s\n", + sc_adapter[card]->devicename, data->channel, dn); + status = send_and_receive(card, CEPID, ceReqTypeCall, + ceReqClass0, ceReqCallSetMyNumber, data->channel, + strlen(dn),dn,rcvmsg, SAR_TIMEOUT); + if(!status && !(rcvmsg->rsp_status)) { + pr_debug("%s: SCIOCSETDN: command successful\n", + sc_adapter[card]->devicename); + kfree(rcvmsg); + kfree(dn); + return 0; + } + else { + pr_debug("%s: SCIOCSETDN: command failed (status = %d)\n", + sc_adapter[card]->devicename, status); + kfree(rcvmsg); + kfree(dn); + return status; + } + } + + case SCIOCTRACE: + + pr_debug("%s: SCIOTRACE: ioctl received\n", + sc_adapter[card]->devicename); +/* sc_adapter[card]->trace = !sc_adapter[card]->trace; + pr_debug("%s: SCIOCTRACE: tracing turned %s\n", + sc_adapter[card]->devicename, + sc_adapter[card]->trace ? "ON" : "OFF"); */ + break; + + case SCIOCSTAT: + { + boardInfo *bi; + + pr_debug("%s: SCIOSTAT: ioctl received\n", + sc_adapter[card]->devicename); + + bi = kmalloc (sizeof(boardInfo), GFP_KERNEL); + if (!bi) { + kfree(rcvmsg); + return -ENOMEM; + } + + kfree(rcvmsg); + GetStatus(card, bi); + + if (copy_to_user(data->dataptr, bi, sizeof(boardInfo))) { + kfree(bi); + return -EFAULT; + } + + kfree(bi); + return 0; + } + + case SCIOCGETSPEED: + { + pr_debug("%s: SCIOGETSPEED: ioctl received\n", + sc_adapter[card]->devicename); + + /* + * Get the speed from the board + */ + status = send_and_receive(card, CEPID, ceReqTypeCall, ceReqClass0, + ceReqCallGetCallType, data->channel, 0, NULL, rcvmsg, SAR_TIMEOUT); + if (!status && !(rcvmsg->rsp_status)) { + pr_debug("%s: SCIOCGETSPEED: command successful\n", + sc_adapter[card]->devicename); + } + else { + pr_debug("%s: SCIOCGETSPEED: command failed (status = %d)\n", + sc_adapter[card]->devicename, status); + kfree(rcvmsg); + return status; + } + + speed = rcvmsg->msg_data.byte_array[0]; + + kfree(rcvmsg); + + /* + * Package the switch type and send to user space + */ + + if (copy_to_user(data->dataptr, &speed, sizeof(char))) + return -EFAULT; + + return 0; + } + + case SCIOCSETSPEED: + pr_debug("%s: SCIOCSETSPEED: ioctl received\n", + sc_adapter[card]->devicename); + break; + + case SCIOCLOOPTST: + pr_debug("%s: SCIOCLOOPTST: ioctl received\n", + sc_adapter[card]->devicename); + break; + + default: + kfree(rcvmsg); + return -1; + } + + kfree(rcvmsg); + return 0; +} + +int GetStatus(int card, boardInfo *bi) +{ + RspMessage rcvmsg; + int i, status; + + /* + * Fill in some of the basic info about the board + */ + bi->modelid = sc_adapter[card]->model; + strcpy(bi->serial_no, sc_adapter[card]->hwconfig.serial_no); + strcpy(bi->part_no, sc_adapter[card]->hwconfig.part_no); + bi->iobase = sc_adapter[card]->iobase; + bi->rambase = sc_adapter[card]->rambase; + bi->irq = sc_adapter[card]->interrupt; + bi->ramsize = sc_adapter[card]->hwconfig.ram_size; + bi->interface = sc_adapter[card]->hwconfig.st_u_sense; + strcpy(bi->load_ver, sc_adapter[card]->load_ver); + strcpy(bi->proc_ver, sc_adapter[card]->proc_ver); + + /* + * Get the current PhyStats and LnkStats + */ + status = send_and_receive(card, CEPID, ceReqTypePhy, ceReqClass2, + ceReqPhyStatus, 0, 0, NULL, &rcvmsg, SAR_TIMEOUT); + if(!status) { + if(sc_adapter[card]->model < PRI_BOARD) { + bi->l1_status = rcvmsg.msg_data.byte_array[2]; + for(i = 0 ; i < BRI_CHANNELS ; i++) + bi->status.bristats[i].phy_stat = + rcvmsg.msg_data.byte_array[i]; + } + else { + bi->l1_status = rcvmsg.msg_data.byte_array[0]; + bi->l2_status = rcvmsg.msg_data.byte_array[1]; + for(i = 0 ; i < PRI_CHANNELS ; i++) + bi->status.pristats[i].phy_stat = + rcvmsg.msg_data.byte_array[i+2]; + } + } + + /* + * Get the call types for each channel + */ + for (i = 0 ; i < sc_adapter[card]->nChannels ; i++) { + status = send_and_receive(card, CEPID, ceReqTypeCall, ceReqClass0, + ceReqCallGetCallType, 0, 0, NULL, &rcvmsg, SAR_TIMEOUT); + if(!status) { + if (sc_adapter[card]->model == PRI_BOARD) { + bi->status.pristats[i].call_type = + rcvmsg.msg_data.byte_array[0]; + } + else { + bi->status.bristats[i].call_type = + rcvmsg.msg_data.byte_array[0]; + } + } + } + + /* + * If PRI, get the call states and service states for each channel + */ + if (sc_adapter[card]->model == PRI_BOARD) { + /* + * Get the call states + */ + status = send_and_receive(card, CEPID, ceReqTypeStat, ceReqClass2, + ceReqPhyChCallState, 0, 0, NULL, &rcvmsg, SAR_TIMEOUT); + if(!status) { + for( i = 0 ; i < PRI_CHANNELS ; i++ ) + bi->status.pristats[i].call_state = + rcvmsg.msg_data.byte_array[i]; + } + + /* + * Get the service states + */ + status = send_and_receive(card, CEPID, ceReqTypeStat, ceReqClass2, + ceReqPhyChServState, 0, 0, NULL, &rcvmsg, SAR_TIMEOUT); + if(!status) { + for( i = 0 ; i < PRI_CHANNELS ; i++ ) + bi->status.pristats[i].serv_state = + rcvmsg.msg_data.byte_array[i]; + } + + /* + * Get the link stats for the channels + */ + for (i = 1 ; i <= PRI_CHANNELS ; i++) { + status = send_and_receive(card, CEPID, ceReqTypeLnk, ceReqClass0, + ceReqLnkGetStats, i, 0, NULL, &rcvmsg, SAR_TIMEOUT); + if (!status) { + bi->status.pristats[i-1].link_stats.tx_good = + (unsigned long)rcvmsg.msg_data.byte_array[0]; + bi->status.pristats[i-1].link_stats.tx_bad = + (unsigned long)rcvmsg.msg_data.byte_array[4]; + bi->status.pristats[i-1].link_stats.rx_good = + (unsigned long)rcvmsg.msg_data.byte_array[8]; + bi->status.pristats[i-1].link_stats.rx_bad = + (unsigned long)rcvmsg.msg_data.byte_array[12]; + } + } + + /* + * Link stats for the D channel + */ + status = send_and_receive(card, CEPID, ceReqTypeLnk, ceReqClass0, + ceReqLnkGetStats, 0, 0, NULL, &rcvmsg, SAR_TIMEOUT); + if (!status) { + bi->dch_stats.tx_good = (unsigned long)rcvmsg.msg_data.byte_array[0]; + bi->dch_stats.tx_bad = (unsigned long)rcvmsg.msg_data.byte_array[4]; + bi->dch_stats.rx_good = (unsigned long)rcvmsg.msg_data.byte_array[8]; + bi->dch_stats.rx_bad = (unsigned long)rcvmsg.msg_data.byte_array[12]; + } + + return 0; + } + + /* + * If BRI or POTS, Get SPID, DN and call types for each channel + */ + + /* + * Get the link stats for the channels + */ + status = send_and_receive(card, CEPID, ceReqTypeLnk, ceReqClass0, + ceReqLnkGetStats, 0, 0, NULL, &rcvmsg, SAR_TIMEOUT); + if (!status) { + bi->dch_stats.tx_good = (unsigned long)rcvmsg.msg_data.byte_array[0]; + bi->dch_stats.tx_bad = (unsigned long)rcvmsg.msg_data.byte_array[4]; + bi->dch_stats.rx_good = (unsigned long)rcvmsg.msg_data.byte_array[8]; + bi->dch_stats.rx_bad = (unsigned long)rcvmsg.msg_data.byte_array[12]; + bi->status.bristats[0].link_stats.tx_good = + (unsigned long)rcvmsg.msg_data.byte_array[16]; + bi->status.bristats[0].link_stats.tx_bad = + (unsigned long)rcvmsg.msg_data.byte_array[20]; + bi->status.bristats[0].link_stats.rx_good = + (unsigned long)rcvmsg.msg_data.byte_array[24]; + bi->status.bristats[0].link_stats.rx_bad = + (unsigned long)rcvmsg.msg_data.byte_array[28]; + bi->status.bristats[1].link_stats.tx_good = + (unsigned long)rcvmsg.msg_data.byte_array[32]; + bi->status.bristats[1].link_stats.tx_bad = + (unsigned long)rcvmsg.msg_data.byte_array[36]; + bi->status.bristats[1].link_stats.rx_good = + (unsigned long)rcvmsg.msg_data.byte_array[40]; + bi->status.bristats[1].link_stats.rx_bad = + (unsigned long)rcvmsg.msg_data.byte_array[44]; + } + + /* + * Get the SPIDs + */ + for (i = 0 ; i < BRI_CHANNELS ; i++) { + status = send_and_receive(card, CEPID, ceReqTypeCall, ceReqClass0, + ceReqCallGetSPID, i+1, 0, NULL, &rcvmsg, SAR_TIMEOUT); + if (!status) + strcpy(bi->status.bristats[i].spid, rcvmsg.msg_data.byte_array); + } + + /* + * Get the DNs + */ + for (i = 0 ; i < BRI_CHANNELS ; i++) { + status = send_and_receive(card, CEPID, ceReqTypeCall, ceReqClass0, + ceReqCallGetMyNumber, i+1, 0, NULL, &rcvmsg, SAR_TIMEOUT); + if (!status) + strcpy(bi->status.bristats[i].dn, rcvmsg.msg_data.byte_array); + } + + return 0; +} diff --git a/drivers/isdn/sc/message.c b/drivers/isdn/sc/message.c new file mode 100644 index 000000000000..ca204da3257d --- /dev/null +++ b/drivers/isdn/sc/message.c @@ -0,0 +1,241 @@ +/* $Id: message.c,v 1.5.8.2 2001/09/23 22:24:59 kai Exp $ + * + * functions for sending and receiving control messages + * + * Copyright (C) 1996 SpellCaster Telecommunications Inc. + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * For more information, please contact gpl-info@spellcast.com or write: + * + * SpellCaster Telecommunications Inc. + * 5621 Finch Avenue East, Unit #3 + * Scarborough, Ontario Canada + * M1B 2T9 + * +1 (416) 297-8565 + * +1 (416) 297-6433 Facsimile + */ + +#include "includes.h" +#include "hardware.h" +#include "message.h" +#include "card.h" + +extern board *sc_adapter[]; +extern unsigned int cinst; + +/* + * Obligatory function prototypes + */ +extern int indicate_status(int,ulong,char*); +extern int scm_command(isdn_ctrl *); + + +/* + * receive a message from the board + */ +int receivemessage(int card, RspMessage *rspmsg) +{ + DualPortMemory *dpm; + unsigned long flags; + + if (!IS_VALID_CARD(card)) { + pr_debug("Invalid param: %d is not a valid card id\n", card); + return -EINVAL; + } + + pr_debug("%s: Entered receivemessage\n", + sc_adapter[card]->devicename); + + /* + * See if there are messages waiting + */ + if (inb(sc_adapter[card]->ioport[FIFO_STATUS]) & RF_HAS_DATA) { + /* + * Map in the DPM to the base page and copy the message + */ + spin_lock_irqsave(&sc_adapter[card]->lock, flags); + outb((sc_adapter[card]->shmem_magic >> 14) | 0x80, + sc_adapter[card]->ioport[sc_adapter[card]->shmem_pgport]); + dpm = (DualPortMemory *) sc_adapter[card]->rambase; + memcpy_fromio(rspmsg, &(dpm->rsp_queue[dpm->rsp_tail]), + MSG_LEN); + dpm->rsp_tail = (dpm->rsp_tail+1) % MAX_MESSAGES; + inb(sc_adapter[card]->ioport[FIFO_READ]); + spin_unlock_irqrestore(&sc_adapter[card]->lock, flags); + /* + * Tell the board that the message is received + */ + pr_debug("%s: Received Message seq:%d pid:%d time:%d cmd:%d " + "cnt:%d (type,class,code):(%d,%d,%d) " + "link:%d stat:0x%x\n", + sc_adapter[card]->devicename, + rspmsg->sequence_no, + rspmsg->process_id, + rspmsg->time_stamp, + rspmsg->cmd_sequence_no, + rspmsg->msg_byte_cnt, + rspmsg->type, + rspmsg->class, + rspmsg->code, + rspmsg->phy_link_no, + rspmsg->rsp_status); + + return 0; + } + return -ENOMSG; +} + +/* + * send a message to the board + */ +int sendmessage(int card, + unsigned int procid, + unsigned int type, + unsigned int class, + unsigned int code, + unsigned int link, + unsigned int data_len, + unsigned int *data) +{ + DualPortMemory *dpm; + ReqMessage sndmsg; + unsigned long flags; + + if (!IS_VALID_CARD(card)) { + pr_debug("Invalid param: %d is not a valid card id\n", card); + return -EINVAL; + } + + /* + * Make sure we only send CEPID messages when the engine is up + * and CMPID messages when it is down + */ + if(sc_adapter[card]->EngineUp && procid == CMPID) { + pr_debug("%s: Attempt to send CM message with engine up\n", + sc_adapter[card]->devicename); + return -ESRCH; + } + + if(!sc_adapter[card]->EngineUp && procid == CEPID) { + pr_debug("%s: Attempt to send CE message with engine down\n", + sc_adapter[card]->devicename); + return -ESRCH; + } + + memset(&sndmsg, 0, MSG_LEN); + sndmsg.msg_byte_cnt = 4; + sndmsg.type = type; + sndmsg.class = class; + sndmsg.code = code; + sndmsg.phy_link_no = link; + + if (data_len > 0) { + if (data_len > MSG_DATA_LEN) + data_len = MSG_DATA_LEN; + memcpy(&(sndmsg.msg_data), data, data_len); + sndmsg.msg_byte_cnt = data_len + 8; + } + + sndmsg.process_id = procid; + sndmsg.sequence_no = sc_adapter[card]->seq_no++ % 256; + + /* + * wait for an empty slot in the queue + */ + while (!(inb(sc_adapter[card]->ioport[FIFO_STATUS]) & WF_NOT_FULL)) + udelay(1); + + /* + * Disable interrupts and map in shared memory + */ + spin_lock_irqsave(&sc_adapter[card]->lock, flags); + outb((sc_adapter[card]->shmem_magic >> 14) | 0x80, + sc_adapter[card]->ioport[sc_adapter[card]->shmem_pgport]); + dpm = (DualPortMemory *) sc_adapter[card]->rambase; /* Fix me */ + memcpy_toio(&(dpm->req_queue[dpm->req_head]),&sndmsg,MSG_LEN); + dpm->req_head = (dpm->req_head+1) % MAX_MESSAGES; + outb(sndmsg.sequence_no, sc_adapter[card]->ioport[FIFO_WRITE]); + spin_unlock_irqrestore(&sc_adapter[card]->lock, flags); + + pr_debug("%s: Sent Message seq:%d pid:%d time:%d " + "cnt:%d (type,class,code):(%d,%d,%d) " + "link:%d\n ", + sc_adapter[card]->devicename, + sndmsg.sequence_no, + sndmsg.process_id, + sndmsg.time_stamp, + sndmsg.msg_byte_cnt, + sndmsg.type, + sndmsg.class, + sndmsg.code, + sndmsg.phy_link_no); + + return 0; +} + +int send_and_receive(int card, + unsigned int procid, + unsigned char type, + unsigned char class, + unsigned char code, + unsigned char link, + unsigned char data_len, + unsigned char *data, + RspMessage *mesgdata, + int timeout) +{ + int retval; + int tries; + + if (!IS_VALID_CARD(card)) { + pr_debug("Invalid param: %d is not a valid card id\n", card); + return -EINVAL; + } + + sc_adapter[card]->want_async_messages = 1; + retval = sendmessage(card, procid, type, class, code, link, + data_len, (unsigned int *) data); + + if (retval) { + pr_debug("%s: SendMessage failed in SAR\n", + sc_adapter[card]->devicename); + sc_adapter[card]->want_async_messages = 0; + return -EIO; + } + + tries = 0; + /* wait for the response */ + while (tries < timeout) { + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(1); + + pr_debug("SAR waiting..\n"); + + /* + * See if we got our message back + */ + if ((sc_adapter[card]->async_msg.type == type) && + (sc_adapter[card]->async_msg.class == class) && + (sc_adapter[card]->async_msg.code == code) && + (sc_adapter[card]->async_msg.phy_link_no == link)) { + + /* + * Got it! + */ + pr_debug("%s: Got ASYNC message\n", + sc_adapter[card]->devicename); + memcpy(mesgdata, &(sc_adapter[card]->async_msg), + sizeof(RspMessage)); + sc_adapter[card]->want_async_messages = 0; + return 0; + } + + tries++; + } + + pr_debug("%s: SAR message timeout\n", sc_adapter[card]->devicename); + sc_adapter[card]->want_async_messages = 0; + return -ETIME; +} diff --git a/drivers/isdn/sc/message.h b/drivers/isdn/sc/message.h new file mode 100644 index 000000000000..8eb15e7306b2 --- /dev/null +++ b/drivers/isdn/sc/message.h @@ -0,0 +1,245 @@ +/* $Id: message.h,v 1.1.10.1 2001/09/23 22:24:59 kai Exp $ + * + * Copyright (C) 1996 SpellCaster Telecommunications Inc. + * + * structures, macros and defines useful for sending + * messages to the adapter + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * For more information, please contact gpl-info@spellcast.com or write: + * + * SpellCaster Telecommunications Inc. + * 5621 Finch Avenue East, Unit #3 + * Scarborough, Ontario Canada + * M1B 2T9 + * +1 (416) 297-8565 + * +1 (416) 297-6433 Facsimile + */ + +/* + * Board message macros, defines and structures + */ + +#ifndef MESSAGE_H +#define MESSAGE_H + +#define MAX_MESSAGES 32 /* Maximum messages that can be + queued */ +#define MSG_DATA_LEN 48 /* Maximum size of message payload */ +#define MSG_LEN 64 /* Size of a message */ +#define CMPID 0 /* Loader message process ID */ +#define CEPID 64 /* Firmware message process ID */ + +/* + * Macro to determine if a message is a loader message + */ +#define IS_CM_MESSAGE(mesg, tx, cx, dx) \ + ((mesg.type == cmRspType##tx) \ + &&(mesg.class == cmRspClass##cx) \ + &&(mesg.code == cmRsp##dx)) + +/* + * Macro to determine if a message is a firmware message + */ +#define IS_CE_MESSAGE(mesg, tx, cx, dx) \ + ((mesg.type == ceRspType##tx) \ + &&(mesg.class == ceRspClass##cx) \ + &&(mesg.code == ceRsp##tx##dx)) + +/* + * Loader Request and Response Messages + */ + +/* message types */ +#define cmReqType1 1 +#define cmReqType2 2 +#define cmRspType0 0 +#define cmRspType1 1 +#define cmRspType2 2 +#define cmRspType5 5 + +/* message classes */ +#define cmReqClass0 0 +#define cmRspClass0 0 + +/* message codes */ +#define cmReqHWConfig 1 /* 1,0,1 */ +#define cmReqMsgLpbk 2 /* 1,0,2 */ +#define cmReqVersion 3 /* 1,0,3 */ +#define cmReqLoadProc 1 /* 2,0,1 */ +#define cmReqStartProc 2 /* 2,0,2 */ +#define cmReqReadMem 6 /* 2,0,6 */ +#define cmRspHWConfig cmReqHWConfig +#define cmRspMsgLpbk cmReqMsgLpbk +#define cmRspVersion cmReqVersion +#define cmRspLoadProc cmReqLoadProc +#define cmRspStartProc cmReqStartProc +#define cmRspReadMem cmReqReadMem +#define cmRspMiscEngineUp 1 /* 5,0,1 */ +#define cmRspInvalid 0 /* 0,0,0 */ + + +/* + * Firmware Request and Response Messages + */ + +/* message types */ +#define ceReqTypePhy 1 +#define ceReqTypeLnk 2 +#define ceReqTypeCall 3 +#define ceReqTypeStat 1 +#define ceRspTypeErr 0 +#define ceRspTypePhy ceReqTypePhy +#define ceRspTypeLnk ceReqTypeLnk +#define ceRspTypeCall ceReqTypeCall +#define ceRspTypeStat ceReqTypeStat + +/* message classes */ +#define ceReqClass0 0 +#define ceReqClass1 1 +#define ceReqClass2 2 +#define ceReqClass3 3 +#define ceRspClass0 ceReqClass0 +#define ceRspClass1 ceReqClass1 +#define ceRspClass2 ceReqClass2 +#define ceRspClass3 ceReqClass3 + +/* message codes (B) = BRI only, (P) = PRI only, (V) = POTS only */ +#define ceReqPhyProcInfo 1 /* 1,0,1 */ +#define ceReqPhyConnect 1 /* 1,1,1 */ +#define ceReqPhyDisconnect 2 /* 1,1,2 */ +#define ceReqPhySetParams 3 /* 1,1,3 (P) */ +#define ceReqPhyGetParams 4 /* 1,1,4 (P) */ +#define ceReqPhyStatus 1 /* 1,2,1 */ +#define ceReqPhyAcfaStatus 2 /* 1,2,2 (P) */ +#define ceReqPhyChCallState 3 /* 1,2,3 (P) */ +#define ceReqPhyChServState 4 /* 1,2,4 (P) */ +#define ceReqPhyRLoopBack 1 /* 1,3,1 */ +#define ceRspPhyProcInfo ceReqPhyProcInfo +#define ceRspPhyConnect ceReqPhyConnect +#define ceRspPhyDisconnect ceReqPhyDisconnect +#define ceRspPhySetParams ceReqPhySetParams +#define ceRspPhyGetParams ceReqPhyGetParams +#define ceRspPhyStatus ceReqPhyStatus +#define ceRspPhyAcfaStatus ceReqPhyAcfaStatus +#define ceRspPhyChCallState ceReqPhyChCallState +#define ceRspPhyChServState ceReqPhyChServState +#define ceRspPhyRLoopBack ceReqphyRLoopBack +#define ceReqLnkSetParam 1 /* 2,0,1 */ +#define ceReqLnkGetParam 2 /* 2,0,2 */ +#define ceReqLnkGetStats 3 /* 2,0,3 */ +#define ceReqLnkWrite 1 /* 2,1,1 */ +#define ceReqLnkRead 2 /* 2,1,2 */ +#define ceReqLnkFlush 3 /* 2,1,3 */ +#define ceReqLnkWrBufTrc 4 /* 2,1,4 */ +#define ceReqLnkRdBufTrc 5 /* 2,1,5 */ +#define ceRspLnkSetParam ceReqLnkSetParam +#define ceRspLnkGetParam ceReqLnkGetParam +#define ceRspLnkGetStats ceReqLnkGetStats +#define ceRspLnkWrite ceReqLnkWrite +#define ceRspLnkRead ceReqLnkRead +#define ceRspLnkFlush ceReqLnkFlush +#define ceRspLnkWrBufTrc ceReqLnkWrBufTrc +#define ceRspLnkRdBufTrc ceReqLnkRdBufTrc +#define ceReqCallSetSwitchType 1 /* 3,0,1 */ +#define ceReqCallGetSwitchType 2 /* 3,0,2 */ +#define ceReqCallSetFrameFormat 3 /* 3,0,3 */ +#define ceReqCallGetFrameFormat 4 /* 3,0,4 */ +#define ceReqCallSetCallType 5 /* 3,0,5 */ +#define ceReqCallGetCallType 6 /* 3,0,6 */ +#define ceReqCallSetSPID 7 /* 3,0,7 (!P) */ +#define ceReqCallGetSPID 8 /* 3,0,8 (!P) */ +#define ceReqCallSetMyNumber 9 /* 3,0,9 (!P) */ +#define ceReqCallGetMyNumber 10 /* 3,0,10 (!P) */ +#define ceRspCallSetSwitchType ceReqCallSetSwitchType +#define ceRspCallGetSwitchType ceReqCallSetSwitchType +#define ceRspCallSetFrameFormat ceReqCallSetFrameFormat +#define ceRspCallGetFrameFormat ceReqCallGetFrameFormat +#define ceRspCallSetCallType ceReqCallSetCallType +#define ceRspCallGetCallType ceReqCallGetCallType +#define ceRspCallSetSPID ceReqCallSetSPID +#define ceRspCallGetSPID ceReqCallGetSPID +#define ceRspCallSetMyNumber ceReqCallSetMyNumber +#define ceRspCallGetMyNumber ceReqCallGetMyNumber +#define ceRspStatAcfaStatus 2 +#define ceRspStat +#define ceRspErrError 0 /* 0,0,0 */ + +/* + * Call Types + */ +#define CALLTYPE_64K 0 +#define CALLTYPE_56K 1 +#define CALLTYPE_SPEECH 2 +#define CALLTYPE_31KHZ 3 + +/* + * Link Level data contains a pointer to and the length of + * a buffer in shared RAM. Used by LnkRead and LnkWrite message + * types. Part of RspMsgStruct and ReqMsgStruct. + */ +typedef struct { + unsigned long buff_offset; + unsigned short msg_len; +} LLData; + + +/* + * Message payload template for an HWConfig message + */ +typedef struct { + char st_u_sense; + char powr_sense; + char sply_sense; + unsigned char asic_id; + long ram_size; + char serial_no[13]; + char part_no[13]; + char rev_no[2]; +} HWConfig_pl; + +/* + * A Message + */ +struct message { + unsigned char sequence_no; + unsigned char process_id; + unsigned char time_stamp; + unsigned char cmd_sequence_no; /* Rsp messages only */ + unsigned char reserved1[3]; + unsigned char msg_byte_cnt; + unsigned char type; + unsigned char class; + unsigned char code; + unsigned char phy_link_no; + unsigned char rsp_status; /* Rsp messages only */ + unsigned char reseved2[3]; + union { + unsigned char byte_array[MSG_DATA_LEN]; + LLData response; + HWConfig_pl HWCresponse; + } msg_data; +}; + +typedef struct message ReqMessage; /* Request message */ +typedef struct message RspMessage; /* Response message */ + +/* + * The first 5010 bytes of shared memory contain the message queues, + * indexes and other data. This structure is its template + */ +typedef struct { + volatile ReqMessage req_queue[MAX_MESSAGES]; + volatile RspMessage rsp_queue[MAX_MESSAGES]; + volatile unsigned char req_head; + volatile unsigned char req_tail; + volatile unsigned char rsp_head; + volatile unsigned char rsp_tail; + volatile unsigned long signature; + volatile unsigned long trace_enable; + volatile unsigned char reserved[4]; +} DualPortMemory; + +#endif diff --git a/drivers/isdn/sc/packet.c b/drivers/isdn/sc/packet.c new file mode 100644 index 000000000000..8e3fac3ba1a1 --- /dev/null +++ b/drivers/isdn/sc/packet.c @@ -0,0 +1,231 @@ +/* $Id: packet.c,v 1.5.8.1 2001/09/23 22:24:59 kai Exp $ + * + * Copyright (C) 1996 SpellCaster Telecommunications Inc. + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * For more information, please contact gpl-info@spellcast.com or write: + * + * SpellCaster Telecommunications Inc. + * 5621 Finch Avenue East, Unit #3 + * Scarborough, Ontario Canada + * M1B 2T9 + * +1 (416) 297-8565 + * +1 (416) 297-6433 Facsimile + */ + +#include "includes.h" +#include "hardware.h" +#include "message.h" +#include "card.h" + +extern board *sc_adapter[]; +extern unsigned int cinst; + +extern int get_card_from_id(int); +extern int indicate_status(int, int,ulong, char*); +extern void memcpy_toshmem(int, void *, const void *, size_t); +extern void memcpy_fromshmem(int, void *, const void *, size_t); +extern int sendmessage(int, unsigned int, unsigned int, unsigned int, + unsigned int, unsigned int, unsigned int, unsigned int *); + +int sndpkt(int devId, int channel, struct sk_buff *data) +{ + LLData ReqLnkWrite; + int status; + int card; + unsigned long len; + + card = get_card_from_id(devId); + + if(!IS_VALID_CARD(card)) { + pr_debug("invalid param: %d is not a valid card id\n", card); + return -ENODEV; + } + + pr_debug("%s: sndpkt: frst = 0x%x nxt = %d f = %d n = %d\n", + sc_adapter[card]->devicename, + sc_adapter[card]->channel[channel].first_sendbuf, + sc_adapter[card]->channel[channel].next_sendbuf, + sc_adapter[card]->channel[channel].free_sendbufs, + sc_adapter[card]->channel[channel].num_sendbufs); + + if(!sc_adapter[card]->channel[channel].free_sendbufs) { + pr_debug("%s: out of TX buffers\n", + sc_adapter[card]->devicename); + return -EINVAL; + } + + if(data->len > BUFFER_SIZE) { + pr_debug("%s: data overflows buffer size (data > buffer)\n", + sc_adapter[card]->devicename); + return -EINVAL; + } + + ReqLnkWrite.buff_offset = sc_adapter[card]->channel[channel].next_sendbuf * + BUFFER_SIZE + sc_adapter[card]->channel[channel].first_sendbuf; + ReqLnkWrite.msg_len = data->len; /* sk_buff size */ + pr_debug("%s: writing %d bytes to buffer offset 0x%x\n", + sc_adapter[card]->devicename, + ReqLnkWrite.msg_len, ReqLnkWrite.buff_offset); + memcpy_toshmem(card, (char *)ReqLnkWrite.buff_offset, data->data, ReqLnkWrite.msg_len); + + /* + * sendmessage + */ + pr_debug("%s: sndpkt size=%d, buf_offset=0x%x buf_indx=%d\n", + sc_adapter[card]->devicename, + ReqLnkWrite.msg_len, ReqLnkWrite.buff_offset, + sc_adapter[card]->channel[channel].next_sendbuf); + + status = sendmessage(card, CEPID, ceReqTypeLnk, ceReqClass1, ceReqLnkWrite, + channel+1, sizeof(LLData), (unsigned int*)&ReqLnkWrite); + len = data->len; + if(status) { + pr_debug("%s: failed to send packet, status = %d\n", + sc_adapter[card]->devicename, status); + return -1; + } + else { + sc_adapter[card]->channel[channel].free_sendbufs--; + sc_adapter[card]->channel[channel].next_sendbuf = + ++sc_adapter[card]->channel[channel].next_sendbuf == + sc_adapter[card]->channel[channel].num_sendbufs ? 0 : + sc_adapter[card]->channel[channel].next_sendbuf; + pr_debug("%s: packet sent successfully\n", sc_adapter[card]->devicename); + dev_kfree_skb(data); + indicate_status(card,ISDN_STAT_BSENT,channel, (char *)&len); + } + return len; +} + +void rcvpkt(int card, RspMessage *rcvmsg) +{ + LLData newll; + struct sk_buff *skb; + + if(!IS_VALID_CARD(card)) { + pr_debug("invalid param: %d is not a valid card id\n", card); + return; + } + + switch(rcvmsg->rsp_status){ + case 0x01: + case 0x02: + case 0x70: + pr_debug("%s: error status code: 0x%x\n", + sc_adapter[card]->devicename, rcvmsg->rsp_status); + return; + case 0x00: + if (!(skb = dev_alloc_skb(rcvmsg->msg_data.response.msg_len))) { + printk(KERN_WARNING "%s: rcvpkt out of memory, dropping packet\n", + sc_adapter[card]->devicename); + return; + } + skb_put(skb, rcvmsg->msg_data.response.msg_len); + pr_debug("%s: getting data from offset: 0x%x\n", + sc_adapter[card]->devicename, + rcvmsg->msg_data.response.buff_offset); + memcpy_fromshmem(card, + skb_put(skb, rcvmsg->msg_data.response.msg_len), + (char *)rcvmsg->msg_data.response.buff_offset, + rcvmsg->msg_data.response.msg_len); + sc_adapter[card]->card->rcvcallb_skb(sc_adapter[card]->driverId, + rcvmsg->phy_link_no-1, skb); + + case 0x03: + /* + * Recycle the buffer + */ + pr_debug("%s: buffer size : %d\n", + sc_adapter[card]->devicename, BUFFER_SIZE); +/* memset_shmem(card, rcvmsg->msg_data.response.buff_offset, 0, BUFFER_SIZE); */ + newll.buff_offset = rcvmsg->msg_data.response.buff_offset; + newll.msg_len = BUFFER_SIZE; + pr_debug("%s: recycled buffer at offset 0x%x size %d\n", + sc_adapter[card]->devicename, + newll.buff_offset, newll.msg_len); + sendmessage(card, CEPID, ceReqTypeLnk, ceReqClass1, ceReqLnkRead, + rcvmsg->phy_link_no, sizeof(LLData), (unsigned int *)&newll); + } + +} + +int setup_buffers(int card, int c) +{ + unsigned int nBuffers, i, cBase; + unsigned int buffer_size; + LLData RcvBuffOffset; + + if(!IS_VALID_CARD(card)) { + pr_debug("invalid param: %d is not a valid card id\n", card); + return -ENODEV; + } + + /* + * Calculate the buffer offsets (send/recv/send/recv) + */ + pr_debug("%s: setting up channel buffer space in shared RAM\n", + sc_adapter[card]->devicename); + buffer_size = BUFFER_SIZE; + nBuffers = ((sc_adapter[card]->ramsize - BUFFER_BASE) / buffer_size) / 2; + nBuffers = nBuffers > BUFFERS_MAX ? BUFFERS_MAX : nBuffers; + pr_debug("%s: calculating buffer space: %d buffers, %d big\n", + sc_adapter[card]->devicename, + nBuffers, buffer_size); + if(nBuffers < 2) { + pr_debug("%s: not enough buffer space\n", + sc_adapter[card]->devicename); + return -1; + } + cBase = (nBuffers * buffer_size) * (c - 1); + pr_debug("%s: channel buffer offset from shared RAM: 0x%x\n", + sc_adapter[card]->devicename, cBase); + sc_adapter[card]->channel[c-1].first_sendbuf = BUFFER_BASE + cBase; + sc_adapter[card]->channel[c-1].num_sendbufs = nBuffers / 2; + sc_adapter[card]->channel[c-1].free_sendbufs = nBuffers / 2; + sc_adapter[card]->channel[c-1].next_sendbuf = 0; + pr_debug("%s: send buffer setup complete: first=0x%x n=%d f=%d, nxt=%d\n", + sc_adapter[card]->devicename, + sc_adapter[card]->channel[c-1].first_sendbuf, + sc_adapter[card]->channel[c-1].num_sendbufs, + sc_adapter[card]->channel[c-1].free_sendbufs, + sc_adapter[card]->channel[c-1].next_sendbuf); + + /* + * Prep the receive buffers + */ + pr_debug("%s: adding %d RecvBuffers:\n", + sc_adapter[card]->devicename, nBuffers /2); + for (i = 0 ; i < nBuffers / 2; i++) { + RcvBuffOffset.buff_offset = + ((sc_adapter[card]->channel[c-1].first_sendbuf + + (nBuffers / 2) * buffer_size) + (buffer_size * i)); + RcvBuffOffset.msg_len = buffer_size; + pr_debug("%s: adding RcvBuffer #%d offset=0x%x sz=%d bufsz:%d\n", + sc_adapter[card]->devicename, + i + 1, RcvBuffOffset.buff_offset, + RcvBuffOffset.msg_len,buffer_size); + sendmessage(card, CEPID, ceReqTypeLnk, ceReqClass1, ceReqLnkRead, + c, sizeof(LLData), (unsigned int *)&RcvBuffOffset); + } + return 0; +} + +int print_skb(int card,char *skb_p, int len){ + int i,data; + pr_debug("%s: data at 0x%x len: 0x%x\n", sc_adapter[card]->devicename, + skb_p,len); + for(i=1;i<=len;i++,skb_p++){ + data = (int) (0xff & (*skb_p)); + pr_debug("%s: data = 0x%x", sc_adapter[card]->devicename,data); + if(!(i%4)) + pr_debug(" "); + if(!(i%32)) + pr_debug("\n"); + } + pr_debug("\n"); + return 0; +} + diff --git a/drivers/isdn/sc/scioc.h b/drivers/isdn/sc/scioc.h new file mode 100644 index 000000000000..d08e650c7b6a --- /dev/null +++ b/drivers/isdn/sc/scioc.h @@ -0,0 +1,105 @@ +/* + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + */ + +/* + * IOCTL Command Codes + */ +#define SCIOCLOAD 0x01 /* Load a firmware record */ +#define SCIOCRESET 0x02 /* Perform hard reset */ +#define SCIOCDEBUG 0x03 /* Set debug level */ +#define SCIOCREV 0x04 /* Get driver revision(s) */ +#define SCIOCSTART 0x05 /* Start the firmware */ +#define SCIOCGETSWITCH 0x06 /* Get switch type */ +#define SCIOCSETSWITCH 0x07 /* Set switch type */ +#define SCIOCGETSPID 0x08 /* Get channel SPID */ +#define SCIOCSETSPID 0x09 /* Set channel SPID */ +#define SCIOCGETDN 0x0A /* Get channel DN */ +#define SCIOCSETDN 0x0B /* Set channel DN */ +#define SCIOCTRACE 0x0C /* Toggle trace mode */ +#define SCIOCSTAT 0x0D /* Get line status */ +#define SCIOCGETSPEED 0x0E /* Set channel speed */ +#define SCIOCSETSPEED 0x0F /* Set channel speed */ +#define SCIOCLOOPTST 0x10 /* Perform loopback test */ + +typedef struct { + int device; + int channel; + unsigned long command; + void __user *dataptr; +} scs_ioctl; + +/* Size of strings */ +#define SCIOC_SPIDSIZE 49 +#define SCIOC_DNSIZE SCIOC_SPIDSIZE +#define SCIOC_REVSIZE SCIOC_SPIDSIZE +#define SCIOC_SRECSIZE 49 + +typedef struct { + unsigned long tx_good; + unsigned long tx_bad; + unsigned long rx_good; + unsigned long rx_bad; +} ChLinkStats; + +typedef struct { + char spid[49]; + char dn[49]; + char call_type; + char phy_stat; + ChLinkStats link_stats; +} BRIStat; + +typedef BRIStat POTStat; + +typedef struct { + char call_type; + char call_state; + char serv_state; + char phy_stat; + ChLinkStats link_stats; +} PRIStat; + +typedef char PRIInfo; +typedef char BRIInfo; +typedef char POTInfo; + + +typedef struct { + char acfa_nos; + char acfa_ais; + char acfa_los; + char acfa_rra; + char acfa_slpp; + char acfa_slpn; + char acfa_fsrf; +} ACFAStat; + +typedef struct { + unsigned char modelid; + char serial_no[13]; + char part_no[13]; + char load_ver[11]; + char proc_ver[11]; + int iobase; + long rambase; + char irq; + long ramsize; + char interface; + char switch_type; + char l1_status; + char l2_status; + ChLinkStats dch_stats; + ACFAStat AcfaStats; + union { + PRIStat pristats[23]; + BRIStat bristats[2]; + POTStat potsstats[2]; + } status; + union { + PRIInfo priinfo; + BRIInfo briinfo; + POTInfo potsinfo; + } info; +} boardInfo; diff --git a/drivers/isdn/sc/shmem.c b/drivers/isdn/sc/shmem.c new file mode 100644 index 000000000000..7bc2dfad0775 --- /dev/null +++ b/drivers/isdn/sc/shmem.c @@ -0,0 +1,143 @@ +/* $Id: shmem.c,v 1.2.10.1 2001/09/23 22:24:59 kai Exp $ + * + * Copyright (C) 1996 SpellCaster Telecommunications Inc. + * + * Card functions implementing ISDN4Linux functionality + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * For more information, please contact gpl-info@spellcast.com or write: + * + * SpellCaster Telecommunications Inc. + * 5621 Finch Avenue East, Unit #3 + * Scarborough, Ontario Canada + * M1B 2T9 + * +1 (416) 297-8565 + * +1 (416) 297-6433 Facsimile + */ + +#include "includes.h" /* This must be first */ +#include "hardware.h" +#include "card.h" + +/* + * Main adapter array + */ +extern board *sc_adapter[]; +extern int cinst; + +/* + * + */ +void memcpy_toshmem(int card, void *dest, const void *src, size_t n) +{ + unsigned long flags; + unsigned char ch; + + if(!IS_VALID_CARD(card)) { + pr_debug("Invalid param: %d is not a valid card id\n", card); + return; + } + + if(n > SRAM_PAGESIZE) { + return; + } + + /* + * determine the page to load from the address + */ + ch = (unsigned long) dest / SRAM_PAGESIZE; + pr_debug("%s: loaded page %d\n", sc_adapter[card]->devicename,ch); + /* + * Block interrupts and load the page + */ + spin_lock_irqsave(&sc_adapter[card]->lock, flags); + + outb(((sc_adapter[card]->shmem_magic + ch * SRAM_PAGESIZE) >> 14) | 0x80, + sc_adapter[card]->ioport[sc_adapter[card]->shmem_pgport]); + memcpy_toio(sc_adapter[card]->rambase + + ((unsigned long) dest % 0x4000), src, n); + spin_unlock_irqrestore(&sc_adapter[card]->lock, flags); + pr_debug("%s: set page to %#x\n",sc_adapter[card]->devicename, + ((sc_adapter[card]->shmem_magic + ch * SRAM_PAGESIZE)>>14)|0x80); + pr_debug("%s: copying %d bytes from %#x to %#x\n", + sc_adapter[card]->devicename, n, + (unsigned long) src, + sc_adapter[card]->rambase + ((unsigned long) dest %0x4000)); +} + +/* + * Reverse of above + */ +void memcpy_fromshmem(int card, void *dest, const void *src, size_t n) +{ + unsigned long flags; + unsigned char ch; + + if(!IS_VALID_CARD(card)) { + pr_debug("Invalid param: %d is not a valid card id\n", card); + return; + } + + if(n > SRAM_PAGESIZE) { + return; + } + + /* + * determine the page to load from the address + */ + ch = (unsigned long) src / SRAM_PAGESIZE; + pr_debug("%s: loaded page %d\n", sc_adapter[card]->devicename,ch); + + + /* + * Block interrupts and load the page + */ + spin_lock_irqsave(&sc_adapter[card]->lock, flags); + + outb(((sc_adapter[card]->shmem_magic + ch * SRAM_PAGESIZE) >> 14) | 0x80, + sc_adapter[card]->ioport[sc_adapter[card]->shmem_pgport]); + memcpy_fromio(dest,(void *)(sc_adapter[card]->rambase + + ((unsigned long) src % 0x4000)), n); + spin_unlock_irqrestore(&sc_adapter[card]->lock, flags); + pr_debug("%s: set page to %#x\n",sc_adapter[card]->devicename, + ((sc_adapter[card]->shmem_magic + ch * SRAM_PAGESIZE)>>14)|0x80); +/* pr_debug("%s: copying %d bytes from %#x to %#x\n", + sc_adapter[card]->devicename, n, + sc_adapter[card]->rambase + ((unsigned long) src %0x4000), (unsigned long) dest); */ +} + +void memset_shmem(int card, void *dest, int c, size_t n) +{ + unsigned long flags; + unsigned char ch; + + if(!IS_VALID_CARD(card)) { + pr_debug("Invalid param: %d is not a valid card id\n", card); + return; + } + + if(n > SRAM_PAGESIZE) { + return; + } + + /* + * determine the page to load from the address + */ + ch = (unsigned long) dest / SRAM_PAGESIZE; + pr_debug("%s: loaded page %d\n",sc_adapter[card]->devicename,ch); + + /* + * Block interrupts and load the page + */ + spin_lock_irqsave(&sc_adapter[card]->lock, flags); + + outb(((sc_adapter[card]->shmem_magic + ch * SRAM_PAGESIZE) >> 14) | 0x80, + sc_adapter[card]->ioport[sc_adapter[card]->shmem_pgport]); + memset_io(sc_adapter[card]->rambase + + ((unsigned long) dest % 0x4000), c, n); + pr_debug("%s: set page to %#x\n",sc_adapter[card]->devicename, + ((sc_adapter[card]->shmem_magic + ch * SRAM_PAGESIZE)>>14)|0x80); + spin_unlock_irqrestore(&sc_adapter[card]->lock, flags); +} diff --git a/drivers/isdn/sc/timer.c b/drivers/isdn/sc/timer.c new file mode 100644 index 000000000000..710d0f47ca35 --- /dev/null +++ b/drivers/isdn/sc/timer.c @@ -0,0 +1,147 @@ +/* $Id: timer.c,v 1.3.6.1 2001/09/23 22:24:59 kai Exp $ + * + * Copyright (C) 1996 SpellCaster Telecommunications Inc. + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * For more information, please contact gpl-info@spellcast.com or write: + * + * SpellCaster Telecommunications Inc. + * 5621 Finch Avenue East, Unit #3 + * Scarborough, Ontario Canada + * M1B 2T9 + * +1 (416) 297-8565 + * +1 (416) 297-6433 Facsimile + */ + +#include "includes.h" +#include "hardware.h" +#include "message.h" +#include "card.h" + +extern board *sc_adapter[]; + +extern void flushreadfifo(int); +extern int startproc(int); +extern int indicate_status(int, int, unsigned long, char *); +extern int sendmessage(int, unsigned int, unsigned int, unsigned int, + unsigned int, unsigned int, unsigned int, unsigned int *); + + +/* + * Write the proper values into the I/O ports following a reset + */ +void setup_ports(int card) +{ + + outb((sc_adapter[card]->rambase >> 12), sc_adapter[card]->ioport[EXP_BASE]); + + /* And the IRQ */ + outb((sc_adapter[card]->interrupt | 0x80), + sc_adapter[card]->ioport[IRQ_SELECT]); +} + +/* + * Timed function to check the status of a previous reset + * Must be very fast as this function runs in the context of + * an interrupt handler. + * + * Setup the ioports for the board that were cleared by the reset. + * Then, check to see if the signate has been set. Next, set the + * signature to a known value and issue a startproc if needed. + */ +void check_reset(unsigned long data) +{ + unsigned long flags; + unsigned long sig; + int card = (unsigned int) data; + + pr_debug("%s: check_timer timer called\n", + sc_adapter[card]->devicename); + + /* Setup the io ports */ + setup_ports(card); + + spin_lock_irqsave(&sc_adapter[card]->lock, flags); + outb(sc_adapter[card]->ioport[sc_adapter[card]->shmem_pgport], + (sc_adapter[card]->shmem_magic>>14) | 0x80); + sig = (unsigned long) *((unsigned long *)(sc_adapter[card]->rambase + SIG_OFFSET)); + + /* check the signature */ + if(sig == SIGNATURE) { + flushreadfifo(card); + spin_unlock_irqrestore(&sc_adapter[card]->lock, flags); + /* See if we need to do a startproc */ + if (sc_adapter[card]->StartOnReset) + startproc(card); + } else { + pr_debug("%s: No signature yet, waiting another %d jiffies.\n", + sc_adapter[card]->devicename, CHECKRESET_TIME); + mod_timer(&sc_adapter[card]->reset_timer, jiffies+CHECKRESET_TIME); + spin_unlock_irqrestore(&sc_adapter[card]->lock, flags); + } +} + +/* + * Timed function to check the status of a previous reset + * Must be very fast as this function runs in the context of + * an interrupt handler. + * + * Send check sc_adapter->phystat to see if the channels are up + * If they are, tell ISDN4Linux that the board is up. If not, + * tell IADN4Linux that it is up. Always reset the timer to + * fire again (endless loop). + */ +void check_phystat(unsigned long data) +{ + unsigned long flags; + int card = (unsigned int) data; + + pr_debug("%s: Checking status...\n", sc_adapter[card]->devicename); + /* + * check the results of the last PhyStat and change only if + * has changed drastically + */ + if (sc_adapter[card]->nphystat && !sc_adapter[card]->phystat) { /* All is well */ + pr_debug("PhyStat transition to RUN\n"); + pr_info("%s: Switch contacted, transmitter enabled\n", + sc_adapter[card]->devicename); + indicate_status(card, ISDN_STAT_RUN, 0, NULL); + } + else if (!sc_adapter[card]->nphystat && sc_adapter[card]->phystat) { /* All is not well */ + pr_debug("PhyStat transition to STOP\n"); + pr_info("%s: Switch connection lost, transmitter disabled\n", + sc_adapter[card]->devicename); + + indicate_status(card, ISDN_STAT_STOP, 0, NULL); + } + + sc_adapter[card]->phystat = sc_adapter[card]->nphystat; + + /* Reinitialize the timer */ + spin_lock_irqsave(&sc_adapter[card]->lock, flags); + mod_timer(&sc_adapter[card]->stat_timer, jiffies+CHECKSTAT_TIME); + spin_unlock_irqrestore(&sc_adapter[card]->lock, flags); + + /* Send a new cePhyStatus message */ + sendmessage(card, CEPID,ceReqTypePhy,ceReqClass2, + ceReqPhyStatus,0,0,NULL); +} + +/* + * When in trace mode, this callback is used to swap the working shared + * RAM page to the trace page(s) and process all received messages. It + * must be called often enough to get all of the messages out of RAM before + * it loops around. + * Trace messages are \n terminated strings. + * We output the messages in 64 byte chunks through readstat. Each chunk + * is scanned for a \n followed by a time stamp. If the timerstamp is older + * than the current time, scanning stops and the page and offset are recorded + * as the starting point the next time the trace timer is called. The final + * step is to restore the working page and reset the timer. + */ +void trace_timer(unsigned long data) +{ + /* not implemented */ +} |