V9/sys/sundev/zs.c

Compare this file to the similar file:
Show the results in this format:

/*
 *  ZS driver
 */
#include "zs.h"
#define	TRC(c)

#include "../h/param.h"
#include "../h/stream.h"
#include "../h/ttyio.h"
#include "../h/ttyld.h"
#include "../machine/pte.h"
#include "../h/map.h"
#include "../h/buf.h"
#include "../h/systm.h"
#include "../h/conf.h"

#include "../sundev/mbvar.h"
#include "../sundev/zsreg.h"
#include "../sundev/zscom.h"
#include "../sundev/zsvar.h"
#include "../sun/fbio.h"

#define	ZSWR1_INIT	(ZSWR1_SIE|ZSWR1_TIE|ZSWR1_RIE)
#define	ZS_ON	(ZSWR5_DTR|ZSWR5_RTS)
#define	ZS_OFF	0

#define	NZSLINE 	(NZS*2)
#define	SSPEED		B9600		/* std speed = 9600 baud */

#define	EXISTS	01
#define	ISOPEN	02
#define	WOPEN	04
#define	TIMEOUT	010
#define	CARR_ON	020
#define	ZSSTOP	040
#define	HPCL	0100
#define	BRKING	0200
#define	DIALOUT	0400	/* set when used as dialout with smart modems */

#define	ZSPRI	30

/*
 * Modem control commands.
 */
#define	DMSET		0
#define	DMBIS		1
#define	DMBIC		2
#define	DMGET		3

/*
 * One structure per line.
 * Includes communication between H/W level 6 interrupts
 * and software interrupts
 */
#define ZSIBUFSZ	256
struct	zsaline {
/* from v9 dz.c */
	short	state;
	short	flags;
	struct	block	*oblock;
	struct	queue	*rdq;
	char	speed;
/* from SUN zsasync.c */
	short	za_needsoft;		/* need for software interrupt */
	short	za_break;		/* break occurred */
	short	za_overrun;		/* overrun (either hw or sw) */
	short	za_ext;			/* modem status change */
	short	za_work;		/* work to do */
	u_char	za_rr0;			/* for break detection */
	u_char	za_ibuf[ZSIBUFSZ];	/* circular input buffer */
	short	za_iptr;		/* producing ptr for input */
	short	za_sptr;		/* consuming ptr for input */
/* additions */
	dev_t	za_dev;
	struct	zscom *za_addr;
} zsaline[NZSLINE];

extern int zssoftflags[NZSLINE];

int	zsopen(), zsclose(), zsoput(), nodev();

static	struct qinit zsrinit = { nodev, NULL, zsopen, zsclose, 0, 0 };
	struct qinit zswinit = { zsoput, NULL, zsopen, zsclose, 200, 100 };
struct	streamtab zsinfo = { &zsrinit, &zswinit };


#define	PCLK	(19660800/4)	/* basic clock rate for UARTs */
#define	ZSPEED(n)	ZSTimeConst(PCLK, n)
#define	NSPEED	16	/* max # of speeds */
u_short	zs_speeds[NSPEED] = {
	0,
	ZSPEED(50),
	ZSPEED(75),
	ZSPEED(110),
#ifdef lint
	ZSPEED(134),
#else
	ZSPEED(134.5),
#endif
	ZSPEED(150),
	ZSPEED(200),
	ZSPEED(300),
	ZSPEED(600),
	ZSPEED(1200),
	ZSPEED(1800),
	ZSPEED(2400),
	ZSPEED(4800),
	ZSPEED(9600),
	ZSPEED(19200),
	ZSPEED(38400),
};

int zsticks = 3;		/* polling frequency */

/*
 * The async zs protocol
 */
int	zsa_attach(), zsa_txint(), zsa_xsint(), zsa_rxint(), zsa_srint(),
	zsa_softint();

struct zsops zsops_async = {
	zsa_attach,
	zsa_txint,
	zsa_xsint,
	zsa_rxint,
	zsa_srint,
	zsa_softint,
};

zsa_attach(zs)
	register struct zscom *zs;
{
	register struct zsaline *za = &zsaline[zs->zs_unit];

	za->za_addr = zs;
	za->za_dev = zs->zs_unit;
	za->state = EXISTS;
}

