/*
 *		Vdu driver -- local terminal on PALS
 *
 *	- uses Autodriver Channel for output
 *
 */

#include "../h/param.h"
#include "../h/conf.h"
#include "../h/tty.h"
#include "../h/dir.h"
#include "../h/user.h"
#include "../h/proc.h"
#include "../h/systm.h"
#include "../h/ccb.h"

extern	int	vduacd;				/* ACD control */
extern int	nvdu;		/* number of ports */
extern char	vduaddr[];	/* port addresses */
extern char vdurate[][4];	/* Baud rate table */
extern struct tty vdu[];	/* common tty structure */

extern char	consaddr;	/* 'console' address */
extern char	conscmd2;	/* PALS command 2 for 'console' */

extern struct ccb *vduccb;	/* output channel control blocks */
extern short isp[];		/* interrupt service ptr table */

int vdustart(), vdurint(), vduxint(), ttrstrt();
int nulldev();

/*
 * PALS commands & status bits
 */

	/* command 1 */
#define	DIS	0200
#define	EN	0100
#define	DTR	0040
#define	WRT	0002
#define	CMD1	0001

	/* command 2 */
#define CLKSHIFT	6	/* offset of CLOCK bits in command 2 */
#define DATA8	0060		/* 8 data bits */
#define DATA7	0040		/* 7 data bits */
#define STOP	0010		/* 2 stop bits */
#define PARE	0006		/* even parity */

	/* Status */
#define PF		0100
#define FRERR		0040
#define	BSY		0010
#define EXA		0004
#define	CARR_OFF	0002

/*
 * open routine:
 *	called each time a process opens a terminal as a character file
 *
 *	- if the terminal was previously inactive, set up the initial status
 *	  and arm interrupts
 */
vduopen(dev)
{
	register struct tty *tp;

	if (minor(dev) >= nvdu) {
		u.u_error = ENXIO;
		return;
	}
	tp = &vdu[minor(dev)];
	if ((tp->t_state& ISOPEN) == 0) {
		tp->t_dev = dev;
		tp->t_state = ISOPEN;
		if (tp->t_flags == 0)
			tp->t_flags =  EVENP | ODDP | XTABS | CRMOD | ECHO;
		ttychars(tp);
		tp->t_oproc = vdustart;
		tp->t_iproc = nulldev;

		if (vduacd & CC_OUTPUT)
			isp[vduaddr[minor(dev)] + 1] = (short)&vduccb[minor(dev)] + 1;
		vduenab(tp);
	}
	spl4();
	while (!(tp->t_state&CARR_ON))
		sleep(&tp->t_rawq, TTIPRI);
	spl0();
	if ((tp->t_state&XCLUDE) && u.u_uid != 0) {
		u.u_error = EBUSY;
		return;
	}
	(*linesw[tp->t_line].l_open)(dev, tp);
}

/*
 * close routine:
 *	- called only when last process using terminal releases it
 */
vduclose(dev)
{
	register struct tty *tp;
	tp = &vdu[minor(dev)];
	(*linesw[tp->t_line].l_close)(tp);
	ttyclose(tp);
	vdudisab(dev);
}

/*
 * read, write, ioctl routines:
 *	- call standard tty routines
 */
vduread(dev)
{
	register struct tty *tp;

	tp = &vdu[minor(dev)];
	(*linesw[tp->t_line].l_read)(tp);
}

vduwrite(dev)
{
	register struct tty *tp;

	tp = &vdu[minor(dev)];
	(*linesw[tp->t_line].l_write)(tp);
}

vduioctl(dev, cmd, addr, flag)
caddr_t addr;
{
	register struct tty *tp;

	tp = &vdu[minor(dev)];
	if (ttioccom(cmd, tp, addr, dev) == 0) {
		u.u_error = ENOTTY;
		return;
	}

	/* Stty - reissue CMD 2 to change baud rate */
	if (cmd == TIOCSETP || cmd == TIOCSETN)
		vduenab(tp);
}

