/*****************************************************************************
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *
 *	NOTE TO LINUX KERNEL HACKERS:  DO NOT REFORMAT THIS CODE!
 *
 *	This is shared code between Digi's CVS archive and the
 *	Linux Kernel sources.
 *	Changing the source just for reformatting needlessly breaks
 *	our CVS diff history.
 *
 *	Send any bug fixes/changes to:  Eng.Linux at digi dot com.
 *	Thank you.
 *
 *****************************************************************************/

/****************************************************************************
 *
 *  Filename:
 *
 *     $Id: dgrp_tty.c,v 1.20 2024/12/19 22:29:45 postebee Exp $
 *
 *  Description:
 *
 *     This file implements the tty driver functionality for the
 *     RealPort driver software.
 *
 *  Author:
 *
 *     James A. Puzzo
 *     Ann-Marie Westgate
 *
 *****************************************************************************/

#include "linux_ver_fix.h"

#include <linux/tty.h>
#include <linux/tty_flip.h>
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,11,0)
#include <linux/sched.h>	/* For in_egroup_p() and signal_pending() */
#else
#include <linux/sched/signal.h>	/* For in_egroup_p() and signal_pending() */
#endif

#include <linux/serial.h>
#include <linux/termios.h>
#include <linux/delay.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0)
#include <linux/slab.h>
#endif

#include "dgrp_dpa_ops.h"
#include "dgrp_common.h"
#include "dgrp_tty.h"
#include "dgrp_sysfs.h"

#ifndef _POSIX_VDISABLE
#define   _POSIX_VDISABLE '\0'
#endif

#ifndef USHRT_MAX
#define USHRT_MAX       65535
#endif

/*
 *	forward declarations
 */

static void drp_param(struct ch_struct *);
static int drp_wait_ack(struct ch_struct *, unsigned long *, int ttylock);
static void dgrp_tty_close(struct tty_struct *, struct file *);

/* ioctl helper functions */
static int dgrp_print_KME(struct ch_struct *);
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 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 *);
#if LINUX_VERSION_CODE < KERNEL_VERSION(6,6,0) && !IS_RHEL_9_5
static int dgrp_tty_write(struct tty_struct *, const unsigned char *, int);
#else
static ssize_t dgrp_tty_write(struct tty_struct *, const u8 *, size_t);
#endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(5,14,0)
static int dgrp_tty_write_room(struct tty_struct *);
static int dgrp_tty_chars_in_buffer(struct tty_struct *);
#else
static unsigned int dgrp_tty_write_room(struct tty_struct *);
static unsigned int dgrp_tty_chars_in_buffer(struct tty_struct *);
#endif
static void dgrp_tty_flush_buffer(struct tty_struct *);
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,0,0)
static int dgrp_tty_ioctl(struct tty_struct *, struct file *, unsigned int, unsigned long);
#else
static int dgrp_tty_ioctl(struct tty_struct *, unsigned int, unsigned long);
#endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(6,1,0) && !IS_RHEL_9_5
static void dgrp_tty_set_termios(struct tty_struct *, struct ktermios *);
#else
static void dgrp_tty_set_termios(struct tty_struct *, const struct ktermios *);
#endif
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);
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,0,0)
static int dgrp_tty_tiocmget(struct tty_struct *, struct file *);
static int dgrp_tty_tiocmset(struct tty_struct *, struct file *, unsigned int, unsigned int);
#else
static int dgrp_tty_tiocmget(struct tty_struct *);
static int dgrp_tty_tiocmset(struct tty_struct *, unsigned int, unsigned int);
#endif
static int dgrp_tty_send_break(struct tty_struct *, int);
#if LINUX_VERSION_CODE < KERNEL_VERSION(6,6,0)
static void dgrp_tty_send_xchar(struct tty_struct *, char);
#else
static void dgrp_tty_send_xchar(struct tty_struct *, u8);
#endif
#ifdef MISSING_TTY_CHECK_CHANGE
/* they decided that tty_check_change was internal to tty subsystem and
 * no longer export it; now it is down in kernel/driver/tty/.  So far
 * the symbol is still visible though, so can get away with a local decl.
 *
 * This change was implemented in the mainline kernel after v5.12.19,
 * but it has been backported to earlier kernels for some distros (e.g:
 * Debian 11's 5.10.0-28 kernel).  So now, rather than:
 *   #if LINUX_VERSION_CODE > KERNEL_VERSION(5,12,19)
 * we instead check for the declaration and optionally set
 * MISSING_TTY_CHECK_CHANGE in the Makefile.
 */
int tty_check_change(struct tty_struct *t);
#endif


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

#if LINUX_VERSION_CODE >= KERNEL_VERSION(6,1,0) || IS_RHEL_9_5
//PEB:  From v6.1 termios_internal.h:

/*	intr=^C		quit=^\		erase=del	kill=^U
	eof=^D		vtime=\0	vmin=\1		sxtc=\0
	start=^Q	stop=^S		susp=^Z		eol=\0
	reprint=^R	discard=^O	werase=^W	lnext=^V
	eol2=\0
*/

#ifdef VDSUSP
#define INIT_C_CC_VDSUSP_EXTRA [VDSUSP] = 'Y'-0x40,
#else
#define INIT_C_CC_VDSUSP_EXTRA
#endif

#define INIT_C_CC {		\
	[VINTR] = 'C'-0x40,	\
	[VQUIT] = '\\'-0x40,	\
	[VERASE] = '\177',	\
	[VKILL] = 'U'-0x40,	\
	[VEOF] = 'D'-0x40,	\
	[VSTART] = 'Q'-0x40,	\
	[VSTOP] = 'S'-0x40,	\
	[VSUSP] = 'Z'-0x40,	\
	[VREPRINT] = 'R'-0x40,	\
	[VDISCARD] = 'O'-0x40,	\
	[VWERASE] = 'W'-0x40,	\
	[VLNEXT] = 'V'-0x40,	\
	INIT_C_CC_VDSUSP_EXTRA	\
	[VMIN] = 1 }
#endif

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



/*****************************************************************************
*
* Function:
*
*    drp_param
*
* Author:
*
*    Gene Olson (under HP-UX)
*
* Parameters:
*
*    struct ch_struct *ch     Channel structure of port to modify
*
* Return Values:
*
*    none
*
* Description:
*
*    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;
	ushort cook;
	tcflag_t oflag;

	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) {

		dbg_tty_trace(IOCTL, ("drp_param(%x): start\n",
			MINOR(tty_devnum(ch->ch_tun.un_tty))));

		dbg_tty_trace(IOCTL, ("drp_param(%x): TOSTOP %s set\n",
			MINOR(tty_devnum(ch->ch_tun.un_tty)),
			L_TOSTOP(ch->ch_tun.un_tty) == 0 ? "is not" : "is"));

		un = &ch->ch_tun;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,0)
		tts = &(ch->ch_tun.un_tty->termios);
#else
		tts = ch->ch_tun.un_tty->termios;
#endif

		/*
		 *  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) {

#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,0)
			pts = &(ch->ch_pun.un_tty->termios);
#else
			pts = ch->ch_pun.un_tty->termios;
#endif

			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) {
		/* this shouldn't happen!! */
		assert(ch->ch_pun.un_open_count != 0);
		return;
	} else {
		dbg_tty_trace(IOCTL, ("drp_param(%x): start\n",
			MINOR(tty_devnum(ch->ch_pun.un_tty))));

		dbg_tty_trace(IOCTL, ("drp_param(%x): TOSTOP %s set\n",
			MINOR(tty_devnum(ch->ch_pun.un_tty)),
			L_TOSTOP(ch->ch_pun.un_tty) == 0 ? "is not" : "is"));

		un = &ch->ch_pun;
	}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,0)
	uts = &(un->un_tty->termios);
#else
	uts = un->un_tty->termios;
#endif

	/*
	 * Determine if FAST writes can be performed.
	 */

	if ((ch->ch_digi.digi_flags & DIGI_COOK) != 0 &&
	    (ch->ch_tun.un_open_count != 0)  &&
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,31)
 #if LINUX_VERSION_CODE < KERNEL_VERSION(5,18,0) && !IS_RHEL_9_5
	    !((un->un_tty)->ldisc->ops->flags & LDISC_FLAG_DEFINED) &&
 #endif
#else
	    !((un->un_tty)->ldisc.ops->flags & LDISC_FLAG_DEFINED) &&
