/* $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: mem.c 1.21 90/04/20 $"

/*
 * mem.c
 *	Memory special file.
 *
 * 80386 version.
 */

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

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

#include "sys/pte.h"
#include "sys/hwparam.h"
#include "sys/plocal.h"
#include "sys/autoconf.h"

/*
 * Minor number mnemonics.
 */

#define	DEV_MEM		0
#define	DEV_KMEM	1
#define	DEV_NULL	2

extern int mm_c_major;

extern int mmopen();
extern int mmread();
extern int mmwrite();
extern int mmmmap();
extern int nulldev();
extern int nodev();
extern int nostrmsgio();
extern int nostrpoll();

struct devops mmdevops = {
	mmopen,			/* d_open */
	nulldev,		/* d_close */
	nodev,			/* d_strategy */
	nodev,			/* d_minphys */
	nodev,			/* d_psize */
	nodev,			/* d_print */
	mmread,			/* d_read */
	mmwrite,		/* d_write */
	nodev,			/* d_ioctl */
	mmmmap,			/* d_mmap */
	nodev,			/* d_intr */
	0,			/* d_str */
	nostrmsgio,		/* d_strmsgio */
	nostrpoll		/* d_strpoll */
};

struct dev memdev[3];		/* device objects for MEM devices */

/*ARGSUSED*/
void
mm_init(ndevs)
	int ndevs;
{
	register int i;
	register struct dev *dp;

	for (i = 0; i < 3; i++) {
		dp = &memdev[i];

		switch (i) {
		case 0:
			dp->d_dev = makedev(mm_c_major, DEV_MEM);
			break;
		case 1:
			dp->d_dev = makedev(mm_c_major, DEV_KMEM);
			break;
		case 2:
			dp->d_dev = makedev(mm_c_major, DEV_NULL);
			break;
		}
		dp->d_ops = &mmdevops;
		if (devsw_add(dp) != 0) {
			cmn_err(CE_WARN, "mem%d: unable to add device to devsw",
				i);
			/*
			 *+ A problem occurred when the kernel tried to install one
			 *+ of the memory devices (/dev/mem, /dev/kmem,
			 *+ /dev/null) into devsw.  This is usually due to
			 *+ a device driver that has added a device to devsw; the 
			 *+ major/minor number for the device already belongs to another device.
			 */
		}
	}
}

/*ARGSUSED*/
mmopen(dp)
struct dev *dp;
{
	return(0);
}

/*ARGSUSED*/
mmread(dp, uio, flags)
	struct dev *dp;
	struct uio *uio;
{
	return (mmrw(dp, uio, UIO_READ));
}

/*ARGSUSED*/
mmwrite(dp, uio, flags)
	struct dev *dp;
	struct uio *uio;
{
	return (mmrw(dp, uio, UIO_WRITE));
}

static
mmrw(dp, uio, rw)
	struct dev *dp;
	struct uio *uio;
	enum uio_rw rw;
{
	register uint o;
	register uint c;
	register struct iovec *iov;
	struct pte lpte;
	int	error = 0;
	extern	caddr_t maxkmem;
	extern	caddr_t topmem;
	extern	etext;
	dev_t dev = dp->d_dev;

