#
/*
 */

/*
 * general TTY subroutines
 */
#include "../head/param.h"
#include "../head/systm.h"
#include "../head/user.h"
#include "../head/userx.h"
#include "../head/tty.h"
#include "../head/proc.h"
#include "../head/procx.h"
#include "../head/inode.h"
#include "../head/inodex.h"
#include "../head/file.h"
#include "../head/filex.h"
#include "../head/reg.h"
#include "../head/conf.h"

int	nltype;		/* number of line disciplines */
char	partab[];

#define	SSPEED	7	/* standard speed: 300 baud */
#define	DLDELAY	4	/* Extra delay for DL's (double buff) */

/*
 * Input mapping table-- if an entry is non-zero, when the
 * corresponding character is typed preceded by "\" the escape
 * sequence is replaced by the table value.  Mostly used for
 * upper-case only terminals.
 */
char	maptab[]
{
	000,000,000,000,CEOT,00,000,000,
	000,000,000,000,000,000,000,000,
	000,000,000,000,000,000,000,000,
	000,000,000,000,000,000,000,000,
	000,'|',000,'#',000,000,000,'`',
	'{','}',000,000,000,000,000,000,
	000,000,000,000,000,000,000,000,
	000,000,000,000,000,000,000,000,
	'@',000,000,000,000,000,000,000,
	000,000,000,000,000,000,000,000,
	000,000,000,000,000,000,000,000,
	000,000,000,000,000,000,'~',000,
	000,'A','B','C','D','E','F','G',
	'H','I','J','K','L','M','N','O',
	'P','Q','R','S','T','U','V','W',
	'X','Y','Z',000,000,000,000,000,
};

/*
 * The actual structure of a clist block manipulated by
 * getc and putc (mch.s)
 */
struct cblock {
	struct cblock *c_next;
	char info[6];
};

/* The character lists-- space for 6*NCLIST characters */
struct cblock cfree[NCLIST];
/* List head for unused character blocks. */
struct cblock *cfreelist;

/*
 * structure of device registers for KL, DL, and DC
 * interfaces-- more particularly, those for which the
 * SSTART bit is off and can be treated by general routines
 * (that is, not DH).
 */
struct {
	int ttrcsr;
	int ttrbuf;
	int tttcsr;
	int tttbuf;
};

/*
 * Routine to open a tty line. Initialize and wait
 * for carrier to come on. Establish a process
 * group for the distribution of quits and interrupts
 * from the tty.
 */
ttopen(dev, atp)
struct tty *atp;
{
	register struct tty *tp;
	register struct proc *pp;

	tp = atp;
	tp->t_state =| WOPEN;
	if ((tp->t_state&ISOPEN) == 0) {
		tp->t_erase = CERASE;
		tp->t_kill = CKILL;
		tp->t_speeds = SSPEED | (SSPEED<<8);
		tp->t_flags = ODDP|EVENP|ECHO;
		(*ctlsw[tp->t_dtype].d_param)(dev, tp);
	} else if (tp->t_state&XCLUDE && u.u_uid!=0) {
		u.u_error = EBUSY;
		return;
	}
	ttmopen(dev, tp);
	pp = u.u_procp;
	if(pp->p_pgrp == 0) {
		if(tp->t_pgrp)
			pp->p_pgrp = tp->t_pgrp;
		else {
			tp->t_pgrp = pp->p_pid;
			pp->p_pgrp = pp->p_pid;
		}
		u.u_ttyp = tp;
		u.u_ttyd = dev;
	}
	tp->t_state =& ~WOPEN;
	tp->t_state =| ISOPEN;
}

/*
 * Modem open for tty lines. Set up modem to answer phone
 * and transmit main carrier. Wait until carrier is received
 * from other side.
 */
ttmopen(dev, atp)
struct tty *atp;
{
	register struct tty *tp;
	register dtype;

	tp = atp;
	dtype =	tp->t_dtype;
	(*ctlsw[dtype].d_mctl)(dev, TURNON|RQS);
	spl5();
	if((*ctlsw[dtype].d_mctl)(dev, FSTATUS) & CARRIER)
		tp->t_state =| CARR_ON;
	else
		tp->t_state =& ~CARR_ON;
	while((tp->t_state&CARR_ON)==0)
		sleep(&tp->t_rawq, TTIPRI);
	spl0();
}

/*
 * Close a tty line.
 */
ttclose(dev, atp)
struct tty *atp;
{
	register struct tty *tp;

