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

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

/*
 * Copyright (c) 1982, 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	7.1 (Berkeley) 6/5/86
 */

#include "../machine/pte.h"
#if defined(ibm032) && defined(DEBUG)
#include "../machine/debug.h"
#else
#define DEBUGF(cond,stmt)
#endif ibm032 && DEBUG

#include "param.h"
#include "systm.h"
#include "dir.h"
#include "user.h"
#include "proc.h"
#include "file.h"
#ifndef VFS
#include "inode.h"
#else !VFS
#include "vnode.h"
#endif !VFS
#include "vm.h"
#include "kernel.h"
#include "buf.h"

#ifdef vax
#include "../vax/mtpr.h"	/* XXX */
#endif

#ifdef ibm032
int roundshift = 3;		/* shift by 3 == division by 8 */
#endif

/*
 * Force switch among equal priority processes every 100ms or so.
 */
roundrobin()
{

#if	!defined(ibm370)
	runrun++;
	aston();
#ifdef ibm032
	timeout(roundrobin, (caddr_t)0, hz >> roundshift);
#else
	timeout(roundrobin, (caddr_t)0, hz / 10);
#endif ibm032
#endif	/* !defined(ibm370) */
}

#if	!defined(ibm370)
/* constants to digital decay and forget 90% of usage in 5*loadav time */
#undef ave
#define	ave(a,b) ((int)(((int)(a*b))/(b+1)))
#undef ave2
/*
 * optimized version of ave where the b+1 is precalculated and scaled
 * before: (a * b) / (b + 1)
 * becomes:  ( a * b * 1024 ) / (( b  + 1 ) * 1024)
 *           ( a * b * 1024 ) / ( b * 1024 + 1024)
 * and b * 1024 --> t2 and b * 1024 + 1024 --> t3.
 */
#define	ave2(a,b,c) ((int)(((int)(a*b))/(c)))
#ifdef ibm032
#define nrscale 2			/* only used here */
#else
int	nrscale = 2;
#endif

/* fraction for digital decay to forget 90% of usage in 5*loadav sec */
#define	filter(loadav) ((2 * (loadav)) / (2 * (loadav) + 1))

double	ccpu = 0.95122942450071400909;		/* exp(-1/20) */
#define CCPU_SCALE	62340		/* ccpu * PCT_SCALE */
#endif	/* !defined(ibm370) */

/*
 * Recompute process priorities, once a second
 */
schedcpu()
{
#if	!defined(ibm370)
	register struct proc *p;
	register int s, a;
	register int t2 = avenrun[0]*(nrscale * 1024);
	register int t3 = t2 + 1024;
#endif	/* !defined(ibm370) */
	wakeup((caddr_t)&lbolt);
#if	!defined(ibm370)
#ifdef SHOW_LOAD
	a = t2 / (nrscale*1024/10);
#ifdef ATR
	if (a > 999)
		a = 999;
	display_load(a);
#endif ATR
#ifdef IBMRTPC
	if (a > 99)
		a = 99;
	display(((a/10) << 4) + (a % 10)); 	/* display in lights */
#endif IBMRTPC
#endif SHOW_LOAD
	for (p = allproc; p != NULL; p = p->p_nxt) {
		if (p->p_time != 127)
			p->p_time++;
		if (p->p_stat==SSLEEP || p->p_stat==SSTOP)
			if (p->p_slptime != 127)
				p->p_slptime++;
		/*
		 * If the process has slept the entire second,
		 * stop recalculating its priority until it wakes up.
		 */
		if (p->p_slptime > 1) {
#ifndef ibm032
			p->p_pctcpu *= ccpu;
#else ibm032
			p->p_pctcpu =  p->p_pctcpu * CCPU_SCALE / PCT_SCALE;
#endif ibm032
			continue;
		}
		/*
		 * p_pctcpu is only for ps.
		 */
#ifndef ibm032
		p->p_pctcpu = p->p_pctcpu * ccpu + 
		    (1.0 - ccpu) * (p->p_cpticks/(float)hz);
#else ibm032
		p->p_pctcpu =  p->p_pctcpu * CCPU_SCALE / PCT_SCALE;
		if (p->p_cpticks)
			p->p_pctcpu +=  (p->p_cpticks * (PCT_SCALE-CCPU_SCALE))/hz ;
#endif ibm032
		p->p_cpticks = 0;
	/* if p->p_cpu is already 0 then no need to age it */
		if (a = p->p_cpu)
			a = ave2(a, t2, t3);
		a += p->p_nice - NZERO;
		if (a < 0)
			a = 0;
		if (a > 255)
			a = 255;
		p->p_cpu = a;
		(void) setpri(p);
		s = splhigh();	/* prevent state changes */
		if (p->p_pri >= PUSER) {
#define	PPQ	(128 / NQS)
			if ((p != u.u_procp || noproc) &&
			    p->p_stat == SRUN &&
			    (p->p_flag & SLOAD) &&
			    (p->p_pri / PPQ) != (p->p_usrpri / PPQ)) {
				remrq(p);
				p->p_pri = p->p_usrpri;
				setrq(p);
			} else
				p->p_pri = p->p_usrpri;
		}
		splx(s);
	}
	vmmeter();
	if (runin!=0) {
		runin = 0;
		wakeup((caddr_t)&runin);
	}
	if (bclnlist != NULL)
		wakeup((caddr_t)&proc[2]);
#endif	/* !defined(ibm370) */
	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;
#ifndef ibm032
	float scale = filter(avenrun[0]);
#endif ibm032

	p->p_slptime--;		/* the first time was done in schedcpu */
	while (a && --p->p_slptime)
#ifdef ibm032
	/*
	 * we use scaled integer arithmetic here instead of floating point
	 * because floating point can be slow and this routine is called
	 * at interrupt level
	 */
		a = (avenrun_scale * a) / PCT_SCALE;
#else
		a = (int) (scale * a) /* + p->p_nice */;
#endif ibm032
	if (a < 0)
		a = 0;
	if (a > 255)
		a = 255;
	p->p_cpu = a;
	(void) setpri(p);
}

