/*
 * This file has been modified as part of the MH-MiNT project. See
 * the file Changes.MH for details and dates.
 */

/*
Copyright 1990,1991,1992 Eric R. Smith.
Copyright 1992,1993,1994 Atari Corporation.
All rights reserved.
*/

/*
 * read/write routines for TTY devices
 */

#include "mint.h"

/* emulate mdm0 ioctls too */
#define MDM0_IOCTLS
#ifdef MDM0_IOCTLS
#define TIOCGHUPCL	(('T'<< 8) | 98)
#define TIOCSHUPCL	(('T'<< 8) | 99)
#define TIOCGSOFTCAR	(('T'<< 8) | 100)
#define TIOCSSOFTCAR	(('T'<< 8) | 101)
#endif

static void _erase P_((FILEPTR *, int, int));
static int escseq P_((struct tty *, int));

/* setting a special character to this value disables it */
#define UNDEF 0

#define HAS_WRITEB(f)	(((f)->fc.fs != &bios_filesys || \
			 (((struct bios_file *)(f)->fc.index)->drvsize > \
				offsetof (DEVDRV, writeb))) && \
			 (f)->dev->writeb)
extern FILESYS bios_filesys;

/* default terminal characteristics */

struct tty default_tty = {
	0,			/* process group */
	0,			/* state */
	0,			/* use_cnt */
	0,			/* aux_cnt */
	{
	13, 13,			/* input speed == output speed == 9600 baud */
	CTRL('H'),		/* erase */
	CTRL('U'),		/* kill */
	T_ECHO|T_CRMOD|T_TOSTOP|T_XKEY|T_ECHOCTL, /* flags */
	},
	{
	CTRL('C'),		/* interrupt */
	CTRL('\\'),		/* quit */
	CTRL('Q'),		/* start */
	CTRL('S'),		/* stop */
	CTRL('D'),		/* EOF */
	'\r'			/* alternate end of line */
	},
	{
	CTRL('Z'),		/* suspend */
	CTRL('Y'),		/* suspend after read */
	CTRL('R'),		/* reprint */
	CTRL('O'),		/* flush output */
	CTRL('W'),		/* erase word */
	CTRL('V')		/* quote next char */
	},
	{
	0, 0, 0, 0		/* window size is unknown */
	},
	0,			/* no process is selecting us for reading */
	0,			/* or for writing */
	0,			/* use default XKEY map */
	0,			/* not currently hanging up */
	1, 0			/* RAW reads need 1 char, no timeout */
};

#define _put(f, c) (tty_putchar((f), (c), RAW))

static void
_erase(f, c, mode)
	FILEPTR *f;
	int c, mode;
{
	_put(f, '\010');
	_put(f, ' ');
	_put(f, '\010');
/* watch out for control characters -- they're printed as e.g. "^C" */
/* BUG: \t is messed up. We really need to keep track of the output
 * column
 */
	if ((mode & T_ECHOCTL) && c >= 0 && c < ' ' && c != '\t') {
		_put(f, '\010'); _put(f, ' '); _put(f, '\010');
	}
}

#define put(f, c)   { if (mode & T_ECHO) _put(f, c); }
#define erase(f, c) { if (mode & T_ECHO) _erase(f, c, mode); }

