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

/*
 * kmmem.c
 *	Multibus Memory special file.
 *
 * 80386 version.
 */

/* $Log:	kmmem.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/mbad.h"

/*
 * Minor number mnemonics.
 */
#define	DEV_KMWMEM	0
#define	DEV_KMBMEM	1

#define KMMDEVS		2

extern int kmm_c_major;

extern struct devops kmmdevops;

struct dev kmmdev[KMMDEVS];
	
/*ARGSUSED*/
void
kmm_init(ndevs)
	int ndevs; 
{
	int i;

	for (i = 0; i < KMMDEVS; i++) {
		kmmdev[i].d_dev = makedev(kmm_c_major, i);
		kmmdev[i].d_ops = &kmmdevops;
		kmmdev[i].d_flags = 0;
		if (devsw_add(&kmmdev[i]) < 0)
			CPRINTF("kmm%d: problem adding dev 0x%x to devsw\n", i);
	}
}

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

/*ARGSUSED*/
kmmread(dp, uio, flags)
	struct dev *dp;
	struct uio *uio;
{
	dev_t dev = dp->d_dev;
	return (kmmrw(dev, uio, UIO_READ));
}

/*ARGSUSED*/
kmmwrite(dp, uio, flags)
	struct dev *dp;
	struct uio *uio;
{
	dev_t dev = dp->d_dev;
	return (kmmrw(dev, uio, UIO_WRITE));
}

static
kmmrw(dev, uio, rw)
	dev_t dev;
	struct uio *uio;
	enum uio_rw rw;
{
	register uint o;
	register uint c;
	register struct iovec *iov;
	int	MBAd_idx;
	int	error = 0;

	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, "kmmrw");
				/*
				 *+ The MULTIBUS 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_KMWMEM:	/* multibus memory (access by shorts) */
		case DEV_KMBMEM:	/* multibus memory (access by chars) */
			/*
			 * History: ns32000 kernel insisted on read/write
			 * to MBAd use kernel virtual addresses (8Meg).
			 * We allow this, but also allow zero-origin.
			 */
			if (o >= OLD_VIRT_MBAD)
				o -= OLD_VIRT_MBAD;
			/*
			 * If the addresses are in the same (existing) MBAd,
			 * then pte's are valid and not ever remapped.
			 *
			 * Don't know here if the address being read/written
			 * actually exists in the multibus; if not, MBAd causes
			 * an NMI (access error) and the kernel panics.
			 */
			MBAd_idx = o / MBAD_ADDR_SPACE;
			if ((MBAdvec & (1 << MBAd_idx)) == 0
			||  c > MBAD_ADDR_SPACE
			||  ((o + c - 1) / MBAD_ADDR_SPACE) > MBAd_idx)
				goto fault;
			if (!useracc(iov->iov_base, c, rw == UIO_READ ? B_WRITE : B_READ))
				goto fault;
			o &= (MBAD_ADDR_SPACE-1);
			MBAdcpy((caddr_t)(VA_MBAd(MBAd_idx) + o),
				iov->iov_base, (int)c, rw, minor(dev));
			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);
}

/*
 * Multibus Address Space <--> User Space transfer
 */

static
MBAdcpy(mbad_add, usradd, n, rw, mindev)
	register caddr_t mbad_add;
	register caddr_t usradd;
	register int n;
	enum uio_rw rw;
	int mindev;
{
	union {
		short	svar;
		char	sbuf[2];
	} x;

	if (mindev == DEV_KMWMEM) {
		/*
		 * Reads and writes must be as shorts.
		 */
		if (rw == UIO_READ) {
			for (n >>= 1; n > 0; n--) {
				x.svar = *(short *)mbad_add;
				mbad_add += 2;
				(void) subyte(usradd++, x.sbuf[0]);
				(void) subyte(usradd++, x.sbuf[1]);
			}
		} else {
			for (n >>= 1; n > 0; n--) {
				x.sbuf[0] = fubyte(usradd++);
				x.sbuf[1] = fubyte(usradd++);
				*(short *)mbad_add = x.svar;
				mbad_add += 2;
			}
		}
	} else {
		/*
		 * Reads and writes must be as characters.
		 */
		if (rw == UIO_READ) {
			for (; n > 0; n--)
				(void) subyte(usradd++, *mbad_add++);
		} else {
			for (; n > 0; n--)
				*mbad_add++ = fubyte(usradd++);
		}
	}
}

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

/*ARGSUSED*/
kmmmmap(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);
	int		MBAd_idx;
	dev_t		dev = dp->d_dev;

	switch(cmd) {

	case MM_MAP:
		/*
		 * All legit except /dev/null.  Check sizes.
		 * /dev/mem and /dev/kmem allow IO services,
		 * MBAd devices don't.
		 */

		switch(minor(dev)) {

		case DEV_KMWMEM:
		case DEV_KMBMEM:
			/*
			 * History: ns32000 kernel insisted on read/write
			 * to MBAd use kernel virtual addresses (8Meg).
			 * We allow this, but also allow zero-origin.
			 */
			if (addr >= OLD_VIRT_MBAD) {
				addr -= OLD_VIRT_MBAD;
				max_addr -= OLD_VIRT_MBAD;
			}
			/*
			 * Insure range fits within a single existing MBAd.
			 */
			MBAd_idx = addr / MBAD_ADDR_SPACE;
			if ((MBAdvec & (1 << MBAd_idx)) == 0
			||  size > btop(MBAD_ADDR_SPACE)
			||  (max_addr - 1) / MBAD_ADDR_SPACE > MBAd_idx)
				return(ENXIO);
			else
				return(MM_PHYS);

		default:
			return(ENXIO);
		}

	case MM_REFPG:

		switch(minor(dev)) {

		case DEV_KMWMEM:
		case DEV_KMBMEM:
			/*
			 * Same history...
			 */
			if (addr >= OLD_VIRT_MBAD)
				addr -= OLD_VIRT_MBAD;
			return(PA_MBAd(0) + addr);

		default:
			cmn_err(CE_PANIC, "kmmmmap: ref minor");
			/*
			 *+ The kernel attempted to reference a page that
			 *+ was 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, "kmmmmap: invalid function: %d", cmd);
		/*
		 *+ The mapping support function of the MULTIBUS memory 
		 *+ driver was asked to perform an invalid mapping operation.
		 */
		/*NOTREACHED*/
	}
}