	tp = atp;
	if (tp->t_state&ISOPEN) {
		if (tp->t_flags&HUPCL)
			(*ctlsw[tp->t_dtype].d_mctl)(dev, HUP);
		tp->t_state =& (CARR_ON|SSTART);
		tp->t_pgrp = 0;
		if(tp->t_state&CARR_ON)
			wflushtty(tp);
		else
			flushtty(tp);
	}
}

/*
 * Routine implementing the gtty system call.
 * Translate format and handle using ioctl.
 */
gtty()
{
	u.u_arg[1] = u.u_arg[0];
	u.u_arg[0] = ('t'<<8)|GTTY;
	ioctl();
}

/*
 * The routine implementing the stty system call.
 * Translate format and handle using ioctl.
 */
stty()
{
	u.u_arg[1] = u.u_arg[0];
	u.u_arg[0] = ('t'<<8)|STTY;
	ioctl();
}

/*
 * Routine implementing the ioctl system call. Check
 * legality and switch out to individual device routine.
 */
ioctl()
{
	register struct file *fp;
	register struct inode *ip;
	register flag;

	if ((fp = getf(u.u_ar0[R0])) == NULL)
		return;
	ip = fp->f_inode;
	if ((ip->i_mode&IFMT) != IFCHR) {
		u.u_error = ENOTTY;
		return;
	}
	flag = fp->f_flag&(FREAD|FWRITE);
	(*cdevsw[ip->i_addr[0].d_major].d_sgtty)(ip->i_addr[0], flag);
}

/*
 * ioctl for line discipline devices. Handle setting and
 * fetching of line discipline. Switch out to line discipline
 * routines for actual work.
 */
ttioctl(dev, atp, flag)
struct tty *atp;
{
	register struct tty *tp;
	register int *v;
	register cmd;
	int word0;

	tp = atp;
	if (u.u_arg[0].hibyte != 't') {
		u.u_error = EBIOCTL;
		return;
	}
	v = u.u_arg[1];
	u.u_arg[2] = flag;
	switch (cmd = u.u_arg[0].lobyte&0377) {

	case SET:
	case UNSET:
		u.u_error = EBIOCTL;
		break;

	/*
	 * Get line discipline number
	 */
	case GETD:
		suword(v, tp->t_discp<<8);
		break;

	/*
	 * Change to specified line discipline if not same
	 * as current one. Execute subcommand.
	 */
	case CHNGD:

		word0 = fuword(v);
		if(word0.hibyte >= nltype) {
			u.u_error = EBIOCTL;
			break;
		}
		if (flag==0 && (tp->t_state&ISOPEN)==0) {
			tp->t_discp = word0.hibyte;
			break;
		}
		if (word0.hibyte != tp->t_discp) {
			(*linesw[tp->t_discp].l_sgtty)(dev, tp, UNSET);
			tp->t_discp = word0.hibyte;
			(*linesw[tp->t_discp].l_sgtty)(dev, tp, SET);
			break;
		}

	default:
		(*linesw[tp->t_discp].l_sgtty)(dev, tp, cmd);

	}
}

/*
 * Wait for output to drain, then flush input waiting.
 */
wflushtty(atp)
struct tty *atp;
{
	register struct tty *tp;

	tp = atp;
	spl5();
	while (tp->t_outq.c_cc) {
		tp->t_state =| ASLEEP;
		sleep(&tp->t_outq, TTOPRI);
	}
	flushtty(tp);
	spl0();
}

/*
 * Initialize clist by freeing all character blocks, then count
 * number of character devices, number of line disciplines and
 * number of terminal subdisciplines. (Once-only routine)
 */
cinit()
{
	register int ccp;
	register struct cblock *cp;
	register struct cdevsw *cdp;
	struct linesw *lsp;

	ccp = cfree;
	for (cp=(ccp+07)&~07; cp <= &cfree[NCLIST-1]; cp++) {
		cp->c_next = cfreelist;
		cfreelist = cp;
	}
	ccp = 0;
	for(cdp = cdevsw; cdp->d_open; cdp++)
		ccp++;
	nchrdev = ccp;
	ccp = 0;
	for(lsp = linesw; lsp->l_open; lsp++)
		ccp++;
	nltype = ccp;
}

/*
 * flush all TTY queues
 */
flushtty(atp)
struct tty *atp;
{
	register struct tty *tp;
	register int sps;

