aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/staging/isdn/gigaset
diff options
context:
space:
mode:
authorArnd Bergmann <arnd@arndb.de>2019-04-20 22:28:45 +0200
committerArnd Bergmann <arnd@arndb.de>2019-05-31 11:17:41 +0200
commit6d97985072dc270032dc7a08631080bfd6253e82 (patch)
treeb11f1ee9bcd819e63c84306032a5ff7c5ad90791 /drivers/staging/isdn/gigaset
parentisdn: hdlc: move into mISDN (diff)
downloadlinux-dev-6d97985072dc270032dc7a08631080bfd6253e82.tar.xz
linux-dev-6d97985072dc270032dc7a08631080bfd6253e82.zip
isdn: move capi drivers to staging
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 three drivers (avm, hysdn, gigaset) into drivers/staging/ just in case someone still uses them. 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. As Paul Bolle notes, he is still testing the gigaset driver as long as he can, but the Dutch ISDN network will be shut down in September 2019, which puts an end to that. Marcel Holtmann still maintains the Bluetooth CMTP profile and wants to keep that alive, so the actual CAPI subsystem code remains in place for now, after all other drivers are gone, CMTP and CAPI can be merged into a single driver directory. Cc: Marcel Holtmann <marcel@holtmann.org> Cc: Paul Bolle <pebolle@tiscali.nl> Signed-off-by: Arnd Bergmann <arnd@arndb.de>
Diffstat (limited to 'drivers/staging/isdn/gigaset')
-rw-r--r--drivers/staging/isdn/gigaset/Kconfig62
-rw-r--r--drivers/staging/isdn/gigaset/Makefile17
-rw-r--r--drivers/staging/isdn/gigaset/asyncdata.c609
-rw-r--r--drivers/staging/isdn/gigaset/bas-gigaset.c2675
-rw-r--r--drivers/staging/isdn/gigaset/capi.c2520
-rw-r--r--drivers/staging/isdn/gigaset/common.c1156
-rw-r--r--drivers/staging/isdn/gigaset/dummyll.c77
-rw-r--r--drivers/staging/isdn/gigaset/ev-layer.c1913
-rw-r--r--drivers/staging/isdn/gigaset/gigaset.h830
-rw-r--r--drivers/staging/isdn/gigaset/interface.c616
-rw-r--r--drivers/staging/isdn/gigaset/isocdata.c1009
-rw-r--r--drivers/staging/isdn/gigaset/proc.c80
-rw-r--r--drivers/staging/isdn/gigaset/ser-gigaset.c799
-rw-r--r--drivers/staging/isdn/gigaset/usb-gigaset.c949
14 files changed, 13312 insertions, 0 deletions
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, &parameter))
+ 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");