-- File: AltoPRDriver.mesa
--  Edit: BLyon  January 16, 1981  1:33 PM
--  Edit: HGM  February 17, 1981  6:00 AM
--  Edit: L. Stewart  February 20, 1980  4:24 PM

DIRECTORY
  BcplOps USING [CleanupReason],
  BitBltDefs USING [BBptr, BBTableSpace, AlignedBBTable, BITBLT],
  ImageDefs USING [
    CleanupItem, AddCleanupProcedure, RemoveCleanupProcedure, AllReasons],
  InlineDefs USING [COPY],
  Process USING [Detach, DisableTimeout, MsecToTicks, SetPriority, SetTimeout],
  Put USING [Line],
  Storage USING [Node, Free],
  String USING [AppendString, AppendDecimal],
  Time USING [AppendCurrent],
  Alto1822Defs,
  AltoPRDefs,
  BufferDefs,
  AltoRam USING [Shorten, GetTicks, msPerTick],
  StatsDefs USING [StatBump, StatIncr, StatCounterIndex],
  CommFlags USING [doStats],
  CommUtilDefs USING [AddInterruptHandler, RemoveInterruptHandler],
  DriverDefs USING [
    PacketRadioStats, GetGiantVector, Glitch, GetInputBuffer, MaybeGetFreeBuffer,
    Network, NetworkObject, AddDeviceToChain, PutOnGlobalDoneQueue,
    PutOnGlobalInputQueue],
  DriverTypes USING [prEncapsulationOffset, prEncapsulationBytes],
  PupTypes USING [allHosts, PupErrorCode],
  SpecialSystem USING [HostNumber];

