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

/************************************************************************
*									*
*				Copyright 1984				*
*			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.				*
*									*
************************************************************************/

/*
 * jam 840810
 */

#ifdef USE_COW

#include "../machine/pte.h"

#include "../h/param.h"
#include "../h/systm.h"
#include "../h/dir.h"
#include "../h/user.h"
#include "../h/kernel.h"
#include "../h/proc.h"
#include "../h/cmap.h"
#include "../h/vm.h"
#include "../h/vmmac.h"

#include "../s32/debug.h"

#ifdef COW_STATS

typedef struct cow_stats
{
	int	dups;		/* calls to cow_dup() */
	int	duppages;	/*  total pages to duplicate */
	int	vmdups;		/*  pages handed to vmdup */
	int	dupshare;	/*  pages merely shared */
	int	firstshare;	/*  first time sharing of pages */
	int	pageins;	/* calls to cow_pagein() */
	int	copies;		/*  pages copied by cow_pagein() */
	int	newowner;	/*  pages cow_pagein() found a new owner for */
	int	extrapagein;	/*  pages validated to avoid faulting */
	int	vmemfrees;	/* calls to cow_vmemfree() */
	int	vmemfreepages;	/*  total pages passed to cow_vmemfree() */
	int	unshares;	/*  total pages unshared by cow_vmemfree() */
	int	newowner1;	/*  pages given a new owner */
	int	extraunshare;	/*  pages validated to avoid faulting */
	int	rootfinds;	/* calls to cow_findshare() at the root */
	int	finds;		/*  calls to cow_findshare() */
	int	searched;	/*  processes searched by cow_findshare() */
} cow_stats_t;

#define COW_INCSTAT(name)	++cow_stats.name
#define COW_ADDSTAT(name,inc)	cow_stats.name += inc

cow_stats_t cow_stats;

#else
#define COW_INCSTAT(name)
#define COW_ADDSTAT(name,inc)
#endif COW_STATS

#ifdef COW_DEBUG
#undef COW_DEBUG
#define COW_DEBUG(stuff)	DODEBUG(D_COW,stuff)
#else COW_DEBUG
#define COW_DEBUG(stuff)
#endif COW_DEBUG

u_long	cow_minsize = 0xFFF;	/* Minimum size (pages) before cow is used */

struct proc *cow_findshare();

cow_dup(childp, childpte, vpage, count, type)
   struct proc *childp;
   register struct pte *childpte;
   register unsigned vpage;
   register size_t count;
   int type;
{
	register struct pte *parentpte = vtopte(u.u_procp, vpage);

	/*
	 * If the process is too small we will gain
	 * nothing (and may actually lose) if we use
	 * copy-on-write; so ...
	 */
	if (u.u_procp->p_rssize < cow_minsize)
		return(vmdup(childp, childpte, vpage, count, type));

	COW_INCSTAT(dups);
	COW_ADDSTAT(duppages, count);
	COW_DEBUG(("cow_dup: proc 0x%x, pte 0x%x, vpage 0x%x, count %d, type %d\n",childp,childpte,vpage,count,type));
	while (count != 0)
	{
		register int i;

		count -= CLSIZE;

		COW_DEBUG((" %x", *(int *)parentpte));
		/*
		 * Fill on demand pages table entries
		 * can just be copied over.
		 */
		if ((int)parentpte <= 0)	/* jht -- firedoor */
			panic("cow_dup/E: vtopte() <= 0");
		if (parentpte->pg_fod)
		{
			vpage += CLSIZE;
			for (i=0; i<CLSIZE; ++i)
				*(int *)childpte++ = *(int *)parentpte++;
			continue;
		}

		/*
		 * If any of the pages are swapped then call
		 * vmdup to do the work.
		 */
		for (i=0; i<CLSIZE; ++i)
			if (parentpte[i].pg_v == 0 &&
			    parentpte[i].pg_shared == 0)
			{
				COW_ADDSTAT(vmdups, CLSIZE);
				COW_DEBUG(("v"));
				vmdup(childp, childpte, vpage, CLSIZE, type);
				childpte += CLSIZE;
				parentpte += CLSIZE;
				vpage += CLSIZE;
				goto cont2;
			}

		/*
		 * The pages in this click are already
		 * resident so we increment the share
		 * count, mark the pages shared and
		 * copy the pte's.
		 */
		COW_ADDSTAT(dupshare, CLSIZE);
		COW_DEBUG(("s"));
		if (parentpte->pg_shared == 0)
		{
			COW_ADDSTAT(firstshare, CLSIZE);
			COW_DEBUG(("S"));
			cmap[pgtocm(parentpte->pg_pfnum)].c_shcount = 1;
			parentpte->pg_shared = 1;
			*(int *)parentpte &= ~PG_PROT;
			*(int *)parentpte |= PG_URKR;
#ifdef s32
			/*
			 * On the S32 we must clear the valid
			 * bit since we cannot make individual
			 * pages read only.
			 */
			parentpte->pg_v = 0;
#endif s32
			distcl(parentpte);
			newptes(parentpte, vpage, CLSIZE);
		}
		else
			++cmap[pgtocm(parentpte->pg_pfnum)].c_shcount;
		for (i=0; i<CLSIZE; ++i)
			*(int *)childpte++ = *(int *)parentpte++;
		childp->p_rssize += CLSIZE;
		vpage += CLSIZE;
	cont2:
		;
	}
	COW_DEBUG(("\n"));
}

