staging: Add dgrp driver for Digi Realport devices

This is based on dgrp-1.9 available from
ftp://ftp1.digi.com/support/beta/linux/dgrp/dgrp-1.9.tgz

Signed-off-by: Bill Pemberton <wfp5p@virginia.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
diff --git a/drivers/staging/dgrp/dgrp_tty.c b/drivers/staging/dgrp/dgrp_tty.c
new file mode 100644
index 0000000..7d7de87
--- /dev/null
+++ b/drivers/staging/dgrp/dgrp_tty.c
@@ -0,0 +1,3331 @@
+/*
+ *
+ * Copyright 1999 Digi International (www.digi.com)
+ *     Gene Olson    <Gene_Olson at digi dot com>
+ *     James Puzzo   <jamesp at digi dot com>
+ *     Jeff Randall
+ *     Scott Kilau   <scottk 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.
+ *
+ */
+
+/*
+ *
+ *  Filename:
+ *
+ *     dgrp_tty.c
+ *
+ *  Description:
+ *
+ *     This file implements the tty driver functionality for the
+ *     RealPort driver software.
+ *
+ *  Author:
+ *
+ *     James A. Puzzo
+ *     Ann-Marie Westgate
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/sched.h>
+
+#include "dgrp_common.h"
+
+#ifndef _POSIX_VDISABLE
+#define   _POSIX_VDISABLE ('\0')
+#endif
+
+/*
+ *	forward declarations
+ */
+
+static void drp_param(struct ch_struct *);
+static void dgrp_tty_close(struct tty_struct *, struct file *);
+
+/* ioctl helper functions */
+static int set_modem_info(struct ch_struct *, unsigned int, unsigned int *);
+static int get_modem_info(struct ch_struct *, unsigned int *);
+static void dgrp_set_custom_speed(struct ch_struct *, int);
+static int dgrp_tty_digigetedelay(struct tty_struct *, int *);
+static int dgrp_tty_digisetedelay(struct tty_struct *, int *);
+static int dgrp_send_break(struct ch_struct *, int);
+
+static ushort  tty_to_ch_flags(struct tty_struct *, char);
+static tcflag_t ch_to_tty_flags(unsigned short, char);
+
+static void dgrp_tty_input_start(struct tty_struct *);
+static void dgrp_tty_input_stop(struct tty_struct *);
+
+static void drp_wmove(struct ch_struct *, int, void*, int);
+
+static int dgrp_tty_open(struct tty_struct *, struct file *);
+static void dgrp_tty_close(struct tty_struct *, struct file *);
+static int dgrp_tty_write(struct tty_struct *, const unsigned char *, int);
+static int dgrp_tty_write_room(struct tty_struct *);
+static void dgrp_tty_flush_buffer(struct tty_struct *);
+static int dgrp_tty_chars_in_buffer(struct tty_struct *);
+static int dgrp_tty_ioctl(struct tty_struct *, unsigned int, unsigned long);
+static void dgrp_tty_set_termios(struct tty_struct *, struct ktermios *);
+static void dgrp_tty_stop(struct tty_struct *);
+static void dgrp_tty_start(struct tty_struct *);
+static void dgrp_tty_throttle(struct tty_struct *);
+static void dgrp_tty_unthrottle(struct tty_struct *);
+static void dgrp_tty_hangup(struct tty_struct *);
+static int dgrp_tty_put_char(struct tty_struct *, unsigned char);
+static int dgrp_tty_tiocmget(struct tty_struct *);
+static int dgrp_tty_tiocmset(struct tty_struct *, unsigned int, unsigned int);
+static int dgrp_tty_send_break(struct tty_struct *, int);
+static void dgrp_tty_send_xchar(struct tty_struct *, char);
+
+/*
+ *	tty defines
+ */
+#define	SERIAL_TYPE_NORMAL	1
+#define	SERIAL_TYPE_CALLOUT	2
+#define	SERIAL_TYPE_XPRINT	3
+
+
+/*
+ *	tty globals/statics
+ */
+
+
+#define PORTSERVER_DIVIDEND	1843200
+
+/*
+ *  Default transparent print information.
+ */
+static struct digi_struct 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 DefaultTermios = {
+	.c_iflag = (ICRNL | IXON),
+	.c_oflag = (OPOST | ONLCR),
+	.c_cflag = (B9600 | CS8 | CREAD | HUPCL | CLOCAL),
+	.c_lflag = (ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHOCTL
+		    | ECHOKE | IEXTEN),
+	.c_cc    = INIT_C_CC,
+	.c_line  = 0,
+};
+
+/* Define our tty operations struct */
+static const struct tty_operations dgrp_tty_ops = {
+	.open            = dgrp_tty_open,
+	.close           = dgrp_tty_close,
+	.write           = dgrp_tty_write,
+	.write_room      = dgrp_tty_write_room,
+	.flush_buffer    = dgrp_tty_flush_buffer,
+	.chars_in_buffer = dgrp_tty_chars_in_buffer,
+	.flush_chars     = NULL,
+	.ioctl           = dgrp_tty_ioctl,
+	.set_termios     = dgrp_tty_set_termios,
+	.stop            = dgrp_tty_stop,
+	.start           = dgrp_tty_start,
+	.throttle        = dgrp_tty_throttle,
+	.unthrottle      = dgrp_tty_unthrottle,
+	.hangup          = dgrp_tty_hangup,
+	.put_char        = dgrp_tty_put_char,
+	.tiocmget        = dgrp_tty_tiocmget,
+	.tiocmset        = dgrp_tty_tiocmset,
+	.break_ctl       = dgrp_tty_send_break,
+	.send_xchar      = dgrp_tty_send_xchar
+};
+
+
+static int calc_baud_rate(struct un_struct *un)
+{
+	int i;
+	int brate;
+
+	struct baud_rates {
+		unsigned int rate;
+		unsigned int cflag;
+	};
+
+	static struct baud_rates baud_rates[] = {
+		{ 921600, B921600 },
+		{ 460800, B460800 },
+		{ 230400, B230400 },
+		{ 115200, B115200 },
+		{  57600, B57600  },
+		{  38400, B38400  },
+		{  19200, B19200  },
+		{   9600, B9600   },
+		{   4800, B4800   },
+		{   2400, B2400   },
+		{   1200, B1200   },
+		{    600, B600    },
+		{    300, B300    },
+		{    200, B200    },
+		{    150, B150    },
+		{    134, B134    },
+		{    110, B110    },
+		{     75, B75     },
+		{     50, B50     },
+		{      0, B9600  }
+	};
+
+	brate = C_BAUD(un->un_tty);
+
+	for (i = 0; baud_rates[i].rate; i++) {
+		if (baud_rates[i].cflag == brate)
+			break;
+	}
+
+	return baud_rates[i].rate;
+}
+
+static int calc_fastbaud_rate(struct un_struct *un, struct ktermios *uts)
+{
+	int i;
+	int brate;
+
+	ulong bauds[2][16] = {
+		{ /* fastbaud*/
+			0,      57600,	 76800,	115200,
+			131657, 153600, 230400, 460800,
+			921600, 1200,   1800,	2400,
+			4800,   9600,	19200,	38400 },
+		{ /* fastbaud & CBAUDEX */
+			0,      57600,	115200,	230400,
+			460800, 150,    200,    921600,
+			600,    1200,   1800,	2400,
+			4800,   9600,	19200,	38400 }
+	};
+
+	brate = C_BAUD(un->un_tty) & 0xff;
+
+	i = (uts->c_cflag & CBAUDEX) ? 1 : 0;
+
+
+	if ((i >= 0) && (i < 2) && (brate >= 0) && (brate < 16))
+		brate = bauds[i][brate];
+	else
+		brate = 0;
+
+	return brate;
+}
+
+/**
+ * drp_param() -- send parameter values to be sent to the node
+ * @ch: channel structure of port to modify
+ *
+ * Interprets the tty and modem changes made by an application
+ * program (by examining the termios structures) and sets up
+ * parameter values to be sent to the node.
+ */
+static void drp_param(struct ch_struct *ch)
+{
+	struct nd_struct *nd;
+	struct un_struct *un;
+	int   brate;
+	int   mflow;
+	int   xflag;
+	int   iflag;
+	struct ktermios *tts, *pts, *uts;
+
+	nd = ch->ch_nd;
+
+	/*
+	 *  If the terminal device is open, use it to set up all tty
+	 *  modes and functions.  Otherwise use the printer device.
+	 */
+
+	if (ch->ch_tun.un_open_count) {
+
+		un = &ch->ch_tun;
+		tts = &ch->ch_tun.un_tty->termios;
+
+		/*
+		 *  If both devices are open, copy critical line
+		 *  parameters from the tty device to the printer,
+		 *  so that if the tty is closed, the printer will
+		 *  continue without disruption.
+		 */
+
+		if (ch->ch_pun.un_open_count) {
+
+			pts = &ch->ch_pun.un_tty->termios;
+
+			pts->c_cflag ^=
+				(pts->c_cflag ^ tts->c_cflag) &
+				(CBAUD  | CSIZE | CSTOPB | CREAD | PARENB |
+				 PARODD | HUPCL | CLOCAL);
+
+			pts->c_iflag ^=
+				(pts->c_iflag ^ tts->c_iflag) &
+				(IGNBRK | BRKINT | IGNPAR | PARMRK | INPCK |
+				 ISTRIP | IXON   | IXANY  | IXOFF);
+
+			pts->c_cc[VSTART] = tts->c_cc[VSTART];
+			pts->c_cc[VSTOP] = tts->c_cc[VSTOP];
+		}
+	} else if (ch->ch_pun.un_open_count == 0) {
+		pr_warn("%s - ch_pun.un_open_count shouldn't be 0\n",
+		       __func__);
+		return;
+	} else {
+		un = &ch->ch_pun;
+	}
+
+	uts = &un->un_tty->termios;
+
+	/*
+	 * Determine if FAST writes can be performed.
+	 */
+
+	if ((ch->ch_digi.digi_flags & DIGI_COOK) != 0 &&
+	    (ch->ch_tun.un_open_count != 0)  &&
+	    !((un->un_tty)->ldisc->ops->flags & LDISC_FLAG_DEFINED) &&
+	    !(L_XCASE(un->un_tty))) {
+		ch->ch_flag |= CH_FAST_WRITE;
+	} else {
+		ch->ch_flag &= ~CH_FAST_WRITE;
+	}
+
+	/*
+	 *  If FAST writes can be performed, and OPOST is on in the
+	 *  terminal device, do OPOST handling in the server.
+	 */
+
+	if ((ch->ch_flag & CH_FAST_WRITE) &&
+	      O_OPOST(un->un_tty) != 0) {
+		int oflag = tty_to_ch_flags(un->un_tty, 'o');
+
+		/* add to ch_ocook any processing flags set in the termio */
+		ch->ch_ocook |= oflag & (OF_OLCUC |
+					 OF_ONLCR |
+					 OF_OCRNL |
+					 OF_ONLRET |
+					 OF_TABDLY);
+
+		/*
+		 * the hpux driver clears any flags set in ch_ocook
+		 * from the termios oflag.  It is STILL reported though
+		 * by a TCGETA
+		 */
+
+		oflag = ch_to_tty_flags(ch->ch_ocook, 'o');
+		uts->c_oflag &= ~oflag;
+
+	} else {
+		/* clear the ch->ch_ocook flag */
+		int oflag = ch_to_tty_flags(ch->ch_ocook, 'o');
+		uts->c_oflag |= oflag;
+		ch->ch_ocook = 0;
+	}
+
+	ch->ch_oflag = ch->ch_ocook;
+
+
+	ch->ch_flag &= ~CH_FAST_READ;
+
+	/*
+	 *  Generate channel flags
+	 */
+
+	if (C_BAUD(un->un_tty) == B0) {
+		if (!(ch->ch_flag & CH_BAUD0)) {
+			/* TODO : the HPUX driver flushes line */
+			/* TODO : discipline, I assume I don't have to */
+
+			ch->ch_tout = ch->ch_tin;
+			ch->ch_rout = ch->ch_rin;
+
+			ch->ch_break_time = 0;
+
+			ch->ch_send |= RR_TX_FLUSH | RR_RX_FLUSH;
+
+			ch->ch_mout &= ~(DM_DTR | DM_RTS);
+
+			ch->ch_flag |= CH_BAUD0;
+		}
+	} else if (ch->ch_custom_speed) {
+		ch->ch_brate = PORTSERVER_DIVIDEND / ch->ch_custom_speed ;
+
+		if (ch->ch_flag & CH_BAUD0) {
+			ch->ch_mout |= DM_DTR | DM_RTS;
+
+			ch->ch_flag &= ~CH_BAUD0;
+		}
+	} else {
+		/*
+		 * Baud rate mapping.
+		 *
+		 * If FASTBAUD isn't on, we can scan the new baud rate list
+		 * as required.
+		 *
+		 * However, if FASTBAUD is on, we must go to the old
+		 * baud rate mapping that existed many many moons ago,
+		 * for compatibility reasons.
+		 */
+
+		if (!(ch->ch_digi.digi_flags & DIGI_FAST))
+			brate = calc_baud_rate(un);
+		else
+			brate = calc_fastbaud_rate(un, uts);
+
+		if (brate == 0)
+			brate = 9600;
+
+		ch->ch_brate = PORTSERVER_DIVIDEND / brate;
+
+		if (ch->ch_flag & CH_BAUD0) {
+			ch->ch_mout |= DM_DTR | DM_RTS;
+
+			ch->ch_flag &= ~CH_BAUD0;
+		}
+	}
+
+	/*
+	 *  Generate channel cflags from the termio.
+	 */
+
+	ch->ch_cflag = tty_to_ch_flags(un->un_tty, 'c');
+
+	/*
+	 *  Generate channel iflags from the termio.
+	 */
+
+	iflag = (int) tty_to_ch_flags(un->un_tty, 'i');
+
+	if (START_CHAR(un->un_tty) == _POSIX_VDISABLE ||
+	    STOP_CHAR(un->un_tty) == _POSIX_VDISABLE) {
+		iflag &= ~(IF_IXON | IF_IXANY | IF_IXOFF);
+	}
+
+	ch->ch_iflag = iflag;
+
+	/*
+	 *  Generate flow control characters
+	 */
+
+	/*
+	 * From the POSIX.1 spec (7.1.2.6): "If {_POSIX_VDISABLE}
+	 * is defined for the terminal device file, and the value
+	 * of one of the changable special control characters (see
+	 * 7.1.1.9) is {_POSIX_VDISABLE}, that function shall be
+	 * disabled, that is, no input data shall be recognized as
+	 * the disabled special character."
+	 *
+	 * OK, so we don't ever assign S/DXB XON or XOFF to _POSIX_VDISABLE.
+	 */
+
+	if (uts->c_cc[VSTART] != _POSIX_VDISABLE)
+		ch->ch_xon = uts->c_cc[VSTART];
+	if (uts->c_cc[VSTOP] != _POSIX_VDISABLE)
+		ch->ch_xoff = uts->c_cc[VSTOP];
+
+	ch->ch_lnext = (uts->c_cc[VLNEXT] == _POSIX_VDISABLE ? 0 :
+			uts->c_cc[VLNEXT]);
+
+	/*
+	 * Also, if either c_cc[START] or c_cc[STOP] is set to
+	 * _POSIX_VDISABLE, we can't really do software flow
+	 * control--in either direction--so we turn it off as
+	 * far as S/DXB is concerned.  In essence, if you disable
+	 * one, you disable the other too.
+	 */
+	if ((uts->c_cc[VSTART] == _POSIX_VDISABLE) ||
+	    (uts->c_cc[VSTOP] == _POSIX_VDISABLE))
+		ch->ch_iflag &= ~(IF_IXOFF | IF_IXON);
+
+	/*
+	 *  Update xflags.
+	 */
+
+	xflag = 0;
+
+	if (ch->ch_digi.digi_flags & DIGI_AIXON)
+		xflag = XF_XIXON;
+
+	if ((ch->ch_xxon == _POSIX_VDISABLE) ||
+	    (ch->ch_xxoff == _POSIX_VDISABLE))
+		xflag &= ~XF_XIXON;
+
+	ch->ch_xflag = xflag;
+
+
+	/*
+	 *  Figure effective DCD value.
+	 */
+
+	if (C_CLOCAL(un->un_tty))
+		ch->ch_flag |= CH_CLOCAL;
+	else
+		ch->ch_flag &= ~CH_CLOCAL;
+
+	/*
+	 *  Check modem signals
+	 */
+
+	dgrp_carrier(ch);
+
+	/*
+	 *  Get hardware handshake value.
+	 */
+
+	mflow = 0;
+
+	if (C_CRTSCTS(un->un_tty))
+		mflow |= (DM_RTS | DM_CTS);
+
+	if (ch->ch_digi.digi_flags & RTSPACE)
+		mflow |= DM_RTS;
+
+	if (ch->ch_digi.digi_flags & DTRPACE)
+		mflow |= DM_DTR;
+
+	if (ch->ch_digi.digi_flags & CTSPACE)
+		mflow |= DM_CTS;
+
+	if (ch->ch_digi.digi_flags & DSRPACE)
+		mflow |= DM_DSR;
+
+	if (ch->ch_digi.digi_flags & DCDPACE)
+		mflow |= DM_CD;
+
+	if (ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE)
+		mflow |= DM_RTS_TOGGLE;
+
+	ch->ch_mflow = mflow;
+
+	/*
+	 *  Send the changes to the server.
+	 */
+
+	ch->ch_flag |= CH_PARAM;
+	(ch->ch_nd)->nd_tx_work = 1;
+
+	if (waitqueue_active(&ch->ch_flag_wait))
+		wake_up_interruptible(&ch->ch_flag_wait);
+}
+
+/*
+ * This function is just used as a callback for timeouts
+ * waiting on the ch_sleep flag.
+ */
+static void wake_up_drp_sleep_timer(unsigned long ptr)
+{
+	struct ch_struct *ch = (struct ch_struct *) ptr;
+	if (ch)
+		wake_up(&ch->ch_sleep);
+}
+
+
+/*
+ * Set up our own sleep that can't be cancelled
+ * until our timeout occurs.
+ */
+static void drp_my_sleep(struct ch_struct *ch)
+{
+	struct timer_list drp_wakeup_timer;
+	DECLARE_WAITQUEUE(wait, current);
+
+	/*
+	 * First make sure we're ready to receive the wakeup.
+	 */
+
+	add_wait_queue(&ch->ch_sleep, &wait);
+	current->state = TASK_UNINTERRUPTIBLE;
+
+	/*
+	 * Since we are uninterruptible, set a timer to
+	 * unset the uninterruptable state in 1 second.
+	 */
+
+	init_timer(&drp_wakeup_timer);
+	drp_wakeup_timer.function = wake_up_drp_sleep_timer;
+	drp_wakeup_timer.data = (unsigned long) ch;
+	drp_wakeup_timer.expires = jiffies + (1 * HZ);
+	add_timer(&drp_wakeup_timer);
+
+	schedule();
+
+	del_timer(&drp_wakeup_timer);
+
+	remove_wait_queue(&ch->ch_sleep, &wait);
+}
+
+/*
+ * dgrp_tty_open()
+ *
+ * returns:
+ *    -EBUSY    - this is a callout device and the normal device is active
+ *              - there is an error in opening the tty
+ *    -ENODEV   - the channel does not exist
+ *    -EAGAIN   - we are in the middle of hanging up or closing
+ *              - IMMEDIATE_OPEN fails
+ *    -ENXIO or -EAGAIN
+ *              - if the port is outside physical range
+ *    -EINTR    - the open is interrupted
+ *
+ */
+static int dgrp_tty_open(struct tty_struct *tty, struct file *file)
+{
+	int    retval = 0;
+	struct nd_struct  *nd;
+	struct ch_struct *ch;
+	struct un_struct  *un;
+	int    port;
+	int    delay_error;
+	int    otype;
+	int    unf;
+	int    wait_carrier;
+	int    category;
+	int    counts_were_incremented = 0;
+	ulong lock_flags;
+	DECLARE_WAITQUEUE(wait, current);
+
+	/*
+	 * Do some initial checks to see if the node and port exist
+	 */
+
+	nd = nd_struct_get(MAJOR(tty_devnum(tty)));
+	port = PORT_NUM(MINOR(tty_devnum(tty)));
+	category = OPEN_CATEGORY(MINOR(tty_devnum(tty)));
+
+	if (!nd)
+		return -ENODEV;
+
+	if (port >= CHAN_MAX)
+		return -ENODEV;
+
+	/*
+	 *  The channel exists.
+	 */
+
+	ch = nd->nd_chan + port;
+
+	un = IS_PRINT(MINOR(tty_devnum(tty))) ? &ch->ch_pun : &ch->ch_tun;
+	un->un_tty = tty;
+	tty->driver_data = un;
+
+	/*
+	 * If we are in the middle of hanging up,
+	 * then return an error
+	 */
+	if (tty_hung_up_p(file)) {
+		retval = ((un->un_flag & UN_HUP_NOTIFY) ?
+			   -EAGAIN : -ERESTARTSYS);
+		goto done;
+	}
+
+	/*
+	 * If the port is in the middle of closing, then block
+	 * until it is done, then try again.
+	 */
+	retval = wait_event_interruptible(un->un_close_wait,
+			((un->un_flag & UN_CLOSING) == 0));
+
+	if (retval)
+		goto done;
+
+	/*
+	 * If the port is in the middle of a reopen after a network disconnect,
+	 * wait until it is done, then try again.
+	 */
+	retval = wait_event_interruptible(ch->ch_flag_wait,
+			((ch->ch_flag & CH_PORT_GONE) == 0));
+
+	if (retval)
+		goto done;
+
+	/*
+	 * If this is a callout device, then just make sure the normal
+	 * device isn't being used.
+	 */
+
+	if (tty->driver->subtype == SERIAL_TYPE_CALLOUT) {
+		if (un->un_flag & UN_NORMAL_ACTIVE) {
+			retval = -EBUSY;
+			goto done;
+		} else {
+			un->un_flag |= UN_CALLOUT_ACTIVE;
+		}
+	}
+
+	/*
+	 *  Loop waiting until the open can be successfully completed.
+	 */
+
+	spin_lock_irqsave(&nd->nd_lock, lock_flags);
+
+	nd->nd_tx_work = 1;
+
+	for (;;) {
+		wait_carrier = 0;
+
+		/*
+		 * Determine the open type from the flags provided.
+		 */
+
+		/*
+		 * If the port is not enabled, then exit
+		 */
+		if (test_bit(TTY_IO_ERROR, &tty->flags)) {
+			/* there was an error in opening the tty */
+			if (un->un_flag & UN_CALLOUT_ACTIVE)
+				retval = -EBUSY;
+			else
+				un->un_flag |= UN_NORMAL_ACTIVE;
+			goto unlock;
+		}
+
+		if (file->f_flags & O_NONBLOCK) {
+
+			/*
+			 * if the O_NONBLOCK is set, errors on read and write
+			 * must return -EAGAIN immediately and NOT sleep
+			 * on the waitqs.
+			 */
+			otype = OTYPE_IMMEDIATE;
+			delay_error = -EAGAIN;
+
+		} else if (!OPEN_WAIT_AVAIL(category) ||
+			  (file->f_flags & O_NDELAY) != 0) {
+			otype = OTYPE_IMMEDIATE;
+			delay_error = -EBUSY;
+
+		} else if (!OPEN_WAIT_CARRIER(category) ||
+			  ((ch->ch_digi.digi_flags & DIGI_FORCEDCD) != 0) ||
+			  C_CLOCAL(tty)) {
+			otype = OTYPE_PERSISTENT;
+			delay_error = 0;
+
+		} else {
+			otype = OTYPE_INCOMING;
+			delay_error = 0;
+		}
+
+		/*
+		 * Handle port currently outside physical port range.
+		 */
+
+		if (port >= nd->nd_chan_count) {
+			if (otype == OTYPE_IMMEDIATE) {
+				retval = (nd->nd_state == NS_READY) ?
+						-ENXIO : -EAGAIN;
+				goto unlock;
+			}
+		}
+
+		/*
+		 *  Handle port not currently open.
+		 */
+
+		else if (ch->ch_open_count == 0) {
+			/*
+			 * Return an error when an Incoming Open
+			 * response indicates the port is busy.
+			 */
+
+			if (ch->ch_open_error != 0 && otype == ch->ch_otype) {
+				retval = (ch->ch_open_error <= 2) ?
+					  delay_error : -ENXIO ;
+				goto unlock;
+			}
+
+			/*
+			 * Fail any new Immediate open if we do not have
+			 * a normal connection to the server.
+			 */
+
+			if (nd->nd_state != NS_READY &&
+			    otype == OTYPE_IMMEDIATE) {
+				retval = -EAGAIN;
+				goto unlock;
+			}
+
+			/*
+			 * If a Realport open of the correct type has
+			 * succeeded, complete the open.
+			 */
+
+			if (ch->ch_state == CS_READY && ch->ch_otype == otype)
+				break;
+		}
+
+		/*
+		 * Handle port already open and active as a device
+		 * of same category.
+		 */
+
+		else if ((ch->ch_category == category) ||
+			  IS_PRINT(MINOR(tty_devnum(tty)))) {
+			/*
+			 * Fail if opening the device now would
+			 * violate exclusive use.
+			 */
+			unf = ch->ch_tun.un_flag | ch->ch_pun.un_flag;
+
+			if ((file->f_flags & O_EXCL) || (unf & UN_EXCL)) {
+				retval = -EBUSY;
+				goto unlock;
+			}
+
+			/*
+			 * If the open device is in the hangup state, all
+			 * system calls fail except close().
+			 */
+
+			/* TODO : check on hangup_p calls */
+
+			if (ch->ch_flag & CH_HANGUP) {
+				retval = -ENXIO;
+				goto unlock;
+			}
+
+			/*
+			 * If the port is ready, and carrier is ignored
+			 * or present, then complete the open.
+			 */
+
+			if (ch->ch_state == CS_READY &&
+			    (otype != OTYPE_INCOMING ||
+			    ch->ch_flag & CH_VIRT_CD))
+				break;
+
+			wait_carrier = 1;
+		}
+
+		/*
+		 *  Handle port active with a different category device.
+		 */
+
+		else {
+			if (otype == OTYPE_IMMEDIATE) {
+				retval = delay_error;
+				goto unlock;
+			}
+		}
+
+		/*
+		 * Wait until conditions change, then take another
+		 * try at the open.
+		 */
+
+		ch->ch_wait_count[otype]++;
+
+		if (wait_carrier)
+			ch->ch_wait_carrier++;
+
+		/*
+		 * Prepare the task to accept the wakeup, then
+		 * release our locks and release control.
+		 */
+
+		add_wait_queue(&ch->ch_flag_wait, &wait);
+		current->state = TASK_INTERRUPTIBLE;
+
+		spin_unlock_irqrestore(&nd->nd_lock, lock_flags);
+
+		/*
+		 * Give up control, we'll come back if we're
+		 * interrupted or are woken up.
+		 */
+		schedule();
+		remove_wait_queue(&ch->ch_flag_wait, &wait);
+
+		spin_lock_irqsave(&nd->nd_lock, lock_flags);
+
+		current->state = TASK_RUNNING;
+
+		ch->ch_wait_count[otype]--;
+
+		if (wait_carrier)
+			ch->ch_wait_carrier--;
+
+		nd->nd_tx_work = 1;
+
+		if (signal_pending(current)) {
+			retval = -EINTR;
+			goto unlock;
+		}
+	} /* end for(;;) */
+
+	/*
+	 *  The open has succeeded.  No turning back.
+	 */
+	counts_were_incremented = 1;
+	un->un_open_count++;
+	ch->ch_open_count++;
+
+	/*
+	 * Initialize the channel, if it's not already open.
+	 */
+
+	if (ch->ch_open_count == 1) {
+		ch->ch_flag = 0;
+		ch->ch_inwait = 0;
+		ch->ch_category = category;
+		ch->ch_pscan_state = 0;
+
+		/* TODO : find out what PS-1 bug Gene was referring to */
+		/* TODO : in the following comment. */
+
+		ch->ch_send = RR_TX_START | RR_RX_START;  /* PS-1 bug */
+
+		if (C_CLOCAL(tty) ||
+		    ch->ch_s_mlast & DM_CD ||
+		    ch->ch_digi.digi_flags & DIGI_FORCEDCD)
+			ch->ch_flag |= CH_VIRT_CD;
+		else if (OPEN_FORCES_CARRIER(category))
+			ch->ch_flag |= CH_VIRT_CD;
+
+	}
+
+	/*
+	 *  Initialize the unit, if it is not already open.
+	 */
+
+	if (un->un_open_count == 1) {
+		/*
+		 *  Since all terminal options are always sticky in Linux,
+		 *  we don't need the UN_STICKY flag to be handled specially.
+		 */
+		/* clears all the digi flags, leaves serial flags */
+		un->un_flag &= ~UN_DIGI_MASK;
+
+		if (file->f_flags & O_EXCL)
+			un->un_flag |= UN_EXCL;
+
+		/* TODO : include "session" and "pgrp" */
+
+		/*
+		 *  In Linux, all terminal parameters are intended to be sticky.
+		 *  as a result, we "remove" the code which once reset the ports
+		 *  to sane values.
+		 */
+
+		drp_param(ch);
+
+	}
+
+	un->un_flag |= UN_INITIALIZED;
+
+	retval = 0;
+
+unlock:
+
+	spin_unlock_irqrestore(&nd->nd_lock, lock_flags);
+
+done:
+	/*
+	 * Linux does a close for every open, even failed ones!
+	 */
+	if (!counts_were_incremented) {
+		un->un_open_count++;
+		ch->ch_open_count++;
+	}
+
+	if (retval)
+		dev_err(tty->dev, "tty open bad return (%i)\n", retval);
+
+	return retval;
+}
+
+
+
+
+/*
+ * dgrp_tty_close() -- close function for tty_operations
+ */
+static void dgrp_tty_close(struct tty_struct *tty, struct file *file)
+{
+	struct ch_struct *ch;
+	struct un_struct *un;
+	struct nd_struct *nd;
+	int	tpos;
+	int	port;
+	int	err = 0;
+	int	s = 0;
+	ulong  waketime;
+	ulong  lock_flags;
+	int sent_printer_offstr = 0;
+
+	port = PORT_NUM(MINOR(tty_devnum(tty)));
+
+	un = tty->driver_data;
+
+	if (!un)
+		return;
+
+	ch = un->un_ch;
+
+	if (!ch)
+		return;
+
+	nd = ch->ch_nd;
+
+	if (!nd)
+		return;
+
+	spin_lock_irqsave(&nd->nd_lock, lock_flags);
+
+
+	/* Used to be on channel basis, now we check on a unit basis. */
+	if (un->un_open_count != 1)
+		goto unlock;
+
+	/*
+	 * OK, its the last close on the unit
+	 */
+	un->un_flag |= UN_CLOSING;
+
+	/*
+	 * Notify the discipline to only process XON/XOFF characters.
+	 */
+	tty->closing = 1;
+
+	/*
+	 * Wait for output to drain only if this is
+	 * the last close against the channel
+	 */
+
+	if (ch->ch_open_count == 1) {
+		/*
+		 * If its the print device, we need to ensure at all costs that
+		 * the offstr will fit. If it won't, flush our tbuf.
+		 */
+		if (IS_PRINT(MINOR(tty_devnum(tty))) &&
+		    (((ch->ch_tout - ch->ch_tin - 1) & TBUF_MASK) <
+		    ch->ch_digi.digi_offlen))
+			ch->ch_tin = ch->ch_tout;
+
+		/*
+		 * Turn off the printer.  Don't bother checking to see if its
+		 * IS_PRINT... Since this is the last close the flag is going
+		 * to be cleared, so we MUST make sure the offstr gets inserted
+		 * into tbuf.
+		 */
+
+		if ((ch->ch_flag & CH_PRON) != 0) {
+			drp_wmove(ch, 0, ch->ch_digi.digi_offstr,
+				  ch->ch_digi.digi_offlen);
+			ch->ch_flag &= ~CH_PRON;
+			sent_printer_offstr = 1;
+		}
+	}
+
+	/*
+	 *  Wait until either the output queue has drained, or we see
+	 *  absolutely no progress for 15 seconds.
+	 */
+
+	tpos = ch->ch_s_tpos;
+
+	waketime = jiffies + 15 * HZ;
+
+	for (;;) {
+
+		/*
+		 *  Make sure the port still exists.
+		 */
+
+		if (port >= nd->nd_chan_count) {
+			err = 1;
+			break;
+		}
+
+		if (signal_pending(current)) {
+			err = 1;
+			break;
+		}
+
+		/*
+		 * If the port is idle (not opened on the server), we have
+		 * no way of draining/flushing/closing the port on that server.
+		 * So break out of loop.
+		 */
+		if (ch->ch_state == CS_IDLE)
+			break;
+
+		nd->nd_tx_work = 1;
+
+		/*
+		 *  Exit if the queues for this unit are empty,
+		 *  and either the other unit is still open or all
+		 *  data has drained.
+		 */
+
+		if ((un->un_tty)->ops->chars_in_buffer ?
+		    ((un->un_tty)->ops->chars_in_buffer)(un->un_tty) == 0 : 1) {
+
+			/*
+			 * We don't need to wait for a buffer to drain
+			 * if the other unit is open.
+			 */
+
+			if (ch->ch_open_count != un->un_open_count)
+				break;
+
+			/*
+			 *  The wait is complete when all queues are
+			 *  drained, and any break in progress is complete.
+			 */
+
+			if (ch->ch_tin == ch->ch_tout &&
+			    ch->ch_s_tin == ch->ch_s_tpos &&
+			    (ch->ch_send & RR_TX_BREAK) == 0) {
+				break;
+			}
+		}
+
+		/*
+		 * Flush TX data and exit the wait if NDELAY is set,
+		 * or this is not a DIGI printer, and the close timeout
+		 * expires.
+		 */
+
+		if ((file->f_flags & (O_NDELAY | O_NONBLOCK)) ||
+		    ((long)(jiffies - waketime) >= 0 &&
+		      (ch->ch_digi.digi_flags & DIGI_PRINTER) == 0)) {
+
+				/*
+				 * If we sent the printer off string, we cannot
+				 * flush our internal buffers, or we might lose
+				 * the offstr.
+				 */
+				if (!sent_printer_offstr)
+					dgrp_tty_flush_buffer(tty);
+
+				tty_ldisc_flush(tty);
+				break;
+		}
+
+		/*
+		 *  Otherwise take a short nap.
+		 */
+
+		ch->ch_flag |= CH_DRAIN;
+
+		spin_unlock_irqrestore(&nd->nd_lock, lock_flags);
+
+		schedule_timeout_interruptible(1);
+		s = signal_pending(current);
+
+		spin_lock_irqsave(&nd->nd_lock, lock_flags);
+
+		if (s) {
+			/*
+			 * If we had sent the printer off string, we now have
+			 * some problems.
+			 *
+			 * The system won't let us sleep since we got an error
+			 * back from sleep, presumably because the user did
+			 * a ctrl-c...
+			 * But we need to ensure that the offstr gets sent!
+			 * Thus, we have to do something else besides sleeping.
+			 * The plan:
+			 * 1) Make this task uninterruptable.
+			 * 2) Set up a timer to go off in 1 sec.
+			 * 3) Act as tho we just got out of the sleep above.
+			 *
+			 * Thankfully, in the real world, this just
+			 * never happens.
+			 */
+
+			if (sent_printer_offstr) {
+				spin_unlock_irqrestore(&nd->nd_lock,
+						       lock_flags);
+				drp_my_sleep(ch);
+				spin_lock_irqsave(&nd->nd_lock, lock_flags);
+			} else {
+				err = 1;
+				break;
+			}
+		}
+
+		/*
+		 *  Restart the wait if any progress is seen.
+		 */
+
+		if (ch->ch_s_tpos != tpos) {
+			tpos = ch->ch_s_tpos;
+
+			/* TODO:  this gives us timeout problems with nist ?? */
+			waketime = jiffies + 15 * HZ;
+		}
+	}
+
+	/*
+	 *  Close the line discipline
+	 */
+
+	/* this is done in tty_io.c */
+	/* if ((un->un_tty)->ldisc.close)
+	 *	((un->un_tty)->ldisc.close)(un->un_tty);
+	 */
+
+	/* don't do this here */
+	/* un->un_flag = 0; */
+
+	/*
+	 *  Flush the receive buffer on terminal unit close only.
+	 */
+
+	if (!IS_PRINT(MINOR(tty_devnum(tty))))
+		ch->ch_rout = ch->ch_rin;
+
+
+	/*
+	 * Don't permit the close to happen until we get any pending
+	 * sync request responses.
+	 * There could be other ports depending upon the response as well.
+	 *
+	 * Also, don't permit the close to happen until any parameter
+	 * changes have been sent out from the state machine as well.
+	 * This is required because of a ditty -a race with -HUPCL
+	 * We MUST make sure all channel parameters have been sent to the
+	 * Portserver before sending a close.
+	 */
+
+	if ((err != 1) && (ch->ch_state != CS_IDLE)) {
+		spin_unlock_irqrestore(&nd->nd_lock, lock_flags);
+		s = wait_event_interruptible(ch->ch_flag_wait,
+			((ch->ch_flag & (CH_WAITING_SYNC | CH_PARAM)) == 0));
+		spin_lock_irqsave(&nd->nd_lock, lock_flags);
+	}
+
+	/*
+	 * Cleanup the channel if last unit open.
+	 */
+
+	if (ch->ch_open_count == 1) {
+		ch->ch_flag = 0;
+		ch->ch_category = 0;
+		ch->ch_send = 0;
+		ch->ch_expect = 0;
+		ch->ch_tout = ch->ch_tin;
+		/* (un->un_tty)->device = 0; */
+
+		if (ch->ch_state == CS_READY)
+			ch->ch_state = CS_SEND_CLOSE;
+	}
+
+	/*
+	 * Send the changes to the server
+	 */
+	if (ch->ch_state != CS_IDLE) {
+		ch->ch_flag |= CH_PARAM;
+		wake_up_interruptible(&ch->ch_flag_wait);
+	}
+
+	nd->nd_tx_work = 1;
+	nd->nd_tx_ready = 1;
+
+unlock:
+	tty->closing = 0;
+
+	if (ch->ch_open_count <= 0)
+		dev_info(tty->dev,
+			 "%s - unexpected value for ch->ch_open_count: %i\n",
+			 __func__, ch->ch_open_count);
+	else
+		ch->ch_open_count--;
+
+	if (un->un_open_count <= 0)
+		dev_info(tty->dev,
+			 "%s - unexpected value for un->un_open_count: %i\n",
+			 __func__, un->un_open_count);
+	else
+		un->un_open_count--;
+
+	un->un_flag &= ~(UN_NORMAL_ACTIVE | UN_CALLOUT_ACTIVE | UN_CLOSING);
+	if (waitqueue_active(&un->un_close_wait))
+		wake_up_interruptible(&un->un_close_wait);
+
+	spin_unlock_irqrestore(&nd->nd_lock, lock_flags);
+
+	return;
+
+}
+
+static void drp_wmove(struct ch_struct *ch, int from_user, void *buf, int count)
+{
+	int n;
+	int ret = 0;
+
+	ch->ch_nd->nd_tx_work = 1;
+
+	n = TBUF_MAX - ch->ch_tin;
+
+	if (count >= n) {
+		if (from_user)
+			ret = copy_from_user(ch->ch_tbuf + ch->ch_tin,
+					     (void __user *) buf, n);
+		else
+			memcpy(ch->ch_tbuf + ch->ch_tin, buf, n);
+
+		buf = (char *) buf + n;
+		count -= n;
+		ch->ch_tin = 0;
+	}
+
+	if (from_user)
+		ret = copy_from_user(ch->ch_tbuf + ch->ch_tin,
+				     (void __user *) buf, count);
+	else
+		memcpy(ch->ch_tbuf + ch->ch_tin, buf, count);
+
+	ch->ch_tin += count;
+}
+
+
+static int dgrp_calculate_txprint_bounds(struct ch_struct *ch, int space,
+					 int *un_flag)
+{
+	clock_t tt;
+	clock_t mt;
+	unsigned short tmax = 0;
+
+	/*
+	 * If the terminal device is busy, reschedule when
+	 * the terminal device becomes idle.
+	 */
+
+	if (ch->ch_tun.un_open_count != 0 &&
+	    ch->ch_tun.un_tty->ops->chars_in_buffer &&
+	    ((ch->ch_tun.un_tty->ops->chars_in_buffer)(ch->ch_tun.un_tty) != 0)) {
+		*un_flag = UN_PWAIT;
+		return 0;
+	}
+
+	/*
+	 * Assure that whenever there is printer data in the output
+	 * buffer, there always remains enough space after it to
+	 * turn the printer off.
+	 */
+	space -= ch->ch_digi.digi_offlen;
+
+	if (space <= 0) {
+		*un_flag = UN_EMPTY;
+		return 0;
+	}
+
+	/*
+	 * We measure printer CPS speed by incrementing
+	 * ch_cpstime by (HZ / digi_maxcps) for every
+	 * character we output, restricting output so
+	 * that ch_cpstime never exceeds lbolt.
+	 *
+	 * However if output has not been done for some
+	 * time, lbolt will grow to very much larger than
+	 * ch_cpstime, which would allow essentially
+	 * unlimited amounts of output until ch_cpstime
+	 * finally caught up.   To avoid this, we adjust
+	 * cps_time when necessary so the difference
+	 * between lbolt and ch_cpstime never results
+	 * in sending more than digi_bufsize characters.
+	 *
+	 * This nicely models a printer with an internal
+	 * buffer of digi_bufsize characters.
+	 *
+	 * Get the time between lbolt and ch->ch_cpstime;
+	 */
+
+	tt = jiffies - ch->ch_cpstime;
+
+	/*
+	 * Compute the time required to send digi_bufsize
+	 * characters.
+	 */
+
+	mt = HZ * ch->ch_digi.digi_bufsize / ch->ch_digi.digi_maxcps;
+
+	/*
+	 * Compute the number of characters that can be sent
+	 * without violating the time constraint.   If the
+	 * direct calculation of this number is bigger than
+	 * digi_bufsize, limit the number to digi_bufsize,
+	 * and adjust cpstime to match.
+	 */
+
+	if ((clock_t)(tt + HZ) > (clock_t)(mt + HZ)) {
+		tmax = ch->ch_digi.digi_bufsize;
+		ch->ch_cpstime = jiffies - mt;
+	} else {
+		tmax = ch->ch_digi.digi_maxcps * tt / HZ;
+	}
+
+	/*
+	 * If the time constraint now binds, limit the transmit
+	 * count accordingly, and tentatively arrange to be
+	 * rescheduled based on time.
+	 */
+
+	if (tmax < space) {
+		*un_flag = UN_TIME;
+		space = tmax;
+	}
+
+	/*
+	 * Compute the total number of characters we can
+	 * output before the total number of characters known
+	 * to be in the output queue exceeds digi_maxchar.
+	 */
+
+	tmax = (ch->ch_digi.digi_maxchar -
+		((ch->ch_tin - ch->ch_tout) & TBUF_MASK) -
+		((ch->ch_s_tin - ch->ch_s_tpos) & 0xffff));
+
+
+	/*
+	 * If the digi_maxchar constraint now holds, limit
+	 * the transmit count accordingly, and arrange to
+	 * be rescheduled when the queue becomes empty.
+	 */
+
+	if (space > tmax) {
+		*un_flag = UN_EMPTY;
+		space = tmax;
+	}
+
+	if (space <= 0)
+		*un_flag |= UN_EMPTY;
+
+	return space;
+}
+
+
+static int dgrp_tty_write(struct tty_struct *tty,
+			  const unsigned char *buf,
+			  int count)
+{
+	struct nd_struct *nd;
+	struct un_struct *un;
+	struct ch_struct *ch;
+	int	space;
+	int	n;
+	int	t;
+	int sendcount;
+	int un_flag;
+	ulong lock_flags;
+
+	if (tty == NULL)
+		return 0;
+
+	un = tty->driver_data;
+	if (!un)
+		return 0;
+
+	ch = un->un_ch;
+	if (!ch)
+		return 0;
+
+	nd = ch->ch_nd;
+	if (!nd)
+		return 0;
+
+	/*
+	 * Ignore the request if the channel is not ready.
+	 */
+	if (ch->ch_state != CS_READY)
+		return 0;
+
+	spin_lock_irqsave(&dgrp_poll_data.poll_lock, lock_flags);
+
+	/*
+	 * Ignore the request if output is blocked.
+	 */
+	if ((un->un_flag & (UN_EMPTY | UN_LOW | UN_TIME | UN_PWAIT)) != 0) {
+		count = 0;
+		goto out;
+	}
+
+	/*
+	 * Also ignore the request if DPA has this port open,
+	 * and is flow controlled on reading more data.
+	 */
+	if (nd->nd_dpa_debug && nd->nd_dpa_flag & DPA_WAIT_SPACE &&
+		nd->nd_dpa_port == MINOR(tty_devnum(ch->ch_tun.un_tty))) {
+		count = 0;
+		goto out;
+	}
+
+	/*
+	 *	Limit amount we will write to the amount of space
+	 *	available in the channel buffer.
+	 */
+	sendcount = 0;
+
+	space = (ch->ch_tout - ch->ch_tin - 1) & TBUF_MASK;
+
+	/*
+	 * Handle the printer device.
+	 */
+
+	un_flag = UN_LOW;
+
+	if (IS_PRINT(MINOR(tty_devnum(tty)))) {
+		clock_t tt;
+		clock_t mt;
+		unsigned short tmax = 0;
+
+		/*
+		 * If the terminal device is busy, reschedule when
+		 * the terminal device becomes idle.
+		 */
+
+		if (ch->ch_tun.un_open_count != 0 &&
+		    ((ch->ch_tun.un_tty->ops->chars_in_buffer)(ch->ch_tun.un_tty) != 0)) {
+			un->un_flag |= UN_PWAIT;
+			count = 0;
+			goto out;
+		}
+
+		/*
+		 * Assure that whenever there is printer data in the output
+		 * buffer, there always remains enough space after it to
+		 * turn the printer off.
+		 */
+		space -= ch->ch_digi.digi_offlen;
+
+		/*
+		 * Output the printer on string.
+		 */
+
+		if ((ch->ch_flag & CH_PRON) == 0) {
+			space -= ch->ch_digi.digi_onlen;
+
+			if (space < 0) {
+				un->un_flag |= UN_EMPTY;
+				(ch->ch_nd)->nd_tx_work = 1;
+				count = 0;
+				goto out;
+			}
+
+			drp_wmove(ch, 0, ch->ch_digi.digi_onstr,
+				ch->ch_digi.digi_onlen);
+
+			ch->ch_flag |= CH_PRON;
+		}
+
+		/*
+		 * We measure printer CPS speed by incrementing
+		 * ch_cpstime by (HZ / digi_maxcps) for every
+		 * character we output, restricting output so
+		 * that ch_cpstime never exceeds lbolt.
+		 *
+		 * However if output has not been done for some
+		 * time, lbolt will grow to very much larger than
+		 * ch_cpstime, which would allow essentially
+		 * unlimited amounts of output until ch_cpstime
+		 * finally caught up.   To avoid this, we adjust
+		 * cps_time when necessary so the difference
+		 * between lbolt and ch_cpstime never results
+		 * in sending more than digi_bufsize characters.
+		 *
+		 * This nicely models a printer with an internal
+		 * buffer of digi_bufsize characters.
+		 *
+		 * Get the time between lbolt and ch->ch_cpstime;
+		 */
+
+		tt = jiffies - ch->ch_cpstime;
+
+		/*
+		 * Compute the time required to send digi_bufsize
+		 * characters.
+		 */
+
+		mt = HZ * ch->ch_digi.digi_bufsize / ch->ch_digi.digi_maxcps;
+
+		/*
+		 * Compute the number of characters that can be sent
+		 * without violating the time constraint.   If the
+		 * direct calculation of this number is bigger than
+		 * digi_bufsize, limit the number to digi_bufsize,
+		 * and adjust cpstime to match.
+		 */
+
+		if ((clock_t)(tt + HZ) > (clock_t)(mt + HZ)) {
+			tmax = ch->ch_digi.digi_bufsize;
+			ch->ch_cpstime = jiffies - mt;
+		} else {
+			tmax = ch->ch_digi.digi_maxcps * tt / HZ;
+		}
+
+		/*
+		 * If the time constraint now binds, limit the transmit
+		 * count accordingly, and tentatively arrange to be
+		 * rescheduled based on time.
+		 */
+
+		if (tmax < space) {
+			space = tmax;
+			un_flag = UN_TIME;
+		}
+
+		/*
+		 * Compute the total number of characters we can
+		 * output before the total number of characters known
+		 * to be in the output queue exceeds digi_maxchar.
+		 */
+
+		tmax = (ch->ch_digi.digi_maxchar -
+			((ch->ch_tin - ch->ch_tout) & TBUF_MASK) -
+			((ch->ch_s_tin - ch->ch_s_tpos) & 0xffff));
+
+
+		/*
+		 * If the digi_maxchar constraint now holds, limit
+		 * the transmit count accordingly, and arrange to
+		 * be rescheduled when the queue becomes empty.
+		 */
+
+		if (space > tmax) {
+			space = tmax;
+			un_flag = UN_EMPTY;
+		}
+
+	}
+	/*
+	 * Handle the terminal device.
+	 */
+	else {
+
+		/*
+		 * If the printer device is on, turn it off.
+		 */
+
+		if ((ch->ch_flag & CH_PRON) != 0) {
+
+			space -= ch->ch_digi.digi_offlen;
+
+			drp_wmove(ch, 0, ch->ch_digi.digi_offstr,
+				  ch->ch_digi.digi_offlen);
+
+			ch->ch_flag &= ~CH_PRON;
+		}
+	}
+
+	/*
+	 *	If space is 0 and its because the ch->tbuf
+	 *	is full, then Linux will handle a callback when queue
+	 *	space becomes available.
+	 *	tty_write returns count = 0
+	 */
+
+	if (space <= 0) {
+		/* the linux tty_io.c handles this if we return 0 */
+		/* if (fp->flags & O_NONBLOCK) return -EAGAIN; */
+
+		un->un_flag |= UN_EMPTY;
+		(ch->ch_nd)->nd_tx_work = 1;
+		count = 0;
+		goto out;
+	}
+
+	count = min(count, space);
+
+	if (count > 0) {
+
+		un->un_tbusy++;
+
+		/*
+		 *	Copy the buffer contents to the ch_tbuf
+		 *	being careful to wrap around the circular queue
+		 */
+
+		t = TBUF_MAX - ch->ch_tin;
+		n = count;
+
+		if (n >= t) {
+			memcpy(ch->ch_tbuf + ch->ch_tin, buf, t);
+			if (nd->nd_dpa_debug && nd->nd_dpa_port == PORT_NUM(MINOR(tty_devnum(un->un_tty))))
+				dgrp_dpa_data(nd, 0, (char *) buf, t);
+			buf += t;
+			n -= t;
+			ch->ch_tin = 0;
+			sendcount += n;
+		}
+
+		memcpy(ch->ch_tbuf + ch->ch_tin, buf, n);
+		if (nd->nd_dpa_debug && nd->nd_dpa_port == PORT_NUM(MINOR(tty_devnum(un->un_tty))))
+			dgrp_dpa_data(nd, 0, (char *) buf, n);
+		buf += n;
+		ch->ch_tin += n;
+		sendcount += n;
+
+		un->un_tbusy--;
+		(ch->ch_nd)->nd_tx_work = 1;
+		if (ch->ch_edelay != DGRP_RTIME) {
+			(ch->ch_nd)->nd_tx_ready = 1;
+			wake_up_interruptible(&nd->nd_tx_waitq);
+		}
+	}
+
+	ch->ch_txcount += count;
+
+	if (IS_PRINT(MINOR(tty_devnum(tty)))) {
+
+		/*
+		 * Adjust ch_cpstime to account
+		 * for the characters just output.
+		 */
+
+		if (sendcount > 0) {
+			int cc = HZ * sendcount + ch->ch_cpsrem;
+
+			ch->ch_cpstime += cc / ch->ch_digi.digi_maxcps;
+			ch->ch_cpsrem   = cc % ch->ch_digi.digi_maxcps;
+		}
+
+		/*
+		 * If we are now waiting on time, schedule ourself
+		 * back when we'll be able to send a block of
+		 * digi_maxchar characters.
+		 */
+
+		if ((un_flag & UN_TIME) != 0) {
+			ch->ch_waketime = (ch->ch_cpstime +
+				(ch->ch_digi.digi_maxchar * HZ /
+				ch->ch_digi.digi_maxcps));
+		}
+	}
+
+	/*
+	 * If the printer unit is waiting for completion
+	 * of terminal output, get him going again.
+	 */
+
+	if ((ch->ch_pun.un_flag & UN_PWAIT) != 0)
+		(ch->ch_nd)->nd_tx_work = 1;
+
+out:
+	spin_unlock_irqrestore(&dgrp_poll_data.poll_lock, lock_flags);
+
+	return count;
+}
+
+
+/*
+ *	Put a character into ch->ch_buf
+ *
+ *	- used by the line discipline for OPOST processing
+ */
+
+static int dgrp_tty_put_char(struct tty_struct *tty, unsigned char new_char)
+{
+	struct un_struct *un;
+	struct ch_struct *ch;
+	ulong  lock_flags;
+	int space;
+	int retval = 0;
+
+	if (tty == NULL)
+		return 0;
+
+	un = tty->driver_data;
+	if (!un)
+		return 0;
+
+	ch = un->un_ch;
+	if (!ch)
+		return 0;
+
+	if (ch->ch_state != CS_READY)
+		return 0;
+
+	spin_lock_irqsave(&dgrp_poll_data.poll_lock, lock_flags);
+
+
+	/*
+	 *	If space is 0 and its because the ch->tbuf
+	 *	Warn and dump the character, there isn't anything else
+	 *	we can do about it.  David_Fries@digi.com
+	 */
+
+	space = (ch->ch_tout - ch->ch_tin - 1) & TBUF_MASK;
+
+	un->un_tbusy++;
+
+	/*
+	 * Output the printer on string if device is TXPrint.
+	 */
+	if (IS_PRINT(MINOR(tty_devnum(tty))) && (ch->ch_flag & CH_PRON) == 0) {
+		if (space < ch->ch_digi.digi_onlen) {
+			un->un_tbusy--;
+			goto out;
+		}
+		space -= ch->ch_digi.digi_onlen;
+		drp_wmove(ch, 0, ch->ch_digi.digi_onstr,
+			  ch->ch_digi.digi_onlen);
+		ch->ch_flag |= CH_PRON;
+	}
+
+	/*
+	 * Output the printer off string if device is NOT TXPrint.
+	 */
+
+	if (!IS_PRINT(MINOR(tty_devnum(tty))) &&
+	    ((ch->ch_flag & CH_PRON) != 0)) {
+		if (space < ch->ch_digi.digi_offlen) {
+			un->un_tbusy--;
+			goto out;
+		}
+
+		space -= ch->ch_digi.digi_offlen;
+		drp_wmove(ch, 0, ch->ch_digi.digi_offstr,
+			  ch->ch_digi.digi_offlen);
+		ch->ch_flag &= ~CH_PRON;
+	}
+
+	if (!space) {
+		un->un_tbusy--;
+		goto out;
+	}
+
+	/*
+	 *	Copy the character to the ch_tbuf being
+	 *	careful to wrap around the circular queue
+	 */
+	ch->ch_tbuf[ch->ch_tin] = new_char;
+	ch->ch_tin = (1 + ch->ch_tin) & TBUF_MASK;
+
+	if (IS_PRINT(MINOR(tty_devnum(tty)))) {
+
+		/*
+		 * Adjust ch_cpstime to account
+		 * for the character just output.
+		 */
+
+		int cc = HZ + ch->ch_cpsrem;
+
+		ch->ch_cpstime += cc / ch->ch_digi.digi_maxcps;
+		ch->ch_cpsrem   = cc % ch->ch_digi.digi_maxcps;
+
+		/*
+		 * If we are now waiting on time, schedule ourself
+		 * back when we'll be able to send a block of
+		 * digi_maxchar characters.
+		 */
+
+		ch->ch_waketime = (ch->ch_cpstime +
+			(ch->ch_digi.digi_maxchar * HZ /
+			ch->ch_digi.digi_maxcps));
+	}
+
+
+	un->un_tbusy--;
+	(ch->ch_nd)->nd_tx_work = 1;
+
+	retval = 1;
+out:
+	spin_unlock_irqrestore(&dgrp_poll_data.poll_lock, lock_flags);
+	return retval;
+}
+
+
+
+/*
+ *	Flush TX buffer (make in == out)
+ *
+ *	check tty_ioctl.c  -- this is called after TCOFLUSH
+ */
+static void dgrp_tty_flush_buffer(struct tty_struct *tty)
+{
+	struct un_struct *un;
+	struct ch_struct *ch;
+
+	if (!tty)
+		return;
+	un = tty->driver_data;
+	if (!un)
+		return;
+
+	ch = un->un_ch;
+	if (!ch)
+		return;
+
+	ch->ch_tout = ch->ch_tin;
+	/* do NOT do this here! */
+	/* ch->ch_s_tpos = ch->ch_s_tin = 0; */
+
+	/* send the flush output command now */
+	ch->ch_send |= RR_TX_FLUSH;
+	(ch->ch_nd)->nd_tx_ready = 1;
+	(ch->ch_nd)->nd_tx_work = 1;
+	wake_up_interruptible(&(ch->ch_nd)->nd_tx_waitq);
+
+	if (waitqueue_active(&tty->write_wait))
+		wake_up_interruptible(&tty->write_wait);
+
+	tty_wakeup(tty);
+
+}
+
+/*
+ *	Return space available in Tx buffer
+ *	count = ( ch->ch_tout - ch->ch_tin ) mod (TBUF_MAX - 1)
+ */
+static int dgrp_tty_write_room(struct tty_struct *tty)
+{
+	struct un_struct *un;
+	struct ch_struct *ch;
+	int	count;
+
+	if (!tty)
+		return 0;
+
+	un = tty->driver_data;
+	if (!un)
+		return 0;
+
+	ch = un->un_ch;
+	if (!ch)
+		return 0;
+
+	count = (ch->ch_tout - ch->ch_tin - 1) & TBUF_MASK;
+
+	/* We *MUST* check this, and return 0 if the Printer Unit cannot
+	 * take any more data within its time constraints...  If we don't
+	 * return 0 and the printer has hit it time constraint, the ld will
+	 * call us back doing a put_char, which cannot be rejected!!!
+	 */
+	if (IS_PRINT(MINOR(tty_devnum(tty)))) {
+		int un_flag = 0;
+		count = dgrp_calculate_txprint_bounds(ch, count, &un_flag);
+		if (count <= 0)
+			count = 0;
+
+		ch->ch_pun.un_flag |= un_flag;
+		(ch->ch_nd)->nd_tx_work = 1;
+	}
+
+	return count;
+}
+
+/*
+ *	Return number of characters that have not been transmitted yet.
+ *	chars_in_buffer = ( ch->ch_tin - ch->ch_tout ) mod (TBUF_MAX - 1)
+ *			+ ( ch->ch_s_tin - ch->ch_s_tout ) mod (0xffff)
+ *			= number of characters "in transit"
+ *
+ * Remember that sequence number math is always with a sixteen bit
+ * mask, not the TBUF_MASK.
+ */
+
+static int dgrp_tty_chars_in_buffer(struct tty_struct *tty)
+{
+	struct un_struct *un;
+	struct ch_struct *ch;
+	int	count;
+	int	count1;
+
+	if (!tty)
+		return 0;
+
+	un = tty->driver_data;
+	if (!un)
+		return 0;
+
+	ch = un->un_ch;
+	if (!ch)
+		return 0;
+
+	count1 = count = (ch->ch_tin - ch->ch_tout) & TBUF_MASK;
+	count += (ch->ch_s_tin - ch->ch_s_tpos) & 0xffff;
+	/* one for tbuf, one for the PS */
+
+	/*
+	 * If we are busy transmitting add 1
+	 */
+	count += un->un_tbusy;
+
+	return count;
+}
+
+
+/*****************************************************************************
+ *
+ * Helper applications for dgrp_tty_ioctl()
+ *
+ *****************************************************************************
+ */
+
+
+/**
+ * ch_to_tty_flags() -- convert channel flags to termio flags
+ * @ch_flag: Digi channel flags
+ * @flagtype: type of ch_flag (iflag, oflag or cflag)
+ *
+ * take the channel flags of the specified type and return the
+ * corresponding termio flag
+ */
+static tcflag_t ch_to_tty_flags(ushort ch_flag, char flagtype)
+{
+	tcflag_t retval = 0;
+
+	switch (flagtype) {
+	case 'i':
+		retval = ((ch_flag & IF_IGNBRK) ? IGNBRK : 0)
+		     | ((ch_flag & IF_BRKINT) ? BRKINT : 0)
+		     | ((ch_flag & IF_IGNPAR) ? IGNPAR : 0)
+		     | ((ch_flag & IF_PARMRK) ? PARMRK : 0)
+		     | ((ch_flag & IF_INPCK) ? INPCK  : 0)
+		     | ((ch_flag & IF_ISTRIP) ? ISTRIP : 0)
+		     | ((ch_flag & IF_IXON) ? IXON   : 0)
+		     | ((ch_flag & IF_IXANY) ? IXANY  : 0)
+		     | ((ch_flag & IF_IXOFF) ? IXOFF  : 0);
+		break;
+
+	case 'o':
+		retval = ((ch_flag & OF_OLCUC) ? OLCUC : 0)
+		     | ((ch_flag & OF_ONLCR) ? ONLCR  : 0)
+		     | ((ch_flag & OF_OCRNL) ? OCRNL  : 0)
+		     | ((ch_flag & OF_ONOCR) ? ONOCR  : 0)
+		     | ((ch_flag & OF_ONLRET) ? ONLRET : 0)
+		  /* | ((ch_flag & OF_OTAB3) ? OFILL  : 0) */
+		     | ((ch_flag & OF_TABDLY) ? TABDLY : 0);
+		break;
+
+	case 'c':
+		retval = ((ch_flag & CF_CSTOPB) ? CSTOPB : 0)
+		     | ((ch_flag & CF_CREAD) ? CREAD  : 0)
+		     | ((ch_flag & CF_PARENB) ? PARENB : 0)
+		     | ((ch_flag & CF_PARODD) ? PARODD : 0)
+		     | ((ch_flag & CF_HUPCL) ? HUPCL  : 0);
+
+		switch (ch_flag & CF_CSIZE) {
+		case CF_CS5:
+			retval |= CS5;
+			break;
+		case CF_CS6:
+			retval |= CS6;
+			break;
+		case CF_CS7:
+			retval |= CS7;
+			break;
+		case CF_CS8:
+			retval |= CS8;
+			break;
+		default:
+			retval |= CS8;
+			break;
+		}
+		break;
+	case 'x':
+		break;
+	case 'l':
+		break;
+	default:
+		return 0;
+	}
+
+	return retval;
+}
+
+
+/**
+ * tty_to_ch_flags() -- convert termio flags to digi channel flags
+ * @tty: pointer to a TTY structure holding flag to be converted
+ * @flagtype: identifies which flag (iflags, oflags, or cflags) should
+ *                 be converted
+ *
+ * take the termio flag of the specified type and return the
+ * corresponding Digi version of the flags
+ */
+static ushort tty_to_ch_flags(struct tty_struct *tty, char flagtype)
+{
+	ushort retval = 0;
+	tcflag_t tflag = 0;
+
+	switch (flagtype) {
+	case 'i':
+		tflag  = tty->termios.c_iflag;
+		retval = (I_IGNBRK(tty) ? IF_IGNBRK : 0)
+		      | (I_BRKINT(tty) ? IF_BRKINT : 0)
+		      | (I_IGNPAR(tty) ? IF_IGNPAR : 0)
+		      | (I_PARMRK(tty) ? IF_PARMRK : 0)
+		      | (I_INPCK(tty)  ? IF_INPCK  : 0)
+		      | (I_ISTRIP(tty) ? IF_ISTRIP : 0)
+		      | (I_IXON(tty)   ? IF_IXON   : 0)
+		      | (I_IXANY(tty)  ? IF_IXANY  : 0)
+		      | (I_IXOFF(tty)  ? IF_IXOFF  : 0);
+		break;
+	case 'o':
+		tflag  = tty->termios.c_oflag;
+		/*
+		 * If OPOST is set, then do the post processing in the
+		 * firmware by setting all the processing flags on.
+		 * If ~OPOST, then make sure we are not doing any
+		 * output processing!!
+		 */
+		if (!O_OPOST(tty))
+			retval = 0;
+		else
+			retval = (O_OLCUC(tty) ? OF_OLCUC : 0)
+			     | (O_ONLCR(tty)  ? OF_ONLCR  : 0)
+			     | (O_OCRNL(tty)  ? OF_OCRNL  : 0)
+			     | (O_ONOCR(tty)  ? OF_ONOCR  : 0)
+			     | (O_ONLRET(tty) ? OF_ONLRET : 0)
+			  /* | (O_OFILL(tty)  ? OF_TAB3   : 0) */
+			     | (O_TABDLY(tty) ? OF_TABDLY : 0);
+		break;
+	case 'c':
+		tflag  = tty->termios.c_cflag;
+		retval = (C_CSTOPB(tty) ? CF_CSTOPB : 0)
+		     | (C_CREAD(tty)  ? CF_CREAD  : 0)
+		     | (C_PARENB(tty) ? CF_PARENB : 0)
+		     | (C_PARODD(tty) ? CF_PARODD : 0)
+		     | (C_HUPCL(tty)  ? CF_HUPCL  : 0);
+		switch (C_CSIZE(tty)) {
+		case CS8:
+			retval |= CF_CS8;
+			break;
+		case CS7:
+			retval |= CF_CS7;
+			break;
+		case CS6:
+			retval |= CF_CS6;
+			break;
+		case CS5:
+			retval |= CF_CS5;
+			break;
+		default:
+			retval |= CF_CS8;
+			break;
+		}
+		break;
+	case 'x':
+		break;
+	case 'l':
+		break;
+	default:
+		return 0;
+	}
+
+	return retval;
+}
+
+
+static int dgrp_tty_send_break(struct tty_struct *tty, int msec)
+{
+	struct un_struct *un;
+	struct ch_struct *ch;
+	int ret = -EIO;
+
+	if (!tty)
+		return ret;
+
+	un = tty->driver_data;
+	if (!un)
+		return ret;
+
+	ch = un->un_ch;
+	if (!ch)
+		return ret;
+
+	dgrp_send_break(ch, msec);
+	return 0;
+}
+
+
+/*
+ * This routine sends a break character out the serial port.
+ *
+ * duration is in 1/1000's of a second
+ */
+static int dgrp_send_break(struct ch_struct *ch, int msec)
+{
+	ulong x;
+
+	wait_event_interruptible(ch->ch_flag_wait,
+		((ch->ch_flag & CH_TX_BREAK) == 0));
+	ch->ch_break_time += max(msec, 250);
+	ch->ch_send |= RR_TX_BREAK;
+	ch->ch_flag |= CH_TX_BREAK;
+	(ch->ch_nd)->nd_tx_work = 1;
+
+	x = (msec * HZ) / 1000;
+	wake_up_interruptible(&(ch->ch_nd)->nd_tx_waitq);
+
+	return 0;
+}
+
+
+/*
+ * Return modem signals to ld.
+ */
+static int dgrp_tty_tiocmget(struct tty_struct *tty)
+{
+	unsigned int mlast;
+	struct un_struct *un = tty->driver_data;
+	struct ch_struct *ch;
+
+	if (!un)
+		return -ENODEV;
+
+	ch = un->un_ch;
+	if (!ch)
+		return -ENODEV;
+
+	mlast = ((ch->ch_s_mlast & ~(DM_RTS | DM_DTR)) |
+		(ch->ch_mout & (DM_RTS | DM_DTR)));
+
+	/* defined in /usr/include/asm/termios.h */
+	mlast =   ((mlast & DM_RTS) ? TIOCM_RTS : 0)
+		| ((mlast & DM_DTR) ? TIOCM_DTR : 0)
+		| ((mlast & DM_CD)  ? TIOCM_CAR : 0)
+		| ((mlast & DM_RI)  ? TIOCM_RNG : 0)
+		| ((mlast & DM_DSR) ? TIOCM_DSR : 0)
+		| ((mlast & DM_CTS) ? TIOCM_CTS : 0);
+
+	return mlast;
+}
+
+
+/*
+ *      Set modem lines
+ */
+static int dgrp_tty_tiocmset(struct tty_struct *tty,
+			     unsigned int set, unsigned int clear)
+{
+	ulong lock_flags;
+	struct un_struct *un = tty->driver_data;
+	struct ch_struct *ch;
+
+	if (!un)
+		return -ENODEV;
+
+	ch = un->un_ch;
+	if (!ch)
+		return -ENODEV;
+
+	if (set & TIOCM_RTS)
+		ch->ch_mout |= DM_RTS;
+
+	if (set & TIOCM_DTR)
+		ch->ch_mout |= DM_DTR;
+
+	if (clear & TIOCM_RTS)
+		ch->ch_mout &= ~(DM_RTS);
+
+	if (clear & TIOCM_DTR)
+		ch->ch_mout &= ~(DM_DTR);
+
+	spin_lock_irqsave(&(ch->ch_nd)->nd_lock, lock_flags);
+	ch->ch_flag |= CH_PARAM;
+	(ch->ch_nd)->nd_tx_work = 1;
+	wake_up_interruptible(&ch->ch_flag_wait);
+
+	spin_unlock_irqrestore(&(ch->ch_nd)->nd_lock, lock_flags);
+
+	return 0;
+}
+
+
+
+/*
+ *      Get current modem status
+ */
+static int get_modem_info(struct ch_struct *ch, unsigned int *value)
+{
+	unsigned int mlast;
+
+	mlast = ((ch->ch_s_mlast & ~(DM_RTS | DM_DTR)) |
+		(ch->ch_mout    &  (DM_RTS | DM_DTR)));
+
+	/* defined in /usr/include/asm/termios.h */
+	mlast =   ((mlast & DM_RTS) ? TIOCM_RTS : 0)
+		| ((mlast & DM_DTR) ? TIOCM_DTR : 0)
+		| ((mlast & DM_CD)  ? TIOCM_CAR : 0)
+		| ((mlast & DM_RI)  ? TIOCM_RNG : 0)
+		| ((mlast & DM_DSR) ? TIOCM_DSR : 0)
+		| ((mlast & DM_CTS) ? TIOCM_CTS : 0);
+	put_user(mlast, (unsigned int __user *) value);
+
+	return 0;
+}
+
+/*
+ *      Set modem lines
+ */
+static int set_modem_info(struct ch_struct *ch, unsigned int command,
+			  unsigned int *value)
+{
+	int error;
+	unsigned int arg;
+	int mval = 0;
+	ulong lock_flags;
+
+	error = access_ok(VERIFY_READ, (void __user *) value, sizeof(int));
+	if (error == 0)
+		return -EFAULT;
+
+	get_user(arg, (unsigned int __user *) value);
+	mval |= ((arg & TIOCM_RTS) ? DM_RTS : 0)
+		| ((arg & TIOCM_DTR) ? DM_DTR : 0);
+
+	switch (command) {
+	case TIOCMBIS:  /* set flags */
+		ch->ch_mout |= mval;
+		break;
+	case TIOCMBIC:  /* clear flags */
+		ch->ch_mout &= ~mval;
+		break;
+	case TIOCMSET:
+		ch->ch_mout = mval;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	spin_lock_irqsave(&(ch->ch_nd)->nd_lock, lock_flags);
+
+	ch->ch_flag |= CH_PARAM;
+	(ch->ch_nd)->nd_tx_work = 1;
+	wake_up_interruptible(&ch->ch_flag_wait);
+
+	spin_unlock_irqrestore(&(ch->ch_nd)->nd_lock, lock_flags);
+
+	return 0;
+}
+
+
+/*
+ *  Assign the custom baud rate to the channel structure
+ */
+static void dgrp_set_custom_speed(struct ch_struct *ch, int newrate)
+{
+	int testdiv;
+	int testrate_high;
+	int testrate_low;
+
+	int deltahigh, deltalow;
+
+	if (newrate < 0)
+		newrate = 0;
+
+	/*
+	 * 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 < ((PORTSERVER_DIVIDEND / 0xFFFF) + 1))
+		newrate = ((PORTSERVER_DIVIDEND / 0xFFFF) + 1);
+	if (newrate && newrate > PORTSERVER_DIVIDEND)
+		newrate = PORTSERVER_DIVIDEND;
+
+	while (newrate > 0) {
+		testdiv = PORTSERVER_DIVIDEND / newrate;
+
+		/*
+		 * If we try to figure out what rate the PortServer 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 = PORTSERVER_DIVIDEND / testdiv;
+		testrate_low  = PORTSERVER_DIVIDEND / (testdiv + 1);
+
+		/*
+		 * If the rate for the requested divisor is correct, just
+		 * use it and be done.
+		 */
+		if (testrate_high == newrate)
+			break;
+
+		/*
+		 * 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;
+
+		break;
+	}
+
+	ch->ch_custom_speed = newrate;
+
+	drp_param(ch);
+
+	return;
+}
+
+
+/*
+ # dgrp_tty_digiseta()
+ *
+ * Ioctl to set the information from ditty.
+ *
+ * NOTE: DIGI_IXON, DSRPACE, DCDPACE, and DTRPACE are unsupported.  JAR 990922
+ */
+static int dgrp_tty_digiseta(struct tty_struct *tty,
+			     struct digi_struct *new_info)
+{
+	struct un_struct *un = tty->driver_data;
+	struct ch_struct *ch;
+
+	if (!un)
+		return -ENODEV;
+
+	ch = un->un_ch;
+	if (!ch)
+		return -ENODEV;
+
+	if (copy_from_user(&ch->ch_digi, (void __user *) new_info,
+			   sizeof(struct digi_struct)))
+		return -EFAULT;
+
+	if ((ch->ch_digi.digi_flags & RTSPACE) ||
+	    (ch->ch_digi.digi_flags & CTSPACE))
+		tty->termios.c_cflag |= CRTSCTS;
+	else
+		tty->termios.c_cflag &= ~CRTSCTS;
+
+	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;
+
+	/* make the changes now */
+	drp_param(ch);
+
+	return 0;
+}
+
+
+
+/*
+ * dgrp_tty_digigetedelay()
+ *
+ * Ioctl to get the current edelay setting.
+ *
+ *
+ *
+ */
+static int dgrp_tty_digigetedelay(struct tty_struct *tty, int *retinfo)
+{
+	struct un_struct *un;
+	struct ch_struct *ch;
+	int tmp;
+
+	if (!retinfo)
+		return -EFAULT;
+
+	if (!tty || tty->magic != TTY_MAGIC)
+		return -EFAULT;
+
+	un = tty->driver_data;
+
+	if (!un)
+		return -ENODEV;
+
+	ch = un->un_ch;
+	if (!ch)
+		return -ENODEV;
+
+	tmp = ch->ch_edelay;
+
+	if (copy_to_user((void __user *) retinfo, &tmp, sizeof(*retinfo)))
+		return -EFAULT;
+
+	return 0;
+}
+
+
+/*
+ * dgrp_tty_digisetedelay()
+ *
+ * Ioctl to set the EDELAY setting
+ *
+ */
+static int dgrp_tty_digisetedelay(struct tty_struct *tty, int *new_info)
+{
+	struct un_struct *un;
+	struct ch_struct *ch;
+	int new_digi;
+
+	if (!tty || tty->magic != TTY_MAGIC)
+		return -EFAULT;
+
+	un = tty->driver_data;
+
+	if (!un)
+		return -ENODEV;
+
+	ch = un->un_ch;
+	if (!ch)
+		return -ENODEV;
+
+	if (copy_from_user(&new_digi, (void __user *)new_info, sizeof(int)))
+		return -EFAULT;
+
+	ch->ch_edelay = new_digi;
+
+	/* make the changes now */
+	drp_param(ch);
+
+	return 0;
+}
+
+
+/*
+ *	The usual assortment of ioctl's
+ *
+ *	note:  use tty_check_change to make sure that we are not
+ *	changing the state of a terminal when we are not a process
+ *	in the forground.  See tty_io.c
+ *		rc = tty_check_change(tty);
+ *		if (rc) return rc;
+ */
+static int dgrp_tty_ioctl(struct tty_struct *tty, unsigned int cmd,
+			  unsigned long arg)
+{
+	struct un_struct *un;
+	struct ch_struct *ch;
+	int rc;
+	struct digiflow_struct   dflow;
+
+	if (!tty)
+		return -ENODEV;
+
+	un = tty->driver_data;
+	if (!un)
+		return -ENODEV;
+
+	ch = un->un_ch;
+	if (!ch)
+		return -ENODEV;
+
+	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
+		 */
+
+		rc = tty_check_change(tty);
+		if (rc)
+			return rc;
+		tty_wait_until_sent(tty, 0);
+
+		if (!arg)
+			rc = dgrp_send_break(ch, 250); /* 1/4 second */
+
+		if (dgrp_tty_chars_in_buffer(tty) != 0)
+			return -EINTR;
+
+		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);
+		if (rc)
+			return rc;
+		tty_wait_until_sent(tty, 0);
+
+		rc = dgrp_send_break(ch, arg ? arg*250 : 250);
+
+		if (dgrp_tty_chars_in_buffer(tty) != 0)
+			return -EINTR;
+		return 0;
+
+	case TIOCSBRK:
+		rc = tty_check_change(tty);
+		if (rc)
+			return rc;
+		tty_wait_until_sent(tty, 0);
+
+		/*
+		 * RealPort doesn't support turning on a break unconditionally.
+		 * The RealPort device will stop sending a break automatically
+		 * after the specified time value that we send in.
+		 */
+		rc = dgrp_send_break(ch, 250); /* 1/4 second */
+
+		if (dgrp_tty_chars_in_buffer(tty) != 0)
+			return -EINTR;
+		return 0;
+
+	case TIOCCBRK:
+		/*
+		 * RealPort doesn't support turning off a break unconditionally.
+		 * The RealPort device will stop sending a break automatically
+		 * after the specified time value that was sent when turning on
+		 * the break.
+		 */
+		return 0;
+
+	case TIOCGSOFTCAR:
+		rc = access_ok(VERIFY_WRITE, (void __user *) arg,
+			       sizeof(long));
+		if (rc == 0)
+			return -EFAULT;
+		put_user(C_CLOCAL(tty) ? 1 : 0, (unsigned long __user *) arg);
+		return 0;
+
+	case TIOCSSOFTCAR:
+		get_user(arg, (unsigned long __user *) arg);
+		tty->termios.c_cflag =
+			((tty->termios.c_cflag & ~CLOCAL) |
+			 (arg ? CLOCAL : 0));
+		return 0;
+
+	case TIOCMGET:
+		rc = access_ok(VERIFY_WRITE, (void __user *) arg,
+				 sizeof(unsigned int));
+		if (rc == 0)
+			return -EFAULT;
+		return get_modem_info(ch, (unsigned int *) arg);
+
+	case TIOCMBIS:
+	case TIOCMBIC:
+	case TIOCMSET:
+		return set_modem_info(ch, cmd, (unsigned int *) arg);
+
+	/*
+	 * 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)
+			return rc;
+
+		switch (arg) {
+		case TCIFLUSH:
+		case TCIOFLUSH:
+			/* only flush input if this is the only open unit */
+			if (!IS_PRINT(MINOR(tty_devnum(tty)))) {
+				ch->ch_rout = ch->ch_rin;
+				ch->ch_send |= RR_RX_FLUSH;
+				(ch->ch_nd)->nd_tx_work = 1;
+				(ch->ch_nd)->nd_tx_ready = 1;
+				wake_up_interruptible(&(ch->ch_nd)->nd_tx_waitq);
+			}
+			if (arg == TCIFLUSH)
+				break;
+
+		case TCOFLUSH: /* flush output, or the receive buffer */
+			/*
+			 * This is handled in the tty_ioctl.c code
+			 * calling tty_flush_buffer
+			 */
+			break;
+
+		default:
+			/* POSIX.1 says return EINVAL if we got a bad arg */
+			return -EINVAL;
+		}
+		/* pretend we didn't recognize this IOCTL */
+		return -ENOIOCTLCMD;
+
+#ifdef TIOCGETP
+	case TIOCGETP:
+#endif
+	/*****************************************
+	Linux		HPUX		Function
+	TCSETA		TCSETA		- set the termios
+	TCSETAF		TCSETAF		- wait for drain first, then set termios
+	TCSETAW		TCSETAW		- wait for drain, flush the input queue, then set termios
+	- looking at the tty_ioctl code, these command all call our
+	tty_set_termios at the driver's end, when a TCSETA* is sent,
+	it is expecting the tty to have a termio structure,
+	NOT a termios stucture.  These two structures differ in size
+	and the tty_ioctl code does a conversion before processing them both.
+	- we should treat the TCSETAW TCSETAF ioctls the same, and let
+	the tty_ioctl code do the conversion stuff.
+
+	TCSETS
+	TCSETSF		(none)
+	TCSETSW
+	- the associated tty structure has a termios structure.
+	*****************************************/
+
+	case TCGETS:
+	case TCGETA:
+		return -ENOIOCTLCMD;
+
+	case TCSETAW:
+	case TCSETAF:
+	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.
+		 */
+
+		/*
+		 * Also, now that we have TXPrint, we have to check
+		 * if this is the TXPrint device and the terminal
+		 * device is open. If so, do NOT run check_change,
+		 * as the terminal device is ALWAYS the parent.
+		 */
+		if (!IS_PRINT(MINOR(tty_devnum(tty))) ||
+		    !ch->ch_tun.un_open_count) {
+			rc = tty_check_change(tty);
+			if (rc)
+				return rc;
+		}
+
+		/* wait for all the characters in tbuf to drain */
+		tty_wait_until_sent(tty, 0);
+
+		if ((cmd == TCSETSF) || (cmd == TCSETAF)) {
+			/* flush the contents of the rbuf queue */
+			/* TODO:  check if this is print device? */
+			ch->ch_send |= RR_RX_FLUSH;
+			(ch->ch_nd)->nd_tx_ready = 1;
+			(ch->ch_nd)->nd_tx_work = 1;
+			wake_up_interruptible(&(ch->ch_nd)->nd_tx_waitq);
+			/* do we need to do this?  just to be safe! */
+			ch->ch_rout = ch->ch_rin;
+		}
+
+		/* pretend we didn't recognize this */
+		return -ENOIOCTLCMD;
+
+	case TCXONC:
+		/*
+		 * The Linux Line Discipline (LD) would do this for us if we
+		 * let it, but we have the special firmware options to do this
+		 * the "right way" regardless of hardware or software flow
+		 * control so we'll do it outselves instead of letting the LD
+		 * do it.
+		 */
+		rc = tty_check_change(tty);
+		if (rc)
+			return rc;
+
+		switch (arg) {
+		case TCOON:
+			dgrp_tty_start(tty);
+			return 0;
+		case TCOOFF:
+			dgrp_tty_stop(tty);
+			return 0;
+		case TCION:
+			dgrp_tty_input_start(tty);
+			return 0;
+		case TCIOFF:
+			dgrp_tty_input_stop(tty);
+			return 0;
+		default:
+			return -EINVAL;
+		}
+
+	case DIGI_GETA:
+		/* get information for ditty */
+		if (copy_to_user((struct digi_struct __user *) arg,
+				 &ch->ch_digi, sizeof(struct digi_struct)))
+			return -EFAULT;
+		break;
+
+	case DIGI_SETAW:
+	case DIGI_SETAF:
+		/* wait for all the characters in tbuf to drain */
+		tty_wait_until_sent(tty, 0);
+
+		if (cmd == DIGI_SETAF) {
+			/* flush the contents of the rbuf queue */
+			/* send down a packet with RR_RX_FLUSH set */
+			ch->ch_send |= RR_RX_FLUSH;
+			(ch->ch_nd)->nd_tx_ready = 1;
+			(ch->ch_nd)->nd_tx_work = 1;
+			wake_up_interruptible(&(ch->ch_nd)->nd_tx_waitq);
+			/* do we need to do this?  just to be safe! */
+			ch->ch_rout = ch->ch_rin;
+		}
+
+		/* pretend we didn't recognize this */
+
+	case DIGI_SETA:
+		return dgrp_tty_digiseta(tty, (struct digi_struct *) arg);
+
+	case DIGI_SEDELAY:
+		return dgrp_tty_digisetedelay(tty, (int *) arg);
+
+	case DIGI_GEDELAY:
+		return dgrp_tty_digigetedelay(tty, (int *) arg);
+
+	case DIGI_GETFLOW:
+	case DIGI_GETAFLOW:
+		if (cmd == (DIGI_GETFLOW)) {
+			dflow.startc = tty->termios.c_cc[VSTART];
+			dflow.stopc = tty->termios.c_cc[VSTOP];
+		} else {
+			dflow.startc = ch->ch_xxon;
+			dflow.stopc = ch->ch_xxoff;
+		}
+
+		if (copy_to_user((char __user *)arg, &dflow, sizeof(dflow)))
+			return -EFAULT;
+		break;
+
+	case DIGI_SETFLOW:
+	case DIGI_SETAFLOW:
+
+		if (copy_from_user(&dflow, (char __user *)arg, sizeof(dflow)))
+			return -EFAULT;
+
+		if (cmd == (DIGI_SETFLOW)) {
+			tty->termios.c_cc[VSTART] = dflow.startc;
+			tty->termios.c_cc[VSTOP] = dflow.stopc;
+		} else {
+			ch->ch_xxon = dflow.startc;
+			ch->ch_xxoff = dflow.stopc;
+		}
+		break;
+
+	case DIGI_GETCUSTOMBAUD:
+		rc = access_ok(VERIFY_WRITE, (void __user *) arg, sizeof(int));
+		if (rc == 0)
+			return -EFAULT;
+		put_user(ch->ch_custom_speed, (unsigned int __user *) arg);
+		break;
+
+	case DIGI_SETCUSTOMBAUD:
+	{
+		int new_rate;
+
+		get_user(new_rate, (unsigned int __user *) arg);
+		dgrp_set_custom_speed(ch, new_rate);
+
+		break;
+	}
+
+	default:
+		return -ENOIOCTLCMD;
+	}
+
+	return 0;
+}
+
+/*
+ *  This routine allows the tty driver to be notified when
+ *  the device's termios setting have changed.  Note that we
+ *  should be prepared to accept the case where old == NULL
+ *  and try to do something rational.
+ *
+ *  So we need to make sure that our copies of ch_oflag,
+ *  ch_clag, and ch_iflag reflect the tty->termios flags.
+ */
+static void dgrp_tty_set_termios(struct tty_struct *tty, struct ktermios *old)
+{
+	struct ktermios *ts;
+	struct ch_struct *ch;
+	struct un_struct *un;
+
+	/* seems silly, but we have to check all these! */
+	if (!tty)
+		return;
+
+	un = tty->driver_data;
+	if (!un)
+		return;
+
+	ts = &tty->termios;
+
+	ch = un->un_ch;
+	if (!ch)
+		return;
+
+	drp_param(ch);
+
+	/* the CLOCAL flag has just been set */
+	if (!(old->c_cflag & CLOCAL) && C_CLOCAL(tty))
+		wake_up_interruptible(&un->un_open_wait);
+}
+
+
+/*
+ *	Throttle receiving data.  We just set a bit and stop reading
+ *	data out of the channel buffer.  It will back up and the
+ *	FEP will do whatever is necessary to stop the far end.
+ */
+static void dgrp_tty_throttle(struct tty_struct *tty)
+{
+	struct ch_struct *ch;
+
+	if (!tty)
+		return;
+
+	ch = ((struct un_struct *) tty->driver_data)->un_ch;
+	if (!ch)
+		return;
+
+	ch->ch_flag |= CH_RXSTOP;
+}
+
+
+static void dgrp_tty_unthrottle(struct tty_struct *tty)
+{
+	struct ch_struct *ch;
+
+	if (!tty)
+		return;
+
+	ch = ((struct un_struct *) tty->driver_data)->un_ch;
+	if (!ch)
+		return;
+
+	ch->ch_flag &= ~CH_RXSTOP;
+}
+
+/*
+ *	Stop the transmitter
+ */
+static void dgrp_tty_stop(struct tty_struct *tty)
+{
+	struct ch_struct *ch;
+
+	if (!tty)
+		return;
+
+	ch = ((struct un_struct *) tty->driver_data)->un_ch;
+	if (!ch)
+		return;
+
+	ch->ch_send |= RR_TX_STOP;
+	ch->ch_send &= ~RR_TX_START;
+
+	/* make the change NOW! */
+	(ch->ch_nd)->nd_tx_ready = 1;
+	if (waitqueue_active(&(ch->ch_nd)->nd_tx_waitq))
+		wake_up_interruptible(&(ch->ch_nd)->nd_tx_waitq);
+}
+
+/*
+ *	Start the transmitter
+ */
+static void dgrp_tty_start(struct tty_struct *tty)
+{
+	struct ch_struct *ch;
+
+	if (!tty)
+		return;
+
+	ch = ((struct un_struct *) tty->driver_data)->un_ch;
+	if (!ch)
+		return;
+
+	/* TODO: don't do anything if the transmitter is not stopped */
+
+	ch->ch_send |= RR_TX_START;
+	ch->ch_send &= ~RR_TX_STOP;
+
+	/* make the change NOW! */
+	(ch->ch_nd)->nd_tx_ready = 1;
+	(ch->ch_nd)->nd_tx_work = 1;
+	if (waitqueue_active(&(ch->ch_nd)->nd_tx_waitq))
+		wake_up_interruptible(&(ch->ch_nd)->nd_tx_waitq);
+
+}
+
+/*
+ *	Stop the reciever
+ */
+static void dgrp_tty_input_stop(struct tty_struct *tty)
+{
+	struct ch_struct *ch;
+
+	if (!tty)
+		return;
+
+	ch = ((struct un_struct *) tty->driver_data)->un_ch;
+	if (!ch)
+		return;
+
+	ch->ch_send |= RR_RX_STOP;
+	ch->ch_send &= ~RR_RX_START;
+	(ch->ch_nd)->nd_tx_ready = 1;
+	if (waitqueue_active(&(ch->ch_nd)->nd_tx_waitq))
+		wake_up_interruptible(&(ch->ch_nd)->nd_tx_waitq);
+
+}
+
+
+static void dgrp_tty_send_xchar(struct tty_struct *tty, char c)
+{
+	struct un_struct *un;
+	struct ch_struct *ch;
+
+	if (!tty)
+		return;
+
+	un = tty->driver_data;
+	if (!un)
+		return;
+
+	ch = un->un_ch;
+	if (!ch)
+		return;
+	if (c == STOP_CHAR(tty))
+		ch->ch_send |= RR_RX_STOP;
+	else if (c == START_CHAR(tty))
+		ch->ch_send |= RR_RX_START;
+
+	ch->ch_nd->nd_tx_ready = 1;
+	ch->ch_nd->nd_tx_work = 1;
+
+	return;
+}
+
+
+static void dgrp_tty_input_start(struct tty_struct *tty)
+{
+	struct ch_struct *ch;
+
+	if (!tty)
+		return;
+
+	ch = ((struct un_struct *) tty->driver_data)->un_ch;
+	if (!ch)
+		return;
+
+	ch->ch_send |= RR_RX_START;
+	ch->ch_send &= ~RR_RX_STOP;
+	(ch->ch_nd)->nd_tx_ready = 1;
+	(ch->ch_nd)->nd_tx_work = 1;
+	if (waitqueue_active(&(ch->ch_nd)->nd_tx_waitq))
+		wake_up_interruptible(&(ch->ch_nd)->nd_tx_waitq);
+
+}
+
+
+/*
+ *	Hangup the port.  Like a close, but don't wait for output
+ *	to drain.
+ *
+ *	How do we close all the channels that are open?
+ */
+static void dgrp_tty_hangup(struct tty_struct *tty)
+{
+	struct ch_struct *ch;
+	struct nd_struct *nd;
+	struct un_struct *un;
+
+	if (!tty)
+		return;
+
+	un = tty->driver_data;
+	if (!un)
+		return;
+
+	ch = un->un_ch;
+	if (!ch)
+		return;
+
+	nd = ch->ch_nd;
+
+	if (C_HUPCL(tty)) {
+		/* LOWER DTR */
+		ch->ch_mout &= ~DM_DTR;
+		/* Don't do this here */
+		/* ch->ch_flag |= CH_HANGUP; */
+		ch->ch_nd->nd_tx_ready = 1;
+		ch->ch_nd->nd_tx_work  = 1;
+		if (waitqueue_active(&ch->ch_flag_wait))
+			wake_up_interruptible(&ch->ch_flag_wait);
+	}
+
+}
+
+/************************************************************************/
+/*                                                                      */
+/*       TTY Initialization/Cleanup Functions                           */
+/*                                                                      */
+/************************************************************************/
+
+/*
+ *	Uninitialize the TTY portion of the supplied node.  Free all
+ *      memory and resources associated with this node.  Do it in reverse
+ *      allocation order: this might possibly result in less fragmentation
+ *      of memory, though I don't know this for sure.
+ */
+void
+dgrp_tty_uninit(struct nd_struct *nd)
+{
+	char id[3];
+
+	ID_TO_CHAR(nd->nd_ID, id);
+
+	if (nd->nd_ttdriver_flags & SERIAL_TTDRV_REG) {
+		tty_unregister_driver(nd->nd_serial_ttdriver);
+
+		kfree(nd->nd_serial_ttdriver->ttys);
+		nd->nd_serial_ttdriver->ttys = NULL;
+
+		put_tty_driver(nd->nd_serial_ttdriver);
+		nd->nd_ttdriver_flags &= ~SERIAL_TTDRV_REG;
+	}
+
+	if (nd->nd_ttdriver_flags & CALLOUT_TTDRV_REG) {
+		tty_unregister_driver(nd->nd_callout_ttdriver);
+
+		kfree(nd->nd_callout_ttdriver->ttys);
+		nd->nd_callout_ttdriver->ttys = NULL;
+
+		put_tty_driver(nd->nd_callout_ttdriver);
+		nd->nd_ttdriver_flags &= ~CALLOUT_TTDRV_REG;
+	}
+
+	if (nd->nd_ttdriver_flags & XPRINT_TTDRV_REG) {
+		tty_unregister_driver(nd->nd_xprint_ttdriver);
+
+		kfree(nd->nd_xprint_ttdriver->ttys);
+		nd->nd_xprint_ttdriver->ttys = NULL;
+
+		put_tty_driver(nd->nd_xprint_ttdriver);
+		nd->nd_ttdriver_flags &= ~XPRINT_TTDRV_REG;
+	}
+}
+
+
+
+/*
+ *     Initialize the TTY portion of the supplied node.
+ */
+int
+dgrp_tty_init(struct nd_struct *nd)
+{
+	char id[3];
+	int  rc;
+	int  i;
+
+	ID_TO_CHAR(nd->nd_ID, id);
+
+	/*
+	 *  Initialize the TTDRIVER structures.
+	 */
+
+	nd->nd_serial_ttdriver = alloc_tty_driver(CHAN_MAX);
+	sprintf(nd->nd_serial_name,  "tty_dgrp_%s_", id);
+
+	nd->nd_serial_ttdriver->owner = THIS_MODULE;
+	nd->nd_serial_ttdriver->name = nd->nd_serial_name;
+	nd->nd_serial_ttdriver->name_base = 0;
+	nd->nd_serial_ttdriver->major = 0;
+	nd->nd_serial_ttdriver->minor_start = 0;
+	nd->nd_serial_ttdriver->type = TTY_DRIVER_TYPE_SERIAL;
+	nd->nd_serial_ttdriver->subtype = SERIAL_TYPE_NORMAL;
+	nd->nd_serial_ttdriver->init_termios = DefaultTermios;
+	nd->nd_serial_ttdriver->driver_name = "dgrp";
+	nd->nd_serial_ttdriver->flags = (TTY_DRIVER_REAL_RAW |
+					 TTY_DRIVER_DYNAMIC_DEV |
+					 TTY_DRIVER_HARDWARE_BREAK);
+
+	/* The kernel wants space to store pointers to tty_structs. */
+	nd->nd_serial_ttdriver->ttys =
+		kzalloc(CHAN_MAX * sizeof(struct tty_struct *), GFP_KERNEL);
+	if (!nd->nd_serial_ttdriver->ttys)
+		return -ENOMEM;
+
+	tty_set_operations(nd->nd_serial_ttdriver, &dgrp_tty_ops);
+
+	if (!(nd->nd_ttdriver_flags & SERIAL_TTDRV_REG)) {
+		/*
+		 *   Register tty devices
+		 */
+		rc = tty_register_driver(nd->nd_serial_ttdriver);
+		if (rc < 0) {
+			/*
+			 * If errno is EBUSY, this means there are no more
+			 * slots available to have us auto-majored.
+			 * (Which is currently supported up to 256)
+			 *
+			 * We can still request majors above 256,
+			 * we just have to do it manually.
+			 */
+			if (rc == -EBUSY) {
+				int i;
+				int max_majors = 1U << (32 - MINORBITS);
+				for (i = 256; i < max_majors; i++) {
+					nd->nd_serial_ttdriver->major = i;
+					rc = tty_register_driver(nd->nd_serial_ttdriver);
+					if (rc >= 0)
+						break;
+				}
+				/* Really fail now, since we ran out
+				 * of majors to try. */
+				if (i == max_majors)
+					return rc;
+
+			} else {
+				return rc;
+			}
+		}
+		nd->nd_ttdriver_flags |= SERIAL_TTDRV_REG;
+	}
+
+	nd->nd_callout_ttdriver = alloc_tty_driver(CHAN_MAX);
+	sprintf(nd->nd_callout_name, "cu_dgrp_%s_",  id);
+
+	nd->nd_callout_ttdriver->owner = THIS_MODULE;
+	nd->nd_callout_ttdriver->name = nd->nd_callout_name;
+	nd->nd_callout_ttdriver->name_base = 0;
+	nd->nd_callout_ttdriver->major = nd->nd_serial_ttdriver->major;
+	nd->nd_callout_ttdriver->minor_start = 0x40;
+	nd->nd_callout_ttdriver->type = TTY_DRIVER_TYPE_SERIAL;
+	nd->nd_callout_ttdriver->subtype = SERIAL_TYPE_CALLOUT;
+	nd->nd_callout_ttdriver->init_termios = DefaultTermios;
+	nd->nd_callout_ttdriver->driver_name = "dgrp";
+	nd->nd_callout_ttdriver->flags = (TTY_DRIVER_REAL_RAW |
+					  TTY_DRIVER_DYNAMIC_DEV |
+					  TTY_DRIVER_HARDWARE_BREAK);
+
+	/* The kernel wants space to store pointers to tty_structs. */
+	nd->nd_callout_ttdriver->ttys =
+		kzalloc(CHAN_MAX * sizeof(struct tty_struct *), GFP_KERNEL);
+	if (!nd->nd_callout_ttdriver->ttys)
+		return -ENOMEM;
+
+	tty_set_operations(nd->nd_callout_ttdriver, &dgrp_tty_ops);
+
+	if (dgrp_register_cudevices) {
+		if (!(nd->nd_ttdriver_flags & CALLOUT_TTDRV_REG)) {
+			/*
+			 *   Register cu devices
+			 */
+			rc = tty_register_driver(nd->nd_callout_ttdriver);
+			if (rc < 0)
+				return rc;
+			nd->nd_ttdriver_flags |= CALLOUT_TTDRV_REG;
+		}
+	}
+
+
+	nd->nd_xprint_ttdriver = alloc_tty_driver(CHAN_MAX);
+	sprintf(nd->nd_xprint_name,  "pr_dgrp_%s_", id);
+
+	nd->nd_xprint_ttdriver->owner = THIS_MODULE;
+	nd->nd_xprint_ttdriver->name = nd->nd_xprint_name;
+	nd->nd_xprint_ttdriver->name_base = 0;
+	nd->nd_xprint_ttdriver->major = nd->nd_serial_ttdriver->major;
+	nd->nd_xprint_ttdriver->minor_start = 0x80;
+	nd->nd_xprint_ttdriver->type = TTY_DRIVER_TYPE_SERIAL;
+	nd->nd_xprint_ttdriver->subtype = SERIAL_TYPE_XPRINT;
+	nd->nd_xprint_ttdriver->init_termios = DefaultTermios;
+	nd->nd_xprint_ttdriver->driver_name = "dgrp";
+	nd->nd_xprint_ttdriver->flags = (TTY_DRIVER_REAL_RAW |
+					 TTY_DRIVER_DYNAMIC_DEV |
+					 TTY_DRIVER_HARDWARE_BREAK);
+
+	/* The kernel wants space to store pointers to tty_structs. */
+	nd->nd_xprint_ttdriver->ttys =
+		kzalloc(CHAN_MAX * sizeof(struct tty_struct *), GFP_KERNEL);
+	if (!nd->nd_xprint_ttdriver->ttys)
+		return -ENOMEM;
+
+	tty_set_operations(nd->nd_xprint_ttdriver, &dgrp_tty_ops);
+
+	if (dgrp_register_prdevices) {
+		if (!(nd->nd_ttdriver_flags & XPRINT_TTDRV_REG)) {
+			/*
+			 *   Register transparent print devices
+			 */
+			rc = tty_register_driver(nd->nd_xprint_ttdriver);
+			if (rc < 0)
+				return rc;
+			nd->nd_ttdriver_flags |= XPRINT_TTDRV_REG;
+		}
+	}
+
+	for (i = 0; i < CHAN_MAX; i++) {
+		struct ch_struct *ch = nd->nd_chan + i;
+
+		ch->ch_nd = nd;
+		ch->ch_digi = digi_init;
+		ch->ch_edelay = 100;
+		ch->ch_custom_speed = 0;
+		ch->ch_portnum = i;
+		ch->ch_tun.un_ch = ch;
+		ch->ch_pun.un_ch = ch;
+		ch->ch_tun.un_type = SERIAL_TYPE_NORMAL;
+		ch->ch_pun.un_type = SERIAL_TYPE_XPRINT;
+
+		init_waitqueue_head(&(ch->ch_flag_wait));
+		init_waitqueue_head(&(ch->ch_sleep));
+
+		init_waitqueue_head(&(ch->ch_tun.un_open_wait));
+		init_waitqueue_head(&(ch->ch_tun.un_close_wait));
+
+		init_waitqueue_head(&(ch->ch_pun.un_open_wait));
+		init_waitqueue_head(&(ch->ch_pun.un_close_wait));
+		tty_port_init(&ch->port);
+		tty_port_init(&ch->port);
+	}
+	return 0;
+}