/* @(#)slp.c	1.8 */
#include "sys/param.h"
#include "sys/config.h"
#include "sys/mmu.h"
#include "sys/types.h"
#include "sys/sysmacros.h"
#include "sys/dir.h"
#include "sys/signal.h"
#include "sys/user.h"
#include "sys/proc.h"
#include "sys/context.h"
#include "sys/text.h"
#include "sys/systm.h"
#include "sys/sysinfo.h"
#include "sys/map.h"
#include "sys/file.h"
#include "sys/inode.h"
#include "sys/buf.h"
#include "sys/var.h"
#include "sys/ipc.h"
#include "sys/shm.h"
#include "sys/errno.h"
#include "sys/scat.h"

typedef int	mem_t;

#define	NHSQUE	64	/* must be power of 2 */
#define	sqhash(X)	(&hsque[((int)(X) >> 3) & (NHSQUE-1)])
struct proc *hsque[NHSQUE];
char	runin, runout, runrun, curpri;
struct proc *curproc, *runq;

/*
 * sleep according to a cpu adjusted priority
 */
asleep(chan, pri)
caddr_t chan;
{
	return(sleep(chan, pri + ((u.u_procp->p_cpu&0xFF) >> 5)));
}

/*
 * Give up the processor till a wakeup occurs
 * on chan, at which time the process
 * enters the scheduling queue at priority pri.
 * The most important effect of pri is that when
 * pri<=PZERO a signal cannot disturb the sleep;
 * if pri>PZERO signals will be processed.
 * Callers of this routine must be prepared for
 * premature return, and check that the reason for
 * sleeping has gone away.
 */
#define	TZERO	10
sleep(chan, disp)
caddr_t chan;
{
	register struct proc *rp = u.u_procp;
	register struct proc **q = sqhash(chan);
	register s;

	s = splhi();
	if (panicstr) {
		SPL0();
		splx(s);
		return(0);
	}
	rp->p_stat = SSLEEP;
	rp->p_wchan = chan;
	rp->p_link = *q;
	*q = rp;
	if (rp->p_time > TZERO)
		rp->p_time = TZERO;
	if ((rp->p_pri = (disp&PMASK)) > PZERO) {
		if (rp->p_sig && issig()) {
			rp->p_wchan = 0;
			rp->p_stat = SRUN;
			*q = rp->p_link;
			SPL0();
			goto psig;
		}
		SPL0();
		if (runin != 0) {
			runin = 0;
			wakeup((caddr_t)&runin);
		}
		swtch();
		if (rp->p_sig && issig())
			goto psig;
	} else {
		SPL0();
		swtch();
	}
	splx(s);
	return(0);

	/*
	 * If priority was low (>PZERO) and there has been a signal,
	 * if PCATCH is set, return 1, else
	 * execute non-local goto to the qsav location.
	 */
psig:
	splx(s);
	if (disp&PCATCH)
		return(1);
#ifdef NONSCATLOAD
	resume(u.u_procp->p_addr, u.u_qsav);
#else
	resume(ixtoc(u.u_procp->p_scat), u.u_qsav);
#endif
	/* NOTREACHED */
}

/*
 * Wake up all processes sleeping on chan.
 */
wakeup(chan)
register caddr_t chan;
{
	register struct proc *p;
	register struct proc **q;
	register s;

	s = splhi();
	for (q = sqhash(chan); p = *q; )
		if (p->p_wchan==chan && p->p_stat==SSLEEP) {
			p->p_stat = SRUN;
			p->p_wchan = 0;
			/* take off sleep queue, put on run queue */
			*q = p->p_link;
			p->p_link = runq;
			runq = p;
			if (!(p->p_flag&SLOAD)) {
				p->p_time = 0;
				/* defer setrun to avoid breaking link chain */
				if (runout > 0)
					runout = -runout;
			} else if (p->p_pri < curpri)
				runrun++;
		} else
			q = &p->p_link;
	if (runout < 0) {
		runout = 0;
		setrun(&proc[0]);
	}
	splx(s);
}

setrq(p)
register struct proc *p;
{
	register struct proc *q;
	register s;

	s = splhi();
	for(q=runq; q!=NULL; q=q->p_link)
		if (q == p) {
			printf("proc on q\n");
			goto out;
		}
	p->p_link = runq;
	runq = p;
out:
	splx(s);
}

