-- EthernetOneHeadDorado.mesa
-- Last Edited by: Taft, February 13, 1983 1:20 pm

-- Some modifications should be made to clean up multicast receiving, but they would cause
--  incompatibilities in existing volumes (Germ, Othello, CoPilot, etc.)

DIRECTORY
  DeviceCleanup USING [Item, Await],
  DoradoInputOutput USING [Input, IOAddress, Output, ResetEther],
  Environment USING [Byte],
  EthernetOneFace,
  EthernetOneFaceExtras USING [ HostArray ],
  HeadStartChain USING [Start],
  Inline USING [BITAND, HighByte, LowHalf],
  PilotMP USING [Code],
  ProcessorFace USING [SetMP];

EthernetOneHeadDorado: PROGRAM
  IMPORTS DeviceCleanup, DoradoInputOutput, RemainingStartChain: HeadStartChain, Inline,
    ProcessorFace
  EXPORTS EthernetOneFace, EthernetOneFaceExtras, HeadStartChain =
  BEGIN OPEN EthernetOneFace, EthernetOneFaceExtras;

  OCSB: TYPE = LONG POINTER TO OutputControllerStatusBlock;
  OutputControllerStatusBlock: TYPE = MACHINE DEPENDENT RECORD [
    next: ShortIOCB,
    interruptBit: WORD,
    minPacketSpacing: CARDINAL,
    -- words after here are unused by microcode
    last: IOCB]; -- last IOCB on output queue, valid if next#noIOCB

  ICSB: TYPE = LONG POINTER TO InputControllerStatusBlock;
  InputControllerStatusBlock: TYPE = MACHINE DEPENDENT RECORD [
    next: ShortIOCB,
    interruptBit: WORD,
    host: WORD,		-- if >=0, old-style single-host receive microcode will be used.
    missed: CARDINAL,
    -- words after here (except "hosts") are unused by microcode
    last: IOCB,	-- last IOCB on input queue, valid if next#noIOCB
    interstices: ARRAY [6..30B) OF WORD←NULL,	-- hosts start 30B beyond next
    				-- for compatibility with existing location of oCSB.
    hosts: HostArray]; -- current set of hosts to which receiver code responds

  IOCB: TYPE = LONG POINTER TO IOControlBlock;
  ShortIOCB: TYPE = POINTER TO IOControlBlock;
  IOControlBlock: TYPE = MACHINE DEPENDENT RECORD [
    next: ShortIOCB,
    completion: CompletionStatus,
    used: CARDINAL, -- input only
    load: CARDINAL, -- output only
    length: CARDINAL,
    buffer: LONG POINTER,
    -- words after here are unused by microcode
    direction: {input, output},
    fill: [0..77777B] ← NULL];

  CompletionStatus: TYPE = MACHINE DEPENDENT RECORD [
    microcode (0: 0..7): {pending (0), done (1), bufferOverflow (2), loadOverflow (3)},
    hardware (0: 8..15): SELECT OVERLAID * FROM
      input => [
        rxCollision (0: 8..8),
        fill1 (0: 9..9),
        rxDataLate (0: 10..10),
        fill2 (0: 11..11),
        rxCRCError (0: 12..12),
        fill3 (0: 13..14),
        rxIncTrans (0: 15..15): BOOLEAN],
      output => [
        rxOn (0: 8..8),
        txOn (0: 9..9),
        loopBack (0: 10..10),
        txCollision (0: 11..11),
        noWakeups (0: 12..12),
        txDataLate (0: 13..13),
        singleStep (0: 14..14),
        txFifoPE (0: 15..15): BOOLEAN],
      ENDCASE];
  
  multiHosts: CARDINAL = 177777B;

  inputErrorMask: input CompletionStatus = [
    microcode: pending, 
    hardware: input[rxCollision: TRUE, fill1: FALSE, rxDataLate: TRUE, fill2: FALSE,
      rxCRCError: TRUE, fill3: FALSE, rxIncTrans: TRUE]];
  outputErrorMask: output CompletionStatus = [
    microcode: pending,
    hardware: output[rxOn: FALSE, txOn: FALSE, loopBack: FALSE, txCollision: TRUE,
      noWakeups: FALSE, txDataLate: TRUE, singleStep: FALSE, txFifoPE: TRUE]];
  nullCompletion: CompletionStatus = LOOPHOLE[0];

  Command: TYPE = RECORD [WORD];
  enableInput: Command = [173377B];
  enableOutput: Command = [047777B];
  control: DoradoInputOutput.IOAddress = 16B;

  noIOCB: ShortIOCB = LOOPHOLE[0];

  -- This implementation has no concept of a "device", but something has to
  -- be exported to EthernetOneFace (see below)
  Device: TYPE = RECORD [WORD];

  Shorten: PROCEDURE [iocb: IOCB] RETURNS [ShortIOCB] = INLINE
    BEGIN
    -- Maybe we should check to be sure that the high half is zero
    RETURN[Inline.LowHalf[iocb]];
    END;


  -- EXPORTed TYPEs

  DeviceHandle: PUBLIC TYPE = Device;
  ControlBlockRecord: PUBLIC TYPE = IOControlBlock;

  -- EXPORTed variables

  nullDeviceHandle: PUBLIC DeviceHandle ← LOOPHOLE[123456B];
  globalStateSize: PUBLIC CARDINAL ← 0;
  controlBlockSize: PUBLIC CARDINAL ← SIZE[IOControlBlock];
  hearSelf: PUBLIC BOOLEAN ← TRUE;


  -- Non EXPORTed things.  Note that all the state information lives in the CSBs.

  oCSB: OCSB = LOOPHOLE[LONG[177610B]];
  iCSB: ICSB = LOOPHOLE[LONG[177600B]];

  QueueOutput: PUBLIC PROCEDURE [
    device: Device, buffer: LONG POINTER, length: CARDINAL, cb: IOCB] =
    BEGIN
    cb↑ ← [next: noIOCB, completion: nullCompletion, used: 0, load: 0, length: length,
      buffer: buffer, direction: output];
    IF oCSB.next # noIOCB THEN oCSB.last.next ← Shorten[cb];
    IF oCSB.next = noIOCB AND cb.completion = nullCompletion THEN
      BEGIN
      oCSB.next ← Shorten[cb];
      DoradoInputOutput.Output[enableOutput, control];
      END;
    oCSB.last ← cb;
    END;

  QueueInput: PUBLIC PROCEDURE [
    device: Device, buffer: LONG POINTER, length: CARDINAL, cb: IOCB] =
    BEGIN
    cb↑ ← [next: noIOCB, completion: nullCompletion, used: 0, load: 0, length: length,
      buffer: buffer, direction: input];
    IF iCSB.next # noIOCB THEN iCSB.last.next ← Shorten[cb];
    IF iCSB.next = noIOCB AND cb.completion = nullCompletion THEN
      iCSB.next ← Shorten[cb];
    iCSB.last ← cb;
    END;

  GetStatus: PUBLIC PROCEDURE [cb: IOCB] RETURNS [status: Status] =
    BEGIN OPEN cb.completion;
    RETURN[
      SELECT microcode FROM
	pending => pending,
	done => SELECT cb.direction FROM
           input => IF Inline.BITAND[cb.completion, inputErrorMask]=0 THEN ok
             ELSE SELECT TRUE FROM
               rxCollision => otherError,
               rxDataLate => overrun,
               rxCRCError => IF rxIncTrans THEN crcAndBadAlignment ELSE crc,
               rxIncTrans => badAlignmentButOkCrc,
               ENDCASE => otherError,
           output => IF Inline.BITAND[cb.completion, outputErrorMask]=0 THEN ok
             ELSE SELECT TRUE FROM
               txCollision => tooManyCollisions,
               txDataLate => underrun,
               txFifoPE => otherError,
               ENDCASE => otherError,
           ENDCASE => otherError,
	bufferOverflow => packetTooLong,
	loadOverflow => tooManyCollisions,
	ENDCASE => otherError];
    END;

  GetRetries: PUBLIC PROCEDURE [cb: IOCB] RETURNS [CARDINAL] =
    BEGIN
    RETURN[
      SELECT cb.load 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: IOCB] RETURNS [CARDINAL] =
    BEGIN RETURN[cb.used]; END;

  GetPacketsMissed: PUBLIC PROCEDURE [device: Device] RETURNS [CARDINAL] =
    BEGIN RETURN[iCSB.missed]; END;

  nonNullDeviceHandle: DeviceHandle = LOOPHOLE[22334]; -- # nullDeviceHandle
  GetNextDevice: PUBLIC PROCEDURE [device: Device] RETURNS [Device] =
    BEGIN
    RETURN[IF device=nullDeviceHandle THEN nonNullDeviceHandle ELSE nullDeviceHandle];
    END;

  GetEthernet1Address: PUBLIC PROCEDURE [device: Device]
    RETURNS [net, host: Environment.Byte] =
    BEGIN
    RETURN[net: 0, host: Inline.HighByte[DoradoInputOutput.Input[control]]];
    END;
  
  TurnOn: PUBLIC PROCEDURE [
    device: Device, host: Environment.Byte, inInterrupt, outInterrupt: WORD,
    globalState: GlobalStatePtr] =
    BEGIN
    DoradoInputOutput.ResetEther[];
    iCSB.next ← noIOCB;
    iCSB.interruptBit←inInterrupt;
    iCSB.missed←0;
    iCSB.last←NIL;
    -- If in multiHost mode, set all hosts true for promiscuous mode, or add specified
    --  host to currently-accepted list.
    IF iCSB.host#multiHosts THEN iCSB.host←host
    ELSE IF host = 0 THEN iCSB.hosts←ALL[TRUE]
    ELSE iCSB.hosts[host]←TRUE;
    oCSB↑ ← [next: noIOCB, interruptBit: outInterrupt, last: NIL,
      -- for now, always space output packets at least 500 microseconds apart
      minPacketSpacing: 500/32];
    DoradoInputOutput.Output[enableInput, control];
    END;

  TurnOff: PUBLIC PROCEDURE [device: Device] =
    BEGIN DoradoInputOutput.ResetEther[]; END;

  -- The next three procedure should probably be monitored, or be called from properly
  --  locked places.
  -- inputHosts is complete specification (including 0, if broadcasts are to be accepted),
  -- of the hosts whose input packets the microcode will accept.
  InputHosts: PUBLIC PROCEDURE [device: DeviceHandle,
  	inputHosts: LONG POINTER TO HostArray] = {
    iCSB.hosts←inputHosts↑;
    iCSB.host←multiHosts; }; -- enable multicast host array

  InputHost: PUBLIC PROCEDURE[device: DeviceHandle, host: Environment.Byte] = {
    iCSB.host←host; };     -- disable multicast host array, specify host.

  MulticastCapabilities: PUBLIC PROCEDURE[device: DeviceHandle]
	RETURNS [ canDo: BOOLEAN, multicastsEnabled: BOOLEAN] = {
	RETURN [ canDo: TRUE, multicastsEnabled: iCSB.host = multiHosts]; };

