/*
 *              Sin driver -- Simplex Input on PALS
 *              Line Mode Input Driver
 *              - autodriver channel hopefully thru dios
 *              - based on vdu version 1.4
 *
 *              (C) 1981, Dan Ladermann & Gary Gorgen
 *              The Wollongong Group Inc.
 *              Palo Alto, California
 *
 */

#include "../h/local.h"

#ifdef  SCCS_ID
static char SCCS_ID [] = "@(#)sin.c    	1.19	 19:59:16 - 81/11/28 ";
#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/systm.h"
#include "../h/ccb.h"
#include "../h/mx.h"
#include "../h/buf.h"
#ifdef  DIOS
#include "../h/dios.h"
#endif  DIOS

#ifndef  YES
#define  YES 1
#define  NO  0
#endif   YES

/*#undef trace                    /* TRACING ON */
#define TRACE1 0x01000000
#define TRACE2 0x02000000
extern int trmask;

extern int	nsin;		/* number of ports */
/* the following arrays have nsin elements */
extern char	sinaddr[];	/* port addresses */
extern char sinrate[][4];	/* Baud rate table */
extern struct tty sin[];	/* common tty structure */
extern struct ccb *sinccb;      /* channel control blocks */
#ifdef  DIOS
extern int      sindios[];      /* dioses for sin drivers */
#endif  DIOS

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

extern short isp[];		/* interrupt service ptr table */
extern char devmap[];
extern char devint[];
extern int (*handler[]) ();

int sinrint (), sintint ();
int sinint ();

/*
 * 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 PODD    0004            /* odd  parity */
#define PARE	0006		/* even parity */

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

	/* Status  as returned by AutoDriver Channel interrupt */
#define ADCBADSTATUS            0x100
#define ADCBUFLIMIT             0x200

	/* ADC mask used in ccb command */
#define CCBSEX  0x400


	/* Translate table defines */
#define XLATESUB        0x8000  /* substitute on translate */
#define NXLATE          256     /* size of translate table */

short   sinxlate[NXLATE];               /* one translate table for all sins */
short   sinrawxlate[NXLATE];            /* one translate table for all sins */


	/* Buffer management defines */
#define SIZEBUF         BSIZE
#define ENDBUF          (SIZEBUF-1)
#define STRTBUF0        0
#define STRTBUF1        0

	/* Structure needed for each sin device
	 *      should be interfaced with mkconf, so you can change number
	 *      of buffers allocated at sysgen time.  For now you need to
	 *      recompile the dirver if you want a larger number of sins
	 *      - could do without this struct but it makes things easier
	 */
#define MAXSINS         6       /* max number of sins, used in local buff */
#define SINWAIT         0x01    /* set by read if waiting for input       */

struct lsin {
	struct buf      *sin_0cbuf;     /*save area for cache buffer pointer*/
	struct buf      *sin_1cbuf;     /*save area for cache buffer pointer*/
	char            *sin_buf[2];    /* address of buffer 0 and 1 */
	short           *sin_cnt[2];    /* address of ccbcount 0 and 1 */
	int             sin_off[2];     /* offset used for reading     */
	int             sin_curb;       /* current buffer being read from */
	int             sin_stat;       /* status not in tty struct    */
	int             sin_minbrk;     /* minimum chars to break on */
					/* without at least one timeout */
};
struct lsin     lsin[MAXSINS];          /* this should be done by mkconf */
#define MINBREAK 128    /* default sin_minbrk */
int sinerr[MAXSINS];    /* count of chars thrown out cause of ADCBADSTATUS */

int     xlateset = NO;  /* flag used for setting up translate table */
int     sint_in_p = NO; /* timeout in progress */
int     sin_time = HZ;  /* size of time out in ticks, can be set by adb */
/*
 * open routine:
 *      called each time a process opens sin as a character file
 *
 *      - if the state was previously inactive, set up the initial status
 *	  and arm interrupts
 *
 *      - if it is the first open, allocate a buffer from the buffer cache
 *        to be used for the auto driver channel imput buffers
 *
 */