#endif
	    !(L_XCASE(un->un_tty))) {
		ch->ch_flag |= CH_FAST_WRITE;
	} else {
		ch->ch_flag &= ~CH_FAST_WRITE;
	}

	/* 2 things to do vis.a.vis. c_oflags
	 *   1. get it translated to channel 'cook' bits
	 *      Had to change tty_to_ch_flags so that it would not be just 0
	 *      when OFLAGS are not set.  That is a seperate decision.
	 *      And this is the only call to that fnc with 'o'
	 *   2. map that cook back to tty-land oflags to show which of the
	 *      requested oflags we actually support.
	 */
	cook = tty_to_ch_flags(un->un_tty, 'o');
	oflag = ch_to_tty_flags(cook, 'o');
	/* if OPOST is set in the tty termios, then 2 things:
	 * 1. assign cook to ch_cook to implement it.
	 * 2. make sure to OR back in OPOST from the supported bits when we
	 *    map back from ch_to_tty
	 * Otherwise, the resultant tty termios c_oflag is the supported bits
	 * that survived translation to & from cook.
	 */
	if (O_OPOST(un->un_tty)) {
		if (ch->ch_flag & CH_FAST_WRITE)
			ch->ch_ocook = cook;
		uts->c_oflag = oflag | OPOST;
	} else {
		ch->ch_ocook = 0;
		uts->c_oflag = oflag;
	}

	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)) {
			dbg_tty_trace(IOCTL, ("dgrp_param(%x) DTR dropped!!! flushing...\n",
				MINOR(tty_devnum(un->un_tty))));

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

		dbg_tty_trace(IOCTL, ("tty param -- cspeed (%d)baud (%d)divisor\n",
			ch->ch_custom_speed, ch->ch_brate));

		if (ch->ch_flag & CH_BAUD0) {
			dbg_tty_trace(IOCTL, ("dgrp_param(%x) recovering from B0\n",
				MINOR(tty_devnum(un->un_tty))));

			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)) {
			int i = 0;

			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;
			}

			brate = baud_rates[i].rate;
		}

		/*
		 * If fastbaud mapping is on, we must use
		 * our "old" style baud rate table.
		 */
		else {
			int iindex = 0;
			int jindex = 0;
			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;

			if (uts->c_cflag & CBAUDEX)
				iindex = 1;

			jindex = brate;

			if ((iindex >= 0) && (iindex < 2) && (jindex >= 0) && (jindex < 16)) {
				brate = bauds[iindex][jindex];
			} else {
				dbg_tty_trace(IOCTL, ("brate indices were out of range (%d)(%d)",
					iindex, jindex));
				brate = 0;
			}

		}

		if (brate == 0)
			brate = 9600;

		ch->ch_brate = PORTSERVER_DIVIDEND / brate;

		dbg_tty_trace(IOCTL, ("tty param -- (%d)baud (%d)divisor\n",
				brate, ch->ch_brate));

		if (ch->ch_flag & CH_BAUD0) {
			dbg_tty_trace(IOCTL, ("dgrp_param(%x) recovering from B0\n",
					MINOR(tty_devnum(un->un_tty))));

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



/*****************************************************************************
*
* Function:
*
*    drp_wait_ack
*
* Author:
*
*    Gene Olson (HP-UX)
*    James Puzzo (linux port)
*
* Parameters:
*
*    struct ch_struct *ch            Channel structure
*    ulong *lock_flags  Flags associated with currently held lock
*    int ttylock      whether need to step around gbl.tty lock
*
* Return Values:
*
*    0        Success
*    -EINTR   Wait was interrupted
*
* Description:
*
*    Waits for the server to acknowledge all pending changes, according
*    to the variable GLBL(wait_control).  Returns non-zero if the wait
*    was interrupted.
*
*    Called if wait_control is non-zero
*
******************************************************************************/

static int drp_wait_ack(struct ch_struct *ch, ulong *lock_flags, int ttylock)
{
	struct nd_struct *nd;
	int in;
	int i;
	int n;

	DECLARE_WAITQUEUE(wait, current);

	nd = ch->ch_nd;

	/*
	 *  Forget the wait if there are no pending changes.
	 */

	if (ch->ch_send    == 0            &&
	    ch->ch_s_brate == ch->ch_brate &&
	    ch->ch_s_cflag == ch->ch_cflag &&
	    ch->ch_s_iflag == ch->ch_iflag &&
	    ch->ch_s_oflag == ch->ch_oflag &&
	    ch->ch_s_xflag == ch->ch_xflag &&
	    ch->ch_s_mout  == ch->ch_mout  &&
	    ch->ch_s_mflow == ch->ch_mflow &&
	    ch->ch_s_mctrl == ch->ch_mctrl &&
	    ch->ch_s_xon   == ch->ch_xon   &&
	    ch->ch_s_xoff  == ch->ch_xoff  &&
	    ch->ch_s_lnext == ch->ch_lnext &&
	    ch->ch_s_xxon  == ch->ch_xxon  &&
	    ch->ch_s_xxoff == ch->ch_xxoff) {
		return 0;
	}

	/*
	 *  Loop to wait one or more round trip times to the server.
	 *
	 *  There's no theoretical base here; just crank up the number
	 *  until the Posix tests are stable.
	 *
	 *  This is a rubber band, scotch tape, coathanger kludge.
	 *  Just like doctoring science lab results in high school.
	 */
	/*
	 *  TODO : rewrite the above comment
	 */

	n = GLBL(wait_control) & 0xf;

	for (i = 0; i < n; i++) {
		/*
		 *  Wait for a round-trip acknowledgement from the server.
		 */

		in = nd->nd_seq_in;

		nd->nd_seq_wait[in] = 1;

		nd->nd_tx_work = 1;

		for (;;) {
			/*
			 *  Prepare the task to accept the wakeup, then
			 *  release our locks and release control.
			 */

			add_wait_queue(&nd->nd_seq_wque[in], &wait);
#ifdef __set_current_state
			__set_current_state(TASK_INTERRUPTIBLE);
#else
			current->state = TASK_INTERRUPTIBLE;
#endif

			DGRP_UNLOCK(nd->nd_lock, *lock_flags);

			/*
			 *  Give up control, we'll come back if we're
			 *  interrupted or are woken up.
			 */
/* Note: I think this is a dead codepath since I don't see
 * any code that sets GLBL(wait_control) */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0)
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0)
if (ttylock) tty_unlock();
#else
if (ttylock) tty_unlock(ch->port.tty);
#endif
#endif

			schedule();

#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0)
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0)
if (ttylock) tty_lock();
#else
if (ttylock) tty_lock(ch->port.tty);
#endif
#endif

			remove_wait_queue(&nd->nd_seq_wque[in], &wait);

			DGRP_LOCK(nd->nd_lock, *lock_flags);

#ifdef __set_current_state
			__set_current_state(TASK_RUNNING);
#else
			current->state = TASK_RUNNING;
#endif

			if (signal_pending(current))
				return -EINTR;

			if (nd->nd_seq_wait[in] == 0)
				break;
		}
	}

	return 0;
}


/*
 * This function is just used as a callback for timeouts
 * waiting on the ch_sleep flag.
 */
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,15,0)
static void wake_up_drp_sleep_timer(unsigned long ptr)
#else
static void wake_up_drp_sleep_timer(struct timer_list *t)
#endif
{
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,15,0)
	struct ch_struct *ch = (struct ch_struct *) ptr;
#else
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6,16,0) || !defined from_timer
    struct ch_struct *ch = timer_container_of(ch, t, drp_wakeup_timer);
#else
	struct ch_struct *ch = from_timer(ch, t, drp_wakeup_timer);
#endif
#endif

	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)
{
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,15,0)
	struct timer_list drp_wakeup_timer;
#endif
	DECLARE_WAITQUEUE(wait, current);

	/*
	 * First make sure we're ready to receive the wakeup.
	 */

	add_wait_queue(&ch->ch_sleep, &wait);
#ifdef __set_current_state
	__set_current_state(TASK_UNINTERRUPTIBLE);
#else
	current->state = TASK_UNINTERRUPTIBLE;
#endif

	/*
	 * Since we are uninterruptible, set a timer to
	 * unset the uninterruptable state in 1 second.
	 */


#if LINUX_VERSION_CODE < KERNEL_VERSION(4,15,0)
	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);
#else
	timer_setup(&ch->drp_wakeup_timer, wake_up_drp_sleep_timer, 0);
	ch->drp_wakeup_timer.expires = jiffies + (1 * HZ);

	add_timer(&ch->drp_wakeup_timer);
	schedule();
#if LINUX_VERSION_CODE < KERNEL_VERSION(6,15,0)
	del_timer(&ch->drp_wakeup_timer);
#else
	timer_delete(&ch->drp_wakeup_timer);
#endif
#endif

	remove_wait_queue(&ch->ch_sleep, &wait);
}


