#
/*
 *	Copyright 1973 Bell Telephone Laboratories Inc
 */

#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"


#define DISCON	300
#define DLAY	10
#define DLAY1	30
#define M40STP	90
#define M40GO 	30
#define QESC	037
#define QBRK	0
#define HSPEED	9

/* half-duplex state bits: t_lword */
#define LOCAL	01
#define PEVENT	02
#define CURSET	04
#define	QDELIM	0200
#define WRTON   0400
#define CRLCHA  01000
#define TIMER1  02000
#define	TIMER2	04000
#define	EXTND	010000
#define	ESCHA	020000
#define SNDRFLG	040000
#define	SOLICIT	0100000

/* Indicates state of comm. channel: t_linest*/
#define QUIESNT 000
#define REC     001
#define INTRPT1 002
#define SEND    003
#define ERROR   004
#define RECHLD  005
#define SENDREQ 006
#define SENDSTP 007
#define INACT   010
#define INTRPT2 011
#define SENDHLD	012
#define RETURN  013
/*
 * Matrix defines new state given current state and state of CD,CTS, and SR.
 */
char    action[11][6]{

/*	0,0,0	0,0,1	0,1,0	0,1,1	1,0,0	1,0,1		(CD,CTS,SR) */


	INTRPT2,RETURN ,ERROR  ,ERROR  ,REC    ,INTRPT1,	/*QUIESNT*/
	INTRPT1,QUIESNT,ERROR  ,ERROR  ,RETURN ,INTRPT1,	/*REC*/
	RETURN ,QUIESNT,ERROR  ,ERROR  ,REC    ,RETURN ,	/*INTPRT1*/
	ERROR  ,ERROR  ,SENDHLD,RETURN ,ERROR  ,ERROR  ,	/*SEND*/
	ERROR  ,ERROR  ,ERROR  ,ERROR  ,ERROR  ,ERROR  ,	/*ERROR*/
	INTRPT1,QUIESNT,ERROR  ,ERROR  ,RETURN ,INTRPT1,	/*RECHLD*/
	INTRPT2,RETURN ,INTRPT2,SEND   ,REC    ,INTRPT1,	/*SENDREQ*/
	INTRPT2,QUIESNT,INTRPT2,RETURN ,REC    ,INTRPT1,	/*SENDSTP*/
	RETURN ,QUIESNT,ERROR  ,ERROR  ,REC    ,INTRPT1,	/*INACT*/
	RETURN ,QUIESNT,RETURN ,RETURN ,REC    ,INTRPT1,	/*INTRPT2*/
	ERROR  ,ERROR  ,RETURN ,RETURN ,ERROR  ,ERROR  ,	/*SENDHLD*/
	};


/*
 * Routine to open a half-duplex line. Initialize and wait
 * for carrier to come on. Establish a process
 * group for the distribution of quits and interrupts
 * from the terminal.
 */
hdopen(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_linest = INACT;
		tp->t_lword = QDELIM;
		tp->t_erase = CERASE;
		tp->t_kill = CKILL;
		tp->t_speeds = HSPEED | (HSPEED<<8);
		tp->t_flags = HDUP|EVENP;
		(*ctlsw[tp->t_dtype].d_param)(dev, tp);
	} else if (tp->t_state&XCLUDE && u.u_uid!=0) {
		u.u_error = EBUSY;
		return;
	}
	hdmopen(dev, tp);
	pp = u.u_procp;
	if(pp->p_pgrp == 0) {
		if(tp->t_pgrp)
			pp->p_pgrp = tp->t_pgrp;
		else {
			pp->p_pgrp = pp->p_pid;
			tp->t_pgrp = pp->p_pid;
		}
		u.u_ttyp = tp;
		u.u_ttyd = dev;
	}
	tp->t_state =& ~WOPEN;
	tp->t_state =| ISOPEN;
}

/*
 * Modem open for half-duplex lines. Set up modem to answer phone.
 * Test state of CD,CTS, and SR and set protocol state accordingly.
 * Sleep until CD or SR goes high.
 */
