static char rcsid[] = "$Header: vm_proc.c,v 820.1 86/12/04 19:59:50 root Exp $";
static char sccsid[]="%W% %Y% %Q% %G%";

/************************************************************************
*									*
*				Copyright 1984, 1985			*
*			VALID LOGIC SYSTEMS INCORPORATED		*
*									*
*	This listing contains confidential proprietary information	*
*	which is not to be disclosed to unauthorized persons without	*
*	written consent of an officer of Valid Logic Systems 		*
*	Incorporated.							*
*									*
*	The copyright notice appearing above is included to provide	*
*	statutory protection in the event of unauthorized or 		*
*	unintentional public disclosure.				*
*									*
************************************************************************/

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

/*
 * History:
 * ========
 * 860209 jht -- Add code to support 32Mb, 64Mb, 128Mb vaddr: M68020_REV_B
 */
#include "../machine/pte.h"

#include "../h/param.h"
#include "../h/systm.h"
#include "../h/dir.h"
#include "../h/user.h"
#include "../h/proc.h"
#include "../h/map.h"
#include "../h/cmap.h"
#include "../h/text.h"
#include "../h/vm.h"

/*
 * Get virtual memory resources for a new process.
 * Called after page tables are allocated, but before they
 * are initialized, we initialize the memory management registers,
 * and then expand the page tables for the data and stack segments
 * creating zero fill pte's there.  Text pte's are set up elsewhere.
 *
 * SHOULD FREE EXTRA PAGE TABLE PAGES HERE OR SOMEWHERE.
 */
vgetvm(ts, ds, ss)
	size_t ts, ds, ss;
{

	u.u_pcb.pcb_p0lr = AST_NONE;
	setp0lr(ts);
#ifdef s32
	setp1lr(p1pages(u.u_procp) - HIGHPAGES);
#else s32
	setp1lr(P1PAGES - HIGHPAGES);
#endif s32
	u.u_procp->p_tsize = ts;
	u.u_tsize = ts;
	expand((int)ss, 1);
	expand((int)ds, 0);
}

/*
 * Release the virtual memory resources (memory
 * pages, and swap area) associated with the current process.
 * Caller must not be swappable.  Used at exit or execl.
 */
vrelvm()
{
	register struct proc *p = u.u_procp;
#ifdef s32
	int s;
#endif s32
	
	/*
	 * Release memory; text first, then data and stack pages.
	 */
	xfree();
#ifdef USE_COW
	p->p_rssize -= cow_vmemfree(dptopte(p, 0), p->p_dsize);
	p->p_rssize -= cow_vmemfree(sptopte(p, p->p_ssize - 1), p->p_ssize);
#else USE_COW
	p->p_rssize -= vmemfree(dptopte(p, 0), p->p_dsize);
	p->p_rssize -= vmemfree(sptopte(p, p->p_ssize - 1), p->p_ssize);
#endif USE_COW
	if (p->p_rssize != 0)
		panic("vrelvm rss");
	/*
	 * Wait for all page outs to complete, then
	 * release swap space.
	 */
	p->p_swrss = 0;
	while (p->p_poip)
		sleep((caddr_t)&p->p_poip, PSWP+1);
	(void) vsexpand((size_t)0, &u.u_dmap, 1);
	(void) vsexpand((size_t)0, &u.u_smap, 1);
#ifdef s32
#ifdef USE_CTXT
	/*
	 * Need to make sure to fix suregp[01]lr so that we don't try
	 * to map in anything with sureg(). See comment in expand().
	 * sas 860218
	 */
	s = spl7();
	p->p_tsize = 0;
	p->p_dsize = 0;
	p->p_ssize = 0;
	u.u_tsize = 0;
	u.u_dsize = 0;
	u.u_ssize = 0;
	setp0lr(0);
	setp1lr(p1pages(u.u_procp) - HIGHPAGES);
	ctxt_invalidate(p);
	splx(s);
#else USE_CTXT
	s = spl7();
	p->p_tsize = 0;
	p->p_dsize = 0;
	p->p_ssize = 0;
	u.u_tsize = 0;
	u.u_dsize = 0;
	u.u_ssize = 0;
	setp0lr(0);
	setp1lr(p1pages(u.u_procp) - HIGHPAGES);
	splx(s);
#endif USE_CTXT
#else s32
	p->p_tsize = 0;
	p->p_dsize = 0;
	p->p_ssize = 0;
	u.u_tsize = 0;
	u.u_dsize = 0;
	u.u_ssize = 0;
#endif s32
}

/*
 * Pass virtual memory resources from p to q.
 * P's u. area is up, q's is uq.  Used internally
 * when starting/ending a vfork().
 */
vpassvm(p, q, up, uq, umap)
	register struct proc *p, *q;
	register struct user *up, *uq;
	struct pte *umap;
{