	tp = atp;
	while (getc(&tp->t_canq) >= 0);
	while (getc(&tp->t_outq) >= 0);
	wakeup(&tp->t_rawq);
	wakeup(&tp->t_outq);
	sps = PS->integ;
	spl5();
	while (getc(&tp->t_rawq) >= 0);
	tp->t_delct = 0;
	PS->integ = sps;
}

/*
 * transfer raw input list to canonical list,
 * doing erase-kill processing and handling escapes.
 * It waits until a full line has been typed in cooked mode,
 * or until any character has been typed in raw mode.
 */
canon(atp)
struct tty *atp;
{
	register char *bp;
	char *bp1;
	register struct tty *tp;
	register int c;
	int mc;

	tp = atp;
	spl5();
	while ((tp->t_flags&RAW)==0 && tp->t_delct==0
	    || (tp->t_flags&RAW)!=0 && tp->t_rawq.c_cc==0) {
		if ((tp->t_state&CARR_ON)==0) {
			spl0();
			return(0);
		}
		sleep(&tp->t_rawq, TTIPRI);
	}
	spl0();
loop:
	bp = &canonb[2];
	while ((c=getc(&tp->t_rawq)) >= 0) {
		if ((tp->t_flags&RAW)==0) {
			if (c==0377) {
				tp->t_delct--;
				break;
			}
			if (bp[-1]!='\\') {
				if (c==tp->t_erase) {
					if (bp > &canonb[2])
						bp--;
					continue;
				}
				if (c==tp->t_kill)
					goto loop;
				if (c==CEOT)
					continue;
			} else {
				mc = maptab[c];
				if (c==tp->t_erase || c==tp->t_kill)
					mc = c;
				if (mc && (mc==c || (tp->t_flags&LCASE))) {
					if (bp[-2] != '\\')
						c = mc;
					bp--;
				}
			}
		}
		*bp++ = c;
		if (bp>=canonb+CANBSIZ)
			break;
	}
	bp1 = bp;
	bp = &canonb[2];
	c = &tp->t_canq;
	while (bp<bp1)
		putc(*bp++, c);
	return(1);
}

/*
 * Place a character on raw TTY input queue, putting in delimiters
 * and waking up top half as needed.
 * Also echo if required.
 * The arguments are the character and the appropriate
 * tty structure.
 */
ttyinput(ac, atp)
struct tty *atp;
{
	register int t_flags, c;
	register struct tty *tp;

	tk_nin =+ 1;
	tp = atp;
	c = ac;
	t_flags = tp->t_flags;
	if((tp->t_state&ISOPEN)==0 || (c & OVERRUN)) {
		wakeup(&tp->t_rawq);
		return;
	}
	if (c&FRERROR)			/* break */
		if (t_flags&RAW)
			c = 0;		/* null (for getty) */
		else
			c = 0177;	/* DEL (intr) */
	if (c&PERROR)
		if ((t_flags&(EVENP|ODDP))==EVENP
		 || (t_flags&(EVENP|ODDP))==ODDP )
			return;
	if((c =& 0177) == '\r' && t_flags & CRMOD)
		c = '\n';
	if((t_flags&RAW)==0) {
		if(c==CQUIT || c==CINTR) {
			signal(tp->t_pgrp, c==CINTR? SIGINT:SIGQIT);
			flushtty(tp);
			return;
		} else
		if(c==CXSTOP) {
			if(tp->t_state&XMTSTOP) {
				tp->t_state =& ~XMTSTOP;
				ttstart(tp);
			} else
				tp->t_state =| XMTSTOP;
			return;
		}
	}
	if (tp->t_rawq.c_cc>TTYHOG) {
		flushtty(tp);
		return;
	}
	if (t_flags&LCASE && c>='A' && c<='Z')
		c =+ 'a'-'A';
	putc(c, &tp->t_rawq);
	if (t_flags&RAW || (c=='\n' || c==CEOT)) {
		wakeup(&tp->t_rawq);
		if ((t_flags&RAW)==0 && putc(0377, &tp->t_rawq)==0)
			tp->t_delct++;
	}
	if (t_flags&ECHO) {
		ttyoutput(c, tp);
		tp->t_state =& ~XMTSTOP;
		if (c==tp->t_kill && (t_flags&RAW)==0)
			ttyoutput('\n', tp);
		ttstart(tp);
	}
}