/*
 * Set the process running;
 * arrange for it to be swapped in if necessary.
 */
setrun(p)
register struct proc *p;
{
	register struct proc **q;
	register s;

	s = splhi();
	if (p->p_stat == SSLEEP) {
		/* take off sleep queue */
		for (q = sqhash(p->p_wchan); *q != p; q = &(*q)->p_link) ;
		*q = p->p_link;
		p->p_wchan = 0;
	} else if (p->p_stat == SRUN) {
		/* already on run queue - just return */
		splx(s);
		return;
	}
	/* put on run queue */
	p->p_stat = SRUN;
	p->p_link = runq;
	runq = p;
	if (!(p->p_flag&SLOAD)) {
		p->p_time = 0;
		if (runout > 0) {
			runout = 0;
			setrun(&proc[0]);
		}
	} else if (p->p_pri < curpri)
		runrun++;
	splx(s);
}

/*
 * The main loop of the scheduling (swapping) process.
 * The basic idea is:
 *  see if anyone wants to be swapped in;
 *  swap out processes until there is room;
 *  swap him in;
 *  repeat.
 * The runout flag is set whenever someone is swapped out.
 * Sched sleeps on it awaiting work.
 *
 * Sched sleeps on runin whenever it cannot find enough
 * memory (by swapping out or otherwise) to fit the
 * selected swapped process.  It is awakened when the
 * memory situation changes and in any case once per second.
 */
sched()
{
	register struct proc *rp, *p;
	register outage, inage;
	int maxbad;
	int tmp;

	/*
	 * find user to swap in;
	 * of users ready, select one out longest
	 */

loop:
	SPL6();
	outage = -20000;
#ifdef NONSCATLOAD
	for (rp = &proc[0]; rp < (struct proc *)v.ve_proc; rp++)
	if (rp->p_stat==SRUN && (rp->p_flag&SLOAD) == 0 &&
	    rp->p_time > outage) {
		p = rp;
		outage = rp->p_time;
	}
#else
	for (rp = &proc[0]; rp < (struct proc *)v.ve_proc; rp++)
	if ((rp->p_flag&(SSWAPIT|SLOAD)) == (SSWAPIT|SLOAD)) {
		p = rp;
		SPL0();
		goto swapit;
	} else if (rp->p_stat==SRUN && (rp->p_flag&SLOAD) == 0 &&
	    rp->p_time > outage) {
		p = rp;
		outage = rp->p_time;
	}
#endif
	/*
	 * If there is no one there, wait.
	 */
	if (outage == -20000) {
		runout++;
		(void) sleep((caddr_t)&runout, PSWP);
		goto loop;
	}
	SPL0();

	/*
	 * See if there is memory for that process;
	 * if so, swap it in.
	 */

	if (swapin(p))
		goto loop;

	/*
	 * none found.
	 * look around for memory.
	 * Select the largest of those sleeping
	 * at bad priority; if none, select the oldest.
	 */

	SPL6();
	p = NULL;
	maxbad = 0;
	inage = 0;
	for (rp = &proc[0]; rp < (struct proc *)v.ve_proc; rp++) {
		if (rp->p_stat==SZOMB ||
		    (rp->p_flag&(SSYS|SLOCK|SLOAD))!=SLOAD)
			continue;
		if (rp->p_textp && rp->p_textp->x_flag&XLOCK)
			continue;
		if (rp->p_stat==SSLEEP || rp->p_stat==SSTOP) {
			tmp = rp->p_pri - PZERO + rp->p_time;
			if (maxbad < tmp) {
				p = rp;
				maxbad = tmp;
			}
		} else if (maxbad<=0 && rp->p_stat==SRUN) {
			tmp = rp->p_time + rp->p_nice - NZERO;
			if (tmp > inage) {
				p = rp;
				inage = tmp;
			}
		}
	}
	SPL0();
	/*
	 * Swap found user out if sleeping at bad pri,
	 * or if he has spent at least 2 seconds in memory and
	 * the swapped-out process has spent at least 2 seconds out.
	 * Otherwise wait a bit and try again.
	 */
	if (maxbad>0 || (outage>=2 && inage>=2)) {
#ifndef NONSCATLOAD
	swapit:
#endif
		p->p_flag &= ~SLOAD;
		xswap(p, 1, 0);
		goto loop;
	}
	SPL6();
	runin++;
	(void) sleep((caddr_t)&runin, PSWP);
	goto loop;
}

