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