/*
 * put character on TTY output queue, adding delays,
 * expanding tabs, and handling the CR/NL bit.
 * It is called both from the top half for output, and from
 * interrupt level for echoing.
 * The arguments are the character and the tty structure.
 */
ttyoutput(ac, tp)
struct tty *tp;
{
	register int c;
	register struct tty *rtp;
	register char *colp;
	int ctype;

	tk_nout =+ 1;
	rtp = tp;
	c = ac & 0177;
	/*
	 * Ignore EOT in normal mode to avoid hanging up
	 * certain terminals.
	 * In raw mode dump the char unchanged.
	 */

	if(c == CEOT && (rtp->t_flags&RAW) == 0)
		return;
	/*
	 * Turn tabs to spaces as required
	 */
	if (c=='\t' && rtp->t_flags&XTABS) {
		do
			ttyoutput(' ', rtp);
		while (rtp->t_col&07);
		return;
	}
	/*
	 * for upper-case-only terminals,
	 * generate escapes.
	 */
	if (rtp->t_flags&LCASE) {
		colp = "({)}!|^~'`";
		while(*colp++)
			if(c == *colp++) {
				ttyoutput('\\', rtp);
				c = colp[-2];
				break;
			}
		if ('a'<=c && c<='z')
			c =+ 'A' - 'a';
	}
	/*
	 * turn <nl> to <cr><lf> if desired.
	 */
	if (c=='\n' && rtp->t_flags&CRMOD)
		ttyoutput('\r', rtp);
	putc(c, &rtp->t_outq);
	/*
	 * Calculate delays.
	 * The numbers here represent clock ticks
	 * and are not necessarily optimal for all terminals.
	 * The delays are indicated by characters above 0200.
	 * In raw mode there are no delays and the
	 * transmission path is 8 bits wide.
	 */
	colp = &rtp->t_col;
	ctype = partab[c];
	c = 0;
	switch (ctype&077) {

	/* ordinary */
	case 0:
		(*colp)++;

	/* non-printing */
	case 1:
		break;

	/* backspace */
	case 2:
		if (*colp)
			(*colp)--;
		break;

	/* newline */
	case 3:
		ctype = (rtp->t_flags >> 8) & 03;
		if(ctype == 1) { /* tty 37 */
			if (*colp)
				c = max((*colp>>4) + 3, 6);
		} else
		if(ctype == 2) {
			c = 2;
		} else
		if(ctype == 3) {	/* TTY 40 printer */
			if(*colp <= 34) {
				if(*colp)
					c = (34 - *colp)>>1;
				else
					c = 2;
			}
		}
		*colp = 0;
		break;

	/* tab */
	case 4:
		ctype = (rtp->t_flags >> 10) & 03;
		if(ctype == 1) { /* tty 37 */
			c = 1 - (*colp | ~07);
			if(c < 5)
				c = 0;
		}
		*colp =| 07;
		(*colp)++;
		break;

	/* vertical motion */
	case 5:
		if(rtp->t_flags & VTDELAY) /* tty 37 */
			c = 0177;
		break;

	/* carriage return */
	case 6:
		ctype = (rtp->t_flags >> 12) & 03;
		if(ctype == 1) { /* tn 300 */
			c = 4;
		} else
		if(ctype == 2) {
			c = 4;
		} else
		if(ctype == 3) {
			c = 10;
		}
		*colp = 0;
	}
	if(c)
		putc(c|0200, &rtp->t_outq);
}

/*
 * Restart typewriter output following a delay
 * timeout.
 * The name of the routine is passed to the timeout
 * subroutine and it is called during a clock interrupt.
 */
ttrstrt(atp)
{
	register struct tty *tp;

	tp = atp;
	tp->t_state =& ~TIMEOUT;
	ttstart(tp);
}

/*
 * Start output on the typewriter. It is used from the top half
 * after some characters have been put on the output queue,
 * from the interrupt routine to transmit the next
 * character, and after a timeout has finished.
 * If the SSTART bit is off for the tty the work is done here,
 * using the protocol of the single-line interfaces (KL, DL, DC);
 * otherwise the address word of the tty structure is
 * taken to be the name of the device-dependent startup routine.
 */
ttstart(atp)
struct tty *atp;
{
	register int *addr, c;
	int sps;
	register struct tty *tp;
	struct { int (*func)(); };