/*
 * A fault occurred on a shared page.  We
 * must allocate a new page, copy the old
 * one into it, and decrement the shared
 * count.
 */
cow_pagein(pte, vpage, type)
   register struct pte *pte;
   register unsigned vpage;
   int type;
{
	register struct cmap *c = &cmap[pgtocm(pte->pg_pfnum)];
	struct pte oldpte;

	COW_INCSTAT(pageins);
	COW_DEBUG(("cow_pagein: pte 0x%x = 0x%x, vpage 0x%x, type %d:", pte, *(int *)pte, vpage, type));
	*(int *)&oldpte = *(int *)pte;
	/*
	 * If the shared count is 0 then the last
	 * of the processes sharing this page has
	 * copied it so we can mark the page as
	 * unshared, make it writeable, and be on
	 * our way.
	 */
	if (c->c_shcount)
	{
		COW_ADDSTAT(copies, CLSIZE);

		/*
		 * If we are the rightful owners of the
		 * page then we must give it to someone
		 * else before we copy.
		 */
		if (u.u_procp - proc == c->c_ndx)
		{
			register struct proc *newowner;

			COW_INCSTAT(newowner);
			COW_DEBUG((" newowner"));
			COW_INCSTAT(rootfinds);
			newowner = cow_findshare(u.u_procp, vpage, pte);
			if (newowner == NULL)
				panic("cow_pagein: no sharing process");
			c->c_ndx = newowner - proc;
		}

		--c->c_shcount;
		if (freemem < CLSIZE)
			panic("cow_pagein: no mem");
		(void) memall(pte, CLSIZE, u.u_procp, type);
		COW_DEBUG((" copy to 0x%x", pte->pg_pfnum));
		/* BUG next line assumes that CLSIZE is 1 */
		copybuf(ptob(oldpte.pg_pfnum), ptob(pte->pg_pfnum), NBPG);
		pte->pg_prot = oldpte.pg_prot;
		pte->pg_v = 1;
		munlock(pte->pg_pfnum);
	}
	pte->pg_shared = 0;
	*(int *)pte &= ~PG_PROT;
	*(int *)pte |= PG_UW;
#ifdef s32
	/*
	 * On the S32 we must now set the valid
	 * bit since we cleared it in cow_dup()
	 * above.
	 */
	pte->pg_v = 1;
#endif s32
	distcl(pte);
	newptes(pte, vpage, CLSIZE);

	/*
	 * If the page is no longer shared then we
	 * convert it back into a normal page so that
	 * we will avoid more page faults than necessary.
	 */
	if (c->c_shcount == 0)
	{
		COW_INCSTAT(extrapagein);
		COW_DEBUG((" unshare"));
		pte = vtopte(&proc[c->c_ndx], vpage);
		pte->pg_shared = 0;
		*(int *)pte &= ~PG_PROT;
		*(int *)pte |= PG_UW;
#ifdef s32
		pte->pg_v = 1;
#endif s32
		distcl(pte);
#ifdef USE_CTXT
		ctxt_pageupdate(&proc[c->c_ndx], vpage, pte);
#endif USE_CTXT
	}
	COW_DEBUG(("\n"));
}

