#include	"../h/param.h"
#include	"../h/systm.h"
#include	"../h/dir.h"
#include	"../h/user.h"
#include	"../h/tty.h"
#include	"../h/buf.h"
#include	"../h/inode.h"
#include	"../h/file.h"
#include	"../h/mxchan.h"
#include	"../h/mx.h"
#include	"../h/conf.h"
#include	"../h/msg.h"


extern		struct	mxchan	*getchan();
extern		int		mxopen();


/*
 *   Multiplexed file driver.
 */

#define		INRANGE(x)	(x >= 0  &&  x <NMXCHAN)


int	nmxf	=	 0;			/* mx files existing */
int	mxdev	=	-1;			/* major dev number */
char	mxmbuf[4*1024];				/* Message storage */
struct	buddy	*mxarena = &mxmbuf[0];

extern	struct	mxf	mxf[ ];			/* mx file table */
extern	int	mxunit;				/* max number of mx files */
struct	mxchan	mxchan[NCHAN];			/* chan table */


/*
 *   MPX system call
 *	Used to create a multiplexed file (char dev 9, *),
 *	attach processes to a mx file and detach channels
 *	(and hence processes) from a mx file.
 *	Minor device mxunit is special - it means that this
 *	multiplexed file has no owner process.
 */
mxsys()
{
	register int mxn;		/* mx file number */
	register struct mxf *mxp;	/* mx file pointer */
	register struct inode *ip;	/* inode of created mxfile */
	register struct mxchan *chp;	/* chan pointer */
	struct	a	{		/* sys call args */
		int	fdes;		/* fd of mx file */
		int	fcmd;		/* op to perform */
		int	find;		/* index chan #  */
		char	*file;		/* file name arg */
		}	*uap;


	uap = (struct a *) u.u_ap;
	if (!mxsetup())
		return;

	trace(0x40000000, "mxsys", uap->fcmd);
	switch (uap->fcmd)  {

			/* non command */
		default:
			u.u_error = ENXIO;
			break;

			/* make a new mxfile */
		case MXSCREATE:
			u.u_dirp = uap->file;
			ip = namei(uchar, 1);
			if (ip != NULL)  {
				iput(ip);
				u.u_error = EEXIST;
				}

			if (u.u_error)
				break;

			if ((mxn = mxalloc()) < 0)  {
				u.u_error = ENXIO;
				break;
				}

			ip = maknode((uap->find & 0777) | IFCHR);
			if (ip == NULL)  {
				u.u_error = EPERM;
				return;
				}

			mxp = &mxf[mxn];
			mxp->mx_ip = ip;
			ip->i_un.i_rdev = makedev(mxdev, mxn);

			/* open1() will call mxopen() to do the work */
			open1(ip, FREAD|FWRITE, 2);
			if (u.u_error)  {
				mxfree(mxp);
				break;
				}

			ip->i_count++;		/* allow mxclose the final iput */
			ip->i_flag |= (IACC|IUPD|ICHG);
			return;


			/* allow attach to proceed */
		case MXSATTACH:
			chp = getchan(uap->fdes);
			if (!chp)
				return;

			if (chp->ch_chan)  {
				/* not owner */
				u.u_error = ENXIO;
				break;
				}

			if (!INRANGE(uap->find))  {
				/* chan number out of range */
				u.u_error = EMFILE;
				break;
				}

			if (!(chp->ch_mxp->mx_chan[uap->find]))  {
				/* chan not used */
				u.u_error = ENFILE;
				break;
				}

			mxwakeup(chp->ch_mxp->mx_chan[uap->find]);
			return;


			/* detach & de-allocate chan */
		case MXSDETACH:
			chp = getchan(uap->fdes);
			if (!chp)
				/* not using mx files */
				return;

			if (chp->ch_chan)  {
				/* not owner */
				u.u_error = ENXIO;
				break;
				}

			mxp = chp->ch_mxp;
			if (!INRANGE(uap->find))  {
				/* chan out of range */
				u.u_error = EMFILE;
				break;
				}

			chp = mxp->mx_chan[uap->find];
			if (!chp)  {
				/* chan not used */
				u.u_error = ENFILE;
				break;
				}

			if (!chp->ch_chan)  {
				/* detach on owner not allowed */
				u.u_error = ENXIO;
				break;
				}

			if (chp->ch_flags & MXCLOSED)
				/*
				 * this is in response to a closed chan,
				 *   in answer to a detach msg.  the chan
				 *   is waiting to be freed & can be now.
				 */
				mxfrchan(chp);
			else  {
				/*
				 * mark the chan as now closed.
				 *   this is in response to a detach() without
				 *   a close.  when woken up, the user will
				 *   close the chan.
				 */
				chp->ch_flags |= (MXERROR);
				mxwakeup(chp);
				}

			return;
		}

	/*
	 *   Error  --  return(-errno).
	 */
	u.u_error = -u.u_error;
	return;
}


