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 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: XNS.Host, promiscuous: BOOL, stats: EthernetDriverStats.EtherStats ]; defaultNumberOfInputBuffers: NAT _ 5; thirtySecondsOfPulses: BasicTime.Pulses _ BasicTime.MicrosecondsToPulses[30000000]; fiveSecondsOfPulses: BasicTime.Pulses _ BasicTime.MicrosecondsToPulses[5000000]; 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 { 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]; }; 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; }; 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; }; 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; }; 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 _ EthernetFace.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: 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; }; 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]; }; 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; 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; }; 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. ΪEthernetDriver.mesa Copyright c 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. 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... Bug in 12.0. Fling packet to random place if don't know translation info yet. START Trap Κƒ˜codešœ™Kšœ Οmœ7™BKšœ/™/Kšœ'™'K™(—K˜K™K™šΟk ˜ Kšœžœ˜Kšœžœ˜Kšœ žœ0˜?Kšœžœ)˜6Kšœ žœ˜Kšœžœ˜5Kšœ žœš˜ͺKšœžœm˜Jšœ žœ˜"Kšœ žœžœ\˜ρKšœžœ'˜@Kšœ žœΟc˜0Kšœžœ$˜7KšœžœW˜dKšœžœ˜$Kšœ žœ ˜Kšžœžœ0˜9K˜—š Οnœžœžœžœžœ˜AKšžœrž˜|Kšžœ˜K˜Kšœžœ˜!Kšœ žœ˜#Kšœžœžœ ˜:K˜š  œžœ žœ žœžœ˜:Kšžœžœ˜ —š œžœ žœžœžœžœžœ˜@Kšžœžœ=˜E—š œžœ žœžœžœžœžœ˜9Kšžœ7˜=—š   œžœžœžœžœ˜*Kšžœ#˜)—š  œžœ žœžœžœžœ˜EKšžœžœ˜ —š  œžœžœžœ˜WKšžœ,˜2K˜—Kšœžœžœ˜)šœžœž œžœ˜*Kšœ˜Kšœž œ˜Kš œžœžœžœž œžœ˜1Kšœž œ˜Kšœžœ˜Kšœžœ˜Kšœ#žœ˜(K˜*K˜,Kšœ0˜0Kšœ žœ˜Kšœžœ˜Kšœžœ˜ Kšœ žœ˜Kšœ(˜(—K˜Kšœžœ˜%K˜K˜S˜PK˜——headš ™š  œžœžœžœžœ˜gKšœžœ˜2šœ˜Kšœ˜Kšœ˜Kšœ˜—Kšœ˜K˜—š œžœ+žœ˜?Kšœžœ˜2Kšœ@˜@Kšœ*˜*Kšœ˜Kšœ˜K˜—Kšœ žœžœ˜š œžœ+žœ˜=Kšœžœ˜2K˜3Kšœžœ&˜/Kšžœžœžœ˜šžœ3žœ˜;Kšœžœ˜Kšžœ˜ —Kšžœ$žœ.˜XKšžœ˜šžœžœ&žœžœ˜WKšŸ7™7Kšœ(˜(Kšœžœ>˜HKšœ˜Kšœžœ˜Kšœ˜šžœ˜ KšœM˜M—šžœž˜'Kšœ5˜5Kšœ8˜8KšœL˜LKšœ7˜7Kšœ?˜?Kšžœ4˜;—Kšžœžœžœ ˜2—K˜Kšœžœ˜Kšœ˜Kšžœ$žœ˜IKšœ˜K˜—Kšœ―™―š  œžœžœ(žœ˜EKšœ3˜3K˜Kšœ žœ˜Kšœžœ)˜4K˜EKš žœžœžœ"žœžœžœ˜Tšžœ&žœ˜-KšœB˜B—Kšœ&Ÿ$˜JKšžœžœžœ˜?Kšžœ$˜(K˜K˜2šž˜Kšžœžœ˜ KšœD˜DKšžœžœžœ˜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šœ žœ˜Kšžœ3žœž˜FKšžœ;˜?šžœž˜ šžœž˜$Kšœ/˜/Kšœ2˜2KšœF˜FKšœ1˜1Kšœ9˜9Kšžœ-˜4——Kšžœ+˜/Kšžœžœžœ˜-Kšžœ˜—Kšœ˜K˜—Kšœ―™―š   œžœžœ!žœžœ žœ˜ZKšœ3˜3Kšœ˜KšœE˜EKš žœžœžœ!žœžœžœ˜Ršžœ&žœ˜-KšœB˜B—Kšœ&Ÿ#˜IKšžœžœžœ˜=Kšžœ#˜'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šœžœ-˜=Kšœ ž œ˜1KšœIžœ™LKšžœžœ-˜FKšœ˜Kšœί™ίš žœžœžœ žœŸ"˜;Kšžœžœžœ˜3šžœžœ˜Kšœžœ˜Kšœžœ˜!Kšœ˜—Kšžœ˜—š žœžœžœ žœŸ#˜žœ˜GKšœ˜Kšœ$žœ˜)Kšœ˜—Kšœ˜Kšžœ˜—Kšœ˜K˜—š   œžœžœžœžœ˜@Kšœ˜Kš žœžœžœžœžœ˜1KšœM˜MKšžœžœžœžœ˜&Kšžœžœ˜Kšœ˜K˜—š   œžœžœžœžœ˜AKšœ˜Kš žœžœžœžœžœ˜2KšœR˜RKšžœžœžœžœ˜&Kšžœžœ˜Kšœ˜K˜—š œžœžœžœ˜?Kšžœ˜Kšœ˜K˜—š œžœžœžœ˜@Kšžœ˜Kšœ˜K˜—š œžœžœ˜-K˜!šžœ,žœžœž˜?Kšœ˜Kšœ9˜9Kšžœžœ"˜:Kšžœžœ˜!Kšžœ˜—šžœ-žœžœž˜@Kšœ˜Kšœ=˜=Kšžœžœ"˜:Kšžœžœ˜"Kšžœ˜—Kšž œ˜Kšž œ˜KšœM˜MKšœ<˜Kšœžœ˜2Kšœ5˜5Kšœ˜K˜K˜—Kšœ žœ˜š  œžœ$žœžœ˜LKšœžœ˜2Kšœžœ&˜/Kšžœžœžœžœ˜$Kš žœžœžœžœžœ˜.šžœ&žœžœ˜LK™M—Kšžœžœ˜Kšœ˜K˜—š  œžœ$žœžœ˜LKšœžœ&˜/Kšžœ&žœžœžœ˜:Kšžœžœ˜Kšœ˜K˜—š  œžœžœ˜4Kšœžœ˜2Kšžœ#žœžœ˜1šžœžœžœ$ž˜3Kšžœžœ˜0Kšžœ˜—Kšœ"˜"Kšœ˜K˜——Lš  ™ ™Kšžœ8žœž˜EKšžœžœ˜4K˜—Kšœ˜K˜K˜i—…—Cx\Υ