/*****************************************************************************
*
* Function:
*
*    dgrp_tty_open
*
* Author:
*
*    Gene Olson (under HP-UX)
*    James Puzzo (linux port)
*
* Parameters:
*
*    struct tty_struct	*tty	TTY structure for port to open
*    struct file	*file	FILE pointer for port to open
*
* Return Values:
*
*    -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
*
* Description:
*
*    - the open returns immediately if the un or ch do not exist, so
*      we don't try to access these structures by accident
*    - tty_io.c from the driver source package does a close for every
*      open.  So we must also increment our counts for EVERY open to
*      remain consistent.
*
******************************************************************************/

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);


	dbg_tty_trace(OPEN, ("tty open: (%x)\n", MINOR(tty_devnum(tty))));

	retval = 0;

	/*
	 * Do some initial checks to see if the node and port exist
	 */

	nd = nd_struct_get(MAJOR(tty_devnum(tty)));
	/* Jira RP48: "nd_open_count" gives "dgrp_cfg_node uninit" a way to
	 * see that a node is busy even before the open finishes */
	if (nd) {
		DGRP_LOCK(nd->nd_lock, lock_flags);
		nd->nd_open_count++;
		DGRP_UNLOCK(nd->nd_lock, lock_flags);
	}


	port = PORT_NUM(MINOR(tty_devnum(tty)));
	category = OPEN_CATEGORY(MINOR(tty_devnum(tty)));

	if (!nd) {
		dbg_tty_trace(OPEN, ("tty open: unknown major number (%d)\n",
					MAJOR(tty_devnum(tty))));
		return -ENODEV;
	}

	if (port >= CHAN_MAX) {
		dbg_tty_trace(OPEN, ("tty open: port number out of bounds "
					"(%d)\n", port));
		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 ((DEBUG_KME & GLBL(tty_debug)) != 0)
		dgrp_print_KME(ch);

	/*
	 * If we are in the middle of hanging up,
	 * then return an error
	 */
	if (tty_hung_up_p(file)) {
		dbg_tty_trace(OPEN, ("tty_open:(%x) tty_hung_up_p\n",
			MINOR(tty_devnum(tty))));
		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.
	 */
	dbg_tty_trace(OPEN, ("tty_open:(%x) UN_CLOSING sleeping\n",
				MINOR(tty_devnum(tty))));

	retval = wait_event_interruptible(un->un_close_wait,
			((un->un_flag & UN_CLOSING) == 0));

	dbg_tty_trace(OPEN, ("tty_open:(%x) UN_CLOSING done sleeping\n",
				MINOR(tty_devnum(tty))));

	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.
	 */
	dbg_tty_trace(OPEN, ("tty_open:(%x) CH_PORT_GONE sleeping\n",
				MINOR(tty_devnum(tty))));

	retval = wait_event_interruptible(ch->ch_flag_wait,
			((ch->ch_flag & CH_PORT_GONE) == 0));

	dbg_tty_trace(OPEN, ("tty_open:(%x) CH_PORT_GONE done sleeping\n",
				MINOR(tty_devnum(tty))));

	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;
			dbg_tty_trace(OPEN, ("tty_open(%x) - NORMAL_ACTIVE\n",
					MINOR(tty_devnum(tty))));
			goto done;
		} else {
			un->un_flag |= UN_CALLOUT_ACTIVE;
			dbg_tty_trace(OPEN, ("tty_open(%x) - CALLOUT_ACTIVE\n",
					MINOR(tty_devnum(tty))));
		}
	}

	/*
	 *  Loop waiting until the open can be successfully completed.
	 */

	DGRP_LOCK(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.
			 */

			dbg_tty_trace(OPEN, ("tty_open:(%x) OTYPE_IMMEDIATE, O_NONBLOCK\n",
					MINOR(tty_devnum(tty))));
			otype = OTYPE_IMMEDIATE;
			delay_error = -EAGAIN;

		} else if (!OPEN_WAIT_AVAIL(category) ||
			  (file->f_flags & O_NDELAY) != 0) {

			dbg_tty_trace(OPEN, ("tty_open:(%x) OTYPE_IMMEDIATE2\n",
					MINOR(tty_devnum(tty))));
			otype = OTYPE_IMMEDIATE;
			delay_error = -EBUSY;

		} else if (!OPEN_WAIT_CARRIER(category) ||
			  ((ch->ch_digi.digi_flags & DIGI_FORCEDCD) != 0) ||
			  C_CLOCAL(tty)) {

			dbg_tty_trace(OPEN, ("tty_open:(%x) OTYPE_PERSISTENT\n",
					MINOR(tty_devnum(tty))));
			otype = OTYPE_PERSISTENT;
			delay_error = 0;

		} else {

			dbg_tty_trace(OPEN, ("tty_open:(%x) OTYPE_INCOMING\n",
					MINOR(tty_devnum(tty))));
			otype = OTYPE_INCOMING;
			delay_error = 0;
		}

		/*
		 * Handle port currently outside physical port range.
		 */

		if (port >= nd->nd_chan_count) {
			if (otype == OTYPE_IMMEDIATE) {
				dbg_tty_trace(OPEN, ("tty_open:(%x) out of range\n",
					MINOR(tty_devnum(tty))));
				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) {
				dbg_tty_trace(OPEN, ("tty_open:(%x) port is busy\n",
					MINOR(tty_devnum(tty))));
				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) {
				dbg_tty_trace(OPEN, ("tty_open:(%x) port is not NS_READY\n",
					MINOR(tty_devnum(tty))));
				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;
			else
				dbg_tty_trace(OPEN, ("tty open:(%x) port not CS_READY yet"
					" or otype(%x) not ch_otype(%x)\n",
					MINOR(tty_devnum(tty)), otype,
					ch->ch_otype));
		}

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

			dbg_tty_trace(OPEN, ("tty_open:(%x) port is open same category\n",
					MINOR(tty_devnum(tty))));
			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) {
				dbg_tty_trace(OPEN, ("tty_open:(%x) CH_HANGUP is set\n",
					MINOR(tty_devnum(tty))));
				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) {
				dbg_tty_trace(OPEN, ("tty_open:(%x) port active"
					" different categoy\n",
					MINOR(tty_devnum(tty))));
				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);
#ifdef __set_current_state
		__set_current_state(TASK_INTERRUPTIBLE);
#else
		current->state = TASK_INTERRUPTIBLE;
#endif

		DGRP_UNLOCK(nd->nd_lock, lock_flags);

		/*
		 * Give up control, we'll come back if we're
		 * interrupted or are woken up.
		 */

		dbg_tty_trace(OPEN, ("tty open (%x) sleeping\n",
				MINOR(tty_devnum(tty))));

#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0)
/* release global tty system lock that was taken above us in 
 * drivers/tty/tty_io.c:tty_open().. around the schedule */
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0)
  tty_unlock();
#else
  tty_unlock(tty);
#endif

#endif
		schedule();
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0)
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0)
  tty_lock();
#else
  tty_lock(tty);
#endif
#endif
		remove_wait_queue(&ch->ch_flag_wait, &wait);

		dbg_tty_trace(OPEN, ("tty open (%x) awake\n",
				MINOR(tty_devnum(tty))));

		DGRP_LOCK(nd->nd_lock, lock_flags);

#ifdef __set_current_state
		__set_current_state(TASK_RUNNING);
#else
		current->state = TASK_RUNNING;
#endif

		ch->ch_wait_count[otype]--;

		if (wait_carrier)
			ch->ch_wait_carrier--;

		nd->nd_tx_work = 1;

		if (signal_pending(current)) {
			dbg_tty_trace(OPEN, ("tty open (%x) interrupted\n",
					      MINOR(tty_devnum(tty))));
			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 (OPEN_SETS_CLOCAL(category) || 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) {
#if 0
		/* clears all the digi flags, leaves serial flags */
		un->un_flag &= (UN_STICKY | ~UN_DIGI_MASK);
#else
		/*
		 *  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;
#endif
		un->un_blocked_open = 0;

		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.
		 */
#if 0
		/* initiatize the termio structure */
		if (!(un->un_flag & UN_STICKY)) {
			(un->un_tty)->termios->c_line = 0;
			(un->un_tty)->termios->c_cflag = CS8 | CREAD | HUPCL | B9600;
			(un->un_tty)->termios->c_iflag = ICRNL | IXON;
			(un->un_tty)->termios->c_oflag = ONLCR | OPOST;
			(un->un_tty)->termios->c_lflag = ECHOK | ECHO |
							ICANON | ISIG | IEXTEN;
		}
#endif

		if (OPEN_SETS_CLOCAL(MINOR(tty_devnum(tty->device))))
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0)
			(un->un_tty)->termios->c_cflag |= CLOCAL;
#else
			(un->un_tty)->termios.c_cflag |= CLOCAL;
#endif

		drp_param(ch);

		if (GLBL(wait_control))
			drp_wait_ack(ch, &lock_flags, 1); /* global tty_lock is held in open */
	}

	un->un_flag |= UN_INITIALIZED;

#if 0
	/* Eventually: 64bit <---->32bit translation for ioctls. */
	/* tty_struct->tty_driver->cdev->file_operations->compat_ioctl */
	printk(KERN_ALERT "compat_ioctl: %p\n",
			tty->driver->cdev.ops->compat_ioctl);
#endif

	retval = 0;