hdmopen(dev, atp)
struct tty *atp;
{
	register struct tty *tp;
	register dtype;

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

/*
 * Close a tty line.
 */
hdclose(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;
		wflushtty(tp);
	}
}

/*
 * Handles interrupts caused by transitions of CD,CTS, and SR. The
 * current protocol state and the new state of CD,CTS and SR determine 
 * the new protocol state and thus the new state of RQS and ST.
 */
hdmt(atp,status)
{
	register struct tty *tp;
	int act;
	int intbits;
	int modstat;
	extern timer();
	extern sendstp();
	extern sendhld();
 
	tp = atp;
	intbits = status;
	modstat = 0;
 
/*
 * Determine new protocol state.
 */
	if(intbits > 5)
		act = ERROR;
	else
		act = action[tp->t_linest][intbits];

/*
 * Set new protocol state.
 */

	switch(act){

		case QUIESNT :
			modstat = TURNON;
			if(tp->t_linest == INACT) { 
				tp->t_state =| CARR_ON;
				wakeup(&tp->t_rawq);
			}
  			if(tp->t_linest == INTRPT1)
				if((tp->t_lword&QDELIM) == 0 && (tp->t_flags&RAW) == 0)
					ttyinput('\n',tp);
			if(tp->t_linest == INTRPT2){
				tp->t_lword =| WRTON;
				tp->t_state =& ~XMTSTOP;
				ttstart(tp);
				modstat = 0;
			}
			tp->t_linest = QUIESNT;
			if(tp->t_lword&SOLICIT){
				solicit(tp);
				modstat = 0;
			}
			if(tp->t_lword&SNDRFLG){
				hdmset(tp,SENDREQ);
				modstat = 0;
			}
			break;

		case REC :
			tp->t_lword =| SOLICIT;
			modstat = TURNON|ST;
			tp->t_lword =& ~CURSET;
			if(tp->t_linest == INACT) {
				tp->t_state =| CARR_ON;
				wakeup(&tp->t_rawq);
			}
			tp->t_linest = REC;
			break;

  		case INTRPT1 :
			tp->t_linest = INTRPT1 ;
			modstat = TURNON;
			if(tp->t_lword&LOCAL)
				break;
			if(tp->t_lword&TIMER2){
				tp->t_lword =| EXTND;
			}
			else{
				tp->t_lword =| TIMER2;
				timeout(timer,tp,DISCON);
			}
			break;

		case SEND :
			tp->t_linest = SEND;
			ttstart(tp);
			break;

		case SENDHLD :
			tp->t_state =| XMTSTOP;
			timeout(sendhld,tp,DLAY1);
			modstat = 0;
			tp->t_linest = SENDHLD;
			tp->t_lword =& ~(SNDRFLG|SOLICIT|WRTON);
			break;

		case INTRPT2 :
			tp->t_linest = INTRPT2 ;
			modstat = TURNON;
			if(tp->t_lword&LOCAL)
				break;
			if(tp->t_lword&TIMER2){
				tp->t_lword =| EXTND;
			}
			else{
				tp->t_lword =| TIMER2;
				timeout(timer,tp,DISCON);
			}
			break;

		case ERROR :
			tp->t_linest = ERROR;
			/*printf("er %o %o %o\n",tp->t_linest,intbits,modstat)*/
	 		hdmset(tp,INACT);
	}
	return(modstat);
}
/*
 * Set state of modem interface.
 */
hdmset(atp,state)
{
	register struct tty *tp ;
	register int st ;
	int sps,modstat;

	sps = PS->integ;
	spl5();

	tp = atp;
	st = state;

	switch(st){
		case REC :
			tp->t_linest = REC;
			modstat = TURNON|ST;
			break;

		case RECHLD :
			modstat = TURNON;
			tp->t_linest = RECHLD;
			break;

		case SENDREQ :
			tp->t_lword =& ~SNDRFLG;
			modstat = TURNON|RQS;
			tp->t_linest = SENDREQ;
			break;

		case SENDSTP :
			tp->t_linest = SENDSTP;
			modstat = TURNON;
			break;

		case INACT :
			signal(tp->t_pgrp,SIGHUP);
			flushtty(tp);
			modstat = HUP;
			tp->t_linest = INACT;
			tp->t_state =& ~CARR_ON;
			break;

	}
	(*ctlsw[tp->t_dtype].d_mctl)(tp->t_dev,modstat);

	PS->integ = sps;
}