/*
 * Get the current speed of the console and turn it into something
 * UNIX knows about - used to preserve console speed when UNIX comes up
 */
zsgetspeed(dev)
	dev_t dev;
{
	struct zscom *zs;
	register struct zsaline *za;
	int uspeed, zspeed;

	dev = minor(dev);
	if (dev >= NZSLINE)
		return(SSPEED);
	za = &zsaline[dev];
	zs = za->za_addr;
	zspeed = ZREAD(12);
	zspeed |= ZREAD(13) << 8;
	for (uspeed = 0; uspeed < NSPEED; uspeed++)
		if (zs_speeds[uspeed] == zspeed)
			return (uspeed);
	return (SSPEED);
}

/*ARGSUSED*/
zsopen(q, d, flag)
register struct queue *q;
{
	register dev;
	register struct zsaline *za;
	struct zscom *zs;
	int s;
	static int first = 1;
	int zspoll();
 
	if (q->ptr)			/* already attached */
		return(1);
	dev = minor(d);
	za = &zsaline[dev];
	if (dev >= NZSLINE || (za->state&EXISTS)==0)
		return(0);
	zs = za->za_addr;
	q->ptr = (caddr_t)za;
	WR(q)->ptr = (caddr_t)za;
	s = splzs();
	zs->zs_priv = (caddr_t)za;
	zsopinit(zs, &zsops_async);
	(void) splx(s);
	if (first) {
		first = 0;
		timeout(zspoll, (caddr_t)0, zsticks);
	}
	if ((za->state&ISOPEN)==0 || (za->state&CARR_ON)==0) {
		register s = spl5();
		/* clear any stale input */
		za->za_iptr = 0;
		za->za_sptr = 0;
		za->za_overrun = 0;
		zsmctl(zs, ZS_ON, DMSET);
		for (;;) {
			za->flags = F8BIT|ODDP|EVENP;
			if (zssoftflags[dev] & ZS_KBDMS)
				za->speed = zsgetspeed(dev);
			else
				za->speed = SSPEED;
			zsparam(za);
			if (za->state & CARR_ON)
				break;
			/* ignore carrier? */
			if (zssoftflags[dev] & 1) {
				za->state |= (DIALOUT | CARR_ON);
				break;
			}
			if (tsleep((caddr_t)za, ZSPRI, 0) != TS_OK) {
				wakeup((caddr_t)za);
				za->speed = 0;
				zsparam(za);
				splx(s);
				return(0);
			}
		}
		za->rdq = q;
		za->state |= ISOPEN;
		splx(s);
	}
	TRC('o');
	return(1);
}

zsclose(q, d)
register struct queue *q;
{
	register struct zsaline *za;
	register s;
	int s1;

	za = (struct zsaline *)q->ptr;
	s = spl5();
	s1 = splzs();
	if (za->oblock) {
		register struct block *bp = za->oblock;
		za->oblock = NULL;
		splx(s1);
		freeb(bp);
	}
	flushq(WR(q), 1);
	za->rdq = NULL;
	if (za->state&HPCL || (za->state&CARR_ON)==0) {
		za->speed = 0;
		zsparam(za);
	}
	za->state &= EXISTS;
	splx(s);
	TRC('c');
}

/*
 * ZS write put routine
 */