unlock:

	DGRP_UNLOCK(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++;
	}

	dbg_tty_trace(OPEN, ("tty open (%x): done, ch_count(%d) un_count(%d) tty_count(%d)\n",
			MINOR(tty_devnum(tty)), ch->ch_open_count,
			un->un_open_count, tty->count));

	if (retval) {
		dbg_tty_trace(OPEN, ("tty open (%x) bad return\n",
					MINOR(tty_devnum(tty))));
	}

	dbg_tty_trace(OPEN, ("tty open (%x) return: %x\n",
					MINOR(tty_devnum(tty)), retval));

	return retval;
}




/*****************************************************************************
*
* Function:
*
*    dgrp_tty_close
*
* Author:
*
*    Gene Olson   (HPUX version)
*    James Puzzo  (linux port)
*
* Parameters:
*
*    struct tty_struct	*tty	TTY structure for port to close
*    struct file	*file	FILE pointer for port to close
*
* Return Values:
*
*    none
*
* Description:
*
*    Closes the device.
*
******************************************************************************/

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;

	dbg_tty_trace(CLOSE, ("tty close (%x) start\n",
		MINOR(tty_devnum(tty))));

	port = PORT_NUM(MINOR(tty_devnum(tty)));

	un = tty->driver_data;

	if (!un) {
		dbg_tty_trace(CLOSE, ("tty close (%x) invalid unit\n",
			MINOR(tty_devnum(tty))));
		return;
	}

	ch = un->un_ch;

	if (!ch) {
		dbg_tty_trace(CLOSE, ("tty close (%x) invalid channel\n",
			MINOR(tty_devnum(tty))));
		return;
	}

	nd = ch->ch_nd;

	if (!nd) {
		dbg_tty_trace(CLOSE, ("tty close (%x) invalid node\n",
				MINOR(tty_devnum(tty))));
		return;
	}

	dbg_tty_trace(CLOSE, ("tty close(%x): current ch_count(%d) un_count(%d)\n",
		MINOR(tty_devnum(tty)), ch->ch_open_count, un->un_open_count));

	DGRP_LOCK(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
	 */
	dbg_tty_trace(CLOSE, ("tty_close:(%x) starting last close on unit procedures\n",
				MINOR(tty_devnum(tty))));


	un->un_flag |= UN_CLOSING;

	/*
	 * Notify the discipline to only process XON/XOFF characters.
	 */
	dbg_tty_trace(CLOSE, ("tty close (%x) Waiting for drain...\n",
				MINOR(tty_devnum(tty))));
	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)) {
			dbg_tty_trace(CLOSE, ("tty close (%x) No space for offstr, resetting queue...\n",
				MINOR(tty_devnum(tty))));
			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) {
			dbg_tty_trace(CLOSE, ("tty close (%x) Turning off XPRINT...\n",
				MINOR(tty_devnum(tty))));
			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.
			 */

			dbg_tty_trace(CLOSE, ("is ch_open_count(%d) == un_open_count(%d), ch_send(%4x)\n",
				ch->ch_open_count, un->un_open_count,
				ch->ch_send));

			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)) {
				dbg_tty_trace(CLOSE, ("tty close(%x) Flushing line discipline etc. flag: %x left:%d\n",
					MINOR(tty_devnum(tty)),
					(file->f_flags & (O_NDELAY | O_NONBLOCK)),
					(ch->ch_tin - ch->ch_tout) & TBUF_MASK));

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

				DGRP_UNLOCK(nd->nd_lock, lock_flags);
				tty_ldisc_flush(tty);
				DGRP_LOCK(nd->nd_lock, lock_flags);
				break;
		}

		/*
		 *  Otherwise take a short nap.
		 */

		ch->ch_flag |= CH_DRAIN;

		DGRP_UNLOCK(nd->nd_lock, lock_flags);

		s = dgrp_sleep(1);

		DGRP_LOCK(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) {
				DGRP_UNLOCK(nd->nd_lock, lock_flags);
				drp_my_sleep(ch);
				DGRP_LOCK(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; */

#if 0
	/*
	 *  Since all terminal settings are sticky in Linux, no need
	 *  to specially handle a "sticky" flag.
	 */
	if (ch->ch_digi.digi_flags & DIGI_PRINTER)
		un->un_flag |= UN_STICKY;
#endif

	/*
	 *  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)) {
		DGRP_UNLOCK(nd->nd_lock, lock_flags);
		s = wait_event_interruptible(ch->ch_flag_wait,
			((ch->ch_flag & (CH_WAITING_SYNC | CH_PARAM)) == 0));
		DGRP_LOCK(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;

	if (GLBL(wait_control) && !err)
		drp_wait_ack(ch, &lock_flags,1); /* Glb.tty_lock is held in close */

unlock:
	tty->closing = 0;

	assert(ch->ch_open_count > 0);
	if (ch->ch_open_count > 0)
		ch->ch_open_count--;

	assert(un->un_open_count > 0);
	if (un->un_open_count > 0)
		un->un_open_count--;

	assert(nd->nd_open_count > 0);
	if (nd->nd_open_count > 0)
		nd->nd_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);

	DGRP_UNLOCK(nd->nd_lock, lock_flags);

	dbg_tty_trace(CLOSE, ("tty close (%x): done, ch_count(%d) un_count(%d) tty_count(%d)\n",
		MINOR(tty_devnum(tty)), ch->ch_open_count,
		un->un_open_count, tty->count));

	dbg_tty_trace(CLOSE, ("tty close (%x) return\n",
		MINOR(tty_devnum(tty))));

	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, 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, 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;
}

#if LINUX_VERSION_CODE < KERNEL_VERSION(6,6,0) && !IS_RHEL_9_5
static int dgrp_tty_write(struct tty_struct *tty, const unsigned char *buf, int _count)
#else
static ssize_t dgrp_tty_write(struct tty_struct *tty, const u8 *buf, size_t _count)
#endif
{

	struct nd_struct *nd;
	struct un_struct *un;
	struct ch_struct *ch;
	ssize_t space;
	ssize_t n;
	ssize_t	t;
	ssize_t sendcount;
	int un_flag;
	ulong lock_flags;
	int from_user = 0;
	ssize_t count = (ssize_t) _count;

	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;

	dbg_tty_trace(WRITE, ("dgrp_tty_write(%x): tty=%lx user=%d count=%zd\n",
		   MINOR(tty_devnum(tty)), (long) tty, from_user, count));


	DGRP_LOCK(GLBL(poll_lock), lock_flags);

	/*
	 * Ignore the request if output is blocked.
	 */
	if ((un->un_flag & (UN_EMPTY | UN_LOW | UN_TIME | UN_PWAIT)) != 0) {
		DGRP_UNLOCK(GLBL(poll_lock), lock_flags);
		dbg_tty_trace(WRITE, ("dgrp_tty_write(%x) - wrote 0\n",
			   MINOR(tty_devnum(tty))));
		return 0;
	}

	/*
	 * 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))) {
		DGRP_UNLOCK(GLBL(poll_lock), lock_flags);
		return 0;
	}

	/*
	 *	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;
			DGRP_UNLOCK(GLBL(poll_lock), lock_flags);
			dbg_tty_trace(WRITE, ("dgrp_tty_write(%x) - wrote 0\n",
				   MINOR(tty_devnum(tty))));
			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;

		/*
		 * 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;
				DGRP_UNLOCK(GLBL(poll_lock), lock_flags);
				dbg_tty_trace(WRITE, ("dgrp_tty_write(%x) - wrote 0\n",
					   MINOR(tty_devnum(tty))));
				return 0;
			}

			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;

			assert(space >= 0);

			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) {
		dbg_tty_trace(WRITE, ("Space == %zd in tbuf\n", space));
		/* 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;
		DGRP_UNLOCK(GLBL(poll_lock), lock_flags);
		dbg_tty_trace(WRITE, ("dgrp_tty_write(%x) - wrote 0\n",
			      MINOR(tty_devnum(tty))));
		return 0;
	}

	count = min(count, space);

	/*
	 * If data is coming from user space, copy it into a temporary
	 * buffer so we don't get swapped out while doing the copy to
	 * the board.
	 */
	if (from_user) {

		count = min(count, (ssize_t) WRITEBUFLEN);

		/*
		 * We must drop lock, because we will be
		 * going out to user space...
		 */
		DGRP_UNLOCK(GLBL(poll_lock), lock_flags);

		/*
		 * Grab the writebuf semaphore.
		 *
		 * NOTE: we are allowed to block because its from_user.
		 */
		if (down_interruptible(&nd->nd_writebuf_semaphore))
			return -EINTR;

		/*
		 * NOTE:  This is safe even if we the copy from user sleeps,
		 * because we now hold the writebuf semaphore.
		 * No one else can muck with the writebuf buffer until
		 * we let go of the writebuf semaphore.
		 *
		 * copy_from_user() returns the number
		 * of bytes that could *NOT* be copied.
		 */
		count -= copy_from_user(nd->nd_writebuf, buf, count);

		if (!count) {
			/*
			 *  Release the writebuf semaphore before returning...
			 */
			up(&nd->nd_writebuf_semaphore);
			return -EFAULT;
		}

		/* Reobtain lock... */
		DGRP_LOCK(GLBL(poll_lock), lock_flags);

		/* Point buf pointer to our internal write buffer */
		buf = nd->nd_writebuf;

	}

	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 != GLBL(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;

	/* Let go of any locks/semaphores we might have held... */
	if (from_user) {
		DGRP_UNLOCK(GLBL(poll_lock), lock_flags);
		up(&nd->nd_writebuf_semaphore);
	} else {
		DGRP_UNLOCK(GLBL(poll_lock), lock_flags);
	}

	dbg_tty_trace(WRITE, ("dgrp_tty_write(%x) - wrote %zd\n",
		      MINOR(tty_devnum(tty)), count));
#if LINUX_VERSION_CODE < KERNEL_VERSION(6,6,0) && !IS_RHEL_9_5
	return (int) count;
#else
	return count;
#endif
}


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

	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;

	dbg_tty_trace(WRITE, ("dgrp_tty_put_char(%x): tty=%lx char(%x)\n",
		   MINOR(tty_devnum(tty)), (long)tty, new_char));

	DGRP_LOCK(GLBL(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) {
			dbg_tty_trace(WRITE, ("dgrp_tty_put_char(%x): Space == %d in"
				" tbuf, dropping onstr\n",
				MINOR(tty_devnum(tty)), space));

			un->un_tbusy--;
			DGRP_UNLOCK(GLBL(poll_lock), lock_flags);
			return 0;
		}
		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) {
			dbg_tty_trace(WRITE, ("dgrp_tty_put_char(%x): Space == %d in"
			" tbuf, dropping offstr\n",
			MINOR(tty_devnum(tty)), space));

			un->un_tbusy--;
			DGRP_UNLOCK(GLBL(poll_lock), lock_flags);
			return 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) {
		dbg_tty_trace(WRITE, ("dgrp_tty_put_char(%x): Space == 0 in"
			" tbuf, dropping character\n", MINOR(tty_devnum(tty))));
		un->un_tbusy--;
		DGRP_UNLOCK(GLBL(poll_lock), lock_flags);
		return 0;
	}

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

	DGRP_UNLOCK(GLBL(poll_lock), lock_flags);

	return 1;
}



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

	dbg_tty_trace(WRITE, ("dgrp_tty_flush_buffer(%x) start\n",
		      MINOR(tty_devnum(tty))));

	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);

	dbg_tty_trace(WRITE, ("dgrp_tty_flush_buffer(%x) finish\n",
		      MINOR(tty_devnum(tty))));
}