-- There is no way to remove a cleanup procedure yet, so we have a flag to avoid duplicates.

  alreadyInitializedCleanup: BOOLEAN ← FALSE;

  savedICSB: InputControllerStatusBlock;
  savedOCSB: OutputControllerStatusBlock;

  AddCleanup: PUBLIC PROCEDURE [device: Device] =
    BEGIN OPEN DeviceCleanup;
    item: Item;
    originalHost: Environment.Byte ← GetEthernet1Address[device].host;
    IF alreadyInitializedCleanup THEN RETURN;
    alreadyInitializedCleanup ← TRUE;
    DO
      SELECT Await[@item] FROM
	kill => BEGIN DoradoInputOutput.ResetEther[]; END;
	turnOff =>
	  BEGIN
	  DoradoInputOutput.ResetEther[];
	  savedICSB ← iCSB↑;
	  savedOCSB ← oCSB↑;
	  END;
	turnOn =>
	  BEGIN
	  -- Note that this does NOT really put things back together.
	  -- It simply smashes things to a safe state.  The intention is that the driver
	  -- will notice that nothing is happening and then call TurnOff+TurnOn to reset
	  -- things.  That allows Pilot to reset the GMT clock on the way back from the
	  -- debugger without getting tangled up with the normal Ethernet driver.
	  DoradoInputOutput.ResetEther[];
	  IF GetEthernet1Address[device].host#originalHost THEN
	    ErrorHalt[cHardwareConfigChanged];  -- perhaps InLoaded on another machine
	  iCSB↑ ← [
	      next: noIOCB, interruptBit: 0, host: savedICSB.host, hosts: savedICSB.hosts, 
	      -- Ugh, it would be nice if we could do something better
	      missed: 0, last: NIL];
	  oCSB↑ ← [
	    next: noIOCB, interruptBit: 0,
	    minPacketSpacing: savedOCSB.minPacketSpacing, last: NIL];
	  DoradoInputOutput.Output[enableInput, control];
	  END;
	ENDCASE;
      ENDLOOP;
    END;

  RemoveCleanup: PUBLIC PROCEDURE [device: Device] = BEGIN END;

  ErrorHalt: PROC [code: PilotMP.Code] = INLINE { ProcessorFace.SetMP[code]; DO ENDLOOP };
  
  -- Move this to PilotMP.mesa next time we get a chance to change it:
  cHardwareConfigChanged: PilotMP.Code = 933;

  -- Other routines to keep the rest of the world happy

  Start: PUBLIC PROCEDURE =
    BEGIN -- Start this module (and rest of chain)
    RemainingStartChain.Start[]
    END;

  END.  -- EthernetOneHeadDorado.

LOG
Time: August 20, 1980  2:01 PM By: BLyon, Action: renamed EthernetFace and EthernetHeadD0 to EthernetOneFace and EthernetOneHeadD0, resp.
Time: August 25, 1980  5:36 PM By: BLyon, Action: added [ ELSE h.board ← h.board + 1] clause to GetNextDevice. 
Time: September 4, 1980  11:02 PM By: HGM, Action: add hearSelf, use exported types. 
Time: September 14, 1980  6:36 AM By: HGM, Action: bug in bufferOverflow. 
Time: December 5, 1980  9:50 PM By: Taft, Action: convert for Dorado. 
 8-Jul-82 14:19:21  Taft  Cleanup routine checks for Ethernet address changing at turnOn time. 
Time: September 17, 1982 1:08 pm By: Swinehart, Action: add multicast input
	Requires PilotEther microcode that knows about multicast as well.