/*
 * Swap a process in.
 */
#ifdef NONSCATLOAD
swapin(p)
register struct proc *p;
{
	register struct text *xp;
	register int a, x;
	int ta;

	if ((a = malloc(coremap, p->p_size)) == NULL)
		return(0);
	if (xp = p->p_textp) {
		xlock(xp);
		if (!xmlink(xp) && xp->x_ccount==0) {
			if ((x = malloc(coremap, xp->x_size)) == NULL) {
				mfree(coremap, p->p_size, a);
				if ((x = malloc(coremap, xp->x_size)) == NULL) {
					xunlock(xp);
					return(0);
				}
				if ((a = malloc(coremap, p->p_size)) == NULL) {
					mfree(coremap, xp->x_size, x);
					xunlock(xp);
					return(0);
				}
			}
			xp->x_caddr = x;
			if ((xp->x_flag&XLOAD)==0)
				swap(xp->x_daddr, x, xp->x_size, B_READ);
		}
		xp->x_ccount++;
		xunlock(xp);
	}
	if (p->p_xaddr[0]) {
		ta = a;
		for (x=0; x < NSCATSWAP; x++) {
			if (p->p_xaddr[x] == 0)
				continue;
			swap(p->p_xaddr[x], a, p->p_xsize[x], B_READ);
			mfree(swapmap, ctod(p->p_xsize[x]), (int)p->p_xaddr[x]);
			a += p->p_xsize[x];
			p->p_xaddr[x] = 0;
		}
		p->p_addr = ta;
	} else {
		swap((daddr_t)p->p_dkaddr, a, p->p_size, B_READ);
		mfree(swapmap, ctod(p->p_size), (int)p->p_dkaddr);
		p->p_addr = a;
	}
	cxrelse(p->p_context);
	p->p_flag |= SLOAD;
	p->p_time = 0;
	return(1);
}
#else
swapin(p)
register struct proc *p;
{
	register struct text *xp;
	register int a, x;
	int ta;

	if (p->p_flag&SCONTIG) {
		if ((a = cmemalloc(p->p_size)) == NULL)
			return(0);
	} else if ((a = memalloc(p->p_size)) == NULL)
		return(0);
	if (xp = p->p_textp) {
		xlock(xp);
		if (!xmlink(xp) && xp->x_ccount==0) {
			if ((x = memalloc(xp->x_size)) == NULL) {
				memfree(a);
				xunlock(xp);
				return(0);
			}
			xp->x_scat = x;
			if ((xp->x_flag&XLOAD)==0)
				(void) swap(xp->x_daddr, x, xp->x_size, B_READ);
		}
		xp->x_ccount++;
		xunlock(xp);
	}
	p->p_flag |= SNOMMU;	/* swapping in, do not set mmu registers */
	if (p->p_xaddr[0]) {
		ta = a;
		for (x=0; x < NSCATSWAP; x++) {
			if (p->p_xaddr[x] == 0)
				continue;
			a = swap(p->p_xaddr[x], a, p->p_xsize[x], B_READ);
			mfree(swapmap, ctod(p->p_xsize[x]), (int)p->p_xaddr[x]);
			p->p_xaddr[x] = 0;
		}
		p->p_scat = ta;
	} else {
		(void) swap((daddr_t)p->p_dkaddr, a, p->p_size, B_READ);
		mfree(swapmap, ctod(p->p_size), (int)p->p_dkaddr);
		p->p_scat = a;
	}
	p->p_flag &= ~SNOMMU;
	p->p_addr = ixtoc(p->p_scat);
	cxrelse(p->p_context);
	p->p_flag |= SLOAD;
	p->p_time = 0;
	return(1);
}
#endif

/*
 * put the current process on
 * the Q of running processes and
 * call the scheduler.
 */
qswtch()
{
	setrq(u.u_procp);
	swtch();
}