/*
 *	Return space available in Tx buffer
 * 	count = ( ch->ch_tout - ch->ch_tin ) mod (TBUF_MAX - 1)
 */
#if LINUX_VERSION_CODE < KERNEL_VERSION(5,14,0)
static int dgrp_tty_write_room(struct tty_struct *tty)
#else
static unsigned int dgrp_tty_write_room(struct tty_struct *tty)
#endif
{
	struct un_struct *un;
	struct ch_struct *ch;
#if LINUX_VERSION_CODE < KERNEL_VERSION(5,14,0)
	int	count;
#else
	unsigned int	count;
#endif

	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;
	}

	dbg_tty_trace(WRITE, ("dgrp_tty_write_room(%x) count(%d) busy(%d)\n",
		      MINOR(tty_devnum(tty)), count, un->un_tbusy));

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

#if LINUX_VERSION_CODE < KERNEL_VERSION(5,14,0)
static int dgrp_tty_chars_in_buffer(struct tty_struct *tty)
#else
static unsigned int dgrp_tty_chars_in_buffer(struct tty_struct *tty)
#endif
{
	struct un_struct *un;
	struct ch_struct *ch;
#if LINUX_VERSION_CODE < KERNEL_VERSION(5,14,0)
	int	count;
	int	count1;
#else
	unsigned int	count;
	unsigned int	count1;
#endif

	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;

	dbg_tty_trace(WRITE, ("dgrp_tty_chars_in_buffer(%x) ch_buf(%d) +ch_s_tbuf=(%d) busy(%d)\n",
		      MINOR(tty_devnum(tty)), count1, count, un->un_tbusy));

	return count;
}


/*****************************************************************************
 *
 * Helper applications for dgrp_tty_ioctl()
 *
 *****************************************************************************
 */

static int dgrp_print_KME(struct ch_struct *ch)
{
	/*
	 * Print out some KME information
	 */
	dbg_tty_trace(KME, ("KME: ch(%x) (%x)\n", ch, &ch));
	dbg_tty_trace(KME, ("tty open -KME- edelay(%x) (%x)\n",
			&ch->ch_edelay, ch->ch_edelay));
	dbg_tty_trace(KME, ("KME: tty(%x) pr(%x)\n",
			&ch->ch_tun, &ch->ch_pun));
	dbg_tty_trace(KME, ("KME: tbuf(%x) rbuf(%x)\n",
			ch->ch_tbuf, ch->ch_rbuf));
	dbg_tty_trace(KME, ("KME: state(%x) wcar(%x)\n",
			&ch->ch_state, &ch->ch_wait_carrier));
	dbg_tty_trace(KME, ("KME: wcount(%x) o_cnt(%x)\n",
			&ch->ch_wait_count[0], &ch->ch_open_count));
	dbg_tty_trace(KME, ("KME: wcount(%x, %x, %x)\n",
			ch->ch_wait_count[0], ch->ch_wait_count[1],
			ch->ch_wait_count[2]));
	dbg_tty_trace(KME, ("KME: tin(%x) (%x)\n",
			&ch->ch_tin, ch->ch_tin));
	dbg_tty_trace(KME, ("KME: tmax(%x) (%x)\n",
			&ch->ch_tmax, ch->ch_tmax));
	dbg_tty_trace(KME, ("KME: brate(%x) (%x)\n",
			&ch->ch_brate, ch->ch_brate));
	dbg_tty_trace(KME, ("KME: mout(%x) (%x)\n",
			&ch->ch_mout, ch->ch_mout));
	return 0;
}

/*****************************************************************************
 *
 * Function:
 *
 *     ch_to_tty_flags
 *
 * Author:
 *
 *     Ann-Marie Westgate
 *
 * Parameters:
 *
 *     ch_flag  -- Digi channel flags (iflags, oflags, or cflags)
 *     flagtype -- describes whether ch_flag is iflag, oflag or cflag
 *
 * Return Value:
 *
 *     a ttyflag of the specified type
 *
 * Description:
 *
 *     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;

	dbg_tty_trace(IOCTL, ("ch_to_tty_flags: start\n"));

	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:
		dbg_tty_trace(IOCTL, ("ch_to_tty: error, unrecognized type(%c)\n",
			      flagtype));
		return 0;
	}

	dbg_tty_trace(IOCTL, ("ch_to_tty: type(%c) ch_flag(%x) retval(%o)\n",
		      flagtype, ch_flag, retval));

	return retval;
}


/*****************************************************************************
 *
 * Function:
 *
 *	    tty_to_ch_flags
 *
 * Author:
 *
 *	    Ann-Marie Westgate
 *
 * Parameter:
 *
 *	    tty      -- pointer to a TTY structure holding an iflag, oflag or cflag
 *                 to be converted
 *     flagtype -- identifies whether the iflags, oflags, or cflags should
 *                 be converted
 *
 * Return Value:
 *
 *	    a Digi iflag, oflag or cflag value
 *
 * Description:
 *
 *	    take the termio flag of the specified type and return the
 *	    corresponding Digi version of the flags
 *	    NB: just map bitmasks, do not zero out if no OPOST set
 *
 *****************************************************************************
 */
static ushort tty_to_ch_flags(struct tty_struct *tty, char flagtype)
{
	ushort retval = 0;
	tcflag_t tflag = 0;

	dbg_tty_trace(IOCTL, ("tty_to_ch_flags: start\n"));

	switch (flagtype) {
	case 'i':
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0)
		tflag  = (tty)->termios->c_iflag;
#else
		tflag  = (tty)->termios.c_iflag;
#endif
		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':
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0)
		tflag  = (tty)->termios->c_oflag;
#else
		tflag  = (tty)->termios.c_oflag;
#endif
		/*
		 * 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!!
		 * But here, just return the bitfield mapping, regardless.
		 */
			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':
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0)
		tflag  = (tty)->termios->c_cflag;
#else
		tflag  = (tty)->termios.c_cflag;
#endif
		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:
		dbg_tty_trace(IOCTL, ("tty_to_ch: error unrecognized type(%c)\n",
			flagtype));
		return 0;
	}

	dbg_tty_trace(IOCTL, ("tty_to_ch: type(%c) ttyflag(%o) retval(%x)\n",
		flagtype, tflag, retval));

	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;

	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 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;
	dbg_tty_trace(IOCTL, ("Sending break duration (%d)/1000secs"
		" (%ld)ticks (%d)ch_break_time\n",
		msec, x, ch->ch_break_time));
	wake_up_interruptible(&(ch->ch_nd)->nd_tx_waitq);

	return 0;
}


