/* $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: pmap.c 1.14 89/08/07 $"

/*
 * pmap.c
 *	Physical mapping driver.
 *
 * Semantics:
 *	+ This is a pseudo-device (ie, software driver).
 *	+ Up to 65536 physical space regions (with 16-bit major/minor #'s).
 *	+ Configurable initial parameters, number of units.
 *	+ Can map arbitrary ranges of physical space.
 *	+ Handles MM_PHYS and MM_NPMEM types of physical maps.
 *	+ Full physical d_mmap() support.
 *	+ No read/write procedures in the driver.  Are meaningful,
 *	  just not interesting yet.
 *	+ No checking for validity of physical addresses; use care.
 */

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

#include "sys/types.h"
#include "sys/systm.h"
#include "sys/devsw.h"
#include "sys/param.h"
#include "sys/debug.h"
#include "sys/mutex.h"
#include "sys/ioctl.h"
#include "sys/signal.h"
#include "sys/user.h"
#include "sys/errno.h"
#include "sys/sysmacros.h"
#include "sys/cmn_err.h"

#include "sys/pmap.h"

/*
 * Configuration variables defined in conf/conf_pmap.c
 */

extern	struct	pmap_unit pmap_unit[];
extern	int	pmap_nunit;

extern	struct	devops pmapdevops;
extern	int	pmap_c_major;

/*
 * Driver local variables.
 */

sema_t		pmap_mutex;
struct	dev	*pmapdevs;

/*
 * pmapboot()
 *	Initialize driver structures, set up default units.
 */

void
pmap_init( ndevs)
	int ndevs;
{
	register struct dev *dp;
	register int i;

	if ( ndevs > pmap_nunit)
		cmn_err( CE_WARN,
			"pmap_init: %d is too many units (%d max)",
			ndevs, pmap_nunit);
	/*
	 *+ The configuration line in the system configuration files specified
	 *+ more pmap units than were reserved in pmap_space.c.  
	 *+ Corrective action:  either
	 *+ increase the number of units in pmap_space.c or decrease the number
	 *+ of units in the system configuration file.
	 */
	init_sema(&pmap_mutex, 1);

	pmapdevs = (struct dev *)calloc(ndevs * sizeof(struct dev));
	dp = pmapdevs;
	for (i = 0; i < ndevs; i++, dp++) {
		dp->d_dev = makedev(pmap_c_major, i);
		dp->d_ops = &pmapdevops;
		if (devsw_add(dp) != 0) {
			cmn_err(CE_WARN, "pmap%d: unable to add device to devsw\n",
				i);
			/*
			 *+ The kernel detected a problem when it 
			 *+ attempted to add the
			 *+ device object for a pmap device into devsw.
			 *+ This is usually due to another device driver that
			 *+ has added a device having the same major/minor
			 *+ number.
			 */
		}
	}
}
/*
 * pmapopen()
 *	"Open" the phys-map device -- just check if legal unit.
 */

pmapopen(dp)
	struct	dev	*dp;
{
	struct	pmap_unit *pm;
	int	unit = minor(dp->d_dev);
	int	error = 0;

	if (unit >= pmap_nunit)
		return(ENXIO);

	/* 
	 * Is legal unit.  Allow the open unless held exclusively
	 * (except root).
	 */

	pm = &pmap_unit[unit];

	(void)p_sema(&pmap_mutex, PZERO-1);

	if ((pm->pm_flags & PMAP_EXCL) && u.u_uid != 0)
		error = EBUSY;
	else
		++pm->pm_nopen;

	v_sema(&pmap_mutex);

	return(error);
}

/*
 * pmapclose()
 *	Close a phys-mapped device.
 */

pmapclose(dp)
	struct	dev	*dp;
{
	struct	pmap_unit *pm = &pmap_unit[minor(dp->d_dev)];

	(void)p_sema(&pmap_mutex, PZERO-1);

	if (--pm->pm_nopen == 0)
		pm->pm_flags &= ~PMAP_EXCL;

	ASSERT_DEBUG(pm->pm_nopen>0 || pm->pm_nmaps==0, "pmapclose: mapped");
	ASSERT_DEBUG(pm->pm_nopen >= 0, "pmapclose: open cnt");

	v_sema(&pmap_mutex);
}

/*
 * pmapioctl()
 *	Misc ioctl's to fuss parameters.
 */