zsoput(q, bp)
register struct queue *q;
register struct block *bp;
{
	register struct zsaline *za = (struct zsaline *)q->ptr;
	register union stmsg *sp;
	register s;
	int s1;
	int delaytime;

	TRC('p');
	switch(bp->type) {

	case M_IOCTL:
		TRC('i');
		sp = (union stmsg *)bp->rptr;
		switch (sp->ioc0.com) {

		case TIOCSDEV:
			delaytime = 0;
			if (za->speed != sp->ioc3.sb.ispeed)
				delaytime = 20;
			za->speed = sp->ioc3.sb.ispeed;
			za->flags = sp->ioc3.sb.flags;
			bp->type = M_IOCACK;
			bp->wptr = bp->rptr;
			qreply(q, bp);
			qpctl1(q, M_DELAY, delaytime);	/* wait a bit */
			qpctl(q, M_CTL);		/* means do zsparam */
			zsstart(za);
			return;

		case TIOCGDEV:
			sp->ioc3.sb.ispeed =
			  sp->ioc3.sb.ospeed = za->speed;
			sp->ioc3.sb.flags = za->flags;
			bp->type = M_IOCACK;
			qreply(q, bp);
			return;

		case TIOCHPCL:
			za->state |= HPCL;
			bp->type = M_IOCACK;
			bp->wptr = bp->rptr;
			qreply(q, bp);
			return;

		case KBIOISKBD:
			s = za - zsaline;
			if ((zssoftflags[s] & ZS_KBDMS) && !(s & 1))
				bp->type = M_IOCACK;
			else
				bp->type = M_IOCNAK;
			bp->wptr = bp->rptr;
			qreply(q, bp);
			return;
		
		default:
			bp->wptr = bp->rptr;
			bp->type = M_IOCNAK;
			qreply(q, bp);
			return;
		}

	case M_STOP:
		s = spl5();
		za->state |= ZSSTOP;
		freeb(bp);
		s1 = splzs();
		if (bp = za->oblock) {
			za->oblock = NULL;
			splx(s1);
			putbq(q, bp);
		}
		splx(s);
		return;

	case M_START:
		za->state &= ~ZSSTOP;
		zsstart(za);
		break;

	case M_FLUSH:
		flushq(q, 1);
		freeb(bp);
		s = spl5();
		s1 = splzs();
		if (bp = za->oblock) {
			za->oblock = NULL;
			splx(s1);
			freeb(bp);
		}
		splx(s);
		return;

	case M_BREAK:
		qpctl1(q, M_DELAY, 10);
		putq(q, bp);
		qpctl1(q, M_DELAY, 10);
		zsstart(za);
		return;

	case M_HANGUP:
		za->state &= ~ZSSTOP;
	case M_DELAY:
	case M_DATA:
		putq(q, bp);
		TRC('d');
		zsstart(za);
		return;

	default:		/* not handled; just toss */
		break;
	}
	freeb(bp);
}
 
/*
 * set device parameters
 */