/*
 * Return modem signals to ld.
 */
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,0,0)
static int dgrp_tty_tiocmget(struct tty_struct *tty, struct file *file)
#else
static int dgrp_tty_tiocmget(struct tty_struct *tty)
#endif
{
	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);

	dbg_tty_trace(IOCTL, ("dgrp_tty_tiocmget: mlast(%x)", mlast));

	return mlast;
}


/*
 *      Set modem lines
 */
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,0,0)
static int dgrp_tty_tiocmset(struct tty_struct *tty, struct file *file,
				unsigned int set, unsigned int clear)
#else
static int dgrp_tty_tiocmset(struct tty_struct *tty,
				unsigned int set, unsigned int clear)
#endif
{
	ulong lock_flags;
	struct un_struct *un = tty->driver_data;
	struct ch_struct *ch;
 unsigned char m;

	if (!un)
		return -ENODEV;

	ch = un->un_ch;
	if (!ch)
		return -ENODEV;

 m = ch->ch_mout;

	if (set & TIOCM_RTS)   m |= DM_RTS;

	if (set & TIOCM_DTR)   m |= DM_DTR;

	if (clear & TIOCM_RTS) m &= ~(DM_RTS);

	if (clear & TIOCM_DTR) m &= ~(DM_DTR);

	DGRP_LOCK((ch->ch_nd)->nd_lock, lock_flags);
 ch->ch_mout = m;
	ch->ch_flag |= CH_PARAM;
	(ch->ch_nd)->nd_tx_work = 1;
	wake_up_interruptible(&ch->ch_flag_wait);

	if (GLBL(wait_control))
		drp_wait_ack(ch, &lock_flags, 0);

	DGRP_UNLOCK((ch->ch_nd)->nd_lock, lock_flags);

	dbg_tty_trace(IOCTL, ("dgrp_tty_tiocmset: mval(%x)", ch->ch_mout));

	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, value);

	dbg_tty_trace(IOCTL, ("get_modem_info: mlast(%x)", mlast));
	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;

#if LINUX_VERSION_CODE < KERNEL_VERSION(5,0,0) && defined (VERIFY_WRITE)
	error = access_ok(VERIFY_READ, value, sizeof(int));
#else
	error = access_ok(value, sizeof(int));
#endif
	if (error == 0)
		return -EFAULT;

	get_user(arg, (unsigned int *) 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;
	}

	DGRP_LOCK((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);

	if (GLBL(wait_control))
		drp_wait_ack(ch, &lock_flags,0);

	DGRP_UNLOCK((ch->ch_nd)->nd_lock, lock_flags);

	dbg_tty_trace(IOCTL, ("set_modem_info: mval(%x)", mval));
	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;

	dbg_tty_trace(IOCTL, ("dgrp_tty_digiseta(%x)\n",
				MINOR(tty_devnum(tty))));

	if (copy_from_user(&ch->ch_digi, new_info, sizeof(struct digi_struct)))
		return -EFAULT;

	if ((ch->ch_digi.digi_flags & RTSPACE) || (ch->ch_digi.digi_flags & CTSPACE))
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0)
		tty->termios->c_cflag |= CRTSCTS;
#else
		tty->termios.c_cflag |= CRTSCTS;
#endif
	else
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0)
		tty->termios->c_cflag &= ~CRTSCTS;
#else
		tty->termios.c_cflag &= ~CRTSCTS;
#endif

	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);

	dbg_tty_trace(IOCTL, ("dgrp_tty_digiseta(%x) - done, flags set(%x)\n",
			MINOR(tty_devnum(tty)), ch->ch_digi.digi_flags));

	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 LINUX_VERSION_CODE < KERNEL_VERSION(6,1,0) && !IS_RHEL_9_5
	if (!tty || tty->magic != TTY_MAGIC)
#else
	if (!tty)