/*
 * vdustart routine - called when there might be something to send
 *   to the terminal
 */

vdustart(atp)
struct tty *atp;
{
	register struct tty *tp;
	register c, waddr;
	register struct ccb *ccb;

	tp = atp;
	waddr = vduaddr[minor(tp->t_dev)] + 1;
	trace(1<<16, "vdustart", waddr);
	if ((tp->t_state&(TIMEOUT|TTSTOP|BUSY|CARR_ON)) != CARR_ON
	    || (c = getc(&tp->t_outq)) < 0)
		return;
	if (c > 0177 && (tp->t_flags&RAW) == 0) {
		tp->t_state |= TIMEOUT;
		timeout(ttrstrt, tp, c&0177);
	} else {
		tp->t_state |= BUSY;
		wd(waddr, c);
		trace(1<<16, "wrt", ss(waddr));
		if (vduacd & CC_OUTPUT)  {
			/* Use ACD for output */
			ccb = &vduccb[minor(tp->t_dev)];
			if (vduputc(tp, ccb))
				ccb->cc_ccw = (CCEX | CCWR | CCFAST);
		}
	}
}

vdustop(tp)
register struct tty *tp;
{
	register struct ccb *ccb;
	register n;

	trace(1<<16, "vdstop", tp->t_dev);
	if (vduacd == CC_OFF)
		/* ACD not in use */
		return;

	ccb = &vduccb[minor(tp->t_dev)];
	if ((n = ccb->cc_cnt1 + ccb->cc_cnt0 - 1) > 0)
		ndflush(&tp->t_outq, n);
	ccb->cc_cnt1 = 0;
	ccb->cc_ccw = 0;
}

/*
 * Set up ccb to continue output
 */
vduputc(tp, ccb)
register struct tty *tp;
register struct ccb *ccb;
{
	register n;

	trace(1<<16, "vduputc", ccb->cc_buf1);
	if ((n = ccb->cc_cnt1 = ndqb(&tp->t_outq, 0200)) <= 0)
		return(0);
	n--;
	ccb->cc_cnt0 = -n;
	ccb->cc_buf0 = tp->t_outq.c_cf + n;
	trace(1<<16, "ccount", n);
	return(1);
}

vdurint(dev, stat)
register stat;
{
	register int raddr;
	register struct tty *tp;
	register char c;
	register int cc;

	tp = &vdu[dev];
	raddr = vduaddr[dev];

	if (!(tp->t_state & ISOPEN))
		return;

	trace(1<<16, "rint", raddr);
	trace(1<<16, "stat", stat);

	c = rd(raddr);
	trace(1<<16, "char", c);

	if (stat & CARR_OFF) {
		if (tp->t_state & CARR_ON) {
			/*****
			if (tp->t_chan)
				scontrol(tp->t_chan, M_SIG, SIGHUP);
			else
				*****/
				signal(tp->t_pgrp, SIGHUP);
			flushtty(tp);
		}
		tp->t_state &= ~CARR_ON;
		return;
	}
	if (!(tp->t_state & CARR_ON)) {
		tp->t_state |= CARR_ON;
		wakeup(&tp->t_rawq);
		return;
	}
	if (stat & EXA) {
		/*
		 * Status of BSY|EXA means DSR dropped (e.g. busy signal from
		 * printer)
		 */
		if (stat == (BSY|EXA))
			return;
		/*
		 * Status of FRERR means line-break (treat like NULL in raw
		 * mode, DEL otherwise) or garbled character (ignore)
		 */
		if (stat&FRERR) {
			if (c != 0)
				return;
			if ((tp->t_flags&RAW) == 0)
				c = 0177;
		}
	}
	(*linesw[tp->t_line].l_rint)(c, tp);
}