pmapioctl(dp, com, data)
	struct	dev	*dp;
	int		com;
	register struct pmap_ioc *data;
{
	register struct pmap_unit *pm = &pmap_unit[minor(dp->d_dev)];
	register int	error = 0;
	struct pmap_ioc	rdata;

	(void)p_sema(&pmap_mutex, PZERO-1);			/* for consistency */

	switch(com) {

	case PMAPIOCGETP:				/* get parameters */
		rdata.pi_paddr = pm->pm_paddr;
		rdata.pi_size = pm->pm_size * NBPG;
		rdata.pi_flags = pm->pm_flags;
		if (pm->pm_nmaps > 0)
			rdata.pi_flags |= PMAP_MAPPED;
		if (copyout(&rdata, data, sizeof(struct pmap_ioc))) {
			error = EFAULT;
		}
		break;

	case PMAPIOCSETP:				/* set parameters */
		if (copyin(data, &rdata, sizeof(struct pmap_ioc))) {
			error = EFAULT;
			break;
		}
		/*
		 * If paddr, size are ok and unit not mapped, change
		 * the setting.  Don't copy in flag bits.
		 */

		if (!suser())
			error = EPERM;
		else if ((rdata.pi_paddr|rdata.pi_size) & CLOFSET)
			error = EINVAL;
		else if (pm->pm_nmaps != 0)
			error = EBUSY;
		else {
			pm->pm_paddr = rdata.pi_paddr;
			pm->pm_size = rdata.pi_size / NBPG;
			pm->pm_flags &= ~PMAP_IOCFLAGS;
			pm->pm_flags |= (rdata.pi_flags & PMAP_IOCFLAGS);
		}
		break;

	case PMAPIOCEXCL:				/* exclusive access */
		if (pm->pm_nopen > 1)
			error = EBUSY;
		else
			pm->pm_flags |= PMAP_EXCL;
		break;

	case PMAPIOCNXCL:				/* shared access */
		pm->pm_flags &= ~PMAP_EXCL;
		break;

	default:
		error = ENXIO;
		break;
	}

	v_sema(&pmap_mutex);

	return(error);
}

/*
 * pmapmmap()
 *	Perform mapping functions.
 */

/*ARGSUSED*/
pmapmmap(dp, cmd, off, size, prot)
	struct	dev	*dp;
	int		cmd;
	ulong		off;		/* HW pages */
	int		size;		/* HW pages */
	int		prot;		/* PROT_READ|PROT_WRITE */
{
	register struct	pmap_unit *pm = &pmap_unit[minor(dp->d_dev)];
	int	val = 0;


	switch(cmd) {

	case MM_MAP:		/* verify, set up map: (offset, size, prot) */
		/*
		 * Verify size against what's there.
		 */

		(void)p_sema(&pmap_mutex, PZERO-1);

		if (pm->pm_size == 0)			/* can't map 0 size */
			val = ENXIO;
		else if (off+size > pm->pm_size)	/* too big */
			val = ENOSPC;
		else {
			++pm->pm_nmaps;
			if (pm->pm_flags & PMAP_NPMEM)
				val = MM_NPMEM;
			else
				val = MM_PHYS;
		}

		v_sema(&pmap_mutex);
		break;

	case MM_UNMAP:		/* done with map: (offset, size, prot) */
		(void)p_sema(&pmap_mutex, PZERO-1);
		--pm->pm_nmaps;
		ASSERT_DEBUG(pm->pm_nmaps >= 0, "pmapmmap: unmap count");
		v_sema(&pmap_mutex);
		break;

	case MM_REFPG:		/* add ref to page: (offset) */
		ASSERT_DEBUG(pm->pm_nmaps > 0, "pmapmmap: refpg count");
		ASSERT_DEBUG(off < pm->pm_size, "pmapmmap: refpg size");
		val = pm->pm_paddr + (off * NBPG);
		break;

	case MM_SWPOUT:		/* swap out ref to map:	(offset, size) */
	case MM_SWPIN:		/* swap in ref to map:	(offset, size) */
		break;

	case MM_DEREFPG:	/* remove page ref:	(offset) */
	case MM_REALLOC:	/* loose claim to page:	(page, ndx) */
	case MM_PGOUT:		/* page-out page:	(page, ndx) */
	default:
		cmn_err(CE_CONT, "cmd=%d\n", cmd);
		cmn_err(CE_PANIC, "pmapmmap: bad function");
		/*
		 *+ The pmap driver was called through its mmap entry point
		 *+ to perform an unsupported operation.
		 */
		/*NOTREACHED*/
	}

	return(val);
}