/*
 * Position cursur to a new line; get characters from user and call hdoutput.
 */
hdwrite(atp)
struct tty *atp;
{
	register struct tty *tp;
	register int c;

	tp = atp;

	if(tp->t_lword&SOLICIT)
		solicit(tp);
	if(tp->t_linest != SENDHLD)
		tp->t_lword =| WRTON;

	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();
		hdoutput(c, tp);
	}
	ttstart(tp);
	tp->t_lword =& ~CURSET;
}
/*
 * Call ttyoutput; provide delays for TTY Model 40.
 */
hdoutput(ac, tp)
struct tty *tp;
{
	register int c;
	register struct tty *rtp;
	register char *colp;
	register int dlay;

	rtp = tp;
	colp = &rtp->t_col;
	c = ac&0177;
	dlay = 0;

	ttyoutput(c,rtp);

	if(rtp->t_lword&ESCHA){

		switch(c) {

			case '2':
				dlay = 10;
				break;
	 
			case 'J':
				dlay = 14;
				break;
	
			case 'R':
				dlay = 7;
				*colp = 0;
				break;
	
			case '\010':
				(*colp)--;
				break;
	
			case 'C':
				(*colp)++;
				break;
	
			case '@':
				*colp =| 07;
				(*colp)++;
				break;
	
			case 'L': case 'M':
				dlay = 20;
				break;
	
			case 'G': case 'H':
				*colp = 0;
				break;
		}
		rtp->t_lword =& ~ESCHA;
	}
	if(c == '\033')
		rtp->t_lword =| ESCHA;
	if(dlay)
		putc(dlay|0200,&rtp->t_outq);
}
/*
 * Position cursur to a new line if no characters are availble. Move
 * characters from rawq to canq to user to stasfy read. Account for QESC
 * as result of RECHLD state.
 */
hdread(atp)
struct tty *atp;
{
	struct tty *tp;
	register int c,c1;
	int n;

	tp = atp;

	spl5();
	if(tp->t_delct == 0 && tp->t_rawq.c_cc == 0 && tp->t_canq.c_cc == 0)
 		if((tp->t_lword&CURSET) == 0){
			if(tp->t_linest == QUIESNT)
				solicit(tp);
			else
				tp->t_lword =| SOLICIT;
			}
	spl0();

	while(tp->t_canq.c_cc || hdcanon(tp)){
		c1 = -1;
		n = 0;
		if((c = getc(&tp->t_canq)) == QESC){
			if((c1 = getc(&tp->t_canq)) == QESC)
				n = passc(c1);
		}else{
			n = passc(c);
		}


		if(n<0 || (tp->t_canq.c_cc == 0  && c1 != QBRK ))
			return;
	}
}
/*
 * Put characters on rawq. Wakeup on additional message ending characters.
 * Set queue delimiter (QDELIM) flag. Set RECHLD state if rawq gets full.
 */
hdrcvd(ac,atp)
struct tty *atp;
{
	register int t_flags,c;
	register struct tty *tp;

	tp = atp;
	c = ac;
	t_flags = tp->t_flags;

	if((c&0177)=='\r')
		c = (c&0177600)|'\n';

	ttyinput(c,tp);

	if((c =& 0177) == QESC)
		ttyinput(QESC,tp);
	if(tp->t_state&XMTSTOP){
		tp->t_state =& ~XMTSTOP;
		tp->t_lword =| WRTON;
		ttstart;
	}
	if(c==035 || c==024){
		wakeup(&tp->t_rawq);
		if((t_flags&RAW)==0 && putc(0377,&tp->t_rawq)==0)
			tp->t_delct++;
	}

	if((t_flags&RAW) || (c=='\n' || c==CEOT || c==035 || c==024))
		tp->t_lword =| QDELIM;
	else
		tp->t_lword =& ~QDELIM;

