summaryrefslogtreecommitdiffstats
path: root/drivers/s390/net/ctctty.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/s390/net/ctctty.c')
-rw-r--r--drivers/s390/net/ctctty.c1276
1 files changed, 1276 insertions, 0 deletions
diff --git a/drivers/s390/net/ctctty.c b/drivers/s390/net/ctctty.c
new file mode 100644
index 00000000000..9257d60c783
--- /dev/null
+++ b/drivers/s390/net/ctctty.c
@@ -0,0 +1,1276 @@
+/*
+ * $Id: ctctty.c,v 1.26 2004/08/04 11:06:55 mschwide Exp $
+ *
+ * CTC / ESCON network driver, tty interface.
+ *
+ * Copyright (C) 2001 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ * Author(s): Fritz Elfert (elfert@de.ibm.com, felfert@millenux.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, 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.
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/tty.h>
+#include <linux/serial_reg.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <asm/uaccess.h>
+#include <linux/devfs_fs_kernel.h>
+#include "ctctty.h"
+#include "ctcdbug.h"
+
+#define CTC_TTY_MAJOR 43
+#define CTC_TTY_MAX_DEVICES 64
+
+#define CTC_ASYNC_MAGIC 0x49344C01 /* for paranoia-checking */
+#define CTC_ASYNC_INITIALIZED 0x80000000 /* port was initialized */
+#define CTC_ASYNC_NORMAL_ACTIVE 0x20000000 /* Normal device active */
+#define CTC_ASYNC_CLOSING 0x08000000 /* Serial port is closing */
+#define CTC_ASYNC_CTS_FLOW 0x04000000 /* Do CTS flow control */
+#define CTC_ASYNC_CHECK_CD 0x02000000 /* i.e., CLOCAL */
+#define CTC_ASYNC_HUP_NOTIFY 0x0001 /* Notify tty on hangups/closes */
+#define CTC_ASYNC_NETDEV_OPEN 0x0002 /* Underlying netdev is open */
+#define CTC_ASYNC_TX_LINESTAT 0x0004 /* Must send line status */
+#define CTC_ASYNC_SPLIT_TERMIOS 0x0008 /* Sep. termios for dialin/out */
+#define CTC_TTY_XMIT_SIZE 1024 /* Default bufsize for write */
+#define CTC_SERIAL_XMIT_MAX 4000 /* Maximum bufsize for write */
+
+/* Private data (similar to async_struct in <linux/serial.h>) */
+typedef struct {
+ int magic;
+ int flags; /* defined in tty.h */
+ int mcr; /* Modem control register */
+ int msr; /* Modem status register */
+ int lsr; /* Line status register */
+ int line;
+ int count; /* # of fd on device */
+ int blocked_open; /* # of blocked opens */
+ struct net_device *netdev;
+ struct sk_buff_head tx_queue; /* transmit queue */
+ struct sk_buff_head rx_queue; /* receive queue */
+ struct tty_struct *tty; /* Pointer to corresponding tty */
+ wait_queue_head_t open_wait;
+ wait_queue_head_t close_wait;
+ struct semaphore write_sem;
+ struct tasklet_struct tasklet;
+ struct timer_list stoptimer;
+} ctc_tty_info;
+
+/* Description of one CTC-tty */
+typedef struct {
+ struct tty_driver *ctc_tty_device; /* tty-device */
+ ctc_tty_info info[CTC_TTY_MAX_DEVICES]; /* Private data */
+} ctc_tty_driver;
+
+static ctc_tty_driver *driver;
+
+/* Leave this unchanged unless you know what you do! */
+#define MODEM_PARANOIA_CHECK
+#define MODEM_DO_RESTART
+
+#define CTC_TTY_NAME "ctctty"
+
+static __u32 ctc_tty_magic = CTC_ASYNC_MAGIC;
+static int ctc_tty_shuttingdown = 0;
+
+static spinlock_t ctc_tty_lock;
+
+/* ctc_tty_try_read() is called from within ctc_tty_rcv_skb()
+ * to stuff incoming data directly into a tty's flip-buffer. If the
+ * flip buffer is full, the packet gets queued up.
+ *
+ * Return:
+ * 1 = Success
+ * 0 = Failure, data has to be buffered and later processed by
+ * ctc_tty_readmodem().
+ */
+static int
+ctc_tty_try_read(ctc_tty_info * info, struct sk_buff *skb)
+{
+ int c;
+ int len;
+ struct tty_struct *tty;
+
+ DBF_TEXT(trace, 5, __FUNCTION__);
+ if ((tty = info->tty)) {
+ if (info->mcr & UART_MCR_RTS) {
+ c = TTY_FLIPBUF_SIZE - tty->flip.count;
+ len = skb->len;
+ if (c >= len) {
+ memcpy(tty->flip.char_buf_ptr, skb->data, len);
+ memset(tty->flip.flag_buf_ptr, 0, len);
+ tty->flip.count += len;
+ tty->flip.char_buf_ptr += len;
+ tty->flip.flag_buf_ptr += len;
+ tty_flip_buffer_push(tty);
+ kfree_skb(skb);
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+/* ctc_tty_readmodem() is called periodically from within timer-interrupt.
+ * It tries getting received data from the receive queue an stuff it into
+ * the tty's flip-buffer.
+ */
+static int
+ctc_tty_readmodem(ctc_tty_info *info)
+{
+ int ret = 1;
+ struct tty_struct *tty;
+
+ DBF_TEXT(trace, 5, __FUNCTION__);
+ if ((tty = info->tty)) {
+ if (info->mcr & UART_MCR_RTS) {
+ int c = TTY_FLIPBUF_SIZE - tty->flip.count;
+ struct sk_buff *skb;
+
+ if ((c > 0) && (skb = skb_dequeue(&info->rx_queue))) {
+ int len = skb->len;
+ if (len > c)
+ len = c;
+ memcpy(tty->flip.char_buf_ptr, skb->data, len);
+ skb_pull(skb, len);
+ memset(tty->flip.flag_buf_ptr, 0, len);
+ tty->flip.count += len;
+ tty->flip.char_buf_ptr += len;
+ tty->flip.flag_buf_ptr += len;
+ tty_flip_buffer_push(tty);
+ if (skb->len > 0)
+ skb_queue_head(&info->rx_queue, skb);
+ else {
+ kfree_skb(skb);
+ ret = skb_queue_len(&info->rx_queue);
+ }
+ }
+ }
+ }
+ return ret;
+}
+
+void
+ctc_tty_setcarrier(struct net_device *netdev, int on)
+{
+ int i;
+
+ DBF_TEXT(trace, 4, __FUNCTION__);
+ if ((!driver) || ctc_tty_shuttingdown)
+ return;
+ for (i = 0; i < CTC_TTY_MAX_DEVICES; i++)
+ if (driver->info[i].netdev == netdev) {
+ ctc_tty_info *info = &driver->info[i];
+ if (on)
+ info->msr |= UART_MSR_DCD;
+ else
+ info->msr &= ~UART_MSR_DCD;
+ if ((info->flags & CTC_ASYNC_CHECK_CD) && (!on))
+ tty_hangup(info->tty);
+ }
+}
+
+void
+ctc_tty_netif_rx(struct sk_buff *skb)
+{
+ int i;
+ ctc_tty_info *info = NULL;
+
+ DBF_TEXT(trace, 5, __FUNCTION__);
+ if (!skb)
+ return;
+ if ((!skb->dev) || (!driver) || ctc_tty_shuttingdown) {
+ dev_kfree_skb(skb);
+ return;
+ }
+ for (i = 0; i < CTC_TTY_MAX_DEVICES; i++)
+ if (driver->info[i].netdev == skb->dev) {
+ info = &driver->info[i];
+ break;
+ }
+ if (!info) {
+ dev_kfree_skb(skb);
+ return;
+ }
+ if (skb->len < 6) {
+ dev_kfree_skb(skb);
+ return;
+ }
+ if (memcmp(skb->data, &ctc_tty_magic, sizeof(__u32))) {
+ dev_kfree_skb(skb);
+ return;
+ }
+ skb_pull(skb, sizeof(__u32));
+
+ i = *((int *)skb->data);
+ skb_pull(skb, sizeof(info->mcr));
+ if (i & UART_MCR_RTS) {
+ info->msr |= UART_MSR_CTS;
+ if (info->flags & CTC_ASYNC_CTS_FLOW)
+ info->tty->hw_stopped = 0;
+ } else {
+ info->msr &= ~UART_MSR_CTS;
+ if (info->flags & CTC_ASYNC_CTS_FLOW)
+ info->tty->hw_stopped = 1;
+ }
+ if (i & UART_MCR_DTR)
+ info->msr |= UART_MSR_DSR;
+ else
+ info->msr &= ~UART_MSR_DSR;
+ if (skb->len <= 0) {
+ kfree_skb(skb);
+ return;
+ }
+ /* Try to deliver directly via tty-flip-buf if queue is empty */
+ if (skb_queue_empty(&info->rx_queue))
+ if (ctc_tty_try_read(info, skb))
+ return;
+ /* Direct deliver failed or queue wasn't empty.
+ * Queue up for later dequeueing via timer-irq.
+ */
+ skb_queue_tail(&info->rx_queue, skb);
+ /* Schedule dequeuing */
+ tasklet_schedule(&info->tasklet);
+}
+
+static int
+ctc_tty_tint(ctc_tty_info * info)
+{
+ struct sk_buff *skb = skb_dequeue(&info->tx_queue);
+ int stopped = (info->tty->hw_stopped || info->tty->stopped);
+ int wake = 1;
+ int rc;
+
+ DBF_TEXT(trace, 4, __FUNCTION__);
+ if (!info->netdev) {
+ if (skb)
+ kfree_skb(skb);
+ return 0;
+ }
+ if (info->flags & CTC_ASYNC_TX_LINESTAT) {
+ int skb_res = info->netdev->hard_header_len +
+ sizeof(info->mcr) + sizeof(__u32);
+ /* If we must update line status,
+ * create an empty dummy skb and insert it.
+ */
+ if (skb)
+ skb_queue_head(&info->tx_queue, skb);
+
+ skb = dev_alloc_skb(skb_res);
+ if (!skb) {
+ printk(KERN_WARNING
+ "ctc_tty: Out of memory in %s%d tint\n",
+ CTC_TTY_NAME, info->line);
+ return 1;
+ }
+ skb_reserve(skb, skb_res);
+ stopped = 0;
+ wake = 0;
+ }
+ if (!skb)
+ return 0;
+ if (stopped) {
+ skb_queue_head(&info->tx_queue, skb);
+ return 1;
+ }
+#if 0
+ if (skb->len > 0)
+ printk(KERN_DEBUG "tint: %d %02x\n", skb->len, *(skb->data));
+ else
+ printk(KERN_DEBUG "tint: %d STAT\n", skb->len);
+#endif
+ memcpy(skb_push(skb, sizeof(info->mcr)), &info->mcr, sizeof(info->mcr));
+ memcpy(skb_push(skb, sizeof(__u32)), &ctc_tty_magic, sizeof(__u32));
+ rc = info->netdev->hard_start_xmit(skb, info->netdev);
+ if (rc) {
+ skb_pull(skb, sizeof(info->mcr) + sizeof(__u32));
+ if (skb->len > 0)
+ skb_queue_head(&info->tx_queue, skb);
+ else
+ kfree_skb(skb);
+ } else {
+ struct tty_struct *tty = info->tty;
+
+ info->flags &= ~CTC_ASYNC_TX_LINESTAT;
+ if (tty) {
+ tty_wakeup(tty);
+ }
+ }
+ return (skb_queue_empty(&info->tx_queue) ? 0 : 1);
+}
+
+/************************************************************
+ *
+ * Modem-functions
+ *
+ * mostly "stolen" from original Linux-serial.c and friends.
+ *
+ ************************************************************/
+
+static inline int
+ctc_tty_paranoia_check(ctc_tty_info * info, char *name, const char *routine)
+{
+#ifdef MODEM_PARANOIA_CHECK
+ if (!info) {
+ printk(KERN_WARNING "ctc_tty: null info_struct for %s in %s\n",
+ name, routine);
+ return 1;
+ }
+ if (info->magic != CTC_ASYNC_MAGIC) {
+ printk(KERN_WARNING "ctc_tty: bad magic for info struct %s in %s\n",
+ name, routine);
+ return 1;
+ }
+#endif
+ return 0;
+}
+
+static void
+ctc_tty_inject(ctc_tty_info *info, char c)
+{
+ int skb_res;
+ struct sk_buff *skb;
+
+ DBF_TEXT(trace, 4, __FUNCTION__);
+ if (ctc_tty_shuttingdown)
+ return;
+ skb_res = info->netdev->hard_header_len + sizeof(info->mcr) +
+ sizeof(__u32) + 1;
+ skb = dev_alloc_skb(skb_res);
+ if (!skb) {
+ printk(KERN_WARNING
+ "ctc_tty: Out of memory in %s%d tx_inject\n",
+ CTC_TTY_NAME, info->line);
+ return;
+ }
+ skb_reserve(skb, skb_res);
+ *(skb_put(skb, 1)) = c;
+ skb_queue_head(&info->tx_queue, skb);
+ tasklet_schedule(&info->tasklet);
+}
+
+static void
+ctc_tty_transmit_status(ctc_tty_info *info)
+{
+ DBF_TEXT(trace, 5, __FUNCTION__);
+ if (ctc_tty_shuttingdown)
+ return;
+ info->flags |= CTC_ASYNC_TX_LINESTAT;
+ tasklet_schedule(&info->tasklet);
+}
+
+static void
+ctc_tty_change_speed(ctc_tty_info * info)
+{
+ unsigned int cflag;
+ unsigned int quot;
+ int i;
+
+ DBF_TEXT(trace, 3, __FUNCTION__);
+ if (!info->tty || !info->tty->termios)
+ return;
+ cflag = info->tty->termios->c_cflag;
+
+ quot = i = cflag & CBAUD;
+ if (i & CBAUDEX) {
+ i &= ~CBAUDEX;
+ if (i < 1 || i > 2)
+ info->tty->termios->c_cflag &= ~CBAUDEX;
+ else
+ i += 15;
+ }
+ if (quot) {
+ info->mcr |= UART_MCR_DTR;
+ info->mcr |= UART_MCR_RTS;
+ ctc_tty_transmit_status(info);
+ } else {
+ info->mcr &= ~UART_MCR_DTR;
+ info->mcr &= ~UART_MCR_RTS;
+ ctc_tty_transmit_status(info);
+ return;
+ }
+
+ /* CTS flow control flag and modem status interrupts */
+ if (cflag & CRTSCTS) {
+ info->flags |= CTC_ASYNC_CTS_FLOW;
+ } else
+ info->flags &= ~CTC_ASYNC_CTS_FLOW;
+ if (cflag & CLOCAL)
+ info->flags &= ~CTC_ASYNC_CHECK_CD;
+ else {
+ info->flags |= CTC_ASYNC_CHECK_CD;
+ }
+}
+
+static int
+ctc_tty_startup(ctc_tty_info * info)
+{
+ DBF_TEXT(trace, 3, __FUNCTION__);
+ if (info->flags & CTC_ASYNC_INITIALIZED)
+ return 0;
+#ifdef CTC_DEBUG_MODEM_OPEN
+ printk(KERN_DEBUG "starting up %s%d ...\n", CTC_TTY_NAME, info->line);
+#endif
+ /*
+ * Now, initialize the UART
+ */
+ info->mcr = UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2;
+ if (info->tty)
+ clear_bit(TTY_IO_ERROR, &info->tty->flags);
+ /*
+ * and set the speed of the serial port
+ */
+ ctc_tty_change_speed(info);
+
+ info->flags |= CTC_ASYNC_INITIALIZED;
+ if (!(info->flags & CTC_ASYNC_NETDEV_OPEN))
+ info->netdev->open(info->netdev);
+ info->flags |= CTC_ASYNC_NETDEV_OPEN;
+ return 0;
+}
+
+static void
+ctc_tty_stopdev(unsigned long data)
+{
+ ctc_tty_info *info = (ctc_tty_info *)data;
+
+ if ((!info) || (!info->netdev) ||
+ (info->flags & CTC_ASYNC_INITIALIZED))
+ return;
+ info->netdev->stop(info->netdev);
+ info->flags &= ~CTC_ASYNC_NETDEV_OPEN;
+}
+
+/*
+ * This routine will shutdown a serial port; interrupts are disabled, and
+ * DTR is dropped if the hangup on close termio flag is on.
+ */
+static void
+ctc_tty_shutdown(ctc_tty_info * info)
+{
+ DBF_TEXT(trace, 3, __FUNCTION__);
+ if (!(info->flags & CTC_ASYNC_INITIALIZED))
+ return;
+#ifdef CTC_DEBUG_MODEM_OPEN
+ printk(KERN_DEBUG "Shutting down %s%d ....\n", CTC_TTY_NAME, info->line);
+#endif
+ info->msr &= ~UART_MSR_RI;
+ if (!info->tty || (info->tty->termios->c_cflag & HUPCL))
+ info->mcr &= ~(UART_MCR_DTR | UART_MCR_RTS);
+ if (info->tty)
+ set_bit(TTY_IO_ERROR, &info->tty->flags);
+ mod_timer(&info->stoptimer, jiffies + (10 * HZ));
+ skb_queue_purge(&info->tx_queue);
+ skb_queue_purge(&info->rx_queue);
+ info->flags &= ~CTC_ASYNC_INITIALIZED;
+}
+
+/* ctc_tty_write() is the main send-routine. It is called from the upper
+ * levels within the kernel to perform sending data. Depending on the
+ * online-flag it either directs output to the at-command-interpreter or
+ * to the lower level. Additional tasks done here:
+ * - If online, check for escape-sequence (+++)
+ * - If sending audio-data, call ctc_tty_DLEdown() to parse DLE-codes.
+ * - If receiving audio-data, call ctc_tty_end_vrx() to abort if needed.
+ * - If dialing, abort dial.
+ */
+static int
+ctc_tty_write(struct tty_struct *tty, const u_char * buf, int count)
+{
+ int c;
+ int total = 0;
+ ctc_tty_info *info = (ctc_tty_info *) tty->driver_data;
+
+ DBF_TEXT(trace, 5, __FUNCTION__);
+ if (ctc_tty_shuttingdown)
+ goto ex;
+ if (ctc_tty_paranoia_check(info, tty->name, "ctc_tty_write"))
+ goto ex;
+ if (!tty)
+ goto ex;
+ if (!info->netdev) {
+ total = -ENODEV;
+ goto ex;
+ }
+ while (1) {
+ struct sk_buff *skb;
+ int skb_res;
+
+ c = (count < CTC_TTY_XMIT_SIZE) ? count : CTC_TTY_XMIT_SIZE;
+ if (c <= 0)
+ break;
+
+ skb_res = info->netdev->hard_header_len + sizeof(info->mcr) +
+ + sizeof(__u32);
+ skb = dev_alloc_skb(skb_res + c);
+ if (!skb) {
+ printk(KERN_WARNING
+ "ctc_tty: Out of memory in %s%d write\n",
+ CTC_TTY_NAME, info->line);
+ break;
+ }
+ skb_reserve(skb, skb_res);
+ memcpy(skb_put(skb, c), buf, c);
+ skb_queue_tail(&info->tx_queue, skb);
+ buf += c;
+ total += c;
+ count -= c;
+ }
+ if (skb_queue_len(&info->tx_queue)) {
+ info->lsr &= ~UART_LSR_TEMT;
+ tasklet_schedule(&info->tasklet);
+ }
+ex:
+ DBF_TEXT(trace, 6, __FUNCTION__);
+ return total;
+}
+
+static int
+ctc_tty_write_room(struct tty_struct *tty)
+{
+ ctc_tty_info *info = (ctc_tty_info *) tty->driver_data;
+
+ if (ctc_tty_paranoia_check(info, tty->name, "ctc_tty_write_room"))
+ return 0;
+ return CTC_TTY_XMIT_SIZE;
+}
+
+static int
+ctc_tty_chars_in_buffer(struct tty_struct *tty)
+{
+ ctc_tty_info *info = (ctc_tty_info *) tty->driver_data;
+
+ if (ctc_tty_paranoia_check(info, tty->name, "ctc_tty_chars_in_buffer"))
+ return 0;
+ return 0;
+}
+
+static void
+ctc_tty_flush_buffer(struct tty_struct *tty)
+{
+ ctc_tty_info *info;
+ unsigned long flags;
+
+ DBF_TEXT(trace, 4, __FUNCTION__);
+ if (!tty)
+ goto ex;
+ spin_lock_irqsave(&ctc_tty_lock, flags);
+ info = (ctc_tty_info *) tty->driver_data;
+ if (ctc_tty_paranoia_check(info, tty->name, "ctc_tty_flush_buffer")) {
+ spin_unlock_irqrestore(&ctc_tty_lock, flags);
+ goto ex;
+ }
+ skb_queue_purge(&info->tx_queue);
+ info->lsr |= UART_LSR_TEMT;
+ spin_unlock_irqrestore(&ctc_tty_lock, flags);
+ wake_up_interruptible(&tty->write_wait);
+ tty_wakeup(tty);
+ex:
+ DBF_TEXT_(trace, 2, "ex: %s ", __FUNCTION__);
+ return;
+}
+
+static void
+ctc_tty_flush_chars(struct tty_struct *tty)
+{
+ ctc_tty_info *info = (ctc_tty_info *) tty->driver_data;
+
+ DBF_TEXT(trace, 4, __FUNCTION__);
+ if (ctc_tty_shuttingdown)
+ return;
+ if (ctc_tty_paranoia_check(info, tty->name, "ctc_tty_flush_chars"))
+ return;
+ if (tty->stopped || tty->hw_stopped || (!skb_queue_len(&info->tx_queue)))
+ return;
+ tasklet_schedule(&info->tasklet);
+}
+
+/*
+ * ------------------------------------------------------------
+ * ctc_tty_throttle()
+ *
+ * This routine is called by the upper-layer tty layer to signal that
+ * incoming characters should be throttled.
+ * ------------------------------------------------------------
+ */
+static void
+ctc_tty_throttle(struct tty_struct *tty)
+{
+ ctc_tty_info *info = (ctc_tty_info *) tty->driver_data;
+
+ DBF_TEXT(trace, 4, __FUNCTION__);
+ if (ctc_tty_paranoia_check(info, tty->name, "ctc_tty_throttle"))
+ return;
+ info->mcr &= ~UART_MCR_RTS;
+ if (I_IXOFF(tty))
+ ctc_tty_inject(info, STOP_CHAR(tty));
+ ctc_tty_transmit_status(info);
+}
+
+static void
+ctc_tty_unthrottle(struct tty_struct *tty)
+{
+ ctc_tty_info *info = (ctc_tty_info *) tty->driver_data;
+
+ DBF_TEXT(trace, 4, __FUNCTION__);
+ if (ctc_tty_paranoia_check(info, tty->name, "ctc_tty_unthrottle"))
+ return;
+ info->mcr |= UART_MCR_RTS;
+ if (I_IXOFF(tty))
+ ctc_tty_inject(info, START_CHAR(tty));
+ ctc_tty_transmit_status(info);
+}
+
+/*
+ * ------------------------------------------------------------
+ * ctc_tty_ioctl() and friends
+ * ------------------------------------------------------------
+ */
+
+/*
+ * ctc_tty_get_lsr_info - get line status register info
+ *
+ * Purpose: Let user call ioctl() to get info when the UART physically
+ * is emptied. On bus types like RS485, the transmitter must
+ * release the bus after transmitting. This must be done when
+ * the transmit shift register is empty, not be done when the
+ * transmit holding register is empty. This functionality
+ * allows RS485 driver to be written in user space.
+ */
+static int
+ctc_tty_get_lsr_info(ctc_tty_info * info, uint __user *value)
+{
+ u_char status;
+ uint result;
+ ulong flags;
+
+ DBF_TEXT(trace, 4, __FUNCTION__);
+ spin_lock_irqsave(&ctc_tty_lock, flags);
+ status = info->lsr;
+ spin_unlock_irqrestore(&ctc_tty_lock, flags);
+ result = ((status & UART_LSR_TEMT) ? TIOCSER_TEMT : 0);
+ put_user(result, value);
+ return 0;
+}
+
+
+static int ctc_tty_tiocmget(struct tty_struct *tty, struct file *file)
+{
+ ctc_tty_info *info = (ctc_tty_info *) tty->driver_data;
+ u_char control,
+ status;
+ uint result;
+ ulong flags;
+
+ DBF_TEXT(trace, 4, __FUNCTION__);
+ if (ctc_tty_paranoia_check(info, tty->name, "ctc_tty_ioctl"))
+ return -ENODEV;
+ if (tty->flags & (1 << TTY_IO_ERROR))
+ return -EIO;
+
+ control = info->mcr;
+ spin_lock_irqsave(&ctc_tty_lock, flags);
+ status = info->msr;
+ spin_unlock_irqrestore(&ctc_tty_lock, flags);
+ result = ((control & UART_MCR_RTS) ? TIOCM_RTS : 0)
+ | ((control & UART_MCR_DTR) ? TIOCM_DTR : 0)
+ | ((status & UART_MSR_DCD) ? TIOCM_CAR : 0)
+ | ((status & UART_MSR_RI) ? TIOCM_RNG : 0)
+ | ((status & UART_MSR_DSR) ? TIOCM_DSR : 0)
+ | ((status & UART_MSR_CTS) ? TIOCM_CTS : 0);
+ return result;
+}
+
+static int
+ctc_tty_tiocmset(struct tty_struct *tty, struct file *file,
+ unsigned int set, unsigned int clear)
+{
+ ctc_tty_info *info = (ctc_tty_info *) tty->driver_data;
+
+ DBF_TEXT(trace, 4, __FUNCTION__);
+ if (ctc_tty_paranoia_check(info, tty->name, "ctc_tty_ioctl"))
+ return -ENODEV;
+ if (tty->flags & (1 << TTY_IO_ERROR))
+ return -EIO;
+
+ if (set & TIOCM_RTS)
+ info->mcr |= UART_MCR_RTS;
+ if (set & TIOCM_DTR)
+ info->mcr |= UART_MCR_DTR;
+
+ if (clear & TIOCM_RTS)
+ info->mcr &= ~UART_MCR_RTS;
+ if (clear & TIOCM_DTR)
+ info->mcr &= ~UART_MCR_DTR;
+
+ if ((set | clear) & (TIOCM_RTS|TIOCM_DTR))
+ ctc_tty_transmit_status(info);
+ return 0;
+}
+
+static int
+ctc_tty_ioctl(struct tty_struct *tty, struct file *file,
+ uint cmd, ulong arg)
+{
+ ctc_tty_info *info = (ctc_tty_info *) tty->driver_data;
+ int error;
+ int retval;
+
+ DBF_TEXT(trace, 4, __FUNCTION__);
+ if (ctc_tty_paranoia_check(info, tty->name, "ctc_tty_ioctl"))
+ return -ENODEV;
+ if (tty->flags & (1 << TTY_IO_ERROR))
+ return -EIO;
+ switch (cmd) {
+ case TCSBRK: /* SVID version: non-zero arg --> no break */
+#ifdef CTC_DEBUG_MODEM_IOCTL
+ printk(KERN_DEBUG "%s%d ioctl TCSBRK\n", CTC_TTY_NAME, info->line);
+#endif
+ retval = tty_check_change(tty);
+ if (retval)
+ return retval;
+ tty_wait_until_sent(tty, 0);
+ return 0;
+ case TCSBRKP: /* support for POSIX tcsendbreak() */
+#ifdef CTC_DEBUG_MODEM_IOCTL
+ printk(KERN_DEBUG "%s%d ioctl TCSBRKP\n", CTC_TTY_NAME, info->line);
+#endif
+ retval = tty_check_change(tty);
+ if (retval)
+ return retval;
+ tty_wait_until_sent(tty, 0);
+ return 0;
+ case TIOCGSOFTCAR:
+#ifdef CTC_DEBUG_MODEM_IOCTL
+ printk(KERN_DEBUG "%s%d ioctl TIOCGSOFTCAR\n", CTC_TTY_NAME,
+ info->line);
+#endif
+ error = put_user(C_CLOCAL(tty) ? 1 : 0, (ulong __user *) arg);
+ return error;
+ case TIOCSSOFTCAR:
+#ifdef CTC_DEBUG_MODEM_IOCTL
+ printk(KERN_DEBUG "%s%d ioctl TIOCSSOFTCAR\n", CTC_TTY_NAME,
+ info->line);
+#endif
+ error = get_user(arg, (ulong __user *) arg);
+ if (error)
+ return error;
+ tty->termios->c_cflag =
+ ((tty->termios->c_cflag & ~CLOCAL) |
+ (arg ? CLOCAL : 0));
+ return 0;
+ case TIOCSERGETLSR: /* Get line status register */
+#ifdef CTC_DEBUG_MODEM_IOCTL
+ printk(KERN_DEBUG "%s%d ioctl TIOCSERGETLSR\n", CTC_TTY_NAME,
+ info->line);
+#endif
+ if (access_ok(VERIFY_WRITE, (void __user *) arg, sizeof(uint)))
+ return ctc_tty_get_lsr_info(info, (uint __user *) arg);
+ else
+ return -EFAULT;
+ default:
+#ifdef CTC_DEBUG_MODEM_IOCTL
+ printk(KERN_DEBUG "UNKNOWN ioctl 0x%08x on %s%d\n", cmd,
+ CTC_TTY_NAME, info->line);
+#endif
+ return -ENOIOCTLCMD;
+ }
+ return 0;
+}
+
+static void
+ctc_tty_set_termios(struct tty_struct *tty, struct termios *old_termios)
+{
+ ctc_tty_info *info = (ctc_tty_info *) tty->driver_data;
+ unsigned int cflag = tty->termios->c_cflag;
+
+ DBF_TEXT(trace, 4, __FUNCTION__);
+ ctc_tty_change_speed(info);
+
+ /* Handle transition to B0 */
+ if ((old_termios->c_cflag & CBAUD) && !(cflag & CBAUD)) {
+ info->mcr &= ~(UART_MCR_DTR|UART_MCR_RTS);
+ ctc_tty_transmit_status(info);
+ }
+
+ /* Handle transition from B0 to other */
+ if (!(old_termios->c_cflag & CBAUD) && (cflag & CBAUD)) {
+ info->mcr |= UART_MCR_DTR;
+ if (!(tty->termios->c_cflag & CRTSCTS) ||
+ !test_bit(TTY_THROTTLED, &tty->flags)) {
+ info->mcr |= UART_MCR_RTS;
+ }
+ ctc_tty_transmit_status(info);
+ }
+
+ /* Handle turning off CRTSCTS */
+ if ((old_termios->c_cflag & CRTSCTS) &&
+ !(tty->termios->c_cflag & CRTSCTS))
+ tty->hw_stopped = 0;
+}
+
+/*
+ * ------------------------------------------------------------
+ * ctc_tty_open() and friends
+ * ------------------------------------------------------------
+ */
+static int
+ctc_tty_block_til_ready(struct tty_struct *tty, struct file *filp, ctc_tty_info *info)
+{
+ DECLARE_WAITQUEUE(wait, NULL);
+ int do_clocal = 0;
+ unsigned long flags;
+ int retval;
+
+ DBF_TEXT(trace, 4, __FUNCTION__);
+ /*
+ * If the device is in the middle of being closed, then block
+ * until it's done, and then try again.
+ */
+ if (tty_hung_up_p(filp) ||
+ (info->flags & CTC_ASYNC_CLOSING)) {
+ if (info->flags & CTC_ASYNC_CLOSING)
+ wait_event(info->close_wait,
+ !(info->flags & CTC_ASYNC_CLOSING));
+#ifdef MODEM_DO_RESTART
+ if (info->flags & CTC_ASYNC_HUP_NOTIFY)
+ return -EAGAIN;
+ else
+ return -ERESTARTSYS;
+#else
+ return -EAGAIN;
+#endif
+ }
+ /*
+ * If non-blocking mode is set, then make the check up front
+ * and then exit.
+ */
+ if ((filp->f_flags & O_NONBLOCK) ||
+ (tty->flags & (1 << TTY_IO_ERROR))) {
+ info->flags |= CTC_ASYNC_NORMAL_ACTIVE;
+ return 0;
+ }
+ if (tty->termios->c_cflag & CLOCAL)
+ do_clocal = 1;
+ /*
+ * Block waiting for the carrier detect and the line to become
+ * free (i.e., not in use by the callout). While we are in
+ * this loop, info->count is dropped by one, so that
+ * ctc_tty_close() knows when to free things. We restore it upon
+ * exit, either normal or abnormal.
+ */
+ retval = 0;
+ add_wait_queue(&info->open_wait, &wait);
+#ifdef CTC_DEBUG_MODEM_OPEN
+ printk(KERN_DEBUG "ctc_tty_block_til_ready before block: %s%d, count = %d\n",
+ CTC_TTY_NAME, info->line, info->count);
+#endif
+ spin_lock_irqsave(&ctc_tty_lock, flags);
+ if (!(tty_hung_up_p(filp)))
+ info->count--;
+ spin_unlock_irqrestore(&ctc_tty_lock, flags);
+ info->blocked_open++;
+ while (1) {
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (tty_hung_up_p(filp) ||
+ !(info->flags & CTC_ASYNC_INITIALIZED)) {
+#ifdef MODEM_DO_RESTART
+ if (info->flags & CTC_ASYNC_HUP_NOTIFY)
+ retval = -EAGAIN;
+ else
+ retval = -ERESTARTSYS;
+#else
+ retval = -EAGAIN;
+#endif
+ break;
+ }
+ if (!(info->flags & CTC_ASYNC_CLOSING) &&
+ (do_clocal || (info->msr & UART_MSR_DCD))) {
+ break;
+ }
+ if (signal_pending(current)) {
+ retval = -ERESTARTSYS;
+ break;
+ }
+#ifdef CTC_DEBUG_MODEM_OPEN
+ printk(KERN_DEBUG "ctc_tty_block_til_ready blocking: %s%d, count = %d\n",
+ CTC_TTY_NAME, info->line, info->count);
+#endif
+ schedule();
+ }
+ current->state = TASK_RUNNING;
+ remove_wait_queue(&info->open_wait, &wait);
+ if (!tty_hung_up_p(filp))
+ info->count++;
+ info->blocked_open--;
+#ifdef CTC_DEBUG_MODEM_OPEN
+ printk(KERN_DEBUG "ctc_tty_block_til_ready after blocking: %s%d, count = %d\n",
+ CTC_TTY_NAME, info->line, info->count);
+#endif
+ if (retval)
+ return retval;
+ info->flags |= CTC_ASYNC_NORMAL_ACTIVE;
+ return 0;
+}
+
+/*
+ * This routine is called whenever a serial port is opened. It
+ * enables interrupts for a serial port, linking in its async structure into
+ * the IRQ chain. It also performs the serial-specific
+ * initialization for the tty structure.
+ */
+static int
+ctc_tty_open(struct tty_struct *tty, struct file *filp)
+{
+ ctc_tty_info *info;
+ unsigned long saveflags;
+ int retval,
+ line;
+
+ DBF_TEXT(trace, 3, __FUNCTION__);
+ line = tty->index;
+ if (line < 0 || line > CTC_TTY_MAX_DEVICES)
+ return -ENODEV;
+ info = &driver->info[line];
+ if (ctc_tty_paranoia_check(info, tty->name, "ctc_tty_open"))
+ return -ENODEV;
+ if (!info->netdev)
+ return -ENODEV;
+#ifdef CTC_DEBUG_MODEM_OPEN
+ printk(KERN_DEBUG "ctc_tty_open %s, count = %d\n", tty->name,
+ info->count);
+#endif
+ spin_lock_irqsave(&ctc_tty_lock, saveflags);
+ info->count++;
+ tty->driver_data = info;
+ info->tty = tty;
+ spin_unlock_irqrestore(&ctc_tty_lock, saveflags);
+ /*
+ * Start up serial port
+ */
+ retval = ctc_tty_startup(info);
+ if (retval) {
+#ifdef CTC_DEBUG_MODEM_OPEN
+ printk(KERN_DEBUG "ctc_tty_open return after startup\n");
+#endif
+ return retval;
+ }
+ retval = ctc_tty_block_til_ready(tty, filp, info);
+ if (retval) {
+#ifdef CTC_DEBUG_MODEM_OPEN
+ printk(KERN_DEBUG "ctc_tty_open return after ctc_tty_block_til_ready \n");
+#endif
+ return retval;
+ }
+#ifdef CTC_DEBUG_MODEM_OPEN
+ printk(KERN_DEBUG "ctc_tty_open %s successful...\n", tty->name);
+#endif
+ return 0;
+}
+
+static void
+ctc_tty_close(struct tty_struct *tty, struct file *filp)
+{
+ ctc_tty_info *info = (ctc_tty_info *) tty->driver_data;
+ ulong flags;
+ ulong timeout;
+ DBF_TEXT(trace, 3, __FUNCTION__);
+ if (!info || ctc_tty_paranoia_check(info, tty->name, "ctc_tty_close"))
+ return;
+ spin_lock_irqsave(&ctc_tty_lock, flags);
+ if (tty_hung_up_p(filp)) {
+ spin_unlock_irqrestore(&ctc_tty_lock, flags);
+#ifdef CTC_DEBUG_MODEM_OPEN
+ printk(KERN_DEBUG "ctc_tty_close return after tty_hung_up_p\n");
+#endif
+ return;
+ }
+ if ((tty->count == 1) && (info->count != 1)) {
+ /*
+ * Uh, oh. tty->count is 1, which means that the tty
+ * structure will be freed. Info->count should always
+ * be one in these conditions. If it's greater than
+ * one, we've got real problems, since it means the
+ * serial port won't be shutdown.
+ */
+ printk(KERN_ERR "ctc_tty_close: bad port count; tty->count is 1, "
+ "info->count is %d\n", info->count);
+ info->count = 1;
+ }
+ if (--info->count < 0) {
+ printk(KERN_ERR "ctc_tty_close: bad port count for %s%d: %d\n",
+ CTC_TTY_NAME, info->line, info->count);
+ info->count = 0;
+ }
+ if (info->count) {
+ local_irq_restore(flags);
+#ifdef CTC_DEBUG_MODEM_OPEN
+ printk(KERN_DEBUG "ctc_tty_close after info->count != 0\n");
+#endif
+ return;
+ }
+ info->flags |= CTC_ASYNC_CLOSING;
+ tty->closing = 1;
+ /*
+ * At this point we stop accepting input. To do this, we
+ * disable the receive line status interrupts, and tell the
+ * interrupt driver to stop checking the data ready bit in the
+ * line status register.
+ */
+ if (info->flags & CTC_ASYNC_INITIALIZED) {
+ tty_wait_until_sent(tty, 30*HZ); /* 30 seconds timeout */
+ /*
+ * Before we drop DTR, make sure the UART transmitter
+ * has completely drained; this is especially
+ * important if there is a transmit FIFO!
+ */
+ timeout = jiffies + HZ;
+ while (!(info->lsr & UART_LSR_TEMT)) {
+ spin_unlock_irqrestore(&ctc_tty_lock, flags);
+ msleep(500);
+ spin_lock_irqsave(&ctc_tty_lock, flags);
+ if (time_after(jiffies,timeout))
+ break;
+ }
+ }
+ ctc_tty_shutdown(info);
+ if (tty->driver->flush_buffer) {
+ skb_queue_purge(&info->tx_queue);
+ info->lsr |= UART_LSR_TEMT;
+ }
+ tty_ldisc_flush(tty);
+ info->tty = 0;
+ tty->closing = 0;
+ if (info->blocked_open) {
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(HZ/2);
+ wake_up_interruptible(&info->open_wait);
+ }
+ info->flags &= ~(CTC_ASYNC_NORMAL_ACTIVE | CTC_ASYNC_CLOSING);
+ wake_up_interruptible(&info->close_wait);
+ spin_unlock_irqrestore(&ctc_tty_lock, flags);
+#ifdef CTC_DEBUG_MODEM_OPEN
+ printk(KERN_DEBUG "ctc_tty_close normal exit\n");
+#endif
+}
+
+/*
+ * ctc_tty_hangup() --- called by tty_hangup() when a hangup is signaled.
+ */
+static void
+ctc_tty_hangup(struct tty_struct *tty)
+{
+ ctc_tty_info *info = (ctc_tty_info *)tty->driver_data;
+ unsigned long saveflags;
+ DBF_TEXT(trace, 3, __FUNCTION__);
+ if (ctc_tty_paranoia_check(info, tty->name, "ctc_tty_hangup"))
+ return;
+ ctc_tty_shutdown(info);
+ info->count = 0;
+ info->flags &= ~CTC_ASYNC_NORMAL_ACTIVE;
+ spin_lock_irqsave(&ctc_tty_lock, saveflags);
+ info->tty = 0;
+ spin_unlock_irqrestore(&ctc_tty_lock, saveflags);
+ wake_up_interruptible(&info->open_wait);
+}
+
+
+/*
+ * For all online tty's, try sending data to
+ * the lower levels.
+ */
+static void
+ctc_tty_task(unsigned long arg)
+{
+ ctc_tty_info *info = (void *)arg;
+ unsigned long saveflags;
+ int again;
+
+ DBF_TEXT(trace, 3, __FUNCTION__);
+ spin_lock_irqsave(&ctc_tty_lock, saveflags);
+ if ((!ctc_tty_shuttingdown) && info) {
+ again = ctc_tty_tint(info);
+ if (!again)
+ info->lsr |= UART_LSR_TEMT;
+ again |= ctc_tty_readmodem(info);
+ if (again) {
+ tasklet_schedule(&info->tasklet);
+ }
+ }
+ spin_unlock_irqrestore(&ctc_tty_lock, saveflags);
+}
+
+static struct tty_operations ctc_ops = {
+ .open = ctc_tty_open,
+ .close = ctc_tty_close,
+ .write = ctc_tty_write,
+ .flush_chars = ctc_tty_flush_chars,
+ .write_room = ctc_tty_write_room,
+ .chars_in_buffer = ctc_tty_chars_in_buffer,
+ .flush_buffer = ctc_tty_flush_buffer,
+ .ioctl = ctc_tty_ioctl,
+ .throttle = ctc_tty_throttle,
+ .unthrottle = ctc_tty_unthrottle,
+ .set_termios = ctc_tty_set_termios,
+ .hangup = ctc_tty_hangup,
+ .tiocmget = ctc_tty_tiocmget,
+ .tiocmset = ctc_tty_tiocmset,
+};
+
+int
+ctc_tty_init(void)
+{
+ int i;
+ ctc_tty_info *info;
+ struct tty_driver *device;
+
+ DBF_TEXT(trace, 2, __FUNCTION__);
+ driver = kmalloc(sizeof(ctc_tty_driver), GFP_KERNEL);
+ if (driver == NULL) {
+ printk(KERN_WARNING "Out of memory in ctc_tty_modem_init\n");
+ return -ENOMEM;
+ }
+ memset(driver, 0, sizeof(ctc_tty_driver));
+ device = alloc_tty_driver(CTC_TTY_MAX_DEVICES);
+ if (!device) {
+ kfree(driver);
+ printk(KERN_WARNING "Out of memory in ctc_tty_modem_init\n");
+ return -ENOMEM;
+ }
+
+ device->devfs_name = "ctc/" CTC_TTY_NAME;
+ device->name = CTC_TTY_NAME;
+ device->major = CTC_TTY_MAJOR;
+ device->minor_start = 0;
+ device->type = TTY_DRIVER_TYPE_SERIAL;
+ device->subtype = SERIAL_TYPE_NORMAL;
+ device->init_termios = tty_std_termios;
+ device->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+ device->flags = TTY_DRIVER_REAL_RAW;
+ device->driver_name = "ctc_tty",
+ tty_set_operations(device, &ctc_ops);
+ if (tty_register_driver(device)) {
+ printk(KERN_WARNING "ctc_tty: Couldn't register serial-device\n");
+ put_tty_driver(device);
+ kfree(driver);
+ return -1;
+ }
+ driver->ctc_tty_device = device;
+ for (i = 0; i < CTC_TTY_MAX_DEVICES; i++) {
+ info = &driver->info[i];
+ init_MUTEX(&info->write_sem);
+ tasklet_init(&info->tasklet, ctc_tty_task,
+ (unsigned long) info);
+ info->magic = CTC_ASYNC_MAGIC;
+ info->line = i;
+ info->tty = 0;
+ info->count = 0;
+ info->blocked_open = 0;
+ init_waitqueue_head(&info->open_wait);
+ init_waitqueue_head(&info->close_wait);
+ skb_queue_head_init(&info->tx_queue);
+ skb_queue_head_init(&info->rx_queue);
+ init_timer(&info->stoptimer);
+ info->stoptimer.function = ctc_tty_stopdev;
+ info->stoptimer.data = (unsigned long)info;
+ info->mcr = UART_MCR_RTS;
+ }
+ return 0;
+}
+
+int
+ctc_tty_register_netdev(struct net_device *dev) {
+ int ttynum;
+ char *err;
+ char *p;
+
+ DBF_TEXT(trace, 2, __FUNCTION__);
+ if ((!dev) || (!dev->name)) {
+ printk(KERN_WARNING
+ "ctc_tty_register_netdev called "
+ "with NULL dev or NULL dev-name\n");
+ return -1;
+ }
+
+ /*
+ * If the name is a format string the caller wants us to
+ * do a name allocation : format string must end with %d
+ */
+ if (strchr(dev->name, '%'))
+ {
+ int err = dev_alloc_name(dev, dev->name); // dev->name is changed by this
+ if (err < 0) {
+ printk(KERN_DEBUG "dev_alloc returned error %d\n", err);
+ return err;
+ }
+
+ }
+
+ for (p = dev->name; p && ((*p < '0') || (*p > '9')); p++);
+ ttynum = simple_strtoul(p, &err, 0);
+ if ((ttynum < 0) || (ttynum >= CTC_TTY_MAX_DEVICES) ||
+ (err && *err)) {
+ printk(KERN_WARNING
+ "ctc_tty_register_netdev called "
+ "with number in name '%s'\n", dev->name);
+ return -1;
+ }
+ if (driver->info[ttynum].netdev) {
+ printk(KERN_WARNING
+ "ctc_tty_register_netdev called "
+ "for already registered device '%s'\n",
+ dev->name);
+ return -1;
+ }
+ driver->info[ttynum].netdev = dev;
+ return 0;
+}
+
+void
+ctc_tty_unregister_netdev(struct net_device *dev) {
+ int i;
+ unsigned long saveflags;
+ ctc_tty_info *info = NULL;
+
+ DBF_TEXT(trace, 2, __FUNCTION__);
+ spin_lock_irqsave(&ctc_tty_lock, saveflags);
+ for (i = 0; i < CTC_TTY_MAX_DEVICES; i++)
+ if (driver->info[i].netdev == dev) {
+ info = &driver->info[i];
+ break;
+ }
+ if (info) {
+ info->netdev = NULL;
+ skb_queue_purge(&info->tx_queue);
+ skb_queue_purge(&info->rx_queue);
+ }
+ spin_unlock_irqrestore(&ctc_tty_lock, saveflags);
+}
+
+void
+ctc_tty_cleanup(void) {
+ unsigned long saveflags;
+
+ DBF_TEXT(trace, 2, __FUNCTION__);
+ spin_lock_irqsave(&ctc_tty_lock, saveflags);
+ ctc_tty_shuttingdown = 1;
+ spin_unlock_irqrestore(&ctc_tty_lock, saveflags);
+ tty_unregister_driver(driver->ctc_tty_device);
+ put_tty_driver(driver->ctc_tty_device);
+ kfree(driver);
+ driver = NULL;
+}