long
tty_read(f, buf, nbytes)
	FILEPTR *f;
	void *buf;
	long nbytes;
{
	long r;
	long bytes_read = 0;
	unsigned char ch, *ptr;
	int rdmode, mode;
	struct tty *tty;

	tty = (struct tty *)f->devinfo;
	assert(tty != 0);

	if (f->flags & O_HEAD) {	/* pty server side? */
		rdmode = RAW;		/* yes -- always raw mode */
		mode = T_RAW;
	}
	else if (curproc->domain == DOM_MINT) {	/* MiNT domain process? */
		mode = tty->sg.sg_flags;
		rdmode = COOKED|NOECHO;
		if ( mode & (T_RAW | T_CBREAK) ) {
			rdmode = (mode & T_RAW) ? RAW : COOKED;
		}
		if (mode & T_XKEY)
			rdmode |= ESCSEQ;
	}
	else {
		rdmode = COOKED|NOECHO;
		mode = T_TOS | T_ECHO | T_ECHOCTL;
	}

	if (nbytes == 0) return bytes_read;

	/* if RAW or CBREAK do VTIME:  select for input, return on timeout */
	if (tty->vtime && (mode & (T_RAW|T_CBREAK)) &&
	    !(f->flags & (O_NDELAY|O_HEAD)) &&
	    (!(tty->state & TS_ESC) || !(rdmode & ESCSEQ))) {
		long r, bytes = 0;

		if ((r = (*f->dev->select)(f, (long)curproc, O_RDONLY)) != 1) {
			TIMEOUT *t;

			curproc->wait_cond = (long)wakeselect;	/* flag */
			t = addtimeout((long)tty->vtime, (void (*)P_((PROC *)))wakeselect);
			if (!t) {
				(*f->dev->unselect)(f, (long)curproc, O_RDONLY);
				return ENSMEM;
			}
			while (curproc->wait_cond == (long)wakeselect) {
				TRACE(("sleeping in tty_read (VTIME)"));
				if (sleep(SELECT_Q|0x100, (long)wakeselect))
					break;
			}
			canceltimeout(t);
		}
		(void)(*f->dev->ioctl)(f, FIONREAD, &bytes);
		if (!r) {
			extern short select_coll;	/* in dosfile.c */
			(*f->dev->unselect)(f, (long)curproc, O_RDONLY);
			wake(SELECT_Q, (long)&select_coll);
		}
		if (!bytes)
			return bytes_read;
	}
#if 1
	/* see if we can do fast RAW byte IO thru the device driver... */
	if (rdmode == RAW &&
	    (!(tty->state & TS_ESC) || !(rdmode & ESCSEQ) ||
		(f->flags & O_HEAD)) &&
	    (f->fc.fs != &bios_filesys ||
		(nbytes > 1 &&
		 ((struct bios_file *)f->fc.index)->drvsize >
			offsetof (DEVDRV, readb))) &&
	    f->dev->readb &&
	    ((f->flags & O_HEAD) || ((tty->state &= ~TS_COOKED), !tty->pgrp) ||
		tty->pgrp == curproc->pgrp ||
		f->fc.dev != curproc->control->fc.dev ||
		f->fc.index != curproc->control->fc.index) &&
	    !(tty->state & TS_BLIND) &&
	    (r = (*f->dev->readb)(f, buf, nbytes)) != EUNDEV)
		return r;
#endif

	ptr = buf;

	while (bytes_read < nbytes) {
		r = tty_getchar(f, rdmode);
		if (r < 0) {
tty_error:
			DEBUG(("tty_read: tty_getchar returned %ld", r));
			return (bytes_read) ? bytes_read : r;
		}
		else if (r == MiNTEOF)
			return bytes_read;
		ch = r & 0xff;

		if ( (rdmode & COOKED) && (mode & T_CRMOD) && (ch == '\r') )
			ch = '\n';

/* 1 character reads in TOS mode are always raw */
		if (nbytes == 1 && (mode & T_TOS)) {
			put(f, ch);
			*ptr = ch;
			return 1;
		}

/* T_CBREAK mode doesn't do erase or kill processing */
/* also note that setting a special character to UNDEF disables it */

		if (rdmode & COOKED && !(mode & T_CBREAK) && ch != UNDEF) {
			if ((char)ch == tty->sg.sg_erase) {  /* backspace */
				if (bytes_read > 0) {
					--ptr;
					erase(f, *ptr);
					bytes_read--;
				}
				continue;
			}
			else if ((mode & T_TOS) && ch == CTRL('X')) {
				while (bytes_read > 0) {
					--ptr;
					erase(f, *ptr);
					bytes_read--;
				}
				continue;
			}
			else if ((char)ch ==tty->ltc.t_rprntc || 
				 (char)ch == tty->sg.sg_kill) {
				if (mode & T_TOS)
					put(f, '#');
				put(f, '\r');
				put(f, '\n');
				ptr = buf;
				if ((char)ch == tty->sg.sg_kill) {
					bytes_read = 0;
				}
				else {
					for (r = 0; r < bytes_read; r++, ptr++)
						put(f, *ptr);
				}
				continue;
			}
			else if ((char)ch == tty->ltc.t_werasc) {
				while (bytes_read > 0 &&
				       (ptr[-1] == ' ' || ptr[-1] == '\t')) {
					ptr--;
					erase(f, *ptr);
					bytes_read--;
				}
				while (bytes_read > 0 &&
				       !(ptr[-1] == ' ' || ptr[-1] == '\t')) {
					ptr--;
					erase(f, *ptr);
					bytes_read--;
				}
				continue;
			}
			else if ((char)ch == tty->ltc.t_lnextc) {
				if (mode & T_ECHOCTL) {
					put(f, '^');
					put(f, '\b');
				}
				r = tty_getchar(f, RAW);
				if (rdmode & COOKED)
					tty->state |= TS_COOKED;
				else
					tty->state &= ~TS_COOKED;

				if (r < 0)
					goto tty_error;
				else if (r == MiNTEOF)
					return bytes_read;
				ch = r & 0xff;
				goto stuff_it;
			}
			else if ((char)ch == tty->tc.t_eofc && !(mode & T_TOS))
				return bytes_read;
		}

/* both T_CBREAK and T_COOKED modes have to do signals, though */
		if ((rdmode & COOKED) && ch != UNDEF) {
			if ((char)ch == tty->tc.t_intrc
				 || (char)ch == tty->tc.t_quitc
				 || (char)ch == tty->ltc.t_dsuspc
				 || (char)ch == tty->ltc.t_suspc
				) {
/* the device driver raised the appropriate signal; if we get here, the
   signal was caught by the user (or ignored). flush buffers and continue
 */
				if (!(tty->sg.sg_flags & T_NOFLSH)) {
					DEBUG(("tty_read: flushing input"));
					bytes_read = 0;
					ptr = buf;
				}
				continue;
			}
			else if ((char)ch == tty->ltc.t_flushc) {
				continue;
			}
			else if (ch == '\n' || (char)ch == tty->tc.t_brkc) {
				put(f, '\r');
				if (!(mode & T_TOS)) {
					*ptr = ch;
					put(f, '\n');
					bytes_read++;
				}
				if (rdmode & COOKED)
					tty->state |= TS_COOKED;
				else
					tty->state &= ~TS_COOKED;
				return bytes_read;
			}

		}

/* do the following for both RAW and COOKED mode */
stuff_it:
		*ptr++ = ch;
		if ((mode & T_ECHOCTL) &&
		    ch < ' ' && ch != '\t') {	/* ch is unsigned */
			put(f, '^'); put(f, ch+'@');
		}
		else
			put(f, ch);
		bytes_read++;

/* for RAW mode, if there are no more characters then break */
		if ( (mode & (T_RAW|T_CBREAK)) &&
		    !(tty->state & TS_ESC)) {
			r = 1;
			(void)(*f->dev->ioctl)(f, FIONREAD, &r);
			if (r <= 0) break;
		}	
	}
	if (rdmode & COOKED)
		tty->state |= TS_COOKED;
	else
		tty->state &= ~TS_COOKED;
	return bytes_read;
}

