DIRECTORY Arpa USING [nullAddress], Basics USING [bytesPerWord], BasicTime USING [GetClockPulses, MicrosecondsToPulses, Pulses], Booting USING [RegisterProcs, RollbackProc, switches], CommBuffer USING [], CommBufferExtras USING [gapNoList, gapRecvOne, gapSendOne], CommDriver USING [AllocBuffer, AddNetwork, Buffer, bytesToRead, FreeBuffer, Network, NetworkObject, NoThankYou, recvPriority, sendPriority, watcherPriority, wordsInIocb], CommDriverType USING [Encapsulation, ethernetOneEncapsulationOffset, ethernetOneEncapsulationBytes], DebuggerSwap USING [CallDebugger], EthernetOneFace USING [AddCleanup, controlBlockSize, GetHostNumber, GetNextDevice, GetStatusAndCollisions, GetStatusAndLength, GetPacketsMissed, Handle, hearSelf, HostArray, IOCB, MarkKilled, nullHandle, QueueInput, QueueOutput, SetInputHosts, 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 [allHosts, Host, nullNet], SafeStorage USING [PinObject], XNS USING [unknownNet]; EthernetOneDriver: CEDAR MONITOR LOCKS data USING data: InstanceData IMPORTS BasicTime, Booting, CommDriver, DebuggerSwap, EthernetOneFace, PrincOpsUtils, Process, SafeStorage 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.ethernetOneEncapsulationOffset]; }; Bytes: PROC [bytes: NAT] RETURNS [NAT] = TRUSTED INLINE { RETURN[bytes + CommDriverType.ethernetOneEncapsulationBytes]; }; BytesToRead: PROC RETURNS [NAT] = INLINE { RETURN[Bytes[CommDriver.bytesToRead]]; }; Iocb: PROC [b: Buffer] RETURNS [EthernetOneFace.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: EthernetOneFace.Handle, timer: CONDITION, inWait, outWait: LONG POINTER TO CONDITION _ NIL, 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: Pup.Host, promiscuous: BOOL, stats: EthernetDriverStats.EtherStats ]; defaultNumberOfInputBuffers: NAT _ 5; thirtySecondsOfPulses: BasicTime.Pulses _ BasicTime.MicrosecondsToPulses[30000000]; fiveSecondsOfPulses: BasicTime.Pulses _ BasicTime.MicrosecondsToPulses[5000000]; GetPupEncapsulation: PROC [network: Network, dest: Pup.Host] RETURNS [encap: Encapsulation] = TRUSTED { data: InstanceData = NARROW[network.instanceData]; encap _ [ ethernetOne[ etherSpare1: 0, -- fill with something known so 7 word compare will work etherSpare2: 0, etherSpare3: 0, etherSpare4: 0, etherSpare5: 0, ethernetOneDest: dest, ethernetOneSource: data.me, ethernetOneType: pup ]]; }; Return: PROC [network: Network, buffer: Buffer, bytes: NAT] = { data: InstanceData = NARROW[network.instanceData]; buffer.ovh.encap.ethernetOneDest _ buffer.ovh.encap.ethernetOneSource; buffer.ovh.encap.ethernetOneSource _ 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: Pup.Host = buffer.ovh.encap.ethernetOneDest; IF network.dead THEN RETURN; IF buffer.ovh.encap.ethernetOneType = translationFailed THEN { unknownDest _ unknownDest.SUCC; RETURN; }; IF priority # CommDriver.sendPriority THEN Process.SetPriority[CommDriver.sendPriority]; IF ~EthernetOneFace.hearSelf AND (dest = data.me OR dest = Pup.allHosts OR data.promiscuous) THEN { 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.ethernetOneType FROM arpa => copy _ network.arpa.recv[network, copy, bytes]; arp => copy _ network.arpa.recvTranslate[network, copy, bytes]; xns => copy _ network.xns.recv[network, copy, bytes]; translation => copy _ network.xns.recvTranslate[network, copy, bytes]; pup => copy _ network.pup.recv[network, copy, bytes]; fromImp, toImp => copy _ network.other.recv[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]; }; SendInner: ENTRY PROC [data: InstanceData, b: Buffer, bytes: NAT] = { stats: EthernetDriverStats.EtherStats = data.stats; status: EthernetOneFace.Status; collisions: NAT; EthernetOneFace.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.gapSendOne; -- 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] _ EthernetOneFace.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.gapSendOne 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; }; 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]; IF bytes < CommDriverType.ethernetOneEncapsulationBytes THEN good _ FALSE ELSE bytes _ bytes - CommDriverType.ethernetOneEncapsulationBytes; IF good THEN SELECT b.ovh.encap.ethernetOneType FROM arpa => b _ network.arpa.recv[network, b, bytes]; arp => b _ network.arpa.recvTranslate[network, b, bytes]; xns => b _ network.xns.recv[network, b, bytes]; translation => b _ network.xns.recvTranslate[network, b, bytes]; pup => b _ network.pup.recv[network, b, bytes]; fromImp, toImp => b _ network.other.recv[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; }; RecvInner: ENTRY PROC [data: InstanceData, b: Buffer] RETURNS [good: BOOL, bytes: NAT] = { stats: EthernetDriverStats.EtherStats = data.stats; status: EthernetOneFace.Status; EthernetOneFace.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.gapRecvOne; -- 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] _ EthernetOneFace.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.gapRecvOne 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; }; Rollback: Booting.RollbackProc = { 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 _ EthernetOneFace.GetPacketsMissed[data.ether]; newMissed: CARDINAL _ (missed - data.lastMissed); IF newMissed < 10000 THEN stats.inputOff _ stats.inputOff + newMissed; data.lastMissed _ missed; 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: EthernetOneFace.Status; IF data.firstInputBuffer = NIL THEN RETURN[TRUE]; status _ EthernetOneFace.GetStatusAndLength[Iocb[data.firstInputBuffer]].status; IF status = pending THEN RETURN[TRUE]; RETURN[FALSE]; }; OutputChainOK: ENTRY PROC [data: InstanceData] RETURNS [BOOL] = { status: EthernetOneFace.Status; IF data.firstOutputBuffer = NIL THEN RETURN[TRUE]; status _ EthernetOneFace.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] = { EthernetOneFace.TurnOff[data.ether]; FOR b: Buffer _ data.firstInputBuffer, Next[b] UNTIL b = NIL DO status: EthernetOneFace.Status; status _ EthernetOneFace.GetStatusAndLength[Iocb[b]].status; IF status = pending THEN EthernetOneFace.MarkKilled[Iocb[b]]; TRUSTED { NOTIFY data.inWait^; }; ENDLOOP; FOR b: Buffer _ data.firstOutputBuffer, Next[b] UNTIL b = NIL DO status: EthernetOneFace.Status; status _ EthernetOneFace.GetStatusAndCollisions[Iocb[b]].status; IF status = pending THEN EthernetOneFace.MarkKilled[Iocb[b]]; TRUSTED { NOTIFY data.outWait^; }; ENDLOOP; BROADCAST data.inHickup; BROADCAST data.outHickup; EthernetOneFace.TurnOn[data.ether, data.inInterruptMask, data.outInterruptMask]; data.lastMissed _ EthernetOneFace.GetPacketsMissed[data.ether]; data.timeLastRecv _ BasicTime.GetClockPulses[]; IF data.promiscuous THEN TRUSTED { hostArray: EthernetOneFace.HostArray _ ALL[TRUE]; EthernetOneFace.SetInputHosts[data.ether, @hostArray]; } }; WatcherWait: ENTRY PROC [data: InstanceData] = { WAIT data.timer; }; CreateDefaultDrivers: PROC = { nullHandle: EthernetOneFace.Handle = EthernetOneFace.nullHandle; ether: EthernetOneFace.Handle _ EthernetOneFace.GetNextDevice[nullHandle]; WHILE ether # nullHandle DO me: Pup.Host _ [EthernetOneFace.GetHostNumber[ether]]; 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: NIL, send: Send, return: Return, recv: CommDriver.NoThankYou, sendTranslate: Send, recvTranslate: CommDriver.NoThankYou, translation: NIL ], pup: [ net: Pup.nullNet, host: me, getEncapsulation: GetPupEncapsulation, 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: stats, instanceData: data, type: ethernetOne, speed: 3000000, index: 0, hearSelf: EthernetOneFace.hearSelf, recvSick: FALSE, sendSick: FALSE, dead: FALSE ]]; IF me = 0 THEN ERROR; SafeStorage.PinObject[stats]; -- Not normally needed, but it might help EtherSwapping SafeStorage.PinObject[data]; SafeStorage.PinObject[network]; EthernetOneFace.TurnOff[ether]; EthernetOneFace.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]; }; EthernetOneFace.TurnOn[data.ether, data.inInterruptMask, data.outInterruptMask]; data.lastMissed _ EthernetOneFace.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 _ EthernetOneFace.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]; }; SetPromiscuous: PROC [network: Network, promiscuous: BOOL] = { data: InstanceData = NARROW[network.instanceData]; IF promiscuous THEN TRUSTED { hostArray: EthernetOneFace.HostArray _ ALL[TRUE]; EthernetOneFace.SetInputHosts[data.ether, @hostArray]; } ELSE EthernetOneFace.SetInputHosts[data.ether, NIL]; data.promiscuous _ promiscuous; }; IsThisForMe: PROC [network: Network, buffer: Buffer] RETURNS [yes: BOOL] = { data: InstanceData = NARROW[network.instanceData]; IF buffer.ovh.encap.ethernetOneDest = data.me THEN RETURN[TRUE]; IF buffer.ovh.encap.ethernetOneDest = Pup.allHosts THEN RETURN[TRUE]; RETURN[FALSE]; }; ToBroadcast: PROC [network: Network, buffer: Buffer] RETURNS [yes: BOOL] = { IF buffer.ovh.encap.ethernetOneDest = Pup.allHosts 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; }; IF CommDriver.wordsInIocb < EthernetOneFace.controlBlockSize THEN ERROR; IF ~Booting.switches[a] THEN CreateDefaultDrivers[]; }. Comments on interrupt routines: In the ideal world, the code for an interrupt routine would look like: ... Queue up request WAIT for interrupt ... Unfortunately, things don't work that simply. One problem is that there is a race between the Queue/wait, and the notify that results when the hardware finishes. (In normal WAIT/NOTIFY sequences, the CONDITION is part of the monitored data, so there isn't any race.) The microcode "solves" this problem with a wakeup waiting bit in the condition variable. If you only had one request in progress at any time, the wakup waiting trick would make the code above work. Things get more complicated if several requests are queued since interrupts may happen while the interrupt routine is processing some other request.The wakup waiting bit is only one bit, so it can't remember how many extra wakeups are necessary. Thus the code now looks like: ... Queue up request UNTIL status = done DO WAIT for interrupt ENDLOOP ... The WAIT will be bypassed if an interrupt happens while a previous event was being processed. In that case, the first real try at WAITing will encounter a wakeup waiting and get a wakeup without any work to do. That picture assumes that there is only one interrupt routine for the microcode to notify. This driver has one routine for each active request. Since there is only one process at a time in the critical region a single bit of wakup wating should be enough. As long as they are all running at the same priority, they should get woken up in the right order. Unfortunately, there is a hairy case. Consider what happens if 1) a request is queued and a process is waiting for it, 2) a second process has queued a request but hasn't waited for it, 3) the first request finishes (and it is moved from the CV to the ready list), and 4) the second request finishes, and 5) the second process trys to wait but hits the wakup waiting bit. The result is the second process keeps running, but the first hasn't run yet. (I found this by trial and error. Under heavy load, it would happen about 1 time in 100000. /HGM) ŠEthernetOneDriver.mesa Copyright c 1985 by Xerox Corporation. All rights reserved. Major rewrite of old EthernetOneDriver, HGM, Jan 86 Hal Murray, May 29, 1986 2:41:30 am PDT Doug Wyatt, June 10, 1986 2:26:41 pm PDT EthernetDriver is a very close copy of this module. If you change anything in here, you should probably make the corresponding change there. Sending sending to ourself, copy it over since we can't hear it 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. Receiving 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. Watching [clientData: REF ANY] This is the only place where inputOff gets updated, so we don't need the ML. 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. Initialization Spying... START Trap Κη˜codešœ™Kšœ Οmœ1™Kšœžœ˜Kšžœ˜ —Kšžœ$žœ.˜XKšžœ˜šžœžœžœžœ˜FKšŸ7™7Kšœ(˜(Kšœžœ>˜HKšœ˜Kšœžœ˜Kšœ˜šžœ˜ KšœM˜M—šžœ ž˜*Kšœ7˜7Kšœ?˜?Kšœ5˜5KšœF˜FKšœ5˜5KšœB˜BKšžœ4˜;—Kšžœžœžœ ˜2—K˜Kšœžœ˜Kšœ˜Kšžœ$žœ˜IKšœ˜K˜—Kšœ―™―š  œžœžœ(žœ˜EKšœ3˜3K˜Kšœ žœ˜K˜HKš žœžœžœ"žœžœžœ˜Tšžœ&žœ˜-KšœB˜B—Kšœ)Ÿ$˜MKšžœžœžœ˜?Kšžœ$˜(K˜K˜2šž˜Kšžœžœ˜ KšœG˜GKšžœžœžœ˜Kšœžœ˜#Kšž œ˜Kšžœ˜—šžœž˜#Kšœ"žœ˜'Kšžœ˜Kšžœ˜—Kšœ!˜!Kšœ žœŸ˜0šžœ'žœ˜.KšœJ˜J—Kšœ(Ÿ$˜Lšžœž˜šœ˜K˜*K˜>Kšœ>˜>Kšœ˜—šžœ˜ šžœž˜šœ˜Kšœ žœ ˜,Kšœ;˜;—Kšœ0˜0Kšžœ2˜9—Kšœ˜——Kšž œ˜Kšœ˜K˜——š  ™ š œžœ˜!Kšœžœ˜2Kšœ%˜%K˜-šž˜Kšœžœ˜ Kšœžœ˜ Kšœ˜Kšœ žœ˜Kšœ˜Kšœ#˜#Kšžœ6žœž˜IKšžœ>˜Bšžœž˜ šžœž˜'Kšœ1˜1Kšœ9˜9Kšœ/˜/Kšœ@˜@Kšœ/˜/Kšœ<˜˜>Kšžœžœžœ˜Kšœžœ˜!Kšž œ˜Kšžœ˜—šžœž˜"Kšœ žœ˜%Kšžœ˜Kšžœ˜—K˜ Kšœ žœŸ˜0šžœ'žœ˜.KšœI˜I—Kšœ(Ÿ$˜LK˜/šžœž˜šœ˜Kšœžœ˜ K˜*KšœA˜A—šžœ˜ Kšœžœ˜ K˜.Kšžœžœ(˜@——Kšž œ˜Kšœ˜K˜——š ™•StartOfExpansion -- [clientData: REF ANY]šΟbœ˜"KšΠck™Kšœžœ ˜&Kšœžœ˜2Kšœ˜K˜K˜—š œžœ˜$Kšœžœ˜2Kšœ(žœ˜>Kšœ žœ˜Kšœ žœ˜Kšœžœ˜Kšœžœ˜Kšœ žœ˜Kšœžœ˜K˜0šž˜Kšœžœ0˜@Kšœ ž œ˜1KšœIžœ™LKšžœžœ-˜FKšœ˜Kšœί™ίš žœžœžœ žœŸ"˜;Kšžœžœžœ˜3šžœžœ˜Kšœžœ˜Kšœžœ˜!Kšœ˜—Kšžœ˜—š žœžœžœ žœŸ#˜žœ˜GKšœ˜Kšœ$žœ˜)Kšœ˜—Kšœ˜Kšžœ˜—Kšœ˜K˜—š   œžœžœžœžœ˜@Kšœ˜Kš žœžœžœžœžœ˜1KšœP˜PKšžœžœžœžœ˜&Kšžœžœ˜Kšœ˜K˜—š   œžœžœžœžœ˜AKšœ˜Kš žœžœžœžœžœ˜2KšœU˜UKšžœžœžœžœ˜&Kšžœžœ˜Kšœ˜K˜—š œžœžœžœ˜?Kšžœ˜Kšœ˜K˜—š œžœžœžœ˜@Kšžœ˜Kšœ˜K˜—š œžœžœ˜-Kšœ$˜$šžœ,žœžœž˜?Kšœ˜Kšœ<˜Kšœžœ˜2šžœ žœžœ˜Kšœ'žœžœ˜1Kšœ8˜8—Kšžœ+žœ˜4Kšœ˜K˜K˜—š  œžœ$žœžœ˜LKšœžœ˜2Kšžœ,žœžœžœ˜@Kšžœ1žœžœžœ˜EKšžœžœ˜Kšœ˜K˜—š  œžœ$žœžœ˜LKšžœ1žœžœžœ˜EKšžœžœ˜Kšœ˜K˜—š  œžœžœ˜4Kšœžœ˜2Kšžœ#žœžœ˜1šžœžœžœ$ž˜3Kšžœžœ˜0Kšžœ˜—Kšœ"˜"Kšœ˜K˜—K˜—Lš  ™ ™Kšžœ;žœž˜HKšžœžœ˜4K˜—Kšœ˜K˜K˜˜šœ\žœ˜rK˜—šœ­žœžœž œ©žœžœžœžœ žœΛ˜ƒK˜—K˜†——…—L¨f