summaryrefslogtreecommitdiff
path: root/drivers/isdn/gigaset
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/isdn/gigaset')
-rw-r--r--drivers/isdn/gigaset/Kconfig42
-rw-r--r--drivers/isdn/gigaset/Makefile6
-rw-r--r--drivers/isdn/gigaset/asyncdata.c597
-rw-r--r--drivers/isdn/gigaset/bas-gigaset.c2365
-rw-r--r--drivers/isdn/gigaset/common.c1203
-rw-r--r--drivers/isdn/gigaset/ev-layer.c1983
-rw-r--r--drivers/isdn/gigaset/gigaset.h938
-rw-r--r--drivers/isdn/gigaset/i4l.c567
-rw-r--r--drivers/isdn/gigaset/interface.c718
-rw-r--r--drivers/isdn/gigaset/isocdata.c1009
-rw-r--r--drivers/isdn/gigaset/proc.c81
-rw-r--r--drivers/isdn/gigaset/usb-gigaset.c1008
12 files changed, 10517 insertions, 0 deletions
diff --git a/drivers/isdn/gigaset/Kconfig b/drivers/isdn/gigaset/Kconfig
new file mode 100644
index 000000000000..53c4fb62ed85
--- /dev/null
+++ b/drivers/isdn/gigaset/Kconfig
@@ -0,0 +1,42 @@
+menu "Siemens Gigaset"
+ depends on ISDN_I4L
+
+config ISDN_DRV_GIGASET
+ tristate "Siemens Gigaset support (isdn)"
+ depends on ISDN_I4L && m
+# depends on ISDN_I4L && MODULES
+ help
+ Say m here if you have a Gigaset or Sinus isdn device.
+
+if ISDN_DRV_GIGASET!=n
+
+config GIGASET_BASE
+ tristate "Gigaset base station support"
+ depends on ISDN_DRV_GIGASET && USB
+ help
+ Say m here if you need to communicate with the base
+ directly via USB.
+
+config GIGASET_M105
+ tristate "Gigaset M105 support"
+ depends on ISDN_DRV_GIGASET && USB
+ help
+ Say m here if you need the driver for the Gigaset M105 device.
+
+config GIGASET_DEBUG
+ bool "Gigaset debugging"
+ help
+ This enables debugging code in the Gigaset drivers.
+ If in doubt, say yes.
+
+config GIGASET_UNDOCREQ
+ bool "Support for undocumented USB requests"
+ help
+ This enables support for USB requests we only know from
+ reverse engineering (currently M105 only). If you need
+ features like configuration mode of M105, say yes. If you
+ care about your device, say no.
+
+endif
+
+endmenu
diff --git a/drivers/isdn/gigaset/Makefile b/drivers/isdn/gigaset/Makefile
new file mode 100644
index 000000000000..9b9acf1a21ad
--- /dev/null
+++ b/drivers/isdn/gigaset/Makefile
@@ -0,0 +1,6 @@
+gigaset-y := common.o interface.o proc.o ev-layer.o i4l.o
+usb_gigaset-y := usb-gigaset.o asyncdata.o
+bas_gigaset-y := bas-gigaset.o isocdata.o
+
+obj-$(CONFIG_GIGASET_M105) += usb_gigaset.o gigaset.o
+obj-$(CONFIG_GIGASET_BASE) += bas_gigaset.o gigaset.o
diff --git a/drivers/isdn/gigaset/asyncdata.c b/drivers/isdn/gigaset/asyncdata.c
new file mode 100644
index 000000000000..171f8b703d61
--- /dev/null
+++ b/drivers/isdn/gigaset/asyncdata.c
@@ -0,0 +1,597 @@
+/*
+ * 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 <Eilers.Stefan@epost.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.
+ * =====================================================================
+ * ToDo: ...
+ * =====================================================================
+ * Version: $Id: asyncdata.c,v 1.2.2.7 2005/11/13 23:05:18 hjlipp Exp $
+ * =====================================================================
+ */
+
+#include "gigaset.h"
+#include <linux/crc-ccitt.h>
+
+//#define GIG_M10x_STUFF_VOICE_DATA
+
+/* check if byte must be stuffed/escaped
+ * I'm not sure which data should be encoded.
+ * Therefore I will go the hard way and decode 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 (modem response)
+ * Return value:
+ * number of processed bytes
+ */
+static inline int cmd_loop(unsigned char c, unsigned char *src, int numbytes,
+ struct inbuf_t *inbuf)
+{
+ struct cardstate *cs = inbuf->cs;
+ unsigned cbytes = cs->cbytes;
+ int inputstate = inbuf->inputstate;
+ int startbytes = numbytes;
+
+ for (;;) {
+ cs->respdata[cbytes] = c;
+ if (c == 10 || c == 13) {
+ dbg(DEBUG_TRANSCMD, "%s: End of Command (%d Bytes)",
+ __func__, cbytes);
+ cs->cbytes = cbytes;
+ gigaset_handle_modem_response(cs); /* can change cs->dle */
+ cbytes = 0;
+
+ if (cs->dle &&
+ !(inputstate & INS_DLE_command)) {
+ inputstate &= ~INS_command;
+ break;
+ }
+ } else {
+ /* advance in line buffer, checking for overflow */
+ if (cbytes < MAX_RESP_SIZE - 1)
+ cbytes++;
+ else
+ warn("response too large");
+ }
+
+ if (!numbytes)
+ break;
+ c = *src++;
+ --numbytes;
+ if (c == DLE_FLAG &&
+ (cs->dle || inputstate & INS_DLE_command)) {
+ inputstate |= INS_DLE_char;
+ break;
+ }
+ }
+
+ cs->cbytes = cbytes;
+ inbuf->inputstate = inputstate;
+
+ return startbytes - numbytes;
+}
+
+/* process a block of received bytes in lock mode (tty i/f)
+ * Return value:
+ * number of processed bytes
+ */
+static inline int lock_loop(unsigned char *src, int numbytes,
+ struct inbuf_t *inbuf)
+{
+ struct cardstate *cs = inbuf->cs;
+
+ gigaset_dbg_buffer(DEBUG_LOCKCMD, "received response", numbytes, src, 0);
+ gigaset_if_receive(cs, src, numbytes);
+
+ return numbytes;
+}
+
+/* process a block of received bytes in HDLC data mode
+ * 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
+ * numbytes (all bytes processed) on error --FIXME
+ */
+static inline int hdlc_loop(unsigned char c, unsigned char *src, int numbytes,
+ struct inbuf_t *inbuf)
+{
+ struct cardstate *cs = inbuf->cs;
+ struct bc_state *bcs = inbuf->bcs;
+ int inputstate;
+ __u16 fcs;
+ struct sk_buff *skb;
+ unsigned char error;
+ struct sk_buff *compskb;
+ int startbytes = numbytes;
+ int l;
+
+ IFNULLRETVAL(bcs, numbytes);
+ inputstate = bcs->inputstate;
+ fcs = bcs->fcs;
+ skb = bcs->skb;
+ IFNULLRETVAL(skb, numbytes);
+
+ if (unlikely(inputstate & INS_byte_stuff)) {
+ inputstate &= ~INS_byte_stuff;
+ goto byte_stuff;
+ }
+ for (;;) {
+ if (unlikely(c == PPP_ESCAPE)) {
+ if (unlikely(!numbytes)) {
+ inputstate |= INS_byte_stuff;
+ break;
+ }
+ c = *src++;
+ --numbytes;
+ if (unlikely(c == DLE_FLAG &&
+ (cs->dle ||
+ inbuf->inputstate & INS_DLE_command))) {
+ inbuf->inputstate |= INS_DLE_char;
+ inputstate |= INS_byte_stuff;
+ break;
+ }
+byte_stuff:
+ c ^= PPP_TRANS;
+#ifdef CONFIG_GIGASET_DEBUG
+ if (unlikely(!muststuff(c)))
+ dbg(DEBUG_HDLC,
+ "byte stuffed: 0x%02x", c);
+#endif
+ } else if (unlikely(c == PPP_FLAG)) {
+ if (unlikely(inputstate & INS_skip_frame)) {
+ if (!(inputstate & INS_have_data)) { /* 7E 7E */
+ //dbg(DEBUG_HDLC, "(7e)7e------------------------");
+#ifdef CONFIG_GIGASET_DEBUG
+ ++bcs->emptycount;
+#endif
+ } else
+ dbg(DEBUG_HDLC,
+ "7e----------------------------");
+
+ /* end of frame */
+ error = 1;
+ gigaset_rcv_error(NULL, cs, bcs);
+ } else if (!(inputstate & INS_have_data)) { /* 7E 7E */
+ //dbg(DEBUG_HDLC, "(7e)7e------------------------");
+#ifdef CONFIG_GIGASET_DEBUG
+ ++bcs->emptycount;
+#endif
+ break;
+ } else {
+ dbg(DEBUG_HDLC,
+ "7e----------------------------");
+
+ /* end of frame */
+ error = 0;
+
+ if (unlikely(fcs != PPP_GOODFCS)) {
+ err("Packet checksum at %lu failed, "
+ "packet is corrupted (%u bytes)!",
+ bcs->rcvbytes, skb->len);
+ compskb = NULL;
+ gigaset_rcv_error(compskb, cs, bcs);
+ error = 1;
+ } else {
+ if (likely((l = skb->len) > 2)) {
+ skb->tail -= 2;
+ skb->len -= 2;
+ } else {
+ dev_kfree_skb(skb);
+ skb = NULL;
+ inputstate |= INS_skip_frame;
+ if (l == 1) {
+ err("invalid packet size (1)!");
+ error = 1;
+ gigaset_rcv_error(NULL, cs, bcs);
+ }
+ }
+ if (likely(!(error ||
+ (inputstate &
+ INS_skip_frame)))) {
+ gigaset_rcv_skb(skb, cs, bcs);
+ }
+ }
+ }
+
+ if (unlikely(error))
+ if (skb)
+ dev_kfree_skb(skb);
+
+ fcs = PPP_INITFCS;
+ inputstate &= ~(INS_have_data | INS_skip_frame);
+ if (unlikely(bcs->ignore)) {
+ inputstate |= INS_skip_frame;
+ skb = NULL;
+ } else if (likely((skb = dev_alloc_skb(SBUFSIZE + HW_HDR_LEN)) != NULL)) {
+ skb_reserve(skb, HW_HDR_LEN);
+ } else {
+ warn("could not allocate new skb");
+ inputstate |= INS_skip_frame;
+ }
+
+ break;
+#ifdef CONFIG_GIGASET_DEBUG
+ } else if (unlikely(muststuff(c))) {
+ /* Should not happen. Possible after ZDLE=1<CR><LF>. */
+ dbg(DEBUG_HDLC, "not byte stuffed: 0x%02x", c);
+#endif
+ }
+
+ /* add character */
+
+#ifdef CONFIG_GIGASET_DEBUG
+ if (unlikely(!(inputstate & INS_have_data))) {
+ dbg(DEBUG_HDLC,
+ "7e (%d x) ================", bcs->emptycount);
+ bcs->emptycount = 0;
+ }
+#endif
+
+ inputstate |= INS_have_data;
+
+ if (likely(!(inputstate & INS_skip_frame))) {
+ if (unlikely(skb->len == SBUFSIZE)) {
+ warn("received packet too long");
+ dev_kfree_skb_any(skb);
+ skb = NULL;
+ inputstate |= INS_skip_frame;
+ break;
+ }
+ *gigaset_skb_put_quick(skb, 1) = c;
+ /* *__skb_put (skb, 1) = c; */
+ fcs = crc_ccitt_byte(fcs, c);
+ }
+
+ if (unlikely(!numbytes))
+ break;
+ c = *src++;
+ --numbytes;
+ if (unlikely(c == DLE_FLAG &&
+ (cs->dle ||
+ inbuf->inputstate & INS_DLE_command))) {
+ inbuf->inputstate |= INS_DLE_char;
+ break;
+ }
+ }
+ bcs->inputstate = inputstate;
+ bcs->fcs = fcs;
+ bcs->skb = skb;
+ return startbytes - numbytes;
+}
+
+/* process a block of received bytes in transparent data mode
+ * 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
+ * numbytes (all bytes processed) on error --FIXME
+ */
+static inline int iraw_loop(unsigned char c, unsigned char *src, int numbytes,
+ struct inbuf_t *inbuf)
+{
+ struct cardstate *cs = inbuf->cs;
+ struct bc_state *bcs = inbuf->bcs;
+ int inputstate;
+ struct sk_buff *skb;
+ int startbytes = numbytes;
+
+ IFNULLRETVAL(bcs, numbytes);
+ inputstate = bcs->inputstate;
+ skb = bcs->skb;
+ IFNULLRETVAL(skb, numbytes);
+
+ for (;;) {
+ /* add character */
+ inputstate |= INS_have_data;
+
+ if (likely(!(inputstate & INS_skip_frame))) {
+ if (unlikely(skb->len == SBUFSIZE)) {
+ //FIXME just pass skb up and allocate a new one
+ warn("received packet too long");
+ dev_kfree_skb_any(skb);
+ skb = NULL;
+ inputstate |= INS_skip_frame;
+ break;
+ }
+ *gigaset_skb_put_quick(skb, 1) = gigaset_invtab[c];
+ }
+
+ if (unlikely(!numbytes))
+ break;
+ c = *src++;
+ --numbytes;
+ if (unlikely(c == DLE_FLAG &&
+ (cs->dle ||
+ inbuf->inputstate & INS_DLE_command))) {
+ inbuf->inputstate |= INS_DLE_char;
+ break;
+ }
+ }
+
+ /* pass data up */
+ if (likely(inputstate & INS_have_data)) {
+ if (likely(!(inputstate & INS_skip_frame))) {
+ gigaset_rcv_skb(skb, cs, bcs);
+ }
+ inputstate &= ~(INS_have_data | INS_skip_frame);
+ if (unlikely(bcs->ignore)) {
+ inputstate |= INS_skip_frame;
+ skb = NULL;
+ } else if (likely((skb = dev_alloc_skb(SBUFSIZE + HW_HDR_LEN))
+ != NULL)) {
+ skb_reserve(skb, HW_HDR_LEN);
+ } else {
+ warn("could not allocate new skb");
+ inputstate |= INS_skip_frame;
+ }
+ }
+
+ bcs->inputstate = inputstate;
+ bcs->skb = skb;
+ return startbytes - numbytes;
+}
+
+/* process a block of data received from the device
+ */
+void gigaset_m10x_input(struct inbuf_t *inbuf)
+{
+ struct cardstate *cs;
+ unsigned tail, head, numbytes;
+ unsigned char *src, c;
+ int procbytes;
+
+ head = atomic_read(&inbuf->head);
+ tail = atomic_read(&inbuf->tail);
+ dbg(DEBUG_INTR, "buffer state: %u -> %u", head, tail);
+
+ if (head != tail) {
+ cs = inbuf->cs;
+ src = inbuf->data + head;
+ numbytes = (head > tail ? RBUFSIZE : tail) - head;
+ dbg(DEBUG_INTR, "processing %u bytes", numbytes);
+
+ while (numbytes) {
+ if (atomic_read(&cs->mstate) == MS_LOCKED) {
+ procbytes = lock_loop(src, numbytes, inbuf);
+ src += procbytes;
+ numbytes -= procbytes;
+ } else {
+ c = *src++;
+ --numbytes;
+ if (c == DLE_FLAG && (cs->dle ||
+ inbuf->inputstate & INS_DLE_command)) {
+ if (!(inbuf->inputstate & INS_DLE_char)) {
+ inbuf->inputstate |= INS_DLE_char;
+ goto nextbyte;
+ }
+ /* <DLE> <DLE> => <DLE> in data stream */
+ inbuf->inputstate &= ~INS_DLE_char;
+ }
+
+ if (!(inbuf->inputstate & INS_DLE_char)) {
+
+ /* FIXME Einfach je nach Modus Funktionszeiger in cs setzen [hier+hdlc_loop]? */
+ /* FIXME Spart folgendes "if" und ermoeglicht andere Protokolle */
+ if (inbuf->inputstate & INS_command)
+ procbytes = cmd_loop(c, src, numbytes, inbuf);
+ else if (inbuf->bcs->proto2 == ISDN_PROTO_L2_HDLC)
+ procbytes = hdlc_loop(c, src, numbytes, inbuf);
+ else
+ procbytes = iraw_loop(c, src, numbytes, inbuf);
+
+ src += procbytes;
+ numbytes -= procbytes;
+ } else { /* DLE-char */
+ inbuf->inputstate &= ~INS_DLE_char;
+ switch (c) {
+ case 'X': /*begin of command*/
+#ifdef CONFIG_GIGASET_DEBUG
+ if (inbuf->inputstate & INS_command)
+ err("received <DLE> 'X' in command mode");
+#endif
+ inbuf->inputstate |=
+ INS_command | INS_DLE_command;
+ break;
+ case '.': /*end of command*/
+#ifdef CONFIG_GIGASET_DEBUG
+ if (!(inbuf->inputstate & INS_command))
+ err("received <DLE> '.' in hdlc mode");
+#endif
+ inbuf->inputstate &= cs->dle ?
+ ~(INS_DLE_command|INS_command)
+ : ~INS_DLE_command;
+ break;
+ //case DLE_FLAG: /*DLE_FLAG in data stream*/ /* schon oben behandelt! */
+ default:
+ err("received 0x10 0x%02x!", (int) c);
+ /* FIXME: reset driver?? */
+ }
+ }
+ }
+nextbyte:
+ if (!numbytes) {
+ /* end of buffer, check for wrap */
+ if (head > tail) {
+ head = 0;
+ src = inbuf->data;
+ numbytes = tail;
+ } else {
+ head = tail;
+ break;
+ }
+ }
+ }
+
+ dbg(DEBUG_INTR, "setting head to %u", head);
+ atomic_set(&inbuf->head, head);
+ }
+}
+
+
+/* == data output ========================================================== */
+
+/* Encoding of a PPP packet into an octet stuffed HDLC frame
+ * with FCS, opening and closing flags.
+ * parameters:
+ * skb skb containing original packet (freed upon return)
+ * head number of headroom bytes to allocate in result skb
+ * tail number of tailroom bytes to allocate in result skb
+ * Return value:
+ * pointer to newly allocated skb containing the result frame
+ */
+static struct sk_buff *HDLC_Encode(struct sk_buff *skb, int head, int tail)
+{
+ 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
+ */
+ hdlc_skb = dev_alloc_skb(skb->len + stuf_cnt + 6 + tail + head);
+ if (!hdlc_skb) {
+ err("unable to allocate memory for HDLC encoding!");
+ dev_kfree_skb(skb);
+ return NULL;
+ }
+ skb_reserve(hdlc_skb, head);
+
+ /* Copy acknowledge request into new skb */
+ memcpy(hdlc_skb->head, skb->head, 2);
+
+ /* Add flag sequence in front of everything.. */
+ *(skb_put(hdlc_skb, 1)) = PPP_FLAG;
+
+ /* Perform byte stuffing while copying data. */
+ while (skb->len--) {
+ if (muststuff(*skb->data)) {
+ *(skb_put(hdlc_skb, 1)) = PPP_ESCAPE;
+ *(skb_put(hdlc_skb, 1)) = (*skb->data++) ^ PPP_TRANS;
+ } else
+ *(skb_put(hdlc_skb, 1)) = *skb->data++;
+ }
+
+ /* Finally add FCS (byte stuffed) and flag sequence */
+ c = (fcs & 0x00ff); /* least significant byte first */
+ if (muststuff(c)) {
+ *(skb_put(hdlc_skb, 1)) = PPP_ESCAPE;
+ c ^= PPP_TRANS;
+ }
+ *(skb_put(hdlc_skb, 1)) = c;
+
+ c = ((fcs >> 8) & 0x00ff);
+ if (muststuff(c)) {
+ *(skb_put(hdlc_skb, 1)) = PPP_ESCAPE;
+ c ^= PPP_TRANS;
+ }
+ *(skb_put(hdlc_skb, 1)) = c;
+
+ *(skb_put(hdlc_skb, 1)) = PPP_FLAG;
+
+ dev_kfree_skb(skb);
+ return hdlc_skb;
+}
+
+/* Encoding of a raw packet into an octet stuffed bit inverted frame
+ * parameters:
+ * skb skb containing original packet (freed upon return)
+ * head number of headroom bytes to allocate in result skb
+ * tail number of tailroom bytes to allocate in result skb
+ * Return value:
+ * pointer to newly allocated skb containing the result frame
+ */
+static struct sk_buff *iraw_encode(struct sk_buff *skb, int head, int tail)
+{
+ struct sk_buff *iraw_skb;
+ unsigned char c;
+ unsigned char *cp;
+ int len;
+
+ /* worst case: every byte must be stuffed */
+ iraw_skb = dev_alloc_skb(2*skb->len + tail + head);
+ if (!iraw_skb) {
+ err("unable to allocate memory for HDLC encoding!");
+ dev_kfree_skb(skb);
+ return NULL;
+ }
+ skb_reserve(iraw_skb, head);
+
+ cp = skb->data;
+ len = skb->len;
+ while (len--) {
+ c = gigaset_invtab[*cp++];
+ if (c == DLE_FLAG)
+ *(skb_put(iraw_skb, 1)) = c;
+ *(skb_put(iraw_skb, 1)) = c;
+ }
+ dev_kfree_skb(skb);
+ return iraw_skb;
+}
+
+/* gigaset_send_skb
+ * called by common.c to queue an skb for sending
+ * and start transmission if necessary
+ * parameters:
+ * B Channel control structure
+ * skb
+ * Return value:
+ * number of bytes accepted for sending
+ * (skb->len if ok, 0 if out of buffer space)
+ * or error code (< 0, eg. -EINVAL)
+ */
+int gigaset_m10x_send_skb(struct bc_state *bcs, struct sk_buff *skb)
+{
+ unsigned len;
+
+ IFNULLRETVAL(bcs, -EFAULT);
+ IFNULLRETVAL(skb, -EFAULT);
+ len = skb->len;
+
+ if (bcs->proto2 == ISDN_PROTO_L2_HDLC)
+ skb = HDLC_Encode(skb, HW_HDR_LEN, 0);
+ else
+ skb = iraw_encode(skb, HW_HDR_LEN, 0);
+ if (!skb)
+ return -ENOMEM;
+
+ skb_queue_tail(&bcs->squeue, skb);
+ tasklet_schedule(&bcs->cs->write_tasklet);
+
+ return len; /* ok so far */
+}
diff --git a/drivers/isdn/gigaset/bas-gigaset.c b/drivers/isdn/gigaset/bas-gigaset.c
new file mode 100644
index 000000000000..31f0f07832bc
--- /dev/null
+++ b/drivers/isdn/gigaset/bas-gigaset.c
@@ -0,0 +1,2365 @@
+/*
+ * 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 <Eilers.Stefan@epost.de>.
+ *
+ * Based on usb-gigaset.c.
+ *
+ * =====================================================================
+ * 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.
+ * =====================================================================
+ * ToDo: ...
+ * =====================================================================
+ * Version: $Id: bas-gigaset.c,v 1.52.4.19 2006/02/04 18:28:16 hjlipp Exp $
+ * =====================================================================
+ */
+
+#include "gigaset.h"
+
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/timer.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 <Eilers.Stefan@epost.de>"
+#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_DEVFSNAME "gig/bas/"
+#define GIGASET_DEVNAME "ttyGB"
+
+#define IF_WRITEBUF 256 //FIXME
+
+/* Values for the Gigaset 307x */
+#define USB_GIGA_VENDOR_ID 0x0681
+#define USB_GIGA_PRODUCT_ID 0x0001
+#define USB_4175_PRODUCT_ID 0x0002
+#define USB_SX303_PRODUCT_ID 0x0021
+#define USB_SX353_PRODUCT_ID 0x0022
+
+/* table of devices that work with this driver */
+static struct usb_device_id gigaset_table [] = {
+ { USB_DEVICE(USB_GIGA_VENDOR_ID, USB_GIGA_PRODUCT_ID) },
+ { USB_DEVICE(USB_GIGA_VENDOR_ID, USB_4175_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);
+
+/* Get a minor range for your devices from the usb maintainer */
+#define USB_SKEL_MINOR_BASE 200
+
+/*======================= local function prototypes =============================*/
+
+/* This function is called if a new device is connected to the USB port. It
+ * checks whether this new device belongs to this driver.
+ */
+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);
+
+
+/*==============================================================================*/
+
+struct bas_cardstate {
+ struct usb_device *udev; /* USB device pointer */
+ 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 */
+
+ 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[3];
+
+ spinlock_t lock; /* locks all following */
+ atomic_t basstate; /* bitmap (BS_*) */
+ int pending; /* uncompleted base request */
+ 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
+#define BS_B1OPEN 0x002
+#define BS_B2OPEN 0x004
+#define BS_ATREADY 0x008
+#define BS_INIT 0x010
+#define BS_ATTIMER 0x020
+
+
+static struct gigaset_driver *driver = NULL;
+static struct cardstate *cardstate = NULL;
+
+/* 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,
+};
+
+/* 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 "canceled";
+ case -ECONNRESET:
+ return "canceled (async)";
+ case -EINPROGRESS:
+ return "pending";
+ case -EPROTO:
+ return "bit stuffing or unknown USB error";
+ case -EILSEQ:
+ return "Illegal byte sequence (CRC mismatch)";
+ case -EPIPE:
+ return "babble detect or endpoint stalled";
+ case -ENOSR:
+ return "buffer error";
+ case -ETIMEDOUT:
+ return "timed out";
+ case -ENODEV:
+ return "device not present";
+ case -EREMOTEIO:
+ return "short packet detected";
+ case -EXDEV:
+ return "partial isochronous transfer";
+ case -EINVAL:
+ return "invalid argument";
+ case -ENXIO:
+ return "URB already queued";
+ case -EAGAIN:
+ return "isochronous start frame too early or too much scheduled";
+ case -EFBIG:
+ return "too many isochronous frames requested";
+ case -EMSGSIZE:
+ return "endpoint message size zero";
+ case -ESHUTDOWN:
+ return "endpoint shutdown";
+ case -EBUSY:
+ return "another request pending";
+ default:
+ snprintf(unkmsg, sizeof(unkmsg), "unknown error %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;
+ IFNULLRET(tag);
+ dbg(level, "%s urb(0x%08lx)->{", tag, (unsigned long) urb);
+ if (urb) {
+ dbg(level,
+ " dev=0x%08lx, pipe=%s:EP%d/DV%d:%s, "
+ "status=%d, 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",
+ urb->status, (unsigned long) urb->hcpriv,
+ urb->transfer_flags);
+ dbg(level,
+ " transfer_buffer=0x%08lx[%d], actual_length=%d, "
+ "bandwidth=%d, setup_packet=0x%08lx,",
+ (unsigned long) urb->transfer_buffer,
+ urb->transfer_buffer_length, urb->actual_length,
+ urb->bandwidth, (unsigned long) urb->setup_packet);
+ 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);
+ 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];
+ dbg(level,
+ " {offset=%u, length=%u, actual_length=%u, "
+ "status=%u}",
+ pifd->offset, pifd->length, pifd->actual_length,
+ pifd->status);
+ }
+ }
+ 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;
+}
+
+/* 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.
+ * argument:
+ * B channel control structure
+ */
+static inline void error_hangup(struct bc_state *bcs)
+{
+ struct cardstate *cs = bcs->cs;
+
+ dbg(DEBUG_ANY,
+ "%s: scheduling HUP for channel %d", __func__, bcs->channel);
+
+ if (!gigaset_add_event(cs, &bcs->at_state, EV_HUP, NULL, 0, NULL)) {
+ //FIXME what should we do?
+ return;
+ }
+
+ 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.
+ * argument:
+ * controller state structure
+ */
+static inline void error_reset(struct cardstate *cs)
+{
+ //FIXME try to recover without bothering the user
+ err("unrecoverable error - please disconnect the Gigaset base to reset");
+}
+
+/* check_pending
+ * check for completion of pending control request
+ * parameter:
+ * urb USB request block of completed request
+ * urb->context = hardware specific controller state structure
+ */
+static void check_pending(struct bas_cardstate *ucs)
+{
+ unsigned long flags;
+
+ IFNULLRET(ucs);
+ IFNULLRET(cardstate);
+
+ spin_lock_irqsave(&ucs->lock, flags);
+ switch (ucs->pending) {
+ case 0:
+ break;
+ case HD_OPEN_ATCHANNEL:
+ if (atomic_read(&ucs->basstate) & BS_ATOPEN)
+ ucs->pending = 0;
+ break;
+ case HD_OPEN_B1CHANNEL:
+ if (atomic_read(&ucs->basstate) & BS_B1OPEN)
+ ucs->pending = 0;
+ break;
+ case HD_OPEN_B2CHANNEL:
+ if (atomic_read(&ucs->basstate) & BS_B2OPEN)
+ ucs->pending = 0;
+ break;
+ case HD_CLOSE_ATCHANNEL:
+ if (!(atomic_read(&ucs->basstate) & BS_ATOPEN))
+ ucs->pending = 0;
+ //wake_up_interruptible(cs->initwait);
+ //FIXME need own wait queue?
+ break;
+ case HD_CLOSE_B1CHANNEL:
+ if (!(atomic_read(&ucs->basstate) & BS_B1OPEN))
+ ucs->pending = 0;
+ break;
+ case HD_CLOSE_B2CHANNEL:
+ if (!(atomic_read(&ucs->basstate) & BS_B2OPEN))
+ ucs->pending = 0;
+ break;
+ case HD_DEVICE_INIT_ACK: /* no reply expected */
+ ucs->pending = 0;
+ break;
+ /* HD_READ_ATMESSAGE, HD_WRITE_ATMESSAGE, HD_RESET_INTERRUPTPIPE
+ * are handled separately and should never end up here
+ */
+ default:
+ warn("unknown pending request 0x%02x cleared", 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(unsigned long data)
+{
+ struct cardstate *cs = (struct cardstate *) data;
+ struct bas_cardstate *ucs;
+ unsigned long flags;
+
+ IFNULLRET(cs);
+ ucs = cs->hw.bas;
+ IFNULLRET(ucs);
+
+ spin_lock_irqsave(&cs->lock, flags);
+ if (!atomic_read(&cs->connected)) {
+ dbg(DEBUG_USBREQ, "%s: disconnected", __func__);
+ spin_unlock_irqrestore(&cs->lock, flags);
+ return;
+ }
+ if (!ucs->rcvbuf_size) {
+ dbg(DEBUG_USBREQ, "%s: no receive in progress", __func__);
+ spin_unlock_irqrestore(&cs->lock, flags);
+ return;
+ }
+ spin_unlock_irqrestore(&cs->lock, flags);
+
+ err("timeout reading AT response");
+ error_reset(cs); //FIXME retry?
+}
+
+
+static void read_ctrl_callback(struct urb *urb, struct pt_regs *regs);
+
+/* atread_submit
+ * submit an HD_READ_ATMESSAGE command URB
+ * parameters:
+ * cs controller state structure
+ * timeout timeout in 1/10 sec., 0: none
+ * return value:
+ * 0 on success
+ * -EINVAL if a NULL pointer is encountered somewhere
+ * -EBUSY if another request is pending
+ * any URB submission error code
+ */
+static int atread_submit(struct cardstate *cs, int timeout)
+{
+ struct bas_cardstate *ucs;
+ int ret;
+
+ IFNULLRETVAL(cs, -EINVAL);
+ ucs = cs->hw.bas;
+ IFNULLRETVAL(ucs, -EINVAL);
+ IFNULLRETVAL(ucs->urb_cmd_in, -EINVAL);
+
+ dbg(DEBUG_USBREQ, "-------> HD_READ_ATMESSAGE (%d)", ucs->rcvbuf_size);
+
+ if (ucs->urb_cmd_in->status == -EINPROGRESS) {
+ err("could not submit HD_READ_ATMESSAGE: URB busy");
+ return -EBUSY;
+ }
+
+ 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);
+
+ if ((ret = usb_submit_urb(ucs->urb_cmd_in, SLAB_ATOMIC)) != 0) {
+ err("could not submit HD_READ_ATMESSAGE: %s",
+ get_usb_statmsg(ret));
+ return ret;
+ }
+
+ if (timeout > 0) {
+ dbg(DEBUG_USBREQ, "setting timeout of %d/10 secs", timeout);
+ ucs->timer_cmd_in.expires = jiffies + timeout * HZ / 10;
+ ucs->timer_cmd_in.data = (unsigned long) cs;
+ ucs->timer_cmd_in.function = cmd_in_timeout;
+ add_timer(&ucs->timer_cmd_in);
+ }
+ return 0;
+}
+
+static void stopurbs(struct bas_bc_state *);
+static int start_cbsend(struct cardstate *);
+
+/* set/clear bits in base connection state
+ */
+inline static void update_basstate(struct bas_cardstate *ucs,
+ int set, int clear)
+{
+ unsigned long flags;
+ int state;
+
+ spin_lock_irqsave(&ucs->lock, flags);
+ state = atomic_read(&ucs->basstate);
+ state &= ~clear;
+ state |= set;
+ atomic_set(&ucs->basstate, state);
+ spin_unlock_irqrestore(&ucs->lock, flags);
+}
+
+
+/* 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 pt_regs *regs)
+{
+ struct cardstate *cs;
+ struct bas_cardstate *ucs;
+ struct bc_state *bcs;
+ unsigned long flags;
+ int status;
+ unsigned l;
+ int channel;
+
+ IFNULLRET(urb);
+ cs = (struct cardstate *) urb->context;
+ IFNULLRET(cs);
+ ucs = cs->hw.bas;
+ IFNULLRET(ucs);
+
+ if (unlikely(!atomic_read(&cs->connected))) {
+ warn("%s: disconnected", __func__);
+ return;
+ }
+
+ switch (urb->status) {
+ case 0: /* success */
+ break;
+ case -ENOENT: /* canceled */
+ case -ECONNRESET: /* canceled (async) */
+ case -EINPROGRESS: /* pending */
+ /* ignore silently */
+ dbg(DEBUG_USBREQ,
+ "%s: %s", __func__, get_usb_statmsg(urb->status));
+ return;
+ default: /* severe trouble */
+ warn("interrupt read: %s", get_usb_statmsg(urb->status));
+ //FIXME corrective action? resubmission always ok?
+ goto resubmit;
+ }
+
+ l = (unsigned) ucs->int_in_buf[1] +
+ (((unsigned) ucs->int_in_buf[2]) << 8);
+
+ 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;
+ 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;
+ 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;
+ case HD_B1_FLOW_CONTROL:
+ bcs = cs->bcs + channel;
+ atomic_add((l - BAS_NORMFRAME) * BAS_CORRFRAMES,
+ &bcs->hw.bas->corrbytes);
+ 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) {
+ warn("HD_RECEIVEATDATA_ACK with length 0 ignored");
+ break;
+ }
+ spin_lock_irqsave(&cs->lock, flags);
+ if (ucs->rcvbuf_size) {
+ spin_unlock_irqrestore(&cs->lock, flags);
+ err("receive AT data overrun, %d bytes lost", l);
+ error_reset(cs); //FIXME reschedule
+ break;
+ }
+ if ((ucs->rcvbuf = kmalloc(l, GFP_ATOMIC)) == NULL) {
+ spin_unlock_irqrestore(&cs->lock, flags);
+ err("%s: out of memory, %d bytes lost", __func__, l);
+ error_reset(cs); //FIXME reschedule
+ break;
+ }
+ ucs->rcvbuf_size = l;
+ ucs->retry_cmd_in = 0;
+ if ((status = atread_submit(cs, BAS_TIMEOUT)) < 0) {
+ kfree(ucs->rcvbuf);
+ ucs->rcvbuf = NULL;
+ ucs->rcvbuf_size = 0;
+ error_reset(cs); //FIXME reschedule
+ }
+ spin_unlock_irqrestore(&cs->lock, flags);
+ break;
+
+ case HD_RESET_INTERRUPT_PIPE_ACK:
+ dbg(DEBUG_USBREQ, "HD_RESET_INTERRUPT_PIPE_ACK");
+ break;
+
+ case HD_SUSPEND_END:
+ dbg(DEBUG_USBREQ, "HD_SUSPEND_END");
+ break;
+
+ default:
+ warn("unknown Gigaset signal 0x%02x (%u) ignored",
+ (int) ucs->int_in_buf[0], l);
+ }
+
+ check_pending(ucs);
+
+resubmit:
+ status = usb_submit_urb(urb, SLAB_ATOMIC);
+ if (unlikely(status)) {
+ err("could not resubmit interrupt URB: %s",
+ get_usb_statmsg(status));
+ 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 pt_regs *regs)
+{
+ struct cardstate *cs;
+ struct bas_cardstate *ucs;
+ unsigned numbytes;
+ unsigned long flags;
+ struct inbuf_t *inbuf;
+ int have_data = 0;
+
+ IFNULLRET(urb);
+ inbuf = (struct inbuf_t *) urb->context;
+ IFNULLRET(inbuf);
+ cs = inbuf->cs;
+ IFNULLRET(cs);
+ ucs = cs->hw.bas;
+ IFNULLRET(ucs);
+
+ spin_lock_irqsave(&cs->lock, flags);
+ if (!atomic_read(&cs->connected)) {
+ warn("%s: disconnected", __func__);
+ spin_unlock_irqrestore(&cs->lock, flags);
+ return;
+ }
+
+ if (!ucs->rcvbuf_size) {
+ warn("%s: no receive in progress", __func__);
+ spin_unlock_irqrestore(&cs->lock, flags);
+ return;
+ }
+
+ del_timer(&ucs->timer_cmd_in);
+
+ switch (urb->status) {
+ case 0: /* normal completion */
+ numbytes = urb->actual_length;
+ if (unlikely(numbytes == 0)) {
+ warn("control read: empty block received");
+ goto retry;
+ }
+ if (unlikely(numbytes != ucs->rcvbuf_size)) {
+ warn("control read: received %d chars, expected %d",
+ numbytes, ucs->rcvbuf_size);
+ if (numbytes > ucs->rcvbuf_size)
+ numbytes = ucs->rcvbuf_size;
+ }
+
+ /* copy received bytes to inbuf */
+ have_data = gigaset_fill_inbuf(inbuf, ucs->rcvbuf, numbytes);
+
+ if (unlikely(numbytes < ucs->rcvbuf_size)) {
+ /* incomplete - resubmit for remaining bytes */
+ ucs->rcvbuf_size -= numbytes;
+ ucs->retry_cmd_in = 0;
+ goto retry;
+ }
+ break;
+
+ case -ENOENT: /* canceled */
+ case -ECONNRESET: /* canceled (async) */
+ case -EINPROGRESS: /* pending */
+ /* no action necessary */
+ dbg(DEBUG_USBREQ,
+ "%s: %s", __func__, get_usb_statmsg(urb->status));
+ break;
+
+ default: /* severe trouble */
+ warn("control read: %s", get_usb_statmsg(urb->status));
+ retry:
+ if (ucs->retry_cmd_in++ < BAS_RETRY) {
+ notice("control read: retry %d", ucs->retry_cmd_in);
+ if (atread_submit(cs, BAS_TIMEOUT) >= 0) {
+ /* resubmitted - bypass regular exit block */
+ spin_unlock_irqrestore(&cs->lock, flags);
+ return;
+ }
+ } else {
+ err("control read: giving up after %d tries",
+ ucs->retry_cmd_in);
+ }
+ error_reset(cs);
+ }
+
+ kfree(ucs->rcvbuf);
+ ucs->rcvbuf = NULL;
+ ucs->rcvbuf_size = 0;
+ spin_unlock_irqrestore(&cs->lock, flags);
+ if (have_data) {
+ dbg(DEBUG_INTR, "%s-->BH", __func__);
+ gigaset_schedule_event(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 pt_regs *regs)
+{
+ struct bc_state *bcs;
+ struct bas_bc_state *ubc;
+ unsigned long flags;
+ int i, rc;
+
+ IFNULLRET(urb);
+ IFNULLRET(urb->context);
+ IFNULLRET(cardstate);
+
+ /* status codes not worth bothering the tasklet with */
+ if (unlikely(urb->status == -ENOENT || urb->status == -ECONNRESET ||
+ urb->status == -EINPROGRESS)) {
+ dbg(DEBUG_ISO,
+ "%s: %s", __func__, get_usb_statmsg(urb->status));
+ return;
+ }
+
+ bcs = (struct bc_state *) urb->context;
+ ubc = bcs->hw.bas;
+ IFNULLRET(ubc);
+
+ spin_lock_irqsave(&ubc->isoinlock, flags);
+ if (likely(ubc->isoindone == NULL)) {
+ /* pass URB to tasklet */
+ ubc->isoindone = urb;
+ tasklet_schedule(&ubc->rcvd_tasklet);
+ } else {
+ /* tasklet still busy, drop data and resubmit URB */
+ ubc->loststatus = urb->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(atomic_read(&ubc->running))) {
+ urb->dev = bcs->cs->hw.bas->udev; /* clobbered by USB subsystem */
+ urb->transfer_flags = URB_ISO_ASAP;
+ urb->number_of_packets = BAS_NUMFRAMES;
+ dbg(DEBUG_ISO, "%s: isoc read overrun/resubmit", __func__);
+ rc = usb_submit_urb(urb, SLAB_ATOMIC);
+ if (unlikely(rc != 0)) {
+ err("could not resubmit isochronous read URB: %s",
+ get_usb_statmsg(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 pt_regs *regs)
+{
+ struct isow_urbctx_t *ucx;
+ struct bas_bc_state *ubc;
+ unsigned long flags;
+
+ IFNULLRET(urb);
+ IFNULLRET(urb->context);
+ IFNULLRET(cardstate);
+
+ /* status codes not worth bothering the tasklet with */
+ if (unlikely(urb->status == -ENOENT || urb->status == -ECONNRESET ||
+ urb->status == -EINPROGRESS)) {
+ dbg(DEBUG_ISO,
+ "%s: %s", __func__, get_usb_statmsg(urb->status));
+ return;
+ }
+
+ /* pass URB context to tasklet */
+ ucx = (struct isow_urbctx_t *) urb->context;
+ IFNULLRET(ucx->bcs);
+ ubc = ucx->bcs->hw.bas;
+ IFNULLRET(ubc);
+
+ spin_lock_irqsave(&ubc->isooutlock, flags);
+ ubc->isooutovfl = ubc->isooutdone;
+ ubc->isooutdone = ucx;
+ spin_unlock_irqrestore(&ubc->isooutlock, flags);
+ tasklet_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 urb *urb;
+ struct bas_bc_state *ubc;
+ int j, k;
+ int rc;
+
+ IFNULLRETVAL(bcs, -EFAULT);
+ ubc = bcs->hw.bas;
+ IFNULLRETVAL(ubc, -EFAULT);
+
+ /* initialize L2 reception */
+ if (bcs->proto2 == ISDN_PROTO_L2_HDLC)
+ bcs->inputstate |= INS_flag_hunt;
+
+ /* submit all isochronous input URBs */
+ atomic_set(&ubc->running, 1);
+ for (k = 0; k < BAS_INURBS; k++) {
+ urb = ubc->isoinurbs[k];
+ if (!urb) {
+ err("isoinurbs[%d]==NULL", k);
+ rc = -EFAULT;
+ goto error;
+ }
+
+ urb->dev = bcs->cs->hw.bas->udev;
+ urb->pipe = usb_rcvisocpipe(urb->dev, 3 + 2 * bcs->channel);
+ urb->transfer_flags = URB_ISO_ASAP;
+ urb->transfer_buffer = ubc->isoinbuf + k * BAS_INBUFSIZE;
+ urb->transfer_buffer_length = BAS_INBUFSIZE;
+ urb->number_of_packets = BAS_NUMFRAMES;
+ urb->interval = BAS_FRAMETIME;
+ urb->complete = read_iso_callback;
+ urb->context = bcs;
+ 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);
+ if ((rc = usb_submit_urb(urb, SLAB_ATOMIC)) != 0) {
+ err("could not submit isochronous read URB %d: %s",
+ k, get_usb_statmsg(rc));
+ 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) {
+ err("isoouturbs[%d].urb==NULL", k);
+ rc = -EFAULT;
+ goto error;
+ }
+ urb->dev = bcs->cs->hw.bas->udev;
+ urb->pipe = usb_sndisocpipe(urb->dev, 4 + 2 * bcs->channel);
+ urb->transfer_flags = URB_ISO_ASAP;
+ urb->transfer_buffer = ubc->isooutbuf->data;
+ urb->transfer_buffer_length = sizeof(ubc->isooutbuf->data);
+ urb->number_of_packets = BAS_NUMFRAMES;
+ urb->interval = BAS_FRAMETIME;
+ urb->complete = write_iso_callback;
+ urb->context = &ubc->isoouturbs[k];
+ 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;
+ }
+
+ /* submit two URBs, keep third one */
+ for (k = 0; k < 2; ++k) {
+ dump_urb(DEBUG_ISO, "Initial isoc write", urb);
+ rc = usb_submit_urb(ubc->isoouturbs[k].urb, SLAB_ATOMIC);
+ if (rc != 0) {
+ err("could not submit isochronous write URB %d: %s",
+ k, get_usb_statmsg(rc));
+ goto error;
+ }
+ }
+ dump_urb(DEBUG_ISO, "Initial isoc write (free)", urb);
+ ubc->isooutfree = &ubc->isoouturbs[2];
+ 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;
+
+ IFNULLRET(ubc);
+
+ atomic_set(&ubc->running, 0);
+
+ for (k = 0; k < BAS_INURBS; ++k) {
+ rc = usb_unlink_urb(ubc->isoinurbs[k]);
+ dbg(DEBUG_ISO, "%s: isoc input URB %d unlinked, result = %d",
+ __func__, k, rc);
+ }
+
+ for (k = 0; k < BAS_OUTURBS; ++k) {
+ rc = usb_unlink_urb(ubc->isoouturbs[k].urb);
+ dbg(DEBUG_ISO, "%s: isoc output URB %d unlinked, result = %d",
+ __func__, k, rc);
+ }
+}
+
+/* Isochronous Write - Bottom Half */
+/* =============================== */
+
+/* submit_iso_write_urb
+ * fill and submit the next isochronous write URB
+ * parameters:
+ * bcs B channel state structure
+ * 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;
+ struct bas_bc_state *ubc;
+ struct usb_iso_packet_descriptor *ifd;
+ int corrbytes, nframe, rc;
+
+ IFNULLRETVAL(ucx, -EFAULT);
+ urb = ucx->urb;
+ IFNULLRETVAL(urb, -EFAULT);
+ IFNULLRETVAL(ucx->bcs, -EFAULT);
+ ubc = ucx->bcs->hw.bas;
+ IFNULLRETVAL(ubc, -EFAULT);
+
+ urb->dev = ucx->bcs->cs->hw.bas->udev; /* clobbered by USB subsystem */
+ 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;
+ if ((corrbytes = atomic_read(&ubc->corrbytes)) != 0) {
+ 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);
+ }
+ //dbg(DEBUG_ISO, "%s: frame %d length=%d", __func__, nframe, ifd->length);
+
+ /* retrieve block of data to send */
+ ifd->offset = gigaset_isowbuf_getbytes(ubc->isooutbuf, ifd->length);
+ if (ifd->offset < 0) {
+ if (ifd->offset == -EBUSY) {
+ dbg(DEBUG_ISO, "%s: buffer busy at frame %d",
+ __func__, nframe);
+ /* tasklet will be restarted from gigaset_send_skb() */
+ } else {
+ err("%s: buffer error %d at frame %d",
+ __func__, ifd->offset, nframe);
+ return ifd->offset;
+ }
+ break;
+ }
+ ucx->limit = atomic_read(&ubc->isooutbuf->nextread);
+ ifd->status = 0;
+ ifd->actual_length = 0;
+ }
+ if ((urb->number_of_packets = nframe) > 0) {
+ if ((rc = usb_submit_urb(urb, SLAB_ATOMIC)) != 0) {
+ err("could not submit isochronous write URB: %s",
+ get_usb_statmsg(rc));
+ dump_urb(DEBUG_ISO, "isoc write", urb);
+ 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 bas_bc_state *ubc;
+ struct cardstate *cs;
+ struct isow_urbctx_t *done, *next, *ovfl;
+ struct urb *urb;
+ struct usb_iso_packet_descriptor *ifd;
+ int offset;
+ unsigned long flags;
+ int i;
+ struct sk_buff *skb;
+ int len;
+
+ bcs = (struct bc_state *) data;
+ IFNULLRET(bcs);
+ ubc = bcs->hw.bas;
+ IFNULLRET(ubc);
+ cs = bcs->cs;
+ IFNULLRET(cs);
+
+ /* loop while completed URBs arrive in time */
+ for (;;) {
+ if (unlikely(!atomic_read(&cs->connected))) {
+ warn("%s: disconnected", __func__);
+ return;
+ }
+
+ if (unlikely(!(atomic_read(&ubc->running)))) {
+ 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) {
+ err("isochronous write buffer underrun - buy a faster machine :-)");
+ 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) {
+ if (submit_iso_write_urb(next) <= 0) {
+ /* 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 */
+ err("losing isochronous write URB");
+ error_hangup(bcs);
+ }
+ }
+ }
+
+ /* process completed URB */
+ urb = done->urb;
+ switch (urb->status) {
+ case 0: /* normal completion */
+ break;
+ case -EXDEV: /* inspect individual frames */
+ /* assumptions (for lack of documentation):
+ * - actual_length bytes of the frame in error are successfully sent
+ * - all following frames are not sent at all
+ */
+ dbg(DEBUG_ISO, "%s: URB partially completed", __func__);
+ offset = done->limit; /* just in case */
+ for (i = 0; i < BAS_NUMFRAMES; i++) {
+ ifd = &urb->iso_frame_desc[i];
+ if (ifd->status ||
+ ifd->actual_length != ifd->length) {
+ warn("isochronous write: frame %d: %s, "
+ "only %d of %d bytes sent",
+ i, get_usb_statmsg(ifd->status),
+ ifd->actual_length, ifd->length);
+ offset = (ifd->offset +
+ ifd->actual_length)
+ % BAS_OUTBUFSIZE;
+ break;
+ }
+ }
+#ifdef CONFIG_GIGASET_DEBUG
+ /* check assumption on remaining frames */
+ for (; i < BAS_NUMFRAMES; i++) {
+ ifd = &urb->iso_frame_desc[i];
+ if (ifd->status != -EINPROGRESS
+ || ifd->actual_length != 0) {
+ warn("isochronous write: frame %d: %s, "
+ "%d of %d bytes sent",
+ i, get_usb_statmsg(ifd->status),
+ ifd->actual_length, ifd->length);
+ offset = (ifd->offset +
+ ifd->actual_length)
+ % BAS_OUTBUFSIZE;
+ break;
+ }
+ }
+#endif
+ break;
+ case -EPIPE: //FIXME is this the code for "underrun"?
+ err("isochronous write stalled");
+ error_hangup(bcs);
+ break;
+ default: /* severe trouble */
+ warn("isochronous write: %s",
+ get_usb_statmsg(urb->status));
+ }
+
+ /* mark the write buffer area covered by this URB as free */
+ if (done->limit >= 0)
+ atomic_set(&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 */
+ if (submit_iso_write_urb(next) <= 0) {
+ /* 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);
+ 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 bas_bc_state *ubc;
+ struct cardstate *cs;
+ struct urb *urb;
+ char *rcvbuf;
+ unsigned long flags;
+ int totleft, numbytes, offset, frame, rc;
+
+ bcs = (struct bc_state *) data;
+ IFNULLRET(bcs);
+ ubc = bcs->hw.bas;
+ IFNULLRET(ubc);
+ cs = bcs->cs;
+ IFNULLRET(cs);
+
+ /* loop while more completed URBs arrive in the meantime */
+ for (;;) {
+ if (!atomic_read(&cs->connected)) {
+ warn("%s: disconnected", __func__);
+ return;
+ }
+
+ /* retrieve URB */
+ spin_lock_irqsave(&ubc->isoinlock, flags);
+ if (!(urb = ubc->isoindone)) {
+ spin_unlock_irqrestore(&ubc->isoinlock, flags);
+ return;
+ }
+ ubc->isoindone = NULL;
+ if (unlikely(ubc->loststatus != -EINPROGRESS)) {
+ warn("isochronous read overrun, dropped URB with status: %s, %d bytes lost",
+ get_usb_statmsg(ubc->loststatus), ubc->isoinlost);
+ ubc->loststatus = -EINPROGRESS;
+ }
+ spin_unlock_irqrestore(&ubc->isoinlock, flags);
+
+ if (unlikely(!(atomic_read(&ubc->running)))) {
+ dbg(DEBUG_ISO, "%s: channel not running, dropped URB with status: %s",
+ __func__, get_usb_statmsg(urb->status));
+ return;
+ }
+
+ switch (urb->status) {
+ case 0: /* normal completion */
+ break;
+ case -EXDEV: /* inspect individual frames (we do that anyway) */
+ dbg(DEBUG_ISO, "%s: URB partially completed", __func__);
+ break;
+ case -ENOENT:
+ case -ECONNRESET:
+ dbg(DEBUG_ISO, "%s: URB canceled", __func__);
+ continue; /* -> skip */
+ case -EINPROGRESS: /* huh? */
+ dbg(DEBUG_ISO, "%s: URB still pending", __func__);
+ continue; /* -> skip */
+ case -EPIPE:
+ err("isochronous read stalled");
+ error_hangup(bcs);
+ continue; /* -> skip */
+ default: /* severe trouble */
+ warn("isochronous read: %s",
+ get_usb_statmsg(urb->status));
+ goto error;
+ }
+
+ rcvbuf = urb->transfer_buffer;
+ totleft = urb->actual_length;
+ for (frame = 0; totleft > 0 && frame < BAS_NUMFRAMES; frame++) {
+ if (unlikely(urb->iso_frame_desc[frame].status)) {
+ warn("isochronous read: frame %d: %s",
+ frame, get_usb_statmsg(urb->iso_frame_desc[frame].status));
+ break;
+ }
+ numbytes = urb->iso_frame_desc[frame].actual_length;
+ if (unlikely(numbytes > BAS_MAXFRAME)) {
+ warn("isochronous read: frame %d: numbytes (%d) > BAS_MAXFRAME",
+ frame, numbytes);
+ break;
+ }
+ if (unlikely(numbytes > totleft)) {
+ warn("isochronous read: frame %d: numbytes (%d) > totleft (%d)",
+ frame, numbytes, totleft);
+ break;
+ }
+ offset = urb->iso_frame_desc[frame].offset;
+ if (unlikely(offset + numbytes > BAS_INBUFSIZE)) {
+ warn("isochronous read: frame %d: offset (%d) + numbytes (%d) > BAS_INBUFSIZE",
+ frame, offset, numbytes);
+ break;
+ }
+ gigaset_isoc_receive(rcvbuf + offset, numbytes, bcs);
+ totleft -= numbytes;
+ }
+ if (unlikely(totleft > 0))
+ warn("isochronous read: %d data bytes missing",
+ 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 = bcs->cs->hw.bas->udev; /* clobbered by USB subsystem */
+ urb->transfer_flags = URB_ISO_ASAP;
+ urb->number_of_packets = BAS_NUMFRAMES;
+ if ((rc = usb_submit_urb(urb, SLAB_ATOMIC)) != 0) {
+ err("could not resubmit isochronous read URB: %s",
+ get_usb_statmsg(rc));
+ dump_urb(DEBUG_ISO, "resubmit iso read", urb);
+ error_hangup(bcs);
+ }
+ }
+}
+
+/* Channel Operations */
+/* ================== */
+
+/* req_timeout
+ * timeout routine for control output request
+ * argument:
+ * B channel control structure
+ */
+static void req_timeout(unsigned long data)
+{
+ struct bc_state *bcs = (struct bc_state *) data;
+ struct bas_cardstate *ucs;
+ int pending;
+ unsigned long flags;
+
+ IFNULLRET(bcs);
+ IFNULLRET(bcs->cs);
+ ucs = bcs->cs->hw.bas;
+ IFNULLRET(ucs);
+
+ 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 */
+ dbg(DEBUG_USBREQ, "%s: no request pending", __func__);
+ break;
+
+ case HD_OPEN_ATCHANNEL:
+ err("timeout opening AT channel");
+ error_reset(bcs->cs);
+ break;
+
+ case HD_OPEN_B2CHANNEL:
+ case HD_OPEN_B1CHANNEL:
+ err("timeout opening channel %d", bcs->channel + 1);
+ error_hangup(bcs);
+ break;
+
+ case HD_CLOSE_ATCHANNEL:
+ err("timeout closing AT channel");
+ //wake_up_interruptible(cs->initwait);
+ //FIXME need own wait queue?
+ break;
+
+ case HD_CLOSE_B2CHANNEL:
+ case HD_CLOSE_B1CHANNEL:
+ err("timeout closing channel %d", bcs->channel + 1);
+ break;
+
+ default:
+ warn("request 0x%02x timed out, clearing", pending);
+ }
+}
+
+/* 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 pt_regs *regs)
+{
+ struct bas_cardstate *ucs;
+ unsigned long flags;
+
+ IFNULLRET(urb);
+ IFNULLRET(urb->context);
+ IFNULLRET(cardstate);
+
+ ucs = (struct bas_cardstate *) urb->context;
+ spin_lock_irqsave(&ucs->lock, flags);
+ if (urb->status && ucs->pending) {
+ err("control request 0x%02x failed: %s",
+ ucs->pending, get_usb_statmsg(urb->status));
+ del_timer(&ucs->timer_ctrl);
+ ucs->pending = 0;
+ }
+ /* individual handling of specific request types */
+ switch (ucs->pending) {
+ case HD_DEVICE_INIT_ACK: /* no reply expected */
+ ucs->pending = 0;
+ break;
+ }
+ spin_unlock_irqrestore(&ucs->lock, flags);
+}
+
+/* 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
+ * -EINVAL if a NULL pointer is encountered somewhere
+ * -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;
+ int ret;
+ unsigned long flags;
+
+ IFNULLRETVAL(bcs, -EINVAL);
+ IFNULLRETVAL(bcs->cs, -EINVAL);
+ ucs = bcs->cs->hw.bas;
+ IFNULLRETVAL(ucs, -EINVAL);
+ IFNULLRETVAL(ucs->urb_ctrl, -EINVAL);
+
+ dbg(DEBUG_USBREQ, "-------> 0x%02x (%d)", req, val);
+
+ spin_lock_irqsave(&ucs->lock, flags);
+ if (ucs->pending) {
+ spin_unlock_irqrestore(&ucs->lock, flags);
+ err("submission of request 0x%02x failed: request 0x%02x still pending",
+ req, ucs->pending);
+ return -EBUSY;
+ }
+ if (ucs->urb_ctrl->status == -EINPROGRESS) {
+ spin_unlock_irqrestore(&ucs->lock, flags);
+ err("could not submit request 0x%02x: URB busy", req);
+ 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);
+ if ((ret = usb_submit_urb(ucs->urb_ctrl, SLAB_ATOMIC)) != 0) {
+ err("could not submit request 0x%02x: %s",
+ req, get_usb_statmsg(ret));
+ spin_unlock_irqrestore(&ucs->lock, flags);
+ return ret;
+ }
+ ucs->pending = req;
+
+ if (timeout > 0) {
+ dbg(DEBUG_USBREQ, "setting timeout of %d/10 secs", timeout);
+ ucs->timer_ctrl.expires = jiffies + timeout * HZ / 10;
+ ucs->timer_ctrl.data = (unsigned long) bcs;
+ ucs->timer_ctrl.function = req_timeout;
+ add_timer(&ucs->timer_ctrl);
+ }
+
+ 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)
+{
+ int req, ret;
+
+ IFNULLRETVAL(bcs, -EINVAL);
+
+ if ((ret = starturbs(bcs)) < 0) {
+ err("could not start isochronous I/O for channel %d",
+ bcs->channel + 1);
+ error_hangup(bcs);
+ return ret;
+ }
+
+ req = bcs->channel ? HD_OPEN_B2CHANNEL : HD_OPEN_B1CHANNEL;
+ if ((ret = req_submit(bcs, req, 0, BAS_TIMEOUT)) < 0) {
+ err("could not open channel %d: %s",
+ bcs->channel + 1, get_usb_statmsg(ret));
+ stopurbs(bcs->hw.bas);
+ 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)
+{
+ int req, ret;
+
+ IFNULLRETVAL(bcs, -EINVAL);
+
+ if (!(atomic_read(&bcs->cs->hw.bas->basstate) &
+ (bcs->channel ? BS_B2OPEN : BS_B1OPEN))) {
+ /* channel not running: just signal common.c */
+ gigaset_bchannel_down(bcs);
+ return 0;
+ }
+
+ req = bcs->channel ? HD_CLOSE_B2CHANNEL : HD_CLOSE_B1CHANNEL;
+ if ((ret = req_submit(bcs, req, 0, BAS_TIMEOUT)) < 0)
+ err("could not submit HD_CLOSE_BxCHANNEL request: %s",
+ get_usb_statmsg(ret));
+ 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;
+
+ IFNULLRET(cs);
+ cb = cs->cmdbuf;
+ IFNULLRET(cb);
+
+ /* unqueue completed buffer */
+ cs->cmdbytes -= cs->curlen;
+ dbg(DEBUG_TRANSCMD | DEBUG_LOCKCMD,
+ "write_command: sent %u bytes, %u left",
+ cs->curlen, cs->cmdbytes);
+ if ((cs->cmdbuf = cb->next) != NULL) {
+ cs->cmdbuf->prev = NULL;
+ cs->curlen = cs->cmdbuf->len;
+ } else {
+ cs->lastcmdbuf = NULL;
+ cs->curlen = 0;
+ }
+
+ if (cb->wake_tasklet)
+ tasklet_schedule(cb->wake_tasklet);
+
+ kfree(cb);
+}
+
+static int atwrite_submit(struct cardstate *cs, unsigned char *buf, int len);
+
+/* 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 pt_regs *regs)
+{
+ struct cardstate *cs;
+ unsigned long flags;
+ struct bas_cardstate *ucs;
+
+ IFNULLRET(urb);
+ cs = (struct cardstate *) urb->context;
+ IFNULLRET(cs);
+ ucs = cs->hw.bas;
+ IFNULLRET(ucs);
+
+ /* check status */
+ switch (urb->status) {
+ case 0: /* normal completion */
+ break;
+ case -ENOENT: /* canceled */
+ case -ECONNRESET: /* canceled (async) */
+ case -EINPROGRESS: /* pending */
+ /* ignore silently */
+ dbg(DEBUG_USBREQ,
+ "%s: %s", __func__, get_usb_statmsg(urb->status));
+ return;
+ default: /* any failure */
+ if (++ucs->retry_cmd_out > BAS_RETRY) {
+ warn("command write: %s, giving up after %d retries",
+ get_usb_statmsg(urb->status), ucs->retry_cmd_out);
+ break;
+ }
+ if (cs->cmdbuf == NULL) {
+ warn("command write: %s, cannot retry - cmdbuf gone",
+ get_usb_statmsg(urb->status));
+ break;
+ }
+ notice("command write: %s, retry %d",
+ get_usb_statmsg(urb->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(unsigned long data)
+{
+ struct cardstate *cs = (struct cardstate *) data;
+ struct bas_cardstate *ucs;
+
+ IFNULLRET(cs);
+ ucs = cs->hw.bas;
+ IFNULLRET(ucs);
+
+ warn("timeout waiting for HD_READY_SEND_ATDATA");
+
+ /* 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
+ * -EFAULT if a NULL pointer is encountered somewhere
+ * -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;
+ int ret;
+
+ IFNULLRETVAL(cs, -EFAULT);
+ ucs = cs->hw.bas;
+ IFNULLRETVAL(ucs, -EFAULT);
+ IFNULLRETVAL(ucs->urb_cmd_out, -EFAULT);
+
+ dbg(DEBUG_USBREQ, "-------> HD_WRITE_ATMESSAGE (%d)", len);
+
+ if (ucs->urb_cmd_out->status == -EINPROGRESS) {
+ err("could not submit HD_WRITE_ATMESSAGE: URB busy");
+ 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);
+
+ if ((ret = usb_submit_urb(ucs->urb_cmd_out, SLAB_ATOMIC)) != 0) {
+ err("could not submit HD_WRITE_ATMESSAGE: %s",
+ get_usb_statmsg(ret));
+ return ret;
+ }
+
+ /* submitted successfully */
+ update_basstate(ucs, 0, BS_ATREADY);
+
+ /* start timeout if necessary */
+ if (!(atomic_read(&ucs->basstate) & BS_ATTIMER)) {
+ dbg(DEBUG_OUTPUT,
+ "setting ATREADY timeout of %d/10 secs", ATRDY_TIMEOUT);
+ ucs->timer_atrdy.expires = jiffies + ATRDY_TIMEOUT * HZ / 10;
+ ucs->timer_atrdy.data = (unsigned long) cs;
+ ucs->timer_atrdy.function = atrdy_timeout;
+ add_timer(&ucs->timer_atrdy);
+ update_basstate(ucs, BS_ATTIMER, 0);
+ }
+ 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;
+ unsigned long flags;
+ int rc;
+ int retval = 0;
+
+ IFNULLRETVAL(cs, -EFAULT);
+ ucs = cs->hw.bas;
+ IFNULLRETVAL(ucs, -EFAULT);
+
+ /* check if AT channel is open */
+ if (!(atomic_read(&ucs->basstate) & BS_ATOPEN)) {
+ dbg(DEBUG_TRANSCMD | DEBUG_LOCKCMD, "AT channel not open");
+ rc = req_submit(cs->bcs, HD_OPEN_ATCHANNEL, 0, BAS_TIMEOUT);
+ if (rc < 0) {
+ err("could not open AT channel");
+ /* 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 &&
+ atomic_read(&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
+ * buf command string to send
+ * len number of bytes to send (max. IF_WRITEBUF)
+ * wake_tasklet tasklet to run when transmission is completed (NULL if none)
+ * return value:
+ * number of bytes queued on success
+ * error code < 0 on error
+ */
+static int gigaset_write_cmd(struct cardstate *cs,
+ const unsigned char *buf, int len,
+ struct tasklet_struct *wake_tasklet)
+{
+ struct cmdbuf_t *cb;
+ unsigned long flags;
+ int status;
+
+ gigaset_dbg_buffer(atomic_read(&cs->mstate) != MS_LOCKED ?
+ DEBUG_TRANSCMD : DEBUG_LOCKCMD,
+ "CMD Transmit", len, buf, 0);
+
+ if (!atomic_read(&cs->connected)) {
+ err("%s: not connected", __func__);
+ return -ENODEV;
+ }
+
+ if (len <= 0)
+ return 0; /* nothing to do */
+
+ if (len > IF_WRITEBUF)
+ len = IF_WRITEBUF;
+ if (!(cb = kmalloc(sizeof(struct cmdbuf_t) + len, GFP_ATOMIC))) {
+ err("%s: out of memory", __func__);
+ return -ENOMEM;
+ }
+
+ memcpy(cb->buf, buf, len);
+ cb->len = len;
+ cb->offset = 0;
+ cb->next = NULL;
+ cb->wake_tasklet = wake_tasklet;
+
+ spin_lock_irqsave(&cs->cmdlock, flags);
+ cb->prev = cs->lastcmdbuf;
+ if (cs->lastcmdbuf)
+ cs->lastcmdbuf->next = cb;
+ else {
+ cs->cmdbuf = cb;
+ cs->curlen = len;
+ }
+ cs->cmdbytes += len;
+ cs->lastcmdbuf = cb;
+ spin_unlock_irqrestore(&cs->cmdlock, flags);
+
+ status = start_cbsend(cs);
+
+ return status < 0 ? status : 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)
+{
+ unsigned long flags;
+ unsigned bytes;
+
+ spin_lock_irqsave(&cs->cmdlock, flags);
+ bytes = cs->cmdbytes;
+ spin_unlock_irqrestore(&cs->cmdlock, flags);
+
+ return bytes;
+}
+
+/* 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
+ * return value:
+ * !=0 on success
+ */
+static int gigaset_freebcshw(struct bc_state *bcs)
+{
+ if (!bcs->hw.bas)
+ return 0;
+
+ if (bcs->hw.bas->isooutbuf)
+ kfree(bcs->hw.bas->isooutbuf);
+ kfree(bcs->hw.bas);
+ bcs->hw.bas = NULL;
+ return 1;
+}
+
+/* Initialize hardware dependent part of the B channel structure
+ * parameter:
+ * bcs B channel structure
+ * return value:
+ * !=0 on success
+ */
+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) {
+ err("could not allocate bas_bc_state");
+ return 0;
+ }
+
+ atomic_set(&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;
+ if (!(ubc->isooutbuf = kmalloc(sizeof(struct isowbuf_t), GFP_KERNEL))) {
+ err("could not allocate isochronous output buffer");
+ kfree(ubc);
+ bcs->hw.bas = NULL;
+ return 0;
+ }
+ 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 1;
+}
+
+static void gigaset_reinitbcshw(struct bc_state *bcs)
+{
+ struct bas_bc_state *ubc = bcs->hw.bas;
+
+ atomic_set(&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)
+{
+ struct bas_cardstate *ucs = cs->hw.bas;
+
+ del_timer(&ucs->timer_ctrl);
+ del_timer(&ucs->timer_atrdy);
+ del_timer(&ucs->timer_cmd_in);
+
+ kfree(cs->hw.bas);
+}
+
+static int gigaset_initcshw(struct cardstate *cs)
+{
+ struct bas_cardstate *ucs;
+
+ cs->hw.bas = ucs = kmalloc(sizeof *ucs, GFP_KERNEL);
+ if (!ucs)
+ return 0;
+
+ ucs->urb_cmd_in = NULL;
+ ucs->urb_cmd_out = NULL;
+ ucs->rcvbuf = NULL;
+ ucs->rcvbuf_size = 0;
+
+ spin_lock_init(&ucs->lock);
+ ucs->pending = 0;
+
+ atomic_set(&ucs->basstate, 0);
+ init_timer(&ucs->timer_ctrl);
+ init_timer(&ucs->timer_atrdy);
+ init_timer(&ucs->timer_cmd_in);
+
+ return 1;
+}
+
+/* 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;
+ struct bas_bc_state *ubc;
+ int i, j;
+
+ IFNULLRET(cs);
+ ucs = cs->hw.bas;
+ IFNULLRET(ucs);
+
+ for (j = 0; j < 2; ++j) {
+ ubc = cs->bcs[j].hw.bas;
+ IFNULLCONT(ubc);
+ for (i = 0; i < BAS_OUTURBS; ++i)
+ if (ubc->isoouturbs[i].urb) {
+ usb_kill_urb(ubc->isoouturbs[i].urb);
+ dbg(DEBUG_INIT,
+ "%s: isoc output URB %d/%d unlinked",
+ __func__, j, i);
+ usb_free_urb(ubc->isoouturbs[i].urb);
+ ubc->isoouturbs[i].urb = NULL;
+ }
+ for (i = 0; i < BAS_INURBS; ++i)
+ if (ubc->isoinurbs[i]) {
+ usb_kill_urb(ubc->isoinurbs[i]);
+ dbg(DEBUG_INIT,
+ "%s: isoc input URB %d/%d unlinked",
+ __func__, j, i);
+ usb_free_urb(ubc->isoinurbs[i]);
+ ubc->isoinurbs[i] = NULL;
+ }
+ }
+ if (ucs->urb_int_in) {
+ usb_kill_urb(ucs->urb_int_in);
+ dbg(DEBUG_INIT, "%s: interrupt input URB unlinked", __func__);
+ usb_free_urb(ucs->urb_int_in);
+ ucs->urb_int_in = NULL;
+ }
+ if (ucs->urb_cmd_out) {
+ usb_kill_urb(ucs->urb_cmd_out);
+ dbg(DEBUG_INIT, "%s: command output URB unlinked", __func__);
+ usb_free_urb(ucs->urb_cmd_out);
+ ucs->urb_cmd_out = NULL;
+ }
+ if (ucs->urb_cmd_in) {
+ usb_kill_urb(ucs->urb_cmd_in);
+ dbg(DEBUG_INIT, "%s: command input URB unlinked", __func__);
+ usb_free_urb(ucs->urb_cmd_in);
+ ucs->urb_cmd_in = NULL;
+ }
+ if (ucs->urb_ctrl) {
+ usb_kill_urb(ucs->urb_ctrl);
+ dbg(DEBUG_INIT, "%s: control output URB unlinked", __func__);
+ 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 ret;
+
+ IFNULLRETVAL(udev, -ENODEV);
+
+ dbg(DEBUG_ANY,
+ "%s: Check if device matches .. (Vendor: 0x%x, Product: 0x%x)",
+ __func__, le16_to_cpu(udev->descriptor.idVendor),
+ le16_to_cpu(udev->descriptor.idProduct));
+
+ /* See if the device offered us matches what we can accept */
+ if ((le16_to_cpu(udev->descriptor.idVendor) != USB_GIGA_VENDOR_ID) ||
+ (le16_to_cpu(udev->descriptor.idProduct) != USB_GIGA_PRODUCT_ID &&
+ le16_to_cpu(udev->descriptor.idProduct) != USB_4175_PRODUCT_ID &&
+ le16_to_cpu(udev->descriptor.idProduct) != USB_SX303_PRODUCT_ID &&
+ le16_to_cpu(udev->descriptor.idProduct) != USB_SX353_PRODUCT_ID)) {
+ dbg(DEBUG_ANY, "%s: unmatched ID - exiting", __func__);
+ return -ENODEV;
+ }
+
+ /* set required alternate setting */
+ hostif = interface->cur_altsetting;
+ if (hostif->desc.bAlternateSetting != 3) {
+ dbg(DEBUG_ANY,
+ "%s: wrong alternate setting %d - trying to switch",
+ __func__, hostif->desc.bAlternateSetting);
+ if (usb_set_interface(udev, hostif->desc.bInterfaceNumber, 3) < 0) {
+ warn("usb_set_interface failed, device %d interface %d altsetting %d",
+ udev->devnum, hostif->desc.bInterfaceNumber,
+ hostif->desc.bAlternateSetting);
+ return -ENODEV;
+ }
+ hostif = interface->cur_altsetting;
+ }
+
+ /* Reject application specific interfaces
+ */
+ if (hostif->desc.bInterfaceClass != 255) {
+ warn("%s: bInterfaceClass == %d",
+ __func__, hostif->desc.bInterfaceClass);
+ return -ENODEV;
+ }
+
+ info("%s: Device matched (Vendor: 0x%x, Product: 0x%x)",
+ __func__, le16_to_cpu(udev->descriptor.idVendor),
+ le16_to_cpu(udev->descriptor.idProduct));
+
+ cs = gigaset_getunassignedcs(driver);
+ if (!cs) {
+ err("%s: no free cardstate", __func__);
+ return -ENODEV;
+ }
+ ucs = cs->hw.bas;
+ ucs->udev = udev;
+ ucs->interface = interface;
+
+ /* allocate URBs:
+ * - one for the interrupt pipe
+ * - three for the different uses of the default control pipe
+ * - three for each isochronous pipe
+ */
+ ucs->urb_int_in = usb_alloc_urb(0, SLAB_KERNEL);
+ if (!ucs->urb_int_in) {
+ err("No free urbs available");
+ goto error;
+ }
+ ucs->urb_cmd_in = usb_alloc_urb(0, SLAB_KERNEL);
+ if (!ucs->urb_cmd_in) {
+ err("No free urbs available");
+ goto error;
+ }
+ ucs->urb_cmd_out = usb_alloc_urb(0, SLAB_KERNEL);
+ if (!ucs->urb_cmd_out) {
+ err("No free urbs available");
+ goto error;
+ }
+ ucs->urb_ctrl = usb_alloc_urb(0, SLAB_KERNEL);
+ if (!ucs->urb_ctrl) {
+ err("No free urbs available");
+ goto error;
+ }
+
+ for (j = 0; j < 2; ++j) {
+ ubc = cs->bcs[j].hw.bas;
+ for (i = 0; i < BAS_OUTURBS; ++i) {
+ ubc->isoouturbs[i].urb =
+ usb_alloc_urb(BAS_NUMFRAMES, SLAB_KERNEL);
+ if (!ubc->isoouturbs[i].urb) {
+ err("No free urbs available");
+ goto error;
+ }
+ }
+ for (i = 0; i < BAS_INURBS; ++i) {
+ ubc->isoinurbs[i] =
+ usb_alloc_urb(BAS_NUMFRAMES, SLAB_KERNEL);
+ if (!ubc->isoinurbs[i]) {
+ err("No free urbs available");
+ goto error;
+ }
+ }
+ }
+
+ 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,
+ (endpoint->bEndpointAddress) & 0x0f),
+ ucs->int_in_buf, 3, read_int_callback, cs,
+ endpoint->bInterval);
+ ret = usb_submit_urb(ucs->urb_int_in, SLAB_KERNEL);
+ if (ret) {
+ err("could not submit interrupt URB: %s", get_usb_statmsg(ret));
+ goto error;
+ }
+
+ /* tell the device that the driver is ready */
+ if ((ret = req_submit(cs->bcs, HD_DEVICE_INIT_ACK, 0, 0)) != 0)
+ goto error;
+
+ /* tell common part that the device is ready */
+ if (startmode == SM_LOCKED)
+ atomic_set(&cs->mstate, MS_LOCKED);
+ if (!gigaset_start(cs))
+ goto error;
+
+ /* save address of controller structure */
+ usb_set_intfdata(interface, cs);
+
+ /* set up device sysfs */
+ gigaset_init_dev_sysfs(interface);
+ return 0;
+
+error:
+ freeurbs(cs);
+ gigaset_unassign(cs);
+ return -ENODEV;
+}
+
+/* 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;
+
+ /* clear device sysfs */
+ gigaset_free_dev_sysfs(interface);
+
+ cs = usb_get_intfdata(interface);
+ usb_set_intfdata(interface, NULL);
+
+ IFNULLRET(cs);
+ ucs = cs->hw.bas;
+ IFNULLRET(ucs);
+
+ info("disconnecting GigaSet base");
+ gigaset_stop(cs);
+ freeurbs(cs);
+ kfree(ucs->rcvbuf);
+ ucs->rcvbuf = NULL;
+ ucs->rcvbuf_size = 0;
+ atomic_set(&ucs->basstate, 0);
+ gigaset_unassign(cs);
+}
+
+static struct gigaset_ops gigops = {
+ gigaset_write_cmd,
+ gigaset_write_room,
+ gigaset_chars_in_buffer,
+ gigaset_brkchars,
+ gigaset_init_bchannel,
+ gigaset_close_bchannel,
+ gigaset_initbcshw,
+ gigaset_freebcshw,
+ gigaset_reinitbcshw,
+ gigaset_initcshw,
+ gigaset_freecshw,
+ gigaset_set_modem_ctrl,
+ gigaset_baud_rate,
+ gigaset_set_line_ctrl,
+ gigaset_isoc_send_skb,
+ 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 intialize it */
+ if ((driver = gigaset_initdriver(GIGASET_MINOR, GIGASET_MINORS,
+ GIGASET_MODULENAME, GIGASET_DEVNAME,
+ GIGASET_DEVFSNAME, &gigops,
+ THIS_MODULE)) == NULL)
+ goto error;
+
+ /* allocate memory for our device state and intialize it */
+ cardstate = gigaset_initcs(driver, 2, 0, 0, cidmode, GIGASET_MODULENAME);
+ if (!cardstate)
+ goto error;
+
+ /* register this driver with the USB subsystem */
+ result = usb_register(&gigaset_usb_driver);
+ if (result < 0) {
+ err("usb_register failed (error %d)", -result);
+ goto error;
+ }
+
+ info(DRIVER_AUTHOR);
+ info(DRIVER_DESC);
+ return 0;
+
+error: if (cardstate)
+ gigaset_freecs(cardstate);
+ cardstate = NULL;
+ 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)
+{
+ gigaset_blockdriver(driver); /* => probe will fail
+ * => no gigaset_start any more
+ */
+
+ gigaset_shutdown(cardstate);
+ /* from now on, no isdn callback should be possible */
+
+ if (atomic_read(&cardstate->hw.bas->basstate) & BS_ATOPEN) {
+ dbg(DEBUG_ANY, "closing AT channel");
+ if (req_submit(cardstate->bcs,
+ HD_CLOSE_ATCHANNEL, 0, BAS_TIMEOUT) >= 0) {
+ /* successfully submitted - wait for completion */
+ //wait_event_interruptible(cs->initwait, !cs->hw.bas->pending);
+ //FIXME need own wait queue? wakeup?
+ }
+ }
+
+ /* 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_freecs(cardstate);
+ cardstate = NULL;
+ 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/isdn/gigaset/common.c b/drivers/isdn/gigaset/common.c
new file mode 100644
index 000000000000..64371995c1a9
--- /dev/null
+++ b/drivers/isdn/gigaset/common.c
@@ -0,0 +1,1203 @@
+/*
+ * Stuff used by all variants of the driver
+ *
+ * Copyright (c) 2001 by Stefan Eilers <Eilers.Stefan@epost.de>,
+ * 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.
+ * =====================================================================
+ * ToDo: ...
+ * =====================================================================
+ * Version: $Id: common.c,v 1.104.4.22 2006/02/04 18:28:16 hjlipp Exp $
+ * =====================================================================
+ */
+
+#include "gigaset.h"
+#include <linux/ctype.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 <Eilers.Stefan@epost.de>"
+#define DRIVER_DESC "Driver for Gigaset 307x"
+
+/* Module parameters */
+int gigaset_debuglevel = DEBUG_DEFAULT;
+EXPORT_SYMBOL_GPL(gigaset_debuglevel);
+module_param_named(debug, gigaset_debuglevel, int, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(debug, "debug level");
+
+/*======================================================================
+ Prototypes of internal functions
+ */
+
+//static void gigaset_process_response(int resp_code, int parameter,
+// struct at_state_t *at_state,
+// unsigned char ** pstring);
+static struct cardstate *alloc_cs(struct gigaset_driver *drv);
+static void free_cs(struct cardstate *cs);
+static void make_valid(struct cardstate *cs, unsigned mask);
+static void make_invalid(struct cardstate *cs, unsigned mask);
+
+#define VALID_MINOR 0x01
+#define VALID_ID 0x02
+#define ASSIGNED 0x04
+
+/* bitwise byte inversion table */
+__u8 gigaset_invtab[256] = {
+ 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0,
+ 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
+ 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
+ 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
+ 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4,
+ 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
+ 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec,
+ 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
+ 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
+ 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
+ 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea,
+ 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
+ 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6,
+ 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
+ 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
+ 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
+ 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1,
+ 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
+ 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9,
+ 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
+ 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
+ 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
+ 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed,
+ 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
+ 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3,
+ 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
+ 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
+ 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
+ 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7,
+ 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
+ 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef,
+ 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff
+};
+EXPORT_SYMBOL_GPL(gigaset_invtab);
+
+void gigaset_dbg_buffer(enum debuglevel level, const unsigned char *msg,
+ size_t len, const unsigned char *buf, int from_user)
+{
+ unsigned char outbuf[80];
+ unsigned char inbuf[80 - 1];
+ size_t numin;
+ const unsigned char *in;
+ size_t space = sizeof outbuf - 1;
+ unsigned char *out = outbuf;
+
+ if (!from_user) {
+ in = buf;
+ numin = len;
+ } else {
+ numin = len < sizeof inbuf ? len : sizeof inbuf;
+ in = inbuf;
+ if (copy_from_user(inbuf, (const unsigned char __user *) buf, numin)) {
+ strncpy(inbuf, "<FAULT>", sizeof inbuf);
+ numin = sizeof "<FAULT>" - 1;
+ }
+ }
+
+ for (; numin && space; --numin, ++in) {
+ --space;
+ if (*in >= 32)
+ *out++ = *in;
+ else {
+ *out++ = '^';
+ if (space) {
+ *out++ = '@' + *in;
+ --space;
+ }
+ }
+ }
+ *out = 0;
+
+ 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;
+
+ if (!atomic_read(&cs->connected)) {
+ err("not connected!");
+ return -1;
+ }
+
+ cs->control_state = TIOCM_RTS; //FIXME
+
+ 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:
+ err("error %d on setuartbits!\n", -r);
+ cs->control_state = TIOCM_RTS|TIOCM_DTR; // FIXME is this a good value?
+ cs->ops->set_modem_ctrl(cs, 0, TIOCM_RTS|TIOCM_DTR);
+
+ return -1; //r
+}
+
+static int test_timeout(struct at_state_t *at_state)
+{
+ if (!at_state->timer_expires)
+ return 0;
+
+ if (--at_state->timer_expires) {
+ dbg(DEBUG_MCMD, "decreased timer of %p to %lu",
+ at_state, at_state->timer_expires);
+ return 0;
+ }
+
+ if (!gigaset_add_event(at_state->cs, at_state, EV_TIMEOUT, NULL,
+ atomic_read(&at_state->timer_index), NULL)) {
+ //FIXME what should we do?
+ }
+
+ return 1;
+}
+
+static void timer_tick(unsigned long data)
+{
+ struct cardstate *cs = (struct cardstate *) data;
+ 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 (atomic_read(&cs->running)) {
+ mod_timer(&cs->timer, jiffies + GIG_TICK);
+ if (timeout) {
+ dbg(DEBUG_CMD, "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) {
+ dbg(DEBUG_ANY, "could not allocate channel %d", bcs->channel);
+ spin_unlock_irqrestore(&bcs->cs->lock, flags);
+ return 0;
+ }
+ ++bcs->use_count;
+ bcs->busy = 1;
+ dbg(DEBUG_ANY, "allocated channel %d", bcs->channel);
+ spin_unlock_irqrestore(&bcs->cs->lock, flags);
+ return 1;
+}
+
+void gigaset_free_channel(struct bc_state *bcs)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&bcs->cs->lock, flags);
+ if (!bcs->busy) {
+ dbg(DEBUG_ANY, "could not free channel %d", bcs->channel);
+ spin_unlock_irqrestore(&bcs->cs->lock, flags);
+ return;
+ }
+ --bcs->use_count;
+ bcs->busy = 0;
+ dbg(DEBUG_ANY, "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);
+ dbg(DEBUG_ANY, "could not allocated all channels");
+ return 0;
+ }
+ for (i = 0; i < cs->channels; ++i)
+ ++cs->bcs[i].use_count;
+ spin_unlock_irqrestore(&cs->lock, flags);
+
+ dbg(DEBUG_ANY, "allocated all channels");
+
+ return 1;
+}
+
+void gigaset_free_channels(struct cardstate *cs)
+{
+ unsigned long flags;
+ int i;
+
+ dbg(DEBUG_ANY, "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;
+
+ dbg(DEBUG_ANY, "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;
+
+ /* no locking needed (no reader/writer allowed) */
+
+ head = atomic_read(&cs->ev_head);
+ tail = atomic_read(&cs->ev_tail);
+
+ while (tail != head) {
+ ev = cs->events + head;
+ kfree(ev->ptr);
+
+ head = (head + 1) % MAX_EVENTS;
+ }
+
+ atomic_set(&cs->ev_head, tail);
+}
+
+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;
+
+ spin_lock_irqsave(&cs->ev_lock, flags);
+
+ tail = atomic_read(&cs->ev_tail);
+ next = (tail + 1) % MAX_EVENTS;
+ if (unlikely(next == atomic_read(&cs->ev_head)))
+ err("event queue full");
+ 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;
+ atomic_set(&cs->ev_tail, next);
+ }
+
+ spin_unlock_irqrestore(&cs->ev_lock, flags);
+
+ return event;
+}
+EXPORT_SYMBOL_GPL(gigaset_add_event);
+
+static void free_strings(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 clear_at_state(struct at_state_t *at_state)
+{
+ free_strings(at_state);
+}
+
+static void dealloc_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);
+ free_strings(cur);
+ kfree(cur);
+ }
+}
+
+static void gigaset_freebcs(struct bc_state *bcs)
+{
+ int i;
+
+ dbg(DEBUG_INIT, "freeing bcs[%d]->hw", bcs->channel);
+ if (!bcs->cs->ops->freebcshw(bcs)) {
+ dbg(DEBUG_INIT, "failed");
+ }
+
+ dbg(DEBUG_INIT, "clearing bcs[%d]->at_state", bcs->channel);
+ clear_at_state(&bcs->at_state);
+ dbg(DEBUG_INIT, "freeing bcs[%d]->skb", bcs->channel);
+
+ if (bcs->skb)
+ dev_kfree_skb(bcs->skb);
+ for (i = 0; i < AT_NUM; ++i) {
+ kfree(bcs->commands[i]);
+ bcs->commands[i] = NULL;
+ }
+}
+
+void gigaset_freecs(struct cardstate *cs)
+{
+ int i;
+ unsigned long flags;
+
+ if (!cs)
+ return;
+
+ down(&cs->sem);
+
+ if (!cs->bcs)
+ goto f_cs;
+ if (!cs->inbuf)
+ goto f_bcs;
+
+ spin_lock_irqsave(&cs->lock, flags);
+ atomic_set(&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:
+ gigaset_if_free(cs);
+
+ dbg(DEBUG_INIT, "clearing hw");
+ cs->ops->freecshw(cs);
+
+ //FIXME cmdbuf
+
+ /* fall through */
+ case 2: /* error in initcshw */
+ /* Deregister from LL */
+ make_invalid(cs, VALID_ID);
+ dbg(DEBUG_INIT, "clearing iif");
+ gigaset_i4l_cmd(cs, ISDN_STAT_UNLOAD);
+
+ /* fall through */
+ case 1: /* error when regestering to LL */
+ dbg(DEBUG_INIT, "clearing at_state");
+ clear_at_state(&cs->at_state);
+ dealloc_at_states(cs);
+
+ /* fall through */
+ case 0: /* error in one call to initbcs */
+ for (i = 0; i < cs->channels; ++i) {
+ dbg(DEBUG_INIT, "clearing bcs[%d]", i);
+ gigaset_freebcs(cs->bcs + i);
+ }
+
+ clear_events(cs);
+ dbg(DEBUG_INIT, "freeing inbuf");
+ kfree(cs->inbuf);
+ }
+f_bcs: dbg(DEBUG_INIT, "freeing bcs[]");
+ kfree(cs->bcs);
+f_cs: dbg(DEBUG_INIT, "freeing cs");
+ up(&cs->sem);
+ 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;
+ atomic_set(&at_state->timer_index, 0);
+ atomic_set(&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 bc_state *bcs,
+ struct cardstate *cs, int inputstate)
+/* inbuf->read must be allocated before! */
+{
+ atomic_set(&inbuf->head, 0);
+ atomic_set(&inbuf->tail, 0);
+ inbuf->cs = cs;
+ inbuf->bcs = bcs; /*base driver: NULL*/
+ inbuf->rcvbuf = NULL; //FIXME
+ inbuf->inputstate = inputstate;
+}
+
+/* Initialize the b-channel structure */
+static struct bc_state *gigaset_initbcs(struct bc_state *bcs,
+ struct cardstate *cs, int channel)
+{
+ int i;
+
+ bcs->tx_skb = NULL; //FIXME -> hw part
+
+ skb_queue_head_init(&bcs->squeue);
+
+ bcs->corrupted = 0;
+ bcs->trans_down = 0;
+ bcs->trans_up = 0;
+
+ dbg(DEBUG_INIT, "setting up bcs[%d]->at_state", channel);
+ gigaset_at_init(&bcs->at_state, bcs, cs, -1);
+
+ bcs->rcvbytes = 0;
+
+#ifdef CONFIG_GIGASET_DEBUG
+ bcs->emptycount = 0;
+#endif
+
+ dbg(DEBUG_INIT, "allocating bcs[%d]->skb", channel);
+ bcs->fcs = PPP_INITFCS;
+ bcs->inputstate = 0;
+ if (cs->ignoreframes) {
+ bcs->inputstate |= INS_skip_frame;
+ bcs->skb = NULL;
+ } else if ((bcs->skb = dev_alloc_skb(SBUFSIZE + HW_HDR_LEN)) != NULL)
+ skb_reserve(bcs->skb, HW_HDR_LEN);
+ else {
+ warn("could not allocate skb");
+ bcs->inputstate |= INS_skip_frame;
+ }
+
+ 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;
+
+ dbg(DEBUG_INIT, " setting up bcs[%d]->hw", channel);
+ if (cs->ops->initbcshw(bcs))
+ return bcs;
+
+//error:
+ dbg(DEBUG_INIT, " failed");
+
+ dbg(DEBUG_INIT, " freeing bcs[%d]->skb", channel);
+ if (bcs->skb)
+ dev_kfree_skb(bcs->skb);
+
+ return NULL;
+}
+
+/* gigaset_initcs
+ * 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
+ * parameters:
+ * drv hardware driver the device belongs to
+ * channels number of B channels supported by device
+ * onechannel !=0: B channel data and AT commands share one communication channel
+ * ==0: B channels have separate communication channels
+ * ignoreframes number of frames to ignore after setting up B channel
+ * cidmode !=0: start in CallID mode
+ * modulename name of driver module (used for I4L registration)
+ * 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 = NULL;
+ int i;
+
+ dbg(DEBUG_INIT, "allocating cs");
+ cs = alloc_cs(drv);
+ if (!cs)
+ goto error;
+ dbg(DEBUG_INIT, "allocating bcs[0..%d]", channels - 1);
+ cs->bcs = kmalloc(channels * sizeof(struct bc_state), GFP_KERNEL);
+ if (!cs->bcs)
+ goto error;
+ dbg(DEBUG_INIT, "allocating inbuf");
+ cs->inbuf = kmalloc(sizeof(struct inbuf_t), GFP_KERNEL);
+ if (!cs->inbuf)
+ goto error;
+
+ cs->cs_init = 0;
+ cs->channels = channels;
+ cs->onechannel = onechannel;
+ cs->ignoreframes = ignoreframes;
+ INIT_LIST_HEAD(&cs->temp_at_states);
+ atomic_set(&cs->running, 0);
+ init_timer(&cs->timer); /* clear next & prev */
+ spin_lock_init(&cs->ev_lock);
+ atomic_set(&cs->ev_tail, 0);
+ atomic_set(&cs->ev_head, 0);
+ init_MUTEX_LOCKED(&cs->sem);
+ tasklet_init(&cs->event_tasklet, &gigaset_handle_event, (unsigned long) cs);
+ atomic_set(&cs->commands_pending, 0);
+ cs->cur_at_seq = 0;
+ cs->gotfwver = -1;
+ cs->open_count = 0;
+ cs->tty = NULL;
+ atomic_set(&cs->cidmode, cidmode != 0);
+
+ //if(onechannel) { //FIXME
+ cs->tabnocid = gigaset_tab_nocid_m10x;
+ cs->tabcid = gigaset_tab_cid_m10x;
+ //} else {
+ // cs->tabnocid = gigaset_tab_nocid;
+ // cs->tabcid = gigaset_tab_cid;
+ //}
+
+ init_waitqueue_head(&cs->waitqueue);
+ cs->waiting = 0;
+
+ atomic_set(&cs->mode, M_UNKNOWN);
+ atomic_set(&cs->mstate, MS_UNINITIALIZED);
+
+ for (i = 0; i < channels; ++i) {
+ dbg(DEBUG_INIT, "setting up bcs[%d].read", i);
+ if (!gigaset_initbcs(cs->bcs + i, cs, i))
+ goto error;
+ }
+
+ ++cs->cs_init;
+
+ 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;
+
+ dbg(DEBUG_INIT, "setting up inbuf");
+ if (onechannel) { //FIXME distinction necessary?
+ gigaset_inbuf_init(cs->inbuf, cs->bcs, cs, INS_command);
+ } else
+ gigaset_inbuf_init(cs->inbuf, NULL, cs, INS_command);
+
+ atomic_set(&cs->connected, 0);
+
+ dbg(DEBUG_INIT, "setting up cmdbuf");
+ cs->cmdbuf = cs->lastcmdbuf = NULL;
+ spin_lock_init(&cs->cmdlock);
+ cs->curlen = 0;
+ cs->cmdbytes = 0;
+
+ /*
+ * Tell the ISDN4Linux subsystem (the LL) that
+ * a driver for a USB-Device is available !
+ * If this is done, "isdnctrl" is able to bind a device for this driver even
+ * if no physical usb-device is currently connected.
+ * But this device will just be accessable if a physical USB device is connected
+ * (via "gigaset_probe") .
+ */
+ dbg(DEBUG_INIT, "setting up iif");
+ if (!gigaset_register_to_LL(cs, modulename)) {
+ err("register_isdn=>error");
+ goto error;
+ }
+
+ make_valid(cs, VALID_ID);
+ ++cs->cs_init;
+ dbg(DEBUG_INIT, "setting up hw");
+ if (!cs->ops->initcshw(cs))
+ goto error;
+
+ ++cs->cs_init;
+
+ gigaset_if_init(cs);
+
+ atomic_set(&cs->running, 1);
+ cs->timer.data = (unsigned long) cs;
+ cs->timer.function = timer_tick;
+ cs->timer.expires = jiffies + GIG_TICK;
+ /* FIXME: can jiffies increase too much until the timer is added?
+ * Same problem(?) with mod_timer() in timer_tick(). */
+ add_timer(&cs->timer);
+
+ dbg(DEBUG_INIT, "cs initialized!");
+ up(&cs->sem);
+ return cs;
+
+error: if (cs)
+ up(&cs->sem);
+ dbg(DEBUG_INIT, "failed");
+ gigaset_freecs(cs);
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(gigaset_initcs);
+
+/* ReInitialize the b-channel structure */ /* e.g. called on hangup, disconnect */
+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); //FIXME
+ 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->fcs = PPP_INITFCS;
+ bcs->chstate = 0;
+
+ bcs->ignore = cs->ignoreframes;
+ if (bcs->ignore)
+ bcs->inputstate |= INS_skip_frame;
+
+
+ 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);
+
+ atomic_set(&cs->mode, M_UNKNOWN);
+ atomic_set(&cs->mstate, MS_UNINITIALIZED);
+
+ clear_at_state(&cs->at_state);
+ dealloc_at_states(cs);
+ free_strings(&cs->at_state);
+ gigaset_at_init(&cs->at_state, NULL, cs, 0);
+
+ kfree(cs->inbuf->rcvbuf);
+ cs->inbuf->rcvbuf = NULL;
+ cs->inbuf->inputstate = INS_command;
+ atomic_set(&cs->inbuf->head, 0);
+ atomic_set(&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;
+ atomic_set(&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))
+ break; //FIXME error handling
+ }
+
+ if (cs->waiting) {
+ cs->cmd_result = -ENODEV;
+ cs->waiting = 0;
+ wake_up_interruptible(&cs->waitqueue);
+ }
+}
+
+
+int gigaset_start(struct cardstate *cs)
+{
+ if (down_interruptible(&cs->sem))
+ return 0;
+ //info("USB device for Gigaset 307x now attached to Dev %d", ucs->minor);
+
+ atomic_set(&cs->connected, 1);
+
+ if (atomic_read(&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;
+ } else {
+ //FIXME use some saved values?
+ }
+
+ cs->waiting = 1;
+
+ if (!gigaset_add_event(cs, &cs->at_state, EV_START, NULL, 0, NULL)) {
+ cs->waiting = 0;
+ //FIXME what should we do?
+ goto error;
+ }
+
+ dbg(DEBUG_CMD, "scheduling START");
+ gigaset_schedule_event(cs);
+
+ wait_event(cs->waitqueue, !cs->waiting);
+
+ up(&cs->sem);
+ return 1;
+
+error:
+ up(&cs->sem);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(gigaset_start);
+
+void gigaset_shutdown(struct cardstate *cs)
+{
+ down(&cs->sem);
+
+ cs->waiting = 1;
+
+ if (!gigaset_add_event(cs, &cs->at_state, EV_SHUTDOWN, NULL, 0, NULL)) {
+ //FIXME what should we do?
+ goto exit;
+ }
+
+ dbg(DEBUG_CMD, "scheduling SHUTDOWN");
+ gigaset_schedule_event(cs);
+
+ if (wait_event_interruptible(cs->waitqueue, !cs->waiting)) {
+ warn("aborted");
+ //FIXME
+ }
+
+ if (atomic_read(&cs->mstate) != MS_LOCKED) {
+ //FIXME?
+ //gigaset_baud_rate(cs, B115200);
+ //gigaset_set_line_ctrl(cs, CS8);
+ //gigaset_set_modem_ctrl(cs, TIOCM_DTR|TIOCM_RTS, 0);
+ //cs->control_state = 0;
+ } else {
+ //FIXME use some saved values?
+ }
+
+ cleanup_cs(cs);
+
+exit:
+ up(&cs->sem);
+}
+EXPORT_SYMBOL_GPL(gigaset_shutdown);
+
+void gigaset_stop(struct cardstate *cs)
+{
+ down(&cs->sem);
+
+ atomic_set(&cs->connected, 0);
+
+ cs->waiting = 1;
+
+ if (!gigaset_add_event(cs, &cs->at_state, EV_STOP, NULL, 0, NULL)) {
+ //FIXME what should we do?
+ goto exit;
+ }
+
+ dbg(DEBUG_CMD, "scheduling STOP");
+ gigaset_schedule_event(cs);
+
+ if (wait_event_interruptible(cs->waitqueue, !cs->waiting)) {
+ warn("aborted");
+ //FIXME
+ }
+
+ /* Tell the LL that the device is not available .. */
+ gigaset_i4l_cmd(cs, ISDN_STAT_STOP); // FIXME move to event layer?
+
+ cleanup_cs(cs);
+
+exit:
+ up(&cs->sem);
+}
+EXPORT_SYMBOL_GPL(gigaset_stop);
+
+static LIST_HEAD(drivers);
+static spinlock_t driver_lock = SPIN_LOCK_UNLOCKED;
+
+struct cardstate *gigaset_get_cs_by_id(int id)
+{
+ unsigned long flags;
+ static struct cardstate *ret = NULL;
+ static 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) {
+ if (drv->flags[i] & VALID_ID) {
+ cs = drv->cs + i;
+ if (cs->myid == id)
+ ret = cs;
+ }
+ if (ret)
+ break;
+ }
+ spin_unlock(&drv->lock);
+ if (ret)
+ break;
+ }
+ spin_unlock_irqrestore(&driver_lock, flags);
+ return ret;
+}
+
+void gigaset_debugdrivers(void)
+{
+ unsigned long flags;
+ static struct cardstate *cs;
+ struct gigaset_driver *drv;
+ unsigned i;
+
+ spin_lock_irqsave(&driver_lock, flags);
+ list_for_each_entry(drv, &drivers, list) {
+ dbg(DEBUG_DRIVER, "driver %p", drv);
+ spin_lock(&drv->lock);
+ for (i = 0; i < drv->minors; ++i) {
+ dbg(DEBUG_DRIVER, " index %u", i);
+ dbg(DEBUG_DRIVER, " flags 0x%02x", drv->flags[i]);
+ cs = drv->cs + i;
+ dbg(DEBUG_DRIVER, " cardstate %p", cs);
+ dbg(DEBUG_DRIVER, " minor_index %u", cs->minor_index);
+ dbg(DEBUG_DRIVER, " driver %p", cs->driver);
+ dbg(DEBUG_DRIVER, " i4l id %d", cs->myid);
+ }
+ spin_unlock(&drv->lock);
+ }
+ spin_unlock_irqrestore(&driver_lock, flags);
+}
+EXPORT_SYMBOL_GPL(gigaset_debugdrivers);
+
+struct cardstate *gigaset_get_cs_by_tty(struct tty_struct *tty)
+{
+ if (tty->index < 0 || tty->index >= tty->driver->num)
+ return NULL;
+ return gigaset_get_cs_by_minor(tty->index + tty->driver->minor_start);
+}
+
+struct cardstate *gigaset_get_cs_by_minor(unsigned minor)
+{
+ unsigned long flags;
+ static 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->flags[index] & VALID_MINOR)
+ ret = drv->cs + index;
+ spin_unlock(&drv->lock);
+ if (ret)
+ break;
+ }
+ spin_unlock_irqrestore(&driver_lock, flags);
+ return ret;
+}
+
+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);
+ module_put(drv->owner);
+
+ kfree(drv->cs);
+ kfree(drv->flags);
+ kfree(drv);
+}
+EXPORT_SYMBOL_GPL(gigaset_freedriver);
+
+/* gigaset_initdriver
+ * Allocate and initialize gigaset_driver structure. Initialize interface.
+ * parameters:
+ * minor First minor number
+ * minors Number of minors this driver can handle
+ * procname Name of the driver (e.g. for /proc/tty/drivers, path in /proc/driver)
+ * devname Name of the device files (prefix without minor number)
+ * devfsname Devfs name of the device files without %d
+ * 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 char *devfsname,
+ 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;
+ if (!try_module_get(owner))
+ return NULL;
+
+ drv->cs = 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(minors * sizeof *drv->cs, GFP_KERNEL);
+ if (!drv->cs)
+ goto out1;
+ drv->flags = kmalloc(minors * sizeof *drv->flags, GFP_KERNEL);
+ if (!drv->flags)
+ goto out2;
+
+ for (i = 0; i < minors; ++i) {
+ drv->flags[i] = 0;
+ drv->cs[i].driver = drv;
+ drv->cs[i].ops = drv->ops;
+ drv->cs[i].minor_index = i;
+ }
+
+ gigaset_if_initdriver(drv, procname, devname, devfsname);
+
+ spin_lock_irqsave(&driver_lock, flags);
+ list_add(&drv->list, &drivers);
+ spin_unlock_irqrestore(&driver_lock, flags);
+
+ return drv;
+
+out2:
+ kfree(drv->cs);
+out1:
+ kfree(drv);
+ module_put(owner);
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(gigaset_initdriver);
+
+static struct cardstate *alloc_cs(struct gigaset_driver *drv)
+{
+ unsigned long flags;
+ unsigned i;
+ static struct cardstate *ret = NULL;
+
+ spin_lock_irqsave(&drv->lock, flags);
+ for (i = 0; i < drv->minors; ++i) {
+ if (!(drv->flags[i] & VALID_MINOR)) {
+ drv->flags[i] = VALID_MINOR;
+ ret = drv->cs + i;
+ }
+ if (ret)
+ break;
+ }
+ spin_unlock_irqrestore(&drv->lock, flags);
+ return ret;
+}
+
+static void free_cs(struct cardstate *cs)
+{
+ unsigned long flags;
+ struct gigaset_driver *drv = cs->driver;
+ spin_lock_irqsave(&drv->lock, flags);
+ drv->flags[cs->minor_index] = 0;
+ spin_unlock_irqrestore(&drv->lock, flags);
+}
+
+static void make_valid(struct cardstate *cs, unsigned mask)
+{
+ unsigned long flags;
+ struct gigaset_driver *drv = cs->driver;
+ spin_lock_irqsave(&drv->lock, flags);
+ drv->flags[cs->minor_index] |= 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);
+ drv->flags[cs->minor_index] &= ~mask;
+ spin_unlock_irqrestore(&drv->lock, flags);
+}
+
+/* For drivers without fixed assignment device<->cardstate (usb) */
+struct cardstate *gigaset_getunassignedcs(struct gigaset_driver *drv)
+{
+ unsigned long flags;
+ struct cardstate *cs = NULL;
+ unsigned i;
+
+ spin_lock_irqsave(&drv->lock, flags);
+ if (drv->blocked)
+ goto exit;
+ for (i = 0; i < drv->minors; ++i) {
+ if ((drv->flags[i] & VALID_MINOR) &&
+ !(drv->flags[i] & ASSIGNED)) {
+ drv->flags[i] |= ASSIGNED;
+ cs = drv->cs + i;
+ break;
+ }
+ }
+exit:
+ spin_unlock_irqrestore(&drv->lock, flags);
+ return cs;
+}
+EXPORT_SYMBOL_GPL(gigaset_getunassignedcs);
+
+void gigaset_unassign(struct cardstate *cs)
+{
+ unsigned long flags;
+ unsigned *minor_flags;
+ struct gigaset_driver *drv;
+
+ if (!cs)
+ return;
+ drv = cs->driver;
+ spin_lock_irqsave(&drv->lock, flags);
+ minor_flags = drv->flags + cs->minor_index;
+ if (*minor_flags & VALID_MINOR)
+ *minor_flags &= ~ASSIGNED;
+ spin_unlock_irqrestore(&drv->lock, flags);
+}
+EXPORT_SYMBOL_GPL(gigaset_unassign);
+
+void gigaset_blockdriver(struct gigaset_driver *drv)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&drv->lock, flags);
+ drv->blocked = 1;
+ spin_unlock_irqrestore(&drv->lock, flags);
+}
+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;
+
+ info(DRIVER_AUTHOR);
+ info(DRIVER_DESC);
+ return 0;
+}
+
+static void __exit gigaset_exit_module(void)
+{
+}
+
+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/isdn/gigaset/ev-layer.c b/drivers/isdn/gigaset/ev-layer.c
new file mode 100644
index 000000000000..fdcb80bb21c7
--- /dev/null
+++ b/drivers/isdn/gigaset/ev-layer.c
@@ -0,0 +1,1983 @@
+/*
+ * Stuff used by all variants of the driver
+ *
+ * Copyright (c) 2001 by Stefan Eilers <Eilers.Stefan@epost.de>,
+ * 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.
+ * =====================================================================
+ * ToDo: ...
+ * =====================================================================
+ * Version: $Id: ev-layer.c,v 1.4.2.18 2006/02/04 18:28:16 hjlipp Exp $
+ * =====================================================================
+ */
+
+#include "gigaset.h"
+
+/* ========================================================== */
+/* bit masks for pending commands */
+#define PC_INIT 0x004
+#define PC_DLE0 0x008
+#define PC_DLE1 0x010
+#define PC_CID 0x080
+#define PC_NOCID 0x100
+#define PC_HUP 0x002
+#define PC_DIAL 0x001
+#define PC_ACCEPT 0x040
+#define PC_SHUTDOWN 0x020
+#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_HEX 5
+#define RT_ZCAU 6
+
+/* Possible ASCII responses */
+#define RSP_OK 0
+//#define RSP_BUSY 1
+//#define RSP_CONNECT 2
+#define RSP_ZGCI 3
+#define RSP_RING 4
+#define RSP_ZAOC 5
+#define RSP_ZCSTR 6
+#define RSP_ZCFGT 7
+#define RSP_ZCFG 8
+#define RSP_ZCCR 9
+#define RSP_EMPTY 10
+#define RSP_ZLOG 11
+#define RSP_ZCAU 12
+#define RSP_ZMWI 13
+#define RSP_ZABINFO 14
+#define RSP_ZSMLSTCHG 15
+#define RSP_VAR 100
+#define RSP_ZSAU (RSP_VAR + VAR_ZSAU)
+#define RSP_ZDLE (RSP_VAR + VAR_ZDLE)
+#define RSP_ZVLS (RSP_VAR + VAR_ZVLS)
+#define RSP_ZCTP (RSP_VAR + VAR_ZCTP)
+#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_ERROR -1 /* ERROR */
+#define RSP_WRONG_CID -2 /* unknown cid in cmd */
+//#define RSP_EMPTY -3
+#define RSP_UNKNOWN -4 /* unknown response */
+#define RSP_FAIL -5 /* internal error */
+#define RSP_INVAL -6 /* invalid response */
+
+#define RSP_NONE -19
+#define RSP_STRING -20
+#define RSP_NULL -21
+//#define RSP_RETRYFAIL -22
+//#define RSP_RETRY -23
+//#define RSP_SKIP -24
+#define RSP_INIT -27
+#define RSP_ANY -26
+#define RSP_LAST -28
+#define RSP_NODEV -9
+
+/* 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_PROTO_L2 42
+#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_m10x[]= /* with dle mode */
+{
+ /* resp_code, min_ConState, max_ConState, parameter, new_ConState, timeout, action, command */
+
+ /* initialize device, set cid mode if possible */
+ //{RSP_INIT, -1, -1,100, 900, 0, {ACT_TEST}},
+ //{RSP_ERROR, 900,900, -1, 0, 0, {ACT_FAILINIT}},
+ //{RSP_OK, 900,900, -1, 100, INIT_TIMEOUT,
+ // {ACT_TIMEOUT}},
+
+ {RSP_INIT, -1, -1,SEQ_INIT, 100, INIT_TIMEOUT,
+ {ACT_TIMEOUT}}, /* wait until device is ready */
+
+ {EV_TIMEOUT, 100,100, -1, 101, 3, {0}, "Z\r"}, /* device in transparent mode? try to initialize it. */
+ {RSP_OK, 101,103, -1, 120, 5, {ACT_GETSTRING}, "+GMR\r"}, /* get version */
+
+ {EV_TIMEOUT, 101,101, -1, 102, 5, {0}, "Z\r"}, /* timeout => try once again. */
+ {RSP_ERROR, 101,101, -1, 102, 5, {0}, "Z\r"}, /* error => try once again. */
+
+ {EV_TIMEOUT, 102,102, -1, 108, 5, {ACT_SETDLE1}, "^SDLE=0\r"}, /* timeout => try again in DLE mode. */
+ {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}}, /* still timeout => connection in unimodem mode? */
+ {EV_TIMEOUT, 105,105, -1, 103, 5, {0}, "Z\r"},
+
+ {RSP_ERROR, 102,102, -1, 107, 5, {0}, "^GETPRE\r"}, /* ERROR on ATZ => maybe in config mode? */
+ {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}},
+#if 0
+ {EV_TIMEOUT, 120,121, -1, 130, 5, {ACT_FAILVER}, "^SGCI=1\r"},
+ {RSP_ERROR, 120,121, -1, 130, 5, {ACT_FAILVER}, "^SGCI=1\r"},
+ {RSP_OK, 121,121, -1, 130, 5, {ACT_GOTVER}, "^SGCI=1\r"},
+
+ {RSP_OK, 130,130, -1, 0, 0, {ACT_INIT}},
+ {RSP_ERROR, 130,130, -1, 0, 0, {ACT_FAILINIT}},
+ {EV_TIMEOUT, 130,130, -1, 0, 0, {ACT_FAILINIT}},
+#endif
+
+ /* 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, 202, 0, {ACT_ERROR}},//DELETE
+ {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,300, 901, 0, {ACT_TEST}},
+ //{RSP_ERROR, 901,901, -1, 0, 0, {ACT_FAILCID}},
+ //{RSP_OK, 901,901, -1, 301, 5, {0}, "^SGCI?\r"},
+
+ {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}, "^SGCI=0\r"},
+ {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 */
+#if 0
+ {RSP_INIT, 0, 0,SEQ_SHUTDOWN, 503, 5, {0}, "^SGCI=0\r"},
+ {RSP_OK, 503,503, -1, 504, 5, {0}, "Z\r"},
+#endif
+ {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}}, //FIXME
+ {EV_IF_LOCK, -1, -1, -1, -1,-1, {ACT_IF_LOCK}}, //FIXME
+ {EV_IF_VER, -1, -1, -1, -1,-1, {ACT_IF_VER}}, //FIXME
+ {EV_START, -1, -1, -1, -1,-1, {ACT_START}}, //FIXME
+ {EV_STOP, -1, -1, -1, -1,-1, {ACT_STOP}}, //FIXME
+ {EV_SHUTDOWN, -1, -1, -1, -1,-1, {ACT_SHUTDOWN}}, //FIXME
+
+ /* misc. */
+ {RSP_EMPTY, -1, -1, -1, -1,-1, {ACT_DEBUG}}, //FIXME
+ {RSP_ZCFGT, -1, -1, -1, -1,-1, {ACT_DEBUG}}, //FIXME
+ {RSP_ZCFG, -1, -1, -1, -1,-1, {ACT_DEBUG}}, //FIXME
+ {RSP_ZLOG, -1, -1, -1, -1,-1, {ACT_DEBUG}}, //FIXME
+ {RSP_ZMWI, -1, -1, -1, -1,-1, {ACT_DEBUG}}, //FIXME
+ {RSP_ZABINFO, -1, -1, -1, -1,-1, {ACT_DEBUG}}, //FIXME
+ {RSP_ZSMLSTCHG,-1, -1, -1, -1,-1, {ACT_DEBUG}}, //FIXME
+
+ {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_m10x[] = /* for M10x */
+{
+ /* resp_code, min_ConState, max_ConState, parameter, new_ConState, timeout, action, command */
+
+ /* dial */
+ {EV_DIAL, -1, -1, -1, -1,-1, {ACT_DIAL}}, //FIXME
+ {RSP_INIT, 0, 0,SEQ_DIAL, 601, 5, {ACT_CMD+AT_BC}},
+ {RSP_OK, 601,601, -1, 602, 5, {ACT_CMD+AT_HLC}},
+ {RSP_NULL, 602,602, -1, 603, 5, {ACT_CMD+AT_PROTO}},
+ {RSP_OK, 602,602, -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_OK, 605,605, -1, 606, 5, {ACT_CMD+AT_ISO}},
+ {RSP_NULL, 605,605, -1, 606, 5, {ACT_CMD+AT_ISO}},
+ {RSP_OK, 606,606, -1, 607, 5, {0}, "+VLS=17\r"}, /* set "Endgeraetemodus" */
+ {RSP_OK, 607,607, -1, 608,-1},
+ //{RSP_ZSAU, 608,608,ZSAU_PROCEEDING, 608, 0, {ACT_ERROR}},//DELETE
+ {RSP_ZSAU, 608,608,ZSAU_PROCEEDING, 609, 5, {ACT_CMD+AT_DIAL}},
+ {RSP_OK, 609,609, -1, 650, 0, {ACT_DIALING}},
+
+ {RSP_ZVLS, 608,608, 17, -1,-1, {ACT_DEBUG}},
+ {RSP_ZCTP, 609,609, -1, -1,-1, {ACT_DEBUG}},
+ {RSP_ZCPN, 609,609, -1, -1,-1, {ACT_DEBUG}},
+ {RSP_ERROR, 601,609, -1, 0, 0, {ACT_ABORTDIAL}},
+ {EV_TIMEOUT, 601,609, -1, 0, 0, {ACT_ABORTDIAL}},
+
+ /* dialing */
+ {RSP_ZCTP, 650,650, -1, -1,-1, {ACT_DEBUG}},
+ {RSP_ZCPN, 650,650, -1, -1,-1, {ACT_DEBUG}},
+ {RSP_ZSAU, 650,650,ZSAU_CALL_DELIVERED, -1,-1, {ACT_DEBUG}}, /* some devices don't send this */
+
+ /* connection established */
+ {RSP_ZSAU, 650,650,ZSAU_ACTIVE, 800,-1, {ACT_CONNECT}}, //FIXME -> DLE1
+ {RSP_ZSAU, 750,750,ZSAU_ACTIVE, 800,-1, {ACT_CONNECT}}, //FIXME -> DLE1
+
+ {EV_BC_OPEN, 800,800, -1, 800,-1, {ACT_NOTIFY_BC_UP}}, //FIXME new constate + timeout
+
+ /* remote hangup */
+ {RSP_ZSAU, 650,650,ZSAU_DISCONNECT_IND, 0, 0, {ACT_REMOTEREJECT}},
+ {RSP_ZSAU, 750,750,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}}, //FIXME
+ {RSP_INIT, -1, -1,SEQ_HUP, 401, 5, {0}, "+VLS=0\r"}, /* hang up */ //-1,-1?
+ {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}}, /* if not remote hup */
+ //{RSP_ZSAU, 403,403,ZSAU_NULL, 401, 0, {ACT_ERROR}}, //DELETE//FIXME -> DLE0 // should we do this _before_ hanging up for base driver?
+ {RSP_ZSAU, 403,403,ZSAU_NULL, 0, 0, {ACT_DISCONNECT}}, //FIXME -> DLE0 // should we do this _before_ hanging up for base driver?
+ {RSP_NODEV, 401,403, -1, 0, 0, {ACT_FAKEHUP}}, //FIXME -> DLE0 // should we do this _before_ hanging up for base driver?
+ {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}}, //FIXME new constate + timeout
+
+ /* 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}}, //FIXME
+ {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"}, /* set "Endgeraetemodus" */
+ {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_TIMEOUT, 750,750, -1, 0, 0, {ACT_CONNTIMEOUT}},
+
+ /* misc. */
+ {EV_PROTO_L2, -1, -1, -1, -1,-1, {ACT_PROTO_L2}}, //FIXME
+
+ {RSP_ZCON, -1, -1, -1, -1,-1, {ACT_DEBUG}}, //FIXME
+ {RSP_ZCCR, -1, -1, -1, -1,-1, {ACT_DEBUG}}, //FIXME
+ {RSP_ZAOC, -1, -1, -1, -1,-1, {ACT_DEBUG}}, //FIXME
+ {RSP_ZCSTR, -1, -1, -1, -1,-1, {ACT_DEBUG}}, //FIXME
+
+ {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}
+};
+
+
+#if 0
+static struct reply_t tab_nocid[]= /* no dle mode */ //FIXME aenderungen uebernehmen
+{
+ /* resp_code, min_ConState, max_ConState, parameter, new_ConState, timeout, action, command */
+
+ {RSP_ANY, -1, -1, -1, -1,-1, ACT_WARN, NULL},
+ {RSP_LAST,0,0,0,0,0,0}
+};
+
+static struct reply_t tab_cid[] = /* no dle mode */ //FIXME aenderungen uebernehmen
+{
+ /* resp_code, min_ConState, max_ConState, parameter, new_ConState, timeout, action, command */
+
+ {RSP_ANY, -1, -1, -1, -1,-1, ACT_WARN, NULL},
+ {RSP_LAST,0,0,0,0,0,0}
+};
+#endif
+
+static struct resp_type_t resp_type[]=
+{
+ /*{"", RSP_EMPTY, RT_NOTHING},*/
+ {"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},
+ {"ZCFGT", RSP_ZCFGT, RT_NUMBER},
+ {"ZCCR", RSP_ZCCR, RT_NUMBER},
+ {"ZMWI", RSP_ZMWI, 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},
+ {"ZAOC", RSP_ZAOC, RT_STRING},
+ {"ZCSTR", RSP_ZCSTR, RT_STRING},
+ {"ZCFG", RSP_ZCFG, RT_HEX},
+ {"ZLOG", RSP_ZLOG, RT_NOTHING},
+ {"ZABINFO", RSP_ZABINFO, RT_NOTHING},
+ {"ZSMLSTCHG", RSP_ZSMLSTCHG, RT_NOTHING},
+ {NULL,0,0}
+};
+
+/*
+ * Get integer from char-pointer
+ */
+static int isdn_getnum(char *p)
+{
+ int v = -1;
+
+ IFNULLRETVAL(p, -1);
+
+ dbg(DEBUG_TRANSCMD, "string: %s", p);
+
+ while (*p >= '0' && *p <= '9')
+ v = ((v < 0) ? 0 : (v * 10)) + (int) ((*p++) - '0');
+ if (*p)
+ v = -1; /* invalid Character */
+ return v;
+}
+
+/*
+ * Get integer from char-pointer
+ */
+static int isdn_gethex(char *p)
+{
+ int v = 0;
+ int c;
+
+ IFNULLRETVAL(p, -1);
+
+ dbg(DEBUG_TRANSCMD, "string: %s", p);
+
+ if (!*p)
+ return -1;
+
+ do {
+ if (v > (INT_MAX - 15) / 16)
+ return -1;
+ c = *p;
+ if (c >= '0' && c <= '9')
+ c -= '0';
+ else if (c >= 'a' && c <= 'f')
+ c -= 'a' - 10;
+ else if (c >= 'A' && c <= 'F')
+ c -= 'A' - 10;
+ else
+ return -1;
+ v = v * 16 + c;
+ } while (*++p);
+
+ return v;
+}
+
+static inline void new_index(atomic_t *index, int max)
+{
+ if (atomic_read(index) == max) //FIXME race?
+ atomic_set(index, 0);
+ else
+ atomic_inc(index);
+}
+
+/* retrieve CID from parsed response
+ * returns 0 if no CID, -1 if invalid CID, or CID value 1..65535
+ */
+static int cid_of_response(char *s)
+{
+ int cid;
+
+ if (s[-1] != ';')
+ return 0; /* no CID separator */
+ cid = isdn_getnum(s);
+ if (cid < 0)
+ return 0; /* CID not numeric */
+ if (cid < 1 || cid > 65535)
+ return -1; /* CID out of range */
+ return cid;
+ //FIXME is ;<digit>+ at end of non-CID response really impossible?
+}
+
+/* This function will be called via task queue from the callback handler.
+ * We received a modem response and have to handle it..
+ */
+void gigaset_handle_modem_response(struct cardstate *cs)
+{
+ unsigned char *argv[MAX_REC_PARAMS + 1];
+ int params;
+ int i, j;
+ struct resp_type_t *rt;
+ int curarg;
+ unsigned long flags;
+ unsigned next, tail, head;
+ struct event_t *event;
+ int resp_code;
+ int param_type;
+ int abort;
+ size_t len;
+ int cid;
+ int rawstring;
+
+ IFNULLRET(cs);
+
+ len = cs->cbytes;
+ if (!len) {
+ /* ignore additional LFs/CRs (M10x config mode or cx100) */
+ dbg(DEBUG_MCMD, "skipped EOL [%02X]", cs->respdata[len]);
+ return;
+ }
+ cs->respdata[len] = 0;
+ dbg(DEBUG_TRANSCMD, "raw string: '%s'", cs->respdata);
+ argv[0] = cs->respdata;
+ params = 1;
+ if (cs->at_state.getstring) {
+ /* getstring only allowed without cid at the moment */
+ cs->at_state.getstring = 0;
+ rawstring = 1;
+ cid = 0;
+ } else {
+ /* parse line */
+ for (i = 0; i < len; i++)
+ switch (cs->respdata[i]) {
+ case ';':
+ case ',':
+ case '=':
+ if (params > MAX_REC_PARAMS) {
+ warn("too many parameters in response");
+ /* need last parameter (might be CID) */
+ params--;
+ }
+ argv[params++] = cs->respdata + i + 1;
+ }
+
+ rawstring = 0;
+ cid = params > 1 ? cid_of_response(argv[params-1]) : 0;
+ if (cid < 0) {
+ gigaset_add_event(cs, &cs->at_state, RSP_INVAL,
+ NULL, 0, NULL);
+ return;
+ }
+
+ for (j = 1; j < params; ++j)
+ argv[j][-1] = 0;
+
+ dbg(DEBUG_TRANSCMD, "CMD received: %s", argv[0]);
+ if (cid) {
+ --params;
+ dbg(DEBUG_TRANSCMD, "CID: %s", argv[params]);
+ }
+ dbg(DEBUG_TRANSCMD, "available params: %d", params - 1);
+ for (j = 1; j < params; j++)
+ dbg(DEBUG_TRANSCMD, "param %d: %s", j, argv[j]);
+ }
+
+ spin_lock_irqsave(&cs->ev_lock, flags);
+ head = atomic_read(&cs->ev_head);
+ tail = atomic_read(&cs->ev_tail);
+
+ abort = 1;
+ curarg = 0;
+ while (curarg < params) {
+ next = (tail + 1) % MAX_EVENTS;
+ if (unlikely(next == head)) {
+ err("event queue full");
+ break;
+ }
+
+ event = cs->events + tail;
+ event->at_state = NULL;
+ event->cid = cid;
+ event->ptr = NULL;
+ event->arg = NULL;
+ tail = next;
+
+ if (rawstring) {
+ resp_code = RSP_STRING;
+ param_type = RT_STRING;
+ } else {
+ for (rt = resp_type; rt->response; ++rt)
+ if (!strcmp(argv[curarg], rt->response))
+ break;
+
+ if (!rt->response) {
+ event->type = RSP_UNKNOWN;
+ warn("unknown modem response: %s",
+ argv[curarg]);
+ break;
+ }
+
+ resp_code = rt->resp_code;
+ param_type = rt->type;
+ ++curarg;
+ }
+
+ event->type = resp_code;
+
+ switch (param_type) {
+ case RT_NOTHING:
+ break;
+ case RT_RING:
+ if (!cid) {
+ err("received RING without CID!");
+ event->type = RSP_INVAL;
+ abort = 1;
+ } else {
+ event->cid = 0;
+ event->parameter = cid;
+ abort = 0;
+ }
+ break;
+ case RT_ZSAU:
+ if (curarg >= params) {
+ event->parameter = ZSAU_NONE;
+ break;
+ }
+ if (!strcmp(argv[curarg], "OUTGOING_CALL_PROCEEDING"))
+ event->parameter = ZSAU_OUTGOING_CALL_PROCEEDING;
+ else if (!strcmp(argv[curarg], "CALL_DELIVERED"))
+ event->parameter = ZSAU_CALL_DELIVERED;
+ else if (!strcmp(argv[curarg], "ACTIVE"))
+ event->parameter = ZSAU_ACTIVE;
+ else if (!strcmp(argv[curarg], "DISCONNECT_IND"))
+ event->parameter = ZSAU_DISCONNECT_IND;
+ else if (!strcmp(argv[curarg], "NULL"))
+ event->parameter = ZSAU_NULL;
+ else if (!strcmp(argv[curarg], "DISCONNECT_REQ"))
+ event->parameter = ZSAU_DISCONNECT_REQ;
+ else {
+ event->parameter = ZSAU_UNKNOWN;
+ warn("%s: unknown parameter %s after ZSAU",
+ __func__, argv[curarg]);
+ }
+ ++curarg;
+ break;
+ case RT_STRING:
+ if (curarg < params) {
+ len = strlen(argv[curarg]) + 1;
+ event->ptr = kmalloc(len, GFP_ATOMIC);
+ if (event->ptr)
+ memcpy(event->ptr, argv[curarg], len);
+ else
+ err("no memory for string!");
+ ++curarg;
+ }
+#ifdef CONFIG_GIGASET_DEBUG
+ if (!event->ptr)
+ dbg(DEBUG_CMD, "string==NULL");
+ else
+ dbg(DEBUG_CMD,
+ "string==%s", (char *) event->ptr);
+#endif
+ break;
+ case RT_ZCAU:
+ event->parameter = -1;
+ if (curarg + 1 < params) {
+ i = isdn_gethex(argv[curarg]);
+ j = isdn_gethex(argv[curarg + 1]);
+ if (i >= 0 && i < 256 && j >= 0 && j < 256)
+ event->parameter = (unsigned) i << 8
+ | j;
+ curarg += 2;
+ } else
+ curarg = params - 1;
+ break;
+ case RT_NUMBER:
+ case RT_HEX:
+ if (curarg < params) {
+ if (param_type == RT_HEX)
+ event->parameter =
+ isdn_gethex(argv[curarg]);
+ else
+ event->parameter =
+ isdn_getnum(argv[curarg]);
+ ++curarg;
+ } else
+ event->parameter = -1;
+#ifdef CONFIG_GIGASET_DEBUG
+ dbg(DEBUG_CMD, "parameter==%d", event->parameter);
+#endif
+ break;
+ }
+
+ if (resp_code == RSP_ZDLE)
+ cs->dle = event->parameter;
+
+ if (abort)
+ break;
+ }
+
+ atomic_set(&cs->ev_tail, tail);
+ spin_unlock_irqrestore(&cs->ev_lock, flags);
+
+ if (curarg != params)
+ dbg(DEBUG_ANY, "invalid number of processed parameters: %d/%d",
+ curarg, params);
+}
+EXPORT_SYMBOL_GPL(gigaset_handle_modem_response);
+
+/* disconnect
+ * process closing of connection associated with given AT state structure
+ */
+static void disconnect(struct at_state_t **at_state_p)
+{
+ unsigned long flags;
+ struct bc_state *bcs;
+ struct cardstate *cs;
+
+ IFNULLRET(at_state_p);
+ IFNULLRET(*at_state_p);
+ bcs = (*at_state_p)->bcs;
+ cs = (*at_state_p)->cs;
+ IFNULLRET(cs);
+
+ new_index(&(*at_state_p)->seq_index, MAX_SEQ_INDEX);
+
+ /* revert to selected idle mode */
+ if (!atomic_read(&cs->cidmode)) {
+ cs->at_state.pending_commands |= PC_UMMODE;
+ atomic_set(&cs->commands_pending, 1); //FIXME
+ dbg(DEBUG_CMD, "Scheduling PC_UMMODE");
+ }
+
+ if (bcs) {
+ /* B channel assigned: invoke hardware specific handler */
+ cs->ops->close_bchannel(bcs);
+ } else {
+ /* no B channel assigned: just deallocate */
+ spin_lock_irqsave(&cs->lock, flags);
+ list_del(&(*at_state_p)->list);
+ kfree(*at_state_p);
+ *at_state_p = NULL;
+ spin_unlock_irqrestore(&cs->lock, flags);
+ }
+}
+
+/* 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() 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)) {
+ 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;
+ atomic_set(&cs->mode, mode);
+ atomic_set(&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;
+ atomic_set(&cs->commands_pending, 1);
+ }
+ }
+}
+
+static void schedule_init(struct cardstate *cs, int state)
+{
+ if (cs->at_state.pending_commands & PC_INIT) {
+ dbg(DEBUG_CMD, "not scheduling PC_INIT again");
+ return;
+ }
+ atomic_set(&cs->mstate, state);
+ atomic_set(&cs->mode, M_UNKNOWN);
+ gigaset_block_channels(cs);
+ cs->at_state.pending_commands |= PC_INIT;
+ atomic_set(&cs->commands_pending, 1);
+ dbg(DEBUG_CMD, "Scheduling PC_INIT");
+}
+
+/* Add "AT" to a command, add the cid, dle encode it, send the result to the hardware. */
+static void send_command(struct cardstate *cs, const char *cmd, int cid,
+ int dle, gfp_t kmallocflags)
+{
+ size_t cmdlen, buflen;
+ char *cmdpos, *cmdbuf, *cmdtail;
+
+ cmdlen = strlen(cmd);
+ buflen = 11 + cmdlen;
+
+ if (likely(buflen > cmdlen)) {
+ cmdbuf = kmalloc(buflen, kmallocflags);
+ if (likely(cmdbuf != NULL)) {
+ cmdpos = cmdbuf + 9;
+ cmdtail = cmdpos + cmdlen;
+ memcpy(cmdpos, cmd, cmdlen);
+
+ if (cid > 0 && cid <= 65535) {
+ do {
+ *--cmdpos = '0' + cid % 10;
+ cid /= 10;
+ ++cmdlen;
+ } while (cid);
+ }
+
+ cmdlen += 2;
+ *--cmdpos = 'T';
+ *--cmdpos = 'A';
+
+ if (dle) {
+ cmdlen += 4;
+ *--cmdpos = '(';
+ *--cmdpos = 0x10;
+ *cmdtail++ = 0x10;
+ *cmdtail++ = ')';
+ }
+
+ cs->ops->write_cmd(cs, cmdpos, cmdlen, NULL);
+ kfree(cmdbuf);
+ } else
+ err("no memory for command buffer");
+ } else
+ err("overflow in buflen");
+}
+
+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)
+{
+ IFNULLRET(bcs);
+ IFNULLRET(bcs->cs);
+
+ if (bcs->chstate & CHS_B_UP) {
+ bcs->chstate &= ~CHS_B_UP;
+ gigaset_i4l_channel_cmd(bcs, ISDN_STAT_BHUP);
+ }
+
+ if (bcs->chstate & (CHS_D_UP | CHS_NOTIFY_LL)) {
+ bcs->chstate &= ~(CHS_D_UP | CHS_NOTIFY_LL);
+ gigaset_i4l_channel_cmd(bcs, ISDN_STAT_DHUP);
+ }
+
+ gigaset_free_channel(bcs);
+
+ gigaset_bcs_reinit(bcs);
+}
+
+static void bchannel_up(struct bc_state *bcs)
+{
+ IFNULLRET(bcs);
+
+ if (!(bcs->chstate & CHS_D_UP)) {
+ notice("%s: D channel not up", __func__);
+ bcs->chstate |= CHS_D_UP;
+ gigaset_i4l_channel_cmd(bcs, ISDN_STAT_DCONN);
+ }
+
+ if (bcs->chstate & CHS_B_UP) {
+ notice("%s: B channel already up", __func__);
+ return;
+ }
+
+ bcs->chstate |= CHS_B_UP;
+ gigaset_i4l_channel_cmd(bcs, ISDN_STAT_BCONN);
+}
+
+static void start_dial(struct at_state_t *at_state, void *data, int seq_index)
+{
+ struct bc_state *bcs = at_state->bcs;
+ struct cardstate *cs = at_state->cs;
+ int retval;
+
+ bcs->chstate |= CHS_NOTIFY_LL;
+ //atomic_set(&bcs->status, BCS_INIT);
+
+ if (atomic_read(&at_state->seq_index) != seq_index)
+ goto error;
+
+ retval = gigaset_isdn_setup_dial(at_state, data);
+ if (retval != 0)
+ goto error;
+
+
+ at_state->pending_commands |= PC_CID;
+ dbg(DEBUG_CMD, "Scheduling PC_CID");
+//#ifdef GIG_MAYINITONDIAL
+// if (atomic_read(&cs->MState) == MS_UNKNOWN) {
+// cs->at_state.pending_commands |= PC_INIT;
+// dbg(DEBUG_CMD, "Scheduling PC_INIT");
+// }
+//#endif
+ atomic_set(&cs->commands_pending, 1); //FIXME
+ return;
+
+error:
+ at_state->pending_commands |= PC_NOCID;
+ dbg(DEBUG_CMD, "Scheduling PC_NOCID");
+ atomic_set(&cs->commands_pending, 1); //FIXME
+ return;
+}
+
+static void start_accept(struct at_state_t *at_state)
+{
+ struct cardstate *cs = at_state->cs;
+ int retval;
+
+ retval = gigaset_isdn_setup_accept(at_state);
+
+ if (retval == 0) {
+ at_state->pending_commands |= PC_ACCEPT;
+ dbg(DEBUG_CMD, "Scheduling PC_ACCEPT");
+ atomic_set(&cs->commands_pending, 1); //FIXME
+ } else {
+ //FIXME
+ at_state->pending_commands |= PC_HUP;
+ dbg(DEBUG_CMD, "Scheduling PC_HUP");
+ atomic_set(&cs->commands_pending, 1); //FIXME
+ }
+}
+
+static void do_start(struct cardstate *cs)
+{
+ gigaset_free_channels(cs);
+
+ if (atomic_read(&cs->mstate) != MS_LOCKED)
+ schedule_init(cs, MS_INIT);
+
+ gigaset_i4l_cmd(cs, ISDN_STAT_RUN);
+ // FIXME: not in locked mode
+ // FIXME 2: only after init sequence
+
+ cs->waiting = 0;
+ wake_up(&cs->waitqueue);
+}
+
+static void finish_shutdown(struct cardstate *cs)
+{
+ if (atomic_read(&cs->mstate) != MS_LOCKED) {
+ atomic_set(&cs->mstate, MS_UNINITIALIZED);
+ atomic_set(&cs->mode, M_UNKNOWN);
+ }
+
+ /* The rest is done by cleanup_cs () in user mode. */
+
+ cs->cmd_result = -ENODEV;
+ cs->waiting = 0;
+ wake_up_interruptible(&cs->waitqueue);
+}
+
+static void do_shutdown(struct cardstate *cs)
+{
+ gigaset_block_channels(cs);
+
+ if (atomic_read(&cs->mstate) == MS_READY) {
+ atomic_set(&cs->mstate, MS_SHUTDOWN);
+ cs->at_state.pending_commands |= PC_SHUTDOWN;
+ atomic_set(&cs->commands_pending, 1); //FIXME
+ dbg(DEBUG_CMD, "Scheduling PC_SHUTDOWN"); //FIXME
+ //gigaset_schedule_event(cs); //FIXME
+ } else
+ finish_shutdown(cs);
+}
+
+static void do_stop(struct cardstate *cs)
+{
+ 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 failure
+ */
+static int reinit_and_retry(struct cardstate *cs, int channel)
+{
+ int i;
+
+ if (--cs->retry_count <= 0)
+ return 0;
+
+ for (i = 0; i < cs->channels; ++i)
+ if (cs->bcs[i].at_state.cid > 0)
+ return 0;
+
+ if (channel < 0)
+ warn("Could not enter cid mode. Reinit device and try again.");
+ else {
+ warn("Could not get a call id. Reinit device and try again.");
+ cs->bcs[channel].at_state.pending_commands |= PC_CID;
+ }
+ schedule_init(cs, MS_INIT);
+ return 1;
+}
+
+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 **p_at_state)
+{
+ int retval;
+ struct at_state_t *at_state = *p_at_state;
+
+ retval = gigaset_isdn_icall(at_state);
+ switch (retval) {
+ case ICALL_ACCEPT:
+ break;
+ default:
+ err("internal error: disposition=%d", retval);
+ /* --v-- fall through --v-- */
+ 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;
+ atomic_set(&cs->commands_pending, 1);
+ break;
+ }
+}
+
+static int do_lock(struct cardstate *cs)
+{
+ int mode;
+ int i;
+
+ switch (atomic_read(&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))
+ return -EBUSY;
+
+ break;
+ case MS_LOCKED:
+ //retval = -EACCES;
+ break;
+ default:
+ return -EBUSY;
+ }
+
+ mode = atomic_read(&cs->mode);
+ atomic_set(&cs->mstate, MS_LOCKED);
+ atomic_set(&cs->mode, M_UNKNOWN);
+ //FIXME reset card state / at states / bcs states
+
+ return mode;
+}
+
+static int do_unlock(struct cardstate *cs)
+{
+ if (atomic_read(&cs->mstate) != MS_LOCKED)
+ return -EINVAL;
+
+ atomic_set(&cs->mstate, MS_UNINITIALIZED);
+ atomic_set(&cs->mode, M_UNKNOWN);
+ gigaset_free_channels(cs);
+ //FIXME reset card state / at states / bcs states
+ if (atomic_read(&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 at_state_t *at_state2;
+ 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:
+ //FIXME setup everything
+ cs->at_state.pending_commands &= ~PC_INIT;
+ cs->cur_at_seq = SEQ_NONE;
+ atomic_set(&cs->mode, M_UNIMODEM);
+ if (!atomic_read(&cs->cidmode)) {
+ gigaset_free_channels(cs);
+ atomic_set(&cs->mstate, MS_READY);
+ break;
+ }
+ cs->at_state.pending_commands |= PC_CIDMODE;
+ atomic_set(&cs->commands_pending, 1); //FIXME
+ dbg(DEBUG_CMD, "Scheduling PC_CIDMODE");
+ break;
+ case ACT_FAILINIT:
+ warn("Could not initialize the device.");
+ 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 (atomic_read(&cs->mstate) == MS_INIT ||
+ atomic_read(&cs->mstate) == MS_RECOVER) {
+ gigaset_free_channels(cs);
+ atomic_set(&cs->mstate, MS_READY);
+ }
+ atomic_set(&cs->mode, M_CID);
+ cs->cur_at_seq = SEQ_NONE;
+ break;
+ case ACT_UMODESET:
+ atomic_set(&cs->mode, M_UNIMODEM);
+ cs->cur_at_seq = SEQ_NONE;
+ break;
+ case ACT_FAILCMODE:
+ cs->cur_at_seq = SEQ_NONE;
+ if (atomic_read(&cs->mstate) == MS_INIT ||
+ atomic_read(&cs->mstate) == MS_RECOVER) {
+ init_failed(cs, M_UNKNOWN);
+ break;
+ }
+ if (!reinit_and_retry(cs, -1))
+ 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) */
+ cs->ops->write_cmd(cs, "+++", 3, NULL);
+ break;
+ case ACT_RING:
+ /* get fresh AT state structure for new CID */
+ at_state2 = get_free_channel(cs, ev->parameter);
+ if (!at_state2) {
+ warn("RING ignored: "
+ "could not allocate channel structure");
+ break;
+ }
+
+ /* initialize AT state structure
+ * note that bcs may be NULL if no B channel is free
+ */
+ at_state2->ConState = 700;
+ kfree(at_state2->str_var[STR_NMBR]);
+ at_state2->str_var[STR_NMBR] = NULL;
+ kfree(at_state2->str_var[STR_ZCPN]);
+ at_state2->str_var[STR_ZCPN] = NULL;
+ kfree(at_state2->str_var[STR_ZBC]);
+ at_state2->str_var[STR_ZBC] = NULL;
+ kfree(at_state2->str_var[STR_ZHLC]);
+ at_state2->str_var[STR_ZHLC] = NULL;
+ at_state2->int_var[VAR_ZCTP] = -1;
+
+ spin_lock_irqsave(&cs->lock, flags);
+ at_state2->timer_expires = RING_TIMEOUT;
+ at_state2->timer_active = 1;
+ spin_unlock_irqrestore(&cs->lock, flags);
+ break;
+ case ACT_ICALL:
+ handle_icall(cs, bcs, p_at_state);
+ at_state = *p_at_state;
+ break;
+ case ACT_FAILSDOWN:
+ warn("Could not shut down the device.");
+ /* 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;
+ atomic_set(&cs->commands_pending, 1);
+ break;
+ }
+ bcs->chstate |= CHS_D_UP;
+ gigaset_i4l_channel_cmd(bcs, ISDN_STAT_DCONN);
+ 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_i4l_channel_cmd(bcs, ISDN_STAT_DCONN);
+ 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 && 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;
+ atomic_set(&cs->commands_pending, 1);
+ } else {
+ disconnect(p_at_state);
+ at_state = *p_at_state;
+ }
+ 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;
+ at_state2 = &cs->bcs[cs->curchannel].at_state;
+ disconnect(&at_state2);
+ break;
+ case ACT_ABORTHUP:
+ cs->cur_at_seq = SEQ_NONE;
+ warn("Could not hang up.");
+ at_state->cid = -1;
+ if (bcs && cs->onechannel)
+ at_state->pending_commands |= PC_DLE0;
+ else {
+ disconnect(p_at_state);
+ at_state = *p_at_state;
+ }
+ schedule_init(cs, MS_RECOVER);
+ break;
+ case ACT_FAILDLE0:
+ cs->cur_at_seq = SEQ_NONE;
+ warn("Could not leave DLE mode.");
+ at_state2 = &cs->bcs[cs->curchannel].at_state;
+ disconnect(&at_state2);
+ schedule_init(cs, MS_RECOVER);
+ break;
+ case ACT_FAILDLE1:
+ cs->cur_at_seq = SEQ_NONE;
+ warn("Could not enter DLE mode. Try to hang up.");
+ channel = cs->curchannel;
+ cs->bcs[channel].at_state.pending_commands |= PC_HUP;
+ atomic_set(&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;
+ atomic_set(&cs->commands_pending, 1);
+ break;
+ }
+ /* fall through */
+ case ACT_FAILCID:
+ cs->cur_at_seq = SEQ_NONE;
+ channel = cs->curchannel;
+ if (!reinit_and_retry(cs, channel)) {
+ warn("Could not get a call id. Dialing not possible");
+ at_state2 = &cs->bcs[channel].at_state;
+ disconnect(&at_state2);
+ }
+ break;
+ case ACT_ABORTCID:
+ cs->cur_at_seq = SEQ_NONE;
+ at_state2 = &cs->bcs[cs->curchannel].at_state;
+ disconnect(&at_state2);
+ break;
+
+ case ACT_DIALING:
+ case ACT_ACCEPTED:
+ cs->cur_at_seq = SEQ_NONE;
+ break;
+
+ case ACT_ABORTACCEPT: /* hangup/error/timeout during ICALL processing */
+ disconnect(p_at_state);
+ at_state = *p_at_state;
+ break;
+
+ case ACT_ABORTDIAL: /* error/timeout during dial preparation */
+ cs->cur_at_seq = SEQ_NONE;
+ at_state->pending_commands |= PC_HUP;
+ atomic_set(&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;
+ atomic_set(&cs->commands_pending, 1);
+ break;
+ case ACT_GETSTRING: /* warning: RING, ZDLE, ... are not handled properly any more */
+ 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")) {
+ *p_genresp = 1;
+ *p_resp_code = RSP_ERROR;
+ 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;
+ }
+ /*at_state->getstring = 1;*/
+ cs->gotfwver = 0;
+ break;
+ case ACT_GOTVER:
+ if (cs->gotfwver == 0) {
+ cs->gotfwver = 1;
+ dbg(DEBUG_ANY,
+ "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;
+ err("could not read firmware version.");
+ break;
+#ifdef CONFIG_GIGASET_DEBUG
+ case ACT_ERROR:
+ *p_genresp = 1;
+ *p_resp_code = RSP_ERROR;
+ break;
+ case ACT_TEST:
+ {
+ static int count = 3; //2; //1;
+ *p_genresp = 1;
+ *p_resp_code = count ? RSP_ERROR : RSP_OK;
+ if (count > 0)
+ --count;
+ }
+ break;
+#endif
+ case ACT_DEBUG:
+ dbg(DEBUG_ANY, "%s: resp_code %d in ConState %d",
+ __func__, ev->type, at_state->ConState);
+ break;
+ case ACT_WARN:
+ warn("%s: resp_code %d in ConState %d!",
+ __func__, ev->type, at_state->ConState);
+ break;
+ case ACT_ZCAU:
+ warn("cause code %04x in connection state %d.",
+ ev->parameter, at_state->ConState);
+ break;
+
+ /* events from the LL */
+ case ACT_DIAL:
+ start_dial(at_state, ev->ptr, ev->parameter);
+ break;
+ case ACT_ACCEPT:
+ start_accept(at_state);
+ break;
+ case ACT_PROTO_L2:
+ dbg(DEBUG_CMD,
+ "set protocol to %u", (unsigned) ev->parameter);
+ at_state->bcs->proto2 = ev->parameter;
+ break;
+ case ACT_HUP:
+ at_state->pending_commands |= PC_HUP;
+ atomic_set(&cs->commands_pending, 1); //FIXME
+ dbg(DEBUG_CMD, "Scheduling PC_HUP");
+ break;
+
+ /* hotplug events */
+ case ACT_STOP:
+ do_stop(cs);
+ break;
+ case ACT_START:
+ do_start(cs);
+ break;
+
+ /* events from the interface */ // FIXME without ACT_xxxx?
+ 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 */ // FIXME without ACT_xxxx?
+ case ACT_PROC_CIDMODE:
+ if (ev->parameter != atomic_read(&cs->cidmode)) {
+ atomic_set(&cs->cidmode, ev->parameter);
+ if (ev->parameter) {
+ cs->at_state.pending_commands |= PC_CIDMODE;
+ dbg(DEBUG_CMD, "Scheduling PC_CIDMODE");
+ } else {
+ cs->at_state.pending_commands |= PC_UMMODE;
+ dbg(DEBUG_CMD, "Scheduling PC_UMMODE");
+ }
+ atomic_set(&cs->commands_pending, 1);
+ }
+ 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
+ err("%s: action==%d!", __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;
+ int sendcid;
+ struct at_state_t *at_state;
+ int index;
+ int curact;
+ unsigned long flags;
+
+ IFNULLRET(cs);
+ IFNULLRET(ev);
+
+ if (ev->cid >= 0) {
+ at_state = at_state_from_cid(cs, ev->cid);
+ if (!at_state) {
+ 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)) {
+ dbg(DEBUG_ANY,
+ "event for invalid at_state %p", at_state);
+ return;
+ }
+ }
+
+ dbg(DEBUG_CMD,
+ "connection state %d, event %d", at_state->ConState, ev->type);
+
+ bcs = at_state->bcs;
+ sendcid = at_state->cid;
+
+ /* Setting the pointer to the dial array */
+ rep = at_state->replystruct;
+ IFNULLRET(rep);
+
+ if (ev->type == EV_TIMEOUT) {
+ if (ev->parameter != atomic_read(&at_state->timer_index)
+ || !at_state->timer_active) {
+ ev->type = RSP_NONE; /* old timeout */
+ dbg(DEBUG_ANY, "old timeout");
+ } else if (!at_state->waiting)
+ dbg(DEBUG_ANY, "timeout occured");
+ else
+ dbg(DEBUG_ANY, "stopped waiting");
+ }
+
+ /* 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;
+ /* dbg (DEBUG_ANY, "rcode %d", rcode); */
+ if (rcode == RSP_LAST) {
+ /* found nothing...*/
+ warn("%s: rcode=RSP_LAST: resp_code %d in ConState %d!",
+ __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)
+ break; /* may be freed after disconnect */
+ }
+
+ if (at_state) {
+ /* 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; //FIXME
+ at_state->timer_active = 0; //FIXME
+ 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/*rep->command*/) {
+ if (atomic_read(&cs->connected))
+ send_command(cs, p_command,
+ sendcid, cs->dle,
+ GFP_ATOMIC);
+ 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;
+ new_index(&at_state->timer_index,
+ MAX_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;
+
+ IFNULLRET(cs);
+
+ atomic_set(&cs->commands_pending, 0);
+
+ if (cs->cur_at_seq) {
+ dbg(DEBUG_CMD, "not searching scheduled commands: busy");
+ return;
+ }
+
+ dbg(DEBUG_CMD, "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 (atomic_read(&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 */
+ if (cs->at_state.pending_commands == PC_UMMODE
+ && !atomic_read(&cs->cidmode)
+ && list_empty(&cs->temp_at_states)
+ && atomic_read(&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;
+ }
+ }
+ }
+ 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) {
+ 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; //FIXME
+ cs->inbuf->inputstate = INS_command;
+ //FIXME reset card state (or -> LOCK0)?
+ 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 (atomic_read(&cs->mode) == M_UNIMODEM) {
+#if 0
+ cs->retry_count = 2;
+#else
+ cs->retry_count = 1;
+#endif
+ 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 (atomic_read(&cs->mode)) {
+ case M_UNIMODEM:
+ cs->at_state.pending_commands |= PC_CIDMODE;
+ dbg(DEBUG_CMD, "Scheduling PC_CIDMODE");
+ atomic_set(&cs->commands_pending, 1);
+ return;
+#ifdef GIG_MAYINITONDIAL
+ case M_UNKNOWN:
+ schedule_init(cs, MS_INIT);
+ return;
+#endif
+ }
+ bcs->at_state.pending_commands &= ~PC_CID;
+ cs->curchannel = bcs->channel;
+#ifdef GIG_RETRYCID
+ cs->retry_count = 2;
+#else
+ cs->retry_count = 1;
+#endif
+ 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;
+
+ /* no locking needed (only one reader) */
+ head = atomic_read(&cs->ev_head);
+
+ for (i = 0; i < 2 * MAX_EVENTS; ++i) {
+ tail = atomic_read(&cs->ev_tail);
+ if (tail == head) {
+ if (!check_flags && !atomic_read(&cs->commands_pending))
+ break;
+ check_flags = 0;
+ process_command_flags(cs);
+ tail = atomic_read(&cs->ev_tail);
+ if (tail == head) {
+ if (!atomic_read(&cs->commands_pending))
+ break;
+ continue;
+ }
+ }
+
+ ev = cs->events + head;
+ was_busy = cs->cur_at_seq != SEQ_NONE;
+ process_event(cs, ev);
+ kfree(ev->ptr);
+ ev->ptr = NULL;
+ if (was_busy && cs->cur_at_seq == SEQ_NONE)
+ check_flags = 1;
+
+ head = (head + 1) % MAX_EVENTS;
+ atomic_set(&cs->ev_head, head);
+ }
+
+ if (i == 2 * MAX_EVENTS) {
+ err("infinite loop in process_events; aborting.");
+ }
+}
+
+/* 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;
+
+ IFNULLRET(cs);
+ IFNULLRET(cs->inbuf);
+
+ /* handle incoming data on control/common channel */
+ if (atomic_read(&cs->inbuf->head) != atomic_read(&cs->inbuf->tail)) {
+ dbg(DEBUG_INTR, "processing new data");
+ cs->ops->handle_input(cs->inbuf);
+ }
+
+ process_events(cs);
+}
diff --git a/drivers/isdn/gigaset/gigaset.h b/drivers/isdn/gigaset/gigaset.h
new file mode 100644
index 000000000000..729edcdb6dac
--- /dev/null
+++ b/drivers/isdn/gigaset/gigaset.h
@@ -0,0 +1,938 @@
+/* Siemens Gigaset 307x driver
+ * Common header file for all connection variants
+ *
+ * Written by Stefan Eilers <Eilers.Stefan@epost.de>
+ * and Hansjoerg Lipp <hjlipp@web.de>
+ *
+ * Version: $Id: gigaset.h,v 1.97.4.26 2006/02/04 18:28:16 hjlipp Exp $
+ * ===========================================================================
+ */
+
+#ifndef GIGASET_H
+#define GIGASET_H
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/compiler.h>
+#include <linux/types.h>
+#include <asm/atomic.h>
+#include <linux/spinlock.h>
+#include <linux/isdnif.h>
+#include <linux/usb.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>
+
+#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 512 /* Max. size of a response string */
+#define HW_HDR_LEN 2 /* Header size used to store ack info */
+
+#define MAX_EVENTS 64 /* size of event queue */
+
+#define RBUFSIZE 8192
+#define SBUFSIZE 4096 /* sk_buff payload size */
+
+#define MAX_BUF_SIZE (SBUFSIZE - 2) /* Max. size of a data packet from LL */
+#define TRANSBUFSIZE 768 /* bytes per skb for transparent receive */
+
+/* compile time options */
+#define GIG_MAJOR 0
+
+#define GIG_MAYINITONDIAL
+#define GIG_RETRYCID
+#define GIG_X75
+
+#define MAX_TIMER_INDEX 1000
+#define MAX_SEQ_INDEX 1000
+
+#define GIG_TICK (HZ / 10)
+
+/* 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
+
+#define IFNULL(a) if (unlikely(!(a)))
+#define IFNULLRET(a) if (unlikely(!(a))) {err("%s==NULL at %s:%d!", #a, __FILE__, __LINE__); return; }
+#define IFNULLRETVAL(a,b) if (unlikely(!(a))) {err("%s==NULL at %s:%d!", #a, __FILE__, __LINE__); return (b); }
+#define IFNULLCONT(a) if (unlikely(!(a))) {err("%s==NULL at %s:%d!", #a, __FILE__, __LINE__); continue; }
+#define IFNULLGOTO(a,b) if (unlikely(!(a))) {err("%s==NULL at %s:%d!", #a, __FILE__, __LINE__); goto b; }
+
+extern int gigaset_debuglevel; /* "needs" cast to (enum debuglevel) */
+
+/* any combination of these can be given with the 'debug=' parameter to insmod, e.g.
+ * 'insmod usb_gigaset.o debug=0x2c' will set DEBUG_OPEN, DEBUG_CMD and DEBUG_INTR. */
+enum debuglevel { /* up to 24 bits (atomic_t) */
+ DEBUG_REG = 0x0002, /* serial port I/O register operations */
+ DEBUG_OPEN = 0x0004, /* open/close serial port */
+ DEBUG_INTR = 0x0008, /* interrupt processing */
+ DEBUG_INTR_DUMP = 0x0010, /* Activating hexdump debug output on interrupt
+ requests, not available as run-time option */
+ 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_INTR_0 = 0x00200, /* serial port output interrupt processing */
+ DEBUG_DRIVER = 0x00400, /* driver structure */
+ DEBUG_HDLC = 0x00800, /* M10x HDLC processing */
+ DEBUG_WRITE = 0x01000, /* M105 data write */
+ 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_LOCK = 0x10000, /* semaphore operations */
+ 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 DEBUG_DEFAULT (DEBUG_INIT | DEBUG_TRANSCMD | DEBUG_CMD | DEBUG_USBREQ)
+//#define DEBUG_DEFAULT (DEBUG_LOCK | DEBUG_INIT | DEBUG_TRANSCMD | DEBUG_CMD | DEBUF_IF | DEBUG_DRIVER | DEBUG_OUTPUT | DEBUG_INTR)
+#else
+#define DEBUG_DEFAULT 0
+#endif
+
+/* redefine syslog macros to prepend module name instead of entire source path */
+/* The space before the comma in ", ##" is needed by gcc 2.95 */
+#undef info
+#define info(format, arg...) printk(KERN_INFO "%s: " format "\n", THIS_MODULE ? THIS_MODULE->name : "gigaset_hw" , ## arg)
+
+#undef notice
+#define notice(format, arg...) printk(KERN_NOTICE "%s: " format "\n", THIS_MODULE ? THIS_MODULE->name : "gigaset_hw" , ## arg)
+
+#undef warn
+#define warn(format, arg...) printk(KERN_WARNING "%s: " format "\n", THIS_MODULE ? THIS_MODULE->name : "gigaset_hw" , ## arg)
+
+#undef err
+#define err(format, arg...) printk(KERN_ERR "%s: " format "\n", THIS_MODULE ? THIS_MODULE->name : "gigaset_hw" , ## arg)
+
+#undef dbg
+#ifdef CONFIG_GIGASET_DEBUG
+#define dbg(level, format, arg...) do { if (unlikely(((enum debuglevel)gigaset_debuglevel) & (level))) \
+ printk(KERN_DEBUG "%s: " format "\n", THIS_MODULE ? THIS_MODULE->name : "gigaset_hw" , ## arg); } while (0)
+#else
+#define dbg(level, format, arg...) do {} while (0)
+#endif
+
+void gigaset_dbg_buffer(enum debuglevel level, const unsigned char *msg,
+ size_t len, const unsigned char *buf, int from_user);
+
+/* connection state */
+#define ZSAU_NONE 0
+#define ZSAU_DISCONNECT_IND 4
+#define ZSAU_OUTGOING_CALL_PROCEEDING 1
+#define ZSAU_PROCEEDING 1
+#define ZSAU_CALL_DELIVERED 2
+#define ZSAU_ACTIVE 3
+#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)
+
+/* int-in-events 3070 */
+#define HD_B1_FLOW_CONTROL 0x80
+#define HD_B2_FLOW_CONTROL 0x81
+#define HD_RECEIVEATDATA_ACK (0x35) // 3070 // att: HD_RECEIVE>>AT<<DATA_ACK
+#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
+// Powermangment
+#define HD_SUSPEND_END (0x61) // ISurf USB
+// Configuration
+#define HD_RESET_INTERRUPT_PIPE_ACK (0xFF) // ISurf USB + 3070
+
+/* control requests 3070 */
+#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
+
+/* 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 isochronous input buffer per URB */
+#define BAS_OUTBUFSIZE 4096 /* size of common isochronous output buffer */
+#define BAS_OUTBUFPAD BAS_MAXFRAME /* size of pad area for isochronous output buffer */
+
+#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_HLC 6
+#define AT_NUM 7
+
+/* variables in struct at_state_t */
+#define VAR_ZSAU 0
+#define VAR_ZDLE 1
+#define VAR_ZVLS 2
+#define VAR_ZCTP 3
+#define VAR_NUM 4
+
+#define STR_NMBR 0
+#define STR_ZCPN 1
+#define STR_ZCON 2
+#define STR_ZBC 3
+#define STR_ZHLC 4
+#define STR_NUM 5
+
+#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_PROTO_L2 -113
+#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
+#define INS_DLE_char 0x0002
+#define INS_byte_stuff 0x0004
+#define INS_have_data 0x0008
+#define INS_skip_frame 0x0010
+#define INS_DLE_command 0x0020
+#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 */
+
+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_m10x[];
+extern struct reply_t gigaset_tab_nocid_m10x[];
+
+struct inbuf_t {
+ unsigned char *rcvbuf; /* usb-gigaset receive buffer */
+ struct bc_state *bcs;
+ struct cardstate *cs;
+ int inputstate;
+
+ atomic_t 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
+ * - optionally, the following statistics on the buffer's usage can be collected:
+ * maxfill: maximum number of bytes occupied
+ * idlefills: number of times a frame of idle bytes is prepared
+ * emptygets: number of times the buffer was empty when a data frame was requested
+ * backtoback: number of times two data packets were entered into the buffer
+ * without intervening idle flags
+ * nakedback: set if no idle flags have been inserted since the last data packet
+ */
+struct isowbuf_t {
+ atomic_t read;
+ atomic_t nextread;
+ atomic_t 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
+ */
+struct isow_urbctx_t {
+ struct urb *urb;
+ struct bc_state *bcs;
+ int limit;
+};
+
+/* 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;
+ atomic_t 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 */
+ atomic_t seq_index;
+
+ struct cardstate *cs;
+ struct bc_state *bcs;
+};
+
+struct resp_type_t {
+ unsigned char *response;
+ int resp_code; /* RSP_XXXX */
+ int type; /* RT_XXXX */
+};
+
+struct prot_skb {
+ atomic_t empty;
+ struct semaphore *sem;
+ struct sk_buff *skb;
+};
+
+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;
+ unsigned long rcvbytes;
+
+ __u16 fcs;
+ struct sk_buff *skb;
+ int inputstate; /* see INS_XXXX */
+
+ int channel;
+
+ struct cardstate *cs;
+
+ unsigned chstate; /* bitmap (CHS_*) */
+ int ignore;
+ unsigned proto2; /* Layer 2 protocol (ISDN_PROTO_L2_*) */
+ char *commands[AT_NUM]; /* see AT_XXXX */
+
+#ifdef CONFIG_GIGASET_DEBUG
+ int emptycount;
+#endif
+ int busy;
+ int use_count;
+
+ /* hardware drivers */
+ union {
+ struct ser_bc_state *ser; /* private data of serial hardware driver */
+ struct usb_bc_state *usb; /* private data of usb hardware driver */
+ struct bas_bc_state *bas;
+ } hw;
+};
+
+struct cardstate {
+ struct gigaset_driver *driver;
+ unsigned minor_index;
+
+ const struct gigaset_ops *ops;
+
+ /* Stuff to handle communication */
+ //wait_queue_head_t initwait;
+ wait_queue_head_t waitqueue;
+ int waiting;
+ atomic_t mode; /* see M_XXXX */
+ atomic_t 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;
+
+ unsigned open_count;
+ struct tty_struct *tty;
+ struct tasklet_struct if_wake_tasklet;
+ unsigned control_state;
+
+ unsigned fwver[4];
+ int gotfwver;
+
+ atomic_t running; /* !=0 if events are handled */
+ atomic_t connected; /* !=0 if hardware is connected */
+
+ atomic_t cidmode;
+
+ int myid; /* id for communication with LL */
+ isdn_if iif;
+
+ struct reply_t *tabnocid;
+ struct reply_t *tabcid;
+ int cs_init;
+ int ignoreframes; /* frames to ignore after setting up the B channel */
+ struct semaphore sem; /* 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 modem commands/responses are dle encoded */
+ int cur_at_seq; /* sequence of AT commands being processed */
+ int curchannel; /* channel, those commands are meant for */
+ atomic_t 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 incomming 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];
+ atomic_t ev_tail, ev_head;
+ spinlock_t ev_lock;
+
+ /* current modem response */
+ unsigned char respdata[MAX_RESP_SIZE];
+ unsigned cbytes;
+
+ /* hardware drivers */
+ union {
+ struct usb_cardstate *usb; /* private data of USB hardware driver */
+ struct ser_cardstate *ser; /* private data of serial hardware driver */
+ struct bas_cardstate *bas; /* private data of base hardware driver */
+ } hw;
+};
+
+struct gigaset_driver {
+ struct list_head list;
+ spinlock_t lock; /* locks minor tables and blocked */
+ //struct semaphore sem; /* locks this structure */
+ struct tty_driver *tty;
+ unsigned have_tty;
+ unsigned minor;
+ unsigned minors;
+ struct cardstate *cs;
+ unsigned *flags;
+ 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 */
+ atomic_t 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 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,
+ const unsigned char *buf, int len,
+ struct tasklet_struct *wake_tasklet);
+
+ /* 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 */
+ int (*freebcshw)(struct bc_state *bcs);
+
+ /* Called by gigaset_stop() or 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 by gigaset_stop() for killing URBs, shutting down the device, ...
+ // hardwareup: ==0: don't try to shut down the device, hardware is really not accessible
+ // !=0: hardware still up */
+ //void (*stophw)(struct cardstate *cs, int hardwareup);
+
+ /* 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 i4l.c to put an skb into the send-queue. */
+ 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 i4l.c to put an skb into the send-queue.
+ * After sending gigaset_skb_sent() should be called. */
+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 i4l.c to put an skb into the send-queue.
+ * After sending gigaset_skb_sent() should be called. */
+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 i4l.c/gigaset.h
+ */
+
+/* Called by gigaset_initcs() for setting up with the isdn4linux subsystem */
+int gigaset_register_to_LL(struct cardstate *cs, const char *isdnid);
+
+/* Called from xxx-gigaset.c to indicate completion of sending an skb */
+void gigaset_skb_sent(struct bc_state *bcs, struct sk_buff *skb);
+
+/* Called from common.c/ev-layer.c to indicate events relevant to the LL */
+int gigaset_isdn_icall(struct at_state_t *at_state);
+int gigaset_isdn_setup_accept(struct at_state_t *at_state);
+int gigaset_isdn_setup_dial(struct at_state_t *at_state, void *data);
+
+void gigaset_i4l_cmd(struct cardstate *cs, int cmd);
+void gigaset_i4l_channel_cmd(struct bc_state *bcs, int cmd);
+
+
+static inline void gigaset_isdn_rcv_err(struct bc_state *bcs)
+{
+ isdn_ctrl response;
+
+ /* error -> LL */
+ dbg(DEBUG_CMD, "sending L1ERR");
+ response.driver = bcs->cs->myid;
+ response.command = ISDN_STAT_L1ERR;
+ response.arg = bcs->channel;
+ response.parm.errcode = ISDN_STAT_L1ERR_RECV;
+ bcs->cs->iif.statcallb(&response);
+}
+
+/* ===========================================================================
+ * 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 usb_interface *interface);
+void gigaset_free_dev_sysfs(struct usb_interface *interface);
+
+/* ===========================================================================
+ * 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);
+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 char *devfsname,
+ const struct gigaset_ops *ops,
+ struct module *owner);
+
+/* Deallocate driver structure. */
+void gigaset_freedriver(struct gigaset_driver *drv);
+void gigaset_debugdrivers(void);
+struct cardstate *gigaset_get_cs_by_minor(unsigned minor);
+struct cardstate *gigaset_get_cs_by_tty(struct tty_struct *tty);
+struct cardstate *gigaset_get_cs_by_id(int id);
+
+/* For drivers without fixed assignment device<->cardstate (usb) */
+struct cardstate *gigaset_getunassignedcs(struct gigaset_driver *drv);
+void gigaset_unassign(struct cardstate *cs);
+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. */
+void gigaset_shutdown(struct cardstate *cs);
+
+/* Tell common.c that an skb has been sent. */
+void gigaset_skb_sent(struct bc_state *bcs, struct sk_buff *skb);
+
+/* 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); //0: success <0: errorcode
+
+/* 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 (atomic_read(&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);
+
+ dbg(DEBUG_CMD, "scheduling BC_CLOSED");
+ 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);
+
+ dbg(DEBUG_CMD, "scheduling BC_OPEN");
+ gigaset_schedule_event(bcs->cs);
+}
+
+/* handling routines for sk_buff */
+/* ============================= */
+
+/* private version of __skb_put()
+ * append 'len' bytes to the content of 'skb', already knowing that the
+ * existing buffer can accomodate them
+ * returns a pointer to the location where the new bytes should be copied to
+ * This function does not take any locks so it must be called with the
+ * appropriate locks held only.
+ */
+static inline unsigned char *gigaset_skb_put_quick(struct sk_buff *skb,
+ unsigned int len)
+{
+ unsigned char *tmp = skb->tail;
+ /*SKB_LINEAR_ASSERT(skb);*/ /* not needed here */
+ skb->tail += len;
+ skb->len += len;
+ return tmp;
+}
+
+/* pass received skb to LL
+ * Warning: skb must not be accessed anymore!
+ */
+static inline void gigaset_rcv_skb(struct sk_buff *skb,
+ struct cardstate *cs,
+ struct bc_state *bcs)
+{
+ cs->iif.rcvcallb_skb(cs->myid, bcs->channel, skb);
+ bcs->trans_down++;
+}
+
+/* handle reception of corrupted skb
+ * Warning: skb must not be accessed anymore!
+ */
+static inline void gigaset_rcv_error(struct sk_buff *procskb,
+ struct cardstate *cs,
+ struct bc_state *bcs)
+{
+ if (procskb)
+ dev_kfree_skb(procskb);
+
+ if (bcs->ignore)
+ --bcs->ignore;
+ else {
+ ++bcs->corrupted;
+ gigaset_isdn_rcv_err(bcs);
+ }
+}
+
+
+/* bitwise byte inversion table */
+extern __u8 gigaset_invtab[]; /* in common.c */
+
+
+/* append received bytes to inbuf */
+static inline int gigaset_fill_inbuf(struct inbuf_t *inbuf,
+ const unsigned char *src,
+ unsigned numbytes)
+{
+ unsigned n, head, tail, bytesleft;
+
+ dbg(DEBUG_INTR, "received %u bytes", numbytes);
+
+ if (!numbytes)
+ return 0;
+
+ bytesleft = numbytes;
+ tail = atomic_read(&inbuf->tail);
+ head = atomic_read(&inbuf->head);
+ 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) {
+ err("buffer overflow (%u bytes lost)", bytesleft);
+ break;
+ }
+ if (n > bytesleft)
+ n = bytesleft;
+ memcpy(inbuf->data + tail, src, n);
+ bytesleft -= n;
+ tail = (tail + n) % RBUFSIZE;
+ src += n;
+ }
+ dbg(DEBUG_INTR, "setting tail to %u", tail);
+ atomic_set(&inbuf->tail, tail);
+ return numbytes != bytesleft;
+}
+
+/* ===========================================================================
+ * Functions implemented in interface.c
+ */
+
+/* initialize interface */
+void gigaset_if_initdriver(struct gigaset_driver *drv, const char *procname,
+ const char *devname, const char *devfsname);
+/* 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/isdn/gigaset/i4l.c b/drivers/isdn/gigaset/i4l.c
new file mode 100644
index 000000000000..731a675f21b0
--- /dev/null
+++ b/drivers/isdn/gigaset/i4l.c
@@ -0,0 +1,567 @@
+/*
+ * Stuff used by all variants of the driver
+ *
+ * Copyright (c) 2001 by Stefan Eilers (Eilers.Stefan@epost.de),
+ * 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.
+ * =====================================================================
+ * ToDo: ...
+ * =====================================================================
+ * Version: $Id: i4l.c,v 1.3.2.9 2006/02/04 18:28:16 hjlipp Exp $
+ * =====================================================================
+ */
+
+#include "gigaset.h"
+
+/* == Handling of I4L IO ============================================================================*/
+
+/* writebuf_from_LL
+ * called by LL to transmit data on an open channel
+ * inserts the buffer data into the send queue and starts the transmission
+ * Note that this operation must not sleep!
+ * When the buffer is processed completely, gigaset_skb_sent() should be called.
+ * parameters:
+ * driverID driver ID as assigned by LL
+ * channel channel number
+ * ack if != 0 LL wants to be notified on completion via statcallb(ISDN_STAT_BSENT)
+ * skb skb containing data to send
+ * return value:
+ * number of accepted bytes
+ * 0 if temporarily unable to accept data (out of buffer space)
+ * <0 on error (eg. -EINVAL)
+ */
+static int writebuf_from_LL(int driverID, int channel, int ack, struct sk_buff *skb)
+{
+ struct cardstate *cs;
+ struct bc_state *bcs;
+ unsigned len;
+ unsigned skblen;
+
+ if (!(cs = gigaset_get_cs_by_id(driverID))) {
+ err("%s: invalid driver ID (%d)", __func__, driverID);
+ return -ENODEV;
+ }
+ if (channel < 0 || channel >= cs->channels) {
+ err("%s: invalid channel ID (%d)", __func__, channel);
+ return -ENODEV;
+ }
+ bcs = &cs->bcs[channel];
+ len = skb->len;
+
+ dbg(DEBUG_LLDATA,
+ "Receiving data from LL (id: %d, channel: %d, ack: %d, size: %d)",
+ driverID, channel, ack, len);
+
+ if (!len) {
+ if (ack)
+ warn("not ACKing empty packet from LL");
+ return 0;
+ }
+ if (len > MAX_BUF_SIZE) {
+ err("%s: packet too large (%d bytes)", __func__, channel);
+ return -EINVAL;
+ }
+
+ if (!atomic_read(&cs->connected))
+ return -ENODEV;
+
+ skblen = ack ? len : 0;
+ skb->head[0] = skblen & 0xff;
+ skb->head[1] = skblen >> 8;
+ dbg(DEBUG_MCMD, "skb: len=%u, skblen=%u: %02x %02x", len, skblen,
+ (unsigned) skb->head[0], (unsigned) skb->head[1]);
+
+ /* pass to device-specific module */
+ return cs->ops->send_skb(bcs, skb);
+}
+
+void gigaset_skb_sent(struct bc_state *bcs, struct sk_buff *skb)
+{
+ unsigned len;
+ isdn_ctrl response;
+
+ ++bcs->trans_up;
+
+ if (skb->len)
+ warn("%s: skb->len==%d", __func__, skb->len);
+
+ len = (unsigned char) skb->head[0] |
+ (unsigned) (unsigned char) skb->head[1] << 8;
+ if (len) {
+ dbg(DEBUG_MCMD,
+ "Acknowledge sending to LL (id: %d, channel: %d size: %u)",
+ bcs->cs->myid, bcs->channel, len);
+
+ response.driver = bcs->cs->myid;
+ response.command = ISDN_STAT_BSENT;
+ response.arg = bcs->channel;
+ response.parm.length = len;
+ bcs->cs->iif.statcallb(&response);
+ }
+}
+EXPORT_SYMBOL_GPL(gigaset_skb_sent);
+
+/* This function will be called by LL to send commands
+ * NOTE: LL ignores the returned value, for commands other than ISDN_CMD_IOCTL,
+ * so don't put too much effort into it.
+ */
+static int command_from_LL(isdn_ctrl *cntrl)
+{
+ struct cardstate *cs = gigaset_get_cs_by_id(cntrl->driver);
+ //isdn_ctrl response;
+ //unsigned long flags;
+ struct bc_state *bcs;
+ int retval = 0;
+ struct setup_parm *sp;
+
+ //dbg(DEBUG_ANY, "Gigaset_HW: Receiving command");
+ gigaset_debugdrivers();
+
+ /* Terminate this call if no device is present. Bt if the command is "ISDN_CMD_LOCK" or
+ * "ISDN_CMD_UNLOCK" then execute it due to the fact that they are device independent !
+ */
+ //FIXME "remove test for &connected"
+ if ((!cs || !atomic_read(&cs->connected))) {
+ warn("LL tried to access unknown device with nr. %d",
+ cntrl->driver);
+ return -ENODEV;
+ }
+
+ switch (cntrl->command) {
+ case ISDN_CMD_IOCTL:
+
+ dbg(DEBUG_ANY, "ISDN_CMD_IOCTL (driver:%d,arg: %ld)",
+ cntrl->driver, cntrl->arg);
+
+ warn("ISDN_CMD_IOCTL is not supported.");
+ return -EINVAL;
+
+ case ISDN_CMD_DIAL:
+ dbg(DEBUG_ANY, "ISDN_CMD_DIAL (driver: %d, channel: %ld, "
+ "phone: %s,ownmsn: %s, si1: %d, si2: %d)",
+ cntrl->driver, cntrl->arg,
+ cntrl->parm.setup.phone, cntrl->parm.setup.eazmsn,
+ cntrl->parm.setup.si1, cntrl->parm.setup.si2);
+
+ if (cntrl->arg >= cs->channels) {
+ err("invalid channel (%d)", (int) cntrl->arg);
+ return -EINVAL;
+ }
+
+ bcs = cs->bcs + cntrl->arg;
+
+ if (!gigaset_get_channel(bcs)) {
+ err("channel not free");
+ return -EBUSY;
+ }
+
+ sp = kmalloc(sizeof *sp, GFP_ATOMIC);
+ if (!sp) {
+ gigaset_free_channel(bcs);
+ err("ISDN_CMD_DIAL: out of memory");
+ return -ENOMEM;
+ }
+ *sp = cntrl->parm.setup;
+
+ if (!gigaset_add_event(cs, &bcs->at_state, EV_DIAL, sp,
+ atomic_read(&bcs->at_state.seq_index),
+ NULL)) {
+ //FIXME what should we do?
+ kfree(sp);
+ gigaset_free_channel(bcs);
+ return -ENOMEM;
+ }
+
+ dbg(DEBUG_CMD, "scheduling DIAL");
+ gigaset_schedule_event(cs);
+ break;
+ case ISDN_CMD_ACCEPTD: //FIXME
+ dbg(DEBUG_ANY, "ISDN_CMD_ACCEPTD");
+
+ if (cntrl->arg >= cs->channels) {
+ err("invalid channel (%d)", (int) cntrl->arg);
+ return -EINVAL;
+ }
+
+ if (!gigaset_add_event(cs, &cs->bcs[cntrl->arg].at_state,
+ EV_ACCEPT, NULL, 0, NULL)) {
+ //FIXME what should we do?
+ return -ENOMEM;
+ }
+
+ dbg(DEBUG_CMD, "scheduling ACCEPT");
+ gigaset_schedule_event(cs);
+
+ break;
+ case ISDN_CMD_ACCEPTB:
+ dbg(DEBUG_ANY, "ISDN_CMD_ACCEPTB");
+ break;
+ case ISDN_CMD_HANGUP:
+ dbg(DEBUG_ANY,
+ "ISDN_CMD_HANGUP (channel: %d)", (int) cntrl->arg);
+
+ if (cntrl->arg >= cs->channels) {
+ err("ISDN_CMD_HANGUP: invalid channel (%u)",
+ (unsigned) cntrl->arg);
+ return -EINVAL;
+ }
+
+ if (!gigaset_add_event(cs, &cs->bcs[cntrl->arg].at_state,
+ EV_HUP, NULL, 0, NULL)) {
+ //FIXME what should we do?
+ return -ENOMEM;
+ }
+
+ dbg(DEBUG_CMD, "scheduling HUP");
+ gigaset_schedule_event(cs);
+
+ break;
+ case ISDN_CMD_CLREAZ: /* Do not signal incoming signals */ //FIXME
+ dbg(DEBUG_ANY, "ISDN_CMD_CLREAZ");
+ break;
+ case ISDN_CMD_SETEAZ: /* Signal incoming calls for given MSN */ //FIXME
+ dbg(DEBUG_ANY,
+ "ISDN_CMD_SETEAZ (id:%d, channel: %ld, number: %s)",
+ cntrl->driver, cntrl->arg, cntrl->parm.num);
+ break;
+ case ISDN_CMD_SETL2: /* Set L2 to given protocol */
+ dbg(DEBUG_ANY, "ISDN_CMD_SETL2 (Channel: %ld, Proto: %lx)",
+ cntrl->arg & 0xff, (cntrl->arg >> 8));
+
+ if ((cntrl->arg & 0xff) >= cs->channels) {
+ err("invalid channel (%u)",
+ (unsigned) cntrl->arg & 0xff);
+ return -EINVAL;
+ }
+
+ if (!gigaset_add_event(cs, &cs->bcs[cntrl->arg & 0xff].at_state,
+ EV_PROTO_L2, NULL, cntrl->arg >> 8,
+ NULL)) {
+ //FIXME what should we do?
+ return -ENOMEM;
+ }
+
+ dbg(DEBUG_CMD, "scheduling PROTO_L2");
+ gigaset_schedule_event(cs);
+ break;
+ case ISDN_CMD_SETL3: /* Set L3 to given protocol */
+ dbg(DEBUG_ANY, "ISDN_CMD_SETL3 (Channel: %ld, Proto: %lx)",
+ cntrl->arg & 0xff, (cntrl->arg >> 8));
+
+ if ((cntrl->arg & 0xff) >= cs->channels) {
+ err("invalid channel (%u)",
+ (unsigned) cntrl->arg & 0xff);
+ return -EINVAL;
+ }
+
+ if (cntrl->arg >> 8 != ISDN_PROTO_L3_TRANS) {
+ err("invalid protocol %lu", cntrl->arg >> 8);
+ return -EINVAL;
+ }
+
+ break;
+ case ISDN_CMD_PROCEED:
+ dbg(DEBUG_ANY, "ISDN_CMD_PROCEED"); //FIXME
+ break;
+ case ISDN_CMD_ALERT:
+ dbg(DEBUG_ANY, "ISDN_CMD_ALERT"); //FIXME
+ if (cntrl->arg >= cs->channels) {
+ err("invalid channel (%d)", (int) cntrl->arg);
+ return -EINVAL;
+ }
+ //bcs = cs->bcs + cntrl->arg;
+ //bcs->proto2 = -1;
+ // FIXME
+ break;
+ case ISDN_CMD_REDIR:
+ dbg(DEBUG_ANY, "ISDN_CMD_REDIR"); //FIXME
+ break;
+ case ISDN_CMD_PROT_IO:
+ dbg(DEBUG_ANY, "ISDN_CMD_PROT_IO");
+ break;
+ case ISDN_CMD_FAXCMD:
+ dbg(DEBUG_ANY, "ISDN_CMD_FAXCMD");
+ break;
+ case ISDN_CMD_GETL2:
+ dbg(DEBUG_ANY, "ISDN_CMD_GETL2");
+ break;
+ case ISDN_CMD_GETL3:
+ dbg(DEBUG_ANY, "ISDN_CMD_GETL3");
+ break;
+ case ISDN_CMD_GETEAZ:
+ dbg(DEBUG_ANY, "ISDN_CMD_GETEAZ");
+ break;
+ case ISDN_CMD_SETSIL:
+ dbg(DEBUG_ANY, "ISDN_CMD_SETSIL");
+ break;
+ case ISDN_CMD_GETSIL:
+ dbg(DEBUG_ANY, "ISDN_CMD_GETSIL");
+ break;
+ default:
+ err("unknown command %d from LL",
+ cntrl->command);
+ return -EINVAL;
+ }
+
+ return retval;
+}
+
+void gigaset_i4l_cmd(struct cardstate *cs, int cmd)
+{
+ isdn_ctrl command;
+
+ command.driver = cs->myid;
+ command.command = cmd;
+ command.arg = 0;
+ cs->iif.statcallb(&command);
+}
+
+void gigaset_i4l_channel_cmd(struct bc_state *bcs, int cmd)
+{
+ isdn_ctrl command;
+
+ command.driver = bcs->cs->myid;
+ command.command = cmd;
+ command.arg = bcs->channel;
+ bcs->cs->iif.statcallb(&command);
+}
+
+int gigaset_isdn_setup_dial(struct at_state_t *at_state, void *data)
+{
+ struct bc_state *bcs = at_state->bcs;
+ unsigned proto;
+ const char *bc;
+ size_t length[AT_NUM];
+ size_t l;
+ int i;
+ struct setup_parm *sp = data;
+
+ switch (bcs->proto2) {
+ case ISDN_PROTO_L2_HDLC:
+ proto = 1; /* 0: Bitsynchron, 1: HDLC, 2: voice */
+ break;
+ case ISDN_PROTO_L2_TRANS:
+ proto = 2; /* 0: Bitsynchron, 1: HDLC, 2: voice */
+ break;
+ default:
+ err("invalid protocol: %u", bcs->proto2);
+ return -EINVAL;
+ }
+
+ switch (sp->si1) {
+ case 1: /* audio */
+ bc = "9090A3"; /* 3.1 kHz audio, A-law */
+ break;
+ case 7: /* data */
+ default: /* hope the app knows what it is doing */
+ bc = "8890"; /* unrestricted digital information */
+ }
+ //FIXME add missing si1 values from 1TR6, inspect si2, set HLC/LLC
+
+ length[AT_DIAL ] = 1 + strlen(sp->phone) + 1 + 1;
+ l = strlen(sp->eazmsn);
+ length[AT_MSN ] = l ? 6 + l + 1 + 1 : 0;
+ length[AT_BC ] = 5 + strlen(bc) + 1 + 1;
+ length[AT_PROTO] = 6 + 1 + 1 + 1; /* proto: 1 character */
+ length[AT_ISO ] = 6 + 1 + 1 + 1; /* channel: 1 character */
+ length[AT_TYPE ] = 6 + 1 + 1 + 1; /* call type: 1 character */
+ length[AT_HLC ] = 0;
+
+ for (i = 0; i < AT_NUM; ++i) {
+ kfree(bcs->commands[i]);
+ bcs->commands[i] = NULL;
+ if (length[i] &&
+ !(bcs->commands[i] = kmalloc(length[i], GFP_ATOMIC))) {
+ err("out of memory");
+ return -ENOMEM;
+ }
+ }
+
+ /* type = 1: extern, 0: intern, 2: recall, 3: door, 4: centrex */
+ if (sp->phone[0] == '*' && sp->phone[1] == '*') {
+ /* internal call: translate ** prefix to CTP value */
+ snprintf(bcs->commands[AT_DIAL], length[AT_DIAL],
+ "D%s\r", sp->phone+2);
+ strncpy(bcs->commands[AT_TYPE], "^SCTP=0\r", length[AT_TYPE]);
+ } else {
+ snprintf(bcs->commands[AT_DIAL], length[AT_DIAL],
+ "D%s\r", sp->phone);
+ strncpy(bcs->commands[AT_TYPE], "^SCTP=1\r", length[AT_TYPE]);
+ }
+
+ if (bcs->commands[AT_MSN])
+ snprintf(bcs->commands[AT_MSN], length[AT_MSN], "^SMSN=%s\r", sp->eazmsn);
+ snprintf(bcs->commands[AT_BC ], length[AT_BC ], "^SBC=%s\r", bc);
+ snprintf(bcs->commands[AT_PROTO], length[AT_PROTO], "^SBPR=%u\r", proto);
+ snprintf(bcs->commands[AT_ISO ], length[AT_ISO ], "^SISO=%u\r", (unsigned)bcs->channel + 1);
+
+ return 0;
+}
+
+int gigaset_isdn_setup_accept(struct at_state_t *at_state)
+{
+ unsigned proto;
+ size_t length[AT_NUM];
+ int i;
+ struct bc_state *bcs = at_state->bcs;
+
+ switch (bcs->proto2) {
+ case ISDN_PROTO_L2_HDLC:
+ proto = 1; /* 0: Bitsynchron, 1: HDLC, 2: voice */
+ break;
+ case ISDN_PROTO_L2_TRANS:
+ proto = 2; /* 0: Bitsynchron, 1: HDLC, 2: voice */
+ break;
+ default:
+ err("invalid protocol: %u", bcs->proto2);
+ return -EINVAL;
+ }
+
+ length[AT_DIAL ] = 0;
+ length[AT_MSN ] = 0;
+ length[AT_BC ] = 0;
+ length[AT_PROTO] = 6 + 1 + 1 + 1; /* proto: 1 character */
+ length[AT_ISO ] = 6 + 1 + 1 + 1; /* channel: 1 character */
+ length[AT_TYPE ] = 0;
+ length[AT_HLC ] = 0;
+
+ for (i = 0; i < AT_NUM; ++i) {
+ kfree(bcs->commands[i]);
+ bcs->commands[i] = NULL;
+ if (length[i] &&
+ !(bcs->commands[i] = kmalloc(length[i], GFP_ATOMIC))) {
+ err("out of memory");
+ return -ENOMEM;
+ }
+ }
+
+ snprintf(bcs->commands[AT_PROTO], length[AT_PROTO], "^SBPR=%u\r", proto);
+ snprintf(bcs->commands[AT_ISO ], length[AT_ISO ], "^SISO=%u\r", (unsigned) bcs->channel + 1);
+
+ return 0;
+}
+
+int gigaset_isdn_icall(struct at_state_t *at_state)
+{
+ struct cardstate *cs = at_state->cs;
+ struct bc_state *bcs = at_state->bcs;
+ isdn_ctrl response;
+ int retval;
+
+ /* fill ICALL structure */
+ response.parm.setup.si1 = 0; /* default: unknown */
+ response.parm.setup.si2 = 0;
+ response.parm.setup.screen = 0; //FIXME how to set these?
+ response.parm.setup.plan = 0;
+ if (!at_state->str_var[STR_ZBC]) {
+ /* no BC (internal call): assume speech, A-law */
+ response.parm.setup.si1 = 1;
+ } else if (!strcmp(at_state->str_var[STR_ZBC], "8890")) {
+ /* unrestricted digital information */
+ response.parm.setup.si1 = 7;
+ } else if (!strcmp(at_state->str_var[STR_ZBC], "8090A3")) {
+ /* speech, A-law */
+ response.parm.setup.si1 = 1;
+ } else if (!strcmp(at_state->str_var[STR_ZBC], "9090A3")) {
+ /* 3,1 kHz audio, A-law */
+ response.parm.setup.si1 = 1;
+ response.parm.setup.si2 = 2;
+ } else {
+ warn("RING ignored - unsupported BC %s",
+ at_state->str_var[STR_ZBC]);
+ return ICALL_IGNORE;
+ }
+ if (at_state->str_var[STR_NMBR]) {
+ strncpy(response.parm.setup.phone, at_state->str_var[STR_NMBR],
+ sizeof response.parm.setup.phone - 1);
+ response.parm.setup.phone[sizeof response.parm.setup.phone - 1] = 0;
+ } else
+ response.parm.setup.phone[0] = 0;
+ if (at_state->str_var[STR_ZCPN]) {
+ strncpy(response.parm.setup.eazmsn, at_state->str_var[STR_ZCPN],
+ sizeof response.parm.setup.eazmsn - 1);
+ response.parm.setup.eazmsn[sizeof response.parm.setup.eazmsn - 1] = 0;
+ } else
+ response.parm.setup.eazmsn[0] = 0;
+
+ if (!bcs) {
+ notice("no channel for incoming call");
+ dbg(DEBUG_CMD, "Sending ICALLW");
+ response.command = ISDN_STAT_ICALLW;
+ response.arg = 0; //FIXME
+ } else {
+ dbg(DEBUG_CMD, "Sending ICALL");
+ response.command = ISDN_STAT_ICALL;
+ response.arg = bcs->channel; //FIXME
+ }
+ response.driver = cs->myid;
+ retval = cs->iif.statcallb(&response);
+ dbg(DEBUG_CMD, "Response: %d", retval);
+ switch (retval) {
+ case 0: /* no takers */
+ return ICALL_IGNORE;
+ case 1: /* alerting */
+ bcs->chstate |= CHS_NOTIFY_LL;
+ return ICALL_ACCEPT;
+ case 2: /* reject */
+ return ICALL_REJECT;
+ case 3: /* incomplete */
+ warn("LL requested unsupported feature: Incomplete Number");
+ return ICALL_IGNORE;
+ case 4: /* proceeding */
+ /* Gigaset will send ALERTING anyway.
+ * There doesn't seem to be a way to avoid this.
+ */
+ return ICALL_ACCEPT;
+ case 5: /* deflect */
+ warn("LL requested unsupported feature: Call Deflection");
+ return ICALL_IGNORE;
+ default:
+ err("LL error %d on ICALL", retval);
+ return ICALL_IGNORE;
+ }
+}
+
+/* Set Callback function pointer */
+int gigaset_register_to_LL(struct cardstate *cs, const char *isdnid)
+{
+ isdn_if *iif = &cs->iif;
+
+ dbg(DEBUG_ANY, "Register driver capabilities to LL");
+
+ //iif->id[sizeof(iif->id) - 1]=0;
+ //strncpy(iif->id, isdnid, sizeof(iif->id) - 1);
+ if (snprintf(iif->id, sizeof iif->id, "%s_%u", isdnid, cs->minor_index)
+ >= sizeof iif->id)
+ return -ENOMEM; //FIXME EINVAL/...??
+
+ iif->owner = THIS_MODULE;
+ iif->channels = cs->channels; /* I am supporting just one channel *//* I was supporting...*/
+ iif->maxbufsize = MAX_BUF_SIZE;
+ iif->features = ISDN_FEATURE_L2_TRANS | /* Our device is very advanced, therefore */
+ ISDN_FEATURE_L2_HDLC |
+#ifdef GIG_X75
+ ISDN_FEATURE_L2_X75I |
+#endif
+ ISDN_FEATURE_L3_TRANS |
+ ISDN_FEATURE_P_EURO;
+ iif->hl_hdrlen = HW_HDR_LEN; /* Area for storing ack */
+ iif->command = command_from_LL;
+ iif->writebuf_skb = writebuf_from_LL;
+ iif->writecmd = NULL; /* Don't support isdnctrl */
+ iif->readstat = NULL; /* Don't support isdnctrl */
+ iif->rcvcallb_skb = NULL; /* Will be set by LL */
+ iif->statcallb = NULL; /* Will be set by LL */
+
+ if (!register_isdn(iif))
+ return 0;
+
+ cs->myid = iif->channels; /* Set my device id */
+ return 1;
+}
diff --git a/drivers/isdn/gigaset/interface.c b/drivers/isdn/gigaset/interface.c
new file mode 100644
index 000000000000..3a81d9c65141
--- /dev/null
+++ b/drivers/isdn/gigaset/interface.c
@@ -0,0 +1,718 @@
+/*
+ * 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.
+ * =====================================================================
+ * Version: $Id: interface.c,v 1.14.4.15 2006/02/04 18:28:16 hjlipp Exp $
+ * =====================================================================
+ */
+
+#include "gigaset.h"
+#include <linux/gigaset_dev.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+
+/*** our ioctls ***/
+
+static int if_lock(struct cardstate *cs, int *arg)
+{
+ int cmd = *arg;
+
+ dbg(DEBUG_IF, "%u: if_lock (%d)", cs->minor_index, cmd);
+
+ if (cmd > 1)
+ return -EINVAL;
+
+ if (cmd < 0) {
+ *arg = atomic_read(&cs->mstate) == MS_LOCKED; //FIXME remove?
+ return 0;
+ }
+
+ if (!cmd && atomic_read(&cs->mstate) == MS_LOCKED
+ && atomic_read(&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;
+ }
+
+ dbg(DEBUG_CMD, "scheduling IF_LOCK");
+ 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];
+
+ 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;
+ }
+
+ dbg(DEBUG_CMD, "scheduling IF_VER");
+ 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)
+{
+ dbg(DEBUG_IF, "%u: if_config (%d)", cs->minor_index, *arg);
+
+ if (*arg != 1)
+ return -EINVAL;
+
+ if (atomic_read(&cs->mstate) != MS_LOCKED)
+ return -EBUSY;
+
+ *arg = 0;
+ return gigaset_enterconfigmode(cs);
+}
+
+/*** the terminal driver ***/
+/* stolen from usbserial and some other tty drivers */
+
+static int if_open(struct tty_struct *tty, struct file *filp);
+static void if_close(struct tty_struct *tty, struct file *filp);
+static int if_ioctl(struct tty_struct *tty, struct file *file,
+ unsigned int cmd, unsigned long arg);
+static int if_write_room(struct tty_struct *tty);
+static int if_chars_in_buffer(struct tty_struct *tty);
+static void if_throttle(struct tty_struct *tty);
+static void if_unthrottle(struct tty_struct *tty);
+static void if_set_termios(struct tty_struct *tty, struct termios *old);
+static int if_tiocmget(struct tty_struct *tty, struct file *file);
+static int if_tiocmset(struct tty_struct *tty, struct file *file,
+ unsigned int set, unsigned int clear);
+static int if_write(struct tty_struct *tty,
+ const unsigned char *buf, int count);
+
+static struct tty_operations if_ops = {
+ .open = if_open,
+ .close = if_close,
+ .ioctl = if_ioctl,
+ .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,
+#if 0
+ .break_ctl = serial_break,
+#endif
+ .tiocmget = if_tiocmget,
+ .tiocmset = if_tiocmset,
+};
+
+static int if_open(struct tty_struct *tty, struct file *filp)
+{
+ struct cardstate *cs;
+ unsigned long flags;
+
+ dbg(DEBUG_IF, "%d+%d: %s()", tty->driver->minor_start, tty->index,
+ __FUNCTION__);
+
+ tty->driver_data = NULL;
+
+ cs = gigaset_get_cs_by_tty(tty);
+ if (!cs)
+ return -ENODEV;
+
+ if (down_interruptible(&cs->sem))
+ return -ERESTARTSYS; // FIXME -EINTR?
+ tty->driver_data = cs;
+
+ ++cs->open_count;
+
+ if (cs->open_count == 1) {
+ spin_lock_irqsave(&cs->lock, flags);
+ cs->tty = tty;
+ spin_unlock_irqrestore(&cs->lock, flags);
+ tty->low_latency = 1; //FIXME test
+ //FIXME
+ }
+
+ up(&cs->sem);
+ return 0;
+}
+
+static void if_close(struct tty_struct *tty, struct file *filp)
+{
+ struct cardstate *cs;
+ unsigned long flags;
+
+ cs = (struct cardstate *) tty->driver_data;
+ if (!cs) {
+ err("cs==NULL in %s", __FUNCTION__);
+ return;
+ }
+
+ dbg(DEBUG_IF, "%u: %s()", cs->minor_index, __FUNCTION__);
+
+ down(&cs->sem);
+
+ if (!cs->open_count)
+ warn("%s: device not opened", __FUNCTION__);
+ else {
+ if (!--cs->open_count) {
+ spin_lock_irqsave(&cs->lock, flags);
+ cs->tty = NULL;
+ spin_unlock_irqrestore(&cs->lock, flags);
+ //FIXME
+ }
+ }
+
+ up(&cs->sem);
+}
+
+static int if_ioctl(struct tty_struct *tty, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct cardstate *cs;
+ int retval = -ENODEV;
+ int int_arg;
+ unsigned char buf[6];
+ unsigned version[4];
+
+ cs = (struct cardstate *) tty->driver_data;
+ if (!cs) {
+ err("cs==NULL in %s", __FUNCTION__);
+ return -ENODEV;
+ }
+
+ dbg(DEBUG_IF, "%u: %s(0x%x)", cs->minor_index, __FUNCTION__, cmd);
+
+ if (down_interruptible(&cs->sem))
+ return -ERESTARTSYS; // FIXME -EINTR?
+
+ if (!cs->open_count)
+ warn("%s: device not opened", __FUNCTION__);
+ 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:
+ //FIXME test if MS_LOCKED
+ gigaset_dbg_buffer(DEBUG_IF, "GIGASET_BRKCHARS",
+ 6, (const unsigned char *) arg, 1);
+ if (!atomic_read(&cs->connected)) {
+ dbg(DEBUG_ANY, "can't communicate with unplugged device");
+ retval = -ENODEV;
+ break;
+ }
+ retval = copy_from_user(&buf,
+ (const unsigned char __user *) arg, 6)
+ ? -EFAULT : 0;
+ if (retval >= 0)
+ 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:
+ dbg(DEBUG_ANY, "%s: arg not supported - 0x%04x",
+ __FUNCTION__, cmd);
+ retval = -ENOIOCTLCMD;
+ }
+ }
+
+ up(&cs->sem);
+
+ return retval;
+}
+
+static int if_tiocmget(struct tty_struct *tty, struct file *file)
+{
+ struct cardstate *cs;
+ int retval;
+
+ cs = (struct cardstate *) tty->driver_data;
+ if (!cs) {
+ err("cs==NULL in %s", __FUNCTION__);
+ return -ENODEV;
+ }
+
+ dbg(DEBUG_IF, "%u: %s()", cs->minor_index, __FUNCTION__);
+
+ if (down_interruptible(&cs->sem))
+ return -ERESTARTSYS; // FIXME -EINTR?
+
+ // FIXME read from device?
+ retval = cs->control_state & (TIOCM_RTS|TIOCM_DTR);
+
+ up(&cs->sem);
+
+ return retval;
+}
+
+static int if_tiocmset(struct tty_struct *tty, struct file *file,
+ unsigned int set, unsigned int clear)
+{
+ struct cardstate *cs;
+ int retval;
+ unsigned mc;
+
+ cs = (struct cardstate *) tty->driver_data;
+ if (!cs) {
+ err("cs==NULL in %s", __FUNCTION__);
+ return -ENODEV;
+ }
+
+ dbg(DEBUG_IF,
+ "%u: %s(0x%x, 0x%x)", cs->minor_index, __FUNCTION__, set, clear);
+
+ if (down_interruptible(&cs->sem))
+ return -ERESTARTSYS; // FIXME -EINTR?
+
+ if (!atomic_read(&cs->connected)) {
+ dbg(DEBUG_ANY, "can't communicate with unplugged device");
+ 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;
+ }
+
+ up(&cs->sem);
+
+ return retval;
+}
+
+static int if_write(struct tty_struct *tty, const unsigned char *buf, int count)
+{
+ struct cardstate *cs;
+ int retval = -ENODEV;
+
+ cs = (struct cardstate *) tty->driver_data;
+ if (!cs) {
+ err("cs==NULL in %s", __FUNCTION__);
+ return -ENODEV;
+ }
+
+ dbg(DEBUG_IF, "%u: %s()", cs->minor_index, __FUNCTION__);
+
+ if (down_interruptible(&cs->sem))
+ return -ERESTARTSYS; // FIXME -EINTR?
+
+ if (!cs->open_count)
+ warn("%s: device not opened", __FUNCTION__);
+ else if (atomic_read(&cs->mstate) != MS_LOCKED) {
+ warn("can't write to unlocked device");
+ retval = -EBUSY;
+ } else if (!atomic_read(&cs->connected)) {
+ dbg(DEBUG_ANY, "can't write to unplugged device");
+ retval = -EBUSY; //FIXME
+ } else {
+ retval = cs->ops->write_cmd(cs, buf, count,
+ &cs->if_wake_tasklet);
+ }
+
+ up(&cs->sem);
+
+ return retval;
+}
+
+static int if_write_room(struct tty_struct *tty)
+{
+ struct cardstate *cs;
+ int retval = -ENODEV;
+
+ cs = (struct cardstate *) tty->driver_data;
+ if (!cs) {
+ err("cs==NULL in %s", __FUNCTION__);
+ return -ENODEV;
+ }
+
+ dbg(DEBUG_IF, "%u: %s()", cs->minor_index, __FUNCTION__);
+
+ if (down_interruptible(&cs->sem))
+ return -ERESTARTSYS; // FIXME -EINTR?
+
+ if (!cs->open_count)
+ warn("%s: device not opened", __FUNCTION__);
+ else if (atomic_read(&cs->mstate) != MS_LOCKED) {
+ warn("can't write to unlocked device");
+ retval = -EBUSY; //FIXME
+ } else if (!atomic_read(&cs->connected)) {
+ dbg(DEBUG_ANY, "can't write to unplugged device");
+ retval = -EBUSY; //FIXME
+ } else
+ retval = cs->ops->write_room(cs);
+
+ up(&cs->sem);
+
+ return retval;
+}
+
+static int if_chars_in_buffer(struct tty_struct *tty)
+{
+ struct cardstate *cs;
+ int retval = -ENODEV;
+
+ cs = (struct cardstate *) tty->driver_data;
+ if (!cs) {
+ err("cs==NULL in %s", __FUNCTION__);
+ return -ENODEV;
+ }
+
+ dbg(DEBUG_IF, "%u: %s()", cs->minor_index, __FUNCTION__);
+
+ if (down_interruptible(&cs->sem))
+ return -ERESTARTSYS; // FIXME -EINTR?
+
+ if (!cs->open_count)
+ warn("%s: device not opened", __FUNCTION__);
+ else if (atomic_read(&cs->mstate) != MS_LOCKED) {
+ warn("can't write to unlocked device");
+ retval = -EBUSY;
+ } else if (!atomic_read(&cs->connected)) {
+ dbg(DEBUG_ANY, "can't write to unplugged device");
+ retval = -EBUSY; //FIXME
+ } else
+ retval = cs->ops->chars_in_buffer(cs);
+
+ up(&cs->sem);
+
+ return retval;
+}
+
+static void if_throttle(struct tty_struct *tty)
+{
+ struct cardstate *cs;
+
+ cs = (struct cardstate *) tty->driver_data;
+ if (!cs) {
+ err("cs==NULL in %s", __FUNCTION__);
+ return;
+ }
+
+ dbg(DEBUG_IF, "%u: %s()", cs->minor_index, __FUNCTION__);
+
+ down(&cs->sem);
+
+ if (!cs->open_count)
+ warn("%s: device not opened", __FUNCTION__);
+ else {
+ //FIXME
+ }
+
+ up(&cs->sem);
+}
+
+static void if_unthrottle(struct tty_struct *tty)
+{
+ struct cardstate *cs;
+
+ cs = (struct cardstate *) tty->driver_data;
+ if (!cs) {
+ err("cs==NULL in %s", __FUNCTION__);
+ return;
+ }
+
+ dbg(DEBUG_IF, "%u: %s()", cs->minor_index, __FUNCTION__);
+
+ down(&cs->sem);
+
+ if (!cs->open_count)
+ warn("%s: device not opened", __FUNCTION__);
+ else {
+ //FIXME
+ }
+
+ up(&cs->sem);
+}
+
+static void if_set_termios(struct tty_struct *tty, struct termios *old)
+{
+ struct cardstate *cs;
+ unsigned int iflag;
+ unsigned int cflag;
+ unsigned int old_cflag;
+ unsigned int control_state, new_state;
+
+ cs = (struct cardstate *) tty->driver_data;
+ if (!cs) {
+ err("cs==NULL in %s", __FUNCTION__);
+ return;
+ }
+
+ dbg(DEBUG_IF, "%u: %s()", cs->minor_index, __FUNCTION__);
+
+ down(&cs->sem);
+
+ if (!cs->open_count) {
+ warn("%s: device not opened", __FUNCTION__);
+ goto out;
+ }
+
+ if (!atomic_read(&cs->connected)) {
+ dbg(DEBUG_ANY, "can't communicate with unplugged device");
+ goto out;
+ }
+
+ // stolen from mct_u232.c
+ iflag = tty->termios->c_iflag;
+ cflag = tty->termios->c_cflag;
+ old_cflag = old ? old->c_cflag : cflag; //FIXME?
+ 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;
+ 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 */
+ 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);
+
+#if 0
+ //FIXME this hangs M101 [ts 2005-03-09]
+ //FIXME do we need this?
+ /*
+ * Set flow control: well, I do not really now how to handle DTR/RTS.
+ * Just do what we have seen with SniffUSB on Win98.
+ */
+ /* Drop DTR/RTS if no flow control otherwise assert */
+ dbg(DEBUG_IF, "%u: control_state %x", cs->minor_index, control_state);
+ new_state = control_state;
+ if ((iflag & IXOFF) || (iflag & IXON) || (cflag & CRTSCTS))
+ new_state |= TIOCM_DTR | TIOCM_RTS;
+ else
+ new_state &= ~(TIOCM_DTR | TIOCM_RTS);
+ if (new_state != control_state) {
+ dbg(DEBUG_IF, "%u: new_state %x", cs->minor_index, new_state);
+ gigaset_set_modem_ctrl(cs, control_state, new_state); // FIXME: mct_u232.c sets the old state here. is this a bug?
+ control_state = new_state;
+ }
+#endif
+
+ /* save off the modified port settings */
+ cs->control_state = control_state;
+
+out:
+ up(&cs->sem);
+}
+
+
+/* wakeup tasklet for the write operation */
+static void if_wake(unsigned long data)
+{
+ struct cardstate *cs = (struct cardstate *) data;
+ struct tty_struct *tty;
+
+ tty = cs->tty;
+ if (!tty)
+ return;
+
+ if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
+ tty->ldisc.write_wakeup) {
+ dbg(DEBUG_IF, "write wakeup call");
+ tty->ldisc.write_wakeup(tty);
+ }
+
+ wake_up_interruptible(&tty->write_wait);
+}
+
+/*** 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);
+ tty_register_device(drv->tty, cs->minor_index, NULL);
+}
+
+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);
+ tty_unregister_device(drv->tty, cs->minor_index);
+}
+
+void gigaset_if_receive(struct cardstate *cs,
+ unsigned char *buffer, size_t len)
+{
+ unsigned long flags;
+ struct tty_struct *tty;
+
+ spin_lock_irqsave(&cs->lock, flags);
+ if ((tty = cs->tty) == NULL)
+ dbg(DEBUG_ANY, "receive on closed device");
+ else {
+ tty_buffer_request_room(tty, len);
+ tty_insert_flip_string(tty, buffer, len);
+ tty_flip_buffer_push(tty);
+ }
+ spin_unlock_irqrestore(&cs->lock, flags);
+}
+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)
+ * devfsname Devfs name of the device files without %d
+ */
+void gigaset_if_initdriver(struct gigaset_driver *drv, const char *procname,
+ const char *devname, const char *devfsname)
+{
+ unsigned minors = drv->minors;
+ int ret;
+ struct tty_driver *tty;
+
+ drv->have_tty = 0;
+
+ if ((drv->tty = alloc_tty_driver(minors)) == NULL)
+ goto enomem;
+ tty = drv->tty;
+
+ tty->magic = TTY_DRIVER_MAGIC,
+ tty->major = GIG_MAJOR,
+ tty->type = TTY_DRIVER_TYPE_SERIAL,
+ tty->subtype = SERIAL_TYPE_NORMAL,
+ tty->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_NO_DEVFS,
+
+ tty->driver_name = procname;
+ tty->name = devname;
+ tty->minor_start = drv->minor;
+ tty->num = drv->minors;
+
+ tty->owner = THIS_MODULE;
+ tty->devfs_name = devfsname;
+
+ tty->init_termios = tty_std_termios; //FIXME
+ tty->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; //FIXME
+ tty_set_operations(tty, &if_ops);
+
+ ret = tty_register_driver(tty);
+ if (ret < 0) {
+ warn("failed to register tty driver (error %d)", ret);
+ goto error;
+ }
+ dbg(DEBUG_IF, "tty driver initialized");
+ drv->have_tty = 1;
+ return;
+
+enomem:
+ warn("could not allocate tty structures");
+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/isdn/gigaset/isocdata.c b/drivers/isdn/gigaset/isocdata.c
new file mode 100644
index 000000000000..5744eb91b315
--- /dev/null
+++ b/drivers/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.
+ * =====================================================================
+ * ToDo: ...
+ * =====================================================================
+ * Version: $Id: isocdata.c,v 1.2.2.5 2005/11/13 23:05:19 hjlipp Exp $
+ * =====================================================================
+ */
+
+#include "gigaset.h"
+#include <linux/crc-ccitt.h>
+
+/* access methods for isowbuf_t */
+/* ============================ */
+
+/* initialize buffer structure
+ */
+void gigaset_isowbuf_init(struct isowbuf_t *iwb, unsigned char idle)
+{
+ atomic_set(&iwb->read, 0);
+ atomic_set(&iwb->nextread, 0);
+ atomic_set(&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 = atomic_read(&iwb->read);
+ write = atomic_read(&iwb->write);
+ if ((freebytes = read - write) > 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;
+ }
+}
+
+/* compare two offsets within the buffer
+ * The buffer is seen as circular, with the read position as start
+ * returns -1/0/1 if position a </=/> position b without crossing 'read'
+ */
+static inline int isowbuf_poscmp(struct isowbuf_t *iwb, int a, int b)
+{
+ int read;
+ if (a == b)
+ return 0;
+ read = atomic_read(&iwb->read);
+ if (a < b) {
+ if (a < read && read <= b)
+ return +1;
+ else
+ return -1;
+ } else {
+ if (b < read && read <= a)
+ return -1;
+ else
+ return +1;
+ }
+}
+
+/* start writing
+ * acquire the write semaphore
+ * return true if acquired, false if busy
+ */
+static inline int isowbuf_startwrite(struct isowbuf_t *iwb)
+{
+ if (!atomic_dec_and_test(&iwb->writesem)) {
+ atomic_inc(&iwb->writesem);
+ dbg(DEBUG_ISO,
+ "%s: couldn't acquire iso write semaphore", __func__);
+ return 0;
+ }
+#ifdef CONFIG_GIGASET_DEBUG
+ dbg(DEBUG_ISO,
+ "%s: acquired iso write semaphore, data[write]=%02x, nbits=%d",
+ __func__, iwb->data[atomic_read(&iwb->write)], iwb->wbits);
+#endif
+ return 1;
+}
+
+/* finish writing
+ * release the write semaphore and update the maximum buffer fill level
+ * returns the current write position
+ */
+static inline int isowbuf_donewrite(struct isowbuf_t *iwb)
+{
+ int write = atomic_read(&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 = atomic_read(&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;
+ atomic_set(&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 = atomic_read(&iwb->write);
+ iwb->idle = iwb->data[write];
+ 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 = atomic_read(&iwb->nextread);
+ write = atomic_read(&iwb->write);
+ if (likely(read == write)) {
+ //dbg(DEBUG_STREAM, "%s: send buffer empty", __func__);
+ /* return idle frame */
+ return read < BAS_OUTBUFPAD ?
+ BAS_OUTBUFSIZE : read - BAS_OUTBUFPAD;
+ }
+
+ limit = read + size;
+ 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)) {
+ err("invalid size %d", size);
+ return -EINVAL;
+ }
+ src = atomic_read(&iwb->read);
+ if (unlikely(limit > BAS_OUTBUFSIZE + BAS_OUTBUFPAD ||
+ (read < src && limit >= src))) {
+ err("isoc write buffer frame reservation violated");
+ return -EFAULT;
+ }
+#endif
+
+ if (read < write) {
+ /* no wraparound in valid data */
+ if (limit >= write) {
+ /* append idle frame */
+ if (!isowbuf_startwrite(iwb))
+ return -EBUSY;
+ /* write position could have changed */
+ if (limit >= (write = atomic_read(&iwb->write))) {
+ pbyte = iwb->data[write]; /* save partial byte */
+ limit = write + BAS_OUTBUFPAD;
+ 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;
+ }
+ dbg(DEBUG_STREAM, "%s: restoring %02x at %d",
+ __func__, pbyte, limit);
+ iwb->data[limit] = pbyte; /* restore partial byte */
+ atomic_set(&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;
+ }
+ }
+ atomic_set(&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];
+ static const char hexdigit[] = "0123456789abcdef";
+ int i = 0;
+ IFNULLRET(tag);
+ IFNULLRET(bytes);
+ while (count-- > 0) {
+ if (i > sizeof(dbgline) - 4) {
+ dbgline[i] = '\0';
+ dbg(level, "%s:%s", tag, dbgline);
+ i = 0;
+ }
+ c = *bytes++;
+ dbgline[i] = (i && !(i % 12)) ? '-' : ' ';
+ i++;
+ dbgline[i++] = hexdigit[(c >> 4) & 0x0f];
+ dbgline[i++] = hexdigit[c & 0x0f];
+ }
+ dbgline[i] = '\0';
+ 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 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)) {
+ dbg(DEBUG_ISO, "%s: %d bytes free -> -EAGAIN",
+ __func__, isowbuf_freebytes(iwb));
+ return -EAGAIN;
+ }
+
+ dump_bytes(DEBUG_STREAM, "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);
+ dump_bytes(DEBUG_STREAM_DUMP, "isowbuf", iwb->data, end + 1);
+ 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 atomic_read(&iwb->write); /* better ideas? */
+
+ if (isowbuf_freebytes(iwb) < count ||
+ !isowbuf_startwrite(iwb)) {
+ dbg(DEBUG_ISO, "can't put %d bytes", count);
+ return -EAGAIN;
+ }
+
+ dbg(DEBUG_STREAM, "put %d bytes", count);
+ write = atomic_read(&iwb->write);
+ do {
+ c = gigaset_invtab[*in++];
+ iwb->data[write++] = c;
+ write %= BAS_OUTBUFSIZE;
+ } while (--count > 0);
+ atomic_set(&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 ISDN_PROTO_L2_HDLC:
+ result = hdlc_buildframe(bcs->hw.bas->isooutbuf, in, len);
+ dbg(DEBUG_ISO, "%s: %d bytes HDLC -> %d", __func__, len, result);
+ break;
+ default: /* assume transparent */
+ result = trans_buildframe(bcs->hw.bas->isooutbuf, in, len);
+ 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->fcs = crc_ccitt_byte(bcs->fcs, c);
+ if (unlikely(bcs->skb == NULL)) {
+ /* skipping */
+ return;
+ }
+ if (unlikely(bcs->skb->len == SBUFSIZE)) {
+ warn("received oversized packet discarded");
+ bcs->hw.bas->giants++;
+ dev_kfree_skb_any(bcs->skb);
+ bcs->skb = NULL;
+ return;
+ }
+ *gigaset_skb_put_quick(bcs->skb, 1) = 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 (likely(bcs->skb != NULL))
+ skb_trim(bcs->skb, 0);
+ else if (!bcs->ignore) {
+ if ((bcs->skb = dev_alloc_skb(SBUFSIZE + HW_HDR_LEN)) != NULL)
+ skb_reserve(bcs->skb, HW_HDR_LEN);
+ else
+ err("could not allocate skb");
+ }
+
+ /* reset packet state */
+ bcs->fcs = PPP_INITFCS;
+}
+
+/* hdlc_done
+ * process completed HDLC data packet
+ */
+static inline void hdlc_done(struct bc_state *bcs)
+{
+ struct sk_buff *procskb;
+
+ if (unlikely(bcs->ignore)) {
+ bcs->ignore--;
+ hdlc_flush(bcs);
+ return;
+ }
+
+ if ((procskb = bcs->skb) == NULL) {
+ /* previous error */
+ dbg(DEBUG_ISO, "%s: skb=NULL", __func__);
+ gigaset_rcv_error(NULL, bcs->cs, bcs);
+ } else if (procskb->len < 2) {
+ notice("received short frame (%d octets)", procskb->len);
+ bcs->hw.bas->runts++;
+ gigaset_rcv_error(procskb, bcs->cs, bcs);
+ } else if (bcs->fcs != PPP_GOODFCS) {
+ notice("frame check error (0x%04x)", bcs->fcs);
+ bcs->hw.bas->fcserrs++;
+ gigaset_rcv_error(procskb, bcs->cs, bcs);
+ } else {
+ procskb->len -= 2; /* subtract FCS */
+ procskb->tail -= 2;
+ dbg(DEBUG_ISO,
+ "%s: good frame (%d octets)", __func__, procskb->len);
+ dump_bytes(DEBUG_STREAM,
+ "rcv data", procskb->data, procskb->len);
+ bcs->hw.bas->goodbytes += procskb->len;
+ gigaset_rcv_skb(procskb, bcs->cs, bcs);
+ }
+
+ if ((bcs->skb = dev_alloc_skb(SBUFSIZE + HW_HDR_LEN)) != NULL)
+ skb_reserve(bcs->skb, HW_HDR_LEN);
+ else
+ err("could not allocate skb");
+ bcs->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;
+ }
+
+ notice("received partial byte (%d bits)", inbits);
+ bcs->hw.bas->alignerrs++;
+ gigaset_rcv_error(bcs->skb, bcs->cs, bcs);
+
+ if ((bcs->skb = dev_alloc_skb(SBUFSIZE + HW_HDR_LEN)) != NULL)
+ skb_reserve(bcs->skb, HW_HDR_LEN);
+ else
+ err("could not allocate skb");
+ bcs->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 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_rcv_skb
+ * notify of errors via gigaset_rcv_error
+ * 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;
+ int inputstate;
+ unsigned seqlen, inbyte, inbits;
+
+ IFNULLRET(bcs);
+ ubc = bcs->hw.bas;
+ IFNULLRET(ubc);
+
+ /* 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 */
+ inbits--;
+ /* correct the incorrectly handled cases individually */
+ 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 */
+ inbits--;
+ /* correct the incorrectly handled cases individually */
+ 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 */
+ switch (c) { /* unstuff individually */
+ 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_rcv_skb
+ * 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--;
+ hdlc_flush(bcs);
+ return;
+ }
+ if (unlikely((skb = bcs->skb) == NULL)) {
+ bcs->skb = skb = dev_alloc_skb(SBUFSIZE + HW_HDR_LEN);
+ if (!skb) {
+ err("could not allocate skb");
+ return;
+ }
+ skb_reserve(skb, HW_HDR_LEN);
+ }
+ bcs->hw.bas->goodbytes += skb->len;
+ dobytes = TRANSBUFSIZE - skb->len;
+ while (count > 0) {
+ dst = skb_put(skb, count < dobytes ? count : dobytes);
+ while (count > 0 && dobytes > 0) {
+ *dst++ = gigaset_invtab[*src++];
+ count--;
+ dobytes--;
+ }
+ if (dobytes == 0) {
+ gigaset_rcv_skb(skb, bcs->cs, bcs);
+ bcs->skb = skb = dev_alloc_skb(SBUFSIZE + HW_HDR_LEN);
+ if (!skb) {
+ err("could not allocate skb");
+ return;
+ }
+ skb_reserve(bcs->skb, HW_HDR_LEN);
+ dobytes = TRANSBUFSIZE;
+ }
+ }
+}
+
+void gigaset_isoc_receive(unsigned char *src, unsigned count, struct bc_state *bcs)
+{
+ switch (bcs->proto2) {
+ case ISDN_PROTO_L2_HDLC:
+ hdlc_unpack(src, count, bcs);
+ break;
+ default: /* assume transparent */
+ trans_receive(src, count, bcs);
+ }
+}
+
+/* == data input =========================================================== */
+
+static void cmd_loop(unsigned char *src, int numbytes, struct inbuf_t *inbuf)
+{
+ struct cardstate *cs = inbuf->cs;
+ unsigned cbytes = cs->cbytes;
+
+ while (numbytes--) {
+ /* copy next character, check for end of line */
+ switch (cs->respdata[cbytes] = *src++) {
+ case '\r':
+ case '\n':
+ /* end of line */
+ dbg(DEBUG_TRANSCMD, "%s: End of Command (%d Bytes)",
+ __func__, cbytes);
+ cs->cbytes = cbytes;
+ gigaset_handle_modem_response(cs);
+ cbytes = 0;
+ break;
+ default:
+ /* advance in line buffer, checking for overflow */
+ if (cbytes < MAX_RESP_SIZE - 1)
+ cbytes++;
+ else
+ warn("response too large");
+ }
+ }
+
+ /* 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 = atomic_read(&inbuf->head);
+ while (head != (tail = atomic_read(&inbuf->tail))) {
+ dbg(DEBUG_INTR, "buffer state: %u -> %u", head, tail);
+ if (head > tail)
+ tail = RBUFSIZE;
+ src = inbuf->data + head;
+ numbytes = tail - head;
+ dbg(DEBUG_INTR, "processing %u bytes", numbytes);
+
+ if (atomic_read(&cs->mstate) == MS_LOCKED) {
+ gigaset_dbg_buffer(DEBUG_LOCKCMD, "received response",
+ numbytes, src, 0);
+ gigaset_if_receive(inbuf->cs, src, numbytes);
+ } else {
+ gigaset_dbg_buffer(DEBUG_CMD, "received response",
+ numbytes, src, 0);
+ cmd_loop(src, numbytes, inbuf);
+ }
+
+ head += numbytes;
+ if (head == RBUFSIZE)
+ head = 0;
+ dbg(DEBUG_INTR, "setting head to %u", head);
+ atomic_set(&inbuf->head, head);
+ }
+}
+
+
+/* == data output ========================================================== */
+
+/* gigaset_send_skb
+ * called by common.c to queue an skb for sending
+ * and start transmission if necessary
+ * parameters:
+ * B Channel control structure
+ * skb
+ * return value:
+ * number of bytes accepted for sending
+ * (skb->len if ok, 0 if out of buffer space)
+ * or error code (< 0, eg. -EINVAL)
+ */
+int gigaset_isoc_send_skb(struct bc_state *bcs, struct sk_buff *skb)
+{
+ int len;
+
+ IFNULLRETVAL(bcs, -EFAULT);
+ IFNULLRETVAL(skb, -EFAULT);
+ len = skb->len;
+
+ skb_queue_tail(&bcs->squeue, skb);
+ 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);
+
+ return len; /* ok so far */
+}
diff --git a/drivers/isdn/gigaset/proc.c b/drivers/isdn/gigaset/proc.c
new file mode 100644
index 000000000000..c6915fa2be6c
--- /dev/null
+++ b/drivers/isdn/gigaset/proc.c
@@ -0,0 +1,81 @@
+/*
+ * Stuff used by all variants of the driver
+ *
+ * Copyright (c) 2001 by Stefan Eilers <Eilers.Stefan@epost.de>,
+ * 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.
+ * =====================================================================
+ * ToDo: ...
+ * =====================================================================
+ * Version: $Id: proc.c,v 1.5.2.13 2006/02/04 18:28:16 hjlipp Exp $
+ * =====================================================================
+ */
+
+#include "gigaset.h"
+#include <linux/ctype.h>
+
+static ssize_t show_cidmode(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct cardstate *cs = usb_get_intfdata(intf);
+ return sprintf(buf, "%d\n", atomic_read(&cs->cidmode)); // FIXME use scnprintf for 13607 bit architectures (if PAGE_SIZE==4096)
+}
+
+static ssize_t set_cidmode(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct cardstate *cs = usb_get_intfdata(intf);
+ 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 (down_interruptible(&cs->sem))
+ return -ERESTARTSYS; // FIXME -EINTR?
+
+ cs->waiting = 1;
+ if (!gigaset_add_event(cs, &cs->at_state, EV_PROC_CIDMODE,
+ NULL, value, NULL)) {
+ cs->waiting = 0;
+ up(&cs->sem);
+ return -ENOMEM;
+ }
+
+ dbg(DEBUG_CMD, "scheduling PROC_CIDMODE");
+ gigaset_schedule_event(cs);
+
+ wait_event(cs->waitqueue, !cs->waiting);
+
+ up(&cs->sem);
+
+ return count;
+}
+
+static DEVICE_ATTR(cidmode, S_IRUGO|S_IWUSR, show_cidmode, set_cidmode);
+
+/* free sysfs for device */
+void gigaset_free_dev_sysfs(struct usb_interface *interface)
+{
+ dbg(DEBUG_INIT, "removing sysfs entries");
+ device_remove_file(&interface->dev, &dev_attr_cidmode);
+}
+EXPORT_SYMBOL_GPL(gigaset_free_dev_sysfs);
+
+/* initialize sysfs for device */
+void gigaset_init_dev_sysfs(struct usb_interface *interface)
+{
+ dbg(DEBUG_INIT, "setting up sysfs");
+ device_create_file(&interface->dev, &dev_attr_cidmode);
+}
+EXPORT_SYMBOL_GPL(gigaset_init_dev_sysfs);
diff --git a/drivers/isdn/gigaset/usb-gigaset.c b/drivers/isdn/gigaset/usb-gigaset.c
new file mode 100644
index 000000000000..323fc7349dec
--- /dev/null
+++ b/drivers/isdn/gigaset/usb-gigaset.c
@@ -0,0 +1,1008 @@
+/*
+ * USB driver for Gigaset 307x directly or using M105 Data.
+ *
+ * Copyright (c) 2001 by Stefan Eilers <Eilers.Stefan@epost.de>
+ * 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.
+ * =====================================================================
+ * ToDo: ...
+ * =====================================================================
+ * Version: $Id: usb-gigaset.c,v 1.85.4.18 2006/02/04 18:28:16 hjlipp Exp $
+ * =====================================================================
+ */
+
+#include "gigaset.h"
+
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.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 <Eilers.Stefan@epost.de>"
+#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_DEVFSNAME "gig/usb/"
+#define GIGASET_DEVNAME "ttyGU"
+
+#define IF_WRITEBUF 2000 //FIXME // WAKEUP_CHARS: 256
+
+/* 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 struct usb_device_id gigaset_table [] = {
+ { USB_DEVICE(USB_M105_VENDOR_ID, USB_M105_PRODUCT_ID) },
+ { } /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, gigaset_table);
+
+/* Get a minor range for your devices from the usb maintainer */
+#define USB_SKEL_MINOR_BASE 200
+
+
+/*
+ * 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.
+ */
+
+static int gigaset_probe(struct usb_interface *interface,
+ const struct usb_device_id *id);
+static void gigaset_disconnect(struct usb_interface *interface);
+
+static struct gigaset_driver *driver = NULL;
+static struct cardstate *cardstate = NULL;
+
+/* 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,
+};
+
+struct usb_cardstate {
+ struct usb_device *udev; /* save off the usb device pointer */
+ struct usb_interface *interface; /* the interface for this device */
+ atomic_t busy; /* bulk output in progress */
+
+ /* Output buffer for commands (M105: and data)*/
+ unsigned char *bulk_out_buffer; /* the buffer to send data */
+ int bulk_out_size; /* the size of the send buffer */
+ __u8 bulk_out_endpointAddr; /* the address of the bulk out endpoint */
+ struct urb *bulk_out_urb; /* the urb used to transmit data */
+
+ /* Input buffer for command responses (M105: and data)*/
+ int rcvbuf_size; /* the size of the receive buffer */
+ struct urb *read_urb; /* the urb used to receive data */
+ __u8 int_in_endpointAddr; /* the address of the bulk in endpoint */
+
+ char bchars[6]; /* req. 0x19 */
+};
+
+struct usb_bc_state {};
+
+static inline unsigned tiocm_to_gigaset(unsigned state)
+{
+ return ((state & TIOCM_DTR) ? 1 : 0) | ((state & TIOCM_RTS) ? 2 : 0);
+}
+
+#ifdef CONFIG_GIGASET_UNDOCREQ
+/* WARNING: EXPERIMENTAL! */
+static int gigaset_set_modem_ctrl(struct cardstate *cs, unsigned old_state,
+ unsigned new_state)
+{
+ unsigned mask, val;
+ int r;
+
+ mask = tiocm_to_gigaset(old_state ^ new_state);
+ val = tiocm_to_gigaset(new_state);
+
+ dbg(DEBUG_USBREQ, "set flags 0x%02x with mask 0x%02x", val, mask);
+ r = usb_control_msg(cs->hw.usb->udev,
+ usb_sndctrlpipe(cs->hw.usb->udev, 0), 7, 0x41,
+ (val & 0xff) | ((mask & 0xff) << 8), 0,
+ NULL, 0, 2000 /*timeout??*/); // don't use this in an interrupt/BH
+ if (r < 0)
+ return r;
+ //..
+ return 0;
+}
+
+static int set_value(struct cardstate *cs, u8 req, u16 val)
+{
+ int r, r2;
+
+ dbg(DEBUG_USBREQ, "request %02x (%04x)", (unsigned)req, (unsigned)val);
+ r = usb_control_msg(cs->hw.usb->udev,
+ usb_sndctrlpipe(cs->hw.usb->udev, 0), 0x12, 0x41,
+ 0xf /*?*/, 0,
+ NULL, 0, 2000 /*?*/); /* no idea, what this does */
+ if (r < 0) {
+ err("error %d on request 0x12", -r);
+ return r;
+ }
+
+ r = usb_control_msg(cs->hw.usb->udev,
+ usb_sndctrlpipe(cs->hw.usb->udev, 0), req, 0x41,
+ val, 0,
+ NULL, 0, 2000 /*?*/);
+ if (r < 0)
+ err("error %d on request 0x%02x", -r, (unsigned)req);
+
+ r2 = usb_control_msg(cs->hw.usb->udev,
+ usb_sndctrlpipe(cs->hw.usb->udev, 0), 0x19, 0x41,
+ 0, 0, cs->hw.usb->bchars, 6, 2000 /*?*/);
+ if (r2 < 0)
+ err("error %d on request 0x19", -r2);
+
+ return r < 0 ? r : (r2 < 0 ? r2 : 0);
+}
+
+/* WARNING: HIGHLY EXPERIMENTAL! */
+// don't use this in an interrupt/BH
+static int gigaset_baud_rate(struct cardstate *cs, unsigned cflag)
+{
+ u16 val;
+ u32 rate;
+
+ cflag &= CBAUD;
+
+ switch (cflag) {
+ //FIXME more values?
+ 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;
+ err("unsupported baudrate request 0x%x,"
+ " using default of B9600", cflag);
+ }
+
+ val = 0x383fff / rate + 1;
+
+ return set_value(cs, 1, val);
+}
+
+/* WARNING: HIGHLY EXPERIMENTAL! */
+// don't use this in an interrupt/BH
+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:
+ err("CSIZE was not CS5-CS8, using default of 8");
+ val |= 8 << 8;
+ break;
+ }
+
+ /* set the number of stop bits */
+ if (cflag & CSTOPB) {
+ if ((cflag & CSIZE) == CS5)
+ val |= 1; /* 1.5 stop bits */ //FIXME is this okay?
+ else
+ val |= 2; /* 2 stop bits */
+ }
+
+ return set_value(cs, 3, val);
+}
+
+#else
+static int gigaset_set_modem_ctrl(struct cardstate *cs, unsigned old_state,
+ unsigned new_state)
+{
+ return -EINVAL;
+}
+
+static int gigaset_set_line_ctrl(struct cardstate *cs, unsigned cflag)
+{
+ return -EINVAL;
+}
+
+static int gigaset_baud_rate(struct cardstate *cs, unsigned cflag)
+{
+ return -EINVAL;
+}
+#endif
+
+
+ /*================================================================================================================*/
+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;
+}
+
+//void send_ack_to_LL(void *data);
+static int write_modem(struct cardstate *cs);
+static int send_cb(struct cardstate *cs, struct cmdbuf_t *cb);
+
+
+/* Handling of send queue. 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.
+ * This function will be called by the ISR via "transmit_chars" (USB: B-Channel Bulk callback handler
+ * via immediate task queue) or by writebuf_from_LL if the LL wants to transmit data.
+ */
+static void gigaset_modem_fill(unsigned long data)
+{
+ struct cardstate *cs = (struct cardstate *) data;
+ struct bc_state *bcs = &cs->bcs[0]; /* only one channel */
+ struct cmdbuf_t *cb;
+ unsigned long flags;
+ int again;
+
+ dbg(DEBUG_OUTPUT, "modem_fill");
+
+ if (atomic_read(&cs->hw.usb->busy)) {
+ dbg(DEBUG_OUTPUT, "modem_fill: busy");
+ return;
+ }
+
+ do {
+ again = 0;
+ if (!bcs->tx_skb) { /* no skb is being sent */
+ spin_lock_irqsave(&cs->cmdlock, flags);
+ cb = cs->cmdbuf;
+ spin_unlock_irqrestore(&cs->cmdlock, flags);
+ if (cb) { /* commands to send? */
+ dbg(DEBUG_OUTPUT, "modem_fill: cb");
+ if (send_cb(cs, cb) < 0) {
+ dbg(DEBUG_OUTPUT,
+ "modem_fill: send_cb failed");
+ again = 1; /* no callback will be called! */
+ }
+ } else { /* skbs to send? */
+ bcs->tx_skb = skb_dequeue(&bcs->squeue);
+ if (bcs->tx_skb)
+ dbg(DEBUG_INTR,
+ "Dequeued skb (Adr: %lx)!",
+ (unsigned long) bcs->tx_skb);
+ }
+ }
+
+ if (bcs->tx_skb) {
+ dbg(DEBUG_OUTPUT, "modem_fill: tx_skb");
+ if (write_modem(cs) < 0) {
+ dbg(DEBUG_OUTPUT,
+ "modem_fill: write_modem failed");
+ // FIXME should we tell the LL?
+ again = 1; /* no callback will be called! */
+ }
+ }
+ } while (again);
+}
+
+/**
+ * gigaset_read_int_callback
+ *
+ * It is called if the data was received from the device. This is almost similiar to
+ * the interrupt service routine in the serial device.
+ */
+static void gigaset_read_int_callback(struct urb *urb, struct pt_regs *regs)
+{
+ int resubmit = 0;
+ int r;
+ struct cardstate *cs;
+ unsigned numbytes;
+ unsigned char *src;
+ //unsigned long flags;
+ struct inbuf_t *inbuf;
+
+ IFNULLRET(urb);
+ inbuf = (struct inbuf_t *) urb->context;
+ IFNULLRET(inbuf);
+ //spin_lock_irqsave(&inbuf->lock, flags);
+ cs = inbuf->cs;
+ IFNULLGOTO(cs, exit);
+ IFNULLGOTO(cardstate, exit);
+
+ if (!atomic_read(&cs->connected)) {
+ err("%s: disconnected", __func__);
+ goto exit;
+ }
+
+ if (!urb->status) {
+ numbytes = urb->actual_length;
+
+ if (numbytes) {
+ src = inbuf->rcvbuf;
+ if (unlikely(*src))
+ warn("%s: There was no leading 0, but 0x%02x!",
+ __func__, (unsigned) *src);
+ ++src; /* skip leading 0x00 */
+ --numbytes;
+ if (gigaset_fill_inbuf(inbuf, src, numbytes)) {
+ dbg(DEBUG_INTR, "%s-->BH", __func__);
+ gigaset_schedule_event(inbuf->cs);
+ }
+ } else
+ dbg(DEBUG_INTR, "Received zero block length");
+ resubmit = 1;
+ } else {
+ /* The urb might have been killed. */
+ dbg(DEBUG_ANY, "%s - nonzero read bulk status received: %d",
+ __func__, urb->status);
+ if (urb->status != -ENOENT) /* not killed */
+ resubmit = 1;
+ }
+exit:
+ //spin_unlock_irqrestore(&inbuf->lock, flags);
+ if (resubmit) {
+ r = usb_submit_urb(urb, SLAB_ATOMIC);
+ if (r)
+ err("error %d when resubmitting urb.", -r);
+ }
+}
+
+
+/* This callback routine is called when data was transmitted to a B-Channel.
+ * Therefore it has to check if there is still data to transmit. This
+ * happens by calling modem_fill via task queue.
+ *
+ */
+static void gigaset_write_bulk_callback(struct urb *urb, struct pt_regs *regs)
+{
+ struct cardstate *cs = (struct cardstate *) urb->context;
+
+ IFNULLRET(cs);
+#ifdef CONFIG_GIGASET_DEBUG
+ if (!atomic_read(&cs->connected)) {
+ err("%s:not connected", __func__);
+ return;
+ }
+#endif
+ if (urb->status)
+ err("bulk transfer failed (status %d)", -urb->status); /* That's all we can do. Communication problems
+ are handeled by timeouts or network protocols */
+
+ atomic_set(&cs->hw.usb->busy, 0);
+ tasklet_schedule(&cs->write_tasklet);
+}
+
+static int send_cb(struct cardstate *cs, struct cmdbuf_t *cb)
+{
+ struct cmdbuf_t *tcb;
+ unsigned long flags;
+ int count;
+ int status = -ENOENT; // FIXME
+ struct usb_cardstate *ucs = cs->hw.usb;
+
+ do {
+ if (!cb->len) {
+ tcb = cb;
+
+ spin_lock_irqsave(&cs->cmdlock, flags);
+ cs->cmdbytes -= cs->curlen;
+ dbg(DEBUG_OUTPUT, "send_cb: sent %u bytes, %u left",
+ cs->curlen, cs->cmdbytes);
+ 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);
+ }
+ if (cb) {
+ count = min(cb->len, ucs->bulk_out_size);
+ usb_fill_bulk_urb(ucs->bulk_out_urb, ucs->udev,
+ usb_sndbulkpipe(ucs->udev,
+ ucs->bulk_out_endpointAddr & 0x0f),
+ cb->buf + cb->offset, count,
+ gigaset_write_bulk_callback, cs);
+
+ cb->offset += count;
+ cb->len -= count;
+ atomic_set(&ucs->busy, 1);
+ dbg(DEBUG_OUTPUT, "send_cb: send %d bytes", count);
+
+ status = usb_submit_urb(ucs->bulk_out_urb, SLAB_ATOMIC);
+ if (status) {
+ atomic_set(&ucs->busy, 0);
+ err("could not submit urb (error %d).",
+ -status);
+ cb->len = 0; /* skip urb => remove cb+wakeup in next loop cycle */
+ }
+ }
+ } while (cb && status); /* bei Fehler naechster Befehl //FIXME: ist das OK? */
+
+ return status;
+}
+
+/* Write string into transbuf and send it to modem.
+ */
+static int gigaset_write_cmd(struct cardstate *cs, const unsigned char *buf,
+ int len, struct tasklet_struct *wake_tasklet)
+{
+ struct cmdbuf_t *cb;
+ unsigned long flags;
+
+ gigaset_dbg_buffer(atomic_read(&cs->mstate) != MS_LOCKED ?
+ DEBUG_TRANSCMD : DEBUG_LOCKCMD,
+ "CMD Transmit", len, buf, 0);
+
+ if (!atomic_read(&cs->connected)) {
+ err("%s: not connected", __func__);
+ return -ENODEV;
+ }
+
+ if (len <= 0)
+ return 0;
+
+ if (!(cb = kmalloc(sizeof(struct cmdbuf_t) + len, GFP_ATOMIC))) {
+ err("%s: out of memory", __func__);
+ return -ENOMEM;
+ }
+
+ memcpy(cb->buf, buf, len);
+ cb->len = len;
+ cb->offset = 0;
+ cb->next = NULL;
+ cb->wake_tasklet = wake_tasklet;
+
+ spin_lock_irqsave(&cs->cmdlock, flags);
+ cb->prev = cs->lastcmdbuf;
+ if (cs->lastcmdbuf)
+ cs->lastcmdbuf->next = cb;
+ else {
+ cs->cmdbuf = cb;
+ cs->curlen = len;
+ }
+ cs->cmdbytes += len;
+ cs->lastcmdbuf = cb;
+ spin_unlock_irqrestore(&cs->cmdlock, flags);
+
+ tasklet_schedule(&cs->write_tasklet);
+ return len;
+}
+
+static int gigaset_write_room(struct cardstate *cs)
+{
+ unsigned long flags;
+ unsigned bytes;
+
+ spin_lock_irqsave(&cs->cmdlock, flags);
+ bytes = cs->cmdbytes;
+ spin_unlock_irqrestore(&cs->cmdlock, flags);
+
+ return bytes < IF_WRITEBUF ? IF_WRITEBUF - bytes : 0;
+}
+
+static int gigaset_chars_in_buffer(struct cardstate *cs)
+{
+ return cs->cmdbytes;
+}
+
+static int gigaset_brkchars(struct cardstate *cs, const unsigned char buf[6])
+{
+#ifdef CONFIG_GIGASET_UNDOCREQ
+ gigaset_dbg_buffer(DEBUG_USBREQ, "brkchars", 6, buf, 0);
+ memcpy(cs->hw.usb->bchars, buf, 6);
+ return usb_control_msg(cs->hw.usb->udev,
+ usb_sndctrlpipe(cs->hw.usb->udev, 0), 0x19, 0x41,
+ 0, 0, &buf, 6, 2000);
+#else
+ return -EINVAL;
+#endif
+}
+
+static int gigaset_freebcshw(struct bc_state *bcs)
+{
+ if (!bcs->hw.usb)
+ return 0;
+ //FIXME
+ kfree(bcs->hw.usb);
+ return 1;
+}
+
+/* Initialize the b-channel structure */
+static int gigaset_initbcshw(struct bc_state *bcs)
+{
+ bcs->hw.usb = kmalloc(sizeof(struct usb_bc_state), GFP_KERNEL);
+ if (!bcs->hw.usb)
+ return 0;
+
+ //bcs->hw.usb->trans_flg = READY_TO_TRNSMIT; /* B-Channel ready to transmit */
+ return 1;
+}
+
+static void gigaset_reinitbcshw(struct bc_state *bcs)
+{
+}
+
+static void gigaset_freecshw(struct cardstate *cs)
+{
+ //FIXME
+ 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)
+ return 0;
+
+ 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->urb_cmd_out = NULL;
+ ucs->read_urb = NULL;
+ tasklet_init(&cs->write_tasklet,
+ &gigaset_modem_fill, (unsigned long) cs);
+
+ return 1;
+}
+
+/* Writes the data of the current open skb into the modem.
+ * We have to protect against multiple calls until the
+ * callback handler () is called , due to the fact that we
+ * are just allowed to send data once to an endpoint. Therefore
+ * we using "trans_flg" to synchonize ...
+ */
+static int write_modem(struct cardstate *cs)
+{
+ int ret;
+ int count;
+ struct bc_state *bcs = &cs->bcs[0]; /* only one channel */
+ struct usb_cardstate *ucs = cs->hw.usb;
+ //unsigned long flags;
+
+ IFNULLRETVAL(bcs->tx_skb, -EINVAL);
+
+ dbg(DEBUG_WRITE, "len: %d...", bcs->tx_skb->len);
+
+ ret = -ENODEV;
+ IFNULLGOTO(ucs->bulk_out_buffer, error);
+ IFNULLGOTO(ucs->bulk_out_urb, error);
+ ret = 0;
+
+ 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 // FIXME copying not necessary
+ * transmit data
+ */
+ count = min(bcs->tx_skb->len, (unsigned) ucs->bulk_out_size);
+ memcpy(ucs->bulk_out_buffer, bcs->tx_skb->data, count);
+ skb_pull(bcs->tx_skb, count);
+
+ usb_fill_bulk_urb(ucs->bulk_out_urb, ucs->udev,
+ usb_sndbulkpipe(ucs->udev,
+ ucs->bulk_out_endpointAddr & 0x0f),
+ ucs->bulk_out_buffer, count,
+ gigaset_write_bulk_callback, cs);
+ atomic_set(&ucs->busy, 1);
+ dbg(DEBUG_OUTPUT, "write_modem: send %d bytes", count);
+
+ ret = usb_submit_urb(ucs->bulk_out_urb, SLAB_ATOMIC);
+ if (ret) {
+ err("could not submit urb (error %d).", -ret);
+ atomic_set(&ucs->busy, 0);
+ }
+ if (!bcs->tx_skb->len) {
+ /* skb sent completely */
+ gigaset_skb_sent(bcs, bcs->tx_skb); //FIXME also, when ret<0?
+
+ 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;
+error:
+ 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);
+ unsigned int ifnum;
+ struct usb_host_interface *hostif;
+ struct cardstate *cs = NULL;
+ struct usb_cardstate *ucs = NULL;
+ //struct usb_interface_descriptor *iface_desc;
+ struct usb_endpoint_descriptor *endpoint;
+ //isdn_ctrl command;
+ int buffer_size;
+ int alt;
+ //unsigned long flags;
+
+ info("%s: Check if device matches .. (Vendor: 0x%x, Product: 0x%x)",
+ __func__, le16_to_cpu(udev->descriptor.idVendor),
+ le16_to_cpu(udev->descriptor.idProduct));
+
+ retval = -ENODEV; //FIXME
+
+ /* 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)))
+ return -ENODEV;
+
+ /* this starts to become ascii art... */
+ hostif = interface->cur_altsetting;
+ alt = hostif->desc.bAlternateSetting;
+ ifnum = hostif->desc.bInterfaceNumber; // FIXME ?
+
+ if (alt != 0 || ifnum != 0) {
+ warn("ifnum %d, alt %d", ifnum, alt);
+ return -ENODEV;
+ }
+
+ /* Reject application specific intefaces
+ *
+ */
+ if (hostif->desc.bInterfaceClass != 255) {
+ info("%s: Device matched, but iface_desc[%d]->bInterfaceClass==%d !",
+ __func__, ifnum, hostif->desc.bInterfaceClass);
+ return -ENODEV;
+ }
+
+ info("%s: Device matched ... !", __func__);
+
+ cs = gigaset_getunassignedcs(driver);
+ if (!cs) {
+ warn("No free cardstate!");
+ return -ENODEV;
+ }
+ ucs = cs->hw.usb;
+
+#if 0
+ if (usb_set_configuration(udev, udev->config[0].desc.bConfigurationValue) < 0) {
+ warn("set_configuration failed");
+ goto error;
+ }
+
+
+ if (usb_set_interface(udev, ifnum/*==0*/, alt/*==0*/) < 0) {
+ warn("usb_set_interface failed, device %d interface %d altsetting %d",
+ udev->devnum, ifnum, alt);
+ goto error;
+ }
+#endif
+
+ /* set up the endpoint information */
+ /* check out the endpoints */
+ /* We will get 2 endpoints: One for sending commands to the device (bulk out) and one to
+ * poll messages from the device(int in).
+ * Therefore we will have an almost similiar situation as with our serial port handler.
+ * If an connection will be established, we will have to create data in/out pipes
+ * dynamically...
+ */
+
+ endpoint = &hostif->endpoint[0].desc;
+
+ buffer_size = le16_to_cpu(endpoint->wMaxPacketSize);
+ ucs->bulk_out_size = buffer_size;
+ ucs->bulk_out_endpointAddr = endpoint->bEndpointAddress;
+ ucs->bulk_out_buffer = kmalloc(buffer_size, GFP_KERNEL);
+ if (!ucs->bulk_out_buffer) {
+ err("Couldn't allocate bulk_out_buffer");
+ retval = -ENOMEM;
+ goto error;
+ }
+
+ ucs->bulk_out_urb = usb_alloc_urb(0, SLAB_KERNEL);
+ if (!ucs->bulk_out_urb) {
+ err("Couldn't allocate bulk_out_buffer");
+ retval = -ENOMEM;
+ goto error;
+ }
+
+ endpoint = &hostif->endpoint[1].desc;
+
+ atomic_set(&ucs->busy, 0);
+ ucs->udev = udev;
+ ucs->interface = interface;
+
+ ucs->read_urb = usb_alloc_urb(0, SLAB_KERNEL);
+ if (!ucs->read_urb) {
+ err("No free urbs available");
+ retval = -ENOMEM;
+ goto error;
+ }
+ buffer_size = le16_to_cpu(endpoint->wMaxPacketSize);
+ ucs->rcvbuf_size = buffer_size;
+ ucs->int_in_endpointAddr = endpoint->bEndpointAddress;
+ cs->inbuf[0].rcvbuf = kmalloc(buffer_size, GFP_KERNEL);
+ if (!cs->inbuf[0].rcvbuf) {
+ err("Couldn't allocate rcvbuf");
+ 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,
+ endpoint->bEndpointAddress & 0x0f),
+ cs->inbuf[0].rcvbuf, buffer_size,
+ gigaset_read_int_callback,
+ cs->inbuf + 0, endpoint->bInterval);
+
+ retval = usb_submit_urb(ucs->read_urb, SLAB_KERNEL);
+ if (retval) {
+ err("Could not submit URB!");
+ goto error;
+ }
+
+ /* tell common part that the device is ready */
+ if (startmode == SM_LOCKED)
+ atomic_set(&cs->mstate, MS_LOCKED);
+ if (!gigaset_start(cs)) {
+ tasklet_kill(&cs->write_tasklet);
+ retval = -ENODEV; //FIXME
+ goto error;
+ }
+
+ /* save address of controller structure */
+ usb_set_intfdata(interface, cs);
+
+ /* set up device sysfs */
+ gigaset_init_dev_sysfs(interface);
+ return 0;
+
+error:
+ if (ucs->read_urb)
+ usb_kill_urb(ucs->read_urb);
+ kfree(ucs->bulk_out_buffer);
+ if (ucs->bulk_out_urb != NULL)
+ usb_free_urb(ucs->bulk_out_urb);
+ kfree(cs->inbuf[0].rcvbuf);
+ if (ucs->read_urb != NULL)
+ usb_free_urb(ucs->read_urb);
+ ucs->read_urb = ucs->bulk_out_urb = NULL;
+ cs->inbuf[0].rcvbuf = ucs->bulk_out_buffer = NULL;
+ gigaset_unassign(cs);
+ return retval;
+}
+
+/**
+ * skel_disconnect
+ */
+static void gigaset_disconnect(struct usb_interface *interface)
+{
+ struct cardstate *cs;
+ struct usb_cardstate *ucs;
+
+ cs = usb_get_intfdata(interface);
+
+ /* clear device sysfs */
+ gigaset_free_dev_sysfs(interface);
+
+ usb_set_intfdata(interface, NULL);
+ ucs = cs->hw.usb;
+ usb_kill_urb(ucs->read_urb);
+ //info("GigaSet USB device #%d will be disconnected", minor);
+
+ gigaset_stop(cs);
+
+ tasklet_kill(&cs->write_tasklet);
+
+ usb_kill_urb(ucs->bulk_out_urb); /* FIXME: nur, wenn noetig */
+ //usb_kill_urb(ucs->urb_cmd_out); /* FIXME: nur, wenn noetig */
+
+ kfree(ucs->bulk_out_buffer);
+ if (ucs->bulk_out_urb != NULL)
+ usb_free_urb(ucs->bulk_out_urb);
+ //if(ucs->urb_cmd_out != NULL)
+ // usb_free_urb(ucs->urb_cmd_out);
+ kfree(cs->inbuf[0].rcvbuf);
+ if (ucs->read_urb != NULL)
+ usb_free_urb(ucs->read_urb);
+ ucs->read_urb = ucs->bulk_out_urb/*=ucs->urb_cmd_out*/=NULL;
+ cs->inbuf[0].rcvbuf = ucs->bulk_out_buffer = NULL;
+
+ gigaset_unassign(cs);
+}
+
+static struct gigaset_ops ops = {
+ gigaset_write_cmd,
+ gigaset_write_room,
+ gigaset_chars_in_buffer,
+ gigaset_brkchars,
+ gigaset_init_bchannel,
+ gigaset_close_bchannel,
+ gigaset_initbcshw,
+ gigaset_freebcshw,
+ gigaset_reinitbcshw,
+ gigaset_initcshw,
+ gigaset_freecshw,
+ gigaset_set_modem_ctrl,
+ gigaset_baud_rate,
+ gigaset_set_line_ctrl,
+ gigaset_m10x_send_skb,
+ gigaset_m10x_input,
+};
+
+/**
+ * usb_gigaset_init
+ * 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 intialize it */
+ if ((driver = gigaset_initdriver(GIGASET_MINOR, GIGASET_MINORS,
+ GIGASET_MODULENAME, GIGASET_DEVNAME,
+ GIGASET_DEVFSNAME, &ops,
+ THIS_MODULE)) == NULL)
+ goto error;
+
+ /* allocate memory for our device state and intialize it */
+ cardstate = gigaset_initcs(driver, 1, 1, 0, cidmode, GIGASET_MODULENAME);
+ if (!cardstate)
+ goto error;
+
+ /* register this driver with the USB subsystem */
+ result = usb_register(&gigaset_usb_driver);
+ if (result < 0) {
+ err("usb_gigaset: usb_register failed (error %d)",
+ -result);
+ goto error;
+ }
+
+ info(DRIVER_AUTHOR);
+ info(DRIVER_DESC);
+ return 0;
+
+error: if (cardstate)
+ gigaset_freecs(cardstate);
+ cardstate = NULL;
+ if (driver)
+ gigaset_freedriver(driver);
+ driver = NULL;
+ return -1;
+}
+
+
+/**
+ * usb_gigaset_exit
+ * This function is called while unloading the kernel-module
+ */
+static void __exit usb_gigaset_exit(void)
+{
+ gigaset_blockdriver(driver); /* => probe will fail
+ * => no gigaset_start any more
+ */
+
+ gigaset_shutdown(cardstate);
+ /* 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_freecs(cardstate);
+ cardstate = NULL;
+ 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");