Kernel  |  3.14

下载     查看原文件
C++程序  |  3330行  |  73.31 KB
/*
 *
 * 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/device.h>
#include <linux/sched.h>
#include <linux/uaccess.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 changeable 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);

				spin_unlock_irqrestore(&nd->nd_lock, lock_flags);
				tty_ldisc_flush(tty);
				spin_lock_irqsave(&nd->nd_lock, lock_flags);
				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);
	return put_user(mlast, (unsigned int __user *) value);
}

/*
 *      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;

	if (get_user(arg, (unsigned int __user *) value))
		return -EFAULT;
	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 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 structure.  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:
		if (put_user(ch->ch_custom_speed, (unsigned int __user *) arg))
			return -EFAULT;
		break;

	case DIGI_SETCUSTOMBAUD:
	{
		int new_rate;

		if (get_user(new_rate, (unsigned int __user *) arg))
			return -EFAULT;
		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 receiver
 */
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)
{
	unsigned int i;
	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;
	}
	for (i = 0; i < CHAN_MAX; i++)
		tty_port_destroy(&nd->nd_chan[i].port);
}



/*
 *     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);
	if (!nd->nd_serial_ttdriver)
		return -ENOMEM;

	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);
	if (!nd->nd_callout_ttdriver)
		return -ENOMEM;

	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);
	if (!nd->nd_xprint_ttdriver)
		return -ENOMEM;

	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);
	}
	return 0;
}