#endif
		return -EFAULT;

	un = tty->driver_data;

	if (!un)
		return -ENODEV;

	ch = un->un_ch;
	if (!ch)
		return -ENODEV;

	memset(&tmp, 0, sizeof(tmp));

	tmp = ch->ch_edelay;

	if (copy_to_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 LINUX_VERSION_CODE < KERNEL_VERSION(6,1,0) && !IS_RHEL_9_5
	if (!tty || tty->magic != TTY_MAGIC)
#else
	if (!tty)
#endif
		return -EFAULT;

	un = tty->driver_data;

	if (!un)
		return -ENODEV;

	ch = un->un_ch;
	if (!ch)
		return -ENODEV;

	if (copy_from_user(&new_digi, 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
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,0,0)
dgrp_tty_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg)
#else
dgrp_tty_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg)
#endif
{
	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;

	dbg_tty_trace(IOCTL,
		("ioctl(%x) cmd=%s('%c',x%x,x%x) arg=%x\n",
		MINOR(tty_devnum(tty)),
		((cmd & IOC_INOUT) == IOC_INOUT ? "IOWR" :
		(cmd & IOC_IN)    != 0         ? "IOW"  :
		(cmd & IOC_OUT)   != 0         ? "IOR"  : "???"),
		((cmd >> 8) & 0xff), (cmd & 0xff),
		((cmd >> 16) & 0xff), arg));

	switch (cmd) {

#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0)
	case TIOCGSERIAL:
	case TIOCSSERIAL:
	case TIOCSERCONFIG:
   return -ENOIOCTLCMD; /* dontcare.  n/a for RealPort */
 break;

	case TIOCMIWAIT:
	{
 	 uchar m,c;
		/* arg is a value mask of TIOCM_[RNG|DSR|CD|CTS] and the idea is that
		   if eg. RNG is set, then we wait for our RNG state to change.  I.e.
		   we are not waiting for RNG to become set, just to change
     ..i dont _think_ there is need to LOCK around this...
		 */
	 m = ((ch->ch_s_mlast & ~(DM_RTS|DM_DTR)) | (ch->ch_mout & (DM_RTS|DM_DTR)));
		for (;;) {
		 /* wait for something to change : NB: tty_lock not held on IOCTL..
		  * The monolithic and 'invariant' code in dgrp_net_ops does not issue
		  * any wakeups on modem changes unless we're waiting for carrier, so
		  * instead of changing that code, poll.. */
			msleep (100); /* sleep 1/10 of a second. */
			if (signal_pending(current))  return -ERESTARTSYS;
			if (un->un_flag & UN_CLOSING) return -EIO;
			c = ((ch->ch_s_mlast & ~(DM_RTS|DM_DTR)) | (ch->ch_mout & (DM_RTS|DM_DTR)));
			if ((c ^= m)) {
			if (((arg & TIOCM_RNG) && (c & DM_RI))  ||
			    ((arg & TIOCM_DSR) && (c & DM_DSR)) ||
			    ((arg & TIOCM_CD)  && (c & DM_CD))  ||
			    ((arg & TIOCM_CTS) && (c & DM_CTS)))
				return 0;
		 }
	 }
	}
 break;
	case TIOCSERGETLSR: 
	{
	 unsigned char lsr;
		/* well, its FIFO information, so intepret as txempty? */
		lsr = (ch->ch_tout == ch->ch_tin);
		return put_user(lsr, (unsigned int *)arg);
		break;
	}
#endif


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

		dbg_tty_trace(IOCTL, ("tty_ioctl(%x) - TCSBRK and %s|%s %s|%s\n",
				MINOR(tty_devnum(tty)),
				(I_IGNBRK(tty) ? "IGNBRK" : "~IGNBRK"),
				(I_BRKINT(tty) ? "BRKINT" : "~BRKINT"),
				((ch->ch_iflag & IF_IGNBRK) ? "IF_IGNBRK" : "~IF_IGNBRK"),
				((ch->ch_iflag & IF_BRKINT) ? "IF_BRKINT" : "~IF_BRKINT")));


		rc = tty_check_change(tty);

		if (rc)
			return rc;
		tty_wait_until_sent(tty, 0);

		if (!arg) {
			dbg_tty_trace(IOCTL, ("tty_ioctl(%x) - TCSBRK\n",
				MINOR(tty_devnum(tty))));
			 rc = send_break(ch, 250); /* 1/4 second */
		} else {
			dbg_tty_trace(IOCTL, ("tty_ioctl(%x) - TCDRAIN\n",
				MINOR(tty_devnum(tty))));
		}
		if (dgrp_tty_chars_in_buffer(tty) != 0) {
			dbg_tty_trace(IOCTL, ("tty_ioctl(%x) - TCDRAIN failed!\n",
				MINOR(tty_devnum(tty))));
			return -EINTR;
		}
		dbg_tty_trace(IOCTL, ("tty_ioctl(%x) - TCSBRK done.\n",
				MINOR(tty_devnum(tty))));
		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 = send_break(ch, arg ? arg*250 : 250);

		if (dgrp_tty_chars_in_buffer(tty) != 0) {
			dbg_tty_trace(IOCTL, ("tty_ioctl(%x) - TCPDRAIN failed!\n",
				MINOR(tty_devnum(tty))));
			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 = send_break(ch, 250); /* 1/4 second */

		if (dgrp_tty_chars_in_buffer(tty) != 0) {
			dbg_tty_trace(IOCTL, ("tty_ioctl(%x) - TCPDRAIN failed!\n",
				MINOR(tty_devnum(tty))));
			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:
#if LINUX_VERSION_CODE < KERNEL_VERSION(5,0,0) && defined (VERIFY_WRITE)
		rc = access_ok(VERIFY_WRITE, (void *) arg, sizeof(long));
#else
		rc = access_ok((void *) arg, sizeof(long));
#endif
		if (rc == 0)
			return -EFAULT;
		put_user(C_CLOCAL(tty) ? 1 : 0, (unsigned long *) arg);
		return 0;

	case TIOCSSOFTCAR:
		get_user(arg, (unsigned long *) arg);
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0)
		tty->termios->c_cflag =
			((tty->termios->c_cflag & ~CLOCAL) |
			 (arg ? CLOCAL : 0));
#else
		tty->termios.c_cflag =
			((tty->termios.c_cflag & ~CLOCAL) |
			 (arg ? CLOCAL : 0));
#endif
		return 0;

	case TIOCMGET:
#if LINUX_VERSION_CODE < KERNEL_VERSION(5,0,0) && defined (VERIFY_WRITE)
		rc = access_ok(VERIFY_WRITE, (void *) arg,
				 sizeof(unsigned int));
#else
		rc = access_ok((void *) arg, sizeof(unsigned int));
#endif
		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;

		dbg_tty_trace(IOCTL, ("tty_ioctl(%x) - TCFLSH flushing\n",
			MINOR(tty_devnum(tty))));
		switch (arg) {
		case TCIFLUSH:
		case TCIOFLUSH:
			/* only flush input if this is the only open unit */
			if (!IS_PRINT(MINOR(tty_devnum(tty)))) {
				dbg_tty_trace(IOCTL, ("tty_ioctl(%x) - TCIFLUSH flushing "
				"input\n", 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:	/* translate our termios to return a termio */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,31)
		if (tty->ldisc->ops->ioctl && !IS_PRINT(MINOR(tty_devnum(tty)))) {
#else
		if (tty->ldisc.ops->ioctl && !IS_PRINT(MINOR(tty_devnum(tty)))) {
#endif
			int retval;

			/* take into consideration our cooked output settings */
			int oflag = _O_FLAG(tty, 0xffff);
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0)
			tty->termios->c_oflag |= ch_to_tty_flags(ch->ch_ocook, 'o');
#else
			tty->termios.c_oflag |= ch_to_tty_flags(ch->ch_ocook, 'o');
#endif

#if LINUX_VERSION_CODE < KERNEL_VERSION(3,0,0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,31)
			retval = (tty->ldisc->ops->ioctl)(tty, file, cmd, arg);
#else
			retval = (tty->ldisc.ops->ioctl)(tty, file, cmd, arg);
#endif
#else
   retval = -ENOIOCTLCMD; /* so that caller proceeds to the ldisc version */
#endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0)
			tty->termios->c_oflag = oflag;
#else
			tty->termios.c_oflag = oflag;
#endif
			return retval;

		}
		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);
		dbg_tty_trace(IOCTL, ("tty_ioctl(%x) - in TCSET[AS][SW] after "
			   "tty_wait_until_sent\n", MINOR(tty_devnum(tty))));

		if ((cmd == TCSETSF) || (cmd == TCSETAF)) {
			/* flush the contents of the rbuf queue */
			/* TODO:  check if this is print device? */
			dbg_tty_trace(IOCTL, ("tty_ioctl(%x) - TCSETS[F] "
				   "flushing input\n", MINOR(tty_devnum(tty))));
			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 */
		dbg_tty_trace(IOCTL, ("tty_ioctl - TCSETS[WF] done flushing\n"));
		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.
		 */
		dbg_tty_trace(IOCTL, ("tty_ioctl(%x) -  in TCXONC\n",
			MINOR(tty_devnum(tty))));

		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 *) 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);
		dbg_tty_trace(IOCTL, ("tty_ioctl(%x) - in DIGI_SETA[WF] after "
			   "tty_wait_until_sent\n", MINOR(tty_devnum(tty))));

		if (cmd == DIGI_SETAF) {
			/* flush the contents of the rbuf queue */
			/* send down a packet with RR_RX_FLUSH set */
			dbg_tty_trace(IOCTL, ("tty_ioctl(%x) - TCSETS[F] "
				   "flushing input\n", MINOR(tty_devnum(tty))));
			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 */
#if defined(__GNUC__) && __GNUC__ >= 7
__attribute__ ((__fallthrough__));
#endif

	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)) {
			dbg_tty_trace(IOCTL, ("tty_ioctl(%x) - in "
				"DIGI_GETFLOW\n", MINOR(tty_devnum(tty))));
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0)
			dflow.startc = tty->termios->c_cc[VSTART];
			dflow.stopc = tty->termios->c_cc[VSTOP];
#else
			dflow.startc = tty->termios.c_cc[VSTART];
			dflow.stopc = tty->termios.c_cc[VSTOP];
#endif
		} else {
			dbg_tty_trace(IOCTL, ("tty_ioctl(%x) - in "
				"DIGI_GETAFLOW\n", MINOR(tty_devnum(tty))));
			dflow.startc = ch->ch_xxon;
			dflow.stopc = ch->ch_xxoff;
		}

		if (copy_to_user((char *)arg, &dflow, sizeof(dflow)))
			return -EFAULT;
		break;

	case DIGI_SETFLOW:
	case DIGI_SETAFLOW:

		if (copy_from_user(&dflow, (char *)arg, sizeof(dflow)))
			return -EFAULT;

		if (cmd == (DIGI_SETFLOW)) {
			dbg_tty_trace(IOCTL, ("tty_ioctl(%x) - in "
				"DIGI_SETFLOW \n", MINOR(tty_devnum(tty))));
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0)
			tty->termios->c_cc[VSTART] = dflow.startc;
			tty->termios->c_cc[VSTOP] = dflow.stopc;
#else
			tty->termios.c_cc[VSTART] = dflow.startc;
			tty->termios.c_cc[VSTOP] = dflow.stopc;
#endif
		} else {
			dbg_tty_trace(IOCTL, ("tty_ioctl(%x) - in "
				"DIGI_SETAFLOW \n", MINOR(tty_devnum(tty))));
			ch->ch_xxon = dflow.startc;
			ch->ch_xxoff = dflow.stopc;
		}
		break;

	case DIGI_GETCUSTOMBAUD:
#if LINUX_VERSION_CODE < KERNEL_VERSION(5,0,0) && defined (VERIFY_WRITE)
		rc = access_ok(VERIFY_WRITE, (void *) arg, sizeof(int));
#else
		rc = access_ok((void *) arg, sizeof(int));
#endif
		if (rc == 0)
			return -EFAULT;
		put_user(ch->ch_custom_speed, (unsigned int *) arg);
		break;

	case DIGI_SETCUSTOMBAUD:
	{
		int new_rate;

		get_user(new_rate, (unsigned int *) arg);
		dgrp_set_custom_speed(ch, new_rate);

		break;
	}

	default:
		dbg_tty_trace(IOCTL, ("dgrp_tty_ioctl(%x) - in default\n",
			MINOR(tty_devnum(tty))));
		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.
 */
#if LINUX_VERSION_CODE < KERNEL_VERSION(6,1,0) && !IS_RHEL_9_5
static void dgrp_tty_set_termios(struct tty_struct *tty, struct ktermios *old)
#else
static void dgrp_tty_set_termios(struct tty_struct *tty, const struct ktermios *old)
#endif
{
	struct ktermios *ts;
	struct ch_struct *ch;
	struct un_struct *un;

	dbg_tty_trace(IOCTL, ("now in dgrp_tty_set_termios\n"));

	/* seems silly, but we have to check all these! */
	if (!tty)
		return;

	un = tty->driver_data;
	if (!un)
		return;

#if LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0)
	ts = tty->termios; 	/* HAVE to check this! */
#else
	ts = &(tty->termios); 	/* HAVE to check this! */
#endif
	if (!ts)
		return;

	ch = un->un_ch;
	if (!ch)
		return;

	dbg_tty_trace(INPUT, ("dgrp_tty_set_termios(%x) cflag(%x) oflag(%x) lflag(%x) iflag(%x)\n",
		      MINOR(tty_devnum(tty)), ts->c_cflag, ts->c_oflag,
		      ts->c_lflag, ts->c_iflag));

	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;

	dbg_tty_trace(IOCTL, ("now in dgrp_tty_throttle\n"));
}


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;

	dbg_tty_trace(IOCTL, ("now in dgrp_tty_unthrottle\n"));
}

/*
 *	Stop the transmitter
 */
