-- Copyright (C) 1984, 1985  by Xerox Corporation. All rights reserved. 
-- TestPhoneLines.mesa, HGM, 11-Dec-85 20:26:27

DIRECTORY
  Environment USING [Byte],
  Inline USING [BITAND, BITOR, LowHalf],
  Process USING [Detach, GetPriority, Pause, Priority, priorityBackground, SecondsToTicks, SetPriority, Yield, Ticks],
  ProcessOperations USING [DisableInterrupts, EnableInterrupts],
  Put USING [Char, CR, Decimal, Line, LongNumber, Number, Text],
  System USING [GetClockPulses, Pulses, PulsesToMicroseconds],
  Window USING [Handle],

  CommUtil USING [AllocateBuffers, AllocateIocbs],
  DicentraInputOutput USING [
    Input, InterruptLevel, IOAddress, JumpBank, Output, SetPhonelineCSB, SetWakeupMask],
  MultibusAddresses USING [dialer0, dialer1, dialer2, modem0, scc0, scc1, scc2, scc3],
  OthelloDefs USING [
    CheckUserAbort, CommandProcessor, Confirm, IndexTooLarge,
    MyNameIs, ReadChar, RegisterCommandProc],
  PhoneCreate USING [CreatePhone];

TestPhoneLines: PROGRAM
  IMPORTS
    Inline, Process, ProcessOperations, Put, System,
    CommUtil, DicentraInputOutput, OthelloDefs, PhoneCreate =
  BEGIN
  
  wordsPerBuffer: CARDINAL = 1000;
  wordsToSend: CARDINAL = 350;
  bytesToSend: CARDINAL = 2*wordsToSend;
  bytesToRecv: CARDINAL = bytesToSend + 1;  -- Half of CRC
  recvBufferBytes: CARDINAL = 1000;
  Buffer: TYPE = LONG POINTER TO BufferRecord;
  BufferRecord: TYPE = RECORD [
    next: Buffer,
    iocb: IOCB,
    data: ARRAY [0..wordsPerBuffer) OF WORD,
    slop: ARRAY [0..10) OF WORD ];

  DtrFlapper: PROCEDURE =
    BEGIN
    normal: WORD = 0EBH; -- DTR, 8bits/char, TxE, RTS, CRC
    noDtrNoRts: WORD = 069H; -- ~DTR, 8bits/char, TxE, ~RTS, CRC
    noRts: WORD = 0E9H; -- DTR, 8bits/char, TxE, ~RTS, CRC
    noDtr: WORD = 06BH; -- ~DTR, 8bits/char, TxE, RTS, CRC
    priority: Process.Priority = Process.GetPriority[];
    pleaseStop: BOOLEAN ← FALSE;
    Watch: PROCEDURE = 
      BEGIN
      [] ← OthelloDefs.ReadChar[ ! ABORTED => CONTINUE];
      pleaseStop ← TRUE;
      END;
    Put.Line[log, "Flapping DTR and RTS..."L];
    Process.Detach[FORK Watch[]];
    Process.SetPriority[Process.priorityBackground];
    UNTIL pleaseStop DO
      Flappem[noDtrNoRts, normal];
      Flappem[noRts, normal];
      Flappem[noDtr, normal];
      Put.Char[log, '!];
      Process.Pause[Process.SecondsToTicks[3]];
      ENDLOOP;
    Process.SetPriority[priority];
    Put.Line[log, " done."L];
    END;
  
  Flappem: PROCEDURE [down, up: WORD] =
    BEGIN
    start: System.Pulses = System.GetClockPulses[];
    random: LONG CARDINAL = start MOD 10000; -- 0 to 10 ms
    FOR line: CARDINAL IN [0..lines) DO -- Very Quick flap
      chan: LONG POINTER TO Words = channels[line];
      DicentraInputOutput.Output[down, @chan.r5];
      DicentraInputOutput.Output[up, @chan.r5];
      ENDLOOP;
    FOR line: CARDINAL IN [0..lines) DO -- Quick flap
      chan: LONG POINTER TO Words = channels[line];
      DicentraInputOutput.Output[down, @chan.r5];
      Process.Yield[];
      DicentraInputOutput.Output[up, @chan.r5];
      Process.Pause[2];
      ENDLOOP;
    FOR line: CARDINAL IN [0..lines) DO -- Slighlty different Quick flap
      chan: LONG POINTER TO Words = channels[line];
      DicentraInputOutput.Output[down, @chan.r5];
      ENDLOOP;
    Process.Pause[2];
    FOR line: CARDINAL IN [0..lines) DO
      chan: LONG POINTER TO Words = channels[line];
      DicentraInputOutput.Output[up, @chan.r5];
      ENDLOOP;
    Process.Pause[Process.SecondsToTicks[1]];
    FOR line: CARDINAL IN [0..lines) DO -- Longer flap
      chan: LONG POINTER TO Words = channels[line];
      DicentraInputOutput.Output[down, @chan.r5];
      ENDLOOP;
    UNTIL System.PulsesToMicroseconds[[System.GetClockPulses[]-start]] > random DO
        Process.Yield[];
	ENDLOOP;
    FOR line: CARDINAL IN [0..lines) DO
        chan: LONG POINTER TO Words = channels[line];
        DicentraInputOutput.Output[up, @chan.r5];
        ENDLOOP;
    END;
  
  PushPackets: PROCEDURE =
    BEGIN
    buffers: ARRAY [0..lines) OF Buffer;
    pleaseStop: BOOLEAN ← FALSE;
    Watch: PROCEDURE = 
      BEGIN
      [] ← OthelloDefs.ReadChar[ ! ABORTED => CONTINUE];
      pleaseStop ← TRUE;
      END;
    Put.Line[log, "Sending packets..."L];
    Process.Detach[FORK Watch[]];
    TurnOn[];
    FOR line: CARDINAL IN [0..lines) DO
      buffers[line] ← AllocateBuffer[bytesToSend];
      QueueOutput[line, buffers[line].iocb];
      ENDLOOP;
    UNTIL pleaseStop DO
      FOR line: CARDINAL IN [0..lines) DO
        iocb: IOCB ← buffers[line].iocb;
	IF iocb.status # 0 THEN
	  BEGIN
	  Put.Char[log, 'A + line];
	  IF iocb.status = 0FFFFH THEN
	    goodSend[line] ← goodSend[line] + 1
	  ELSE
	    BEGIN
	    badSend[line] ← badSend[line] + 1;
	    Put.Char[log, '~];
	    Put.Number[log, iocb.status, [16, FALSE, TRUE, 0]];
	    END;
	  QueueOutput[line, iocb];
	  END;
        ENDLOOP;
      Process.Yield[];
      ENDLOOP;
    Put.Line[log, " done."L];
    TurnOff[];
    PrintStats[];
    FOR line: CARDINAL IN [0..lines) DO
      FreeBuffer[buffers[line]];
      ENDLOOP;
    END;
  
  TimeSending: PROCEDURE =
    BEGIN
    bufferA, bufferB: ARRAY [0..lines) OF Buffer;
    start, stop: System.Pulses;
    ms: LONG CARDINAL;
    pleaseStop: BOOLEAN ← FALSE;
    Watch: PROCEDURE = 
      BEGIN
      [] ← OthelloDefs.ReadChar[ ! ABORTED => CONTINUE];
      pleaseStop ← TRUE;
      END;
    Put.Line[log, "Sending packets..."L];
    Process.Detach[FORK Watch[]];
    TurnOn[];
    FOR line: CARDINAL IN [0..lines) DO
      bufferA[line] ← AllocateBuffer[bytesToSend];
      bufferB[line] ← AllocateBuffer[bytesToSend];
      ENDLOOP;
    start ← System.GetClockPulses[];
    FOR line: CARDINAL IN [0..lines) DO
      QueueOutput[line, bufferA[line].iocb];
      QueueOutput[line, bufferB[line].iocb];
      ENDLOOP;
    UNTIL pleaseStop DO
      FOR line: CARDINAL IN [0..lines) DO
        iocb: IOCB ← bufferA[line].iocb;
	IF iocb.status # 0 THEN
	  BEGIN
	  IF iocb.status = 0FFFFH THEN
	    BEGIN
	    goodSend[line] ← goodSend[line] + 1;
	    bytesSent[line] ← bytesSent[line] + iocb.bytes;
	    END
	  ELSE
	    badSend[line] ← badSend[line] + 1;
	  QueueOutput[line, iocb];
	  END;
        ENDLOOP;
      FOR line: CARDINAL IN [0..lines) DO
        iocb: IOCB ← bufferB[line].iocb;
	IF iocb.status # 0 THEN
	  BEGIN
	  IF iocb.status = 0FFFFH THEN
	    BEGIN
	    goodSend[line] ← goodSend[line] + 1;
	    bytesSent[line] ← bytesSent[line] + iocb.bytes;
	    END
	  ELSE
	    badSend[line] ← badSend[line] + 1;
	  QueueOutput[line, iocb];
	  END;
        ENDLOOP;
      Process.Yield[];
      ENDLOOP;
    stop ← System.GetClockPulses[];
    ms ← System.PulsesToMicroseconds[[stop-start]]/1000;
    Put.Line[log, " done."L];
    Put.Line[log, "Line   Bytes     BPS"L];
    FOR line: CARDINAL IN [0..lines) DO
      Put.Number[log, line, [10, FALSE, TRUE, 4]];
      Put.LongNumber[log, bytesSent[line], [10, FALSE, TRUE, 8]];
      Put.LongNumber[log, bytesSent[line]*8*1000/ms, [10, FALSE, TRUE, 8]];
      Put.CR[log];
      ENDLOOP;
    TurnOff[];
    PrintStats[];
    FOR line: CARDINAL IN [0..lines) DO
      FreeBuffer[bufferA[line]];
      FreeBuffer[bufferB[line]];
      ENDLOOP;
    END;
  
  PullPackets: PROCEDURE =
    BEGIN
    buffers: ARRAY [0..lines) OF Buffer;
    pleaseStop: BOOLEAN ← FALSE;
    Watch: PROCEDURE = 
      BEGIN
      [] ← OthelloDefs.ReadChar[ ! ABORTED => CONTINUE];
      pleaseStop ← TRUE;
      END;
    Put.Line[log, "Receiving packets..."L];
    Process.Detach[FORK Watch[]];
    TurnOn[];
    FOR line: CARDINAL IN [0..lines) DO
      buffers[line] ← AllocateBuffer[recvBufferBytes];
      QueueInput[line, buffers[line].iocb];
      ENDLOOP;
    UNTIL pleaseStop DO
      FOR line: CARDINAL IN [0..lines) DO
        iocb: IOCB ← buffers[line].iocb;
	IF iocb.status # 0 THEN
	  BEGIN
	  status: WORD = Inline.BITAND[iocb.status, 0FFH];
	  Put.Char[log, 'a + line];
	  IF status = 87H THEN
	    goodRecv[line] ← goodRecv[line] + 1
	  ELSE
	    BEGIN
	    badRecv[line] ← badRecv[line] + 1;
	    Put.Char[log, '~];
	    Put.Number[log, iocb.status, [16, FALSE, TRUE, 0]];
	    END;
	  QueueInput[line, iocb];
	  END;
        ENDLOOP;
      Process.Pause[1];
      ENDLOOP;
    Put.Line[log, " done."L];
    TurnOff[];
    PrintStats[];
    FOR line: CARDINAL IN [0..lines) DO
      FreeBuffer[buffers[line]];
      ENDLOOP;
    END;
  
  DoubleLoopBack: PROCEDURE =
    BEGIN
    line: Line = 0;
    pleaseStop: BOOLEAN ← FALSE;
    Watch: PROCEDURE = 
      BEGIN
      [] ← OthelloDefs.ReadChar[ ! ABORTED => CONTINUE];
      pleaseStop ← TRUE;
      END;
    sendBufferA: Buffer ← AllocateBuffer[bytesToSend];
    sendBufferB: Buffer ← AllocateBuffer[bytesToSend];
    recvBufferA: Buffer ← AllocateBuffer[recvBufferBytes];
    recvBufferB: Buffer ← AllocateBuffer[recvBufferBytes];
    recvBufferC: Buffer ← AllocateBuffer[recvBufferBytes];
    sendAgain: BOOLEAN ← FALSE;
    Put.Line[log, "Sending packets to myself (double buffered) on line 0."L];
    Process.Detach[FORK Watch[]];
    TurnOn[];
    FOR i: CARDINAL IN [0..wordsToSend) DO sendBufferA.data[i] ← i*i; ENDLOOP;
    FOR i: CARDINAL IN [0..wordsToSend) DO sendBufferB.data[i] ← i*i; ENDLOOP;
    QueueInput[line, recvBufferA.iocb];
    QueueInput[line, recvBufferB.iocb];
    QueueInput[line, recvBufferC.iocb];
    QueueOutput[line, sendBufferA.iocb];
    QueueOutput[line, sendBufferB.iocb];
    UNTIL pleaseStop DO
      IF sendBufferA.iocb.status # 0 THEN ProcessSendBuffer[sendBufferA, line];
      IF sendBufferB.iocb.status # 0 THEN ProcessSendBuffer[sendBufferB, line];
      IF recvBufferA.iocb.status # 0 THEN ProcessRecvBuffer[recvBufferA, line, line];
      IF recvBufferB.iocb.status # 0 THEN ProcessRecvBuffer[recvBufferB, line, line];
      IF recvBufferC.iocb.status # 0 THEN ProcessRecvBuffer[recvBufferC, line, line];
      Process.Yield[];
      ENDLOOP;
    Put.Line[log, " done."L];
    TurnOff[];
    PrintStats[];
    FreeBuffer[sendBufferA];
    FreeBuffer[sendBufferB];
    FreeBuffer[recvBufferA];
    FreeBuffer[recvBufferB];
    FreeBuffer[recvBufferC];
    END;
  
  LoopBack: PROCEDURE =
    BEGIN
    line: Line = 0;
    pleaseStop: BOOLEAN ← FALSE;
    under, abort, idleAborts, extAborts: CARDINAL ← 0;
    Watch: PROCEDURE = 
      BEGIN
      [] ← OthelloDefs.ReadChar[ ! ABORTED => CONTINUE];
      pleaseStop ← TRUE;
      END;
    sendBuffer: Buffer ← AllocateBuffer[bytesToSend];
    recvBuffer: Buffer ← AllocateBuffer[recvBufferBytes];
    sendAgain: BOOLEAN ← FALSE;
    Put.Line[log, "Sending packets to myself on line 0."L];
    Process.Detach[FORK Watch[]];
    TurnOn[];
    FOR i: CARDINAL IN [0..wordsToSend) DO sendBuffer.data[i] ← i*i; ENDLOOP;
    QueueInput[line, recvBuffer.iocb];
    QueueOutput[line, sendBuffer.iocb];
    UNTIL pleaseStop DO
      IF under # csb[line].tranUnderruns THEN
        BEGIN
        Put.Char[log, '#];
	under ← under + 1;
	LOOP;
	END;
      IF abort # csb[line].recvAborted THEN
        BEGIN
        Put.Char[log, '$];
	abort ← abort + 1;
	LOOP;
	END;
      IF idleAborts # csb[line].recvAbortIdle THEN
        BEGIN
        Put.Char[log, '%];
	idleAborts ← idleAborts + 1;
	LOOP;
	END;
      IF extAborts # csb[line].recvAbortEx THEN
        BEGIN
        Put.Char[log, '&];
	extAborts ← extAborts + 1;
	LOOP;
	END;
      IF sendBuffer.iocb.status # 0 THEN
	BEGIN
	IF ~sendAgain THEN  -- Give packet time to arrive (single buffer)
	  BEGIN
	  Put.Char[log, 'A + line];
	  IF sendBuffer.iocb.status # 0 THEN
	    BEGIN
	    IF sendBuffer.iocb.status = 0FFFFH THEN
	      BEGIN
	      goodSend[line] ← goodSend[line] + 1;
	      bytesSent[line] ← bytesSent[line] + sendBuffer.iocb.bytes;
	      END
	    ELSE
	      BEGIN
	      badSend[line] ← badSend[line] + 1;
	      Put.Number[log, sendBuffer.iocb.status, [16, FALSE, TRUE, 0]];
	      END;
	    END;
	  sendAgain ← TRUE;
	  Process.Pause[2];
	  END
	ELSE
	  BEGIN
	  sendAgain ← FALSE;
	  QueueOutput[line, sendBuffer.iocb];
	  END;
	END;
      IF recvBuffer.iocb.status # 0 THEN ProcessRecvBuffer[recvBuffer, line, line];
      Process.Yield[];
      ENDLOOP;
    Put.Line[log, " done."L];
    TurnOff[];
    PrintStats[];
    FreeBuffer[sendBuffer];
    FreeBuffer[recvBuffer];
    END;
  
  ProcessSendBuffer: PROCEDURE [sendBuffer: Buffer, line: Line] =
    BEGIN
    Put.Char[log, 'A + line];
    IF sendBuffer.iocb.status = 0FFFFH THEN
      BEGIN
      goodSend[line] ← goodSend[line] + 1;
      bytesSent[line] ← bytesSent[line] + sendBuffer.iocb.bytes;
      END
    ELSE
      BEGIN
      badSend[line] ← badSend[line] + 1;
      Put.Number[log, sendBuffer.iocb.status, [16, FALSE, TRUE, 0]];
      END;
    QueueOutput[line, sendBuffer.iocb];
    END;
    
  ProcessRecvBuffer: PROCEDURE [buffer: Buffer, line: Line, check: CARDINAL] =
    BEGIN
    status: WORD = Inline.BITAND[buffer.iocb.status, 0FFH];
    length: CARDINAL = buffer.iocb.bytes - buffer.iocb.bytesLeft;
    Put.Char[log, 'a + line];
    IF status = 87H THEN
      goodRecv[line] ← goodRecv[line] + 1
    ELSE
      BEGIN
      badRecv[line] ← badRecv[line] + 1;
      Put.Char[log, '~];
      Put.Number[log, buffer.iocb.status, [16, FALSE, TRUE, 0]];
      Put.Char[log, '.];
      Put.Decimal[log, length];
      Put.CR[log];
      PrintBuffer[buffer, line];
      END;
    IF status = 87H AND length # bytesToRecv THEN
      BEGIN
      Put.Char[log, '(];
      Put.Decimal[log, length];
      Put.Char[log, ')];
      PrintBuffer[buffer, line];
      END;
    IF status = 87H AND length = bytesToRecv THEN
      BEGIN
      bad: BOOLEAN ← FALSE;
      FOR i: CARDINAL IN [0..2*wordsToSend) DO
        data: LONG POINTER TO PACKED ARRAY [0..0) OF Environment.Byte = LOOPHOLE[@buffer.data];
        IF data[i] # check * 16 THEN bad ← TRUE;
        ENDLOOP;
      IF bad THEN PrintBuffer[buffer, line];
      END;
    FOR i: CARDINAL IN [0..wordsPerBuffer) DO buffer.data[i] ← 0; ENDLOOP;
    QueueInput[line, buffer.iocb];
    END;

  PrintBuffer: PROCEDURE [buffer: Buffer, line: CARDINAL] =
    BEGIN
    iocb: LONG POINTER TO ARRAY [0..0) OF WORD = LOOPHOLE[buffer.iocb];
    length: CARDINAL = buffer.iocb.bytes - buffer.iocb.bytesLeft;
    Put.CR[log];
    Put.Text[log, "Line: "L];
    Put.Number[log, line, [16, FALSE, TRUE, 0]];
    Put.Text[log, ", Status: "L];
    Put.Number[log, buffer.iocb.status, [16, FALSE, TRUE, 0]];
    Put.CR[log];
    Put.Text[log, "IOCB/ "L];
    FOR i: CARDINAL IN [0..16) DO
      Put.Number[log, iocb[i], [16, FALSE, TRUE, 6]];
      IF i = 7 THEN Put.Text[log, "\n      "L];
      ENDLOOP;
    FOR i: CARDINAL IN [0..(length+1)/2) DO
      IF (i MOD 8) = 0 THEN
        BEGIN
        Put.CR[log];
        Put.Number[log, i, [16, FALSE, TRUE, 4]];
        Put.Text[log, "/ "L];
        END;
      Put.Number[log, buffer.data[i], [16, FALSE, TRUE, 5]];
      IF buffer.data[i] # i*i THEN Put.Char[log, '←] ELSE Put.Char[log, ' ];
      ENDLOOP;
    Put.CR[log];
    END;

  Thrash: PROCEDURE =
    BEGIN
    pause: CARDINAL ← 0;
    pleaseStop: BOOLEAN ← FALSE;
    Watch: PROCEDURE = 
      BEGIN
      [] ← OthelloDefs.ReadChar[ ! ABORTED => CONTINUE];
      pleaseStop ← TRUE;
      END;
    sendBufferA: ARRAY [0..lines) OF Buffer;
    sendBufferB: ARRAY [0..lines) OF Buffer;
    recvBufferA: ARRAY [0..lines) OF Buffer;
    recvBufferB: ARRAY [0..lines) OF Buffer;
    recvBufferC: ARRAY [0..lines) OF Buffer;
    Put.Line[log, "Sending and receiving lots of packets on all lines."L];
    Process.Detach[FORK Watch[]];
    FOR line: Line IN [0..lines) DO
      sendBufferA[line] ← AllocateBuffer[500+line];
      sendBufferB[line] ← AllocateBuffer[500+line];
      recvBufferA[line] ← AllocateBuffer[recvBufferBytes];
      recvBufferB[line] ← AllocateBuffer[recvBufferBytes];
      recvBufferC[line] ← AllocateBuffer[recvBufferBytes];
      ENDLOOP;
    TurnOn[];
    FOR line: Line IN [0..lines) DO
      QueueInput[line, recvBufferA[line].iocb];
      QueueInput[line, recvBufferB[line].iocb];
      QueueInput[line, recvBufferC[line].iocb];
      ENDLOOP;
    FOR line: Line IN [0..lines) DO
      QueueOutput[line, sendBufferA[line].iocb];
      QueueOutput[line, sendBufferB[line].iocb];
      ENDLOOP;
    UNTIL pleaseStop DO
      FOR line: Line IN [0..lines) DO
	CheckOutput[line, sendBufferA[line]];
	CheckOutput[line, sendBufferB[line]];
	CheckInput[line, recvBufferA[line]];
	CheckInput[line, recvBufferB[line]];
	CheckInput[line, recvBufferC[line]];
        ENDLOOP;
      Process.Yield[];
      pause ← pause + 1;
      IF pause = 1000 THEN
        BEGIN
        Process.Pause[1];  -- Let Watchdog run
	pause ← 0;
	Put.Char[log, '!];
	END;
      ENDLOOP;
    Put.Line[log, " done."L];
    TurnOff[];
    PrintStats[];
    FOR line: Line IN [0..lines) DO
      FreeBuffer[sendBufferA[line]];
      FreeBuffer[sendBufferB[line]];
      FreeBuffer[recvBufferA[line]];
      FreeBuffer[recvBufferB[line]];
      FreeBuffer[recvBufferC[line]];
      ENDLOOP;
    END;
  
  CheckOutput: PROCEDURE [line: CARDINAL, buffer: Buffer] =
    BEGIN
    IF buffer.iocb.status = 0 THEN RETURN;
    IF buffer.iocb.status = 0FFFFH THEN
      BEGIN
      goodSend[line] ← goodSend[line] + 1;
      bytesSent[line] ← bytesSent[line] + buffer.iocb.bytes;
      END
    ELSE
      BEGIN
      badSend[line] ← badSend[line] + 1;
      Put.Char[log, 'A+line];
      Put.Number[log, buffer.iocb.status, [16, FALSE, TRUE, 0]];
      END;
    QueueOutput[line, buffer.iocb];
    END;

  CheckInput: PROCEDURE [line: CARDINAL, buffer: Buffer] =
    BEGIN
    status: WORD;
    IF buffer.iocb.status = 0 THEN RETURN;
    status ← Inline.BITAND[buffer.iocb.status, 0FFH];
    IF status = 87H THEN goodRecv[line] ← goodRecv[line] + 1
    ELSE
      BEGIN
      Put.Char[log, 'a+line];
      Put.Number[log, buffer.iocb.status, [16, FALSE, TRUE, 0]];
      badRecv[line] ← badRecv[line] + 1;
      END;
    QueueInput[line, buffer.iocb];
    END;
  
  PushPull: PROCEDURE =
    BEGIN
    line: Line = 0;
    pleaseStop: BOOLEAN ← FALSE;
    under, abort, idleAborts, extAborts: CARDINAL ← 0;
    Watch: PROCEDURE = 
      BEGIN
      [] ← OthelloDefs.ReadChar[ ! ABORTED => CONTINUE];
      pleaseStop ← TRUE;
      END;
    sendBuffer: Buffer ← AllocateBuffer[bytesToSend];
    recvBuffer: Buffer ← AllocateBuffer[recvBufferBytes];
    Put.Line[log, "Sending and receiving packets on line 0."L];
    Process.Detach[FORK Watch[]];
    TurnOn[];
    FOR i: CARDINAL IN [0..wordsToSend) DO sendBuffer.data[i] ← i*i; ENDLOOP;
    QueueInput[line, recvBuffer.iocb];
    QueueOutput[line, sendBuffer.iocb];
    UNTIL pleaseStop DO
      IF under # csb[line].tranUnderruns THEN
        BEGIN
        Put.Char[log, '#];
        under ← under + 1;
	LOOP;
	END;
      IF abort # csb[line].recvAborted THEN
        BEGIN
        Put.Char[log, '$];
	abort ← abort + 1;
	LOOP;
	END;
      IF idleAborts # csb[line].recvAbortIdle THEN
        BEGIN
        Put.Char[log, '%];
	idleAborts ← idleAborts + 1;
	LOOP;
	END;
      IF extAborts # csb[line].recvAbortEx THEN
        BEGIN
        Put.Char[log, '&];
	extAborts ← extAborts + 1;
	LOOP;
	END;
      IF sendBuffer.iocb.status # 0 THEN
	BEGIN
        Put.Char[log, 'A + line];
        IF sendBuffer.iocb.status = 0FFFFH THEN
          BEGIN
	  goodSend[line] ← goodSend[line] + 1;
	  bytesSent[line] ← bytesSent[line] + sendBuffer.iocb.bytes;
	  END
        ELSE
          BEGIN
	  badSend[line] ← badSend[line] + 1;
	  Put.Number[log, sendBuffer.iocb.status, [16, FALSE, TRUE, 0]];
	  END;
	QueueOutput[line, sendBuffer.iocb];
	END;
      IF recvBuffer.iocb.status # 0 THEN
	BEGIN
	status: WORD = Inline.BITAND[recvBuffer.iocb.status, 0FFH];
	Put.Char[log, 'a + line];
	IF status = 87H THEN goodRecv[line] ← goodRecv[line] + 1
	ELSE badRecv[line] ← badRecv[line] + 1;
	QueueInput[line, recvBuffer.iocb];
	END;
      Process.Yield[];
      ENDLOOP;
    Put.Line[log, " done."L];
    TurnOff[];
    PrintStats[];
    FreeBuffer[sendBuffer];
    FreeBuffer[recvBuffer];
    END;
  
  PrintStats: PROCEDURE =
    BEGIN
    Put.Line[log, "PhoneLine Statistics:"L];
    Put.Line[log,  "Line   Under   Abort    IAbt    EAbt  Missed    G Snd   B Snd   G Rcv   B Rcv"L];
    FOR line: Line IN [0..lines) DO
      Put.Number[log, line, [10, FALSE, TRUE, 4]];
      Put.Number[log, csb[line].tranUnderruns, [10, FALSE, TRUE, 8]];
      Put.Number[log, csb[line].recvAborted, [10, FALSE, TRUE, 8]];
      Put.Number[log, csb[line].recvAbortIdle, [10, FALSE, TRUE, 8]];
      Put.Number[log, csb[line].recvAbortEx, [10, FALSE, TRUE, 8]];
      Put.Number[log, csb[line].recvMissed, [10, FALSE, TRUE, 8]];
      Put.Number[log, goodSend[line], [10, FALSE, TRUE, 8]];
      Put.Number[log, badSend[line], [10, FALSE, TRUE, 8]];
      Put.Number[log, goodRecv[line], [10, FALSE, TRUE, 8]];
      Put.Number[log, badRecv[line], [10, FALSE, TRUE, 8]];
      Put.CR[log];
      ENDLOOP;
    Put.CR[log];
    END;
  
  commandProcessor: OthelloDefs.CommandProcessor ← [Commands];
  Commands: PROCEDURE [index: CARDINAL] =
    BEGIN
    SELECT index FROM
      0 =>
        BEGIN
        OthelloDefs.MyNameIs[
	  myNameIs: "Activate 4 phone lines"L,
	  myHelpIs: "Turn on the phone lines so you can talk to the rest of the world"L];
        OthelloDefs.Confirm[];
        PhoneCreate.CreatePhone[376B, 0, 0, NIL];
        PhoneCreate.CreatePhone[376B, 0, 1, NIL];
        PhoneCreate.CreatePhone[376B, 0, 2, NIL];
        PhoneCreate.CreatePhone[376B, 0, 3, NIL];
	END;
      1 =>
        BEGIN
        OthelloDefs.MyNameIs[
	  myNameIs: "PhoneLine Double Buffered LoopBack"L,
	  myHelpIs: "Send packets to self on line 0"L];
	DoubleLoopBack[];
	END;
      2 =>
        BEGIN
        OthelloDefs.MyNameIs[
	  myNameIs: "PhoneLine DTR Flapper"L,
	  myHelpIs: "Flap DTR and xx on all 8 lines"L];
	DtrFlapper[];
	END;
      3 =>
        BEGIN
        OthelloDefs.MyNameIs[
	  myNameIs: "PhoneLine LoopBack"L,
	  myHelpIs: "Send packets to self on line 0"L];
	LoopBack[];
	END;
      4 =>
        BEGIN
        OthelloDefs.MyNameIs[
	  myNameIs: "PhoneLine PushPull"L,
	  myHelpIs: "Send and Recv packets on line 0"L];
	PushPull[];
	END;
      5 =>
        BEGIN
        OthelloDefs.MyNameIs[
	  myNameIs: "PhoneLine Recv"L,
	  myHelpIs: "Recv packets via Phone line"L];
	PullPackets[];
	END;
      6 =>
        BEGIN
        OthelloDefs.MyNameIs[
	  myNameIs: "PhoneLine Send"L,
	  myHelpIs: "Send packets via Phone line"L];
	PushPackets[];
	END;
      7 =>
        BEGIN
        OthelloDefs.MyNameIs[
	  myNameIs: "PhoneLine Statistics"L,
	  myHelpIs: "Print PhoneLine Testing statistics"L];
	PrintStats[];
	END;
      8 =>
        BEGIN
        OthelloDefs.MyNameIs[
	  myNameIs: "PhoneLine Timing"L,
	  myHelpIs: "Time sending packets"L];
	TimeSending[];
	END;
      9 =>
        BEGIN
        OthelloDefs.MyNameIs[
	  myNameIs: "PhoneLine Thrash"L,
	  myHelpIs: "Send+Recv (double buffered) on all 8 lines"L];
	Thrash[];
	END;
      10 =>
        BEGIN
        OthelloDefs.MyNameIs[
	  myNameIs: "Test Bank Hopping"L,
	  myHelpIs: "Jump back and forth between Bank0 and Bank 1"L];
	TestBankHopping[];
	END;
      11 =>
        BEGIN
        OthelloDefs.MyNameIs[
	  myNameIs: "Test Dialers Noisy"L,
	  myHelpIs: "Test Dialer - print info on errors"L];
	TestDialers[FALSE];
	END;
      12 =>
        BEGIN
        OthelloDefs.MyNameIs[
	  myNameIs: "Test Dialers Quietly"L,
	  myHelpIs: "Test Dialer - ignore errors"L];
	TestDialers[TRUE];
	END;
      13 =>
        BEGIN
        OthelloDefs.MyNameIs[
	  myNameIs: "Test Mesa Clocks"L,
	  myHelpIs: "Compare Process Ticker with High Res Clock"L];
	TestMesaClocks[];
	END;
      ENDCASE => OthelloDefs.IndexTooLarge;
    END;
  
  TestBankHopping: PROCEDURE =
    BEGIN
    Put.Text[log, "Bank Hopping..."L];
    FOR i: CARDINAL IN [0..100) DO
      DicentraInputOutput.JumpBank[0];
      DicentraInputOutput.JumpBank[1];
      ENDLOOP;
    DicentraInputOutput.JumpBank[1];
    Put.Line[log, " done."L];
    END;
   
  TestDialers: PROCEDURE [silent: BOOLEAN] =
    BEGIN
    pleaseStop: BOOLEAN ← FALSE;
    Watch: PROCEDURE = 
      BEGIN
      [] ← OthelloDefs.ReadChar[ ! ABORTED => CONTINUE];
      pleaseStop ← TRUE;
      END;
    Put.Text[log, "Dialing..."L];
    Process.Detach[FORK Watch[]];
    InitDialers[];
    UNTIL pleaseStop DO
      FOR dialer: CARDINAL IN [0..dialers) UNTIL pleaseStop DO
        FOR i: CARDINAL IN [0..256) UNTIL pleaseStop DO
          n: WORD;
          SetDialerOutput[dialer, i];
          n ← GetDialerInput[dialer];
	  Process.Yield[];
	  IF i # n AND ~ silent THEN
	    BEGIN
	    Put.Text[log, "Dialer mixup: dialer "L];
	    Put.Number[log, dialer, [16, FALSE, TRUE, 0]];
	    Put.Text[log, ", expected = "L];
	    Put.Number[log, i, [16, FALSE, TRUE, 0]];
	    Put.Text[log, ", found = "L];
	    Put.Number[log, n, [16, FALSE, TRUE, 0]];
	    Put.CR[log];
	    END;
          ENDLOOP;
        ENDLOOP;
      Put.Char[log, '!];
      ENDLOOP;
    Put.Line[log, " done."L];
    END;
   
  TestMesaClocks: PROCEDURE =
    BEGIN
    oneSecond: Process.Ticks = Process.SecondsToTicks[1];
    start, stop, elapsed: System.Pulses;
    micro, max, min: LONG CARDINAL;
    data: ARRAY [0..1000) OF System.Pulses;
    BEGIN
    Put.Line[log, "Collecting a clump of close samples..."L];
    ProcessOperations.DisableInterrupts[];
    FOR i: CARDINAL IN [0..100) DO
      data[i] ← System.GetClockPulses[];
      ENDLOOP;
    ProcessOperations.EnableInterrupts[];
    max ← 0;
    min ← LAST[LONG CARDINAL];
    start ← data[0];
    FOR i: CARDINAL IN [1..100) DO
      stop ← data[i];
      elapsed ← LOOPHOLE[stop - start];
      min ← MIN[min, elapsed];
      max ← MAX[max, elapsed];
      Put.LongNumber[log, stop, [16, FALSE, TRUE, 10]];
      SELECT elapsed FROM
        0 => Put.Text[log, "    "L];
	< 0300H =>
	  BEGIN
	  Put.LongNumber[log, elapsed, [16, FALSE, TRUE, 4]];
	  Put.Text[log, " "L];
	  END;
	> 0FFFFFF00H =>
	  BEGIN
	  Put.LongNumber[log, -elapsed, [16, FALSE, TRUE, 4]];
	  Put.Text[log, "←"L];
	  END;
	ENDCASE => Put.Text[log, "    "L];
      OthelloDefs.CheckUserAbort[! ABORTED => EXIT];
      IF ((i+1) MOD 5) = 0 THEN Put.CR[log];
      start ← stop;
      ENDLOOP;
    Put.Text[log, "The min separation was "L];
    Put.LongNumber[log, min, [10, FALSE, TRUE, 0]];
    Put.Text[log, " ticks = "L];
    Put.LongNumber[log, System.PulsesToMicroseconds[[min]], [10, FALSE, TRUE, 0]];
    Put.Line[log, " microseconds."L];
    Put.Text[log, "The max separation was "L];
    Put.LongNumber[log, max, [10, FALSE, TRUE, 0]];
    Put.Text[log, " ticks = "L];
    Put.LongNumber[log, System.PulsesToMicroseconds[[max]], [10, FALSE, TRUE, 0]];
    Put.Line[log, " microseconds."L];
    END;
    BEGIN
    Put.CR[log];
    Put.Line[log, "Collecting a clump of 100 LOOP samples..."L];
    ProcessOperations.DisableInterrupts[];
    FOR i: CARDINAL IN [0..100) DO
      FOR x: CARDINAL IN [0..100) DO ENDLOOP;
      data[i] ← System.GetClockPulses[];
      ENDLOOP;
    ProcessOperations.EnableInterrupts[];
    max ← 0;
    min ← LAST[LONG CARDINAL];
    start ← data[0];
    FOR i: CARDINAL IN [1..100) DO
      stop ← data[i];
      elapsed ← LOOPHOLE[stop - start];
      min ← MIN[min, elapsed];
      max ← MAX[max, elapsed];
      Put.LongNumber[log, stop, [16, FALSE, TRUE, 10]];
      SELECT elapsed FROM
        0 => Put.Text[log, "    "L];
	< 0300H =>
	  BEGIN
	  Put.LongNumber[log, elapsed, [16, FALSE, TRUE, 4]];
	  Put.Text[log, " "L];
	  END;
	> 0FFFFFF00H =>
	  BEGIN
	  Put.LongNumber[log, -elapsed, [16, FALSE, TRUE, 4]];
	  Put.Text[log, "←"L];
	  END;
	ENDCASE => Put.Text[log, "    "L];
      OthelloDefs.CheckUserAbort[! ABORTED => EXIT];
      IF ((i+1) MOD 5) = 0 THEN Put.CR[log];
      start ← stop;
      ENDLOOP;
    Put.Text[log, "The min separation was "L];
    Put.LongNumber[log, min, [10, FALSE, TRUE, 0]];
    Put.Text[log, " ticks = "L];
    Put.LongNumber[log, System.PulsesToMicroseconds[[min]], [10, FALSE, TRUE, 0]];
    Put.Line[log, " microseconds."L];
    Put.Text[log, "The max separation was "L];
    Put.LongNumber[log, max, [10, FALSE, TRUE, 0]];
    Put.Text[log, " ticks = "L];
    Put.LongNumber[log, System.PulsesToMicroseconds[[max]], [10, FALSE, TRUE, 0]];
    Put.Line[log, " microseconds."L];
    END;
    BEGIN
    Put.CR[log];
    Put.Line[log, "Collecting a clump of 255 LOOP samples..."L];
    ProcessOperations.DisableInterrupts[];
    start ← System.GetClockPulses[];
    FOR i: CARDINAL IN [0..100) DO
      FOR x: CARDINAL IN [0..Inline.BITAND[255, 0FFH]) DO ENDLOOP;
      data[i] ← System.GetClockPulses[];
      ENDLOOP;
    ProcessOperations.EnableInterrupts[];
    max ← 0;
    min ← LAST[LONG CARDINAL];
    start ← data[0];
    FOR i: CARDINAL IN [1..100) DO
      stop ← data[i];
      elapsed ← LOOPHOLE[stop - start];
      min ← MIN[min, elapsed];
      max ← MAX[max, elapsed];
      Put.LongNumber[log, stop, [16, FALSE, TRUE, 10]];
      SELECT elapsed FROM
        0 => Put.Text[log, "    "L];
	< 0300H =>
	  BEGIN
	  Put.LongNumber[log, elapsed, [16, FALSE, TRUE, 4]];
	  Put.Text[log, " "L];
	  END;
	> 0FFFFFF00H =>
	  BEGIN
	  Put.LongNumber[log, -elapsed, [16, FALSE, TRUE, 4]];
	  Put.Text[log, "←"L];
	  END;
	ENDCASE => Put.Text[log, "    "L];
      OthelloDefs.CheckUserAbort[! ABORTED => EXIT];
      IF ((i+1) MOD 5) = 0 THEN Put.CR[log];
      start ← stop;
      ENDLOOP;
    Put.Text[log, "The min separation was "L];
    Put.LongNumber[log, min, [10, FALSE, TRUE, 0]];
    Put.Text[log, " ticks = "L];
    Put.LongNumber[log, System.PulsesToMicroseconds[[min]], [10, FALSE, TRUE, 0]];
    Put.Line[log, " microseconds."L];
    Put.Text[log, "The max separation was "L];
    Put.LongNumber[log, max, [10, FALSE, TRUE, 0]];
    Put.Text[log, " ticks = "L];
    Put.LongNumber[log, System.PulsesToMicroseconds[[max]], [10, FALSE, TRUE, 0]];
    Put.Line[log, " microseconds."L];
    END;
    BEGIN
    Put.CR[log];
    Put.Line[log, "Scanning for hickups..."L];
    FOR k: CARDINAL IN [0..1000) DO
      ProcessOperations.DisableInterrupts[];
      start ← System.GetClockPulses[];
      FOR i: CARDINAL IN [0..100) DO
        data[i] ← System.GetClockPulses[];
	FOR j: CARDINAL IN [0..Inline.BITAND[k, 0FFH]) DO ENDLOOP;
        ENDLOOP;
      ProcessOperations.EnableInterrupts[];
      max ← 0;
      FOR i: CARDINAL IN [0..100) DO
        stop ← data[i];
        elapsed ← LOOPHOLE[stop - start];
        max ← MAX[max, elapsed];
        IF elapsed > 200H THEN {
          Put.LongNumber[log, k, [16, FALSE, TRUE, 4]];
          Put.LongNumber[log, i, [16, FALSE, TRUE, 4]];
          Put.LongNumber[log, start, [16, FALSE, TRUE, 10]];
          Put.LongNumber[log, stop, [16, FALSE, TRUE, 10]];
          Put.LongNumber[log, elapsed, [16, FALSE, TRUE, 10]];
	  Put.CR[log]; };
        start ← stop;
        ENDLOOP;
      Put.Char[log, '!];
      Process.Pause[5]; -- WatchDog keeps attacking
      OthelloDefs.CheckUserAbort[! ABORTED => EXIT];
      ENDLOOP;
    END;
    Put.CR[log];
    Put.Line[log, "Comparing Mesa clocks (1 sec samples)..."L];
    FOR i: CARDINAL IN [0..100) DO
      Process.Pause[1];  -- Wait for clock to tick
      start ← System.GetClockPulses[];
      Process.Pause[oneSecond];
      stop ← System.GetClockPulses[];
      elapsed ← LOOPHOLE[stop - start];
      micro ← System.PulsesToMicroseconds[elapsed];
      Put.LongNumber[log, micro, [10, FALSE, TRUE, 8]];
      Put.LongNumber[log, start, [16, FALSE, TRUE, 10]];
      Put.LongNumber[log, stop, [16, FALSE, TRUE, 10]];
      Put.LongNumber[log, elapsed, [16, FALSE, TRUE, 10]];
      Put.CR[log];
      OthelloDefs.CheckUserAbort[! ABORTED => EXIT];
      ENDLOOP;
    Put.Line[log, " done."L];
    END;
   
  log: Window.Handle ← NIL;
  
  -- Utlities

  freeChain: Buffer ← NIL;  -- Pilot dies (regionCacheFull) if we do the obvious thing

  AllocateBuffer: PROCEDURE [bytes: CARDINAL] RETURNS [buffer: Buffer] =
    BEGIN
    IF freeChain = NIL THEN
      BEGIN
      buffer ← CommUtil.AllocateBuffers[SIZE[BufferRecord]];
      buffer.iocb ← CommUtil.AllocateIocbs[SIZE[IOCBlock]];
      END
    ELSE
      BEGIN
      buffer ← freeChain;
      freeChain ← buffer.next;
      END;
    buffer.next ← NIL;
    buffer.iocb↑ ← [
      next: NIL,
      status: 0,
      buffer: @buffer.data,
      bytes: bytes,
      bytesLeft: 0,
      unused: ALL[0],
      finger: NIL,
      mapped: NIL,
      spare: ALL[0] ];
    FOR i: CARDINAL IN [0..wordsPerBuffer) DO buffer.data[i] ← 0; ENDLOOP;
    END;

  FreeBuffer: PROCEDURE [buffer: Buffer] =
    BEGIN
    buffer.next ← freeChain;
    freeChain ← buffer;
    END;

  -- Driver sorts of things

  maxLines: CARDINAL = 16;
  Line: TYPE = [0..maxLines);
  
  sendTail, recvTail: ARRAY Line OF IOCB;
  
  goodSend: ARRAY Line OF CARDINAL ← ALL[0];
  bytesSent: ARRAY Line OF LONG CARDINAL ← ALL[0];
  badSend: ARRAY Line OF CARDINAL ← ALL[0];
  goodRecv: ARRAY Line OF CARDINAL ← ALL[0];
  bytesRecv: ARRAY Line OF LONG CARDINAL ← ALL[0];
  badRecv: ARRAY Line OF CARDINAL ← ALL[0];
  
  csb: LONG POINTER TO CSB = CommUtil.AllocateBuffers[SIZE[CSB]];
  CSB: TYPE = ARRAY Line OF ControlBlock;
  ControlBlock: TYPE = RECORD [
    tranState: WORD,
    tranIOCB: ShortIOCB,
    tranIOCBMapped: LONG POINTER,
    recvState: WORD,
    recvIOCB: ShortIOCB,
    recvIOCBMapped: LONG POINTER,
    tranUnderruns: CARDINAL,
    tranUnused: ARRAY [9..12) OF WORD,
    recvMissed: CARDINAL,
    recvAborted: CARDINAL,
    recvAbortIdle: CARDINAL,
    recvAbortEx: CARDINAL];
    
  IOCB: TYPE = LONG POINTER TO IOCBlock;
  ShortIOCB: TYPE = POINTER TO IOCBlock;
  
  IOCBlock: TYPE = RECORD [
    next: ShortIOCB,
    status: WORD,
    buffer: LONG POINTER,
    bytes: CARDINAL,
    bytesLeft: CARDINAL,
    unused: ARRAY [6..8) OF WORD,
    
    finger: LONG POINTER,
    mapped: LONG POINTER,
    spare: ARRAY [12..16) OF WORD ];
    
  lines: CARDINAL = 8;

  channels: ARRAY [0..lines) OF LONG POINTER TO Words = [
    LOOPHOLE[MultibusAddresses.scc0 + 10H],  -- Chan A
    LOOPHOLE[MultibusAddresses.scc0 + 00H],  -- Chan B
    LOOPHOLE[MultibusAddresses.scc1 + 10H],
    LOOPHOLE[MultibusAddresses.scc1 + 00H],
    LOOPHOLE[MultibusAddresses.scc2 + 10H],
    LOOPHOLE[MultibusAddresses.scc2 + 00H],
    LOOPHOLE[MultibusAddresses.scc3 + 10H],
    LOOPHOLE[MultibusAddresses.scc3 + 00H]];
    
  interruptVect: ARRAY [0..lines) OF WORD = [0, 0, 10H, 10H, 20H, 20H, 30H, 30H];
  baudRateNumber: ARRAY [0..lines) OF WORD = [014H, 014H, 077H, 077H, 077H, 077H, 077H, 077H];
  
  dialers: CARDINAL = 3;

  dialerAddrs: ARRAY [0..dialers) OF LONG POINTER TO Words = [
    LOOPHOLE[MultibusAddresses.dialer0],
    LOOPHOLE[MultibusAddresses.dialer1],
    LOOPHOLE[MultibusAddresses.dialer2]];
    
  Words: TYPE = RECORD [
    r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15: WORD];

  SetupTestClocks: PROCEDURE =
    BEGIN
    SetupTestClock[MultibusAddresses.modem0, 00BH];  -- LCa for first 2 lines at 56KB
    SetupTestClock[MultibusAddresses.dialer0, 041H];  -- LCb for next 2 lines at 9600
    SetupTestClock[MultibusAddresses.dialer1, 041H];  -- LCc for next 2 lines at 9600
    SetupTestClock[MultibusAddresses.dialer2, 041H];  -- LCd for next 2 lines at 9600
    END;

  SetupTestClock: PROCEDURE [chip: DicentraInputOutput.IOAddress, ticks: CARDINAL] =
    BEGIN
    DicentraInputOutput.Output[001H, chip + 000H];  -- Master Interrupt Control ← Reset
    DicentraInputOutput.Output[000H, chip + 000H];  -- Master Interrupt Control ← No Reset
    DicentraInputOutput.Output[094H, chip + 001H];  -- Master Config Control ← Enable Ports and Cnt3
    DicentraInputOutput.Output[000H, chip + 01AH];  -- Cnt3 MSB
    DicentraInputOutput.Output[ticks, chip + 01BH];  -- Cnt3 LSB
    DicentraInputOutput.Output[0C2H, chip + 01EH];  -- Cnt3 Mode ← Sq Out
    DicentraInputOutput.Output[006H, chip + 00CH];  -- Cnt3 Command ← Go
    END;


  InitChip: PROCEDURE [line: Line] =
    BEGIN
    chan: LONG POINTER TO Words = channels[line];
    chanB: LONG POINTER TO Words = channels[Inline.BITOR[line, 1]];
    DicentraInputOutput.Output[002H, @chanB.r0];  -- Shift Left (ADR0 is ignored)
    DicentraInputOutput.Output[0C9H, @chan.r9];  -- Hardware Reset, MIE, V, VIS
    END;

  InitLine: PROCEDURE [line: Line] =
    BEGIN
    chan: LONG POINTER TO Words = channels[line];
    DicentraInputOutput.Output[interruptVect[line], @chan.r2];  -- Int Vector Base
    DicentraInputOutput.Output[020H, @chan.r4];  -- SDLC
    DicentraInputOutput.Output[013H, @chan.r1];  -- Rx All Int, Tx Int En, Ext Int En
    DicentraInputOutput.Output[0D9H, @chan.r3];  -- 8bits/char, Hunt, CRC, RxE
    DicentraInputOutput.Output[0EBH, @chan.r5];  -- DTR, 8bits/char, TxE, RTS, CRC
    DicentraInputOutput.Output[07EH, @chan.r7];  -- Flag Char
    DicentraInputOutput.Output[084H, @chan.r10];  -- CRC←1, Abort on Underrun
    DicentraInputOutput.Output[080H, @chan.r15];  -- Enable Ext Int on Abort
    DicentraInputOutput.Output[070H, @chan.r0];  -- Reset Rx CRC, Error Reset
    DicentraInputOutput.Output[090H, @chan.r0];  -- Reset Tx CRC, Reset Ext/Sts Int
    DicentraInputOutput.Output[008H, @chan.r11];  -- External Clocks
    IF FALSE THEN
      BEGIN
      DicentraInputOutput.Output[baudRateNumber[line], @chan.r12];  -- Low byte of time constant
      DicentraInputOutput.Output[000H, @chan.r13];  -- High byte of time constant
      DicentraInputOutput.Output[003H, @chan.r14];  -- Baud Rate Gen from PClk
      DicentraInputOutput.Output[050H, @chan.r11];  -- Clocks from BR Gen
      END;
    END;

  InitDialers: PROCEDURE =
    BEGIN
    SetupTestClocks[];
    FOR dialer: CARDINAL IN [0..dialers) DO
      InitDialer[dialerAddrs[dialer]];
      ENDLOOP;
    END;

  InitDialer: PROCEDURE [chip: DicentraInputOutput.IOAddress] =
    BEGIN
    DicentraInputOutput.Output[000H, chip + 023H];  -- Port A Direction, All Out
    DicentraInputOutput.Output[0FFH, chip + 02BH];  -- Port B Direction, All In
    END;

  SetDialerOutput: PROCEDURE [dialer: CARDINAL, data: WORD] =
    BEGIN
    chip: DicentraInputOutput.IOAddress = dialerAddrs[dialer];
    DicentraInputOutput.Output[data, chip+00DH]; -- Port A
    END;

  GetDialerInput: PROCEDURE [dialer: CARDINAL] RETURNS [data: WORD] =
    BEGIN
    chip: DicentraInputOutput.IOAddress = dialerAddrs[dialer];
    data ← DicentraInputOutput.Input[chip+00EH]; -- Port B
    END;

  TurnOn: PROCEDURE =
    BEGIN
    SetupTestClocks[];
    goodSend ← ALL[0];
    bytesSent ← ALL[0];
    badSend ← ALL[0];
    goodRecv  ← ALL[0];
    bytesRecv  ← ALL[0];
    badRecv ← ALL[0];
    csb↑ ← ALL[ [
        tranState: 0,
        tranIOCB: NIL,
        tranIOCBMapped: NIL,
        recvState: 0,
        recvIOCB: NIL,
        recvIOCBMapped: NIL,
        tranUnderruns: 0,
        tranUnused: ALL[0],
        recvMissed: 0,
        recvAborted: 0,
        recvAbortIdle: 0,
        recvAbortEx: 0 ] ];
    FOR line: Line IN [0..lines) DO
      InitChip[line];
      ENDLOOP;
    FOR line: Line IN [0..lines) DO
      InitLine[line];
      ENDLOOP;
    DicentraInputOutput.SetWakeupMask[0, phoneLine];
    DicentraInputOutput.SetPhonelineCSB[csb];
    END;
  
  TurnOff: PROCEDURE =
    BEGIN
    DicentraInputOutput.SetPhonelineCSB[NIL];
    END;

  QueueOutput: PROCEDURE [line: Line, iocb: IOCB] =
    BEGIN
    chan: LONG POINTER TO Words = channels[line];
    bytes: LONG POINTER TO PACKED ARRAY [0..0) OF Environment.Byte = iocb.buffer;
    iocb.status ← 0;
    iocb.next ← NIL;
    IF csb[line].tranIOCB = NIL THEN
      BEGIN
      temp: WORD ← csb[line].tranState;
      IF temp # 0 THEN
        BEGIN
	Put.CR[log];
	Put.Text[log, "Unexpected tran state = "L];
	Put.Number[log, temp, [16, FALSE, TRUE, 0]];
	Put.CR[log];
	END;
      csb[line].tranIOCB ← Inline.LowHalf[iocb];
      DicentraInputOutput.Output[080H, @chan.r0];  -- Reset Tx CRC
      DicentraInputOutput.Output[bytes[0], @chan.r8];  -- Send first data byte
      END
    ELSE
      BEGIN
      sendTail[line].next ← Inline.LowHalf[iocb];
      IF csb[line].tranIOCB = NIL AND iocb.status = 0 THEN  -- Rats, lost race
        BEGIN
        csb[line].tranIOCB ← Inline.LowHalf[iocb];
        DicentraInputOutput.Output[080H, @chan.r0];  -- Reset Tx CRC
        DicentraInputOutput.Output[bytes[0], @chan.r8];  -- Send first data byte
        END;
      END;
    sendTail[line] ← iocb;
    END;

  QueueInput: PROCEDURE [line: Line, iocb: IOCB] =
    BEGIN
    iocb.status ← 0;
    iocb.next ← NIL;
    IF csb[line].recvIOCB = NIL THEN
      BEGIN
      csb[line].recvIOCB ← Inline.LowHalf[iocb]
      END
    ELSE
      BEGIN
      recvTail[line].next ← Inline.LowHalf[iocb];
      END;
    recvTail[line] ← iocb;
    END;

  -- Main Body
  OthelloDefs.RegisterCommandProc[@commandProcessor];
  END.