-- Resident.Mesa; edited by Sandman on October 9, 1980  7:52 AM
-- Copyright  Xerox Corporation 1979, 1980

DIRECTORY
  AltoDefs USING [PageSize],
  AltoFileDefs USING [CFP],
  BcplOps USING [BcplInLd, BcplJSR, BcplOutLd],
  ControlDefs USING [
    AV, AVItem, ControlLink, EntryInfo, FrameHandle, FrameVec, GFT, GFTIndex,
    GFTItem, GlobalFrameHandle, LargeReturnSlot, Port, PrefixHandle, ProcDesc,
    SpecialReturnSlot, StateVector, TraceNext, TraceOff, TrapStatus],
  CoreSwapDefs USING [ExternalStateVector, PuntInfo, SVPointer],
  DiskDefs USING [CBNil, NextDiskCommand],
  ForgotOps USING [],
  FrameDefs USING [SwapInCode],
  FrameOps USING [
    GetReturnLink, MyLocalFrame, ReleaseCode, SetReturnFrame, SetReturnLink,
    Start],
  ImageDefs USING [AbortMesa, PuntMesa],
  InlineDefs USING [
    BcplLongNumber, BcplToMesaLongNumber, BITOR, MesaToBcplLongNumber],
  KeyDefs USING [KeyBits, Keys, updown],
  Mopcodes USING [zBRK, zKFCB, zPOP, zRBL, zSTARTIO],
  NucleusOps USING [InterruptPriority],
  OsStaticDefs USING [OsStatics],
  ProcessDefs USING [DisableInterrupts, EnableInterrupts, Priority],
  ProcessOps USING [
    ActiveWord, FirstStateVector, ReadWDC, WakeupsWaiting, WriteWDC],
  Runtime USING [],
  SDDefs USING [
    sAllocTrap, SD, sInterrupt, sIOResetBits, sProcessBreakpoint, sXferTrap],
  SDOps USING [],
  SegmentDefs USING [
    DataSegmentAddress, DataSegmentHandle, DefaultMDSBase, DeleteDataSegment,
    FrameDS, MakeDataSegment],
  TrapDefs USING [],
  TrapOps USING [ReadATP, ReadOTP, ReadXTS, WriteXTS];