sinopen (dev)
{
	register struct tty *tp;
	register struct ccb *ccbp;      /* pointer to channel control block */
	register struct buf *dbuf0;      /* pointer to buffer structure 0   */
	register struct buf *dbuf1;      /* pointer to buffer structure 1   */
	register struct lsin *lsinp;    /* pointer to local sin structure   */
	register int minordev;

	if ((minordev = minor (dev)) >= nsin) {
		u.u_error = ENXIO;
		return;
	}

/*      trmask |= TRACE1 | TRACE2;        /* TURN ON TRACING */
trace (TRACE1, "open", dev  );

	tp = &sin[minordev];
	if ((tp->t_state & ISOPEN) == 0) {
		tp->t_dev = dev;
		tp->t_state = ISOPEN;
		if (tp->t_flags == 0)
			tp->t_flags =  EVENP | ODDP;
		ttychars (tp);

		ccbp = &sinccb[minordev];
		lsinp = &lsin[minordev];

		isp[sinaddr[minordev] ] = (char *)ccbp + 1;

		/* set pointer to translate table */
		if ( !xlateset) {       /* if first open of any sin,   */
			xlateinit ();   /* initialize translate table  */
			xlateset = YES;
		}
		if ((tp->t_flags & RAW)==0)
			ccbp->cc_tab = sinxlate;
		else
			ccbp->cc_tab = sinrawxlate;

		/* get buffer from buffer cache and set up auto driver */
		/* ccb to use the buffer which is split in half        */
trace (TRACE1, "bgbl", dev  );

		dbuf0 = geteblk ();
		dbuf1 = geteblk ();
trace (TRACE1, "agbl", dev  );

		lsinp->sin_0cbuf = dbuf0;/* save address of buffer for brelse */
		lsinp->sin_1cbuf = dbuf1;/* on close                        */

		ccbp->cc_buf0 = & (dbuf0->b_un.b_addr[ENDBUF]);
		ccbp->cc_buf1 = & (dbuf1->b_un.b_addr[ENDBUF]);
		ccbp->cc_cnt0 = -ENDBUF;
		ccbp->cc_cnt1 = -ENDBUF;
		ccbp->cc_ccw  = CCBSEX | CCBEXECUTE | CCBREAD | CCBXLATE | CCBLRC;

		lsinp->sin_buf[0] = & (dbuf0->b_un.b_addr[STRTBUF0]);
		lsinp->sin_buf[1] = & (dbuf1->b_un.b_addr[STRTBUF1]);
		lsinp->sin_cnt[0] = & (ccbp->cc_cnt0);
		lsinp->sin_cnt[1] = & (ccbp->cc_cnt1);
		lsinp->sin_off[0] = lsinp->sin_off[1] = 0;
		lsinp->sin_curb   = 0;
		lsinp->sin_minbrk = MINBREAK;
		sinerr[minordev] = 0;

trace (TRACE1, "benb", dev  );

		sinenab (tp, minordev);
trace (TRACE1, "aenb", dev  );
	}
#ifdef  WAIT_FOR_CARRIER
	spl4 ();
	while ( !(tp->t_state&CARR_ON))
		sleep (&tp->t_rawq, TTIPRI);
	spl0 ();
#endif  WAIT_FOR_CARRIER

	if ((tp->t_state&XCLUDE) && u.u_uid != 0) {
		u.u_error = EBUSY;
		return;
	}
}

/*
 * close routine:
 *	- called only when last process using terminal releases it
 */
sinclose (dev)
{
	register struct tty *tp;
	register int minordev;

	tp = &sin[minordev = minor (dev)];
trace (TRACE1, "bclo", dev  );
	tp->t_state = 0;
	brelse (lsin[minordev].sin_0cbuf); /*give buffer back to buffer cache*/
	brelse (lsin[minordev].sin_1cbuf); /*give buffer back to buffer cache*/

	sindisab (dev);
	if (sinerr[minordev])
		printf ("sin%d lost %d character%s\n", minordev,
			sinerr[minordev], sinerr[minordev] == 1 ? "" : "s");
trace (TRACE1, "aclo", dev  );
}

/*
 * read, ioctl routines:
 */