/* job control checks */
/* AKP: added T_TOSTOP; don't stop BG output if T_TOSTOP is clear */
/*
entropy: only do the job control if SIGTTOU is neither blocked nor ignored,
and only for the controlling tty (IEEE 1003.1-1990 7.1.1.4 79-87).
BUG:  if the process group is orphaned and SIGTTOU *is not* blocked
or ignored, we should return EIO instead of signalling.
*/
INLINE void
tty_checkttou (f, tty)
	FILEPTR *f;
	struct tty *tty;
{
	if (tty->pgrp && tty->pgrp != curproc->pgrp &&
	         (tty->sg.sg_flags & T_TOSTOP) &&
                 (curproc->sighandle[SIGTTOU] != SIG_IGN) &&
                 ((curproc->sigmask & (1L << SIGTTOU)) == 0L) &&
                 (f->fc.dev == curproc->control->fc.dev) &&
                 (f->fc.index == curproc->control->fc.index)) {
		TRACE(("job control: tty pgrp is %d proc pgrp is %d",
			tty->pgrp, curproc->pgrp));
		killgroup(curproc->pgrp, SIGTTOU, 1);
		check_sigs();
	}
}

long
tty_write(f, buf, nbytes)
	FILEPTR *f;
	const void *buf;
	long nbytes;
{
	unsigned const char *ptr;
	long c;
	long bytes_written;
	int mode, rwmode;
	struct tty *tty;
	int use_putchar = 0;
	static long cr_char = '\r';
#define LBUFSIZ 128
	long lbuf[LBUFSIZ];

	tty = (struct tty *)f->devinfo;
	assert(tty != 0);

	ptr = (unsigned const char *)buf;
	if (f->flags & O_HEAD) {
		use_putchar = 1;
		mode = T_RAW;
	}
	else if (curproc->domain == DOM_TOS)
/* for TOS programs, 1 byte writes are always in raw mode */
		mode = (nbytes == 1) ? T_RAW : T_TOS;
	else
		mode = tty->sg.sg_flags;

	rwmode = (mode & T_RAW) ? RAW : COOKED;

	bytes_written = 0;

/*
 * "mode" can now be reduced to just T_CRMODE or not
 */
	if ((curproc->domain == DOM_MINT) && (mode & T_CRMOD) &&
	    !(mode & T_RAW))
		mode = T_CRMOD;
	else
		mode = 0;

	if (nbytes == 0) return bytes_written;
#if 1
	/* see if we can do fast RAW byte IO thru the device driver... */
	if (!use_putchar && HAS_WRITEB(f)) {

		tty_checkttou (f, tty);
		if (rwmode & COOKED)
			tty->state |= TS_COOKED;
		else
			tty->state &= ~TS_COOKED;
		if (tty->state & TS_BLIND)
			return bytes_written;
		if (mode) {	/* i.e. T_CRMODE */
			if ((*f->dev->writeb)(f, buf, 0L) != EUNDEV) {
	/* write in big chunks if possible; lines if CRMODE
	 * (if we get here flow control is taken care of by the device)
	 */
				long bytes_to_write = 0;
				unsigned const char *s = ptr;

				while (nbytes-- > 0) {
					if (*ptr++ == '\n') {
						if (0 != (bytes_to_write = ptr-s-1)) {
							c = (*f->dev->writeb)(f, (const char *)s,
								bytes_to_write);
							bytes_written += c;
							if (c != bytes_to_write) {
								if (c < 0)
									bytes_written = c;
								return bytes_written;
							}
						}
						s = ptr-1;
						c = (*f->dev->writeb)(f, "\r", 1);
						if (c != 1) {
							if (c < 0)
								bytes_written = c;
							return bytes_written;
						}
					}
				}
				if (0 != (bytes_to_write = ptr-s)) {
					c = (*f->dev->writeb)(f, (const char *)s, bytes_to_write);
					bytes_written += c;
					if (c < 0)
						bytes_written = c;
				}
				return bytes_written;
			}
		} else if ((c = (*f->dev->writeb)(f, buf, nbytes)) != EUNDEV)
			return c;
	}
#endif

/*
 * we always write at least 1 byte with tty_putchar, since that takes
 * care of job control and terminal states. After that, we may be able
 * to use (*f->dev->write) directly.
 */

	c = *ptr++;

	if (c == '\n' && mode) {	/* remember, "mode" now means CRMOD */
		tty_putchar(f, cr_char, rwmode);
	}
	tty_putchar(f, c, rwmode);
	nbytes--;
	bytes_written++;

	if (use_putchar) {
		while (nbytes-- > 0) {
			c = *ptr++;
			if (c == '\n' && mode)
				tty_putchar(f, cr_char, rwmode);
			tty_putchar(f, c, rwmode);
			bytes_written++;
		}
	} else {
/* write in big chunks if possible; but never more than 1 line
 * (so that ^S/^Q can happen reasonably quickly for the user)
 */
		long bytes_to_write = 0;
		long *s = lbuf;

		while (nbytes-- > 0) {
			c = *ptr++;
			if (c == '\n') {
				if (bytes_to_write) {
					(*f->dev->write)(f, (char *)lbuf,
						bytes_to_write);
					bytes_to_write = 0;
					s = lbuf;
				}
				if (mode)	/* i.e. T_CRMODE */
					tty_putchar(f, cr_char, rwmode);
				tty_putchar(f, (long)c, rwmode);
				bytes_written++;
			} else {
				*s++ = c;
				bytes_written++;
				bytes_to_write += 4;
				if (bytes_to_write >= LBUFSIZ*4) {
					(*f->dev->write)(f, (char *)lbuf,
							 bytes_to_write);
					bytes_to_write = 0;
					s = lbuf;
				}
			}
		}
		if (bytes_to_write) {
			(*f->dev->write)(f, (char *)lbuf, bytes_to_write);
		}
	}

	return bytes_written;
}

