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

#ifdef  SCCS_ID
static char SCCS_ID [] = "@(#)mx2.c    	3.2	 02:10:08 - 81/12/10 ";
#endif  SCCS_ID

#include "../h/param.h"
#include "../h/systm.h"
#include "../h/dir.h"
#include "../h/user.h"
#include "../h/proc.h"
#include "../h/tty.h"
#include "../h/inode.h"
#define	KERNEL	1
#include "../h/mx.h"
#include "../h/file.h"
#include "../h/conf.h"
#include "../h/buf.h"

/*
 * multiplexor driver
 */
struct	chan	chans[NCHANS];
struct	group	groups[NGROUPS];	/***/
int	mpxline;
struct chan *xcp();
struct chan *addch();
struct chan *nextcp();

#define	MIN(a,b)	((a<b)?a:b)
short	cmask[16]	={
	01,	02,	04,
	010,	020,	040,
	0100,	0200,	0400,
	01000,	02000,	04000,
	010000,	020000,	040000, 0100000
};

#define	HIQ	100
#define	LOQ	20
#define	FP	((struct file *)cp)



char mcdebugs[NDEBUGS];

/*
 * Timing cell
 */
int	mxdummy;
int	*MP	= &mxdummy;



struct group *
getmpx(dev)
dev_t dev;
{
register d;

	d = minor(dev);
	if (d >= NGROUPS) 
		return(NULL);
/***/	return(&groups[d]);
}




mxopen(dev, flag)
{
register struct group *gp;
register struct file *fp;
register struct chan *cp;
int	msg;
	gp = getmpx(dev);
	if (gp == NULL) {
	bad:
		u.u_error = ENXIO;
		return;
	}

	if (gp->g_state == COPEN) {
		gp->g_state = INUSE+ISGRP;
		return;
	}
	if (!(gp->g_state&INUSE)) 
		goto bad;
	fp = u.u_ofile[u.u_r.r_val1];
	if (fp->f_inode != gp->g_inode) 
		goto bad;
	if ((cp=addch(gp->g_inode, 0))==NULL)
		goto bad;
	cp->c_flags = XGRP;
	cp->c_ottyp = cp->c_ttyp = (struct tty *)cp;
	cp->c_line = cp->c_oline = mpxline;
	fp->f_flag |= FMPY;
	fp->f_flag |= FREAD+FWRITE;
	fp->f_un.f_chan = cp;
	if (gp->g_inode == mpxip) {
		plock(mpxip);
		mpxname(cp);
		msg = M_OPEN;
	} else
		msg = M_WATCH;
/***/	trace(0x80000000, "mxopen", msg);
	scontrol(cp, msg, u.u_uid);
	sleep((caddr_t)cp,TTIPRI);
	if (cp->c_flags&NMBUF)
		prele(mpxip);
	if (cp->c_flags & WCLOSE) {
		chdrain(cp);
		chfree(cp);
		goto bad;
	}
	cp->c_fy = fp;
	cp->c_pgrp = u.u_procp->p_pgrp;
}


char	mxnmbuf[NMSIZE];
int	nmsize;
struct	chan *mxnmcp;
mpxname(cp)
register struct chan *cp;
{
register char *np;
register c;
	np = mxnmbuf;
	u.u_dirp = (caddr_t)u.u_arg[0];
	
	while (np < &mxnmbuf[NMSIZE]) {
		c = uchar();
		if (c <= 0)
			break;
		*np++ = c;
	}
	*np++ = '\0';
	nmsize = np - mxnmbuf;

	cp->c_flags |= NMBUF;
}


mxclose(dev, flag, cp)
dev_t	dev;
register struct chan *cp;
{
register struct group *gp;
register struct inode *ip;
register struct file *fp;
int	i, fmp;

	fmp = flag&FMP;

	/*
	 * close a channel
	 */
	if (cp!=NULL && fmp && fmp!=FMP) {
		for(fp=file; fp < fileNFILE; fp++)
		    if(fp->f_count && fp->f_flag&FMP && fp->f_un.f_chan==cp){
				return;
			}
		chdrain(cp);
		if ((cp->c_flags&WCLOSE)==0) {
/***/			trace(0x80000000, "mxclose", M_CLOSE);
			scontrol(cp, M_CLOSE, 0);
			cp->c_flags |= WCLOSE;
		} else {
			chfree(cp);
/***/			trace(0x80000000, "mxclose", -1);
		}
		return;
	}

	if ((gp = getmpx(dev)) == NULL)
		return;
	ip = gp->g_inode;
	if (ip==NULL || (ip->i_mode&IFMT)!=IFMPC) {
		return;
	}

	for(fp=file; fp < fileNFILE; fp++) {
		if (fp->f_count && (fp->f_flag&FMP)==FMP && fp->f_inode==ip) {
			return;
		}
	}

	if (ip == mpxip) {
		mpxip = NULL;
		prele(ip);
	}

	for(i=0;i<NINDEX;i++)
		if ((cp=gp->g_chans[i])!=NULL)
			detach(cp);

/***/	groups[minor(dev)].g_inode = NULL;
	plock(ip);
	i = ip->i_mode;
	i &= ~IFMT;
	i |= IFCHR;
	ip->i_mode = i;
	zero((caddr_t)gp, sizeof (struct group));
	ip->i_flag |= IUPD|ICHG;
	iput(ip);
}

