Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / drivers / staging / dgnc / dgnc_tty.c
diff --git a/kernel/drivers/staging/dgnc/dgnc_tty.c b/kernel/drivers/staging/dgnc/dgnc_tty.c
new file mode 100644 (file)
index 0000000..ce4187f
--- /dev/null
@@ -0,0 +1,3057 @@
+/*
+ * Copyright 2003 Digi International (www.digi.com)
+ *     Scott H Kilau <Scott_Kilau at digi dot 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, EXPRESS OR IMPLIED; without even the
+ * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ */
+
+/************************************************************************
+ *
+ * This file implements the tty driver functionality for the
+ * Neo and ClassicBoard PCI based product lines.
+ *
+ ************************************************************************
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/sched.h>       /* For jiffies, task states */
+#include <linux/interrupt.h>   /* For tasklet and interrupt structs/defines */
+#include <linux/module.h>
+#include <linux/ctype.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/types.h>
+#include <linux/serial_reg.h>
+#include <linux/slab.h>
+#include <linux/delay.h>       /* For udelay */
+#include <linux/uaccess.h>     /* For copy_from_user/copy_to_user */
+#include <linux/pci.h>
+#include "dgnc_driver.h"
+#include "dgnc_tty.h"
+#include "dgnc_neo.h"
+#include "dgnc_cls.h"
+#include "dgnc_sysfs.h"
+#include "dgnc_utils.h"
+
+#define init_MUTEX(sem)         sema_init(sem, 1)
+#define DECLARE_MUTEX(name)     \
+       struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)
+
+/*
+ * internal variables
+ */
+static struct dgnc_board       *dgnc_BoardsByMajor[256];
+static unsigned char           *dgnc_TmpWriteBuf;
+static DECLARE_MUTEX(dgnc_TmpWriteSem);
+
+/*
+ * Default transparent print information.
+ */
+static struct digi_t dgnc_digi_init = {
+       .digi_flags =   DIGI_COOK,      /* Flags                        */
+       .digi_maxcps =  100,            /* Max CPS                      */
+       .digi_maxchar = 50,             /* Max chars in print queue     */
+       .digi_bufsize = 100,            /* Printer buffer size          */
+       .digi_onlen =   4,              /* size of printer on string    */
+       .digi_offlen =  4,              /* size of printer off string   */
+       .digi_onstr =   "\033[5i",      /* ANSI printer on string ]     */
+       .digi_offstr =  "\033[4i",      /* ANSI printer off string ]    */
+       .digi_term =    "ansi"          /* default terminal type        */
+};
+
+
+/*
+ * Define a local default termios struct. All ports will be created
+ * with this termios initially.
+ *
+ * This defines a raw port at 9600 baud, 8 data bits, no parity,
+ * 1 stop bit.
+ */
+static struct ktermios DgncDefaultTermios = {
+       .c_iflag =      (DEFAULT_IFLAGS),       /* iflags */
+       .c_oflag =      (DEFAULT_OFLAGS),       /* oflags */
+       .c_cflag =      (DEFAULT_CFLAGS),       /* cflags */
+       .c_lflag =      (DEFAULT_LFLAGS),       /* lflags */
+       .c_cc =         INIT_C_CC,
+       .c_line =       0,
+};
+
+
+/* Our function prototypes */
+static int dgnc_tty_open(struct tty_struct *tty, struct file *file);
+static void dgnc_tty_close(struct tty_struct *tty, struct file *file);
+static int dgnc_block_til_ready(struct tty_struct *tty, struct file *file, struct channel_t *ch);
+static int dgnc_tty_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg);
+static int dgnc_tty_digigeta(struct tty_struct *tty, struct digi_t __user *retinfo);
+static int dgnc_tty_digiseta(struct tty_struct *tty, struct digi_t __user *new_info);
+static int dgnc_tty_write_room(struct tty_struct *tty);
+static int dgnc_tty_put_char(struct tty_struct *tty, unsigned char c);
+static int dgnc_tty_chars_in_buffer(struct tty_struct *tty);
+static void dgnc_tty_start(struct tty_struct *tty);
+static void dgnc_tty_stop(struct tty_struct *tty);
+static void dgnc_tty_throttle(struct tty_struct *tty);
+static void dgnc_tty_unthrottle(struct tty_struct *tty);
+static void dgnc_tty_flush_chars(struct tty_struct *tty);
+static void dgnc_tty_flush_buffer(struct tty_struct *tty);
+static void dgnc_tty_hangup(struct tty_struct *tty);
+static int dgnc_set_modem_info(struct tty_struct *tty, unsigned int command, unsigned int __user *value);
+static int dgnc_get_modem_info(struct channel_t *ch, unsigned int __user *value);
+static int dgnc_tty_tiocmget(struct tty_struct *tty);
+static int dgnc_tty_tiocmset(struct tty_struct *tty, unsigned int set, unsigned int clear);
+static int dgnc_tty_send_break(struct tty_struct *tty, int msec);
+static void dgnc_tty_wait_until_sent(struct tty_struct *tty, int timeout);
+static int dgnc_tty_write(struct tty_struct *tty, const unsigned char *buf, int count);
+static void dgnc_tty_set_termios(struct tty_struct *tty, struct ktermios *old_termios);
+static void dgnc_tty_send_xchar(struct tty_struct *tty, char ch);
+
+
+static const struct tty_operations dgnc_tty_ops = {
+       .open = dgnc_tty_open,
+       .close = dgnc_tty_close,
+       .write = dgnc_tty_write,
+       .write_room = dgnc_tty_write_room,
+       .flush_buffer = dgnc_tty_flush_buffer,
+       .chars_in_buffer = dgnc_tty_chars_in_buffer,
+       .flush_chars = dgnc_tty_flush_chars,
+       .ioctl = dgnc_tty_ioctl,
+       .set_termios = dgnc_tty_set_termios,
+       .stop = dgnc_tty_stop,
+       .start = dgnc_tty_start,
+       .throttle = dgnc_tty_throttle,
+       .unthrottle = dgnc_tty_unthrottle,
+       .hangup = dgnc_tty_hangup,
+       .put_char = dgnc_tty_put_char,
+       .tiocmget = dgnc_tty_tiocmget,
+       .tiocmset = dgnc_tty_tiocmset,
+       .break_ctl = dgnc_tty_send_break,
+       .wait_until_sent = dgnc_tty_wait_until_sent,
+       .send_xchar = dgnc_tty_send_xchar
+};
+
+/************************************************************************
+ *
+ * TTY Initialization/Cleanup Functions
+ *
+ ************************************************************************/
+
+/*
+ * dgnc_tty_preinit()
+ *
+ * Initialize any global tty related data before we download any boards.
+ */
+int dgnc_tty_preinit(void)
+{
+       /*
+        * Allocate a buffer for doing the copy from user space to
+        * kernel space in dgnc_write().  We only use one buffer and
+        * control access to it with a semaphore.  If we are paging, we
+        * are already in trouble so one buffer won't hurt much anyway.
+        *
+        * We are okay to sleep in the malloc, as this routine
+        * is only called during module load, (not in interrupt context),
+        * and with no locks held.
+        */
+       dgnc_TmpWriteBuf = kmalloc(WRITEBUFLEN, GFP_KERNEL);
+
+       if (!dgnc_TmpWriteBuf)
+               return -ENOMEM;
+
+       return 0;
+}
+
+
+/*
+ * dgnc_tty_register()
+ *
+ * Init the tty subsystem for this board.
+ */
+int dgnc_tty_register(struct dgnc_board *brd)
+{
+       int rc = 0;
+
+       brd->SerialDriver.magic = TTY_DRIVER_MAGIC;
+
+       snprintf(brd->SerialName, MAXTTYNAMELEN, "tty_dgnc_%d_", brd->boardnum);
+
+       brd->SerialDriver.name = brd->SerialName;
+       brd->SerialDriver.name_base = 0;
+       brd->SerialDriver.major = 0;
+       brd->SerialDriver.minor_start = 0;
+       brd->SerialDriver.num = brd->maxports;
+       brd->SerialDriver.type = TTY_DRIVER_TYPE_SERIAL;
+       brd->SerialDriver.subtype = SERIAL_TYPE_NORMAL;
+       brd->SerialDriver.init_termios = DgncDefaultTermios;
+       brd->SerialDriver.driver_name = DRVSTR;
+       brd->SerialDriver.flags = (TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV | TTY_DRIVER_HARDWARE_BREAK);
+
+       /*
+        * The kernel wants space to store pointers to
+        * tty_struct's and termios's.
+        */
+       brd->SerialDriver.ttys = kcalloc(brd->maxports, sizeof(*brd->SerialDriver.ttys), GFP_KERNEL);
+       if (!brd->SerialDriver.ttys)
+               return -ENOMEM;
+
+       kref_init(&brd->SerialDriver.kref);
+       brd->SerialDriver.termios = kcalloc(brd->maxports, sizeof(*brd->SerialDriver.termios), GFP_KERNEL);
+       if (!brd->SerialDriver.termios)
+               return -ENOMEM;
+
+       /*
+        * Entry points for driver.  Called by the kernel from
+        * tty_io.c and n_tty.c.
+        */
+       tty_set_operations(&brd->SerialDriver, &dgnc_tty_ops);
+
+       if (!brd->dgnc_Major_Serial_Registered) {
+               /* Register tty devices */
+               rc = tty_register_driver(&brd->SerialDriver);
+               if (rc < 0) {
+                       dev_dbg(&brd->pdev->dev,
+                               "Can't register tty device (%d)\n", rc);
+                       return rc;
+               }
+               brd->dgnc_Major_Serial_Registered = true;
+       }
+
+       /*
+        * If we're doing transparent print, we have to do all of the above
+        * again, separately so we don't get the LD confused about what major
+        * we are when we get into the dgnc_tty_open() routine.
+        */
+       brd->PrintDriver.magic = TTY_DRIVER_MAGIC;
+       snprintf(brd->PrintName, MAXTTYNAMELEN, "pr_dgnc_%d_", brd->boardnum);
+
+       brd->PrintDriver.name = brd->PrintName;
+       brd->PrintDriver.name_base = 0;
+       brd->PrintDriver.major = brd->SerialDriver.major;
+       brd->PrintDriver.minor_start = 0x80;
+       brd->PrintDriver.num = brd->maxports;
+       brd->PrintDriver.type = TTY_DRIVER_TYPE_SERIAL;
+       brd->PrintDriver.subtype = SERIAL_TYPE_NORMAL;
+       brd->PrintDriver.init_termios = DgncDefaultTermios;
+       brd->PrintDriver.driver_name = DRVSTR;
+       brd->PrintDriver.flags = (TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV | TTY_DRIVER_HARDWARE_BREAK);
+
+       /*
+        * The kernel wants space to store pointers to
+        * tty_struct's and termios's.  Must be separated from
+        * the Serial Driver so we don't get confused
+        */
+       brd->PrintDriver.ttys = kcalloc(brd->maxports, sizeof(*brd->PrintDriver.ttys), GFP_KERNEL);
+       if (!brd->PrintDriver.ttys)
+               return -ENOMEM;
+       kref_init(&brd->PrintDriver.kref);
+       brd->PrintDriver.termios = kcalloc(brd->maxports, sizeof(*brd->PrintDriver.termios), GFP_KERNEL);
+       if (!brd->PrintDriver.termios)
+               return -ENOMEM;
+
+       /*
+        * Entry points for driver.  Called by the kernel from
+        * tty_io.c and n_tty.c.
+        */
+       tty_set_operations(&brd->PrintDriver, &dgnc_tty_ops);
+
+       if (!brd->dgnc_Major_TransparentPrint_Registered) {
+               /* Register Transparent Print devices */
+               rc = tty_register_driver(&brd->PrintDriver);
+               if (rc < 0) {
+                       dev_dbg(&brd->pdev->dev,
+                               "Can't register Transparent Print device(%d)\n",
+                               rc);
+                       return rc;
+               }
+               brd->dgnc_Major_TransparentPrint_Registered = true;
+       }
+
+       dgnc_BoardsByMajor[brd->SerialDriver.major] = brd;
+       brd->dgnc_Serial_Major = brd->SerialDriver.major;
+       brd->dgnc_TransparentPrint_Major = brd->PrintDriver.major;
+
+       return rc;
+}
+
+
+/*
+ * dgnc_tty_init()
+ *
+ * Init the tty subsystem.  Called once per board after board has been
+ * downloaded and init'ed.
+ */
+int dgnc_tty_init(struct dgnc_board *brd)
+{
+       int i;
+       void __iomem *vaddr;
+       struct channel_t *ch;
+
+       if (!brd)
+               return -ENXIO;
+
+       /*
+        * Initialize board structure elements.
+        */
+
+       vaddr = brd->re_map_membase;
+
+       brd->nasync = brd->maxports;
+
+       /*
+        * Allocate channel memory that might not have been allocated
+        * when the driver was first loaded.
+        */
+       for (i = 0; i < brd->nasync; i++) {
+               if (!brd->channels[i]) {
+
+                       /*
+                        * Okay to malloc with GFP_KERNEL, we are not at
+                        * interrupt context, and there are no locks held.
+                        */
+                       brd->channels[i] = kzalloc(sizeof(*brd->channels[i]), GFP_KERNEL);
+               }
+       }
+
+       ch = brd->channels[0];
+       vaddr = brd->re_map_membase;
+
+       /* Set up channel variables */
+       for (i = 0; i < brd->nasync; i++, ch = brd->channels[i]) {
+
+               if (!brd->channels[i])
+                       continue;
+
+               spin_lock_init(&ch->ch_lock);
+
+               /* Store all our magic numbers */
+               ch->magic = DGNC_CHANNEL_MAGIC;
+               ch->ch_tun.magic = DGNC_UNIT_MAGIC;
+               ch->ch_tun.un_ch = ch;
+               ch->ch_tun.un_type = DGNC_SERIAL;
+               ch->ch_tun.un_dev = i;
+
+               ch->ch_pun.magic = DGNC_UNIT_MAGIC;
+               ch->ch_pun.un_ch = ch;
+               ch->ch_pun.un_type = DGNC_PRINT;
+               ch->ch_pun.un_dev = i + 128;
+
+               if (brd->bd_uart_offset == 0x200)
+                       ch->ch_neo_uart = vaddr + (brd->bd_uart_offset * i);
+               else
+                       ch->ch_cls_uart = vaddr + (brd->bd_uart_offset * i);
+
+               ch->ch_bd = brd;
+               ch->ch_portnum = i;
+               ch->ch_digi = dgnc_digi_init;
+
+               /* .25 second delay */
+               ch->ch_close_delay = 250;
+
+               init_waitqueue_head(&ch->ch_flags_wait);
+               init_waitqueue_head(&ch->ch_tun.un_flags_wait);
+               init_waitqueue_head(&ch->ch_pun.un_flags_wait);
+
+               {
+                       struct device *classp;
+
+                       classp = tty_register_device(&brd->SerialDriver, i,
+                               &(ch->ch_bd->pdev->dev));
+                       ch->ch_tun.un_sysfs = classp;
+                       dgnc_create_tty_sysfs(&ch->ch_tun, classp);
+
+                       classp = tty_register_device(&brd->PrintDriver, i,
+                               &(ch->ch_bd->pdev->dev));
+                       ch->ch_pun.un_sysfs = classp;
+                       dgnc_create_tty_sysfs(&ch->ch_pun, classp);
+               }
+
+       }
+
+       return 0;
+}
+
+
+/*
+ * dgnc_tty_post_uninit()
+ *
+ * UnInitialize any global tty related data.
+ */
+void dgnc_tty_post_uninit(void)
+{
+       kfree(dgnc_TmpWriteBuf);
+       dgnc_TmpWriteBuf = NULL;
+}
+
+
+/*
+ * dgnc_tty_uninit()
+ *
+ * Uninitialize the TTY portion of this driver.  Free all memory and
+ * resources.
+ */
+void dgnc_tty_uninit(struct dgnc_board *brd)
+{
+       int i = 0;
+
+       if (brd->dgnc_Major_Serial_Registered) {
+               dgnc_BoardsByMajor[brd->SerialDriver.major] = NULL;
+               brd->dgnc_Serial_Major = 0;
+               for (i = 0; i < brd->nasync; i++) {
+                       dgnc_remove_tty_sysfs(brd->channels[i]->ch_tun.un_sysfs);
+                       tty_unregister_device(&brd->SerialDriver, i);
+               }
+               tty_unregister_driver(&brd->SerialDriver);
+               brd->dgnc_Major_Serial_Registered = false;
+       }
+
+       if (brd->dgnc_Major_TransparentPrint_Registered) {
+               dgnc_BoardsByMajor[brd->PrintDriver.major] = NULL;
+               brd->dgnc_TransparentPrint_Major = 0;
+               for (i = 0; i < brd->nasync; i++) {
+                       dgnc_remove_tty_sysfs(brd->channels[i]->ch_pun.un_sysfs);
+                       tty_unregister_device(&brd->PrintDriver, i);
+               }
+               tty_unregister_driver(&brd->PrintDriver);
+               brd->dgnc_Major_TransparentPrint_Registered = false;
+       }
+
+       kfree(brd->SerialDriver.ttys);
+       brd->SerialDriver.ttys = NULL;
+       kfree(brd->PrintDriver.ttys);
+       brd->PrintDriver.ttys = NULL;
+}
+
+
+#define TMPBUFLEN (1024)
+
+/*=======================================================================
+ *
+ *     dgnc_wmove - Write data to transmit queue.
+ *
+ *             ch      - Pointer to channel structure.
+ *             buf     - Poiter to characters to be moved.
+ *             n       - Number of characters to move.
+ *
+ *=======================================================================*/
+static void dgnc_wmove(struct channel_t *ch, char *buf, uint n)
+{
+       int     remain;
+       uint    head;
+
+       if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+               return;
+
+       head = ch->ch_w_head & WQUEUEMASK;
+
+       /*
+        * If the write wraps over the top of the circular buffer,
+        * move the portion up to the wrap point, and reset the
+        * pointers to the bottom.
+        */
+       remain = WQUEUESIZE - head;
+
+       if (n >= remain) {
+               n -= remain;
+               memcpy(ch->ch_wqueue + head, buf, remain);
+               head = 0;
+               buf += remain;
+       }
+
+       if (n > 0) {
+               /*
+                * Move rest of data.
+                */
+               remain = n;
+               memcpy(ch->ch_wqueue + head, buf, remain);
+               head += remain;
+       }
+
+       head &= WQUEUEMASK;
+       ch->ch_w_head = head;
+}
+
+
+
+
+/*=======================================================================
+ *
+ *      dgnc_input - Process received data.
+ *
+ *           ch      - Pointer to channel structure.
+ *
+ *=======================================================================*/
+void dgnc_input(struct channel_t *ch)
+{
+       struct dgnc_board *bd;
+       struct tty_struct *tp;
+       struct tty_ldisc *ld = NULL;
+       uint    rmask;
+       ushort  head;
+       ushort  tail;
+       int     data_len;
+       unsigned long flags;
+       int flip_len;
+       int len = 0;
+       int n = 0;
+       int s = 0;
+       int i = 0;
+
+       if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+               return;
+
+       tp = ch->ch_tun.un_tty;
+
+       bd = ch->ch_bd;
+       if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+               return;
+
+       spin_lock_irqsave(&ch->ch_lock, flags);
+
+       /*
+        *      Figure the number of characters in the buffer.
+        *      Exit immediately if none.
+        */
+       rmask = RQUEUEMASK;
+       head = ch->ch_r_head & rmask;
+       tail = ch->ch_r_tail & rmask;
+       data_len = (head - tail) & rmask;
+
+       if (data_len == 0)
+               goto exit_unlock;
+
+       /*
+        * If the device is not open, or CREAD is off,
+        * flush input data and return immediately.
+        */
+       if (!tp || (tp->magic != TTY_MAGIC) || !(ch->ch_tun.un_flags & UN_ISOPEN) ||
+           !(tp->termios.c_cflag & CREAD) || (ch->ch_tun.un_flags & UN_CLOSING)) {
+
+               ch->ch_r_head = tail;
+
+               /* Force queue flow control to be released, if needed */
+               dgnc_check_queue_flow_control(ch);
+
+               goto exit_unlock;
+       }
+
+       /*
+        * If we are throttled, simply don't read any data.
+        */
+       if (ch->ch_flags & CH_FORCED_STOPI)
+               goto exit_unlock;
+
+       flip_len = TTY_FLIPBUF_SIZE;
+
+       /* Chop down the length, if needed */
+       len = min(data_len, flip_len);
+       len = min(len, (N_TTY_BUF_SIZE - 1));
+
+       ld = tty_ldisc_ref(tp);
+
+#ifdef TTY_DONT_FLIP
+       /*
+        * If the DONT_FLIP flag is on, don't flush our buffer, and act
+        * like the ld doesn't have any space to put the data right now.
+        */
+       if (test_bit(TTY_DONT_FLIP, &tp->flags))
+               len = 0;
+#endif
+
+       /*
+        * If we were unable to get a reference to the ld,
+        * don't flush our buffer, and act like the ld doesn't
+        * have any space to put the data right now.
+        */
+       if (!ld) {
+               len = 0;
+       } else {
+               /*
+                * If ld doesn't have a pointer to a receive_buf function,
+                * flush the data, then act like the ld doesn't have any
+                * space to put the data right now.
+                */
+               if (!ld->ops->receive_buf) {
+                       ch->ch_r_head = ch->ch_r_tail;
+                       len = 0;
+               }
+       }
+
+       if (len <= 0)
+               goto exit_unlock;
+
+       /*
+        * The tty layer in the kernel has changed in 2.6.16+.
+        *
+        * The flip buffers in the tty structure are no longer exposed,
+        * and probably will be going away eventually.
+        *
+        * If we are completely raw, we don't need to go through a lot
+        * of the tty layers that exist.
+        * In this case, we take the shortest and fastest route we
+        * can to relay the data to the user.
+        *
+        * On the other hand, if we are not raw, we need to go through
+        * the new 2.6.16+ tty layer, which has its API more well defined.
+        */
+       len = tty_buffer_request_room(tp->port, len);
+       n = len;
+
+       /*
+        * n now contains the most amount of data we can copy,
+        * bounded either by how much the Linux tty layer can handle,
+        * or the amount of data the card actually has pending...
+        */
+       while (n) {
+               s = ((head >= tail) ? head : RQUEUESIZE) - tail;
+               s = min(s, n);
+
+               if (s <= 0)
+                       break;
+
+               /*
+                * If conditions are such that ld needs to see all
+                * UART errors, we will have to walk each character
+                * and error byte and send them to the buffer one at
+                * a time.
+                */
+               if (I_PARMRK(tp) || I_BRKINT(tp) || I_INPCK(tp)) {
+                       for (i = 0; i < s; i++) {
+                               if (*(ch->ch_equeue + tail + i) & UART_LSR_BI)
+                                       tty_insert_flip_char(tp->port, *(ch->ch_rqueue + tail + i), TTY_BREAK);
+                               else if (*(ch->ch_equeue + tail + i) & UART_LSR_PE)
+                                       tty_insert_flip_char(tp->port, *(ch->ch_rqueue + tail + i), TTY_PARITY);
+                               else if (*(ch->ch_equeue + tail + i) & UART_LSR_FE)
+                                       tty_insert_flip_char(tp->port, *(ch->ch_rqueue + tail + i), TTY_FRAME);
+                               else
+                                       tty_insert_flip_char(tp->port, *(ch->ch_rqueue + tail + i), TTY_NORMAL);
+                       }
+               } else {
+                       tty_insert_flip_string(tp->port, ch->ch_rqueue + tail, s);
+               }
+
+               tail += s;
+               n -= s;
+               /* Flip queue if needed */
+               tail &= rmask;
+       }
+
+       ch->ch_r_tail = tail & rmask;
+       ch->ch_e_tail = tail & rmask;
+       dgnc_check_queue_flow_control(ch);
+       spin_unlock_irqrestore(&ch->ch_lock, flags);
+
+       /* Tell the tty layer its okay to "eat" the data now */
+       tty_flip_buffer_push(tp->port);
+
+       if (ld)
+               tty_ldisc_deref(ld);
+       return;
+
+exit_unlock:
+       spin_unlock_irqrestore(&ch->ch_lock, flags);
+       if (ld)
+               tty_ldisc_deref(ld);
+}
+
+
+/************************************************************************
+ * Determines when CARRIER changes state and takes appropriate
+ * action.
+ ************************************************************************/
+void dgnc_carrier(struct channel_t *ch)
+{
+       struct dgnc_board *bd;
+
+       int virt_carrier = 0;
+       int phys_carrier = 0;
+
+       if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+               return;
+
+       bd = ch->ch_bd;
+
+       if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+               return;
+
+       if (ch->ch_mistat & UART_MSR_DCD)
+               phys_carrier = 1;
+
+       if (ch->ch_digi.digi_flags & DIGI_FORCEDCD)
+               virt_carrier = 1;
+
+       if (ch->ch_c_cflag & CLOCAL)
+               virt_carrier = 1;
+
+       /*
+        * Test for a VIRTUAL carrier transition to HIGH.
+        */
+       if (((ch->ch_flags & CH_FCAR) == 0) && (virt_carrier == 1)) {
+
+               /*
+                * When carrier rises, wake any threads waiting
+                * for carrier in the open routine.
+                */
+
+               if (waitqueue_active(&(ch->ch_flags_wait)))
+                       wake_up_interruptible(&ch->ch_flags_wait);
+       }
+
+       /*
+        * Test for a PHYSICAL carrier transition to HIGH.
+        */
+       if (((ch->ch_flags & CH_CD) == 0) && (phys_carrier == 1)) {
+
+               /*
+                * When carrier rises, wake any threads waiting
+                * for carrier in the open routine.
+                */
+
+               if (waitqueue_active(&(ch->ch_flags_wait)))
+                       wake_up_interruptible(&ch->ch_flags_wait);
+       }
+
+       /*
+        *  Test for a PHYSICAL transition to low, so long as we aren't
+        *  currently ignoring physical transitions (which is what "virtual
+        *  carrier" indicates).
+        *
+        *  The transition of the virtual carrier to low really doesn't
+        *  matter... it really only means "ignore carrier state", not
+        *  "make pretend that carrier is there".
+        */
+       if ((virt_carrier == 0) && ((ch->ch_flags & CH_CD) != 0) &&
+           (phys_carrier == 0)) {
+
+               /*
+                *   When carrier drops:
+                *
+                *   Drop carrier on all open units.
+                *
+                *   Flush queues, waking up any task waiting in the
+                *   line discipline.
+                *
+                *   Send a hangup to the control terminal.
+                *
+                *   Enable all select calls.
+                */
+               if (waitqueue_active(&(ch->ch_flags_wait)))
+                       wake_up_interruptible(&ch->ch_flags_wait);
+
+               if (ch->ch_tun.un_open_count > 0)
+                       tty_hangup(ch->ch_tun.un_tty);
+
+               if (ch->ch_pun.un_open_count > 0)
+                       tty_hangup(ch->ch_pun.un_tty);
+       }
+
+       /*
+        *  Make sure that our cached values reflect the current reality.
+        */
+       if (virt_carrier == 1)
+               ch->ch_flags |= CH_FCAR;
+       else
+               ch->ch_flags &= ~CH_FCAR;
+
+       if (phys_carrier == 1)
+               ch->ch_flags |= CH_CD;
+       else
+               ch->ch_flags &= ~CH_CD;
+}
+
+/*
+ *  Assign the custom baud rate to the channel structure
+ */
+static void dgnc_set_custom_speed(struct channel_t *ch, uint newrate)
+{
+       int testdiv;
+       int testrate_high;
+       int testrate_low;
+       int deltahigh;
+       int deltalow;
+
+       if (newrate <= 0) {
+               ch->ch_custom_speed = 0;
+               return;
+       }
+
+       /*
+        *  Since the divisor is stored in a 16-bit integer, we make sure
+        *  we don't allow any rates smaller than a 16-bit integer would allow.
+        *  And of course, rates above the dividend won't fly.
+        */
+       if (newrate && newrate < ((ch->ch_bd->bd_dividend / 0xFFFF) + 1))
+               newrate = ((ch->ch_bd->bd_dividend / 0xFFFF) + 1);
+
+       if (newrate && newrate > ch->ch_bd->bd_dividend)
+               newrate = ch->ch_bd->bd_dividend;
+
+       if (newrate > 0) {
+               testdiv = ch->ch_bd->bd_dividend / newrate;
+
+               /*
+                *  If we try to figure out what rate the board would use
+                *  with the test divisor, it will be either equal or higher
+                *  than the requested baud rate.  If we then determine the
+                *  rate with a divisor one higher, we will get the next lower
+                *  supported rate below the requested.
+                */
+               testrate_high = ch->ch_bd->bd_dividend / testdiv;
+               testrate_low  = ch->ch_bd->bd_dividend / (testdiv + 1);
+
+               /*
+                *  If the rate for the requested divisor is correct, just
+                *  use it and be done.
+                */
+               if (testrate_high != newrate) {
+                       /*
+                        *  Otherwise, pick the rate that is closer (i.e. whichever rate
+                        *  has a smaller delta).
+                        */
+                       deltahigh = testrate_high - newrate;
+                       deltalow = newrate - testrate_low;
+
+                       if (deltahigh < deltalow)
+                               newrate = testrate_high;
+                       else
+                               newrate = testrate_low;
+               }
+       }
+
+       ch->ch_custom_speed = newrate;
+}
+
+
+void dgnc_check_queue_flow_control(struct channel_t *ch)
+{
+       int qleft = 0;
+
+       /* Store how much space we have left in the queue */
+       qleft = ch->ch_r_tail - ch->ch_r_head - 1;
+       if (qleft < 0)
+               qleft += RQUEUEMASK + 1;
+
+       /*
+        * Check to see if we should enforce flow control on our queue because
+        * the ld (or user) isn't reading data out of our queue fast enuf.
+        *
+        * NOTE: This is done based on what the current flow control of the
+        * port is set for.
+        *
+        * 1) HWFLOW (RTS) - Turn off the UART's Receive interrupt.
+        *      This will cause the UART's FIFO to back up, and force
+        *      the RTS signal to be dropped.
+        * 2) SWFLOW (IXOFF) - Keep trying to send a stop character to
+        *      the other side, in hopes it will stop sending data to us.
+        * 3) NONE - Nothing we can do.  We will simply drop any extra data
+        *      that gets sent into us when the queue fills up.
+        */
+       if (qleft < 256) {
+               /* HWFLOW */
+               if (ch->ch_digi.digi_flags & CTSPACE || ch->ch_c_cflag & CRTSCTS) {
+                       if (!(ch->ch_flags & CH_RECEIVER_OFF)) {
+                               ch->ch_bd->bd_ops->disable_receiver(ch);
+                               ch->ch_flags |= (CH_RECEIVER_OFF);
+                       }
+               }
+               /* SWFLOW */
+               else if (ch->ch_c_iflag & IXOFF) {
+                       if (ch->ch_stops_sent <= MAX_STOPS_SENT) {
+                               ch->ch_bd->bd_ops->send_stop_character(ch);
+                               ch->ch_stops_sent++;
+                       }
+               }
+       }
+
+       /*
+        * Check to see if we should unenforce flow control because
+        * ld (or user) finally read enuf data out of our queue.
+        *
+        * NOTE: This is done based on what the current flow control of the
+        * port is set for.
+        *
+        * 1) HWFLOW (RTS) - Turn back on the UART's Receive interrupt.
+        *      This will cause the UART's FIFO to raise RTS back up,
+        *      which will allow the other side to start sending data again.
+        * 2) SWFLOW (IXOFF) - Send a start character to
+        *      the other side, so it will start sending data to us again.
+        * 3) NONE - Do nothing. Since we didn't do anything to turn off the
+        *      other side, we don't need to do anything now.
+        */
+       if (qleft > (RQUEUESIZE / 2)) {
+               /* HWFLOW */
+               if (ch->ch_digi.digi_flags & RTSPACE || ch->ch_c_cflag & CRTSCTS) {
+                       if (ch->ch_flags & CH_RECEIVER_OFF) {
+                               ch->ch_bd->bd_ops->enable_receiver(ch);
+                               ch->ch_flags &= ~(CH_RECEIVER_OFF);
+                       }
+               }
+               /* SWFLOW */
+               else if (ch->ch_c_iflag & IXOFF && ch->ch_stops_sent) {
+                       ch->ch_stops_sent = 0;
+                       ch->ch_bd->bd_ops->send_start_character(ch);
+               }
+               /* No FLOW */
+               else {
+                       /* Nothing needed. */
+               }
+       }
+}
+
+
+void dgnc_wakeup_writes(struct channel_t *ch)
+{
+       int qlen = 0;
+       unsigned long flags;
+
+       if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+               return;
+
+       spin_lock_irqsave(&ch->ch_lock, flags);
+
+       /*
+        * If channel now has space, wake up anyone waiting on the condition.
+        */
+       qlen = ch->ch_w_head - ch->ch_w_tail;
+       if (qlen < 0)
+               qlen += WQUEUESIZE;
+
+       if (qlen >= (WQUEUESIZE - 256)) {
+               spin_unlock_irqrestore(&ch->ch_lock, flags);
+               return;
+       }
+
+       if (ch->ch_tun.un_flags & UN_ISOPEN) {
+               if ((ch->ch_tun.un_tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
+                       ch->ch_tun.un_tty->ldisc->ops->write_wakeup) {
+                       spin_unlock_irqrestore(&ch->ch_lock, flags);
+                       (ch->ch_tun.un_tty->ldisc->ops->write_wakeup)(ch->ch_tun.un_tty);
+                       spin_lock_irqsave(&ch->ch_lock, flags);
+               }
+
+               wake_up_interruptible(&ch->ch_tun.un_tty->write_wait);
+
+               /*
+                * If unit is set to wait until empty, check to make sure
+                * the queue AND FIFO are both empty.
+                */
+               if (ch->ch_tun.un_flags & UN_EMPTY) {
+                       if ((qlen == 0) && (ch->ch_bd->bd_ops->get_uart_bytes_left(ch) == 0)) {
+                               ch->ch_tun.un_flags &= ~(UN_EMPTY);
+
+                               /*
+                                * If RTS Toggle mode is on, whenever
+                                * the queue and UART is empty, keep RTS low.
+                                */
+                               if (ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE) {
+                                       ch->ch_mostat &= ~(UART_MCR_RTS);
+                                       ch->ch_bd->bd_ops->assert_modem_signals(ch);
+                               }
+
+                               /*
+                                * If DTR Toggle mode is on, whenever
+                                * the queue and UART is empty, keep DTR low.
+                                */
+                               if (ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE) {
+                                       ch->ch_mostat &= ~(UART_MCR_DTR);
+                                       ch->ch_bd->bd_ops->assert_modem_signals(ch);
+                               }
+                       }
+               }
+
+               wake_up_interruptible(&ch->ch_tun.un_flags_wait);
+       }
+
+       if (ch->ch_pun.un_flags & UN_ISOPEN) {
+               if ((ch->ch_pun.un_tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
+                       ch->ch_pun.un_tty->ldisc->ops->write_wakeup) {
+                       spin_unlock_irqrestore(&ch->ch_lock, flags);
+                       (ch->ch_pun.un_tty->ldisc->ops->write_wakeup)(ch->ch_pun.un_tty);
+                       spin_lock_irqsave(&ch->ch_lock, flags);
+               }
+
+               wake_up_interruptible(&ch->ch_pun.un_tty->write_wait);
+
+               /*
+                * If unit is set to wait until empty, check to make sure
+                * the queue AND FIFO are both empty.
+                */
+               if (ch->ch_pun.un_flags & UN_EMPTY) {
+                       if ((qlen == 0) && (ch->ch_bd->bd_ops->get_uart_bytes_left(ch) == 0))
+                               ch->ch_pun.un_flags &= ~(UN_EMPTY);
+               }
+
+               wake_up_interruptible(&ch->ch_pun.un_flags_wait);
+       }
+
+       spin_unlock_irqrestore(&ch->ch_lock, flags);
+}
+
+
+
+/************************************************************************
+ *
+ * TTY Entry points and helper functions
+ *
+ ************************************************************************/
+
+/*
+ * dgnc_tty_open()
+ *
+ */
+static int dgnc_tty_open(struct tty_struct *tty, struct file *file)
+{
+       struct dgnc_board       *brd;
+       struct channel_t *ch;
+       struct un_t     *un;
+       uint            major = 0;
+       uint            minor = 0;
+       int             rc = 0;
+       unsigned long flags;
+
+       rc = 0;
+
+       major = MAJOR(tty_devnum(tty));
+       minor = MINOR(tty_devnum(tty));
+
+       if (major > 255)
+               return -ENXIO;
+
+       /* Get board pointer from our array of majors we have allocated */
+       brd = dgnc_BoardsByMajor[major];
+       if (!brd)
+               return -ENXIO;
+
+       /*
+        * If board is not yet up to a state of READY, go to
+        * sleep waiting for it to happen or they cancel the open.
+        */
+       rc = wait_event_interruptible(brd->state_wait,
+               (brd->state & BOARD_READY));
+
+       if (rc)
+               return rc;
+
+       spin_lock_irqsave(&brd->bd_lock, flags);
+
+       /* If opened device is greater than our number of ports, bail. */
+       if (PORT_NUM(minor) >= brd->nasync) {
+               spin_unlock_irqrestore(&brd->bd_lock, flags);
+               return -ENXIO;
+       }
+
+       ch = brd->channels[PORT_NUM(minor)];
+       if (!ch) {
+               spin_unlock_irqrestore(&brd->bd_lock, flags);
+               return -ENXIO;
+       }
+
+       /* Drop board lock */
+       spin_unlock_irqrestore(&brd->bd_lock, flags);
+
+       /* Grab channel lock */
+       spin_lock_irqsave(&ch->ch_lock, flags);
+
+       /* Figure out our type */
+       if (!IS_PRINT(minor)) {
+               un = &brd->channels[PORT_NUM(minor)]->ch_tun;
+               un->un_type = DGNC_SERIAL;
+       } else if (IS_PRINT(minor)) {
+               un = &brd->channels[PORT_NUM(minor)]->ch_pun;
+               un->un_type = DGNC_PRINT;
+       } else {
+               spin_unlock_irqrestore(&ch->ch_lock, flags);
+               return -ENXIO;
+       }
+
+       /*
+        * If the port is still in a previous open, and in a state
+        * where we simply cannot safely keep going, wait until the
+        * state clears.
+        */
+       spin_unlock_irqrestore(&ch->ch_lock, flags);
+
+       rc = wait_event_interruptible(ch->ch_flags_wait, ((ch->ch_flags & CH_OPENING) == 0));
+
+       /* If ret is non-zero, user ctrl-c'ed us */
+       if (rc)
+               return -EINTR;
+
+       /*
+        * If either unit is in the middle of the fragile part of close,
+        * we just cannot touch the channel safely.
+        * Go to sleep, knowing that when the channel can be
+        * touched safely, the close routine will signal the
+        * ch_flags_wait to wake us back up.
+        */
+       rc = wait_event_interruptible(ch->ch_flags_wait,
+               (((ch->ch_tun.un_flags | ch->ch_pun.un_flags) & UN_CLOSING) == 0));
+
+       /* If ret is non-zero, user ctrl-c'ed us */
+       if (rc)
+               return -EINTR;
+
+       spin_lock_irqsave(&ch->ch_lock, flags);
+
+
+       /* Store our unit into driver_data, so we always have it available. */
+       tty->driver_data = un;
+
+
+       /*
+        * Initialize tty's
+        */
+       if (!(un->un_flags & UN_ISOPEN)) {
+               /* Store important variables. */
+               un->un_tty     = tty;
+
+               /* Maybe do something here to the TTY struct as well? */
+       }
+
+
+       /*
+        * Allocate channel buffers for read/write/error.
+        * Set flag, so we don't get trounced on.
+        */
+       ch->ch_flags |= (CH_OPENING);
+
+       /* Drop locks, as malloc with GFP_KERNEL can sleep */
+       spin_unlock_irqrestore(&ch->ch_lock, flags);
+
+       if (!ch->ch_rqueue)
+               ch->ch_rqueue = kzalloc(RQUEUESIZE, GFP_KERNEL);
+       if (!ch->ch_equeue)
+               ch->ch_equeue = kzalloc(EQUEUESIZE, GFP_KERNEL);
+       if (!ch->ch_wqueue)
+               ch->ch_wqueue = kzalloc(WQUEUESIZE, GFP_KERNEL);
+
+       spin_lock_irqsave(&ch->ch_lock, flags);
+
+       ch->ch_flags &= ~(CH_OPENING);
+       wake_up_interruptible(&ch->ch_flags_wait);
+
+       /*
+        * Initialize if neither terminal or printer is open.
+        */
+       if (!((ch->ch_tun.un_flags | ch->ch_pun.un_flags) & UN_ISOPEN)) {
+
+               /*
+                * Flush input queues.
+                */
+               ch->ch_r_head = 0;
+               ch->ch_r_tail = 0;
+               ch->ch_e_head = 0;
+               ch->ch_e_tail = 0;
+               ch->ch_w_head = 0;
+               ch->ch_w_tail = 0;
+
+               brd->bd_ops->flush_uart_write(ch);
+               brd->bd_ops->flush_uart_read(ch);
+
+               ch->ch_flags = 0;
+               ch->ch_cached_lsr = 0;
+               ch->ch_stop_sending_break = 0;
+               ch->ch_stops_sent = 0;
+
+               ch->ch_c_cflag   = tty->termios.c_cflag;
+               ch->ch_c_iflag   = tty->termios.c_iflag;
+               ch->ch_c_oflag   = tty->termios.c_oflag;
+               ch->ch_c_lflag   = tty->termios.c_lflag;
+               ch->ch_startc = tty->termios.c_cc[VSTART];
+               ch->ch_stopc  = tty->termios.c_cc[VSTOP];
+
+               /*
+                * Bring up RTS and DTR...
+                * Also handle RTS or DTR toggle if set.
+                */
+               if (!(ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE))
+                       ch->ch_mostat |= (UART_MCR_RTS);
+               if (!(ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE))
+                       ch->ch_mostat |= (UART_MCR_DTR);
+
+               /* Tell UART to init itself */
+               brd->bd_ops->uart_init(ch);
+       }
+
+       /*
+        * Run param in case we changed anything
+        */
+       brd->bd_ops->param(tty);
+
+       dgnc_carrier(ch);
+
+       /*
+        * follow protocol for opening port
+        */
+
+       spin_unlock_irqrestore(&ch->ch_lock, flags);
+
+       rc = dgnc_block_til_ready(tty, file, ch);
+
+       /* No going back now, increment our unit and channel counters */
+       spin_lock_irqsave(&ch->ch_lock, flags);
+       ch->ch_open_count++;
+       un->un_open_count++;
+       un->un_flags |= (UN_ISOPEN);
+       spin_unlock_irqrestore(&ch->ch_lock, flags);
+
+       return rc;
+}
+
+
+/*
+ * dgnc_block_til_ready()
+ *
+ * Wait for DCD, if needed.
+ */
+static int dgnc_block_til_ready(struct tty_struct *tty, struct file *file, struct channel_t *ch)
+{
+       int retval = 0;
+       struct un_t *un = NULL;
+       unsigned long flags;
+       uint    old_flags = 0;
+       int     sleep_on_un_flags = 0;
+
+       if (!tty || tty->magic != TTY_MAGIC || !file || !ch || ch->magic != DGNC_CHANNEL_MAGIC)
+               return -ENXIO;
+
+       un = tty->driver_data;
+       if (!un || un->magic != DGNC_UNIT_MAGIC)
+               return -ENXIO;
+
+       spin_lock_irqsave(&ch->ch_lock, flags);
+
+       ch->ch_wopen++;
+
+       /* Loop forever */
+       while (1) {
+
+               sleep_on_un_flags = 0;
+
+               /*
+                * If board has failed somehow during our sleep, bail with error.
+                */
+               if (ch->ch_bd->state == BOARD_FAILED) {
+                       retval = -ENXIO;
+                       break;
+               }
+
+               /* If tty was hung up, break out of loop and set error. */
+               if (tty_hung_up_p(file)) {
+                       retval = -EAGAIN;
+                       break;
+               }
+
+               /*
+                * If either unit is in the middle of the fragile part of close,
+                * we just cannot touch the channel safely.
+                * Go back to sleep, knowing that when the channel can be
+                * touched safely, the close routine will signal the
+                * ch_wait_flags to wake us back up.
+                */
+               if (!((ch->ch_tun.un_flags | ch->ch_pun.un_flags) & UN_CLOSING)) {
+
+                       /*
+                        * Our conditions to leave cleanly and happily:
+                        * 1) NONBLOCKING on the tty is set.
+                        * 2) CLOCAL is set.
+                        * 3) DCD (fake or real) is active.
+                        */
+
+                       if (file->f_flags & O_NONBLOCK)
+                               break;
+
+                       if (tty->flags & (1 << TTY_IO_ERROR)) {
+                               retval = -EIO;
+                               break;
+                       }
+
+                       if (ch->ch_flags & CH_CD)
+                               break;
+
+                       if (ch->ch_flags & CH_FCAR)
+                               break;
+               } else {
+                       sleep_on_un_flags = 1;
+               }
+
+               /*
+                * If there is a signal pending, the user probably
+                * interrupted (ctrl-c) us.
+                * Leave loop with error set.
+                */
+               if (signal_pending(current)) {
+                       retval = -ERESTARTSYS;
+                       break;
+               }
+
+               /*
+                * Store the flags before we let go of channel lock
+                */
+               if (sleep_on_un_flags)
+                       old_flags = ch->ch_tun.un_flags | ch->ch_pun.un_flags;
+               else
+                       old_flags = ch->ch_flags;
+
+               /*
+                * Let go of channel lock before calling schedule.
+                * Our poller will get any FEP events and wake us up when DCD
+                * eventually goes active.
+                */
+
+               spin_unlock_irqrestore(&ch->ch_lock, flags);
+
+               /*
+                * Wait for something in the flags to change from the current value.
+                */
+               if (sleep_on_un_flags)
+                       retval = wait_event_interruptible(un->un_flags_wait,
+                               (old_flags != (ch->ch_tun.un_flags | ch->ch_pun.un_flags)));
+               else
+                       retval = wait_event_interruptible(ch->ch_flags_wait,
+                               (old_flags != ch->ch_flags));
+
+               /*
+                * We got woken up for some reason.
+                * Before looping around, grab our channel lock.
+                */
+               spin_lock_irqsave(&ch->ch_lock, flags);
+       }
+
+       ch->ch_wopen--;
+
+       spin_unlock_irqrestore(&ch->ch_lock, flags);
+
+       if (retval)
+               return retval;
+
+       return 0;
+}
+
+
+/*
+ * dgnc_tty_hangup()
+ *
+ * Hangup the port.  Like a close, but don't wait for output to drain.
+ */
+static void dgnc_tty_hangup(struct tty_struct *tty)
+{
+       struct un_t     *un;
+
+       if (!tty || tty->magic != TTY_MAGIC)
+               return;
+
+       un = tty->driver_data;
+       if (!un || un->magic != DGNC_UNIT_MAGIC)
+               return;
+
+       /* flush the transmit queues */
+       dgnc_tty_flush_buffer(tty);
+
+}
+
+
+/*
+ * dgnc_tty_close()
+ *
+ */
+static void dgnc_tty_close(struct tty_struct *tty, struct file *file)
+{
+       struct ktermios *ts;
+       struct dgnc_board *bd;
+       struct channel_t *ch;
+       struct un_t *un;
+       unsigned long flags;
+       int rc = 0;
+
+       if (!tty || tty->magic != TTY_MAGIC)
+               return;
+
+       un = tty->driver_data;
+       if (!un || un->magic != DGNC_UNIT_MAGIC)
+               return;
+
+       ch = un->un_ch;
+       if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+               return;
+
+       bd = ch->ch_bd;
+       if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+               return;
+
+       ts = &tty->termios;
+
+       spin_lock_irqsave(&ch->ch_lock, flags);
+
+       /*
+        * Determine if this is the last close or not - and if we agree about
+        * which type of close it is with the Line Discipline
+        */
+       if ((tty->count == 1) && (un->un_open_count != 1)) {
+               /*
+                * Uh, oh.  tty->count is 1, which means that the tty
+                * structure will be freed.  un_open_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.
+                */
+               dev_dbg(tty->dev,
+                       "tty->count is 1, un open count is %d\n",
+                       un->un_open_count);
+               un->un_open_count = 1;
+       }
+
+       if (un->un_open_count)
+               un->un_open_count--;
+       else
+               dev_dbg(tty->dev,
+                       "bad serial port open count of %d\n",
+                       un->un_open_count);
+
+       ch->ch_open_count--;
+
+       if (ch->ch_open_count && un->un_open_count) {
+               spin_unlock_irqrestore(&ch->ch_lock, flags);
+               return;
+       }
+
+       /* OK, its the last close on the unit */
+       un->un_flags |= UN_CLOSING;
+
+       tty->closing = 1;
+
+
+       /*
+        * Only officially close channel if count is 0 and
+        * DIGI_PRINTER bit is not set.
+        */
+       if ((ch->ch_open_count == 0) && !(ch->ch_digi.digi_flags & DIGI_PRINTER)) {
+
+               ch->ch_flags &= ~(CH_STOPI | CH_FORCED_STOPI);
+
+               /*
+                * turn off print device when closing print device.
+                */
+               if ((un->un_type == DGNC_PRINT) && (ch->ch_flags & CH_PRON)) {
+                       dgnc_wmove(ch, ch->ch_digi.digi_offstr,
+                               (int) ch->ch_digi.digi_offlen);
+                       ch->ch_flags &= ~CH_PRON;
+               }
+
+               spin_unlock_irqrestore(&ch->ch_lock, flags);
+               /* wait for output to drain */
+               /* This will also return if we take an interrupt */
+
+               rc = bd->bd_ops->drain(tty, 0);
+
+               dgnc_tty_flush_buffer(tty);
+               tty_ldisc_flush(tty);
+
+               spin_lock_irqsave(&ch->ch_lock, flags);
+
+               tty->closing = 0;
+
+               /*
+                * If we have HUPCL set, lower DTR and RTS
+                */
+               if (ch->ch_c_cflag & HUPCL) {
+
+                       /* Drop RTS/DTR */
+                       ch->ch_mostat &= ~(UART_MCR_DTR | UART_MCR_RTS);
+                       bd->bd_ops->assert_modem_signals(ch);
+
+                       /*
+                        * Go to sleep to ensure RTS/DTR
+                        * have been dropped for modems to see it.
+                        */
+                       if (ch->ch_close_delay) {
+                               spin_unlock_irqrestore(&ch->ch_lock,
+                                                      flags);
+                               dgnc_ms_sleep(ch->ch_close_delay);
+                               spin_lock_irqsave(&ch->ch_lock, flags);
+                       }
+               }
+
+               ch->ch_old_baud = 0;
+
+               /* Turn off UART interrupts for this port */
+               ch->ch_bd->bd_ops->uart_off(ch);
+       } else {
+               /*
+                * turn off print device when closing print device.
+                */
+               if ((un->un_type == DGNC_PRINT) && (ch->ch_flags & CH_PRON)) {
+                       dgnc_wmove(ch, ch->ch_digi.digi_offstr,
+                               (int) ch->ch_digi.digi_offlen);
+                       ch->ch_flags &= ~CH_PRON;
+               }
+       }
+
+       un->un_tty = NULL;
+       un->un_flags &= ~(UN_ISOPEN | UN_CLOSING);
+
+       wake_up_interruptible(&ch->ch_flags_wait);
+       wake_up_interruptible(&un->un_flags_wait);
+
+       spin_unlock_irqrestore(&ch->ch_lock, flags);
+}
+
+
+/*
+ * dgnc_tty_chars_in_buffer()
+ *
+ * Return number of characters that have not been transmitted yet.
+ *
+ * This routine is used by the line discipline to determine if there
+ * is data waiting to be transmitted/drained/flushed or not.
+ */
+static int dgnc_tty_chars_in_buffer(struct tty_struct *tty)
+{
+       struct channel_t *ch = NULL;
+       struct un_t *un = NULL;
+       ushort thead;
+       ushort ttail;
+       uint tmask;
+       uint chars = 0;
+       unsigned long flags;
+
+       if (tty == NULL)
+               return 0;
+
+       un = tty->driver_data;
+       if (!un || un->magic != DGNC_UNIT_MAGIC)
+               return 0;
+
+       ch = un->un_ch;
+       if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+               return 0;
+
+       spin_lock_irqsave(&ch->ch_lock, flags);
+
+       tmask = WQUEUEMASK;
+       thead = ch->ch_w_head & tmask;
+       ttail = ch->ch_w_tail & tmask;
+
+       spin_unlock_irqrestore(&ch->ch_lock, flags);
+
+       if (ttail == thead) {
+               chars = 0;
+       } else {
+               if (thead >= ttail)
+                       chars = thead - ttail;
+               else
+                       chars = thead - ttail + WQUEUESIZE;
+       }
+
+       return chars;
+}
+
+
+/*
+ * dgnc_maxcps_room
+ *
+ * Reduces bytes_available to the max number of characters
+ * that can be sent currently given the maxcps value, and
+ * returns the new bytes_available.  This only affects printer
+ * output.
+ */
+static int dgnc_maxcps_room(struct tty_struct *tty, int bytes_available)
+{
+       struct channel_t *ch = NULL;
+       struct un_t *un = NULL;
+
+       if (!tty)
+               return bytes_available;
+
+       un = tty->driver_data;
+       if (!un || un->magic != DGNC_UNIT_MAGIC)
+               return bytes_available;
+
+       ch = un->un_ch;
+       if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+               return bytes_available;
+
+       /*
+        * If its not the Transparent print device, return
+        * the full data amount.
+        */
+       if (un->un_type != DGNC_PRINT)
+               return bytes_available;
+
+       if (ch->ch_digi.digi_maxcps > 0 && ch->ch_digi.digi_bufsize > 0) {
+               int cps_limit = 0;
+               unsigned long current_time = jiffies;
+               unsigned long buffer_time = current_time +
+                       (HZ * ch->ch_digi.digi_bufsize) / ch->ch_digi.digi_maxcps;
+
+               if (ch->ch_cpstime < current_time) {
+                       /* buffer is empty */
+                       ch->ch_cpstime = current_time;      /* reset ch_cpstime */
+                       cps_limit = ch->ch_digi.digi_bufsize;
+               } else if (ch->ch_cpstime < buffer_time) {
+                       /* still room in the buffer */
+                       cps_limit = ((buffer_time - ch->ch_cpstime) * ch->ch_digi.digi_maxcps) / HZ;
+               } else {
+                       /* no room in the buffer */
+                       cps_limit = 0;
+               }
+
+               bytes_available = min(cps_limit, bytes_available);
+       }
+
+       return bytes_available;
+}
+
+
+/*
+ * dgnc_tty_write_room()
+ *
+ * Return space available in Tx buffer
+ */
+static int dgnc_tty_write_room(struct tty_struct *tty)
+{
+       struct channel_t *ch = NULL;
+       struct un_t *un = NULL;
+       ushort head;
+       ushort tail;
+       ushort tmask;
+       int ret = 0;
+       unsigned long flags;
+
+       if (tty == NULL || dgnc_TmpWriteBuf == NULL)
+               return 0;
+
+       un = tty->driver_data;
+       if (!un || un->magic != DGNC_UNIT_MAGIC)
+               return 0;
+
+       ch = un->un_ch;
+       if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+               return 0;
+
+       spin_lock_irqsave(&ch->ch_lock, flags);
+
+       tmask = WQUEUEMASK;
+       head = (ch->ch_w_head) & tmask;
+       tail = (ch->ch_w_tail) & tmask;
+
+       ret = tail - head - 1;
+       if (ret < 0)
+               ret += WQUEUESIZE;
+
+       /* Limit printer to maxcps */
+       ret = dgnc_maxcps_room(tty, ret);
+
+       /*
+        * If we are printer device, leave space for
+        * possibly both the on and off strings.
+        */
+       if (un->un_type == DGNC_PRINT) {
+               if (!(ch->ch_flags & CH_PRON))
+                       ret -= ch->ch_digi.digi_onlen;
+               ret -= ch->ch_digi.digi_offlen;
+       } else {
+               if (ch->ch_flags & CH_PRON)
+                       ret -= ch->ch_digi.digi_offlen;
+       }
+
+       if (ret < 0)
+               ret = 0;
+
+       spin_unlock_irqrestore(&ch->ch_lock, flags);
+
+       return ret;
+}
+
+
+/*
+ * dgnc_tty_put_char()
+ *
+ * Put a character into ch->ch_buf
+ *
+ *      - used by the line discipline for OPOST processing
+ */
+static int dgnc_tty_put_char(struct tty_struct *tty, unsigned char c)
+{
+       /*
+        * Simply call tty_write.
+        */
+       dgnc_tty_write(tty, &c, 1);
+       return 1;
+}
+
+
+/*
+ * dgnc_tty_write()
+ *
+ * Take data from the user or kernel and send it out to the FEP.
+ * In here exists all the Transparent Print magic as well.
+ */
+static int dgnc_tty_write(struct tty_struct *tty,
+               const unsigned char *buf, int count)
+{
+       struct channel_t *ch = NULL;
+       struct un_t *un = NULL;
+       int bufcount = 0, n = 0;
+       int orig_count = 0;
+       unsigned long flags;
+       ushort head;
+       ushort tail;
+       ushort tmask;
+       uint remain;
+       int from_user = 0;
+
+       if (tty == NULL || dgnc_TmpWriteBuf == NULL)
+               return 0;
+
+       un = tty->driver_data;
+       if (!un || un->magic != DGNC_UNIT_MAGIC)
+               return 0;
+
+       ch = un->un_ch;
+       if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+               return 0;
+
+       if (!count)
+               return 0;
+
+       /*
+        * Store original amount of characters passed in.
+        * This helps to figure out if we should ask the FEP
+        * to send us an event when it has more space available.
+        */
+       orig_count = count;
+
+       spin_lock_irqsave(&ch->ch_lock, flags);
+
+       /* Get our space available for the channel from the board */
+       tmask = WQUEUEMASK;
+       head = (ch->ch_w_head) & tmask;
+       tail = (ch->ch_w_tail) & tmask;
+
+       bufcount = tail - head - 1;
+       if (bufcount < 0)
+               bufcount += WQUEUESIZE;
+
+       /*
+        * Limit printer output to maxcps overall, with bursts allowed
+        * up to bufsize characters.
+        */
+       bufcount = dgnc_maxcps_room(tty, bufcount);
+
+       /*
+        * Take minimum of what the user wants to send, and the
+        * space available in the FEP buffer.
+        */
+       count = min(count, bufcount);
+
+       /*
+        * Bail if no space left.
+        */
+       if (count <= 0)
+               goto exit_retry;
+
+       /*
+        * Output the printer ON string, if we are in terminal mode, but
+        * need to be in printer mode.
+        */
+       if ((un->un_type == DGNC_PRINT) && !(ch->ch_flags & CH_PRON)) {
+               dgnc_wmove(ch, ch->ch_digi.digi_onstr,
+                   (int) ch->ch_digi.digi_onlen);
+               head = (ch->ch_w_head) & tmask;
+               ch->ch_flags |= CH_PRON;
+       }
+
+       /*
+        * On the other hand, output the printer OFF string, if we are
+        * currently in printer mode, but need to output to the terminal.
+        */
+       if ((un->un_type != DGNC_PRINT) && (ch->ch_flags & CH_PRON)) {
+               dgnc_wmove(ch, ch->ch_digi.digi_offstr,
+                       (int) ch->ch_digi.digi_offlen);
+               head = (ch->ch_w_head) & tmask;
+               ch->ch_flags &= ~CH_PRON;
+       }
+
+       /*
+        * If there is nothing left to copy, or I can't handle any more data, leave.
+        */
+       if (count <= 0)
+               goto exit_retry;
+
+       if (from_user) {
+
+               count = min(count, WRITEBUFLEN);
+
+               spin_unlock_irqrestore(&ch->ch_lock, flags);
+
+               /*
+                * If data is coming from user space, copy it into a temporary
+                * buffer so we don't get swapped out while doing the copy to
+                * the board.
+                */
+               /* we're allowed to block if it's from_user */
+               if (down_interruptible(&dgnc_TmpWriteSem))
+                       return -EINTR;
+
+               /*
+                * copy_from_user() returns the number
+                * of bytes that could *NOT* be copied.
+                */
+               count -= copy_from_user(dgnc_TmpWriteBuf, (const unsigned char __user *) buf, count);
+
+               if (!count) {
+                       up(&dgnc_TmpWriteSem);
+                       return -EFAULT;
+               }
+
+               spin_lock_irqsave(&ch->ch_lock, flags);
+
+               buf = dgnc_TmpWriteBuf;
+
+       }
+
+       n = count;
+
+       /*
+        * If the write wraps over the top of the circular buffer,
+        * move the portion up to the wrap point, and reset the
+        * pointers to the bottom.
+        */
+       remain = WQUEUESIZE - head;
+
+       if (n >= remain) {
+               n -= remain;
+               memcpy(ch->ch_wqueue + head, buf, remain);
+               head = 0;
+               buf += remain;
+       }
+
+       if (n > 0) {
+               /*
+                * Move rest of data.
+                */
+               remain = n;
+               memcpy(ch->ch_wqueue + head, buf, remain);
+               head += remain;
+       }
+
+       if (count) {
+               head &= tmask;
+               ch->ch_w_head = head;
+       }
+
+       /* Update printer buffer empty time. */
+       if ((un->un_type == DGNC_PRINT) && (ch->ch_digi.digi_maxcps > 0)
+           && (ch->ch_digi.digi_bufsize > 0)) {
+               ch->ch_cpstime += (HZ * count) / ch->ch_digi.digi_maxcps;
+       }
+
+       if (from_user) {
+               spin_unlock_irqrestore(&ch->ch_lock, flags);
+               up(&dgnc_TmpWriteSem);
+       } else {
+               spin_unlock_irqrestore(&ch->ch_lock, flags);
+       }
+
+       if (count) {
+               /*
+                * Channel lock is grabbed and then released
+                * inside this routine.
+                */
+               ch->ch_bd->bd_ops->copy_data_from_queue_to_uart(ch);
+       }
+
+       return count;
+
+exit_retry:
+
+       spin_unlock_irqrestore(&ch->ch_lock, flags);
+       return 0;
+}
+
+
+/*
+ * Return modem signals to ld.
+ */
+
+static int dgnc_tty_tiocmget(struct tty_struct *tty)
+{
+       struct channel_t *ch;
+       struct un_t *un;
+       int result = -EIO;
+       unsigned char mstat = 0;
+       unsigned long flags;
+
+       if (!tty || tty->magic != TTY_MAGIC)
+               return result;
+
+       un = tty->driver_data;
+       if (!un || un->magic != DGNC_UNIT_MAGIC)
+               return result;
+
+       ch = un->un_ch;
+       if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+               return result;
+
+       spin_lock_irqsave(&ch->ch_lock, flags);
+
+       mstat = (ch->ch_mostat | ch->ch_mistat);
+
+       spin_unlock_irqrestore(&ch->ch_lock, flags);
+
+       result = 0;
+
+       if (mstat & UART_MCR_DTR)
+               result |= TIOCM_DTR;
+       if (mstat & UART_MCR_RTS)
+               result |= TIOCM_RTS;
+       if (mstat & UART_MSR_CTS)
+               result |= TIOCM_CTS;
+       if (mstat & UART_MSR_DSR)
+               result |= TIOCM_DSR;
+       if (mstat & UART_MSR_RI)
+               result |= TIOCM_RI;
+       if (mstat & UART_MSR_DCD)
+               result |= TIOCM_CD;
+
+       return result;
+}
+
+
+/*
+ * dgnc_tty_tiocmset()
+ *
+ * Set modem signals, called by ld.
+ */
+
+static int dgnc_tty_tiocmset(struct tty_struct *tty,
+               unsigned int set, unsigned int clear)
+{
+       struct dgnc_board *bd;
+       struct channel_t *ch;
+       struct un_t *un;
+       int ret = -EIO;
+       unsigned long flags;
+
+       if (!tty || tty->magic != TTY_MAGIC)
+               return ret;
+
+       un = tty->driver_data;
+       if (!un || un->magic != DGNC_UNIT_MAGIC)
+               return ret;
+
+       ch = un->un_ch;
+       if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+               return ret;
+
+       bd = ch->ch_bd;
+       if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+               return ret;
+
+       spin_lock_irqsave(&ch->ch_lock, flags);
+
+       if (set & TIOCM_RTS)
+               ch->ch_mostat |= UART_MCR_RTS;
+
+       if (set & TIOCM_DTR)
+               ch->ch_mostat |= UART_MCR_DTR;
+
+       if (clear & TIOCM_RTS)
+               ch->ch_mostat &= ~(UART_MCR_RTS);
+
+       if (clear & TIOCM_DTR)
+               ch->ch_mostat &= ~(UART_MCR_DTR);
+
+       ch->ch_bd->bd_ops->assert_modem_signals(ch);
+
+       spin_unlock_irqrestore(&ch->ch_lock, flags);
+
+       return 0;
+}
+
+
+/*
+ * dgnc_tty_send_break()
+ *
+ * Send a Break, called by ld.
+ */
+static int dgnc_tty_send_break(struct tty_struct *tty, int msec)
+{
+       struct dgnc_board *bd;
+       struct channel_t *ch;
+       struct un_t *un;
+       int ret = -EIO;
+       unsigned long flags;
+
+       if (!tty || tty->magic != TTY_MAGIC)
+               return ret;
+
+       un = tty->driver_data;
+       if (!un || un->magic != DGNC_UNIT_MAGIC)
+               return ret;
+
+       ch = un->un_ch;
+       if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+               return ret;
+
+       bd = ch->ch_bd;
+       if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+               return ret;
+
+       switch (msec) {
+       case -1:
+               msec = 0xFFFF;
+               break;
+       case 0:
+               msec = 0;
+               break;
+       default:
+               break;
+       }
+
+       spin_lock_irqsave(&ch->ch_lock, flags);
+
+       ch->ch_bd->bd_ops->send_break(ch, msec);
+
+       spin_unlock_irqrestore(&ch->ch_lock, flags);
+
+       return 0;
+
+}
+
+
+/*
+ * dgnc_tty_wait_until_sent()
+ *
+ * wait until data has been transmitted, called by ld.
+ */
+static void dgnc_tty_wait_until_sent(struct tty_struct *tty, int timeout)
+{
+       struct dgnc_board *bd;
+       struct channel_t *ch;
+       struct un_t *un;
+       int rc;
+
+       if (!tty || tty->magic != TTY_MAGIC)
+               return;
+
+       un = tty->driver_data;
+       if (!un || un->magic != DGNC_UNIT_MAGIC)
+               return;
+
+       ch = un->un_ch;
+       if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+               return;
+
+       bd = ch->ch_bd;
+       if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+               return;
+
+       rc = bd->bd_ops->drain(tty, 0);
+}
+
+
+/*
+ * dgnc_send_xchar()
+ *
+ * send a high priority character, called by ld.
+ */
+static void dgnc_tty_send_xchar(struct tty_struct *tty, char c)
+{
+       struct dgnc_board *bd;
+       struct channel_t *ch;
+       struct un_t *un;
+       unsigned long flags;
+
+       if (!tty || tty->magic != TTY_MAGIC)
+               return;
+
+       un = tty->driver_data;
+       if (!un || un->magic != DGNC_UNIT_MAGIC)
+               return;
+
+       ch = un->un_ch;
+       if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+               return;
+
+       bd = ch->ch_bd;
+       if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+               return;
+
+       dev_dbg(tty->dev, "dgnc_tty_send_xchar start\n");
+
+       spin_lock_irqsave(&ch->ch_lock, flags);
+       bd->bd_ops->send_immediate_char(ch, c);
+       spin_unlock_irqrestore(&ch->ch_lock, flags);
+
+       dev_dbg(tty->dev, "dgnc_tty_send_xchar finish\n");
+}
+
+
+
+
+/*
+ * Return modem signals to ld.
+ */
+static inline int dgnc_get_mstat(struct channel_t *ch)
+{
+       unsigned char mstat;
+       int result = -EIO;
+       unsigned long flags;
+
+       if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+               return -ENXIO;
+
+       spin_lock_irqsave(&ch->ch_lock, flags);
+
+       mstat = (ch->ch_mostat | ch->ch_mistat);
+
+       spin_unlock_irqrestore(&ch->ch_lock, flags);
+
+       result = 0;
+
+       if (mstat & UART_MCR_DTR)
+               result |= TIOCM_DTR;
+       if (mstat & UART_MCR_RTS)
+               result |= TIOCM_RTS;
+       if (mstat & UART_MSR_CTS)
+               result |= TIOCM_CTS;
+       if (mstat & UART_MSR_DSR)
+               result |= TIOCM_DSR;
+       if (mstat & UART_MSR_RI)
+               result |= TIOCM_RI;
+       if (mstat & UART_MSR_DCD)
+               result |= TIOCM_CD;
+
+       return result;
+}
+
+
+
+/*
+ * Return modem signals to ld.
+ */
+static int dgnc_get_modem_info(struct channel_t *ch, unsigned int  __user *value)
+{
+       int result;
+
+       if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+               return -ENXIO;
+
+       result = dgnc_get_mstat(ch);
+
+       if (result < 0)
+               return -ENXIO;
+
+       return put_user(result, value);
+}
+
+
+/*
+ * dgnc_set_modem_info()
+ *
+ * Set modem signals, called by ld.
+ */
+static int dgnc_set_modem_info(struct tty_struct *tty, unsigned int command, unsigned int __user *value)
+{
+       struct dgnc_board *bd;
+       struct channel_t *ch;
+       struct un_t *un;
+       int ret = -ENXIO;
+       unsigned int arg = 0;
+       unsigned long flags;
+
+       if (!tty || tty->magic != TTY_MAGIC)
+               return ret;
+
+       un = tty->driver_data;
+       if (!un || un->magic != DGNC_UNIT_MAGIC)
+               return ret;
+
+       ch = un->un_ch;
+       if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+               return ret;
+
+       bd = ch->ch_bd;
+       if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+               return ret;
+
+       ret = get_user(arg, value);
+       if (ret)
+               return ret;
+
+       switch (command) {
+       case TIOCMBIS:
+               if (arg & TIOCM_RTS)
+                       ch->ch_mostat |= UART_MCR_RTS;
+
+               if (arg & TIOCM_DTR)
+                       ch->ch_mostat |= UART_MCR_DTR;
+
+               break;
+
+       case TIOCMBIC:
+               if (arg & TIOCM_RTS)
+                       ch->ch_mostat &= ~(UART_MCR_RTS);
+
+               if (arg & TIOCM_DTR)
+                       ch->ch_mostat &= ~(UART_MCR_DTR);
+
+               break;
+
+       case TIOCMSET:
+
+               if (arg & TIOCM_RTS)
+                       ch->ch_mostat |= UART_MCR_RTS;
+               else
+                       ch->ch_mostat &= ~(UART_MCR_RTS);
+
+               if (arg & TIOCM_DTR)
+                       ch->ch_mostat |= UART_MCR_DTR;
+               else
+                       ch->ch_mostat &= ~(UART_MCR_DTR);
+
+               break;
+
+       default:
+               return -EINVAL;
+       }
+
+       spin_lock_irqsave(&ch->ch_lock, flags);
+
+       ch->ch_bd->bd_ops->assert_modem_signals(ch);
+
+       spin_unlock_irqrestore(&ch->ch_lock, flags);
+
+       return 0;
+}
+
+
+/*
+ * dgnc_tty_digigeta()
+ *
+ * Ioctl to get the information for ditty.
+ *
+ *
+ *
+ */
+static int dgnc_tty_digigeta(struct tty_struct *tty, struct digi_t __user *retinfo)
+{
+       struct channel_t *ch;
+       struct un_t *un;
+       struct digi_t tmp;
+       unsigned long flags;
+
+       if (!retinfo)
+               return -EFAULT;
+
+       if (!tty || tty->magic != TTY_MAGIC)
+               return -EFAULT;
+
+       un = tty->driver_data;
+       if (!un || un->magic != DGNC_UNIT_MAGIC)
+               return -EFAULT;
+
+       ch = un->un_ch;
+       if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+               return -EFAULT;
+
+       memset(&tmp, 0, sizeof(tmp));
+
+       spin_lock_irqsave(&ch->ch_lock, flags);
+       memcpy(&tmp, &ch->ch_digi, sizeof(tmp));
+       spin_unlock_irqrestore(&ch->ch_lock, flags);
+
+       if (copy_to_user(retinfo, &tmp, sizeof(*retinfo)))
+               return -EFAULT;
+
+       return 0;
+}
+
+
+/*
+ * dgnc_tty_digiseta()
+ *
+ * Ioctl to set the information for ditty.
+ *
+ *
+ *
+ */
+static int dgnc_tty_digiseta(struct tty_struct *tty, struct digi_t __user *new_info)
+{
+       struct dgnc_board *bd;
+       struct channel_t *ch;
+       struct un_t *un;
+       struct digi_t new_digi;
+       unsigned long flags;
+
+       if (!tty || tty->magic != TTY_MAGIC)
+               return -EFAULT;
+
+       un = tty->driver_data;
+       if (!un || un->magic != DGNC_UNIT_MAGIC)
+               return -EFAULT;
+
+       ch = un->un_ch;
+       if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+               return -EFAULT;
+
+       bd = ch->ch_bd;
+       if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+               return -EFAULT;
+
+       if (copy_from_user(&new_digi, new_info, sizeof(new_digi)))
+               return -EFAULT;
+
+       spin_lock_irqsave(&ch->ch_lock, flags);
+
+       /*
+        * Handle transistions to and from RTS Toggle.
+        */
+       if (!(ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE) && (new_digi.digi_flags & DIGI_RTS_TOGGLE))
+               ch->ch_mostat &= ~(UART_MCR_RTS);
+       if ((ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE) && !(new_digi.digi_flags & DIGI_RTS_TOGGLE))
+               ch->ch_mostat |= (UART_MCR_RTS);
+
+       /*
+        * Handle transistions to and from DTR Toggle.
+        */
+       if (!(ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE) && (new_digi.digi_flags & DIGI_DTR_TOGGLE))
+               ch->ch_mostat &= ~(UART_MCR_DTR);
+       if ((ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE) && !(new_digi.digi_flags & DIGI_DTR_TOGGLE))
+               ch->ch_mostat |= (UART_MCR_DTR);
+
+       memcpy(&ch->ch_digi, &new_digi, sizeof(new_digi));
+
+       if (ch->ch_digi.digi_maxcps < 1)
+               ch->ch_digi.digi_maxcps = 1;
+
+       if (ch->ch_digi.digi_maxcps > 10000)
+               ch->ch_digi.digi_maxcps = 10000;
+
+       if (ch->ch_digi.digi_bufsize < 10)
+               ch->ch_digi.digi_bufsize = 10;
+
+       if (ch->ch_digi.digi_maxchar < 1)
+               ch->ch_digi.digi_maxchar = 1;
+
+       if (ch->ch_digi.digi_maxchar > ch->ch_digi.digi_bufsize)
+               ch->ch_digi.digi_maxchar = ch->ch_digi.digi_bufsize;
+
+       if (ch->ch_digi.digi_onlen > DIGI_PLEN)
+               ch->ch_digi.digi_onlen = DIGI_PLEN;
+
+       if (ch->ch_digi.digi_offlen > DIGI_PLEN)
+               ch->ch_digi.digi_offlen = DIGI_PLEN;
+
+       ch->ch_bd->bd_ops->param(tty);
+
+       spin_unlock_irqrestore(&ch->ch_lock, flags);
+
+       return 0;
+}
+
+
+/*
+ * dgnc_set_termios()
+ */
+static void dgnc_tty_set_termios(struct tty_struct *tty, struct ktermios *old_termios)
+{
+       struct dgnc_board *bd;
+       struct channel_t *ch;
+       struct un_t *un;
+       unsigned long flags;
+
+       if (!tty || tty->magic != TTY_MAGIC)
+               return;
+
+       un = tty->driver_data;
+       if (!un || un->magic != DGNC_UNIT_MAGIC)
+               return;
+
+       ch = un->un_ch;
+       if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+               return;
+
+       bd = ch->ch_bd;
+       if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+               return;
+
+       spin_lock_irqsave(&ch->ch_lock, flags);
+
+       ch->ch_c_cflag   = tty->termios.c_cflag;
+       ch->ch_c_iflag   = tty->termios.c_iflag;
+       ch->ch_c_oflag   = tty->termios.c_oflag;
+       ch->ch_c_lflag   = tty->termios.c_lflag;
+       ch->ch_startc = tty->termios.c_cc[VSTART];
+       ch->ch_stopc  = tty->termios.c_cc[VSTOP];
+
+       ch->ch_bd->bd_ops->param(tty);
+       dgnc_carrier(ch);
+
+       spin_unlock_irqrestore(&ch->ch_lock, flags);
+}
+
+
+static void dgnc_tty_throttle(struct tty_struct *tty)
+{
+       struct channel_t *ch;
+       struct un_t *un;
+       unsigned long flags;
+
+       if (!tty || tty->magic != TTY_MAGIC)
+               return;
+
+       un = tty->driver_data;
+       if (!un || un->magic != DGNC_UNIT_MAGIC)
+               return;
+
+       ch = un->un_ch;
+       if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+               return;
+
+       spin_lock_irqsave(&ch->ch_lock, flags);
+
+       ch->ch_flags |= (CH_FORCED_STOPI);
+
+       spin_unlock_irqrestore(&ch->ch_lock, flags);
+}
+
+
+static void dgnc_tty_unthrottle(struct tty_struct *tty)
+{
+       struct channel_t *ch;
+       struct un_t *un;
+       unsigned long flags;
+
+       if (!tty || tty->magic != TTY_MAGIC)
+               return;
+
+       un = tty->driver_data;
+       if (!un || un->magic != DGNC_UNIT_MAGIC)
+               return;
+
+       ch = un->un_ch;
+       if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+               return;
+
+       spin_lock_irqsave(&ch->ch_lock, flags);
+
+       ch->ch_flags &= ~(CH_FORCED_STOPI);
+
+       spin_unlock_irqrestore(&ch->ch_lock, flags);
+}
+
+
+static void dgnc_tty_start(struct tty_struct *tty)
+{
+       struct dgnc_board *bd;
+       struct channel_t *ch;
+       struct un_t *un;
+       unsigned long flags;
+
+       if (!tty || tty->magic != TTY_MAGIC)
+               return;
+
+       un = tty->driver_data;
+       if (!un || un->magic != DGNC_UNIT_MAGIC)
+               return;
+
+       ch = un->un_ch;
+       if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+               return;
+
+       bd = ch->ch_bd;
+       if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+               return;
+
+       spin_lock_irqsave(&ch->ch_lock, flags);
+
+       ch->ch_flags &= ~(CH_FORCED_STOP);
+
+       spin_unlock_irqrestore(&ch->ch_lock, flags);
+}
+
+
+static void dgnc_tty_stop(struct tty_struct *tty)
+{
+       struct dgnc_board *bd;
+       struct channel_t *ch;
+       struct un_t *un;
+       unsigned long flags;
+
+       if (!tty || tty->magic != TTY_MAGIC)
+               return;
+
+       un = tty->driver_data;
+       if (!un || un->magic != DGNC_UNIT_MAGIC)
+               return;
+
+       ch = un->un_ch;
+       if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+               return;
+
+       bd = ch->ch_bd;
+       if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+               return;
+
+       spin_lock_irqsave(&ch->ch_lock, flags);
+
+       ch->ch_flags |= (CH_FORCED_STOP);
+
+       spin_unlock_irqrestore(&ch->ch_lock, flags);
+}
+
+
+/*
+ * dgnc_tty_flush_chars()
+ *
+ * Flush the cook buffer
+ *
+ * Note to self, and any other poor souls who venture here:
+ *
+ * flush in this case DOES NOT mean dispose of the data.
+ * instead, it means "stop buffering and send it if you
+ * haven't already."  Just guess how I figured that out...   SRW 2-Jun-98
+ *
+ * It is also always called in interrupt context - JAR 8-Sept-99
+ */
+static void dgnc_tty_flush_chars(struct tty_struct *tty)
+{
+       struct dgnc_board *bd;
+       struct channel_t *ch;
+       struct un_t *un;
+       unsigned long flags;
+
+       if (!tty || tty->magic != TTY_MAGIC)
+               return;
+
+       un = tty->driver_data;
+       if (!un || un->magic != DGNC_UNIT_MAGIC)
+               return;
+
+       ch = un->un_ch;
+       if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+               return;
+
+       bd = ch->ch_bd;
+       if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+               return;
+
+       spin_lock_irqsave(&ch->ch_lock, flags);
+
+       /* Do something maybe here */
+
+       spin_unlock_irqrestore(&ch->ch_lock, flags);
+}
+
+
+
+/*
+ * dgnc_tty_flush_buffer()
+ *
+ * Flush Tx buffer (make in == out)
+ */
+static void dgnc_tty_flush_buffer(struct tty_struct *tty)
+{
+       struct channel_t *ch;
+       struct un_t *un;
+       unsigned long flags;
+
+       if (!tty || tty->magic != TTY_MAGIC)
+               return;
+
+       un = tty->driver_data;
+       if (!un || un->magic != DGNC_UNIT_MAGIC)
+               return;
+
+       ch = un->un_ch;
+       if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+               return;
+
+       spin_lock_irqsave(&ch->ch_lock, flags);
+
+       ch->ch_flags &= ~CH_STOP;
+
+       /* Flush our write queue */
+       ch->ch_w_head = ch->ch_w_tail;
+
+       /* Flush UARTs transmit FIFO */
+       ch->ch_bd->bd_ops->flush_uart_write(ch);
+
+       if (ch->ch_tun.un_flags & (UN_LOW|UN_EMPTY)) {
+               ch->ch_tun.un_flags &= ~(UN_LOW|UN_EMPTY);
+               wake_up_interruptible(&ch->ch_tun.un_flags_wait);
+       }
+       if (ch->ch_pun.un_flags & (UN_LOW|UN_EMPTY)) {
+               ch->ch_pun.un_flags &= ~(UN_LOW|UN_EMPTY);
+               wake_up_interruptible(&ch->ch_pun.un_flags_wait);
+       }
+
+       spin_unlock_irqrestore(&ch->ch_lock, flags);
+}
+
+
+
+/*****************************************************************************
+ *
+ * The IOCTL function and all of its helpers
+ *
+ *****************************************************************************/
+
+/*
+ * dgnc_tty_ioctl()
+ *
+ * The usual assortment of ioctl's
+ */
+static int dgnc_tty_ioctl(struct tty_struct *tty, unsigned int cmd,
+               unsigned long arg)
+{
+       struct dgnc_board *bd;
+       struct channel_t *ch;
+       struct un_t *un;
+       int rc;
+       unsigned long flags;
+       void __user *uarg = (void __user *) arg;
+
+       if (!tty || tty->magic != TTY_MAGIC)
+               return -ENODEV;
+
+       un = tty->driver_data;
+       if (!un || un->magic != DGNC_UNIT_MAGIC)
+               return -ENODEV;
+
+       ch = un->un_ch;
+       if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+               return -ENODEV;
+
+       bd = ch->ch_bd;
+       if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+               return -ENODEV;
+
+       spin_lock_irqsave(&ch->ch_lock, flags);
+
+       if (un->un_open_count <= 0) {
+               spin_unlock_irqrestore(&ch->ch_lock, flags);
+               return -EIO;
+       }
+
+       switch (cmd) {
+
+       /* Here are all the standard ioctl's that we MUST implement */
+
+       case TCSBRK:
+               /*
+                * TCSBRK is SVID version: non-zero arg --> no break
+                * this behaviour is exploited by tcdrain().
+                *
+                * According to POSIX.1 spec (7.2.2.1.2) breaks should be
+                * between 0.25 and 0.5 seconds so we'll ask for something
+                * in the middle: 0.375 seconds.
+                */
+               rc = tty_check_change(tty);
+               spin_unlock_irqrestore(&ch->ch_lock, flags);
+               if (rc)
+                       return rc;
+
+               rc = ch->ch_bd->bd_ops->drain(tty, 0);
+
+               if (rc)
+                       return -EINTR;
+
+               spin_lock_irqsave(&ch->ch_lock, flags);
+
+               if (((cmd == TCSBRK) && (!arg)) || (cmd == TCSBRKP))
+                       ch->ch_bd->bd_ops->send_break(ch, 250);
+
+               spin_unlock_irqrestore(&ch->ch_lock, flags);
+
+               return 0;
+
+
+       case TCSBRKP:
+               /* support for POSIX tcsendbreak()
+                * According to POSIX.1 spec (7.2.2.1.2) breaks should be
+                * between 0.25 and 0.5 seconds so we'll ask for something
+                * in the middle: 0.375 seconds.
+                */
+               rc = tty_check_change(tty);
+               spin_unlock_irqrestore(&ch->ch_lock, flags);
+               if (rc)
+                       return rc;
+
+               rc = ch->ch_bd->bd_ops->drain(tty, 0);
+               if (rc)
+                       return -EINTR;
+
+               spin_lock_irqsave(&ch->ch_lock, flags);
+
+               ch->ch_bd->bd_ops->send_break(ch, 250);
+
+               spin_unlock_irqrestore(&ch->ch_lock, flags);
+
+               return 0;
+
+       case TIOCSBRK:
+               rc = tty_check_change(tty);
+               spin_unlock_irqrestore(&ch->ch_lock, flags);
+               if (rc)
+                       return rc;
+
+               rc = ch->ch_bd->bd_ops->drain(tty, 0);
+               if (rc)
+                       return -EINTR;
+
+               spin_lock_irqsave(&ch->ch_lock, flags);
+
+               ch->ch_bd->bd_ops->send_break(ch, 250);
+
+               spin_unlock_irqrestore(&ch->ch_lock, flags);
+
+               return 0;
+
+       case TIOCCBRK:
+               /* Do Nothing */
+               spin_unlock_irqrestore(&ch->ch_lock, flags);
+               return 0;
+
+       case TIOCGSOFTCAR:
+
+               spin_unlock_irqrestore(&ch->ch_lock, flags);
+
+               rc = put_user(C_CLOCAL(tty) ? 1 : 0, (unsigned long __user *) arg);
+               return rc;
+
+       case TIOCSSOFTCAR:
+
+               spin_unlock_irqrestore(&ch->ch_lock, flags);
+               rc = get_user(arg, (unsigned long __user *) arg);
+               if (rc)
+                       return rc;
+
+               spin_lock_irqsave(&ch->ch_lock, flags);
+               tty->termios.c_cflag = ((tty->termios.c_cflag & ~CLOCAL) | (arg ? CLOCAL : 0));
+               ch->ch_bd->bd_ops->param(tty);
+               spin_unlock_irqrestore(&ch->ch_lock, flags);
+
+               return 0;
+
+       case TIOCMGET:
+               spin_unlock_irqrestore(&ch->ch_lock, flags);
+               return dgnc_get_modem_info(ch, uarg);
+
+       case TIOCMBIS:
+       case TIOCMBIC:
+       case TIOCMSET:
+               spin_unlock_irqrestore(&ch->ch_lock, flags);
+               return dgnc_set_modem_info(tty, cmd, uarg);
+
+               /*
+                * Here are any additional ioctl's that we want to implement
+                */
+
+       case TCFLSH:
+               /*
+                * The linux tty driver doesn't have a flush
+                * input routine for the driver, assuming all backed
+                * up data is in the line disc. buffers.  However,
+                * we all know that's not the case.  Here, we
+                * act on the ioctl, but then lie and say we didn't
+                * so the line discipline will process the flush
+                * also.
+                */
+               rc = tty_check_change(tty);
+               if (rc) {
+                       spin_unlock_irqrestore(&ch->ch_lock, flags);
+                       return rc;
+               }
+
+               if ((arg == TCIFLUSH) || (arg == TCIOFLUSH)) {
+                       ch->ch_r_head = ch->ch_r_tail;
+                       ch->ch_bd->bd_ops->flush_uart_read(ch);
+                       /* Force queue flow control to be released, if needed */
+                       dgnc_check_queue_flow_control(ch);
+               }
+
+               if ((arg == TCOFLUSH) || (arg == TCIOFLUSH)) {
+                       if (!(un->un_type == DGNC_PRINT)) {
+                               ch->ch_w_head = ch->ch_w_tail;
+                               ch->ch_bd->bd_ops->flush_uart_write(ch);
+
+                               if (ch->ch_tun.un_flags & (UN_LOW|UN_EMPTY)) {
+                                       ch->ch_tun.un_flags &= ~(UN_LOW|UN_EMPTY);
+                                       wake_up_interruptible(&ch->ch_tun.un_flags_wait);
+                               }
+
+                               if (ch->ch_pun.un_flags & (UN_LOW|UN_EMPTY)) {
+                                       ch->ch_pun.un_flags &= ~(UN_LOW|UN_EMPTY);
+                                       wake_up_interruptible(&ch->ch_pun.un_flags_wait);
+                               }
+
+                       }
+               }
+
+               /* pretend we didn't recognize this IOCTL */
+               spin_unlock_irqrestore(&ch->ch_lock, flags);
+               return -ENOIOCTLCMD;
+       case TCSETSF:
+       case TCSETSW:
+               /*
+                * The linux tty driver doesn't have a flush
+                * input routine for the driver, assuming all backed
+                * up data is in the line disc. buffers.  However,
+                * we all know that's not the case.  Here, we
+                * act on the ioctl, but then lie and say we didn't
+                * so the line discipline will process the flush
+                * also.
+                */
+               if (cmd == TCSETSF) {
+                       /* flush rx */
+                       ch->ch_flags &= ~CH_STOP;
+                       ch->ch_r_head = ch->ch_r_tail;
+                       ch->ch_bd->bd_ops->flush_uart_read(ch);
+                       /* Force queue flow control to be released, if needed */
+                       dgnc_check_queue_flow_control(ch);
+               }
+
+               /* now wait for all the output to drain */
+               spin_unlock_irqrestore(&ch->ch_lock, flags);
+               rc = ch->ch_bd->bd_ops->drain(tty, 0);
+               if (rc)
+                       return -EINTR;
+
+               /* pretend we didn't recognize this */
+               return -ENOIOCTLCMD;
+
+       case TCSETAW:
+
+               spin_unlock_irqrestore(&ch->ch_lock, flags);
+               rc = ch->ch_bd->bd_ops->drain(tty, 0);
+               if (rc)
+                       return -EINTR;
+
+               /* pretend we didn't recognize this */
+               return -ENOIOCTLCMD;
+
+       case TCXONC:
+               spin_unlock_irqrestore(&ch->ch_lock, flags);
+               /* Make the ld do it */
+               return -ENOIOCTLCMD;
+
+       case DIGI_GETA:
+               /* get information for ditty */
+               spin_unlock_irqrestore(&ch->ch_lock, flags);
+               return dgnc_tty_digigeta(tty, uarg);
+
+       case DIGI_SETAW:
+       case DIGI_SETAF:
+
+               /* set information for ditty */
+               if (cmd == (DIGI_SETAW)) {
+
+                       spin_unlock_irqrestore(&ch->ch_lock, flags);
+                       rc = ch->ch_bd->bd_ops->drain(tty, 0);
+
+                       if (rc)
+                               return -EINTR;
+
+                       spin_lock_irqsave(&ch->ch_lock, flags);
+               } else {
+                       tty_ldisc_flush(tty);
+               }
+               /* fall thru */
+
+       case DIGI_SETA:
+               spin_unlock_irqrestore(&ch->ch_lock, flags);
+               return dgnc_tty_digiseta(tty, uarg);
+
+       case DIGI_LOOPBACK:
+               {
+                       uint loopback = 0;
+                       /* Let go of locks when accessing user space, could sleep */
+                       spin_unlock_irqrestore(&ch->ch_lock, flags);
+                       rc = get_user(loopback, (unsigned int __user *) arg);
+                       if (rc)
+                               return rc;
+                       spin_lock_irqsave(&ch->ch_lock, flags);
+
+                       /* Enable/disable internal loopback for this port */
+                       if (loopback)
+                               ch->ch_flags |= CH_LOOPBACK;
+                       else
+                               ch->ch_flags &= ~(CH_LOOPBACK);
+
+                       ch->ch_bd->bd_ops->param(tty);
+                       spin_unlock_irqrestore(&ch->ch_lock, flags);
+                       return 0;
+               }
+
+       case DIGI_GETCUSTOMBAUD:
+               spin_unlock_irqrestore(&ch->ch_lock, flags);
+               rc = put_user(ch->ch_custom_speed, (unsigned int __user *) arg);
+               return rc;
+
+       case DIGI_SETCUSTOMBAUD:
+       {
+               int new_rate;
+               /* Let go of locks when accessing user space, could sleep */
+               spin_unlock_irqrestore(&ch->ch_lock, flags);
+               rc = get_user(new_rate, (int __user *) arg);
+               if (rc)
+                       return rc;
+               spin_lock_irqsave(&ch->ch_lock, flags);
+               dgnc_set_custom_speed(ch, new_rate);
+               ch->ch_bd->bd_ops->param(tty);
+               spin_unlock_irqrestore(&ch->ch_lock, flags);
+               return 0;
+       }
+
+       /*
+        * This ioctl allows insertion of a character into the front
+        * of any pending data to be transmitted.
+        *
+        * This ioctl is to satify the "Send Character Immediate"
+        * call that the RealPort protocol spec requires.
+        */
+       case DIGI_REALPORT_SENDIMMEDIATE:
+       {
+               unsigned char c;
+
+               spin_unlock_irqrestore(&ch->ch_lock, flags);
+               rc = get_user(c, (unsigned char __user *) arg);
+               if (rc)
+                       return rc;
+               spin_lock_irqsave(&ch->ch_lock, flags);
+               ch->ch_bd->bd_ops->send_immediate_char(ch, c);
+               spin_unlock_irqrestore(&ch->ch_lock, flags);
+               return 0;
+       }
+
+       /*
+        * This ioctl returns all the current counts for the port.
+        *
+        * This ioctl is to satify the "Line Error Counters"
+        * call that the RealPort protocol spec requires.
+        */
+       case DIGI_REALPORT_GETCOUNTERS:
+       {
+               struct digi_getcounter buf;
+
+               buf.norun = ch->ch_err_overrun;
+               buf.noflow = 0;         /* The driver doesn't keep this stat */
+               buf.nframe = ch->ch_err_frame;
+               buf.nparity = ch->ch_err_parity;
+               buf.nbreak = ch->ch_err_break;
+               buf.rbytes = ch->ch_rxcount;
+               buf.tbytes = ch->ch_txcount;
+
+               spin_unlock_irqrestore(&ch->ch_lock, flags);
+
+               if (copy_to_user(uarg, &buf, sizeof(buf)))
+                       return -EFAULT;
+
+               return 0;
+       }
+
+       /*
+        * This ioctl returns all current events.
+        *
+        * This ioctl is to satify the "Event Reporting"
+        * call that the RealPort protocol spec requires.
+        */
+       case DIGI_REALPORT_GETEVENTS:
+       {
+               unsigned int events = 0;
+
+               /* NOTE: MORE EVENTS NEEDS TO BE ADDED HERE */
+               if (ch->ch_flags & CH_BREAK_SENDING)
+                       events |= EV_TXB;
+               if ((ch->ch_flags & CH_STOP) || (ch->ch_flags & CH_FORCED_STOP))
+                       events |= (EV_OPU | EV_OPS);
+
+               if ((ch->ch_flags & CH_STOPI) || (ch->ch_flags & CH_FORCED_STOPI))
+                       events |= (EV_IPU | EV_IPS);
+
+               spin_unlock_irqrestore(&ch->ch_lock, flags);
+               rc = put_user(events, (unsigned int __user *) arg);
+               return rc;
+       }
+
+       /*
+        * This ioctl returns TOUT and TIN counters based
+        * upon the values passed in by the RealPort Server.
+        * It also passes back whether the UART Transmitter is
+        * empty as well.
+        */
+       case DIGI_REALPORT_GETBUFFERS:
+       {
+               struct digi_getbuffer buf;
+               int tdist;
+               int count;
+
+               spin_unlock_irqrestore(&ch->ch_lock, flags);
+
+               /*
+                * Get data from user first.
+                */
+               if (copy_from_user(&buf, uarg, sizeof(buf)))
+                       return -EFAULT;
+
+               spin_lock_irqsave(&ch->ch_lock, flags);
+
+               /*
+                * Figure out how much data is in our RX and TX queues.
+                */
+               buf.rxbuf = (ch->ch_r_head - ch->ch_r_tail) & RQUEUEMASK;
+               buf.txbuf = (ch->ch_w_head - ch->ch_w_tail) & WQUEUEMASK;
+
+               /*
+                * Is the UART empty? Add that value to whats in our TX queue.
+                */
+               count = buf.txbuf + ch->ch_bd->bd_ops->get_uart_bytes_left(ch);
+
+               /*
+                * Figure out how much data the RealPort Server believes should
+                * be in our TX queue.
+                */
+               tdist = (buf.tIn - buf.tOut) & 0xffff;
+
+               /*
+                * If we have more data than the RealPort Server believes we
+                * should have, reduce our count to its amount.
+                *
+                * This count difference CAN happen because the Linux LD can
+                * insert more characters into our queue for OPOST processing
+                * that the RealPort Server doesn't know about.
+                */
+               if (buf.txbuf > tdist)
+                       buf.txbuf = tdist;
+
+               /*
+                * Report whether our queue and UART TX are completely empty.
+                */
+               if (count)
+                       buf.txdone = 0;
+               else
+                       buf.txdone = 1;
+
+               spin_unlock_irqrestore(&ch->ch_lock, flags);
+
+               if (copy_to_user(uarg, &buf, sizeof(buf)))
+                       return -EFAULT;
+
+               return 0;
+       }
+       default:
+               spin_unlock_irqrestore(&ch->ch_lock, flags);
+
+               return -ENOIOCTLCMD;
+       }
+}