misc: Add TS27.010 Mux driver
authorKazuhiro Ondo <kazuhiro.ondo@motorola.com>
Wed, 21 Jul 2010 18:58:29 +0000 (11:58 -0700)
committerColin Cross <ccross@android.com>
Wed, 6 Oct 2010 23:33:19 +0000 (16:33 -0700)
Change-Id: I1ab0cf0e141fcd2915b2d27d1badaa274803b73e
Signed-off-by: Rebecca Schultz Zavin <rebecca@android.com>
drivers/misc/Kconfig
drivers/misc/Makefile
drivers/misc/ts27010mux/Kconfig [new file with mode: 0644]
drivers/misc/ts27010mux/Makefile [new file with mode: 0644]
drivers/misc/ts27010mux/ts0710.h [new file with mode: 0644]
drivers/misc/ts27010mux/ts27010_ldisc.c [new file with mode: 0644]
drivers/misc/ts27010mux/ts27010_mux.c [new file with mode: 0644]
drivers/misc/ts27010mux/ts27010_mux.h [new file with mode: 0644]
drivers/misc/ts27010mux/ts27010_ringbuf.h [new file with mode: 0644]
drivers/misc/ts27010mux/ts27010_tty.c [new file with mode: 0644]

index 231b3fb882eed0e70671babede1fca426a9cc124..d826c4a7556f73aac58136abad191732380ea162 100644 (file)
@@ -480,6 +480,7 @@ config GPS_GPIO_BRCM4750
 source "drivers/misc/c2port/Kconfig"
 source "drivers/misc/eeprom/Kconfig"
 source "drivers/misc/cb710/Kconfig"
+source "drivers/misc/ts27010mux/Kconfig"
 source "drivers/misc/iwmc3200top/Kconfig"
 
 endif # MISC_DEVICES
index dc1002259afd304ce45f6867d0a04627b414f946..d0dc080d7925ad421c400a0d1b97a6a665e9f986 100644 (file)
@@ -46,3 +46,4 @@ obj-$(CONFIG_SENSORS_MAX9635) += max9635.o
 obj-$(CONFIG_SENSORS_L3G4200D) += l3g4200d.o
 obj-$(CONFIG_GPS_GPIO_BRCM4750)        += gps-gpio-brcm4750.o
 obj-$(CONFIG_MDM6600_CTRL)     += mdm6600_ctrl.o
