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.