/*
 * This routine is called to reschedule the CPU.
 * if the calling process is not in RUN state,
 * arrangements for it to restart must have
 * been made elsewhere, usually by calling via sleep.
 * There is a race here. A process may become
 * ready after it has been examined.
 * In this case, idle() will be called and
 * will return in at most 1HZ time.
 * i.e. its not worth putting an spl() in.
 */
swtch()
{
	register n;
	register struct proc *p, *q, *pp, *pq;
#ifdef mc68881		/* MC68881 floating-point coprocessor */
	extern short fp881;		/* is there an MC68881? */
#endif mc68881

	/*
	 * If not the idle process, resume the idle process.
	 */
	sysinfo.pswitch++;
	if (u.u_procp != &proc[0]) {
		if (save(u.u_rsav)) {
			sureg();
			return;
		}
#ifdef FLOAT		/* sky floating point board */
		if (u.u_fpinuse && u.u_fpsaved==0) {
			savfp();
			u.u_fpsaved = 1;
		}
#endif
#ifdef mc68881		/* MC68881 floating-point coprocessor */
		if (fp881)
			fpsave();
#endif mc68881
#ifdef NONSCATLOAD
		resume(proc[0].p_addr, u.u_qsav);
#else
		resume(ixtoc(proc[0].p_scat), u.u_qsav);
#endif
	}
	/*
	 * The first save returns nonzero when proc 0 is resumed
	 * by another process (above); then the second is not done
	 * and the process-search loop is entered.
	 *
	 * The first save returns 0 when swtch is called in proc 0
	 * from sched().  The second save returns 0 immediately, so
	 * in this case too the process-search loop is entered.
	 * Thus when proc 0 is awakened by being made runnable, it will
	 * find itself and resume itself at rsav, and return to sched().
	 */
	if (save(u.u_qsav) == 0 && save(u.u_rsav))
		return;
loop:
	SPL6();
	runrun = 0;
	/*
	 * Search for highest-priority runnable process
	 */
	if (p = runq) {
		q = NULL;
		pp = NULL;
		n = 128;
		do {
			if ((p->p_flag&SLOAD) && p->p_pri <= n) {
				pp = p;
				pq = q;
				n = p->p_pri;
			}
			q = p;
		} while (p = p->p_link);
	} else
		goto cont;
	/*
	 * If no process is runnable, idle.
	 */
	if (pp == NULL) {
cont:		curpri = PIDLE;
		curproc = &proc[0];
		idle();
		goto loop;
	}
	p = pp;
	q = pq;
	if (q == NULL)
		runq = p->p_link;
	else
		q->p_link = p->p_link;
	curpri = n;
	curproc = p;
	SPL0();
	/*
	 * The rsav (ssav) contents are interpreted in the new address space
	 */
	n = p->p_flag&SSWAP;
	p->p_flag &= ~SSWAP;
#ifdef NONSCATLOAD
	resume(p->p_addr, n? u.u_ssav: u.u_rsav);
#else
	resume(ixtoc(p->p_scat), n? u.u_ssav: u.u_rsav);
#endif
}

/*
 * Create a new process-- the internal version of
 * sys fork.
 * It returns 1 in the new process, 0 in the old.
 */