	/*
	 * Pass fields related to vm sizes.
	 */
	uq->u_tsize = q->p_tsize = p->p_tsize; up->u_tsize = p->p_tsize = 0;
	uq->u_dsize = q->p_dsize = p->p_dsize; up->u_dsize = p->p_dsize = 0;
	uq->u_ssize = q->p_ssize = p->p_ssize; up->u_ssize = p->p_ssize = 0;

	/*
	 * Pass proc table paging statistics.
	 */
	q->p_swrss = p->p_swrss; p->p_swrss = 0;
	q->p_rssize = p->p_rssize; p->p_rssize = 0;
	q->p_poip = p->p_poip; p->p_poip = 0;

	/*
	 * Relink text segment.
	 */
	q->p_textp = p->p_textp;
	xrepl(p, q);
	p->p_textp = 0;

	/*
	 * Pass swap space maps.
	 */
	uq->u_dmap = up->u_dmap; up->u_dmap = zdmap;
	uq->u_smap = up->u_smap; up->u_smap = zdmap;

	/*
	 * Pass u. paging statistics.
	 */
	uq->u_outime = up->u_outime; up->u_outime = 0;
	uq->u_ru = up->u_ru;
	bzero((caddr_t)&up->u_ru, sizeof (struct rusage));
	uq->u_cru = up->u_cru;
	bzero((caddr_t)&up->u_cru, sizeof (struct rusage));

	/*
	 * And finally, pass the page tables themselves.
	 * On return we are running on the other set of
	 * page tables, but still with the same u. area.
	 */
	vpasspt(p, q, up, uq, umap);
}

/*
 * Change the size of the data+stack regions of the process.
 * If the size is shrinking, it's easy-- just release virtual memory.
 * If it's growing, initalize new page table entries as 
 * 'zero fill on demand' pages.
 */
