/*
 * Copyright (c) 1986 Regents of the University of California.
 * All rights reserved.  The Berkeley software License Agreement
 * specifies the terms and conditions for redistribution.
 *
 *	@(#)kern_synch.c	1.5 (2.11BSD) 1999/9/13
 */

#include "param.h"
#include "../machine/seg.h"

#include "user.h"
#include "proc.h"
#include "buf.h"
#include "signal.h"
#include "signalvar.h"
#include "vm.h"
#include "kernel.h"
#include "systm.h"

#define	SQSIZE	16	/* Must be power of 2 */

#define	HASH(x)	(((int)x >> 5) & (SQSIZE - 1))
#define	SCHMAG	8/10

struct	proc *slpque[SQSIZE];

/*
 * Recompute process priorities, once a second
 */
schedcpu()
{
	register struct proc *p;
	register int a;

	wakeup((caddr_t)&lbolt);
	for (p = allproc; p != NULL; p = p->p_nxt) {
		if (p->p_time != 127)
			p->p_time++;
		/*
		 * this is where 2.11 does its real time alarms.  4.X uses
		 * timeouts, since it offers better than second resolution.
		 * Putting it here allows us to continue using use an int
		 * to store the number of ticks in the callout structure,
		 * since the kernel never has a timeout of greater than
		 * around 9 minutes.
		 */
		if (p->p_realtimer.it_value && !--p->p_realtimer.it_value) {
			psignal(p, SIGALRM);
			p->p_realtimer.it_value = p->p_realtimer.it_interval;
		}
		if (p->p_stat == SSLEEP || p->p_stat == SSTOP)
			if (p->p_slptime != 127)
				p->p_slptime++;
		if (p->p_slptime > 1)
			continue;
		a = (p->p_cpu & 0377) * SCHMAG + p->p_nice;
		if (a < 0)
			a = 0;
		if (a > 255)
			a = 255;
		p->p_cpu = a;
		if (p->p_pri >= PUSER)
			setpri(p);
	}
	vmmeter();
	if (runin!=0) {
		runin = 0;
		wakeup((caddr_t)&runin);
	}
	++runrun;			/* swtch at least once a second */
	timeout(schedcpu, (caddr_t)0, hz);
}

/*
 * Recalculate the priority of a process after it has slept for a while.
 */
updatepri(p)
	register struct proc *p;
{
	register int a = p->p_cpu & 0377;

	p->p_slptime--;		/* the first time was done in schedcpu */
	while (a && --p->p_slptime)
		a = (SCHMAG * a) /* + p->p_nice */;
	if (a < 0)
		a = 0;
	if (a > 255)
		a = 255;
	p->p_cpu = a;
	(void) setpri(p);
}

/*
 * General sleep call "borrowed" from 4.4BSD - the 'wmesg' parameter was
 * removed due to data space concerns.  Sleeps at most timo/hz seconds
 * 0 means no timeout). NOTE: timeouts in 2.11BSD use a signed int and 
 * thus can be at most 32767 'ticks' or about 540 seconds in the US with 
 * 60hz power (~650 seconds if 50hz power is being used).
 *
 * If 'pri' includes the PCATCH flag signals are checked before and after
 * sleeping otherwise  signals are not checked.   Returns 0 if a wakeup was
 * done, EWOULDBLOCK if the timeout expired, ERESTART if the current system
 * call should be restarted, and EINTR if the system call should be
 * interrupted and EINTR returned to the user process.
*/

int
tsleep(ident, priority, timo)
	caddr_t	ident;
	int	priority;
	u_short	timo;
	{
	register struct proc *p = u.u_procp;
	register struct proc **qp;
	int	s;
	int	sig, catch = priority & PCATCH;
	void	endtsleep();

	s = splhigh();
	if	(panicstr)
		{
/*
 * After a panic just give interrupts a chance then just return.  Don't
 * run any other procs (or panic again below) in case this is the idle
 * process and already asleep.  The splnet should be spl0 if the network
 * was being used but for now avoid network interrupts that might cause
 * another panic.
*/
		(void)_splnet();
		noop();
		splx(s);
		return;
		}
#ifdef	DIAGNOSTIC
	if	(ident == NULL || p->p_stat != SRUN)
		panic("tsleep");
#endif
	p->p_wchan = ident;
	p->p_slptime = 0;
	p->p_pri = priority & PRIMASK;
	qp = &slpque[HASH(ident)];
	p->p_link = *qp;
	*qp =p;
	if	(timo)
		timeout(endtsleep, (caddr_t)p, timo);
/*
 * We put outselves on the sleep queue and start the timeout before calling
 * CURSIG as we could stop there and a wakeup or a SIGCONT (or both) could
 * occur while we were stopped.  A SIGCONT would cause us to be marked SSLEEP
 * without resuming us thus we must be ready for sleep when CURSIG is called.
 * If the wakeup happens while we're stopped p->p_wchan will be 0 upon 
 * return from CURSIG.
*/
	if	(catch)
		{
		p->p_flag |= P_SINTR;
		if	(sig = CURSIG(p))
			{
			if	(p->p_wchan)
				unsleep(p);
			p->p_stat = SRUN;
			goto resume;
			}
		if	(p->p_wchan == 0)
			{
			catch = 0;
			goto resume;
			}
		}
	else
		sig = 0;
	p->p_stat = SSLEEP;
	u.u_ru.ru_nvcsw++;
	swtch();
resume:
	splx(s);
	p->p_flag &= ~P_SINTR;
	if	(p->p_flag & P_TIMEOUT)
		{
		p->p_flag &= ~P_TIMEOUT;
		if	(sig == 0)
			return(EWOULDBLOCK);
		}
	else if (timo)
		untimeout(endtsleep, (caddr_t)p);
	if	(catch && (sig != 0 || (sig = CURSIG(p))))
		{
		if	(u.u_sigintr & sigmask(sig))
			return(EINTR);
		return(ERESTART);
		}
	return(0);
	}