zero(s, cc)
register char *s;
register cc;
{
	while (cc--)
		*s++ = 0;
}

char	m_eot[] = {
#ifdef DEC
	M_EOT, 0, 0, 0
#else
	0, M_EOT, 0, 0
#endif
};

/*
 * Mxread + mxwrite are entered from cdevsw
 * for all read/write calls.  Operations on
 * an mpx file are handled here.
 * Calls are made through linesw to handle actual
 * data movement.
 */
mxread(dev)
{
register struct group *gp;
register struct chan *cp;
register esc;
struct rh h;
caddr_t	base;
unsigned count;
int	s, xfr, more, fmp;

	if ((gp=getmpx(dev))==NULL) {
bad:
		u.u_error = ENXIO;
		return;
	}
	FP = getf(u.u_arg[0]);
	fmp = FP->f_flag & FMP;
	if (fmp != FMP) {
		msread(fmp, FP->f_un.f_chan);
		return;
	}

	if ((int)u.u_base & 1)
		goto bad;
	s = spl6();
	while (gp->g_datq == 0) {

		sleep((caddr_t)&gp->g_datq, TTIPRI);
	}

/***/	trace(0x80000000, "mxread", u.u_count);
	while (gp->g_datq && u.u_count >= CNTLSIZ + 2) {
		splx(s);
		esc = 0;
		cp = nextcp(gp);
		if (cp==NULL) {
			continue;
		}
		h.index = cpx(cp);
		if (count = cp->c_ctlx.c_cc) {
			count += CNTLSIZ;
			if (cp->c_flags&NMBUF)
				count += nmsize;
			if (count > u.u_count) {
				sdata(cp);
				return;
			}
			esc++;
		}
		base = u.u_base;
		count = u.u_count;
		u.u_base += sizeof h;
		u.u_count -= sizeof h;
		xfr = u.u_count;
		if (esc && cp->c_flags&PORT) {
			more = mcread(cp);
		} else {
			more = (*linesw[cp->c_line].l_read)(cp->c_ttyp);
		}
		if (more > 0)
			sdata(cp);
		if (more < 0)
			scontrol(cp, M_CLOSE, 0);
		if (xfr == u.u_count) {
			esc++;
			iomove((caddr_t)m_eot, sizeof m_eot, B_READ);
		}

/***/		trace(0x80000000, "mxread-u", u.u_count);
/***/		trace(0x80000000, "mxread-x", xfr);
/***/		trace(0x80000000, "mxread-e", esc);
		xfr -= u.u_count;
		if (esc) {
			h.count = 0;
			h.ccount = xfr;
		} else {
			h.count = xfr;
			h.ccount = 0;
			mxrstrt(cp, &cp->cx.datq, BLOCK|ALT);
		}
		if (u.u_count && (xfr&1)) {
			u.u_base++;
			u.u_count--;
		}
		copyout((caddr_t)&h, base, sizeof h);

		s = spl6();
	}
}





