-- File: AltoEthernetDriver.mesa,  Last Edit:
  -- MAS  August 20, 1980  12:59 PM
  -- HGM  August 23, 1980  7:14 PM

-- Copyright  Xerox Corporation 1979, 1980

DIRECTORY
  InlineDefs: FROM "InlineDefs" USING [BITSHIFT, COPY],
  StatsDefs: FROM "StatsDefs" USING [StatBump, StatIncr, StatCounterIndex],
  CommUtilDefs: FROM "CommUtilDefs" USING [
    GetTicks, msPerTick,
    MyGlobalFrame, Copy, Zero,
    DisableTimeout, SetTimeout, MsecToTicks,
    InterruptLevel, AssignInterruptLevel,
    SetPriority, AddInterruptHandler, RemoveInterruptHandler,
    GetEthernetHostNumber, AllocateLockedNode,
    CleanupItem, CleanupReason, AllReasons,
    AddCleanupProcedure, RemoveCleanupProcedure],
  AltoEthernetDefs: FROM "AltoEthernetDefs",
  DriverDefs: FROM "DriverDefs" USING [
    giantVector, GiantVector, doCheck, doDebug, doStats, Glitch,
    GetInputBuffer, NetworkObject, AddDeviceToChain,
    PutOnGlobalDoneQueue, PutOnGlobalInputQueue],
  BufferDefs:  FROM "BufferDefs",
  PupTypes:  FROM "PupTypes" USING [
    PupErrorCode, noErrorPupErrorCode, gatewayResourceLimitsPupErrorCode],
  DriverTypes:  FROM "DriverTypes" USING [
    ethernetEncapsulationOffset, ethernetEncapsulationBytes,
    pupEthernetPacket, ethernetBroadcastHost];

AltoEthernetDriver: MONITOR
  IMPORTS InlineDefs, CommUtilDefs, StatsDefs, DriverDefs, BufferDefs, AltoEthernetDefs
  EXPORTS DriverDefs
  SHARES BufferDefs, DriverTypes =
BEGIN OPEN StatsDefs, BufferDefs, DriverDefs, AltoEthernetDefs;

ethernetEncapsulationOffset: CARDINAL = DriverTypes.ethernetEncapsulationOffset;
ethernetEncapsulationBytes: CARDINAL = DriverTypes.ethernetEncapsulationBytes;

myDevice: EthernetDeviceBlockHandle ← NIL;
cleanupItem: CommUtilDefs.CleanupItem ← [,CommUtilDefs.AllReasons,Broom];
hardProcess: PROCESS;
hardware: CONDITION;
watcherProcess: PROCESS;
pleaseStop: BOOLEAN;
timer: CONDITION;
nextBufferPointer: POINTER;
currentInputBuffer, nextInputBuffer: Buffer;
outputQueue: QueueObject;
currentOutputBuffer: Buffer;
timeSendStarted: CARDINAL;
timeLastRecv: CARDINAL;

myNetwork: DriverDefs.NetworkObject ← [
  decapsulateBuffer: DecapsulateBuffer,
  encapsulatePup: EncapsulatePup,
  encapsulateRpp: EncapsulateRpp,
  sendBuffer: SendBuffer,
  forwardBuffer: ForwardBuffer,
  activateDriver: ActivateDriver,
  deactivateDriver: DeactivateDriver,
  deleteDriver: DeleteDriver,
  interrupt: Interrupt,
  device: ethernet,
  alive: TRUE,
  speed: 3000,
  index: ,
  netNumber: ,
  hostNumber: ,
  next: NIL,
  pupStats: NIL,
  stats: NIL ];