/*
 *   setup -
 *	Done once only.  Used to find the major device number
 *	associated with this driver, so that the MXSCREATE
 *	system call can make character special device nodes
 *	for other than the super-user.
 */
mxsetup()
{
	register int i;

	if (mxdev >= 0)
		/* already done */
		return;

	/* message storage system */
	msginit(mxarena, sizeof mxmbuf);

	/* find the major device number of the driver */
	for (i=0; cdevsw[i].d_open; i++)
		if (cdevsw[i].d_open == mxopen)  {
			mxdev = i;
			return(1);
			}

	u.u_error = EIO;
	return(0);
}


/*
 *   mxalloc -
 *	Allocate one mxf structure.
 */
mxalloc()
{
	register struct mxf *mxp;
	register int i;
	register int j;
	register int s;

	s = spl6();
	if (nmxf >= mxunit)  {
		splx(s);
		return(-1);
		}

	nmxf++;
	for (i=0,mxp=(&mxf[0]);  i<mxunit;  i++,mxp++)
		if (mxp->mx_flags == NULL)  {
			mxp->mx_flags = MXALLOC;
			splx(s);
			mxp->mx_nchan = 0;
			for (j=0; j<NMXCHAN; j++)
				mxp->mx_chan[j] = NULL;
			trace(0x40000000, "mxalloc", i);
			return(i);
			}

	/* table did have at least one slot ... */
	panic("no mxfs");
}



/*
 *   mxfree -
 *	Free mxf structure.
 */
mxfree(mxp)
register struct mxf *mxp;
{
	register int s;

	if (mxp->mx_ip)
		iput(mxp->mx_ip);

	s = spl6();
	mxp->mx_nchan = 0;
	mxp->mx_flags = NULL;
	mxp->mx_ip    = NULL;
	nmxf--;
	splx(s);
}


/*
 *   chanalloc -
 *	Allocate a channel structure from the table,
 *	mark as allocated, and point back to the
 *	controlling mxf structure.
 */
struct mxpchan	*chanalloc(mxp)
register struct mxf *mxp;
{
	register struct mxchan *chp;
	register int i;
	register int s;

	trace(0x40000000, "chanalloc", 0);
	s = spl6();
	for (i=0,chp=(&mxchan[0]);  i<NCHAN;  i++,chp++)
		if (chp->ch_flags == NULL)  {
			/* found a free entry */
			chp->ch_flags = MXALLOC;
			splx(s);
			chp->ch_mxp  = mxp;
			chp->ch_qtot = 0;
			chp->ch_head = NULL;
			chp->ch_tail = NULL;
			return(chp);
			}

	/* no chans free */
	splx(s);
	return(NULL);
}


/*
 *   mxalchan -
 *	Find an empty chan slot in the mxf structure,
 *	set up the pointer and info in the chan struct.
 */
struct	mxchan	*mxalchan(mxp)
register struct mxf *mxp;
{
	register struct mxchan *chp;
	register int i;
	register int s;

	trace(0x40000000, "mxalchan", 0);
	chp = chanalloc(mxp);
	if (!chp)
		/* no chans */
		return(NULL);

	s = spl6();
	if (mxp->mx_nchan >= NMXCHAN)  {
		/* no chans left in mxf structure */
		splx(s);
		chp->ch_flags = NULL;
		return(NULL);
		}

	for (i=0; i<NMXCHAN; i++)
		if (mxp->mx_chan[i] == NULL)  {
			/* found a slot */
			mxp->mx_chan[i] = chp;
			chp->ch_chan    = i;
			mxp->mx_nchan++;
			splx(s);
			return(chp);
			}

	/* there was at least one free entry ... */
	panic("chan table full");
}


