/* $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: ss.c 1.18 89/08/16 $"

/*
 * ss.c 
 *	SSM internal device driver.
 */

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

#include "sys/types.h"
#include "sys/devsw.h"
#include "sys/param.h"
#include "sys/sysmacros.h"
#include "sys/mutex.h"
#include "sys/signal.h"
#include "sys/user.h"
#include "sys/conf.h"
#include "sys/buf.h"
#include "sys/uio.h"
#include "sys/systm.h"
#include "sys/consmemioctl.h"
#include "sys/vm.h"
#include "sys/errno.h"
#include "sys/cmn_err.h"

#include "sys/cfg.h"
#include "sys/engine.h"
#include "sys/slic.h"

#include "sys/pte.h"
#include "sys/intctl.h"
#include "sys/plocal.h"

#include "sys/ssm.h"	
#include "sys/ssm_misc.h"
#include "sys/ss.h"			/* driver local structures */
#include "sys/ssm_device.h"
#include "sys/autoconf.h"

#ifdef DEBUG
int ssdebug = 0;
#endif

/*
 * Global data structures.
 */
static struct ss SS;
static int ss_max_idx = -1;
static int ss_cons;


extern int ss_c_major;
extern struct devops ssdevops;
struct dev ssdev[MAXNUMSSM];

/*
 * ssboot()
 *	Initialize each ss-device's info structure.
 *
 *	Allocates and initializes all device driver data
 *	structures based off of the configuration information.
 *	Also notes which of its boards are driving the 
 *	console/front panel for special operations.
 */
void
ssboot(driver)
	struct driver_module *driver;
{
	register struct	ss_info *infop;
	register struct ssm_device *devp;
	int dev;

#ifdef DEBUG
	if(ssdebug)
		cmn_err(CE_NOTE, "ssboot: SS @ 0x%x\n", &SS);
#endif
	/*
	 * Initialize the SS structure.
	 */
	init_sema(&SS.ss_sync, 1);

	/* 
	 * Initialize each configured ss-device.
	 */
	for (dev = 0, devp = (struct ssm_device *)driver->devices;
	     dev < driver->numdev; dev++, devp++) {

		if (!devp->driver)	  /* alive test for ssm */
			continue;

		SS.ss_info[dev] = infop = (struct ss_info *) 
			calloc(sizeof(struct ss_info));
		infop->ss_desc = SSM_desc + dev;
		if (infop->ss_desc->ssm_is_cons) {
			SS.ss_cons = infop;
			ss_cons = dev;
		}
		ss_max_idx = dev;
		/*
		 * Now set up one dev for each of the possible minor devices.
		 */
		ssdev[dev].d_dev = makedev(ss_c_major, dev);
		ssdev[dev].d_ops = &ssdevops;
		ssdev[dev].d_flags = 0;
		if (devsw_add(&ssdev[dev]) < 0)
			CPRINTF("ss%d: problem adding dev 0x%x to devsw\n",
				dev);
	}
}

/*
 * ssopen()	
 *	Open the device.  It must specify
 *	either the console/front panel unit
 *	or a unit that was found.
 */
ssopen(dp)
	struct	dev *dp;
{
	dev_t dev = dp->d_dev;
	register int unit = minor(dev);
	
	if (unit == CTLR_MINOR && SS.ss_cons 
	||  unit <= ss_max_idx && SS.ss_info[unit]) 
		return(0);
	else 
		return(ENXIO);
}

/*
 * sswrite()
 *	Write the SSM's RAM.
 */
sswrite(dp, uio)
	struct dev *dp;
	struct uio *uio;
{
	dev_t dev = dp->d_dev;
	return(ss_startio(dev, UIO_WRITE, uio));
}

/*
 * ssread()
 *	Read the SSM's RAM.
 */
ssread(dp, uio)
	struct dev *dp;
	struct uio *uio;
{
	dev_t dev = dp->d_dev;
	return(ss_startio(dev, UIO_READ, uio));
}

/*
 * ss_startio()
 *	Start an I/O request to the SSM's
 *	RAM.  Synchronize with other accesses
 *	since we use driver's buffer.
 */