/* some notable scan codes */
#define K_INSERT	0x52
#define K_HOME		0x47
#define K_UNDO		0x61
#define K_HELP		0x62
#define CURS_UP		0x48
#define CURS_DN		0x50
#define CURS_RT		0x4d
#define CURS_LF		0x4b
#define F_1		0x3b
#define F_10		0x44
#define F_11		0x54
#define F_20		0x5d
#define ALT_1		0x78
#define ALT_0		0x81

/* Default function key table:
 * entries:	0-9 are F1-F10
 *        	10-19 are F11-F20
 *		20-23 are cursor up, down, right, and left
 *		24-27 are help, undo, insert, and home
 *		28-31 are shift+cursor up, down, right, and left
 */

static char vt52xkey[256] = {
'\033', 'P', 0, 0, 0, 0, 0, 0,
'\033', 'Q', 0, 0, 0, 0, 0, 0,
'\033', 'R', 0, 0, 0, 0, 0, 0,
'\033', 'S', 0, 0, 0, 0, 0, 0,
'\033', 'T', 0, 0, 0, 0, 0, 0,
'\033', 'U', 0, 0, 0, 0, 0, 0,
'\033', 'V', 0, 0, 0, 0, 0, 0,
'\033', 'W', 0, 0, 0, 0, 0, 0,
'\033', 'X', 0, 0, 0, 0, 0, 0,
'\033', 'Y', 0, 0, 0, 0, 0, 0,
'\033', 'p', 0, 0, 0, 0, 0, 0,
'\033', 'q', 0, 0, 0, 0, 0, 0,
'\033', 'r', 0, 0, 0, 0, 0, 0,
'\033', 's', 0, 0, 0, 0, 0, 0,
'\033', 't', 0, 0, 0, 0, 0, 0,
'\033', 'u', 0, 0, 0, 0, 0, 0,
'\033', 'v', 0, 0, 0, 0, 0, 0,
'\033', 'w', 0, 0, 0, 0, 0, 0,
'\033', 'x', 0, 0, 0, 0, 0, 0,
'\033', 'y', 0, 0, 0, 0, 0, 0,
'\033', 'A', 0, 0, 0, 0, 0, 0,
'\033', 'B', 0, 0, 0, 0, 0, 0,
'\033', 'C', 0, 0, 0, 0, 0, 0,
'\033', 'D', 0, 0, 0, 0, 0, 0,
'\033', 'H', 0, 0, 0, 0, 0, 0,
'\033', 'K', 0, 0, 0, 0, 0, 0,
'\033', 'I', 0, 0, 0, 0, 0, 0,
'\033', 'E', 0, 0, 0, 0, 0, 0,
'\033', 'a', 0, 0, 0, 0, 0, 0,
'\033', 'b', 0, 0, 0, 0, 0, 0,
'\033', 'c', 0, 0, 0, 0, 0, 0,
'\033', 'd', 0, 0, 0, 0, 0, 0,
};

static char unxbaud P_((long));

/* convert a number describing the baud rate into a Unix
 * style baud rate number. Returns the Unix baud rate,
 * or 16 (EXTA) if the rate is unknown
 */

#define EXTA 16

static long ubaud[EXTA] = {
0L, 50L, 75L, 110L, 134L, 150L, 200L, 300L,
600L, 1200L, 1800L, 2400L, 4800L, 9600L, 19200L, 38400L
};

static char
unxbaud(baud)
	long baud;
{
	int i;
	for (i = 1; i < EXTA; i++) {
		if (ubaud[i] == baud)
			break;
	}
	return i;
}

#define tosbaud(c) ( ((c) < 0 || (c) >= EXTA) ? -1L : ubaud[(unsigned)c] )

