-- Copyright (C) 1984  by Xerox Corporation. All rights reserved. 
-- EthernetOneHeadDicentra.mesa, HGM,  2-Dec-84  3:37:03

DIRECTORY
  DicentraInputOutput USING [
    GetExternalStatus, Input, InterruptLevel,
    Output, ReadHole, SetNxmExpected, SetWakeupBits, SetWakeupMask, WriteHole],
  Environment USING [Byte],
  EthernetOneFace USING [GlobalStatePtr, Status],
  HeadStartChain USING [Start],
  Inline USING [BITAND, BITXOR],
  MultibusAddresses USING [firstSunBoard],
  Runtime USING [CallDebugger, IsBound],
  Process USING [Detach, DisableTimeout, SetPriority],
  ProcessPriorities USING [priorityPageFaultIO],  -- Higher than Driver
  SpecialRuntime USING [AllocateNakedCondition];

EthernetOneHeadDicentra: MONITOR
  IMPORTS
    RemainingHeads: HeadStartChain,
    DicentraInputOutput, Inline, Process, Runtime, SpecialRuntime
  EXPORTS HeadStartChain, EthernetOneFace =
  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,
    status: EthernetOneFace.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 ← TRUE;
  
  CSB: TYPE = RECORD [
    next: DeviceHandle,
    sunIn: LONG POINTER TO SunIn,
    sunOut: LONG POINTER TO SunOut,
    firstIn, lastIn: ControlBlock,
    firstOut, lastOut: ControlBlock,
    missed: CARDINAL,
    inInterrupt, outInterrupt: WORD];
  
  -- SUN 3mb board layout
  offsetOfNextBoard: CARDINAL = 128;  -- 256 bytes
  SunIn: TYPE = RECORD [
    data: WORD,
    unused: WORD,
    ready: WORD,
    switches: WORD ];  -- Reading switches resets Transmitter interrupt
  SunOut: TYPE = RECORD [
    data: WORD,
    unused: WORD,
    control: WORD,
    addressFilter: WORD ];
    
  maxNumberOfDevices: CARDINAL = 6;
  first: DeviceHandle ← nullDeviceHandle;
  csbs: ARRAY [0..maxNumberOfDevices) OF CSB;
    
  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, 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;
    DicentraInputOutput.Output[cb.length, @device.sunOut.data];
    DicentraInputOutput.WriteHole[from: cb.buffer, to: @device.sunOut.data, words: cb.length];
    END;
  
  CheckSendStatus: INTERNAL PROCEDURE [device: DeviceHandle]
    RETURNS [bogus: BOOLEAN ← FALSE] =
    BEGIN
    ready: WORD = DicentraInputOutput.Input[@device.sunIn.ready];
    cb: ControlBlock = device.firstOut;
    [] ← DicentraInputOutput.Input[@device.sunIn.switches];  -- Clears Out Int
    IF cb = NIL THEN RETURN[TRUE];
    cb.rawBits ← ready;
    IF Inline.BITAND[ready, 100000B] = 0 THEN RETURN[TRUE]; -- Interrupt vanished!
    IF Inline.BITAND[ready, 40000B] # 0 THEN
      cb.status ← tooManyCollisions  -- Timeout
    ELSE cb.status ← ok;
    DicentraInputOutput.SetWakeupBits[device.outInterrupt];
    device.firstOut ← device.firstOut.next;
    IF device.firstOut # NIL THEN StartOutput[device];
    END;
  
  QueueInput: PUBLIC ENTRY PROCEDURE [
    device: DeviceHandle, buffer: LONG POINTER, length: CARDINAL, cb: ControlBlock] =
    BEGIN
    cb↑ ← [NIL, buffer, length, 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 ← FALSE] =
    BEGIN
    first, status: WORD;
    cb: ControlBlock = device.firstIn;
    words, slosh: CARDINAL;
    first ← status ← DicentraInputOutput.Input[@device.sunIn.data];
    IF Inline.BITAND[status, 100000B] # 0 THEN
      status ← DicentraInputOutput.Input[@device.sunIn.data];  -- Look ahead krock
    IF Inline.BITAND[status, 100000B] # 0 THEN RETURN[TRUE];
    words ← Inline.BITAND[status, 7777B];
    IF cb = NIL THEN
      BEGIN
      device.missed ← device.missed + 1;
      FOR i: CARDINAL IN [0..words) DO
        [] ← DicentraInputOutput.Input[@device.sunIn.data];  -- discard packet
        ENDLOOP;
      RETURN;
      END;
    SELECT TRUE FROM
      Inline.BITAND[status, 040000B] # 0 => cb.status ← overrun;
      Inline.BITAND[status, 030000B] = 030000B => cb.status ← crcAndBadAlignment;
      Inline.BITAND[status, 010000B] # 0 => cb.status ← crc;
      Inline.BITAND[status, 020000B] # 0 => cb.status ← badAlignmentButOkCrc;
      ENDCASE => cb.status ← ok;
    cb.rawBits ← status;
    device.firstIn ← device.firstIn.next;
    DicentraInputOutput.SetWakeupBits[device.inInterrupt];
    slosh ← MIN[words, cb.length];
    DicentraInputOutput.ReadHole[to: cb.buffer, from: @device.sunIn.data, words: slosh];
    IF cb.status = ok AND slosh < 3 THEN cb.status ← badAlignmentButOkCrc;
    IF slosh # words THEN
      BEGIN
      cb.status ← packetTooLong;
      FOR i: CARDINAL IN [slosh..words) DO
        [] ← DicentraInputOutput.Input[@device.sunIn.data];
        ENDLOOP;
      END;
    cb.used ← slosh-1;  -- CRC is included
    END;

  GetStatus: PUBLIC ENTRY PROCEDURE [cb: ControlBlock] RETURNS [EthernetOneFace.Status] =
    BEGIN
    IF interruptBits = 0 THEN [] ← CheckInterrupts[cb.device];
    RETURN[cb.status];
    END;
  
  GetRetries: PUBLIC PROCEDURE [cb: ControlBlock] RETURNS [CARDINAL] =
    BEGIN RETURN[0]; 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[ethernetOne] DO
        hit: BOOLEAN ← FALSE;
        FOR device: DeviceHandle ← first, device.next UNTIL device = nullDeviceHandle 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;
	maxBogus ← MAX[maxBogus, bogus];
	IF bogus > 100 THEN Runtime.CallDebugger["Dangling 3mb Interrupt"L];
	ENDLOOP;
      ENDLOOP;
    END;

  CheckInterrupts: INTERNAL PROCEDURE [device: DeviceHandle]
    RETURNS [hit: BOOLEAN ← FALSE, recvBogus, tranBogus: CARDINAL ← 0] =
    BEGIN
    DO
      ready: WORD = DicentraInputOutput.Input[@device.sunIn.ready];
      SELECT TRUE FROM
        Inline.BITAND[ready, 010000B] = 0 => EXIT;
        Inline.BITAND[ready, 020000B] # 0 =>
	  BEGIN
	  IF CheckRecvStatus[device] THEN recvBogus ← recvBogus + 1 ELSE hit ← TRUE;
	  END;
        Inline.BITAND[ready, 100000B] # 0 =>
	  BEGIN
	  IF CheckSendStatus[device] THEN tranBogus ← tranBogus + 1 ELSE hit ← TRUE;
	  END;
	ENDCASE =>
	  BEGIN
          again: WORD = DicentraInputOutput.Input[@device.sunIn.ready];
	  ERROR;
	  END;
      ENDLOOP;
    END;
  
  TurnOn: PUBLIC ENTRY PROCEDURE [
    device: DeviceHandle, host: Environment.Byte,
    inInterrupt: WORD, outInterrupt: WORD,
    globalState: EthernetOneFace.GlobalStatePtr] =
    BEGIN
    intLev: WORD = Inline.BITXOR[DicentraInputOutput.InterruptLevel[ethernetOne], 7B]*256;
    device.firstIn ← device.lastIn ← device.firstOut ← device.lastOut ← NIL;
    device.inInterrupt ← inInterrupt;
    device.outInterrupt ← outInterrupt;
    DicentraInputOutput.Output[134000B, @device.sunOut.control];  -- Init, no Int, reject
    FOR i: CARDINAL IN [0..256) DO
      DicentraInputOutput.Output[i, @device.sunOut.addressFilter];
      ENDLOOP;
    DicentraInputOutput.Output[114000B, @device.sunOut.control];  -- Init, no Int, accept
    DicentraInputOutput.Output[0, @device.sunOut.addressFilter];  -- broadcast
    DicentraInputOutput.Output[host, @device.sunOut.addressFilter];  -- me
    DicentraInputOutput.SetWakeupMask[interruptBits, ethernetOne];
    DicentraInputOutput.Output[0B+intLev, @device.sunOut.control];
    FOR i: CARDINAL IN [0..10000) DO
      [] ← DicentraInputOutput.Input[@device.sunIn.data];
      ENDLOOP;
    [] ← DicentraInputOutput.Input[@device.sunIn.switches];  -- Turn off Tran int
    END;

  TurnOff: PUBLIC ENTRY PROCEDURE [device: DeviceHandle] =
    BEGIN
    DicentraInputOutput.Output[114000B, @device.sunOut.control];  -- Init, no Int
    DicentraInputOutput.SetWakeupMask[interruptBits, ethernetOne];
    device.firstIn ← device.lastIn ← device.firstOut ← device.lastOut ← NIL;
    device.inInterrupt ← device.outInterrupt ← 0;
    END;

  GetEthernet1Address: PUBLIC PROCEDURE [device: DeviceHandle]
    RETURNS [net, host: [0..377B]] =
    BEGIN
    net ← 0;
    host ← DicentraInputOutput.Input[@device.sunIn.switches];
    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.firstSunBoard + i*offsetOfNextBoard;
      switches: WORD;
      device↑ ← [
        next: nullDeviceHandle,
        sunIn: thisBoard,
        sunOut: thisBoard,
        firstIn: NIL, lastIn: NIL,
        firstOut: NIL, lastOut: NIL,
        missed: 0,
        inInterrupt: 0, outInterrupt: 0 ];
      switches ← DicentraInputOutput.Input[@device.sunIn.switches];
      IF switches = 0 THEN EXIT;
      IF first = nullDeviceHandle THEN first ← device;
      IF last # nullDeviceHandle THEN last.next ← device;
      last ← device;
      DicentraInputOutput.Output[114000B, @device.sunOut.control];  -- Init, no Int
      ENDLOOP;
    DicentraInputOutput.SetNxmExpected[FALSE];
    IF Runtime.IsBound[LOOPHOLE[Process.Detach]] THEN
      Process.Detach[FORK DeMultiplexInterrupts[]];
    RemainingHeads.Start[];
    END;    

  END....