mxwrite(dev)
{
register struct chan *cp;
struct	wh h;
struct group *gp;
int	ucount, esc, fmp, burpcount;
caddr_t	ubase, hbase;

	if ((gp=getmpx(dev))==NULL) {
		u.u_error = ENXIO;
		return;
	}
	FP = getf(u.u_arg[0]);
	fmp = FP->f_flag & FMP;
	if (fmp != FMP) {
		mswrite(fmp, FP->f_un.f_chan);
		return;
	}
	burpcount = 0;
	while (u.u_count >= sizeof h) {
		hbase = u.u_base;
		iomove((caddr_t)&h, sizeof h, B_WRITE);
		if (u.u_error)
			return;
		esc = 0;
		if (h.count==0) {
			esc++;
			h.count = h.ccount;
		}
		cp = xcp(gp, h.index);
		if (cp==NULL)  {
			continue;
		}
		ucount = u.u_count;
		ubase = u.u_base;
		u.u_count = h.count;
		u.u_base = h.data;

		if (esc==0) {
			struct tty *tp;
			caddr_t waddr;
			int line;

			if (cp->c_flags&PORT) {
				line = cp->c_line;
				tp = cp->c_ttyp;
			} else {
				line = cp->c_oline;
				tp = cp->c_ottyp;
			}
		loop:
			waddr = (caddr_t)(*linesw[line].l_write)(tp);
			if (u.u_count) {
				if (gp->g_state&ENAMSG) {
					burpcount++;
					cp->c_flags |= BLKMSG;
/*
					scontrol(cp, M_BLK, u.u_count);
*/
					h.ccount = -1;
					h.count = u.u_count;
					h.data = u.u_base;
					copyout((caddr_t)&h, hbase, sizeof h);
				} else {
					if (waddr==0) {
						u.u_error = ENXIO;
						return;
					}
					sleep(waddr, TTOPRI);
					goto loop;
				}
			}
		} else
			mxwcontrol(cp); 
		u.u_count = ucount;
		u.u_base = ubase;
	}
	u.u_count = burpcount;
}



/*
 * Mcread and mcwrite move data on an mpx file.
 * Transfer addr and length is controlled by mxread/mxwrite.
 * Kernel-to-Kernel and other special transfers are not
 * yet in.
 */
mcread(cp)
register struct chan *cp;
{
register struct clist *q;
register char *np;

int cc;

	q = (cp->c_ctlx.c_cc) ? &cp->c_ctlx : &cp->cx.datq;
	cc = mxmove(q, B_READ);
/***/	trace(0x80000000, "mcread", cc);

	if (cp->c_flags&NMBUF && q == &cp->c_ctlx) {
		np = mxnmbuf;
		while (nmsize--)
			passc(*np++);
		cp->c_flags &= ~NMBUF;
		prele(mpxip);
	}
	if (cp->c_flags&PORT)
		return(cp->c_ctlx.c_cc + cp->c_ttyp->t_rawq.c_cc); else
		return(cp->c_ctlx.c_cc + cp->cx.datq.c_cc);

}


char *
mcwrite(cp)
register struct chan *cp;
{
register struct clist *q;
register cc;
int	s;

	q = &cp->cy.datq;
/***/	trace(0x80000000, "mcwrite", u.u_count);
	while (u.u_count) {
		s = spl6();
		if (q->c_cc > HIQ || (cp->c_flags&EOTMARK)) {
			cp->c_flags |= SIGBLK;
			splx(s);
			break;
		}
		splx(s);
		cc = mxmove(q, B_WRITE);
	}
	wakeup((caddr_t)q);
	return((caddr_t)q);
}


/*
 * Msread and mswrite move bytes
 * between user and non-multiplexed channel.
 */
msread(fmp, cp)
register struct chan *cp;
{
register struct clist *q;
int s;

	q = (fmp&FMPX) ? &cp->cx.datq : &cp->cy.datq;
	s = spl6();
	while (q->c_cc == 0) {
		if (cp->c_flags & EOTMARK) {
			cp->c_flags &= ~EOTMARK;
			if (cp->c_flags&ENAMSG)
				scontrol(cp, M_UBLK, 0);
			else {
				wakeup((caddr_t)cp);
				wakeup((caddr_t)q);
			}
			goto out;
		}
		if (cp->c_flags&WCLOSE) {
			u.u_error = ENXIO;
			goto out;
		}
		sleep((caddr_t)q,TTIPRI);
	}
	splx(s);
/***/	trace(0x80000000, "mcread", q->c_cc);
	while (mxmove(q, B_READ) > 0)
		;
	mxrstrt(cp, q, SIGBLK);
	return;
out:
	splx(s);
}


mswrite(fmp, cp)
register struct chan *cp;
{
register struct clist *q;
register int cc;

	q = (fmp&FMPX) ? &cp->cy.datq : &cp->cx.datq;
/***/	trace(0x80000000, "mcwrite", u.u_count);
	while (u.u_count) {

		spl6();
		if (cp->c_flags&WCLOSE) {
		bad:
			signal(SIGPIPE, cp->c_pgrp);
			return;
		}

		while (q->c_cc>100) {
			if (cp->c_flags&WCLOSE)
				goto bad;
			sdata(cp);
			cp->c_flags |= BLOCK;
			sleep((caddr_t)q+1,TTOPRI);
		}
		spl0();
		cc = mxmove(q, B_WRITE);
		if (cc < 0)
			break;
	}
	if (fmp&FMPX) {
		if (cp->c_flags&YGRP) 
			sdata(cp); else
			wakeup((caddr_t)q);
	} else {
		if (cp->c_flags&XGRP) 
			sdata(cp); else
			wakeup((caddr_t)q);
	}
}