/*
 *   Add message to channel queue
 */
mxputq(chp, q)
register struct mxchan *chp;
register struct mxmsg *q;
{
	trace(0x80000000, "mxputq", chp->ch_chan);
	q->m_link = NULL;
	if (!chp->ch_qtot)
		/* none queued yet - set up head/tail */
		chp->ch_head = chp->ch_tail = q;
	else  {
		/* add to end of tail */
		chp->ch_tail->m_link = q;
		chp->ch_tail = q;
		}

	chp->ch_qtot++;
	return;
}


/*
 *   Remove message from channel queue
 */
struct	mxmsg	*mxgetq(chp)
register struct mxchan *chp;
{
	register struct mxmsg *q;

	trace(0x80000000, "mxgetq", chp->ch_chan);
	if (!chp->ch_qtot)
		/* no messages!!!! */
		panic("no mxmsg");

	q = chp->ch_head;
	chp->ch_head = q->m_link;
	chp->ch_qtot--;
	if (!chp->ch_qtot)
		/* empty queue - reset tail */
		chp->ch_tail = NULL;

	return(q);
}


/*
 *   Add message onto head of queue - will be the first to come off
 */
mxaddq(chp, q)
register struct mxchan *chp;
register struct mxmsg  *q;
{
	trace(0x80000000, "mxaddq", chp->ch_chan);

	if (!chp->ch_qtot)  {
		chp->ch_head = chp->ch_tail = q;
		q->m_link = NULL;
		}
	else  {
		q->m_link = chp->ch_head;
		chp->ch_head = q;
		}

	chp->ch_qtot++;
	return;
}


/*
 *   mxopen -
 *	If the open is the first on the file, (mxp->mx_nchan == 0),
 *	then the process opening is the owner, and has special
 *	format i/o generated by the driver.  The owner must also
 *	receive and handle driver control messages.
 *
 *	Open1() has already allocated a file desc (u.u_r.r_val1).
 */
mxopen(dev)
dev_t	dev;
{
	register struct mxchan *chp;
	register struct file *fp;

	trace(0x40000000, "mxopen", minor(dev));
	if (DEAD(dev))  {
		/* owner has died */
		u.u_error = EPERM;
		return;
		}

	fp = getf(u.u_r.r_val1);
	if (!fp)  {
		u.u_error = ENFILE;
		return;
		}

	chp = mxalchan(&mxf[minor(dev)]);
	if (!chp)  {
		u.u_error = EMFILE;
		return;
		}

	/* indicate an open mx file */
	fp->f_un.f_chan = chp;
	fp->f_flag     |= FMX;
	chp->ch_fp      = fp;

	if (chp->ch_chan)  {
		/* user: wait for owner to attach this chan */
		ctrlmsg(chp->ch_mxp->mx_chan[0], M_WATCH, chp->ch_chan);
		mxsleep(chp);

		if (u.u_error)  {
			/* owner has died/detached the chan */
			chp->ch_flags |= MXCLOSED;
			closef(fp);
			}
		}

	return;
}


/*
 *   mxclose -
 *	Indicate that the channel has been closed.  When the mx
 *	file was made, the inode ref count was incremented, so
 *	that we get a chance to fudge the inode after the last
 *	closef, which would have sync'd the inode out to disc.
 */