Resident: MONITOR
  IMPORTS
    FrameDefs, FrameOps, ImageDefs, InlineDefs, BcplOps, ProcessDefs, ProcessOps,
    SegmentDefs, TrapOps
  EXPORTS CoreSwapDefs, ForgotOps, FrameOps, NucleusOps, Runtime, SDOps, TrapDefs =
  BEGIN OPEN ControlDefs;

  -- allocation of frame space

  LargeFrameSlot: CARDINAL = 12;

  FrameSize: PUBLIC PROCEDURE [fsi: CARDINAL] RETURNS [CARDINAL] =
    BEGIN RETURN[FrameVec[fsi]] END;

  pgft: TYPE = POINTER TO ARRAY [0..0) OF GFTItem;

  ItemPointer: TYPE = POINTER TO ControlDefs.AVItem;

  FrameSegment: TYPE = MACHINE DEPENDENT RECORD [
    segment: SegmentDefs.DataSegmentHandle,
    link: POINTER TO FrameSegment,
    size, fsi: CARDINAL];

  -- maintain a list of all new "permanent" frame segments;

  SegHeader: PUBLIC TYPE = RECORD [
    seg: SegmentDefs.DataSegmentHandle, link: pSegHeader];
  pSegHeader: PUBLIC TYPE = POINTER TO SegHeader;
  SegListHead: PUBLIC pSegHeader ← NIL;

  ExtraSpaceSize: CARDINAL = 170B;
  ExtraSpace: ARRAY [0..ExtraSpaceSize) OF WORD;
  InitNewSpace: POINTER = LOOPHOLE[InlineDefs.BITOR[BASE[ExtraSpace], 3]];
  InitWordsLeft: CARDINAL = BASE[ExtraSpace] + ExtraSpaceSize - InitNewSpace;

  NULLPtr: FrameHandle = LOOPHOLE[0];

  AllocTrap: PUBLIC PROCEDURE [otherframe: FrameHandle]
    RETURNS [myframe: FrameHandle] =
    BEGIN OPEN SegmentDefs;
    ATFrame: TYPE = POINTER TO FRAME[AllocTrap];
    state: StateVector;
    newframe: FrameHandle;
    newseg: DataSegmentHandle;
    long: BOOLEAN;
    i, fsize, fIndex: CARDINAL;
    p: POINTER;
    newG: GlobalFrameHandle;
    NewSpacePtr: POINTER;
    WordsLeft: CARDINAL ← 0;
    recurring: BOOLEAN ← otherframe = NULLPtr;
    alloc: BOOLEAN;
    dest, tempdest: ControlLink;
    gfi: GFTIndex;
    ep: CARDINAL;
    myframe ← FrameOps.MyLocalFrame[];
    state.dest ← myframe.returnlink;
    state.source ← 0;
    state.instbyte ← 0;
    state.stk[0] ← myframe;
    state.stkptr ← 1;
    ProcessDefs.DisableInterrupts[]; -- so that undo below works
    DO
      ENABLE ANY => ImageDefs.PuntMesa[];
      IF ~recurring THEN
	BEGIN
	LOOPHOLE[otherframe, ATFrame].NewSpacePtr ← InitNewSpace;
	LOOPHOLE[otherframe, ATFrame].WordsLeft ← InitWordsLeft;
	AV[SpecialReturnSlot] ← [data[0, empty]];
	END;
      -- the following RR and POP is to guarantee that there is no NOOP between
      -- the DWDC and the LST
      [] ← TrapOps.ReadATP[];
      ProcessDefs.EnableInterrupts[];
      TRANSFER WITH state;
      ProcessDefs.DisableInterrupts[];
      state ← STATE;
      dest ← TrapOps.ReadATP[];
      SDDefs.SD[SDDefs.sAllocTrap] ← otherframe;
      myframe.returnlink ← state.source;
      tempdest ← dest;
      DO
	SELECT tempdest.tag FROM
	  frame =>
	    BEGIN alloc ← TRUE; fIndex ← LOOPHOLE[tempdest, CARDINAL]/4; EXIT END;
	  procedure =>
	    BEGIN OPEN proc: LOOPHOLE[tempdest, ProcDesc];
	    gfi ← proc.gfi;
	    ep ← proc.ep;
	    [frame: newG, epbase: fIndex] ← GFT[gfi]; -- use fIndex as temp
	    long ← newG.code.highByte = 0;
	    IF long THEN
	      BEGIN
	      GetEntryInfo: PROCEDURE [LONG POINTER] RETURNS [EntryInfo] = MACHINE
		CODE BEGIN Mopcodes.zRBL, 1 END;
	      info: EntryInfo ← GetEntryInfo[
		@LOOPHOLE[newG.code.longbase, LONG PrefixHandle].entry[
		fIndex + ep]];
	      fIndex ← info.framesize;
	      END
	    ELSE
	      fIndex ← LOOPHOLE[newG.code.shortbase, PrefixHandle].entry[
		fIndex + ep].info.framesize;
	    alloc ← FALSE;
	    EXIT
	    END;
	  indirect => tempdest ← tempdest.link↑;
	  ENDCASE => ImageDefs.PuntMesa[];
	ENDLOOP;
      IF ~recurring THEN FlushLargeFrames[]
      ELSE
	IF (p ← AV[SpecialReturnSlot].link) # LOOPHOLE[AVItem[data[0, empty]]]
	  THEN
	  BEGIN
	  WordsLeft ← WordsLeft + (NewSpacePtr - p + 1);
	  NewSpacePtr ← p - 1;
	  AV[SpecialReturnSlot] ← [data[0, empty]];
	  END;
      IF fIndex < LargeFrameSlot THEN
	BEGIN
	fsize ← FrameVec[fIndex] + 1; -- includes overhead word
	THROUGH [0..1] DO
	  p ← NewSpacePtr + 1;
	  IF fsize <= WordsLeft THEN
	    BEGIN
	    newframe ← p;
	    (p - 1)↑ ← IF recurring THEN SpecialReturnSlot ELSE fIndex;
	    WordsLeft ← WordsLeft - fsize;
	    NewSpacePtr ← NewSpacePtr + fsize;
	    EXIT;
	    END
	  ELSE
	    BEGIN
	    IF recurring THEN ImageDefs.PuntMesa[];
	    FOR i DECREASING IN [0..fIndex) DO
	      IF FrameVec[i] < WordsLeft THEN
		BEGIN (p - 1)↑ ← i; p↑ ← AV[i].link; AV[i].link ← p; EXIT; END;
	      ENDLOOP;
	    newseg ← MakeDataSegment[DefaultMDSBase, 1, [hard, topdown, frame]];
	    newseg.type ← FrameDS;
	    NewSpacePtr ← (p ← DataSegmentAddress[newseg]) + 3;
	    LOOPHOLE[p, pSegHeader]↑ ← [newseg, SegListHead];
	    SegListHead ← p;
	    WordsLeft ← AltoDefs.PageSize - 3;
	    END;
	  ENDLOOP
	END
      ELSE
	BEGIN
	fsize ← FrameVec[fIndex];
	p ← DataSegmentAddress[
	  newseg ← MakeDataSegment[
	  base: DefaultMDSBase, info: [hard, topdown, frame],
	  pages: (fsize + AltoDefs.PageSize + 3)/AltoDefs.PageSize]];
	newseg.type ← FrameDS;
	newframe ← p + 4;
	LOOPHOLE[p, POINTER TO FrameSegment]↑ ←
	  [segment: newseg, link: NIL, size: fsize, fsi: LargeReturnSlot];
	END;
      IF alloc THEN
	BEGIN
	state.dest ← myframe.returnlink;
	state.stk[state.stkptr] ← newframe;
	state.stkptr ← state.stkptr + 1;
	END
      ELSE
	BEGIN
	state.dest ← dest;
	newframe.accesslink ← LOOPHOLE[AV[fIndex].link];
	AV[fIndex].frame ← newframe;
	state.source ← myframe.returnlink;
	END;
      SDDefs.SD[SDDefs.sAllocTrap] ← myframe;
      ENDLOOP;
    END;

  -- FlushLargeFrames' frame must be larger than MDSRegion Update's frame


  FlushLargeFrames: PUBLIC PROCEDURE =
    BEGIN
    p: ARRAY [0..10) OF POINTER; -- ensure large enough frame
    item: ItemPointer ← @AV[LargeReturnSlot];
    WHILE item.tag = frame DO
      p[0] ← item.frame;
      item.frame ← p[0]↑;
      SegmentDefs.DeleteDataSegment[LOOPHOLE[(p[0] - 4)↑]];
      ENDLOOP;
    END;

  -- other traps

  UnboundProcedure: PUBLIC SIGNAL [dest: ControlLink] RETURNS [ControlLink] =
    CODE;

  UnboundProcedureTrap: PUBLIC PROCEDURE =
    BEGIN
    dest: ControlLink;
    state: StateVector;
    ProcessDefs.DisableInterrupts[];
    state ← STATE;
    dest ← TrapOps.ReadOTP[];
    ProcessDefs.EnableInterrupts[];
    state.source ← FrameOps.GetReturnLink[];
    state.dest ← SIGNAL UnboundProcedure[dest];
    RETURN WITH state
    END;

  CodeTrap: PUBLIC PROCEDURE =
    BEGIN
    dest: ControlLink;
    state: StateVector;
    frame: GlobalFrameHandle;
    ProcessDefs.DisableInterrupts[];
    state ← STATE;
    dest ← TrapOps.ReadOTP[];
    ProcessDefs.EnableInterrupts[];
    state.dest ← dest;
    state.source ← FrameOps.GetReturnLink[];
    DO
      SELECT dest.tag FROM
	frame => BEGIN frame ← dest.frame.accesslink; EXIT END;
	procedure => BEGIN frame ← GFT[dest.gfi].frame; EXIT END;
	ENDCASE => dest ← dest.link↑;
      ENDLOOP;
    IF ~frame.started THEN FrameOps.Start[[frame[frame]]];
    FrameDefs.SwapInCode[frame];
    FrameOps.ReleaseCode[frame];
    RETURN WITH state;
    END;

  -- Parity Errors

  ParityError: PUBLIC SIGNAL [address: POINTER] = CODE;
  PhantomParityError: PUBLIC SIGNAL = CODE;

  parityWakeup: PUBLIC CONDITION;

  ParityProcess: PUBLIC ENTRY PROCEDURE =
    BEGIN OPEN ProcessOps;
    p: ORDERED POINTER;
    ww: POINTER TO MACHINE DEPENDENT RECORD [
      other: [0..77777B], parity: BOOLEAN] ← LOOPHOLE[WakeupsWaiting];
    POP: PROCEDURE [WORD] = MACHINE CODE BEGIN Mopcodes.zPOP END;
    DO
      -- forever
      WAIT parityWakeup;
      ActiveWord↑ ← 0;
      FOR p DECREASING IN [LOOPHOLE[0]..LOOPHOLE[177000B]) DO
	POP[p↑];
	IF ww.parity THEN BEGIN SIGNAL ParityError[p]; EXIT END;
	REPEAT FINISHED => SIGNAL PhantomParityError;
	ENDLOOP;
      ww.parity ← FALSE;
      ActiveWord↑ ← 77777B;
      ENDLOOP;
    END;

  -- Getting the Debugger

  level: PUBLIC INTEGER;

  StartIO: PROCEDURE [CARDINAL] = MACHINE CODE BEGIN Mopcodes.zSTARTIO END;

  CSPort: PORT RETURNS [POINTER TO CoreSwapDefs.ExternalStateVector];
  WBPort: PORT [POINTER TO CoreSwapDefs.ExternalStateVector];

  MemorySwap: PROCEDURE [ESV: POINTER TO CoreSwapDefs.ExternalStateVector] =
    BEGIN
    savewdc: UNSPECIFIED;
    xferTrapStatus: UNSPECIFIED;
    xferTrapHandler: UNSPECIFIED;
    flag: [0..2];
    DO
      ESV ← CSPort[];
      FrameOps.SetReturnLink[LOOPHOLE[CSPort, Port].dest];
      StartIO[SDDefs.SD[SDDefs.sIOResetBits]]; -- reset IO devices
      ESV.level ← level;
      xferTrapStatus ← TrapOps.ReadXTS[];
      xferTrapHandler ← SDDefs.SD[SDDefs.sXferTrap];
      SDDefs.SD[SDDefs.sXferTrap] ← FrameOps.MyLocalFrame[];
      TrapOps.WriteXTS[TraceOff];
      UNTIL DiskDefs.NextDiskCommand↑ = DiskDefs.CBNil DO NULL ENDLOOP;
      savewdc ← ProcessOps.ReadWDC[];
      flag ← BcplOps.BcplOutLd[OutLd, CoreSwapDefs.PuntInfo↑.pCoreFP, ESV];
      ProcessOps.WriteWDC[savewdc];
      SELECT flag FROM
	0 => BcplOps.BcplInLd[InLd, CoreSwapDefs.PuntInfo↑.pDebuggerFP, ESV];
	1 => level ← ESV.level;
	ENDCASE => ESV.reason ← proceed;
      TrapOps.WriteXTS[xferTrapStatus];
      SDDefs.SD[SDDefs.sXferTrap] ← xferTrapHandler;
      ENDLOOP;
    END;

  Break: PUBLIC PROCEDURE = -- executed by (non-worry) BRK instruction
    BEGIN
    ProcessBreakpoint: PROCEDURE [CoreSwapDefs.SVPointer] = MACHINE CODE
      BEGIN Mopcodes.zKFCB, SDDefs.sProcessBreakpoint END;
    f: FrameHandle;
    state: StateVector;
    xferTrapStatus: TrapStatus;
    state ← STATE;
    state.dest ← f ← state.source;
    state.source ← FrameOps.MyLocalFrame[];
    f.pc ← [IF f.pc < 0 THEN -f.pc ELSE (1 - f.pc)];
    ProcessBreakpoint[@state];
    xferTrapStatus ← TrapOps.ReadXTS[];
    IF xferTrapStatus.state = on THEN TrapOps.WriteXTS[TraceNext];
    RETURN WITH state
    END;

  -- Worry mode breakpoints


  WorryBreaker: PUBLIC PROCEDURE RETURNS [FrameHandle] =
    BEGIN
    state: StateVector;
    frame: FrameHandle;
    esv: CoreSwapDefs.ExternalStateVector;
    xferTrapStatus: TrapStatus;
    state.instbyte ← 0;
    state.stkptr ← 1;
    state.stk[0] ← FrameOps.MyLocalFrame[];
    state.dest ← FrameOps.GetReturnLink[];
    ProcessDefs.DisableInterrupts[];
    ProcessDefs.DisableInterrupts[];
    DO
      xferTrapStatus ← TrapOps.ReadXTS[];
      IF xferTrapStatus.state = on THEN TrapOps.WriteXTS[TraceNext];
      -- previous WR is aligned so now 2 DWDCs to prevent a NOOP
      ProcessDefs.EnableInterrupts[];
      ProcessDefs.EnableInterrupts[];
      TRANSFER WITH state;
      ProcessDefs.DisableInterrupts[];
      ProcessDefs.DisableInterrupts[];
      state ← STATE;
      frame ← state.dest ← state.source;
      FrameOps.SetReturnFrame[frame];
      state.source ← FrameOps.MyLocalFrame[];
      frame.pc ← [IF frame.pc < 0 THEN -frame.pc ELSE (1 - frame.pc)];
      esv ← CoreSwapDefs.PuntInfo↑.puntESV;
      esv.state ← @state;
      esv.reason ← worrybreak;
      DO
	OPEN KeyDefs;
	WBPort[@esv];
	SELECT esv.reason FROM
	  proceed => EXIT;
	  kill => ImageDefs.AbortMesa[];
	  showscreen =>
	    UNTIL Keys.Spare3 = down OR Keys.FR5 = down DO NULL ENDLOOP;
	  ENDCASE;
	esv.reason ← return;
	ENDLOOP;
      ENDLOOP;
    END;

  WorryCallDebugger: PUBLIC PROCEDURE RETURNS [FrameHandle] =
    BEGIN
    state: StateVector;
    esv: CoreSwapDefs.ExternalStateVector;
    state.instbyte ← 0;
    state.stkptr ← 1;
    state.stk[0] ← FrameOps.MyLocalFrame[];
    state.dest ← FrameOps.GetReturnLink[];
    ProcessDefs.DisableInterrupts[];
    DO
      -- the following RR and POP is to guarantee that there is no NOOP between
      -- the DWDC and the LST
      [] ← TrapOps.ReadATP[];
      ProcessDefs.EnableInterrupts[];
      TRANSFER WITH state;
      ProcessDefs.DisableInterrupts[];
      state ← STATE;
      state.dest ← state.source;
      FrameOps.SetReturnFrame[state.dest];
      state.source ← FrameOps.MyLocalFrame[];
      esv ← CoreSwapDefs.PuntInfo↑.puntESV;
      esv.state ← @state;
      esv.reason ← worrycall;
      DO
	OPEN KeyDefs;
	WBPort[@esv];
	SELECT esv.reason FROM
	  proceed => EXIT;
	  kill => ImageDefs.AbortMesa[];
	  showscreen =>
	    UNTIL Keys.Spare3 = down OR Keys.FR5 = down DO NULL ENDLOOP;
	  ENDCASE;
	esv.reason ← return;
	ENDLOOP;
      ENDLOOP;
    END;

  interruptWakeup: PUBLIC CONDITION;

  GetDebugger: PROCEDURE = MACHINE CODE
    BEGIN Mopcodes.zKFCB, SDDefs.sInterrupt END;

  TimeData: POINTER TO TimeDataRecord = LOOPHOLE[572B];
  TimeDataRecord: TYPE = MACHINE DEPENDENT RECORD [
    timeOfDay: InlineDefs.BcplLongNumber,
    milliSeconds: InlineDefs.BcplLongNumber,
    timeBase: InlineDefs.BcplLongNumber];

  disableInterrupt: PUBLIC BOOLEAN;

  InterruptProcess: PUBLIC ENTRY PROCEDURE =
    BEGIN
    keys: POINTER TO KeyDefs.KeyBits ← KeyDefs.Keys;
    interruptState: KeyDefs.updown ← up;
    pp: ProcessDefs.Priority;
    SV: POINTER TO ARRAY ProcessDefs.Priority OF ControlDefs.StateVector =
      LOOPHOLE[ProcessOps.FirstStateVector↑];
    timeData: POINTER TO TimeDataRecord ← TimeData;
    RealTimeClock: POINTER TO CARDINAL = LOOPHOLE[430B];
    clockSecond: POINTER TO InlineDefs.BcplLongNumber;
    DO
      WAIT interruptWakeup[ ! ABORTED => CONTINUE];
      IF disableInterrupt THEN EXIT;
      clockSecond ← OsStaticDefs.OsStatics.ClockSecond;
      IF RealTimeClock↑ - timeData.timeBase.highbits > clockSecond.highbits THEN
	BEGIN OPEN InlineDefs;
	timeData.timeOfDay ← MesaToBcplLongNumber[
	  BcplToMesaLongNumber[timeData.timeOfDay] + 1];
	timeData.milliSeconds ← MesaToBcplLongNumber[
	  BcplToMesaLongNumber[timeData.milliSeconds] + 1000];
	timeData.timeBase ← MesaToBcplLongNumber[
	  BcplToMesaLongNumber[timeData.timeBase] + BcplToMesaLongNumber[
	    clockSecond↑]];
	END; -- The following code checks for Shft-Swat, the swat interrupt keys.
      IF keys.LeftShift = down AND keys.Spare3 = down THEN
	BEGIN
	ProcessDefs.DisableInterrupts[];
	UNTIL DiskDefs.NextDiskCommand↑ = DiskDefs.CBNil DO NULL ENDLOOP;
	IF keys.Ctrl = down THEN
	  BEGIN
	  code: ARRAY [0..2) OF WORD ← [77410B, 1400B];
	  [] ← BcplOps.BcplJSR[code: JSR, address: @code, arg: NIL];
	  ProcessDefs.EnableInterrupts[];
	  END
	ELSE
	  BEGIN
	  SwatVector: POINTER TO POINTER TO RECORD [
	    fill: ARRAY [0..4) OF WORD, swatee: AltoFileDefs.CFP] =
	    LOOPHOLE[567B];
	  [] ← BcplOps.BcplOutLd[
	    code: OutLd, file: @SwatVector.swatee, message: NIL];
	  ProcessDefs.EnableInterrupts[];
	  ImageDefs.AbortMesa[];
	  END;
	END
	-- The following code checks for Ctrl-Swat, the debugger interrupt keys.
      ELSE
	IF keys.Ctrl = down AND (keys.Spare3 = down OR keys.FR5 = down) THEN
	  BEGIN
	  IF interruptState = up AND CoreSwapDefs.PuntInfo↑ # LOOPHOLE[0] 
	    AND CoreSwapDefs.PuntInfo.pDebuggerFP # NIL THEN
	    BEGIN
	    interruptState ← down;
	    FOR pp IN [0..NucleusOps.InterruptPriority) DO
	      SV[pp].instbyte ← Mopcodes.zBRK;
	      WAIT interruptWakeup;
	      IF SV[pp].instbyte = 0 THEN EXIT ELSE SV[pp].instbyte ← 0;
	      REPEAT FINISHED => GetDebugger[ ! ABORTED => CONTINUE];
	      ENDLOOP;
	    END;
	  END
	ELSE interruptState ← up;
      ENDLOOP;
    END;


  END.