/*
 * 5799-WZQ (C) COPYRIGHT IBM CORPORATION  1986,1987,1988
 * LICENSED MATERIALS - PROPERTY OF IBM
 * REFER TO COPYRIGHT INSTRUCTIONS FORM NUMBER G120-2083
 */
/* $Header:mem.c 12.0$ */
/* $ACIS:mem.c 12.0$ */
/* $Source: /ibm/acis/usr/sys/ca/RCS/mem.c,v $ */

#if !defined(lint) && !defined(NO_RCS_HDRS)
static char *rcsid = "$Header:mem.c 12.0$";
#endif

/*     mem.c   6.1     83/07/29        */

/*
 * Memory special file
 */

#include "pte.h"
#include "param.h"
#include "dir.h"
#include "user.h"
#include "conf.h"
#include "buf.h"
#include "systm.h"
#include "vm.h"
#include "cmap.h"
#include "uio.h"
#include "mmu.h"

#if	defined(FPA)
#include "float.h"
#include "fpa.h"		/* For address validity checking */
#include "debug.h"
#endif	/* defined(FPA) */

#ifdef ATR
#include "io.h"
#include "../ca_atr/pcif.h"
#endif ATR

mmread(dev, uio)
	dev_t dev;
	struct uio *uio;
{

	return (mmrw(dev, uio, UIO_READ));
}


mmwrite(dev, uio)
	dev_t dev;
	struct uio *uio;
{

	return (mmrw(dev, uio, UIO_WRITE));
}