expand(change, region)
	int change, region;
{
	register struct proc *p;
	register struct pte *base, *p0, *p1;
	struct pte proto;
	int p0lr, p1lr;
	struct pte *base0;
	size_t ods, oss;
	int size;
	u_int v;
#ifdef s32
	u_char neg = (change < 0) ? 1 : 0;	/* Set if change is negative */
#endif s32

	p = u.u_procp;
	if (change == 0)
		return;
	if (change % CLSIZE)
		panic("expand");

#ifdef s32	/* PGINPROF */
	vmsizmon();
#endif

	/*
	 * Update the sizes to reflect the change.  Note that we may
	 * swap as a result of a ptexpand, but this will work, because
	 * the routines which swap out will get the current text and data
	 * sizes from the arguments they are passed, and when the process
	 * resumes the lengths in the proc structure are used to 
	 * build the new page tables.
	 */
	ods = u.u_dsize;
	oss = u.u_ssize;
#ifdef s32
	/*
	 * The standard BSD will work fine if we are always "growing".
	 * If we are shrinking however we run into a problem especially
	 * if we are frugal with segment mapping (i.e., USE_CTXT).  The 
	 * problem as I see it is that we misdefine "v" (the page table
	 * base) if we are shrinking.  For example, assume that we have
	 * 0x10 pages of text and 0x10 pages of data (all hypothetical
	 * of course :-).  Now let's shrink the data segment by one page.
	 * The pointers look something like the following:
	 *
	 * 0              10              20
	 * +---------------+---------------+
	 * |		   |               |
	 * +---------------+---------------+
	 *                 			tsize = 0x10, dsize = 0x10
	 *                                 ^
	 *				  "v"
	 *
	 * when in fact "v" should be set to 0x1F (or in the general case,
	 * tsize + dsize - abs(change) when change is negative).  The same
	 * holds true for shrinking the stack but since the stack grows
	 * downward (i.e., toward zero) the logic is reversed.
	 *
	 * sas 860214
	 */
	if (region == 0) {
		if (neg) {
			p->p_dsize += change;
			v = dptov(p, p->p_dsize);
			u.u_dsize += change;
		} else {
			v = dptov(p, p->p_dsize);
			p->p_dsize += change;
			u.u_dsize += change;
		}
	} else {
		if (neg) {
			v = sptov(p, p->p_ssize-1);
			p->p_ssize += change;
			u.u_ssize += change;
		} else {
			p->p_ssize += change;
			v = sptov(p, p->p_ssize-1);
			u.u_ssize += change;
		}
	}
#else s32
	if (region == 0) {
		v = dptov(p, p->p_dsize);
		p->p_dsize += change;
		u.u_dsize += change;
	} else {
		p->p_ssize += change;
		v = sptov(p, p->p_ssize-1);
		u.u_ssize += change;
	}
#endif s32

	/*
	 * Compute the end of the text+data regions and the beginning
	 * of the stack region in the page tables,
	 * and expand the page tables if necessary.
	 */
	p0 = u.u_pcb.pcb_p0br + (u.u_pcb.pcb_p0lr&~AST_CLR);
	p1 = u.u_pcb.pcb_p1br + (u.u_pcb.pcb_p1lr&~PME_CLR);
	if (change > p1 - p0)
		ptexpand(clrnd(ctopt(change - (p1 - p0))), ods, oss);
	/* PTEXPAND SHOULD GIVE BACK EXCESS PAGE TABLE PAGES */

	/*
	 * Compute the base of the allocated/freed region.
	 */
	p0lr = u.u_pcb.pcb_p0lr&~AST_CLR;
	p1lr = u.u_pcb.pcb_p1lr&~PME_CLR;
	if (region == 0)
		base = u.u_pcb.pcb_p0br + p0lr + (change > 0 ? 0 : change);
	else
		base = u.u_pcb.pcb_p1br + p1lr - (change > 0 ? change : 0);

	/*
	 * If we shrunk, give back the virtual memory.
	 */
	if (change < 0)
		p->p_rssize -= vmemfree(base, -change);

	/*
	 * Update the processor length registers and copies in the pcb.
	 */
	/*
	 * If we are managing contexts, we need to make sure that the
	 * change to p[01]lr and the context invalidation are atomic.
	 * The problem is that we could change p[01]lr and (by hardclock) not
	 * change the proc-values used to map in segments.  This would
	 * cause sureg() to mis-map in newly expanded pages through a
	 * "zero" segment table entry thus causing a particularly
	 * flaming death.
	 *
	 * The real bug is not here but is in the management of the contexts
	 * and segment maps
	 *
	 * sas 860218
	 */
	if (region == 0) {
#ifdef USE_CTXT
		if (neg == 0) {
			int s = spl7();
			setp0lr(p0lr + change);
			ctxt_invalidate(p);
			splx(s);
		} else
#endif USE_CTXT
			setp0lr(p0lr + change);
	} else {
#ifdef USE_CTXT
		if (neg == 0) {
			int s = spl7();
			setp1lr(p1lr - change);
			ctxt_invalidate(p);
			splx(s);
		} else
#endif USE_CTXT
			setp1lr(p1lr - change);
	}

	/*
	 * If shrinking, clear pte's, otherwise
	 * initialize zero fill on demand pte's.
	 */
	*(int *)&proto = PG_UW;
	if (change < 0) {
		change = -change;
	} else {
		proto.pg_fod = 1;
		((struct fpte *)&proto)->pg_fileno = PG_FZERO;
		cnt.v_nzfod += change;
	}
	base0 = base;
	size = change;
	while (--change >= 0)
		*base++ = proto;

#ifdef s32
#ifdef	M68020_REV_B
	/*
	 * We should clear the virtual segment list so that the are no
	 * misunderstandings in trap about the 'instantiated' bit.
	 */
	 if (neg) {
		emptyVSmapList();
		u.u_vSmapsInUse	= 0;	/* # of virt smaps in use	*/
		bclear(U_sMaps, pagesize*U_SMAPSPAGES);
	}
#endif	M68020_REV_B
#ifdef USE_CTXT
	/*
	 * We changed mapping for the current process,
	 * so must update the hardware translation
	 */
	/*
	 * If we are shrinking, we need to write to the map and then 
	 * invalidate and remap.  If we do otherwise, we run the risk 
	 * of unmapping the segment into which we want to write.
	 * sas 860213
	 */
	if (neg) {
		newptes(base0, v, size);
		ctxt_invalidate(p);
		sureg();
	} else {
		/*
		 * ctxt_invalidate was done above when setting p[01]lr
		 */
		sureg();
		newptes(base0, v, size);
	}
#else USE_CTXT
	/*
	 * In order to make sure that our segments are properly mapped
	 * at all times we MUST do a sureg() at this point.  Not doing
	 * so can cause all manner of strange problems ala Lou
	 * Scheffer's 5times program.
	 *
	 * ELP - 5/16/86
	 */
	if (neg) {
		newptes(base0, v, size);
		sureg ();
	} else {
		sureg ();
		newptes(base0, v, size);
	}
#endif USE_CTXT
#else s32
	newptes(base0, v, size);
#endif s32
}

/*
 * Create a duplicate copy of the current process
 * in process slot p, which has been partially initialized
 * by newproc().
 *
 * BUG: Could deadlock here if two large proc's get page tables
 * and then each gets part of his UPAGES if they then have
 * consumed all the available memory.  This can only happen when
 *	USRPTSIZE + UPAGES * NPROC > maxmem
 * which is impossible except on systems with tiny real memories,
 * when large procs stupidly fork() instead of vfork().
 */
