-- EthernetOneHeadD0.mesa    modified September 23, 1982 1:35 pm by Swinehart

DIRECTORY
  D0InputOutput USING [
    CSB, IOPage, ethernet1In, ethernet1Out,
    ControllerNumber, GetNextController, nullControllerNumber],
  DeviceCleanup USING [Item, Await],
  EthernetOneFace,
  EthernetOneFaceExtras,
  Environment USING [Byte],
  HeadStartChain USING [Start],
  Inline USING [DIVMOD, LowHalf],
  Mopcodes USING [zMISC, zSTARTIO],
  PilotMP USING [Code],
  ProcessorFace USING [SetMP];

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

-- These are the data structures that the microcode knows about.  If some other module (for example, a diagnostic) ever needs this info, it should probably get split out into a separate module.  For now, it is lumped in here to avoid cluttering up the world with yet another file.

OCSB: TYPE = LONG POINTER TO OutputControllerStatusBlock;
OutputControllerStatusBlock: TYPE = MACHINE DEPENDENT RECORD [ 
  reserved: WORD,
  next: ShortIOCB,
  unused1: WORD,
  unused2: WORD,
  interruptBit: WORD, -- 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 [ 
  reserved: WORD,
  next: ShortIOCB,
  host: LONG CARDINAL,
  interruptBit: WORD,
  missed: WORD,  -- for debugging only
  spare1: WORD,
  spare2: WORD,
  buffer: ARRAY [0..4) OF CARDINAL, -- words after here are unused by microcode
  last: IOCB ]; -- last IOCB on input queue, valid if next#noIOCB

IOCB: TYPE = LONG POINTER TO IOControlBlock;
-- Beware that you don't automatically lengthen one of these.  If you do you will end up with a pointer into your MDS rather then the first 64K where the IOCBs live.  That won't work unless your MDS is also the first 64K.
ShortIOCB: TYPE = POINTER TO IOControlBlock;
IOControlBlock: TYPE = MACHINE DEPENDENT RECORD [
  next: ShortIOCB,
  mask: WORD,
  spare: WORD,
  completion: WORD,
  used: CARDINAL,  -- input only
  length: CARDINAL,
  buffer: LONG POINTER ];  -- NB: Must be QuadWord Aligned

StartIO: PROCEDURE [SioParameter] =
    MACHINE CODE BEGIN Mopcodes.zSTARTIO END;

SioParameter: TYPE = RECORD [WORD];
  firstFixupOutput: SioParameter = [1B];
  firstFixupInput: SioParameter = [2B];
  firstReset: SioParameter = [3B];
  secondFixupOutput: SioParameter = [4B];
  secondFixupInput: SioParameter = [10B];
  secondReset: SioParameter = [14B];

-- On old boards, output reg 0 of either task is control word for both tasks
-- On new boards, you must direct the output to the correct half
-- Input from reg 0 is device id
-- Input from reg 1 is net&host number switches

Output: PROCEDURE [Command, Register] =
    MACHINE CODE BEGIN Mopcodes.zMISC, 6; END;
Input: PROCEDURE [Register] RETURNS [WORD] =
    MACHINE CODE BEGIN Mopcodes.zMISC, 5; END;

Command: TYPE = RECORD [WORD];
  enableInput: Command = [220B];
  enableOutput: Command = [103B];

Register: TYPE = MACHINE DEPENDENT RECORD [
  zero: [0..377B] ← 0,
  controller: D0InputOutput.ControllerNumber,
  register: [0..17B]];

-- completion bits
processed: WORD = 040000B;
error: WORD = 020000B;
hardwareError: WORD = 010000B;
fragment, tooLong: WORD = 004000B;
loadOverflow: WORD = 002000B;
nothingYet: WORD = 0;
-- 001000 and 000400 are unused so far

-- Hardware error bits:
-- 001: Bad Alignment
-- 002: Bad Parity (between mem and shifter)
-- 004: Memory Data Fault
-- 010: CRC
-- 020: Collision
-- 040: Input Overrun
-- 100: Output Overrun
-- 200: Jam

noIOCB: ShortIOCB = LOOPHOLE[0];

Device: TYPE = RECORD [
  board: Board,
  in, out: D0InputOutput.ControllerNumber];

Board: TYPE = [0..2);

ICSBFronDevice: PROCEDURE [device: Device] RETURNS [ICSB] = INLINE
  BEGIN
  RETURN[LOOPHOLE[@D0InputOutput.IOPage[device.in]]];
  END;

OCSBFronDevice: PROCEDURE [device: Device] RETURNS [OCSB] = INLINE
  BEGIN
  RETURN[LOOPHOLE[@D0InputOutput.IOPage[device.out]]];
  END;

ControlRegister: PROCEDURE [c: D0InputOutput.ControllerNumber] RETURNS [Register] = INLINE
  BEGIN
  RETURN[[0,c,0]];  -- Register 0 is the control register
  END;

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.
 
fixupInputBits: ARRAY Board OF SioParameter = [firstFixupInput, secondFixupInput];
fixupOutputBits: ARRAY Board OF SioParameter = [firstFixupOutput, secondFixupOutput];
resetBits: ARRAY Board OF SioParameter = [firstReset, secondReset];

QueueOutput: PUBLIC PROCEDURE [
  device: Device, buffer: LONG POINTER, length: CARDINAL, cb: IOCB] =
  BEGIN
  out: OCSB = OCSBFronDevice[device];
  cb↑ ← [
    next: noIOCB,
    mask: 0,
    spare: 0,
    completion: 0,
    used: 0,
    length: length,
    buffer: buffer ];
  IF out.next=noIOCB THEN
    BEGIN  -- new iocb, hardware idle
    out.next ← Shorten[cb];
    Output[enableOutput,ControlRegister[device.out]];  -- poke hardware
    END
  ELSE
    BEGIN  -- output active, add to end of chain
    out.last.next ← Shorten[cb];
    IF out.next=noIOCB AND cb.completion=0 THEN
      BEGIN  -- oops, hardware went idle
      out.next ← Shorten[cb];
      Output[enableOutput,ControlRegister[device.out]];  -- poke hardware
      END;
    END;
  out.last ← cb;
  END;

QueueInput: PUBLIC PROCEDURE [
    device: Device, buffer: LONG POINTER, length: CARDINAL, cb: IOCB] =
  BEGIN
  in: ICSB = ICSBFronDevice[device];
  cb↑ ← [
    next: noIOCB,
    mask: 0,
    spare: 0,
    completion: 0,
    used: 0,
    length: length,
    buffer: buffer ];
  IF in.next#noIOCB THEN in.last.next ← Shorten[cb];
  IF in.next=noIOCB AND cb.completion=0 THEN
    BEGIN
    in.next ← Shorten[cb];
    -- Poke the microcode so it will notice this buffer, delete the next line when the microcode is fixed
    StartIO[fixupInputBits[device.board]];
    Output[enableInput,ControlRegister[device.in]];
    END;
  in.last ← cb;
  END;

GetStatus: PUBLIC PROCEDURE [cb: IOCB] RETURNS [status: Status] =
  BEGIN
  RETURN [
    SELECT cb.completion FROM
      0 => pending,
      40000B => ok,
      62000B => tooManyCollisions,
      61000B => packetTooLong,
      70001B => badAlignmentButOkCrc,
      70010B => crc,
      70011B => crcAndBadAlignment,
      70040B, 70041B, 70050B, 70051B => overrun,
      70100B, 70120B => underrun,
      ENDCASE => otherError ];
  END;

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

GetPacketsMissed: PUBLIC PROCEDURE [device: Device] RETURNS [CARDINAL] =
  BEGIN
  RETURN [ICSBFronDevice[device].missed];
  END;

GetNextDevice: PUBLIC PROCEDURE [device: Device] RETURNS [Device] =
  BEGIN OPEN D0InputOutput;
  IF device=nullDeviceHandle THEN
    device ← [0,nullControllerNumber,nullControllerNumber]
  ELSE device.board ← device.board + 1;
  device.in ← GetNextController[ethernet1In,device.in];
  device.out ← GetNextController[ethernet1Out,device.out];
  IF device.in=nullControllerNumber OR device.out=nullControllerNumber THEN
    RETURN[nullDeviceHandle];
  RETURN[device];
  END;

GetEthernet1Address: PUBLIC PROCEDURE [device: Device] RETURNS [net, host: Environment.Byte] =
  BEGIN
  reg: Register = [controller: device.in, register: 1];
  [net,host] ← Inline.DIVMOD[Input[reg],400B];
  END;

TurnOn: PUBLIC PROCEDURE [
    device: Device,
    host: Environment.Byte,
    inInterrupt, outInterrupt: WORD,
    globalState: GlobalStatePtr] =
  BEGIN
  board: Board = device.board;
  out: OCSB = OCSBFronDevice[device];
  in: ICSB = ICSBFronDevice[device];
  StartIO[resetBits[board]];
  out↑ ← [
    reserved: 0,
    next: noIOCB,
    unused1: 0,
    unused2: 0,
    interruptBit: outInterrupt,
    last: NIL ];
  in↑ ← [
    reserved: 0,
    next: noIOCB,
    host: host,
    interruptBit: inInterrupt,
    missed: 0,
    spare1: 0,
    spare2: 0,
    buffer: [0,0,0,0],
    last: NIL ];
  StartIO[fixupInputBits[board]];
  StartIO[fixupOutputBits[board]];
  Output[enableInput,ControlRegister[device.in]];
  END;

TurnOff: PUBLIC PROCEDURE [device: Device] =
  BEGIN
  StartIO[resetBits[device.board]];
  END;

-- InputHosts and InputHost would support input packets from multiple hosts.  They are not
--   supported in this implementation.

  InputHosts: PUBLIC PROCEDURE [device: DeviceHandle,
  	inputHosts: LONG POINTER TO EthernetOneFaceExtras.HostArray] = { ERROR; };

  InputHost: PUBLIC PROCEDURE[device: DeviceHandle,
  	host: Environment.Byte] = { ERROR; };

  MulticastCapabilities: PUBLIC PROCEDURE[device: DeviceHandle]
	RETURNS [ canDo: BOOLEAN, multicastsEnabled: BOOLEAN] = {
	RETURN [ canDo: FALSE, multicastsEnabled: FALSE]; };

-- There is no way to remove a cleanup procedure yet, so we have a flag to avoid duplicates.
alreadyInitializedCleanup: ARRAY Board OF BOOLEAN ← ALL[FALSE];

savedICSB: InputControllerStatusBlock;
savedOCSB: OutputControllerStatusBlock;

AddCleanup: PUBLIC PROCEDURE [device: Device] =
  BEGIN OPEN DeviceCleanup;
  item: Item;
  board: Board = device.board;
  out: OCSB = OCSBFronDevice[device];
  in: ICSB = ICSBFronDevice[device];
  oldHost: LONG CARDINAL;
  originalHost: Environment.Byte;
  IF alreadyInitializedCleanup[device.board] THEN RETURN;
  alreadyInitializedCleanup[device.board] ← TRUE;
  originalHost ← GetEthernet1Address[device].host;
  DO
    SELECT Await[@item] FROM
      kill =>
        BEGIN
        StartIO[resetBits[board]];
        END;
      turnOff =>
        BEGIN
        StartIO[resetBits[board]];
        savedICSB ← in↑;
        savedOCSB ← out↑;
        oldHost ← in.host;
        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.
        StartIO[resetBits[board]];
	IF GetEthernet1Address[device].host#originalHost THEN
	  ErrorHalt[cHardwareConfigChanged];  -- perhaps InLoaded on another machine
        out↑ ← [
          reserved: 0,
          next: noIOCB,
          unused1: 0,
          unused2: 0,
          interruptBit: 0,
          last: NIL ];
        in↑ ← [
          reserved: 0,
          next: noIOCB,
          host: oldHost,  -- Ugh, it would be nice if we could do something better
          interruptBit: 0,
          missed: 0,
          spare1: 0,
          spare2: 0,
          buffer: [0,0,0,0],
          last: NIL ];
        StartIO[fixupInputBits[board]];
        StartIO[fixupOutputBits[board]];
        Output[enableInput,ControlRegister[device.in]];
        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.  -- EthernetOneHeadD0.

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. 
 8-Jul-82 14:50:19  Taft  Cleanup routine checks for Ethernet address changing at turnOn time. 
Time: September 23, 1982 1:42 pm By: Swinehart, Action:  Add Dummy multicast functions.