sinread (dev)
{
	register struct lsin *lsinp;    /* pointer to local sin structure   */
	register int    cb;             /* current buffer                   */
	register int nready;            /* num bytes ready to copy to user */
	register int numdone;           /* number transferred so far */
	register int havetimed;         /* we have timed out at least once */
	register int minordev;

	lsinp = &lsin[minordev = minor(dev)];
	cb = lsinp->sin_curb;

#ifdef  DIOS
	/* testing */
	if (sindios[minordev]) {
		diosint (0, 0);
		goto out;
	}
#endif  DIOS

trace (TRACE1, "bred", dev  );
	for (havetimed = NO, numdone = 0; ; ) {
		spl5 ();         /* lock out auto driver  */
#ifdef  DIOS
		if (sindios[minordev]) {
		}
#endif  DIOS
trace (TRACE1, "rdloop", dev  );
		while (0 == (nready =
			 ENDBUF + *lsinp->sin_cnt[cb] - lsinp->sin_off[cb])) {
			/* if sizes the same set them back to start */
			if (   numdone
			    && (   havetimed
				|| numdone > lsinp->sin_minbrk
			       )
			   ) {
				spl0 ();
				goto out;
			}
			/* wait until there is data to read  */
			if ( !sint_in_p) {   /* if timeout not in progress */
				sint_in_p = YES;
				timeout (sintint, 0, sin_time);
			}
trace (TRACE1, "brdsleep", dev  );
			sleep ((caddr_t)lsin, TTIPRI);
trace (TRACE1, "ardsleep", dev  );
			havetimed = YES;
		}
		spl0 ();         /* enable interrupts */

trace (TRACE1, "rdmidloop", dev  );
		/* now copy data to user */
		if (nready > 0) {
			if (nready > u.u_count)
				nready = u.u_count;
			iomove(&lsinp->sin_buf[cb][lsinp->sin_off[cb]],
			       nready, B_READ);
			lsinp->sin_off[cb] += nready;
			if (lsinp->sin_off[cb] == SIZEBUF) {
				/* if we are at the end of the current
				 * buffer, switch to the other one.
				 */
				lsinp->sin_off[cb] = 0;
				*lsinp->sin_cnt[cb] = -ENDBUF;
				cb = lsinp->sin_curb = lsinp->sin_curb ? 0 :1;
			}
			if (u.u_count == 0)
				goto out;
			numdone += nready;
		}
	}
 out:
trace (TRACE1, "ared", dev  );
	return;
}

/* ARGSUSED */
sinioctl (dev, cmd, addr, flag)
caddr_t addr;
{
	register struct tty *tp;

	tp = &sin[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)
		sinenab (tp, minor (dev));
}


sinrint (minordev, stat)
register stat;
{
	register int raddr;
	register struct tty *tp;
	register struct ccb *ccbp;      /* pointer to channel control block */
	register struct lsin *lsinp;    /* pointer to local sin structure   */

	tp = &sin[minordev];
	raddr = sinaddr[minordev];

	if ( !(tp->t_state & ISOPEN)) {
		printf ("sinrint while closed\n");
		return;
	}
trace (TRACE1, "rint", raddr);
trace (TRACE1, "stat", stat);

#ifdef  DIOS
	if (sindios[minordev]) {
		stat = ss (raddr);
	}
#endif  DIOS

	if (stat & ADCBADSTATUS ) {
		register char c;
		/*
		 * EXA (bad satsus means error condition, read from device
		 * to reset the mode, and just throw any data away for now
		 */
		c = rd (raddr);
	     /* ++sinerr[minordev];     /* we should really find out what */
					/* types of errors are occurring */
		goto out;
	}

	if (stat &  ADCBUFLIMIT) {
		/* buffer overflow just wakeup reader if sleeping  */
		wakeup ((caddr_t)lsin);
		goto out;
	}

	/* must be translate interrupt */
trace (TRACE1, "xlat", minordev);

	goto out;       /* should not happen in this version */

#ifdef TRANSLATE
	ccbp = &sinccb[minordev];
	lsinp = &lsin[minordev];
	/* set donebuf to current buffer        */
	donebuf = (ccbp->cc_ccw & CCBBUFSW) ? 1 : 0;

	/* was translate interrupt, put break character in buffer */
	/* and increment the count                                */

	lsinp->sin_buf[donebuf][ENDBUF + *lsinp->sin_cnt[donebuf]++]
		= (char) (stat&0xFF);

	/* if we are not over TTYHOG move the data to the */
	/* raw queue, note could use TTYHOG + SIZEBUF     */

	if ( tp->t_rawq.c_cc < TTYHOG ) {
		b_to_q ( lsinp->sin_buf[donebuf],
			ENDBUF + *lsinp->sin_cnt[donebuf],
			&tp->t_rawq );
	}

#endif TRANSLATE
 out:
	return;
}

/*
 * Handle time out interrupts, set by sinread
 */

sintint (arg) {
	sint_in_p = NO;
	wakeup ((caddr_t)lsin);
}




/*
 * Arm interrupts from PALS and set baud rate
 */
sinenab (atp, minordev)
struct tty *atp;
int minordev;
{
	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 = &sinrate[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) {
			if ( tp->t_flags & ODDP )
				cmd2 |= DATA7 | STOP | PODD;
			if ( tp->t_flags & EVENP )
				cmd2 |= DATA7 | STOP | PARE;
		}
		else
			cmd2 |= DATA8 | STOP;
	}

#ifdef  DIOS
/*      kickdios (minordev); /* testing */

	/* initialize the dios if applicable */
	if (sindios[minordev])
		diosinit (sindios[minordev]);
