-- PhoneNetworkDriver.mesa (last edited by: BLyon on: March 13, 1981 1:03 PM DIRECTORY BufferDefs USING [ Buffer, BufferAccessHandle, FreeBufferPool, MakeBufferPool, OisBuffer, PupBuffer, BufferType], CommFlags USING [doStats], CommUtilDefs USING [GetEthernetHostNumber], Dialup USING [Dial, RetryCount], DriverDefs USING [ NetworkObject, Network, PutOnGlobalDoneQueue, DriverXmitStatus, AddDeviceToChain, GetInputBuffer, PutOnGlobalInputQueue, ReturnFreeBuffer, RemoveDeviceFromChain], DriverTypes USING [Byte, phoneEncapsulationOffset, phoneEncapsulationBytes], Environment USING [Block], HalfDuplex USING [Initialize, Destroy, WaitToSend, SendCompleted, CheckForTurnAround], OISCP USING [unknownNetID], OISCPConstants USING [phoneNetID], OISTransporter USING [], PhoneNetwork USING [FindPhonePath, UnknownPath], Process USING [SetPriority, SetTimeout, MsecToTicks], PupTypes USING [PupHostID, PupErrorCode], RS232C USING [ ChannelHandle, CompletionHandle, PhysicalRecord, PhysicalRecordHandle, TransferStatus, DeviceStatus, LineSpeed, Get, Put, TransferWait, TransmitNow, GetStatus, StatusWait, SetParameter, Suspend, Restart, ChannelSuspended], RS232CManager USING [NetAccess, CommParamObject, CommParamHandle, CommDuplex], SpecialSystem USING [ GetProcessorID, HostNumber, NetworkNumber, ProcessorID, nullProcessorID, broadcastHostNumber]; PhoneNetworkDriver: MONITOR IMPORTS BufferDefs, CommUtilDefs, Dialup, DriverDefs, HalfDuplex, PhoneNetwork, Process, RS232C, SpecialSystem EXPORTS BufferDefs, OISTransporter SHARES BufferDefs = BEGIN -- EXPORTed TYPEs Network: PUBLIC TYPE = DriverDefs.Network; -- various definitions NetworkState: TYPE = {available, unavailable}; LineState: TYPE = {closed, active}; ConnectionStatus: TYPE = {successful, modemDown, channelPreempted}; CreatePhonePathOutcome: TYPE = { success, busyOrNoAnswer, noDialer, noTranslation, dialerError}; CheckPathOutcome: TYPE = { success, noTranslation, busyOrNoAnswer, noDialer, dialerError, circuitInUse}; StatsRecord: TYPE = RECORD [ pktsSent, pktsReceived, pktsRejected, notTheCurrentPath, noPathSendNoNet, busyOrNoAnswer, noTranslationForAddress, sendErrorBadStatus, noPathSendLineDown, rcvErrorDataLost, rcvErrorChecksum, rcvErrorNoGet, rcvErrorUnknown, rcvErrorFrameTimeout, rcvDeviceError, bytesSent, bytesReceived, dialError, dsrDropped: CARDINAL]; -- state things lineState: LineState; -- becomes active when DSR comes up networkState: NetworkState; -- becomes available after we tell router about ourselves; unavailable when channel deleted (includes preempted) sendRecProcessesActive: BOOLEAN; -- Pup support pupAddrKnown: BOOLEAN; -- FALSE means they cannot be JOINed during cleanup dialing: BOOLEAN; -- Receiver process waits for this awaitingLineUpAfterDial: BOOLEAN; -- Receiver process waits for this dialComplete: CONDITION; -- current channel usage channelHandle: RS232C.ChannelHandle; currentPathSystemElement: SpecialSystem.HostNumber; mySystemElement: SpecialSystem.ProcessorID; duplexity: RS232CManager.CommDuplex; modemSpeed: RS232C.LineSpeed; lineMode: RS232CManager.NetAccess; autoDial: BOOLEAN; dialRetries: Dialup.RetryCount; -- output queue headOutputBuffer, tailOutputBuffer: BufferDefs.Buffer; packetToSend: CONDITION; -- phone network object for Router phoneNetObject: DriverDefs.NetworkObject ← [next: NIL, decapsulateBuffer: TypeOfBuffer, encapsulatePup: PupFrameIt, encapsulateOis: OISFrameIt, sendBuffer: EnqueueSend, forwardBuffer: ForwardFrame, -- rejects activateDriver: ActivateTransporter, deactivateDriver: KillTransporter, deleteDriver: KillTransporter, interrupt: InterruptNop, -- this gets locked! (a crok) changeNumberOfInputBuffers: NIL, index:, device: phonenet, alive: TRUE, speed: 1, buffers: 0, spare:, netNumber:, hostNumber:, pupStats: StatsNop, stats: NIL]; phoneNetwork: Network ← @phoneNetObject; -- process handles senderProcess: PROCESS; receiverProcess: PROCESS; statusWaitProcess: PROCESS; -- personal buffer phoneBufferAccessHandle: BufferDefs.BufferAccessHandle; -- stats statsRec: StatsRecord; -- constants noError: DriverTypes.Byte = 0B; noPathError: DriverTypes.Byte = 100B; noTranslationError: DriverTypes.Byte = 101B; circuitInUseError: DriverTypes.Byte = 102B; noDialerError: DriverTypes.Byte = 103B; dialerHardwareError: DriverTypes.Byte = 104B; noAnswerOrBusyError: DriverTypes.Byte = 104B; dialNetNumber: SpecialSystem.NetworkNumber = OISCPConstants.phoneNetID; outstandingGets: CARDINAL = 2; -- determines the amount of Channel receives for multiple buffering -- signals and errors -- ************ Initialization/Termination (from RS232CManager) ************ Initialize: PUBLIC PROCEDURE [ chHandle: RS232C.ChannelHandle, commParams: RS232CManager.CommParamObject] = -- start the Transporter with the given params BEGIN -- locals -- initialize globals lineState ← closed; -- becomes active when DSR comes up networkState ← unavailable; -- becomes available after we tell router about ourselves sendRecProcessesActive ← FALSE; awaitingLineUpAfterDial ← FALSE; pupAddrKnown ← FALSE; channelHandle ← chHandle; duplexity ← commParams.duplex; modemSpeed ← commParams.lineSpeed; headOutputBuffer ← tailOutputBuffer ← NIL; -- stats IF CommFlags.doStats THEN statsRec ← [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; -- get our own host number, initialize remote ID mySystemElement ← SpecialSystem.GetProcessorID[]; phoneNetwork.hostNumber ← 0; currentPathSystemElement ← SpecialSystem.nullProcessorID; -- decode Communication parameters WITH commParams SELECT FROM directConn => BEGIN phoneNetwork.netNumber ← OISCP.unknownNetID; lineMode ← directConn; END; dialConn => BEGIN phoneNetwork.netNumber ← dialNetNumber; autoDial ← (dialMode = auto); dialRetries ← retryCount; lineMode ← dialConn; END; ENDCASE; -- set RS-232-C parameters that matter (note: bitSynchronous has mostly defaults) RS232C.SetParameter[channelHandle, [dataTerminalReady[TRUE]]]; -- tell modem we are ready to boogie networkState ← available; -- allow sends from Router -- start status change watch process (which will start the receiver process and any half-duplex) statusWaitProcess ← FORK StatusChangeWait[]; -- tell Router about ourselves DriverDefs.AddDeviceToChain[phoneNetwork, 0]; END; Destroy: PUBLIC PROCEDURE [chHandle: RS232C.ChannelHandle] = -- stop everything so we don't make any more channel calls BEGIN -- locals pupAddrKnown ← FALSE; RS232C.Suspend[channelHandle, all]; JOIN statusWaitProcess; -- sender and receiver JOINED by status wait guy END; -- **************** Encapsulation **************** FrameAny: PROCEDURE [buffer: BufferDefs.Buffer] = -- fill buffer with level 0 header stuff BEGIN -- locals -- encapsulation offset with no HDLC framing should be 3, with HDLC framing should be 1 buffer.encapsulation ← [phonenet[ framing0: noError, framing1: 0, framing2: 0, framing3: 0, framing4: 0, framing5: 0, recognition: 0, -- for auto-recog pnType: oisPhonePacket, pnSrcID: mySystemElement]]; END; OISFrameIt: PROCEDURE [ buffer: BufferDefs.OisBuffer, systemElem: SpecialSystem.HostNumber] = -- fill buffer with OIS level 0 header stuff BEGIN -- locals errorByte: DriverTypes.Byte; -- check if we are talking to the system element (may cause dialing) errorByte ← SELECT CheckPath[systemElem] FROM success => noError, busyOrNoAnswer => noAnswerOrBusyError, noTranslation => noTranslationError, dialerError => dialerHardwareError, noDialer => noDialerError, circuitInUse => circuitInUseError, ENDCASE => noPathError; IF errorByte # noError THEN BEGIN -- set error in encapsulation field buffer.encapsulation.framing0 ← errorByte; IF CommFlags.doStats THEN SELECT errorByte FROM = noAnswerOrBusyError => StatIncr[@statsRec.busyOrNoAnswer]; = noTranslationError => StatIncr[@statsRec.noTranslationForAddress]; = circuitInUseError => StatIncr[@statsRec.notTheCurrentPath]; ENDCASE; RETURN; END; FrameAny[buffer]; buffer.encapsulation.pnType ← oisPhonePacket; buffer.length ← (buffer.ois.pktLength + 1 + DriverTypes.phoneEncapsulationBytes)/2; -- note that length is in words. Since software checksum is on words, it expects the driver to pad. END; PupFrameIt: PROCEDURE [ buffer: BufferDefs.PupBuffer, systemElem: PupTypes.PupHostID] = -- fill buffer with Pup level 0 header stuff BEGIN -- locals IF ~pupAddrKnown THEN BEGIN phoneNetwork.hostNumber ← CommUtilDefs.GetEthernetHostNumber[]; pupAddrKnown ← TRUE; END; FrameAny[buffer]; buffer.encapsulation.pnType ← pupPhonePacket; buffer.length ← (buffer.pupLength + 1 + DriverTypes.phoneEncapsulationBytes)/2; -- note that length is in rounded-up words, because higher-level checksum on words END; TypeOfBuffer: PROCEDURE [buffer: BufferDefs.Buffer] RETURNS [type: BufferDefs.BufferType] = -- determine buffer type BEGIN SELECT buffer.encapsulation.pnType FROM oisPhonePacket => type ← ois; pupPhonePacket => type ← pup; ENDCASE => BEGIN type ← rejected; IF CommFlags.doStats THEN StatIncr[@statsRec.pktsRejected]; END; END; -- **************** Packet Transport / Sending **************** EnqueueSend: ENTRY PROCEDURE [buffer: BufferDefs.Buffer] = -- queue the buffer for output BEGIN ENABLE UNWIND => NULL; -- locals errorByte: DriverTypes.Byte ← buffer.encapsulation.framing0; xmitStatus: DriverDefs.DriverXmitStatus; -- check for error states (some set in encapsulation routine) IF (lineState = closed) OR (networkState = unavailable) OR (errorByte # noError) THEN BEGIN IF networkState = unavailable THEN xmitStatus ← noRouteToNetwork ELSE IF ~awaitingLineUpAfterDial THEN BEGIN xmitStatus ← SELECT errorByte FROM = noPathError => noRouteToNetwork, = noAnswerOrBusyError => noAnswerOrBusy, = noTranslationError => noTranslationForDestination, = dialerHardwareError => dialerHardwareProblem, = noDialerError => noDialingHardware, = circuitInUseError => circuitInUse, ENDCASE => IF lineState = closed THEN circuitNotReady ELSE noRouteToNetwork; END ELSE xmitStatus ← goodCompletion; -- drop on the floor waiting for DSR buffer.status ← LOOPHOLE[xmitStatus]; DriverDefs.PutOnGlobalDoneQueue[buffer]; IF CommFlags.doStats THEN BEGIN IF lineState = closed THEN StatIncr[@statsRec.noPathSendLineDown]; IF networkState = unavailable THEN StatIncr[@statsRec.noPathSendNoNet]; END; RETURN; END; IF headOutputBuffer = NIL THEN headOutputBuffer ← tailOutputBuffer ← buffer ELSE BEGIN tailOutputBuffer.next ← buffer; tailOutputBuffer ← buffer; END; NOTIFY packetToSend; END; Sender: PROCEDURE = -- process that waits for things to send BEGIN -- locals b: BufferDefs.Buffer; AwaitPacketToSend: ENTRY PROCEDURE = -- wait for a non-empty queue BEGIN ENABLE UNWIND => NULL; UNTIL (headOutputBuffer # NIL) OR ~sendRecProcessesActive DO WAIT packetToSend; ENDLOOP; END; UNTIL ~sendRecProcessesActive DO AwaitPacketToSend[]; SendFrame[DequeueSend[]]; ENDLOOP; -- clear any remaining things to send and go away UNTIL (b ← DequeueSend[]) = NIL DO DriverDefs.PutOnGlobalDoneQueue[b]; ENDLOOP; END; NotifySenderToGoAway: ENTRY PROCEDURE = -- make Sender wake up BEGIN NOTIFY packetToSend; END; DequeueSend: ENTRY PROCEDURE RETURNS [buffer: BufferDefs.Buffer] = -- dequeue the buffer for output BEGIN -- locals IF headOutputBuffer = NIL THEN buffer ← NIL ELSE BEGIN buffer ← headOutputBuffer; headOutputBuffer ← buffer.next; END; END; SendFrame: PROCEDURE [buffer: BufferDefs.Buffer] = -- build and Put a frame to the channel BEGIN -- locals complHandle: RS232C.CompletionHandle; rec: RS232C.PhysicalRecord ← [header: [NIL, 0, 0], body:, trailer: [NIL, 0, 0]]; IF buffer = NIL THEN RETURN; -- FIll Channel Physical record rec.body.blockPointer ← @buffer.encapsulation + DriverTypes.phoneEncapsulationOffset; -- word boundary rec.body.startIndex ← 0; rec.body.stopIndexPlusOne ← buffer.length*2; -- even bytes -- if half duplex, set RTS and await CTS IF duplexity = half THEN HalfDuplex.WaitToSend[]; -- do the Put complHandle ← RS232C.Put[ channelHandle, @rec ! RS232C.ChannelSuspended => BEGIN -- someone wants us to go away PreemptedSending[buffer]; IF duplexity = half THEN HalfDuplex.SendCompleted[FALSE]; GOTO returnPlace; END]; -- wait for completion SendWait[buffer, complHandle]; IF duplexity = half THEN HalfDuplex.SendCompleted[(headOutputBuffer # NIL)]; -- give a chance for line turn-around and say if we have more to send EXITS returnPlace => NULL; END; SendWait: PROCEDURE [ buffer: BufferDefs.Buffer, complete: RS232C.CompletionHandle] = -- waits for Put to complete BEGIN -- locals bytes: CARDINAL; xferstatus: RS232C.TransferStatus; -- wait for completion [bytes, xferstatus] ← RS232C.TransmitNow[channelHandle, complete]; -- fill status, bytes in buffer buffer.status ← TranslateChannelStatus[xferstatus]; buffer.length ← bytes; -- stats IF CommFlags.doStats THEN IF xferstatus = success THEN BEGIN StatIncr[@statsRec.pktsSent]; StatBump[@statsRec.bytesSent, bytes]; END ELSE StatIncr[@statsRec.sendErrorBadStatus]; -- call completion procedure DriverDefs.PutOnGlobalDoneQueue[buffer]; END; TranslateChannelStatus: PRIVATE PROCEDURE [chStatus: RS232C.TransferStatus] RETURNS [oisStatus: CARDINAL] = -- translate channel status to driver xmit status BEGIN -- need more definitive codes ??????? -- locals frameStatus: DriverDefs.DriverXmitStatus; SELECT chStatus FROM success => frameStatus ← goodCompletion; aborted => frameStatus ← aborted; ENDCASE => frameStatus ← hardwareProblem; oisStatus ← LOOPHOLE[frameStatus, CARDINAL]; END; PreemptedSending: PRIVATE PROCEDURE [buffer: BufferDefs.Buffer] = -- called if channel ripped away due to preemption (by Channel Mgr) BEGIN networkUnavailableStatus: DriverDefs.DriverXmitStatus ← aborted; buffer.status ← LOOPHOLE[networkUnavailableStatus, CARDINAL]; --we may not be a network driver if preemption reported in Receiver or StatusWait, but we can still return the buffer (Yogen says) DriverDefs.PutOnGlobalDoneQueue[buffer]; END; ForwardFrame: PROCEDURE [buffer: BufferDefs.Buffer] RETURNS [PupTypes.PupErrorCode] = -- reject forward attempt BEGIN RETURN[gatewayResourceLimitsPupErrorCode]; END; -- **************** Packet Transport / Receiving **************** Receiver: PROCEDURE = -- process that receives frames and notifies OIS BEGIN DoGet: PRIVATE PROCEDURE [ recHandle: RS232C.PhysicalRecordHandle, buffer: BufferDefs.Buffer] RETURNS [preempted: BOOLEAN, complHandle: RS232C.CompletionHandle] = -- perform the channel Get and watch for preemption -- assumes record header and trailer nil INLINE BEGIN -- locals preempted ← FALSE; -- fill physical record (it is being reused) -- clear encapsulation words ????? recHandle.body ← [blockPointer: @buffer.encapsulation + DriverTypes.phoneEncapsulationOffset, startIndex: 0, stopIndexPlusOne: (buffer.length - DriverTypes.phoneEncapsulationOffset)*2]; -- perform receive -- need more error handling here ???? complHandle ← RS232C.Get[ channelHandle, recHandle ! RS232C.ChannelSuspended => BEGIN DriverDefs.ReturnFreeBuffer[buffer]; -- return unused buffer preempted ← TRUE; CONTINUE; END]; END; -- DoGet -- locals recArray: ARRAY [0..outstandingGets) OF RS232C.PhysicalRecord ← ALL[[[NIL, 0, 0], [NIL, 0, 0], [NIL, 0, 0]]]; bufferArray: ARRAY [0..outstandingGets) OF BufferDefs.Buffer ← ALL[NIL]; complArray: ARRAY [0..outstandingGets) OF RS232C.CompletionHandle; preemptedArray: ARRAY [0..outstandingGets) OF BOOLEAN ← ALL[FALSE]; someoneGotPreempted: BOOLEAN ← FALSE; desperatelyNeedABuffer: BOOLEAN ← FALSE; nilBufCount, i: CARDINAL; Process.SetPriority[3]; -- wait for dialing if necessary WaitDialComplete[]; -- perform some Gets for multiple buffering UNTIL (lineState = closed) OR (networkState = unavailable) OR someoneGotPreempted DO -- get frames, pass to OIS nilBufCount ← 0; FOR i IN [0..outstandingGets) DO -- wait for a completion and give to Router or discard IF bufferArray[i]#NIL THEN AwaitAndDisposeOfFrame[complArray[i], bufferArray[i]] ELSE desperatelyNeedABuffer ← (nilBufCount ← nilBufCount + 1)>=outstandingGets; -- get next buffer IF (bufferArray[i] ← DriverDefs.GetInputBuffer[desperatelyNeedABuffer])#NIL THEN BEGIN desperatelyNeedABuffer ← FALSE; -- if we have one then OK -- gets buffer.length (words) includes encaps. -- perform next receive [preemptedArray[i], complArray[i]] ← DoGet[@recArray[i], bufferArray[i]]; IF (someoneGotPreempted ← preemptedArray[i]) THEN EXIT; END; ENDLOOP; ENDLOOP; -- of UNTIL -- wait for Get completion of those not preempted FOR i IN [0..outstandingGets) DO IF (~preemptedArray[i]) AND (bufferArray[i]#NIL) THEN AwaitAndDisposeOfFrame[complArray[i], bufferArray[i]]; ENDLOOP; END; AwaitAndDisposeOfFrame: PROCEDURE [ complHandle: RS232C.CompletionHandle, buffer: BufferDefs.Buffer] = -- wait for get to complete and either hand to router or throw away BEGIN -- locals transferStatus: RS232C.TransferStatus; globalStatus: RS232C.DeviceStatus; bytes: CARDINAL; statsPtr: POINTER TO CARDINAL; checksum: CARDINAL = 2; -- wait for completion (no errors can occur here) [bytes, transferStatus] ← RS232C.TransferWait[channelHandle, complHandle]; IF transferStatus = success THEN BEGIN -- pass only good frames to Router -- set byte count for consistency check in decapsulation buffer.length ← bytes - checksum; -- add network object to buffer buffer.network ← phoneNetwork; buffer.device ← phonenet; -- check contents of header and trailer currentPathSystemElement ← buffer.encapsulation.pnSrcID; IF (duplexity = half) AND HalfDuplex.CheckForTurnAround[buffer] THEN BEGIN -- throw buffer away DriverDefs.ReturnFreeBuffer[buffer]; RETURN; END; -- notify OIS DriverDefs.PutOnGlobalInputQueue[buffer]; -- stats IF CommFlags.doStats THEN BEGIN StatIncr[@statsRec.pktsReceived]; StatBump[@statsRec.bytesReceived, bytes - checksum]; END; END ELSE BEGIN -- bad frame => free the buffer DriverDefs.ReturnFreeBuffer[buffer]; IF CommFlags.doStats AND (transferStatus # aborted) THEN BEGIN statsPtr ← SELECT transferStatus FROM dataLost => @statsRec.rcvErrorDataLost, checksumError => @statsRec.rcvErrorChecksum, frameTimeout => @statsRec.rcvErrorFrameTimeout, deviceError => @statsRec.rcvDeviceError, ENDCASE => @statsRec.rcvErrorUnknown; StatIncr[statsPtr]; END; IF transferStatus = deviceError THEN BEGIN globalStatus ← RS232C.GetStatus[channelHandle]; IF globalStatus.dataLost THEN -- clear the data lost latch bit BEGIN RS232C.SetParameter[ channelHandle, [latchBitClear[globalStatus]] ! RS232C.ChannelSuspended => CONTINUE]; IF CommFlags.doStats THEN StatIncr[@statsRec.rcvErrorNoGet]; END; END; END; END; -- **************** Driver Management (by Router) **************** ActivateTransporter: PROCEDURE = -- call from Router when we add ourselves as network driver BEGIN -- locals END; KillTransporter: PROCEDURE = -- called by Router when we removed ourselves as a network driver BEGIN -- locals END; StatsNop: PROCEDURE [buffer: BufferDefs.PupBuffer, network: Network] RETURNS [BOOLEAN] = -- NOP BEGIN -- locals RETURN[FALSE]; END; InterruptNop: PROCEDURE = -- NOP BEGIN -- locals END; -- **************** Connection Management **************** CheckPath: PRIVATE PROCEDURE [systemElem: SpecialSystem.HostNumber] RETURNS [outcome: CheckPathOutcome] = -- create new path if necessary & auto-dial mode BEGIN -- locals IF lineMode = directConn OR ~autoDial THEN RETURN[success]; IF lineState = closed THEN BEGIN -- must create auto-dialed path SELECT CreatePhonePath[systemElem] FROM success => BEGIN currentPathSystemElement ← systemElem; RETURN[success]; END; noTranslation => RETURN[noTranslation]; busyOrNoAnswer => RETURN[busyOrNoAnswer]; dialerError => RETURN[dialerError]; noDialer => RETURN[noDialer]; ENDCASE => RETURN[dialerError]; END ELSE BEGIN -- check if same as previous or probing (uses broadcast processor ID) IF systemElem = currentPathSystemElement OR systemElem = SpecialSystem.broadcastHostNumber --probing-- THEN RETURN[success] ELSE RETURN[circuitInUse]; END; END; CreatePhonePath: PROCEDURE [systemElem: SpecialSystem.HostNumber] RETURNS [outcome: CreatePhonePathOutcome] = -- dial thru the auto-dial hardware BEGIN -- locals commParamHandle: RS232CManager.CommParamHandle; phoneNumber: STRING; dialerZero: CARDINAL = 0; -- get phone number, etc. [commParamHandle, phoneNumber] ← PhoneNetwork.FindPhonePath[ systemElem ! PhoneNetwork.UnknownPath => GOTO noTranslation]; -- set params that may be different (ignore for now, use global ones) -- dial it dialing ← TRUE; outcome ← SELECT Dialup.Dial[dialerZero, phoneNumber, dialRetries] FROM success => success, failure => busyOrNoAnswer, dialerNotPresent => noDialer, ENDCASE => dialerError; IF ~(outcome = success) THEN IF CommFlags.doStats THEN StatIncr[@statsRec.dialError]; NotifyDialComplete[(outcome = success)]; RETURN[outcome]; EXITS noTranslation => RETURN[noTranslation]; END; NotifyDialComplete: ENTRY PROCEDURE [successfulDial: BOOLEAN] = -- tell Receiver that it can receive now (in case it was created because DSR came up) BEGIN dialing ← FALSE; awaitingLineUpAfterDial ← successfulDial; NOTIFY dialComplete; END; WaitDialComplete: ENTRY PROCEDURE = -- wait for dialing and resetting of line params (if not RS366) BEGIN ENABLE UNWIND => NULL; UNTIL ~dialing DO WAIT dialComplete; ENDLOOP; END; StatusChangeWait: PRIVATE PROCEDURE = -- a process to wait for an abnormal status change -- this process must terminate if channel goes away BEGIN --locals newStatus: RS232C.DeviceStatus; BEGIN DO -- until channel is preempted -- wait for complete connection (may be complete already) newStatus ← RS232C.GetStatus[ channelHandle ! RS232C.ChannelSuspended => GOTO preempted]; UNTIL newStatus.dataSetReady DO newStatus ← RS232C.StatusWait[ channelHandle, newStatus ! RS232C.ChannelSuspended => GOTO preempted]; ENDLOOP; lineState ← active; CreateSendReceiveProcesses[]; -- await line drop or preemption UNTIL ProcessStatusChange[newStatus] = modemDown DO newStatus ← RS232C.StatusWait[ channelHandle, newStatus ! RS232C.ChannelSuspended => GOTO preempted]; ENDLOOP; IF CommFlags.doStats THEN StatIncr[@statsRec.dsrDropped]; -- abort the channel to reach consistent state (make Receiver go away) lineState ← closed; RS232C.Suspend[channelHandle, input]; RS232C.Suspend[channelHandle, output]; RemoveSendReceiveProcesses[]; RS232C.Restart[channelHandle, input]; RS232C.Restart[channelHandle, output]; currentPathSystemElement ← SpecialSystem.nullProcessorID; -- current guy is nobody ENDLOOP; EXITS preempted => BEGIN PreemptCleanup[]; RemoveSendReceiveProcesses[]; END; END; END; ProcessStatusChange: PRIVATE ENTRY PROCEDURE [status: RS232C.DeviceStatus] RETURNS [ConnectionStatus] = -- check line status; determine if transient BEGIN ENABLE UNWIND => NULL; --locals timeOut: CONDITION; dsrRecovTimeInSecs: CARDINAL = 2; -- secs to wait for DataSetReady recovery loopWait: CARDINAL ← 500; -- msecs between checking for DataSetReady loopCount: CARDINAL ← dsrRecovTimeInSecs*1000/loopWait; ourWaits: CARDINAL ← 0; newStatus: RS232C.DeviceStatus; -- check for lost line IF ~status.dataSetReady THEN BEGIN -- wait a reasonable time for it to come back without hogging UNTIL ourWaits > loopCount DO Process.SetTimeout[@timeOut, Process.MsecToTicks[loopWait]]; WAIT timeOut; newStatus ← RS232C.GetStatus[ channelHandle ! RS232C.ChannelSuspended => GOTO fatalPlace]; IF newStatus.dataSetReady THEN RETURN[successful]; ourWaits ← ourWaits + 1; REPEAT fatalPlace => RETURN[channelPreempted]; ENDLOOP; RETURN[modemDown]; END ELSE RETURN[successful]; END; CreateSendReceiveProcesses: PROCEDURE = -- start the receiver BEGIN phoneBufferAccessHandle ← BufferDefs.MakeBufferPool[outstandingGets+1]; awaitingLineUpAfterDial ← FALSE; sendRecProcessesActive ← TRUE; receiverProcess ← FORK Receiver; senderProcess ← FORK Sender; IF duplexity = half THEN HalfDuplex.Initialize[channelHandle, modemSpeed, mySystemElement]; END; RemoveSendReceiveProcesses: PROCEDURE = -- bring back the receiver BEGIN IF sendRecProcessesActive THEN BEGIN sendRecProcessesActive ← FALSE; NotifySenderToGoAway[]; IF duplexity = half THEN HalfDuplex.Destroy[]; JOIN receiverProcess; JOIN senderProcess; BufferDefs.FreeBufferPool[phoneBufferAccessHandle]; END; END; PreemptCleanup: ENTRY PROCEDURE = -- called if channel ripped away due to preemption (by Channel Mgr) BEGIN ENABLE UNWIND => NULL; IF networkState = available THEN BEGIN -- inform our cohorts networkState ← unavailable; -- inform other processes DriverDefs.RemoveDeviceFromChain[phoneNetwork]; END; END; -- **************** 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. Issues to address in future: 1. Don't hog the output buffers so other media drivers (especially Ethernets) get their share. Either drop packets on the floor or send back a flow control status to the router. 2. Keep the queue length for half duplex and for #1. Can use the MoreToSend packet-type to tell the other end to hurry with the line turn-around. LOG Time: August 2, 1978 10:04 AM By: Garlick Action: Created file Time: January 22, 1980 1:29 PM By: Garlick Action: fixed CreatePhonePath to call RS232C.SetLineType after dialing. Time: January 25, 1980 5:13 PM By: Garlick Action: fixed initialization of currentPathSystemElement in InitOisTransporter and StatusChangeWait. Time: January 29, 1980 9:48 AM By: Garlick Action: made Receiver wait for dialing and all associated line resetting before performing Gets, since SetLineType does an implicit abort of the channel. Time: January 29, 1980 11:49 AM By: Garlick Action: added padding of OISCP packet so that we always send integral words. This is a compatibility service (hack) provided by all drivers. Time: January 30, 1980 6:27 PM By: Garlick Action: made changes for compatibility with new SpecialSystem, new RS232C and RS366, and new PhoneNetwork. Time: March 17, 1980 9:28 AM By: Garlick Action: in CheckPath, allows a broadcastProcessorID at any time. Allows flexibility in our probe protocol. Time: June 11, 1980 2:49 PM By: McJones Action: broadcastProcessorID=>broadcastHostNumber. Time: July 2, 1980 12:01 PM By: Garlick Action: changed name of module to PhoneNetworkDriver. Time: July 7, 1980 1:24 AM By: Garlick Action: made termination compatible with new RS232C changes. Time: July 18, 1980 2:17 PM By: Garlick Action: added half-duplex support and a queued sending interface. Time: August 5, 1980 6:39 PM By: Garlick Action: 1.) changed EnqueueSend to not report noRouteToNetwork when awaiting line up after a dial. 2.) made compatible with new PhoneNetwork (that uses HostNumbers). 3.) added a Destroy and renamed InitOisTransporter to be Initialize. Now we JOIN the StatusWait process. Time: August 6, 1980 8:57 AM By: Garlick Action: No longer get a net number for a leased line; rather use OISCPTypes.unknownNetID. Use OISCPConstants.phoneNetID for the phone network number instead of 40B. Time: August 11, 1980 3:18 PM By: Garlick Action: Changed order of JOINs and deleting HalfDuplex guy. Time: September 15, 1980 9:26 AM By: Garlick Action: Implemented return of many new error codes to be returned when sending. Time: September 18, 1980 5:17 PM By: BLyon Action: added buffers & spare to phoneNetObject. Time: September 19, 1980 5:22 PM By: Garlick Action: fixed bugs in error codes. CircuitNotReady overriding other errors. Time: October 12, 1980 9:18 PM By: Garlick Action: added implementtation of DriverDefs XmitStatus[noAnswerOrBusy]. Time: January 23, 1981 4:30 PM By: Garlick Action: added several UNWINDs to release monitors if errors arise. Time: January 23, 1981 4:30 PM By: BLyon Action: re-wrote Receive and DoGet to take care of getting a NIL buffer.