-- File: EthernetOneDriver.mesa -- Edit by: Andrew Birrell on: 9-Oct-81 16:43:32 -- Edit by: BLyon on: March 13, 1981 10:47 PM -- Last Edited by: Levin, August 9, 1983 9:28 am DIRECTORY Basics USING [BITAND, LongNumber], BufferDefs, CommFlags USING [doDebug, doStats], CommUtilDefs USING [Zero], DriverDefs USING [ GetGiantVector, GiantVector, Glitch, GetDeviceChain, GetInputBuffer, Network, NetworkObject, AddDeviceToChain, PutOnGlobalDoneQueue, PutOnGlobalInputQueue], DriverTypes USING [ Byte, ethernetEncapsulationOffset, ethernetEncapsulationBytes], EthernetOneFace, GermSwap USING [switches], LoadState USING [SelfDestruct], PrincOpsUtils USING [ AllocateNakedCondition, BITAND, DeallocateNakedCondition, GlobalFrame, HighHalf, LongCOPY, LowHalf], Process USING [ Detach, DisableTimeout, MsecToTicks, SecondsToTicks, SetPriority, SetTimeout, Ticks, Yield], ProcessorFace USING [ GetGreenwichMeanTime, GetClockPulses, GreenwichMeanTime, microsecondsPerHundredPulses], PupTypes USING [allHosts, PupErrorCode, PupHostID], RPCPrivate USING[ rpcSocket ], StatsDefs USING [StatBump, StatIncr, StatCounterIndex], SpecialCommunication USING [], NSAddress USING [ broadcastHostNumber, ProcessorID, HostNumber, GetProcessorID, nullNetworkNumber]; EthernetOneDriver: MONITOR IMPORTS Basics, BufferDefs, CommUtilDefs, DriverDefs, GermSwap, StatsDefs, EthernetOneFace, LoadState, PrincOpsUtils, Process, ProcessorFace, NSAddress EXPORTS BufferDefs, DriverDefs, RPCPrivate--ReturnBuffer--, SpecialCommunication SHARES BufferDefs, DriverTypes, NSAddress = BEGIN OPEN StatsDefs, BufferDefs, EthernetOneFace, NSAddress; -- RPC: disable "doStats" for this module -- doStats: BOOLEAN = CommFlags.doStats AND FALSE; -- EXPORTed TYPEs Network: PUBLIC TYPE = DriverDefs.Network; ethernetEncapsulationOffset: CARDINAL = DriverTypes.ethernetEncapsulationOffset; ethernetEncapsulationBytes: CARDINAL = DriverTypes.ethernetEncapsulationBytes; ether: DeviceHandle; globalStatePtr: GlobalStatePtr; -- Allocate space if needed inProcess, outProcess: PROCESS; inWait, outWait: LONG POINTER TO CONDITION _ NIL; firstOutputBuffer, lastOutputBuffer: Buffer; firstInputBuffer, lastInputBuffer: Buffer; inInterruptMask, outInterruptMask: WORD; watcherProcess: PROCESS; pleaseStop: BOOLEAN; timer: CONDITION; timeLastRecv, timeSendStarted: Pulses; fiveSecondsOfPulses: Pulses; fiveHalfSecondsOfPulses: Pulses; oneSecondOfPulses: Pulses; inputQueueLength: CARDINAL _ 1; inputBuffersInQueue: CARDINAL; --RPC-- loanedToRPC: CARDINAL; -- this number from inputBuffersInQueue aren't really -- in the input queue, because RPCRuntime has them! --RPC-- systemBufferSize: CARDINAL _ 0; myNetwork: DriverDefs.NetworkObject _ [decapsulateBuffer: DecapsulateBuffer, encapsulatePup: EncapsulatePup, encapsulateOis: EncapsulateOis, sendBuffer: SendBuffer, forwardBuffer: ForwardBuffer, activateDriver: ActivateDriver, deactivateDriver: DeactivateDriver, deleteDriver: DeleteDriver, interrupt: InInterrupt, changeNumberOfInputBuffers: MaybeChangeNumberOfInputBuffers, alive: TRUE, speed: 3000, -- in kiloBits/sec buffers:, spare:, device: ethernetOne, index:, netNumber: nullNetworkNumber, hostNumber:, next: NIL, pupStats: NIL, stats: NIL]; myEar: CARDINAL; -- what address am I listening for (verses myNetwork.hostNumber, my real address). getGarbage: BOOLEAN _ FALSE; -- when true, we deliver any packet FunnyRetransmissionMask: PUBLIC ERROR = CODE; MachineIDTooBigForEthernet: PUBLIC ERROR = CODE; DriverNotActive: PUBLIC ERROR = CODE; DriverAlreadyActive: PUBLIC ERROR = CODE; EthernetNetNumberScrambled: PUBLIC ERROR = CODE; CantMakImageWhileEtherentDriverIsActive: PUBLIC ERROR = CODE; OnlyTwoDriversArePossible: PUBLIC ERROR = CODE; BufferMustBeQuadWordAligned: PUBLIC ERROR = CODE; IOCBMustBeQuadWordAligned: PUBLIC ERROR = CODE; IOCBMustBeInFirstMDS: PUBLIC ERROR = CODE; EtherStatsInfo: TYPE = RECORD [ packetsSent: LONG CARDINAL, wordsSent: LONG CARDINAL, badSendStatus: LONG CARDINAL, overruns: LONG CARDINAL, packetsRecv: LONG CARDINAL, wordsRecv: LONG CARDINAL, badRecvStatus: LONG CARDINAL, inputOff: LONG CARDINAL, loadTable: ARRAY [0..16] OF LONG CARDINAL]; etherStatsInfo: EtherStatsInfo; etherStats: POINTER TO EtherStatsInfo _ @etherStatsInfo; -- Hot Procedures Pulses: TYPE = LONG CARDINAL; MicrosecondsToPulses: PROC[m: LONG CARDINAL] RETURNS[ Pulses] = { RETURN[ MIN[LAST[LONG CARDINAL]/100,m/ProcessorFace.microsecondsPerHundredPulses]*100 ] }; InInterrupt: ENTRY PROCEDURE = BEGIN acceptBuffer: BOOLEAN; this, new: Buffer; status: Status; lastMissed, missed: CARDINAL _ GetPacketsMissed[ether]; Process.SetPriority[3]; DO UNTIL pleaseStop OR (this _ firstInputBuffer) # NIL DO WAIT inWait; ENDLOOP; IF pleaseStop THEN EXIT; status _ GetStatus[this.iocbChain]; IF doStats AND status # pending THEN StatIncr[statEtherInterruptDuringInterrupt]; UNTIL pleaseStop OR status # pending DO WAIT inWait; status _ GetStatus[this.iocbChain]; IF doStats AND status = pending THEN StatIncr[statEtherMissingStatus]; ENDLOOP; IF doStats AND (missed _ GetPacketsMissed[ether]) # lastMissed THEN BEGIN StatBump[statEtherEmptyNoBuffer, missed - lastMissed]; lastMissed _ missed; END; IF pleaseStop THEN EXIT; firstInputBuffer _ firstInputBuffer.next; SELECT status FROM ok => acceptBuffer _ TRUE; ENDCASE => BEGIN etherStats.badRecvStatus _ etherStats.badRecvStatus + 1; acceptBuffer _ getGarbage; -- we may be collecting garbage packets IF doStats THEN SELECT status FROM packetTooLong => StatIncr[statEtherReceivedTooLong]; badAlignmentButOkCrc => StatIncr[statEtherReceivedNot16]; crc => StatIncr[statEtherReceivedBadCRC]; crcAndBadAlignment => StatIncr[statEtherReceivedNot16BadCRC]; overrun => BEGIN etherStats.overruns _ etherStats.overruns + 1; StatIncr[statEtherReceivedOverrun]; END; ENDCASE => StatIncr[statEtherReceivedBadStatus]; END; IF acceptBuffer THEN BEGIN allocateAnother: BOOLEAN; this.time _ timeLastRecv _ ProcessorFace.GetClockPulses[]; this.length _ GetPacketLength[this.iocbChain]; this.network _ LONG[@myNetwork]; -- LONG because of Mokelumne compiler bug IF doStats THEN BEGIN etherStats.packetsRecv _ etherStats.packetsRecv + 1; etherStats.wordsRecv _ etherStats.wordsRecv + this.length; StatIncr[statEtherPacketsReceived]; StatBump[statEtherWordsReceived, this.length]; END; -- RPC: side door into RPC packet dispatching code -- IF EnqueueRecvd # NIL AND this.encapsulation.ethernetOneType = pup AND 2*this.length >= this.pupLength + ethernetEncapsulationBytes AND this.dest.socket = RPCPrivate.rpcSocket THEN BEGIN this.next _ NIL -- assumed by SendBuffer --; IF EnqueueRecvd[LOOPHOLE[this,PupBuffer]] THEN { new _ NIL; allocateAnother _ (loanedToRPC+2 > inputBuffersInQueue) } ELSE { new _ this; allocateAnother _ FALSE; new.length _ systemBufferSize }; END ELSE { DriverDefs.PutOnGlobalInputQueue[this]; allocateAnother _ TRUE }; IF allocateAnother THEN BEGIN IF (new _ DriverDefs.GetInputBuffer[]) = NIL THEN BEGIN -- Rats, couldn't get a new buffer inputBuffersInQueue _ inputBuffersInQueue - 1; NOTIFY timer; IF doStats THEN BEGIN etherStats.inputOff _ etherStats.inputOff + 1; StatIncr[statEtherEmptyFreeQueue]; END; END; -- cant get new input buffer clause END ELSE -- we may have loaned the buffer to RPC; don't replace it -- IF new = NIL THEN loanedToRPC _ loanedToRPC + 1; END -- accept buffer clause ELSE BEGIN new _ this; -- Some kind of error, recycle this buffer END; -- reject buffer clause -- add new buffer to end of input chain IF new # NIL THEN BEGIN new.device _ ethernetOne; QueueInput[ ether, @new.encapsulation + ethernetEncapsulationOffset, new.length - ethernetEncapsulationOffset, new.iocbChain]; IF systemBufferSize = 0 THEN systemBufferSize _ new.length; new.next _ NIL; IF firstInputBuffer = NIL THEN firstInputBuffer _ new ELSE lastInputBuffer.next _ new; lastInputBuffer _ new; END; ENDLOOP; END; OutInterrupt: ENTRY PROCEDURE = BEGIN b: Buffer; status: Status; Process.SetPriority[3]; UNTIL pleaseStop DO DO -- forever until something interesting happens IF pleaseStop THEN EXIT; -- we compute the values each time around since the value of b can change if -- the watcher shoots down the output. IF (b _ firstOutputBuffer) # NIL AND (status _ GetStatus[b.iocbChain]) # pending THEN EXIT; WAIT outWait; ENDLOOP; IF pleaseStop THEN EXIT; -- so that we do not do something below SELECT status FROM ok => BEGIN IF doStats THEN BEGIN tries: CARDINAL _ GetRetries[b.iocbChain]; statEtherSendsCollision1: StatsDefs.StatCounterIndex = statEtherSendsCollision1; first: CARDINAL = LOOPHOLE[statEtherSendsCollision1]; etherStats.packetsSent _ etherStats.packetsSent + 1; etherStats.wordsSent _ etherStats.wordsSent + b.length; IF tries # 0 THEN StatIncr[LOOPHOLE[first + tries]]; etherStats.loadTable[tries] _ etherStats.loadTable[tries] + 1; END; END; ENDCASE => BEGIN IF doStats THEN SELECT status FROM tooManyCollisions => BEGIN etherStats.loadTable[16] _ etherStats.loadTable[16] + 1; StatIncr[statEtherSendsCollisionLoadOverflow]; END; underrun => BEGIN etherStats.overruns _ etherStats.overruns + 1; StatIncr[statEtherSendOverrun]; END; ENDCASE => BEGIN etherStats.badSendStatus _ etherStats.badSendStatus + 1; StatIncr[statEtherSendBadStatus]; END; END; -- We don't resend things that screwup firstOutputBuffer _ firstOutputBuffer.next; DriverDefs.PutOnGlobalDoneQueue[b]; ENDLOOP; END; GetElapsedPulses: PROCEDURE [startTime: Pulses] RETURNS [Pulses] = INLINE BEGIN RETURN[ProcessorFace.GetClockPulses[] - startTime]; END; Watcher: PROCEDURE = BEGIN UNTIL pleaseStop DO -- In either case, 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 several times. Of course, if it is still not zero when we look again, it could be a new interrupt that has just arrived. -- Check for lost input interrupt THROUGH [0..25) DO IF InputChainOK[] THEN EXIT; REPEAT FINISHED => BEGIN WatcherNotify[]; END; ENDLOOP; -- Check for lost output interrupt THROUGH [0..25) DO IF OutputChainOK[] THEN EXIT; REPEAT FINISHED => BEGIN WatcherNotify[]; END; ENDLOOP; -- Check for stuck input IF GetElapsedPulses[timeLastRecv] > fiveSecondsOfPulses THEN FixupInput[]; -- Check for stuck output IF firstOutputBuffer # NIL AND (GetElapsedPulses[timeSendStarted] > fiveHalfSecondsOfPulses) THEN ShootDownOutput[]; IF InputBufferQueueOK[] THEN WatcherWait[]; ENDLOOP; END; InputChainOK: ENTRY PROCEDURE RETURNS [BOOLEAN] = INLINE BEGIN RETURN[ (firstInputBuffer = NIL) OR (GetStatus[firstInputBuffer.iocbChain] = pending)]; END; OutputChainOK: ENTRY PROCEDURE RETURNS [BOOLEAN] = INLINE BEGIN RETURN[ (firstOutputBuffer = NIL) OR (GetStatus[firstOutputBuffer.iocbChain] = pending)]; END; InputBufferQueueOK: PROCEDURE RETURNS [BOOLEAN] = INLINE BEGIN b: Buffer; enterTime: Pulses _ ProcessorFace.GetClockPulses[]; QueueInputBufferLocked: ENTRY PROCEDURE = INLINE BEGIN QueueInput[ ether, @b.encapsulation + ethernetEncapsulationOffset, b.length - ethernetEncapsulationOffset, b.iocbChain]; IF systemBufferSize = 0 THEN systemBufferSize _ b.length; IF firstInputBuffer = NIL THEN firstInputBuffer _ b ELSE lastInputBuffer.next _ b; lastInputBuffer _ b; inputBuffersInQueue _ inputBuffersInQueue + 1; END; WHILE (inputBuffersInQueue < myNetwork.buffers) DO -- not MONITOR protected compair !! IF (b _ DriverDefs.GetInputBuffer[TRUE]) # NIL THEN BEGIN b.device _ ethernetOne; b.next _ NIL; QueueInputBufferLocked[]; END; IF GetElapsedPulses[enterTime] > oneSecondOfPulses THEN RETURN[FALSE]; ENDLOOP; RETURN[TRUE]; END; WatcherWait: ENTRY PROCEDURE = INLINE BEGIN WAIT timer; END; WatcherNotify: ENTRY PROCEDURE = INLINE BEGIN IF doStats THEN StatIncr[statEtherLostInterrupts]; SmashCSBs[]; -- this will leave output dangling END; FixupInput: ENTRY PROCEDURE = INLINE BEGIN IF doStats THEN StatIncr[statInputIdle]; SmashCSBs[]; -- this will leave output dangling END; ShootDownOutput: ENTRY PROCEDURE = INLINE BEGIN -- This happens if the transciever is unplugged b: Buffer; TurnOff[ether]; UNTIL firstOutputBuffer = NIL DO b _ firstOutputBuffer; firstOutputBuffer _ firstOutputBuffer.next; DriverDefs.PutOnGlobalDoneQueue[b]; IF doStats THEN StatIncr[statPacketsStuckInOutput]; ENDLOOP; SmashCSBs[]; END; EnqueueRecvd: PROC[BufferDefs.PupBuffer]RETURNS[BOOLEAN] _ NIL; GetRPCPackets: PUBLIC--RPCPrivate-- ENTRY PROC[ p: PROC[BufferDefs.PupBuffer]RETURNS[BOOLEAN] ] = { EnqueueRecvd _ p }; ReturnBuffer: PUBLIC--RPCPrivate-- ENTRY PROC[c: PupBuffer] = BEGIN b: Buffer = c; IF loanedToRPC > 0 THEN BEGIN QueueInput[ ether, @b.encapsulation + ethernetEncapsulationOffset, (b.length _ systemBufferSize) - ethernetEncapsulationOffset, b.iocbChain]; IF firstInputBuffer = NIL THEN firstInputBuffer _ b ELSE lastInputBuffer.next _ b; lastInputBuffer _ b; loanedToRPC _ loanedToRPC-1; END ELSE ReturnFreeBuffer[b]; END; DecapsulateBuffer: PROCEDURE [b: Buffer] RETURNS [BufferType] = BEGIN SELECT b.encapsulation.ethernetOneType FROM pup => BEGIN IF 2*b.length < b.pupLength + ethernetEncapsulationBytes THEN BEGIN IF doStats THEN StatIncr[statPupsDiscarded]; RETURN[rejected]; END; RETURN[pup]; END; ois => BEGIN IF 2*b.length < b.ois.pktLength + ethernetEncapsulationBytes THEN BEGIN IF doStats THEN StatIncr[statOisDiscarded]; RETURN[rejected]; END; RETURN[ois]; END; translation => BEGIN IF b.rawWords[0] = translationRequest THEN ReceiveRequest[b] ELSE IF b.rawWords[0] = translationResponse THEN receiveAck[b] ELSE RETURN[rejected]; 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: NSAddress.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 FALSE THEN -- outputQueue.length>10 THEN RETURN[gatewayResourceLimitsPupErrorCode]; -- transceiver unplugged? SendBuffer[b]; RETURN[noErrorPupErrorCode]; END; SendBuffer: ENTRY PROCEDURE [b: Buffer] = BEGIN IF pleaseStop THEN DriverDefs.Glitch[DriverNotActive]; IF ~b.encapsulation.translationWorked THEN BEGIN DriverDefs.PutOnGlobalDoneQueue[b]; RETURN; END; IF ~hearSelf AND (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: Buffer _ DriverDefs.GetInputBuffer[]; IF copy # NIL THEN BEGIN copy.device _ ethernetOne; PrincOpsUtils.LongCOPY[ from: @b.encapsulation + ethernetEncapsulationOffset, nwords: b.length, to: @copy.encapsulation + ethernetEncapsulationOffset]; copy.length _ b.length; copy.network _ LONG[@myNetwork]; -- LONG because of Mokelumne compiler bug IF doStats THEN StatIncr[statEtherPacketsLocal]; IF doStats THEN StatBump[statEtherWordsLocal, b.length]; DriverDefs.PutOnGlobalInputQueue[copy]; END ELSE IF doStats THEN StatIncr[statEtherEmptyFreeQueue]; END; SendBufferInternal[b]; END; SendBufferInternal: INTERNAL PROCEDURE [b: Buffer] = BEGIN b.device _ ethernetOne; QueueOutput[ ether, @b.encapsulation + ethernetEncapsulationOffset, b.length, b.iocbChain]; IF doStats THEN StatIncr[statEtherPacketsSent]; IF doStats THEN StatBump[statEtherWordsSent, b.length]; IF firstOutputBuffer = NIL THEN firstOutputBuffer _ b ELSE lastOutputBuffer.next _ b; lastOutputBuffer _ b; timeSendStarted _ ProcessorFace.GetClockPulses[]; END; -- for changing the number of buffers while running numberOfExtraBuffer: CARDINAL = 3; bufferAccessHandle: BufferDefs.BufferAccessHandle; -- this should only be called from Boss -- No MONITOR PROTECTION here. MaybeChangeNumberOfInputBuffers: PROCEDURE [increaseBuffers: BOOLEAN] = BEGIN IF increaseBuffers THEN BEGIN IF bufferAccessHandle = NIL THEN BEGIN bufferAccessHandle _ BufferDefs.MakeBufferPool[numberOfExtraBuffer]; myNetwork.buffers _ myNetwork.buffers + numberOfExtraBuffer; END; END ELSE BEGIN IF bufferAccessHandle # NIL THEN BEGIN myNetwork.buffers _ myNetwork.buffers - numberOfExtraBuffer; BufferDefs.FreeBufferPool[bufferAccessHandle]; bufferAccessHandle _ NIL; END; END; END; -- COLD code, only used when turning things on+off AdjustLengtoOfD0EthernetInputQueue: PUBLIC PROCEDURE [n: CARDINAL] = BEGIN inputQueueLength _ n; END; CreateDefaultEthernetOneDrivers: PUBLIC PROCEDURE RETURNS [BOOLEAN] = BEGIN deviceNumber: CARDINAL _ 0; etherDevice: DeviceHandle _ GetNextDevice[nullDeviceHandle]; IF GermSwap.switches[a] THEN RETURN[FALSE]; IF etherDevice = nullDeviceHandle THEN RETURN[FALSE]; WHILE etherDevice # nullDeviceHandle DO CreateAnEthernetOneDriver[etherDevice, deviceNumber]; etherDevice _ GetNextDevice[etherDevice]; deviceNumber _ deviceNumber + 1; ENDLOOP; RETURN[TRUE]; END; CreateAnEthernetOneDriver: PROCEDURE [ etherDevice: DeviceHandle, deviceNumber: CARDINAL] = BEGIN IF deviceNumber # 0 THEN BEGIN him: POINTER TO FRAME[EthernetOneDriver] _ NEW EthernetOneDriver; him.SetupEthernetOneDriver[etherDevice]; END ELSE SetupEthernetOneDriver[etherDevice]; END; SetupEthernetOneDriver: PROCEDURE [etherDevice: DeviceHandle] = BEGIN net, host: DriverTypes.Byte; ether _ etherDevice; [net, host] _ GetEthernet1Address[ether]; IF net # 0 AND net # myNetwork.netNumber.b AND myNetwork.netNumber # nullNetworkNumber THEN DriverDefs.Glitch[EthernetNetNumberScrambled]; IF myNetwork.netNumber = nullNetworkNumber THEN myNetwork.netNumber _ [a: 0, b: net]; pleaseStop _ TRUE; myNetwork.buffers _ inputQueueLength; DriverDefs.AddDeviceToChain[@myNetwork, controlBlockSize]; IF doStats THEN BEGIN myNetwork.stats _ etherStats; CommUtilDefs.Zero[etherStats, SIZE[EtherStatsInfo]]; END; END; ActivateDriver: PROCEDURE = BEGIN b: Buffer; net, host: DriverTypes.Byte; IF ~pleaseStop THEN DriverDefs.Glitch[DriverAlreadyActive]; fiveSecondsOfPulses _ MicrosecondsToPulses[5000000]; fiveHalfSecondsOfPulses _ MicrosecondsToPulses[2500000]; oneSecondOfPulses _ MicrosecondsToPulses[1000000]; [net, host] _ GetEthernet1Address[ether]; getGarbage _ pleaseStop _ FALSE; TurnOff[ether]; AddCleanup[ether]; firstInputBuffer _ lastInputBuffer _ NIL; firstOutputBuffer _ lastOutputBuffer _ NIL; bufferAccessHandle _ NIL; myEar _ myNetwork.hostNumber _ host; inputBuffersInQueue _ loanedToRPC _ 0; THROUGH [0..myNetwork.buffers) DO IF (b _ DriverDefs.GetInputBuffer[TRUE])#NIL THEN BEGIN inputBuffersInQueue _ inputBuffersInQueue + 1; IF CommFlags.doDebug AND Basics.BITAND[ LOOPHOLE[LOOPHOLE[@b.encapsulation, LONG CARDINAL] + ethernetEncapsulationOffset, Basics.LongNumber].lowbits, 3] # 0 THEN DriverDefs.Glitch[BufferMustBeQuadWordAligned]; IF CommFlags.doDebug AND PrincOpsUtils.BITAND[PrincOpsUtils.LowHalf[b.iocbChain], 3] # 0 THEN DriverDefs.Glitch[IOCBMustBeQuadWordAligned]; IF CommFlags.doDebug AND PrincOpsUtils.HighHalf[b.iocbChain] # 0 THEN DriverDefs.Glitch[IOCBMustBeInFirstMDS]; b.device _ ethernetOne; IF firstInputBuffer = NIL THEN firstInputBuffer _ b; IF lastInputBuffer # NIL THEN lastInputBuffer.next _ b; lastInputBuffer _ b; END; ENDLOOP; [cv: inWait, mask: inInterruptMask] _ PrincOpsUtils.AllocateNakedCondition[ ]; Process.DisableTimeout[inWait]; [cv: outWait, mask: outInterruptMask] _ PrincOpsUtils.AllocateNakedCondition[]; Process.DisableTimeout[outWait]; SmashCSBs[]; inProcess _ FORK InInterrupt[]; outProcess _ FORK OutInterrupt[]; watcherProcess _ FORK Watcher[]; CreateCache[]; END; SetEthernetOneListener: PUBLIC ENTRY PROCEDURE [ physicalOrder: CARDINAL, newHostNumber: CARDINAL] RETURNS [success: BOOLEAN] = BEGIN him: POINTER TO FRAME[EthernetOneDriver]; network: Network _ GetNthDeviceLikeMe[physicalOrder]; IF network = NIL THEN RETURN[FALSE]; him _ LOOPHOLE[PrincOpsUtils.GlobalFrame[network.interrupt]]; him.EthernetOneListenForHost[newHostNumber]; RETURN[TRUE]; END; EthernetOneListenForHost: PROCEDURE [newHostNumber: CARDINAL] = BEGIN myEar _ newHostNumber; SmashCSBs[]; END; GetNthDeviceLikeMe: PROCEDURE [physicalOrder: CARDINAL] RETURNS [net: Network] = BEGIN i: CARDINAL _ 0; net _ DriverDefs.GetDeviceChain[]; WHILE net # NIL DO IF net.device = myNetwork.device THEN IF (i _ i + 1) = physicalOrder THEN RETURN; net _ net.next; ENDLOOP; END; SetEthernetOneCollectGarbageToo: PUBLIC ENTRY PROCEDURE [ physicalOrder: CARDINAL, collectGarbage: BOOLEAN] RETURNS [success: BOOLEAN] = BEGIN him: POINTER TO FRAME[EthernetOneDriver]; network: Network _ GetNthDeviceLikeMe[physicalOrder]; IF network = NIL THEN RETURN[FALSE]; him _ LOOPHOLE[PrincOpsUtils.GlobalFrame[network.interrupt]]; him.SetCollectGarbageToo[collectGarbage]; RETURN[TRUE]; END; SetCollectGarbageToo: PROCEDURE [collectGarbage: BOOLEAN] = BEGIN getGarbage _ collectGarbage; END; SmashCSBs: PROCEDURE = BEGIN b: Buffer; TurnOn[ether, myEar, inInterruptMask, outInterruptMask, globalStatePtr]; FOR b _ firstInputBuffer, b.next UNTIL b = NIL DO QueueInput[ ether, @b.encapsulation + ethernetEncapsulationOffset, b.length - ethernetEncapsulationOffset, b.iocbChain]; ENDLOOP; timeLastRecv _ ProcessorFace.GetClockPulses[]; END; DeleteDriver: PROCEDURE = BEGIN IF ether # GetNextDevice[nullDeviceHandle] THEN LoadState.SelfDestruct[]; END; DeactivateDriver: PROCEDURE = BEGIN b: Buffer; IF pleaseStop THEN DriverDefs.Glitch[DriverNotActive]; pleaseStop _ TRUE; KillInterruptRoutines[]; JOIN inProcess; JOIN outProcess; TurnOff[ether]; PrincOpsUtils.DeallocateNakedCondition[inWait]; PrincOpsUtils.DeallocateNakedCondition[outWait]; inWait _ outWait _ NIL; MaybeChangeNumberOfInputBuffers[FALSE]; KillDriverLocked[]; JOIN watcherProcess; RemoveCleanup[ether]; UNTIL firstInputBuffer = NIL DO b _ firstInputBuffer; firstInputBuffer _ b.next; ReturnFreeBuffer[b]; ENDLOOP; UNTIL firstOutputBuffer = NIL DO b _ firstOutputBuffer; firstOutputBuffer _ b.next; ReturnFreeBuffer[b]; ENDLOOP; DeleteCache[]; END; KillInterruptRoutines: ENTRY PROCEDURE = INLINE BEGIN NOTIFY inWait^; NOTIFY outWait^; END; KillDriverLocked: ENTRY PROCEDURE = INLINE BEGIN NOTIFY timer; END; -- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -- Ugly thing to take care of handling the Ethernet1 (it uses 8 bit addresses). -- This will go away when OIS Communications stops using Ethernet1's. -- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -- types OisAddr: TYPE = NSAddress.HostNumber; Ethernet1Addr: TYPE = DriverTypes.Byte; AddressPair: TYPE = MACHINE DEPENDENT RECORD [ oisAddr: OisAddr, ethernet1Addr: Ethernet1Addr, filler: [0..377B]]; CacheEntry: TYPE = REF CacheObject; CacheObject: TYPE = MACHINE DEPENDENT RECORD [ nextLink: CacheEntry, addressPair: AddressPair, tries: CARDINAL, timeStamp: ProcessorFace.GreenwichMeanTime, status: CacheStatus, filler: [0..37777B]]; CacheStatus: TYPE = {new, pending, active, zombie}; -- variables translationRequest: CARDINAL = 10101B; translationResponse: CARDINAL = 7070B; cacheQueueHead: CacheEntry; broadCastPairEntry: CacheEntry; -- permanent myAddressPairEntry: CacheEntry; -- permanent retryLimit: CARDINAL _ 10B; retryTime: LONG CARDINAL _ 2; -- two seconds demonActiveTime: Process.Ticks _ Process.SecondsToTicks[1]; -- one second deactivateTime: LONG CARDINAL _ 3*60; -- three minutes demonSleepTime: Process.Ticks _ Process.SecondsToTicks[60*5]; -- five minutes cacheEvent: CONDITION; demonRunning: BOOLEAN; lastTranslationTime: ProcessorFace.GreenwichMeanTime; receiveAck: PROCEDURE [b: Buffer] _ InactiveReceiveAck; translate: PROCEDURE [OisAddr] RETURNS [BOOLEAN, Ethernet1Addr] _ InactiveTranslate; etherHost: Ethernet1Addr; oisHost: OisAddr; -- interface CreateCache: ENTRY PROCEDURE = INLINE BEGIN cacheQueueHead _ broadCastPairEntry _ myAddressPairEntry _ NIL; translate _ InactiveTranslate; receiveAck _ InactiveReceiveAck; demonRunning _ FALSE; [, etherHost] _ GetEthernet1Address[ether]; oisHost _ NSAddress.GetProcessorID[]; END; StartCache: ENTRY PROCEDURE = INLINE BEGIN etherHost: Ethernet1Addr; oisHost: OisAddr _ NSAddress.GetProcessorID[]; aP: AddressPair _ [NSAddress.broadcastHostNumber, PupTypes.allHosts, 0]; [, etherHost] _ GetEthernet1Address[ether]; translate _ Translate; receiveAck _ ReceiveAck; Process.SetTimeout[@cacheEvent, demonActiveTime]; broadCastPairEntry _ AddAddressPair[aP]; aP _ [oisHost, etherHost, 0]; myAddressPairEntry _ AddAddressPair[aP]; demonRunning _ TRUE; Process.Detach[FORK Demon[]]; END; DeleteCache: PROCEDURE = INLINE BEGIN e: CacheEntry; DeleteCacheLocked: ENTRY PROCEDURE = INLINE BEGIN NOTIFY cacheEvent; END; -- DeleteCacheLocked WHILE demonRunning DO DeleteCacheLocked[]; Process.Yield[]; ENDLOOP; -- cleanup in case demon was never running WHILE (cacheQueueHead # NIL) DO e _ cacheQueueHead; cacheQueueHead _ e.nextLink; --Heap.FreeNode[p: e];-- ENDLOOP; END; -- assume protection by lock depth: CARDINAL; FindEntry: INTERNAL PROCEDURE [oisAddr: OisAddr] RETURNS [entry: CacheEntry] = BEGIN IF doStats THEN depth _ 0; entry _ cacheQueueHead; WHILE entry # NIL DO IF oisAddr = entry.addressPair.oisAddr THEN RETURN; entry _ entry.nextLink; IF doStats THEN depth _ depth + 1; ENDLOOP; END; -- assume protection by lock AddEntry: INTERNAL PROCEDURE [entry: CacheEntry] = BEGIN entry.nextLink _ cacheQueueHead; cacheQueueHead _ entry; END; -- assume protection by lock RemoveEntry: INTERNAL PROCEDURE [entry: CacheEntry] = BEGIN e, pred: CacheEntry; IF (pred _ cacheQueueHead) = entry THEN BEGIN cacheQueueHead _ cacheQueueHead.nextLink; RETURN; END; e _ pred.nextLink; WHILE e # NIL DO IF e = entry THEN BEGIN pred.nextLink _ entry.nextLink; RETURN; END; pred _ e; e _ pred.nextLink; ENDLOOP; ERROR; -- entry not found END; InactiveTranslate: PROCEDURE [oisAddr: OisAddr] RETURNS [foundIt: BOOLEAN, ethernet1Addr: Ethernet1Addr] = BEGIN StartCache[]; [foundIt, ethernet1Addr] _ Translate[oisAddr]; END; -- locks Translate: ENTRY PROCEDURE [oisAddr: OisAddr] RETURNS [foundIt: BOOLEAN, ethernet1Addr: Ethernet1Addr] = BEGIN e: CacheEntry; foundIt _ FALSE; lastTranslationTime _ ProcessorFace.GetGreenwichMeanTime[]; IF (e _ FindEntry[oisAddr]) # NIL THEN BEGIN IF e # cacheQueueHead THEN -- put e at the head of the queue BEGIN IF doStats THEN StatBump[cacheDepth, depth]; RemoveEntry[e]; AddEntry[e]; END; SELECT e.status FROM active => BEGIN foundIt _ TRUE; ethernet1Addr _ e.addressPair.ethernet1Addr; e.timeStamp _ lastTranslationTime; END; zombie => BEGIN e.status _ new; e.tries _ 0; e.timeStamp _ lastTranslationTime; NOTIFY cacheEvent; END; ENDCASE => NULL; END -- of found it ELSE -- entry not found, so add a new one BEGIN IF doStats THEN StatIncr[cacheFault]; e _ NEW[CacheObject]; e.status _ new; e.tries _ 0; e.timeStamp _ lastTranslationTime; e.addressPair _ [oisAddr: oisAddr, ethernet1Addr:, filler:]; AddEntry[e]; NOTIFY cacheEvent; END; END; -- assume protection by lock AddAddressPair: INTERNAL PROCEDURE [aP: AddressPair] RETURNS [e: CacheEntry] = BEGIN IF (e _ FindEntry[aP.oisAddr]) = NIL THEN BEGIN e _ NEW[CacheObject]; AddEntry[e]; END; e.addressPair _ aP; e.status _ active; e.timeStamp _ ProcessorFace.GetGreenwichMeanTime[]; END; -- assume protection by lock 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.timeStamp _ ProcessorFace.GetGreenwichMeanTime[] ELSE BEGIN RemoveEntry[e]; --Heap.FreeNode[p: e];-- END; END; -- locks Demon: ENTRY PROCEDURE = BEGIN translationInactiveTime: LONG CARDINAL = 10*60; -- demon will die if no services are needed in ten minutes now: ProcessorFace.GreenwichMeanTime; t: LONG CARDINAL; e, nextE: CacheEntry; pendingEntries: BOOLEAN; demonRunning _ TRUE; lastTranslationTime _ ProcessorFace.GetGreenwichMeanTime[]; Process.SetPriority[3]; UNTIL pleaseStop DO WAIT cacheEvent; IF (now _ ProcessorFace.GetGreenwichMeanTime[]) - lastTranslationTime > translationInactiveTime OR pleaseStop THEN EXIT; pendingEntries _ FALSE; e _ cacheQueueHead; WHILE (e # NIL) DO nextE _ e.nextLink; t _ now - e.timeStamp; SELECT e.status FROM active, zombie => BEGIN IF t > deactivateTime THEN DeallocateEntry[e]; END; pending => BEGIN pendingEntries _ TRUE; IF t > retryTime THEN BEGIN e.tries _ e.tries + 1; IF e.tries > retryLimit THEN BEGIN e.status _ zombie; IF doStats THEN StatIncr[unsuccessfulTranslation]; END ELSE BEGIN IF doStats THEN StatIncr[translationRetries]; SendRequest[e]; e.timeStamp _ ProcessorFace.GetGreenwichMeanTime[]; END; END; END; new => BEGIN pendingEntries _ TRUE; SendRequest[e]; e.status _ pending; e.timeStamp _ ProcessorFace.GetGreenwichMeanTime[]; END; ENDCASE => ERROR; e _ nextE; ENDLOOP; -- end of queue entries loop IF pendingEntries THEN Process.SetTimeout[@cacheEvent, demonActiveTime] ELSE Process.SetTimeout[@cacheEvent, demonSleepTime]; ENDLOOP; -- end of infinite loop receiveAck _ InactiveReceiveAck; translate _ InactiveTranslate; e _ cacheQueueHead; cacheQueueHead _ myAddressPairEntry _ broadCastPairEntry _ NIL; WHILE e # NIL DO nextE _ e.nextLink; --Heap.FreeNode[p: e];-- e _ nextE; ENDLOOP; demonRunning _ FALSE; END; -- assume locked SendRequest: INTERNAL PROCEDURE [e: CacheEntry] = BEGIN b: Buffer; request: LONG POINTER TO AddressPair; IF (b _ DriverDefs.GetInputBuffer[]) # NIL THEN BEGIN -- broadcast the trnslation 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; -- send it SendBufferInternal[b]; END; END; -- locks -- we now own buffer b InactiveReceiveAck: PROCEDURE [b: Buffer] = BEGIN b.requeueProcedure[b]; 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; b.requeueProcedure[b]; END; -- locks -- we now own buffer b ReceiveRequest: ENTRY PROCEDURE [b: Buffer] = BEGIN request, requesterAddr: LONG POINTER TO AddressPair; request _ LOOPHOLE[@b.rawWords[1]]; IF request.oisAddr = oisHost THEN BEGIN -- since the requester is probably going to talk to us, add his address before we take a fault requesterAddr _ request + SIZE[AddressPair]; [] _ AddAddressPair[requesterAddr^]; IF doStats THEN StatIncr[requestsForMe]; request.ethernet1Addr _ etherHost; SendAck[request^, b.encapsulation.ethernetOneSource, b]; -- we lose ownership of b END ELSE b.requeueProcedure[b]; END; -- assume protection by lock -- we now own buffer b SendAck: INTERNAL PROCEDURE [aP: AddressPair, to: DriverTypes.Byte, b: Buffer] = INLINE BEGIN response: LONG POINTER TO AddressPair; IF b # 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; -- send it SendBufferInternal[b]; END; END; -- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -- End of Ethernet1 uglyness -- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -- initialization Process.SetTimeout[@timer, Process.MsecToTicks[1000]]; IF CommFlags.doDebug THEN BEGIN debugPointer: LONG POINTER TO DriverDefs.GiantVector _ DriverDefs.GetGiantVector[]; debugPointer.currentInputBuffer _ @firstInputBuffer; debugPointer.nextInputBuffer _ @lastInputBuffer; debugPointer.currentOutputBuffer _ @firstOutputBuffer; END; END. -- EthernetOneDriver February 23, 1980 9:10 PM By forrest; Change PrincOpsUtilsDefs to PrincOpsUtils, and move EtherStatsInfo Type from AltoEthernetDefs (RIP) to inside here. April 18, 1980 3:00 AM By Murray; Face changes (and other Pilotization). May 19, 1980 10:20 AM By BLyon; DisableTimeout, SetTimeout, MsecToTicks, SetPriority imported from Process instead of CommUtilDefs. May 27, 1980 11:44 AM By BLyon; Added ethernet1 translation uglyness. August 1, 1980 4:24 PM By BLyon; 8bit->32bit net number Kludge in CreateDefaultEthernetsDrivers and use NSAddress.nullNetworkNumber instead of 0. August 13, 1980 1:13 PM By BLyon; added InputChainOK and OutputChainOK to insure the bodies of the two loops in Watcher are protected by the monitor lock. August 25, 1980 1:50 PM By BLyon; multiple ethernets now possible. August 28, 1980 12:50 PM By McJones; converted to PrincOpsUtils.AllocateNakedCondition. September 5, 1980 4:44 PM By HGM; use hearSelf, a switch to ignore boards even if they are there. September 17, 1980 4:41 PM By BLyon; added myNetwork.buffers stuff. October 20, 1980 4:04 PM By BLyon; added SetYourHostNumber & myEar for Ois peeking. October 22, 1980 9:57 AM By BLyon; let the inputter collect garbage packets. November 6, 1980 6:34 PM By BLyon; Records input time in buffer.time. November 11, 1980 10:33 AM By BLyon; No longer smashes myNetwork.netNumber in DeactivateDriver. February 13, 1981 3:46 PM By BLyon; zombie translation entries can be changed to new if needed AND Demon is started immediately and never dies AND stores the ethernetOneHostNumber. February 24, 1981 3:28 PM By BLyon; undid February 13 - demon is created when needed and goes away when not needed AND changed WatcherNotify to SmashCSBs and to NOTIFY inWait^ and outWait^. Ź>˜Jš œ›kĻkœœœEœœœęĮ˜˜®—…———^