	if(tp->t_rawq.c_cc >= M40STP && tp->t_linest == REC){
		hdmset(tp,RECHLD);
		putc(QESC,&tp->t_rawq);
		putc(QBRK,&tp->t_rawq);
		if(putc(0377,&tp->t_rawq) == 0)
			tp->t_delct++;
		wakeup(&tp->t_rawq);
	}
}
/*
 * Sets the SENDREQ state when there are characters to pass to the calling
 * routine. Calls timeout with sendstp when the output queue count is zero.
 * Returns a character when in the SEND state and characters are available.
 */
hdxmtd(atp)
struct tty *atp;
{

	register struct tty *tp;
	register c;
	extern sendstp();

	tp = atp;
	c = 0;

	if(tp->t_state&XMTSTOP)
		return(0);
	if(tp->t_outq.c_cc)
		if(tp->t_linest == SEND){
			c = getc(&tp->t_outq);
			if(c<=0177)
				c =| CPRES|(partab[c]&0200);
			else
				c =| CTOUT;
		}
		else
			if(tp->t_linest == QUIESNT)
				hdmset(tp,SENDREQ);
			else
				tp->t_lword =| SNDRFLG;
	else
		if(tp->t_linest == SEND)
			tp->t_lword =& ~WRTON;
			if((tp->t_lword&TIMER1) == 0){
				tp->t_lword =| TIMER1;
				timeout(sendstp,tp,DLAY);
			}
	if(tp->t_outq.c_cc <= TTLOWAT && tp->t_state&ASLEEP){
		tp->t_state =& ~ASLEEP;
		wakeup(&tp->t_outq);
	}
	return(c);
}
/*
 * Common code for tty line disciplines
 */
hdstty(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:
		hdclose(dev, tp);
		break;

	case SET:
		hdopen(dev, tp);
		break;

	case CHNGD:

	case STTY:

	case STTYNF:

	case GTTY:

	/*
	 * Prevent opens on channel.
	 */
	case XCLD:

	case FEK:

	case SEK:

		ttstty(dev,atp,cmd);
		break;

	default:
		u.u_error = EBIOCTL;
	}
}
/*
 * Call canon. Allow input when queue reaches low point.
 */
hdcanon(atp)
struct	tty	*atp;
{
	register struct tty *tp;
	int n;

	tp = atp;

	n = canon(tp);

	spl5();
	if(tp->t_linest == RECHLD && tp->t_rawq.c_cc < M40GO)
		hdmset(tp,REC);
	spl0();

	if(tp->t_canq.c_cc)
		return(n);
	else
		return(0);
}
/*
 * Put bell, backspace, and newline on outq.
 */
solicit(atp)
struct tty *atp;
{
	register struct tty *tp;

	tp = atp;
	tp->t_lword =| CURSET;
	tp->t_lword =& ~SOLICIT;
	tp->t_lword =| WRTON;
	ttyoutput('\007',tp);
	ttyoutput('\010',tp);
	ttyoutput('\n',tp);
	ttstart(tp);
}
/*
 *Set SENDSTP after timeout.
 */
sendstp(atp)
{
	register struct tty *tp;

	tp=atp;
	if((tp->t_state&(TIMEOUT|BUSY)) == 0 && (tp->t_lword&WRTON) == 0
		&& (tp->t_linest == SEND))
		if(tp->t_lword&SOLICIT)
			solicit(tp);
		else
			hdmset(tp,SENDSTP);
	tp->t_lword =& ~TIMER1;
}
/*
 * Unconditionally set SENDSTP state.
 */
sendhld(atp)
{
	register struct tty *tp;

	tp = atp;

	if(tp->t_linest == SENDHLD)
		hdmset(tp,SENDSTP);
}
/*
 * Set INACT if no carrier for timeout period.
 */
timer(atp)
{
	register struct tty *tp;

	tp=atp;
	if((tp->t_linest != INTRPT1) && (tp->t_linest != INTRPT2)){
		tp->t_lword =& ~(EXTND|TIMER2);
		return;
	}
	if(tp->t_lword&EXTND){
		timeout(timer,tp,DISCON);
		tp->t_lword =& ~EXTND;
	}
	else{
		hdmset(tp,INACT);
		tp->t_lword =& ~TIMER2;
	}
}