/*
 * Free virtual memory.  If the memory is shared
 * then just decrement the shared count.  If not
 * then call vmemfree().
 */
cow_vmemfree(pte, count)
   register struct pte *pte;
   register int count;
{
	register int savecount = count;
	register struct pte *savepte = pte;
	register int pcnt = 0;

	COW_INCSTAT(vmemfrees);
	COW_ADDSTAT(vmemfreepages, count);
	COW_DEBUG(("cow_vmemfree: pte 0x%x, count %d:", pte, count));
	/*
	 * Clear all page table entries which
	 * are currently shared.  This prevents
	 * vmemfree() from toing anything with
	 * them.  We must also decrement the
	 * shared count in the core map.
	 */
	while (count > 0)
	{
		COW_DEBUG((" 0x%x", *(int *)pte));
		if (pte->pg_fod) {
			/*
			 * Fixes problem with not checking fptes here.
			 */
			vmemfree(pte, CLSIZE);
			goto next;
		}
		if (pte->pg_shared)
		{
			register struct cmap *c = &cmap[pgtocm(pte->pg_pfnum)];
			register unsigned vpage = ptetov(u.u_procp, pte);
			register int i;

			COW_DEBUG(("s"));
			/*
			 * If we are the rightful owners of the
			 * page then we must give it to someone
			 * else before we copy.
			 */
			if (u.u_procp - proc == c->c_ndx)
			{
				register struct proc *newowner;

				COW_INCSTAT(newowner1);
				COW_DEBUG((" newowner"));
				COW_INCSTAT(rootfinds);
				newowner = cow_findshare(u.u_procp, vpage, pte);
				if (newowner == NULL)
					panic("cow_vmemfree: no sharing process");
				c->c_ndx = newowner - proc;
			}

			if (c->c_shcount)
				--c->c_shcount;
			pcnt += CLSIZE;
			for (i=0; i<CLSIZE; ++i)
				*(int *)&pte[i] &= PG_PROT;

			/*
			 * If the page is no longer shared then we
			 * convert it back into a normal page so that
			 * we will avoid more page faults than necessary.
			 */
			if (c->c_shcount == 0)
			{
				register struct pte *hispte;

				COW_INCSTAT(extraunshare);
				COW_DEBUG((" unshare"));
				hispte = vtopte(&proc[c->c_ndx], vpage);
				hispte->pg_shared = 0;
				*(int *)hispte &= ~PG_PROT;
				*(int *)hispte |= PG_UW;
#ifdef s32
				hispte->pg_v = 1;
#endif s32
				distcl(hispte);
#ifdef USE_CTXT
				ctxt_pageupdate(&proc[c->c_ndx], vpage, hispte);
#endif USE_CTXT
			}
		}
next:
		pte += CLSIZE;
		count -= CLSIZE;
	}
	COW_ADDSTAT(unshares, pcnt);
	COW_DEBUG(("; unshared %d\n", pcnt));
	return(vmemfree(savepte, savecount, 1/*Detach*/) + pcnt);
}

/*
 * Find the process ID of a process which is
 * sharing a particular page with this process.
 * We are given a virtual page number and a
 * pte and must check every descendent until
 * we find a match or have searched all of them.
 * Return a pointer to the sharing process if
 * found and NULL otherwise.
 */
struct proc *cow_findshare(rootp, vpage, rootpte)
   struct proc *rootp;
   register unsigned vpage;
   register struct pte *rootpte;
{
	register struct proc *nextp;

	COW_INCSTAT(finds);
	for (nextp = rootp->p_cptr; nextp; nextp = nextp->p_osptr)
	{
		register struct pte *pte = vtopte(nextp, vpage);
		register struct proc *foundp;

		COW_INCSTAT(searched);
		if (pte->pg_fod == 0 && pte->pg_pfnum == rootpte->pg_pfnum)
			return(nextp);

		/*
		 * Recursively search children's children.
		 */
		if (nextp->p_cptr &&
		    (foundp = cow_findshare(nextp, vpage, rootpte)) != NULL)
			return(foundp);
	}
	return(NULL);
}

#endif USE_COW
