/*
 *	SCCS id	@(#)mx2.c	2.1 (Berkeley)	8/5/83
 */

#include "param.h"
#include <sys/systm.h>
#include <sys/dir.h>
#include <sys/user.h>
#include <sys/proc.h>
#include <sys/tty.h>
#include <sys/inode.h>
#include <sys/mx.h>
#include <sys/file.h>
#include <sys/conf.h>
#include <sys/buf.h>

/*
 * multiplexor driver
 */
struct	chan	chans[NCHANS];
struct	group	*groups[NGROUPS];
int	mpxline;

short	cmask[16]	={
	01,	02,	04,
	010,	020,	040,
	0100,	0200,	0400,
	01000,	02000,	04000,
	010000,	020000,	040000, 0100000
};

#define	IOMOVE	iomove
struct chan *xcp(),*addch(),*nextcp();

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

char mcdebugs[NDEBUGS];

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

	d = minor(dev);
	if (d >= NGROUPS) {
		u.u_error = ENXIO;
		return(NULL);
	}
	return(groups[d]);
}


/*ARGSUSED*/
mxopen(dev, flag)
{
	register struct group *gp;
	register struct file *fp;
	register struct chan *cp;
	int	msg;

	if ((gp=getmpx(dev)) == NULL) {
		return;
	}
	if (!(gp->g_state&INUSE)) {
		u.u_error = ENXIO;
		return;
	}
	fp = u.u_ofile[u.u_r.r_val1];
	if (fp->f_inode != gp->g_inode) {
		u.u_error = ENXIO;
		return;
	}
	if ((cp=addch(gp->g_inode,0)) == NULL) {
		u.u_error = ENXIO;
		return;
	}

	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;

	scontrol(cp, msg+(cp->c_index<<8), u.u_uid);
	sleep((caddr_t)cp,TTIPRI);
	if (cp->c_flags&NMBUF)
		prele(mpxip);
	if (cp->c_flags & WCLOSE) {
		chdrain(cp);
		chfree(cp);
		u.u_error = ENXIO;
		return;
	}
	cp->c_fy = fp;
	cp->c_pgrp = u.u_procp->p_pgrp;
}