long
tty_ioctl(f, mode, arg)
	FILEPTR *f;
	int mode;
	void *arg;
{
	struct sgttyb *sg;
	struct tchars *tc;
	struct ltchars *ltc;
	struct tty *tty;
	struct winsize *sz;
	struct xkey *xk;
	char *xktab;
	int i;
	long baud;
	short flags;
	long outq;

	if (!is_terminal(f)) {
		DEBUG(("tty_ioctl(mode %x): file is not a tty", mode));
		return EINVFN;
	}
	tty = (struct tty *)f->devinfo;
	assert(tty != 0);

	switch(mode) {
	case FIONREAD:
	    {
		long r;

		r = (*f->dev->ioctl)(f, FIONREAD, (void *)arg);
		if (r || (f->flags & O_HEAD))
			return r;
		if (tty->state & TS_BLIND)
			*(long *)arg = 0;
		if ((tty->sg.sg_flags & T_XKEY) && (tty->state & TS_ESC) &&
		    !*(long *)arg)
			*(long *)arg = 1;
		return 0;
	    }
	case FIONWRITE:
	    {
		long r;

		r = (*f->dev->ioctl)(f, FIONWRITE, (void *)arg);
		if (r || (f->flags & O_HEAD))
			return r;
		if ((tty->state & (TS_BLIND|TS_HOLD)))
			*(long *)arg = 0;
		return 0;
	    }
	case TIOCSBRK:
		if (!(tty->state & TS_BLIND) || (f->flags & O_HEAD))
			return (*f->dev->ioctl)(f, TIOCSBRK, (void *)arg);
		return 0;
	case TIOCFLUSH:
	    {
		long r;

		r = (*f->dev->ioctl)(f, TIOCFLUSH, (void *)arg);
		if (r || (f->flags & O_HEAD))
			return r;
		if (!arg || (*(int *)arg & 1))
			tty->state &= ~TS_ESC;
		return 0;
	    }
	case TIOCGETP:
	    {
		unsigned long bits[2] = {-1, TF_FLAGS};
		sg = (struct sgttyb *)arg;
	/* get input and output baud rates from the terminal device */
		baud = -1L;
		(*f->dev->ioctl)(f, TIOCIBAUD, &baud);
		tty->sg.sg_ispeed = unxbaud(baud);
		baud = -1L;
		(*f->dev->ioctl)(f, TIOCOBAUD, &baud);
		tty->sg.sg_ospeed = unxbaud(baud);
	/* get terminal flags */
		flags = 0;
		if ((*f->dev->ioctl)(f, TIOCSFLAGSB, &bits) == 0) {
			tty->sg.sg_flags &= ~TF_FLAGS;
			tty->sg.sg_flags |= (*bits & TF_FLAGS);
		} else if ((*f->dev->ioctl)(f, TIOCGFLAGS, &flags) == 0) {
			tty->sg.sg_flags &= ~TF_FLAGS;
			tty->sg.sg_flags |= (flags & TF_FLAGS);
		}
		*sg = tty->sg;
		return 0;
	    }
	case TIOCSETP:
		while (((*f->dev->ioctl)(f, TIOCOUTQ, &outq) == 0) && outq)
			nap(200);
		/* FALL THROUGH */
	case TIOCSETN:
	    {
		unsigned long bits[2];
		static unsigned short v[] = {1, 0};
		unsigned short oflags = tty->sg.sg_flags;

		sg = (struct sgttyb *)arg;
		tty->sg = *sg;
	/* change tty state */
		if (sg->sg_flags & T_RAW) {
			tty->state &= ~TS_COOKED;
		} else {
			tty->state |= TS_COOKED;
		}
		if (!(sg->sg_flags & T_XKEY)) {
			tty->state &= ~TS_ESC;
		}
	/* set baud rates */
		baud = tosbaud(sg->sg_ispeed);
		(*f->dev->ioctl)(f, TIOCIBAUD, &baud);
		baud = tosbaud(sg->sg_ospeed);
		(*f->dev->ioctl)(f, TIOCOBAUD, &baud);
	/* reset VMIN/VTIME */
		if ((*f->dev->ioctl)(f, TIOCSVMIN, &v) < 0) {
			tty->vmin = 1;
			tty->vtime = 0;
		}
	/* set parity, etc. */
		flags = TF_8BIT;
		if (sg->sg_flags & (T_EVENP|T_ODDP)) {
			flags = TF_7BIT;
		}
		flags |= (sg->sg_flags & TF_FLAGS);
	/* default allow breaks to SIGINT unless RAW and no echo... */
		if (!(sg->sg_flags & T_RAW) || (sg->sg_flags & T_ECHO))
			flags |= TF_BRKINT;
	/* leave local mode bit alone */
		bits[0] = (unsigned)flags; bits[1] = ~TF_CAR;
		if ((*f->dev->ioctl)(f, TIOCSFLAGSB, &bits) >= 0)
			return 0;
	/* if TIOCSFLAGSB failed clear TF_CAR, assume the device doesn't
	 * know about carrier anyway... */
		if ((*f->dev->ioctl)(f, TIOCSFLAGS, &flags) >= 0)
			return 0;
	/* cannot set flags, don't save them */
		tty->sg.sg_flags = (tty->sg.sg_flags & ~TF_FLAGS)|
						(oflags & TF_FLAGS);
		return 0;
	    }
	case TIOCGETC:
		tc = (struct tchars *)arg;
		*tc = tty->tc;
		return 0;
	case TIOCSETC:
		tc = (struct tchars *)arg;
		tty->tc = *tc;
		return 0;
	case TIOCGLTC:
		ltc = (struct ltchars *)arg;
		*ltc = tty->ltc;
		return 0;
	case TIOCSLTC:
		ltc = (struct ltchars *)arg;
		tty->ltc = *ltc;
		return 0;
	case TIOCGWINSZ:
		sz = (struct winsize *)arg;
		*sz = tty->wsiz;
		return 0;
	case TIOCSWINSZ:
		sz = (struct winsize *)arg;
		if (sz->ws_row != tty->wsiz.ws_row
		    || sz->ws_col != tty->wsiz.ws_col
		    || sz->ws_xpixel != tty->wsiz.ws_xpixel
		    || sz->ws_ypixel != tty->wsiz.ws_ypixel)
		  i = 1;
		else
		  i = 0;
		tty->wsiz = *sz;
		if (i && tty->pgrp) killgroup(tty->pgrp, SIGWINCH, 1);
		return 0;
	case TIOCGPGRP:
		*((long *)arg) = tty->pgrp;
		return 0;
	case TIOCSPGRP:
		if (!tty->pgrp) {
			tty->pgrp = (*((long *)arg) & 0x00007fffL);

			if (!(f->flags & O_NDELAY) && (tty->state & TS_BLIND))
				(*f->dev->ioctl)(f, TIOCWONLINE, 0);
		} else {
			tty->pgrp = (*((long *)arg) & 0x00007fffL);
		}
		return 0;
	case TIOCSTART:
		/* tty in the middle of a hangup? */
		if (tty->hup_ospeed)
			return 0;
/* if the device has writeb writers may sleep for TS_HOLD (instead of polling),
 * tell the device and wake them up
 */
		if (HAS_WRITEB(f)) {
			(void)(*f->dev->ioctl)(f, TIOCSTART, &tty->state);
			tty->state &= ~TS_HOLD;
			wake (IO_Q, (long)&tty->state);
		}
		tty->state &= ~TS_HOLD;
		if (tty->wsel) {
			long r = 0;

			(void)(*f->dev->ioctl)(f, FIONWRITE, &r);
			if (r && !(tty->state & TS_BLIND))
				wakeselect (tty->wsel);
		}
		return 0;
	case TIOCSTOP:
		if (HAS_WRITEB(f))
			(void)(*f->dev->ioctl)(f, TIOCSTOP, &tty->state);
		tty->state |= TS_HOLD;
		return 0;
	case TIOCGXKEY:
		xk = (struct xkey *)arg;
		i = xk->xk_num;
		if (i < 0 || i > 31) return ERANGE;
		xktab = tty->xkey;
		if (!xktab) xktab = vt52xkey;
		xktab += i*8;
		for (i = 0; i < 8; i++)
			xk->xk_def[i] = *xktab++;
		return 0;
	case TIOCSXKEY:
		xk = (struct xkey *)arg;
		xktab = tty->xkey;
		if (!xktab) {
			xktab = kmalloc((long)256);
			if (!xktab) return ENSMEM;
			for (i = 0; i < 256; i++)
				xktab[i] = vt52xkey[i];
			tty->xkey = xktab;
		}
		i = xk->xk_num;
		if (i < 0 || i > 31) return ERANGE;
		xktab += i*8;
		for (i = 0; i < 7; i++)
			xktab[i] = xk->xk_def[i];
		xktab[7] = 0;
		return 0;
/*
 * change tty->state bits...  really only makes sense to touch TS_HPCL
 * or maybe TS_COOKED.  (TS_HOLD is already handled by TIOCSTART/STOP)
 */
	case TIOCSSTATEB:
	    {
		long mask = ((long *)arg)[1] & ~(TS_HOLD|TS_BLIND);
		if (!(tty->sg.sg_flags & T_XKEY))
			mask &= ~TS_ESC;
		if (*(long *)arg != -1)
			tty->state = (tty->state & ~mask) | (*((long *)arg) & mask);
		*(long *)arg = tty->state;
		return 0;
	    }
	case TIOCGSTATE:
		*(long *)arg = tty->state;
		return 0;
/* hang up on close, handled by kernel if the device understands TIOCOBAUD */
	case TIOCHPCL:
		tty->state |= TS_HPCL;
		return 0;
/* set/reset local mode */
	case TIOCNCAR:
	case TIOCCAR:
	    {
		unsigned long bits[2] = {0, TF_CAR};
		if (mode == TIOCCAR)
			*bits = TF_CAR;
		(*f->dev->ioctl)(f, TIOCSFLAGSB, &bits);
/*
 * if the ioctl failed the device does not know about carrier but don't
 * return an error then since its the same as carrier always on
 * (and anyway who puts a dialup line on a port that doesn't know
 * how to SIGHUP or hang up safely... :)
 */
		return 0;
	    }
/* emulate some new calls, they only get here when a device does not know them:
 */
	case TIOCSFLAGSB:
	    {
		long fnew, mask = ((unsigned long *)arg)[1];
		if (*(long *)arg < 0) {
			(*f->dev->ioctl)(f, TIOCGFLAGS, &flags);
			*((unsigned long *)arg) = flags;
			return 0;
		}
		flags = 0;
		if (mask != -1)
			(*f->dev->ioctl)(f, TIOCGFLAGS, &flags);
		fnew = (flags & ~mask) | (*((unsigned long *)arg) & mask);
		if (mask == -1 || fnew != flags) {
			flags = fnew;
			(*f->dev->ioctl)(f, TIOCSFLAGS, &flags);
			(*f->dev->ioctl)(f, TIOCGFLAGS, &flags);
		}
		*(unsigned long *)arg = flags;
		return 0;
	    }
/*
 * tty_read handles VTIME itself but doing VMIN > 1 without support
 * from the device won't be very efficient
 */
	case TIOCGVMIN:
		((unsigned short *)arg)[0] = 1;			/* VMIN */
		((unsigned short *)arg)[1] = tty->vtime;	/* VTIME */
		return 0;
	case TIOCSVMIN:
		tty->vmin = 1;
		tty->vtime = ((unsigned short *)arg)[1];
		return 0;
/* devices that don't know about carrier are always online... */
	case TIOCWONLINE:
		return 0;
/* if the device didn't do TIOC[GS]FLAGS try transforming into TIOCSFLAGSB */
	case TIOCGFLAGS:
	case TIOCSFLAGS:
	    {
		unsigned long bits[2] = {-1, (unsigned short)-1};
		long r;

		if (mode == TIOCSFLAGS)
			bits[0] = *(unsigned short *)arg;
		r = (*f->dev->ioctl)(f, TIOCSFLAGSB, &bits);
		if (!r && mode == TIOCGFLAGS)
			*((unsigned short *)arg) = *bits;
		return r;
	    }
#ifdef MDM0_IOCTLS
/*
 * transform mdm0 ioctls, to allow old binaries run on new devices
 * note this does nothing for the other way around i.e. transform the
 * BSD ones (TIOCCAR/HPCL etc.) for mdm0...
 */
	case TIOCGHUPCL:
		*(short *)arg = tty->state & TS_HPCL ? 1 : 0;
		return 0;
	case TIOCSHUPCL:
		flags = *(short *)arg;
		*(short *)arg = tty->state & TS_HPCL ? 1 : 0;
		if (flags)
			tty->state |= TS_HPCL;
		else
			tty->state &= ~TS_HPCL;
		return 0;
	case TIOCGSOFTCAR:
	    {
		long bits[2];
		flags = 1;
		bits[0] = -1; bits[1] = TF_CAR;
		if ((*f->dev->ioctl)(f, TIOCSFLAGSB, &bits) >= 0)
			flags = bits[0] & TF_CAR ? 0 : 1;
		*(short *)arg = flags;
		return 0;
	    }
	case TIOCSSOFTCAR:
	    {
		long bits[2];
		flags = 1;
		bits[0] = *(short *)arg ? 0 : TF_CAR; bits[1] = TF_CAR;
		if ((*f->dev->ioctl)(f, TIOCSFLAGSB, &bits) >= 0)
			flags = bits[0] & TF_CAR ? 0 : 1;
		*(short *)arg = flags;
		return 0;
	    }
#endif /* MDM0_IOCTLS */
	default:
		DEBUG(("tty_ioctl: bad function call"));
		return EINVFN;
	}
}

