summaryrefslogtreecommitdiffstats
path: root/drivers/isdn/act2000
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
commit1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch)
tree0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/isdn/act2000
downloadlinux-stable-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.tar.gz
linux-stable-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.tar.bz2
linux-stable-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.zip
Linux-2.6.12-rc2v2.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/act2000')
-rw-r--r--drivers/isdn/act2000/Kconfig13
-rw-r--r--drivers/isdn/act2000/Makefile9
-rw-r--r--drivers/isdn/act2000/act2000.h202
-rw-r--r--drivers/isdn/act2000/act2000_isa.c449
-rw-r--r--drivers/isdn/act2000/act2000_isa.h136
-rw-r--r--drivers/isdn/act2000/capi.c1177
-rw-r--r--drivers/isdn/act2000/capi.h366
-rw-r--r--drivers/isdn/act2000/module.c808
8 files changed, 3160 insertions, 0 deletions
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);