/*	dh.c	4.3	11/13/80	*/

#include "../conf/dh.h"
#if NDH11 > 0
/*
 * DH-11 driver
 *
 * Loaded with dhdm if there are DM-11's otherwise with dhfdm.
 *
 * NB: WE HAVEN'T TESTED dhdm CODE ON VAX.
 */

#include "../h/param.h"
#include "../h/conf.h"
#include "../h/dir.h"
#include "../h/user.h"
#include "../h/tty.h"
#include "../h/map.h"
#include "../h/pte.h"
#include "../h/uba.h"
#include "../h/bk.h"
#include "../h/clist.h"
#include "../h/mx.h"

/*
 * When running dz's using only SAE (silo alarm) on input
 * it is necessary to call dzrint() at clock interrupt time.
 * This is unsafe unless spl5()s in tty code are changed to
 * spl6()s to block clock interrupts.  Note that the dh driver
 * currently in use works the same way as the dz, even though
 * we could try to more intelligently manage its silo.
 * Thus don't take this out if you have no dz's unless you
 * change clock.c and dhtimer().
 */
#define	spl5	spl6

#define	UBACVT(x) (cbase + (short)((x)-(char *)cfree))

struct	tty dh11[NDH11];
int	dhact;
int	dhisilo;
int	ndh11	= NDH11;
int	dhstart();
int	ttrstrt();
int	dh_ubinfo;
int	cbase;
int	getcbase;

/*
 * Hardware control bits
 */
#define	BITS6	01
#define	BITS7	02
#define	BITS8	03
#define	TWOSB	04
#define	PENABLE	020
/* DEC manuals incorrectly say this bit causes generation of even parity. */
#define	OPAR	040
#define	HDUPLX	040000

#define	IENAB	030100
#define	NXM	02000
#define	CLRNXM	0400
#define	PERROR	010000
#define	FRERROR	020000
#define	OVERRUN	040000
#define	XINT	0100000
#define	SSPEED	7	/* standard speed: 300 baud */

/*
 * DM control bits
 */
#define	TURNON	03	/* CD lead + line enable */
#define	TURNOFF	01	/* line enable */
#define	DTR	02	/* data terminal ready */
#define	RQS	04	/* request to send */

/*
 * Software copy of last dhbar
 */
short	dhsar[(NDH11+15)/16];

struct device
{
	union {
		short	dhcsr;
		char	dhcsrl;
	} un;
	short	dhnxch;
	short	dhlpr;
	unsigned short	dhcar;
	short	dhbcr;
	unsigned short	dhbar;
	short	dhbreak;
	short	dhsilo;
};

/*
 * Open a DH11 line.
 */
/*ARGSUSED*/
dhopen(dev, flag)
{
	register struct tty *tp;
	register d;
	register struct device *addr;
	int s;

	d = minor(dev) & 0177;
	if (d >= NDH11) {
		u.u_error = ENXIO;
		return;
	}
	tp = &dh11[d];
	addr = DHADDR;
	addr += d>>4;
	tp->t_addr = (caddr_t)addr;
	tp->t_oproc = dhstart;
	tp->t_iproc = NULL;
	tp->t_state |= WOPEN;
	s = spl6();
	if (!getcbase) {
		getcbase++;
		/* 512+ is a kludge to try to get around a hardware problem */
		dh_ubinfo = uballoc((caddr_t)cfree, 512+NCLIST*sizeof(struct cblock), 0);
		cbase = (short)dh_ubinfo;
	}
	splx(s);
	addr->un.dhcsr |= IENAB;
	dhact |= (1<<(d>>4));
	if ((tp->t_state&ISOPEN) == 0) {
		ttychars(tp);
		if (tp->t_ispeed == 0) {
			tp->t_ispeed = SSPEED;
			tp->t_ospeed = SSPEED;
			tp->t_flags = ODDP|EVENP|ECHO;
		}
		dhparam(d);
	}
	if (tp->t_state&XCLUDE && u.u_uid!=0) {
		u.u_error = EBUSY;
		return;
	}
	dmopen(dev);
	(*linesw[tp->t_line].l_open)(dev,tp);
}

/*
 * Close a DH11 line.
 */
/*ARGSUSED*/
dhclose(dev, flag)
dev_t dev;
int  flag;
{
	register struct tty *tp;
	register d;

	d = minor(dev) & 0177;
	tp = &dh11[d];
	(*linesw[tp->t_line].l_close)(tp);
	if (tp->t_state&HUPCLS || (tp->t_state&ISOPEN)==0)
		dmctl(d, TURNOFF, DMSET);
	ttyclose(tp);
}

