-- Swapper>PageFaultImpl.mesa  (December 8, 1982 10:10 pm by Levin)

DIRECTORY
  Environment USING [PageNumber],
  Frame USING [GetReturnFrame, MyLocalFrame, SetReturnFrame],
  PageFault USING [],
  PrincOps USING [BytePC, Frame, GlobalFrameHandle, StateVector],
  ProcessOperations USING
      [IndexToHandle, LongReEnter, LongWait, ReadPSB, Requeue],
  PSB USING [
    FaultIndex, NoTimeout, NullPsbHandle, PDA, PDABase,
    ProcessStateBlock, PsbHandle, PsbIndex, PsbNull, qPageFault,
    qWriteProtectFault, Queue, QueueEmpty],
  Runtime USING [CallDebugger],
  SwapperPrograms USING [],
  Utilities USING [PageFromLongPointer],
  VM USING [Interval, PageNumber],
  WriteFault USING [];

--SwapperPrograms.--PageFaultImpl: MONITOR LOCKS pageFaultLock
  IMPORTS Frame, ProcessOperations, Runtime, Utilities
  EXPORTS PageFault, SwapperPrograms, WriteFault =

BEGIN

pageFaultLock: MONITORLOCK;  -- in principle, two locks are appropriate, but one will do.

pda: PSB.PDABase = PSB.PDA;
qPageFault: PSB.FaultIndex = PSB.qPageFault;
qPageFaultsBeingProcessed: PSB.FaultIndex = PSB.qPageFault+4;
qWriteFault: PSB.FaultIndex = PSB.qWriteProtectFault;
qWriteFaultsBeingProcessed: PSB.FaultIndex = PSB.qWriteProtectFault+4;
pPageFaultCondition: LONG POINTER TO CONDITION =
  LOOPHOLE[@pda.fault[qPageFault].condition];
pWriteFaultCondition: LONG POINTER TO CONDITION =
  LOOPHOLE[@pda.fault[qWriteFault].condition];

beingProcessedAF: LONG POINTER TO PSB.Queue =
  -- contains processes for whom page fault processing has been initiated.
  @pda.fault[qPageFaultsBeingProcessed].queue;
beingProcessedWP: LONG POINTER TO PSB.Queue =
  -- contains processes for whom write fault processing has been initiated.
  @pda.fault[qWriteFaultsBeingProcessed].queue;

-- Exports to PageFault --

AwaitPageFault: PUBLIC ENTRY PROCEDURE RETURNS [page: VM.PageNumber] =
  BEGIN
  process: PSB.PsbHandle;
  WHILE pda.fault[qPageFault].queue = PSB.QueueEmpty DO
    ProcessOperations.LongWait[@pageFaultLock, pPageFaultCondition, PSB.NoTimeout];
    UNTIL ProcessOperations.LongReEnter[
                   @pageFaultLock, pPageFaultCondition] DO NULL ENDLOOP;
    ENDLOOP;
  process ← ProcessOperations.IndexToHandle[
    pda.block[pda.fault[qPageFault].queue.tail]
          .link.next];  -- walk to tail, then to head.
  ProcessOperations.Requeue[
      @pda.fault[qPageFault].queue, beingProcessedAF, process];
  page ← Utilities.PageFromLongPointer[
           pda[pda[process].context.state].memPointer];
  Log[page, pda[pda[process].context.state].frame];  -- REMOVE THIS SOMEDAY!
  END;

RestartPageFault: PUBLIC ENTRY PROCEDURE [interval: VM.Interval] =
  BEGIN
  process, pNext: PSB.PsbHandle;
  pLast: PSB.PsbHandle = ProcessOperations.IndexToHandle[beingProcessedAF.tail];
  IF beingProcessedAF↑ ~= PSB.QueueEmpty THEN
    FOR process ← ProcessOperations.IndexToHandle[
                 pda.block[beingProcessedAF.tail].link.next], pNext DO
      pNext ← ProcessOperations.IndexToHandle[pda[process].link.next];
      IF Utilities.PageFromLongPointer[
              pda[pda[process].context.state].memPointer]
          IN [interval.page..interval.page+interval.count) THEN
        ProcessOperations.Requeue[beingProcessedAF, @pda.ready, process];  -- wake him up.
      IF process = pLast THEN EXIT;
      ENDLOOP;
  END;