+obj-$(CONFIG_TS27010MUX)       += ts27010mux/
diff --git a/drivers/misc/ts27010mux/Kconfig b/drivers/misc/ts27010mux/Kconfig
new file mode 100644 (file)
index 0000000..4ccb475
--- /dev/null
@@ -0,0 +1,11 @@
+#
+# TS 27.010 configuration
+#
+
+menu "Motorola TS 27.010 Mux driver"
+
+config TS27010MUX
+        tristate "Motorola TS 27.010 Mux driver"
+        default n
+
+endmenu
diff --git a/drivers/misc/ts27010mux/Makefile b/drivers/misc/ts27010mux/Makefile
new file mode 100644 (file)
index 0000000..e83cd30
--- /dev/null
@@ -0,0 +1,11 @@
+#
+# Makefile for the ts27010mux driver.
+#
+#
+MODULE_NAME = ts27010mux
+
+obj-$(CONFIG_TS27010MUX) += $(MODULE_NAME).o
+
+$(MODULE_NAME)-objs := ts27010_mux.o \
+                       ts27010_tty.o \
+                       ts27010_ldisc.o
diff --git a/drivers/misc/ts27010mux/ts0710.h b/drivers/misc/ts27010mux/ts0710.h
new file mode 100644 (file)
index 0000000..663102b
--- /dev/null
@@ -0,0 +1,273 @@
+/*
+ * File: ts0710.h
+ *
+ * Portions derived from rfcomm.c, original header as follows:
+ *
+ * Copyright (C) 2000, 2001  Axis Communications AB
+ * Copyright (C) 2002, 2004, 2009 Motorola
+ *
+ * Author: Mats Friden <mats.friden@axis.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Exceptionally, Axis Communications AB grants discretionary and
+ * conditional permissions for additional use of the text contained
+ * in the company's release of the AXIS OpenBT Stack under the
+ * provisions set forth hereunder.
+ *
+ * Provided that, if you use the AXIS OpenBT Stack with other files,
+ * that do not implement functionality as specified in the Bluetooth
+ * System specification, to produce an executable, this does not by
+ * itself cause the resulting executable to be covered by the GNU
+ * General Public License. Your use of that executable is in no way
+ * restricted on account of using the AXIS OpenBT Stack code with it.
+ *
+ * This exception does not however invalidate any other reasons why
+ * the executable file might be covered by the provisions of the GNU
+ * General Public License.
+ *
+ */
+
+#define TS0710_MAX_CHN 17
+
+#define SET_PF(ctr) ((ctr) | (1 << 4))
+#define CLR_PF(ctr) ((ctr) & 0xef)
+#define GET_PF(ctr) (((ctr) >> 4) & 0x1)
+
+#define SHORT_PAYLOAD_SIZE 127
+
+#define EA 1
+#define FCS_SIZE 1
+#define FLAG_SIZE 2
+
+#define TS0710_MAX_HDR_SIZE 5
+#define DEF_TS0710_MTU 1024
+
+#define TS0710_BASIC_FLAG 0xF9
+
+/* the control field */
+#define SABM 0x2f
+#define SABM_SIZE 4
+#define UA 0x63
+#define UA_SIZE 4
+#define DM 0x0f
+#define DISC 0x43
+#define UIH 0xef
+
+/* the type field in a multiplexer command packet */
+#define TEST 0x8
+#define FCON 0x28
+#define FCOFF 0x18
+#define MSC 0x38
+#define RPN 0x24
+#define RLS 0x14
+#define PN 0x20
+#define NSC 0x4
+
+/* V.24 modem control signals */
+#define FC 0x2
+#define RTC 0x4
+#define RTR 0x8
+#define IC 0x40
+#define DV 0x80
+
+#define CTRL_CHAN 0            /* The control channel is defined as DLCI 0 */
+#define MCC_CR 0x2
+#define MCC_CMD 1              /* Multiplexer command cr */
+#define MCC_RSP 0              /* Multiplexer response cr */
+
+static inline int mcc_is_cmd(u8 type)
+{
+       return type & MCC_CR;
+}
+
+static inline int mcc_is_rsp(u8 type)
+{
+       return !(type & MCC_CR);
+}
+
+
+#ifdef __LITTLE_ENDIAN_BITFIELD
+
+struct address_field {
+       u8 ea:1;
+       u8 cr:1;
+       u8 d:1;
+       u8 server_chn:5;
+} __attribute__ ((packed));
+
+static inline int ts0710_dlci(u8 addr)
+{
+       return (addr >> 2) & 0x3f;
+}
+
+
+struct short_length {
+       u8 ea:1;
+       u8 len:7;
+} __attribute__ ((packed));
+
+struct long_length {
+       u8 ea:1;
+       u8 l_len:7;
+       u8 h_len;
+} __attribute__ ((packed));
+
+struct short_frame_head {
+       struct address_field addr;
+       u8 control;
+       struct short_length length;
+} __attribute__ ((packed));
+
+struct short_frame {
+       struct short_frame_head h;
+       u8 data[0];
+} __attribute__ ((packed));
+
+struct long_frame_head {
+       struct address_field addr;
+       u8 control;
+       struct long_length length;
+       u8 data[0];
+} __attribute__ ((packed));
+
+struct long_frame {
+       struct long_frame_head h;
+       u8 data[0];
+} __attribute__ ((packed));
+
+/* Typedefinitions for structures used for the multiplexer commands */
+struct mcc_type {
+       u8 ea:1;
+       u8 cr:1;
+       u8 type:6;
+} __attribute__ ((packed));
+
+struct mcc_short_frame_head {
+       struct mcc_type type;
+       struct short_length length;
+       u8 value[0];
+} __attribute__ ((packed));
+
+struct mcc_short_frame {
+       struct mcc_short_frame_head h;
+       u8 value[0];
+} __attribute__ ((packed));
+
+struct mcc_long_frame_head {
+       struct mcc_type type;
+       struct long_length length;
+       u8 value[0];
+} __attribute__ ((packed));
+
+struct mcc_long_frame {
+       struct mcc_long_frame_head h;
+       u8 value[0];
+} __attribute__ ((packed));
+
+/* MSC-command */
+struct v24_sigs {
+       u8 ea:1;
+       u8 fc:1;
+       u8 rtc:1;
+       u8 rtr:1;
+       u8 reserved:2;
+       u8 ic:1;
+       u8 dv:1;
+} __attribute__ ((packed));
+
+struct brk_sigs {
+       u8 ea:1;
+       u8 b1:1;
+       u8 b2:1;
+       u8 b3:1;
+       u8 len:4;
+} __attribute__ ((packed));
+
+struct msc_msg_data {
+       struct address_field dlci;
+       u8 v24_sigs;
+} __attribute__ ((packed));
+
+struct pn_msg_data {
+       u8 dlci:6;
+       u8 res1:2;
+
+       u8 frame_type:4;
+       u8 credit_flow:4;
+
+       u8 prior:6;
+       u8 res2:2;
+
+       u8 ack_timer;
+       u8 frame_sizel;
+       u8 frame_sizeh;
+       u8 max_nbrof_retrans;
+       u8 credits;
+} __attribute__ ((packed));
+
+#else
+#error Only littel-endianess supported now!
+#endif
+
+#define TS0710_FRAME_SIZE(len)                                         \
+       ((len) > SHORT_PAYLOAD_SIZE ?                                   \
+        (len) + FLAG_SIZE + sizeof(struct long_frame) + FCS_SIZE :     \
+        (len) + FLAG_SIZE + sizeof(struct short_frame) + FCS_SIZE)
+
+#define TS0710_MCC_FRAME_SIZE(len) \
+       TS0710_FRAME_SIZE((len) + sizeof(struct mcc_short_frame))
+
+
+
+enum {
+       REJECTED = 0,
+       DISCONNECTED,
+       CONNECTING,
+       NEGOTIATING,
+       CONNECTED,
+       DISCONNECTING,
+       FLOW_STOPPED
+};
+
+enum ts0710_events {
+       CONNECT_IND,
+       CONNECT_CFM,
+       DISCONN_CFM
+};
+
+struct dlci_struct {
+       u8 state;
+       u8 flow_control;
+       u16 mtu;
+       int clients;
+       struct mutex lock;
+       wait_queue_head_t open_wait;
+       wait_queue_head_t close_wait;
+};
+
+struct chan_struct {
+       struct mutex    write_lock;
+       u8              *buf;
+};
+
+
+/* user space interfaces */
+struct ts0710_con {
+       u16 mtu;
+
+       struct dlci_struct      dlci[TS0710_MAX_CHN];
+       struct chan_struct      chan[NR_MUXS];
+};
diff --git a/drivers/misc/ts27010mux/ts27010_ldisc.c b/drivers/misc/ts27010mux/ts27010_ldisc.c
new file mode 100644 (file)
index 0000000..3a9be8d
--- /dev/null
@@ -0,0 +1,223 @@
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/tty.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/poll.h>
+
+#include "ts27010_mux.h"
+#include "ts27010_ringbuf.h"
+
+struct ts27010_ldisc_data {
+       struct ts27010_ringbuf          *rbuf;
+       struct work_struct              recv_work;
+       spinlock_t                      recv_lock;
+
+       struct mutex                    send_lock;
+};
+
+static void ts27010_ldisc_recv_worker(struct work_struct *work)
+{
+       struct ts27010_ldisc_data *ts =
+               container_of(work, struct ts27010_ldisc_data, recv_work);
+
+       /* TODO: should have a *mux to pass around */
+       ts27010_mux_recv(ts->rbuf);
+}
+
+
+int ts27010_ldisc_send(struct tty_struct *tty, u8 *data, int len)
+{
+       struct ts27010_ldisc_data *ts = 0;
+
+       if (tty->disc_data == NULL) {
+               pr_err("\n %s try to send mux command while     \
+                       ttyS is closed.\n", __func__);
+               return len;
+       } else
+               ts = tty->disc_data;
+
+       mutex_lock(&ts->send_lock);
+       if (tty->driver->ops->write_room(tty) < len)
+               pr_err("\n******** write overflow ********\n\n");
+       len = tty->driver->ops->write(tty, data, len);
+       mutex_unlock(&ts->send_lock);
+       return len;
+}
+
+/*
+ * Called when a tty is put into tx27010mux line discipline. Called in process
+ * context.
+ */
+static int ts27010_ldisc_open(struct tty_struct *tty)
+{
+       struct ts27010_ldisc_data *ts;
+       int err;
+
+       ts = kzalloc(sizeof(*ts), GFP_KERNEL);
+       if (ts == NULL) {
+               err = -ENOMEM;
+               goto err0;
+       }
+
+       ts->rbuf = ts27010_ringbuf_alloc(LDISC_BUFFER_SIZE);
+       if (ts->rbuf == NULL) {
+               err = ENOMEM;
+               goto err1;
+       }
+       INIT_WORK(&ts->recv_work, ts27010_ldisc_recv_worker);
+
+       mutex_init(&ts->send_lock);
+       ts->recv_lock = __SPIN_LOCK_UNLOCKED(ts->recv_lock);
+
+       tty->disc_data = ts;
+
+       /* TODO: goes away with clean tty interface */
+       ts27010mux_tty = tty;
+
+       return 0;
+
+err1:
+       kfree(ts);
+err0:
+       return err;
+}
+
+/*
+ * Called when the tty is put into another line discipline
+ * or it hangs up.  We have to wait for any cpu currently
+ * executing in any of the other ts27010_tty_* routines to
+ * finish before we can call tsmux27010_unregister_channel and free
+ * the tsmux27010 struct.  This routine must be called from
+ * process context, not interrupt or softirq context.
+ */
+static void ts27010_ldisc_close(struct tty_struct *tty)
+{
+       struct ts27010_ldisc_data *ts = tty->disc_data;
+
+       /* TODO: goes away with clean tty interface */
+       ts27010mux_tty = NULL;
+       /* TODO: find some way of dealing with ts_data freeing safely */
+       ts27010_ringbuf_free(ts->rbuf);
+       kfree(ts);
+}
+
+/*
+ * Called on tty hangup in process context.
+ *
+ * Wait for I/O to driver to complete and unregister ts27010mux channel.
+ * This is already done by the close routine, so just call that.
+ */
+static int ts27010_ldisc_hangup(struct tty_struct *tty)
+{
+       ts27010_ldisc_close(tty);
+       return 0;
+}
+/*
+ * Read does nothing - no data is ever available this way.
+ */
+static ssize_t ts27010_ldisc_read(struct tty_struct *tty, struct file *file,
+                                  unsigned char __user *buf, size_t count)
+{
+       return -EAGAIN;
+}
+
+/*
+ * Write on the tty does nothing.
+ */
+static ssize_t ts27010_ldisc_write(struct tty_struct *tty, struct file *file,
+                                  const unsigned char *buf, size_t count)
+{
+       return -EAGAIN;
+}
+
+/*
+ * Called in process context only. May be re-entered by multiple
+ * ioctl calling threads.
+ */
+static int ts27010_ldisc_ioctl(struct tty_struct *tty, struct file *file,
+                               unsigned int cmd, unsigned long arg)
+{
+       int err;
+
+       switch (cmd) {
+       default:
+               /* Try the various mode ioctls */
+               err = tty_mode_ioctl(tty, file, cmd, arg);
+       }
+
+       return err;
+}
+
+/* No kernel lock - fine */
+static unsigned int ts27010_ldisc_poll(struct tty_struct *tty,
+                                      struct file *file,
+                                      poll_table *wait)
+{
+       return 0;
+}
+
+/*
+ * This can now be called from hard interrupt level as well
+ * as soft interrupt level or mainline.  Because of this,
+ * we copy the data and schedule work so that we can assure
+ * the mux receive code is called in processes context.
+ */
+static void ts27010_ldisc_receive(struct tty_struct *tty,
+                                 const unsigned char *data,
+                                 char *cflags, int count)
+{
+       struct ts27010_ldisc_data *ts = tty->disc_data;
+       int n;
+       unsigned long flags;
+
+       WARN_ON(count == 0);
+
+       spin_lock_irqsave(&ts->recv_lock, flags);
+       n = ts27010_ringbuf_write(ts->rbuf, data, count);
+       spin_unlock_irqrestore(&ts->recv_lock, flags);
+
+       if (n < count)
+               pr_err("ts27010_ldisc: buffer overrun.  dropping data.\n");
+
+       schedule_work(&ts->recv_work);
+}
+
+static void ts27010_ldisc_wakeup(struct tty_struct *tty)
+{
+       pr_info(" Enter into ts27010mux_tty_wakeup\n");
+}
+
+
+
+static struct tty_ldisc_ops ts27010_ldisc = {
+       .owner  = THIS_MODULE,
+       .magic  = TTY_LDISC_MAGIC,
+       .name   = "n_ts27010",
+       .open   = ts27010_ldisc_open,
+       .close  = ts27010_ldisc_close,
+       .hangup = ts27010_ldisc_hangup,
+       .read   = ts27010_ldisc_read,
+       .write  = ts27010_ldisc_write,
+       .ioctl  = ts27010_ldisc_ioctl,
+       .poll   = ts27010_ldisc_poll,
+       .receive_buf = ts27010_ldisc_receive,
+       .write_wakeup = ts27010_ldisc_wakeup,
+};
+
+int ts27010_ldisc_init(void)
+{
+       int err;
+
+       err = tty_register_ldisc(N_GSM0710, &ts27010_ldisc);
+       if (err < 0)
+               pr_err("ts27010: unable to register line discipline\n");
+
+       return err;
+}
+
+void ts27010_ldisc_remove(void)
+{
+       tty_unregister_ldisc(N_GSM0710);
+}
diff --git a/drivers/misc/ts27010mux/ts27010_mux.c b/drivers/misc/ts27010mux/ts27010_mux.c
new file mode 100644 (file)
index 0000000..3c6f48c
--- /dev/null
@@ -0,0 +1,1389 @@
+/*
+ * File: ts27010_mux.c
+ *
+ * Portions derived from rfcomm.c, original header as follows:
+ *
+ * Copyright (C) 2000, 2001  Axis Communications AB
+ * Copyright (C) 2002, 2004, 2009 Motorola, Inc.7
+ *
+ * Author: Mats Friden <mats.friden@axis.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Exceptionally, Axis Communications AB grants discretionary and
+ * conditional permissions for additional use of the text contained
+ * in the company's release of the AXIS OpenBT Stack under the
+ * provisions set forth hereunder.
+ *
+ * Provided that, if you use the AXIS OpenBT Stack with other files,
+ * that do not implement functionality as specified in the Bluetooth
+ * System specification, to produce an executable, this does not by
+ * itself cause the resulting executable to be covered by the GNU
+ * General Public License. Your use of that executable is in no way
+ * restricted on account of using the AXIS OpenBT Stack code with it.
+ *
+ * This exception does not however invalidate any other reasons why
+ * the executable file might be covered by the provisions of the GNU
+ * General Public License.
+ *
+ * TODO:
+ *     * test command
+ *     * flow control
+ *     * support for non sholes
+ */
+
+#define DEBUG
+
+#include <linux/module.h>
+#include <linux/types.h>
+
+#include <linux/kernel.h>
+#include <linux/proc_fs.h>
+
+#include <linux/serial.h>
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/fcntl.h>
+#include <linux/string.h>
+#include <linux/major.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/uaccess.h>
+#include <linux/bitops.h>
+
+#include <asm/system.h>
+
+#include "ts27010_mux.h"
+#include "ts27010_ringbuf.h"
+#include "ts0710.h"
+
+#define TS0710MUX_MAJOR 245
+#define TS0710MUX_MINOR_START 0
+
+/* 2500ms, for BP UART hardware flow control AP UART  */
+#define TS0710MUX_TIME_OUT 250
+
+#define CRC_VALID 0xcf
+
+#define TS0710MUX_IO_DLCI_FC_ON 0x54F2
+#define TS0710MUX_IO_DLCI_FC_OFF 0x54F3
+#define TS0710MUX_IO_FC_ON 0x54F4
+#define TS0710MUX_IO_FC_OFF 0x54F5
+
+
+#define TS0710MUX_SEND_BUF_SIZE (TS0710_FRAME_SIZE(DEF_TS0710_MTU))
+
+#define TS0710MUX_SERIAL_BUF_SIZE 2048
+
+#define CMDTAG 0x55
+#define DATATAG 0xAA
+
+static const u8 tty2dlci[NR_MUXS] = {
+       1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
+};
+
+static const u8 iscmdtty[NR_MUXS] = {
+       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+};
+
+struct dlci_tty {
+       const u8 cmdtty;
+       const u8 datatty;
+};
+
+static const struct dlci_tty dlci2tty[] = {
+       {0, 0}, /* DLCI 0 */
+       {0, 0},                         /* DLCI 1 */
+       {1, 1},                         /* DLCI 2 */
+       {2, 2},                         /* DLCI 3 */
+       {3, 3},                         /* DLCI 4 */
+       {4, 4},                         /* DLCI 5 */
+       {5, 5},                         /* DLCI 6 */
+       {6, 6},                         /* DLCI 7 */
+       {7, 7},                         /* DLCI 8 */
+       {8, 8},                         /* DLCI 9 */
+       {9, 9},                         /* DLCI 10 */
+       {10, 10},                       /* DLCI 11 */
+       {11, 11},                       /* DLCI 12 */
+       {12, 12},                       /* DLCI 13 */
+       {13, 13},                       /* DLCI 14 */
+       {14, 14},                       /* DLCI 15 */
+       {15, 15},                       /* DLCI 16 */
+};
+
+enum recv_state {
+       RECV_STATE_IDLE,
+       RECV_STATE_ADDR,
+       RECV_STATE_CONTROL,
+       RECV_STATE_LEN,
+       RECV_STATE_LEN2,
+       RECV_STATE_DATA,
+       RECV_STATE_END,
+};
+
+/* Bit number in flags of mux_send_struct */
+struct tty_struct *ts27010mux_tty;
+
+static u8 crctable[256];
+static struct ts0710_con ts0710_connection;
+
+#define DBG_DATA       (1<<0)
+#define DBG_CMD                (1<<1)
+#define DBG_VERBOSE    (1<<2)
+
+#ifdef DEBUG
+
+static int debug;
+
+module_param_named(debug_level, debug, int, S_IRUGO | S_IWUSR);
+
+#define ts_debug(level, format, arg...)        do {    \
+       if (debug & level)                      \
+               pr_debug(format , ## arg);      \
+       } while (0)
+
+#define TS0710_DBG_BUF_SIZE    2048
+static unsigned char dbg_buf[TS0710_DBG_BUF_SIZE];
+
+static void ts27010_debughex(int level, const char *header,
+                            const u8 *buf, int len)
+{
+       int i;
+       int c;
+
+       if (len <= 0)
+               return;
+
+       c = 0;
+       for (i = 0; (i < len) && (c < (TS0710_DBG_BUF_SIZE - 3)); i++) {
+               sprintf(&dbg_buf[c], "%02x ", buf[i]);
+               c += 3;
+       }
+       dbg_buf[c] = 0;
+
+       ts_debug(level, "%s%s\n", header, dbg_buf);
+}
+
+static void ts27010_debugrbufhex(int level, const char *header,
+                                struct ts27010_ringbuf *rbuf,
+                                int idx, int len)
+{
+       int i;
+       int c;
+
+       if (len <= 0)
+               return;
+
+       c = 0;
+       for (i = 0; (i < len) && (c < (TS0710_DBG_BUF_SIZE - 3)); i++) {
+               sprintf(&dbg_buf[c], "%02x ",
+                       ts27010_ringbuf_peek(rbuf, idx+i));
+               c += 3;
+       }
+       dbg_buf[c] = 0;
+
+       ts_debug(level, "%s%s\n", header, dbg_buf);
+}
+
+static void ts27010_debugrbuf(int level, const char *header,
+                             struct ts27010_ringbuf *rbuf,
+                             int idx, int len)
+{
+       int i;
+
+       len = min(TS0710_DBG_BUF_SIZE - 1, len);
+
+       for (i = 0; i < len; i++)
+               dbg_buf[i] = ts27010_ringbuf_peek(rbuf, idx+i);
+
+       if (dbg_buf[i-1] == '\n')
+               dbg_buf[i-1] = '\0';
+       else
+               dbg_buf[i] = '\0';
+
+       ts_debug(level, "%s%s\n", header, dbg_buf);
+}
+
+static void ts27010_debugstr(int level, const char *header,
+                            const char *buf, int len)
+{
+       if (len <= 0)
+               return;
+
+       len = min(TS0710_DBG_BUF_SIZE - 1, len);
+
+       memcpy(dbg_buf, buf, len);
+
+       if (dbg_buf[len-1] == '\n')
+               dbg_buf[len-1] = '\0';
+       else
+               dbg_buf[len] = '\0';
+
+       ts_debug(level, "%s%s\n", header, dbg_buf);
+}
+
+#else /* DEBUG */
+
+static inline void ts0710_debughex(u8 *buf, int len) { }
+static inline void ts27010_debugrbuf(struct ts27010_ringbuf *rbuf,
+                                    int idx, int len) { }
+static void ts27010_debugstr(const char *buf, int len) { }
+#define ts_debug(level, format, arg...)        do {    \
+       } while (0)
+
+#endif /* DEBUG */
+
+
+static int ts0710_valid_dlci(u8 dlci)
+{
+       if ((dlci < TS0710_MAX_CHN) && (dlci > 0))
+               return 1;
+       else
+               return 0;
+}
+
+static void ts0710_crc_create_table(u8 table[])
+{
+       int i, j;
+
+       u8 data;
+       u8 code_word = 0xe0;
+       u8 sr = 0;
+
+       for (j = 0; j < 256; j++) {
+               data = (u8) j;
+
+               for (i = 0; i < 8; i++) {
+                       if ((data & 0x1) ^ (sr & 0x1)) {
+                               sr >>= 1;
+                               sr ^= code_word;
+                       } else {
+                               sr >>= 1;
+                       }
+
+                       data >>= 1;
+                       sr &= 0xff;
+               }
+
+               table[j] = sr;
+               sr = 0;
+       }
+}
+
+static u8 ts0710_crc_start(void)
+{
+       return 0xff;
+}
+
+static u8 ts0710_crc_calc(u8 fcs, u8 c)
+{
+       return crctable[fcs ^ c];
+}
+
+static u8 ts0710_crc_end(u8 fcs)
+{
+       return 0xff - fcs;
+}
+
+static int ts0710_crc_check(u8 fcs)
+{
+       return fcs == CRC_VALID;
+}
+
+static u8 ts0710_crc_data(u8 *data, int length)
+{
+       u8 fcs = ts0710_crc_start();
+
+       while (length--)
+               fcs = ts0710_crc_calc(fcs, *data++);
+
+       return ts0710_crc_end(fcs);
+}
+
+static void ts0710_pkt_set_header(u8 *data, int len, int addr_ea,
+                                        int addr_cr, int addr_dlci,
+                                        int control)
+{
+       struct short_frame *pkt = (struct short_frame *)(data + 1);
+
+       pkt->h.addr.ea = addr_ea;
+       pkt->h.addr.cr = addr_cr;
+       pkt->h.addr.d = addr_dlci & 0x1;
+       pkt->h.addr.server_chn = addr_dlci >> 1;
+       pkt->h.control = control;
+
+       if ((len) > SHORT_PAYLOAD_SIZE) {
+               struct long_frame *long_pkt = (struct long_frame *)(data + 1);
+               long_pkt->h.length.ea = 0;
+               long_pkt->h.length.l_len = len & 0x7F;
+               long_pkt->h.length.h_len = (len >> 7) & 0xFF;
+       } else {
+               pkt->h.length.ea = 1;
+               pkt->h.length.len = len;
+       }
+}
+
+static void *ts0710_pkt_data(u8 *data)
+{
+       struct short_frame *pkt = (struct short_frame *)(data + 1);
+       if (pkt->h.length.ea == 1)
+               return pkt->data;
+       else
+               return pkt->data+1;
+}
+
+static int ts0710_pkt_send(struct ts0710_con *ts0710, u8 *data)
+{
+       struct short_frame *pkt = (struct short_frame *)(data + 1);
+       u8 *d;
+       int len;
+       int header_len;
+       int res;
+
+       if (pkt->h.length.ea == 1) {
+               len = pkt->h.length.len;
+               d = pkt->data;
+               header_len = sizeof(*pkt);
+       } else {
+               struct long_frame *long_pkt = (struct long_frame *)(data + 1);
+               len = (long_pkt->h.length.h_len << 7) |
+                       long_pkt->h.length.l_len;
+               d = pkt->data+1;
+               header_len = sizeof(*long_pkt);
+       }
+
+       data[0] = TS0710_BASIC_FLAG;
+       d[len] = ts0710_crc_data(data+1, header_len);
+       d[len+1] = TS0710_BASIC_FLAG;
+
+       ts27010_debughex(DBG_VERBOSE, "ts27010: > ",
+                        data, TS0710_FRAME_SIZE(len));
+
+       if (!ts27010mux_tty) {
+               pr_warning("ts27010: ldisc closed.  discarding %d bytes\n",
+                          TS0710_FRAME_SIZE(len));
+               return TS0710_FRAME_SIZE(len);
+       }
+
+       res = ts27010_ldisc_send(ts27010mux_tty, data,
+                                TS0710_FRAME_SIZE(len));
+
+       if (res < 0) {
+               pr_err("ts27010: pkt write error %d\n", res);
+               return res;
+       } else if (res != TS0710_FRAME_SIZE(len)) {
+               pr_err("ts27010: short write %d < %d\n", res,
+                      TS0710_FRAME_SIZE(len));
+               return -EIO;
+       }
+
+       return res;
+
+}
+
+/* TODO: look at this */
+static void ts0710_reset_dlci(u8 j)
+{
+       if (j >= TS0710_MAX_CHN)
+               return;
+
+       ts0710_connection.dlci[j].state = DISCONNECTED;
+       ts0710_connection.dlci[j].flow_control = 0;
+       ts0710_connection.dlci[j].mtu = DEF_TS0710_MTU;
+       init_waitqueue_head(&ts0710_connection.dlci[j].open_wait);
+       init_waitqueue_head(&ts0710_connection.dlci[j].close_wait);
+}
+
+/* TODO: look at this */
+static void ts0710_reset_con(void)
+{
+       int j;
+
+       ts0710_connection.mtu = DEF_TS0710_MTU + TS0710_MAX_HDR_SIZE;
+
+       for (j = 0; j < TS0710_MAX_CHN; j++)
+               ts0710_reset_dlci(j);
+}
+
+static void ts0710_init(void)
+{
+       ts0710_crc_create_table(crctable);
+       ts0710_reset_con();
+}
+
+/* TODO: look at this */
+static void ts0710_upon_disconnect(void)
+{
+       struct ts0710_con *ts0710 = &ts0710_connection;
+       int j;
+
+       for (j = 0; j < TS0710_MAX_CHN; j++) {
+               ts0710->dlci[j].state = DISCONNECTED;
+               wake_up_interruptible(&ts0710->dlci[j].open_wait);
+               wake_up_interruptible(&ts0710->dlci[j].close_wait);
+       }
+       ts0710_reset_con();
+}
+
+static int ts27010_send_cmd(struct ts0710_con *ts0710, u8 dlci, u8 cmd)
+{
+       u8 frame[TS0710_FRAME_SIZE(0)];
+       ts0710_pkt_set_header(frame, 0, 1, MCC_RSP, dlci, SET_PF(cmd));
+       return ts0710_pkt_send(ts0710, frame);
+}
+
+
+static int ts27010_send_ua(struct ts0710_con *ts0710, u8 dlci)
+{
+       ts_debug(DBG_CMD, "ts27010: sending UA packet to DLCI %d\n", dlci);
+       return ts27010_send_cmd(ts0710, dlci, UA);
+}
+
+static int ts27010_send_dm(struct ts0710_con *ts0710, u8 dlci)
+{
+       ts_debug(DBG_CMD, "ts27010: sending DM packet to DLCI %d\n", dlci);
+       return ts27010_send_cmd(ts0710, dlci, DM);
+}
+
+static int ts27010_send_sabm(struct ts0710_con *ts0710, u8 dlci)
+{
+       ts_debug(DBG_CMD, "ts27010: sending SABM packet to DLCI %d\n", dlci);
+       return ts27010_send_cmd(ts0710, dlci, SABM);
+}
+
+static int ts27010_send_disc(struct ts0710_con *ts0710, u8 dlci)
+{
+       ts_debug(DBG_CMD, "ts27010: sending DISC packet to DLCI %d\n", dlci);
+       return ts27010_send_cmd(ts0710, dlci, DISC);
+}
+
+static int ts27010_send_uih(struct ts0710_con *ts0710, u8 dlci,
+                           u8 *frame, u8 tag, const u8 *data, int len)
+{
+       ts_debug(DBG_CMD,
+                "ts27010: sending %d length UIH packet to DLCI %d\n",
+                len, dlci);
+       ts0710_pkt_set_header(frame, len+1, 1, MCC_CMD, dlci, CLR_PF(UIH));
+       *(u8 *)ts0710_pkt_data(frame) = tag;
+       memcpy(ts0710_pkt_data(frame)+1, data, len);
+       return ts0710_pkt_send(ts0710, frame);
+}
+
+static void ts27010_mcc_set_header(u8 *frame, int len, int cr, int cmd)
+{
+       struct mcc_short_frame *mcc_pkt;
+       ts0710_pkt_set_header(frame, sizeof(struct mcc_short_frame) + len,
+                             1, MCC_CMD, CTRL_CHAN, CLR_PF(UIH));
+
+       mcc_pkt = ts0710_pkt_data(frame);
+       mcc_pkt->h.type.ea = EA;
+       mcc_pkt->h.type.cr = cr;
+       mcc_pkt->h.type.type = cmd;
+       mcc_pkt->h.length.ea = EA;
+       mcc_pkt->h.length.len = len;
+}
+
+static void *ts27010_mcc_data(u8 *frame)
+{
+       return ((struct mcc_short_frame *)ts0710_pkt_data(frame))->value;
+}
+
+static int ts27010_send_pn(struct ts0710_con *ts0710, u8 prior, int frame_size,
+                          u8 credit_flow, u8 credits, u8 dlci, u8 cr)
+{
+       u8 frame[TS0710_MCC_FRAME_SIZE(sizeof(struct pn_msg_data))];
+       struct pn_msg_data *pn;
+
+       ts_debug(DBG_CMD, "ts27010: sending PN MCC\n");
+       ts27010_mcc_set_header(frame, sizeof(struct pn_msg_data), cr, PN);
+
+       pn = ts27010_mcc_data(frame);
+       pn->res1 = 0;
+       pn->res2 = 0;
+       pn->dlci = dlci;
+       pn->frame_type = 0;
+       pn->credit_flow = credit_flow;
+       pn->prior = prior;
+       pn->ack_timer = 0;
+       pn->frame_sizel = frame_size & 0xff;
+       pn->frame_sizeh = frame_size >> 8;
+       pn->credits = credits;
+       pn->max_nbrof_retrans = 0;
+
+       return ts0710_pkt_send(ts0710, frame);
+}
+
+static int ts27010_send_nsc(struct ts0710_con *ts0710, u8 type, int cr)
+{
+       u8 frame[TS0710_MCC_FRAME_SIZE(sizeof(struct mcc_type))];
+       struct mcc_type *t;
+
+       ts_debug(DBG_CMD, "ts27010: sending NSC MCC\n");
+       ts27010_mcc_set_header(frame, sizeof(struct mcc_type), cr, NSC);
+
+       t = ts27010_mcc_data(frame);
+       t->ea = 1;
+       t->cr = mcc_is_cmd(type);
+       t->type = type >> 2;
+
+       return ts0710_pkt_send(ts0710, frame);
+}
+
+static int ts27010_send_msc(struct ts0710_con *ts0710,
+                           u8 value, int cr, u8 dlci)
+{
+       u8 frame[TS0710_MCC_FRAME_SIZE(sizeof(struct msc_msg_data))];
+       struct msc_msg_data *msc;
+
+       ts_debug(DBG_CMD, "ts27010: sending MSC MCC\n");
+       ts27010_mcc_set_header(frame, sizeof(struct msc_msg_data), cr, MSC);
+
+       msc = ts27010_mcc_data(frame);
+
+       msc->dlci.ea = 1;
+       msc->dlci.cr = 1;
+       msc->dlci.d = dlci & 1;
+       msc->dlci.server_chn = (dlci >> 1) & 0x1f;
+
+       msc->v24_sigs = value;
+
+       return ts0710_pkt_send(ts0710, frame);
+}
+
+static void ts27010_handle_msc(struct ts0710_con *ts0710, u8 type,
+                              struct ts27010_ringbuf *rbuf,
+                              int data_idx, int len)
+{
+       u8 dlci;
+       u8 v24_sigs;
+
+       dlci = ts27010_ringbuf_peek(rbuf, data_idx) >> 2;
+       v24_sigs = ts27010_ringbuf_peek(rbuf, data_idx + 1);
+
+       if ((ts0710->dlci[dlci].state != CONNECTED)
+           && (ts0710->dlci[dlci].state != FLOW_STOPPED)) {
+               ts27010_send_dm(ts0710, dlci);
+               return;
+       }
+
+       if (mcc_is_cmd(type)) {
+               ts_debug(DBG_VERBOSE,
+                        "ts27010: received modem status command\n");
+               if (v24_sigs & FC) {
+                       if (ts0710->dlci[dlci].state == CONNECTED) {
+                               ts_debug(DBG_CMD,
+                                        "ts27010: flow off on dlci%d\n", dlci);
+                               ts0710->dlci[dlci].state = FLOW_STOPPED;
+                       }
+               } else {
+                       if (ts0710->dlci[dlci].state == FLOW_STOPPED) {
+                               ts0710->dlci[dlci].state = CONNECTED;
+                               ts_debug(DBG_CMD,
+                                        "ts27010: flow on on dlci%d\n", dlci);
+                               /* TODO: flow control not supported */
+                       }
+               }
+               ts27010_send_msc(ts0710, v24_sigs, MCC_RSP, dlci);
+       } else {
+               ts_debug(DBG_VERBOSE,
+                        "ts27010: received modem status response\n");
+
+               if (v24_sigs & FC)
+                       ts_debug(DBG_CMD, "ts27010: flow stop accepted\n");
+       }
+}
+
+static void ts27010_handle_pn(struct ts0710_con *ts0710, u8 type,
+                             struct ts27010_ringbuf *rbuf,
+                             int data_idx, int len)
+{
+       u8 dlci;
+       u16 frame_size;
+       struct pn_msg_data pn;
+       int i;
+
+       if (len != 8) {
+               pr_err("ts27010: reveived pn on length:%d != 8\n", len);
+               return;
+       }
+
+       for (i = 0; i < 8; i++)
+               ((u8 *)&pn)[i] = ts27010_ringbuf_peek(rbuf, data_idx + i);
+
+       dlci = pn.dlci;
+       frame_size = pn.frame_sizel | (pn.frame_sizeh << 8);
+
+       if (mcc_is_cmd(type)) {
+               ts_debug(DBG_CMD,
+                        "ts27010: received PN command with frame size %d\n",
+                       frame_size);
+
+               /* TODO: this looks like it will only ever shrink mtu */
+               frame_size = min(frame_size, ts0710->dlci[dlci].mtu);
+               ts27010_send_pn(ts0710, pn.prior, frame_size,
+                               0, 0, dlci, MCC_RSP);
+               ts0710->dlci[dlci].mtu = frame_size;
+
+               ts_debug(DBG_VERBOSE,
+                        "ts27010: mtu set to %d on dlci%d\n",
+                        frame_size, dlci);
+       } else {
+               ts_debug(DBG_CMD,
+                        "ts27010: received PN response with frame size %d\n",
+                       frame_size);
+
+               frame_size = min(frame_size, ts0710->dlci[dlci].mtu);
+               ts0710->dlci[dlci].mtu = frame_size;
+
+               ts_debug(DBG_VERBOSE, "ts27010: mtu set to %d on dlci%d\n",
+                        frame_size, dlci);
+
+               if (ts0710->dlci[dlci].state == NEGOTIATING) {
+                       ts0710->dlci[dlci].state = CONNECTING;
+                       wake_up_interruptible(&ts0710->dlci[dlci].open_wait);
+               }
+       }
+}
+
+
+
+static void ts27010_handle_mcc(struct ts0710_con *ts0710, u8 control,
+                              struct ts27010_ringbuf *rbuf,
+                              int data_idx, int len)
+{
+       u8 type;
+       u8 mcc_len;
+
+       type = ts27010_ringbuf_peek(rbuf, data_idx++);
+       len--;
+       mcc_len = ts27010_ringbuf_peek(rbuf, data_idx++);
+       len--;
+
+       if (mcc_len != len) {
+               pr_warning("ts27010: handle_mcc: mcc_len:%d != len:%d\n",
+                          mcc_len, len);
+       }
+
+       switch (type >> 2) {
+       case TEST:
+               pr_warning("ts27010: test command unimplemented\n");
+               break;
+
+       case FCON:
+               ts_debug(DBG_CMD,
+                        "ts27010: received all channels flow control on\n");
+               pr_warning("ts27010: flow control unimplemented\n");
+               break;
+
+       case FCOFF:
+               ts_debug(DBG_CMD,
+                        "ts27010: received all channels flow control off\n");
+               pr_warning("ts27010: flow control unimplemented\n");
+               break;
+
+       case MSC:
+               ts27010_handle_msc(ts0710, type, rbuf, data_idx, len);
+               break;
+
+       case PN:
+               ts27010_handle_pn(ts0710, type, rbuf, data_idx, len);
+               break;
+
+       case NSC:
+               pr_warning("ts27010: received non supported cmd response\n");
+               break;
+
+       default:
+               pr_warning("ts27010: received a non supported command\n");
+               ts27010_send_nsc(ts0710, type, MCC_RSP);
+               break;
+       }
+}
+
+static void ts27010_handle_sabm(struct ts0710_con *ts0710, u8 control, int dlci)
+{
+       ts_debug(DBG_CMD, "ts27010: SABM received on dlci %d\n", dlci);
+
+       if (ts0710_valid_dlci(dlci)) {
+               ts27010_send_ua(ts0710, dlci);
+
+               ts0710->dlci[dlci].state = CONNECTED;
+               wake_up_interruptible(&ts0710->dlci[dlci].open_wait);
+       } else {
+               pr_warning("ts27010: invalid dlci %d. sending DM\n", dlci);
+               ts27010_send_dm(ts0710, dlci);
+       }
+}
+
+static void ts27010_handle_ua(struct ts0710_con *ts0710, u8 control, int dlci)
+{
+       ts_debug(DBG_CMD, "ts27010: UA packet received on dlci %d\n", dlci);
+
+       if (ts0710_valid_dlci(dlci)) {
+               if (ts0710->dlci[dlci].state == CONNECTING) {
+                       ts0710->dlci[dlci].state = CONNECTED;
+                       wake_up_interruptible(&ts0710->dlci[dlci].
+                                             open_wait);
+               } else if (ts0710->dlci[dlci].state == DISCONNECTING) {
+                       if (dlci == 0) {
+                               ts0710_upon_disconnect();
+                       } else {
+                               ts0710->dlci[dlci].state = DISCONNECTED;
+                               wake_up_interruptible(&ts0710->dlci[dlci].
+                                                     open_wait);
+                               wake_up_interruptible(&ts0710->dlci[dlci].
+                                                     close_wait);
+                               ts0710_reset_dlci(dlci);
+                       }
+               } else {
+                       pr_warning("ts27010: invalid UA packet\n");
+               }
+       } else {
+               pr_warning("ts27010: invalid dlci %d\n", dlci);
+       }
+}
+
+static void ts27010_handle_dm(struct ts0710_con *ts0710, u8 control, int dlci)
+{
+       int oldstate;
+
+       ts_debug(DBG_CMD, "ts27010: DM packet received on dlci %d\n", dlci);
+
+       if (dlci == 0) {
+               oldstate = ts0710->dlci[0].state;
+               ts0710_upon_disconnect();
+               if (oldstate == CONNECTING)
+                       ts0710->dlci[0].state = REJECTED;
+       } else if (ts0710_valid_dlci(dlci)) {
+               if (ts0710->dlci[dlci].state == CONNECTING)
+                       ts0710->dlci[dlci].state = REJECTED;
+               else
+                       ts0710->dlci[dlci].state = DISCONNECTED;
+
+               wake_up_interruptible(&ts0710->dlci[dlci].open_wait);
+               wake_up_interruptible(&ts0710->dlci[dlci].close_wait);
+               ts0710_reset_dlci(dlci);
+       } else {
+               pr_warning("ts27010: invalid dlci %d\n", dlci);
+       }
+}
+
+static void ts27010_handle_disc(struct ts0710_con *ts0710, u8 control, int dlci)
+{
+       ts_debug(DBG_CMD, "ts27010: DISC packet received on dlci %d\n", dlci);
+
+       if (!dlci) {
+               ts27010_send_ua(ts0710, dlci);
+               ts_debug(DBG_CMD, "ts27010: sending back UA\n");
+
+               ts0710_upon_disconnect();
+       } else if (ts0710_valid_dlci(dlci)) {
+               ts27010_send_ua(ts0710, dlci);
+               ts_debug(DBG_CMD, "ts27010: sending back UA\n");
+
+               ts0710->dlci[dlci].state = DISCONNECTED;
+               wake_up_interruptible(&ts0710->dlci[dlci].open_wait);
+               wake_up_interruptible(&ts0710->dlci[dlci].close_wait);
+               ts0710_reset_dlci(dlci);
+       } else {
+               pr_warning("ts27010: invalid dlci %d\n", dlci);
+       }
+}
+
+static void ts27010_handle_uih(struct ts0710_con *ts0710, u8 control, int dlci,
+                              struct ts27010_ringbuf *rbuf,
+                              int data_idx, int len)
+{
+       int tag;
+       int tty_idx;
+
+       if ((dlci >= TS0710_MAX_CHN)) {
+               pr_warning("invalid dlci %d\n", dlci);
+               ts27010_send_dm(ts0710, dlci);
+               return;
+       }
+
+       if (GET_PF(control)) {
+               pr_warning("ts27010: uih packet with P/F set, discarding.\n");
+               return;
+       }
+
+       if ((ts0710->dlci[dlci].state != CONNECTED)
+           && (ts0710->dlci[dlci].state != FLOW_STOPPED)) {
+               pr_warning("ts27010: uih: dlci %d not connected, discarding.\n",
+                       dlci);
+               ts27010_send_dm(ts0710, dlci);
+               return;
+       }
+
+       if (dlci == 0) {
+               pr_info("ts27010: mcc on channel 0\n");
+               ts27010_handle_mcc(ts0710, control, rbuf, data_idx, len);
+               return;
+       }
+
+       ts_debug(DBG_CMD, "ts27010: uih on channel %d\n", dlci);
+
+       if (len > ts0710->dlci[dlci].mtu) {
+               pr_warning("ts27010: dlci%d: uih_len:%d "
+                          "is bigger than mtu:%d, discarding.\n",
+                           dlci, len, ts0710->dlci[dlci].mtu);
+               return;
+       }
+
+       tag = ts27010_ringbuf_peek(rbuf, data_idx);
+       len--;
+       data_idx++;
+
+       if (len == 0)
+               return;
+
+       switch (tag) {
+       case CMDTAG:
+               tty_idx = dlci2tty[dlci].cmdtty;
+               ts_debug(DBG_VERBOSE,
+                        "ts27010: CMDTAG on DLCI %d, /dev/mux%d\n",
+                        dlci, tty_idx);
+               ts27010_debugrbuf(DBG_DATA, "ts27010: <C ",
+                                 rbuf, data_idx, len);
+               if (!(iscmdtty[tty_idx])) {
+                       pr_warning("ts27010: wrong CMDTAG on DLCI %d,"
+                                  " /dev/mux%d\n", dlci, tty_idx);
+               }
+               break;
+
+       case DATATAG:
+       default:
+               tty_idx = dlci2tty[dlci].datatty;
+               ts_debug(DBG_VERBOSE,
+                        "ts27010: NON-CMDTAG on DLCI %d, /dev/mux%d\n",
+                        dlci, tty_idx);
+               ts27010_debugrbufhex(DBG_DATA, "ts27010: <D ",
+                                    rbuf, data_idx, len);
+               if (iscmdtty[tty_idx]) {
+                       pr_warning("ts27010: wrong NON-CMDTAG on DLCI %d,"
+                                  " /dev/mux%d\n", dlci, tty_idx);
+               }
+               break;
+       }
+
+       ts27010_tty_send_rbuf(tty_idx, rbuf, data_idx, len);
+}
+
+
+static void ts27010_handle_frame(struct ts27010_ringbuf *rbuf, u8 addr,
+                                u8 control, int data_idx, int len)
+{
+       struct ts0710_con *ts0710 = &ts0710_connection;
+       int dlci;
+
+       dlci = ts0710_dlci(addr);
+       switch (CLR_PF(control)) {
+       case SABM:
+               ts27010_handle_sabm(ts0710, control, dlci);
+               break;
+
+       case UA:
+               ts27010_handle_ua(ts0710, control, dlci);
+               break;
+
+       case DM:
+               ts27010_handle_dm(ts0710, control, dlci);
+               break;
+
+       case DISC:
+               ts27010_handle_disc(ts0710, control, dlci);
+               break;
+
+       case UIH:
+               ts27010_handle_uih(ts0710, control, dlci, rbuf, data_idx, len);
+               break;
+
+       default:
+               ts_debug(DBG_VERBOSE, "ts27010: illegal packet\n");
+               break;
+       }
+}
+
+static int ts0710_close_channel(u8 dlci)
+{
+       struct ts0710_con *ts0710 = &ts0710_connection;
+       struct dlci_struct *d = &ts0710->dlci[dlci];
+       int try;
+       int retval;
+
+       ts_debug(DBG_CMD, "ts27010: closing dlci %d\n", dlci);
+
+       mutex_lock(&d->lock);
+
+       if (d->clients > 1) {
+               d->clients--;
+               mutex_unlock(&d->lock);
+               return 0;
+       }
+
+       if (d->state == DISCONNECTED ||
+           d->state == REJECTED ||
+           d->state == DISCONNECTING) {
+               d->clients--;
+               mutex_unlock(&d->lock);
+               return 0;
+       }
+
+       d->state = DISCONNECTING;
+       try = 3;
+       while (try--) {
+               ts27010_send_disc(ts0710, dlci);
+               mutex_unlock(&d->lock);
+               retval = wait_event_interruptible_timeout(d->close_wait,
+                                                         d->state !=
+                                                         DISCONNECTING,
+                                                         TS0710MUX_TIME_OUT);
+               mutex_lock(&d->lock);
+
+               if (retval == 0)
+                       continue;
+
+               if (retval == -ERESTARTSYS) {
+                       retval = -EAGAIN;
+                       break;
+               }
+
+               if (d->state != DISCONNECTED) {
+                       retval = -EIO;
+                       break;
+               }
+
+               retval = 0;
+               break;
+       }
+
+       if (try < 0)
+               retval = -EIO;
+
+       /* TODO: unclear if this is the right thing to do */
+       if (d->state != DISCONNECTED) {
+               if (dlci == 0) {
+                       ts0710_upon_disconnect();
+               } else {
+                       d->state = DISCONNECTED;
+                       wake_up_interruptible(&d->close_wait);
+                       ts0710_reset_dlci(dlci);
+               }
+       }
+
+       d->clients--;
+
+       mutex_unlock(&d->lock);
+
+       return retval;
+}
+
+/* call with dlci locked held */
+int ts0710_wait_for_open(struct ts0710_con *ts0710, int dlci)
+{
+       int try = 8;
+       int ret;
+       struct dlci_struct *d = &ts0710->dlci[dlci];
+
+       while (try--) {
+               mutex_unlock(&d->lock);
+               ret = wait_event_interruptible_timeout(d->open_wait,
+                                                      d->state != CONNECTING,
+                                                      TS0710MUX_TIME_OUT);
+               /*
+                * It is possible that d->state could have changed back to
+                * to connecting between being woken up and aquiring the lock.
+                * The side effect is that this open() will return turn -ENODEV.
+                */
+
+               mutex_lock(&d->lock);
+               if (ret == 0)
+                       continue;
+
+               if (ret == -ERESTARTSYS)
+                       return -EAGAIN;
+
+               if (d->state == REJECTED)
+                       return -EREJECTED;
+
+               if (d->state != CONNECTED)
+                       return -ENODEV;
+
+               return 0;
+       }
+
+       return -ENODEV;
+}
+
+
+int ts0710_open_channel(u8 dlci)
+{
+       struct ts0710_con *ts0710 = &ts0710_connection;
+       struct dlci_struct *d = &ts0710->dlci[dlci];
+       int try;
+       int retval;
+
+       mutex_lock(&d->lock);
+       if (d->clients > 0) {
+               if (d->state == CONNECTED) {
+                       d->clients++;
+                       mutex_unlock(&d->lock);
+                       return 0;
+               }
+
+               if (d->state != CONNECTING) {
+                       mutex_unlock(&d->lock);
+                       return -EREJECTED;
+               }
+               retval = ts0710_wait_for_open(ts0710, dlci);
+               d->clients++;
+               mutex_unlock(&d->lock);
+               return retval;
+       }
+
+       if (d->state != DISCONNECTED && d->state != REJECTED) {
+               pr_err("ts27010: DLCI%d: state invalid on open\n", dlci);
+               mutex_unlock(&d->lock);
+               return -ENODEV;
+       }
+
+       /* we are the first to try to open the dlci */
+       d->state = CONNECTING;
+       try = dlci == 0 ? 10 : 3;
+       while (try--) {
+               ts27010_send_sabm(ts0710, dlci);
+
+               mutex_unlock(&d->lock);
+               retval = wait_event_interruptible_timeout(d->open_wait,
+                                                         d->state !=
+                                                         CONNECTING,
+                                                         TS0710MUX_TIME_OUT);
+               mutex_lock(&d->lock);
+
+               if (retval == 0)
+                       continue;
+
+               if (retval == -ERESTARTSYS) {
+                       retval = -EAGAIN;
+                       break;
+               }
+
+               if (d->state == REJECTED) {
+                       retval = -EREJECTED;
+                       break;
+               }
+
+               if (d->state != CONNECTED) {
+                       retval = -ENODEV;
+                       break;
+               }
+
+               d->clients++;
+
+               retval = 0;
+               break;
+       }
+
+       if (try < 0)
+               retval = -ENODEV;
+
+       if (d->state == CONNECTING)
+               d->state = DISCONNECTED;
+
+       /* other ttys might be waiting on this dlci */
+       wake_up_interruptible(&d->open_wait);
+
+       mutex_unlock(&d->lock);
+       return retval;
+}
+
+int ts27010_mux_active(void)
+{
+       return ts27010mux_tty != NULL;
+}
+
+
+int ts27010_mux_line_open(int line)
+{
+       int dlci;
+
+       dlci = tty2dlci[line];
+
+       /* TODO: need to make sure channel 0 is open */
+
+       return ts0710_open_channel(dlci);
+}
+
+void ts27010_mux_line_close(int line)
+{
+       int dlci;
+
+       dlci = tty2dlci[line];
+       ts0710_close_channel(dlci);
+
+}
+
+int ts27010_mux_line_write(int line, const unsigned char *buf, int count)
+{
+       /* TODO: this should come from somewhere good */
+       struct ts0710_con *ts0710 = &ts0710_connection;
+       int dlci;
+       int err;
+       int c;
+       u8 tag;
+
+       dlci = tty2dlci[line];
+       if (ts0710->dlci[0].state == FLOW_STOPPED) {
+               /* TODO: this should block */
+               pr_info("Flow stopped on all channels, "
+                       "returning zero /dev/mux%d\n",
+                    line);
+               return 0;
+       } else if (ts0710->dlci[dlci].state == FLOW_STOPPED) {
+               /* TODO: this should block */
+               pr_info("Flow stopped, returning zero /dev/mux%d\n", line);
+               return 0;
+       } else if (ts0710->dlci[dlci].state == CONNECTED) {
+               mutex_lock(&ts0710->chan[line].write_lock);
+
+               c = min(count, (ts0710->dlci[dlci].mtu - 1));
+               if (c <= 0) {
+                       err = 0;
+                       goto err;
+               }
+
+               ts_debug(DBG_VERBOSE, "ts27010: preparing to send %d bytes "
+                        "from /dev/mux%d\n", c, line);
+
+               if (iscmdtty[line]) {
+                       ts27010_debugstr(DBG_DATA, "ts27010: >C ", buf, c);
+                       ts_debug(DBG_VERBOSE, "CMDTAG\n");
+                       tag = CMDTAG;
+               } else {
+                       ts27010_debughex(DBG_DATA, "ts27010: >D ", buf, c);
+                       ts_debug(DBG_VERBOSE, "DATATAG\n");
+                       tag = DATATAG;
+               }
+
+               ts27010_send_uih(ts0710, dlci, ts0710->chan[line].buf,
+                                tag, buf, c);
+
+               mutex_unlock(&ts0710->chan[line].write_lock);
+
+               /*
+                * TODO: should check write notify flag and call back
+                * into the tty layer
+                */
+
+               return c;
+       } else {
+               pr_warning("ts27010: write on DLCI %d while not connected\n",
+                          dlci);
+               return -EDISCONNECTED;
+       }
+
+err:
+       mutex_unlock(&ts0710->chan[line].write_lock);
+       return err;
+}
+
+int ts27010_mux_line_chars_in_buffer(int line)
+{
+       struct ts0710_con *ts0710 = &ts0710_connection;
+
+       if (mutex_is_locked(&ts0710->chan[line].write_lock))
+               return TS0710MUX_SERIAL_BUF_SIZE;
+       else
+               return 0;
+}
+
+int ts27010_mux_line_write_room(int line)
+{
+       struct ts0710_con *ts0710 = &ts0710_connection;
+
+       if (mutex_is_locked(&ts0710->chan[line].write_lock))
+               return 0;
+       else
+               return TS0710MUX_SERIAL_BUF_SIZE;
+}
+
+
+void ts27010_mux_recv(struct ts27010_ringbuf *rbuf)
+{
+       int count;
+       int i;
+       u8 c;
+       int state = RECV_STATE_IDLE;
+       int consume_idx = -1;
+       int data_idx = 0;
+       u8 addr = 0;
+       u8 control = 0;
+       int len = 0;
+       u8 fcs = 0;
+
+       count = ts27010_ringbuf_level(rbuf);
+
+       for (i = 0; i < count; i++) {
+               c = ts27010_ringbuf_peek(rbuf, i);
+
+               switch (state) {
+               case RECV_STATE_IDLE:
+                       if (c == TS0710_BASIC_FLAG) {
+                               fcs = ts0710_crc_start();
+                               state = RECV_STATE_ADDR;
+                       } else {
+                               consume_idx = i;
+                       }
+                       break;
+
+               case RECV_STATE_ADDR:
+                       if (c != TS0710_BASIC_FLAG) {
+                               fcs = ts0710_crc_calc(fcs, c);
+                               addr = c;
+                               state = RECV_STATE_CONTROL;
+                       } else {
+                               pr_warning(
+                                       "ts27010: RX wrong data. Drop msg.\n");
+                               consume_idx = i;
+                       }
+                       break;
+
+               case RECV_STATE_CONTROL:
+                       fcs = ts0710_crc_calc(fcs, c);
+                       control = c;
+                       state = RECV_STATE_LEN;
+                       break;
+
+               case RECV_STATE_LEN:
+                       fcs = ts0710_crc_calc(fcs, c);
+                       len = c>>1;
+                       if (c & 0x1) {
+                               data_idx = i+1;
+                               state = RECV_STATE_DATA;
+                       } else {
+                               state = RECV_STATE_LEN2;
+                       }
+                       break;
+
+               case RECV_STATE_LEN2:
+                       fcs = ts0710_crc_calc(fcs, c);
+                       len |= c<<7;
+                       data_idx = i+1;
+                       if (len + data_idx >= LDISC_BUFFER_SIZE) {
+                               pr_warning(
+                                       "ts27010: wrong length, Drop msg.\n");
+                               state = RECV_STATE_IDLE;
+                               consume_idx = i;
+                               break;
+                       }
+                       state = RECV_STATE_DATA;
+                       break;
+
+               case RECV_STATE_DATA:
+                       if (i == data_idx+len) {
+                               /* FCS byte */
+                               fcs = ts0710_crc_calc(fcs, c);
+                               state = RECV_STATE_END;
+                       }
+                       break;
+
+               case RECV_STATE_END:
+                       if (c == TS0710_BASIC_FLAG && ts0710_crc_check(fcs)) {
+                               ts27010_handle_frame(rbuf, addr, control,
+                                                     data_idx, len);
+                       } else {
+                               pr_warning("ts27010: lost synchronization\n");
+                       }
+                       consume_idx = i;
+                       state = RECV_STATE_IDLE;
+                       break;
+               }
+       }
+
+       ts27010_ringbuf_consume(rbuf, consume_idx+1);
+
+}
+
+static int __init mux_init(void)
+{
+       int err;
+       int j;
+
+       ts0710_init();
+
+       for (j = 0; j < TS0710_MAX_CHN; j++)
+               mutex_init(&ts0710_connection.dlci[j].lock);
+
+       for (j = 0; j < NR_MUXS; j++) {
+               ts0710_connection.chan[j].buf =
+                       kmalloc(TS0710MUX_SEND_BUF_SIZE, GFP_KERNEL);
+               if (ts0710_connection.chan[j].buf == NULL) {
+                       err = -ENOMEM;
+                       goto err0;
+               }
+
+               mutex_init(&ts0710_connection.chan[j].write_lock);
+
+       }
+
+       err = ts27010_ldisc_init();
+       if (err != 0) {
+               pr_err("ts27010mux: error %d registering line disc.\n", err);
+               goto err0;
+       }
+
+       err = ts27010_tty_init();
+       if (err != 0) {
+               pr_err("ts27010mux: error %d registering tty.\n", err);
+               goto err1;
+       }
+
+       pr_info("ts27010 mux registered\n");
+
+       return 0;
+
+err1:
+       ts27010_ldisc_remove();
+
+err0:
+       for (j = 0; j < NR_MUXS; j++)
+               kfree(ts0710_connection.chan[j].buf);
+
+       return err;
+}
+
+static void __exit mux_exit(void)
+{
+       int j;
+
+       for (j = 0; j < NR_MUXS; j++)
+               kfree(&ts0710_connection.chan[j].buf);
+
+       ts27010_tty_remove();
+       ts27010_ldisc_remove();
+}
+
+module_init(mux_init);
+module_exit(mux_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Harald Welte <laforge@openezx.org>");
+MODULE_DESCRIPTION("TS 07.10 Multiplexer");
diff --git a/drivers/misc/ts27010mux/ts27010_mux.h b/drivers/misc/ts27010mux/ts27010_mux.h
new file mode 100644 (file)
index 0000000..3013dd7
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * ts27010_mux.h
+ *
+ * Copyright (C) 2002, 2004, 2009 Motorola, Inc.
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+/*
+* This header file should be included by both MUX and other applications
+* which access MUX device files. It gives the additional macro definitions
+* shared between MUX and applications.
+*/
+
+#define NR_MUXS 16
+
+#define NUM_MUX_CMD_FILES 16
+#define NUM_MUX_DATA_FILES 0
+#define NUM_MUX_FILES (NUM_MUX_CMD_FILES  +  NUM_MUX_DATA_FILES)
+
+#define LDISC_BUFFER_SIZE (4096 - sizeof(struct ts27010_ringbuf))
+
+/* TODO: should use the IOCTLNUM macros */
+/* Special ioctl() upon a MUX device file for hanging up a call */
+#define TS0710MUX_IO_MSC_HANGUP 0x54F0
+
+/* Special ioctl() upon a MUX device file for MUX loopback test */
+#define TS0710MUX_IO_TEST_CMD 0x54F1
+
+/* TODO: get rid of these */
+/* Special Error code might be return from write() to a MUX device file */
+#define EDISCONNECTED 900      /* link is disconnected */
+
+/* Special Error code might be return from open() to a MUX device file  */
+#define EREJECTED 901          /* link connection request is rejected */
+
+/* TODO: goes away with clean tty interface */
+extern struct tty_struct *ts27010mux_tty;
+
+struct ts27010_ringbuf;
+
+int ts27010_mux_active(void);
+int ts27010_mux_line_open(int line);
+void ts27010_mux_line_close(int line);
+int ts27010_mux_line_write(int line, const unsigned char *buf, int count);
+int ts27010_mux_line_chars_in_buffer(int line);
+int ts27010_mux_line_write_room(int line);
+void ts27010_mux_recv(struct ts27010_ringbuf *rbuf);
+
+int ts27010_ldisc_init(void);
+void ts27010_ldisc_remove(void);
+int ts27010_ldisc_send(struct tty_struct *tty, u8 *data, int len);
+
+
+int ts27010_tty_init(void);
+void ts27010_tty_remove(void);
+int ts27010_tty_send(int line, u8 *data, int len);
+int ts27010_tty_send_rbuf(int line, struct ts27010_ringbuf *rbuf,
+                         int data_idx, int len);
+
+
diff --git a/drivers/misc/ts27010mux/ts27010_ringbuf.h b/drivers/misc/ts27010mux/ts27010_ringbuf.h
new file mode 100644 (file)
index 0000000..939143a
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * simple ring buffer
+ *
+ * supports a concurrent reader and writer without locking
+ */
+
+
+struct ts27010_ringbuf {
+       int len;
+       int head;
+       int tail;
+       u8 buf[];
+};
+
+
+static inline struct ts27010_ringbuf *ts27010_ringbuf_alloc(int len)
+{
+       struct ts27010_ringbuf *rbuf;
+
+       rbuf = kzalloc(sizeof(*rbuf) + len, GFP_KERNEL);
+       if (rbuf == NULL)
+               return NULL;
+
+       rbuf->len = len;
+       rbuf->head = 0;
+       rbuf->tail = 0;
+
+       return rbuf;
+}
+
+static inline void ts27010_ringbuf_free(struct ts27010_ringbuf *rbuf)
+{
+       kfree(rbuf);
+}
+
+static inline int ts27010_ringbuf_level(struct ts27010_ringbuf *rbuf)
+{
+       int level = rbuf->head - rbuf->tail;
+
+       if (level < 0)
+               level = rbuf->len + level;
+
+       return level;
+}
+
+static inline int ts27010_ringbuf_room(struct ts27010_ringbuf *rbuf)
+{
+       return rbuf->len - ts27010_ringbuf_level(rbuf) - 1;
+}
+
+static inline u8 ts27010_ringbuf_peek(struct ts27010_ringbuf *rbuf, int i)
+{
+       return rbuf->buf[(rbuf->tail + i) % rbuf->len];
+}
+
+static inline int ts27010_ringbuf_consume(struct ts27010_ringbuf *rbuf,
+                                         int count)
+{
+       count = min(count, ts27010_ringbuf_level(rbuf));
+
+       rbuf->tail = (rbuf->tail + count) % rbuf->len;
+
+       return count;
+}
+
+static inline int ts27010_ringbuf_push(struct ts27010_ringbuf *rbuf, u8 datum)
+{
+       if (ts27010_ringbuf_room(rbuf) == 0)
+               return 0;
+
+       rbuf->buf[rbuf->head] = datum;
+       rbuf->head = (rbuf->head + 1) % rbuf->len;
+
+       return 1;
+}
+
+static inline int ts27010_ringbuf_write(struct ts27010_ringbuf *rbuf,
+                                       const u8 *data, int len)
+{
+       int count = 0;
+       int i;
+
+       for (i = 0; i < len; i++)
+               count += ts27010_ringbuf_push(rbuf, data[i]);
+
+       return count;
+}
+
+
diff --git a/drivers/misc/ts27010mux/ts27010_tty.c b/drivers/misc/ts27010mux/ts27010_tty.c
new file mode 100644 (file)
index 0000000..3249540
--- /dev/null
@@ -0,0 +1,254 @@
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+
+#include "ts27010_mux.h"
+#include "ts27010_ringbuf.h"
+
+struct ts27010_tty_channel_data {
+       atomic_t ref_count;
+       struct tty_struct *tty;
+};
+
+struct ts27010_tty_data {
+       struct ts27010_tty_channel_data         chan[NR_MUXS];
+};
+
+/* TODO: find a good place to put this */
+struct tty_driver *driver;
+
+/* TODO: should request a major */
+#define TS0710MUX_MAJOR 245
+#define TS0710MUX_MINOR_START 0
+
+int ts27010_tty_send(int line, u8 *data, int len)
+{
+       struct ts27010_tty_data *td = driver->driver_state;
+       struct tty_struct *tty = td->chan[line].tty;
+
+       if (!tty) {
+               pr_info("ts27010: mux%d no open.  discarding %d bytes\n",
+                       line, len);
+               return 0;
+       }
+
+       BUG_ON(tty_insert_flip_string(tty, data, len) != len);
+       tty_flip_buffer_push(tty);
+       return len;
+}
+
+int ts27010_tty_send_rbuf(int line, struct ts27010_ringbuf *rbuf,
+                         int data_idx, int len)
+{
+       struct ts27010_tty_data *td = driver->driver_state;
+       struct tty_struct *tty = td->chan[line].tty;
+
+       if (!tty) {
+               pr_info("ts27010: mux%d no open.  discarding %d bytes\n",
+                       line, len);
+               return 0;
+       }
+
+       while (len--) {
+               char c = ts27010_ringbuf_peek(rbuf, data_idx++);
+               tty_insert_flip_char(tty, c, TTY_NORMAL);
+       }
+       tty_flip_buffer_push(tty);
+       return len;
+}
+
+static int ts27010_tty_open(struct tty_struct *tty, struct file *filp)
+{
+       struct ts27010_tty_data *td = tty->driver->driver_state;
+       int err;
+       int line;
+
+       if (!ts27010_mux_active()) {
+               pr_err("ts27010: tty open when line discipline not active.\n");
+               err = -ENODEV;
+               goto err;
+       }
+
+       line = tty->index;
+       if ((line < 0) || (line >= NR_MUXS)) {
+               pr_err("ts27010: tty index out of range.\n");
+               err = -ENODEV;
+               goto err;
+       }
+
+       atomic_inc(&td->chan[line].ref_count);
+
+       td->chan[line].tty = tty;
+
+       err = ts27010_mux_line_open(line);
+       if (err < 0)
+               goto err;
+
+       return 0;
+
+err:
+       return err;
+}
+
+
+static void ts27010_tty_close(struct tty_struct *tty, struct file *filp)
+{
+       struct ts27010_tty_data *td = tty->driver->driver_state;
+
+       if (atomic_dec_and_test(&td->chan[tty->index].ref_count)) {
+               ts27010_mux_line_close(tty->index);
+
+               td->chan[tty->index].tty = NULL;
+
+               /*
+                * the old code did:
+                *   wake_up_interruptible(&tty->read_wait);
+                *   wake_up_interruptible(&tty->write_wait);
+                *
+                * I belive this is unecessary
+                */
+       }
+}
+
+static int ts27010_tty_write(struct tty_struct *tty,
+                            const unsigned char *buf, int count)
+{
+       return ts27010_mux_line_write(tty->index, buf, count);
+}
+
+
+static int ts27010_tty_write_room(struct tty_struct *tty)
+{
+       return ts27010_mux_line_write_room(tty->index);
+}
+
+static void ts27010_tty_flush_buffer(struct tty_struct *tty)
+{
+       pr_warning("ts27010: flush_buffer not implemented on line %d\n",
+               tty->index);
+}
+
+static int ts27010_tty_chars_in_buffer(struct tty_struct *tty)
+{
+       return ts27010_mux_line_chars_in_buffer(tty->index);
+}
+
+static void ts27010_tty_throttle(struct tty_struct *tty)
+{
+       pr_warning("ts27010: throttle not implemented on line %d\n",
+               tty->index);
+}
+
+static void ts27010_tty_unthrottle(struct tty_struct *tty)
+{
+       pr_warning("ts27010: unthrottle not implemented on line %d\n",
+               tty->index);
+}
+
+static int ts27010_tty_ioctl(struct tty_struct *tty, struct file *file,
+                    unsigned int cmd, unsigned long arg)
+{
+       int line;
+
+       line = tty->index;
+       if ((line < 0) || (line >= NR_MUXS))
+               return -ENODEV;
+
+       switch (cmd) {
+       case TS0710MUX_IO_MSC_HANGUP:
+               pr_warning("ts27010: ioctl msc_hangup not implemented\n");
+               return 0;
+
+       case TS0710MUX_IO_TEST_CMD:
+               pr_warning("ts27010: ioctl msc_hangup not implemented\n");
+               return 0;
+
+       default:
+               break;
+       }
+
+       return -ENOIOCTLCMD;
+}
+
+static const struct tty_operations ts27010_tty_ops = {
+       .open = ts27010_tty_open,
+       .close = ts27010_tty_close,
+       .write = ts27010_tty_write,
+       .write_room = ts27010_tty_write_room,
+       .flush_buffer = ts27010_tty_flush_buffer,
+       .chars_in_buffer = ts27010_tty_chars_in_buffer,
+       .throttle = ts27010_tty_throttle,
+       .unthrottle = ts27010_tty_unthrottle,
+       .ioctl = ts27010_tty_ioctl,
+};
+
+int ts27010_tty_init(void)
+{
+       struct ts27010_tty_data *td;
+       int err;
+       int i;
+
+       driver = alloc_tty_driver(NR_MUXS);
+       if (driver == NULL) {
+               err = -ENOMEM;
+               goto err0;
+       }
+
+       td = kzalloc(sizeof(*td), GFP_KERNEL);
+       if (td == NULL) {
+               err = -ENOMEM;
+               goto err1;
+       }
+
+       for (i = 0; i < NR_MUXS; i++)
+               atomic_set(&td->chan[i].ref_count, 0);
+
+       driver->driver_state = td;
+
+       driver->driver_name = "ts0710mux";
+       driver->name = "ts0710mux";
+       driver->major = TS0710MUX_MAJOR;
+       driver->major = 234;
+       driver->minor_start = TS0710MUX_MINOR_START;
+       driver->type = TTY_DRIVER_TYPE_SERIAL;
+       driver->subtype = SERIAL_TYPE_NORMAL;
+       driver->init_termios = tty_std_termios;
+       driver->init_termios.c_iflag = 0;
+       driver->init_termios.c_oflag = 0;
+       driver->init_termios.c_cflag = B38400 | CS8 | CREAD;
+       driver->init_termios.c_lflag = 0;
+       driver->flags = TTY_DRIVER_RESET_TERMIOS | TTY_DRIVER_REAL_RAW;
+
+       driver->other = NULL;
+       driver->owner = THIS_MODULE;
+
+       tty_set_operations(driver, &ts27010_tty_ops);
+
+       if (tty_register_driver(driver)) {
+               pr_err("ts27010: can't register tty driver\n");
+               err = -EINVAL;
+               goto err2;
+       }
+
+       return 0;
+
+err2:
+       kfree(td);
+err1:
+       put_tty_driver(driver);
+err0:
+       return err;
+}
+
+void ts27010_tty_remove(void)
+{
+       struct ts27010_tty_data *td = driver->driver_state;
+       tty_unregister_driver(driver);
+       kfree(td);
+       put_tty_driver(driver);
+}
+