-- RTPageFaultImpl.Mesa

-- ******* NOTE COMPILE THIS /-c *********

-- last edited 16-Mar-82 11:09:05 by Paul Rovner


DIRECTORY
  Process USING [SetPriority, priorityInterrupt, Detach, Pause, MsecToTicks, priorityNormal],
  ProcessInternal USING[EnableInterrupts, DisableInterrupts],
  ProcessOperations USING[HandleToIndex, ReadPSB, LongNotify, LongWait, LongReEnter],
  PSB USING[PsbIndex, PsbNull, PDA, qPageFault, Condition],
  RTOS USING[UnregisterCedarProcess],
  RTProcess USING[],  -- EXPORTS only
  RTProcessPrivate USING[],  -- EXPORTS only
  Runtime USING[GlobalFrame],
  SpecialSpace USING[MakeCodeResident, MakeCodeSwappable, MakeGlobalFrameResident--, MakeGlobalFrameSwappable--];

RTPageFaultImpl: MONITOR
  IMPORTS Process, ProcessInternal, ProcessOperations, RTOS, Runtime, SpecialSpace
  EXPORTS RTProcess, RTProcessPrivate
    
= BEGIN

--constants, variables, TYPEs
Int: TYPE = LONG INTEGER;

recorderLock: MONITORLOCK;
pPageFaultCondition: LONG POINTER TO PSB.Condition = @PSB.PDA.fault[PSB.qPageFault].condition;
pPageFaultCONDITION: LONG POINTER TO CONDITION = LOOPHOLE[pPageFaultCondition];
    
totalPageFaults: Int ← 0;

MaxPSBIVecIndex: NAT = 40;
NextPSBIVecIndex: NAT ← 0;
PSBIVec: ARRAY [0..MaxPSBIVecIndex] OF PSBIEntry;
PSBIEntry: TYPE = RECORD[psbi: PSB.PsbIndex ← PSB.PsbNull, count: Int ← 0];


faultWatcherRunning: BOOLEAN ← FALSE;
faultWatcherStartCount: NAT ← 0;


