/* 
 * procTable.c --
 *
 *	Routines to manage the process table.  This maintains a monitor
 *	that synchronizes access to PCB's.
 *
 * Copyright 1985, 1988 Regents of the University of California
 * Permission to use, copy, modify, and distribute this
 * software and its documentation for any purpose and without
 * fee is hereby granted, provided that the above copyright
 * notice appear in all copies.  The University of California
 * makes no representations about the suitability of this
 * software for any purpose.  It is provided "as is" without
 * express or implied warranty.
 */

#ifndef lint
static char rcsid[] = "$Header: /cdrom/src/kernel/Cvsroot/kernel/proc/procTable.c,v 9.16 92/01/06 15:03:44 kupfer Exp $ SPRITE (Berkeley)";
#endif not lint

#include <sprite.h>
#include <mach.h>
#include <proc.h>
#include <procInt.h>
#include <sync.h>
#include <sched.h>
#include <timer.h>
#include <list.h>
#include <vm.h>
#include <sys.h>
#include <stdlib.h>
#include <rpc.h>

static Sync_Lock	tableLock;
#define	LOCKPTR &tableLock

static Proc_ControlBlock  *RunningProcesses[MACH_MAX_NUM_PROCESSORS];
Proc_ControlBlock  **proc_RunningProcesses = RunningProcesses;
Proc_ControlBlock **proc_PCBTable;

#define PROC_MAX_PROCESSES 256
#define PROC_PCB_NUM_ALLOC 16
int proc_MaxNumProcesses;

int procLastSlot = 0;	/* Circular index into proctable for choosing slots */
static int realMaxProcesses;	/* The absolute number of process table
				 * entries, not necessarily allocated yet. */
static int entriesInUse = 0;	/* Number of PCB's in use. */

static void 	InitPCB _ARGS_((Proc_ControlBlock *pcbPtr, int i));
static void	AddPCBs _ARGS_((Proc_ControlBlock **procPtrPtr));


/*
 * ----------------------------------------------------------------------------
 *
 * ProcInitTable --
 *
 *	Initializes the PCB table and running process table.  Must be called
 *	at initialization time with interrupts off.  Initializes an array
 *	of PROC_MAX_PROCESSES pointers to PCB's but only allocates
 *	PROC_PCB_NUM_ALLOC entries at first.  The rest are done dynamically.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The PCB table is initialized.
 *
 * ----------------------------------------------------------------------------
 */

void
ProcInitTable()
{
    register	int 		  i;
    register	Proc_ControlBlock *pcbPtr;
    int 	maxRunningProcesses;

    maxRunningProcesses = MACH_MAX_NUM_PROCESSORS;
    proc_MaxNumProcesses     = PROC_PCB_NUM_ALLOC;
    realMaxProcesses         = PROC_MAX_PROCESSES;

    proc_PCBTable = (Proc_ControlBlock **)
        Vm_BootAlloc(realMaxProcesses * sizeof(pcbPtr));

    for (i = 0; i < proc_MaxNumProcesses; i++) {
	pcbPtr = (Proc_ControlBlock *) Vm_BootAlloc(sizeof(Proc_ControlBlock));
	proc_PCBTable[i] = pcbPtr;
	InitPCB(pcbPtr, i);
    }

    /*
     * Set the rest of the proc table to catch any misuse of nonexistent
     * entries.
     */

    for (i = proc_MaxNumProcesses; i < realMaxProcesses; i++) {
	proc_PCBTable[i] = (Proc_ControlBlock *) NIL;
    }

    for (i = 0; i < maxRunningProcesses; i++) {
        proc_RunningProcesses[i] = (Proc_ControlBlock *) NIL;
    }
    Sync_LockInitDynamic(&tableLock, "Proc:tableLock");
}


/*
 * ----------------------------------------------------------------------------
 *
 * InitPCB --
 *
 *	Initializes a process control block.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */
static void
InitPCB(pcbPtr, i)
    Proc_ControlBlock *pcbPtr;
    int i;
{
    List_InitElement((List_Links *)pcbPtr);
    pcbPtr->state		= PROC_UNUSED;
    pcbPtr->processID	= i;
    pcbPtr->genFlags	= 0;

    /*
     *  Initialize the pointers to the list headers and the
     *  PCB entry. These values do not change when the PCB
     *  entry is re-used.
     */
    pcbPtr->childList	= &(pcbPtr->childListHdr);
    pcbPtr->siblingElement.procPtr	= pcbPtr;
    pcbPtr->familyElement.procPtr	= pcbPtr;

    /*
     *  Set the links to NIL to catch any invalid uses of
     *  the lists before they are properly initialized.
     *  These pointers change whenever the PCB entry is re-used.
     */
    pcbPtr->childListHdr.nextPtr	= (List_Links *) NIL;
    pcbPtr->childListHdr.prevPtr	= (List_Links *) NIL;

    List_InitElement((List_Links *)&pcbPtr->siblingElement);
    List_InitElement((List_Links *)&pcbPtr->familyElement);

    pcbPtr->eventHashChain.procPtr = pcbPtr;
    List_InitElement((List_Links *)&pcbPtr->eventHashChain);
    pcbPtr->event = NIL;

    pcbPtr->peerHostID = NIL;
    pcbPtr->peerProcessID = (Proc_PID) NIL;
    pcbPtr->remoteExecBuffer = (Address) NIL;
    pcbPtr->migCmdBuffer = (Address) NIL;
    pcbPtr->migCmdBufSize = 0;
    pcbPtr->migFlags = 0;
    pcbPtr->argString = (char *) NIL;
#ifdef LOCKDEP
    pcbPtr->lockStackSize = 0;
#endif
    pcbPtr->vmPtr = (Vm_ProcInfo *)NIL;
    pcbPtr->fsPtr = (Fs_ProcessState *)NIL;
    pcbPtr->rpcClientProcess = (Proc_ControlBlock *) NIL;

    pcbPtr->waitToken = 0;
    pcbPtr->timerArray = (struct ProcIntTimerInfo *) NIL;

    pcbPtr->kcallTable = mach_NormalHandlers;
    pcbPtr->specialHandling = 0;
    pcbPtr->machStatePtr = (struct Mach_State *)NIL;
#ifndef CLEAN_LOCK
    Sync_SemInitDynamic(&pcbPtr->lockInfo, "Proc:perPCBlock");
#endif
#ifdef LOCKREG
    Sync_LockRegister(&pcbPtr->lockInfo);
#endif
}


/*
 * ----------------------------------------------------------------------------
 *
 *  AddPCBs --
 *
 *	Add new proc_ControlBlocks with sched_Mutex locked.  This avoids
 *	conflicts accessing the proc_MaxNumProcesses variable, such as in
 *	the Sched_ForgetUsage routine.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The global array of process control blocks is updated to point
 *	to the PCB's pointed to by procPtrPtr, and the count of useable entries
 *	is updated.
 *
 * ----------------------------------------------------------------------------
 */

static void
AddPCBs(procPtrPtr)
    Proc_ControlBlock **procPtrPtr;
{
    register int i;
    
    /*
     *  Gain exclusive access to the process table.
     */
    MASTER_LOCK(sched_MutexPtr);

    for (i = 0; i < PROC_PCB_NUM_ALLOC; i++) {
	proc_PCBTable[proc_MaxNumProcesses] = *procPtrPtr;
	procPtrPtr++;
	proc_MaxNumProcesses++;
    }

    MASTER_UNLOCK(sched_MutexPtr);
}
    

/*
 * ----------------------------------------------------------------------------
 *
 * Proc_InitMainProc --
 *
 *	Finish initializing the process table by making a proc table entry
 *	for the main process.  Called with interrupts disabled.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The first element of the proc table is modified, and the count of
 *	used entries is set to 1.
 *
 * ----------------------------------------------------------------------------
 */