/*
 * move chars between clist and user space.
 */
mxmove(q, dir)
register struct clist *q;
register dir;
{
register cc;
char buf[HIQ];

	cc = MIN(u.u_count, sizeof buf);
/***/	trace(0x80000000, "mxmove", cc);
	if (dir == B_READ) 
		cc = q_to_b(q, buf, cc);
	if (cc <= 0)
		return(cc);
	iomove((caddr_t)buf, cc, dir);
	if (dir == B_WRITE)
		cc = b_to_q(buf, cc, q);
/***/	trace(0x80000000, "mxmove-r", cc);
	return(cc);
}



mxrstrt(cp, q, b)
register struct chan *cp;
register struct clist *q;
register b;
{
int s;

	s = spl6();
/***/	trace(0x80000000, "mxrstrt", 0);
	if (cp->c_flags&b && q->c_cc<LOQ) {
		cp->c_flags &= ~b;
		if (b&ALT)
			wakeup((caddr_t)q+1); else
			mcstart(cp, (caddr_t)q);
	}
	if (cp->c_flags&WFLUSH)
		wakeup((caddr_t)q+2);
	splx(s);
}



/*
 * called from driver start or xint routines
 * to wakeup output sleeper.
 */
mcstart(cp, q)
register struct chan *cp;
register caddr_t q;
{

	if (cp->c_flags&(BLKMSG)) {
		cp->c_flags &= ~BLKMSG;
		scontrol(cp, M_UBLK, 0);
	} else
		wakeup((caddr_t)q);
}


mxwcontrol(cp)
register struct chan *cp;
{
short	cmd[2];
int	s;

	iomove((caddr_t)cmd, sizeof cmd, B_WRITE);
	switch(cmd[0]) {
	/*
	 * not ready to queue this up yet.
	 */
	case M_EOT:
		s = spl6();
		while (cp->c_flags & EOTMARK)
			if (cp->c_flags&ENAMSG) {
				scontrol(cp, M_BLK, 0);
				goto out;
			} else
				sleep((caddr_t)cp, TTOPRI);
		cp->c_flags |= EOTMARK;
	out:
		splx(s);
		break;
	case M_IOCTL:
printf("M_IOCTL");
		break;
	default:
		u.u_error = ENXIO;
	}
}








mxioctl(dev, cmd, addr, flag)
caddr_t addr;
{
struct group *gp;
int fmp;
struct file *fp;

	if ((gp = getmpx(dev)) == NULL) {
bad:
		u.u_error = ENXIO;
		return;
	}

	fp = getf(u.u_arg[0]);
	if (fp==NULL)
		goto bad;

	fmp = fp->f_flag & FMP;

	if (fmp == FMP) {
		switch(cmd) {
		case MXLSTN:
			if (mpxip == NULL) {
				mpxip = gp->g_inode;
			} else
				goto bad;
			break;
		case MXNBLK:
			gp->g_state |= ENAMSG;
			break;
		default:
			goto bad;
		}
	} else {
		struct {
			short		cntl[2];
			struct ttiocb	iocb;
		} vec;
		register struct chan	*cp;
		register int		s;

		vec.cntl[0] = M_IOCTL;
		vec.cntl[1] = cmd;
		if (copyin(addr, (caddr_t)&vec.iocb, sizeof(vec.iocb))) {
			u.u_error = EFAULT;
			return;
		}
		cp = fp->f_un.f_chan;
		s = spl6();
		if (sdata(cp) == NULL) {
			splx(s);
			return;
		}
		b_to_q((caddr_t)&vec, sizeof(vec), &cp->c_ctlx);
		splx(s);
	}
}




chdrain(cp)
register struct chan *cp;
{
register struct tty *tp;
int wflag;

	chwake(cp);

	wflag = (cp->c_flags&WCLOSE)==0;
	tp = cp->c_ttyp;
	if (tp == NULL)		/* prob not required */
		return;
	if (cp->c_flags&PORT && tp->t_chan == cp) {
		cp->c_ttyp = NULL;
		tp->t_chan = NULL;
		return;
	}
	if (wflag) 
		wflush(cp,&cp->cx.datq); else
		flush(&cp->cx.datq);
	if (!(cp->c_flags&YGRP)) {
		flush(&cp->cy.datq);
	}
}