ImpossibleEndcase: PUBLIC ERROR = CODE;
FunnyRetransmissionMask: PUBLIC ERROR = CODE;
ZeroLengthBuffer: PUBLIC ERROR = CODE;
UnreasonableHardwareStatus: PUBLIC ERROR = CODE;
QueueScrambled: PUBLIC ERROR = CODE;
MachineIDTooBigForEthernet: PUBLIC ERROR = CODE;
DriverNotActive: PUBLIC ERROR = CODE;
DriverAlreadyActive: PUBLIC ERROR = CODE;
NoEthernetBoard: PUBLIC ERROR = CODE;
CantSwitchMachinesWhileEtherentDriverIsActive: PUBLIC ERROR = CODE;
CantMakImageWhileEtherentDriverIsActive: PUBLIC ERROR = CODE;
OnlyThreeDriversArePossible: PUBLIC ERROR = CODE;

-- things needed for chained input
first, last: EthernetDeviceBlockHandle;
headBuffer, tailBuffer: Buffer;
notChained: BOOLEAN;

interruptLevel: CommUtilDefs.InterruptLevel;
interruptBit: WORD;  -- BITSHIFT[1,interruptLevel];
inputCommand: SioParameter;
outputCommand: SioParameter;
resetCommand: SioParameter;

etherStats: POINTER TO EtherStatsInfo;

Interrupt: ENTRY PROCEDURE =
  BEGIN
  b, temporaryBuffer: Buffer;
  savedWordsLeft: CARDINAL;
  savedPostData: EthernetPost;
  device: EthernetDeviceBlockHandle = myDevice;
