-- EthernetHeadDLion.mesa (last edited by: Sandman on: 18-Mar-81 17:20:56)
-- EthernetHeadDLion.mesa (last edited by: Blyon on : 22-Apr-81 17:43:34)

DIRECTORY
  Environment USING [Base, Long, wordsPerPage],
  Inline USING [BITAND, LowHalf],
  HeadStartChain USING [Start],
  DeviceCleanup USING [Item, Await],
  DLionInputOutput USING [IOPage, Input, Output],
  SpecialSystem USING [ProcessorID],
  EthernetFace;

EthernetHeadDLion: PROGRAM
  IMPORTS
    DLionInputOutput, DeviceCleanup, RemainingStartChain: HeadStartChain, Inline
  EXPORTS EthernetFace, HeadStartChain =
  BEGIN OPEN Inline, EthernetFace;

  -- 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.

  CSB: TYPE = LONG POINTER TO ControllerStatusBlock;
  ControllerStatusBlock: TYPE = MACHINE DEPENDENT RECORD [
    host: SpecialSystem.ProcessorID,
    input: ShortIocb,
    inputWakeups: WORD,
    output: ShortIocb,
    outputWakeups: WORD,
    missed: CARDINAL,
    lastInput: IOCB, -- last IOCB on input queue, valid if input#noIocb
    lastOutput: IOCB]; -- last IOCB on output queue, valid if output#noIocb
  EthernetCSBOffset: CARDINAL = 12*16 + 0; -- 0C0X

  IOCB: TYPE = LONG POINTER TO IOControlBlock;
  ShortIocb: TYPE = Environment.Base RELATIVE POINTER TO IOControlBlock;
  IOControlBlock: TYPE = MACHINE DEPENDENT RECORD [
    length: CARDINAL,
    buffer: LONG POINTER,
    mask: WORD, -- load location for output
    used: CARDINAL, -- input only
    completion: WORD,
    next: ShortIocb,
    spare: WORD]; -- NB: QuadWord Alignment

  Input: PROCEDURE [Register] RETURNS [Data] = LOOPHOLE[DLionInputOutput.Input];
  Output: PROCEDURE [Command, Register] = LOOPHOLE[DLionInputOutput.Output];
  Data: TYPE = RECORD [WORD];

  Command: TYPE = RECORD [WORD];
  eOff: Command = [0B];
  eEnableTrn: Command = [1B];
  eEnableRcv: Command = [1B];
  eTurnOff: Command = [2B];

  Register: TYPE = RECORD [[0..17B]];
  eEOCtl: Register = [14B];
  eEICtl: Register = [5B];
  eEStatus: Register = [1B];
  eHaveEther: Register = [12B];

  -- completion bits
  collision: WORD = 100B;
  underRun: WORD = 40B;
  notGoodAlign: WORD = 20B;
  overRun: WORD = 10B;
  notGoodCRC: WORD = 4B;
  notEvenLen: WORD = 2B;

  -- other status bits
  eRcvEnabled: WORD = 2000B;
  eTrnEnabled: WORD = 400B;

  noIocb: ShortIocb = LOOPHOLE[0];

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

  FixAlignment: PROCEDURE [iocb: IOCB] RETURNS [IOCB] = INLINE {
    LOOPHOLE[iocb, Environment.Long].lowbits ← BITAND[
      LOOPHOLE[iocb, Environment.Long].lowbits + 7, 177770B];
    RETURN[iocb]};

  MakeSureOff: PROC = INLINE {
    Output[eTurnOff, eEICtl];
    THROUGH [0..2) UNTIL BITAND[Input[eEStatus], eRcvEnabled + eTrnEnabled] = 0 DO
      ENDLOOP};

  -- EXPORTed TYPEs

  DeviceHandle: PUBLIC TYPE = RECORD [CARDINAL];
  ControlBlockRecord: PUBLIC TYPE = IOControlBlock;

  -- EXPORTed variables

  nullDeviceHandle: PUBLIC DeviceHandle ← [123456B];
  self: DeviceHandle = [0];
  globalStateSize: PUBLIC CARDINAL ← 0;
  controlBlockSize: PUBLIC CARDINAL;
  hearSelf: PUBLIC BOOLEAN ← FALSE;


  -- Non EXPORTed things.  Note that all the state information lives in the CSBs.
  csb: CSB ← DLionInputOutput.IOPage + EthernetCSBOffset;
  haveEther: BOOLEAN;


  QueueOutput: PUBLIC PROCEDURE [
    device: DeviceHandle, buffer: LONG POINTER, length: CARDINAL, cb: IOCB] = {
    count: CARDINAL;
    p: LONG POINTER ← buffer;
    IF device # self THEN RETURN;
    count ← length - 1;
    DO
      foo: UNSPECIFIED ← (p + count)↑;
      IF count < Environment.wordsPerPage THEN {foo ← p↑; EXIT};
      count ← count - Environment.wordsPerPage;
      ENDLOOP;
    cb ← FixAlignment[cb];
    cb↑ ← [
      next: noIocb, mask: 0, spare: 1, completion: 0, used: 0,
      length: length - 1, buffer: buffer];
    IF csb.output = noIocb THEN {
      -- new iocb, hardware idle
      csb.output ← Shorten[cb];
      Output[eEnableTrn, eEOCtl]} -- poke hardware
    ELSE {
      -- output active, add to end of chain
      csb.lastOutput.next ← Shorten[cb];
      IF csb.output = noIocb AND cb.completion = 0 THEN {
	-- oops, hardware went idle
	csb.output ← Shorten[cb];
	Output[eEnableTrn, eEOCtl]}}; -- poke hardware
    csb.lastOutput ← cb};

  QueueInput: PUBLIC PROCEDURE [
    device: DeviceHandle, buffer: LONG POINTER, length: CARDINAL, cb: IOCB] = {
    IF device # self THEN RETURN;
    cb ← FixAlignment[cb];
    cb↑ ← [
      next: noIocb, mask: 0, spare: 0, completion: 0, used: 0, length: length,
      buffer: buffer];
    IF csb.input # noIocb THEN csb.lastInput.next ← Shorten[cb];
    IF csb.input = noIocb AND cb.completion = 0 THEN {
      csb.input ← Shorten[cb]; Output[eEnableRcv, eEICtl]};
    csb.lastInput ← cb};

  GetStatus: PUBLIC PROCEDURE [cb: IOCB] RETURNS [status: Status] = {
    completion: WORD;
    cb ← FixAlignment[cb];
    IF (completion ← cb.completion) = 0 THEN RETURN[pending];
    IF cb.spare = 0 THEN {
      IF cb.length = 177777B THEN RETURN[packetTooLong];
      status ←
	SELECT BITAND[
	completion, notGoodAlign + overRun + notGoodCRC + notEvenLen] FROM
	  0 => ok,
	  notGoodAlign+notEvenLen => ok,  -- dippled bit case - someday drippled should be returned
	  notGoodAlign => badAlignmentButOkCrc,
	  notGoodCRC => crc,
	  notGoodCRC + notGoodAlign => crcAndBadAlignment,
	  overRun, overRun + notGoodAlign, overRun + notGoodCRC,
	    overRun + notGoodCRC + notGoodAlign => overrun,
	  ENDCASE => otherError;
      IF status = ok THEN {
	count: CARDINAL ← cb.length;
	p: LONG POINTER ← cb.buffer;
	DO
	  (p + count)↑ ← (p + count)↑;
	  IF count < Environment.wordsPerPage THEN {p↑ ← p↑; EXIT};
	  count ← count - Environment.wordsPerPage;
	  ENDLOOP};
      RETURN[status]};
    IF cb.spare = 1 THEN {
      IF cb.mask = 17777B THEN RETURN[tooManyCollisions];
      RETURN[
	SELECT BITAND[completion, collision + underRun] FROM
	  0 => ok,
	  underRun, underRun + collision => underrun,
	  ENDCASE => otherError]};
    RETURN[otherError]};

  GetRetries: PUBLIC PROCEDURE [cb: IOCB] RETURNS [CARDINAL] = {
    cb ← FixAlignment[cb];
    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,
	ENDCASE => 16]};

  GetPacketLength: PUBLIC PROCEDURE [cb: IOCB] RETURNS [CARDINAL] = {
    cb ← FixAlignment[cb]; RETURN[cb.used]};

  GetPacketsMissed: PUBLIC PROCEDURE [device: DeviceHandle] RETURNS [CARDINAL] = {
    RETURN[IF device # self THEN 0 ELSE csb.missed]};

  GetNextDevice: PUBLIC PROCEDURE [device: DeviceHandle] RETURNS [DeviceHandle] = {
    IF ~haveEther THEN RETURN[nullDeviceHandle];
    IF device = nullDeviceHandle THEN RETURN[self] ELSE RETURN[nullDeviceHandle]};

  TurnOn: PUBLIC PROCEDURE [
    device: DeviceHandle, host: SpecialSystem.ProcessorID,
    inInterrupt, outInterrupt: WORD, globalState: GlobalStatePtr] = {
    IF device # self THEN RETURN;
    MakeSureOff[];
    csb↑ ← [
      host: host, input: noIocb, inputWakeups: inInterrupt, output: noIocb,
      outputWakeups: outInterrupt, missed: 0, lastInput: NIL, lastOutput: NIL];
    Output[eEnableRcv, eEICtl]};

  TurnOff: PUBLIC PROCEDURE [device: DeviceHandle] = {
    IF device # self THEN RETURN; MakeSureOff[]};

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

  cleanupInitialized: BOOLEAN ← FALSE;

  savedCSB: ControllerStatusBlock;

  AddCleanup: PUBLIC PROCEDURE [device: DeviceHandle] = {
    OPEN DeviceCleanup;
    item: Item;
    oldHost: SpecialSystem.ProcessorID;
    IF cleanupInitialized OR ~haveEther THEN RETURN;
    cleanupInitialized ← TRUE;
    DO
      SELECT Await[@item] FROM
	kill => MakeSureOff[];
	turnOff => {MakeSureOff[]; savedCSB ← csb↑; oldHost ← csb.host};
	turnOn => {
	  -- 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.
	  MakeSureOff[];
	  csb↑ ← [
            host: oldHost, -- Ugh, it would be nice if we could do something better
	    input: noIocb, inputWakeups: 0, output: noIocb, outputWakeups: 0,
	    missed: 0, lastInput: NIL, lastOutput: NIL];
	  Output[eEnableRcv, eEICtl]};
	ENDCASE;
      ENDLOOP};

  RemoveCleanup: PUBLIC PROCEDURE [device: DeviceHandle] = {};

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

  Start: PUBLIC PROCEDURE = {
    IF (haveEther ← Input[eHaveEther] = Data[1]) THEN
      controlBlockSize ← SIZE[IOControlBlock] + 4
    ELSE controlBlockSize ← 0;
    RemainingStartChain.Start[]};

  END.  -- EthernetHeadDLion.

LOG
Time: October 29, 1980  11:16 AM By: Sandman, Action: create file. 
Time: 18-Mar-81 17:20:25 By: Sandman, Action: Removed minimum packet length test, general cleanup.