/*
 * Read from a DH11 line.
 */
dhread(dev)
{
register struct tty *tp;

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

/*
 * write on a DH11 line
 */
dhwrite(dev)
{
register struct tty *tp;

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

/*
 * DH11 receiver interrupt.
 */
dhrint(dev)
{
	register struct tty *tp;
	register short c;
	register struct device *addr;
	register struct tty *tp0;
	int s;

	s = spl6();	/* see comment in clock.c */
	addr = DHADDR;
	addr += minor(dev) & 0177;
	tp0 = &dh11[((minor(dev)&0177)<<4)];
	while ((c = addr->dhnxch) < 0) {	/* char. present */
		tp = tp0 + ((c>>8)&017);
		if (tp >= &dh11[NDH11])
			continue;
		if((tp->t_state&ISOPEN)==0) {
			wakeup((caddr_t)tp);
			continue;
		}
		if (c&PERROR)
			if ((tp->t_flags&(EVENP|ODDP))==EVENP
			 || (tp->t_flags&(EVENP|ODDP))==ODDP )
				continue;
		if (c&OVERRUN)
			printf("O");
		if (c&FRERROR)		/* break */
			if (tp->t_flags&RAW)
				c = 0;	/* null (for getty) */
			else
#ifdef IIASA
				continue;
#else
				c = tun.t_intrc;
#endif
		if (tp->t_line == NETLDISC) {
			c &= 0177;
			BKINPUT(c, tp);
		} else
			(*linesw[tp->t_line].l_rint)(c,tp);
	}
	splx(s);
}

/*
 * stty/gtty for DH11
 */
/*ARGSUSED*/
dhioctl(dev, cmd, addr, flag)
caddr_t addr;
{
	register struct tty *tp;

	tp = &dh11[minor(dev) & 0177];
	cmd = (*linesw[tp->t_line].l_ioctl)(tp, cmd, addr);
	if (cmd==0)
		return;
	if (ttioctl(cmd, tp, addr, dev, flag)) {
		if (cmd==TIOCSETP||cmd==TIOCSETN)
			dhparam(dev);
	} else switch(cmd) {
	case TIOCSBRK:
		((struct device *)(tp->t_addr))->dhbreak |= 1<<(minor(dev)&017);
		break;
	case TIOCCBRK:
		((struct device *)(tp->t_addr))->dhbreak &= ~(1<<(minor(dev)&017));
		break;
	case TIOCSDTR:
		dmctl(minor(dev), DTR|RQS, DMBIS);
		break;
	case TIOCCDTR:
		dmctl(minor(dev), DTR|RQS, DMBIC);
		break;
	default:
		u.u_error = ENOTTY;
	}
}

/*
 * Set parameters from open or stty into the DH hardware
 * registers.
 */
dhparam(dev)
{
	register struct tty *tp;
	register struct device *addr;
	register d;
	int s;

	d = minor(dev) & 0177;
	tp = &dh11[d];
	addr = (struct device *)tp->t_addr;
	s = spl5();
	addr->un.dhcsrl = (d&017) | IENAB;
	/*
	 * Hang up line?
	 */
	if ((tp->t_ispeed)==0) {
		tp->t_state |= HUPCLS;
		dmctl(d, TURNOFF, DMSET);
		return;
	}
	d = ((tp->t_ospeed)<<10) | ((tp->t_ispeed)<<6);
	if ((tp->t_ispeed) == 4)		/* 134.5 baud */
		d |= BITS6|PENABLE|HDUPLX;
	else if (tp->t_flags&RAW)
		d |= BITS8;
	else
		d |= BITS7|PENABLE;
	if ((tp->t_flags&EVENP) == 0)
		d |= OPAR;
	if ((tp->t_ospeed) == 3)	/* 110 baud */
		d |= TWOSB;
	addr->dhlpr = d;
	splx(s);
}

/*
 * DH11 transmitter interrupt.
 * Restart each line which used to be active but has
 * terminated transmission since the last interrupt.
 */
dhxint(dev)
{
	register struct tty *tp;
	register struct device *addr;
	register d;
	short ttybit, bar, *sbar;
	int s;

	s = spl6();	/* block the clock */
	d = minor(dev) & 0177;
	addr = DHADDR + d;
	addr->un.dhcsr &= (short)~XINT;
	if (addr->un.dhcsr & NXM) {
		addr->un.dhcsr |= CLRNXM;
		printf("dh clr NXM\n");
	}
	sbar = &dhsar[d];
	bar = *sbar & ~addr->dhbar;
	d <<= 4; ttybit = 1;

	for(; bar; d++, ttybit <<= 1) {
		if(bar&ttybit) {
			*sbar &= ~ttybit;
			bar &= ~ttybit;
			tp = &dh11[d];
			tp->t_state &= ~BUSY;
			if (tp->t_state&FLUSH)
				tp->t_state &= ~FLUSH;
			else {
				addr->un.dhcsrl = (d&017)|IENAB;
				ndflush(&tp->t_outq,
				    (int)(short)addr->dhcar-UBACVT(tp->t_outq.c_cf));
			}
			if (tp->t_line)
				(*linesw[tp->t_line].l_start)(tp);
			else
				dhstart(tp);
		}
	}
	splx(s);
}

/*
 * Start (restart) transmission on the given DH11 line.
 */
dhstart(tp)
register struct tty *tp;
{
	register struct device *addr;
	register short nch;
	int s, d;

	/*
	 * If it's currently active, or delaying,
	 * no need to do anything.
	 */
	s = spl5();
	d = tp-dh11;
	addr = (struct device *)tp->t_addr;
	if (tp->t_state&(TIMEOUT|BUSY|TTSTOP))
		goto out;

	/*
	 * If the writer was sleeping on output overflow,
	 * wake him when low tide is reached.
	 */
	if (tp->t_state&ASLEEP && tp->t_outq.c_cc<=TTLOWAT(tp)) {
		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_outq.c_cc == 0)
		goto out;

	/*
	 * Find number of characters to transfer.
	 */
	if (tp->t_flags & RAW) {
		nch = ndqb(&tp->t_outq, 0);
	} else {
		nch = ndqb(&tp->t_outq, 0200);
		if (nch == 0) {
			nch = getc(&tp->t_outq);
			timeout(ttrstrt, (caddr_t)tp, (nch&0177)+6);
			tp->t_state |= TIMEOUT;
			goto out;
		}
	}
	/*
	 * If any characters were set up, start transmission;
	 */
	if (nch) {
		addr->un.dhcsrl = (d&017)|IENAB;
		addr->dhcar = UBACVT(tp->t_outq.c_cf);
		addr->dhbcr = -nch;
		nch = 1<<(d&017);
		addr->dhbar |= nch;
		dhsar[d>>4] |= nch;
		tp->t_state |= BUSY;
	}
    out:
	splx(s);
}

/*
 * Stop output on a line.
 * Assume call is made at spl6.
 */
/*ARGSUSED*/
dhstop(tp, flag)
register struct tty *tp;
{
	register struct device *addr;
	register d, s;

	addr = (struct device *)tp->t_addr;
	s = spl6();
	if (tp->t_state & BUSY) {
		d = minor(tp->t_dev);
		addr->un.dhcsrl = (d&017) | IENAB;
		if ((tp->t_state&TTSTOP)==0)
			tp->t_state |= FLUSH;
		addr->dhbcr = -1;
	}
	splx(s);
}

int	dhsilo = 16;
/*
 * Silo control is fixed strategy
 * here, paralleling only option available
 * on DZ-11.
 */
/*ARGSUSED*/
dhtimer()
{
	register d;
	register struct device *addr;

	addr = DHADDR; d = 0;
	do {
		if (dhact & (1<<d)) {
			if ((dhisilo & (1<<d)) == 0) {
				addr->dhsilo = dhsilo;
				dhisilo |= 1<<d;
			}
			dhrint(d);
		}
		d++;
		addr++;
	} while (d < (NDH11+15)/16);
}

/*
 * Reset state of driver if UBA reset was necessary.
 * Reset the csrl and lpr registers on open lines, and
 * restart transmitters.
 */
dhreset()
{
	int d;
	register struct tty *tp;
	register struct device *addr;

	if (getcbase == 0)
		return;
	printf(" dh");
	dhisilo = 0;
	ubafree(dh_ubinfo);
	dh_ubinfo = uballoc((caddr_t)cfree, NCLIST*sizeof (struct cblock), 0);
	cbase = (short)dh_ubinfo;
	d = 0;
	do {
		addr = DHADDR + d;
		if (dhact & (1<<d))
			addr->un.dhcsr |= IENAB;
		d++;
	} while (d < (NDH11+15)/16);
	for (d = 0; d < NDH11; d++) {
		tp = &dh11[d];
		if (tp->t_state & (ISOPEN|WOPEN)) {
			dhparam(d);
			dmctl(d, TURNON, DMSET);
			tp->t_state &= ~BUSY;
			dhstart(tp);
		}
	}
	dhtimer();
}
#endif