mxclose(dev, flag, chp)
dev_t	dev;
int	flag;
register struct mxchan *chp;
{
	register struct mxchan *own;
	register struct mxf *mxp;
	register int s;

	trace(0x40000000, "mxclose", dev);
	if (chp->ch_chan)  {
		/* not owner */
		own = chp->ch_mxp->mx_chan[0];
		if (!own  ||  (chp->ch_flags & MXCLOSED))  {
			/* no owner exists || don't inform owner */
			mxfrchan(chp);
			return;
			}

		/* owner exists - send control message */
		chp->ch_flags |= MXCLOSED;
		ctrlmsg(own, M_CLOSE, chp->ch_chan);

		return;
		}

	/*
	 *   Owner closed -- make mx file an illegal device,
	 *   so that no more read/write/open/close/ioctl's
	 *   are allowed.
	 */
	mxp = chp->ch_mxp;
	s = spl6();
	mxp->mx_ip->i_un.i_rdev = makedev(mxdev, mxunit);
	mxp->mx_ip->i_flag     |= (IACC|IUPD|ICHG);
	splx(s);
	iput(mxp->mx_ip);
	mxdetach(chp);
}


/*
 *   mxdetach -
 *	Detaching owner - indicate no more i/o.
 *	Decrement ref count on mxf structure,
 *	and free the channel.
 */
mxdetach(chp)
register struct mxchan *chp;
{
	register struct mxchan *cp;
	register struct mxf *mxp;
	register int i;

	chp->ch_flags |= MXERROR;
	mxp = chp->ch_mxp;
	mxfrchan(chp);
	for (i=1; i<NMXCHAN; i++)  {
		/* wakeup each channel */
		cp = mxp->mx_chan[i];
		if (!(cp->ch_flags & MXALLOC))
			/* chan not used */
			continue;

		cp->ch_flags |= MXERROR;
		if (cp->ch_flags & MXSLEEP)
			/* someone sleeping */
			mxwakeup(cp);
		}
}



/*
 *   mxread -
 *	Move data from a chan queue to the process doing the read.
 *	Only this process can be sleeping on it's i/o channel - these
 *	are one-user-only i/o devices.
 *	Grave sync errors will occur if more than one process (child/parent
 *	relationship) tries to read from the same chan.
 *	Headers are transferred to the owner only.
 *	The headers are already on the queue. A read that
 *	is shorter than the message on the queue gets part of the
 *	message, and the rest looks like a data message.
 */
mxread(dev)
dev_t	dev;
{
	register struct mxchan *chp;
	register struct mxf *mxp;
	register struct mxmsg *q;
	register int	s;
	register int	len, mov;

	trace(0x40000000, "mxread", u.u_count);
	if (DEAD(dev))  {
		/* owner gone */
		u.u_error = EPERM;
		return;
		}

	chp = getchan(u.u_arg[0]);
	if (chp->ch_flags & MXERROR)  {
		/* further i/o not allowed */
		u.u_error = EIO;
		return;
		}

	if (!chp->ch_chan  &&  u.u_count < sizeof (struct hdr))
		/* owner && read is too small */
		return;

	if (chp->ch_chan  &&  u.u_count <= 0)
		/* user && read too small */
		return;

	/* wait for data to appear on the channel */
	while (!chp->ch_qtot)
		/* nothing queued yet */
		if (!mxsleep(chp))
			return;

	s = spl6();
	q = mxgetq(chp);

	if (q->m_h.hd_msg == M_EOF)  {
		/* signal end-of-file */
		splx(s);
		msgfree(mxarena, q);
		mxwakeup(chp);
		return;
		}

	if (!chp->ch_chan)  {
		/* owner - transfer header */
		iomove((caddr_t) &q->m_h, sizeof (struct hdr), B_READ);
		if (u.u_error)  {
			trace(0x800000000, "read hdr err", q->m_h.hd_index);
			splx(s);
			msgfree(mxarena, q);
			mxwakeup(chp);
			return;
			}
		}


	/*
	 *   If data in msg buffer <= amount wanted,
	 *   transfer it and satisfy the read.
	 *   If not, give the reader as much as is
	 *   wanted, and make a new message buffer
	 *   with the remainder of the data and link it
	 *   onto the head of the queue.
	 */
	len = q->m_h.hd_length;
	mov = (len <= u.u_count  ?  len : u.u_count);
	iomove((caddr_t) &q->m_data[0], mov, B_READ);
	len -= mov;

	if (u.u_error)
		trace(0x80000000, "read error", q->m_h.hd_length);

	if (len  &&  !u.u_error)  {
		/* data remains to be transferred */
		register struct mxmsg *r;

		trace(0x80000000, "short read", len);
		r = msgblock(mxarena, len + sizeof (struct mxmsg));
		r->m_h.hd_index  = q->m_h.hd_index;
		r->m_h.hd_msg    = M_DATA;
		r->m_h.hd_length = len;
		mxcopy(&q->m_data[mov], &r->m_data[0], len);
		mxaddq(chp, r);
		}

	/* wakeup writer waiting for chan to drain */
	splx(s);
	msgfree(mxarena, q);
	mxwakeup(chp);
	return;
}


