Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net
authorDavid S. Miller <davem@davemloft.net>
Fri, 7 Jun 2019 18:00:14 +0000 (11:00 -0700)
committerDavid S. Miller <davem@davemloft.net>
Fri, 7 Jun 2019 18:00:14 +0000 (11:00 -0700)
Some ISDN files that got removed in net-next had some changes
done in mainline, take the removals.

Signed-off-by: David S. Miller <davem@davemloft.net>
113 files changed:
1  2 
MAINTAINERS
drivers/i2c/i2c-core-acpi.c
drivers/isdn/hardware/mISDN/netjet.c
drivers/media/dvb-frontends/tua6100.c
drivers/net/dsa/mv88e6xxx/chip.c
drivers/net/dsa/mv88e6xxx/chip.h
drivers/net/dsa/mv88e6xxx/global1.c
drivers/net/dsa/mv88e6xxx/global1.h
drivers/net/dsa/mv88e6xxx/global1_atu.c
drivers/net/dsa/mv88e6xxx/global1_vtu.c
drivers/net/dsa/mv88e6xxx/global2.c
drivers/net/dsa/mv88e6xxx/global2.h
drivers/net/dsa/mv88e6xxx/port.c
drivers/net/dsa/mv88e6xxx/port.h
drivers/net/dsa/mv88e6xxx/smi.c
drivers/net/dsa/sja1105/sja1105_main.c
drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c
drivers/net/ethernet/mediatek/mtk_eth_soc.c
drivers/net/ethernet/rocker/rocker_main.c
drivers/net/ethernet/rocker/rocker_ofdpa.c
drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c
drivers/net/macvlan.c
drivers/net/phy/phylink.c
drivers/net/phy/sfp.c
drivers/net/plip/plip.c
drivers/net/wan/hdlc_cisco.c
drivers/staging/isdn/gigaset/asyncdata.c
drivers/staging/isdn/gigaset/bas-gigaset.c
drivers/staging/isdn/gigaset/capi.c
drivers/staging/isdn/gigaset/common.c
drivers/staging/isdn/gigaset/dummyll.c
drivers/staging/isdn/gigaset/ev-layer.c
drivers/staging/isdn/gigaset/gigaset.h
drivers/staging/isdn/gigaset/interface.c
drivers/staging/isdn/gigaset/isocdata.c
drivers/staging/isdn/gigaset/proc.c
drivers/staging/isdn/gigaset/ser-gigaset.c
drivers/staging/isdn/gigaset/usb-gigaset.c
fs/afs/cmservice.c
fs/afs/internal.h
include/dt-bindings/net/ti-dp83867.h
include/linux/bpf.h
include/linux/bpf_verifier.h
include/linux/igmp.h
include/linux/phy.h
include/linux/skbuff.h
include/net/devlink.h
include/net/dsa.h
include/net/ip.h
include/net/ip6_fib.h
include/net/ip_fib.h
include/net/ipv6.h
include/net/tcp.h
include/net/tls.h
kernel/bpf/core.c
kernel/bpf/stackmap.c
kernel/cgroup/cgroup.c
net/8021q/vlan_dev.c
net/bridge/br_device.c
net/bridge/br_private.h
net/core/devlink.c
net/core/filter.c
net/core/neighbour.c
net/core/pktgen.c
net/core/skbuff.c
net/dsa/dsa2.c
net/dsa/dsa_priv.h
net/dsa/port.c
net/dsa/slave.c
net/ethernet/eth.c
net/ieee802154/6lowpan/reassembly.c
net/ipv4/devinet.c
net/ipv4/fib_frontend.c
net/ipv4/fib_rules.c
net/ipv4/fib_semantics.c
net/ipv4/fib_trie.c
net/ipv4/icmp.c
net/ipv4/igmp.c
net/ipv4/inet_connection_sock.c
net/ipv4/inet_fragment.c
net/ipv4/inet_hashtables.c
net/ipv4/proc.c
net/ipv4/route.c
net/ipv4/tcp.c
net/ipv4/tcp_ipv4.c
net/ipv4/udp.c
net/ipv4/udp_offload.c
net/ipv6/addrconf.c
net/ipv6/af_inet6.c
net/ipv6/icmp.c
net/ipv6/inet6_hashtables.c
net/ipv6/ip6_fib.c
net/ipv6/ip6_output.c
net/ipv6/ndisc.c
net/ipv6/netfilter/nf_conntrack_reasm.c
net/ipv6/proc.c
net/ipv6/reassembly.c
net/ipv6/route.c
net/ipv6/tcp_ipv6.c
net/ipv6/udp.c
net/netfilter/ipvs/ip_vs_app.c
net/netfilter/ipvs/ip_vs_core.c
net/netfilter/ipvs/ip_vs_ctl.c
net/netfilter/ipvs/ip_vs_ftp.c
net/netfilter/ipvs/ip_vs_proto_tcp.c
net/netfilter/ipvs/ip_vs_proto_udp.c
net/netfilter/ipvs/ip_vs_xmit.c
net/netfilter/nf_conntrack_broadcast.c
net/rds/ib.c
net/sctp/offload.c
net/socket.c
net/tls/tls_device.c
tools/testing/selftests/bpf/test_verifier.c

diff --cc MAINTAINERS
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
@@@ -1695,21 -1606,23 +1698,23 @@@ static int phylink_sfp_module_insert(vo
                return ret;
        }
  
+       linkmode_copy(support1, support);
        iface = sfp_select_interface(pl->sfp_bus, id, config.advertising);
        if (iface == PHY_INTERFACE_MODE_NA) {
 -              netdev_err(pl->netdev,
 -                         "selection of interface failed, advertisement %*pb\n",
 -                         __ETHTOOL_LINK_MODE_MASK_NBITS, config.advertising);
 +              phylink_err(pl,
 +                          "selection of interface failed, advertisement %*pb\n",
 +                          __ETHTOOL_LINK_MODE_MASK_NBITS, config.advertising);
                return -EINVAL;
        }
  
        config.interface = iface;
-       ret = phylink_validate(pl, support, &config);
+       ret = phylink_validate(pl, support1, &config);
        if (ret) {
 -              netdev_err(pl->netdev, "validation of %s/%s with support %*pb failed: %d\n",
 -                         phylink_an_mode_str(MLO_AN_INBAND),
 -                         phy_modes(config.interface),
 -                         __ETHTOOL_LINK_MODE_MASK_NBITS, support, ret);
 +              phylink_err(pl, "validation of %s/%s with support %*pb failed: %d\n",
 +                          phylink_an_mode_str(MLO_AN_INBAND),
 +                          phy_modes(config.interface),
 +                          __ETHTOOL_LINK_MODE_MASK_NBITS, support, ret);
                return ret;
        }
  
Simple merge
Simple merge
Simple merge
index c0cbee0,0000000..a34b3c9
mode 100644,000000..100644
--- /dev/null
@@@ -1,609 -1,0 +1,606 @@@
-  *    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.
++// SPDX-License-Identifier: GPL-2.0-or-later
 +/*
 + * 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.
 + *
 + * =====================================================================
 + * =====================================================================
 + */
 +
 +#include "gigaset.h"
 +#include <linux/crc-ccitt.h>
 +#include <linux/bitrev.h>
 +#include <linux/export.h>
 +
 +/* check if byte must be stuffed/escaped
 + * I'm not sure which data should be encoded.
 + * Therefore I will go the hard way and encode every value
 + * less than 0x20, the flag sequence and the control escape char.
 + */
 +static inline int muststuff(unsigned char c)
 +{
 +      if (c < PPP_TRANS) return 1;
 +      if (c == PPP_FLAG) return 1;
 +      if (c == PPP_ESCAPE) return 1;
 +      /* other possible candidates: */
 +      /* 0x91: XON with parity set */
 +      /* 0x93: XOFF with parity set */
 +      return 0;
 +}
 +
 +/* == data input =========================================================== */
 +
 +/* process a block of received bytes in command mode
 + * (mstate != MS_LOCKED && (inputstate & INS_command))
 + * Append received bytes to the command response buffer and forward them
 + * line by line to the response handler. Exit whenever a mode/state change
 + * might have occurred.
 + * Note: Received lines may be terminated by CR, LF, or CR LF, which will be
 + * removed before passing the line to the response handler.
 + * Return value:
 + *    number of processed bytes
 + */
 +static unsigned cmd_loop(unsigned numbytes, struct inbuf_t *inbuf)
 +{
 +      unsigned char *src = inbuf->data + inbuf->head;
 +      struct cardstate *cs = inbuf->cs;
 +      unsigned cbytes = cs->cbytes;
 +      unsigned procbytes = 0;
 +      unsigned char c;
 +
 +      while (procbytes < numbytes) {
 +              c = *src++;
 +              procbytes++;
 +
 +              switch (c) {
 +              case '\n':
 +                      if (cbytes == 0 && cs->respdata[0] == '\r') {
 +                              /* collapse LF with preceding CR */
 +                              cs->respdata[0] = 0;
 +                              break;
 +                      }
 +                      /* fall through */
 +              case '\r':
 +                      /* end of message line, pass to response handler */
 +                      if (cbytes >= MAX_RESP_SIZE) {
 +                              dev_warn(cs->dev, "response too large (%d)\n",
 +                                       cbytes);
 +                              cbytes = MAX_RESP_SIZE;
 +                      }
 +                      cs->cbytes = cbytes;
 +                      gigaset_dbg_buffer(DEBUG_TRANSCMD, "received response",
 +                                         cbytes, cs->respdata);
 +                      gigaset_handle_modem_response(cs);
 +                      cbytes = 0;
 +
 +                      /* store EOL byte for CRLF collapsing */
 +                      cs->respdata[0] = c;
 +
 +                      /* cs->dle may have changed */
 +                      if (cs->dle && !(inbuf->inputstate & INS_DLE_command))
 +                              inbuf->inputstate &= ~INS_command;
 +
 +                      /* return for reevaluating state */
 +                      goto exit;
 +
 +              case DLE_FLAG:
 +                      if (inbuf->inputstate & INS_DLE_char) {
 +                              /* quoted DLE: clear quote flag */
 +                              inbuf->inputstate &= ~INS_DLE_char;
 +                      } else if (cs->dle ||
 +                                 (inbuf->inputstate & INS_DLE_command)) {
 +                              /* DLE escape, pass up for handling */
 +                              inbuf->inputstate |= INS_DLE_char;
 +                              goto exit;
 +                      }
 +                      /* quoted or not in DLE mode: treat as regular data */
 +                      /* fall through */
 +              default:
 +                      /* append to line buffer if possible */
 +                      if (cbytes < MAX_RESP_SIZE)
 +                              cs->respdata[cbytes] = c;
 +                      cbytes++;
 +              }
 +      }
 +exit:
 +      cs->cbytes = cbytes;
 +      return procbytes;
 +}
 +
 +/* process a block of received bytes in lock mode
 + * All received bytes are passed unmodified to the tty i/f.
 + * Return value:
 + *    number of processed bytes
 + */
 +static unsigned lock_loop(unsigned numbytes, struct inbuf_t *inbuf)
 +{
 +      unsigned char *src = inbuf->data + inbuf->head;
 +
 +      gigaset_dbg_buffer(DEBUG_LOCKCMD, "received response", numbytes, src);
 +      gigaset_if_receive(inbuf->cs, src, numbytes);
 +      return numbytes;
 +}
 +
 +/* process a block of received bytes in HDLC data mode
 + * (mstate != MS_LOCKED && !(inputstate & INS_command) && proto2 == L2_HDLC)
 + * Collect HDLC frames, undoing byte stuffing and watching for DLE escapes.
 + * When a frame is complete, check the FCS and pass valid frames to the LL.
 + * If DLE is encountered, return immediately to let the caller handle it.
 + * Return value:
 + *    number of processed bytes
 + */
 +static unsigned hdlc_loop(unsigned numbytes, struct inbuf_t *inbuf)
 +{
 +      struct cardstate *cs = inbuf->cs;
 +      struct bc_state *bcs = cs->bcs;
 +      int inputstate = bcs->inputstate;
 +      __u16 fcs = bcs->rx_fcs;
 +      struct sk_buff *skb = bcs->rx_skb;
 +      unsigned char *src = inbuf->data + inbuf->head;
 +      unsigned procbytes = 0;
 +      unsigned char c;
 +
 +      if (inputstate & INS_byte_stuff) {
 +              if (!numbytes)
 +                      return 0;
 +              inputstate &= ~INS_byte_stuff;
 +              goto byte_stuff;
 +      }
 +
 +      while (procbytes < numbytes) {
 +              c = *src++;
 +              procbytes++;
 +              if (c == DLE_FLAG) {
 +                      if (inputstate & INS_DLE_char) {
 +                              /* quoted DLE: clear quote flag */
 +                              inputstate &= ~INS_DLE_char;
 +                      } else if (cs->dle || (inputstate & INS_DLE_command)) {
 +                              /* DLE escape, pass up for handling */
 +                              inputstate |= INS_DLE_char;
 +                              break;
 +                      }
 +              }
 +
 +              if (c == PPP_ESCAPE) {
 +                      /* byte stuffing indicator: pull in next byte */
 +                      if (procbytes >= numbytes) {
 +                              /* end of buffer, save for later processing */
 +                              inputstate |= INS_byte_stuff;
 +                              break;
 +                      }
 +byte_stuff:
 +                      c = *src++;
 +                      procbytes++;
 +                      if (c == DLE_FLAG) {
 +                              if (inputstate & INS_DLE_char) {
 +                                      /* quoted DLE: clear quote flag */
 +                                      inputstate &= ~INS_DLE_char;
 +                              } else if (cs->dle ||
 +                                         (inputstate & INS_DLE_command)) {
 +                                      /* DLE escape, pass up for handling */
 +                                      inputstate |=
 +                                              INS_DLE_char | INS_byte_stuff;
 +                                      break;
 +                              }
 +                      }
 +                      c ^= PPP_TRANS;
 +#ifdef CONFIG_GIGASET_DEBUG
 +                      if (!muststuff(c))
 +                              gig_dbg(DEBUG_HDLC, "byte stuffed: 0x%02x", c);
 +#endif
 +              } else if (c == PPP_FLAG) {
 +                      /* end of frame: process content if any */
 +                      if (inputstate & INS_have_data) {
 +                              gig_dbg(DEBUG_HDLC,
 +                                      "7e----------------------------");
 +
 +                              /* check and pass received frame */
 +                              if (!skb) {
 +                                      /* skipped frame */
 +                                      gigaset_isdn_rcv_err(bcs);
 +                              } else if (skb->len < 2) {
 +                                      /* frame too short for FCS */
 +                                      dev_warn(cs->dev,
 +                                               "short frame (%d)\n",
 +                                               skb->len);
 +                                      gigaset_isdn_rcv_err(bcs);
 +                                      dev_kfree_skb_any(skb);
 +                              } else if (fcs != PPP_GOODFCS) {
 +                                      /* frame check error */
 +                                      dev_err(cs->dev,
 +                                              "Checksum failed, %u bytes corrupted!\n",
 +                                              skb->len);
 +                                      gigaset_isdn_rcv_err(bcs);
 +                                      dev_kfree_skb_any(skb);
 +                              } else {
 +                                      /* good frame */
 +                                      __skb_trim(skb, skb->len - 2);
 +                                      gigaset_skb_rcvd(bcs, skb);
 +                              }
 +
 +                              /* prepare reception of next frame */
 +                              inputstate &= ~INS_have_data;
 +                              skb = gigaset_new_rx_skb(bcs);
 +                      } else {
 +                              /* empty frame (7E 7E) */
 +#ifdef CONFIG_GIGASET_DEBUG
 +                              ++bcs->emptycount;
 +#endif
 +                              if (!skb) {
 +                                      /* skipped (?) */
 +                                      gigaset_isdn_rcv_err(bcs);
 +                                      skb = gigaset_new_rx_skb(bcs);
 +                              }
 +                      }
 +
 +                      fcs = PPP_INITFCS;
 +                      continue;
 +#ifdef CONFIG_GIGASET_DEBUG
 +              } else if (muststuff(c)) {
 +                      /* Should not happen. Possible after ZDLE=1<CR><LF>. */
 +                      gig_dbg(DEBUG_HDLC, "not byte stuffed: 0x%02x", c);
 +#endif
 +              }
 +
 +              /* regular data byte, append to skb */
 +#ifdef CONFIG_GIGASET_DEBUG
 +              if (!(inputstate & INS_have_data)) {
 +                      gig_dbg(DEBUG_HDLC, "7e (%d x) ================",
 +                              bcs->emptycount);
 +                      bcs->emptycount = 0;
 +              }
 +#endif
 +              inputstate |= INS_have_data;
 +              if (skb) {
 +                      if (skb->len >= bcs->rx_bufsize) {
 +                              dev_warn(cs->dev, "received packet too long\n");
 +                              dev_kfree_skb_any(skb);
 +                              /* skip remainder of packet */
 +                              bcs->rx_skb = skb = NULL;
 +                      } else {
 +                              __skb_put_u8(skb, c);
 +                              fcs = crc_ccitt_byte(fcs, c);
 +                      }
 +              }
 +      }
 +
 +      bcs->inputstate = inputstate;
 +      bcs->rx_fcs = fcs;
 +      return procbytes;
 +}
 +
 +/* process a block of received bytes in transparent data mode
 + * (mstate != MS_LOCKED && !(inputstate & INS_command) && proto2 != L2_HDLC)
 + * Invert bytes, undoing byte stuffing and watching for DLE escapes.
 + * If DLE is encountered, return immediately to let the caller handle it.
 + * Return value:
 + *    number of processed bytes
 + */
 +static unsigned iraw_loop(unsigned numbytes, struct inbuf_t *inbuf)
 +{
 +      struct cardstate *cs = inbuf->cs;
 +      struct bc_state *bcs = cs->bcs;
 +      int inputstate = bcs->inputstate;
 +      struct sk_buff *skb = bcs->rx_skb;
 +      unsigned char *src = inbuf->data + inbuf->head;
 +      unsigned procbytes = 0;
 +      unsigned char c;
 +
 +      if (!skb) {
 +              /* skip this block */
 +              gigaset_new_rx_skb(bcs);
 +              return numbytes;
 +      }
 +
 +      while (procbytes < numbytes && skb->len < bcs->rx_bufsize) {
 +              c = *src++;
 +              procbytes++;
 +
 +              if (c == DLE_FLAG) {
 +                      if (inputstate & INS_DLE_char) {
 +                              /* quoted DLE: clear quote flag */
 +                              inputstate &= ~INS_DLE_char;
 +                      } else if (cs->dle || (inputstate & INS_DLE_command)) {
 +                              /* DLE escape, pass up for handling */
 +                              inputstate |= INS_DLE_char;
 +                              break;
 +                      }
 +              }
 +
 +              /* regular data byte: append to current skb */
 +              inputstate |= INS_have_data;
 +              __skb_put_u8(skb, bitrev8(c));
 +      }
 +
 +      /* pass data up */
 +      if (inputstate & INS_have_data) {
 +              gigaset_skb_rcvd(bcs, skb);
 +              inputstate &= ~INS_have_data;
 +              gigaset_new_rx_skb(bcs);
 +      }
 +
 +      bcs->inputstate = inputstate;
 +      return procbytes;
 +}
 +
 +/* process DLE escapes
 + * Called whenever a DLE sequence might be encountered in the input stream.
 + * Either processes the entire DLE sequence or, if that isn't possible,
 + * notes the fact that an initial DLE has been received in the INS_DLE_char
 + * inputstate flag and resumes processing of the sequence on the next call.
 + */
 +static void handle_dle(struct inbuf_t *inbuf)
 +{
 +      struct cardstate *cs = inbuf->cs;
 +
 +      if (cs->mstate == MS_LOCKED)
 +              return;         /* no DLE processing in lock mode */
 +
 +      if (!(inbuf->inputstate & INS_DLE_char)) {
 +              /* no DLE pending */
 +              if (inbuf->data[inbuf->head] == DLE_FLAG &&
 +                  (cs->dle || inbuf->inputstate & INS_DLE_command)) {
 +                      /* start of DLE sequence */
 +                      inbuf->head++;
 +                      if (inbuf->head == inbuf->tail ||
 +                          inbuf->head == RBUFSIZE) {
 +                              /* end of buffer, save for later processing */
 +                              inbuf->inputstate |= INS_DLE_char;
 +                              return;
 +                      }
 +              } else {
 +                      /* regular data byte */
 +                      return;
 +              }
 +      }
 +
 +      /* consume pending DLE */
 +      inbuf->inputstate &= ~INS_DLE_char;
 +
 +      switch (inbuf->data[inbuf->head]) {
 +      case 'X':       /* begin of event message */
 +              if (inbuf->inputstate & INS_command)
 +                      dev_notice(cs->dev,
 +                                 "received <DLE>X in command mode\n");
 +              inbuf->inputstate |= INS_command | INS_DLE_command;
 +              inbuf->head++;  /* byte consumed */
 +              break;
 +      case '.':       /* end of event message */
 +              if (!(inbuf->inputstate & INS_DLE_command))
 +                      dev_notice(cs->dev,
 +                                 "received <DLE>. without <DLE>X\n");
 +              inbuf->inputstate &= ~INS_DLE_command;
 +              /* return to data mode if in DLE mode */
 +              if (cs->dle)
 +                      inbuf->inputstate &= ~INS_command;
 +              inbuf->head++;  /* byte consumed */
 +              break;
 +      case DLE_FLAG:  /* DLE in data stream */
 +              /* mark as quoted */
 +              inbuf->inputstate |= INS_DLE_char;
 +              if (!(cs->dle || inbuf->inputstate & INS_DLE_command))
 +                      dev_notice(cs->dev,
 +                                 "received <DLE><DLE> not in DLE mode\n");
 +              break;  /* quoted byte left in buffer */
 +      default:
 +              dev_notice(cs->dev, "received <DLE><%02x>\n",
 +                         inbuf->data[inbuf->head]);
 +              /* quoted byte left in buffer */
 +      }
 +}
 +
 +/**
 + * gigaset_m10x_input() - process a block of data received from the device
 + * @inbuf:    received data and device descriptor structure.
 + *
 + * Called by hardware module {ser,usb}_gigaset with a block of received
 + * bytes. Separates the bytes received over the serial data channel into
 + * user data and command replies (locked/unlocked) according to the
 + * current state of the interface.
 + */
 +void gigaset_m10x_input(struct inbuf_t *inbuf)
 +{
 +      struct cardstate *cs = inbuf->cs;
 +      unsigned numbytes, procbytes;
 +
 +      gig_dbg(DEBUG_INTR, "buffer state: %u -> %u", inbuf->head, inbuf->tail);
 +
 +      while (inbuf->head != inbuf->tail) {
 +              /* check for DLE escape */
 +              handle_dle(inbuf);
 +
 +              /* process a contiguous block of bytes */
 +              numbytes = (inbuf->head > inbuf->tail ?
 +                          RBUFSIZE : inbuf->tail) - inbuf->head;
 +              gig_dbg(DEBUG_INTR, "processing %u bytes", numbytes);
 +              /*
 +               * numbytes may be 0 if handle_dle() ate the last byte.
 +               * This does no harm, *_loop() will just return 0 immediately.
 +               */
 +
 +              if (cs->mstate == MS_LOCKED)
 +                      procbytes = lock_loop(numbytes, inbuf);
 +              else if (inbuf->inputstate & INS_command)
 +                      procbytes = cmd_loop(numbytes, inbuf);
 +              else if (cs->bcs->proto2 == L2_HDLC)
 +                      procbytes = hdlc_loop(numbytes, inbuf);
 +              else
 +                      procbytes = iraw_loop(numbytes, inbuf);
 +              inbuf->head += procbytes;
 +
 +              /* check for buffer wraparound */
 +              if (inbuf->head >= RBUFSIZE)
 +                      inbuf->head = 0;
 +
 +              gig_dbg(DEBUG_INTR, "head set to %u", inbuf->head);
 +      }
 +}
 +EXPORT_SYMBOL_GPL(gigaset_m10x_input);
 +
 +
 +/* == data output ========================================================== */
 +
 +/*
 + * Encode a data packet into an octet stuffed HDLC frame with FCS,
 + * opening and closing flags, preserving headroom data.
 + * parameters:
 + *    skb             skb containing original packet (freed upon return)
 + * Return value:
 + *    pointer to newly allocated skb containing the result frame
 + *    and the original link layer header, NULL on error
 + */
 +static struct sk_buff *HDLC_Encode(struct sk_buff *skb)
 +{
 +      struct sk_buff *hdlc_skb;
 +      __u16 fcs;
 +      unsigned char c;
 +      unsigned char *cp;
 +      int len;
 +      unsigned int stuf_cnt;
 +
 +      stuf_cnt = 0;
 +      fcs = PPP_INITFCS;
 +      cp = skb->data;
 +      len = skb->len;
 +      while (len--) {
 +              if (muststuff(*cp))
 +                      stuf_cnt++;
 +              fcs = crc_ccitt_byte(fcs, *cp++);
 +      }
 +      fcs ^= 0xffff;                  /* complement */
 +
 +      /* size of new buffer: original size + number of stuffing bytes
 +       * + 2 bytes FCS + 2 stuffing bytes for FCS (if needed) + 2 flag bytes
 +       * + room for link layer header
 +       */
 +      hdlc_skb = dev_alloc_skb(skb->len + stuf_cnt + 6 + skb->mac_len);
 +      if (!hdlc_skb) {
 +              dev_kfree_skb_any(skb);
 +              return NULL;
 +      }
 +
 +      /* Copy link layer header into new skb */
 +      skb_reset_mac_header(hdlc_skb);
 +      skb_reserve(hdlc_skb, skb->mac_len);
 +      memcpy(skb_mac_header(hdlc_skb), skb_mac_header(skb), skb->mac_len);
 +      hdlc_skb->mac_len = skb->mac_len;
 +
 +      /* Add flag sequence in front of everything.. */
 +      skb_put_u8(hdlc_skb, PPP_FLAG);
 +
 +      /* Perform byte stuffing while copying data. */
 +      while (skb->len--) {
 +              if (muststuff(*skb->data)) {
 +                      skb_put_u8(hdlc_skb, PPP_ESCAPE);
 +                      skb_put_u8(hdlc_skb, (*skb->data++) ^ PPP_TRANS);
 +              } else
 +                      skb_put_u8(hdlc_skb, *skb->data++);
 +      }
 +
 +      /* Finally add FCS (byte stuffed) and flag sequence */
 +      c = (fcs & 0x00ff);     /* least significant byte first */
 +      if (muststuff(c)) {
 +              skb_put_u8(hdlc_skb, PPP_ESCAPE);
 +              c ^= PPP_TRANS;
 +      }
 +      skb_put_u8(hdlc_skb, c);
 +
 +      c = ((fcs >> 8) & 0x00ff);
 +      if (muststuff(c)) {
 +              skb_put_u8(hdlc_skb, PPP_ESCAPE);
 +              c ^= PPP_TRANS;
 +      }
 +      skb_put_u8(hdlc_skb, c);
 +
 +      skb_put_u8(hdlc_skb, PPP_FLAG);
 +
 +      dev_kfree_skb_any(skb);
 +      return hdlc_skb;
 +}
 +
 +/*
 + * Encode a data packet into an octet stuffed raw bit inverted frame,
 + * preserving headroom data.
 + * parameters:
 + *    skb             skb containing original packet (freed upon return)
 + * Return value:
 + *    pointer to newly allocated skb containing the result frame
 + *    and the original link layer header, NULL on error
 + */
 +static struct sk_buff *iraw_encode(struct sk_buff *skb)
 +{
 +      struct sk_buff *iraw_skb;
 +      unsigned char c;
 +      unsigned char *cp;
 +      int len;
 +
 +      /* size of new buffer (worst case = every byte must be stuffed):
 +       * 2 * original size + room for link layer header
 +       */
 +      iraw_skb = dev_alloc_skb(2 * skb->len + skb->mac_len);
 +      if (!iraw_skb) {
 +              dev_kfree_skb_any(skb);
 +              return NULL;
 +      }
 +
 +      /* copy link layer header into new skb */
 +      skb_reset_mac_header(iraw_skb);
 +      skb_reserve(iraw_skb, skb->mac_len);
 +      memcpy(skb_mac_header(iraw_skb), skb_mac_header(skb), skb->mac_len);
 +      iraw_skb->mac_len = skb->mac_len;
 +
 +      /* copy and stuff data */
 +      cp = skb->data;
 +      len = skb->len;
 +      while (len--) {
 +              c = bitrev8(*cp++);
 +              if (c == DLE_FLAG)
 +                      skb_put_u8(iraw_skb, c);
 +              skb_put_u8(iraw_skb, c);
 +      }
 +      dev_kfree_skb_any(skb);
 +      return iraw_skb;
 +}
 +
 +/**
 + * gigaset_m10x_send_skb() - queue an skb for sending
 + * @bcs:      B channel descriptor structure.
 + * @skb:      data to send.
 + *
 + * Called by LL to encode and queue an skb for sending, and start
 + * transmission if necessary.
 + * Once the payload data has been transmitted completely, gigaset_skb_sent()
 + * will be called with the skb's link layer header preserved.
 + *
 + * Return value:
 + *    number of bytes accepted for sending (skb->len) if ok,
 + *    error code < 0 (eg. -ENOMEM) on error
 + */
 +int gigaset_m10x_send_skb(struct bc_state *bcs, struct sk_buff *skb)
 +{
 +      struct cardstate *cs = bcs->cs;
 +      unsigned len = skb->len;
 +      unsigned long flags;
 +
 +      if (bcs->proto2 == L2_HDLC)
 +              skb = HDLC_Encode(skb);
 +      else
 +              skb = iraw_encode(skb);
 +      if (!skb) {
 +              dev_err(cs->dev,
 +                      "unable to allocate memory for encoding!\n");
 +              return -ENOMEM;
 +      }
 +
 +      skb_queue_tail(&bcs->squeue, skb);
 +      spin_lock_irqsave(&cs->lock, flags);
 +      if (cs->connected)
 +              tasklet_schedule(&cs->write_tasklet);
 +      spin_unlock_irqrestore(&cs->lock, flags);
 +
 +      return len;     /* ok so far */
 +}
 +EXPORT_SYMBOL_GPL(gigaset_m10x_send_skb);
index 149b1ac,0000000..c334525
mode 100644,000000..100644
--- /dev/null
@@@ -1,2675 -1,0 +1,2672 @@@
-  *    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.
++// SPDX-License-Identifier: GPL-2.0-or-later
 +/*
 + * 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.
 + *
 + * =====================================================================
 + * =====================================================================
 + */
 +
 +#include "gigaset.h"
 +#include <linux/usb.h>
 +#include <linux/module.h>
 +#include <linux/moduleparam.h>
 +
 +/* Version Information */
 +#define DRIVER_AUTHOR "Tilman Schmidt <tilman@imap.cc>, Hansjoerg Lipp <hjlipp@web.de>, Stefan Eilers"
 +#define DRIVER_DESC "USB Driver for Gigaset 307x"
 +
 +
 +/* Module parameters */
 +
 +static int startmode = SM_ISDN;
 +static int cidmode = 1;
 +
 +module_param(startmode, int, S_IRUGO);
 +module_param(cidmode, int, S_IRUGO);
 +MODULE_PARM_DESC(startmode, "start in isdn4linux mode");
 +MODULE_PARM_DESC(cidmode, "Call-ID mode");
 +
 +#define GIGASET_MINORS     1
 +#define GIGASET_MINOR      16
 +#define GIGASET_MODULENAME "bas_gigaset"
 +#define GIGASET_DEVNAME    "ttyGB"
 +
 +/* length limit according to Siemens 3070usb-protokoll.doc ch. 2.1 */
 +#define IF_WRITEBUF 264
 +
 +/* interrupt pipe message size according to ibid. ch. 2.2 */
 +#define IP_MSGSIZE 3
 +
 +/* Values for the Gigaset 307x */
 +#define USB_GIGA_VENDOR_ID      0x0681
 +#define USB_3070_PRODUCT_ID     0x0001
 +#define USB_3075_PRODUCT_ID     0x0002
 +#define USB_SX303_PRODUCT_ID    0x0021
 +#define USB_SX353_PRODUCT_ID    0x0022
 +
 +/* table of devices that work with this driver */
 +static const struct usb_device_id gigaset_table[] = {
 +      { USB_DEVICE(USB_GIGA_VENDOR_ID, USB_3070_PRODUCT_ID) },
 +      { USB_DEVICE(USB_GIGA_VENDOR_ID, USB_3075_PRODUCT_ID) },
 +      { USB_DEVICE(USB_GIGA_VENDOR_ID, USB_SX303_PRODUCT_ID) },
 +      { USB_DEVICE(USB_GIGA_VENDOR_ID, USB_SX353_PRODUCT_ID) },
 +      { } /* Terminating entry */
 +};
 +
 +MODULE_DEVICE_TABLE(usb, gigaset_table);
 +
 +/*======================= local function prototypes ==========================*/
 +
 +/* function called if a new device belonging to this driver is connected */
 +static int gigaset_probe(struct usb_interface *interface,
 +                       const struct usb_device_id *id);
 +
 +/* Function will be called if the device is unplugged */
 +static void gigaset_disconnect(struct usb_interface *interface);
 +
 +/* functions called before/after suspend */
 +static int gigaset_suspend(struct usb_interface *intf, pm_message_t message);
 +static int gigaset_resume(struct usb_interface *intf);
 +
 +/* functions called before/after device reset */
 +static int gigaset_pre_reset(struct usb_interface *intf);
 +static int gigaset_post_reset(struct usb_interface *intf);
 +
 +static int atread_submit(struct cardstate *, int);
 +static void stopurbs(struct bas_bc_state *);
 +static int req_submit(struct bc_state *, int, int, int);
 +static int atwrite_submit(struct cardstate *, unsigned char *, int);
 +static int start_cbsend(struct cardstate *);
 +
 +/*============================================================================*/
 +
 +struct bas_cardstate {
 +      struct usb_device       *udev;          /* USB device pointer */
 +      struct cardstate        *cs;
 +      struct usb_interface    *interface;     /* interface for this device */
 +      unsigned char           minor;          /* starting minor number */
 +
 +      struct urb              *urb_ctrl;      /* control pipe default URB */
 +      struct usb_ctrlrequest  dr_ctrl;
 +      struct timer_list       timer_ctrl;     /* control request timeout */
 +      int                     retry_ctrl;
 +
 +      struct timer_list       timer_atrdy;    /* AT command ready timeout */
 +      struct urb              *urb_cmd_out;   /* for sending AT commands */
 +      struct usb_ctrlrequest  dr_cmd_out;
 +      int                     retry_cmd_out;
 +
 +      struct urb              *urb_cmd_in;    /* for receiving AT replies */
 +      struct usb_ctrlrequest  dr_cmd_in;
 +      struct timer_list       timer_cmd_in;   /* receive request timeout */
 +      unsigned char           *rcvbuf;        /* AT reply receive buffer */
 +
 +      struct urb              *urb_int_in;    /* URB for interrupt pipe */
 +      unsigned char           *int_in_buf;
 +      struct work_struct      int_in_wq;      /* for usb_clear_halt() */
 +      struct timer_list       timer_int_in;   /* int read retry delay */
 +      int                     retry_int_in;
 +
 +      spinlock_t              lock;           /* locks all following */
 +      int                     basstate;       /* bitmap (BS_*) */
 +      int                     pending;        /* uncompleted base request */
 +      wait_queue_head_t       waitqueue;
 +      int                     rcvbuf_size;    /* size of AT receive buffer */
 +                                              /* 0: no receive in progress */
 +      int                     retry_cmd_in;   /* receive req retry count */
 +};
 +
 +/* status of direct USB connection to 307x base (bits in basstate) */
 +#define BS_ATOPEN     0x001   /* AT channel open */
 +#define BS_B1OPEN     0x002   /* B channel 1 open */
 +#define BS_B2OPEN     0x004   /* B channel 2 open */
 +#define BS_ATREADY    0x008   /* base ready for AT command */
 +#define BS_INIT               0x010   /* base has signalled INIT_OK */
 +#define BS_ATTIMER    0x020   /* waiting for HD_READY_SEND_ATDATA */
 +#define BS_ATRDPEND   0x040   /* urb_cmd_in in use */
 +#define BS_ATWRPEND   0x080   /* urb_cmd_out in use */
 +#define BS_SUSPEND    0x100   /* USB port suspended */
 +#define BS_RESETTING  0x200   /* waiting for HD_RESET_INTERRUPT_PIPE_ACK */
 +
 +
 +static struct gigaset_driver *driver;
 +
 +/* usb specific object needed to register this driver with the usb subsystem */
 +static struct usb_driver gigaset_usb_driver = {
 +      .name =         GIGASET_MODULENAME,
 +      .probe =        gigaset_probe,
 +      .disconnect =   gigaset_disconnect,
 +      .id_table =     gigaset_table,
 +      .suspend =      gigaset_suspend,
 +      .resume =       gigaset_resume,
 +      .reset_resume = gigaset_post_reset,
 +      .pre_reset =    gigaset_pre_reset,
 +      .post_reset =   gigaset_post_reset,
 +      .disable_hub_initiated_lpm = 1,
 +};
 +
 +/* get message text for usb_submit_urb return code
 + */
 +static char *get_usb_rcmsg(int rc)
 +{
 +      static char unkmsg[28];
 +
 +      switch (rc) {
 +      case 0:
 +              return "success";
 +      case -ENOMEM:
 +              return "out of memory";
 +      case -ENODEV:
 +              return "device not present";
 +      case -ENOENT:
 +              return "endpoint not present";
 +      case -ENXIO:
 +              return "URB type not supported";
 +      case -EINVAL:
 +              return "invalid argument";
 +      case -EAGAIN:
 +              return "start frame too early or too much scheduled";
 +      case -EFBIG:
 +              return "too many isoc frames requested";
 +      case -EPIPE:
 +              return "endpoint stalled";
 +      case -EMSGSIZE:
 +              return "invalid packet size";
 +      case -ENOSPC:
 +              return "would overcommit USB bandwidth";
 +      case -ESHUTDOWN:
 +              return "device shut down";
 +      case -EPERM:
 +              return "reject flag set";
 +      case -EHOSTUNREACH:
 +              return "device suspended";
 +      default:
 +              snprintf(unkmsg, sizeof(unkmsg), "unknown error %d", rc);
 +              return unkmsg;
 +      }
 +}
 +
 +/* get message text for USB status code
 + */
 +static char *get_usb_statmsg(int status)
 +{
 +      static char unkmsg[28];
 +
 +      switch (status) {
 +      case 0:
 +              return "success";
 +      case -ENOENT:
 +              return "unlinked (sync)";
 +      case -EINPROGRESS:
 +              return "URB still pending";
 +      case -EPROTO:
 +              return "bitstuff error, timeout, or unknown USB error";
 +      case -EILSEQ:
 +              return "CRC mismatch, timeout, or unknown USB error";
 +      case -ETIME:
 +              return "USB response timeout";
 +      case -EPIPE:
 +              return "endpoint stalled";
 +      case -ECOMM:
 +              return "IN buffer overrun";
 +      case -ENOSR:
 +              return "OUT buffer underrun";
 +      case -EOVERFLOW:
 +              return "endpoint babble";
 +      case -EREMOTEIO:
 +              return "short packet";
 +      case -ENODEV:
 +              return "device removed";
 +      case -EXDEV:
 +              return "partial isoc transfer";
 +      case -EINVAL:
 +              return "ISO madness";
 +      case -ECONNRESET:
 +              return "unlinked (async)";
 +      case -ESHUTDOWN:
 +              return "device shut down";
 +      default:
 +              snprintf(unkmsg, sizeof(unkmsg), "unknown status %d", status);
 +              return unkmsg;
 +      }
 +}
 +
 +/* usb_pipetype_str
 + * retrieve string representation of USB pipe type
 + */
 +static inline char *usb_pipetype_str(int pipe)
 +{
 +      if (usb_pipeisoc(pipe))
 +              return "Isoc";
 +      if (usb_pipeint(pipe))
 +              return "Int";
 +      if (usb_pipecontrol(pipe))
 +              return "Ctrl";
 +      if (usb_pipebulk(pipe))
 +              return "Bulk";
 +      return "?";
 +}
 +
 +/* dump_urb
 + * write content of URB to syslog for debugging
 + */
 +static inline void dump_urb(enum debuglevel level, const char *tag,
 +                          struct urb *urb)
 +{
 +#ifdef CONFIG_GIGASET_DEBUG
 +      int i;
 +      gig_dbg(level, "%s urb(0x%08lx)->{", tag, (unsigned long) urb);
 +      if (urb) {
 +              gig_dbg(level,
 +                      "  dev=0x%08lx, pipe=%s:EP%d/DV%d:%s, "
 +                      "hcpriv=0x%08lx, transfer_flags=0x%x,",
 +                      (unsigned long) urb->dev,
 +                      usb_pipetype_str(urb->pipe),
 +                      usb_pipeendpoint(urb->pipe), usb_pipedevice(urb->pipe),
 +                      usb_pipein(urb->pipe) ? "in" : "out",
 +                      (unsigned long) urb->hcpriv,
 +                      urb->transfer_flags);
 +              gig_dbg(level,
 +                      "  transfer_buffer=0x%08lx[%d], actual_length=%d, "
 +                      "setup_packet=0x%08lx,",
 +                      (unsigned long) urb->transfer_buffer,
 +                      urb->transfer_buffer_length, urb->actual_length,
 +                      (unsigned long) urb->setup_packet);
 +              gig_dbg(level,
 +                      "  start_frame=%d, number_of_packets=%d, interval=%d, "
 +                      "error_count=%d,",
 +                      urb->start_frame, urb->number_of_packets, urb->interval,
 +                      urb->error_count);
 +              gig_dbg(level,
 +                      "  context=0x%08lx, complete=0x%08lx, "
 +                      "iso_frame_desc[]={",
 +                      (unsigned long) urb->context,
 +                      (unsigned long) urb->complete);
 +              for (i = 0; i < urb->number_of_packets; i++) {
 +                      struct usb_iso_packet_descriptor *pifd
 +                              = &urb->iso_frame_desc[i];
 +                      gig_dbg(level,
 +                              "    {offset=%u, length=%u, actual_length=%u, "
 +                              "status=%u}",
 +                              pifd->offset, pifd->length, pifd->actual_length,
 +                              pifd->status);
 +              }
 +      }
 +      gig_dbg(level, "}}");
 +#endif
 +}
 +
 +/* read/set modem control bits etc. (m10x only) */
 +static int gigaset_set_modem_ctrl(struct cardstate *cs, unsigned old_state,
 +                                unsigned new_state)
 +{
 +      return -EINVAL;
 +}
 +
 +static int gigaset_baud_rate(struct cardstate *cs, unsigned cflag)
 +{
 +      return -EINVAL;
 +}
 +
 +static int gigaset_set_line_ctrl(struct cardstate *cs, unsigned cflag)
 +{
 +      return -EINVAL;
 +}
 +
 +/* set/clear bits in base connection state, return previous state
 + */
 +static inline int update_basstate(struct bas_cardstate *ucs,
 +                                int set, int clear)
 +{
 +      unsigned long flags;
 +      int state;
 +
 +      spin_lock_irqsave(&ucs->lock, flags);
 +      state = ucs->basstate;
 +      ucs->basstate = (state & ~clear) | set;
 +      spin_unlock_irqrestore(&ucs->lock, flags);
 +      return state;
 +}
 +
 +/* error_hangup
 + * hang up any existing connection because of an unrecoverable error
 + * This function may be called from any context and takes care of scheduling
 + * the necessary actions for execution outside of interrupt context.
 + * cs->lock must not be held.
 + * argument:
 + *    B channel control structure
 + */
 +static inline void error_hangup(struct bc_state *bcs)
 +{
 +      struct cardstate *cs = bcs->cs;
 +
 +      gigaset_add_event(cs, &bcs->at_state, EV_HUP, NULL, 0, NULL);
 +      gigaset_schedule_event(cs);
 +}
 +
 +/* error_reset
 + * reset Gigaset device because of an unrecoverable error
 + * This function may be called from any context, and takes care of
 + * scheduling the necessary actions for execution outside of interrupt context.
 + * cs->hw.bas->lock must not be held.
 + * argument:
 + *    controller state structure
 + */
 +static inline void error_reset(struct cardstate *cs)
 +{
 +      /* reset interrupt pipe to recover (ignore errors) */
 +      update_basstate(cs->hw.bas, BS_RESETTING, 0);
 +      if (req_submit(cs->bcs, HD_RESET_INTERRUPT_PIPE, 0, BAS_TIMEOUT))
 +              /* submission failed, escalate to USB port reset */
 +              usb_queue_reset_device(cs->hw.bas->interface);
 +}
 +
 +/* check_pending
 + * check for completion of pending control request
 + * parameter:
 + *    ucs     hardware specific controller state structure
 + */
 +static void check_pending(struct bas_cardstate *ucs)
 +{
 +      unsigned long flags;
 +
 +      spin_lock_irqsave(&ucs->lock, flags);
 +      switch (ucs->pending) {
 +      case 0:
 +              break;
 +      case HD_OPEN_ATCHANNEL:
 +              if (ucs->basstate & BS_ATOPEN)
 +                      ucs->pending = 0;
 +              break;
 +      case HD_OPEN_B1CHANNEL:
 +              if (ucs->basstate & BS_B1OPEN)
 +                      ucs->pending = 0;
 +              break;
 +      case HD_OPEN_B2CHANNEL:
 +              if (ucs->basstate & BS_B2OPEN)
 +                      ucs->pending = 0;
 +              break;
 +      case HD_CLOSE_ATCHANNEL:
 +              if (!(ucs->basstate & BS_ATOPEN))
 +                      ucs->pending = 0;
 +              break;
 +      case HD_CLOSE_B1CHANNEL:
 +              if (!(ucs->basstate & BS_B1OPEN))
 +                      ucs->pending = 0;
 +              break;
 +      case HD_CLOSE_B2CHANNEL:
 +              if (!(ucs->basstate & BS_B2OPEN))
 +                      ucs->pending = 0;
 +              break;
 +      case HD_DEVICE_INIT_ACK:                /* no reply expected */
 +              ucs->pending = 0;
 +              break;
 +      case HD_RESET_INTERRUPT_PIPE:
 +              if (!(ucs->basstate & BS_RESETTING))
 +                      ucs->pending = 0;
 +              break;
 +      /*
 +       * HD_READ_ATMESSAGE and HD_WRITE_ATMESSAGE are handled separately
 +       * and should never end up here
 +       */
 +      default:
 +              dev_warn(&ucs->interface->dev,
 +                       "unknown pending request 0x%02x cleared\n",
 +                       ucs->pending);
 +              ucs->pending = 0;
 +      }
 +
 +      if (!ucs->pending)
 +              del_timer(&ucs->timer_ctrl);
 +
 +      spin_unlock_irqrestore(&ucs->lock, flags);
 +}
 +
 +/* cmd_in_timeout
 + * timeout routine for command input request
 + * argument:
 + *    controller state structure
 + */
 +static void cmd_in_timeout(struct timer_list *t)
 +{
 +      struct bas_cardstate *ucs = from_timer(ucs, t, timer_cmd_in);
 +      struct cardstate *cs = ucs->cs;
 +      int rc;
 +
 +      if (!ucs->rcvbuf_size) {
 +              gig_dbg(DEBUG_USBREQ, "%s: no receive in progress", __func__);
 +              return;
 +      }
 +
 +      if (ucs->retry_cmd_in++ >= BAS_RETRY) {
 +              dev_err(cs->dev,
 +                      "control read: timeout, giving up after %d tries\n",
 +                      ucs->retry_cmd_in);
 +              kfree(ucs->rcvbuf);
 +              ucs->rcvbuf = NULL;
 +              ucs->rcvbuf_size = 0;
 +              error_reset(cs);
 +              return;
 +      }
 +
 +      gig_dbg(DEBUG_USBREQ, "%s: timeout, retry %d",
 +              __func__, ucs->retry_cmd_in);
 +      rc = atread_submit(cs, BAS_TIMEOUT);
 +      if (rc < 0) {
 +              kfree(ucs->rcvbuf);
 +              ucs->rcvbuf = NULL;
 +              ucs->rcvbuf_size = 0;
 +              if (rc != -ENODEV)
 +                      error_reset(cs);
 +      }
 +}
 +
 +/* read_ctrl_callback
 + * USB completion handler for control pipe input
 + * called by the USB subsystem in interrupt context
 + * parameter:
 + *    urb     USB request block
 + *            urb->context = inbuf structure for controller state
 + */
 +static void read_ctrl_callback(struct urb *urb)
 +{
 +      struct inbuf_t *inbuf = urb->context;
 +      struct cardstate *cs = inbuf->cs;
 +      struct bas_cardstate *ucs = cs->hw.bas;
 +      int status = urb->status;
 +      unsigned numbytes;
 +      int rc;
 +
 +      update_basstate(ucs, 0, BS_ATRDPEND);
 +      wake_up(&ucs->waitqueue);
 +      del_timer(&ucs->timer_cmd_in);
 +
 +      switch (status) {
 +      case 0:                         /* normal completion */
 +              numbytes = urb->actual_length;
 +              if (unlikely(numbytes != ucs->rcvbuf_size)) {
 +                      dev_warn(cs->dev,
 +                               "control read: received %d chars, expected %d\n",
 +                               numbytes, ucs->rcvbuf_size);
 +                      if (numbytes > ucs->rcvbuf_size)
 +                              numbytes = ucs->rcvbuf_size;
 +              }
 +
 +              /* copy received bytes to inbuf, notify event layer */
 +              if (gigaset_fill_inbuf(inbuf, ucs->rcvbuf, numbytes)) {
 +                      gig_dbg(DEBUG_INTR, "%s-->BH", __func__);
 +                      gigaset_schedule_event(cs);
 +              }
 +              break;
 +
 +      case -ENOENT:                   /* cancelled */
 +      case -ECONNRESET:               /* cancelled (async) */
 +      case -EINPROGRESS:              /* pending */
 +      case -ENODEV:                   /* device removed */
 +      case -ESHUTDOWN:                /* device shut down */
 +              /* no further action necessary */
 +              gig_dbg(DEBUG_USBREQ, "%s: %s",
 +                      __func__, get_usb_statmsg(status));
 +              break;
 +
 +      default:                        /* other errors: retry */
 +              if (ucs->retry_cmd_in++ < BAS_RETRY) {
 +                      gig_dbg(DEBUG_USBREQ, "%s: %s, retry %d", __func__,
 +                              get_usb_statmsg(status), ucs->retry_cmd_in);
 +                      rc = atread_submit(cs, BAS_TIMEOUT);
 +                      if (rc >= 0)
 +                              /* successfully resubmitted, skip freeing */
 +                              return;
 +                      if (rc == -ENODEV)
 +                              /* disconnect, no further action necessary */
 +                              break;
 +              }
 +              dev_err(cs->dev, "control read: %s, giving up after %d tries\n",
 +                      get_usb_statmsg(status), ucs->retry_cmd_in);
 +              error_reset(cs);
 +      }
 +
 +      /* read finished, free buffer */
 +      kfree(ucs->rcvbuf);
 +      ucs->rcvbuf = NULL;
 +      ucs->rcvbuf_size = 0;
 +}
 +
 +/* atread_submit
 + * submit an HD_READ_ATMESSAGE command URB and optionally start a timeout
 + * parameters:
 + *    cs      controller state structure
 + *    timeout timeout in 1/10 sec., 0: none
 + * return value:
 + *    0 on success
 + *    -EBUSY if another request is pending
 + *    any URB submission error code
 + */
 +static int atread_submit(struct cardstate *cs, int timeout)
 +{
 +      struct bas_cardstate *ucs = cs->hw.bas;
 +      int basstate;
 +      int ret;
 +
 +      gig_dbg(DEBUG_USBREQ, "-------> HD_READ_ATMESSAGE (%d)",
 +              ucs->rcvbuf_size);
 +
 +      basstate = update_basstate(ucs, BS_ATRDPEND, 0);
 +      if (basstate & BS_ATRDPEND) {
 +              dev_err(cs->dev,
 +                      "could not submit HD_READ_ATMESSAGE: URB busy\n");
 +              return -EBUSY;
 +      }
 +
 +      if (basstate & BS_SUSPEND) {
 +              dev_notice(cs->dev,
 +                         "HD_READ_ATMESSAGE not submitted, "
 +                         "suspend in progress\n");
 +              update_basstate(ucs, 0, BS_ATRDPEND);
 +              /* treat like disconnect */
 +              return -ENODEV;
 +      }
 +
 +      ucs->dr_cmd_in.bRequestType = IN_VENDOR_REQ;
 +      ucs->dr_cmd_in.bRequest = HD_READ_ATMESSAGE;
 +      ucs->dr_cmd_in.wValue = 0;
 +      ucs->dr_cmd_in.wIndex = 0;
 +      ucs->dr_cmd_in.wLength = cpu_to_le16(ucs->rcvbuf_size);
 +      usb_fill_control_urb(ucs->urb_cmd_in, ucs->udev,
 +                           usb_rcvctrlpipe(ucs->udev, 0),
 +                           (unsigned char *) &ucs->dr_cmd_in,
 +                           ucs->rcvbuf, ucs->rcvbuf_size,
 +                           read_ctrl_callback, cs->inbuf);
 +
 +      ret = usb_submit_urb(ucs->urb_cmd_in, GFP_ATOMIC);
 +      if (ret != 0) {
 +              update_basstate(ucs, 0, BS_ATRDPEND);
 +              dev_err(cs->dev, "could not submit HD_READ_ATMESSAGE: %s\n",
 +                      get_usb_rcmsg(ret));
 +              return ret;
 +      }
 +
 +      if (timeout > 0) {
 +              gig_dbg(DEBUG_USBREQ, "setting timeout of %d/10 secs", timeout);
 +              mod_timer(&ucs->timer_cmd_in, jiffies + timeout * HZ / 10);
 +      }
 +      return 0;
 +}
 +
 +/* int_in_work
 + * workqueue routine to clear halt on interrupt in endpoint
 + */
 +
 +static void int_in_work(struct work_struct *work)
 +{
 +      struct bas_cardstate *ucs =
 +              container_of(work, struct bas_cardstate, int_in_wq);
 +      struct urb *urb = ucs->urb_int_in;
 +      struct cardstate *cs = urb->context;
 +      int rc;
 +
 +      /* clear halt condition */
 +      rc = usb_clear_halt(ucs->udev, urb->pipe);
 +      gig_dbg(DEBUG_USBREQ, "clear_halt: %s", get_usb_rcmsg(rc));
 +      if (rc == 0)
 +              /* success, resubmit interrupt read URB */
 +              rc = usb_submit_urb(urb, GFP_ATOMIC);
 +
 +      switch (rc) {
 +      case 0:         /* success */
 +      case -ENODEV:   /* device gone */
 +      case -EINVAL:   /* URB already resubmitted, or terminal badness */
 +              break;
 +      default:        /* failure: try to recover by resetting the device */
 +              dev_err(cs->dev, "clear halt failed: %s\n", get_usb_rcmsg(rc));
 +              rc = usb_lock_device_for_reset(ucs->udev, ucs->interface);
 +              if (rc == 0) {
 +                      rc = usb_reset_device(ucs->udev);
 +                      usb_unlock_device(ucs->udev);
 +              }
 +      }
 +      ucs->retry_int_in = 0;
 +}
 +
 +/* int_in_resubmit
 + * timer routine for interrupt read delayed resubmit
 + * argument:
 + *    controller state structure
 + */
 +static void int_in_resubmit(struct timer_list *t)
 +{
 +      struct bas_cardstate *ucs = from_timer(ucs, t, timer_int_in);
 +      struct cardstate *cs = ucs->cs;
 +      int rc;
 +
 +      if (ucs->retry_int_in++ >= BAS_RETRY) {
 +              dev_err(cs->dev, "interrupt read: giving up after %d tries\n",
 +                      ucs->retry_int_in);
 +              usb_queue_reset_device(ucs->interface);
 +              return;
 +      }
 +
 +      gig_dbg(DEBUG_USBREQ, "%s: retry %d", __func__, ucs->retry_int_in);
 +      rc = usb_submit_urb(ucs->urb_int_in, GFP_ATOMIC);
 +      if (rc != 0 && rc != -ENODEV) {
 +              dev_err(cs->dev, "could not resubmit interrupt URB: %s\n",
 +                      get_usb_rcmsg(rc));
 +              usb_queue_reset_device(ucs->interface);
 +      }
 +}
 +
 +/* read_int_callback
 + * USB completion handler for interrupt pipe input
 + * called by the USB subsystem in interrupt context
 + * parameter:
 + *    urb     USB request block
 + *            urb->context = controller state structure
 + */
 +static void read_int_callback(struct urb *urb)
 +{
 +      struct cardstate *cs = urb->context;
 +      struct bas_cardstate *ucs = cs->hw.bas;
 +      struct bc_state *bcs;
 +      int status = urb->status;
 +      unsigned long flags;
 +      int rc;
 +      unsigned l;
 +      int channel;
 +
 +      switch (status) {
 +      case 0:                 /* success */
 +              ucs->retry_int_in = 0;
 +              break;
 +      case -EPIPE:                    /* endpoint stalled */
 +              schedule_work(&ucs->int_in_wq);
 +              /* fall through */
 +      case -ENOENT:                   /* cancelled */
 +      case -ECONNRESET:               /* cancelled (async) */
 +      case -EINPROGRESS:              /* pending */
 +      case -ENODEV:                   /* device removed */
 +      case -ESHUTDOWN:                /* device shut down */
 +              /* no further action necessary */
 +              gig_dbg(DEBUG_USBREQ, "%s: %s",
 +                      __func__, get_usb_statmsg(status));
 +              return;
 +      case -EPROTO:                   /* protocol error or unplug */
 +      case -EILSEQ:
 +      case -ETIME:
 +              /* resubmit after delay */
 +              gig_dbg(DEBUG_USBREQ, "%s: %s",
 +                      __func__, get_usb_statmsg(status));
 +              mod_timer(&ucs->timer_int_in, jiffies + HZ / 10);
 +              return;
 +      default:                /* other errors: just resubmit */
 +              dev_warn(cs->dev, "interrupt read: %s\n",
 +                       get_usb_statmsg(status));
 +              goto resubmit;
 +      }
 +
 +      /* drop incomplete packets even if the missing bytes wouldn't matter */
 +      if (unlikely(urb->actual_length < IP_MSGSIZE)) {
 +              dev_warn(cs->dev, "incomplete interrupt packet (%d bytes)\n",
 +                       urb->actual_length);
 +              goto resubmit;
 +      }
 +
 +      l = (unsigned) ucs->int_in_buf[1] +
 +              (((unsigned) ucs->int_in_buf[2]) << 8);
 +
 +      gig_dbg(DEBUG_USBREQ, "<-------%d: 0x%02x (%u [0x%02x 0x%02x])",
 +              urb->actual_length, (int)ucs->int_in_buf[0], l,
 +              (int)ucs->int_in_buf[1], (int)ucs->int_in_buf[2]);
 +
 +      channel = 0;
 +
 +      switch (ucs->int_in_buf[0]) {
 +      case HD_DEVICE_INIT_OK:
 +              update_basstate(ucs, BS_INIT, 0);
 +              break;
 +
 +      case HD_READY_SEND_ATDATA:
 +              del_timer(&ucs->timer_atrdy);
 +              update_basstate(ucs, BS_ATREADY, BS_ATTIMER);
 +              start_cbsend(cs);
 +              break;
 +
 +      case HD_OPEN_B2CHANNEL_ACK:
 +              ++channel;
 +              /* fall through */
 +      case HD_OPEN_B1CHANNEL_ACK:
 +              bcs = cs->bcs + channel;
 +              update_basstate(ucs, BS_B1OPEN << channel, 0);
 +              gigaset_bchannel_up(bcs);
 +              break;
 +
 +      case HD_OPEN_ATCHANNEL_ACK:
 +              update_basstate(ucs, BS_ATOPEN, 0);
 +              start_cbsend(cs);
 +              break;
 +
 +      case HD_CLOSE_B2CHANNEL_ACK:
 +              ++channel;
 +              /* fall through */
 +      case HD_CLOSE_B1CHANNEL_ACK:
 +              bcs = cs->bcs + channel;
 +              update_basstate(ucs, 0, BS_B1OPEN << channel);
 +              stopurbs(bcs->hw.bas);
 +              gigaset_bchannel_down(bcs);
 +              break;
 +
 +      case HD_CLOSE_ATCHANNEL_ACK:
 +              update_basstate(ucs, 0, BS_ATOPEN);
 +              break;
 +
 +      case HD_B2_FLOW_CONTROL:
 +              ++channel;
 +              /* fall through */
 +      case HD_B1_FLOW_CONTROL:
 +              bcs = cs->bcs + channel;
 +              atomic_add((l - BAS_NORMFRAME) * BAS_CORRFRAMES,
 +                         &bcs->hw.bas->corrbytes);
 +              gig_dbg(DEBUG_ISO,
 +                      "Flow control (channel %d, sub %d): 0x%02x => %d",
 +                      channel, bcs->hw.bas->numsub, l,
 +                      atomic_read(&bcs->hw.bas->corrbytes));
 +              break;
 +
 +      case HD_RECEIVEATDATA_ACK:      /* AT response ready to be received */
 +              if (!l) {
 +                      dev_warn(cs->dev,
 +                               "HD_RECEIVEATDATA_ACK with length 0 ignored\n");
 +                      break;
 +              }
 +              spin_lock_irqsave(&cs->lock, flags);
 +              if (ucs->basstate & BS_ATRDPEND) {
 +                      spin_unlock_irqrestore(&cs->lock, flags);
 +                      dev_warn(cs->dev,
 +                               "HD_RECEIVEATDATA_ACK(%d) during HD_READ_ATMESSAGE(%d) ignored\n",
 +                               l, ucs->rcvbuf_size);
 +                      break;
 +              }
 +              if (ucs->rcvbuf_size) {
 +                      /* throw away previous buffer - we have no queue */
 +                      dev_err(cs->dev,
 +                              "receive AT data overrun, %d bytes lost\n",
 +                              ucs->rcvbuf_size);
 +                      kfree(ucs->rcvbuf);
 +                      ucs->rcvbuf_size = 0;
 +              }
 +              ucs->rcvbuf = kmalloc(l, GFP_ATOMIC);
 +              if (ucs->rcvbuf == NULL) {
 +                      spin_unlock_irqrestore(&cs->lock, flags);
 +                      dev_err(cs->dev, "out of memory receiving AT data\n");
 +                      break;
 +              }
 +              ucs->rcvbuf_size = l;
 +              ucs->retry_cmd_in = 0;
 +              rc = atread_submit(cs, BAS_TIMEOUT);
 +              if (rc < 0) {
 +                      kfree(ucs->rcvbuf);
 +                      ucs->rcvbuf = NULL;
 +                      ucs->rcvbuf_size = 0;
 +              }
 +              spin_unlock_irqrestore(&cs->lock, flags);
 +              if (rc < 0 && rc != -ENODEV)
 +                      error_reset(cs);
 +              break;
 +
 +      case HD_RESET_INTERRUPT_PIPE_ACK:
 +              update_basstate(ucs, 0, BS_RESETTING);
 +              dev_notice(cs->dev, "interrupt pipe reset\n");
 +              break;
 +
 +      case HD_SUSPEND_END:
 +              gig_dbg(DEBUG_USBREQ, "HD_SUSPEND_END");
 +              break;
 +
 +      default:
 +              dev_warn(cs->dev,
 +                       "unknown Gigaset signal 0x%02x (%u) ignored\n",
 +                       (int) ucs->int_in_buf[0], l);
 +      }
 +
 +      check_pending(ucs);
 +      wake_up(&ucs->waitqueue);
 +
 +resubmit:
 +      rc = usb_submit_urb(urb, GFP_ATOMIC);
 +      if (unlikely(rc != 0 && rc != -ENODEV)) {
 +              dev_err(cs->dev, "could not resubmit interrupt URB: %s\n",
 +                      get_usb_rcmsg(rc));
 +              error_reset(cs);
 +      }
 +}
 +
 +/* read_iso_callback
 + * USB completion handler for B channel isochronous input
 + * called by the USB subsystem in interrupt context
 + * parameter:
 + *    urb     USB request block of completed request
 + *            urb->context = bc_state structure
 + */
 +static void read_iso_callback(struct urb *urb)
 +{
 +      struct bc_state *bcs;
 +      struct bas_bc_state *ubc;
 +      int status = urb->status;
 +      unsigned long flags;
 +      int i, rc;
 +
 +      /* status codes not worth bothering the tasklet with */
 +      if (unlikely(status == -ENOENT ||
 +                   status == -ECONNRESET ||
 +                   status == -EINPROGRESS ||
 +                   status == -ENODEV ||
 +                   status == -ESHUTDOWN)) {
 +              gig_dbg(DEBUG_ISO, "%s: %s",
 +                      __func__, get_usb_statmsg(status));
 +              return;
 +      }
 +
 +      bcs = urb->context;
 +      ubc = bcs->hw.bas;
 +
 +      spin_lock_irqsave(&ubc->isoinlock, flags);
 +      if (likely(ubc->isoindone == NULL)) {
 +              /* pass URB to tasklet */
 +              ubc->isoindone = urb;
 +              ubc->isoinstatus = status;
 +              tasklet_hi_schedule(&ubc->rcvd_tasklet);
 +      } else {
 +              /* tasklet still busy, drop data and resubmit URB */
 +              gig_dbg(DEBUG_ISO, "%s: overrun", __func__);
 +              ubc->loststatus = status;
 +              for (i = 0; i < BAS_NUMFRAMES; i++) {
 +                      ubc->isoinlost += urb->iso_frame_desc[i].actual_length;
 +                      if (unlikely(urb->iso_frame_desc[i].status != 0 &&
 +                                   urb->iso_frame_desc[i].status != -EINPROGRESS))
 +                              ubc->loststatus = urb->iso_frame_desc[i].status;
 +                      urb->iso_frame_desc[i].status = 0;
 +                      urb->iso_frame_desc[i].actual_length = 0;
 +              }
 +              if (likely(ubc->running)) {
 +                      /* urb->dev is clobbered by USB subsystem */
 +                      urb->dev = bcs->cs->hw.bas->udev;
 +                      urb->transfer_flags = URB_ISO_ASAP;
 +                      urb->number_of_packets = BAS_NUMFRAMES;
 +                      rc = usb_submit_urb(urb, GFP_ATOMIC);
 +                      if (unlikely(rc != 0 && rc != -ENODEV)) {
 +                              dev_err(bcs->cs->dev,
 +                                      "could not resubmit isoc read URB: %s\n",
 +                                      get_usb_rcmsg(rc));
 +                              dump_urb(DEBUG_ISO, "isoc read", urb);
 +                              error_hangup(bcs);
 +                      }
 +              }
 +      }
 +      spin_unlock_irqrestore(&ubc->isoinlock, flags);
 +}
 +
 +/* write_iso_callback
 + * USB completion handler for B channel isochronous output
 + * called by the USB subsystem in interrupt context
 + * parameter:
 + *    urb     USB request block of completed request
 + *            urb->context = isow_urbctx_t structure
 + */
 +static void write_iso_callback(struct urb *urb)
 +{
 +      struct isow_urbctx_t *ucx;
 +      struct bas_bc_state *ubc;
 +      int status = urb->status;
 +      unsigned long flags;
 +
 +      /* status codes not worth bothering the tasklet with */
 +      if (unlikely(status == -ENOENT ||
 +                   status == -ECONNRESET ||
 +                   status == -EINPROGRESS ||
 +                   status == -ENODEV ||
 +                   status == -ESHUTDOWN)) {
 +              gig_dbg(DEBUG_ISO, "%s: %s",
 +                      __func__, get_usb_statmsg(status));
 +              return;
 +      }
 +
 +      /* pass URB context to tasklet */
 +      ucx = urb->context;
 +      ubc = ucx->bcs->hw.bas;
 +      ucx->status = status;
 +
 +      spin_lock_irqsave(&ubc->isooutlock, flags);
 +      ubc->isooutovfl = ubc->isooutdone;
 +      ubc->isooutdone = ucx;
 +      spin_unlock_irqrestore(&ubc->isooutlock, flags);
 +      tasklet_hi_schedule(&ubc->sent_tasklet);
 +}
 +
 +/* starturbs
 + * prepare and submit USB request blocks for isochronous input and output
 + * argument:
 + *    B channel control structure
 + * return value:
 + *    0 on success
 + *    < 0 on error (no URBs submitted)
 + */
 +static int starturbs(struct bc_state *bcs)
 +{
 +      struct usb_device *udev = bcs->cs->hw.bas->udev;
 +      struct bas_bc_state *ubc = bcs->hw.bas;
 +      struct urb *urb;
 +      int j, k;
 +      int rc;
 +
 +      /* initialize L2 reception */
 +      if (bcs->proto2 == L2_HDLC)
 +              bcs->inputstate |= INS_flag_hunt;
 +
 +      /* submit all isochronous input URBs */
 +      ubc->running = 1;
 +      for (k = 0; k < BAS_INURBS; k++) {
 +              urb = ubc->isoinurbs[k];
 +              if (!urb) {
 +                      rc = -EFAULT;
 +                      goto error;
 +              }
 +              usb_fill_int_urb(urb, udev,
 +                               usb_rcvisocpipe(udev, 3 + 2 * bcs->channel),
 +                               ubc->isoinbuf + k * BAS_INBUFSIZE,
 +                               BAS_INBUFSIZE, read_iso_callback, bcs,
 +                               BAS_FRAMETIME);
 +
 +              urb->transfer_flags = URB_ISO_ASAP;
 +              urb->number_of_packets = BAS_NUMFRAMES;
 +              for (j = 0; j < BAS_NUMFRAMES; j++) {
 +                      urb->iso_frame_desc[j].offset = j * BAS_MAXFRAME;
 +                      urb->iso_frame_desc[j].length = BAS_MAXFRAME;
 +                      urb->iso_frame_desc[j].status = 0;
 +                      urb->iso_frame_desc[j].actual_length = 0;
 +              }
 +
 +              dump_urb(DEBUG_ISO, "Initial isoc read", urb);
 +              rc = usb_submit_urb(urb, GFP_ATOMIC);
 +              if (rc != 0)
 +                      goto error;
 +      }
 +
 +      /* initialize L2 transmission */
 +      gigaset_isowbuf_init(ubc->isooutbuf, PPP_FLAG);
 +
 +      /* set up isochronous output URBs for flag idling */
 +      for (k = 0; k < BAS_OUTURBS; ++k) {
 +              urb = ubc->isoouturbs[k].urb;
 +              if (!urb) {
 +                      rc = -EFAULT;
 +                      goto error;
 +              }
 +              usb_fill_int_urb(urb, udev,
 +                               usb_sndisocpipe(udev, 4 + 2 * bcs->channel),
 +                               ubc->isooutbuf->data,
 +                               sizeof(ubc->isooutbuf->data),
 +                               write_iso_callback, &ubc->isoouturbs[k],
 +                               BAS_FRAMETIME);
 +
 +              urb->transfer_flags = URB_ISO_ASAP;
 +              urb->number_of_packets = BAS_NUMFRAMES;
 +              for (j = 0; j < BAS_NUMFRAMES; ++j) {
 +                      urb->iso_frame_desc[j].offset = BAS_OUTBUFSIZE;
 +                      urb->iso_frame_desc[j].length = BAS_NORMFRAME;
 +                      urb->iso_frame_desc[j].status = 0;
 +                      urb->iso_frame_desc[j].actual_length = 0;
 +              }
 +              ubc->isoouturbs[k].limit = -1;
 +      }
 +
 +      /* keep one URB free, submit the others */
 +      for (k = 0; k < BAS_OUTURBS - 1; ++k) {
 +              dump_urb(DEBUG_ISO, "Initial isoc write", urb);
 +              rc = usb_submit_urb(ubc->isoouturbs[k].urb, GFP_ATOMIC);
 +              if (rc != 0)
 +                      goto error;
 +      }
 +      dump_urb(DEBUG_ISO, "Initial isoc write (free)", urb);
 +      ubc->isooutfree = &ubc->isoouturbs[BAS_OUTURBS - 1];
 +      ubc->isooutdone = ubc->isooutovfl = NULL;
 +      return 0;
 +error:
 +      stopurbs(ubc);
 +      return rc;
 +}
 +
 +/* stopurbs
 + * cancel the USB request blocks for isochronous input and output
 + * errors are silently ignored
 + * argument:
 + *    B channel control structure
 + */
 +static void stopurbs(struct bas_bc_state *ubc)
 +{
 +      int k, rc;
 +
 +      ubc->running = 0;
 +
 +      for (k = 0; k < BAS_INURBS; ++k) {
 +              rc = usb_unlink_urb(ubc->isoinurbs[k]);
 +              gig_dbg(DEBUG_ISO,
 +                      "%s: isoc input URB %d unlinked, result = %s",
 +                      __func__, k, get_usb_rcmsg(rc));
 +      }
 +
 +      for (k = 0; k < BAS_OUTURBS; ++k) {
 +              rc = usb_unlink_urb(ubc->isoouturbs[k].urb);
 +              gig_dbg(DEBUG_ISO,
 +                      "%s: isoc output URB %d unlinked, result = %s",
 +                      __func__, k, get_usb_rcmsg(rc));
 +      }
 +}
 +
 +/* Isochronous Write - Bottom Half */
 +/* =============================== */
 +
 +/* submit_iso_write_urb
 + * fill and submit the next isochronous write URB
 + * parameters:
 + *    ucx     context structure containing URB
 + * return value:
 + *    number of frames submitted in URB
 + *    0 if URB not submitted because no data available (isooutbuf busy)
 + *    error code < 0 on error
 + */
 +static int submit_iso_write_urb(struct isow_urbctx_t *ucx)
 +{
 +      struct urb *urb = ucx->urb;
 +      struct bas_bc_state *ubc = ucx->bcs->hw.bas;
 +      struct usb_iso_packet_descriptor *ifd;
 +      int corrbytes, nframe, rc;
 +
 +      /* urb->dev is clobbered by USB subsystem */
 +      urb->dev = ucx->bcs->cs->hw.bas->udev;
 +      urb->transfer_flags = URB_ISO_ASAP;
 +      urb->transfer_buffer = ubc->isooutbuf->data;
 +      urb->transfer_buffer_length = sizeof(ubc->isooutbuf->data);
 +
 +      for (nframe = 0; nframe < BAS_NUMFRAMES; nframe++) {
 +              ifd = &urb->iso_frame_desc[nframe];
 +
 +              /* compute frame length according to flow control */
 +              ifd->length = BAS_NORMFRAME;
 +              corrbytes = atomic_read(&ubc->corrbytes);
 +              if (corrbytes != 0) {
 +                      gig_dbg(DEBUG_ISO, "%s: corrbytes=%d",
 +                              __func__, corrbytes);
 +                      if (corrbytes > BAS_HIGHFRAME - BAS_NORMFRAME)
 +                              corrbytes = BAS_HIGHFRAME - BAS_NORMFRAME;
 +                      else if (corrbytes < BAS_LOWFRAME - BAS_NORMFRAME)
 +                              corrbytes = BAS_LOWFRAME - BAS_NORMFRAME;
 +                      ifd->length += corrbytes;
 +                      atomic_add(-corrbytes, &ubc->corrbytes);
 +              }
 +
 +              /* retrieve block of data to send */
 +              rc = gigaset_isowbuf_getbytes(ubc->isooutbuf, ifd->length);
 +              if (rc < 0) {
 +                      if (rc == -EBUSY) {
 +                              gig_dbg(DEBUG_ISO,
 +                                      "%s: buffer busy at frame %d",
 +                                      __func__, nframe);
 +                              /* tasklet will be restarted from
 +                                 gigaset_isoc_send_skb() */
 +                      } else {
 +                              dev_err(ucx->bcs->cs->dev,
 +                                      "%s: buffer error %d at frame %d\n",
 +                                      __func__, rc, nframe);
 +                              return rc;
 +                      }
 +                      break;
 +              }
 +              ifd->offset = rc;
 +              ucx->limit = ubc->isooutbuf->nextread;
 +              ifd->status = 0;
 +              ifd->actual_length = 0;
 +      }
 +      if (unlikely(nframe == 0))
 +              return 0;       /* no data to send */
 +      urb->number_of_packets = nframe;
 +
 +      rc = usb_submit_urb(urb, GFP_ATOMIC);
 +      if (unlikely(rc)) {
 +              if (rc == -ENODEV)
 +                      /* device removed - give up silently */
 +                      gig_dbg(DEBUG_ISO, "%s: disconnected", __func__);
 +              else
 +                      dev_err(ucx->bcs->cs->dev,
 +                              "could not submit isoc write URB: %s\n",
 +                              get_usb_rcmsg(rc));
 +              return rc;
 +      }
 +      ++ubc->numsub;
 +      return nframe;
 +}
 +
 +/* write_iso_tasklet
 + * tasklet scheduled when an isochronous output URB from the Gigaset device
 + * has completed
 + * parameter:
 + *    data    B channel state structure
 + */
 +static void write_iso_tasklet(unsigned long data)
 +{
 +      struct bc_state *bcs = (struct bc_state *) data;
 +      struct bas_bc_state *ubc = bcs->hw.bas;
 +      struct cardstate *cs = bcs->cs;
 +      struct isow_urbctx_t *done, *next, *ovfl;
 +      struct urb *urb;
 +      int status;
 +      struct usb_iso_packet_descriptor *ifd;
 +      unsigned long flags;
 +      int i;
 +      struct sk_buff *skb;
 +      int len;
 +      int rc;
 +
 +      /* loop while completed URBs arrive in time */
 +      for (;;) {
 +              if (unlikely(!(ubc->running))) {
 +                      gig_dbg(DEBUG_ISO, "%s: not running", __func__);
 +                      return;
 +              }
 +
 +              /* retrieve completed URBs */
 +              spin_lock_irqsave(&ubc->isooutlock, flags);
 +              done = ubc->isooutdone;
 +              ubc->isooutdone = NULL;
 +              ovfl = ubc->isooutovfl;
 +              ubc->isooutovfl = NULL;
 +              spin_unlock_irqrestore(&ubc->isooutlock, flags);
 +              if (ovfl) {
 +                      dev_err(cs->dev, "isoc write underrun\n");
 +                      error_hangup(bcs);
 +                      break;
 +              }
 +              if (!done)
 +                      break;
 +
 +              /* submit free URB if available */
 +              spin_lock_irqsave(&ubc->isooutlock, flags);
 +              next = ubc->isooutfree;
 +              ubc->isooutfree = NULL;
 +              spin_unlock_irqrestore(&ubc->isooutlock, flags);
 +              if (next) {
 +                      rc = submit_iso_write_urb(next);
 +                      if (unlikely(rc <= 0 && rc != -ENODEV)) {
 +                              /* could not submit URB, put it back */
 +                              spin_lock_irqsave(&ubc->isooutlock, flags);
 +                              if (ubc->isooutfree == NULL) {
 +                                      ubc->isooutfree = next;
 +                                      next = NULL;
 +                              }
 +                              spin_unlock_irqrestore(&ubc->isooutlock, flags);
 +                              if (next) {
 +                                      /* couldn't put it back */
 +                                      dev_err(cs->dev,
 +                                              "losing isoc write URB\n");
 +                                      error_hangup(bcs);
 +                              }
 +                      }
 +              }
 +
 +              /* process completed URB */
 +              urb = done->urb;
 +              status = done->status;
 +              switch (status) {
 +              case -EXDEV:                    /* partial completion */
 +                      gig_dbg(DEBUG_ISO, "%s: URB partially completed",
 +                              __func__);
 +                      /* fall through - what's the difference anyway? */
 +              case 0:                         /* normal completion */
 +                      /* inspect individual frames
 +                       * assumptions (for lack of documentation):
 +                       * - actual_length bytes of first frame in error are
 +                       *   successfully sent
 +                       * - all following frames are not sent at all
 +                       */
 +                      for (i = 0; i < BAS_NUMFRAMES; i++) {
 +                              ifd = &urb->iso_frame_desc[i];
 +                              if (ifd->status ||
 +                                  ifd->actual_length != ifd->length) {
 +                                      dev_warn(cs->dev,
 +                                               "isoc write: frame %d[%d/%d]: %s\n",
 +                                               i, ifd->actual_length,
 +                                               ifd->length,
 +                                               get_usb_statmsg(ifd->status));
 +                                      break;
 +                              }
 +                      }
 +                      break;
 +              case -EPIPE:                    /* stall - probably underrun */
 +                      dev_err(cs->dev, "isoc write: stalled\n");
 +                      error_hangup(bcs);
 +                      break;
 +              default:                        /* other errors */
 +                      dev_warn(cs->dev, "isoc write: %s\n",
 +                               get_usb_statmsg(status));
 +              }
 +
 +              /* mark the write buffer area covered by this URB as free */
 +              if (done->limit >= 0)
 +                      ubc->isooutbuf->read = done->limit;
 +
 +              /* mark URB as free */
 +              spin_lock_irqsave(&ubc->isooutlock, flags);
 +              next = ubc->isooutfree;
 +              ubc->isooutfree = done;
 +              spin_unlock_irqrestore(&ubc->isooutlock, flags);
 +              if (next) {
 +                      /* only one URB still active - resubmit one */
 +                      rc = submit_iso_write_urb(next);
 +                      if (unlikely(rc <= 0 && rc != -ENODEV)) {
 +                              /* couldn't submit */
 +                              error_hangup(bcs);
 +                      }
 +              }
 +      }
 +
 +      /* process queued SKBs */
 +      while ((skb = skb_dequeue(&bcs->squeue))) {
 +              /* copy to output buffer, doing L2 encapsulation */
 +              len = skb->len;
 +              if (gigaset_isoc_buildframe(bcs, skb->data, len) == -EAGAIN) {
 +                      /* insufficient buffer space, push back onto queue */
 +                      skb_queue_head(&bcs->squeue, skb);
 +                      gig_dbg(DEBUG_ISO, "%s: skb requeued, qlen=%d",
 +                              __func__, skb_queue_len(&bcs->squeue));
 +                      break;
 +              }
 +              skb_pull(skb, len);
 +              gigaset_skb_sent(bcs, skb);
 +              dev_kfree_skb_any(skb);
 +      }
 +}
 +
 +/* Isochronous Read - Bottom Half */
 +/* ============================== */
 +
 +/* read_iso_tasklet
 + * tasklet scheduled when an isochronous input URB from the Gigaset device
 + * has completed
 + * parameter:
 + *    data    B channel state structure
 + */
 +static void read_iso_tasklet(unsigned long data)
 +{
 +      struct bc_state *bcs = (struct bc_state *) data;
 +      struct bas_bc_state *ubc = bcs->hw.bas;
 +      struct cardstate *cs = bcs->cs;
 +      struct urb *urb;
 +      int status;
 +      struct usb_iso_packet_descriptor *ifd;
 +      char *rcvbuf;
 +      unsigned long flags;
 +      int totleft, numbytes, offset, frame, rc;
 +
 +      /* loop while more completed URBs arrive in the meantime */
 +      for (;;) {
 +              /* retrieve URB */
 +              spin_lock_irqsave(&ubc->isoinlock, flags);
 +              urb = ubc->isoindone;
 +              if (!urb) {
 +                      spin_unlock_irqrestore(&ubc->isoinlock, flags);
 +                      return;
 +              }
 +              status = ubc->isoinstatus;
 +              ubc->isoindone = NULL;
 +              if (unlikely(ubc->loststatus != -EINPROGRESS)) {
 +                      dev_warn(cs->dev,
 +                               "isoc read overrun, URB dropped (status: %s, %d bytes)\n",
 +                               get_usb_statmsg(ubc->loststatus),
 +                               ubc->isoinlost);
 +                      ubc->loststatus = -EINPROGRESS;
 +              }
 +              spin_unlock_irqrestore(&ubc->isoinlock, flags);
 +
 +              if (unlikely(!(ubc->running))) {
 +                      gig_dbg(DEBUG_ISO,
 +                              "%s: channel not running, "
 +                              "dropped URB with status: %s",
 +                              __func__, get_usb_statmsg(status));
 +                      return;
 +              }
 +
 +              switch (status) {
 +              case 0:                         /* normal completion */
 +                      break;
 +              case -EXDEV:                    /* inspect individual frames
 +                                                 (we do that anyway) */
 +                      gig_dbg(DEBUG_ISO, "%s: URB partially completed",
 +                              __func__);
 +                      break;
 +              case -ENOENT:
 +              case -ECONNRESET:
 +              case -EINPROGRESS:
 +                      gig_dbg(DEBUG_ISO, "%s: %s",
 +                              __func__, get_usb_statmsg(status));
 +                      continue;               /* -> skip */
 +              case -EPIPE:
 +                      dev_err(cs->dev, "isoc read: stalled\n");
 +                      error_hangup(bcs);
 +                      continue;               /* -> skip */
 +              default:                        /* other error */
 +                      dev_warn(cs->dev, "isoc read: %s\n",
 +                               get_usb_statmsg(status));
 +                      goto error;
 +              }
 +
 +              rcvbuf = urb->transfer_buffer;
 +              totleft = urb->actual_length;
 +              for (frame = 0; totleft > 0 && frame < BAS_NUMFRAMES; frame++) {
 +                      ifd = &urb->iso_frame_desc[frame];
 +                      numbytes = ifd->actual_length;
 +                      switch (ifd->status) {
 +                      case 0:                 /* success */
 +                              break;
 +                      case -EPROTO:           /* protocol error or unplug */
 +                      case -EILSEQ:
 +                      case -ETIME:
 +                              /* probably just disconnected, ignore */
 +                              gig_dbg(DEBUG_ISO,
 +                                      "isoc read: frame %d[%d]: %s\n",
 +                                      frame, numbytes,
 +                                      get_usb_statmsg(ifd->status));
 +                              break;
 +                      default:                /* other error */
 +                              /* report, assume transferred bytes are ok */
 +                              dev_warn(cs->dev,
 +                                       "isoc read: frame %d[%d]: %s\n",
 +                                       frame, numbytes,
 +                                       get_usb_statmsg(ifd->status));
 +                      }
 +                      if (unlikely(numbytes > BAS_MAXFRAME))
 +                              dev_warn(cs->dev,
 +                                       "isoc read: frame %d[%d]: %s\n",
 +                                       frame, numbytes,
 +                                       "exceeds max frame size");
 +                      if (unlikely(numbytes > totleft)) {
 +                              dev_warn(cs->dev,
 +                                       "isoc read: frame %d[%d]: %s\n",
 +                                       frame, numbytes,
 +                                       "exceeds total transfer length");
 +                              numbytes = totleft;
 +                      }
 +                      offset = ifd->offset;
 +                      if (unlikely(offset + numbytes > BAS_INBUFSIZE)) {
 +                              dev_warn(cs->dev,
 +                                       "isoc read: frame %d[%d]: %s\n",
 +                                       frame, numbytes,
 +                                       "exceeds end of buffer");
 +                              numbytes = BAS_INBUFSIZE - offset;
 +                      }
 +                      gigaset_isoc_receive(rcvbuf + offset, numbytes, bcs);
 +                      totleft -= numbytes;
 +              }
 +              if (unlikely(totleft > 0))
 +                      dev_warn(cs->dev, "isoc read: %d data bytes missing\n",
 +                               totleft);
 +
 +error:
 +              /* URB processed, resubmit */
 +              for (frame = 0; frame < BAS_NUMFRAMES; frame++) {
 +                      urb->iso_frame_desc[frame].status = 0;
 +                      urb->iso_frame_desc[frame].actual_length = 0;
 +              }
 +              /* urb->dev is clobbered by USB subsystem */
 +              urb->dev = bcs->cs->hw.bas->udev;
 +              urb->transfer_flags = URB_ISO_ASAP;
 +              urb->number_of_packets = BAS_NUMFRAMES;
 +              rc = usb_submit_urb(urb, GFP_ATOMIC);
 +              if (unlikely(rc != 0 && rc != -ENODEV)) {
 +                      dev_err(cs->dev,
 +                              "could not resubmit isoc read URB: %s\n",
 +                              get_usb_rcmsg(rc));
 +                      dump_urb(DEBUG_ISO, "resubmit isoc read", urb);
 +                      error_hangup(bcs);
 +              }
 +      }
 +}
 +
 +/* Channel Operations */
 +/* ================== */
 +
 +/* req_timeout
 + * timeout routine for control output request
 + * argument:
 + *    controller state structure
 + */
 +static void req_timeout(struct timer_list *t)
 +{
 +      struct bas_cardstate *ucs = from_timer(ucs, t, timer_ctrl);
 +      struct cardstate *cs = ucs->cs;
 +      int pending;
 +      unsigned long flags;
 +
 +      check_pending(ucs);
 +
 +      spin_lock_irqsave(&ucs->lock, flags);
 +      pending = ucs->pending;
 +      ucs->pending = 0;
 +      spin_unlock_irqrestore(&ucs->lock, flags);
 +
 +      switch (pending) {
 +      case 0:                                 /* no pending request */
 +              gig_dbg(DEBUG_USBREQ, "%s: no request pending", __func__);
 +              break;
 +
 +      case HD_OPEN_ATCHANNEL:
 +              dev_err(cs->dev, "timeout opening AT channel\n");
 +              error_reset(cs);
 +              break;
 +
 +      case HD_OPEN_B1CHANNEL:
 +              dev_err(cs->dev, "timeout opening channel 1\n");
 +              error_hangup(&cs->bcs[0]);
 +              break;
 +
 +      case HD_OPEN_B2CHANNEL:
 +              dev_err(cs->dev, "timeout opening channel 2\n");
 +              error_hangup(&cs->bcs[1]);
 +              break;
 +
 +      case HD_CLOSE_ATCHANNEL:
 +              dev_err(cs->dev, "timeout closing AT channel\n");
 +              error_reset(cs);
 +              break;
 +
 +      case HD_CLOSE_B1CHANNEL:
 +              dev_err(cs->dev, "timeout closing channel 1\n");
 +              error_reset(cs);
 +              break;
 +
 +      case HD_CLOSE_B2CHANNEL:
 +              dev_err(cs->dev, "timeout closing channel 2\n");
 +              error_reset(cs);
 +              break;
 +
 +      case HD_RESET_INTERRUPT_PIPE:
 +              /* error recovery escalation */
 +              dev_err(cs->dev,
 +                      "reset interrupt pipe timeout, attempting USB reset\n");
 +              usb_queue_reset_device(ucs->interface);
 +              break;
 +
 +      default:
 +              dev_warn(cs->dev, "request 0x%02x timed out, clearing\n",
 +                       pending);
 +      }
 +
 +      wake_up(&ucs->waitqueue);
 +}
 +
 +/* write_ctrl_callback
 + * USB completion handler for control pipe output
 + * called by the USB subsystem in interrupt context
 + * parameter:
 + *    urb     USB request block of completed request
 + *            urb->context = hardware specific controller state structure
 + */
 +static void write_ctrl_callback(struct urb *urb)
 +{
 +      struct bas_cardstate *ucs = urb->context;
 +      int status = urb->status;
 +      int rc;
 +      unsigned long flags;
 +
 +      /* check status */
 +      switch (status) {
 +      case 0:                                 /* normal completion */
 +              spin_lock_irqsave(&ucs->lock, flags);
 +              switch (ucs->pending) {
 +              case HD_DEVICE_INIT_ACK:        /* no reply expected */
 +                      del_timer(&ucs->timer_ctrl);
 +                      ucs->pending = 0;
 +                      break;
 +              }
 +              spin_unlock_irqrestore(&ucs->lock, flags);
 +              return;
 +
 +      case -ENOENT:                   /* cancelled */
 +      case -ECONNRESET:               /* cancelled (async) */
 +      case -EINPROGRESS:              /* pending */
 +      case -ENODEV:                   /* device removed */
 +      case -ESHUTDOWN:                /* device shut down */
 +              /* ignore silently */
 +              gig_dbg(DEBUG_USBREQ, "%s: %s",
 +                      __func__, get_usb_statmsg(status));
 +              break;
 +
 +      default:                                /* any failure */
 +              /* don't retry if suspend requested */
 +              if (++ucs->retry_ctrl > BAS_RETRY ||
 +                  (ucs->basstate & BS_SUSPEND)) {
 +                      dev_err(&ucs->interface->dev,
 +                              "control request 0x%02x failed: %s\n",
 +                              ucs->dr_ctrl.bRequest,
 +                              get_usb_statmsg(status));
 +                      break;          /* give up */
 +              }
 +              dev_notice(&ucs->interface->dev,
 +                         "control request 0x%02x: %s, retry %d\n",
 +                         ucs->dr_ctrl.bRequest, get_usb_statmsg(status),
 +                         ucs->retry_ctrl);
 +              /* urb->dev is clobbered by USB subsystem */
 +              urb->dev = ucs->udev;
 +              rc = usb_submit_urb(urb, GFP_ATOMIC);
 +              if (unlikely(rc)) {
 +                      dev_err(&ucs->interface->dev,
 +                              "could not resubmit request 0x%02x: %s\n",
 +                              ucs->dr_ctrl.bRequest, get_usb_rcmsg(rc));
 +                      break;
 +              }
 +              /* resubmitted */
 +              return;
 +      }
 +
 +      /* failed, clear pending request */
 +      spin_lock_irqsave(&ucs->lock, flags);
 +      del_timer(&ucs->timer_ctrl);
 +      ucs->pending = 0;
 +      spin_unlock_irqrestore(&ucs->lock, flags);
 +      wake_up(&ucs->waitqueue);
 +}
 +
 +/* req_submit
 + * submit a control output request without message buffer to the Gigaset base
 + * and optionally start a timeout
 + * parameters:
 + *    bcs     B channel control structure
 + *    req     control request code (HD_*)
 + *    val     control request parameter value (set to 0 if unused)
 + *    timeout timeout in seconds (0: no timeout)
 + * return value:
 + *    0 on success
 + *    -EBUSY if another request is pending
 + *    any URB submission error code
 + */
 +static int req_submit(struct bc_state *bcs, int req, int val, int timeout)
 +{
 +      struct bas_cardstate *ucs = bcs->cs->hw.bas;
 +      int ret;
 +      unsigned long flags;
 +
 +      gig_dbg(DEBUG_USBREQ, "-------> 0x%02x (%d)", req, val);
 +
 +      spin_lock_irqsave(&ucs->lock, flags);
 +      if (ucs->pending) {
 +              spin_unlock_irqrestore(&ucs->lock, flags);
 +              dev_err(bcs->cs->dev,
 +                      "submission of request 0x%02x failed: "
 +                      "request 0x%02x still pending\n",
 +                      req, ucs->pending);
 +              return -EBUSY;
 +      }
 +
 +      ucs->dr_ctrl.bRequestType = OUT_VENDOR_REQ;
 +      ucs->dr_ctrl.bRequest = req;
 +      ucs->dr_ctrl.wValue = cpu_to_le16(val);
 +      ucs->dr_ctrl.wIndex = 0;
 +      ucs->dr_ctrl.wLength = 0;
 +      usb_fill_control_urb(ucs->urb_ctrl, ucs->udev,
 +                           usb_sndctrlpipe(ucs->udev, 0),
 +                           (unsigned char *) &ucs->dr_ctrl, NULL, 0,
 +                           write_ctrl_callback, ucs);
 +      ucs->retry_ctrl = 0;
 +      ret = usb_submit_urb(ucs->urb_ctrl, GFP_ATOMIC);
 +      if (unlikely(ret)) {
 +              dev_err(bcs->cs->dev, "could not submit request 0x%02x: %s\n",
 +                      req, get_usb_rcmsg(ret));
 +              spin_unlock_irqrestore(&ucs->lock, flags);
 +              return ret;
 +      }
 +      ucs->pending = req;
 +
 +      if (timeout > 0) {
 +              gig_dbg(DEBUG_USBREQ, "setting timeout of %d/10 secs", timeout);
 +              mod_timer(&ucs->timer_ctrl, jiffies + timeout * HZ / 10);
 +      }
 +
 +      spin_unlock_irqrestore(&ucs->lock, flags);
 +      return 0;
 +}
 +
 +/* gigaset_init_bchannel
 + * called by common.c to connect a B channel
 + * initialize isochronous I/O and tell the Gigaset base to open the channel
 + * argument:
 + *    B channel control structure
 + * return value:
 + *    0 on success, error code < 0 on error
 + */
 +static int gigaset_init_bchannel(struct bc_state *bcs)
 +{
 +      struct cardstate *cs = bcs->cs;
 +      int req, ret;
 +      unsigned long flags;
 +
 +      spin_lock_irqsave(&cs->lock, flags);
 +      if (unlikely(!cs->connected)) {
 +              gig_dbg(DEBUG_USBREQ, "%s: not connected", __func__);
 +              spin_unlock_irqrestore(&cs->lock, flags);
 +              return -ENODEV;
 +      }
 +
 +      if (cs->hw.bas->basstate & BS_SUSPEND) {
 +              dev_notice(cs->dev,
 +                         "not starting isoc I/O, suspend in progress\n");
 +              spin_unlock_irqrestore(&cs->lock, flags);
 +              return -EHOSTUNREACH;
 +      }
 +
 +      ret = starturbs(bcs);
 +      if (ret < 0) {
 +              spin_unlock_irqrestore(&cs->lock, flags);
 +              dev_err(cs->dev,
 +                      "could not start isoc I/O for channel B%d: %s\n",
 +                      bcs->channel + 1,
 +                      ret == -EFAULT ? "null URB" : get_usb_rcmsg(ret));
 +              if (ret != -ENODEV)
 +                      error_hangup(bcs);
 +              return ret;
 +      }
 +
 +      req = bcs->channel ? HD_OPEN_B2CHANNEL : HD_OPEN_B1CHANNEL;
 +      ret = req_submit(bcs, req, 0, BAS_TIMEOUT);
 +      if (ret < 0) {
 +              dev_err(cs->dev, "could not open channel B%d\n",
 +                      bcs->channel + 1);
 +              stopurbs(bcs->hw.bas);
 +      }
 +
 +      spin_unlock_irqrestore(&cs->lock, flags);
 +      if (ret < 0 && ret != -ENODEV)
 +              error_hangup(bcs);
 +      return ret;
 +}
 +
 +/* gigaset_close_bchannel
 + * called by common.c to disconnect a B channel
 + * tell the Gigaset base to close the channel
 + * stopping isochronous I/O and LL notification will be done when the
 + * acknowledgement for the close arrives
 + * argument:
 + *    B channel control structure
 + * return value:
 + *    0 on success, error code < 0 on error
 + */
 +static int gigaset_close_bchannel(struct bc_state *bcs)
 +{
 +      struct cardstate *cs = bcs->cs;
 +      int req, ret;
 +      unsigned long flags;
 +
 +      spin_lock_irqsave(&cs->lock, flags);
 +      if (unlikely(!cs->connected)) {
 +              spin_unlock_irqrestore(&cs->lock, flags);
 +              gig_dbg(DEBUG_USBREQ, "%s: not connected", __func__);
 +              return -ENODEV;
 +      }
 +
 +      if (!(cs->hw.bas->basstate & (bcs->channel ? BS_B2OPEN : BS_B1OPEN))) {
 +              /* channel not running: just signal common.c */
 +              spin_unlock_irqrestore(&cs->lock, flags);
 +              gigaset_bchannel_down(bcs);
 +              return 0;
 +      }
 +
 +      /* channel running: tell device to close it */
 +      req = bcs->channel ? HD_CLOSE_B2CHANNEL : HD_CLOSE_B1CHANNEL;
 +      ret = req_submit(bcs, req, 0, BAS_TIMEOUT);
 +      if (ret < 0)
 +              dev_err(cs->dev, "closing channel B%d failed\n",
 +                      bcs->channel + 1);
 +
 +      spin_unlock_irqrestore(&cs->lock, flags);
 +      return ret;
 +}
 +
 +/* Device Operations */
 +/* ================= */
 +
 +/* complete_cb
 + * unqueue first command buffer from queue, waking any sleepers
 + * must be called with cs->cmdlock held
 + * parameter:
 + *    cs      controller state structure
 + */
 +static void complete_cb(struct cardstate *cs)
 +{
 +      struct cmdbuf_t *cb = cs->cmdbuf;
 +
 +      /* unqueue completed buffer */
 +      cs->cmdbytes -= cs->curlen;
 +      gig_dbg(DEBUG_OUTPUT, "write_command: sent %u bytes, %u left",
 +              cs->curlen, cs->cmdbytes);
 +      if (cb->next != NULL) {
 +              cs->cmdbuf = cb->next;
 +              cs->cmdbuf->prev = NULL;
 +              cs->curlen = cs->cmdbuf->len;
 +      } else {
 +              cs->cmdbuf = NULL;
 +              cs->lastcmdbuf = NULL;
 +              cs->curlen = 0;
 +      }
 +
 +      if (cb->wake_tasklet)
 +              tasklet_schedule(cb->wake_tasklet);
 +
 +      kfree(cb);
 +}
 +
 +/* write_command_callback
 + * USB completion handler for AT command transmission
 + * called by the USB subsystem in interrupt context
 + * parameter:
 + *    urb     USB request block of completed request
 + *            urb->context = controller state structure
 + */
 +static void write_command_callback(struct urb *urb)
 +{
 +      struct cardstate *cs = urb->context;
 +      struct bas_cardstate *ucs = cs->hw.bas;
 +      int status = urb->status;
 +      unsigned long flags;
 +
 +      update_basstate(ucs, 0, BS_ATWRPEND);
 +      wake_up(&ucs->waitqueue);
 +
 +      /* check status */
 +      switch (status) {
 +      case 0:                                 /* normal completion */
 +              break;
 +      case -ENOENT:                   /* cancelled */
 +      case -ECONNRESET:               /* cancelled (async) */
 +      case -EINPROGRESS:              /* pending */
 +      case -ENODEV:                   /* device removed */
 +      case -ESHUTDOWN:                /* device shut down */
 +              /* ignore silently */
 +              gig_dbg(DEBUG_USBREQ, "%s: %s",
 +                      __func__, get_usb_statmsg(status));
 +              return;
 +      default:                                /* any failure */
 +              if (++ucs->retry_cmd_out > BAS_RETRY) {
 +                      dev_warn(cs->dev,
 +                               "command write: %s, "
 +                               "giving up after %d retries\n",
 +                               get_usb_statmsg(status),
 +                               ucs->retry_cmd_out);
 +                      break;
 +              }
 +              if (ucs->basstate & BS_SUSPEND) {
 +                      dev_warn(cs->dev,
 +                               "command write: %s, "
 +                               "won't retry - suspend requested\n",
 +                               get_usb_statmsg(status));
 +                      break;
 +              }
 +              if (cs->cmdbuf == NULL) {
 +                      dev_warn(cs->dev,
 +                               "command write: %s, "
 +                               "cannot retry - cmdbuf gone\n",
 +                               get_usb_statmsg(status));
 +                      break;
 +              }
 +              dev_notice(cs->dev, "command write: %s, retry %d\n",
 +                         get_usb_statmsg(status), ucs->retry_cmd_out);
 +              if (atwrite_submit(cs, cs->cmdbuf->buf, cs->cmdbuf->len) >= 0)
 +                      /* resubmitted - bypass regular exit block */
 +                      return;
 +              /* command send failed, assume base still waiting */
 +              update_basstate(ucs, BS_ATREADY, 0);
 +      }
 +
 +      spin_lock_irqsave(&cs->cmdlock, flags);
 +      if (cs->cmdbuf != NULL)
 +              complete_cb(cs);
 +      spin_unlock_irqrestore(&cs->cmdlock, flags);
 +}
 +
 +/* atrdy_timeout
 + * timeout routine for AT command transmission
 + * argument:
 + *    controller state structure
 + */
 +static void atrdy_timeout(struct timer_list *t)
 +{
 +      struct bas_cardstate *ucs = from_timer(ucs, t, timer_atrdy);
 +      struct cardstate *cs = ucs->cs;
 +
 +      dev_warn(cs->dev, "timeout waiting for HD_READY_SEND_ATDATA\n");
 +
 +      /* fake the missing signal - what else can I do? */
 +      update_basstate(ucs, BS_ATREADY, BS_ATTIMER);
 +      start_cbsend(cs);
 +}
 +
 +/* atwrite_submit
 + * submit an HD_WRITE_ATMESSAGE command URB
 + * parameters:
 + *    cs      controller state structure
 + *    buf     buffer containing command to send
 + *    len     length of command to send
 + * return value:
 + *    0 on success
 + *    -EBUSY if another request is pending
 + *    any URB submission error code
 + */
 +static int atwrite_submit(struct cardstate *cs, unsigned char *buf, int len)
 +{
 +      struct bas_cardstate *ucs = cs->hw.bas;
 +      int rc;
 +
 +      gig_dbg(DEBUG_USBREQ, "-------> HD_WRITE_ATMESSAGE (%d)", len);
 +
 +      if (update_basstate(ucs, BS_ATWRPEND, 0) & BS_ATWRPEND) {
 +              dev_err(cs->dev,
 +                      "could not submit HD_WRITE_ATMESSAGE: URB busy\n");
 +              return -EBUSY;
 +      }
 +
 +      ucs->dr_cmd_out.bRequestType = OUT_VENDOR_REQ;
 +      ucs->dr_cmd_out.bRequest = HD_WRITE_ATMESSAGE;
 +      ucs->dr_cmd_out.wValue = 0;
 +      ucs->dr_cmd_out.wIndex = 0;
 +      ucs->dr_cmd_out.wLength = cpu_to_le16(len);
 +      usb_fill_control_urb(ucs->urb_cmd_out, ucs->udev,
 +                           usb_sndctrlpipe(ucs->udev, 0),
 +                           (unsigned char *) &ucs->dr_cmd_out, buf, len,
 +                           write_command_callback, cs);
 +      rc = usb_submit_urb(ucs->urb_cmd_out, GFP_ATOMIC);
 +      if (unlikely(rc)) {
 +              update_basstate(ucs, 0, BS_ATWRPEND);
 +              dev_err(cs->dev, "could not submit HD_WRITE_ATMESSAGE: %s\n",
 +                      get_usb_rcmsg(rc));
 +              return rc;
 +      }
 +
 +      /* submitted successfully, start timeout if necessary */
 +      if (!(update_basstate(ucs, BS_ATTIMER, BS_ATREADY) & BS_ATTIMER)) {
 +              gig_dbg(DEBUG_OUTPUT, "setting ATREADY timeout of %d/10 secs",
 +                      ATRDY_TIMEOUT);
 +              mod_timer(&ucs->timer_atrdy, jiffies + ATRDY_TIMEOUT * HZ / 10);
 +      }
 +      return 0;
 +}
 +
 +/* start_cbsend
 + * start transmission of AT command queue if necessary
 + * parameter:
 + *    cs              controller state structure
 + * return value:
 + *    0 on success
 + *    error code < 0 on error
 + */
 +static int start_cbsend(struct cardstate *cs)
 +{
 +      struct cmdbuf_t *cb;
 +      struct bas_cardstate *ucs = cs->hw.bas;
 +      unsigned long flags;
 +      int rc;
 +      int retval = 0;
 +
 +      /* check if suspend requested */
 +      if (ucs->basstate & BS_SUSPEND) {
 +              gig_dbg(DEBUG_OUTPUT, "suspending");
 +              return -EHOSTUNREACH;
 +      }
 +
 +      /* check if AT channel is open */
 +      if (!(ucs->basstate & BS_ATOPEN)) {
 +              gig_dbg(DEBUG_OUTPUT, "AT channel not open");
 +              rc = req_submit(cs->bcs, HD_OPEN_ATCHANNEL, 0, BAS_TIMEOUT);
 +              if (rc < 0) {
 +                      /* flush command queue */
 +                      spin_lock_irqsave(&cs->cmdlock, flags);
 +                      while (cs->cmdbuf != NULL)
 +                              complete_cb(cs);
 +                      spin_unlock_irqrestore(&cs->cmdlock, flags);
 +              }
 +              return rc;
 +      }
 +
 +      /* try to send first command in queue */
 +      spin_lock_irqsave(&cs->cmdlock, flags);
 +
 +      while ((cb = cs->cmdbuf) != NULL && (ucs->basstate & BS_ATREADY)) {
 +              ucs->retry_cmd_out = 0;
 +              rc = atwrite_submit(cs, cb->buf, cb->len);
 +              if (unlikely(rc)) {
 +                      retval = rc;
 +                      complete_cb(cs);
 +              }
 +      }
 +
 +      spin_unlock_irqrestore(&cs->cmdlock, flags);
 +      return retval;
 +}
 +
 +/* gigaset_write_cmd
 + * This function is called by the device independent part of the driver
 + * to transmit an AT command string to the Gigaset device.
 + * It encapsulates the device specific method for transmission over the
 + * direct USB connection to the base.
 + * The command string is added to the queue of commands to send, and
 + * USB transmission is started if necessary.
 + * parameters:
 + *    cs              controller state structure
 + *    cb              command buffer structure
 + * return value:
 + *    number of bytes queued on success
 + *    error code < 0 on error
 + */
 +static int gigaset_write_cmd(struct cardstate *cs, struct cmdbuf_t *cb)
 +{
 +      unsigned long flags;
 +      int rc;
 +
 +      gigaset_dbg_buffer(cs->mstate != MS_LOCKED ?
 +                         DEBUG_TRANSCMD : DEBUG_LOCKCMD,
 +                         "CMD Transmit", cb->len, cb->buf);
 +
 +      /* translate "+++" escape sequence sent as a single separate command
 +       * into "close AT channel" command for error recovery
 +       * The next command will reopen the AT channel automatically.
 +       */
 +      if (cb->len == 3 && !memcmp(cb->buf, "+++", 3)) {
 +              /* If an HD_RECEIVEATDATA_ACK message remains unhandled
 +               * because of an error, the base never sends another one.
 +               * The response channel is thus effectively blocked.
 +               * Closing and reopening the AT channel does *not* clear
 +               * this condition.
 +               * As a stopgap measure, submit a zero-length AT read
 +               * before closing the AT channel. This has the undocumented
 +               * effect of triggering a new HD_RECEIVEATDATA_ACK message
 +               * from the base if necessary.
 +               * The subsequent AT channel close then discards any pending
 +               * messages.
 +               */
 +              spin_lock_irqsave(&cs->lock, flags);
 +              if (!(cs->hw.bas->basstate & BS_ATRDPEND)) {
 +                      kfree(cs->hw.bas->rcvbuf);
 +                      cs->hw.bas->rcvbuf = NULL;
 +                      cs->hw.bas->rcvbuf_size = 0;
 +                      cs->hw.bas->retry_cmd_in = 0;
 +                      atread_submit(cs, 0);
 +              }
 +              spin_unlock_irqrestore(&cs->lock, flags);
 +
 +              rc = req_submit(cs->bcs, HD_CLOSE_ATCHANNEL, 0, BAS_TIMEOUT);
 +              if (cb->wake_tasklet)
 +                      tasklet_schedule(cb->wake_tasklet);
 +              if (!rc)
 +                      rc = cb->len;
 +              kfree(cb);
 +              return rc;
 +      }
 +
 +      spin_lock_irqsave(&cs->cmdlock, flags);
 +      cb->prev = cs->lastcmdbuf;
 +      if (cs->lastcmdbuf)
 +              cs->lastcmdbuf->next = cb;
 +      else {
 +              cs->cmdbuf = cb;
 +              cs->curlen = cb->len;
 +      }
 +      cs->cmdbytes += cb->len;
 +      cs->lastcmdbuf = cb;
 +      spin_unlock_irqrestore(&cs->cmdlock, flags);
 +
 +      spin_lock_irqsave(&cs->lock, flags);
 +      if (unlikely(!cs->connected)) {
 +              spin_unlock_irqrestore(&cs->lock, flags);
 +              gig_dbg(DEBUG_USBREQ, "%s: not connected", __func__);
 +              /* flush command queue */
 +              spin_lock_irqsave(&cs->cmdlock, flags);
 +              while (cs->cmdbuf != NULL)
 +                      complete_cb(cs);
 +              spin_unlock_irqrestore(&cs->cmdlock, flags);
 +              return -ENODEV;
 +      }
 +      rc = start_cbsend(cs);
 +      spin_unlock_irqrestore(&cs->lock, flags);
 +      return rc < 0 ? rc : cb->len;
 +}
 +
 +/* gigaset_write_room
 + * tty_driver.write_room interface routine
 + * return number of characters the driver will accept to be written via
 + * gigaset_write_cmd
 + * parameter:
 + *    controller state structure
 + * return value:
 + *    number of characters
 + */
 +static int gigaset_write_room(struct cardstate *cs)
 +{
 +      return IF_WRITEBUF;
 +}
 +
 +/* gigaset_chars_in_buffer
 + * tty_driver.chars_in_buffer interface routine
 + * return number of characters waiting to be sent
 + * parameter:
 + *    controller state structure
 + * return value:
 + *    number of characters
 + */
 +static int gigaset_chars_in_buffer(struct cardstate *cs)
 +{
 +      return cs->cmdbytes;
 +}
 +
 +/* gigaset_brkchars
 + * implementation of ioctl(GIGASET_BRKCHARS)
 + * parameter:
 + *    controller state structure
 + * return value:
 + *    -EINVAL (unimplemented function)
 + */
 +static int gigaset_brkchars(struct cardstate *cs, const unsigned char buf[6])
 +{
 +      return -EINVAL;
 +}
 +
 +
 +/* Device Initialization/Shutdown */
 +/* ============================== */
 +
 +/* Free hardware dependent part of the B channel structure
 + * parameter:
 + *    bcs     B channel structure
 + */
 +static void gigaset_freebcshw(struct bc_state *bcs)
 +{
 +      struct bas_bc_state *ubc = bcs->hw.bas;
 +      int i;
 +
 +      if (!ubc)
 +              return;
 +
 +      /* kill URBs and tasklets before freeing - better safe than sorry */
 +      ubc->running = 0;
 +      gig_dbg(DEBUG_INIT, "%s: killing isoc URBs", __func__);
 +      for (i = 0; i < BAS_OUTURBS; ++i) {
 +              usb_kill_urb(ubc->isoouturbs[i].urb);
 +              usb_free_urb(ubc->isoouturbs[i].urb);
 +      }
 +      for (i = 0; i < BAS_INURBS; ++i) {
 +              usb_kill_urb(ubc->isoinurbs[i]);
 +              usb_free_urb(ubc->isoinurbs[i]);
 +      }
 +      tasklet_kill(&ubc->sent_tasklet);
 +      tasklet_kill(&ubc->rcvd_tasklet);
 +      kfree(ubc->isooutbuf);
 +      kfree(ubc);
 +      bcs->hw.bas = NULL;
 +}
 +
 +/* Initialize hardware dependent part of the B channel structure
 + * parameter:
 + *    bcs     B channel structure
 + * return value:
 + *    0 on success, error code < 0 on failure
 + */
 +static int gigaset_initbcshw(struct bc_state *bcs)
 +{
 +      int i;
 +      struct bas_bc_state *ubc;
 +
 +      bcs->hw.bas = ubc = kmalloc(sizeof(struct bas_bc_state), GFP_KERNEL);
 +      if (!ubc) {
 +              pr_err("out of memory\n");
 +              return -ENOMEM;
 +      }
 +
 +      ubc->running = 0;
 +      atomic_set(&ubc->corrbytes, 0);
 +      spin_lock_init(&ubc->isooutlock);
 +      for (i = 0; i < BAS_OUTURBS; ++i) {
 +              ubc->isoouturbs[i].urb = NULL;
 +              ubc->isoouturbs[i].bcs = bcs;
 +      }
 +      ubc->isooutdone = ubc->isooutfree = ubc->isooutovfl = NULL;
 +      ubc->numsub = 0;
 +      ubc->isooutbuf = kmalloc(sizeof(struct isowbuf_t), GFP_KERNEL);
 +      if (!ubc->isooutbuf) {
 +              pr_err("out of memory\n");
 +              kfree(ubc);
 +              bcs->hw.bas = NULL;
 +              return -ENOMEM;
 +      }
 +      tasklet_init(&ubc->sent_tasklet,
 +                   write_iso_tasklet, (unsigned long) bcs);
 +
 +      spin_lock_init(&ubc->isoinlock);
 +      for (i = 0; i < BAS_INURBS; ++i)
 +              ubc->isoinurbs[i] = NULL;
 +      ubc->isoindone = NULL;
 +      ubc->loststatus = -EINPROGRESS;
 +      ubc->isoinlost = 0;
 +      ubc->seqlen = 0;
 +      ubc->inbyte = 0;
 +      ubc->inbits = 0;
 +      ubc->goodbytes = 0;
 +      ubc->alignerrs = 0;
 +      ubc->fcserrs = 0;
 +      ubc->frameerrs = 0;
 +      ubc->giants = 0;
 +      ubc->runts = 0;
 +      ubc->aborts = 0;
 +      ubc->shared0s = 0;
 +      ubc->stolen0s = 0;
 +      tasklet_init(&ubc->rcvd_tasklet,
 +                   read_iso_tasklet, (unsigned long) bcs);
 +      return 0;
 +}
 +
 +static void gigaset_reinitbcshw(struct bc_state *bcs)
 +{
 +      struct bas_bc_state *ubc = bcs->hw.bas;
 +
 +      bcs->hw.bas->running = 0;
 +      atomic_set(&bcs->hw.bas->corrbytes, 0);
 +      bcs->hw.bas->numsub = 0;
 +      spin_lock_init(&ubc->isooutlock);
 +      spin_lock_init(&ubc->isoinlock);
 +      ubc->loststatus = -EINPROGRESS;
 +}
 +
 +static void gigaset_freecshw(struct cardstate *cs)
 +{
 +      /* timers, URBs and rcvbuf are disposed of in disconnect */
 +      kfree(cs->hw.bas->int_in_buf);
 +      kfree(cs->hw.bas);
 +      cs->hw.bas = NULL;
 +}
 +
 +/* Initialize hardware dependent part of the cardstate structure
 + * parameter:
 + *    cs      cardstate structure
 + * return value:
 + *    0 on success, error code < 0 on failure
 + */
 +static int gigaset_initcshw(struct cardstate *cs)
 +{
 +      struct bas_cardstate *ucs;
 +
 +      cs->hw.bas = ucs = kzalloc(sizeof(*ucs), GFP_KERNEL);
 +      if (!ucs) {
 +              pr_err("out of memory\n");
 +              return -ENOMEM;
 +      }
 +      ucs->int_in_buf = kmalloc(IP_MSGSIZE, GFP_KERNEL);
 +      if (!ucs->int_in_buf) {
 +              kfree(ucs);
 +              pr_err("out of memory\n");
 +              return -ENOMEM;
 +      }
 +
 +      spin_lock_init(&ucs->lock);
 +      ucs->cs = cs;
 +      timer_setup(&ucs->timer_ctrl, req_timeout, 0);
 +      timer_setup(&ucs->timer_atrdy, atrdy_timeout, 0);
 +      timer_setup(&ucs->timer_cmd_in, cmd_in_timeout, 0);
 +      timer_setup(&ucs->timer_int_in, int_in_resubmit, 0);
 +      init_waitqueue_head(&ucs->waitqueue);
 +      INIT_WORK(&ucs->int_in_wq, int_in_work);
 +
 +      return 0;
 +}
 +
 +/* freeurbs
 + * unlink and deallocate all URBs unconditionally
 + * caller must make sure that no commands are still in progress
 + * parameter:
 + *    cs      controller state structure
 + */
 +static void freeurbs(struct cardstate *cs)
 +{
 +      struct bas_cardstate *ucs = cs->hw.bas;
 +      struct bas_bc_state *ubc;
 +      int i, j;
 +
 +      gig_dbg(DEBUG_INIT, "%s: killing URBs", __func__);
 +      for (j = 0; j < BAS_CHANNELS; ++j) {
 +              ubc = cs->bcs[j].hw.bas;
 +              for (i = 0; i < BAS_OUTURBS; ++i) {
 +                      usb_kill_urb(ubc->isoouturbs[i].urb);
 +                      usb_free_urb(ubc->isoouturbs[i].urb);
 +                      ubc->isoouturbs[i].urb = NULL;
 +              }
 +              for (i = 0; i < BAS_INURBS; ++i) {
 +                      usb_kill_urb(ubc->isoinurbs[i]);
 +                      usb_free_urb(ubc->isoinurbs[i]);
 +                      ubc->isoinurbs[i] = NULL;
 +              }
 +      }
 +      usb_kill_urb(ucs->urb_int_in);
 +      usb_free_urb(ucs->urb_int_in);
 +      ucs->urb_int_in = NULL;
 +      usb_kill_urb(ucs->urb_cmd_out);
 +      usb_free_urb(ucs->urb_cmd_out);
 +      ucs->urb_cmd_out = NULL;
 +      usb_kill_urb(ucs->urb_cmd_in);
 +      usb_free_urb(ucs->urb_cmd_in);
 +      ucs->urb_cmd_in = NULL;
 +      usb_kill_urb(ucs->urb_ctrl);
 +      usb_free_urb(ucs->urb_ctrl);
 +      ucs->urb_ctrl = NULL;
 +}
 +
 +/* gigaset_probe
 + * This function is called when a new USB device is connected.
 + * It checks whether the new device is handled by this driver.
 + */
 +static int gigaset_probe(struct usb_interface *interface,
 +                       const struct usb_device_id *id)
 +{
 +      struct usb_host_interface *hostif;
 +      struct usb_device *udev = interface_to_usbdev(interface);
 +      struct cardstate *cs = NULL;
 +      struct bas_cardstate *ucs = NULL;
 +      struct bas_bc_state *ubc;
 +      struct usb_endpoint_descriptor *endpoint;
 +      int i, j;
 +      int rc;
 +
 +      gig_dbg(DEBUG_INIT,
 +              "%s: Check if device matches .. (Vendor: 0x%x, Product: 0x%x)",
 +              __func__, le16_to_cpu(udev->descriptor.idVendor),
 +              le16_to_cpu(udev->descriptor.idProduct));
 +
 +      /* set required alternate setting */
 +      hostif = interface->cur_altsetting;
 +      if (hostif->desc.bAlternateSetting != 3) {
 +              gig_dbg(DEBUG_INIT,
 +                      "%s: wrong alternate setting %d - trying to switch",
 +                      __func__, hostif->desc.bAlternateSetting);
 +              if (usb_set_interface(udev, hostif->desc.bInterfaceNumber, 3)
 +                  < 0) {
 +                      dev_warn(&udev->dev, "usb_set_interface failed, "
 +                               "device %d interface %d altsetting %d\n",
 +                               udev->devnum, hostif->desc.bInterfaceNumber,
 +                               hostif->desc.bAlternateSetting);
 +                      return -ENODEV;
 +              }
 +              hostif = interface->cur_altsetting;
 +      }
 +
 +      /* Reject application specific interfaces
 +       */
 +      if (hostif->desc.bInterfaceClass != 255) {
 +              dev_warn(&udev->dev, "%s: bInterfaceClass == %d\n",
 +                       __func__, hostif->desc.bInterfaceClass);
 +              return -ENODEV;
 +      }
 +
 +      if (hostif->desc.bNumEndpoints < 1)
 +              return -ENODEV;
 +
 +      dev_info(&udev->dev,
 +               "%s: Device matched (Vendor: 0x%x, Product: 0x%x)\n",
 +               __func__, le16_to_cpu(udev->descriptor.idVendor),
 +               le16_to_cpu(udev->descriptor.idProduct));
 +
 +      /* allocate memory for our device state and initialize it */
 +      cs = gigaset_initcs(driver, BAS_CHANNELS, 0, 0, cidmode,
 +                          GIGASET_MODULENAME);
 +      if (!cs)
 +              return -ENODEV;
 +      ucs = cs->hw.bas;
 +
 +      /* save off device structure ptrs for later use */
 +      usb_get_dev(udev);
 +      ucs->udev = udev;
 +      ucs->interface = interface;
 +      cs->dev = &interface->dev;
 +
 +      /* allocate URBs:
 +       * - one for the interrupt pipe
 +       * - three for the different uses of the default control pipe
 +       * - three for each isochronous pipe
 +       */
 +      if (!(ucs->urb_int_in = usb_alloc_urb(0, GFP_KERNEL)) ||
 +          !(ucs->urb_cmd_in = usb_alloc_urb(0, GFP_KERNEL)) ||
 +          !(ucs->urb_cmd_out = usb_alloc_urb(0, GFP_KERNEL)) ||
 +          !(ucs->urb_ctrl = usb_alloc_urb(0, GFP_KERNEL)))
 +              goto allocerr;
 +
 +      for (j = 0; j < BAS_CHANNELS; ++j) {
 +              ubc = cs->bcs[j].hw.bas;
 +              for (i = 0; i < BAS_OUTURBS; ++i)
 +                      if (!(ubc->isoouturbs[i].urb =
 +                            usb_alloc_urb(BAS_NUMFRAMES, GFP_KERNEL)))
 +                              goto allocerr;
 +              for (i = 0; i < BAS_INURBS; ++i)
 +                      if (!(ubc->isoinurbs[i] =
 +                            usb_alloc_urb(BAS_NUMFRAMES, GFP_KERNEL)))
 +                              goto allocerr;
 +      }
 +
 +      ucs->rcvbuf = NULL;
 +      ucs->rcvbuf_size = 0;
 +
 +      /* Fill the interrupt urb and send it to the core */
 +      endpoint = &hostif->endpoint[0].desc;
 +      usb_fill_int_urb(ucs->urb_int_in, udev,
 +                       usb_rcvintpipe(udev,
 +                                      usb_endpoint_num(endpoint)),
 +                       ucs->int_in_buf, IP_MSGSIZE, read_int_callback, cs,
 +                       endpoint->bInterval);
 +      rc = usb_submit_urb(ucs->urb_int_in, GFP_KERNEL);
 +      if (rc != 0) {
 +              dev_err(cs->dev, "could not submit interrupt URB: %s\n",
 +                      get_usb_rcmsg(rc));
 +              goto error;
 +      }
 +      ucs->retry_int_in = 0;
 +
 +      /* tell the device that the driver is ready */
 +      rc = req_submit(cs->bcs, HD_DEVICE_INIT_ACK, 0, 0);
 +      if (rc != 0)
 +              goto error;
 +
 +      /* tell common part that the device is ready */
 +      if (startmode == SM_LOCKED)
 +              cs->mstate = MS_LOCKED;
 +
 +      /* save address of controller structure */
 +      usb_set_intfdata(interface, cs);
 +
 +      rc = gigaset_start(cs);
 +      if (rc < 0)
 +              goto error;
 +
 +      return 0;
 +
 +allocerr:
 +      dev_err(cs->dev, "could not allocate URBs\n");
 +      rc = -ENOMEM;
 +error:
 +      freeurbs(cs);
 +      usb_set_intfdata(interface, NULL);
 +      usb_put_dev(udev);
 +      gigaset_freecs(cs);
 +      return rc;
 +}
 +
 +/* gigaset_disconnect
 + * This function is called when the Gigaset base is unplugged.
 + */
 +static void gigaset_disconnect(struct usb_interface *interface)
 +{
 +      struct cardstate *cs;
 +      struct bas_cardstate *ucs;
 +      int j;
 +
 +      cs = usb_get_intfdata(interface);
 +
 +      ucs = cs->hw.bas;
 +
 +      dev_info(cs->dev, "disconnecting Gigaset base\n");
 +
 +      /* mark base as not ready, all channels disconnected */
 +      ucs->basstate = 0;
 +
 +      /* tell LL all channels are down */
 +      for (j = 0; j < BAS_CHANNELS; ++j)
 +              gigaset_bchannel_down(cs->bcs + j);
 +
 +      /* stop driver (common part) */
 +      gigaset_stop(cs);
 +
 +      /* stop delayed work and URBs, free ressources */
 +      del_timer_sync(&ucs->timer_ctrl);
 +      del_timer_sync(&ucs->timer_atrdy);
 +      del_timer_sync(&ucs->timer_cmd_in);
 +      del_timer_sync(&ucs->timer_int_in);
 +      cancel_work_sync(&ucs->int_in_wq);
 +      freeurbs(cs);
 +      usb_set_intfdata(interface, NULL);
 +      kfree(ucs->rcvbuf);
 +      ucs->rcvbuf = NULL;
 +      ucs->rcvbuf_size = 0;
 +      usb_put_dev(ucs->udev);
 +      ucs->interface = NULL;
 +      ucs->udev = NULL;
 +      cs->dev = NULL;
 +      gigaset_freecs(cs);
 +}
 +
 +/* gigaset_suspend
 + * This function is called before the USB connection is suspended
 + * or before the USB device is reset.
 + * In the latter case, message == PMSG_ON.
 + */
 +static int gigaset_suspend(struct usb_interface *intf, pm_message_t message)
 +{
 +      struct cardstate *cs = usb_get_intfdata(intf);
 +      struct bas_cardstate *ucs = cs->hw.bas;
 +      int rc;
 +
 +      /* set suspend flag; this stops AT command/response traffic */
 +      if (update_basstate(ucs, BS_SUSPEND, 0) & BS_SUSPEND) {
 +              gig_dbg(DEBUG_SUSPEND, "already suspended");
 +              return 0;
 +      }
 +
 +      /* wait a bit for blocking conditions to go away */
 +      rc = wait_event_timeout(ucs->waitqueue,
 +                              !(ucs->basstate &
 +                                (BS_B1OPEN | BS_B2OPEN | BS_ATRDPEND | BS_ATWRPEND)),
 +                              BAS_TIMEOUT * HZ / 10);
 +      gig_dbg(DEBUG_SUSPEND, "wait_event_timeout() -> %d", rc);
 +
 +      /* check for conditions preventing suspend */
 +      if (ucs->basstate & (BS_B1OPEN | BS_B2OPEN | BS_ATRDPEND | BS_ATWRPEND)) {
 +              dev_warn(cs->dev, "cannot suspend:\n");
 +              if (ucs->basstate & BS_B1OPEN)
 +                      dev_warn(cs->dev, " B channel 1 open\n");
 +              if (ucs->basstate & BS_B2OPEN)
 +                      dev_warn(cs->dev, " B channel 2 open\n");
 +              if (ucs->basstate & BS_ATRDPEND)
 +                      dev_warn(cs->dev, " receiving AT reply\n");
 +              if (ucs->basstate & BS_ATWRPEND)
 +                      dev_warn(cs->dev, " sending AT command\n");
 +              update_basstate(ucs, 0, BS_SUSPEND);
 +              return -EBUSY;
 +      }
 +
 +      /* close AT channel if open */
 +      if (ucs->basstate & BS_ATOPEN) {
 +              gig_dbg(DEBUG_SUSPEND, "closing AT channel");
 +              rc = req_submit(cs->bcs, HD_CLOSE_ATCHANNEL, 0, 0);
 +              if (rc) {
 +                      update_basstate(ucs, 0, BS_SUSPEND);
 +                      return rc;
 +              }
 +              wait_event_timeout(ucs->waitqueue, !ucs->pending,
 +                                 BAS_TIMEOUT * HZ / 10);
 +              /* in case of timeout, proceed anyway */
 +      }
 +
 +      /* kill all URBs and delayed work that might still be pending */
 +      usb_kill_urb(ucs->urb_ctrl);
 +      usb_kill_urb(ucs->urb_int_in);
 +      del_timer_sync(&ucs->timer_ctrl);
 +      del_timer_sync(&ucs->timer_atrdy);
 +      del_timer_sync(&ucs->timer_cmd_in);
 +      del_timer_sync(&ucs->timer_int_in);
 +
 +      /* don't try to cancel int_in_wq from within reset as it
 +       * might be the one requesting the reset
 +       */
 +      if (message.event != PM_EVENT_ON)
 +              cancel_work_sync(&ucs->int_in_wq);
 +
 +      gig_dbg(DEBUG_SUSPEND, "suspend complete");
 +      return 0;
 +}
 +
 +/* gigaset_resume
 + * This function is called after the USB connection has been resumed.
 + */
 +static int gigaset_resume(struct usb_interface *intf)
 +{
 +      struct cardstate *cs = usb_get_intfdata(intf);
 +      struct bas_cardstate *ucs = cs->hw.bas;
 +      int rc;
 +
 +      /* resubmit interrupt URB for spontaneous messages from base */
 +      rc = usb_submit_urb(ucs->urb_int_in, GFP_KERNEL);
 +      if (rc) {
 +              dev_err(cs->dev, "could not resubmit interrupt URB: %s\n",
 +                      get_usb_rcmsg(rc));
 +              return rc;
 +      }
 +      ucs->retry_int_in = 0;
 +
 +      /* clear suspend flag to reallow activity */
 +      update_basstate(ucs, 0, BS_SUSPEND);
 +
 +      gig_dbg(DEBUG_SUSPEND, "resume complete");
 +      return 0;
 +}
 +
 +/* gigaset_pre_reset
 + * This function is called before the USB connection is reset.
 + */
 +static int gigaset_pre_reset(struct usb_interface *intf)
 +{
 +      /* handle just like suspend */
 +      return gigaset_suspend(intf, PMSG_ON);
 +}
 +
 +/* gigaset_post_reset
 + * This function is called after the USB connection has been reset.
 + */
 +static int gigaset_post_reset(struct usb_interface *intf)
 +{
 +      /* FIXME: send HD_DEVICE_INIT_ACK? */
 +
 +      /* resume operations */
 +      return gigaset_resume(intf);
 +}
 +
 +
 +static const struct gigaset_ops gigops = {
 +      .write_cmd = gigaset_write_cmd,
 +      .write_room = gigaset_write_room,
 +      .chars_in_buffer = gigaset_chars_in_buffer,
 +      .brkchars = gigaset_brkchars,
 +      .init_bchannel = gigaset_init_bchannel,
 +      .close_bchannel = gigaset_close_bchannel,
 +      .initbcshw = gigaset_initbcshw,
 +      .freebcshw = gigaset_freebcshw,
 +      .reinitbcshw = gigaset_reinitbcshw,
 +      .initcshw = gigaset_initcshw,
 +      .freecshw = gigaset_freecshw,
 +      .set_modem_ctrl = gigaset_set_modem_ctrl,
 +      .baud_rate = gigaset_baud_rate,
 +      .set_line_ctrl = gigaset_set_line_ctrl,
 +      .send_skb = gigaset_isoc_send_skb,
 +      .handle_input = gigaset_isoc_input,
 +};
 +
 +/* bas_gigaset_init
 + * This function is called after the kernel module is loaded.
 + */
 +static int __init bas_gigaset_init(void)
 +{
 +      int result;
 +
 +      /* allocate memory for our driver state and initialize it */
 +      driver = gigaset_initdriver(GIGASET_MINOR, GIGASET_MINORS,
 +                                  GIGASET_MODULENAME, GIGASET_DEVNAME,
 +                                  &gigops, THIS_MODULE);
 +      if (driver == NULL)
 +              goto error;
 +
 +      /* register this driver with the USB subsystem */
 +      result = usb_register(&gigaset_usb_driver);
 +      if (result < 0) {
 +              pr_err("error %d registering USB driver\n", -result);
 +              goto error;
 +      }
 +
 +      pr_info(DRIVER_DESC "\n");
 +      return 0;
 +
 +error:
 +      if (driver)
 +              gigaset_freedriver(driver);
 +      driver = NULL;
 +      return -1;
 +}
 +
 +/* bas_gigaset_exit
 + * This function is called before the kernel module is unloaded.
 + */
 +static void __exit bas_gigaset_exit(void)
 +{
 +      struct bas_cardstate *ucs;
 +      int i;
 +
 +      gigaset_blockdriver(driver); /* => probe will fail
 +                                    * => no gigaset_start any more
 +                                    */
 +
 +      /* stop all connected devices */
 +      for (i = 0; i < driver->minors; i++) {
 +              if (gigaset_shutdown(driver->cs + i) < 0)
 +                      continue;               /* no device */
 +              /* from now on, no isdn callback should be possible */
 +
 +              /* close all still open channels */
 +              ucs = driver->cs[i].hw.bas;
 +              if (ucs->basstate & BS_B1OPEN) {
 +                      gig_dbg(DEBUG_INIT, "closing B1 channel");
 +                      usb_control_msg(ucs->udev,
 +                                      usb_sndctrlpipe(ucs->udev, 0),
 +                                      HD_CLOSE_B1CHANNEL, OUT_VENDOR_REQ,
 +                                      0, 0, NULL, 0, BAS_TIMEOUT);
 +              }
 +              if (ucs->basstate & BS_B2OPEN) {
 +                      gig_dbg(DEBUG_INIT, "closing B2 channel");
 +                      usb_control_msg(ucs->udev,
 +                                      usb_sndctrlpipe(ucs->udev, 0),
 +                                      HD_CLOSE_B2CHANNEL, OUT_VENDOR_REQ,
 +                                      0, 0, NULL, 0, BAS_TIMEOUT);
 +              }
 +              if (ucs->basstate & BS_ATOPEN) {
 +                      gig_dbg(DEBUG_INIT, "closing AT channel");
 +                      usb_control_msg(ucs->udev,
 +                                      usb_sndctrlpipe(ucs->udev, 0),
 +                                      HD_CLOSE_ATCHANNEL, OUT_VENDOR_REQ,
 +                                      0, 0, NULL, 0, BAS_TIMEOUT);
 +              }
 +              ucs->basstate = 0;
 +      }
 +
 +      /* deregister this driver with the USB subsystem */
 +      usb_deregister(&gigaset_usb_driver);
 +      /* this will call the disconnect-callback */
 +      /* from now on, no disconnect/probe callback should be running */
 +
 +      gigaset_freedriver(driver);
 +      driver = NULL;
 +}
 +
 +
 +module_init(bas_gigaset_init);
 +module_exit(bas_gigaset_exit);
 +
 +MODULE_AUTHOR(DRIVER_AUTHOR);
 +MODULE_DESCRIPTION(DRIVER_DESC);
 +MODULE_LICENSE("GPL");
index 9cb2ab5,0000000..83d7dd4
mode 100644,000000..100644
--- /dev/null
@@@ -1,2520 -1,0 +1,2517 @@@
-  *    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.
++// SPDX-License-Identifier: GPL-2.0-or-later
 +/*
 + * Kernel CAPI interface for the Gigaset driver
 + *
 + * Copyright (c) 2009 by Tilman Schmidt <tilman@imap.cc>.
 + *
 + * =====================================================================
 + * =====================================================================
 + */
 +
 +#include "gigaset.h"
 +#include <linux/proc_fs.h>
 +#include <linux/seq_file.h>
 +#include <linux/ratelimit.h>
 +#include <linux/isdn/capilli.h>
 +#include <linux/isdn/capicmd.h>
 +#include <linux/isdn/capiutil.h>
 +#include <linux/export.h>
 +
 +/* missing from kernelcapi.h */
 +#define CapiNcpiNotSupportedByProtocol        0x0001
 +#define CapiFlagsNotSupportedByProtocol       0x0002
 +#define CapiAlertAlreadySent          0x0003
 +#define CapiFacilitySpecificFunctionNotSupported      0x3011
 +
 +/* missing from capicmd.h */
 +#define CAPI_CONNECT_IND_BASELEN      (CAPI_MSG_BASELEN + 4 + 2 + 8 * 1)
 +#define CAPI_CONNECT_ACTIVE_IND_BASELEN       (CAPI_MSG_BASELEN + 4 + 3 * 1)
 +#define CAPI_CONNECT_B3_IND_BASELEN   (CAPI_MSG_BASELEN + 4 + 1)
 +#define CAPI_CONNECT_B3_ACTIVE_IND_BASELEN    (CAPI_MSG_BASELEN + 4 + 1)
 +#define CAPI_DATA_B3_REQ_LEN64                (CAPI_MSG_BASELEN + 4 + 4 + 2 + 2 + 2 + 8)
 +#define CAPI_DATA_B3_CONF_LEN         (CAPI_MSG_BASELEN + 4 + 2 + 2)
 +#define CAPI_DISCONNECT_IND_LEN               (CAPI_MSG_BASELEN + 4 + 2)
 +#define CAPI_DISCONNECT_B3_IND_BASELEN        (CAPI_MSG_BASELEN + 4 + 2 + 1)
 +#define CAPI_FACILITY_CONF_BASELEN    (CAPI_MSG_BASELEN + 4 + 2 + 2 + 1)
 +/* most _CONF messages contain only Controller/PLCI/NCCI and Info parameters */
 +#define CAPI_STDCONF_LEN              (CAPI_MSG_BASELEN + 4 + 2)
 +
 +#define CAPI_FACILITY_HANDSET 0x0000
 +#define CAPI_FACILITY_DTMF    0x0001
 +#define CAPI_FACILITY_V42BIS  0x0002
 +#define CAPI_FACILITY_SUPPSVC 0x0003
 +#define CAPI_FACILITY_WAKEUP  0x0004
 +#define CAPI_FACILITY_LI      0x0005
 +
 +#define CAPI_SUPPSVC_GETSUPPORTED     0x0000
 +#define CAPI_SUPPSVC_LISTEN           0x0001
 +
 +/* missing from capiutil.h */
 +#define CAPIMSG_PLCI_PART(m)  CAPIMSG_U8(m, 9)
 +#define CAPIMSG_NCCI_PART(m)  CAPIMSG_U16(m, 10)
 +#define CAPIMSG_HANDLE_REQ(m) CAPIMSG_U16(m, 18) /* DATA_B3_REQ/_IND only! */
 +#define CAPIMSG_FLAGS(m)      CAPIMSG_U16(m, 20)
 +#define CAPIMSG_SETCONTROLLER(m, contr)       capimsg_setu8(m, 8, contr)
 +#define CAPIMSG_SETPLCI_PART(m, plci) capimsg_setu8(m, 9, plci)
 +#define CAPIMSG_SETNCCI_PART(m, ncci) capimsg_setu16(m, 10, ncci)
 +#define CAPIMSG_SETFLAGS(m, flags)    capimsg_setu16(m, 20, flags)
 +
 +/* parameters with differing location in DATA_B3_CONF/_RESP: */
 +#define CAPIMSG_SETHANDLE_CONF(m, handle)     capimsg_setu16(m, 12, handle)
 +#define       CAPIMSG_SETINFO_CONF(m, info)           capimsg_setu16(m, 14, info)
 +
 +/* Flags (DATA_B3_REQ/_IND) */
 +#define CAPI_FLAGS_DELIVERY_CONFIRMATION      0x04
 +#define CAPI_FLAGS_RESERVED                   (~0x1f)
 +
 +/* buffer sizes */
 +#define MAX_BC_OCTETS 11
 +#define MAX_HLC_OCTETS 3
 +#define MAX_NUMBER_DIGITS 20
 +#define MAX_FMT_IE_LEN 20
 +
 +/* values for bcs->apconnstate */
 +#define APCONN_NONE   0       /* inactive/listening */
 +#define APCONN_SETUP  1       /* connecting */
 +#define APCONN_ACTIVE 2       /* B channel up */
 +
 +/* registered application data structure */
 +struct gigaset_capi_appl {
 +      struct list_head ctrlist;
 +      struct gigaset_capi_appl *bcnext;
 +      u16 id;
 +      struct capi_register_params rp;
 +      u16 nextMessageNumber;
 +      u32 listenInfoMask;
 +      u32 listenCIPmask;
 +};
 +
 +/* CAPI specific controller data structure */
 +struct gigaset_capi_ctr {
 +      struct capi_ctr ctr;
 +      struct list_head appls;
 +      struct sk_buff_head sendqueue;
 +      atomic_t sendqlen;
 +      /* two _cmsg structures possibly used concurrently: */
 +      _cmsg hcmsg;    /* for message composition triggered from hardware */
 +      _cmsg acmsg;    /* for dissection of messages sent from application */
 +      u8 bc_buf[MAX_BC_OCTETS + 1];
 +      u8 hlc_buf[MAX_HLC_OCTETS + 1];
 +      u8 cgpty_buf[MAX_NUMBER_DIGITS + 3];
 +      u8 cdpty_buf[MAX_NUMBER_DIGITS + 2];
 +};
 +
 +/* CIP Value table (from CAPI 2.0 standard, ch. 6.1) */
 +static struct {
 +      u8 *bc;
 +      u8 *hlc;
 +} cip2bchlc[] = {
 +      [1] = { "8090A3", NULL },       /* Speech (A-law) */
 +      [2] = { "8890", NULL },         /* Unrestricted digital information */
 +      [3] = { "8990", NULL },         /* Restricted digital information */
 +      [4] = { "9090A3", NULL },       /* 3,1 kHz audio (A-law) */
 +      [5] = { "9190", NULL },         /* 7 kHz audio */
 +      [6] = { "9890", NULL },         /* Video */
 +      [7] = { "88C0C6E6", NULL },     /* Packet mode */
 +      [8] = { "8890218F", NULL },     /* 56 kbit/s rate adaptation */
 +      [9] = { "9190A5", NULL },       /* Unrestricted digital information
 +                                       * with tones/announcements */
 +      [16] = { "8090A3", "9181" },    /* Telephony */
 +      [17] = { "9090A3", "9184" },    /* Group 2/3 facsimile */
 +      [18] = { "8890", "91A1" },      /* Group 4 facsimile Class 1 */
 +      [19] = { "8890", "91A4" },      /* Teletex service basic and mixed mode
 +                                       * and Group 4 facsimile service
 +                                       * Classes II and III */
 +      [20] = { "8890", "91A8" },      /* Teletex service basic and
 +                                       * processable mode */
 +      [21] = { "8890", "91B1" },      /* Teletex service basic mode */
 +      [22] = { "8890", "91B2" },      /* International interworking for
 +                                       * Videotex */
 +      [23] = { "8890", "91B5" },      /* Telex */
 +      [24] = { "8890", "91B8" },      /* Message Handling Systems
 +                                       * in accordance with X.400 */
 +      [25] = { "8890", "91C1" },      /* OSI application
 +                                       * in accordance with X.200 */
 +      [26] = { "9190A5", "9181" },    /* 7 kHz telephony */
 +      [27] = { "9190A5", "916001" },  /* Video telephony, first connection */
 +      [28] = { "8890", "916002" },    /* Video telephony, second connection */
 +};
 +
 +/*
 + * helper functions
 + * ================
 + */
 +
 +/*
 + * emit unsupported parameter warning
 + */
 +static inline void ignore_cstruct_param(struct cardstate *cs, _cstruct param,
 +                                      char *msgname, char *paramname)
 +{
 +      if (param && *param)
 +              dev_warn(cs->dev, "%s: ignoring unsupported parameter: %s\n",
 +                       msgname, paramname);
 +}
 +
 +/*
 + * convert an IE from Gigaset hex string to ETSI binary representation
 + * including length byte
 + * return value: result length, -1 on error
 + */
 +static int encode_ie(char *in, u8 *out, int maxlen)
 +{
 +      int l = 0;
 +      while (*in) {
 +              if (!isxdigit(in[0]) || !isxdigit(in[1]) || l >= maxlen)
 +                      return -1;
 +              out[++l] = (hex_to_bin(in[0]) << 4) + hex_to_bin(in[1]);
 +              in += 2;
 +      }
 +      out[0] = l;
 +      return l;
 +}
 +
 +/*
 + * convert an IE from ETSI binary representation including length byte
 + * to Gigaset hex string
 + */
 +static void decode_ie(u8 *in, char *out)
 +{
 +      int i = *in;
 +      while (i-- > 0) {
 +              /* ToDo: conversion to upper case necessary? */
 +              *out++ = toupper(hex_asc_hi(*++in));
 +              *out++ = toupper(hex_asc_lo(*in));
 +      }
 +}
 +
 +/*
 + * retrieve application data structure for an application ID
 + */
 +static inline struct gigaset_capi_appl *
 +get_appl(struct gigaset_capi_ctr *iif, u16 appl)
 +{
 +      struct gigaset_capi_appl *ap;
 +
 +      list_for_each_entry(ap, &iif->appls, ctrlist)
 +              if (ap->id == appl)
 +                      return ap;
 +      return NULL;
 +}
 +
 +/*
 + * dump CAPI message to kernel messages for debugging
 + */
 +static inline void dump_cmsg(enum debuglevel level, const char *tag, _cmsg *p)
 +{
 +#ifdef CONFIG_GIGASET_DEBUG
 +      /* dump at most 20 messages in 20 secs */
 +      static DEFINE_RATELIMIT_STATE(msg_dump_ratelimit, 20 * HZ, 20);
 +      _cdebbuf *cdb;
 +
 +      if (!(gigaset_debuglevel & level))
 +              return;
 +      if (!___ratelimit(&msg_dump_ratelimit, tag))
 +              return;
 +
 +      cdb = capi_cmsg2str(p);
 +      if (cdb) {
 +              gig_dbg(level, "%s: [%d] %s", tag, p->ApplId, cdb->buf);
 +              cdebbuf_free(cdb);
 +      } else {
 +              gig_dbg(level, "%s: [%d] %s", tag, p->ApplId,
 +                      capi_cmd2str(p->Command, p->Subcommand));
 +      }
 +#endif
 +}
 +
 +static inline void dump_rawmsg(enum debuglevel level, const char *tag,
 +                             unsigned char *data)
 +{
 +#ifdef CONFIG_GIGASET_DEBUG
 +      char *dbgline;
 +      int i, l;
 +
 +      if (!(gigaset_debuglevel & level))
 +              return;
 +
 +      l = CAPIMSG_LEN(data);
 +      if (l < 12) {
 +              gig_dbg(level, "%s: ??? LEN=%04d", tag, l);
 +              return;
 +      }
 +      gig_dbg(level, "%s: 0x%02x:0x%02x: ID=%03d #0x%04x LEN=%04d NCCI=0x%x",
 +              tag, CAPIMSG_COMMAND(data), CAPIMSG_SUBCOMMAND(data),
 +              CAPIMSG_APPID(data), CAPIMSG_MSGID(data), l,
 +              CAPIMSG_CONTROL(data));
 +      l -= 12;
 +      if (l <= 0)
 +              return;
 +      if (l > 64)
 +              l = 64; /* arbitrary limit */
 +      dbgline = kmalloc_array(3, l, GFP_ATOMIC);
 +      if (!dbgline)
 +              return;
 +      for (i = 0; i < l; i++) {
 +              dbgline[3 * i] = hex_asc_hi(data[12 + i]);
 +              dbgline[3 * i + 1] = hex_asc_lo(data[12 + i]);
 +              dbgline[3 * i + 2] = ' ';
 +      }
 +      dbgline[3 * l - 1] = '\0';
 +      gig_dbg(level, "  %s", dbgline);
 +      kfree(dbgline);
 +      if (CAPIMSG_COMMAND(data) == CAPI_DATA_B3 &&
 +          (CAPIMSG_SUBCOMMAND(data) == CAPI_REQ ||
 +           CAPIMSG_SUBCOMMAND(data) == CAPI_IND)) {
 +              l = CAPIMSG_DATALEN(data);
 +              gig_dbg(level, "   DataLength=%d", l);
 +              if (l <= 0 || !(gigaset_debuglevel & DEBUG_LLDATA))
 +                      return;
 +              if (l > 64)
 +                      l = 64; /* arbitrary limit */
 +              dbgline = kmalloc_array(3, l, GFP_ATOMIC);
 +              if (!dbgline)
 +                      return;
 +              data += CAPIMSG_LEN(data);
 +              for (i = 0; i < l; i++) {
 +                      dbgline[3 * i] = hex_asc_hi(data[i]);
 +                      dbgline[3 * i + 1] = hex_asc_lo(data[i]);
 +                      dbgline[3 * i + 2] = ' ';
 +              }
 +              dbgline[3 * l - 1] = '\0';
 +              gig_dbg(level, "  %s", dbgline);
 +              kfree(dbgline);
 +      }
 +#endif
 +}
 +
 +/*
 + * format CAPI IE as string
 + */
 +
 +#ifdef CONFIG_GIGASET_DEBUG
 +static const char *format_ie(const char *ie)
 +{
 +      static char result[3 * MAX_FMT_IE_LEN];
 +      int len, count;
 +      char *pout = result;
 +
 +      if (!ie)
 +              return "NULL";
 +
 +      count = len = ie[0];
 +      if (count > MAX_FMT_IE_LEN)
 +              count = MAX_FMT_IE_LEN - 1;
 +      while (count--) {
 +              *pout++ = hex_asc_hi(*++ie);
 +              *pout++ = hex_asc_lo(*ie);
 +              *pout++ = ' ';
 +      }
 +      if (len > MAX_FMT_IE_LEN) {
 +              *pout++ = '.';
 +              *pout++ = '.';
 +              *pout++ = '.';
 +      }
 +      *--pout = 0;
 +      return result;
 +}
 +#endif
 +
 +/*
 + * emit DATA_B3_CONF message
 + */
 +static void send_data_b3_conf(struct cardstate *cs, struct capi_ctr *ctr,
 +                            u16 appl, u16 msgid, int channel,
 +                            u16 handle, u16 info)
 +{
 +      struct sk_buff *cskb;
 +      u8 *msg;
 +
 +      cskb = alloc_skb(CAPI_DATA_B3_CONF_LEN, GFP_ATOMIC);
 +      if (!cskb) {
 +              dev_err(cs->dev, "%s: out of memory\n", __func__);
 +              return;
 +      }
 +      /* frequent message, avoid _cmsg overhead */
 +      msg = __skb_put(cskb, CAPI_DATA_B3_CONF_LEN);
 +      CAPIMSG_SETLEN(msg, CAPI_DATA_B3_CONF_LEN);
 +      CAPIMSG_SETAPPID(msg, appl);
 +      CAPIMSG_SETCOMMAND(msg, CAPI_DATA_B3);
 +      CAPIMSG_SETSUBCOMMAND(msg,  CAPI_CONF);
 +      CAPIMSG_SETMSGID(msg, msgid);
 +      CAPIMSG_SETCONTROLLER(msg, ctr->cnr);
 +      CAPIMSG_SETPLCI_PART(msg, channel);
 +      CAPIMSG_SETNCCI_PART(msg, 1);
 +      CAPIMSG_SETHANDLE_CONF(msg, handle);
 +      CAPIMSG_SETINFO_CONF(msg, info);
 +
 +      /* emit message */
 +      dump_rawmsg(DEBUG_MCMD, __func__, msg);
 +      capi_ctr_handle_message(ctr, appl, cskb);
 +}
 +
 +
 +/*
 + * driver interface functions
 + * ==========================
 + */
 +
 +/**
 + * gigaset_skb_sent() - acknowledge transmission of outgoing skb
 + * @bcs:      B channel descriptor structure.
 + * @skb:      sent data.
 + *
 + * Called by hardware module {bas,ser,usb}_gigaset when the data in a
 + * skb has been successfully sent, for signalling completion to the LL.
 + */
 +void gigaset_skb_sent(struct bc_state *bcs, struct sk_buff *dskb)
 +{
 +      struct cardstate *cs = bcs->cs;
 +      struct gigaset_capi_ctr *iif = cs->iif;
 +      struct gigaset_capi_appl *ap = bcs->ap;
 +      unsigned char *req = skb_mac_header(dskb);
 +      u16 flags;
 +
 +      /* update statistics */
 +      ++bcs->trans_up;
 +
 +      if (!ap) {
 +              gig_dbg(DEBUG_MCMD, "%s: application gone", __func__);
 +              return;
 +      }
 +
 +      /* don't send further B3 messages if disconnected */
 +      if (bcs->apconnstate < APCONN_ACTIVE) {
 +              gig_dbg(DEBUG_MCMD, "%s: disconnected", __func__);
 +              return;
 +      }
 +
 +      /*
 +       * send DATA_B3_CONF if "delivery confirmation" bit was set in request;
 +       * otherwise it has already been sent by do_data_b3_req()
 +       */
 +      flags = CAPIMSG_FLAGS(req);
 +      if (flags & CAPI_FLAGS_DELIVERY_CONFIRMATION)
 +              send_data_b3_conf(cs, &iif->ctr, ap->id, CAPIMSG_MSGID(req),
 +                                bcs->channel + 1, CAPIMSG_HANDLE_REQ(req),
 +                                (flags & ~CAPI_FLAGS_DELIVERY_CONFIRMATION) ?
 +                                CapiFlagsNotSupportedByProtocol :
 +                                CAPI_NOERROR);
 +}
 +EXPORT_SYMBOL_GPL(gigaset_skb_sent);
 +
 +/**
 + * gigaset_skb_rcvd() - pass received skb to LL
 + * @bcs:      B channel descriptor structure.
 + * @skb:      received data.
 + *
 + * Called by hardware module {bas,ser,usb}_gigaset when user data has
 + * been successfully received, for passing to the LL.
 + * Warning: skb must not be accessed anymore!
 + */
 +void gigaset_skb_rcvd(struct bc_state *bcs, struct sk_buff *skb)
 +{
 +      struct cardstate *cs = bcs->cs;
 +      struct gigaset_capi_ctr *iif = cs->iif;
 +      struct gigaset_capi_appl *ap = bcs->ap;
 +      int len = skb->len;
 +
 +      /* update statistics */
 +      bcs->trans_down++;
 +
 +      if (!ap) {
 +              gig_dbg(DEBUG_MCMD, "%s: application gone", __func__);
 +              dev_kfree_skb_any(skb);
 +              return;
 +      }
 +
 +      /* don't send further B3 messages if disconnected */
 +      if (bcs->apconnstate < APCONN_ACTIVE) {
 +              gig_dbg(DEBUG_MCMD, "%s: disconnected", __func__);
 +              dev_kfree_skb_any(skb);
 +              return;
 +      }
 +
 +      /*
 +       * prepend DATA_B3_IND message to payload
 +       * Parameters: NCCI = 1, all others 0/unused
 +       * frequent message, avoid _cmsg overhead
 +       */
 +      skb_push(skb, CAPI_DATA_B3_REQ_LEN);
 +      CAPIMSG_SETLEN(skb->data, CAPI_DATA_B3_REQ_LEN);
 +      CAPIMSG_SETAPPID(skb->data, ap->id);
 +      CAPIMSG_SETCOMMAND(skb->data, CAPI_DATA_B3);
 +      CAPIMSG_SETSUBCOMMAND(skb->data,  CAPI_IND);
 +      CAPIMSG_SETMSGID(skb->data, ap->nextMessageNumber++);
 +      CAPIMSG_SETCONTROLLER(skb->data, iif->ctr.cnr);
 +      CAPIMSG_SETPLCI_PART(skb->data, bcs->channel + 1);
 +      CAPIMSG_SETNCCI_PART(skb->data, 1);
 +      /* Data parameter not used */
 +      CAPIMSG_SETDATALEN(skb->data, len);
 +      /* Data handle parameter not used */
 +      CAPIMSG_SETFLAGS(skb->data, 0);
 +      /* Data64 parameter not present */
 +
 +      /* emit message */
 +      dump_rawmsg(DEBUG_MCMD, __func__, skb->data);
 +      capi_ctr_handle_message(&iif->ctr, ap->id, skb);
 +}
 +EXPORT_SYMBOL_GPL(gigaset_skb_rcvd);
 +
 +/**
 + * gigaset_isdn_rcv_err() - signal receive error
 + * @bcs:      B channel descriptor structure.
 + *
 + * Called by hardware module {bas,ser,usb}_gigaset when a receive error
 + * has occurred, for signalling to the LL.
 + */
 +void gigaset_isdn_rcv_err(struct bc_state *bcs)
 +{
 +      /* if currently ignoring packets, just count down */
 +      if (bcs->ignore) {
 +              bcs->ignore--;
 +              return;
 +      }
 +
 +      /* update statistics */
 +      bcs->corrupted++;
 +
 +      /* ToDo: signal error -> LL */
 +}
 +EXPORT_SYMBOL_GPL(gigaset_isdn_rcv_err);
 +
 +/**
 + * gigaset_isdn_icall() - signal incoming call
 + * @at_state: connection state structure.
 + *
 + * Called by main module at tasklet level to notify the LL that an incoming
 + * call has been received. @at_state contains the parameters of the call.
 + *
 + * Return value: call disposition (ICALL_*)
 + */
 +int gigaset_isdn_icall(struct at_state_t *at_state)
 +{
 +      struct cardstate *cs = at_state->cs;
 +      struct bc_state *bcs = at_state->bcs;
 +      struct gigaset_capi_ctr *iif = cs->iif;
 +      struct gigaset_capi_appl *ap;
 +      u32 actCIPmask;
 +      struct sk_buff *skb;
 +      unsigned int msgsize;
 +      unsigned long flags;
 +      int i;
 +
 +      /*
 +       * ToDo: signal calls without a free B channel, too
 +       * (requires a u8 handle for the at_state structure that can
 +       * be stored in the PLCI and used in the CONNECT_RESP message
 +       * handler to retrieve it)
 +       */
 +      if (!bcs)
 +              return ICALL_IGNORE;
 +
 +      /* prepare CONNECT_IND message, using B channel number as PLCI */
 +      capi_cmsg_header(&iif->hcmsg, 0, CAPI_CONNECT, CAPI_IND, 0,
 +                       iif->ctr.cnr | ((bcs->channel + 1) << 8));
 +
 +      /* minimum size, all structs empty */
 +      msgsize = CAPI_CONNECT_IND_BASELEN;
 +
 +      /* Bearer Capability (mandatory) */
 +      if (at_state->str_var[STR_ZBC]) {
 +              /* pass on BC from Gigaset */
 +              if (encode_ie(at_state->str_var[STR_ZBC], iif->bc_buf,
 +                            MAX_BC_OCTETS) < 0) {
 +                      dev_warn(cs->dev, "RING ignored - bad BC %s\n",
 +                               at_state->str_var[STR_ZBC]);
 +                      return ICALL_IGNORE;
 +              }
 +
 +              /* look up corresponding CIP value */
 +              iif->hcmsg.CIPValue = 0;        /* default if nothing found */
 +              for (i = 0; i < ARRAY_SIZE(cip2bchlc); i++)
 +                      if (cip2bchlc[i].bc != NULL &&
 +                          cip2bchlc[i].hlc == NULL &&
 +                          !strcmp(cip2bchlc[i].bc,
 +                                  at_state->str_var[STR_ZBC])) {
 +                              iif->hcmsg.CIPValue = i;
 +                              break;
 +                      }
 +      } else {
 +              /* no BC (internal call): assume CIP 1 (speech, A-law) */
 +              iif->hcmsg.CIPValue = 1;
 +              encode_ie(cip2bchlc[1].bc, iif->bc_buf, MAX_BC_OCTETS);
 +      }
 +      iif->hcmsg.BC = iif->bc_buf;
 +      msgsize += iif->hcmsg.BC[0];
 +
 +      /* High Layer Compatibility (optional) */
 +      if (at_state->str_var[STR_ZHLC]) {
 +              /* pass on HLC from Gigaset */
 +              if (encode_ie(at_state->str_var[STR_ZHLC], iif->hlc_buf,
 +                            MAX_HLC_OCTETS) < 0) {
 +                      dev_warn(cs->dev, "RING ignored - bad HLC %s\n",
 +                               at_state->str_var[STR_ZHLC]);
 +                      return ICALL_IGNORE;
 +              }
 +              iif->hcmsg.HLC = iif->hlc_buf;
 +              msgsize += iif->hcmsg.HLC[0];
 +
 +              /* look up corresponding CIP value */
 +              /* keep BC based CIP value if none found */
 +              if (at_state->str_var[STR_ZBC])
 +                      for (i = 0; i < ARRAY_SIZE(cip2bchlc); i++)
 +                              if (cip2bchlc[i].hlc != NULL &&
 +                                  !strcmp(cip2bchlc[i].hlc,
 +                                          at_state->str_var[STR_ZHLC]) &&
 +                                  !strcmp(cip2bchlc[i].bc,
 +                                          at_state->str_var[STR_ZBC])) {
 +                                      iif->hcmsg.CIPValue = i;
 +                                      break;
 +                              }
 +      }
 +
 +      /* Called Party Number (optional) */
 +      if (at_state->str_var[STR_ZCPN]) {
 +              i = strlen(at_state->str_var[STR_ZCPN]);
 +              if (i > MAX_NUMBER_DIGITS) {
 +                      dev_warn(cs->dev, "RING ignored - bad number %s\n",
 +                               at_state->str_var[STR_ZBC]);
 +                      return ICALL_IGNORE;
 +              }
 +              iif->cdpty_buf[0] = i + 1;
 +              iif->cdpty_buf[1] = 0x80; /* type / numbering plan unknown */
 +              memcpy(iif->cdpty_buf + 2, at_state->str_var[STR_ZCPN], i);
 +              iif->hcmsg.CalledPartyNumber = iif->cdpty_buf;
 +              msgsize += iif->hcmsg.CalledPartyNumber[0];
 +      }
 +
 +      /* Calling Party Number (optional) */
 +      if (at_state->str_var[STR_NMBR]) {
 +              i = strlen(at_state->str_var[STR_NMBR]);
 +              if (i > MAX_NUMBER_DIGITS) {
 +                      dev_warn(cs->dev, "RING ignored - bad number %s\n",
 +                               at_state->str_var[STR_ZBC]);
 +                      return ICALL_IGNORE;
 +              }
 +              iif->cgpty_buf[0] = i + 2;
 +              iif->cgpty_buf[1] = 0x00; /* type / numbering plan unknown */
 +              iif->cgpty_buf[2] = 0x80; /* pres. allowed, not screened */
 +              memcpy(iif->cgpty_buf + 3, at_state->str_var[STR_NMBR], i);
 +              iif->hcmsg.CallingPartyNumber = iif->cgpty_buf;
 +              msgsize += iif->hcmsg.CallingPartyNumber[0];
 +      }
 +
 +      /* remaining parameters (not supported, always left NULL):
 +       * - CalledPartySubaddress
 +       * - CallingPartySubaddress
 +       * - AdditionalInfo
 +       *   - BChannelinformation
 +       *   - Keypadfacility
 +       *   - Useruserdata
 +       *   - Facilitydataarray
 +       */
 +
 +      gig_dbg(DEBUG_CMD, "icall: PLCI %x CIP %d BC %s",
 +              iif->hcmsg.adr.adrPLCI, iif->hcmsg.CIPValue,
 +              format_ie(iif->hcmsg.BC));
 +      gig_dbg(DEBUG_CMD, "icall: HLC %s",
 +              format_ie(iif->hcmsg.HLC));
 +      gig_dbg(DEBUG_CMD, "icall: CgPty %s",
 +              format_ie(iif->hcmsg.CallingPartyNumber));
 +      gig_dbg(DEBUG_CMD, "icall: CdPty %s",
 +              format_ie(iif->hcmsg.CalledPartyNumber));
 +
 +      /* scan application list for matching listeners */
 +      spin_lock_irqsave(&bcs->aplock, flags);
 +      if (bcs->ap != NULL || bcs->apconnstate != APCONN_NONE) {
 +              dev_warn(cs->dev, "%s: channel not properly cleared (%p/%d)\n",
 +                       __func__, bcs->ap, bcs->apconnstate);
 +              bcs->ap = NULL;
 +              bcs->apconnstate = APCONN_NONE;
 +      }
 +      spin_unlock_irqrestore(&bcs->aplock, flags);
 +      actCIPmask = 1 | (1 << iif->hcmsg.CIPValue);
 +      list_for_each_entry(ap, &iif->appls, ctrlist)
 +              if (actCIPmask & ap->listenCIPmask) {
 +                      /* build CONNECT_IND message for this application */
 +                      iif->hcmsg.ApplId = ap->id;
 +                      iif->hcmsg.Messagenumber = ap->nextMessageNumber++;
 +
 +                      skb = alloc_skb(msgsize, GFP_ATOMIC);
 +                      if (!skb) {
 +                              dev_err(cs->dev, "%s: out of memory\n",
 +                                      __func__);
 +                              break;
 +                      }
 +                      if (capi_cmsg2message(&iif->hcmsg,
 +                                            __skb_put(skb, msgsize))) {
 +                              dev_err(cs->dev, "%s: message parser failure\n",
 +                                      __func__);
 +                              dev_kfree_skb_any(skb);
 +                              break;
 +                      }
 +                      dump_cmsg(DEBUG_CMD, __func__, &iif->hcmsg);
 +
 +                      /* add to listeners on this B channel, update state */
 +                      spin_lock_irqsave(&bcs->aplock, flags);
 +                      ap->bcnext = bcs->ap;
 +                      bcs->ap = ap;
 +                      bcs->chstate |= CHS_NOTIFY_LL;
 +                      bcs->apconnstate = APCONN_SETUP;
 +                      spin_unlock_irqrestore(&bcs->aplock, flags);
 +
 +                      /* emit message */
 +                      capi_ctr_handle_message(&iif->ctr, ap->id, skb);
 +              }
 +
 +      /*
 +       * Return "accept" if any listeners.
 +       * Gigaset will send ALERTING.
 +       * There doesn't seem to be a way to avoid this.
 +       */
 +      return bcs->ap ? ICALL_ACCEPT : ICALL_IGNORE;
 +}
 +
 +/*
 + * send a DISCONNECT_IND message to an application
 + * does not sleep, clobbers the controller's hcmsg structure
 + */
 +static void send_disconnect_ind(struct bc_state *bcs,
 +                              struct gigaset_capi_appl *ap, u16 reason)
 +{
 +      struct cardstate *cs = bcs->cs;
 +      struct gigaset_capi_ctr *iif = cs->iif;
 +      struct sk_buff *skb;
 +
 +      if (bcs->apconnstate == APCONN_NONE)
 +              return;
 +
 +      capi_cmsg_header(&iif->hcmsg, ap->id, CAPI_DISCONNECT, CAPI_IND,
 +                       ap->nextMessageNumber++,
 +                       iif->ctr.cnr | ((bcs->channel + 1) << 8));
 +      iif->hcmsg.Reason = reason;
 +      skb = alloc_skb(CAPI_DISCONNECT_IND_LEN, GFP_ATOMIC);
 +      if (!skb) {
 +              dev_err(cs->dev, "%s: out of memory\n", __func__);
 +              return;
 +      }
 +      if (capi_cmsg2message(&iif->hcmsg,
 +                            __skb_put(skb, CAPI_DISCONNECT_IND_LEN))) {
 +              dev_err(cs->dev, "%s: message parser failure\n", __func__);
 +              dev_kfree_skb_any(skb);
 +              return;
 +      }
 +      dump_cmsg(DEBUG_CMD, __func__, &iif->hcmsg);
 +      capi_ctr_handle_message(&iif->ctr, ap->id, skb);
 +}
 +
 +/*
 + * send a DISCONNECT_B3_IND message to an application
 + * Parameters: NCCI = 1, NCPI empty, Reason_B3 = 0
 + * does not sleep, clobbers the controller's hcmsg structure
 + */
 +static void send_disconnect_b3_ind(struct bc_state *bcs,
 +                                 struct gigaset_capi_appl *ap)
 +{
 +      struct cardstate *cs = bcs->cs;
 +      struct gigaset_capi_ctr *iif = cs->iif;
 +      struct sk_buff *skb;
 +
 +      /* nothing to do if no logical connection active */
 +      if (bcs->apconnstate < APCONN_ACTIVE)
 +              return;
 +      bcs->apconnstate = APCONN_SETUP;
 +
 +      capi_cmsg_header(&iif->hcmsg, ap->id, CAPI_DISCONNECT_B3, CAPI_IND,
 +                       ap->nextMessageNumber++,
 +                       iif->ctr.cnr | ((bcs->channel + 1) << 8) | (1 << 16));
 +      skb = alloc_skb(CAPI_DISCONNECT_B3_IND_BASELEN, GFP_ATOMIC);
 +      if (!skb) {
 +              dev_err(cs->dev, "%s: out of memory\n", __func__);
 +              return;
 +      }
 +      if (capi_cmsg2message(&iif->hcmsg,
 +                        __skb_put(skb, CAPI_DISCONNECT_B3_IND_BASELEN))) {
 +              dev_err(cs->dev, "%s: message parser failure\n", __func__);
 +              dev_kfree_skb_any(skb);
 +              return;
 +      }
 +      dump_cmsg(DEBUG_CMD, __func__, &iif->hcmsg);
 +      capi_ctr_handle_message(&iif->ctr, ap->id, skb);
 +}
 +
 +/**
 + * gigaset_isdn_connD() - signal D channel connect
 + * @bcs:      B channel descriptor structure.
 + *
 + * Called by main module at tasklet level to notify the LL that the D channel
 + * connection has been established.
 + */
 +void gigaset_isdn_connD(struct bc_state *bcs)
 +{
 +      struct cardstate *cs = bcs->cs;
 +      struct gigaset_capi_ctr *iif = cs->iif;
 +      struct gigaset_capi_appl *ap;
 +      struct sk_buff *skb;
 +      unsigned int msgsize;
 +      unsigned long flags;
 +
 +      spin_lock_irqsave(&bcs->aplock, flags);
 +      ap = bcs->ap;
 +      if (!ap) {
 +              spin_unlock_irqrestore(&bcs->aplock, flags);
 +              gig_dbg(DEBUG_CMD, "%s: application gone", __func__);
 +              return;
 +      }
 +      if (bcs->apconnstate == APCONN_NONE) {
 +              spin_unlock_irqrestore(&bcs->aplock, flags);
 +              dev_warn(cs->dev, "%s: application %u not connected\n",
 +                       __func__, ap->id);
 +              return;
 +      }
 +      spin_unlock_irqrestore(&bcs->aplock, flags);
 +      while (ap->bcnext) {
 +              /* this should never happen */
 +              dev_warn(cs->dev, "%s: dropping extra application %u\n",
 +                       __func__, ap->bcnext->id);
 +              send_disconnect_ind(bcs, ap->bcnext,
 +                                  CapiCallGivenToOtherApplication);
 +              ap->bcnext = ap->bcnext->bcnext;
 +      }
 +
 +      /* prepare CONNECT_ACTIVE_IND message
 +       * Note: LLC not supported by device
 +       */
 +      capi_cmsg_header(&iif->hcmsg, ap->id, CAPI_CONNECT_ACTIVE, CAPI_IND,
 +                       ap->nextMessageNumber++,
 +                       iif->ctr.cnr | ((bcs->channel + 1) << 8));
 +
 +      /* minimum size, all structs empty */
 +      msgsize = CAPI_CONNECT_ACTIVE_IND_BASELEN;
 +
 +      /* ToDo: set parameter: Connected number
 +       * (requires ev-layer state machine extension to collect
 +       * ZCON device reply)
 +       */
 +
 +      /* build and emit CONNECT_ACTIVE_IND message */
 +      skb = alloc_skb(msgsize, GFP_ATOMIC);
 +      if (!skb) {
 +              dev_err(cs->dev, "%s: out of memory\n", __func__);
 +              return;
 +      }
 +      if (capi_cmsg2message(&iif->hcmsg, __skb_put(skb, msgsize))) {
 +              dev_err(cs->dev, "%s: message parser failure\n", __func__);
 +              dev_kfree_skb_any(skb);
 +              return;
 +      }
 +      dump_cmsg(DEBUG_CMD, __func__, &iif->hcmsg);
 +      capi_ctr_handle_message(&iif->ctr, ap->id, skb);
 +}
 +
 +/**
 + * gigaset_isdn_hupD() - signal D channel hangup
 + * @bcs:      B channel descriptor structure.
 + *
 + * Called by main module at tasklet level to notify the LL that the D channel
 + * connection has been shut down.
 + */
 +void gigaset_isdn_hupD(struct bc_state *bcs)
 +{
 +      struct gigaset_capi_appl *ap;
 +      unsigned long flags;
 +
 +      /*
 +       * ToDo: pass on reason code reported by device
 +       * (requires ev-layer state machine extension to collect
 +       * ZCAU device reply)
 +       */
 +      spin_lock_irqsave(&bcs->aplock, flags);
 +      while (bcs->ap != NULL) {
 +              ap = bcs->ap;
 +              bcs->ap = ap->bcnext;
 +              spin_unlock_irqrestore(&bcs->aplock, flags);
 +              send_disconnect_b3_ind(bcs, ap);
 +              send_disconnect_ind(bcs, ap, 0);
 +              spin_lock_irqsave(&bcs->aplock, flags);
 +      }
 +      bcs->apconnstate = APCONN_NONE;
 +      spin_unlock_irqrestore(&bcs->aplock, flags);
 +}
 +
 +/**
 + * gigaset_isdn_connB() - signal B channel connect
 + * @bcs:      B channel descriptor structure.
 + *
 + * Called by main module at tasklet level to notify the LL that the B channel
 + * connection has been established.
 + */
 +void gigaset_isdn_connB(struct bc_state *bcs)
 +{
 +      struct cardstate *cs = bcs->cs;
 +      struct gigaset_capi_ctr *iif = cs->iif;
 +      struct gigaset_capi_appl *ap;
 +      struct sk_buff *skb;
 +      unsigned long flags;
 +      unsigned int msgsize;
 +      u8 command;
 +
 +      spin_lock_irqsave(&bcs->aplock, flags);
 +      ap = bcs->ap;
 +      if (!ap) {
 +              spin_unlock_irqrestore(&bcs->aplock, flags);
 +              gig_dbg(DEBUG_CMD, "%s: application gone", __func__);
 +              return;
 +      }
 +      if (!bcs->apconnstate) {
 +              spin_unlock_irqrestore(&bcs->aplock, flags);
 +              dev_warn(cs->dev, "%s: application %u not connected\n",
 +                       __func__, ap->id);
 +              return;
 +      }
 +
 +      /*
 +       * emit CONNECT_B3_ACTIVE_IND if we already got CONNECT_B3_REQ;
 +       * otherwise we have to emit CONNECT_B3_IND first, and follow up with
 +       * CONNECT_B3_ACTIVE_IND in reply to CONNECT_B3_RESP
 +       * Parameters in both cases always: NCCI = 1, NCPI empty
 +       */
 +      if (bcs->apconnstate >= APCONN_ACTIVE) {
 +              command = CAPI_CONNECT_B3_ACTIVE;
 +              msgsize = CAPI_CONNECT_B3_ACTIVE_IND_BASELEN;
 +      } else {
 +              command = CAPI_CONNECT_B3;
 +              msgsize = CAPI_CONNECT_B3_IND_BASELEN;
 +      }
 +      bcs->apconnstate = APCONN_ACTIVE;
 +
 +      spin_unlock_irqrestore(&bcs->aplock, flags);
 +
 +      while (ap->bcnext) {
 +              /* this should never happen */
 +              dev_warn(cs->dev, "%s: dropping extra application %u\n",
 +                       __func__, ap->bcnext->id);
 +              send_disconnect_ind(bcs, ap->bcnext,
 +                                  CapiCallGivenToOtherApplication);
 +              ap->bcnext = ap->bcnext->bcnext;
 +      }
 +
 +      capi_cmsg_header(&iif->hcmsg, ap->id, command, CAPI_IND,
 +                       ap->nextMessageNumber++,
 +                       iif->ctr.cnr | ((bcs->channel + 1) << 8) | (1 << 16));
 +      skb = alloc_skb(msgsize, GFP_ATOMIC);
 +      if (!skb) {
 +              dev_err(cs->dev, "%s: out of memory\n", __func__);
 +              return;
 +      }
 +      if (capi_cmsg2message(&iif->hcmsg, __skb_put(skb, msgsize))) {
 +              dev_err(cs->dev, "%s: message parser failure\n", __func__);
 +              dev_kfree_skb_any(skb);
 +              return;
 +      }
 +      dump_cmsg(DEBUG_CMD, __func__, &iif->hcmsg);
 +      capi_ctr_handle_message(&iif->ctr, ap->id, skb);
 +}
 +
 +/**
 + * gigaset_isdn_hupB() - signal B channel hangup
 + * @bcs:      B channel descriptor structure.
 + *
 + * Called by main module to notify the LL that the B channel connection has
 + * been shut down.
 + */
 +void gigaset_isdn_hupB(struct bc_state *bcs)
 +{
 +      struct gigaset_capi_appl *ap = bcs->ap;
 +
 +      /* ToDo: assure order of DISCONNECT_B3_IND and DISCONNECT_IND ? */
 +
 +      if (!ap) {
 +              gig_dbg(DEBUG_CMD, "%s: application gone", __func__);
 +              return;
 +      }
 +
 +      send_disconnect_b3_ind(bcs, ap);
 +}
 +
 +/**
 + * gigaset_isdn_start() - signal device availability
 + * @cs:               device descriptor structure.
 + *
 + * Called by main module to notify the LL that the device is available for
 + * use.
 + */
 +void gigaset_isdn_start(struct cardstate *cs)
 +{
 +      struct gigaset_capi_ctr *iif = cs->iif;
 +
 +      /* fill profile data: manufacturer name */
 +      strcpy(iif->ctr.manu, "Siemens");
 +      /* CAPI and device version */
 +      iif->ctr.version.majorversion = 2;              /* CAPI 2.0 */
 +      iif->ctr.version.minorversion = 0;
 +      /* ToDo: check/assert cs->gotfwver? */
 +      iif->ctr.version.majormanuversion = cs->fwver[0];
 +      iif->ctr.version.minormanuversion = cs->fwver[1];
 +      /* number of B channels supported */
 +      iif->ctr.profile.nbchannel = cs->channels;
 +      /* global options: internal controller, supplementary services */
 +      iif->ctr.profile.goptions = 0x11;
 +      /* B1 protocols: 64 kbit/s HDLC or transparent */
 +      iif->ctr.profile.support1 =  0x03;
 +      /* B2 protocols: transparent only */
 +      /* ToDo: X.75 SLP ? */
 +      iif->ctr.profile.support2 =  0x02;
 +      /* B3 protocols: transparent only */
 +      iif->ctr.profile.support3 =  0x01;
 +      /* no serial number */
 +      strcpy(iif->ctr.serial, "0");
 +      capi_ctr_ready(&iif->ctr);
 +}
 +
 +/**
 + * gigaset_isdn_stop() - signal device unavailability
 + * @cs:               device descriptor structure.
 + *
 + * Called by main module to notify the LL that the device is no longer
 + * available for use.
 + */
 +void gigaset_isdn_stop(struct cardstate *cs)
 +{
 +      struct gigaset_capi_ctr *iif = cs->iif;
 +      capi_ctr_down(&iif->ctr);
 +}
 +
 +/*
 + * kernel CAPI callback methods
 + * ============================
 + */
 +
 +/*
 + * register CAPI application
 + */
 +static void gigaset_register_appl(struct capi_ctr *ctr, u16 appl,
 +                                capi_register_params *rp)
 +{
 +      struct gigaset_capi_ctr *iif
 +              = container_of(ctr, struct gigaset_capi_ctr, ctr);
 +      struct cardstate *cs = ctr->driverdata;
 +      struct gigaset_capi_appl *ap;
 +
 +      gig_dbg(DEBUG_CMD, "%s [%u] l3cnt=%u blkcnt=%u blklen=%u",
 +              __func__, appl, rp->level3cnt, rp->datablkcnt, rp->datablklen);
 +
 +      list_for_each_entry(ap, &iif->appls, ctrlist)
 +              if (ap->id == appl) {
 +                      dev_notice(cs->dev,
 +                                 "application %u already registered\n", appl);
 +                      return;
 +              }
 +
 +      ap = kzalloc(sizeof(*ap), GFP_KERNEL);
 +      if (!ap) {
 +              dev_err(cs->dev, "%s: out of memory\n", __func__);
 +              return;
 +      }
 +      ap->id = appl;
 +      ap->rp = *rp;
 +
 +      list_add(&ap->ctrlist, &iif->appls);
 +      dev_info(cs->dev, "application %u registered\n", ap->id);
 +}
 +
 +/*
 + * remove CAPI application from channel
 + * helper function to keep indentation levels down and stay in 80 columns
 + */
 +
 +static inline void remove_appl_from_channel(struct bc_state *bcs,
 +                                          struct gigaset_capi_appl *ap)
 +{
 +      struct cardstate *cs = bcs->cs;
 +      struct gigaset_capi_appl *bcap;
 +      unsigned long flags;
 +      int prevconnstate;
 +
 +      spin_lock_irqsave(&bcs->aplock, flags);
 +      bcap = bcs->ap;
 +      if (bcap == NULL) {
 +              spin_unlock_irqrestore(&bcs->aplock, flags);
 +              return;
 +      }
 +
 +      /* check first application on channel */
 +      if (bcap == ap) {
 +              bcs->ap = ap->bcnext;
 +              if (bcs->ap != NULL) {
 +                      spin_unlock_irqrestore(&bcs->aplock, flags);
 +                      return;
 +              }
 +
 +              /* none left, clear channel state */
 +              prevconnstate = bcs->apconnstate;
 +              bcs->apconnstate = APCONN_NONE;
 +              spin_unlock_irqrestore(&bcs->aplock, flags);
 +
 +              if (prevconnstate == APCONN_ACTIVE) {
 +                      dev_notice(cs->dev, "%s: hanging up channel %u\n",
 +                                 __func__, bcs->channel);
 +                      gigaset_add_event(cs, &bcs->at_state,
 +                                        EV_HUP, NULL, 0, NULL);
 +                      gigaset_schedule_event(cs);
 +              }
 +              return;
 +      }
 +
 +      /* check remaining list */
 +      do {
 +              if (bcap->bcnext == ap) {
 +                      bcap->bcnext = bcap->bcnext->bcnext;
 +                      spin_unlock_irqrestore(&bcs->aplock, flags);
 +                      return;
 +              }
 +              bcap = bcap->bcnext;
 +      } while (bcap != NULL);
 +      spin_unlock_irqrestore(&bcs->aplock, flags);
 +}
 +
 +/*
 + * release CAPI application
 + */
 +static void gigaset_release_appl(struct capi_ctr *ctr, u16 appl)
 +{
 +      struct gigaset_capi_ctr *iif
 +              = container_of(ctr, struct gigaset_capi_ctr, ctr);
 +      struct cardstate *cs = iif->ctr.driverdata;
 +      struct gigaset_capi_appl *ap, *tmp;
 +      unsigned ch;
 +
 +      gig_dbg(DEBUG_CMD, "%s [%u]", __func__, appl);
 +
 +      list_for_each_entry_safe(ap, tmp, &iif->appls, ctrlist)
 +              if (ap->id == appl) {
 +                      /* remove from any channels */
 +                      for (ch = 0; ch < cs->channels; ch++)
 +                              remove_appl_from_channel(&cs->bcs[ch], ap);
 +
 +                      /* remove from registration list */
 +                      list_del(&ap->ctrlist);
 +                      kfree(ap);
 +                      dev_info(cs->dev, "application %u released\n", appl);
 +              }
 +}
 +
 +/*
 + * =====================================================================
 + * outgoing CAPI message handler
 + * =====================================================================
 + */
 +
 +/*
 + * helper function: emit reply message with given Info value
 + */
 +static void send_conf(struct gigaset_capi_ctr *iif,
 +                    struct gigaset_capi_appl *ap,
 +                    struct sk_buff *skb,
 +                    u16 info)
 +{
 +      struct cardstate *cs = iif->ctr.driverdata;
 +
 +      /*
 +       * _CONF replies always only have NCCI and Info parameters
 +       * so they'll fit into the _REQ message skb
 +       */
 +      capi_cmsg_answer(&iif->acmsg);
 +      iif->acmsg.Info = info;
 +      if (capi_cmsg2message(&iif->acmsg, skb->data)) {
 +              dev_err(cs->dev, "%s: message parser failure\n", __func__);
 +              dev_kfree_skb_any(skb);
 +              return;
 +      }
 +      __skb_trim(skb, CAPI_STDCONF_LEN);
 +      dump_cmsg(DEBUG_CMD, __func__, &iif->acmsg);
 +      capi_ctr_handle_message(&iif->ctr, ap->id, skb);
 +}
 +
 +/*
 + * process FACILITY_REQ message
 + */
 +static void do_facility_req(struct gigaset_capi_ctr *iif,
 +                          struct gigaset_capi_appl *ap,
 +                          struct sk_buff *skb)
 +{
 +      struct cardstate *cs = iif->ctr.driverdata;
 +      _cmsg *cmsg = &iif->acmsg;
 +      struct sk_buff *cskb;
 +      u8 *pparam;
 +      unsigned int msgsize = CAPI_FACILITY_CONF_BASELEN;
 +      u16 function, info;
 +      static u8 confparam[10];        /* max. 9 octets + length byte */
 +
 +      /* decode message */
 +      if (capi_message2cmsg(cmsg, skb->data)) {
 +              dev_err(cs->dev, "%s: message parser failure\n", __func__);
 +              dev_kfree_skb_any(skb);
 +              return;
 +      }
 +      dump_cmsg(DEBUG_CMD, __func__, cmsg);
 +
 +      /*
 +       * Facility Request Parameter is not decoded by capi_message2cmsg()
 +       * encoding depends on Facility Selector
 +       */
 +      switch (cmsg->FacilitySelector) {
 +      case CAPI_FACILITY_DTMF:        /* ToDo */
 +              info = CapiFacilityNotSupported;
 +              confparam[0] = 2;       /* length */
 +              /* DTMF information: Unknown DTMF request */
 +              capimsg_setu16(confparam, 1, 2);
 +              break;
 +
 +      case CAPI_FACILITY_V42BIS:      /* not supported */
 +              info = CapiFacilityNotSupported;
 +              confparam[0] = 2;       /* length */
 +              /* V.42 bis information: not available */
 +              capimsg_setu16(confparam, 1, 1);
 +              break;
 +
 +      case CAPI_FACILITY_SUPPSVC:
 +              /* decode Function parameter */
 +              pparam = cmsg->FacilityRequestParameter;
 +              if (pparam == NULL || pparam[0] < 2) {
 +                      dev_notice(cs->dev, "%s: %s missing\n", "FACILITY_REQ",
 +                                 "Facility Request Parameter");
 +                      send_conf(iif, ap, skb, CapiIllMessageParmCoding);
 +                      return;
 +              }
 +              function = CAPIMSG_U16(pparam, 1);
 +              switch (function) {
 +              case CAPI_SUPPSVC_GETSUPPORTED:
 +                      info = CapiSuccess;
 +                      /* Supplementary Service specific parameter */
 +                      confparam[3] = 6;       /* length */
 +                      /* Supplementary services info: Success */
 +                      capimsg_setu16(confparam, 4, CapiSuccess);
 +                      /* Supported Services: none */
 +                      capimsg_setu32(confparam, 6, 0);
 +                      break;
 +              case CAPI_SUPPSVC_LISTEN:
 +                      if (pparam[0] < 7 || pparam[3] < 4) {
 +                              dev_notice(cs->dev, "%s: %s missing\n",
 +                                         "FACILITY_REQ", "Notification Mask");
 +                              send_conf(iif, ap, skb,
 +                                        CapiIllMessageParmCoding);
 +                              return;
 +                      }
 +                      if (CAPIMSG_U32(pparam, 4) != 0) {
 +                              dev_notice(cs->dev,
 +                                         "%s: unsupported supplementary service notification mask 0x%x\n",
 +                                         "FACILITY_REQ", CAPIMSG_U32(pparam, 4));
 +                              info = CapiFacilitySpecificFunctionNotSupported;
 +                              confparam[3] = 2;       /* length */
 +                              capimsg_setu16(confparam, 4,
 +                                             CapiSupplementaryServiceNotSupported);
 +                              break;
 +                      }
 +                      info = CapiSuccess;
 +                      confparam[3] = 2;       /* length */
 +                      capimsg_setu16(confparam, 4, CapiSuccess);
 +                      break;
 +
 +              /* ToDo: add supported services */
 +
 +              default:
 +                      dev_notice(cs->dev,
 +                                 "%s: unsupported supplementary service function 0x%04x\n",
 +                                 "FACILITY_REQ", function);
 +                      info = CapiFacilitySpecificFunctionNotSupported;
 +                      /* Supplementary Service specific parameter */
 +                      confparam[3] = 2;       /* length */
 +                      /* Supplementary services info: not supported */
 +                      capimsg_setu16(confparam, 4,
 +                                     CapiSupplementaryServiceNotSupported);
 +              }
 +
 +              /* Facility confirmation parameter */
 +              confparam[0] = confparam[3] + 3;        /* total length */
 +              /* Function: copy from _REQ message */
 +              capimsg_setu16(confparam, 1, function);
 +              /* Supplementary Service specific parameter already set above */
 +              break;
 +
 +      case CAPI_FACILITY_WAKEUP:      /* ToDo */
 +              info = CapiFacilityNotSupported;
 +              confparam[0] = 2;       /* length */
 +              /* Number of accepted awake request parameters: 0 */
 +              capimsg_setu16(confparam, 1, 0);
 +              break;
 +
 +      default:
 +              info = CapiFacilityNotSupported;
 +              confparam[0] = 0;       /* empty struct */
 +      }
 +
 +      /* send FACILITY_CONF with given Info and confirmation parameter */
 +      dev_kfree_skb_any(skb);
 +      capi_cmsg_answer(cmsg);
 +      cmsg->Info = info;
 +      cmsg->FacilityConfirmationParameter = confparam;
 +      msgsize += confparam[0];        /* length */
 +      cskb = alloc_skb(msgsize, GFP_ATOMIC);
 +      if (!cskb) {
 +              dev_err(cs->dev, "%s: out of memory\n", __func__);
 +              return;
 +      }
 +      if (capi_cmsg2message(cmsg, __skb_put(cskb, msgsize))) {
 +              dev_err(cs->dev, "%s: message parser failure\n", __func__);
 +              dev_kfree_skb_any(cskb);
 +              return;
 +      }
 +      dump_cmsg(DEBUG_CMD, __func__, cmsg);
 +      capi_ctr_handle_message(&iif->ctr, ap->id, cskb);
 +}
 +
 +
 +/*
 + * process LISTEN_REQ message
 + * just store the masks in the application data structure
 + */
 +static void do_listen_req(struct gigaset_capi_ctr *iif,
 +                        struct gigaset_capi_appl *ap,
 +                        struct sk_buff *skb)
 +{
 +      struct cardstate *cs = iif->ctr.driverdata;
 +
 +      /* decode message */
 +      if (capi_message2cmsg(&iif->acmsg, skb->data)) {
 +              dev_err(cs->dev, "%s: message parser failure\n", __func__);
 +              dev_kfree_skb_any(skb);
 +              return;
 +      }
 +      dump_cmsg(DEBUG_CMD, __func__, &iif->acmsg);
 +
 +      /* store listening parameters */
 +      ap->listenInfoMask = iif->acmsg.InfoMask;
 +      ap->listenCIPmask = iif->acmsg.CIPmask;
 +      send_conf(iif, ap, skb, CapiSuccess);
 +}
 +
 +/*
 + * process ALERT_REQ message
 + * nothing to do, Gigaset always alerts anyway
 + */
 +static void do_alert_req(struct gigaset_capi_ctr *iif,
 +                       struct gigaset_capi_appl *ap,
 +                       struct sk_buff *skb)
 +{
 +      struct cardstate *cs = iif->ctr.driverdata;
 +
 +      /* decode message */
 +      if (capi_message2cmsg(&iif->acmsg, skb->data)) {
 +              dev_err(cs->dev, "%s: message parser failure\n", __func__);
 +              dev_kfree_skb_any(skb);
 +              return;
 +      }
 +      dump_cmsg(DEBUG_CMD, __func__, &iif->acmsg);
 +      send_conf(iif, ap, skb, CapiAlertAlreadySent);
 +}
 +
 +/*
 + * process CONNECT_REQ message
 + * allocate a B channel, prepare dial commands, queue a DIAL event,
 + * emit CONNECT_CONF reply
 + */
 +static void do_connect_req(struct gigaset_capi_ctr *iif,
 +                         struct gigaset_capi_appl *ap,
 +                         struct sk_buff *skb)
 +{
 +      struct cardstate *cs = iif->ctr.driverdata;
 +      _cmsg *cmsg = &iif->acmsg;
 +      struct bc_state *bcs;
 +      char **commands;
 +      char *s;
 +      u8 *pp;
 +      unsigned long flags;
 +      int i, l, lbc, lhlc;
 +      u16 info;
 +
 +      /* decode message */
 +      if (capi_message2cmsg(cmsg, skb->data)) {
 +              dev_err(cs->dev, "%s: message parser failure\n", __func__);
 +              dev_kfree_skb_any(skb);
 +              return;
 +      }
 +      dump_cmsg(DEBUG_CMD, __func__, cmsg);
 +
 +      /* get free B channel & construct PLCI */
 +      bcs = gigaset_get_free_channel(cs);
 +      if (!bcs) {
 +              dev_notice(cs->dev, "%s: no B channel available\n",
 +                         "CONNECT_REQ");
 +              send_conf(iif, ap, skb, CapiNoPlciAvailable);
 +              return;
 +      }
 +      spin_lock_irqsave(&bcs->aplock, flags);
 +      if (bcs->ap != NULL || bcs->apconnstate != APCONN_NONE)
 +              dev_warn(cs->dev, "%s: channel not properly cleared (%p/%d)\n",
 +                       __func__, bcs->ap, bcs->apconnstate);
 +      ap->bcnext = NULL;
 +      bcs->ap = ap;
 +      bcs->apconnstate = APCONN_SETUP;
 +      spin_unlock_irqrestore(&bcs->aplock, flags);
 +
 +      bcs->rx_bufsize = ap->rp.datablklen;
 +      dev_kfree_skb(bcs->rx_skb);
 +      gigaset_new_rx_skb(bcs);
 +      cmsg->adr.adrPLCI |= (bcs->channel + 1) << 8;
 +
 +      /* build command table */
 +      commands = kcalloc(AT_NUM, sizeof(*commands), GFP_KERNEL);
 +      if (!commands)
 +              goto oom;
 +
 +      /* encode parameter: Called party number */
 +      pp = cmsg->CalledPartyNumber;
 +      if (pp == NULL || *pp == 0) {
 +              dev_notice(cs->dev, "%s: %s missing\n",
 +                         "CONNECT_REQ", "Called party number");
 +              info = CapiIllMessageParmCoding;
 +              goto error;
 +      }
 +      l = *pp++;
 +      /* check type of number/numbering plan byte */
 +      switch (*pp) {
 +      case 0x80:      /* unknown type / unknown numbering plan */
 +      case 0x81:      /* unknown type / ISDN/Telephony numbering plan */
 +              break;
 +      default:        /* others: warn about potential misinterpretation */
 +              dev_notice(cs->dev, "%s: %s type/plan 0x%02x unsupported\n",
 +                         "CONNECT_REQ", "Called party number", *pp);
 +      }
 +      pp++;
 +      l--;
 +      /* translate "**" internal call prefix to CTP value */
 +      if (l >= 2 && pp[0] == '*' && pp[1] == '*') {
 +              s = "^SCTP=0\r";
 +              pp += 2;
 +              l -= 2;
 +      } else {
 +              s = "^SCTP=1\r";
 +      }
 +      commands[AT_TYPE] = kstrdup(s, GFP_KERNEL);
 +      if (!commands[AT_TYPE])
 +              goto oom;
 +      commands[AT_DIAL] = kmalloc(l + 3, GFP_KERNEL);
 +      if (!commands[AT_DIAL])
 +              goto oom;
 +      snprintf(commands[AT_DIAL], l + 3, "D%.*s\r", l, pp);
 +
 +      /* encode parameter: Calling party number */
 +      pp = cmsg->CallingPartyNumber;
 +      if (pp != NULL && *pp > 0) {
 +              l = *pp++;
 +
 +              /* check type of number/numbering plan byte */
 +              /* ToDo: allow for/handle Ext=1? */
 +              switch (*pp) {
 +              case 0x00:      /* unknown type / unknown numbering plan */
 +              case 0x01:      /* unknown type / ISDN/Telephony num. plan */
 +                      break;
 +              default:
 +                      dev_notice(cs->dev,
 +                                 "%s: %s type/plan 0x%02x unsupported\n",
 +                                 "CONNECT_REQ", "Calling party number", *pp);
 +              }
 +              pp++;
 +              l--;
 +
 +              /* check presentation indicator */
 +              if (!l) {
 +                      dev_notice(cs->dev, "%s: %s IE truncated\n",
 +                                 "CONNECT_REQ", "Calling party number");
 +                      info = CapiIllMessageParmCoding;
 +                      goto error;
 +              }
 +              switch (*pp & 0xfc) { /* ignore Screening indicator */
 +              case 0x80:      /* Presentation allowed */
 +                      s = "^SCLIP=1\r";
 +                      break;
 +              case 0xa0:      /* Presentation restricted */
 +                      s = "^SCLIP=0\r";
 +                      break;
 +              default:
 +                      dev_notice(cs->dev, "%s: invalid %s 0x%02x\n",
 +                                 "CONNECT_REQ",
 +                                 "Presentation/Screening indicator",
 +                                 *pp);
 +                      s = "^SCLIP=1\r";
 +              }
 +              commands[AT_CLIP] = kstrdup(s, GFP_KERNEL);
 +              if (!commands[AT_CLIP])
 +                      goto oom;
 +              pp++;
 +              l--;
 +
 +              if (l) {
 +                      /* number */
 +                      commands[AT_MSN] = kmalloc(l + 8, GFP_KERNEL);
 +                      if (!commands[AT_MSN])
 +                              goto oom;
 +                      snprintf(commands[AT_MSN], l + 8, "^SMSN=%*s\r", l, pp);
 +              }
 +      }
 +
 +      /* check parameter: CIP Value */
 +      if (cmsg->CIPValue >= ARRAY_SIZE(cip2bchlc) ||
 +          (cmsg->CIPValue > 0 && cip2bchlc[cmsg->CIPValue].bc == NULL)) {
 +              dev_notice(cs->dev, "%s: unknown CIP value %d\n",
 +                         "CONNECT_REQ", cmsg->CIPValue);
 +              info = CapiCipValueUnknown;
 +              goto error;
 +      }
 +
 +      /*
 +       * check/encode parameters: BC & HLC
 +       * must be encoded together as device doesn't accept HLC separately
 +       * explicit parameters override values derived from CIP
 +       */
 +
 +      /* determine lengths */
 +      if (cmsg->BC && cmsg->BC[0])            /* BC specified explicitly */
 +              lbc = 2 * cmsg->BC[0];
 +      else if (cip2bchlc[cmsg->CIPValue].bc)  /* BC derived from CIP */
 +              lbc = strlen(cip2bchlc[cmsg->CIPValue].bc);
 +      else                                    /* no BC */
 +              lbc = 0;
 +      if (cmsg->HLC && cmsg->HLC[0])          /* HLC specified explicitly */
 +              lhlc = 2 * cmsg->HLC[0];
 +      else if (cip2bchlc[cmsg->CIPValue].hlc) /* HLC derived from CIP */
 +              lhlc = strlen(cip2bchlc[cmsg->CIPValue].hlc);
 +      else                                    /* no HLC */
 +              lhlc = 0;
 +
 +      if (lbc) {
 +              /* have BC: allocate and assemble command string */
 +              l = lbc + 7;            /* "^SBC=" + value + "\r" + null byte */
 +              if (lhlc)
 +                      l += lhlc + 7;  /* ";^SHLC=" + value */
 +              commands[AT_BC] = kmalloc(l, GFP_KERNEL);
 +              if (!commands[AT_BC])
 +                      goto oom;
 +              strcpy(commands[AT_BC], "^SBC=");
 +              if (cmsg->BC && cmsg->BC[0])    /* BC specified explicitly */
 +                      decode_ie(cmsg->BC, commands[AT_BC] + 5);
 +              else                            /* BC derived from CIP */
 +                      strcpy(commands[AT_BC] + 5,
 +                             cip2bchlc[cmsg->CIPValue].bc);
 +              if (lhlc) {
 +                      strcpy(commands[AT_BC] + lbc + 5, ";^SHLC=");
 +                      if (cmsg->HLC && cmsg->HLC[0])
 +                              /* HLC specified explicitly */
 +                              decode_ie(cmsg->HLC,
 +                                        commands[AT_BC] + lbc + 12);
 +                      else    /* HLC derived from CIP */
 +                              strcpy(commands[AT_BC] + lbc + 12,
 +                                     cip2bchlc[cmsg->CIPValue].hlc);
 +              }
 +              strcpy(commands[AT_BC] + l - 2, "\r");
 +      } else {
 +              /* no BC */
 +              if (lhlc) {
 +                      dev_notice(cs->dev, "%s: cannot set HLC without BC\n",
 +                                 "CONNECT_REQ");
 +                      info = CapiIllMessageParmCoding; /* ? */
 +                      goto error;
 +              }
 +      }
 +
 +      /* check/encode parameter: B Protocol */
 +      if (cmsg->BProtocol == CAPI_DEFAULT) {
 +              bcs->proto2 = L2_HDLC;
 +              dev_warn(cs->dev,
 +                       "B2 Protocol X.75 SLP unsupported, using Transparent\n");
 +      } else {
 +              switch (cmsg->B1protocol) {
 +              case 0:
 +                      bcs->proto2 = L2_HDLC;
 +                      break;
 +              case 1:
 +                      bcs->proto2 = L2_VOICE;
 +                      break;
 +              default:
 +                      dev_warn(cs->dev,
 +                               "B1 Protocol %u unsupported, using Transparent\n",
 +                               cmsg->B1protocol);
 +                      bcs->proto2 = L2_VOICE;
 +              }
 +              if (cmsg->B2protocol != 1)
 +                      dev_warn(cs->dev,
 +                               "B2 Protocol %u unsupported, using Transparent\n",
 +                               cmsg->B2protocol);
 +              if (cmsg->B3protocol != 0)
 +                      dev_warn(cs->dev,
 +                               "B3 Protocol %u unsupported, using Transparent\n",
 +                               cmsg->B3protocol);
 +              ignore_cstruct_param(cs, cmsg->B1configuration,
 +                                   "CONNECT_REQ", "B1 Configuration");
 +              ignore_cstruct_param(cs, cmsg->B2configuration,
 +                                   "CONNECT_REQ", "B2 Configuration");
 +              ignore_cstruct_param(cs, cmsg->B3configuration,
 +                                   "CONNECT_REQ", "B3 Configuration");
 +      }
 +      commands[AT_PROTO] = kmalloc(9, GFP_KERNEL);
 +      if (!commands[AT_PROTO])
 +              goto oom;
 +      snprintf(commands[AT_PROTO], 9, "^SBPR=%u\r", bcs->proto2);
 +
 +      /* ToDo: check/encode remaining parameters */
 +      ignore_cstruct_param(cs, cmsg->CalledPartySubaddress,
 +                           "CONNECT_REQ", "Called pty subaddr");
 +      ignore_cstruct_param(cs, cmsg->CallingPartySubaddress,
 +                           "CONNECT_REQ", "Calling pty subaddr");
 +      ignore_cstruct_param(cs, cmsg->LLC,
 +                           "CONNECT_REQ", "LLC");
 +      if (cmsg->AdditionalInfo != CAPI_DEFAULT) {
 +              ignore_cstruct_param(cs, cmsg->BChannelinformation,
 +                                   "CONNECT_REQ", "B Channel Information");
 +              ignore_cstruct_param(cs, cmsg->Keypadfacility,
 +                                   "CONNECT_REQ", "Keypad Facility");
 +              ignore_cstruct_param(cs, cmsg->Useruserdata,
 +                                   "CONNECT_REQ", "User-User Data");
 +              ignore_cstruct_param(cs, cmsg->Facilitydataarray,
 +                                   "CONNECT_REQ", "Facility Data Array");
 +      }
 +
 +      /* encode parameter: B channel to use */
 +      commands[AT_ISO] = kmalloc(9, GFP_KERNEL);
 +      if (!commands[AT_ISO])
 +              goto oom;
 +      snprintf(commands[AT_ISO], 9, "^SISO=%u\r",
 +               (unsigned) bcs->channel + 1);
 +
 +      /* queue & schedule EV_DIAL event */
 +      if (!gigaset_add_event(cs, &bcs->at_state, EV_DIAL, commands,
 +                             bcs->at_state.seq_index, NULL)) {
 +              info = CAPI_MSGOSRESOURCEERR;
 +              goto error;
 +      }
 +      gigaset_schedule_event(cs);
 +      send_conf(iif, ap, skb, CapiSuccess);
 +      return;
 +
 +oom:
 +      dev_err(cs->dev, "%s: out of memory\n", __func__);
 +      info = CAPI_MSGOSRESOURCEERR;
 +error:
 +      if (commands)
 +              for (i = 0; i < AT_NUM; i++)
 +                      kfree(commands[i]);
 +      kfree(commands);
 +      gigaset_free_channel(bcs);
 +      send_conf(iif, ap, skb, info);
 +}
 +
 +/*
 + * process CONNECT_RESP message
 + * checks protocol parameters and queues an ACCEPT or HUP event
 + */
 +static void do_connect_resp(struct gigaset_capi_ctr *iif,
 +                          struct gigaset_capi_appl *ap,
 +                          struct sk_buff *skb)
 +{
 +      struct cardstate *cs = iif->ctr.driverdata;
 +      _cmsg *cmsg = &iif->acmsg;
 +      struct bc_state *bcs;
 +      struct gigaset_capi_appl *oap;
 +      unsigned long flags;
 +      int channel;
 +
 +      /* decode message */
 +      if (capi_message2cmsg(cmsg, skb->data)) {
 +              dev_err(cs->dev, "%s: message parser failure\n", __func__);
 +              dev_kfree_skb_any(skb);
 +              return;
 +      }
 +      dump_cmsg(DEBUG_CMD, __func__, cmsg);
 +      dev_kfree_skb_any(skb);
 +
 +      /* extract and check channel number from PLCI */
 +      channel = (cmsg->adr.adrPLCI >> 8) & 0xff;
 +      if (!channel || channel > cs->channels) {
 +              dev_notice(cs->dev, "%s: invalid %s 0x%02x\n",
 +                         "CONNECT_RESP", "PLCI", cmsg->adr.adrPLCI);
 +              return;
 +      }
 +      bcs = cs->bcs + channel - 1;
 +
 +      switch (cmsg->Reject) {
 +      case 0:         /* Accept */
 +              /* drop all competing applications, keep only this one */
 +              spin_lock_irqsave(&bcs->aplock, flags);
 +              while (bcs->ap != NULL) {
 +                      oap = bcs->ap;
 +                      bcs->ap = oap->bcnext;
 +                      if (oap != ap) {
 +                              spin_unlock_irqrestore(&bcs->aplock, flags);
 +                              send_disconnect_ind(bcs, oap,
 +                                                  CapiCallGivenToOtherApplication);
 +                              spin_lock_irqsave(&bcs->aplock, flags);
 +                      }
 +              }
 +              ap->bcnext = NULL;
 +              bcs->ap = ap;
 +              spin_unlock_irqrestore(&bcs->aplock, flags);
 +
 +              bcs->rx_bufsize = ap->rp.datablklen;
 +              dev_kfree_skb(bcs->rx_skb);
 +              gigaset_new_rx_skb(bcs);
 +              bcs->chstate |= CHS_NOTIFY_LL;
 +
 +              /* check/encode B channel protocol */
 +              if (cmsg->BProtocol == CAPI_DEFAULT) {
 +                      bcs->proto2 = L2_HDLC;
 +                      dev_warn(cs->dev,
 +                               "B2 Protocol X.75 SLP unsupported, using Transparent\n");
 +              } else {
 +                      switch (cmsg->B1protocol) {
 +                      case 0:
 +                              bcs->proto2 = L2_HDLC;
 +                              break;
 +                      case 1:
 +                              bcs->proto2 = L2_VOICE;
 +                              break;
 +                      default:
 +                              dev_warn(cs->dev,
 +                                       "B1 Protocol %u unsupported, using Transparent\n",
 +                                       cmsg->B1protocol);
 +                              bcs->proto2 = L2_VOICE;
 +                      }
 +                      if (cmsg->B2protocol != 1)
 +                              dev_warn(cs->dev,
 +                                       "B2 Protocol %u unsupported, using Transparent\n",
 +                                       cmsg->B2protocol);
 +                      if (cmsg->B3protocol != 0)
 +                              dev_warn(cs->dev,
 +                                       "B3 Protocol %u unsupported, using Transparent\n",
 +                                       cmsg->B3protocol);
 +                      ignore_cstruct_param(cs, cmsg->B1configuration,
 +                                           "CONNECT_RESP", "B1 Configuration");
 +                      ignore_cstruct_param(cs, cmsg->B2configuration,
 +                                           "CONNECT_RESP", "B2 Configuration");
 +                      ignore_cstruct_param(cs, cmsg->B3configuration,
 +                                           "CONNECT_RESP", "B3 Configuration");
 +              }
 +
 +              /* ToDo: check/encode remaining parameters */
 +              ignore_cstruct_param(cs, cmsg->ConnectedNumber,
 +                                   "CONNECT_RESP", "Connected Number");
 +              ignore_cstruct_param(cs, cmsg->ConnectedSubaddress,
 +                                   "CONNECT_RESP", "Connected Subaddress");
 +              ignore_cstruct_param(cs, cmsg->LLC,
 +                                   "CONNECT_RESP", "LLC");
 +              if (cmsg->AdditionalInfo != CAPI_DEFAULT) {
 +                      ignore_cstruct_param(cs, cmsg->BChannelinformation,
 +                                           "CONNECT_RESP", "BChannel Information");
 +                      ignore_cstruct_param(cs, cmsg->Keypadfacility,
 +                                           "CONNECT_RESP", "Keypad Facility");
 +                      ignore_cstruct_param(cs, cmsg->Useruserdata,
 +                                           "CONNECT_RESP", "User-User Data");
 +                      ignore_cstruct_param(cs, cmsg->Facilitydataarray,
 +                                           "CONNECT_RESP", "Facility Data Array");
 +              }
 +
 +              /* Accept call */
 +              if (!gigaset_add_event(cs, &cs->bcs[channel - 1].at_state,
 +                                     EV_ACCEPT, NULL, 0, NULL))
 +                      return;
 +              gigaset_schedule_event(cs);
 +              return;
 +
 +      case 1:                 /* Ignore */
 +              /* send DISCONNECT_IND to this application */
 +              send_disconnect_ind(bcs, ap, 0);
 +
 +              /* remove it from the list of listening apps */
 +              spin_lock_irqsave(&bcs->aplock, flags);
 +              if (bcs->ap == ap) {
 +                      bcs->ap = ap->bcnext;
 +                      if (bcs->ap == NULL) {
 +                              /* last one: stop ev-layer hupD notifications */
 +                              bcs->apconnstate = APCONN_NONE;
 +                              bcs->chstate &= ~CHS_NOTIFY_LL;
 +                      }
 +                      spin_unlock_irqrestore(&bcs->aplock, flags);
 +                      return;
 +              }
 +              for (oap = bcs->ap; oap != NULL; oap = oap->bcnext) {
 +                      if (oap->bcnext == ap) {
 +                              oap->bcnext = oap->bcnext->bcnext;
 +                              spin_unlock_irqrestore(&bcs->aplock, flags);
 +                              return;
 +                      }
 +              }
 +              spin_unlock_irqrestore(&bcs->aplock, flags);
 +              dev_err(cs->dev, "%s: application %u not found\n",
 +                      __func__, ap->id);
 +              return;
 +
 +      default:                /* Reject */
 +              /* drop all competing applications, keep only this one */
 +              spin_lock_irqsave(&bcs->aplock, flags);
 +              while (bcs->ap != NULL) {
 +                      oap = bcs->ap;
 +                      bcs->ap = oap->bcnext;
 +                      if (oap != ap) {
 +                              spin_unlock_irqrestore(&bcs->aplock, flags);
 +                              send_disconnect_ind(bcs, oap,
 +                                                  CapiCallGivenToOtherApplication);
 +                              spin_lock_irqsave(&bcs->aplock, flags);
 +                      }
 +              }
 +              ap->bcnext = NULL;
 +              bcs->ap = ap;
 +              spin_unlock_irqrestore(&bcs->aplock, flags);
 +
 +              /* reject call - will trigger DISCONNECT_IND for this app */
 +              dev_info(cs->dev, "%s: Reject=%x\n",
 +                       "CONNECT_RESP", cmsg->Reject);
 +              if (!gigaset_add_event(cs, &cs->bcs[channel - 1].at_state,
 +                                     EV_HUP, NULL, 0, NULL))
 +                      return;
 +              gigaset_schedule_event(cs);
 +              return;
 +      }
 +}
 +
 +/*
 + * process CONNECT_B3_REQ message
 + * build NCCI and emit CONNECT_B3_CONF reply
 + */
 +static void do_connect_b3_req(struct gigaset_capi_ctr *iif,
 +                            struct gigaset_capi_appl *ap,
 +                            struct sk_buff *skb)
 +{
 +      struct cardstate *cs = iif->ctr.driverdata;
 +      _cmsg *cmsg = &iif->acmsg;
 +      struct bc_state *bcs;
 +      int channel;
 +
 +      /* decode message */
 +      if (capi_message2cmsg(cmsg, skb->data)) {
 +              dev_err(cs->dev, "%s: message parser failure\n", __func__);
 +              dev_kfree_skb_any(skb);
 +              return;
 +      }
 +      dump_cmsg(DEBUG_CMD, __func__, cmsg);
 +
 +      /* extract and check channel number from PLCI */
 +      channel = (cmsg->adr.adrPLCI >> 8) & 0xff;
 +      if (!channel || channel > cs->channels) {
 +              dev_notice(cs->dev, "%s: invalid %s 0x%02x\n",
 +                         "CONNECT_B3_REQ", "PLCI", cmsg->adr.adrPLCI);
 +              send_conf(iif, ap, skb, CapiIllContrPlciNcci);
 +              return;
 +      }
 +      bcs = &cs->bcs[channel - 1];
 +
 +      /* mark logical connection active */
 +      bcs->apconnstate = APCONN_ACTIVE;
 +
 +      /* build NCCI: always 1 (one B3 connection only) */
 +      cmsg->adr.adrNCCI |= 1 << 16;
 +
 +      /* NCPI parameter: not applicable for B3 Transparent */
 +      ignore_cstruct_param(cs, cmsg->NCPI, "CONNECT_B3_REQ", "NCPI");
 +      send_conf(iif, ap, skb,
 +                (cmsg->NCPI && cmsg->NCPI[0]) ?
 +                CapiNcpiNotSupportedByProtocol : CapiSuccess);
 +}
 +
 +/*
 + * process CONNECT_B3_RESP message
 + * Depending on the Reject parameter, either emit CONNECT_B3_ACTIVE_IND
 + * or queue EV_HUP and emit DISCONNECT_B3_IND.
 + * The emitted message is always shorter than the received one,
 + * allowing to reuse the skb.
 + */
 +static void do_connect_b3_resp(struct gigaset_capi_ctr *iif,
 +                             struct gigaset_capi_appl *ap,
 +                             struct sk_buff *skb)
 +{
 +      struct cardstate *cs = iif->ctr.driverdata;
 +      _cmsg *cmsg = &iif->acmsg;
 +      struct bc_state *bcs;
 +      int channel;
 +      unsigned int msgsize;
 +      u8 command;
 +
 +      /* decode message */
 +      if (capi_message2cmsg(cmsg, skb->data)) {
 +              dev_err(cs->dev, "%s: message parser failure\n", __func__);
 +              dev_kfree_skb_any(skb);
 +              return;
 +      }
 +      dump_cmsg(DEBUG_CMD, __func__, cmsg);
 +
 +      /* extract and check channel number and NCCI */
 +      channel = (cmsg->adr.adrNCCI >> 8) & 0xff;
 +      if (!channel || channel > cs->channels ||
 +          ((cmsg->adr.adrNCCI >> 16) & 0xffff) != 1) {
 +              dev_notice(cs->dev, "%s: invalid %s 0x%02x\n",
 +                         "CONNECT_B3_RESP", "NCCI", cmsg->adr.adrNCCI);
 +              dev_kfree_skb_any(skb);
 +              return;
 +      }
 +      bcs = &cs->bcs[channel - 1];
 +
 +      if (cmsg->Reject) {
 +              /* Reject: clear B3 connect received flag */
 +              bcs->apconnstate = APCONN_SETUP;
 +
 +              /* trigger hangup, causing eventual DISCONNECT_IND */
 +              if (!gigaset_add_event(cs, &bcs->at_state,
 +                                     EV_HUP, NULL, 0, NULL)) {
 +                      dev_kfree_skb_any(skb);
 +                      return;
 +              }
 +              gigaset_schedule_event(cs);
 +
 +              /* emit DISCONNECT_B3_IND */
 +              command = CAPI_DISCONNECT_B3;
 +              msgsize = CAPI_DISCONNECT_B3_IND_BASELEN;
 +      } else {
 +              /*
 +               * Accept: emit CONNECT_B3_ACTIVE_IND immediately, as
 +               * we only send CONNECT_B3_IND if the B channel is up
 +               */
 +              command = CAPI_CONNECT_B3_ACTIVE;
 +              msgsize = CAPI_CONNECT_B3_ACTIVE_IND_BASELEN;
 +      }
 +      capi_cmsg_header(cmsg, ap->id, command, CAPI_IND,
 +                       ap->nextMessageNumber++, cmsg->adr.adrNCCI);
 +      __skb_trim(skb, msgsize);
 +      if (capi_cmsg2message(cmsg, skb->data)) {
 +              dev_err(cs->dev, "%s: message parser failure\n", __func__);
 +              dev_kfree_skb_any(skb);
 +              return;
 +      }
 +      dump_cmsg(DEBUG_CMD, __func__, cmsg);
 +      capi_ctr_handle_message(&iif->ctr, ap->id, skb);
 +}
 +
 +/*
 + * process DISCONNECT_REQ message
 + * schedule EV_HUP and emit DISCONNECT_B3_IND if necessary,
 + * emit DISCONNECT_CONF reply
 + */
 +static void do_disconnect_req(struct gigaset_capi_ctr *iif,
 +                            struct gigaset_capi_appl *ap,
 +                            struct sk_buff *skb)
 +{
 +      struct cardstate *cs = iif->ctr.driverdata;
 +      _cmsg *cmsg = &iif->acmsg;
 +      struct bc_state *bcs;
 +      _cmsg *b3cmsg;
 +      struct sk_buff *b3skb;
 +      int channel;
 +
 +      /* decode message */
 +      if (capi_message2cmsg(cmsg, skb->data)) {
 +              dev_err(cs->dev, "%s: message parser failure\n", __func__);
 +              dev_kfree_skb_any(skb);
 +              return;
 +      }
 +      dump_cmsg(DEBUG_CMD, __func__, cmsg);
 +
 +      /* extract and check channel number from PLCI */
 +      channel = (cmsg->adr.adrPLCI >> 8) & 0xff;
 +      if (!channel || channel > cs->channels) {
 +              dev_notice(cs->dev, "%s: invalid %s 0x%02x\n",
 +                         "DISCONNECT_REQ", "PLCI", cmsg->adr.adrPLCI);
 +              send_conf(iif, ap, skb, CapiIllContrPlciNcci);
 +              return;
 +      }
 +      bcs = cs->bcs + channel - 1;
 +
 +      /* ToDo: process parameter: Additional info */
 +      if (cmsg->AdditionalInfo != CAPI_DEFAULT) {
 +              ignore_cstruct_param(cs, cmsg->BChannelinformation,
 +                                   "DISCONNECT_REQ", "B Channel Information");
 +              ignore_cstruct_param(cs, cmsg->Keypadfacility,
 +                                   "DISCONNECT_REQ", "Keypad Facility");
 +              ignore_cstruct_param(cs, cmsg->Useruserdata,
 +                                   "DISCONNECT_REQ", "User-User Data");
 +              ignore_cstruct_param(cs, cmsg->Facilitydataarray,
 +                                   "DISCONNECT_REQ", "Facility Data Array");
 +      }
 +
 +      /* skip if DISCONNECT_IND already sent */
 +      if (!bcs->apconnstate)
 +              return;
 +
 +      /* check for active logical connection */
 +      if (bcs->apconnstate >= APCONN_ACTIVE) {
 +              /* clear it */
 +              bcs->apconnstate = APCONN_SETUP;
 +
 +              /*
 +               * emit DISCONNECT_B3_IND with cause 0x3301
 +               * use separate cmsg structure, as the content of iif->acmsg
 +               * is still needed for creating the _CONF message
 +               */
 +              b3cmsg = kmalloc(sizeof(*b3cmsg), GFP_KERNEL);
 +              if (!b3cmsg) {
 +                      dev_err(cs->dev, "%s: out of memory\n", __func__);
 +                      send_conf(iif, ap, skb, CAPI_MSGOSRESOURCEERR);
 +                      return;
 +              }
 +              capi_cmsg_header(b3cmsg, ap->id, CAPI_DISCONNECT_B3, CAPI_IND,
 +                               ap->nextMessageNumber++,
 +                               cmsg->adr.adrPLCI | (1 << 16));
 +              b3cmsg->Reason_B3 = CapiProtocolErrorLayer1;
 +              b3skb = alloc_skb(CAPI_DISCONNECT_B3_IND_BASELEN, GFP_KERNEL);
 +              if (b3skb == NULL) {
 +                      dev_err(cs->dev, "%s: out of memory\n", __func__);
 +                      send_conf(iif, ap, skb, CAPI_MSGOSRESOURCEERR);
 +                      kfree(b3cmsg);
 +                      return;
 +              }
 +              if (capi_cmsg2message(b3cmsg,
 +                                    __skb_put(b3skb, CAPI_DISCONNECT_B3_IND_BASELEN))) {
 +                      dev_err(cs->dev, "%s: message parser failure\n",
 +                              __func__);
 +                      kfree(b3cmsg);
 +                      dev_kfree_skb_any(b3skb);
 +                      return;
 +              }
 +              dump_cmsg(DEBUG_CMD, __func__, b3cmsg);
 +              kfree(b3cmsg);
 +              capi_ctr_handle_message(&iif->ctr, ap->id, b3skb);
 +      }
 +
 +      /* trigger hangup, causing eventual DISCONNECT_IND */
 +      if (!gigaset_add_event(cs, &bcs->at_state, EV_HUP, NULL, 0, NULL)) {
 +              send_conf(iif, ap, skb, CAPI_MSGOSRESOURCEERR);
 +              return;
 +      }
 +      gigaset_schedule_event(cs);
 +
 +      /* emit reply */
 +      send_conf(iif, ap, skb, CapiSuccess);
 +}
 +
 +/*
 + * process DISCONNECT_B3_REQ message
 + * schedule EV_HUP and emit DISCONNECT_B3_CONF reply
 + */
 +static void do_disconnect_b3_req(struct gigaset_capi_ctr *iif,
 +                               struct gigaset_capi_appl *ap,
 +                               struct sk_buff *skb)
 +{
 +      struct cardstate *cs = iif->ctr.driverdata;
 +      _cmsg *cmsg = &iif->acmsg;
 +      struct bc_state *bcs;
 +      int channel;
 +
 +      /* decode message */
 +      if (capi_message2cmsg(cmsg, skb->data)) {
 +              dev_err(cs->dev, "%s: message parser failure\n", __func__);
 +              dev_kfree_skb_any(skb);
 +              return;
 +      }
 +      dump_cmsg(DEBUG_CMD, __func__, cmsg);
 +
 +      /* extract and check channel number and NCCI */
 +      channel = (cmsg->adr.adrNCCI >> 8) & 0xff;
 +      if (!channel || channel > cs->channels ||
 +          ((cmsg->adr.adrNCCI >> 16) & 0xffff) != 1) {
 +              dev_notice(cs->dev, "%s: invalid %s 0x%02x\n",
 +                         "DISCONNECT_B3_REQ", "NCCI", cmsg->adr.adrNCCI);
 +              send_conf(iif, ap, skb, CapiIllContrPlciNcci);
 +              return;
 +      }
 +      bcs = &cs->bcs[channel - 1];
 +
 +      /* reject if logical connection not active */
 +      if (bcs->apconnstate < APCONN_ACTIVE) {
 +              send_conf(iif, ap, skb,
 +                        CapiMessageNotSupportedInCurrentState);
 +              return;
 +      }
 +
 +      /* trigger hangup, causing eventual DISCONNECT_B3_IND */
 +      if (!gigaset_add_event(cs, &bcs->at_state, EV_HUP, NULL, 0, NULL)) {
 +              send_conf(iif, ap, skb, CAPI_MSGOSRESOURCEERR);
 +              return;
 +      }
 +      gigaset_schedule_event(cs);
 +
 +      /* NCPI parameter: not applicable for B3 Transparent */
 +      ignore_cstruct_param(cs, cmsg->NCPI,
 +                           "DISCONNECT_B3_REQ", "NCPI");
 +      send_conf(iif, ap, skb,
 +                (cmsg->NCPI && cmsg->NCPI[0]) ?
 +                CapiNcpiNotSupportedByProtocol : CapiSuccess);
 +}
 +
 +/*
 + * process DATA_B3_REQ message
 + */
 +static void do_data_b3_req(struct gigaset_capi_ctr *iif,
 +                         struct gigaset_capi_appl *ap,
 +                         struct sk_buff *skb)
 +{
 +      struct cardstate *cs = iif->ctr.driverdata;
 +      struct bc_state *bcs;
 +      int channel = CAPIMSG_PLCI_PART(skb->data);
 +      u16 ncci = CAPIMSG_NCCI_PART(skb->data);
 +      u16 msglen = CAPIMSG_LEN(skb->data);
 +      u16 datalen = CAPIMSG_DATALEN(skb->data);
 +      u16 flags = CAPIMSG_FLAGS(skb->data);
 +      u16 msgid = CAPIMSG_MSGID(skb->data);
 +      u16 handle = CAPIMSG_HANDLE_REQ(skb->data);
 +
 +      /* frequent message, avoid _cmsg overhead */
 +      dump_rawmsg(DEBUG_MCMD, __func__, skb->data);
 +
 +      /* check parameters */
 +      if (channel == 0 || channel > cs->channels || ncci != 1) {
 +              dev_notice(cs->dev, "%s: invalid %s 0x%02x\n",
 +                         "DATA_B3_REQ", "NCCI", CAPIMSG_NCCI(skb->data));
 +              send_conf(iif, ap, skb, CapiIllContrPlciNcci);
 +              return;
 +      }
 +      bcs = &cs->bcs[channel - 1];
 +      if (msglen != CAPI_DATA_B3_REQ_LEN && msglen != CAPI_DATA_B3_REQ_LEN64)
 +              dev_notice(cs->dev, "%s: unexpected length %d\n",
 +                         "DATA_B3_REQ", msglen);
 +      if (msglen + datalen != skb->len)
 +              dev_notice(cs->dev, "%s: length mismatch (%d+%d!=%d)\n",
 +                         "DATA_B3_REQ", msglen, datalen, skb->len);
 +      if (msglen + datalen > skb->len) {
 +              /* message too short for announced data length */
 +              send_conf(iif, ap, skb, CapiIllMessageParmCoding); /* ? */
 +              return;
 +      }
 +      if (flags & CAPI_FLAGS_RESERVED) {
 +              dev_notice(cs->dev, "%s: reserved flags set (%x)\n",
 +                         "DATA_B3_REQ", flags);
 +              send_conf(iif, ap, skb, CapiIllMessageParmCoding);
 +              return;
 +      }
 +
 +      /* reject if logical connection not active */
 +      if (bcs->apconnstate < APCONN_ACTIVE) {
 +              send_conf(iif, ap, skb, CapiMessageNotSupportedInCurrentState);
 +              return;
 +      }
 +
 +      /* pull CAPI message into link layer header */
 +      skb_reset_mac_header(skb);
 +      skb->mac_len = msglen;
 +      skb_pull(skb, msglen);
 +
 +      /* pass to device-specific module */
 +      if (cs->ops->send_skb(bcs, skb) < 0) {
 +              send_conf(iif, ap, skb, CAPI_MSGOSRESOURCEERR);
 +              return;
 +      }
 +
 +      /*
 +       * DATA_B3_CONF will be sent by gigaset_skb_sent() only if "delivery
 +       * confirmation" bit is set; otherwise we have to send it now
 +       */
 +      if (!(flags & CAPI_FLAGS_DELIVERY_CONFIRMATION))
 +              send_data_b3_conf(cs, &iif->ctr, ap->id, msgid, channel, handle,
 +                                flags ? CapiFlagsNotSupportedByProtocol
 +                                : CAPI_NOERROR);
 +}
 +
 +/*
 + * process RESET_B3_REQ message
 + * just always reply "not supported by current protocol"
 + */
 +static void do_reset_b3_req(struct gigaset_capi_ctr *iif,
 +                          struct gigaset_capi_appl *ap,
 +                          struct sk_buff *skb)
 +{
 +      struct cardstate *cs = iif->ctr.driverdata;
 +
 +      /* decode message */
 +      if (capi_message2cmsg(&iif->acmsg, skb->data)) {
 +              dev_err(cs->dev, "%s: message parser failure\n", __func__);
 +              dev_kfree_skb_any(skb);
 +              return;
 +      }
 +      dump_cmsg(DEBUG_CMD, __func__, &iif->acmsg);
 +      send_conf(iif, ap, skb,
 +                CapiResetProcedureNotSupportedByCurrentProtocol);
 +}
 +
 +/*
 + * unsupported CAPI message handler
 + */
 +static void do_unsupported(struct gigaset_capi_ctr *iif,
 +                         struct gigaset_capi_appl *ap,
 +                         struct sk_buff *skb)
 +{
 +      struct cardstate *cs = iif->ctr.driverdata;
 +
 +      /* decode message */
 +      if (capi_message2cmsg(&iif->acmsg, skb->data)) {
 +              dev_err(cs->dev, "%s: message parser failure\n", __func__);
 +              dev_kfree_skb_any(skb);
 +              return;
 +      }
 +      dump_cmsg(DEBUG_CMD, __func__, &iif->acmsg);
 +      send_conf(iif, ap, skb, CapiMessageNotSupportedInCurrentState);
 +}
 +
 +/*
 + * CAPI message handler: no-op
 + */
 +static void do_nothing(struct gigaset_capi_ctr *iif,
 +                     struct gigaset_capi_appl *ap,
 +                     struct sk_buff *skb)
 +{
 +      struct cardstate *cs = iif->ctr.driverdata;
 +
 +      /* decode message */
 +      if (capi_message2cmsg(&iif->acmsg, skb->data)) {
 +              dev_err(cs->dev, "%s: message parser failure\n", __func__);
 +              dev_kfree_skb_any(skb);
 +              return;
 +      }
 +      dump_cmsg(DEBUG_CMD, __func__, &iif->acmsg);
 +      dev_kfree_skb_any(skb);
 +}
 +
 +static void do_data_b3_resp(struct gigaset_capi_ctr *iif,
 +                          struct gigaset_capi_appl *ap,
 +                          struct sk_buff *skb)
 +{
 +      dump_rawmsg(DEBUG_MCMD, __func__, skb->data);
 +      dev_kfree_skb_any(skb);
 +}
 +
 +/* table of outgoing CAPI message handlers with lookup function */
 +typedef void (*capi_send_handler_t)(struct gigaset_capi_ctr *,
 +                                  struct gigaset_capi_appl *,
 +                                  struct sk_buff *);
 +
 +static struct {
 +      u16 cmd;
 +      capi_send_handler_t handler;
 +} capi_send_handler_table[] = {
 +      /* most frequent messages first for faster lookup */
 +      { CAPI_DATA_B3_REQ, do_data_b3_req },
 +      { CAPI_DATA_B3_RESP, do_data_b3_resp },
 +
 +      { CAPI_ALERT_REQ, do_alert_req },
 +      { CAPI_CONNECT_ACTIVE_RESP, do_nothing },
 +      { CAPI_CONNECT_B3_ACTIVE_RESP, do_nothing },
 +      { CAPI_CONNECT_B3_REQ, do_connect_b3_req },
 +      { CAPI_CONNECT_B3_RESP, do_connect_b3_resp },
 +      { CAPI_CONNECT_B3_T90_ACTIVE_RESP, do_nothing },
 +      { CAPI_CONNECT_REQ, do_connect_req },
 +      { CAPI_CONNECT_RESP, do_connect_resp },
 +      { CAPI_DISCONNECT_B3_REQ, do_disconnect_b3_req },
 +      { CAPI_DISCONNECT_B3_RESP, do_nothing },
 +      { CAPI_DISCONNECT_REQ, do_disconnect_req },
 +      { CAPI_DISCONNECT_RESP, do_nothing },
 +      { CAPI_FACILITY_REQ, do_facility_req },
 +      { CAPI_FACILITY_RESP, do_nothing },
 +      { CAPI_LISTEN_REQ, do_listen_req },
 +      { CAPI_SELECT_B_PROTOCOL_REQ, do_unsupported },
 +      { CAPI_RESET_B3_REQ, do_reset_b3_req },
 +      { CAPI_RESET_B3_RESP, do_nothing },
 +
 +      /*
 +       * ToDo: support overlap sending (requires ev-layer state
 +       * machine extension to generate additional ATD commands)
 +       */
 +      { CAPI_INFO_REQ, do_unsupported },
 +      { CAPI_INFO_RESP, do_nothing },
 +
 +      /*
 +       * ToDo: what's the proper response for these?
 +       */
 +      { CAPI_MANUFACTURER_REQ, do_nothing },
 +      { CAPI_MANUFACTURER_RESP, do_nothing },
 +};
 +
 +/* look up handler */
 +static inline capi_send_handler_t lookup_capi_send_handler(const u16 cmd)
 +{
 +      size_t i;
 +
 +      for (i = 0; i < ARRAY_SIZE(capi_send_handler_table); i++)
 +              if (capi_send_handler_table[i].cmd == cmd)
 +                      return capi_send_handler_table[i].handler;
 +      return NULL;
 +}
 +
 +
 +/**
 + * gigaset_send_message() - accept a CAPI message from an application
 + * @ctr:      controller descriptor structure.
 + * @skb:      CAPI message.
 + *
 + * Return value: CAPI error code
 + * Note: capidrv (and probably others, too) only uses the return value to
 + * decide whether it has to free the skb (only if result != CAPI_NOERROR (0))
 + */
 +static u16 gigaset_send_message(struct capi_ctr *ctr, struct sk_buff *skb)
 +{
 +      struct gigaset_capi_ctr *iif
 +              = container_of(ctr, struct gigaset_capi_ctr, ctr);
 +      struct cardstate *cs = ctr->driverdata;
 +      struct gigaset_capi_appl *ap;
 +      capi_send_handler_t handler;
 +
 +      /* can only handle linear sk_buffs */
 +      if (skb_linearize(skb) < 0) {
 +              dev_warn(cs->dev, "%s: skb_linearize failed\n", __func__);
 +              return CAPI_MSGOSRESOURCEERR;
 +      }
 +
 +      /* retrieve application data structure */
 +      ap = get_appl(iif, CAPIMSG_APPID(skb->data));
 +      if (!ap) {
 +              dev_notice(cs->dev, "%s: application %u not registered\n",
 +                         __func__, CAPIMSG_APPID(skb->data));
 +              return CAPI_ILLAPPNR;
 +      }
 +
 +      /* look up command */
 +      handler = lookup_capi_send_handler(CAPIMSG_CMD(skb->data));
 +      if (!handler) {
 +              /* unknown/unsupported message type */
 +              if (printk_ratelimit())
 +                      dev_notice(cs->dev, "%s: unsupported message %u\n",
 +                                 __func__, CAPIMSG_CMD(skb->data));
 +              return CAPI_ILLCMDORSUBCMDORMSGTOSMALL;
 +      }
 +
 +      /* serialize */
 +      if (atomic_add_return(1, &iif->sendqlen) > 1) {
 +              /* queue behind other messages */
 +              skb_queue_tail(&iif->sendqueue, skb);
 +              return CAPI_NOERROR;
 +      }
 +
 +      /* process message */
 +      handler(iif, ap, skb);
 +
 +      /* process other messages arrived in the meantime */
 +      while (atomic_sub_return(1, &iif->sendqlen) > 0) {
 +              skb = skb_dequeue(&iif->sendqueue);
 +              if (!skb) {
 +                      /* should never happen */
 +                      dev_err(cs->dev, "%s: send queue empty\n", __func__);
 +                      continue;
 +              }
 +              ap = get_appl(iif, CAPIMSG_APPID(skb->data));
 +              if (!ap) {
 +                      /* could that happen? */
 +                      dev_warn(cs->dev, "%s: application %u vanished\n",
 +                               __func__, CAPIMSG_APPID(skb->data));
 +                      continue;
 +              }
 +              handler = lookup_capi_send_handler(CAPIMSG_CMD(skb->data));
 +              if (!handler) {
 +                      /* should never happen */
 +                      dev_err(cs->dev, "%s: handler %x vanished\n",
 +                              __func__, CAPIMSG_CMD(skb->data));
 +                      continue;
 +              }
 +              handler(iif, ap, skb);
 +      }
 +
 +      return CAPI_NOERROR;
 +}
 +
 +/**
 + * gigaset_procinfo() - build single line description for controller
 + * @ctr:      controller descriptor structure.
 + *
 + * Return value: pointer to generated string (null terminated)
 + */
 +static char *gigaset_procinfo(struct capi_ctr *ctr)
 +{
 +      return ctr->name;       /* ToDo: more? */
 +}
 +
 +static int gigaset_proc_show(struct seq_file *m, void *v)
 +{
 +      struct capi_ctr *ctr = m->private;
 +      struct cardstate *cs = ctr->driverdata;
 +      char *s;
 +      int i;
 +
 +      seq_printf(m, "%-16s %s\n", "name", ctr->name);
 +      seq_printf(m, "%-16s %s %s\n", "dev",
 +                 dev_driver_string(cs->dev), dev_name(cs->dev));
 +      seq_printf(m, "%-16s %d\n", "id", cs->myid);
 +      if (cs->gotfwver)
 +              seq_printf(m, "%-16s %d.%d.%d.%d\n", "firmware",
 +                         cs->fwver[0], cs->fwver[1], cs->fwver[2], cs->fwver[3]);
 +      seq_printf(m, "%-16s %d\n", "channels", cs->channels);
 +      seq_printf(m, "%-16s %s\n", "onechannel", cs->onechannel ? "yes" : "no");
 +
 +      switch (cs->mode) {
 +      case M_UNKNOWN:
 +              s = "unknown";
 +              break;
 +      case M_CONFIG:
 +              s = "config";
 +              break;
 +      case M_UNIMODEM:
 +              s = "Unimodem";
 +              break;
 +      case M_CID:
 +              s = "CID";
 +              break;
 +      default:
 +              s = "??";
 +      }
 +      seq_printf(m, "%-16s %s\n", "mode", s);
 +
 +      switch (cs->mstate) {
 +      case MS_UNINITIALIZED:
 +              s = "uninitialized";
 +              break;
 +      case MS_INIT:
 +              s = "init";
 +              break;
 +      case MS_LOCKED:
 +              s = "locked";
 +              break;
 +      case MS_SHUTDOWN:
 +              s = "shutdown";
 +              break;
 +      case MS_RECOVER:
 +              s = "recover";
 +              break;
 +      case MS_READY:
 +              s = "ready";
 +              break;
 +      default:
 +              s = "??";
 +      }
 +      seq_printf(m, "%-16s %s\n", "mstate", s);
 +
 +      seq_printf(m, "%-16s %s\n", "running", cs->running ? "yes" : "no");
 +      seq_printf(m, "%-16s %s\n", "connected", cs->connected ? "yes" : "no");
 +      seq_printf(m, "%-16s %s\n", "isdn_up", cs->isdn_up ? "yes" : "no");
 +      seq_printf(m, "%-16s %s\n", "cidmode", cs->cidmode ? "yes" : "no");
 +
 +      for (i = 0; i < cs->channels; i++) {
 +              seq_printf(m, "[%d]%-13s %d\n", i, "corrupted",
 +                         cs->bcs[i].corrupted);
 +              seq_printf(m, "[%d]%-13s %d\n", i, "trans_down",
 +                         cs->bcs[i].trans_down);
 +              seq_printf(m, "[%d]%-13s %d\n", i, "trans_up",
 +                         cs->bcs[i].trans_up);
 +              seq_printf(m, "[%d]%-13s %d\n", i, "chstate",
 +                         cs->bcs[i].chstate);
 +              switch (cs->bcs[i].proto2) {
 +              case L2_BITSYNC:
 +                      s = "bitsync";
 +                      break;
 +              case L2_HDLC:
 +                      s = "HDLC";
 +                      break;
 +              case L2_VOICE:
 +                      s = "voice";
 +                      break;
 +              default:
 +                      s = "??";
 +              }
 +              seq_printf(m, "[%d]%-13s %s\n", i, "proto2", s);
 +      }
 +      return 0;
 +}
 +
 +/**
 + * gigaset_isdn_regdev() - register device to LL
 + * @cs:               device descriptor structure.
 + * @isdnid:   device name.
 + *
 + * Return value: 0 on success, error code < 0 on failure
 + */
 +int gigaset_isdn_regdev(struct cardstate *cs, const char *isdnid)
 +{
 +      struct gigaset_capi_ctr *iif;
 +      int rc;
 +
 +      iif = kzalloc(sizeof(*iif), GFP_KERNEL);
 +      if (!iif) {
 +              pr_err("%s: out of memory\n", __func__);
 +              return -ENOMEM;
 +      }
 +
 +      /* prepare controller structure */
 +      iif->ctr.owner         = THIS_MODULE;
 +      iif->ctr.driverdata    = cs;
 +      strncpy(iif->ctr.name, isdnid, sizeof(iif->ctr.name) - 1);
 +      iif->ctr.driver_name   = "gigaset";
 +      iif->ctr.load_firmware = NULL;
 +      iif->ctr.reset_ctr     = NULL;
 +      iif->ctr.register_appl = gigaset_register_appl;
 +      iif->ctr.release_appl  = gigaset_release_appl;
 +      iif->ctr.send_message  = gigaset_send_message;
 +      iif->ctr.procinfo      = gigaset_procinfo;
 +      iif->ctr.proc_show     = gigaset_proc_show,
 +      INIT_LIST_HEAD(&iif->appls);
 +      skb_queue_head_init(&iif->sendqueue);
 +      atomic_set(&iif->sendqlen, 0);
 +
 +      /* register controller with CAPI */
 +      rc = attach_capi_ctr(&iif->ctr);
 +      if (rc) {
 +              pr_err("attach_capi_ctr failed (%d)\n", rc);
 +              kfree(iif);
 +              return rc;
 +      }
 +
 +      cs->iif = iif;
 +      cs->hw_hdr_len = CAPI_DATA_B3_REQ_LEN;
 +      return 0;
 +}
 +
 +/**
 + * gigaset_isdn_unregdev() - unregister device from LL
 + * @cs:               device descriptor structure.
 + */
 +void gigaset_isdn_unregdev(struct cardstate *cs)
 +{
 +      struct gigaset_capi_ctr *iif = cs->iif;
 +
 +      detach_capi_ctr(&iif->ctr);
 +      kfree(iif);
 +      cs->iif = NULL;
 +}
 +
 +static struct capi_driver capi_driver_gigaset = {
 +      .name           = "gigaset",
 +      .revision       = "1.0",
 +};
 +
 +/**
 + * gigaset_isdn_regdrv() - register driver to LL
 + */
 +void gigaset_isdn_regdrv(void)
 +{
 +      pr_info("Kernel CAPI interface\n");
 +      register_capi_driver(&capi_driver_gigaset);
 +}
 +
 +/**
 + * gigaset_isdn_unregdrv() - unregister driver from LL
 + */
 +void gigaset_isdn_unregdrv(void)
 +{
 +      unregister_capi_driver(&capi_driver_gigaset);
 +}
index 76b5407,0000000..3bb8092
mode 100644,000000..100644
--- /dev/null
@@@ -1,1156 -1,0 +1,1153 @@@
-  *    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.
++// SPDX-License-Identifier: GPL-2.0-or-later
 +/*
 + * Stuff used by all variants of the driver
 + *
 + * Copyright (c) 2001 by Stefan Eilers,
 + *                       Hansjoerg Lipp <hjlipp@web.de>,
 + *                       Tilman Schmidt <tilman@imap.cc>.
 + *
 + * =====================================================================
 + * =====================================================================
 + */
 +
 +#include "gigaset.h"
 +#include <linux/module.h>
 +#include <linux/moduleparam.h>
 +
 +/* Version Information */
 +#define DRIVER_AUTHOR "Hansjoerg Lipp <hjlipp@web.de>, Tilman Schmidt <tilman@imap.cc>, Stefan Eilers"
 +#define DRIVER_DESC "Driver for Gigaset 307x"
 +
 +#ifdef CONFIG_GIGASET_DEBUG
 +#define DRIVER_DESC_DEBUG " (debug build)"
 +#else
 +#define DRIVER_DESC_DEBUG ""
 +#endif
 +
 +/* Module parameters */
 +int gigaset_debuglevel;
 +EXPORT_SYMBOL_GPL(gigaset_debuglevel);
 +module_param_named(debug, gigaset_debuglevel, int, S_IRUGO | S_IWUSR);
 +MODULE_PARM_DESC(debug, "debug level");
 +
 +/* driver state flags */
 +#define VALID_MINOR   0x01
 +#define VALID_ID      0x02
 +
 +/**
 + * gigaset_dbg_buffer() - dump data in ASCII and hex for debugging
 + * @level:    debugging level.
 + * @msg:      message prefix.
 + * @len:      number of bytes to dump.
 + * @buf:      data to dump.
 + *
 + * If the current debugging level includes one of the bits set in @level,
 + * @len bytes starting at @buf are logged to dmesg at KERN_DEBUG prio,
 + * prefixed by the text @msg.
 + */
 +void gigaset_dbg_buffer(enum debuglevel level, const unsigned char *msg,
 +                      size_t len, const unsigned char *buf)
 +{
 +      unsigned char outbuf[80];
 +      unsigned char c;
 +      size_t space = sizeof outbuf - 1;
 +      unsigned char *out = outbuf;
 +      size_t numin = len;
 +
 +      while (numin--) {
 +              c = *buf++;
 +              if (c == '~' || c == '^' || c == '\\') {
 +                      if (!space--)
 +                              break;
 +                      *out++ = '\\';
 +              }
 +              if (c & 0x80) {
 +                      if (!space--)
 +                              break;
 +                      *out++ = '~';
 +                      c ^= 0x80;
 +              }
 +              if (c < 0x20 || c == 0x7f) {
 +                      if (!space--)
 +                              break;
 +                      *out++ = '^';
 +                      c ^= 0x40;
 +              }
 +              if (!space--)
 +                      break;
 +              *out++ = c;
 +      }
 +      *out = 0;
 +
 +      gig_dbg(level, "%s (%u bytes): %s", msg, (unsigned) len, outbuf);
 +}
 +EXPORT_SYMBOL_GPL(gigaset_dbg_buffer);
 +
 +static int setflags(struct cardstate *cs, unsigned flags, unsigned delay)
 +{
 +      int r;
 +
 +      r = cs->ops->set_modem_ctrl(cs, cs->control_state, flags);
 +      cs->control_state = flags;
 +      if (r < 0)
 +              return r;
 +
 +      if (delay) {
 +              set_current_state(TASK_INTERRUPTIBLE);
 +              schedule_timeout(delay * HZ / 1000);
 +      }
 +
 +      return 0;
 +}
 +
 +int gigaset_enterconfigmode(struct cardstate *cs)
 +{
 +      int i, r;
 +
 +      cs->control_state = TIOCM_RTS;
 +
 +      r = setflags(cs, TIOCM_DTR, 200);
 +      if (r < 0)
 +              goto error;
 +      r = setflags(cs, 0, 200);
 +      if (r < 0)
 +              goto error;
 +      for (i = 0; i < 5; ++i) {
 +              r = setflags(cs, TIOCM_RTS, 100);
 +              if (r < 0)
 +                      goto error;
 +              r = setflags(cs, 0, 100);
 +              if (r < 0)
 +                      goto error;
 +      }
 +      r = setflags(cs, TIOCM_RTS | TIOCM_DTR, 800);
 +      if (r < 0)
 +              goto error;
 +
 +      return 0;
 +
 +error:
 +      dev_err(cs->dev, "error %d on setuartbits\n", -r);
 +      cs->control_state = TIOCM_RTS | TIOCM_DTR;
 +      cs->ops->set_modem_ctrl(cs, 0, TIOCM_RTS | TIOCM_DTR);
 +
 +      return -1;
 +}
 +
 +static int test_timeout(struct at_state_t *at_state)
 +{
 +      if (!at_state->timer_expires)
 +              return 0;
 +
 +      if (--at_state->timer_expires) {
 +              gig_dbg(DEBUG_MCMD, "decreased timer of %p to %lu",
 +                      at_state, at_state->timer_expires);
 +              return 0;
 +      }
 +
 +      gigaset_add_event(at_state->cs, at_state, EV_TIMEOUT, NULL,
 +                        at_state->timer_index, NULL);
 +      return 1;
 +}
 +
 +static void timer_tick(struct timer_list *t)
 +{
 +      struct cardstate *cs = from_timer(cs, t, timer);
 +      unsigned long flags;
 +      unsigned channel;
 +      struct at_state_t *at_state;
 +      int timeout = 0;
 +
 +      spin_lock_irqsave(&cs->lock, flags);
 +
 +      for (channel = 0; channel < cs->channels; ++channel)
 +              if (test_timeout(&cs->bcs[channel].at_state))
 +                      timeout = 1;
 +
 +      if (test_timeout(&cs->at_state))
 +              timeout = 1;
 +
 +      list_for_each_entry(at_state, &cs->temp_at_states, list)
 +              if (test_timeout(at_state))
 +                      timeout = 1;
 +
 +      if (cs->running) {
 +              mod_timer(&cs->timer, jiffies + msecs_to_jiffies(GIG_TICK));
 +              if (timeout) {
 +                      gig_dbg(DEBUG_EVENT, "scheduling timeout");
 +                      tasklet_schedule(&cs->event_tasklet);
 +              }
 +      }
 +
 +      spin_unlock_irqrestore(&cs->lock, flags);
 +}
 +
 +int gigaset_get_channel(struct bc_state *bcs)
 +{
 +      unsigned long flags;
 +
 +      spin_lock_irqsave(&bcs->cs->lock, flags);
 +      if (bcs->use_count || !try_module_get(bcs->cs->driver->owner)) {
 +              gig_dbg(DEBUG_CHANNEL, "could not allocate channel %d",
 +                      bcs->channel);
 +              spin_unlock_irqrestore(&bcs->cs->lock, flags);
 +              return -EBUSY;
 +      }
 +      ++bcs->use_count;
 +      bcs->busy = 1;
 +      gig_dbg(DEBUG_CHANNEL, "allocated channel %d", bcs->channel);
 +      spin_unlock_irqrestore(&bcs->cs->lock, flags);
 +      return 0;
 +}
 +
 +struct bc_state *gigaset_get_free_channel(struct cardstate *cs)
 +{
 +      unsigned long flags;
 +      int i;
 +
 +      spin_lock_irqsave(&cs->lock, flags);
 +      if (!try_module_get(cs->driver->owner)) {
 +              gig_dbg(DEBUG_CHANNEL,
 +                      "could not get module for allocating channel");
 +              spin_unlock_irqrestore(&cs->lock, flags);
 +              return NULL;
 +      }
 +      for (i = 0; i < cs->channels; ++i)
 +              if (!cs->bcs[i].use_count) {
 +                      ++cs->bcs[i].use_count;
 +                      cs->bcs[i].busy = 1;
 +                      spin_unlock_irqrestore(&cs->lock, flags);
 +                      gig_dbg(DEBUG_CHANNEL, "allocated channel %d", i);
 +                      return cs->bcs + i;
 +              }
 +      module_put(cs->driver->owner);
 +      spin_unlock_irqrestore(&cs->lock, flags);
 +      gig_dbg(DEBUG_CHANNEL, "no free channel");
 +      return NULL;
 +}
 +
 +void gigaset_free_channel(struct bc_state *bcs)
 +{
 +      unsigned long flags;
 +
 +      spin_lock_irqsave(&bcs->cs->lock, flags);
 +      if (!bcs->busy) {
 +              gig_dbg(DEBUG_CHANNEL, "could not free channel %d",
 +                      bcs->channel);
 +              spin_unlock_irqrestore(&bcs->cs->lock, flags);
 +              return;
 +      }
 +      --bcs->use_count;
 +      bcs->busy = 0;
 +      module_put(bcs->cs->driver->owner);
 +      gig_dbg(DEBUG_CHANNEL, "freed channel %d", bcs->channel);
 +      spin_unlock_irqrestore(&bcs->cs->lock, flags);
 +}
 +
 +int gigaset_get_channels(struct cardstate *cs)
 +{
 +      unsigned long flags;
 +      int i;
 +
 +      spin_lock_irqsave(&cs->lock, flags);
 +      for (i = 0; i < cs->channels; ++i)
 +              if (cs->bcs[i].use_count) {
 +                      spin_unlock_irqrestore(&cs->lock, flags);
 +                      gig_dbg(DEBUG_CHANNEL,
 +                              "could not allocate all channels");
 +                      return -EBUSY;
 +              }
 +      for (i = 0; i < cs->channels; ++i)
 +              ++cs->bcs[i].use_count;
 +      spin_unlock_irqrestore(&cs->lock, flags);
 +
 +      gig_dbg(DEBUG_CHANNEL, "allocated all channels");
 +
 +      return 0;
 +}
 +
 +void gigaset_free_channels(struct cardstate *cs)
 +{
 +      unsigned long flags;
 +      int i;
 +
 +      gig_dbg(DEBUG_CHANNEL, "unblocking all channels");
 +      spin_lock_irqsave(&cs->lock, flags);
 +      for (i = 0; i < cs->channels; ++i)
 +              --cs->bcs[i].use_count;
 +      spin_unlock_irqrestore(&cs->lock, flags);
 +}
 +
 +void gigaset_block_channels(struct cardstate *cs)
 +{
 +      unsigned long flags;
 +      int i;
 +
 +      gig_dbg(DEBUG_CHANNEL, "blocking all channels");
 +      spin_lock_irqsave(&cs->lock, flags);
 +      for (i = 0; i < cs->channels; ++i)
 +              ++cs->bcs[i].use_count;
 +      spin_unlock_irqrestore(&cs->lock, flags);
 +}
 +
 +static void clear_events(struct cardstate *cs)
 +{
 +      struct event_t *ev;
 +      unsigned head, tail;
 +      unsigned long flags;
 +
 +      spin_lock_irqsave(&cs->ev_lock, flags);
 +
 +      head = cs->ev_head;
 +      tail = cs->ev_tail;
 +
 +      while (tail != head) {
 +              ev = cs->events + head;
 +              kfree(ev->ptr);
 +              head = (head + 1) % MAX_EVENTS;
 +      }
 +
 +      cs->ev_head = tail;
 +
 +      spin_unlock_irqrestore(&cs->ev_lock, flags);
 +}
 +
 +/**
 + * gigaset_add_event() - add event to device event queue
 + * @cs:               device descriptor structure.
 + * @at_state: connection state structure.
 + * @type:     event type.
 + * @ptr:      pointer parameter for event.
 + * @parameter:        integer parameter for event.
 + * @arg:      pointer parameter for event.
 + *
 + * Allocate an event queue entry from the device's event queue, and set it up
 + * with the parameters given.
 + *
 + * Return value: added event
 + */
 +struct event_t *gigaset_add_event(struct cardstate *cs,
 +                                struct at_state_t *at_state, int type,
 +                                void *ptr, int parameter, void *arg)
 +{
 +      unsigned long flags;
 +      unsigned next, tail;
 +      struct event_t *event = NULL;
 +
 +      gig_dbg(DEBUG_EVENT, "queueing event %d", type);
 +
 +      spin_lock_irqsave(&cs->ev_lock, flags);
 +
 +      tail = cs->ev_tail;
 +      next = (tail + 1) % MAX_EVENTS;
 +      if (unlikely(next == cs->ev_head))
 +              dev_err(cs->dev, "event queue full\n");
 +      else {
 +              event = cs->events + tail;
 +              event->type = type;
 +              event->at_state = at_state;
 +              event->cid = -1;
 +              event->ptr = ptr;
 +              event->arg = arg;
 +              event->parameter = parameter;
 +              cs->ev_tail = next;
 +      }
 +
 +      spin_unlock_irqrestore(&cs->ev_lock, flags);
 +
 +      return event;
 +}
 +EXPORT_SYMBOL_GPL(gigaset_add_event);
 +
 +static void clear_at_state(struct at_state_t *at_state)
 +{
 +      int i;
 +
 +      for (i = 0; i < STR_NUM; ++i) {
 +              kfree(at_state->str_var[i]);
 +              at_state->str_var[i] = NULL;
 +      }
 +}
 +
 +static void dealloc_temp_at_states(struct cardstate *cs)
 +{
 +      struct at_state_t *cur, *next;
 +
 +      list_for_each_entry_safe(cur, next, &cs->temp_at_states, list) {
 +              list_del(&cur->list);
 +              clear_at_state(cur);
 +              kfree(cur);
 +      }
 +}
 +
 +static void gigaset_freebcs(struct bc_state *bcs)
 +{
 +      int i;
 +
 +      gig_dbg(DEBUG_INIT, "freeing bcs[%d]->hw", bcs->channel);
 +      bcs->cs->ops->freebcshw(bcs);
 +
 +      gig_dbg(DEBUG_INIT, "clearing bcs[%d]->at_state", bcs->channel);
 +      clear_at_state(&bcs->at_state);
 +      gig_dbg(DEBUG_INIT, "freeing bcs[%d]->skb", bcs->channel);
 +      dev_kfree_skb(bcs->rx_skb);
 +      bcs->rx_skb = NULL;
 +
 +      for (i = 0; i < AT_NUM; ++i) {
 +              kfree(bcs->commands[i]);
 +              bcs->commands[i] = NULL;
 +      }
 +}
 +
 +static struct cardstate *alloc_cs(struct gigaset_driver *drv)
 +{
 +      unsigned long flags;
 +      unsigned i;
 +      struct cardstate *cs;
 +      struct cardstate *ret = NULL;
 +
 +      spin_lock_irqsave(&drv->lock, flags);
 +      if (drv->blocked)
 +              goto exit;
 +      for (i = 0; i < drv->minors; ++i) {
 +              cs = drv->cs + i;
 +              if (!(cs->flags & VALID_MINOR)) {
 +                      cs->flags = VALID_MINOR;
 +                      ret = cs;
 +                      break;
 +              }
 +      }
 +exit:
 +      spin_unlock_irqrestore(&drv->lock, flags);
 +      return ret;
 +}
 +
 +static void free_cs(struct cardstate *cs)
 +{
 +      cs->flags = 0;
 +}
 +
 +static void make_valid(struct cardstate *cs, unsigned mask)
 +{
 +      unsigned long flags;
 +      struct gigaset_driver *drv = cs->driver;
 +      spin_lock_irqsave(&drv->lock, flags);
 +      cs->flags |= mask;
 +      spin_unlock_irqrestore(&drv->lock, flags);
 +}
 +
 +static void make_invalid(struct cardstate *cs, unsigned mask)
 +{
 +      unsigned long flags;
 +      struct gigaset_driver *drv = cs->driver;
 +      spin_lock_irqsave(&drv->lock, flags);
 +      cs->flags &= ~mask;
 +      spin_unlock_irqrestore(&drv->lock, flags);
 +}
 +
 +/**
 + * gigaset_freecs() - free all associated ressources of a device
 + * @cs:               device descriptor structure.
 + *
 + * Stops all tasklets and timers, unregisters the device from all
 + * subsystems it was registered to, deallocates the device structure
 + * @cs and all structures referenced from it.
 + * Operations on the device should be stopped before calling this.
 + */
 +void gigaset_freecs(struct cardstate *cs)
 +{
 +      int i;
 +      unsigned long flags;
 +
 +      if (!cs)
 +              return;
 +
 +      mutex_lock(&cs->mutex);
 +
 +      spin_lock_irqsave(&cs->lock, flags);
 +      cs->running = 0;
 +      spin_unlock_irqrestore(&cs->lock, flags); /* event handler and timer are
 +                                                   not rescheduled below */
 +
 +      tasklet_kill(&cs->event_tasklet);
 +      del_timer_sync(&cs->timer);
 +
 +      switch (cs->cs_init) {
 +      default:
 +              /* clear B channel structures */
 +              for (i = 0; i < cs->channels; ++i) {
 +                      gig_dbg(DEBUG_INIT, "clearing bcs[%d]", i);
 +                      gigaset_freebcs(cs->bcs + i);
 +              }
 +
 +              /* clear device sysfs */
 +              gigaset_free_dev_sysfs(cs);
 +
 +              gigaset_if_free(cs);
 +
 +              gig_dbg(DEBUG_INIT, "clearing hw");
 +              cs->ops->freecshw(cs);
 +
 +              /* fall through */
 +      case 2: /* error in initcshw */
 +              /* Deregister from LL */
 +              make_invalid(cs, VALID_ID);
 +              gigaset_isdn_unregdev(cs);
 +
 +              /* fall through */
 +      case 1: /* error when registering to LL */
 +              gig_dbg(DEBUG_INIT, "clearing at_state");
 +              clear_at_state(&cs->at_state);
 +              dealloc_temp_at_states(cs);
 +              clear_events(cs);
 +              tty_port_destroy(&cs->port);
 +
 +              /* fall through */
 +      case 0: /* error in basic setup */
 +              gig_dbg(DEBUG_INIT, "freeing inbuf");
 +              kfree(cs->inbuf);
 +              kfree(cs->bcs);
 +      }
 +
 +      mutex_unlock(&cs->mutex);
 +      free_cs(cs);
 +}
 +EXPORT_SYMBOL_GPL(gigaset_freecs);
 +
 +void gigaset_at_init(struct at_state_t *at_state, struct bc_state *bcs,
 +                   struct cardstate *cs, int cid)
 +{
 +      int i;
 +
 +      INIT_LIST_HEAD(&at_state->list);
 +      at_state->waiting = 0;
 +      at_state->getstring = 0;
 +      at_state->pending_commands = 0;
 +      at_state->timer_expires = 0;
 +      at_state->timer_active = 0;
 +      at_state->timer_index = 0;
 +      at_state->seq_index = 0;
 +      at_state->ConState = 0;
 +      for (i = 0; i < STR_NUM; ++i)
 +              at_state->str_var[i] = NULL;
 +      at_state->int_var[VAR_ZDLE] = 0;
 +      at_state->int_var[VAR_ZCTP] = -1;
 +      at_state->int_var[VAR_ZSAU] = ZSAU_NULL;
 +      at_state->cs = cs;
 +      at_state->bcs = bcs;
 +      at_state->cid = cid;
 +      if (!cid)
 +              at_state->replystruct = cs->tabnocid;
 +      else
 +              at_state->replystruct = cs->tabcid;
 +}
 +
 +
 +static void gigaset_inbuf_init(struct inbuf_t *inbuf, struct cardstate *cs)
 +/* inbuf->read must be allocated before! */
 +{
 +      inbuf->head = 0;
 +      inbuf->tail = 0;
 +      inbuf->cs = cs;
 +      inbuf->inputstate = INS_command;
 +}
 +
 +/**
 + * gigaset_fill_inbuf() - append received data to input buffer
 + * @inbuf:    buffer structure.
 + * @src:      received data.
 + * @numbytes: number of bytes received.
 + *
 + * Return value: !=0 if some data was appended
 + */
 +int gigaset_fill_inbuf(struct inbuf_t *inbuf, const unsigned char *src,
 +                     unsigned numbytes)
 +{
 +      unsigned n, head, tail, bytesleft;
 +
 +      gig_dbg(DEBUG_INTR, "received %u bytes", numbytes);
 +
 +      if (!numbytes)
 +              return 0;
 +
 +      bytesleft = numbytes;
 +      tail = inbuf->tail;
 +      head = inbuf->head;
 +      gig_dbg(DEBUG_INTR, "buffer state: %u -> %u", head, tail);
 +
 +      while (bytesleft) {
 +              if (head > tail)
 +                      n = head - 1 - tail;
 +              else if (head == 0)
 +                      n = (RBUFSIZE - 1) - tail;
 +              else
 +                      n = RBUFSIZE - tail;
 +              if (!n) {
 +                      dev_err(inbuf->cs->dev,
 +                              "buffer overflow (%u bytes lost)\n",
 +                              bytesleft);
 +                      break;
 +              }
 +              if (n > bytesleft)
 +                      n = bytesleft;
 +              memcpy(inbuf->data + tail, src, n);
 +              bytesleft -= n;
 +              tail = (tail + n) % RBUFSIZE;
 +              src += n;
 +      }
 +      gig_dbg(DEBUG_INTR, "setting tail to %u", tail);
 +      inbuf->tail = tail;
 +      return numbytes != bytesleft;
 +}
 +EXPORT_SYMBOL_GPL(gigaset_fill_inbuf);
 +
 +/* Initialize the b-channel structure */
 +static int gigaset_initbcs(struct bc_state *bcs, struct cardstate *cs,
 +                         int channel)
 +{
 +      int i;
 +
 +      bcs->tx_skb = NULL;
 +
 +      skb_queue_head_init(&bcs->squeue);
 +
 +      bcs->corrupted = 0;
 +      bcs->trans_down = 0;
 +      bcs->trans_up = 0;
 +
 +      gig_dbg(DEBUG_INIT, "setting up bcs[%d]->at_state", channel);
 +      gigaset_at_init(&bcs->at_state, bcs, cs, -1);
 +
 +#ifdef CONFIG_GIGASET_DEBUG
 +      bcs->emptycount = 0;
 +#endif
 +
 +      bcs->rx_bufsize = 0;
 +      bcs->rx_skb = NULL;
 +      bcs->rx_fcs = PPP_INITFCS;
 +      bcs->inputstate = 0;
 +      bcs->channel = channel;
 +      bcs->cs = cs;
 +
 +      bcs->chstate = 0;
 +      bcs->use_count = 1;
 +      bcs->busy = 0;
 +      bcs->ignore = cs->ignoreframes;
 +
 +      for (i = 0; i < AT_NUM; ++i)
 +              bcs->commands[i] = NULL;
 +
 +      spin_lock_init(&bcs->aplock);
 +      bcs->ap = NULL;
 +      bcs->apconnstate = 0;
 +
 +      gig_dbg(DEBUG_INIT, "  setting up bcs[%d]->hw", channel);
 +      return cs->ops->initbcshw(bcs);
 +}
 +
 +/**
 + * gigaset_initcs() - initialize device structure
 + * @drv:      hardware driver the device belongs to
 + * @channels: number of B channels supported by device
 + * @onechannel:       !=0 if B channel data and AT commands share one
 + *                communication channel (M10x),
 + *            ==0 if B channels have separate communication channels (base)
 + * @ignoreframes:     number of frames to ignore after setting up B channel
 + * @cidmode:  !=0: start in CallID mode
 + * @modulename:       name of driver module for LL registration
 + *
 + * Allocate and initialize cardstate structure for Gigaset driver
 + * Calls hardware dependent gigaset_initcshw() function
 + * Calls B channel initialization function gigaset_initbcs() for each B channel
 + *
 + * Return value:
 + *    pointer to cardstate structure
 + */
 +struct cardstate *gigaset_initcs(struct gigaset_driver *drv, int channels,
 +                               int onechannel, int ignoreframes,
 +                               int cidmode, const char *modulename)
 +{
 +      struct cardstate *cs;
 +      unsigned long flags;
 +      int i;
 +
 +      gig_dbg(DEBUG_INIT, "allocating cs");
 +      cs = alloc_cs(drv);
 +      if (!cs) {
 +              pr_err("maximum number of devices exceeded\n");
 +              return NULL;
 +      }
 +
 +      cs->cs_init = 0;
 +      cs->channels = channels;
 +      cs->onechannel = onechannel;
 +      cs->ignoreframes = ignoreframes;
 +      INIT_LIST_HEAD(&cs->temp_at_states);
 +      cs->running = 0;
 +      timer_setup(&cs->timer, timer_tick, 0);
 +      spin_lock_init(&cs->ev_lock);
 +      cs->ev_tail = 0;
 +      cs->ev_head = 0;
 +
 +      tasklet_init(&cs->event_tasklet, gigaset_handle_event,
 +                   (unsigned long) cs);
 +      tty_port_init(&cs->port);
 +      cs->commands_pending = 0;
 +      cs->cur_at_seq = 0;
 +      cs->gotfwver = -1;
 +      cs->dev = NULL;
 +      cs->tty_dev = NULL;
 +      cs->cidmode = cidmode != 0;
 +      cs->tabnocid = gigaset_tab_nocid;
 +      cs->tabcid = gigaset_tab_cid;
 +
 +      init_waitqueue_head(&cs->waitqueue);
 +      cs->waiting = 0;
 +
 +      cs->mode = M_UNKNOWN;
 +      cs->mstate = MS_UNINITIALIZED;
 +
 +      cs->bcs = kmalloc_array(channels, sizeof(struct bc_state), GFP_KERNEL);
 +      cs->inbuf = kmalloc(sizeof(struct inbuf_t), GFP_KERNEL);
 +      if (!cs->bcs || !cs->inbuf) {
 +              pr_err("out of memory\n");
 +              goto error;
 +      }
 +      ++cs->cs_init;
 +
 +      gig_dbg(DEBUG_INIT, "setting up at_state");
 +      spin_lock_init(&cs->lock);
 +      gigaset_at_init(&cs->at_state, NULL, cs, 0);
 +      cs->dle = 0;
 +      cs->cbytes = 0;
 +
 +      gig_dbg(DEBUG_INIT, "setting up inbuf");
 +      gigaset_inbuf_init(cs->inbuf, cs);
 +
 +      cs->connected = 0;
 +      cs->isdn_up = 0;
 +
 +      gig_dbg(DEBUG_INIT, "setting up cmdbuf");
 +      cs->cmdbuf = cs->lastcmdbuf = NULL;
 +      spin_lock_init(&cs->cmdlock);
 +      cs->curlen = 0;
 +      cs->cmdbytes = 0;
 +
 +      gig_dbg(DEBUG_INIT, "setting up iif");
 +      if (gigaset_isdn_regdev(cs, modulename) < 0) {
 +              pr_err("error registering ISDN device\n");
 +              goto error;
 +      }
 +
 +      make_valid(cs, VALID_ID);
 +      ++cs->cs_init;
 +      gig_dbg(DEBUG_INIT, "setting up hw");
 +      if (cs->ops->initcshw(cs) < 0)
 +              goto error;
 +
 +      ++cs->cs_init;
 +
 +      /* set up character device */
 +      gigaset_if_init(cs);
 +
 +      /* set up device sysfs */
 +      gigaset_init_dev_sysfs(cs);
 +
 +      /* set up channel data structures */
 +      for (i = 0; i < channels; ++i) {
 +              gig_dbg(DEBUG_INIT, "setting up bcs[%d]", i);
 +              if (gigaset_initbcs(cs->bcs + i, cs, i) < 0) {
 +                      pr_err("could not allocate channel %d data\n", i);
 +                      goto error;
 +              }
 +      }
 +
 +      spin_lock_irqsave(&cs->lock, flags);
 +      cs->running = 1;
 +      spin_unlock_irqrestore(&cs->lock, flags);
 +      cs->timer.expires = jiffies + msecs_to_jiffies(GIG_TICK);
 +      add_timer(&cs->timer);
 +
 +      gig_dbg(DEBUG_INIT, "cs initialized");
 +      return cs;
 +
 +error:
 +      gig_dbg(DEBUG_INIT, "failed");
 +      gigaset_freecs(cs);
 +      return NULL;
 +}
 +EXPORT_SYMBOL_GPL(gigaset_initcs);
 +
 +/* ReInitialize the b-channel structure on hangup */
 +void gigaset_bcs_reinit(struct bc_state *bcs)
 +{
 +      struct sk_buff *skb;
 +      struct cardstate *cs = bcs->cs;
 +      unsigned long flags;
 +
 +      while ((skb = skb_dequeue(&bcs->squeue)) != NULL)
 +              dev_kfree_skb(skb);
 +
 +      spin_lock_irqsave(&cs->lock, flags);
 +      clear_at_state(&bcs->at_state);
 +      bcs->at_state.ConState = 0;
 +      bcs->at_state.timer_active = 0;
 +      bcs->at_state.timer_expires = 0;
 +      bcs->at_state.cid = -1;                 /* No CID defined */
 +      spin_unlock_irqrestore(&cs->lock, flags);
 +
 +      bcs->inputstate = 0;
 +
 +#ifdef CONFIG_GIGASET_DEBUG
 +      bcs->emptycount = 0;
 +#endif
 +
 +      bcs->rx_fcs = PPP_INITFCS;
 +      bcs->chstate = 0;
 +
 +      bcs->ignore = cs->ignoreframes;
 +      dev_kfree_skb(bcs->rx_skb);
 +      bcs->rx_skb = NULL;
 +
 +      cs->ops->reinitbcshw(bcs);
 +}
 +
 +static void cleanup_cs(struct cardstate *cs)
 +{
 +      struct cmdbuf_t *cb, *tcb;
 +      int i;
 +      unsigned long flags;
 +
 +      spin_lock_irqsave(&cs->lock, flags);
 +
 +      cs->mode = M_UNKNOWN;
 +      cs->mstate = MS_UNINITIALIZED;
 +
 +      clear_at_state(&cs->at_state);
 +      dealloc_temp_at_states(cs);
 +      gigaset_at_init(&cs->at_state, NULL, cs, 0);
 +
 +      cs->inbuf->inputstate = INS_command;
 +      cs->inbuf->head = 0;
 +      cs->inbuf->tail = 0;
 +
 +      cb = cs->cmdbuf;
 +      while (cb) {
 +              tcb = cb;
 +              cb = cb->next;
 +              kfree(tcb);
 +      }
 +      cs->cmdbuf = cs->lastcmdbuf = NULL;
 +      cs->curlen = 0;
 +      cs->cmdbytes = 0;
 +      cs->gotfwver = -1;
 +      cs->dle = 0;
 +      cs->cur_at_seq = 0;
 +      cs->commands_pending = 0;
 +      cs->cbytes = 0;
 +
 +      spin_unlock_irqrestore(&cs->lock, flags);
 +
 +      for (i = 0; i < cs->channels; ++i) {
 +              gigaset_freebcs(cs->bcs + i);
 +              if (gigaset_initbcs(cs->bcs + i, cs, i) < 0)
 +                      pr_err("could not allocate channel %d data\n", i);
 +      }
 +
 +      if (cs->waiting) {
 +              cs->cmd_result = -ENODEV;
 +              cs->waiting = 0;
 +              wake_up_interruptible(&cs->waitqueue);
 +      }
 +}
 +
 +
 +/**
 + * gigaset_start() - start device operations
 + * @cs:               device descriptor structure.
 + *
 + * Prepares the device for use by setting up communication parameters,
 + * scheduling an EV_START event to initiate device initialization, and
 + * waiting for completion of the initialization.
 + *
 + * Return value:
 + *    0 on success, error code < 0 on failure
 + */
 +int gigaset_start(struct cardstate *cs)
 +{
 +      unsigned long flags;
 +
 +      if (mutex_lock_interruptible(&cs->mutex))
 +              return -EBUSY;
 +
 +      spin_lock_irqsave(&cs->lock, flags);
 +      cs->connected = 1;
 +      spin_unlock_irqrestore(&cs->lock, flags);
 +
 +      if (cs->mstate != MS_LOCKED) {
 +              cs->ops->set_modem_ctrl(cs, 0, TIOCM_DTR | TIOCM_RTS);
 +              cs->ops->baud_rate(cs, B115200);
 +              cs->ops->set_line_ctrl(cs, CS8);
 +              cs->control_state = TIOCM_DTR | TIOCM_RTS;
 +      }
 +
 +      cs->waiting = 1;
 +
 +      if (!gigaset_add_event(cs, &cs->at_state, EV_START, NULL, 0, NULL)) {
 +              cs->waiting = 0;
 +              goto error;
 +      }
 +      gigaset_schedule_event(cs);
 +
 +      wait_event(cs->waitqueue, !cs->waiting);
 +
 +      mutex_unlock(&cs->mutex);
 +      return 0;
 +
 +error:
 +      mutex_unlock(&cs->mutex);
 +      return -ENOMEM;
 +}
 +EXPORT_SYMBOL_GPL(gigaset_start);
 +
 +/**
 + * gigaset_shutdown() - shut down device operations
 + * @cs:               device descriptor structure.
 + *
 + * Deactivates the device by scheduling an EV_SHUTDOWN event and
 + * waiting for completion of the shutdown.
 + *
 + * Return value:
 + *    0 - success, -ENODEV - error (no device associated)
 + */
 +int gigaset_shutdown(struct cardstate *cs)
 +{
 +      mutex_lock(&cs->mutex);
 +
 +      if (!(cs->flags & VALID_MINOR)) {
 +              mutex_unlock(&cs->mutex);
 +              return -ENODEV;
 +      }
 +
 +      cs->waiting = 1;
 +
 +      if (!gigaset_add_event(cs, &cs->at_state, EV_SHUTDOWN, NULL, 0, NULL))
 +              goto exit;
 +      gigaset_schedule_event(cs);
 +
 +      wait_event(cs->waitqueue, !cs->waiting);
 +
 +      cleanup_cs(cs);
 +
 +exit:
 +      mutex_unlock(&cs->mutex);
 +      return 0;
 +}
 +EXPORT_SYMBOL_GPL(gigaset_shutdown);
 +
 +/**
 + * gigaset_stop() - stop device operations
 + * @cs:               device descriptor structure.
 + *
 + * Stops operations on the device by scheduling an EV_STOP event and
 + * waiting for completion of the shutdown.
 + */
 +void gigaset_stop(struct cardstate *cs)
 +{
 +      mutex_lock(&cs->mutex);
 +
 +      cs->waiting = 1;
 +
 +      if (!gigaset_add_event(cs, &cs->at_state, EV_STOP, NULL, 0, NULL))
 +              goto exit;
 +      gigaset_schedule_event(cs);
 +
 +      wait_event(cs->waitqueue, !cs->waiting);
 +
 +      cleanup_cs(cs);
 +
 +exit:
 +      mutex_unlock(&cs->mutex);
 +}
 +EXPORT_SYMBOL_GPL(gigaset_stop);
 +
 +static LIST_HEAD(drivers);
 +static DEFINE_SPINLOCK(driver_lock);
 +
 +struct cardstate *gigaset_get_cs_by_id(int id)
 +{
 +      unsigned long flags;
 +      struct cardstate *ret = NULL;
 +      struct cardstate *cs;
 +      struct gigaset_driver *drv;
 +      unsigned i;
 +
 +      spin_lock_irqsave(&driver_lock, flags);
 +      list_for_each_entry(drv, &drivers, list) {
 +              spin_lock(&drv->lock);
 +              for (i = 0; i < drv->minors; ++i) {
 +                      cs = drv->cs + i;
 +                      if ((cs->flags & VALID_ID) && cs->myid == id) {
 +                              ret = cs;
 +                              break;
 +                      }
 +              }
 +              spin_unlock(&drv->lock);
 +              if (ret)
 +                      break;
 +      }
 +      spin_unlock_irqrestore(&driver_lock, flags);
 +      return ret;
 +}
 +
 +static struct cardstate *gigaset_get_cs_by_minor(unsigned minor)
 +{
 +      unsigned long flags;
 +      struct cardstate *ret = NULL;
 +      struct gigaset_driver *drv;
 +      unsigned index;
 +
 +      spin_lock_irqsave(&driver_lock, flags);
 +      list_for_each_entry(drv, &drivers, list) {
 +              if (minor < drv->minor || minor >= drv->minor + drv->minors)
 +                      continue;
 +              index = minor - drv->minor;
 +              spin_lock(&drv->lock);
 +              if (drv->cs[index].flags & VALID_MINOR)
 +                      ret = drv->cs + index;
 +              spin_unlock(&drv->lock);
 +              if (ret)
 +                      break;
 +      }
 +      spin_unlock_irqrestore(&driver_lock, flags);
 +      return ret;
 +}
 +
 +struct cardstate *gigaset_get_cs_by_tty(struct tty_struct *tty)
 +{
 +      return gigaset_get_cs_by_minor(tty->index + tty->driver->minor_start);
 +}
 +
 +/**
 + * gigaset_freedriver() - free all associated ressources of a driver
 + * @drv:      driver descriptor structure.
 + *
 + * Unregisters the driver from the system and deallocates the driver
 + * structure @drv and all structures referenced from it.
 + * All devices should be shut down before calling this.
 + */
 +void gigaset_freedriver(struct gigaset_driver *drv)
 +{
 +      unsigned long flags;
 +
 +      spin_lock_irqsave(&driver_lock, flags);
 +      list_del(&drv->list);
 +      spin_unlock_irqrestore(&driver_lock, flags);
 +
 +      gigaset_if_freedriver(drv);
 +
 +      kfree(drv->cs);
 +      kfree(drv);
 +}
 +EXPORT_SYMBOL_GPL(gigaset_freedriver);
 +
 +/**
 + * gigaset_initdriver() - initialize driver structure
 + * @minor:    First minor number
 + * @minors:   Number of minors this driver can handle
 + * @procname: Name of the driver
 + * @devname:  Name of the device files (prefix without minor number)
 + *
 + * Allocate and initialize gigaset_driver structure. Initialize interface.
 + *
 + * Return value:
 + *    Pointer to the gigaset_driver structure on success, NULL on failure.
 + */
 +struct gigaset_driver *gigaset_initdriver(unsigned minor, unsigned minors,
 +                                        const char *procname,
 +                                        const char *devname,
 +                                        const struct gigaset_ops *ops,
 +                                        struct module *owner)
 +{
 +      struct gigaset_driver *drv;
 +      unsigned long flags;
 +      unsigned i;
 +
 +      drv = kmalloc(sizeof *drv, GFP_KERNEL);
 +      if (!drv)
 +              return NULL;
 +
 +      drv->have_tty = 0;
 +      drv->minor = minor;
 +      drv->minors = minors;
 +      spin_lock_init(&drv->lock);
 +      drv->blocked = 0;
 +      drv->ops = ops;
 +      drv->owner = owner;
 +      INIT_LIST_HEAD(&drv->list);
 +
 +      drv->cs = kmalloc_array(minors, sizeof(*drv->cs), GFP_KERNEL);
 +      if (!drv->cs)
 +              goto error;
 +
 +      for (i = 0; i < minors; ++i) {
 +              drv->cs[i].flags = 0;
 +              drv->cs[i].driver = drv;
 +              drv->cs[i].ops = drv->ops;
 +              drv->cs[i].minor_index = i;
 +              mutex_init(&drv->cs[i].mutex);
 +      }
 +
 +      gigaset_if_initdriver(drv, procname, devname);
 +
 +      spin_lock_irqsave(&driver_lock, flags);
 +      list_add(&drv->list, &drivers);
 +      spin_unlock_irqrestore(&driver_lock, flags);
 +
 +      return drv;
 +
 +error:
 +      kfree(drv);
 +      return NULL;
 +}
 +EXPORT_SYMBOL_GPL(gigaset_initdriver);
 +
 +/**
 + * gigaset_blockdriver() - block driver
 + * @drv:      driver descriptor structure.
 + *
 + * Prevents the driver from attaching new devices, in preparation for
 + * deregistration.
 + */
 +void gigaset_blockdriver(struct gigaset_driver *drv)
 +{
 +      drv->blocked = 1;
 +}
 +EXPORT_SYMBOL_GPL(gigaset_blockdriver);
 +
 +static int __init gigaset_init_module(void)
 +{
 +      /* in accordance with the principle of least astonishment,
 +       * setting the 'debug' parameter to 1 activates a sensible
 +       * set of default debug levels
 +       */
 +      if (gigaset_debuglevel == 1)
 +              gigaset_debuglevel = DEBUG_DEFAULT;
 +
 +      pr_info(DRIVER_DESC DRIVER_DESC_DEBUG "\n");
 +      gigaset_isdn_regdrv();
 +      return 0;
 +}
 +
 +static void __exit gigaset_exit_module(void)
 +{
 +      gigaset_isdn_unregdrv();
 +}
 +
 +module_init(gigaset_init_module);
 +module_exit(gigaset_exit_module);
 +
 +MODULE_AUTHOR(DRIVER_AUTHOR);
 +MODULE_DESCRIPTION(DRIVER_DESC);
 +
 +MODULE_LICENSE("GPL");
index 570c2d5,0000000..4b9637e
mode 100644,000000..100644
--- /dev/null
@@@ -1,77 -1,0 +1,74 @@@
-  *    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.
++// SPDX-License-Identifier: GPL-2.0-or-later
 +/*
 + * Dummy LL interface for the Gigaset driver
 + *
 + * Copyright (c) 2009 by Tilman Schmidt <tilman@imap.cc>.
 + *
 + * =====================================================================
 + * =====================================================================
 + */
 +
 +#include <linux/export.h>
 +#include "gigaset.h"
 +
 +void gigaset_skb_sent(struct bc_state *bcs, struct sk_buff *skb)
 +{
 +}
 +EXPORT_SYMBOL_GPL(gigaset_skb_sent);
 +
 +void gigaset_skb_rcvd(struct bc_state *bcs, struct sk_buff *skb)
 +{
 +}
 +EXPORT_SYMBOL_GPL(gigaset_skb_rcvd);
 +
 +void gigaset_isdn_rcv_err(struct bc_state *bcs)
 +{
 +}
 +EXPORT_SYMBOL_GPL(gigaset_isdn_rcv_err);
 +
 +int gigaset_isdn_icall(struct at_state_t *at_state)
 +{
 +      return ICALL_IGNORE;
 +}
 +
 +void gigaset_isdn_connD(struct bc_state *bcs)
 +{
 +}
 +
 +void gigaset_isdn_hupD(struct bc_state *bcs)
 +{
 +}
 +
 +void gigaset_isdn_connB(struct bc_state *bcs)
 +{
 +}
 +
 +void gigaset_isdn_hupB(struct bc_state *bcs)
 +{
 +}
 +
 +void gigaset_isdn_start(struct cardstate *cs)
 +{
 +}
 +
 +void gigaset_isdn_stop(struct cardstate *cs)
 +{
 +}
 +
 +int gigaset_isdn_regdev(struct cardstate *cs, const char *isdnid)
 +{
 +      return 0;
 +}
 +
 +void gigaset_isdn_unregdev(struct cardstate *cs)
 +{
 +}
 +
 +void gigaset_isdn_regdrv(void)
 +{
 +      pr_info("no ISDN subsystem interface\n");
 +}
 +
 +void gigaset_isdn_unregdrv(void)
 +{
 +}
index 182826e,0000000..f8bb186
mode 100644,000000..100644
--- /dev/null
@@@ -1,1913 -1,0 +1,1910 @@@
-  *    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.
++// SPDX-License-Identifier: GPL-2.0-or-later
 +/*
 + * Stuff used by all variants of the driver
 + *
 + * Copyright (c) 2001 by Stefan Eilers,
 + *                       Hansjoerg Lipp <hjlipp@web.de>,
 + *                       Tilman Schmidt <tilman@imap.cc>.
 + *
 + * =====================================================================
 + * =====================================================================
 + */
 +
 +#include <linux/export.h>
 +#include "gigaset.h"
 +
 +/* ========================================================== */
 +/* bit masks for pending commands */
 +#define PC_DIAL               0x001
 +#define PC_HUP                0x002
 +#define PC_INIT               0x004
 +#define PC_DLE0               0x008
 +#define PC_DLE1               0x010
 +#define PC_SHUTDOWN   0x020
 +#define PC_ACCEPT     0x040
 +#define PC_CID                0x080
 +#define PC_NOCID      0x100
 +#define PC_CIDMODE    0x200
 +#define PC_UMMODE     0x400
 +
 +/* types of modem responses */
 +#define RT_NOTHING    0
 +#define RT_ZSAU               1
 +#define RT_RING               2
 +#define RT_NUMBER     3
 +#define RT_STRING     4
 +#define RT_ZCAU               6
 +
 +/* Possible ASCII responses */
 +#define RSP_OK                0
 +#define RSP_ERROR     1
 +#define RSP_ZGCI      3
 +#define RSP_RING      4
 +#define RSP_ZVLS      5
 +#define RSP_ZCAU      6
 +
 +/* responses with values to store in at_state */
 +/* - numeric */
 +#define RSP_VAR               100
 +#define RSP_ZSAU      (RSP_VAR + VAR_ZSAU)
 +#define RSP_ZDLE      (RSP_VAR + VAR_ZDLE)
 +#define RSP_ZCTP      (RSP_VAR + VAR_ZCTP)
 +/* - string */
 +#define RSP_STR               (RSP_VAR + VAR_NUM)
 +#define RSP_NMBR      (RSP_STR + STR_NMBR)
 +#define RSP_ZCPN      (RSP_STR + STR_ZCPN)
 +#define RSP_ZCON      (RSP_STR + STR_ZCON)
 +#define RSP_ZBC               (RSP_STR + STR_ZBC)
 +#define RSP_ZHLC      (RSP_STR + STR_ZHLC)
 +
 +#define RSP_WRONG_CID -2      /* unknown cid in cmd */
 +#define RSP_INVAL     -6      /* invalid response   */
 +#define RSP_NODEV     -9      /* device not connected */
 +
 +#define RSP_NONE      -19
 +#define RSP_STRING    -20
 +#define RSP_NULL      -21
 +#define RSP_INIT      -27
 +#define RSP_ANY               -26
 +#define RSP_LAST      -28
 +
 +/* actions for process_response */
 +#define ACT_NOTHING           0
 +#define ACT_SETDLE1           1
 +#define ACT_SETDLE0           2
 +#define ACT_FAILINIT          3
 +#define ACT_HUPMODEM          4
 +#define ACT_CONFIGMODE                5
 +#define ACT_INIT              6
 +#define ACT_DLE0              7
 +#define ACT_DLE1              8
 +#define ACT_FAILDLE0          9
 +#define ACT_FAILDLE1          10
 +#define ACT_RING              11
 +#define ACT_CID                       12
 +#define ACT_FAILCID           13
 +#define ACT_SDOWN             14
 +#define ACT_FAILSDOWN         15
 +#define ACT_DEBUG             16
 +#define ACT_WARN              17
 +#define ACT_DIALING           18
 +#define ACT_ABORTDIAL         19
 +#define ACT_DISCONNECT                20
 +#define ACT_CONNECT           21
 +#define ACT_REMOTEREJECT      22
 +#define ACT_CONNTIMEOUT               23
 +#define ACT_REMOTEHUP         24
 +#define ACT_ABORTHUP          25
 +#define ACT_ICALL             26
 +#define ACT_ACCEPTED          27
 +#define ACT_ABORTACCEPT               28
 +#define ACT_TIMEOUT           29
 +#define ACT_GETSTRING         30
 +#define ACT_SETVER            31
 +#define ACT_FAILVER           32
 +#define ACT_GOTVER            33
 +#define ACT_TEST              34
 +#define ACT_ERROR             35
 +#define ACT_ABORTCID          36
 +#define ACT_ZCAU              37
 +#define ACT_NOTIFY_BC_DOWN    38
 +#define ACT_NOTIFY_BC_UP      39
 +#define ACT_DIAL              40
 +#define ACT_ACCEPT            41
 +#define ACT_HUP                       43
 +#define ACT_IF_LOCK           44
 +#define ACT_START             45
 +#define ACT_STOP              46
 +#define ACT_FAKEDLE0          47
 +#define ACT_FAKEHUP           48
 +#define ACT_FAKESDOWN         49
 +#define ACT_SHUTDOWN          50
 +#define ACT_PROC_CIDMODE      51
 +#define ACT_UMODESET          52
 +#define ACT_FAILUMODE         53
 +#define ACT_CMODESET          54
 +#define ACT_FAILCMODE         55
 +#define ACT_IF_VER            56
 +#define ACT_CMD                       100
 +
 +/* at command sequences */
 +#define SEQ_NONE      0
 +#define SEQ_INIT      100
 +#define SEQ_DLE0      200
 +#define SEQ_DLE1      250
 +#define SEQ_CID               300
 +#define SEQ_NOCID     350
 +#define SEQ_HUP               400
 +#define SEQ_DIAL      600
 +#define SEQ_ACCEPT    720
 +#define SEQ_SHUTDOWN  500
 +#define SEQ_CIDMODE   10
 +#define SEQ_UMMODE    11
 +
 +
 +/* 100: init, 200: dle0, 250:dle1, 300: get cid (dial), 350: "hup" (no cid),
 + * 400: hup, 500: reset, 600: dial, 700: ring */
 +struct reply_t gigaset_tab_nocid[] =
 +{
 +/* resp_code, min_ConState, max_ConState, parameter, new_ConState, timeout,
 + * action, command */
 +
 +/* initialize device, set cid mode if possible */
 +      {RSP_INIT,       -1,  -1, SEQ_INIT,     100,  1, {ACT_TIMEOUT} },
 +
 +      {EV_TIMEOUT,    100, 100, -1,           101,  3, {0},   "Z\r"},
 +      {RSP_OK,        101, 103, -1,           120,  5, {ACT_GETSTRING},
 +                                                              "+GMR\r"},
 +
 +      {EV_TIMEOUT,    101, 101, -1,           102,  5, {0},   "Z\r"},
 +      {RSP_ERROR,     101, 101, -1,           102,  5, {0},   "Z\r"},
 +
 +      {EV_TIMEOUT,    102, 102, -1,           108,  5, {ACT_SETDLE1},
 +                                                              "^SDLE=0\r"},
 +      {RSP_OK,        108, 108, -1,           104, -1},
 +      {RSP_ZDLE,      104, 104,  0,           103,  5, {0},   "Z\r"},
 +      {EV_TIMEOUT,    104, 104, -1,             0,  0, {ACT_FAILINIT} },
 +      {RSP_ERROR,     108, 108, -1,             0,  0, {ACT_FAILINIT} },
 +
 +      {EV_TIMEOUT,    108, 108, -1,           105,  2, {ACT_SETDLE0,
 +                                                        ACT_HUPMODEM,
 +                                                        ACT_TIMEOUT} },
 +      {EV_TIMEOUT,    105, 105, -1,           103,  5, {0},   "Z\r"},
 +
 +      {RSP_ERROR,     102, 102, -1,           107,  5, {0},   "^GETPRE\r"},
 +      {RSP_OK,        107, 107, -1,             0,  0, {ACT_CONFIGMODE} },
 +      {RSP_ERROR,     107, 107, -1,             0,  0, {ACT_FAILINIT} },
 +      {EV_TIMEOUT,    107, 107, -1,             0,  0, {ACT_FAILINIT} },
 +
 +      {RSP_ERROR,     103, 103, -1,             0,  0, {ACT_FAILINIT} },
 +      {EV_TIMEOUT,    103, 103, -1,             0,  0, {ACT_FAILINIT} },
 +
 +      {RSP_STRING,    120, 120, -1,           121, -1, {ACT_SETVER} },
 +
 +      {EV_TIMEOUT,    120, 121, -1,             0,  0, {ACT_FAILVER,
 +                                                        ACT_INIT} },
 +      {RSP_ERROR,     120, 121, -1,             0,  0, {ACT_FAILVER,
 +                                                        ACT_INIT} },
 +      {RSP_OK,        121, 121, -1,             0,  0, {ACT_GOTVER,
 +                                                        ACT_INIT} },
 +      {RSP_NONE,      121, 121, -1,           120,  0, {ACT_GETSTRING} },
 +
 +/* leave dle mode */
 +      {RSP_INIT,        0,   0, SEQ_DLE0,     201,  5, {0},   "^SDLE=0\r"},
 +      {RSP_OK,        201, 201, -1,           202, -1},
 +      {RSP_ZDLE,      202, 202,  0,             0,  0, {ACT_DLE0} },
 +      {RSP_NODEV,     200, 249, -1,             0,  0, {ACT_FAKEDLE0} },
 +      {RSP_ERROR,     200, 249, -1,             0,  0, {ACT_FAILDLE0} },
 +      {EV_TIMEOUT,    200, 249, -1,             0,  0, {ACT_FAILDLE0} },
 +
 +/* enter dle mode */
 +      {RSP_INIT,        0,   0, SEQ_DLE1,     251,  5, {0},   "^SDLE=1\r"},
 +      {RSP_OK,        251, 251, -1,           252, -1},
 +      {RSP_ZDLE,      252, 252,  1,             0,  0, {ACT_DLE1} },
 +      {RSP_ERROR,     250, 299, -1,             0,  0, {ACT_FAILDLE1} },
 +      {EV_TIMEOUT,    250, 299, -1,             0,  0, {ACT_FAILDLE1} },
 +
 +/* incoming call */
 +      {RSP_RING,       -1,  -1, -1,            -1, -1, {ACT_RING} },
 +
 +/* get cid */
 +      {RSP_INIT,        0,   0, SEQ_CID,      301,  5, {0},   "^SGCI?\r"},
 +      {RSP_OK,        301, 301, -1,           302, -1},
 +      {RSP_ZGCI,      302, 302, -1,             0,  0, {ACT_CID} },
 +      {RSP_ERROR,     301, 349, -1,             0,  0, {ACT_FAILCID} },
 +      {EV_TIMEOUT,    301, 349, -1,             0,  0, {ACT_FAILCID} },
 +
 +/* enter cid mode */
 +      {RSP_INIT,        0,   0, SEQ_CIDMODE,  150,  5, {0},   "^SGCI=1\r"},
 +      {RSP_OK,        150, 150, -1,             0,  0, {ACT_CMODESET} },
 +      {RSP_ERROR,     150, 150, -1,             0,  0, {ACT_FAILCMODE} },
 +      {EV_TIMEOUT,    150, 150, -1,             0,  0, {ACT_FAILCMODE} },
 +
 +/* leave cid mode */
 +      {RSP_INIT,        0,   0, SEQ_UMMODE,   160,  5, {0},   "Z\r"},
 +      {RSP_OK,        160, 160, -1,             0,  0, {ACT_UMODESET} },
 +      {RSP_ERROR,     160, 160, -1,             0,  0, {ACT_FAILUMODE} },
 +      {EV_TIMEOUT,    160, 160, -1,             0,  0, {ACT_FAILUMODE} },
 +
 +/* abort getting cid */
 +      {RSP_INIT,        0,   0, SEQ_NOCID,      0,  0, {ACT_ABORTCID} },
 +
 +/* reset */
 +      {RSP_INIT,        0,   0, SEQ_SHUTDOWN, 504,  5, {0},   "Z\r"},
 +      {RSP_OK,        504, 504, -1,             0,  0, {ACT_SDOWN} },
 +      {RSP_ERROR,     501, 599, -1,             0,  0, {ACT_FAILSDOWN} },
 +      {EV_TIMEOUT,    501, 599, -1,             0,  0, {ACT_FAILSDOWN} },
 +      {RSP_NODEV,     501, 599, -1,             0,  0, {ACT_FAKESDOWN} },
 +
 +      {EV_PROC_CIDMODE, -1, -1, -1,            -1, -1, {ACT_PROC_CIDMODE} },
 +      {EV_IF_LOCK,     -1,  -1, -1,            -1, -1, {ACT_IF_LOCK} },
 +      {EV_IF_VER,      -1,  -1, -1,            -1, -1, {ACT_IF_VER} },
 +      {EV_START,       -1,  -1, -1,            -1, -1, {ACT_START} },
 +      {EV_STOP,        -1,  -1, -1,            -1, -1, {ACT_STOP} },
 +      {EV_SHUTDOWN,    -1,  -1, -1,            -1, -1, {ACT_SHUTDOWN} },
 +
 +/* misc. */
 +      {RSP_ERROR,      -1,  -1, -1,            -1, -1, {ACT_ERROR} },
 +      {RSP_ZCAU,       -1,  -1, -1,            -1, -1, {ACT_ZCAU} },
 +      {RSP_NONE,       -1,  -1, -1,            -1, -1, {ACT_DEBUG} },
 +      {RSP_ANY,        -1,  -1, -1,            -1, -1, {ACT_WARN} },
 +      {RSP_LAST}
 +};
 +
 +/* 600: start dialing, 650: dial in progress, 800: connection is up, 700: ring,
 + * 400: hup, 750: accepted icall */
 +struct reply_t gigaset_tab_cid[] =
 +{
 +/* resp_code, min_ConState, max_ConState, parameter, new_ConState, timeout,
 + * action, command */
 +
 +/* dial */
 +      {EV_DIAL,        -1,  -1, -1,            -1, -1, {ACT_DIAL} },
 +      {RSP_INIT,        0,   0, SEQ_DIAL,     601,  5, {ACT_CMD + AT_BC} },
 +      {RSP_OK,        601, 601, -1,           603,  5, {ACT_CMD + AT_PROTO} },
 +      {RSP_OK,        603, 603, -1,           604,  5, {ACT_CMD + AT_TYPE} },
 +      {RSP_OK,        604, 604, -1,           605,  5, {ACT_CMD + AT_MSN} },
 +      {RSP_NULL,      605, 605, -1,           606,  5, {ACT_CMD + AT_CLIP} },
 +      {RSP_OK,        605, 605, -1,           606,  5, {ACT_CMD + AT_CLIP} },
 +      {RSP_NULL,      606, 606, -1,           607,  5, {ACT_CMD + AT_ISO} },
 +      {RSP_OK,        606, 606, -1,           607,  5, {ACT_CMD + AT_ISO} },
 +      {RSP_OK,        607, 607, -1,           608,  5, {0},   "+VLS=17\r"},
 +      {RSP_OK,        608, 608, -1,           609, -1},
 +      {RSP_ZSAU,      609, 609, ZSAU_PROCEEDING, 610, 5, {ACT_CMD + AT_DIAL} },
 +      {RSP_OK,        610, 610, -1,           650,  0, {ACT_DIALING} },
 +
 +      {RSP_ERROR,     601, 610, -1,             0,  0, {ACT_ABORTDIAL} },
 +      {EV_TIMEOUT,    601, 610, -1,             0,  0, {ACT_ABORTDIAL} },
 +
 +/* optional dialing responses */
 +      {EV_BC_OPEN,    650, 650, -1,           651, -1},
 +      {RSP_ZVLS,      609, 651, 17,            -1, -1, {ACT_DEBUG} },
 +      {RSP_ZCTP,      610, 651, -1,            -1, -1, {ACT_DEBUG} },
 +      {RSP_ZCPN,      610, 651, -1,            -1, -1, {ACT_DEBUG} },
 +      {RSP_ZSAU,      650, 651, ZSAU_CALL_DELIVERED, -1, -1, {ACT_DEBUG} },
 +
 +/* connect */
 +      {RSP_ZSAU,      650, 650, ZSAU_ACTIVE,  800, -1, {ACT_CONNECT} },
 +      {RSP_ZSAU,      651, 651, ZSAU_ACTIVE,  800, -1, {ACT_CONNECT,
 +                                                        ACT_NOTIFY_BC_UP} },
 +      {RSP_ZSAU,      750, 750, ZSAU_ACTIVE,  800, -1, {ACT_CONNECT} },
 +      {RSP_ZSAU,      751, 751, ZSAU_ACTIVE,  800, -1, {ACT_CONNECT,
 +                                                        ACT_NOTIFY_BC_UP} },
 +      {EV_BC_OPEN,    800, 800, -1,           800, -1, {ACT_NOTIFY_BC_UP} },
 +
 +/* remote hangup */
 +      {RSP_ZSAU,      650, 651, ZSAU_DISCONNECT_IND, 0, 0, {ACT_REMOTEREJECT} },
 +      {RSP_ZSAU,      750, 751, ZSAU_DISCONNECT_IND, 0, 0, {ACT_REMOTEHUP} },
 +      {RSP_ZSAU,      800, 800, ZSAU_DISCONNECT_IND, 0, 0, {ACT_REMOTEHUP} },
 +
 +/* hangup */
 +      {EV_HUP,         -1,  -1, -1,            -1, -1, {ACT_HUP} },
 +      {RSP_INIT,       -1,  -1, SEQ_HUP,      401,  5, {0},   "+VLS=0\r"},
 +      {RSP_OK,        401, 401, -1,           402,  5},
 +      {RSP_ZVLS,      402, 402,  0,           403,  5},
 +      {RSP_ZSAU,      403, 403, ZSAU_DISCONNECT_REQ, -1, -1, {ACT_DEBUG} },
 +      {RSP_ZSAU,      403, 403, ZSAU_NULL,      0,  0, {ACT_DISCONNECT} },
 +      {RSP_NODEV,     401, 403, -1,             0,  0, {ACT_FAKEHUP} },
 +      {RSP_ERROR,     401, 401, -1,             0,  0, {ACT_ABORTHUP} },
 +      {EV_TIMEOUT,    401, 403, -1,             0,  0, {ACT_ABORTHUP} },
 +
 +      {EV_BC_CLOSED,    0,   0, -1,             0, -1, {ACT_NOTIFY_BC_DOWN} },
 +
 +/* ring */
 +      {RSP_ZBC,       700, 700, -1,            -1, -1, {0} },
 +      {RSP_ZHLC,      700, 700, -1,            -1, -1, {0} },
 +      {RSP_NMBR,      700, 700, -1,            -1, -1, {0} },
 +      {RSP_ZCPN,      700, 700, -1,            -1, -1, {0} },
 +      {RSP_ZCTP,      700, 700, -1,            -1, -1, {0} },
 +      {EV_TIMEOUT,    700, 700, -1,           720, 720, {ACT_ICALL} },
 +      {EV_BC_CLOSED,  720, 720, -1,             0, -1, {ACT_NOTIFY_BC_DOWN} },
 +
 +/*accept icall*/
 +      {EV_ACCEPT,      -1,  -1, -1,            -1, -1, {ACT_ACCEPT} },
 +      {RSP_INIT,      720, 720, SEQ_ACCEPT,   721,  5, {ACT_CMD + AT_PROTO} },
 +      {RSP_OK,        721, 721, -1,           722,  5, {ACT_CMD + AT_ISO} },
 +      {RSP_OK,        722, 722, -1,           723,  5, {0},   "+VLS=17\r"},
 +      {RSP_OK,        723, 723, -1,           724,  5, {0} },
 +      {RSP_ZVLS,      724, 724, 17,           750, 50, {ACT_ACCEPTED} },
 +      {RSP_ERROR,     721, 729, -1,             0,  0, {ACT_ABORTACCEPT} },
 +      {EV_TIMEOUT,    721, 729, -1,             0,  0, {ACT_ABORTACCEPT} },
 +      {RSP_ZSAU,      700, 729, ZSAU_NULL,      0,  0, {ACT_ABORTACCEPT} },
 +      {RSP_ZSAU,      700, 729, ZSAU_ACTIVE,    0,  0, {ACT_ABORTACCEPT} },
 +      {RSP_ZSAU,      700, 729, ZSAU_DISCONNECT_IND, 0, 0, {ACT_ABORTACCEPT} },
 +
 +      {EV_BC_OPEN,    750, 750, -1,           751, -1},
 +      {EV_TIMEOUT,    750, 751, -1,             0,  0, {ACT_CONNTIMEOUT} },
 +
 +/* B channel closed (general case) */
 +      {EV_BC_CLOSED,   -1,  -1, -1,            -1, -1, {ACT_NOTIFY_BC_DOWN} },
 +
 +/* misc. */
 +      {RSP_ZCON,       -1,  -1, -1,            -1, -1, {ACT_DEBUG} },
 +      {RSP_ZCAU,       -1,  -1, -1,            -1, -1, {ACT_ZCAU} },
 +      {RSP_NONE,       -1,  -1, -1,            -1, -1, {ACT_DEBUG} },
 +      {RSP_ANY,        -1,  -1, -1,            -1, -1, {ACT_WARN} },
 +      {RSP_LAST}
 +};
 +
 +
 +static const struct resp_type_t {
 +      char    *response;
 +      int     resp_code;
 +      int     type;
 +}
 +resp_type[] =
 +{
 +      {"OK",          RSP_OK,         RT_NOTHING},
 +      {"ERROR",       RSP_ERROR,      RT_NOTHING},
 +      {"ZSAU",        RSP_ZSAU,       RT_ZSAU},
 +      {"ZCAU",        RSP_ZCAU,       RT_ZCAU},
 +      {"RING",        RSP_RING,       RT_RING},
 +      {"ZGCI",        RSP_ZGCI,       RT_NUMBER},
 +      {"ZVLS",        RSP_ZVLS,       RT_NUMBER},
 +      {"ZCTP",        RSP_ZCTP,       RT_NUMBER},
 +      {"ZDLE",        RSP_ZDLE,       RT_NUMBER},
 +      {"ZHLC",        RSP_ZHLC,       RT_STRING},
 +      {"ZBC",         RSP_ZBC,        RT_STRING},
 +      {"NMBR",        RSP_NMBR,       RT_STRING},
 +      {"ZCPN",        RSP_ZCPN,       RT_STRING},
 +      {"ZCON",        RSP_ZCON,       RT_STRING},
 +      {NULL,          0,              0}
 +};
 +
 +static const struct zsau_resp_t {
 +      char    *str;
 +      int     code;
 +}
 +zsau_resp[] =
 +{
 +      {"OUTGOING_CALL_PROCEEDING",    ZSAU_PROCEEDING},
 +      {"CALL_DELIVERED",              ZSAU_CALL_DELIVERED},
 +      {"ACTIVE",                      ZSAU_ACTIVE},
 +      {"DISCONNECT_IND",              ZSAU_DISCONNECT_IND},
 +      {"NULL",                        ZSAU_NULL},
 +      {"DISCONNECT_REQ",              ZSAU_DISCONNECT_REQ},
 +      {NULL,                          ZSAU_UNKNOWN}
 +};
 +
 +/* check for and remove fixed string prefix
 + * If s starts with prefix terminated by a non-alphanumeric character,
 + * return pointer to the first character after that, otherwise return NULL.
 + */
 +static char *skip_prefix(char *s, const char *prefix)
 +{
 +      while (*prefix)
 +              if (*s++ != *prefix++)
 +                      return NULL;
 +      if (isalnum(*s))
 +              return NULL;
 +      return s;
 +}
 +
 +/* queue event with CID */
 +static void add_cid_event(struct cardstate *cs, int cid, int type,
 +                        void *ptr, int parameter)
 +{
 +      unsigned long flags;
 +      unsigned next, tail;
 +      struct event_t *event;
 +
 +      gig_dbg(DEBUG_EVENT, "queueing event %d for cid %d", type, cid);
 +
 +      spin_lock_irqsave(&cs->ev_lock, flags);
 +
 +      tail = cs->ev_tail;
 +      next = (tail + 1) % MAX_EVENTS;
 +      if (unlikely(next == cs->ev_head)) {
 +              dev_err(cs->dev, "event queue full\n");
 +              kfree(ptr);
 +      } else {
 +              event = cs->events + tail;
 +              event->type = type;
 +              event->cid = cid;
 +              event->ptr = ptr;
 +              event->arg = NULL;
 +              event->parameter = parameter;
 +              event->at_state = NULL;
 +              cs->ev_tail = next;
 +      }
 +
 +      spin_unlock_irqrestore(&cs->ev_lock, flags);
 +}
 +
 +/**
 + * gigaset_handle_modem_response() - process received modem response
 + * @cs:               device descriptor structure.
 + *
 + * Called by asyncdata/isocdata if a block of data received from the
 + * device must be processed as a modem command response. The data is
 + * already in the cs structure.
 + */
 +void gigaset_handle_modem_response(struct cardstate *cs)
 +{
 +      char *eoc, *psep, *ptr;
 +      const struct resp_type_t *rt;
 +      const struct zsau_resp_t *zr;
 +      int cid, parameter;
 +      u8 type, value;
 +
 +      if (!cs->cbytes) {
 +              /* ignore additional LFs/CRs (M10x config mode or cx100) */
 +              gig_dbg(DEBUG_MCMD, "skipped EOL [%02X]", cs->respdata[0]);
 +              return;
 +      }
 +      cs->respdata[cs->cbytes] = 0;
 +
 +      if (cs->at_state.getstring) {
 +              /* state machine wants next line verbatim */
 +              cs->at_state.getstring = 0;
 +              ptr = kstrdup(cs->respdata, GFP_ATOMIC);
 +              gig_dbg(DEBUG_EVENT, "string==%s", ptr ? ptr : "NULL");
 +              add_cid_event(cs, 0, RSP_STRING, ptr, 0);
 +              return;
 +      }
 +
 +      /* look up response type */
 +      for (rt = resp_type; rt->response; ++rt) {
 +              eoc = skip_prefix(cs->respdata, rt->response);
 +              if (eoc)
 +                      break;
 +      }
 +      if (!rt->response) {
 +              add_cid_event(cs, 0, RSP_NONE, NULL, 0);
 +              gig_dbg(DEBUG_EVENT, "unknown modem response: '%s'\n",
 +                      cs->respdata);
 +              return;
 +      }
 +
 +      /* check for CID */
 +      psep = strrchr(cs->respdata, ';');
 +      if (psep &&
 +          !kstrtoint(psep + 1, 10, &cid) &&
 +          cid >= 1 && cid <= 65535) {
 +              /* valid CID: chop it off */
 +              *psep = 0;
 +      } else {
 +              /* no valid CID: leave unchanged */
 +              cid = 0;
 +      }
 +
 +      gig_dbg(DEBUG_EVENT, "CMD received: %s", cs->respdata);
 +      if (cid)
 +              gig_dbg(DEBUG_EVENT, "CID: %d", cid);
 +
 +      switch (rt->type) {
 +      case RT_NOTHING:
 +              /* check parameter separator */
 +              if (*eoc)
 +                      goto bad_param; /* extra parameter */
 +
 +              add_cid_event(cs, cid, rt->resp_code, NULL, 0);
 +              break;
 +
 +      case RT_RING:
 +              /* check parameter separator */
 +              if (!*eoc)
 +                      eoc = NULL;     /* no parameter */
 +              else if (*eoc++ != ',')
 +                      goto bad_param;
 +
 +              add_cid_event(cs, 0, rt->resp_code, NULL, cid);
 +
 +              /* process parameters as individual responses */
 +              while (eoc) {
 +                      /* look up parameter type */
 +                      psep = NULL;
 +                      for (rt = resp_type; rt->response; ++rt) {
 +                              psep = skip_prefix(eoc, rt->response);
 +                              if (psep)
 +                                      break;
 +                      }
 +
 +                      /* all legal parameters are of type RT_STRING */
 +                      if (!psep || rt->type != RT_STRING) {
 +                              dev_warn(cs->dev,
 +                                       "illegal RING parameter: '%s'\n",
 +                                       eoc);
 +                              return;
 +                      }
 +
 +                      /* skip parameter value separator */
 +                      if (*psep++ != '=')
 +                              goto bad_param;
 +
 +                      /* look up end of parameter */
 +                      eoc = strchr(psep, ',');
 +                      if (eoc)
 +                              *eoc++ = 0;
 +
 +                      /* retrieve parameter value */
 +                      ptr = kstrdup(psep, GFP_ATOMIC);
 +
 +                      /* queue event */
 +                      add_cid_event(cs, cid, rt->resp_code, ptr, 0);
 +              }
 +              break;
 +
 +      case RT_ZSAU:
 +              /* check parameter separator */
 +              if (!*eoc) {
 +                      /* no parameter */
 +                      add_cid_event(cs, cid, rt->resp_code, NULL, ZSAU_NONE);
 +                      break;
 +              }
 +              if (*eoc++ != '=')
 +                      goto bad_param;
 +
 +              /* look up parameter value */
 +              for (zr = zsau_resp; zr->str; ++zr)
 +                      if (!strcmp(eoc, zr->str))
 +                              break;
 +              if (!zr->str)
 +                      goto bad_param;
 +
 +              add_cid_event(cs, cid, rt->resp_code, NULL, zr->code);
 +              break;
 +
 +      case RT_STRING:
 +              /* check parameter separator */
 +              if (*eoc++ != '=')
 +                      goto bad_param;
 +
 +              /* retrieve parameter value */
 +              ptr = kstrdup(eoc, GFP_ATOMIC);
 +
 +              /* queue event */
 +              add_cid_event(cs, cid, rt->resp_code, ptr, 0);
 +              break;
 +
 +      case RT_ZCAU:
 +              /* check parameter separators */
 +              if (*eoc++ != '=')
 +                      goto bad_param;
 +              psep = strchr(eoc, ',');
 +              if (!psep)
 +                      goto bad_param;
 +              *psep++ = 0;
 +
 +              /* decode parameter values */
 +              if (kstrtou8(eoc, 16, &type) || kstrtou8(psep, 16, &value)) {
 +                      *--psep = ',';
 +                      goto bad_param;
 +              }
 +              parameter = (type << 8) | value;
 +
 +              add_cid_event(cs, cid, rt->resp_code, NULL, parameter);
 +              break;
 +
 +      case RT_NUMBER:
 +              /* check parameter separator */
 +              if (*eoc++ != '=')
 +                      goto bad_param;
 +
 +              /* decode parameter value */
 +              if (kstrtoint(eoc, 10, &parameter))
 +                      goto bad_param;
 +
 +              /* special case ZDLE: set flag before queueing event */
 +              if (rt->resp_code == RSP_ZDLE)
 +                      cs->dle = parameter;
 +
 +              add_cid_event(cs, cid, rt->resp_code, NULL, parameter);
 +              break;
 +
 +bad_param:
 +              /* parameter unexpected, incomplete or malformed */
 +              dev_warn(cs->dev, "bad parameter in response '%s'\n",
 +                       cs->respdata);
 +              add_cid_event(cs, cid, rt->resp_code, NULL, -1);
 +              break;
 +
 +      default:
 +              dev_err(cs->dev, "%s: internal error on '%s'\n",
 +                      __func__, cs->respdata);
 +      }
 +}
 +EXPORT_SYMBOL_GPL(gigaset_handle_modem_response);
 +
 +/* disconnect_nobc
 + * process closing of connection associated with given AT state structure
 + * without B channel
 + */
 +static void disconnect_nobc(struct at_state_t **at_state_p,
 +                          struct cardstate *cs)
 +{
 +      unsigned long flags;
 +
 +      spin_lock_irqsave(&cs->lock, flags);
 +      ++(*at_state_p)->seq_index;
 +
 +      /* revert to selected idle mode */
 +      if (!cs->cidmode) {
 +              cs->at_state.pending_commands |= PC_UMMODE;
 +              gig_dbg(DEBUG_EVENT, "Scheduling PC_UMMODE");
 +              cs->commands_pending = 1;
 +      }
 +
 +      /* check for and deallocate temporary AT state */
 +      if (!list_empty(&(*at_state_p)->list)) {
 +              list_del(&(*at_state_p)->list);
 +              kfree(*at_state_p);
 +              *at_state_p = NULL;
 +      }
 +
 +      spin_unlock_irqrestore(&cs->lock, flags);
 +}
 +
 +/* disconnect_bc
 + * process closing of connection associated with given AT state structure
 + * and B channel
 + */
 +static void disconnect_bc(struct at_state_t *at_state,
 +                        struct cardstate *cs, struct bc_state *bcs)
 +{
 +      unsigned long flags;
 +
 +      spin_lock_irqsave(&cs->lock, flags);
 +      ++at_state->seq_index;
 +
 +      /* revert to selected idle mode */
 +      if (!cs->cidmode) {
 +              cs->at_state.pending_commands |= PC_UMMODE;
 +              gig_dbg(DEBUG_EVENT, "Scheduling PC_UMMODE");
 +              cs->commands_pending = 1;
 +      }
 +      spin_unlock_irqrestore(&cs->lock, flags);
 +
 +      /* invoke hardware specific handler */
 +      cs->ops->close_bchannel(bcs);
 +
 +      /* notify LL */
 +      if (bcs->chstate & (CHS_D_UP | CHS_NOTIFY_LL)) {
 +              bcs->chstate &= ~(CHS_D_UP | CHS_NOTIFY_LL);
 +              gigaset_isdn_hupD(bcs);
 +      }
 +}
 +
 +/* get_free_channel
 + * get a free AT state structure: either one of those associated with the
 + * B channels of the Gigaset device, or if none of those is available,
 + * a newly allocated one with bcs=NULL
 + * The structure should be freed by calling disconnect_nobc() after use.
 + */
 +static inline struct at_state_t *get_free_channel(struct cardstate *cs,
 +                                                int cid)
 +/* cids: >0: siemens-cid
 + *        0: without cid
 + *       -1: no cid assigned yet
 + */
 +{
 +      unsigned long flags;
 +      int i;
 +      struct at_state_t *ret;
 +
 +      for (i = 0; i < cs->channels; ++i)
 +              if (gigaset_get_channel(cs->bcs + i) >= 0) {
 +                      ret = &cs->bcs[i].at_state;
 +                      ret->cid = cid;
 +                      return ret;
 +              }
 +
 +      spin_lock_irqsave(&cs->lock, flags);
 +      ret = kmalloc(sizeof(struct at_state_t), GFP_ATOMIC);
 +      if (ret) {
 +              gigaset_at_init(ret, NULL, cs, cid);
 +              list_add(&ret->list, &cs->temp_at_states);
 +      }
 +      spin_unlock_irqrestore(&cs->lock, flags);
 +      return ret;
 +}
 +
 +static void init_failed(struct cardstate *cs, int mode)
 +{
 +      int i;
 +      struct at_state_t *at_state;
 +
 +      cs->at_state.pending_commands &= ~PC_INIT;
 +      cs->mode = mode;
 +      cs->mstate = MS_UNINITIALIZED;
 +      gigaset_free_channels(cs);
 +      for (i = 0; i < cs->channels; ++i) {
 +              at_state = &cs->bcs[i].at_state;
 +              if (at_state->pending_commands & PC_CID) {
 +                      at_state->pending_commands &= ~PC_CID;
 +                      at_state->pending_commands |= PC_NOCID;
 +                      cs->commands_pending = 1;
 +              }
 +      }
 +}
 +
 +static void schedule_init(struct cardstate *cs, int state)
 +{
 +      if (cs->at_state.pending_commands & PC_INIT) {
 +              gig_dbg(DEBUG_EVENT, "not scheduling PC_INIT again");
 +              return;
 +      }
 +      cs->mstate = state;
 +      cs->mode = M_UNKNOWN;
 +      gigaset_block_channels(cs);
 +      cs->at_state.pending_commands |= PC_INIT;
 +      gig_dbg(DEBUG_EVENT, "Scheduling PC_INIT");
 +      cs->commands_pending = 1;
 +}
 +
 +/* send an AT command
 + * adding the "AT" prefix, cid and DLE encapsulation as appropriate
 + */
 +static void send_command(struct cardstate *cs, const char *cmd,
 +                       struct at_state_t *at_state)
 +{
 +      int cid = at_state->cid;
 +      struct cmdbuf_t *cb;
 +      size_t buflen;
 +
 +      buflen = strlen(cmd) + 12; /* DLE ( A T 1 2 3 4 5 <cmd> DLE ) \0 */
 +      cb = kmalloc(sizeof(struct cmdbuf_t) + buflen, GFP_ATOMIC);
 +      if (!cb) {
 +              dev_err(cs->dev, "%s: out of memory\n", __func__);
 +              return;
 +      }
 +      if (cid > 0 && cid <= 65535)
 +              cb->len = snprintf(cb->buf, buflen,
 +                                 cs->dle ? "\020(AT%d%s\020)" : "AT%d%s",
 +                                 cid, cmd);
 +      else
 +              cb->len = snprintf(cb->buf, buflen,
 +                                 cs->dle ? "\020(AT%s\020)" : "AT%s",
 +                                 cmd);
 +      cb->offset = 0;
 +      cb->next = NULL;
 +      cb->wake_tasklet = NULL;
 +      cs->ops->write_cmd(cs, cb);
 +}
 +
 +static struct at_state_t *at_state_from_cid(struct cardstate *cs, int cid)
 +{
 +      struct at_state_t *at_state;
 +      int i;
 +      unsigned long flags;
 +
 +      if (cid == 0)
 +              return &cs->at_state;
 +
 +      for (i = 0; i < cs->channels; ++i)
 +              if (cid == cs->bcs[i].at_state.cid)
 +                      return &cs->bcs[i].at_state;
 +
 +      spin_lock_irqsave(&cs->lock, flags);
 +
 +      list_for_each_entry(at_state, &cs->temp_at_states, list)
 +              if (cid == at_state->cid) {
 +                      spin_unlock_irqrestore(&cs->lock, flags);
 +                      return at_state;
 +              }
 +
 +      spin_unlock_irqrestore(&cs->lock, flags);
 +
 +      return NULL;
 +}
 +
 +static void bchannel_down(struct bc_state *bcs)
 +{
 +      if (bcs->chstate & CHS_B_UP) {
 +              bcs->chstate &= ~CHS_B_UP;
 +              gigaset_isdn_hupB(bcs);
 +      }
 +
 +      if (bcs->chstate & (CHS_D_UP | CHS_NOTIFY_LL)) {
 +              bcs->chstate &= ~(CHS_D_UP | CHS_NOTIFY_LL);
 +              gigaset_isdn_hupD(bcs);
 +      }
 +
 +      gigaset_free_channel(bcs);
 +
 +      gigaset_bcs_reinit(bcs);
 +}
 +
 +static void bchannel_up(struct bc_state *bcs)
 +{
 +      if (bcs->chstate & CHS_B_UP) {
 +              dev_notice(bcs->cs->dev, "%s: B channel already up\n",
 +                         __func__);
 +              return;
 +      }
 +
 +      bcs->chstate |= CHS_B_UP;
 +      gigaset_isdn_connB(bcs);
 +}
 +
 +static void start_dial(struct at_state_t *at_state, void *data,
 +                     unsigned seq_index)
 +{
 +      struct bc_state *bcs = at_state->bcs;
 +      struct cardstate *cs = at_state->cs;
 +      char **commands = data;
 +      unsigned long flags;
 +      int i;
 +
 +      bcs->chstate |= CHS_NOTIFY_LL;
 +
 +      spin_lock_irqsave(&cs->lock, flags);
 +      if (at_state->seq_index != seq_index) {
 +              spin_unlock_irqrestore(&cs->lock, flags);
 +              goto error;
 +      }
 +      spin_unlock_irqrestore(&cs->lock, flags);
 +
 +      for (i = 0; i < AT_NUM; ++i) {
 +              kfree(bcs->commands[i]);
 +              bcs->commands[i] = commands[i];
 +      }
 +
 +      at_state->pending_commands |= PC_CID;
 +      gig_dbg(DEBUG_EVENT, "Scheduling PC_CID");
 +      cs->commands_pending = 1;
 +      return;
 +
 +error:
 +      for (i = 0; i < AT_NUM; ++i) {
 +              kfree(commands[i]);
 +              commands[i] = NULL;
 +      }
 +      at_state->pending_commands |= PC_NOCID;
 +      gig_dbg(DEBUG_EVENT, "Scheduling PC_NOCID");
 +      cs->commands_pending = 1;
 +      return;
 +}
 +
 +static void start_accept(struct at_state_t *at_state)
 +{
 +      struct cardstate *cs = at_state->cs;
 +      struct bc_state *bcs = at_state->bcs;
 +      int i;
 +
 +      for (i = 0; i < AT_NUM; ++i) {
 +              kfree(bcs->commands[i]);
 +              bcs->commands[i] = NULL;
 +      }
 +
 +      bcs->commands[AT_PROTO] = kmalloc(9, GFP_ATOMIC);
 +      bcs->commands[AT_ISO] = kmalloc(9, GFP_ATOMIC);
 +      if (!bcs->commands[AT_PROTO] || !bcs->commands[AT_ISO]) {
 +              dev_err(at_state->cs->dev, "out of memory\n");
 +              /* error reset */
 +              at_state->pending_commands |= PC_HUP;
 +              gig_dbg(DEBUG_EVENT, "Scheduling PC_HUP");
 +              cs->commands_pending = 1;
 +              return;
 +      }
 +
 +      snprintf(bcs->commands[AT_PROTO], 9, "^SBPR=%u\r", bcs->proto2);
 +      snprintf(bcs->commands[AT_ISO], 9, "^SISO=%u\r", bcs->channel + 1);
 +
 +      at_state->pending_commands |= PC_ACCEPT;
 +      gig_dbg(DEBUG_EVENT, "Scheduling PC_ACCEPT");
 +      cs->commands_pending = 1;
 +}
 +
 +static void do_start(struct cardstate *cs)
 +{
 +      gigaset_free_channels(cs);
 +
 +      if (cs->mstate != MS_LOCKED)
 +              schedule_init(cs, MS_INIT);
 +
 +      cs->isdn_up = 1;
 +      gigaset_isdn_start(cs);
 +
 +      cs->waiting = 0;
 +      wake_up(&cs->waitqueue);
 +}
 +
 +static void finish_shutdown(struct cardstate *cs)
 +{
 +      if (cs->mstate != MS_LOCKED) {
 +              cs->mstate = MS_UNINITIALIZED;
 +              cs->mode = M_UNKNOWN;
 +      }
 +
 +      /* Tell the LL that the device is not available .. */
 +      if (cs->isdn_up) {
 +              cs->isdn_up = 0;
 +              gigaset_isdn_stop(cs);
 +      }
 +
 +      /* The rest is done by cleanup_cs() in process context. */
 +
 +      cs->cmd_result = -ENODEV;
 +      cs->waiting = 0;
 +      wake_up(&cs->waitqueue);
 +}
 +
 +static void do_shutdown(struct cardstate *cs)
 +{
 +      gigaset_block_channels(cs);
 +
 +      if (cs->mstate == MS_READY) {
 +              cs->mstate = MS_SHUTDOWN;
 +              cs->at_state.pending_commands |= PC_SHUTDOWN;
 +              gig_dbg(DEBUG_EVENT, "Scheduling PC_SHUTDOWN");
 +              cs->commands_pending = 1;
 +      } else
 +              finish_shutdown(cs);
 +}
 +
 +static void do_stop(struct cardstate *cs)
 +{
 +      unsigned long flags;
 +
 +      spin_lock_irqsave(&cs->lock, flags);
 +      cs->connected = 0;
 +      spin_unlock_irqrestore(&cs->lock, flags);
 +
 +      do_shutdown(cs);
 +}
 +
 +/* Entering cid mode or getting a cid failed:
 + * try to initialize the device and try again.
 + *
 + * channel >= 0: getting cid for the channel failed
 + * channel < 0:  entering cid mode failed
 + *
 + * returns 0 on success, <0 on failure
 + */
 +static int reinit_and_retry(struct cardstate *cs, int channel)
 +{
 +      int i;
 +
 +      if (--cs->retry_count <= 0)
 +              return -EFAULT;
 +
 +      for (i = 0; i < cs->channels; ++i)
 +              if (cs->bcs[i].at_state.cid > 0)
 +                      return -EBUSY;
 +
 +      if (channel < 0)
 +              dev_warn(cs->dev,
 +                       "Could not enter cid mode. Reinit device and try again.\n");
 +      else {
 +              dev_warn(cs->dev,
 +                       "Could not get a call id. Reinit device and try again.\n");
 +              cs->bcs[channel].at_state.pending_commands |= PC_CID;
 +      }
 +      schedule_init(cs, MS_INIT);
 +      return 0;
 +}
 +
 +static int at_state_invalid(struct cardstate *cs,
 +                          struct at_state_t *test_ptr)
 +{
 +      unsigned long flags;
 +      unsigned channel;
 +      struct at_state_t *at_state;
 +      int retval = 0;
 +
 +      spin_lock_irqsave(&cs->lock, flags);
 +
 +      if (test_ptr == &cs->at_state)
 +              goto exit;
 +
 +      list_for_each_entry(at_state, &cs->temp_at_states, list)
 +              if (at_state == test_ptr)
 +                      goto exit;
 +
 +      for (channel = 0; channel < cs->channels; ++channel)
 +              if (&cs->bcs[channel].at_state == test_ptr)
 +                      goto exit;
 +
 +      retval = 1;
 +exit:
 +      spin_unlock_irqrestore(&cs->lock, flags);
 +      return retval;
 +}
 +
 +static void handle_icall(struct cardstate *cs, struct bc_state *bcs,
 +                       struct at_state_t *at_state)
 +{
 +      int retval;
 +
 +      retval = gigaset_isdn_icall(at_state);
 +      switch (retval) {
 +      case ICALL_ACCEPT:
 +              break;
 +      default:
 +              dev_err(cs->dev, "internal error: disposition=%d\n", retval);
 +              /* fall through */
 +      case ICALL_IGNORE:
 +      case ICALL_REJECT:
 +              /* hang up actively
 +               * Device doc says that would reject the call.
 +               * In fact it doesn't.
 +               */
 +              at_state->pending_commands |= PC_HUP;
 +              cs->commands_pending = 1;
 +              break;
 +      }
 +}
 +
 +static int do_lock(struct cardstate *cs)
 +{
 +      int mode;
 +      int i;
 +
 +      switch (cs->mstate) {
 +      case MS_UNINITIALIZED:
 +      case MS_READY:
 +              if (cs->cur_at_seq || !list_empty(&cs->temp_at_states) ||
 +                  cs->at_state.pending_commands)
 +                      return -EBUSY;
 +
 +              for (i = 0; i < cs->channels; ++i)
 +                      if (cs->bcs[i].at_state.pending_commands)
 +                              return -EBUSY;
 +
 +              if (gigaset_get_channels(cs) < 0)
 +                      return -EBUSY;
 +
 +              break;
 +      case MS_LOCKED:
 +              break;
 +      default:
 +              return -EBUSY;
 +      }
 +
 +      mode = cs->mode;
 +      cs->mstate = MS_LOCKED;
 +      cs->mode = M_UNKNOWN;
 +
 +      return mode;
 +}
 +
 +static int do_unlock(struct cardstate *cs)
 +{
 +      if (cs->mstate != MS_LOCKED)
 +              return -EINVAL;
 +
 +      cs->mstate = MS_UNINITIALIZED;
 +      cs->mode = M_UNKNOWN;
 +      gigaset_free_channels(cs);
 +      if (cs->connected)
 +              schedule_init(cs, MS_INIT);
 +
 +      return 0;
 +}
 +
 +static void do_action(int action, struct cardstate *cs,
 +                    struct bc_state *bcs,
 +                    struct at_state_t **p_at_state, char **pp_command,
 +                    int *p_genresp, int *p_resp_code,
 +                    struct event_t *ev)
 +{
 +      struct at_state_t *at_state = *p_at_state;
 +      struct bc_state *bcs2;
 +      unsigned long flags;
 +
 +      int channel;
 +
 +      unsigned char *s, *e;
 +      int i;
 +      unsigned long val;
 +
 +      switch (action) {
 +      case ACT_NOTHING:
 +              break;
 +      case ACT_TIMEOUT:
 +              at_state->waiting = 1;
 +              break;
 +      case ACT_INIT:
 +              cs->at_state.pending_commands &= ~PC_INIT;
 +              cs->cur_at_seq = SEQ_NONE;
 +              cs->mode = M_UNIMODEM;
 +              spin_lock_irqsave(&cs->lock, flags);
 +              if (!cs->cidmode) {
 +                      spin_unlock_irqrestore(&cs->lock, flags);
 +                      gigaset_free_channels(cs);
 +                      cs->mstate = MS_READY;
 +                      break;
 +              }
 +              spin_unlock_irqrestore(&cs->lock, flags);
 +              cs->at_state.pending_commands |= PC_CIDMODE;
 +              gig_dbg(DEBUG_EVENT, "Scheduling PC_CIDMODE");
 +              cs->commands_pending = 1;
 +              break;
 +      case ACT_FAILINIT:
 +              dev_warn(cs->dev, "Could not initialize the device.\n");
 +              cs->dle = 0;
 +              init_failed(cs, M_UNKNOWN);
 +              cs->cur_at_seq = SEQ_NONE;
 +              break;
 +      case ACT_CONFIGMODE:
 +              init_failed(cs, M_CONFIG);
 +              cs->cur_at_seq = SEQ_NONE;
 +              break;
 +      case ACT_SETDLE1:
 +              cs->dle = 1;
 +              /* cs->inbuf[0].inputstate |= INS_command | INS_DLE_command; */
 +              cs->inbuf[0].inputstate &=
 +                      ~(INS_command | INS_DLE_command);
 +              break;
 +      case ACT_SETDLE0:
 +              cs->dle = 0;
 +              cs->inbuf[0].inputstate =
 +                      (cs->inbuf[0].inputstate & ~INS_DLE_command)
 +                      | INS_command;
 +              break;
 +      case ACT_CMODESET:
 +              if (cs->mstate == MS_INIT || cs->mstate == MS_RECOVER) {
 +                      gigaset_free_channels(cs);
 +                      cs->mstate = MS_READY;
 +              }
 +              cs->mode = M_CID;
 +              cs->cur_at_seq = SEQ_NONE;
 +              break;
 +      case ACT_UMODESET:
 +              cs->mode = M_UNIMODEM;
 +              cs->cur_at_seq = SEQ_NONE;
 +              break;
 +      case ACT_FAILCMODE:
 +              cs->cur_at_seq = SEQ_NONE;
 +              if (cs->mstate == MS_INIT || cs->mstate == MS_RECOVER) {
 +                      init_failed(cs, M_UNKNOWN);
 +                      break;
 +              }
 +              if (reinit_and_retry(cs, -1) < 0)
 +                      schedule_init(cs, MS_RECOVER);
 +              break;
 +      case ACT_FAILUMODE:
 +              cs->cur_at_seq = SEQ_NONE;
 +              schedule_init(cs, MS_RECOVER);
 +              break;
 +      case ACT_HUPMODEM:
 +              /* send "+++" (hangup in unimodem mode) */
 +              if (cs->connected) {
 +                      struct cmdbuf_t *cb;
 +
 +                      cb = kmalloc(sizeof(struct cmdbuf_t) + 3, GFP_ATOMIC);
 +                      if (!cb) {
 +                              dev_err(cs->dev, "%s: out of memory\n",
 +                                      __func__);
 +                              return;
 +                      }
 +                      memcpy(cb->buf, "+++", 3);
 +                      cb->len = 3;
 +                      cb->offset = 0;
 +                      cb->next = NULL;
 +                      cb->wake_tasklet = NULL;
 +                      cs->ops->write_cmd(cs, cb);
 +              }
 +              break;
 +      case ACT_RING:
 +              /* get fresh AT state structure for new CID */
 +              at_state = get_free_channel(cs, ev->parameter);
 +              if (!at_state) {
 +                      dev_warn(cs->dev,
 +                               "RING ignored: could not allocate channel structure\n");
 +                      break;
 +              }
 +
 +              /* initialize AT state structure
 +               * note that bcs may be NULL if no B channel is free
 +               */
 +              at_state->ConState = 700;
 +              for (i = 0; i < STR_NUM; ++i) {
 +                      kfree(at_state->str_var[i]);
 +                      at_state->str_var[i] = NULL;
 +              }
 +              at_state->int_var[VAR_ZCTP] = -1;
 +
 +              spin_lock_irqsave(&cs->lock, flags);
 +              at_state->timer_expires = RING_TIMEOUT;
 +              at_state->timer_active = 1;
 +              spin_unlock_irqrestore(&cs->lock, flags);
 +              break;
 +      case ACT_ICALL:
 +              handle_icall(cs, bcs, at_state);
 +              break;
 +      case ACT_FAILSDOWN:
 +              dev_warn(cs->dev, "Could not shut down the device.\n");
 +              /* fall through */
 +      case ACT_FAKESDOWN:
 +      case ACT_SDOWN:
 +              cs->cur_at_seq = SEQ_NONE;
 +              finish_shutdown(cs);
 +              break;
 +      case ACT_CONNECT:
 +              if (cs->onechannel) {
 +                      at_state->pending_commands |= PC_DLE1;
 +                      cs->commands_pending = 1;
 +                      break;
 +              }
 +              bcs->chstate |= CHS_D_UP;
 +              gigaset_isdn_connD(bcs);
 +              cs->ops->init_bchannel(bcs);
 +              break;
 +      case ACT_DLE1:
 +              cs->cur_at_seq = SEQ_NONE;
 +              bcs = cs->bcs + cs->curchannel;
 +
 +              bcs->chstate |= CHS_D_UP;
 +              gigaset_isdn_connD(bcs);
 +              cs->ops->init_bchannel(bcs);
 +              break;
 +      case ACT_FAKEHUP:
 +              at_state->int_var[VAR_ZSAU] = ZSAU_NULL;
 +              /* fall through */
 +      case ACT_DISCONNECT:
 +              cs->cur_at_seq = SEQ_NONE;
 +              at_state->cid = -1;
 +              if (!bcs) {
 +                      disconnect_nobc(p_at_state, cs);
 +              } else if (cs->onechannel && cs->dle) {
 +                      /* Check for other open channels not needed:
 +                       * DLE only used for M10x with one B channel.
 +                       */
 +                      at_state->pending_commands |= PC_DLE0;
 +                      cs->commands_pending = 1;
 +              } else {
 +                      disconnect_bc(at_state, cs, bcs);
 +              }
 +              break;
 +      case ACT_FAKEDLE0:
 +              at_state->int_var[VAR_ZDLE] = 0;
 +              cs->dle = 0;
 +              /* fall through */
 +      case ACT_DLE0:
 +              cs->cur_at_seq = SEQ_NONE;
 +              bcs2 = cs->bcs + cs->curchannel;
 +              disconnect_bc(&bcs2->at_state, cs, bcs2);
 +              break;
 +      case ACT_ABORTHUP:
 +              cs->cur_at_seq = SEQ_NONE;
 +              dev_warn(cs->dev, "Could not hang up.\n");
 +              at_state->cid = -1;
 +              if (!bcs)
 +                      disconnect_nobc(p_at_state, cs);
 +              else if (cs->onechannel)
 +                      at_state->pending_commands |= PC_DLE0;
 +              else
 +                      disconnect_bc(at_state, cs, bcs);
 +              schedule_init(cs, MS_RECOVER);
 +              break;
 +      case ACT_FAILDLE0:
 +              cs->cur_at_seq = SEQ_NONE;
 +              dev_warn(cs->dev, "Error leaving DLE mode.\n");
 +              cs->dle = 0;
 +              bcs2 = cs->bcs + cs->curchannel;
 +              disconnect_bc(&bcs2->at_state, cs, bcs2);
 +              schedule_init(cs, MS_RECOVER);
 +              break;
 +      case ACT_FAILDLE1:
 +              cs->cur_at_seq = SEQ_NONE;
 +              dev_warn(cs->dev,
 +                       "Could not enter DLE mode. Trying to hang up.\n");
 +              channel = cs->curchannel;
 +              cs->bcs[channel].at_state.pending_commands |= PC_HUP;
 +              cs->commands_pending = 1;
 +              break;
 +
 +      case ACT_CID: /* got cid; start dialing */
 +              cs->cur_at_seq = SEQ_NONE;
 +              channel = cs->curchannel;
 +              if (ev->parameter > 0 && ev->parameter <= 65535) {
 +                      cs->bcs[channel].at_state.cid = ev->parameter;
 +                      cs->bcs[channel].at_state.pending_commands |=
 +                              PC_DIAL;
 +                      cs->commands_pending = 1;
 +                      break;
 +              }
 +              /* fall through - bad cid */
 +      case ACT_FAILCID:
 +              cs->cur_at_seq = SEQ_NONE;
 +              channel = cs->curchannel;
 +              if (reinit_and_retry(cs, channel) < 0) {
 +                      dev_warn(cs->dev,
 +                               "Could not get a call ID. Cannot dial.\n");
 +                      bcs2 = cs->bcs + channel;
 +                      disconnect_bc(&bcs2->at_state, cs, bcs2);
 +              }
 +              break;
 +      case ACT_ABORTCID:
 +              cs->cur_at_seq = SEQ_NONE;
 +              bcs2 = cs->bcs + cs->curchannel;
 +              disconnect_bc(&bcs2->at_state, cs, bcs2);
 +              break;
 +
 +      case ACT_DIALING:
 +      case ACT_ACCEPTED:
 +              cs->cur_at_seq = SEQ_NONE;
 +              break;
 +
 +      case ACT_ABORTACCEPT:   /* hangup/error/timeout during ICALL procssng */
 +              if (bcs)
 +                      disconnect_bc(at_state, cs, bcs);
 +              else
 +                      disconnect_nobc(p_at_state, cs);
 +              break;
 +
 +      case ACT_ABORTDIAL:     /* error/timeout during dial preparation */
 +              cs->cur_at_seq = SEQ_NONE;
 +              at_state->pending_commands |= PC_HUP;
 +              cs->commands_pending = 1;
 +              break;
 +
 +      case ACT_REMOTEREJECT:  /* DISCONNECT_IND after dialling */
 +      case ACT_CONNTIMEOUT:   /* timeout waiting for ZSAU=ACTIVE */
 +      case ACT_REMOTEHUP:     /* DISCONNECT_IND with established connection */
 +              at_state->pending_commands |= PC_HUP;
 +              cs->commands_pending = 1;
 +              break;
 +      case ACT_GETSTRING: /* warning: RING, ZDLE, ...
 +                             are not handled properly anymore */
 +              at_state->getstring = 1;
 +              break;
 +      case ACT_SETVER:
 +              if (!ev->ptr) {
 +                      *p_genresp = 1;
 +                      *p_resp_code = RSP_ERROR;
 +                      break;
 +              }
 +              s = ev->ptr;
 +
 +              if (!strcmp(s, "OK")) {
 +                      /* OK without version string: assume old response */
 +                      *p_genresp = 1;
 +                      *p_resp_code = RSP_NONE;
 +                      break;
 +              }
 +
 +              for (i = 0; i < 4; ++i) {
 +                      val = simple_strtoul(s, (char **) &e, 10);
 +                      if (val > INT_MAX || e == s)
 +                              break;
 +                      if (i == 3) {
 +                              if (*e)
 +                                      break;
 +                      } else if (*e != '.')
 +                              break;
 +                      else
 +                              s = e + 1;
 +                      cs->fwver[i] = val;
 +              }
 +              if (i != 4) {
 +                      *p_genresp = 1;
 +                      *p_resp_code = RSP_ERROR;
 +                      break;
 +              }
 +              cs->gotfwver = 0;
 +              break;
 +      case ACT_GOTVER:
 +              if (cs->gotfwver == 0) {
 +                      cs->gotfwver = 1;
 +                      gig_dbg(DEBUG_EVENT,
 +                              "firmware version %02d.%03d.%02d.%02d",
 +                              cs->fwver[0], cs->fwver[1],
 +                              cs->fwver[2], cs->fwver[3]);
 +                      break;
 +              }
 +              /* fall through */
 +      case ACT_FAILVER:
 +              cs->gotfwver = -1;
 +              dev_err(cs->dev, "could not read firmware version.\n");
 +              break;
 +      case ACT_ERROR:
 +              gig_dbg(DEBUG_ANY, "%s: ERROR response in ConState %d",
 +                      __func__, at_state->ConState);
 +              cs->cur_at_seq = SEQ_NONE;
 +              break;
 +      case ACT_DEBUG:
 +              gig_dbg(DEBUG_ANY, "%s: resp_code %d in ConState %d",
 +                      __func__, ev->type, at_state->ConState);
 +              break;
 +      case ACT_WARN:
 +              dev_warn(cs->dev, "%s: resp_code %d in ConState %d!\n",
 +                       __func__, ev->type, at_state->ConState);
 +              break;
 +      case ACT_ZCAU:
 +              dev_warn(cs->dev, "cause code %04x in connection state %d.\n",
 +                       ev->parameter, at_state->ConState);
 +              break;
 +
 +      /* events from the LL */
 +
 +      case ACT_DIAL:
 +              if (!ev->ptr) {
 +                      *p_genresp = 1;
 +                      *p_resp_code = RSP_ERROR;
 +                      break;
 +              }
 +              start_dial(at_state, ev->ptr, ev->parameter);
 +              break;
 +      case ACT_ACCEPT:
 +              start_accept(at_state);
 +              break;
 +      case ACT_HUP:
 +              at_state->pending_commands |= PC_HUP;
 +              gig_dbg(DEBUG_EVENT, "Scheduling PC_HUP");
 +              cs->commands_pending = 1;
 +              break;
 +
 +      /* hotplug events */
 +
 +      case ACT_STOP:
 +              do_stop(cs);
 +              break;
 +      case ACT_START:
 +              do_start(cs);
 +              break;
 +
 +      /* events from the interface */
 +
 +      case ACT_IF_LOCK:
 +              cs->cmd_result = ev->parameter ? do_lock(cs) : do_unlock(cs);
 +              cs->waiting = 0;
 +              wake_up(&cs->waitqueue);
 +              break;
 +      case ACT_IF_VER:
 +              if (ev->parameter != 0)
 +                      cs->cmd_result = -EINVAL;
 +              else if (cs->gotfwver != 1) {
 +                      cs->cmd_result = -ENOENT;
 +              } else {
 +                      memcpy(ev->arg, cs->fwver, sizeof cs->fwver);
 +                      cs->cmd_result = 0;
 +              }
 +              cs->waiting = 0;
 +              wake_up(&cs->waitqueue);
 +              break;
 +
 +      /* events from the proc file system */
 +
 +      case ACT_PROC_CIDMODE:
 +              spin_lock_irqsave(&cs->lock, flags);
 +              if (ev->parameter != cs->cidmode) {
 +                      cs->cidmode = ev->parameter;
 +                      if (ev->parameter) {
 +                              cs->at_state.pending_commands |= PC_CIDMODE;
 +                              gig_dbg(DEBUG_EVENT, "Scheduling PC_CIDMODE");
 +                      } else {
 +                              cs->at_state.pending_commands |= PC_UMMODE;
 +                              gig_dbg(DEBUG_EVENT, "Scheduling PC_UMMODE");
 +                      }
 +                      cs->commands_pending = 1;
 +              }
 +              spin_unlock_irqrestore(&cs->lock, flags);
 +              cs->waiting = 0;
 +              wake_up(&cs->waitqueue);
 +              break;
 +
 +      /* events from the hardware drivers */
 +
 +      case ACT_NOTIFY_BC_DOWN:
 +              bchannel_down(bcs);
 +              break;
 +      case ACT_NOTIFY_BC_UP:
 +              bchannel_up(bcs);
 +              break;
 +      case ACT_SHUTDOWN:
 +              do_shutdown(cs);
 +              break;
 +
 +
 +      default:
 +              if (action >= ACT_CMD && action < ACT_CMD + AT_NUM) {
 +                      *pp_command = at_state->bcs->commands[action - ACT_CMD];
 +                      if (!*pp_command) {
 +                              *p_genresp = 1;
 +                              *p_resp_code = RSP_NULL;
 +                      }
 +              } else
 +                      dev_err(cs->dev, "%s: action==%d!\n", __func__, action);
 +      }
 +}
 +
 +/* State machine to do the calling and hangup procedure */
 +static void process_event(struct cardstate *cs, struct event_t *ev)
 +{
 +      struct bc_state *bcs;
 +      char *p_command = NULL;
 +      struct reply_t *rep;
 +      int rcode;
 +      int genresp = 0;
 +      int resp_code = RSP_ERROR;
 +      struct at_state_t *at_state;
 +      int index;
 +      int curact;
 +      unsigned long flags;
 +
 +      if (ev->cid >= 0) {
 +              at_state = at_state_from_cid(cs, ev->cid);
 +              if (!at_state) {
 +                      gig_dbg(DEBUG_EVENT, "event %d for invalid cid %d",
 +                              ev->type, ev->cid);
 +                      gigaset_add_event(cs, &cs->at_state, RSP_WRONG_CID,
 +                                        NULL, 0, NULL);
 +                      return;
 +              }
 +      } else {
 +              at_state = ev->at_state;
 +              if (at_state_invalid(cs, at_state)) {
 +                      gig_dbg(DEBUG_EVENT, "event for invalid at_state %p",
 +                              at_state);
 +                      return;
 +              }
 +      }
 +
 +      gig_dbg(DEBUG_EVENT, "connection state %d, event %d",
 +              at_state->ConState, ev->type);
 +
 +      bcs = at_state->bcs;
 +
 +      /* Setting the pointer to the dial array */
 +      rep = at_state->replystruct;
 +
 +      spin_lock_irqsave(&cs->lock, flags);
 +      if (ev->type == EV_TIMEOUT) {
 +              if (ev->parameter != at_state->timer_index
 +                  || !at_state->timer_active) {
 +                      ev->type = RSP_NONE; /* old timeout */
 +                      gig_dbg(DEBUG_EVENT, "old timeout");
 +              } else {
 +                      if (at_state->waiting)
 +                              gig_dbg(DEBUG_EVENT, "stopped waiting");
 +                      else
 +                              gig_dbg(DEBUG_EVENT, "timeout occurred");
 +              }
 +      }
 +      spin_unlock_irqrestore(&cs->lock, flags);
 +
 +      /* if the response belongs to a variable in at_state->int_var[VAR_XXXX]
 +         or at_state->str_var[STR_XXXX], set it */
 +      if (ev->type >= RSP_VAR && ev->type < RSP_VAR + VAR_NUM) {
 +              index = ev->type - RSP_VAR;
 +              at_state->int_var[index] = ev->parameter;
 +      } else if (ev->type >= RSP_STR && ev->type < RSP_STR + STR_NUM) {
 +              index = ev->type - RSP_STR;
 +              kfree(at_state->str_var[index]);
 +              at_state->str_var[index] = ev->ptr;
 +              ev->ptr = NULL; /* prevent process_events() from
 +                                 deallocating ptr */
 +      }
 +
 +      if (ev->type == EV_TIMEOUT || ev->type == RSP_STRING)
 +              at_state->getstring = 0;
 +
 +      /* Search row in dial array which matches modem response and current
 +         constate */
 +      for (;; rep++) {
 +              rcode = rep->resp_code;
 +              if (rcode == RSP_LAST) {
 +                      /* found nothing...*/
 +                      dev_warn(cs->dev, "%s: rcode=RSP_LAST: "
 +                               "resp_code %d in ConState %d!\n",
 +                               __func__, ev->type, at_state->ConState);
 +                      return;
 +              }
 +              if ((rcode == RSP_ANY || rcode == ev->type)
 +                  && ((int) at_state->ConState >= rep->min_ConState)
 +                  && (rep->max_ConState < 0
 +                      || (int) at_state->ConState <= rep->max_ConState)
 +                  && (rep->parameter < 0 || rep->parameter == ev->parameter))
 +                      break;
 +      }
 +
 +      p_command = rep->command;
 +
 +      at_state->waiting = 0;
 +      for (curact = 0; curact < MAXACT; ++curact) {
 +              /* The row tells us what we should do  ..
 +               */
 +              do_action(rep->action[curact], cs, bcs, &at_state, &p_command,
 +                        &genresp, &resp_code, ev);
 +              if (!at_state)
 +                      /* at_state destroyed by disconnect */
 +                      return;
 +      }
 +
 +      /* Jump to the next con-state regarding the array */
 +      if (rep->new_ConState >= 0)
 +              at_state->ConState = rep->new_ConState;
 +
 +      if (genresp) {
 +              spin_lock_irqsave(&cs->lock, flags);
 +              at_state->timer_expires = 0;
 +              at_state->timer_active = 0;
 +              spin_unlock_irqrestore(&cs->lock, flags);
 +              gigaset_add_event(cs, at_state, resp_code, NULL, 0, NULL);
 +      } else {
 +              /* Send command to modem if not NULL... */
 +              if (p_command) {
 +                      if (cs->connected)
 +                              send_command(cs, p_command, at_state);
 +                      else
 +                              gigaset_add_event(cs, at_state, RSP_NODEV,
 +                                                NULL, 0, NULL);
 +              }
 +
 +              spin_lock_irqsave(&cs->lock, flags);
 +              if (!rep->timeout) {
 +                      at_state->timer_expires = 0;
 +                      at_state->timer_active = 0;
 +              } else if (rep->timeout > 0) { /* new timeout */
 +                      at_state->timer_expires = rep->timeout * 10;
 +                      at_state->timer_active = 1;
 +                      ++at_state->timer_index;
 +              }
 +              spin_unlock_irqrestore(&cs->lock, flags);
 +      }
 +}
 +
 +static void schedule_sequence(struct cardstate *cs,
 +                            struct at_state_t *at_state, int sequence)
 +{
 +      cs->cur_at_seq = sequence;
 +      gigaset_add_event(cs, at_state, RSP_INIT, NULL, sequence, NULL);
 +}
 +
 +static void process_command_flags(struct cardstate *cs)
 +{
 +      struct at_state_t *at_state = NULL;
 +      struct bc_state *bcs;
 +      int i;
 +      int sequence;
 +      unsigned long flags;
 +
 +      cs->commands_pending = 0;
 +
 +      if (cs->cur_at_seq) {
 +              gig_dbg(DEBUG_EVENT, "not searching scheduled commands: busy");
 +              return;
 +      }
 +
 +      gig_dbg(DEBUG_EVENT, "searching scheduled commands");
 +
 +      sequence = SEQ_NONE;
 +
 +      /* clear pending_commands and hangup channels on shutdown */
 +      if (cs->at_state.pending_commands & PC_SHUTDOWN) {
 +              cs->at_state.pending_commands &= ~PC_CIDMODE;
 +              for (i = 0; i < cs->channels; ++i) {
 +                      bcs = cs->bcs + i;
 +                      at_state = &bcs->at_state;
 +                      at_state->pending_commands &=
 +                              ~(PC_DLE1 | PC_ACCEPT | PC_DIAL);
 +                      if (at_state->cid > 0)
 +                              at_state->pending_commands |= PC_HUP;
 +                      if (at_state->pending_commands & PC_CID) {
 +                              at_state->pending_commands |= PC_NOCID;
 +                              at_state->pending_commands &= ~PC_CID;
 +                      }
 +              }
 +      }
 +
 +      /* clear pending_commands and hangup channels on reset */
 +      if (cs->at_state.pending_commands & PC_INIT) {
 +              cs->at_state.pending_commands &= ~PC_CIDMODE;
 +              for (i = 0; i < cs->channels; ++i) {
 +                      bcs = cs->bcs + i;
 +                      at_state = &bcs->at_state;
 +                      at_state->pending_commands &=
 +                              ~(PC_DLE1 | PC_ACCEPT | PC_DIAL);
 +                      if (at_state->cid > 0)
 +                              at_state->pending_commands |= PC_HUP;
 +                      if (cs->mstate == MS_RECOVER) {
 +                              if (at_state->pending_commands & PC_CID) {
 +                                      at_state->pending_commands |= PC_NOCID;
 +                                      at_state->pending_commands &= ~PC_CID;
 +                              }
 +                      }
 +              }
 +      }
 +
 +      /* only switch back to unimodem mode if no commands are pending and
 +       * no channels are up */
 +      spin_lock_irqsave(&cs->lock, flags);
 +      if (cs->at_state.pending_commands == PC_UMMODE
 +          && !cs->cidmode
 +          && list_empty(&cs->temp_at_states)
 +          && cs->mode == M_CID) {
 +              sequence = SEQ_UMMODE;
 +              at_state = &cs->at_state;
 +              for (i = 0; i < cs->channels; ++i) {
 +                      bcs = cs->bcs + i;
 +                      if (bcs->at_state.pending_commands ||
 +                          bcs->at_state.cid > 0) {
 +                              sequence = SEQ_NONE;
 +                              break;
 +                      }
 +              }
 +      }
 +      spin_unlock_irqrestore(&cs->lock, flags);
 +      cs->at_state.pending_commands &= ~PC_UMMODE;
 +      if (sequence != SEQ_NONE) {
 +              schedule_sequence(cs, at_state, sequence);
 +              return;
 +      }
 +
 +      for (i = 0; i < cs->channels; ++i) {
 +              bcs = cs->bcs + i;
 +              if (bcs->at_state.pending_commands & PC_HUP) {
 +                      if (cs->dle) {
 +                              cs->curchannel = bcs->channel;
 +                              schedule_sequence(cs, &cs->at_state, SEQ_DLE0);
 +                              return;
 +                      }
 +                      bcs->at_state.pending_commands &= ~PC_HUP;
 +                      if (bcs->at_state.pending_commands & PC_CID) {
 +                              /* not yet dialing: PC_NOCID is sufficient */
 +                              bcs->at_state.pending_commands |= PC_NOCID;
 +                              bcs->at_state.pending_commands &= ~PC_CID;
 +                      } else {
 +                              schedule_sequence(cs, &bcs->at_state, SEQ_HUP);
 +                              return;
 +                      }
 +              }
 +              if (bcs->at_state.pending_commands & PC_NOCID) {
 +                      bcs->at_state.pending_commands &= ~PC_NOCID;
 +                      cs->curchannel = bcs->channel;
 +                      schedule_sequence(cs, &cs->at_state, SEQ_NOCID);
 +                      return;
 +              } else if (bcs->at_state.pending_commands & PC_DLE0) {
 +                      bcs->at_state.pending_commands &= ~PC_DLE0;
 +                      cs->curchannel = bcs->channel;
 +                      schedule_sequence(cs, &cs->at_state, SEQ_DLE0);
 +                      return;
 +              }
 +      }
 +
 +      list_for_each_entry(at_state, &cs->temp_at_states, list)
 +              if (at_state->pending_commands & PC_HUP) {
 +                      at_state->pending_commands &= ~PC_HUP;
 +                      schedule_sequence(cs, at_state, SEQ_HUP);
 +                      return;
 +              }
 +
 +      if (cs->at_state.pending_commands & PC_INIT) {
 +              cs->at_state.pending_commands &= ~PC_INIT;
 +              cs->dle = 0;
 +              cs->inbuf->inputstate = INS_command;
 +              schedule_sequence(cs, &cs->at_state, SEQ_INIT);
 +              return;
 +      }
 +      if (cs->at_state.pending_commands & PC_SHUTDOWN) {
 +              cs->at_state.pending_commands &= ~PC_SHUTDOWN;
 +              schedule_sequence(cs, &cs->at_state, SEQ_SHUTDOWN);
 +              return;
 +      }
 +      if (cs->at_state.pending_commands & PC_CIDMODE) {
 +              cs->at_state.pending_commands &= ~PC_CIDMODE;
 +              if (cs->mode == M_UNIMODEM) {
 +                      cs->retry_count = 1;
 +                      schedule_sequence(cs, &cs->at_state, SEQ_CIDMODE);
 +                      return;
 +              }
 +      }
 +
 +      for (i = 0; i < cs->channels; ++i) {
 +              bcs = cs->bcs + i;
 +              if (bcs->at_state.pending_commands & PC_DLE1) {
 +                      bcs->at_state.pending_commands &= ~PC_DLE1;
 +                      cs->curchannel = bcs->channel;
 +                      schedule_sequence(cs, &cs->at_state, SEQ_DLE1);
 +                      return;
 +              }
 +              if (bcs->at_state.pending_commands & PC_ACCEPT) {
 +                      bcs->at_state.pending_commands &= ~PC_ACCEPT;
 +                      schedule_sequence(cs, &bcs->at_state, SEQ_ACCEPT);
 +                      return;
 +              }
 +              if (bcs->at_state.pending_commands & PC_DIAL) {
 +                      bcs->at_state.pending_commands &= ~PC_DIAL;
 +                      schedule_sequence(cs, &bcs->at_state, SEQ_DIAL);
 +                      return;
 +              }
 +              if (bcs->at_state.pending_commands & PC_CID) {
 +                      switch (cs->mode) {
 +                      case M_UNIMODEM:
 +                              cs->at_state.pending_commands |= PC_CIDMODE;
 +                              gig_dbg(DEBUG_EVENT, "Scheduling PC_CIDMODE");
 +                              cs->commands_pending = 1;
 +                              return;
 +                      case M_UNKNOWN:
 +                              schedule_init(cs, MS_INIT);
 +                              return;
 +                      }
 +                      bcs->at_state.pending_commands &= ~PC_CID;
 +                      cs->curchannel = bcs->channel;
 +                      cs->retry_count = 2;
 +                      schedule_sequence(cs, &cs->at_state, SEQ_CID);
 +                      return;
 +              }
 +      }
 +}
 +
 +static void process_events(struct cardstate *cs)
 +{
 +      struct event_t *ev;
 +      unsigned head, tail;
 +      int i;
 +      int check_flags = 0;
 +      int was_busy;
 +      unsigned long flags;
 +
 +      spin_lock_irqsave(&cs->ev_lock, flags);
 +      head = cs->ev_head;
 +
 +      for (i = 0; i < 2 * MAX_EVENTS; ++i) {
 +              tail = cs->ev_tail;
 +              if (tail == head) {
 +                      if (!check_flags && !cs->commands_pending)
 +                              break;
 +                      check_flags = 0;
 +                      spin_unlock_irqrestore(&cs->ev_lock, flags);
 +                      process_command_flags(cs);
 +                      spin_lock_irqsave(&cs->ev_lock, flags);
 +                      tail = cs->ev_tail;
 +                      if (tail == head) {
 +                              if (!cs->commands_pending)
 +                                      break;
 +                              continue;
 +                      }
 +              }
 +
 +              ev = cs->events + head;
 +              was_busy = cs->cur_at_seq != SEQ_NONE;
 +              spin_unlock_irqrestore(&cs->ev_lock, flags);
 +              process_event(cs, ev);
 +              spin_lock_irqsave(&cs->ev_lock, flags);
 +              kfree(ev->ptr);
 +              ev->ptr = NULL;
 +              if (was_busy && cs->cur_at_seq == SEQ_NONE)
 +                      check_flags = 1;
 +
 +              head = (head + 1) % MAX_EVENTS;
 +              cs->ev_head = head;
 +      }
 +
 +      spin_unlock_irqrestore(&cs->ev_lock, flags);
 +
 +      if (i == 2 * MAX_EVENTS) {
 +              dev_err(cs->dev,
 +                      "infinite loop in process_events; aborting.\n");
 +      }
 +}
 +
 +/* tasklet scheduled on any event received from the Gigaset device
 + * parameter:
 + *    data    ISDN controller state structure
 + */
 +void gigaset_handle_event(unsigned long data)
 +{
 +      struct cardstate *cs = (struct cardstate *) data;
 +
 +      /* handle incoming data on control/common channel */
 +      if (cs->inbuf->head != cs->inbuf->tail) {
 +              gig_dbg(DEBUG_INTR, "processing new data");
 +              cs->ops->handle_input(cs->inbuf);
 +      }
 +
 +      process_events(cs);
 +}
index 166537e,0000000..0ecc2b5
mode 100644,000000..100644
--- /dev/null
@@@ -1,830 -1,0 +1,827 @@@
-  *    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.
++/* SPDX-License-Identifier: GPL-2.0-or-later */
 +/*
 + * Siemens Gigaset 307x driver
 + * Common header file for all connection variants
 + *
 + * Written by Stefan Eilers
 + *        and Hansjoerg Lipp <hjlipp@web.de>
 + *
 + * =====================================================================
 + * =====================================================================
 + */
 +
 +#ifndef GIGASET_H
 +#define GIGASET_H
 +
 +/* define global prefix for pr_ macros in linux/kernel.h */
 +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 +
 +#include <linux/kernel.h>
 +#include <linux/sched.h>
 +#include <linux/compiler.h>
 +#include <linux/types.h>
 +#include <linux/ctype.h>
 +#include <linux/slab.h>
 +#include <linux/spinlock.h>
 +#include <linux/skbuff.h>
 +#include <linux/netdevice.h>
 +#include <linux/ppp_defs.h>
 +#include <linux/timer.h>
 +#include <linux/interrupt.h>
 +#include <linux/tty.h>
 +#include <linux/tty_driver.h>
 +#include <linux/list.h>
 +#include <linux/atomic.h>
 +
 +#define GIG_VERSION {0, 5, 0, 0}
 +#define GIG_COMPAT  {0, 4, 0, 0}
 +
 +#define MAX_REC_PARAMS 10     /* Max. number of params in response string */
 +#define MAX_RESP_SIZE 511     /* Max. size of a response string */
 +
 +#define MAX_EVENTS 64         /* size of event queue */
 +
 +#define RBUFSIZE 8192
 +
 +#define GIG_TICK 100          /* in milliseconds */
 +
 +/* timeout values (unit: 1 sec) */
 +#define INIT_TIMEOUT 1
 +
 +/* timeout values (unit: 0.1 sec) */
 +#define RING_TIMEOUT 3                /* for additional parameters to RING */
 +#define BAS_TIMEOUT 20                /* for response to Base USB ops */
 +#define ATRDY_TIMEOUT 3               /* for HD_READY_SEND_ATDATA */
 +
 +#define BAS_RETRY 3           /* max. retries for base USB ops */
 +
 +#define MAXACT 3
 +
 +extern int gigaset_debuglevel;        /* "needs" cast to (enum debuglevel) */
 +
 +/* debug flags, combine by adding/bitwise OR */
 +enum debuglevel {
 +      DEBUG_INTR        = 0x00008, /* interrupt processing */
 +      DEBUG_CMD         = 0x00020, /* sent/received LL commands */
 +      DEBUG_STREAM      = 0x00040, /* application data stream I/O events */
 +      DEBUG_STREAM_DUMP = 0x00080, /* application data stream content */
 +      DEBUG_LLDATA      = 0x00100, /* sent/received LL data */
 +      DEBUG_EVENT       = 0x00200, /* event processing */
 +      DEBUG_HDLC        = 0x00800, /* M10x HDLC processing */
 +      DEBUG_CHANNEL     = 0x01000, /* channel allocation/deallocation */
 +      DEBUG_TRANSCMD    = 0x02000, /* AT-COMMANDS+RESPONSES */
 +      DEBUG_MCMD        = 0x04000, /* COMMANDS THAT ARE SENT VERY OFTEN */
 +      DEBUG_INIT        = 0x08000, /* (de)allocation+initialization of data
 +                                      structures */
 +      DEBUG_SUSPEND     = 0x10000, /* suspend/resume processing */
 +      DEBUG_OUTPUT      = 0x20000, /* output to device */
 +      DEBUG_ISO         = 0x40000, /* isochronous transfers */
 +      DEBUG_IF          = 0x80000, /* character device operations */
 +      DEBUG_USBREQ      = 0x100000, /* USB communication (except payload
 +                                       data) */
 +      DEBUG_LOCKCMD     = 0x200000, /* AT commands and responses when
 +                                       MS_LOCKED */
 +
 +      DEBUG_ANY         = 0x3fffff, /* print message if any of the others is
 +                                       activated */
 +};
 +
 +#ifdef CONFIG_GIGASET_DEBUG
 +
 +#define gig_dbg(level, format, arg...)                                        \
 +      do {                                                            \
 +              if (unlikely(((enum debuglevel)gigaset_debuglevel) & (level))) \
 +                      printk(KERN_DEBUG KBUILD_MODNAME ": " format "\n", \
 +                             ## arg);                                 \
 +      } while (0)
 +#define DEBUG_DEFAULT (DEBUG_TRANSCMD | DEBUG_CMD | DEBUG_USBREQ)
 +
 +#else
 +
 +#define gig_dbg(level, format, arg...) do {} while (0)
 +#define DEBUG_DEFAULT 0
 +
 +#endif
 +
 +void gigaset_dbg_buffer(enum debuglevel level, const unsigned char *msg,
 +                      size_t len, const unsigned char *buf);
 +
 +/* connection state */
 +#define ZSAU_NONE                     0
 +#define ZSAU_PROCEEDING                       1
 +#define ZSAU_CALL_DELIVERED           2
 +#define ZSAU_ACTIVE                   3
 +#define ZSAU_DISCONNECT_IND           4
 +#define ZSAU_NULL                     5
 +#define ZSAU_DISCONNECT_REQ           6
 +#define ZSAU_UNKNOWN                  -1
 +
 +/* USB control transfer requests */
 +#define OUT_VENDOR_REQ        (USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_ENDPOINT)
 +#define IN_VENDOR_REQ (USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_ENDPOINT)
 +
 +/* interrupt pipe messages */
 +#define HD_B1_FLOW_CONTROL            0x80
 +#define HD_B2_FLOW_CONTROL            0x81
 +#define HD_RECEIVEATDATA_ACK          (0x35)          /* 3070 */
 +#define HD_READY_SEND_ATDATA          (0x36)          /* 3070 */
 +#define HD_OPEN_ATCHANNEL_ACK         (0x37)          /* 3070 */
 +#define HD_CLOSE_ATCHANNEL_ACK                (0x38)          /* 3070 */
 +#define HD_DEVICE_INIT_OK             (0x11)          /* ISurf USB + 3070 */
 +#define HD_OPEN_B1CHANNEL_ACK         (0x51)          /* ISurf USB + 3070 */
 +#define HD_OPEN_B2CHANNEL_ACK         (0x52)          /* ISurf USB + 3070 */
 +#define HD_CLOSE_B1CHANNEL_ACK                (0x53)          /* ISurf USB + 3070 */
 +#define HD_CLOSE_B2CHANNEL_ACK                (0x54)          /* ISurf USB + 3070 */
 +#define HD_SUSPEND_END                        (0x61)          /* ISurf USB */
 +#define HD_RESET_INTERRUPT_PIPE_ACK   (0xFF)          /* ISurf USB + 3070 */
 +
 +/* control requests */
 +#define       HD_OPEN_B1CHANNEL               (0x23)          /* ISurf USB + 3070 */
 +#define       HD_CLOSE_B1CHANNEL              (0x24)          /* ISurf USB + 3070 */
 +#define       HD_OPEN_B2CHANNEL               (0x25)          /* ISurf USB + 3070 */
 +#define       HD_CLOSE_B2CHANNEL              (0x26)          /* ISurf USB + 3070 */
 +#define HD_RESET_INTERRUPT_PIPE               (0x27)          /* ISurf USB + 3070 */
 +#define       HD_DEVICE_INIT_ACK              (0x34)          /* ISurf USB + 3070 */
 +#define       HD_WRITE_ATMESSAGE              (0x12)          /* 3070 */
 +#define       HD_READ_ATMESSAGE               (0x13)          /* 3070 */
 +#define       HD_OPEN_ATCHANNEL               (0x28)          /* 3070 */
 +#define       HD_CLOSE_ATCHANNEL              (0x29)          /* 3070 */
 +
 +/* number of B channels supported by base driver */
 +#define BAS_CHANNELS  2
 +
 +/* USB frames for isochronous transfer */
 +#define BAS_FRAMETIME 1       /* number of milliseconds between frames */
 +#define BAS_NUMFRAMES 8       /* number of frames per URB */
 +#define BAS_MAXFRAME  16      /* allocated bytes per frame */
 +#define BAS_NORMFRAME 8       /* send size without flow control */
 +#define BAS_HIGHFRAME 10      /* "    "    with positive flow control */
 +#define BAS_LOWFRAME  5       /* "    "    with negative flow control */
 +#define BAS_CORRFRAMES        4       /* flow control multiplicator */
 +
 +#define BAS_INBUFSIZE (BAS_MAXFRAME * BAS_NUMFRAMES)  /* size of isoc in buf
 +                                                       * per URB */
 +#define BAS_OUTBUFSIZE        4096            /* size of common isoc out buffer */
 +#define BAS_OUTBUFPAD BAS_MAXFRAME    /* size of pad area for isoc out buf */
 +
 +#define BAS_INURBS    3
 +#define BAS_OUTURBS   3
 +
 +/* variable commands in struct bc_state */
 +#define AT_ISO                0
 +#define AT_DIAL               1
 +#define AT_MSN                2
 +#define AT_BC         3
 +#define AT_PROTO      4
 +#define AT_TYPE               5
 +#define AT_CLIP               6
 +/* total number */
 +#define AT_NUM                7
 +
 +/* variables in struct at_state_t */
 +/* - numeric */
 +#define VAR_ZSAU      0
 +#define VAR_ZDLE      1
 +#define VAR_ZCTP      2
 +/* total number */
 +#define VAR_NUM               3
 +/* - string */
 +#define STR_NMBR      0
 +#define STR_ZCPN      1
 +#define STR_ZCON      2
 +#define STR_ZBC               3
 +#define STR_ZHLC      4
 +/* total number */
 +#define STR_NUM               5
 +
 +/* event types */
 +#define EV_TIMEOUT    -105
 +#define EV_IF_VER     -106
 +#define EV_PROC_CIDMODE       -107
 +#define EV_SHUTDOWN   -108
 +#define EV_START      -110
 +#define EV_STOP               -111
 +#define EV_IF_LOCK    -112
 +#define EV_ACCEPT     -114
 +#define EV_DIAL               -115
 +#define EV_HUP                -116
 +#define EV_BC_OPEN    -117
 +#define EV_BC_CLOSED  -118
 +
 +/* input state */
 +#define INS_command   0x0001  /* receiving messages (not payload data) */
 +#define INS_DLE_char  0x0002  /* DLE flag received (in DLE mode) */
 +#define INS_byte_stuff        0x0004
 +#define INS_have_data 0x0008
 +#define INS_DLE_command       0x0020  /* DLE message start (<DLE> X) received */
 +#define INS_flag_hunt 0x0040
 +
 +/* channel state */
 +#define CHS_D_UP      0x01
 +#define CHS_B_UP      0x02
 +#define CHS_NOTIFY_LL 0x04
 +
 +#define ICALL_REJECT  0
 +#define ICALL_ACCEPT  1
 +#define ICALL_IGNORE  2
 +
 +/* device state */
 +#define MS_UNINITIALIZED      0
 +#define MS_INIT                       1
 +#define MS_LOCKED             2
 +#define MS_SHUTDOWN           3
 +#define MS_RECOVER            4
 +#define MS_READY              5
 +
 +/* mode */
 +#define M_UNKNOWN     0
 +#define M_CONFIG      1
 +#define M_UNIMODEM    2
 +#define M_CID         3
 +
 +/* start mode */
 +#define SM_LOCKED     0
 +#define SM_ISDN               1 /* default */
 +
 +/* layer 2 protocols (AT^SBPR=...) */
 +#define L2_BITSYNC    0
 +#define L2_HDLC               1
 +#define L2_VOICE      2
 +
 +struct gigaset_ops;
 +struct gigaset_driver;
 +
 +struct usb_cardstate;
 +struct ser_cardstate;
 +struct bas_cardstate;
 +
 +struct bc_state;
 +struct usb_bc_state;
 +struct ser_bc_state;
 +struct bas_bc_state;
 +
 +struct reply_t {
 +      int     resp_code;      /* RSP_XXXX */
 +      int     min_ConState;   /* <0 => ignore */
 +      int     max_ConState;   /* <0 => ignore */
 +      int     parameter;      /* e.g. ZSAU_XXXX <0: ignore*/
 +      int     new_ConState;   /* <0 => ignore */
 +      int     timeout;        /* >0 => *HZ; <=0 => TOUT_XXXX*/
 +      int     action[MAXACT]; /* ACT_XXXX */
 +      char    *command;       /* NULL==none */
 +};
 +
 +extern struct reply_t gigaset_tab_cid[];
 +extern struct reply_t gigaset_tab_nocid[];
 +
 +struct inbuf_t {
 +      struct cardstate        *cs;
 +      int                     inputstate;
 +      int                     head, tail;
 +      unsigned char           data[RBUFSIZE];
 +};
 +
 +/* isochronous write buffer structure
 + * circular buffer with pad area for extraction of complete USB frames
 + * - data[read..nextread-1] is valid data already submitted to the USB subsystem
 + * - data[nextread..write-1] is valid data yet to be sent
 + * - data[write] is the next byte to write to
 + *   - in byte-oriented L2 procotols, it is completely free
 + *   - in bit-oriented L2 procotols, it may contain a partial byte of valid data
 + * - data[write+1..read-1] is free
 + * - wbits is the number of valid data bits in data[write], starting at the LSB
 + * - writesem is the semaphore for writing to the buffer:
 + *   if writesem <= 0, data[write..read-1] is currently being written to
 + * - idle contains the byte value to repeat when the end of valid data is
 + *   reached; if nextread==write (buffer contains no data to send), either the
 + *   BAS_OUTBUFPAD bytes immediately before data[write] (if
 + *   write>=BAS_OUTBUFPAD) or those of the pad area (if write<BAS_OUTBUFPAD)
 + *   are also filled with that value
 + */
 +struct isowbuf_t {
 +      int             read;
 +      int             nextread;
 +      int             write;
 +      atomic_t        writesem;
 +      int             wbits;
 +      unsigned char   data[BAS_OUTBUFSIZE + BAS_OUTBUFPAD];
 +      unsigned char   idle;
 +};
 +
 +/* isochronous write URB context structure
 + * data to be stored along with the URB and retrieved when it is returned
 + * as completed by the USB subsystem
 + * - urb: pointer to the URB itself
 + * - bcs: pointer to the B Channel control structure
 + * - limit: end of write buffer area covered by this URB
 + * - status: URB completion status
 + */
 +struct isow_urbctx_t {
 +      struct urb *urb;
 +      struct bc_state *bcs;
 +      int limit;
 +      int status;
 +};
 +
 +/* AT state structure
 + * data associated with the state of an ISDN connection, whether or not
 + * it is currently assigned a B channel
 + */
 +struct at_state_t {
 +      struct list_head        list;
 +      int                     waiting;
 +      int                     getstring;
 +      unsigned                timer_index;
 +      unsigned long           timer_expires;
 +      int                     timer_active;
 +      unsigned int            ConState;       /* State of connection */
 +      struct reply_t          *replystruct;
 +      int                     cid;
 +      int                     int_var[VAR_NUM];       /* see VAR_XXXX */
 +      char                    *str_var[STR_NUM];      /* see STR_XXXX */
 +      unsigned                pending_commands;       /* see PC_XXXX */
 +      unsigned                seq_index;
 +
 +      struct cardstate        *cs;
 +      struct bc_state         *bcs;
 +};
 +
 +struct event_t {
 +      int type;
 +      void *ptr, *arg;
 +      int parameter;
 +      int cid;
 +      struct at_state_t *at_state;
 +};
 +
 +/* This buffer holds all information about the used B-Channel */
 +struct bc_state {
 +      struct sk_buff *tx_skb;         /* Current transfer buffer to modem */
 +      struct sk_buff_head squeue;     /* B-Channel send Queue */
 +
 +      /* Variables for debugging .. */
 +      int corrupted;                  /* Counter for corrupted packages */
 +      int trans_down;                 /* Counter of packages (downstream) */
 +      int trans_up;                   /* Counter of packages (upstream) */
 +
 +      struct at_state_t at_state;
 +
 +      /* receive buffer */
 +      unsigned rx_bufsize;            /* max size accepted by application */
 +      struct sk_buff *rx_skb;
 +      __u16 rx_fcs;
 +      int inputstate;                 /* see INS_XXXX */
 +
 +      int channel;
 +
 +      struct cardstate *cs;
 +
 +      unsigned chstate;               /* bitmap (CHS_*) */
 +      int ignore;
 +      unsigned proto2;                /* layer 2 protocol (L2_*) */
 +      char *commands[AT_NUM];         /* see AT_XXXX */
 +
 +#ifdef CONFIG_GIGASET_DEBUG
 +      int emptycount;
 +#endif
 +      int busy;
 +      int use_count;
 +
 +      /* private data of hardware drivers */
 +      union {
 +              struct ser_bc_state *ser;       /* serial hardware driver */
 +              struct usb_bc_state *usb;       /* usb hardware driver (m105) */
 +              struct bas_bc_state *bas;       /* usb hardware driver (base) */
 +      } hw;
 +
 +      void *ap;                       /* associated LL application */
 +      int apconnstate;                /* LL application connection state */
 +      spinlock_t aplock;
 +};
 +
 +struct cardstate {
 +      struct gigaset_driver *driver;
 +      unsigned minor_index;
 +      struct device *dev;
 +      struct device *tty_dev;
 +      unsigned flags;
 +
 +      const struct gigaset_ops *ops;
 +
 +      /* Stuff to handle communication */
 +      wait_queue_head_t waitqueue;
 +      int waiting;
 +      int mode;                       /* see M_XXXX */
 +      int mstate;                     /* Modem state: see MS_XXXX */
 +                                      /* only changed by the event layer */
 +      int cmd_result;
 +
 +      int channels;
 +      struct bc_state *bcs;           /* Array of struct bc_state */
 +
 +      int onechannel;                 /* data and commands transmitted in one
 +                                         stream (M10x) */
 +
 +      spinlock_t lock;
 +      struct at_state_t at_state;     /* at_state_t for cid == 0 */
 +      struct list_head temp_at_states;/* list of temporary "struct
 +                                         at_state_t"s without B channel */
 +
 +      struct inbuf_t *inbuf;
 +
 +      struct cmdbuf_t *cmdbuf, *lastcmdbuf;
 +      spinlock_t cmdlock;
 +      unsigned curlen, cmdbytes;
 +
 +      struct tty_port port;
 +      struct tasklet_struct if_wake_tasklet;
 +      unsigned control_state;
 +
 +      unsigned fwver[4];
 +      int gotfwver;
 +
 +      unsigned running;               /* !=0 if events are handled */
 +      unsigned connected;             /* !=0 if hardware is connected */
 +      unsigned isdn_up;               /* !=0 after gigaset_isdn_start() */
 +
 +      unsigned cidmode;
 +
 +      int myid;                       /* id for communication with LL */
 +      void *iif;                      /* LL interface structure */
 +      unsigned short hw_hdr_len;      /* headroom needed in data skbs */
 +
 +      struct reply_t *tabnocid;
 +      struct reply_t *tabcid;
 +      int cs_init;
 +      int ignoreframes;               /* frames to ignore after setting up the
 +                                         B channel */
 +      struct mutex mutex;             /* locks this structure:
 +                                       *   connected is not changed,
 +                                       *   hardware_up is not changed,
 +                                       *   MState is not changed to or from
 +                                       *   MS_LOCKED */
 +
 +      struct timer_list timer;
 +      int retry_count;
 +      int dle;                        /* !=0 if DLE mode is active
 +                                         (ZDLE=1 received -- M10x only) */
 +      int cur_at_seq;                 /* sequence of AT commands being
 +                                         processed */
 +      int curchannel;                 /* channel those commands are meant
 +                                         for */
 +      int commands_pending;           /* flag(s) in xxx.commands_pending have
 +                                         been set */
 +      struct tasklet_struct
 +              event_tasklet;          /* tasklet for serializing AT commands.
 +                                       * Scheduled
 +                                       *   -> for modem reponses (and
 +                                       *      incoming data for M10x)
 +                                       *   -> on timeout
 +                                       *   -> after setting bits in
 +                                       *      xxx.at_state.pending_command
 +                                       *      (e.g. command from LL) */
 +      struct tasklet_struct
 +              write_tasklet;          /* tasklet for serial output
 +                                       * (not used in base driver) */
 +
 +      /* event queue */
 +      struct event_t events[MAX_EVENTS];
 +      unsigned ev_tail, ev_head;
 +      spinlock_t ev_lock;
 +
 +      /* current modem response */
 +      unsigned char respdata[MAX_RESP_SIZE + 1];
 +      unsigned cbytes;
 +
 +      /* private data of hardware drivers */
 +      union {
 +              struct usb_cardstate *usb; /* USB hardware driver (m105) */
 +              struct ser_cardstate *ser; /* serial hardware driver */
 +              struct bas_cardstate *bas; /* USB hardware driver (base) */
 +      } hw;
 +};
 +
 +struct gigaset_driver {
 +      struct list_head list;
 +      spinlock_t lock;                /* locks minor tables and blocked */
 +      struct tty_driver *tty;
 +      unsigned have_tty;
 +      unsigned minor;
 +      unsigned minors;
 +      struct cardstate *cs;
 +      int blocked;
 +
 +      const struct gigaset_ops *ops;
 +      struct module *owner;
 +};
 +
 +struct cmdbuf_t {
 +      struct cmdbuf_t *next, *prev;
 +      int len, offset;
 +      struct tasklet_struct *wake_tasklet;
 +      unsigned char buf[0];
 +};
 +
 +struct bas_bc_state {
 +      /* isochronous output state */
 +      int             running;
 +      atomic_t        corrbytes;
 +      spinlock_t      isooutlock;
 +      struct isow_urbctx_t    isoouturbs[BAS_OUTURBS];
 +      struct isow_urbctx_t    *isooutdone, *isooutfree, *isooutovfl;
 +      struct isowbuf_t        *isooutbuf;
 +      unsigned numsub;                /* submitted URB counter
 +                                         (for diagnostic messages only) */
 +      struct tasklet_struct   sent_tasklet;
 +
 +      /* isochronous input state */
 +      spinlock_t isoinlock;
 +      struct urb *isoinurbs[BAS_INURBS];
 +      unsigned char isoinbuf[BAS_INBUFSIZE * BAS_INURBS];
 +      struct urb *isoindone;          /* completed isoc read URB */
 +      int isoinstatus;                /* status of completed URB */
 +      int loststatus;                 /* status of dropped URB */
 +      unsigned isoinlost;             /* number of bytes lost */
 +      /* state of bit unstuffing algorithm
 +         (in addition to BC_state.inputstate) */
 +      unsigned seqlen;                /* number of '1' bits not yet
 +                                         unstuffed */
 +      unsigned inbyte, inbits;        /* collected bits for next byte */
 +      /* statistics */
 +      unsigned goodbytes;             /* bytes correctly received */
 +      unsigned alignerrs;             /* frames with incomplete byte at end */
 +      unsigned fcserrs;               /* FCS errors */
 +      unsigned frameerrs;             /* framing errors */
 +      unsigned giants;                /* long frames */
 +      unsigned runts;                 /* short frames */
 +      unsigned aborts;                /* HDLC aborts */
 +      unsigned shared0s;              /* '0' bits shared between flags */
 +      unsigned stolen0s;              /* '0' stuff bits also serving as
 +                                         leading flag bits */
 +      struct tasklet_struct rcvd_tasklet;
 +};
 +
 +struct gigaset_ops {
 +      /* Called from ev-layer.c/interface.c for sending AT commands to the
 +         device */
 +      int (*write_cmd)(struct cardstate *cs, struct cmdbuf_t *cb);
 +
 +      /* Called from interface.c for additional device control */
 +      int (*write_room)(struct cardstate *cs);
 +      int (*chars_in_buffer)(struct cardstate *cs);
 +      int (*brkchars)(struct cardstate *cs, const unsigned char buf[6]);
 +
 +      /* Called from ev-layer.c after setting up connection
 +       * Should call gigaset_bchannel_up(), when finished. */
 +      int (*init_bchannel)(struct bc_state *bcs);
 +
 +      /* Called from ev-layer.c after hanging up
 +       * Should call gigaset_bchannel_down(), when finished. */
 +      int (*close_bchannel)(struct bc_state *bcs);
 +
 +      /* Called by gigaset_initcs() for setting up bcs->hw.xxx */
 +      int (*initbcshw)(struct bc_state *bcs);
 +
 +      /* Called by gigaset_freecs() for freeing bcs->hw.xxx */
 +      void (*freebcshw)(struct bc_state *bcs);
 +
 +      /* Called by gigaset_bchannel_down() for resetting bcs->hw.xxx */
 +      void (*reinitbcshw)(struct bc_state *bcs);
 +
 +      /* Called by gigaset_initcs() for setting up cs->hw.xxx */
 +      int (*initcshw)(struct cardstate *cs);
 +
 +      /* Called by gigaset_freecs() for freeing cs->hw.xxx */
 +      void (*freecshw)(struct cardstate *cs);
 +
 +      /* Called from common.c/interface.c for additional serial port
 +         control */
 +      int (*set_modem_ctrl)(struct cardstate *cs, unsigned old_state,
 +                            unsigned new_state);
 +      int (*baud_rate)(struct cardstate *cs, unsigned cflag);
 +      int (*set_line_ctrl)(struct cardstate *cs, unsigned cflag);
 +
 +      /* Called from LL interface to put an skb into the send-queue.
 +       * After sending is completed, gigaset_skb_sent() must be called
 +       * with the skb's link layer header preserved. */
 +      int (*send_skb)(struct bc_state *bcs, struct sk_buff *skb);
 +
 +      /* Called from ev-layer.c to process a block of data
 +       * received through the common/control channel. */
 +      void (*handle_input)(struct inbuf_t *inbuf);
 +
 +};
 +
 +/* = Common structures and definitions =======================================
 + */
 +
 +/* Parser states for DLE-Event:
 + * <DLE-EVENT>: <DLE_FLAG> "X" <EVENT> <DLE_FLAG> "."
 + * <DLE_FLAG>:  0x10
 + * <EVENT>:     ((a-z)* | (A-Z)* | (0-10)*)+
 + */
 +#define DLE_FLAG      0x10
 +
 +/* ===========================================================================
 + *  Functions implemented in asyncdata.c
 + */
 +
 +/* Called from LL interface to put an skb into the send queue. */
 +int gigaset_m10x_send_skb(struct bc_state *bcs, struct sk_buff *skb);
 +
 +/* Called from ev-layer.c to process a block of data
 + * received through the common/control channel. */
 +void gigaset_m10x_input(struct inbuf_t *inbuf);
 +
 +/* ===========================================================================
 + *  Functions implemented in isocdata.c
 + */
 +
 +/* Called from LL interface to put an skb into the send queue. */
 +int gigaset_isoc_send_skb(struct bc_state *bcs, struct sk_buff *skb);
 +
 +/* Called from ev-layer.c to process a block of data
 + * received through the common/control channel. */
 +void gigaset_isoc_input(struct inbuf_t *inbuf);
 +
 +/* Called from bas-gigaset.c to process a block of data
 + * received through the isochronous channel */
 +void gigaset_isoc_receive(unsigned char *src, unsigned count,
 +                        struct bc_state *bcs);
 +
 +/* Called from bas-gigaset.c to put a block of data
 + * into the isochronous output buffer */
 +int gigaset_isoc_buildframe(struct bc_state *bcs, unsigned char *in, int len);
 +
 +/* Called from bas-gigaset.c to initialize the isochronous output buffer */
 +void gigaset_isowbuf_init(struct isowbuf_t *iwb, unsigned char idle);
 +
 +/* Called from bas-gigaset.c to retrieve a block of bytes for sending */
 +int gigaset_isowbuf_getbytes(struct isowbuf_t *iwb, int size);
 +
 +/* ===========================================================================
 + *  Functions implemented in LL interface
 + */
 +
 +/* Called from common.c for setting up/shutting down with the ISDN subsystem */
 +void gigaset_isdn_regdrv(void);
 +void gigaset_isdn_unregdrv(void);
 +int gigaset_isdn_regdev(struct cardstate *cs, const char *isdnid);
 +void gigaset_isdn_unregdev(struct cardstate *cs);
 +
 +/* Called from hardware module to indicate completion of an skb */
 +void gigaset_skb_sent(struct bc_state *bcs, struct sk_buff *skb);
 +void gigaset_skb_rcvd(struct bc_state *bcs, struct sk_buff *skb);
 +void gigaset_isdn_rcv_err(struct bc_state *bcs);
 +
 +/* Called from common.c/ev-layer.c to indicate events relevant to the LL */
 +void gigaset_isdn_start(struct cardstate *cs);
 +void gigaset_isdn_stop(struct cardstate *cs);
 +int gigaset_isdn_icall(struct at_state_t *at_state);
 +void gigaset_isdn_connD(struct bc_state *bcs);
 +void gigaset_isdn_hupD(struct bc_state *bcs);
 +void gigaset_isdn_connB(struct bc_state *bcs);
 +void gigaset_isdn_hupB(struct bc_state *bcs);
 +
 +/* ===========================================================================
 + *  Functions implemented in ev-layer.c
 + */
 +
 +/* tasklet called from common.c to process queued events */
 +void gigaset_handle_event(unsigned long data);
 +
 +/* called from isocdata.c / asyncdata.c
 + * when a complete modem response line has been received */
 +void gigaset_handle_modem_response(struct cardstate *cs);
 +
 +/* ===========================================================================
 + *  Functions implemented in proc.c
 + */
 +
 +/* initialize sysfs for device */
 +void gigaset_init_dev_sysfs(struct cardstate *cs);
 +void gigaset_free_dev_sysfs(struct cardstate *cs);
 +
 +/* ===========================================================================
 + *  Functions implemented in common.c/gigaset.h
 + */
 +
 +void gigaset_bcs_reinit(struct bc_state *bcs);
 +void gigaset_at_init(struct at_state_t *at_state, struct bc_state *bcs,
 +                   struct cardstate *cs, int cid);
 +int gigaset_get_channel(struct bc_state *bcs);
 +struct bc_state *gigaset_get_free_channel(struct cardstate *cs);
 +void gigaset_free_channel(struct bc_state *bcs);
 +int gigaset_get_channels(struct cardstate *cs);
 +void gigaset_free_channels(struct cardstate *cs);
 +void gigaset_block_channels(struct cardstate *cs);
 +
 +/* Allocate and initialize driver structure. */
 +struct gigaset_driver *gigaset_initdriver(unsigned minor, unsigned minors,
 +                                        const char *procname,
 +                                        const char *devname,
 +                                        const struct gigaset_ops *ops,
 +                                        struct module *owner);
 +
 +/* Deallocate driver structure. */
 +void gigaset_freedriver(struct gigaset_driver *drv);
 +
 +struct cardstate *gigaset_get_cs_by_tty(struct tty_struct *tty);
 +struct cardstate *gigaset_get_cs_by_id(int id);
 +void gigaset_blockdriver(struct gigaset_driver *drv);
 +
 +/* Allocate and initialize card state. Calls hardware dependent
 +   gigaset_init[b]cs(). */
 +struct cardstate *gigaset_initcs(struct gigaset_driver *drv, int channels,
 +                               int onechannel, int ignoreframes,
 +                               int cidmode, const char *modulename);
 +
 +/* Free card state. Calls hardware dependent gigaset_free[b]cs(). */
 +void gigaset_freecs(struct cardstate *cs);
 +
 +/* Tell common.c that hardware and driver are ready. */
 +int gigaset_start(struct cardstate *cs);
 +
 +/* Tell common.c that the device is not present any more. */
 +void gigaset_stop(struct cardstate *cs);
 +
 +/* Tell common.c that the driver is being unloaded. */
 +int gigaset_shutdown(struct cardstate *cs);
 +
 +/* Append event to the queue.
 + * Returns NULL on failure or a pointer to the event on success.
 + * ptr must be kmalloc()ed (and not be freed by the caller).
 + */
 +struct event_t *gigaset_add_event(struct cardstate *cs,
 +                                struct at_state_t *at_state, int type,
 +                                void *ptr, int parameter, void *arg);
 +
 +/* Called on CONFIG1 command from frontend. */
 +int gigaset_enterconfigmode(struct cardstate *cs);
 +
 +/* cs->lock must not be locked */
 +static inline void gigaset_schedule_event(struct cardstate *cs)
 +{
 +      unsigned long flags;
 +      spin_lock_irqsave(&cs->lock, flags);
 +      if (cs->running)
 +              tasklet_schedule(&cs->event_tasklet);
 +      spin_unlock_irqrestore(&cs->lock, flags);
 +}
 +
 +/* Tell common.c that B channel has been closed. */
 +/* cs->lock must not be locked */
 +static inline void gigaset_bchannel_down(struct bc_state *bcs)
 +{
 +      gigaset_add_event(bcs->cs, &bcs->at_state, EV_BC_CLOSED, NULL, 0, NULL);
 +      gigaset_schedule_event(bcs->cs);
 +}
 +
 +/* Tell common.c that B channel has been opened. */
 +/* cs->lock must not be locked */
 +static inline void gigaset_bchannel_up(struct bc_state *bcs)
 +{
 +      gigaset_add_event(bcs->cs, &bcs->at_state, EV_BC_OPEN, NULL, 0, NULL);
 +      gigaset_schedule_event(bcs->cs);
 +}
 +
 +/* set up next receive skb for data mode */
 +static inline struct sk_buff *gigaset_new_rx_skb(struct bc_state *bcs)
 +{
 +      struct cardstate *cs = bcs->cs;
 +      unsigned short hw_hdr_len = cs->hw_hdr_len;
 +
 +      if (bcs->ignore) {
 +              bcs->rx_skb = NULL;
 +      } else {
 +              bcs->rx_skb = dev_alloc_skb(bcs->rx_bufsize + hw_hdr_len);
 +              if (bcs->rx_skb == NULL)
 +                      dev_warn(cs->dev, "could not allocate skb\n");
 +              else
 +                      skb_reserve(bcs->rx_skb, hw_hdr_len);
 +      }
 +      return bcs->rx_skb;
 +}
 +
 +/* append received bytes to inbuf */
 +int gigaset_fill_inbuf(struct inbuf_t *inbuf, const unsigned char *src,
 +                     unsigned numbytes);
 +
 +/* ===========================================================================
 + *  Functions implemented in interface.c
 + */
 +
 +/* initialize interface */
 +void gigaset_if_initdriver(struct gigaset_driver *drv, const char *procname,
 +                         const char *devname);
 +/* release interface */
 +void gigaset_if_freedriver(struct gigaset_driver *drv);
 +/* add minor */
 +void gigaset_if_init(struct cardstate *cs);
 +/* remove minor */
 +void gigaset_if_free(struct cardstate *cs);
 +/* device received data */
 +void gigaset_if_receive(struct cardstate *cs,
 +                      unsigned char *buffer, size_t len);
 +
 +#endif
index d9a578a,0000000..17fa615
mode 100644,000000..100644
--- /dev/null
@@@ -1,616 -1,0 +1,613 @@@
-  *    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.
++// SPDX-License-Identifier: GPL-2.0-or-later
 +/*
 + * interface to user space for the gigaset driver
 + *
 + * Copyright (c) 2004 by Hansjoerg Lipp <hjlipp@web.de>
 + *
 + * =====================================================================
 + * =====================================================================
 + */
 +
 +#include "gigaset.h"
 +#include <linux/gigaset_dev.h>
 +#include <linux/tty_flip.h>
 +#include <linux/module.h>
 +
 +/*** our ioctls ***/
 +
 +static int if_lock(struct cardstate *cs, int *arg)
 +{
 +      int cmd = *arg;
 +
 +      gig_dbg(DEBUG_IF, "%u: if_lock (%d)", cs->minor_index, cmd);
 +
 +      if (cmd > 1)
 +              return -EINVAL;
 +
 +      if (cmd < 0) {
 +              *arg = cs->mstate == MS_LOCKED;
 +              return 0;
 +      }
 +
 +      if (!cmd && cs->mstate == MS_LOCKED && cs->connected) {
 +              cs->ops->set_modem_ctrl(cs, 0, TIOCM_DTR | TIOCM_RTS);
 +              cs->ops->baud_rate(cs, B115200);
 +              cs->ops->set_line_ctrl(cs, CS8);
 +              cs->control_state = TIOCM_DTR | TIOCM_RTS;
 +      }
 +
 +      cs->waiting = 1;
 +      if (!gigaset_add_event(cs, &cs->at_state, EV_IF_LOCK,
 +                             NULL, cmd, NULL)) {
 +              cs->waiting = 0;
 +              return -ENOMEM;
 +      }
 +      gigaset_schedule_event(cs);
 +
 +      wait_event(cs->waitqueue, !cs->waiting);
 +
 +      if (cs->cmd_result >= 0) {
 +              *arg = cs->cmd_result;
 +              return 0;
 +      }
 +
 +      return cs->cmd_result;
 +}
 +
 +static int if_version(struct cardstate *cs, unsigned arg[4])
 +{
 +      static const unsigned version[4] = GIG_VERSION;
 +      static const unsigned compat[4] = GIG_COMPAT;
 +      unsigned cmd = arg[0];
 +
 +      gig_dbg(DEBUG_IF, "%u: if_version (%d)", cs->minor_index, cmd);
 +
 +      switch (cmd) {
 +      case GIGVER_DRIVER:
 +              memcpy(arg, version, sizeof version);
 +              return 0;
 +      case GIGVER_COMPAT:
 +              memcpy(arg, compat, sizeof compat);
 +              return 0;
 +      case GIGVER_FWBASE:
 +              cs->waiting = 1;
 +              if (!gigaset_add_event(cs, &cs->at_state, EV_IF_VER,
 +                                     NULL, 0, arg)) {
 +                      cs->waiting = 0;
 +                      return -ENOMEM;
 +              }
 +              gigaset_schedule_event(cs);
 +
 +              wait_event(cs->waitqueue, !cs->waiting);
 +
 +              if (cs->cmd_result >= 0)
 +                      return 0;
 +
 +              return cs->cmd_result;
 +      default:
 +              return -EINVAL;
 +      }
 +}
 +
 +static int if_config(struct cardstate *cs, int *arg)
 +{
 +      gig_dbg(DEBUG_IF, "%u: if_config (%d)", cs->minor_index, *arg);
 +
 +      if (*arg != 1)
 +              return -EINVAL;
 +
 +      if (cs->mstate != MS_LOCKED)
 +              return -EBUSY;
 +
 +      if (!cs->connected) {
 +              pr_err("%s: not connected\n", __func__);
 +              return -ENODEV;
 +      }
 +
 +      *arg = 0;
 +      return gigaset_enterconfigmode(cs);
 +}
 +
 +/*** the terminal driver ***/
 +
 +static int if_open(struct tty_struct *tty, struct file *filp)
 +{
 +      struct cardstate *cs;
 +
 +      gig_dbg(DEBUG_IF, "%d+%d: %s()",
 +              tty->driver->minor_start, tty->index, __func__);
 +
 +      cs = gigaset_get_cs_by_tty(tty);
 +      if (!cs || !try_module_get(cs->driver->owner))
 +              return -ENODEV;
 +
 +      if (mutex_lock_interruptible(&cs->mutex)) {
 +              module_put(cs->driver->owner);
 +              return -ERESTARTSYS;
 +      }
 +      tty->driver_data = cs;
 +
 +      ++cs->port.count;
 +
 +      if (cs->port.count == 1) {
 +              tty_port_tty_set(&cs->port, tty);
 +              cs->port.low_latency = 1;
 +      }
 +
 +      mutex_unlock(&cs->mutex);
 +      return 0;
 +}
 +
 +static void if_close(struct tty_struct *tty, struct file *filp)
 +{
 +      struct cardstate *cs = tty->driver_data;
 +
 +      if (!cs) { /* happens if we didn't find cs in open */
 +              gig_dbg(DEBUG_IF, "%s: no cardstate", __func__);
 +              return;
 +      }
 +
 +      gig_dbg(DEBUG_IF, "%u: %s()", cs->minor_index, __func__);
 +
 +      mutex_lock(&cs->mutex);
 +
 +      if (!cs->connected)
 +              gig_dbg(DEBUG_IF, "not connected");     /* nothing to do */
 +      else if (!cs->port.count)
 +              dev_warn(cs->dev, "%s: device not opened\n", __func__);
 +      else if (!--cs->port.count)
 +              tty_port_tty_set(&cs->port, NULL);
 +
 +      mutex_unlock(&cs->mutex);
 +
 +      module_put(cs->driver->owner);
 +}
 +
 +static int if_ioctl(struct tty_struct *tty,
 +                  unsigned int cmd, unsigned long arg)
 +{
 +      struct cardstate *cs = tty->driver_data;
 +      int retval = -ENODEV;
 +      int int_arg;
 +      unsigned char buf[6];
 +      unsigned version[4];
 +
 +      gig_dbg(DEBUG_IF, "%u: %s(0x%x)", cs->minor_index, __func__, cmd);
 +
 +      if (mutex_lock_interruptible(&cs->mutex))
 +              return -ERESTARTSYS;
 +
 +      if (!cs->connected) {
 +              gig_dbg(DEBUG_IF, "not connected");
 +              retval = -ENODEV;
 +      } else {
 +              retval = 0;
 +              switch (cmd) {
 +              case GIGASET_REDIR:
 +                      retval = get_user(int_arg, (int __user *) arg);
 +                      if (retval >= 0)
 +                              retval = if_lock(cs, &int_arg);
 +                      if (retval >= 0)
 +                              retval = put_user(int_arg, (int __user *) arg);
 +                      break;
 +              case GIGASET_CONFIG:
 +                      retval = get_user(int_arg, (int __user *) arg);
 +                      if (retval >= 0)
 +                              retval = if_config(cs, &int_arg);
 +                      if (retval >= 0)
 +                              retval = put_user(int_arg, (int __user *) arg);
 +                      break;
 +              case GIGASET_BRKCHARS:
 +                      retval = copy_from_user(&buf,
 +                                              (const unsigned char __user *) arg, 6)
 +                              ? -EFAULT : 0;
 +                      if (retval >= 0) {
 +                              gigaset_dbg_buffer(DEBUG_IF, "GIGASET_BRKCHARS",
 +                                                 6, buf);
 +                              retval = cs->ops->brkchars(cs, buf);
 +                      }
 +                      break;
 +              case GIGASET_VERSION:
 +                      retval = copy_from_user(version,
 +                                              (unsigned __user *) arg, sizeof version)
 +                              ? -EFAULT : 0;
 +                      if (retval >= 0)
 +                              retval = if_version(cs, version);
 +                      if (retval >= 0)
 +                              retval = copy_to_user((unsigned __user *) arg,
 +                                                    version, sizeof version)
 +                                      ? -EFAULT : 0;
 +                      break;
 +              default:
 +                      gig_dbg(DEBUG_IF, "%s: arg not supported - 0x%04x",
 +                              __func__, cmd);
 +                      retval = -ENOIOCTLCMD;
 +              }
 +      }
 +
 +      mutex_unlock(&cs->mutex);
 +
 +      return retval;
 +}
 +
 +#ifdef CONFIG_COMPAT
 +static long if_compat_ioctl(struct tty_struct *tty,
 +                  unsigned int cmd, unsigned long arg)
 +{
 +      return if_ioctl(tty, cmd, (unsigned long)compat_ptr(arg));
 +}
 +#endif
 +
 +static int if_tiocmget(struct tty_struct *tty)
 +{
 +      struct cardstate *cs = tty->driver_data;
 +      int retval;
 +
 +      gig_dbg(DEBUG_IF, "%u: %s()", cs->minor_index, __func__);
 +
 +      if (mutex_lock_interruptible(&cs->mutex))
 +              return -ERESTARTSYS;
 +
 +      retval = cs->control_state & (TIOCM_RTS | TIOCM_DTR);
 +
 +      mutex_unlock(&cs->mutex);
 +
 +      return retval;
 +}
 +
 +static int if_tiocmset(struct tty_struct *tty,
 +                     unsigned int set, unsigned int clear)
 +{
 +      struct cardstate *cs = tty->driver_data;
 +      int retval;
 +      unsigned mc;
 +
 +      gig_dbg(DEBUG_IF, "%u: %s(0x%x, 0x%x)",
 +              cs->minor_index, __func__, set, clear);
 +
 +      if (mutex_lock_interruptible(&cs->mutex))
 +              return -ERESTARTSYS;
 +
 +      if (!cs->connected) {
 +              gig_dbg(DEBUG_IF, "not connected");
 +              retval = -ENODEV;
 +      } else {
 +              mc = (cs->control_state | set) & ~clear & (TIOCM_RTS | TIOCM_DTR);
 +              retval = cs->ops->set_modem_ctrl(cs, cs->control_state, mc);
 +              cs->control_state = mc;
 +      }
 +
 +      mutex_unlock(&cs->mutex);
 +
 +      return retval;
 +}
 +
 +static int if_write(struct tty_struct *tty, const unsigned char *buf, int count)
 +{
 +      struct cardstate *cs = tty->driver_data;
 +      struct cmdbuf_t *cb;
 +      int retval;
 +
 +      gig_dbg(DEBUG_IF, "%u: %s()", cs->minor_index, __func__);
 +
 +      if (mutex_lock_interruptible(&cs->mutex))
 +              return -ERESTARTSYS;
 +
 +      if (!cs->connected) {
 +              gig_dbg(DEBUG_IF, "not connected");
 +              retval = -ENODEV;
 +              goto done;
 +      }
 +      if (cs->mstate != MS_LOCKED) {
 +              dev_warn(cs->dev, "can't write to unlocked device\n");
 +              retval = -EBUSY;
 +              goto done;
 +      }
 +      if (count <= 0) {
 +              /* nothing to do */
 +              retval = 0;
 +              goto done;
 +      }
 +
 +      cb = kmalloc(sizeof(struct cmdbuf_t) + count, GFP_KERNEL);
 +      if (!cb) {
 +              dev_err(cs->dev, "%s: out of memory\n", __func__);
 +              retval = -ENOMEM;
 +              goto done;
 +      }
 +
 +      memcpy(cb->buf, buf, count);
 +      cb->len = count;
 +      cb->offset = 0;
 +      cb->next = NULL;
 +      cb->wake_tasklet = &cs->if_wake_tasklet;
 +      retval = cs->ops->write_cmd(cs, cb);
 +done:
 +      mutex_unlock(&cs->mutex);
 +      return retval;
 +}
 +
 +static int if_write_room(struct tty_struct *tty)
 +{
 +      struct cardstate *cs = tty->driver_data;
 +      int retval;
 +
 +      gig_dbg(DEBUG_IF, "%u: %s()", cs->minor_index, __func__);
 +
 +      if (mutex_lock_interruptible(&cs->mutex))
 +              return -ERESTARTSYS;
 +
 +      if (!cs->connected) {
 +              gig_dbg(DEBUG_IF, "not connected");
 +              retval = -ENODEV;
 +      } else if (cs->mstate != MS_LOCKED) {
 +              dev_warn(cs->dev, "can't write to unlocked device\n");
 +              retval = -EBUSY;
 +      } else
 +              retval = cs->ops->write_room(cs);
 +
 +      mutex_unlock(&cs->mutex);
 +
 +      return retval;
 +}
 +
 +static int if_chars_in_buffer(struct tty_struct *tty)
 +{
 +      struct cardstate *cs = tty->driver_data;
 +      int retval = 0;
 +
 +      gig_dbg(DEBUG_IF, "%u: %s()", cs->minor_index, __func__);
 +
 +      mutex_lock(&cs->mutex);
 +
 +      if (!cs->connected)
 +              gig_dbg(DEBUG_IF, "not connected");
 +      else if (cs->mstate != MS_LOCKED)
 +              dev_warn(cs->dev, "can't write to unlocked device\n");
 +      else
 +              retval = cs->ops->chars_in_buffer(cs);
 +
 +      mutex_unlock(&cs->mutex);
 +
 +      return retval;
 +}
 +
 +static void if_throttle(struct tty_struct *tty)
 +{
 +      struct cardstate *cs = tty->driver_data;
 +
 +      gig_dbg(DEBUG_IF, "%u: %s()", cs->minor_index, __func__);
 +
 +      mutex_lock(&cs->mutex);
 +
 +      if (!cs->connected)
 +              gig_dbg(DEBUG_IF, "not connected");     /* nothing to do */
 +      else
 +              gig_dbg(DEBUG_IF, "%s: not implemented\n", __func__);
 +
 +      mutex_unlock(&cs->mutex);
 +}
 +
 +static void if_unthrottle(struct tty_struct *tty)
 +{
 +      struct cardstate *cs = tty->driver_data;
 +
 +      gig_dbg(DEBUG_IF, "%u: %s()", cs->minor_index, __func__);
 +
 +      mutex_lock(&cs->mutex);
 +
 +      if (!cs->connected)
 +              gig_dbg(DEBUG_IF, "not connected");     /* nothing to do */
 +      else
 +              gig_dbg(DEBUG_IF, "%s: not implemented\n", __func__);
 +
 +      mutex_unlock(&cs->mutex);
 +}
 +
 +static void if_set_termios(struct tty_struct *tty, struct ktermios *old)
 +{
 +      struct cardstate *cs = tty->driver_data;
 +      unsigned int iflag;
 +      unsigned int cflag;
 +      unsigned int old_cflag;
 +      unsigned int control_state, new_state;
 +
 +      gig_dbg(DEBUG_IF, "%u: %s()", cs->minor_index, __func__);
 +
 +      mutex_lock(&cs->mutex);
 +
 +      if (!cs->connected) {
 +              gig_dbg(DEBUG_IF, "not connected");
 +              goto out;
 +      }
 +
 +      iflag = tty->termios.c_iflag;
 +      cflag = tty->termios.c_cflag;
 +      old_cflag = old ? old->c_cflag : cflag;
 +      gig_dbg(DEBUG_IF, "%u: iflag %x cflag %x old %x",
 +              cs->minor_index, iflag, cflag, old_cflag);
 +
 +      /* get a local copy of the current port settings */
 +      control_state = cs->control_state;
 +
 +      /*
 +       * Update baud rate.
 +       * Do not attempt to cache old rates and skip settings,
 +       * disconnects screw such tricks up completely.
 +       * Premature optimization is the root of all evil.
 +       */
 +
 +      /* reassert DTR and (maybe) RTS on transition from B0 */
 +      if ((old_cflag & CBAUD) == B0) {
 +              new_state = control_state | TIOCM_DTR;
 +              /* don't set RTS if using hardware flow control */
 +              if (!(old_cflag & CRTSCTS))
 +                      new_state |= TIOCM_RTS;
 +              gig_dbg(DEBUG_IF, "%u: from B0 - set DTR%s",
 +                      cs->minor_index,
 +                      (new_state & TIOCM_RTS) ? " only" : "/RTS");
 +              cs->ops->set_modem_ctrl(cs, control_state, new_state);
 +              control_state = new_state;
 +      }
 +
 +      cs->ops->baud_rate(cs, cflag & CBAUD);
 +
 +      if ((cflag & CBAUD) == B0) {
 +              /* Drop RTS and DTR */
 +              gig_dbg(DEBUG_IF, "%u: to B0 - drop DTR/RTS", cs->minor_index);
 +              new_state = control_state & ~(TIOCM_DTR | TIOCM_RTS);
 +              cs->ops->set_modem_ctrl(cs, control_state, new_state);
 +              control_state = new_state;
 +      }
 +
 +      /*
 +       * Update line control register (LCR)
 +       */
 +
 +      cs->ops->set_line_ctrl(cs, cflag);
 +
 +      /* save off the modified port settings */
 +      cs->control_state = control_state;
 +
 +out:
 +      mutex_unlock(&cs->mutex);
 +}
 +
 +static const struct tty_operations if_ops = {
 +      .open =                 if_open,
 +      .close =                if_close,
 +      .ioctl =                if_ioctl,
 +#ifdef CONFIG_COMPAT
 +      .compat_ioctl =         if_compat_ioctl,
 +#endif
 +      .write =                if_write,
 +      .write_room =           if_write_room,
 +      .chars_in_buffer =      if_chars_in_buffer,
 +      .set_termios =          if_set_termios,
 +      .throttle =             if_throttle,
 +      .unthrottle =           if_unthrottle,
 +      .tiocmget =             if_tiocmget,
 +      .tiocmset =             if_tiocmset,
 +};
 +
 +
 +/* wakeup tasklet for the write operation */
 +static void if_wake(unsigned long data)
 +{
 +      struct cardstate *cs = (struct cardstate *)data;
 +
 +      tty_port_tty_wakeup(&cs->port);
 +}
 +
 +/*** interface to common ***/
 +
 +void gigaset_if_init(struct cardstate *cs)
 +{
 +      struct gigaset_driver *drv;
 +
 +      drv = cs->driver;
 +      if (!drv->have_tty)
 +              return;
 +
 +      tasklet_init(&cs->if_wake_tasklet, if_wake, (unsigned long) cs);
 +
 +      mutex_lock(&cs->mutex);
 +      cs->tty_dev = tty_port_register_device(&cs->port, drv->tty,
 +                      cs->minor_index, NULL);
 +
 +      if (!IS_ERR(cs->tty_dev))
 +              dev_set_drvdata(cs->tty_dev, cs);
 +      else {
 +              pr_warning("could not register device to the tty subsystem\n");
 +              cs->tty_dev = NULL;
 +      }
 +      mutex_unlock(&cs->mutex);
 +}
 +
 +void gigaset_if_free(struct cardstate *cs)
 +{
 +      struct gigaset_driver *drv;
 +
 +      drv = cs->driver;
 +      if (!drv->have_tty)
 +              return;
 +
 +      tasklet_disable(&cs->if_wake_tasklet);
 +      tasklet_kill(&cs->if_wake_tasklet);
 +      cs->tty_dev = NULL;
 +      tty_unregister_device(drv->tty, cs->minor_index);
 +}
 +
 +/**
 + * gigaset_if_receive() - pass a received block of data to the tty device
 + * @cs:               device descriptor structure.
 + * @buffer:   received data.
 + * @len:      number of bytes received.
 + *
 + * Called by asyncdata/isocdata if a block of data received from the
 + * device must be sent to userspace through the ttyG* device.
 + */
 +void gigaset_if_receive(struct cardstate *cs,
 +                      unsigned char *buffer, size_t len)
 +{
 +      tty_insert_flip_string(&cs->port, buffer, len);
 +      tty_flip_buffer_push(&cs->port);
 +}
 +EXPORT_SYMBOL_GPL(gigaset_if_receive);
 +
 +/* gigaset_if_initdriver
 + * Initialize tty interface.
 + * parameters:
 + *    drv             Driver
 + *    procname        Name of the driver (e.g. for /proc/tty/drivers)
 + *    devname         Name of the device files (prefix without minor number)
 + */
 +void gigaset_if_initdriver(struct gigaset_driver *drv, const char *procname,
 +                         const char *devname)
 +{
 +      int ret;
 +      struct tty_driver *tty;
 +
 +      drv->have_tty = 0;
 +
 +      drv->tty = tty = alloc_tty_driver(drv->minors);
 +      if (tty == NULL)
 +              goto enomem;
 +
 +      tty->type =             TTY_DRIVER_TYPE_SERIAL;
 +      tty->subtype =          SERIAL_TYPE_NORMAL;
 +      tty->flags =            TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
 +
 +      tty->driver_name =      procname;
 +      tty->name =             devname;
 +      tty->minor_start =      drv->minor;
 +
 +      tty->init_termios          = tty_std_termios;
 +      tty->init_termios.c_cflag  = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
 +      tty_set_operations(tty, &if_ops);
 +
 +      ret = tty_register_driver(tty);
 +      if (ret < 0) {
 +              pr_err("error %d registering tty driver\n", ret);
 +              goto error;
 +      }
 +      gig_dbg(DEBUG_IF, "tty driver initialized");
 +      drv->have_tty = 1;
 +      return;
 +
 +enomem:
 +      pr_err("out of memory\n");
 +error:
 +      if (drv->tty)
 +              put_tty_driver(drv->tty);
 +}
 +
 +void gigaset_if_freedriver(struct gigaset_driver *drv)
 +{
 +      if (!drv->have_tty)
 +              return;
 +
 +      drv->have_tty = 0;
 +      tty_unregister_driver(drv->tty);
 +      put_tty_driver(drv->tty);
 +}
index f9264ba,0000000..3ecf6e3
mode 100644,000000..100644
--- /dev/null
@@@ -1,1009 -1,0 +1,1006 @@@
-  *    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.
++// SPDX-License-Identifier: GPL-2.0-or-later
 +/*
 + * Common data handling layer for bas_gigaset
 + *
 + * Copyright (c) 2005 by Tilman Schmidt <tilman@imap.cc>,
 + *                       Hansjoerg Lipp <hjlipp@web.de>.
 + *
 + * =====================================================================
 + * =====================================================================
 + */
 +
 +#include "gigaset.h"
 +#include <linux/crc-ccitt.h>
 +#include <linux/bitrev.h>
 +
 +/* access methods for isowbuf_t */
 +/* ============================ */
 +
 +/* initialize buffer structure
 + */
 +void gigaset_isowbuf_init(struct isowbuf_t *iwb, unsigned char idle)
 +{
 +      iwb->read = 0;
 +      iwb->nextread = 0;
 +      iwb->write = 0;
 +      atomic_set(&iwb->writesem, 1);
 +      iwb->wbits = 0;
 +      iwb->idle = idle;
 +      memset(iwb->data + BAS_OUTBUFSIZE, idle, BAS_OUTBUFPAD);
 +}
 +
 +/* compute number of bytes which can be appended to buffer
 + * so that there is still room to append a maximum frame of flags
 + */
 +static inline int isowbuf_freebytes(struct isowbuf_t *iwb)
 +{
 +      int read, write, freebytes;
 +
 +      read = iwb->read;
 +      write = iwb->write;
 +      freebytes = read - write;
 +      if (freebytes > 0) {
 +              /* no wraparound: need padding space within regular area */
 +              return freebytes - BAS_OUTBUFPAD;
 +      } else if (read < BAS_OUTBUFPAD) {
 +              /* wraparound: can use space up to end of regular area */
 +              return BAS_OUTBUFSIZE - write;
 +      } else {
 +              /* following the wraparound yields more space */
 +              return freebytes + BAS_OUTBUFSIZE - BAS_OUTBUFPAD;
 +      }
 +}
 +
 +/* start writing
 + * acquire the write semaphore
 + * return 0 if acquired, <0 if busy
 + */
 +static inline int isowbuf_startwrite(struct isowbuf_t *iwb)
 +{
 +      if (!atomic_dec_and_test(&iwb->writesem)) {
 +              atomic_inc(&iwb->writesem);
 +              gig_dbg(DEBUG_ISO, "%s: couldn't acquire iso write semaphore",
 +                      __func__);
 +              return -EBUSY;
 +      }
 +      gig_dbg(DEBUG_ISO,
 +              "%s: acquired iso write semaphore, data[write]=%02x, nbits=%d",
 +              __func__, iwb->data[iwb->write], iwb->wbits);
 +      return 0;
 +}
 +
 +/* finish writing
 + * release the write semaphore
 + * returns the current write position
 + */
 +static inline int isowbuf_donewrite(struct isowbuf_t *iwb)
 +{
 +      int write = iwb->write;
 +      atomic_inc(&iwb->writesem);
 +      return write;
 +}
 +
 +/* append bits to buffer without any checks
 + * - data contains bits to append, starting at LSB
 + * - nbits is number of bits to append (0..24)
 + * must be called with the write semaphore held
 + * If more than nbits bits are set in data, the extraneous bits are set in the
 + * buffer too, but the write position is only advanced by nbits.
 + */
 +static inline void isowbuf_putbits(struct isowbuf_t *iwb, u32 data, int nbits)
 +{
 +      int write = iwb->write;
 +      data <<= iwb->wbits;
 +      data |= iwb->data[write];
 +      nbits += iwb->wbits;
 +      while (nbits >= 8) {
 +              iwb->data[write++] = data & 0xff;
 +              write %= BAS_OUTBUFSIZE;
 +              data >>= 8;
 +              nbits -= 8;
 +      }
 +      iwb->wbits = nbits;
 +      iwb->data[write] = data & 0xff;
 +      iwb->write = write;
 +}
 +
 +/* put final flag on HDLC bitstream
 + * also sets the idle fill byte to the correspondingly shifted flag pattern
 + * must be called with the write semaphore held
 + */
 +static inline void isowbuf_putflag(struct isowbuf_t *iwb)
 +{
 +      int write;
 +
 +      /* add two flags, thus reliably covering one byte */
 +      isowbuf_putbits(iwb, 0x7e7e, 8);
 +      /* recover the idle flag byte */
 +      write = iwb->write;
 +      iwb->idle = iwb->data[write];
 +      gig_dbg(DEBUG_ISO, "idle fill byte %02x", iwb->idle);
 +      /* mask extraneous bits in buffer */
 +      iwb->data[write] &= (1 << iwb->wbits) - 1;
 +}
 +
 +/* retrieve a block of bytes for sending
 + * The requested number of bytes is provided as a contiguous block.
 + * If necessary, the frame is filled to the requested number of bytes
 + * with the idle value.
 + * returns offset to frame, < 0 on busy or error
 + */
 +int gigaset_isowbuf_getbytes(struct isowbuf_t *iwb, int size)
 +{
 +      int read, write, limit, src, dst;
 +      unsigned char pbyte;
 +
 +      read = iwb->nextread;
 +      write = iwb->write;
 +      if (likely(read == write)) {
 +              /* return idle frame */
 +              return read < BAS_OUTBUFPAD ?
 +                      BAS_OUTBUFSIZE : read - BAS_OUTBUFPAD;
 +      }
 +
 +      limit = read + size;
 +      gig_dbg(DEBUG_STREAM, "%s: read=%d write=%d limit=%d",
 +              __func__, read, write, limit);
 +#ifdef CONFIG_GIGASET_DEBUG
 +      if (unlikely(size < 0 || size > BAS_OUTBUFPAD)) {
 +              pr_err("invalid size %d\n", size);
 +              return -EINVAL;
 +      }
 +#endif
 +
 +      if (read < write) {
 +              /* no wraparound in valid data */
 +              if (limit >= write) {
 +                      /* append idle frame */
 +                      if (isowbuf_startwrite(iwb) < 0)
 +                              return -EBUSY;
 +                      /* write position could have changed */
 +                      write = iwb->write;
 +                      if (limit >= write) {
 +                              pbyte = iwb->data[write]; /* save
 +                                                           partial byte */
 +                              limit = write + BAS_OUTBUFPAD;
 +                              gig_dbg(DEBUG_STREAM,
 +                                      "%s: filling %d->%d with %02x",
 +                                      __func__, write, limit, iwb->idle);
 +                              if (write + BAS_OUTBUFPAD < BAS_OUTBUFSIZE)
 +                                      memset(iwb->data + write, iwb->idle,
 +                                             BAS_OUTBUFPAD);
 +                              else {
 +                                      /* wraparound, fill entire pad area */
 +                                      memset(iwb->data + write, iwb->idle,
 +                                             BAS_OUTBUFSIZE + BAS_OUTBUFPAD
 +                                             - write);
 +                                      limit = 0;
 +                              }
 +                              gig_dbg(DEBUG_STREAM,
 +                                      "%s: restoring %02x at %d",
 +                                      __func__, pbyte, limit);
 +                              iwb->data[limit] = pbyte; /* restore
 +                                                           partial byte */
 +                              iwb->write = limit;
 +                      }
 +                      isowbuf_donewrite(iwb);
 +              }
 +      } else {
 +              /* valid data wraparound */
 +              if (limit >= BAS_OUTBUFSIZE) {
 +                      /* copy wrapped part into pad area */
 +                      src = 0;
 +                      dst = BAS_OUTBUFSIZE;
 +                      while (dst < limit && src < write)
 +                              iwb->data[dst++] = iwb->data[src++];
 +                      if (dst <= limit) {
 +                              /* fill pad area with idle byte */
 +                              memset(iwb->data + dst, iwb->idle,
 +                                     BAS_OUTBUFSIZE + BAS_OUTBUFPAD - dst);
 +                      }
 +                      limit = src;
 +              }
 +      }
 +      iwb->nextread = limit;
 +      return read;
 +}
 +
 +/* dump_bytes
 + * write hex bytes to syslog for debugging
 + */
 +static inline void dump_bytes(enum debuglevel level, const char *tag,
 +                            unsigned char *bytes, int count)
 +{
 +#ifdef CONFIG_GIGASET_DEBUG
 +      unsigned char c;
 +      static char dbgline[3 * 32 + 1];
 +      int i = 0;
 +
 +      if (!(gigaset_debuglevel & level))
 +              return;
 +
 +      while (count-- > 0) {
 +              if (i > sizeof(dbgline) - 4) {
 +                      dbgline[i] = '\0';
 +                      gig_dbg(level, "%s:%s", tag, dbgline);
 +                      i = 0;
 +              }
 +              c = *bytes++;
 +              dbgline[i] = (i && !(i % 12)) ? '-' : ' ';
 +              i++;
 +              dbgline[i++] = hex_asc_hi(c);
 +              dbgline[i++] = hex_asc_lo(c);
 +      }
 +      dbgline[i] = '\0';
 +      gig_dbg(level, "%s:%s", tag, dbgline);
 +#endif
 +}
 +
 +/*============================================================================*/
 +
 +/* bytewise HDLC bitstuffing via table lookup
 + * lookup table: 5 subtables for 0..4 preceding consecutive '1' bits
 + * index: 256*(number of preceding '1' bits) + (next byte to stuff)
 + * value: bit  9.. 0 = result bits
 + *        bit 12..10 = number of trailing '1' bits in result
 + *        bit 14..13 = number of bits added by stuffing
 + */
 +static const u16 stufftab[5 * 256] = {
 +/* previous 1s = 0: */
 +      0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
 +      0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x201f,
 +      0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
 +      0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x203e, 0x205f,
 +      0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
 +      0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x209f,
 +      0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
 +      0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x207c, 0x207d, 0x20be, 0x20df,
 +      0x0480, 0x0481, 0x0482, 0x0483, 0x0484, 0x0485, 0x0486, 0x0487, 0x0488, 0x0489, 0x048a, 0x048b, 0x048c, 0x048d, 0x048e, 0x048f,
 +      0x0490, 0x0491, 0x0492, 0x0493, 0x0494, 0x0495, 0x0496, 0x0497, 0x0498, 0x0499, 0x049a, 0x049b, 0x049c, 0x049d, 0x049e, 0x251f,
 +      0x04a0, 0x04a1, 0x04a2, 0x04a3, 0x04a4, 0x04a5, 0x04a6, 0x04a7, 0x04a8, 0x04a9, 0x04aa, 0x04ab, 0x04ac, 0x04ad, 0x04ae, 0x04af,
 +      0x04b0, 0x04b1, 0x04b2, 0x04b3, 0x04b4, 0x04b5, 0x04b6, 0x04b7, 0x04b8, 0x04b9, 0x04ba, 0x04bb, 0x04bc, 0x04bd, 0x253e, 0x255f,
 +      0x08c0, 0x08c1, 0x08c2, 0x08c3, 0x08c4, 0x08c5, 0x08c6, 0x08c7, 0x08c8, 0x08c9, 0x08ca, 0x08cb, 0x08cc, 0x08cd, 0x08ce, 0x08cf,
 +      0x08d0, 0x08d1, 0x08d2, 0x08d3, 0x08d4, 0x08d5, 0x08d6, 0x08d7, 0x08d8, 0x08d9, 0x08da, 0x08db, 0x08dc, 0x08dd, 0x08de, 0x299f,
 +      0x0ce0, 0x0ce1, 0x0ce2, 0x0ce3, 0x0ce4, 0x0ce5, 0x0ce6, 0x0ce7, 0x0ce8, 0x0ce9, 0x0cea, 0x0ceb, 0x0cec, 0x0ced, 0x0cee, 0x0cef,
 +      0x10f0, 0x10f1, 0x10f2, 0x10f3, 0x10f4, 0x10f5, 0x10f6, 0x10f7, 0x20f8, 0x20f9, 0x20fa, 0x20fb, 0x257c, 0x257d, 0x29be, 0x2ddf,
 +
 +/* previous 1s = 1: */
 +      0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x200f,
 +      0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x202f,
 +      0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x204f,
 +      0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x203e, 0x206f,
 +      0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x208f,
 +      0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x20af,
 +      0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x20cf,
 +      0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x207c, 0x207d, 0x20be, 0x20ef,
 +      0x0480, 0x0481, 0x0482, 0x0483, 0x0484, 0x0485, 0x0486, 0x0487, 0x0488, 0x0489, 0x048a, 0x048b, 0x048c, 0x048d, 0x048e, 0x250f,
 +      0x0490, 0x0491, 0x0492, 0x0493, 0x0494, 0x0495, 0x0496, 0x0497, 0x0498, 0x0499, 0x049a, 0x049b, 0x049c, 0x049d, 0x049e, 0x252f,
 +      0x04a0, 0x04a1, 0x04a2, 0x04a3, 0x04a4, 0x04a5, 0x04a6, 0x04a7, 0x04a8, 0x04a9, 0x04aa, 0x04ab, 0x04ac, 0x04ad, 0x04ae, 0x254f,
 +      0x04b0, 0x04b1, 0x04b2, 0x04b3, 0x04b4, 0x04b5, 0x04b6, 0x04b7, 0x04b8, 0x04b9, 0x04ba, 0x04bb, 0x04bc, 0x04bd, 0x253e, 0x256f,
 +      0x08c0, 0x08c1, 0x08c2, 0x08c3, 0x08c4, 0x08c5, 0x08c6, 0x08c7, 0x08c8, 0x08c9, 0x08ca, 0x08cb, 0x08cc, 0x08cd, 0x08ce, 0x298f,
 +      0x08d0, 0x08d1, 0x08d2, 0x08d3, 0x08d4, 0x08d5, 0x08d6, 0x08d7, 0x08d8, 0x08d9, 0x08da, 0x08db, 0x08dc, 0x08dd, 0x08de, 0x29af,
 +      0x0ce0, 0x0ce1, 0x0ce2, 0x0ce3, 0x0ce4, 0x0ce5, 0x0ce6, 0x0ce7, 0x0ce8, 0x0ce9, 0x0cea, 0x0ceb, 0x0cec, 0x0ced, 0x0cee, 0x2dcf,
 +      0x10f0, 0x10f1, 0x10f2, 0x10f3, 0x10f4, 0x10f5, 0x10f6, 0x10f7, 0x20f8, 0x20f9, 0x20fa, 0x20fb, 0x257c, 0x257d, 0x29be, 0x31ef,
 +
 +/* previous 1s = 2: */
 +      0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x2007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x2017,
 +      0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x2027, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x2037,
 +      0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x2047, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x2057,
 +      0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x2067, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x203e, 0x2077,
 +      0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x2087, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x2097,
 +      0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x20a7, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x20b7,
 +      0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x20c7, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x20d7,
 +      0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x20e7, 0x0078, 0x0079, 0x007a, 0x007b, 0x207c, 0x207d, 0x20be, 0x20f7,
 +      0x0480, 0x0481, 0x0482, 0x0483, 0x0484, 0x0485, 0x0486, 0x2507, 0x0488, 0x0489, 0x048a, 0x048b, 0x048c, 0x048d, 0x048e, 0x2517,
 +      0x0490, 0x0491, 0x0492, 0x0493, 0x0494, 0x0495, 0x0496, 0x2527, 0x0498, 0x0499, 0x049a, 0x049b, 0x049c, 0x049d, 0x049e, 0x2537,
 +      0x04a0, 0x04a1, 0x04a2, 0x04a3, 0x04a4, 0x04a5, 0x04a6, 0x2547, 0x04a8, 0x04a9, 0x04aa, 0x04ab, 0x04ac, 0x04ad, 0x04ae, 0x2557,
 +      0x04b0, 0x04b1, 0x04b2, 0x04b3, 0x04b4, 0x04b5, 0x04b6, 0x2567, 0x04b8, 0x04b9, 0x04ba, 0x04bb, 0x04bc, 0x04bd, 0x253e, 0x2577,
 +      0x08c0, 0x08c1, 0x08c2, 0x08c3, 0x08c4, 0x08c5, 0x08c6, 0x2987, 0x08c8, 0x08c9, 0x08ca, 0x08cb, 0x08cc, 0x08cd, 0x08ce, 0x2997,
 +      0x08d0, 0x08d1, 0x08d2, 0x08d3, 0x08d4, 0x08d5, 0x08d6, 0x29a7, 0x08d8, 0x08d9, 0x08da, 0x08db, 0x08dc, 0x08dd, 0x08de, 0x29b7,
 +      0x0ce0, 0x0ce1, 0x0ce2, 0x0ce3, 0x0ce4, 0x0ce5, 0x0ce6, 0x2dc7, 0x0ce8, 0x0ce9, 0x0cea, 0x0ceb, 0x0cec, 0x0ced, 0x0cee, 0x2dd7,
 +      0x10f0, 0x10f1, 0x10f2, 0x10f3, 0x10f4, 0x10f5, 0x10f6, 0x31e7, 0x20f8, 0x20f9, 0x20fa, 0x20fb, 0x257c, 0x257d, 0x29be, 0x41f7,
 +
 +/* previous 1s = 3: */
 +      0x0000, 0x0001, 0x0002, 0x2003, 0x0004, 0x0005, 0x0006, 0x200b, 0x0008, 0x0009, 0x000a, 0x2013, 0x000c, 0x000d, 0x000e, 0x201b,
 +      0x0010, 0x0011, 0x0012, 0x2023, 0x0014, 0x0015, 0x0016, 0x202b, 0x0018, 0x0019, 0x001a, 0x2033, 0x001c, 0x001d, 0x001e, 0x203b,
 +      0x0020, 0x0021, 0x0022, 0x2043, 0x0024, 0x0025, 0x0026, 0x204b, 0x0028, 0x0029, 0x002a, 0x2053, 0x002c, 0x002d, 0x002e, 0x205b,
 +      0x0030, 0x0031, 0x0032, 0x2063, 0x0034, 0x0035, 0x0036, 0x206b, 0x0038, 0x0039, 0x003a, 0x2073, 0x003c, 0x003d, 0x203e, 0x207b,
 +      0x0040, 0x0041, 0x0042, 0x2083, 0x0044, 0x0045, 0x0046, 0x208b, 0x0048, 0x0049, 0x004a, 0x2093, 0x004c, 0x004d, 0x004e, 0x209b,
 +      0x0050, 0x0051, 0x0052, 0x20a3, 0x0054, 0x0055, 0x0056, 0x20ab, 0x0058, 0x0059, 0x005a, 0x20b3, 0x005c, 0x005d, 0x005e, 0x20bb,
 +      0x0060, 0x0061, 0x0062, 0x20c3, 0x0064, 0x0065, 0x0066, 0x20cb, 0x0068, 0x0069, 0x006a, 0x20d3, 0x006c, 0x006d, 0x006e, 0x20db,
 +      0x0070, 0x0071, 0x0072, 0x20e3, 0x0074, 0x0075, 0x0076, 0x20eb, 0x0078, 0x0079, 0x007a, 0x20f3, 0x207c, 0x207d, 0x20be, 0x40fb,
 +      0x0480, 0x0481, 0x0482, 0x2503, 0x0484, 0x0485, 0x0486, 0x250b, 0x0488, 0x0489, 0x048a, 0x2513, 0x048c, 0x048d, 0x048e, 0x251b,
 +      0x0490, 0x0491, 0x0492, 0x2523, 0x0494, 0x0495, 0x0496, 0x252b, 0x0498, 0x0499, 0x049a, 0x2533, 0x049c, 0x049d, 0x049e, 0x253b,
 +      0x04a0, 0x04a1, 0x04a2, 0x2543, 0x04a4, 0x04a5, 0x04a6, 0x254b, 0x04a8, 0x04a9, 0x04aa, 0x2553, 0x04ac, 0x04ad, 0x04ae, 0x255b,
 +      0x04b0, 0x04b1, 0x04b2, 0x2563, 0x04b4, 0x04b5, 0x04b6, 0x256b, 0x04b8, 0x04b9, 0x04ba, 0x2573, 0x04bc, 0x04bd, 0x253e, 0x257b,
 +      0x08c0, 0x08c1, 0x08c2, 0x2983, 0x08c4, 0x08c5, 0x08c6, 0x298b, 0x08c8, 0x08c9, 0x08ca, 0x2993, 0x08cc, 0x08cd, 0x08ce, 0x299b,
 +      0x08d0, 0x08d1, 0x08d2, 0x29a3, 0x08d4, 0x08d5, 0x08d6, 0x29ab, 0x08d8, 0x08d9, 0x08da, 0x29b3, 0x08dc, 0x08dd, 0x08de, 0x29bb,
 +      0x0ce0, 0x0ce1, 0x0ce2, 0x2dc3, 0x0ce4, 0x0ce5, 0x0ce6, 0x2dcb, 0x0ce8, 0x0ce9, 0x0cea, 0x2dd3, 0x0cec, 0x0ced, 0x0cee, 0x2ddb,
 +      0x10f0, 0x10f1, 0x10f2, 0x31e3, 0x10f4, 0x10f5, 0x10f6, 0x31eb, 0x20f8, 0x20f9, 0x20fa, 0x41f3, 0x257c, 0x257d, 0x29be, 0x46fb,
 +
 +/* previous 1s = 4: */
 +      0x0000, 0x2001, 0x0002, 0x2005, 0x0004, 0x2009, 0x0006, 0x200d, 0x0008, 0x2011, 0x000a, 0x2015, 0x000c, 0x2019, 0x000e, 0x201d,
 +      0x0010, 0x2021, 0x0012, 0x2025, 0x0014, 0x2029, 0x0016, 0x202d, 0x0018, 0x2031, 0x001a, 0x2035, 0x001c, 0x2039, 0x001e, 0x203d,
 +      0x0020, 0x2041, 0x0022, 0x2045, 0x0024, 0x2049, 0x0026, 0x204d, 0x0028, 0x2051, 0x002a, 0x2055, 0x002c, 0x2059, 0x002e, 0x205d,
 +      0x0030, 0x2061, 0x0032, 0x2065, 0x0034, 0x2069, 0x0036, 0x206d, 0x0038, 0x2071, 0x003a, 0x2075, 0x003c, 0x2079, 0x203e, 0x407d,
 +      0x0040, 0x2081, 0x0042, 0x2085, 0x0044, 0x2089, 0x0046, 0x208d, 0x0048, 0x2091, 0x004a, 0x2095, 0x004c, 0x2099, 0x004e, 0x209d,
 +      0x0050, 0x20a1, 0x0052, 0x20a5, 0x0054, 0x20a9, 0x0056, 0x20ad, 0x0058, 0x20b1, 0x005a, 0x20b5, 0x005c, 0x20b9, 0x005e, 0x20bd,
 +      0x0060, 0x20c1, 0x0062, 0x20c5, 0x0064, 0x20c9, 0x0066, 0x20cd, 0x0068, 0x20d1, 0x006a, 0x20d5, 0x006c, 0x20d9, 0x006e, 0x20dd,
 +      0x0070, 0x20e1, 0x0072, 0x20e5, 0x0074, 0x20e9, 0x0076, 0x20ed, 0x0078, 0x20f1, 0x007a, 0x20f5, 0x207c, 0x40f9, 0x20be, 0x417d,
 +      0x0480, 0x2501, 0x0482, 0x2505, 0x0484, 0x2509, 0x0486, 0x250d, 0x0488, 0x2511, 0x048a, 0x2515, 0x048c, 0x2519, 0x048e, 0x251d,
 +      0x0490, 0x2521, 0x0492, 0x2525, 0x0494, 0x2529, 0x0496, 0x252d, 0x0498, 0x2531, 0x049a, 0x2535, 0x049c, 0x2539, 0x049e, 0x253d,
 +      0x04a0, 0x2541, 0x04a2, 0x2545, 0x04a4, 0x2549, 0x04a6, 0x254d, 0x04a8, 0x2551, 0x04aa, 0x2555, 0x04ac, 0x2559, 0x04ae, 0x255d,
 +      0x04b0, 0x2561, 0x04b2, 0x2565, 0x04b4, 0x2569, 0x04b6, 0x256d, 0x04b8, 0x2571, 0x04ba, 0x2575, 0x04bc, 0x2579, 0x253e, 0x467d,
 +      0x08c0, 0x2981, 0x08c2, 0x2985, 0x08c4, 0x2989, 0x08c6, 0x298d, 0x08c8, 0x2991, 0x08ca, 0x2995, 0x08cc, 0x2999, 0x08ce, 0x299d,
 +      0x08d0, 0x29a1, 0x08d2, 0x29a5, 0x08d4, 0x29a9, 0x08d6, 0x29ad, 0x08d8, 0x29b1, 0x08da, 0x29b5, 0x08dc, 0x29b9, 0x08de, 0x29bd,
 +      0x0ce0, 0x2dc1, 0x0ce2, 0x2dc5, 0x0ce4, 0x2dc9, 0x0ce6, 0x2dcd, 0x0ce8, 0x2dd1, 0x0cea, 0x2dd5, 0x0cec, 0x2dd9, 0x0cee, 0x2ddd,
 +      0x10f0, 0x31e1, 0x10f2, 0x31e5, 0x10f4, 0x31e9, 0x10f6, 0x31ed, 0x20f8, 0x41f1, 0x20fa, 0x41f5, 0x257c, 0x46f9, 0x29be, 0x4b7d
 +};
 +
 +/* hdlc_bitstuff_byte
 + * perform HDLC bitstuffing for one input byte (8 bits, LSB first)
 + * parameters:
 + *    cin     input byte
 + *    ones    number of trailing '1' bits in result before this step
 + *    iwb     pointer to output buffer structure
 + *            (write semaphore must be held)
 + * return value:
 + *    number of trailing '1' bits in result after this step
 + */
 +
 +static inline int hdlc_bitstuff_byte(struct isowbuf_t *iwb, unsigned char cin,
 +                                   int ones)
 +{
 +      u16 stuff;
 +      int shiftinc, newones;
 +
 +      /* get stuffing information for input byte
 +       * value: bit  9.. 0 = result bits
 +       *        bit 12..10 = number of trailing '1' bits in result
 +       *        bit 14..13 = number of bits added by stuffing
 +       */
 +      stuff = stufftab[256 * ones + cin];
 +      shiftinc = (stuff >> 13) & 3;
 +      newones = (stuff >> 10) & 7;
 +      stuff &= 0x3ff;
 +
 +      /* append stuffed byte to output stream */
 +      isowbuf_putbits(iwb, stuff, 8 + shiftinc);
 +      return newones;
 +}
 +
 +/* hdlc_buildframe
 + * Perform HDLC framing with bitstuffing on a byte buffer
 + * The input buffer is regarded as a sequence of bits, starting with the least
 + * significant bit of the first byte and ending with the most significant bit
 + * of the last byte. A 16 bit FCS is appended as defined by RFC 1662.
 + * Whenever five consecutive '1' bits appear in the resulting bit sequence, a
 + * '0' bit is inserted after them.
 + * The resulting bit string and a closing flag pattern (PPP_FLAG, '01111110')
 + * are appended to the output buffer starting at the given bit position, which
 + * is assumed to already contain a leading flag.
 + * The output buffer must have sufficient length; count + count/5 + 6 bytes
 + * starting at *out are safe and are verified to be present.
 + * parameters:
 + *    in      input buffer
 + *    count   number of bytes in input buffer
 + *    iwb     pointer to output buffer structure
 + *            (write semaphore must be held)
 + * return value:
 + *    position of end of packet in output buffer on success,
 + *    -EAGAIN if write semaphore busy or buffer full
 + */
 +
 +static inline int hdlc_buildframe(struct isowbuf_t *iwb,
 +                                unsigned char *in, int count)
 +{
 +      int ones;
 +      u16 fcs;
 +      int end;
 +      unsigned char c;
 +
 +      if (isowbuf_freebytes(iwb) < count + count / 5 + 6 ||
 +          isowbuf_startwrite(iwb) < 0) {
 +              gig_dbg(DEBUG_ISO, "%s: %d bytes free -> -EAGAIN",
 +                      __func__, isowbuf_freebytes(iwb));
 +              return -EAGAIN;
 +      }
 +
 +      dump_bytes(DEBUG_STREAM_DUMP, "snd data", in, count);
 +
 +      /* bitstuff and checksum input data */
 +      fcs = PPP_INITFCS;
 +      ones = 0;
 +      while (count-- > 0) {
 +              c = *in++;
 +              ones = hdlc_bitstuff_byte(iwb, c, ones);
 +              fcs = crc_ccitt_byte(fcs, c);
 +      }
 +
 +      /* bitstuff and append FCS
 +       * (complemented, least significant byte first) */
 +      fcs ^= 0xffff;
 +      ones = hdlc_bitstuff_byte(iwb, fcs & 0x00ff, ones);
 +      ones = hdlc_bitstuff_byte(iwb, (fcs >> 8) & 0x00ff, ones);
 +
 +      /* put closing flag and repeat byte for flag idle */
 +      isowbuf_putflag(iwb);
 +      end = isowbuf_donewrite(iwb);
 +      return end;
 +}
 +
 +/* trans_buildframe
 + * Append a block of 'transparent' data to the output buffer,
 + * inverting the bytes.
 + * The output buffer must have sufficient length; count bytes
 + * starting at *out are safe and are verified to be present.
 + * parameters:
 + *    in      input buffer
 + *    count   number of bytes in input buffer
 + *    iwb     pointer to output buffer structure
 + *            (write semaphore must be held)
 + * return value:
 + *    position of end of packet in output buffer on success,
 + *    -EAGAIN if write semaphore busy or buffer full
 + */
 +
 +static inline int trans_buildframe(struct isowbuf_t *iwb,
 +                                 unsigned char *in, int count)
 +{
 +      int write;
 +      unsigned char c;
 +
 +      if (unlikely(count <= 0))
 +              return iwb->write;
 +
 +      if (isowbuf_freebytes(iwb) < count ||
 +          isowbuf_startwrite(iwb) < 0) {
 +              gig_dbg(DEBUG_ISO, "can't put %d bytes", count);
 +              return -EAGAIN;
 +      }
 +
 +      gig_dbg(DEBUG_STREAM, "put %d bytes", count);
 +      dump_bytes(DEBUG_STREAM_DUMP, "snd data", in, count);
 +
 +      write = iwb->write;
 +      do {
 +              c = bitrev8(*in++);
 +              iwb->data[write++] = c;
 +              write %= BAS_OUTBUFSIZE;
 +      } while (--count > 0);
 +      iwb->write = write;
 +      iwb->idle = c;
 +
 +      return isowbuf_donewrite(iwb);
 +}
 +
 +int gigaset_isoc_buildframe(struct bc_state *bcs, unsigned char *in, int len)
 +{
 +      int result;
 +
 +      switch (bcs->proto2) {
 +      case L2_HDLC:
 +              result = hdlc_buildframe(bcs->hw.bas->isooutbuf, in, len);
 +              gig_dbg(DEBUG_ISO, "%s: %d bytes HDLC -> %d",
 +                      __func__, len, result);
 +              break;
 +      default:                        /* assume transparent */
 +              result = trans_buildframe(bcs->hw.bas->isooutbuf, in, len);
 +              gig_dbg(DEBUG_ISO, "%s: %d bytes trans -> %d",
 +                      __func__, len, result);
 +      }
 +      return result;
 +}
 +
 +/* hdlc_putbyte
 + * append byte c to current skb of B channel structure *bcs, updating fcs
 + */
 +static inline void hdlc_putbyte(unsigned char c, struct bc_state *bcs)
 +{
 +      bcs->rx_fcs = crc_ccitt_byte(bcs->rx_fcs, c);
 +      if (bcs->rx_skb == NULL)
 +              /* skipping */
 +              return;
 +      if (bcs->rx_skb->len >= bcs->rx_bufsize) {
 +              dev_warn(bcs->cs->dev, "received oversized packet discarded\n");
 +              bcs->hw.bas->giants++;
 +              dev_kfree_skb_any(bcs->rx_skb);
 +              bcs->rx_skb = NULL;
 +              return;
 +      }
 +      __skb_put_u8(bcs->rx_skb, c);
 +}
 +
 +/* hdlc_flush
 + * drop partial HDLC data packet
 + */
 +static inline void hdlc_flush(struct bc_state *bcs)
 +{
 +      /* clear skb or allocate new if not skipping */
 +      if (bcs->rx_skb != NULL)
 +              skb_trim(bcs->rx_skb, 0);
 +      else
 +              gigaset_new_rx_skb(bcs);
 +
 +      /* reset packet state */
 +      bcs->rx_fcs = PPP_INITFCS;
 +}
 +
 +/* hdlc_done
 + * process completed HDLC data packet
 + */
 +static inline void hdlc_done(struct bc_state *bcs)
 +{
 +      struct cardstate *cs = bcs->cs;
 +      struct sk_buff *procskb;
 +      unsigned int len;
 +
 +      if (unlikely(bcs->ignore)) {
 +              bcs->ignore--;
 +              hdlc_flush(bcs);
 +              return;
 +      }
 +      procskb = bcs->rx_skb;
 +      if (procskb == NULL) {
 +              /* previous error */
 +              gig_dbg(DEBUG_ISO, "%s: skb=NULL", __func__);
 +              gigaset_isdn_rcv_err(bcs);
 +      } else if (procskb->len < 2) {
 +              dev_notice(cs->dev, "received short frame (%d octets)\n",
 +                         procskb->len);
 +              bcs->hw.bas->runts++;
 +              dev_kfree_skb_any(procskb);
 +              gigaset_isdn_rcv_err(bcs);
 +      } else if (bcs->rx_fcs != PPP_GOODFCS) {
 +              dev_notice(cs->dev, "frame check error\n");
 +              bcs->hw.bas->fcserrs++;
 +              dev_kfree_skb_any(procskb);
 +              gigaset_isdn_rcv_err(bcs);
 +      } else {
 +              len = procskb->len;
 +              __skb_trim(procskb, len -= 2);  /* subtract FCS */
 +              gig_dbg(DEBUG_ISO, "%s: good frame (%d octets)", __func__, len);
 +              dump_bytes(DEBUG_STREAM_DUMP,
 +                         "rcv data", procskb->data, len);
 +              bcs->hw.bas->goodbytes += len;
 +              gigaset_skb_rcvd(bcs, procskb);
 +      }
 +      gigaset_new_rx_skb(bcs);
 +      bcs->rx_fcs = PPP_INITFCS;
 +}
 +
 +/* hdlc_frag
 + * drop HDLC data packet with non-integral last byte
 + */
 +static inline void hdlc_frag(struct bc_state *bcs, unsigned inbits)
 +{
 +      if (unlikely(bcs->ignore)) {
 +              bcs->ignore--;
 +              hdlc_flush(bcs);
 +              return;
 +      }
 +
 +      dev_notice(bcs->cs->dev, "received partial byte (%d bits)\n", inbits);
 +      bcs->hw.bas->alignerrs++;
 +      gigaset_isdn_rcv_err(bcs);
 +      __skb_trim(bcs->rx_skb, 0);
 +      bcs->rx_fcs = PPP_INITFCS;
 +}
 +
 +/* bit counts lookup table for HDLC bit unstuffing
 + * index: input byte
 + * value: bit 0..3 = number of consecutive '1' bits starting from LSB
 + *        bit 4..6 = number of consecutive '1' bits starting from MSB
 + *                 (replacing 8 by 7 to make it fit; the algorithm won't care)
 + *        bit 7 set if there are 5 or more "interior" consecutive '1' bits
 + */
 +static const unsigned char bitcounts[256] = {
 +      0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x04,
 +      0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x05,
 +      0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x04,
 +      0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x80, 0x06,
 +      0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x04,
 +      0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x05,
 +      0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x04,
 +      0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x80, 0x81, 0x80, 0x07,
 +      0x10, 0x11, 0x10, 0x12, 0x10, 0x11, 0x10, 0x13, 0x10, 0x11, 0x10, 0x12, 0x10, 0x11, 0x10, 0x14,
 +      0x10, 0x11, 0x10, 0x12, 0x10, 0x11, 0x10, 0x13, 0x10, 0x11, 0x10, 0x12, 0x10, 0x11, 0x10, 0x15,
 +      0x10, 0x11, 0x10, 0x12, 0x10, 0x11, 0x10, 0x13, 0x10, 0x11, 0x10, 0x12, 0x10, 0x11, 0x10, 0x14,
 +      0x10, 0x11, 0x10, 0x12, 0x10, 0x11, 0x10, 0x13, 0x10, 0x11, 0x10, 0x12, 0x10, 0x11, 0x90, 0x16,
 +      0x20, 0x21, 0x20, 0x22, 0x20, 0x21, 0x20, 0x23, 0x20, 0x21, 0x20, 0x22, 0x20, 0x21, 0x20, 0x24,
 +      0x20, 0x21, 0x20, 0x22, 0x20, 0x21, 0x20, 0x23, 0x20, 0x21, 0x20, 0x22, 0x20, 0x21, 0x20, 0x25,
 +      0x30, 0x31, 0x30, 0x32, 0x30, 0x31, 0x30, 0x33, 0x30, 0x31, 0x30, 0x32, 0x30, 0x31, 0x30, 0x34,
 +      0x40, 0x41, 0x40, 0x42, 0x40, 0x41, 0x40, 0x43, 0x50, 0x51, 0x50, 0x52, 0x60, 0x61, 0x70, 0x78
 +};
 +
 +/* hdlc_unpack
 + * perform HDLC frame processing (bit unstuffing, flag detection, FCS
 + * calculation) on a sequence of received data bytes (8 bits each, LSB first)
 + * pass on successfully received, complete frames as SKBs via gigaset_skb_rcvd
 + * notify of errors via gigaset_isdn_rcv_err
 + * tally frames, errors etc. in BC structure counters
 + * parameters:
 + *    src     received data
 + *    count   number of received bytes
 + *    bcs     receiving B channel structure
 + */
 +static inline void hdlc_unpack(unsigned char *src, unsigned count,
 +                             struct bc_state *bcs)
 +{
 +      struct bas_bc_state *ubc = bcs->hw.bas;
 +      int inputstate;
 +      unsigned seqlen, inbyte, inbits;
 +
 +      /* load previous state:
 +       * inputstate = set of flag bits:
 +       * - INS_flag_hunt: no complete opening flag received since connection
 +       *                  setup or last abort
 +       * - INS_have_data: at least one complete data byte received since last
 +       *                  flag
 +       * seqlen = number of consecutive '1' bits in last 7 input stream bits
 +       *          (0..7)
 +       * inbyte = accumulated partial data byte (if !INS_flag_hunt)
 +       * inbits = number of valid bits in inbyte, starting at LSB (0..6)
 +       */
 +      inputstate = bcs->inputstate;
 +      seqlen = ubc->seqlen;
 +      inbyte = ubc->inbyte;
 +      inbits = ubc->inbits;
 +
 +      /* bit unstuffing a byte a time
 +       * Take your time to understand this; it's straightforward but tedious.
 +       * The "bitcounts" lookup table is used to speed up the counting of
 +       * leading and trailing '1' bits.
 +       */
 +      while (count--) {
 +              unsigned char c = *src++;
 +              unsigned char tabentry = bitcounts[c];
 +              unsigned lead1 = tabentry & 0x0f;
 +              unsigned trail1 = (tabentry >> 4) & 0x0f;
 +
 +              seqlen += lead1;
 +
 +              if (unlikely(inputstate & INS_flag_hunt)) {
 +                      if (c == PPP_FLAG) {
 +                              /* flag-in-one */
 +                              inputstate &= ~(INS_flag_hunt | INS_have_data);
 +                              inbyte = 0;
 +                              inbits = 0;
 +                      } else if (seqlen == 6 && trail1 != 7) {
 +                              /* flag completed & not followed by abort */
 +                              inputstate &= ~(INS_flag_hunt | INS_have_data);
 +                              inbyte = c >> (lead1 + 1);
 +                              inbits = 7 - lead1;
 +                              if (trail1 >= 8) {
 +                                      /* interior stuffing:
 +                                       * omitting the MSB handles most cases,
 +                                       * correct the incorrectly handled
 +                                       * cases individually */
 +                                      inbits--;
 +                                      switch (c) {
 +                                      case 0xbe:
 +                                              inbyte = 0x3f;
 +                                              break;
 +                                      }
 +                              }
 +                      }
 +                      /* else: continue flag-hunting */
 +              } else if (likely(seqlen < 5 && trail1 < 7)) {
 +                      /* streamlined case: 8 data bits, no stuffing */
 +                      inbyte |= c << inbits;
 +                      hdlc_putbyte(inbyte & 0xff, bcs);
 +                      inputstate |= INS_have_data;
 +                      inbyte >>= 8;
 +                      /* inbits unchanged */
 +              } else if (likely(seqlen == 6 && inbits == 7 - lead1 &&
 +                                trail1 + 1 == inbits &&
 +                                !(inputstate & INS_have_data))) {
 +                      /* streamlined case: flag idle - state unchanged */
 +              } else if (unlikely(seqlen > 6)) {
 +                      /* abort sequence */
 +                      ubc->aborts++;
 +                      hdlc_flush(bcs);
 +                      inputstate |= INS_flag_hunt;
 +              } else if (seqlen == 6) {
 +                      /* closing flag, including (6 - lead1) '1's
 +                       * and one '0' from inbits */
 +                      if (inbits > 7 - lead1) {
 +                              hdlc_frag(bcs, inbits + lead1 - 7);
 +                              inputstate &= ~INS_have_data;
 +                      } else {
 +                              if (inbits < 7 - lead1)
 +                                      ubc->stolen0s++;
 +                              if (inputstate & INS_have_data) {
 +                                      hdlc_done(bcs);
 +                                      inputstate &= ~INS_have_data;
 +                              }
 +                      }
 +
 +                      if (c == PPP_FLAG) {
 +                              /* complete flag, LSB overlaps preceding flag */
 +                              ubc->shared0s++;
 +                              inbits = 0;
 +                              inbyte = 0;
 +                      } else if (trail1 != 7) {
 +                              /* remaining bits */
 +                              inbyte = c >> (lead1 + 1);
 +                              inbits = 7 - lead1;
 +                              if (trail1 >= 8) {
 +                                      /* interior stuffing:
 +                                       * omitting the MSB handles most cases,
 +                                       * correct the incorrectly handled
 +                                       * cases individually */
 +                                      inbits--;
 +                                      switch (c) {
 +                                      case 0xbe:
 +                                              inbyte = 0x3f;
 +                                              break;
 +                                      }
 +                              }
 +                      } else {
 +                              /* abort sequence follows,
 +                               * skb already empty anyway */
 +                              ubc->aborts++;
 +                              inputstate |= INS_flag_hunt;
 +                      }
 +              } else { /* (seqlen < 6) && (seqlen == 5 || trail1 >= 7) */
 +
 +                      if (c == PPP_FLAG) {
 +                              /* complete flag */
 +                              if (seqlen == 5)
 +                                      ubc->stolen0s++;
 +                              if (inbits) {
 +                                      hdlc_frag(bcs, inbits);
 +                                      inbits = 0;
 +                                      inbyte = 0;
 +                              } else if (inputstate & INS_have_data)
 +                                      hdlc_done(bcs);
 +                              inputstate &= ~INS_have_data;
 +                      } else if (trail1 == 7) {
 +                              /* abort sequence */
 +                              ubc->aborts++;
 +                              hdlc_flush(bcs);
 +                              inputstate |= INS_flag_hunt;
 +                      } else {
 +                              /* stuffed data */
 +                              if (trail1 < 7) { /* => seqlen == 5 */
 +                                      /* stuff bit at position lead1,
 +                                       * no interior stuffing */
 +                                      unsigned char mask = (1 << lead1) - 1;
 +                                      c = (c & mask) | ((c & ~mask) >> 1);
 +                                      inbyte |= c << inbits;
 +                                      inbits += 7;
 +                              } else if (seqlen < 5) { /* trail1 >= 8 */
 +                                      /* interior stuffing:
 +                                       * omitting the MSB handles most cases,
 +                                       * correct the incorrectly handled
 +                                       * cases individually */
 +                                      switch (c) {
 +                                      case 0xbe:
 +                                              c = 0x7e;
 +                                              break;
 +                                      }
 +                                      inbyte |= c << inbits;
 +                                      inbits += 7;
 +                              } else { /* seqlen == 5 && trail1 >= 8 */
 +
 +                                      /* stuff bit at lead1 *and* interior
 +                                       * stuffing -- unstuff individually */
 +                                      switch (c) {
 +                                      case 0x7d:
 +                                              c = 0x3f;
 +                                              break;
 +                                      case 0xbe:
 +                                              c = 0x3f;
 +                                              break;
 +                                      case 0x3e:
 +                                              c = 0x1f;
 +                                              break;
 +                                      case 0x7c:
 +                                              c = 0x3e;
 +                                              break;
 +                                      }
 +                                      inbyte |= c << inbits;
 +                                      inbits += 6;
 +                              }
 +                              if (inbits >= 8) {
 +                                      inbits -= 8;
 +                                      hdlc_putbyte(inbyte & 0xff, bcs);
 +                                      inputstate |= INS_have_data;
 +                                      inbyte >>= 8;
 +                              }
 +                      }
 +              }
 +              seqlen = trail1 & 7;
 +      }
 +
 +      /* save new state */
 +      bcs->inputstate = inputstate;
 +      ubc->seqlen = seqlen;
 +      ubc->inbyte = inbyte;
 +      ubc->inbits = inbits;
 +}
 +
 +/* trans_receive
 + * pass on received USB frame transparently as SKB via gigaset_skb_rcvd
 + * invert bytes
 + * tally frames, errors etc. in BC structure counters
 + * parameters:
 + *    src     received data
 + *    count   number of received bytes
 + *    bcs     receiving B channel structure
 + */
 +static inline void trans_receive(unsigned char *src, unsigned count,
 +                               struct bc_state *bcs)
 +{
 +      struct sk_buff *skb;
 +      int dobytes;
 +      unsigned char *dst;
 +
 +      if (unlikely(bcs->ignore)) {
 +              bcs->ignore--;
 +              return;
 +      }
 +      skb = bcs->rx_skb;
 +      if (skb == NULL) {
 +              skb = gigaset_new_rx_skb(bcs);
 +              if (skb == NULL)
 +                      return;
 +      }
 +      dobytes = bcs->rx_bufsize - skb->len;
 +      while (count > 0) {
 +              dst = skb_put(skb, count < dobytes ? count : dobytes);
 +              while (count > 0 && dobytes > 0) {
 +                      *dst++ = bitrev8(*src++);
 +                      count--;
 +                      dobytes--;
 +              }
 +              if (dobytes == 0) {
 +                      dump_bytes(DEBUG_STREAM_DUMP,
 +                                 "rcv data", skb->data, skb->len);
 +                      bcs->hw.bas->goodbytes += skb->len;
 +                      gigaset_skb_rcvd(bcs, skb);
 +                      skb = gigaset_new_rx_skb(bcs);
 +                      if (skb == NULL)
 +                              return;
 +                      dobytes = bcs->rx_bufsize;
 +              }
 +      }
 +}
 +
 +void gigaset_isoc_receive(unsigned char *src, unsigned count,
 +                        struct bc_state *bcs)
 +{
 +      switch (bcs->proto2) {
 +      case L2_HDLC:
 +              hdlc_unpack(src, count, bcs);
 +              break;
 +      default:                /* assume transparent */
 +              trans_receive(src, count, bcs);
 +      }
 +}
 +
 +/* == data input =========================================================== */
 +
 +/* process a block of received bytes in command mode (mstate != MS_LOCKED)
 + * Append received bytes to the command response buffer and forward them
 + * line by line to the response handler.
 + * Note: Received lines may be terminated by CR, LF, or CR LF, which will be
 + * removed before passing the line to the response handler.
 + */
 +static void cmd_loop(unsigned char *src, int numbytes, struct inbuf_t *inbuf)
 +{
 +      struct cardstate *cs = inbuf->cs;
 +      unsigned cbytes      = cs->cbytes;
 +      unsigned char c;
 +
 +      while (numbytes--) {
 +              c = *src++;
 +              switch (c) {
 +              case '\n':
 +                      if (cbytes == 0 && cs->respdata[0] == '\r') {
 +                              /* collapse LF with preceding CR */
 +                              cs->respdata[0] = 0;
 +                              break;
 +                      }
 +                      /* fall through */
 +              case '\r':
 +                      /* end of message line, pass to response handler */
 +                      if (cbytes >= MAX_RESP_SIZE) {
 +                              dev_warn(cs->dev, "response too large (%d)\n",
 +                                       cbytes);
 +                              cbytes = MAX_RESP_SIZE;
 +                      }
 +                      cs->cbytes = cbytes;
 +                      gigaset_dbg_buffer(DEBUG_TRANSCMD, "received response",
 +                                         cbytes, cs->respdata);
 +                      gigaset_handle_modem_response(cs);
 +                      cbytes = 0;
 +
 +                      /* store EOL byte for CRLF collapsing */
 +                      cs->respdata[0] = c;
 +                      break;
 +              default:
 +                      /* append to line buffer if possible */
 +                      if (cbytes < MAX_RESP_SIZE)
 +                              cs->respdata[cbytes] = c;
 +                      cbytes++;
 +              }
 +      }
 +
 +      /* save state */
 +      cs->cbytes = cbytes;
 +}
 +
 +
 +/* process a block of data received through the control channel
 + */
 +void gigaset_isoc_input(struct inbuf_t *inbuf)
 +{
 +      struct cardstate *cs = inbuf->cs;
 +      unsigned tail, head, numbytes;
 +      unsigned char *src;
 +
 +      head = inbuf->head;
 +      while (head != (tail = inbuf->tail)) {
 +              gig_dbg(DEBUG_INTR, "buffer state: %u -> %u", head, tail);
 +              if (head > tail)
 +                      tail = RBUFSIZE;
 +              src = inbuf->data + head;
 +              numbytes = tail - head;
 +              gig_dbg(DEBUG_INTR, "processing %u bytes", numbytes);
 +
 +              if (cs->mstate == MS_LOCKED) {
 +                      gigaset_dbg_buffer(DEBUG_LOCKCMD, "received response",
 +                                         numbytes, src);
 +                      gigaset_if_receive(inbuf->cs, src, numbytes);
 +              } else {
 +                      cmd_loop(src, numbytes, inbuf);
 +              }
 +
 +              head += numbytes;
 +              if (head == RBUFSIZE)
 +                      head = 0;
 +              gig_dbg(DEBUG_INTR, "setting head to %u", head);
 +              inbuf->head = head;
 +      }
 +}
 +
 +
 +/* == data output ========================================================== */
 +
 +/**
 + * gigaset_isoc_send_skb() - queue an skb for sending
 + * @bcs:      B channel descriptor structure.
 + * @skb:      data to send.
 + *
 + * Called by LL to queue an skb for sending, and start transmission if
 + * necessary.
 + * Once the payload data has been transmitted completely, gigaset_skb_sent()
 + * will be called with the skb's link layer header preserved.
 + *
 + * Return value:
 + *    number of bytes accepted for sending (skb->len) if ok,
 + *    error code < 0 (eg. -ENODEV) on error
 + */
 +int gigaset_isoc_send_skb(struct bc_state *bcs, struct sk_buff *skb)
 +{
 +      int len = skb->len;
 +      unsigned long flags;
 +
 +      spin_lock_irqsave(&bcs->cs->lock, flags);
 +      if (!bcs->cs->connected) {
 +              spin_unlock_irqrestore(&bcs->cs->lock, flags);
 +              return -ENODEV;
 +      }
 +
 +      skb_queue_tail(&bcs->squeue, skb);
 +      gig_dbg(DEBUG_ISO, "%s: skb queued, qlen=%d",
 +              __func__, skb_queue_len(&bcs->squeue));
 +
 +      /* tasklet submits URB if necessary */
 +      tasklet_schedule(&bcs->hw.bas->sent_tasklet);
 +      spin_unlock_irqrestore(&bcs->cs->lock, flags);
 +
 +      return len;     /* ok so far */
 +}
index e3f9d0f,0000000..8914439
mode 100644,000000..100644
--- /dev/null
@@@ -1,80 -1,0 +1,77 @@@
-  *    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.
++// SPDX-License-Identifier: GPL-2.0-or-later
 +/*
 + * Stuff used by all variants of the driver
 + *
 + * Copyright (c) 2001 by Stefan Eilers,
 + *                       Hansjoerg Lipp <hjlipp@web.de>,
 + *                       Tilman Schmidt <tilman@imap.cc>.
 + *
 + * =====================================================================
 + * =====================================================================
 + */
 +
 +#include "gigaset.h"
 +
 +static ssize_t show_cidmode(struct device *dev,
 +                          struct device_attribute *attr, char *buf)
 +{
 +      struct cardstate *cs = dev_get_drvdata(dev);
 +
 +      return sprintf(buf, "%u\n", cs->cidmode);
 +}
 +
 +static ssize_t set_cidmode(struct device *dev, struct device_attribute *attr,
 +                         const char *buf, size_t count)
 +{
 +      struct cardstate *cs = dev_get_drvdata(dev);
 +      long int value;
 +      char *end;
 +
 +      value = simple_strtol(buf, &end, 0);
 +      while (*end)
 +              if (!isspace(*end++))
 +                      return -EINVAL;
 +      if (value < 0 || value > 1)
 +              return -EINVAL;
 +
 +      if (mutex_lock_interruptible(&cs->mutex))
 +              return -ERESTARTSYS;
 +
 +      cs->waiting = 1;
 +      if (!gigaset_add_event(cs, &cs->at_state, EV_PROC_CIDMODE,
 +                             NULL, value, NULL)) {
 +              cs->waiting = 0;
 +              mutex_unlock(&cs->mutex);
 +              return -ENOMEM;
 +      }
 +      gigaset_schedule_event(cs);
 +
 +      wait_event(cs->waitqueue, !cs->waiting);
 +
 +      mutex_unlock(&cs->mutex);
 +
 +      return count;
 +}
 +
 +static DEVICE_ATTR(cidmode, S_IRUGO | S_IWUSR, show_cidmode, set_cidmode);
 +
 +/* free sysfs for device */
 +void gigaset_free_dev_sysfs(struct cardstate *cs)
 +{
 +      if (!cs->tty_dev)
 +              return;
 +
 +      gig_dbg(DEBUG_INIT, "removing sysfs entries");
 +      device_remove_file(cs->tty_dev, &dev_attr_cidmode);
 +}
 +
 +/* initialize sysfs for device */
 +void gigaset_init_dev_sysfs(struct cardstate *cs)
 +{
 +      if (!cs->tty_dev)
 +              return;
 +
 +      gig_dbg(DEBUG_INIT, "setting up sysfs");
 +      if (device_create_file(cs->tty_dev, &dev_attr_cidmode))
 +              pr_err("could not create sysfs attribute\n");
 +}
index e1de8b1,0000000..5587e9e
mode 100644,000000..100644
--- /dev/null
@@@ -1,799 -1,0 +1,796 @@@
-  * 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.
++// SPDX-License-Identifier: GPL-2.0-or-later
 +/* This is the serial hardware link layer (HLL) for the Gigaset 307x isdn
 + * DECT base (aka Sinus 45 isdn) using the RS232 DECT data module M101,
 + * written as a line discipline.
 + *
 + * =====================================================================
 + * =====================================================================
 + */
 +
 +#include "gigaset.h"
 +#include <linux/module.h>
 +#include <linux/moduleparam.h>
 +#include <linux/platform_device.h>
 +#include <linux/completion.h>
 +
 +/* Version Information */
 +#define DRIVER_AUTHOR "Tilman Schmidt"
 +#define DRIVER_DESC "Serial Driver for Gigaset 307x using Siemens M101"
 +
 +#define GIGASET_MINORS     1
 +#define GIGASET_MINOR      0
 +#define GIGASET_MODULENAME "ser_gigaset"
 +#define GIGASET_DEVNAME    "ttyGS"
 +
 +/* length limit according to Siemens 3070usb-protokoll.doc ch. 2.1 */
 +#define IF_WRITEBUF 264
 +
 +MODULE_AUTHOR(DRIVER_AUTHOR);
 +MODULE_DESCRIPTION(DRIVER_DESC);
 +MODULE_LICENSE("GPL");
 +MODULE_ALIAS_LDISC(N_GIGASET_M101);
 +
 +static int startmode = SM_ISDN;
 +module_param(startmode, int, S_IRUGO);
 +MODULE_PARM_DESC(startmode, "initial operation mode");
 +static int cidmode = 1;
 +module_param(cidmode, int, S_IRUGO);
 +MODULE_PARM_DESC(cidmode, "stay in CID mode when idle");
 +
 +static struct gigaset_driver *driver;
 +
 +struct ser_cardstate {
 +      struct platform_device  dev;
 +      struct tty_struct       *tty;
 +      atomic_t                refcnt;
 +      struct completion       dead_cmp;
 +};
 +
 +static struct platform_driver device_driver = {
 +      .driver = {
 +              .name = GIGASET_MODULENAME,
 +      },
 +};
 +
 +static void flush_send_queue(struct cardstate *);
 +
 +/* transmit data from current open skb
 + * result: number of bytes sent or error code < 0
 + */
 +static int write_modem(struct cardstate *cs)
 +{
 +      struct tty_struct *tty = cs->hw.ser->tty;
 +      struct bc_state *bcs = &cs->bcs[0];     /* only one channel */
 +      struct sk_buff *skb = bcs->tx_skb;
 +      int sent = -EOPNOTSUPP;
 +
 +      WARN_ON(!tty || !tty->ops || !skb);
 +
 +      if (!skb->len) {
 +              dev_kfree_skb_any(skb);
 +              bcs->tx_skb = NULL;
 +              return -EINVAL;
 +      }
 +
 +      set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
 +      if (tty->ops->write)
 +              sent = tty->ops->write(tty, skb->data, skb->len);
 +      gig_dbg(DEBUG_OUTPUT, "write_modem: sent %d", sent);
 +      if (sent < 0) {
 +              /* error */
 +              flush_send_queue(cs);
 +              return sent;
 +      }
 +      skb_pull(skb, sent);
 +      if (!skb->len) {
 +              /* skb sent completely */
 +              gigaset_skb_sent(bcs, skb);
 +
 +              gig_dbg(DEBUG_INTR, "kfree skb (Adr: %lx)!",
 +                      (unsigned long) skb);
 +              dev_kfree_skb_any(skb);
 +              bcs->tx_skb = NULL;
 +      }
 +      return sent;
 +}
 +
 +/*
 + * transmit first queued command buffer
 + * result: number of bytes sent or error code < 0
 + */
 +static int send_cb(struct cardstate *cs)
 +{
 +      struct tty_struct *tty = cs->hw.ser->tty;
 +      struct cmdbuf_t *cb, *tcb;
 +      unsigned long flags;
 +      int sent = 0;
 +
 +      WARN_ON(!tty || !tty->ops);
 +
 +      cb = cs->cmdbuf;
 +      if (!cb)
 +              return 0;       /* nothing to do */
 +
 +      if (cb->len) {
 +              set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
 +              sent = tty->ops->write(tty, cb->buf + cb->offset, cb->len);
 +              if (sent < 0) {
 +                      /* error */
 +                      gig_dbg(DEBUG_OUTPUT, "send_cb: write error %d", sent);
 +                      flush_send_queue(cs);
 +                      return sent;
 +              }
 +              cb->offset += sent;
 +              cb->len -= sent;
 +              gig_dbg(DEBUG_OUTPUT, "send_cb: sent %d, left %u, queued %u",
 +                      sent, cb->len, cs->cmdbytes);
 +      }
 +
 +      while (cb && !cb->len) {
 +              spin_lock_irqsave(&cs->cmdlock, flags);
 +              cs->cmdbytes -= cs->curlen;
 +              tcb = cb;
 +              cs->cmdbuf = cb = cb->next;
 +              if (cb) {
 +                      cb->prev = NULL;
 +                      cs->curlen = cb->len;
 +              } else {
 +                      cs->lastcmdbuf = NULL;
 +                      cs->curlen = 0;
 +              }
 +              spin_unlock_irqrestore(&cs->cmdlock, flags);
 +
 +              if (tcb->wake_tasklet)
 +                      tasklet_schedule(tcb->wake_tasklet);
 +              kfree(tcb);
 +      }
 +      return sent;
 +}
 +
 +/*
 + * send queue tasklet
 + * If there is already a skb opened, put data to the transfer buffer
 + * by calling "write_modem".
 + * Otherwise take a new skb out of the queue.
 + */
 +static void gigaset_modem_fill(unsigned long data)
 +{
 +      struct cardstate *cs = (struct cardstate *) data;
 +      struct bc_state *bcs;
 +      struct sk_buff *nextskb;
 +      int sent = 0;
 +
 +      if (!cs) {
 +              gig_dbg(DEBUG_OUTPUT, "%s: no cardstate", __func__);
 +              return;
 +      }
 +      bcs = cs->bcs;
 +      if (!bcs) {
 +              gig_dbg(DEBUG_OUTPUT, "%s: no cardstate", __func__);
 +              return;
 +      }
 +      if (!bcs->tx_skb) {
 +              /* no skb is being sent; send command if any */
 +              sent = send_cb(cs);
 +              gig_dbg(DEBUG_OUTPUT, "%s: send_cb -> %d", __func__, sent);
 +              if (sent)
 +                      /* something sent or error */
 +                      return;
 +
 +              /* no command to send; get skb */
 +              nextskb = skb_dequeue(&bcs->squeue);
 +              if (!nextskb)
 +                      /* no skb either, nothing to do */
 +                      return;
 +              bcs->tx_skb = nextskb;
 +
 +              gig_dbg(DEBUG_INTR, "Dequeued skb (Adr: %lx)",
 +                      (unsigned long) bcs->tx_skb);
 +      }
 +
 +      /* send skb */
 +      gig_dbg(DEBUG_OUTPUT, "%s: tx_skb", __func__);
 +      if (write_modem(cs) < 0)
 +              gig_dbg(DEBUG_OUTPUT, "%s: write_modem failed", __func__);
 +}
 +
 +/*
 + * throw away all data queued for sending
 + */
 +static void flush_send_queue(struct cardstate *cs)
 +{
 +      struct sk_buff *skb;
 +      struct cmdbuf_t *cb;
 +      unsigned long flags;
 +
 +      /* command queue */
 +      spin_lock_irqsave(&cs->cmdlock, flags);
 +      while ((cb = cs->cmdbuf) != NULL) {
 +              cs->cmdbuf = cb->next;
 +              if (cb->wake_tasklet)
 +                      tasklet_schedule(cb->wake_tasklet);
 +              kfree(cb);
 +      }
 +      cs->cmdbuf = cs->lastcmdbuf = NULL;
 +      cs->cmdbytes = cs->curlen = 0;
 +      spin_unlock_irqrestore(&cs->cmdlock, flags);
 +
 +      /* data queue */
 +      if (cs->bcs->tx_skb)
 +              dev_kfree_skb_any(cs->bcs->tx_skb);
 +      while ((skb = skb_dequeue(&cs->bcs->squeue)) != NULL)
 +              dev_kfree_skb_any(skb);
 +}
 +
 +
 +/* Gigaset Driver Interface */
 +/* ======================== */
 +
 +/*
 + * queue an AT command string for transmission to the Gigaset device
 + * parameters:
 + *    cs              controller state structure
 + *    buf             buffer containing the string to send
 + *    len             number of characters to send
 + *    wake_tasklet    tasklet to run when transmission is complete, or NULL
 + * return value:
 + *    number of bytes queued, or error code < 0
 + */
 +static int gigaset_write_cmd(struct cardstate *cs, struct cmdbuf_t *cb)
 +{
 +      unsigned long flags;
 +
 +      gigaset_dbg_buffer(cs->mstate != MS_LOCKED ?
 +                         DEBUG_TRANSCMD : DEBUG_LOCKCMD,
 +                         "CMD Transmit", cb->len, cb->buf);
 +
 +      spin_lock_irqsave(&cs->cmdlock, flags);
 +      cb->prev = cs->lastcmdbuf;
 +      if (cs->lastcmdbuf)
 +              cs->lastcmdbuf->next = cb;
 +      else {
 +              cs->cmdbuf = cb;
 +              cs->curlen = cb->len;
 +      }
 +      cs->cmdbytes += cb->len;
 +      cs->lastcmdbuf = cb;
 +      spin_unlock_irqrestore(&cs->cmdlock, flags);
 +
 +      spin_lock_irqsave(&cs->lock, flags);
 +      if (cs->connected)
 +              tasklet_schedule(&cs->write_tasklet);
 +      spin_unlock_irqrestore(&cs->lock, flags);
 +      return cb->len;
 +}
 +
 +/*
 + * tty_driver.write_room interface routine
 + * return number of characters the driver will accept to be written
 + * parameter:
 + *    controller state structure
 + * return value:
 + *    number of characters
 + */
 +static int gigaset_write_room(struct cardstate *cs)
 +{
 +      unsigned bytes;
 +
 +      bytes = cs->cmdbytes;
 +      return bytes < IF_WRITEBUF ? IF_WRITEBUF - bytes : 0;
 +}
 +
 +/*
 + * tty_driver.chars_in_buffer interface routine
 + * return number of characters waiting to be sent
 + * parameter:
 + *    controller state structure
 + * return value:
 + *    number of characters
 + */
 +static int gigaset_chars_in_buffer(struct cardstate *cs)
 +{
 +      return cs->cmdbytes;
 +}
 +
 +/*
 + * implementation of ioctl(GIGASET_BRKCHARS)
 + * parameter:
 + *    controller state structure
 + * return value:
 + *    -EINVAL (unimplemented function)
 + */
 +static int gigaset_brkchars(struct cardstate *cs, const unsigned char buf[6])
 +{
 +      /* not implemented */
 +      return -EINVAL;
 +}
 +
 +/*
 + * Open B channel
 + * Called by "do_action" in ev-layer.c
 + */
 +static int gigaset_init_bchannel(struct bc_state *bcs)
 +{
 +      /* nothing to do for M10x */
 +      gigaset_bchannel_up(bcs);
 +      return 0;
 +}
 +
 +/*
 + * Close B channel
 + * Called by "do_action" in ev-layer.c
 + */
 +static int gigaset_close_bchannel(struct bc_state *bcs)
 +{
 +      /* nothing to do for M10x */
 +      gigaset_bchannel_down(bcs);
 +      return 0;
 +}
 +
 +/*
 + * Set up B channel structure
 + * This is called by "gigaset_initcs" in common.c
 + */
 +static int gigaset_initbcshw(struct bc_state *bcs)
 +{
 +      /* unused */
 +      bcs->hw.ser = NULL;
 +      return 0;
 +}
 +
 +/*
 + * Free B channel structure
 + * Called by "gigaset_freebcs" in common.c
 + */
 +static void gigaset_freebcshw(struct bc_state *bcs)
 +{
 +      /* unused */
 +}
 +
 +/*
 + * Reinitialize B channel structure
 + * This is called by "bcs_reinit" in common.c
 + */
 +static void gigaset_reinitbcshw(struct bc_state *bcs)
 +{
 +      /* nothing to do for M10x */
 +}
 +
 +/*
 + * Free hardware specific device data
 + * This will be called by "gigaset_freecs" in common.c
 + */
 +static void gigaset_freecshw(struct cardstate *cs)
 +{
 +      tasklet_kill(&cs->write_tasklet);
 +      if (!cs->hw.ser)
 +              return;
 +      platform_device_unregister(&cs->hw.ser->dev);
 +}
 +
 +static void gigaset_device_release(struct device *dev)
 +{
 +      kfree(container_of(dev, struct ser_cardstate, dev.dev));
 +}
 +
 +/*
 + * Set up hardware specific device data
 + * This is called by "gigaset_initcs" in common.c
 + */
 +static int gigaset_initcshw(struct cardstate *cs)
 +{
 +      int rc;
 +      struct ser_cardstate *scs;
 +
 +      scs = kzalloc(sizeof(struct ser_cardstate), GFP_KERNEL);
 +      if (!scs) {
 +              pr_err("out of memory\n");
 +              return -ENOMEM;
 +      }
 +      cs->hw.ser = scs;
 +
 +      cs->hw.ser->dev.name = GIGASET_MODULENAME;
 +      cs->hw.ser->dev.id = cs->minor_index;
 +      cs->hw.ser->dev.dev.release = gigaset_device_release;
 +      rc = platform_device_register(&cs->hw.ser->dev);
 +      if (rc != 0) {
 +              pr_err("error %d registering platform device\n", rc);
 +              kfree(cs->hw.ser);
 +              cs->hw.ser = NULL;
 +              return rc;
 +      }
 +
 +      tasklet_init(&cs->write_tasklet,
 +                   gigaset_modem_fill, (unsigned long) cs);
 +      return 0;
 +}
 +
 +/*
 + * set modem control lines
 + * Parameters:
 + *    card state structure
 + *    modem control line state ([TIOCM_DTR]|[TIOCM_RTS])
 + * Called by "gigaset_start" and "gigaset_enterconfigmode" in common.c
 + * and by "if_lock" and "if_termios" in interface.c
 + */
 +static int gigaset_set_modem_ctrl(struct cardstate *cs, unsigned old_state,
 +                                unsigned new_state)
 +{
 +      struct tty_struct *tty = cs->hw.ser->tty;
 +      unsigned int set, clear;
 +
 +      WARN_ON(!tty || !tty->ops);
 +      /* tiocmset is an optional tty driver method */
 +      if (!tty->ops->tiocmset)
 +              return -EINVAL;
 +      set = new_state & ~old_state;
 +      clear = old_state & ~new_state;
 +      if (!set && !clear)
 +              return 0;
 +      gig_dbg(DEBUG_IF, "tiocmset set %x clear %x", set, clear);
 +      return tty->ops->tiocmset(tty, set, clear);
 +}
 +
 +static int gigaset_baud_rate(struct cardstate *cs, unsigned cflag)
 +{
 +      return -EINVAL;
 +}
 +
 +static int gigaset_set_line_ctrl(struct cardstate *cs, unsigned cflag)
 +{
 +      return -EINVAL;
 +}
 +
 +static const struct gigaset_ops ops = {
 +      .write_cmd = gigaset_write_cmd,
 +      .write_room = gigaset_write_room,
 +      .chars_in_buffer = gigaset_chars_in_buffer,
 +      .brkchars = gigaset_brkchars,
 +      .init_bchannel = gigaset_init_bchannel,
 +      .close_bchannel = gigaset_close_bchannel,
 +      .initbcshw = gigaset_initbcshw,
 +      .freebcshw = gigaset_freebcshw,
 +      .reinitbcshw = gigaset_reinitbcshw,
 +      .initcshw = gigaset_initcshw,
 +      .freecshw = gigaset_freecshw,
 +      .set_modem_ctrl = gigaset_set_modem_ctrl,
 +      .baud_rate = gigaset_baud_rate,
 +      .set_line_ctrl = gigaset_set_line_ctrl,
 +      .send_skb = gigaset_m10x_send_skb,      /* asyncdata.c */
 +      .handle_input = gigaset_m10x_input,     /* asyncdata.c */
 +};
 +
 +
 +/* Line Discipline Interface */
 +/* ========================= */
 +
 +/* helper functions for cardstate refcounting */
 +static struct cardstate *cs_get(struct tty_struct *tty)
 +{
 +      struct cardstate *cs = tty->disc_data;
 +
 +      if (!cs || !cs->hw.ser) {
 +              gig_dbg(DEBUG_ANY, "%s: no cardstate", __func__);
 +              return NULL;
 +      }
 +      atomic_inc(&cs->hw.ser->refcnt);
 +      return cs;
 +}
 +
 +static void cs_put(struct cardstate *cs)
 +{
 +      if (atomic_dec_and_test(&cs->hw.ser->refcnt))
 +              complete(&cs->hw.ser->dead_cmp);
 +}
 +
 +/*
 + * Called by the tty driver when the line discipline is pushed onto the tty.
 + * Called in process context.
 + */
 +static int
 +gigaset_tty_open(struct tty_struct *tty)
 +{
 +      struct cardstate *cs;
 +      int rc;
 +
 +      gig_dbg(DEBUG_INIT, "Starting HLL for Gigaset M101");
 +
 +      pr_info(DRIVER_DESC "\n");
 +
 +      if (!driver) {
 +              pr_err("%s: no driver structure\n", __func__);
 +              return -ENODEV;
 +      }
 +
 +      /* allocate memory for our device state and initialize it */
 +      cs = gigaset_initcs(driver, 1, 1, 0, cidmode, GIGASET_MODULENAME);
 +      if (!cs) {
 +              rc = -ENODEV;
 +              goto error;
 +      }
 +
 +      cs->dev = &cs->hw.ser->dev.dev;
 +      cs->hw.ser->tty = tty;
 +      atomic_set(&cs->hw.ser->refcnt, 1);
 +      init_completion(&cs->hw.ser->dead_cmp);
 +      tty->disc_data = cs;
 +
 +      /* Set the amount of data we're willing to receive per call
 +       * from the hardware driver to half of the input buffer size
 +       * to leave some reserve.
 +       * Note: We don't do flow control towards the hardware driver.
 +       * If more data is received than will fit into the input buffer,
 +       * it will be dropped and an error will be logged. This should
 +       * never happen as the device is slow and the buffer size ample.
 +       */
 +      tty->receive_room = RBUFSIZE/2;
 +
 +      /* OK.. Initialization of the datastructures and the HW is done.. Now
 +       * startup system and notify the LL that we are ready to run
 +       */
 +      if (startmode == SM_LOCKED)
 +              cs->mstate = MS_LOCKED;
 +      rc = gigaset_start(cs);
 +      if (rc < 0) {
 +              tasklet_kill(&cs->write_tasklet);
 +              goto error;
 +      }
 +
 +      gig_dbg(DEBUG_INIT, "Startup of HLL done");
 +      return 0;
 +
 +error:
 +      gig_dbg(DEBUG_INIT, "Startup of HLL failed");
 +      tty->disc_data = NULL;
 +      gigaset_freecs(cs);
 +      return rc;
 +}
 +
 +/*
 + * Called by the tty driver when the line discipline is removed.
 + * Called from process context.
 + */
 +static void
 +gigaset_tty_close(struct tty_struct *tty)
 +{
 +      struct cardstate *cs = tty->disc_data;
 +
 +      gig_dbg(DEBUG_INIT, "Stopping HLL for Gigaset M101");
 +
 +      if (!cs) {
 +              gig_dbg(DEBUG_INIT, "%s: no cardstate", __func__);
 +              return;
 +      }
 +
 +      /* prevent other callers from entering ldisc methods */
 +      tty->disc_data = NULL;
 +
 +      if (!cs->hw.ser)
 +              pr_err("%s: no hw cardstate\n", __func__);
 +      else {
 +              /* wait for running methods to finish */
 +              if (!atomic_dec_and_test(&cs->hw.ser->refcnt))
 +                      wait_for_completion(&cs->hw.ser->dead_cmp);
 +      }
 +
 +      /* stop operations */
 +      gigaset_stop(cs);
 +      tasklet_kill(&cs->write_tasklet);
 +      flush_send_queue(cs);
 +      cs->dev = NULL;
 +      gigaset_freecs(cs);
 +
 +      gig_dbg(DEBUG_INIT, "Shutdown of HLL done");
 +}
 +
 +/*
 + * Called by the tty driver when the tty line is hung up.
 + * Wait for I/O to driver to complete and unregister ISDN device.
 + * This is already done by the close routine, so just call that.
 + * Called from process context.
 + */
 +static int gigaset_tty_hangup(struct tty_struct *tty)
 +{
 +      gigaset_tty_close(tty);
 +      return 0;
 +}
 +
 +/*
 + * Ioctl on the tty.
 + * Called in process context only.
 + * May be re-entered by multiple ioctl calling threads.
 + */
 +static int
 +gigaset_tty_ioctl(struct tty_struct *tty, struct file *file,
 +                unsigned int cmd, unsigned long arg)
 +{
 +      struct cardstate *cs = cs_get(tty);
 +      int rc, val;
 +      int __user *p = (int __user *)arg;
 +
 +      if (!cs)
 +              return -ENXIO;
 +
 +      switch (cmd) {
 +
 +      case FIONREAD:
 +              /* unused, always return zero */
 +              val = 0;
 +              rc = put_user(val, p);
 +              break;
 +
 +      case TCFLSH:
 +              /* flush our buffers and the serial port's buffer */
 +              switch (arg) {
 +              case TCIFLUSH:
 +                      /* no own input buffer to flush */
 +                      break;
 +              case TCIOFLUSH:
 +              case TCOFLUSH:
 +                      flush_send_queue(cs);
 +                      break;
 +              }
 +              /* fall through */
 +
 +      default:
 +              /* pass through to underlying serial device */
 +              rc = n_tty_ioctl_helper(tty, file, cmd, arg);
 +              break;
 +      }
 +      cs_put(cs);
 +      return rc;
 +}
 +
 +/*
 + * Called by the tty driver when a block of data has been received.
 + * Will not be re-entered while running but other ldisc functions
 + * may be called in parallel.
 + * Can be called from hard interrupt level as well as soft interrupt
 + * level or mainline.
 + * Parameters:
 + *    tty     tty structure
 + *    buf     buffer containing received characters
 + *    cflags  buffer containing error flags for received characters (ignored)
 + *    count   number of received characters
 + */
 +static void
 +gigaset_tty_receive(struct tty_struct *tty, const unsigned char *buf,
 +                  char *cflags, int count)
 +{
 +      struct cardstate *cs = cs_get(tty);
 +      unsigned tail, head, n;
 +      struct inbuf_t *inbuf;
 +
 +      if (!cs)
 +              return;
 +      inbuf = cs->inbuf;
 +      if (!inbuf) {
 +              dev_err(cs->dev, "%s: no inbuf\n", __func__);
 +              cs_put(cs);
 +              return;
 +      }
 +
 +      tail = inbuf->tail;
 +      head = inbuf->head;
 +      gig_dbg(DEBUG_INTR, "buffer state: %u -> %u, receive %u bytes",
 +              head, tail, count);
 +
 +      if (head <= tail) {
 +              /* possible buffer wraparound */
 +              n = min_t(unsigned, count, RBUFSIZE - tail);
 +              memcpy(inbuf->data + tail, buf, n);
 +              tail = (tail + n) % RBUFSIZE;
 +              buf += n;
 +              count -= n;
 +      }
 +
 +      if (count > 0) {
 +              /* tail < head and some data left */
 +              n = head - tail - 1;
 +              if (count > n) {
 +                      dev_err(cs->dev,
 +                              "inbuf overflow, discarding %d bytes\n",
 +                              count - n);
 +                      count = n;
 +              }
 +              memcpy(inbuf->data + tail, buf, count);
 +              tail += count;
 +      }
 +
 +      gig_dbg(DEBUG_INTR, "setting tail to %u", tail);
 +      inbuf->tail = tail;
 +
 +      /* Everything was received .. Push data into handler */
 +      gig_dbg(DEBUG_INTR, "%s-->BH", __func__);
 +      gigaset_schedule_event(cs);
 +      cs_put(cs);
 +}
 +
 +/*
 + * Called by the tty driver when there's room for more data to send.
 + */
 +static void
 +gigaset_tty_wakeup(struct tty_struct *tty)
 +{
 +      struct cardstate *cs = cs_get(tty);
 +
 +      clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
 +      if (!cs)
 +              return;
 +      tasklet_schedule(&cs->write_tasklet);
 +      cs_put(cs);
 +}
 +
 +static struct tty_ldisc_ops gigaset_ldisc = {
 +      .owner          = THIS_MODULE,
 +      .magic          = TTY_LDISC_MAGIC,
 +      .name           = "ser_gigaset",
 +      .open           = gigaset_tty_open,
 +      .close          = gigaset_tty_close,
 +      .hangup         = gigaset_tty_hangup,
 +      .ioctl          = gigaset_tty_ioctl,
 +      .receive_buf    = gigaset_tty_receive,
 +      .write_wakeup   = gigaset_tty_wakeup,
 +};
 +
 +
 +/* Initialization / Shutdown */
 +/* ========================= */
 +
 +static int __init ser_gigaset_init(void)
 +{
 +      int rc;
 +
 +      gig_dbg(DEBUG_INIT, "%s", __func__);
 +      rc = platform_driver_register(&device_driver);
 +      if (rc != 0) {
 +              pr_err("error %d registering platform driver\n", rc);
 +              return rc;
 +      }
 +
 +      /* allocate memory for our driver state and initialize it */
 +      driver = gigaset_initdriver(GIGASET_MINOR, GIGASET_MINORS,
 +                                  GIGASET_MODULENAME, GIGASET_DEVNAME,
 +                                  &ops, THIS_MODULE);
 +      if (!driver) {
 +              rc = -ENOMEM;
 +              goto error;
 +      }
 +
 +      rc = tty_register_ldisc(N_GIGASET_M101, &gigaset_ldisc);
 +      if (rc != 0) {
 +              pr_err("error %d registering line discipline\n", rc);
 +              goto error;
 +      }
 +
 +      return 0;
 +
 +error:
 +      if (driver) {
 +              gigaset_freedriver(driver);
 +              driver = NULL;
 +      }
 +      platform_driver_unregister(&device_driver);
 +      return rc;
 +}
 +
 +static void __exit ser_gigaset_exit(void)
 +{
 +      int rc;
 +
 +      gig_dbg(DEBUG_INIT, "%s", __func__);
 +
 +      if (driver) {
 +              gigaset_freedriver(driver);
 +              driver = NULL;
 +      }
 +
 +      rc = tty_unregister_ldisc(N_GIGASET_M101);
 +      if (rc != 0)
 +              pr_err("error %d unregistering line discipline\n", rc);
 +
 +      platform_driver_unregister(&device_driver);
 +}
 +
 +module_init(ser_gigaset_init);
 +module_exit(ser_gigaset_exit);
index eade36d,0000000..1b9b436
mode 100644,000000..100644
--- /dev/null
@@@ -1,949 -1,0 +1,946 @@@
-  *    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.
++// SPDX-License-Identifier: GPL-2.0-or-later
 +/*
 + * USB driver for Gigaset 307x directly or using M105 Data.
 + *
 + * Copyright (c) 2001 by Stefan Eilers
 + *                   and Hansjoerg Lipp <hjlipp@web.de>.
 + *
 + * This driver was derived from the USB skeleton driver by
 + * Greg Kroah-Hartman <greg@kroah.com>
 + *
 + * =====================================================================
 + * =====================================================================
 + */
 +
 +#include "gigaset.h"
 +#include <linux/usb.h>
 +#include <linux/module.h>
 +#include <linux/moduleparam.h>
 +
 +/* Version Information */
 +#define DRIVER_AUTHOR "Hansjoerg Lipp <hjlipp@web.de>, Stefan Eilers"
 +#define DRIVER_DESC "USB Driver for Gigaset 307x using M105"
 +
 +/* Module parameters */
 +
 +static int startmode = SM_ISDN;
 +static int cidmode = 1;
 +
 +module_param(startmode, int, S_IRUGO);
 +module_param(cidmode, int, S_IRUGO);
 +MODULE_PARM_DESC(startmode, "start in isdn4linux mode");
 +MODULE_PARM_DESC(cidmode, "Call-ID mode");
 +
 +#define GIGASET_MINORS     1
 +#define GIGASET_MINOR      8
 +#define GIGASET_MODULENAME "usb_gigaset"
 +#define GIGASET_DEVNAME    "ttyGU"
 +
 +/* length limit according to Siemens 3070usb-protokoll.doc ch. 2.1 */
 +#define IF_WRITEBUF 264
 +
 +/* Values for the Gigaset M105 Data */
 +#define USB_M105_VENDOR_ID    0x0681
 +#define USB_M105_PRODUCT_ID   0x0009
 +
 +/* table of devices that work with this driver */
 +static const struct usb_device_id gigaset_table[] = {
 +      { USB_DEVICE(USB_M105_VENDOR_ID, USB_M105_PRODUCT_ID) },
 +      { }                                     /* Terminating entry */
 +};
 +
 +MODULE_DEVICE_TABLE(usb, gigaset_table);
 +
 +/*
 + * Control requests (empty fields: 00)
 + *
 + *       RT|RQ|VALUE|INDEX|LEN  |DATA
 + * In:
 + *       C1 08             01
 + *            Get flags (1 byte). Bits: 0=dtr,1=rts,3-7:?
 + *       C1 0F             ll ll
 + *            Get device information/status (llll: 0x200 and 0x40 seen).
 + *            Real size: I only saw MIN(llll,0x64).
 + *            Contents: seems to be always the same...
 + *              offset 0x00: Length of this structure (0x64) (len: 1,2,3 bytes)
 + *              offset 0x3c: String (16 bit chars): "MCCI USB Serial V2.0"
 + *              rest:        ?
 + * Out:
 + *       41 11
 + *            Initialize/reset device ?
 + *       41 00 xx 00
 + *            ? (xx=00 or 01; 01 on start, 00 on close)
 + *       41 07 vv mm
 + *            Set/clear flags vv=value, mm=mask (see RQ 08)
 + *       41 12 xx
 + *            Used before the following configuration requests are issued
 + *            (with xx=0x0f). I've seen other values<0xf, though.
 + *       41 01 xx xx
 + *            Set baud rate. xxxx=ceil(0x384000/rate)=trunc(0x383fff/rate)+1.
 + *       41 03 ps bb
 + *            Set byte size and parity. p:  0x20=even,0x10=odd,0x00=no parity
 + *                                     [    0x30: m, 0x40: s           ]
 + *                                     [s:  0: 1 stop bit; 1: 1.5; 2: 2]
 + *                                      bb: bits/byte (seen 7 and 8)
 + *       41 13 -- -- -- -- 10 00 ww 00 00 00 xx 00 00 00 yy 00 00 00 zz 00 00 00
 + *            ??
 + *            Initialization: 01, 40, 00, 00
 + *            Open device:    00  40, 00, 00
 + *            yy and zz seem to be equal, either 0x00 or 0x0a
 + *            (ww,xx) pairs seen: (00,00), (00,40), (01,40), (09,80), (19,80)
 + *       41 19 -- -- -- -- 06 00 00 00 00 xx 11 13
 + *            Used after every "configuration sequence" (RQ 12, RQs 01/03/13).
 + *            xx is usually 0x00 but was 0x7e before starting data transfer
 + *            in unimodem mode. So, this might be an array of characters that
 + *            need special treatment ("commit all bufferd data"?), 11=^Q, 13=^S.
 + *
 + * Unimodem mode: use "modprobe ppp_async flag_time=0" as the device _needs_ two
 + * flags per packet.
 + */
 +
 +/* functions called if a device of this driver is connected/disconnected */
 +static int gigaset_probe(struct usb_interface *interface,
 +                       const struct usb_device_id *id);
 +static void gigaset_disconnect(struct usb_interface *interface);
 +
 +/* functions called before/after suspend */
 +static int gigaset_suspend(struct usb_interface *intf, pm_message_t message);
 +static int gigaset_resume(struct usb_interface *intf);
 +static int gigaset_pre_reset(struct usb_interface *intf);
 +
 +static struct gigaset_driver *driver;
 +
 +/* usb specific object needed to register this driver with the usb subsystem */
 +static struct usb_driver gigaset_usb_driver = {
 +      .name =         GIGASET_MODULENAME,
 +      .probe =        gigaset_probe,
 +      .disconnect =   gigaset_disconnect,
 +      .id_table =     gigaset_table,
 +      .suspend =      gigaset_suspend,
 +      .resume =       gigaset_resume,
 +      .reset_resume = gigaset_resume,
 +      .pre_reset =    gigaset_pre_reset,
 +      .post_reset =   gigaset_resume,
 +      .disable_hub_initiated_lpm = 1,
 +};
 +
 +struct usb_cardstate {
 +      struct usb_device       *udev;          /* usb device pointer */
 +      struct usb_interface    *interface;     /* interface for this device */
 +      int                     busy;           /* bulk output in progress */
 +
 +      /* Output buffer */
 +      unsigned char           *bulk_out_buffer;
 +      int                     bulk_out_size;
 +      int                     bulk_out_epnum;
 +      struct urb              *bulk_out_urb;
 +
 +      /* Input buffer */
 +      unsigned char           *rcvbuf;
 +      int                     rcvbuf_size;
 +      struct urb              *read_urb;
 +
 +      char                    bchars[6];              /* for request 0x19 */
 +};
 +
 +static inline unsigned tiocm_to_gigaset(unsigned state)
 +{
 +      return ((state & TIOCM_DTR) ? 1 : 0) | ((state & TIOCM_RTS) ? 2 : 0);
 +}
 +
 +static int gigaset_set_modem_ctrl(struct cardstate *cs, unsigned old_state,
 +                                unsigned new_state)
 +{
 +      struct usb_device *udev = cs->hw.usb->udev;
 +      unsigned mask, val;
 +      int r;
 +
 +      mask = tiocm_to_gigaset(old_state ^ new_state);
 +      val = tiocm_to_gigaset(new_state);
 +
 +      gig_dbg(DEBUG_USBREQ, "set flags 0x%02x with mask 0x%02x", val, mask);
 +      r = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 7, 0x41,
 +                          (val & 0xff) | ((mask & 0xff) << 8), 0,
 +                          NULL, 0, 2000 /* timeout? */);
 +      if (r < 0)
 +              return r;
 +      return 0;
 +}
 +
 +/*
 + * Set M105 configuration value
 + * using undocumented device commands reverse engineered from USB traces
 + * of the Siemens Windows driver
 + */
 +static int set_value(struct cardstate *cs, u8 req, u16 val)
 +{
 +      struct usb_device *udev = cs->hw.usb->udev;
 +      int r, r2;
 +
 +      gig_dbg(DEBUG_USBREQ, "request %02x (%04x)",
 +              (unsigned)req, (unsigned)val);
 +      r = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0x12, 0x41,
 +                          0xf /*?*/, 0, NULL, 0, 2000 /*?*/);
 +      /* no idea what this does */
 +      if (r < 0) {
 +              dev_err(&udev->dev, "error %d on request 0x12\n", -r);
 +              return r;
 +      }
 +
 +      r = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), req, 0x41,
 +                          val, 0, NULL, 0, 2000 /*?*/);
 +      if (r < 0)
 +              dev_err(&udev->dev, "error %d on request 0x%02x\n",
 +                      -r, (unsigned)req);
 +
 +      r2 = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0x19, 0x41,
 +                           0, 0, cs->hw.usb->bchars, 6, 2000 /*?*/);
 +      if (r2 < 0)
 +              dev_err(&udev->dev, "error %d on request 0x19\n", -r2);
 +
 +      return r < 0 ? r : (r2 < 0 ? r2 : 0);
 +}
 +
 +/*
 + * set the baud rate on the internal serial adapter
 + * using the undocumented parameter setting command
 + */
 +static int gigaset_baud_rate(struct cardstate *cs, unsigned cflag)
 +{
 +      u16 val;
 +      u32 rate;
 +
 +      cflag &= CBAUD;
 +
 +      switch (cflag) {
 +      case    B300: rate =     300; break;
 +      case    B600: rate =     600; break;
 +      case   B1200: rate =    1200; break;
 +      case   B2400: rate =    2400; break;
 +      case   B4800: rate =    4800; break;
 +      case   B9600: rate =    9600; break;
 +      case  B19200: rate =   19200; break;
 +      case  B38400: rate =   38400; break;
 +      case  B57600: rate =   57600; break;
 +      case B115200: rate =  115200; break;
 +      default:
 +              rate =  9600;
 +              dev_err(cs->dev, "unsupported baudrate request 0x%x,"
 +                      " using default of B9600\n", cflag);
 +      }
 +
 +      val = 0x383fff / rate + 1;
 +
 +      return set_value(cs, 1, val);
 +}
 +
 +/*
 + * set the line format on the internal serial adapter
 + * using the undocumented parameter setting command
 + */
 +static int gigaset_set_line_ctrl(struct cardstate *cs, unsigned cflag)
 +{
 +      u16 val = 0;
 +
 +      /* set the parity */
 +      if (cflag & PARENB)
 +              val |= (cflag & PARODD) ? 0x10 : 0x20;
 +
 +      /* set the number of data bits */
 +      switch (cflag & CSIZE) {
 +      case CS5:
 +              val |= 5 << 8; break;
 +      case CS6:
 +              val |= 6 << 8; break;
 +      case CS7:
 +              val |= 7 << 8; break;
 +      case CS8:
 +              val |= 8 << 8; break;
 +      default:
 +              dev_err(cs->dev, "CSIZE was not CS5-CS8, using default of 8\n");
 +              val |= 8 << 8;
 +              break;
 +      }
 +
 +      /* set the number of stop bits */
 +      if (cflag & CSTOPB) {
 +              if ((cflag & CSIZE) == CS5)
 +                      val |= 1; /* 1.5 stop bits */
 +              else
 +                      val |= 2; /* 2 stop bits */
 +      }
 +
 +      return set_value(cs, 3, val);
 +}
 +
 +
 +/*============================================================================*/
 +static int gigaset_init_bchannel(struct bc_state *bcs)
 +{
 +      /* nothing to do for M10x */
 +      gigaset_bchannel_up(bcs);
 +      return 0;
 +}
 +
 +static int gigaset_close_bchannel(struct bc_state *bcs)
 +{
 +      /* nothing to do for M10x */
 +      gigaset_bchannel_down(bcs);
 +      return 0;
 +}
 +
 +static int write_modem(struct cardstate *cs);
 +static int send_cb(struct cardstate *cs);
 +
 +
 +/* Write tasklet handler: Continue sending current skb, or send command, or
 + * start sending an skb from the send queue.
 + */
 +static void gigaset_modem_fill(unsigned long data)
 +{
 +      struct cardstate *cs = (struct cardstate *) data;
 +      struct bc_state *bcs = &cs->bcs[0]; /* only one channel */
 +
 +      gig_dbg(DEBUG_OUTPUT, "modem_fill");
 +
 +      if (cs->hw.usb->busy) {
 +              gig_dbg(DEBUG_OUTPUT, "modem_fill: busy");
 +              return;
 +      }
 +
 +again:
 +      if (!bcs->tx_skb) {     /* no skb is being sent */
 +              if (cs->cmdbuf) {       /* commands to send? */
 +                      gig_dbg(DEBUG_OUTPUT, "modem_fill: cb");
 +                      if (send_cb(cs) < 0) {
 +                              gig_dbg(DEBUG_OUTPUT,
 +                                      "modem_fill: send_cb failed");
 +                              goto again; /* no callback will be called! */
 +                      }
 +                      return;
 +              }
 +
 +              /* skbs to send? */
 +              bcs->tx_skb = skb_dequeue(&bcs->squeue);
 +              if (!bcs->tx_skb)
 +                      return;
 +
 +              gig_dbg(DEBUG_INTR, "Dequeued skb (Adr: %lx)!",
 +                      (unsigned long) bcs->tx_skb);
 +      }
 +
 +      gig_dbg(DEBUG_OUTPUT, "modem_fill: tx_skb");
 +      if (write_modem(cs) < 0) {
 +              gig_dbg(DEBUG_OUTPUT, "modem_fill: write_modem failed");
 +              goto again;     /* no callback will be called! */
 +      }
 +}
 +
 +/*
 + * Interrupt Input URB completion routine
 + */
 +static void gigaset_read_int_callback(struct urb *urb)
 +{
 +      struct cardstate *cs = urb->context;
 +      struct inbuf_t *inbuf = cs->inbuf;
 +      int status = urb->status;
 +      int r;
 +      unsigned numbytes;
 +      unsigned char *src;
 +      unsigned long flags;
 +
 +      if (!status) {
 +              numbytes = urb->actual_length;
 +
 +              if (numbytes) {
 +                      src = cs->hw.usb->rcvbuf;
 +                      if (unlikely(*src))
 +                              dev_warn(cs->dev,
 +                                       "%s: There was no leading 0, but 0x%02x!\n",
 +                                       __func__, (unsigned) *src);
 +                      ++src; /* skip leading 0x00 */
 +                      --numbytes;
 +                      if (gigaset_fill_inbuf(inbuf, src, numbytes)) {
 +                              gig_dbg(DEBUG_INTR, "%s-->BH", __func__);
 +                              gigaset_schedule_event(inbuf->cs);
 +                      }
 +              } else
 +                      gig_dbg(DEBUG_INTR, "Received zero block length");
 +      } else {
 +              /* The urb might have been killed. */
 +              gig_dbg(DEBUG_ANY, "%s - nonzero status received: %d",
 +                      __func__, status);
 +              if (status == -ENOENT || status == -ESHUTDOWN)
 +                      /* killed or endpoint shutdown: don't resubmit */
 +                      return;
 +      }
 +
 +      /* resubmit URB */
 +      spin_lock_irqsave(&cs->lock, flags);
 +      if (!cs->connected) {
 +              spin_unlock_irqrestore(&cs->lock, flags);
 +              pr_err("%s: disconnected\n", __func__);
 +              return;
 +      }
 +      r = usb_submit_urb(urb, GFP_ATOMIC);
 +      spin_unlock_irqrestore(&cs->lock, flags);
 +      if (r)
 +              dev_err(cs->dev, "error %d resubmitting URB\n", -r);
 +}
 +
 +
 +/* This callback routine is called when data was transmitted to the device. */
 +static void gigaset_write_bulk_callback(struct urb *urb)
 +{
 +      struct cardstate *cs = urb->context;
 +      int status = urb->status;
 +      unsigned long flags;
 +
 +      switch (status) {
 +      case 0:                 /* normal completion */
 +              break;
 +      case -ENOENT:           /* killed */
 +              gig_dbg(DEBUG_ANY, "%s: killed", __func__);
 +              cs->hw.usb->busy = 0;
 +              return;
 +      default:
 +              dev_err(cs->dev, "bulk transfer failed (status %d)\n",
 +                      -status);
 +              /* That's all we can do. Communication problems
 +                 are handled by timeouts or network protocols. */
 +      }
 +
 +      spin_lock_irqsave(&cs->lock, flags);
 +      if (!cs->connected) {
 +              pr_err("%s: disconnected\n", __func__);
 +      } else {
 +              cs->hw.usb->busy = 0;
 +              tasklet_schedule(&cs->write_tasklet);
 +      }
 +      spin_unlock_irqrestore(&cs->lock, flags);
 +}
 +
 +static int send_cb(struct cardstate *cs)
 +{
 +      struct cmdbuf_t *cb = cs->cmdbuf;
 +      unsigned long flags;
 +      int count;
 +      int status = -ENOENT;
 +      struct usb_cardstate *ucs = cs->hw.usb;
 +
 +      do {
 +              if (!cb->len) {
 +                      spin_lock_irqsave(&cs->cmdlock, flags);
 +                      cs->cmdbytes -= cs->curlen;
 +                      gig_dbg(DEBUG_OUTPUT, "send_cb: sent %u bytes, %u left",
 +                              cs->curlen, cs->cmdbytes);
 +                      cs->cmdbuf = cb->next;
 +                      if (cs->cmdbuf) {
 +                              cs->cmdbuf->prev = NULL;
 +                              cs->curlen = cs->cmdbuf->len;
 +                      } else {
 +                              cs->lastcmdbuf = NULL;
 +                              cs->curlen = 0;
 +                      }
 +                      spin_unlock_irqrestore(&cs->cmdlock, flags);
 +
 +                      if (cb->wake_tasklet)
 +                              tasklet_schedule(cb->wake_tasklet);
 +                      kfree(cb);
 +
 +                      cb = cs->cmdbuf;
 +              }
 +
 +              if (cb) {
 +                      count = min(cb->len, ucs->bulk_out_size);
 +                      gig_dbg(DEBUG_OUTPUT, "send_cb: send %d bytes", count);
 +
 +                      usb_fill_bulk_urb(ucs->bulk_out_urb, ucs->udev,
 +                                        usb_sndbulkpipe(ucs->udev,
 +                                                        ucs->bulk_out_epnum),
 +                                        cb->buf + cb->offset, count,
 +                                        gigaset_write_bulk_callback, cs);
 +
 +                      cb->offset += count;
 +                      cb->len -= count;
 +                      ucs->busy = 1;
 +
 +                      spin_lock_irqsave(&cs->lock, flags);
 +                      status = cs->connected ?
 +                              usb_submit_urb(ucs->bulk_out_urb, GFP_ATOMIC) :
 +                              -ENODEV;
 +                      spin_unlock_irqrestore(&cs->lock, flags);
 +
 +                      if (status) {
 +                              ucs->busy = 0;
 +                              dev_err(cs->dev,
 +                                      "could not submit urb (error %d)\n",
 +                                      -status);
 +                              cb->len = 0; /* skip urb => remove cb+wakeup
 +                                              in next loop cycle */
 +                      }
 +              }
 +      } while (cb && status); /* next command on error */
 +
 +      return status;
 +}
 +
 +/* Send command to device. */
 +static int gigaset_write_cmd(struct cardstate *cs, struct cmdbuf_t *cb)
 +{
 +      unsigned long flags;
 +      int len;
 +
 +      gigaset_dbg_buffer(cs->mstate != MS_LOCKED ?
 +                         DEBUG_TRANSCMD : DEBUG_LOCKCMD,
 +                         "CMD Transmit", cb->len, cb->buf);
 +
 +      spin_lock_irqsave(&cs->cmdlock, flags);
 +      cb->prev = cs->lastcmdbuf;
 +      if (cs->lastcmdbuf)
 +              cs->lastcmdbuf->next = cb;
 +      else {
 +              cs->cmdbuf = cb;
 +              cs->curlen = cb->len;
 +      }
 +      cs->cmdbytes += cb->len;
 +      cs->lastcmdbuf = cb;
 +      spin_unlock_irqrestore(&cs->cmdlock, flags);
 +
 +      spin_lock_irqsave(&cs->lock, flags);
 +      len = cb->len;
 +      if (cs->connected)
 +              tasklet_schedule(&cs->write_tasklet);
 +      spin_unlock_irqrestore(&cs->lock, flags);
 +      return len;
 +}
 +
 +static int gigaset_write_room(struct cardstate *cs)
 +{
 +      unsigned bytes;
 +
 +      bytes = cs->cmdbytes;
 +      return bytes < IF_WRITEBUF ? IF_WRITEBUF - bytes : 0;
 +}
 +
 +static int gigaset_chars_in_buffer(struct cardstate *cs)
 +{
 +      return cs->cmdbytes;
 +}
 +
 +/*
 + * set the break characters on the internal serial adapter
 + * using undocumented device commands reverse engineered from USB traces
 + * of the Siemens Windows driver
 + */
 +static int gigaset_brkchars(struct cardstate *cs, const unsigned char buf[6])
 +{
 +      struct usb_device *udev = cs->hw.usb->udev;
 +
 +      gigaset_dbg_buffer(DEBUG_USBREQ, "brkchars", 6, buf);
 +      memcpy(cs->hw.usb->bchars, buf, 6);
 +      return usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0x19, 0x41,
 +                             0, 0, &buf, 6, 2000);
 +}
 +
 +static void gigaset_freebcshw(struct bc_state *bcs)
 +{
 +      /* unused */
 +}
 +
 +/* Initialize the b-channel structure */
 +static int gigaset_initbcshw(struct bc_state *bcs)
 +{
 +      /* unused */
 +      bcs->hw.usb = NULL;
 +      return 0;
 +}
 +
 +static void gigaset_reinitbcshw(struct bc_state *bcs)
 +{
 +      /* nothing to do for M10x */
 +}
 +
 +static void gigaset_freecshw(struct cardstate *cs)
 +{
 +      tasklet_kill(&cs->write_tasklet);
 +      kfree(cs->hw.usb);
 +}
 +
 +static int gigaset_initcshw(struct cardstate *cs)
 +{
 +      struct usb_cardstate *ucs;
 +
 +      cs->hw.usb = ucs =
 +              kmalloc(sizeof(struct usb_cardstate), GFP_KERNEL);
 +      if (!ucs) {
 +              pr_err("out of memory\n");
 +              return -ENOMEM;
 +      }
 +
 +      ucs->bchars[0] = 0;
 +      ucs->bchars[1] = 0;
 +      ucs->bchars[2] = 0;
 +      ucs->bchars[3] = 0;
 +      ucs->bchars[4] = 0x11;
 +      ucs->bchars[5] = 0x13;
 +      ucs->bulk_out_buffer = NULL;
 +      ucs->bulk_out_urb = NULL;
 +      ucs->read_urb = NULL;
 +      tasklet_init(&cs->write_tasklet,
 +                   gigaset_modem_fill, (unsigned long) cs);
 +
 +      return 0;
 +}
 +
 +/* Send data from current skb to the device. */
 +static int write_modem(struct cardstate *cs)
 +{
 +      int ret = 0;
 +      int count;
 +      struct bc_state *bcs = &cs->bcs[0]; /* only one channel */
 +      struct usb_cardstate *ucs = cs->hw.usb;
 +      unsigned long flags;
 +
 +      gig_dbg(DEBUG_OUTPUT, "len: %d...", bcs->tx_skb->len);
 +
 +      if (!bcs->tx_skb->len) {
 +              dev_kfree_skb_any(bcs->tx_skb);
 +              bcs->tx_skb = NULL;
 +              return -EINVAL;
 +      }
 +
 +      /* Copy data to bulk out buffer and transmit data */
 +      count = min(bcs->tx_skb->len, (unsigned) ucs->bulk_out_size);
 +      skb_copy_from_linear_data(bcs->tx_skb, ucs->bulk_out_buffer, count);
 +      skb_pull(bcs->tx_skb, count);
 +      ucs->busy = 1;
 +      gig_dbg(DEBUG_OUTPUT, "write_modem: send %d bytes", count);
 +
 +      spin_lock_irqsave(&cs->lock, flags);
 +      if (cs->connected) {
 +              usb_fill_bulk_urb(ucs->bulk_out_urb, ucs->udev,
 +                                usb_sndbulkpipe(ucs->udev,
 +                                                ucs->bulk_out_epnum),
 +                                ucs->bulk_out_buffer, count,
 +                                gigaset_write_bulk_callback, cs);
 +              ret = usb_submit_urb(ucs->bulk_out_urb, GFP_ATOMIC);
 +      } else {
 +              ret = -ENODEV;
 +      }
 +      spin_unlock_irqrestore(&cs->lock, flags);
 +
 +      if (ret) {
 +              dev_err(cs->dev, "could not submit urb (error %d)\n", -ret);
 +              ucs->busy = 0;
 +      }
 +
 +      if (!bcs->tx_skb->len) {
 +              /* skb sent completely */
 +              gigaset_skb_sent(bcs, bcs->tx_skb);
 +
 +              gig_dbg(DEBUG_INTR, "kfree skb (Adr: %lx)!",
 +                      (unsigned long) bcs->tx_skb);
 +              dev_kfree_skb_any(bcs->tx_skb);
 +              bcs->tx_skb = NULL;
 +      }
 +
 +      return ret;
 +}
 +
 +static int gigaset_probe(struct usb_interface *interface,
 +                       const struct usb_device_id *id)
 +{
 +      int retval;
 +      struct usb_device *udev = interface_to_usbdev(interface);
 +      struct usb_host_interface *hostif = interface->cur_altsetting;
 +      struct cardstate *cs = NULL;
 +      struct usb_cardstate *ucs = NULL;
 +      struct usb_endpoint_descriptor *endpoint;
 +      int buffer_size;
 +
 +      gig_dbg(DEBUG_ANY, "%s: Check if device matches ...", __func__);
 +
 +      /* See if the device offered us matches what we can accept */
 +      if ((le16_to_cpu(udev->descriptor.idVendor)  != USB_M105_VENDOR_ID) ||
 +          (le16_to_cpu(udev->descriptor.idProduct) != USB_M105_PRODUCT_ID)) {
 +              gig_dbg(DEBUG_ANY, "device ID (0x%x, 0x%x) not for me - skip",
 +                      le16_to_cpu(udev->descriptor.idVendor),
 +                      le16_to_cpu(udev->descriptor.idProduct));
 +              return -ENODEV;
 +      }
 +      if (hostif->desc.bInterfaceNumber != 0) {
 +              gig_dbg(DEBUG_ANY, "interface %d not for me - skip",
 +                      hostif->desc.bInterfaceNumber);
 +              return -ENODEV;
 +      }
 +      if (hostif->desc.bAlternateSetting != 0) {
 +              dev_notice(&udev->dev, "unsupported altsetting %d - skip",
 +                         hostif->desc.bAlternateSetting);
 +              return -ENODEV;
 +      }
 +      if (hostif->desc.bInterfaceClass != 255) {
 +              dev_notice(&udev->dev, "unsupported interface class %d - skip",
 +                         hostif->desc.bInterfaceClass);
 +              return -ENODEV;
 +      }
 +
 +      dev_info(&udev->dev, "%s: Device matched ... !\n", __func__);
 +
 +      /* allocate memory for our device state and initialize it */
 +      cs = gigaset_initcs(driver, 1, 1, 0, cidmode, GIGASET_MODULENAME);
 +      if (!cs)
 +              return -ENODEV;
 +      ucs = cs->hw.usb;
 +
 +      /* save off device structure ptrs for later use */
 +      usb_get_dev(udev);
 +      ucs->udev = udev;
 +      ucs->interface = interface;
 +      cs->dev = &interface->dev;
 +
 +      /* save address of controller structure */
 +      usb_set_intfdata(interface, cs);
 +
 +      endpoint = &hostif->endpoint[0].desc;
 +
 +      buffer_size = le16_to_cpu(endpoint->wMaxPacketSize);
 +      ucs->bulk_out_size = buffer_size;
 +      ucs->bulk_out_epnum = usb_endpoint_num(endpoint);
 +      ucs->bulk_out_buffer = kmalloc(buffer_size, GFP_KERNEL);
 +      if (!ucs->bulk_out_buffer) {
 +              dev_err(cs->dev, "Couldn't allocate bulk_out_buffer\n");
 +              retval = -ENOMEM;
 +              goto error;
 +      }
 +
 +      ucs->bulk_out_urb = usb_alloc_urb(0, GFP_KERNEL);
 +      if (!ucs->bulk_out_urb) {
 +              dev_err(cs->dev, "Couldn't allocate bulk_out_urb\n");
 +              retval = -ENOMEM;
 +              goto error;
 +      }
 +
 +      endpoint = &hostif->endpoint[1].desc;
 +
 +      ucs->busy = 0;
 +
 +      ucs->read_urb = usb_alloc_urb(0, GFP_KERNEL);
 +      if (!ucs->read_urb) {
 +              dev_err(cs->dev, "No free urbs available\n");
 +              retval = -ENOMEM;
 +              goto error;
 +      }
 +      buffer_size = le16_to_cpu(endpoint->wMaxPacketSize);
 +      ucs->rcvbuf_size = buffer_size;
 +      ucs->rcvbuf = kmalloc(buffer_size, GFP_KERNEL);
 +      if (!ucs->rcvbuf) {
 +              dev_err(cs->dev, "Couldn't allocate rcvbuf\n");
 +              retval = -ENOMEM;
 +              goto error;
 +      }
 +      /* Fill the interrupt urb and send it to the core */
 +      usb_fill_int_urb(ucs->read_urb, udev,
 +                       usb_rcvintpipe(udev, usb_endpoint_num(endpoint)),
 +                       ucs->rcvbuf, buffer_size,
 +                       gigaset_read_int_callback,
 +                       cs, endpoint->bInterval);
 +
 +      retval = usb_submit_urb(ucs->read_urb, GFP_KERNEL);
 +      if (retval) {
 +              dev_err(cs->dev, "Could not submit URB (error %d)\n", -retval);
 +              goto error;
 +      }
 +
 +      /* tell common part that the device is ready */
 +      if (startmode == SM_LOCKED)
 +              cs->mstate = MS_LOCKED;
 +
 +      retval = gigaset_start(cs);
 +      if (retval < 0) {
 +              tasklet_kill(&cs->write_tasklet);
 +              goto error;
 +      }
 +      return 0;
 +
 +error:
 +      usb_kill_urb(ucs->read_urb);
 +      kfree(ucs->bulk_out_buffer);
 +      usb_free_urb(ucs->bulk_out_urb);
 +      kfree(ucs->rcvbuf);
 +      usb_free_urb(ucs->read_urb);
 +      usb_set_intfdata(interface, NULL);
 +      ucs->read_urb = ucs->bulk_out_urb = NULL;
 +      ucs->rcvbuf = ucs->bulk_out_buffer = NULL;
 +      usb_put_dev(ucs->udev);
 +      ucs->udev = NULL;
 +      ucs->interface = NULL;
 +      gigaset_freecs(cs);
 +      return retval;
 +}
 +
 +static void gigaset_disconnect(struct usb_interface *interface)
 +{
 +      struct cardstate *cs;
 +      struct usb_cardstate *ucs;
 +
 +      cs = usb_get_intfdata(interface);
 +      ucs = cs->hw.usb;
 +
 +      dev_info(cs->dev, "disconnecting Gigaset USB adapter\n");
 +
 +      usb_kill_urb(ucs->read_urb);
 +
 +      gigaset_stop(cs);
 +
 +      usb_set_intfdata(interface, NULL);
 +      tasklet_kill(&cs->write_tasklet);
 +
 +      usb_kill_urb(ucs->bulk_out_urb);
 +
 +      kfree(ucs->bulk_out_buffer);
 +      usb_free_urb(ucs->bulk_out_urb);
 +      kfree(ucs->rcvbuf);
 +      usb_free_urb(ucs->read_urb);
 +      ucs->read_urb = ucs->bulk_out_urb = NULL;
 +      ucs->rcvbuf = ucs->bulk_out_buffer = NULL;
 +
 +      usb_put_dev(ucs->udev);
 +      ucs->interface = NULL;
 +      ucs->udev = NULL;
 +      cs->dev = NULL;
 +      gigaset_freecs(cs);
 +}
 +
 +/* gigaset_suspend
 + * This function is called before the USB connection is suspended or reset.
 + */
 +static int gigaset_suspend(struct usb_interface *intf, pm_message_t message)
 +{
 +      struct cardstate *cs = usb_get_intfdata(intf);
 +
 +      /* stop activity */
 +      cs->connected = 0;      /* prevent rescheduling */
 +      usb_kill_urb(cs->hw.usb->read_urb);
 +      tasklet_kill(&cs->write_tasklet);
 +      usb_kill_urb(cs->hw.usb->bulk_out_urb);
 +
 +      gig_dbg(DEBUG_SUSPEND, "suspend complete");
 +      return 0;
 +}
 +
 +/* gigaset_resume
 + * This function is called after the USB connection has been resumed or reset.
 + */
 +static int gigaset_resume(struct usb_interface *intf)
 +{
 +      struct cardstate *cs = usb_get_intfdata(intf);
 +      int rc;
 +
 +      /* resubmit interrupt URB */
 +      cs->connected = 1;
 +      rc = usb_submit_urb(cs->hw.usb->read_urb, GFP_KERNEL);
 +      if (rc) {
 +              dev_err(cs->dev, "Could not submit read URB (error %d)\n", -rc);
 +              return rc;
 +      }
 +
 +      gig_dbg(DEBUG_SUSPEND, "resume complete");
 +      return 0;
 +}
 +
 +/* gigaset_pre_reset
 + * This function is called before the USB connection is reset.
 + */
 +static int gigaset_pre_reset(struct usb_interface *intf)
 +{
 +      /* same as suspend */
 +      return gigaset_suspend(intf, PMSG_ON);
 +}
 +
 +static const struct gigaset_ops ops = {
 +      .write_cmd = gigaset_write_cmd,
 +      .write_room = gigaset_write_room,
 +      .chars_in_buffer = gigaset_chars_in_buffer,
 +      .brkchars = gigaset_brkchars,
 +      .init_bchannel = gigaset_init_bchannel,
 +      .close_bchannel = gigaset_close_bchannel,
 +      .initbcshw = gigaset_initbcshw,
 +      .freebcshw = gigaset_freebcshw,
 +      .reinitbcshw = gigaset_reinitbcshw,
 +      .initcshw = gigaset_initcshw,
 +      .freecshw = gigaset_freecshw,
 +      .set_modem_ctrl = gigaset_set_modem_ctrl,
 +      .baud_rate = gigaset_baud_rate,
 +      .set_line_ctrl = gigaset_set_line_ctrl,
 +      .send_skb = gigaset_m10x_send_skb,
 +      .handle_input = gigaset_m10x_input,
 +};
 +
 +/*
 + * This function is called while kernel-module is loaded
 + */
 +static int __init usb_gigaset_init(void)
 +{
 +      int result;
 +
 +      /* allocate memory for our driver state and initialize it */
 +      driver = gigaset_initdriver(GIGASET_MINOR, GIGASET_MINORS,
 +                                  GIGASET_MODULENAME, GIGASET_DEVNAME,
 +                                  &ops, THIS_MODULE);
 +      if (driver == NULL) {
 +              result = -ENOMEM;
 +              goto error;
 +      }
 +
 +      /* register this driver with the USB subsystem */
 +      result = usb_register(&gigaset_usb_driver);
 +      if (result < 0) {
 +              pr_err("error %d registering USB driver\n", -result);
 +              goto error;
 +      }
 +
 +      pr_info(DRIVER_DESC "\n");
 +      return 0;
 +
 +error:
 +      if (driver)
 +              gigaset_freedriver(driver);
 +      driver = NULL;
 +      return result;
 +}
 +
 +/*
 + * This function is called while unloading the kernel-module
 + */
 +static void __exit usb_gigaset_exit(void)
 +{
 +      int i;
 +
 +      gigaset_blockdriver(driver); /* => probe will fail
 +                                    * => no gigaset_start any more
 +                                    */
 +
 +      /* stop all connected devices */
 +      for (i = 0; i < driver->minors; i++)
 +              gigaset_shutdown(driver->cs + i);
 +
 +      /* from now on, no isdn callback should be possible */
 +
 +      /* deregister this driver with the USB subsystem */
 +      usb_deregister(&gigaset_usb_driver);
 +      /* this will call the disconnect-callback */
 +      /* from now on, no disconnect/probe callback should be running */
 +
 +      gigaset_freedriver(driver);
 +      driver = NULL;
 +}
 +
 +
 +module_init(usb_gigaset_init);
 +module_exit(usb_gigaset_exit);
 +
 +MODULE_AUTHOR(DRIVER_AUTHOR);
 +MODULE_DESCRIPTION(DRIVER_DESC);
 +
 +MODULE_LICENSE("GPL");
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
@@@ -207,8 -206,13 +207,12 @@@ struct tls_offload_context_tx 
  };
  
  #define TLS_OFFLOAD_CONTEXT_SIZE_TX                                            \
 -      (ALIGN(sizeof(struct tls_offload_context_tx), sizeof(void *)) +        \
 -       TLS_DRIVER_STATE_SIZE)
 +      (sizeof(struct tls_offload_context_tx) + TLS_DRIVER_STATE_SIZE_TX)
  
+ enum tls_context_flags {
+       TLS_RX_SYNC_RUNNING = 0,
+ };
  struct cipher_context {
        char *iv;
        char *rec_seq;
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
diff --cc net/dsa/dsa2.c
Simple merge
Simple merge
diff --cc net/dsa/port.c
Simple merge
diff --cc net/dsa/slave.c
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
diff --cc net/ipv4/icmp.c
Simple merge
diff --cc net/ipv4/igmp.c
Simple merge
Simple merge
Simple merge
Simple merge
diff --cc net/ipv4/proc.c
Simple merge
Simple merge
diff --cc net/ipv4/tcp.c
Simple merge
Simple merge
diff --cc net/ipv4/udp.c
Simple merge
Simple merge
Simple merge
Simple merge
diff --cc net/ipv6/icmp.c
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
diff --cc net/ipv6/proc.c
Simple merge
Simple merge
Simple merge
Simple merge
diff --cc net/ipv6/udp.c
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
diff --cc net/rds/ib.c
Simple merge
Simple merge
diff --cc net/socket.c
Simple merge
Simple merge