diff options
Diffstat (limited to 'drivers/staging')
45 files changed, 22774 insertions, 0 deletions
diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig index d5f771fafc21..7c96a01eef6c 100644 --- a/drivers/staging/Kconfig +++ b/drivers/staging/Kconfig @@ -118,4 +118,6 @@ source "drivers/staging/fieldbus/Kconfig" source "drivers/staging/kpc2000/Kconfig" +source "drivers/staging/isdn/Kconfig" + endif # STAGING diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile index 0da0d3f0b5e4..fcaac9693b83 100644 --- a/drivers/staging/Makefile +++ b/drivers/staging/Makefile @@ -49,3 +49,4 @@ obj-$(CONFIG_XIL_AXIS_FIFO) += axis-fifo/ obj-$(CONFIG_EROFS_FS) += erofs/ obj-$(CONFIG_FIELDBUS_DEV) += fieldbus/ obj-$(CONFIG_KPC2000) += kpc2000/ +obj-$(CONFIG_ISDN_CAPI) += isdn/ diff --git a/drivers/staging/isdn/Kconfig b/drivers/staging/isdn/Kconfig new file mode 100644 index 000000000000..faaf63887094 --- /dev/null +++ b/drivers/staging/isdn/Kconfig @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0-only +menu "ISDN CAPI drivers" + depends on ISDN_CAPI + +source "drivers/staging/isdn/avm/Kconfig" + +source "drivers/staging/isdn/gigaset/Kconfig" + +source "drivers/staging/isdn/hysdn/Kconfig" + +endmenu + diff --git a/drivers/staging/isdn/Makefile b/drivers/staging/isdn/Makefile new file mode 100644 index 000000000000..025504bae5df --- /dev/null +++ b/drivers/staging/isdn/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 +# Makefile for the kernel ISDN subsystem and device drivers. + +# Object files in subdirectories + +obj-$(CONFIG_CAPI_AVM) += avm/ +obj-$(CONFIG_HYSDN) += hysdn/ +obj-$(CONFIG_ISDN_DRV_GIGASET) += gigaset/ diff --git a/drivers/staging/isdn/TODO b/drivers/staging/isdn/TODO new file mode 100644 index 000000000000..9210d11eb68b --- /dev/null +++ b/drivers/staging/isdn/TODO @@ -0,0 +1,22 @@ +TODO: Remove in late 2019 unless there are users + + +I tried to find any indication of whether the capi drivers are +still in use, and have not found anything from a long time ago. + +With public ISDN networks almost completely shut down over the past 12 +months, there is very little you can actually do with this hardware. The +main remaining use case would be to connect ISDN voice phones to an +in-house installation with Asterisk or LCR, but anyone trying this in +turn seems to be using either the mISDN driver stack, or out-of-tree +drivers from the hardware vendors. + +I may of course have missed something, so I would suggest moving +these into drivers/staging/ just in case someone still uses one +of the three remaining in-kernel drivers (avm, hysdn, gigaset). + +If nobody complains, we can remove them entirely in six months, +or otherwise move the core code and any drivers that are still +needed back into drivers/isdn. + + Arnd Bergmann <arnd@arndb.de> diff --git a/drivers/staging/isdn/avm/Kconfig b/drivers/staging/isdn/avm/Kconfig new file mode 100644 index 000000000000..81483db067bb --- /dev/null +++ b/drivers/staging/isdn/avm/Kconfig @@ -0,0 +1,65 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# ISDN AVM drivers +# + +menuconfig CAPI_AVM + bool "Active AVM cards" + help + Enable support for AVM active ISDN cards. + +if CAPI_AVM + +config ISDN_DRV_AVMB1_B1ISA + tristate "AVM B1 ISA support" + depends on 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 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 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 PCMCIA + 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 + 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 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 PCI + help + Enable support for the AVM C4/C2 PCI cards. + These cards handle 4/2 BRI ISDN lines (8/4 channels). + +endif # CAPI_AVM diff --git a/drivers/staging/isdn/avm/Makefile b/drivers/staging/isdn/avm/Makefile new file mode 100644 index 000000000000..3830a0573fcc --- /dev/null +++ b/drivers/staging/isdn/avm/Makefile @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0 +# 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/staging/isdn/avm/avm_cs.c b/drivers/staging/isdn/avm/avm_cs.c new file mode 100644 index 000000000000..62b8030ee331 --- /dev/null +++ b/drivers/staging/isdn/avm/avm_cs.c @@ -0,0 +1,166 @@ +/* $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/ptrace.h> +#include <linux/string.h> +#include <linux/tty.h> +#include <linux/serial.h> +#include <linux/major.h> +#include <asm/io.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"); + +/*====================================================================*/ + +static int avmcs_config(struct pcmcia_device *link); +static void avmcs_release(struct pcmcia_device *link); +static void avmcs_detach(struct pcmcia_device *p_dev); + +static int avmcs_probe(struct pcmcia_device *p_dev) +{ + /* General socket configuration */ + p_dev->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO; + p_dev->config_index = 1; + p_dev->config_regs = PRESENT_OPTION; + + return avmcs_config(p_dev); +} /* avmcs_attach */ + + +static void avmcs_detach(struct pcmcia_device *link) +{ + avmcs_release(link); +} /* avmcs_detach */ + +static int avmcs_configcheck(struct pcmcia_device *p_dev, void *priv_data) +{ + p_dev->resource[0]->end = 16; + p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH; + p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_8; + + return pcmcia_request_io(p_dev); +} + +static int avmcs_config(struct pcmcia_device *link) +{ + int i = -1; + char devname[128]; + int cardtype; + int (*addcard)(unsigned int port, unsigned irq); + + devname[0] = 0; + if (link->prod_id[1]) + strlcpy(devname, link->prod_id[1], sizeof(devname)); + + /* + * find IO port + */ + if (pcmcia_loop_config(link, avmcs_configcheck, NULL)) + return -ENODEV; + + do { + if (!link->irq) { + /* undo */ + pcmcia_disable_device(link); + break; + } + + /* + * configure the PCMCIA socket + */ + i = pcmcia_enable_device(link); + if (i != 0) { + pcmcia_disable_device(link); + break; + } + + } while (0); + + if (devname[0]) { + char *s = strrchr(devname, ' '); + if (!s) + s = devname; + else 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 + cardtype = AVM_CARDTYPE_B1; + + /* If any step failed, release any partially configured state */ + if (i != 0) { + avmcs_release(link); + return -ENODEV; + } + + + 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->resource[0]->start, link->irq)) < 0) { + dev_err(&link->dev, + "avm_cs: failed to add AVM-Controller at i/o %#x, irq %d\n", + (unsigned int) link->resource[0]->start, link->irq); + avmcs_release(link); + return -ENODEV; + } + return 0; + +} /* avmcs_config */ + + +static void avmcs_release(struct pcmcia_device *link) +{ + b1pcmcia_delcard(link->resource[0]->start, link->irq); + pcmcia_disable_device(link); +} /* avmcs_release */ + + +static const struct pcmcia_device_id avmcs_ids[] = { + PCMCIA_DEVICE_PROD_ID12("AVM", "ISDN-Controller B1", 0x95d42008, 0x845dc335), + PCMCIA_DEVICE_PROD_ID12("AVM", "Mobile ISDN-Controller M1", 0x95d42008, 0x81e10430), + PCMCIA_DEVICE_PROD_ID12("AVM", "Mobile ISDN-Controller M2", 0x95d42008, 0x18e8558a), + PCMCIA_DEVICE_NULL +}; +MODULE_DEVICE_TABLE(pcmcia, avmcs_ids); + +static struct pcmcia_driver avmcs_driver = { + .owner = THIS_MODULE, + .name = "avm_cs", + .probe = avmcs_probe, + .remove = avmcs_detach, + .id_table = avmcs_ids, +}; +module_pcmcia_driver(avmcs_driver); diff --git a/drivers/staging/isdn/avm/avmcard.h b/drivers/staging/isdn/avm/avmcard.h new file mode 100644 index 000000000000..cdfa89c71997 --- /dev/null +++ b/drivers/staging/isdn/avm/avmcard.h @@ -0,0 +1,581 @@ +/* $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; + break; + 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); + +int b1_proc_show(struct seq_file *m, void *v); + +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); + +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 b1dma_proc_show(struct seq_file *m, void *v); + +#endif /* _AVMCARD_H_ */ diff --git a/drivers/staging/isdn/avm/b1.c b/drivers/staging/isdn/avm/b1.c new file mode 100644 index 000000000000..40ca1e8fa09f --- /dev/null +++ b/drivers/staging/isdn/avm/b1.c @@ -0,0 +1,804 @@ +/* $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/proc_fs.h> +#include <linux/seq_file.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 <linux/slab.h> +#include <asm/io.h> +#include <linux/init.h> +#include <linux/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 = kzalloc(sizeof(*card), GFP_KERNEL); + if (!card) + return NULL; + + cinfo = kcalloc(nr_controllers, sizeof(*cinfo), GFP_KERNEL); + if (!cinfo) { + kfree(card); + return NULL; + } + + 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; + unsigned long flags; + + b1_reset(port); + b1_reset(port); + + memset(cinfo->version, 0, sizeof(cinfo->version)); + spin_lock_irqsave(&card->lock, flags); + capilib_release(&cinfo->ncci_head); + spin_unlock_irqrestore(&card->lock, flags); + capi_ctr_down(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; + + spin_lock_irqsave(&card->lock, flags); + capilib_release_appl(&cinfo->ncci_head, appl); + 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; + + spin_lock_irqsave(&card->lock, flags); + 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) { + spin_unlock_irqrestore(&card->lock, flags); + return retval; + } + + dlen = CAPIMSG_DATALEN(skb->data); + + b1_put_byte(port, SEND_DATA_B3_REQ); + b1_put_slice(port, skb->data, len); + b1_put_slice(port, skb->data + len, dlen); + } else { + 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] = ""; + 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) +{ + 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 { + skb_put_data(skb, card->msgbuf, MsgLen); + skb_put_data(skb, 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); + if (!(skb = alloc_skb(MsgLen, GFP_ATOMIC))) { + printk(KERN_ERR "%s: incoming packet dropped\n", + card->name); + spin_unlock_irqrestore(&card->lock, flags); + } else { + skb_put_data(skb, 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)); + spin_unlock_irqrestore(&card->lock, flags); + 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); + capilib_new_ncci(&cinfo->ncci_head, ApplId, NCCI, WindowSize); + spin_unlock_irqrestore(&card->lock, flags); + break; + + case RECEIVE_FREE_NCCI: + + ApplId = b1_get_word(card->port); + NCCI = b1_get_word(card->port); + if (NCCI != 0xffffffff) + capilib_free_ncci(&cinfo->ncci_head, ApplId, NCCI); + spin_unlock_irqrestore(&card->lock, flags); + 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 b1_proc_show(struct seq_file *m, void *v) +{ + struct capi_ctr *ctrl = m->private; + avmctrl_info *cinfo = (avmctrl_info *)(ctrl->driverdata); + avmcard *card = cinfo->card; + u8 flag; + char *s; + + seq_printf(m, "%-16s %s\n", "name", card->name); + seq_printf(m, "%-16s 0x%x\n", "io", card->port); + seq_printf(m, "%-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; + } + seq_printf(m, "%-16s %s\n", "type", s); + if (card->cardtype == avm_t1isa) + seq_printf(m, "%-16s %d\n", "cardnr", card->cardnr); + if ((s = cinfo->version[VER_DRIVER]) != NULL) + seq_printf(m, "%-16s %s\n", "ver_driver", s); + if ((s = cinfo->version[VER_CARDTYPE]) != NULL) + seq_printf(m, "%-16s %s\n", "ver_cardtype", s); + if ((s = cinfo->version[VER_SERIAL]) != NULL) + seq_printf(m, "%-16s %s\n", "ver_serial", s); + + if (card->cardtype != avm_m1) { + flag = ((u8 *)(ctrl->profile.manu))[3]; + if (flag) + seq_printf(m, "%-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) + seq_printf(m, "%-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" : "" + ); + } + seq_printf(m, "%-16s %s\n", "cardname", cinfo->cardname); + + return 0; +} +EXPORT_SYMBOL(b1_proc_show); + +/* ------------------------------------------------------------- */ + +#ifdef CONFIG_PCI + +avmcard_dmainfo * +avmcard_dma_alloc(char *name, struct pci_dev *pdev, long rsize, long ssize) +{ + avmcard_dmainfo *p; + void *buf; + + p = kzalloc(sizeof(avmcard_dmainfo), GFP_KERNEL); + if (!p) { + printk(KERN_WARNING "%s: no memory.\n", name); + goto err; + } + + 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); + +static int __init b1_init(void) +{ + char *p; + char rev[32]; + + if ((p = strchr(revision, ':')) != NULL && p[1]) { + strlcpy(rev, p + 2, 32); + if ((p = strchr(rev, '$')) != NULL && 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/staging/isdn/avm/b1dma.c b/drivers/staging/isdn/avm/b1dma.c new file mode 100644 index 000000000000..6a3dc9937ce5 --- /dev/null +++ b/drivers/staging/isdn/avm/b1dma.c @@ -0,0 +1,981 @@ +/* $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/module.h> +#include <linux/kernel.h> +#include <linux/proc_fs.h> +#include <linux/seq_file.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 <linux/gfp.h> +#include <asm/io.h> +#include <linux/init.h> +#include <linux/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 AVM_B1DMA_DEBUG + +/* ------------------------------------------------------------- */ + +MODULE_DESCRIPTION("CAPI4Linux: DMA support for active AVM cards"); +MODULE_AUTHOR("Carsten Paeth"); +MODULE_LICENSE("GPL"); + +static bool suppress_pollack = 0; +module_param(suppress_pollack, bool, 0); + +/* ------------------------------------------------------------- */ + +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 AVM_B1DMA_DEBUG + printk(KERN_DEBUG "tx: put msg len=%d\n", txlen); +#endif + } else { + txlen = skb->len - 2; +#ifdef AVM_B1DMA_POLLDEBUG + if (skb->data[2] == SEND_POLLACK) + printk(KERN_INFO "%s: send ack\n", card->name); +#endif +#ifdef AVM_B1DMA_DEBUG + printk(KERN_DEBUG "tx: put 0x%x len=%d\n", + skb->data[2], txlen); +#endif + skb_copy_from_linear_data_offset(skb, 2, dma->sendbuf.dmabuf, + 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 AVM_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 { + skb_put_data(skb, card->msgbuf, MsgLen); + skb_put_data(skb, 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 { + skb_put_data(skb, card->msgbuf, MsgLen); + if (CAPIMSG_CMD(skb->data) == CAPI_DATA_B3_CONF) { + spin_lock(&card->lock); + capilib_data_b3_conf(&cinfo->ncci_head, ApplId, + CAPIMSG_NCCI(skb->data), + CAPIMSG_MSGID(skb->data)); + spin_unlock(&card->lock); + } + capi_ctr_handle_message(ctrl, ApplId, skb); + } + break; + + case RECEIVE_NEW_NCCI: + + ApplId = _get_word(&p); + NCCI = _get_word(&p); + WindowSize = _get_word(&p); + spin_lock(&card->lock); + capilib_new_ncci(&cinfo->ncci_head, ApplId, NCCI, WindowSize); + spin_unlock(&card->lock); + break; + + case RECEIVE_FREE_NCCI: + + ApplId = _get_word(&p); + NCCI = _get_word(&p); + + if (NCCI != 0xffffffff) { + spin_lock(&card->lock); + capilib_free_ncci(&cinfo->ncci_head, ApplId, NCCI); + spin_unlock(&card->lock); + } + break; + + case RECEIVE_START: +#ifdef AVM_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 AVM_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) +{ + 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); + + memset(cinfo->version, 0, sizeof(cinfo->version)); + capilib_release(&cinfo->ncci_head); + spin_unlock_irqrestore(&card->lock, flags); + capi_ctr_down(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; + unsigned long flags; + + spin_lock_irqsave(&card->lock, flags); + capilib_release_appl(&cinfo->ncci_head, appl); + spin_unlock_irqrestore(&card->lock, flags); + + 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) { + unsigned long flags; + spin_lock_irqsave(&card->lock, flags); + retval = capilib_data_b3_req(&cinfo->ncci_head, + CAPIMSG_APPID(skb->data), + CAPIMSG_NCCI(skb->data), + CAPIMSG_MSGID(skb->data)); + spin_unlock_irqrestore(&card->lock, flags); + } + if (retval == CAPI_NOERROR) + b1dma_queue_tx(card, skb); + + return retval; +} + +/* ------------------------------------------------------------- */ + +int b1dma_proc_show(struct seq_file *m, void *v) +{ + struct capi_ctr *ctrl = m->private; + avmctrl_info *cinfo = (avmctrl_info *)(ctrl->driverdata); + avmcard *card = cinfo->card; + u8 flag; + char *s; + u32 txoff, txlen, rxoff, rxlen, csr; + unsigned long flags; + + seq_printf(m, "%-16s %s\n", "name", card->name); + seq_printf(m, "%-16s 0x%x\n", "io", card->port); + seq_printf(m, "%-16s %d\n", "irq", card->irq); + seq_printf(m, "%-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; + } + seq_printf(m, "%-16s %s\n", "type", s); + if ((s = cinfo->version[VER_DRIVER]) != NULL) + seq_printf(m, "%-16s %s\n", "ver_driver", s); + if ((s = cinfo->version[VER_CARDTYPE]) != NULL) + seq_printf(m, "%-16s %s\n", "ver_cardtype", s); + if ((s = cinfo->version[VER_SERIAL]) != NULL) + seq_printf(m, "%-16s %s\n", "ver_serial", s); + + if (card->cardtype != avm_m1) { + flag = ((u8 *)(ctrl->profile.manu))[3]; + if (flag) + seq_printf(m, "%-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) + seq_printf(m, "%-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" : "" + ); + } + seq_printf(m, "%-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); + + seq_printf(m, "%-16s 0x%lx\n", "csr (cached)", (unsigned long)card->csr); + seq_printf(m, "%-16s 0x%lx\n", "csr", (unsigned long)csr); + seq_printf(m, "%-16s %lu\n", "txoff", (unsigned long)txoff); + seq_printf(m, "%-16s %lu\n", "txlen", (unsigned long)txlen); + seq_printf(m, "%-16s %lu\n", "rxoff", (unsigned long)rxoff); + seq_printf(m, "%-16s %lu\n", "rxlen", (unsigned long)rxlen); + + return 0; +} +EXPORT_SYMBOL(b1dma_proc_show); + +/* ------------------------------------------------------------- */ + +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); + +static int __init b1dma_init(void) +{ + char *p; + char rev[32]; + + if ((p = strchr(revision, ':')) != NULL && p[1]) { + strlcpy(rev, p + 2, sizeof(rev)); + if ((p = strchr(rev, '$')) != NULL && p > rev) + *(p - 1) = 0; + } else + strcpy(rev, "1.0"); + + printk(KERN_INFO "b1dma: revision %s\n", rev); + + return 0; +} + +static void __exit b1dma_exit(void) +{ +} + +module_init(b1dma_init); +module_exit(b1dma_exit); diff --git a/drivers/staging/isdn/avm/b1isa.c b/drivers/staging/isdn/avm/b1isa.c new file mode 100644 index 000000000000..cdfea72e0ef6 --- /dev/null +++ b/drivers/staging/isdn/avm/b1isa.c @@ -0,0 +1,243 @@ +/* $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.proc_show = b1_proc_show; + 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_param_hw_array(io, int, ioport, NULL, 0); +module_param_hw_array(irq, int, irq, NULL, 0); +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, ':')) != NULL && p[1]) { + strlcpy(rev, p + 2, 32); + if ((p = strchr(rev, '$')) != NULL && 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 (isa_dev[i].resource[0].start) + b1isa_remove(&isa_dev[i]); + } + unregister_capi_driver(&capi_driver_b1isa); +} + +module_init(b1isa_init); +module_exit(b1isa_exit); diff --git a/drivers/staging/isdn/avm/b1pci.c b/drivers/staging/isdn/avm/b1pci.c new file mode 100644 index 000000000000..b76b57a82c02 --- /dev/null +++ b/drivers/staging/isdn/avm/b1pci.c @@ -0,0 +1,416 @@ +/* $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/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, IRQF_SHARED, 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.proc_show = b1_proc_show; + 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, IRQF_SHARED, 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.proc_show = b1dma_proc_show; + 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 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 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 = 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, ':')) != NULL && p[1]) { + strlcpy(rev, p + 2, 32); + if ((p = strchr(rev, '$')) != NULL && 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/staging/isdn/avm/b1pcmcia.c b/drivers/staging/isdn/avm/b1pcmcia.c new file mode 100644 index 000000000000..3aca16e62902 --- /dev/null +++ b/drivers/staging/isdn/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, IRQF_SHARED, 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.proc_show = b1_proc_show; + 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, ':')) != NULL && p[1]) { + strlcpy(rev, p + 2, 32); + if ((p = strchr(rev, '$')) != NULL && 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/staging/isdn/avm/c4.c b/drivers/staging/isdn/avm/c4.c new file mode 100644 index 000000000000..ac72cd204c4d --- /dev/null +++ b/drivers/staging/isdn/avm/c4.c @@ -0,0 +1,1317 @@ +/* $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/module.h> +#include <linux/kernel.h> +#include <linux/proc_fs.h> +#include <linux/seq_file.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 <linux/gfp.h> +#include <asm/io.h> +#include <linux/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 AVM_C4_DEBUG +#undef AVM_C4_POLLDEBUG + +/* ------------------------------------------------------------- */ + +static char *revision = "$Revision: 1.1.2.2 $"; + +/* ------------------------------------------------------------- */ + +static bool suppress_pollack; + +static const 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_param(suppress_pollack, bool, 0); + +/* ------------------------------------------------------------- */ + +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 AVM_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 AVM_C4_DEBUG + printk(KERN_DEBUG "%s: tx put msg len=%d\n", card->name, txlen); +#endif + } else { + txlen = skb->len - 2; +#ifdef AVM_C4_POLLDEBUG + if (skb->data[2] == SEND_POLLACK) + printk(KERN_INFO "%s: ack to c4\n", card->name); +#endif +#ifdef AVM_C4_DEBUG + printk(KERN_DEBUG "%s: tx put 0x%x len=%d\n", + card->name, skb->data[2], txlen); +#endif + skb_copy_from_linear_data_offset(skb, 2, dma->sendbuf.dmabuf, + 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 AVM_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 { + skb_put_data(skb, card->msgbuf, MsgLen); + skb_put_data(skb, 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 { + skb_put_data(skb, 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 AVM_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)); + spin_lock_irqsave(&card->lock, flags); + capilib_release(&cinfo->ncci_head); + spin_unlock_irqrestore(&card->lock, flags); + capi_ctr_down(&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) +{ + avmcard *card = devptr; + + return c4_handle_interrupt(card); +} + +/* ------------------------------------------------------------- */ + +static void c4_send_init(avmcard *card) +{ + struct sk_buff *skb; + void *p; + unsigned long flags; + + 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); + spin_lock_irqsave(&card->lock, flags); + c4_dispatch_tx(card); + spin_unlock_irqrestore(&card->lock, flags); +} + +static int queue_sendconfigword(avmcard *card, u32 val) +{ + 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_word(&p, val); + 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 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; +} + + +static 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_down(&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); +} + +/* ------------------------------------------------------------- */ + + +static 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); + } +} + +/* ------------------------------------------------------------- */ + +static 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; + + spin_lock_irqsave(&card->lock, flags); + capilib_release_appl(&cinfo->ncci_head, appl); + spin_unlock_irqrestore(&card->lock, flags); + + 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; + + spin_lock_irqsave(&card->lock, 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); + 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_proc_show(struct seq_file *m, void *v) +{ + struct capi_ctr *ctrl = m->private; + avmctrl_info *cinfo = (avmctrl_info *)(ctrl->driverdata); + avmcard *card = cinfo->card; + u8 flag; + char *s; + + seq_printf(m, "%-16s %s\n", "name", card->name); + seq_printf(m, "%-16s 0x%x\n", "io", card->port); + seq_printf(m, "%-16s %d\n", "irq", card->irq); + seq_printf(m, "%-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; + } + seq_printf(m, "%-16s %s\n", "type", s); + if ((s = cinfo->version[VER_DRIVER]) != NULL) + seq_printf(m, "%-16s %s\n", "ver_driver", s); + if ((s = cinfo->version[VER_CARDTYPE]) != NULL) + seq_printf(m, "%-16s %s\n", "ver_cardtype", s); + if ((s = cinfo->version[VER_SERIAL]) != NULL) + seq_printf(m, "%-16s %s\n", "ver_serial", s); + + if (card->cardtype != avm_m1) { + flag = ((u8 *)(ctrl->profile.manu))[3]; + if (flag) + seq_printf(m, "%-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) + seq_printf(m, "%-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" : "" + ); + } + seq_printf(m, "%-16s %s\n", "cardname", cinfo->cardname); + + return 0; +} + +/* ------------------------------------------------------------- */ + +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 == NULL) { + 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, IRQF_SHARED, 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.proc_show = c4_proc_show; + 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 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); + pci_disable_device(dev); + 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, ':')) != NULL && p[1]) { + strlcpy(rev, p + 2, 32); + if ((p = strchr(rev, '$')) != NULL && 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/staging/isdn/avm/t1isa.c b/drivers/staging/isdn/avm/t1isa.c new file mode 100644 index 000000000000..2153619c5b31 --- /dev/null +++ b/drivers/staging/isdn/avm/t1isa.c @@ -0,0 +1,594 @@ +/* $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 <linux/gfp.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) +{ + 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 { + skb_put_data(skb, card->msgbuf, MsgLen); + skb_put_data(skb, 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); + if (!(skb = alloc_skb(MsgLen, GFP_ATOMIC))) { + spin_unlock_irqrestore(&card->lock, flags); + printk(KERN_ERR "%s: incoming packet dropped\n", + card->name); + } else { + skb_put_data(skb, 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)); + spin_unlock_irqrestore(&card->lock, flags); + 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); + capilib_new_ncci(&cinfo->ncci_head, ApplId, NCCI, WindowSize); + spin_unlock_irqrestore(&card->lock, flags); + break; + + case RECEIVE_FREE_NCCI: + + ApplId = b1_get_word(card->port); + NCCI = b1_get_word(card->port); + if (NCCI != 0xffffffff) + capilib_free_ncci(&cinfo->ncci_head, ApplId, NCCI); + spin_unlock_irqrestore(&card->lock, flags); + 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; +} + +static void t1isa_reset_ctr(struct capi_ctr *ctrl) +{ + avmctrl_info *cinfo = (avmctrl_info *)(ctrl->driverdata); + avmcard *card = cinfo->card; + unsigned int port = card->port; + unsigned long flags; + + t1_disable_irq(port); + b1_reset(port); + b1_reset(port); + + memset(cinfo->version, 0, sizeof(cinfo->version)); + spin_lock_irqsave(&card->lock, flags); + capilib_release(&cinfo->ncci_head); + spin_unlock_irqrestore(&card->lock, flags); + capi_ctr_down(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.proc_show = b1_proc_show; + 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; + + spin_lock_irqsave(&card->lock, flags); + 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) { + spin_unlock_irqrestore(&card->lock, flags); + return retval; + } + dlen = CAPIMSG_DATALEN(skb->data); + + b1_put_byte(port, SEND_DATA_B3_REQ); + t1_put_slice(port, skb->data, len); + t1_put_slice(port, skb->data + len, dlen); + } else { + 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_param_hw_array(io, int, ioport, NULL, 0); +module_param_hw_array(irq, int, irq, NULL, 0); +module_param_array(cardnr, int, NULL, 0); +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, ':')) != NULL && p[1]) { + strlcpy(rev, p + 2, 32); + if ((p = strchr(rev, '$')) != NULL && 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; + + unregister_capi_driver(&capi_driver_t1isa); + 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/staging/isdn/avm/t1pci.c b/drivers/staging/isdn/avm/t1pci.c new file mode 100644 index 000000000000..f5ed1d5004c9 --- /dev/null +++ b/drivers/staging/isdn/avm/t1pci.c @@ -0,0 +1,259 @@ +/* $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/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, IRQF_SHARED, 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.proc_show = b1dma_proc_show; + 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 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); + pci_disable_device(dev); + 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, ':')) != NULL && p[1]) { + strlcpy(rev, p + 2, 32); + if ((p = strchr(rev, '$')) != NULL && 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/staging/isdn/gigaset/Kconfig b/drivers/staging/isdn/gigaset/Kconfig new file mode 100644 index 000000000000..c593105b3600 --- /dev/null +++ b/drivers/staging/isdn/gigaset/Kconfig @@ -0,0 +1,62 @@ +# SPDX-License-Identifier: GPL-2.0-only +menuconfig ISDN_DRV_GIGASET + tristate "Siemens Gigaset support" + depends on TTY + select CRC_CCITT + select BITREVERSE + help + This driver supports the Siemens Gigaset SX205/255 family of + ISDN DECT bases, including the predecessors Gigaset 3070/3075 + and 4170/4175 and their T-Com versions Sinus 45isdn and Sinus + 721X. + If you have one of these devices, say M here and for at least + one of the connection specific parts that follow. + This will build a module called "gigaset". + Note: If you build your ISDN subsystem (ISDN_CAPI or ISDN_I4L) + as a module, you have to build this driver as a module too, + otherwise the Gigaset device won't show up as an ISDN device. + +if ISDN_DRV_GIGASET + +config GIGASET_CAPI + bool "Gigaset CAPI support" + depends on ISDN_CAPI='y'||(ISDN_CAPI='m'&&ISDN_DRV_GIGASET='m') + default 'y' + help + Build the Gigaset driver as a CAPI 2.0 driver interfacing with + the Kernel CAPI subsystem. To use it with the old ISDN4Linux + subsystem you'll have to enable the capidrv glue driver. + (select ISDN_CAPI_CAPIDRV.) + Say N to build the old native ISDN4Linux variant. + If unsure, say Y. + +config GIGASET_BASE + tristate "Gigaset base station support" + depends on USB + help + Say M here if you want to use the USB interface of the Gigaset + base for connection to your system. + This will build a module called "bas_gigaset". + +config GIGASET_M105 + tristate "Gigaset M105 support" + depends on USB + help + Say M here if you want to connect to the Gigaset base via DECT + using a Gigaset M105 (Sinus 45 Data 2) USB DECT device. + This will build a module called "usb_gigaset". + +config GIGASET_M101 + tristate "Gigaset M101 support" + help + Say M here if you want to connect to the Gigaset base via DECT + using a Gigaset M101 (Sinus 45 Data 1) RS232 DECT device. + This will build a module called "ser_gigaset". + +config GIGASET_DEBUG + bool "Gigaset debugging" + help + This enables debugging code in the Gigaset drivers. + If in doubt, say yes. + +endif # ISDN_DRV_GIGASET diff --git a/drivers/staging/isdn/gigaset/Makefile b/drivers/staging/isdn/gigaset/Makefile new file mode 100644 index 000000000000..9c010891dcd7 --- /dev/null +++ b/drivers/staging/isdn/gigaset/Makefile @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0 +gigaset-y := common.o interface.o proc.o ev-layer.o asyncdata.o + +ifdef CONFIG_GIGASET_CAPI +gigaset-y += capi.o +else +gigaset-y += dummyll.o +endif + +usb_gigaset-y := usb-gigaset.o +ser_gigaset-y := ser-gigaset.o +bas_gigaset-y := bas-gigaset.o isocdata.o + +obj-$(CONFIG_ISDN_DRV_GIGASET) += gigaset.o +obj-$(CONFIG_GIGASET_M105) += usb_gigaset.o +obj-$(CONFIG_GIGASET_BASE) += bas_gigaset.o +obj-$(CONFIG_GIGASET_M101) += ser_gigaset.o diff --git a/drivers/staging/isdn/gigaset/asyncdata.c b/drivers/staging/isdn/gigaset/asyncdata.c new file mode 100644 index 000000000000..c0cbee06bc21 --- /dev/null +++ b/drivers/staging/isdn/gigaset/asyncdata.c @@ -0,0 +1,609 @@ +/* + * Common data handling layer for ser_gigaset and usb_gigaset + * + * Copyright (c) 2005 by Tilman Schmidt <tilman@imap.cc>, + * Hansjoerg Lipp <hjlipp@web.de>, + * Stefan Eilers. + * + * ===================================================================== + * 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. + * ===================================================================== + */ + +#include "gigaset.h" +#include <linux/crc-ccitt.h> +#include <linux/bitrev.h> +#include <linux/export.h> + +/* check if byte must be stuffed/escaped + * I'm not sure which data should be encoded. + * Therefore I will go the hard way and encode every value + * less than 0x20, the flag sequence and the control escape char. + */ +static inline int muststuff(unsigned char c) +{ + if (c < PPP_TRANS) return 1; + if (c == PPP_FLAG) return 1; + if (c == PPP_ESCAPE) return 1; + /* other possible candidates: */ + /* 0x91: XON with parity set */ + /* 0x93: XOFF with parity set */ + return 0; +} + +/* == data input =========================================================== */ + +/* process a block of received bytes in command mode + * (mstate != MS_LOCKED && (inputstate & INS_command)) + * Append received bytes to the command response buffer and forward them + * line by line to the response handler. Exit whenever a mode/state change + * might have occurred. + * Note: Received lines may be terminated by CR, LF, or CR LF, which will be + * removed before passing the line to the response handler. + * Return value: + * number of processed bytes + */ +static unsigned cmd_loop(unsigned numbytes, struct inbuf_t *inbuf) +{ + unsigned char *src = inbuf->data + inbuf->head; + struct cardstate *cs = inbuf->cs; + unsigned cbytes = cs->cbytes; + unsigned procbytes = 0; + unsigned char c; + + while (procbytes < numbytes) { + c = *src++; + procbytes++; + + switch (c) { + case '\n': + if (cbytes == 0 && cs->respdata[0] == '\r') { + /* collapse LF with preceding CR */ + cs->respdata[0] = 0; + break; + } + /* fall through */ + case '\r': + /* end of message line, pass to response handler */ + if (cbytes >= MAX_RESP_SIZE) { + dev_warn(cs->dev, "response too large (%d)\n", + cbytes); + cbytes = MAX_RESP_SIZE; + } + cs->cbytes = cbytes; + gigaset_dbg_buffer(DEBUG_TRANSCMD, "received response", + cbytes, cs->respdata); + gigaset_handle_modem_response(cs); + cbytes = 0; + + /* store EOL byte for CRLF collapsing */ + cs->respdata[0] = c; + + /* cs->dle may have changed */ + if (cs->dle && !(inbuf->inputstate & INS_DLE_command)) + inbuf->inputstate &= ~INS_command; + + /* return for reevaluating state */ + goto exit; + + case DLE_FLAG: + if (inbuf->inputstate & INS_DLE_char) { + /* quoted DLE: clear quote flag */ + inbuf->inputstate &= ~INS_DLE_char; + } else if (cs->dle || + (inbuf->inputstate & INS_DLE_command)) { + /* DLE escape, pass up for handling */ + inbuf->inputstate |= INS_DLE_char; + goto exit; + } + /* quoted or not in DLE mode: treat as regular data */ + /* fall through */ + default: + /* append to line buffer if possible */ + if (cbytes < MAX_RESP_SIZE) + cs->respdata[cbytes] = c; + cbytes++; + } + } +exit: + cs->cbytes = cbytes; + return procbytes; +} + +/* process a block of received bytes in lock mode + * All received bytes are passed unmodified to the tty i/f. + * Return value: + * number of processed bytes + */ +static unsigned lock_loop(unsigned numbytes, struct inbuf_t *inbuf) +{ + unsigned char *src = inbuf->data + inbuf->head; + + gigaset_dbg_buffer(DEBUG_LOCKCMD, "received response", numbytes, src); + gigaset_if_receive(inbuf->cs, src, numbytes); + return numbytes; +} + +/* process a block of received bytes in HDLC data mode + * (mstate != MS_LOCKED && !(inputstate & INS_command) && proto2 == L2_HDLC) + * Collect HDLC frames, undoing byte stuffing and watching for DLE escapes. + * When a frame is complete, check the FCS and pass valid frames to the LL. + * If DLE is encountered, return immediately to let the caller handle it. + * Return value: + * number of processed bytes + */ +static unsigned hdlc_loop(unsigned numbytes, struct inbuf_t *inbuf) +{ + struct cardstate *cs = inbuf->cs; + struct bc_state *bcs = cs->bcs; + int inputstate = bcs->inputstate; + __u16 fcs = bcs->rx_fcs; + struct sk_buff *skb = bcs->rx_skb; + unsigned char *src = inbuf->data + inbuf->head; + unsigned procbytes = 0; + unsigned char c; + + if (inputstate & INS_byte_stuff) { + if (!numbytes) + return 0; + inputstate &= ~INS_byte_stuff; + goto byte_stuff; + } + + while (procbytes < numbytes) { + c = *src++; + procbytes++; + if (c == DLE_FLAG) { + if (inputstate & INS_DLE_char) { + /* quoted DLE: clear quote flag */ + inputstate &= ~INS_DLE_char; + } else if (cs->dle || (inputstate & INS_DLE_command)) { + /* DLE escape, pass up for handling */ + inputstate |= INS_DLE_char; + break; + } + } + + if (c == PPP_ESCAPE) { + /* byte stuffing indicator: pull in next byte */ + if (procbytes >= numbytes) { + /* end of buffer, save for later processing */ + inputstate |= INS_byte_stuff; + break; + } +byte_stuff: + c = *src++; + procbytes++; + if (c == DLE_FLAG) { + if (inputstate & INS_DLE_char) { + /* quoted DLE: clear quote flag */ + inputstate &= ~INS_DLE_char; + } else if (cs->dle || + (inputstate & INS_DLE_command)) { + /* DLE escape, pass up for handling */ + inputstate |= + INS_DLE_char | INS_byte_stuff; + break; + } + } + c ^= PPP_TRANS; +#ifdef CONFIG_GIGASET_DEBUG + if (!muststuff(c)) + gig_dbg(DEBUG_HDLC, "byte stuffed: 0x%02x", c); +#endif + } else if (c == PPP_FLAG) { + /* end of frame: process content if any */ + if (inputstate & INS_have_data) { + gig_dbg(DEBUG_HDLC, + "7e----------------------------"); + + /* check and pass received frame */ + if (!skb) { + /* skipped frame */ + gigaset_isdn_rcv_err(bcs); + } else if (skb->len < 2) { + /* frame too short for FCS */ + dev_warn(cs->dev, + "short frame (%d)\n", + skb->len); + gigaset_isdn_rcv_err(bcs); + dev_kfree_skb_any(skb); + } else if (fcs != PPP_GOODFCS) { + /* frame check error */ + dev_err(cs->dev, + "Checksum failed, %u bytes corrupted!\n", + skb->len); + gigaset_isdn_rcv_err(bcs); + dev_kfree_skb_any(skb); + } else { + /* good frame */ + __skb_trim(skb, skb->len - 2); + gigaset_skb_rcvd(bcs, skb); + } + + /* prepare reception of next frame */ + inputstate &= ~INS_have_data; + skb = gigaset_new_rx_skb(bcs); + } else { + /* empty frame (7E 7E) */ +#ifdef CONFIG_GIGASET_DEBUG + ++bcs->emptycount; +#endif + if (!skb) { + /* skipped (?) */ + gigaset_isdn_rcv_err(bcs); + skb = gigaset_new_rx_skb(bcs); + } + } + + fcs = PPP_INITFCS; + continue; +#ifdef CONFIG_GIGASET_DEBUG + } else if (muststuff(c)) { + /* Should not happen. Possible after ZDLE=1<CR><LF>. */ + gig_dbg(DEBUG_HDLC, "not byte stuffed: 0x%02x", c); +#endif + } + + /* regular data byte, append to skb */ +#ifdef CONFIG_GIGASET_DEBUG + if (!(inputstate & INS_have_data)) { + gig_dbg(DEBUG_HDLC, "7e (%d x) ================", + bcs->emptycount); + bcs->emptycount = 0; + } +#endif + inputstate |= INS_have_data; + if (skb) { + if (skb->len >= bcs->rx_bufsize) { + dev_warn(cs->dev, "received packet too long\n"); + dev_kfree_skb_any(skb); + /* skip remainder of packet */ + bcs->rx_skb = skb = NULL; + } else { + __skb_put_u8(skb, c); + fcs = crc_ccitt_byte(fcs, c); + } + } + } + + bcs->inputstate = inputstate; + bcs->rx_fcs = fcs; + return procbytes; +} + +/* process a block of received bytes in transparent data mode + * (mstate != MS_LOCKED && !(inputstate & INS_command) && proto2 != L2_HDLC) + * Invert bytes, undoing byte stuffing and watching for DLE escapes. + * If DLE is encountered, return immediately to let the caller handle it. + * Return value: + * number of processed bytes + */ +static unsigned iraw_loop(unsigned numbytes, struct inbuf_t *inbuf) +{ + struct cardstate *cs = inbuf->cs; + struct bc_state *bcs = cs->bcs; + int inputstate = bcs->inputstate; + struct sk_buff *skb = bcs->rx_skb; + unsigned char *src = inbuf->data + inbuf->head; + unsigned procbytes = 0; + unsigned char c; + + if (!skb) { + /* skip this block */ + gigaset_new_rx_skb(bcs); + return numbytes; + } + + while (procbytes < numbytes && skb->len < bcs->rx_bufsize) { + c = *src++; + procbytes++; + + if (c == DLE_FLAG) { + if (inputstate & INS_DLE_char) { + /* quoted DLE: clear quote flag */ + inputstate &= ~INS_DLE_char; + } else if (cs->dle || (inputstate & INS_DLE_command)) { + /* DLE escape, pass up for handling */ + inputstate |= INS_DLE_char; + break; + } + } + + /* regular data byte: append to current skb */ + inputstate |= INS_have_data; + __skb_put_u8(skb, bitrev8(c)); + } + + /* pass data up */ + if (inputstate & INS_have_data) { + gigaset_skb_rcvd(bcs, skb); + inputstate &= ~INS_have_data; + gigaset_new_rx_skb(bcs); + } + + bcs->inputstate = inputstate; + return procbytes; +} + +/* process DLE escapes + * Called whenever a DLE sequence might be encountered in the input stream. + * Either processes the entire DLE sequence or, if that isn't possible, + * notes the fact that an initial DLE has been received in the INS_DLE_char + * inputstate flag and resumes processing of the sequence on the next call. + */ +static void handle_dle(struct inbuf_t *inbuf) +{ + struct cardstate *cs = inbuf->cs; + + if (cs->mstate == MS_LOCKED) + return; /* no DLE processing in lock mode */ + + if (!(inbuf->inputstate & INS_DLE_char)) { + /* no DLE pending */ + if (inbuf->data[inbuf->head] == DLE_FLAG && + (cs->dle || inbuf->inputstate & INS_DLE_command)) { + /* start of DLE sequence */ + inbuf->head++; + if (inbuf->head == inbuf->tail || + inbuf->head == RBUFSIZE) { + /* end of buffer, save for later processing */ + inbuf->inputstate |= INS_DLE_char; + return; + } + } else { + /* regular data byte */ + return; + } + } + + /* consume pending DLE */ + inbuf->inputstate &= ~INS_DLE_char; + + switch (inbuf->data[inbuf->head]) { + case 'X': /* begin of event message */ + if (inbuf->inputstate & INS_command) + dev_notice(cs->dev, + "received <DLE>X in command mode\n"); + inbuf->inputstate |= INS_command | INS_DLE_command; + inbuf->head++; /* byte consumed */ + break; + case '.': /* end of event message */ + if (!(inbuf->inputstate & INS_DLE_command)) + dev_notice(cs->dev, + "received <DLE>. without <DLE>X\n"); + inbuf->inputstate &= ~INS_DLE_command; + /* return to data mode if in DLE mode */ + if (cs->dle) + inbuf->inputstate &= ~INS_command; + inbuf->head++; /* byte consumed */ + break; + case DLE_FLAG: /* DLE in data stream */ + /* mark as quoted */ + inbuf->inputstate |= INS_DLE_char; + if (!(cs->dle || inbuf->inputstate & INS_DLE_command)) + dev_notice(cs->dev, + "received <DLE><DLE> not in DLE mode\n"); + break; /* quoted byte left in buffer */ + default: + dev_notice(cs->dev, "received <DLE><%02x>\n", + inbuf->data[inbuf->head]); + /* quoted byte left in buffer */ + } +} + +/** + * gigaset_m10x_input() - process a block of data received from the device + * @inbuf: received data and device descriptor structure. + * + * Called by hardware module {ser,usb}_gigaset with a block of received + * bytes. Separates the bytes received over the serial data channel into + * user data and command replies (locked/unlocked) according to the + * current state of the interface. + */ +void gigaset_m10x_input(struct inbuf_t *inbuf) +{ + struct cardstate *cs = inbuf->cs; + unsigned numbytes, procbytes; + + gig_dbg(DEBUG_INTR, "buffer state: %u -> %u", inbuf->head, inbuf->tail); + + while (inbuf->head != inbuf->tail) { + /* check for DLE escape */ + handle_dle(inbuf); + + /* process a contiguous block of bytes */ + numbytes = (inbuf->head > inbuf->tail ? + RBUFSIZE : inbuf->tail) - inbuf->head; + gig_dbg(DEBUG_INTR, "processing %u bytes", numbytes); + /* + * numbytes may be 0 if handle_dle() ate the last byte. + * This does no harm, *_loop() will just return 0 immediately. + */ + + if (cs->mstate == MS_LOCKED) + procbytes = lock_loop(numbytes, inbuf); + else if (inbuf->inputstate & INS_command) + procbytes = cmd_loop(numbytes, inbuf); + else if (cs->bcs->proto2 == L2_HDLC) + procbytes = hdlc_loop(numbytes, inbuf); + else + procbytes = iraw_loop(numbytes, inbuf); + inbuf->head += procbytes; + + /* check for buffer wraparound */ + if (inbuf->head >= RBUFSIZE) + inbuf->head = 0; + + gig_dbg(DEBUG_INTR, "head set to %u", inbuf->head); + } +} +EXPORT_SYMBOL_GPL(gigaset_m10x_input); + + +/* == data output ========================================================== */ + +/* + * Encode a data packet into an octet stuffed HDLC frame with FCS, + * opening and closing flags, preserving headroom data. + * parameters: + * skb skb containing original packet (freed upon return) + * Return value: + * pointer to newly allocated skb containing the result frame + * and the original link layer header, NULL on error + */ +static struct sk_buff *HDLC_Encode(struct sk_buff *skb) +{ + struct sk_buff *hdlc_skb; + __u16 fcs; + unsigned char c; + unsigned char *cp; + int len; + unsigned int stuf_cnt; + + stuf_cnt = 0; + fcs = PPP_INITFCS; + cp = skb->data; + len = skb->len; + while (len--) { + if (muststuff(*cp)) + stuf_cnt++; + fcs = crc_ccitt_byte(fcs, *cp++); + } + fcs ^= 0xffff; /* complement */ + + /* size of new buffer: original size + number of stuffing bytes + * + 2 bytes FCS + 2 stuffing bytes for FCS (if needed) + 2 flag bytes + * + room for link layer header + */ + hdlc_skb = dev_alloc_skb(skb->len + stuf_cnt + 6 + skb->mac_len); + if (!hdlc_skb) { + dev_kfree_skb_any(skb); + return NULL; + } + + /* Copy link layer header into new skb */ + skb_reset_mac_header(hdlc_skb); + skb_reserve(hdlc_skb, skb->mac_len); + memcpy(skb_mac_header(hdlc_skb), skb_mac_header(skb), skb->mac_len); + hdlc_skb->mac_len = skb->mac_len; + + /* Add flag sequence in front of everything.. */ + skb_put_u8(hdlc_skb, PPP_FLAG); + + /* Perform byte stuffing while copying data. */ + while (skb->len--) { + if (muststuff(*skb->data)) { + skb_put_u8(hdlc_skb, PPP_ESCAPE); + skb_put_u8(hdlc_skb, (*skb->data++) ^ PPP_TRANS); + } else + skb_put_u8(hdlc_skb, *skb->data++); + } + + /* Finally add FCS (byte stuffed) and flag sequence */ + c = (fcs & 0x00ff); /* least significant byte first */ + if (muststuff(c)) { + skb_put_u8(hdlc_skb, PPP_ESCAPE); + c ^= PPP_TRANS; + } + skb_put_u8(hdlc_skb, c); + + c = ((fcs >> 8) & 0x00ff); + if (muststuff(c)) { + skb_put_u8(hdlc_skb, PPP_ESCAPE); + c ^= PPP_TRANS; + } + skb_put_u8(hdlc_skb, c); + + skb_put_u8(hdlc_skb, PPP_FLAG); + + dev_kfree_skb_any(skb); + return hdlc_skb; +} + +/* + * Encode a data packet into an octet stuffed raw bit inverted frame, + * preserving headroom data. + * parameters: + * skb skb containing original packet (freed upon return) + * Return value: + * pointer to newly allocated skb containing the result frame + * and the original link layer header, NULL on error + */ +static struct sk_buff *iraw_encode(struct sk_buff *skb) +{ + struct sk_buff *iraw_skb; + unsigned char c; + unsigned char *cp; + int len; + + /* size of new buffer (worst case = every byte must be stuffed): + * 2 * original size + room for link layer header + */ + iraw_skb = dev_alloc_skb(2 * skb->len + skb->mac_len); + if (!iraw_skb) { + dev_kfree_skb_any(skb); + return NULL; + } + + /* copy link layer header into new skb */ + skb_reset_mac_header(iraw_skb); + skb_reserve(iraw_skb, skb->mac_len); + memcpy(skb_mac_header(iraw_skb), skb_mac_header(skb), skb->mac_len); + iraw_skb->mac_len = skb->mac_len; + + /* copy and stuff data */ + cp = skb->data; + len = skb->len; + while (len--) { + c = bitrev8(*cp++); + if (c == DLE_FLAG) + skb_put_u8(iraw_skb, c); + skb_put_u8(iraw_skb, c); + } + dev_kfree_skb_any(skb); + return iraw_skb; +} + +/** + * gigaset_m10x_send_skb() - queue an skb for sending + * @bcs: B channel descriptor structure. + * @skb: data to send. + * + * Called by LL to encode and queue an skb for sending, and start + * transmission if necessary. + * Once the payload data has been transmitted completely, gigaset_skb_sent() + * will be called with the skb's link layer header preserved. + * + * Return value: + * number of bytes accepted for sending (skb->len) if ok, + * error code < 0 (eg. -ENOMEM) on error + */ +int gigaset_m10x_send_skb(struct bc_state *bcs, struct sk_buff *skb) +{ + struct cardstate *cs = bcs->cs; + unsigned len = skb->len; + unsigned long flags; + + if (bcs->proto2 == L2_HDLC) + skb = HDLC_Encode(skb); + else + skb = iraw_encode(skb); + if (!skb) { + dev_err(cs->dev, + "unable to allocate memory for encoding!\n"); + return -ENOMEM; + } + + skb_queue_tail(&bcs->squeue, skb); + spin_lock_irqsave(&cs->lock, flags); + if (cs->connected) + tasklet_schedule(&cs->write_tasklet); + spin_unlock_irqrestore(&cs->lock, flags); + + return len; /* ok so far */ +} +EXPORT_SYMBOL_GPL(gigaset_m10x_send_skb); diff --git a/drivers/staging/isdn/gigaset/bas-gigaset.c b/drivers/staging/isdn/gigaset/bas-gigaset.c new file mode 100644 index 000000000000..149b1aca52a2 --- /dev/null +++ b/drivers/staging/isdn/gigaset/bas-gigaset.c @@ -0,0 +1,2675 @@ +/* + * USB driver for Gigaset 307x base via direct USB connection. + * + * Copyright (c) 2001 by Hansjoerg Lipp <hjlipp@web.de>, + * Tilman Schmidt <tilman@imap.cc>, + * Stefan Eilers. + * + * ===================================================================== + * 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. + * ===================================================================== + */ + +#include "gigaset.h" +#include <linux/usb.h> +#include <linux/module.h> +#include <linux/moduleparam.h> + +/* Version Information */ +#define DRIVER_AUTHOR "Tilman Schmidt <tilman@imap.cc>, Hansjoerg Lipp <hjlipp@web.de>, Stefan Eilers" +#define DRIVER_DESC "USB Driver for Gigaset 307x" + + +/* Module parameters */ + +static int startmode = SM_ISDN; +static int cidmode = 1; + +module_param(startmode, int, S_IRUGO); +module_param(cidmode, int, S_IRUGO); +MODULE_PARM_DESC(startmode, "start in isdn4linux mode"); +MODULE_PARM_DESC(cidmode, "Call-ID mode"); + +#define GIGASET_MINORS 1 +#define GIGASET_MINOR 16 +#define GIGASET_MODULENAME "bas_gigaset" +#define GIGASET_DEVNAME "ttyGB" + +/* length limit according to Siemens 3070usb-protokoll.doc ch. 2.1 */ +#define IF_WRITEBUF 264 + +/* interrupt pipe message size according to ibid. ch. 2.2 */ +#define IP_MSGSIZE 3 + +/* Values for the Gigaset 307x */ +#define USB_GIGA_VENDOR_ID 0x0681 +#define USB_3070_PRODUCT_ID 0x0001 +#define USB_3075_PRODUCT_ID 0x0002 +#define USB_SX303_PRODUCT_ID 0x0021 +#define USB_SX353_PRODUCT_ID 0x0022 + +/* table of devices that work with this driver */ +static const struct usb_device_id gigaset_table[] = { + { USB_DEVICE(USB_GIGA_VENDOR_ID, USB_3070_PRODUCT_ID) }, + { USB_DEVICE(USB_GIGA_VENDOR_ID, USB_3075_PRODUCT_ID) }, + { USB_DEVICE(USB_GIGA_VENDOR_ID, USB_SX303_PRODUCT_ID) }, + { USB_DEVICE(USB_GIGA_VENDOR_ID, USB_SX353_PRODUCT_ID) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, gigaset_table); + +/*======================= local function prototypes ==========================*/ + +/* function called if a new device belonging to this driver is connected */ +static int gigaset_probe(struct usb_interface *interface, + const struct usb_device_id *id); + +/* Function will be called if the device is unplugged */ +static void gigaset_disconnect(struct usb_interface *interface); + +/* functions called before/after suspend */ +static int gigaset_suspend(struct usb_interface *intf, pm_message_t message); +static int gigaset_resume(struct usb_interface *intf); + +/* functions called before/after device reset */ +static int gigaset_pre_reset(struct usb_interface *intf); +static int gigaset_post_reset(struct usb_interface *intf); + +static int atread_submit(struct cardstate *, int); +static void stopurbs(struct bas_bc_state *); +static int req_submit(struct bc_state *, int, int, int); +static int atwrite_submit(struct cardstate *, unsigned char *, int); +static int start_cbsend(struct cardstate *); + +/*============================================================================*/ + +struct bas_cardstate { + struct usb_device *udev; /* USB device pointer */ + struct cardstate *cs; + struct usb_interface *interface; /* interface for this device */ + unsigned char minor; /* starting minor number */ + + struct urb *urb_ctrl; /* control pipe default URB */ + struct usb_ctrlrequest dr_ctrl; + struct timer_list timer_ctrl; /* control request timeout */ + int retry_ctrl; + + struct timer_list timer_atrdy; /* AT command ready timeout */ + struct urb *urb_cmd_out; /* for sending AT commands */ + struct usb_ctrlrequest dr_cmd_out; + int retry_cmd_out; + + struct urb *urb_cmd_in; /* for receiving AT replies */ + struct usb_ctrlrequest dr_cmd_in; + struct timer_list timer_cmd_in; /* receive request timeout */ + unsigned char *rcvbuf; /* AT reply receive buffer */ + + struct urb *urb_int_in; /* URB for interrupt pipe */ + unsigned char *int_in_buf; + struct work_struct int_in_wq; /* for usb_clear_halt() */ + struct timer_list timer_int_in; /* int read retry delay */ + int retry_int_in; + + spinlock_t lock; /* locks all following */ + int basstate; /* bitmap (BS_*) */ + int pending; /* uncompleted base request */ + wait_queue_head_t waitqueue; + int rcvbuf_size; /* size of AT receive buffer */ + /* 0: no receive in progress */ + int retry_cmd_in; /* receive req retry count */ +}; + +/* status of direct USB connection to 307x base (bits in basstate) */ +#define BS_ATOPEN 0x001 /* AT channel open */ +#define BS_B1OPEN 0x002 /* B channel 1 open */ +#define BS_B2OPEN 0x004 /* B channel 2 open */ +#define BS_ATREADY 0x008 /* base ready for AT command */ +#define BS_INIT 0x010 /* base has signalled INIT_OK */ +#define BS_ATTIMER 0x020 /* waiting for HD_READY_SEND_ATDATA */ +#define BS_ATRDPEND 0x040 /* urb_cmd_in in use */ +#define BS_ATWRPEND 0x080 /* urb_cmd_out in use */ +#define BS_SUSPEND 0x100 /* USB port suspended */ +#define BS_RESETTING 0x200 /* waiting for HD_RESET_INTERRUPT_PIPE_ACK */ + + +static struct gigaset_driver *driver; + +/* usb specific object needed to register this driver with the usb subsystem */ +static struct usb_driver gigaset_usb_driver = { + .name = GIGASET_MODULENAME, + .probe = gigaset_probe, + .disconnect = gigaset_disconnect, + .id_table = gigaset_table, + .suspend = gigaset_suspend, + .resume = gigaset_resume, + .reset_resume = gigaset_post_reset, + .pre_reset = gigaset_pre_reset, + .post_reset = gigaset_post_reset, + .disable_hub_initiated_lpm = 1, +}; + +/* get message text for usb_submit_urb return code + */ +static char *get_usb_rcmsg(int rc) +{ + static char unkmsg[28]; + + switch (rc) { + case 0: + return "success"; + case -ENOMEM: + return "out of memory"; + case -ENODEV: + return "device not present"; + case -ENOENT: + return "endpoint not present"; + case -ENXIO: + return "URB type not supported"; + case -EINVAL: + return "invalid argument"; + case -EAGAIN: + return "start frame too early or too much scheduled"; + case -EFBIG: + return "too many isoc frames requested"; + case -EPIPE: + return "endpoint stalled"; + case -EMSGSIZE: + return "invalid packet size"; + case -ENOSPC: + return "would overcommit USB bandwidth"; + case -ESHUTDOWN: + return "device shut down"; + case -EPERM: + return "reject flag set"; + case -EHOSTUNREACH: + return "device suspended"; + default: + snprintf(unkmsg, sizeof(unkmsg), "unknown error %d", rc); + return unkmsg; + } +} + +/* get message text for USB status code + */ +static char *get_usb_statmsg(int status) +{ + static char unkmsg[28]; + + switch (status) { + case 0: + return "success"; + case -ENOENT: + return "unlinked (sync)"; + case -EINPROGRESS: + return "URB still pending"; + case -EPROTO: + return "bitstuff error, timeout, or unknown USB error"; + case -EILSEQ: + return "CRC mismatch, timeout, or unknown USB error"; + case -ETIME: + return "USB response timeout"; + case -EPIPE: + return "endpoint stalled"; + case -ECOMM: + return "IN buffer overrun"; + case -ENOSR: + return "OUT buffer underrun"; + case -EOVERFLOW: + return "endpoint babble"; + case -EREMOTEIO: + return "short packet"; + case -ENODEV: + return "device removed"; + case -EXDEV: + return "partial isoc transfer"; + case -EINVAL: + return "ISO madness"; + case -ECONNRESET: + return "unlinked (async)"; + case -ESHUTDOWN: + return "device shut down"; + default: + snprintf(unkmsg, sizeof(unkmsg), "unknown status %d", status); + return unkmsg; + } +} + +/* usb_pipetype_str + * retrieve string representation of USB pipe type + */ +static inline char *usb_pipetype_str(int pipe) +{ + if (usb_pipeisoc(pipe)) + return "Isoc"; + if (usb_pipeint(pipe)) + return "Int"; + if (usb_pipecontrol(pipe)) + return "Ctrl"; + if (usb_pipebulk(pipe)) + return "Bulk"; + return "?"; +} + +/* dump_urb + * write content of URB to syslog for debugging + */ +static inline void dump_urb(enum debuglevel level, const char *tag, + struct urb *urb) +{ +#ifdef CONFIG_GIGASET_DEBUG + int i; + gig_dbg(level, "%s urb(0x%08lx)->{", tag, (unsigned long) urb); + if (urb) { + gig_dbg(level, + " dev=0x%08lx, pipe=%s:EP%d/DV%d:%s, " + "hcpriv=0x%08lx, transfer_flags=0x%x,", + (unsigned long) urb->dev, + usb_pipetype_str(urb->pipe), + usb_pipeendpoint(urb->pipe), usb_pipedevice(urb->pipe), + usb_pipein(urb->pipe) ? "in" : "out", + (unsigned long) urb->hcpriv, + urb->transfer_flags); + gig_dbg(level, + " transfer_buffer=0x%08lx[%d], actual_length=%d, " + "setup_packet=0x%08lx,", + (unsigned long) urb->transfer_buffer, + urb->transfer_buffer_length, urb->actual_length, + (unsigned long) urb->setup_packet); + gig_dbg(level, + " start_frame=%d, number_of_packets=%d, interval=%d, " + "error_count=%d,", + urb->start_frame, urb->number_of_packets, urb->interval, + urb->error_count); + gig_dbg(level, + " context=0x%08lx, complete=0x%08lx, " + "iso_frame_desc[]={", + (unsigned long) urb->context, + (unsigned long) urb->complete); + for (i = 0; i < urb->number_of_packets; i++) { + struct usb_iso_packet_descriptor *pifd + = &urb->iso_frame_desc[i]; + gig_dbg(level, + " {offset=%u, length=%u, actual_length=%u, " + "status=%u}", + pifd->offset, pifd->length, pifd->actual_length, + pifd->status); + } + } + gig_dbg(level, "}}"); +#endif +} + +/* read/set modem control bits etc. (m10x only) */ +static int gigaset_set_modem_ctrl(struct cardstate *cs, unsigned old_state, + unsigned new_state) +{ + return -EINVAL; +} + +static int gigaset_baud_rate(struct cardstate *cs, unsigned cflag) +{ + return -EINVAL; +} + +static int gigaset_set_line_ctrl(struct cardstate *cs, unsigned cflag) +{ + return -EINVAL; +} + +/* set/clear bits in base connection state, return previous state + */ +static inline int update_basstate(struct bas_cardstate *ucs, + int set, int clear) +{ + unsigned long flags; + int state; + + spin_lock_irqsave(&ucs->lock, flags); + state = ucs->basstate; + ucs->basstate = (state & ~clear) | set; + spin_unlock_irqrestore(&ucs->lock, flags); + return state; +} + +/* error_hangup + * hang up any existing connection because of an unrecoverable error + * This function may be called from any context and takes care of scheduling + * the necessary actions for execution outside of interrupt context. + * cs->lock must not be held. + * argument: + * B channel control structure + */ +static inline void error_hangup(struct bc_state *bcs) +{ + struct cardstate *cs = bcs->cs; + + gigaset_add_event(cs, &bcs->at_state, EV_HUP, NULL, 0, NULL); + gigaset_schedule_event(cs); +} + +/* error_reset + * reset Gigaset device because of an unrecoverable error + * This function may be called from any context, and takes care of + * scheduling the necessary actions for execution outside of interrupt context. + * cs->hw.bas->lock must not be held. + * argument: + * controller state structure + */ +static inline void error_reset(struct cardstate *cs) +{ + /* reset interrupt pipe to recover (ignore errors) */ + update_basstate(cs->hw.bas, BS_RESETTING, 0); + if (req_submit(cs->bcs, HD_RESET_INTERRUPT_PIPE, 0, BAS_TIMEOUT)) + /* submission failed, escalate to USB port reset */ + usb_queue_reset_device(cs->hw.bas->interface); +} + +/* check_pending + * check for completion of pending control request + * parameter: + * ucs hardware specific controller state structure + */ +static void check_pending(struct bas_cardstate *ucs) +{ + unsigned long flags; + + spin_lock_irqsave(&ucs->lock, flags); + switch (ucs->pending) { + case 0: + break; + case HD_OPEN_ATCHANNEL: + if (ucs->basstate & BS_ATOPEN) + ucs->pending = 0; + break; + case HD_OPEN_B1CHANNEL: + if (ucs->basstate & BS_B1OPEN) + ucs->pending = 0; + break; + case HD_OPEN_B2CHANNEL: + if (ucs->basstate & BS_B2OPEN) + ucs->pending = 0; + break; + case HD_CLOSE_ATCHANNEL: + if (!(ucs->basstate & BS_ATOPEN)) + ucs->pending = 0; + break; + case HD_CLOSE_B1CHANNEL: + if (!(ucs->basstate & BS_B1OPEN)) + ucs->pending = 0; + break; + case HD_CLOSE_B2CHANNEL: + if (!(ucs->basstate & BS_B2OPEN)) + ucs->pending = 0; + break; + case HD_DEVICE_INIT_ACK: /* no reply expected */ + ucs->pending = 0; + break; + case HD_RESET_INTERRUPT_PIPE: + if (!(ucs->basstate & BS_RESETTING)) + ucs->pending = 0; + break; + /* + * HD_READ_ATMESSAGE and HD_WRITE_ATMESSAGE are handled separately + * and should never end up here + */ + default: + dev_warn(&ucs->interface->dev, + "unknown pending request 0x%02x cleared\n", + ucs->pending); + ucs->pending = 0; + } + + if (!ucs->pending) + del_timer(&ucs->timer_ctrl); + + spin_unlock_irqrestore(&ucs->lock, flags); +} + +/* cmd_in_timeout + * timeout routine for command input request + * argument: + * controller state structure + */ +static void cmd_in_timeout(struct timer_list *t) +{ + struct bas_cardstate *ucs = from_timer(ucs, t, timer_cmd_in); + struct cardstate *cs = ucs->cs; + int rc; + + if (!ucs->rcvbuf_size) { + gig_dbg(DEBUG_USBREQ, "%s: no receive in progress", __func__); + return; + } + + if (ucs->retry_cmd_in++ >= BAS_RETRY) { + dev_err(cs->dev, + "control read: timeout, giving up after %d tries\n", + ucs->retry_cmd_in); + kfree(ucs->rcvbuf); + ucs->rcvbuf = NULL; + ucs->rcvbuf_size = 0; + error_reset(cs); + return; + } + + gig_dbg(DEBUG_USBREQ, "%s: timeout, retry %d", + __func__, ucs->retry_cmd_in); + rc = atread_submit(cs, BAS_TIMEOUT); + if (rc < 0) { + kfree(ucs->rcvbuf); + ucs->rcvbuf = NULL; + ucs->rcvbuf_size = 0; + if (rc != -ENODEV) + error_reset(cs); + } +} + +/* read_ctrl_callback + * USB completion handler for control pipe input + * called by the USB subsystem in interrupt context + * parameter: + * urb USB request block + * urb->context = inbuf structure for controller state + */ +static void read_ctrl_callback(struct urb *urb) +{ + struct inbuf_t *inbuf = urb->context; + struct cardstate *cs = inbuf->cs; + struct bas_cardstate *ucs = cs->hw.bas; + int status = urb->status; + unsigned numbytes; + int rc; + + update_basstate(ucs, 0, BS_ATRDPEND); + wake_up(&ucs->waitqueue); + del_timer(&ucs->timer_cmd_in); + + switch (status) { + case 0: /* normal completion */ + numbytes = urb->actual_length; + if (unlikely(numbytes != ucs->rcvbuf_size)) { + dev_warn(cs->dev, + "control read: received %d chars, expected %d\n", + numbytes, ucs->rcvbuf_size); + if (numbytes > ucs->rcvbuf_size) + numbytes = ucs->rcvbuf_size; + } + + /* copy received bytes to inbuf, notify event layer */ + if (gigaset_fill_inbuf(inbuf, ucs->rcvbuf, numbytes)) { + gig_dbg(DEBUG_INTR, "%s-->BH", __func__); + gigaset_schedule_event(cs); + } + break; + + case -ENOENT: /* cancelled */ + case -ECONNRESET: /* cancelled (async) */ + case -EINPROGRESS: /* pending */ + case -ENODEV: /* device removed */ + case -ESHUTDOWN: /* device shut down */ + /* no further action necessary */ + gig_dbg(DEBUG_USBREQ, "%s: %s", + __func__, get_usb_statmsg(status)); + break; + + default: /* other errors: retry */ + if (ucs->retry_cmd_in++ < BAS_RETRY) { + gig_dbg(DEBUG_USBREQ, "%s: %s, retry %d", __func__, + get_usb_statmsg(status), ucs->retry_cmd_in); + rc = atread_submit(cs, BAS_TIMEOUT); + if (rc >= 0) + /* successfully resubmitted, skip freeing */ + return; + if (rc == -ENODEV) + /* disconnect, no further action necessary */ + break; + } + dev_err(cs->dev, "control read: %s, giving up after %d tries\n", + get_usb_statmsg(status), ucs->retry_cmd_in); + error_reset(cs); + } + + /* read finished, free buffer */ + kfree(ucs->rcvbuf); + ucs->rcvbuf = NULL; + ucs->rcvbuf_size = 0; +} + +/* atread_submit + * submit an HD_READ_ATMESSAGE command URB and optionally start a timeout + * parameters: + * cs controller state structure + * timeout timeout in 1/10 sec., 0: none + * return value: + * 0 on success + * -EBUSY if another request is pending + * any URB submission error code + */ +static int atread_submit(struct cardstate *cs, int timeout) +{ + struct bas_cardstate *ucs = cs->hw.bas; + int basstate; + int ret; + + gig_dbg(DEBUG_USBREQ, "-------> HD_READ_ATMESSAGE (%d)", + ucs->rcvbuf_size); + + basstate = update_basstate(ucs, BS_ATRDPEND, 0); + if (basstate & BS_ATRDPEND) { + dev_err(cs->dev, + "could not submit HD_READ_ATMESSAGE: URB busy\n"); + return -EBUSY; + } + + if (basstate & BS_SUSPEND) { + dev_notice(cs->dev, + "HD_READ_ATMESSAGE not submitted, " + "suspend in progress\n"); + update_basstate(ucs, 0, BS_ATRDPEND); + /* treat like disconnect */ + return -ENODEV; + } + + ucs->dr_cmd_in.bRequestType = IN_VENDOR_REQ; + ucs->dr_cmd_in.bRequest = HD_READ_ATMESSAGE; + ucs->dr_cmd_in.wValue = 0; + ucs->dr_cmd_in.wIndex = 0; + ucs->dr_cmd_in.wLength = cpu_to_le16(ucs->rcvbuf_size); + usb_fill_control_urb(ucs->urb_cmd_in, ucs->udev, + usb_rcvctrlpipe(ucs->udev, 0), + (unsigned char *) &ucs->dr_cmd_in, + ucs->rcvbuf, ucs->rcvbuf_size, + read_ctrl_callback, cs->inbuf); + + ret = usb_submit_urb(ucs->urb_cmd_in, GFP_ATOMIC); + if (ret != 0) { + update_basstate(ucs, 0, BS_ATRDPEND); + dev_err(cs->dev, "could not submit HD_READ_ATMESSAGE: %s\n", + get_usb_rcmsg(ret)); + return ret; + } + + if (timeout > 0) { + gig_dbg(DEBUG_USBREQ, "setting timeout of %d/10 secs", timeout); + mod_timer(&ucs->timer_cmd_in, jiffies + timeout * HZ / 10); + } + return 0; +} + +/* int_in_work + * workqueue routine to clear halt on interrupt in endpoint + */ + +static void int_in_work(struct work_struct *work) +{ + struct bas_cardstate *ucs = + container_of(work, struct bas_cardstate, int_in_wq); + struct urb *urb = ucs->urb_int_in; + struct cardstate *cs = urb->context; + int rc; + + /* clear halt condition */ + rc = usb_clear_halt(ucs->udev, urb->pipe); + gig_dbg(DEBUG_USBREQ, "clear_halt: %s", get_usb_rcmsg(rc)); + if (rc == 0) + /* success, resubmit interrupt read URB */ + rc = usb_submit_urb(urb, GFP_ATOMIC); + + switch (rc) { + case 0: /* success */ + case -ENODEV: /* device gone */ + case -EINVAL: /* URB already resubmitted, or terminal badness */ + break; + default: /* failure: try to recover by resetting the device */ + dev_err(cs->dev, "clear halt failed: %s\n", get_usb_rcmsg(rc)); + rc = usb_lock_device_for_reset(ucs->udev, ucs->interface); + if (rc == 0) { + rc = usb_reset_device(ucs->udev); + usb_unlock_device(ucs->udev); + } + } + ucs->retry_int_in = 0; +} + +/* int_in_resubmit + * timer routine for interrupt read delayed resubmit + * argument: + * controller state structure + */ +static void int_in_resubmit(struct timer_list *t) +{ + struct bas_cardstate *ucs = from_timer(ucs, t, timer_int_in); + struct cardstate *cs = ucs->cs; + int rc; + + if (ucs->retry_int_in++ >= BAS_RETRY) { + dev_err(cs->dev, "interrupt read: giving up after %d tries\n", + ucs->retry_int_in); + usb_queue_reset_device(ucs->interface); + return; + } + + gig_dbg(DEBUG_USBREQ, "%s: retry %d", __func__, ucs->retry_int_in); + rc = usb_submit_urb(ucs->urb_int_in, GFP_ATOMIC); + if (rc != 0 && rc != -ENODEV) { + dev_err(cs->dev, "could not resubmit interrupt URB: %s\n", + get_usb_rcmsg(rc)); + usb_queue_reset_device(ucs->interface); + } +} + +/* read_int_callback + * USB completion handler for interrupt pipe input + * called by the USB subsystem in interrupt context + * parameter: + * urb USB request block + * urb->context = controller state structure + */ +static void read_int_callback(struct urb *urb) +{ + struct cardstate *cs = urb->context; + struct bas_cardstate *ucs = cs->hw.bas; + struct bc_state *bcs; + int status = urb->status; + unsigned long flags; + int rc; + unsigned l; + int channel; + + switch (status) { + case 0: /* success */ + ucs->retry_int_in = 0; + break; + case -EPIPE: /* endpoint stalled */ + schedule_work(&ucs->int_in_wq); + /* fall through */ + case -ENOENT: /* cancelled */ + case -ECONNRESET: /* cancelled (async) */ + case -EINPROGRESS: /* pending */ + case -ENODEV: /* device removed */ + case -ESHUTDOWN: /* device shut down */ + /* no further action necessary */ + gig_dbg(DEBUG_USBREQ, "%s: %s", + __func__, get_usb_statmsg(status)); + return; + case -EPROTO: /* protocol error or unplug */ + case -EILSEQ: + case -ETIME: + /* resubmit after delay */ + gig_dbg(DEBUG_USBREQ, "%s: %s", + __func__, get_usb_statmsg(status)); + mod_timer(&ucs->timer_int_in, jiffies + HZ / 10); + return; + default: /* other errors: just resubmit */ + dev_warn(cs->dev, "interrupt read: %s\n", + get_usb_statmsg(status)); + goto resubmit; + } + + /* drop incomplete packets even if the missing bytes wouldn't matter */ + if (unlikely(urb->actual_length < IP_MSGSIZE)) { + dev_warn(cs->dev, "incomplete interrupt packet (%d bytes)\n", + urb->actual_length); + goto resubmit; + } + + l = (unsigned) ucs->int_in_buf[1] + + (((unsigned) ucs->int_in_buf[2]) << 8); + + gig_dbg(DEBUG_USBREQ, "<-------%d: 0x%02x (%u [0x%02x 0x%02x])", + urb->actual_length, (int)ucs->int_in_buf[0], l, + (int)ucs->int_in_buf[1], (int)ucs->int_in_buf[2]); + + channel = 0; + + switch (ucs->int_in_buf[0]) { + case HD_DEVICE_INIT_OK: + update_basstate(ucs, BS_INIT, 0); + break; + + case HD_READY_SEND_ATDATA: + del_timer(&ucs->timer_atrdy); + update_basstate(ucs, BS_ATREADY, BS_ATTIMER); + start_cbsend(cs); + break; + + case HD_OPEN_B2CHANNEL_ACK: + ++channel; + /* fall through */ + case HD_OPEN_B1CHANNEL_ACK: + bcs = cs->bcs + channel; + update_basstate(ucs, BS_B1OPEN << channel, 0); + gigaset_bchannel_up(bcs); + break; + + case HD_OPEN_ATCHANNEL_ACK: + update_basstate(ucs, BS_ATOPEN, 0); + start_cbsend(cs); + break; + + case HD_CLOSE_B2CHANNEL_ACK: + ++channel; + /* fall through */ + case HD_CLOSE_B1CHANNEL_ACK: + bcs = cs->bcs + channel; + update_basstate(ucs, 0, BS_B1OPEN << channel); + stopurbs(bcs->hw.bas); + gigaset_bchannel_down(bcs); + break; + + case HD_CLOSE_ATCHANNEL_ACK: + update_basstate(ucs, 0, BS_ATOPEN); + break; + + case HD_B2_FLOW_CONTROL: + ++channel; + /* fall through */ + case HD_B1_FLOW_CONTROL: + bcs = cs->bcs + channel; + atomic_add((l - BAS_NORMFRAME) * BAS_CORRFRAMES, + &bcs->hw.bas->corrbytes); + gig_dbg(DEBUG_ISO, + "Flow control (channel %d, sub %d): 0x%02x => %d", + channel, bcs->hw.bas->numsub, l, + atomic_read(&bcs->hw.bas->corrbytes)); + break; + + case HD_RECEIVEATDATA_ACK: /* AT response ready to be received */ + if (!l) { + dev_warn(cs->dev, + "HD_RECEIVEATDATA_ACK with length 0 ignored\n"); + break; + } + spin_lock_irqsave(&cs->lock, flags); + if (ucs->basstate & BS_ATRDPEND) { + spin_unlock_irqrestore(&cs->lock, flags); + dev_warn(cs->dev, + "HD_RECEIVEATDATA_ACK(%d) during HD_READ_ATMESSAGE(%d) ignored\n", + l, ucs->rcvbuf_size); + break; + } + if (ucs->rcvbuf_size) { + /* throw away previous buffer - we have no queue */ + dev_err(cs->dev, + "receive AT data overrun, %d bytes lost\n", + ucs->rcvbuf_size); + kfree(ucs->rcvbuf); + ucs->rcvbuf_size = 0; + } + ucs->rcvbuf = kmalloc(l, GFP_ATOMIC); + if (ucs->rcvbuf == NULL) { + spin_unlock_irqrestore(&cs->lock, flags); + dev_err(cs->dev, "out of memory receiving AT data\n"); + break; + } + ucs->rcvbuf_size = l; + ucs->retry_cmd_in = 0; + rc = atread_submit(cs, BAS_TIMEOUT); + if (rc < 0) { + kfree(ucs->rcvbuf); + ucs->rcvbuf = NULL; + ucs->rcvbuf_size = 0; + } + spin_unlock_irqrestore(&cs->lock, flags); + if (rc < 0 && rc != -ENODEV) + error_reset(cs); + break; + + case HD_RESET_INTERRUPT_PIPE_ACK: + update_basstate(ucs, 0, BS_RESETTING); + dev_notice(cs->dev, "interrupt pipe reset\n"); + break; + + case HD_SUSPEND_END: + gig_dbg(DEBUG_USBREQ, "HD_SUSPEND_END"); + break; + + default: + dev_warn(cs->dev, + "unknown Gigaset signal 0x%02x (%u) ignored\n", + (int) ucs->int_in_buf[0], l); + } + + check_pending(ucs); + wake_up(&ucs->waitqueue); + +resubmit: + rc = usb_submit_urb(urb, GFP_ATOMIC); + if (unlikely(rc != 0 && rc != -ENODEV)) { + dev_err(cs->dev, "could not resubmit interrupt URB: %s\n", + get_usb_rcmsg(rc)); + error_reset(cs); + } +} + +/* read_iso_callback + * USB completion handler for B channel isochronous input + * called by the USB subsystem in interrupt context + * parameter: + * urb USB request block of completed request + * urb->context = bc_state structure + */ +static void read_iso_callback(struct urb *urb) +{ + struct bc_state *bcs; + struct bas_bc_state *ubc; + int status = urb->status; + unsigned long flags; + int i, rc; + + /* status codes not worth bothering the tasklet with */ + if (unlikely(status == -ENOENT || + status == -ECONNRESET || + status == -EINPROGRESS || + status == -ENODEV || + status == -ESHUTDOWN)) { + gig_dbg(DEBUG_ISO, "%s: %s", + __func__, get_usb_statmsg(status)); + return; + } + + bcs = urb->context; + ubc = bcs->hw.bas; + + spin_lock_irqsave(&ubc->isoinlock, flags); + if (likely(ubc->isoindone == NULL)) { + /* pass URB to tasklet */ + ubc->isoindone = urb; + ubc->isoinstatus = status; + tasklet_hi_schedule(&ubc->rcvd_tasklet); + } else { + /* tasklet still busy, drop data and resubmit URB */ + gig_dbg(DEBUG_ISO, "%s: overrun", __func__); + ubc->loststatus = status; + for (i = 0; i < BAS_NUMFRAMES; i++) { + ubc->isoinlost += urb->iso_frame_desc[i].actual_length; + if (unlikely(urb->iso_frame_desc[i].status != 0 && + urb->iso_frame_desc[i].status != -EINPROGRESS)) + ubc->loststatus = urb->iso_frame_desc[i].status; + urb->iso_frame_desc[i].status = 0; + urb->iso_frame_desc[i].actual_length = 0; + } + if (likely(ubc->running)) { + /* urb->dev is clobbered by USB subsystem */ + urb->dev = bcs->cs->hw.bas->udev; + urb->transfer_flags = URB_ISO_ASAP; + urb->number_of_packets = BAS_NUMFRAMES; + rc = usb_submit_urb(urb, GFP_ATOMIC); + if (unlikely(rc != 0 && rc != -ENODEV)) { + dev_err(bcs->cs->dev, + "could not resubmit isoc read URB: %s\n", + get_usb_rcmsg(rc)); + dump_urb(DEBUG_ISO, "isoc read", urb); + error_hangup(bcs); + } + } + } + spin_unlock_irqrestore(&ubc->isoinlock, flags); +} + +/* write_iso_callback + * USB completion handler for B channel isochronous output + * called by the USB subsystem in interrupt context + * parameter: + * urb USB request block of completed request + * urb->context = isow_urbctx_t structure + */ +static void write_iso_callback(struct urb *urb) +{ + struct isow_urbctx_t *ucx; + struct bas_bc_state *ubc; + int status = urb->status; + unsigned long flags; + + /* status codes not worth bothering the tasklet with */ + if (unlikely(status == -ENOENT || + status == -ECONNRESET || + status == -EINPROGRESS || + status == -ENODEV || + status == -ESHUTDOWN)) { + gig_dbg(DEBUG_ISO, "%s: %s", + __func__, get_usb_statmsg(status)); + return; + } + + /* pass URB context to tasklet */ + ucx = urb->context; + ubc = ucx->bcs->hw.bas; + ucx->status = status; + + spin_lock_irqsave(&ubc->isooutlock, flags); + ubc->isooutovfl = ubc->isooutdone; + ubc->isooutdone = ucx; + spin_unlock_irqrestore(&ubc->isooutlock, flags); + tasklet_hi_schedule(&ubc->sent_tasklet); +} + +/* starturbs + * prepare and submit USB request blocks for isochronous input and output + * argument: + * B channel control structure + * return value: + * 0 on success + * < 0 on error (no URBs submitted) + */ +static int starturbs(struct bc_state *bcs) +{ + struct usb_device *udev = bcs->cs->hw.bas->udev; + struct bas_bc_state *ubc = bcs->hw.bas; + struct urb *urb; + int j, k; + int rc; + + /* initialize L2 reception */ + if (bcs->proto2 == L2_HDLC) + bcs->inputstate |= INS_flag_hunt; + + /* submit all isochronous input URBs */ + ubc->running = 1; + for (k = 0; k < BAS_INURBS; k++) { + urb = ubc->isoinurbs[k]; + if (!urb) { + rc = -EFAULT; + goto error; + } + usb_fill_int_urb(urb, udev, + usb_rcvisocpipe(udev, 3 + 2 * bcs->channel), + ubc->isoinbuf + k * BAS_INBUFSIZE, + BAS_INBUFSIZE, read_iso_callback, bcs, + BAS_FRAMETIME); + + urb->transfer_flags = URB_ISO_ASAP; + urb->number_of_packets = BAS_NUMFRAMES; + for (j = 0; j < BAS_NUMFRAMES; j++) { + urb->iso_frame_desc[j].offset = j * BAS_MAXFRAME; + urb->iso_frame_desc[j].length = BAS_MAXFRAME; + urb->iso_frame_desc[j].status = 0; + urb->iso_frame_desc[j].actual_length = 0; + } + + dump_urb(DEBUG_ISO, "Initial isoc read", urb); + rc = usb_submit_urb(urb, GFP_ATOMIC); + if (rc != 0) + goto error; + } + + /* initialize L2 transmission */ + gigaset_isowbuf_init(ubc->isooutbuf, PPP_FLAG); + + /* set up isochronous output URBs for flag idling */ + for (k = 0; k < BAS_OUTURBS; ++k) { + urb = ubc->isoouturbs[k].urb; + if (!urb) { + rc = -EFAULT; + goto error; + } + usb_fill_int_urb(urb, udev, + usb_sndisocpipe(udev, 4 + 2 * bcs->channel), + ubc->isooutbuf->data, + sizeof(ubc->isooutbuf->data), + write_iso_callback, &ubc->isoouturbs[k], + BAS_FRAMETIME); + + urb->transfer_flags = URB_ISO_ASAP; + urb->number_of_packets = BAS_NUMFRAMES; + for (j = 0; j < BAS_NUMFRAMES; ++j) { + urb->iso_frame_desc[j].offset = BAS_OUTBUFSIZE; + urb->iso_frame_desc[j].length = BAS_NORMFRAME; + urb->iso_frame_desc[j].status = 0; + urb->iso_frame_desc[j].actual_length = 0; + } + ubc->isoouturbs[k].limit = -1; + } + + /* keep one URB free, submit the others */ + for (k = 0; k < BAS_OUTURBS - 1; ++k) { + dump_urb(DEBUG_ISO, "Initial isoc write", urb); + rc = usb_submit_urb(ubc->isoouturbs[k].urb, GFP_ATOMIC); + if (rc != 0) + goto error; + } + dump_urb(DEBUG_ISO, "Initial isoc write (free)", urb); + ubc->isooutfree = &ubc->isoouturbs[BAS_OUTURBS - 1]; + ubc->isooutdone = ubc->isooutovfl = NULL; + return 0; +error: + stopurbs(ubc); + return rc; +} + +/* stopurbs + * cancel the USB request blocks for isochronous input and output + * errors are silently ignored + * argument: + * B channel control structure + */ +static void stopurbs(struct bas_bc_state *ubc) +{ + int k, rc; + + ubc->running = 0; + + for (k = 0; k < BAS_INURBS; ++k) { + rc = usb_unlink_urb(ubc->isoinurbs[k]); + gig_dbg(DEBUG_ISO, + "%s: isoc input URB %d unlinked, result = %s", + __func__, k, get_usb_rcmsg(rc)); + } + + for (k = 0; k < BAS_OUTURBS; ++k) { + rc = usb_unlink_urb(ubc->isoouturbs[k].urb); + gig_dbg(DEBUG_ISO, + "%s: isoc output URB %d unlinked, result = %s", + __func__, k, get_usb_rcmsg(rc)); + } +} + +/* Isochronous Write - Bottom Half */ +/* =============================== */ + +/* submit_iso_write_urb + * fill and submit the next isochronous write URB + * parameters: + * ucx context structure containing URB + * return value: + * number of frames submitted in URB + * 0 if URB not submitted because no data available (isooutbuf busy) + * error code < 0 on error + */ +static int submit_iso_write_urb(struct isow_urbctx_t *ucx) +{ + struct urb *urb = ucx->urb; + struct bas_bc_state *ubc = ucx->bcs->hw.bas; + struct usb_iso_packet_descriptor *ifd; + int corrbytes, nframe, rc; + + /* urb->dev is clobbered by USB subsystem */ + urb->dev = ucx->bcs->cs->hw.bas->udev; + urb->transfer_flags = URB_ISO_ASAP; + urb->transfer_buffer = ubc->isooutbuf->data; + urb->transfer_buffer_length = sizeof(ubc->isooutbuf->data); + + for (nframe = 0; nframe < BAS_NUMFRAMES; nframe++) { + ifd = &urb->iso_frame_desc[nframe]; + + /* compute frame length according to flow control */ + ifd->length = BAS_NORMFRAME; + corrbytes = atomic_read(&ubc->corrbytes); + if (corrbytes != 0) { + gig_dbg(DEBUG_ISO, "%s: corrbytes=%d", + __func__, corrbytes); + if (corrbytes > BAS_HIGHFRAME - BAS_NORMFRAME) + corrbytes = BAS_HIGHFRAME - BAS_NORMFRAME; + else if (corrbytes < BAS_LOWFRAME - BAS_NORMFRAME) + corrbytes = BAS_LOWFRAME - BAS_NORMFRAME; + ifd->length += corrbytes; + atomic_add(-corrbytes, &ubc->corrbytes); + } + + /* retrieve block of data to send */ + rc = gigaset_isowbuf_getbytes(ubc->isooutbuf, ifd->length); + if (rc < 0) { + if (rc == -EBUSY) { + gig_dbg(DEBUG_ISO, + "%s: buffer busy at frame %d", + __func__, nframe); + /* tasklet will be restarted from + gigaset_isoc_send_skb() */ + } else { + dev_err(ucx->bcs->cs->dev, + "%s: buffer error %d at frame %d\n", + __func__, rc, nframe); + return rc; + } + break; + } + ifd->offset = rc; + ucx->limit = ubc->isooutbuf->nextread; + ifd->status = 0; + ifd->actual_length = 0; + } + if (unlikely(nframe == 0)) + return 0; /* no data to send */ + urb->number_of_packets = nframe; + + rc = usb_submit_urb(urb, GFP_ATOMIC); + if (unlikely(rc)) { + if (rc == -ENODEV) + /* device removed - give up silently */ + gig_dbg(DEBUG_ISO, "%s: disconnected", __func__); + else + dev_err(ucx->bcs->cs->dev, + "could not submit isoc write URB: %s\n", + get_usb_rcmsg(rc)); + return rc; + } + ++ubc->numsub; + return nframe; +} + +/* write_iso_tasklet + * tasklet scheduled when an isochronous output URB from the Gigaset device + * has completed + * parameter: + * data B channel state structure + */ +static void write_iso_tasklet(unsigned long data) +{ + struct bc_state *bcs = (struct bc_state *) data; + struct bas_bc_state *ubc = bcs->hw.bas; + struct cardstate *cs = bcs->cs; + struct isow_urbctx_t *done, *next, *ovfl; + struct urb *urb; + int status; + struct usb_iso_packet_descriptor *ifd; + unsigned long flags; + int i; + struct sk_buff *skb; + int len; + int rc; + + /* loop while completed URBs arrive in time */ + for (;;) { + if (unlikely(!(ubc->running))) { + gig_dbg(DEBUG_ISO, "%s: not running", __func__); + return; + } + + /* retrieve completed URBs */ + spin_lock_irqsave(&ubc->isooutlock, flags); + done = ubc->isooutdone; + ubc->isooutdone = NULL; + ovfl = ubc->isooutovfl; + ubc->isooutovfl = NULL; + spin_unlock_irqrestore(&ubc->isooutlock, flags); + if (ovfl) { + dev_err(cs->dev, "isoc write underrun\n"); + error_hangup(bcs); + break; + } + if (!done) + break; + + /* submit free URB if available */ + spin_lock_irqsave(&ubc->isooutlock, flags); + next = ubc->isooutfree; + ubc->isooutfree = NULL; + spin_unlock_irqrestore(&ubc->isooutlock, flags); + if (next) { + rc = submit_iso_write_urb(next); + if (unlikely(rc <= 0 && rc != -ENODEV)) { + /* could not submit URB, put it back */ + spin_lock_irqsave(&ubc->isooutlock, flags); + if (ubc->isooutfree == NULL) { + ubc->isooutfree = next; + next = NULL; + } + spin_unlock_irqrestore(&ubc->isooutlock, flags); + if (next) { + /* couldn't put it back */ + dev_err(cs->dev, + "losing isoc write URB\n"); + error_hangup(bcs); + } + } + } + + /* process completed URB */ + urb = done->urb; + status = done->status; + switch (status) { + case -EXDEV: /* partial completion */ + gig_dbg(DEBUG_ISO, "%s: URB partially completed", + __func__); + /* fall through - what's the difference anyway? */ + case 0: /* normal completion */ + /* inspect individual frames + * assumptions (for lack of documentation): + * - actual_length bytes of first frame in error are + * successfully sent + * - all following frames are not sent at all + */ + for (i = 0; i < BAS_NUMFRAMES; i++) { + ifd = &urb->iso_frame_desc[i]; + if (ifd->status || + ifd->actual_length != ifd->length) { + dev_warn(cs->dev, + "isoc write: frame %d[%d/%d]: %s\n", + i, ifd->actual_length, + ifd->length, + get_usb_statmsg(ifd->status)); + break; + } + } + break; + case -EPIPE: /* stall - probably underrun */ + dev_err(cs->dev, "isoc write: stalled\n"); + error_hangup(bcs); + break; + default: /* other errors */ + dev_warn(cs->dev, "isoc write: %s\n", + get_usb_statmsg(status)); + } + + /* mark the write buffer area covered by this URB as free */ + if (done->limit >= 0) + ubc->isooutbuf->read = done->limit; + + /* mark URB as free */ + spin_lock_irqsave(&ubc->isooutlock, flags); + next = ubc->isooutfree; + ubc->isooutfree = done; + spin_unlock_irqrestore(&ubc->isooutlock, flags); + if (next) { + /* only one URB still active - resubmit one */ + rc = submit_iso_write_urb(next); + if (unlikely(rc <= 0 && rc != -ENODEV)) { + /* couldn't submit */ + error_hangup(bcs); + } + } + } + + /* process queued SKBs */ + while ((skb = skb_dequeue(&bcs->squeue))) { + /* copy to output buffer, doing L2 encapsulation */ + len = skb->len; + if (gigaset_isoc_buildframe(bcs, skb->data, len) == -EAGAIN) { + /* insufficient buffer space, push back onto queue */ + skb_queue_head(&bcs->squeue, skb); + gig_dbg(DEBUG_ISO, "%s: skb requeued, qlen=%d", + __func__, skb_queue_len(&bcs->squeue)); + break; + } + skb_pull(skb, len); + gigaset_skb_sent(bcs, skb); + dev_kfree_skb_any(skb); + } +} + +/* Isochronous Read - Bottom Half */ +/* ============================== */ + +/* read_iso_tasklet + * tasklet scheduled when an isochronous input URB from the Gigaset device + * has completed + * parameter: + * data B channel state structure + */ +static void read_iso_tasklet(unsigned long data) +{ + struct bc_state *bcs = (struct bc_state *) data; + struct bas_bc_state *ubc = bcs->hw.bas; + struct cardstate *cs = bcs->cs; + struct urb *urb; + int status; + struct usb_iso_packet_descriptor *ifd; + char *rcvbuf; + unsigned long flags; + int totleft, numbytes, offset, frame, rc; + + /* loop while more completed URBs arrive in the meantime */ + for (;;) { + /* retrieve URB */ + spin_lock_irqsave(&ubc->isoinlock, flags); + urb = ubc->isoindone; + if (!urb) { + spin_unlock_irqrestore(&ubc->isoinlock, flags); + return; + } + status = ubc->isoinstatus; + ubc->isoindone = NULL; + if (unlikely(ubc->loststatus != -EINPROGRESS)) { + dev_warn(cs->dev, + "isoc read overrun, URB dropped (status: %s, %d bytes)\n", + get_usb_statmsg(ubc->loststatus), + ubc->isoinlost); + ubc->loststatus = -EINPROGRESS; + } + spin_unlock_irqrestore(&ubc->isoinlock, flags); + + if (unlikely(!(ubc->running))) { + gig_dbg(DEBUG_ISO, + "%s: channel not running, " + "dropped URB with status: %s", + __func__, get_usb_statmsg(status)); + return; + } + + switch (status) { + case 0: /* normal completion */ + break; + case -EXDEV: /* inspect individual frames + (we do that anyway) */ + gig_dbg(DEBUG_ISO, "%s: URB partially completed", + __func__); + break; + case -ENOENT: + case -ECONNRESET: + case -EINPROGRESS: + gig_dbg(DEBUG_ISO, "%s: %s", + __func__, get_usb_statmsg(status)); + continue; /* -> skip */ + case -EPIPE: + dev_err(cs->dev, "isoc read: stalled\n"); + error_hangup(bcs); + continue; /* -> skip */ + default: /* other error */ + dev_warn(cs->dev, "isoc read: %s\n", + get_usb_statmsg(status)); + goto error; + } + + rcvbuf = urb->transfer_buffer; + totleft = urb->actual_length; + for (frame = 0; totleft > 0 && frame < BAS_NUMFRAMES; frame++) { + ifd = &urb->iso_frame_desc[frame]; + numbytes = ifd->actual_length; + switch (ifd->status) { + case 0: /* success */ + break; + case -EPROTO: /* protocol error or unplug */ + case -EILSEQ: + case -ETIME: + /* probably just disconnected, ignore */ + gig_dbg(DEBUG_ISO, + "isoc read: frame %d[%d]: %s\n", + frame, numbytes, + get_usb_statmsg(ifd->status)); + break; + default: /* other error */ + /* report, assume transferred bytes are ok */ + dev_warn(cs->dev, + "isoc read: frame %d[%d]: %s\n", + frame, numbytes, + get_usb_statmsg(ifd->status)); + } + if (unlikely(numbytes > BAS_MAXFRAME)) + dev_warn(cs->dev, + "isoc read: frame %d[%d]: %s\n", + frame, numbytes, + "exceeds max frame size"); + if (unlikely(numbytes > totleft)) { + dev_warn(cs->dev, + "isoc read: frame %d[%d]: %s\n", + frame, numbytes, + "exceeds total transfer length"); + numbytes = totleft; + } + offset = ifd->offset; + if (unlikely(offset + numbytes > BAS_INBUFSIZE)) { + dev_warn(cs->dev, + "isoc read: frame %d[%d]: %s\n", + frame, numbytes, + "exceeds end of buffer"); + numbytes = BAS_INBUFSIZE - offset; + } + gigaset_isoc_receive(rcvbuf + offset, numbytes, bcs); + totleft -= numbytes; + } + if (unlikely(totleft > 0)) + dev_warn(cs->dev, "isoc read: %d data bytes missing\n", + totleft); + +error: + /* URB processed, resubmit */ + for (frame = 0; frame < BAS_NUMFRAMES; frame++) { + urb->iso_frame_desc[frame].status = 0; + urb->iso_frame_desc[frame].actual_length = 0; + } + /* urb->dev is clobbered by USB subsystem */ + urb->dev = bcs->cs->hw.bas->udev; + urb->transfer_flags = URB_ISO_ASAP; + urb->number_of_packets = BAS_NUMFRAMES; + rc = usb_submit_urb(urb, GFP_ATOMIC); + if (unlikely(rc != 0 && rc != -ENODEV)) { + dev_err(cs->dev, + "could not resubmit isoc read URB: %s\n", + get_usb_rcmsg(rc)); + dump_urb(DEBUG_ISO, "resubmit isoc read", urb); + error_hangup(bcs); + } + } +} + +/* Channel Operations */ +/* ================== */ + +/* req_timeout + * timeout routine for control output request + * argument: + * controller state structure + */ +static void req_timeout(struct timer_list *t) +{ + struct bas_cardstate *ucs = from_timer(ucs, t, timer_ctrl); + struct cardstate *cs = ucs->cs; + int pending; + unsigned long flags; + + check_pending(ucs); + + spin_lock_irqsave(&ucs->lock, flags); + pending = ucs->pending; + ucs->pending = 0; + spin_unlock_irqrestore(&ucs->lock, flags); + + switch (pending) { + case 0: /* no pending request */ + gig_dbg(DEBUG_USBREQ, "%s: no request pending", __func__); + break; + + case HD_OPEN_ATCHANNEL: + dev_err(cs->dev, "timeout opening AT channel\n"); + error_reset(cs); + break; + + case HD_OPEN_B1CHANNEL: + dev_err(cs->dev, "timeout opening channel 1\n"); + error_hangup(&cs->bcs[0]); + break; + + case HD_OPEN_B2CHANNEL: + dev_err(cs->dev, "timeout opening channel 2\n"); + error_hangup(&cs->bcs[1]); + break; + + case HD_CLOSE_ATCHANNEL: + dev_err(cs->dev, "timeout closing AT channel\n"); + error_reset(cs); + break; + + case HD_CLOSE_B1CHANNEL: + dev_err(cs->dev, "timeout closing channel 1\n"); + error_reset(cs); + break; + + case HD_CLOSE_B2CHANNEL: + dev_err(cs->dev, "timeout closing channel 2\n"); + error_reset(cs); + break; + + case HD_RESET_INTERRUPT_PIPE: + /* error recovery escalation */ + dev_err(cs->dev, + "reset interrupt pipe timeout, attempting USB reset\n"); + usb_queue_reset_device(ucs->interface); + break; + + default: + dev_warn(cs->dev, "request 0x%02x timed out, clearing\n", + pending); + } + + wake_up(&ucs->waitqueue); +} + +/* write_ctrl_callback + * USB completion handler for control pipe output + * called by the USB subsystem in interrupt context + * parameter: + * urb USB request block of completed request + * urb->context = hardware specific controller state structure + */ +static void write_ctrl_callback(struct urb *urb) +{ + struct bas_cardstate *ucs = urb->context; + int status = urb->status; + int rc; + unsigned long flags; + + /* check status */ + switch (status) { + case 0: /* normal completion */ + spin_lock_irqsave(&ucs->lock, flags); + switch (ucs->pending) { + case HD_DEVICE_INIT_ACK: /* no reply expected */ + del_timer(&ucs->timer_ctrl); + ucs->pending = 0; + break; + } + spin_unlock_irqrestore(&ucs->lock, flags); + return; + + case -ENOENT: /* cancelled */ + case -ECONNRESET: /* cancelled (async) */ + case -EINPROGRESS: /* pending */ + case -ENODEV: /* device removed */ + case -ESHUTDOWN: /* device shut down */ + /* ignore silently */ + gig_dbg(DEBUG_USBREQ, "%s: %s", + __func__, get_usb_statmsg(status)); + break; + + default: /* any failure */ + /* don't retry if suspend requested */ + if (++ucs->retry_ctrl > BAS_RETRY || + (ucs->basstate & BS_SUSPEND)) { + dev_err(&ucs->interface->dev, + "control request 0x%02x failed: %s\n", + ucs->dr_ctrl.bRequest, + get_usb_statmsg(status)); + break; /* give up */ + } + dev_notice(&ucs->interface->dev, + "control request 0x%02x: %s, retry %d\n", + ucs->dr_ctrl.bRequest, get_usb_statmsg(status), + ucs->retry_ctrl); + /* urb->dev is clobbered by USB subsystem */ + urb->dev = ucs->udev; + rc = usb_submit_urb(urb, GFP_ATOMIC); + if (unlikely(rc)) { + dev_err(&ucs->interface->dev, + "could not resubmit request 0x%02x: %s\n", + ucs->dr_ctrl.bRequest, get_usb_rcmsg(rc)); + break; + } + /* resubmitted */ + return; + } + + /* failed, clear pending request */ + spin_lock_irqsave(&ucs->lock, flags); + del_timer(&ucs->timer_ctrl); + ucs->pending = 0; + spin_unlock_irqrestore(&ucs->lock, flags); + wake_up(&ucs->waitqueue); +} + +/* req_submit + * submit a control output request without message buffer to the Gigaset base + * and optionally start a timeout + * parameters: + * bcs B channel control structure + * req control request code (HD_*) + * val control request parameter value (set to 0 if unused) + * timeout timeout in seconds (0: no timeout) + * return value: + * 0 on success + * -EBUSY if another request is pending + * any URB submission error code + */ +static int req_submit(struct bc_state *bcs, int req, int val, int timeout) +{ + struct bas_cardstate *ucs = bcs->cs->hw.bas; + int ret; + unsigned long flags; + + gig_dbg(DEBUG_USBREQ, "-------> 0x%02x (%d)", req, val); + + spin_lock_irqsave(&ucs->lock, flags); + if (ucs->pending) { + spin_unlock_irqrestore(&ucs->lock, flags); + dev_err(bcs->cs->dev, + "submission of request 0x%02x failed: " + "request 0x%02x still pending\n", + req, ucs->pending); + return -EBUSY; + } + + ucs->dr_ctrl.bRequestType = OUT_VENDOR_REQ; + ucs->dr_ctrl.bRequest = req; + ucs->dr_ctrl.wValue = cpu_to_le16(val); + ucs->dr_ctrl.wIndex = 0; + ucs->dr_ctrl.wLength = 0; + usb_fill_control_urb(ucs->urb_ctrl, ucs->udev, + usb_sndctrlpipe(ucs->udev, 0), + (unsigned char *) &ucs->dr_ctrl, NULL, 0, + write_ctrl_callback, ucs); + ucs->retry_ctrl = 0; + ret = usb_submit_urb(ucs->urb_ctrl, GFP_ATOMIC); + if (unlikely(ret)) { + dev_err(bcs->cs->dev, "could not submit request 0x%02x: %s\n", + req, get_usb_rcmsg(ret)); + spin_unlock_irqrestore(&ucs->lock, flags); + return ret; + } + ucs->pending = req; + + if (timeout > 0) { + gig_dbg(DEBUG_USBREQ, "setting timeout of %d/10 secs", timeout); + mod_timer(&ucs->timer_ctrl, jiffies + timeout * HZ / 10); + } + + spin_unlock_irqrestore(&ucs->lock, flags); + return 0; +} + +/* gigaset_init_bchannel + * called by common.c to connect a B channel + * initialize isochronous I/O and tell the Gigaset base to open the channel + * argument: + * B channel control structure + * return value: + * 0 on success, error code < 0 on error + */ +static int gigaset_init_bchannel(struct bc_state *bcs) +{ + struct cardstate *cs = bcs->cs; + int req, ret; + unsigned long flags; + + spin_lock_irqsave(&cs->lock, flags); + if (unlikely(!cs->connected)) { + gig_dbg(DEBUG_USBREQ, "%s: not connected", __func__); + spin_unlock_irqrestore(&cs->lock, flags); + return -ENODEV; + } + + if (cs->hw.bas->basstate & BS_SUSPEND) { + dev_notice(cs->dev, + "not starting isoc I/O, suspend in progress\n"); + spin_unlock_irqrestore(&cs->lock, flags); + return -EHOSTUNREACH; + } + + ret = starturbs(bcs); + if (ret < 0) { + spin_unlock_irqrestore(&cs->lock, flags); + dev_err(cs->dev, + "could not start isoc I/O for channel B%d: %s\n", + bcs->channel + 1, + ret == -EFAULT ? "null URB" : get_usb_rcmsg(ret)); + if (ret != -ENODEV) + error_hangup(bcs); + return ret; + } + + req = bcs->channel ? HD_OPEN_B2CHANNEL : HD_OPEN_B1CHANNEL; + ret = req_submit(bcs, req, 0, BAS_TIMEOUT); + if (ret < 0) { + dev_err(cs->dev, "could not open channel B%d\n", + bcs->channel + 1); + stopurbs(bcs->hw.bas); + } + + spin_unlock_irqrestore(&cs->lock, flags); + if (ret < 0 && ret != -ENODEV) + error_hangup(bcs); + return ret; +} + +/* gigaset_close_bchannel + * called by common.c to disconnect a B channel + * tell the Gigaset base to close the channel + * stopping isochronous I/O and LL notification will be done when the + * acknowledgement for the close arrives + * argument: + * B channel control structure + * return value: + * 0 on success, error code < 0 on error + */ +static int gigaset_close_bchannel(struct bc_state *bcs) +{ + struct cardstate *cs = bcs->cs; + int req, ret; + unsigned long flags; + + spin_lock_irqsave(&cs->lock, flags); + if (unlikely(!cs->connected)) { + spin_unlock_irqrestore(&cs->lock, flags); + gig_dbg(DEBUG_USBREQ, "%s: not connected", __func__); + return -ENODEV; + } + + if (!(cs->hw.bas->basstate & (bcs->channel ? BS_B2OPEN : BS_B1OPEN))) { + /* channel not running: just signal common.c */ + spin_unlock_irqrestore(&cs->lock, flags); + gigaset_bchannel_down(bcs); + return 0; + } + + /* channel running: tell device to close it */ + req = bcs->channel ? HD_CLOSE_B2CHANNEL : HD_CLOSE_B1CHANNEL; + ret = req_submit(bcs, req, 0, BAS_TIMEOUT); + if (ret < 0) + dev_err(cs->dev, "closing channel B%d failed\n", + bcs->channel + 1); + + spin_unlock_irqrestore(&cs->lock, flags); + return ret; +} + +/* Device Operations */ +/* ================= */ + +/* complete_cb + * unqueue first command buffer from queue, waking any sleepers + * must be called with cs->cmdlock held + * parameter: + * cs controller state structure + */ +static void complete_cb(struct cardstate *cs) +{ + struct cmdbuf_t *cb = cs->cmdbuf; + + /* unqueue completed buffer */ + cs->cmdbytes -= cs->curlen; + gig_dbg(DEBUG_OUTPUT, "write_command: sent %u bytes, %u left", + cs->curlen, cs->cmdbytes); + if (cb->next != NULL) { + cs->cmdbuf = cb->next; + cs->cmdbuf->prev = NULL; + cs->curlen = cs->cmdbuf->len; + } else { + cs->cmdbuf = NULL; + cs->lastcmdbuf = NULL; + cs->curlen = 0; + } + + if (cb->wake_tasklet) + tasklet_schedule(cb->wake_tasklet); + + kfree(cb); +} + +/* write_command_callback + * USB completion handler for AT command transmission + * called by the USB subsystem in interrupt context + * parameter: + * urb USB request block of completed request + * urb->context = controller state structure + */ +static void write_command_callback(struct urb *urb) +{ + struct cardstate *cs = urb->context; + struct bas_cardstate *ucs = cs->hw.bas; + int status = urb->status; + unsigned long flags; + + update_basstate(ucs, 0, BS_ATWRPEND); + wake_up(&ucs->waitqueue); + + /* check status */ + switch (status) { + case 0: /* normal completion */ + break; + case -ENOENT: /* cancelled */ + case -ECONNRESET: /* cancelled (async) */ + case -EINPROGRESS: /* pending */ + case -ENODEV: /* device removed */ + case -ESHUTDOWN: /* device shut down */ + /* ignore silently */ + gig_dbg(DEBUG_USBREQ, "%s: %s", + __func__, get_usb_statmsg(status)); + return; + default: /* any failure */ + if (++ucs->retry_cmd_out > BAS_RETRY) { + dev_warn(cs->dev, + "command write: %s, " + "giving up after %d retries\n", + get_usb_statmsg(status), + ucs->retry_cmd_out); + break; + } + if (ucs->basstate & BS_SUSPEND) { + dev_warn(cs->dev, + "command write: %s, " + "won't retry - suspend requested\n", + get_usb_statmsg(status)); + break; + } + if (cs->cmdbuf == NULL) { + dev_warn(cs->dev, + "command write: %s, " + "cannot retry - cmdbuf gone\n", + get_usb_statmsg(status)); + break; + } + dev_notice(cs->dev, "command write: %s, retry %d\n", + get_usb_statmsg(status), ucs->retry_cmd_out); + if (atwrite_submit(cs, cs->cmdbuf->buf, cs->cmdbuf->len) >= 0) + /* resubmitted - bypass regular exit block */ + return; + /* command send failed, assume base still waiting */ + update_basstate(ucs, BS_ATREADY, 0); + } + + spin_lock_irqsave(&cs->cmdlock, flags); + if (cs->cmdbuf != NULL) + complete_cb(cs); + spin_unlock_irqrestore(&cs->cmdlock, flags); +} + +/* atrdy_timeout + * timeout routine for AT command transmission + * argument: + * controller state structure + */ +static void atrdy_timeout(struct timer_list *t) +{ + struct bas_cardstate *ucs = from_timer(ucs, t, timer_atrdy); + struct cardstate *cs = ucs->cs; + + dev_warn(cs->dev, "timeout waiting for HD_READY_SEND_ATDATA\n"); + + /* fake the missing signal - what else can I do? */ + update_basstate(ucs, BS_ATREADY, BS_ATTIMER); + start_cbsend(cs); +} + +/* atwrite_submit + * submit an HD_WRITE_ATMESSAGE command URB + * parameters: + * cs controller state structure + * buf buffer containing command to send + * len length of command to send + * return value: + * 0 on success + * -EBUSY if another request is pending + * any URB submission error code + */ +static int atwrite_submit(struct cardstate *cs, unsigned char *buf, int len) +{ + struct bas_cardstate *ucs = cs->hw.bas; + int rc; + + gig_dbg(DEBUG_USBREQ, "-------> HD_WRITE_ATMESSAGE (%d)", len); + + if (update_basstate(ucs, BS_ATWRPEND, 0) & BS_ATWRPEND) { + dev_err(cs->dev, + "could not submit HD_WRITE_ATMESSAGE: URB busy\n"); + return -EBUSY; + } + + ucs->dr_cmd_out.bRequestType = OUT_VENDOR_REQ; + ucs->dr_cmd_out.bRequest = HD_WRITE_ATMESSAGE; + ucs->dr_cmd_out.wValue = 0; + ucs->dr_cmd_out.wIndex = 0; + ucs->dr_cmd_out.wLength = cpu_to_le16(len); + usb_fill_control_urb(ucs->urb_cmd_out, ucs->udev, + usb_sndctrlpipe(ucs->udev, 0), + (unsigned char *) &ucs->dr_cmd_out, buf, len, + write_command_callback, cs); + rc = usb_submit_urb(ucs->urb_cmd_out, GFP_ATOMIC); + if (unlikely(rc)) { + update_basstate(ucs, 0, BS_ATWRPEND); + dev_err(cs->dev, "could not submit HD_WRITE_ATMESSAGE: %s\n", + get_usb_rcmsg(rc)); + return rc; + } + + /* submitted successfully, start timeout if necessary */ + if (!(update_basstate(ucs, BS_ATTIMER, BS_ATREADY) & BS_ATTIMER)) { + gig_dbg(DEBUG_OUTPUT, "setting ATREADY timeout of %d/10 secs", + ATRDY_TIMEOUT); + mod_timer(&ucs->timer_atrdy, jiffies + ATRDY_TIMEOUT * HZ / 10); + } + return 0; +} + +/* start_cbsend + * start transmission of AT command queue if necessary + * parameter: + * cs controller state structure + * return value: + * 0 on success + * error code < 0 on error + */ +static int start_cbsend(struct cardstate *cs) +{ + struct cmdbuf_t *cb; + struct bas_cardstate *ucs = cs->hw.bas; + unsigned long flags; + int rc; + int retval = 0; + + /* check if suspend requested */ + if (ucs->basstate & BS_SUSPEND) { + gig_dbg(DEBUG_OUTPUT, "suspending"); + return -EHOSTUNREACH; + } + + /* check if AT channel is open */ + if (!(ucs->basstate & BS_ATOPEN)) { + gig_dbg(DEBUG_OUTPUT, "AT channel not open"); + rc = req_submit(cs->bcs, HD_OPEN_ATCHANNEL, 0, BAS_TIMEOUT); + if (rc < 0) { + /* flush command queue */ + spin_lock_irqsave(&cs->cmdlock, flags); + while (cs->cmdbuf != NULL) + complete_cb(cs); + spin_unlock_irqrestore(&cs->cmdlock, flags); + } + return rc; + } + + /* try to send first command in queue */ + spin_lock_irqsave(&cs->cmdlock, flags); + + while ((cb = cs->cmdbuf) != NULL && (ucs->basstate & BS_ATREADY)) { + ucs->retry_cmd_out = 0; + rc = atwrite_submit(cs, cb->buf, cb->len); + if (unlikely(rc)) { + retval = rc; + complete_cb(cs); + } + } + + spin_unlock_irqrestore(&cs->cmdlock, flags); + return retval; +} + +/* gigaset_write_cmd + * This function is called by the device independent part of the driver + * to transmit an AT command string to the Gigaset device. + * It encapsulates the device specific method for transmission over the + * direct USB connection to the base. + * The command string is added to the queue of commands to send, and + * USB transmission is started if necessary. + * parameters: + * cs controller state structure + * cb command buffer structure + * return value: + * number of bytes queued on success + * error code < 0 on error + */ +static int gigaset_write_cmd(struct cardstate *cs, struct cmdbuf_t *cb) +{ + unsigned long flags; + int rc; + + gigaset_dbg_buffer(cs->mstate != MS_LOCKED ? + DEBUG_TRANSCMD : DEBUG_LOCKCMD, + "CMD Transmit", cb->len, cb->buf); + + /* translate "+++" escape sequence sent as a single separate command + * into "close AT channel" command for error recovery + * The next command will reopen the AT channel automatically. + */ + if (cb->len == 3 && !memcmp(cb->buf, "+++", 3)) { + /* If an HD_RECEIVEATDATA_ACK message remains unhandled + * because of an error, the base never sends another one. + * The response channel is thus effectively blocked. + * Closing and reopening the AT channel does *not* clear + * this condition. + * As a stopgap measure, submit a zero-length AT read + * before closing the AT channel. This has the undocumented + * effect of triggering a new HD_RECEIVEATDATA_ACK message + * from the base if necessary. + * The subsequent AT channel close then discards any pending + * messages. + */ + spin_lock_irqsave(&cs->lock, flags); + if (!(cs->hw.bas->basstate & BS_ATRDPEND)) { + kfree(cs->hw.bas->rcvbuf); + cs->hw.bas->rcvbuf = NULL; + cs->hw.bas->rcvbuf_size = 0; + cs->hw.bas->retry_cmd_in = 0; + atread_submit(cs, 0); + } + spin_unlock_irqrestore(&cs->lock, flags); + + rc = req_submit(cs->bcs, HD_CLOSE_ATCHANNEL, 0, BAS_TIMEOUT); + if (cb->wake_tasklet) + tasklet_schedule(cb->wake_tasklet); + if (!rc) + rc = cb->len; + kfree(cb); + return rc; + } + + spin_lock_irqsave(&cs->cmdlock, flags); + cb->prev = cs->lastcmdbuf; + if (cs->lastcmdbuf) + cs->lastcmdbuf->next = cb; + else { + cs->cmdbuf = cb; + cs->curlen = cb->len; + } + cs->cmdbytes += cb->len; + cs->lastcmdbuf = cb; + spin_unlock_irqrestore(&cs->cmdlock, flags); + + spin_lock_irqsave(&cs->lock, flags); + if (unlikely(!cs->connected)) { + spin_unlock_irqrestore(&cs->lock, flags); + gig_dbg(DEBUG_USBREQ, "%s: not connected", __func__); + /* flush command queue */ + spin_lock_irqsave(&cs->cmdlock, flags); + while (cs->cmdbuf != NULL) + complete_cb(cs); + spin_unlock_irqrestore(&cs->cmdlock, flags); + return -ENODEV; + } + rc = start_cbsend(cs); + spin_unlock_irqrestore(&cs->lock, flags); + return rc < 0 ? rc : cb->len; +} + +/* gigaset_write_room + * tty_driver.write_room interface routine + * return number of characters the driver will accept to be written via + * gigaset_write_cmd + * parameter: + * controller state structure + * return value: + * number of characters + */ +static int gigaset_write_room(struct cardstate *cs) +{ + return IF_WRITEBUF; +} + +/* gigaset_chars_in_buffer + * tty_driver.chars_in_buffer interface routine + * return number of characters waiting to be sent + * parameter: + * controller state structure + * return value: + * number of characters + */ +static int gigaset_chars_in_buffer(struct cardstate *cs) +{ + return cs->cmdbytes; +} + +/* gigaset_brkchars + * implementation of ioctl(GIGASET_BRKCHARS) + * parameter: + * controller state structure + * return value: + * -EINVAL (unimplemented function) + */ +static int gigaset_brkchars(struct cardstate *cs, const unsigned char buf[6]) +{ + return -EINVAL; +} + + +/* Device Initialization/Shutdown */ +/* ============================== */ + +/* Free hardware dependent part of the B channel structure + * parameter: + * bcs B channel structure + */ +static void gigaset_freebcshw(struct bc_state *bcs) +{ + struct bas_bc_state *ubc = bcs->hw.bas; + int i; + + if (!ubc) + return; + + /* kill URBs and tasklets before freeing - better safe than sorry */ + ubc->running = 0; + gig_dbg(DEBUG_INIT, "%s: killing isoc URBs", __func__); + for (i = 0; i < BAS_OUTURBS; ++i) { + usb_kill_urb(ubc->isoouturbs[i].urb); + usb_free_urb(ubc->isoouturbs[i].urb); + } + for (i = 0; i < BAS_INURBS; ++i) { + usb_kill_urb(ubc->isoinurbs[i]); + usb_free_urb(ubc->isoinurbs[i]); + } + tasklet_kill(&ubc->sent_tasklet); + tasklet_kill(&ubc->rcvd_tasklet); + kfree(ubc->isooutbuf); + kfree(ubc); + bcs->hw.bas = NULL; +} + +/* Initialize hardware dependent part of the B channel structure + * parameter: + * bcs B channel structure + * return value: + * 0 on success, error code < 0 on failure + */ +static int gigaset_initbcshw(struct bc_state *bcs) +{ + int i; + struct bas_bc_state *ubc; + + bcs->hw.bas = ubc = kmalloc(sizeof(struct bas_bc_state), GFP_KERNEL); + if (!ubc) { + pr_err("out of memory\n"); + return -ENOMEM; + } + + ubc->running = 0; + atomic_set(&ubc->corrbytes, 0); + spin_lock_init(&ubc->isooutlock); + for (i = 0; i < BAS_OUTURBS; ++i) { + ubc->isoouturbs[i].urb = NULL; + ubc->isoouturbs[i].bcs = bcs; + } + ubc->isooutdone = ubc->isooutfree = ubc->isooutovfl = NULL; + ubc->numsub = 0; + ubc->isooutbuf = kmalloc(sizeof(struct isowbuf_t), GFP_KERNEL); + if (!ubc->isooutbuf) { + pr_err("out of memory\n"); + kfree(ubc); + bcs->hw.bas = NULL; + return -ENOMEM; + } + tasklet_init(&ubc->sent_tasklet, + write_iso_tasklet, (unsigned long) bcs); + + spin_lock_init(&ubc->isoinlock); + for (i = 0; i < BAS_INURBS; ++i) + ubc->isoinurbs[i] = NULL; + ubc->isoindone = NULL; + ubc->loststatus = -EINPROGRESS; + ubc->isoinlost = 0; + ubc->seqlen = 0; + ubc->inbyte = 0; + ubc->inbits = 0; + ubc->goodbytes = 0; + ubc->alignerrs = 0; + ubc->fcserrs = 0; + ubc->frameerrs = 0; + ubc->giants = 0; + ubc->runts = 0; + ubc->aborts = 0; + ubc->shared0s = 0; + ubc->stolen0s = 0; + tasklet_init(&ubc->rcvd_tasklet, + read_iso_tasklet, (unsigned long) bcs); + return 0; +} + +static void gigaset_reinitbcshw(struct bc_state *bcs) +{ + struct bas_bc_state *ubc = bcs->hw.bas; + + bcs->hw.bas->running = 0; + atomic_set(&bcs->hw.bas->corrbytes, 0); + bcs->hw.bas->numsub = 0; + spin_lock_init(&ubc->isooutlock); + spin_lock_init(&ubc->isoinlock); + ubc->loststatus = -EINPROGRESS; +} + +static void gigaset_freecshw(struct cardstate *cs) +{ + /* timers, URBs and rcvbuf are disposed of in disconnect */ + kfree(cs->hw.bas->int_in_buf); + kfree(cs->hw.bas); + cs->hw.bas = NULL; +} + +/* Initialize hardware dependent part of the cardstate structure + * parameter: + * cs cardstate structure + * return value: + * 0 on success, error code < 0 on failure + */ +static int gigaset_initcshw(struct cardstate *cs) +{ + struct bas_cardstate *ucs; + + cs->hw.bas = ucs = kzalloc(sizeof(*ucs), GFP_KERNEL); + if (!ucs) { + pr_err("out of memory\n"); + return -ENOMEM; + } + ucs->int_in_buf = kmalloc(IP_MSGSIZE, GFP_KERNEL); + if (!ucs->int_in_buf) { + kfree(ucs); + pr_err("out of memory\n"); + return -ENOMEM; + } + + spin_lock_init(&ucs->lock); + ucs->cs = cs; + timer_setup(&ucs->timer_ctrl, req_timeout, 0); + timer_setup(&ucs->timer_atrdy, atrdy_timeout, 0); + timer_setup(&ucs->timer_cmd_in, cmd_in_timeout, 0); + timer_setup(&ucs->timer_int_in, int_in_resubmit, 0); + init_waitqueue_head(&ucs->waitqueue); + INIT_WORK(&ucs->int_in_wq, int_in_work); + + return 0; +} + +/* freeurbs + * unlink and deallocate all URBs unconditionally + * caller must make sure that no commands are still in progress + * parameter: + * cs controller state structure + */ +static void freeurbs(struct cardstate *cs) +{ + struct bas_cardstate *ucs = cs->hw.bas; + struct bas_bc_state *ubc; + int i, j; + + gig_dbg(DEBUG_INIT, "%s: killing URBs", __func__); + for (j = 0; j < BAS_CHANNELS; ++j) { + ubc = cs->bcs[j].hw.bas; + for (i = 0; i < BAS_OUTURBS; ++i) { + usb_kill_urb(ubc->isoouturbs[i].urb); + usb_free_urb(ubc->isoouturbs[i].urb); + ubc->isoouturbs[i].urb = NULL; + } + for (i = 0; i < BAS_INURBS; ++i) { + usb_kill_urb(ubc->isoinurbs[i]); + usb_free_urb(ubc->isoinurbs[i]); + ubc->isoinurbs[i] = NULL; + } + } + usb_kill_urb(ucs->urb_int_in); + usb_free_urb(ucs->urb_int_in); + ucs->urb_int_in = NULL; + usb_kill_urb(ucs->urb_cmd_out); + usb_free_urb(ucs->urb_cmd_out); + ucs->urb_cmd_out = NULL; + usb_kill_urb(ucs->urb_cmd_in); + usb_free_urb(ucs->urb_cmd_in); + ucs->urb_cmd_in = NULL; + usb_kill_urb(ucs->urb_ctrl); + usb_free_urb(ucs->urb_ctrl); + ucs->urb_ctrl = NULL; +} + +/* gigaset_probe + * This function is called when a new USB device is connected. + * It checks whether the new device is handled by this driver. + */ +static int gigaset_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_host_interface *hostif; + struct usb_device *udev = interface_to_usbdev(interface); + struct cardstate *cs = NULL; + struct bas_cardstate *ucs = NULL; + struct bas_bc_state *ubc; + struct usb_endpoint_descriptor *endpoint; + int i, j; + int rc; + + gig_dbg(DEBUG_INIT, + "%s: Check if device matches .. (Vendor: 0x%x, Product: 0x%x)", + __func__, le16_to_cpu(udev->descriptor.idVendor), + le16_to_cpu(udev->descriptor.idProduct)); + + /* set required alternate setting */ + hostif = interface->cur_altsetting; + if (hostif->desc.bAlternateSetting != 3) { + gig_dbg(DEBUG_INIT, + "%s: wrong alternate setting %d - trying to switch", + __func__, hostif->desc.bAlternateSetting); + if (usb_set_interface(udev, hostif->desc.bInterfaceNumber, 3) + < 0) { + dev_warn(&udev->dev, "usb_set_interface failed, " + "device %d interface %d altsetting %d\n", + udev->devnum, hostif->desc.bInterfaceNumber, + hostif->desc.bAlternateSetting); + return -ENODEV; + } + hostif = interface->cur_altsetting; + } + + /* Reject application specific interfaces + */ + if (hostif->desc.bInterfaceClass != 255) { + dev_warn(&udev->dev, "%s: bInterfaceClass == %d\n", + __func__, hostif->desc.bInterfaceClass); + return -ENODEV; + } + + if (hostif->desc.bNumEndpoints < 1) + return -ENODEV; + + dev_info(&udev->dev, + "%s: Device matched (Vendor: 0x%x, Product: 0x%x)\n", + __func__, le16_to_cpu(udev->descriptor.idVendor), + le16_to_cpu(udev->descriptor.idProduct)); + + /* allocate memory for our device state and initialize it */ + cs = gigaset_initcs(driver, BAS_CHANNELS, 0, 0, cidmode, + GIGASET_MODULENAME); + if (!cs) + return -ENODEV; + ucs = cs->hw.bas; + + /* save off device structure ptrs for later use */ + usb_get_dev(udev); + ucs->udev = udev; + ucs->interface = interface; + cs->dev = &interface->dev; + + /* allocate URBs: + * - one for the interrupt pipe + * - three for the different uses of the default control pipe + * - three for each isochronous pipe + */ + if (!(ucs->urb_int_in = usb_alloc_urb(0, GFP_KERNEL)) || + !(ucs->urb_cmd_in = usb_alloc_urb(0, GFP_KERNEL)) || + !(ucs->urb_cmd_out = usb_alloc_urb(0, GFP_KERNEL)) || + !(ucs->urb_ctrl = usb_alloc_urb(0, GFP_KERNEL))) + goto allocerr; + + for (j = 0; j < BAS_CHANNELS; ++j) { + ubc = cs->bcs[j].hw.bas; + for (i = 0; i < BAS_OUTURBS; ++i) + if (!(ubc->isoouturbs[i].urb = + usb_alloc_urb(BAS_NUMFRAMES, GFP_KERNEL))) + goto allocerr; + for (i = 0; i < BAS_INURBS; ++i) + if (!(ubc->isoinurbs[i] = + usb_alloc_urb(BAS_NUMFRAMES, GFP_KERNEL))) + goto allocerr; + } + + ucs->rcvbuf = NULL; + ucs->rcvbuf_size = 0; + + /* Fill the interrupt urb and send it to the core */ + endpoint = &hostif->endpoint[0].desc; + usb_fill_int_urb(ucs->urb_int_in, udev, + usb_rcvintpipe(udev, + usb_endpoint_num(endpoint)), + ucs->int_in_buf, IP_MSGSIZE, read_int_callback, cs, + endpoint->bInterval); + rc = usb_submit_urb(ucs->urb_int_in, GFP_KERNEL); + if (rc != 0) { + dev_err(cs->dev, "could not submit interrupt URB: %s\n", + get_usb_rcmsg(rc)); + goto error; + } + ucs->retry_int_in = 0; + + /* tell the device that the driver is ready */ + rc = req_submit(cs->bcs, HD_DEVICE_INIT_ACK, 0, 0); + if (rc != 0) + goto error; + + /* tell common part that the device is ready */ + if (startmode == SM_LOCKED) + cs->mstate = MS_LOCKED; + + /* save address of controller structure */ + usb_set_intfdata(interface, cs); + + rc = gigaset_start(cs); + if (rc < 0) + goto error; + + return 0; + +allocerr: + dev_err(cs->dev, "could not allocate URBs\n"); + rc = -ENOMEM; +error: + freeurbs(cs); + usb_set_intfdata(interface, NULL); + usb_put_dev(udev); + gigaset_freecs(cs); + return rc; +} + +/* gigaset_disconnect + * This function is called when the Gigaset base is unplugged. + */ +static void gigaset_disconnect(struct usb_interface *interface) +{ + struct cardstate *cs; + struct bas_cardstate *ucs; + int j; + + cs = usb_get_intfdata(interface); + + ucs = cs->hw.bas; + + dev_info(cs->dev, "disconnecting Gigaset base\n"); + + /* mark base as not ready, all channels disconnected */ + ucs->basstate = 0; + + /* tell LL all channels are down */ + for (j = 0; j < BAS_CHANNELS; ++j) + gigaset_bchannel_down(cs->bcs + j); + + /* stop driver (common part) */ + gigaset_stop(cs); + + /* stop delayed work and URBs, free ressources */ + del_timer_sync(&ucs->timer_ctrl); + del_timer_sync(&ucs->timer_atrdy); + del_timer_sync(&ucs->timer_cmd_in); + del_timer_sync(&ucs->timer_int_in); + cancel_work_sync(&ucs->int_in_wq); + freeurbs(cs); + usb_set_intfdata(interface, NULL); + kfree(ucs->rcvbuf); + ucs->rcvbuf = NULL; + ucs->rcvbuf_size = 0; + usb_put_dev(ucs->udev); + ucs->interface = NULL; + ucs->udev = NULL; + cs->dev = NULL; + gigaset_freecs(cs); +} + +/* gigaset_suspend + * This function is called before the USB connection is suspended + * or before the USB device is reset. + * In the latter case, message == PMSG_ON. + */ +static int gigaset_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct cardstate *cs = usb_get_intfdata(intf); + struct bas_cardstate *ucs = cs->hw.bas; + int rc; + + /* set suspend flag; this stops AT command/response traffic */ + if (update_basstate(ucs, BS_SUSPEND, 0) & BS_SUSPEND) { + gig_dbg(DEBUG_SUSPEND, "already suspended"); + return 0; + } + + /* wait a bit for blocking conditions to go away */ + rc = wait_event_timeout(ucs->waitqueue, + !(ucs->basstate & + (BS_B1OPEN | BS_B2OPEN | BS_ATRDPEND | BS_ATWRPEND)), + BAS_TIMEOUT * HZ / 10); + gig_dbg(DEBUG_SUSPEND, "wait_event_timeout() -> %d", rc); + + /* check for conditions preventing suspend */ + if (ucs->basstate & (BS_B1OPEN | BS_B2OPEN | BS_ATRDPEND | BS_ATWRPEND)) { + dev_warn(cs->dev, "cannot suspend:\n"); + if (ucs->basstate & BS_B1OPEN) + dev_warn(cs->dev, " B channel 1 open\n"); + if (ucs->basstate & BS_B2OPEN) + dev_warn(cs->dev, " B channel 2 open\n"); + if (ucs->basstate & BS_ATRDPEND) + dev_warn(cs->dev, " receiving AT reply\n"); + if (ucs->basstate & BS_ATWRPEND) + dev_warn(cs->dev, " sending AT command\n"); + update_basstate(ucs, 0, BS_SUSPEND); + return -EBUSY; + } + + /* close AT channel if open */ + if (ucs->basstate & BS_ATOPEN) { + gig_dbg(DEBUG_SUSPEND, "closing AT channel"); + rc = req_submit(cs->bcs, HD_CLOSE_ATCHANNEL, 0, 0); + if (rc) { + update_basstate(ucs, 0, BS_SUSPEND); + return rc; + } + wait_event_timeout(ucs->waitqueue, !ucs->pending, + BAS_TIMEOUT * HZ / 10); + /* in case of timeout, proceed anyway */ + } + + /* kill all URBs and delayed work that might still be pending */ + usb_kill_urb(ucs->urb_ctrl); + usb_kill_urb(ucs->urb_int_in); + del_timer_sync(&ucs->timer_ctrl); + del_timer_sync(&ucs->timer_atrdy); + del_timer_sync(&ucs->timer_cmd_in); + del_timer_sync(&ucs->timer_int_in); + + /* don't try to cancel int_in_wq from within reset as it + * might be the one requesting the reset + */ + if (message.event != PM_EVENT_ON) + cancel_work_sync(&ucs->int_in_wq); + + gig_dbg(DEBUG_SUSPEND, "suspend complete"); + return 0; +} + +/* gigaset_resume + * This function is called after the USB connection has been resumed. + */ +static int gigaset_resume(struct usb_interface *intf) +{ + struct cardstate *cs = usb_get_intfdata(intf); + struct bas_cardstate *ucs = cs->hw.bas; + int rc; + + /* resubmit interrupt URB for spontaneous messages from base */ + rc = usb_submit_urb(ucs->urb_int_in, GFP_KERNEL); + if (rc) { + dev_err(cs->dev, "could not resubmit interrupt URB: %s\n", + get_usb_rcmsg(rc)); + return rc; + } + ucs->retry_int_in = 0; + + /* clear suspend flag to reallow activity */ + update_basstate(ucs, 0, BS_SUSPEND); + + gig_dbg(DEBUG_SUSPEND, "resume complete"); + return 0; +} + +/* gigaset_pre_reset + * This function is called before the USB connection is reset. + */ +static int gigaset_pre_reset(struct usb_interface *intf) +{ + /* handle just like suspend */ + return gigaset_suspend(intf, PMSG_ON); +} + +/* gigaset_post_reset + * This function is called after the USB connection has been reset. + */ +static int gigaset_post_reset(struct usb_interface *intf) +{ + /* FIXME: send HD_DEVICE_INIT_ACK? */ + + /* resume operations */ + return gigaset_resume(intf); +} + + +static const struct gigaset_ops gigops = { + .write_cmd = gigaset_write_cmd, + .write_room = gigaset_write_room, + .chars_in_buffer = gigaset_chars_in_buffer, + .brkchars = gigaset_brkchars, + .init_bchannel = gigaset_init_bchannel, + .close_bchannel = gigaset_close_bchannel, + .initbcshw = gigaset_initbcshw, + .freebcshw = gigaset_freebcshw, + .reinitbcshw = gigaset_reinitbcshw, + .initcshw = gigaset_initcshw, + .freecshw = gigaset_freecshw, + .set_modem_ctrl = gigaset_set_modem_ctrl, + .baud_rate = gigaset_baud_rate, + .set_line_ctrl = gigaset_set_line_ctrl, + .send_skb = gigaset_isoc_send_skb, + .handle_input = gigaset_isoc_input, +}; + +/* bas_gigaset_init + * This function is called after the kernel module is loaded. + */ +static int __init bas_gigaset_init(void) +{ + int result; + + /* allocate memory for our driver state and initialize it */ + driver = gigaset_initdriver(GIGASET_MINOR, GIGASET_MINORS, + GIGASET_MODULENAME, GIGASET_DEVNAME, + &gigops, THIS_MODULE); + if (driver == NULL) + goto error; + + /* register this driver with the USB subsystem */ + result = usb_register(&gigaset_usb_driver); + if (result < 0) { + pr_err("error %d registering USB driver\n", -result); + goto error; + } + + pr_info(DRIVER_DESC "\n"); + return 0; + +error: + if (driver) + gigaset_freedriver(driver); + driver = NULL; + return -1; +} + +/* bas_gigaset_exit + * This function is called before the kernel module is unloaded. + */ +static void __exit bas_gigaset_exit(void) +{ + struct bas_cardstate *ucs; + int i; + + gigaset_blockdriver(driver); /* => probe will fail + * => no gigaset_start any more + */ + + /* stop all connected devices */ + for (i = 0; i < driver->minors; i++) { + if (gigaset_shutdown(driver->cs + i) < 0) + continue; /* no device */ + /* from now on, no isdn callback should be possible */ + + /* close all still open channels */ + ucs = driver->cs[i].hw.bas; + if (ucs->basstate & BS_B1OPEN) { + gig_dbg(DEBUG_INIT, "closing B1 channel"); + usb_control_msg(ucs->udev, + usb_sndctrlpipe(ucs->udev, 0), + HD_CLOSE_B1CHANNEL, OUT_VENDOR_REQ, + 0, 0, NULL, 0, BAS_TIMEOUT); + } + if (ucs->basstate & BS_B2OPEN) { + gig_dbg(DEBUG_INIT, "closing B2 channel"); + usb_control_msg(ucs->udev, + usb_sndctrlpipe(ucs->udev, 0), + HD_CLOSE_B2CHANNEL, OUT_VENDOR_REQ, + 0, 0, NULL, 0, BAS_TIMEOUT); + } + if (ucs->basstate & BS_ATOPEN) { + gig_dbg(DEBUG_INIT, "closing AT channel"); + usb_control_msg(ucs->udev, + usb_sndctrlpipe(ucs->udev, 0), + HD_CLOSE_ATCHANNEL, OUT_VENDOR_REQ, + 0, 0, NULL, 0, BAS_TIMEOUT); + } + ucs->basstate = 0; + } + + /* deregister this driver with the USB subsystem */ + usb_deregister(&gigaset_usb_driver); + /* this will call the disconnect-callback */ + /* from now on, no disconnect/probe callback should be running */ + + gigaset_freedriver(driver); + driver = NULL; +} + + +module_init(bas_gigaset_init); +module_exit(bas_gigaset_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/isdn/gigaset/capi.c b/drivers/staging/isdn/gigaset/capi.c new file mode 100644 index 000000000000..9cb2ab57fa4a --- /dev/null +++ b/drivers/staging/isdn/gigaset/capi.c @@ -0,0 +1,2520 @@ +/* + * Kernel CAPI interface for the Gigaset driver + * + * Copyright (c) 2009 by Tilman Schmidt <tilman@imap.cc>. + * + * ===================================================================== + * 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. + * ===================================================================== + */ + +#include "gigaset.h" +#include <linux/proc_fs.h> +#include <linux/seq_file.h> +#include <linux/ratelimit.h> +#include <linux/isdn/capilli.h> +#include <linux/isdn/capicmd.h> +#include <linux/isdn/capiutil.h> +#include <linux/export.h> + +/* missing from kernelcapi.h */ +#define CapiNcpiNotSupportedByProtocol 0x0001 +#define CapiFlagsNotSupportedByProtocol 0x0002 +#define CapiAlertAlreadySent 0x0003 +#define CapiFacilitySpecificFunctionNotSupported 0x3011 + +/* missing from capicmd.h */ +#define CAPI_CONNECT_IND_BASELEN (CAPI_MSG_BASELEN + 4 + 2 + 8 * 1) +#define CAPI_CONNECT_ACTIVE_IND_BASELEN (CAPI_MSG_BASELEN + 4 + 3 * 1) +#define CAPI_CONNECT_B3_IND_BASELEN (CAPI_MSG_BASELEN + 4 + 1) +#define CAPI_CONNECT_B3_ACTIVE_IND_BASELEN (CAPI_MSG_BASELEN + 4 + 1) +#define CAPI_DATA_B3_REQ_LEN64 (CAPI_MSG_BASELEN + 4 + 4 + 2 + 2 + 2 + 8) +#define CAPI_DATA_B3_CONF_LEN (CAPI_MSG_BASELEN + 4 + 2 + 2) +#define CAPI_DISCONNECT_IND_LEN (CAPI_MSG_BASELEN + 4 + 2) +#define CAPI_DISCONNECT_B3_IND_BASELEN (CAPI_MSG_BASELEN + 4 + 2 + 1) +#define CAPI_FACILITY_CONF_BASELEN (CAPI_MSG_BASELEN + 4 + 2 + 2 + 1) +/* most _CONF messages contain only Controller/PLCI/NCCI and Info parameters */ +#define CAPI_STDCONF_LEN (CAPI_MSG_BASELEN + 4 + 2) + +#define CAPI_FACILITY_HANDSET 0x0000 +#define CAPI_FACILITY_DTMF 0x0001 +#define CAPI_FACILITY_V42BIS 0x0002 +#define CAPI_FACILITY_SUPPSVC 0x0003 +#define CAPI_FACILITY_WAKEUP 0x0004 +#define CAPI_FACILITY_LI 0x0005 + +#define CAPI_SUPPSVC_GETSUPPORTED 0x0000 +#define CAPI_SUPPSVC_LISTEN 0x0001 + +/* missing from capiutil.h */ +#define CAPIMSG_PLCI_PART(m) CAPIMSG_U8(m, 9) +#define CAPIMSG_NCCI_PART(m) CAPIMSG_U16(m, 10) +#define CAPIMSG_HANDLE_REQ(m) CAPIMSG_U16(m, 18) /* DATA_B3_REQ/_IND only! */ +#define CAPIMSG_FLAGS(m) CAPIMSG_U16(m, 20) +#define CAPIMSG_SETCONTROLLER(m, contr) capimsg_setu8(m, 8, contr) +#define CAPIMSG_SETPLCI_PART(m, plci) capimsg_setu8(m, 9, plci) +#define CAPIMSG_SETNCCI_PART(m, ncci) capimsg_setu16(m, 10, ncci) +#define CAPIMSG_SETFLAGS(m, flags) capimsg_setu16(m, 20, flags) + +/* parameters with differing location in DATA_B3_CONF/_RESP: */ +#define CAPIMSG_SETHANDLE_CONF(m, handle) capimsg_setu16(m, 12, handle) +#define CAPIMSG_SETINFO_CONF(m, info) capimsg_setu16(m, 14, info) + +/* Flags (DATA_B3_REQ/_IND) */ +#define CAPI_FLAGS_DELIVERY_CONFIRMATION 0x04 +#define CAPI_FLAGS_RESERVED (~0x1f) + +/* buffer sizes */ +#define MAX_BC_OCTETS 11 +#define MAX_HLC_OCTETS 3 +#define MAX_NUMBER_DIGITS 20 +#define MAX_FMT_IE_LEN 20 + +/* values for bcs->apconnstate */ +#define APCONN_NONE 0 /* inactive/listening */ +#define APCONN_SETUP 1 /* connecting */ +#define APCONN_ACTIVE 2 /* B channel up */ + +/* registered application data structure */ +struct gigaset_capi_appl { + struct list_head ctrlist; + struct gigaset_capi_appl *bcnext; + u16 id; + struct capi_register_params rp; + u16 nextMessageNumber; + u32 listenInfoMask; + u32 listenCIPmask; +}; + +/* CAPI specific controller data structure */ +struct gigaset_capi_ctr { + struct capi_ctr ctr; + struct list_head appls; + struct sk_buff_head sendqueue; + atomic_t sendqlen; + /* two _cmsg structures possibly used concurrently: */ + _cmsg hcmsg; /* for message composition triggered from hardware */ + _cmsg acmsg; /* for dissection of messages sent from application */ + u8 bc_buf[MAX_BC_OCTETS + 1]; + u8 hlc_buf[MAX_HLC_OCTETS + 1]; + u8 cgpty_buf[MAX_NUMBER_DIGITS + 3]; + u8 cdpty_buf[MAX_NUMBER_DIGITS + 2]; +}; + +/* CIP Value table (from CAPI 2.0 standard, ch. 6.1) */ +static struct { + u8 *bc; + u8 *hlc; +} cip2bchlc[] = { + [1] = { "8090A3", NULL }, /* Speech (A-law) */ + [2] = { "8890", NULL }, /* Unrestricted digital information */ + [3] = { "8990", NULL }, /* Restricted digital information */ + [4] = { "9090A3", NULL }, /* 3,1 kHz audio (A-law) */ + [5] = { "9190", NULL }, /* 7 kHz audio */ + [6] = { "9890", NULL }, /* Video */ + [7] = { "88C0C6E6", NULL }, /* Packet mode */ + [8] = { "8890218F", NULL }, /* 56 kbit/s rate adaptation */ + [9] = { "9190A5", NULL }, /* Unrestricted digital information + * with tones/announcements */ + [16] = { "8090A3", "9181" }, /* Telephony */ + [17] = { "9090A3", "9184" }, /* Group 2/3 facsimile */ + [18] = { "8890", "91A1" }, /* Group 4 facsimile Class 1 */ + [19] = { "8890", "91A4" }, /* Teletex service basic and mixed mode + * and Group 4 facsimile service + * Classes II and III */ + [20] = { "8890", "91A8" }, /* Teletex service basic and + * processable mode */ + [21] = { "8890", "91B1" }, /* Teletex service basic mode */ + [22] = { "8890", "91B2" }, /* International interworking for + * Videotex */ + [23] = { "8890", "91B5" }, /* Telex */ + [24] = { "8890", "91B8" }, /* Message Handling Systems + * in accordance with X.400 */ + [25] = { "8890", "91C1" }, /* OSI application + * in accordance with X.200 */ + [26] = { "9190A5", "9181" }, /* 7 kHz telephony */ + [27] = { "9190A5", "916001" }, /* Video telephony, first connection */ + [28] = { "8890", "916002" }, /* Video telephony, second connection */ +}; + +/* + * helper functions + * ================ + */ + +/* + * emit unsupported parameter warning + */ +static inline void ignore_cstruct_param(struct cardstate *cs, _cstruct param, + char *msgname, char *paramname) +{ + if (param && *param) + dev_warn(cs->dev, "%s: ignoring unsupported parameter: %s\n", + msgname, paramname); +} + +/* + * convert an IE from Gigaset hex string to ETSI binary representation + * including length byte + * return value: result length, -1 on error + */ +static int encode_ie(char *in, u8 *out, int maxlen) +{ + int l = 0; + while (*in) { + if (!isxdigit(in[0]) || !isxdigit(in[1]) || l >= maxlen) + return -1; + out[++l] = (hex_to_bin(in[0]) << 4) + hex_to_bin(in[1]); + in += 2; + } + out[0] = l; + return l; +} + +/* + * convert an IE from ETSI binary representation including length byte + * to Gigaset hex string + */ +static void decode_ie(u8 *in, char *out) +{ + int i = *in; + while (i-- > 0) { + /* ToDo: conversion to upper case necessary? */ + *out++ = toupper(hex_asc_hi(*++in)); + *out++ = toupper(hex_asc_lo(*in)); + } +} + +/* + * retrieve application data structure for an application ID + */ +static inline struct gigaset_capi_appl * +get_appl(struct gigaset_capi_ctr *iif, u16 appl) +{ + struct gigaset_capi_appl *ap; + + list_for_each_entry(ap, &iif->appls, ctrlist) + if (ap->id == appl) + return ap; + return NULL; +} + +/* + * dump CAPI message to kernel messages for debugging + */ +static inline void dump_cmsg(enum debuglevel level, const char *tag, _cmsg *p) +{ +#ifdef CONFIG_GIGASET_DEBUG + /* dump at most 20 messages in 20 secs */ + static DEFINE_RATELIMIT_STATE(msg_dump_ratelimit, 20 * HZ, 20); + _cdebbuf *cdb; + + if (!(gigaset_debuglevel & level)) + return; + if (!___ratelimit(&msg_dump_ratelimit, tag)) + return; + + cdb = capi_cmsg2str(p); + if (cdb) { + gig_dbg(level, "%s: [%d] %s", tag, p->ApplId, cdb->buf); + cdebbuf_free(cdb); + } else { + gig_dbg(level, "%s: [%d] %s", tag, p->ApplId, + capi_cmd2str(p->Command, p->Subcommand)); + } +#endif +} + +static inline void dump_rawmsg(enum debuglevel level, const char *tag, + unsigned char *data) +{ +#ifdef CONFIG_GIGASET_DEBUG + char *dbgline; + int i, l; + + if (!(gigaset_debuglevel & level)) + return; + + l = CAPIMSG_LEN(data); + if (l < 12) { + gig_dbg(level, "%s: ??? LEN=%04d", tag, l); + return; + } + gig_dbg(level, "%s: 0x%02x:0x%02x: ID=%03d #0x%04x LEN=%04d NCCI=0x%x", + tag, CAPIMSG_COMMAND(data), CAPIMSG_SUBCOMMAND(data), + CAPIMSG_APPID(data), CAPIMSG_MSGID(data), l, + CAPIMSG_CONTROL(data)); + l -= 12; + if (l <= 0) + return; + if (l > 64) + l = 64; /* arbitrary limit */ + dbgline = kmalloc_array(3, l, GFP_ATOMIC); + if (!dbgline) + return; + for (i = 0; i < l; i++) { + dbgline[3 * i] = hex_asc_hi(data[12 + i]); + dbgline[3 * i + 1] = hex_asc_lo(data[12 + i]); + dbgline[3 * i + 2] = ' '; + } + dbgline[3 * l - 1] = '\0'; + gig_dbg(level, " %s", dbgline); + kfree(dbgline); + if (CAPIMSG_COMMAND(data) == CAPI_DATA_B3 && + (CAPIMSG_SUBCOMMAND(data) == CAPI_REQ || + CAPIMSG_SUBCOMMAND(data) == CAPI_IND)) { + l = CAPIMSG_DATALEN(data); + gig_dbg(level, " DataLength=%d", l); + if (l <= 0 || !(gigaset_debuglevel & DEBUG_LLDATA)) + return; + if (l > 64) + l = 64; /* arbitrary limit */ + dbgline = kmalloc_array(3, l, GFP_ATOMIC); + if (!dbgline) + return; + data += CAPIMSG_LEN(data); + for (i = 0; i < l; i++) { + dbgline[3 * i] = hex_asc_hi(data[i]); + dbgline[3 * i + 1] = hex_asc_lo(data[i]); + dbgline[3 * i + 2] = ' '; + } + dbgline[3 * l - 1] = '\0'; + gig_dbg(level, " %s", dbgline); + kfree(dbgline); + } +#endif +} + +/* + * format CAPI IE as string + */ + +#ifdef CONFIG_GIGASET_DEBUG +static const char *format_ie(const char *ie) +{ + static char result[3 * MAX_FMT_IE_LEN]; + int len, count; + char *pout = result; + + if (!ie) + return "NULL"; + + count = len = ie[0]; + if (count > MAX_FMT_IE_LEN) + count = MAX_FMT_IE_LEN - 1; + while (count--) { + *pout++ = hex_asc_hi(*++ie); + *pout++ = hex_asc_lo(*ie); + *pout++ = ' '; + } + if (len > MAX_FMT_IE_LEN) { + *pout++ = '.'; + *pout++ = '.'; + *pout++ = '.'; + } + *--pout = 0; + return result; +} +#endif + +/* + * emit DATA_B3_CONF message + */ +static void send_data_b3_conf(struct cardstate *cs, struct capi_ctr *ctr, + u16 appl, u16 msgid, int channel, + u16 handle, u16 info) +{ + struct sk_buff *cskb; + u8 *msg; + + cskb = alloc_skb(CAPI_DATA_B3_CONF_LEN, GFP_ATOMIC); + if (!cskb) { + dev_err(cs->dev, "%s: out of memory\n", __func__); + return; + } + /* frequent message, avoid _cmsg overhead */ + msg = __skb_put(cskb, CAPI_DATA_B3_CONF_LEN); + CAPIMSG_SETLEN(msg, CAPI_DATA_B3_CONF_LEN); + CAPIMSG_SETAPPID(msg, appl); + CAPIMSG_SETCOMMAND(msg, CAPI_DATA_B3); + CAPIMSG_SETSUBCOMMAND(msg, CAPI_CONF); + CAPIMSG_SETMSGID(msg, msgid); + CAPIMSG_SETCONTROLLER(msg, ctr->cnr); + CAPIMSG_SETPLCI_PART(msg, channel); + CAPIMSG_SETNCCI_PART(msg, 1); + CAPIMSG_SETHANDLE_CONF(msg, handle); + CAPIMSG_SETINFO_CONF(msg, info); + + /* emit message */ + dump_rawmsg(DEBUG_MCMD, __func__, msg); + capi_ctr_handle_message(ctr, appl, cskb); +} + + +/* + * driver interface functions + * ========================== + */ + +/** + * gigaset_skb_sent() - acknowledge transmission of outgoing skb + * @bcs: B channel descriptor structure. + * @skb: sent data. + * + * Called by hardware module {bas,ser,usb}_gigaset when the data in a + * skb has been successfully sent, for signalling completion to the LL. + */ +void gigaset_skb_sent(struct bc_state *bcs, struct sk_buff *dskb) +{ + struct cardstate *cs = bcs->cs; + struct gigaset_capi_ctr *iif = cs->iif; + struct gigaset_capi_appl *ap = bcs->ap; + unsigned char *req = skb_mac_header(dskb); + u16 flags; + + /* update statistics */ + ++bcs->trans_up; + + if (!ap) { + gig_dbg(DEBUG_MCMD, "%s: application gone", __func__); + return; + } + + /* don't send further B3 messages if disconnected */ + if (bcs->apconnstate < APCONN_ACTIVE) { + gig_dbg(DEBUG_MCMD, "%s: disconnected", __func__); + return; + } + + /* + * send DATA_B3_CONF if "delivery confirmation" bit was set in request; + * otherwise it has already been sent by do_data_b3_req() + */ + flags = CAPIMSG_FLAGS(req); + if (flags & CAPI_FLAGS_DELIVERY_CONFIRMATION) + send_data_b3_conf(cs, &iif->ctr, ap->id, CAPIMSG_MSGID(req), + bcs->channel + 1, CAPIMSG_HANDLE_REQ(req), + (flags & ~CAPI_FLAGS_DELIVERY_CONFIRMATION) ? + CapiFlagsNotSupportedByProtocol : + CAPI_NOERROR); +} +EXPORT_SYMBOL_GPL(gigaset_skb_sent); + +/** + * gigaset_skb_rcvd() - pass received skb to LL + * @bcs: B channel descriptor structure. + * @skb: received data. + * + * Called by hardware module {bas,ser,usb}_gigaset when user data has + * been successfully received, for passing to the LL. + * Warning: skb must not be accessed anymore! + */ +void gigaset_skb_rcvd(struct bc_state *bcs, struct sk_buff *skb) +{ + struct cardstate *cs = bcs->cs; + struct gigaset_capi_ctr *iif = cs->iif; + struct gigaset_capi_appl *ap = bcs->ap; + int len = skb->len; + + /* update statistics */ + bcs->trans_down++; + + if (!ap) { + gig_dbg(DEBUG_MCMD, "%s: application gone", __func__); + dev_kfree_skb_any(skb); + return; + } + + /* don't send further B3 messages if disconnected */ + if (bcs->apconnstate < APCONN_ACTIVE) { + gig_dbg(DEBUG_MCMD, "%s: disconnected", __func__); + dev_kfree_skb_any(skb); + return; + } + + /* + * prepend DATA_B3_IND message to payload + * Parameters: NCCI = 1, all others 0/unused + * frequent message, avoid _cmsg overhead + */ + skb_push(skb, CAPI_DATA_B3_REQ_LEN); + CAPIMSG_SETLEN(skb->data, CAPI_DATA_B3_REQ_LEN); + CAPIMSG_SETAPPID(skb->data, ap->id); + CAPIMSG_SETCOMMAND(skb->data, CAPI_DATA_B3); + CAPIMSG_SETSUBCOMMAND(skb->data, CAPI_IND); + CAPIMSG_SETMSGID(skb->data, ap->nextMessageNumber++); + CAPIMSG_SETCONTROLLER(skb->data, iif->ctr.cnr); + CAPIMSG_SETPLCI_PART(skb->data, bcs->channel + 1); + CAPIMSG_SETNCCI_PART(skb->data, 1); + /* Data parameter not used */ + CAPIMSG_SETDATALEN(skb->data, len); + /* Data handle parameter not used */ + CAPIMSG_SETFLAGS(skb->data, 0); + /* Data64 parameter not present */ + + /* emit message */ + dump_rawmsg(DEBUG_MCMD, __func__, skb->data); + capi_ctr_handle_message(&iif->ctr, ap->id, skb); +} +EXPORT_SYMBOL_GPL(gigaset_skb_rcvd); + +/** + * gigaset_isdn_rcv_err() - signal receive error + * @bcs: B channel descriptor structure. + * + * Called by hardware module {bas,ser,usb}_gigaset when a receive error + * has occurred, for signalling to the LL. + */ +void gigaset_isdn_rcv_err(struct bc_state *bcs) +{ + /* if currently ignoring packets, just count down */ + if (bcs->ignore) { + bcs->ignore--; + return; + } + + /* update statistics */ + bcs->corrupted++; + + /* ToDo: signal error -> LL */ +} +EXPORT_SYMBOL_GPL(gigaset_isdn_rcv_err); + +/** + * gigaset_isdn_icall() - signal incoming call + * @at_state: connection state structure. + * + * Called by main module at tasklet level to notify the LL that an incoming + * call has been received. @at_state contains the parameters of the call. + * + * Return value: call disposition (ICALL_*) + */ +int gigaset_isdn_icall(struct at_state_t *at_state) +{ + struct cardstate *cs = at_state->cs; + struct bc_state *bcs = at_state->bcs; + struct gigaset_capi_ctr *iif = cs->iif; + struct gigaset_capi_appl *ap; + u32 actCIPmask; + struct sk_buff *skb; + unsigned int msgsize; + unsigned long flags; + int i; + + /* + * ToDo: signal calls without a free B channel, too + * (requires a u8 handle for the at_state structure that can + * be stored in the PLCI and used in the CONNECT_RESP message + * handler to retrieve it) + */ + if (!bcs) + return ICALL_IGNORE; + + /* prepare CONNECT_IND message, using B channel number as PLCI */ + capi_cmsg_header(&iif->hcmsg, 0, CAPI_CONNECT, CAPI_IND, 0, + iif->ctr.cnr | ((bcs->channel + 1) << 8)); + + /* minimum size, all structs empty */ + msgsize = CAPI_CONNECT_IND_BASELEN; + + /* Bearer Capability (mandatory) */ + if (at_state->str_var[STR_ZBC]) { + /* pass on BC from Gigaset */ + if (encode_ie(at_state->str_var[STR_ZBC], iif->bc_buf, + MAX_BC_OCTETS) < 0) { + dev_warn(cs->dev, "RING ignored - bad BC %s\n", + at_state->str_var[STR_ZBC]); + return ICALL_IGNORE; + } + + /* look up corresponding CIP value */ + iif->hcmsg.CIPValue = 0; /* default if nothing found */ + for (i = 0; i < ARRAY_SIZE(cip2bchlc); i++) + if (cip2bchlc[i].bc != NULL && + cip2bchlc[i].hlc == NULL && + !strcmp(cip2bchlc[i].bc, + at_state->str_var[STR_ZBC])) { + iif->hcmsg.CIPValue = i; + break; + } + } else { + /* no BC (internal call): assume CIP 1 (speech, A-law) */ + iif->hcmsg.CIPValue = 1; + encode_ie(cip2bchlc[1].bc, iif->bc_buf, MAX_BC_OCTETS); + } + iif->hcmsg.BC = iif->bc_buf; + msgsize += iif->hcmsg.BC[0]; + + /* High Layer Compatibility (optional) */ + if (at_state->str_var[STR_ZHLC]) { + /* pass on HLC from Gigaset */ + if (encode_ie(at_state->str_var[STR_ZHLC], iif->hlc_buf, + MAX_HLC_OCTETS) < 0) { + dev_warn(cs->dev, "RING ignored - bad HLC %s\n", + at_state->str_var[STR_ZHLC]); + return ICALL_IGNORE; + } + iif->hcmsg.HLC = iif->hlc_buf; + msgsize += iif->hcmsg.HLC[0]; + + /* look up corresponding CIP value */ + /* keep BC based CIP value if none found */ + if (at_state->str_var[STR_ZBC]) + for (i = 0; i < ARRAY_SIZE(cip2bchlc); i++) + if (cip2bchlc[i].hlc != NULL && + !strcmp(cip2bchlc[i].hlc, + at_state->str_var[STR_ZHLC]) && + !strcmp(cip2bchlc[i].bc, + at_state->str_var[STR_ZBC])) { + iif->hcmsg.CIPValue = i; + break; + } + } + + /* Called Party Number (optional) */ + if (at_state->str_var[STR_ZCPN]) { + i = strlen(at_state->str_var[STR_ZCPN]); + if (i > MAX_NUMBER_DIGITS) { + dev_warn(cs->dev, "RING ignored - bad number %s\n", + at_state->str_var[STR_ZBC]); + return ICALL_IGNORE; + } + iif->cdpty_buf[0] = i + 1; + iif->cdpty_buf[1] = 0x80; /* type / numbering plan unknown */ + memcpy(iif->cdpty_buf + 2, at_state->str_var[STR_ZCPN], i); + iif->hcmsg.CalledPartyNumber = iif->cdpty_buf; + msgsize += iif->hcmsg.CalledPartyNumber[0]; + } + + /* Calling Party Number (optional) */ + if (at_state->str_var[STR_NMBR]) { + i = strlen(at_state->str_var[STR_NMBR]); + if (i > MAX_NUMBER_DIGITS) { + dev_warn(cs->dev, "RING ignored - bad number %s\n", + at_state->str_var[STR_ZBC]); + return ICALL_IGNORE; + } + iif->cgpty_buf[0] = i + 2; + iif->cgpty_buf[1] = 0x00; /* type / numbering plan unknown */ + iif->cgpty_buf[2] = 0x80; /* pres. allowed, not screened */ + memcpy(iif->cgpty_buf + 3, at_state->str_var[STR_NMBR], i); + iif->hcmsg.CallingPartyNumber = iif->cgpty_buf; + msgsize += iif->hcmsg.CallingPartyNumber[0]; + } + + /* remaining parameters (not supported, always left NULL): + * - CalledPartySubaddress + * - CallingPartySubaddress + * - AdditionalInfo + * - BChannelinformation + * - Keypadfacility + * - Useruserdata + * - Facilitydataarray + */ + + gig_dbg(DEBUG_CMD, "icall: PLCI %x CIP %d BC %s", + iif->hcmsg.adr.adrPLCI, iif->hcmsg.CIPValue, + format_ie(iif->hcmsg.BC)); + gig_dbg(DEBUG_CMD, "icall: HLC %s", + format_ie(iif->hcmsg.HLC)); + gig_dbg(DEBUG_CMD, "icall: CgPty %s", + format_ie(iif->hcmsg.CallingPartyNumber)); + gig_dbg(DEBUG_CMD, "icall: CdPty %s", + format_ie(iif->hcmsg.CalledPartyNumber)); + + /* scan application list for matching listeners */ + spin_lock_irqsave(&bcs->aplock, flags); + if (bcs->ap != NULL || bcs->apconnstate != APCONN_NONE) { + dev_warn(cs->dev, "%s: channel not properly cleared (%p/%d)\n", + __func__, bcs->ap, bcs->apconnstate); + bcs->ap = NULL; + bcs->apconnstate = APCONN_NONE; + } + spin_unlock_irqrestore(&bcs->aplock, flags); + actCIPmask = 1 | (1 << iif->hcmsg.CIPValue); + list_for_each_entry(ap, &iif->appls, ctrlist) + if (actCIPmask & ap->listenCIPmask) { + /* build CONNECT_IND message for this application */ + iif->hcmsg.ApplId = ap->id; + iif->hcmsg.Messagenumber = ap->nextMessageNumber++; + + skb = alloc_skb(msgsize, GFP_ATOMIC); + if (!skb) { + dev_err(cs->dev, "%s: out of memory\n", + __func__); + break; + } + if (capi_cmsg2message(&iif->hcmsg, + __skb_put(skb, msgsize))) { + dev_err(cs->dev, "%s: message parser failure\n", + __func__); + dev_kfree_skb_any(skb); + break; + } + dump_cmsg(DEBUG_CMD, __func__, &iif->hcmsg); + + /* add to listeners on this B channel, update state */ + spin_lock_irqsave(&bcs->aplock, flags); + ap->bcnext = bcs->ap; + bcs->ap = ap; + bcs->chstate |= CHS_NOTIFY_LL; + bcs->apconnstate = APCONN_SETUP; + spin_unlock_irqrestore(&bcs->aplock, flags); + + /* emit message */ + capi_ctr_handle_message(&iif->ctr, ap->id, skb); + } + + /* + * Return "accept" if any listeners. + * Gigaset will send ALERTING. + * There doesn't seem to be a way to avoid this. + */ + return bcs->ap ? ICALL_ACCEPT : ICALL_IGNORE; +} + +/* + * send a DISCONNECT_IND message to an application + * does not sleep, clobbers the controller's hcmsg structure + */ +static void send_disconnect_ind(struct bc_state *bcs, + struct gigaset_capi_appl *ap, u16 reason) +{ + struct cardstate *cs = bcs->cs; + struct gigaset_capi_ctr *iif = cs->iif; + struct sk_buff *skb; + + if (bcs->apconnstate == APCONN_NONE) + return; + + capi_cmsg_header(&iif->hcmsg, ap->id, CAPI_DISCONNECT, CAPI_IND, + ap->nextMessageNumber++, + iif->ctr.cnr | ((bcs->channel + 1) << 8)); + iif->hcmsg.Reason = reason; + skb = alloc_skb(CAPI_DISCONNECT_IND_LEN, GFP_ATOMIC); + if (!skb) { + dev_err(cs->dev, "%s: out of memory\n", __func__); + return; + } + if (capi_cmsg2message(&iif->hcmsg, + __skb_put(skb, CAPI_DISCONNECT_IND_LEN))) { + dev_err(cs->dev, "%s: message parser failure\n", __func__); + dev_kfree_skb_any(skb); + return; + } + dump_cmsg(DEBUG_CMD, __func__, &iif->hcmsg); + capi_ctr_handle_message(&iif->ctr, ap->id, skb); +} + +/* + * send a DISCONNECT_B3_IND message to an application + * Parameters: NCCI = 1, NCPI empty, Reason_B3 = 0 + * does not sleep, clobbers the controller's hcmsg structure + */ +static void send_disconnect_b3_ind(struct bc_state *bcs, + struct gigaset_capi_appl *ap) +{ + struct cardstate *cs = bcs->cs; + struct gigaset_capi_ctr *iif = cs->iif; + struct sk_buff *skb; + + /* nothing to do if no logical connection active */ + if (bcs->apconnstate < APCONN_ACTIVE) + return; + bcs->apconnstate = APCONN_SETUP; + + capi_cmsg_header(&iif->hcmsg, ap->id, CAPI_DISCONNECT_B3, CAPI_IND, + ap->nextMessageNumber++, + iif->ctr.cnr | ((bcs->channel + 1) << 8) | (1 << 16)); + skb = alloc_skb(CAPI_DISCONNECT_B3_IND_BASELEN, GFP_ATOMIC); + if (!skb) { + dev_err(cs->dev, "%s: out of memory\n", __func__); + return; + } + if (capi_cmsg2message(&iif->hcmsg, + __skb_put(skb, CAPI_DISCONNECT_B3_IND_BASELEN))) { + dev_err(cs->dev, "%s: message parser failure\n", __func__); + dev_kfree_skb_any(skb); + return; + } + dump_cmsg(DEBUG_CMD, __func__, &iif->hcmsg); + capi_ctr_handle_message(&iif->ctr, ap->id, skb); +} + +/** + * gigaset_isdn_connD() - signal D channel connect + * @bcs: B channel descriptor structure. + * + * Called by main module at tasklet level to notify the LL that the D channel + * connection has been established. + */ +void gigaset_isdn_connD(struct bc_state *bcs) +{ + struct cardstate *cs = bcs->cs; + struct gigaset_capi_ctr *iif = cs->iif; + struct gigaset_capi_appl *ap; + struct sk_buff *skb; + unsigned int msgsize; + unsigned long flags; + + spin_lock_irqsave(&bcs->aplock, flags); + ap = bcs->ap; + if (!ap) { + spin_unlock_irqrestore(&bcs->aplock, flags); + gig_dbg(DEBUG_CMD, "%s: application gone", __func__); + return; + } + if (bcs->apconnstate == APCONN_NONE) { + spin_unlock_irqrestore(&bcs->aplock, flags); + dev_warn(cs->dev, "%s: application %u not connected\n", + __func__, ap->id); + return; + } + spin_unlock_irqrestore(&bcs->aplock, flags); + while (ap->bcnext) { + /* this should never happen */ + dev_warn(cs->dev, "%s: dropping extra application %u\n", + __func__, ap->bcnext->id); + send_disconnect_ind(bcs, ap->bcnext, + CapiCallGivenToOtherApplication); + ap->bcnext = ap->bcnext->bcnext; + } + + /* prepare CONNECT_ACTIVE_IND message + * Note: LLC not supported by device + */ + capi_cmsg_header(&iif->hcmsg, ap->id, CAPI_CONNECT_ACTIVE, CAPI_IND, + ap->nextMessageNumber++, + iif->ctr.cnr | ((bcs->channel + 1) << 8)); + + /* minimum size, all structs empty */ + msgsize = CAPI_CONNECT_ACTIVE_IND_BASELEN; + + /* ToDo: set parameter: Connected number + * (requires ev-layer state machine extension to collect + * ZCON device reply) + */ + + /* build and emit CONNECT_ACTIVE_IND message */ + skb = alloc_skb(msgsize, GFP_ATOMIC); + if (!skb) { + dev_err(cs->dev, "%s: out of memory\n", __func__); + return; + } + if (capi_cmsg2message(&iif->hcmsg, __skb_put(skb, msgsize))) { + dev_err(cs->dev, "%s: message parser failure\n", __func__); + dev_kfree_skb_any(skb); + return; + } + dump_cmsg(DEBUG_CMD, __func__, &iif->hcmsg); + capi_ctr_handle_message(&iif->ctr, ap->id, skb); +} + +/** + * gigaset_isdn_hupD() - signal D channel hangup + * @bcs: B channel descriptor structure. + * + * Called by main module at tasklet level to notify the LL that the D channel + * connection has been shut down. + */ +void gigaset_isdn_hupD(struct bc_state *bcs) +{ + struct gigaset_capi_appl *ap; + unsigned long flags; + + /* + * ToDo: pass on reason code reported by device + * (requires ev-layer state machine extension to collect + * ZCAU device reply) + */ + spin_lock_irqsave(&bcs->aplock, flags); + while (bcs->ap != NULL) { + ap = bcs->ap; + bcs->ap = ap->bcnext; + spin_unlock_irqrestore(&bcs->aplock, flags); + send_disconnect_b3_ind(bcs, ap); + send_disconnect_ind(bcs, ap, 0); + spin_lock_irqsave(&bcs->aplock, flags); + } + bcs->apconnstate = APCONN_NONE; + spin_unlock_irqrestore(&bcs->aplock, flags); +} + +/** + * gigaset_isdn_connB() - signal B channel connect + * @bcs: B channel descriptor structure. + * + * Called by main module at tasklet level to notify the LL that the B channel + * connection has been established. + */ +void gigaset_isdn_connB(struct bc_state *bcs) +{ + struct cardstate *cs = bcs->cs; + struct gigaset_capi_ctr *iif = cs->iif; + struct gigaset_capi_appl *ap; + struct sk_buff *skb; + unsigned long flags; + unsigned int msgsize; + u8 command; + + spin_lock_irqsave(&bcs->aplock, flags); + ap = bcs->ap; + if (!ap) { + spin_unlock_irqrestore(&bcs->aplock, flags); + gig_dbg(DEBUG_CMD, "%s: application gone", __func__); + return; + } + if (!bcs->apconnstate) { + spin_unlock_irqrestore(&bcs->aplock, flags); + dev_warn(cs->dev, "%s: application %u not connected\n", + __func__, ap->id); + return; + } + + /* + * emit CONNECT_B3_ACTIVE_IND if we already got CONNECT_B3_REQ; + * otherwise we have to emit CONNECT_B3_IND first, and follow up with + * CONNECT_B3_ACTIVE_IND in reply to CONNECT_B3_RESP + * Parameters in both cases always: NCCI = 1, NCPI empty + */ + if (bcs->apconnstate >= APCONN_ACTIVE) { + command = CAPI_CONNECT_B3_ACTIVE; + msgsize = CAPI_CONNECT_B3_ACTIVE_IND_BASELEN; + } else { + command = CAPI_CONNECT_B3; + msgsize = CAPI_CONNECT_B3_IND_BASELEN; + } + bcs->apconnstate = APCONN_ACTIVE; + + spin_unlock_irqrestore(&bcs->aplock, flags); + + while (ap->bcnext) { + /* this should never happen */ + dev_warn(cs->dev, "%s: dropping extra application %u\n", + __func__, ap->bcnext->id); + send_disconnect_ind(bcs, ap->bcnext, + CapiCallGivenToOtherApplication); + ap->bcnext = ap->bcnext->bcnext; + } + + capi_cmsg_header(&iif->hcmsg, ap->id, command, CAPI_IND, + ap->nextMessageNumber++, + iif->ctr.cnr | ((bcs->channel + 1) << 8) | (1 << 16)); + skb = alloc_skb(msgsize, GFP_ATOMIC); + if (!skb) { + dev_err(cs->dev, "%s: out of memory\n", __func__); + return; + } + if (capi_cmsg2message(&iif->hcmsg, __skb_put(skb, msgsize))) { + dev_err(cs->dev, "%s: message parser failure\n", __func__); + dev_kfree_skb_any(skb); + return; + } + dump_cmsg(DEBUG_CMD, __func__, &iif->hcmsg); + capi_ctr_handle_message(&iif->ctr, ap->id, skb); +} + +/** + * gigaset_isdn_hupB() - signal B channel hangup + * @bcs: B channel descriptor structure. + * + * Called by main module to notify the LL that the B channel connection has + * been shut down. + */ +void gigaset_isdn_hupB(struct bc_state *bcs) +{ + struct gigaset_capi_appl *ap = bcs->ap; + + /* ToDo: assure order of DISCONNECT_B3_IND and DISCONNECT_IND ? */ + + if (!ap) { + gig_dbg(DEBUG_CMD, "%s: application gone", __func__); + return; + } + + send_disconnect_b3_ind(bcs, ap); +} + +/** + * gigaset_isdn_start() - signal device availability + * @cs: device descriptor structure. + * + * Called by main module to notify the LL that the device is available for + * use. + */ +void gigaset_isdn_start(struct cardstate *cs) +{ + struct gigaset_capi_ctr *iif = cs->iif; + + /* fill profile data: manufacturer name */ + strcpy(iif->ctr.manu, "Siemens"); + /* CAPI and device version */ + iif->ctr.version.majorversion = 2; /* CAPI 2.0 */ + iif->ctr.version.minorversion = 0; + /* ToDo: check/assert cs->gotfwver? */ + iif->ctr.version.majormanuversion = cs->fwver[0]; + iif->ctr.version.minormanuversion = cs->fwver[1]; + /* number of B channels supported */ + iif->ctr.profile.nbchannel = cs->channels; + /* global options: internal controller, supplementary services */ + iif->ctr.profile.goptions = 0x11; + /* B1 protocols: 64 kbit/s HDLC or transparent */ + iif->ctr.profile.support1 = 0x03; + /* B2 protocols: transparent only */ + /* ToDo: X.75 SLP ? */ + iif->ctr.profile.support2 = 0x02; + /* B3 protocols: transparent only */ + iif->ctr.profile.support3 = 0x01; + /* no serial number */ + strcpy(iif->ctr.serial, "0"); + capi_ctr_ready(&iif->ctr); +} + +/** + * gigaset_isdn_stop() - signal device unavailability + * @cs: device descriptor structure. + * + * Called by main module to notify the LL that the device is no longer + * available for use. + */ +void gigaset_isdn_stop(struct cardstate *cs) +{ + struct gigaset_capi_ctr *iif = cs->iif; + capi_ctr_down(&iif->ctr); +} + +/* + * kernel CAPI callback methods + * ============================ + */ + +/* + * register CAPI application + */ +static void gigaset_register_appl(struct capi_ctr *ctr, u16 appl, + capi_register_params *rp) +{ + struct gigaset_capi_ctr *iif + = container_of(ctr, struct gigaset_capi_ctr, ctr); + struct cardstate *cs = ctr->driverdata; + struct gigaset_capi_appl *ap; + + gig_dbg(DEBUG_CMD, "%s [%u] l3cnt=%u blkcnt=%u blklen=%u", + __func__, appl, rp->level3cnt, rp->datablkcnt, rp->datablklen); + + list_for_each_entry(ap, &iif->appls, ctrlist) + if (ap->id == appl) { + dev_notice(cs->dev, + "application %u already registered\n", appl); + return; + } + + ap = kzalloc(sizeof(*ap), GFP_KERNEL); + if (!ap) { + dev_err(cs->dev, "%s: out of memory\n", __func__); + return; + } + ap->id = appl; + ap->rp = *rp; + + list_add(&ap->ctrlist, &iif->appls); + dev_info(cs->dev, "application %u registered\n", ap->id); +} + +/* + * remove CAPI application from channel + * helper function to keep indentation levels down and stay in 80 columns + */ + +static inline void remove_appl_from_channel(struct bc_state *bcs, + struct gigaset_capi_appl *ap) +{ + struct cardstate *cs = bcs->cs; + struct gigaset_capi_appl *bcap; + unsigned long flags; + int prevconnstate; + + spin_lock_irqsave(&bcs->aplock, flags); + bcap = bcs->ap; + if (bcap == NULL) { + spin_unlock_irqrestore(&bcs->aplock, flags); + return; + } + + /* check first application on channel */ + if (bcap == ap) { + bcs->ap = ap->bcnext; + if (bcs->ap != NULL) { + spin_unlock_irqrestore(&bcs->aplock, flags); + return; + } + + /* none left, clear channel state */ + prevconnstate = bcs->apconnstate; + bcs->apconnstate = APCONN_NONE; + spin_unlock_irqrestore(&bcs->aplock, flags); + + if (prevconnstate == APCONN_ACTIVE) { + dev_notice(cs->dev, "%s: hanging up channel %u\n", + __func__, bcs->channel); + gigaset_add_event(cs, &bcs->at_state, + EV_HUP, NULL, 0, NULL); + gigaset_schedule_event(cs); + } + return; + } + + /* check remaining list */ + do { + if (bcap->bcnext == ap) { + bcap->bcnext = bcap->bcnext->bcnext; + spin_unlock_irqrestore(&bcs->aplock, flags); + return; + } + bcap = bcap->bcnext; + } while (bcap != NULL); + spin_unlock_irqrestore(&bcs->aplock, flags); +} + +/* + * release CAPI application + */ +static void gigaset_release_appl(struct capi_ctr *ctr, u16 appl) +{ + struct gigaset_capi_ctr *iif + = container_of(ctr, struct gigaset_capi_ctr, ctr); + struct cardstate *cs = iif->ctr.driverdata; + struct gigaset_capi_appl *ap, *tmp; + unsigned ch; + + gig_dbg(DEBUG_CMD, "%s [%u]", __func__, appl); + + list_for_each_entry_safe(ap, tmp, &iif->appls, ctrlist) + if (ap->id == appl) { + /* remove from any channels */ + for (ch = 0; ch < cs->channels; ch++) + remove_appl_from_channel(&cs->bcs[ch], ap); + + /* remove from registration list */ + list_del(&ap->ctrlist); + kfree(ap); + dev_info(cs->dev, "application %u released\n", appl); + } +} + +/* + * ===================================================================== + * outgoing CAPI message handler + * ===================================================================== + */ + +/* + * helper function: emit reply message with given Info value + */ +static void send_conf(struct gigaset_capi_ctr *iif, + struct gigaset_capi_appl *ap, + struct sk_buff *skb, + u16 info) +{ + struct cardstate *cs = iif->ctr.driverdata; + + /* + * _CONF replies always only have NCCI and Info parameters + * so they'll fit into the _REQ message skb + */ + capi_cmsg_answer(&iif->acmsg); + iif->acmsg.Info = info; + if (capi_cmsg2message(&iif->acmsg, skb->data)) { + dev_err(cs->dev, "%s: message parser failure\n", __func__); + dev_kfree_skb_any(skb); + return; + } + __skb_trim(skb, CAPI_STDCONF_LEN); + dump_cmsg(DEBUG_CMD, __func__, &iif->acmsg); + capi_ctr_handle_message(&iif->ctr, ap->id, skb); +} + +/* + * process FACILITY_REQ message + */ +static void do_facility_req(struct gigaset_capi_ctr *iif, + struct gigaset_capi_appl *ap, + struct sk_buff *skb) +{ + struct cardstate *cs = iif->ctr.driverdata; + _cmsg *cmsg = &iif->acmsg; + struct sk_buff *cskb; + u8 *pparam; + unsigned int msgsize = CAPI_FACILITY_CONF_BASELEN; + u16 function, info; + static u8 confparam[10]; /* max. 9 octets + length byte */ + + /* decode message */ + if (capi_message2cmsg(cmsg, skb->data)) { + dev_err(cs->dev, "%s: message parser failure\n", __func__); + dev_kfree_skb_any(skb); + return; + } + dump_cmsg(DEBUG_CMD, __func__, cmsg); + + /* + * Facility Request Parameter is not decoded by capi_message2cmsg() + * encoding depends on Facility Selector + */ + switch (cmsg->FacilitySelector) { + case CAPI_FACILITY_DTMF: /* ToDo */ + info = CapiFacilityNotSupported; + confparam[0] = 2; /* length */ + /* DTMF information: Unknown DTMF request */ + capimsg_setu16(confparam, 1, 2); + break; + + case CAPI_FACILITY_V42BIS: /* not supported */ + info = CapiFacilityNotSupported; + confparam[0] = 2; /* length */ + /* V.42 bis information: not available */ + capimsg_setu16(confparam, 1, 1); + break; + + case CAPI_FACILITY_SUPPSVC: + /* decode Function parameter */ + pparam = cmsg->FacilityRequestParameter; + if (pparam == NULL || pparam[0] < 2) { + dev_notice(cs->dev, "%s: %s missing\n", "FACILITY_REQ", + "Facility Request Parameter"); + send_conf(iif, ap, skb, CapiIllMessageParmCoding); + return; + } + function = CAPIMSG_U16(pparam, 1); + switch (function) { + case CAPI_SUPPSVC_GETSUPPORTED: + info = CapiSuccess; + /* Supplementary Service specific parameter */ + confparam[3] = 6; /* length */ + /* Supplementary services info: Success */ + capimsg_setu16(confparam, 4, CapiSuccess); + /* Supported Services: none */ + capimsg_setu32(confparam, 6, 0); + break; + case CAPI_SUPPSVC_LISTEN: + if (pparam[0] < 7 || pparam[3] < 4) { + dev_notice(cs->dev, "%s: %s missing\n", + "FACILITY_REQ", "Notification Mask"); + send_conf(iif, ap, skb, + CapiIllMessageParmCoding); + return; + } + if (CAPIMSG_U32(pparam, 4) != 0) { + dev_notice(cs->dev, + "%s: unsupported supplementary service notification mask 0x%x\n", + "FACILITY_REQ", CAPIMSG_U32(pparam, 4)); + info = CapiFacilitySpecificFunctionNotSupported; + confparam[3] = 2; /* length */ + capimsg_setu16(confparam, 4, + CapiSupplementaryServiceNotSupported); + break; + } + info = CapiSuccess; + confparam[3] = 2; /* length */ + capimsg_setu16(confparam, 4, CapiSuccess); + break; + + /* ToDo: add supported services */ + + default: + dev_notice(cs->dev, + "%s: unsupported supplementary service function 0x%04x\n", + "FACILITY_REQ", function); + info = CapiFacilitySpecificFunctionNotSupported; + /* Supplementary Service specific parameter */ + confparam[3] = 2; /* length */ + /* Supplementary services info: not supported */ + capimsg_setu16(confparam, 4, + CapiSupplementaryServiceNotSupported); + } + + /* Facility confirmation parameter */ + confparam[0] = confparam[3] + 3; /* total length */ + /* Function: copy from _REQ message */ + capimsg_setu16(confparam, 1, function); + /* Supplementary Service specific parameter already set above */ + break; + + case CAPI_FACILITY_WAKEUP: /* ToDo */ + info = CapiFacilityNotSupported; + confparam[0] = 2; /* length */ + /* Number of accepted awake request parameters: 0 */ + capimsg_setu16(confparam, 1, 0); + break; + + default: + info = CapiFacilityNotSupported; + confparam[0] = 0; /* empty struct */ + } + + /* send FACILITY_CONF with given Info and confirmation parameter */ + dev_kfree_skb_any(skb); + capi_cmsg_answer(cmsg); + cmsg->Info = info; + cmsg->FacilityConfirmationParameter = confparam; + msgsize += confparam[0]; /* length */ + cskb = alloc_skb(msgsize, GFP_ATOMIC); + if (!cskb) { + dev_err(cs->dev, "%s: out of memory\n", __func__); + return; + } + if (capi_cmsg2message(cmsg, __skb_put(cskb, msgsize))) { + dev_err(cs->dev, "%s: message parser failure\n", __func__); + dev_kfree_skb_any(cskb); + return; + } + dump_cmsg(DEBUG_CMD, __func__, cmsg); + capi_ctr_handle_message(&iif->ctr, ap->id, cskb); +} + + +/* + * process LISTEN_REQ message + * just store the masks in the application data structure + */ +static void do_listen_req(struct gigaset_capi_ctr *iif, + struct gigaset_capi_appl *ap, + struct sk_buff *skb) +{ + struct cardstate *cs = iif->ctr.driverdata; + + /* decode message */ + if (capi_message2cmsg(&iif->acmsg, skb->data)) { + dev_err(cs->dev, "%s: message parser failure\n", __func__); + dev_kfree_skb_any(skb); + return; + } + dump_cmsg(DEBUG_CMD, __func__, &iif->acmsg); + + /* store listening parameters */ + ap->listenInfoMask = iif->acmsg.InfoMask; + ap->listenCIPmask = iif->acmsg.CIPmask; + send_conf(iif, ap, skb, CapiSuccess); +} + +/* + * process ALERT_REQ message + * nothing to do, Gigaset always alerts anyway + */ +static void do_alert_req(struct gigaset_capi_ctr *iif, + struct gigaset_capi_appl *ap, + struct sk_buff *skb) +{ + struct cardstate *cs = iif->ctr.driverdata; + + /* decode message */ + if (capi_message2cmsg(&iif->acmsg, skb->data)) { + dev_err(cs->dev, "%s: message parser failure\n", __func__); + dev_kfree_skb_any(skb); + return; + } + dump_cmsg(DEBUG_CMD, __func__, &iif->acmsg); + send_conf(iif, ap, skb, CapiAlertAlreadySent); +} + +/* + * process CONNECT_REQ message + * allocate a B channel, prepare dial commands, queue a DIAL event, + * emit CONNECT_CONF reply + */ +static void do_connect_req(struct gigaset_capi_ctr *iif, + struct gigaset_capi_appl *ap, + struct sk_buff *skb) +{ + struct cardstate *cs = iif->ctr.driverdata; + _cmsg *cmsg = &iif->acmsg; + struct bc_state *bcs; + char **commands; + char *s; + u8 *pp; + unsigned long flags; + int i, l, lbc, lhlc; + u16 info; + + /* decode message */ + if (capi_message2cmsg(cmsg, skb->data)) { + dev_err(cs->dev, "%s: message parser failure\n", __func__); + dev_kfree_skb_any(skb); + return; + } + dump_cmsg(DEBUG_CMD, __func__, cmsg); + + /* get free B channel & construct PLCI */ + bcs = gigaset_get_free_channel(cs); + if (!bcs) { + dev_notice(cs->dev, "%s: no B channel available\n", + "CONNECT_REQ"); + send_conf(iif, ap, skb, CapiNoPlciAvailable); + return; + } + spin_lock_irqsave(&bcs->aplock, flags); + if (bcs->ap != NULL || bcs->apconnstate != APCONN_NONE) + dev_warn(cs->dev, "%s: channel not properly cleared (%p/%d)\n", + __func__, bcs->ap, bcs->apconnstate); + ap->bcnext = NULL; + bcs->ap = ap; + bcs->apconnstate = APCONN_SETUP; + spin_unlock_irqrestore(&bcs->aplock, flags); + + bcs->rx_bufsize = ap->rp.datablklen; + dev_kfree_skb(bcs->rx_skb); + gigaset_new_rx_skb(bcs); + cmsg->adr.adrPLCI |= (bcs->channel + 1) << 8; + + /* build command table */ + commands = kcalloc(AT_NUM, sizeof(*commands), GFP_KERNEL); + if (!commands) + goto oom; + + /* encode parameter: Called party number */ + pp = cmsg->CalledPartyNumber; + if (pp == NULL || *pp == 0) { + dev_notice(cs->dev, "%s: %s missing\n", + "CONNECT_REQ", "Called party number"); + info = CapiIllMessageParmCoding; + goto error; + } + l = *pp++; + /* check type of number/numbering plan byte */ + switch (*pp) { + case 0x80: /* unknown type / unknown numbering plan */ + case 0x81: /* unknown type / ISDN/Telephony numbering plan */ + break; + default: /* others: warn about potential misinterpretation */ + dev_notice(cs->dev, "%s: %s type/plan 0x%02x unsupported\n", + "CONNECT_REQ", "Called party number", *pp); + } + pp++; + l--; + /* translate "**" internal call prefix to CTP value */ + if (l >= 2 && pp[0] == '*' && pp[1] == '*') { + s = "^SCTP=0\r"; + pp += 2; + l -= 2; + } else { + s = "^SCTP=1\r"; + } + commands[AT_TYPE] = kstrdup(s, GFP_KERNEL); + if (!commands[AT_TYPE]) + goto oom; + commands[AT_DIAL] = kmalloc(l + 3, GFP_KERNEL); + if (!commands[AT_DIAL]) + goto oom; + snprintf(commands[AT_DIAL], l + 3, "D%.*s\r", l, pp); + + /* encode parameter: Calling party number */ + pp = cmsg->CallingPartyNumber; + if (pp != NULL && *pp > 0) { + l = *pp++; + + /* check type of number/numbering plan byte */ + /* ToDo: allow for/handle Ext=1? */ + switch (*pp) { + case 0x00: /* unknown type / unknown numbering plan */ + case 0x01: /* unknown type / ISDN/Telephony num. plan */ + break; + default: + dev_notice(cs->dev, + "%s: %s type/plan 0x%02x unsupported\n", + "CONNECT_REQ", "Calling party number", *pp); + } + pp++; + l--; + + /* check presentation indicator */ + if (!l) { + dev_notice(cs->dev, "%s: %s IE truncated\n", + "CONNECT_REQ", "Calling party number"); + info = CapiIllMessageParmCoding; + goto error; + } + switch (*pp & 0xfc) { /* ignore Screening indicator */ + case 0x80: /* Presentation allowed */ + s = "^SCLIP=1\r"; + break; + case 0xa0: /* Presentation restricted */ + s = "^SCLIP=0\r"; + break; + default: + dev_notice(cs->dev, "%s: invalid %s 0x%02x\n", + "CONNECT_REQ", + "Presentation/Screening indicator", + *pp); + s = "^SCLIP=1\r"; + } + commands[AT_CLIP] = kstrdup(s, GFP_KERNEL); + if (!commands[AT_CLIP]) + goto oom; + pp++; + l--; + + if (l) { + /* number */ + commands[AT_MSN] = kmalloc(l + 8, GFP_KERNEL); + if (!commands[AT_MSN]) + goto oom; + snprintf(commands[AT_MSN], l + 8, "^SMSN=%*s\r", l, pp); + } + } + + /* check parameter: CIP Value */ + if (cmsg->CIPValue >= ARRAY_SIZE(cip2bchlc) || + (cmsg->CIPValue > 0 && cip2bchlc[cmsg->CIPValue].bc == NULL)) { + dev_notice(cs->dev, "%s: unknown CIP value %d\n", + "CONNECT_REQ", cmsg->CIPValue); + info = CapiCipValueUnknown; + goto error; + } + + /* + * check/encode parameters: BC & HLC + * must be encoded together as device doesn't accept HLC separately + * explicit parameters override values derived from CIP + */ + + /* determine lengths */ + if (cmsg->BC && cmsg->BC[0]) /* BC specified explicitly */ + lbc = 2 * cmsg->BC[0]; + else if (cip2bchlc[cmsg->CIPValue].bc) /* BC derived from CIP */ + lbc = strlen(cip2bchlc[cmsg->CIPValue].bc); + else /* no BC */ + lbc = 0; + if (cmsg->HLC && cmsg->HLC[0]) /* HLC specified explicitly */ + lhlc = 2 * cmsg->HLC[0]; + else if (cip2bchlc[cmsg->CIPValue].hlc) /* HLC derived from CIP */ + lhlc = strlen(cip2bchlc[cmsg->CIPValue].hlc); + else /* no HLC */ + lhlc = 0; + + if (lbc) { + /* have BC: allocate and assemble command string */ + l = lbc + 7; /* "^SBC=" + value + "\r" + null byte */ + if (lhlc) + l += lhlc + 7; /* ";^SHLC=" + value */ + commands[AT_BC] = kmalloc(l, GFP_KERNEL); + if (!commands[AT_BC]) + goto oom; + strcpy(commands[AT_BC], "^SBC="); + if (cmsg->BC && cmsg->BC[0]) /* BC specified explicitly */ + decode_ie(cmsg->BC, commands[AT_BC] + 5); + else /* BC derived from CIP */ + strcpy(commands[AT_BC] + 5, + cip2bchlc[cmsg->CIPValue].bc); + if (lhlc) { + strcpy(commands[AT_BC] + lbc + 5, ";^SHLC="); + if (cmsg->HLC && cmsg->HLC[0]) + /* HLC specified explicitly */ + decode_ie(cmsg->HLC, + commands[AT_BC] + lbc + 12); + else /* HLC derived from CIP */ + strcpy(commands[AT_BC] + lbc + 12, + cip2bchlc[cmsg->CIPValue].hlc); + } + strcpy(commands[AT_BC] + l - 2, "\r"); + } else { + /* no BC */ + if (lhlc) { + dev_notice(cs->dev, "%s: cannot set HLC without BC\n", + "CONNECT_REQ"); + info = CapiIllMessageParmCoding; /* ? */ + goto error; + } + } + + /* check/encode parameter: B Protocol */ + if (cmsg->BProtocol == CAPI_DEFAULT) { + bcs->proto2 = L2_HDLC; + dev_warn(cs->dev, + "B2 Protocol X.75 SLP unsupported, using Transparent\n"); + } else { + switch (cmsg->B1protocol) { + case 0: + bcs->proto2 = L2_HDLC; + break; + case 1: + bcs->proto2 = L2_VOICE; + break; + default: + dev_warn(cs->dev, + "B1 Protocol %u unsupported, using Transparent\n", + cmsg->B1protocol); + bcs->proto2 = L2_VOICE; + } + if (cmsg->B2protocol != 1) + dev_warn(cs->dev, + "B2 Protocol %u unsupported, using Transparent\n", + cmsg->B2protocol); + if (cmsg->B3protocol != 0) + dev_warn(cs->dev, + "B3 Protocol %u unsupported, using Transparent\n", + cmsg->B3protocol); + ignore_cstruct_param(cs, cmsg->B1configuration, + "CONNECT_REQ", "B1 Configuration"); + ignore_cstruct_param(cs, cmsg->B2configuration, + "CONNECT_REQ", "B2 Configuration"); + ignore_cstruct_param(cs, cmsg->B3configuration, + "CONNECT_REQ", "B3 Configuration"); + } + commands[AT_PROTO] = kmalloc(9, GFP_KERNEL); + if (!commands[AT_PROTO]) + goto oom; + snprintf(commands[AT_PROTO], 9, "^SBPR=%u\r", bcs->proto2); + + /* ToDo: check/encode remaining parameters */ + ignore_cstruct_param(cs, cmsg->CalledPartySubaddress, + "CONNECT_REQ", "Called pty subaddr"); + ignore_cstruct_param(cs, cmsg->CallingPartySubaddress, + "CONNECT_REQ", "Calling pty subaddr"); + ignore_cstruct_param(cs, cmsg->LLC, + "CONNECT_REQ", "LLC"); + if (cmsg->AdditionalInfo != CAPI_DEFAULT) { + ignore_cstruct_param(cs, cmsg->BChannelinformation, + "CONNECT_REQ", "B Channel Information"); + ignore_cstruct_param(cs, cmsg->Keypadfacility, + "CONNECT_REQ", "Keypad Facility"); + ignore_cstruct_param(cs, cmsg->Useruserdata, + "CONNECT_REQ", "User-User Data"); + ignore_cstruct_param(cs, cmsg->Facilitydataarray, + "CONNECT_REQ", "Facility Data Array"); + } + + /* encode parameter: B channel to use */ + commands[AT_ISO] = kmalloc(9, GFP_KERNEL); + if (!commands[AT_ISO]) + goto oom; + snprintf(commands[AT_ISO], 9, "^SISO=%u\r", + (unsigned) bcs->channel + 1); + + /* queue & schedule EV_DIAL event */ + if (!gigaset_add_event(cs, &bcs->at_state, EV_DIAL, commands, + bcs->at_state.seq_index, NULL)) { + info = CAPI_MSGOSRESOURCEERR; + goto error; + } + gigaset_schedule_event(cs); + send_conf(iif, ap, skb, CapiSuccess); + return; + +oom: + dev_err(cs->dev, "%s: out of memory\n", __func__); + info = CAPI_MSGOSRESOURCEERR; +error: + if (commands) + for (i = 0; i < AT_NUM; i++) + kfree(commands[i]); + kfree(commands); + gigaset_free_channel(bcs); + send_conf(iif, ap, skb, info); +} + +/* + * process CONNECT_RESP message + * checks protocol parameters and queues an ACCEPT or HUP event + */ +static void do_connect_resp(struct gigaset_capi_ctr *iif, + struct gigaset_capi_appl *ap, + struct sk_buff *skb) +{ + struct cardstate *cs = iif->ctr.driverdata; + _cmsg *cmsg = &iif->acmsg; + struct bc_state *bcs; + struct gigaset_capi_appl *oap; + unsigned long flags; + int channel; + + /* decode message */ + if (capi_message2cmsg(cmsg, skb->data)) { + dev_err(cs->dev, "%s: message parser failure\n", __func__); + dev_kfree_skb_any(skb); + return; + } + dump_cmsg(DEBUG_CMD, __func__, cmsg); + dev_kfree_skb_any(skb); + + /* extract and check channel number from PLCI */ + channel = (cmsg->adr.adrPLCI >> 8) & 0xff; + if (!channel || channel > cs->channels) { + dev_notice(cs->dev, "%s: invalid %s 0x%02x\n", + "CONNECT_RESP", "PLCI", cmsg->adr.adrPLCI); + return; + } + bcs = cs->bcs + channel - 1; + + switch (cmsg->Reject) { + case 0: /* Accept */ + /* drop all competing applications, keep only this one */ + spin_lock_irqsave(&bcs->aplock, flags); + while (bcs->ap != NULL) { + oap = bcs->ap; + bcs->ap = oap->bcnext; + if (oap != ap) { + spin_unlock_irqrestore(&bcs->aplock, flags); + send_disconnect_ind(bcs, oap, + CapiCallGivenToOtherApplication); + spin_lock_irqsave(&bcs->aplock, flags); + } + } + ap->bcnext = NULL; + bcs->ap = ap; + spin_unlock_irqrestore(&bcs->aplock, flags); + + bcs->rx_bufsize = ap->rp.datablklen; + dev_kfree_skb(bcs->rx_skb); + gigaset_new_rx_skb(bcs); + bcs->chstate |= CHS_NOTIFY_LL; + + /* check/encode B channel protocol */ + if (cmsg->BProtocol == CAPI_DEFAULT) { + bcs->proto2 = L2_HDLC; + dev_warn(cs->dev, + "B2 Protocol X.75 SLP unsupported, using Transparent\n"); + } else { + switch (cmsg->B1protocol) { + case 0: + bcs->proto2 = L2_HDLC; + break; + case 1: + bcs->proto2 = L2_VOICE; + break; + default: + dev_warn(cs->dev, + "B1 Protocol %u unsupported, using Transparent\n", + cmsg->B1protocol); + bcs->proto2 = L2_VOICE; + } + if (cmsg->B2protocol != 1) + dev_warn(cs->dev, + "B2 Protocol %u unsupported, using Transparent\n", + cmsg->B2protocol); + if (cmsg->B3protocol != 0) + dev_warn(cs->dev, + "B3 Protocol %u unsupported, using Transparent\n", + cmsg->B3protocol); + ignore_cstruct_param(cs, cmsg->B1configuration, + "CONNECT_RESP", "B1 Configuration"); + ignore_cstruct_param(cs, cmsg->B2configuration, + "CONNECT_RESP", "B2 Configuration"); + ignore_cstruct_param(cs, cmsg->B3configuration, + "CONNECT_RESP", "B3 Configuration"); + } + + /* ToDo: check/encode remaining parameters */ + ignore_cstruct_param(cs, cmsg->ConnectedNumber, + "CONNECT_RESP", "Connected Number"); + ignore_cstruct_param(cs, cmsg->ConnectedSubaddress, + "CONNECT_RESP", "Connected Subaddress"); + ignore_cstruct_param(cs, cmsg->LLC, + "CONNECT_RESP", "LLC"); + if (cmsg->AdditionalInfo != CAPI_DEFAULT) { + ignore_cstruct_param(cs, cmsg->BChannelinformation, + "CONNECT_RESP", "BChannel Information"); + ignore_cstruct_param(cs, cmsg->Keypadfacility, + "CONNECT_RESP", "Keypad Facility"); + ignore_cstruct_param(cs, cmsg->Useruserdata, + "CONNECT_RESP", "User-User Data"); + ignore_cstruct_param(cs, cmsg->Facilitydataarray, + "CONNECT_RESP", "Facility Data Array"); + } + + /* Accept call */ + if (!gigaset_add_event(cs, &cs->bcs[channel - 1].at_state, + EV_ACCEPT, NULL, 0, NULL)) + return; + gigaset_schedule_event(cs); + return; + + case 1: /* Ignore */ + /* send DISCONNECT_IND to this application */ + send_disconnect_ind(bcs, ap, 0); + + /* remove it from the list of listening apps */ + spin_lock_irqsave(&bcs->aplock, flags); + if (bcs->ap == ap) { + bcs->ap = ap->bcnext; + if (bcs->ap == NULL) { + /* last one: stop ev-layer hupD notifications */ + bcs->apconnstate = APCONN_NONE; + bcs->chstate &= ~CHS_NOTIFY_LL; + } + spin_unlock_irqrestore(&bcs->aplock, flags); + return; + } + for (oap = bcs->ap; oap != NULL; oap = oap->bcnext) { + if (oap->bcnext == ap) { + oap->bcnext = oap->bcnext->bcnext; + spin_unlock_irqrestore(&bcs->aplock, flags); + return; + } + } + spin_unlock_irqrestore(&bcs->aplock, flags); + dev_err(cs->dev, "%s: application %u not found\n", + __func__, ap->id); + return; + + default: /* Reject */ + /* drop all competing applications, keep only this one */ + spin_lock_irqsave(&bcs->aplock, flags); + while (bcs->ap != NULL) { + oap = bcs->ap; + bcs->ap = oap->bcnext; + if (oap != ap) { + spin_unlock_irqrestore(&bcs->aplock, flags); + send_disconnect_ind(bcs, oap, + CapiCallGivenToOtherApplication); + spin_lock_irqsave(&bcs->aplock, flags); + } + } + ap->bcnext = NULL; + bcs->ap = ap; + spin_unlock_irqrestore(&bcs->aplock, flags); + + /* reject call - will trigger DISCONNECT_IND for this app */ + dev_info(cs->dev, "%s: Reject=%x\n", + "CONNECT_RESP", cmsg->Reject); + if (!gigaset_add_event(cs, &cs->bcs[channel - 1].at_state, + EV_HUP, NULL, 0, NULL)) + return; + gigaset_schedule_event(cs); + return; + } +} + +/* + * process CONNECT_B3_REQ message + * build NCCI and emit CONNECT_B3_CONF reply + */ +static void do_connect_b3_req(struct gigaset_capi_ctr *iif, + struct gigaset_capi_appl *ap, + struct sk_buff *skb) +{ + struct cardstate *cs = iif->ctr.driverdata; + _cmsg *cmsg = &iif->acmsg; + struct bc_state *bcs; + int channel; + + /* decode message */ + if (capi_message2cmsg(cmsg, skb->data)) { + dev_err(cs->dev, "%s: message parser failure\n", __func__); + dev_kfree_skb_any(skb); + return; + } + dump_cmsg(DEBUG_CMD, __func__, cmsg); + + /* extract and check channel number from PLCI */ + channel = (cmsg->adr.adrPLCI >> 8) & 0xff; + if (!channel || channel > cs->channels) { + dev_notice(cs->dev, "%s: invalid %s 0x%02x\n", + "CONNECT_B3_REQ", "PLCI", cmsg->adr.adrPLCI); + send_conf(iif, ap, skb, CapiIllContrPlciNcci); + return; + } + bcs = &cs->bcs[channel - 1]; + + /* mark logical connection active */ + bcs->apconnstate = APCONN_ACTIVE; + + /* build NCCI: always 1 (one B3 connection only) */ + cmsg->adr.adrNCCI |= 1 << 16; + + /* NCPI parameter: not applicable for B3 Transparent */ + ignore_cstruct_param(cs, cmsg->NCPI, "CONNECT_B3_REQ", "NCPI"); + send_conf(iif, ap, skb, + (cmsg->NCPI && cmsg->NCPI[0]) ? + CapiNcpiNotSupportedByProtocol : CapiSuccess); +} + +/* + * process CONNECT_B3_RESP message + * Depending on the Reject parameter, either emit CONNECT_B3_ACTIVE_IND + * or queue EV_HUP and emit DISCONNECT_B3_IND. + * The emitted message is always shorter than the received one, + * allowing to reuse the skb. + */ +static void do_connect_b3_resp(struct gigaset_capi_ctr *iif, + struct gigaset_capi_appl *ap, + struct sk_buff *skb) +{ + struct cardstate *cs = iif->ctr.driverdata; + _cmsg *cmsg = &iif->acmsg; + struct bc_state *bcs; + int channel; + unsigned int msgsize; + u8 command; + + /* decode message */ + if (capi_message2cmsg(cmsg, skb->data)) { + dev_err(cs->dev, "%s: message parser failure\n", __func__); + dev_kfree_skb_any(skb); + return; + } + dump_cmsg(DEBUG_CMD, __func__, cmsg); + + /* extract and check channel number and NCCI */ + channel = (cmsg->adr.adrNCCI >> 8) & 0xff; + if (!channel || channel > cs->channels || + ((cmsg->adr.adrNCCI >> 16) & 0xffff) != 1) { + dev_notice(cs->dev, "%s: invalid %s 0x%02x\n", + "CONNECT_B3_RESP", "NCCI", cmsg->adr.adrNCCI); + dev_kfree_skb_any(skb); + return; + } + bcs = &cs->bcs[channel - 1]; + + if (cmsg->Reject) { + /* Reject: clear B3 connect received flag */ + bcs->apconnstate = APCONN_SETUP; + + /* trigger hangup, causing eventual DISCONNECT_IND */ + if (!gigaset_add_event(cs, &bcs->at_state, + EV_HUP, NULL, 0, NULL)) { + dev_kfree_skb_any(skb); + return; + } + gigaset_schedule_event(cs); + + /* emit DISCONNECT_B3_IND */ + command = CAPI_DISCONNECT_B3; + msgsize = CAPI_DISCONNECT_B3_IND_BASELEN; + } else { + /* + * Accept: emit CONNECT_B3_ACTIVE_IND immediately, as + * we only send CONNECT_B3_IND if the B channel is up + */ + command = CAPI_CONNECT_B3_ACTIVE; + msgsize = CAPI_CONNECT_B3_ACTIVE_IND_BASELEN; + } + capi_cmsg_header(cmsg, ap->id, command, CAPI_IND, + ap->nextMessageNumber++, cmsg->adr.adrNCCI); + __skb_trim(skb, msgsize); + if (capi_cmsg2message(cmsg, skb->data)) { + dev_err(cs->dev, "%s: message parser failure\n", __func__); + dev_kfree_skb_any(skb); + return; + } + dump_cmsg(DEBUG_CMD, __func__, cmsg); + capi_ctr_handle_message(&iif->ctr, ap->id, skb); +} + +/* + * process DISCONNECT_REQ message + * schedule EV_HUP and emit DISCONNECT_B3_IND if necessary, + * emit DISCONNECT_CONF reply + */ +static void do_disconnect_req(struct gigaset_capi_ctr *iif, + struct gigaset_capi_appl *ap, + struct sk_buff *skb) +{ + struct cardstate *cs = iif->ctr.driverdata; + _cmsg *cmsg = &iif->acmsg; + struct bc_state *bcs; + _cmsg *b3cmsg; + struct sk_buff *b3skb; + int channel; + + /* decode message */ + if (capi_message2cmsg(cmsg, skb->data)) { + dev_err(cs->dev, "%s: message parser failure\n", __func__); + dev_kfree_skb_any(skb); + return; + } + dump_cmsg(DEBUG_CMD, __func__, cmsg); + + /* extract and check channel number from PLCI */ + channel = (cmsg->adr.adrPLCI >> 8) & 0xff; + if (!channel || channel > cs->channels) { + dev_notice(cs->dev, "%s: invalid %s 0x%02x\n", + "DISCONNECT_REQ", "PLCI", cmsg->adr.adrPLCI); + send_conf(iif, ap, skb, CapiIllContrPlciNcci); + return; + } + bcs = cs->bcs + channel - 1; + + /* ToDo: process parameter: Additional info */ + if (cmsg->AdditionalInfo != CAPI_DEFAULT) { + ignore_cstruct_param(cs, cmsg->BChannelinformation, + "DISCONNECT_REQ", "B Channel Information"); + ignore_cstruct_param(cs, cmsg->Keypadfacility, + "DISCONNECT_REQ", "Keypad Facility"); + ignore_cstruct_param(cs, cmsg->Useruserdata, + "DISCONNECT_REQ", "User-User Data"); + ignore_cstruct_param(cs, cmsg->Facilitydataarray, + "DISCONNECT_REQ", "Facility Data Array"); + } + + /* skip if DISCONNECT_IND already sent */ + if (!bcs->apconnstate) + return; + + /* check for active logical connection */ + if (bcs->apconnstate >= APCONN_ACTIVE) { + /* clear it */ + bcs->apconnstate = APCONN_SETUP; + + /* + * emit DISCONNECT_B3_IND with cause 0x3301 + * use separate cmsg structure, as the content of iif->acmsg + * is still needed for creating the _CONF message + */ + b3cmsg = kmalloc(sizeof(*b3cmsg), GFP_KERNEL); + if (!b3cmsg) { + dev_err(cs->dev, "%s: out of memory\n", __func__); + send_conf(iif, ap, skb, CAPI_MSGOSRESOURCEERR); + return; + } + capi_cmsg_header(b3cmsg, ap->id, CAPI_DISCONNECT_B3, CAPI_IND, + ap->nextMessageNumber++, + cmsg->adr.adrPLCI | (1 << 16)); + b3cmsg->Reason_B3 = CapiProtocolErrorLayer1; + b3skb = alloc_skb(CAPI_DISCONNECT_B3_IND_BASELEN, GFP_KERNEL); + if (b3skb == NULL) { + dev_err(cs->dev, "%s: out of memory\n", __func__); + send_conf(iif, ap, skb, CAPI_MSGOSRESOURCEERR); + kfree(b3cmsg); + return; + } + if (capi_cmsg2message(b3cmsg, + __skb_put(b3skb, CAPI_DISCONNECT_B3_IND_BASELEN))) { + dev_err(cs->dev, "%s: message parser failure\n", + __func__); + kfree(b3cmsg); + dev_kfree_skb_any(b3skb); + return; + } + dump_cmsg(DEBUG_CMD, __func__, b3cmsg); + kfree(b3cmsg); + capi_ctr_handle_message(&iif->ctr, ap->id, b3skb); + } + + /* trigger hangup, causing eventual DISCONNECT_IND */ + if (!gigaset_add_event(cs, &bcs->at_state, EV_HUP, NULL, 0, NULL)) { + send_conf(iif, ap, skb, CAPI_MSGOSRESOURCEERR); + return; + } + gigaset_schedule_event(cs); + + /* emit reply */ + send_conf(iif, ap, skb, CapiSuccess); +} + +/* + * process DISCONNECT_B3_REQ message + * schedule EV_HUP and emit DISCONNECT_B3_CONF reply + */ +static void do_disconnect_b3_req(struct gigaset_capi_ctr *iif, + struct gigaset_capi_appl *ap, + struct sk_buff *skb) +{ + struct cardstate *cs = iif->ctr.driverdata; + _cmsg *cmsg = &iif->acmsg; + struct bc_state *bcs; + int channel; + + /* decode message */ + if (capi_message2cmsg(cmsg, skb->data)) { + dev_err(cs->dev, "%s: message parser failure\n", __func__); + dev_kfree_skb_any(skb); + return; + } + dump_cmsg(DEBUG_CMD, __func__, cmsg); + + /* extract and check channel number and NCCI */ + channel = (cmsg->adr.adrNCCI >> 8) & 0xff; + if (!channel || channel > cs->channels || + ((cmsg->adr.adrNCCI >> 16) & 0xffff) != 1) { + dev_notice(cs->dev, "%s: invalid %s 0x%02x\n", + "DISCONNECT_B3_REQ", "NCCI", cmsg->adr.adrNCCI); + send_conf(iif, ap, skb, CapiIllContrPlciNcci); + return; + } + bcs = &cs->bcs[channel - 1]; + + /* reject if logical connection not active */ + if (bcs->apconnstate < APCONN_ACTIVE) { + send_conf(iif, ap, skb, + CapiMessageNotSupportedInCurrentState); + return; + } + + /* trigger hangup, causing eventual DISCONNECT_B3_IND */ + if (!gigaset_add_event(cs, &bcs->at_state, EV_HUP, NULL, 0, NULL)) { + send_conf(iif, ap, skb, CAPI_MSGOSRESOURCEERR); + return; + } + gigaset_schedule_event(cs); + + /* NCPI parameter: not applicable for B3 Transparent */ + ignore_cstruct_param(cs, cmsg->NCPI, + "DISCONNECT_B3_REQ", "NCPI"); + send_conf(iif, ap, skb, + (cmsg->NCPI && cmsg->NCPI[0]) ? + CapiNcpiNotSupportedByProtocol : CapiSuccess); +} + +/* + * process DATA_B3_REQ message + */ +static void do_data_b3_req(struct gigaset_capi_ctr *iif, + struct gigaset_capi_appl *ap, + struct sk_buff *skb) +{ + struct cardstate *cs = iif->ctr.driverdata; + struct bc_state *bcs; + int channel = CAPIMSG_PLCI_PART(skb->data); + u16 ncci = CAPIMSG_NCCI_PART(skb->data); + u16 msglen = CAPIMSG_LEN(skb->data); + u16 datalen = CAPIMSG_DATALEN(skb->data); + u16 flags = CAPIMSG_FLAGS(skb->data); + u16 msgid = CAPIMSG_MSGID(skb->data); + u16 handle = CAPIMSG_HANDLE_REQ(skb->data); + + /* frequent message, avoid _cmsg overhead */ + dump_rawmsg(DEBUG_MCMD, __func__, skb->data); + + /* check parameters */ + if (channel == 0 || channel > cs->channels || ncci != 1) { + dev_notice(cs->dev, "%s: invalid %s 0x%02x\n", + "DATA_B3_REQ", "NCCI", CAPIMSG_NCCI(skb->data)); + send_conf(iif, ap, skb, CapiIllContrPlciNcci); + return; + } + bcs = &cs->bcs[channel - 1]; + if (msglen != CAPI_DATA_B3_REQ_LEN && msglen != CAPI_DATA_B3_REQ_LEN64) + dev_notice(cs->dev, "%s: unexpected length %d\n", + "DATA_B3_REQ", msglen); + if (msglen + datalen != skb->len) + dev_notice(cs->dev, "%s: length mismatch (%d+%d!=%d)\n", + "DATA_B3_REQ", msglen, datalen, skb->len); + if (msglen + datalen > skb->len) { + /* message too short for announced data length */ + send_conf(iif, ap, skb, CapiIllMessageParmCoding); /* ? */ + return; + } + if (flags & CAPI_FLAGS_RESERVED) { + dev_notice(cs->dev, "%s: reserved flags set (%x)\n", + "DATA_B3_REQ", flags); + send_conf(iif, ap, skb, CapiIllMessageParmCoding); + return; + } + + /* reject if logical connection not active */ + if (bcs->apconnstate < APCONN_ACTIVE) { + send_conf(iif, ap, skb, CapiMessageNotSupportedInCurrentState); + return; + } + + /* pull CAPI message into link layer header */ + skb_reset_mac_header(skb); + skb->mac_len = msglen; + skb_pull(skb, msglen); + + /* pass to device-specific module */ + if (cs->ops->send_skb(bcs, skb) < 0) { + send_conf(iif, ap, skb, CAPI_MSGOSRESOURCEERR); + return; + } + + /* + * DATA_B3_CONF will be sent by gigaset_skb_sent() only if "delivery + * confirmation" bit is set; otherwise we have to send it now + */ + if (!(flags & CAPI_FLAGS_DELIVERY_CONFIRMATION)) + send_data_b3_conf(cs, &iif->ctr, ap->id, msgid, channel, handle, + flags ? CapiFlagsNotSupportedByProtocol + : CAPI_NOERROR); +} + +/* + * process RESET_B3_REQ message + * just always reply "not supported by current protocol" + */ +static void do_reset_b3_req(struct gigaset_capi_ctr *iif, + struct gigaset_capi_appl *ap, + struct sk_buff *skb) +{ + struct cardstate *cs = iif->ctr.driverdata; + + /* decode message */ + if (capi_message2cmsg(&iif->acmsg, skb->data)) { + dev_err(cs->dev, "%s: message parser failure\n", __func__); + dev_kfree_skb_any(skb); + return; + } + dump_cmsg(DEBUG_CMD, __func__, &iif->acmsg); + send_conf(iif, ap, skb, + CapiResetProcedureNotSupportedByCurrentProtocol); +} + +/* + * unsupported CAPI message handler + */ +static void do_unsupported(struct gigaset_capi_ctr *iif, + struct gigaset_capi_appl *ap, + struct sk_buff *skb) +{ + struct cardstate *cs = iif->ctr.driverdata; + + /* decode message */ + if (capi_message2cmsg(&iif->acmsg, skb->data)) { + dev_err(cs->dev, "%s: message parser failure\n", __func__); + dev_kfree_skb_any(skb); + return; + } + dump_cmsg(DEBUG_CMD, __func__, &iif->acmsg); + send_conf(iif, ap, skb, CapiMessageNotSupportedInCurrentState); +} + +/* + * CAPI message handler: no-op + */ +static void do_nothing(struct gigaset_capi_ctr *iif, + struct gigaset_capi_appl *ap, + struct sk_buff *skb) +{ + struct cardstate *cs = iif->ctr.driverdata; + + /* decode message */ + if (capi_message2cmsg(&iif->acmsg, skb->data)) { + dev_err(cs->dev, "%s: message parser failure\n", __func__); + dev_kfree_skb_any(skb); + return; + } + dump_cmsg(DEBUG_CMD, __func__, &iif->acmsg); + dev_kfree_skb_any(skb); +} + +static void do_data_b3_resp(struct gigaset_capi_ctr *iif, + struct gigaset_capi_appl *ap, + struct sk_buff *skb) +{ + dump_rawmsg(DEBUG_MCMD, __func__, skb->data); + dev_kfree_skb_any(skb); +} + +/* table of outgoing CAPI message handlers with lookup function */ +typedef void (*capi_send_handler_t)(struct gigaset_capi_ctr *, + struct gigaset_capi_appl *, + struct sk_buff *); + +static struct { + u16 cmd; + capi_send_handler_t handler; +} capi_send_handler_table[] = { + /* most frequent messages first for faster lookup */ + { CAPI_DATA_B3_REQ, do_data_b3_req }, + { CAPI_DATA_B3_RESP, do_data_b3_resp }, + + { CAPI_ALERT_REQ, do_alert_req }, + { CAPI_CONNECT_ACTIVE_RESP, do_nothing }, + { CAPI_CONNECT_B3_ACTIVE_RESP, do_nothing }, + { CAPI_CONNECT_B3_REQ, do_connect_b3_req }, + { CAPI_CONNECT_B3_RESP, do_connect_b3_resp }, + { CAPI_CONNECT_B3_T90_ACTIVE_RESP, do_nothing }, + { CAPI_CONNECT_REQ, do_connect_req }, + { CAPI_CONNECT_RESP, do_connect_resp }, + { CAPI_DISCONNECT_B3_REQ, do_disconnect_b3_req }, + { CAPI_DISCONNECT_B3_RESP, do_nothing }, + { CAPI_DISCONNECT_REQ, do_disconnect_req }, + { CAPI_DISCONNECT_RESP, do_nothing }, + { CAPI_FACILITY_REQ, do_facility_req }, + { CAPI_FACILITY_RESP, do_nothing }, + { CAPI_LISTEN_REQ, do_listen_req }, + { CAPI_SELECT_B_PROTOCOL_REQ, do_unsupported }, + { CAPI_RESET_B3_REQ, do_reset_b3_req }, + { CAPI_RESET_B3_RESP, do_nothing }, + + /* + * ToDo: support overlap sending (requires ev-layer state + * machine extension to generate additional ATD commands) + */ + { CAPI_INFO_REQ, do_unsupported }, + { CAPI_INFO_RESP, do_nothing }, + + /* + * ToDo: what's the proper response for these? + */ + { CAPI_MANUFACTURER_REQ, do_nothing }, + { CAPI_MANUFACTURER_RESP, do_nothing }, +}; + +/* look up handler */ +static inline capi_send_handler_t lookup_capi_send_handler(const u16 cmd) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(capi_send_handler_table); i++) + if (capi_send_handler_table[i].cmd == cmd) + return capi_send_handler_table[i].handler; + return NULL; +} + + +/** + * gigaset_send_message() - accept a CAPI message from an application + * @ctr: controller descriptor structure. + * @skb: CAPI message. + * + * Return value: CAPI error code + * Note: capidrv (and probably others, too) only uses the return value to + * decide whether it has to free the skb (only if result != CAPI_NOERROR (0)) + */ +static u16 gigaset_send_message(struct capi_ctr *ctr, struct sk_buff *skb) +{ + struct gigaset_capi_ctr *iif + = container_of(ctr, struct gigaset_capi_ctr, ctr); + struct cardstate *cs = ctr->driverdata; + struct gigaset_capi_appl *ap; + capi_send_handler_t handler; + + /* can only handle linear sk_buffs */ + if (skb_linearize(skb) < 0) { + dev_warn(cs->dev, "%s: skb_linearize failed\n", __func__); + return CAPI_MSGOSRESOURCEERR; + } + + /* retrieve application data structure */ + ap = get_appl(iif, CAPIMSG_APPID(skb->data)); + if (!ap) { + dev_notice(cs->dev, "%s: application %u not registered\n", + __func__, CAPIMSG_APPID(skb->data)); + return CAPI_ILLAPPNR; + } + + /* look up command */ + handler = lookup_capi_send_handler(CAPIMSG_CMD(skb->data)); + if (!handler) { + /* unknown/unsupported message type */ + if (printk_ratelimit()) + dev_notice(cs->dev, "%s: unsupported message %u\n", + __func__, CAPIMSG_CMD(skb->data)); + return CAPI_ILLCMDORSUBCMDORMSGTOSMALL; + } + + /* serialize */ + if (atomic_add_return(1, &iif->sendqlen) > 1) { + /* queue behind other messages */ + skb_queue_tail(&iif->sendqueue, skb); + return CAPI_NOERROR; + } + + /* process message */ + handler(iif, ap, skb); + + /* process other messages arrived in the meantime */ + while (atomic_sub_return(1, &iif->sendqlen) > 0) { + skb = skb_dequeue(&iif->sendqueue); + if (!skb) { + /* should never happen */ + dev_err(cs->dev, "%s: send queue empty\n", __func__); + continue; + } + ap = get_appl(iif, CAPIMSG_APPID(skb->data)); + if (!ap) { + /* could that happen? */ + dev_warn(cs->dev, "%s: application %u vanished\n", + __func__, CAPIMSG_APPID(skb->data)); + continue; + } + handler = lookup_capi_send_handler(CAPIMSG_CMD(skb->data)); + if (!handler) { + /* should never happen */ + dev_err(cs->dev, "%s: handler %x vanished\n", + __func__, CAPIMSG_CMD(skb->data)); + continue; + } + handler(iif, ap, skb); + } + + return CAPI_NOERROR; +} + +/** + * gigaset_procinfo() - build single line description for controller + * @ctr: controller descriptor structure. + * + * Return value: pointer to generated string (null terminated) + */ +static char *gigaset_procinfo(struct capi_ctr *ctr) +{ + return ctr->name; /* ToDo: more? */ +} + +static int gigaset_proc_show(struct seq_file *m, void *v) +{ + struct capi_ctr *ctr = m->private; + struct cardstate *cs = ctr->driverdata; + char *s; + int i; + + seq_printf(m, "%-16s %s\n", "name", ctr->name); + seq_printf(m, "%-16s %s %s\n", "dev", + dev_driver_string(cs->dev), dev_name(cs->dev)); + seq_printf(m, "%-16s %d\n", "id", cs->myid); + if (cs->gotfwver) + seq_printf(m, "%-16s %d.%d.%d.%d\n", "firmware", + cs->fwver[0], cs->fwver[1], cs->fwver[2], cs->fwver[3]); + seq_printf(m, "%-16s %d\n", "channels", cs->channels); + seq_printf(m, "%-16s %s\n", "onechannel", cs->onechannel ? "yes" : "no"); + + switch (cs->mode) { + case M_UNKNOWN: + s = "unknown"; + break; + case M_CONFIG: + s = "config"; + break; + case M_UNIMODEM: + s = "Unimodem"; + break; + case M_CID: + s = "CID"; + break; + default: + s = "??"; + } + seq_printf(m, "%-16s %s\n", "mode", s); + + switch (cs->mstate) { + case MS_UNINITIALIZED: + s = "uninitialized"; + break; + case MS_INIT: + s = "init"; + break; + case MS_LOCKED: + s = "locked"; + break; + case MS_SHUTDOWN: + s = "shutdown"; + break; + case MS_RECOVER: + s = "recover"; + break; + case MS_READY: + s = "ready"; + break; + default: + s = "??"; + } + seq_printf(m, "%-16s %s\n", "mstate", s); + + seq_printf(m, "%-16s %s\n", "running", cs->running ? "yes" : "no"); + seq_printf(m, "%-16s %s\n", "connected", cs->connected ? "yes" : "no"); + seq_printf(m, "%-16s %s\n", "isdn_up", cs->isdn_up ? "yes" : "no"); + seq_printf(m, "%-16s %s\n", "cidmode", cs->cidmode ? "yes" : "no"); + + for (i = 0; i < cs->channels; i++) { + seq_printf(m, "[%d]%-13s %d\n", i, "corrupted", + cs->bcs[i].corrupted); + seq_printf(m, "[%d]%-13s %d\n", i, "trans_down", + cs->bcs[i].trans_down); + seq_printf(m, "[%d]%-13s %d\n", i, "trans_up", + cs->bcs[i].trans_up); + seq_printf(m, "[%d]%-13s %d\n", i, "chstate", + cs->bcs[i].chstate); + switch (cs->bcs[i].proto2) { + case L2_BITSYNC: + s = "bitsync"; + break; + case L2_HDLC: + s = "HDLC"; + break; + case L2_VOICE: + s = "voice"; + break; + default: + s = "??"; + } + seq_printf(m, "[%d]%-13s %s\n", i, "proto2", s); + } + return 0; +} + +/** + * gigaset_isdn_regdev() - register device to LL + * @cs: device descriptor structure. + * @isdnid: device name. + * + * Return value: 0 on success, error code < 0 on failure + */ +int gigaset_isdn_regdev(struct cardstate *cs, const char *isdnid) +{ + struct gigaset_capi_ctr *iif; + int rc; + + iif = kzalloc(sizeof(*iif), GFP_KERNEL); + if (!iif) { + pr_err("%s: out of memory\n", __func__); + return -ENOMEM; + } + + /* prepare controller structure */ + iif->ctr.owner = THIS_MODULE; + iif->ctr.driverdata = cs; + strncpy(iif->ctr.name, isdnid, sizeof(iif->ctr.name) - 1); + iif->ctr.driver_name = "gigaset"; + iif->ctr.load_firmware = NULL; + iif->ctr.reset_ctr = NULL; + iif->ctr.register_appl = gigaset_register_appl; + iif->ctr.release_appl = gigaset_release_appl; + iif->ctr.send_message = gigaset_send_message; + iif->ctr.procinfo = gigaset_procinfo; + iif->ctr.proc_show = gigaset_proc_show, + INIT_LIST_HEAD(&iif->appls); + skb_queue_head_init(&iif->sendqueue); + atomic_set(&iif->sendqlen, 0); + + /* register controller with CAPI */ + rc = attach_capi_ctr(&iif->ctr); + if (rc) { + pr_err("attach_capi_ctr failed (%d)\n", rc); + kfree(iif); + return rc; + } + + cs->iif = iif; + cs->hw_hdr_len = CAPI_DATA_B3_REQ_LEN; + return 0; +} + +/** + * gigaset_isdn_unregdev() - unregister device from LL + * @cs: device descriptor structure. + */ +void gigaset_isdn_unregdev(struct cardstate *cs) +{ + struct gigaset_capi_ctr *iif = cs->iif; + + detach_capi_ctr(&iif->ctr); + kfree(iif); + cs->iif = NULL; +} + +static struct capi_driver capi_driver_gigaset = { + .name = "gigaset", + .revision = "1.0", +}; + +/** + * gigaset_isdn_regdrv() - register driver to LL + */ +void gigaset_isdn_regdrv(void) +{ + pr_info("Kernel CAPI interface\n"); + register_capi_driver(&capi_driver_gigaset); +} + +/** + * gigaset_isdn_unregdrv() - unregister driver from LL + */ +void gigaset_isdn_unregdrv(void) +{ + unregister_capi_driver(&capi_driver_gigaset); +} diff --git a/drivers/staging/isdn/gigaset/common.c b/drivers/staging/isdn/gigaset/common.c new file mode 100644 index 000000000000..76b5407b5277 --- /dev/null +++ b/drivers/staging/isdn/gigaset/common.c @@ -0,0 +1,1156 @@ +/* + * Stuff used by all variants of the driver + * + * Copyright (c) 2001 by Stefan Eilers, + * Hansjoerg Lipp <hjlipp@web.de>, + * Tilman Schmidt <tilman@imap.cc>. + * + * ===================================================================== + * 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. + * ===================================================================== + */ + +#include "gigaset.h" +#include <linux/module.h> +#include <linux/moduleparam.h> + +/* Version Information */ +#define DRIVER_AUTHOR "Hansjoerg Lipp <hjlipp@web.de>, Tilman Schmidt <tilman@imap.cc>, Stefan Eilers" +#define DRIVER_DESC "Driver for Gigaset 307x" + +#ifdef CONFIG_GIGASET_DEBUG +#define DRIVER_DESC_DEBUG " (debug build)" +#else +#define DRIVER_DESC_DEBUG "" +#endif + +/* Module parameters */ +int gigaset_debuglevel; +EXPORT_SYMBOL_GPL(gigaset_debuglevel); +module_param_named(debug, gigaset_debuglevel, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "debug level"); + +/* driver state flags */ +#define VALID_MINOR 0x01 +#define VALID_ID 0x02 + +/** + * gigaset_dbg_buffer() - dump data in ASCII and hex for debugging + * @level: debugging level. + * @msg: message prefix. + * @len: number of bytes to dump. + * @buf: data to dump. + * + * If the current debugging level includes one of the bits set in @level, + * @len bytes starting at @buf are logged to dmesg at KERN_DEBUG prio, + * prefixed by the text @msg. + */ +void gigaset_dbg_buffer(enum debuglevel level, const unsigned char *msg, + size_t len, const unsigned char *buf) +{ + unsigned char outbuf[80]; + unsigned char c; + size_t space = sizeof outbuf - 1; + unsigned char *out = outbuf; + size_t numin = len; + + while (numin--) { + c = *buf++; + if (c == '~' || c == '^' || c == '\\') { + if (!space--) + break; + *out++ = '\\'; + } + if (c & 0x80) { + if (!space--) + break; + *out++ = '~'; + c ^= 0x80; + } + if (c < 0x20 || c == 0x7f) { + if (!space--) + break; + *out++ = '^'; + c ^= 0x40; + } + if (!space--) + break; + *out++ = c; + } + *out = 0; + + gig_dbg(level, "%s (%u bytes): %s", msg, (unsigned) len, outbuf); +} +EXPORT_SYMBOL_GPL(gigaset_dbg_buffer); + +static int setflags(struct cardstate *cs, unsigned flags, unsigned delay) +{ + int r; + + r = cs->ops->set_modem_ctrl(cs, cs->control_state, flags); + cs->control_state = flags; + if (r < 0) + return r; + + if (delay) { + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(delay * HZ / 1000); + } + + return 0; +} + +int gigaset_enterconfigmode(struct cardstate *cs) +{ + int i, r; + + cs->control_state = TIOCM_RTS; + + r = setflags(cs, TIOCM_DTR, 200); + if (r < 0) + goto error; + r = setflags(cs, 0, 200); + if (r < 0) + goto error; + for (i = 0; i < 5; ++i) { + r = setflags(cs, TIOCM_RTS, 100); + if (r < 0) + goto error; + r = setflags(cs, 0, 100); + if (r < 0) + goto error; + } + r = setflags(cs, TIOCM_RTS | TIOCM_DTR, 800); + if (r < 0) + goto error; + + return 0; + +error: + dev_err(cs->dev, "error %d on setuartbits\n", -r); + cs->control_state = TIOCM_RTS | TIOCM_DTR; + cs->ops->set_modem_ctrl(cs, 0, TIOCM_RTS | TIOCM_DTR); + + return -1; +} + +static int test_timeout(struct at_state_t *at_state) +{ + if (!at_state->timer_expires) + return 0; + + if (--at_state->timer_expires) { + gig_dbg(DEBUG_MCMD, "decreased timer of %p to %lu", + at_state, at_state->timer_expires); + return 0; + } + + gigaset_add_event(at_state->cs, at_state, EV_TIMEOUT, NULL, + at_state->timer_index, NULL); + return 1; +} + +static void timer_tick(struct timer_list *t) +{ + struct cardstate *cs = from_timer(cs, t, timer); + unsigned long flags; + unsigned channel; + struct at_state_t *at_state; + int timeout = 0; + + spin_lock_irqsave(&cs->lock, flags); + + for (channel = 0; channel < cs->channels; ++channel) + if (test_timeout(&cs->bcs[channel].at_state)) + timeout = 1; + + if (test_timeout(&cs->at_state)) + timeout = 1; + + list_for_each_entry(at_state, &cs->temp_at_states, list) + if (test_timeout(at_state)) + timeout = 1; + + if (cs->running) { + mod_timer(&cs->timer, jiffies + msecs_to_jiffies(GIG_TICK)); + if (timeout) { + gig_dbg(DEBUG_EVENT, "scheduling timeout"); + tasklet_schedule(&cs->event_tasklet); + } + } + + spin_unlock_irqrestore(&cs->lock, flags); +} + +int gigaset_get_channel(struct bc_state *bcs) +{ + unsigned long flags; + + spin_lock_irqsave(&bcs->cs->lock, flags); + if (bcs->use_count || !try_module_get(bcs->cs->driver->owner)) { + gig_dbg(DEBUG_CHANNEL, "could not allocate channel %d", + bcs->channel); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + return -EBUSY; + } + ++bcs->use_count; + bcs->busy = 1; + gig_dbg(DEBUG_CHANNEL, "allocated channel %d", bcs->channel); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + return 0; +} + +struct bc_state *gigaset_get_free_channel(struct cardstate *cs) +{ + unsigned long flags; + int i; + + spin_lock_irqsave(&cs->lock, flags); + if (!try_module_get(cs->driver->owner)) { + gig_dbg(DEBUG_CHANNEL, + "could not get module for allocating channel"); + spin_unlock_irqrestore(&cs->lock, flags); + return NULL; + } + for (i = 0; i < cs->channels; ++i) + if (!cs->bcs[i].use_count) { + ++cs->bcs[i].use_count; + cs->bcs[i].busy = 1; + spin_unlock_irqrestore(&cs->lock, flags); + gig_dbg(DEBUG_CHANNEL, "allocated channel %d", i); + return cs->bcs + i; + } + module_put(cs->driver->owner); + spin_unlock_irqrestore(&cs->lock, flags); + gig_dbg(DEBUG_CHANNEL, "no free channel"); + return NULL; +} + +void gigaset_free_channel(struct bc_state *bcs) +{ + unsigned long flags; + + spin_lock_irqsave(&bcs->cs->lock, flags); + if (!bcs->busy) { + gig_dbg(DEBUG_CHANNEL, "could not free channel %d", + bcs->channel); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + return; + } + --bcs->use_count; + bcs->busy = 0; + module_put(bcs->cs->driver->owner); + gig_dbg(DEBUG_CHANNEL, "freed channel %d", bcs->channel); + spin_unlock_irqrestore(&bcs->cs->lock, flags); +} + +int gigaset_get_channels(struct cardstate *cs) +{ + unsigned long flags; + int i; + + spin_lock_irqsave(&cs->lock, flags); + for (i = 0; i < cs->channels; ++i) + if (cs->bcs[i].use_count) { + spin_unlock_irqrestore(&cs->lock, flags); + gig_dbg(DEBUG_CHANNEL, + "could not allocate all channels"); + return -EBUSY; + } + for (i = 0; i < cs->channels; ++i) + ++cs->bcs[i].use_count; + spin_unlock_irqrestore(&cs->lock, flags); + + gig_dbg(DEBUG_CHANNEL, "allocated all channels"); + + return 0; +} + +void gigaset_free_channels(struct cardstate *cs) +{ + unsigned long flags; + int i; + + gig_dbg(DEBUG_CHANNEL, "unblocking all channels"); + spin_lock_irqsave(&cs->lock, flags); + for (i = 0; i < cs->channels; ++i) + --cs->bcs[i].use_count; + spin_unlock_irqrestore(&cs->lock, flags); +} + +void gigaset_block_channels(struct cardstate *cs) +{ + unsigned long flags; + int i; + + gig_dbg(DEBUG_CHANNEL, "blocking all channels"); + spin_lock_irqsave(&cs->lock, flags); + for (i = 0; i < cs->channels; ++i) + ++cs->bcs[i].use_count; + spin_unlock_irqrestore(&cs->lock, flags); +} + +static void clear_events(struct cardstate *cs) +{ + struct event_t *ev; + unsigned head, tail; + unsigned long flags; + + spin_lock_irqsave(&cs->ev_lock, flags); + + head = cs->ev_head; + tail = cs->ev_tail; + + while (tail != head) { + ev = cs->events + head; + kfree(ev->ptr); + head = (head + 1) % MAX_EVENTS; + } + + cs->ev_head = tail; + + spin_unlock_irqrestore(&cs->ev_lock, flags); +} + +/** + * gigaset_add_event() - add event to device event queue + * @cs: device descriptor structure. + * @at_state: connection state structure. + * @type: event type. + * @ptr: pointer parameter for event. + * @parameter: integer parameter for event. + * @arg: pointer parameter for event. + * + * Allocate an event queue entry from the device's event queue, and set it up + * with the parameters given. + * + * Return value: added event + */ +struct event_t *gigaset_add_event(struct cardstate *cs, + struct at_state_t *at_state, int type, + void *ptr, int parameter, void *arg) +{ + unsigned long flags; + unsigned next, tail; + struct event_t *event = NULL; + + gig_dbg(DEBUG_EVENT, "queueing event %d", type); + + spin_lock_irqsave(&cs->ev_lock, flags); + + tail = cs->ev_tail; + next = (tail + 1) % MAX_EVENTS; + if (unlikely(next == cs->ev_head)) + dev_err(cs->dev, "event queue full\n"); + else { + event = cs->events + tail; + event->type = type; + event->at_state = at_state; + event->cid = -1; + event->ptr = ptr; + event->arg = arg; + event->parameter = parameter; + cs->ev_tail = next; + } + + spin_unlock_irqrestore(&cs->ev_lock, flags); + + return event; +} +EXPORT_SYMBOL_GPL(gigaset_add_event); + +static void clear_at_state(struct at_state_t *at_state) +{ + int i; + + for (i = 0; i < STR_NUM; ++i) { + kfree(at_state->str_var[i]); + at_state->str_var[i] = NULL; + } +} + +static void dealloc_temp_at_states(struct cardstate *cs) +{ + struct at_state_t *cur, *next; + + list_for_each_entry_safe(cur, next, &cs->temp_at_states, list) { + list_del(&cur->list); + clear_at_state(cur); + kfree(cur); + } +} + +static void gigaset_freebcs(struct bc_state *bcs) +{ + int i; + + gig_dbg(DEBUG_INIT, "freeing bcs[%d]->hw", bcs->channel); + bcs->cs->ops->freebcshw(bcs); + + gig_dbg(DEBUG_INIT, "clearing bcs[%d]->at_state", bcs->channel); + clear_at_state(&bcs->at_state); + gig_dbg(DEBUG_INIT, "freeing bcs[%d]->skb", bcs->channel); + dev_kfree_skb(bcs->rx_skb); + bcs->rx_skb = NULL; + + for (i = 0; i < AT_NUM; ++i) { + kfree(bcs->commands[i]); + bcs->commands[i] = NULL; + } +} + +static struct cardstate *alloc_cs(struct gigaset_driver *drv) +{ + unsigned long flags; + unsigned i; + struct cardstate *cs; + struct cardstate *ret = NULL; + + spin_lock_irqsave(&drv->lock, flags); + if (drv->blocked) + goto exit; + for (i = 0; i < drv->minors; ++i) { + cs = drv->cs + i; + if (!(cs->flags & VALID_MINOR)) { + cs->flags = VALID_MINOR; + ret = cs; + break; + } + } +exit: + spin_unlock_irqrestore(&drv->lock, flags); + return ret; +} + +static void free_cs(struct cardstate *cs) +{ + cs->flags = 0; +} + +static void make_valid(struct cardstate *cs, unsigned mask) +{ + unsigned long flags; + struct gigaset_driver *drv = cs->driver; + spin_lock_irqsave(&drv->lock, flags); + cs->flags |= mask; + spin_unlock_irqrestore(&drv->lock, flags); +} + +static void make_invalid(struct cardstate *cs, unsigned mask) +{ + unsigned long flags; + struct gigaset_driver *drv = cs->driver; + spin_lock_irqsave(&drv->lock, flags); + cs->flags &= ~mask; + spin_unlock_irqrestore(&drv->lock, flags); +} + +/** + * gigaset_freecs() - free all associated ressources of a device + * @cs: device descriptor structure. + * + * Stops all tasklets and timers, unregisters the device from all + * subsystems it was registered to, deallocates the device structure + * @cs and all structures referenced from it. + * Operations on the device should be stopped before calling this. + */ +void gigaset_freecs(struct cardstate *cs) +{ + int i; + unsigned long flags; + + if (!cs) + return; + + mutex_lock(&cs->mutex); + + spin_lock_irqsave(&cs->lock, flags); + cs->running = 0; + spin_unlock_irqrestore(&cs->lock, flags); /* event handler and timer are + not rescheduled below */ + + tasklet_kill(&cs->event_tasklet); + del_timer_sync(&cs->timer); + + switch (cs->cs_init) { + default: + /* clear B channel structures */ + for (i = 0; i < cs->channels; ++i) { + gig_dbg(DEBUG_INIT, "clearing bcs[%d]", i); + gigaset_freebcs(cs->bcs + i); + } + + /* clear device sysfs */ + gigaset_free_dev_sysfs(cs); + + gigaset_if_free(cs); + + gig_dbg(DEBUG_INIT, "clearing hw"); + cs->ops->freecshw(cs); + + /* fall through */ + case 2: /* error in initcshw */ + /* Deregister from LL */ + make_invalid(cs, VALID_ID); + gigaset_isdn_unregdev(cs); + + /* fall through */ + case 1: /* error when registering to LL */ + gig_dbg(DEBUG_INIT, "clearing at_state"); + clear_at_state(&cs->at_state); + dealloc_temp_at_states(cs); + clear_events(cs); + tty_port_destroy(&cs->port); + + /* fall through */ + case 0: /* error in basic setup */ + gig_dbg(DEBUG_INIT, "freeing inbuf"); + kfree(cs->inbuf); + kfree(cs->bcs); + } + + mutex_unlock(&cs->mutex); + free_cs(cs); +} +EXPORT_SYMBOL_GPL(gigaset_freecs); + +void gigaset_at_init(struct at_state_t *at_state, struct bc_state *bcs, + struct cardstate *cs, int cid) +{ + int i; + + INIT_LIST_HEAD(&at_state->list); + at_state->waiting = 0; + at_state->getstring = 0; + at_state->pending_commands = 0; + at_state->timer_expires = 0; + at_state->timer_active = 0; + at_state->timer_index = 0; + at_state->seq_index = 0; + at_state->ConState = 0; + for (i = 0; i < STR_NUM; ++i) + at_state->str_var[i] = NULL; + at_state->int_var[VAR_ZDLE] = 0; + at_state->int_var[VAR_ZCTP] = -1; + at_state->int_var[VAR_ZSAU] = ZSAU_NULL; + at_state->cs = cs; + at_state->bcs = bcs; + at_state->cid = cid; + if (!cid) + at_state->replystruct = cs->tabnocid; + else + at_state->replystruct = cs->tabcid; +} + + +static void gigaset_inbuf_init(struct inbuf_t *inbuf, struct cardstate *cs) +/* inbuf->read must be allocated before! */ +{ + inbuf->head = 0; + inbuf->tail = 0; + inbuf->cs = cs; + inbuf->inputstate = INS_command; +} + +/** + * gigaset_fill_inbuf() - append received data to input buffer + * @inbuf: buffer structure. + * @src: received data. + * @numbytes: number of bytes received. + * + * Return value: !=0 if some data was appended + */ +int gigaset_fill_inbuf(struct inbuf_t *inbuf, const unsigned char *src, + unsigned numbytes) +{ + unsigned n, head, tail, bytesleft; + + gig_dbg(DEBUG_INTR, "received %u bytes", numbytes); + + if (!numbytes) + return 0; + + bytesleft = numbytes; + tail = inbuf->tail; + head = inbuf->head; + gig_dbg(DEBUG_INTR, "buffer state: %u -> %u", head, tail); + + while (bytesleft) { + if (head > tail) + n = head - 1 - tail; + else if (head == 0) + n = (RBUFSIZE - 1) - tail; + else + n = RBUFSIZE - tail; + if (!n) { + dev_err(inbuf->cs->dev, + "buffer overflow (%u bytes lost)\n", + bytesleft); + break; + } + if (n > bytesleft) + n = bytesleft; + memcpy(inbuf->data + tail, src, n); + bytesleft -= n; + tail = (tail + n) % RBUFSIZE; + src += n; + } + gig_dbg(DEBUG_INTR, "setting tail to %u", tail); + inbuf->tail = tail; + return numbytes != bytesleft; +} +EXPORT_SYMBOL_GPL(gigaset_fill_inbuf); + +/* Initialize the b-channel structure */ +static int gigaset_initbcs(struct bc_state *bcs, struct cardstate *cs, + int channel) +{ + int i; + + bcs->tx_skb = NULL; + + skb_queue_head_init(&bcs->squeue); + + bcs->corrupted = 0; + bcs->trans_down = 0; + bcs->trans_up = 0; + + gig_dbg(DEBUG_INIT, "setting up bcs[%d]->at_state", channel); + gigaset_at_init(&bcs->at_state, bcs, cs, -1); + +#ifdef CONFIG_GIGASET_DEBUG + bcs->emptycount = 0; +#endif + + bcs->rx_bufsize = 0; + bcs->rx_skb = NULL; + bcs->rx_fcs = PPP_INITFCS; + bcs->inputstate = 0; + bcs->channel = channel; + bcs->cs = cs; + + bcs->chstate = 0; + bcs->use_count = 1; + bcs->busy = 0; + bcs->ignore = cs->ignoreframes; + + for (i = 0; i < AT_NUM; ++i) + bcs->commands[i] = NULL; + + spin_lock_init(&bcs->aplock); + bcs->ap = NULL; + bcs->apconnstate = 0; + + gig_dbg(DEBUG_INIT, " setting up bcs[%d]->hw", channel); + return cs->ops->initbcshw(bcs); +} + +/** + * gigaset_initcs() - initialize device structure + * @drv: hardware driver the device belongs to + * @channels: number of B channels supported by device + * @onechannel: !=0 if B channel data and AT commands share one + * communication channel (M10x), + * ==0 if B channels have separate communication channels (base) + * @ignoreframes: number of frames to ignore after setting up B channel + * @cidmode: !=0: start in CallID mode + * @modulename: name of driver module for LL registration + * + * Allocate and initialize cardstate structure for Gigaset driver + * Calls hardware dependent gigaset_initcshw() function + * Calls B channel initialization function gigaset_initbcs() for each B channel + * + * Return value: + * pointer to cardstate structure + */ +struct cardstate *gigaset_initcs(struct gigaset_driver *drv, int channels, + int onechannel, int ignoreframes, + int cidmode, const char *modulename) +{ + struct cardstate *cs; + unsigned long flags; + int i; + + gig_dbg(DEBUG_INIT, "allocating cs"); + cs = alloc_cs(drv); + if (!cs) { + pr_err("maximum number of devices exceeded\n"); + return NULL; + } + + cs->cs_init = 0; + cs->channels = channels; + cs->onechannel = onechannel; + cs->ignoreframes = ignoreframes; + INIT_LIST_HEAD(&cs->temp_at_states); + cs->running = 0; + timer_setup(&cs->timer, timer_tick, 0); + spin_lock_init(&cs->ev_lock); + cs->ev_tail = 0; + cs->ev_head = 0; + + tasklet_init(&cs->event_tasklet, gigaset_handle_event, + (unsigned long) cs); + tty_port_init(&cs->port); + cs->commands_pending = 0; + cs->cur_at_seq = 0; + cs->gotfwver = -1; + cs->dev = NULL; + cs->tty_dev = NULL; + cs->cidmode = cidmode != 0; + cs->tabnocid = gigaset_tab_nocid; + cs->tabcid = gigaset_tab_cid; + + init_waitqueue_head(&cs->waitqueue); + cs->waiting = 0; + + cs->mode = M_UNKNOWN; + cs->mstate = MS_UNINITIALIZED; + + cs->bcs = kmalloc_array(channels, sizeof(struct bc_state), GFP_KERNEL); + cs->inbuf = kmalloc(sizeof(struct inbuf_t), GFP_KERNEL); + if (!cs->bcs || !cs->inbuf) { + pr_err("out of memory\n"); + goto error; + } + ++cs->cs_init; + + gig_dbg(DEBUG_INIT, "setting up at_state"); + spin_lock_init(&cs->lock); + gigaset_at_init(&cs->at_state, NULL, cs, 0); + cs->dle = 0; + cs->cbytes = 0; + + gig_dbg(DEBUG_INIT, "setting up inbuf"); + gigaset_inbuf_init(cs->inbuf, cs); + + cs->connected = 0; + cs->isdn_up = 0; + + gig_dbg(DEBUG_INIT, "setting up cmdbuf"); + cs->cmdbuf = cs->lastcmdbuf = NULL; + spin_lock_init(&cs->cmdlock); + cs->curlen = 0; + cs->cmdbytes = 0; + + gig_dbg(DEBUG_INIT, "setting up iif"); + if (gigaset_isdn_regdev(cs, modulename) < 0) { + pr_err("error registering ISDN device\n"); + goto error; + } + + make_valid(cs, VALID_ID); + ++cs->cs_init; + gig_dbg(DEBUG_INIT, "setting up hw"); + if (cs->ops->initcshw(cs) < 0) + goto error; + + ++cs->cs_init; + + /* set up character device */ + gigaset_if_init(cs); + + /* set up device sysfs */ + gigaset_init_dev_sysfs(cs); + + /* set up channel data structures */ + for (i = 0; i < channels; ++i) { + gig_dbg(DEBUG_INIT, "setting up bcs[%d]", i); + if (gigaset_initbcs(cs->bcs + i, cs, i) < 0) { + pr_err("could not allocate channel %d data\n", i); + goto error; + } + } + + spin_lock_irqsave(&cs->lock, flags); + cs->running = 1; + spin_unlock_irqrestore(&cs->lock, flags); + cs->timer.expires = jiffies + msecs_to_jiffies(GIG_TICK); + add_timer(&cs->timer); + + gig_dbg(DEBUG_INIT, "cs initialized"); + return cs; + +error: + gig_dbg(DEBUG_INIT, "failed"); + gigaset_freecs(cs); + return NULL; +} +EXPORT_SYMBOL_GPL(gigaset_initcs); + +/* ReInitialize the b-channel structure on hangup */ +void gigaset_bcs_reinit(struct bc_state *bcs) +{ + struct sk_buff *skb; + struct cardstate *cs = bcs->cs; + unsigned long flags; + + while ((skb = skb_dequeue(&bcs->squeue)) != NULL) + dev_kfree_skb(skb); + + spin_lock_irqsave(&cs->lock, flags); + clear_at_state(&bcs->at_state); + bcs->at_state.ConState = 0; + bcs->at_state.timer_active = 0; + bcs->at_state.timer_expires = 0; + bcs->at_state.cid = -1; /* No CID defined */ + spin_unlock_irqrestore(&cs->lock, flags); + + bcs->inputstate = 0; + +#ifdef CONFIG_GIGASET_DEBUG + bcs->emptycount = 0; +#endif + + bcs->rx_fcs = PPP_INITFCS; + bcs->chstate = 0; + + bcs->ignore = cs->ignoreframes; + dev_kfree_skb(bcs->rx_skb); + bcs->rx_skb = NULL; + + cs->ops->reinitbcshw(bcs); +} + +static void cleanup_cs(struct cardstate *cs) +{ + struct cmdbuf_t *cb, *tcb; + int i; + unsigned long flags; + + spin_lock_irqsave(&cs->lock, flags); + + cs->mode = M_UNKNOWN; + cs->mstate = MS_UNINITIALIZED; + + clear_at_state(&cs->at_state); + dealloc_temp_at_states(cs); + gigaset_at_init(&cs->at_state, NULL, cs, 0); + + cs->inbuf->inputstate = INS_command; + cs->inbuf->head = 0; + cs->inbuf->tail = 0; + + cb = cs->cmdbuf; + while (cb) { + tcb = cb; + cb = cb->next; + kfree(tcb); + } + cs->cmdbuf = cs->lastcmdbuf = NULL; + cs->curlen = 0; + cs->cmdbytes = 0; + cs->gotfwver = -1; + cs->dle = 0; + cs->cur_at_seq = 0; + cs->commands_pending = 0; + cs->cbytes = 0; + + spin_unlock_irqrestore(&cs->lock, flags); + + for (i = 0; i < cs->channels; ++i) { + gigaset_freebcs(cs->bcs + i); + if (gigaset_initbcs(cs->bcs + i, cs, i) < 0) + pr_err("could not allocate channel %d data\n", i); + } + + if (cs->waiting) { + cs->cmd_result = -ENODEV; + cs->waiting = 0; + wake_up_interruptible(&cs->waitqueue); + } +} + + +/** + * gigaset_start() - start device operations + * @cs: device descriptor structure. + * + * Prepares the device for use by setting up communication parameters, + * scheduling an EV_START event to initiate device initialization, and + * waiting for completion of the initialization. + * + * Return value: + * 0 on success, error code < 0 on failure + */ +int gigaset_start(struct cardstate *cs) +{ + unsigned long flags; + + if (mutex_lock_interruptible(&cs->mutex)) + return -EBUSY; + + spin_lock_irqsave(&cs->lock, flags); + cs->connected = 1; + spin_unlock_irqrestore(&cs->lock, flags); + + if (cs->mstate != MS_LOCKED) { + cs->ops->set_modem_ctrl(cs, 0, TIOCM_DTR | TIOCM_RTS); + cs->ops->baud_rate(cs, B115200); + cs->ops->set_line_ctrl(cs, CS8); + cs->control_state = TIOCM_DTR | TIOCM_RTS; + } + + cs->waiting = 1; + + if (!gigaset_add_event(cs, &cs->at_state, EV_START, NULL, 0, NULL)) { + cs->waiting = 0; + goto error; + } + gigaset_schedule_event(cs); + + wait_event(cs->waitqueue, !cs->waiting); + + mutex_unlock(&cs->mutex); + return 0; + +error: + mutex_unlock(&cs->mutex); + return -ENOMEM; +} +EXPORT_SYMBOL_GPL(gigaset_start); + +/** + * gigaset_shutdown() - shut down device operations + * @cs: device descriptor structure. + * + * Deactivates the device by scheduling an EV_SHUTDOWN event and + * waiting for completion of the shutdown. + * + * Return value: + * 0 - success, -ENODEV - error (no device associated) + */ +int gigaset_shutdown(struct cardstate *cs) +{ + mutex_lock(&cs->mutex); + + if (!(cs->flags & VALID_MINOR)) { + mutex_unlock(&cs->mutex); + return -ENODEV; + } + + cs->waiting = 1; + + if (!gigaset_add_event(cs, &cs->at_state, EV_SHUTDOWN, NULL, 0, NULL)) + goto exit; + gigaset_schedule_event(cs); + + wait_event(cs->waitqueue, !cs->waiting); + + cleanup_cs(cs); + +exit: + mutex_unlock(&cs->mutex); + return 0; +} +EXPORT_SYMBOL_GPL(gigaset_shutdown); + +/** + * gigaset_stop() - stop device operations + * @cs: device descriptor structure. + * + * Stops operations on the device by scheduling an EV_STOP event and + * waiting for completion of the shutdown. + */ +void gigaset_stop(struct cardstate *cs) +{ + mutex_lock(&cs->mutex); + + cs->waiting = 1; + + if (!gigaset_add_event(cs, &cs->at_state, EV_STOP, NULL, 0, NULL)) + goto exit; + gigaset_schedule_event(cs); + + wait_event(cs->waitqueue, !cs->waiting); + + cleanup_cs(cs); + +exit: + mutex_unlock(&cs->mutex); +} +EXPORT_SYMBOL_GPL(gigaset_stop); + +static LIST_HEAD(drivers); +static DEFINE_SPINLOCK(driver_lock); + +struct cardstate *gigaset_get_cs_by_id(int id) +{ + unsigned long flags; + struct cardstate *ret = NULL; + struct cardstate *cs; + struct gigaset_driver *drv; + unsigned i; + + spin_lock_irqsave(&driver_lock, flags); + list_for_each_entry(drv, &drivers, list) { + spin_lock(&drv->lock); + for (i = 0; i < drv->minors; ++i) { + cs = drv->cs + i; + if ((cs->flags & VALID_ID) && cs->myid == id) { + ret = cs; + break; + } + } + spin_unlock(&drv->lock); + if (ret) + break; + } + spin_unlock_irqrestore(&driver_lock, flags); + return ret; +} + +static struct cardstate *gigaset_get_cs_by_minor(unsigned minor) +{ + unsigned long flags; + struct cardstate *ret = NULL; + struct gigaset_driver *drv; + unsigned index; + + spin_lock_irqsave(&driver_lock, flags); + list_for_each_entry(drv, &drivers, list) { + if (minor < drv->minor || minor >= drv->minor + drv->minors) + continue; + index = minor - drv->minor; + spin_lock(&drv->lock); + if (drv->cs[index].flags & VALID_MINOR) + ret = drv->cs + index; + spin_unlock(&drv->lock); + if (ret) + break; + } + spin_unlock_irqrestore(&driver_lock, flags); + return ret; +} + +struct cardstate *gigaset_get_cs_by_tty(struct tty_struct *tty) +{ + return gigaset_get_cs_by_minor(tty->index + tty->driver->minor_start); +} + +/** + * gigaset_freedriver() - free all associated ressources of a driver + * @drv: driver descriptor structure. + * + * Unregisters the driver from the system and deallocates the driver + * structure @drv and all structures referenced from it. + * All devices should be shut down before calling this. + */ +void gigaset_freedriver(struct gigaset_driver *drv) +{ + unsigned long flags; + + spin_lock_irqsave(&driver_lock, flags); + list_del(&drv->list); + spin_unlock_irqrestore(&driver_lock, flags); + + gigaset_if_freedriver(drv); + + kfree(drv->cs); + kfree(drv); +} +EXPORT_SYMBOL_GPL(gigaset_freedriver); + +/** + * gigaset_initdriver() - initialize driver structure + * @minor: First minor number + * @minors: Number of minors this driver can handle + * @procname: Name of the driver + * @devname: Name of the device files (prefix without minor number) + * + * Allocate and initialize gigaset_driver structure. Initialize interface. + * + * Return value: + * Pointer to the gigaset_driver structure on success, NULL on failure. + */ +struct gigaset_driver *gigaset_initdriver(unsigned minor, unsigned minors, + const char *procname, + const char *devname, + const struct gigaset_ops *ops, + struct module *owner) +{ + struct gigaset_driver *drv; + unsigned long flags; + unsigned i; + + drv = kmalloc(sizeof *drv, GFP_KERNEL); + if (!drv) + return NULL; + + drv->have_tty = 0; + drv->minor = minor; + drv->minors = minors; + spin_lock_init(&drv->lock); + drv->blocked = 0; + drv->ops = ops; + drv->owner = owner; + INIT_LIST_HEAD(&drv->list); + + drv->cs = kmalloc_array(minors, sizeof(*drv->cs), GFP_KERNEL); + if (!drv->cs) + goto error; + + for (i = 0; i < minors; ++i) { + drv->cs[i].flags = 0; + drv->cs[i].driver = drv; + drv->cs[i].ops = drv->ops; + drv->cs[i].minor_index = i; + mutex_init(&drv->cs[i].mutex); + } + + gigaset_if_initdriver(drv, procname, devname); + + spin_lock_irqsave(&driver_lock, flags); + list_add(&drv->list, &drivers); + spin_unlock_irqrestore(&driver_lock, flags); + + return drv; + +error: + kfree(drv); + return NULL; +} +EXPORT_SYMBOL_GPL(gigaset_initdriver); + +/** + * gigaset_blockdriver() - block driver + * @drv: driver descriptor structure. + * + * Prevents the driver from attaching new devices, in preparation for + * deregistration. + */ +void gigaset_blockdriver(struct gigaset_driver *drv) +{ + drv->blocked = 1; +} +EXPORT_SYMBOL_GPL(gigaset_blockdriver); + +static int __init gigaset_init_module(void) +{ + /* in accordance with the principle of least astonishment, + * setting the 'debug' parameter to 1 activates a sensible + * set of default debug levels + */ + if (gigaset_debuglevel == 1) + gigaset_debuglevel = DEBUG_DEFAULT; + + pr_info(DRIVER_DESC DRIVER_DESC_DEBUG "\n"); + gigaset_isdn_regdrv(); + return 0; +} + +static void __exit gigaset_exit_module(void) +{ + gigaset_isdn_unregdrv(); +} + +module_init(gigaset_init_module); +module_exit(gigaset_exit_module); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); + +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/isdn/gigaset/dummyll.c b/drivers/staging/isdn/gigaset/dummyll.c new file mode 100644 index 000000000000..570c2d53b84e --- /dev/null +++ b/drivers/staging/isdn/gigaset/dummyll.c @@ -0,0 +1,77 @@ +/* + * Dummy LL interface for the Gigaset driver + * + * Copyright (c) 2009 by Tilman Schmidt <tilman@imap.cc>. + * + * ===================================================================== + * 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. + * ===================================================================== + */ + +#include <linux/export.h> +#include "gigaset.h" + +void gigaset_skb_sent(struct bc_state *bcs, struct sk_buff *skb) +{ +} +EXPORT_SYMBOL_GPL(gigaset_skb_sent); + +void gigaset_skb_rcvd(struct bc_state *bcs, struct sk_buff *skb) +{ +} +EXPORT_SYMBOL_GPL(gigaset_skb_rcvd); + +void gigaset_isdn_rcv_err(struct bc_state *bcs) +{ +} +EXPORT_SYMBOL_GPL(gigaset_isdn_rcv_err); + +int gigaset_isdn_icall(struct at_state_t *at_state) +{ + return ICALL_IGNORE; +} + +void gigaset_isdn_connD(struct bc_state *bcs) +{ +} + +void gigaset_isdn_hupD(struct bc_state *bcs) +{ +} + +void gigaset_isdn_connB(struct bc_state *bcs) +{ +} + +void gigaset_isdn_hupB(struct bc_state *bcs) +{ +} + +void gigaset_isdn_start(struct cardstate *cs) +{ +} + +void gigaset_isdn_stop(struct cardstate *cs) +{ +} + +int gigaset_isdn_regdev(struct cardstate *cs, const char *isdnid) +{ + return 0; +} + +void gigaset_isdn_unregdev(struct cardstate *cs) +{ +} + +void gigaset_isdn_regdrv(void) +{ + pr_info("no ISDN subsystem interface\n"); +} + +void gigaset_isdn_unregdrv(void) +{ +} diff --git a/drivers/staging/isdn/gigaset/ev-layer.c b/drivers/staging/isdn/gigaset/ev-layer.c new file mode 100644 index 000000000000..182826e9d07c --- /dev/null +++ b/drivers/staging/isdn/gigaset/ev-layer.c @@ -0,0 +1,1913 @@ +/* + * Stuff used by all variants of the driver + * + * Copyright (c) 2001 by Stefan Eilers, + * Hansjoerg Lipp <hjlipp@web.de>, + * Tilman Schmidt <tilman@imap.cc>. + * + * ===================================================================== + * 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. + * ===================================================================== + */ + +#include <linux/export.h> +#include "gigaset.h" + +/* ========================================================== */ +/* bit masks for pending commands */ +#define PC_DIAL 0x001 +#define PC_HUP 0x002 +#define PC_INIT 0x004 +#define PC_DLE0 0x008 +#define PC_DLE1 0x010 +#define PC_SHUTDOWN 0x020 +#define PC_ACCEPT 0x040 +#define PC_CID 0x080 +#define PC_NOCID 0x100 +#define PC_CIDMODE 0x200 +#define PC_UMMODE 0x400 + +/* types of modem responses */ +#define RT_NOTHING 0 +#define RT_ZSAU 1 +#define RT_RING 2 +#define RT_NUMBER 3 +#define RT_STRING 4 +#define RT_ZCAU 6 + +/* Possible ASCII responses */ +#define RSP_OK 0 +#define RSP_ERROR 1 +#define RSP_ZGCI 3 +#define RSP_RING 4 +#define RSP_ZVLS 5 +#define RSP_ZCAU 6 + +/* responses with values to store in at_state */ +/* - numeric */ +#define RSP_VAR 100 +#define RSP_ZSAU (RSP_VAR + VAR_ZSAU) +#define RSP_ZDLE (RSP_VAR + VAR_ZDLE) +#define RSP_ZCTP (RSP_VAR + VAR_ZCTP) +/* - string */ +#define RSP_STR (RSP_VAR + VAR_NUM) +#define RSP_NMBR (RSP_STR + STR_NMBR) +#define RSP_ZCPN (RSP_STR + STR_ZCPN) +#define RSP_ZCON (RSP_STR + STR_ZCON) +#define RSP_ZBC (RSP_STR + STR_ZBC) +#define RSP_ZHLC (RSP_STR + STR_ZHLC) + +#define RSP_WRONG_CID -2 /* unknown cid in cmd */ +#define RSP_INVAL -6 /* invalid response */ +#define RSP_NODEV -9 /* device not connected */ + +#define RSP_NONE -19 +#define RSP_STRING -20 +#define RSP_NULL -21 +#define RSP_INIT -27 +#define RSP_ANY -26 +#define RSP_LAST -28 + +/* actions for process_response */ +#define ACT_NOTHING 0 +#define ACT_SETDLE1 1 +#define ACT_SETDLE0 2 +#define ACT_FAILINIT 3 +#define ACT_HUPMODEM 4 +#define ACT_CONFIGMODE 5 +#define ACT_INIT 6 +#define ACT_DLE0 7 +#define ACT_DLE1 8 +#define ACT_FAILDLE0 9 +#define ACT_FAILDLE1 10 +#define ACT_RING 11 +#define ACT_CID 12 +#define ACT_FAILCID 13 +#define ACT_SDOWN 14 +#define ACT_FAILSDOWN 15 +#define ACT_DEBUG 16 +#define ACT_WARN 17 +#define ACT_DIALING 18 +#define ACT_ABORTDIAL 19 +#define ACT_DISCONNECT 20 +#define ACT_CONNECT 21 +#define ACT_REMOTEREJECT 22 +#define ACT_CONNTIMEOUT 23 +#define ACT_REMOTEHUP 24 +#define ACT_ABORTHUP 25 +#define ACT_ICALL 26 +#define ACT_ACCEPTED 27 +#define ACT_ABORTACCEPT 28 +#define ACT_TIMEOUT 29 +#define ACT_GETSTRING 30 +#define ACT_SETVER 31 +#define ACT_FAILVER 32 +#define ACT_GOTVER 33 +#define ACT_TEST 34 +#define ACT_ERROR 35 +#define ACT_ABORTCID 36 +#define ACT_ZCAU 37 +#define ACT_NOTIFY_BC_DOWN 38 +#define ACT_NOTIFY_BC_UP 39 +#define ACT_DIAL 40 +#define ACT_ACCEPT 41 +#define ACT_HUP 43 +#define ACT_IF_LOCK 44 +#define ACT_START 45 +#define ACT_STOP 46 +#define ACT_FAKEDLE0 47 +#define ACT_FAKEHUP 48 +#define ACT_FAKESDOWN 49 +#define ACT_SHUTDOWN 50 +#define ACT_PROC_CIDMODE 51 +#define ACT_UMODESET 52 +#define ACT_FAILUMODE 53 +#define ACT_CMODESET 54 +#define ACT_FAILCMODE 55 +#define ACT_IF_VER 56 +#define ACT_CMD 100 + +/* at command sequences */ +#define SEQ_NONE 0 +#define SEQ_INIT 100 +#define SEQ_DLE0 200 +#define SEQ_DLE1 250 +#define SEQ_CID 300 +#define SEQ_NOCID 350 +#define SEQ_HUP 400 +#define SEQ_DIAL 600 +#define SEQ_ACCEPT 720 +#define SEQ_SHUTDOWN 500 +#define SEQ_CIDMODE 10 +#define SEQ_UMMODE 11 + + +/* 100: init, 200: dle0, 250:dle1, 300: get cid (dial), 350: "hup" (no cid), + * 400: hup, 500: reset, 600: dial, 700: ring */ +struct reply_t gigaset_tab_nocid[] = +{ +/* resp_code, min_ConState, max_ConState, parameter, new_ConState, timeout, + * action, command */ + +/* initialize device, set cid mode if possible */ + {RSP_INIT, -1, -1, SEQ_INIT, 100, 1, {ACT_TIMEOUT} }, + + {EV_TIMEOUT, 100, 100, -1, 101, 3, {0}, "Z\r"}, + {RSP_OK, 101, 103, -1, 120, 5, {ACT_GETSTRING}, + "+GMR\r"}, + + {EV_TIMEOUT, 101, 101, -1, 102, 5, {0}, "Z\r"}, + {RSP_ERROR, 101, 101, -1, 102, 5, {0}, "Z\r"}, + + {EV_TIMEOUT, 102, 102, -1, 108, 5, {ACT_SETDLE1}, + "^SDLE=0\r"}, + {RSP_OK, 108, 108, -1, 104, -1}, + {RSP_ZDLE, 104, 104, 0, 103, 5, {0}, "Z\r"}, + {EV_TIMEOUT, 104, 104, -1, 0, 0, {ACT_FAILINIT} }, + {RSP_ERROR, 108, 108, -1, 0, 0, {ACT_FAILINIT} }, + + {EV_TIMEOUT, 108, 108, -1, 105, 2, {ACT_SETDLE0, + ACT_HUPMODEM, + ACT_TIMEOUT} }, + {EV_TIMEOUT, 105, 105, -1, 103, 5, {0}, "Z\r"}, + + {RSP_ERROR, 102, 102, -1, 107, 5, {0}, "^GETPRE\r"}, + {RSP_OK, 107, 107, -1, 0, 0, {ACT_CONFIGMODE} }, + {RSP_ERROR, 107, 107, -1, 0, 0, {ACT_FAILINIT} }, + {EV_TIMEOUT, 107, 107, -1, 0, 0, {ACT_FAILINIT} }, + + {RSP_ERROR, 103, 103, -1, 0, 0, {ACT_FAILINIT} }, + {EV_TIMEOUT, 103, 103, -1, 0, 0, {ACT_FAILINIT} }, + + {RSP_STRING, 120, 120, -1, 121, -1, {ACT_SETVER} }, + + {EV_TIMEOUT, 120, 121, -1, 0, 0, {ACT_FAILVER, + ACT_INIT} }, + {RSP_ERROR, 120, 121, -1, 0, 0, {ACT_FAILVER, + ACT_INIT} }, + {RSP_OK, 121, 121, -1, 0, 0, {ACT_GOTVER, + ACT_INIT} }, + {RSP_NONE, 121, 121, -1, 120, 0, {ACT_GETSTRING} }, + +/* leave dle mode */ + {RSP_INIT, 0, 0, SEQ_DLE0, 201, 5, {0}, "^SDLE=0\r"}, + {RSP_OK, 201, 201, -1, 202, -1}, + {RSP_ZDLE, 202, 202, 0, 0, 0, {ACT_DLE0} }, + {RSP_NODEV, 200, 249, -1, 0, 0, {ACT_FAKEDLE0} }, + {RSP_ERROR, 200, 249, -1, 0, 0, {ACT_FAILDLE0} }, + {EV_TIMEOUT, 200, 249, -1, 0, 0, {ACT_FAILDLE0} }, + +/* enter dle mode */ + {RSP_INIT, 0, 0, SEQ_DLE1, 251, 5, {0}, "^SDLE=1\r"}, + {RSP_OK, 251, 251, -1, 252, -1}, + {RSP_ZDLE, 252, 252, 1, 0, 0, {ACT_DLE1} }, + {RSP_ERROR, 250, 299, -1, 0, 0, {ACT_FAILDLE1} }, + {EV_TIMEOUT, 250, 299, -1, 0, 0, {ACT_FAILDLE1} }, + +/* incoming call */ + {RSP_RING, -1, -1, -1, -1, -1, {ACT_RING} }, + +/* get cid */ + {RSP_INIT, 0, 0, SEQ_CID, 301, 5, {0}, "^SGCI?\r"}, + {RSP_OK, 301, 301, -1, 302, -1}, + {RSP_ZGCI, 302, 302, -1, 0, 0, {ACT_CID} }, + {RSP_ERROR, 301, 349, -1, 0, 0, {ACT_FAILCID} }, + {EV_TIMEOUT, 301, 349, -1, 0, 0, {ACT_FAILCID} }, + +/* enter cid mode */ + {RSP_INIT, 0, 0, SEQ_CIDMODE, 150, 5, {0}, "^SGCI=1\r"}, + {RSP_OK, 150, 150, -1, 0, 0, {ACT_CMODESET} }, + {RSP_ERROR, 150, 150, -1, 0, 0, {ACT_FAILCMODE} }, + {EV_TIMEOUT, 150, 150, -1, 0, 0, {ACT_FAILCMODE} }, + +/* leave cid mode */ + {RSP_INIT, 0, 0, SEQ_UMMODE, 160, 5, {0}, "Z\r"}, + {RSP_OK, 160, 160, -1, 0, 0, {ACT_UMODESET} }, + {RSP_ERROR, 160, 160, -1, 0, 0, {ACT_FAILUMODE} }, + {EV_TIMEOUT, 160, 160, -1, 0, 0, {ACT_FAILUMODE} }, + +/* abort getting cid */ + {RSP_INIT, 0, 0, SEQ_NOCID, 0, 0, {ACT_ABORTCID} }, + +/* reset */ + {RSP_INIT, 0, 0, SEQ_SHUTDOWN, 504, 5, {0}, "Z\r"}, + {RSP_OK, 504, 504, -1, 0, 0, {ACT_SDOWN} }, + {RSP_ERROR, 501, 599, -1, 0, 0, {ACT_FAILSDOWN} }, + {EV_TIMEOUT, 501, 599, -1, 0, 0, {ACT_FAILSDOWN} }, + {RSP_NODEV, 501, 599, -1, 0, 0, {ACT_FAKESDOWN} }, + + {EV_PROC_CIDMODE, -1, -1, -1, -1, -1, {ACT_PROC_CIDMODE} }, + {EV_IF_LOCK, -1, -1, -1, -1, -1, {ACT_IF_LOCK} }, + {EV_IF_VER, -1, -1, -1, -1, -1, {ACT_IF_VER} }, + {EV_START, -1, -1, -1, -1, -1, {ACT_START} }, + {EV_STOP, -1, -1, -1, -1, -1, {ACT_STOP} }, + {EV_SHUTDOWN, -1, -1, -1, -1, -1, {ACT_SHUTDOWN} }, + +/* misc. */ + {RSP_ERROR, -1, -1, -1, -1, -1, {ACT_ERROR} }, + {RSP_ZCAU, -1, -1, -1, -1, -1, {ACT_ZCAU} }, + {RSP_NONE, -1, -1, -1, -1, -1, {ACT_DEBUG} }, + {RSP_ANY, -1, -1, -1, -1, -1, {ACT_WARN} }, + {RSP_LAST} +}; + +/* 600: start dialing, 650: dial in progress, 800: connection is up, 700: ring, + * 400: hup, 750: accepted icall */ +struct reply_t gigaset_tab_cid[] = +{ +/* resp_code, min_ConState, max_ConState, parameter, new_ConState, timeout, + * action, command */ + +/* dial */ + {EV_DIAL, -1, -1, -1, -1, -1, {ACT_DIAL} }, + {RSP_INIT, 0, 0, SEQ_DIAL, 601, 5, {ACT_CMD + AT_BC} }, + {RSP_OK, 601, 601, -1, 603, 5, {ACT_CMD + AT_PROTO} }, + {RSP_OK, 603, 603, -1, 604, 5, {ACT_CMD + AT_TYPE} }, + {RSP_OK, 604, 604, -1, 605, 5, {ACT_CMD + AT_MSN} }, + {RSP_NULL, 605, 605, -1, 606, 5, {ACT_CMD + AT_CLIP} }, + {RSP_OK, 605, 605, -1, 606, 5, {ACT_CMD + AT_CLIP} }, + {RSP_NULL, 606, 606, -1, 607, 5, {ACT_CMD + AT_ISO} }, + {RSP_OK, 606, 606, -1, 607, 5, {ACT_CMD + AT_ISO} }, + {RSP_OK, 607, 607, -1, 608, 5, {0}, "+VLS=17\r"}, + {RSP_OK, 608, 608, -1, 609, -1}, + {RSP_ZSAU, 609, 609, ZSAU_PROCEEDING, 610, 5, {ACT_CMD + AT_DIAL} }, + {RSP_OK, 610, 610, -1, 650, 0, {ACT_DIALING} }, + + {RSP_ERROR, 601, 610, -1, 0, 0, {ACT_ABORTDIAL} }, + {EV_TIMEOUT, 601, 610, -1, 0, 0, {ACT_ABORTDIAL} }, + +/* optional dialing responses */ + {EV_BC_OPEN, 650, 650, -1, 651, -1}, + {RSP_ZVLS, 609, 651, 17, -1, -1, {ACT_DEBUG} }, + {RSP_ZCTP, 610, 651, -1, -1, -1, {ACT_DEBUG} }, + {RSP_ZCPN, 610, 651, -1, -1, -1, {ACT_DEBUG} }, + {RSP_ZSAU, 650, 651, ZSAU_CALL_DELIVERED, -1, -1, {ACT_DEBUG} }, + +/* connect */ + {RSP_ZSAU, 650, 650, ZSAU_ACTIVE, 800, -1, {ACT_CONNECT} }, + {RSP_ZSAU, 651, 651, ZSAU_ACTIVE, 800, -1, {ACT_CONNECT, + ACT_NOTIFY_BC_UP} }, + {RSP_ZSAU, 750, 750, ZSAU_ACTIVE, 800, -1, {ACT_CONNECT} }, + {RSP_ZSAU, 751, 751, ZSAU_ACTIVE, 800, -1, {ACT_CONNECT, + ACT_NOTIFY_BC_UP} }, + {EV_BC_OPEN, 800, 800, -1, 800, -1, {ACT_NOTIFY_BC_UP} }, + +/* remote hangup */ + {RSP_ZSAU, 650, 651, ZSAU_DISCONNECT_IND, 0, 0, {ACT_REMOTEREJECT} }, + {RSP_ZSAU, 750, 751, ZSAU_DISCONNECT_IND, 0, 0, {ACT_REMOTEHUP} }, + {RSP_ZSAU, 800, 800, ZSAU_DISCONNECT_IND, 0, 0, {ACT_REMOTEHUP} }, + +/* hangup */ + {EV_HUP, -1, -1, -1, -1, -1, {ACT_HUP} }, + {RSP_INIT, -1, -1, SEQ_HUP, 401, 5, {0}, "+VLS=0\r"}, + {RSP_OK, 401, 401, -1, 402, 5}, + {RSP_ZVLS, 402, 402, 0, 403, 5}, + {RSP_ZSAU, 403, 403, ZSAU_DISCONNECT_REQ, -1, -1, {ACT_DEBUG} }, + {RSP_ZSAU, 403, 403, ZSAU_NULL, 0, 0, {ACT_DISCONNECT} }, + {RSP_NODEV, 401, 403, -1, 0, 0, {ACT_FAKEHUP} }, + {RSP_ERROR, 401, 401, -1, 0, 0, {ACT_ABORTHUP} }, + {EV_TIMEOUT, 401, 403, -1, 0, 0, {ACT_ABORTHUP} }, + + {EV_BC_CLOSED, 0, 0, -1, 0, -1, {ACT_NOTIFY_BC_DOWN} }, + +/* ring */ + {RSP_ZBC, 700, 700, -1, -1, -1, {0} }, + {RSP_ZHLC, 700, 700, -1, -1, -1, {0} }, + {RSP_NMBR, 700, 700, -1, -1, -1, {0} }, + {RSP_ZCPN, 700, 700, -1, -1, -1, {0} }, + {RSP_ZCTP, 700, 700, -1, -1, -1, {0} }, + {EV_TIMEOUT, 700, 700, -1, 720, 720, {ACT_ICALL} }, + {EV_BC_CLOSED, 720, 720, -1, 0, -1, {ACT_NOTIFY_BC_DOWN} }, + +/*accept icall*/ + {EV_ACCEPT, -1, -1, -1, -1, -1, {ACT_ACCEPT} }, + {RSP_INIT, 720, 720, SEQ_ACCEPT, 721, 5, {ACT_CMD + AT_PROTO} }, + {RSP_OK, 721, 721, -1, 722, 5, {ACT_CMD + AT_ISO} }, + {RSP_OK, 722, 722, -1, 723, 5, {0}, "+VLS=17\r"}, + {RSP_OK, 723, 723, -1, 724, 5, {0} }, + {RSP_ZVLS, 724, 724, 17, 750, 50, {ACT_ACCEPTED} }, + {RSP_ERROR, 721, 729, -1, 0, 0, {ACT_ABORTACCEPT} }, + {EV_TIMEOUT, 721, 729, -1, 0, 0, {ACT_ABORTACCEPT} }, + {RSP_ZSAU, 700, 729, ZSAU_NULL, 0, 0, {ACT_ABORTACCEPT} }, + {RSP_ZSAU, 700, 729, ZSAU_ACTIVE, 0, 0, {ACT_ABORTACCEPT} }, + {RSP_ZSAU, 700, 729, ZSAU_DISCONNECT_IND, 0, 0, {ACT_ABORTACCEPT} }, + + {EV_BC_OPEN, 750, 750, -1, 751, -1}, + {EV_TIMEOUT, 750, 751, -1, 0, 0, {ACT_CONNTIMEOUT} }, + +/* B channel closed (general case) */ + {EV_BC_CLOSED, -1, -1, -1, -1, -1, {ACT_NOTIFY_BC_DOWN} }, + +/* misc. */ + {RSP_ZCON, -1, -1, -1, -1, -1, {ACT_DEBUG} }, + {RSP_ZCAU, -1, -1, -1, -1, -1, {ACT_ZCAU} }, + {RSP_NONE, -1, -1, -1, -1, -1, {ACT_DEBUG} }, + {RSP_ANY, -1, -1, -1, -1, -1, {ACT_WARN} }, + {RSP_LAST} +}; + + +static const struct resp_type_t { + char *response; + int resp_code; + int type; +} +resp_type[] = +{ + {"OK", RSP_OK, RT_NOTHING}, + {"ERROR", RSP_ERROR, RT_NOTHING}, + {"ZSAU", RSP_ZSAU, RT_ZSAU}, + {"ZCAU", RSP_ZCAU, RT_ZCAU}, + {"RING", RSP_RING, RT_RING}, + {"ZGCI", RSP_ZGCI, RT_NUMBER}, + {"ZVLS", RSP_ZVLS, RT_NUMBER}, + {"ZCTP", RSP_ZCTP, RT_NUMBER}, + {"ZDLE", RSP_ZDLE, RT_NUMBER}, + {"ZHLC", RSP_ZHLC, RT_STRING}, + {"ZBC", RSP_ZBC, RT_STRING}, + {"NMBR", RSP_NMBR, RT_STRING}, + {"ZCPN", RSP_ZCPN, RT_STRING}, + {"ZCON", RSP_ZCON, RT_STRING}, + {NULL, 0, 0} +}; + +static const struct zsau_resp_t { + char *str; + int code; +} +zsau_resp[] = +{ + {"OUTGOING_CALL_PROCEEDING", ZSAU_PROCEEDING}, + {"CALL_DELIVERED", ZSAU_CALL_DELIVERED}, + {"ACTIVE", ZSAU_ACTIVE}, + {"DISCONNECT_IND", ZSAU_DISCONNECT_IND}, + {"NULL", ZSAU_NULL}, + {"DISCONNECT_REQ", ZSAU_DISCONNECT_REQ}, + {NULL, ZSAU_UNKNOWN} +}; + +/* check for and remove fixed string prefix + * If s starts with prefix terminated by a non-alphanumeric character, + * return pointer to the first character after that, otherwise return NULL. + */ +static char *skip_prefix(char *s, const char *prefix) +{ + while (*prefix) + if (*s++ != *prefix++) + return NULL; + if (isalnum(*s)) + return NULL; + return s; +} + +/* queue event with CID */ +static void add_cid_event(struct cardstate *cs, int cid, int type, + void *ptr, int parameter) +{ + unsigned long flags; + unsigned next, tail; + struct event_t *event; + + gig_dbg(DEBUG_EVENT, "queueing event %d for cid %d", type, cid); + + spin_lock_irqsave(&cs->ev_lock, flags); + + tail = cs->ev_tail; + next = (tail + 1) % MAX_EVENTS; + if (unlikely(next == cs->ev_head)) { + dev_err(cs->dev, "event queue full\n"); + kfree(ptr); + } else { + event = cs->events + tail; + event->type = type; + event->cid = cid; + event->ptr = ptr; + event->arg = NULL; + event->parameter = parameter; + event->at_state = NULL; + cs->ev_tail = next; + } + + spin_unlock_irqrestore(&cs->ev_lock, flags); +} + +/** + * gigaset_handle_modem_response() - process received modem response + * @cs: device descriptor structure. + * + * Called by asyncdata/isocdata if a block of data received from the + * device must be processed as a modem command response. The data is + * already in the cs structure. + */ +void gigaset_handle_modem_response(struct cardstate *cs) +{ + char *eoc, *psep, *ptr; + const struct resp_type_t *rt; + const struct zsau_resp_t *zr; + int cid, parameter; + u8 type, value; + + if (!cs->cbytes) { + /* ignore additional LFs/CRs (M10x config mode or cx100) */ + gig_dbg(DEBUG_MCMD, "skipped EOL [%02X]", cs->respdata[0]); + return; + } + cs->respdata[cs->cbytes] = 0; + + if (cs->at_state.getstring) { + /* state machine wants next line verbatim */ + cs->at_state.getstring = 0; + ptr = kstrdup(cs->respdata, GFP_ATOMIC); + gig_dbg(DEBUG_EVENT, "string==%s", ptr ? ptr : "NULL"); + add_cid_event(cs, 0, RSP_STRING, ptr, 0); + return; + } + + /* look up response type */ + for (rt = resp_type; rt->response; ++rt) { + eoc = skip_prefix(cs->respdata, rt->response); + if (eoc) + break; + } + if (!rt->response) { + add_cid_event(cs, 0, RSP_NONE, NULL, 0); + gig_dbg(DEBUG_EVENT, "unknown modem response: '%s'\n", + cs->respdata); + return; + } + + /* check for CID */ + psep = strrchr(cs->respdata, ';'); + if (psep && + !kstrtoint(psep + 1, 10, &cid) && + cid >= 1 && cid <= 65535) { + /* valid CID: chop it off */ + *psep = 0; + } else { + /* no valid CID: leave unchanged */ + cid = 0; + } + + gig_dbg(DEBUG_EVENT, "CMD received: %s", cs->respdata); + if (cid) + gig_dbg(DEBUG_EVENT, "CID: %d", cid); + + switch (rt->type) { + case RT_NOTHING: + /* check parameter separator */ + if (*eoc) + goto bad_param; /* extra parameter */ + + add_cid_event(cs, cid, rt->resp_code, NULL, 0); + break; + + case RT_RING: + /* check parameter separator */ + if (!*eoc) + eoc = NULL; /* no parameter */ + else if (*eoc++ != ',') + goto bad_param; + + add_cid_event(cs, 0, rt->resp_code, NULL, cid); + + /* process parameters as individual responses */ + while (eoc) { + /* look up parameter type */ + psep = NULL; + for (rt = resp_type; rt->response; ++rt) { + psep = skip_prefix(eoc, rt->response); + if (psep) + break; + } + + /* all legal parameters are of type RT_STRING */ + if (!psep || rt->type != RT_STRING) { + dev_warn(cs->dev, + "illegal RING parameter: '%s'\n", + eoc); + return; + } + + /* skip parameter value separator */ + if (*psep++ != '=') + goto bad_param; + + /* look up end of parameter */ + eoc = strchr(psep, ','); + if (eoc) + *eoc++ = 0; + + /* retrieve parameter value */ + ptr = kstrdup(psep, GFP_ATOMIC); + + /* queue event */ + add_cid_event(cs, cid, rt->resp_code, ptr, 0); + } + break; + + case RT_ZSAU: + /* check parameter separator */ + if (!*eoc) { + /* no parameter */ + add_cid_event(cs, cid, rt->resp_code, NULL, ZSAU_NONE); + break; + } + if (*eoc++ != '=') + goto bad_param; + + /* look up parameter value */ + for (zr = zsau_resp; zr->str; ++zr) + if (!strcmp(eoc, zr->str)) + break; + if (!zr->str) + goto bad_param; + + add_cid_event(cs, cid, rt->resp_code, NULL, zr->code); + break; + + case RT_STRING: + /* check parameter separator */ + if (*eoc++ != '=') + goto bad_param; + + /* retrieve parameter value */ + ptr = kstrdup(eoc, GFP_ATOMIC); + + /* queue event */ + add_cid_event(cs, cid, rt->resp_code, ptr, 0); + break; + + case RT_ZCAU: + /* check parameter separators */ + if (*eoc++ != '=') + goto bad_param; + psep = strchr(eoc, ','); + if (!psep) + goto bad_param; + *psep++ = 0; + + /* decode parameter values */ + if (kstrtou8(eoc, 16, &type) || kstrtou8(psep, 16, &value)) { + *--psep = ','; + goto bad_param; + } + parameter = (type << 8) | value; + + add_cid_event(cs, cid, rt->resp_code, NULL, parameter); + break; + + case RT_NUMBER: + /* check parameter separator */ + if (*eoc++ != '=') + goto bad_param; + + /* decode parameter value */ + if (kstrtoint(eoc, 10, ¶meter)) + goto bad_param; + + /* special case ZDLE: set flag before queueing event */ + if (rt->resp_code == RSP_ZDLE) + cs->dle = parameter; + + add_cid_event(cs, cid, rt->resp_code, NULL, parameter); + break; + +bad_param: + /* parameter unexpected, incomplete or malformed */ + dev_warn(cs->dev, "bad parameter in response '%s'\n", + cs->respdata); + add_cid_event(cs, cid, rt->resp_code, NULL, -1); + break; + + default: + dev_err(cs->dev, "%s: internal error on '%s'\n", + __func__, cs->respdata); + } +} +EXPORT_SYMBOL_GPL(gigaset_handle_modem_response); + +/* disconnect_nobc + * process closing of connection associated with given AT state structure + * without B channel + */ +static void disconnect_nobc(struct at_state_t **at_state_p, + struct cardstate *cs) +{ + unsigned long flags; + + spin_lock_irqsave(&cs->lock, flags); + ++(*at_state_p)->seq_index; + + /* revert to selected idle mode */ + if (!cs->cidmode) { + cs->at_state.pending_commands |= PC_UMMODE; + gig_dbg(DEBUG_EVENT, "Scheduling PC_UMMODE"); + cs->commands_pending = 1; + } + + /* check for and deallocate temporary AT state */ + if (!list_empty(&(*at_state_p)->list)) { + list_del(&(*at_state_p)->list); + kfree(*at_state_p); + *at_state_p = NULL; + } + + spin_unlock_irqrestore(&cs->lock, flags); +} + +/* disconnect_bc + * process closing of connection associated with given AT state structure + * and B channel + */ +static void disconnect_bc(struct at_state_t *at_state, + struct cardstate *cs, struct bc_state *bcs) +{ + unsigned long flags; + + spin_lock_irqsave(&cs->lock, flags); + ++at_state->seq_index; + + /* revert to selected idle mode */ + if (!cs->cidmode) { + cs->at_state.pending_commands |= PC_UMMODE; + gig_dbg(DEBUG_EVENT, "Scheduling PC_UMMODE"); + cs->commands_pending = 1; + } + spin_unlock_irqrestore(&cs->lock, flags); + + /* invoke hardware specific handler */ + cs->ops->close_bchannel(bcs); + + /* notify LL */ + if (bcs->chstate & (CHS_D_UP | CHS_NOTIFY_LL)) { + bcs->chstate &= ~(CHS_D_UP | CHS_NOTIFY_LL); + gigaset_isdn_hupD(bcs); + } +} + +/* get_free_channel + * get a free AT state structure: either one of those associated with the + * B channels of the Gigaset device, or if none of those is available, + * a newly allocated one with bcs=NULL + * The structure should be freed by calling disconnect_nobc() after use. + */ +static inline struct at_state_t *get_free_channel(struct cardstate *cs, + int cid) +/* cids: >0: siemens-cid + * 0: without cid + * -1: no cid assigned yet + */ +{ + unsigned long flags; + int i; + struct at_state_t *ret; + + for (i = 0; i < cs->channels; ++i) + if (gigaset_get_channel(cs->bcs + i) >= 0) { + ret = &cs->bcs[i].at_state; + ret->cid = cid; + return ret; + } + + spin_lock_irqsave(&cs->lock, flags); + ret = kmalloc(sizeof(struct at_state_t), GFP_ATOMIC); + if (ret) { + gigaset_at_init(ret, NULL, cs, cid); + list_add(&ret->list, &cs->temp_at_states); + } + spin_unlock_irqrestore(&cs->lock, flags); + return ret; +} + +static void init_failed(struct cardstate *cs, int mode) +{ + int i; + struct at_state_t *at_state; + + cs->at_state.pending_commands &= ~PC_INIT; + cs->mode = mode; + cs->mstate = MS_UNINITIALIZED; + gigaset_free_channels(cs); + for (i = 0; i < cs->channels; ++i) { + at_state = &cs->bcs[i].at_state; + if (at_state->pending_commands & PC_CID) { + at_state->pending_commands &= ~PC_CID; + at_state->pending_commands |= PC_NOCID; + cs->commands_pending = 1; + } + } +} + +static void schedule_init(struct cardstate *cs, int state) +{ + if (cs->at_state.pending_commands & PC_INIT) { + gig_dbg(DEBUG_EVENT, "not scheduling PC_INIT again"); + return; + } + cs->mstate = state; + cs->mode = M_UNKNOWN; + gigaset_block_channels(cs); + cs->at_state.pending_commands |= PC_INIT; + gig_dbg(DEBUG_EVENT, "Scheduling PC_INIT"); + cs->commands_pending = 1; +} + +/* send an AT command + * adding the "AT" prefix, cid and DLE encapsulation as appropriate + */ +static void send_command(struct cardstate *cs, const char *cmd, + struct at_state_t *at_state) +{ + int cid = at_state->cid; + struct cmdbuf_t *cb; + size_t buflen; + + buflen = strlen(cmd) + 12; /* DLE ( A T 1 2 3 4 5 <cmd> DLE ) \0 */ + cb = kmalloc(sizeof(struct cmdbuf_t) + buflen, GFP_ATOMIC); + if (!cb) { + dev_err(cs->dev, "%s: out of memory\n", __func__); + return; + } + if (cid > 0 && cid <= 65535) + cb->len = snprintf(cb->buf, buflen, + cs->dle ? "\020(AT%d%s\020)" : "AT%d%s", + cid, cmd); + else + cb->len = snprintf(cb->buf, buflen, + cs->dle ? "\020(AT%s\020)" : "AT%s", + cmd); + cb->offset = 0; + cb->next = NULL; + cb->wake_tasklet = NULL; + cs->ops->write_cmd(cs, cb); +} + +static struct at_state_t *at_state_from_cid(struct cardstate *cs, int cid) +{ + struct at_state_t *at_state; + int i; + unsigned long flags; + + if (cid == 0) + return &cs->at_state; + + for (i = 0; i < cs->channels; ++i) + if (cid == cs->bcs[i].at_state.cid) + return &cs->bcs[i].at_state; + + spin_lock_irqsave(&cs->lock, flags); + + list_for_each_entry(at_state, &cs->temp_at_states, list) + if (cid == at_state->cid) { + spin_unlock_irqrestore(&cs->lock, flags); + return at_state; + } + + spin_unlock_irqrestore(&cs->lock, flags); + + return NULL; +} + +static void bchannel_down(struct bc_state *bcs) +{ + if (bcs->chstate & CHS_B_UP) { + bcs->chstate &= ~CHS_B_UP; + gigaset_isdn_hupB(bcs); + } + + if (bcs->chstate & (CHS_D_UP | CHS_NOTIFY_LL)) { + bcs->chstate &= ~(CHS_D_UP | CHS_NOTIFY_LL); + gigaset_isdn_hupD(bcs); + } + + gigaset_free_channel(bcs); + + gigaset_bcs_reinit(bcs); +} + +static void bchannel_up(struct bc_state *bcs) +{ + if (bcs->chstate & CHS_B_UP) { + dev_notice(bcs->cs->dev, "%s: B channel already up\n", + __func__); + return; + } + + bcs->chstate |= CHS_B_UP; + gigaset_isdn_connB(bcs); +} + +static void start_dial(struct at_state_t *at_state, void *data, + unsigned seq_index) +{ + struct bc_state *bcs = at_state->bcs; + struct cardstate *cs = at_state->cs; + char **commands = data; + unsigned long flags; + int i; + + bcs->chstate |= CHS_NOTIFY_LL; + + spin_lock_irqsave(&cs->lock, flags); + if (at_state->seq_index != seq_index) { + spin_unlock_irqrestore(&cs->lock, flags); + goto error; + } + spin_unlock_irqrestore(&cs->lock, flags); + + for (i = 0; i < AT_NUM; ++i) { + kfree(bcs->commands[i]); + bcs->commands[i] = commands[i]; + } + + at_state->pending_commands |= PC_CID; + gig_dbg(DEBUG_EVENT, "Scheduling PC_CID"); + cs->commands_pending = 1; + return; + +error: + for (i = 0; i < AT_NUM; ++i) { + kfree(commands[i]); + commands[i] = NULL; + } + at_state->pending_commands |= PC_NOCID; + gig_dbg(DEBUG_EVENT, "Scheduling PC_NOCID"); + cs->commands_pending = 1; + return; +} + +static void start_accept(struct at_state_t *at_state) +{ + struct cardstate *cs = at_state->cs; + struct bc_state *bcs = at_state->bcs; + int i; + + for (i = 0; i < AT_NUM; ++i) { + kfree(bcs->commands[i]); + bcs->commands[i] = NULL; + } + + bcs->commands[AT_PROTO] = kmalloc(9, GFP_ATOMIC); + bcs->commands[AT_ISO] = kmalloc(9, GFP_ATOMIC); + if (!bcs->commands[AT_PROTO] || !bcs->commands[AT_ISO]) { + dev_err(at_state->cs->dev, "out of memory\n"); + /* error reset */ + at_state->pending_commands |= PC_HUP; + gig_dbg(DEBUG_EVENT, "Scheduling PC_HUP"); + cs->commands_pending = 1; + return; + } + + snprintf(bcs->commands[AT_PROTO], 9, "^SBPR=%u\r", bcs->proto2); + snprintf(bcs->commands[AT_ISO], 9, "^SISO=%u\r", bcs->channel + 1); + + at_state->pending_commands |= PC_ACCEPT; + gig_dbg(DEBUG_EVENT, "Scheduling PC_ACCEPT"); + cs->commands_pending = 1; +} + +static void do_start(struct cardstate *cs) +{ + gigaset_free_channels(cs); + + if (cs->mstate != MS_LOCKED) + schedule_init(cs, MS_INIT); + + cs->isdn_up = 1; + gigaset_isdn_start(cs); + + cs->waiting = 0; + wake_up(&cs->waitqueue); +} + +static void finish_shutdown(struct cardstate *cs) +{ + if (cs->mstate != MS_LOCKED) { + cs->mstate = MS_UNINITIALIZED; + cs->mode = M_UNKNOWN; + } + + /* Tell the LL that the device is not available .. */ + if (cs->isdn_up) { + cs->isdn_up = 0; + gigaset_isdn_stop(cs); + } + + /* The rest is done by cleanup_cs() in process context. */ + + cs->cmd_result = -ENODEV; + cs->waiting = 0; + wake_up(&cs->waitqueue); +} + +static void do_shutdown(struct cardstate *cs) +{ + gigaset_block_channels(cs); + + if (cs->mstate == MS_READY) { + cs->mstate = MS_SHUTDOWN; + cs->at_state.pending_commands |= PC_SHUTDOWN; + gig_dbg(DEBUG_EVENT, "Scheduling PC_SHUTDOWN"); + cs->commands_pending = 1; + } else + finish_shutdown(cs); +} + +static void do_stop(struct cardstate *cs) +{ + unsigned long flags; + + spin_lock_irqsave(&cs->lock, flags); + cs->connected = 0; + spin_unlock_irqrestore(&cs->lock, flags); + + do_shutdown(cs); +} + +/* Entering cid mode or getting a cid failed: + * try to initialize the device and try again. + * + * channel >= 0: getting cid for the channel failed + * channel < 0: entering cid mode failed + * + * returns 0 on success, <0 on failure + */ +static int reinit_and_retry(struct cardstate *cs, int channel) +{ + int i; + + if (--cs->retry_count <= 0) + return -EFAULT; + + for (i = 0; i < cs->channels; ++i) + if (cs->bcs[i].at_state.cid > 0) + return -EBUSY; + + if (channel < 0) + dev_warn(cs->dev, + "Could not enter cid mode. Reinit device and try again.\n"); + else { + dev_warn(cs->dev, + "Could not get a call id. Reinit device and try again.\n"); + cs->bcs[channel].at_state.pending_commands |= PC_CID; + } + schedule_init(cs, MS_INIT); + return 0; +} + +static int at_state_invalid(struct cardstate *cs, + struct at_state_t *test_ptr) +{ + unsigned long flags; + unsigned channel; + struct at_state_t *at_state; + int retval = 0; + + spin_lock_irqsave(&cs->lock, flags); + + if (test_ptr == &cs->at_state) + goto exit; + + list_for_each_entry(at_state, &cs->temp_at_states, list) + if (at_state == test_ptr) + goto exit; + + for (channel = 0; channel < cs->channels; ++channel) + if (&cs->bcs[channel].at_state == test_ptr) + goto exit; + + retval = 1; +exit: + spin_unlock_irqrestore(&cs->lock, flags); + return retval; +} + +static void handle_icall(struct cardstate *cs, struct bc_state *bcs, + struct at_state_t *at_state) +{ + int retval; + + retval = gigaset_isdn_icall(at_state); + switch (retval) { + case ICALL_ACCEPT: + break; + default: + dev_err(cs->dev, "internal error: disposition=%d\n", retval); + /* fall through */ + case ICALL_IGNORE: + case ICALL_REJECT: + /* hang up actively + * Device doc says that would reject the call. + * In fact it doesn't. + */ + at_state->pending_commands |= PC_HUP; + cs->commands_pending = 1; + break; + } +} + +static int do_lock(struct cardstate *cs) +{ + int mode; + int i; + + switch (cs->mstate) { + case MS_UNINITIALIZED: + case MS_READY: + if (cs->cur_at_seq || !list_empty(&cs->temp_at_states) || + cs->at_state.pending_commands) + return -EBUSY; + + for (i = 0; i < cs->channels; ++i) + if (cs->bcs[i].at_state.pending_commands) + return -EBUSY; + + if (gigaset_get_channels(cs) < 0) + return -EBUSY; + + break; + case MS_LOCKED: + break; + default: + return -EBUSY; + } + + mode = cs->mode; + cs->mstate = MS_LOCKED; + cs->mode = M_UNKNOWN; + + return mode; +} + +static int do_unlock(struct cardstate *cs) +{ + if (cs->mstate != MS_LOCKED) + return -EINVAL; + + cs->mstate = MS_UNINITIALIZED; + cs->mode = M_UNKNOWN; + gigaset_free_channels(cs); + if (cs->connected) + schedule_init(cs, MS_INIT); + + return 0; +} + +static void do_action(int action, struct cardstate *cs, + struct bc_state *bcs, + struct at_state_t **p_at_state, char **pp_command, + int *p_genresp, int *p_resp_code, + struct event_t *ev) +{ + struct at_state_t *at_state = *p_at_state; + struct bc_state *bcs2; + unsigned long flags; + + int channel; + + unsigned char *s, *e; + int i; + unsigned long val; + + switch (action) { + case ACT_NOTHING: + break; + case ACT_TIMEOUT: + at_state->waiting = 1; + break; + case ACT_INIT: + cs->at_state.pending_commands &= ~PC_INIT; + cs->cur_at_seq = SEQ_NONE; + cs->mode = M_UNIMODEM; + spin_lock_irqsave(&cs->lock, flags); + if (!cs->cidmode) { + spin_unlock_irqrestore(&cs->lock, flags); + gigaset_free_channels(cs); + cs->mstate = MS_READY; + break; + } + spin_unlock_irqrestore(&cs->lock, flags); + cs->at_state.pending_commands |= PC_CIDMODE; + gig_dbg(DEBUG_EVENT, "Scheduling PC_CIDMODE"); + cs->commands_pending = 1; + break; + case ACT_FAILINIT: + dev_warn(cs->dev, "Could not initialize the device.\n"); + cs->dle = 0; + init_failed(cs, M_UNKNOWN); + cs->cur_at_seq = SEQ_NONE; + break; + case ACT_CONFIGMODE: + init_failed(cs, M_CONFIG); + cs->cur_at_seq = SEQ_NONE; + break; + case ACT_SETDLE1: + cs->dle = 1; + /* cs->inbuf[0].inputstate |= INS_command | INS_DLE_command; */ + cs->inbuf[0].inputstate &= + ~(INS_command | INS_DLE_command); + break; + case ACT_SETDLE0: + cs->dle = 0; + cs->inbuf[0].inputstate = + (cs->inbuf[0].inputstate & ~INS_DLE_command) + | INS_command; + break; + case ACT_CMODESET: + if (cs->mstate == MS_INIT || cs->mstate == MS_RECOVER) { + gigaset_free_channels(cs); + cs->mstate = MS_READY; + } + cs->mode = M_CID; + cs->cur_at_seq = SEQ_NONE; + break; + case ACT_UMODESET: + cs->mode = M_UNIMODEM; + cs->cur_at_seq = SEQ_NONE; + break; + case ACT_FAILCMODE: + cs->cur_at_seq = SEQ_NONE; + if (cs->mstate == MS_INIT || cs->mstate == MS_RECOVER) { + init_failed(cs, M_UNKNOWN); + break; + } + if (reinit_and_retry(cs, -1) < 0) + schedule_init(cs, MS_RECOVER); + break; + case ACT_FAILUMODE: + cs->cur_at_seq = SEQ_NONE; + schedule_init(cs, MS_RECOVER); + break; + case ACT_HUPMODEM: + /* send "+++" (hangup in unimodem mode) */ + if (cs->connected) { + struct cmdbuf_t *cb; + + cb = kmalloc(sizeof(struct cmdbuf_t) + 3, GFP_ATOMIC); + if (!cb) { + dev_err(cs->dev, "%s: out of memory\n", + __func__); + return; + } + memcpy(cb->buf, "+++", 3); + cb->len = 3; + cb->offset = 0; + cb->next = NULL; + cb->wake_tasklet = NULL; + cs->ops->write_cmd(cs, cb); + } + break; + case ACT_RING: + /* get fresh AT state structure for new CID */ + at_state = get_free_channel(cs, ev->parameter); + if (!at_state) { + dev_warn(cs->dev, + "RING ignored: could not allocate channel structure\n"); + break; + } + + /* initialize AT state structure + * note that bcs may be NULL if no B channel is free + */ + at_state->ConState = 700; + for (i = 0; i < STR_NUM; ++i) { + kfree(at_state->str_var[i]); + at_state->str_var[i] = NULL; + } + at_state->int_var[VAR_ZCTP] = -1; + + spin_lock_irqsave(&cs->lock, flags); + at_state->timer_expires = RING_TIMEOUT; + at_state->timer_active = 1; + spin_unlock_irqrestore(&cs->lock, flags); + break; + case ACT_ICALL: + handle_icall(cs, bcs, at_state); + break; + case ACT_FAILSDOWN: + dev_warn(cs->dev, "Could not shut down the device.\n"); + /* fall through */ + case ACT_FAKESDOWN: + case ACT_SDOWN: + cs->cur_at_seq = SEQ_NONE; + finish_shutdown(cs); + break; + case ACT_CONNECT: + if (cs->onechannel) { + at_state->pending_commands |= PC_DLE1; + cs->commands_pending = 1; + break; + } + bcs->chstate |= CHS_D_UP; + gigaset_isdn_connD(bcs); + cs->ops->init_bchannel(bcs); + break; + case ACT_DLE1: + cs->cur_at_seq = SEQ_NONE; + bcs = cs->bcs + cs->curchannel; + + bcs->chstate |= CHS_D_UP; + gigaset_isdn_connD(bcs); + cs->ops->init_bchannel(bcs); + break; + case ACT_FAKEHUP: + at_state->int_var[VAR_ZSAU] = ZSAU_NULL; + /* fall through */ + case ACT_DISCONNECT: + cs->cur_at_seq = SEQ_NONE; + at_state->cid = -1; + if (!bcs) { + disconnect_nobc(p_at_state, cs); + } else if (cs->onechannel && cs->dle) { + /* Check for other open channels not needed: + * DLE only used for M10x with one B channel. + */ + at_state->pending_commands |= PC_DLE0; + cs->commands_pending = 1; + } else { + disconnect_bc(at_state, cs, bcs); + } + break; + case ACT_FAKEDLE0: + at_state->int_var[VAR_ZDLE] = 0; + cs->dle = 0; + /* fall through */ + case ACT_DLE0: + cs->cur_at_seq = SEQ_NONE; + bcs2 = cs->bcs + cs->curchannel; + disconnect_bc(&bcs2->at_state, cs, bcs2); + break; + case ACT_ABORTHUP: + cs->cur_at_seq = SEQ_NONE; + dev_warn(cs->dev, "Could not hang up.\n"); + at_state->cid = -1; + if (!bcs) + disconnect_nobc(p_at_state, cs); + else if (cs->onechannel) + at_state->pending_commands |= PC_DLE0; + else + disconnect_bc(at_state, cs, bcs); + schedule_init(cs, MS_RECOVER); + break; + case ACT_FAILDLE0: + cs->cur_at_seq = SEQ_NONE; + dev_warn(cs->dev, "Error leaving DLE mode.\n"); + cs->dle = 0; + bcs2 = cs->bcs + cs->curchannel; + disconnect_bc(&bcs2->at_state, cs, bcs2); + schedule_init(cs, MS_RECOVER); + break; + case ACT_FAILDLE1: + cs->cur_at_seq = SEQ_NONE; + dev_warn(cs->dev, + "Could not enter DLE mode. Trying to hang up.\n"); + channel = cs->curchannel; + cs->bcs[channel].at_state.pending_commands |= PC_HUP; + cs->commands_pending = 1; + break; + + case ACT_CID: /* got cid; start dialing */ + cs->cur_at_seq = SEQ_NONE; + channel = cs->curchannel; + if (ev->parameter > 0 && ev->parameter <= 65535) { + cs->bcs[channel].at_state.cid = ev->parameter; + cs->bcs[channel].at_state.pending_commands |= + PC_DIAL; + cs->commands_pending = 1; + break; + } + /* fall through - bad cid */ + case ACT_FAILCID: + cs->cur_at_seq = SEQ_NONE; + channel = cs->curchannel; + if (reinit_and_retry(cs, channel) < 0) { + dev_warn(cs->dev, + "Could not get a call ID. Cannot dial.\n"); + bcs2 = cs->bcs + channel; + disconnect_bc(&bcs2->at_state, cs, bcs2); + } + break; + case ACT_ABORTCID: + cs->cur_at_seq = SEQ_NONE; + bcs2 = cs->bcs + cs->curchannel; + disconnect_bc(&bcs2->at_state, cs, bcs2); + break; + + case ACT_DIALING: + case ACT_ACCEPTED: + cs->cur_at_seq = SEQ_NONE; + break; + + case ACT_ABORTACCEPT: /* hangup/error/timeout during ICALL procssng */ + if (bcs) + disconnect_bc(at_state, cs, bcs); + else + disconnect_nobc(p_at_state, cs); + break; + + case ACT_ABORTDIAL: /* error/timeout during dial preparation */ + cs->cur_at_seq = SEQ_NONE; + at_state->pending_commands |= PC_HUP; + cs->commands_pending = 1; + break; + + case ACT_REMOTEREJECT: /* DISCONNECT_IND after dialling */ + case ACT_CONNTIMEOUT: /* timeout waiting for ZSAU=ACTIVE */ + case ACT_REMOTEHUP: /* DISCONNECT_IND with established connection */ + at_state->pending_commands |= PC_HUP; + cs->commands_pending = 1; + break; + case ACT_GETSTRING: /* warning: RING, ZDLE, ... + are not handled properly anymore */ + at_state->getstring = 1; + break; + case ACT_SETVER: + if (!ev->ptr) { + *p_genresp = 1; + *p_resp_code = RSP_ERROR; + break; + } + s = ev->ptr; + + if (!strcmp(s, "OK")) { + /* OK without version string: assume old response */ + *p_genresp = 1; + *p_resp_code = RSP_NONE; + break; + } + + for (i = 0; i < 4; ++i) { + val = simple_strtoul(s, (char **) &e, 10); + if (val > INT_MAX || e == s) + break; + if (i == 3) { + if (*e) + break; + } else if (*e != '.') + break; + else + s = e + 1; + cs->fwver[i] = val; + } + if (i != 4) { + *p_genresp = 1; + *p_resp_code = RSP_ERROR; + break; + } + cs->gotfwver = 0; + break; + case ACT_GOTVER: + if (cs->gotfwver == 0) { + cs->gotfwver = 1; + gig_dbg(DEBUG_EVENT, + "firmware version %02d.%03d.%02d.%02d", + cs->fwver[0], cs->fwver[1], + cs->fwver[2], cs->fwver[3]); + break; + } + /* fall through */ + case ACT_FAILVER: + cs->gotfwver = -1; + dev_err(cs->dev, "could not read firmware version.\n"); + break; + case ACT_ERROR: + gig_dbg(DEBUG_ANY, "%s: ERROR response in ConState %d", + __func__, at_state->ConState); + cs->cur_at_seq = SEQ_NONE; + break; + case ACT_DEBUG: + gig_dbg(DEBUG_ANY, "%s: resp_code %d in ConState %d", + __func__, ev->type, at_state->ConState); + break; + case ACT_WARN: + dev_warn(cs->dev, "%s: resp_code %d in ConState %d!\n", + __func__, ev->type, at_state->ConState); + break; + case ACT_ZCAU: + dev_warn(cs->dev, "cause code %04x in connection state %d.\n", + ev->parameter, at_state->ConState); + break; + + /* events from the LL */ + + case ACT_DIAL: + if (!ev->ptr) { + *p_genresp = 1; + *p_resp_code = RSP_ERROR; + break; + } + start_dial(at_state, ev->ptr, ev->parameter); + break; + case ACT_ACCEPT: + start_accept(at_state); + break; + case ACT_HUP: + at_state->pending_commands |= PC_HUP; + gig_dbg(DEBUG_EVENT, "Scheduling PC_HUP"); + cs->commands_pending = 1; + break; + + /* hotplug events */ + + case ACT_STOP: + do_stop(cs); + break; + case ACT_START: + do_start(cs); + break; + + /* events from the interface */ + + case ACT_IF_LOCK: + cs->cmd_result = ev->parameter ? do_lock(cs) : do_unlock(cs); + cs->waiting = 0; + wake_up(&cs->waitqueue); + break; + case ACT_IF_VER: + if (ev->parameter != 0) + cs->cmd_result = -EINVAL; + else if (cs->gotfwver != 1) { + cs->cmd_result = -ENOENT; + } else { + memcpy(ev->arg, cs->fwver, sizeof cs->fwver); + cs->cmd_result = 0; + } + cs->waiting = 0; + wake_up(&cs->waitqueue); + break; + + /* events from the proc file system */ + + case ACT_PROC_CIDMODE: + spin_lock_irqsave(&cs->lock, flags); + if (ev->parameter != cs->cidmode) { + cs->cidmode = ev->parameter; + if (ev->parameter) { + cs->at_state.pending_commands |= PC_CIDMODE; + gig_dbg(DEBUG_EVENT, "Scheduling PC_CIDMODE"); + } else { + cs->at_state.pending_commands |= PC_UMMODE; + gig_dbg(DEBUG_EVENT, "Scheduling PC_UMMODE"); + } + cs->commands_pending = 1; + } + spin_unlock_irqrestore(&cs->lock, flags); + cs->waiting = 0; + wake_up(&cs->waitqueue); + break; + + /* events from the hardware drivers */ + + case ACT_NOTIFY_BC_DOWN: + bchannel_down(bcs); + break; + case ACT_NOTIFY_BC_UP: + bchannel_up(bcs); + break; + case ACT_SHUTDOWN: + do_shutdown(cs); + break; + + + default: + if (action >= ACT_CMD && action < ACT_CMD + AT_NUM) { + *pp_command = at_state->bcs->commands[action - ACT_CMD]; + if (!*pp_command) { + *p_genresp = 1; + *p_resp_code = RSP_NULL; + } + } else + dev_err(cs->dev, "%s: action==%d!\n", __func__, action); + } +} + +/* State machine to do the calling and hangup procedure */ +static void process_event(struct cardstate *cs, struct event_t *ev) +{ + struct bc_state *bcs; + char *p_command = NULL; + struct reply_t *rep; + int rcode; + int genresp = 0; + int resp_code = RSP_ERROR; + struct at_state_t *at_state; + int index; + int curact; + unsigned long flags; + + if (ev->cid >= 0) { + at_state = at_state_from_cid(cs, ev->cid); + if (!at_state) { + gig_dbg(DEBUG_EVENT, "event %d for invalid cid %d", + ev->type, ev->cid); + gigaset_add_event(cs, &cs->at_state, RSP_WRONG_CID, + NULL, 0, NULL); + return; + } + } else { + at_state = ev->at_state; + if (at_state_invalid(cs, at_state)) { + gig_dbg(DEBUG_EVENT, "event for invalid at_state %p", + at_state); + return; + } + } + + gig_dbg(DEBUG_EVENT, "connection state %d, event %d", + at_state->ConState, ev->type); + + bcs = at_state->bcs; + + /* Setting the pointer to the dial array */ + rep = at_state->replystruct; + + spin_lock_irqsave(&cs->lock, flags); + if (ev->type == EV_TIMEOUT) { + if (ev->parameter != at_state->timer_index + || !at_state->timer_active) { + ev->type = RSP_NONE; /* old timeout */ + gig_dbg(DEBUG_EVENT, "old timeout"); + } else { + if (at_state->waiting) + gig_dbg(DEBUG_EVENT, "stopped waiting"); + else + gig_dbg(DEBUG_EVENT, "timeout occurred"); + } + } + spin_unlock_irqrestore(&cs->lock, flags); + + /* if the response belongs to a variable in at_state->int_var[VAR_XXXX] + or at_state->str_var[STR_XXXX], set it */ + if (ev->type >= RSP_VAR && ev->type < RSP_VAR + VAR_NUM) { + index = ev->type - RSP_VAR; + at_state->int_var[index] = ev->parameter; + } else if (ev->type >= RSP_STR && ev->type < RSP_STR + STR_NUM) { + index = ev->type - RSP_STR; + kfree(at_state->str_var[index]); + at_state->str_var[index] = ev->ptr; + ev->ptr = NULL; /* prevent process_events() from + deallocating ptr */ + } + + if (ev->type == EV_TIMEOUT || ev->type == RSP_STRING) + at_state->getstring = 0; + + /* Search row in dial array which matches modem response and current + constate */ + for (;; rep++) { + rcode = rep->resp_code; + if (rcode == RSP_LAST) { + /* found nothing...*/ + dev_warn(cs->dev, "%s: rcode=RSP_LAST: " + "resp_code %d in ConState %d!\n", + __func__, ev->type, at_state->ConState); + return; + } + if ((rcode == RSP_ANY || rcode == ev->type) + && ((int) at_state->ConState >= rep->min_ConState) + && (rep->max_ConState < 0 + || (int) at_state->ConState <= rep->max_ConState) + && (rep->parameter < 0 || rep->parameter == ev->parameter)) + break; + } + + p_command = rep->command; + + at_state->waiting = 0; + for (curact = 0; curact < MAXACT; ++curact) { + /* The row tells us what we should do .. + */ + do_action(rep->action[curact], cs, bcs, &at_state, &p_command, + &genresp, &resp_code, ev); + if (!at_state) + /* at_state destroyed by disconnect */ + return; + } + + /* Jump to the next con-state regarding the array */ + if (rep->new_ConState >= 0) + at_state->ConState = rep->new_ConState; + + if (genresp) { + spin_lock_irqsave(&cs->lock, flags); + at_state->timer_expires = 0; + at_state->timer_active = 0; + spin_unlock_irqrestore(&cs->lock, flags); + gigaset_add_event(cs, at_state, resp_code, NULL, 0, NULL); + } else { + /* Send command to modem if not NULL... */ + if (p_command) { + if (cs->connected) + send_command(cs, p_command, at_state); + else + gigaset_add_event(cs, at_state, RSP_NODEV, + NULL, 0, NULL); + } + + spin_lock_irqsave(&cs->lock, flags); + if (!rep->timeout) { + at_state->timer_expires = 0; + at_state->timer_active = 0; + } else if (rep->timeout > 0) { /* new timeout */ + at_state->timer_expires = rep->timeout * 10; + at_state->timer_active = 1; + ++at_state->timer_index; + } + spin_unlock_irqrestore(&cs->lock, flags); + } +} + +static void schedule_sequence(struct cardstate *cs, + struct at_state_t *at_state, int sequence) +{ + cs->cur_at_seq = sequence; + gigaset_add_event(cs, at_state, RSP_INIT, NULL, sequence, NULL); +} + +static void process_command_flags(struct cardstate *cs) +{ + struct at_state_t *at_state = NULL; + struct bc_state *bcs; + int i; + int sequence; + unsigned long flags; + + cs->commands_pending = 0; + + if (cs->cur_at_seq) { + gig_dbg(DEBUG_EVENT, "not searching scheduled commands: busy"); + return; + } + + gig_dbg(DEBUG_EVENT, "searching scheduled commands"); + + sequence = SEQ_NONE; + + /* clear pending_commands and hangup channels on shutdown */ + if (cs->at_state.pending_commands & PC_SHUTDOWN) { + cs->at_state.pending_commands &= ~PC_CIDMODE; + for (i = 0; i < cs->channels; ++i) { + bcs = cs->bcs + i; + at_state = &bcs->at_state; + at_state->pending_commands &= + ~(PC_DLE1 | PC_ACCEPT | PC_DIAL); + if (at_state->cid > 0) + at_state->pending_commands |= PC_HUP; + if (at_state->pending_commands & PC_CID) { + at_state->pending_commands |= PC_NOCID; + at_state->pending_commands &= ~PC_CID; + } + } + } + + /* clear pending_commands and hangup channels on reset */ + if (cs->at_state.pending_commands & PC_INIT) { + cs->at_state.pending_commands &= ~PC_CIDMODE; + for (i = 0; i < cs->channels; ++i) { + bcs = cs->bcs + i; + at_state = &bcs->at_state; + at_state->pending_commands &= + ~(PC_DLE1 | PC_ACCEPT | PC_DIAL); + if (at_state->cid > 0) + at_state->pending_commands |= PC_HUP; + if (cs->mstate == MS_RECOVER) { + if (at_state->pending_commands & PC_CID) { + at_state->pending_commands |= PC_NOCID; + at_state->pending_commands &= ~PC_CID; + } + } + } + } + + /* only switch back to unimodem mode if no commands are pending and + * no channels are up */ + spin_lock_irqsave(&cs->lock, flags); + if (cs->at_state.pending_commands == PC_UMMODE + && !cs->cidmode + && list_empty(&cs->temp_at_states) + && cs->mode == M_CID) { + sequence = SEQ_UMMODE; + at_state = &cs->at_state; + for (i = 0; i < cs->channels; ++i) { + bcs = cs->bcs + i; + if (bcs->at_state.pending_commands || + bcs->at_state.cid > 0) { + sequence = SEQ_NONE; + break; + } + } + } + spin_unlock_irqrestore(&cs->lock, flags); + cs->at_state.pending_commands &= ~PC_UMMODE; + if (sequence != SEQ_NONE) { + schedule_sequence(cs, at_state, sequence); + return; + } + + for (i = 0; i < cs->channels; ++i) { + bcs = cs->bcs + i; + if (bcs->at_state.pending_commands & PC_HUP) { + if (cs->dle) { + cs->curchannel = bcs->channel; + schedule_sequence(cs, &cs->at_state, SEQ_DLE0); + return; + } + bcs->at_state.pending_commands &= ~PC_HUP; + if (bcs->at_state.pending_commands & PC_CID) { + /* not yet dialing: PC_NOCID is sufficient */ + bcs->at_state.pending_commands |= PC_NOCID; + bcs->at_state.pending_commands &= ~PC_CID; + } else { + schedule_sequence(cs, &bcs->at_state, SEQ_HUP); + return; + } + } + if (bcs->at_state.pending_commands & PC_NOCID) { + bcs->at_state.pending_commands &= ~PC_NOCID; + cs->curchannel = bcs->channel; + schedule_sequence(cs, &cs->at_state, SEQ_NOCID); + return; + } else if (bcs->at_state.pending_commands & PC_DLE0) { + bcs->at_state.pending_commands &= ~PC_DLE0; + cs->curchannel = bcs->channel; + schedule_sequence(cs, &cs->at_state, SEQ_DLE0); + return; + } + } + + list_for_each_entry(at_state, &cs->temp_at_states, list) + if (at_state->pending_commands & PC_HUP) { + at_state->pending_commands &= ~PC_HUP; + schedule_sequence(cs, at_state, SEQ_HUP); + return; + } + + if (cs->at_state.pending_commands & PC_INIT) { + cs->at_state.pending_commands &= ~PC_INIT; + cs->dle = 0; + cs->inbuf->inputstate = INS_command; + schedule_sequence(cs, &cs->at_state, SEQ_INIT); + return; + } + if (cs->at_state.pending_commands & PC_SHUTDOWN) { + cs->at_state.pending_commands &= ~PC_SHUTDOWN; + schedule_sequence(cs, &cs->at_state, SEQ_SHUTDOWN); + return; + } + if (cs->at_state.pending_commands & PC_CIDMODE) { + cs->at_state.pending_commands &= ~PC_CIDMODE; + if (cs->mode == M_UNIMODEM) { + cs->retry_count = 1; + schedule_sequence(cs, &cs->at_state, SEQ_CIDMODE); + return; + } + } + + for (i = 0; i < cs->channels; ++i) { + bcs = cs->bcs + i; + if (bcs->at_state.pending_commands & PC_DLE1) { + bcs->at_state.pending_commands &= ~PC_DLE1; + cs->curchannel = bcs->channel; + schedule_sequence(cs, &cs->at_state, SEQ_DLE1); + return; + } + if (bcs->at_state.pending_commands & PC_ACCEPT) { + bcs->at_state.pending_commands &= ~PC_ACCEPT; + schedule_sequence(cs, &bcs->at_state, SEQ_ACCEPT); + return; + } + if (bcs->at_state.pending_commands & PC_DIAL) { + bcs->at_state.pending_commands &= ~PC_DIAL; + schedule_sequence(cs, &bcs->at_state, SEQ_DIAL); + return; + } + if (bcs->at_state.pending_commands & PC_CID) { + switch (cs->mode) { + case M_UNIMODEM: + cs->at_state.pending_commands |= PC_CIDMODE; + gig_dbg(DEBUG_EVENT, "Scheduling PC_CIDMODE"); + cs->commands_pending = 1; + return; + case M_UNKNOWN: + schedule_init(cs, MS_INIT); + return; + } + bcs->at_state.pending_commands &= ~PC_CID; + cs->curchannel = bcs->channel; + cs->retry_count = 2; + schedule_sequence(cs, &cs->at_state, SEQ_CID); + return; + } + } +} + +static void process_events(struct cardstate *cs) +{ + struct event_t *ev; + unsigned head, tail; + int i; + int check_flags = 0; + int was_busy; + unsigned long flags; + + spin_lock_irqsave(&cs->ev_lock, flags); + head = cs->ev_head; + + for (i = 0; i < 2 * MAX_EVENTS; ++i) { + tail = cs->ev_tail; + if (tail == head) { + if (!check_flags && !cs->commands_pending) + break; + check_flags = 0; + spin_unlock_irqrestore(&cs->ev_lock, flags); + process_command_flags(cs); + spin_lock_irqsave(&cs->ev_lock, flags); + tail = cs->ev_tail; + if (tail == head) { + if (!cs->commands_pending) + break; + continue; + } + } + + ev = cs->events + head; + was_busy = cs->cur_at_seq != SEQ_NONE; + spin_unlock_irqrestore(&cs->ev_lock, flags); + process_event(cs, ev); + spin_lock_irqsave(&cs->ev_lock, flags); + kfree(ev->ptr); + ev->ptr = NULL; + if (was_busy && cs->cur_at_seq == SEQ_NONE) + check_flags = 1; + + head = (head + 1) % MAX_EVENTS; + cs->ev_head = head; + } + + spin_unlock_irqrestore(&cs->ev_lock, flags); + + if (i == 2 * MAX_EVENTS) { + dev_err(cs->dev, + "infinite loop in process_events; aborting.\n"); + } +} + +/* tasklet scheduled on any event received from the Gigaset device + * parameter: + * data ISDN controller state structure + */ +void gigaset_handle_event(unsigned long data) +{ + struct cardstate *cs = (struct cardstate *) data; + + /* handle incoming data on control/common channel */ + if (cs->inbuf->head != cs->inbuf->tail) { + gig_dbg(DEBUG_INTR, "processing new data"); + cs->ops->handle_input(cs->inbuf); + } + + process_events(cs); +} diff --git a/drivers/staging/isdn/gigaset/gigaset.h b/drivers/staging/isdn/gigaset/gigaset.h new file mode 100644 index 000000000000..166537e2dfca --- /dev/null +++ b/drivers/staging/isdn/gigaset/gigaset.h @@ -0,0 +1,830 @@ +/* + * Siemens Gigaset 307x driver + * Common header file for all connection variants + * + * Written by Stefan Eilers + * and Hansjoerg Lipp <hjlipp@web.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. + * ===================================================================== + */ + +#ifndef GIGASET_H +#define GIGASET_H + +/* define global prefix for pr_ macros in linux/kernel.h */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/compiler.h> +#include <linux/types.h> +#include <linux/ctype.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/skbuff.h> +#include <linux/netdevice.h> +#include <linux/ppp_defs.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/tty.h> +#include <linux/tty_driver.h> +#include <linux/list.h> +#include <linux/atomic.h> + +#define GIG_VERSION {0, 5, 0, 0} +#define GIG_COMPAT {0, 4, 0, 0} + +#define MAX_REC_PARAMS 10 /* Max. number of params in response string */ +#define MAX_RESP_SIZE 511 /* Max. size of a response string */ + +#define MAX_EVENTS 64 /* size of event queue */ + +#define RBUFSIZE 8192 + +#define GIG_TICK 100 /* in milliseconds */ + +/* timeout values (unit: 1 sec) */ +#define INIT_TIMEOUT 1 + +/* timeout values (unit: 0.1 sec) */ +#define RING_TIMEOUT 3 /* for additional parameters to RING */ +#define BAS_TIMEOUT 20 /* for response to Base USB ops */ +#define ATRDY_TIMEOUT 3 /* for HD_READY_SEND_ATDATA */ + +#define BAS_RETRY 3 /* max. retries for base USB ops */ + +#define MAXACT 3 + +extern int gigaset_debuglevel; /* "needs" cast to (enum debuglevel) */ + +/* debug flags, combine by adding/bitwise OR */ +enum debuglevel { + DEBUG_INTR = 0x00008, /* interrupt processing */ + DEBUG_CMD = 0x00020, /* sent/received LL commands */ + DEBUG_STREAM = 0x00040, /* application data stream I/O events */ + DEBUG_STREAM_DUMP = 0x00080, /* application data stream content */ + DEBUG_LLDATA = 0x00100, /* sent/received LL data */ + DEBUG_EVENT = 0x00200, /* event processing */ + DEBUG_HDLC = 0x00800, /* M10x HDLC processing */ + DEBUG_CHANNEL = 0x01000, /* channel allocation/deallocation */ + DEBUG_TRANSCMD = 0x02000, /* AT-COMMANDS+RESPONSES */ + DEBUG_MCMD = 0x04000, /* COMMANDS THAT ARE SENT VERY OFTEN */ + DEBUG_INIT = 0x08000, /* (de)allocation+initialization of data + structures */ + DEBUG_SUSPEND = 0x10000, /* suspend/resume processing */ + DEBUG_OUTPUT = 0x20000, /* output to device */ + DEBUG_ISO = 0x40000, /* isochronous transfers */ + DEBUG_IF = 0x80000, /* character device operations */ + DEBUG_USBREQ = 0x100000, /* USB communication (except payload + data) */ + DEBUG_LOCKCMD = 0x200000, /* AT commands and responses when + MS_LOCKED */ + + DEBUG_ANY = 0x3fffff, /* print message if any of the others is + activated */ +}; + +#ifdef CONFIG_GIGASET_DEBUG + +#define gig_dbg(level, format, arg...) \ + do { \ + if (unlikely(((enum debuglevel)gigaset_debuglevel) & (level))) \ + printk(KERN_DEBUG KBUILD_MODNAME ": " format "\n", \ + ## arg); \ + } while (0) +#define DEBUG_DEFAULT (DEBUG_TRANSCMD | DEBUG_CMD | DEBUG_USBREQ) + +#else + +#define gig_dbg(level, format, arg...) do {} while (0) +#define DEBUG_DEFAULT 0 + +#endif + +void gigaset_dbg_buffer(enum debuglevel level, const unsigned char *msg, + size_t len, const unsigned char *buf); + +/* connection state */ +#define ZSAU_NONE 0 +#define ZSAU_PROCEEDING 1 +#define ZSAU_CALL_DELIVERED 2 +#define ZSAU_ACTIVE 3 +#define ZSAU_DISCONNECT_IND 4 +#define ZSAU_NULL 5 +#define ZSAU_DISCONNECT_REQ 6 +#define ZSAU_UNKNOWN -1 + +/* USB control transfer requests */ +#define OUT_VENDOR_REQ (USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_ENDPOINT) +#define IN_VENDOR_REQ (USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_ENDPOINT) + +/* interrupt pipe messages */ +#define HD_B1_FLOW_CONTROL 0x80 +#define HD_B2_FLOW_CONTROL 0x81 +#define HD_RECEIVEATDATA_ACK (0x35) /* 3070 */ +#define HD_READY_SEND_ATDATA (0x36) /* 3070 */ +#define HD_OPEN_ATCHANNEL_ACK (0x37) /* 3070 */ +#define HD_CLOSE_ATCHANNEL_ACK (0x38) /* 3070 */ +#define HD_DEVICE_INIT_OK (0x11) /* ISurf USB + 3070 */ +#define HD_OPEN_B1CHANNEL_ACK (0x51) /* ISurf USB + 3070 */ +#define HD_OPEN_B2CHANNEL_ACK (0x52) /* ISurf USB + 3070 */ +#define HD_CLOSE_B1CHANNEL_ACK (0x53) /* ISurf USB + 3070 */ +#define HD_CLOSE_B2CHANNEL_ACK (0x54) /* ISurf USB + 3070 */ +#define HD_SUSPEND_END (0x61) /* ISurf USB */ +#define HD_RESET_INTERRUPT_PIPE_ACK (0xFF) /* ISurf USB + 3070 */ + +/* control requests */ +#define HD_OPEN_B1CHANNEL (0x23) /* ISurf USB + 3070 */ +#define HD_CLOSE_B1CHANNEL (0x24) /* ISurf USB + 3070 */ +#define HD_OPEN_B2CHANNEL (0x25) /* ISurf USB + 3070 */ +#define HD_CLOSE_B2CHANNEL (0x26) /* ISurf USB + 3070 */ +#define HD_RESET_INTERRUPT_PIPE (0x27) /* ISurf USB + 3070 */ +#define HD_DEVICE_INIT_ACK (0x34) /* ISurf USB + 3070 */ +#define HD_WRITE_ATMESSAGE (0x12) /* 3070 */ +#define HD_READ_ATMESSAGE (0x13) /* 3070 */ +#define HD_OPEN_ATCHANNEL (0x28) /* 3070 */ +#define HD_CLOSE_ATCHANNEL (0x29) /* 3070 */ + +/* number of B channels supported by base driver */ +#define BAS_CHANNELS 2 + +/* USB frames for isochronous transfer */ +#define BAS_FRAMETIME 1 /* number of milliseconds between frames */ +#define BAS_NUMFRAMES 8 /* number of frames per URB */ +#define BAS_MAXFRAME 16 /* allocated bytes per frame */ +#define BAS_NORMFRAME 8 /* send size without flow control */ +#define BAS_HIGHFRAME 10 /* " " with positive flow control */ +#define BAS_LOWFRAME 5 /* " " with negative flow control */ +#define BAS_CORRFRAMES 4 /* flow control multiplicator */ + +#define BAS_INBUFSIZE (BAS_MAXFRAME * BAS_NUMFRAMES) /* size of isoc in buf + * per URB */ +#define BAS_OUTBUFSIZE 4096 /* size of common isoc out buffer */ +#define BAS_OUTBUFPAD BAS_MAXFRAME /* size of pad area for isoc out buf */ + +#define BAS_INURBS 3 +#define BAS_OUTURBS 3 + +/* variable commands in struct bc_state */ +#define AT_ISO 0 +#define AT_DIAL 1 +#define AT_MSN 2 +#define AT_BC 3 +#define AT_PROTO 4 +#define AT_TYPE 5 +#define AT_CLIP 6 +/* total number */ +#define AT_NUM 7 + +/* variables in struct at_state_t */ +/* - numeric */ +#define VAR_ZSAU 0 +#define VAR_ZDLE 1 +#define VAR_ZCTP 2 +/* total number */ +#define VAR_NUM 3 +/* - string */ +#define STR_NMBR 0 +#define STR_ZCPN 1 +#define STR_ZCON 2 +#define STR_ZBC 3 +#define STR_ZHLC 4 +/* total number */ +#define STR_NUM 5 + +/* event types */ +#define EV_TIMEOUT -105 +#define EV_IF_VER -106 +#define EV_PROC_CIDMODE -107 +#define EV_SHUTDOWN -108 +#define EV_START -110 +#define EV_STOP -111 +#define EV_IF_LOCK -112 +#define EV_ACCEPT -114 +#define EV_DIAL -115 +#define EV_HUP -116 +#define EV_BC_OPEN -117 +#define EV_BC_CLOSED -118 + +/* input state */ +#define INS_command 0x0001 /* receiving messages (not payload data) */ +#define INS_DLE_char 0x0002 /* DLE flag received (in DLE mode) */ +#define INS_byte_stuff 0x0004 +#define INS_have_data 0x0008 +#define INS_DLE_command 0x0020 /* DLE message start (<DLE> X) received */ +#define INS_flag_hunt 0x0040 + +/* channel state */ +#define CHS_D_UP 0x01 +#define CHS_B_UP 0x02 +#define CHS_NOTIFY_LL 0x04 + +#define ICALL_REJECT 0 +#define ICALL_ACCEPT 1 +#define ICALL_IGNORE 2 + +/* device state */ +#define MS_UNINITIALIZED 0 +#define MS_INIT 1 +#define MS_LOCKED 2 +#define MS_SHUTDOWN 3 +#define MS_RECOVER 4 +#define MS_READY 5 + +/* mode */ +#define M_UNKNOWN 0 +#define M_CONFIG 1 +#define M_UNIMODEM 2 +#define M_CID 3 + +/* start mode */ +#define SM_LOCKED 0 +#define SM_ISDN 1 /* default */ + +/* layer 2 protocols (AT^SBPR=...) */ +#define L2_BITSYNC 0 +#define L2_HDLC 1 +#define L2_VOICE 2 + +struct gigaset_ops; +struct gigaset_driver; + +struct usb_cardstate; +struct ser_cardstate; +struct bas_cardstate; + +struct bc_state; +struct usb_bc_state; +struct ser_bc_state; +struct bas_bc_state; + +struct reply_t { + int resp_code; /* RSP_XXXX */ + int min_ConState; /* <0 => ignore */ + int max_ConState; /* <0 => ignore */ + int parameter; /* e.g. ZSAU_XXXX <0: ignore*/ + int new_ConState; /* <0 => ignore */ + int timeout; /* >0 => *HZ; <=0 => TOUT_XXXX*/ + int action[MAXACT]; /* ACT_XXXX */ + char *command; /* NULL==none */ +}; + +extern struct reply_t gigaset_tab_cid[]; +extern struct reply_t gigaset_tab_nocid[]; + +struct inbuf_t { + struct cardstate *cs; + int inputstate; + int head, tail; + unsigned char data[RBUFSIZE]; +}; + +/* isochronous write buffer structure + * circular buffer with pad area for extraction of complete USB frames + * - data[read..nextread-1] is valid data already submitted to the USB subsystem + * - data[nextread..write-1] is valid data yet to be sent + * - data[write] is the next byte to write to + * - in byte-oriented L2 procotols, it is completely free + * - in bit-oriented L2 procotols, it may contain a partial byte of valid data + * - data[write+1..read-1] is free + * - wbits is the number of valid data bits in data[write], starting at the LSB + * - writesem is the semaphore for writing to the buffer: + * if writesem <= 0, data[write..read-1] is currently being written to + * - idle contains the byte value to repeat when the end of valid data is + * reached; if nextread==write (buffer contains no data to send), either the + * BAS_OUTBUFPAD bytes immediately before data[write] (if + * write>=BAS_OUTBUFPAD) or those of the pad area (if write<BAS_OUTBUFPAD) + * are also filled with that value + */ +struct isowbuf_t { + int read; + int nextread; + int write; + atomic_t writesem; + int wbits; + unsigned char data[BAS_OUTBUFSIZE + BAS_OUTBUFPAD]; + unsigned char idle; +}; + +/* isochronous write URB context structure + * data to be stored along with the URB and retrieved when it is returned + * as completed by the USB subsystem + * - urb: pointer to the URB itself + * - bcs: pointer to the B Channel control structure + * - limit: end of write buffer area covered by this URB + * - status: URB completion status + */ +struct isow_urbctx_t { + struct urb *urb; + struct bc_state *bcs; + int limit; + int status; +}; + +/* AT state structure + * data associated with the state of an ISDN connection, whether or not + * it is currently assigned a B channel + */ +struct at_state_t { + struct list_head list; + int waiting; + int getstring; + unsigned timer_index; + unsigned long timer_expires; + int timer_active; + unsigned int ConState; /* State of connection */ + struct reply_t *replystruct; + int cid; + int int_var[VAR_NUM]; /* see VAR_XXXX */ + char *str_var[STR_NUM]; /* see STR_XXXX */ + unsigned pending_commands; /* see PC_XXXX */ + unsigned seq_index; + + struct cardstate *cs; + struct bc_state *bcs; +}; + +struct event_t { + int type; + void *ptr, *arg; + int parameter; + int cid; + struct at_state_t *at_state; +}; + +/* This buffer holds all information about the used B-Channel */ +struct bc_state { + struct sk_buff *tx_skb; /* Current transfer buffer to modem */ + struct sk_buff_head squeue; /* B-Channel send Queue */ + + /* Variables for debugging .. */ + int corrupted; /* Counter for corrupted packages */ + int trans_down; /* Counter of packages (downstream) */ + int trans_up; /* Counter of packages (upstream) */ + + struct at_state_t at_state; + + /* receive buffer */ + unsigned rx_bufsize; /* max size accepted by application */ + struct sk_buff *rx_skb; + __u16 rx_fcs; + int inputstate; /* see INS_XXXX */ + + int channel; + + struct cardstate *cs; + + unsigned chstate; /* bitmap (CHS_*) */ + int ignore; + unsigned proto2; /* layer 2 protocol (L2_*) */ + char *commands[AT_NUM]; /* see AT_XXXX */ + +#ifdef CONFIG_GIGASET_DEBUG + int emptycount; +#endif + int busy; + int use_count; + + /* private data of hardware drivers */ + union { + struct ser_bc_state *ser; /* serial hardware driver */ + struct usb_bc_state *usb; /* usb hardware driver (m105) */ + struct bas_bc_state *bas; /* usb hardware driver (base) */ + } hw; + + void *ap; /* associated LL application */ + int apconnstate; /* LL application connection state */ + spinlock_t aplock; +}; + +struct cardstate { + struct gigaset_driver *driver; + unsigned minor_index; + struct device *dev; + struct device *tty_dev; + unsigned flags; + + const struct gigaset_ops *ops; + + /* Stuff to handle communication */ + wait_queue_head_t waitqueue; + int waiting; + int mode; /* see M_XXXX */ + int mstate; /* Modem state: see MS_XXXX */ + /* only changed by the event layer */ + int cmd_result; + + int channels; + struct bc_state *bcs; /* Array of struct bc_state */ + + int onechannel; /* data and commands transmitted in one + stream (M10x) */ + + spinlock_t lock; + struct at_state_t at_state; /* at_state_t for cid == 0 */ + struct list_head temp_at_states;/* list of temporary "struct + at_state_t"s without B channel */ + + struct inbuf_t *inbuf; + + struct cmdbuf_t *cmdbuf, *lastcmdbuf; + spinlock_t cmdlock; + unsigned curlen, cmdbytes; + + struct tty_port port; + struct tasklet_struct if_wake_tasklet; + unsigned control_state; + + unsigned fwver[4]; + int gotfwver; + + unsigned running; /* !=0 if events are handled */ + unsigned connected; /* !=0 if hardware is connected */ + unsigned isdn_up; /* !=0 after gigaset_isdn_start() */ + + unsigned cidmode; + + int myid; /* id for communication with LL */ + void *iif; /* LL interface structure */ + unsigned short hw_hdr_len; /* headroom needed in data skbs */ + + struct reply_t *tabnocid; + struct reply_t *tabcid; + int cs_init; + int ignoreframes; /* frames to ignore after setting up the + B channel */ + struct mutex mutex; /* locks this structure: + * connected is not changed, + * hardware_up is not changed, + * MState is not changed to or from + * MS_LOCKED */ + + struct timer_list timer; + int retry_count; + int dle; /* !=0 if DLE mode is active + (ZDLE=1 received -- M10x only) */ + int cur_at_seq; /* sequence of AT commands being + processed */ + int curchannel; /* channel those commands are meant + for */ + int commands_pending; /* flag(s) in xxx.commands_pending have + been set */ + struct tasklet_struct + event_tasklet; /* tasklet for serializing AT commands. + * Scheduled + * -> for modem reponses (and + * incoming data for M10x) + * -> on timeout + * -> after setting bits in + * xxx.at_state.pending_command + * (e.g. command from LL) */ + struct tasklet_struct + write_tasklet; /* tasklet for serial output + * (not used in base driver) */ + + /* event queue */ + struct event_t events[MAX_EVENTS]; + unsigned ev_tail, ev_head; + spinlock_t ev_lock; + + /* current modem response */ + unsigned char respdata[MAX_RESP_SIZE + 1]; + unsigned cbytes; + + /* private data of hardware drivers */ + union { + struct usb_cardstate *usb; /* USB hardware driver (m105) */ + struct ser_cardstate *ser; /* serial hardware driver */ + struct bas_cardstate *bas; /* USB hardware driver (base) */ + } hw; +}; + +struct gigaset_driver { + struct list_head list; + spinlock_t lock; /* locks minor tables and blocked */ + struct tty_driver *tty; + unsigned have_tty; + unsigned minor; + unsigned minors; + struct cardstate *cs; + int blocked; + + const struct gigaset_ops *ops; + struct module *owner; +}; + +struct cmdbuf_t { + struct cmdbuf_t *next, *prev; + int len, offset; + struct tasklet_struct *wake_tasklet; + unsigned char buf[0]; +}; + +struct bas_bc_state { + /* isochronous output state */ + int running; + atomic_t corrbytes; + spinlock_t isooutlock; + struct isow_urbctx_t isoouturbs[BAS_OUTURBS]; + struct isow_urbctx_t *isooutdone, *isooutfree, *isooutovfl; + struct isowbuf_t *isooutbuf; + unsigned numsub; /* submitted URB counter + (for diagnostic messages only) */ + struct tasklet_struct sent_tasklet; + + /* isochronous input state */ + spinlock_t isoinlock; + struct urb *isoinurbs[BAS_INURBS]; + unsigned char isoinbuf[BAS_INBUFSIZE * BAS_INURBS]; + struct urb *isoindone; /* completed isoc read URB */ + int isoinstatus; /* status of completed URB */ + int loststatus; /* status of dropped URB */ + unsigned isoinlost; /* number of bytes lost */ + /* state of bit unstuffing algorithm + (in addition to BC_state.inputstate) */ + unsigned seqlen; /* number of '1' bits not yet + unstuffed */ + unsigned inbyte, inbits; /* collected bits for next byte */ + /* statistics */ + unsigned goodbytes; /* bytes correctly received */ + unsigned alignerrs; /* frames with incomplete byte at end */ + unsigned fcserrs; /* FCS errors */ + unsigned frameerrs; /* framing errors */ + unsigned giants; /* long frames */ + unsigned runts; /* short frames */ + unsigned aborts; /* HDLC aborts */ + unsigned shared0s; /* '0' bits shared between flags */ + unsigned stolen0s; /* '0' stuff bits also serving as + leading flag bits */ + struct tasklet_struct rcvd_tasklet; +}; + +struct gigaset_ops { + /* Called from ev-layer.c/interface.c for sending AT commands to the + device */ + int (*write_cmd)(struct cardstate *cs, struct cmdbuf_t *cb); + + /* Called from interface.c for additional device control */ + int (*write_room)(struct cardstate *cs); + int (*chars_in_buffer)(struct cardstate *cs); + int (*brkchars)(struct cardstate *cs, const unsigned char buf[6]); + + /* Called from ev-layer.c after setting up connection + * Should call gigaset_bchannel_up(), when finished. */ + int (*init_bchannel)(struct bc_state *bcs); + + /* Called from ev-layer.c after hanging up + * Should call gigaset_bchannel_down(), when finished. */ + int (*close_bchannel)(struct bc_state *bcs); + + /* Called by gigaset_initcs() for setting up bcs->hw.xxx */ + int (*initbcshw)(struct bc_state *bcs); + + /* Called by gigaset_freecs() for freeing bcs->hw.xxx */ + void (*freebcshw)(struct bc_state *bcs); + + /* Called by gigaset_bchannel_down() for resetting bcs->hw.xxx */ + void (*reinitbcshw)(struct bc_state *bcs); + + /* Called by gigaset_initcs() for setting up cs->hw.xxx */ + int (*initcshw)(struct cardstate *cs); + + /* Called by gigaset_freecs() for freeing cs->hw.xxx */ + void (*freecshw)(struct cardstate *cs); + + /* Called from common.c/interface.c for additional serial port + control */ + int (*set_modem_ctrl)(struct cardstate *cs, unsigned old_state, + unsigned new_state); + int (*baud_rate)(struct cardstate *cs, unsigned cflag); + int (*set_line_ctrl)(struct cardstate *cs, unsigned cflag); + + /* Called from LL interface to put an skb into the send-queue. + * After sending is completed, gigaset_skb_sent() must be called + * with the skb's link layer header preserved. */ + int (*send_skb)(struct bc_state *bcs, struct sk_buff *skb); + + /* Called from ev-layer.c to process a block of data + * received through the common/control channel. */ + void (*handle_input)(struct inbuf_t *inbuf); + +}; + +/* = Common structures and definitions ======================================= + */ + +/* Parser states for DLE-Event: + * <DLE-EVENT>: <DLE_FLAG> "X" <EVENT> <DLE_FLAG> "." + * <DLE_FLAG>: 0x10 + * <EVENT>: ((a-z)* | (A-Z)* | (0-10)*)+ + */ +#define DLE_FLAG 0x10 + +/* =========================================================================== + * Functions implemented in asyncdata.c + */ + +/* Called from LL interface to put an skb into the send queue. */ +int gigaset_m10x_send_skb(struct bc_state *bcs, struct sk_buff *skb); + +/* Called from ev-layer.c to process a block of data + * received through the common/control channel. */ +void gigaset_m10x_input(struct inbuf_t *inbuf); + +/* =========================================================================== + * Functions implemented in isocdata.c + */ + +/* Called from LL interface to put an skb into the send queue. */ +int gigaset_isoc_send_skb(struct bc_state *bcs, struct sk_buff *skb); + +/* Called from ev-layer.c to process a block of data + * received through the common/control channel. */ +void gigaset_isoc_input(struct inbuf_t *inbuf); + +/* Called from bas-gigaset.c to process a block of data + * received through the isochronous channel */ +void gigaset_isoc_receive(unsigned char *src, unsigned count, + struct bc_state *bcs); + +/* Called from bas-gigaset.c to put a block of data + * into the isochronous output buffer */ +int gigaset_isoc_buildframe(struct bc_state *bcs, unsigned char *in, int len); + +/* Called from bas-gigaset.c to initialize the isochronous output buffer */ +void gigaset_isowbuf_init(struct isowbuf_t *iwb, unsigned char idle); + +/* Called from bas-gigaset.c to retrieve a block of bytes for sending */ +int gigaset_isowbuf_getbytes(struct isowbuf_t *iwb, int size); + +/* =========================================================================== + * Functions implemented in LL interface + */ + +/* Called from common.c for setting up/shutting down with the ISDN subsystem */ +void gigaset_isdn_regdrv(void); +void gigaset_isdn_unregdrv(void); +int gigaset_isdn_regdev(struct cardstate *cs, const char *isdnid); +void gigaset_isdn_unregdev(struct cardstate *cs); + +/* Called from hardware module to indicate completion of an skb */ +void gigaset_skb_sent(struct bc_state *bcs, struct sk_buff *skb); +void gigaset_skb_rcvd(struct bc_state *bcs, struct sk_buff *skb); +void gigaset_isdn_rcv_err(struct bc_state *bcs); + +/* Called from common.c/ev-layer.c to indicate events relevant to the LL */ +void gigaset_isdn_start(struct cardstate *cs); +void gigaset_isdn_stop(struct cardstate *cs); +int gigaset_isdn_icall(struct at_state_t *at_state); +void gigaset_isdn_connD(struct bc_state *bcs); +void gigaset_isdn_hupD(struct bc_state *bcs); +void gigaset_isdn_connB(struct bc_state *bcs); +void gigaset_isdn_hupB(struct bc_state *bcs); + +/* =========================================================================== + * Functions implemented in ev-layer.c + */ + +/* tasklet called from common.c to process queued events */ +void gigaset_handle_event(unsigned long data); + +/* called from isocdata.c / asyncdata.c + * when a complete modem response line has been received */ +void gigaset_handle_modem_response(struct cardstate *cs); + +/* =========================================================================== + * Functions implemented in proc.c + */ + +/* initialize sysfs for device */ +void gigaset_init_dev_sysfs(struct cardstate *cs); +void gigaset_free_dev_sysfs(struct cardstate *cs); + +/* =========================================================================== + * Functions implemented in common.c/gigaset.h + */ + +void gigaset_bcs_reinit(struct bc_state *bcs); +void gigaset_at_init(struct at_state_t *at_state, struct bc_state *bcs, + struct cardstate *cs, int cid); +int gigaset_get_channel(struct bc_state *bcs); +struct bc_state *gigaset_get_free_channel(struct cardstate *cs); +void gigaset_free_channel(struct bc_state *bcs); +int gigaset_get_channels(struct cardstate *cs); +void gigaset_free_channels(struct cardstate *cs); +void gigaset_block_channels(struct cardstate *cs); + +/* Allocate and initialize driver structure. */ +struct gigaset_driver *gigaset_initdriver(unsigned minor, unsigned minors, + const char *procname, + const char *devname, + const struct gigaset_ops *ops, + struct module *owner); + +/* Deallocate driver structure. */ +void gigaset_freedriver(struct gigaset_driver *drv); + +struct cardstate *gigaset_get_cs_by_tty(struct tty_struct *tty); +struct cardstate *gigaset_get_cs_by_id(int id); +void gigaset_blockdriver(struct gigaset_driver *drv); + +/* Allocate and initialize card state. Calls hardware dependent + gigaset_init[b]cs(). */ +struct cardstate *gigaset_initcs(struct gigaset_driver *drv, int channels, + int onechannel, int ignoreframes, + int cidmode, const char *modulename); + +/* Free card state. Calls hardware dependent gigaset_free[b]cs(). */ +void gigaset_freecs(struct cardstate *cs); + +/* Tell common.c that hardware and driver are ready. */ +int gigaset_start(struct cardstate *cs); + +/* Tell common.c that the device is not present any more. */ +void gigaset_stop(struct cardstate *cs); + +/* Tell common.c that the driver is being unloaded. */ +int gigaset_shutdown(struct cardstate *cs); + +/* Append event to the queue. + * Returns NULL on failure or a pointer to the event on success. + * ptr must be kmalloc()ed (and not be freed by the caller). + */ +struct event_t *gigaset_add_event(struct cardstate *cs, + struct at_state_t *at_state, int type, + void *ptr, int parameter, void *arg); + +/* Called on CONFIG1 command from frontend. */ +int gigaset_enterconfigmode(struct cardstate *cs); + +/* cs->lock must not be locked */ +static inline void gigaset_schedule_event(struct cardstate *cs) +{ + unsigned long flags; + spin_lock_irqsave(&cs->lock, flags); + if (cs->running) + tasklet_schedule(&cs->event_tasklet); + spin_unlock_irqrestore(&cs->lock, flags); +} + +/* Tell common.c that B channel has been closed. */ +/* cs->lock must not be locked */ +static inline void gigaset_bchannel_down(struct bc_state *bcs) +{ + gigaset_add_event(bcs->cs, &bcs->at_state, EV_BC_CLOSED, NULL, 0, NULL); + gigaset_schedule_event(bcs->cs); +} + +/* Tell common.c that B channel has been opened. */ +/* cs->lock must not be locked */ +static inline void gigaset_bchannel_up(struct bc_state *bcs) +{ + gigaset_add_event(bcs->cs, &bcs->at_state, EV_BC_OPEN, NULL, 0, NULL); + gigaset_schedule_event(bcs->cs); +} + +/* set up next receive skb for data mode */ +static inline struct sk_buff *gigaset_new_rx_skb(struct bc_state *bcs) +{ + struct cardstate *cs = bcs->cs; + unsigned short hw_hdr_len = cs->hw_hdr_len; + + if (bcs->ignore) { + bcs->rx_skb = NULL; + } else { + bcs->rx_skb = dev_alloc_skb(bcs->rx_bufsize + hw_hdr_len); + if (bcs->rx_skb == NULL) + dev_warn(cs->dev, "could not allocate skb\n"); + else + skb_reserve(bcs->rx_skb, hw_hdr_len); + } + return bcs->rx_skb; +} + +/* append received bytes to inbuf */ +int gigaset_fill_inbuf(struct inbuf_t *inbuf, const unsigned char *src, + unsigned numbytes); + +/* =========================================================================== + * Functions implemented in interface.c + */ + +/* initialize interface */ +void gigaset_if_initdriver(struct gigaset_driver *drv, const char *procname, + const char *devname); +/* release interface */ +void gigaset_if_freedriver(struct gigaset_driver *drv); +/* add minor */ +void gigaset_if_init(struct cardstate *cs); +/* remove minor */ +void gigaset_if_free(struct cardstate *cs); +/* device received data */ +void gigaset_if_receive(struct cardstate *cs, + unsigned char *buffer, size_t len); + +#endif diff --git a/drivers/staging/isdn/gigaset/interface.c b/drivers/staging/isdn/gigaset/interface.c new file mode 100644 index 000000000000..d9a578ac32cd --- /dev/null +++ b/drivers/staging/isdn/gigaset/interface.c @@ -0,0 +1,616 @@ +/* + * interface to user space for the gigaset driver + * + * Copyright (c) 2004 by Hansjoerg Lipp <hjlipp@web.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. + * ===================================================================== + */ + +#include "gigaset.h" +#include <linux/gigaset_dev.h> +#include <linux/tty_flip.h> +#include <linux/module.h> + +/*** our ioctls ***/ + +static int if_lock(struct cardstate *cs, int *arg) +{ + int cmd = *arg; + + gig_dbg(DEBUG_IF, "%u: if_lock (%d)", cs->minor_index, cmd); + + if (cmd > 1) + return -EINVAL; + + if (cmd < 0) { + *arg = cs->mstate == MS_LOCKED; + return 0; + } + + if (!cmd && cs->mstate == MS_LOCKED && cs->connected) { + cs->ops->set_modem_ctrl(cs, 0, TIOCM_DTR | TIOCM_RTS); + cs->ops->baud_rate(cs, B115200); + cs->ops->set_line_ctrl(cs, CS8); + cs->control_state = TIOCM_DTR | TIOCM_RTS; + } + + cs->waiting = 1; + if (!gigaset_add_event(cs, &cs->at_state, EV_IF_LOCK, + NULL, cmd, NULL)) { + cs->waiting = 0; + return -ENOMEM; + } + gigaset_schedule_event(cs); + + wait_event(cs->waitqueue, !cs->waiting); + + if (cs->cmd_result >= 0) { + *arg = cs->cmd_result; + return 0; + } + + return cs->cmd_result; +} + +static int if_version(struct cardstate *cs, unsigned arg[4]) +{ + static const unsigned version[4] = GIG_VERSION; + static const unsigned compat[4] = GIG_COMPAT; + unsigned cmd = arg[0]; + + gig_dbg(DEBUG_IF, "%u: if_version (%d)", cs->minor_index, cmd); + + switch (cmd) { + case GIGVER_DRIVER: + memcpy(arg, version, sizeof version); + return 0; + case GIGVER_COMPAT: + memcpy(arg, compat, sizeof compat); + return 0; + case GIGVER_FWBASE: + cs->waiting = 1; + if (!gigaset_add_event(cs, &cs->at_state, EV_IF_VER, + NULL, 0, arg)) { + cs->waiting = 0; + return -ENOMEM; + } + gigaset_schedule_event(cs); + + wait_event(cs->waitqueue, !cs->waiting); + + if (cs->cmd_result >= 0) + return 0; + + return cs->cmd_result; + default: + return -EINVAL; + } +} + +static int if_config(struct cardstate *cs, int *arg) +{ + gig_dbg(DEBUG_IF, "%u: if_config (%d)", cs->minor_index, *arg); + + if (*arg != 1) + return -EINVAL; + + if (cs->mstate != MS_LOCKED) + return -EBUSY; + + if (!cs->connected) { + pr_err("%s: not connected\n", __func__); + return -ENODEV; + } + + *arg = 0; + return gigaset_enterconfigmode(cs); +} + +/*** the terminal driver ***/ + +static int if_open(struct tty_struct *tty, struct file *filp) +{ + struct cardstate *cs; + + gig_dbg(DEBUG_IF, "%d+%d: %s()", + tty->driver->minor_start, tty->index, __func__); + + cs = gigaset_get_cs_by_tty(tty); + if (!cs || !try_module_get(cs->driver->owner)) + return -ENODEV; + + if (mutex_lock_interruptible(&cs->mutex)) { + module_put(cs->driver->owner); + return -ERESTARTSYS; + } + tty->driver_data = cs; + + ++cs->port.count; + + if (cs->port.count == 1) { + tty_port_tty_set(&cs->port, tty); + cs->port.low_latency = 1; + } + + mutex_unlock(&cs->mutex); + return 0; +} + +static void if_close(struct tty_struct *tty, struct file *filp) +{ + struct cardstate *cs = tty->driver_data; + + if (!cs) { /* happens if we didn't find cs in open */ + gig_dbg(DEBUG_IF, "%s: no cardstate", __func__); + return; + } + + gig_dbg(DEBUG_IF, "%u: %s()", cs->minor_index, __func__); + + mutex_lock(&cs->mutex); + + if (!cs->connected) + gig_dbg(DEBUG_IF, "not connected"); /* nothing to do */ + else if (!cs->port.count) + dev_warn(cs->dev, "%s: device not opened\n", __func__); + else if (!--cs->port.count) + tty_port_tty_set(&cs->port, NULL); + + mutex_unlock(&cs->mutex); + + module_put(cs->driver->owner); +} + +static int if_ioctl(struct tty_struct *tty, + unsigned int cmd, unsigned long arg) +{ + struct cardstate *cs = tty->driver_data; + int retval = -ENODEV; + int int_arg; + unsigned char buf[6]; + unsigned version[4]; + + gig_dbg(DEBUG_IF, "%u: %s(0x%x)", cs->minor_index, __func__, cmd); + + if (mutex_lock_interruptible(&cs->mutex)) + return -ERESTARTSYS; + + if (!cs->connected) { + gig_dbg(DEBUG_IF, "not connected"); + retval = -ENODEV; + } else { + retval = 0; + switch (cmd) { + case GIGASET_REDIR: + retval = get_user(int_arg, (int __user *) arg); + if (retval >= 0) + retval = if_lock(cs, &int_arg); + if (retval >= 0) + retval = put_user(int_arg, (int __user *) arg); + break; + case GIGASET_CONFIG: + retval = get_user(int_arg, (int __user *) arg); + if (retval >= 0) + retval = if_config(cs, &int_arg); + if (retval >= 0) + retval = put_user(int_arg, (int __user *) arg); + break; + case GIGASET_BRKCHARS: + retval = copy_from_user(&buf, + (const unsigned char __user *) arg, 6) + ? -EFAULT : 0; + if (retval >= 0) { + gigaset_dbg_buffer(DEBUG_IF, "GIGASET_BRKCHARS", + 6, buf); + retval = cs->ops->brkchars(cs, buf); + } + break; + case GIGASET_VERSION: + retval = copy_from_user(version, + (unsigned __user *) arg, sizeof version) + ? -EFAULT : 0; + if (retval >= 0) + retval = if_version(cs, version); + if (retval >= 0) + retval = copy_to_user((unsigned __user *) arg, + version, sizeof version) + ? -EFAULT : 0; + break; + default: + gig_dbg(DEBUG_IF, "%s: arg not supported - 0x%04x", + __func__, cmd); + retval = -ENOIOCTLCMD; + } + } + + mutex_unlock(&cs->mutex); + + return retval; +} + +#ifdef CONFIG_COMPAT +static long if_compat_ioctl(struct tty_struct *tty, + unsigned int cmd, unsigned long arg) +{ + return if_ioctl(tty, cmd, (unsigned long)compat_ptr(arg)); +} +#endif + +static int if_tiocmget(struct tty_struct *tty) +{ + struct cardstate *cs = tty->driver_data; + int retval; + + gig_dbg(DEBUG_IF, "%u: %s()", cs->minor_index, __func__); + + if (mutex_lock_interruptible(&cs->mutex)) + return -ERESTARTSYS; + + retval = cs->control_state & (TIOCM_RTS | TIOCM_DTR); + + mutex_unlock(&cs->mutex); + + return retval; +} + +static int if_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct cardstate *cs = tty->driver_data; + int retval; + unsigned mc; + + gig_dbg(DEBUG_IF, "%u: %s(0x%x, 0x%x)", + cs->minor_index, __func__, set, clear); + + if (mutex_lock_interruptible(&cs->mutex)) + return -ERESTARTSYS; + + if (!cs->connected) { + gig_dbg(DEBUG_IF, "not connected"); + retval = -ENODEV; + } else { + mc = (cs->control_state | set) & ~clear & (TIOCM_RTS | TIOCM_DTR); + retval = cs->ops->set_modem_ctrl(cs, cs->control_state, mc); + cs->control_state = mc; + } + + mutex_unlock(&cs->mutex); + + return retval; +} + +static int if_write(struct tty_struct *tty, const unsigned char *buf, int count) +{ + struct cardstate *cs = tty->driver_data; + struct cmdbuf_t *cb; + int retval; + + gig_dbg(DEBUG_IF, "%u: %s()", cs->minor_index, __func__); + + if (mutex_lock_interruptible(&cs->mutex)) + return -ERESTARTSYS; + + if (!cs->connected) { + gig_dbg(DEBUG_IF, "not connected"); + retval = -ENODEV; + goto done; + } + if (cs->mstate != MS_LOCKED) { + dev_warn(cs->dev, "can't write to unlocked device\n"); + retval = -EBUSY; + goto done; + } + if (count <= 0) { + /* nothing to do */ + retval = 0; + goto done; + } + + cb = kmalloc(sizeof(struct cmdbuf_t) + count, GFP_KERNEL); + if (!cb) { + dev_err(cs->dev, "%s: out of memory\n", __func__); + retval = -ENOMEM; + goto done; + } + + memcpy(cb->buf, buf, count); + cb->len = count; + cb->offset = 0; + cb->next = NULL; + cb->wake_tasklet = &cs->if_wake_tasklet; + retval = cs->ops->write_cmd(cs, cb); +done: + mutex_unlock(&cs->mutex); + return retval; +} + +static int if_write_room(struct tty_struct *tty) +{ + struct cardstate *cs = tty->driver_data; + int retval; + + gig_dbg(DEBUG_IF, "%u: %s()", cs->minor_index, __func__); + + if (mutex_lock_interruptible(&cs->mutex)) + return -ERESTARTSYS; + + if (!cs->connected) { + gig_dbg(DEBUG_IF, "not connected"); + retval = -ENODEV; + } else if (cs->mstate != MS_LOCKED) { + dev_warn(cs->dev, "can't write to unlocked device\n"); + retval = -EBUSY; + } else + retval = cs->ops->write_room(cs); + + mutex_unlock(&cs->mutex); + + return retval; +} + +static int if_chars_in_buffer(struct tty_struct *tty) +{ + struct cardstate *cs = tty->driver_data; + int retval = 0; + + gig_dbg(DEBUG_IF, "%u: %s()", cs->minor_index, __func__); + + mutex_lock(&cs->mutex); + + if (!cs->connected) + gig_dbg(DEBUG_IF, "not connected"); + else if (cs->mstate != MS_LOCKED) + dev_warn(cs->dev, "can't write to unlocked device\n"); + else + retval = cs->ops->chars_in_buffer(cs); + + mutex_unlock(&cs->mutex); + + return retval; +} + +static void if_throttle(struct tty_struct *tty) +{ + struct cardstate *cs = tty->driver_data; + + gig_dbg(DEBUG_IF, "%u: %s()", cs->minor_index, __func__); + + mutex_lock(&cs->mutex); + + if (!cs->connected) + gig_dbg(DEBUG_IF, "not connected"); /* nothing to do */ + else + gig_dbg(DEBUG_IF, "%s: not implemented\n", __func__); + + mutex_unlock(&cs->mutex); +} + +static void if_unthrottle(struct tty_struct *tty) +{ + struct cardstate *cs = tty->driver_data; + + gig_dbg(DEBUG_IF, "%u: %s()", cs->minor_index, __func__); + + mutex_lock(&cs->mutex); + + if (!cs->connected) + gig_dbg(DEBUG_IF, "not connected"); /* nothing to do */ + else + gig_dbg(DEBUG_IF, "%s: not implemented\n", __func__); + + mutex_unlock(&cs->mutex); +} + +static void if_set_termios(struct tty_struct *tty, struct ktermios *old) +{ + struct cardstate *cs = tty->driver_data; + unsigned int iflag; + unsigned int cflag; + unsigned int old_cflag; + unsigned int control_state, new_state; + + gig_dbg(DEBUG_IF, "%u: %s()", cs->minor_index, __func__); + + mutex_lock(&cs->mutex); + + if (!cs->connected) { + gig_dbg(DEBUG_IF, "not connected"); + goto out; + } + + iflag = tty->termios.c_iflag; + cflag = tty->termios.c_cflag; + old_cflag = old ? old->c_cflag : cflag; + gig_dbg(DEBUG_IF, "%u: iflag %x cflag %x old %x", + cs->minor_index, iflag, cflag, old_cflag); + + /* get a local copy of the current port settings */ + control_state = cs->control_state; + + /* + * Update baud rate. + * Do not attempt to cache old rates and skip settings, + * disconnects screw such tricks up completely. + * Premature optimization is the root of all evil. + */ + + /* reassert DTR and (maybe) RTS on transition from B0 */ + if ((old_cflag & CBAUD) == B0) { + new_state = control_state | TIOCM_DTR; + /* don't set RTS if using hardware flow control */ + if (!(old_cflag & CRTSCTS)) + new_state |= TIOCM_RTS; + gig_dbg(DEBUG_IF, "%u: from B0 - set DTR%s", + cs->minor_index, + (new_state & TIOCM_RTS) ? " only" : "/RTS"); + cs->ops->set_modem_ctrl(cs, control_state, new_state); + control_state = new_state; + } + + cs->ops->baud_rate(cs, cflag & CBAUD); + + if ((cflag & CBAUD) == B0) { + /* Drop RTS and DTR */ + gig_dbg(DEBUG_IF, "%u: to B0 - drop DTR/RTS", cs->minor_index); + new_state = control_state & ~(TIOCM_DTR | TIOCM_RTS); + cs->ops->set_modem_ctrl(cs, control_state, new_state); + control_state = new_state; + } + + /* + * Update line control register (LCR) + */ + + cs->ops->set_line_ctrl(cs, cflag); + + /* save off the modified port settings */ + cs->control_state = control_state; + +out: + mutex_unlock(&cs->mutex); +} + +static const struct tty_operations if_ops = { + .open = if_open, + .close = if_close, + .ioctl = if_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = if_compat_ioctl, +#endif + .write = if_write, + .write_room = if_write_room, + .chars_in_buffer = if_chars_in_buffer, + .set_termios = if_set_termios, + .throttle = if_throttle, + .unthrottle = if_unthrottle, + .tiocmget = if_tiocmget, + .tiocmset = if_tiocmset, +}; + + +/* wakeup tasklet for the write operation */ +static void if_wake(unsigned long data) +{ + struct cardstate *cs = (struct cardstate *)data; + + tty_port_tty_wakeup(&cs->port); +} + +/*** interface to common ***/ + +void gigaset_if_init(struct cardstate *cs) +{ + struct gigaset_driver *drv; + + drv = cs->driver; + if (!drv->have_tty) + return; + + tasklet_init(&cs->if_wake_tasklet, if_wake, (unsigned long) cs); + + mutex_lock(&cs->mutex); + cs->tty_dev = tty_port_register_device(&cs->port, drv->tty, + cs->minor_index, NULL); + + if (!IS_ERR(cs->tty_dev)) + dev_set_drvdata(cs->tty_dev, cs); + else { + pr_warning("could not register device to the tty subsystem\n"); + cs->tty_dev = NULL; + } + mutex_unlock(&cs->mutex); +} + +void gigaset_if_free(struct cardstate *cs) +{ + struct gigaset_driver *drv; + + drv = cs->driver; + if (!drv->have_tty) + return; + + tasklet_disable(&cs->if_wake_tasklet); + tasklet_kill(&cs->if_wake_tasklet); + cs->tty_dev = NULL; + tty_unregister_device(drv->tty, cs->minor_index); +} + +/** + * gigaset_if_receive() - pass a received block of data to the tty device + * @cs: device descriptor structure. + * @buffer: received data. + * @len: number of bytes received. + * + * Called by asyncdata/isocdata if a block of data received from the + * device must be sent to userspace through the ttyG* device. + */ +void gigaset_if_receive(struct cardstate *cs, + unsigned char *buffer, size_t len) +{ + tty_insert_flip_string(&cs->port, buffer, len); + tty_flip_buffer_push(&cs->port); +} +EXPORT_SYMBOL_GPL(gigaset_if_receive); + +/* gigaset_if_initdriver + * Initialize tty interface. + * parameters: + * drv Driver + * procname Name of the driver (e.g. for /proc/tty/drivers) + * devname Name of the device files (prefix without minor number) + */ +void gigaset_if_initdriver(struct gigaset_driver *drv, const char *procname, + const char *devname) +{ + int ret; + struct tty_driver *tty; + + drv->have_tty = 0; + + drv->tty = tty = alloc_tty_driver(drv->minors); + if (tty == NULL) + goto enomem; + + tty->type = TTY_DRIVER_TYPE_SERIAL; + tty->subtype = SERIAL_TYPE_NORMAL; + tty->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; + + tty->driver_name = procname; + tty->name = devname; + tty->minor_start = drv->minor; + + tty->init_termios = tty_std_termios; + tty->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; + tty_set_operations(tty, &if_ops); + + ret = tty_register_driver(tty); + if (ret < 0) { + pr_err("error %d registering tty driver\n", ret); + goto error; + } + gig_dbg(DEBUG_IF, "tty driver initialized"); + drv->have_tty = 1; + return; + +enomem: + pr_err("out of memory\n"); +error: + if (drv->tty) + put_tty_driver(drv->tty); +} + +void gigaset_if_freedriver(struct gigaset_driver *drv) +{ + if (!drv->have_tty) + return; + + drv->have_tty = 0; + tty_unregister_driver(drv->tty); + put_tty_driver(drv->tty); +} diff --git a/drivers/staging/isdn/gigaset/isocdata.c b/drivers/staging/isdn/gigaset/isocdata.c new file mode 100644 index 000000000000..f9264ba0fe77 --- /dev/null +++ b/drivers/staging/isdn/gigaset/isocdata.c @@ -0,0 +1,1009 @@ +/* + * Common data handling layer for bas_gigaset + * + * Copyright (c) 2005 by Tilman Schmidt <tilman@imap.cc>, + * Hansjoerg Lipp <hjlipp@web.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. + * ===================================================================== + */ + +#include "gigaset.h" +#include <linux/crc-ccitt.h> +#include <linux/bitrev.h> + +/* access methods for isowbuf_t */ +/* ============================ */ + +/* initialize buffer structure + */ +void gigaset_isowbuf_init(struct isowbuf_t *iwb, unsigned char idle) +{ + iwb->read = 0; + iwb->nextread = 0; + iwb->write = 0; + atomic_set(&iwb->writesem, 1); + iwb->wbits = 0; + iwb->idle = idle; + memset(iwb->data + BAS_OUTBUFSIZE, idle, BAS_OUTBUFPAD); +} + +/* compute number of bytes which can be appended to buffer + * so that there is still room to append a maximum frame of flags + */ +static inline int isowbuf_freebytes(struct isowbuf_t *iwb) +{ + int read, write, freebytes; + + read = iwb->read; + write = iwb->write; + freebytes = read - write; + if (freebytes > 0) { + /* no wraparound: need padding space within regular area */ + return freebytes - BAS_OUTBUFPAD; + } else if (read < BAS_OUTBUFPAD) { + /* wraparound: can use space up to end of regular area */ + return BAS_OUTBUFSIZE - write; + } else { + /* following the wraparound yields more space */ + return freebytes + BAS_OUTBUFSIZE - BAS_OUTBUFPAD; + } +} + +/* start writing + * acquire the write semaphore + * return 0 if acquired, <0 if busy + */ +static inline int isowbuf_startwrite(struct isowbuf_t *iwb) +{ + if (!atomic_dec_and_test(&iwb->writesem)) { + atomic_inc(&iwb->writesem); + gig_dbg(DEBUG_ISO, "%s: couldn't acquire iso write semaphore", + __func__); + return -EBUSY; + } + gig_dbg(DEBUG_ISO, + "%s: acquired iso write semaphore, data[write]=%02x, nbits=%d", + __func__, iwb->data[iwb->write], iwb->wbits); + return 0; +} + +/* finish writing + * release the write semaphore + * returns the current write position + */ +static inline int isowbuf_donewrite(struct isowbuf_t *iwb) +{ + int write = iwb->write; + atomic_inc(&iwb->writesem); + return write; +} + +/* append bits to buffer without any checks + * - data contains bits to append, starting at LSB + * - nbits is number of bits to append (0..24) + * must be called with the write semaphore held + * If more than nbits bits are set in data, the extraneous bits are set in the + * buffer too, but the write position is only advanced by nbits. + */ +static inline void isowbuf_putbits(struct isowbuf_t *iwb, u32 data, int nbits) +{ + int write = iwb->write; + data <<= iwb->wbits; + data |= iwb->data[write]; + nbits += iwb->wbits; + while (nbits >= 8) { + iwb->data[write++] = data & 0xff; + write %= BAS_OUTBUFSIZE; + data >>= 8; + nbits -= 8; + } + iwb->wbits = nbits; + iwb->data[write] = data & 0xff; + iwb->write = write; +} + +/* put final flag on HDLC bitstream + * also sets the idle fill byte to the correspondingly shifted flag pattern + * must be called with the write semaphore held + */ +static inline void isowbuf_putflag(struct isowbuf_t *iwb) +{ + int write; + + /* add two flags, thus reliably covering one byte */ + isowbuf_putbits(iwb, 0x7e7e, 8); + /* recover the idle flag byte */ + write = iwb->write; + iwb->idle = iwb->data[write]; + gig_dbg(DEBUG_ISO, "idle fill byte %02x", iwb->idle); + /* mask extraneous bits in buffer */ + iwb->data[write] &= (1 << iwb->wbits) - 1; +} + +/* retrieve a block of bytes for sending + * The requested number of bytes is provided as a contiguous block. + * If necessary, the frame is filled to the requested number of bytes + * with the idle value. + * returns offset to frame, < 0 on busy or error + */ +int gigaset_isowbuf_getbytes(struct isowbuf_t *iwb, int size) +{ + int read, write, limit, src, dst; + unsigned char pbyte; + + read = iwb->nextread; + write = iwb->write; + if (likely(read == write)) { + /* return idle frame */ + return read < BAS_OUTBUFPAD ? + BAS_OUTBUFSIZE : read - BAS_OUTBUFPAD; + } + + limit = read + size; + gig_dbg(DEBUG_STREAM, "%s: read=%d write=%d limit=%d", + __func__, read, write, limit); +#ifdef CONFIG_GIGASET_DEBUG + if (unlikely(size < 0 || size > BAS_OUTBUFPAD)) { + pr_err("invalid size %d\n", size); + return -EINVAL; + } +#endif + + if (read < write) { + /* no wraparound in valid data */ + if (limit >= write) { + /* append idle frame */ + if (isowbuf_startwrite(iwb) < 0) + return -EBUSY; + /* write position could have changed */ + write = iwb->write; + if (limit >= write) { + pbyte = iwb->data[write]; /* save + partial byte */ + limit = write + BAS_OUTBUFPAD; + gig_dbg(DEBUG_STREAM, + "%s: filling %d->%d with %02x", + __func__, write, limit, iwb->idle); + if (write + BAS_OUTBUFPAD < BAS_OUTBUFSIZE) + memset(iwb->data + write, iwb->idle, + BAS_OUTBUFPAD); + else { + /* wraparound, fill entire pad area */ + memset(iwb->data + write, iwb->idle, + BAS_OUTBUFSIZE + BAS_OUTBUFPAD + - write); + limit = 0; + } + gig_dbg(DEBUG_STREAM, + "%s: restoring %02x at %d", + __func__, pbyte, limit); + iwb->data[limit] = pbyte; /* restore + partial byte */ + iwb->write = limit; + } + isowbuf_donewrite(iwb); + } + } else { + /* valid data wraparound */ + if (limit >= BAS_OUTBUFSIZE) { + /* copy wrapped part into pad area */ + src = 0; + dst = BAS_OUTBUFSIZE; + while (dst < limit && src < write) + iwb->data[dst++] = iwb->data[src++]; + if (dst <= limit) { + /* fill pad area with idle byte */ + memset(iwb->data + dst, iwb->idle, + BAS_OUTBUFSIZE + BAS_OUTBUFPAD - dst); + } + limit = src; + } + } + iwb->nextread = limit; + return read; +} + +/* dump_bytes + * write hex bytes to syslog for debugging + */ +static inline void dump_bytes(enum debuglevel level, const char *tag, + unsigned char *bytes, int count) +{ +#ifdef CONFIG_GIGASET_DEBUG + unsigned char c; + static char dbgline[3 * 32 + 1]; + int i = 0; + + if (!(gigaset_debuglevel & level)) + return; + + while (count-- > 0) { + if (i > sizeof(dbgline) - 4) { + dbgline[i] = '\0'; + gig_dbg(level, "%s:%s", tag, dbgline); + i = 0; + } + c = *bytes++; + dbgline[i] = (i && !(i % 12)) ? '-' : ' '; + i++; + dbgline[i++] = hex_asc_hi(c); + dbgline[i++] = hex_asc_lo(c); + } + dbgline[i] = '\0'; + gig_dbg(level, "%s:%s", tag, dbgline); +#endif +} + +/*============================================================================*/ + +/* bytewise HDLC bitstuffing via table lookup + * lookup table: 5 subtables for 0..4 preceding consecutive '1' bits + * index: 256*(number of preceding '1' bits) + (next byte to stuff) + * value: bit 9.. 0 = result bits + * bit 12..10 = number of trailing '1' bits in result + * bit 14..13 = number of bits added by stuffing + */ +static const u16 stufftab[5 * 256] = { +/* previous 1s = 0: */ + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x201f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x203e, 0x205f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x209f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x207c, 0x207d, 0x20be, 0x20df, + 0x0480, 0x0481, 0x0482, 0x0483, 0x0484, 0x0485, 0x0486, 0x0487, 0x0488, 0x0489, 0x048a, 0x048b, 0x048c, 0x048d, 0x048e, 0x048f, + 0x0490, 0x0491, 0x0492, 0x0493, 0x0494, 0x0495, 0x0496, 0x0497, 0x0498, 0x0499, 0x049a, 0x049b, 0x049c, 0x049d, 0x049e, 0x251f, + 0x04a0, 0x04a1, 0x04a2, 0x04a3, 0x04a4, 0x04a5, 0x04a6, 0x04a7, 0x04a8, 0x04a9, 0x04aa, 0x04ab, 0x04ac, 0x04ad, 0x04ae, 0x04af, + 0x04b0, 0x04b1, 0x04b2, 0x04b3, 0x04b4, 0x04b5, 0x04b6, 0x04b7, 0x04b8, 0x04b9, 0x04ba, 0x04bb, 0x04bc, 0x04bd, 0x253e, 0x255f, + 0x08c0, 0x08c1, 0x08c2, 0x08c3, 0x08c4, 0x08c5, 0x08c6, 0x08c7, 0x08c8, 0x08c9, 0x08ca, 0x08cb, 0x08cc, 0x08cd, 0x08ce, 0x08cf, + 0x08d0, 0x08d1, 0x08d2, 0x08d3, 0x08d4, 0x08d5, 0x08d6, 0x08d7, 0x08d8, 0x08d9, 0x08da, 0x08db, 0x08dc, 0x08dd, 0x08de, 0x299f, + 0x0ce0, 0x0ce1, 0x0ce2, 0x0ce3, 0x0ce4, 0x0ce5, 0x0ce6, 0x0ce7, 0x0ce8, 0x0ce9, 0x0cea, 0x0ceb, 0x0cec, 0x0ced, 0x0cee, 0x0cef, + 0x10f0, 0x10f1, 0x10f2, 0x10f3, 0x10f4, 0x10f5, 0x10f6, 0x10f7, 0x20f8, 0x20f9, 0x20fa, 0x20fb, 0x257c, 0x257d, 0x29be, 0x2ddf, + +/* previous 1s = 1: */ + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x200f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x202f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x204f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x203e, 0x206f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x208f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x20af, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x20cf, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x207c, 0x207d, 0x20be, 0x20ef, + 0x0480, 0x0481, 0x0482, 0x0483, 0x0484, 0x0485, 0x0486, 0x0487, 0x0488, 0x0489, 0x048a, 0x048b, 0x048c, 0x048d, 0x048e, 0x250f, + 0x0490, 0x0491, 0x0492, 0x0493, 0x0494, 0x0495, 0x0496, 0x0497, 0x0498, 0x0499, 0x049a, 0x049b, 0x049c, 0x049d, 0x049e, 0x252f, + 0x04a0, 0x04a1, 0x04a2, 0x04a3, 0x04a4, 0x04a5, 0x04a6, 0x04a7, 0x04a8, 0x04a9, 0x04aa, 0x04ab, 0x04ac, 0x04ad, 0x04ae, 0x254f, + 0x04b0, 0x04b1, 0x04b2, 0x04b3, 0x04b4, 0x04b5, 0x04b6, 0x04b7, 0x04b8, 0x04b9, 0x04ba, 0x04bb, 0x04bc, 0x04bd, 0x253e, 0x256f, + 0x08c0, 0x08c1, 0x08c2, 0x08c3, 0x08c4, 0x08c5, 0x08c6, 0x08c7, 0x08c8, 0x08c9, 0x08ca, 0x08cb, 0x08cc, 0x08cd, 0x08ce, 0x298f, + 0x08d0, 0x08d1, 0x08d2, 0x08d3, 0x08d4, 0x08d5, 0x08d6, 0x08d7, 0x08d8, 0x08d9, 0x08da, 0x08db, 0x08dc, 0x08dd, 0x08de, 0x29af, + 0x0ce0, 0x0ce1, 0x0ce2, 0x0ce3, 0x0ce4, 0x0ce5, 0x0ce6, 0x0ce7, 0x0ce8, 0x0ce9, 0x0cea, 0x0ceb, 0x0cec, 0x0ced, 0x0cee, 0x2dcf, + 0x10f0, 0x10f1, 0x10f2, 0x10f3, 0x10f4, 0x10f5, 0x10f6, 0x10f7, 0x20f8, 0x20f9, 0x20fa, 0x20fb, 0x257c, 0x257d, 0x29be, 0x31ef, + +/* previous 1s = 2: */ + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x2007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x2017, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x2027, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x2037, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x2047, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x2057, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x2067, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x203e, 0x2077, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x2087, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x2097, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x20a7, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x20b7, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x20c7, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x20d7, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x20e7, 0x0078, 0x0079, 0x007a, 0x007b, 0x207c, 0x207d, 0x20be, 0x20f7, + 0x0480, 0x0481, 0x0482, 0x0483, 0x0484, 0x0485, 0x0486, 0x2507, 0x0488, 0x0489, 0x048a, 0x048b, 0x048c, 0x048d, 0x048e, 0x2517, + 0x0490, 0x0491, 0x0492, 0x0493, 0x0494, 0x0495, 0x0496, 0x2527, 0x0498, 0x0499, 0x049a, 0x049b, 0x049c, 0x049d, 0x049e, 0x2537, + 0x04a0, 0x04a1, 0x04a2, 0x04a3, 0x04a4, 0x04a5, 0x04a6, 0x2547, 0x04a8, 0x04a9, 0x04aa, 0x04ab, 0x04ac, 0x04ad, 0x04ae, 0x2557, + 0x04b0, 0x04b1, 0x04b2, 0x04b3, 0x04b4, 0x04b5, 0x04b6, 0x2567, 0x04b8, 0x04b9, 0x04ba, 0x04bb, 0x04bc, 0x04bd, 0x253e, 0x2577, + 0x08c0, 0x08c1, 0x08c2, 0x08c3, 0x08c4, 0x08c5, 0x08c6, 0x2987, 0x08c8, 0x08c9, 0x08ca, 0x08cb, 0x08cc, 0x08cd, 0x08ce, 0x2997, + 0x08d0, 0x08d1, 0x08d2, 0x08d3, 0x08d4, 0x08d5, 0x08d6, 0x29a7, 0x08d8, 0x08d9, 0x08da, 0x08db, 0x08dc, 0x08dd, 0x08de, 0x29b7, + 0x0ce0, 0x0ce1, 0x0ce2, 0x0ce3, 0x0ce4, 0x0ce5, 0x0ce6, 0x2dc7, 0x0ce8, 0x0ce9, 0x0cea, 0x0ceb, 0x0cec, 0x0ced, 0x0cee, 0x2dd7, + 0x10f0, 0x10f1, 0x10f2, 0x10f3, 0x10f4, 0x10f5, 0x10f6, 0x31e7, 0x20f8, 0x20f9, 0x20fa, 0x20fb, 0x257c, 0x257d, 0x29be, 0x41f7, + +/* previous 1s = 3: */ + 0x0000, 0x0001, 0x0002, 0x2003, 0x0004, 0x0005, 0x0006, 0x200b, 0x0008, 0x0009, 0x000a, 0x2013, 0x000c, 0x000d, 0x000e, 0x201b, + 0x0010, 0x0011, 0x0012, 0x2023, 0x0014, 0x0015, 0x0016, 0x202b, 0x0018, 0x0019, 0x001a, 0x2033, 0x001c, 0x001d, 0x001e, 0x203b, + 0x0020, 0x0021, 0x0022, 0x2043, 0x0024, 0x0025, 0x0026, 0x204b, 0x0028, 0x0029, 0x002a, 0x2053, 0x002c, 0x002d, 0x002e, 0x205b, + 0x0030, 0x0031, 0x0032, 0x2063, 0x0034, 0x0035, 0x0036, 0x206b, 0x0038, 0x0039, 0x003a, 0x2073, 0x003c, 0x003d, 0x203e, 0x207b, + 0x0040, 0x0041, 0x0042, 0x2083, 0x0044, 0x0045, 0x0046, 0x208b, 0x0048, 0x0049, 0x004a, 0x2093, 0x004c, 0x004d, 0x004e, 0x209b, + 0x0050, 0x0051, 0x0052, 0x20a3, 0x0054, 0x0055, 0x0056, 0x20ab, 0x0058, 0x0059, 0x005a, 0x20b3, 0x005c, 0x005d, 0x005e, 0x20bb, + 0x0060, 0x0061, 0x0062, 0x20c3, 0x0064, 0x0065, 0x0066, 0x20cb, 0x0068, 0x0069, 0x006a, 0x20d3, 0x006c, 0x006d, 0x006e, 0x20db, + 0x0070, 0x0071, 0x0072, 0x20e3, 0x0074, 0x0075, 0x0076, 0x20eb, 0x0078, 0x0079, 0x007a, 0x20f3, 0x207c, 0x207d, 0x20be, 0x40fb, + 0x0480, 0x0481, 0x0482, 0x2503, 0x0484, 0x0485, 0x0486, 0x250b, 0x0488, 0x0489, 0x048a, 0x2513, 0x048c, 0x048d, 0x048e, 0x251b, + 0x0490, 0x0491, 0x0492, 0x2523, 0x0494, 0x0495, 0x0496, 0x252b, 0x0498, 0x0499, 0x049a, 0x2533, 0x049c, 0x049d, 0x049e, 0x253b, + 0x04a0, 0x04a1, 0x04a2, 0x2543, 0x04a4, 0x04a5, 0x04a6, 0x254b, 0x04a8, 0x04a9, 0x04aa, 0x2553, 0x04ac, 0x04ad, 0x04ae, 0x255b, + 0x04b0, 0x04b1, 0x04b2, 0x2563, 0x04b4, 0x04b5, 0x04b6, 0x256b, 0x04b8, 0x04b9, 0x04ba, 0x2573, 0x04bc, 0x04bd, 0x253e, 0x257b, + 0x08c0, 0x08c1, 0x08c2, 0x2983, 0x08c4, 0x08c5, 0x08c6, 0x298b, 0x08c8, 0x08c9, 0x08ca, 0x2993, 0x08cc, 0x08cd, 0x08ce, 0x299b, + 0x08d0, 0x08d1, 0x08d2, 0x29a3, 0x08d4, 0x08d5, 0x08d6, 0x29ab, 0x08d8, 0x08d9, 0x08da, 0x29b3, 0x08dc, 0x08dd, 0x08de, 0x29bb, + 0x0ce0, 0x0ce1, 0x0ce2, 0x2dc3, 0x0ce4, 0x0ce5, 0x0ce6, 0x2dcb, 0x0ce8, 0x0ce9, 0x0cea, 0x2dd3, 0x0cec, 0x0ced, 0x0cee, 0x2ddb, + 0x10f0, 0x10f1, 0x10f2, 0x31e3, 0x10f4, 0x10f5, 0x10f6, 0x31eb, 0x20f8, 0x20f9, 0x20fa, 0x41f3, 0x257c, 0x257d, 0x29be, 0x46fb, + +/* previous 1s = 4: */ + 0x0000, 0x2001, 0x0002, 0x2005, 0x0004, 0x2009, 0x0006, 0x200d, 0x0008, 0x2011, 0x000a, 0x2015, 0x000c, 0x2019, 0x000e, 0x201d, + 0x0010, 0x2021, 0x0012, 0x2025, 0x0014, 0x2029, 0x0016, 0x202d, 0x0018, 0x2031, 0x001a, 0x2035, 0x001c, 0x2039, 0x001e, 0x203d, + 0x0020, 0x2041, 0x0022, 0x2045, 0x0024, 0x2049, 0x0026, 0x204d, 0x0028, 0x2051, 0x002a, 0x2055, 0x002c, 0x2059, 0x002e, 0x205d, + 0x0030, 0x2061, 0x0032, 0x2065, 0x0034, 0x2069, 0x0036, 0x206d, 0x0038, 0x2071, 0x003a, 0x2075, 0x003c, 0x2079, 0x203e, 0x407d, + 0x0040, 0x2081, 0x0042, 0x2085, 0x0044, 0x2089, 0x0046, 0x208d, 0x0048, 0x2091, 0x004a, 0x2095, 0x004c, 0x2099, 0x004e, 0x209d, + 0x0050, 0x20a1, 0x0052, 0x20a5, 0x0054, 0x20a9, 0x0056, 0x20ad, 0x0058, 0x20b1, 0x005a, 0x20b5, 0x005c, 0x20b9, 0x005e, 0x20bd, + 0x0060, 0x20c1, 0x0062, 0x20c5, 0x0064, 0x20c9, 0x0066, 0x20cd, 0x0068, 0x20d1, 0x006a, 0x20d5, 0x006c, 0x20d9, 0x006e, 0x20dd, + 0x0070, 0x20e1, 0x0072, 0x20e5, 0x0074, 0x20e9, 0x0076, 0x20ed, 0x0078, 0x20f1, 0x007a, 0x20f5, 0x207c, 0x40f9, 0x20be, 0x417d, + 0x0480, 0x2501, 0x0482, 0x2505, 0x0484, 0x2509, 0x0486, 0x250d, 0x0488, 0x2511, 0x048a, 0x2515, 0x048c, 0x2519, 0x048e, 0x251d, + 0x0490, 0x2521, 0x0492, 0x2525, 0x0494, 0x2529, 0x0496, 0x252d, 0x0498, 0x2531, 0x049a, 0x2535, 0x049c, 0x2539, 0x049e, 0x253d, + 0x04a0, 0x2541, 0x04a2, 0x2545, 0x04a4, 0x2549, 0x04a6, 0x254d, 0x04a8, 0x2551, 0x04aa, 0x2555, 0x04ac, 0x2559, 0x04ae, 0x255d, + 0x04b0, 0x2561, 0x04b2, 0x2565, 0x04b4, 0x2569, 0x04b6, 0x256d, 0x04b8, 0x2571, 0x04ba, 0x2575, 0x04bc, 0x2579, 0x253e, 0x467d, + 0x08c0, 0x2981, 0x08c2, 0x2985, 0x08c4, 0x2989, 0x08c6, 0x298d, 0x08c8, 0x2991, 0x08ca, 0x2995, 0x08cc, 0x2999, 0x08ce, 0x299d, + 0x08d0, 0x29a1, 0x08d2, 0x29a5, 0x08d4, 0x29a9, 0x08d6, 0x29ad, 0x08d8, 0x29b1, 0x08da, 0x29b5, 0x08dc, 0x29b9, 0x08de, 0x29bd, + 0x0ce0, 0x2dc1, 0x0ce2, 0x2dc5, 0x0ce4, 0x2dc9, 0x0ce6, 0x2dcd, 0x0ce8, 0x2dd1, 0x0cea, 0x2dd5, 0x0cec, 0x2dd9, 0x0cee, 0x2ddd, + 0x10f0, 0x31e1, 0x10f2, 0x31e5, 0x10f4, 0x31e9, 0x10f6, 0x31ed, 0x20f8, 0x41f1, 0x20fa, 0x41f5, 0x257c, 0x46f9, 0x29be, 0x4b7d +}; + +/* hdlc_bitstuff_byte + * perform HDLC bitstuffing for one input byte (8 bits, LSB first) + * parameters: + * cin input byte + * ones number of trailing '1' bits in result before this step + * iwb pointer to output buffer structure + * (write semaphore must be held) + * return value: + * number of trailing '1' bits in result after this step + */ + +static inline int hdlc_bitstuff_byte(struct isowbuf_t *iwb, unsigned char cin, + int ones) +{ + u16 stuff; + int shiftinc, newones; + + /* get stuffing information for input byte + * value: bit 9.. 0 = result bits + * bit 12..10 = number of trailing '1' bits in result + * bit 14..13 = number of bits added by stuffing + */ + stuff = stufftab[256 * ones + cin]; + shiftinc = (stuff >> 13) & 3; + newones = (stuff >> 10) & 7; + stuff &= 0x3ff; + + /* append stuffed byte to output stream */ + isowbuf_putbits(iwb, stuff, 8 + shiftinc); + return newones; +} + +/* hdlc_buildframe + * Perform HDLC framing with bitstuffing on a byte buffer + * The input buffer is regarded as a sequence of bits, starting with the least + * significant bit of the first byte and ending with the most significant bit + * of the last byte. A 16 bit FCS is appended as defined by RFC 1662. + * Whenever five consecutive '1' bits appear in the resulting bit sequence, a + * '0' bit is inserted after them. + * The resulting bit string and a closing flag pattern (PPP_FLAG, '01111110') + * are appended to the output buffer starting at the given bit position, which + * is assumed to already contain a leading flag. + * The output buffer must have sufficient length; count + count/5 + 6 bytes + * starting at *out are safe and are verified to be present. + * parameters: + * in input buffer + * count number of bytes in input buffer + * iwb pointer to output buffer structure + * (write semaphore must be held) + * return value: + * position of end of packet in output buffer on success, + * -EAGAIN if write semaphore busy or buffer full + */ + +static inline int hdlc_buildframe(struct isowbuf_t *iwb, + unsigned char *in, int count) +{ + int ones; + u16 fcs; + int end; + unsigned char c; + + if (isowbuf_freebytes(iwb) < count + count / 5 + 6 || + isowbuf_startwrite(iwb) < 0) { + gig_dbg(DEBUG_ISO, "%s: %d bytes free -> -EAGAIN", + __func__, isowbuf_freebytes(iwb)); + return -EAGAIN; + } + + dump_bytes(DEBUG_STREAM_DUMP, "snd data", in, count); + + /* bitstuff and checksum input data */ + fcs = PPP_INITFCS; + ones = 0; + while (count-- > 0) { + c = *in++; + ones = hdlc_bitstuff_byte(iwb, c, ones); + fcs = crc_ccitt_byte(fcs, c); + } + + /* bitstuff and append FCS + * (complemented, least significant byte first) */ + fcs ^= 0xffff; + ones = hdlc_bitstuff_byte(iwb, fcs & 0x00ff, ones); + ones = hdlc_bitstuff_byte(iwb, (fcs >> 8) & 0x00ff, ones); + + /* put closing flag and repeat byte for flag idle */ + isowbuf_putflag(iwb); + end = isowbuf_donewrite(iwb); + return end; +} + +/* trans_buildframe + * Append a block of 'transparent' data to the output buffer, + * inverting the bytes. + * The output buffer must have sufficient length; count bytes + * starting at *out are safe and are verified to be present. + * parameters: + * in input buffer + * count number of bytes in input buffer + * iwb pointer to output buffer structure + * (write semaphore must be held) + * return value: + * position of end of packet in output buffer on success, + * -EAGAIN if write semaphore busy or buffer full + */ + +static inline int trans_buildframe(struct isowbuf_t *iwb, + unsigned char *in, int count) +{ + int write; + unsigned char c; + + if (unlikely(count <= 0)) + return iwb->write; + + if (isowbuf_freebytes(iwb) < count || + isowbuf_startwrite(iwb) < 0) { + gig_dbg(DEBUG_ISO, "can't put %d bytes", count); + return -EAGAIN; + } + + gig_dbg(DEBUG_STREAM, "put %d bytes", count); + dump_bytes(DEBUG_STREAM_DUMP, "snd data", in, count); + + write = iwb->write; + do { + c = bitrev8(*in++); + iwb->data[write++] = c; + write %= BAS_OUTBUFSIZE; + } while (--count > 0); + iwb->write = write; + iwb->idle = c; + + return isowbuf_donewrite(iwb); +} + +int gigaset_isoc_buildframe(struct bc_state *bcs, unsigned char *in, int len) +{ + int result; + + switch (bcs->proto2) { + case L2_HDLC: + result = hdlc_buildframe(bcs->hw.bas->isooutbuf, in, len); + gig_dbg(DEBUG_ISO, "%s: %d bytes HDLC -> %d", + __func__, len, result); + break; + default: /* assume transparent */ + result = trans_buildframe(bcs->hw.bas->isooutbuf, in, len); + gig_dbg(DEBUG_ISO, "%s: %d bytes trans -> %d", + __func__, len, result); + } + return result; +} + +/* hdlc_putbyte + * append byte c to current skb of B channel structure *bcs, updating fcs + */ +static inline void hdlc_putbyte(unsigned char c, struct bc_state *bcs) +{ + bcs->rx_fcs = crc_ccitt_byte(bcs->rx_fcs, c); + if (bcs->rx_skb == NULL) + /* skipping */ + return; + if (bcs->rx_skb->len >= bcs->rx_bufsize) { + dev_warn(bcs->cs->dev, "received oversized packet discarded\n"); + bcs->hw.bas->giants++; + dev_kfree_skb_any(bcs->rx_skb); + bcs->rx_skb = NULL; + return; + } + __skb_put_u8(bcs->rx_skb, c); +} + +/* hdlc_flush + * drop partial HDLC data packet + */ +static inline void hdlc_flush(struct bc_state *bcs) +{ + /* clear skb or allocate new if not skipping */ + if (bcs->rx_skb != NULL) + skb_trim(bcs->rx_skb, 0); + else + gigaset_new_rx_skb(bcs); + + /* reset packet state */ + bcs->rx_fcs = PPP_INITFCS; +} + +/* hdlc_done + * process completed HDLC data packet + */ +static inline void hdlc_done(struct bc_state *bcs) +{ + struct cardstate *cs = bcs->cs; + struct sk_buff *procskb; + unsigned int len; + + if (unlikely(bcs->ignore)) { + bcs->ignore--; + hdlc_flush(bcs); + return; + } + procskb = bcs->rx_skb; + if (procskb == NULL) { + /* previous error */ + gig_dbg(DEBUG_ISO, "%s: skb=NULL", __func__); + gigaset_isdn_rcv_err(bcs); + } else if (procskb->len < 2) { + dev_notice(cs->dev, "received short frame (%d octets)\n", + procskb->len); + bcs->hw.bas->runts++; + dev_kfree_skb_any(procskb); + gigaset_isdn_rcv_err(bcs); + } else if (bcs->rx_fcs != PPP_GOODFCS) { + dev_notice(cs->dev, "frame check error\n"); + bcs->hw.bas->fcserrs++; + dev_kfree_skb_any(procskb); + gigaset_isdn_rcv_err(bcs); + } else { + len = procskb->len; + __skb_trim(procskb, len -= 2); /* subtract FCS */ + gig_dbg(DEBUG_ISO, "%s: good frame (%d octets)", __func__, len); + dump_bytes(DEBUG_STREAM_DUMP, + "rcv data", procskb->data, len); + bcs->hw.bas->goodbytes += len; + gigaset_skb_rcvd(bcs, procskb); + } + gigaset_new_rx_skb(bcs); + bcs->rx_fcs = PPP_INITFCS; +} + +/* hdlc_frag + * drop HDLC data packet with non-integral last byte + */ +static inline void hdlc_frag(struct bc_state *bcs, unsigned inbits) +{ + if (unlikely(bcs->ignore)) { + bcs->ignore--; + hdlc_flush(bcs); + return; + } + + dev_notice(bcs->cs->dev, "received partial byte (%d bits)\n", inbits); + bcs->hw.bas->alignerrs++; + gigaset_isdn_rcv_err(bcs); + __skb_trim(bcs->rx_skb, 0); + bcs->rx_fcs = PPP_INITFCS; +} + +/* bit counts lookup table for HDLC bit unstuffing + * index: input byte + * value: bit 0..3 = number of consecutive '1' bits starting from LSB + * bit 4..6 = number of consecutive '1' bits starting from MSB + * (replacing 8 by 7 to make it fit; the algorithm won't care) + * bit 7 set if there are 5 or more "interior" consecutive '1' bits + */ +static const unsigned char bitcounts[256] = { + 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x04, + 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x05, + 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x04, + 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x80, 0x06, + 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x04, + 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x05, + 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x04, + 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x80, 0x81, 0x80, 0x07, + 0x10, 0x11, 0x10, 0x12, 0x10, 0x11, 0x10, 0x13, 0x10, 0x11, 0x10, 0x12, 0x10, 0x11, 0x10, 0x14, + 0x10, 0x11, 0x10, 0x12, 0x10, 0x11, 0x10, 0x13, 0x10, 0x11, 0x10, 0x12, 0x10, 0x11, 0x10, 0x15, + 0x10, 0x11, 0x10, 0x12, 0x10, 0x11, 0x10, 0x13, 0x10, 0x11, 0x10, 0x12, 0x10, 0x11, 0x10, 0x14, + 0x10, 0x11, 0x10, 0x12, 0x10, 0x11, 0x10, 0x13, 0x10, 0x11, 0x10, 0x12, 0x10, 0x11, 0x90, 0x16, + 0x20, 0x21, 0x20, 0x22, 0x20, 0x21, 0x20, 0x23, 0x20, 0x21, 0x20, 0x22, 0x20, 0x21, 0x20, 0x24, + 0x20, 0x21, 0x20, 0x22, 0x20, 0x21, 0x20, 0x23, 0x20, 0x21, 0x20, 0x22, 0x20, 0x21, 0x20, 0x25, + 0x30, 0x31, 0x30, 0x32, 0x30, 0x31, 0x30, 0x33, 0x30, 0x31, 0x30, 0x32, 0x30, 0x31, 0x30, 0x34, + 0x40, 0x41, 0x40, 0x42, 0x40, 0x41, 0x40, 0x43, 0x50, 0x51, 0x50, 0x52, 0x60, 0x61, 0x70, 0x78 +}; + +/* hdlc_unpack + * perform HDLC frame processing (bit unstuffing, flag detection, FCS + * calculation) on a sequence of received data bytes (8 bits each, LSB first) + * pass on successfully received, complete frames as SKBs via gigaset_skb_rcvd + * notify of errors via gigaset_isdn_rcv_err + * tally frames, errors etc. in BC structure counters + * parameters: + * src received data + * count number of received bytes + * bcs receiving B channel structure + */ +static inline void hdlc_unpack(unsigned char *src, unsigned count, + struct bc_state *bcs) +{ + struct bas_bc_state *ubc = bcs->hw.bas; + int inputstate; + unsigned seqlen, inbyte, inbits; + + /* load previous state: + * inputstate = set of flag bits: + * - INS_flag_hunt: no complete opening flag received since connection + * setup or last abort + * - INS_have_data: at least one complete data byte received since last + * flag + * seqlen = number of consecutive '1' bits in last 7 input stream bits + * (0..7) + * inbyte = accumulated partial data byte (if !INS_flag_hunt) + * inbits = number of valid bits in inbyte, starting at LSB (0..6) + */ + inputstate = bcs->inputstate; + seqlen = ubc->seqlen; + inbyte = ubc->inbyte; + inbits = ubc->inbits; + + /* bit unstuffing a byte a time + * Take your time to understand this; it's straightforward but tedious. + * The "bitcounts" lookup table is used to speed up the counting of + * leading and trailing '1' bits. + */ + while (count--) { + unsigned char c = *src++; + unsigned char tabentry = bitcounts[c]; + unsigned lead1 = tabentry & 0x0f; + unsigned trail1 = (tabentry >> 4) & 0x0f; + + seqlen += lead1; + + if (unlikely(inputstate & INS_flag_hunt)) { + if (c == PPP_FLAG) { + /* flag-in-one */ + inputstate &= ~(INS_flag_hunt | INS_have_data); + inbyte = 0; + inbits = 0; + } else if (seqlen == 6 && trail1 != 7) { + /* flag completed & not followed by abort */ + inputstate &= ~(INS_flag_hunt | INS_have_data); + inbyte = c >> (lead1 + 1); + inbits = 7 - lead1; + if (trail1 >= 8) { + /* interior stuffing: + * omitting the MSB handles most cases, + * correct the incorrectly handled + * cases individually */ + inbits--; + switch (c) { + case 0xbe: + inbyte = 0x3f; + break; + } + } + } + /* else: continue flag-hunting */ + } else if (likely(seqlen < 5 && trail1 < 7)) { + /* streamlined case: 8 data bits, no stuffing */ + inbyte |= c << inbits; + hdlc_putbyte(inbyte & 0xff, bcs); + inputstate |= INS_have_data; + inbyte >>= 8; + /* inbits unchanged */ + } else if (likely(seqlen == 6 && inbits == 7 - lead1 && + trail1 + 1 == inbits && + !(inputstate & INS_have_data))) { + /* streamlined case: flag idle - state unchanged */ + } else if (unlikely(seqlen > 6)) { + /* abort sequence */ + ubc->aborts++; + hdlc_flush(bcs); + inputstate |= INS_flag_hunt; + } else if (seqlen == 6) { + /* closing flag, including (6 - lead1) '1's + * and one '0' from inbits */ + if (inbits > 7 - lead1) { + hdlc_frag(bcs, inbits + lead1 - 7); + inputstate &= ~INS_have_data; + } else { + if (inbits < 7 - lead1) + ubc->stolen0s++; + if (inputstate & INS_have_data) { + hdlc_done(bcs); + inputstate &= ~INS_have_data; + } + } + + if (c == PPP_FLAG) { + /* complete flag, LSB overlaps preceding flag */ + ubc->shared0s++; + inbits = 0; + inbyte = 0; + } else if (trail1 != 7) { + /* remaining bits */ + inbyte = c >> (lead1 + 1); + inbits = 7 - lead1; + if (trail1 >= 8) { + /* interior stuffing: + * omitting the MSB handles most cases, + * correct the incorrectly handled + * cases individually */ + inbits--; + switch (c) { + case 0xbe: + inbyte = 0x3f; + break; + } + } + } else { + /* abort sequence follows, + * skb already empty anyway */ + ubc->aborts++; + inputstate |= INS_flag_hunt; + } + } else { /* (seqlen < 6) && (seqlen == 5 || trail1 >= 7) */ + + if (c == PPP_FLAG) { + /* complete flag */ + if (seqlen == 5) + ubc->stolen0s++; + if (inbits) { + hdlc_frag(bcs, inbits); + inbits = 0; + inbyte = 0; + } else if (inputstate & INS_have_data) + hdlc_done(bcs); + inputstate &= ~INS_have_data; + } else if (trail1 == 7) { + /* abort sequence */ + ubc->aborts++; + hdlc_flush(bcs); + inputstate |= INS_flag_hunt; + } else { + /* stuffed data */ + if (trail1 < 7) { /* => seqlen == 5 */ + /* stuff bit at position lead1, + * no interior stuffing */ + unsigned char mask = (1 << lead1) - 1; + c = (c & mask) | ((c & ~mask) >> 1); + inbyte |= c << inbits; + inbits += 7; + } else if (seqlen < 5) { /* trail1 >= 8 */ + /* interior stuffing: + * omitting the MSB handles most cases, + * correct the incorrectly handled + * cases individually */ + switch (c) { + case 0xbe: + c = 0x7e; + break; + } + inbyte |= c << inbits; + inbits += 7; + } else { /* seqlen == 5 && trail1 >= 8 */ + + /* stuff bit at lead1 *and* interior + * stuffing -- unstuff individually */ + switch (c) { + case 0x7d: + c = 0x3f; + break; + case 0xbe: + c = 0x3f; + break; + case 0x3e: + c = 0x1f; + break; + case 0x7c: + c = 0x3e; + break; + } + inbyte |= c << inbits; + inbits += 6; + } + if (inbits >= 8) { + inbits -= 8; + hdlc_putbyte(inbyte & 0xff, bcs); + inputstate |= INS_have_data; + inbyte >>= 8; + } + } + } + seqlen = trail1 & 7; + } + + /* save new state */ + bcs->inputstate = inputstate; + ubc->seqlen = seqlen; + ubc->inbyte = inbyte; + ubc->inbits = inbits; +} + +/* trans_receive + * pass on received USB frame transparently as SKB via gigaset_skb_rcvd + * invert bytes + * tally frames, errors etc. in BC structure counters + * parameters: + * src received data + * count number of received bytes + * bcs receiving B channel structure + */ +static inline void trans_receive(unsigned char *src, unsigned count, + struct bc_state *bcs) +{ + struct sk_buff *skb; + int dobytes; + unsigned char *dst; + + if (unlikely(bcs->ignore)) { + bcs->ignore--; + return; + } + skb = bcs->rx_skb; + if (skb == NULL) { + skb = gigaset_new_rx_skb(bcs); + if (skb == NULL) + return; + } + dobytes = bcs->rx_bufsize - skb->len; + while (count > 0) { + dst = skb_put(skb, count < dobytes ? count : dobytes); + while (count > 0 && dobytes > 0) { + *dst++ = bitrev8(*src++); + count--; + dobytes--; + } + if (dobytes == 0) { + dump_bytes(DEBUG_STREAM_DUMP, + "rcv data", skb->data, skb->len); + bcs->hw.bas->goodbytes += skb->len; + gigaset_skb_rcvd(bcs, skb); + skb = gigaset_new_rx_skb(bcs); + if (skb == NULL) + return; + dobytes = bcs->rx_bufsize; + } + } +} + +void gigaset_isoc_receive(unsigned char *src, unsigned count, + struct bc_state *bcs) +{ + switch (bcs->proto2) { + case L2_HDLC: + hdlc_unpack(src, count, bcs); + break; + default: /* assume transparent */ + trans_receive(src, count, bcs); + } +} + +/* == data input =========================================================== */ + +/* process a block of received bytes in command mode (mstate != MS_LOCKED) + * Append received bytes to the command response buffer and forward them + * line by line to the response handler. + * Note: Received lines may be terminated by CR, LF, or CR LF, which will be + * removed before passing the line to the response handler. + */ +static void cmd_loop(unsigned char *src, int numbytes, struct inbuf_t *inbuf) +{ + struct cardstate *cs = inbuf->cs; + unsigned cbytes = cs->cbytes; + unsigned char c; + + while (numbytes--) { + c = *src++; + switch (c) { + case '\n': + if (cbytes == 0 && cs->respdata[0] == '\r') { + /* collapse LF with preceding CR */ + cs->respdata[0] = 0; + break; + } + /* fall through */ + case '\r': + /* end of message line, pass to response handler */ + if (cbytes >= MAX_RESP_SIZE) { + dev_warn(cs->dev, "response too large (%d)\n", + cbytes); + cbytes = MAX_RESP_SIZE; + } + cs->cbytes = cbytes; + gigaset_dbg_buffer(DEBUG_TRANSCMD, "received response", + cbytes, cs->respdata); + gigaset_handle_modem_response(cs); + cbytes = 0; + + /* store EOL byte for CRLF collapsing */ + cs->respdata[0] = c; + break; + default: + /* append to line buffer if possible */ + if (cbytes < MAX_RESP_SIZE) + cs->respdata[cbytes] = c; + cbytes++; + } + } + + /* save state */ + cs->cbytes = cbytes; +} + + +/* process a block of data received through the control channel + */ +void gigaset_isoc_input(struct inbuf_t *inbuf) +{ + struct cardstate *cs = inbuf->cs; + unsigned tail, head, numbytes; + unsigned char *src; + + head = inbuf->head; + while (head != (tail = inbuf->tail)) { + gig_dbg(DEBUG_INTR, "buffer state: %u -> %u", head, tail); + if (head > tail) + tail = RBUFSIZE; + src = inbuf->data + head; + numbytes = tail - head; + gig_dbg(DEBUG_INTR, "processing %u bytes", numbytes); + + if (cs->mstate == MS_LOCKED) { + gigaset_dbg_buffer(DEBUG_LOCKCMD, "received response", + numbytes, src); + gigaset_if_receive(inbuf->cs, src, numbytes); + } else { + cmd_loop(src, numbytes, inbuf); + } + + head += numbytes; + if (head == RBUFSIZE) + head = 0; + gig_dbg(DEBUG_INTR, "setting head to %u", head); + inbuf->head = head; + } +} + + +/* == data output ========================================================== */ + +/** + * gigaset_isoc_send_skb() - queue an skb for sending + * @bcs: B channel descriptor structure. + * @skb: data to send. + * + * Called by LL to queue an skb for sending, and start transmission if + * necessary. + * Once the payload data has been transmitted completely, gigaset_skb_sent() + * will be called with the skb's link layer header preserved. + * + * Return value: + * number of bytes accepted for sending (skb->len) if ok, + * error code < 0 (eg. -ENODEV) on error + */ +int gigaset_isoc_send_skb(struct bc_state *bcs, struct sk_buff *skb) +{ + int len = skb->len; + unsigned long flags; + + spin_lock_irqsave(&bcs->cs->lock, flags); + if (!bcs->cs->connected) { + spin_unlock_irqrestore(&bcs->cs->lock, flags); + return -ENODEV; + } + + skb_queue_tail(&bcs->squeue, skb); + gig_dbg(DEBUG_ISO, "%s: skb queued, qlen=%d", + __func__, skb_queue_len(&bcs->squeue)); + + /* tasklet submits URB if necessary */ + tasklet_schedule(&bcs->hw.bas->sent_tasklet); + spin_unlock_irqrestore(&bcs->cs->lock, flags); + + return len; /* ok so far */ +} diff --git a/drivers/staging/isdn/gigaset/proc.c b/drivers/staging/isdn/gigaset/proc.c new file mode 100644 index 000000000000..e3f9d0f089fa --- /dev/null +++ b/drivers/staging/isdn/gigaset/proc.c @@ -0,0 +1,80 @@ +/* + * Stuff used by all variants of the driver + * + * Copyright (c) 2001 by Stefan Eilers, + * Hansjoerg Lipp <hjlipp@web.de>, + * Tilman Schmidt <tilman@imap.cc>. + * + * ===================================================================== + * 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. + * ===================================================================== + */ + +#include "gigaset.h" + +static ssize_t show_cidmode(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cardstate *cs = dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", cs->cidmode); +} + +static ssize_t set_cidmode(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct cardstate *cs = dev_get_drvdata(dev); + long int value; + char *end; + + value = simple_strtol(buf, &end, 0); + while (*end) + if (!isspace(*end++)) + return -EINVAL; + if (value < 0 || value > 1) + return -EINVAL; + + if (mutex_lock_interruptible(&cs->mutex)) + return -ERESTARTSYS; + + cs->waiting = 1; + if (!gigaset_add_event(cs, &cs->at_state, EV_PROC_CIDMODE, + NULL, value, NULL)) { + cs->waiting = 0; + mutex_unlock(&cs->mutex); + return -ENOMEM; + } + gigaset_schedule_event(cs); + + wait_event(cs->waitqueue, !cs->waiting); + + mutex_unlock(&cs->mutex); + + return count; +} + +static DEVICE_ATTR(cidmode, S_IRUGO | S_IWUSR, show_cidmode, set_cidmode); + +/* free sysfs for device */ +void gigaset_free_dev_sysfs(struct cardstate *cs) +{ + if (!cs->tty_dev) + return; + + gig_dbg(DEBUG_INIT, "removing sysfs entries"); + device_remove_file(cs->tty_dev, &dev_attr_cidmode); +} + +/* initialize sysfs for device */ +void gigaset_init_dev_sysfs(struct cardstate *cs) +{ + if (!cs->tty_dev) + return; + + gig_dbg(DEBUG_INIT, "setting up sysfs"); + if (device_create_file(cs->tty_dev, &dev_attr_cidmode)) + pr_err("could not create sysfs attribute\n"); +} diff --git a/drivers/staging/isdn/gigaset/ser-gigaset.c b/drivers/staging/isdn/gigaset/ser-gigaset.c new file mode 100644 index 000000000000..e1de8b1a1001 --- /dev/null +++ b/drivers/staging/isdn/gigaset/ser-gigaset.c @@ -0,0 +1,799 @@ +/* This is the serial hardware link layer (HLL) for the Gigaset 307x isdn + * DECT base (aka Sinus 45 isdn) using the RS232 DECT data module M101, + * written as a line discipline. + * + * ===================================================================== + * 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. + * ===================================================================== + */ + +#include "gigaset.h" +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/platform_device.h> +#include <linux/completion.h> + +/* Version Information */ +#define DRIVER_AUTHOR "Tilman Schmidt" +#define DRIVER_DESC "Serial Driver for Gigaset 307x using Siemens M101" + +#define GIGASET_MINORS 1 +#define GIGASET_MINOR 0 +#define GIGASET_MODULENAME "ser_gigaset" +#define GIGASET_DEVNAME "ttyGS" + +/* length limit according to Siemens 3070usb-protokoll.doc ch. 2.1 */ +#define IF_WRITEBUF 264 + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_LDISC(N_GIGASET_M101); + +static int startmode = SM_ISDN; +module_param(startmode, int, S_IRUGO); +MODULE_PARM_DESC(startmode, "initial operation mode"); +static int cidmode = 1; +module_param(cidmode, int, S_IRUGO); +MODULE_PARM_DESC(cidmode, "stay in CID mode when idle"); + +static struct gigaset_driver *driver; + +struct ser_cardstate { + struct platform_device dev; + struct tty_struct *tty; + atomic_t refcnt; + struct completion dead_cmp; +}; + +static struct platform_driver device_driver = { + .driver = { + .name = GIGASET_MODULENAME, + }, +}; + +static void flush_send_queue(struct cardstate *); + +/* transmit data from current open skb + * result: number of bytes sent or error code < 0 + */ +static int write_modem(struct cardstate *cs) +{ + struct tty_struct *tty = cs->hw.ser->tty; + struct bc_state *bcs = &cs->bcs[0]; /* only one channel */ + struct sk_buff *skb = bcs->tx_skb; + int sent = -EOPNOTSUPP; + + WARN_ON(!tty || !tty->ops || !skb); + + if (!skb->len) { + dev_kfree_skb_any(skb); + bcs->tx_skb = NULL; + return -EINVAL; + } + + set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); + if (tty->ops->write) + sent = tty->ops->write(tty, skb->data, skb->len); + gig_dbg(DEBUG_OUTPUT, "write_modem: sent %d", sent); + if (sent < 0) { + /* error */ + flush_send_queue(cs); + return sent; + } + skb_pull(skb, sent); + if (!skb->len) { + /* skb sent completely */ + gigaset_skb_sent(bcs, skb); + + gig_dbg(DEBUG_INTR, "kfree skb (Adr: %lx)!", + (unsigned long) skb); + dev_kfree_skb_any(skb); + bcs->tx_skb = NULL; + } + return sent; +} + +/* + * transmit first queued command buffer + * result: number of bytes sent or error code < 0 + */ +static int send_cb(struct cardstate *cs) +{ + struct tty_struct *tty = cs->hw.ser->tty; + struct cmdbuf_t *cb, *tcb; + unsigned long flags; + int sent = 0; + + WARN_ON(!tty || !tty->ops); + + cb = cs->cmdbuf; + if (!cb) + return 0; /* nothing to do */ + + if (cb->len) { + set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); + sent = tty->ops->write(tty, cb->buf + cb->offset, cb->len); + if (sent < 0) { + /* error */ + gig_dbg(DEBUG_OUTPUT, "send_cb: write error %d", sent); + flush_send_queue(cs); + return sent; + } + cb->offset += sent; + cb->len -= sent; + gig_dbg(DEBUG_OUTPUT, "send_cb: sent %d, left %u, queued %u", + sent, cb->len, cs->cmdbytes); + } + + while (cb && !cb->len) { + spin_lock_irqsave(&cs->cmdlock, flags); + cs->cmdbytes -= cs->curlen; + tcb = cb; + cs->cmdbuf = cb = cb->next; + if (cb) { + cb->prev = NULL; + cs->curlen = cb->len; + } else { + cs->lastcmdbuf = NULL; + cs->curlen = 0; + } + spin_unlock_irqrestore(&cs->cmdlock, flags); + + if (tcb->wake_tasklet) + tasklet_schedule(tcb->wake_tasklet); + kfree(tcb); + } + return sent; +} + +/* + * send queue tasklet + * If there is already a skb opened, put data to the transfer buffer + * by calling "write_modem". + * Otherwise take a new skb out of the queue. + */ +static void gigaset_modem_fill(unsigned long data) +{ + struct cardstate *cs = (struct cardstate *) data; + struct bc_state *bcs; + struct sk_buff *nextskb; + int sent = 0; + + if (!cs) { + gig_dbg(DEBUG_OUTPUT, "%s: no cardstate", __func__); + return; + } + bcs = cs->bcs; + if (!bcs) { + gig_dbg(DEBUG_OUTPUT, "%s: no cardstate", __func__); + return; + } + if (!bcs->tx_skb) { + /* no skb is being sent; send command if any */ + sent = send_cb(cs); + gig_dbg(DEBUG_OUTPUT, "%s: send_cb -> %d", __func__, sent); + if (sent) + /* something sent or error */ + return; + + /* no command to send; get skb */ + nextskb = skb_dequeue(&bcs->squeue); + if (!nextskb) + /* no skb either, nothing to do */ + return; + bcs->tx_skb = nextskb; + + gig_dbg(DEBUG_INTR, "Dequeued skb (Adr: %lx)", + (unsigned long) bcs->tx_skb); + } + + /* send skb */ + gig_dbg(DEBUG_OUTPUT, "%s: tx_skb", __func__); + if (write_modem(cs) < 0) + gig_dbg(DEBUG_OUTPUT, "%s: write_modem failed", __func__); +} + +/* + * throw away all data queued for sending + */ +static void flush_send_queue(struct cardstate *cs) +{ + struct sk_buff *skb; + struct cmdbuf_t *cb; + unsigned long flags; + + /* command queue */ + spin_lock_irqsave(&cs->cmdlock, flags); + while ((cb = cs->cmdbuf) != NULL) { + cs->cmdbuf = cb->next; + if (cb->wake_tasklet) + tasklet_schedule(cb->wake_tasklet); + kfree(cb); + } + cs->cmdbuf = cs->lastcmdbuf = NULL; + cs->cmdbytes = cs->curlen = 0; + spin_unlock_irqrestore(&cs->cmdlock, flags); + + /* data queue */ + if (cs->bcs->tx_skb) + dev_kfree_skb_any(cs->bcs->tx_skb); + while ((skb = skb_dequeue(&cs->bcs->squeue)) != NULL) + dev_kfree_skb_any(skb); +} + + +/* Gigaset Driver Interface */ +/* ======================== */ + +/* + * queue an AT command string for transmission to the Gigaset device + * parameters: + * cs controller state structure + * buf buffer containing the string to send + * len number of characters to send + * wake_tasklet tasklet to run when transmission is complete, or NULL + * return value: + * number of bytes queued, or error code < 0 + */ +static int gigaset_write_cmd(struct cardstate *cs, struct cmdbuf_t *cb) +{ + unsigned long flags; + + gigaset_dbg_buffer(cs->mstate != MS_LOCKED ? + DEBUG_TRANSCMD : DEBUG_LOCKCMD, + "CMD Transmit", cb->len, cb->buf); + + spin_lock_irqsave(&cs->cmdlock, flags); + cb->prev = cs->lastcmdbuf; + if (cs->lastcmdbuf) + cs->lastcmdbuf->next = cb; + else { + cs->cmdbuf = cb; + cs->curlen = cb->len; + } + cs->cmdbytes += cb->len; + cs->lastcmdbuf = cb; + spin_unlock_irqrestore(&cs->cmdlock, flags); + + spin_lock_irqsave(&cs->lock, flags); + if (cs->connected) + tasklet_schedule(&cs->write_tasklet); + spin_unlock_irqrestore(&cs->lock, flags); + return cb->len; +} + +/* + * tty_driver.write_room interface routine + * return number of characters the driver will accept to be written + * parameter: + * controller state structure + * return value: + * number of characters + */ +static int gigaset_write_room(struct cardstate *cs) +{ + unsigned bytes; + + bytes = cs->cmdbytes; + return bytes < IF_WRITEBUF ? IF_WRITEBUF - bytes : 0; +} + +/* + * tty_driver.chars_in_buffer interface routine + * return number of characters waiting to be sent + * parameter: + * controller state structure + * return value: + * number of characters + */ +static int gigaset_chars_in_buffer(struct cardstate *cs) +{ + return cs->cmdbytes; +} + +/* + * implementation of ioctl(GIGASET_BRKCHARS) + * parameter: + * controller state structure + * return value: + * -EINVAL (unimplemented function) + */ +static int gigaset_brkchars(struct cardstate *cs, const unsigned char buf[6]) +{ + /* not implemented */ + return -EINVAL; +} + +/* + * Open B channel + * Called by "do_action" in ev-layer.c + */ +static int gigaset_init_bchannel(struct bc_state *bcs) +{ + /* nothing to do for M10x */ + gigaset_bchannel_up(bcs); + return 0; +} + +/* + * Close B channel + * Called by "do_action" in ev-layer.c + */ +static int gigaset_close_bchannel(struct bc_state *bcs) +{ + /* nothing to do for M10x */ + gigaset_bchannel_down(bcs); + return 0; +} + +/* + * Set up B channel structure + * This is called by "gigaset_initcs" in common.c + */ +static int gigaset_initbcshw(struct bc_state *bcs) +{ + /* unused */ + bcs->hw.ser = NULL; + return 0; +} + +/* + * Free B channel structure + * Called by "gigaset_freebcs" in common.c + */ +static void gigaset_freebcshw(struct bc_state *bcs) +{ + /* unused */ +} + +/* + * Reinitialize B channel structure + * This is called by "bcs_reinit" in common.c + */ +static void gigaset_reinitbcshw(struct bc_state *bcs) +{ + /* nothing to do for M10x */ +} + +/* + * Free hardware specific device data + * This will be called by "gigaset_freecs" in common.c + */ +static void gigaset_freecshw(struct cardstate *cs) +{ + tasklet_kill(&cs->write_tasklet); + if (!cs->hw.ser) + return; + platform_device_unregister(&cs->hw.ser->dev); +} + +static void gigaset_device_release(struct device *dev) +{ + kfree(container_of(dev, struct ser_cardstate, dev.dev)); +} + +/* + * Set up hardware specific device data + * This is called by "gigaset_initcs" in common.c + */ +static int gigaset_initcshw(struct cardstate *cs) +{ + int rc; + struct ser_cardstate *scs; + + scs = kzalloc(sizeof(struct ser_cardstate), GFP_KERNEL); + if (!scs) { + pr_err("out of memory\n"); + return -ENOMEM; + } + cs->hw.ser = scs; + + cs->hw.ser->dev.name = GIGASET_MODULENAME; + cs->hw.ser->dev.id = cs->minor_index; + cs->hw.ser->dev.dev.release = gigaset_device_release; + rc = platform_device_register(&cs->hw.ser->dev); + if (rc != 0) { + pr_err("error %d registering platform device\n", rc); + kfree(cs->hw.ser); + cs->hw.ser = NULL; + return rc; + } + + tasklet_init(&cs->write_tasklet, + gigaset_modem_fill, (unsigned long) cs); + return 0; +} + +/* + * set modem control lines + * Parameters: + * card state structure + * modem control line state ([TIOCM_DTR]|[TIOCM_RTS]) + * Called by "gigaset_start" and "gigaset_enterconfigmode" in common.c + * and by "if_lock" and "if_termios" in interface.c + */ +static int gigaset_set_modem_ctrl(struct cardstate *cs, unsigned old_state, + unsigned new_state) +{ + struct tty_struct *tty = cs->hw.ser->tty; + unsigned int set, clear; + + WARN_ON(!tty || !tty->ops); + /* tiocmset is an optional tty driver method */ + if (!tty->ops->tiocmset) + return -EINVAL; + set = new_state & ~old_state; + clear = old_state & ~new_state; + if (!set && !clear) + return 0; + gig_dbg(DEBUG_IF, "tiocmset set %x clear %x", set, clear); + return tty->ops->tiocmset(tty, set, clear); +} + +static int gigaset_baud_rate(struct cardstate *cs, unsigned cflag) +{ + return -EINVAL; +} + +static int gigaset_set_line_ctrl(struct cardstate *cs, unsigned cflag) +{ + return -EINVAL; +} + +static const struct gigaset_ops ops = { + .write_cmd = gigaset_write_cmd, + .write_room = gigaset_write_room, + .chars_in_buffer = gigaset_chars_in_buffer, + .brkchars = gigaset_brkchars, + .init_bchannel = gigaset_init_bchannel, + .close_bchannel = gigaset_close_bchannel, + .initbcshw = gigaset_initbcshw, + .freebcshw = gigaset_freebcshw, + .reinitbcshw = gigaset_reinitbcshw, + .initcshw = gigaset_initcshw, + .freecshw = gigaset_freecshw, + .set_modem_ctrl = gigaset_set_modem_ctrl, + .baud_rate = gigaset_baud_rate, + .set_line_ctrl = gigaset_set_line_ctrl, + .send_skb = gigaset_m10x_send_skb, /* asyncdata.c */ + .handle_input = gigaset_m10x_input, /* asyncdata.c */ +}; + + +/* Line Discipline Interface */ +/* ========================= */ + +/* helper functions for cardstate refcounting */ +static struct cardstate *cs_get(struct tty_struct *tty) +{ + struct cardstate *cs = tty->disc_data; + + if (!cs || !cs->hw.ser) { + gig_dbg(DEBUG_ANY, "%s: no cardstate", __func__); + return NULL; + } + atomic_inc(&cs->hw.ser->refcnt); + return cs; +} + +static void cs_put(struct cardstate *cs) +{ + if (atomic_dec_and_test(&cs->hw.ser->refcnt)) + complete(&cs->hw.ser->dead_cmp); +} + +/* + * Called by the tty driver when the line discipline is pushed onto the tty. + * Called in process context. + */ +static int +gigaset_tty_open(struct tty_struct *tty) +{ + struct cardstate *cs; + int rc; + + gig_dbg(DEBUG_INIT, "Starting HLL for Gigaset M101"); + + pr_info(DRIVER_DESC "\n"); + + if (!driver) { + pr_err("%s: no driver structure\n", __func__); + return -ENODEV; + } + + /* allocate memory for our device state and initialize it */ + cs = gigaset_initcs(driver, 1, 1, 0, cidmode, GIGASET_MODULENAME); + if (!cs) { + rc = -ENODEV; + goto error; + } + + cs->dev = &cs->hw.ser->dev.dev; + cs->hw.ser->tty = tty; + atomic_set(&cs->hw.ser->refcnt, 1); + init_completion(&cs->hw.ser->dead_cmp); + tty->disc_data = cs; + + /* Set the amount of data we're willing to receive per call + * from the hardware driver to half of the input buffer size + * to leave some reserve. + * Note: We don't do flow control towards the hardware driver. + * If more data is received than will fit into the input buffer, + * it will be dropped and an error will be logged. This should + * never happen as the device is slow and the buffer size ample. + */ + tty->receive_room = RBUFSIZE/2; + + /* OK.. Initialization of the datastructures and the HW is done.. Now + * startup system and notify the LL that we are ready to run + */ + if (startmode == SM_LOCKED) + cs->mstate = MS_LOCKED; + rc = gigaset_start(cs); + if (rc < 0) { + tasklet_kill(&cs->write_tasklet); + goto error; + } + + gig_dbg(DEBUG_INIT, "Startup of HLL done"); + return 0; + +error: + gig_dbg(DEBUG_INIT, "Startup of HLL failed"); + tty->disc_data = NULL; + gigaset_freecs(cs); + return rc; +} + +/* + * Called by the tty driver when the line discipline is removed. + * Called from process context. + */ +static void +gigaset_tty_close(struct tty_struct *tty) +{ + struct cardstate *cs = tty->disc_data; + + gig_dbg(DEBUG_INIT, "Stopping HLL for Gigaset M101"); + + if (!cs) { + gig_dbg(DEBUG_INIT, "%s: no cardstate", __func__); + return; + } + + /* prevent other callers from entering ldisc methods */ + tty->disc_data = NULL; + + if (!cs->hw.ser) + pr_err("%s: no hw cardstate\n", __func__); + else { + /* wait for running methods to finish */ + if (!atomic_dec_and_test(&cs->hw.ser->refcnt)) + wait_for_completion(&cs->hw.ser->dead_cmp); + } + + /* stop operations */ + gigaset_stop(cs); + tasklet_kill(&cs->write_tasklet); + flush_send_queue(cs); + cs->dev = NULL; + gigaset_freecs(cs); + + gig_dbg(DEBUG_INIT, "Shutdown of HLL done"); +} + +/* + * Called by the tty driver when the tty line is hung up. + * Wait for I/O to driver to complete and unregister ISDN device. + * This is already done by the close routine, so just call that. + * Called from process context. + */ +static int gigaset_tty_hangup(struct tty_struct *tty) +{ + gigaset_tty_close(tty); + return 0; +} + +/* + * Ioctl on the tty. + * Called in process context only. + * May be re-entered by multiple ioctl calling threads. + */ +static int +gigaset_tty_ioctl(struct tty_struct *tty, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct cardstate *cs = cs_get(tty); + int rc, val; + int __user *p = (int __user *)arg; + + if (!cs) + return -ENXIO; + + switch (cmd) { + + case FIONREAD: + /* unused, always return zero */ + val = 0; + rc = put_user(val, p); + break; + + case TCFLSH: + /* flush our buffers and the serial port's buffer */ + switch (arg) { + case TCIFLUSH: + /* no own input buffer to flush */ + break; + case TCIOFLUSH: + case TCOFLUSH: + flush_send_queue(cs); + break; + } + /* fall through */ + + default: + /* pass through to underlying serial device */ + rc = n_tty_ioctl_helper(tty, file, cmd, arg); + break; + } + cs_put(cs); + return rc; +} + +/* + * Called by the tty driver when a block of data has been received. + * Will not be re-entered while running but other ldisc functions + * may be called in parallel. + * Can be called from hard interrupt level as well as soft interrupt + * level or mainline. + * Parameters: + * tty tty structure + * buf buffer containing received characters + * cflags buffer containing error flags for received characters (ignored) + * count number of received characters + */ +static void +gigaset_tty_receive(struct tty_struct *tty, const unsigned char *buf, + char *cflags, int count) +{ + struct cardstate *cs = cs_get(tty); + unsigned tail, head, n; + struct inbuf_t *inbuf; + + if (!cs) + return; + inbuf = cs->inbuf; + if (!inbuf) { + dev_err(cs->dev, "%s: no inbuf\n", __func__); + cs_put(cs); + return; + } + + tail = inbuf->tail; + head = inbuf->head; + gig_dbg(DEBUG_INTR, "buffer state: %u -> %u, receive %u bytes", + head, tail, count); + + if (head <= tail) { + /* possible buffer wraparound */ + n = min_t(unsigned, count, RBUFSIZE - tail); + memcpy(inbuf->data + tail, buf, n); + tail = (tail + n) % RBUFSIZE; + buf += n; + count -= n; + } + + if (count > 0) { + /* tail < head and some data left */ + n = head - tail - 1; + if (count > n) { + dev_err(cs->dev, + "inbuf overflow, discarding %d bytes\n", + count - n); + count = n; + } + memcpy(inbuf->data + tail, buf, count); + tail += count; + } + + gig_dbg(DEBUG_INTR, "setting tail to %u", tail); + inbuf->tail = tail; + + /* Everything was received .. Push data into handler */ + gig_dbg(DEBUG_INTR, "%s-->BH", __func__); + gigaset_schedule_event(cs); + cs_put(cs); +} + +/* + * Called by the tty driver when there's room for more data to send. + */ +static void +gigaset_tty_wakeup(struct tty_struct *tty) +{ + struct cardstate *cs = cs_get(tty); + + clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); + if (!cs) + return; + tasklet_schedule(&cs->write_tasklet); + cs_put(cs); +} + +static struct tty_ldisc_ops gigaset_ldisc = { + .owner = THIS_MODULE, + .magic = TTY_LDISC_MAGIC, + .name = "ser_gigaset", + .open = gigaset_tty_open, + .close = gigaset_tty_close, + .hangup = gigaset_tty_hangup, + .ioctl = gigaset_tty_ioctl, + .receive_buf = gigaset_tty_receive, + .write_wakeup = gigaset_tty_wakeup, +}; + + +/* Initialization / Shutdown */ +/* ========================= */ + +static int __init ser_gigaset_init(void) +{ + int rc; + + gig_dbg(DEBUG_INIT, "%s", __func__); + rc = platform_driver_register(&device_driver); + if (rc != 0) { + pr_err("error %d registering platform driver\n", rc); + return rc; + } + + /* allocate memory for our driver state and initialize it */ + driver = gigaset_initdriver(GIGASET_MINOR, GIGASET_MINORS, + GIGASET_MODULENAME, GIGASET_DEVNAME, + &ops, THIS_MODULE); + if (!driver) { + rc = -ENOMEM; + goto error; + } + + rc = tty_register_ldisc(N_GIGASET_M101, &gigaset_ldisc); + if (rc != 0) { + pr_err("error %d registering line discipline\n", rc); + goto error; + } + + return 0; + +error: + if (driver) { + gigaset_freedriver(driver); + driver = NULL; + } + platform_driver_unregister(&device_driver); + return rc; +} + +static void __exit ser_gigaset_exit(void) +{ + int rc; + + gig_dbg(DEBUG_INIT, "%s", __func__); + + if (driver) { + gigaset_freedriver(driver); + driver = NULL; + } + + rc = tty_unregister_ldisc(N_GIGASET_M101); + if (rc != 0) + pr_err("error %d unregistering line discipline\n", rc); + + platform_driver_unregister(&device_driver); +} + +module_init(ser_gigaset_init); +module_exit(ser_gigaset_exit); diff --git a/drivers/staging/isdn/gigaset/usb-gigaset.c b/drivers/staging/isdn/gigaset/usb-gigaset.c new file mode 100644 index 000000000000..eade36dafa34 --- /dev/null +++ b/drivers/staging/isdn/gigaset/usb-gigaset.c @@ -0,0 +1,949 @@ +/* + * USB driver for Gigaset 307x directly or using M105 Data. + * + * Copyright (c) 2001 by Stefan Eilers + * and Hansjoerg Lipp <hjlipp@web.de>. + * + * This driver was derived from the USB skeleton driver by + * Greg Kroah-Hartman <greg@kroah.com> + * + * ===================================================================== + * 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. + * ===================================================================== + */ + +#include "gigaset.h" +#include <linux/usb.h> +#include <linux/module.h> +#include <linux/moduleparam.h> + +/* Version Information */ +#define DRIVER_AUTHOR "Hansjoerg Lipp <hjlipp@web.de>, Stefan Eilers" +#define DRIVER_DESC "USB Driver for Gigaset 307x using M105" + +/* Module parameters */ + +static int startmode = SM_ISDN; +static int cidmode = 1; + +module_param(startmode, int, S_IRUGO); +module_param(cidmode, int, S_IRUGO); +MODULE_PARM_DESC(startmode, "start in isdn4linux mode"); +MODULE_PARM_DESC(cidmode, "Call-ID mode"); + +#define GIGASET_MINORS 1 +#define GIGASET_MINOR 8 +#define GIGASET_MODULENAME "usb_gigaset" +#define GIGASET_DEVNAME "ttyGU" + +/* length limit according to Siemens 3070usb-protokoll.doc ch. 2.1 */ +#define IF_WRITEBUF 264 + +/* Values for the Gigaset M105 Data */ +#define USB_M105_VENDOR_ID 0x0681 +#define USB_M105_PRODUCT_ID 0x0009 + +/* table of devices that work with this driver */ +static const struct usb_device_id gigaset_table[] = { + { USB_DEVICE(USB_M105_VENDOR_ID, USB_M105_PRODUCT_ID) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, gigaset_table); + +/* + * Control requests (empty fields: 00) + * + * RT|RQ|VALUE|INDEX|LEN |DATA + * In: + * C1 08 01 + * Get flags (1 byte). Bits: 0=dtr,1=rts,3-7:? + * C1 0F ll ll + * Get device information/status (llll: 0x200 and 0x40 seen). + * Real size: I only saw MIN(llll,0x64). + * Contents: seems to be always the same... + * offset 0x00: Length of this structure (0x64) (len: 1,2,3 bytes) + * offset 0x3c: String (16 bit chars): "MCCI USB Serial V2.0" + * rest: ? + * Out: + * 41 11 + * Initialize/reset device ? + * 41 00 xx 00 + * ? (xx=00 or 01; 01 on start, 00 on close) + * 41 07 vv mm + * Set/clear flags vv=value, mm=mask (see RQ 08) + * 41 12 xx + * Used before the following configuration requests are issued + * (with xx=0x0f). I've seen other values<0xf, though. + * 41 01 xx xx + * Set baud rate. xxxx=ceil(0x384000/rate)=trunc(0x383fff/rate)+1. + * 41 03 ps bb + * Set byte size and parity. p: 0x20=even,0x10=odd,0x00=no parity + * [ 0x30: m, 0x40: s ] + * [s: 0: 1 stop bit; 1: 1.5; 2: 2] + * bb: bits/byte (seen 7 and 8) + * 41 13 -- -- -- -- 10 00 ww 00 00 00 xx 00 00 00 yy 00 00 00 zz 00 00 00 + * ?? + * Initialization: 01, 40, 00, 00 + * Open device: 00 40, 00, 00 + * yy and zz seem to be equal, either 0x00 or 0x0a + * (ww,xx) pairs seen: (00,00), (00,40), (01,40), (09,80), (19,80) + * 41 19 -- -- -- -- 06 00 00 00 00 xx 11 13 + * Used after every "configuration sequence" (RQ 12, RQs 01/03/13). + * xx is usually 0x00 but was 0x7e before starting data transfer + * in unimodem mode. So, this might be an array of characters that + * need special treatment ("commit all bufferd data"?), 11=^Q, 13=^S. + * + * Unimodem mode: use "modprobe ppp_async flag_time=0" as the device _needs_ two + * flags per packet. + */ + +/* functions called if a device of this driver is connected/disconnected */ +static int gigaset_probe(struct usb_interface *interface, + const struct usb_device_id *id); +static void gigaset_disconnect(struct usb_interface *interface); + +/* functions called before/after suspend */ +static int gigaset_suspend(struct usb_interface *intf, pm_message_t message); +static int gigaset_resume(struct usb_interface *intf); +static int gigaset_pre_reset(struct usb_interface *intf); + +static struct gigaset_driver *driver; + +/* usb specific object needed to register this driver with the usb subsystem */ +static struct usb_driver gigaset_usb_driver = { + .name = GIGASET_MODULENAME, + .probe = gigaset_probe, + .disconnect = gigaset_disconnect, + .id_table = gigaset_table, + .suspend = gigaset_suspend, + .resume = gigaset_resume, + .reset_resume = gigaset_resume, + .pre_reset = gigaset_pre_reset, + .post_reset = gigaset_resume, + .disable_hub_initiated_lpm = 1, +}; + +struct usb_cardstate { + struct usb_device *udev; /* usb device pointer */ + struct usb_interface *interface; /* interface for this device */ + int busy; /* bulk output in progress */ + + /* Output buffer */ + unsigned char *bulk_out_buffer; + int bulk_out_size; + int bulk_out_epnum; + struct urb *bulk_out_urb; + + /* Input buffer */ + unsigned char *rcvbuf; + int rcvbuf_size; + struct urb *read_urb; + + char bchars[6]; /* for request 0x19 */ +}; + +static inline unsigned tiocm_to_gigaset(unsigned state) +{ + return ((state & TIOCM_DTR) ? 1 : 0) | ((state & TIOCM_RTS) ? 2 : 0); +} + +static int gigaset_set_modem_ctrl(struct cardstate *cs, unsigned old_state, + unsigned new_state) +{ + struct usb_device *udev = cs->hw.usb->udev; + unsigned mask, val; + int r; + + mask = tiocm_to_gigaset(old_state ^ new_state); + val = tiocm_to_gigaset(new_state); + + gig_dbg(DEBUG_USBREQ, "set flags 0x%02x with mask 0x%02x", val, mask); + r = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 7, 0x41, + (val & 0xff) | ((mask & 0xff) << 8), 0, + NULL, 0, 2000 /* timeout? */); + if (r < 0) + return r; + return 0; +} + +/* + * Set M105 configuration value + * using undocumented device commands reverse engineered from USB traces + * of the Siemens Windows driver + */ +static int set_value(struct cardstate *cs, u8 req, u16 val) +{ + struct usb_device *udev = cs->hw.usb->udev; + int r, r2; + + gig_dbg(DEBUG_USBREQ, "request %02x (%04x)", + (unsigned)req, (unsigned)val); + r = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0x12, 0x41, + 0xf /*?*/, 0, NULL, 0, 2000 /*?*/); + /* no idea what this does */ + if (r < 0) { + dev_err(&udev->dev, "error %d on request 0x12\n", -r); + return r; + } + + r = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), req, 0x41, + val, 0, NULL, 0, 2000 /*?*/); + if (r < 0) + dev_err(&udev->dev, "error %d on request 0x%02x\n", + -r, (unsigned)req); + + r2 = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0x19, 0x41, + 0, 0, cs->hw.usb->bchars, 6, 2000 /*?*/); + if (r2 < 0) + dev_err(&udev->dev, "error %d on request 0x19\n", -r2); + + return r < 0 ? r : (r2 < 0 ? r2 : 0); +} + +/* + * set the baud rate on the internal serial adapter + * using the undocumented parameter setting command + */ +static int gigaset_baud_rate(struct cardstate *cs, unsigned cflag) +{ + u16 val; + u32 rate; + + cflag &= CBAUD; + + switch (cflag) { + case B300: rate = 300; break; + case B600: rate = 600; break; + case B1200: rate = 1200; break; + case B2400: rate = 2400; break; + case B4800: rate = 4800; break; + case B9600: rate = 9600; break; + case B19200: rate = 19200; break; + case B38400: rate = 38400; break; + case B57600: rate = 57600; break; + case B115200: rate = 115200; break; + default: + rate = 9600; + dev_err(cs->dev, "unsupported baudrate request 0x%x," + " using default of B9600\n", cflag); + } + + val = 0x383fff / rate + 1; + + return set_value(cs, 1, val); +} + +/* + * set the line format on the internal serial adapter + * using the undocumented parameter setting command + */ +static int gigaset_set_line_ctrl(struct cardstate *cs, unsigned cflag) +{ + u16 val = 0; + + /* set the parity */ + if (cflag & PARENB) + val |= (cflag & PARODD) ? 0x10 : 0x20; + + /* set the number of data bits */ + switch (cflag & CSIZE) { + case CS5: + val |= 5 << 8; break; + case CS6: + val |= 6 << 8; break; + case CS7: + val |= 7 << 8; break; + case CS8: + val |= 8 << 8; break; + default: + dev_err(cs->dev, "CSIZE was not CS5-CS8, using default of 8\n"); + val |= 8 << 8; + break; + } + + /* set the number of stop bits */ + if (cflag & CSTOPB) { + if ((cflag & CSIZE) == CS5) + val |= 1; /* 1.5 stop bits */ + else + val |= 2; /* 2 stop bits */ + } + + return set_value(cs, 3, val); +} + + +/*============================================================================*/ +static int gigaset_init_bchannel(struct bc_state *bcs) +{ + /* nothing to do for M10x */ + gigaset_bchannel_up(bcs); + return 0; +} + +static int gigaset_close_bchannel(struct bc_state *bcs) +{ + /* nothing to do for M10x */ + gigaset_bchannel_down(bcs); + return 0; +} + +static int write_modem(struct cardstate *cs); +static int send_cb(struct cardstate *cs); + + +/* Write tasklet handler: Continue sending current skb, or send command, or + * start sending an skb from the send queue. + */ +static void gigaset_modem_fill(unsigned long data) +{ + struct cardstate *cs = (struct cardstate *) data; + struct bc_state *bcs = &cs->bcs[0]; /* only one channel */ + + gig_dbg(DEBUG_OUTPUT, "modem_fill"); + + if (cs->hw.usb->busy) { + gig_dbg(DEBUG_OUTPUT, "modem_fill: busy"); + return; + } + +again: + if (!bcs->tx_skb) { /* no skb is being sent */ + if (cs->cmdbuf) { /* commands to send? */ + gig_dbg(DEBUG_OUTPUT, "modem_fill: cb"); + if (send_cb(cs) < 0) { + gig_dbg(DEBUG_OUTPUT, + "modem_fill: send_cb failed"); + goto again; /* no callback will be called! */ + } + return; + } + + /* skbs to send? */ + bcs->tx_skb = skb_dequeue(&bcs->squeue); + if (!bcs->tx_skb) + return; + + gig_dbg(DEBUG_INTR, "Dequeued skb (Adr: %lx)!", + (unsigned long) bcs->tx_skb); + } + + gig_dbg(DEBUG_OUTPUT, "modem_fill: tx_skb"); + if (write_modem(cs) < 0) { + gig_dbg(DEBUG_OUTPUT, "modem_fill: write_modem failed"); + goto again; /* no callback will be called! */ + } +} + +/* + * Interrupt Input URB completion routine + */ +static void gigaset_read_int_callback(struct urb *urb) +{ + struct cardstate *cs = urb->context; + struct inbuf_t *inbuf = cs->inbuf; + int status = urb->status; + int r; + unsigned numbytes; + unsigned char *src; + unsigned long flags; + + if (!status) { + numbytes = urb->actual_length; + + if (numbytes) { + src = cs->hw.usb->rcvbuf; + if (unlikely(*src)) + dev_warn(cs->dev, + "%s: There was no leading 0, but 0x%02x!\n", + __func__, (unsigned) *src); + ++src; /* skip leading 0x00 */ + --numbytes; + if (gigaset_fill_inbuf(inbuf, src, numbytes)) { + gig_dbg(DEBUG_INTR, "%s-->BH", __func__); + gigaset_schedule_event(inbuf->cs); + } + } else + gig_dbg(DEBUG_INTR, "Received zero block length"); + } else { + /* The urb might have been killed. */ + gig_dbg(DEBUG_ANY, "%s - nonzero status received: %d", + __func__, status); + if (status == -ENOENT || status == -ESHUTDOWN) + /* killed or endpoint shutdown: don't resubmit */ + return; + } + + /* resubmit URB */ + spin_lock_irqsave(&cs->lock, flags); + if (!cs->connected) { + spin_unlock_irqrestore(&cs->lock, flags); + pr_err("%s: disconnected\n", __func__); + return; + } + r = usb_submit_urb(urb, GFP_ATOMIC); + spin_unlock_irqrestore(&cs->lock, flags); + if (r) + dev_err(cs->dev, "error %d resubmitting URB\n", -r); +} + + +/* This callback routine is called when data was transmitted to the device. */ +static void gigaset_write_bulk_callback(struct urb *urb) +{ + struct cardstate *cs = urb->context; + int status = urb->status; + unsigned long flags; + + switch (status) { + case 0: /* normal completion */ + break; + case -ENOENT: /* killed */ + gig_dbg(DEBUG_ANY, "%s: killed", __func__); + cs->hw.usb->busy = 0; + return; + default: + dev_err(cs->dev, "bulk transfer failed (status %d)\n", + -status); + /* That's all we can do. Communication problems + are handled by timeouts or network protocols. */ + } + + spin_lock_irqsave(&cs->lock, flags); + if (!cs->connected) { + pr_err("%s: disconnected\n", __func__); + } else { + cs->hw.usb->busy = 0; + tasklet_schedule(&cs->write_tasklet); + } + spin_unlock_irqrestore(&cs->lock, flags); +} + +static int send_cb(struct cardstate *cs) +{ + struct cmdbuf_t *cb = cs->cmdbuf; + unsigned long flags; + int count; + int status = -ENOENT; + struct usb_cardstate *ucs = cs->hw.usb; + + do { + if (!cb->len) { + spin_lock_irqsave(&cs->cmdlock, flags); + cs->cmdbytes -= cs->curlen; + gig_dbg(DEBUG_OUTPUT, "send_cb: sent %u bytes, %u left", + cs->curlen, cs->cmdbytes); + cs->cmdbuf = cb->next; + if (cs->cmdbuf) { + cs->cmdbuf->prev = NULL; + cs->curlen = cs->cmdbuf->len; + } else { + cs->lastcmdbuf = NULL; + cs->curlen = 0; + } + spin_unlock_irqrestore(&cs->cmdlock, flags); + + if (cb->wake_tasklet) + tasklet_schedule(cb->wake_tasklet); + kfree(cb); + + cb = cs->cmdbuf; + } + + if (cb) { + count = min(cb->len, ucs->bulk_out_size); + gig_dbg(DEBUG_OUTPUT, "send_cb: send %d bytes", count); + + usb_fill_bulk_urb(ucs->bulk_out_urb, ucs->udev, + usb_sndbulkpipe(ucs->udev, + ucs->bulk_out_epnum), + cb->buf + cb->offset, count, + gigaset_write_bulk_callback, cs); + + cb->offset += count; + cb->len -= count; + ucs->busy = 1; + + spin_lock_irqsave(&cs->lock, flags); + status = cs->connected ? + usb_submit_urb(ucs->bulk_out_urb, GFP_ATOMIC) : + -ENODEV; + spin_unlock_irqrestore(&cs->lock, flags); + + if (status) { + ucs->busy = 0; + dev_err(cs->dev, + "could not submit urb (error %d)\n", + -status); + cb->len = 0; /* skip urb => remove cb+wakeup + in next loop cycle */ + } + } + } while (cb && status); /* next command on error */ + + return status; +} + +/* Send command to device. */ +static int gigaset_write_cmd(struct cardstate *cs, struct cmdbuf_t *cb) +{ + unsigned long flags; + int len; + + gigaset_dbg_buffer(cs->mstate != MS_LOCKED ? + DEBUG_TRANSCMD : DEBUG_LOCKCMD, + "CMD Transmit", cb->len, cb->buf); + + spin_lock_irqsave(&cs->cmdlock, flags); + cb->prev = cs->lastcmdbuf; + if (cs->lastcmdbuf) + cs->lastcmdbuf->next = cb; + else { + cs->cmdbuf = cb; + cs->curlen = cb->len; + } + cs->cmdbytes += cb->len; + cs->lastcmdbuf = cb; + spin_unlock_irqrestore(&cs->cmdlock, flags); + + spin_lock_irqsave(&cs->lock, flags); + len = cb->len; + if (cs->connected) + tasklet_schedule(&cs->write_tasklet); + spin_unlock_irqrestore(&cs->lock, flags); + return len; +} + +static int gigaset_write_room(struct cardstate *cs) +{ + unsigned bytes; + + bytes = cs->cmdbytes; + return bytes < IF_WRITEBUF ? IF_WRITEBUF - bytes : 0; +} + +static int gigaset_chars_in_buffer(struct cardstate *cs) +{ + return cs->cmdbytes; +} + +/* + * set the break characters on the internal serial adapter + * using undocumented device commands reverse engineered from USB traces + * of the Siemens Windows driver + */ +static int gigaset_brkchars(struct cardstate *cs, const unsigned char buf[6]) +{ + struct usb_device *udev = cs->hw.usb->udev; + + gigaset_dbg_buffer(DEBUG_USBREQ, "brkchars", 6, buf); + memcpy(cs->hw.usb->bchars, buf, 6); + return usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0x19, 0x41, + 0, 0, &buf, 6, 2000); +} + +static void gigaset_freebcshw(struct bc_state *bcs) +{ + /* unused */ +} + +/* Initialize the b-channel structure */ +static int gigaset_initbcshw(struct bc_state *bcs) +{ + /* unused */ + bcs->hw.usb = NULL; + return 0; +} + +static void gigaset_reinitbcshw(struct bc_state *bcs) +{ + /* nothing to do for M10x */ +} + +static void gigaset_freecshw(struct cardstate *cs) +{ + tasklet_kill(&cs->write_tasklet); + kfree(cs->hw.usb); +} + +static int gigaset_initcshw(struct cardstate *cs) +{ + struct usb_cardstate *ucs; + + cs->hw.usb = ucs = + kmalloc(sizeof(struct usb_cardstate), GFP_KERNEL); + if (!ucs) { + pr_err("out of memory\n"); + return -ENOMEM; + } + + ucs->bchars[0] = 0; + ucs->bchars[1] = 0; + ucs->bchars[2] = 0; + ucs->bchars[3] = 0; + ucs->bchars[4] = 0x11; + ucs->bchars[5] = 0x13; + ucs->bulk_out_buffer = NULL; + ucs->bulk_out_urb = NULL; + ucs->read_urb = NULL; + tasklet_init(&cs->write_tasklet, + gigaset_modem_fill, (unsigned long) cs); + + return 0; +} + +/* Send data from current skb to the device. */ +static int write_modem(struct cardstate *cs) +{ + int ret = 0; + int count; + struct bc_state *bcs = &cs->bcs[0]; /* only one channel */ + struct usb_cardstate *ucs = cs->hw.usb; + unsigned long flags; + + gig_dbg(DEBUG_OUTPUT, "len: %d...", bcs->tx_skb->len); + + if (!bcs->tx_skb->len) { + dev_kfree_skb_any(bcs->tx_skb); + bcs->tx_skb = NULL; + return -EINVAL; + } + + /* Copy data to bulk out buffer and transmit data */ + count = min(bcs->tx_skb->len, (unsigned) ucs->bulk_out_size); + skb_copy_from_linear_data(bcs->tx_skb, ucs->bulk_out_buffer, count); + skb_pull(bcs->tx_skb, count); + ucs->busy = 1; + gig_dbg(DEBUG_OUTPUT, "write_modem: send %d bytes", count); + + spin_lock_irqsave(&cs->lock, flags); + if (cs->connected) { + usb_fill_bulk_urb(ucs->bulk_out_urb, ucs->udev, + usb_sndbulkpipe(ucs->udev, + ucs->bulk_out_epnum), + ucs->bulk_out_buffer, count, + gigaset_write_bulk_callback, cs); + ret = usb_submit_urb(ucs->bulk_out_urb, GFP_ATOMIC); + } else { + ret = -ENODEV; + } + spin_unlock_irqrestore(&cs->lock, flags); + + if (ret) { + dev_err(cs->dev, "could not submit urb (error %d)\n", -ret); + ucs->busy = 0; + } + + if (!bcs->tx_skb->len) { + /* skb sent completely */ + gigaset_skb_sent(bcs, bcs->tx_skb); + + gig_dbg(DEBUG_INTR, "kfree skb (Adr: %lx)!", + (unsigned long) bcs->tx_skb); + dev_kfree_skb_any(bcs->tx_skb); + bcs->tx_skb = NULL; + } + + return ret; +} + +static int gigaset_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + int retval; + struct usb_device *udev = interface_to_usbdev(interface); + struct usb_host_interface *hostif = interface->cur_altsetting; + struct cardstate *cs = NULL; + struct usb_cardstate *ucs = NULL; + struct usb_endpoint_descriptor *endpoint; + int buffer_size; + + gig_dbg(DEBUG_ANY, "%s: Check if device matches ...", __func__); + + /* See if the device offered us matches what we can accept */ + if ((le16_to_cpu(udev->descriptor.idVendor) != USB_M105_VENDOR_ID) || + (le16_to_cpu(udev->descriptor.idProduct) != USB_M105_PRODUCT_ID)) { + gig_dbg(DEBUG_ANY, "device ID (0x%x, 0x%x) not for me - skip", + le16_to_cpu(udev->descriptor.idVendor), + le16_to_cpu(udev->descriptor.idProduct)); + return -ENODEV; + } + if (hostif->desc.bInterfaceNumber != 0) { + gig_dbg(DEBUG_ANY, "interface %d not for me - skip", + hostif->desc.bInterfaceNumber); + return -ENODEV; + } + if (hostif->desc.bAlternateSetting != 0) { + dev_notice(&udev->dev, "unsupported altsetting %d - skip", + hostif->desc.bAlternateSetting); + return -ENODEV; + } + if (hostif->desc.bInterfaceClass != 255) { + dev_notice(&udev->dev, "unsupported interface class %d - skip", + hostif->desc.bInterfaceClass); + return -ENODEV; + } + + dev_info(&udev->dev, "%s: Device matched ... !\n", __func__); + + /* allocate memory for our device state and initialize it */ + cs = gigaset_initcs(driver, 1, 1, 0, cidmode, GIGASET_MODULENAME); + if (!cs) + return -ENODEV; + ucs = cs->hw.usb; + + /* save off device structure ptrs for later use */ + usb_get_dev(udev); + ucs->udev = udev; + ucs->interface = interface; + cs->dev = &interface->dev; + + /* save address of controller structure */ + usb_set_intfdata(interface, cs); + + endpoint = &hostif->endpoint[0].desc; + + buffer_size = le16_to_cpu(endpoint->wMaxPacketSize); + ucs->bulk_out_size = buffer_size; + ucs->bulk_out_epnum = usb_endpoint_num(endpoint); + ucs->bulk_out_buffer = kmalloc(buffer_size, GFP_KERNEL); + if (!ucs->bulk_out_buffer) { + dev_err(cs->dev, "Couldn't allocate bulk_out_buffer\n"); + retval = -ENOMEM; + goto error; + } + + ucs->bulk_out_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!ucs->bulk_out_urb) { + dev_err(cs->dev, "Couldn't allocate bulk_out_urb\n"); + retval = -ENOMEM; + goto error; + } + + endpoint = &hostif->endpoint[1].desc; + + ucs->busy = 0; + + ucs->read_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!ucs->read_urb) { + dev_err(cs->dev, "No free urbs available\n"); + retval = -ENOMEM; + goto error; + } + buffer_size = le16_to_cpu(endpoint->wMaxPacketSize); + ucs->rcvbuf_size = buffer_size; + ucs->rcvbuf = kmalloc(buffer_size, GFP_KERNEL); + if (!ucs->rcvbuf) { + dev_err(cs->dev, "Couldn't allocate rcvbuf\n"); + retval = -ENOMEM; + goto error; + } + /* Fill the interrupt urb and send it to the core */ + usb_fill_int_urb(ucs->read_urb, udev, + usb_rcvintpipe(udev, usb_endpoint_num(endpoint)), + ucs->rcvbuf, buffer_size, + gigaset_read_int_callback, + cs, endpoint->bInterval); + + retval = usb_submit_urb(ucs->read_urb, GFP_KERNEL); + if (retval) { + dev_err(cs->dev, "Could not submit URB (error %d)\n", -retval); + goto error; + } + + /* tell common part that the device is ready */ + if (startmode == SM_LOCKED) + cs->mstate = MS_LOCKED; + + retval = gigaset_start(cs); + if (retval < 0) { + tasklet_kill(&cs->write_tasklet); + goto error; + } + return 0; + +error: + usb_kill_urb(ucs->read_urb); + kfree(ucs->bulk_out_buffer); + usb_free_urb(ucs->bulk_out_urb); + kfree(ucs->rcvbuf); + usb_free_urb(ucs->read_urb); + usb_set_intfdata(interface, NULL); + ucs->read_urb = ucs->bulk_out_urb = NULL; + ucs->rcvbuf = ucs->bulk_out_buffer = NULL; + usb_put_dev(ucs->udev); + ucs->udev = NULL; + ucs->interface = NULL; + gigaset_freecs(cs); + return retval; +} + +static void gigaset_disconnect(struct usb_interface *interface) +{ + struct cardstate *cs; + struct usb_cardstate *ucs; + + cs = usb_get_intfdata(interface); + ucs = cs->hw.usb; + + dev_info(cs->dev, "disconnecting Gigaset USB adapter\n"); + + usb_kill_urb(ucs->read_urb); + + gigaset_stop(cs); + + usb_set_intfdata(interface, NULL); + tasklet_kill(&cs->write_tasklet); + + usb_kill_urb(ucs->bulk_out_urb); + + kfree(ucs->bulk_out_buffer); + usb_free_urb(ucs->bulk_out_urb); + kfree(ucs->rcvbuf); + usb_free_urb(ucs->read_urb); + ucs->read_urb = ucs->bulk_out_urb = NULL; + ucs->rcvbuf = ucs->bulk_out_buffer = NULL; + + usb_put_dev(ucs->udev); + ucs->interface = NULL; + ucs->udev = NULL; + cs->dev = NULL; + gigaset_freecs(cs); +} + +/* gigaset_suspend + * This function is called before the USB connection is suspended or reset. + */ +static int gigaset_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct cardstate *cs = usb_get_intfdata(intf); + + /* stop activity */ + cs->connected = 0; /* prevent rescheduling */ + usb_kill_urb(cs->hw.usb->read_urb); + tasklet_kill(&cs->write_tasklet); + usb_kill_urb(cs->hw.usb->bulk_out_urb); + + gig_dbg(DEBUG_SUSPEND, "suspend complete"); + return 0; +} + +/* gigaset_resume + * This function is called after the USB connection has been resumed or reset. + */ +static int gigaset_resume(struct usb_interface *intf) +{ + struct cardstate *cs = usb_get_intfdata(intf); + int rc; + + /* resubmit interrupt URB */ + cs->connected = 1; + rc = usb_submit_urb(cs->hw.usb->read_urb, GFP_KERNEL); + if (rc) { + dev_err(cs->dev, "Could not submit read URB (error %d)\n", -rc); + return rc; + } + + gig_dbg(DEBUG_SUSPEND, "resume complete"); + return 0; +} + +/* gigaset_pre_reset + * This function is called before the USB connection is reset. + */ +static int gigaset_pre_reset(struct usb_interface *intf) +{ + /* same as suspend */ + return gigaset_suspend(intf, PMSG_ON); +} + +static const struct gigaset_ops ops = { + .write_cmd = gigaset_write_cmd, + .write_room = gigaset_write_room, + .chars_in_buffer = gigaset_chars_in_buffer, + .brkchars = gigaset_brkchars, + .init_bchannel = gigaset_init_bchannel, + .close_bchannel = gigaset_close_bchannel, + .initbcshw = gigaset_initbcshw, + .freebcshw = gigaset_freebcshw, + .reinitbcshw = gigaset_reinitbcshw, + .initcshw = gigaset_initcshw, + .freecshw = gigaset_freecshw, + .set_modem_ctrl = gigaset_set_modem_ctrl, + .baud_rate = gigaset_baud_rate, + .set_line_ctrl = gigaset_set_line_ctrl, + .send_skb = gigaset_m10x_send_skb, + .handle_input = gigaset_m10x_input, +}; + +/* + * This function is called while kernel-module is loaded + */ +static int __init usb_gigaset_init(void) +{ + int result; + + /* allocate memory for our driver state and initialize it */ + driver = gigaset_initdriver(GIGASET_MINOR, GIGASET_MINORS, + GIGASET_MODULENAME, GIGASET_DEVNAME, + &ops, THIS_MODULE); + if (driver == NULL) { + result = -ENOMEM; + goto error; + } + + /* register this driver with the USB subsystem */ + result = usb_register(&gigaset_usb_driver); + if (result < 0) { + pr_err("error %d registering USB driver\n", -result); + goto error; + } + + pr_info(DRIVER_DESC "\n"); + return 0; + +error: + if (driver) + gigaset_freedriver(driver); + driver = NULL; + return result; +} + +/* + * This function is called while unloading the kernel-module + */ +static void __exit usb_gigaset_exit(void) +{ + int i; + + gigaset_blockdriver(driver); /* => probe will fail + * => no gigaset_start any more + */ + + /* stop all connected devices */ + for (i = 0; i < driver->minors; i++) + gigaset_shutdown(driver->cs + i); + + /* from now on, no isdn callback should be possible */ + + /* deregister this driver with the USB subsystem */ + usb_deregister(&gigaset_usb_driver); + /* this will call the disconnect-callback */ + /* from now on, no disconnect/probe callback should be running */ + + gigaset_freedriver(driver); + driver = NULL; +} + + +module_init(usb_gigaset_init); +module_exit(usb_gigaset_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); + +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/isdn/hysdn/Kconfig b/drivers/staging/isdn/hysdn/Kconfig new file mode 100644 index 000000000000..1971ef850c9a --- /dev/null +++ b/drivers/staging/isdn/hysdn/Kconfig @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-only +config HYSDN + tristate "Hypercope HYSDN cards (Champ, Ergo, Metro) support (module only)" + depends on m && PROC_FS && PCI + 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/staging/isdn/hysdn/Makefile b/drivers/staging/isdn/hysdn/Makefile new file mode 100644 index 000000000000..e01f17f22ebb --- /dev/null +++ b/drivers/staging/isdn/hysdn/Makefile @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0-only +# 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/staging/isdn/hysdn/boardergo.c b/drivers/staging/isdn/hysdn/boardergo.c new file mode 100644 index 000000000000..2aa2a0e08247 --- /dev/null +++ b/drivers/staging/isdn/hysdn/boardergo.c @@ -0,0 +1,445 @@ +/* $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/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) +{ + hysdn_card *card = dev_id; /* parameter from irq */ + tErgDpram *dpr; + unsigned long flags; + unsigned char volatile b; + + if (!card) + return IRQ_NONE; /* error -> spurious interrupt */ + if (!card->irq_enabled) + return IRQ_NONE; /* other device interrupting or irq switched off */ + + spin_lock_irqsave(&card->hysdn_lock, flags); /* no further irqs allowed */ + + if (!(bytein(card->iobase + PCI9050_INTR_REG) & PCI9050_INTR_REG_STAT1)) { + spin_unlock_irqrestore(&card->hysdn_lock, 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); + spin_unlock_irqrestore(&card->hysdn_lock, flags); + return IRQ_HANDLED; +} /* ergo_interrupt */ + +/******************************************************************************/ +/* ergo_irq_bh will be called as part of the kernel clearing its shared work */ +/* queue sometime after a call to schedule_work has been made passing our */ +/* work_struct. 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(struct work_struct *ugli_api) +{ + hysdn_card *card = container_of(ugli_api, hysdn_card, irq_queue); + tErgDpram *dpr; + int again; + unsigned long flags; + + if (card->state != CARD_STATE_RUN) + return; /* invalid call */ + + dpr = card->dpram; /* point to DPRAM */ + + spin_lock_irqsave(&card->hysdn_lock, flags); + if (card->hw_lock) { + spin_unlock_irqrestore(&card->hysdn_lock, flags); /* hardware currently unavailable */ + return; + } + card->hw_lock = 1; /* we now lock the hardware */ + + do { + 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 */ + 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 */ + + spin_unlock_irqrestore(&card->hysdn_lock, flags); +} /* ergo_irq_bh */ + + +/*********************************************************/ +/* stop the card (hardware reset) and disable interrupts */ +/*********************************************************/ +static void +ergo_stopcard(hysdn_card *card) +{ + unsigned long flags; + unsigned char val; + + hysdn_net_release(card); /* first release the net device if existing */ +#ifdef CONFIG_HYSDN_CAPI + hycapi_capi_stop(card); +#endif /* CONFIG_HYSDN_CAPI */ + spin_lock_irqsave(&card->hysdn_lock, flags); + 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 */ + + spin_unlock_irqrestore(&card->hysdn_lock, 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) +{ + unsigned long flags; + + if (card->state != CARD_STATE_RUN) { + card->err_log_state = ERRLOG_STATE_OFF; /* must be off */ + return; + } + spin_lock_irqsave(&card->hysdn_lock, flags); + + if (((card->err_log_state == ERRLOG_STATE_OFF) && !on) || + ((card->err_log_state == ERRLOG_STATE_ON) && on)) { + spin_unlock_irqrestore(&card->hysdn_lock, 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 */ + + spin_unlock_irqrestore(&card->hysdn_lock, 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, unsigned char *buf, + unsigned long offs) +{ + unsigned char *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 */ + + 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, unsigned char *buf, int len) +{ + tDpramBootSpooler *sp = (tDpramBootSpooler *) card->dpram; + unsigned char *dst; + unsigned char buflen; + int nr_write; + unsigned char tmp_rdptr; + unsigned char 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 */ + + /* 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. */ + unsigned long 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) || + ((*(unsigned long *) 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"); + spin_lock_irqsave(&card->hysdn_lock, flags); + + 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 */ + + spin_unlock_irqrestore(&card->hysdn_lock, 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 */ + 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); + iounmap(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, IRQF_SHARED, "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, ergo_irq_bh); + spin_lock_init(&card->hysdn_lock); + + return (0); +} /* ergo_inithardware */ diff --git a/drivers/staging/isdn/hysdn/boardergo.h b/drivers/staging/isdn/hysdn/boardergo.h new file mode 100644 index 000000000000..e99bd81c4034 --- /dev/null +++ b/drivers/staging/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 */ unsigned char ToHyBuf[ERG_TO_HY_BUF_SIZE]; + /*0E00 */ unsigned char ToPcBuf[ERG_TO_PC_BUF_SIZE]; + + /*1C00 */ unsigned char bSoftUart[SIZE_RSV_SOFT_UART]; + /* size 0x1B0 */ + + /*1DB0 *//* tErrLogEntry */ unsigned char volatile ErrLogMsg[64]; + /* size 64 bytes */ + /*1DB0 unsigned long ulErrType; */ + /*1DB4 unsigned long ulErrSubtype; */ + /*1DB8 unsigned long ucTextSize; */ + /*1DB9 unsigned long ucText[ERRLOG_TEXT_SIZE]; *//* ASCIIZ of len ucTextSize-1 */ + /*1DF0 */ + + /*1DF0 */ unsigned short volatile ToHyChannel; + /*1DF2 */ unsigned short volatile ToHySize; + /*1DF4 */ unsigned char volatile ToHyFlag; + /* !=0: msg for Hy waiting */ + /*1DF5 */ unsigned char volatile ToPcFlag; + /* !=0: msg for PC waiting */ + /*1DF6 */ unsigned short volatile ToPcChannel; + /*1DF8 */ unsigned short volatile ToPcSize; + /*1DFA */ unsigned char bRes1DBA[0x1E00 - 0x1DFA]; + /* 6 bytes */ + + /*1E00 */ unsigned char bRestOfEntryTbl[0x1F00 - 0x1E00]; + /*1F00 */ unsigned long TrapTable[62]; + /*1FF8 */ unsigned char bRes1FF8[0x1FFB - 0x1FF8]; + /* low part of reset vetor */ + /*1FFB */ unsigned char 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 */ unsigned char 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 */ unsigned char bRes1FFD; + /*1FFE */ unsigned char ToPcInt; + /* E1_intclear; on CHAMP2: E1_intset */ + /*1FFF */ unsigned char 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/staging/isdn/hysdn/hycapi.c b/drivers/staging/isdn/hysdn/hycapi.c new file mode 100644 index 000000000000..a2c15cd7bf67 --- /dev/null +++ b/drivers/staging/isdn/hysdn/hycapi.c @@ -0,0 +1,785 @@ +/* $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/proc_fs.h> +#include <linux/seq_file.h> +#include <linux/signal.h> +#include <linux/kernel.h> +#include <linux/skbuff.h> +#include <linux/netdevice.h> +#include <linux/slab.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_param(hycapi_enable, uint, 0); + +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 u16 hycapi_send_message(struct capi_ctr *ctrl, struct sk_buff *skb); + +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 +******************************/ + +static 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_down(ctrl); +} + +/****************************** +Kernel-Capi callback remove_ctr +******************************/ + +static 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; + } + skb_put_data(skb, &len, sizeof(__u16)); + skb_put_data(skb, &appl, sizeof(__u16)); + skb_put_data(skb, &_command, sizeof(__u8)); + skb_put_data(skb, &_subcommand, sizeof(__u8)); + skb_put_data(skb, &MessageNumber, sizeof(__u16)); + skb_put_data(skb, &MessageBufferSize, sizeof(__u16)); + skb_put_data(skb, &(rp->level3cnt), sizeof(__u16)); + skb_put_data(skb, &(rp->datablkcnt), sizeof(__u16)); + skb_put_data(skb, &(rp->datablklen), sizeof(__u16)); + skb_put_data(skb, 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. +*************************************************************/ + +static 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; + } + skb_put_data(skb, &len, sizeof(__u16)); + skb_put_data(skb, &appl, sizeof(__u16)); + skb_put_data(skb, &_command, sizeof(__u8)); + skb_put_data(skb, &_subcommand, sizeof(__u8)); + skb_put_data(skb, &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 +******************************************************************/ + +static 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_down(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! + +***************************************************************/ + +static 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; + skb_copy_from_linear_data(skb, msghead, 22); + skb_copy_to_linear_data_offset(skb, _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; +} + +static int hycapi_proc_show(struct seq_file *m, void *v) +{ + struct capi_ctr *ctrl = m->private; + hycapictrl_info *cinfo = (hycapictrl_info *)(ctrl->driverdata); + hysdn_card *card = cinfo->card; + char *s; + + seq_printf(m, "%-16s %s\n", "name", cinfo->cardname); + seq_printf(m, "%-16s 0x%x\n", "io", card->iobase); + seq_printf(m, "%-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; + } + seq_printf(m, "%-16s %s\n", "type", s); + if ((s = cinfo->version[VER_DRIVER]) != NULL) + seq_printf(m, "%-16s %s\n", "ver_driver", s); + if ((s = cinfo->version[VER_CARDTYPE]) != NULL) + seq_printf(m, "%-16s %s\n", "ver_cardtype", s); + if ((s = cinfo->version[VER_SERIAL]) != NULL) + seq_printf(m, "%-16s %s\n", "ver_serial", s); + + seq_printf(m, "%-16s %s\n", "cardname", cinfo->cardname); + + return 0; +} + +/************************************************************** +hycapi_load_firmware + +This does NOT load any firmware, but the callback somehow is needed +on capi-interface registration. + +**************************************************************/ + +static int hycapi_load_firmware(struct capi_ctr *ctrl, capiloaddata *data) +{ +#ifdef HYCAPI_PRINTFNAMES + printk(KERN_NOTICE "hycapi_load_firmware\n"); +#endif + return 0; +} + + +static char *hycapi_procinfo(struct capi_ctr *ctrl) +{ + hycapictrl_info *cinfo = (hycapictrl_info *)(ctrl->driverdata); +#ifdef HYCAPI_PRINTFNAMES + printk(KERN_NOTICE "%s\n", __func__); +#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, unsigned char *buf, unsigned short 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, length %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; + } + skb_put_data(skb, buf, MsgLen); + skb_put_data(skb, CP64, 2 * sizeof(__u32)); + skb_put_data(skb, 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; + } + skb_put_data(skb, 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 = kzalloc(sizeof(hycapictrl_info), GFP_ATOMIC); + if (!cinfo) { + printk(KERN_WARNING "HYSDN: no memory for capi-ctrl.\n"); + return -ENOMEM; + } + 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->proc_show = hycapi_proc_show; + 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/staging/isdn/hysdn/hysdn_boot.c b/drivers/staging/isdn/hysdn/hysdn_boot.c new file mode 100644 index 000000000000..ba177c3a621b --- /dev/null +++ b/drivers/staging/isdn/hysdn/hysdn_boot.c @@ -0,0 +1,400 @@ +/* $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 <linux/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 { + unsigned short Cryptor; /* for use with Decrypt function */ + unsigned short Nrecs; /* records remaining in file */ + unsigned char pof_state;/* actual state of read handler */ + unsigned char is_crypted;/* card data is crypted */ + int BufSize; /* actual number of bytes bufferd */ + int last_error; /* last occurred error */ + unsigned short pof_recid;/* actual pof recid */ + unsigned long pof_reclen;/* total length of pof record data */ + unsigned long pof_recoffset;/* actual offset inside pof record */ + union { + unsigned char 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. */ +/*****************************************************/ +static 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. */ +/***************************************************************/ +static void +DecryptBuf(struct boot_data *boot, int cnt) +{ + unsigned char *bufp = boot->buf.BootBuf; + + while (cnt--) { + boot->Cryptor = (boot->Cryptor >> 1) ^ ((boot->Cryptor & 1U) ? CRYPT_FEEDTERM : 0); + *bufp++ ^= (unsigned char)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; + unsigned char *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 */ + /* fall through */ + 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 */ + /* fall through */ + 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 = (unsigned short)(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, unsigned char **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 = kzalloc(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; + + 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, unsigned char *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/staging/isdn/hysdn/hysdn_defs.h b/drivers/staging/isdn/hysdn/hysdn_defs.h new file mode 100644 index 000000000000..cdac46a21692 --- /dev/null +++ b/drivers/staging/isdn/hysdn/hysdn_defs.h @@ -0,0 +1,282 @@ +/* $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/hysdn_if.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/skbuff.h> + +#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 */ + unsigned char bus; /* pci bus the card is connected to */ + unsigned char devfn; /* slot+function bit encoded */ + unsigned short subsysid;/* PCI subsystem id */ + unsigned char brdtype; /* type of card */ + unsigned int bchans; /* number of available B-channels */ + unsigned int faxchans; /* number of available fax-channels */ + unsigned char mac_addr[6];/* MAC Address read from card */ + unsigned int irq; /* interrupt number */ + unsigned int iobase; /* IO-port base address */ + unsigned long plxbase; /* PLX memory base */ + unsigned long membase; /* DPRAM memory base */ + unsigned long 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 */ + unsigned char err_log_state;/* actual error log state of the card */ + unsigned long 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 */ + unsigned char volatile irq_enabled;/* interrupt enabled if != 0 */ + unsigned char volatile hw_lock;/* hardware is currently locked -> no access */ + + /* boot process */ + void *boot; /* pointer to boot private data */ + int (*writebootimg) (struct HYSDN_CARD *, unsigned char *, unsigned long); + int (*writebootseq) (struct HYSDN_CARD *, unsigned char *, int); + int (*waitpofready) (struct HYSDN_CARD *); + int (*testram) (struct HYSDN_CARD *); + + /* scheduler for data transfer (only async parts) */ + unsigned char async_data[256];/* async data to be sent (normally for config) */ + unsigned short volatile async_len;/* length of data to sent */ + unsigned short 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 *); + + spinlock_t hysdn_lock; +#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 hysdn_card *card_root; /* pointer to first card */ + + + +/*************************/ +/* im/exported functions */ +/*************************/ + +/* 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 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 *, unsigned char **); /* open proc file for writing pof */ +extern int pof_write_buffer(hysdn_card *, int); /* write boot data to card */ +extern int EvalSysrTokData(hysdn_card *, unsigned char *, int); /* Check Sysready Token Data */ + +/* hysdn_sched.c */ +extern int hysdn_sched_tx(hysdn_card *, unsigned char *, + unsigned short volatile *, unsigned short volatile *, + unsigned short); +extern int hysdn_sched_rx(hysdn_card *, unsigned char *, unsigned short, + unsigned short); +extern int hysdn_tx_cfgline(hysdn_card *, unsigned char *, + unsigned short); /* send one cfg line */ + +/* hysdn_net.c */ +extern unsigned int hynet_enable; +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 *, unsigned char *, + unsigned short); /* 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 void hycapi_rx_capipkt(hysdn_card *card, unsigned char *buf, + unsigned short 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/staging/isdn/hysdn/hysdn_init.c b/drivers/staging/isdn/hysdn/hysdn_init.c new file mode 100644 index 000000000000..0db2f7506250 --- /dev/null +++ b/drivers/staging/isdn/hysdn/hysdn_init.c @@ -0,0 +1,213 @@ +/* $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/module.h> +#include <linux/init.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, 0, 0, BD_METRO }, + { PCI_VENDOR_ID_HYPERCOPE, PCI_DEVICE_ID_HYPERCOPE_PLX, + PCI_ANY_ID, PCI_SUBDEVICE_ID_HYPERCOPE_CHAMP2, 0, 0, BD_CHAMP2 }, + { PCI_VENDOR_ID_HYPERCOPE, PCI_DEVICE_ID_HYPERCOPE_PLX, + PCI_ANY_ID, PCI_SUBDEVICE_ID_HYPERCOPE_ERGO, 0, 0, BD_ERGO }, + { PCI_VENDOR_ID_HYPERCOPE, PCI_DEVICE_ID_HYPERCOPE_PLX, + PCI_ANY_ID, PCI_SUBDEVICE_ID_HYPERCOPE_OLD_ERGO, 0, 0, BD_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 int cardmax; /* number of found cards */ +hysdn_card *card_root = NULL; /* pointer to first card */ +static hysdn_card *card_last = NULL; /* pointer to first card */ + + +/****************************************************************************/ +/* 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. */ +/****************************************************************************/ + +/****************************************************************************/ +/* 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 hysdn_pci_init_one(struct pci_dev *akt_pcidev, + const struct pci_device_id *ent) +{ + hysdn_card *card; + int rc; + + rc = pci_enable_device(akt_pcidev); + if (rc) + return rc; + + if (!(card = kzalloc(sizeof(hysdn_card), GFP_KERNEL))) { + printk(KERN_ERR "HYSDN: unable to alloc device mem \n"); + rc = -ENOMEM; + goto err_out; + } + 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 */ + card->brdtype = ent->driver_data; + + if (ergo_inithardware(card)) { + printk(KERN_WARNING "HYSDN: card at io 0x%04x already in use\n", card->iobase); + rc = -EBUSY; + goto err_out_card; + } + + 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 */ + + pci_set_drvdata(akt_pcidev, card); + return 0; + +err_out_card: + kfree(card); +err_out: + pci_disable_device(akt_pcidev); + return rc; +} + +static void hysdn_pci_remove_one(struct pci_dev *akt_pcidev) +{ + hysdn_card *card = pci_get_drvdata(akt_pcidev); + + pci_set_drvdata(akt_pcidev, NULL); + + if (card->stopcard) + card->stopcard(card); + +#ifdef CONFIG_HYSDN_CAPI + hycapi_capi_release(card); +#endif + + if (card->releasehardware) + card->releasehardware(card); /* free all hardware resources */ + + if (card == card_root) { + card_root = card_root->next; + if (!card_root) + card_last = NULL; + } else { + hysdn_card *tmp = card_root; + while (tmp) { + if (tmp->next == card) + tmp->next = card->next; + card_last = tmp; + tmp = tmp->next; + } + } + + kfree(card); + pci_disable_device(akt_pcidev); +} + +static struct pci_driver hysdn_pci_driver = { + .name = "hysdn", + .id_table = hysdn_pci_tbl, + .probe = hysdn_pci_init_one, + .remove = hysdn_pci_remove_one, +}; + +static int hysdn_have_procfs; + +static int __init +hysdn_init(void) +{ + int rc; + + printk(KERN_NOTICE "HYSDN: module loaded\n"); + + rc = pci_register_driver(&hysdn_pci_driver); + if (rc) + return rc; + + printk(KERN_INFO "HYSDN: %d card(s) found.\n", cardmax); + + if (!hysdn_procconf_init()) + hysdn_have_procfs = 1; + +#ifdef CONFIG_HYSDN_CAPI + if (cardmax > 0) { + if (hycapi_init()) { + printk(KERN_ERR "HYCAPI: init failed\n"); + + if (hysdn_have_procfs) + hysdn_procconf_release(); + + pci_unregister_driver(&hysdn_pci_driver); + return -ESPIPE; + } + } +#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) +{ + if (hysdn_have_procfs) + hysdn_procconf_release(); + + pci_unregister_driver(&hysdn_pci_driver); + +#ifdef CONFIG_HYSDN_CAPI + hycapi_cleanup(); +#endif /* CONFIG_HYSDN_CAPI */ + + printk(KERN_NOTICE "HYSDN: module unloaded\n"); +} /* cleanup_module */ + +module_init(hysdn_init); +module_exit(hysdn_exit); diff --git a/drivers/staging/isdn/hysdn/hysdn_net.c b/drivers/staging/isdn/hysdn/hysdn_net.c new file mode 100644 index 000000000000..8e9c34f33d86 --- /dev/null +++ b/drivers/staging/isdn/hysdn/hysdn_net.c @@ -0,0 +1,326 @@ +/* $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/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_param(hynet_enable, uint, 0); + +#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 { + /* 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. + */ + struct net_device *dev; + 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 */ + + + +/*********************************************************************/ +/* 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->ml_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; 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(ifa->ifa_local)), &ifa->ifa_local, sizeof(ifa->ifa_local)); + } + } 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 netdev_tx_t +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 */ + netif_trans_update(dev); + + /* 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->ml_priv)->irq_queue); + } + return NETDEV_TX_OK; /* 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->dev->stats.tx_packets++; + lp->dev->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, unsigned char *buf, unsigned short len) +{ + struct net_local *lp = card->netif; + struct net_device *dev; + struct sk_buff *skb; + + if (!lp) + return; /* non existing device */ + + dev = lp->dev; + dev->stats.rx_bytes += len; + + skb = dev_alloc_skb(len); + if (skb == NULL) { + printk(KERN_NOTICE "%s: Memory squeeze, dropping packet.\n", + dev->name); + dev->stats.rx_dropped++; + return; + } + /* copy the data */ + skb_put_data(skb, buf, len); + + /* determine the used protocol */ + skb->protocol = eth_type_trans(skb, dev); + + dev->stats.rx_packets++; /* adjust packet count */ + + netif_rx(skb); +} /* 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 */ + +static const struct net_device_ops hysdn_netdev_ops = { + .ndo_open = net_open, + .ndo_stop = net_close, + .ndo_start_xmit = net_send_packet, + .ndo_set_mac_address = eth_mac_addr, + .ndo_validate_addr = eth_validate_addr, +}; + + +/*****************************************************************************/ +/* 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; + struct net_local *lp; + + if (!card) { + printk(KERN_WARNING "No card-pt in hysdn_net_create!\n"); + return (-ENOMEM); + } + hysdn_net_release(card); /* release an existing net device */ + + dev = alloc_etherdev(sizeof(struct net_local)); + if (!dev) { + printk(KERN_WARNING "HYSDN: unable to allocate mem\n"); + return (-ENOMEM); + } + + lp = netdev_priv(dev); + lp->dev = dev; + + dev->netdev_ops = &hysdn_netdev_ops; + 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->netdev_ops = &hysdn_netdev_ops; + if ((i = register_netdev(dev))) { + printk(KERN_WARNING "HYSDN: unable to create network device\n"); + free_netdev(dev); + return (i); + } + dev->ml_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 */ + net_close(dev); + + 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/staging/isdn/hysdn/hysdn_pof.h b/drivers/staging/isdn/hysdn/hysdn_pof.h new file mode 100644 index 000000000000..f63f5fa59d7e --- /dev/null +++ b/drivers/staging/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 */ unsigned long Magic __attribute__((packed)); + /*04 */ unsigned long N_PofRecs __attribute__((packed)); +/*08 */ +} tPofFileHdr; + +typedef struct PofRecHdr_tag { /* Pof record header */ + /*00 */ unsigned short PofRecId __attribute__((packed)); + /*02 */ unsigned long PofRecDataLen __attribute__((packed)); +/*06 */ +} tPofRecHdr; + +typedef struct PofTimeStamp_tag { + /*00 */ unsigned long UnixTime __attribute__((packed)); + /*04 */ unsigned char DateTimeText[0x28]; + /* =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/staging/isdn/hysdn/hysdn_procconf.c b/drivers/staging/isdn/hysdn/hysdn_procconf.c new file mode 100644 index 000000000000..73079213ec94 --- /dev/null +++ b/drivers/staging/isdn/hysdn/hysdn_procconf.c @@ -0,0 +1,411 @@ +/* $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/cred.h> +#include <linux/module.h> +#include <linux/poll.h> +#include <linux/proc_fs.h> +#include <linux/pci.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <net/net_namespace.h> + +#include "hysdn_defs.h" + +static DEFINE_MUTEX(hysdn_conf_mutex); + +#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 */ + unsigned char conf_line[CONF_LINE_LEN]; /* buffered conf line */ + unsigned short channel; /* active channel number */ + unsigned char *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) +{ + unsigned char *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; + unsigned char 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; + + if (!(file->f_mode & FMODE_READ)) + return -EPERM; /* no permission to read */ + + if (!(cp = file->private_data)) + return -EFAULT; /* should never happen */ + + return simple_read_from_buffer(buf, count, off, cp, strlen(cp)); +} /* hysdn_conf_read */ + +/******************/ +/* open conf file */ +/******************/ +static int +hysdn_conf_open(struct inode *ino, struct file *filep) +{ + hysdn_card *card; + struct conf_writedata *cnf; + char *cp, *tmp; + + /* now search the addressed card */ + mutex_lock(&hysdn_conf_mutex); + card = PDE_DATA(ino); + 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_cred->fsuid, filep->f_cred->fsgid, + 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))) { + mutex_unlock(&hysdn_conf_mutex); + 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 = kmalloc(INFO_OUT_LEN * 2 + 2, GFP_KERNEL))) { + mutex_unlock(&hysdn_conf_mutex); + 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 ! */ + mutex_unlock(&hysdn_conf_mutex); + return (-EPERM); /* no permission this time */ + } + mutex_unlock(&hysdn_conf_mutex); + 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; + + mutex_lock(&hysdn_conf_mutex); + card = PDE_DATA(ino); + 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_cred->fsuid, filep->f_cred->fsgid, + 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 */ + + kfree(filep->private_data); /* release memory */ + } + mutex_unlock(&hysdn_conf_mutex); + return (retval); +} /* hysdn_conf_close */ + +/******************************************************/ +/* table for conf filesystem functions defined above. */ +/******************************************************/ +static const struct file_operations conf_fops = +{ + .owner = THIS_MODULE, + .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; + unsigned char conf_name[20]; + + hysdn_proc_entry = proc_mkdir(PROC_SUBDIR_NAME, init_net.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 *) proc_create_data(conf_name, + S_IFREG | S_IRUGO | S_IWUSR, + hysdn_proc_entry, + &conf_fops, + card)) != NULL) { + hysdn_proclog_init(card); /* init the log file entry */ + } + card = card->next; /* next entry */ + } + + printk(KERN_NOTICE "HYSDN: procfs initialised\n"); + 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; + unsigned char 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, init_net.proc_net); +} diff --git a/drivers/staging/isdn/hysdn/hysdn_proclog.c b/drivers/staging/isdn/hysdn/hysdn_proclog.c new file mode 100644 index 000000000000..6e898b90e86e --- /dev/null +++ b/drivers/staging/isdn/hysdn/hysdn_proclog.c @@ -0,0 +1,357 @@ +/* $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/poll.h> +#include <linux/proc_fs.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/kernel.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; + +static DEFINE_MUTEX(hysdn_log_mutex); +static void put_log_buffer(hysdn_card *card, char *cp); + +/*************************************************/ +/* structure keeping ascii log for device output */ +/*************************************************/ +struct log_data { + struct log_data *next; + unsigned long 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 */ + unsigned char 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. */ +/********************************************/ +static void +put_log_buffer(hysdn_card *card, char *cp) +{ + struct log_data *ib; + struct procdata *pd = card->proclog; + 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 = 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 */ + spin_lock_irqsave(&card->hysdn_lock, flags); + 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 */ + + /* delete old entrys */ + 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 */ + + spin_unlock_irqrestore(&card->hysdn_lock, flags); + + 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) +{ + int rc; + hysdn_card *card = file->private_data; + + rc = kstrtoul_from_user(buf, count, 0, &card->debug_flags); + if (rc < 0) + return rc; + 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; + hysdn_card *card = PDE_DATA(file_inode(file)); + + if (!(inf = *((struct log_data **) file->private_data))) { + struct procdata *pd = card->proclog; + if (file->f_flags & O_NONBLOCK) + return (-EAGAIN); + + wait_event_interruptible(pd->rd_queue, (inf = + *((struct log_data **) file->private_data))); + } + if (!inf) + 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 = PDE_DATA(ino); + + mutex_lock(&hysdn_log_mutex); + if ((filep->f_mode & (FMODE_READ | FMODE_WRITE)) == FMODE_WRITE) { + /* write only access -> write log level only */ + filep->private_data = card; /* remember our own card */ + } else if ((filep->f_mode & (FMODE_READ | FMODE_WRITE)) == FMODE_READ) { + struct procdata *pd = card->proclog; + unsigned long flags; + + /* read access -> log/debug read */ + spin_lock_irqsave(&card->hysdn_lock, flags); + pd->if_used++; + if (pd->log_head) + filep->private_data = &pd->log_tail->next; + else + filep->private_data = &pd->log_head; + spin_unlock_irqrestore(&card->hysdn_lock, flags); + } else { /* simultaneous read/write access forbidden ! */ + mutex_unlock(&hysdn_log_mutex); + return (-EPERM); /* no permission this time */ + } + mutex_unlock(&hysdn_log_mutex); + 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; + + mutex_lock(&hysdn_log_mutex); + 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 */ + + 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 = PDE_DATA(file_inode(filep)); + 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; + } + + 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 */ + mutex_unlock(&hysdn_log_mutex); + + return (retval); +} /* hysdn_log_close */ + +/*************************************************/ +/* select/poll routine to be able using select() */ +/*************************************************/ +static __poll_t +hysdn_log_poll(struct file *file, poll_table *wait) +{ + __poll_t mask = 0; + hysdn_card *card = PDE_DATA(file_inode(file)); + struct procdata *pd = card->proclog; + + if ((file->f_mode & (FMODE_READ | FMODE_WRITE)) == FMODE_WRITE) + return (mask); /* no polling for write supported */ + + poll_wait(file, &(pd->rd_queue), wait); + + if (*((struct log_data **) file->private_data)) + mask |= EPOLLIN | EPOLLRDNORM; + + return mask; +} /* hysdn_log_poll */ + +/**************************************************/ +/* table for log filesystem functions defined above. */ +/**************************************************/ +static const struct file_operations log_fops = +{ + .owner = THIS_MODULE, + .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 = kzalloc(sizeof(struct procdata), GFP_KERNEL)) != NULL) { + sprintf(pd->log_name, "%s%d", PROC_LOG_BASENAME, card->myid); + pd->log = proc_create_data(pd->log_name, + S_IFREG | S_IRUGO | S_IWUSR, hysdn_proc_entry, + &log_fops, card); + + 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/staging/isdn/hysdn/hysdn_sched.c b/drivers/staging/isdn/hysdn/hysdn_sched.c new file mode 100644 index 000000000000..31d7c1415543 --- /dev/null +++ b/drivers/staging/isdn/hysdn/hysdn_sched.c @@ -0,0 +1,197 @@ +/* $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/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, unsigned char *buf, unsigned short len, + unsigned short 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, unsigned char *buf, + unsigned short volatile *len, unsigned short volatile *chan, + unsigned short 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) { + /* copy the packet to the buffer */ + skb_copy_from_linear_data(skb, buf, skb->len); + *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) { + skb_copy_from_linear_data(skb, buf, 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 because only some lines */ +/* are to be sent and this happens very seldom. */ +/*****************************************************************************/ +int +hysdn_tx_cfgline(hysdn_card *card, unsigned char *line, unsigned short chan) +{ + int cnt = 50; /* timeout intervalls */ + unsigned long flags; + + if (card->debug_flags & LOG_SCHED_ASYN) + hysdn_addlog(card, "async tx-cfg chan=%d len=%d", chan, strlen(line) + 1); + + while (card->async_busy) { + + if (card->debug_flags & LOG_SCHED_ASYN) + hysdn_addlog(card, "async tx-cfg delayed"); + + msleep_interruptible(20); /* Timeout 20ms */ + if (!--cnt) + return (-ERR_ASYNC_TIME); /* timed out */ + } /* wait for buffer to become free */ + + spin_lock_irqsave(&card->hysdn_lock, flags); + 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); + spin_unlock_irqrestore(&card->hysdn_lock, flags); + + if (card->debug_flags & LOG_SCHED_ASYN) + hysdn_addlog(card, "async tx-cfg data queued"); + + cnt++; /* short delay */ + + while (card->async_busy) { + + if (card->debug_flags & LOG_SCHED_ASYN) + hysdn_addlog(card, "async tx-cfg waiting for tx-ready"); + + msleep_interruptible(20); /* Timeout 20ms */ + if (!--cnt) + return (-ERR_ASYNC_TIME); /* timed out */ + } /* wait for buffer to become free again */ + + 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/staging/isdn/hysdn/ince1pc.h b/drivers/staging/isdn/hysdn/ince1pc.h new file mode 100644 index 000000000000..cab68361de65 --- /dev/null +++ b/drivers/staging/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 unsigned char + * 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 */ unsigned long ulErrType; + + /*04 */ unsigned long ulErrSubtype; + + /*08 */ unsigned char ucTextSize; + + /*09 */ unsigned char 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 */ unsigned char Len; + + /*01 */ volatile unsigned char RdPtr; + + /*02 */ unsigned char WrPtr; + + /*03 */ unsigned char 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__ */ |