/*
 * $Id: ctctty.c,v 1.29 2005/04/05 08:50:44 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/tty_flip.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 len;
	struct tty_struct *tty;

	DBF_TEXT(trace, 5, __FUNCTION__);
	if ((tty = info->tty)) {
		if (info->mcr & UART_MCR_RTS) {
			len = skb->len;
			tty_insert_flip_string(tty, skb->data, 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) {
			struct sk_buff *skb;
			
			if ((skb = skb_dequeue(&info->rx_queue))) {
				int len = skb->len;
				tty_insert_flip_string(tty, skb->data, len);
				skb_pull(skb, len);
				tty_flip_buffer_push(tty);
				if (skb->len > 0)
					skb_queue_head(&info->rx_queue, skb);
				else {
					kfree_skb(skb);
					ret = !skb_queue_empty(&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_empty(&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_empty(&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) {
		msleep_interruptible(500);
		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;
}
