-- File: AltoEthernetDriver.mesa, Last Edit: HGM March 27, 1981 12:17 AM DIRECTORY BcplOps USING [CleanupReason], ImageDefs USING [ CleanupItem, AddCleanupProcedure, RemoveCleanupProcedure, AllReasons], Inline USING [BITSHIFT, BITAND, HighHalf, LowHalf, LowByte], MiscDefs USING [Zero], Process USING [ Detach, SetPriority, DisableTimeout, SetTimeout, MsecToTicks, SecondsToTicks], Runtime USING [SelfDestruct], Storage USING [Free, Node], CommFlags USING [doDebug, doStats], CommUtilDefs USING [ CopyLong, GetEthernetHostNumber, InterruptLevel, AddInterruptHandler, RemoveInterruptHandler], StatsDefs USING [StatBump, StatIncr, StatCounterIndex], AltoRam USING [Shorten, GetTicks, msPerTick], AltoEthernetDefs, DriverDefs USING [ GetGiantVector, GiantVector, Glitch, GetInputBuffer, Network, NetworkObject, AddDeviceToChain, PutOnGlobalDoneQueue, PutOnGlobalInputQueue], BufferDefs, OISCP USING [allHostIDs], PupTypes USING [allHosts, PupErrorCode, PupHostID], DriverTypes USING [ Byte, ethernetEncapsulationOffset, ethernetEncapsulationBytes], SpecialSystem USING [GetProcessorID, HostNumber]; AltoEthernetDriver: MONITOR IMPORTS ImageDefs, Inline, MiscDefs, Process, Runtime, Storage, SpecialSystem, AltoRam, CommUtilDefs, StatsDefs, DriverDefs, BufferDefs, AltoEthernetDefs EXPORTS BufferDefs, DriverDefs SHARES BufferDefs, DriverTypes, SpecialSystem = BEGIN OPEN StatsDefs, BufferDefs, DriverDefs, AltoEthernetDefs; -- EXPORTed TYPEs Network: PUBLIC TYPE = DriverDefs.Network; ethernetEncapsulationOffset: CARDINAL = DriverTypes.ethernetEncapsulationOffset; ethernetEncapsulationBytes: CARDINAL = DriverTypes.ethernetEncapsulationBytes; myDevice: EthernetDeviceBlockHandle ← NIL; cleanupItem: ImageDefs.CleanupItem ← [, ImageDefs.AllReasons, Broom]; hardProcess: PROCESS; hardware: CONDITION; watcherProcess: PROCESS; pleaseStop: BOOLEAN; timer: CONDITION; nextBufferPointer: POINTER; currentInputBuffer, nextInputBuffer: Buffer; outputQueue: QueueObject; currentOutputBuffer: Buffer; timeSendStarted: CARDINAL; timeLastRecv: CARDINAL; myNetwork: DriverDefs.NetworkObject ← [decapsulateBuffer: DecapsulateBuffer, encapsulatePup: EncapsulatePup, encapsulateOis: EncapsulateOis, sendBuffer: SendBuffer, forwardBuffer: ForwardBuffer, activateDriver: ActivateDriver, deactivateDriver: DeactivateDriver, deleteDriver: DeleteDriver, interrupt: Interrupt, index:, device: ethernetOne, alive: TRUE, speed: 3000, buffers:, spare:, netNumber:, hostNumber:, next: NIL, pupStats: NIL, stats: NIL]; ImpossibleEndcase: PUBLIC ERROR = CODE; FunnyRetransmissionMask: PUBLIC ERROR = CODE; ZeroLengthBuffer: PUBLIC ERROR = CODE; UnreasonableHardwareStatus: PUBLIC ERROR = CODE; QueueScrambled: PUBLIC ERROR = CODE; MachineIDTooBigForEthernet: PUBLIC ERROR = CODE; DriverNotActive: PUBLIC ERROR = CODE; DriverAlreadyActive: PUBLIC ERROR = CODE; NoEthernetBoard: PUBLIC ERROR = CODE; CantSwitchMachinesWhileEtherentDriverIsActive: PUBLIC ERROR = CODE; CantMakImageWhileEtherentDriverIsActive: PUBLIC ERROR = CODE; OnlyThreeDriversArePossible: PUBLIC ERROR = CODE; HyperspaceNotSupported: PUBLIC ERROR = CODE; BufferNotAlignedProperly: PUBLIC ERROR = CODE; -- things needed for chained input first, last: EthernetDeviceBlockHandle; headBuffer, tailBuffer: Buffer; notChained: BOOLEAN; numberOfInputBuffers: CARDINAL = 4; interruptLevel: CommUtilDefs.InterruptLevel; interruptBit: WORD; -- BITSHIFT[1,interruptLevel]; inputCommand: SioParameter; outputCommand: SioParameter; resetCommand: SioParameter; etherStats: POINTER TO EtherStatsInfo; Interrupt: ENTRY PROCEDURE = BEGIN b, temporaryBuffer: Buffer; savedWordsLeft: CARDINAL; savedPostData: EthernetPost; device: EthernetDeviceBlockHandle = myDevice; -- things needed for chained input doMoreInput: BOOLEAN; Process.SetPriority[4]; UNTIL pleaseStop DO IF device.postData # ethernetNotPosted OR (~notChained AND first.postData # ethernetNotPosted) THEN BEGIN IF CommFlags.doStats THEN StatIncr[statEtherInterruptDuringInterrupt]; END ELSE BEGIN DO WAIT hardware; IF device.postData # ethernetNotPosted OR (~notChained AND first.postData # ethernetNotPosted) THEN EXIT; IF CommFlags.doStats THEN StatIncr[statEtherMissingStatus]; ENDLOOP; END; SELECT TRUE FROM (~notChained AND first.postData # ethernetNotPosted) => BEGIN savedPostData ← first.postData; savedWordsLeft ← first.wordsLeft; first ← first.inputControlBlock; currentInputBuffer ← headBuffer; headBuffer ← headBuffer.next; doMoreInput ← TRUE; END; device.postData # ethernetNotPosted => BEGIN savedPostData ← device.postData; savedWordsLeft ← device.wordsLeft; device.postData ← ethernetNotPosted; doMoreInput ← notChained; END; ENDCASE => Glitch[ImpossibleEndcase]; IF notChained THEN BEGIN -- Turn on input as soon as we can so we don't drop as many packets. device.inputBuffer.count ← nextInputBuffer.length - ethernetEncapsulationOffset; device.inputBuffer.pointer ← nextBufferPointer; StartIO[inputCommand]; -- input now running, can relax now END; SELECT savedPostData.microcodeStatus FROM inputDone => IF savedPostData.hardwareStatus = hardwareAOK THEN BEGIN IF (temporaryBuffer ← GetInputBuffer[]) # NIL THEN BEGIN temporaryBuffer.device ← ethernetOne; -- should unwiredown packet, not now under interface b ← currentInputBuffer; b.length ← (b.length - ethernetEncapsulationOffset) - savedWordsLeft; b.network ← LONG[@myNetwork]; IF CommFlags.doStats THEN BEGIN etherStats.packetsRecv ← etherStats.packetsRecv + 1; StatIncr[statEtherPacketsReceived]; StatBump[statEtherWordsReceived, b.length]; END; PutOnGlobalInputQueue[b]; currentInputBuffer ← temporaryBuffer; IF CommFlags.doStats AND currentOutputBuffer # NIL THEN StatIncr[statEtherInUnderOut]; END ELSE IF CommFlags.doStats THEN BEGIN etherStats.inputOff ← etherStats.inputOff + 1; StatIncr[statEtherEmptyFreeQueue]; END; END ELSE IF CommFlags.doStats THEN BEGIN etherStats.badRecvStatus ← etherStats.badRecvStatus + 1; SELECT 377B - savedPostData.hardwareStatus FROM 1B => StatIncr[statEtherReceivedNot16]; 10B => StatIncr[statEtherReceivedBadCRC]; 11B => StatIncr[statEtherReceivedNot16BadCRC]; 40B, 50B => BEGIN etherStats.overruns ← etherStats.overruns + 1; StatIncr[statEtherReceivedOverrun]; END; 6B, 7B, 16B, 17B => StatIncr[statEtherReceivedKlobberedByReset]; ENDCASE => StatIncr[statEtherReceivedBadStatus]; END; outputDone => IF savedPostData.hardwareStatus = hardwareAOK THEN BEGIN device.outputBuffer ← [0, NIL0]; PutOnGlobalDoneQueue[currentOutputBuffer]; currentOutputBuffer ← NIL; IF CommFlags.doStats THEN BEGIN tries: CARDINAL; statEtherSendsCollision1: StatsDefs.StatCounterIndex = statEtherSendsCollision1; first: CARDINAL = LOOPHOLE[statEtherSendsCollision1]; etherStats.packetsSent ← etherStats.packetsSent + 1; SELECT (tries ← device.retransmissionMask) FROM 1 => tries ← 0; 3 => tries ← 1; 7 => tries ← 2; 17B => tries ← 3; 37B => tries ← 4; 77B => tries ← 5; 177B => tries ← 6; 377B => tries ← 7; 777B => tries ← 8; 1777B => tries ← 9; 3777B => tries ← 10; 7777B => tries ← 11; 17777B => tries ← 12; 37777B => tries ← 13; 77777B => tries ← 14; 177777B => tries ← 15; ENDCASE => Glitch[FunnyRetransmissionMask]; IF tries # 0 THEN StatIncr[LOOPHOLE[first + tries]]; etherStats.loadTable[tries] ← etherStats.loadTable[tries] + 1; END; END ELSE BEGIN IF CommFlags.doStats THEN etherStats.badSendSatus ← etherStats.badSendSatus + 1; IF (b ← currentOutputBuffer) # NIL AND (AltoRam.GetTicks[] - timeSendStarted) > 500/AltoRam.msPerTick THEN BEGIN -- requeue it so one packet won't hog the interface device.outputBuffer ← [0, NIL0]; PutOnGlobalDoneQueue[currentOutputBuffer]; currentOutputBuffer ← NIL; IF CommFlags.doStats THEN StatIncr[statPacketsStuckInOutput]; END ELSE IF CommFlags.doStats THEN StatIncr[statEtherSendBadStatus]; END; inputBufferOverflow => IF CommFlags.doStats THEN StatIncr[statEtherReceivedTooLong]; outputLoadOverflow => BEGIN -- requeue it so one packet won't hog the interface device.outputBuffer ← [0, NIL0]; PutOnGlobalDoneQueue[currentOutputBuffer]; currentOutputBuffer ← NIL; IF CommFlags.doStats THEN BEGIN etherStats.loadTable[16] ← etherStats.loadTable[16] + 1; StatIncr[statEtherSendsCollisionLoadOverflow]; END; END; zeroLengthBuffer => Glitch[ZeroLengthBuffer]; hardwareReset => -- two reasons for getting here: -- 1) getting back from debugger -- 2) stuck output packet (transciever not plugged in) BEGIN IF (b ← currentOutputBuffer) # NIL AND (AltoRam.GetTicks[] - timeSendStarted) > 500/AltoRam.msPerTick THEN BEGIN -- requeue it so one packet won't hog the interface -- Watch the order of these stores device.outputBuffer.pointer ← NIL0; device.outputBuffer.count ← 0; PutOnGlobalDoneQueue[currentOutputBuffer]; currentOutputBuffer ← NIL; IF CommFlags.doStats THEN StatIncr[statPacketsStuckInOutput]; END ELSE IF CommFlags.doStats THEN StatIncr[statInterfaceReset]; IF ~notChained AND device.inputControlBlock # NIL0 THEN BEGIN -- restart input which got shot down -- this could screwup if the microcode got restarted device.inputControlBlock.inputBuffer.pointer↑ ← 0; StartIO[inputCommand]; END; END; interfaceBroken => Glitch[UnreasonableHardwareStatus]; ENDCASE => Glitch[ImpossibleEndcase]; IF doMoreInput THEN BEGIN -- The normal mode uses two buffers for input. The current one, and a hot standby. -- Way up at the beginning of this loop, a read was started into the standby buffer. -- At this point, currentInputBuffer has a buffer to be setup for use next time. -- If we just finished a read, -- then it is a new one left there by the inputDone processing, -- (or the old one if we are recycling it because we couldn't get a new one) -- otherwise, we are reusing the previous one. temporaryBuffer ← nextInputBuffer; nextInputBuffer ← currentInputBuffer; currentInputBuffer ← temporaryBuffer; nextBufferPointer ← AltoRam.Shorten[ @nextInputBuffer.encapsulation + ethernetEncapsulationOffset]; nextBufferPointer↑ ← 0; IF ~notChained THEN BEGIN temp: EthernetDeviceBlockHandle; temp ← AltoRam.Shorten[nextInputBuffer.iocbChain]; temp↑ ← [postData: ethernetNotPosted, interruptBit: interruptBit, wordsLeft: 0, retransmissionMask: 0, inputBuffer: [nextInputBuffer.length - ethernetEncapsulationOffset, AltoRam.Shorten[ @nextInputBuffer.encapsulation + ethernetEncapsulationOffset]], outputBuffer: [0, NIL0], hostNumber: 0, inputControlBlock: NIL0]; last.inputControlBlock ← temp; last ← temp; tailBuffer.next ← nextInputBuffer; tailBuffer ← nextInputBuffer; IF device.inputControlBlock = NIL0 AND temp.postData = ethernetNotPosted THEN BEGIN -- adding a new buffer device.inputControlBlock ← temp; StartIO[inputCommand]; StatIncr[statEtherEmptyInputChain]; END; END; END; -- see if there is output to do, there will be some for several reasons: -- 1 output finished, and another buffer was queued -- 2 input finished, and leftover outputBuffer (in under out) -- 3 input finished, and somebody didn't kick us because a packet was pouring in -- 4 (chained) we got some input first BEGIN IF currentOutputBuffer # NIL THEN GOTO SendThisOne; IF BufferDefs.QueueEmpty[@outputQueue] THEN GOTO NothingToSend; currentOutputBuffer ← Dequeue[@outputQueue]; timeSendStarted ← AltoRam.GetTicks[]; IF CommFlags.doDebug AND currentOutputBuffer = NIL THEN Glitch[QueueScrambled]; IF CommFlags.doStats THEN StatIncr[statEtherSendFromOutputQueue]; GOTO SendThisOne; EXITS SendThisOne => BEGIN -- start output if not already input coming in IF notChained THEN BEGIN IF device.inputBuffer.pointer↑ # 0 THEN GOTO PouringIn; device.interruptBit ← 0; -- inhibit interrupts for reset device.postData ← ethernetNotPosted; -- Beware of hardware/microcode screwup -- it seems to hang while sending, after a collision, until a gateway packet arrives IF CommFlags.doStats THEN StartIO[resetCommand]; -- should post immediately UNTIL device.postData # ethernetNotPosted DO IF CommFlags.doStats THEN StatIncr[statResetDidntPost]; StartIO[resetCommand]; ENDLOOP; device.interruptBit ← interruptBit; --interrupts back on device.postData ← ethernetNotPosted; END ELSE BEGIN -- Don't reverse the order of these tests. It might be about ready to post. IF device.outputBuffer.pointer # NIL THEN GOTO AlreadySending; IF device.postData # ethernetNotPosted THEN GOTO DontSendAgain; END; device.retransmissionMask ← 0; device.outputBuffer.count ← currentOutputBuffer.length; device.outputBuffer.pointer ← AltoRam.Shorten[ @currentOutputBuffer.encapsulation + ethernetEncapsulationOffset]; IF ~notChained THEN BEGIN now: EthernetDeviceBlockHandle = device.inputControlBlock; IF now # NIL AND now.inputBuffer.pointer↑ # 0 THEN GOTO PouringIn; IF device.retransmissionMask # 0 THEN GOTO AlreadySending; END; StartIO[outputCommand]; EXITS PouringIn, AlreadySending, DontSendAgain => NULL; END; NothingToSend => NULL; END; ENDLOOP; END; -- Interrupt Watcher: PROCEDURE = BEGIN UNTIL pleaseStop DO THROUGH [0..25) DO IF WatcherCheck[] THEN EXIT; -- If the post location is not zero, an interrupt should be pending. Since the interrupt routine is higher priority than we are, it should get processed before we can see it. If we get here, an interrupt has probably been lost. It could have been generated between the time we started decoding the instruction and the time that the data is actually fetched. That is why we look at the post location several times. Of course, if it is still not zero when we look again, it could be a new interrupt that has just arrived. REPEAT FINISHED => BEGIN IF CommFlags.doStats THEN StatIncr[statEtherLostInterrupts]; WatcherNotify[]; END; ENDLOOP; IF currentOutputBuffer # NIL AND (AltoRam.GetTicks[] - timeSendStarted) > 250/AltoRam.msPerTick THEN StartIO[resetCommand]; -- interrupt code will flush it IF (AltoRam.GetTicks[] - timeLastRecv) > 30000/AltoRam.msPerTick THEN BEGIN -- Blast receiver since it may be stuck. This might kill a good packet. -- This shouldn't hurt (much) if thing are ok since it won't happen very often. timeLastRecv ← AltoRam.GetTicks[]; IF CommFlags.doStats THEN StatIncr[statInputIdle]; StartIO[resetCommand]; END; WatcherWait[]; ENDLOOP; END; WatcherCheck: ENTRY PROCEDURE RETURNS [ok: BOOLEAN] = BEGIN RETURN[ myDevice.postData = ethernetNotPosted AND (notChained OR first.postData = ethernetNotPosted)]; END; WatcherWait: ENTRY PROCEDURE = BEGIN WAIT timer; END; WatcherNotify: ENTRY PROCEDURE = BEGIN NOTIFY hardware; END; DecapsulateBuffer: PROCEDURE [b: Buffer] RETURNS [BufferType] = BEGIN timeLastRecv ← AltoRam.GetTicks[]; SELECT b.encapsulation.ethernetOneType FROM pup => BEGIN IF b.length # ((b.pupLength + 1 + ethernetEncapsulationBytes)/2) THEN BEGIN IF CommFlags.doStats THEN StatIncr[statPupsDiscarded]; RETURN[rejected]; END; RETURN[pup]; END; ois => BEGIN IF b.length # ((b.ois.pktLength + 1 + ethernetEncapsulationBytes)/2) THEN BEGIN IF CommFlags.doStats THEN StatIncr[statOisDiscarded]; RETURN[rejected]; END; RETURN[ois]; END; translation => BEGIN SELECT b.rawWords[0] FROM translationRequest => ReceiveRequest[b]; translationResponse => ReceiveAck[b]; ENDCASE => NULL; ReturnFreeBuffer[b]; RETURN[processed]; END; ENDCASE => RETURN[rejected]; END; EncapsulatePup: PROCEDURE [b: PupBuffer, destination: PupHostID] = BEGIN b.encapsulation ← [ethernetOne[ etherSpare1:, etherSpare2:, etherSpare3:, etherSpare4:, etherSpare5:, translationWorked: TRUE, ethernetOneDest: destination, ethernetOneSource: myNetwork.hostNumber, ethernetOneType: pup]]; b.length ← (b.pupLength + 1 + ethernetEncapsulationBytes)/2; END; EncapsulateOis: PROCEDURE [ b: OisBuffer, destination: SpecialSystem.HostNumber] = BEGIN foundIt: BOOLEAN; ethernetAddr: Ethernet1Addr; [foundIt, ethernetAddr] ← Translate[destination]; IF foundIt THEN BEGIN b.encapsulation ← [ethernetOne[ etherSpare1:, etherSpare2:, etherSpare3:, etherSpare4:, etherSpare5:, translationWorked: TRUE, ethernetOneDest: ethernetAddr, ethernetOneSource: myNetwork.hostNumber, ethernetOneType: ois]]; b.length ← (b.ois.pktLength + 1 + ethernetEncapsulationBytes)/2; END ELSE BEGIN b.encapsulation ← [ethernetOne[ etherSpare1:, etherSpare2:, etherSpare3:, etherSpare4:, etherSpare5:, translationWorked: FALSE, ethernetOneDest:, ethernetOneSource:, ethernetOneType:]]; END; END; ForwardBuffer: PROCEDURE [b: Buffer] RETURNS [PupTypes.PupErrorCode] = BEGIN IF outputQueue.length > 10 THEN RETURN[gatewayResourceLimitsPupErrorCode]; -- transciever unpluged? SendBuffer[b]; RETURN[noErrorPupErrorCode]; END; SendBuffer: ENTRY PROCEDURE [b: Buffer] = BEGIN device: EthernetDeviceBlockHandle = myDevice; copy: Buffer; IF pleaseStop THEN Glitch[DriverNotActive]; IF ~b.encapsulation.translationWorked THEN BEGIN PutOnGlobalDoneQueue[b]; RETURN; END; b.device ← ethernetOne; IF b.encapsulation.ethernetOneDest = myNetwork.hostNumber OR b.encapsulation.ethernetOneDest = PupTypes.allHosts THEN BEGIN -- sending to ourself, copy it over since we can't hear it copy ← GetInputBuffer[]; IF copy # NIL THEN BEGIN copy.device ← ethernetOne; CommUtilDefs.CopyLong[ from: @b.encapsulation + ethernetEncapsulationOffset, nwords: b.length, to: @copy.encapsulation + ethernetEncapsulationOffset]; copy.length ← b.length; copy.network ← LONG[@myNetwork]; IF CommFlags.doStats THEN StatIncr[statEtherPacketsLocal]; IF CommFlags.doStats THEN StatBump[statEtherWordsLocal, b.length]; PutOnGlobalInputQueue[copy]; END ELSE IF CommFlags.doStats THEN StatIncr[statEtherEmptyFreeQueue]; END; IF currentOutputBuffer = NIL THEN BEGIN currentOutputBuffer ← b; timeSendStarted ← AltoRam.GetTicks[]; device.retransmissionMask ← 0; device.outputBuffer.count ← b.length; device.outputBuffer.pointer ← AltoRam.Shorten[ @b.encapsulation + ethernetEncapsulationOffset]; IF notChained THEN BEGIN IF device.inputBuffer.pointer↑ # 0 THEN GOTO PouringIn; device.interruptBit ← 0; -- inhibit interrupts for reset device.postData ← ethernetNotPosted; -- beware of hardware/microcode screwup -- it seems to hang while sending, after a collision, until a gateway packet arrives IF CommFlags.doStats THEN StartIO[resetCommand]; -- should post immediately UNTIL device.postData # ethernetNotPosted DO IF CommFlags.doStats THEN StatIncr[statResetDidntPost]; StartIO[resetCommand]; ENDLOOP; device.interruptBit ← interruptBit; --interrupts back on device.postData ← ethernetNotPosted; END; IF ~notChained THEN BEGIN now: EthernetDeviceBlockHandle = device.inputControlBlock; IF now # NIL AND now.inputBuffer.pointer↑ # 0 THEN GOTO PouringIn; IF device.retransmissionMask # 0 THEN GOTO AlreadySending; END; StartIO[outputCommand]; -- This could possibly clobber a packet that just started EXITS AlreadySending => NULL; PouringIn => BEGIN -- data already arriving, don't klobber it IF CommFlags.doStats THEN StatIncr[statEtherSendWhileReceiving]; END; END ELSE Enqueue[@outputQueue, b]; -- output already in progress, don't klobber it IF CommFlags.doStats THEN StatIncr[statEtherPacketsSent]; IF CommFlags.doStats THEN StatBump[statEtherWordsSent, b.length]; END; -- Saving the status is helpful when debugging. Comment it out to save space. ethernetStatus: EthernetDeviceBlockHandle; Broom: PROCEDURE [why: BcplOps.CleanupReason] = BEGIN IF CommFlags.doDebug THEN ethernetStatus↑ ← myDevice↑; SELECT why FROM Finish, Abort, OutLd => myDevice.interruptBit ← 0; InLd => BEGIN IF myNetwork.hostNumber # CommUtilDefs.GetEthernetHostNumber[] THEN Glitch[CantSwitchMachinesWhileEtherentDriverIsActive]; myDevice.interruptBit ← interruptBit; END; Save, Checkpoint => Glitch[CantMakImageWhileEtherentDriverIsActive]; ENDCASE; StartIO[resetCommand]; END; -- COLD code, only used when turning things on+off CreateDefaultEthernetOneDrivers: PUBLIC PROCEDURE RETURNS [BOOLEAN] = BEGIN OPEN AltoEthernetDefs; SetupEthernetDriver[ 0, 5, standardInput, standardOutput, standardEthernet, FALSE]; RETURN[TRUE]; END; CreateDefaultEthernetDrivers: PUBLIC PROCEDURE RETURNS [BOOLEAN] = BEGIN RETURN[FALSE]; END; CreateEthernetDriver: PUBLIC PROCEDURE [ netNumber: CARDINAL, deviceNumber: [0..3)] RETURNS [BOOLEAN] = BEGIN OPEN AltoEthernetDefs; him: POINTER TO FRAME[AltoEthernetDriver]; -- There is no way to delete the new frame. IF deviceNumber # 0 THEN him ← NEW AltoEthernetDriver; SELECT deviceNumber FROM 0 => SetupEthernetDriver[ netNumber, 5, standardInput, standardOutput, standardEthernet, FALSE]; 1 => him.SetupEthernetDriver[ netNumber, 6, secondInput, secondOutput, secondEthernet, FALSE]; 2 => him.SetupEthernetDriver[ netNumber, 8, -- 7 used by keyboard thirdInput, thirdOutput, thirdEthernet, FALSE]; ENDCASE => Glitch[OnlyThreeDriversArePossible]; RETURN[TRUE]; END; CreateChainedEthernetDriver: PUBLIC PROCEDURE [ netNumber: CARDINAL, deviceNumber: [0..3)] RETURNS [BOOLEAN] = BEGIN OPEN AltoEthernetDefs; him: POINTER TO FRAME[AltoEthernetDriver]; -- There is no way to delete the new frame. IF deviceNumber # 0 THEN him ← NEW AltoEthernetDriver; SELECT deviceNumber FROM 0 => SetupEthernetDriver[ netNumber, 5, standardInput, standardOutput, standardEthernet, TRUE]; 1 => him.SetupEthernetDriver[ netNumber, 6, secondInput, secondOutput, secondEthernet, TRUE]; 2 => him.SetupEthernetDriver[ netNumber, 8, -- 7 used by keyboard thirdInput, thirdOutput, thirdEthernet, TRUE]; ENDCASE => Glitch[OnlyThreeDriversArePossible]; RETURN[TRUE]; END; SetupEthernetDriver: PROCEDURE [ netNumber: CARDINAL, intLevel: WORD, inputBit, outputBit: WORD, deviceBlock: EthernetDeviceBlockHandle, chained: BOOLEAN] = BEGIN size: CARDINAL ← IF chained THEN SIZE[EthernetDeviceBlock] ELSE 0; notChained ← ~chained; myNetwork.netNumber ← [0, netNumber]; myNetwork.buffers ← IF chained THEN numberOfInputBuffers ELSE 2; inputCommand ← [inputBit]; outputCommand ← [outputBit]; interruptLevel ← intLevel; myDevice ← deviceBlock; resetCommand ← [inputCommand + outputCommand]; interruptBit ← Inline.BITSHIFT[1, interruptLevel]; pleaseStop ← TRUE; myDevice.postData ← ethernetNotPosted; AddDeviceToChain[@myNetwork, size]; IF CommFlags.doStats THEN BEGIN myNetwork.stats ← etherStats ← Storage.Node[SIZE[EtherStatsInfo]]; MiscDefs.Zero[etherStats, SIZE[EtherStatsInfo]]; END; END; DeleteDriver: PROCEDURE = BEGIN IF CommFlags.doStats THEN BEGIN Storage.Free[etherStats]; myNetwork.stats ← etherStats ← NIL; END; IF myDevice # standardEthernet THEN Runtime.SelfDestruct[]; END; -- Be sure the microcode has been loaded by now if this is a second Ethernet Board ActivateDriver: PROCEDURE = BEGIN IF ~pleaseStop THEN Glitch[DriverAlreadyActive]; pleaseStop ← FALSE; StartIO[resetCommand]; StartIO[resetCommand]; -- sometimes it doesn't work IF myDevice.postData = ethernetNotPosted THEN Glitch[NoEthernetBoard]; QueueInitialize[@outputQueue]; currentInputBuffer ← nextInputBuffer ← currentOutputBuffer ← NIL; myNetwork.hostNumber ← CommUtilDefs.GetEthernetHostNumber[]; myDevice.hostNumber ← myNetwork.hostNumber; myDevice.inputBuffer ← [0, NIL0]; myDevice.outputBuffer ← [0, NIL0]; IF notChained THEN BEGIN where: LONG POINTER; nextInputBuffer ← GetInputBuffer[]; currentInputBuffer ← GetInputBuffer[]; IF Inline.HighHalf[currentInputBuffer] # 0 THEN Glitch[HyperspaceNotSupported]; where ← @currentInputBuffer.encapsulation + ethernetEncapsulationOffset; IF Inline.BITAND[Inline.LowHalf[where], 3] # 0 THEN Glitch[BufferNotAlignedProperly]; nextInputBuffer.device ← currentInputBuffer.device ← ethernetOne; nextBufferPointer ← AltoRam.Shorten[ @nextInputBuffer.encapsulation + ethernetEncapsulationOffset]; nextBufferPointer↑ ← 0; -- show no input in yet myDevice.inputControlBlock ← NIL0; END ELSE BEGIN temp: EthernetDeviceBlockHandle; where: LONG POINTER; first ← last ← NIL; headBuffer ← tailBuffer ← NIL; THROUGH [0..numberOfInputBuffers) DO currentInputBuffer ← GetInputBuffer[]; IF Inline.HighHalf[currentInputBuffer] # 0 THEN Glitch[HyperspaceNotSupported]; where ← @currentInputBuffer.encapsulation + ethernetEncapsulationOffset; IF Inline.BITAND[Inline.LowHalf[where], 3] # 0 THEN Glitch[BufferNotAlignedProperly]; temp ← AltoRam.Shorten[currentInputBuffer.iocbChain]; IF first = NIL THEN BEGIN first ← temp; headBuffer ← currentInputBuffer; END; temp↑ ← [postData: ethernetNotPosted, interruptBit: interruptBit, wordsLeft: 0, retransmissionMask: 0, inputBuffer: [currentInputBuffer.length - ethernetEncapsulationOffset, AltoRam.Shorten[ @currentInputBuffer.encapsulation + ethernetEncapsulationOffset]], outputBuffer: [0, NIL0], hostNumber: 0, inputControlBlock: NIL0]; temp.inputBuffer.pointer↑ ← 0; IF last # NIL THEN BEGIN last.inputControlBlock ← temp; tailBuffer.next ← currentInputBuffer; END; last ← temp; currentInputBuffer.next ← NIL; tailBuffer ← currentInputBuffer; ENDLOOP; myDevice.inputControlBlock ← first; END; ImageDefs.AddCleanupProcedure[@cleanupItem]; CommUtilDefs.AddInterruptHandler[interruptLevel, @hardware, resetCommand]; hardProcess ← FORK Interrupt[]; -- The first interrupt will set things up. myDevice.interruptBit ← interruptBit; StartIO[resetCommand]; watcherProcess ← FORK Watcher[]; CreateCache[]; END; DeactivateDriver: PROCEDURE = BEGIN IF pleaseStop THEN Glitch[DriverNotActive]; pleaseStop ← TRUE; StartIO[resetCommand]; -- includes (naked)NOTIFY JOIN hardProcess; ImageDefs.RemoveCleanupProcedure[@cleanupItem]; myDevice.interruptBit ← 0; CommUtilDefs.RemoveInterruptHandler[interruptLevel]; StartIO[resetCommand]; DeleteDriverLocked[]; JOIN watcherProcess; IF currentInputBuffer # NIL THEN ReturnFreeBuffer[currentInputBuffer]; IF nextInputBuffer # NIL THEN ReturnFreeBuffer[nextInputBuffer]; IF currentOutputBuffer # NIL THEN PutOnGlobalDoneQueue[currentOutputBuffer]; QueueCleanup[@outputQueue]; myNetwork.netNumber ← [0, 0]; -- in case we turn it on after moving to another machine -- maybe we should turn off the SD bits if we are an extra board DeleteCache[]; END; DeleteDriverLocked: ENTRY PROCEDURE = INLINE BEGIN NOTIFY timer; END; -- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -- ADdress translation for 8 bit EthernetOne (Pup) and 48 bit Ethernet (OISCP) -- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * OisAddr: TYPE = SpecialSystem.HostNumber; Ethernet1Addr: TYPE = PupTypes.PupHostID; AddressPair: TYPE = MACHINE DEPENDENT RECORD [ oisAddr: OisAddr, ethernet1Addr: Ethernet1Addr, filler: [0..377B] ← 0]; CacheStatus: TYPE = {pending, active}; CacheEntry: TYPE = POINTER TO CacheObject; CacheObject: TYPE = RECORD [ next: CacheEntry, addressPair: AddressPair, counter: [0..deactivateCount], status: CacheStatus ]; translationRequest: CARDINAL = 10101B; translationResponse: CARDINAL = 7070B; cacheQueueHead: CacheEntry ← NIL; broadCastPairEntry: CacheEntry; -- permanent myAddressPairEntry: CacheEntry; -- permanent retryTime: CARDINAL = 2; -- two seconds deactivateTime: CARDINAL = 3*60; -- three minutes deactivateCount: CARDINAL = deactivateTime/retryTime; cacheEvent: CONDITION; demonProcess: PROCESS; CreateCache: ENTRY PROCEDURE = BEGIN aP: AddressPair ← [OISCP.allHostIDs, PupTypes.allHosts, 0]; cacheQueueHead ← NIL; broadCastPairEntry ← AddAddressPair[aP]; Process.SetTimeout[@cacheEvent, Process.SecondsToTicks[retryTime]]; demonProcess ← FORK Demon[]; Process.Detach[FORK PutMeIntoCache[]]; END; -- This crazy structure is needed because GetProcessorID hangs until we learn our network number, which may not happen until the driver has been started. PutMeIntoCache: PROCEDURE = BEGIN Rats: ENTRY PROCEDURE = INLINE BEGIN myAddressPairEntry ← AddAddressPair[aP]; END; aP: AddressPair; aP ← [SpecialSystem.GetProcessorID[], Inline.LowByte[myNetwork.hostNumber], 0]; IF ~pleaseStop THEN Rats[]; END; DeleteCache: PROCEDURE = BEGIN DeleteCacheLocked: ENTRY PROCEDURE = INLINE BEGIN NOTIFY cacheEvent; END; e, nextE: CacheEntry; DeleteCacheLocked[]; JOIN demonProcess; e ← cacheQueueHead; cacheQueueHead ← NIL; WHILE e # NIL DO nextE ← e.next; Storage.Free[e]; e ← nextE; ENDLOOP; END; depth: CARDINAL; FindEntry: INTERNAL PROCEDURE [oisAddr: OisAddr] RETURNS [entry: CacheEntry] = BEGIN IF CommFlags.doStats THEN depth ← 0; FOR entry ← cacheQueueHead, entry.next WHILE entry # NIL DO IF oisAddr = entry.addressPair.oisAddr THEN RETURN; IF CommFlags.doStats THEN depth ← depth + 1; ENDLOOP; END; AddEntry: INTERNAL PROCEDURE [entry: CacheEntry] = BEGIN entry.next ← cacheQueueHead; cacheQueueHead ← entry; END; RemoveEntry: INTERNAL PROCEDURE [entry: CacheEntry] = BEGIN e, pred: CacheEntry; IF (pred ← cacheQueueHead) = entry THEN BEGIN cacheQueueHead ← cacheQueueHead.next; RETURN; END; e ← pred.next; WHILE e # NIL DO IF e = entry THEN BEGIN pred.next ← entry.next; RETURN; END; pred ← e; e ← pred.next; ENDLOOP; ERROR; -- entry not found END; Translate: ENTRY PROCEDURE [oisAddr: OisAddr] RETURNS [foundIt: BOOLEAN, ethernet1Addr: Ethernet1Addr] = BEGIN e: CacheEntry ← FindEntry[oisAddr]; IF e # NIL THEN BEGIN e.counter ← 0; IF foundIt ← (e.status = active) THEN BEGIN ethernet1Addr ← e.addressPair.ethernet1Addr; END; IF e # cacheQueueHead THEN -- put e at the head of the queue BEGIN IF CommFlags.doStats THEN StatBump[cacheDepth, depth]; RemoveEntry[e]; AddEntry[e]; END; END ELSE -- entry not found, so add a new one BEGIN foundIt ← FALSE; IF CommFlags.doStats THEN StatIncr[cacheFault]; e ← Storage.Node[SIZE[CacheObject]]; e↑ ← [ next: NIL, addressPair: [oisAddr: oisAddr, ethernet1Addr: [377B]], counter: 0, status: pending]; AddEntry[e]; SendRequest[e]; END; END; AddAddressPair: INTERNAL PROCEDURE [aP: AddressPair] RETURNS [e: CacheEntry] = BEGIN IF (e ← FindEntry[aP.oisAddr]) = NIL THEN BEGIN e ← Storage.Node[SIZE[CacheObject]]; AddEntry[e]; END; e.addressPair ← aP; e.status ← active; e.counter ← 0; END; DeallocateEntry: INTERNAL PROCEDURE [e: CacheEntry] = BEGIN -- there are two entries that we do not want to throw out!! IF (e = broadCastPairEntry) OR (e = myAddressPairEntry) THEN e.counter ← 0 ELSE BEGIN RemoveEntry[e]; Storage.Free[e]; END; END; Demon: ENTRY PROCEDURE = BEGIN next: CacheEntry; Process.SetPriority[3]; UNTIL pleaseStop DO WAIT cacheEvent; IF pleaseStop THEN EXIT; FOR e: CacheEntry ← cacheQueueHead, next WHILE (e # NIL) DO next ← e.next; e.counter ← e.counter + 1; SELECT TRUE FROM e.counter = deactivateCount => DeallocateEntry[e]; e.status = pending => BEGIN IF CommFlags.doStats THEN StatIncr[translationRetries]; SendRequest[e]; END; ENDCASE; ENDLOOP; -- end of queue entries loop ENDLOOP; -- end of infinite loop END; SendRequest: INTERNAL PROCEDURE [e: CacheEntry] = BEGIN b: Buffer; request: LONG POINTER TO AddressPair; IF (b ← GetInputBuffer[]) # NIL THEN BEGIN -- broadcast the translation request b.encapsulation ← [ethernetOne[ etherSpare1:, etherSpare2:, etherSpare3:, etherSpare4:, etherSpare5:, translationWorked:, ethernetOneDest: PupTypes.allHosts, ethernetOneSource: myNetwork.hostNumber, ethernetOneType: translation]]; b.length ← (1 + ethernetEncapsulationBytes)/2 + 2*SIZE[AddressPair] + 1; b.rawWords[0] ← translationRequest; request ← LOOPHOLE[@b.rawWords[1]]; request↑ ← e.addressPair; -- also send our addresses, so responder does not fault request ← request + SIZE[AddressPair]; request↑ ← myAddressPairEntry.addressPair; SendBufferLocked[b]; END; END; ReceiveAck: ENTRY PROCEDURE [b: Buffer] = BEGIN IF b.encapsulation.ethernetOneDest = myAddressPairEntry.addressPair.ethernet1Addr THEN BEGIN receipt: LONG POINTER TO AddressPair ← LOOPHOLE[@b.rawWords[1]]; [] ← AddAddressPair[receipt↑]; END; END; ReceiveRequest: ENTRY PROCEDURE [b: Buffer] = BEGIN request, requesterAddr: LONG POINTER TO AddressPair; request ← LOOPHOLE[@b.rawWords[1]]; IF request.oisAddr = myAddressPairEntry.addressPair.oisAddr THEN BEGIN IF CommFlags.doStats THEN StatIncr[requestsForMe]; request.ethernet1Addr ← myAddressPairEntry.addressPair.ethernet1Addr; SendAck[request↑, b.encapsulation.ethernetOneSource]; -- since the requester is probably going to talk to us, add his address before we take a fault requesterAddr ← request + SIZE[AddressPair]; [] ← AddAddressPair[requesterAddr↑]; END; END; SendAck: INTERNAL PROCEDURE [aP: AddressPair, to: DriverTypes.Byte] = BEGIN b: Buffer; response: LONG POINTER TO AddressPair; IF (b ← GetInputBuffer[]) # NIL THEN BEGIN b.encapsulation ← [ethernetOne[ etherSpare1:, etherSpare2:, etherSpare3:, etherSpare4:, etherSpare5:, translationWorked:, ethernetOneDest: to, ethernetOneSource: myNetwork.hostNumber, ethernetOneType: translation]]; b.length ← (1 + ethernetEncapsulationBytes)/2 + SIZE[AddressPair] + 1; b.rawWords[0] ← translationResponse; response ← LOOPHOLE[@b.rawWords[1]]; response↑ ← aP; SendBufferLocked[b]; END; END; SendBufferLocked: INTERNAL PROCEDURE [b: Buffer] = BEGIN device: EthernetDeviceBlockHandle = myDevice; IF pleaseStop THEN Glitch[DriverNotActive]; b.device ← ethernetOne; IF currentOutputBuffer = NIL THEN BEGIN currentOutputBuffer ← b; timeSendStarted ← AltoRam.GetTicks[]; device.retransmissionMask ← 0; device.outputBuffer.count ← b.length; device.outputBuffer.pointer ← AltoRam.Shorten[ @b.encapsulation + ethernetEncapsulationOffset]; IF notChained THEN BEGIN IF device.inputBuffer.pointer↑ # 0 THEN GOTO PouringIn; device.interruptBit ← 0; -- inhibit interrupts for reset device.postData ← ethernetNotPosted; -- beware of hardware/microcode screwup -- it seems to hang while sending, after a collision, until a gateway packet arrives IF CommFlags.doStats THEN StartIO[resetCommand]; -- should post immediately UNTIL device.postData # ethernetNotPosted DO IF CommFlags.doStats THEN StatIncr[statResetDidntPost]; StartIO[resetCommand]; ENDLOOP; device.interruptBit ← interruptBit; --interrupts back on device.postData ← ethernetNotPosted; END; IF ~notChained THEN BEGIN now: EthernetDeviceBlockHandle = device.inputControlBlock; IF now # NIL AND now.inputBuffer.pointer↑ # 0 THEN GOTO PouringIn; IF device.retransmissionMask # 0 THEN GOTO AlreadySending; END; StartIO[outputCommand]; -- This could possibly clobber a packet that just started EXITS AlreadySending => NULL; PouringIn => BEGIN -- data already arriving, don't klobber it IF CommFlags.doStats THEN StatIncr[statEtherSendWhileReceiving]; END; END ELSE Enqueue[@outputQueue, b]; -- output already in progress, don't klobber it IF CommFlags.doStats THEN StatIncr[statEtherPacketsSent]; IF CommFlags.doStats THEN StatBump[statEtherWordsSent, b.length]; END; -- initialization Process.DisableTimeout[@hardware]; Process.SetTimeout[@timer, Process.MsecToTicks[1000]]; IF CommFlags.doDebug THEN BEGIN debugPointer: LONG POINTER TO GiantVector ← GetGiantVector[]; debugPointer.ethernetOutputQueue ← @outputQueue; debugPointer.currentInputBuffer ← @currentInputBuffer; debugPointer.nextInputBuffer ← @nextInputBuffer; debugPointer.currentOutputBuffer ← @currentOutputBuffer; ethernetStatus ← Storage.Node[SIZE[EthernetDeviceBlock]]; END; END.