AddressFault: PUBLIC ERROR [address: LONG POINTER] = CODE;

ReportAddressFault: PUBLIC PROC [page: VM.PageNumber] =
  {ReportFault[page, beingProcessedAF, AddressFault]};

-- Exports to WriteFault --

AwaitWriteFault: PUBLIC ENTRY PROCEDURE RETURNS [page: VM.PageNumber] =
  BEGIN
  process: PSB.PsbHandle;
  WHILE pda.fault[qWriteFault].queue = PSB.QueueEmpty DO
    ProcessOperations.LongWait[@pageFaultLock, pWriteFaultCondition, PSB.NoTimeout];
    UNTIL ProcessOperations.LongReEnter[
                   @pageFaultLock, pWriteFaultCondition] DO NULL ENDLOOP;
    ENDLOOP;
  process ← ProcessOperations.IndexToHandle[
    pda.block[pda.fault[qWriteFault].queue.tail].link.next];  -- walk to tail, then to head.
  ProcessOperations.Requeue[
      @pda.fault[qWriteFault].queue, beingProcessedWP, process];
  page ← Utilities.PageFromLongPointer[
           pda[pda[process].context.state].memPointer];
  END;

RestartWriteFault: PUBLIC ENTRY PROCEDURE [interval: VM.Interval] =
  BEGIN
  process, pNext: PSB.PsbHandle;
  pLast: PSB.PsbHandle = ProcessOperations.IndexToHandle[beingProcessedWP.tail];
  IF beingProcessedWP↑ ~= PSB.QueueEmpty THEN
    FOR process ← ProcessOperations.IndexToHandle[
                 pda.block[beingProcessedWP.tail].link.next], pNext DO
      pNext ← ProcessOperations.IndexToHandle[pda[process].link.next];
      IF Utilities.PageFromLongPointer[
              pda[pda[process].context.state].memPointer]
          IN [interval.page..interval.page+interval.count) THEN
        ProcessOperations.Requeue[beingProcessedWP, @pda.ready, process];  -- wake him up.
      IF process = pLast THEN EXIT;
      ENDLOOP;
  END;

WriteProtectFault: PUBLIC ERROR [address: LONG POINTER] = CODE;

ReportWriteProtectFault: PUBLIC PROC [page: VM.PageNumber] =
  {ReportFault[page, beingProcessedWP, WriteProtectFault]};


-- Internal procedures --

ReportFault: PROC [
  page: VM.PageNumber, queue: LONG POINTER TO PSB.Queue, error: ERROR [LONG POINTER]] = {
  faultees: PSB.Queue ← PSB.QueueEmpty;
  RemoveFaultees: ENTRY PROC = --INLINE-- {
    qEnd: PSB.PsbIndex ← queue.tail;
    IF qEnd ~= PSB.PsbNull THEN {
      psbi: PSB.PsbIndex ← pda.block[qEnd].link.next;
      DO
        psb: PSB.ProcessStateBlock ← pda.block[psbi];  -- note: a copy of the psb
        IF ~psb.link.vector THEN Runtime.CallDebugger["Bug in fault handling"L];
        IF Utilities.PageFromLongPointer[pda[psb.context.state].memPointer] = page THEN
          ProcessOperations.Requeue[
            from: queue, to: @faultees, p: ProcessOperations.IndexToHandle[psbi]];
        IF psbi = qEnd THEN EXIT;
        psbi ← psb.link.next;  -- relies on psb being a copy
        ENDLOOP;
      };
    };
  RemoveFaultees[];
  UNTIL faultees = PSB.QueueEmpty DO
    psb: PSB.PsbHandle ← ProcessOperations.IndexToHandle[faultees.tail];
    MemoryFault[psb, error];
    ProcessOperations.Requeue[from: @faultees, to: @pda.ready, p: psb];
    ENDLOOP;
  };