char	mxnmbuf[NMSIZE];
int	nmsize;

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, fp)
dev_t	dev;
register struct file *fp;
{
register struct group *gp;
register struct inode *ip;
register struct chan *cp = { fp->f_un.f_chan };
int	i, fmp;

	fmp = flag&FMP;
	if ((gp=getmpx(dev)) == NULL)
		return;

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

	/*
	 * 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) {
			scontrol(cp, M_CLOSE, 0);
			cp->c_flags |= WCLOSE;
		} else {
			chfree(cp);
		}
		goto out;
	}


	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++)
		(void) detach(gp->g_chans[i]);

out:
	if (ip->i_count == 1) {
		groups[minor(dev)] = NULL;
		plock(ip);
		zero((caddr_t)gp, sizeof (struct group));
		ip->i_mode = IFREG + 0666;
		ip->i_un.i_rdev = 0;
		ip->i_flag |= IUPD|ICHG;
		iput(ip);
	}
}

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

char	m_eot[] ={ M_EOT, 0, 0, 0};

/*
 * 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 || (FP=getf(u.u_arg[0]))==NULL) {
		u.u_error = ENXIO;
		return;
	}

	fmp = FP->f_flag & FMP;
	if (fmp != FMP) {
		if (u.u_count == 0)
			return;
		msread(fmp, FP->f_un.f_chan);
		return;
	}

	if ((int)u.u_base & 1) {
		u.u_error = ENXIO;
		return;
	}

	s = spl6();
	if (u.u_count == 0)
	{
		if (gp->g_datq == 0)
			u.u_error = ENXIO;
		splx(s);
		return;
	}
	while (gp->g_datq == 0) {
		sleep((caddr_t)&gp->g_datq, TTIPRI);
	}
	splx(s);

	while (gp->g_datq && u.u_count >= CNTLSIZ + 2) {
		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) {
				(void) 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) {
			more = mcread(cp);
		} else {
			more = (*linesw[cp->c_line].l_read)(cp->c_ttyp);
		}
		if (more > 0)
			(void) sdata(cp);
		if (more < 0)
			scontrol(cp, M_CLOSE, 0);
		(void) _spl0();
		if (xfr == u.u_count) {
			esc++;
			IOMOVE((caddr_t)m_eot, sizeof m_eot, B_READ);
		}
		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--;
		}
		(void) copyout((caddr_t)&h, base, sizeof h);

	}
}


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 || (FP=getf(u.u_arg[0]))==NULL) {
		return;
	}
	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 || cp->c_flags&ISGRP) {
			u.u_error = ENXIO;
			return;
		}
		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;
					(void) 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;


	q = (cp->c_ctlx.c_cc) ? &cp->c_ctlx : &cp->cx.datq;
	(void) mxmove(q, B_READ);

	if (cp->c_flags&NMBUF && q == &cp->c_ctlx) {
		np = mxnmbuf;
		while (nmsize--)
			(void) 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);

}


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

	q = &cp->cy.datq;
	while (u.u_count) {
		s = spl6();
		if (q->c_cc > HIQ || (cp->c_flags&EOTMARK)) {
			cp->c_flags |= SIGBLK;
			splx(s);
			break;
		}
		splx(s);
		(void) 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&WCLOSE) {
			u.u_error = ENXIO;
			goto out;
		}
		if (cp->c_flags & EOTMARK) {
			cp->c_flags &= ~EOTMARK;
			if(msgenab(cp))
				scontrol(cp, M_UBLK, 0);
			else {
				wakeup((caddr_t)cp);
				wakeup((caddr_t)q);
			}
			goto out;
		}
		sleep((caddr_t)q,TTIPRI);
	}
	if (cp->c_flags&WCLOSE) {
		u.u_error = ENXIO;
		goto out;
	}
	splx(s);
	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;
	while (u.u_count) {
		(void) _spl6();
		if (cp->c_flags&WCLOSE) {
			gsignal(cp->c_pgrp, SIGPIPE);
			(void) _spl0();
			return;
		}
		if (q->c_cc>= HIQ || cp->c_flags&FBLOCK) {
			if (cp->c_flags&WCLOSE) {
				gsignal(cp->c_pgrp, SIGPIPE);
				(void) _spl0();
				return;
			}
			(void) sdata(cp);
			cp->c_flags |= BLOCK;
			sleep((caddr_t)q+1,TTOPRI);
			(void) _spl0();
			continue;
		}
		(void) _spl0();
		cc = mxmove(q, B_WRITE);
		if (cc < 0)
			break;
	}
	if (fmp&FMPX) {
		if (cp->c_flags&YGRP)  (void) sdata(cp);
		else		       wakeup((caddr_t)q);
	} else {
		if (cp->c_flags&XGRP)  (void) 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 cbuf[HIQ];

	cc = MIN(u.u_count, sizeof cbuf);
	if (dir == B_READ) 
		cc = q_to_b(q, cbuf, cc);
	if (cc <= 0)
		return(cc);
	IOMOVE((caddr_t)cbuf, (unsigned)cc, dir);
	if (dir == B_WRITE)
		cc = b_to_q(cbuf, cc, q);
	return(cc);
}



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

	s = spl6();
	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;
struct sgttyb vec;
int	s;

	IOMOVE((caddr_t)&cmd, sizeof cmd, B_WRITE);
	if (u.u_error)
		return;
	switch(cmd) {
	/*
	 * not ready to queue this up yet.
	 */
	case M_EOT:
		s = spl6();
		while (cp->c_flags & EOTMARK)
			if(msgenab(cp)){
				scontrol(cp, M_BLK, 0);
				goto out;
			} else
				sleep((caddr_t)cp, TTOPRI);
				cp->c_flags |= EOTMARK;
	out:
		wakeup((caddr_t)&cp->cy.datq);
		splx(s);
		break;
	case M_IOCTL:
		break;
	case M_IOANS:
		if (cp->c_flags&SIOCTL) {
			IOMOVE((caddr_t)&vec, sizeof vec, B_WRITE);
			(void) b_to_q((caddr_t)&vec, sizeof vec, &cp->c_ctly);
			cp->c_flags &= ~SIOCTL;
			wakeup((caddr_t)cp);
		}
		break;
	case M_BLK:
		cp->c_flags |= FBLOCK;
		break;
	case M_UBLK:
		cp->c_flags &= ~FBLOCK;
		chwake(cp);
		break;
	default:
		u.u_error = ENXIO;
	}
}