void
Proc_InitMainProc()
{
    register	Proc_ControlBlock *procPtr;

#define MAIN_PID 0

    entriesInUse = 1;
    
    procPtr = proc_PCBTable[MAIN_PID];

    /*
     *  Initialize the main process.
     */
    procPtr->state		= PROC_RUNNING;
    procPtr->genFlags		= PROC_KERNEL;
    procPtr->syncFlags		= 0;
    procPtr->schedFlags		= 0;
    procPtr->processID	 	= MAIN_PID | (1 << PROC_GEN_NUM_SHIFT) | 
					(rpc_SpriteID << PROC_ID_NUM_SHIFT);
    procPtr->parentID		= procPtr->processID;
    procPtr->billingRate 	= PROC_NORMAL_PRIORITY;
    procPtr->recentUsage 	= 0;
    procPtr->weightedUsage 	= 0;
    procPtr->unweightedUsage 	= 0;
    procPtr->kernelCpuUsage.ticks     = timer_TicksZeroSeconds;
    procPtr->userCpuUsage.ticks       = timer_TicksZeroSeconds;
    procPtr->childKernelCpuUsage.ticks = timer_TicksZeroSeconds;
    procPtr->childUserCpuUsage.ticks  = timer_TicksZeroSeconds;
    procPtr->numQuantumEnds 	= 0;
    procPtr->numWaitEvents 	= 0;

    procPtr->Prof_Buffer        = (short *) NIL;
    procPtr->Prof_BufferSize    = 0;
    procPtr->Prof_Offset        = 0;
    procPtr->Prof_Scale         = 0;
    procPtr->Prof_PC            = 0;

    Mach_InitFirstProc(procPtr);
    Vm_ProcInit(procPtr);
    (void) VmMach_SetupContext(procPtr);

    procPtr->familyID 		= PROC_NO_FAMILY;	/* not in a family */
    
    List_Init(procPtr->childList);

    procPtr->userID		= 0;
    procPtr->effectiveUserID	= 0;

    Sig_ProcInit(procPtr);

    procPtr->processor = Mach_GetProcessorNumber();
    Proc_SetCurrentProc(procPtr);

    ProcInitMainEnviron(procPtr);

    ProcFamilyHashInit();

    procPtr->peerProcessID = (Proc_PID) NIL;
    procPtr->peerHostID = (int) NIL;
    procPtr->remoteExecBuffer = (Address) NIL;
}


/*
 * ----------------------------------------------------------------------------
 *
 * Proc_LockPID --
 *
 *	Determine the validity of the given pid and if valid return a pointer
 *	to the proc table entry.  The proc table entry is returned locked.
 *
 * Results:
 *	Pointer to proc table entry.
 *
 * Side effects:
 *	Proc table entry is locked.
 *
 * ----------------------------------------------------------------------------
 */

ENTRY Proc_ControlBlock *
Proc_LockPID(pid)
    Proc_PID	pid;
{
    register	Proc_ControlBlock *procPtr;
#ifndef CLEAN_LOCK
    register	Sync_Semaphore	  *lockPtr;
#endif

    LOCK_MONITOR;

    if (Proc_PIDToIndex(pid) >= proc_MaxNumProcesses) {
	UNLOCK_MONITOR;
	return((Proc_ControlBlock *) NIL);
    }
    procPtr = proc_PCBTable[Proc_PIDToIndex(pid)];
#ifndef CLEAN_LOCK
    lockPtr = &(procPtr->lockInfo);
#endif

    while (TRUE) {
	if (procPtr->state == PROC_UNUSED || procPtr->state == PROC_DEAD) {
	    procPtr = (Proc_ControlBlock *) NIL;
	    break;
	}

	if (procPtr->genFlags & PROC_LOCKED) {
	    do {
		Sync_RecordMiss(lockPtr);
		(void) Sync_Wait(&procPtr->lockedCondition, FALSE);
	    } while (procPtr->genFlags & PROC_LOCKED);
	} else {
	    if (!Proc_ComparePIDs(procPtr->processID, pid)) {
		procPtr = (Proc_ControlBlock *) NIL;
	    } else {
		procPtr->genFlags |= PROC_LOCKED;
		Sync_RecordHit(lockPtr);
		Sync_StoreDbgInfo(lockPtr, FALSE);
		Sync_AddPrior(lockPtr);
	    }
	    break;
	}
    }

    UNLOCK_MONITOR;
    return(procPtr);
}