chwake(cp)
register struct chan *cp;
{
register char *p;

	wakeup((caddr_t)cp);
	flush(&cp->c_ctlx);
	p = (char *)&cp->cx.datq;
	wakeup((caddr_t)p); wakeup((caddr_t)++p); wakeup((caddr_t)++p);
	p = (char *)&cp->cy.datq;
	wakeup((caddr_t)p); wakeup((caddr_t)++p); wakeup((caddr_t)++p);
}


chfree(cp)
register struct chan *cp;
{
register struct group *gp;
register i;

	gp = cp->c_group;
	if (gp==NULL)
		return;
	i = cp->c_index;
	if (cp == gp->g_chans[i]) {
		gp->g_chans[i] = NULL;
	}
	cp->c_group = NULL;
}


flush(q)
register struct clist *q;
{

	while(q->c_cc)
		getc(q);
}


wflush(cp,q)
register struct chan *cp;
register struct clist *q;
{
register s;

	s = spl6();
	while(q->c_cc) {
		if (cp->c_flags & WCLOSE) {
			flush(q);
			goto out;
		}
		cp->c_flags |= WFLUSH;
		sdata(cp);
		sleep((caddr_t)q+2,TTOPRI);
	}
out:
	cp->c_flags &= ~WFLUSH;
	splx(s);
}


scontrol(cp,event,value)
register struct chan *cp;
short event,value;
{
register struct clist *q;
int s;

	trace(0x80000000, "scontrol", event);
	q = &cp->c_ctlx;
	s = spl6();
	if (sdata(cp) == NULL)
		return;
	putw(event,q);
	putw(value,q);
	splx(s);
}

sdata(cp)
register struct chan *cp;
{
register struct group *gp;
register short x;
register struct group *lgp;
int s;

	gp = cp->c_group;
	if (gp==NULL) {
		return(0);
	}
	x = cp->c_index;

	s = spl6();
	while (gp) {
		if ((gp->g_state&ISGRP)==0) {
			return(0);
		}
		gp->g_datq |= cmask[x];
		x = gp->g_index;
		lgp = gp;
		gp = gp->g_group;
	}
	gp =  lgp;
	splx(s);
	wakeup((caddr_t)&gp->g_datq);
	return((int)gp);
}



struct chan *
xcp(gp, x)
register struct group *gp;
register short x;
{
register i;

	i = 0;
/***/	while (i<NLEVELS && gp!=NULL && gp->g_state&ISGRP) {
		gp = (struct group *)gp->g_chans[x&017];
		x >>= 4;
		if ((x&017) >= NINDEX)
			break;
		i++;
	}
	return((struct chan *)gp);
}

cpx(cp)
register struct chan *cp;
{
register x;
register struct group *gp;

	if (cp==NULL)
		return(-1);
	x = (-1<<4) + cp->c_index;
	gp = cp->c_group;
	if (gp==NULL || (gp->g_state&ISGRP)==0)
		return(-1);
	gp = gp->g_group;
	while (gp && gp->g_state&ISGRP) {
		x <<= 4;
		x |= gp->g_index;
		gp = gp->g_group;
	}
	return(x);
}



struct chan *
nextcp(gp)
register struct group *gp;
{

	if (gp->g_datq == 0) {
		gp = NULL;
		goto out;
	}

	while (gp != NULL && gp->g_state&ISGRP) {
		while ( (gp->g_datq & gp->g_rotmask) == 0) {
			gp->g_rot++;
			gp->g_rot &= 017;
			if (gp->g_rot)
				gp->g_rotmask <<= 1; else
				gp->g_rotmask = 1;
		}
		gp = (struct group *)gp->g_chans[gp->g_rot];
	}
	if (gp)
		rmdata(gp);
out:
	return((struct chan *)gp);
}

rmdata(cp)
register struct chan *cp;
{
register struct group *gp;
register short x;

	gp = cp->c_group;
	x = cp->c_index;

	while (gp) {
		gp->g_datq &= ~cmask[x];
		if (gp->g_datq)
			return;
		x = gp->g_index;
		gp = gp->g_group;
	}
}





mcrint(c, tp)
struct tty *tp;
{
}

mcxint(tp)
struct tty *tp;
{
}
/*
prstuff(s,cc)
register char *s;
register cc;
{
	while (cc--)
		printf("%o ",*s++&0377);
}

prascii(s, cc)
register char *s;
register cc;
{
register c;
	while (cc--) {
		c = *s++;
		if (c>=040 && c<=0176)
			putchar(c); else
			printf(" %o ", c&0377);
	}
}
*/