/*
 * function for translating extended characters (e.g. cursor keys, or
 * ALT+key sequences) into either escape sequences or meta characters.
 * for escape sequences, we return the the first character of the
 * sequence (normally ESC) and set the tty's state so that subsequent
 * calls to tty_getchar will pick up the remaining characters.
 * Note that escape sequences are limited to 7 characters at most.
 */

static int
escseq(tty, scan)
	struct tty *tty;
	int scan;
{
	char *tab;
	int i;

	switch(scan) {
	case CURS_UP: i = 20; break;
	case CURS_DN: i = 21; break;
	case CURS_RT: i = 22; break;
	case CURS_LF: i = 23; break;
	case K_HELP:  i = 24; break;
	case K_UNDO:  i = 25; break;
	case K_INSERT:i = 26; break;
	case K_HOME:  i = 27; break;
	case CURS_UP+0x100: i = 28; break;
	case CURS_DN+0x100: i = 29; break;
	case CURS_RT+0x100: i = 30; break;
	case CURS_LF+0x100: i = 31; break;
	default:
		if (scan >= F_1 && scan <= F_10) {
			i = scan - F_1;
		} else if (scan >= F_11 && scan <= F_20) {
			i = 10 + scan - F_11;
		} else
			i = -1;
	}

	if (i >= 0) {		/* an extended escape sequence */
		tab = tty->xkey;
		if (!tab) tab = vt52xkey;
		i *= 8;
		scan = tab[i++];
		if (scan) {
			if (tab[i] == 0) i = 0;
			tty->state = (tty->state & ~TS_ESC) | i;
		}
		return scan;
	}

	if (scan >= ALT_1 && scan <= ALT_0) {
		scan -= (ALT_1-1);
		if (scan == 10) scan = 0;
		return (scan + '0') | 0x80;
	}

	tab = *( ((char **)Keytbl((void *)-1UL, (void *)-1UL, (void *)-1UL)) + 2 );	/* gratuitous (void *) for Lattice */
	scan = tab[scan];
	if (scan >= 'A' && scan <= 'Z') return scan | 0x80;
	return 0;
}