/*
 *   mxwrite -
 *	Transfer data from process space to a channel queue.
 */
mxwrite(dev)
dev_t	dev;
{
	register struct mxchan *chp;
	register struct mxf *mxp;
	register struct mxmsg *q;
	struct	 hdr	h;
	register int	s;
	register int	wrindex;

	trace(0x40000000, "mxwrite", minor(dev));
	if (DEAD(dev))  {
		/* owner gone */
		u.u_error = EPERM;
		return;
		}

	if (u.u_count < 0  ||  u.u_count > MXMAXMSG)  {
		/* write - too small || too large */
		u.u_error = ENXIO;
		return;
		}

	chp = getchan(u.u_arg[0]);
	if (chp->ch_flags & MXERROR)  {
		/* no further i/o allowed */
		u.u_error = EIO;
		return;
		}


	/*
	 *   Destination chan - if user writing, then to owner,
	 *   if owner writing, then user index is part of data.
	 *   Record index of writing process (wrindex).
	 */
	wrindex = chp->ch_chan;
	if (!wrindex)  {
		/* owner - fetch header */
		register int ind;

		iomove((caddr_t) &h, sizeof (struct hdr), B_WRITE);
		ind = h.hd_index;
		if (!INRANGE(ind)  ||  !ind)  {
			/* illegal chan number */
			u.u_error = ENXIO;
			return;
			}

		chp = chp->ch_mxp->mx_chan[ind];
		}
	else  {
		/* user - find owners chan */
		chp = chp->ch_mxp->mx_chan[0];
		h.hd_index  = 0;		/* owner chan number */
		h.hd_msg    = M_DATA;		/* normal user data */
		h.hd_length = u.u_count;	/* size of message */
		}

	if (!chp)  {
		/* destination chan gone */
		u.u_error = ENXIO;
		return;
		}

	if (chp->ch_flags & (MXERROR | MXCLOSED))  {
		/* destination chan closed or err'd */
		u.u_error = ENXIO;
		return;
		}


	/* check message request */
	switch (h.hd_msg)  {
		case M_EOF:
			ctrlmsg(chp, h.hd_msg, wrindex);
			return;

		case M_DATA:
			if (!u.u_count)  {
				/* implied eof */
				ctrlmsg(chp, M_EOF, wrindex);
				return;
				}
			break;

		default:
			u.u_error = EIO;
			return;
		}

	/*
	 *   Wait for the channel to drain.  This is the one
	 *   case when a process can sleep on another proc's
	 *   mx io channel.
	 */
	while (chp->ch_qtot >= MXTHRESH)
		if (!mxsleep(chp))
			/* err'd */
			return;

	s = spl6();
	q = msgblock(mxarena, h.hd_length + sizeof (struct mxmsg));
	q->m_h.hd_index  = wrindex;
	q->m_h.hd_msg    = M_DATA;
	q->m_h.hd_length = u.u_count;

	iomove((caddr_t) &q->m_data[0], u.u_count, B_WRITE);
	if (u.u_error)  {
		trace(0x80000000, "mx write err", u.u_count);
		q->m_h.hd_length -= u.u_count;
		}

	mxputq(chp, q);

	/* inform any waiting reader that data is here */
	splx(s);
	mxwakeup(chp);
	return;
}


/*
 *   mxioctl -
 *	I/o control on mx files.
 */
mxioctl(dev)
dev_t	dev;
{
	trace(0x40000000, "mxioctl", minor(dev));
	if (DEAD(dev))  {
		/* owner gone */
		u.u_error = EPERM;
		return;
		}

	/* send ioctl parameter structure to owner */
}