/*ARGSUSED*/
mxioctl(dev, cmd, addr, flag)
caddr_t addr;
{
struct group *gp;
int fmp;
struct file *fp;
struct {
	short c_ctl;
	short c_cmd;
	struct sgttyb c_vec;
} ctlbuf;

	if ((gp=getmpx(dev))==NULL || (fp=getf(u.u_arg[0]))==NULL) {
		return;
	}

	fmp = fp->f_flag & FMP;
	if (fmp == FMP) {
		switch(cmd) {

		case MXLSTN:
			if (mpxip == NULL) {
				mpxip = gp->g_inode;
			} else {
				u.u_error = ENXIO;
				return;
			}
			break;

		case MXNBLK:
			gp->g_state |= ENAMSG;
			break;

		default:
			u.u_error = ENXIO;
			return;
		}
	} else {
		ctlbuf.c_ctl = M_IOCTL;
		ctlbuf.c_cmd = cmd;
		(void) copyin(addr, (caddr_t)&ctlbuf.c_vec, sizeof (struct sgttyb));
		sioctl(fp->f_un.f_chan, (char *)&ctlbuf, sizeof ctlbuf);
		(void) copyout((caddr_t)&ctlbuf, addr, sizeof (struct sgttyb));
	}
}


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;
	wakeup((caddr_t)gp);
}


flush(q)
register struct clist *q;
{

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


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

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


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

	q = &cp->c_ctlx;
	s = spl6();
	if (sdata(cp) == NULL)
		return;
	(void) putw(event,q);
	(void) putw(value,q);
	splx(s);
}



sioctl(cp, vec, cc)
register struct chan *cp;
char *vec;
{
register s;
register struct clist *q;

	s = spl6();
	q = &cp->cx.datq;
	while (q->c_cc) {
		cp->c_flags |= BLOCK;
		if (sdata(cp)==NULL) {
			u.u_error = ENXIO;
			return;
		}
		sleep((caddr_t)q+1, TTOPRI);
	}
	(void) b_to_q(vec, cc, &cp->c_ctlx);
	cp->c_flags |= SIOCTL;
	while (cp->c_flags&SIOCTL) {
		if (cp->c_ctlx.c_cc)
			if (sdata(cp)==NULL) {
				u.u_error = ENXIO;
				return;
			}
		sleep((caddr_t)cp, TTOPRI);
	}
	(void) q_to_b(&cp->c_ctly, vec, cp->c_ctly.c_cc);
	splx(s);
}

sdata(cp)
struct chan *cp;
{
	register struct group *gp = (struct group *)cp;
	register struct group *ngp;
	register int	s;

	ngp = gp->g_group;
	if (ngp==NULL || (ngp->g_state&ISGRP)==0)
		return(NULL);

	s = spl6();
	do {
		ngp->g_datq |= cmask[gp->g_index];
		wakeup((caddr_t)&ngp->g_datq);
		gp = ngp;
	} while(ngp=ngp->g_group);
	splx(s);
	return((int)gp);
}



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

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

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

	x = (-1<<4) + cp->c_index;
	gp = cp->c_group;
	while (gp->g_group) {
		x <<= 4;
		x |= gp->g_index;
		gp = gp->g_group;
	}
	return(x);
}


struct chan *
nextcp(gp)
register struct group *gp;
{
	register struct group *lgp, *ngp;

	do {
		while ((gp->g_datq & cmask[gp->g_rot]) == 0) {
			gp->g_rot = (gp->g_rot+1)%NINDEX;
		}
		lgp = gp;
		gp = (struct group *)gp->g_chans[gp->g_rot];
	} while (gp!=NULL && gp->g_state&ISGRP);

	lgp->g_datq &= ~cmask[lgp->g_rot];
	lgp->g_rot  =  (lgp->g_rot+1)%NINDEX;

	while (ngp=lgp->g_group) {
		ngp->g_datq &= ~cmask[lgp->g_index];
		if (ngp->g_datq)
			break;
		lgp = ngp;
	}
	return((struct chan *)gp);
}



msgenab(cp)
register struct chan *cp;
{
	register struct group *gp;

	for(gp=cp->c_group;gp;gp=gp->g_group)
		if(gp->g_state & ENAMSG)return(1);
	return(0);
}