mmrw(dev, uio, rw)
	dev_t dev;
	struct uio *uio;
	enum uio_rw rw;
{
	register u_int c, v;
	register struct iovec *iov;
	int error = 0;
	struct buf *bp;

	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)
				panic("mmrw");
			continue;
		}
		switch (minor(dev)) {

/* minor device 0 is physical memory */
/* Strategy:
 * 1. verify the address is within physical memory (taking into account
 *	the possible existance of a hole in the address space).
 * 2. verity that the end address does not exceed memory
 *     if write, uimove user's data to kernel buffer
 * 3. go to V=R mode (target address cannot be in segment 0 as this is
 *	read-only text (note: should check to make sure that this gets
 *	picked up properly if it is in segment 0))
 * 4. do the copy via uiomove
 * 5. restore previous (presumably non V=R).
 *     if read, uiomove the kernel buffer to the user's area
 */
		case 0:
		ourmem:
			v = btop(uio->uio_offset);
			if (v >= endmem || ishole(v))
				goto fault; /* start address too big */
			v = btop(uio->uio_offset + iov->iov_len + -1);
			if (v >= endmem || ishole(v))
				goto fault; /* end address too big */
			c = MIN((u_int)iov->iov_len, MAXBSIZE);
			bp = geteblk(c);
			if (rw == UIO_READ) {
				register int s, x;
				s = spl5(); /* 5??? */
				GET_VR0(x);
				bcopy((caddr_t)uio->uio_offset,
					bp->b_un.b_addr,
					c);
				SET_VR(x); /* restore virtual */
				splx(s);  /* restore prio */
				error = uiomove(bp->b_un.b_addr,
					(int)c, rw, uio);
			} else {	/* note: uiomove modifies uio_offset */
				register int s, x;
				register caddr_t off = (caddr_t)uio->uio_offset;
				error = uiomove(bp->b_un.b_addr,
					(int)c, rw, uio);
				s = spl5(); /* 5??? */
				GET_VR0(x);
				bcopy(bp->b_un.b_addr, off, c);
				SET_VR(x); /* restore virtual */
				splx(s);  /* restore prio */
			}
			brelse(bp);
			continue;

/* minor device 1 is kernel memory */
		case 1:
			c = iov->iov_len;
			if (!kernacc((caddr_t)uio->uio_offset,
				c, rw == UIO_READ ? B_READ : B_WRITE))
				goto fault;
			error = uiomove((caddr_t)uio->uio_offset, (int)c, rw, uio);
			continue;

/* minor device 2 is EOF/RATHOLE */
		case 2:
			if (rw == UIO_READ)
				return (0);
			c = iov->iov_len;
			break;

#ifdef IBMRTPC
/* minor device 3 is kernel memory, addressed as bytes */
/* minor device 4 is kernel memory, addressed as shorts */
/* minor device 5 is kernel memory, addressed as longs */
		case 3:
		case 4:
		case 5:
			c = iov->iov_len;
			if (!kernacc((caddr_t)uio->uio_offset,
				c, rw == UIO_READ ? B_READ : B_WRITE))
				goto fault;
			if (!useracc(iov->iov_base,
				c, rw == UIO_READ ? B_READ : B_WRITE))
				goto fault;
			error = IOcpy((caddr_t)uio->uio_offset, iov->iov_base,
				(int)c, rw, minor(dev) - 3);
			break;
#endif IBMRTPC
#if	defined(FPA)
/* minor device 12 is the path to access the afpa control store */
		case 12: {
#define AFPA_VALID(x) \
    ((unsigned)(x) <= \
		((unsigned) AFPA_UCODE_HIGHEST)-((unsigned) AFPA_UCODE_LOWEST))

			unsigned long to, from;
			unsigned int to_first_inc, to_last_inc;
			unsigned int from_first_inc, from_last_inc;
			int s;
			int count;

			c = iov->iov_len;
			/*
			 * Make sure length and user base aligned properly.
			 *
			 * The idea is we are accessing double words into the
			 * control store.  So, the count and the control
			 * store addresses need to be double word aligned.
			 *
			 * In addition, we are accessing the user storage
			 * via a "long" pointer, so user storage must be
			 * word aligned.
			 */
			if ((c&7) ||
			   (((int) uio->uio_offset)&7) ||
			   (((int) iov->iov_base)&3)) {
				return EINVAL;
			}
			/*
			 * Check that the limits are correct.
			 *
			 * Note that we run through the loop 'c/8'
			 * times; that each time through we are
			 * incrementing the control store offset
			 * by 4.  So, at the end of the
			 * loop, the control store offset is
			 * set to ((c/8)*4).  So, at the start of
			 * the last iteration, the offset is
			 * (((c/8)*4)-4, and the LAST offset
			 * we access is, therefore, (((c/8)*4)-4+1
			 * == (c/2)-3.
			 */
			if (!(AFPA_VALID(uio->uio_offset)
			      && AFPA_VALID(((unsigned)uio->uio_offset)
						+((c/2)-(sizeof (long)-1))))) {
				goto fault;
			}
			/* Make sure things are legal */
			if ((float_hardware&FLOAT_AFPA_CONTROL_STORE_ENABLE)
									== 0) {
				return EACCES;
			}
			if (!useracc(iov->iov_base,
				c, rw == UIO_READ ? B_WRITE : B_READ)) {
				goto fault;
			}
			if (rw == UIO_READ) {
				to = (unsigned long) iov->iov_base;
				to_first_inc = to_last_inc = sizeof (long);
				from = (unsigned) AFPA_UCODE_LOWEST +
						(unsigned long) uio->uio_offset;
				from_first_inc = 1;
				from_last_inc = sizeof (long) -1;
			} else {
				to = (unsigned) AFPA_UCODE_LOWEST +
						(unsigned long) uio->uio_offset;
				to_first_inc = 1;
				to_last_inc = sizeof (long) -1;
				from = (unsigned long) iov->iov_base;
				from_first_inc = from_last_inc = sizeof (long);
			}
			/*
			 * Now, make sure we have the user's pages locked in
			 * core.
			 *
			 * This is so we can do the loop with the system locked
			 * out.  Otherwise, we seem to crash depending on
			 * other events.
			 */
			vslock(iov->iov_base, c);
			DEBUGF(fpadebug & SHOW_RDWR, printf("afpamem: copying %d double words from %x to %x\n", c>>3, from, to));
			s = spl7();
			count = c >> 3;		/* Writing double words */
			/*
			 * We go to some trouble to make sure
			 * that our "pointer" arithmetic is unsigned.
			 * we have to use volatile on both from and to 
			 * pointers since either one can be the afpa control
			 * store.
			 */
			while (count--) {
			    * (long volatile *) to = * (long volatile *) from;
			    to += to_first_inc;
			    from += from_first_inc;
			    * (long volatile *) to = * (long volatile *) from;
			    to += to_last_inc;
			    from += from_last_inc;
			}
			splx(s);
			vsunlock(iov->iov_base, c,
					rw == UIO_READ ? B_READ : B_WRITE);
			break;
		}
#endif	/* defined(FPA) */

#ifdef IBMRTPC
/*
 * minor device 6 is ROS memory.
 * we use a static buffer in order to insure that we can copy
 * from ROS into a memory address below ROS.
 * we then copy this onto the stack so that we can stand to be interrupted
 * ROS_CHUNK	should be reasonably small to reduce interrupt latency
 *		on systems with > 8meg of memory as interrupts are
 *		inhibited because ROS is mapped in over top of some
 *		RAM addresses (which might be used as UPAGES).
 * Note that we reject reads of ROS when DMA is in progress if we must remap
 * ROS as we might remap ROS while the DMA is in progress to
 * it which will cause bad things to happen.
 */
		case 6:
#define K * 1024 
#define M * 1024 * 1024 
#define ROS_CHUNK	64	/* a reasonably small chunk */

			if (rw == UIO_READ) {
			static char rosbuffer[ROS_CHUNK];	/* small price to pay */
			char stackbuffer[ROS_CHUNK];	/* small price to pay */
			register int s;
			register int size;
			static int memsizes[16] = {
				0, 64 K, 64 K, 64 K, 64 K, 64 K, 64 K, 64 K,
				128 K, 256 K, 512 K, 1 M, 2 M, 4 M, 8 M, 16 M
			};
			extern char dma_in_use;	/* in ../caio/dmavar.h */

			int reg = ior((unsigned) MMUBASE + MMU_ROS);
			int oldreg = reg;
			if (reg == 0)
				reg = MMU_ROS_SPEC;	/* use default */
			c = size = memsizes[reg & 0x0f];
			v = (uio->uio_offset);
			if (v >= c)
				goto fault; /* start address too big */
			v = (uio->uio_offset + iov->iov_len + -1);
			if (v >= c)
				goto fault; /* end address too big */
			c = MIN((u_int)iov->iov_len, ROS_CHUNK);
			s = splhigh();		/* inhibit simultanious use */
			if (oldreg == 0 && dma_in_use) {
				splx(s);
				error = EBUSY;	/* DMA busy - can't read ROS */
				break;
			}
			roscopy((((reg & 0xff0) >> 4) * size) + (caddr_t)uio->uio_offset,
				vtop(rosbuffer), c);
			bcopy(rosbuffer, stackbuffer, c);
			splx(s);
			error = uiomove(stackbuffer, (int)c, rw, uio);
			continue;
			} else
				goto fault;		/* can't write ROS! */

#endif IBMRTPC

#ifdef ATR
/* minor device 7 is PS/2 physical memory */
/* minor device 8 is PS/2 extended memory (above 1meg) */
/* Strategy:
 * 1. verify the address is within physical memory and
 *	adjust length to insure that it doesn't cross a 512K boundary
 * 2. verify that the end address does not exceed memory
 *     if write, uimove user's data to kernel buffer
 * 3. set the 512K window to the memory address
 * 4. do the copy via uiomove
 * 5. restore previous 512k window
 *     if read, uiomove the kernel buffer to the user's area
 */
		case 7:
#define PCMEM_EXT 8
		case PCMEM_EXT:
			{
				register int off = uio->uio_offset;
				int endpcmem = ((0x100000 + config.pc_mem_size*1024)/NBPG);
				if (minor(dev) == 8)
					off += 0x100000;	/* offset into extend mem */
				v = btop(off);
				if (v >= endpcmem)
					goto fault; /* start address too big */
				v = btop(off + iov->iov_len + -1);
				if (v >= endpcmem)
					goto fault; /* end address too big */
				c = MIN((u_int)iov->iov_len, MAXBSIZE);
				c = MIN(c, 0x80000-(off&0x7ffff));
				bp = geteblk(c);
				if (rw == UIO_READ) {
					register int s = splhigh(), x;
					register int old_window = get_512_window();
					extern int pcif_512_fw;
					x = set_512_window(off);
					bcopy((caddr_t)x+pcif_512_fw,
						bp->b_un.b_addr,
						c);
					set_512_window(old_window);
					splx(s);  /* restore prio */
					error = uiomove(bp->b_un.b_addr,
						(int)c, rw, uio);
				} else {
					register int s = splhigh(), x;
					register int old_window = get_512_window();
					extern int pcif_512_fw;
					error = uiomove(bp->b_un.b_addr,
						(int)c, rw, uio);
					x = set_512_window(off);
					bcopy(bp->b_un.b_addr, (caddr_t)x+pcif_512_fw, c);
					set_512_window(old_window);
					splx(s);  /* restore prio */
				}
				brelse(bp);
				continue;
			}
/*
 * look at memory on another CPU, we do this by mapping the 512K window
 * into the other processors 64K windows and setting the 64K window
 * appropriately.
 */
#define PCMEM0	9
#define PCMEM1	10
#define PCMEM2	11
#define MEM_WINDOW 0x10000	/* window size is 64k */
#define SWAPH(n) ((((n)>>8)&0xff) | (((n)&0xff) << 8))
		case PCMEM0:
		case PCMEM1:
		case PCMEM2:
			{
				register int off = uio->uio_offset;
				register int old_window = get_512_window();
				int x;
				static unsigned char wmap[3][3] = {
					{ 0, 2, 2},
					{ 2, 0, 3},
					{ 3, 3, 0}};
				int cpu = minor(dev) - PCMEM0;
				extern int pcif_512_fw;
				struct cbcb *alt_cbcb;	/* for other cbcb */
				int port;
				if (cpu >= config.maxcpu)
					goto fault; /* cpu number too big */
				if (cpu == config.cpu)
					goto ourmem;	/* our own memory is easy */
				port = wmap[config.cpu][cpu];
/*				printf("cpu %d reading from cpu %d at %x\n", config.cpu,cpu,off); /* DEBUG */
				set_512_window(cbcb_addr);	/* our CBCB */
				if ((x = cbcb->cbcb[cpu]) == 0) {
					set_512_window(old_window);
					goto fault;	/* cpu not present */
				}
				alt_cbcb = (struct cbcb *) (set_512_window(x) + pcif_512_fw);
				if ((off + iov->iov_len + -1) >= swapw(alt_cbcb->physmem_bytes)) {
					set_512_window(old_window);
					goto fault; /* end address too big */
				}
/*				printf("setting port %d to %x\n",port,off>>16); /* DEBUG */
				OUT(SWAPH(alt_cbcb->port)+port, off >> 16);
				x = set_512_window(swapw(alt_cbcb->window)+MEM_WINDOW*port+(off&(MEM_WINDOW-1)));
				c = MIN((u_int)iov->iov_len, MAXBSIZE);
				c = MIN(c, MEM_WINDOW-(off&(MEM_WINDOW-1)));
				bp = geteblk(c);
				if (rw == UIO_READ) {
					register int s = splhigh();
					bcopy((caddr_t)x+pcif_512_fw,
						bp->b_un.b_addr,
						c);
					splx(s);  /* restore prio */
					error = uiomove(bp->b_un.b_addr,
						(int)c, rw, uio);
				} else {
					register int s = splhigh();
					error = uiomove(bp->b_un.b_addr,
						(int)c, rw, uio);
					bcopy(bp->b_un.b_addr, (caddr_t)x+pcif_512_fw, c);
					splx(s);  /* restore prio */
				}
				set_512_window(old_window);
				brelse(bp);
				continue;
			}
#endif ATR

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

#ifdef IBMRTPC
/*
 * Aligned Kernel Address Space <--> User Space transfer
 */
IOcpy(ioadd, usradd, n, rw, log2length)
	caddr_t ioadd, usradd;
	register int n;
	enum uio_rw rw;
	int log2length;
{
	register caddr_t from, to;
	register int length;
	register int mask;

	/*
	 * Check for length and alignment commensurate
	 * with the request
	 */
	mask = 3 >> (2 - log2length);
	if (n & mask || (int) ioadd & mask || (int) usradd & mask)
		return (EINVAL);

	if (rw == UIO_READ) {
		from = ioadd;
		to = usradd;
	} else {
		from = usradd;
		to = ioadd;
	}

	n >>= log2length;
	length = 1 << log2length;

	switch(log2length) {

	case 0:
		for (; n > 0; n--) {
			*(char *)to = *(char *)from;
			to += length;
			from += length;
		}
		break;

	case 1:
		for (; n > 0; n--) {
			*(short *)to = *(short *)from;
			to += length;
			from += length;
		}
		break;

	case 2:
		for (; n > 0; n--) {
			*(long *)to = *(long *)from;
			to += length;
			from += length;
		}
		break;

	default:
		panic("IOcpy");
	}
	return (0);
}
#endif IBMRTPC

#ifdef ATR
/*
 * strategy routine so that we can have a block device for pc extended memory
 * which allows us to use it as either a fast swapping device or as a fast
 * mounted filesystem.
 */
#define SIZE_512K	0x80000

mmstrategy(bp)
struct buf *bp;
{
	int off = (bp->b_blkno << DEV_BSHIFT) + 0x100000;
	int endpcmem = (0x100000 + config.pc_mem_size*1024);
	int count = bp->b_bcount;
	int xcount;
	char *addr = bp->b_un.b_addr;
	int old_window;


	if (minor(bp->b_dev) != PCMEM_EXT || (off+count) > endpcmem) {
		bp->b_flags |= B_ERROR;
		iodone(bp);
		return;
	}
	old_window = get_512_window();
	for (; count > 0; count -= xcount, addr += xcount, off += xcount) {
		int s = splhigh();
		int x = set_512_window(off);
		char *paddr = (caddr_t) real_buf_addr(bp, addr);
		extern int pcif_512_fw;
		int v;

		xcount = NBPG - ((int)addr&(NBPG-1));	/* amount left on page */
		v = SIZE_512K - (off&(SIZE_512K-1));
		if (xcount > v)
			xcount = v;	/* adjust length if we cross 512K boundary */
		if (xcount > count)
			xcount = count;	/* adjust length if we are near end of xfer */

		GET_VR0(v);	/* go Virtual=Real mode for bcopy */
		if (bp->b_flags&B_READ)
			bcopy((caddr_t)x+pcif_512_fw, paddr, xcount);
		else 
			bcopy(paddr, (caddr_t)x+pcif_512_fw, xcount);
		SET_VR(v);	/* restore v=r */
		splx(s); 	/* restore prio */
	}
	set_512_window(old_window);
	bp->b_resid = 0;
	iodone(bp);
}

mmsize(dev)
{
	if (minor(dev) != PCMEM_EXT)
		return(-1);
	return((config.pc_mem_size*1024) >> DEV_BSHIFT);
}

#include	"../h/ioctl.h"
#include	"../machine/dkio.h"

mmioctl(dev, cmd, data, flag)
	caddr_t         data;
	dev_t           dev;
{
	if (minor(dev) == PCMEM_EXT && cmd == DKIOCGPART) {
		register struct dkpart *dk = (struct dkpart *) data;

		bzero(data, sizeof(struct dkpart));	/* make sure its zero */
		dk->dk_size = (config.pc_mem_size*1024) >> DEV_BSHIFT;	/* size */
		dk->dk_ntrack = 4;			/* 64k cylinders */
		dk->dk_nsector = 32;			/* 16 k track */
		dk->dk_start = 0x100000 >> DEV_BSHIFT;
		dk->dk_blocksize = DEV_BSIZE;	/* device blocksize */
		dk->dk_ncyl = dk->dk_size / (dk->dk_ntrack * dk->dk_nsector);

		strcpy(dk->dk_name, "extpcmem");
		return (0);
	} else
		return (ENOTTY);
}
#endif ATR
