-- File: SptpDriver.mesa - last edit:
-- AOF 3-Feb-88 19:37:37
-- Copyright (C) 1987, 1988 by Xerox Corporation. All rights reserved.
--See Notes and Comments at end of module
DIRECTORY
Buffer USING [
AccessHandle, Buffer, Byte, DataBytesPerRawBuffer, dataLinkReserve,
Dequeue, DestroyPool, Device, DriverInformation, Enqueue, MakePool,
Queue, QueueCleanup, QueueInitialize, QueueObject, TransferStatus],
ByteBlt USING [ByteBlt],
CommFlags USING [doDebug, doErrors, doStats, driverStats],
CommPriorities USING [driver],
CommUtil USING [AllocateIocbs, FreeIocbs],
Driver USING [
AddDeviceToChain, Device, DeviceObject, GetDeviceChain, GetInputBuffer,
Glitch, PutOnGlobalDoneQueue, PutOnGlobalInputQueue, RemoveDeviceFromChain,
ReturnFreeBuffer],
Environment USING [Block, Byte, bytesPerWord],
HostNumbers USING [HostNumber, IsMulticastID],
Mopcodes USING [op, zAND],
NewRS232CFace USING [
Command, CommandStatus, DeviceStatus, GetDeviceStatus, GetHandle,
Handle, Initialize, InitializeCleanup, InitiateCommand, InitiateReceive,
InitiateSetParameters, InitiateTransmit, Operation, OperationPtr,
operationSize, ParameterRecord, ParameterStatus, PollCommand,
PollReceiveOrTransmit, PollSetParameters, ReleaseHandle, ResetRecord,
InitiateResetStatusBits, InitiateSetControlBits, TransferStatus,
PollSetControlBits, ControlRecord],
Process USING [
Abort, DisableTimeout, EnableAborts, GetPriority,
MsecToTicks, Pause, Priority, SetPriority, SetTimeout],
ProcessorFace USING [SetMP],
RS232CCorrespondents USING [nsSystemElement],
Runtime USING [GlobalFrame, SelfDestruct],
SpecialRuntime USING [AllocateNakedCondition, DeallocateNakedCondition],
SpecialSystem USING [GetProcessorID, HostNumber],
SppOps USING [SetWindow, sppWindowSize],
SptpOps USING [
defaultMaxRS232CBytes, defaultMinRS232CBytes, point5Duplex, Reservation,
ReservationObject, siuSupport, StatsRecord, TraceProc],
SptpProtocol USING [
ActiveNegotiation, AwaitingOptionAck, AwaitingOptions,
AwaitTerminateReply, DriverInformation, Encapsulation, EntityClass,
EncapsulationFromBlock, EncapsulationObject, PassiveNegotiation,
ProcessControl, ProtocolInfo, ProtocolRecord, ProtocolVersion,
SendAreYouThere, SendNull, SendTerminateRequest, TerminationDally,
WaitForControl],
SptpStats USING [Bump, Incr, StatCounterIndex],
Stats USING [StatCounterIndex, StatIncr],
System USING [
GetClockPulses, GreenwichMeanTime, HostNumber,
MicrosecondsToPulses, nullHostNumber, PulsesToMicroseconds];
SptpDriver: MONITOR
IMPORTS
Buffer, ByteBlt, CommUtil, Driver, NewRS232CFace, HostNumbers,
SptpProtocol, SptpStats, SppOps, Stats, Process, ProcessorFace, Runtime,
SpecialRuntime, SpecialSystem, System
EXPORTS Buffer, SptpOps, System =
BEGIN
--EXPORTed TYPEs and variables
Device: PUBLIC <<Buffer>> TYPE = Driver.Device;
HostNumber: PUBLIC <<System>> TYPE = SpecialSystem.HostNumber;
probeCount: NATURAL = 8; --just an arbitrary counter value;
window: NATURAL ← 1; --actually (window + 2) or 1 == 3 (small 3, large 1)
measurableLength: NATURAL ← 64; --minimum byte length for computation
lineSpeed: PUBLIC CARDINAL; --to hold computed line speeds
maxRS232CBytes: PUBLIC <<SptpOps>> CARDINAL ← SptpOps.defaultMaxRS232CBytes;
minRS232CBytes: PUBLIC <<SptpOps>> CARDINAL ← SptpOps.defaultMinRS232CBytes;
bpw: NATURAL = Environment.bytesPerWord;
setupDriver: PROC[SptpOps.Reservation];
instanceBusy: BOOLEAN ← FALSE; --so we can tell if zeroth instance is in use
instanceCopied: BOOLEAN ← FALSE; --so we know to delete him
refCount: NATURAL ← 0; --so we can tolerate multiple creates
<<+++++++>>
reason: ReasonForEnteringTerminate2State;
ReasonForEnteringTerminate2State: TYPE = {
voluntary, couldntClearLatches, noCTS, dsrDropped, cdDropped};
<<+++++++>>
rs232c: RECORD[
process: PROCESS,
lta, pleaseStop: BOOLEAN,
probe, transmits: NATURAL,
cmdDone, bitClock: CONDITION,
traceProc: SptpOps.TraceProc,
lastStatusChange: LONG CARDINAL,
interrupt: LONG POINTER TO CONDITION,
handle: NewRS232CFace.Handle, mask: WORD,
reservation: SptpOps.ReservationObject ← TRASH,
newStatus: NewRS232CFace.DeviceStatus ← latchBits,
oldStatus: NewRS232CFace.DeviceStatus ← allOffStatus,
getGarbage, cmdInProgress, lineStatusChange: BOOLEAN ← FALSE];
input: RECORD[
q, r: Buffer.QueueObject,
nextFragment: NATURAL ← 0,
access: Buffer.AccessHandle,
timeLastRecv: LONG CARDINAL,
queueAllowed: NATURAL,
lastStatus: NewRS232CFace.TransferStatus];
inputQueueLength: CARDINAL = 4;
output: RECORD[
notify: CONDITION,
q, w: Buffer.QueueObject,
timeXmtDone: LONG CARDINAL,
lastStatus: NewRS232CFace.TransferStatus];
protocol: SptpProtocol.ProtocolRecord;
clock: RECORD[
giveup: CARDINAL, --seconds
shortTmo, onHook, clockCksm: CARDINAL, --ticks
idleInput, stuckOutput, mstrTmo, ltaHold, cmdTmo, cdDelay, ctsDelay:
LONG CARDINAL];
--IOCBs
iocbState: RECORD[first, free: FreeIocb, avail: INTEGER];
FreeIocb: TYPE = LONG POINTER TO free IocbObject;
InuseIocb: TYPE = LONG POINTER TO inuse IocbObject;
IocbObject: TYPE = RECORD[
field: SELECT COMPUTED * FROM
free => [next: FreeIocb, rest: SEQUENCE COMPUTED CARDINAL OF WORD],
inuse => [op: NewRS232CFace.Operation], --do we need the rest?
ENDCASE];
--THE NETWORK OBJECT FOR THIS DRIVER
myDevice: Driver.DeviceObject ← [
matrix: NIL, sendRawBuffer: SendRawBuffer, activateDriver: ActivateDriver,
deactivateDriver: DeactivateDriver, deleteDriver: DeleteDriver,
changeNumberOfInputBuffers: Null, buffers:, device: phonenet,
getThroughput: GetThroughput, lineSpeed: 0, lineNumber: 0,
stats: NIL, receiveBufferLen: maxRS232CBytes, alive: FALSE,
index:, next: NIL];
sptpStats: SptpOps.StatsRecord; --where to keep interesting info
--This module doesn't support .5 duplex even if the interface says it does
point5Duplex: BOOLEAN = ~SptpOps.point5Duplex; --level of support
BUG: ERROR = CODE;
IOCBSizeIsZero: ERROR = CODE;
OverlayingIocb: ERROR = CODE;
PacketTooLarge: ERROR = CODE;
DriverNotActive: ERROR = CODE;
DriverAlreadyActive: ERROR = CODE;
allOffStatus: NewRS232CFace.DeviceStatus = [
breakDetected: FALSE, dataLost: FALSE, ringHeard: FALSE,
carrierDetect: FALSE, clearToSend: FALSE, dataSetReady: FALSE,
ringIndicator: FALSE, unused: 0];
latchBits: NewRS232CFace.DeviceStatus ← [
breakDetected: TRUE, dataLost: TRUE, ringHeard: TRUE,
carrierDetect: FALSE, clearToSend: FALSE, dataSetReady: FALSE,
ringIndicator: FALSE, unused: 0];
dceLatches: NewRS232CFace.DeviceStatus ← [
breakDetected: FALSE, dataLost: TRUE, ringHeard: FALSE,
carrierDetect: FALSE, clearToSend: FALSE, dataSetReady: FALSE,
ringIndicator: FALSE, unused: 0];
dceUp: NewRS232CFace.DeviceStatus ← [
breakDetected: FALSE, dataLost: FALSE, ringHeard: FALSE,
carrierDetect: TRUE, clearToSend: TRUE, dataSetReady: TRUE,
ringIndicator: FALSE, unused: 0];
StatusToReset: PROC[s, m: NewRS232CFace.DeviceStatus]
RETURNS[NewRS232CFace.ResetRecord] = MACHINE CODE {Mopcodes.zAND};
StatusToStatus: PROC[s, m: NewRS232CFace.DeviceStatus]
RETURNS[NewRS232CFace.DeviceStatus] = MACHINE CODE {Mopcodes.zAND};
Instance: TYPE = LONG POINTER TO FRAME[SptpDriver];
framing: NATURAL = bpw * SIZE[SptpProtocol.EncapsulationObject] + 2; --2 is lrc
frameOffset: NATURAL = (Buffer.dataLinkReserve - framing) / bpw; --in words
ActiveDataState: PROC[] =
BEGIN
--don't wait for anything but interrupt
b: Buffer.Buffer = SptpProtocol.WaitForControl[@protocol, 0];
IF b # NIL THEN SptpProtocol.ProcessControl[@protocol, b];
END; --ActiveDataState
ActivateDriver: PROC =
BEGIN
iocbs: CARDINAL = 2 * myDevice.buffers; --2X the number of receive buffers
priority: Process.Priority = Process.GetPriority[]; --so can restore later
IF ~rs232c.pleaseStop THEN Driver.Glitch[DriverAlreadyActive];
lineSpeed ← 0; --so it knows to go and compute again
rs232c.getGarbage ← FALSE; --everybody's favorite default
protocol.object.me ← SpecialSystem.GetProcessorID[];
IF HostNumbers.IsMulticastID[@protocol.object.me] THEN WaitForHellToFreeze[];
rs232c.handle ← NewRS232CFace.GetHandle[
protocol.object.lineNumber, debuggerClient];
[cv: rs232c.interrupt, mask: rs232c.mask] ←
SpecialRuntime.AllocateNakedCondition[];
NewRS232CFace.Initialize[rs232c.mask];
NewRS232CFace.InitializeCleanup[];
Process.DisableTimeout[rs232c.interrupt];
IF ~CommFlags.driverStats THEN myDevice.stats ← NIL
ELSE (myDevice.stats ← LONG[@sptpStats])↑ ← [];
Buffer.QueueInitialize[@protocol.q];
protocol.lock ← @LOCK;
rs232c.probe ← rs232c.transmits ← 0;
rs232c.lta ← FALSE; --ie., we have the line
rs232c.traceProc ← NIL; --'cause we ain't
rs232c.pleaseStop ← FALSE; --and we're not stopping
protocol.sendControlFrame ← SendControlFrame;
IF SptpOps.siuSupport THEN --only works for single port
protocol.sppAllocationWindow ← SppOps.sppWindowSize;
IF point5Duplex AND (protocol.object.duplex = half) THEN
dceUp.clearToSend ← dceUp.carrierDetect ← FALSE;
protocol.object.state ← idle; --temporary state
input.lastStatus ← output.lastStatus ← disaster; --forces new read
DoCommandEntry[off]; --maybe this will inhibit reboots (see note[1])
<<+++++++>>
reason ← voluntary;
<<+++++++>>
Buffer.QueueInitialize[@input.q];
Buffer.QueueInitialize[@input.r];
Buffer.QueueInitialize[@output.q];
Buffer.QueueInitialize[@output.w];
input.queueAllowed ← myDevice.buffers;
input.access ← Buffer.MakePool[0, myDevice.buffers];
input.timeLastRecv ← output.timeXmtDone ← System.GetClockPulses[];
iocbState.free ← NIL;
IF NewRS232CFace.operationSize = 0 THEN Driver.Glitch[IOCBSizeIsZero];
IF CommFlags.doDebug THEN {iocbState.avail ← iocbs};
iocbState.first ← iocbState.free ← CommUtil.AllocateIocbs[
bpw * iocbs * NewRS232CFace.operationSize]; --special storage for iocbs
THROUGH [0..iocbs - 1) DO
iocbState.first ← iocbState.first.next ←
iocbState.first + NewRS232CFace.operationSize;
REPEAT FINISHED =>
{iocbState.first.next ← NIL; iocbState.first ← iocbState.free};
ENDLOOP;
Process.SetPriority[CommPriorities.driver]; --these guys are hot
rs232c.process ← FORK Interrupt[]; --gets naked notifies
protocol.watcher ← FORK Watcher[]; --runs the state machine
Process.SetPriority[priority]; --back to caller priority
END; --ActivateDriver
ComputeLineSpeed: PROC[b: Buffer.Buffer] =
BEGIN
bits: LONG CARDINAL = 8D6 * b.fo.driver.length; --bits involved X 10↑6
duration: LONG CARDINAL ← output.timeXmtDone - b.fo.time; --in pulses
duration ← System.PulsesToMicroseconds[[duration]]; --in usecs
lineSpeed ← CARDINAL[bits--10↑6-- / duration--10↑6--]; --bits per second
IF point5Duplex AND (protocol.object.duplex = half) THEN
BEGIN
clock.mstrTmo ← MasterTimeout[lineSpeed]; --now that we know better
clock.ltaHold ← clock.mstrTmo / 64; --that's based on measured line speed
END;
END; --ComputeLineSpeed
CreateDriver: PUBLIC <<SptpOps>> PROC[reservation: SptpOps.Reservation] =
BEGIN
him: Instance;
device: Device = GetDevice[reservation.lineNumber];
SELECT TRUE FROM
(device # NIL) =>
BEGIN
him ← FrameFromDevice[device];
him.refCount ← him.refCount.SUCC; --one more ref
END;
(instanceBusy) => --testing the zeroth instance
BEGIN
him ← NEW SptpDriver; --brand new one
START him; --so he'll initialize the procedure
him.instanceCopied ← TRUE; --he's destructable
refCount ← refCount.SUCC; --now has a reference
him.setupDriver[reservation];
END;
ENDCASE =>
BEGIN
refCount ← refCount.SUCC; --now has a reference
instanceBusy ← TRUE; --now he's busy
setupDriver[reservation];
END;
END; --CreateDriver
DeactivateDriver: PROC =
BEGIN
process: PROCESS;
IF rs232c.interrupt = NIL THEN RETURN; --sign of a deactive driver
myDevice.alive ← FALSE; --I'm dying - soon
Process.Abort[protocol.watcher]; JOIN protocol.watcher;
IF (protocol.object.state > option2) --up far enough to make this real
AND (protocol.object.theirEntityClass # siu) THEN --and it's not an SIU
BEGIN
protocol.object.state ← terminate1; --going down voluntarily
SptpProtocol.SendTerminateRequest[@protocol]; --send our initial request
THROUGH [0..4) DO
SptpProtocol.AwaitTerminateReply[@protocol]; --wait answer/send request
IF protocol.object.state = idle THEN EXIT; --got cooperation
ENDLOOP;
END;
TurnDeviceOff[]; --this should put us back on hook
DoCommandEntry[off]; --then shut down for good
rs232c.pleaseStop ← TRUE; --for others to check
rs232c.handle ← NewRS232CFace.ReleaseHandle[rs232c.handle];
IF (process ← rs232c.process) # NIL THEN
UNTIL rs232c.process = NIL DO
Process.EnableAborts[rs232c.interrupt]; Process.Abort[process];
REPEAT FINISHED => JOIN process;
ENDLOOP;
Buffer.QueueCleanup[@protocol.q]; --make sure they're all freed
SpecialRuntime.DeallocateNakedCondition[rs232c.interrupt];
rs232c.interrupt ← NIL; --so we can detect deactivates followed by deletes
CommUtil.FreeIocbs[iocbState.first];
Buffer.DestroyPool[input.access];
END; --DeactivateDriver
DeleteDriver: PROC =
BEGIN
IF refCount = 0 THEN RETURN; --what the &~#%?
IF (refCount ← refCount.PRED) # 0 THEN RETURN; --should be MONITORed
IF rs232c.pleaseStop THEN Driver.Glitch[DriverNotActive];
Driver.RemoveDeviceFromChain[protocol.myDevice]; --get's us out of chain
IF instanceCopied THEN Runtime.SelfDestruct[] --it was a copied frame
ELSE instanceBusy ← FALSE; --or he was the zeroth instance
END; --DeleteDriver
DoCommandEntry: ENTRY PROC[command: NewRS232CFace.Command] = INLINE
{ENABLE UNWIND => NULL; DoCommandInternal[command]}; --DoCommandEntry
DoCommandInternal: INTERNAL PROC[command: NewRS232CFace.Command] =
BEGIN
timein: LONG CARDINAL;
commandStatus: NewRS232CFace.CommandStatus ← rejected;
WHILE rs232c.cmdInProgress DO WAIT rs232c.cmdDone; ENDLOOP;
IF CommFlags.doStats THEN SptpStats.Incr[commandInit];
IF CommFlags.driverStats THEN
sptpStats.commandInitiated ← sptpStats.commandInitiated.SUCC;
rs232c.cmdInProgress ← TRUE; --flag us as busy
timein ← System.GetClockPulses[]; --record time command requested
--UNTIL commandStatus = complete-- DO
ENABLE UNWIND => rs232c.cmdInProgress ← FALSE; --aborted?
commandStatus ← (IF commandStatus = rejected
THEN NewRS232CFace.InitiateCommand[rs232c.handle, command]
ELSE NewRS232CFace.PollCommand[rs232c.handle]);
SELECT TRUE FROM
(commandStatus = completed) => EXIT; --that's success
((System.GetClockPulses[] - timein) > clock.cmdTmo) => GOTO commandLost;
ENDCASE;
WAIT rs232c.cmdDone; --wait some
REPEAT commandLost =>
BEGIN
IF CommFlags.doStats THEN SptpStats.Incr[commandLost];
IF CommFlags.driverStats THEN
sptpStats.commandLost ← sptpStats.commandLost.SUCC;
END;
ENDLOOP;
rs232c.cmdInProgress ← FALSE; --we're done
END; --DoCommandInternal
EnqueueReceive: ENTRY PROC[b: Buffer.Buffer] RETURNS[BOOLEAN ← TRUE] =
BEGIN
--NO SIGNALS OUT OF HERE
SELECT TRUE FROM
(CommFlags.doDebug AND (b.fo.driver.iocb # NIL)) =>
Driver.Glitch[OverlayingIocb];
((b.fo.driver.iocb ← iocbState.free) # NIL) =>
BEGIN
OPEN inuse: NARROW[b.fo.driver.iocb, InuseIocb];
iocbState.free ← iocbState.free.next;
iocbState.avail ← iocbState.avail.PRED;
b.linkLayer.blockPointer ← b.linkLayer.blockPointer + frameOffset;
inuse.op ← [b.linkLayer.blockPointer,
MIN[(b.highLayer.stopIndexPlusOne + framing), maxRS232CBytes]];
SetFaceStatus[
b, NewRS232CFace.InitiateReceive[rs232c.handle, @inuse.op]];
Buffer.Enqueue[@input.q, b];
END;
ENDCASE => --the ENDCASE better be rare!
BEGIN
IF CommFlags.doStats THEN Stats.StatIncr[statsIocbWait];
Driver.ReturnFreeBuffer[b]; --give the buffer back (ARGH!!)
RETURN[FALSE]; --and tell caller we failed
END;
END; --EnqueueReceive
FaceStatusToTransferStatus: PROC[fs: NewRS232CFace.TransferStatus]
RETURNS[Buffer.TransferStatus] = INLINE {
RETURN[SELECT fs FROM
inProgress => pending, success => goodCompletion, aborted => aborted,
<<
asyncFramingError, checksumError, dataLost => hardwareProblem,
deviceError, disaster, frameTimeout => hardwareProblem,
invalidCharacter, invalidFrame, parityError => hardwareProblem,
>>
ENDCASE => hardwareProblem]}; --FaceStatusToTransferStatus
FlushReassemblyQueue: INTERNAL PROC[b: Buffer.Buffer] =
BEGIN
IF b # NIL THEN Driver.ReturnFreeBuffer[b]; --return this buffer
UNTIL input.r.length = 0 DO --and all those in frag queue
Driver.ReturnFreeBuffer[Buffer.Dequeue[@input.r]]; ENDLOOP;
input.nextFragment ← 0; --we'll be restarting at zero
END; --FlushReassemblyQueue
<<
MAKING THIS AN INLINE WILL CAUSE THE COMPILATION TO FAIL.
>>
FrameFromDevice: PROC[device: Device] RETURNS[Instance] = --INLINE
{RETURN[LOOPHOLE[Runtime.GlobalFrame[LOOPHOLE[device.activateDriver]]]]};
FreeBufferAndIocb: INTERNAL PROC[
b: Buffer.Buffer, queueProc: PROC[b: Buffer.Buffer]] =
BEGIN
iocb: FreeIocb ← b.fo.driver.iocb;
IF iocb # NIL THEN
BEGIN
iocb.next ← iocbState.free;
iocbState.free ← iocb;
b.fo.driver.iocb ← NIL;
iocbState.avail ← iocbState.avail.SUCC;
END;
b.fo.network ← protocol.myDevice; --that's where the buffer came from
queueProc[b]; --then put it on the relavent queue
END; --FreeBufferAndIocb
GetBufferAndIocb: INTERNAL PROC[length: NATURAL ← maxRS232CBytes]
RETURNS[b: Buffer.Buffer] =
BEGIN
SELECT TRUE FROM
(iocbState.free = NIL) =>
{IF CommFlags.doStats THEN Stats.StatIncr[statsIocbWait]; RETURN[NIL]};
((b ← Driver.GetInputBuffer[FALSE, length + framing]) = NIL) => RETURN;
ENDCASE;
IF CommFlags.doDebug AND (b.fo.driver.iocb # NIL) THEN
Driver.Glitch[OverlayingIocb];
b.fo.driver.iocb ← iocbState.free;
iocbState.free ← iocbState.free.next;
iocbState.avail ← iocbState.avail.PRED;
b.linkLayer.blockPointer ← b.linkLayer.blockPointer + frameOffset;
END; --GetBufferAndIocb
GetDevice: PUBLIC <<SptpOps>> PROC[lineNumber: CARDINAL]
RETURNS [net: Device] =
BEGIN
FOR net ← Driver.GetDeviceChain[], net.next WHILE net # NIL DO
SELECT TRUE FROM
(net.device # myDevice.device) => NULL;
(net.lineNumber = lineNumber) => RETURN; --'net' is interesting
ENDCASE;
ENDLOOP;
<<RETURN[NIL]; --interesting but not useful in the larger sense>>
END; --GetDevice
GetEntityClass: PUBLIC <<SptpOps>> PROC[device: Buffer.Device]
RETURNS[SptpProtocol.EntityClass] =
BEGIN
gf: Instance = FrameFromDevice[device];
RETURN[gf.protocol.object.theirEntityClass];
END; --GetEntityClass
GetProtocolInfo: PUBLIC <<SptpOps>> PROC[
device: Device, info: SptpProtocol.ProtocolInfo] =
BEGIN
gf: Instance = FrameFromDevice[device];
info↑ ← gf.protocol.object;
END; --GetProtocolInfo
GetStatusEntry: ENTRY PROC[] = INLINE
{ENABLE UNWIND => NULL; GetStatusInternal[]};
GetStatusInternal: INTERNAL PROC[] =
BEGIN
tempStatus: NewRS232CFace.DeviceStatus;
DoCommandInternal[getDeviceStatus]; --issue the command
tempStatus ← NewRS232CFace.GetDeviceStatus[rs232c.handle];
IF rs232c.newStatus # tempStatus THEN
BEGIN
rs232c.oldStatus ← rs232c.newStatus;
rs232c.newStatus ← tempStatus;
rs232c.lastStatusChange ← System.GetClockPulses[];
END;
rs232c.lineStatusChange ← FALSE; --now that we responded
END; --GetStatusInternal
GetThroughput: PROC[] RETURNS[CARDINAL] = {RETURN[lineSpeed / 1000]};
GetVersion: PUBLIC <<SptpOps>> PROC[device: Device]
RETURNS[SptpProtocol.ProtocolVersion] =
BEGIN
gf: Instance = FrameFromDevice[device];
RETURN[gf.protocol.object.protocolVersion];
END; --GetVersion
GetXmtQueue: PUBLIC <<SptpOps>> PROC[device: Buffer.Device]
RETURNS[Buffer.Queue, LONG POINTER TO CONDITION] =
BEGIN
gf: Instance ← FrameFromDevice[device];
RETURN[@gf.output.w, @gf.output.notify];
END; --GetXmtQueue
InitiateOutput: INTERNAL PROC[b: Buffer.Buffer] =
BEGIN
OPEN inuse: NARROW[b.fo.driver.iocb, InuseIocb];
ENABLE UNWIND =>
{b.fo.status ← aborted; Driver.PutOnGlobalDoneQueue[b]};
down: BOOLEAN = StatusToStatus[rs232c.newStatus, dceUp] # dceUp;
NOTIFY output.notify; --in case any client is watching
IF rs232c.lineStatusChange OR down THEN
BEGIN
GetStatusInternal[]; --get latest info from the IOP
IF ~rs232c.newStatus.dataSetReady THEN GOTO terminate; --dead connection
END;
SELECT TRUE FROM
(StatusToStatus[rs232c.newStatus, dceLatches] = allOffStatus) => NULL;
(~ResetLatchesInternal[]) => GOTO terminate; --test and reset latches
ENDCASE;
IF point5Duplex AND (protocol.object.duplex = half) THEN
BEGIN
timein: LONG CARDINAL = System.GetClockPulses[];
IF rs232c.lta AND (protocol.object.state = data) THEN GOTO rejected;
--UNTIL (System.GetClockPulses[] - timein) > clock.ctsDelay-- DO
ENABLE UNWIND => {b.fo.status ← aborted; Driver.PutOnGlobalDoneQueue[b]};
commandStatus: NewRS232CFace.CommandStatus ← rejected;
GetStatusInternal[]; --read the current device status
IF rs232c.newStatus.clearToSend --must be up
AND ~rs232c.newStatus.carrierDetect THEN EXIT; --must be down
IF (System.GetClockPulses[] - timein) > clock.ctsDelay THEN
{reason ← noCTS; GOTO terminate}; --give it up
SetControlInternal[[TRUE, TRUE]]; --raise RTS and keep DTR
ENDLOOP;
WITH SptpProtocol.EncapsulationFromBlock[b.linkLayer.blockPointer]
SELECT FROM
ns, arpa, iso, pup => --we control LTA on these
rs232c.lta ← LTA ← (rs232c.transmits > window) OR (output.w.length = 0);
control => rs232c.lta ← LTA; --but not on all others
ENDCASE => GOTO rejected; --I don't want to deal with these right now
rs232c.transmits ← IF ~rs232c.lta THEN rs232c.transmits.SUCC ELSE 0;
END;
IF (b.fo.driver.iocb ← iocbState.free) = NIL THEN
BEGIN
IF CommFlags.doStats THEN Stats.StatIncr[statsIocbWait];
b.fo.status ← aborted; Driver.PutOnGlobalDoneQueue[b]; RETURN;
END;
iocbState.free ← iocbState.free.next;
iocbState.avail ← iocbState.avail.PRED;
inuse.op ← [b.linkLayer.blockPointer, b.fo.driver.length];
IF (lineSpeed = 0) AND (b.requeueProcedure = Driver.ReturnFreeBuffer)
AND (b.fo.driver.length > measurableLength) THEN --compute line speed
BEGIN
lineSpeed ← lineSpeed.SUCC; --so we don't do it again
b.fo.time ← System.GetClockPulses[]; --record time transmit init'd
END;
SetFaceStatus[b, NewRS232CFace.InitiateTransmit[rs232c.handle, @inuse.op]];
IF output.q.length = 0 THEN output.timeXmtDone ← System.GetClockPulses[];
Buffer.Enqueue[@output.q, b];
EXITS
rejected =>
BEGIN
b.fo.status ← rejected;
Driver.PutOnGlobalDoneQueue[b];
END;
terminate =>
BEGIN
protocol.object.state ← terminate2;
b.fo.status ← rejected;
Driver.PutOnGlobalDoneQueue[b];
END;
END; --InitiateOutput
Interrupt: <<FORKED>> ENTRY PROC[] =
BEGIN
<<
This is the procedure that responds to the naked notifies. It decides
whether the interrupt completes an input, output or control operation
and calls the proper routine to deal with the I/O completion or sets
the state of the line, whichever is appropriate.
Input processing has precedence over output.
All possible sources of the interrupt are polled each time we wake up.
Commands and SetParameters were requested by the Watcher. Notify him if
the state of either changes.
>>
ENABLE UNWIND => NULL;
b: Buffer.Buffer;
causeForInterruptKnown: BOOLEAN;
status: NewRS232CFace.TransferStatus;
--UNTIL ABORTED-- DO
ENABLE ABORTED => EXIT;
WAIT rs232c.interrupt; --wait for something to finish
causeForInterruptKnown ← FALSE; --until we find out why
UNTIL (b ← input.q.first) = NIL DO
operation: NewRS232CFace.OperationPtr ← b.fo.driver.iocb;
[b.fo.driver.length, status] ← NewRS232CFace.PollReceiveOrTransmit[
rs232c.handle, operation];
IF status = inProgress THEN EXIT;
InInterrupt[Buffer.Dequeue[@input.q], status];
causeForInterruptKnown ← TRUE; --that fits
ENDLOOP;
UNTIL (b ← output.q.first) = NIL DO
operation: NewRS232CFace.OperationPtr ← b.fo.driver.iocb;
[b.fo.driver.length, status] ← NewRS232CFace.PollReceiveOrTransmit[
rs232c.handle, operation];
IF status = inProgress THEN EXIT;
OutInterrupt[Buffer.Dequeue[@output.q], status];
causeForInterruptKnown ← TRUE; --and so does that
ENDLOOP;
SELECT TRUE FROM
(causeForInterruptKnown) => NULL; --we think we're under control
(rs232c.cmdInProgress) => BROADCAST rs232c.cmdDone; --hopefully his
ENDCASE => {rs232c.lineStatusChange ← TRUE; BROADCAST protocol.engine};
ENDLOOP;
rs232c.process ← NIL; --we're out of here
END; --Interrupt
InInterrupt: INTERNAL PROC[
b: Buffer.Buffer, status: NewRS232CFace.TransferStatus] =
BEGIN
OPEN inuse: NARROW[b.fo.driver.iocb, InuseIocb];
<<
Called by the interrupt state process when an input operation has just
completed. The buffer has been dequeued and the operation is in the
NewRS232CFace.CompletedTransferStatus category (ie., not "inProgress").
>>
encapsulation: SptpProtocol.Encapsulation =
SptpProtocol.EncapsulationFromBlock[b.linkLayer.blockPointer];
input.lastStatus ← status; --record for posterity
b.fo.status ← FaceStatusToTransferStatus[status];
SELECT status FROM
success =>
BEGIN
rs232c.probe ← 0; --any reception is as good as a "iAmHere"
input.timeLastRecv ← b.fo.time ← System.GetClockPulses[]; --line's alive
IF point5Duplex AND (protocol.object.duplex = half) THEN
BEGIN
<<
The polarity of rs232c.lta is for sending. It has been reversed on
the receive side. Watch out for this! The correct definition is:
"The line is not turned around. Therefore we can transmit."
Therefore NOT rs232c.lta => requestToSend: TRUE;
>>
lta: BOOLEAN = WITH encapsulation SELECT FROM
control => ~LTA, pup, ns, arpa, iso => ~LTA, ENDCASE => TRUE;
IF lta # rs232c.lta THEN --it's different that last time
BEGIN
rs232c.lta ← lta; --record for posterity
BROADCAST protocol.engine; --notify engine
IF CommFlags.doStats THEN SptpStats.Incr[lta];
END;
END;
IF CommFlags.driverStats THEN
BEGIN
sptpStats.pktsReceived ← sptpStats.pktsReceived.SUCC;
sptpStats.bytesReceived ←
sptpStats.bytesReceived + b.fo.driver.length;
END;
IF CommFlags.doStats THEN
BEGIN
SptpStats.Incr[packetsRcvd];
SptpStats.Bump[bytesRcvd, b.fo.driver.length];
END;
b.linkLayer.stopIndexPlusOne ← b.fo.driver.length;
IF rs232c.traceProc # NIL THEN
rs232c.traceProc[b.linkLayer, rx, rs232c.newStatus];
FreeBufferAndIocb[b, WITH eo: encapsulation SELECT FROM
control => PutOnControlQueue, --these go directly to control
pup, ns, arpa, iso => --these might need reassembly
(IF eo.more THEN PutOnReassemblyQueue ELSE
Driver.PutOnGlobalInputQueue),
ENDCASE => Driver.PutOnGlobalInputQueue]; --all the rest just go here
b ← GetBufferAndIocb[maxRS232CBytes]; --get buffer to replace
END;
#aborted =>
BEGIN
IF CommFlags.doStats THEN
BEGIN
dif: NATURAL = status.ORD - NewRS232CFace.TransferStatus[aborted].ORD;
stat: NATURAL = SptpStats.StatCounterIndex[aborted].ORD + dif;
SptpStats.Incr[LOOPHOLE[stat, SptpStats.StatCounterIndex]];
END;
IF CommFlags.driverStats THEN
SELECT status FROM
checksumError =>
sptpStats.checksumError ← sptpStats.checksumError.SUCC;
dataLost =>
sptpStats.dataLost ← sptpStats.dataLost.SUCC;
deviceError =>
sptpStats.deviceError ← sptpStats.dataLost.SUCC;
disaster =>
sptpStats.disaster ← sptpStats.disaster.SUCC;
invalidFrame =>
sptpStats.invalidFrame ← sptpStats.invalidFrame.SUCC;
ENDCASE;
rs232c.lineStatusChange ← TRUE; --this is more than a guess
BROADCAST protocol.engine; --and that's who needs to check it out
SELECT TRUE FROM
(rs232c.getGarbage) =>
BEGIN
FreeBufferAndIocb[b, Driver.PutOnGlobalInputQueue]; --he wants it
b ← GetBufferAndIocb[maxRS232CBytes]; --get buffer
END;
((Buffer.DataBytesPerRawBuffer[b] + Buffer.dataLinkReserve) <
maxRS232CBytes) =>
BEGIN
FreeBufferAndIocb[b, Driver.PutOnGlobalDoneQueue]; --can't reuse
b ← GetBufferAndIocb[maxRS232CBytes]; --get new buffer
END;
ENDCASE;
FlushReassemblyQueue[NIL]; --if there was one
END;
ENDCASE =>
BEGIN
IF CommFlags.doStats THEN SptpStats.Incr[aborted];
IF CommFlags.driverStats THEN
sptpStats.receiveAborted ← sptpStats.receiveAborted.SUCC;
--Aborted!? Maybe the protocol engine is in trouble...
FreeBufferAndIocb[b, Driver.PutOnGlobalDoneQueue];
b ← NIL; --let the engine worry about the input queue refilling
FlushReassemblyQueue[NIL]; --if there was one
END;
IF b = NIL THEN BROADCAST protocol.engine --let him deal with it
ELSE
BEGIN
OPEN inuse: NARROW[b.fo.driver.iocb, InuseIocb];
<<
It's important here to make sure b.highLayer.stopIndexPlusOne
hasn't been modified since the buffer was gotten from the BufferMgr
>>
inuse.op ← [b.linkLayer.blockPointer,
MIN[(b.highLayer.stopIndexPlusOne + framing), maxRS232CBytes]];
SetFaceStatus[b,
(status ← NewRS232CFace.InitiateReceive[rs232c.handle, @inuse.op])];
IF status = inProgress THEN Buffer.Enqueue[@input.q, b]
ELSE
BEGIN
IF CommFlags.doStats THEN SptpStats.Incr[rcvFailed];
FreeBufferAndIocb[b, Driver.ReturnFreeBuffer]; --aborted
BROADCAST protocol.engine; --what's going on?
END;
END;
END; --InInterrupt
MasterTimeout: PROC[ls: LONG CARDINAL] RETURNS[mwt: LONG CARDINAL] =
BEGIN
<<
AVOIDING OVER/UNDER FLOW!!!!
Master watchdog timer = 1 + (3 packets X MPS X 8 bits/octet) / LS
MPS = maximum packet size in octets per packet
LS = line speed in bits per second
>>
mwt --bits--
← 1 + (3 --packets-- * maxRS232CBytes --bytes/packet-- * 8 --bits/byte--);
mwt --bits X 10↑3-- ← mwt --bits-- * LONG[1000];
mwt --(msecs)-- ← mwt --bits X 10↑3-- / ls --bits/second-- ;
mwt --(usecs)-- ← mwt --(msecs)-- * LONG[1000];
RETURN[System.MicrosecondsToPulses[mwt]]; --(pulses)
END; --MasterTimeout
MediumNotAvailable: PROC[] RETURNS[down: BOOLEAN] =
BEGIN
<<
We don't get notified every time a line status changes. So if we don't
suspect something and the currently know status is okay, we trust it.
Otherwise, go read the status and figure out the truth.
NB: This is all to keep from hammering on the IOP for status.
>>
down ← StatusToStatus[rs232c.newStatus, dceUp] # dceUp; --current info
IF rs232c.lineStatusChange OR down THEN
BEGIN
GetStatusEntry[]; --get latest info from the IOP
down ← StatusToStatus[rs232c.newStatus, dceUp] # dceUp; --and recompute
IF ~rs232c.newStatus.dataSetReady THEN --do we still have a connection?
BEGIN
reason ← dsrDropped; --we had it once
protocol.object.state ← terminate2; --and then we lost it
RETURN; -- but this line isn't useful anymore
END;
END;
IF protocol.q.length # 0 THEN RETURN[FALSE]; --input data to process
IF point5Duplex AND (protocol.object.duplex = half) THEN
BEGIN
IF rs232c.lta THEN
BEGIN
<<
MASTER TIMEOUT
He has the line but he's not the master. Maybe we can grab away
it and make things work again. This works for the time when we don't
know who the master is too.
The test here is that the line has been idle for more than
clock.mstrTmo. We can't count the time we had the line in the
computation for how long he's had it.
>>
SELECT TRUE FROM
((System.GetClockPulses[] - input.timeLastRecv) < clock.mstrTmo) => {};
((System.GetClockPulses[] - output.timeXmtDone) < clock.mstrTmo) => {};
(protocol.object.master # him) => --let's go for it
BEGIN
IF CommFlags.driverStats THEN
sptpStats.masterTmo ← sptpStats.masterTmo.SUCC;
IF CommFlags.doStats THEN SptpStats.Incr[masterTMO];
rs232c.lta ← FALSE; --okay, we own it now
input.timeLastRecv ← System.GetClockPulses[]; --restart timer
RETURN[FALSE]; --lie about this so we use the line
END;
ENDCASE;
END
ELSE
BEGIN
<<
VOLUNTARY LTA
We own the line, but we haven't used it for quite a while. Maybe
we should turn it around by sending a null packet.
>>
SELECT TRUE FROM
(protocol.object.state # data) => NULL; --only works in data state
(output.q.length + output.w.length # 0) => NULL; --sending now
((System.GetClockPulses[] - input.timeLastRecv) > clock.ltaHold) =>
SptpProtocol.SendNull[@protocol]; --he gave us the line long ago
ENDCASE; --not in right state to surrender line
END;
RETURN; --and in any case, return
END;
<<
The following monitoring code is only for full duplex (half returned above).
If this is full duplex and we don't have clear to send, something funnies
going on. Try raising RTS and see what happens (probably an SIU).
If the protocol hasn't made contact with the remote, then we're probably
just trying to get the line up (state < option3).
If it's in a terminate2 state then we're already dealing with the modem.
If the line isn't up but it wasn't last time either, then that's nothing
new, so just report it to the caller.
If the line is down, but it hasn't been for long, then report the fact
but don't do anything rash.
But, if the protocol is active and the line is down and it has been for some
time, then force the phone back on hook.
>>
SELECT TRUE FROM
(~down) => NULL; --line is not down (aka "up")
(~rs232c.newStatus.clearToSend) => SetControlEntry[[TRUE, TRUE]];
(protocol.object.state ~IN[option3..terminate1]) => NULL; --not active
~(StatusToStatus[rs232c.oldStatus, dceUp] = dceUp) => NULL; --just changed
((System.GetClockPulses[] - rs232c.lastStatusChange) > clock.cdDelay) =>
{reason ← cdDropped; protocol.object.state ← terminate2};
ENDCASE;
END; --MediumNotAvailable
Null: PROC[BOOLEAN] = {--ChangeInputBuffers proc--};
OutInterrupt: INTERNAL PROC[
b: Buffer.Buffer, status: NewRS232CFace.TransferStatus] =
BEGIN
b.fo.status ← FaceStatusToTransferStatus[(output.lastStatus ← status)];
output.timeXmtDone ← System.GetClockPulses[]; --mark the time, good or bad
IF point5Duplex AND (protocol.object.duplex = half) THEN
BEGIN
ENABLE UNWIND => NULL; --this block is abortable
<<
What's it mean of we transmitted a LTA and the transmit failed?
For the time being, we'll just act as if the other end will see
the packet. Let the master's timeout figure out the situations where
we guessed wrong.
The follwing construct just pulls the LTA out of the packet just
transmitted. Since the packet is COMPUTED/OVERLAID there's no gain
in generating local variables with the results, so it's all bundled
into a single expression.
Don't copy the result into rs232c.lta. That's done when the frame is
queued for transmit to block subsequent transmission attemps. All this
code is doing is controlling the modem, which has to be done after the
packet offering the LTA is transmitted.
>>
IF (WITH SptpProtocol.EncapsulationFromBlock[
b.linkLayer.blockPointer] SELECT FROM
control => LTA, pup, ns, arpa, iso => LTA, ENDCASE => FALSE) THEN
BEGIN
rs232c.oldStatus ← rs232c.newStatus; --preserve old status
WAIT rs232c.bitClock; --wait for bits to clock out
SetControlInternal[[TRUE, FALSE]]; --dropping RTS
GetStatusInternal[]; --force new status to be read
IF CommFlags.doErrors AND rs232c.newStatus.clearToSend THEN
Driver.Glitch[BUG]; --well, it should have dropped
rs232c.lineStatusChange ← FALSE; --we believe status is current
rs232c.lastStatusChange ← System.GetClockPulses[]; --as of this moment
BROADCAST protocol.engine; --anybody care?
IF CommFlags.doStats THEN SptpStats.Incr[lta];
END;
END;
SELECT status FROM
success =>
BEGIN
IF (lineSpeed = 1) AND (b.requeueProcedure = Driver.ReturnFreeBuffer)
AND (b.fo.driver.length > measurableLength) THEN ComputeLineSpeed[b];
SELECT TRUE FROM
(point5Duplex AND rs232c.lta) => NULL; --we don't own the line
(output.q.length # 0) => NULL; --already got output queued
(output.w.length # 0) => InitiateOutput[Buffer.Dequeue[@output.w]];
ENDCASE; --endcase - nothing to initiate
IF CommFlags.driverStats THEN
BEGIN
sptpStats.packetsSent ← sptpStats.packetsSent.SUCC;
sptpStats.bytesSent ← sptpStats.bytesSent + b.fo.driver.length;
END;
IF CommFlags.doStats THEN
BEGIN
SptpStats.Incr[packetsXmt];
SptpStats.Bump[bytesXmt, b.fo.driver.length];
END;
b.linkLayer.stopIndexPlusOne ← b.fo.driver.length;
IF rs232c.traceProc # NIL THEN
rs232c.traceProc[b.linkLayer, tx, rs232c.newStatus];
END;
ENDCASE => IF CommFlags.doStats THEN
BEGIN
dif: NATURAL = status.ORD - NewRS232CFace.TransferStatus[aborted].ORD;
stat: NATURAL = SptpStats.StatCounterIndex[aborted].ORD + dif;
SptpStats.Incr[LOOPHOLE[stat, SptpStats.StatCounterIndex]];
IF CommFlags.driverStats THEN
SELECT status FROM
aborted =>
sptpStats.sendAborted ← sptpStats.sendAborted.SUCC;
deviceError =>
sptpStats.deviceError ← sptpStats.dataLost.SUCC;
disaster =>
sptpStats.disaster ← sptpStats.disaster.SUCC;
invalidFrame =>
sptpStats.invalidFrame ← sptpStats.invalidFrame.SUCC;
ENDCASE;
rs232c.lineStatusChange ← TRUE; --this is more than a guess
BROADCAST protocol.engine; --let engine deal with such things
END;
FreeBufferAndIocb[b, Driver.PutOnGlobalDoneQueue]; --finished with this
END; --OutInterrupt
PutOnControlQueue: INTERNAL PROC[b: Buffer.Buffer] =
BEGIN
Buffer.Enqueue[@protocol.q, b];
BROADCAST protocol.engine;
IF CommFlags.driverStats THEN
sptpStats.controlPktReceived ← SUCC[sptpStats.controlPktReceived];
IF CommFlags.doStats THEN
BEGIN
SptpStats.Incr[cntrlRcvd];
SptpStats.Bump[cntrlBytesRcvd, b.fo.driver.length];
END;
END; --PutOnControlQueue
PutOnReassemblyQueue: INTERNAL PROC[b: Buffer.Buffer] =
BEGIN
<<
Only data packets with the more bit set get here. The IOCB has already
been freed.
If this is the last packet in a sequence, then reassemble them and
put them on the GlobalInputQueue.
>>
eo: SptpProtocol.EncapsulationObject = --this is the new one
SptpProtocol.EncapsulationFromBlock[b.linkLayer.blockPointer]↑;
WITH eo SELECT FROM
pup, ns, arpa, iso =>
BEGIN
SELECT TRUE FROM
(fragment.n # input.nextFragment) => --out of sequence
BEGIN
IF CommFlags.doStats THEN SptpStats.Incr[badFragSeq];
FlushReassemblyQueue[b]; --this one is busted
END;
(fragment.n < fragment.of) => --next in the sequence
BEGIN
IF CommFlags.doStats THEN SptpStats.Incr[fragRcvd];
Buffer.Enqueue[@input.r, b]; --put it on the frag queue
input.nextFragment ← input.nextFragment.SUCC; --that one's next
END;
ENDCASE => --all here; reassemble them in a single packet
BEGIN
moved: NATURAL; --counter for the blt operations
rb: Buffer.Buffer ← Driver.GetInputBuffer[ --final assembly buffer
FALSE, myDevice.receiveBufferLen + framing];
IF rb = NIL THEN {FlushReassemblyQueue[b]; RETURN};
IF CommFlags.doStats THEN SptpStats.Incr[reassembly];
Buffer.Enqueue[@input.r, b]; --put it on end of reassembly queue
SptpProtocol.EncapsulationFromBlock[b.linkLayer.blockPointer]↑ ← eo;
rb.linkLayer.startIndex ← rb.linkLayer.startIndex +
bpw * SIZE[ns SptpProtocol.EncapsulationObject]; --encap moved
UNTIL input.r.length = 0 DO --and all those in frag queue
b ← Buffer.Dequeue[@input.r]; --get the first/next fragment
b.linkLayer.startIndex ←
bpw * SIZE[ns SptpProtocol.EncapsulationObject];
moved ← ByteBlt.ByteBlt[to: rb.linkLayer, from: b.linkLayer];
rb.fo.driver.length ← moved + rb.fo.driver.length;
Driver.ReturnFreeBuffer[b]; --return the fragment
ENDLOOP;
input.nextFragment ← 0; --we'll be restarting at zero
Driver.PutOnGlobalInputQueue[rb]; --send it upstairs
END;
END;
ENDCASE => IF CommFlags.doDebug THEN Driver.Glitch[BUG];
END; --PutOnReassemblyQueue
ResetLatchesEntry: ENTRY PROC[] RETURNS[BOOLEAN] = INLINE
{ENABLE UNWIND => NULL; RETURN ResetLatchesInternal[]}; --ResetLatchesEntry
ResetLatchesInternal: INTERNAL PROC[] RETURNS[okay: BOOLEAN ← TRUE] =
BEGIN
THROUGH[0..4) DO
timein: LONG CARDINAL;
latched: NewRS232CFace.ResetRecord;
commandStatus: NewRS232CFace.CommandStatus ← rejected;
WHILE rs232c.cmdInProgress DO WAIT rs232c.cmdDone; ENDLOOP;
rs232c.cmdInProgress ← TRUE; --so we have the IOP's attention
timein ← System.GetClockPulses[]; --and when we got his attention
latched ← StatusToReset[rs232c.newStatus, latchBits]; --bits to clear
IF CommFlags.driverStats AND latched.resetDataLost THEN
sptpStats.dataLost ← sptpStats.dataLost.SUCC;
IF CommFlags.doStats AND latched.resetDataLost THEN
SptpStats.Incr[dataLost];
--UNTIL commandStatus = complete-- DO
ENABLE UNWIND => rs232c.cmdInProgress ← FALSE; --aborted?
commandStatus ← (IF commandStatus = rejected
THEN NewRS232CFace.InitiateResetStatusBits[rs232c.handle, latched]
ELSE NewRS232CFace.PollSetControlBits[rs232c.handle]);
SELECT TRUE FROM
(commandStatus = completed) => EXIT; --that's success
((System.GetClockPulses[] - timein) < clock.cmdTmo) => NULL; --cont.
(CommFlags.doErrors) => Driver.Glitch[BUG]; --that's a failure
ENDCASE; --loop forever
WAIT rs232c.cmdDone; --wait some
ENDLOOP;
rs232c.cmdInProgress ← FALSE; --so we can do a get status
GetStatusInternal[]; --force a read of the device status
IF StatusToStatus[rs232c.newStatus, dceLatches] = allOffStatus THEN EXIT;
REPEAT FINISHED => {reason ← couldntClearLatches; okay ← FALSE};
ENDLOOP;
END; --ResetLatchesInternal
ResetQueues: PROC[] =
BEGIN
DumpQ: ENTRY PROC[q: Buffer.Queue, p: PROC[b: Buffer.Buffer]] =
BEGIN
UNTIL q.length = 0 DO
b: Buffer.Buffer = Buffer.Dequeue[q];
b.fo.status ← aborted;
FreeBufferAndIocb[b, p];
ENDLOOP;
END; --DumpQ
--head doesn't know about the 'w' queue
DumpQ[@output.w, Driver.PutOnGlobalDoneQueue];
THROUGH[0..2) UNTIL (input.q.length + output.q.length = 0) DO
IF input.q.length > 0 THEN DoCommandEntry[abortReceive];
IF output.q.length > 0 THEN DoCommandEntry[abortTransmit];
DoCommandEntry[on]; --maybe we can make the IOP notice
Process.Pause[clock.shortTmo]; --wait for the gas to take effect
ENDLOOP;
IF CommFlags.doStats THEN SptpStats.Incr[faceReset];
IF CommFlags.driverStats THEN
sptpStats.protocolDown ← sptpStats.protocolDown.SUCC;
<<
Aborting the trasmit|receive queues should cause all the requests on
the respective queue to be marked aborted and completed. The interrupt
routine should run before us and by time we look, the queues should be
empty. If for some reason that doesn't happen, we can go ahead and free
them from here. Some day I should sort out why it doesn't happen reliably.
>>
DumpQ[@input.q, Driver.ReturnFreeBuffer];
DumpQ[@output.q, Driver.PutOnGlobalDoneQueue];
END; --ResetQueues
SendBufferEntry: ENTRY PROC[b: Buffer.Buffer] = INLINE
{ENABLE UNWIND => NULL; SendBufferInternal[b]}; --SendBufferEntry
SendBufferInternal: INTERNAL PROC[b: Buffer.Buffer] =
BEGIN
--THERE ARE NO SIGNALS OUT OF HERE - GLITCHES DON'T COUNT--
IF CommFlags.doDebug AND b.fo.driver.iocb # NIL THEN
Driver.Glitch[OverlayingIocb];
IF CommFlags.doErrors AND b.fo.driver.length > maxRS232CBytes THEN
Driver.Glitch[PacketTooLarge];
SELECT TRUE FROM
(output.q.length # 0) => Buffer.Enqueue[@output.w, b]; --to waiting queue
(~point5Duplex) => --we're not doing any half duplex stuff
BEGIN
IF CommFlags.doStats THEN SptpStats.Incr[xmtQEmpty];
InitiateOutput[b]; --queue's empty, so stuff it out there
END;
(~rs232c.lta) => --.5 duplex and we own the line
BEGIN
output.timeXmtDone ← System.GetClockPulses[]; --so it doesn't look stuck
IF CommFlags.doStats THEN SptpStats.Incr[xmtQEmpty];
InitiateOutput[b]; --queue's empty, so stuff it out there
END;
ENDCASE => Buffer.Enqueue[@output.w, b]; --we can't transmit (.5 duplex)
END; --SendBufferInternal
SendControlFrame: ENTRY PROC[b: Buffer.Buffer, size: NATURAL] =
BEGIN
ENABLE UNWIND => NULL;
b.fo.driver.length ← size; --this many bytes
IF protocol.object.duplex = full THEN
WITH SptpProtocol.EncapsulationFromBlock[b.linkLayer.blockPointer]
SELECT FROM control => LTA ← FALSE; ENDCASE; --don't toggle in full
InitiateOutput[b]; --put it directly on output queue
IF CommFlags.driverStats THEN
sptpStats.controlPktSent ← SUCC[sptpStats.controlPktSent];
IF CommFlags.doStats THEN
BEGIN
SptpStats.Incr[cntrlXmt];
SptpStats.Bump[cntrlBytesXmt, size];
END;
END; --SendControlFrame
SendRawBuffer: PROC[b: Buffer.Buffer] =
BEGIN
fl: NATURAL; --fragment length
ob: Environment.Block; --copy so we can modify
q: Buffer.QueueObject; --it's local, so don't have to monitor
el: NATURAL = SIZE[ns SptpProtocol.EncapsulationObject] * bpw;
eo: SptpProtocol.EncapsulationObject ← SptpProtocol.EncapsulationFromBlock[
b.linkLayer.blockPointer]↑; --coy the original so we can modify
SELECT TRUE FROM
(~myDevice.alive) => --can't use a dead driver
{b.fo.status ← rejected; Driver.PutOnGlobalDoneQueue[b]};
(protocol.object.protocolVersion = version4) =>
BEGIN
WITH neo: eo SELECT FROM --get the NARROW'd EncapsulationObject
pup, ns, arpa, iso =>
BEGIN
IF ~neo.more THEN {SendBufferEntry[b]; RETURN};
ob ← b.highLayer; --copy out the block so we can modify
fl ← SptpOps.defaultMaxRS232CBytes - framing; --fragment length
Buffer.QueueInitialize[@q]; --get all the fields assigned
Buffer.Enqueue[@q, b]; --the first buffer in the queue is client's
b.fo.driver.length ← fl; --w/ modified length
UNTIL neo.fragment.n = neo.fragment.of DO
fb: Buffer.Buffer ← Driver.GetInputBuffer[ --get a fragment buffer
TRUE, SptpOps.defaultMaxRS232CBytes]; --even wait
IF fb = NIL THEN LOOP; --this isn't going to fly without buffers
neo.fragment.n ← neo.fragment.n.SUCC; --and update sequence number
ob.startIndex ← ob.startIndex + fl; --we just did this many
SptpProtocol.EncapsulationFromBlock[
fb.linkLayer.blockPointer]↑ ← eo;
fb.linkLayer.startIndex ← el; --offset past the encapsulation
fb.highLayer.stopIndexPlusOne ← SptpOps.defaultMaxRS232CBytes;
fl ← ByteBlt.ByteBlt[to: fb.linkLayer, from: ob]; --copy frag
b.fo.driver.length ← fl; --the length of this fragment
Buffer.Enqueue[@q, fb]; --stick it on the queue
ENDLOOP;
SendQueueEntry[@q]; --acquire the monitor and send all buffers
END;
ENDCASE;
END;
ENDCASE => SendBufferEntry[b]; --only version 4 supports fragmentation
END; --SendRawBuffer
SendQueueEntry: ENTRY PROC[q: Buffer.Queue] =
{ENABLE UNWIND => NULL;
UNTIL (q.length = 0) DO
SendBufferInternal[Buffer.Dequeue[q]]; ENDLOOP};
SetFaceStatus: PROC[
b: Buffer.Buffer, status: NewRS232CFace.TransferStatus] = INLINE
BEGIN
OPEN di: LOOPHOLE[b.fo.driver, SptpProtocol.DriverInformation];
di.faceStatus ← phonenet[status];
END; --SetFaceStatus
SetupDriver: PROC[reservation: SptpOps.Reservation] =
BEGIN
rs232c.pleaseStop ← TRUE;
rs232c.reservation ← reservation↑; --save initial values
myDevice.lineSpeed ← SELECT reservation.lineSpeed FROM
<= bps1200 => 1, bps2400 => 2, <= bps4800 => 5, bps7200 => 7,
bps9600 => 9, bps19200 => 19, bps28800 => 28, bps38400 => 38,
bps48000 => 48, ENDCASE => 56;
clock.giveup ← 80; --seconds to establish connection
clock.shortTmo ← Process.MsecToTicks[100]; --short pause
clock.clockCksm ← Process.MsecToTicks[30]; --wait for cksm to clear SIO
clock.onHook ← Process.MsecToTicks[2000]; --lowering DTR
clock.cmdTmo ← System.MicrosecondsToPulses[5D5]; --500 msecs
clock.cdDelay ← System.MicrosecondsToPulses[5D5]; --500 msecs
clock.ctsDelay ← System.MicrosecondsToPulses[1D6]; --1 full second
clock.idleInput ← System.MicrosecondsToPulses[30D6]; --30 secs
clock.stuckOutput ← System.MicrosecondsToPulses[10D6]; --10 secs
clock.mstrTmo ← MasterTimeout[<<myDevice.lineSpeed * 1000>> 1200];
clock.ltaHold ← clock.mstrTmo / 64; --that's based on 1200 bps
protocol.object.duplex ← reservation.duplex;
protocol.object.lineNumber ← reservation.lineNumber;
protocol.object.ourEntityClass ← reservation.entity;
Process.EnableAborts[@rs232c.cmdDone];
Process.SetTimeout[@rs232c.cmdDone, clock.shortTmo];
Process.EnableAborts[@rs232c.bitClock];
Process.SetTimeout[@rs232c.bitClock, clock.clockCksm];
Process.EnableAborts[@protocol.engine];
Process.SetTimeout[@protocol.engine, clock.shortTmo];
myDevice.buffers ← input.queueAllowed ← inputQueueLength; --default
Driver.AddDeviceToChain[(protocol.myDevice ← @myDevice)];
END; --SetupDriver
SetCollectGarbageToo: PUBLIC <<SptpOps>> PROC[
device: Device, collectGarbage: BOOLEAN] =
BEGIN
gf: Instance ← FrameFromDevice[device];
gf.rs232c.getGarbage ← collectGarbage;
END; --SetCollectGarbageToo
SetTrace: PUBLIC <<SptpOps>> PROC[device: Device, proc: SptpOps.TraceProc]
RETURNS[old: SptpOps.TraceProc] =
BEGIN
gf: Instance ← FrameFromDevice[device];
old ← gf.rs232c.traceProc; --record the old one
gf.rs232c.traceProc ← proc; --and set the new one
END; --SetTrace
SetControlEntry: ENTRY PROC[bits: NewRS232CFace.ControlRecord] = INLINE
{ENABLE UNWIND => NULL; SetControlInternal[bits]}; --SetControlEntry
SetControlInternal: INTERNAL PROC[bits: NewRS232CFace.ControlRecord] =
BEGIN
timein: LONG CARDINAL;
commandStatus: NewRS232CFace.CommandStatus ← rejected;
WHILE rs232c.cmdInProgress DO WAIT rs232c.cmdDone; ENDLOOP;
rs232c.cmdInProgress ← TRUE; --so we have the IOP's attention
timein ← System.GetClockPulses[]; --and when we got his attention
--UNTIL commandStatus = complete-- DO
ENABLE UNWIND => rs232c.cmdInProgress ← FALSE; --aborted?
commandStatus ← (IF commandStatus = rejected
THEN NewRS232CFace.InitiateSetControlBits[rs232c.handle, bits]
ELSE NewRS232CFace.PollSetControlBits[rs232c.handle]);
SELECT TRUE FROM
(commandStatus = completed) => EXIT; --that's a success
((System.GetClockPulses[] - timein) < clock.cmdTmo) => NULL; --continue
(CommFlags.doErrors) => Driver.Glitch[BUG]; --that's a failure
ENDCASE; --loop forever
WAIT rs232c.cmdDone; --wait for something of interest to happen
ENDLOOP;
rs232c.cmdInProgress ← FALSE; --turn loose
END; --SetControlInternal
TurnDeviceOff: PROC[] =
BEGIN
myDevice.alive ← FALSE; --I'm dead
SetControlEntry[[FALSE, FALSE]]; --hang up
Process.Pause[clock.onHook]; --just wait (clock.onHook == clock.to5)
ResetQueues[]; --get rid of the input and output queues
Buffer.QueueCleanup[@protocol.q]; --make sure they're all freed
protocol.object.state ← idle; --now I'm idle
IF SptpOps.siuSupport AND (protocol.object.theirEntityClass = siu) THEN
SppOps.SetWindow[protocol.sppAllocationWindow]; --reset back to original
END; --TurnDeviceOff
TurnDeviceOn: PROC[] =
BEGIN
set: NewRS232CFace.ParameterStatus;
parameters: NewRS232CFace.ParameterRecord ← [
charLength: 8, clientType: unknown,
correspondent: RS232CCorrespondents.nsSystemElement,
echo: FALSE, flowControl: [none, 0, 0], frameTimeout: 500,
lineSpeed: rs232c.reservation.lineSpeed, lineType: bitSynchronous,
parity: none, stopBits: 2, syncChar: 26B, syncCount: 4];
DoCommandEntry[on]; --turn IOP on
THROUGH[0..4) DO
set ← NewRS232CFace.InitiateSetParameters[rs232c.handle, @parameters];
IF set # rejected THEN EXIT ELSE Process.Pause[clock.shortTmo];
REPEAT FINISHED => IF CommFlags.doErrors THEN Driver.Glitch[BUG];
ENDLOOP;
THROUGH[0..4) DO
set ← NewRS232CFace.PollSetParameters[rs232c.handle];
IF set # inProgress THEN EXIT ELSE Process.Pause[clock.shortTmo];
REPEAT FINISHED => IF CommFlags.doErrors THEN Driver.Glitch[BUG];
ENDLOOP;
rs232c.newStatus ← latchBits; --force the latches to appear set
[] ← ResetLatchesEntry[]; --then reset them
IF point5Duplex AND (protocol.object.duplex = half) THEN
{protocol.object.master ← undetermined; rs232c.lta ← FALSE};
rs232c.probe ← 0; --just like starting over
protocol.object.started ← protocol.object.established ← [0];
protocol.object.him ← System.nullHostNumber; --we don't know him yet
protocol.object.protocolVersion ← version4; --this is where we start
rs232c.newStatus ← allOffStatus; --make sure this is reasonably bogus
UNTIL rs232c.newStatus.dataSetReady DO
SetControlEntry[[TRUE, TRUE]]; --raise DTR and RTS
Process.Pause[clock.shortTmo]; --wait for the plot to thicken
GetStatusEntry[]; --read status to see if we're where we want to be
ENDLOOP;
protocol.object.state ← option1; --and advance the state
END; --TurnDeviceOn
WaitForHellToFreeze: ENTRY PROC[] =
BEGIN
ProcessorFace.SetMP[989]; --should probably warn PSCO
DO ENABLE ABORTED => CONTINUE; Process.Pause[LAST[CARDINAL]]; ENDLOOP;
END; --WaitForHellToFreeze
Watcher: PROC =
BEGIN
FeedOutputQueue: ENTRY PROC[] RETURNS[BOOLEAN] = INLINE
BEGIN
ENABLE UNWIND => NULL;
IF (output.q.length # 0) OR (output.w.length = 0) THEN RETURN[FALSE];
IF point5Duplex AND rs232c.lta THEN RETURN[FALSE];
InitiateOutput[Buffer.Dequeue[@output.w]]; RETURN[TRUE];
END; --FeedOutputQueue
WaitForLineChange: ENTRY PROC[] = INLINE
{ENABLE UNWIND => NULL; WAIT protocol.engine}; --WaitForLineChange
b: Buffer.Buffer;
UNTIL rs232c.pleaseStop --OR ABORTED-- DO
ENABLE ABORTED => EXIT;
IF (protocol.object.state = terminate2) THEN
BEGIN
SptpProtocol.TerminationDally[@protocol]; --obligatory dally time
TurnDeviceOff[]; --then shut down the device - state == idle
END;
IF (protocol.object.state = idle) THEN TurnDeviceOn[]; --back up
IF MediumNotAvailable[] THEN {WaitForLineChange[]; LOOP}; --'till ready
<<
The following loop is trying to keep the input queue full. If the
interrupt routine couldn't get a new buffer (and he's not permitted
to wait while holding the monitory), then he will notify the protocol
engine (that's me). This loop does WAIT for a buffer, but if it still
comes back NIL, it bails out.
>>
WHILE (input.q.length < input.queueAllowed) DO
b ← Driver.GetInputBuffer[TRUE, maxRS232CBytes + framing];
IF b = NIL THEN EXIT; --we waited and still didn't get one
IF ~EnqueueReceive[b] THEN EXIT; --queue buffer if an iocb available
ENDLOOP;
--Check the state machine
SELECT protocol.object.state FROM
option1 => SptpProtocol.ActiveNegotiation[@protocol]; --try establish
option2 => SptpProtocol.PassiveNegotiation[@protocol]; --wait for him
option3 => SptpProtocol.AwaitingOptions[@protocol]; --wait his myOptions
option4 => SptpProtocol.AwaitingOptionAck[@protocol]; --wait his ack
data => ActiveDataState[]; --dominant state of the engine
terminate1 => SptpProtocol.AwaitTerminateReply[@protocol]; --death song
ENDCASE;
--STUCK OUTPUT
SELECT TRUE FROM
(FeedOutputQueue[]) => NULL; --just keeping the queue fed
(output.q.length = 0) => NULL; --nothing to check
((System.GetClockPulses[] - output.timeXmtDone) > clock.stuckOutput) =>
BEGIN
IF CommFlags.doStats THEN Stats.StatIncr[statPacketsStuckInOutput];
ResetQueues[]; --life isn't good any more
output.timeXmtDone ← System.GetClockPulses[]; --to keep from looping
LOOP; --and since that shut the device off...
END;
ENDCASE;
--IDLE INPUT
IF (System.GetClockPulses[] - input.timeLastRecv) > clock.idleInput THEN
BEGIN
SELECT protocol.object.state FROM
= data => NULL; --dominant case
< option3 => GOTO skip; --waiting patiently
ENDCASE => GOTO dead; --just drop the line
input.timeLastRecv ← System.GetClockPulses[]; --to avoid looping
IF CommFlags.doStats THEN Stats.StatIncr[statInputIdle];
SELECT TRUE FROM
(protocol.object.protocolVersion = version2) => NULL; --too dumb
((rs232c.probe ← rs232c.probe.SUCC) > probeCount) => GOTO dead; --...
ENDCASE => SptpProtocol.SendAreYouThere[@protocol]; --see if line works
EXITS
skip => NULL; --not valid state for test
dead => {TurnDeviceOff[]; LOOP}; --failed connection
END;
ENDLOOP;
END; --Watcher
--initialization
setupDriver ← SetupDriver; --for multi instances
END. --SptpDriver
Notes and Comments
This is modeled after the EthernetDriver which is built in the model being
promoted as "correct" for PrincOps systems. It is a client of the NewRS232CFace.
The previous effort in this area was a client of RS232C (the channel). That
was a driver written on top of a driver that was a client of a head that had
its own mini-driver imbedded.
In the process of writing this, lots of things have been discovered about the
RS232C heads (especially DLions). Some of those are noted here. This log should
change as/if we fix the strangeness in the heads.
1-Turning RS232C 'on' may cause the DLion system to reboot.
If the IOP is turned 'on' and the device is soft booted, trying to turn the
device on again may cause it to reboot. I believe that a soft boot doesn't
reset the entire system (IOP) as completely as a Reset-B does.
2-Turning RS232C 'off' more than once per session doesn't work
Previously the device was only turned off once per session (soft or hard).
That was done when the RS232CHeadDLion was start trapped. That protected it
from the note mentioned in the previous paragraph. But subsequent 'off's seem
to not work, perhaps due to a recording of the parameters by the head and
subsequent suppression of commands after that.
As noted by BKI 24 Nov 87 19:21:48 PST, "A quick fix to the head is to set
curParameterValid to FALSE after initiating the off command."
**This one is fixed.
3-The interrupt from RS232C is ambiguous. You can't tell if it's from an I/O
operation, a command completion (they all interrupt when complete) or a modem
signal changing. Consequently, this module tries to match an interrupt up with
some known cause. If there is I/O complete (discovered by polling outstanding
requests) the interrupt is credited to that operation. If there is a command
in progress, then that gets credited. Otherwise it guesses that there was a
modem signal change and proceeds on that assumption.
4-This implementation implements V.4 of the Synchronous Point to Point
protocol. Since there are no other implementors of V.4 out there, it will
always negotiate down to V.3 or (heaven forbid V.2, aka SIUs). V.4 is fully
compliant with V.3 if the packet sizes do not require fragmentation.
5-The fragmentation code takes a buffer and gets 'n' smaller buffers and BLTs
fragments of the first into them. All along it's queuing, first the origninal
with it's length changed, then the latter fragments into a local queue
(Buffer.Queue). It does all this without holding the monitor, using the
client process. That means the Driver.GetInputBuffer can WAIT. Then, when
it has the queue with all the fragments constructed, it grabs the monitor
and dumps them all (in sequence) onto the transmit queue. Hence, no client
data interleave.
6-On the receive side, the queuing is done using holding the monitor, in the
InInterrupt procedure (forget PhonenetDriver, think EthernetDriver - that's
where the model came from). When the last fragment comes in, it reassembles
them, still holding the montitor, and puts them on the global input queue.
Because its holding the monitor, it can't wait. If it doesn't get a buffer,
it tosses the entire sequence! The only alternative I can think of is another
process, and I'm resisting. Getting the buffers shouldn't be a problem.
The driver will probably extend the pool when it has to support reassembly.
The client will bear the cost.
LOG
time - by - action
19-Aug-87 17:33:26 AOF Created file from EthernetDriver
21-Oct-87 20:40:39 AOF Adding code for line status change monitoring
2-Nov-87 10:38:31 AOF Better (different) I/O abort code
3-Nov-87 15:07:47 AOF Chasing commands lost
11-Nov-87 20:58:22 AOF Fragmentation & reassembly
23-Nov-87 18:26:37 AOF Not relying on client selection of line speed
25-Nov-87 9:30:56 AOF Notes and Comments, tweaking 'on' and 'off'
27-Nov-87 11:32:35 AOF Add XmtQueue visability and computing lineSpeed
30-Nov-87 14:35:04 AOF Addressing notes #1 and #2
30-Nov-87 18:21:18 AOF Reset master when line is dropped
2-Dec-87 15:15:23 AOF Don't transmit out of LineArbitration when dce down
5-Jan-88 11:22:56 AOF Voluntary line turnaround clocking masterTimeout
12-Jan-88 12:29:56 AOF Using new New RS232CFace
15-Jan-88 9:44:34 AOF Controlling SPP allocation window for SIUs
16-Jan-88 18:27:59 AOF Frame tracing for debugging
20-Jan-88 16:44:28 AOF Free buffer on UNWIND from InitiateOutput
3-Feb-88 11:28:32 AOF PupGateway (no .5 duplex)