-- File: SptpDriver.mesa - last edit: -- AOF 3-Feb-88 19:37:37 -- Copyright (C) 1987, 1988 by Xerox Corporation. All rights reserved. --See Notes and Comments at end of module DIRECTORY Buffer USING [ AccessHandle, Buffer, Byte, DataBytesPerRawBuffer, dataLinkReserve, Dequeue, DestroyPool, Device, DriverInformation, Enqueue, MakePool, Queue, QueueCleanup, QueueInitialize, QueueObject, TransferStatus], ByteBlt USING [ByteBlt], CommFlags USING [doDebug, doErrors, doStats, driverStats], CommPriorities USING [driver], CommUtil USING [AllocateIocbs, FreeIocbs], Driver USING [ AddDeviceToChain, Device, DeviceObject, GetDeviceChain, GetInputBuffer, Glitch, PutOnGlobalDoneQueue, PutOnGlobalInputQueue, RemoveDeviceFromChain, ReturnFreeBuffer], Environment USING [Block, Byte, bytesPerWord], HostNumbers USING [HostNumber, IsMulticastID], Mopcodes USING [op, zAND], NewRS232CFace USING [ Command, CommandStatus, DeviceStatus, GetDeviceStatus, GetHandle, Handle, Initialize, InitializeCleanup, InitiateCommand, InitiateReceive, InitiateSetParameters, InitiateTransmit, Operation, OperationPtr, operationSize, ParameterRecord, ParameterStatus, PollCommand, PollReceiveOrTransmit, PollSetParameters, ReleaseHandle, ResetRecord, InitiateResetStatusBits, InitiateSetControlBits, TransferStatus, PollSetControlBits, ControlRecord], Process USING [ Abort, DisableTimeout, EnableAborts, GetPriority, MsecToTicks, Pause, Priority, SetPriority, SetTimeout], ProcessorFace USING [SetMP], RS232CCorrespondents USING [nsSystemElement], Runtime USING [GlobalFrame, SelfDestruct], SpecialRuntime USING [AllocateNakedCondition, DeallocateNakedCondition], SpecialSystem USING [GetProcessorID, HostNumber], SppOps USING [SetWindow, sppWindowSize], SptpOps USING [ defaultMaxRS232CBytes, defaultMinRS232CBytes, point5Duplex, Reservation, ReservationObject, siuSupport, StatsRecord, TraceProc], SptpProtocol USING [ ActiveNegotiation, AwaitingOptionAck, AwaitingOptions, AwaitTerminateReply, DriverInformation, Encapsulation, EntityClass, EncapsulationFromBlock, EncapsulationObject, PassiveNegotiation, ProcessControl, ProtocolInfo, ProtocolRecord, ProtocolVersion, SendAreYouThere, SendNull, SendTerminateRequest, TerminationDally, WaitForControl], SptpStats USING [Bump, Incr, StatCounterIndex], Stats USING [StatCounterIndex, StatIncr], System USING [ GetClockPulses, GreenwichMeanTime, HostNumber, MicrosecondsToPulses, nullHostNumber, PulsesToMicroseconds]; SptpDriver: MONITOR IMPORTS Buffer, ByteBlt, CommUtil, Driver, NewRS232CFace, HostNumbers, SptpProtocol, SptpStats, SppOps, Stats, Process, ProcessorFace, Runtime, SpecialRuntime, SpecialSystem, System EXPORTS Buffer, SptpOps, System = BEGIN --EXPORTed TYPEs and variables Device: PUBLIC <> TYPE = Driver.Device; HostNumber: PUBLIC <> TYPE = SpecialSystem.HostNumber; probeCount: NATURAL = 8; --just an arbitrary counter value; window: NATURAL ¬ 1; --actually (window + 2) or 1 == 3 (small 3, large 1) measurableLength: NATURAL ¬ 64; --minimum byte length for computation lineSpeed: PUBLIC CARDINAL; --to hold computed line speeds maxRS232CBytes: PUBLIC <> CARDINAL ¬ SptpOps.defaultMaxRS232CBytes; minRS232CBytes: PUBLIC <> CARDINAL ¬ SptpOps.defaultMinRS232CBytes; bpw: NATURAL = Environment.bytesPerWord; setupDriver: PROC[SptpOps.Reservation]; instanceBusy: BOOLEAN ¬ FALSE; --so we can tell if zeroth instance is in use instanceCopied: BOOLEAN ¬ FALSE; --so we know to delete him refCount: NATURAL ¬ 0; --so we can tolerate multiple creates <<+++++++>> reason: ReasonForEnteringTerminate2State; ReasonForEnteringTerminate2State: TYPE = { voluntary, couldntClearLatches, noCTS, dsrDropped, cdDropped}; <<+++++++>> rs232c: RECORD[ process: PROCESS, lta, pleaseStop: BOOLEAN, probe, transmits: NATURAL, cmdDone, bitClock: CONDITION, traceProc: SptpOps.TraceProc, lastStatusChange: LONG CARDINAL, interrupt: LONG POINTER TO CONDITION, handle: NewRS232CFace.Handle, mask: WORD, reservation: SptpOps.ReservationObject ¬ TRASH, newStatus: NewRS232CFace.DeviceStatus ¬ latchBits, oldStatus: NewRS232CFace.DeviceStatus ¬ allOffStatus, getGarbage, cmdInProgress, lineStatusChange: BOOLEAN ¬ FALSE]; input: RECORD[ q, r: Buffer.QueueObject, nextFragment: NATURAL ¬ 0, access: Buffer.AccessHandle, timeLastRecv: LONG CARDINAL, queueAllowed: NATURAL, lastStatus: NewRS232CFace.TransferStatus]; inputQueueLength: CARDINAL = 4; output: RECORD[ notify: CONDITION, q, w: Buffer.QueueObject, timeXmtDone: LONG CARDINAL, lastStatus: NewRS232CFace.TransferStatus]; protocol: SptpProtocol.ProtocolRecord; clock: RECORD[ giveup: CARDINAL, --seconds shortTmo, onHook, clockCksm: CARDINAL, --ticks idleInput, stuckOutput, mstrTmo, ltaHold, cmdTmo, cdDelay, ctsDelay: LONG CARDINAL]; --IOCBs iocbState: RECORD[first, free: FreeIocb, avail: INTEGER]; FreeIocb: TYPE = LONG POINTER TO free IocbObject; InuseIocb: TYPE = LONG POINTER TO inuse IocbObject; IocbObject: TYPE = RECORD[ field: SELECT COMPUTED * FROM free => [next: FreeIocb, rest: SEQUENCE COMPUTED CARDINAL OF WORD], inuse => [op: NewRS232CFace.Operation], --do we need the rest? ENDCASE]; --THE NETWORK OBJECT FOR THIS DRIVER myDevice: Driver.DeviceObject ¬ [ matrix: NIL, sendRawBuffer: SendRawBuffer, activateDriver: ActivateDriver, deactivateDriver: DeactivateDriver, deleteDriver: DeleteDriver, changeNumberOfInputBuffers: Null, buffers:, device: phonenet, getThroughput: GetThroughput, lineSpeed: 0, lineNumber: 0, stats: NIL, receiveBufferLen: maxRS232CBytes, alive: FALSE, index:, next: NIL]; sptpStats: SptpOps.StatsRecord; --where to keep interesting info --This module doesn't support .5 duplex even if the interface says it does point5Duplex: BOOLEAN = ~SptpOps.point5Duplex; --level of support BUG: ERROR = CODE; IOCBSizeIsZero: ERROR = CODE; OverlayingIocb: ERROR = CODE; PacketTooLarge: ERROR = CODE; DriverNotActive: ERROR = CODE; DriverAlreadyActive: ERROR = CODE; allOffStatus: NewRS232CFace.DeviceStatus = [ breakDetected: FALSE, dataLost: FALSE, ringHeard: FALSE, carrierDetect: FALSE, clearToSend: FALSE, dataSetReady: FALSE, ringIndicator: FALSE, unused: 0]; latchBits: NewRS232CFace.DeviceStatus ¬ [ breakDetected: TRUE, dataLost: TRUE, ringHeard: TRUE, carrierDetect: FALSE, clearToSend: FALSE, dataSetReady: FALSE, ringIndicator: FALSE, unused: 0]; dceLatches: NewRS232CFace.DeviceStatus ¬ [ breakDetected: FALSE, dataLost: TRUE, ringHeard: FALSE, carrierDetect: FALSE, clearToSend: FALSE, dataSetReady: FALSE, ringIndicator: FALSE, unused: 0]; dceUp: NewRS232CFace.DeviceStatus ¬ [ breakDetected: FALSE, dataLost: FALSE, ringHeard: FALSE, carrierDetect: TRUE, clearToSend: TRUE, dataSetReady: TRUE, ringIndicator: FALSE, unused: 0]; StatusToReset: PROC[s, m: NewRS232CFace.DeviceStatus] RETURNS[NewRS232CFace.ResetRecord] = MACHINE CODE {Mopcodes.zAND}; StatusToStatus: PROC[s, m: NewRS232CFace.DeviceStatus] RETURNS[NewRS232CFace.DeviceStatus] = MACHINE CODE {Mopcodes.zAND}; Instance: TYPE = LONG POINTER TO FRAME[SptpDriver]; framing: NATURAL = bpw * SIZE[SptpProtocol.EncapsulationObject] + 2; --2 is lrc frameOffset: NATURAL = (Buffer.dataLinkReserve - framing) / bpw; --in words ActiveDataState: PROC[] = BEGIN --don't wait for anything but interrupt b: Buffer.Buffer = SptpProtocol.WaitForControl[@protocol, 0]; IF b # NIL THEN SptpProtocol.ProcessControl[@protocol, b]; END; --ActiveDataState ActivateDriver: PROC = BEGIN iocbs: CARDINAL = 2 * myDevice.buffers; --2X the number of receive buffers priority: Process.Priority = Process.GetPriority[]; --so can restore later IF ~rs232c.pleaseStop THEN Driver.Glitch[DriverAlreadyActive]; lineSpeed ¬ 0; --so it knows to go and compute again rs232c.getGarbage ¬ FALSE; --everybody's favorite default protocol.object.me ¬ SpecialSystem.GetProcessorID[]; IF HostNumbers.IsMulticastID[@protocol.object.me] THEN WaitForHellToFreeze[]; rs232c.handle ¬ NewRS232CFace.GetHandle[ protocol.object.lineNumber, debuggerClient]; [cv: rs232c.interrupt, mask: rs232c.mask] ¬ SpecialRuntime.AllocateNakedCondition[]; NewRS232CFace.Initialize[rs232c.mask]; NewRS232CFace.InitializeCleanup[]; Process.DisableTimeout[rs232c.interrupt]; IF ~CommFlags.driverStats THEN myDevice.stats ¬ NIL ELSE (myDevice.stats ¬ LONG[@sptpStats])­ ¬ []; Buffer.QueueInitialize[@protocol.q]; protocol.lock ¬ @LOCK; rs232c.probe ¬ rs232c.transmits ¬ 0; rs232c.lta ¬ FALSE; --ie., we have the line rs232c.traceProc ¬ NIL; --'cause we ain't rs232c.pleaseStop ¬ FALSE; --and we're not stopping protocol.sendControlFrame ¬ SendControlFrame; IF SptpOps.siuSupport THEN --only works for single port protocol.sppAllocationWindow ¬ SppOps.sppWindowSize; IF point5Duplex AND (protocol.object.duplex = half) THEN dceUp.clearToSend ¬ dceUp.carrierDetect ¬ FALSE; protocol.object.state ¬ idle; --temporary state input.lastStatus ¬ output.lastStatus ¬ disaster; --forces new read DoCommandEntry[off]; --maybe this will inhibit reboots (see note[1]) <<+++++++>> reason ¬ voluntary; <<+++++++>> Buffer.QueueInitialize[@input.q]; Buffer.QueueInitialize[@input.r]; Buffer.QueueInitialize[@output.q]; Buffer.QueueInitialize[@output.w]; input.queueAllowed ¬ myDevice.buffers; input.access ¬ Buffer.MakePool[0, myDevice.buffers]; input.timeLastRecv ¬ output.timeXmtDone ¬ System.GetClockPulses[]; iocbState.free ¬ NIL; IF NewRS232CFace.operationSize = 0 THEN Driver.Glitch[IOCBSizeIsZero]; IF CommFlags.doDebug THEN {iocbState.avail ¬ iocbs}; iocbState.first ¬ iocbState.free ¬ CommUtil.AllocateIocbs[ bpw * iocbs * NewRS232CFace.operationSize]; --special storage for iocbs THROUGH [0..iocbs - 1) DO iocbState.first ¬ iocbState.first.next ¬ iocbState.first + NewRS232CFace.operationSize; REPEAT FINISHED => {iocbState.first.next ¬ NIL; iocbState.first ¬ iocbState.free}; ENDLOOP; Process.SetPriority[CommPriorities.driver]; --these guys are hot rs232c.process ¬ FORK Interrupt[]; --gets naked notifies protocol.watcher ¬ FORK Watcher[]; --runs the state machine Process.SetPriority[priority]; --back to caller priority END; --ActivateDriver ComputeLineSpeed: PROC[b: Buffer.Buffer] = BEGIN bits: LONG CARDINAL = 8D6 * b.fo.driver.length; --bits involved X 10­6 duration: LONG CARDINAL ¬ output.timeXmtDone - b.fo.time; --in pulses duration ¬ System.PulsesToMicroseconds[[duration]]; --in usecs lineSpeed ¬ CARDINAL[bits--10­6-- / duration--10­6--]; --bits per second IF point5Duplex AND (protocol.object.duplex = half) THEN BEGIN clock.mstrTmo ¬ MasterTimeout[lineSpeed]; --now that we know better clock.ltaHold ¬ clock.mstrTmo / 64; --that's based on measured line speed END; END; --ComputeLineSpeed CreateDriver: PUBLIC <> PROC[reservation: SptpOps.Reservation] = BEGIN him: Instance; device: Device = GetDevice[reservation.lineNumber]; SELECT TRUE FROM (device # NIL) => BEGIN him ¬ FrameFromDevice[device]; him.refCount ¬ him.refCount.SUCC; --one more ref END; (instanceBusy) => --testing the zeroth instance BEGIN him ¬ NEW SptpDriver; --brand new one START him; --so he'll initialize the procedure him.instanceCopied ¬ TRUE; --he's destructable refCount ¬ refCount.SUCC; --now has a reference him.setupDriver[reservation]; END; ENDCASE => BEGIN refCount ¬ refCount.SUCC; --now has a reference instanceBusy ¬ TRUE; --now he's busy setupDriver[reservation]; END; END; --CreateDriver DeactivateDriver: PROC = BEGIN process: PROCESS; IF rs232c.interrupt = NIL THEN RETURN; --sign of a deactive driver myDevice.alive ¬ FALSE; --I'm dying - soon Process.Abort[protocol.watcher]; JOIN protocol.watcher; IF (protocol.object.state > option2) --up far enough to make this real AND (protocol.object.theirEntityClass # siu) THEN --and it's not an SIU BEGIN protocol.object.state ¬ terminate1; --going down voluntarily SptpProtocol.SendTerminateRequest[@protocol]; --send our initial request THROUGH [0..4) DO SptpProtocol.AwaitTerminateReply[@protocol]; --wait answer/send request IF protocol.object.state = idle THEN EXIT; --got cooperation ENDLOOP; END; TurnDeviceOff[]; --this should put us back on hook DoCommandEntry[off]; --then shut down for good rs232c.pleaseStop ¬ TRUE; --for others to check rs232c.handle ¬ NewRS232CFace.ReleaseHandle[rs232c.handle]; IF (process ¬ rs232c.process) # NIL THEN UNTIL rs232c.process = NIL DO Process.EnableAborts[rs232c.interrupt]; Process.Abort[process]; REPEAT FINISHED => JOIN process; ENDLOOP; Buffer.QueueCleanup[@protocol.q]; --make sure they're all freed SpecialRuntime.DeallocateNakedCondition[rs232c.interrupt]; rs232c.interrupt ¬ NIL; --so we can detect deactivates followed by deletes CommUtil.FreeIocbs[iocbState.first]; Buffer.DestroyPool[input.access]; END; --DeactivateDriver DeleteDriver: PROC = BEGIN IF refCount = 0 THEN RETURN; --what the &~#%? IF (refCount ¬ refCount.PRED) # 0 THEN RETURN; --should be MONITORed IF rs232c.pleaseStop THEN Driver.Glitch[DriverNotActive]; Driver.RemoveDeviceFromChain[protocol.myDevice]; --get's us out of chain IF instanceCopied THEN Runtime.SelfDestruct[] --it was a copied frame ELSE instanceBusy ¬ FALSE; --or he was the zeroth instance END; --DeleteDriver DoCommandEntry: ENTRY PROC[command: NewRS232CFace.Command] = INLINE {ENABLE UNWIND => NULL; DoCommandInternal[command]}; --DoCommandEntry DoCommandInternal: INTERNAL PROC[command: NewRS232CFace.Command] = BEGIN timein: LONG CARDINAL; commandStatus: NewRS232CFace.CommandStatus ¬ rejected; WHILE rs232c.cmdInProgress DO WAIT rs232c.cmdDone; ENDLOOP; IF CommFlags.doStats THEN SptpStats.Incr[commandInit]; IF CommFlags.driverStats THEN sptpStats.commandInitiated ¬ sptpStats.commandInitiated.SUCC; rs232c.cmdInProgress ¬ TRUE; --flag us as busy timein ¬ System.GetClockPulses[]; --record time command requested --UNTIL commandStatus = complete-- DO ENABLE UNWIND => rs232c.cmdInProgress ¬ FALSE; --aborted? commandStatus ¬ (IF commandStatus = rejected THEN NewRS232CFace.InitiateCommand[rs232c.handle, command] ELSE NewRS232CFace.PollCommand[rs232c.handle]); SELECT TRUE FROM (commandStatus = completed) => EXIT; --that's success ((System.GetClockPulses[] - timein) > clock.cmdTmo) => GOTO commandLost; ENDCASE; WAIT rs232c.cmdDone; --wait some REPEAT commandLost => BEGIN IF CommFlags.doStats THEN SptpStats.Incr[commandLost]; IF CommFlags.driverStats THEN sptpStats.commandLost ¬ sptpStats.commandLost.SUCC; END; ENDLOOP; rs232c.cmdInProgress ¬ FALSE; --we're done END; --DoCommandInternal EnqueueReceive: ENTRY PROC[b: Buffer.Buffer] RETURNS[BOOLEAN ¬ TRUE] = BEGIN --NO SIGNALS OUT OF HERE SELECT TRUE FROM (CommFlags.doDebug AND (b.fo.driver.iocb # NIL)) => Driver.Glitch[OverlayingIocb]; ((b.fo.driver.iocb ¬ iocbState.free) # NIL) => BEGIN OPEN inuse: NARROW[b.fo.driver.iocb, InuseIocb]; iocbState.free ¬ iocbState.free.next; iocbState.avail ¬ iocbState.avail.PRED; b.linkLayer.blockPointer ¬ b.linkLayer.blockPointer + frameOffset; inuse.op ¬ [b.linkLayer.blockPointer, MIN[(b.highLayer.stopIndexPlusOne + framing), maxRS232CBytes]]; SetFaceStatus[ b, NewRS232CFace.InitiateReceive[rs232c.handle, @inuse.op]]; Buffer.Enqueue[@input.q, b]; END; ENDCASE => --the ENDCASE better be rare! BEGIN IF CommFlags.doStats THEN Stats.StatIncr[statsIocbWait]; Driver.ReturnFreeBuffer[b]; --give the buffer back (ARGH!!) RETURN[FALSE]; --and tell caller we failed END; END; --EnqueueReceive FaceStatusToTransferStatus: PROC[fs: NewRS232CFace.TransferStatus] RETURNS[Buffer.TransferStatus] = INLINE { RETURN[SELECT fs FROM inProgress => pending, success => goodCompletion, aborted => aborted, << asyncFramingError, checksumError, dataLost => hardwareProblem, deviceError, disaster, frameTimeout => hardwareProblem, invalidCharacter, invalidFrame, parityError => hardwareProblem, >> ENDCASE => hardwareProblem]}; --FaceStatusToTransferStatus FlushReassemblyQueue: INTERNAL PROC[b: Buffer.Buffer] = BEGIN IF b # NIL THEN Driver.ReturnFreeBuffer[b]; --return this buffer UNTIL input.r.length = 0 DO --and all those in frag queue Driver.ReturnFreeBuffer[Buffer.Dequeue[@input.r]]; ENDLOOP; input.nextFragment ¬ 0; --we'll be restarting at zero END; --FlushReassemblyQueue << MAKING THIS AN INLINE WILL CAUSE THE COMPILATION TO FAIL. >> FrameFromDevice: PROC[device: Device] RETURNS[Instance] = --INLINE {RETURN[LOOPHOLE[Runtime.GlobalFrame[LOOPHOLE[device.activateDriver]]]]}; FreeBufferAndIocb: INTERNAL PROC[ b: Buffer.Buffer, queueProc: PROC[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; iocbState.avail ¬ iocbState.avail.SUCC; END; b.fo.network ¬ protocol.myDevice; --that's where the buffer came from queueProc[b]; --then put it on the relavent queue END; --FreeBufferAndIocb GetBufferAndIocb: INTERNAL PROC[length: NATURAL ¬ maxRS232CBytes] RETURNS[b: Buffer.Buffer] = BEGIN SELECT TRUE FROM (iocbState.free = NIL) => {IF CommFlags.doStats THEN Stats.StatIncr[statsIocbWait]; RETURN[NIL]}; ((b ¬ Driver.GetInputBuffer[FALSE, length + framing]) = 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; iocbState.avail ¬ iocbState.avail.PRED; b.linkLayer.blockPointer ¬ b.linkLayer.blockPointer + frameOffset; END; --GetBufferAndIocb GetDevice: PUBLIC <> PROC[lineNumber: CARDINAL] RETURNS [net: Device] = BEGIN FOR net ¬ Driver.GetDeviceChain[], net.next WHILE net # NIL DO SELECT TRUE FROM (net.device # myDevice.device) => NULL; (net.lineNumber = lineNumber) => RETURN; --'net' is interesting ENDCASE; ENDLOOP; <> END; --GetDevice GetEntityClass: PUBLIC <> PROC[device: Buffer.Device] RETURNS[SptpProtocol.EntityClass] = BEGIN gf: Instance = FrameFromDevice[device]; RETURN[gf.protocol.object.theirEntityClass]; END; --GetEntityClass GetProtocolInfo: PUBLIC <> PROC[ device: Device, info: SptpProtocol.ProtocolInfo] = BEGIN gf: Instance = FrameFromDevice[device]; info­ ¬ gf.protocol.object; END; --GetProtocolInfo GetStatusEntry: ENTRY PROC[] = INLINE {ENABLE UNWIND => NULL; GetStatusInternal[]}; GetStatusInternal: INTERNAL PROC[] = BEGIN tempStatus: NewRS232CFace.DeviceStatus; DoCommandInternal[getDeviceStatus]; --issue the command tempStatus ¬ NewRS232CFace.GetDeviceStatus[rs232c.handle]; IF rs232c.newStatus # tempStatus THEN BEGIN rs232c.oldStatus ¬ rs232c.newStatus; rs232c.newStatus ¬ tempStatus; rs232c.lastStatusChange ¬ System.GetClockPulses[]; END; rs232c.lineStatusChange ¬ FALSE; --now that we responded END; --GetStatusInternal GetThroughput: PROC[] RETURNS[CARDINAL] = {RETURN[lineSpeed / 1000]}; GetVersion: PUBLIC <> PROC[device: Device] RETURNS[SptpProtocol.ProtocolVersion] = BEGIN gf: Instance = FrameFromDevice[device]; RETURN[gf.protocol.object.protocolVersion]; END; --GetVersion GetXmtQueue: PUBLIC <> PROC[device: Buffer.Device] RETURNS[Buffer.Queue, LONG POINTER TO CONDITION] = BEGIN gf: Instance ¬ FrameFromDevice[device]; RETURN[@gf.output.w, @gf.output.notify]; END; --GetXmtQueue InitiateOutput: INTERNAL PROC[b: Buffer.Buffer] = BEGIN OPEN inuse: NARROW[b.fo.driver.iocb, InuseIocb]; ENABLE UNWIND => {b.fo.status ¬ aborted; Driver.PutOnGlobalDoneQueue[b]}; down: BOOLEAN = StatusToStatus[rs232c.newStatus, dceUp] # dceUp; NOTIFY output.notify; --in case any client is watching IF rs232c.lineStatusChange OR down THEN BEGIN GetStatusInternal[]; --get latest info from the IOP IF ~rs232c.newStatus.dataSetReady THEN GOTO terminate; --dead connection END; SELECT TRUE FROM (StatusToStatus[rs232c.newStatus, dceLatches] = allOffStatus) => NULL; (~ResetLatchesInternal[]) => GOTO terminate; --test and reset latches ENDCASE; IF point5Duplex AND (protocol.object.duplex = half) THEN BEGIN timein: LONG CARDINAL = System.GetClockPulses[]; IF rs232c.lta AND (protocol.object.state = data) THEN GOTO rejected; --UNTIL (System.GetClockPulses[] - timein) > clock.ctsDelay-- DO ENABLE UNWIND => {b.fo.status ¬ aborted; Driver.PutOnGlobalDoneQueue[b]}; commandStatus: NewRS232CFace.CommandStatus ¬ rejected; GetStatusInternal[]; --read the current device status IF rs232c.newStatus.clearToSend --must be up AND ~rs232c.newStatus.carrierDetect THEN EXIT; --must be down IF (System.GetClockPulses[] - timein) > clock.ctsDelay THEN {reason ¬ noCTS; GOTO terminate}; --give it up SetControlInternal[[TRUE, TRUE]]; --raise RTS and keep DTR ENDLOOP; WITH SptpProtocol.EncapsulationFromBlock[b.linkLayer.blockPointer] SELECT FROM ns, arpa, iso, pup => --we control LTA on these rs232c.lta ¬ LTA ¬ (rs232c.transmits > window) OR (output.w.length = 0); control => rs232c.lta ¬ LTA; --but not on all others ENDCASE => GOTO rejected; --I don't want to deal with these right now rs232c.transmits ¬ IF ~rs232c.lta THEN rs232c.transmits.SUCC ELSE 0; END; 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; iocbState.avail ¬ iocbState.avail.PRED; inuse.op ¬ [b.linkLayer.blockPointer, b.fo.driver.length]; IF (lineSpeed = 0) AND (b.requeueProcedure = Driver.ReturnFreeBuffer) AND (b.fo.driver.length > measurableLength) THEN --compute line speed BEGIN lineSpeed ¬ lineSpeed.SUCC; --so we don't do it again b.fo.time ¬ System.GetClockPulses[]; --record time transmit init'd END; SetFaceStatus[b, NewRS232CFace.InitiateTransmit[rs232c.handle, @inuse.op]]; IF output.q.length = 0 THEN output.timeXmtDone ¬ System.GetClockPulses[]; Buffer.Enqueue[@output.q, b]; EXITS rejected => BEGIN b.fo.status ¬ rejected; Driver.PutOnGlobalDoneQueue[b]; END; terminate => BEGIN protocol.object.state ¬ terminate2; b.fo.status ¬ rejected; Driver.PutOnGlobalDoneQueue[b]; END; END; --InitiateOutput Interrupt: <> ENTRY PROC[] = BEGIN << This is the procedure that responds to the naked notifies. It decides whether the interrupt completes an input, output or control operation and calls the proper routine to deal with the I/O completion or sets the state of the line, whichever is appropriate. Input processing has precedence over output. All possible sources of the interrupt are polled each time we wake up. Commands and SetParameters were requested by the Watcher. Notify him if the state of either changes. >> ENABLE UNWIND => NULL; b: Buffer.Buffer; causeForInterruptKnown: BOOLEAN; status: NewRS232CFace.TransferStatus; --UNTIL ABORTED-- DO ENABLE ABORTED => EXIT; WAIT rs232c.interrupt; --wait for something to finish causeForInterruptKnown ¬ FALSE; --until we find out why UNTIL (b ¬ input.q.first) = NIL DO operation: NewRS232CFace.OperationPtr ¬ b.fo.driver.iocb; [b.fo.driver.length, status] ¬ NewRS232CFace.PollReceiveOrTransmit[ rs232c.handle, operation]; IF status = inProgress THEN EXIT; InInterrupt[Buffer.Dequeue[@input.q], status]; causeForInterruptKnown ¬ TRUE; --that fits ENDLOOP; UNTIL (b ¬ output.q.first) = NIL DO operation: NewRS232CFace.OperationPtr ¬ b.fo.driver.iocb; [b.fo.driver.length, status] ¬ NewRS232CFace.PollReceiveOrTransmit[ rs232c.handle, operation]; IF status = inProgress THEN EXIT; OutInterrupt[Buffer.Dequeue[@output.q], status]; causeForInterruptKnown ¬ TRUE; --and so does that ENDLOOP; SELECT TRUE FROM (causeForInterruptKnown) => NULL; --we think we're under control (rs232c.cmdInProgress) => BROADCAST rs232c.cmdDone; --hopefully his ENDCASE => {rs232c.lineStatusChange ¬ TRUE; BROADCAST protocol.engine}; ENDLOOP; rs232c.process ¬ NIL; --we're out of here END; --Interrupt InInterrupt: INTERNAL PROC[ b: Buffer.Buffer, status: NewRS232CFace.TransferStatus] = BEGIN OPEN inuse: NARROW[b.fo.driver.iocb, InuseIocb]; << Called by the interrupt state process when an input operation has just completed. The buffer has been dequeued and the operation is in the NewRS232CFace.CompletedTransferStatus category (ie., not "inProgress"). >> encapsulation: SptpProtocol.Encapsulation = SptpProtocol.EncapsulationFromBlock[b.linkLayer.blockPointer]; input.lastStatus ¬ status; --record for posterity b.fo.status ¬ FaceStatusToTransferStatus[status]; SELECT status FROM success => BEGIN rs232c.probe ¬ 0; --any reception is as good as a "iAmHere" input.timeLastRecv ¬ b.fo.time ¬ System.GetClockPulses[]; --line's alive IF point5Duplex AND (protocol.object.duplex = half) THEN BEGIN << The polarity of rs232c.lta is for sending. It has been reversed on the receive side. Watch out for this! The correct definition is: "The line is not turned around. Therefore we can transmit." Therefore NOT rs232c.lta => requestToSend: TRUE; >> lta: BOOLEAN = WITH encapsulation SELECT FROM control => ~LTA, pup, ns, arpa, iso => ~LTA, ENDCASE => TRUE; IF lta # rs232c.lta THEN --it's different that last time BEGIN rs232c.lta ¬ lta; --record for posterity BROADCAST protocol.engine; --notify engine IF CommFlags.doStats THEN SptpStats.Incr[lta]; END; END; IF CommFlags.driverStats THEN BEGIN sptpStats.pktsReceived ¬ sptpStats.pktsReceived.SUCC; sptpStats.bytesReceived ¬ sptpStats.bytesReceived + b.fo.driver.length; END; IF CommFlags.doStats THEN BEGIN SptpStats.Incr[packetsRcvd]; SptpStats.Bump[bytesRcvd, b.fo.driver.length]; END; b.linkLayer.stopIndexPlusOne ¬ b.fo.driver.length; IF rs232c.traceProc # NIL THEN rs232c.traceProc[b.linkLayer, rx, rs232c.newStatus]; FreeBufferAndIocb[b, WITH eo: encapsulation SELECT FROM control => PutOnControlQueue, --these go directly to control pup, ns, arpa, iso => --these might need reassembly (IF eo.more THEN PutOnReassemblyQueue ELSE Driver.PutOnGlobalInputQueue), ENDCASE => Driver.PutOnGlobalInputQueue]; --all the rest just go here b ¬ GetBufferAndIocb[maxRS232CBytes]; --get buffer to replace END; #aborted => BEGIN IF CommFlags.doStats THEN BEGIN dif: NATURAL = status.ORD - NewRS232CFace.TransferStatus[aborted].ORD; stat: NATURAL = SptpStats.StatCounterIndex[aborted].ORD + dif; SptpStats.Incr[LOOPHOLE[stat, SptpStats.StatCounterIndex]]; END; IF CommFlags.driverStats THEN SELECT status FROM checksumError => sptpStats.checksumError ¬ sptpStats.checksumError.SUCC; dataLost => sptpStats.dataLost ¬ sptpStats.dataLost.SUCC; deviceError => sptpStats.deviceError ¬ sptpStats.dataLost.SUCC; disaster => sptpStats.disaster ¬ sptpStats.disaster.SUCC; invalidFrame => sptpStats.invalidFrame ¬ sptpStats.invalidFrame.SUCC; ENDCASE; rs232c.lineStatusChange ¬ TRUE; --this is more than a guess BROADCAST protocol.engine; --and that's who needs to check it out SELECT TRUE FROM (rs232c.getGarbage) => BEGIN FreeBufferAndIocb[b, Driver.PutOnGlobalInputQueue]; --he wants it b ¬ GetBufferAndIocb[maxRS232CBytes]; --get buffer END; ((Buffer.DataBytesPerRawBuffer[b] + Buffer.dataLinkReserve) < maxRS232CBytes) => BEGIN FreeBufferAndIocb[b, Driver.PutOnGlobalDoneQueue]; --can't reuse b ¬ GetBufferAndIocb[maxRS232CBytes]; --get new buffer END; ENDCASE; FlushReassemblyQueue[NIL]; --if there was one END; ENDCASE => BEGIN IF CommFlags.doStats THEN SptpStats.Incr[aborted]; IF CommFlags.driverStats THEN sptpStats.receiveAborted ¬ sptpStats.receiveAborted.SUCC; --Aborted!? Maybe the protocol engine is in trouble... FreeBufferAndIocb[b, Driver.PutOnGlobalDoneQueue]; b ¬ NIL; --let the engine worry about the input queue refilling FlushReassemblyQueue[NIL]; --if there was one END; IF b = NIL THEN BROADCAST protocol.engine --let him deal with it ELSE BEGIN OPEN inuse: NARROW[b.fo.driver.iocb, InuseIocb]; << It's important here to make sure b.highLayer.stopIndexPlusOne hasn't been modified since the buffer was gotten from the BufferMgr >> inuse.op ¬ [b.linkLayer.blockPointer, MIN[(b.highLayer.stopIndexPlusOne + framing), maxRS232CBytes]]; SetFaceStatus[b, (status ¬ NewRS232CFace.InitiateReceive[rs232c.handle, @inuse.op])]; IF status = inProgress THEN Buffer.Enqueue[@input.q, b] ELSE BEGIN IF CommFlags.doStats THEN SptpStats.Incr[rcvFailed]; FreeBufferAndIocb[b, Driver.ReturnFreeBuffer]; --aborted BROADCAST protocol.engine; --what's going on? END; END; END; --InInterrupt MasterTimeout: PROC[ls: LONG CARDINAL] RETURNS[mwt: LONG CARDINAL] = BEGIN << AVOIDING OVER/UNDER FLOW!!!! Master watchdog timer = 1 + (3 packets X MPS X 8 bits/octet) / LS MPS = maximum packet size in octets per packet LS = line speed in bits per second >> mwt --bits-- ¬ 1 + (3 --packets-- * maxRS232CBytes --bytes/packet-- * 8 --bits/byte--); mwt --bits X 10­3-- ¬ mwt --bits-- * LONG[1000]; mwt --(msecs)-- ¬ mwt --bits X 10­3-- / ls --bits/second-- ; mwt --(usecs)-- ¬ mwt --(msecs)-- * LONG[1000]; RETURN[System.MicrosecondsToPulses[mwt]]; --(pulses) END; --MasterTimeout MediumNotAvailable: PROC[] RETURNS[down: BOOLEAN] = BEGIN << We don't get notified every time a line status changes. So if we don't suspect something and the currently know status is okay, we trust it. Otherwise, go read the status and figure out the truth. NB: This is all to keep from hammering on the IOP for status. >> down ¬ StatusToStatus[rs232c.newStatus, dceUp] # dceUp; --current info IF rs232c.lineStatusChange OR down THEN BEGIN GetStatusEntry[]; --get latest info from the IOP down ¬ StatusToStatus[rs232c.newStatus, dceUp] # dceUp; --and recompute IF ~rs232c.newStatus.dataSetReady THEN --do we still have a connection? BEGIN reason ¬ dsrDropped; --we had it once protocol.object.state ¬ terminate2; --and then we lost it RETURN; -- but this line isn't useful anymore END; END; IF protocol.q.length # 0 THEN RETURN[FALSE]; --input data to process IF point5Duplex AND (protocol.object.duplex = half) THEN BEGIN IF rs232c.lta THEN BEGIN << MASTER TIMEOUT He has the line but he's not the master. Maybe we can grab away it and make things work again. This works for the time when we don't know who the master is too. The test here is that the line has been idle for more than clock.mstrTmo. We can't count the time we had the line in the computation for how long he's had it. >> SELECT TRUE FROM ((System.GetClockPulses[] - input.timeLastRecv) < clock.mstrTmo) => {}; ((System.GetClockPulses[] - output.timeXmtDone) < clock.mstrTmo) => {}; (protocol.object.master # him) => --let's go for it BEGIN IF CommFlags.driverStats THEN sptpStats.masterTmo ¬ sptpStats.masterTmo.SUCC; IF CommFlags.doStats THEN SptpStats.Incr[masterTMO]; rs232c.lta ¬ FALSE; --okay, we own it now input.timeLastRecv ¬ System.GetClockPulses[]; --restart timer RETURN[FALSE]; --lie about this so we use the line END; ENDCASE; END ELSE BEGIN << VOLUNTARY LTA We own the line, but we haven't used it for quite a while. Maybe we should turn it around by sending a null packet. >> SELECT TRUE FROM (protocol.object.state # data) => NULL; --only works in data state (output.q.length + output.w.length # 0) => NULL; --sending now ((System.GetClockPulses[] - input.timeLastRecv) > clock.ltaHold) => SptpProtocol.SendNull[@protocol]; --he gave us the line long ago ENDCASE; --not in right state to surrender line END; RETURN; --and in any case, return END; << The following monitoring code is only for full duplex (half returned above). If this is full duplex and we don't have clear to send, something funnies going on. Try raising RTS and see what happens (probably an SIU). If the protocol hasn't made contact with the remote, then we're probably just trying to get the line up (state < option3). If it's in a terminate2 state then we're already dealing with the modem. If the line isn't up but it wasn't last time either, then that's nothing new, so just report it to the caller. If the line is down, but it hasn't been for long, then report the fact but don't do anything rash. But, if the protocol is active and the line is down and it has been for some time, then force the phone back on hook. >> SELECT TRUE FROM (~down) => NULL; --line is not down (aka "up") (~rs232c.newStatus.clearToSend) => SetControlEntry[[TRUE, TRUE]]; (protocol.object.state ~IN[option3..terminate1]) => NULL; --not active ~(StatusToStatus[rs232c.oldStatus, dceUp] = dceUp) => NULL; --just changed ((System.GetClockPulses[] - rs232c.lastStatusChange) > clock.cdDelay) => {reason ¬ cdDropped; protocol.object.state ¬ terminate2}; ENDCASE; END; --MediumNotAvailable Null: PROC[BOOLEAN] = {--ChangeInputBuffers proc--}; OutInterrupt: INTERNAL PROC[ b: Buffer.Buffer, status: NewRS232CFace.TransferStatus] = BEGIN b.fo.status ¬ FaceStatusToTransferStatus[(output.lastStatus ¬ status)]; output.timeXmtDone ¬ System.GetClockPulses[]; --mark the time, good or bad IF point5Duplex AND (protocol.object.duplex = half) THEN BEGIN ENABLE UNWIND => NULL; --this block is abortable << What's it mean of we transmitted a LTA and the transmit failed? For the time being, we'll just act as if the other end will see the packet. Let the master's timeout figure out the situations where we guessed wrong. The follwing construct just pulls the LTA out of the packet just transmitted. Since the packet is COMPUTED/OVERLAID there's no gain in generating local variables with the results, so it's all bundled into a single expression. Don't copy the result into rs232c.lta. That's done when the frame is queued for transmit to block subsequent transmission attemps. All this code is doing is controlling the modem, which has to be done after the packet offering the LTA is transmitted. >> IF (WITH SptpProtocol.EncapsulationFromBlock[ b.linkLayer.blockPointer] SELECT FROM control => LTA, pup, ns, arpa, iso => LTA, ENDCASE => FALSE) THEN BEGIN rs232c.oldStatus ¬ rs232c.newStatus; --preserve old status WAIT rs232c.bitClock; --wait for bits to clock out SetControlInternal[[TRUE, FALSE]]; --dropping RTS GetStatusInternal[]; --force new status to be read IF CommFlags.doErrors AND rs232c.newStatus.clearToSend THEN Driver.Glitch[BUG]; --well, it should have dropped rs232c.lineStatusChange ¬ FALSE; --we believe status is current rs232c.lastStatusChange ¬ System.GetClockPulses[]; --as of this moment BROADCAST protocol.engine; --anybody care? IF CommFlags.doStats THEN SptpStats.Incr[lta]; END; END; SELECT status FROM success => BEGIN IF (lineSpeed = 1) AND (b.requeueProcedure = Driver.ReturnFreeBuffer) AND (b.fo.driver.length > measurableLength) THEN ComputeLineSpeed[b]; SELECT TRUE FROM (point5Duplex AND rs232c.lta) => NULL; --we don't own the line (output.q.length # 0) => NULL; --already got output queued (output.w.length # 0) => InitiateOutput[Buffer.Dequeue[@output.w]]; ENDCASE; --endcase - nothing to initiate IF CommFlags.driverStats THEN BEGIN sptpStats.packetsSent ¬ sptpStats.packetsSent.SUCC; sptpStats.bytesSent ¬ sptpStats.bytesSent + b.fo.driver.length; END; IF CommFlags.doStats THEN BEGIN SptpStats.Incr[packetsXmt]; SptpStats.Bump[bytesXmt, b.fo.driver.length]; END; b.linkLayer.stopIndexPlusOne ¬ b.fo.driver.length; IF rs232c.traceProc # NIL THEN rs232c.traceProc[b.linkLayer, tx, rs232c.newStatus]; END; ENDCASE => IF CommFlags.doStats THEN BEGIN dif: NATURAL = status.ORD - NewRS232CFace.TransferStatus[aborted].ORD; stat: NATURAL = SptpStats.StatCounterIndex[aborted].ORD + dif; SptpStats.Incr[LOOPHOLE[stat, SptpStats.StatCounterIndex]]; IF CommFlags.driverStats THEN SELECT status FROM aborted => sptpStats.sendAborted ¬ sptpStats.sendAborted.SUCC; deviceError => sptpStats.deviceError ¬ sptpStats.dataLost.SUCC; disaster => sptpStats.disaster ¬ sptpStats.disaster.SUCC; invalidFrame => sptpStats.invalidFrame ¬ sptpStats.invalidFrame.SUCC; ENDCASE; rs232c.lineStatusChange ¬ TRUE; --this is more than a guess BROADCAST protocol.engine; --let engine deal with such things END; FreeBufferAndIocb[b, Driver.PutOnGlobalDoneQueue]; --finished with this END; --OutInterrupt PutOnControlQueue: INTERNAL PROC[b: Buffer.Buffer] = BEGIN Buffer.Enqueue[@protocol.q, b]; BROADCAST protocol.engine; IF CommFlags.driverStats THEN sptpStats.controlPktReceived ¬ SUCC[sptpStats.controlPktReceived]; IF CommFlags.doStats THEN BEGIN SptpStats.Incr[cntrlRcvd]; SptpStats.Bump[cntrlBytesRcvd, b.fo.driver.length]; END; END; --PutOnControlQueue PutOnReassemblyQueue: INTERNAL PROC[b: Buffer.Buffer] = BEGIN << Only data packets with the more bit set get here. The IOCB has already been freed. If this is the last packet in a sequence, then reassemble them and put them on the GlobalInputQueue. >> eo: SptpProtocol.EncapsulationObject = --this is the new one SptpProtocol.EncapsulationFromBlock[b.linkLayer.blockPointer]­; WITH eo SELECT FROM pup, ns, arpa, iso => BEGIN SELECT TRUE FROM (fragment.n # input.nextFragment) => --out of sequence BEGIN IF CommFlags.doStats THEN SptpStats.Incr[badFragSeq]; FlushReassemblyQueue[b]; --this one is busted END; (fragment.n < fragment.of) => --next in the sequence BEGIN IF CommFlags.doStats THEN SptpStats.Incr[fragRcvd]; Buffer.Enqueue[@input.r, b]; --put it on the frag queue input.nextFragment ¬ input.nextFragment.SUCC; --that one's next END; ENDCASE => --all here; reassemble them in a single packet BEGIN moved: NATURAL; --counter for the blt operations rb: Buffer.Buffer ¬ Driver.GetInputBuffer[ --final assembly buffer FALSE, myDevice.receiveBufferLen + framing]; IF rb = NIL THEN {FlushReassemblyQueue[b]; RETURN}; IF CommFlags.doStats THEN SptpStats.Incr[reassembly]; Buffer.Enqueue[@input.r, b]; --put it on end of reassembly queue SptpProtocol.EncapsulationFromBlock[b.linkLayer.blockPointer]­ ¬ eo; rb.linkLayer.startIndex ¬ rb.linkLayer.startIndex + bpw * SIZE[ns SptpProtocol.EncapsulationObject]; --encap moved UNTIL input.r.length = 0 DO --and all those in frag queue b ¬ Buffer.Dequeue[@input.r]; --get the first/next fragment b.linkLayer.startIndex ¬ bpw * SIZE[ns SptpProtocol.EncapsulationObject]; moved ¬ ByteBlt.ByteBlt[to: rb.linkLayer, from: b.linkLayer]; rb.fo.driver.length ¬ moved + rb.fo.driver.length; Driver.ReturnFreeBuffer[b]; --return the fragment ENDLOOP; input.nextFragment ¬ 0; --we'll be restarting at zero Driver.PutOnGlobalInputQueue[rb]; --send it upstairs END; END; ENDCASE => IF CommFlags.doDebug THEN Driver.Glitch[BUG]; END; --PutOnReassemblyQueue ResetLatchesEntry: ENTRY PROC[] RETURNS[BOOLEAN] = INLINE {ENABLE UNWIND => NULL; RETURN ResetLatchesInternal[]}; --ResetLatchesEntry ResetLatchesInternal: INTERNAL PROC[] RETURNS[okay: BOOLEAN ¬ TRUE] = BEGIN THROUGH[0..4) DO timein: LONG CARDINAL; latched: NewRS232CFace.ResetRecord; commandStatus: NewRS232CFace.CommandStatus ¬ rejected; WHILE rs232c.cmdInProgress DO WAIT rs232c.cmdDone; ENDLOOP; rs232c.cmdInProgress ¬ TRUE; --so we have the IOP's attention timein ¬ System.GetClockPulses[]; --and when we got his attention latched ¬ StatusToReset[rs232c.newStatus, latchBits]; --bits to clear IF CommFlags.driverStats AND latched.resetDataLost THEN sptpStats.dataLost ¬ sptpStats.dataLost.SUCC; IF CommFlags.doStats AND latched.resetDataLost THEN SptpStats.Incr[dataLost]; --UNTIL commandStatus = complete-- DO ENABLE UNWIND => rs232c.cmdInProgress ¬ FALSE; --aborted? commandStatus ¬ (IF commandStatus = rejected THEN NewRS232CFace.InitiateResetStatusBits[rs232c.handle, latched] ELSE NewRS232CFace.PollSetControlBits[rs232c.handle]); SELECT TRUE FROM (commandStatus = completed) => EXIT; --that's success ((System.GetClockPulses[] - timein) < clock.cmdTmo) => NULL; --cont. (CommFlags.doErrors) => Driver.Glitch[BUG]; --that's a failure ENDCASE; --loop forever WAIT rs232c.cmdDone; --wait some ENDLOOP; rs232c.cmdInProgress ¬ FALSE; --so we can do a get status GetStatusInternal[]; --force a read of the device status IF StatusToStatus[rs232c.newStatus, dceLatches] = allOffStatus THEN EXIT; REPEAT FINISHED => {reason ¬ couldntClearLatches; okay ¬ FALSE}; ENDLOOP; END; --ResetLatchesInternal ResetQueues: PROC[] = BEGIN DumpQ: ENTRY PROC[q: Buffer.Queue, p: PROC[b: Buffer.Buffer]] = BEGIN UNTIL q.length = 0 DO b: Buffer.Buffer = Buffer.Dequeue[q]; b.fo.status ¬ aborted; FreeBufferAndIocb[b, p]; ENDLOOP; END; --DumpQ --head doesn't know about the 'w' queue DumpQ[@output.w, Driver.PutOnGlobalDoneQueue]; THROUGH[0..2) UNTIL (input.q.length + output.q.length = 0) DO IF input.q.length > 0 THEN DoCommandEntry[abortReceive]; IF output.q.length > 0 THEN DoCommandEntry[abortTransmit]; DoCommandEntry[on]; --maybe we can make the IOP notice Process.Pause[clock.shortTmo]; --wait for the gas to take effect ENDLOOP; IF CommFlags.doStats THEN SptpStats.Incr[faceReset]; IF CommFlags.driverStats THEN sptpStats.protocolDown ¬ sptpStats.protocolDown.SUCC; << Aborting the trasmit|receive queues should cause all the requests on the respective queue to be marked aborted and completed. The interrupt routine should run before us and by time we look, the queues should be empty. If for some reason that doesn't happen, we can go ahead and free them from here. Some day I should sort out why it doesn't happen reliably. >> DumpQ[@input.q, Driver.ReturnFreeBuffer]; DumpQ[@output.q, Driver.PutOnGlobalDoneQueue]; END; --ResetQueues SendBufferEntry: ENTRY PROC[b: Buffer.Buffer] = INLINE {ENABLE UNWIND => NULL; SendBufferInternal[b]}; --SendBufferEntry SendBufferInternal: INTERNAL PROC[b: Buffer.Buffer] = BEGIN --THERE ARE NO SIGNALS OUT OF HERE - GLITCHES DON'T COUNT-- IF CommFlags.doDebug AND b.fo.driver.iocb # NIL THEN Driver.Glitch[OverlayingIocb]; IF CommFlags.doErrors AND b.fo.driver.length > maxRS232CBytes THEN Driver.Glitch[PacketTooLarge]; SELECT TRUE FROM (output.q.length # 0) => Buffer.Enqueue[@output.w, b]; --to waiting queue (~point5Duplex) => --we're not doing any half duplex stuff BEGIN IF CommFlags.doStats THEN SptpStats.Incr[xmtQEmpty]; InitiateOutput[b]; --queue's empty, so stuff it out there END; (~rs232c.lta) => --.5 duplex and we own the line BEGIN output.timeXmtDone ¬ System.GetClockPulses[]; --so it doesn't look stuck IF CommFlags.doStats THEN SptpStats.Incr[xmtQEmpty]; InitiateOutput[b]; --queue's empty, so stuff it out there END; ENDCASE => Buffer.Enqueue[@output.w, b]; --we can't transmit (.5 duplex) END; --SendBufferInternal SendControlFrame: ENTRY PROC[b: Buffer.Buffer, size: NATURAL] = BEGIN ENABLE UNWIND => NULL; b.fo.driver.length ¬ size; --this many bytes IF protocol.object.duplex = full THEN WITH SptpProtocol.EncapsulationFromBlock[b.linkLayer.blockPointer] SELECT FROM control => LTA ¬ FALSE; ENDCASE; --don't toggle in full InitiateOutput[b]; --put it directly on output queue IF CommFlags.driverStats THEN sptpStats.controlPktSent ¬ SUCC[sptpStats.controlPktSent]; IF CommFlags.doStats THEN BEGIN SptpStats.Incr[cntrlXmt]; SptpStats.Bump[cntrlBytesXmt, size]; END; END; --SendControlFrame SendRawBuffer: PROC[b: Buffer.Buffer] = BEGIN fl: NATURAL; --fragment length ob: Environment.Block; --copy so we can modify q: Buffer.QueueObject; --it's local, so don't have to monitor el: NATURAL = SIZE[ns SptpProtocol.EncapsulationObject] * bpw; eo: SptpProtocol.EncapsulationObject ¬ SptpProtocol.EncapsulationFromBlock[ b.linkLayer.blockPointer]­; --coy the original so we can modify SELECT TRUE FROM (~myDevice.alive) => --can't use a dead driver {b.fo.status ¬ rejected; Driver.PutOnGlobalDoneQueue[b]}; (protocol.object.protocolVersion = version4) => BEGIN WITH neo: eo SELECT FROM --get the NARROW'd EncapsulationObject pup, ns, arpa, iso => BEGIN IF ~neo.more THEN {SendBufferEntry[b]; RETURN}; ob ¬ b.highLayer; --copy out the block so we can modify fl ¬ SptpOps.defaultMaxRS232CBytes - framing; --fragment length Buffer.QueueInitialize[@q]; --get all the fields assigned Buffer.Enqueue[@q, b]; --the first buffer in the queue is client's b.fo.driver.length ¬ fl; --w/ modified length UNTIL neo.fragment.n = neo.fragment.of DO fb: Buffer.Buffer ¬ Driver.GetInputBuffer[ --get a fragment buffer TRUE, SptpOps.defaultMaxRS232CBytes]; --even wait IF fb = NIL THEN LOOP; --this isn't going to fly without buffers neo.fragment.n ¬ neo.fragment.n.SUCC; --and update sequence number ob.startIndex ¬ ob.startIndex + fl; --we just did this many SptpProtocol.EncapsulationFromBlock[ fb.linkLayer.blockPointer]­ ¬ eo; fb.linkLayer.startIndex ¬ el; --offset past the encapsulation fb.highLayer.stopIndexPlusOne ¬ SptpOps.defaultMaxRS232CBytes; fl ¬ ByteBlt.ByteBlt[to: fb.linkLayer, from: ob]; --copy frag b.fo.driver.length ¬ fl; --the length of this fragment Buffer.Enqueue[@q, fb]; --stick it on the queue ENDLOOP; SendQueueEntry[@q]; --acquire the monitor and send all buffers END; ENDCASE; END; ENDCASE => SendBufferEntry[b]; --only version 4 supports fragmentation END; --SendRawBuffer SendQueueEntry: ENTRY PROC[q: Buffer.Queue] = {ENABLE UNWIND => NULL; UNTIL (q.length = 0) DO SendBufferInternal[Buffer.Dequeue[q]]; ENDLOOP}; SetFaceStatus: PROC[ b: Buffer.Buffer, status: NewRS232CFace.TransferStatus] = INLINE BEGIN OPEN di: LOOPHOLE[b.fo.driver, SptpProtocol.DriverInformation]; di.faceStatus ¬ phonenet[status]; END; --SetFaceStatus SetupDriver: PROC[reservation: SptpOps.Reservation] = BEGIN rs232c.pleaseStop ¬ TRUE; rs232c.reservation ¬ reservation­; --save initial values myDevice.lineSpeed ¬ SELECT reservation.lineSpeed FROM <= bps1200 => 1, bps2400 => 2, <= bps4800 => 5, bps7200 => 7, bps9600 => 9, bps19200 => 19, bps28800 => 28, bps38400 => 38, bps48000 => 48, ENDCASE => 56; clock.giveup ¬ 80; --seconds to establish connection clock.shortTmo ¬ Process.MsecToTicks[100]; --short pause clock.clockCksm ¬ Process.MsecToTicks[30]; --wait for cksm to clear SIO clock.onHook ¬ Process.MsecToTicks[2000]; --lowering DTR clock.cmdTmo ¬ System.MicrosecondsToPulses[5D5]; --500 msecs clock.cdDelay ¬ System.MicrosecondsToPulses[5D5]; --500 msecs clock.ctsDelay ¬ System.MicrosecondsToPulses[1D6]; --1 full second clock.idleInput ¬ System.MicrosecondsToPulses[30D6]; --30 secs clock.stuckOutput ¬ System.MicrosecondsToPulses[10D6]; --10 secs clock.mstrTmo ¬ MasterTimeout[<> 1200]; clock.ltaHold ¬ clock.mstrTmo / 64; --that's based on 1200 bps protocol.object.duplex ¬ reservation.duplex; protocol.object.lineNumber ¬ reservation.lineNumber; protocol.object.ourEntityClass ¬ reservation.entity; Process.EnableAborts[@rs232c.cmdDone]; Process.SetTimeout[@rs232c.cmdDone, clock.shortTmo]; Process.EnableAborts[@rs232c.bitClock]; Process.SetTimeout[@rs232c.bitClock, clock.clockCksm]; Process.EnableAborts[@protocol.engine]; Process.SetTimeout[@protocol.engine, clock.shortTmo]; myDevice.buffers ¬ input.queueAllowed ¬ inputQueueLength; --default Driver.AddDeviceToChain[(protocol.myDevice ¬ @myDevice)]; END; --SetupDriver SetCollectGarbageToo: PUBLIC <> PROC[ device: Device, collectGarbage: BOOLEAN] = BEGIN gf: Instance ¬ FrameFromDevice[device]; gf.rs232c.getGarbage ¬ collectGarbage; END; --SetCollectGarbageToo SetTrace: PUBLIC <> PROC[device: Device, proc: SptpOps.TraceProc] RETURNS[old: SptpOps.TraceProc] = BEGIN gf: Instance ¬ FrameFromDevice[device]; old ¬ gf.rs232c.traceProc; --record the old one gf.rs232c.traceProc ¬ proc; --and set the new one END; --SetTrace SetControlEntry: ENTRY PROC[bits: NewRS232CFace.ControlRecord] = INLINE {ENABLE UNWIND => NULL; SetControlInternal[bits]}; --SetControlEntry SetControlInternal: INTERNAL PROC[bits: NewRS232CFace.ControlRecord] = BEGIN timein: LONG CARDINAL; commandStatus: NewRS232CFace.CommandStatus ¬ rejected; WHILE rs232c.cmdInProgress DO WAIT rs232c.cmdDone; ENDLOOP; rs232c.cmdInProgress ¬ TRUE; --so we have the IOP's attention timein ¬ System.GetClockPulses[]; --and when we got his attention --UNTIL commandStatus = complete-- DO ENABLE UNWIND => rs232c.cmdInProgress ¬ FALSE; --aborted? commandStatus ¬ (IF commandStatus = rejected THEN NewRS232CFace.InitiateSetControlBits[rs232c.handle, bits] ELSE NewRS232CFace.PollSetControlBits[rs232c.handle]); SELECT TRUE FROM (commandStatus = completed) => EXIT; --that's a success ((System.GetClockPulses[] - timein) < clock.cmdTmo) => NULL; --continue (CommFlags.doErrors) => Driver.Glitch[BUG]; --that's a failure ENDCASE; --loop forever WAIT rs232c.cmdDone; --wait for something of interest to happen ENDLOOP; rs232c.cmdInProgress ¬ FALSE; --turn loose END; --SetControlInternal TurnDeviceOff: PROC[] = BEGIN myDevice.alive ¬ FALSE; --I'm dead SetControlEntry[[FALSE, FALSE]]; --hang up Process.Pause[clock.onHook]; --just wait (clock.onHook == clock.to5) ResetQueues[]; --get rid of the input and output queues Buffer.QueueCleanup[@protocol.q]; --make sure they're all freed protocol.object.state ¬ idle; --now I'm idle IF SptpOps.siuSupport AND (protocol.object.theirEntityClass = siu) THEN SppOps.SetWindow[protocol.sppAllocationWindow]; --reset back to original END; --TurnDeviceOff TurnDeviceOn: PROC[] = BEGIN set: NewRS232CFace.ParameterStatus; parameters: NewRS232CFace.ParameterRecord ¬ [ charLength: 8, clientType: unknown, correspondent: RS232CCorrespondents.nsSystemElement, echo: FALSE, flowControl: [none, 0, 0], frameTimeout: 500, lineSpeed: rs232c.reservation.lineSpeed, lineType: bitSynchronous, parity: none, stopBits: 2, syncChar: 26B, syncCount: 4]; DoCommandEntry[on]; --turn IOP on THROUGH[0..4) DO set ¬ NewRS232CFace.InitiateSetParameters[rs232c.handle, @parameters]; IF set # rejected THEN EXIT ELSE Process.Pause[clock.shortTmo]; REPEAT FINISHED => IF CommFlags.doErrors THEN Driver.Glitch[BUG]; ENDLOOP; THROUGH[0..4) DO set ¬ NewRS232CFace.PollSetParameters[rs232c.handle]; IF set # inProgress THEN EXIT ELSE Process.Pause[clock.shortTmo]; REPEAT FINISHED => IF CommFlags.doErrors THEN Driver.Glitch[BUG]; ENDLOOP; rs232c.newStatus ¬ latchBits; --force the latches to appear set [] ¬ ResetLatchesEntry[]; --then reset them IF point5Duplex AND (protocol.object.duplex = half) THEN {protocol.object.master ¬ undetermined; rs232c.lta ¬ FALSE}; rs232c.probe ¬ 0; --just like starting over protocol.object.started ¬ protocol.object.established ¬ [0]; protocol.object.him ¬ System.nullHostNumber; --we don't know him yet protocol.object.protocolVersion ¬ version4; --this is where we start rs232c.newStatus ¬ allOffStatus; --make sure this is reasonably bogus UNTIL rs232c.newStatus.dataSetReady DO SetControlEntry[[TRUE, TRUE]]; --raise DTR and RTS Process.Pause[clock.shortTmo]; --wait for the plot to thicken GetStatusEntry[]; --read status to see if we're where we want to be ENDLOOP; protocol.object.state ¬ option1; --and advance the state END; --TurnDeviceOn WaitForHellToFreeze: ENTRY PROC[] = BEGIN ProcessorFace.SetMP[989]; --should probably warn PSCO DO ENABLE ABORTED => CONTINUE; Process.Pause[LAST[CARDINAL]]; ENDLOOP; END; --WaitForHellToFreeze Watcher: PROC = BEGIN FeedOutputQueue: ENTRY PROC[] RETURNS[BOOLEAN] = INLINE BEGIN ENABLE UNWIND => NULL; IF (output.q.length # 0) OR (output.w.length = 0) THEN RETURN[FALSE]; IF point5Duplex AND rs232c.lta THEN RETURN[FALSE]; InitiateOutput[Buffer.Dequeue[@output.w]]; RETURN[TRUE]; END; --FeedOutputQueue WaitForLineChange: ENTRY PROC[] = INLINE {ENABLE UNWIND => NULL; WAIT protocol.engine}; --WaitForLineChange b: Buffer.Buffer; UNTIL rs232c.pleaseStop --OR ABORTED-- DO ENABLE ABORTED => EXIT; IF (protocol.object.state = terminate2) THEN BEGIN SptpProtocol.TerminationDally[@protocol]; --obligatory dally time TurnDeviceOff[]; --then shut down the device - state == idle END; IF (protocol.object.state = idle) THEN TurnDeviceOn[]; --back up IF MediumNotAvailable[] THEN {WaitForLineChange[]; LOOP}; --'till ready << The following loop is trying to keep the input queue full. If the interrupt routine couldn't get a new buffer (and he's not permitted to wait while holding the monitory), then he will notify the protocol engine (that's me). This loop does WAIT for a buffer, but if it still comes back NIL, it bails out. >> WHILE (input.q.length < input.queueAllowed) DO b ¬ Driver.GetInputBuffer[TRUE, maxRS232CBytes + framing]; IF b = NIL THEN EXIT; --we waited and still didn't get one IF ~EnqueueReceive[b] THEN EXIT; --queue buffer if an iocb available ENDLOOP; --Check the state machine SELECT protocol.object.state FROM option1 => SptpProtocol.ActiveNegotiation[@protocol]; --try establish option2 => SptpProtocol.PassiveNegotiation[@protocol]; --wait for him option3 => SptpProtocol.AwaitingOptions[@protocol]; --wait his myOptions option4 => SptpProtocol.AwaitingOptionAck[@protocol]; --wait his ack data => ActiveDataState[]; --dominant state of the engine terminate1 => SptpProtocol.AwaitTerminateReply[@protocol]; --death song ENDCASE; --STUCK OUTPUT SELECT TRUE FROM (FeedOutputQueue[]) => NULL; --just keeping the queue fed (output.q.length = 0) => NULL; --nothing to check ((System.GetClockPulses[] - output.timeXmtDone) > clock.stuckOutput) => BEGIN IF CommFlags.doStats THEN Stats.StatIncr[statPacketsStuckInOutput]; ResetQueues[]; --life isn't good any more output.timeXmtDone ¬ System.GetClockPulses[]; --to keep from looping LOOP; --and since that shut the device off... END; ENDCASE; --IDLE INPUT IF (System.GetClockPulses[] - input.timeLastRecv) > clock.idleInput THEN BEGIN SELECT protocol.object.state FROM = data => NULL; --dominant case < option3 => GOTO skip; --waiting patiently ENDCASE => GOTO dead; --just drop the line input.timeLastRecv ¬ System.GetClockPulses[]; --to avoid looping IF CommFlags.doStats THEN Stats.StatIncr[statInputIdle]; SELECT TRUE FROM (protocol.object.protocolVersion = version2) => NULL; --too dumb ((rs232c.probe ¬ rs232c.probe.SUCC) > probeCount) => GOTO dead; --... ENDCASE => SptpProtocol.SendAreYouThere[@protocol]; --see if line works EXITS skip => NULL; --not valid state for test dead => {TurnDeviceOff[]; LOOP}; --failed connection END; ENDLOOP; END; --Watcher --initialization setupDriver ¬ SetupDriver; --for multi instances END. --SptpDriver Notes and Comments This is modeled after the EthernetDriver which is built in the model being promoted as "correct" for PrincOps systems. It is a client of the NewRS232CFace. The previous effort in this area was a client of RS232C (the channel). That was a driver written on top of a driver that was a client of a head that had its own mini-driver imbedded. In the process of writing this, lots of things have been discovered about the RS232C heads (especially DLions). Some of those are noted here. This log should change as/if we fix the strangeness in the heads. 1-Turning RS232C 'on' may cause the DLion system to reboot. If the IOP is turned 'on' and the device is soft booted, trying to turn the device on again may cause it to reboot. I believe that a soft boot doesn't reset the entire system (IOP) as completely as a Reset-B does. 2-Turning RS232C 'off' more than once per session doesn't work Previously the device was only turned off once per session (soft or hard). That was done when the RS232CHeadDLion was start trapped. That protected it from the note mentioned in the previous paragraph. But subsequent 'off's seem to not work, perhaps due to a recording of the parameters by the head and subsequent suppression of commands after that. As noted by BKI 24 Nov 87 19:21:48 PST, "A quick fix to the head is to set curParameterValid to FALSE after initiating the off command." **This one is fixed. 3-The interrupt from RS232C is ambiguous. You can't tell if it's from an I/O operation, a command completion (they all interrupt when complete) or a modem signal changing. Consequently, this module tries to match an interrupt up with some known cause. If there is I/O complete (discovered by polling outstanding requests) the interrupt is credited to that operation. If there is a command in progress, then that gets credited. Otherwise it guesses that there was a modem signal change and proceeds on that assumption. 4-This implementation implements V.4 of the Synchronous Point to Point protocol. Since there are no other implementors of V.4 out there, it will always negotiate down to V.3 or (heaven forbid V.2, aka SIUs). V.4 is fully compliant with V.3 if the packet sizes do not require fragmentation. 5-The fragmentation code takes a buffer and gets 'n' smaller buffers and BLTs fragments of the first into them. All along it's queuing, first the origninal with it's length changed, then the latter fragments into a local queue (Buffer.Queue). It does all this without holding the monitor, using the client process. That means the Driver.GetInputBuffer can WAIT. Then, when it has the queue with all the fragments constructed, it grabs the monitor and dumps them all (in sequence) onto the transmit queue. Hence, no client data interleave. 6-On the receive side, the queuing is done using holding the monitor, in the InInterrupt procedure (forget PhonenetDriver, think EthernetDriver - that's where the model came from). When the last fragment comes in, it reassembles them, still holding the montitor, and puts them on the global input queue. Because its holding the monitor, it can't wait. If it doesn't get a buffer, it tosses the entire sequence! The only alternative I can think of is another process, and I'm resisting. Getting the buffers shouldn't be a problem. The driver will probably extend the pool when it has to support reassembly. The client will bear the cost. LOG time - by - action 19-Aug-87 17:33:26 AOF Created file from EthernetDriver 21-Oct-87 20:40:39 AOF Adding code for line status change monitoring 2-Nov-87 10:38:31 AOF Better (different) I/O abort code 3-Nov-87 15:07:47 AOF Chasing commands lost 11-Nov-87 20:58:22 AOF Fragmentation & reassembly 23-Nov-87 18:26:37 AOF Not relying on client selection of line speed 25-Nov-87 9:30:56 AOF Notes and Comments, tweaking 'on' and 'off' 27-Nov-87 11:32:35 AOF Add XmtQueue visability and computing lineSpeed 30-Nov-87 14:35:04 AOF Addressing notes #1 and #2 30-Nov-87 18:21:18 AOF Reset master when line is dropped 2-Dec-87 15:15:23 AOF Don't transmit out of LineArbitration when dce down 5-Jan-88 11:22:56 AOF Voluntary line turnaround clocking masterTimeout 12-Jan-88 12:29:56 AOF Using new New RS232CFace 15-Jan-88 9:44:34 AOF Controlling SPP allocation window for SIUs 16-Jan-88 18:27:59 AOF Frame tracing for debugging 20-Jan-88 16:44:28 AOF Free buffer on UNWIND from InitiateOutput 3-Feb-88 11:28:32 AOF PupGateway (no .5 duplex)