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