-- things needed for chained input
  doMoreInput: BOOLEAN;

  CommUtilDefs.SetPriority[4];

  UNTIL pleaseStop DO

    IF device.postData#EthernetNotPosted
    OR (~notChained AND first.postData#EthernetNotPosted) THEN
      BEGIN
      IF doStats THEN StatIncr[statEtherInterruptDuringInterrupt];
      END
    ELSE
      BEGIN
      DO
        WAIT hardware;
        IF device.postData#EthernetNotPosted
        OR (~notChained AND first.postData#EthernetNotPosted) THEN EXIT;
        IF doStats THEN StatIncr[statEtherMissingStatus];
        ENDLOOP;
      END;

    SELECT TRUE FROM
      (~notChained AND first.postData#EthernetNotPosted) =>
        BEGIN
        savedPostData ← first.postData;
        savedWordsLeft ← first.wordsLeft;
        first ← first.inputControlBlock;
        currentInputBuffer ← headBuffer;
        headBuffer ← headBuffer.next;
        doMoreInput ← TRUE;
        END;
      device.postData#EthernetNotPosted =>
        BEGIN
        savedPostData ← device.postData;
        savedWordsLeft ← device.wordsLeft;
        device.postData ← EthernetNotPosted;
        doMoreInput ← notChained;
        END;
      ENDCASE => Glitch[ImpossibleEndcase];

    IF notChained THEN
      BEGIN
      -- Turn on input as soon as we can so we don't drop as many packets.
      device.inputBuffer.count ← nextInputBuffer.length-ethernetEncapsulationOffset;
      device.inputBuffer.pointer ← nextBufferPointer;
      StartIO[inputCommand];
      -- input now running, can relax now
      END;

SELECT savedPostData.microcodeStatus FROM

inputDone =>
  IF savedPostData.hardwareStatus=hardwareAOK THEN
    BEGIN IF (temporaryBuffer←GetInputBuffer[])#NIL THEN
      BEGIN
      temporaryBuffer.device ← ethernet;
      -- should unwiredown packet, not now under interface
      b ← currentInputBuffer;
      b.length ← (b.length-ethernetEncapsulationOffset)-savedWordsLeft;
      b.network ← @myNetwork;
      IF doStats THEN
        BEGIN
        etherStats.packetsRecv ← etherStats.packetsRecv+1;
        StatIncr[statEtherPacketsReceived];
        StatBump[statEtherWordsReceived,b.length];
        END;
      PutOnGlobalInputQueue[b];
      currentInputBuffer ← temporaryBuffer;
      IF doStats AND currentOutputBuffer#NIL THEN StatIncr[statEtherInUnderOut];
      END
    ELSE
      IF doStats THEN
        BEGIN
        etherStats.inputOff ← etherStats.inputOff+1;
        StatIncr[statEtherEmptyFreeQueue];
        END;
    END
  ELSE
    IF doStats THEN
      BEGIN
      etherStats.badRecvStatus ← etherStats.badRecvStatus+1;
      SELECT 377B-savedPostData.hardwareStatus FROM
        1B => StatIncr[statEtherReceivedNot16];
        10B => StatIncr[statEtherReceivedBadCRC];
        11B => StatIncr[statEtherReceivedNot16BadCRC];
        40B, 50B =>
          BEGIN
          etherStats.overruns ← etherStats.overruns+1;
          StatIncr[statEtherReceivedOverrun];
          END;
        6B, 7B, 16B, 17B => StatIncr[statEtherReceivedKlobberedByReset];
        ENDCASE => StatIncr[statEtherReceivedBadStatus];
      END;

outputDone =>
  IF savedPostData.hardwareStatus=hardwareAOK THEN
    BEGIN
    device.outputBuffer ← [0,NIL0];
    PutOnGlobalDoneQueue[currentOutputBuffer];
    currentOutputBuffer ← NIL;
    IF doStats THEN
      BEGIN
      tries: CARDINAL;
      statEtherSendsCollision1: StatsDefs.StatCounterIndex = statEtherSendsCollision1;
      first: CARDINAL = LOOPHOLE[statEtherSendsCollision1];
      etherStats.packetsSent ← etherStats.packetsSent+1;
      SELECT (tries←device.retransmissionMask) FROM
        1 => tries ← 0;
        3 => tries ← 1;
        7 => tries ← 2;
        17B => tries ← 3;
        37B => tries ← 4;
        77B => tries ← 5;
        177B => tries ← 6;
        377B => tries ← 7;
        777B => tries ← 8;
        1777B => tries ← 9;
        3777B => tries ← 10;
        7777B => tries ← 11;
        17777B => tries ← 12;
        37777B => tries ← 13;
        77777B => tries ← 14;
        177777B => tries ← 15;
        ENDCASE => Glitch[FunnyRetransmissionMask];
      IF tries#0 THEN StatIncr[LOOPHOLE[first+tries]];
      etherStats.loadTable[tries] ← etherStats.loadTable[tries]+1;
      END;
    END
  ELSE
    BEGIN
    IF doStats THEN etherStats.badSendSatus ← etherStats.badSendSatus+1;
    IF (b←currentOutputBuffer)#NIL
    AND (CommUtilDefs.GetTicks[]-timeSendStarted)>500/CommUtilDefs.msPerTick THEN
      BEGIN
      -- requeue it so one packet won't hog the interface
      device.outputBuffer ← [0,NIL0];
      PutOnGlobalDoneQueue[currentOutputBuffer];
      currentOutputBuffer ← NIL;
      IF doStats THEN StatIncr[statPacketsStuckInOutput];
      END
    ELSE IF doStats THEN StatIncr[statEtherSendBadStatus];
    END;

  inputBufferOverflow => IF doStats THEN StatIncr[statEtherReceivedTooLong];

  outputLoadOverflow =>
    BEGIN
    -- requeue it so one packet won't hog the interface
    device.outputBuffer ← [0,NIL0];
    PutOnGlobalDoneQueue[currentOutputBuffer];
    currentOutputBuffer ← NIL;
    IF doStats THEN
      BEGIN
      etherStats.loadTable[16] ← etherStats.loadTable[16]+1;
      StatIncr[statEtherSendsCollisionLoadOverflow];
      END;
    END;

  zeroLengthBuffer => Glitch[ZeroLengthBuffer];

  hardwareReset =>
    -- three reasons for getting here:
    -- 1) getting back from debugger
    -- 2) lost interrupt
    -- 3) stuck output packet (transciever not plugged in)
    BEGIN
    IF (b←currentOutputBuffer)#NIL
    AND (CommUtilDefs.GetTicks[]-timeSendStarted)>500/CommUtilDefs.msPerTick THEN
      BEGIN
      -- requeue it so one packet won't hog the interface
      -- Watch the order of these stores
      device.outputBuffer.pointer ← NIL0;
      device.outputBuffer.count ← 0;
      PutOnGlobalDoneQueue[currentOutputBuffer];
      currentOutputBuffer ← NIL;
      IF doStats THEN StatIncr[statPacketsStuckInOutput];
      END
    ELSE IF doStats THEN StatIncr[statInterfaceReset];
    IF ~notChained AND device.inputControlBlock#NIL0 THEN
      BEGIN  -- restart input which got shot down
