/*
 *		Synchronous Data Set Adapter driver
 *
 *	- uses Autodriver Channel for input & output
 *
 *	- for QSA (Quad Synchronous Adapter) #define QSA
 *
 */
#include "../h/local.h"

#ifdef  SCCS_ID
static char SCCS_ID [] = "@(#)dsa.c    	4.2	 13:48:38 - 82/01/16 ";
#endif  SCCS_ID


#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/ccb.h"

#define	QSA
#define ISIZE	256
#define	OSIZE	256


extern int	ndsa;		/* number of DSA'S */
extern char	dsaaddr[];	/* DSA addresses */
extern struct tty dsa[];	/* common tty structure */

#define	q1	tp->t_rawq
#define	q2	tp->t_outq

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

char	dsaibuf[ISIZE];
char	dsaobuf[OSIZE];

int dsastin(), dsastout();

/*
 * DSA commands & status bits
 */

#define	DIS	0xc0
#define	ENB	0x40
#define PAR	0x20
#define SYNSCH	0x10
#define RSDTA	0x10
#define	DTR	0x08
#define	WR	0x02
#define RQ2S	0x02
#define RD	0x01
#define CMD	0x01

#define BITS7	0x20
#define BITS8	0x30
#define PODD	0x04
#define PEVEN	0x06

#define OV		0x80
#define PARF		0x40
#define SYNC		0x20
#define BSY		0x08
#define	EX		0x04
#define	CARR_OFF	0x02
#define NOT_CL2S	0x02
#define DU		0x01

#define RDERR	(OV|PARF|CARR_OFF|DU)
#define WRERR	(OV|BSY|NOT_CL2S|DU)

#define SYN	0x16
#define ETX	0x03
#define PAD	0xff

/*
 * open routine:
 *	called each time a process opens a DSA as a character file
 *
 *	- set up the initial status
 *	  and arm interrupts
 */
dsaopen(dev)
{
	register struct tty *tp;
	register struct ccb *ccb;

	if (minor(dev) >= ndsa) {
		u.u_error = ENXIO;
		return;
	}
	tp = &dsa[minor(dev)];
	if (tp->t_state & ISOPEN) {
		u.u_error = ENXIO;
		return;
	}
	tp->t_state = ISOPEN;
	tp->t_dev = dev;
	tp->t_flags = RAW;
	tp->t_oproc = dsastout;
	tp->t_iproc = dsastin;
	q1.c_cf = q1.c_cl = dsaibuf;
	q1.c_cc = - (ISIZE);
	ccb = &dsaccb[minor(dev)<<1];
	isp[dsaaddr[minor(dev)]] = (short)ccb + 1;
	isp[dsaaddr[minor(dev)] + 1] = (short)(ccb + 1) + 1;
	tp->t_addr = (caddr_t)ccb;

	dsaenab(tp);
	spl4();
	while (!(tp->t_state&CARR_ON))
		sleep((caddr_t)&q1, TTIPRI);
	spl0();
	(*linesw[tp->t_line].l_open)(dev, tp);
}

/*
 * close routine:
 */
dsaclose(dev)
{
	register struct tty *tp;
	tp = &dsa[minor(dev)];
	(*linesw[tp->t_line].l_close)(tp);
	ttyclose(tp);
	dsadisab(dev);
}

/*
 * read, write, ioctl routines:
 */
dsaread(dev)
{
	register struct tty *tp;
	register c;

	tp = &dsa[minor(dev)];
	if (tp->t_line) {
		(*linesw[tp->t_line].l_read)(tp);
		return;
	}
	spl4();
	while (q1.c_cl == dsaibuf) {
		dsastin(tp);
		sleep((caddr_t)&q1, TTIPRI);
	}
	spl0();
	while (q1.c_cf < q1.c_cl)
		if (passc(*q1.c_cf++) < 0)
			return;
	q1.c_cf = q1.c_cl = dsaibuf;
}

dsawrite(dev)
{
	register struct tty *tp;
	register char *p;
	register c;
	static char syns[] = SYN, SYN, SYN, SYN, 0;
	static char pads[] = PAD, PAD, 0;

	tp = &dsa[minor(dev)];
	if (tp->t_line) {
		(*linesw[tp->t_line].l_write)(tp);
		return;
	}
	if ((tp->t_state & CARR_ON) == 0)
		return(0);
	spl4();
	while (q2.c_cc) {
		tp->t_state |= ASLEEP;
		sleep((caddr_t)&q2, TTOPRI);
	}
	spl0();
	q2.c_cf = q2.c_cl = dsaobuf;
	for (p = syns; (c = *p); p++)
		*q2.c_cl++ = c;
	while (q2.c_cl < dsaobuf + OSIZE - sizeof pads) {
		if ((c = cpass()) < 0)
			break;
		*q2.c_cl++ = c;
	}
	for (p = pads; (c = *p); p++)
		*q2.c_cl++ = c;
	q2.c_cc = q2.c_cf - q2.c_cl;
	spl4();
	dsastout(tp);
	spl0();
}

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

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

/*
 * start output to DSA
 */

