-- File: AltoSlaDriver.mesa
-- Edit: HGM,  March 12, 1981  1:02 PM
-- Edit: BLyon,  January 16, 1981  1:29 PM

DIRECTORY
  BcplOps USING [CleanupReason],
  ImageDefs USING [CleanupItem, AllReasons, AddCleanupProcedure],
  Inline USING [BITAND, BITXOR, BITSHIFT, LowByte, HighByte, HighHalf],
  Process USING [SetTimeout, DisableTimeout, MsecToTicks, SetPriority],
  Storage USING [Node, CopyString, FreeString],
  Password USING [Check, Encrypted],
  StatsDefs USING [StatIncr, StatBump, StatCounterIndex],
  CommFlags USING [doDebug, doStats],
  CommUtilDefs USING [AddInterruptHandler, CopyLong, InterruptLevel],
  AltoRam USING [GetTicks, msPerTick, ChangeControlReg, EnableEia, SetLineTab],
  AltoSlaDefs,
  DriverDefs USING [
    GetGiantVector, Glitch, SlaStats, MaybeGetFreeBuffer, GetInputBuffer, Network,
    NetworkObject, AddDeviceToChain, PutOnGlobalDoneQueue, PutOnGlobalInputQueue],
  PupDefs,
  BufferDefs,
  PupTypes USING [allHosts, PupErrorCode],
  DriverTypes USING [slaEncapsulationOffset, slaEncapsulationBytes, bufferSeal],
  SpecialSystem USING [broadcastHostNumber, HostNumber];