/*
 *   ctrlmsg -
 *	Send the message `msg' to the owner process on channel `chp'
 *	and indicate that the message came from channel index `index'.
 *	Only the message M_EOF can be sent to chans other than 0 (owner).
 *	All other messages are restricted to the owner only.
 */
ctrlmsg(chp, msg, index)
register struct	mxchan *chp;
int	 msg;
int	 index;
{
	register struct mxmsg *q;
	register int s;

	trace(0x40000000, "ctrlmsg", msg);
	if (chp->ch_chan  &&  msg != M_EOF)  {
		/* destination not valid */
		u.u_error = EIO;
		return;
		}

	q = msgblock(mxarena, sizeof (struct mxmsg));
	s = spl6();
	if (chp->ch_flags & MXERROR)  {
		/* no further i/o allowed */
		u.u_error = ENXIO;
		msgfree(mxarena, q);
		splx(s);
		return;
		}

	q->m_h.hd_index  = index;
	q->m_h.hd_msg    = msg;
	q->m_h.hd_length = 0;

	mxputq(chp, q);

	/* message arrived - wake up reader */
	splx(s);
	mxwakeup(chp);
	return;
}


/*
 *   getchan -
 *	Returns pointer to the chan table structure associated
 *	with the file descriptor `fd'.
 */
struct	mxchan	*getchan(fd)
register int fd;
{
	register struct chan *chp;
	register struct file *fp;

	fp = getf(fd);
	if (!fp)
		/* fd not an open file */
		return(NULL);

	if (!(fp->f_flag & FMX))
		/* open file is not mx */
		return(NULL);

	return(fp->f_un.f_chan);
}


/*
 *   mxsleep -
 *	Process control/sync.  A process waiting for an event on a
 *	multiplexed file always waits on a channel address.  When
 *	woken up, if MXERROR is set, the event has failed or will
 *	never occur, and the channel must be detached.
 */
mxsleep(chp)
register struct mxchan *chp;
{
	trace(0x40000000, "mxsleep", chp->ch_chan);
	if (chp->ch_chan)
		/* not owner */
		chp->ch_flags |= MXSLEEP;

	sleep((caddr_t) chp, TTIPRI);

	/* wakeup has occured */
	if (chp->ch_flags & MXERROR)  {
		/* event failed - chan is 'gone' */
		if (chp->ch_flags & MXCLOSED)
			/* immediate chan free required */
			mxfrchan(chp);

		u.u_error = EIO;
		return(0);
		}

	/* sleep successful */
	return(1);
}


/*
 *   mxwakeup -
 *	Tell the waiting process (if any) to start up again.
 */
mxwakeup(chp)
register struct mxchan *chp;
{
	trace(0x40000000, "mxwakeup", chp->ch_chan);
	if (chp->ch_chan)
		/* not owner */
		chp->ch_flags &= ~MXSLEEP;

	wakeup((caddr_t) chp);
}



/*
 *   mxfrchan -
 *	Disconnect chan permanently - drain io waiting.
 */
mxfrchan(chp)
register struct mxchan *chp;
{
	register struct mxf *mxp;
	register int s;

	trace(0x40000000, "mxfrchan", chp);
	mxflush(chp);
	mxp = chp->ch_mxp;
	s = spl6();
	chp->ch_mxp->mx_chan[chp->ch_chan] = NULL;
	chp->ch_mxp->mx_nchan--;
	chp->ch_mxp   = NULL;
	chp->ch_fp    = NULL;
	chp->ch_flags = NULL;
	splx(s);

	if (mxp->mx_nchan == 0)
		/* last free - free mx structure */
		mxfree(mxp);
}



/*
 *   mxflush -
 *	Flush data waiting on message queue.
 */
mxflush(chp)
register struct mxchan *chp;
{
	register struct mxmsg *q;
	register struct mxmsg *r;

	q = chp->ch_head;
	r = q->m_link;
	while (q)  {
		msgfree(mxarena, q);
		q = r;
		if (r)
			r = r->m_link;
		}
}


mxcopy(from, to, n)
register char *from;
register char *to;
register int   n;
{
	trace(0x80000000, "mxcopy", from);
	while (n--)
		*to++ = *from++;
}