/*
 * ----------------------------------------------------------------------------
 *
 * Proc_Lock --
 *
 *	Lock the proc table entry.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Proc table entry is locked.
 *
 * ----------------------------------------------------------------------------
 */

ENTRY void
Proc_Lock(procPtr)
    register	Proc_ControlBlock *procPtr;
{
#ifndef CLEAN_LOCK
    register	Sync_Semaphore	  *lockPtr;
#endif

    LOCK_MONITOR;

#ifndef CLEAN_LOCK
    lockPtr = &(procPtr->lockInfo);
#endif

    while (procPtr->genFlags & PROC_LOCKED) {
	Sync_RecordMiss(lockPtr);
	(void) Sync_Wait(&procPtr->lockedCondition, FALSE);
    }
    procPtr->genFlags |= PROC_LOCKED;

    Sync_RecordHit(lockPtr);
    Sync_StoreDbgInfo(lockPtr, FALSE);
    Sync_AddPrior(lockPtr);

    UNLOCK_MONITOR;
}


/*
 * ----------------------------------------------------------------------------
 *
 * Proc_Unlock --
 *
 *	Unlock the proc table entry.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Proc table entry is unlocked.
 *
 * ----------------------------------------------------------------------------
 */

ENTRY void
Proc_Unlock(procPtr)
    register	Proc_ControlBlock *procPtr;
{
    LOCK_MONITOR;

    if (!(procPtr->genFlags & PROC_LOCKED)) {
	panic("Proc_Unlock: PCB not locked.\n");
    }
    procPtr->genFlags &= ~PROC_LOCKED;
    Sync_Broadcast(&procPtr->lockedCondition);

    UNLOCK_MONITOR;
}


/*
 *----------------------------------------------------------------------
 *
 * Proc_UnlockAndSwitch --
 *
 *	Unlock a PCB and perform a context switch to the given state.  
 *	This is done atomically: no other process can lock the PCB before 
 *	this process context switches.  
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
Proc_UnlockAndSwitch(procPtr, state)
    Proc_ControlBlock *procPtr;	/* the PCB to unlock */
    Proc_State state;		/* the state to context switch to */
{
    LOCK_MONITOR;

    if (!(procPtr->genFlags & PROC_LOCKED)) {
	panic("Proc_Unlock: PCB not locked.\n");
    }
    procPtr->genFlags &= ~PROC_LOCKED;
    Sync_Broadcast(&procPtr->lockedCondition);

    Sync_UnlockAndSwitch(LOCKPTR, state);
}


/*
 * ----------------------------------------------------------------------------
 *
 * ProcGetUnusedPCB --
 *
 *	Return the first unused PCB.
 *
 * Results:
 *	Pointer to PCB.
 *
 * Side effects:
 *	Proc table entry is locked and marked as PROC_NEW.
 *
 * ----------------------------------------------------------------------------
 */