static void dgrp_tty_stop(struct tty_struct *tty)
{
	struct ch_struct *ch;

	if (!tty)
		return;

	dbg_tty_trace(IOCTL, ("tty_stop(%x) start\n", MINOR(tty_devnum(tty))));

	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);

	dbg_tty_trace(IOCTL, ("tty_stop(%x) finish\n",
		MINOR(tty_devnum(tty))));
}

/*
 *	Start the transmitter
 */
static void dgrp_tty_start(struct tty_struct *tty)
{
	struct ch_struct *ch;

	if (!tty)
		return;

	dbg_tty_trace(IOCTL, ("tty_start(%x) start\n",
		MINOR(tty_devnum(tty))));

	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);

	dbg_tty_trace(IOCTL, ("tty_start(%x) finish\n",
		MINOR(tty_devnum(tty))));
}

/*
 *	Stop the reciever
 */
static void dgrp_tty_input_stop(struct tty_struct *tty)
{
	struct ch_struct *ch;

	if (!tty)
		return;

	dbg_tty_trace(IOCTL, ("tty_input_stop(%x) finish\n",
		MINOR(tty_devnum(tty))));

	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);

}


#if LINUX_VERSION_CODE < KERNEL_VERSION(6,6,0)
static void dgrp_tty_send_xchar(struct tty_struct *tty, char c)
#else
static void dgrp_tty_send_xchar(struct tty_struct *tty, u8 c)
#endif
{
	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;

	dbg_tty_trace(IOCTL, ("dgrp_tty_input_start(%x)\n",
		MINOR(tty_devnum(tty))));

	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 0
	ch->ch_tun.un_flag |= (UN_CLOSING);
#endif

	dbg_tty_trace(INPUT, ("dgrp_tty_hangup(%x) c_hupcl(%x)\n",
		MINOR(tty_devnum(tty)), C_HUPCL(tty)));

#if LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0)
	if (!(tty->termios) || C_HUPCL(tty))
#else
	if (C_HUPCL(tty))
#endif
	{
		/* LOWER DTR */
		ch->ch_mout &= ~DM_DTR;
		/* Don't do this here */
		/* ch->ch_flag |= CH_HANGUP; */
		dbg_tty_trace(OPEN, ("tty_open:(%x) CH_HANGUP is %s set\n",
			MINOR(tty_devnum(tty)),
			(ch->ch_flag & CH_HANGUP) ? " " : "not"));
		(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);
	}

	dbg_tty_trace(INPUT, ("dgrp_tty_hangup(%x): done, ch_count(%d)\n",
		MINOR(tty_devnum(tty)), ch->ch_open_count));
}

/************************************************************************/
/*                                                                      */
/*       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];
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0)
	int i;
#endif

	ID_TO_CHAR(nd->nd_ID, id);

	dbg_tty_trace(UNINIT, ("tty uninit: %s\n", id));
	if (nd->nd_ttdriver_flags & SERIAL_TTDRV_REG) {
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0)
		for (i = 0; i < CHAN_MAX; i++) {
			tty_unregister_device(nd->nd_serial_ttdriver, i);
		}
#endif
		tty_unregister_driver(nd->nd_serial_ttdriver);

		if (nd->nd_serial_ttdriver->ttys) {
			kfree(nd->nd_serial_ttdriver->ttys);
			nd->nd_serial_ttdriver->ttys = NULL;
		}
#if LINUX_VERSION_CODE < KERNEL_VERSION(5,14,0)
		put_tty_driver(nd->nd_serial_ttdriver);
#else
		tty_driver_kref_put(nd->nd_serial_ttdriver);
#endif
		nd->nd_ttdriver_flags &= ~SERIAL_TTDRV_REG;
	}

	if (nd->nd_ttdriver_flags & CALLOUT_TTDRV_REG) {
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0)
		for (i = 0; i < CHAN_MAX; i++) {
			tty_unregister_device(nd->nd_callout_ttdriver, i);
		}
#endif
		tty_unregister_driver(nd->nd_callout_ttdriver);

		if (nd->nd_callout_ttdriver->ttys) {
			kfree(nd->nd_callout_ttdriver->ttys);
			nd->nd_callout_ttdriver->ttys = NULL;
		}
#if LINUX_VERSION_CODE < KERNEL_VERSION(5,14,0)
		put_tty_driver(nd->nd_callout_ttdriver);
#else
		tty_driver_kref_put(nd->nd_callout_ttdriver);
#endif
		nd->nd_ttdriver_flags &= ~CALLOUT_TTDRV_REG;
	}

	if (nd->nd_ttdriver_flags & XPRINT_TTDRV_REG) {
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0)
		for (i = 0; i < CHAN_MAX; i++) {
			tty_unregister_device(nd->nd_xprint_ttdriver, i);
		}
#endif
		tty_unregister_driver(nd->nd_xprint_ttdriver);

		if (nd->nd_xprint_ttdriver->ttys) {
			kfree(nd->nd_xprint_ttdriver->ttys);
			nd->nd_xprint_ttdriver->ttys = NULL;
		}
#if LINUX_VERSION_CODE < KERNEL_VERSION(5,14,0)
		put_tty_driver(nd->nd_xprint_ttdriver);
#else
		tty_driver_kref_put(nd->nd_xprint_ttdriver);
#endif
		nd->nd_ttdriver_flags &= ~XPRINT_TTDRV_REG;
	}

	dbg_tty_trace(UNINIT, ("tty uninit: done\n"));
}


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

	dbg_tty_trace(INIT, ("tty init: %s\n", id));

	/*
	 *  Initialize the TTDRIVER structures.
	 */

#if LINUX_VERSION_CODE < KERNEL_VERSION(5,14,0)
	nd->nd_serial_ttdriver = alloc_tty_driver(CHAN_MAX);
#else
	nd->nd_serial_ttdriver = tty_alloc_driver(CHAN_MAX, 
 (TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV | TTY_DRIVER_HARDWARE_BREAK));
#endif
	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  = DRVSTR "(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 =
		dgrp_kzmalloc(CHAN_MAX * sizeof(struct tty_struct *), GFP_KERNEL);
	if (!nd->nd_serial_ttdriver->ttys)
		return -ENOMEM;

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28)
	nd->nd_serial_ttdriver->refcount = nd->nd_tty_ref_cnt;
#endif

	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) {
					dbg_trace(("Can't register tty device (%d)\n", rc));
					return(rc);
				}
			}
			else {
				dbg_trace(("Can't register tty device (%d)\n", rc));
				return(rc);
			}
		}
		nd->nd_ttdriver_flags |= SERIAL_TTDRV_REG;
	}

#if LINUX_VERSION_CODE < KERNEL_VERSION(5,14,0)
	nd->nd_callout_ttdriver = alloc_tty_driver(CHAN_MAX);
#else
	nd->nd_callout_ttdriver = tty_alloc_driver(CHAN_MAX, 
 (TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV | TTY_DRIVER_HARDWARE_BREAK));
#endif
	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  = DRVSTR "(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 =
		dgrp_kzmalloc(CHAN_MAX * sizeof(struct tty_struct *), GFP_KERNEL);
	if (!nd->nd_callout_ttdriver->ttys)
		return -ENOMEM;

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28)
	nd->nd_callout_ttdriver->refcount = nd->nd_tty_ref_cnt;
#endif

	tty_set_operations(nd->nd_callout_ttdriver, &dgrp_tty_ops);

	if (GLBL(register_cudevices)) {
		if (!(nd->nd_ttdriver_flags & CALLOUT_TTDRV_REG)) {
			/*
			 *   Register cu devices
			 */
			rc = tty_register_driver(nd->nd_callout_ttdriver);
			if (rc < 0) {
				dbg_trace(("Can't register cu device (%d)\n", rc));
				return rc;
			}
			nd->nd_ttdriver_flags |= CALLOUT_TTDRV_REG;
		}
	}


#if LINUX_VERSION_CODE < KERNEL_VERSION(5,14,0)
	nd->nd_xprint_ttdriver = alloc_tty_driver(CHAN_MAX);
#else
	nd->nd_xprint_ttdriver = tty_alloc_driver(CHAN_MAX, 
 (TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV | TTY_DRIVER_HARDWARE_BREAK));
#endif
	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   = DRVSTR "(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 =
		dgrp_kzmalloc(CHAN_MAX * sizeof(struct tty_struct *), GFP_KERNEL);
	if (!nd->nd_xprint_ttdriver->ttys)
		return -ENOMEM;

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28)
	nd->nd_xprint_ttdriver->refcount = nd->nd_tty_ref_cnt;
#endif

	tty_set_operations(nd->nd_xprint_ttdriver, &dgrp_tty_ops);

	if (GLBL(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) {
				dbg_trace(("Can't register transparent print"
					   " device (%d)\n", rc));
				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));
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,0)
		tty_port_init(&ch->port);
#endif

	}

	dbg_tty_trace(INIT, ("tty init: done\n"));

	return 0;
}