newproc(i)
{
	register struct proc *rpp, *rip;
	register struct user *up;
	register n;
	register a;
	struct proc *pend;
	static mpid;

	/*
	 * First, just locate a slot for a process
	 * and copy the useful info from this process into it.
	 * The panic "cannot happen" because fork has already
	 * checked for the existence of a slot.
	 */
	up = &u;
	rpp = NULL;
retry:
	mpid++;
	if (mpid >= MAXPID) {
		mpid = 0;
		goto retry;
	}
	rip = &proc[0];
	n = (struct proc *)v.ve_proc - rip;
	a = 0;
	do {
		if (rip->p_stat == NULL) {
			if (rpp == NULL)
				rpp = rip;
			continue;
		}
		if (rip->p_pid==mpid)
			goto retry;
		if (rip->p_uid == up->u_ruid)
			a++;
		pend = rip;
	} while(rip++, --n);
	if (rpp==NULL) {
		if ((struct proc *)v.ve_proc >= &proc[(short)v.v_proc]) {
			if (i) {
				syserr.procovf++;
				up->u_error = EAGAIN;
				return(-1);
			} else
				panic("no procs");
		}
		rpp = (struct proc *)v.ve_proc;
	}
	if (rpp > pend)
		pend = rpp;
	pend++;
#ifdef lint
	v.ve_proc = pend;
#else
	v.ve_proc = (char *)pend;
#endif
	if (up->u_uid && up->u_ruid) {
		if (rpp == &proc[(short)(v.v_proc-1)] || a > v.v_maxup) {
			up->u_error = EAGAIN;
			return(-1);
		}
	}
	/*
	 * make proc entry for new proc
	 */

	rip = up->u_procp;
	rpp->p_stat = SRUN;
	rpp->p_clktim = 0;
	rpp->p_flag = SLOAD;
	rpp->p_uid = rip->p_uid;
	rpp->p_suid = rip->p_suid;
	rpp->p_pgrp = rip->p_pgrp;
	rpp->p_nice = rip->p_nice;
	rpp->p_textp = rip->p_textp;
	rpp->p_pid = mpid;
	rpp->p_ppid = rip->p_pid;
	rpp->p_time = 0;
	rpp->p_cpu = rip->p_cpu;
	rpp->p_sigign = rip->p_sigign;
	rpp->p_pri = PUSER + rip->p_nice - NZERO;
#ifndef NONSCATLOAD
	rpp->p_scat = rip->p_scat;
#endif
	rpp->p_addr = rip->p_addr;
	rpp->p_size = rip->p_size;

	/*
	 * make duplicate entries
	 * where needed
	 */

	for(n=0; n<NOFILE; n++)
		if (up->u_ofile[n] != NULL)
			up->u_ofile[n]->f_count++;
	if (rpp->p_textp != NULL) {
		rpp->p_textp->x_count++;
		rpp->p_textp->x_ccount++;
	}
	up->u_cdir->i_count++;
	if (up->u_rdir)
		up->u_rdir->i_count++;

	shmfork(rpp, rip);

	/*
	 * Partially simulate the environment
	 * of the new process so that when it is actually
	 * created (by copying) it will look right.
	 */
	up->u_procp = rpp;
	curproc = rpp;
	/*
	 * When the resume is executed for the new process,
	 * here's where it will resume.
	 */
	if (save(up->u_ssav)) {
		sureg();
		return(1);
	}
	/*
	 * If there is not enough memory for the
	 * new process, swap out the current process to generate the
	 * copy.
	 */
	if (procdup(rpp) == NULL) {
		rip->p_stat = SIDL;
		xswap(rpp, 0, 0);
		rip->p_stat = SRUN;
	}
	up->u_procp = rip;
	curproc = rip;
	if (rip != &proc[0])	/* only do if not scheduler */
		sureg();
	setrq(rpp);
	rpp->p_flag |= SSWAP;
	up->u_rval1 = rpp->p_pid;	/* parent returns pid of child */
	return(0);
}

/*
 * Change the size of the data+stack regions of the process.
 * If the size is shrinking, it's easy-- just release the extra core.
 * If it's growing, and there is core, just allocate it
 * and copy the image, taking care to reset registers to account
 * for the fact that the system's stack has moved.
 * If there is no memory, arrange for the process to be swapped
 * out after adjusting the size requirement-- when it comes
 * in, enough memory will be allocated.
 *
 * After the expansion, the caller will take care of copying
 * the user's stack towards or away from the data area.
 */
