/* $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
 */

#ident	"$Header: cn.c 1.18 90/04/20 $"

/*
 * indirect driver for /dev/syscon
 *	This driver's purpose in life is to give usrs of
 *	The "/dev/console" device (e.g. daemons, etc) a device
 *	which is immune to job control interference, and which
 *	tracks the selectd console device on either ssm or
 *	sced.    This was particularly a problem when the
 *	"console" device is also used to put login's on.
 *	
 *	This driver creates three special files:
 *	/dev/syscon:	tracks the "console device", and is
 *			useable interactive purposes:
 *			single-user shells, and login.
 *			It DOES support job control
 *
 *	/dev/altcon:	Is the "other" port on the console
 *			sced or ssm.  Again, is  useful for
 *			interactive purposes, i.e. login.
 *			It DOES support job control.
 *
 *	/dev/console:	Is meant to be used only by daemons
 *			and for logging things to the
 *			"console device".  The special file tracks
 *			the console device, but does not allow
 *			processes opening it to become controlling
 *			processes.  This way, these daemon
 *			processes don't interfere with users of
 *			/dev/syscon, who WANT to become controlling
 *			processes.
 *
 *			Reads/writes/ioctls to this device pass an extra
 *			flag to the stream code which forces it to
 *			processes accessing /dev/console as foreground
 *			processes.  Without this treatment, these processes
 *			could be STOPPED via job control.
 *
 *
 *	altcon and syscon are streams devices which "link"
 *	up with the appropriate console driver (/dev/ttyc0
 *	/dev/ttyc1 of sced, or /dev/ttysm0 or /dev/ttysm1 if
 *	ssm).  So, references to /dev/console, /dev/syscon
 *	and /dev/ttyc0 (if console device is a local sced line
 *	will all reference the same stream).  However, the
 *	getmsg() and putmsg() interfaces are not available
 *	to any of these special files.  getmsg and putmsg
 *	ARE available through /dev/ttyc0, etc.
 *
 *	This driver uses the standard (non-streams) character
 *	device interface, but CALLS streams interfaces.  So,
 *	it's a kind of "layer" between the two interface types.
 *	
 *	The implementation allocates three snodes at
 *	compile time, and does its own partial initialization
 *	of them.  Only fields needed by the stream code are
 *	initialized.  They are initialized with dev_t's
 *	appropriate to the underlying console devices.
 *	for example, if the console device is the remote port
 *	of a sced, then the snodes for /dev/syscon and /dev/console
 *	are initialized with dev_t makedev(1,1).  This snode
 *	is then passed to the stream code on every operation on
 *	either the syscon and console special files.  This is how
 *	this driver implements the tracking of the console device.
 *	If /dev/ttyc1 and /dev/syscon special files are BOTH opened,
 *	they will like up to the same stream.  This behavior
 *	is VERY IMPORTANT.   Having multiple streams open for the
 *	same physical device will cause a panic.
 *
 *	Also, opens on /dev/console force the FNOCTTY flag ON
 *	before calling stropen().  Read/write(/ioctl) on /dev/console
 *	force the IO_NOTOSTOP (FNOTOSTOP) flag ON  before calling
 *	the stream function.
 *
 *	Also, you should take note of the "funny" in the
 *	initialization of the private snodes.  The dev_t in
 *	the snode (s_dev) is set to the major/minor of the
 *	device driver we want to hook up with (e.g. /dev/ttyc1).
 *	The dev_t in the vnode (v_rdev) is set to the major/minor
 *	of the special file that was opened (/dev/syscon). This
 *	is done (and works) because the streams code looks at the
 *	s_dev field to determine which stream to hook up with.
 *	The ps command looks at the v_rdev field to determine what
 *	special file is your controlling terminal.  We'd like
 *	"/dev/syscon" to show up as your controlling special file
 *	while linking up with /dev/ttyc1's stream. So.....
 *
 */

/* $Log:	cn.c,v $
 */

#include "sys/types.h"
#include "sys/param.h"
#include "sys/conf.h"
#include "sys/devsw.h"
#include "sys/errno.h"
#include "sys/signal.h"
#include "sys/timer.h"
#include "sys/file.h"
#include "sys/vnode.h"
#include "sys/user.h"
#include "sys/proc.h"
#include "sys/uio.h"
#include "sys/sysmacros.h"
#include "sys/autoconf.h"
#include "sys/mutex.h"
#include "sys/intctl.h"
#include "sys/debug.h"
#include "sys/cmn_err.h"
#include "sys/snode.h"
#include "sys/cn.h"
#include "sys/ioctl.h"
#include "sys/stropts.h"
#include "sys/session.h"
#include "sys/stream.h"

/*
 * These are set by the sec and ssm initialization code
 * to whichever is the console device, as indicated by front
 * panel switches and hardware and driver configuration selections, etc.
 */

dev_t cons_id = makedev(1, 64);
dev_t altcons_id = makedev(1, 64);

#ifdef DEBUG		
int	cndebug;
#endif

extern int cn_c_major;
extern struct devops cndevops;

#define NCN	3
#define SYSCON	0
#define ALTCON	1
#define CONSOLE	2
struct cnstate cnstate[NCN];