long
tty_getchar(f, mode)
	FILEPTR *f;
	int mode;
{
	struct tty *tty = (struct tty *)f->devinfo;
	char c, *tab;
	long r, ret;
	int scan;
	int master = f->flags & O_HEAD;

	assert(tty);

/* pty masters never worry about job control and always read in raw mode */
	if (master) {
		ret = (*f->dev->read)(f, (char *)&r, 4L);
		return (ret != 4L) ? MiNTEOF : r;
	}

/* job control check */

/*
entropy: only do the job control if SIGTTIN is neither blocked nor ignored,
and only for the controlling tty (IEEE 1003.1-1990 7.1.1.4 70-78).
BUG:  if the process group is orphaned or SIGTTIN *is* blocked or ignored,
we should return EIO instead of signalling.
*/

	if ((tty->pgrp && tty->pgrp != curproc->pgrp) &&
            (f->fc.dev == curproc->control->fc.dev) &&
            (f->fc.index == curproc->control->fc.index)) {
		TRACE(("job control: tty pgrp is %d proc pgrp is %d",
			tty->pgrp, curproc->pgrp));
		killgroup(curproc->pgrp, SIGTTIN, 1);
		check_sigs();
	}

	if (mode & COOKED)
		tty->state |= TS_COOKED;
	else
		tty->state &= ~TS_COOKED;

	c = UNDEF+1;	/* set to UNDEF when we successfully read a character */

/* we may be in the middle of an escape sequence */
	scan = (tty->state & TS_ESC);
	if (scan != 0) {
		tab = tty->xkey ? tty->xkey : vt52xkey;
		r = (unsigned char) tab[scan++];
		if (r) {
			c = UNDEF;
			if (tab[scan] == 0) scan = 0;
		}
		else
			scan = 0;
		tty->state = (tty->state & ~TS_ESC) | scan;
	}

	while (c != UNDEF) {
		if (tty->state & TS_BLIND) {
			TRACE(("tty_getchar: offline"));
			return MiNTEOF;
		}
		ret = (*f->dev->read)(f, (char *)&r, 4L);
		if (ret != 4L) {
			DEBUG(("EOF on tty device"));
			return MiNTEOF;
		}
		c = r & 0x00ff;
		scan = (int)((r & 0x00ff0000L) >> 16);
		if ( (c == 0) && (mode & ESCSEQ) && scan) {
			c = UNDEF;
	/* translate cursor keys, etc. into escape sequences or
	 * META characters
	 */
			r = escseq(tty, scan);
		} else if ((mode & ESCSEQ) && ((scan == CURS_UP && c == '8') ||
			   (scan == CURS_DN && c == '2') ||
			   (scan == CURS_RT && c == '6') ||
			   (scan == CURS_LF && c == '4'))) {
			c = UNDEF;
			r = escseq(tty, scan+0x100);
		} else if (mode & COOKED) {
			if (c == UNDEF)
				;	/* do nothing */
			else if (c == tty->ltc.t_dsuspc) {
				killgroup(curproc->pgrp, SIGTSTP, 1);
				check_sigs();
			} else if (c == tty->tc.t_intrc) {
				killgroup(curproc->pgrp, SIGINT, 1);
				check_sigs();
			} else if (c == tty->tc.t_quitc) {
				killgroup(curproc->pgrp, SIGQUIT, 1);
				check_sigs();
			} else if (c == tty->tc.t_stopc)
				tty_ioctl(f, TIOCSTOP, 0);
			else if (c == tty->tc.t_startc)
				tty_ioctl(f, TIOCSTART, 0);
			else
				c = UNDEF;
		}
		else
				c = UNDEF;
	}

	if (mode & ECHO)
		tty_putchar(f, r, mode);

	return r;
}