	tp = atp;
	addr = tp->t_addr;
	if (tp->t_state&SSTART) {
		(*addr.func)(tp);
		return;
	}
	sps = PS->integ;
	spl5();
	if ((addr->tttcsr&DONE)==0 || tp->t_state&TIMEOUT)
		goto out;
	if((c = (*linesw[tp->t_discp].l_xmtd)(tp))< 0)
		addr->tttbuf = c;
	else if (c&CTOUT) {
		timeout(ttrstrt, tp, (c&0177) + DLDELAY);
		tp->t_state =| TIMEOUT;
	}
   out:
	PS->integ = sps;
}

/*
 * Get next character for transmission to a tty.
 * Accessed via l_xmtd entry in linesw table in conf.c
 */
ttxmtd(atp)
struct tty *atp;
{
	register struct tty *tp;
	register c;

	tp = atp;
	if(tp->t_state&XMTSTOP || (c = getc(&tp->t_outq)) < 0)
		return(0);
	if (c<=0177)
		c =| CPRES|(partab[c]&0200);
	else
		c =| CTOUT;
	/*
	 * If the writer was sleeping on output overflow,
	 * wake him when low tide is reached.
	 */
	if (tp->t_outq.c_cc<=TTLOWAT && tp->t_state&ASLEEP) {
		tp->t_state =& ~ASLEEP;
		wakeup(&tp->t_outq);
	}
	return(c);
}

/*
 * Called from device's read routine after it has
 * calculated the tty-structure given as argument.
 * The pc is backed up for the duration of this call.
 * In case of a caught interrupt, an RTI will re-execute.
 */
ttread(atp)
struct tty *atp;
{
	register struct tty *tp;

	tp = atp;
	if ((tp->t_state&CARR_ON)==0)
		return;
	if (tp->t_canq.c_cc || canon(tp))
		while (tp->t_canq.c_cc && passc(getc(&tp->t_canq))>=0);
}

/*
 * Called from the device's write routine after it has
 * calculated the tty-structure given as argument.
 */
ttwrite(atp)
struct tty *atp;
{
	register struct tty *tp;
	register int c;

	tp = atp;
	if ((tp->t_state&CARR_ON)==0)
		return;
	while ((c=cpass())>=0) {
		spl5();
		while (tp->t_outq.c_cc > TTHIWAT) {
			ttstart(tp);
			tp->t_state =| ASLEEP;
			sleep(&tp->t_outq, TTOPRI);
		}
		spl0();
		ttyoutput(c, tp);
	}
	ttstart(tp);
}

/*
 * Common code for tty line disciplines
 */
ttstty(dev, atp, cmd)
struct tty *atp;
{
	register struct tty *tp;
	register int *v;
	int word1;

	tp = atp;
	v = u.u_arg[1];
	switch(cmd) {

	case UNSET:
		ttclose(dev, tp);
		break;

	case SET:
		ttopen(dev, tp);

	case CHNGD:
		break;

	case STTY:
		wflushtty(tp);

	case STTYNF:
		tp->t_speeds = fuword(v++);
		tp->t_flags = (fuword(++v))&077777;
		if (tp->t_speeds.lobyte == 0) {
			tp->t_flags =| HUPCL;
			(*ctlsw[tp->t_dtype].d_mctl)(dev, HUP);
			break;
		}
		(*ctlsw[tp->t_dtype].d_param)(dev, tp);
		break;

	case GTTY:
		suword(v++, tp->t_speeds);
		suword(++v, tp->t_flags);
		break;

	/*
	 * Prevent opens on channel.
	 */
	case XCLD:
		tp->t_state =| XCLUDE;
		break;

	case FEK:
		word1.lobyte = tp->t_erase;
		word1.hibyte = tp->t_kill;
		suword(++v, word1);
		break;

	case SEK:
		word1 = fuword(++v);
		tp->t_erase = word1.lobyte;
		tp->t_kill = word1.hibyte;
		break;

	default:
		u.u_error = EBIOCTL;
	}
}

/*
 * Modem transition handling. If carrier is
 * off and open is not in progress, hang up the
 * local dataset and signal a hangup.
 */
ttmt(atp, status)
struct tty *atp;
{
	register struct tty *tp;
	register action;

	tp = atp;
	action = 0;
	wakeup(&tp->t_rawq);
	if ((status&CARRIER)==0) {
		if ((tp->t_state&WOPEN)==0) {
			signal(tp->t_pgrp, SIGHUP);
			action = DISABLE;
			flushtty(tp);
		}
		tp->t_state =& ~CARR_ON;
	} else
		tp->t_state =| CARR_ON;
	return(action);
}
