-- 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)