procdup(p, isvfork)
	register struct proc *p;
{

	/*
	 * Allocate page tables for new process, waiting
	 * for memory to be free.
	 */
	while (vgetpt(p, vmemall) == 0) {
		kmapwnt++;
		sleep((caddr_t)kernelmap, PSWP+4);
	}

#ifdef s32
#ifndef WHITE
	mmskymap(p);		/* Map in SKY board if necessary */
#endif not WHITE
#endif s32
	/*
	 * Snapshot the current u. area pcb and get a u.
	 * for the new process, a copy of our u.
	 */
#ifdef s32
	resume(u.u_procp->p_addr);
#else s32
	resume(pcbb(u.u_procp));
#endif s32
	(void) vgetu(p, vmemall, Forkmap, &forkutl, &u);

	/*
	 * Arrange for a non-local goto when the new process
	 * is started, to resume here, returning nonzero from setjmp.
	 */
	forkutl.u_pcb.pcb_sswap = (int *)&u.u_ssave;
	if (setjmp(&forkutl.u_ssave)) {
#ifdef	M68020_REV_B
		/*
		 * We should clear the virtual segment list so that the are no
		 * misunderstandings in trap() about the 'instantiated' bit.
		 */
		emptyVSmapList();
		u.u_vSmapsInUse	= 0;	/* # of virt smaps in use	*/
		bclear(U_sMaps, pagesize*U_SMAPSPAGES);
#endif	M68020_REV_B
		/*
		 * Return 1 in child.
		 */
		return (1);
	}

	/*
	 * If the new process is being created in vfork(), then
	 * exchange vm resources with it.  We want to end up with
	 * just a u. area and an empty p0 region, so initialize the
	 * prototypes in the other area before the exchange.
	 */
	if (isvfork) {
		forkutl.u_pcb.pcb_p0lr = u.u_pcb.pcb_p0lr & AST_CLR;
#ifdef s32
		forkutl.u_pcb.pcb_p1lr = p1pages(u.u_procp) - HIGHPAGES;
#else s32
		forkutl.u_pcb.pcb_p1lr = P1PAGES - HIGHPAGES;
#endif s32
		vpassvm(u.u_procp, p, &u, &forkutl, Forkmap);
		/*
		 * Return 0 in parent.
		 */
		return (0);
	}
	/*
	 * A real fork; clear vm statistics of new process
	 * and link into the new text segment.
	 * Equivalent things happen during vfork() in vpassvm().
	 */
	bzero((caddr_t)&forkutl.u_ru, sizeof (struct rusage));
	bzero((caddr_t)&forkutl.u_cru, sizeof (struct rusage));
	forkutl.u_dmap = u.u_cdmap;
	forkutl.u_smap = u.u_csmap;
	forkutl.u_outime = 0;

	/*
	 * Attach to the text segment.
	 */
	if (p->p_textp) {
		p->p_textp->x_count++;
		xlink(p);
	}

	/*
	 * Duplicate data and stack space of current process
	 * in the new process.
	 */
#ifdef USE_COW
	cow_dup(p, dptopte(p, 0), dptov(p, 0), p->p_dsize, CDATA);
	cow_dup(p, sptopte(p, p->p_ssize - 1), sptov(p, p->p_ssize - 1), p->p_ssize, CSTACK);
#else USE_COW
	vmdup(p, dptopte(p, 0), dptov(p, 0), p->p_dsize, CDATA);
	vmdup(p, sptopte(p, p->p_ssize - 1), sptov(p, p->p_ssize - 1), p->p_ssize, CSTACK);
#endif USE_COW

	/*
	 * Return 0 in parent.
	 */
	return (0);
}

vmdup(p, pte, v, count, type)
	struct proc *p;
	register struct pte *pte;
	register unsigned v;
	register size_t count;
	int type;
{
	register struct pte *opte = vtopte(u.u_procp, v);
	register int i;

	while (count != 0) {
		count -= CLSIZE;
		if ((int)opte <= 0)	/* jht -- firedoor */
			panic("vmdup/E: vtopte() <= 0");
		if (opte->pg_fod) {
			v += CLSIZE;
			for (i = 0; i < CLSIZE; i++)
				*(int *)pte++ = *(int *)opte++;
			continue;
		}
		opte += CLSIZE;
		(void) vmemall(pte, CLSIZE, p, type);
		p->p_rssize += CLSIZE;
		for (i = 0; i < CLSIZE; i++) {
			copyseg((caddr_t)ctob(v+i), (pte+i)->pg_pfnum);
			*(int *)(pte+i) |= (PG_V|PG_M) + PG_UW;
		}
		v += CLSIZE;
		munlock(pte->pg_pfnum);
		pte += CLSIZE;
	}
	p->p_flag |= SPTECHG;
}
