-- Copyright (C) 1983, 1984  by Xerox Corporation. All rights reserved. 
-- EthernetHeadDicentra.mesa, HGM, 20-Nov-84 14:47:59

DIRECTORY
  DicentraInputOutput USING [
    GetExternalStatus, Input, Output, ReadBlock,
    SetNxmExpected, SetWakeupBits, SetWakeupMask, WriteBlock],
  EthernetFace USING [GlobalStatePtr, Status],
  HeadStartChain USING [Start],
  Inline USING [BITAND, BITNOT, BITOR, BITXOR, LowHalf],
  MultibusAddresses USING [first3ComBoard],
  Runtime USING [CallDebugger, IsBound],
  Process USING [Detach, DisableTimeout, SetPriority],
  ProcessPriorities USING [priorityPageFaultIO],  -- Higher than Driver
  SpecialRuntime USING [AllocateNakedCondition],
  SpecialSystem USING [ProcessorID],
  System USING [GetClockPulses];

EthernetHeadDicentra: MONITOR
  IMPORTS
    RemainingHeads: HeadStartChain,
    DicentraInputOutput, Inline, Process, Runtime, SpecialRuntime, System
  EXPORTS HeadStartChain, EthernetFace =
  BEGIN

  -- TYPEs
  ControlBlock: TYPE = LONG POINTER TO ControlBlockRecord;

  -- EXPORTed TYPEs
  DeviceHandle: PUBLIC TYPE = POINTER TO CSB;
  ControlBlockRecord: PUBLIC TYPE = RECORD [
    next: ControlBlock,
    buffer: LONG POINTER,
    length: CARDINAL,
    used: CARDINAL,
    mask: WORD,
    status: EthernetFace.Status,
    rawBits: WORD,
    device: DeviceHandle,
    direction: {in, out} ];

  -- EXPORTed variables
  nullDeviceHandle: PUBLIC DeviceHandle ← NIL;
  globalStateSize: PUBLIC CARDINAL ← 0;
  controlBlockSize: PUBLIC CARDINAL ← SIZE[ControlBlockRecord];
  hearSelf: PUBLIC BOOLEAN ← FALSE;
  
  -- My CSB (and such)
  maxNumberOfDevices: CARDINAL = 6;
  first: DeviceHandle ← nullDeviceHandle;
  csbs: ARRAY [0..maxNumberOfDevices) OF CSB;
  
  CSB: TYPE = RECORD [
    next: DeviceHandle,
    header: LONG POINTER TO Header,
    transmitBuffer: LONG POINTER TO ARRAY [0..1024) OF WORD,
    recvBufferA: LONG POINTER TO ARRAY [0..1024) OF WORD,
    recvBufferB: LONG POINTER TO ARRAY [0..1024) OF WORD,
    control: WORD,
    firstIn, lastIn: ControlBlock,
    firstOut, lastOut: ControlBlock,
    missed: CARDINAL,
    inInterrupt, outInterrupt: WORD ];
  
    
  offsetOfNextBoard: CARDINAL = 8000H;  -- 64K Bytes.  Shadow behind first board
  -- 3Com board layout: BEWARE of ADR0
  -- Yetch, a simple record "exceeds addressing limits".
  Header: TYPE = RECORD [
    mecsr: WORD,
    meback: CARDINAL,
      trash1: ARRAY [0..512-(SIZE[WORD]+SIZE[CARDINAL])) OF WORD,
    stationAddressROM: SpecialSystem.ProcessorID,
      trash2: ARRAY [0..256-SIZE[SpecialSystem.ProcessorID]) OF WORD,
    stationAddressRAM: SpecialSystem.ProcessorID ];
    
  packetFilter: WORD = 6B;  -- Me and Broadcast
  inInterruptsAndPacketFilter: WORD = 306B;  -- Buffer A and Buffer B
  outInterrupts: WORD = 60B;  -- Output done and Jam
  
  GetNextDevice: PUBLIC PROCEDURE [in: DeviceHandle] RETURNS [out: DeviceHandle] =
    BEGIN
    IF in = nullDeviceHandle THEN out ← first
    ELSE out ← in.next;
    END;

  QueueOutput: PUBLIC ENTRY PROCEDURE [
    device: DeviceHandle, buffer: LONG POINTER, length: CARDINAL, cb: ControlBlock] =
    BEGIN
    cb↑ ← [NIL, buffer, length, 0, 1, pending, 0, device, out];
    IF device.firstOut = NIL THEN BEGIN device.firstOut ← cb; StartOutput[device]; END
    ELSE device.lastOut.next ← cb;
    device.lastOut ← cb;
    END;
  
  StartOutput: INTERNAL PROCEDURE [device: DeviceHandle] =
    BEGIN
    cb: ControlBlock = device.firstOut;
    wordOffset: CARDINAL = 1024 - cb.length;
    DicentraInputOutput.WriteBlock[from: cb.buffer, to: device.transmitBuffer + wordOffset, words: cb.length];
    device.control ← Inline.BITOR[device.control, outInterrupts];
    DicentraInputOutput.Output[-20, @device.header.meback]; -- Initial 1ms delay
    DicentraInputOutput.Output[wordOffset*2, device.transmitBuffer];
    DicentraInputOutput.Output[30000B+device.control, @device.header.mecsr];  -- Set TBSW, Clear Jam
    END;
  
  CheckSendStatus: INTERNAL PROCEDURE [device: DeviceHandle] RETURNS [bogus: BOOLEAN ← FALSE] =
    BEGIN
    status: WORD = DicentraInputOutput.Input[@device.header.mecsr];
    cb: ControlBlock = device.firstOut;
    IF cb = NIL THEN
      BEGIN
      device.control ← Inline.BITAND[device.control, Inline.BITNOT[outInterrupts]];
      DicentraInputOutput.Output[device.control, @device.header.mecsr];  -- Clear Out Int
      RETURN[TRUE];
      END;
    IF Inline.BITAND[status, 10000B] # 0 THEN
      BEGIN -- Collision
      random: WORD ← Inline.LowHalf[System.GetClockPulses[]];
      random ← Inline.BITAND[random, 1777B];
      random ← Inline.BITAND[random, cb.mask];
      IF cb.mask = 177777B THEN
        BEGIN
	-- Yetch.   I think we have to reset the board.  Yetch.
        cb.status ← tooManyCollisions;
        cb.rawBits ← status;
        DicentraInputOutput.SetWakeupBits[device.outInterrupt];
        device.firstOut ← device.firstOut.next;
        IF device.firstOut # NIL THEN StartOutput[device]
	ELSE
	  BEGIN
          device.control ← Inline.BITAND[device.control, Inline.BITNOT[outInterrupts]];
          DicentraInputOutput.Output[device.control, @device.header.mecsr];  -- Clear Out Int
	  END;
	RETURN;
        END;
      -- Note: Documentation bug: It doesn't mention the extra 1.
      DicentraInputOutput.Output[-(random+1), @device.header.meback];
      DicentraInputOutput.Output[30000B+device.control, @device.header.mecsr];  -- Set TBSW, Clear Jam
      cb.mask ← cb.mask * 2 + 1;
      RETURN;
      END;
    IF Inline.BITAND[status, 20000B] # 0 THEN RETURN[TRUE];
    cb.status ← ok;
    cb.rawBits ← status;
    DicentraInputOutput.SetWakeupBits[device.outInterrupt];
    device.firstOut ← device.firstOut.next;
    IF device.firstOut # NIL THEN StartOutput[device]
    ELSE
      BEGIN
      device.control ← Inline.BITAND[device.control, Inline.BITNOT[outInterrupts]];
      DicentraInputOutput.Output[device.control, @device.header.mecsr];  -- Clear Out Int
      END;
    END;
  
  QueueInput: PUBLIC ENTRY PROCEDURE [
    device: DeviceHandle, buffer: LONG POINTER, length: CARDINAL, cb: ControlBlock] =
    BEGIN
    cb↑ ← [NIL, buffer, length, 0, 0, pending, 0, device, in];
    IF device.firstIn = NIL THEN device.firstIn ← cb
    ELSE device.lastIn.next ← cb;
    device.lastIn ← cb;
    END;

  CheckRecvStatus: INTERNAL PROCEDURE [device: DeviceHandle] RETURNS [bogus: BOOLEAN ← TRUE] =
    BEGIN
    DO
      status: WORD = Inline.BITAND[DicentraInputOutput.Input[@device.header.mecsr], 142000B];
      SELECT status FROM
        140000B, 142000B => RETURN;
        0 =>
	  BEGIN
	  GrabBuffer[device, device.recvBufferA, 40000B];
	  GrabBuffer[device, device.recvBufferB, 100000B];
	  END;
        2000B =>
	  BEGIN
	  GrabBuffer[device, device.recvBufferB, 100000B];
	  GrabBuffer[device, device.recvBufferA, 40000B];
	  END;
        100000B, 102000B => GrabBuffer[device, device.recvBufferA, 40000B];
        40000B, 42000B => GrabBuffer[device, device.recvBufferB, 100000B];
        ENDCASE => ERROR;
      bogus ← FALSE
      ENDLOOP;
    END;
  
  GrabBuffer: INTERNAL PROCEDURE [device: DeviceHandle, recv: LONG POINTER, mask: WORD] =
    BEGIN
    cb: ControlBlock = device.firstIn;
    rawBits: WORD ← DicentraInputOutput.Input[recv];
    bytes, words: CARDINAL;
    bytes ← Inline.BITAND[rawBits, 3777B] - 2;
    IF cb = NIL THEN
      BEGIN
      device.missed ← device.missed + 1;
      DicentraInputOutput.Output[mask+device.control, @device.header.mecsr];  -- Start next read
      RETURN;
      END;
    words ← bytes/2;
    SELECT TRUE FROM
      Inline.BITAND[rawBits, 020000B] # 0 => cb.status ← packetTooLong;
      Inline.BITAND[rawBits, 104000B] # 0 =>
        BEGIN
        error: WORD = Inline.BITAND[rawBits, 124000B];
	SELECT error FROM
	  100000B => cb.status ← crc;
	  104000B => cb.status ← crcAndBadAlignment;
	  004000B => cb.status ← badAlignmentButOkCrc;
	  ENDCASE => cb.status ← packetTooLong;
	END;
      bytes MOD 2 # 0 => cb.status ← badAlignmentButOkCrc;
      ENDCASE => cb.status ← ok;
    IF words > cb.length THEN
      BEGIN
      cb.status ← packetTooLong;
      words ← cb.length;
      END;
    DicentraInputOutput.ReadBlock[to: cb.buffer, from: recv + 1, words: words];
    cb.used ← words;
    cb.mask ← bytes;
    cb.rawBits ← rawBits;
    DicentraInputOutput.SetWakeupBits[device.inInterrupt];
    DicentraInputOutput.Output[mask+device.control, @device.header.mecsr];  -- Start next read
    device.firstIn ← device.firstIn.next;
    END;
  
  GetStatus: PUBLIC ENTRY PROCEDURE [cb: ControlBlock] RETURNS [EthernetFace.Status] =
    BEGIN
    IF interruptBits = 0 THEN [] ← CheckInterrupts[cb.device];
    RETURN[cb.status];
    END;
  
  GetRetries: PUBLIC PROCEDURE [cb: ControlBlock] RETURNS [CARDINAL] =
    BEGIN
    IF cb.direction # out THEN ERROR;
    IF cb.status = pending THEN ERROR;
    RETURN[
      SELECT cb.mask FROM
        1 => 0,
        3 => 1,
        7 => 2,
        17B => 3,
        37B => 4,
        77B => 5,
        177B => 6,
        377B => 7,
        777B => 8,
        1777B => 9,
        3777B => 10,
        7777B => 11,
        17777B => 12,
        37777B => 13,
        77777B => 14,
        177777B => 15,
        ENDCASE => 16];
    END;
    
  GetPacketLength: PUBLIC PROCEDURE [cb: ControlBlock] RETURNS [CARDINAL] =
    BEGIN
    IF cb.direction # in THEN ERROR;
    IF cb.status = pending THEN ERROR;
    RETURN[cb.used];
    END;
    
  GetPacketsMissed: PUBLIC PROCEDURE [device: DeviceHandle] RETURNS [CARDINAL] =
    BEGIN RETURN[device.missed]; END;

  wait: LONG POINTER TO CONDITION ← NIL;
  interruptBits: WORD ← 0;
  maxBogus, totalRecvBogus, totalTranBogus: CARDINAL ← 0;
  DeMultiplexInterrupts: ENTRY PROCEDURE =
    BEGIN
    [cv: wait, mask: interruptBits] ← SpecialRuntime.AllocateNakedCondition[];
    Process.DisableTimeout[wait];
    Process.SetPriority[ProcessPriorities.priorityPageFaultIO];
    DO
      bogus: CARDINAL ← 0;
      WAIT wait;
      WHILE DicentraInputOutput.GetExternalStatus[].ints[ethernet] DO
        hit: BOOLEAN ← FALSE;
        FOR device: DeviceHandle ← first, device.next UNTIL device = NIL DO
	  thisHit: BOOLEAN;
	  thisRecvBogus, thisTranBogus: CARDINAL;
          [thisHit, thisRecvBogus, thisTranBogus] ← CheckInterrupts[device];
          hit ← hit OR thisHit;
	  bogus ← bogus + thisRecvBogus + thisTranBogus;
	  totalRecvBogus ← totalRecvBogus + thisRecvBogus;
	  totalTranBogus ← totalTranBogus + thisTranBogus;
	  ENDLOOP;
	IF ~hit THEN bogus ← bogus + 1;
	maxBogus ← MAX[maxBogus, bogus];
	IF bogus > 100 THEN Runtime.CallDebugger["Dangling 10mb Interrupt"L];
	ENDLOOP;
      ENDLOOP;
    END;

  CheckInterrupts: INTERNAL PROCEDURE [device: DeviceHandle]
    RETURNS [hit: BOOLEAN ← FALSE, recvBogus, tranBogus: CARDINAL ← 0] =
    BEGIN
    DO
      status: WORD ← DicentraInputOutput.Input[@device.header.mecsr];
      status ← Inline.BITXOR[status, 160000B];
      SELECT TRUE FROM
        Inline.BITAND[status, 140000B] # 0 =>
	  BEGIN
	  IF CheckRecvStatus[device] THEN recvBogus ← recvBogus + 1 ELSE hit ← TRUE;
	  END;
        Inline.BITAND[status, 30000B] # 0 AND Inline.BITAND[status, outInterrupts] # 0 =>
	  BEGIN
	  IF CheckSendStatus[device] THEN tranBogus ← tranBogus + 1 ELSE hit ← TRUE;
	  END;
	ENDCASE => RETURN;
      ENDLOOP;
    END;
  
  TurnOn: PUBLIC ENTRY PROCEDURE [
    device: DeviceHandle, host: SpecialSystem.ProcessorID,
    inInterrupt: WORD, outInterrupt: WORD,
    globalState: EthernetFace.GlobalStatePtr] =
    BEGIN
    source: POINTER = @host;
    dest: LONG POINTER = @device.header.stationAddressRAM;
    DicentraInputOutput.Output[400B+0, @device.header.mecsr];  -- Reset
    device.firstIn ← device.lastIn ← device.firstOut ← device.lastOut ← NIL;
    device.inInterrupt ← inInterrupt;
    device.outInterrupt ← outInterrupt;
    FOR i: CARDINAL IN [0..SIZE[SpecialSystem.ProcessorID]) DO
      DicentraInputOutput.Output[(source+i)↑, dest+i];
      ENDLOOP;
    device.control ← inInterruptsAndPacketFilter;
    DicentraInputOutput.SetWakeupMask[interruptBits, ethernet];
    DicentraInputOutput.Output[144000B+device.control, @device.header.mecsr]; -- Set BBSW, ABSW, AMSW
    END;

  TurnOff: PUBLIC ENTRY PROCEDURE [device: DeviceHandle] =
    BEGIN
    device.control ← packetFilter;
    DicentraInputOutput.Output[400B+device.control, @device.header.mecsr];  -- Reset
    DicentraInputOutput.SetWakeupMask[interruptBits, ethernet];
    device.firstIn ← device.lastIn ← device.firstOut ← device.lastOut ← NIL;
    END;

  -- None needed: it's not reading directly into our memory
  AddCleanup, RemoveCleanup: PUBLIC PROCEDURE [DeviceHandle] = BEGIN END;

  Start: PUBLIC PROCEDURE =
    BEGIN
    last: DeviceHandle ← nullDeviceHandle;
    DicentraInputOutput.SetNxmExpected[TRUE];
    FOR i: CARDINAL IN [0..maxNumberOfDevices) DO
      device: DeviceHandle = @csbs[i];
      thisBoard: LONG POINTER = MultibusAddresses.first3ComBoard + i*LONG[offsetOfNextBoard];
      hostNumber1, hostNumber2: WORD;
      device↑ ← [
        next: nullDeviceHandle,
        header: thisBoard,
        transmitBuffer: thisBoard + 1*1024,
        recvBufferA: thisBoard + 2*1024,
        recvBufferB: thisBoard + 3*1024,
        control: packetFilter,
        firstIn: NIL, lastIn: NIL,
        firstOut: NIL, lastOut: NIL,
        missed: 0,
        inInterrupt: 0, outInterrupt: 0 ];
      DicentraInputOutput.Output[400B+device.control, @device.header.mecsr];  -- Reset
      hostNumber1 ← DicentraInputOutput.Input[@device.header.stationAddressROM + 0];
      hostNumber2 ← DicentraInputOutput.Input[@device.header.stationAddressROM + 1];
      hostNumber2 ← Inline.BITAND[hostNumber2, 0FF00H];
      IF hostNumber1 # 0260H OR hostNumber2 # 8C00H THEN EXIT;
      IF first = nullDeviceHandle THEN first ← device;
      IF last # nullDeviceHandle THEN last.next ← device;
      last ← device;
      DicentraInputOutput.Output[400B+device.control, @device.header.mecsr];  -- Reset
      DicentraInputOutput.Output[144000B+device.control, @device.header.mecsr]; -- Set BBSW, ABSW, AMSW
      ENDLOOP;
    DicentraInputOutput.SetNxmExpected[FALSE];
    IF Runtime.IsBound[LOOPHOLE[Process.Detach]] THEN
      Process.Detach[FORK DeMultiplexInterrupts[]];
    RemainingHeads.Start[];
    END;    

  END....