zsparam(za)
	register struct zsaline *za;
{
	register struct zscom *zs = za->za_addr;
	register int wr1, wr3, wr4, wr5, speed;
	int loops;
	int s;
	char c;
 
	if (za->speed == 0) {
		(void) zsmctl(zs, ZS_OFF, DMSET);	/* hang up line */
		return;
	}
	wr1 = ZSWR1_INIT;
	wr3 = ZSWR3_RX_ENABLE;
	wr4 = ZSWR4_X16_CLK;
	wr5 = (zs->zs_wreg[5] & (ZSWR5_RTS|ZSWR5_DTR)) | ZSWR5_TX_ENABLE;
	if (za->speed == B134) {	/* what a joke! */
		wr1 |= ZSWR1_PARITY_SPECIAL;
		wr3 |= ZSWR3_RX_6;
		wr4 |= ZSWR4_PARITY_ENABLE | ZSWR4_PARITY_EVEN;
		wr5 |= ZSWR5_TX_6;
	} else if (za->flags&(F8BIT)) {
		wr3 |= ZSWR3_RX_8;
		wr5 |= ZSWR5_TX_8;
	} else switch (za->flags & (EVENP|ODDP)) {
	case 0:
		wr3 |= ZSWR3_RX_8;
		wr5 |= ZSWR5_TX_8;
		break;

	case EVENP:
		wr1 |= ZSWR1_PARITY_SPECIAL;
		wr3 |= ZSWR3_RX_7;
		wr4 |= ZSWR4_PARITY_ENABLE + ZSWR4_PARITY_EVEN;
		wr5 |= ZSWR5_TX_7;
		break;

	case ODDP:
		wr1 |= ZSWR1_PARITY_SPECIAL;
		wr3 |= ZSWR3_RX_7;
		wr4 |= ZSWR4_PARITY_ENABLE;
		wr5 |= ZSWR5_TX_7;
		break;

	case EVENP|ODDP:
		wr3 |= ZSWR3_RX_7;
		wr4 |= ZSWR4_PARITY_ENABLE + ZSWR4_PARITY_EVEN;
		wr5 |= ZSWR5_TX_7;
		break;
	}
	if (za->speed == B110)
		wr4 |= ZSWR4_2_STOP;
	else if (za->speed == B134)
		wr4 |= ZSWR4_1_5_STOP;
	else
		wr4 |= ZSWR4_1_STOP;
	speed = zs->zs_wreg[12] + (zs->zs_wreg[13] << 8);
	if (wr1 != zs->zs_wreg[1] || wr3 != zs->zs_wreg[3] ||
	    wr4 != zs->zs_wreg[4] || wr5 != zs->zs_wreg[5] ||
	    speed != zs_speeds[za->speed&017]) {
		/* 
		 * Wait for that last damn character to get out the
		 * door.  At most 1000 loops of 100 usec each is worst
		 * case of 110 baud.  If something appears on the output
		 * queue then somebody higher up isn't synchronized
		 * and we give up.
		 */
		s = splzs();
		loops = 1000;
		while ((ZREAD(1) & ZSRR1_ALL_SENT) == 0 && --loops > 0) {
			(void) splx(s);
			DELAY(100);
			s = splzs();
		}
		ZWRITE(3, 0);	/* disable receiver while setting parameters */
		zs->zs_addr->zscc_control = ZSWR0_RESET_STATUS;
		zs->zs_addr->zscc_control = ZSWR0_RESET_ERRORS;
		c = zs->zs_addr->zscc_data; /* swallow junk */
		c = zs->zs_addr->zscc_data; /* swallow junk */
		c = zs->zs_addr->zscc_data; /* swallow junk */
		ZWRITE(1, wr1);
		ZWRITE(4, wr4);
		ZWRITE(3, wr3);
		ZWRITE(5, wr5);
		speed = zs_speeds[za->speed&017];
		ZWRITE(11, ZSWR11_TXCLK_BAUD + ZSWR11_RXCLK_BAUD);
		ZWRITE(14, ZSWR14_BAUD_FROM_PCLK);
		ZWRITE(12, speed);
		ZWRITE(13, speed >> 8);
		ZWRITE(14, ZSWR14_BAUD_ENA + ZSWR14_BAUD_FROM_PCLK);
		(void) splx(s);
	}
}
 
zstimo(za)
register struct zsaline *za;
{
	register struct zscom *zs = za->za_addr;
	int s;

	if (za->state&BRKING) {
		s =  splzs();
		ZBIC(5, ZSWR5_BREAK);
		splx(s);
	}
	za->state &= ~(TIMEOUT|BRKING);
	zsstart(za);
}

zsstart(za)
register struct zsaline *za;
{
	register struct zscom *zs = za->za_addr;
	register s = spl5();
	register struct block *bp;
	int s1;
 
	TRC('s');
again:
	if (za->state & (TIMEOUT|ZSSTOP|BRKING) || za->oblock) {
		TRC('t');
		goto out;
	}
	if (za->rdq == NULL)
		goto out;
	if ((bp = getq(WR(za->rdq))) == NULL) {
		TRC('n');
		goto out;
	}
	switch (bp->type) {

	case M_DATA:
		za->oblock = bp;
		if (zs->zs_addr->zscc_control & ZSRR0_TX_READY)
			zs->zs_addr->zscc_data = *bp->rptr++;
		break;

	case M_BREAK:
		s1 = splzs();
		ZBIS(5, ZSWR5_BREAK);
		splx(s1);
		za->state |= BRKING|TIMEOUT;
		timeout(zstimo, (caddr_t)za, 15);	/* about 250 ms */
		freeb(bp);
		break;

	case M_DELAY:
		za->state |= TIMEOUT;
		timeout(zstimo, (caddr_t)za, (int)*bp->rptr + 6);
		freeb(bp);
		break;

	case M_HANGUP:
		za->speed = 0;
	case M_CTL:
		freeb(bp);
		zsparam(za);
		goto again;

	}
out:
	splx(s);
}
 