/*
 * Implement timeout for tsleep above.  If process hasn't been awakened
 * (p_wchan non zero) then set timeout flag and undo the sleep.  If proc
 * is stopped just unsleep so it will remain stopped.
*/

void
endtsleep(p)
	register struct proc *p;
	{
	register int	s;

	s = splhigh();
	if	(p->p_wchan)
		{
		if	(p->p_stat == SSLEEP)
			setrun(p);
		else
			unsleep(p);
		p->p_flag |= P_TIMEOUT;
		}
	splx(s);
	}

/*
 * Give up the processor till a wakeup occurs on chan, at which time the 
 * process enters the scheduling queue at priority pri.
 *
 * This routine was rewritten to use 'tsleep'.  The  old behaviour of sleep
 * being interruptible (if 'pri>PZERO') is emulated by setting PCATCH and
 * then performing the 'longjmp' if the return value of 'tsleep' is 
 * ERESTART.
 * 
 * Callers of this routine must be prepared for premature return, and check 
 * that the reason for sleeping has gone away.
 */
sleep(chan, pri)
	caddr_t chan;
	int pri;
	{
	register int priority = pri;
	
	if	(pri > PZERO)
		priority |= PCATCH;

	u.u_error = tsleep(chan, priority, 0);
/*
 * sleep does not return anything.  If it was a non-interruptible sleep _or_ 
 * a successful/normal sleep (one for which a wakeup was done) then return.
*/
	if	((priority & PCATCH) == 0 || (u.u_error == 0))
		return;
/*
 * XXX - compatibility uglyness.
 *
 * The tsleep() above will leave one of the following in u_error:
 *
 * 0 - a wakeup was done, this is handled above
 * EWOULDBLOCK - since no timeout was passed to tsleep we will not see this
 * EINTR - put into u_error for trap.c to find (interrupted syscall)
 * ERESTART - system call to be restared
*/
	longjmp(u.u_procp->p_addr, &u.u_qsave);
	/*NOTREACHED*/
	}

/*
 * Remove a process from its wait queue
 */
unsleep(p)
	register struct proc *p;
{
	register struct proc **hp;
	register int s;

	s = splhigh();
	if (p->p_wchan) {
		hp = &slpque[HASH(p->p_wchan)];
		while (*hp != p)
			hp = &(*hp)->p_link;
		*hp = p->p_link;
		p->p_wchan = 0;
	}
	splx(s);
}

/*
 * Wake up all processes sleeping on chan.
 */
wakeup(chan)
	register caddr_t chan;
{
	register struct proc *p, **q;
	struct proc **qp;
	int s;
	mapinfo map;

	/*
	 * Since we are called at interrupt time, must insure normal
	 * kernel mapping to access proc.
	 */
	savemap(map);
	s = splclock();
	qp = &slpque[HASH(chan)];
restart:
	for (q = qp; p = *q; ) {
		if (p->p_stat != SSLEEP && p->p_stat != SSTOP)
			panic("wakeup");
		if (p->p_wchan==chan) {
			p->p_wchan = 0;
			*q = p->p_link;
			if (p->p_stat == SSLEEP) {
				/* OPTIMIZED INLINE EXPANSION OF setrun(p) */
				if (p->p_slptime > 1)
					updatepri(p);
				p->p_slptime = 0;
				p->p_stat = SRUN;
				if (p->p_flag & SLOAD)
					setrq(p);
				/*
				 * Since curpri is a usrpri,
				 * p->p_pri is always better than curpri.
				 */
				runrun++;
				if ((p->p_flag&SLOAD) == 0) {
					if (runout != 0) {
						runout = 0;
						wakeup((caddr_t)&runout);
					}
				}
				/* END INLINE EXPANSION */
				goto restart;
			}
			p->p_slptime = 0;
		} else
			q = &p->p_link;
	}
	splx(s);
	restormap(map);
}