#define SQSIZE 0100	/* Must be power of 2 */
#define HASH(x)	(( (int) x >> 5) & (SQSIZE-1))
struct slpque {
	struct proc *sq_head;
	struct proc **sq_tailp;
} slpque[SQSIZE];

/*
 * 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.
#ifdef VFS
 * If pri&PCATCH is set, signals will cause sleep
 * to return 1, rather than longjmp.
#endif VFS
 * 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 struct proc *rp;
	register struct slpque *qp;
	register s;

	rp = u.u_procp;
	s = splhigh();
	DEBUGF( sydebug&SHOW_SLEEP, printf("Process %d sleeping on 0x%x priority %d\n",
			  rp->p_pid, chan, pri));
	if (panicstr) {
		/*
		 * After a panic, just give interrupts a chance,
		 * then just return; don't run any other procs 
		 * or panic below, in case this is the idle process
		 * and already asleep.
		 * The splnet should be spl0 if the network was being used
		 * by the filesystem, but for now avoid network interrupts
		 * that might cause another panic.
		 */
		(void) splnet();
		splx(s);
		return;
	}
	if (chan==0 || rp->p_stat != SRUN || rp->p_rlink)
		panic("sleep");
	rp->p_wchan = chan;
	rp->p_slptime = 0;
#ifndef VFS
	rp->p_pri = pri;
#else !VFS
	rp->p_pri = pri & PMASK;
#endif !VFS
	qp = &slpque[HASH(chan)];
	if (qp->sq_head == 0)
		qp->sq_head = rp;
	else
		*qp->sq_tailp = rp;
	*(qp->sq_tailp = &rp->p_link) = 0;
	if (pri > PZERO) {
		/*
		 * If we stop in issig(), wakeup may already have happened
		 * when we return (rp->p_wchan will then be 0).
		 */
		if (ISSIG(rp)) {
			if (rp->p_wchan)
				unsleep(rp);
			rp->p_stat = SRUN;
			(void) spl0();
			goto psig;
		}
		if (rp->p_wchan == 0)
			goto out;
		rp->p_stat = SSLEEP;
		(void) spl0();
		u.u_ru.ru_nvcsw++;
		swtch();
		if (ISSIG(rp))
			goto psig;
	} else {
		rp->p_stat = SSLEEP;
		(void) spl0();
		u.u_ru.ru_nvcsw++;
		swtch();
	}
	curpri = rp->p_usrpri;
