-- HalfDuplexImpl.mesa (last edited by: Garlick on: February 27, 1981 5:22 PM) -- DIRECTORY BufferDefs USING [Buffer], CommFlags USING [doStats], DriverTypes USING [ Encapsulation, PhonePacketType, phoneEncapsulationOffset, phoneEncapsulationBytes], HalfDuplex USING [], Process USING [SetTimeout, MsecToTicks], ProcessorFace USING [microsecondsPerHundredPulses, GetClockPulses], RS232C USING [ ChannelHandle, LineSpeed, DeviceStatus, CompletionHandle, PhysicalRecord, ChannelSuspended, Put, TransmitNow, SetParameter, GetStatus, StatusWait], NSAddress USING [ProcessorID]; HalfDuplexImpl: MONITOR IMPORTS RS232C, Process, ProcessorFace EXPORTS HalfDuplex SHARES BufferDefs = BEGIN -- types ControlMode: TYPE = {unknown, master, slave}; TurnAroundState: TYPE = {sending, receiving}; Timer: TYPE = { turnAround, -- max time sending in one direction noMoreToSend, -- idle sender becomes receiver after this time masterResponse}; -- master sends turn-around if it receives nothing in this time StatsRecord: TYPE = RECORD [ turnArndSent, turnArndRcvd, masterTOs, noSendTOs, turnTOs: CARDINAL]; -- writeable data timerProcess: PROCESS; turnAroundState: TurnAroundState; controlMode: ControlMode; timer: ARRAY Timer OF LONG CARDINAL; -- in pulses timeout: ARRAY Timer OF LONG CARDINAL ← [ turnAround:, noMoreToSend: nothingSentNMTSPulses, masterResponse: initialMasterResponsePulses]; stopTimer: BOOLEAN; timeoutTimer: CONDITION; turnAroundArrived: CONDITION; ourTurnToSend: BOOLEAN; driverSending: BOOLEAN; senderHasMore: BOOLEAN; sendFinished: CONDITION; channel: RS232C.ChannelHandle; clearToSendUp: BOOLEAN; ourProcessorID: NSAddress.ProcessorID; statsRec: StatsRecord; -- constants timerWakeup: CARDINAL = 250; -- the following are optimized for the current packet stream allocation window size (statically set to 3, currently). Our algorithm won't give great performance if window size gets bigger. bps1200TurnAroundTimeout: CARDINAL = 13000; -- msecs bps2400TurnAroundTimeout: CARDINAL = bps1200TurnAroundTimeout/2; -- msecs bps4800TurnAroundTimeout: CARDINAL = bps1200TurnAroundTimeout/4; -- msecs bps9600TurnAroundTimeout: CARDINAL = bps1200TurnAroundTimeout/8; -- msecs -- noMoreToSend (NMTS) Timeouts nothingSentNMTSPulses: LONG CARDINAL ← MilliSecondsToPulses[ nothingSentNMTSTimeout]; sentSomethingNMTSPulses: LONG CARDINAL ← MilliSecondsToPulses[ sentSomethingNMTSTimeout]; remoteAnxiousNMTSPulses: LONG CARDINAL ← MilliSecondsToPulses[ remoteAnxiousNMTSTimeout]; initialMasterResponsePulses: LONG CARDINAL ← MilliSecondsToPulses[ initialMasterResponseTimeout]; nothingSentNMTSTimeout: CARDINAL = 500; -- msecs sentSomethingNMTSTimeout: CARDINAL = 250; -- msecs remoteAnxiousNMTSTimeout: CARDINAL = 250; -- msecs initialMasterResponseTimeout: CARDINAL = 5000; -- msecs -- errors and signals -- ********Init/termination******** Initialize: PUBLIC PROCEDURE [ chHandle: RS232C.ChannelHandle, lineSpeed: RS232C.LineSpeed, ourHostNumber: NSAddress.ProcessorID] = -- initialize timer BOOLEANs and start timer process BEGIN -- local timeInMilliSecs: CARDINAL; channel ← chHandle; -- save channel handle -- the following is to optimize for OISCP windowing, which currently batches 3 packets before requesting an ACK timeInMilliSecs ← SELECT lineSpeed FROM bps2400 => bps2400TurnAroundTimeout, bps4800 => bps4800TurnAroundTimeout, bps9600 => bps9600TurnAroundTimeout, ENDCASE => bps1200TurnAroundTimeout; timeout[turnAround] ← MilliSecondsToPulses[timeInMilliSecs]; stopTimer ← FALSE; clearToSendUp ← FALSE; controlMode ← unknown; turnAroundState ← receiving; ourProcessorID ← ourHostNumber; ourTurnToSend ← FALSE; driverSending ← FALSE; senderHasMore ← FALSE; StartTimer[masterResponse]; Process.SetTimeout[@timeoutTimer, Process.MsecToTicks[timerWakeup]]; timerProcess ← FORK TimerProcess[]; IF CommFlags.doStats THEN statsRec ← [0, 0, 0, 0, 0]; END; Destroy: PUBLIC PROCEDURE = -- terminate timer process BEGIN -- local NotifyConditions: ENTRY PROCEDURE = BEGIN stopTimer ← TRUE; ourTurnToSend ← TRUE; driverSending ← FALSE; NOTIFY timeoutTimer; NOTIFY turnAroundArrived; NOTIFY sendFinished; END; NotifyConditions[]; JOIN timerProcess; END; -- ********timers******** TimerProcess: PROCEDURE = -- loop checking times, depending on the turnAroundState BEGIN CheckIfTimedOut: PROCEDURE [timerSelect: Timer] RETURNS [timedOut: BOOLEAN] = BEGIN timedOut ← (GetClock[] - timer[timerSelect] > timeout[timerSelect]); IF CommFlags.doStats THEN IF timedOut THEN SELECT timerSelect FROM masterResponse => StatIncr[@statsRec.masterTOs]; turnAround => StatIncr[@statsRec.turnTOs]; noMoreToSend => StatIncr[@statsRec.noSendTOs]; ENDCASE => ERROR; END; RandomWaitTime: PROCEDURE RETURNS [CARDINAL] = INLINE -- gives random number in range [1000..3048) BEGIN LowExtractor: TYPE = MACHINE DEPENDENT RECORD [ highBitsLowWord: [0..32), lowTenBits: [0..2048), secondWord: CARDINAL]; RETURN[(LOOPHOLE[GetClock[], LowExtractor]).lowTenBits + 1000]; END; UNTIL stopTimer DO SELECT turnAroundState FROM sending => IF CheckIfTimedOut[noMoreToSend] OR CheckIfTimedOut[turnAround] THEN BEGIN SendLTA[]; -- will happen after any SendFrame in progress IF controlMode = unknown THEN SetTimer[masterResponse, RandomWaitTime[]]; IF controlMode # slave THEN StartTimer[masterResponse]; turnAroundState ← receiving; END; receiving => -- slave side has no timeout, it just awaits turnaround IF controlMode # slave THEN BEGIN IF CheckIfTimedOut[masterResponse] THEN BEGIN -- assume some packet lost StartTimer[turnAround]; SetTimer[noMoreToSend, nothingSentNMTSPulses]; -- longer if waiting to send first packet than if we sent one already StartTimer[noMoreToSend]; turnAroundState ← sending; senderHasMore ← FALSE; IF controlMode # unknown THEN NotifyOurTurnToSend[]; END; END; ENDCASE => ERROR; WaitTimer[]; ENDLOOP; END; WaitTimer: ENTRY PROCEDURE = BEGIN WAIT timeoutTimer; END; StartTimer: PROCEDURE [timerSelect: Timer] = INLINE -- put current time in the timer BEGIN timer[timerSelect] ← ProcessorFace.GetClockPulses[]; END; SetTimer: PROCEDURE [timer: Timer, pulses: LONG CARDINAL] = INLINE BEGIN timeout[timer] ← pulses; END; GetClock: PROCEDURE RETURNS [LONG CARDINAL] = INLINE -- put current pulses in the timer; pulse arithmetic works in wraparound case BEGIN RETURN[ProcessorFace.GetClockPulses[]]; END; -- ********Turn-around******** WaitToSend: PUBLIC PROCEDURE = -- wait to be in send mode and for CTS BEGIN -- locals AwaitLineTurnAround: ENTRY PROCEDURE = BEGIN UNTIL ourTurnToSend DO WAIT turnAroundArrived; ENDLOOP; driverSending ← TRUE; END; AwaitLineTurnAround[]; AwaitCTS[]; -- set RTS if necessary, wait for CTS END; SendCompleted: PUBLIC ENTRY PROCEDURE [moreToSend: BOOLEAN] = -- tells us we can send LTA now BEGIN ENABLE UNWIND => NULL; driverSending ← FALSE; NOTIFY sendFinished; SetTimer[noMoreToSend, sentSomethingNMTSPulses]; -- reduce timeout StartTimer[noMoreToSend]; senderHasMore ← moreToSend; END; CheckForTurnAround: PUBLIC PROCEDURE [buffer: BufferDefs.Buffer] RETURNS [throwAway: BOOLEAN] = -- check packet for line turn-around; determine mode if we need to BEGIN OPEN phoneEncap: buffer.encapsulation; -- locals remoteProcessorID: NSAddress.ProcessorID; IF controlMode = unknown THEN BEGIN remoteProcessorID ← LOOPHOLE[phoneEncap.pnSrcID]; SELECT ourProcessorID.a FROM -- determine mode by comparing processorIDs = remoteProcessorID.a => SELECT ourProcessorID.b FROM = remoteProcessorID.b => controlMode ← (IF ourProcessorID.c > remoteProcessorID.c THEN master ELSE slave); > remoteProcessorID.b => controlMode ← master; ENDCASE => controlMode ← slave; > remoteProcessorID.a => controlMode ← master; ENDCASE => controlMode ← slave; IF controlMode = master THEN SetTimer[masterResponse, initialMasterResponsePulses]; END; IF phoneEncap.pnType IN [turnAroundPhonePacket..turnAroundMTSPhonePacket] THEN BEGIN -- a driver turn-around packet (no piggybacking yet) IF phoneEncap.pnType = turnAroundMTSPhonePacket THEN SetTimer[noMoreToSend, remoteAnxiousNMTSTimeout]; IF CommFlags.doStats THEN StatIncr[@statsRec.turnArndRcvd]; StartTimer[turnAround]; StartTimer[noMoreToSend]; turnAroundState ← sending; NotifyOurTurnToSend[]; -- tell anyone waiting to send real packets senderHasMore ← FALSE; throwAway ← TRUE; END ELSE BEGIN throwAway ← FALSE; StartTimer[masterResponse]; -- time only from last thing heard END; END; NotifyOurTurnToSend: ENTRY PROCEDURE = -- tell those waiting to send real packets BEGIN ourTurnToSend ← TRUE; NOTIFY turnAroundArrived; END; AwaitCTS: PROCEDURE = -- if modem not ready, make it so (set RTS and await CTS) BEGIN -- locals status: RS232C.DeviceStatus; IF ~clearToSendUp THEN BEGIN ENABLE RS232C.ChannelSuspended => GOTO fatalPlace; RS232C.SetParameter[channel, [requestToSend[TRUE]]]; status ← RS232C.GetStatus[channel]; UNTIL status.clearToSend DO status ← RS232C.StatusWait[channel, status]; ENDLOOP; clearToSendUp ← TRUE; END; EXITS fatalPlace => NULL; END; SendLTA: PROCEDURE = -- set flag under monitor and so SendFrame won't send (and not piggyback the turnaround on another packet) BEGIN -- locals complHandle: RS232C.CompletionHandle; rec: RS232C.PhysicalRecord ← [ header: [NIL, 0, 0], body:, trailer: [NIL, 0, 0]]; buffer: DriverTypes.Encapsulation ← [ phonenet[ framing0:, framing1:, framing2:, framing3:, framing4:, framing5:, recognition: 0, pnType: (IF senderHasMore THEN turnAroundMTSPhonePacket ELSE turnAroundPhonePacket), pnSrcID: LOOPHOLE[ourProcessorID]]]; ClearOurTurn[]; AwaitNoSending[]; AwaitCTS[]; -- send it, wait for completion, and lower RTS rec.body.blockPointer ← @buffer + DriverTypes.phoneEncapsulationOffset; -- word boundary rec.body.startIndex ← 0; rec.body.stopIndexPlusOne ← DriverTypes.phoneEncapsulationBytes; BEGIN ENABLE RS232C.ChannelSuspended => GOTO fatalPlace; complHandle ← RS232C.Put[channel, @rec]; [, ] ← RS232C.TransmitNow[channel, complHandle]; RS232C.SetParameter[channel, [requestToSend[FALSE]]]; IF CommFlags.doStats THEN StatIncr[@statsRec.turnArndSent]; END; -- ENABLE clearToSendUp ← FALSE; EXITS fatalPlace => NULL; END; ClearOurTurn: ENTRY PROCEDURE = BEGIN ourTurnToSend ← FALSE; END; AwaitNoSending: ENTRY PROCEDURE = BEGIN UNTIL ~driverSending DO WAIT sendFinished; ENDLOOP; END; -- **************** Pulse conversion **************** Pulses: TYPE = RECORD[LONG CARDINAL]; MilliSecondsToPulses: PROCEDURE [ms: LONG CARDINAL] RETURNS [pulses: Pulses] = BEGIN -- we must be carefull about multiplication overflow since milliSeconds must be -- converted to microSeconds micro: LONG CARDINAL = MIN[LAST[LONG CARDINAL]/1000,ms] * 1000; hundreds: LONG CARDINAL = micro / ProcessorFace.microsecondsPerHundredPulses; pulses ← [MIN[LAST[LONG CARDINAL]/100,hundreds]*100]; END; -- end MilliSecondsToPulses -- **************** Statistics **************** StatIncr: PROCEDURE [counter: POINTER TO CARDINAL] = -- add one to counter BEGIN -- locals counter↑ ← (counter↑ + 1) MOD (LAST[CARDINAL] - 1); END; StatBump: PROCEDURE [counter: POINTER TO CARDINAL, bumpAmount: CARDINAL] = -- add bumpAmount to counter; if bumpAmount < 10000, there will never be overflow BEGIN -- locals counter↑ ← (counter↑ + bumpAmount) MOD (LAST[CARDINAL] - 10000); END; -- MAIN PROGRAM -- END. LOG Time: July 11, 1980 3:46 PM By: Garlick Action: Created. Time: August 11, 1980 3:12 PM By: Garlick Action: Added notification of the turnAroundArrived and sendFinished conditions in Destroy. Time: January 23, 1981 3:06 PM By: Garlick Action: Converted all timers to pulses. Pulse arithmetic works correctly with overflow and clock wraparound. Time: February 27, 1981 5:22 PM By: Garlick Action: Fixed a couple of places that didn't get all timers converted to pulses.