/*
 * Set the process running;
 * arrange for it to be swapped in if necessary.
 */
setrun(p)
	register struct proc *p;
{
	register int s;

	s = splhigh();
	switch (p->p_stat) {

	case 0:
	case SWAIT:
	case SRUN:
	case SZOMB:
	default:
		panic("setrun");

	case SSTOP:
	case SSLEEP:
		unsleep(p);		/* e.g. when sending signals */
		break;

	case SIDL:
		break;
	}
	if (p->p_slptime > 1)
		updatepri(p);
	p->p_stat = SRUN;
	if (p->p_flag & SLOAD)
		setrq(p);
	splx(s);
	if (p->p_pri < curpri)
		runrun++;
	if ((p->p_flag&SLOAD) == 0) {
		if (runout != 0) {
			runout = 0;
			wakeup((caddr_t)&runout);
		}
	}
}

/*
 * Set user priority.
 * The rescheduling flag (runrun)
 * is set if the priority is better
 * than the currently running process.
 */
setpri(pp)
	register struct proc *pp;
{
	register int p;

	p = (pp->p_cpu & 0377)/16;
	p += PUSER + pp->p_nice;
	if (p > 127)
		p = 127;
	if (p < curpri)
		runrun++;
	pp->p_pri = p;
	return (p);
}

/*
 * 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, e.g. it's not worth putting an
 * spl() in.
 */
swtch()
{
	register struct proc *p, *q;
	register int n;
	struct proc *pp, *pq;
	int s;

#ifdef DIAGNOSTIC
	extern struct buf *hasmap;
	if (hasmap)
		panic("swtch hasmap");
#endif
#ifdef UCB_METER
	cnt.v_swtch++;
#endif
	/* If not the idle process, resume the idle process. */
	if (u.u_procp != &proc[0]) {
		if (setjmp(&u.u_rsave)) {
			sureg();
			return;
		}
		if (u.u_fpsaved == 0) {
			savfp(&u.u_fps);
			u.u_fpsaved = 1;
		}
		longjmp(proc[0].p_addr, &u.u_qsave);
	}
	/*
	 * 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 rsave, and return to sched().
	 */
	if (setjmp(&u.u_qsave)==0 && setjmp(&u.u_rsave))
		return;
loop:
	s = splhigh();
	noproc = 0;
	runrun = 0;
#ifdef DIAGNOSTIC
	for (p = qs; p; p = p->p_link)
		if (p->p_stat != SRUN)
			panic("swtch SRUN");
#endif
	pp = NULL;
	q = NULL;
	n = 128;
	/*
	 * search for highest-priority runnable process
	 */
	for (p = qs; p; p = p->p_link) {
		if (p->p_flag & SLOAD && p->p_pri < n) {
			pp = p;
			pq = q;
			n = p->p_pri;
		}
		q = p;
	}
	/*
	 * if no process is runnable, idle.
	 */
	p = pp;
	if (p == NULL) {
		idle();
		goto loop;
	}
	if (pq)
		pq->p_link = p->p_link;
	else
		qs = p->p_link;
	curpri = n;
	splx(s);
	/*
	 * the rsave (ssave) contents are interpreted
	 * in the new address space
	 */
	n = p->p_flag & SSWAP;
	p->p_flag &= ~SSWAP;
	longjmp(p->p_addr, n ? &u.u_ssave: &u.u_rsave);
}

setrq(p)
	register struct proc *p;
{
	register int s;

	s = splhigh();
#ifdef DIAGNOSTIC
	{			/* see if already on the run queue */
		register struct proc *q;

		for (q = qs;q != NULL;q = q->p_link)
			if (q == p)
				panic("setrq");
	}
#endif
	p->p_link = qs;
	qs = p;
	splx(s);
}

/*
 * Remove runnable job from run queue.  This is done when a runnable job
 * is swapped out so that it won't be selected in swtch().  It will be
 * reinserted in the qs with setrq when it is swapped back in.
 */
remrq(p)
	register struct proc *p;
{
	register struct proc *q;
	register int s;

	s = splhigh();
	if (p == qs)
		qs = p->p_link;
	else {
		for (q = qs; q; q = q->p_link)
			if (q->p_link == p) {
				q->p_link = p->p_link;
				goto done;
			}
			panic("remrq");
	}
done:
	splx(s);
}
