-- Copyright (C) 1984, 1985  by Xerox Corporation. All rights reserved. 
-- PhoneDriver.mesa, HGM,  9-Apr-85 22:17:39

DIRECTORY
  Environment USING [Byte],
  Inline USING [BITXOR, LowHalf],
  Process USING [DisableTimeout, InitializeCondition, MsecToTicks, SetPriority, Yield],
  ProcessPriorities USING [priorityPageFaultHigh],  -- Below Ethernet Drivers
  SpecialRuntime USING [AllocateNakedCondition],
  String USING [EqualString],
  System USING [
    GetClockPulses, GetGreenwichMeanTime, HostNumber, localHostNumber,
    MicrosecondsToPulses, nullHostNumber, nullNetworkNumber, Pulses, PulsesToMicroseconds],

  Buffer USING [AccessHandle, Buffer, MakePool, NSBuffer, PupBuffer, ReturnBuffer, Type],
  CommUtil USING [AllocateIocbs],
  Driver USING [
    NetworkObject, Network, PutOnGlobalDoneQueue,
    AddDeviceToChain, GetInputBuffer, PutOnGlobalInputQueue],
  DriverTypes USING [DeviceType, Encapsulation],
  PhoneNetFriends USING [NetEnumerator, nullNetEnumerate, PhoneNetInfo],
  PhoneNetExtras USING [nsCongestion, nsTooGreedy, pupCongestion, pupTooGreedy],
  PupTypes USING [PupHostID, PupErrorCode],

  DESFace USING [
    Block, CorrectParity, ECBDecrypt, ECBEncrypt, nullKey, Key],
  PhoneCreate USING [],
  PhoneFace USING [
    controlBlockSize, GetPacketLength, GetPacketsMissed, GetRecvStatus, GetSendStatus,
    QueueInput, QueueOutput, ResetLine, Status, TurnOn];