MemoryFault: PROC [psb: PSB.PsbHandle, error: ERROR [LONG POINTER]] = {
  Return: PROC ← LOOPHOLE[Frame.GetReturnFrame[]];
  state: PrincOps.StateVector ← pda[pda[psb].context.state];
  -- Splice me in as top-of-stack of psb.
  pda[pda[psb].context.state].stkptr ← pda[pda[psb].context.state].instbyte ← 0;
  Frame.SetReturnFrame[state.frame];
  pda[pda[psb].context.state].frame ← LOOPHOLE[Frame.MyLocalFrame[]];
  -- Return to ReportFault without disturbing this frame.
  Return[];
  -- Control returns here in the context of the faulting process.  If it has
  -- a dirty cleanup link, disaster may ensue if a signal is raised, since it is
  -- almost certain to be uncaught and the AMEvents stuff will doubtless do a WAIT.
  -- If someone caught it and did a wait, similar bad things would happen.  So, we
  -- call the world-swap debugger instead.
  IF pda[psb].flags.cleanup ~= PSB.PsbNull THEN
    Runtime.CallDebugger["Address fault with dirty cleanup link (see a wizard)"L];
  ERROR error[state.memPointer];
  };



-- Event log (for debugging)
Event: TYPE = RECORD [
  page: VM.PageNumber,
  process: PSB.PsbHandle,
  local: POINTER TO local PrincOps.Frame,
  global: PrincOps.GlobalFrameHandle,
  pc: PrincOps.BytePC];
IEvent: TYPE = [0..3);
rgEvent: ARRAY IEvent OF Event ← ALL[[0, PSB.NullPsbHandle, NIL, NIL, [0]]];
pEvent: POINTER TO Event ← @rgEvent[FIRST[IEvent]];

Log: INTERNAL PROC [
    page: VM.PageNumber, trapee: POINTER TO local PrincOps.Frame] = INLINE
  BEGIN
  pEvent↑ ←
    [page, ProcessOperations.ReadPSB[], trapee, trapee.accesslink, trapee.pc];
  pEvent ←
    IF pEvent = @rgEvent[LAST[IEvent]] THEN @rgEvent[FIRST[IEvent]]
    ELSE pEvent + SIZE[Event];
  END;

END.

June 12, 1978  9:46 AM   McJones   Created file.

June 20, 1978  10:53 AM   McJones   Replaced "mark bit" with two queues; add correct simulation of naked notify.

June 23, 1978  1:30 PM   McJones   Removed RestartQuantifier.

July 25, 1978  5:53 PM   McJones   Trap didn't set state.dest (or .source); Restart traversed queue incorrectly.

September 19, 1978  12:03 PM   McJones   Added event log.

March 28, 1979  10:10 AM   McJones   Initialization of rgEvent clobbered following words.

April 28, 1980  10:59 AM   Forrest   FrameOps=>Frame, ControlDefs=>PrincOps, TrapOps=>Trap, PSBDefs=>PSB, ProcessOps=>ProcessOperations; rearrange logging code a bit using an INLINE; use ALL construct to initialize log table.

June 10, 1980  8:27 AM   Forrest    Detect -1 parameter to PageFaultTrap.

August 26, 1980  7:07 PM   McJones   New PSB, ProcessOperation.

January 16, 1981  1:18 PM   Knutsen   New PSB names. Fault Notification. CallDebugger, not worryCall.

February 4, 1981  11:22 AM   Knutsen   Remove LOOPHOLEs to access fault parameter.

February 14, 1981  5:54 PM   Knutsen   Move processes being worked on to unused PDA fault queue instead of private queue.

August 4, 1982 12:07 pm	Levin	Cleanup USING lists.
September 15, 1982 8:32 pm	Levin	Add address/write protect fault stuff.
December 8, 1982 10:04 pm	Levin	Detect fault with dirty cleanup link and world-swap.