	while (uio->uio_resid > 0 && error == 0) {
		iov = uio->uio_iov;
		if (iov->iov_len == 0) {
			uio->uio_iov++;
			uio->uio_iovcnt--;
			if (uio->uio_iovcnt < 0)
				cmn_err(CE_PANIC, "mmrw");
				/*
				 *+ The memory driver was passed an invalid
				 *+ uio structure describing the I/O operation
				 *+ on a memory read/write.
				 */
			continue;
		}
		o = uio->uio_offset;
		c = iov->iov_len;
		switch (minor(dev)) {

		case DEV_MEM:				/* physical memory */
			/*
			 * Since might read/write over hole, do one page at a
			 * time.  Could do whole thing if there are no holes.
			 *
			 * If mapping of holes changes (ie, no Sysmap[] entry
			 * for holes; invalid at level 1), need to change this.
			 */
			if (o >= (uint)topmem
			||  (o + c) > (uint)topmem
			||  (o + c) < o			/* wrap-around */
			||  !Sysmap[btop(o)].pg_v)	/* hole or bad */
				goto fault;
			c = min(c, NBPG - (o & PGOFSET));
			error = uiomove((caddr_t)o, (int)c, rw, uio);
			continue;

		case DEV_KMEM:				/* kernel memory */
			/*
			 * Must grab static copy in case another processor
			 * causes Sysmap[] to change after we check the pte.
			 * 80386 doesn't allow page-tables to protect text,
			 * so protect by using &etext.
			 */
			if (o >= (uint)maxkmem)
				goto fault;
			lpte = Sysmap[btop(o)];
			if (lpte.pg_v == 0
			||  (rw == UIO_WRITE && o < (uint)&etext))
				goto fault;
			o &= PGOFSET;
			c = min(c, NBPG-o);
			error = uiomove((caddr_t)(PTETOPHYS(lpte)+o), (int)c, rw, uio);
			continue;

		case DEV_NULL:				/* EOF/RATHOLE */
			if (rw == UIO_READ)
				return (0);
			break;

		default:
			error = ENXIO;
			break;
		}
		if (error)
			break;
		iov->iov_base += c;
		iov->iov_len -= c;
		uio->uio_offset += c;
		uio->uio_resid -= c;
	}
	return (error);
fault:
	return (EFAULT);
}

/*
 * mmmmap()
 *	Perform mapping functions (neat name, huh?).
 */

static	char	mm_generic[CLBYTES+CLBYTES-1];	/* could calloc... */

/*ARGSUSED*/
mmmmap(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 ulong	addr = (ulong)ptob(off);
	register ulong	max_addr = (ulong)ptob(off+size);
	struct	pte	lpte;
	dev_t		dev = dp->d_dev;
	extern		etext;

	switch(cmd) {

	case MM_MAP:
		/*
		 * All legit except /dev/null.  Check sizes.
		 * /dev/mem and /dev/kmem allow IO services.
		 */

		switch(minor(dev)) {

		case DEV_MEM:
			/*
			 * Insure valid physical memory address.
			 */
			if (addr < (ulong)topmem
			&&  max_addr > addr
			&&  max_addr <= (ulong)topmem)
				return(MM_NPMEM);
			else
				return(ENXIO);

		case DEV_KMEM:
			/*
			 * Insure legal "kmem" range, and not trying to
			 * write on kernel text.
			 */
			if (addr < (ulong)maxkmem
			&&  max_addr <= (ulong)maxkmem
			&&  max_addr > addr
			&&  ((prot&PROT_WRITE) == 0 || addr >= (ulong)&etext))
				return(MM_NPMEM);
			else
				return(ENXIO);

		default:
			return(ENXIO);
		}

	case MM_REFPG:

		switch(minor(dev)) {

		case DEV_MEM:
			/*
			 * If page exists, return its physical address.
			 * Else return generic page.
			 */
			if (Sysmap[off].pg_v)
				return((int)ptob(off));
			else
				return(((int)mm_generic + CLOFSET) & ~CLOFSET);

		case DEV_KMEM:
			/*
			 * If invalid part of kernel address space
			 * (transient in parts), return generic page.
			 * Problem is that MM_MAP can't accurately
			 * check what's valid, and part of kernel
			 * address space change dynamically.
			 */
/* COMPILER_BUG  */
			lpte = Sysmap[off];
			if (lpte.pg_v)
				return((int)PTETOPHYS(lpte));
			return(((int)mm_generic + CLOFSET) & ~CLOFSET);
/* COMPILER_BUG */

		default:
			cmn_err(CE_PANIC, "mmmmap: ref minor");
			/*
			 *+ The kernel attempted to reference a page 
			 *+ mapped by the memory driver and passed the driver
			 *+ an invalid minor device number.
			 */
			/*NOTREACHED*/
		}

	case MM_UNMAP:
	case MM_SWPOUT:
	case MM_SWPIN:
		return(0);	/* NOP */

	default:
		cmn_err(CE_PANIC, "mmmmap: bad cmd=%d", cmd);
		/*
		 *+ The mapping support function of the memory driver was
		 *+ asked to perform an invalid mapping operation.
		 */
		/*NOTREACHED*/
	}
}
