-- File: EthernetDriver.mesa
-- Edit by: BLyon on: March 21, 1981 11:05 AM
-- Edit by: HGM on: March 14, 1981 9:10 PM
DIRECTORY
BufferDefs,
CommFlags USING [doDebug, doStats],
CommUtilDefs USING [Zero, GetEthernetHostNumber],
DriverTypes USING [Encapsulation, ethernetBroadcastHost],
DriverDefs USING [
Glitch, GetDeviceChain, GetInputBuffer, Network,
NetworkObject, AddDeviceToChain, PutOnGlobalDoneQueue, PutOnGlobalInputQueue],
EthernetFace,
Heap USING [MakeNode, FreeNode],
Inline USING [LowHalf, HighHalf, BITAND, LongCOPY],
PupTypes USING [allHosts, PupErrorCode, PupHostID],
StatsDefs USING [StatBump, StatIncr, StatCounterIndex],
PilotSwitches USING [switches],
Process USING [
Detach, DisableTimeout, MsecToTicks, SecondsToTicks, SetPriority, SetTimeout,
Ticks, Yield],
ProcessInternal USING [AllocateNakedCondition, DeallocateNakedCondition],
Runtime USING [GlobalFrame, SelfDestruct],
SpecialCommunication USING [],
SpecialSystem USING [
broadcastHostNumber, ProcessorID, HostNumber, GetProcessorID, nullHostNumber,
nullNetworkNumber],
System USING [
GetGreenwichMeanTime, GetClockPulses, Microseconds, Pulses,
MicrosecondsToPulses, GreenwichMeanTime];
EthernetDriver: MONITOR
IMPORTS
BufferDefs, CommUtilDefs, DriverDefs, StatsDefs,
EthernetFace, Heap, Inline, PilotSwitches, Process, ProcessInternal, Runtime,
System, SpecialSystem
EXPORTS BufferDefs, DriverDefs, SpecialCommunication
SHARES BufferDefs, SpecialSystem =
BEGIN OPEN StatsDefs, BufferDefs, EthernetFace, SpecialSystem;
-- EXPORTed TYPEs
Network: PUBLIC TYPE = DriverDefs.Network;
ether: DeviceHandle;
me: SpecialSystem.ProcessorID;
myEar: SpecialSystem.HostNumber;
-- what address am I listening for (verses me, my real address)
getGarbage: BOOLEAN ← FALSE; -- when true, we deliver any packet
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: System.Pulses;
oneSecondOfPulses: System.Pulses;
inputQueueLength: CARDINAL ← 1;
inputBuffersInQueue: CARDINAL;
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: 10000, -- in kiloBits/sec
buffers:, spare:, device: ethernet, index:, netNumber: nullNetworkNumber,
hostNumber: 0, next: NIL, pupStats: NIL, stats: NIL];
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;
BufferMustBeAlmostQuadWordAligned: 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
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 CommFlags.doStats AND status # pending THEN
StatIncr[statEtherInterruptDuringInterrupt];
UNTIL pleaseStop OR status # pending DO
WAIT inWait;
status ← GetStatus[this.iocbChain];
IF CommFlags.doStats AND status = pending THEN StatIncr[statEtherMissingStatus];
ENDLOOP;
IF CommFlags.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 CommFlags.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
this.time ← timeLastRecv ← System.GetClockPulses[];
this.length ← GetPacketLength[this.iocbChain];
this.network ← LONG[@myNetwork]; -- LONG because of Mokelumne compiler bug
IF CommFlags.doStats THEN
BEGIN
etherStats.packetsRecv ← etherStats.packetsRecv + 1;
etherStats.wordsRecv ← etherStats.wordsRecv + this.length;
StatIncr[statEtherPacketsReceived];
StatBump[statEtherWordsReceived, this.length];
END;
DriverDefs.PutOnGlobalInputQueue[this];
IF (new ← DriverDefs.GetInputBuffer[]) = NIL THEN
BEGIN
-- Rats, couldn't get a new buffer
inputBuffersInQueue ← inputBuffersInQueue - 1;
NOTIFY timer;
IF CommFlags.doStats THEN
BEGIN
etherStats.inputOff ← etherStats.inputOff + 1;
StatIncr[statEtherEmptyFreeQueue];
END;
END; -- cant get new buffer clause
END -- acceptBuffer 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 ← ethernet;
QueueInput[ether, @new.encapsulation, new.length, new.iocbChain];
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 CommFlags.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 CommFlags.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: System.Pulses] RETURNS [System.Pulses] =
INLINE
BEGIN
RETURN[LOOPHOLE[System.GetClockPulses[] - startTime, System.Pulses]];
END;
Watcher: PROCEDURE =
BEGIN
fiveSecondsOfPulses: System.Pulses ← System.MicrosecondsToPulses[5000000];
fiveHalfSecondsOfPulses: System.Pulses ← System.MicrosecondsToPulses[2500000];
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: System.Pulses ← System.GetClockPulses[];
QueueInputBufferLocked: ENTRY PROCEDURE = INLINE
BEGIN
QueueInput[ether, @b.encapsulation, b.length, b.iocbChain];
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 ← ethernet; 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 CommFlags.doStats THEN StatIncr[statEtherLostInterrupts];
SmashCSBs[]; -- this will leave output dangling
END;
FixupInput: ENTRY PROCEDURE = INLINE
BEGIN
IF CommFlags.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 CommFlags.doStats THEN StatIncr[statPacketsStuckInOutput];
ENDLOOP;
SmashCSBs[];
END;
DecapsulateBuffer: PROCEDURE [b: Buffer] RETURNS [BufferType] =
BEGIN
SELECT b.encapsulation.ethernetType FROM
pup =>
BEGIN
IF 2*b.length < b.pupLength + 2*SIZE[DriverTypes.Encapsulation] THEN
BEGIN
IF CommFlags.doStats THEN StatIncr[statPupsDiscarded];
RETURN[rejected];
END;
RETURN[pup];
END;
ois =>
BEGIN
IF 2*b.length < b.ois.pktLength + 2*SIZE[DriverTypes.Encapsulation] THEN
BEGIN IF CommFlags.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
foundIt: BOOLEAN;
oisAddr: OisAddr;
[foundIt, oisAddr] ← translate[destination];
IF foundIt THEN
BEGIN
b.encapsulation ←
[ethernet[ethernetDest: oisAddr, ethernetSource: me, ethernetType: pup]];
b.length ← (b.pupLength + 1)/2 + SIZE[DriverTypes.Encapsulation];
END
ELSE
BEGIN
b.encapsulation ←
[ethernet[
ethernetDest:, ethernetSource: SpecialSystem.nullHostNumber,
-- Marker for translation failed
ethernetType: pup]];
END;
END;
EncapsulateOis: PROCEDURE [
b: OisBuffer, destination: SpecialSystem.HostNumber] =
BEGIN
b.encapsulation ←
[ethernet[ethernetDest: destination, ethernetSource: me, ethernetType: ois]];
b.length ← (b.ois.pktLength + 1)/2 + SIZE[DriverTypes.Encapsulation];
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.ethernetSource = SpecialSystem.nullHostNumber THEN
BEGIN DriverDefs.PutOnGlobalDoneQueue[b]; RETURN; END;
IF ~hearSelf AND
(b.encapsulation.ethernetDest = me OR
b.encapsulation.ethernetDest = DriverTypes.ethernetBroadcastHost) 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 ← ethernet;
Inline.LongCOPY[
from: @b.encapsulation, nwords: b.length, to: @copy.encapsulation];
copy.length ← b.length;
copy.network ← LONG[@myNetwork]; -- LONG because of Mokelumne compiler bug
IF CommFlags.doStats THEN StatIncr[statEtherPacketsLocal];
IF CommFlags.doStats THEN StatBump[statEtherWordsLocal, b.length];
DriverDefs.PutOnGlobalInputQueue[copy];
END
ELSE IF CommFlags.doStats THEN StatIncr[statEtherEmptyFreeQueue];
END;
SendBufferInternal[b];
END;
SendBufferInternal: INTERNAL PROCEDURE [b: Buffer] =
BEGIN
minWordsPerEthernetPacket: CARDINAL = (64/2)-2; --*** Should move to DriverTypes
words: CARDINAL ← MAX[b.length,minWordsPerEthernetPacket];
b.device ← ethernet;
QueueOutput[ether, @b.encapsulation, words, b.iocbChain];
IF CommFlags.doStats THEN StatIncr[statEtherPacketsSent];
IF CommFlags.doStats THEN StatBump[statEtherWordsSent, b.length];
IF firstOutputBuffer = NIL THEN firstOutputBuffer ← b
ELSE lastOutputBuffer.next ← b;
lastOutputBuffer ← b;
timeSendStarted ← System.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;
CreateDefaultEthernetDrivers: PUBLIC PROCEDURE RETURNS [BOOLEAN] =
BEGIN
deviceNumber: CARDINAL ← 0;
etherDevice: DeviceHandle ← GetNextDevice[nullDeviceHandle];
IF PilotSwitches.switches.b = down THEN RETURN[FALSE];
IF etherDevice = nullDeviceHandle THEN RETURN[FALSE];
WHILE etherDevice # nullDeviceHandle DO
CreateAnEthernetDriver[etherDevice, deviceNumber];
etherDevice ← GetNextDevice[etherDevice];
deviceNumber ← deviceNumber + 1;
ENDLOOP;
RETURN[TRUE];
END;
CreateAnEthernetDriver: PROCEDURE [
etherDevice: DeviceHandle, deviceNumber: CARDINAL] =
BEGIN
IF deviceNumber # 0 THEN
BEGIN
him: POINTER TO FRAME[EthernetDriver] ← NEW EthernetDriver;
him.SetupEthernetDriver[etherDevice];
END
ELSE SetupEthernetDriver[etherDevice];
END;
SetupEthernetDriver: PROCEDURE [etherDevice: DeviceHandle] =
BEGIN
ether ← etherDevice;
myEar ← me ← SpecialSystem.GetProcessorID[];
myNetwork.netNumber ← nullNetworkNumber;
pleaseStop ← TRUE;
myNetwork.buffers ← inputQueueLength;
DriverDefs.AddDeviceToChain[@myNetwork, controlBlockSize];
IF CommFlags.doStats THEN
BEGIN
myNetwork.stats ← etherStats;
CommUtilDefs.Zero[etherStats, SIZE[EtherStatsInfo]];
END;
END;
ActivateDriver: PROCEDURE =
BEGIN
b: Buffer;
IF ~pleaseStop THEN DriverDefs.Glitch[DriverAlreadyActive];
oneSecondOfPulses ← System.MicrosecondsToPulses[1000000];
getGarbage ← pleaseStop ← FALSE;
TurnOff[ether];
AddCleanup[ether];
myEar ← me ← SpecialSystem.GetProcessorID[];
firstInputBuffer ← lastInputBuffer ← NIL;
firstOutputBuffer ← lastOutputBuffer ← NIL;
bufferAccessHandle ← NIL;
inputBuffersInQueue ← 0;
THROUGH [0..myNetwork.buffers) DO
IF (b ← DriverDefs.GetInputBuffer[TRUE])#NIL THEN
BEGIN
inputBuffersInQueue ← inputBuffersInQueue + 1;
IF CommFlags.doDebug AND Inline.BITAND[Inline.LowHalf[@b.encapsulation], 3] # 3 THEN DriverDefs.Glitch[BufferMustBeAlmostQuadWordAligned];
IF CommFlags.doDebug AND Inline.BITAND[Inline.LowHalf[b.iocbChain], 3] # 0 THEN
DriverDefs.Glitch[IOCBMustBeQuadWordAligned];
IF CommFlags.doDebug AND Inline.HighHalf[b.iocbChain] # 0 THEN
DriverDefs.Glitch[IOCBMustBeInFirstMDS];
b.device ← ethernet;
IF firstInputBuffer = NIL THEN firstInputBuffer ← b;
IF lastInputBuffer # NIL THEN lastInputBuffer.next ← b;
lastInputBuffer ← b;
END;
ENDLOOP;
[cv: inWait, mask: inInterruptMask] ← ProcessInternal.AllocateNakedCondition[
];
Process.DisableTimeout[inWait];
[cv: outWait, mask: outInterruptMask] ←
ProcessInternal.AllocateNakedCondition[];
Process.DisableTimeout[outWait];
SmashCSBs[];
inProcess ← FORK InInterrupt[];
outProcess ← FORK OutInterrupt[];
watcherProcess ← FORK Watcher[];
CreateCache[];
END;
SetEthernetListener: PUBLIC ENTRY PROCEDURE [
physicalOrder: CARDINAL, newHostNumber: SpecialSystem.HostNumber]
RETURNS [success: BOOLEAN] =
BEGIN
him: POINTER TO FRAME[EthernetDriver];
network: Network ← GetNthDeviceLikeMe[physicalOrder];
IF network = NIL THEN RETURN[FALSE];
him ← LOOPHOLE[Runtime.GlobalFrame[network.interrupt]];
him.EthernetListenForHost[newHostNumber];
RETURN[TRUE];
END;
EthernetListenForHost: PROCEDURE [newHostNumber: SpecialSystem.HostNumber] =
BEGIN myEar ← newHostNumber; SmashCSBs[]; END;
SetEthernetCollectGarbageToo: PUBLIC ENTRY PROCEDURE [
physicalOrder: CARDINAL, collectGarbage: BOOLEAN] RETURNS [success: BOOLEAN] =
BEGIN
him: POINTER TO FRAME[EthernetDriver];
network: Network ← GetNthDeviceLikeMe[physicalOrder];
IF network = NIL THEN RETURN[FALSE];
him ← LOOPHOLE[Runtime.GlobalFrame[network.interrupt]];
him.SetCollectGarbageToo[collectGarbage];
RETURN[TRUE];
END;
SetCollectGarbageToo: PROCEDURE [collectGarbage: BOOLEAN] =
BEGIN getGarbage ← collectGarbage; 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;
SmashCSBs: PROCEDURE =
BEGIN
b: Buffer;
TurnOn[
ether, LOOPHOLE[myEar, SpecialSystem.ProcessorID], inInterruptMask,
outInterruptMask, globalStatePtr];
FOR b ← firstInputBuffer, b.next UNTIL b = NIL DO
QueueInput[ether, @b.encapsulation, b.length, b.iocbChain]; ENDLOOP;
timeLastRecv ← System.GetClockPulses[];
END;
DeleteDriver: PROCEDURE =
BEGIN
IF ether # GetNextDevice[nullDeviceHandle] THEN Runtime.SelfDestruct[];
END;
DeactivateDriver: PROCEDURE =
BEGIN
b: Buffer;
IF pleaseStop THEN DriverDefs.Glitch[DriverNotActive];
pleaseStop ← TRUE;
KillInterruptRoutines[];
JOIN inProcess;
JOIN outProcess;
TurnOff[ether];
ProcessInternal.DeallocateNakedCondition[inWait];
ProcessInternal.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;
myNetwork.netNumber ← nullNetworkNumber;
-- in case we turn it on after moving to another machine
DeleteCache[];
END;
KillInterruptRoutines: ENTRY PROCEDURE = INLINE
BEGIN NOTIFY inWait↑; NOTIFY outWait↑; END;
KillDriverLocked: ENTRY PROCEDURE = INLINE BEGIN NOTIFY timer; END;
OisAddr: TYPE = SpecialSystem.HostNumber;
Ethernet1Addr: TYPE = PupTypes.PupHostID;
AddressPair: TYPE = MACHINE DEPENDENT RECORD [
oisAddr: OisAddr, ethernet1Addr: Ethernet1Addr, filler: [0..377B]];
CacheEntry: TYPE = LONG POINTER TO CacheObject;
CacheObject: TYPE = MACHINE DEPENDENT RECORD [
nextLink: CacheEntry,
addressPair: AddressPair,
tries: CARDINAL,
timeStamp: System.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: System.GreenwichMeanTime;
translate: PROCEDURE [Ethernet1Addr] RETURNS [BOOLEAN, OisAddr] ←
InactiveTranslate;
receiveRequest: PROCEDURE [Buffer] ← InactiveReceiveAckOrRequest;
receiveAck: PROCEDURE [Buffer] ← InactiveReceiveAckOrRequest;
-- interface
CreateCache: ENTRY PROCEDURE = INLINE
BEGIN
cacheQueueHead ← broadCastPairEntry ← myAddressPairEntry ← NIL;
translate ← InactiveTranslate;
receiveRequest ← InactiveReceiveAckOrRequest;
receiveAck ← InactiveReceiveAckOrRequest;
demonRunning ← FALSE;
END;
StartCache: ENTRY PROCEDURE [myEthernetOneAddr: CARDINAL] = INLINE
BEGIN
aP: AddressPair ← [SpecialSystem.broadcastHostNumber, PupTypes.allHosts, 0];
translate ← Translate;
receiveRequest ← ReceiveRequest;
receiveAck ← ReceiveAck;
Process.SetTimeout[@cacheEvent, demonActiveTime];
broadCastPairEntry ← AddAddressPair[aP];
aP ← [me, [myNetwork.hostNumber ← myEthernetOneAddr], 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;
depth: CARDINAL;
FindEntry: INTERNAL PROCEDURE [ethernet1Addr: Ethernet1Addr]
RETURNS [entry: CacheEntry] =
BEGIN
IF CommFlags.doStats THEN depth ← 0;
entry ← cacheQueueHead;
WHILE entry # NIL DO
IF ethernet1Addr = entry.addressPair.ethernet1Addr THEN RETURN;
entry ← entry.nextLink;
IF CommFlags.doStats THEN depth ← depth + 1;
ENDLOOP;
END;
AddEntry: INTERNAL PROCEDURE [entry: CacheEntry] =
BEGIN entry.nextLink ← cacheQueueHead; cacheQueueHead ← entry; END;
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 [ethernet1Addr: Ethernet1Addr]
RETURNS [foundIt: BOOLEAN, oisAddr: OisAddr] =
BEGIN
ethernetOneAddr: CARDINAL ← CommUtilDefs.GetEthernetHostNumber[];
StartCache[ethernetOneAddr];
[foundIt, oisAddr] ← Translate[ethernet1Addr];
END;
Translate: ENTRY PROCEDURE [ethernet1Addr: Ethernet1Addr]
RETURNS [foundIt: BOOLEAN, oisAddr: OisAddr] =
BEGIN
e: CacheEntry;
foundIt ← FALSE;
lastTranslationTime ← System.GetGreenwichMeanTime[];
IF (e ← FindEntry[ethernet1Addr]) # NIL THEN
BEGIN
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;
SELECT e.status FROM
active => BEGIN
foundIt ← TRUE;
oisAddr ← e.addressPair.oisAddr;
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 CommFlags.doStats THEN StatIncr[cacheFault];
e ← Heap.MakeNode[n: SIZE[CacheObject]];
e.status ← new;
e.tries ← 0;
e.timeStamp ← lastTranslationTime;
e.addressPair ← [oisAddr:, ethernet1Addr: ethernet1Addr, filler:];
AddEntry[e];
NOTIFY cacheEvent;
END;
END;
AddAddressPair: INTERNAL PROCEDURE [aP: AddressPair] RETURNS [e: CacheEntry] =
BEGIN
IF (e ← FindEntry[aP.ethernet1Addr]) = NIL THEN
BEGIN e ← Heap.MakeNode[n: SIZE[CacheObject]]; AddEntry[e]; END;
e.addressPair ← aP;
e.status ← active;
e.timeStamp ← System.GetGreenwichMeanTime[];
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.timeStamp ← System.GetGreenwichMeanTime[]
ELSE BEGIN RemoveEntry[e]; Heap.FreeNode[p: e]; END;
END;
Demon: ENTRY PROCEDURE =
BEGIN
translationInactiveTime: LONG CARDINAL = 10*60;
-- demon will die if no services are needed in ten minutes
now: System.GreenwichMeanTime;
t: LONG CARDINAL;
e, nextE: CacheEntry;
pendingEntries: BOOLEAN;
demonRunning ← TRUE;
lastTranslationTime ← System.GetGreenwichMeanTime[];
Process.SetPriority[3];
UNTIL pleaseStop DO
WAIT cacheEvent;
IF (now ← System.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 CommFlags.doStats THEN StatIncr[unsuccessfulTranslation];
END
ELSE
BEGIN
IF CommFlags.doStats THEN StatIncr[translationRetries];
SendRequest[e];
e.timeStamp ← System.GetGreenwichMeanTime[];
END;
END;
END;
new =>
BEGIN
pendingEntries ← TRUE;
SendRequest[e];
e.status ← pending;
e.timeStamp ← System.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 ← InactiveReceiveAckOrRequest;
receiveRequest ← InactiveReceiveAckOrRequest;
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;
SendRequest: INTERNAL PROCEDURE [e: CacheEntry] =
BEGIN
b: Buffer;
request: LONG POINTER TO AddressPair;
IF (b ← DriverDefs.GetInputBuffer[TRUE]) # NIL THEN
BEGIN
-- broadcast the translation request
b.encapsulation ←
[ethernet[
ethernetDest: DriverTypes.ethernetBroadcastHost, ethernetSource: me,
ethernetType: translation]];
b.length ← SIZE[DriverTypes.Encapsulation] + 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
-- we donot allow back door requests if we are not actively translating
InactiveReceiveAckOrRequest: PROCEDURE [b: Buffer] =
BEGIN
b.requeueProcedure[b];
END;
-- we now own buffer b
ReceiveAck: ENTRY PROCEDURE [b: Buffer] =
BEGIN
IF b.encapsulation.ethernetDest = myAddressPairEntry.addressPair.oisAddr 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 (myAddressPairEntry # NIL) AND
(request.ethernet1Addr = myAddressPairEntry.addressPair.ethernet1Addr) THEN
BEGIN
IF CommFlags.doStats THEN StatIncr[requestsForMe];
-- since the requester is probably going to talk to us, add his address before we take a fault
requesterAddr ← request + SIZE[AddressPair];
[] ← AddAddressPair[requesterAddr↑];
request.oisAddr ← myAddressPairEntry.addressPair.oisAddr;
SendAck[request↑, b.encapsulation.ethernetSource, b]; -- we lose ownership of b
END
ELSE b.requeueProcedure[b];
END;
SendAck: INTERNAL PROCEDURE [
aP: AddressPair, to: SpecialSystem.HostNumber, b: Buffer] = INLINE
BEGIN
response: LONG POINTER TO AddressPair;
IF b # NIL THEN
BEGIN
b.encapsulation ←
[ethernet[ethernetDest: to, ethernetSource: me, ethernetType: translation]];
b.length ← SIZE[DriverTypes.Encapsulation] + SIZE[AddressPair] + 1;
b.rawWords[0] ← translationResponse;
response ← LOOPHOLE[@b.rawWords[1]];
response↑ ← aP;
-- send it
SendBufferInternal[b];
END;
END;
-- initialization
Process.SetTimeout[@timer, Process.MsecToTicks[1000]];
END. -- EthernetDriver
September 5, 1980 1:36 AM By HGM; create from EthernetOneDriver.
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:47 AM By BLyon; let the inputter collect garbage packets.
November 6, 1980 6:18 PM By BLyon; records input time in buffer.time field.
February 13, 1981 3:38 PM By BLyon; zombie translation entries are changed to new if needed AND Demon is immmediately started and never dies.
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↑.