#ifdef NONSCATLOAD
expand(newsize)
register newsize;
{
	register struct proc *p;
	register a1, a2;
	register i, n;

	p = u.u_procp;
	n = p->p_size;
	p->p_size = newsize;
	if (n == newsize)
		return;
	a1 = p->p_addr;
	if (n >= newsize) {
#ifdef EXPANDTRACE
		printf("expand:shrinking process by %d clicks\n", n-newsize);
#endif
		mfree(coremap, (mem_t)(n-newsize), (mem_t)(a1+newsize));
		return;
	}
	if (save(u.u_ssav)) {
		sureg();
		return;
	}
	/*
	 * See if can just expand in place
	 */
loop:
	a2 = mallocat(coremap, newsize-n, (mem_t)(a1+n));
	if (a2 != NULL) {
#ifdef EXPANDTRACE
		printf("expanding in place by %d clicks at click %d\n",
			newsize-n, a1+n);
#endif
		cxrelse(p->p_context);
		sureg();
		return;
	}
	/*
	 * Will we be releasing shared text space anyway.
	 * If so, then release it now and try in place
	 * expansion again.
	 */
	if ((a2 = domall(coremap, (mem_t)newsize)) == NULL)
		if (xmrelse())
			goto loop;
	if (a2 == NULL && (a2 = malloc(coremap, (mem_t)newsize)) == NULL) {
#ifdef EXPANDTRACE
		printf("expand:calling xswap\n");
#endif
		xswap(p, 1, n);
		p->p_flag |= SSWAP;
		qswtch();
		/* no return */
	}
	p->p_addr = a2;
	for(i=0; i<n; i++)
		copyseg(a1+i, a2+i);
#ifdef EXPANDTRACE
	printf("expand:copyseg %d from 0x%x to 0x%x\n", n, a1, a2);
#endif
	mfree(coremap, (mem_t)n, (mem_t)a1);
	cxrelse(p->p_context);
	resume((mem_t)a2, u.u_ssav);
}
#else
expand(newsize)
register newsize;
{
	register struct scatter *s;
	register struct proc *p;
	register a1, a2;
	register i, n;
	int t;

	p = u.u_procp;
	n = p->p_size;
	p->p_size = newsize;
	if (n == newsize)
		return;
	s = scatmap;
	a1 = p->p_scat;
	if (n >= newsize) {
		/*
		 * shrink memory
		 */
		for (i=1; i<newsize; i++)
			a1 = s[a1].sc_index;
		t = scatfreelist.sc_index;
		scatfreelist.sc_index = s[a1].sc_index;
		i = a1;
		while ((a2 = s[a1].sc_index) != SCATEND)
			a1 = a2;
		s[i].sc_index = SCATEND;
		s[a1].sc_index = t;
		nscatfree += n-newsize;
		/*
		 * Wake scheduler when freeing memory
		 */
		if (runin) {
			runin = 0;
			wakeup((caddr_t)&runin);
		}
		return;
	}
	if (save(u.u_ssav)) {
		sureg();
		return;
	}
	/*
	 * expand memory
	 */
	if (a2 = memalloc(newsize-n)) {
		/* printf("expanding from %d clicks to %d clicks\n",
			n, newsize); */
		for (i=1; i<n; i++)
			a1 = s[a1].sc_index;
		if (s[a1].sc_index != SCATEND)
			printf("expand:SCATEND expected\n");
		s[a1].sc_index = a2;
		return;
	}
	xswap(p, 1, n);
	p->p_flag |= SSWAP;
	qswtch();
	/* no return */
}
#endif

#ifndef NONSCATLOAD
checkscat(s)
char *s;
{
	register struct proc *p;
	register struct text *xp;
	register i;

	i = countscat(scatfreelist.sc_index);
	printf("%s nscatfree=%d actual=%d\n", s, nscatfree, i);
	if (nscatfree < 30) {
		printf("    freelist chain is ");
		dumpscat(scatfreelist.sc_index);
	}
	for (p = &proc[0]; p < (struct proc *)v.ve_proc; p++) {
		if (p->p_stat==0)
			continue;
		xp = p->p_textp;
		printf("    pid=%d %d used (%d text)\n",
			p->p_pid, countscat(p->p_scat),
			xp ? countscat(xp->x_scat) : 0);
		dumpscat(p->p_scat);
		if (xp) {
			dumpscat(xp->x_scat);
		}
	}
}

dumpscat(a1)
register a1;
{
	register struct scatter *s;

	s = scatmap;
	printf("     ");
	while (a1 != SCATEND) {
		printf(" %d,%x", a1, ixtoc(a1));
		a1 = s[a1].sc_index;
	}
	printf("\n");
}

countscat(a1)
register a1;
{
	register struct scatter *s;
	register i;

	i = 0;
	s = scatmap;
	while (a1 != SCATEND) {
		a1 = s[a1].sc_index;
		i++;
	}
	return(i);
}
#endif