-- PUBLIC procs

  StartWatchingFaults: PUBLIC ENTRY PROC =
    {ENABLE UNWIND => NULL;
     IF (faultWatcherStartCount ← faultWatcherStartCount + 1) = 1
      THEN {faultWatcherRunning ← TRUE;
            Process.Detach[FORK FaultWatcher]}};

  StopWatchingFaults: PUBLIC PROC =
    { firstCall: BOOLEAN ← TRUE;
      UNTIL DoStopWatchingFaults[firstCall]
        DO firstCall ← FALSE;
	   Process.Pause[Process.MsecToTicks[120]]
       ENDLOOP};

  DoStopWatchingFaults: ENTRY PROC[firstCall: BOOLEAN]
        RETURNS[success: BOOLEAN] =
    { ENABLE UNWIND => NULL;
      IF firstCall
       THEN {IF faultWatcherStartCount = 0 THEN RETURN[TRUE];
             faultWatcherStartCount ← faultWatcherStartCount - 1};
      RETURN[faultWatcherStartCount # 0 OR NOT faultWatcherRunning]};

    -- NOTE not an ENTRY, to avoid deadlock
  InvalidateFaultingCedarProcess: PUBLIC PROC[psbi: PSB.PsbIndex] =
    { IF NOT faultWatcherRunning THEN RETURN;
      FOR i: NAT IN [0..NextPSBIVecIndex)
       DO IF PSBIVec[i].psbi = psbi THEN {PSBIVec[i] ← []; EXIT}
      ENDLOOP};

  GetTotalPageFaults: PUBLIC PROC RETURNS[LONG INTEGER] = {RETURN[totalPageFaults]};
  
  ClearFaultHistory: PUBLIC ENTRY PROC =
    { ENABLE UNWIND => NULL;
      totalPageFaults ← NextPSBIVecIndex ← 0};
    
  GetPSBIPageFaults: PUBLIC ENTRY PROC[psbi: PSB.PsbIndex] RETURNS[Int] =
    { ENABLE UNWIND => NULL;
      FOR i: NAT IN [0..NextPSBIVecIndex)
       DO IF PSBIVec[i].psbi = psbi
	   THEN RETURN[PSBIVec[i].count];
      ENDLOOP;
     RETURN[0]};

  EnumerateFaultingProcesses:
       PUBLIC PROC[p: PROC[psbi: PSB.PsbIndex,
                           pageFaultCount, numberEnumerated: LONG INTEGER] RETURNS[stop: BOOLEAN]]
          RETURNS[stopped: BOOLEAN] =
    {npsbi: NAT = NextPSBIVecIndex;
     FOR i: NAT IN [0..npsbi)
       DO psbi: PSB.PsbIndex = PSBIVec[i].psbi;
          count: Int = PSBIVec[i].count;
          IF count # 0 AND psbi # PSB.PsbNull THEN IF p[psbi, count, npsbi] THEN RETURN[TRUE];
      ENDLOOP;
     RETURN[FALSE]};

-- high priority stuff for monitoring page fault activity

BumpPSBIFaultCounter: PROC[psbi: PSB.PsbIndex] =
INLINE
  {empty: NAT ← NextPSBIVecIndex;
   totalPageFaults ← totalPageFaults + 1;

   FOR i: NAT IN [0..NextPSBIVecIndex)
     DO IF PSBIVec[i].psbi = psbi
	 THEN {PSBIVec[i].count ← PSBIVec[i].count + 1; RETURN}
	 ELSE IF PSBIVec[i].psbi = PSB.PsbNull
	 THEN empty ← i;
    ENDLOOP;
   IF empty > MaxPSBIVecIndex THEN RETURN;  -- forget it.
   PSBIVec[empty] ← [psbi: psbi, count: 1];
   IF empty = NextPSBIVecIndex THEN NextPSBIVecIndex ← NextPSBIVecIndex + 1};
  
  
GetCurrent: PROC RETURNS[PSB.PsbIndex] =
INLINE
   {RETURN[ProcessOperations.HandleToIndex[ProcessOperations.ReadPSB[]]]};

LongNakedNotify: PROC [pCondition: LONG POINTER TO CONDITION] =
INLINE
   {-- used to notify a condition from outside the relevant monitor.
    pCond: LONG POINTER TO PSB.Condition = LOOPHOLE[pCondition];
    ProcessInternal.DisableInterrupts[];
    IF pCond↑.tail=PSB.PsbNull
       THEN
         { pCond↑.wakeup ← TRUE;
	   ProcessInternal.EnableInterrupts[] }
        ELSE
	 { ProcessInternal.EnableInterrupts[];
           ProcessOperations.LongNotify[pCondition] } };

WaitForFault: PROC =
INLINE
   {ProcessOperations.LongWait
      [@recorderLock, pPageFaultCONDITION, --timeout:-- 1];
    UNTIL ProcessOperations.LongReEnter
            [@recorderLock, pPageFaultCONDITION] DO
        NULL ENDLOOP;
    
    -- either a new page fault came along or we timed out..
    IF PSB.PDA.fault[PSB.qPageFault].queue.tail = PSB.PsbNull
       THEN RETURN;  -- just timed out, so forget it

    BumpPSBIFaultCounter[PSB.PDA.fault[PSB.qPageFault].queue.tail];

    -- conditionally wake up the Pilot fault handler:
    ProcessInternal.DisableInterrupts[];
    IF pPageFaultCondition↑.tail # PSB.PsbNull
       THEN LongNakedNotify[pPageFaultCONDITION];
    ProcessInternal.EnableInterrupts[];
    };

-- this guy runs as a detached process.  
FaultWatcher: PROC =
  { -- make the fault watcher resident
    SpecialSpace.MakeGlobalFrameResident[RTPageFaultImpl];
    SpecialSpace.MakeCodeResident[Runtime.GlobalFrame[FaultWatcher]];

    -- unregister self
    RTOS.UnregisterCedarProcess[GetCurrent[]];

    -- raise priority
    Process.SetPriority[Process.priorityInterrupt];
 
    DO IF faultWatcherStartCount = 0 THEN EXIT ELSE WaitForFault[] ENDLOOP;

    Process.SetPriority[Process.priorityNormal];
    SpecialSpace.MakeCodeSwappable[Runtime.GlobalFrame[FaultWatcher]];
--    SpecialSpace.MakeGlobalFrameSwappable[RTPageFaultImpl];
    faultWatcherRunning ← FALSE};


END.