out:
	splx(s);
	return (0);

	/*
	 * If priority was low (>PZERO) and
	 * there has been a signal, execute non-local goto through
	 * u.u_qsave, aborting the system call in progress (see trap.c)
#ifdef VFS
	 * unless PCATCH is set, in which case we just return 1 so our
	 * caller can release resources and unwind the system call.
#endif VFS
	 */
psig:
#ifdef VFS
	if (pri & PCATCH)
		return (1);
#endif VFS
	longjmp(&u.u_qsave);
	/*NOTREACHED*/
}

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

	s = splhigh();
	DEBUGF(sydebug&SHOW_UNSLEEP, printf("unsleep %d\n",p->p_pid));
	if (p->p_wchan) {
		hp = &(qp = &slpque[HASH(p->p_wchan)])->sq_head;
		while (*hp != p)
			hp = &(*hp)->p_link;
		*hp = p->p_link;
		if (qp->sq_tailp == &p->p_link)
			qp->sq_tailp = hp;
		p->p_wchan = 0;
	}
	splx(s);
}

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

	s = splhigh();
	qp = &slpque[HASH(chan)];
restart:
	for (q = &qp->sq_head; p = *q; ) {
		if (p->p_rlink || p->p_stat != SSLEEP && p->p_stat != SSTOP)
			panic("wakeup");
		if (p->p_wchan==chan) {
			p->p_wchan = 0;
			*q = p->p_link;
			if (qp->sq_tailp == &p->p_link)
				qp->sq_tailp = q;
			if (p->p_stat == SSLEEP) {
				/* OPTIMIZED INLINE EXPANSION OF setrun(p) */
				DEBUGF(sydebug&SHOW_SETRUN, printf("setrun %d \n", p->p_pid));
				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++;
				aston();
				if ((p->p_flag&SLOAD) == 0) {
					if (runout != 0) {
						runout = 0;
						wakeup((caddr_t)&runout);
					}
					wantin++;
				}
				/* END INLINE EXPANSION */
				goto restart;
			}
			p->p_slptime = 0;
		} else
			q = &p->p_link;
	}
	splx(s);
}

#ifdef VFS
/*
 * Wake up the first process sleeping on chan.
 *
 * Be very sure that the first process is really
 * the right one to wakeup.
 */
wakeup_one(chan)
	register caddr_t chan;
{
	register struct slpque *qp;
	register struct proc *p, **q;
	int s;

	s = splhigh();
	qp = &slpque[HASH(chan)];
	for (q = &qp->sq_head; p = *q; ) {
		if (p->p_rlink || p->p_stat != SSLEEP && p->p_stat != SSTOP)
			panic("wakeup_one");
		if (p->p_wchan==chan) {
			p->p_wchan = 0;
			*q = p->p_link;
			if (qp->sq_tailp == &p->p_link)
				qp->sq_tailp = q;
			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++;
				aston();
				if ((p->p_flag&SLOAD) == 0) {
					if (runout != 0) {
						runout = 0;
						wakeup((caddr_t)&runout);
					}
					wantin++;
				}
				/* END INLINE EXPANSION */
				break;		/* all done */
			}
		} else
			q = &p->p_link;
	}
	splx(s);
}
#endif VFS

/*
 * Initialize the (doubly-linked) run queues
 * to be empty.
 */
rqinit()
{
	register int i;

	for (i = 0; i < NQS; i++)
		qs[i].ph_link = qs[i].ph_rlink = (struct proc *)&qs[i];
}

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

	s = splhigh();
	DEBUGF(sydebug&SHOW_SETRUN, printf("setrun %d \n", p->p_pid));
	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++;
		aston();
	}
	if ((p->p_flag&SLOAD) == 0) {
		if (runout != 0) {
			runout = 0;
			wakeup((caddr_t)&runout);
		}
		wantin++;
	}
}

/*
 * 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)/4;
	p += PUSER + 2 * pp->p_nice;
	if (pp->p_rssize > pp->p_maxrss && freemem < desfree)
		p += 2*4;	/* effectively, nice(4) */
	if (p > 127)
		p = 127;
	if (p < curpri) {
		runrun++;
		DEBUGF(sydebug&SHOW_SETPRI, printf("setpri %d to %d\n",pp->p_pid,p));
		aston();
	}
	pp->p_usrpri = p;
	return (p);
}