zsmctl(zs, bits, how)
	register struct zscom *zs;
	int bits, how;
{
	register int mbits, s;

	s = splzs();
	mbits = zs->zs_wreg[5] & (ZSWR5_RTS|ZSWR5_DTR);
	zs->zs_addr->zscc_control = ZSWR0_RESET_STATUS;
	DELAY(2);
	mbits |= zs->zs_addr->zscc_control & (ZSRR0_CD|ZSRR0_CTS);
	switch (how) {
	case DMSET:
		mbits = bits;
		break;

	case DMBIS:
		mbits |= bits;
		break;

	case DMBIC:
		mbits &= ~bits;
		break;

	case DMGET:
		(void) splx(s);
		return (mbits);
	}
	zs->zs_wreg[5] &= ~(ZSWR5_RTS|ZSWR5_DTR);
	ZBIS(5, mbits & (ZSWR5_RTS|ZSWR5_DTR));
	(void) splx(s);
	return (mbits);
}

zsa_txint(zs)
	register struct zscom *zs;
{
	register struct zsaline *za = (struct zsaline *)zs->zs_priv;
	register struct zscc_device *zsaddr = zs->zs_addr;
	register struct block *bp;

	if ((bp = za->oblock) && bp->rptr < bp->wptr &&
	    (zsaddr->zscc_control & ZSRR0_TX_READY)) {
		zsaddr->zscc_data = *bp->rptr++;
	} else {
		za->za_work++;
		zsaddr->zscc_control = ZSWR0_RESET_TXINT;
		ZSSETSOFT(zs);
	}
}

zsa_xsint(zs)
	register struct zscom *zs;
{
	register struct zsaline *za = (struct zsaline *)zs->zs_priv;
	register struct zscc_device *zsaddr = zs->zs_addr;
	register u_char s0, x0, c;

	s0 = zsaddr->zscc_control;
	x0 = s0 ^ za->za_rr0;
	za->za_rr0 = s0;
	zsaddr->zscc_control = ZSWR0_RESET_STATUS;
	if ((x0 & ZSRR0_BREAK) && (s0 & ZSRR0_BREAK) == 0) {
		za->za_break++;
		c = zsaddr->zscc_data; /* swallow null */
#ifdef lint
		c = c;
#endif
		zsaddr->zscc_control = ZSWR0_RESET_ERRORS;
/*
		if (za->za_dev == kbddev)
			montrap(*romp->v_abortent);
*/
	}
	za->za_work++;
	za->za_ext++;
	ZSSETSOFT(zs);
}

zsa_rxint(zs, d0)
	register struct zscom *zs;
{
	struct lregs {
		int r[4];	/* d0, d1, a0, a1 */
		u_short sr;
		int pc;
	} *lp;
	extern struct zscom zscom[];
	register struct zsaline *za = (struct zsaline *)zs->zs_priv;
	register struct zscc_device *zsaddr = zs->zs_addr;
	register u_char c;

	c = zsaddr->zscc_data;
	if (zs == &zscom[2] && c == 1) { /* A no nonsense abort */
		lp = (struct lregs *)&d0;
		printf("kernel aborted\n");
		printf("pc = 0x%x, sr= 0x%x, &pc = 0x%x\n", lp->pc, lp->sr,
			(int)&lp->pc);
		panic("abort");
	}
	if (c == 0 && (za->za_rr0 & ZSRR0_BREAK))
		return;
	za->za_ibuf[za->za_iptr++] = c;
	if (za->za_iptr >= ZSIBUFSZ)
		za->za_iptr = 0;
	if (za->za_iptr == za->za_sptr)
		za->za_overrun++;
	za->za_work++;
	if (++za->za_needsoft > 20) {
		za->za_needsoft = 0;
		ZSSETSOFT(zs);
	}
}

zsa_srint(zs)
	register struct zscom *zs;
{
	register struct zsaline *za = (struct zsaline *)zs->zs_priv;
	register struct zscc_device *zsaddr = zs->zs_addr;
	register short s1;
	register u_char c;

	s1 = ZREAD(1);
	c = zsaddr->zscc_data;	/* swallow bad char */
#ifdef lint
	c = c;
#endif
	zsaddr->zscc_control = ZSWR0_RESET_ERRORS;
	if (s1 & ZSRR1_DO) {
		za->za_overrun++;
		za->za_work++;
		ZSSETSOFT(zs);
	}
}
/*
 * Handle a software interrupt 
 */