AltoSlaDriver: MONITOR
  IMPORTS
    ImageDefs, Inline, Process, Storage, Password, StatsDefs, DriverDefs,
    CommUtilDefs, BufferDefs, AltoRam, AltoSlaDefs
  EXPORTS BufferDefs, DriverDefs, AltoSlaDefs
  SHARES BufferDefs, DriverDefs, DriverTypes, SpecialSystem =
  BEGIN OPEN StatsDefs, DriverDefs, AltoSlaDefs, BufferDefs;

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

  maxPacketsPerLine: CARDINAL = 10;

  mode: {eia, commProc};

  hardware: CONDITION;
  cleanupItem: ImageDefs.CleanupItem ← [, ImageDefs.AllReasons, Broom];
  hardProcess, watcherProcess: PROCESS;
  pause: CONDITION;

  crcTable: POINTER TO ARRAY [0..maxByte) OF WORD;
  lineTable: POINTER TO ARRAY Line OF LineTableEntry;
  lineInfo: POINTER TO ARRAY Line OF LineInfoBlock ← NIL;
  routingTable: POINTER TO ARRAY SlaHost OF RoutingTableEntry ← NIL;

  myNetwork: DriverDefs.NetworkObject ←
    [decapsulateBuffer: DecapsulateBuffer, encapsulatePup: EncapsulatePup,
      encapsulateOis: EncapsulateOis, sendBuffer: SendBuffer,
      forwardBuffer: ForwardBuffer, activateDriver: ActivateDriver,
      deactivateDriver: DeactivateDriver, deleteDriver: DeleteDriver,
      interrupt: Interrupt, device: sla, alive:, speed: 10, index:, buffers:,
      spare:, netNumber:, hostNumber:, next: NIL, pupStats: DriverDefs.SlaStats,
      stats: NIL];

  ImpossibleEndcase: PUBLIC ERROR = CODE;
  QueueScrambled: PUBLIC ERROR = CODE;
  BufferSealBroken: PUBLIC ERROR = CODE;
  UnexpectedCompletionCodeFromMicrocode: PUBLIC ERROR = CODE;
  LineNumberTooBig: PUBLIC ERROR = CODE;
  CantTurnSlaDriverOffYet: PUBLIC ERROR = CODE;
  CantMakeImageWhileDriverIsActive: PUBLIC ERROR = CODE;
  HyperspaceNotSupported: PUBLIC ERROR = CODE;

  activeLines: CARDINAL ← 0;

  slaInterruptLevel: CommUtilDefs.InterruptLevel = 12;
  slaInterruptBit: WORD = Inline.BITSHIFT[1, slaInterruptLevel];

  routingInterval: CARDINAL = 5000/AltoRam.msPerTick;
  routingTimeout: CARDINAL = 20000/AltoRam.msPerTick;

  maxHiPriWords: CARDINAL = (2 + 50 + 22)/2; -- 50 data bytes in a pup

  pleaseStop: BOOLEAN = FALSE; -- you can't stop this thing yet

  slaEncapsulationOffset: CARDINAL = DriverTypes.slaEncapsulationOffset;
  slaEncapsulationBytes: CARDINAL = DriverTypes.slaEncapsulationBytes;
  slaEncapsulationWords: CARDINAL = slaEncapsulationBytes/2;

  statSlaPacketsSent: PUBLIC StatCounterIndex;
  statSlaWordsSent: PUBLIC StatCounterIndex;

  statSlaPacketsRecv: PUBLIC StatCounterIndex;
  statSlaWordsRecv: PUBLIC StatCounterIndex;

  statSlaRoutingPacketsSent: PUBLIC StatCounterIndex;
  statSlaRoutingPacketsReceived: PUBLIC StatCounterIndex;

  statSlaEmptyFreeQueue: PUBLIC StatCounterIndex;
  statSlaROR: PUBLIC StatCounterIndex;
  statSlaCRCError: PUBLIC StatCounterIndex;
  statSlaSyncError: PUBLIC StatCounterIndex;
  statSlaControlError: PUBLIC StatCounterIndex;

  statSlaNoRoute: PUBLIC StatCounterIndex;
  statSlaQueueFull: PUBLIC StatCounterIndex;
  statSlaConnectionLimit: PUBLIC StatCounterIndex;

  statSlaPacketsStuck: PUBLIC StatCounterIndex;
  statSlaLineTimeout: PUBLIC StatCounterIndex;
  statSlaLineDied: PUBLIC StatCounterIndex;
  statSlaFCT: PUBLIC StatCounterIndex;

  Watcher: ENTRY PROCEDURE =
    BEGIN
    line: Line ← 0;
    routeTime: CARDINAL ← AltoRam.GetTicks[];
    watchit: CONDITION;
    Process.SetTimeout[@watchit, Process.MsecToTicks[250]];
    Process.SetPriority[2];
    UNTIL pleaseStop DO
      WAIT watchit;
      -- should check for lost interrupts here if there is an easy way
      FlushDeadLines[];
      IF AltoRam.GetTicks[] - routeTime > routingInterval THEN
	BEGIN
	-- Send a routing table out over one line each time through to avoid using up too many buffers at the same time.
	IF line < activeLines THEN
	  BEGIN
	  lib: LIB = @lineInfo[line];
	  IF dialoutPassword # NIL AND (lib.state # up AND lib.state # loopedBack)
	    THEN SendPasswordPacket[line];
	  SendRoutingPacket[line];
	  END;
	IF (line ← line + 1) >= activeLines THEN
	  BEGIN
	  routeTime ← routeTime + routingInterval;
	  IF AltoRam.GetTicks[] - routeTime > routingInterval THEN
	    -- don't thrash if buffers are hard to get
	    routeTime ← AltoRam.GetTicks[];
	  line ← 0;
	  END;
	END;
      ENDLOOP;
    END;

  -- 2400 baud is 300 characters/sec.  Allow 10% for slop and whatever to get 270.
  -- There is also the possibility for DLE doubling, so that is another factor of 2.
  -- The framing is 9 bytes: SYN, SYN, SYN, DLE, STX, ..... DLE, ETX, CRC, CRC

  bytesPerTick: CARDINAL = 270/AltoRam.msPerTick; -- rounds down

  FlushDeadLines: INTERNAL PROCEDURE =
    BEGIN
    line: Line;
    lib: LIB;
    b: Buffer;
    stuck, timeout: BOOLEAN;
    t: CARDINAL;
    FOR line IN [0..activeLines) DO
      lib ← @lineInfo[line];
      b ← lib.sendBuffer;
      stuck ←
	(b # NIL AND ~LOOPHOLE[b.iocbChain, LongLCB].finished AND
	  (t ← (AltoRam.GetTicks[] - lib.timeSendStarted)) >
	  -- b.length is words, not bytes
	  (overheadPerPacket + lib.sendBuffer.length*(2*2))/bytesPerTick);
      timeout ← (AltoRam.GetTicks[] - lib.timeOfLastRT) > routingTimeout;
      IF timeout OR stuck THEN
	BEGIN
	IF lib.state # down THEN
	  BEGIN
	  IF CommFlags.doStats THEN StatIncr[statSlaLineDied];
	  IF CommFlags.doStats AND stuck THEN StatIncr[statSlaPacketsStuck];
	  IF CommFlags.doStats AND timeout THEN StatIncr[statSlaLineTimeout];
	  lib.deaths ← lib.deaths + 1;
	  END;
	IF stuck THEN lib.stuck ← lib.stuck + 1;
	IF timeout THEN lib.timeout ← lib.timeout + 1;
	StopLine[line];
	PurgeLineFromRT[line];
	lib.state ← down;
	lib.partner ← noPartner;
	StartLine[line];
	END;
      ENDLOOP;
    END;

  Interrupt: ENTRY PROCEDURE =
    BEGIN
    line: Line;
    lib: LIB;
    b, temp: Buffer;
    lcb: LongLCB;
    Process.SetPriority[3];
    UNTIL pleaseStop DO
      WAIT hardware;
      FOR line IN [0..activeLines) DO
	lib ← @lineInfo[line];
	WHILE (b ← lib.recvBuffer) # NIL DO
	  IF CommFlags.doDebug AND b.seal # DriverTypes.bufferSeal THEN
	    Glitch[BufferSealBroken];
	  lcb ← b.iocbChain;
	  IF ~lcb.finished THEN EXIT;
	  lib.recvBuffer ← b.next;
	  IF lcb.microcodeError # ok THEN
	    BEGIN -- something screwed up, recycle this buffer
	    SELECT lcb.microcodeError FROM
	      hardware =>
		BEGIN -- can only be ROR
		lib.overrun ← lib.overrun + 1;
		IF CommFlags.doStats THEN StatIncr[statSlaROR];
		END;
	      bufferOverflow =>
		BEGIN
		lib.syncErrors ← lib.syncErrors + 1;
		IF CommFlags.doStats THEN StatIncr[statSlaSyncError];
		END;
	      missingStx, trashAfterDle =>
		BEGIN
		lib.controlErrors ← lib.controlErrors + 1;
		IF CommFlags.doStats THEN StatIncr[statSlaControlError];
		END;
	      crc =>
		BEGIN
		lib.crcErrors ← lib.crcErrors + 1;
		IF CommFlags.doStats THEN StatIncr[statSlaCRCError];
		END;
	      ENDCASE => Glitch[UnexpectedCompletionCodeFromMicrocode];
	    AppendBuffer[b, line, recv];
	    LOOP;
	    END;
	  IF (temp ← DriverDefs.GetInputBuffer[]) = NIL THEN
	    BEGIN -- queue empty, recycle this buffer
	    AppendBuffer[b, line, recv];
	    IF CommFlags.doStats THEN StatIncr[statSlaEmptyFreeQueue];
	    LOOP;
	    END;
	  AppendBuffer[temp, line, recv];
	  b.network ← LONG[@myNetwork];
	  b.encapsulation.slaSourceLine ← line;
	  b.length ← (((b.length - slaEncapsulationOffset)*2) - lcb.bytesLeft)/2;
	  IF CommFlags.doStats THEN StatIncr[statSlaPacketsRecv];
	  IF CommFlags.doStats THEN StatBump[statSlaWordsRecv, b.length];
	  lib.packetsRecv ← lib.packetsRecv + 1;
	  lib.bytesRecv ← lib.bytesRecv + b.length*2;
	  PutOnGlobalInputQueue[b];
	  ENDLOOP;
	WHILE (b ← lib.sendBuffer) # NIL DO
	  IF CommFlags.doDebug AND b.seal # DriverTypes.bufferSeal THEN
	    Glitch[BufferSealBroken];
	  lcb ← b.iocbChain;
	  IF ~lcb.finished THEN EXIT;
	  -- there should be only one at a time on the send chain
	  lib.sendBuffer ← b.next;
	  SELECT lcb.microcodeError FROM
	    ok =>
	      BEGIN -- normal good case
	      IF CommFlags.doStats THEN StatIncr[statSlaPacketsSent];
	      IF CommFlags.doStats THEN StatBump[statSlaWordsSent, b.length];
	      IF b.encapsulation.slaHi THEN
		BEGIN
		lib.hiPacketsSent ← lib.hiPacketsSent + 1;
		lib.hiBytesSent ← lib.hiBytesSent + b.length*2;
		lib.hiQueueDelay ←
		  lib.hiQueueDelay +
		    (lib.timeSendStarted - b.encapsulation.slaTimeQueued);
		END;
	      lib.packetsSent ← lib.packetsSent + 1;
	      lib.bytesSent ← lib.bytesSent + b.length*2;
	      lib.queueDelay ←
		lib.queueDelay +
		  (lib.timeSendStarted - b.encapsulation.slaTimeQueued);
	      END;
	    hardware =>
	      BEGIN -- can only be FCT/TransOverrun
	      lib.overrun ← lib.overrun + 1;
	      IF CommFlags.doStats THEN StatIncr[statSlaFCT];
	      END;
	    ENDCASE => Glitch[UnexpectedCompletionCodeFromMicrocode];
	  IF b.encapsulation.slaBroadcast THEN
	    SendOverNextLine[b, b.encapsulation.slaSourceLine + 1]
	  ELSE DriverDefs.PutOnGlobalDoneQueue[b];
	  IF lib.sendBuffer = NIL THEN StartSending[line];
	  -- see if more to go out

	  ENDLOOP;
	ENDLOOP;
      ENDLOOP;
    END;

  AppendBuffer: INTERNAL PROCEDURE [b: Buffer, line: Line, flag: {send, recv}] =
    BEGIN
    lib: LIB = @lineInfo[line];
    lte: LTE = @lineTable[line];
    lcb: LongLCB ← b.iocbChain;
    first: LONG POINTER TO Buffer;
    IF CommFlags.doDebug AND b.seal # DriverTypes.bufferSeal THEN
      Glitch[BufferSealBroken];
    b.device ← sla; -- this gets all actual input+output
    IF Inline.HighHalf[b] # 0 THEN Glitch[HyperspaceNotSupported];
    lcb↑ ←
      [next: noLCB, interruptBit: slaInterruptBit, finished: FALSE,
	microcodeError: ok, hardwareStatus: 0, half: left, bytesLeft: 2*b.length,
	currentData: ShortenData[@b.encapsulation + slaEncapsulationOffset],
	partialCrc: 0, unused:];
    IF flag = recv THEN lcb.bytesLeft ← lcb.bytesLeft - 2*slaEncapsulationOffset;
    SELECT flag FROM
      send => BEGIN first ← @lib.sendBuffer; AppendLCB[@lte.outputLCB, lcb]; END;
      recv => BEGIN first ← @lib.recvBuffer; AppendLCB[@lte.inputLCB, lcb]; END;
      ENDCASE => Glitch[ImpossibleEndcase];
    b.next ← NIL;
    IF first↑ = NIL THEN first↑ ← b
    ELSE
      BEGIN
      UNTIL first↑.next = NIL DO first ← @first↑.next; ENDLOOP;
      first↑.next ← b;
      END;
    END;

  AppendLCB: PROCEDURE [head: POINTER TO LCB, lcb: LongLCB] =
    BEGIN
    short, next, ptr: LCB;
    short ← ShortenIocb[lcb];
    IF (next ← head↑) # noLCB THEN
      BEGIN
      DO ptr ← next; next ← ptr.next; IF next = noLCB THEN EXIT; ENDLOOP;
      ptr.next ← short;
      END;
    IF head↑ = noLCB AND ~lcb.finished THEN head↑ ← short;
    END;

  ProcessRoutingPacket: ENTRY PROCEDURE [b: Buffer] =
    BEGIN
    p: LONG POINTER TO RoutingTablePacket = LOOPHOLE[@b.rawWords];
    line: Line = b.encapsulation.slaSourceLine;
    lib: LIB = @lineInfo[line];
    lineInfo[line].timeOfLastRT ← AltoRam.GetTicks[];
    PurgeLineFromRT[line];
    IF p.sourceHost = myNetwork.hostNumber THEN
      BEGIN -- line is looped back
      IF lib.lineNeedsPassword THEN
	BEGIN
	SELECT lib.state FROM
	  passwordOk => lib.state ← loopedBack;
	  badPassword => NULL;
	  ENDCASE => lib.state ← needPassword;
	END
      ELSE lib.state ← loopedBack;
      routingTable[myNetwork.hostNumber] ← [hops: 1, line: line];
      END
    ELSE
      BEGIN
      SELECT lib.state FROM
	up =>
	  IF p.rt[myNetwork.hostNumber].hops # 1 THEN ReceivedRoutingTable[lib];
	halfUp, passwordOk =>
	  IF p.rt[myNetwork.hostNumber].hops = 1 THEN lib.state ← up;
	ENDCASE => ReceivedRoutingTable[lib];
      IF lib.state = up THEN
	FOR i: CARDINAL IN [0..MIN[p.numEntries, maxSlaHost]) DO
	  newHop: CARDINAL = p.rt[i].hops + 1;
	  rte: POINTER TO RoutingTableEntry = @routingTable[i];
	  IF newHop <= rte.hops THEN
	    BEGIN
	    rte.hops ← IF newHop <= maxHops THEN newHop ELSE longHop;
	    rte.line ← line;
	    END;
	  ENDLOOP;
      END;
    lib.partner ← p.sourceHost;
    IF CommFlags.doStats THEN StatIncr[statSlaRoutingPacketsReceived];
    END;

  ReceivedRoutingTable: PROCEDURE [lib: LIB] =
    BEGIN
    IF lib.lineNeedsPassword THEN
      BEGIN IF lib.state # badPassword THEN lib.state ← needPassword; END
    ELSE lib.state ← halfUp;
    END;

  PurgeLineFromRT: PROCEDURE [line: Line] =
    BEGIN
    FOR host: SlaHost IN SlaHost DO
      IF routingTable[host].line = line THEN routingTable[host].hops ← longHop;
      ENDLOOP;
    END;

  SendRoutingPacket: INTERNAL PROCEDURE [line: Line] =
    BEGIN
    lib: LIB = @lineInfo[line];
    partner: CARDINAL = lib.partner;
    b: Buffer;
    p: LONG POINTER TO RoutingTablePacket;
    IF line >= activeLines THEN Glitch[LineNumberTooBig];
    UNTIL (b ← DriverDefs.MaybeGetFreeBuffer[]) # NIL DO WAIT pause; ENDLOOP;
    p ← LOOPHOLE[@b.rawWords];
    p.sourceHost ← myNetwork.hostNumber;
    p.numEntries ← maxSlaHost;
    p.rt ← routingTable↑;
    p.rt[myNetwork.hostNumber].hops ← 0;
    IF (lib.state = halfUp OR lib.state = passwordOk) AND partner IN SlaHost THEN
      p.rt[partner].hops ← 1;
    b.length ← slaEncapsulationWords + SIZE[RoutingTablePacket];
    b.encapsulation ←
      [sla[
	slaSpare1:, slaSpare2:, slaSpare3:, slaHi:, slaBroadcast: FALSE,
	slaTimeQueued:, slaSourceLine: line, slaDestHost:, slaType: routing]];
    b.encapsulation.slaTimeQueued ← AltoRam.GetTicks[];
    Enqueue[@lib.hiPriQueue, b];
    IF lib.sendBuffer = NIL THEN StartSending[line];
    IF CommFlags.doStats THEN StatIncr[statSlaRoutingPacketsSent];
    END;

  DecapsulateBuffer: PROCEDURE [b: Buffer] RETURNS [BufferType] =
    BEGIN
    SELECT b.encapsulation.slaType FROM
      pup =>
	BEGIN
	IF 2*b.length < b.pupLength + slaEncapsulationBytes THEN
	  BEGIN
	  IF CommFlags.doStats THEN StatIncr[statPupsDiscarded];
	  RETURN[rejected];
	  END;
	RETURN[pup];
	END;
      ois =>
	BEGIN
	IF 2*b.length < b.ois.pktLength + slaEncapsulationBytes THEN
	  BEGIN
	  IF CommFlags.doStats THEN StatIncr[statOisDiscarded];
	  RETURN[rejected];
	  END;
	RETURN[ois];
	END;
      routing =>
	BEGIN
	ProcessRoutingPacket[b];
	ReturnFreeBuffer[b];
	RETURN[processed];
	END;
      password =>
	BEGIN
	ProcessPasswordPacket[b];
	ReturnFreeBuffer[b];
	RETURN[processed];
	END;
      ENDCASE => RETURN[rejected];
    END;

  EncapsulatePup: PROCEDURE [b: PupBuffer, destination: PupHostID] =
    BEGIN
    broadcast: BOOLEAN = destination = PupTypes.allHosts;
    IF CommFlags.doDebug AND b.seal # DriverTypes.bufferSeal THEN
      Glitch[BufferSealBroken];
    b.encapsulation ←
      [sla[
	slaSpare1:, slaSpare2:, slaSpare3:, slaHi:, slaBroadcast: broadcast,
	slaTimeQueued:, slaSourceLine:, slaDestHost: destination, slaType: pup]];
    b.length ← (b.pupLength + 1 + slaEncapsulationBytes)/2;
    END;

  EncapsulateOis: PROCEDURE [
    b: OisBuffer, destination: SpecialSystem.HostNumber] =
    BEGIN
    broadcast: BOOLEAN = destination = SpecialSystem.broadcastHostNumber;
    localDest: WORD;
    IF CommFlags.doDebug AND b.seal # DriverTypes.bufferSeal THEN
      Glitch[BufferSealBroken];
    WITH dest: destination SELECT FROM 
      multicast => localDest ← maxSlaHost;
      physical =>
        BEGIN  -- undo encoding from AltoSpecialSystem
        IF dest.a=0 AND dest.b=125026B
        AND Inline.HighByte[dest.c]=myNetwork.netNumber.b THEN
          localDest ← Inline.LowByte[dest.c]
        ELSE localDest ← maxSlaHost;
        END;
      ENDCASE => ERROR;
    b.encapsulation ←
      [sla[
	slaSpare1:, slaSpare2:, slaSpare3:, slaHi:, slaBroadcast: broadcast,
	slaTimeQueued:, slaSourceLine:, slaDestHost: localDest, slaType: ois]];
    b.length ← (b.ois.pktLength + 1 + slaEncapsulationBytes)/2;
    END;

  ForwardBuffer: ENTRY PROCEDURE [b: Buffer] RETURNS [PupTypes.PupErrorCode] =
    BEGIN
    host: SlaHost ← b.encapsulation.slaDestHost;
    line: Line ← routingTable[host].line;
    lib: LIB ← @lineInfo[line];
    n: CARDINAL;
    high: BOOLEAN;
    b.device ← sla;
    IF b.encapsulation.slaBroadcast THEN
      BEGIN SendOverNextLine[b, 0]; RETURN[noErrorPupErrorCode]; END;
    IF host >= maxSlaHost OR routingTable[host].hops = longHop OR
      (lib.state # up AND lib.state # loopedBack) THEN
      BEGIN -- no route
      IF CommFlags.doStats THEN StatIncr[statSlaNoRoute];
      RETURN[cantGetTherePupErrorCode];
      END;
    IF CommFlags.doDebug AND line >= activeLines THEN Glitch[LineNumberTooBig];
    n ← lib.hiPriQueue.length + lib.lowPriQueue.length;
    -- maybe we should throw away loPri packets if this is a hiPri one ?
    IF n > MIN[maxPacketsPerLine, BufferDefs.BuffersLeft[]] THEN
      BEGIN
      IF CommFlags.doStats THEN StatIncr[statSlaQueueFull];
      lib.rejections ← lib.rejections + 1;
      RETURN[gatewayResourceLimitsPupErrorCode];
      END;
    n ← CountFriends[b, @lib.lowPriQueue];
    high ← b.length < maxHiPriWords AND n = 0;
    n ← n + CountFriends[b, @lib.hiPriQueue];
    IF n > 5 THEN
      BEGIN
      IF CommFlags.doStats THEN StatIncr[statSlaConnectionLimit];
      lib.rejections ← lib.rejections + 1;
      lib.connRejections ← lib.connRejections + 1;
      IF high THEN lib.hiRejections ← lib.hiRejections + 1;
      RETURN[connectionLimitPupErrorCode];
      END;
    b.encapsulation.slaTimeQueued ← AltoRam.GetTicks[];
    Enqueue[IF high THEN @lib.hiPriQueue ELSE @lib.lowPriQueue, b];
    IF lib.sendBuffer = NIL THEN StartSending[line];
    RETURN[noErrorPupErrorCode];
    END;

  SendBuffer: ENTRY PROCEDURE [b: Buffer] =
    BEGIN
    host: SlaHost ← b.encapsulation.slaDestHost;
    line: Line ← routingTable[host].line;
    lib: LIB ← @lineInfo[line];
    n: CARDINAL;
    high: BOOLEAN;
    IF CommFlags.doDebug AND b.seal # DriverTypes.bufferSeal THEN
      Glitch[BufferSealBroken];
    b.device ← sla;
    IF b.encapsulation.slaBroadcast THEN
      BEGIN SendOverNextLine[b, 0]; RETURN; END;
    IF host >= maxSlaHost OR routingTable[host].hops = longHop OR
      (lib.state # up AND lib.state # loopedBack) THEN
      BEGIN -- no route
      IF CommFlags.doStats THEN StatIncr[statSlaNoRoute];
      DriverDefs.PutOnGlobalDoneQueue[b];
      RETURN;
      END;
    IF CommFlags.doDebug AND line >= activeLines THEN Glitch[LineNumberTooBig];
    n ← lib.hiPriQueue.length + lib.lowPriQueue.length;
    -- maybe we should throw away loPri packets if this is a hiPri one ?
    IF n > MIN[maxPacketsPerLine, BufferDefs.BuffersLeft[]] THEN
      BEGIN
      lib.rejections ← lib.rejections + 1;
      IF CommFlags.doStats THEN StatIncr[statSlaQueueFull];
      DriverDefs.PutOnGlobalDoneQueue[b];
      RETURN;
      END;
    n ← CountFriends[b, @lib.lowPriQueue];
    high ← b.length < maxHiPriWords AND n = 0;
    n ← n + CountFriends[b, @lib.hiPriQueue];
    IF n > 5 THEN
      BEGIN
      lib.rejections ← lib.rejections + 1;
      lib.connRejections ← lib.connRejections + 1;
      IF high THEN lib.hiRejections ← lib.hiRejections + 1;
      IF CommFlags.doStats THEN StatIncr[statSlaConnectionLimit];
      DriverDefs.PutOnGlobalDoneQueue[b];
      RETURN;
      END;
    b.encapsulation.slaTimeQueued ← AltoRam.GetTicks[];
    Enqueue[IF high THEN @lib.hiPriQueue ELSE @lib.lowPriQueue, b];
    IF lib.sendBuffer = NIL THEN StartSending[line];
    RETURN;
    END;

  SendOverNextLine: INTERNAL PROCEDURE [b: Buffer, firstLine: Line] =
    BEGIN
    line: Line;
    lib: LIB;
    limit: CARDINAL ← MIN[maxPacketsPerLine, BufferDefs.BuffersLeft[]];
    FOR line IN [firstLine..activeLines) DO
      lib ← @lineInfo[line];
      IF lib.state = up OR lib.state = loopedBack THEN
	BEGIN
	IF (lib.hiPriQueue.length + lib.lowPriQueue.length) > limit THEN LOOP;
	b.encapsulation.slaSourceLine ← line;
	b.encapsulation.slaTimeQueued ← AltoRam.GetTicks[];
	-- Don't bother checking order on broadcast packets
	IF b.length < maxHiPriWords THEN Enqueue[@lib.hiPriQueue, b]
	ELSE Enqueue[@lib.lowPriQueue, b];
	IF lib.sendBuffer = NIL THEN StartSending[line];
	RETURN;
	END;
      ENDLOOP;
    DriverDefs.PutOnGlobalDoneQueue[b]; -- no more lines to send to

    END;

  CountFriends: INTERNAL PROCEDURE [b: Buffer, q: Queue] RETURNS [n: CARDINAL] =
    BEGIN
    n ← 0;
    SELECT b.encapsulation.slaType FROM
      routing, password => RETURN;
      pup =>
	BEGIN
	pup, maybe: PupBuffer;
	pup ← LOOPHOLE[b];
	maybe ← LOOPHOLE[q.first];
	UNTIL maybe = NIL DO
	  IF maybe.encapsulation.slaType = pup
	    --  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.slaType = 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;

  StartSending: INTERNAL PROCEDURE [line: Line] =
    BEGIN
    lib: LIB ← @lineInfo[line];
    b: Buffer;
    SELECT TRUE FROM
      lib.hiPriQueue.length # 0 =>
	BEGIN b ← Dequeue[@lib.hiPriQueue]; b.encapsulation.slaHi ← TRUE; END;
      lib.lowPriQueue.length # 0 =>
	BEGIN b ← Dequeue[@lib.lowPriQueue]; b.encapsulation.slaHi ← FALSE; END;
      ENDCASE => RETURN; -- nothing to send
    AppendBuffer[b, line, send];
    lib.timeSendStarted ← AltoRam.GetTicks[];
    SELECT mode FROM -- Poke Transmitter

      eia =>
	BEGIN
	eiaControlAddr↑ ← 140000B + line*eiaShift;
	-- Yetch.  Bug in hardware looses interrupts.
	THROUGH [0..4) DO ENDLOOP; -- wait at least 38 microseconds
	eiaControlAddr↑ ← 140000B + line*eiaShift;
	END;
      commProc => AltoRam.ChangeControlReg[line*commProcShift, 100100B];
      ENDCASE => DriverDefs.Glitch[ImpossibleEndcase];
    END;

  -- This rest of this module is not used very often.  Someday it should get split out into a separate module so that we don't clutter up core with unused code.  I don't want to bother doing that right now.

  StopLine: INTERNAL PROCEDURE [line: Line] =
    BEGIN
    MaybeSendOverNextLine: INTERNAL PROCEDURE [b: Buffer] =
      BEGIN
      IF b.encapsulation.slaBroadcast THEN
	SendOverNextLine[b, b.encapsulation.slaSourceLine + 1]
      ELSE DriverDefs.PutOnGlobalDoneQueue[b];
      END;
    b: Buffer;
    lib: LIB = @lineInfo[line];
    lte: LTE = @lineTable[line];
    lte.inputState ← lte.outputState ← reset; -- turn off hardware
    lte.inputLCB ← lte.outputLCB ← noLCB; -- yank LCB's out from under it
    UNTIL (b ← Dequeue[@lib.lowPriQueue]) = NIL DO
      MaybeSendOverNextLine[b]; ENDLOOP;
    UNTIL (b ← Dequeue[@lib.hiPriQueue]) = NIL DO
      MaybeSendOverNextLine[b]; ENDLOOP;
    WHILE (b ← lib.recvBuffer) # NIL DO
      lib.recvBuffer ← b.next; ReturnFreeBuffer[b]; ENDLOOP;
    WHILE (b ← lib.sendBuffer) # NIL DO
      lib.sendBuffer ← b.next; MaybeSendOverNextLine[b]; ENDLOOP;
    END;

  StartLine: INTERNAL PROCEDURE [line: Line] =
    BEGIN
    lib: LIB = @lineInfo[line];
    lte: LTE = @lineTable[line];
    b: Buffer;
    lte.inputState ← rIdle;
    lte.outputState ← tIdle;
    lib.timeOfLastRT ← AltoRam.GetTicks[];
    THROUGH [0..2) DO
      UNTIL (b ← DriverDefs.GetInputBuffer[]) # NIL DO WAIT pause; ENDLOOP;
      AppendBuffer[b, line, recv];
      ENDLOOP;
    SELECT mode FROM
      eia =>
	BEGIN
	eiaControlAddr↑ ← 100410B + line*eiaShift; -- talk to dataset, 9600 baud
	eiaControlAddr↑ ← 130200B + line*eiaShift; -- Reset
	eiaControlAddr↑ ← 110700B + line*eiaShift; -- 8 data bits, no parity, sync
	eiaControlAddr↑ ← 120000B + line*eiaShift + syn; -- sync char ← 26B
	eiaControlAddr↑ ← 120400B + line*eiaShift + fill; -- fill character ← 377B
	eiaControlAddr↑ ← 130100B + line*eiaShift; -- Restart Receiver

	END;
      commProc =>
	BEGIN
	foo: CARDINAL; -- Compiler doesn't like line*commProcShift
	foo ← line*commProcShift;
	(commProcControlAddr + foo + 0)↑ ← 60B;
	(commProcControlAddr + foo + 1)↑ ← 302B;
	(commProcControlAddr + foo + 2)↑ ← syn;
	AltoRam.ChangeControlReg[foo, 100200B]; -- Enable Receiver

	END;
      ENDCASE => DriverDefs.Glitch[ImpossibleEndcase];
    END;

  -- Be sure the microcode is loaded by the time you get here...

  ActivateDriver: ENTRY PROCEDURE =
    BEGIN
    ImageDefs.AddCleanupProcedure[@cleanupItem];
    CommUtilDefs.AddInterruptHandler[slaInterruptLevel, @hardware, 0];
    hardProcess ← FORK Interrupt[];
    watcherProcess ← FORK Watcher[];
    SELECT mode FROM
      eia => AltoRam.EnableEia[lineTable];
      commProc => AltoRam.SetLineTab[lineTable];
      ENDCASE => DriverDefs.Glitch[ImpossibleEndcase];
    FOR line: Line IN [0..activeLines) DO StartLine[line]; ENDLOOP;
    END;

  DeactivateDriver: PROCEDURE = BEGIN Glitch[CantTurnSlaDriverOffYet]; END;

  CreateEIADriver: PUBLIC PROCEDURE [host, net, lines: CARDINAL]
    RETURNS [BOOLEAN] =
    BEGIN mode ← eia; CreateSlaDriver[host, net, lines]; RETURN[TRUE]; END;

  CreateCommProcDriver: PUBLIC PROCEDURE [host, net, lines: CARDINAL]
    RETURNS [BOOLEAN] =
    BEGIN mode ← commProc; CreateSlaDriver[host, net, lines]; RETURN[TRUE]; END;

  CreateSlaDriver: PROCEDURE [host, net, lines: CARDINAL] =
    BEGIN
    line: Line;
    h: SlaHost;
    size: CARDINAL =
      8 + maxByte + maxSlaHost*SIZE[RoutingTableEntry] +
	maxLine*(SIZE[LineInfoBlock] + SIZE[LineTableEntry]);
    p: POINTER ← Storage.Node[size + 1];
    IF (LOOPHOLE[p, CARDINAL] MOD 2) # 0 THEN p ← p + 1;
    -- LineTableEntrys must be on EVEN word boundries, so round up if necessary.
    IF CommFlags.doDebug THEN
      BEGIN DriverDefs.GetGiantVector[].slaThings ← p; END;
    -- layout is:  crcTable, junk, LTEs, routingTable, lineInfo
    myNetwork.hostNumber ← host;
    myNetwork.netNumber ← [0, net];
    myNetwork.buffers ← 2*lines;
    myNetwork.alive ← lines # 0;
    activeLines ← lines;
    crcTable ← p;
    p ← p + maxByte;
    IF CommFlags.doStats THEN myNetwork.stats ← p;
    p↑ ← activeLines; -- For Debugging
    (p + 4)↑ ← (p + 5)↑ ← (p + 6)↑ ← 0; -- CommProc Timer stuff
    lineTable ← p + 8;
    (p + 7)↑ ← crcTable;
    p ← 8 + p + maxLine*SIZE[LineTableEntry];
    routingTable ← p;
    p ← p + maxSlaHost*SIZE[RoutingTableEntry];
    lineInfo ← p;
    FOR h IN SlaHost DO routingTable[h] ← [hops: longHop, line: 0]; ENDLOOP;
    FOR line IN Line DO
      lineTable[line] ←
	[link: @lineInfo[line], hardwareStatus: 0, inputLCB: noLCB,
	  inputState: rIdle, outputLCB: noLCB, outputState: tIdle];
      lineInfo[line] ←
	[sendBuffer: NIL, recvBuffer: NIL, state: down, lineNeedsPassword: FALSE,
	  partner: noPartner, timeOfLastRT:, timeSendStarted:, hiPriQueue:,
	  lowPriQueue:, hiPacketsSent: 0, hiBytesSent: 0, hiQueueDelay: 0,
	  queueDelay: 0, connRejections: 0, hiRejections: 0, rejections: 0,
	  deaths: 0, stuck: 0, timeout: 0, overrun: 0, packetsSent: 0,
	  packetsRecv: 0, crcErrors: 0, syncErrors: 0, controlErrors: 0,
	  bytesSent: 0, bytesRecv: 0];
      QueueInitialize[@lineInfo[line].hiPriQueue];
      QueueInitialize[@lineInfo[line].lowPriQueue];
      ENDLOOP;
    InitCrcTable[];
    DriverDefs.AddDeviceToChain[@myNetwork, SIZE[LineControlBlock]];
    END;

  DeleteDriver: PROCEDURE = BEGIN END;

  InitCrcTable: PROCEDURE =
    BEGIN OPEN Inline;
    i, power: CARDINAL;
    crc, val: WORD;
    magic: WORD = 120001B;
    FOR i IN [0..maxByte) DO
      crc ← 0;
      val ← i;
      FOR power IN [0..7] DO
	IF BITAND[val, 1] = 0 THEN val ← BITSHIFT[val, -1]
	ELSE
	  BEGIN
	  crc ← BITXOR[crc, BITSHIFT[magic, -(7 - power)]];
	  val ← BITXOR[BITSHIFT[val, -1], magic];
	  END;
	ENDLOOP;
      crcTable[i] ← crc;
      ENDLOOP;
    END;

  GetSlaLineTable: PUBLIC PROCEDURE
    RETURNS [POINTER TO ARRAY Line OF LineTableEntry] =
    BEGIN RETURN[lineTable]; END;

  GetCrcTable: PUBLIC PROCEDURE RETURNS [POINTER TO ARRAY [0..maxByte) OF WORD] =
    BEGIN RETURN[crcTable]; END;

  GetSlaRoutingTable: PUBLIC PROCEDURE
    RETURNS [POINTER TO ARRAY SlaHost OF RoutingTableEntry] =
    BEGIN RETURN[routingTable]; END;

  GetSlaInfoBlocks: PUBLIC PROCEDURE
    RETURNS [DESCRIPTOR FOR ARRAY OF LineInfoBlock] =
    BEGIN RETURN[DESCRIPTOR[lineInfo, activeLines]]; END;


  -- BEWARE: Don't try using WORRY Mode Breakpoints.  There is no backstop mechanism to turn off the microcode when going to the dubegger, so it will start reading+writing into the Debuggers core image.  Things will probably screwup if you get PUNTed.

  Broom: PROCEDURE [why: BcplOps.CleanupReason] =
    BEGIN
    SELECT why FROM
      Finish, Abort, -- going back to Exec
	OutLd => -- going to Debugger
	SELECT mode FROM
	  eia => AltoRam.EnableEia[NIL0];
	  commProc => SmashOffCommProc[];
	  ENDCASE => DriverDefs.Glitch[ImpossibleEndcase];
      InLd => -- comming back from Debugger
	SELECT mode FROM
	  eia => AltoRam.EnableEia[lineTable];
	  commProc => NULL; -- let things timeout and get restarted for now

	  ENDCASE => DriverDefs.Glitch[ImpossibleEndcase];
      Save, Restore, Checkpoint, Restart, Continue =>
	Glitch[CantMakeImageWhileDriverIsActive];
      ENDCASE => Glitch[ImpossibleEndcase];
    END;


  saveInfo: ARRAY Line OF WORD;
  SmashOffCommProc: PROCEDURE =
    BEGIN
    FOR line: Line IN Line DO
      foo: CARDINAL = line*commProcShift;
      -- Compiler doesn't like line*commProcShift;
      saveInfo[line] ← (commProcControlAddr + foo)↑;
      AltoRam.ChangeControlReg[foo, 300B];
      ENDLOOP;
    END;

  -- Password things

  dialinPassword: POINTER TO Password.Encrypted ← NIL;
  dialoutPassword: STRING ← NIL;

  RememberDialinPassword: PUBLIC PROCEDURE [e: Password.Encrypted] =
    BEGIN
    IF dialinPassword = NIL THEN
      dialinPassword ← Storage.Node[SIZE[Password.Encrypted]];
    dialinPassword↑ ← e;
    END;

  RememberDialoutPassword: PUBLIC PROCEDURE [s: STRING] =
    BEGIN
    Storage.FreeString[dialoutPassword];
    dialoutPassword ← Storage.CopyString[s];
    MixupString[dialoutPassword];
    END;

  MixupString: PROCEDURE [s: STRING] =
    BEGIN
    mixup: ARRAY [0..6) OF WORD = [125B, 252B, 314B, 063B, 307B, 070B];
    FOR i: CARDINAL IN [0..s.length) DO
      s[i] ← Inline.BITXOR[s[i], mixup[i MOD 6]]; ENDLOOP;
    END;

  LineNeedsPassword: PUBLIC PROCEDURE [line: Line] =
    BEGIN
    lib: LIB ← @lineInfo[line];
    IF line >= activeLines THEN Glitch[LineNumberTooBig];
    lib.lineNeedsPassword ← TRUE;
    END;

  ProcessPasswordPacket: PROCEDURE [b: Buffer] =
    BEGIN
    password: STRING = [20];
    longPassword: LONG STRING = LOOPHOLE[@b.rawWords];
    line: Line = b.encapsulation.slaSourceLine;
    lib: LIB = @lineInfo[line];
    ok: BOOLEAN;
    IF longPassword.length > password.maxlength OR dialinPassword = NIL THEN
      RETURN;
    FOR i: CARDINAL IN [0..longPassword.length) DO
      password[i] ← longPassword[i]; ENDLOOP;
    password.length ← longPassword.length;
    MixupString[password];
    ok ← Password.Check[password, dialinPassword↑];
    MixupString[password];
    IF ok THEN
      BEGIN
      IF (lib.state = down OR lib.state = needPassword OR lib.state = badPassword)
	THEN lib.state ← passwordOk;
      END
    ELSE lib.state ← badPassword;
    END;

  SendPasswordPacket: INTERNAL PROCEDURE [line: Line] =
    BEGIN
    -- Copy WordsForString to avoid locking StringsA
    WordsForString: PROCEDURE [chars: CARDINAL] RETURNS [CARDINAL] =
      BEGIN RETURN[2 + ((chars + 1)/2)]; END;
    lib: LIB = @lineInfo[line];
    b: Buffer;
    length: CARDINAL ← WordsForString[dialoutPassword.length];
    IF line >= activeLines THEN Glitch[LineNumberTooBig];
    UNTIL (b ← DriverDefs.MaybeGetFreeBuffer[]) # NIL DO WAIT pause; ENDLOOP;
    CommUtilDefs.CopyLong[to: @b.rawWords, nwords: length, from: dialoutPassword];
    b.length ← slaEncapsulationWords + length;
    b.encapsulation ←
      [sla[
	slaSpare1:, slaSpare2:, slaSpare3:, slaHi:, slaBroadcast: FALSE,
	slaTimeQueued:, slaSourceLine: line, slaDestHost:, slaType: password]];
    b.encapsulation.slaTimeQueued ← AltoRam.GetTicks[];
    Enqueue[@lib.hiPriQueue, b];
    IF lib.sendBuffer = NIL THEN StartSending[line];
    END;


  -- initialization

  Process.DisableTimeout[@hardware];
  Process.SetTimeout[@pause, Process.MsecToTicks[100]];
  IF CommFlags.doStats THEN SetupSlaThings[];
  END.