-- File: EthernetDriver.mesa - last edit:
-- AOF 22-Feb-88 17:34:56
-- MI 1-Aug-86 16:03:13
-- SMA 21-May-86 15:46:20
-- Copyright (C) 1984, 1985, 1986, 1987, 1988 by Xerox Corporation. All rights reserved.
DIRECTORY
Buffer USING [
AccessHandle, Buffer, dataLinkReserve, DataBytesPerRawBuffer, Dequeue,
DestroyPool, Enqueue, MakePool, QueueInitialize, QueueObject],
CommFlags USING [doDebug, doStats, driverStats],
CommPriorities USING [driver],
CommSwitches USING [noEthernet],
CommUtil USING [AllocateIocbs, FreeIocbs],
Driver USING [
AddDeviceToChain, Glitch, GetDeviceChain, GetInputBuffer, Device,
DeviceObject, ChangeNumberOfInputBuffers, PutOnGlobalDoneQueue,
PutOnGlobalInputQueue, RemoveDeviceFromChain, ReturnFreeBuffer],
Environment USING [bytesPerWord],
EthernetFace USING [
AddCleanup, controlBlockSize, DeviceHandle, GetPacketLength, GetRetries,
GetStatus, GetPacketsMissed, GlobalStatePtr, hearSelf, QueueInput,
GetNextDevice, nullDeviceHandle, QueueOutput, RemoveCleanup, Status,
TurnOn, TurnOff, globalStateSize],
EthernetDriverFriends USING [EtherStatsInfo],
HostNumbers USING [IsMulticastID],
IEEE8023 USING [
Encapsulation, ethernetBroadcastHost, minWordsPerEthernetPacket,
EncapObject, EthernetCRC, maxBytesPerEthernetPacket],
Inline USING [LongCOPY],
Stats USING [StatBump, StatIncr, StatCounterIndex],
PrincOpsMinus USING [NewSelf],
Process USING [
Abort, DisableAborts, DisableTimeout, EnableAborts, GetPriority,
MsecToTicks, Priority, SetPriority, SetTimeout],
ProcessorFace USING [SetMP],
ResidentHeap USING [MakeNode, FreeNode],
Runtime USING [GlobalFrame, SelfDestruct],
SpecialCommunication USING [],
SpecialRuntime USING [AllocateNakedCondition, DeallocateNakedCondition],
SpecialSystem USING [
ProcessorID, HostNumber, GetProcessorID, nullHostNumber],
System USING [
GetClockPulses, Pulses, MicrosecondsToPulses, switches--['>]--],
Zone USING [Status];
EthernetDriver: MONITOR
IMPORTS
Buffer, CommUtil, Driver, Stats, EthernetFace, Inline, HostNumbers,
PrincOpsMinus, Process, ProcessorFace, ResidentHeap, Runtime, SpecialRuntime,
System, SpecialSystem
EXPORTS Buffer, IEEE8023, SpecialCommunication, System =
BEGIN
--EXPORTed TYPEs
Device: PUBLIC TYPE = Driver.Device; --to Buffer
HostNumber: PUBLIC <<System>> TYPE = SpecialSystem.HostNumber;
ether: EthernetFace.DeviceHandle;
myEar: HostNumber; --address I listen for
bpw: NATURAL = Environment.bytesPerWord;
localDevice: Device; --cache this to get around compiler
me: SpecialSystem.ProcessorID; --my real (hardware) address
all: SpecialSystem.HostNumber = IEEE8023.ethernetBroadcastHost;
getGarbage: BOOLEAN ← FALSE; --when true, we deliver any packet
globalStatePtr: EthernetFace.GlobalStatePtr; --Allocate space if needed
setupEthernetDriver: PROC[etherDevice: EthernetFace.DeviceHandle];
inputState: RECORD[
mask: WORD,
process: PROCESS,
lastMissed: CARDINAL,
q: Buffer.QueueObject,
access: Buffer.AccessHandle,
timeLastRecv: LONG CARDINAL,
inWait: LONG POINTER TO CONDITION,
queueAllowed, extraBuffers: NATURAL];
outputState: RECORD[
process: PROCESS,
mask: WORD,
q: Buffer.QueueObject,
outWait: LONG POINTER TO CONDITION,
timeSendDone: LONG CARDINAL];
watcherState: RECORD[pleaseStop: BOOLEAN, process: PROCESS, timer: CONDITION];
seconds: RECORD[one, fiveHalf, forty: LONG CARDINAL];
--IOCBs
iocbState: RECORD[first, free: FreeIocb, avail: INTEGER];
FreeIocb: TYPE = LONG POINTER TO IocbObject;
IocbObject: TYPE = RECORD[
next: FreeIocb, rest: SEQUENCE COMPUTED CARDINAL OF WORD];
--THE NETWORK OBJECT FOR THIS DRIVER
myDevice: Driver.DeviceObject ←
[matrix: NIL, sendRawBuffer: SendRawBuffer,
activateDriver: ActivateDriver, deactivateDriver: DeactivateDriver,
deleteDriver: DeleteDriver, buffers:, device: ethernet,
changeNumberOfInputBuffers: MaybeChangeNumberOfInputBuffers,
receiveBufferLen: 1500, -- This is the DLL size in 'bytes'.
alive: TRUE, index:, next: NIL, lineSpeed: 10000, lineNumber: 0, stats: NIL];
DriverNotActive: ERROR = CODE;
DriverAlreadyActive: ERROR = CODE;
LinkLayerNotMod4Minus1: ERROR = CODE;
IOCBSizeIsZero: ERROR = CODE;
LostAllIocbs: ERROR = CODE;
LostSomeIocbs: ERROR = CODE;
NilIocbInBuffer: ERROR = CODE;
OverlayingIocb: ERROR = CODE;
ResidentZoneTrouble: ERROR = CODE;
BadHostID: ERROR = CODE;
PacketTooLarge: ERROR = CODE;
numberOfExtraBuffer: CARDINAL = 4;
etherStats: EthernetDriverFriends.EtherStatsInfo;
<<
On receive the CRC32 is also received, thus the difference in the values
>>
maxEthernetWordsRcv: NATURAL = IEEE8023.maxBytesPerEthernetPacket / bpw;
maxEthernetBytesXmt: NATURAL =
IEEE8023.maxBytesPerEthernetPacket - (bpw * SIZE[IEEE8023.EthernetCRC]);
<<
The physical overhead is the number of bytes used by the MAC layer to
encapsulate a Network Protocol Data Unit. This physical overhead includes
802.2 encapsulation.
>>
physOverhead: NATURAL = bpw *
(SIZE[IEEE8023.EncapObject] + SIZE[IEEE8023.EthernetCRC]);
<<
This driver starts the reception of physical frame offset into the space
reserved by the buffer manager for the LLC. This is so frames received
using one LLC (eg. Ethernet) can be encapsulated using another LLC (eg.
IEEE 802.2) without wiping out the FixedOverhead portion of the buffer.
The offset is based on private knowledge that this driver shouldn't
know about, the lengths of the various encapsulations. The longest know
to date is an old Ethernet packet encapsulated using the IEEE 802.2
extended type field. That is SIZE[IEEE8023.EncapObject] words long.
The shortest is Ethernet.
errorCheck: NATURAL[22..1518] = Buffer.dataLinkReserve; -- this must be true
>>
offsetToDataUnit: NATURAL = --this value is in WORDs
(Buffer.dataLinkReserve / bpw) - SIZE[IEEE8023.EncapObject];
checking: BOOLEAN ← CommFlags.doDebug;
SanityCheck: --INTERNAL-- PROC = --INLINE--
BEGIN
IF CommFlags.doDebug THEN
BEGIN
found: INTEGER ← 0;
IF ~checking THEN RETURN;
FOR link: FreeIocb ← iocbState.free, link.next UNTIL link = NIL DO
found ← found + 1;
ENDLOOP;
FOR b: Buffer.Buffer ← inputState.q.first, b.fo.next UNTIL b = NIL DO
IF b.fo.driver.iocb # NIL THEN found ← found + 1
ELSE Driver.Glitch[NilIocbInBuffer];
ENDLOOP;
FOR b: Buffer.Buffer ← outputState.q.first, b.fo.next UNTIL b = NIL DO
IF b.fo.driver.iocb # NIL THEN found ← found + 1
ELSE Driver.Glitch[NilIocbInBuffer];
ENDLOOP;
IF found # iocbState.avail THEN Driver.Glitch[LostSomeIocbs];
END;
END; --SanityCheck
GetBufferAndIocb: INTERNAL PROC[] RETURNS[b: Buffer.Buffer] =
BEGIN
receiveLength: NATURAL = myDevice.receiveBufferLen + physOverhead;
IF CommFlags.doDebug THEN SanityCheck[];
SELECT TRUE FROM
(iocbState.free = NIL) =>
{IF CommFlags.doStats THEN Stats.StatIncr[statsIocbWait]; RETURN[NIL]};
((b ← Driver.GetInputBuffer[FALSE, receiveLength]) = NIL) => RETURN;
ENDCASE;
IF CommFlags.doDebug AND (b.fo.driver.iocb # NIL) THEN
Driver.Glitch[OverlayingIocb];
b.fo.driver.iocb ← iocbState.free;
iocbState.free ← iocbState.free.next;
--b.fo.driver.length ← receiveLength;
b.fo.driver.faceStatus ← ethernet[pending];
END; --GetBufferAndIocb
GetBufferAndIocbEntry: ENTRY PROC[] RETURNS[b: Buffer.Buffer] = INLINE
{RETURN GetBufferAndIocb[]}; --GetBufferAndIocbEntry
FreeBufferAndIocb: INTERNAL PROC[
queueProc: PROC[b: Buffer.Buffer], b: Buffer.Buffer] =
BEGIN
iocb: FreeIocb ← b.fo.driver.iocb;
IF iocb # NIL THEN
BEGIN
iocb.next ← iocbState.free;
iocbState.free ← iocb;
b.fo.driver.iocb ← NIL;
END;
IF CommFlags.doDebug THEN SanityCheck[];
queueProc[b];
END; --FreeBufferAndIocb
InInterrupt: ENTRY PROC =
BEGIN
words: CARDINAL;
acceptBuffer: BOOLEAN;
this, new: Buffer.Buffer;
status: EthernetFace.Status;
DO
ENABLE ABORTED => {inputState.process ← NIL; EXIT};
IF inputState.q.length = 0 THEN
BEGIN
<<
If the queue length was previously 0, then we can wait for the first
interrupt. 'inputState.inWait' has no timeout, so the process should
only wake when a packet is available. If the buffer is still pending,
something is amiss.
>>
WHILE inputState.q.length = 0 DO WAIT inputState.inWait; ENDLOOP;
status ← EthernetFace.GetStatus[inputState.q.first.fo.driver.iocb];
IF CommFlags.doStats AND status # pending THEN
Stats.StatIncr[statEtherInterruptDuringInterrupt];
END
ELSE status ← EthernetFace.GetStatus[inputState.q.first.fo.driver.iocb];
UNTIL status # pending DO
WAIT inputState.inWait;
status ← EthernetFace.GetStatus[inputState.q.first.fo.driver.iocb];
IF CommFlags.doStats AND status = pending
THEN Stats.StatIncr[statEtherMissingStatus];
ENDLOOP;
this ← Buffer.Dequeue[@inputState.q];
words ← EthernetFace.GetPacketLength[this.fo.driver.iocb];
IF words < IEEE8023.minWordsPerEthernetPacket THEN status ← otherError;
IF words > maxEthernetWordsRcv THEN status ← packetTooLong;
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
badAlignmentButOkCrc =>
BEGIN
acceptBuffer ← TRUE; --we're going to keep this one
IF CommFlags.driverStats THEN etherStats.badAlignmentButOkCrc ←
etherStats.badAlignmentButOkCrc + 1;
IF CommFlags.doStats THEN Stats.StatIncr[statEtherReceivedNot16];
END;
packetTooLong =>
BEGIN
IF CommFlags.driverStats
THEN etherStats.packetTooLong ← etherStats.packetTooLong + 1;
IF CommFlags.doStats THEN Stats.StatIncr[statEtherReceivedTooLong];
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;
SELECT TRUE FROM
(acceptBuffer) =>
BEGIN
inputState.timeLastRecv ← this.fo.time ← System.GetClockPulses[];
this.fo.driver.faceStatus ← ethernet[status];
this.fo.network ← localDevice;
this.linkLayer ← [(this.linkLayer.blockPointer + offsetToDataUnit),
0, (this.fo.driver.length ← words * bpw)]; --convert length to bytes
IF CommFlags.driverStats THEN
BEGIN
etherStats.packetsRecv ← etherStats.packetsRecv + 1;
etherStats.wordsRecv ← etherStats.wordsRecv + words;
END;
IF CommFlags.doStats THEN
BEGIN
Stats.StatIncr[statEtherPacketsReceived];
Stats.StatBump[statEtherWordsReceived, words];
END;
FreeBufferAndIocb[Driver.PutOnGlobalInputQueue, this]; --good input
IF (new ← GetBufferAndIocb[]) = NIL THEN
BEGIN
IF CommFlags.doStats THEN Stats.StatIncr[statEtherEmptyFreeQueue];
NOTIFY watcherState.timer; --couldn't or didn't want a new buffer
LOOP; --there's nothing left to do here
END;
END;
(Buffer.DataBytesPerRawBuffer[this] < myDevice.receiveBufferLen) =>
BEGIN
FreeBufferAndIocb[Driver.ReturnFreeBuffer, this]; --can't reuse
IF (new ← GetBufferAndIocb[]) = NIL THEN
BEGIN
IF CommFlags.doStats THEN Stats.StatIncr[statEtherEmptyFreeQueue];
NOTIFY watcherState.timer; --couldn't or didn't want a new buffer
LOOP; --there's nothing left to do here
END;
END;
ENDCASE => {new ← this; new.fo.next ← NIL}; --recycle this buffer
--add new buffer to end of input chain
new.fo.driver.faceStatus ← ethernet[pending];
EthernetFace.QueueInput[
ether, (new.linkLayer.blockPointer + offsetToDataUnit),
(new.fo.driver.length / bpw) - offsetToDataUnit, new.fo.driver.iocb];
Buffer.Enqueue[@inputState.q, new];
ENDLOOP;
END; --InInterrupt
OutInterrupt: ENTRY PROC =
BEGIN
b: Buffer.Buffer;
status: EthernetFace.Status;
DO
ENABLE ABORTED => {outputState.process ← NIL; EXIT};
--UNTIL 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
(outputState.q.length = 0) => NULL; --any output queued?
--this one's complete
((status ← EthernetFace.GetStatus[
outputState.q.first.fo.driver.iocb]) # pending) => EXIT;
--then why did we interrupt??
ENDCASE => IF CommFlags.doStats THEN
Stats.StatIncr[statEtherMissingStatus];
WAIT outputState.outWait;
ENDLOOP;
b ← Buffer.Dequeue[@outputState.q]; --take it out of queue
b.fo.driver.faceStatus ← ethernet[status]; --and record the status
outputState.timeSendDone ← System.GetClockPulses[]; --still transmiting
IF status = ok THEN
BEGIN
IF CommFlags.doStats OR CommFlags.driverStats THEN
BEGIN
tries: CARDINAL = EthernetFace.GetRetries[b.fo.driver.iocb];
IF CommFlags.driverStats THEN
BEGIN
etherStats.packetsSent ← etherStats.packetsSent + 1;
etherStats.wordsSent ← etherStats.wordsSent +
b.fo.driver.length / bpw;
etherStats.loadTable[tries] ← etherStats.loadTable[tries] + 1;
END;
IF CommFlags.doStats AND tries # 0 THEN
Stats.StatIncr[LOOPHOLE[
Stats.StatCounterIndex[statEtherSendsCollision1].ORD + 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
FreeBufferAndIocb[Driver.PutOnGlobalDoneQueue, b];
ENDLOOP;
END; --OutInterrupt
Watcher: PROC =
BEGIN
CheckQueues: ENTRY PROC = INLINE
BEGIN
enterTime ← System.GetClockPulses[]; --record time at start
--STUCK OUTPUT
SELECT TRUE FROM
(outputState.q.length = 0) => NULL;
((enterTime - outputState.timeSendDone) > seconds.fiveHalf) =>
BEGIN
IF CommFlags.doStats THEN Stats.StatIncr[statPacketsStuckInOutput];
IF CommFlags.driverStats THEN
etherStats.stuckOutput ← etherStats.stuckOutput + 1;
outputState.timeSendDone ← enterTime;
GOTO smash;
END;
ENDCASE;
--IDLE INPUT
IF ((enterTime - inputState.timeLastRecv) > seconds.forty) THEN
BEGIN
IF CommFlags.doStats THEN Stats.StatIncr[statInputIdle];
IF CommFlags.driverStats THEN
etherStats.idleInput ← etherStats.idleInput + 1;
inputState.timeLastRecv ← enterTime;
GOTO smash;
END;
EXITS smash => SmashCSBs[];
END; --CheckQueues
QueueInputOrWait: ENTRY PROC = INLINE
BEGIN
ENABLE UNWIND => NULL;
SELECT TRUE FROM
(b = NIL) => NULL; --just WAIT
(CommFlags.doDebug AND (b.fo.driver.iocb # NIL)) =>
Driver.Glitch[OverlayingIocb];
((b.fo.driver.iocb ← iocbState.free) # NIL) =>
BEGIN
iocbState.free ← iocbState.free.next;
b.fo.driver.faceStatus ← ethernet[pending];
-- EthernetFace uses words for degree of 'length'
EthernetFace.QueueInput[
ether, LOOPHOLE[b.linkLayer.blockPointer + offsetToDataUnit],
(bytes / bpw) - offsetToDataUnit, b.fo.driver.iocb];
Buffer.Enqueue[@inputState.q, b];
IF CommFlags.doDebug THEN SanityCheck[];
RETURN; --and get out of here with b # NIL
END;
ENDCASE => --the ENDCASE better be rare!
BEGIN
IF CommFlags.doStats THEN Stats.StatIncr[statsIocbWait];
Driver.ReturnFreeBuffer[b]; b ← NIL --give the buffer back (ARGH!!)
END;
WAIT watcherState.timer; --this is an alternate WAIT
IF CommFlags.doStats OR CommFlags.driverStats THEN
BEGIN
lost, missed: CARDINAL;
missed ← EthernetFace.GetPacketsMissed[ether];
lost ← 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;
END; --QueueInputOrWait
bytes: NATURAL;
b: Buffer.Buffer;
enterTime: LONG CARDINAL;
DO
ENABLE ABORTED => EXIT;
--Check for lost interrupts
IF CheckBuffer[@inputState.q.first] OR
CheckBuffer[@outputState.q.first] THEN WatchCarefully[];
CheckQueues[]; --for idle input and stuck output
WHILE (inputState.q.length < inputState.queueAllowed) DO
bytes ← myDevice.receiveBufferLen + physOverhead; --compute length
--don't lock monitor and wait for a buffer
b ← Driver.GetInputBuffer[TRUE, bytes]; --use cached length
QueueInputOrWait[]; -- queue buffer or give a chance to wait
IF b = NIL THEN EXIT; --but don't futz around forever
ENDLOOP;
IF (System.GetClockPulses[] - enterTime) < seconds.one THEN
{b ← NIL; QueueInputOrWait[]}; --just using as WAIT
ENDLOOP;
END; --Watcher
CheckBuffer: ENTRY PROC[p: LONG POINTER TO Buffer.Buffer]
RETURNS [trouble: BOOLEAN] =
BEGIN
b: Buffer.Buffer ← p↑;
IF b = NIL THEN RETURN[FALSE];
RETURN[(EthernetFace.GetStatus[b.fo.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.q.first] THEN EXIT;
REPEAT FINISHED => WatcherNotify[];
ENDLOOP;
--Check for lost output interrupt
THROUGH [0..25) DO
IF ~CheckBuffer[@outputState.q.first] THEN EXIT;
REPEAT FINISHED => WatcherNotify[];
ENDLOOP;
END;
WatcherNotify: ENTRY PROC =
BEGIN
IF CommFlags.doStats THEN Stats.StatIncr[statEtherLostInterrupts];
SmashCSBs[];
END;
SendRawBuffer: ENTRY PROC[b: Buffer.Buffer] =
BEGIN
ENABLE UNWIND => NULL; --I don't see any signals, but...
e: IEEE8023.Encapsulation = LOOPHOLE[b.linkLayer.blockPointer];
SELECT TRUE FROM
(~myDevice.alive) =>
{b.fo.status ← rejected; Driver.PutOnGlobalDoneQueue[b]; RETURN};
(e.ethernetDest = SpecialSystem.nullHostNumber) =>
{b.fo.status ← invalidDestAddr; Driver.PutOnGlobalDoneQueue[b]; RETURN};
(EthernetFace.hearSelf) => NULL; --is okay
(HostNumbers.IsMulticastID[@e.ethernetDest]),
(e.ethernetDest = me) =>
BEGIN --sending to ourself, copy it over since we can't hear it
-- Order buffer for length 'b.fo.driver.length' bytes.
copy: Buffer.Buffer ← Driver.GetInputBuffer[
FALSE, Buffer.DataBytesPerRawBuffer[b]];
IF copy # NIL THEN
BEGIN
words: NATURAL = (b.fo.driver.length + bpw - 1) / bpw;
Inline.LongCOPY[
from: e, to: copy.linkLayer.blockPointer, nwords: words];
copy.linkLayer.startIndex ← 0;
copy.linkLayer.stopIndexPlusOne ← b.fo.driver.length; --copy length
copy.fo.driver.length ← b.fo.driver.length; --same length as last one
copy.fo.driver.faceStatus ← ethernet[ok]; --should be ok from here on
copy.fo.network ← localDevice; --set the driver field
copy.fo.time ← System.GetClockPulses[]; --pretty close to this time
IF CommFlags.doStats THEN
BEGIN
Stats.StatIncr[statEtherPacketsLocal];
Stats.StatBump[statEtherWordsLocal, words];
END;
Driver.PutOnGlobalInputQueue[copy]; --doesn't have iocb associated
END
ELSE IF CommFlags.doStats THEN Stats.StatIncr[statEtherEmptyFreeQueue];
END;
ENDCASE;
SendBufferInternal[b];
END; --SendRawBuffer
SendBufferInternal: INTERNAL PROC[b: Buffer.Buffer] =
BEGIN
e: IEEE8023.Encapsulation = LOOPHOLE[b.linkLayer.blockPointer];
-- EthernetFace uses words for degree of 'length'
words: CARDINAL ← MAX[
(b.fo.driver.length + bpw - 1) / bpw, IEEE8023.minWordsPerEthernetPacket];
IF CommFlags.doDebug AND (b.fo.driver.length > maxEthernetBytesXmt) THEN
Driver.Glitch[PacketTooLarge];
b.fo.driver.faceStatus ← ethernet[pending];
IF CommFlags.doDebug AND (b.fo.driver.iocb # NIL) THEN
Driver.Glitch[OverlayingIocb];
IF (b.fo.driver.iocb ← iocbState.free) = NIL THEN
BEGIN
IF CommFlags.doStats THEN Stats.StatIncr[statsIocbWait];
b.fo.status ← aborted; Driver.PutOnGlobalDoneQueue[b]; RETURN;
END;
iocbState.free ← iocbState.free.next;
EthernetFace.QueueOutput[ether, e, words, b.fo.driver.iocb];
SELECT TRUE FROM
(outputState.q.length = 0) =>
outputState.timeSendDone ← System.GetClockPulses[];
(CommFlags.doStats) =>
Stats.StatIncr[statEtherSendFromOutputQueue];
ENDCASE;
Buffer.Enqueue[@outputState.q, b];
IF CommFlags.doDebug THEN SanityCheck[];
IF CommFlags.doStats THEN Stats.StatIncr[statEtherPacketsSent];
IF CommFlags.doStats THEN Stats.StatBump[statEtherWordsSent, words];
END; --SendBufferInternal
--No MONITOR PROTECTION here.
MaybeChangeNumberOfInputBuffers: PROC[increaseBuffers: BOOLEAN] =
BEGIN
IF increaseBuffers THEN
BEGIN
IF inputState.access = NIL THEN
BEGIN
inputState.access ← Buffer.MakePool[0, inputState.extraBuffers];
--make sure we don't over commit the buffers we've allocated
IF inputState.queueAllowed < inputState.extraBuffers THEN
inputState.queueAllowed ← myDevice.buffers ←
myDevice.buffers + inputState.extraBuffers;
END;
END
ELSE
BEGIN
IF inputState.access # NIL THEN
BEGIN
Buffer.DestroyPool[inputState.access]; inputState.access ← NIL;
--don't let the queue to negative | even empty
IF inputState.queueAllowed > inputState.extraBuffers THEN
inputState.queueAllowed ← myDevice.buffers ←
inputState.queueAllowed - inputState.extraBuffers;
END;
END;
END; --MaybeChangeNumberOfInputBuffers
--COLD code, only used when turning things on+off
AdjustLengtoOfD0EthernetInputQueue: PUBLIC PROC[n: CARDINAL] =
{inputState.queueAllowed ← n};
CreateDefaultEthernetDrivers: PUBLIC <<IEEE8023>> PROC RETURNS [BOOLEAN] =
BEGIN
deviceNumber: CARDINAL ← 0;
etherDevice: EthernetFace.DeviceHandle;
etherDevice ← EthernetFace.GetNextDevice[EthernetFace.nullDeviceHandle];
IF System.switches[CommSwitches.noEthernet] = down THEN RETURN[FALSE];
IF etherDevice = EthernetFace.nullDeviceHandle THEN RETURN[FALSE];
WHILE etherDevice # EthernetFace.nullDeviceHandle DO
CreateAnEthernetDriver[etherDevice, deviceNumber];
etherDevice ← EthernetFace.GetNextDevice[etherDevice];
deviceNumber ← deviceNumber + 1;
ENDLOOP;
RETURN[TRUE];
END; --CreateDefaultEthernetDrivers
CreateAnEthernetDriver: PROC[
etherDevice: EthernetFace.DeviceHandle, deviceNumber: CARDINAL] =
BEGIN
IF deviceNumber # 0 THEN
BEGIN
him: LONG POINTER TO FRAME[EthernetDriver];
him ← --NEW EthernetDriver-- PrincOpsMinus.NewSelf[];
START him; --so he'll initialize the procedure
him.setupEthernetDriver[etherDevice];
END
ELSE SetupEthernetDriver[etherDevice];
END; --CreateAnEthernetDriver
SetupEthernetDriver: PROC[etherDevice: EthernetFace.DeviceHandle] =
BEGIN
Wait: ENTRY PROC = INLINE {WAIT condition};
condition: CONDITION;
ether ← etherDevice;
myEar ← me ← SpecialSystem.GetProcessorID[];
IF HostNumbers.IsMulticastID[@myEar] THEN
BEGIN
Process.DisableTimeout[@condition]; --no tricks here
Process.DisableAborts[@condition]; --fools are so smart
ProcessorFace.SetMP[989]; --should probably warn PSCO
--UNTIL HELL FREEZES OVER-- DO Wait[]; ENDLOOP;
END;
watcherState.pleaseStop ← TRUE;
inputState.extraBuffers ← numberOfExtraBuffer; --set default
myDevice.buffers ← inputState.queueAllowed ← 1;
Driver.AddDeviceToChain[(localDevice ← @myDevice)];
IF CommFlags.driverStats THEN
BEGIN
myDevice.stats ← @etherStats;
etherStats ← [];
END;
END; --SetupEthernetDriver
ActivateDriver: PROC =
BEGIN
EnterMonitor: ENTRY PROC = INLINE {SmashCSBs[]};
iocbs: CARDINAL = (myDevice.buffers + inputState.extraBuffers) * 2;
IF ~watcherState.pleaseStop THEN Driver.Glitch[DriverAlreadyActive];
seconds.one ← System.MicrosecondsToPulses[1D6];
seconds.fiveHalf ← System.MicrosecondsToPulses[25D5];
seconds.forty ← System.MicrosecondsToPulses[40D6];
getGarbage ← watcherState.pleaseStop ← FALSE;
EthernetFace.TurnOff[ether];
EthernetFace.AddCleanup[ether];
inputState.access ← NIL;
inputState.queueAllowed ← myDevice.buffers;
Buffer.QueueInitialize[@inputState.q];
Buffer.QueueInitialize[@outputState.q];
inputState.timeLastRecv ← outputState.timeSendDone ← System.GetClockPulses[];
myEar ← me ← SpecialSystem.GetProcessorID[];
IF HostNumbers.IsMulticastID[@myEar] THEN Driver.Glitch[BadHostID];
IF EthernetFace.globalStateSize # 0 THEN
BEGIN
status: Zone.Status;
[globalStatePtr, status] ← ResidentHeap.MakeNode[
EthernetFace.globalStateSize, a8];
IF status # okay THEN Driver.Glitch[ResidentZoneTrouble];
END;
iocbState.free ← NIL;
IF EthernetFace.controlBlockSize = 0 THEN Driver.Glitch[IOCBSizeIsZero];
IF CommFlags.doDebug THEN {iocbState.avail ← iocbs};
iocbState.first ← iocbState.free ← CommUtil.AllocateIocbs[
EthernetFace.controlBlockSize * iocbs * bpw];
THROUGH [0..iocbs - 1) DO
iocbState.first ← iocbState.first.next ←
iocbState.first + EthernetFace.controlBlockSize;
REPEAT FINISHED =>
{iocbState.first.next ← NIL; iocbState.first ← iocbState.free};
ENDLOOP;
THROUGH [0..myDevice.buffers) DO
b: Buffer.Buffer = GetBufferAndIocbEntry[];
IF b # NIL THEN Buffer.Enqueue[@inputState.q, b];
ENDLOOP;
[cv: inputState.inWait, mask: inputState.mask] ←
SpecialRuntime.AllocateNakedCondition[];
Process.DisableTimeout[inputState.inWait];
[cv: outputState.outWait, mask: outputState.mask] ←
SpecialRuntime.AllocateNakedCondition[];
Process.DisableTimeout[outputState.outWait];
EnterMonitor[];
BEGIN
priority: Process.Priority ← Process.GetPriority[];
Process.SetPriority[CommPriorities.driver];
inputState.process ← FORK InInterrupt[];
outputState.process ← FORK OutInterrupt[];
Process.SetPriority[priority];
END;
Process.EnableAborts[@watcherState.timer];
Process.SetTimeout[@watcherState.timer, Process.MsecToTicks[1000]];
watcherState.process ← FORK Watcher[];
myDevice.alive ← TRUE; --and I'm alive
END;
SetEthernetListener: PUBLIC <<SpecialCommunication>> ENTRY PROC[
physicalOrder: CARDINAL, newHostNumber: HostNumber]
RETURNS [success: BOOLEAN] =
BEGIN
him: LONG POINTER TO FRAME[EthernetDriver];
network: Device ← GetNthDeviceLikeMe[physicalOrder];
IF network = NIL THEN RETURN[FALSE];
him ← LOOPHOLE[Runtime.GlobalFrame[LOOPHOLE[network.sendRawBuffer]]];
him.EthernetListenForHost[newHostNumber];
RETURN[TRUE];
END;
EthernetListenForHost: PROC[newHostNumber: HostNumber] ←
RealEthernetListenForHost;
-- The procedure variable for getting linkage to RealEthernetListenForHost.
RealEthernetListenForHost: INTERNAL PROC [newHostNumber: HostNumber] =
{myEar ← newHostNumber; SmashCSBs[]};
SetEthernetCollectGarbageToo: PUBLIC ENTRY PROC[
physicalOrder: CARDINAL, collectGarbage: BOOLEAN] RETURNS [success: BOOLEAN] =
BEGIN
him: LONG POINTER TO FRAME[EthernetDriver];
network: Device ← GetNthDeviceLikeMe[physicalOrder];
IF network = NIL THEN RETURN[FALSE];
him ← LOOPHOLE[Runtime.GlobalFrame[LOOPHOLE[network.sendRawBuffer]]];
him.SetCollectGarbageToo[collectGarbage];
RETURN[TRUE];
END;
SetCollectGarbageToo: PROC[collectGarbage: BOOLEAN] ←
RealSetCollectGarbageToo;
-- The procedure variable for getting linkage to RealSetCollectGarbageToo.
RealSetCollectGarbageToo: PROC[collectGarbage: BOOLEAN] =
BEGIN getGarbage ← collectGarbage; END;
GetNthDeviceLikeMe: PROC[physicalOrder: CARDINAL]
RETURNS [net: Device] =
BEGIN
i: CARDINAL ← 0;
net ← Driver.GetDeviceChain[];
WHILE net # NIL DO
IF net.device = myDevice.device THEN
IF (i ← i + 1) = physicalOrder THEN RETURN;
net ← net.next;
ENDLOOP;
END;
SmashCSBs: INTERNAL PROC =
BEGIN
b: Buffer.Buffer;
EthernetFace.TurnOff[ether]; --shut that down
IF (outputState.q.length # 0) THEN
BEGIN
UNTIL (b ← Buffer.Dequeue[@outputState.q]) = NIL DO
b.fo.status ← aborted;
Stats.StatIncr[statEtherReceivedKlobberedByReset];
FreeBufferAndIocb[Driver.PutOnGlobalDoneQueue, b];
ENDLOOP;
END;
EthernetFace.TurnOn[
ether, LOOPHOLE[myEar, SpecialSystem.ProcessorID], inputState.mask,
outputState.mask, globalStatePtr];
IF CommFlags.doStats THEN Stats.StatIncr[statInterfaceReset];
inputState.lastMissed ← EthernetFace.GetPacketsMissed[ether];
FOR b ← inputState.q.first, b.fo.next UNTIL b = NIL DO
-- EthernetFace uses words for degree of 'length'
EthernetFace.QueueInput[
ether, LOOPHOLE[b.linkLayer.blockPointer + offsetToDataUnit],
(b.fo.driver.length / bpw) - offsetToDataUnit, b.fo.driver.iocb];
ENDLOOP;
IF CommFlags.doDebug THEN SanityCheck[];
END; --SmashCSBs
DeleteDriver: PROC =
BEGIN
IF watcherState.pleaseStop THEN Driver.Glitch[DriverNotActive];
Driver.RemoveDeviceFromChain[localDevice]; --get's us out of chain
IF ether # EthernetFace.GetNextDevice[EthernetFace.nullDeviceHandle]
THEN Runtime.SelfDestruct[];
END; --DeleteDriver
DeactivateDriver: PROC =
BEGIN
process: PROCESS;
b: Buffer.Buffer;
IF watcherState.pleaseStop THEN Driver.Glitch[DriverNotActive];
watcherState.pleaseStop ← TRUE; --for others to check
myDevice.alive ← FALSE; --I'm dying - soon
IF (process ← inputState.process) = NIL THEN RETURN; --already deactive
UNTIL inputState.process = NIL DO
Process.EnableAborts[inputState.inWait]; Process.Abort[process];
REPEAT FINISHED => JOIN process;
ENDLOOP;
process ← outputState.process;
UNTIL outputState.process = NIL DO
Process.EnableAborts[outputState.outWait]; Process.Abort[process];
REPEAT FINISHED => JOIN process;
ENDLOOP;
Process.Abort[watcherState.process]; JOIN watcherState.process;
EthernetFace.TurnOff[ether];
SpecialRuntime.DeallocateNakedCondition[inputState.inWait];
SpecialRuntime.DeallocateNakedCondition[outputState.outWait];
inputState.inWait ← outputState.outWait ← NIL;
Driver.ChangeNumberOfInputBuffers[FALSE]; --call thur Boss to set flags
EthernetFace.RemoveCleanup[ether];
UNTIL (b ← Buffer.Dequeue[@inputState.q]) = NIL DO
b.fo.status ← aborted; --didn't make it
b.fo.driver.iocb ← NIL; --don't worry, iocbs will all be freed later
Driver.ReturnFreeBuffer[b]; --no interesting requeue on input
ENDLOOP;
UNTIL (b ← Buffer.Dequeue[@outputState.q]) = NIL DO
b.fo.status ← aborted; --didn't make it
b.fo.driver.iocb ← NIL; --don't worry, iocbs will all be freed later
Driver.PutOnGlobalDoneQueue[b]; --go thru requeue procedure
ENDLOOP;
CommUtil.FreeIocbs[iocbState.first];
iocbState.free ← iocbState.first ← NIL;
IF CommFlags.doDebug THEN iocbState.avail ← 0;
IF EthernetFace.globalStateSize # 0 THEN
BEGIN
status: Zone.Status;
status ← ResidentHeap.FreeNode[globalStatePtr];
IF status # okay THEN Driver.Glitch[ResidentZoneTrouble];
END;
END; --DeactivateDriver
--initialization
setupEthernetDriver ← SetupEthernetDriver; --for multi instances
END. --EthernetDriver
time - by - action
17-May-84 10:09:09 AOF Post Klamath.
5-Mar-85 19:50:07 AOF Treating broadcast as a subset of multicast.
5-Apr-85 10:55:50 AOF Use PilotSwitches.noEthernet instead of '>.
2-Aug-85 15:28:04 AOF Initialize and allocate 'globalStatePtr'
12-May-86 16:25:15 SMA New encapsulation scheme.
17-Jun-86 18:39:50 AOF Driver.GetInputBuffer takes a length.
23-Jun-86 8:57:31 AOF Readd code for changing input queue length.
1-Aug-86 16:02:41 MI Changed degree of driver.length from word to byte.
4-Aug-86 18:04:21 AOF Change number of buffers to add from 2 => 4.
18-Aug-86 20:06:36 AOF Lengths for copies of buffers come from device object.
20-Aug-86 11:12:21 AOF Fix copy for can't here self broadcasts.
14-Nov-86 13:31:47 AOF Don't start driver with a multicast host ID.
18-Nov-86 16:05:34 AOF PilotSwitches.noEthernet => CommSwitches.noEthernet
7-Jan-87 17:05:26 AOF Tweeks for MDS relief
19-Jan-87 15:21:41 AOF Use bytes for allocating IOCBs
6-Feb-87 9:28:08 AOF Adding physical overhead to buffer size
24-Mar-87 14:52:42 AOF set .fo.time in buffer copy in SendRawBuffer.
9-Apr-87 17:24:39 AOF Put code in to deal with odd length packets again.
14-Apr-87 11:09:15 AOF Allowing for encapsulation expansion on recieve buffers.
30-Jun-87 12:14:37 AOF Suppression of runts and too long in input
18-Jul-87 10:36:38 AOF Moving from EtherMAC to IEEE8023
24-Jul-87 16:46:25 AOF Glitch on transmitting too large packets
20-Aug-87 9:12:47 AOF Use interface definition of DLL size
31-Aug-87 9:37:45 AOF Redefinition of physical overhead
9-Sep-87 15:53:39 AOF User Boss's version of ChangeNumberOfInputBuffers
15-Oct-87 10:45:35 AOF Delete driver calling RemoveDeviceFromChain
23-Oct-87 10:48:21 AOF AR#12162 (EthernetDriver may recycle short buffer)
5-Nov-87 10:06:46 AOF AR#12162 More (changing buffer length ~monitored)
25-Jan-88 13:38:36 AOF Adjusting input length by start of frame adjustment
28-Jan-88 9:47:56 AOF AR#12727 - SmashCSBs wrong buffer length
17-Feb-88 15:24:59 AOF Fix for compiler bug NEWing self
22-Feb-88 17:34:39 AOF Really handling full buffers