static
ss_startio(dev, rw, uio)
	dev_t	dev;
	enum uio_rw rw;
	register struct uio *uio;
{
	register struct ss_info *infop;
	int unit = minor(dev);
	int count;
	char *ram_loc;

	if (unit == CTLR_MINOR) {
		/* Get info ptr and real unit # of console device */
		infop = SS.ss_cons;
		unit = ss_cons;
	} else 
		infop = SS.ss_info[unit];

	(void)p_sema(&SS.ss_sync, PZERO-1);

	while (uio->uio_resid) {
		if (uio->uio_resid < 0) {
			u.u_error = EINVAL;	/* Invalid transfer count */
			break;
		}

		/*
		 * Determine where the data is on the SSM
		 * and how big a chunk of it to copy; must
		 * fragment it if our buffer is too small.
		 */
		ram_loc = (char *)uio->uio_offset;
		count = (uio->uio_resid < SSBUFSZ) ? uio->uio_resid : SSBUFSZ;

		/*
		 * Perform the transfer...
		 */
		if (rw == UIO_WRITE) {
			if (u.u_error = uiomove((caddr_t)&SS.ss_buf[0], 
						count, UIO_WRITE, uio)) 
				break;	/* A problem occurred */
			ssm_set_ram(unit, count, (char *)&SS.ss_buf[0], 
				ram_loc);
		} else {
			ssm_get_ram(unit, count, (char *)&SS.ss_buf[0], 
				ram_loc);
			if (u.u_error = uiomove((caddr_t)&SS.ss_buf[0], 
						count, UIO_READ, uio)) 
				break;	/* A problem occurred */
		}

		/* 
		 * Collect stats and go back for more...
		 */
		infop->ss_stats.consmem_xfers++;
	}

	v_sema(&SS.ss_sync);
	return(u.u_error);
}

/*
 * ssioctl()
 *	Special I/O requests of the driver.
 */
ssioctl(dp, cmd, addr)
	struct	dev *dp;
	int	cmd;
	caddr_t	addr;
{
	dev_t dev = dp->d_dev;
	register int unit = minor(dev);
	register struct ss_info *infop;
	struct ioctl_reboot arb;
	int error = 0;
	ushort flags;
	struct cons_mem mlog;

#ifdef DEBUG
	if(ssdebug)
		cmn_err(CE_NOTE, "ssioctl: dev=%x, cmd=%x, address=%x\n",
			 dev, cmd, addr);
#endif
	if (unit == CTLR_MINOR) {
		/* Get info ptr and real unit # of console device */
		infop = SS.ss_cons;
		unit = ss_cons;
	} else 
		infop = SS.ss_info[unit];

	(void)p_sema(&SS.ss_sync, PZERO-1);

	switch (cmd) {
	case SMIOSTATS:
		if (error = copyout((caddr_t)&infop->ss_stats, addr,
				 sizeof(struct consmem_stats)))
			break;
		infop->ss_stats.consmem_ioctls++;
		infop->ss_stats.consmem_cmd++;
		break;
	case SMIOGETREBOOT0:
	case SMIOGETREBOOT1:
		if (error = copyin(addr, (caddr_t)&arb,
				sizeof(struct ioctl_reboot)))
			break;
		arb.re_cfg_addr = (unchar *)NULL;
		if (infop != SS.ss_cons) {
			/*
			 * This operation applies only to the 
			 * console/front panel unit.
			 */
			error = EBADF;
			break;
		}
		ssm_get_boot((cmd == SMIOGETREBOOT0) ? SM_DYNIX : SM_DUMPER,
			     (arb.re_powerup == RE_PERM_FLAG) ?
			     SM_GET_DFT : SM_GET, &flags, BNAMESIZE,
			     &arb.re_boot_name[0]);
		arb.re_boot_flag = (uint)flags;
		error = copyout((caddr_t)&arb, addr,
				sizeof(struct ioctl_reboot));
		infop->ss_stats.consmem_ioctls++;
		infop->ss_stats.consmem_cmd++;
		break;
	case SMIOSETREBOOT0:
	case SMIOSETREBOOT1:
		if (error = copyin(addr, (caddr_t)&arb,
				sizeof(struct ioctl_reboot)))
			break;
		if (infop != SS.ss_cons) {
			/*
			 * This operation applies only to the 
			 * console/front panel unit.
			 */
			error = EBADF;
			break;
		}
		ssm_set_boot((cmd == SMIOSETREBOOT0) ? SM_DYNIX : SM_DUMPER,
			     (arb.re_powerup == RE_PERM_FLAG) ?
			     SM_SET_DFT : SM_SET, (ushort)arb.re_boot_flag,
			     BNAMESIZE, &arb.re_boot_name[0]);
		infop->ss_stats.consmem_ioctls++;
		infop->ss_stats.consmem_cmd++;
		break;
	case SMIOGETLOG:
		if (infop != SS.ss_cons) {
			/*
			 * This operation applies only to the 
			 * console/front panel unit.
			 */
			error = EBADF;
			break;
		}
		ssm_get_log_info(unit, &mlog.mm_buffer, 
			&mlog.mm_nextchar, &mlog.mm_size);
		error = copyout((caddr_t)&mlog,addr,sizeof(struct cons_mem));
		break;
	default:
		/* Unknown/unsupported command. */
		error = ENXIO;
		break;
	}

	v_sema(&SS.ss_sync);
	return(error);
}
