From 6c1b49f901762aefd047f4d2a0d6e25a66d89d47 Mon Sep 17 00:00:00 2001 From: Kazuhiro Ondo Date: Wed, 21 Jul 2010 11:58:29 -0700 Subject: [PATCH] misc: Add TS27.010 Mux driver Change-Id: I1ab0cf0e141fcd2915b2d27d1badaa274803b73e Signed-off-by: Rebecca Schultz Zavin --- drivers/misc/Kconfig | 1 + drivers/misc/Makefile | 1 + drivers/misc/ts27010mux/Kconfig | 11 + drivers/misc/ts27010mux/Makefile | 11 + drivers/misc/ts27010mux/ts0710.h | 273 ++++ drivers/misc/ts27010mux/ts27010_ldisc.c | 223 ++++ drivers/misc/ts27010mux/ts27010_mux.c | 1389 +++++++++++++++++++++ drivers/misc/ts27010mux/ts27010_mux.h | 74 ++ drivers/misc/ts27010mux/ts27010_ringbuf.h | 89 ++ drivers/misc/ts27010mux/ts27010_tty.c | 254 ++++ 10 files changed, 2326 insertions(+) create mode 100644 drivers/misc/ts27010mux/Kconfig create mode 100644 drivers/misc/ts27010mux/Makefile create mode 100644 drivers/misc/ts27010mux/ts0710.h create mode 100644 drivers/misc/ts27010mux/ts27010_ldisc.c create mode 100644 drivers/misc/ts27010mux/ts27010_mux.c create mode 100644 drivers/misc/ts27010mux/ts27010_mux.h create mode 100644 drivers/misc/ts27010mux/ts27010_ringbuf.h create mode 100644 drivers/misc/ts27010mux/ts27010_tty.c diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 231b3fb882ee..d826c4a7556f 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -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 diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index dc1002259afd..d0dc080d7925 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -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 index 000000000000..4ccb475086d6 --- /dev/null +++ b/drivers/misc/ts27010mux/Kconfig @@ -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 index 000000000000..e83cd3057e7b --- /dev/null +++ b/drivers/misc/ts27010mux/Makefile @@ -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 index 000000000000..663102bf553c --- /dev/null +++ b/drivers/misc/ts27010mux/ts0710.h @@ -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 + * + * 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 index 000000000000..3a9be8d63dba --- /dev/null +++ b/drivers/misc/ts27010mux/ts27010_ldisc.c @@ -0,0 +1,223 @@ +#include +#include +#include +#include +#include +#include +#include + +#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 index 000000000000..3c6f48cd4723 --- /dev/null +++ b/drivers/misc/ts27010mux/ts27010_mux.c @@ -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 + * + * 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 +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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: 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 "); +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 index 000000000000..3013dd725d88 --- /dev/null +++ b/drivers/misc/ts27010mux/ts27010_mux.h @@ -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 index 000000000000..939143a86a11 --- /dev/null +++ b/drivers/misc/ts27010mux/ts27010_ringbuf.h @@ -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 index 000000000000..324954010493 --- /dev/null +++ b/drivers/misc/ts27010mux/ts27010_tty.c @@ -0,0 +1,254 @@ +#include +#include +#include +#include +#include +#include +#include + +#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); +} + -- 2.34.1