/*ARGSUSED*/
void
cn_init(ndevs)
	int ndevs;
{
	int i;
	struct snode *sp;
	extern struct vnodeops spec_vnodeops;
	dev_t rdev;

	for (i = 0; i < NCN; i++) {

		sp = &cnstate[i].cn_snode;

		STOV(sp)->v_op = &spec_vnodeops;
		init_rwsema(&STOV(sp)->v_nodemutex, RWSEMA_SWP);
		init_lock(&STOV(sp)->v_mutex);

		/*
		 * This is a trick to make ps be able to
		 * find your controlling terminal.
		 * Turns out ps looks at the vnode field,
		 * while the rest of streams looks at the
		 * snode.  So, we can get by with having
		 * them be DIFFERENT dev_t's!!
		 */
		rdev = makedev(cn_c_major, i);
		SN_INIT(sp, NULL, VCHR, rdev);
		VN_UNLOCKNODE(STOV(sp));	/* SN_INIT() locks the snode */

		switch (i) {
		case SYSCON:
			sp->s_dev = cons_id;
			break;
		case ALTCON:
			sp->s_dev = altcons_id;
			break;
		case CONSOLE:
			sp->s_dev = cons_id;
			break;
		default:
			cmn_err(CE_PANIC, "cn_init: invalid device init");
			/*
			 *+ An invalid device initialization was attempted
			 *+ in the cn driver.  This indicates a kernel software
			 *+ bug.
			 */
		}
		if ((sp->s_devp = dev_ref(sp->s_dev)) == NULL)
			cmn_err(CE_WARN, "cn_init: real cn dev hasn't been added");
			/*
			 *+ The cn driver initialization code failed because
			 *+ the real console driver hasn't been established
			 *+ yet.  This could be either a kernel software error
			 *+ or a kernel configuration error.
			 */
		cnstate[i].cn_dev.d_dev = makedev(cn_c_major, i);
		cnstate[i].cn_dev.d_ops = &cndevops;
		cnstate[i].cn_dev.d_private = (caddr_t)&cnstate[i];
		if (devsw_add(&cnstate[i].cn_dev) != 0) {
			cmn_err(CE_WARN,
				"cn: problem adding syscons dev to devsw\n");
			/*
			 *+ A problem occurred when the kernel tried to add the syscon
			 *+ tty device to the system.  This is probably due to
			 *+ a driver that has added a device having the same
			 *+ major/minor number.  Corrective action:  check the kernel
			 *+ configuration.
			 */
		}
	}
}


cnopen(dp, flag)
	register struct dev *dp;
	register int flag;
{
	register struct snode *sp;

#ifdef DEBUG		
	if (cndebug)
		printf("cnopen\n");
#endif /* DEBUG */
	if (minor(dp->d_dev) == CONSOLE)
		flag |= FNOCTTY;
	sp = &((struct cnstate *)(dp->d_private))->cn_snode;
	return (stropen(sp, flag));
}


cnread(dp, uiop, flag)
	register struct dev *dp;
	struct uio *uiop;
	register int flag;
{
	register struct snode *sp;

#ifdef DEBUG		
	if (cndebug)
		printf("cnread\n");
#endif /* DEBUG */
	if (minor(dp->d_dev) == CONSOLE)
		flag |= IO_NOTOSTOP;
	sp = &((struct cnstate *)(dp->d_private))->cn_snode;
	return (strread(sp, uiop, flag));
}


cnwrite(dp, uiop, flag)
	register struct dev *dp;
	struct uio *uiop;
	register int flag;
{
	register struct snode *sp;

#ifdef DEBUG		
	if (cndebug)
		printf("cnwrite\n");
#endif /* DEBUG */
	if (minor(dp->d_dev) == CONSOLE)
		flag |= IO_NOTOSTOP;
	sp = &((struct cnstate *)(dp->d_private))->cn_snode;
	return (strwrite(sp, uiop, flag));
}


cnioctl(dp, cmd, arg, flag)
	register struct dev *dp;
	int cmd;
	int arg;
	register int flag;
{
	register struct snode *sp;

#ifdef DEBUG		
	if (cndebug)
		printf("cnioctl\n");
#endif /* DEBUG */
	if (minor(dp->d_dev) == CONSOLE)
		flag |= FNOTOSTOP;
	sp = &((struct cnstate *)(dp->d_private))->cn_snode;
	return (strioctl(sp, cmd, arg, flag));
}


cnstrmsgio(dp, mctl, mdata, rw, uflag, ioflag)
	register struct dev *dp;
	struct strbuf *mctl;
	struct strbuf *mdata;
	enum uio_rw rw;
	int uflag;			/* user's I/O flags */
	register int ioflag;		/* stream I/O flags */
{
	register struct snode *sp;

#ifdef DEBUG		
	if (cndebug)
		printf("cnstrmsgio\n");
#endif /* DEBUG */
	if (minor(dp->d_dev) == CONSOLE)
		ioflag |= IO_NOTOSTOP;
	sp = &((struct cnstate *)(dp->d_private))->cn_snode;
	if (rw == UIO_READ)
		return (strgetmsg(sp, mctl, mdata, uflag, ioflag));
	/* UIO_WRITE */
	return (strputmsg(sp, mctl, mdata, uflag, ioflag));
}


cnstrpoll(dp, events, anyyet)
	register struct dev *dp;
	short events;
	int anyyet;
{
	register struct snode *sp;

#ifdef DEBUG		
	if (cndebug)
		printf("cnstrpoll\n");
#endif /* DEBUG */
	sp = &((struct cnstate *)(dp->d_private))->cn_snode;
	return (strpoll(sp->s_sptr, events, anyyet));
}


cnclose(dp, flag)
	struct dev *dp;
{
	register struct snode *sp;

#ifdef DEBUG		
	if (cndebug)
		printf("cnclose\n");
#endif /* DEBUG */
	sp = &((struct cnstate *)(dp->d_private))->cn_snode;
	strclose(sp, flag);
}