dsastout(tp)
register struct tty *tp;
{
	register c, waddr;
	register struct ccb *ccb;
	register char *s;

	waddr = dsaaddr[minor(tp->t_dev)] + 1;
trace(8<<16, "dsastart", waddr);
	if ((tp->t_state&(BUSY|CARR_ON)) != CARR_ON
	    || q2.c_cc == 0)
		return;
	tp->t_state |= BUSY;
	ccb = (struct ccb *)tp->t_addr + 1;
	if (q2.c_cc > 0) {
		s = dsaobuf;
		s[0] = SYN; s[1] = SYN; s[2] = SYN; s[3] = SYN;
		s += 4;
		s += q_to_b(&q2, s, OSIZE - 6);
		s[0] = PAD; s[1] = PAD;
		s += 1;
		ccb->cc_buf0 = s;
		ccb->cc_cnt0 = dsaobuf - s;
	} else {
		ccb->cc_buf0 = q2.c_cl - 1;
		ccb->cc_cnt0 = q2.c_cf - q2.c_cl + 1;
		q2.c_cc = 0;
	}
	ccb->cc_ccw = (WRERR<<8) | CCBEXECUTE | CCBWRITE | CCBFAST;
#ifdef QSA
	oc(waddr, ENB | RSDTA | DTR | RQ2S | CMD);
	if ((ss(waddr)&BSY) == 0)
		wd(waddr, PAD);
#else
	wd(waddr, -1);
	oc(waddr, ENB | PAR | DTR | WR);
#endif
trace(8<<16, "wrt", ss(waddr));
	if ((tp->t_state&ASLEEP) && q2.c_cc == 0)
		wakeup((caddr_t)&q2);
}

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

trace(8<<16, "dsstop", tp->t_dev);
	ccb = (struct ccb *)tp->t_addr + 1;
	ccb->cc_cnt1 = 0;
	ccb->cc_ccw = 0;
}

/*
 * start input from DSA
 */
dsastin(tp)
register struct tty *tp;
{
	register struct ccb *ccb;
	register int raddr;

	raddr = dsaaddr[minor(tp->t_dev)];
	ccb = (struct ccb *)tp->t_addr;
	ccb->cc_buf0 = q1.c_cf + (-q1.c_cc) - 1;
	ccb->cc_cnt0 = q1.c_cc + 1;
	ccb->cc_ccw = (RDERR<<8) | CCBEXECUTE | CCBREAD | CCBFAST;

#ifdef QSA
	oc(raddr, ENB | SYNSCH | DTR | CMD);
#else
	oc(raddr, ENB | PAR | SYNSCH | DTR);
#endif
	rd(raddr);
}

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

	tp = &dsa[dev];
	raddr = dsaaddr[dev];

	if (!(tp->t_state & ISOPEN)) {
		dsadisab(dev);
		return;
	}
trace(8<<16, "rint", raddr);
trace(8<<16, "stat", stat);

	/*
	 * Loss of DSR - hangup
	 */
	if (stat & DU) {
		if (tp->t_state & CARR_ON) {
			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((caddr_t)&q1);
	}

	/*
	 * overrun - throw away block and resynchronise
	 */
	if (stat & OV) {
		dsastin(tp);
		return;
	}

	/*
	 * if BSY still set - no char to read
	 */
	if ((stat & (BSY|CARR_OFF)) == BSY)
		return;

	/*
	 * buffer full or carrier off -- give data to reader
	 */
#ifdef QSA
	oc(raddr, DIS | DTR | CMD);
#else
	oc(raddr, DIS | DTR | RD);
#endif
	ccb = (struct ccb *)tp->t_addr;
	q1.c_cl = ccb->cc_buf0 + ccb->cc_cnt0;
	if (tp->t_line)
		(*linesw[tp->t_line].l_rend)(tp, q1.c_cf, q1.c_cl);
	else
		wakeup((caddr_t)&q1);
}

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

	tp = &dsa[dev];
	waddr = dsaaddr[dev] + 1;
	if (!(tp->t_state & ISOPEN)) {
		dsadisab(dev);
		return;
	}
trace(8<<16, "wint", waddr);
trace(8<<16, "stat", stat);

	if (stat&BSY)
		return;

	tp->t_state &= ~BUSY;
#ifdef QSA
	oc(waddr, DIS | RSDTA | DTR | CMD);
#else
	oc(waddr, DIS | DTR | WR);
#endif
	(*linesw[tp->t_line].l_start)(tp);
}

/*
 * Arm interrupts from DSA and set reading
 */
dsaenab(atp)
struct tp *atp;
{
	register struct tp *tp;
	register radd;
	register stat;

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

#ifdef QSA
	oc(radd, BITS7 | PODD);
	oc(radd+1, BITS7 | PODD);
	wd(radd, SYN);
	oc(radd, ENB | SYNSCH | DTR | CMD);
#else
	oc(radd, ENB | PAR | SYNSCH | DTR);
#endif
	stat = ss(radd);
	if ((stat&DU) == 0)
		tp->t_state |= CARR_ON;

trace(8<<16, "dsaenab", stat);
	rd(radd);
}

/*
 * Disarm interrupts from DSA
 */
dsadisab(dev)
{
	register radd, wadd;

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

#ifdef QSA
	oc(radd, DIS | CMD);
	oc(wadd, DIS | CMD);
#else
	oc(radd, DIS);
	oc(wadd, DIS);
#endif

trace(8<<16, "dsadisab", ss(radd));
}