#endif  DIOS

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

	oc (radd, cmd2);
	oc (radd, EN | DTR | CMD1);

/***    oc (radd + 1, EN | DTR | WRT | CMD1);    /* oc (wadd ... */

	stat = ss (radd);
	if ((stat&CARR_OFF) == 0)
		tp->t_state |= CARR_ON;

trace (TRACE1, "sinenab", stat);
	rd (radd);
}

sinint (dev, stat)
register stat;
{
	printf ("begin sin interrupt");
	sindisab (dev);
	printf ("end sin interrupt");
	return;
}

/*
 * Disarm interrupts from PALS
 */
sindisab (dev)
{
	register radd;

	radd = sinaddr[minor (dev)];

	oc (radd, DIS | CMD1);

/*
 *      oc (radd + 1, DIS | WRT | CMD1);
 */

trace (TRACE1, "sindisab", ss (radd));
}

/*
 * xlateinit routine
 *      - called to initialize the sinxlate table - it could be initialize
 *      - at define time.
 */
xlateinit ()
{
	register i;
trace (TRACE1, "bxin", 0);
	for (i = 0; i < NXLATE; i++) {
		sinxlate[i] = (i & 0x7F) | XLATESUB;
		sinrawxlate[i] = i | XLATESUB;
	}
trace (TRACE1, "axin", 0);
	return;
}

#ifndef DIOS
diosint (minordev, stat) {
	printf ("dios %d interrupted.  status=0x%x\n", minordev, stat);
}
#else   DIOS
diosint (minordev, stat)
{
	extern short diosaddr[];
	int piq;
	int piqdev;     /* device addr of device to be serviced */
	int piqstatus;  /* reason field from piq */

/*      printf ("dios %d interrupted.  status=0x%x\n", minordev, stat);/**/
	while ( !(ss (diosaddr) & PIQEMPTY)) {
		piq = rh (diosaddr);
		piqstatus = (piq & PIQ_REASON) >> 8;
		printf ("dios piq: (in)valid=%d, reason=0x%x, devaddr=0x%x\n",
			piq & PIQ_VALID ? 1 : 0, piqstatus, piq & PIQ_DEVICE);
		if (piq & PIQ_VALID)
			continue;
		piqdev = (piq & PIQ_DEVICE) | diosaddr[minordev];
		switch (piqstatus) {
		case 0:         /* execute reset */
		case 1:         /* bad status */
		case 2:         /* buffer limit */
		case 8:         /* immmediate interrupt */
		case 9:         /* special character */
		case 0xa:       /* normal termination */
		case 0xb:       /* transfer process */
		case 0xc:       /* response too slow */
			break;
		case 3:         /* impossible */
		case 4:         /* false sync */
		case 5:         /* spurious interrupt */
		case 6:         /* main memory malfunction */
		case 7:         /* impossible */
		case 0xd:       /* piq overflow */
		case 0xe:       /* impossible */
		case 0xf:       /* dios hardware problem */
			printf ("I/O error on dios 0x%x: reason=0x%x device=0x%x\n",
				diosaddr[minordev], piqstatus, piqdev);
			return;
		}
		(*handler[devint[piqdev]]) (devmap[piqdev], piqstatus);
	}
	return;
}

kickdios (minordev)
int minordev;
{
	if ( !sindios[minordev])
		return;
	/* set DIOS to KILL mode so auto driver channel should work */
trace (TRACE2, "dios status is", ss (sindios[minordev]));
trace (TRACE1, "kill dios", 0);
	oc (sindios[minordev], KILL_CMD);
trace (TRACE2, "result of dios kill", ss (sindios[minordev]));
	oc (sindios[minordev], 4 | DISABLE_DIOS | KILL_CMD);
	wh (sindios[minordev], 0x0210);
	oc (sindios[minordev], ACTIVE_CMD);
trace (TRACE2, "dios status after loading piq", ss (sindios[minordev]));
	oc (sindios[minordev], ACTIVE_CMD | ENABLE_DIOS | READPIQ);
trace (TRACE2, "dios enabled", ss (sindios[minordev]));
}

diosinit (dios)
int dios;
{
	static first[MAXDIOS];
	int diosminor;

	if ( !first[diosminor = devmap[dios]]) {
		first[diosminor] = 1;
trace (TRACE2, "dios status is", ss (dios));
	oc (dios, DISARM_DIOS | KILL_CMD);
/*      oc (dios, ENABLE_DIOS | ACTIVE_CMD | READPIQ); /**/
	oc (dios, DISABLE_DIOS | ACTIVE_CMD | READPIQ);
trace (TRACE2, "dios enabled", ss (dios));
	}
}
#endif  DIOS
