-- EthernetHeadD0.mesa (last edited by: HGM on: September 14, 1980 6:33 AM)

DIRECTORY
Inline USING [LowHalf],
HeadStartChain USING [Start],
DeviceCleanup USING [Item, Await],
D0InputOutput USING [
CSB, IOPage, ethernetIn, ethernetOut,
ControllerNumber, GetNextController, nullControllerNumber],
Mopcodes USING [zMISC, zSTARTIO],
SpecialSystem USING [ProcessorID],
EthernetFace;

EthernetHeadD0: PROGRAM
IMPORTS D0InputOutput, DeviceCleanup, RemainingStartChain: HeadStartChain, Inline
EXPORTS EthernetFace, HeadStartChain =
BEGIN OPEN 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.

OCSB: TYPE = LONG POINTER TO OutputControllerStatusBlock;
OutputControllerStatusBlock: TYPE = MACHINE DEPENDENT RECORD [
next: ShortIOCB,
unused1: WORD,
unused2: WORD,
unused3: 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 [
next: ShortIOCB,
host: SpecialSystem.ProcessorID,
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 = [20B];
firstFixupInput: SioParameter = [40B];
firstReset: SioParameter = [60B];
secondFixupOutput: SioParameter = [100B];
secondFixupInput: SioParameter = [200B];
secondReset: SioParameter = [300B];

-- Input from reg 0 is device id

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: Memory Data Fault (OFault)
-- 002: Collision
-- 004: Output Underrrun
-- 010: Bad Parity (between mem and shifter)
-- 020: CRC
-- 040: Jam
-- 100: Input Overrun
-- 200: Bad Alignment

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];
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,
70200B => badAlignmentButOkCrc,
70020B => crc,
70220B => crcAndBadAlignment,
70100B, 70120B, 70300B, 70320B => overrun,
70004B, 70006B => 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[ethernetIn,device.in];
device.out ← GetNextController[ethernetOut,device.out];
IF device.in=nullControllerNumber OR device.out=nullControllerNumber THEN
RETURN[nullDeviceHandle];
RETURN[device];
END;

TurnOn: PUBLIC PROCEDURE [
device: Device,
host: SpecialSystem.ProcessorID,
inInterrupt, outInterrupt: WORD,
globalState: GlobalStatePtr] =
BEGIN
board: Board = device.board;
out: OCSB = OCSBFronDevice[device];
in: ICSB = ICSBFronDevice[device];
StartIO[resetBits[board]];
out↑ ← [
next: noIOCB,
unused1: 0,
unused2: 0,
unused3: 0,
interruptBit: outInterrupt,
last: NIL ];
in↑ ← [
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;

-- 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: SpecialSystem.ProcessorID;
IF alreadyInitializedCleanup[device.board] THEN RETURN;
alreadyInitializedCleanup[device.board] ← TRUE;
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]];
out↑ ← [
next: noIOCB,
unused1: 0,
unused2: 0,
unused3: 0,
interruptBit: 0,
last: NIL ];
in↑ ← [
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;

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

LOG
Time: September 4, 1980 11:02 PM By: HGM, Action: create file.
Time: September 14, 1980 6:33 AM By: HGM, Action: buffer overflow bug.