AltoPRDriver: MONITOR
  IMPORTS
    BitBltDefs, ImageDefs, InlineDefs, Process, Put, Storage, String, Time,
    BufferDefs, AltoRam, CommUtilDefs, DriverDefs, StatsDefs, Alto1822Defs,
    AltoPRDefs
  EXPORTS BufferDefs, DriverDefs, AltoPRDefs
  SHARES BufferDefs, SpecialSystem =
  BEGIN OPEN StatsDefs, BufferDefs, DriverDefs, AltoPRDefs, Alto1822Defs;

  -- EXPORTed TYPEs
  Network: PUBLIC TYPE = DriverDefs.Network;

  statPRPacketsReceived: PUBLIC StatCounterIndex;
  statPRImAliveReceived: PUBLIC StatCounterIndex;
  statPROneFragRcvd: PUBLIC StatCounterIndex;
  statPRTwoFragsRcvd: PUBLIC StatCounterIndex;
  statPRThreeFragsRcvd: PUBLIC StatCounterIndex;
  statPRWordsReceived: PUBLIC StatCounterIndex;

  statPRPacketsSent: PUBLIC StatCounterIndex;
  statPRImAliveSent: PUBLIC StatCounterIndex;
  statPROneFragSent: PUBLIC StatCounterIndex;
  statPRTwoFragsSent: PUBLIC StatCounterIndex;
  statPRThreeFragsSent: PUBLIC StatCounterIndex;
  statPRWordsSent: PUBLIC StatCounterIndex;

  statPRDuplicateFragment: PUBLIC StatCounterIndex;
  statPRAssemblyTimeout: PUBLIC StatCounterIndex;
  statPRAssemblyQOvf: PUBLIC StatCounterIndex;
  statPRInputBufferOvf: PUBLIC StatCounterIndex;

  statPRBadMagic: PUBLIC StatCounterIndex;
  statPRNotForMe: PUBLIC StatCounterIndex;
  statPRTooManyFragments: PUBLIC StatCounterIndex;
  statPRTotalTooBig: PUBLIC StatCounterIndex;
  statPREmptyFreeQueue: PUBLIC StatCounterIndex;

  statPROldPackets: PUBLIC StatCounterIndex;
  statPRPacketsSkipped: PUBLIC StatCounterIndex;
  statPRSequenceReset: PUBLIC StatCounterIndex;

  statPRLengthOvf: PUBLIC StatCounterIndex;
  statPRInvalidAddress: PUBLIC StatCounterIndex;
  statPRDestinationDown: PUBLIC StatCounterIndex;
  statPROutputQOvf: PUBLIC StatCounterIndex;
  statPRConnectionLimit: PUBLIC StatCounterIndex;
  statPROutPacketsDiscarded: PUBLIC StatCounterIndex;

  stat1822MissingInterrupt: PUBLIC StatCounterIndex;
  statPRTransferTimeout: PUBLIC StatCounterIndex;
  statPRROPsReceived: PUBLIC StatCounterIndex;
  statPRTOPsSent: PUBLIC StatCounterIndex;
  statPRImpWasDown: PUBLIC StatCounterIndex;

  --  Private Storage

  storagep1: POINTER;
  storagep2: POINTER;
  storagep3: POINTER;
  cb: POINTER TO AICommandBlock;

  myTOP: PRTOP ← [leader: prTOPLeader, startID:, endID:];
  myTOPd: AIBufferDescriptor ← [start: @myTOP, end: @myTOP + SIZE[PRTOP]];

  myInBuf: POINTER TO PRBuffer;
  myInBufd: AIBufferDescriptor;
  myOutBuf: POINTER TO PRBuffer;
  myOutBufd: AIBufferDescriptor;

  myPRAddress: PRAddress ← prInvalidAddress;
  mySequence: PRSequence ← prInitialSequenceNumber;
  myRoutes: PRRouteSet ← ALL[prNullRoute];

  defaultAssemblyQueueLimit: CARDINAL = 4;
  myVars: PRDriverVars ←
    [outBufp: myOutBuf, inBufp: myInBuf, topp: @myTOP, cbp:,
      prRouteSetp: @myRoutes, prTOPInterval: 6000,
      --  = 5 minutes, these are Alto ticks
      outputTimeoutInterval: 2000/AltoRam.msPerTick,
      assemblyTimeoutInterval: 3000/AltoRam.msPerTick, imAliveInterval: 10,
      --These are seconds
      prHostAliveTimeoutInterval: 41, maxOutputQueueLength: 12,
      maxAssemblyQueueLength: defaultAssemblyQueueLimit, hiQ: @highQueue,
      loQ: @lowQueue, aQ: @assemblyQueue, youAreOK:, iHearYou:];

  maxHiPriWords: CARDINAL = (50 + 22)/2; -- 50 data bytes in a pup
  myGray1: WORD = 125252B;
  myGray2: WORD = 052525B;
  inBuf, outBuf: Buffer;
  inputMode: InputMode;
  outputMode: OutputMode;
  watchMode: WatchMode;
  lastPacketNumber: ARRAY [1..maxPRPupAddress] OF INTEGER ←
    [-100, -100, -100, -100, -100];
  nextPacketNumber: ARRAY [1..maxPRPupAddress] OF INTEGER ← [1, 1, 1, 1, 1];

  youWereOK: PRAliveTable;
  cleanupItem: ImageDefs.CleanupItem ← [, ImageDefs.AllReasons, Broom];
  pleaseStop, oFlush, iFlush, topTime: BOOLEAN;

  highQueue, lowQueue, assemblyQueue: QueueObject;
  topTimer, imAliveTimer, timeSendStarted: CARDINAL;
  watcherProcess, inputInterruptProcess, outputInterruptProcess: PROCESS;
  watchTimer, inputInterrupt, outputInterrupt: CONDITION;
  inputInterruptPriority: WORD = 9;
  outputInterruptPriority: WORD = 10;
  inputInterruptChannel: WORD = 1000B; -- BITSHIFT[1,ProcessPriority];
  outputInterruptChannel: WORD = 2000B;
  controlInterruptChannel: WORD = 0B;

  myNetwork: DriverDefs.NetworkObject ←
    [decapsulateBuffer: DecapsulateBuffer, encapsulatePup: EncapsulatePup,
      encapsulateOis: EncapsulateOis, sendBuffer: SendBuffer,
      forwardBuffer: ForwardBuffer, activateDriver: ActivateDriver,
      deactivateDriver: DeactivateDriver, deleteDriver: DeleteDriver,
      interrupt: InputInterrupt, index:, device: packetradio, alive: TRUE,
      speed: 9, buffers: defaultAssemblyQueueLimit, spare:, netNumber:,
      hostNumber:, next: NIL, pupStats: DriverDefs.PacketRadioStats,
      stats: @myVars];

  FragmentationError: PUBLIC ERROR = CODE;
  ImpossibleEndcase: PUBLIC ERROR = CODE;
  UnreasonableHardwareStatus: PUBLIC ERROR = CODE;
  Unexpected1822Interrupt: PUBLIC ERROR = CODE;
  QueueScrambled: PUBLIC ERROR = CODE;
  DriverNotActive: PUBLIC ERROR = CODE;
  DriverAlreadyActive: PUBLIC ERROR = CODE;
  CantMakeImageWhileActive: PUBLIC ERROR = CODE;
  UnknownPRHost: PUBLIC ERROR = CODE;

  StartInput: INTERNAL PROCEDURE [
    inputBuffer: AIBufferDescriptor, newMode: InputMode] =
    BEGIN
    inputMode ← newMode;
    cb.inputPost ← notPosted;
    cb.inputBuffer ← inputBuffer;
    StartIO[inputSioCode];
    END;

  StartOutput: INTERNAL PROCEDURE [
    outputBuffer: AIBufferDescriptor, newMode: OutputMode] =
    BEGIN
    timeSendStarted ← AltoRam.GetTicks[];
    outputMode ← newMode;
    cb.command ← turnOnLastBit;
    StartIO[controlSioCode];
    cb.outputPost ← notPosted;
    cb.outputBuffer ← outputBuffer;
    StartIO[outputSioCode];
    END;

  SetControl: PROCEDURE [command: AICommandCode] =
    BEGIN
    cb.controlPost ← notPosted;
    cb.command ← command;
    StartIO[controlSioCode];
    END;

  AcceptInputPacket: INTERNAL PROCEDURE =
    BEGIN
    IF CommFlags.doStats THEN StatIncr[statPRPacketsReceived];
    SELECT myInBuf.h.fragmentation.thisFrag FROM
      0 =>
	BEGIN
	IF inBuf.encapsulation.first THEN GOTO dupeRestart;
	inBuf.encapsulation.first ← TRUE;
	END;
      1 =>
	BEGIN
	IF inBuf.encapsulation.second THEN GOTO dupeRestart;
	inBuf.encapsulation.second ← TRUE;
	END;
      2 =>
	BEGIN
	IF inBuf.encapsulation.third THEN GOTO dupeRestart;
	inBuf.encapsulation.third ← TRUE;
	END;
      ENDCASE =>
	BEGIN
	-- This might happen because of a nasty hardware error
	--Glitch[FragmentationError];
	ReturnFreeBuffer[inBuf];
	inBuf ← NIL;
	RETURN;
	END;
    inBuf.encapsulation.numFragsRcvd ← inBuf.encapsulation.numFragsRcvd + 1;
    inBuf.length ←
      inBuf.length + myInBuf.h.leader.length.packet - SIZE[PRPupHeader];
    IF inBuf.encapsulation.numFragsRcvd = myInBuf.h.fragmentation.numFrags THEN
      BEGIN
      inBuf.encapsulation.prType ← myInBuf.h.fragmentation.packetType;
      IF inBuf.encapsulation.prType = imAlive THEN
	BEGIN
	p: LONG POINTER TO PRImAliveEntry ← LOOPHOLE[@inBuf.bufferBody];
	IF p.source IN [1..maxPRPupAddress] THEN
	  BEGIN
	  myVars.iHearYou[p.source] ← myVars.prHostAliveTimeoutInterval;
	  IF p.iHeardYou[myNetwork.hostNumber] THEN
	    myVars.youAreOK[p.source] ← TRUE;
	  END;
	IF CommFlags.doStats THEN StatIncr[statPRImAliveReceived];
	ReturnFreeBuffer[inBuf];
	END
      ELSE
	BEGIN
	inBuf.network ← LONG[@myNetwork];
	inBuf.device ← packetradio;
	SELECT inBuf.encapsulation.numFragsRcvd FROM
	  1 => StatIncr[statPROneFragRcvd];
	  2 => StatIncr[statPRTwoFragsRcvd];
	  3 => StatIncr[statPRThreeFragsRcvd];
	  ENDCASE => Glitch[FragmentationError];
	IF CommFlags.doStats THEN StatBump[statPRWordsReceived, inBuf.length];
	PutOnGlobalInputQueue[inBuf];
	END;
      END
    ELSE Enqueue[@assemblyQueue, inBuf];
    inBuf ← NIL;
    EXITS
      dupeRestart =>
	BEGIN
	Enqueue[@assemblyQueue, inBuf];
	inBuf ← NIL;
	IF CommFlags.doStats THEN StatIncr[statPRDuplicateFragment];
	END;
    END;

  FindInputBuffer: INTERNAL PROCEDURE =
    BEGIN
    q: PRSequence;
    inBuf ← assemblyQueue.first;
    UNTIL inBuf = NIL DO
      q ← LOOPHOLE[inBuf.encapsulation.prSequence];
      IF myInBuf.h.leader.sequence.number = q.number AND myInBuf.h.leader.source =
	inBuf.encapsulation.prAddress THEN
	BEGIN [] ← ExtractFromQueue[@assemblyQueue, inBuf]; RETURN; END;
      inBuf ← inBuf.next;
      ENDLOOP;
    IF assemblyQueue.length > myVars.maxAssemblyQueueLength THEN --recycle buffer
      BEGIN
      IF CommFlags.doStats THEN StatIncr[statPRAssemblyQOvf];
      inBuf ← Dequeue[@assemblyQueue]; --remove from head
      GOTO setupBuffer;
      END
    ELSE
      BEGIN inBuf ← GetInputBuffer[]; IF inBuf # NIL THEN GOTO setupBuffer; END;
    EXITS
      setupBuffer =>
	BEGIN
	inBuf.encapsulation.timer ← AltoRam.GetTicks[];
	inBuf.encapsulation.numFragsRcvd ← 0;
	inBuf.encapsulation.first ← inBuf.encapsulation.second ←
	  inBuf.encapsulation.third ← FALSE;
	inBuf.encapsulation.prSequence ← LOOPHOLE[PRSequence[
	  myInBuf.h.leader.sequence.number, 0]];
	inBuf.encapsulation.prAddress ← myInBuf.h.leader.source;
	inBuf.encapsulation.prLength ←
	  inBuf.length - DriverTypes.prEncapsulationOffset;
	inBuf.length ← 0;
	RETURN;
	END;
    END;

  TryOutput: INTERNAL PROCEDURE =
    BEGIN
    IF topTime THEN GOTO sendTOP;
    IF outBuf # NIL THEN GOTO sendThisOne;
    DO
      SELECT TRUE FROM
	highQueue.length # 0 => outBuf ← Dequeue[@highQueue];
	lowQueue.length # 0 => outBuf ← Dequeue[@lowQueue];
	ENDCASE => BEGIN outBuf ← NIL; EXIT; END;
      mySequence.number ← mySequence.number + 1;
      outBuf.encapsulation.prSequence ← LOOPHOLE[mySequence];
      outBuf.encapsulation.numFragsTrans ← 0;
      SELECT outBuf.encapsulation.prType FROM
	imAlive => GOTO sendThisOne;
	broadcastPup =>
	  FOR i: CARDINAL IN [1..maxPRPupAddress] DO
	    IF myVars.youAreOK[i] THEN
	      BEGIN outBuf.encapsulation.prAddress ← i; GOTO sendThisOne; END;
	    ENDLOOP;
	pup =>
	  IF myVars.youAreOK[outBuf.encapsulation.prAddress] THEN
	    GOTO sendThisOne;
	ENDCASE => Glitch[ImpossibleEndcase];
      PutOnGlobalDoneQueue[outBuf];
      outBuf ← NIL;
      IF CommFlags.doStats THEN StatIncr[statPRDestinationDown];
      ENDLOOP;
    --  ELSE  nothing to do
    outputMode ← idle;
    cb.outputPost ← notPosted;
    EXITS
      sendTOP =>
	BEGIN
	IF CommFlags.doStats THEN StatIncr[statPRTOPsSent];
	StartOutput[myTOPd, sendTop];
	END;
      sendThisOne => SendWithRoute[];
    END;

  SendWithRoute: INTERNAL PROCEDURE =
    BEGIN
    q: PRSequence;
    pn: POINTER TO INTEGER;
    bufOffset, fragSize: CARDINAL;
    q ← LOOPHOLE[outBuf.encapsulation.prSequence];
    bufOffset ← outBuf.encapsulation.numFragsTrans*maxFragSize;
    IF outBuf.encapsulation.numFragsTrans = (outBuf.encapsulation.numFrags - 1)
      THEN fragSize ← outBuf.encapsulation.prLength
    ELSE fragSize ← maxFragSize;
    myOutBuf.h.leader.destination ←
      outBuf.encapsulation.prAddress + prAddressBase;
    myOutBuf.h.leader.route ← myRoutes[outBuf.encapsulation.prAddress];
    myOutBuf.h.leader.sequence ← PRSequence[
      q.number, outBuf.encapsulation.numFragsTrans];
    myOutBufd.end ← @(myOutBuf.body) + fragSize;
    myOutBuf.h.leader.length.packet ← SIZE[PRPupHeader] + fragSize;
    myOutBuf.h.fragmentation.numFrags ← outBuf.encapsulation.numFrags;
    myOutBuf.h.fragmentation.thisFrag ← outBuf.encapsulation.numFragsTrans;
    myOutBuf.h.fragmentation.packetType ← outBuf.encapsulation.prType;
    pn ← @nextPacketNumber[outBuf.encapsulation.prAddress];
    myOutBuf.h.packetNumber ← pn↑;
    pn↑ ← pn↑ + 1;

    PREncrypt[
      from: AltoRam.Shorten[
      @outBuf.encapsulation + DriverTypes.prEncapsulationOffset + bufOffset],
      to: @myOutBuf.body, count: fragSize];



    IF outBuf.encapsulation.numFragsTrans >= outBuf.encapsulation.numFrags THEN
      Glitch[FragmentationError];
    StartOutput[myOutBufd, normal];
    END;


  InputInterrupt: ENTRY PROCEDURE =
    BEGIN
    iPostData: POINTER TO AIPostStatus ← @cb.inputPost;
    bufOffset, fragSize: CARDINAL;
    lastPacketp: POINTER TO INTEGER;
    sequenceError: INTEGER;

    Process.SetPriority[3];

    UNTIL pleaseStop DO
      IF iPostData↑ = notPosted THEN
	DO
	  WAIT inputInterrupt;
	  IF pleaseStop THEN RETURN;
	  IF iPostData↑ # notPosted OR iFlush THEN EXIT;
	  IF CommFlags.doStats THEN StatIncr[stat1822MissingInterrupt];
	  ENDLOOP;
      BEGIN -- solely for exits clauses
      IF iFlush THEN BEGIN iFlush ← FALSE; GOTO Discard; END;
      SELECT inputMode FROM
	discard =>
	  SELECT iPostData↑.mcStat FROM
	    -- iBufLenZero =>  Glitch "can't happen"

	    iBufFull => GOTO Discard;
	    allOK, iBufFullDone => -- discard complete, look at new packet
	      GOTO NewRead;
	    ENDCASE => Glitch[UnreasonableHardwareStatus];
	normal =>
	  SELECT iPostData↑.mcStat FROM
	    -- iBufLenZero =>  Glitch "can't happen"

	    iBufFull => --These shouldn't happen, indicates garbage packet
	      BEGIN
	      IF CommFlags.doStats THEN StatIncr[statPRInputBufferOvf];
	      GOTO Discard;
	      END;
	    iBufFullDone =>
	      BEGIN
	      IF CommFlags.doStats THEN StatIncr[statPRInputBufferOvf];
	      GOTO NewRead;
	      END;
	    allOK =>
	      BEGIN
	      -- Try to obtain a buffer, stick under input otherwise discard
	      -- would like to do a WAIT on getbuffer, discard on timeout
	      IF myInBuf.h.leader.control = 0 THEN
		BEGIN
		IF CommFlags.doStats THEN StatIncr[statPRROPsReceived];
		GOTO NewRead;
		END;
	      IF myInBuf.h.magic # prSecretNumber OR
		myInBuf.h.leader.length.header # SIZE[PRLeader] THEN
		BEGIN
		IF CommFlags.doStats THEN StatIncr[statPRBadMagic];
		GOTO NewRead;
		END;
	      IF myInBuf.h.leader.destination # myPRAddress THEN
		BEGIN
		IF CommFlags.doStats THEN StatIncr[statPRNotForMe];
		GOTO NewRead;
		END;
	      -- ELSE probably a pup
	      IF myInBuf.h.fragmentation.thisFrag >=
		myInBuf.h.fragmentation.numFrags THEN
		BEGIN
		IF CommFlags.doStats THEN StatIncr[statPRTooManyFragments];
		GOTO NewRead;
		END;
	      -- count out-of-orderness
	      bufOffset ← myInBuf.h.leader.source - prAddressBase;
	      IF bufOffset IN [1..maxPRPupAddress] THEN
		BEGIN
		lastPacketp ← @lastPacketNumber[bufOffset];
		sequenceError ← myInBuf.h.packetNumber - lastPacketp↑;
		SELECT sequenceError FROM
		  1 => lastPacketp↑ ← myInBuf.h.packetNumber; -- Just right

		  IN [-9..0] => -- Duplicate or old
		    IF CommFlags.doStats THEN StatIncr[statPROldPackets];
		  IN [2..9] => -- Some apparently lost
		    BEGIN
		    IF CommFlags.doStats THEN
		      StatBump[statPRPacketsSkipped, sequenceError - 1];
		    lastPacketp↑ ← myInBuf.h.packetNumber;
		    END;
		  ENDCASE =>
		    BEGIN
		    IF CommFlags.doStats THEN StatIncr[statPRSequenceReset];
		    lastPacketp↑ ← myInBuf.h.packetNumber;
		    END;
		END;
	      FindInputBuffer[];
	      IF inBuf # NIL THEN
		BEGIN
		bufOffset ← myInBuf.h.fragmentation.thisFrag*maxFragSize;
		fragSize ← myInBuf.h.leader.length.packet - SIZE[PRPupHeader];
		IF bufOffset + fragSize > inBuf.encapsulation.prLength THEN
		  BEGIN
		  ReturnFreeBuffer[inBuf];
		  inBuf ← NIL;
		  IF CommFlags.doStats THEN StatIncr[statPRTotalTooBig];
		  GOTO NewRead;
		  END;
		PREncrypt[
		  from: @myInBuf.body,
		  to: AltoRam.Shorten[
		  @inBuf.encapsulation + DriverTypes.prEncapsulationOffset +
		    bufOffset], count: fragSize];
		AcceptInputPacket[];
		GOTO NewRead;
		END -- Dismiss

	      ELSE
		BEGIN
		IF CommFlags.doStats THEN StatIncr[statPREmptyFreeQueue];
		GOTO NewRead;
		END;
	      END;
	    ENDCASE => Glitch[UnreasonableHardwareStatus];
	ENDCASE => Glitch[ImpossibleEndcase];
      EXITS
	NewRead => StartInput[myInBufd, normal]; --restart input

	Discard => StartInput[myInBufd, discard]; --restart input

      END;
      ENDLOOP; --End of UNTIL pleaseStop Loop

    END; --InputInterrupt

  OutputInterrupt: ENTRY PROCEDURE =
    BEGIN
    oPostData: POINTER TO AIPostStatus ← @cb.outputPost;

    Process.SetPriority[3];

    UNTIL pleaseStop DO
      IF oPostData↑ = notPosted THEN
	DO
	  WAIT outputInterrupt;
	  IF pleaseStop THEN RETURN;
	  IF oPostData↑ # notPosted OR oFlush THEN EXIT;
	  IF CommFlags.doStats THEN StatIncr[stat1822MissingInterrupt];
	  ENDLOOP;



      IF oFlush THEN
	BEGIN --send NOP, then retry
	DO
	  IF outBuf # NIL THEN
	    BEGIN
	    PutOnGlobalDoneQueue[outBuf]; --discard, really
	    IF CommFlags.doStats THEN StatIncr[statPROutPacketsDiscarded];
	    END;
	  SELECT TRUE FROM
	    highQueue.length # 0 => outBuf ← Dequeue[@highQueue];
	    lowQueue.length # 0 => outBuf ← Dequeue[@lowQueue];
	    ENDCASE => BEGIN outBuf ← NIL; EXIT; END;
	  ENDLOOP;
	oFlush ← FALSE;
	StartOutput[myTOPd, sendTop];
	END -- Dismiss

      ELSE -- hardware status is OK
	BEGIN
	IF oPostData↑.mcStat # allOK THEN Glitch[UnreasonableHardwareStatus];
	SELECT outputMode FROM
	  idle => Glitch[Unexpected1822Interrupt];
	  sendTop => -- Probably a nop, check queue
	    BEGIN
	    topTime ← FALSE;
	    TryOutput[]; -- Dismiss

	    END; -- Dismiss

	  normal =>
	    BEGIN
	    IF CommFlags.doStats THEN StatIncr[statPRPacketsSent];
	    IF CommFlags.doStats THEN
	      StatBump[statPRWordsSent, myOutBuf.h.leader.length.packet];
	    outBuf.encapsulation.numFragsTrans ←
	      outBuf.encapsulation.numFragsTrans + 1;
	    IF outBuf.encapsulation.numFragsTrans = outBuf.encapsulation.numFrags
	      THEN
	      BEGIN -- Only for exits clauses
	      SELECT outBuf.encapsulation.prType FROM
		imAlive =>
		  BEGIN
		  IF CommFlags.doStats THEN StatIncr[statPRImAliveSent];
		  IF outBuf.encapsulation.prAddress = maxPRPupAddress THEN
		    GOTO DoneWithIt;
		  outBuf.encapsulation.prAddress ←
		    outBuf.encapsulation.prAddress + 1;
		  GOTO DoNext;
		  END;
		broadcastPup, pup =>
		  BEGIN
		  IF CommFlags.doStats THEN
		    SELECT outBuf.encapsulation.numFrags FROM
		      1 => StatIncr[statPROneFragSent];
		      2 => StatIncr[statPRTwoFragsSent];
		      3 => StatIncr[statPRThreeFragsSent];
		      ENDCASE;
		  IF outBuf.encapsulation.prType = broadcastPup THEN
		    FOR i: CARDINAL IN
		      [outBuf.encapsulation.prAddress + 1..maxPRPupAddress] DO
		      IF myVars.youAreOK[i] THEN
			BEGIN
			outBuf.encapsulation.prAddress ← i;
			GOTO DoNext;
			END;
		      ENDLOOP;
		  GOTO DoneWithIt;
		  END;
		ENDCASE => Glitch[ImpossibleEndcase];
	      EXITS
		DoneWithIt =>
		  BEGIN -- done with this one
		  PutOnGlobalDoneQueue[outBuf];
		  outBuf ← NIL;
		  END;
		DoNext =>
		  BEGIN
		  outBuf.encapsulation.numFragsTrans ← 0;
		  mySequence.number ← mySequence.number + 1;
		  outBuf.encapsulation.prSequence ← LOOPHOLE[mySequence];
		  END;
	      END;
	    TryOutput[];
	    END; -- Dismiss

	  ENDCASE => Glitch[ImpossibleEndcase];
	END; --End of ELSE clause of hardware status IF

      ENDLOOP; --End of UNTIL pleaseStop loop

    END; --OutputInterrupt

  PREncrypt: PROCEDURE [from, to: POINTER, count: CARDINAL] =
    BEGIN
    bbTable: BitBltDefs.BBTableSpace;
    bbt: BitBltDefs.BBptr ← BitBltDefs.AlignedBBTable[@bbTable];
    InlineDefs.COPY[from: from, nwords: count, to: to];
    bbt↑ ←
      [sourcealt: FALSE, destalt: FALSE, sourcetype: gray, function: invert,
	dbca: to, dbmr: 1, dlx: 0, dty: 0, dw: 16, dh: count, sbca: from, sbmr: 1,
	slx: 0, sty: 1, -- Go backwards
	gray0: myGray1, gray1: myGray2, gray2: myGray1, gray3: myGray2];
    BitBltDefs.BITBLT[bbt];
    END;


  StartWatcher: PROCEDURE =
    BEGIN  -- Extra layer of kludgery because of Gateway startup troubles
    Process.SetPriority[1];
    watcherProcess ← FORK Watcher[];
    END;

  Watcher: ENTRY PROCEDURE =
    BEGIN
    UNTIL pleaseStop DO
      SetControl[doNothing]; --get hardware status
      BEGIN --For exits clause
      --Send a ImAlive occasionally
      IF imAliveTimer = 0 THEN
	BEGIN IF SendImAlive[] THEN imAliveTimer ← myVars.imAliveInterval; END
      ELSE imAliveTimer ← imAliveTimer - 1;
      --Timeout old ImAlive entries
      FOR i: CARDINAL IN [1..maxPRPupAddress] DO
	IF myVars.iHearYou[i] > 0 THEN
	  BEGIN
	  myVars.iHearYou[i] ← myVars.iHearYou[i] - 1;
	  IF myVars.iHearYou[i] = 0 THEN myVars.youAreOK[i] ← FALSE;
	  END;
	IF CommFlags.doStats AND myVars.youAreOK[i] # youWereOK[i] THEN
	  LogPRChange[i];
	ENDLOOP;
      youWereOK ← myVars.youAreOK;
      SELECT watchMode FROM
	normal =>
	  BEGIN
	  nextb, thisb: Buffer;
	  IF cb.controlPost.impWasDown = ON THEN
	    BEGIN
	    IF CommFlags.doStats THEN StatIncr[statPRImpWasDown];
	    GOTO Reset;
	    END;
	  --Flush stuck packets
	  IF
	    (outputMode # idle AND (AltoRam.GetTicks[] - timeSendStarted) >
	      myVars.outputTimeoutInterval) THEN
	    BEGIN
	    IF CommFlags.doStats THEN StatIncr[statPRTransferTimeout];
	    GOTO Reset;
	    END;
	  --Check for old partly assembled packets
	  nextb ← assemblyQueue.first;
	  UNTIL nextb = NIL DO
	    thisb ← nextb;
	    nextb ← thisb.next;
	    IF (AltoRam.GetTicks[] - thisb.encapsulation.timer) >
	      myVars.assemblyTimeoutInterval THEN
	      BEGIN
	      IF ExtractFromQueue[@assemblyQueue, thisb] = NIL THEN
		Glitch[QueueScrambled];
	      ReturnFreeBuffer[thisb];
	      IF CommFlags.doStats THEN StatIncr[statPRAssemblyTimeout];
	      END;
	    ENDLOOP;
	  --Send an TOP occasionally
	  IF (NOT topTime) AND (AltoRam.GetTicks[] - topTimer) >
	    myVars.prTOPInterval THEN
	    BEGIN topTimer ← AltoRam.GetTicks[]; topTime ← TRUE; END;
	  END;
	flappingRelay =>
	  BEGIN watchMode ← justFlapped; SetControl[turnOnRelay]; END;
	justFlapped => BEGIN watchMode ← normal; SetControl[tryClearIWD]; END;
	ENDCASE => Glitch[ImpossibleEndcase];
      EXITS
	Reset =>
	  BEGIN
	  watchMode ← flappingRelay;
	  oFlush ← iFlush ← TRUE;
	  SetControl[turnOffRelay];
	  SetControl[masterReset];
	  NOTIFY outputInterrupt;
	  NOTIFY inputInterrupt;
	  END;
      END;
      WAIT watchTimer;
      ENDLOOP;
    END;

  SendImAlive: INTERNAL PROCEDURE RETURNS [BOOLEAN] =
    BEGIN
    b: Buffer ← MaybeGetFreeBuffer[];
    p: LONG POINTER TO PRImAliveEntry;
    IF b = NIL THEN RETURN[FALSE];
    p ← LOOPHOLE[@b.bufferBody];
    b.encapsulation.prType ← imAlive;
    b.encapsulation.prAddress ← 1;
    b.encapsulation.numFrags ← 1;
    b.length ← SIZE[PRImAliveEntry] + 1;
    b.encapsulation.prLength ← b.length;
    b.device ← packetradio;
    p.source ← myNetwork.hostNumber;
    FOR i: CARDINAL IN [1..maxPRPupAddress] DO
      p.iHeardYou[i] ← myVars.iHearYou[i] > 0; ENDLOOP;
    Enqueue[@highQueue, b];
    IF outputMode = idle THEN TryOutput[];
    RETURN[TRUE];
    END;

  LogPRChange: INTERNAL PROCEDURE [host: CARDINAL] =
    BEGIN
    text: STRING = [100];
    Time.AppendCurrent[text];
    String.AppendString[text, "  PR Host "L];
    String.AppendDecimal[text, host];
    String.AppendString[
      text, IF myVars.youAreOK[host] THEN " up."L ELSE " down."L];
    Put.Line[NIL, text];
    END;

  DecapsulateBuffer: PROCEDURE [b: Buffer] RETURNS [BufferType] =
    BEGIN
    SELECT b.encapsulation.prType FROM
      pup, broadcastPup =>
	BEGIN
	IF 2*b.length < b.pupLength + DriverTypes.prEncapsulationBytes THEN
	  BEGIN
	  IF CommFlags.doStats THEN StatIncr[statPupsDiscarded];
	  RETURN[rejected];
	  END;
	RETURN[pup];
	END;
      ENDCASE => RETURN[rejected];
    END;

  EncapsulatePup: PROCEDURE [b: PupBuffer, destination: PupHostID] =
    BEGIN
    b.encapsulation.prType ←
      IF destination = PupTypes.allHosts THEN broadcastPup ELSE pup;
    IF LOOPHOLE[destination, CARDINAL] > maxPRPupAddress THEN
      b.encapsulation.prAddress ← prInvalidAddress
    ELSE b.encapsulation.prAddress ← LOOPHOLE[destination, PRAddress];
    b.length ← (b.pupLength + 1)/2;
    END;

  EncapsulateOis: PROCEDURE [
    b: OisBuffer, destination: SpecialSystem.HostNumber] =
    BEGIN
    b.encapsulation.prType ← ois;
    b.encapsulation.prAddress ← prInvalidAddress;
    END;

  ForwardBuffer: ENTRY PROCEDURE [b: Buffer] RETURNS [PupTypes.PupErrorCode] =
    BEGIN
    n: CARDINAL;
    high: BOOLEAN;
    IF b.encapsulation.prAddress = prInvalidAddress THEN
      BEGIN
      IF CommFlags.doStats THEN StatIncr[statPRInvalidAddress];
      RETURN[cantGetTherePupErrorCode];
      END;
    IF NOT b.encapsulation.prType = broadcastPup AND NOT myVars.youAreOK[
      b.encapsulation.prAddress] THEN
      BEGIN
      IF CommFlags.doStats THEN StatIncr[statPRDestinationDown];
      RETURN[cantGetTherePupErrorCode];
      END;
    IF (highQueue.length + lowQueue.length) >= myVars.maxOutputQueueLength THEN
      BEGIN
      IF CommFlags.doStats THEN StatIncr[statPROutputQOvf];
      RETURN[gatewayResourceLimitsPupErrorCode];
      END;
    n ← CountFriends[b, @lowQueue];
    high ← b.length < maxHiPriWords AND n = 0;
    n ← n + CountFriends[b, @highQueue];
    IF n > 5 THEN
      BEGIN
      IF CommFlags.doStats THEN StatIncr[statPRConnectionLimit];
      RETURN[connectionLimitPupErrorCode];
      END;
    Send[b, high];
    RETURN[noErrorPupErrorCode];
    END;

  SendBuffer: ENTRY PROCEDURE [b: Buffer] =
    BEGIN
    IF b.encapsulation.prAddress = prInvalidAddress THEN
      BEGIN
      IF CommFlags.doStats THEN StatIncr[statPRInvalidAddress];
      PutOnGlobalDoneQueue[b];
      RETURN;
      END;
    IF (highQueue.length + lowQueue.length) >= myVars.maxOutputQueueLength THEN
      BEGIN
      IF CommFlags.doStats THEN StatIncr[statPROutputQOvf];
      PutOnGlobalDoneQueue[b];
      RETURN;
      END;
    Send[b, b.length < maxHiPriWords AND CountFriends[b, @lowQueue] = 0];
    END;

  Send: INTERNAL PROCEDURE [b: Buffer, high: BOOLEAN] =
    BEGIN
    IF pleaseStop THEN Glitch[DriverNotActive];
    SELECT b.length FROM
      <= maxOneFragSize =>
	BEGIN
	b.encapsulation.numFrags ← 1;
	b.encapsulation.prLength ← b.length;
	END;
      <= maxTwoFragSize =>
	BEGIN
	b.encapsulation.numFrags ← 2;
	b.encapsulation.prLength ← b.length - maxOneFragSize;
	END;
      <= maxThreeFragSize =>
	BEGIN
	b.encapsulation.numFrags ← 3;
	b.encapsulation.prLength ← b.length - maxTwoFragSize;
	END;
      ENDCASE =>
	BEGIN
	PutOnGlobalDoneQueue[b];
	IF CommFlags.doStats THEN StatIncr[statPRLengthOvf];
	END;
    b.device ← packetradio;
    Enqueue[IF high THEN @highQueue ELSE @lowQueue, b];
    IF outputMode = idle THEN TryOutput[];
    END;

  CountFriends: INTERNAL PROCEDURE [b: Buffer, q: Queue] RETURNS [n: CARDINAL] =
    BEGIN
    n ← 0;
    SELECT b.encapsulation.prType FROM
      imAlive => RETURN;
      pup, broadcastPup =>
	BEGIN
	pup, maybe: PupBuffer;
	pup ← LOOPHOLE[b];
	maybe ← LOOPHOLE[q.first];
	UNTIL maybe = NIL DO
	  IF
	    (maybe.encapsulation.prType = pup OR maybe.encapsulation.prType =
	      broadcastPup)
	    --  Open code the multi-word compare because it uses non-resident code.
	     AND pup.dest.net = maybe.dest.net AND pup.dest.host = maybe.dest.host
	    AND pup.dest.socket = maybe.dest.socket THEN n ← n + 1;
	  maybe ← LOOPHOLE[maybe.next];
	  ENDLOOP;
	END;
      ois =>
	BEGIN
	ois, maybe: OisBuffer;
	ois ← LOOPHOLE[b];
	maybe ← LOOPHOLE[q.first];
	UNTIL maybe = NIL DO
	  IF maybe.encapsulation.prType = ois
	    --  Again, open code the multi-word compare.
	     AND ois.ois.destination.net = maybe.ois.destination.net AND
	    ois.ois.destination.host = maybe.ois.destination.host AND
	    ois.ois.destination.socket = maybe.ois.destination.socket THEN
	    n ← n + 1;
	  maybe ← LOOPHOLE[maybe.next];
	  ENDLOOP;
	END;
      ENDCASE => Glitch[ImpossibleEndcase];
    END;

  Broom: PROCEDURE [why: BcplOps.CleanupReason] =
    BEGIN
    SELECT why FROM
      Finish, Abort, OutLd =>
	BEGIN
	cb.inputBits ← 0B;
	cb.outputBits ← 0B;
	SetControl[turnOffRelay];
	END;
      InLd =>
	BEGIN
	cb.inputBits ← inputInterruptChannel;
	cb.outputBits ← outputInterruptChannel;
	watchMode ← flappingRelay;
	END;
      Save, Checkpoint => Glitch[CantMakeImageWhileActive];
      ENDCASE;
    SetControl[masterReset];
    END;

  -- COLD code, only used when turning things on+off

  CreatePacketRadioDriver: PUBLIC PROCEDURE [host, net: CARDINAL]
    RETURNS [BOOLEAN] =
    BEGIN
    myNetwork.netNumber ← [0, net];
    myNetwork.hostNumber ← host;
    IF host NOT IN [1..maxPRPupAddress] THEN Glitch[UnknownPRHost]
    ELSE myPRAddress ← LOOPHOLE[host + prAddressBase, PRAddress];
    myTOP.startID ← myTOP.endID ← myPRAddress;
    pleaseStop ← TRUE;
    AddDeviceToChain[@myNetwork, 0];
    RETURN[TRUE];
    END;

  DeleteDriver: PROCEDURE = BEGIN END;

  ActivateDriver: PROCEDURE =
    BEGIN
    IF ~pleaseStop THEN Glitch[DriverAlreadyActive];
    storagep1 ← Storage.Node[SIZE[AICommandBlock] + 1];
    cb ← Even[storagep1];
    cb.blank ← 0;
    cb.controlPost ← cb.outputPost ← cb.inputPost ← notPosted;
    cb.inputBits ← inputInterruptChannel;
    cb.outputBits ← outputInterruptChannel;
    cb.controlBits ← controlInterruptChannel;
    myVars.cbp ← cb;
    storagep2 ← Storage.Node[SIZE[PRBuffer] + 1];
    myInBuf ← Even[storagep2];
    myInBufd.start ← myInBuf;
    myInBufd.end ← myInBuf + SIZE[PRBuffer];
    storagep3 ← Storage.Node[SIZE[PRBuffer] + 1];
    myOutBuf ← Even[storagep3];
    myOutBufd.start ← myOutBuf;
    myOutBuf.h.leader ← prPupLeader;
    myOutBuf.h.leader.source ← myPRAddress;
    myOutBuf.h.magic ← prSecretNumber;
    SetControlPtr[cb];
    SetControl[turnOffTestMode];
    SetControl[masterReset];
    outputMode ← idle;
    watchMode ← flappingRelay; --watcher will turn on relay
    pleaseStop ← oFlush ← iFlush ← FALSE;
    topTime ← TRUE;
    topTimer ← AltoRam.GetTicks[];
    imAliveTimer ← 0;
    FOR i: CARDINAL IN [1..maxPRPupAddress] DO
      myVars.youAreOK[i] ← FALSE;
      myVars.iHearYou[i] ← 0;
      youWereOK[i] ← TRUE;
      ENDLOOP;
    QueueInitialize[@highQueue];
    QueueInitialize[@lowQueue];
    QueueInitialize[@assemblyQueue];
    CommUtilDefs.AddInterruptHandler[inputInterruptPriority, @inputInterrupt, 0];
    CommUtilDefs.AddInterruptHandler[
      outputInterruptPriority, @outputInterrupt, 0];
    inBuf ← outBuf ← NIL;
    ImageDefs.AddCleanupProcedure[@cleanupItem];
    inputInterruptProcess ← FORK InputInterrupt[];
    outputInterruptProcess ← FORK OutputInterrupt[];
    Process.Detach[FORK StartWatcher[]];
    LockedStartup[];
    END;


  Even: PROCEDURE [p: POINTER] RETURNS [POINTER] =
    BEGIN IF (LOOPHOLE[p, CARDINAL] MOD 2) # 0 THEN p ← p + 1; RETURN[p]; END;

  LockedStartup: ENTRY PROCEDURE = BEGIN StartInput[myInBufd, normal]; END;

  DeactivateDriver: PROCEDURE =
    BEGIN
    IF pleaseStop THEN Glitch[DriverNotActive];
    pleaseStop ← TRUE;
    LockedKill[];
    JOIN inputInterruptProcess;
    JOIN outputInterruptProcess;
    JOIN watcherProcess;
    -- These following steps are the same as Broom[finish]
    cb.inputBits ← 0B;
    cb.outputBits ← 0B;
    SetControl[turnOffRelay];
    SetControl[masterReset];

    ImageDefs.RemoveCleanupProcedure[@cleanupItem];
    CommUtilDefs.RemoveInterruptHandler[inputInterruptPriority];
    CommUtilDefs.RemoveInterruptHandler[outputInterruptPriority];
    IF outBuf # NIL THEN PutOnGlobalDoneQueue[outBuf];
    IF inBuf # NIL THEN ReturnFreeBuffer[inBuf];
    QueueCleanup[@highQueue];
    QueueCleanup[@lowQueue];
    QueueCleanup[@assemblyQueue];
    Storage.Free[storagep1];
    Storage.Free[storagep2];
    Storage.Free[storagep3];
    END;

  LockedKill: ENTRY PROCEDURE =
    BEGIN NOTIFY inputInterrupt; NOTIFY outputInterrupt; NOTIFY watchTimer; END;

  GetPRDriverVars: PUBLIC PROCEDURE RETURNS [POINTER TO PRDriverVars] =
    BEGIN RETURN[@myVars]; END;

  -- initialization
  -- IF ~doAlto THEN Glitch[compilationMixup];

  Process.DisableTimeout[@inputInterrupt];
  Process.DisableTimeout[@outputInterrupt];
  Process.SetTimeout[@watchTimer, Process.MsecToTicks[1000]];
  IF TRUE THEN BEGIN DriverDefs.GetGiantVector[].prThings ← @myVars; END;
  SetupPRThings[];
  END.