-- Copyright (C) 1983, 1984 by Xerox Corporation. All rights reserved.
--EthernetOneDriver.mesa, HGM, 29-Feb-84 15:25:11
-- (last edit by: AOF on: 15-Jun-83 11:35:16)
DIRECTORY
Buffer USING [AccessHandle, Buffer, DestroyPool, MakePool, Type],
CommFlags USING [doDebug, doStats, driverStats],
CommHeap USING [MakeNode, FreeNode],
CommUtil USING [AllocateIocbs, FreeIocbs],
Driver USING [
Glitch, GetDeviceChain, GetInputBuffer, Network, NetworkObject,
AddDeviceToChain, PutOnGlobalDoneQueue, PutOnGlobalInputQueue,
ReturnFreeBuffer],
DriverTypes USING [
Byte, ethernetOneEncapsulationOffset, ethernetOneEncapsulationBytes],
Environment USING [Byte],
EthernetOneFace,
EthernetDriverFriends USING [EtherStatsInfo],
Inline USING [LowHalf, HighHalf, BITAND, LongCOPY],
Process USING [
Abort, Detach, DisableTimeout, EnableAborts, GetPriority,
Priority, SecondsToTicks, SetPriority, SetTimeout, Yield],
ProcessPriorities USING [priorityIOHigh],
SpecialRuntime USING [AllocateNakedCondition, DeallocateNakedCondition],
PupTypes USING [allHosts, PupErrorCode, PupHostID],
Runtime USING [GlobalFrame, SelfDestruct],
Stats USING [StatBump, StatIncr, StatCounterIndex],
SpecialCommunication USING [],
SpecialSystem USING [HostNumber, GetProcessorID, NetworkNumber],
System USING [
GetClockPulses, Pulses,
MicrosecondsToPulses, switches--['<]--,
broadcastHostNumber, nullNetworkNumber];
EthernetOneDriver: MONITOR
IMPORTS
Buffer, CommHeap, CommUtil, Driver, Stats, EthernetOneFace,
Inline, Process, Runtime, SpecialRuntime, SpecialSystem, System
EXPORTS Buffer, Driver, SpecialCommunication, System =
BEGIN
OPEN EthernetOneFace;
--EXPORTed TYPEs
Network: PUBLIC TYPE = Driver.Network;
HostNumber: PUBLIC TYPE = SpecialSystem.HostNumber;
NetworkNumber: PUBLIC TYPE = SpecialSystem.NetworkNumber;
encapOffset: CARDINAL =
DriverTypes.ethernetOneEncapsulationOffset;
encapBytes: CARDINAL = DriverTypes.ethernetOneEncapsulationBytes;
ether: DeviceHandle;
globalStatePtr: GlobalStatePtr; --Allocate space if needed
myEar: CARDINAL;
--what address am I listening for (verses myNetwork.pupHostNumber, my real address).
getGarbage: BOOLEAN ← FALSE; --when true, we deliver any packet
setupEthernetOneDriver: PROC[etherDevice: DeviceHandle];
ethernetOneListenForHost: PROC[newHostNumber: CARDINAL];
inputState: RECORD[
process: PROCESS,
mask: WORD,
access: Buffer.AccessHandle,
inWait: LONG POINTER TO CONDITION,
firstBuffer, lastBuffer: Buffer.Buffer,
queueAllowed, queueLength: CARDINAL,
timeLastRecv: LONG CARDINAL,
lastMissed: CARDINAL];
outputState: RECORD[
process: PROCESS,
mask: WORD,
outWait: LONG POINTER TO CONDITION,
firstBuffer, lastBuffer: Buffer.Buffer,
timeSendDone: LONG CARDINAL];
watcherState: RECORD[
pleaseStop: BOOLEAN,
process: PROCESS, timer: CONDITION];
--IOCBs
iocbState: RECORD[wait: CONDITION, first, free: FreeIocb];
FreeIocb: TYPE = LONG POINTER TO IocbObject;
IocbObject: TYPE = RECORD[
next: FreeIocb, rest: SEQUENCE COMPUTED CARDINAL OF WORD];
seconds: RECORD[one, fiveHalf, forty: LONG CARDINAL];
myNetwork: Driver.NetworkObject ← [
decapsulateBuffer: DecapsulateBuffer,
encapsulateAndSendPup: EncapsulateAndSendPup,
encapsulateAndSendNS: EncapsulateAndSendNS,
sendRawBuffer: SendRawBuffer,
encapsulateAndForwardPup: ForwardPup,
encapsulateAndForwardNS: EncapsulateAndForwardNS,
activateDriver: ActivateDriver,
deactivateDriver: DeactivateDriver,
deleteDriver: DeleteDriver,
changeNumberOfInputBuffers: MaybeChangeNumberOfInputBuffers,
alive: TRUE, buffers:, device: ethernetOne, index:,
netNumber: System.nullNetworkNumber, pupNetNumber: 0, pupHostNumber: 0,
next: NIL, pupStats: NIL, statsLevel0: NIL, statsLevel1: NIL];
LostAllIocbs: PUBLIC ERROR = CODE;
IOCBSizeIsZero: PUBLIC ERROR = CODE;
DriverNotActive: PUBLIC ERROR = CODE;
DriverAlreadyActive: PUBLIC ERROR = CODE;
IOCBMustBeInFirstMDS: PUBLIC ERROR = CODE;
IOCBMustBeQuadWordAligned: PUBLIC ERROR = CODE;
EthernetNetNumberScrambled: PUBLIC ERROR = CODE;
BufferMustBeAlmostQuadWordAligned: PUBLIC ERROR = CODE;
etherStats: EthernetDriverFriends.EtherStatsInfo;
--Hot Procedures
GetBufferAndIocb: INTERNAL PROC RETURNS[b: Buffer.Buffer] =
BEGIN
SELECT TRUE FROM
(iocbState.free = NIL) =>
{IF CommFlags.doStats THEN Stats.StatIncr[statsIocbWait]; RETURN[NIL]};
((b ← Driver.GetInputBuffer[FALSE]) = NIL) => RETURN;
ENDCASE;
b.driver.iocb ← iocbState.free;
iocbState.free ← iocbState.free.next;
END; --GetBufferAndIocb
FreeBufferAndIocb: INTERNAL PROC[
queueProc: PROC[b: Buffer.Buffer], b: Buffer.Buffer] =
BEGIN
iocb: FreeIocb ← b.driver.iocb;
IF iocb # NIL THEN
BEGIN
iocb.next ← iocbState.free;
iocbState.free ← iocb;
b.driver.iocb ← NIL;
NOTIFY iocbState.wait;
END;
queueProc[b];
END; --FreeBufferAndIocb
InInterrupt: ENTRY PROC =
BEGIN
status: Status;
acceptBuffer: BOOLEAN;
this, new: Buffer.Buffer;
DO
ENABLE ABORTED => EXIT;
UNTIL (this ← inputState.firstBuffer) # NIL DO
WAIT inputState.inWait; ENDLOOP;
status ← GetStatus[this.driver.iocb];
IF CommFlags.doStats AND status # pending THEN
Stats.StatIncr[statEtherInterruptDuringInterrupt];
UNTIL status # pending DO
WAIT inputState.inWait;
status ← GetStatus[this.driver.iocb];
IF CommFlags.doStats AND status = pending
THEN Stats.StatIncr[statEtherMissingStatus];
ENDLOOP;
inputState.firstBuffer ← inputState.firstBuffer.next;
IF status = ok THEN acceptBuffer ← TRUE
ELSE
BEGIN
acceptBuffer ← getGarbage; --we may be collecting garbage packets
IF CommFlags.driverStats
THEN etherStats.badRecvStatus ← etherStats.badRecvStatus + 1;
IF CommFlags.doStats THEN Stats.StatIncr[statEtherReceivedBadStatus];
IF CommFlags.doStats OR CommFlags.driverStats THEN
SELECT status FROM
packetTooLong =>
BEGIN
IF CommFlags.driverStats
THEN etherStats.packetTooLong ← etherStats.packetTooLong + 1;
IF CommFlags.doStats THEN Stats.StatIncr[statEtherReceivedTooLong];
END;
badAlignmentButOkCrc =>
BEGIN
IF CommFlags.driverStats THEN etherStats.badAlignmentButOkCrc ←
etherStats.badAlignmentButOkCrc + 1;
IF CommFlags.doStats THEN Stats.StatIncr[statEtherReceivedNot16];
END;
crc =>
BEGIN
IF CommFlags.driverStats THEN
etherStats.badCrc ← etherStats.badCrc + 1;
IF CommFlags.doStats THEN Stats.StatIncr[statEtherReceivedBadCRC];
END;
crcAndBadAlignment =>
BEGIN
IF CommFlags.driverStats THEN
etherStats.crcAndBadAlignment ← etherStats.crcAndBadAlignment+1;
IF CommFlags.doStats THEN
Stats.StatIncr[statEtherReceivedNot16BadCRC];
END;
overrun =>
BEGIN
IF CommFlags.driverStats THEN
etherStats.overrun ← etherStats.overrun + 1;
IF CommFlags.doStats THEN Stats.StatIncr[statEtherReceivedOverrun];
END;
ENDCASE;
END;
IF ~acceptBuffer THEN {new ← this; new.next ← NIL} --recycle this buffer
ELSE
BEGIN
inputState.timeLastRecv ← this.time ← System.GetClockPulses[];
this.driver.length ← GetPacketLength[this.driver.iocb];
this.driver.faceStatus ← ethernetOne[status];
this.network ← LONG[@myNetwork];
IF CommFlags.driverStats THEN
BEGIN
etherStats.packetsRecv ← etherStats.packetsRecv + 1;
etherStats.wordsRecv ← etherStats.wordsRecv + this.driver.length;
END;
IF CommFlags.doStats THEN
BEGIN
Stats.StatIncr[statEtherPacketsReceived];
Stats.StatBump[statEtherWordsReceived, this.driver.length];
END;
FreeBufferAndIocb[Driver.PutOnGlobalInputQueue, this];
SELECT TRUE FROM
(inputState.queueLength > inputState.queueAllowed) =>
BEGIN
new ← NIL;
inputState.queueLength ← inputState.queueLength - 1;
NOTIFY watcherState.timer;
IF CommFlags.doStats THEN Stats.StatIncr[statEtherEmptyFreeQueue];
END;
((new ← GetBufferAndIocb[]) = NIL) =>
BEGIN --Rats, couldn't or didn't want a new buffer
inputState.queueLength ← inputState.queueLength - 1;
NOTIFY watcherState.timer;
IF CommFlags.doStats THEN Stats.StatIncr[statEtherEmptyFreeQueue];
END;
ENDCASE;
END; --acceptBuffer clause
--add new buffer to end of input chain
IF new # NIL THEN
BEGIN
new.driver.faceStatus ← ethernetOne[pending];
QueueInput[
ether,
@new.encapsulation + encapOffset,
new.driver.length - encapOffset,
new.driver.iocb];
IF inputState.firstBuffer = NIL THEN inputState.firstBuffer ← new
ELSE inputState.lastBuffer.next ← new;
inputState.lastBuffer ← new;
END;
ENDLOOP;
END;
OutInterrupt: ENTRY PROC =
BEGIN
b: Buffer.Buffer;
status: Status;
DO
ENABLE ABORTED => EXIT;
DO
--we compute the values each time around since the value of b can
--change if the watcher shoots down the output.
SELECT TRUE FROM
((b ← outputState.firstBuffer) = NIL) => NULL;
((status ← GetStatus[b.driver.iocb]) # pending) => EXIT;
ENDCASE;
WAIT outputState.outWait;
ENDLOOP;
outputState.timeSendDone ← System.GetClockPulses[]; --still transmiting
b.driver.faceStatus ← ethernetOne[status];
IF status = ok THEN
BEGIN
IF CommFlags.doStats OR CommFlags.driverStats THEN
BEGIN
tries: CARDINAL ← GetRetries[b.driver.iocb];
statEtherSendsCollision1: Stats.StatCounterIndex =
statEtherSendsCollision1;
first: CARDINAL = LOOPHOLE[statEtherSendsCollision1];
IF CommFlags.driverStats THEN
BEGIN
etherStats.packetsSent ← etherStats.packetsSent + 1;
etherStats.wordsSent ← etherStats.wordsSent + b.driver.length;
etherStats.loadTable[tries] ← etherStats.loadTable[tries] + 1;
END;
IF CommFlags.doStats AND tries # 0 THEN
Stats.StatIncr[LOOPHOLE[first + tries]];
END;
END
ELSE
BEGIN
IF CommFlags.driverStats THEN
etherStats.badSendStatus ← etherStats.badSendStatus + 1;
IF CommFlags.doStats THEN Stats.StatIncr[statEtherSendBadStatus];
IF CommFlags.doStats OR CommFlags.driverStats THEN
SELECT status FROM
tooManyCollisions =>
BEGIN
IF CommFlags.driverStats THEN
etherStats.tooManyCollisions ← etherStats.tooManyCollisions + 1;
IF CommFlags.doStats THEN
Stats.StatIncr[statEtherSendsCollisionLoadOverflow];
END;
underrun =>
BEGIN
IF CommFlags.driverStats THEN
etherStats.underrun ← etherStats.underrun + 1;
IF CommFlags.doStats THEN Stats.StatIncr[statEtherSendOverrun];
END;
ENDCASE;
END;
--We don't resend things that screwup
outputState.firstBuffer ← outputState.firstBuffer.next;
FreeBufferAndIocb[Driver.PutOnGlobalDoneQueue, b];
ENDLOOP;
END;
Watcher: PROC =
BEGIN
CheckForIdleInput: ENTRY PROC = INLINE
BEGIN
IF (System.GetClockPulses[] - inputState.timeLastRecv) <
seconds.forty THEN RETURN;
IF CommFlags.doStats THEN Stats.StatIncr[statInputIdle];
IF CommFlags.driverStats THEN
etherStats.idleInput ← etherStats.idleInput + 1;
SmashCSBs[]; --this will leave output dangling
END; --CheckForIdleInput
CheckForStuckOutput: ENTRY PROC = INLINE
BEGIN
SELECT TRUE FROM
outputState.firstBuffer = NIL => NULL;
((System.GetClockPulses[] - outputState.timeSendDone) < seconds.fiveHalf) => RETURN;
ENDCASE =>
BEGIN
--This happens if the transciever is unplugged
TurnOff[ether];
UNTIL outputState.firstBuffer = NIL DO
b ← outputState.firstBuffer;
outputState.firstBuffer ← outputState.firstBuffer.next;
FreeBufferAndIocb[Driver.PutOnGlobalDoneQueue, b];
IF CommFlags.doStats THEN Stats.StatIncr[statPacketsStuckInOutput];
IF CommFlags.driverStats THEN
etherStats.stuckOutput ← etherStats.stuckOutput + 1;
ENDLOOP;
SmashCSBs[];
END;
outputState.timeSendDone ← System.GetClockPulses[];
END; --CheckForStuckOutput
QueueInputBufferLocked: ENTRY PROC = INLINE
BEGIN ENABLE UNWIND => NULL;
SELECT TRUE FROM
(b = NIL) => WAIT watcherState.timer; --this is an alternate WAIT
((b.driver.iocb ← iocbState.free) # NIL) =>
BEGIN
iocbState.free ← iocbState.free.next;
b.driver.faceStatus ← ethernetOne[pending];
QueueInput[
ether,
@b.encapsulation + encapOffset,
b.driver.length - encapOffset,
b.driver.iocb];
IF inputState.firstBuffer = NIL THEN inputState.firstBuffer ← b
ELSE inputState.lastBuffer.next ← b;
inputState.lastBuffer ← b;
inputState.queueLength ← inputState.queueLength + 1;
END;
ENDCASE => --the ENDCASE better be rare!
BEGIN
IF CommFlags.doStats THEN Stats.StatIncr[statsIocbWait];
Driver.ReturnFreeBuffer[b]; --give the buffer back (ARGH!!)
WAIT watcherState.timer; --this is an alternate WAIT
END;
END; --QueueInputBufferLocked
WaitForTimer: ENTRY PROC = INLINE
BEGIN ENABLE UNWIND => NULL;
WAIT watcherState.timer;
END;
b: Buffer.Buffer;
enterLoop: LONG CARDINAL;
inputState.lastMissed ← GetPacketsMissed[ether];
DO
ENABLE ABORTED => EXIT;
enterLoop ← System.GetClockPulses[];
--Check for lost interrupts
IF CheckBuffer[@inputState.firstBuffer] OR
CheckBuffer[@outputState.firstBuffer] THEN WatchCarefully[];
CheckForIdleInput[];
CheckForStuckOutput[];
WHILE (inputState.queueLength < inputState.queueAllowed) DO
--don't lock monitor and wait for a buffer
b ← Driver.GetInputBuffer[tryToWaitForBuffer: TRUE];
QueueInputBufferLocked[]; --give it a chance to wait
IF b = NIL THEN EXIT; --but don't futz around forever
ENDLOOP;
IF CommFlags.doStats OR CommFlags.driverStats THEN
BEGIN
missed: CARDINAL ← GetPacketsMissed[ether];
lost: CARDINAL ← missed - inputState.lastMissed;
IF lost # 0 THEN
BEGIN
IF CommFlags.doStats
THEN Stats.StatBump[statEtherEmptyNoBuffer, lost];
IF CommFlags.driverStats
THEN etherStats.packetsMissed ← etherStats.packetsMissed + lost;
inputState.lastMissed ← missed;
END;
END;
IF (System.GetClockPulses[] - enterLoop) < seconds.one THEN
WaitForTimer[];
ENDLOOP;
END; --Watcher
CheckBuffer: ENTRY PROC[p: POINTER TO Buffer.Buffer]
RETURNS [trouble: BOOLEAN] =
BEGIN
b: Buffer.Buffer ← p↑;
IF b = NIL THEN RETURN[FALSE];
RETURN[(GetStatus[b.driver.iocb] # pending)];
END;
WatchCarefully: PROC =
BEGIN
<<
In status # pending, an interrupt should have happened. 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 ~CheckBuffer[@inputState.firstBuffer] THEN EXIT;
REPEAT FINISHED => WatcherNotify[];
ENDLOOP;
--Check for lost output interrupt
THROUGH [0..25) DO
IF ~CheckBuffer[@outputState.firstBuffer] THEN EXIT;
REPEAT FINISHED => WatcherNotify[];
ENDLOOP;
END;
WatcherNotify: ENTRY PROC =
BEGIN
IF CommFlags.doStats THEN Stats.StatIncr[statEtherLostInterrupts];
SmashCSBs[]; --this will leave output dangling
END;
DecapsulateBuffer: PROC [b: Buffer.Buffer] RETURNS [type: Buffer.Type] =
BEGIN
bytes: CARDINAL ← 2*b.driver.length;
IF bytes < encapBytes THEN GOTO Rejected;
bytes ← bytes - encapBytes;
SELECT b.encapsulation.ethernetOneType FROM
ns =>
BEGIN
IF bytes < b.ns.pktLength THEN GOTO Rejected;
type ← ns;
END;
pup =>
BEGIN
IF bytes < b.pup.pupLength THEN GOTO Rejected;
type ← pup;
END;
translation =>
BEGIN
SELECT b.rawWords[0] FROM
translationRequest => ReceiveRequest[b];
translationResponse => receiveAck[b];
ENDCASE => GOTO Rejected;
type ← processed;
END;
ENDCASE => GOTO Rejected;
EXITS Rejected =>
BEGIN
type ← rejected;
IF CommFlags.driverStats THEN Stats.StatIncr[statPacketsDiscarded];
END;
END;
EncapsulateAndSendPup: PROC[b: Buffer.Buffer, destination: PupTypes.PupHostID] =
BEGIN
b.encapsulation ← [
ethernetOne[
etherSpare1:, etherSpare2:, etherSpare3:, etherSpare4:, etherSpare5:,
translationWorked: TRUE, ethernetOneDest: destination,
ethernetOneSource: myNetwork.pupHostNumber, ethernetOneType: pup]];
b.driver.length ← (b.pup.pupLength + 1 + encapBytes)/2;
SendRawBuffer[b];
END; --EncapsulateAndSendPup
EncapsulateAndSendNS, EncapsulateAndForwardNS: PROC[
b: Buffer.Buffer, destination: HostNumber] =
BEGIN
foundIt: BOOLEAN;
ethernetAddr: PupTypes.PupHostID;
[foundIt, ethernetAddr] ← translate[destination];
IF foundIt THEN
BEGIN
b.encapsulation ← [
ethernetOne[
etherSpare1:, etherSpare2:, etherSpare3:, etherSpare4:, etherSpare5:,
translationWorked: TRUE, ethernetOneDest: ethernetAddr,
ethernetOneSource: myNetwork.pupHostNumber, ethernetOneType: ns]];
b.driver.length ← (b.ns.pktLength + 1 + encapBytes)/2;
END
ELSE
BEGIN
b.encapsulation ← [
ethernetOne[
etherSpare1:, etherSpare2:, etherSpare3:, etherSpare4:, etherSpare5:,
translationWorked: FALSE, ethernetOneDest:, ethernetOneSource:,
ethernetOneType:]];
END;
SendRawBuffer[b];
END; --EncapsulateAndSendNS, EncapsulateAndForwardNS
ForwardPup: PROC[b: Buffer.Buffer, destination: PupTypes.PupHostID]
RETURNS [PupTypes.PupErrorCode] =
{EncapsulateAndSendPup[b, destination]; RETURN[noErrorPupErrorCode]};
SendRawBuffer: ENTRY PROC[b: Buffer.Buffer] =
BEGIN
IF watcherState.pleaseStop THEN Driver.Glitch[DriverNotActive];
IF ~b.encapsulation.translationWorked THEN
BEGIN Driver.PutOnGlobalDoneQueue[b]; RETURN; END;
IF ~hearSelf
AND
(b.encapsulation.ethernetOneDest = myNetwork.pupHostNumber
OR b.encapsulation.ethernetOneDest = PupTypes.allHosts) THEN
BEGIN --sending to ourself, copy it over since we can't hear it
copy: Buffer.Buffer ← Driver.GetInputBuffer[];
IF copy # NIL THEN
BEGIN
Inline.LongCOPY[
from: @b.encapsulation + encapOffset,
nwords: b.driver.length,
to: @copy.encapsulation + encapOffset];
copy.driver ← b.driver;
copy.network ← LONG[@myNetwork]; --LONG because of Mokelumne compiler bug
IF CommFlags.doStats THEN Stats.StatIncr[statEtherPacketsLocal];
IF CommFlags.doStats THEN Stats.StatBump[statEtherWordsLocal, b.driver.length];
Driver.PutOnGlobalInputQueue[copy];
END
ELSE IF CommFlags.doStats THEN Stats.StatIncr[statEtherEmptyFreeQueue];
END;
SendBufferInternal[b];
END;
SendBufferInternal: INTERNAL PROC[b: Buffer.Buffer] =
BEGIN
b.driver.faceStatus ← ethernetOne[pending];
b.next ← NIL;
UNTIL (b.driver.iocb ← iocbState.free) # NIL DO
IF CommFlags.doStats THEN Stats.StatIncr[statsIocbWait];
WAIT iocbState.wait;
ENDLOOP;
iocbState.free ← iocbState.free.next;
QueueOutput[
ether, @b.encapsulation + encapOffset, b.driver.length,
b.driver.iocb];
IF CommFlags.doStats THEN Stats.StatIncr[statEtherPacketsSent];
IF CommFlags.doStats THEN Stats.StatBump[statEtherWordsSent, b.driver.length];
IF outputState.firstBuffer = NIL THEN
BEGIN
outputState.firstBuffer ← b;
outputState.timeSendDone ← System.GetClockPulses[];
END
ELSE outputState.lastBuffer.next ← b;
outputState.lastBuffer ← b;
END;
--for changing the number of buffers while running
numberOfExtraBuffer: CARDINAL = 2;
--this should only be called from Boss
--No MONITOR PROTECTION here.
MaybeChangeNumberOfInputBuffers: PROC[increaseBuffers: BOOLEAN] =
BEGIN
IF increaseBuffers THEN
BEGIN
IF inputState.access = NIL THEN
inputState.access ← Buffer.MakePool[0, numberOfExtraBuffer];
IF inputState.queueAllowed < numberOfExtraBuffer THEN
inputState.queueAllowed ← myNetwork.buffers ←
myNetwork.buffers + numberOfExtraBuffer;
END
ELSE
BEGIN
IF inputState.access # NIL THEN
{Buffer.DestroyPool[inputState.access]; inputState.access ← NIL};
IF inputState.queueAllowed >= numberOfExtraBuffer THEN
inputState.queueAllowed ← myNetwork.buffers ←
myNetwork.buffers - numberOfExtraBuffer;
END;
END;
--COLD code, only used when turning things on+off
AdjustLengtoOfD0EthernetInputQueue: PUBLIC PROC[n: CARDINAL] =
BEGIN inputState.queueLength ← n; END;
CreateDefaultEthernetOneDrivers: PUBLIC PROC RETURNS [BOOLEAN] =
BEGIN
deviceNumber: CARDINAL ← 0;
etherDevice: DeviceHandle ← GetNextDevice[nullDeviceHandle];
IF System.switches['<] = down 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: PROC[
etherDevice: DeviceHandle, deviceNumber: CARDINAL] =
BEGIN
IF deviceNumber # 0 THEN
BEGIN
him: LONG POINTER TO FRAME[EthernetOneDriver] ← NEW EthernetOneDriver;
START him; --so he'll initialize the procedure
him.setupEthernetOneDriver[etherDevice];
END
ELSE SetupEthernetOneDriver[etherDevice];
END;
first: BOOLEAN ← TRUE;
firstPool: Buffer.AccessHandle;
SetupEthernetOneDriver: PROC[etherDevice: DeviceHandle] =
BEGIN
host: Environment.Byte;
ether ← etherDevice;
[, host] ← GetEthernet1Address[ether];
watcherState.pleaseStop ← TRUE;
myNetwork.buffers ← inputState.queueLength ← 1;
Driver.AddDeviceToChain[@myNetwork];
IF TRUE THEN
BEGIN -- Startup BUGs in Boss
firstPool ← Buffer.MakePool[0, myNetwork.buffers];
END;
IF CommFlags.doStats OR CommFlags.driverStats THEN
BEGIN
myNetwork.statsLevel0 ← @etherStats;
etherStats ← [];
END;
END;
ActivateDriver: PROC =
BEGIN
b: Buffer.Buffer;
net, host: Environment.Byte;
iocbs: CARDINAL = (myNetwork.buffers + numberOfExtraBuffer) * 2;
size: CARDINAL = Inline.BITAND[(controlBlockSize + 3), 0FFFCH];
IF ~watcherState.pleaseStop THEN Driver.Glitch[DriverAlreadyActive];
seconds.forty ← System.MicrosecondsToPulses[40000000];
seconds.fiveHalf ← System.MicrosecondsToPulses[2500000];
seconds.one ← System.MicrosecondsToPulses[1000000];
[net, host] ← GetEthernet1Address[ether];
getGarbage ← watcherState.pleaseStop ← FALSE;
TurnOff[ether];
AddCleanup[ether];
inputState.access ← NIL;
inputState.queueLength ← 0;
inputState.queueAllowed ← myNetwork.buffers;
inputState.firstBuffer ← inputState.lastBuffer ← NIL;
outputState.firstBuffer ← outputState.lastBuffer ← NIL;
myEar ← myNetwork.pupHostNumber ← host;
IF controlBlockSize = 0 THEN Driver.Glitch[IOCBSizeIsZero];
iocbState.free ← NIL;
Process.EnableAborts[@iocbState.wait];
Process.DisableTimeout[@iocbState.wait];
iocbState.first ← iocbState.free ← CommUtil.AllocateIocbs[size * iocbs];
FOR i: CARDINAL IN[0..iocbs - 1) DO
iocbState.first ← iocbState.first.next ←
iocbState.first + size;
REPEAT FINISHED =>
{iocbState.first.next ← NIL; iocbState.first ← iocbState.free};
ENDLOOP;
THROUGH [0..myNetwork.buffers) DO
IF (b ← Driver.GetInputBuffer[TRUE]) # NIL THEN
BEGIN
IF iocbState.free = NIL THEN Driver.Glitch[LostAllIocbs];
b.driver.iocb ← iocbState.free;
iocbState.free ← iocbState.free.next;
inputState.queueLength ← inputState.queueLength + 1;
IF CommFlags.doDebug
AND Inline.BITAND[Inline.LowHalf[
@b.encapsulation + encapOffset], 3] # 0 THEN
Driver.Glitch[BufferMustBeAlmostQuadWordAligned];
IF CommFlags.doDebug
AND Inline.BITAND[Inline.LowHalf[b.driver.iocb], 3] # 0 THEN
Driver.Glitch[IOCBMustBeQuadWordAligned];
IF CommFlags.doDebug AND Inline.HighHalf[b.driver.iocb] # 0 THEN
Driver.Glitch[IOCBMustBeInFirstMDS];
b.driver.faceStatus ← ethernetOne[pending];
IF inputState.firstBuffer = NIL THEN inputState.firstBuffer ← b;
IF inputState.lastBuffer # NIL THEN inputState.lastBuffer.next ← b;
inputState.lastBuffer ← b;
END;
ENDLOOP;
[cv: inputState.inWait, mask: inputState.mask] ←
SpecialRuntime.AllocateNakedCondition[];
Process.DisableTimeout[inputState.inWait];
Process.EnableAborts[inputState.inWait];
[cv: outputState.outWait, mask: outputState.mask] ←
SpecialRuntime.AllocateNakedCondition[];
Process.DisableTimeout[outputState.outWait];
Process.EnableAborts[outputState.outWait];
SmashCSBs[];
BEGIN
priority: Process.Priority ← Process.GetPriority[];
Process.SetPriority[ProcessPriorities.priorityIOHigh];
inputState.process ← FORK InInterrupt[];
outputState.process ← FORK OutInterrupt[];
Process.SetPriority[priority];
END;
Process.EnableAborts[@watcherState.timer];
Process.SetTimeout[@watcherState.timer, Process.SecondsToTicks[1]];
watcherState.process ← FORK Watcher[];
CreateCache[];
END;
SetEthernetOneListener: PUBLIC ENTRY PROC[
physicalOrder: CARDINAL, newHostNumber: CARDINAL]
RETURNS [success: BOOLEAN] =
BEGIN
him: LONG POINTER TO FRAME[EthernetOneDriver];
network: Network ← GetNthDeviceLikeMe[physicalOrder];
IF network = NIL THEN RETURN[FALSE];
him ← LOOPHOLE[Runtime.GlobalFrame[LOOPHOLE[network.sendRawBuffer]]];
him.ethernetOneListenForHost[newHostNumber];
RETURN[TRUE];
END;
EthernetOneListenForHost: PROC[newHostNumber: CARDINAL] =
BEGIN myEar ← newHostNumber; SmashCSBs[]; END;
GetNthDeviceLikeMe: PROC[physicalOrder: CARDINAL]
RETURNS [net: Network] =
BEGIN
i: CARDINAL ← 0;
net ← Driver.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 PROC[
physicalOrder: CARDINAL, collectGarbage: BOOLEAN]
RETURNS [success: BOOLEAN] =
BEGIN
him: LONG POINTER TO FRAME[EthernetOneDriver];
network: Network ← GetNthDeviceLikeMe[physicalOrder];
IF network = NIL THEN RETURN[FALSE];
him ← LOOPHOLE[Runtime.GlobalFrame[LOOPHOLE[network.sendRawBuffer]]];
him.getGarbage ← collectGarbage;
RETURN[TRUE];
END;
SmashCSBs: PROC =
BEGIN
b: Buffer.Buffer;
TurnOn[ether, myEar, inputState.mask, outputState.mask, globalStatePtr];
inputState.lastMissed ← GetPacketsMissed[ether];
FOR b ← inputState.firstBuffer, b.next UNTIL b = NIL DO
QueueInput[
ether, @b.encapsulation + encapOffset,
b.driver.length - encapOffset, b.driver.iocb];
ENDLOOP;
inputState.timeLastRecv ← System.GetClockPulses[];
END;
DeleteDriver: PROC =
BEGIN
IF ether # GetNextDevice[nullDeviceHandle] THEN Runtime.SelfDestruct[];
END;
DeactivateDriver: PROC =
BEGIN
b: Buffer.Buffer;
IF watcherState.pleaseStop THEN Driver.Glitch[DriverNotActive];
watcherState.pleaseStop ← TRUE;
Process.Abort[inputState.process]; JOIN inputState.process;
Process.Abort[outputState.process]; JOIN outputState.process;
Process.Abort[watcherState.process]; JOIN watcherState.process;
TurnOff[ether];
SpecialRuntime.DeallocateNakedCondition[inputState.inWait];
SpecialRuntime.DeallocateNakedCondition[outputState.outWait];
inputState.inWait ← outputState.outWait ← NIL;
MaybeChangeNumberOfInputBuffers[FALSE];
IF first THEN
BEGIN
first ← FALSE;
Buffer.DestroyPool[firstPool];
END;
RemoveCleanup[ether];
UNTIL inputState.firstBuffer = NIL DO
b ← inputState.firstBuffer;
inputState.firstBuffer ← b.next;
Driver.ReturnFreeBuffer[b];
ENDLOOP;
UNTIL outputState.firstBuffer = NIL DO
b ← outputState.firstBuffer;
outputState.firstBuffer ← b.next;
Driver.PutOnGlobalDoneQueue[b];
ENDLOOP;
CommUtil.FreeIocbs[iocbState.first];
iocbState.free ← iocbState.first ← NIL;
DeleteCache[];
END;
--* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
--Ugly thing to take care of handling the Ethernet1 (it uses 8 bit addresses).
--This will go away when NS Communications stops using Ethernet1's.
--* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
--types
AddressPair: TYPE = MACHINE DEPENDENT RECORD [
nsAddr: HostNumber, ethernet1Addr: PupTypes.PupHostID, filler: [0..377B]];
CacheEntry: TYPE = LONG POINTER TO CacheObject;
CacheObject: TYPE = MACHINE DEPENDENT RECORD [
nextLink: CacheEntry,
addressPair: AddressPair,
tries: CARDINAL,
timeStamp: System.Pulses,
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;
depth: CARDINAL; --debugging
retryPulses: System.Pulses ← System.MicrosecondsToPulses[2000000]; --two seconds
deactivatePulses: System.Pulses ← System.MicrosecondsToPulses[180000000]; --three minutes
cacheEvent: CONDITION;
demonRunning: BOOLEAN;
receiveAck: PROC[b: Buffer.Buffer] ← InactiveReceiveAck;
translate: PROC[HostNumber] RETURNS [BOOLEAN, PupTypes.PupHostID] ←
InactiveTranslate;
etherHost: PupTypes.PupHostID;
nsHost: HostNumber;
--interface
CreateCache: ENTRY PROC = INLINE
BEGIN
host: Environment.Byte;
cacheQueueHead ← broadCastPairEntry ← myAddressPairEntry ← NIL;
translate ← InactiveTranslate;
receiveAck ← InactiveReceiveAck;
demonRunning ← FALSE;
[, host] ← GetEthernet1Address[ether];
etherHost ← [host];
nsHost ← SpecialSystem.GetProcessorID[];
END;
StartCache: ENTRY PROC = INLINE
BEGIN
nsHost: HostNumber ← SpecialSystem.GetProcessorID[];
translate ← Translate;
receiveAck ← ReceiveAck;
Process.SetTimeout[@cacheEvent, Process.SecondsToTicks[1]];
broadCastPairEntry ← AddAddressPair[[System.broadcastHostNumber, PupTypes.allHosts, 0]];
myAddressPairEntry ← AddAddressPair[[nsHost, etherHost, 0]];
demonRunning ← TRUE;
Process.Detach[FORK Demon[]];
END;
DeleteCache: PROC = INLINE
BEGIN
e: CacheEntry;
DeleteCacheLocked: ENTRY PROC = 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;
CommHeap.FreeNode[p: e];
ENDLOOP;
END;
FindEntry: INTERNAL PROC[nsAddr: HostNumber] RETURNS [entry: CacheEntry] =
BEGIN
IF CommFlags.doStats THEN depth ← 0;
entry ← cacheQueueHead;
WHILE entry # NIL DO
IF nsAddr = entry.addressPair.nsAddr THEN RETURN;
entry ← entry.nextLink;
IF CommFlags.doStats THEN depth ← depth + 1;
ENDLOOP;
END;
AddEntry: INTERNAL PROC[entry: CacheEntry] =
{entry.nextLink ← cacheQueueHead; cacheQueueHead ← entry};
RemoveEntry: INTERNAL PROC[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: PROC[nsAddr: HostNumber]
RETURNS [foundIt: BOOLEAN, ethernet1Addr: PupTypes.PupHostID] =
BEGIN StartCache[]; [foundIt, ethernet1Addr] ← Translate[nsAddr]; END;
Translate: ENTRY PROC[nsAddr: HostNumber]
RETURNS [foundIt: BOOLEAN, ethernet1Addr: PupTypes.PupHostID] =
BEGIN
e: CacheEntry;
foundIt ← FALSE;
IF (e ← FindEntry[nsAddr]) # NIL THEN
BEGIN
IF e # cacheQueueHead THEN --put e at the head of the queue
BEGIN
IF CommFlags.doStats THEN Stats.StatBump[cacheDepth, depth];
RemoveEntry[e];
AddEntry[e];
END;
SELECT e.status FROM
active =>
BEGIN
foundIt ← TRUE;
ethernet1Addr ← e.addressPair.ethernet1Addr;
e.timeStamp ← System.GetClockPulses[];
END;
zombie =>
BEGIN
e.status ← new;
e.tries ← 0;
e.timeStamp ← System.GetClockPulses[];
NOTIFY cacheEvent;
END;
ENDCASE => NULL;
END --of found it
ELSE --entry not found, so add a new one
BEGIN
IF CommFlags.doStats THEN Stats.StatIncr[cacheFault];
e ← CommHeap.MakeNode[n: SIZE[CacheObject]];
e.status ← new;
e.tries ← 0;
e.timeStamp ← System.GetClockPulses[];
e.addressPair ← [nsAddr: nsAddr, ethernet1Addr:, filler:];
AddEntry[e];
NOTIFY cacheEvent;
END;
END;
--assume protection by lock
AddAddressPair: INTERNAL PROC[aP: AddressPair] RETURNS [e: CacheEntry] =
BEGIN
e ← FindEntry[aP.nsAddr];
SELECT e FROM
(NIL) => {e ← CommHeap.MakeNode[n: SIZE[CacheObject]]; AddEntry[e]};
(broadCastPairEntry), (myAddressPairEntry) => RETURN;
ENDCASE;
e.addressPair ← aP;
e.status ← active;
e.timeStamp ← System.GetClockPulses[];
END;
--assume protection by lock
DeallocateEntry: INTERNAL PROC[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.GetClockPulses[]
ELSE BEGIN RemoveEntry[e]; CommHeap.FreeNode[p: e]; END;
END;
--locks
Demon: ENTRY PROC =
BEGIN
demonRunning ← TRUE;
Process.SetPriority[ProcessPriorities.priorityIOHigh];
UNTIL watcherState.pleaseStop DO
ENABLE ABORTED => EXIT;
pendingEntries: BOOLEAN ← FALSE;
e: CacheEntry;
WAIT cacheEvent;
e ← cacheQueueHead;
WHILE (e # NIL) DO
age: System.Pulses ← [System.GetClockPulses[] - e.timeStamp];
nextE: CacheEntry ← e.nextLink;
SELECT e.status FROM
active, zombie => { IF age > deactivatePulses THEN DeallocateEntry[e]};
pending =>
BEGIN
pendingEntries ← TRUE;
IF age > retryPulses THEN
BEGIN
e.tries ← e.tries + 1;
IF e.tries > retryLimit THEN
BEGIN
e.status ← zombie;
IF CommFlags.doStats THEN Stats.StatIncr[unsuccessfulTranslation];
END
ELSE
BEGIN
IF CommFlags.doStats THEN Stats.StatIncr[translationRetries];
SendRequest[e];
e.timeStamp ← System.GetClockPulses[];
END;
END;
END;
new =>
BEGIN
pendingEntries ← TRUE;
SendRequest[e];
e.status ← pending;
e.timeStamp ← System.GetClockPulses[];
END;
ENDCASE => ERROR;
e ← nextE;
ENDLOOP; --end of queue entries loop
IF pendingEntries THEN
Process.SetTimeout[@cacheEvent, Process.SecondsToTicks[1]]
ELSE Process.SetTimeout[@cacheEvent, Process.SecondsToTicks[60*5]];
ENDLOOP; --end of infinite loop
receiveAck ← InactiveReceiveAck;
translate ← InactiveTranslate;
BEGIN
e, nextE: CacheEntry;
e ← cacheQueueHead;
cacheQueueHead ← myAddressPairEntry ← broadCastPairEntry ← NIL;
WHILE e # NIL DO
nextE ← e.nextLink; CommHeap.FreeNode[p: e]; e ← nextE; ENDLOOP;
END;
demonRunning ← FALSE;
END;
--assume locked
SendRequest: INTERNAL PROC[e: CacheEntry] =
BEGIN
b: Buffer.Buffer;
request: LONG POINTER TO AddressPair;
IF (b ← Driver.GetInputBuffer[FALSE]) # NIL THEN
BEGIN
--broadcast the trnslation request
b.encapsulation ← [
ethernetOne[
etherSpare1:, etherSpare2:, etherSpare3:, etherSpare4:, etherSpare5:,
translationWorked:, ethernetOneDest: PupTypes.allHosts,
ethernetOneSource: myNetwork.pupHostNumber, ethernetOneType: translation]];
b.driver.length ← (1 + encapBytes)/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: PROC[b: Buffer.Buffer] = {b.requeueProcedure[b]};
ReceiveAck: ENTRY PROC[b: Buffer.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 PROC[b: Buffer.Buffer] =
BEGIN
request, requesterAddr: LONG POINTER TO AddressPair;
request ← LOOPHOLE[@b.rawWords[1]];
IF request.nsAddr = nsHost 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 CommFlags.doStats THEN Stats.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 PROC[
aP: AddressPair, to: DriverTypes.Byte, b: Buffer.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.pupHostNumber, ethernetOneType: translation]];
b.driver.length ← (1 + encapBytes)/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
setupEthernetOneDriver ← SetupEthernetOneDriver; --for multi instances
ethernetOneListenForHost ← EthernetOneListenForHost;
END. --EthernetOneDriver