zsa_softint(zs)
	register struct zscom *zs;
{
	register struct zsaline *za = (struct zsaline *)zs->zs_priv;

	if (zsa_process(za))	/* true if too much work at once */
		zspoll(1);
	return (0);
}

/*
 * Poll for events in the zscom structures
 * This routine is called at level 1, we jack up to 3 to lock
 * out zsa_softint.
 */
zspoll(direct)
{
	register struct zsaline *za;
	register short more;
	register int s;

	do {
		more = 0;
		for (za = &zsaline[0]; za < &zsaline[NZSLINE]; za++)
		if (za->za_work) {
			za->za_work = 0;
			s = spl3();
			if (zsa_process(za)) {
				za->za_work++;
				more++;
			}
			(void) splx(s);
		}
	} while (more);
	if (!direct)
		timeout(zspoll, (caddr_t)0, zsticks);
}

/* 
 * Process software interrupts (or poll)
 * Crucial points:
 * 1.  Inner loop gives equal priority to input and output so that
 *     in TANDEM mode the stop character has a chance of being sent
 *     before enough input arrives to exceed TTYHOG.  This has happened
 *     in very busy systems.
 * 2.  The inner loop is executed at most 20 times before the next line
 *     is serviced -- this "schedules" more fairly among lines.
 * 3.  BUG - breaks are handled "out-of-band" - their relative position
 *     among input events is lost, as well as multiple breaks together.
 *     This is probably not a problem in practice.
 */
zsa_process(za)
	register struct zsaline *za;
{
	register struct zscc_device *zsaddr = za->za_addr->zs_addr;
	register struct block *bp;
	register short i;
	register u_char c;

	if (za->za_ext) {
		za->za_ext = 0;
		/* carrier up? */
		if (zsaddr->zscc_control & ZSRR0_CD) {
			/* carrier present */
			if ((za->state & CARR_ON)==0)
				wakeup((caddr_t)za);
			za->state |= CARR_ON;
		} else if ((za->state & CARR_ON) && !(za->state & DIALOUT)) {
			/* carrier lost */
			if (za->state & ISOPEN) {
				(void) zsmctl(za->za_addr, ZSWR5_DTR, DMBIC);
				if (za->rdq)
					putctl(za->rdq->next,M_HANGUP);
			}
			za->state &= ~CARR_ON;
		}
	}
	if (za->za_overrun) {
		za->za_overrun = 0;
		za->za_iptr = 0;
		za->za_sptr = 0;
		if (za->state & ISOPEN)
			printf("zs%d: silo overflow\n", minor(za->za_dev));
	}
	if (za->za_break && (zsaddr->zscc_control & ZSRR0_BREAK) == 0) {
		za->za_break = 0;
		if (za->rdq != NULL && !(za->rdq->next->flag&QFULL) &&
		   (bp = allocb(16)) != NULL) {
			bp->type = M_BREAK;
			(*za->rdq->next->qinfo->putp)(za->rdq->next, bp);
		}
	}
	/* need to handle I & O in same loop to make TANDEM mode work */
	i = 0;
	do {
		if (za->za_sptr != za->za_iptr) {
			c = za->za_ibuf[za->za_sptr++];
			if (za->za_sptr >= ZSIBUFSZ)
				za->za_sptr = 0;
			if (za->rdq != NULL && !(za->rdq->next->flag&QFULL) &&
			   (bp = allocb(16)) != NULL) {
			      *bp->wptr++ = c;
			      (*za->rdq->next->qinfo->putp)(za->rdq->next, bp);
			}
		}
		if ((bp = za->oblock) && bp->rptr >= bp->wptr) {
			freeb(bp);
			za->oblock = NULL;
			zsstart(za);
		}
	} while (za->za_sptr != za->za_iptr && ++i < 20);
	return (i >= 20);
}