/*
 *	CX driver -- raw data capture with handshake on PALS
 *		Supports 16 mux'd lines per physical line.
 */

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

extern	int	cxacd;				/* ACD control */
extern	int	ncx;				/* number of ports */
extern	char	cxaddr[];			/* port addresses */
extern	char	cxrate[][4];			/* Baud rate table */
extern	struct	tty	cx[];			/* common tty structure */
extern	int	trmask;

struct	tty	Cx[16];				/* mux'd line controllers */
int		cxusr;				/* # lines active */

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

int cxstart(), cxrint(), cxxint();

/*
 * 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 */
#define		PARO		0004		/* odd  parity */

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

	/* Protocol characters */
#define		CX_CMD		0x80		/* bit indicating a command */
#define		CX_FN		(0x70|CX_CMD)	/* function bits */
#define		CX_DEV		0x0f		/* communication line number */

#define		CX_OPEN		(0x00|CX_CMD)	/* open - initiate communications */
#define		CX_CLOSE	(0x10|CX_CMD)	/* close - shut down line */
#define		CX_BLOCK	(0x20|CX_CMD)	/* end of data block */
#define		CX_REPLY	(0x30|CX_CMD)	/* handshake reply */
#define		CX_TEXT		(0x40|CX_CMD)	/* text message header */
#define		CX_RESTART	(0x50|CX_CMD)	/* re-sync */
#define		CX_SP1		(0x60|CX_CMD)	/* spare code */
#define		CX_SP2		(0x70|CX_CMD)	/* spare code */


/*
 *   Misc
 */
#define		TRC		0x40		/* trace flag */
#define		REPLY_WAIT	DKCMD		/* waiting for ok to send next message */
#define		IN_MESSAGE	DKMPX		/* remote is sending message */
#define		DELAY_CLOSE	DKLINGR		/* delay actual close until buffer has drained */


/*
 * open routine:
 *	called each time a process opens a cx line.
 *
 *	- if the line was previously inactive, set up the initial status
 *	  and arm interrupts
 */
cxopen(Dev)
dev_t	Dev;
{
	register struct tty *Tp;
	register struct tty *tp;
	register dev_t	dev;
	extern	 int	nulldev();

	if (ncx > 1)
		ncx = 1;

	if (minor(Dev) >= 16)  {
		u.u_error = ENXIO;
		return;
		}

	if (Tp->t_state & XCLUDE)  {
		u.u_error = EBUSY;
		return;
		}

	tp = &cx[0];
	if ((tp->t_state & ISOPEN) == 0)  {
		tp->t_dev = makedev(major(Dev), 0);
		tp->t_state = ISOPEN | CNTLQ;
		if (tp->t_flags == 0)
			tp->t_flags = RAW;
		tp->t_oproc = cxstart;
		tp->t_iproc = nulldev;

		cxenab(tp);
		cxusr++;
		}

	Tp = &Cx[minor(Dev)];
	if ((Tp->t_state & ISOPEN) == 0)  {
		Tp->t_dev = Dev;
		Tp->t_state = ISOPEN;
		Tp->t_delct = 0;
		}

	spl4();
	while (!(tp->t_state & CARR_ON))
		sleep(&tp->t_rawq, TTIPRI);
	spl0();

	/* Inform the remote machine we have another line */
	cxout(CX_OPEN, Dev, tp, CNTLQ);
}

/*
 * close routine:
 *	- called only when last process using line releases it
 */
cxclose(Dev)
dev_t	Dev;
{
	register struct tty *Tp;
	register struct tty *tp;

	Tp = &Cx[minor(Dev)];
	tp = &cx[0];
	cxout(CX_CLOSE, Dev, tp, 0);
	Tp->t_state |= XCLUDE;
	/* When output routine sees a CLOSE, it will close the line */
}

cxcls(tp)
register struct tty *tp;
{
	cxdisab(tp);
	flushtty(tp);
	tp->t_state = 0;
	tp->t_flags = 0;
}

/*
 * read, write, ioctl routines:
 */