/*
 * tty_putchar: returns number of bytes successfully written
 */

long
tty_putchar(f, data, mode)
	FILEPTR *f;
	long data;
	int mode;
{
	struct tty *tty;
	int master;		/* file is pty master side */
	char ch;

	tty = (struct tty *)f->devinfo;

	master = f->flags & O_HEAD;

/* pty masters don't need to worry about job control */
	if (master) {
		ch = data & 0xff;

		if ( (tty->state & TS_COOKED) && ch != UNDEF) {
			long r = 1;

/* see if we're putting control characters into the buffer */
			if (ch == tty->tc.t_intrc) {
				killgroup(tty->pgrp, SIGINT, 1);
				if (!(tty->sg.sg_flags & T_NOFLSH))
					tty_ioctl(f, TIOCFLUSH, &r);
				tty_ioctl (f, TIOCSTART, 0);
				return 4L;
			}
			else if (ch == tty->tc.t_quitc) {
				killgroup(tty->pgrp, SIGQUIT, 1);
				if (!(tty->sg.sg_flags & T_NOFLSH))
					tty_ioctl(f, TIOCFLUSH, &r);
				tty_ioctl (f, TIOCSTART, 0);
				return 4L;
			}
			else if (ch == tty->ltc.t_suspc) {
				killgroup(tty->pgrp, SIGTSTP, 1);
				if (!(tty->sg.sg_flags & T_NOFLSH))
					tty_ioctl(f, TIOCFLUSH, &r);
				tty_ioctl (f, TIOCSTART, 0);
				return 4L;
			}
			else if (ch == tty->tc.t_stopc) {
				tty_ioctl (f, TIOCSTOP, 0);
				return 4L;
			}
			else if (ch == tty->tc.t_startc) {
				tty_ioctl (f, TIOCSTART, 0);
				return 4L;
			}
			else if (ch == tty->ltc.t_flushc) {
				long r = 2;
				tty_ioctl (f, TIOCFLUSH, &r);
				return 4L;
			}
			else if (tty->state & TS_HOLD) {
				return 0;
			}
		}
		goto do_putchar;
	}
#if 1
	tty_checkttou(f, tty);
#else
/* job control checks */
/* AKP: added T_TOSTOP; don't stop BG output if T_TOSTOP is clear */
/*
entropy: only do the job control if SIGTTOU is neither blocked nor ignored,
and only for the controlling tty (IEEE 1003.1-1990 7.1.1.4 79-87).
BUG:  if the process group is orphaned and SIGTTOU *is not* blocked
or ignored, we should return EIO instead of signalling.
*/
	if (tty->pgrp && tty->pgrp != curproc->pgrp &&
	         (tty->sg.sg_flags & T_TOSTOP) &&
                 (curproc->sighandle[SIGTTOU] != SIG_IGN) &&
                 ((curproc->sigmask & (1L << SIGTTOU)) == 0L) &&
                 (f->fc.dev == curproc->control->fc.dev) &&
                 (f->fc.index == curproc->control->fc.index)) {
		TRACE(("job control: tty pgrp is %d proc pgrp is %d",
			tty->pgrp, curproc->pgrp));
		killgroup(curproc->pgrp, SIGTTOU, 1);
	}
#endif

	if (mode & COOKED) {
		tty->state |= TS_COOKED;
		while (tty->state & (TS_HOLD|TS_BLIND)) {
			short bdev;
			extern DEVDRV bios_tdevice;

			if (tty->state & TS_BLIND) {
				TRACE(("tty_putchar: offline"));
				return 0;
			}
#if 1
	/* hack: BIOS devices != console never reset TS_HOLD themselves
	   unless another process happens to call tty_getchar on them while
	   we're here.  someone has a better fix? :-(  -nox
	*/
/* BIOS device definitions */
#define CONSDEV 2
			if (f->dev == &bios_tdevice &&
			    (bdev=f->fc.aux) != CONSDEV) {
				long c;

				if (!bconstat(bdev) ||
				    (c = bconin(bdev) & 0x7fffffffL) == UNDEF)
					;	/* do nothing */
				else if (c == tty->ltc.t_suspc) {
					tty_ioctl(f, TIOCSTART, 0);
					killgroup(tty->pgrp, SIGTSTP, 1);
				} else if (c == tty->tc.t_intrc) {
					tty_ioctl(f, TIOCSTART, 0);
					killgroup(tty->pgrp, SIGINT, 1);
				} else if (c == tty->tc.t_quitc) {
					tty_ioctl(f, TIOCSTART, 0);
					killgroup(tty->pgrp, SIGQUIT, 1);
				} else if (c == tty->tc.t_startc)
					tty_ioctl(f, TIOCSTART, 0);
				else if (c == tty->ltc.t_flushc) {
					long r = 2;
					tty_ioctl(f, TIOCFLUSH, &r);
				}
			}
			else
#endif
			if (HAS_WRITEB(f)) {
/* if the device has writeb assume it wakes us when TS_HOLD resets */
				sleep (IO_Q, (long)&tty->state);
				continue;
			}
			nap(60);	/* sleep for 60 milliseconds */
		}
	}
	else
		tty->state &= ~TS_COOKED;

do_putchar:
	return (*f->dev->write)(f, (char *)&data, 4L);
}

/*
 * special select() function that takes T_XKEY into account
 */
long
tty_select(FILEPTR *f, long proc, int mode)
{
	struct tty *tty;

	tty = (struct tty *)f->devinfo;
	if (mode == O_RDONLY && (tty->sg.sg_flags & T_XKEY) &&
	    (tty->state & TS_ESC))
		return 1;
	return (*f->dev->select)(f, proc, mode);
}

