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 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}; StatsRecord: TYPE = RECORD [ turnArndSent, turnArndRcvd, masterTOs, noSendTOs, turnTOs: CARDINAL]; 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; timerWakeup: CARDINAL = 250; bps1200TurnAroundTimeout: CARDINAL = 13000; -- msecs bps2400TurnAroundTimeout: CARDINAL = bps1200TurnAroundTimeout/2; -- msecs bps4800TurnAroundTimeout: CARDINAL = bps1200TurnAroundTimeout/4; -- msecs bps9600TurnAroundTimeout: CARDINAL = bps1200TurnAroundTimeout/8; -- msecs 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 Initialize: PUBLIC PROCEDURE [ chHandle: RS232C.ChannelHandle, lineSpeed: RS232C.LineSpeed, ourHostNumber: NSAddress.ProcessorID] = BEGIN timeInMilliSecs: CARDINAL; channel _ chHandle; -- save channel handle 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 = BEGIN NotifyConditions: ENTRY PROCEDURE = BEGIN stopTimer _ TRUE; ourTurnToSend _ TRUE; driverSending _ FALSE; NOTIFY timeoutTimer; NOTIFY turnAroundArrived; NOTIFY sendFinished; END; NotifyConditions[]; JOIN timerProcess; END; TimerProcess: PROCEDURE = 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 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]; 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 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 BEGIN RETURN[ProcessorFace.GetClockPulses[]]; END; WaitToSend: PUBLIC PROCEDURE = BEGIN 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] = 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] = BEGIN OPEN phoneEncap: buffer.encapsulation; 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 = BEGIN ourTurnToSend _ TRUE; NOTIFY turnAroundArrived; END; AwaitCTS: PROCEDURE = BEGIN 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 = BEGIN 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[]; rec.body.blockPointer _ @buffer + DriverTypes.phoneEncapsulationOffset; 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; Pulses: TYPE = RECORD[LONG CARDINAL]; MilliSecondsToPulses: PROCEDURE [ms: LONG CARDINAL] RETURNS [pulses: Pulses] = BEGIN 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 StatIncr: PROCEDURE [counter: POINTER TO CARDINAL] = BEGIN counter^ _ (counter^ + 1) MOD (LAST[CARDINAL] - 1); END; StatBump: PROCEDURE [counter: POINTER TO CARDINAL, bumpAmount: CARDINAL] = BEGIN counter^ _ (counter^ + bumpAmount) MOD (LAST[CARDINAL] - 10000); END; 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. ÎHalfDuplexImpl.mesa Copyright c 1985 by Xerox Corporation. All rights reserved. Garlick on: February 27, 1981 5:22 PM types master sends turn-around if it receives nothing in this time writeable data constants 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. noMoreToSend (NMTS) Timeouts errors and signals ********Init/termination******** initialize timer BOOLEANs and start timer process local the following is to optimize for OISCP windowing, which currently batches 3 packets before requesting an ACK terminate timer process local ********timers******** loop checking times, depending on the turnAroundState gives random number in range [1000..3048) longer if waiting to send first packet than if we sent one already put current time in the timer put current pulses in the timer; pulse arithmetic works in wraparound case ********Turn-around******** wait to be in send mode and for CTS locals tells us we can send LTA now check packet for line turn-around; determine mode if we need to locals tell those waiting to send real packets if modem not ready, make it so (set RTS and await CTS) locals set flag under monitor and so SendFrame won't send (and not piggyback the turnaround on another packet) locals send it, wait for completion, and lower RTS word boundary **************** Pulse conversion **************** we must be carefull about multiplication overflow since milliSeconds must be converted to microSeconds **************** Statistics **************** add one to counter locals add bumpAmount to counter; if bumpAmount < 10000, there will never be overflow locals MAIN PROGRAM -- Ê «˜codešœ™Kšœ Ïmœ1™K˜—Kšœ<™<šœ žœžœ˜Kšœ;žœ˜E—Kšœ™Kšœžœ˜K˜!K˜Kš œžœžœžœžœŸ ˜2š œ žœžœžœžœ˜)K˜1K˜-—Kšœ žœ˜Kšœž œ˜Kšœž œ˜Kšœžœ˜Kšœžœ˜Kšœžœ˜Kšœž œ˜K˜Kšœžœ˜K˜&K˜Kšœ ™ Kšœ žœ˜Kšœº™ºKšœžœ Ÿ˜5Kšœžœ Ÿ˜JKšœžœ Ÿ˜JKšœžœ Ÿ˜JKšœ™šœžœžœ˜K˜—šœžœžœ˜>K˜—šœžœžœ˜BK˜—Kšœžœ Ÿ˜1Kšœžœ Ÿ˜3Kšœžœ Ÿ˜3Kšœžœ Ÿ˜8Kšœ™Kšœ ™ šÏn œžœž œ˜K˜—Kšœ2™2K˜Kš œžœžœžœžœ˜%K˜š œž œžœžœ˜3Kšžœ˜Kšž˜KšœL™LKšœ™Kš œžœžœžœžœžœžœ˜?Kšœ žœžœ7˜NKš œ žœžœžœžœ˜5KšžœŸ˜ K˜—Kšœ,™,K˜š  œž œ žœžœžœ˜4Kšœ™Kšž˜Kšœ™Kšœžœžœžœ˜3Kšžœ˜K˜—š  œž œ žœžœžœžœ˜JKšœN™NKšž˜Kšœ™Kšœ#žœžœžœ ˜@Kšžœ˜—Kšœ™K˜Kšžœ˜—Kšž˜Kšœžœ˜9Kšœžœh˜†Kšœžœz˜™Kšœžœ_˜—…—&†9ÿ