-- File: EthernetOneDriver.mesa - last edit:
-- AOF                 17-Feb-88 18:57:53
-- HGM                  5-Oct-85 13:42:20
-- Copyright (C) 1983, 1984, 1985, 1988 by Xerox Corporation. All rights reserved. 

-- NB: Allocates 10 buffers for Dicentras

DIRECTORY
  Buffer USING [
    AccessHandle, Buffer, DataBytesPerRawBuffer, dataLinkReserve,
    DestroyPool, MakePool, Type],
  CommFlags USING [doDebug, doStats, driverStats],
  CommHeap USING [zone],
  CommunicationInternal USING [NSPackageDestroy, NSPackageMake],
  CommUtil USING [AllocateIocbs, FreeIocbs],
  Driver USING [
    Glitch, GetDeviceChain, GetInputBuffer, Device, DeviceObject,
    AddDeviceToChain, PutOnGlobalDoneQueue, PutOnGlobalInputQueue,
    ReturnFreeBuffer],
  EthernetOneDriverTypes USING [Byte, Encapsulation],
  Environment USING [Byte, bytesPerWord],
  EthernetOneFace,
  EthernetDriverFriends USING [EtherStatsInfo],
  Inline USING [LowHalf, HighHalf, BITAND, LongCOPY],
  PilotSwitches USING [noEthernetOne],
  Process USING [
    Abort, Detach, DisableTimeout, EnableAborts, GetPriority,
    Priority, SecondsToTicks, SetPriority, SetTimeout, Yield],
  ProcessPriorities USING [priorityIOHigh],
  SpecialRuntime USING [AllocateNakedCondition, DeallocateNakedCondition],
  Protocol1 USING [AddFamilyMember, Family, MatrixRecord],
  NS3MBit USING [SetFaceStatus],
  NSBuffer USING [Body],
  PupDefs USING [Body, GetPupPackageUseCount, PupPackageDestroy, PupPackageMake],
  PupRouterDefs USING [ContextObject],
  PupTypes USING [allHosts, PupHostID],
  RoutingTable USING [ContextObject],
  Runtime USING [GlobalFrame, SelfDestruct],
  Stats USING [StatBump, StatIncr, StatCounterIndex],
  SpecialCommunication USING [],
  SpecialSystem USING [HostNumber, GetProcessorID, NetworkNumber],
  System USING [
    broadcastHostNumber, GetClockPulses, HostNumber, MicrosecondsToPulses,
    nullHostNumber, nullNetworkNumber, Pulses, switches--['<]--];

EthernetOneDriver: MONITOR
  IMPORTS
    Buffer, CommHeap, CommunicationInternal, CommUtil, Driver, Stats, EthernetOneFace,
    Inline, NS3MBit, Process, Protocol1, PupDefs, Runtime,
    SpecialRuntime, SpecialSystem, System
  EXPORTS Buffer, NS3MBit, SpecialCommunication, System =
  BEGIN
  OPEN EthernetOneFace;

  --EXPORTed TYPEs
  Device: PUBLIC TYPE = Driver.Device;
  HostNumber: PUBLIC TYPE = SpecialSystem.HostNumber;
  NetworkNumber: PUBLIC TYPE = SpecialSystem.NetworkNumber;

  ether: DeviceHandle;
  myEar: CARDINAL;  -- address am I actually listening for
  getGarbage: BOOLEAN ← FALSE;  --when true, we deliver any packet
  setupEthernetOneDriver: PROC[etherDevice: DeviceHandle];
  ethernetOneListenForHost: PROC[newHostNumber: CARDINAL];
  
  ns: RoutingTable.ContextObject ← [
    netNumber: System.nullNetworkNumber, network: @myDevice, stats: NIL];
  pup: PupRouterDefs.ContextObject ← [protocol: NIL, network: @myDevice,
      pupNetNumber: 0, pupHostNumber: 377B];

  inputState: RECORD[
    process: PROCESS,
    mask: WORD,
    access: Buffer.AccessHandle,
    inWait: LONG POINTER TO CONDITION,
    firstBuffer, lastBuffer: Buffer.Buffer,
    queueAllowed, queueLength: CARDINAL,
    timeLastRecv: LONG CARDINAL,
    lastMissed: CARDINAL];
  
  outputState: RECORD[
    process: PROCESS,
    mask: WORD,
    outWait: LONG POINTER TO CONDITION,
    firstBuffer, lastBuffer: Buffer.Buffer,
    timeSendDone: LONG CARDINAL];

  watcherState: RECORD[
    pleaseStop: BOOLEAN,
    process: PROCESS, timer: CONDITION];

  --IOCBs
  iocbState: RECORD[wait: CONDITION, first, free: FreeIocb];
  FreeIocb: TYPE = LONG POINTER TO IocbObject;
  IocbObject: TYPE = RECORD[
    next: FreeIocb, rest: SEQUENCE COMPUTED CARDINAL OF WORD];

  seconds: RECORD[one, fiveHalf, forty: LONG CARDINAL];

  myDevice: Driver.DeviceObject ← [
    matrix: DESCRIPTOR[NIL, 0],
    sendRawBuffer: SendRawBuffer,
    activateDriver: ActivateDriver,
    deactivateDriver: DeactivateDriver, deleteDriver: DeleteDriver,
    buffers:, device: ethernetOne, receiveBufferLen: 1500,
    changeNumberOfInputBuffers: MaybeChangeNumberOfInputBuffers,
    alive: TRUE, index: , next: NIL, lineSpeed: 3000, lineNumber: 0, stats: NIL];

  LostAllIocbs: PUBLIC ERROR = CODE;
  IOCBSizeIsZero: PUBLIC ERROR = CODE;
  DriverNotActive: PUBLIC ERROR = CODE;
  DriverAlreadyActive: PUBLIC ERROR = CODE;
  IOCBMustBeInFirstMDS: PUBLIC ERROR = CODE;
  IOCBMustBeQuadWordAligned: PUBLIC ERROR = CODE;
  EthernetNetNumberScrambled: PUBLIC ERROR = CODE;
  BufferMustBeAlmostQuadWordAligned: PUBLIC ERROR = CODE;

  etherStats: EthernetDriverFriends.EtherStatsInfo;

  bpw: NATURAL = Environment.bytesPerWord;
  <<
  The physical overhead is the number of bytes used by the MAC layer to
  encapsulate a Network Protocol Data Unit. This physical overhead includes
  EthernetOne encapsulation.
  >>
  physOverhead: NATURAL = bpw *
    (SIZE[EthernetOneDriverTypes.Encapsulation] +
    SIZE[WORD--EthernetOneDriverTypes.EthernetOneCRC--]);
  encapBytes: CARDINAL = bpw * SIZE[EthernetOneDriverTypes.Encapsulation];

  <<
  This driver starts the reception of physical frame offset into the space
  reserved by the buffer manager for the LLC. This is so frames received
  using one LLC (eg. EthernetOne) can be encapsulated using another LLC (eg.
  IEEE 802.2) without wiping out the FixedOverhead portion of the buffer.
  errorCheck: NATURAL[22..1518] = Buffer.dataLinkReserve;  -- this must be true
  >>
  offsetToDataUnit: NATURAL = Buffer.dataLinkReserve / bpw -
    SIZE[EthernetOneDriverTypes.Encapsulation];  --words

  --Hot Procedures

  GetBufferAndIocb: INTERNAL PROC RETURNS[b: Buffer.Buffer] =
    BEGIN
      receiveLength: NATURAL = myDevice.receiveBufferLen + physOverhead;
      SELECT TRUE FROM
	(iocbState.free = NIL) =>
	  {IF CommFlags.doStats THEN Stats.StatIncr[statsIocbWait]; RETURN[NIL]};
	((b ← Driver.GetInputBuffer[FALSE, receiveLength]) = NIL) => RETURN;
	ENDCASE;
      b.fo.driver.iocb ← iocbState.free;
      iocbState.free ← iocbState.free.next;
      b.fo.driver.length ← receiveLength;
      b.fo.driver.faceStatus ← ethernet[pending];
  END;  --GetBufferAndIocb

  FreeBufferAndIocb: INTERNAL PROC[
    queueProc: PROC[b: Buffer.Buffer], b: Buffer.Buffer] =
    BEGIN
    iocb: FreeIocb ← b.fo.driver.iocb;
    IF iocb # NIL THEN
      BEGIN
      iocb.next ← iocbState.free;
      iocbState.free ← iocb;
      b.fo.driver.iocb ← NIL;
      END;
    queueProc[b];
    END;  --FreeBufferAndIocb

  InInterrupt: ENTRY PROC =
    BEGIN
    status: Status;
    acceptBuffer: BOOLEAN;
    this, new: Buffer.Buffer;

    DO
      ENABLE ABORTED => EXIT;
      UNTIL (this ← inputState.firstBuffer) # NIL DO
        WAIT inputState.inWait; ENDLOOP;
      status ← GetStatus[this.fo.driver.iocb];
      IF CommFlags.doStats AND status # pending THEN
        Stats.StatIncr[statEtherInterruptDuringInterrupt];
      UNTIL status # pending DO
	WAIT inputState.inWait;
	status ← GetStatus[this.fo.driver.iocb];
	IF CommFlags.doStats AND status = pending
	  THEN Stats.StatIncr[statEtherMissingStatus];
	ENDLOOP;
      inputState.firstBuffer ← inputState.firstBuffer.fo.next;
      IF status = ok THEN acceptBuffer ← TRUE
      ELSE
	BEGIN
	acceptBuffer ← getGarbage; --we may be collecting garbage packets
	IF CommFlags.driverStats
	  THEN etherStats.badRecvStatus ← etherStats.badRecvStatus + 1;
	IF CommFlags.doStats THEN Stats.StatIncr[statEtherReceivedBadStatus];
	IF CommFlags.doStats OR CommFlags.driverStats THEN
	  SELECT status FROM
	    packetTooLong =>
	      BEGIN
	      IF CommFlags.driverStats
		THEN etherStats.packetTooLong ← etherStats.packetTooLong + 1;
	      IF CommFlags.doStats THEN Stats.StatIncr[statEtherReceivedTooLong];
	      END;
	    badAlignmentButOkCrc =>
	      BEGIN
	      IF CommFlags.driverStats THEN etherStats.badAlignmentButOkCrc ←
		etherStats.badAlignmentButOkCrc + 1;
	      IF CommFlags.doStats THEN Stats.StatIncr[statEtherReceivedNot16];
	      END;
	    crc =>
	      BEGIN
	      IF CommFlags.driverStats THEN
		etherStats.badCrc ← etherStats.badCrc + 1;
	      IF CommFlags.doStats THEN Stats.StatIncr[statEtherReceivedBadCRC];
	      END;
	    crcAndBadAlignment =>
	      BEGIN
	      IF CommFlags.driverStats THEN
		etherStats.crcAndBadAlignment ← etherStats.crcAndBadAlignment+1;
	      IF CommFlags.doStats THEN
	        Stats.StatIncr[statEtherReceivedNot16BadCRC];
	      END;
	    overrun =>
	      BEGIN
	      IF CommFlags.driverStats THEN
		etherStats.overrun ← etherStats.overrun + 1;
	      IF CommFlags.doStats THEN Stats.StatIncr[statEtherReceivedOverrun];
	      END;
	    ENDCASE;
        END;
      IF ~acceptBuffer THEN {new ← this; new.fo.next ← NIL} --recycle this buffer
      ELSE
	BEGIN
	inputState.timeLastRecv ← this.fo.time ← System.GetClockPulses[];
	this.fo.driver.length ← GetPacketLength[this.fo.driver.iocb];
	NS3MBit.SetFaceStatus[this, status];
	this.fo.network ← LONG[@myDevice];
	IF CommFlags.driverStats THEN
	  BEGIN
	  etherStats.packetsRecv ← etherStats.packetsRecv + 1;
	  etherStats.wordsRecv ← etherStats.wordsRecv + this.fo.driver.length;
	  END;
	IF CommFlags.doStats THEN
	  BEGIN
	  Stats.StatIncr[statEtherPacketsReceived];
	  Stats.StatBump[statEtherWordsReceived, this.fo.driver.length];
	  END;
	FreeBufferAndIocb[Driver.PutOnGlobalInputQueue, this];

	SELECT TRUE FROM
	  (inputState.queueLength > inputState.queueAllowed) =>
	    BEGIN
	    new ← NIL;
	    inputState.queueLength ← inputState.queueLength - 1;
	    NOTIFY watcherState.timer;
	    IF CommFlags.doStats THEN Stats.StatIncr[statEtherEmptyFreeQueue];
	    END;
	  ((new ← GetBufferAndIocb[]) = NIL) =>
	    BEGIN --Rats, couldn't or didn't want a new buffer
	    inputState.queueLength ← inputState.queueLength - 1;
	    NOTIFY watcherState.timer;
	    IF CommFlags.doStats THEN Stats.StatIncr[statEtherEmptyFreeQueue];
	    END;
	  ENDCASE;

	END; --acceptBuffer clause
      --add new buffer to end of input chain
      IF new # NIL THEN
        BEGIN
	NS3MBit.SetFaceStatus[new, pending];
	QueueInput[
	  ether, (new.linkLayer.blockPointer + offsetToDataUnit),
	  (new.fo.driver.length / bpw) - offsetToDataUnit, new.fo.driver.iocb];
	IF inputState.firstBuffer = NIL THEN inputState.firstBuffer ← new
	ELSE inputState.lastBuffer.fo.next ← new;
	inputState.lastBuffer ← new;
        END;
      ENDLOOP;
    inputState.process ← NIL;
    END;

  OutInterrupt: ENTRY PROC =
    BEGIN
    b: Buffer.Buffer;
    status: Status;

    DO
      ENABLE ABORTED => EXIT;
      DO
        --we compute the values each time around since the value of b can
	--change if the watcher shoots down the output.
	SELECT TRUE FROM
	  ((b ← outputState.firstBuffer) = NIL) => NULL;
	  ((status ← GetStatus[b.fo.driver.iocb]) # pending) => EXIT;
	  ENDCASE;
	WAIT outputState.outWait;
        ENDLOOP;

      outputState.timeSendDone ← System.GetClockPulses[];  --still transmiting
      NS3MBit.SetFaceStatus[b, status];

      IF status = ok THEN
	BEGIN
	IF CommFlags.doStats OR CommFlags.driverStats THEN
	  BEGIN
	  tries: CARDINAL ← GetRetries[b.fo.driver.iocb];
	  statEtherSendsCollision1: Stats.StatCounterIndex =
	    statEtherSendsCollision1;
	  first: CARDINAL = LOOPHOLE[statEtherSendsCollision1];
	  IF CommFlags.driverStats THEN
	    BEGIN
	    etherStats.packetsSent ← etherStats.packetsSent + 1;
	    etherStats.wordsSent ← etherStats.wordsSent + b.fo.driver.length;
	    etherStats.loadTable[tries] ← etherStats.loadTable[tries] + 1;
	    END;
	  IF CommFlags.doStats AND tries # 0 THEN
	    Stats.StatIncr[LOOPHOLE[first + tries]];
	  END;
	END
      ELSE
	BEGIN
	IF CommFlags.driverStats THEN
	  etherStats.badSendStatus ← etherStats.badSendStatus + 1;
	IF CommFlags.doStats THEN Stats.StatIncr[statEtherSendBadStatus];
	IF CommFlags.doStats OR CommFlags.driverStats THEN
	  SELECT status FROM
	    tooManyCollisions =>
	      BEGIN
	      IF CommFlags.driverStats THEN
		etherStats.tooManyCollisions ← etherStats.tooManyCollisions + 1;
	      IF CommFlags.doStats THEN
		Stats.StatIncr[statEtherSendsCollisionLoadOverflow];
	      END;
	    underrun =>
	      BEGIN
	      IF CommFlags.driverStats THEN
		etherStats.underrun ← etherStats.underrun + 1;
	      IF CommFlags.doStats THEN Stats.StatIncr[statEtherSendOverrun];
	      END;
	    ENDCASE;
	END;
      --We don't resend things that screwup
      outputState.firstBuffer ← outputState.firstBuffer.fo.next;
      FreeBufferAndIocb[Driver.PutOnGlobalDoneQueue, b];
      ENDLOOP;
    outputState.process ← NIL;
    END;

  Watcher: PROC =
    BEGIN
    CheckForIdleInput: ENTRY PROC = INLINE
      BEGIN
      IF (System.GetClockPulses[] - inputState.timeLastRecv) <
        seconds.forty THEN RETURN;
      IF CommFlags.doStats THEN Stats.StatIncr[statInputIdle];
      IF CommFlags.driverStats THEN
        etherStats.idleInput ← etherStats.idleInput + 1;
      SmashCSBs[]; --this will leave output dangling
      END;  --CheckForIdleInput

    CheckForStuckOutput: ENTRY PROC = INLINE
      BEGIN
      SELECT TRUE FROM
        outputState.firstBuffer = NIL => NULL;
        ((System.GetClockPulses[] - outputState.timeSendDone) < seconds.fiveHalf) => RETURN;
	ENDCASE =>
	  BEGIN
        --This happens if the transciever is unplugged
        TurnOff[ether];
        UNTIL outputState.firstBuffer = NIL DO
          b ← outputState.firstBuffer;
          outputState.firstBuffer ← outputState.firstBuffer.fo.next;
          FreeBufferAndIocb[Driver.PutOnGlobalDoneQueue, b];
          IF CommFlags.doStats THEN Stats.StatIncr[statPacketsStuckInOutput];
          IF CommFlags.driverStats THEN
            etherStats.stuckOutput ← etherStats.stuckOutput + 1;
          ENDLOOP;
        SmashCSBs[];
	END;
    outputState.timeSendDone ← System.GetClockPulses[];
    END;  --CheckForStuckOutput

    QueueInputBufferLocked: ENTRY PROC = INLINE
      BEGIN ENABLE UNWIND => NULL;
      SELECT TRUE FROM
        (b = NIL) => WAIT watcherState.timer;  --this is an alternate WAIT
	((b.fo.driver.iocb ← iocbState.free) # NIL) =>
	  BEGIN
	  iocbState.free ← iocbState.free.next;
	  NS3MBit.SetFaceStatus[b, pending];
	  QueueInput[
	    ether, (b.linkLayer.blockPointer + offsetToDataUnit),
	    (b.fo.driver.length / bpw) - offsetToDataUnit, b.fo.driver.iocb];
	  IF inputState.firstBuffer = NIL THEN inputState.firstBuffer ← b
	  ELSE inputState.lastBuffer.fo.next ← b;
	  inputState.lastBuffer ← b;
	  inputState.queueLength ← inputState.queueLength + 1;
	  END;
	ENDCASE =>  --the ENDCASE better be rare!
          BEGIN
	  IF CommFlags.doStats THEN Stats.StatIncr[statsIocbWait];
	  Driver.ReturnFreeBuffer[b];  --give the buffer back (ARGH!!)
	  WAIT watcherState.timer;  --this is an alternate WAIT
	  END;
      END;  --QueueInputBufferLocked

    WaitForTimer: ENTRY PROC = INLINE
      BEGIN ENABLE UNWIND => NULL;
      WAIT watcherState.timer;
      END;

    b: Buffer.Buffer;
    enterLoop: LONG CARDINAL;
    inputState.lastMissed ← GetPacketsMissed[ether];
    DO
      ENABLE ABORTED => EXIT;
      enterLoop ← System.GetClockPulses[];
      --Check for lost interrupts
      IF CheckBuffer[@inputState.firstBuffer] OR
        CheckBuffer[@outputState.firstBuffer] THEN WatchCarefully[];
      CheckForIdleInput[];
      CheckForStuckOutput[];

      WHILE (inputState.queueLength < inputState.queueAllowed) DO
        --don't lock monitor and wait for a buffer
        b ← Driver.GetInputBuffer[
	  TRUE, myDevice.receiveBufferLen + physOverhead];
	QueueInputBufferLocked[];  --give it a chance to wait
	IF b = NIL THEN EXIT;  --but don't futz around forever
	ENDLOOP;

      IF CommFlags.doStats OR CommFlags.driverStats THEN
        BEGIN
	missed: CARDINAL ← GetPacketsMissed[ether];
	lost: CARDINAL ← missed - inputState.lastMissed;
	IF lost # 0 THEN
	  BEGIN
	  IF CommFlags.doStats
	    THEN Stats.StatBump[statEtherEmptyNoBuffer, lost];
	  IF CommFlags.driverStats
	    THEN etherStats.packetsMissed ← etherStats.packetsMissed + lost;
	  inputState.lastMissed ← missed;
	  END;
	END;

      IF (System.GetClockPulses[] - enterLoop) < seconds.one THEN 
        WaitForTimer[];
      ENDLOOP;

    END;  --Watcher

  CheckBuffer: ENTRY PROC[p: POINTER TO Buffer.Buffer]
    RETURNS [trouble: BOOLEAN] =
    BEGIN
    b: Buffer.Buffer ← p↑;
    IF b = NIL THEN RETURN[FALSE];
    RETURN[(GetStatus[b.fo.driver.iocb] # pending)];
    END;

  WatchCarefully: PROC =
    BEGIN
    <<
    In status # pending, an interrupt should have happened.  Since the interrupt
    routine is higher priority than we are, it should get processed before we
    can see it.  If we get here, an interrupt has probably been lost.  It could
    have been generated between the time we started decoding the instruction
    and the time that the data is actually fetched.  That is why we look several
    times.  Of course, if it is still not zero when we look again, it could be
    a new interrupt that has just arrived.
    Check for lost input interrupt.
    >>
    THROUGH [0..25) DO
      IF ~CheckBuffer[@inputState.firstBuffer] THEN EXIT;
      REPEAT FINISHED => WatcherNotify[];
      ENDLOOP;
     --Check for lost output interrupt
     THROUGH [0..25) DO
      IF ~CheckBuffer[@outputState.firstBuffer] THEN EXIT;
      REPEAT FINISHED => WatcherNotify[];
      ENDLOOP;
    END;
  
  WatcherNotify: ENTRY PROC =
    BEGIN
    IF CommFlags.doStats THEN Stats.StatIncr[statEtherLostInterrupts];
    SmashCSBs[]; --this will leave output dangling
    END;

  DecapsulateNS: PROC [b: Buffer.Buffer] RETURNS [type: Buffer.Type] =
    BEGIN
    dll: LONG POINTER TO EthernetOneDriverTypes.Encapsulation;
    bytes: CARDINAL ← b.fo.driver.length;
    IF bytes < encapBytes THEN GOTO Rejected;
    dll ← LOOPHOLE[b.linkLayer.blockPointer];
    bytes ← bytes - encapBytes;
    SELECT dll.ethernetOneType FROM
      ns =>
        BEGIN
	ndu: NSBuffer.Body = LOOPHOLE[
	  dll + SIZE[EthernetOneDriverTypes.Encapsulation]];
	IF bytes < ndu.pktLength THEN GOTO Rejected;
	type ← ns;
	b.highLayer ← [LOOPHOLE[dll], 0, bytes - encapBytes];
	b.linkLayer.stopIndexPlusOne ← encapBytes;
        END;
      translation =>
        BEGIN
	ReceiveTranslate[b];
        type ← orphan;
        END;
      ENDCASE => type ← vagrant;
    EXITS Rejected =>
      BEGIN
      type ← orphan;
      IF CommFlags.driverStats THEN Stats.StatIncr[statPacketsDiscarded];
      END;
    END;

  decapsulatePup: PROC [b: Buffer.Buffer]
    RETURNS [type: Buffer.Type] ← DecapsulatePup;

  DecapsulatePup: PROC [b: Buffer.Buffer] RETURNS [type: Buffer.Type] =
    BEGIN
    dll: LONG POINTER TO EthernetOneDriverTypes.Encapsulation;
    bytes: CARDINAL ← b.fo.driver.length;
    IF bytes < encapBytes THEN GOTO Rejected;
    bytes ← bytes - encapBytes;
    dll ← LOOPHOLE[b.linkLayer.blockPointer];
    SELECT dll.ethernetOneType FROM
      pup =>
        BEGIN
	ndu: PupDefs.Body = LOOPHOLE[
	  dll + SIZE[EthernetOneDriverTypes.Encapsulation]];
	IF bytes < ndu.pupLength THEN GOTO Rejected;
        type ← pup;
	b.highLayer ← [LOOPHOLE[dll], 0, bytes - encapBytes];
	b.linkLayer.stopIndexPlusOne ← encapBytes;
        END;
      ENDCASE => type ← vagrant;
    EXITS Rejected =>
      BEGIN
      type ← orphan;
      IF CommFlags.driverStats THEN Stats.StatIncr[statPacketsDiscarded];
      END;
    END;

  encapsulatePup: PROC [
    b: Buffer.Buffer, immediate: LONG POINTER TO PupTypes.PupHostID]
      ← EncapsulatePup;
  EncapsulatePup: PROC [
    b: Buffer.Buffer, immediate: LONG POINTER TO PupTypes.PupHostID] =
    BEGIN
    body: PupDefs.Body = LOOPHOLE[b.highLayer.blockPointer];
    dll: LONG POINTER TO EthernetOneDriverTypes.Encapsulation;
    dll ← LOOPHOLE[body - SIZE[EthernetOneDriverTypes.Encapsulation]];
    dll↑ ← [
      ethernetOne[ethernetOneDest: immediate↑,
      ethernetOneSource: pup.pupHostNumber, ethernetOneType: pup]];
    b.fo.driver.length ← Inline.BITAND[
      body.pupLength + encapBytes + bpw - 1, 177776B];
    END;

  EncapsulateNS: PROC [b: Buffer.Buffer, immediate: LONG POINTER TO HostNumber] =
    BEGIN
    foundIt: BOOLEAN;
    ethernetAddr: PupTypes.PupHostID;
    dll: LONG POINTER TO EthernetOneDriverTypes.Encapsulation;
    body: NSBuffer.Body = LOOPHOLE[b.highLayer.blockPointer];
    dll ← LOOPHOLE[body - SIZE[EthernetOneDriverTypes.Encapsulation]];
    [foundIt, ethernetAddr] ← Translate[immediate↑];
    IF foundIt THEN
      BEGIN
      dll↑ ← [
        ethernetOne[ethernetOneDest: ethernetAddr,
        ethernetOneSource: pup.pupHostNumber, ethernetOneType: ns]];
      b.fo.driver.length ← Inline.BITAND[
	body.pktLength + encapBytes + bpw - 1, 177776B];
      END
    ELSE
      BEGIN
      --If the translation doesn't work, the source will be set to a broadcast
      --address. That is always wrong, so it should be detectable.
      dll↑ ← [
        ethernetOne[ethernetOneDest: , ethernetOneType: ,
	ethernetOneSource: PupTypes.allHosts]];
      END;
    END;

  SendRawBuffer: ENTRY PROC[b: Buffer.Buffer] =
    BEGIN
    dll: LONG POINTER TO EthernetOneDriverTypes.Encapsulation = LOOPHOLE[
      b.linkLayer.blockPointer];
    IF watcherState.pleaseStop THEN Driver.Glitch[DriverNotActive];
    IF dll.ethernetOneSource = PupTypes.allHosts THEN
      BEGIN Driver.PutOnGlobalDoneQueue[b]; RETURN; END;
    IF ~hearSelf
      AND
        (dll.ethernetOneDest = pup.pupHostNumber
          OR dll.ethernetOneDest = PupTypes.allHosts) THEN
      BEGIN  --sending to ourself, copy it over since we can't hear it
      copy: Buffer.Buffer ← Driver.GetInputBuffer[
	  FALSE, Buffer.DataBytesPerRawBuffer[b]];
      IF copy # NIL THEN
        BEGIN
	words: NATURAL = (b.fo.driver.length + bpw - 1) / bpw;
        Inline.LongCOPY[
          from: @b.linkLayer.blockPointer,
          nwords: words, to: @copy.linkLayer.blockPointer];
	copy.linkLayer.startIndex ← 0;
	copy.linkLayer.stopIndexPlusOne ← b.fo.driver.length;  --copy length
        copy.fo.driver ← b.fo.driver;
	copy.fo.time ← System.GetClockPulses[];  --pretty close to this time
        copy.fo.network ← LONG[@myDevice];  --LONG because of Mokelumne compiler bug
        IF CommFlags.doStats THEN Stats.StatIncr[statEtherPacketsLocal];
        IF CommFlags.doStats THEN
	  Stats.StatBump[statEtherWordsLocal, b.fo.driver.length];
        Driver.PutOnGlobalInputQueue[copy];
        END
      ELSE IF CommFlags.doStats THEN Stats.StatIncr[statEtherEmptyFreeQueue];
      END;
    SendBufferInternal[b];
    END;

  SendBufferInternal: INTERNAL PROC[b: Buffer.Buffer] =
    BEGIN
    NS3MBit.SetFaceStatus[b, pending];
    b.fo.next ← NIL;
    UNTIL (b.fo.driver.iocb ← iocbState.free) # NIL DO
      IF CommFlags.doStats THEN Stats.StatIncr[statsIocbWait];
      WAIT iocbState.wait;
      ENDLOOP;
    iocbState.free ← iocbState.free.next;
    QueueOutput[
      ether, @b.linkLayer.blockPointer, b.fo.driver.length / bpw,
      b.fo.driver.iocb];
    IF CommFlags.doStats THEN Stats.StatIncr[statEtherPacketsSent];
    IF CommFlags.doStats THEN Stats.StatBump[statEtherWordsSent, b.fo.driver.length];
    IF outputState.firstBuffer = NIL THEN
      BEGIN
      outputState.firstBuffer ← b;
      outputState.timeSendDone ← System.GetClockPulses[];
      END
    ELSE outputState.lastBuffer.fo.next ← b;
    outputState.lastBuffer ← b;
    END;

  --for changing the number of buffers while running

  numberOfExtraBuffer: CARDINAL = 2;
  --this should only be called from Boss 
  --No MONITOR PROTECTION here.
  MaybeChangeNumberOfInputBuffers: PROC[increaseBuffers: BOOLEAN] =
    BEGIN
    IF increaseBuffers THEN
      BEGIN
      IF inputState.access = NIL THEN
        inputState.access ← Buffer.MakePool[0, numberOfExtraBuffer];
      IF inputState.queueAllowed < numberOfExtraBuffer THEN
	inputState.queueAllowed ← myDevice.buffers ←
	  myDevice.buffers + numberOfExtraBuffer;
      END
    ELSE
      BEGIN
      IF inputState.access # NIL THEN
        {Buffer.DestroyPool[inputState.access]; inputState.access ← NIL};
      IF inputState.queueAllowed >= numberOfExtraBuffer THEN
	inputState.queueAllowed ← myDevice.buffers ←
	  myDevice.buffers - numberOfExtraBuffer;
      END;
    END;

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

  CreateDefaultEthernetOneDrivers: PUBLIC PROC RETURNS [BOOLEAN] =
    BEGIN
    deviceNumber: CARDINAL ← 0;
    etherDevice: DeviceHandle ← GetNextDevice[nullDeviceHandle];
    IF System.switches[PilotSwitches.noEthernetOne] = down THEN RETURN[FALSE];
    IF etherDevice = nullDeviceHandle THEN RETURN[FALSE];
    WHILE etherDevice # nullDeviceHandle DO
      CreateAnEthernetOneDriver[etherDevice, deviceNumber];
      etherDevice ← GetNextDevice[etherDevice];
      deviceNumber ← deviceNumber + 1;
      ENDLOOP;
    RETURN[TRUE];
    END;

  CreateAnEthernetOneDriver: PROC [
    etherDevice: DeviceHandle, deviceNumber: CARDINAL] =
    BEGIN
    IF deviceNumber # 0 THEN
      BEGIN
      him: LONG POINTER TO FRAME[EthernetOneDriver] ← NEW EthernetOneDriver;
      START him;  --so he'll initialize the procedure
      him.setupEthernetOneDriver[etherDevice];
      END
    ELSE SetupEthernetOneDriver[etherDevice];
    END;

  firstTime: BOOL ← TRUE;
  firstPool: Buffer.AccessHandle;
  SetupEthernetOneDriver: PROC[etherDevice: DeviceHandle] =
    BEGIN
    ether ← etherDevice;
    [, pup.pupHostNumber] ← GetEthernet1Address[ether];
    watcherState.pleaseStop ← TRUE;
    myDevice.buffers ← inputState.queueLength ← 10;
    Driver.AddDeviceToChain[@myDevice];
    IF TRUE THEN
      BEGIN  -- Startup BUGs in Boss
      firstPool ← Buffer.MakePool[0, myDevice.buffers + numberOfExtraBuffer];
      END;
    IF CommFlags.doStats OR CommFlags.driverStats THEN
      BEGIN
      myDevice.stats ← @etherStats;
      etherStats ← [];
      END;
    IF PupDefs.GetPupPackageUseCount[] > 0 THEN AdoptForPup[];
    AdoptForNS[];
    END;

  AdoptForNS: PROC =
    BEGIN
    family: Protocol1.Family ← CommunicationInternal.NSPackageMake[];
    matrix: Protocol1.MatrixRecord ← [  --AddFamilyMember copies fields
      family: family, context: @ns,
      encapsulator: EncapsulateNS,
      decapsulator: DecapsulateNS];
    Protocol1.AddFamilyMember[LONG[@myDevice], @matrix];
    CommunicationInternal.NSPackageDestroy[]; -- Didn't really want to turn it on
    END;
    
  AdoptForPup: PROC =
    BEGIN
    family: Protocol1.Family ← PupDefs.PupPackageMake[];
    matrix: Protocol1.MatrixRecord ← [  --AddFamilyMember copies fields
      family: family, context: @pup,
      encapsulator: EncapsulatePup,
      decapsulator: DecapsulatePup];
    Protocol1.AddFamilyMember[LONG[@myDevice], @matrix];
    PupDefs.PupPackageDestroy[]; -- Beware: This undoes everything if the PupPackage wasn't on
    END;
    
  ActivateDriver: PROC =
    BEGIN
    b: Buffer.Buffer;
    iocbs: CARDINAL = (myDevice.buffers + numberOfExtraBuffer) * 2;
    size: CARDINAL = Inline.BITAND[(controlBlockSize + 3), 0FFFCH];
    IF ~watcherState.pleaseStop THEN Driver.Glitch[DriverAlreadyActive];
    seconds.forty ← System.MicrosecondsToPulses[40000000];
    seconds.fiveHalf ← System.MicrosecondsToPulses[2500000];
    seconds.one ← System.MicrosecondsToPulses[1000000];
    [, pup.pupHostNumber] ← GetEthernet1Address[ether];
    getGarbage ← watcherState.pleaseStop ← FALSE;
    TurnOff[ether];
    AddCleanup[ether];
    inputState.access ← NIL;
    inputState.queueLength ← 0;
    inputState.queueAllowed ← myDevice.buffers;
    inputState.firstBuffer ← inputState.lastBuffer ← NIL;
    outputState.firstBuffer ← outputState.lastBuffer ← NIL;
    myEar ← pup.pupHostNumber;
    IF controlBlockSize = 0 THEN Driver.Glitch[IOCBSizeIsZero];
    iocbState.free ← NIL;
    Process.EnableAborts[@iocbState.wait];
    Process.DisableTimeout[@iocbState.wait];
    iocbState.first ← iocbState.free ← CommUtil.AllocateIocbs[size * iocbs];
    FOR i: CARDINAL IN[0..iocbs - 1) DO
      iocbState.first ← iocbState.first.next ←
        iocbState.first + size;
      REPEAT FINISHED =>
        {iocbState.first.next ← NIL; iocbState.first ← iocbState.free};
      ENDLOOP; 
    THROUGH [0..myDevice.buffers) DO
      IF (b ← Driver.GetInputBuffer[
        TRUE, myDevice.receiveBufferLen + physOverhead]) # NIL THEN
        BEGIN
	IF iocbState.free = NIL THEN Driver.Glitch[LostAllIocbs];
	b.fo.driver.iocb ← iocbState.free;
	iocbState.free ← iocbState.free.next;
        inputState.queueLength ← inputState.queueLength + 1;
        <<IF CommFlags.doDebug
          AND Inline.BITAND[Inline.LowHalf[
	    @b.encapsulation + encapOffset], 3] # 0 THEN
          Driver.Glitch[BufferMustBeAlmostQuadWordAligned];>>
        IF CommFlags.doDebug
          AND Inline.BITAND[Inline.LowHalf[b.fo.driver.iocb], 3] # 0 THEN
          Driver.Glitch[IOCBMustBeQuadWordAligned];
        IF CommFlags.doDebug AND Inline.HighHalf[b.fo.driver.iocb] # 0 THEN
          Driver.Glitch[IOCBMustBeInFirstMDS];
        NS3MBit.SetFaceStatus[b, pending];
        IF inputState.firstBuffer = NIL THEN inputState.firstBuffer ← b;
        IF inputState.lastBuffer # NIL THEN inputState.lastBuffer.fo.next ← b;
        inputState.lastBuffer ← b;
        END;
      ENDLOOP;
    [cv: inputState.inWait, mask: inputState.mask] ←
      SpecialRuntime.AllocateNakedCondition[];
    Process.DisableTimeout[inputState.inWait];
    Process.EnableAborts[inputState.inWait];
    [cv: outputState.outWait, mask: outputState.mask] ←
      SpecialRuntime.AllocateNakedCondition[];
    Process.DisableTimeout[outputState.outWait];
    Process.EnableAborts[outputState.outWait];
    SmashCSBs[];
    BEGIN
    priority: Process.Priority ← Process.GetPriority[];
    Process.SetPriority[ProcessPriorities.priorityIOHigh];
    inputState.process ← FORK InInterrupt[];
    outputState.process ← FORK OutInterrupt[];
    Process.SetPriority[priority];
    END;
    Process.EnableAborts[@watcherState.timer];
    Process.SetTimeout[@watcherState.timer, Process.SecondsToTicks[1]];
    watcherState.process ← FORK Watcher[];
    CreateCache[];
    myDevice.alive ← TRUE;  --hope I ain't lying.
    END;

  SetEthernetOneListener: PUBLIC ENTRY PROC[
    physicalOrder: CARDINAL, newHostNumber: CARDINAL]
    RETURNS [success: BOOLEAN] =
    BEGIN
    him: LONG POINTER TO FRAME[EthernetOneDriver];
    network: Device ← GetNthDeviceLikeMe[physicalOrder];
    IF network = NIL THEN RETURN[FALSE];
    him ← LOOPHOLE[Runtime.GlobalFrame[LOOPHOLE[network.sendRawBuffer]]];
    him.ethernetOneListenForHost[newHostNumber];
    RETURN[TRUE];
    END;

  EthernetOneListenForHost: PROC[newHostNumber: CARDINAL] =
    BEGIN myEar ← newHostNumber; SmashCSBs[]; END;

  GetNthDeviceLikeMe: PROC[physicalOrder: CARDINAL]
    RETURNS [net: Device] =
    BEGIN
    i: CARDINAL ← 0;
    net ← Driver.GetDeviceChain[];
    WHILE net # NIL DO
      IF net.device = myDevice.device THEN
        IF (i ← i + 1) = physicalOrder THEN RETURN;
      net ← net.next;
      ENDLOOP;
    END;

  SetEthernetOneCollectGarbageToo: PUBLIC ENTRY PROC[
    physicalOrder: CARDINAL, collectGarbage: BOOLEAN]
    RETURNS [success: BOOLEAN] =
    BEGIN
    him: LONG POINTER TO FRAME[EthernetOneDriver];
    network: Device ← GetNthDeviceLikeMe[physicalOrder];
    IF network = NIL THEN RETURN[FALSE];
    him ← LOOPHOLE[Runtime.GlobalFrame[LOOPHOLE[network.sendRawBuffer]]];
    him.getGarbage ← collectGarbage;
    RETURN[TRUE];
    END;

  SmashCSBs: PROC =
    BEGIN
    TurnOn[ether, myEar, inputState.mask, outputState.mask, LOOPHOLE[0]];
    inputState.lastMissed ← GetPacketsMissed[ether];
    FOR b: Buffer.Buffer ← inputState.firstBuffer, b.fo.next UNTIL b = NIL DO
      QueueInput[
        ether, LOOPHOLE[b.linkLayer.blockPointer + offsetToDataUnit],
	(b.fo.driver.length / bpw) - offsetToDataUnit, b.fo.driver.iocb];
      ENDLOOP;
    inputState.timeLastRecv ← System.GetClockPulses[];
    END;

  DeleteDriver: PROC =
    BEGIN
    IF ether # GetNextDevice[nullDeviceHandle] THEN Runtime.SelfDestruct[];
    END;

  DeactivateDriver: PROC =
    BEGIN
    process: PROCESS;
    b: Buffer.Buffer;
    IF watcherState.pleaseStop THEN Driver.Glitch[DriverNotActive];
    myDevice.alive ← FALSE;  --stop all outgoing traffic
    watcherState.pleaseStop ← TRUE;
    process ← inputState.process;
    UNTIL inputState.process = NIL DO
      Process.EnableAborts[inputState.inWait];
      Process.Abort[process];
      REPEAT FINISHED => JOIN process;
      ENDLOOP;
    process ← outputState.process;
    UNTIL outputState.process = NIL DO
      Process.EnableAborts[outputState.outWait];
      Process.Abort[process];
      REPEAT FINISHED => JOIN process;
      ENDLOOP;
    Process.Abort[watcherState.process]; JOIN watcherState.process;
    TurnOff[ether];
    SpecialRuntime.DeallocateNakedCondition[inputState.inWait];
    SpecialRuntime.DeallocateNakedCondition[outputState.outWait];
    inputState.inWait ← outputState.outWait ← NIL;
    MaybeChangeNumberOfInputBuffers[FALSE];
    IF firstTime THEN
      BEGIN
      firstTime← FALSE;
      Buffer.DestroyPool[firstPool];
      END;
    RemoveCleanup[ether];
    UNTIL inputState.firstBuffer = NIL DO
      b ← inputState.firstBuffer;
      inputState.firstBuffer ← b.fo.next;
      Driver.ReturnFreeBuffer[b];
      ENDLOOP;
    UNTIL outputState.firstBuffer = NIL DO
      b ← outputState.firstBuffer;
      outputState.firstBuffer ← b.fo.next;
      Driver.PutOnGlobalDoneQueue[b];
      ENDLOOP;
    CommUtil.FreeIocbs[iocbState.first];
    iocbState.free ← iocbState.first ← NIL; 
    DeleteCache[];
    END;


  AddressPair: TYPE = MACHINE DEPENDENT RECORD [
    nsAddr: HostNumber, ethernet1Addr: PupTypes.PupHostID, filler: [0..377B]];

  TranslationDataUnit: TYPE = LONG POINTER TO TranslationObject;

  TranslationObject: TYPE = MACHINE DEPENDENT RECORD [
    function: TranslationFunction, his, mine: AddressPair];

  TranslationFunction: TYPE = MACHINE DEPENDENT {
    (0), translationResponse(7070B), translationRequest(10101B), (WORD.LAST)};

  CacheEntry: TYPE = LONG POINTER TO CacheObject;
  CacheObject: TYPE = MACHINE DEPENDENT RECORD [
    nextLink: CacheEntry,
    addressPair: AddressPair,
    tries: CARDINAL,
    timeStamp: System.Pulses,
    status: CacheStatus,
    filler: [0..37777B]];

  CacheStatus: TYPE = {new, pending, active, zombie};

  --variables
  translationRequest: CARDINAL = 10101B;
  translationResponse: CARDINAL = 7070B;
  cacheQueueHead: CacheEntry;
  broadCastPairEntry: CacheEntry;  --permanent
  myAddressPairEntry: CacheEntry;  --permanent
  retryLimit: CARDINAL ← 10B;
  depth: CARDINAL;  --debugging
  retryPulses: System.Pulses ← System.MicrosecondsToPulses[2000000];  --two seconds
  deactivatePulses: System.Pulses ← System.MicrosecondsToPulses[180000000];  --three minutes
  cacheEvent: CONDITION;
  demonRunning: BOOLEAN;
  etherHost: PupTypes.PupHostID;
  nsHost: HostNumber ← SpecialSystem.GetProcessorID[];

  CreateCache: PROC =
    BEGIN
    cacheQueueHead ← NIL;
    etherHost ← [GetEthernet1Address[ether].host];
    Process.SetTimeout[@cacheEvent, Process.SecondsToTicks[1]];
    broadCastPairEntry ← AddAddressPair[[System.broadcastHostNumber, PupTypes.allHosts, 0]];
    myAddressPairEntry ← AddAddressPair[[nsHost, etherHost, 0]];
    demonRunning ← TRUE;
    Process.Detach[FORK Demon[]];
    END;

  DeleteCache: PROC =
    BEGIN
    DeleteCacheLocked: ENTRY PROC = BEGIN NOTIFY cacheEvent; END;
    e: CacheEntry;
    WHILE demonRunning DO DeleteCacheLocked[]; Process.Yield[]; ENDLOOP;
    --cleanup in case demon was never running
    WHILE (cacheQueueHead # NIL) DO
      e ← cacheQueueHead;
      cacheQueueHead ← e.nextLink;
      CommHeap.zone.FREE[@e];
      ENDLOOP;
    END;

  FindEntry: INTERNAL PROC[nsAddr: HostNumber] RETURNS [entry: CacheEntry] =
    BEGIN
    IF CommFlags.doStats THEN depth ← 0;
    entry ← cacheQueueHead;
    WHILE entry # NIL DO
      IF nsAddr = entry.addressPair.nsAddr THEN RETURN;
      entry ← entry.nextLink;
      IF CommFlags.doStats THEN depth ← depth + 1;
      ENDLOOP;
    END;

  AddEntry: INTERNAL PROC [entry: CacheEntry] =
    {entry.nextLink ← cacheQueueHead; cacheQueueHead ← entry};

  RemoveEntry: INTERNAL PROC [entry: CacheEntry] =
    BEGIN
    e, pred: CacheEntry;
    IF (pred ← cacheQueueHead) = entry THEN
      BEGIN cacheQueueHead ← cacheQueueHead.nextLink; RETURN; END;
    e ← pred.nextLink;
    WHILE e # NIL DO
      IF e = entry THEN BEGIN pred.nextLink ← entry.nextLink; RETURN; END;
      pred ← e;
      e ← pred.nextLink;
      ENDLOOP;
    ERROR;
    END;

  Translate: ENTRY PROC [nsAddr: HostNumber]
    RETURNS [foundIt: BOOLEAN, ethernet1Addr: PupTypes.PupHostID] =
    BEGIN
    e: CacheEntry;
    foundIt ← FALSE;
    IF (e ← FindEntry[nsAddr]) # NIL THEN
      BEGIN
      IF e # cacheQueueHead THEN  --put e at the head of the queue
        BEGIN
        IF CommFlags.doStats THEN Stats.StatBump[cacheDepth, depth];
        RemoveEntry[e];
        AddEntry[e];
        END;
      SELECT e.status FROM
        active =>
          BEGIN
          foundIt ← TRUE;
          ethernet1Addr ← e.addressPair.ethernet1Addr;
          e.timeStamp ← System.GetClockPulses[];
          END;
        zombie =>
          BEGIN
          e.status ← new;
          e.tries ← 0;
          e.timeStamp ← System.GetClockPulses[];
          NOTIFY cacheEvent;
          END;
        ENDCASE => NULL;
      END  --of found it
    ELSE  --entry not found, so add a new one
      BEGIN
      IF CommFlags.doStats THEN Stats.StatIncr[cacheFault];
      e ← CommHeap.zone.NEW[CacheObject];
      e.status ← new;
      e.tries ← 0;
      e.timeStamp ← System.GetClockPulses[];
      e.addressPair ← [nsAddr: nsAddr, ethernet1Addr: [0], filler: 0];
      AddEntry[e];
      NOTIFY cacheEvent;
      END;
    END;

  AddAddressPair: ENTRY PROC[aP: AddressPair] RETURNS [e: CacheEntry] =
    BEGIN
    e ← FindEntry[aP.nsAddr];
    SELECT e FROM
      NIL => {
        e ← CommHeap.zone.NEW[CacheObject];
	AddEntry[e]; };
      broadCastPairEntry, myAddressPairEntry => RETURN;
      ENDCASE;
    e.addressPair ← aP;
    e.status ← active;
    e.timeStamp ← System.GetClockPulses[];
    END;

  DeallocateEntry: INTERNAL PROC[e: CacheEntry] =
    BEGIN
    IF (e = broadCastPairEntry) OR (e = myAddressPairEntry) THEN
      e.timeStamp ← System.GetClockPulses[]
    ELSE { RemoveEntry[e]; CommHeap.zone.FREE[@e]; };
    END;

  Demon: ENTRY PROC =
    BEGIN
    demonRunning ← TRUE;
    Process.SetPriority[ProcessPriorities.priorityIOHigh];

    UNTIL watcherState.pleaseStop DO
      ENABLE ABORTED => EXIT;
      pendingEntries: BOOLEAN ← FALSE;
      e: CacheEntry;
      WAIT cacheEvent;
      e ← cacheQueueHead;
      WHILE (e # NIL) DO
        age: System.Pulses ← [System.GetClockPulses[] - e.timeStamp];
        nextE: CacheEntry ← e.nextLink;
        SELECT e.status FROM
          active, zombie => { IF age > deactivatePulses THEN DeallocateEntry[e]};
          pending =>
            BEGIN
            pendingEntries ← TRUE;
            IF age > retryPulses THEN
              BEGIN
              e.tries ← e.tries + 1;
              IF e.tries > retryLimit THEN
                BEGIN
                e.status ← zombie;
                IF CommFlags.doStats THEN Stats.StatIncr[unsuccessfulTranslation];
                END
              ELSE
                BEGIN
                IF CommFlags.doStats THEN Stats.StatIncr[translationRetries];
                SendRequest[e];
                e.timeStamp ← System.GetClockPulses[];
                END;
              END;
            END;
          new =>
            BEGIN
            pendingEntries ← TRUE;
            SendRequest[e];
            e.status ← pending;
            e.timeStamp ← System.GetClockPulses[];
            END;
          ENDCASE => ERROR;
        e ← nextE;
        ENDLOOP;  --end of queue entries loop
      IF pendingEntries THEN
        Process.SetTimeout[@cacheEvent, Process.SecondsToTicks[1]]
      ELSE Process.SetTimeout[@cacheEvent, Process.SecondsToTicks[60*5]];
      ENDLOOP;  --end of infinite loop
    BEGIN
    e, nextE: CacheEntry;
    e ← cacheQueueHead;
    cacheQueueHead ← myAddressPairEntry ← broadCastPairEntry ← NIL;
    WHILE e # NIL DO
      nextE ← e.nextLink; CommHeap.zone.FREE[@e]; e ← nextE; ENDLOOP;
      END;
    demonRunning ← FALSE;
    END;

  bytesPerTranslationResponse: NATURAL = physOverhead + bpw*SIZE[AddressPair];
  bytesPerTranslationRequest: NATURAL = physOverhead + 2*bpw*SIZE[AddressPair];
  SendRequest: INTERNAL PROC[e: CacheEntry] =
    BEGIN
    b: Buffer.Buffer;
    IF (b ← Driver.GetInputBuffer[FALSE, bytesPerTranslationRequest]) # NIL THEN
      BEGIN
      dll: LONG POINTER TO EthernetOneDriverTypes.Encapsulation = LOOPHOLE[
        b.linkLayer.blockPointer];
      tdu: TranslationDataUnit = LOOPHOLE[
        dll + SIZE[EthernetOneDriverTypes.Encapsulation]];
      dll↑ ← [
        ethernetOne[ethernetOneDest: PupTypes.allHosts,
        ethernetOneSource: pup.pupHostNumber, ethernetOneType: translation]];
      tdu↑ ← [function: translationRequest,
	his: e.addressPair, mine: myAddressPairEntry.addressPair];
      tdu.his.filler ← e.tries;
      b.fo.driver.length ← bytesPerTranslationRequest;  --longer than response
      SendBufferInternal[b];  --okay, it should be ready
      END;
    END;

  ReceiveTranslate: PROC [b: Buffer.Buffer] =
    BEGIN
    dll: LONG POINTER TO EthernetOneDriverTypes.Encapsulation = LOOPHOLE[
      b.linkLayer.blockPointer];
    tdu: TranslationDataUnit = LOOPHOLE[
      dll + SIZE[EthernetOneDriverTypes.Encapsulation]];
    SELECT tdu.function FROM
      translationRequest =>
        BEGIN
	IF b.fo.driver.length < bytesPerTranslationRequest THEN RETURN;
        IF tdu.his.nsAddr = nsHost THEN
	  BEGIN
 	  a: Buffer.Buffer;
	  atdu: TranslationDataUnit;
	  adll: LONG POINTER TO EthernetOneDriverTypes.Encapsulation;
          -- since the requester is probably going to talk to us,
          -- add his address before we take a fault
          [] ← AddAddressPair[tdu.mine];
	  tdu.mine.ethernet1Addr ← [pup.pupHostNumber];
          IF CommFlags.doStats THEN Stats.StatIncr[requestsForMe];
	  a ← Driver.GetInputBuffer[FALSE, bytesPerTranslationRequest];
	  IF a = NIL THEN RETURN;
	  adll ← LOOPHOLE[a.linkLayer.blockPointer];
	  atdu ← LOOPHOLE[dll + SIZE[EthernetOneDriverTypes.Encapsulation]];
          adll↑ ← [
            ethernetOne[
            ethernetOneDest: dll.ethernetOneSource,
            ethernetOneSource: pup.pupHostNumber,
	    ethernetOneType: translation]];
	  atdu↑ ← [function: translationResponse, his: tdu.his, mine: ];
          a.fo.driver.length ← bytesPerTranslationResponse;  --shorter than req.
	  SendRawBuffer[a];
	  END;
	END;
      translationResponse =>
        BEGIN
	IF b.fo.driver.length < bytesPerTranslationResponse THEN RETURN;
	IF dll.ethernetOneDest # etherHost THEN RETURN;
        [] ← AddAddressPair[tdu.his];
	END;
      ENDCASE;
    END;

  getInfo: PROC [pup: PupTypes.PupHostID, network: Device]
    RETURNS [ns: System.HostNumber] ← GetInfo;
  GetInfo: PUBLIC ENTRY PROC [pup: PupTypes.PupHostID, network: Device]
    RETURNS [ns: System.HostNumber] =
    BEGIN
    ns ← System.nullHostNumber;
    IF network = @myDevice THEN
      BEGIN
      FOR entry: CacheEntry ← cacheQueueHead, entry.nextLink UNTIL entry = NIL DO
	IF entry.status # active THEN LOOP;
        IF pup # entry.addressPair.ethernet1Addr THEN LOOP;
	ns ← entry.addressPair.nsAddr;
	RETURN;
        ENDLOOP;
      RETURN;
      END;
    FOR finger: Device ← myDevice.next, finger.next UNTIL finger = NIL DO
      him: LONG POINTER TO FRAME[EthernetOneDriver];
      IF finger.device # ethernetOne THEN LOOP;
      him ← LOOPHOLE[Runtime.GlobalFrame[LOOPHOLE[finger.sendRawBuffer]]];
      ns ← him.getInfo[pup, network];
      EXIT;
      ENDLOOP;
    END;

  --* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  --End of Ethernet1 uglyness 
  --* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 


  --initialization

  setupEthernetOneDriver ← SetupEthernetOneDriver;  --for multi instances
  ethernetOneListenForHost ← EthernetOneListenForHost;

  END.  --EthernetOneDriver