HalfDuplexImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
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.