cxread(Dev)
{
	register struct tty *Tp;
	register c;

	Tp = &Cx[minor(Dev)];
	spl4();
	while (Tp->t_rawq.c_cc == 0)  {
		Tp->t_state |= ASLEEP;
		sleep((caddr_t) &Tp->t_rawq, TTIPRI);
		}
	spl0();

	/* Copy data over */
	c = getc(&Tp->t_rawq);
	if ((c & CX_FN) != CX_TEXT)  {
		/* Corrupt data queue */
		while (Tp->t_rawq.c_cc  &&  (c & CX_FN) != CX_BLOCK)
			c = getc(&Tp->t_rawq);
		u.u_error = EIO;
		return;
		}

	while (u.u_count)  {
		c = getc(&Tp->t_rawq);
		if (c < 0)  {
			u.u_error = ENXIO;
			return;
			}

		if ((c & CX_FN) == CX_BLOCK)
			break;

		if (passc(c) < 0)
			break;
		}

	/* Flush remainder of message */
	while ((c & CX_FN) != CX_BLOCK  &&  c >= 0)
		c = getc(&Tp->t_rawq);
	return;
}

cxwrite(Dev)
dev_t	Dev;
{
	register struct tty *Tp;
	register c;

	Tp = &Cx[minor(Dev)];

	spl4();
	while (Tp->t_outq.c_cc >= 256)  {
		Tp->t_state |= ASLEEP;
		sleep((caddr_t) &Tp->t_rawq, TTIPRI);
		}
	spl0();

	putc(CX_TEXT|minor(Dev), &Tp->t_outq);
	while (u.u_count)  {
		c = cpass();
		if (c < 0)
			break;

		putc(c, &Tp->t_outq);
		}

	if (c < 0)
		u.u_error = EIO;

	putc(CX_BLOCK|minor(Dev), &Tp->t_outq);

	/* Start up the I/O */
	cxqueue(Tp);
}