-- this could screwup if the microcode got restarted
      device.inputControlBlock.inputBuffer.pointer↑ ← 0;
      StartIO[inputCommand];
      END;
    END;

  interfaceBroken => Glitch[UnreasonableHardwareStatus];

  ENDCASE => Glitch[ImpossibleEndcase];

  IF doMoreInput THEN
    BEGIN

-- The normal mode uses two buffers for input.  The current one, and a hot standby.
-- Way up at the beginning of this loop, a read was started into the standby buffer.

-- At this point, currentInputBuffer has a buffer to be setup for use next time.
-- If we just finished a read,
--  then it is a new one left there by the inputDone processing,
--  (or the old one if we are recycling it because we couldn't get a new one)
-- otherwise, we are reusing the previous one.

   temporaryBuffer ← nextInputBuffer;
   nextInputBuffer ← currentInputBuffer;
   currentInputBuffer ← temporaryBuffer;
   nextBufferPointer ← ShortenData[
      @nextInputBuffer.encapsulation+ethernetEncapsulationOffset];
   nextBufferPointer↑ ← 0;
    IF ~notChained THEN
      BEGIN
      temp: EthernetDeviceBlockHandle;
      temp ← ShortenIocb[nextInputBuffer.iocbChain];
      temp↑ ← [
        postData: EthernetNotPosted,
        interruptBit: interruptBit,
        wordsLeft: 0,
        retransmissionMask: 0,
        inputBuffer: [
          nextInputBuffer.length-ethernetEncapsulationOffset,
          ShortenData[
            @nextInputBuffer.encapsulation+ethernetEncapsulationOffset] ],
        outputBuffer: [0,NIL0],
        hostNumber: 0,
        inputControlBlock: NIL0];
      last.inputControlBlock ← temp;
      last ← temp;
      tailBuffer.next ← nextInputBuffer;
      tailBuffer ← nextInputBuffer;
      IF device.inputControlBlock=NIL0 AND temp.postData=EthernetNotPosted THEN
        BEGIN  -- adding a new buffer
        device.inputControlBlock ← temp;
        StartIO[inputCommand];
        StatIncr[statEtherEmptyInputChain];
        END;
      END;
    END;

-- see if there is output to do, there will be some for several reasons:
-- 1 output finished, and another buffer was queued
-- 2 input finished, and leftover outputBuffer (in under out)
-- 3 input finished, and somebody didn't kick us because a packet was pouring in
-- 4 (chained) we got some input first

    BEGIN
    IF currentOutputBuffer#NIL THEN GOTO SendThisOne;
    IF outputQueue.length=0 THEN GOTO NothingToSend;
    currentOutputBuffer ← Dequeue[@outputQueue];
    timeSendStarted ← CommUtilDefs.GetTicks[];
    IF doCheck AND currentOutputBuffer=NIL THEN Glitch[QueueScrambled];
    IF doStats THEN StatIncr[statEtherSendFromOutputQueue];
    GOTO SendThisOne;
    EXITS
    SendThisOne =>
      BEGIN  -- start output if not already input coming in
      IF notChained THEN
        BEGIN
        IF device.inputBuffer.pointer↑#0 THEN GOTO PouringIn;
        device.interruptBit ← 0; -- inhibit interrupts for reset
        device.postData ← EthernetNotPosted;
-- Beware of hardware/microcode screwup
-- it seems to hang while sending, after a collision, until a gateway packet arrives
        IF doStats THEN StartIO[resetCommand];  -- should post immediately
        UNTIL device.postData#EthernetNotPosted DO
          IF doStats THEN StatIncr[statResetDidntPost];
          StartIO[resetCommand];
          ENDLOOP;
        device.interruptBit ← interruptBit; --interrupts back on
        device.postData ← EthernetNotPosted;
        END
      ELSE
        BEGIN
        -- Don't reverse the order of these tests.  It might be about ready to post.
        IF device.outputBuffer.pointer#NIL THEN GOTO AlreadySending;
        IF device.postData#EthernetNotPosted THEN GOTO DontSendAgain;
        END;
      device.retransmissionMask ← 0;
      device.outputBuffer.count ← currentOutputBuffer.length;
      device.outputBuffer.pointer ← ShortenData[
        @currentOutputBuffer.encapsulation+ethernetEncapsulationOffset];
      IF ~notChained THEN
        BEGIN
        now: EthernetDeviceBlockHandle = device.inputControlBlock;
        IF now#NIL AND now.inputBuffer.pointer↑#0 THEN GOTO PouringIn;
        IF device.retransmissionMask#0 THEN GOTO AlreadySending;
        END;
      StartIO[outputCommand];
      EXITS
      PouringIn, AlreadySending, DontSendAgain => NULL;
      END;
    NothingToSend => NULL;
    END;

    ENDLOOP;
  END;  -- Interrupt

Watcher: PROCEDURE =
  BEGIN
  UNTIL pleaseStop DO
    THROUGH [0..25) DO
      IF myDevice.postData=EthernetNotPosted
      AND (notChained OR first.postData=EthernetNotPosted) THEN EXIT;
-- If the post location is not zero, an interrupt should be pending.  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 at the post location several times.  Of course, if it is still not zero when we look again, it could be a new interrupt that has just arrived.
      REPEAT FINISHED =>
        BEGIN
        IF doStats THEN StatIncr[statEtherLostInterrupts];
        WatcherNotify[];
        END;
      ENDLOOP;
    IF currentOutputBuffer#NIL
    AND (CommUtilDefs.GetTicks[]-timeSendStarted)>250/CommUtilDefs.msPerTick THEN
      StartIO[resetCommand];  -- interrupt code will flush it
    IF (CommUtilDefs.GetTicks[]-timeLastRecv)>5000/CommUtilDefs.msPerTick THEN
      BEGIN
      -- Blast receiver since it may be stuck.  This might kill a packet.
      -- This shouldn't hurt (much) if it is ok since this doesn't happen very often.
      timeLastRecv ← CommUtilDefs.GetTicks[];
      IF doStats THEN StatIncr[statMouseTrap];
      StartIO[resetCommand];
      END;
    WatcherWait[];
    ENDLOOP;
  END;

WatcherWait: ENTRY PROCEDURE =
  BEGIN
  WAIT timer;
  END;

WatcherNotify: ENTRY PROCEDURE =
  BEGIN
  NOTIFY hardware;
  END;

DecapsulateBuffer: PROCEDURE [b: Buffer] RETURNS [BufferType] =
  BEGIN
  timeLastRecv ← CommUtilDefs.GetTicks[];
  SELECT b.encapsulation.ethernetType FROM
    DriverTypes.pupEthernetPacket =>
      BEGIN
      IF b.length#((b.pupLength+1+ethernetEncapsulationBytes)/2) THEN 
        BEGIN
        IF doStats THEN StatIncr[statPupsDiscarded];
        RETURN[rejected];
        END;
      RETURN[pup];
      END;
    ENDCASE => RETURN[rejected];
  END;

EncapsulateRpp: PROCEDURE [RppBuffer, PupHostID] = LOOPHOLE[EncapsulatePup];
EncapsulatePup: PROCEDURE [b: PupBuffer, destination: PupHostID] =
  BEGIN
  b.encapsulation ← [ ethernet [
    etherSpare1:, etherSpare2:, etherSpare3:, etherSpare4:,
    etherDest: destination, etherSource: myNetwork.hostNumber,
    ethernetType: DriverTypes.pupEthernetPacket ] ];
  b.length ← (b.pupLength+1+ethernetEncapsulationBytes)/2;
  END;

ForwardBuffer: PROCEDURE [b: Buffer] RETURNS [PupTypes.PupErrorCode] =
  BEGIN
  IF outputQueue.length>10 THEN
    RETURN[PupTypes.gatewayResourceLimitsPupErrorCode];  -- transciever unpluged?
  SendBuffer[b];
  RETURN[PupTypes.noErrorPupErrorCode];
  END;

SendBuffer: ENTRY PROCEDURE [b: Buffer] =
  BEGIN
  device: EthernetDeviceBlockHandle = myDevice;
  copy: Buffer;
  IF pleaseStop THEN Glitch[DriverNotActive];
  b.device ← ethernet;
  IF b.encapsulation.etherDest=myNetwork.hostNumber
  OR b.encapsulation.etherDest=DriverTypes.ethernetBroadcastHost THEN
    BEGIN  -- sending to ourself, copy it over
    copy ← GetInputBuffer[];
    IF copy#NIL THEN
      BEGIN
      copy.device ← ethernet;
      InlineDefs.COPY[
        from: @b.encapsulation+ethernetEncapsulationOffset,
        nwords: b.length,
        to: @copy.encapsulation+ethernetEncapsulationOffset ];
      copy.length ← b.length;
      copy.network ← @myNetwork;
      PutOnGlobalInputQueue[copy];
      IF doStats THEN StatIncr[statEtherPacketsLocal];
      IF doStats THEN StatBump[statEtherWordsLocal,b.length];
      END
    ELSE IF doStats THEN StatIncr[statEtherEmptyFreeQueue];
    END;
  IF currentOutputBuffer=NIL THEN
    BEGIN
    currentOutputBuffer ← b;
    timeSendStarted ← CommUtilDefs.GetTicks[];
    device.retransmissionMask ← 0;
    device.outputBuffer.count ← b.length;
    device.outputBuffer.pointer ← ShortenData[
      @b.encapsulation+ethernetEncapsulationOffset];
    IF notChained THEN
      BEGIN
      IF device.inputBuffer.pointer↑#0 THEN GOTO PouringIn;
      device.interruptBit ← 0; -- inhibit interrupts for reset
      device.postData ← EthernetNotPosted;
-- beware of hardware/microcode screwup
-- it seems to hang while sending, after a collision, until a gateway packet arrives
      IF doStats THEN StartIO[resetCommand];  -- should post immediately
      UNTIL device.postData#EthernetNotPosted DO
        IF doStats THEN StatIncr[statResetDidntPost];
        StartIO[resetCommand];
        ENDLOOP;
      device.interruptBit ← interruptBit; --interrupts back on
      device.postData ← EthernetNotPosted;
      END;
    IF ~notChained THEN
      BEGIN
      now: EthernetDeviceBlockHandle = device.inputControlBlock;
      IF now#NIL AND now.inputBuffer.pointer↑#0 THEN GOTO PouringIn;
      IF device.retransmissionMask#0 THEN GOTO AlreadySending;
      END;
    StartIO[outputCommand];  -- This could possibly clobber a packet that just started
    EXITS
      AlreadySending => NULL;
      PouringIn =>
        BEGIN  -- data already arriving, don't klobber it
        IF doStats THEN StatIncr[statEtherSendWhileReceiving];
        END;
    END
  ELSE Enqueue[@outputQueue,b];  -- output already in progress, don't klobber it
  IF doStats THEN StatIncr[statEtherPacketsSent];
  IF doStats THEN StatBump[statEtherWordsSent,b.length];
  END;

-- Saving the satus is helpful when debugging.  Comment it out to save space.
ethernetStatus: EthernetDeviceBlockHandle;
Broom: PROCEDURE [why: CommUtilDefs.CleanupReason] =
  BEGIN
  IF doDebug THEN ethernetStatus↑ ← myDevice↑;
  SELECT why FROM
    Finish, Abort, OutLd => myDevice.interruptBit ← 0;
    InLd =>
      BEGIN
      IF myNetwork.hostNumber#CommUtilDefs.GetEthernetHostNumber[] THEN
        Glitch[CantSwitchMachinesWhileEtherentDriverIsActive];
      myDevice.interruptBit ← interruptBit;
      END;
    Save, Checkpoint => Glitch[CantMakImageWhileEtherentDriverIsActive];
    ENDCASE;
  StartIO[resetCommand];
  END;

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

CreateDefaultEthernetDriver: PUBLIC PROCEDURE RETURNS [BOOLEAN] =
  BEGIN OPEN AltoEthernetDefs;
  SetupEthernetDriver[0,5,standardInput,standardOutput,standardEthernet,FALSE];
  RETURN[TRUE];
  END;


-- NB: All non-Default drivers assume the Chained microcode

CreateEthernetDriver: PUBLIC PROCEDURE [
    netNumber: CARDINAL, deviceNumber: [0..3) ]
  RETURNS [BOOLEAN] =
  BEGIN OPEN AltoEthernetDefs;
  him: POINTER TO FRAME[AltoEthernetDriver];
  -- There is no way to delete the new frame.
  IF deviceNumber#0 THEN him ← CommUtilDefs.Copy[CommUtilDefs.MyGlobalFrame[]];
  SELECT deviceNumber FROM
    0 => SetupEthernetDriver[netNumber,5,
            standardInput,standardOutput,standardEthernet,TRUE];
    1 => him.SetupEthernetDriver[netNumber,6,
            secondInput,secondOutput,secondEthernet,TRUE];
    2 => him.SetupEthernetDriver[netNumber,8,  -- 7 used by keyboard
            thirdInput,thirdOutput,thirdEthernet,TRUE];
    ENDCASE => Glitch[OnlyThreeDriversArePossible];
  RETURN[TRUE];
  END;

SetupEthernetDriver: PROCEDURE [
    netNumber: CARDINAL, intLevel: WORD,
    inputBit, outputBit: WORD, deviceBlock: EthernetDeviceBlockHandle,
    chained: BOOLEAN] =
  BEGIN
  size: CARDINAL ← IF chained THEN SIZE[EthernetDeviceBlock] ELSE 0;
  notChained ← ~chained;
  myNetwork.netNumber ← netNumber;
  inputCommand ← [inputBit];
  outputCommand ← [outputBit];
  interruptLevel ← CommUtilDefs.AssignInterruptLevel[intLevel];
  myDevice ← deviceBlock;
  resetCommand ← [inputCommand+outputCommand];
  interruptBit ← InlineDefs.BITSHIFT[1,interruptLevel];
  pleaseStop ← TRUE;
  myDevice.postData ← EthernetNotPosted;
  AddDeviceToChain[@myNetwork,size];
  IF doStats THEN
    BEGIN
    myNetwork.stats ← etherStats ←
      CommUtilDefs.AllocateLockedNode[SIZE[EtherStatsInfo]];
    CommUtilDefs.Zero[etherStats,SIZE[EtherStatsInfo]];
    END;
  END;

DeleteDriver: PROCEDURE =
  BEGIN
  END;

-- Be sure the microcode has been loaded by now if this is a second Ethernet Board
ActivateDriver: PROCEDURE =
  BEGIN
  IF ~pleaseStop THEN Glitch[DriverAlreadyActive];
  pleaseStop ← FALSE;
  StartIO[resetCommand];
  StartIO[resetCommand];  -- sometimes it doesn't work
  IF myDevice.postData=EthernetNotPosted THEN Glitch[NoEthernetBoard];
  QueueInitialize[@outputQueue];
  currentInputBuffer ← nextInputBuffer ← currentOutputBuffer ← NIL;
  myNetwork.hostNumber ← CommUtilDefs.GetEthernetHostNumber[];
  myDevice.hostNumber ← myNetwork.hostNumber;
  myDevice.inputBuffer ← [0,NIL0];
  myDevice.outputBuffer ← [0,NIL0];
  IF notChained THEN
    BEGIN
    nextInputBuffer ← GetInputBuffer[];
    currentInputBuffer ← GetInputBuffer[];
    nextInputBuffer.device ← currentInputBuffer.device ← ethernet;
    nextBufferPointer ← ShortenData[
      @nextInputBuffer.encapsulation+ethernetEncapsulationOffset];
    nextBufferPointer↑ ← 0; -- show no input in yet
    myDevice.inputControlBlock ← NIL0;
    END
  ELSE
    BEGIN
    temp: EthernetDeviceBlockHandle;
    first ← last ← NIL;
    headBuffer ← tailBuffer ← NIL;
    THROUGH [0..4) DO  -- This should be a parameter
    currentInputBuffer ← GetInputBuffer[];
      temp ← ShortenIocb[currentInputBuffer.iocbChain];
      IF first=NIL THEN BEGIN first ← temp; headBuffer ← currentInputBuffer; END;
      temp↑ ← [
        postData: EthernetNotPosted,
        interruptBit: interruptBit,
        wordsLeft: 0,
        retransmissionMask: 0,
        inputBuffer: [
          currentInputBuffer.length-ethernetEncapsulationOffset,
          ShortenData[
            @currentInputBuffer.encapsulation+ethernetEncapsulationOffset] ],
        outputBuffer: [0,NIL0],
        hostNumber: 0,
        inputControlBlock: NIL0];
      temp.inputBuffer.pointer↑ ← 0;
      IF last#NIL THEN
        BEGIN
        last.inputControlBlock ← temp;
        tailBuffer.next ← currentInputBuffer;
        END;
      last ← temp;
      currentInputBuffer.next ← NIL;
      tailBuffer ← currentInputBuffer;
      ENDLOOP;
    myDevice.inputControlBlock ← first;
    END;
  CommUtilDefs.AddCleanupProcedure[@cleanupItem];
  CommUtilDefs.AddInterruptHandler[interruptLevel,@hardware,resetCommand];
  hardProcess ← FORK Interrupt[];
  -- The first interrupt will set things up.
  myDevice.interruptBit ← interruptBit;
  StartIO[resetCommand];
  watcherProcess ← FORK Watcher[];
  END;

DeactivateDriver: PROCEDURE =
  BEGIN
  IF pleaseStop THEN Glitch[DriverNotActive];
  pleaseStop ← TRUE;
  StartIO[resetCommand];  -- includes (naked)NOTIFY
  JOIN hardProcess;
  CommUtilDefs.RemoveCleanupProcedure[@cleanupItem];
  myDevice.interruptBit ← 0;
  CommUtilDefs.RemoveInterruptHandler[interruptLevel];
  StartIO[resetCommand];
  KillDriverLocked[];
  JOIN watcherProcess;
  IF currentInputBuffer#NIL THEN ReturnFreeBuffer[currentInputBuffer];
  IF nextInputBuffer#NIL THEN ReturnFreeBuffer[nextInputBuffer];
  IF currentOutputBuffer#NIL THEN PutOnGlobalDoneQueue[currentOutputBuffer];
  QueueCleanup[@outputQueue];
  myNetwork.netNumber ← 0;  -- in case we turn it on after moving to another machine
  -- maybe we should turn off the SD bits if we are an extra board
  END;

KillDriverLocked: ENTRY PROCEDURE = INLINE
  BEGIN
  NOTIFY timer;
  END;

-- initialization
CommUtilDefs.DisableTimeout[@hardware];
CommUtilDefs.SetTimeout[@timer,CommUtilDefs.MsecToTicks[1000]];
IF doDebug THEN
  BEGIN
  debugPointer: LONG POINTER TO GiantVector ← giantVector;
  debugPointer.ethernetOutputQueue ← @outputQueue;
  debugPointer.currentInputBuffer ← @currentInputBuffer;
  debugPointer.nextInputBuffer ← @nextInputBuffer;
  debugPointer.currentOutputBuffer ← @currentOutputBuffer;
  ethernetStatus ← CommUtilDefs.AllocateLockedNode[SIZE[EthernetDeviceBlock]];
  END;
END.  -- AltoEthernetDriver