ENTRY Proc_ControlBlock *
ProcGetUnusedPCB()
{
    register	Proc_ControlBlock 	**procPtrPtr;
    register	Proc_ControlBlock 	*procPtr;
    Proc_ControlBlock 			*pcbArray[PROC_PCB_NUM_ALLOC];
    register	int 			i;
    int					generation;

    LOCK_MONITOR;



    /* 
     * See if we need to allocate more process table entries.
     */
    if (entriesInUse == proc_MaxNumProcesses) {
	if (proc_MaxNumProcesses > realMaxProcesses - PROC_PCB_NUM_ALLOC) {
	    panic("ProcGetUnusedPCB: PCB table full!!\n");
	}
	for (i = 0; i < PROC_PCB_NUM_ALLOC; i++) {
	    pcbArray[i] = (Proc_ControlBlock *)
		    Vm_RawAlloc(sizeof(Proc_ControlBlock));
	    InitPCB(pcbArray[i], proc_MaxNumProcesses + i);
	}
	AddPCBs(pcbArray);
    }

	
	
    /*
     * Scan the proc table looking for an unused slot.  The search is
     * circular, starting just after the last slot chosen.  This is done
     * so that slots are not re-used often so the generation number of
     * each slot can just be a few bits wide.
     */
    for (i = procLastSlot, procPtrPtr = &proc_PCBTable[procLastSlot]; ; ) {
	if ((*procPtrPtr)->state == PROC_UNUSED) {
	    break;
	}
	i++;
	procPtrPtr++;
	if (i >= proc_MaxNumProcesses) {
	    i = 0;
	    procPtrPtr = &proc_PCBTable[0];
	}
	/*
	 * Shouldn't hit this, but check to avoid infinite loop.
	 */
	if (i == procLastSlot) {
	    panic("ProcGetUnusedPCB: PCB table full!!\n");
	}
    }

    procLastSlot = i+1;
    if (procLastSlot >= proc_MaxNumProcesses) {
	procLastSlot = 0;
    }
    procPtr = *procPtrPtr;
    procPtr->genFlags = PROC_LOCKED;
    procPtr->migFlags = 0;
    procPtr->state = PROC_NEW;
    /*
     *  The PCB entry has a generation number that is incremented each time
     *  the entry is re-used. The low-order bits are in index into
     *  the PCB table.
     */
    generation = (procPtr->processID & PROC_GEN_NUM_MASK) >> PROC_GEN_NUM_SHIFT;
    generation += 1;
    generation = (generation << PROC_GEN_NUM_SHIFT) & PROC_GEN_NUM_MASK;
    procPtr->processID = i | generation | (rpc_SpriteID << PROC_ID_NUM_SHIFT);

    entriesInUse++;

    UNLOCK_MONITOR;

    return(procPtr);
}


/*
 * ----------------------------------------------------------------------------
 *
 * ProcFreePCB --
 *
 *	Mark the given PCB as unused.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Proc table entry marked as PROC_UNUSED.
 *
 * ----------------------------------------------------------------------------
 */

ENTRY void
ProcFreePCB(procPtr)
    register	Proc_ControlBlock 	*procPtr;
{
#ifdef LOCKREG
    register	Sync_Semaphore	  *lockPtr;
#endif

    LOCK_MONITOR;

#ifdef LOCKREG
    lockPtr = &(procPtr->lockInfo);
#endif

    while (procPtr->genFlags & PROC_LOCKED) {
#ifdef LOCKREG
	Sync_RecordMiss(lockPtr);
#endif
	(void) Sync_Wait(&procPtr->lockedCondition, FALSE);
    }
    procPtr->state = PROC_UNUSED;
    procPtr->genFlags = 0;
    entriesInUse--;

#ifdef LOCKREG
    Sync_RecordHit(lockPtr);
#endif

    UNLOCK_MONITOR;
}


/*
 * ----------------------------------------------------------------------------
 *
 * ProcTableMatch --
 *
 *	Go through the process table and return an array of process
 *	IDs for which the specified function returns TRUE.
 *
 * Results:
 *	The array of PIDs and the number of matches are returned.
 *
 * Side effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */

ENTRY int
ProcTableMatch(maxPids, booleanFuncPtr, pidArray)
    int maxPids;			/* size of pidArray */
    Boolean (*booleanFuncPtr) _ARGS_((Proc_ControlBlock *pcbPtr));
					/* function to match */
    Proc_PID *pidArray;			/* array to store results */
{
    Proc_ControlBlock *pcbPtr;
    int i;
    int matched = 0;
    
    LOCK_MONITOR;

    for (i = 0; i < proc_MaxNumProcesses && matched < maxPids; i++) {
	pcbPtr = proc_PCBTable[i];
	if (pcbPtr->state == PROC_UNUSED) {
	    continue;
	}
	if ((*booleanFuncPtr)(pcbPtr)) {
	    pidArray[matched] = pcbPtr->processID;
	    matched++;
	}
    }
    UNLOCK_MONITOR;
    return(matched);
}