cxioctl(Dev, cmd, addr, flag)
caddr_t addr;
{
	register struct tty *Tp;
	register struct tty *tp;

	Tp = &Cx[minor(Dev)];
	tp = &cx[0];
	if (ttioccom(cmd, tp, addr, tp->t_dev) == 0)  {
		u.u_error = ENOTTY;
		return;
		}

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

/*
 *   Queue up a response.
 */
cxout(c, dest, tp, cntl)
register c;
register struct tty *tp;
{
	register Dev;

	Dev = dest & CX_DEV;
	if (cntl & CNTLQ)
		putc(c | Dev, &tp->t_un.t_ctlq);
	else
		putc(c | Dev, &tp->t_outq);

	cxstart(tp);
}


/*
 *   Transfer the output queue to the real device structure, and invoke
 *	the output routine.
 */
cxqueue(Tp)
register struct tty *Tp;
{
	register struct tty *tp;

	tp = &cx[0];
	while (Tp->t_outq.c_cc)
		putc(getc(&Tp->t_outq), &tp->t_outq);

	cxstart(tp);
	if (Tp->t_state & ASLEEP)  {
		Tp->t_state &= ~ASLEEP;
		wakeup((caddr_t) &Tp->t_rawq);
		}
}

/*
 *  Break down the queue & pass over to the mux'd lines
 */
cxdequeue(tp)
register struct tty *tp;
{
	register struct tty *Tp;
	register c;

	tp->t_state |= TTSTOP;
	while (tp->t_rawq.c_cc)  {
		c = getc(&tp->t_rawq);
		Tp = &Cx[c & CX_DEV];
		putc(c, &Tp->t_rawq);
		do  {
			c = getc(&tp->t_rawq);
			putc(c, &Tp->t_rawq);
			}	while ((c & CX_FN) != CX_BLOCK);
		if (Tp->t_state & ASLEEP)  {
			Tp->t_state &= ~ASLEEP;
			wakeup((caddr_t) &Tp->t_rawq);
			}
		}

	tp->t_state &= ~TTSTOP;
	cxstart(tp);
}

/*
 *   start routine - called when there might be something to send
 */
cxstart(tp)
register struct tty *tp;
{
	register c, waddr;

	waddr = cxaddr[minor(tp->t_dev)] + 1;
	if ((tp->t_state & (TIMEOUT|TTSTOP|BUSY|CARR_ON)) != CARR_ON)
		return;

	if ((c = getc(&tp->t_un.t_ctlq)) < 0)  {
		/* Control queue empty */
		if (tp->t_state & REPLY_WAIT)
			return;

		if ((c = getc(&tp->t_outq)) < 0)
			/* nothing to send */
			return;
		}

	tp->t_state |= BUSY;
	if ((c & CX_FN) == CX_CLOSE)  {
		tp->t_state |= DELAY_CLOSE;
		tp->t_addr = &Cx[c & CX_DEV];
		}

	if (trmask & TRC)
		printf("-%x", c);
	wd(waddr, c);

	if ((c & CX_FN) == CX_BLOCK)
		/* suspend further output */
		tp->t_state |= REPLY_WAIT;
}

cxstop(tp)
register struct tty *tp;
{
}

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

	tp = &cx[minor(dev)];
	raddr = cxaddr[minor(dev)];

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

	c = rd(raddr);
	if (trmask & TRC)
		printf("+%x", 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;
			}
		}


	/* Determine disposition of character */
	if (c & CX_CMD)  switch (c & CX_FN)  {
		case CX_TEXT:
			cxout(CX_REPLY, c, tp, CNTLQ);
			if (tp->t_state & IN_MESSAGE)  {
				/* 2 TEXT-s with no BLOCK !! */
				return;
				}

			tp->t_state |= IN_MESSAGE;
			putc(c, &tp->t_rawq);
			break;

		case CX_BLOCK:
			cxout(CX_REPLY, c, tp, CNTLQ);
			if (!(tp->t_state & IN_MESSAGE))  {
				/* BLOCK without TEXT !! */
				return;
				}

			tp->t_state &= ~IN_MESSAGE;
			putc(c, &tp->t_rawq);
			cxdequeue(tp);
			return;

		case CX_REPLY:
			tp->t_state &= ~(REPLY_WAIT);
			cxout(CX_REPLY, c, tp, CNTLQ);	/* this will restart the i/o */
			break;

		case CX_RESTART:
			cx[0].t_state &= ~(REPLY_WAIT | IN_MESSAGE);
			for (i=0; i<16; i++)
				flushtty(&Cx[i]);
			flushtty(&cx[0]);
			for (i=0; i<16; i++)  {
				if (Cx[i].t_state & ISOPEN)
					cxout(CX_OPEN, i, tp, CNTLQ);
				if (Cx[i].t_state & ASLEEP)  {
					Cx[i].t_state &= ~ASLEEP;
					wakeup((caddr_t) &Cx[i].t_rawq);
					}
				}

			cxout(CX_REPLY, c, tp, CNTLQ);
			break;

		default:
			cxout(CX_REPLY, 0, tp, CNTLQ);
			printf("cx protocol error %x\n", c);
			return;
		}
	else  {
		cxout(CX_REPLY, 0, tp, CNTLQ);
		if (!(tp->t_state & IN_MESSAGE))  {
			/* character without TEXT !! */
			return;
			}

		putc(c, &tp->t_rawq);
		}

	return;
}

cxxint(dev, stat)
{
	register int waddr;
	register struct tty *tp;
	register struct tty *Tp;
	register c;

	tp = &cx[minor(dev)];
	waddr = cxaddr[minor(dev)] + 1;
	if (!(tp->t_state & ISOPEN))
		return;
	if ((stat & ~0xff) == 0) {
		/* Immediate interrupt */
		tp->t_state &= ~BUSY;
		cxstart(tp);
		}

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

	if (tp->t_state & DELAY_CLOSE)  {
		/* final close of mux'd line */
		tp->t_state &= ~DELAY_CLOSE;
		if (--cxusr == 0)
			cxcls(tp);

		/* Clean up the mux'd structure */
		Tp = tp->t_addr;
		Tp->t_flags = 0;
		Tp->t_state = 0;
		}
}

/*
 * Arm interrupts from PALS and set baud rate
 */
cxenab(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)
		tp->t_ispeed = tp->t_ospeed = B1200;

	cmd2 = conscmd2 & (03<<CLKSHIFT);
	rate = &cxrate[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 = cxaddr[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;

	rd(radd);
}

/*
 * Disarm interrupts from PALS
 */
cxdisab(tp)
register struct tty *tp;
{
	register radd, wadd;

	radd = cxaddr[minor(tp->t_dev)];
	wadd = radd + 1;

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