EthernetDriver.mesa
Copyright © 1985, 1986 by Xerox Corporation. All rights reserved.
Derived from new EthernetOneDriver, HGM, Feb 86
Hal Murray, May 29, 1986 2:34:01 am PDT
Doug Wyatt, June 10, 1986 2:30:26 pm PDT
EthernetOneDriver is a very close copy of this module. If you change anything in here, you should probably make the corresponding change there.
DIRECTORY
Arpa USING [nullAddress],
Basics USING [bytesPerWord],
BasicTime USING [GetClockPulses, MicrosecondsToPulses, Pulses],
Booting USING [RegisterProcs, RollbackProc, switches],
CommBuffer USING [],
CommBufferExtras USING [gapNoList, gapRecv, gapSend],
CommDriver USING [AllocBuffer, AddNetwork, Buffer, bytesToRead, FreeBuffer, Network, NetworkObject, NoThankYou, recvPriority, sendPriority, watcherPriority, wordsInIocb],
CommDriverType USING [Encapsulation, ethernetEncapsulationBytes, ethernetEncapsulationOffset, ethernetMinBytes, IsMulticastHost],
DebuggerSwap USING [CallDebugger],
EthernetFace USING [AddCleanup, controlBlockSize, GetNextDevice, GetStatusAndCollisions, GetStatusAndLength, GetPacketsMissed, Handle, hearSelf, IOCB, MarkKilled, nullHandle, QueueInput, QueueOutput, SetPromiscuous, Status, TurnOff, TurnOn],
EthernetDriverStats USING [EtherStats, EtherStatsRep, MaxTries],
GermSwap USING [], -- Needed by Booting.switches
PrincOpsUtils USING [AllocateNakedCondition, LongCopy],
Process USING [Detach, DisableTimeout, GetPriority, MsecToTicks, Priority, SetPriority, SetTimeout],
Pup USING [Host, nullHost, nullNet],
SafeStorage USING [PinObject],
XNS USING [broadcastHost, GetThisHost, Host, unknownNet];
EthernetDriver: CEDAR MONITOR LOCKS data USING data: InstanceData
IMPORTS BasicTime, Booting, CommDriver, CommDriverType, DebuggerSwap, EthernetFace, PrincOpsUtils, Process, SafeStorage, XNS
EXPORTS CommBuffer = {
Buffer: TYPE = CommDriver.Buffer;
Network: TYPE = CommDriver.Network;
Encapsulation: PUBLIC TYPE = CommDriverType.Encapsulation;
Next: PROC [b: Buffer] RETURNS [Buffer] = TRUSTED INLINE {
RETURN[LOOPHOLE[b.ovh.next]]; };
Data: PROC [b: Buffer] RETURNS [LONG POINTER] = TRUSTED INLINE {
RETURN[@b.ovh.encap + CommDriverType.ethernetEncapsulationOffset]; };
Bytes: PROC [bytes: NAT] RETURNS [NAT] = TRUSTED INLINE {
RETURN[bytes + CommDriverType.ethernetEncapsulationBytes]; };
BytesToRead: PROC RETURNS [NAT] = INLINE {
RETURN[Bytes[CommDriver.bytesToRead]]; };
Iocb: PROC [b: Buffer] RETURNS [EthernetFace.IOCB] = TRUSTED INLINE {
RETURN[LOOPHOLE[b.ovh.iocb]]; };
ElapsedPulses: PROC [startTime: BasicTime.Pulses] RETURNS [BasicTime.Pulses] = INLINE {
RETURN[BasicTime.GetClockPulses[] - startTime]; };
InstanceData: TYPE = REF InstanceDataRep;
InstanceDataRep: TYPE = MONITORED RECORD [
ether: EthernetFace.Handle,
timer: CONDITION,
inWait, outWait: LONG POINTER TO CONDITIONNIL,
inHickup, outHickup: CONDITION,
inHickups, inBurps: INT ← 0,
outHickups, outBurps: INT ← 0,
inInterruptMask, outInterruptMask: WORD,
firstInputBuffer, lastInputBuffer: Buffer,
firstOutputBuffer, lastOutputBuffer: Buffer,
timeLastRecv, timeSendStarted: BasicTime.Pulses,
lastMissed: CARDINAL,
numberOfInputBuffers: CARDINAL,
me: XNS.Host,
promiscuous: BOOL,
stats: EthernetDriverStats.EtherStats ];
defaultNumberOfInputBuffers: NAT ← 5;
thirtySecondsOfPulses: BasicTime.Pulses ← BasicTime.MicrosecondsToPulses[30000000];
fiveSecondsOfPulses: BasicTime.Pulses ← BasicTime.MicrosecondsToPulses[5000000];
Sending
GetXNSEncapsulation: PROC [network: Network, dest: XNS.Host] RETURNS [encap: Encapsulation] = TRUSTED {
data: InstanceData = NARROW[network.instanceData];
encap ← [ ethernet[
ethernetDest: dest,
ethernetSource: data.me,
ethernetType: xns ]];
};
Return: PROC [network: Network, buffer: Buffer, bytes: NAT] = {
data: InstanceData = NARROW[network.instanceData];
buffer.ovh.encap.ethernetDest ← buffer.ovh.encap.ethernetSource;
buffer.ovh.encap.ethernetSource ← data.me;
Send[network, buffer, bytes];
};
unknownDest: LONG CARDINAL ← 0;
Send: PROC [network: Network, buffer: Buffer, bytes: NAT] = {
data: InstanceData = NARROW[network.instanceData];
priority: Process.Priority = Process.GetPriority[];
dest: XNS.Host ← buffer.ovh.encap.ethernetDest;
IF network.dead THEN RETURN;
IF buffer.ovh.encap.ethernetType = translationFailed THEN {
unknownDest ← unknownDest.SUCC;
RETURN; };
IF priority # CommDriver.sendPriority THEN Process.SetPriority[CommDriver.sendPriority];
IF ~EthernetFace.hearSelf
AND (dest = data.me OR CommDriverType.IsMulticastHost[dest] OR data.promiscuous) THEN {
sending to ourself, copy it over since we can't hear it
copy: Buffer ← CommDriver.AllocBuffer[];
words: NAT ← (Bytes[bytes]+Basics.bytesPerWord-1) / Basics.bytesPerWord;
copy.ovh.network ← network;
copy.ovh.next ← NIL;
copy.ovh.direction ← none;
TRUSTED {
PrincOpsUtils.LongCopy[from: Data[buffer], nwords: words, to: Data[copy]]; };
SELECT copy.ovh.encap.ethernetType FROM
xns => copy ← network.xns.recv[network, copy, bytes];
oldPup => copy ← network.pup.recv[network, copy, bytes];
oldPupTranslation => copy ← network.pup.recvTranslate[network, copy, bytes];
arpa => copy ← network.arpa.recv[network, copy, bytes];
arp => copy ← network.arpa.recvTranslate[network, copy, bytes];
ENDCASE => copy ← network.other.recv[network, copy, bytes];
IF copy # NIL THEN CommDriver.FreeBuffer[copy]; };
buffer.ovh.network ← network;
buffer.ovh.next ← NIL;
SendInner[data, buffer, bytes];
IF priority # CommDriver.sendPriority THEN Process.SetPriority[priority];
};
Unless you are remote debugging, an error or breakpoint in here will probably kill your whole machine. The problem is that the debugger wants to check the time stamp on files.
SendInner: ENTRY PROC [data: InstanceData, b: Buffer, bytes: NAT] = {
stats: EthernetDriverStats.EtherStats = data.stats;
status: EthernetFace.Status;
collisions: NAT;
bytes ← MAX[bytes, CommDriverType.ethernetMinBytes];
EthernetFace.QueueOutput[data.ether, Data[b], Bytes[bytes], Iocb[b]];
IF data.firstOutputBuffer # NIL AND data.lastOutputBuffer.ovh.next # NIL THEN ERROR;
IF b.ovh.gap#CommBufferExtras.gapNoList THEN
DebuggerSwap.CallDebugger["SendInner: buffer already in a list!"];
b.ovh.gap ← CommBufferExtras.gapSend; -- DKW: b is now in the output queue
IF data.firstOutputBuffer = NIL THEN data.firstOutputBuffer ← b
ELSE data.lastOutputBuffer.ovh.next ← b;
data.lastOutputBuffer ← b;
data.timeSendStarted ← BasicTime.GetClockPulses[];
DO
TRUSTED { WAIT data.outWait^; };
[status, collisions] ← EthernetFace.GetStatusAndCollisions[Iocb[b]];
IF status # pending THEN EXIT;
data.outBurps ← data.outBurps.SUCC;
BROADCAST data.outHickup;
ENDLOOP;
UNTIL b = data.firstOutputBuffer DO
data.outHickups ← data.outHickups.SUCC;
WAIT data.outHickup;
ENDLOOP;
data.firstOutputBuffer ← Next[b];
b.ovh.next ← NIL; -- DKW: just to be careful ...
IF b.ovh.gap#CommBufferExtras.gapSend THEN
DebuggerSwap.CallDebugger["SendInner: clobbered buffer in output queue!"];
b.ovh.gap ← CommBufferExtras.gapNoList; -- DKW: b now removed from the queue
SELECT status FROM
ok => {
stats.packetsSent ← stats.packetsSent + 1;
stats.wordsSent ← stats.wordsSent + bytes/Basics.bytesPerWord;
stats.loadTable[collisions] ← stats.loadTable[collisions] + 1;
};
ENDCASE => {
SELECT status FROM
tooManyCollisions => {
tooMany: NAT = EthernetDriverStats.MaxTries;
stats.loadTable[tooMany] ← stats.loadTable[tooMany] + 1; };
underrun => stats.overruns ← stats.overruns + 1;
ENDCASE => stats.badSendStatus ← stats.badSendStatus + 1;
};
BROADCAST data.outHickup;
};
Receiving
Recv: PROC [network: Network] = {
data: InstanceData = NARROW[network.instanceData];
b: Buffer ← CommDriver.AllocBuffer[];
Process.SetPriority[CommDriver.recvPriority];
DO
good: BOOL;
bytes: NAT;
b.ovh.network ← network;
b.ovh.next ← NIL;
b.ovh.direction ← none;
[good, bytes] ← RecvInner[data, b];
b.ovh.next ← NIL;
IF bytes < CommDriverType.ethernetEncapsulationBytes THEN good ← FALSE
ELSE bytes ← bytes - CommDriverType.ethernetEncapsulationBytes;
IF good THEN
SELECT b.ovh.encap.ethernetType FROM
xns => b ← network.xns.recv[network, b, bytes];
oldPup => b ← network.pup.recv[network, b, bytes];
oldPupTranslation => b ← network.pup.recvTranslate[network, b, bytes];
arpa => b ← network.arpa.recv[network, b, bytes];
arp => b ← network.arpa.recvTranslate[network, b, bytes];
ENDCASE => b ← network.other.recv[network, b, bytes]
ELSE b ← network.error.recv[network, b, bytes];
IF b = NIL THEN b ← CommDriver.AllocBuffer[];
ENDLOOP;
};
Unless you are remote debugging, an error or breakpoint in here will probably kill your whole machine. The problem is that the debugger wants to check the time stamp on files.
RecvInner: ENTRY PROC [data: InstanceData, b: Buffer] RETURNS [good: BOOL, bytes: NAT] = {
stats: EthernetDriverStats.EtherStats = data.stats;
status: EthernetFace.Status;
EthernetFace.QueueInput[data.ether, Data[b], BytesToRead[], Iocb[b]];
IF data.firstInputBuffer # NIL AND data.lastInputBuffer.ovh.next # NIL THEN ERROR;
IF b.ovh.gap#CommBufferExtras.gapNoList THEN
DebuggerSwap.CallDebugger["RecvInner: buffer already in a list!"];
b.ovh.gap ← CommBufferExtras.gapRecv; -- DKW: b is now in the input queue
IF data.firstInputBuffer = NIL THEN data.firstInputBuffer ← b
ELSE data.lastInputBuffer.ovh.next ← b;
data.lastInputBuffer ← b;
DO
TRUSTED { WAIT data.inWait^; };
[status, bytes] ← EthernetFace.GetStatusAndLength[Iocb[b]];
IF status # pending THEN EXIT;
data.inBurps ← data.inBurps.SUCC;
BROADCAST data.inHickup;
ENDLOOP;
UNTIL b = data.firstInputBuffer DO
data.inHickups ← data.inHickups.SUCC;
WAIT data.inHickup;
ENDLOOP;
data.firstInputBuffer ← Next[b];
b.ovh.next ← NIL; -- DKW: just to be careful ...
IF b.ovh.gap#CommBufferExtras.gapRecv THEN
DebuggerSwap.CallDebugger["RecvInner: clobbered buffer in input queue!"];
b.ovh.gap ← CommBufferExtras.gapNoList; -- DKW: b now removed from the queue
data.timeLastRecv ← BasicTime.GetClockPulses[];
SELECT status FROM
ok => {
good ← TRUE;
stats.packetsRecv ← stats.packetsRecv + 1;
stats.wordsRecv ← stats.wordsRecv + bytes/Basics.bytesPerWord; };
ENDCASE => {
good ← FALSE;
stats.badRecvStatus ← stats.badRecvStatus + 1;
IF status = overrun THEN stats.overruns ← stats.overruns + 1; };
BROADCAST data.inHickup;
};
Watching
Rollback: Booting.RollbackProc = {
[clientData: REF ANY]
network: Network = NARROW[clientData];
data: InstanceData = NARROW[network.instanceData];
SmashCSB[data];
};
Watcher: PROC [network: Network] = {
data: InstanceData = NARROW[network.instanceData];
stats: EthernetDriverStats.EtherStats = NARROW[network.stats];
missedIn: NAT ← 0;
missedOut: NAT ← 0;
inputNotifys: INT ← 0;
outputNotifys: INT ← 0;
fixupInputs: INT ← 0;
shootDownOutputs: INT ← 0;
Process.SetPriority[CommDriver.watcherPriority];
DO
missed: CARDINAL ← EthernetFace.GetPacketsMissed[data.ether];
newMissed: CARDINAL ← (missed - data.lastMissed);
This is the only place where inputOff gets updated, so we don't need the ML.
IF newMissed < 10000 THEN stats.inputOff ← stats.inputOff + newMissed;
data.lastMissed ← missed;
Since the interrupt routines are higher priority than we are, all the interrupts should get processed before we can see them. If see anything interesting, an interrupt has probably been lost. However, there is a slim chance it was 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 not process when we look again, it could be a new interrupt that has just arrived.
FOR i: NAT IN [0..25) DO -- Check for lost input interrupts
IF InputChainOK[data] THEN { missedIn ← 0; EXIT; };
REPEAT FINISHED => {
missedIn ← missedIn.SUCC;
inputNotifys ← inputNotifys.SUCC;
WatcherNotifyInput[data]; };
ENDLOOP;
FOR i: NAT IN [0..25) DO -- Check for lost output interrupts
IF OutputChainOK[data] THEN { missedOut ← 0; EXIT; };
REPEAT FINISHED => {
missedOut ← missedOut.SUCC;
outputNotifys ← outputNotifys.SUCC;
WatcherNotifyOutput[data]; };
ENDLOOP;
IF (missedIn > 10) -- Check for input confusion
OR (ElapsedPulses[data.timeLastRecv] > thirtySecondsOfPulses) THEN {
missedIn ← 0;
fixupInputs ← fixupInputs.SUCC;
SmashCSB[data]; };
IF (missedOut > 10) -- Check for output confusion
OR (data.firstOutputBuffer # NIL
AND (ElapsedPulses[data.timeSendStarted] > fiveSecondsOfPulses)) THEN {
missedOut ← 0;
shootDownOutputs ← shootDownOutputs.SUCC;
SmashCSB[data]; };
WatcherWait[data];
ENDLOOP;
};
InputChainOK: ENTRY PROC [data: InstanceData] RETURNS [BOOL] = {
status: EthernetFace.Status;
IF data.firstInputBuffer = NIL THEN RETURN[TRUE];
status ← EthernetFace.GetStatusAndLength[Iocb[data.firstInputBuffer]].status;
IF status = pending THEN RETURN[TRUE];
RETURN[FALSE];
};
OutputChainOK: ENTRY PROC [data: InstanceData] RETURNS [BOOL] = {
status: EthernetFace.Status;
IF data.firstOutputBuffer = NIL THEN RETURN[TRUE];
status ← EthernetFace.GetStatusAndCollisions[Iocb[data.firstOutputBuffer]].status;
IF status = pending THEN RETURN[TRUE];
RETURN[FALSE];
};
WatcherNotifyInput: ENTRY PROC [data: InstanceData] = TRUSTED {
NOTIFY data.inWait^;
};
WatcherNotifyOutput: ENTRY PROC [data: InstanceData] = TRUSTED {
NOTIFY data.outWait^;
};
SmashCSB: ENTRY PROC [data: InstanceData] = {
EthernetFace.TurnOff[data.ether];
FOR b: Buffer ← data.firstInputBuffer, Next[b] UNTIL b = NIL DO
status: EthernetFace.Status;
status ← EthernetFace.GetStatusAndLength[Iocb[b]].status;
IF status = pending THEN EthernetFace.MarkKilled[Iocb[b]];
TRUSTED { NOTIFY data.inWait^; };
ENDLOOP;
FOR b: Buffer ← data.firstOutputBuffer, Next[b] UNTIL b = NIL DO
status: EthernetFace.Status;
status ← EthernetFace.GetStatusAndCollisions[Iocb[b]].status;
IF status = pending THEN EthernetFace.MarkKilled[Iocb[b]];
TRUSTED { NOTIFY data.outWait^; };
ENDLOOP;
BROADCAST data.inHickup;
BROADCAST data.outHickup;
EthernetFace.TurnOn[data.ether, data.inInterruptMask, data.outInterruptMask];
data.lastMissed ← EthernetFace.GetPacketsMissed[data.ether];
data.timeLastRecv ← BasicTime.GetClockPulses[];
IF data.promiscuous THEN EthernetFace.SetPromiscuous[data.ether, TRUE];
};
WatcherWait: ENTRY PROC [data: InstanceData] = {
WAIT data.timer;
};
Initialization
CreateDefaultDrivers: PROC = {
nullHandle: EthernetFace.Handle = EthernetFace.nullHandle;
ether: EthernetFace.Handle ← EthernetFace.GetNextDevice[nullHandle];
WHILE ether # nullHandle DO
me: XNS.Host ← XNS.GetThisHost[];
stats: EthernetDriverStats.EtherStats ← NEW[EthernetDriverStats.EtherStatsRep];
data: InstanceData ← NEW[InstanceDataRep ← [
ether: ether,
timer: ,
inWait: NIL,
outWait: NIL,
inHickup: ,
outHickup: ,
inHickups: 0,
inBurps: 0,
outHickups: 0,
outBurps: 0,
inInterruptMask: 0,
outInterruptMask: 0,
firstInputBuffer: NIL,
lastInputBuffer: NIL,
firstOutputBuffer: NIL,
lastOutputBuffer: NIL,
timeLastRecv: BasicTime.GetClockPulses[],
timeSendStarted: BasicTime.GetClockPulses[],
lastMissed: 0,
numberOfInputBuffers: defaultNumberOfInputBuffers,
me: me,
promiscuous: FALSE,
stats: stats ]];
network: Network ← NEW [CommDriver.NetworkObject ← [
next: NIL,
arpa: [
host: Arpa.nullAddress,
getEncapsulation: NIL,
send: Send,
return: Return,
recv: CommDriver.NoThankYou,
sendTranslate: Send,
recvTranslate: CommDriver.NoThankYou,
translation: NIL ],
xns: [
net: XNS.unknownNet,
getEncapsulation: GetXNSEncapsulation,
send: Send,
return: Return,
recv: CommDriver.NoThankYou,
sendTranslate: Send,
recvTranslate: CommDriver.NoThankYou,
translation: NIL ],
pup: [
net: Pup.nullNet,
host: Pup.nullHost,
getEncapsulation: NIL,
send: Send,
return: Return,
recv: CommDriver.NoThankYou,
sendTranslate: Send,
recvTranslate: CommDriver.NoThankYou,
translation: NIL ],
other: [
netHostOther: NIL,
getEncapsulation: NIL,
send: Send,
return: Return,
recv: CommDriver.NoThankYou,
sendTranslate: Send,
recvTranslate: CommDriver.NoThankYou,
translation: NIL ],
raw: [send: Send],
error: [recv: CommDriver.NoThankYou],
setPromiscuous: SetPromiscuous,
isThisForMe: IsThisForMe,
toBroadcast: ToBroadcast,
moreBuffers: MoreBuffers,
interceptor: NIL,
stats: data.stats,
instanceData: data,
type: ethernet,
speed: 10000000,
index: 0,
hearSelf: EthernetFace.hearSelf,
recvSick: FALSE,
sendSick: FALSE,
dead: FALSE ]];
SafeStorage.PinObject[stats]; -- Not normally needed, but it might help EtherSwapping
SafeStorage.PinObject[data];
SafeStorage.PinObject[network];
EthernetFace.TurnOff[ether];
EthernetFace.AddCleanup[ether];
TRUSTED {
Process.SetTimeout[@data.timer, Process.MsecToTicks[1000]];
Process.DisableTimeout[@data.inHickup];
Process.DisableTimeout[@data.outHickup];
[cv: data.inWait, mask: data.inInterruptMask] ← PrincOpsUtils.AllocateNakedCondition[];
InitCond[data, data.inWait];
[cv: data.outWait, mask: data.outInterruptMask] ← PrincOpsUtils.AllocateNakedCondition[];
InitCond[data, data.outWait]; };
EthernetFace.TurnOn[data.ether, data.inInterruptMask, data.outInterruptMask];
data.lastMissed ← EthernetFace.GetPacketsMissed[data.ether];
FOR i: NAT IN [0..data.numberOfInputBuffers) DO
TRUSTED { Process.Detach[FORK Recv[network]]; };
ENDLOOP;
TRUSTED { Process.Detach[FORK Watcher[network]]; };
Booting.RegisterProcs[r: Rollback, clientData: network];
CommDriver.AddNetwork[network];
ether ← EthernetFace.GetNextDevice[ether];
ENDLOOP;
};
InitCond: ENTRY PROC [data: InstanceData, cond: LONG POINTER TO CONDITION] = TRUSTED {
Process.SetTimeout[cond, 1];
WAIT cond^; -- Eat up any wakeups waiting
Process.DisableTimeout[cond];
};
Spying...
SetPromiscuous: PROC [network: Network, promiscuous: BOOL] = {
data: InstanceData = NARROW[network.instanceData];
EthernetFace.SetPromiscuous[data.ether, promiscuous];
data.promiscuous ← promiscuous;
};
skipTheBugs: INT ← 0;
IsThisForMe: PROC [network: Network, buffer: Buffer] RETURNS [yes: BOOL] = {
data: InstanceData = NARROW[network.instanceData];
dest: XNS.Host ← buffer.ovh.encap.ethernetDest;
IF dest = data.me THEN RETURN[TRUE];
IF dest = XNS.broadcastHost THEN RETURN[TRUE];
IF CommDriverType.IsMulticastHost[dest] THEN skipTheBugs ← skipTheBugs.SUCC;
Bug in 12.0. Fling packet to random place if don't know translation info yet.
RETURN[FALSE];
};
ToBroadcast: PROC [network: Network, buffer: Buffer] RETURNS [yes: BOOL] = {
dest: XNS.Host ← buffer.ovh.encap.ethernetDest;
IF CommDriverType.IsMulticastHost[dest] THEN RETURN[TRUE];
RETURN[FALSE];
};
MoreBuffers: PROC [network: Network, total: NAT] = {
data: InstanceData = NARROW[network.instanceData];
IF total < data.numberOfInputBuffers THEN RETURN;
FOR i: NAT IN [data.numberOfInputBuffers..total) DO
TRUSTED { Process.Detach[FORK Recv[network]]; };
ENDLOOP;
data.numberOfInputBuffers ← total;
};
START Trap
IF CommDriver.wordsInIocb < EthernetFace.controlBlockSize THEN ERROR;
IF ~Booting.switches[b] THEN CreateDefaultDrivers[];
}.
If you are interested in the interrupt tangle, look at the comments at the end of EthernetOneDriver.mesa.