vduxint(dev, stat)
{
	register int waddr;
	register struct tty *tp;
	register struct ccb *ccb;
	register c;

	tp = &vdu[dev];
	waddr = vduaddr[dev] + 1;
	if (!(tp->t_state & ISOPEN))
		return;
	trace(1<<16, "wint", waddr);
	trace(1<<16, "stat", stat);
	if ((stat & ~0xff) == 0) {
		/* Immediate interrupt */
		tp->t_state &= ~BUSY;
		(*linesw[tp->t_line].l_start)(tp);
	} else {
		/* Auto-driver 'channel' routine */
		ccb = &vduccb[minor(tp->t_dev)];
		ndflush(&tp->t_outq, ccb->cc_cnt1);
		if ((tp->t_state&TTSTOP) || vduputc(tp, ccb) == 0)
			ccb->cc_ccw = 0;
	}
	if ((tp->t_state&ASLEEP) && tp->t_outq.c_cc <= TTLOWAT) {
		tp->t_state &= ~ASLEEP;
		/*****
		if (tp->t_chan)
			mcstart(tp->t_chan, (caddr_t)&tp->t_outq);
		else
			*****/
			wakeup((caddr_t)&tp->t_outq);
	}
}

/*
 * Arm interrupts from PALS and set baud rate
 */
vduenab(atp)
struct tty *atp;
{
	register struct tty *tp;
	register radd;
	register char *rate;
	register int clk;
	register cmd2, stat;

	tp = atp;
	if (tp->t_ospeed == 0)
		cmd2 = conscmd2;
	else {
		cmd2 = conscmd2 & (03<<CLKSHIFT);
		rate = &vdurate[minor(tp->t_dev)][0];
		for (clk = 0; clk < 4; clk++)
			if (*rate++ == tp->t_ospeed) {
				cmd2 = clk << CLKSHIFT;
				break;
			}
		if ((tp->t_flags&RAW) == 0)
			cmd2 |= DATA7 | STOP | PARE;
		else
			cmd2 |= DATA8 | STOP;
	}

	radd = vduaddr[minor(tp->t_dev)];

	oc(radd, cmd2);
	oc(radd, EN | DTR | CMD1);
	oc(radd + 1, EN | DTR | WRT | CMD1);
	stat = ss(radd);
	if ((stat&CARR_OFF) == 0)
		tp->t_state |= CARR_ON;

	trace(1<<16, "vduenab", stat);
	rd(radd);
}

/*
 * Disarm interrupts from PALS
 */
vdudisab(dev)
{
	register radd, wadd;

	radd = vduaddr[minor(dev)];
	wadd = radd + 1;

	oc(radd, DIS | CMD1);
	oc(wadd, DIS | WRT | CMD1);

	trace(1<<16, "vdudisab", ss(radd));
}

char	*msgbufp = msgbuf;	/* Next saved printf character */

/*
 * Print a character on console.
 * Attempts to save and restore device
 * status.
 *
 * The last MSGBUFS characters
 * are saved in msgbuf for inspection later.
 */
putchar(c)
register c;
{
	register s, timo;
	register waddr;

	waddr = consaddr | 01;
	if (c != '\0' && c != '\r' && c != 0177) {
		*msgbufp++ = c;
		if(msgbufp >= &msgbuf[MSGBUFS])
			msgbufp = msgbuf;
	}
	/*
	 * Set baud rate & disable interrupts
	 */
	oc(waddr, conscmd2);
	oc(waddr, DIS | WRT | DTR | CMD1);
	/*
	 *  If last char was a break or null, don't print
	*/
/*** ???
	if ((KLADDR->rbuf&0177) == 0)
		return;
***/
	timo = 30000;
	/*
	 * Try waiting for the console tty to come ready,
	 * otherwise give up after a reasonable time.
	 */
	while(ss(waddr)&BSY)
		if(--timo == 0)
			break;
	if(c == 0)
		return;
	wd(consaddr, c);
	if(c == '\n') {
		putchar('\r');
/***
		putchar(0177);
		putchar(0177);
***/
	}
	putchar(0);
	/*
	 * Try to restore previous status
	 */
	oc(waddr, EN | WRT | DTR | CMD1);
}