PhoneDriver: MONITOR LOCKS lock↑
  IMPORTS
    Inline, Process, SpecialRuntime, String, System,
    Buffer, CommUtil, DESFace, Driver, PhoneFace
  EXPORTS Buffer, PhoneCreate, PhoneNetFriends =
  BEGIN
  
  -- EXPORTed TYPEs
  Network: PUBLIC TYPE = Driver.Network;
  
  Byte: TYPE = Environment.Byte;

  maxHiPriWords: CARDINAL = (2 + 50 + 22)/2; -- 50 data bytes in a pup
  maxQueueDepth: CARDINAL ← 15; -- For each of Pup, NS, ...
  maxForwarderDepth: CARDINAL ← 10;
  maxConnectionDepth: CARDINAL ← 5;
  

  PhonePacketType: TYPE = MACHINE DEPENDENT{
    pupPhonePacket(100B),
    nsPhonePacket(300B),
    turnAroundPhonePacket(301B),
    turnAroundMTSPhonePacket(302B),
    terminatePhonePacket(303B),
    terminateAckPhonePacket(304B),
    (LAST[Byte])};

  Encapsulation: TYPE = MACHINE DEPENDENT RECORD [
    SELECT OVERLAID DriverTypes.DeviceType FROM
      phonenet => [
	framing0, framing1, framing2, framing3, framing4, framing5: Byte,
	recognition: Byte, -- 0 for auto recognition of OISCP vs SDLC/HDLC
	pnType: PhonePacketType,
	pnSrcID: System.HostNumber],
      ENDCASE];

  phoneEncapsulationOffset: CARDINAL = 3;
  phoneEncapsulationBytes: CARDINAL = 8;

  encapsulationTrap: BOOLEAN [TRUE..TRUE] =
    (SIZE[Encapsulation] = SIZE[DriverTypes.Encapsulation]);
  
  lock: POINTER TO MONITORLOCK ← @myLock;
  myLock: MONITORLOCK;

  Info: TYPE = LONG POINTER TO InfoRecord;
  InfoRecord: TYPE = RECORD [
    lineNumber: CARDINAL,
    currentSendBuffer: Buffer.Buffer,  -- Beware: this buffer may be encrypted
    firstHiSendBuffer, lastHiSendBuffer: Buffer.Buffer,
    firstLoSendBuffer, lastLoSendBuffer: Buffer.Buffer,
    packetsOnSendQueue: CARDINAL,
    timeSendStarted: System.Pulses,
    firstRecvBuffer, lastRecvBuffer: Buffer.Buffer,
    timeLastRecv: System.Pulses,
    missed: CARDINAL,
    bitsPerSecond: LONG CARDINAL,
    network: Driver.NetworkObject,
    stats: PhoneNetFriends.PhoneNetInfo,
    loophole: CrapForINRLoophole,
    encrypt: BOOLEAN,
    dlion: BOOLEAN,
    key: DESFace.Key,
    pool: Buffer.AccessHandle,
    sendIocb: LONG POINTER,
    recvIocbs: LONG POINTER,
    next: Info];
    
  CrapForINRLoophole: TYPE = MONITORED RECORD [
    clientData: LONG UNSPECIFIED,
    lineNumber: CARDINAL];

  info: InfoRecord ← [
    lineNumber: ,
    currentSendBuffer: NIL,
    firstHiSendBuffer: NIL,
    lastHiSendBuffer: NIL,
    firstLoSendBuffer: NIL,
    lastLoSendBuffer: NIL,
    packetsOnSendQueue: 0,
    timeSendStarted: ,
    firstRecvBuffer: NIL,
    lastRecvBuffer: NIL,
    timeLastRecv: ,
    missed: 0,
    bitsPerSecond: 0,
    network: [
      next: NIL,
      decapsulateBuffer: Decapsulate,
      encapsulateAndSendPup: SendPup,
      encapsulateAndSendNS: SendNS,
      sendRawBuffer: SendRawBuffer,
      encapsulateAndForwardPup: ForwardPup,
      encapsulateAndForwardNS: ForwardNS,
      activateDriver: ActivateDriver,
      deactivateDriver: KillDriver,
      deleteDriver: KillDriver,
      changeNumberOfInputBuffers: NIL,
      index: ,
      netNumber: System.nullNetworkNumber,
      pupNetNumber: 0,
      pupHostNumber: 0,
      device: phonenet,
      alive: FALSE,
      buffers: 10,  -- This should be overkill
      pupStats: NIL,
      statsLevel0: @info.loophole,
      statsLevel1: NIL],
    stats: [
      state: data,
      timeStarted: System.GetGreenwichMeanTime[],
      timeConnectionEstablished: System.GetGreenwichMeanTime[],
      remoteHostNumber: System.nullHostNumber,
      protocolVersion: old,
      lineNumber: NULL,
      duplexity: full,
      lineSpeed: bps9600,
      speed: 0, -- Observed, Kilobits/second
      negotiationMode: NULL,
      ourEntityClass: NULL,
      theirEntityClass: NULL,
      halfDuplexMode: NULL,
      hardwareStatsAvailable: TRUE,
      clientData: 0,
      clientHostNumber: NULL,
      sendQueueLength: 0,
      stats: ALL [0] ],
    loophole: [, @info.stats, ],
    encrypt: FALSE,
    dlion: FALSE,
    key: TRASH,
    pool: NIL,
    sendIocb: NIL,
    recvIocbs: NIL,
    next: NIL ];

  me: System.HostNumber = System.localHostNumber;
  
  -- Shared by all lines
  interrupt, watcher: PROCESS ← NIL;
  pleaseStop: BOOLEAN ← FALSE;
  hardware: LONG POINTER TO CONDITION ← NIL;
  interruptBits: WORD ← 0;
  
  
  DuplicateLineNumber: ERROR = CODE;
  


  -- **************** Interface to Router(s) ****************

  Decapsulate: PROCEDURE [b: Buffer.Buffer] RETURNS [type: Buffer.Type] =
    BEGIN
    encapsulation: LONG POINTER TO Encapsulation = LOOPHOLE[@b.encapsulation];
    bytes: CARDINAL ← 2*b.driver.length;
    IF bytes < phoneEncapsulationBytes THEN GOTO Rejected;
    IF info.encrypt THEN
      BEGIN
      words: CARDINAL ← (b.driver.length+1)/2;
      blocks: CARDINAL = (words+SIZE[DESFace.Block]-1)/SIZE[DESFace.Block];
      data: LONG POINTER = @b.encapsulation + phoneEncapsulationOffset;
      DESFace.ECBDecrypt[info.key, blocks, data, data];
      END;
    SELECT encapsulation.pnType FROM
      nsPhonePacket =>
        BEGIN
	IF bytes < b.ns.pktLength THEN GOTO Rejected;
	type ← ns;
	END;
      pupPhonePacket =>
        BEGIN
	IF bytes < b.pup.pupLength THEN GOTO Rejected;
        type ← pup;
	END;
      ENDCASE => GOTO Rejected;
    info.stats.remoteHostNumber ← encapsulation.pnSrcID;
    EXITS Rejected =>
      BEGIN
      type ← rejected;
      StatIncr[@info.stats.stats[pktsRejected]];
      END;
    END;
    
  SendPup: ENTRY PROCEDURE [b: Buffer.PupBuffer, systemElem: PupTypes.PupHostID] =
    BEGIN
    high: BOOLEAN;
    friends, pups: CARDINAL ← 0;
    encapsulation: LONG POINTER TO Encapsulation = LOOPHOLE[@b.encapsulation];
    encapsulation↑ ← [
      phonenet[
	framing0: 0, framing1: 0, framing2: 0, framing3: 0, framing4: 0,
	framing5: 0, recognition: 0,
	pnType: pupPhonePacket, pnSrcID: me] ];
    b.driver.length ←
      (b.pup.pupLength + 1 + phoneEncapsulationBytes)/2;
    [friends, pups, ] ← CountFriends[b, info.firstLoSendBuffer, friends, pups];
    high ← b.driver.length < maxHiPriWords AND (friends = 0);
    [friends, pups, ] ← CountFriends[b, info.firstHiSendBuffer, friends, pups];
    IF friends > maxConnectionDepth THEN
      BEGIN
      Driver.PutOnGlobalDoneQueue[b];
      StatIncr[@info.stats.stats[connTooGreedy]];
      StatIncr[@info.stats.stats[PhoneNetExtras.pupTooGreedy]];
      RETURN;
      END;
    IF pups > maxQueueDepth THEN
      BEGIN
      Driver.PutOnGlobalDoneQueue[b];
      StatIncr[@info.stats.stats[congestion]];
      StatIncr[@info.stats.stats[PhoneNetExtras.pupCongestion]];
      RETURN;
      END;
    QueueOutputBuffer[b, high];
    StatIncr[@info.stats.stats[pupSent]];
    END;

  SendNS: ENTRY PROCEDURE [b: Buffer.NSBuffer, host: System.HostNumber] =
    BEGIN
    high: BOOLEAN;
    friends, ns: CARDINAL ← 0;
    encapsulation: LONG POINTER TO Encapsulation = LOOPHOLE[@b.encapsulation];
    encapsulation↑ ← [
      phonenet[
	framing0: 0, framing1: 0, framing2: 0, framing3: 0, framing4: 0,
	framing5: 0, recognition: 0,
	pnType: nsPhonePacket, pnSrcID: me] ];
    b.driver.length ←
      (b.ns.pktLength + 1 + phoneEncapsulationBytes)/2;
    [friends, ns, ] ← CountFriends[b, info.firstLoSendBuffer, friends, ns];
    high ← b.driver.length < maxHiPriWords AND (friends = 0);
    [friends, ns, ] ← CountFriends[b, info.firstHiSendBuffer, friends, ns];
    IF friends > maxConnectionDepth THEN
      BEGIN
      Driver.PutOnGlobalDoneQueue[b];
      StatIncr[@info.stats.stats[connTooGreedy]];
      StatIncr[@info.stats.stats[PhoneNetExtras.nsTooGreedy]];
      RETURN;
      END;
    IF ns > maxQueueDepth THEN
      BEGIN
      Driver.PutOnGlobalDoneQueue[b];
      StatIncr[@info.stats.stats[congestion]];
      StatIncr[@info.stats.stats[PhoneNetExtras.nsCongestion]];
      RETURN;
      END;
    QueueOutputBuffer[b, high];
    StatIncr[@info.stats.stats[nsSent]];
    END;

  SendRawBuffer: ENTRY PROCEDURE [b: Buffer.Buffer] =
    BEGIN
    IF info.packetsOnSendQueue > maxQueueDepth THEN
      BEGIN
      Driver.PutOnGlobalDoneQueue[b];
      StatIncr[@info.stats.stats[congestion]];
      RETURN;
      END;
    QueueOutputBuffer[b, FALSE];
    StatIncr[@info.stats.stats[rawSent]];
    END;

  ForwardPup: ENTRY PROCEDURE [b: Buffer.PupBuffer, host: PupTypes.PupHostID]
    RETURNS [PupTypes.PupErrorCode] =
    BEGIN
    high: BOOLEAN;
    dup, lic, ate: BOOLEAN;
    friends, pups: CARDINAL ← 0;
    encapsulation: LONG POINTER TO Encapsulation = LOOPHOLE[@b.encapsulation];
    encapsulation↑ ← [
      phonenet[
	framing0: 0, framing1: 0, framing2: 0, framing3: 0, framing4: 0,
	framing5: 0, recognition: 0,
	pnType: pupPhonePacket, pnSrcID: me] ];
    b.driver.length ←
      (b.pup.pupLength + 1 + phoneEncapsulationBytes)/2;
    [friends, pups, dup] ← CountFriends[b, info.firstLoSendBuffer, friends, pups];
    high ← b.driver.length < maxHiPriWords AND (friends = 0);
    [friends, pups, lic] ← CountFriends[b, info.firstHiSendBuffer, friends, pups];
    [friends, pups, ate] ← CountFriends[b, info.currentSendBuffer, friends, pups];
    IF ((b.pup.pupType = aData OR b.pup.pupType = data) AND (dup OR lic OR ate) OR
      (b.pup.pupType = aData AND friends # 0 AND b.pup.pupLength = 22)) THEN {
      -- Barf, what an ugly hack to put into a gateway
      -- Duplicate data/aData OR (empty aData (probe) when something on the queue)
      StatIncr[@info.stats.stats[controlPktReceived]];
      RETURN[LOOPHOLE[10002]]; };
    IF friends > maxConnectionDepth THEN {
      StatIncr[@info.stats.stats[connTooGreedy]];
      StatIncr[@info.stats.stats[PhoneNetExtras.pupTooGreedy]];
      RETURN[gatewayResourceLimitsPupErrorCode]; };
    IF pups > maxForwarderDepth THEN {
      StatIncr[@info.stats.stats[congestion]];
      StatIncr[@info.stats.stats[PhoneNetExtras.pupCongestion]];
      RETURN[gatewayResourceLimitsPupErrorCode]; };
    QueueOutputBuffer[b, high];
    StatIncr[@info.stats.stats[pupSent]];
    RETURN[noErrorPupErrorCode];
    END;

  ForwardNS: ENTRY PROCEDURE [b: Buffer.PupBuffer, host: System.HostNumber] =
    BEGIN
    high: BOOLEAN;
    friends, ns: CARDINAL ← 0;
    encapsulation: LONG POINTER TO Encapsulation = LOOPHOLE[@b.encapsulation];
    encapsulation↑ ← [
      phonenet[
	framing0: 0, framing1: 0, framing2: 0, framing3: 0, framing4: 0,
	framing5: 0, recognition: 0,
	pnType: nsPhonePacket, pnSrcID: me] ];
    b.driver.length ←
      (b.ns.pktLength + 1 + phoneEncapsulationBytes)/2;
    [friends, ns, ] ← CountFriends[b, info.firstLoSendBuffer, friends, ns];
    high ← b.driver.length < maxHiPriWords AND (friends = 0);
    [friends, ns, ] ← CountFriends[b, info.firstHiSendBuffer, friends, ns];
    IF friends > maxConnectionDepth THEN
      BEGIN
      Driver.PutOnGlobalDoneQueue[b];
      StatIncr[@info.stats.stats[connTooGreedy]];
      StatIncr[@info.stats.stats[PhoneNetExtras.nsTooGreedy]];
      RETURN;
      END;
    IF ns > maxForwarderDepth THEN
      BEGIN
      Driver.PutOnGlobalDoneQueue[b];
      StatIncr[@info.stats.stats[congestion]];
      StatIncr[@info.stats.stats[PhoneNetExtras.nsCongestion]];
      RETURN;
      END;
    QueueOutputBuffer[b, high];
    StatIncr[@info.stats.stats[nsSent]];
    END;

  CountFriends: INTERNAL PROCEDURE [
    b: Buffer.Buffer, head: Buffer.Buffer, friends, neighbors: CARDINAL]
    RETURNS [newFriends, newNeighbors: CARDINAL, headerMatch: BOOLEAN] =
    BEGIN
    encapsulation: LONG POINTER TO Encapsulation = LOOPHOLE[@b.encapsulation];
    headerMatch ← FALSE;
    SELECT encapsulation.pnType FROM
      pupPhonePacket => {
	maybe: Buffer.PupBuffer ← head;
	UNTIL maybe = NIL DO
          encap: LONG POINTER TO Encapsulation = LOOPHOLE[@maybe.encapsulation];
	  IF encap.pnType = pupPhonePacket THEN {
	    neighbors ← neighbors + 1;
	    IF b.pup.dest = maybe.pup.dest THEN {
	      PupHeader: TYPE = ARRAY [0..11) OF WORD;
	      x: LONG POINTER TO PupHeader ← LOOPHOLE[@b.pup.pupLength];
	      y: LONG POINTER TO PupHeader ← LOOPHOLE[@maybe.pup.pupLength];
	      IF x↑ = y↑ THEN headerMatch ← TRUE;
	      friends ← friends + 1; }; };
	  maybe ← maybe.next;
	  ENDLOOP; };
      nsPhonePacket => {
	maybe: Buffer.NSBuffer ← head;
	UNTIL maybe = NIL DO
          encap: LONG POINTER TO Encapsulation = LOOPHOLE[@maybe.encapsulation];
	  IF encap.pnType = nsPhonePacket THEN {
	    neighbors ← neighbors + 1;
	    IF b.ns.destination = maybe.ns.destination THEN friends ← friends + 1; };
	  maybe ← maybe.next;
	  ENDLOOP; };
      ENDCASE => NULL;  -- You get what you deserve
    RETURN[friends, neighbors, headerMatch]
    END;

  QueueOutputBuffer: INTERNAL PROCEDURE [b: Buffer.Buffer, high: BOOLEAN] =
    BEGIN
    b.network ← LONG[@info.network];
    info.packetsOnSendQueue ← info.packetsOnSendQueue + 1;
    b.next ← NIL;
    IF high THEN
      BEGIN
      IF info.firstHiSendBuffer = NIL THEN info.firstHiSendBuffer ← b
      ELSE info.lastHiSendBuffer.next ← b;
      info.lastHiSendBuffer ← b;
      END
    ELSE
      BEGIN
      IF info.firstLoSendBuffer = NIL THEN info.firstLoSendBuffer ← b
      ELSE info.lastLoSendBuffer.next ← b;
      info.lastLoSendBuffer ← b;
      END;
    IF info.currentSendBuffer = NIL THEN StartSending[@info];
    END;
  
  StartSending: INTERNAL PROCEDURE [line: Info] =
    BEGIN
    b: Buffer.Buffer;
    SELECT TRUE FROM
      line.firstHiSendBuffer # NIL =>
        BEGIN
        b ← line.firstHiSendBuffer;
        line.firstHiSendBuffer ← b.next;
        END;
      line.firstLoSendBuffer # NIL =>
        BEGIN
        b ← line.firstLoSendBuffer;
        line.firstLoSendBuffer ← b.next;
        END;
      ENDCASE => RETURN;
    line.currentSendBuffer ← b;
    IF line.encrypt THEN
      BEGIN
      words: CARDINAL ← b.driver.length;
      blocks: CARDINAL = (words+SIZE[DESFace.Block]-1)/SIZE[DESFace.Block];
      data: LONG POINTER = @b.encapsulation + phoneEncapsulationOffset;
      DESFace.ECBEncrypt[line.key, blocks, data, data];
      b.driver.length ← blocks*SIZE[DESFace.Block];  -- Round up to decrypt last block
      END;
    IF line.dlion THEN Wait[7];
    b.driver.iocb ← line.sendIocb;
    PhoneFace.QueueOutput[
      line.lineNumber,
      b.driver.iocb,
      @b.encapsulation + phoneEncapsulationOffset,
      2*b.driver.length];
    line.timeSendStarted ← System.GetClockPulses[];
    END;
    
  Wait: PROCEDURE [ms: CARDINAL] =
    BEGIN
    start: System.Pulses = System.GetClockPulses[];
    DO
      now: System.Pulses = System.GetClockPulses[];
      IF System.PulsesToMicroseconds[[now-start]] > ms*1000 THEN EXIT;
      Process.Yield[];  -- Yetch.  Pause is too crude
      ENDLOOP;
    END;

  FinishSending: PROCEDURE [line: Info, b: Buffer.Buffer] =
    BEGIN
    IF line.encrypt AND (b.allNets OR b.requeueProcedure # Buffer.ReturnBuffer) THEN
      BEGIN  -- Oops. Untrash it
      words: CARDINAL ← b.driver.length;
      blocks: CARDINAL = (words+SIZE[DESFace.Block]-1)/SIZE[DESFace.Block];
      data: LONG POINTER = @b.encapsulation + phoneEncapsulationOffset;
      DESFace.ECBDecrypt[line.key, blocks, data, data];
      END;
    Driver.PutOnGlobalDoneQueue[b];
    END;
    
    
  -- **************** Watcher to timeout stuck things ****************
  
  -- 2400 baud is 300 bytes/sec.  Allow 10% for slop and whatever to get 270.
  pulsesPerByte: System.Pulses = [(System.MicrosecondsToPulses[1000000]/270)+1];
  recvIdleTimeout: System.Pulses = System.MicrosecondsToPulses[30000000];
  Watcher: ENTRY PROCEDURE =
    BEGIN
    watch: CONDITION;
    Process.InitializeCondition[@watch, Process.MsecToTicks[1000]];
    UNTIL pleaseStop DO
      WAIT watch;
      FOR line: Info ← @info, line.next UNTIL line = NIL DO
        flap: BOOLEAN ← FALSE;
	now: System.Pulses = System.GetClockPulses[];
	b: Buffer.Buffer ← line.currentSendBuffer;
	IF PhoneFace.GetPacketsMissed[line.lineNumber] # line.missed THEN
	  BEGIN
          oldMissed: CARDINAL ← line.missed;
          newMissed: CARDINAL ← PhoneFace.GetPacketsMissed[line.lineNumber];
	  line.missed ← newMissed;
          StatBump[@line.stats.stats[rcvErrorNoGet], (newMissed - oldMissed)];
	  END;
        IF b # NIL AND (now - line.timeSendStarted) > pulsesPerByte*b.driver.length*2 THEN
	  BEGIN
          StatIncr[@line.stats.stats[queueTooOld]];
	  flap ← TRUE;
	  END;
        IF (now - line.timeLastRecv) > recvIdleTimeout THEN
	  BEGIN
          StatIncr[@line.stats.stats[tooLongSinceLastReceive]];
	  flap ← TRUE;
	  END;
	IF flap THEN
	  BEGIN
	  line.stats.remoteHostNumber ← System.nullHostNumber;
	  PhoneFace.ResetLine[line.lineNumber];
	  IF line.currentSendBuffer # NIL THEN
	    BEGIN
	    FinishSending[line, line.currentSendBuffer];
	    line.currentSendBuffer ← NIL;
	    END;
	  UNTIL line.firstHiSendBuffer = NIL DO
	    b: Buffer.Buffer ← line.firstHiSendBuffer;
	    line.firstHiSendBuffer ← b.next;
	    Driver.PutOnGlobalDoneQueue[b];
	    ENDLOOP;
	  UNTIL line.firstLoSendBuffer = NIL DO
	    b: Buffer.Buffer ← line.firstLoSendBuffer;
	    line.firstLoSendBuffer ← b.next;
	    Driver.PutOnGlobalDoneQueue[b];
	    ENDLOOP;
          line.packetsOnSendQueue ← 0;
	  FOR b: Buffer.Buffer ← line.firstRecvBuffer, b.next UNTIL b = NIL DO
            PhoneFace.QueueInput[
              line.lineNumber,
              b.driver.iocb,
              @b.encapsulation + phoneEncapsulationOffset,
              2*(b.driver.length - phoneEncapsulationOffset)];
	    ENDLOOP;
	  line.timeLastRecv ← System.GetClockPulses[];
	  END;
        ENDLOOP;
      ENDLOOP;
    END;
 
  
  -- **************** Interrupt Processing ****************
  
  Interrupt: ENTRY PROCEDURE =
    BEGIN
    Process.SetPriority[ProcessPriorities.priorityPageFaultHigh];
    UNTIL pleaseStop DO
      WAIT hardware;
      FOR line: Info ← @info, line.next UNTIL line = NIL DO
	DO
	  status: PhoneFace.Status;
	  b: Buffer.Buffer ← line.currentSendBuffer;
	  IF b = NIL THEN EXIT;
	  status ← PhoneFace.GetSendStatus[b.driver.iocb];
	  IF status = pending THEN EXIT;
	  IF status = ok THEN
	    BEGIN
	    bits: CARDINAL ← 7 + 16*b.driver.length + 16 + 7;
	    now: System.Pulses ← System.GetClockPulses[];
	    micro: LONG CARDINAL ← System.PulsesToMicroseconds[[now-line.timeSendStarted]];
	    -- ARHG!!! 4000 * 1000000 overflows a LONG CARDINAL
	    bitsPerSecond: LONG CARDINAL = ((100000*bits)/micro)*10;
	    line.bitsPerSecond ← (63*line.bitsPerSecond + bitsPerSecond)/64;
	    line.stats.speed ← Inline.LowHalf[line.bitsPerSecond/1000];
	    StatIncr[@line.stats.stats[pktsSent]];
	    StatBump[@line.stats.stats[bytesSent], 2*b.driver.length];
	    END
	  ELSE StatIncr[@line.stats.stats[sendErrorBadStatus]];
          line.packetsOnSendQueue ← line.packetsOnSendQueue - 1;
	  line.currentSendBuffer ← NIL;
	  FinishSending[line, b];
	  StartSending[line];
	  ENDLOOP;
	WHILE line.firstRecvBuffer # NIL DO
	  b: Buffer.Buffer ← line.firstRecvBuffer;
	  status: PhoneFace.Status ← PhoneFace.GetRecvStatus[b.driver.iocb];
	  newBuffer: Buffer.Buffer ← NIL;
	  IF status = pending THEN EXIT;
	  line.firstRecvBuffer ← b.next;
	  IF status # ok THEN
	    BEGIN
	    SELECT status FROM
	      overrun => StatIncr[@line.stats.stats[rcvDeviceError]];
	      packetTooLong => StatIncr[@line.stats.stats[rcvErrorDataLost]];
	      crc => StatIncr[@line.stats.stats[rcvErrorCRC]];
	      ENDCASE => StatIncr[@line.stats.stats[rcvErrorUnknown]];
	    newBuffer ← b;
	    b ← NIL;
	    END
	  ELSE
	    BEGIN
	    newBuffer ← Driver.GetInputBuffer[];
	    IF newBuffer = NIL THEN
	      BEGIN  -- Rats, recycle this packet
	      StatIncr[@line.stats.stats[rcvErrorNoGet]];
	      newBuffer ← b;
	      b ← NIL;
	      END
	    ELSE newBuffer.driver.iocb ← b.driver.iocb;
	    END;
	  IF b # NIL THEN
	    BEGIN
            encapsulation: LONG POINTER TO Encapsulation = LOOPHOLE[@b.encapsulation];
	    bytes: CARDINAL ← PhoneFace.GetPacketLength[b.driver.iocb];
	    b.network ← @line.network;
            b.driver.length ← bytes;
            b.driver.faceStatus ← unknown[101H];
	    Driver.PutOnGlobalInputQueue[b];
	    line.timeLastRecv ← System.GetClockPulses[];
	    StatIncr[@line.stats.stats[pktsReceived]];
	    StatBump[@line.stats.stats[bytesReceived], bytes];
	    END;
	  newBuffer.network ← @line.network;
          PhoneFace.QueueInput[
            line.lineNumber,
	    newBuffer.driver.iocb,
	    @newBuffer.encapsulation + phoneEncapsulationOffset,
	    2*(newBuffer.driver.length - phoneEncapsulationOffset)];
          IF line.firstRecvBuffer = NIL THEN line.firstRecvBuffer ← newBuffer
          ELSE line.lastRecvBuffer.next ← newBuffer;
	  line.lastRecvBuffer ← newBuffer;
	  newBuffer.next ← NIL;
	  ENDLOOP;
        ENDLOOP;
      ENDLOOP;
    END;
  

  -- **************** Driver Management ****************

  CreatePhone: PUBLIC PROCEDURE [host, net, lineNumber: CARDINAL, password: LONG STRING] =
    BEGIN
    line: Info;
    IF watcher = NIL THEN
      BEGIN
      [cv: hardware, mask: interruptBits] ← SpecialRuntime.AllocateNakedCondition[];
      Process.DisableTimeout[hardware];
      PhoneFace.TurnOn[interruptBits];  -- Also gets it STARTed
      interrupt ← FORK Interrupt[];
      watcher ← FORK Watcher[];
      line ← @info;
      END
    ELSE
      BEGIN
      him: LONG POINTER TO FRAME[PhoneDriver] ← NEW PhoneDriver;
      START him;
      him.lock ← @myLock;
      line ← @him.info;
      FOR finger: Info ← @info, finger.next DO
        IF finger.lineNumber = lineNumber THEN ERROR DuplicateLineNumber;
        IF finger.next = NIL THEN
	  BEGIN
	  finger.next ← line;
	  EXIT;
	  END;
	ENDLOOP;
      END;
    line.network.alive ← TRUE;
    line.lineNumber ← lineNumber;
    line.stats.lineNumber ← lineNumber;
    line.loophole.lineNumber ← lineNumber;
    line.network.pupHostNumber ← host;
    line.network.pupNetNumber ← net;
    SELECT TRUE FROM
      password = NIL => NULL;
      String.EqualString[password, "DLion"L] => line.dlion ← TRUE;
      ENDCASE =>
        BEGIN
        line.key ← MakeKey[password];
        line.encrypt ← TRUE;
        END;
    line.pool ← Buffer.MakePool[0, line.network.buffers];
    line.sendIocb ← CommUtil.AllocateIocbs[(line.network.buffers+1)*PhoneFace.controlBlockSize];
    line.recvIocbs ← line.sendIocb + PhoneFace.controlBlockSize;
    Driver.AddDeviceToChain[@line.network];
    END;
    
  -- Don't muck with this unless you are prepared to change both ends.
  -- This doesn't hide the string very well.
  MakeKey: PROCEDURE [source: LONG STRING] RETURNS [key: DESFace.Key] =
    BEGIN
    key ← DESFace.nullKey;
    FOR i: CARDINAL IN [0..source.length) DO
      j: CARDINAL = i MOD 8;
      c: CHARACTER =
        IF source[i] IN ['A..'Z] THEN 'a + (source[i] - 'A) ELSE source[i];
      key[j].b ← Inline.BITXOR[LOOPHOLE[c, [0..127]], key[j].b];
      ENDLOOP;
    DESFace.CorrectParity[@key];
    END;

  ActivateDriver: ENTRY PROCEDURE =
    BEGIN
    tick: CONDITION;
    Process.InitializeCondition[@tick, 1];
    PhoneFace.ResetLine[info.lineNumber];
    FOR i: CARDINAL IN [0..info.network.buffers) DO
      b: Buffer.Buffer;
      UNTIL (b ← Driver.GetInputBuffer[FALSE]) # NIL DO WAIT tick; ENDLOOP;
      b.driver.iocb ← info.recvIocbs + i*PhoneFace.controlBlockSize;
      IF info.firstRecvBuffer = NIL THEN info.firstRecvBuffer ← b
      ELSE info.lastRecvBuffer.next ← b;
      PhoneFace.QueueInput[
        info.lineNumber,
        b.driver.iocb,
        @b.encapsulation + phoneEncapsulationOffset,
        2*(b.driver.length - phoneEncapsulationOffset)];
      info.lastRecvBuffer ← b;
      b.next ← NIL;
      ENDLOOP;
    info.timeLastRecv ← System.GetClockPulses[];
    info.missed ← PhoneFace.GetPacketsMissed[info.lineNumber];
    END;

  KillDriver: PROCEDURE =
    BEGIN
    ERROR;
    END;


  -- **************** Statistics ****************

  StatIncr: PROCEDURE [counter: LONG POINTER TO LONG CARDINAL] = INLINE
    BEGIN
    counter↑ ← counter↑ + 1;
    END;

  StatBump: PROCEDURE [
    counter: LONG POINTER TO LONG CARDINAL, bumpAmount: CARDINAL] = INLINE
    BEGIN
    counter↑ ← counter↑ + bumpAmount;
    END;

  -- **************** Keep Kluger/INR happy ****************
  
  GetDriverInfo: PUBLIC PROCEDURE [lineNumber: CARDINAL]
    RETURNS [--found:-- BOOLEAN, --info:-- PhoneNetFriends.PhoneNetInfo] =
    BEGIN
    FOR finger: Info ← @info, finger.next UNTIL finger = NIL DO
      IF finger.network.alive AND finger.lineNumber = lineNumber THEN
        RETURN [TRUE, finger.stats];
      ENDLOOP;
    RETURN[FALSE, ];
    END;

  StatsPtrToInfo: PUBLIC PROCEDURE [statsLevel0: LONG POINTER]
    RETURNS [clientData: LONG UNSPECIFIED, lineNumber: CARDINAL] =
    -- statsLevel0 argument is from a phonenet network object
    BEGIN
    stats: LONG POINTER TO CrapForINRLoophole = LOOPHOLE[statsLevel0];
    RETURN [stats.clientData, stats.lineNumber];
    END;

  EnumerateDriverInfo: PUBLIC PROCEDURE [current: PhoneNetFriends.NetEnumerator]
    RETURNS [PhoneNetFriends.NetEnumerator, PhoneNetFriends.PhoneNetInfo] =
    BEGIN
    hisInfo: Info ← current;
    IF (current = PhoneNetFriends.nullNetEnumerate) AND info.network.alive THEN
      RETURN[@info, info.stats];
    IF hisInfo.next = NIL THEN RETURN [PhoneNetFriends.nullNetEnumerate, info.stats];
    RETURN [hisInfo.next, hisInfo.next.stats];
    END;
    
  END....