-- ScannerDImpl.mesa
-- Last edited by Tim Diebert:           17-Dec-85 12:26:00
-- Copyright (C) 1985, Xerox Corporation.  All rights reserved.

-- This module is based on EikonixProtocol.mesa of 8-Apr-85  9:25:34.
-- If it is necessary to make changes to SampleLength this code must be changed!

DIRECTORY
  DicentraInputOutput USING [IOAddress, Output, RawRead, RawWrite, ReadHyper],
  EikonixProtocol,
  Environment USING [Block, LongNumber],
  Inline USING [BITAND, BITROTATE, BITSHIFT, DBITAND, DBITSHIFT, HighByte],
  Multibus USING [RawReadBlock, wordsPerBlock],
  MultibusAddresses USING [scc3],
  Process USING [Pause, SecondsToTicks --, Yield -- ],
  PupStream USING [CloseReason, CreatePupByteStreamListener, PupAddress,
    PupListener, RejectThisRequest, SecondsToTocks, StreamClosing],
  Space USING [PagesFromWords, ScratchMap],
  Stream USING [Delete, GetChar, GetWord, Handle, PutBlock, PutChar, PutWord,
    SendNow, TimeOut] -- ,
  -- Watchdog USING [Reactivate]
  ;

ScannerDImpl: PROGRAM
  IMPORTS DicentraInputOutput, Inline, Multibus, Space, Stream, Process, PupStream --, Watchdog
  EXPORTS 
  = BEGIN OPEN EikonixProtocol;
  
  listener: PupStream.PupListener ← NIL;
  active: BOOL ← FALSE;
  
  Buffer: TYPE = RECORD
    [blockPointer: LONG POINTER TO ARRAY [0 .. EikonixProtocol.Width) OF WORD,
    startIndex: CARDINAL ← 0,
    stopIndexPlusOne: CARDINAL ← EikonixProtocol.Width * 2];
  bufferA: Buffer;
  bufferB: Buffer;
  dc: Buffer;
  gain: Buffer;
  longCardBuffer: LONG POINTER TO ARRAY [0 .. EikonixProtocol.Width) OF LONG CARDINAL;
    
  MyAbort: ERROR = CODE;
  Init: PROC [] = BEGIN
    cmd: EikonixCommand ← [reg: Stage, bits: 80H];
    MBWrite[deviceCommandRegister, LOOPHOLE [cmd]];
    active ← FALSE;
    longCardBuffer ← New[EikonixProtocol.Width * SIZE[LONG CARDINAL]];
    bufferA.blockPointer ← New[EikonixProtocol.Width];
    bufferB.blockPointer ← New[EikonixProtocol.Width];
    bufferA.blockPointer↑ ← ALL[0];
    bufferA.startIndex ← 0;
    bufferA.stopIndexPlusOne ← EikonixProtocol.Width * 2;
    bufferB.blockPointer↑ ← ALL[0];
    bufferB.startIndex ← 0;
    bufferB.stopIndexPlusOne ← EikonixProtocol.Width * 2;
    dc.blockPointer ← New[EikonixProtocol.Width];
    dc.blockPointer↑ ← ALL[0];
    dc.startIndex ← 0;
    dc.stopIndexPlusOne ← EikonixProtocol.Width * 2;
    gain.blockPointer ← New[EikonixProtocol.Width];
    gain.blockPointer↑ ← ALL[08000H];  -- Really 800H shifted 4 left.
    gain.startIndex ← 0;
    gain.stopIndexPlusOne ← EikonixProtocol.Width * 2;
    LoadIt[];
    MBWrite[functionControlRegister, 0100h];
    IF listener = NIL THEN listener ← PupStream.CreatePupByteStreamListener[
      local: EikonixProtocol.scannerSocket, 
      proc: ScannerCommand,
      ticks: PupStream.SecondsToTocks[30*60],
      filter: CheckScannerBusy];
    END;
  
  ScannerCommand: PROC [stream: Stream.Handle, pupAddress: PupStream.PupAddress] = BEGIN
    command: EikonixProtocol.Command;
    BEGIN
      ENABLE {PupStream.StreamClosing, Stream.TimeOut, MyAbort, ABORTED => GOTO Exit};
      active ← TRUE;
      DO
        command ← GetCommand[stream];
        SELECT command FROM
          SetCommandReg => SetCommandReg[stream];
          SetDeviceCommandReg => SetDeviceCommandReg[stream];
          GetStatus => GetStatus[stream];
          GetDeviceStatus => GetDeviceStatus[stream];
          SetFCR => SetFCR[stream];
          SetDMAData => SetDMAData[stream];
          SendDataBuffer0 => SendDataBuffer[stream, 0];
          SendDataBuffer1 => SendDataBuffer[stream, 1];
          TurnOnLights => TurnOnLights[stream];
          TurnOffLights => TurnOffLights[stream];
          SetFilter => SetFilter[stream];
          ScanPage => ScanPage[stream];
          SampleScan => SampleScan[stream];
          LoadNormalizer => LoadNormalizer[stream];
	  ScanMaxMin => ScanMaxMin[stream];
	  ComputeCal => ComputeCal[stream];
          ENDCASE => { SendError[stream]; GOTO Exit};
        Stream.SendNow[stream];
        ENDLOOP;
      END;
      EXITS Exit => { Stream.Delete[stream]; KillLights[]; active ← FALSE; RETURN; };
    END;
  
  GetCommand: PROC [stream: Stream.Handle] RETURNS [cmd: EikonixProtocol.Command] = BEGIN
    [] ← Stream.GetChar[stream];
    cmd ← LOOPHOLE[Stream.GetChar[stream]];
    RETURN[cmd];
    END;
  
  SetCommandReg: PROC [stream: Stream.Handle] =  BEGIN
    word: WORD ← LOOPHOLE [Stream.GetWord[stream]];
    MBWrite[commandRegister, word];
    EchoCommand[stream, SetCommandReg];
    END;
    
  SetDeviceCommandReg: PROC [stream: Stream.Handle] =  BEGIN
    word: WORD ← LOOPHOLE [Stream.GetWord[stream]];
    MBWrite[deviceCommandRegister, word];
    EchoCommand[stream, SetDeviceCommandReg];
    END;
 
  GetStatus: PROC [stream: Stream.Handle] =  BEGIN
    word: WORD ← MBRead[statusRegister];
    EchoCommand[stream, GetStatus];
    Stream.PutWord[stream, word];
    END;
 
  GetDeviceStatus: PROC [stream: Stream.Handle] =  BEGIN
    word: WORD ← MBRead[deviceCommandRegister];
    EchoCommand[stream, GetDeviceStatus];
    Stream.PutWord[stream, word];
    END;
    
  SetFCR: PROC [stream: Stream.Handle] =  BEGIN
    word: WORD ← LOOPHOLE [Stream.GetWord[stream]];
    MBWrite[functionControlRegister, word];
    EchoCommand[stream, SetFCR];
    END;
    
  SetDMAData: PROC [stream: Stream.Handle] =  BEGIN
    word: WORD ← LOOPHOLE [Stream.GetWord[stream]];
    MBWrite[dataRegister, word];
    EchoCommand[stream, SetDMAData];
    END;
    
  SendDataBuffer: PROC [stream: Stream.Handle, buf: CARDINAL] =  BEGIN
    a: Address ← IF buf = 0 THEN dataBuffer0 ELSE dataBuffer1;
    EchoCommand[stream, (IF buf = 0 THEN SendDataBuffer0 ELSE SendDataBuffer1)];
    DicentraInputOutput.ReadHyper
      [to: @bufferA.blockPointer↑, from: LOOPHOLE[a], words: Multibus.wordsPerBlock];
    bufferA.startIndex ← 0;
    bufferA.stopIndexPlusOne ← Multibus.wordsPerBlock * 2;
    Stream.PutBlock[stream, LOOPHOLE[bufferA]];
    END;
    
  TurnOnLights: PROC [stream: Stream.Handle] =  BEGIN
    DicentraInputOutput.Output[2, LOOPHOLE [MultibusAddresses.scc3 + 5B]]; -- RTS on port 7.
    Process.Pause[Process.SecondsToTicks[5]];  -- Wait for the lights to come up.
    EchoCommand[stream, TurnOnLights];
    END;
  
  TurnOffLights: PROC [stream: Stream.Handle] =  BEGIN
    KillLights[];
    EchoCommand[stream, TurnOffLights];
    END;

  ScanPage: PROC [stream: Stream.Handle] =  BEGIN
    useA: BOOL ← FALSE;
    first: BOOL ← TRUE;
    sendBuffer: Address ← dataBuffer0; scanBuffer: Address ← dataBuffer1;
       -- tempBuffer: Address;
    scanStart: CARDINAL ← Stream.GetWord[stream];
    pixelStart: CARDINAL ← Stream.GetWord[stream];
    numScans: CARDINAL ← Stream.GetWord[stream];
    numPixel: CARDINAL ← Stream.GetWord[stream];
    reps: CARDINAL ← Inline.BITAND[Stream.GetWord[stream], 17B];
    bit8: BOOL ← (Stream.GetWord[stream] = 0);
    times: CARDINAL ← 1;
    cmd: EikonixCommand ← [reg: Stage, bits: 80H];
    MBWrite[deviceCommandRegister, LOOPHOLE [cmd]];
    FOR i: CARDINAL IN [1 .. reps] DO times ← times * 2; ENDLOOP;
    IF scanStart + numScans > EikonixProtocol.Height THEN SendError[stream];
    IF pixelStart + numPixel > EikonixProtocol.Width THEN SendError[stream];
    EchoCommand[stream, ScanPage];
    Stream.PutWord[stream, numScans];
    Stream.PutWord[stream, numPixel];
    
    SetStagePosition[scanStart];
    BusyWait[];
  --  StartScan[scanBuffer, FALSE, bit8];
  --  DataWait[];
    IF times = 1
      THEN BEGIN
        FOR i: CARDINAL IN [scanStart .. scanStart + numScans) DO
  --        p: PROCESS;
  --        IF NOT first THEN
  --          p ← FORK SendMBBuffer[stream, pixelStart, numPixel, sendBuffer, bit8];
  --        StartScan[scanBuffer, TRUE, bit8];
          SetStagePosition[i]; 
	  BusyWait[];
          StartScan[scanBuffer, FALSE, bit8];
          DataWait[];
  --        SetStagePosition[i];
  --        IF NOT first THEN JOIN p;
  --        BusyWait[];
          SendMBBuffer[stream, pixelStart, numPixel, scanBuffer, bit8];
  --        first ← FALSE;
  --        tempBuffer ← sendBuffer; sendBuffer ← scanBuffer; scanBuffer ← tempBuffer;
          ENDLOOP;
  --      SendMBBuffer[stream, pixelStart, numPixel, sendBuffer, bit8];
        END
      ELSE BEGIN
        IF bit8 THEN SendError[stream];
	FOR i: CARDINAL IN [scanStart .. scanStart + numScans) DO
          p: PROCESS;
          IF NOT first THEN
            p ← FORK SendBuffer[stream, pixelStart, numPixel,
            (IF useA THEN bufferA ELSE bufferB)];
          -- Scan into ~ useA buffer
          FOR k: CARDINAL IN [0 .. times) DO
            StartScan[dataBuffer0, k = (times - 1), FALSE];
--	    Watchdog.Reactivate[3*60];
            BusyWait[];
            SumScan[ dataBuffer0, (IF ~ useA THEN bufferA ELSE bufferB)];
            ENDLOOP;
          IF NOT first THEN JOIN p;
          first ← FALSE;
          useA ← ~ useA;
          ENDLOOP;
        SendBuffer[stream, pixelStart, numPixel, (IF useA THEN bufferA ELSE bufferB)];
        END;
      END;
  
  SampleScan: PROC [stream: Stream.Handle] =  BEGIN
    sendBuffer: Address ← dataBuffer0; scanBuffer: Address ← dataBuffer1;
    tempBuffer: Address;
    cnt: CARDINAL = EikonixProtocol.Height/SampleLength;
    first: BOOL ← TRUE;
    bit8: BOOL ← Stream.GetWord[stream] = 0;
    EchoCommand[stream, SampleScan];
    SetStagePosition[0];
    BusyWait[];
    StartScan[scanBuffer, FALSE, bit8];
    DataWait[];
    FOR i: CARDINAL IN [0 .. cnt) DO
      p: PROCESS;
      IF NOT first THEN
        p ← FORK SendSampledBuffer[stream, sendBuffer, bit8];
      -- Scan into scanBuffer buffer
      StartScan[scanBuffer, FALSE, bit8];
      DataWait[];
      SetStagePosition[Inline.BITSHIFT[i, 4]];
      BusyWait[];
      IF NOT first THEN JOIN p;
      first ← FALSE;
      tempBuffer ← sendBuffer; sendBuffer ← scanBuffer; scanBuffer ← tempBuffer;
      ENDLOOP;
    SendSampledBuffer[stream, sendBuffer, bit8];
    END;
  
  LoadNormalizer: PROC [stream: Stream.Handle] = BEGIN
    FOR i: CARDINAL IN [0 .. EikonixProtocol.Width) DO
      dc.blockPointer[i] ← Stream.GetWord[stream];
      ENDLOOP;
    FOR i: CARDINAL IN [0 .. EikonixProtocol.Width) DO
      gain.blockPointer[i] ← Stream.GetWord[stream];
      ENDLOOP;
    LoadIt[];
    EchoCommand[stream, LoadNormalizer];
    END;
  
  LoadIt: PROC [] = BEGIN
    BusyWait[];
    MBWrite[ functionControlRegister, toDarkCurrent];
    FOR i: CARDINAL IN [0 .. EikonixProtocol.Width) DO
      BusyWait[];
      MBWrite[dataRegister, dc.blockPointer[i]];
      ENDLOOP;
    BusyWait[];
    MBWrite[functionControlRegister, toGain];
    FOR i: CARDINAL IN [0 .. EikonixProtocol.Width) DO
      BusyWait[];
      MBWrite[dataRegister, gain.blockPointer[i]];
      ENDLOOP;
    END;

  ScanMaxMin: PROC [stream: Stream.Handle] = BEGIN
    useA: BOOL ← FALSE;
    first: BOOL ← TRUE;
    sendBuffer: Address ← dataBuffer0; scanBuffer: Address ← dataBuffer1; tempBuffer: Address;
    minScan, minPixel: CARDINAL ← 0;
    maxScan, maxPixel: CARDINAL ← 0;

    scanStart: CARDINAL ← Stream.GetWord[stream];
    pixelStart: CARDINAL ← Stream.GetWord[stream];
    numScans: CARDINAL ← Stream.GetWord[stream];
    numPixel: CARDINAL ← Stream.GetWord[stream];
    min: CARDINAL ← 8000H;
    max: CARDINAL ← 0;
    FindMinMax: PROC [b: Address, i: CARDINAL] = BEGIN
      FOR j: CARDINAL IN [pixelStart .. pixelStart + numPixel) DO
        w: CARDINAL ← Inline.BITAND[MBRead[b + j], 0FFFH];
	IF w < min THEN BEGIN
	  min ← w; minScan ← i; minPixel ← j;
	  END;
	IF w > max THEN BEGIN
	  max ← w; maxScan ← i; maxPixel ← j;
	  END;
        ENDLOOP;
      END;
    IF scanStart + numScans > EikonixProtocol.Height THEN SendError[stream];
    IF pixelStart + numPixel > EikonixProtocol.Width THEN SendError[stream];
    EchoCommand[stream, ScanMaxMin];
    SetStagePosition[scanStart];
    BusyWait[];
    FOR i: CARDINAL IN [scanStart .. scanStart + numScans) DO
      p: PROCESS;
      IF NOT first THEN
        p ← FORK FindMinMax[sendBuffer, i - 1];
      StartScan[scanBuffer, TRUE, FALSE];
      DataWait[];
      IF NOT first THEN JOIN p;
      first ← FALSE;
      tempBuffer ← sendBuffer; sendBuffer ← scanBuffer; scanBuffer ← tempBuffer;
--      Watchdog.Reactivate[3*60];
      ENDLOOP;
    FindMinMax[sendBuffer, scanStart + numScans - 1];
    Stream.PutWord[stream, min];
    Stream.PutWord[stream, minScan];
    Stream.PutWord[stream, minPixel];
    Stream.PutWord[stream, max];
    Stream.PutWord[stream, maxScan];
    Stream.PutWord[stream, maxPixel];
    Stream.SendNow[stream];
    END;

  ComputeCal: PROC [stream: Stream.Handle] = BEGIN
    first: BOOL ← TRUE;
    sendBuffer: Address ← dataBuffer0; scanBuffer: Address ← dataBuffer1;
--    tempBuffer: Address;
    longCardBuffer↑ ← ALL [0];
    SetStagePosition[512];
    BusyWait[];
    FOR i: CARDINAL IN [512 .. 1536) DO
--      p: PROCESS;
--      IF NOT first THEN
--        p ← FORK SumLongCard[sendBuffer];
--      StartScan[scanBuffer, TRUE, FALSE];
      StartScan[scanBuffer, FALSE, FALSE];
      DataWait[];
      SumLongCard[scanBuffer];
      SetStagePosition[i];
      BusyWait[];
--      IF NOT first THEN JOIN p;
      first ← FALSE;
--      tempBuffer ← sendBuffer; sendBuffer ← scanBuffer; scanBuffer ← tempBuffer;
--      Watchdog.Reactivate[3*60];
      IF Inline.BITAND[i, 17B] = 0 THEN Process.Pause[1];
      ENDLOOP;
--    SumLongCard[sendBuffer];
    DivideLongCardBuffer[bufferA];
    EchoCommand[stream, ComputeCal];
    SendBuffer[stream, 0, EikonixProtocol.Width, bufferA];
    END;
    
  cSelectClearFilter: 	CARDINAL = 8108H;
  cSelectRedFilter: 	CARDINAL = 8208H;
  cSelectGreenFilter: 	CARDINAL = 8308H;
  cSelectBlueFilter: 	CARDINAL = 8408H;
  cSelectOpaqueFilter:  CARDINAL = 8508H;
  
  SetFilter: PROC [stream: Stream.Handle] = BEGIN
    color: Color ← LOOPHOLE [Stream.GetWord[stream]];
    c: CARDINAL ← SELECT color FROM
      Red => cSelectRedFilter,
      Green => cSelectGreenFilter,
      Blue => cSelectBlueFilter,
      Opaque => cSelectOpaqueFilter,
      ENDCASE => cSelectClearFilter;
    BusyWait[];
    MBWrite[deviceCommandRegister, c];
    BusyWait[];
    EchoCommand[stream, SetFilter];
    END;
      
  SendSampledBuffer: PROC
    [stream: Stream.Handle, buffer: Address, bit8: BOOL] =  BEGIN
    w: WORD;
    cnt: CARDINAL = EikonixProtocol.Height/SampleLength;
    FOR j: CARDINAL IN [0 .. cnt) DO
      IF bit8
        THEN BEGIN
	  w ← MBRead[buffer + Inline.BITSHIFT[j, 3]];
	  Stream.PutChar[stream, Inline.HighByte[w]];
	  END
	ELSE BEGIN
	  w ← MBRead[buffer + Inline.BITSHIFT[j, 4]];
	  Stream.PutWord[stream, w];
	  END;
      ENDLOOP;
    Stream.SendNow[stream];
    END;
        
  SendMBBuffer: PROC
    [stream: Stream.Handle, start, count: CARDINAL, ib: Address, bit8: BOOL] = BEGIN
    bufferA.blockPointer↑ ← LOOPHOLE[Multibus.RawReadBlock[LOOPHOLE[ib]]];
--  DicentraInputOutput.ReadHyper
--    [to: @bufferA.blockPointer↑, from: LOOPHOLE[ib], words: Multibus.wordsPerBlock];
    bufferA.startIndex ← start * (IF bit8 THEN 1 ELSE 2);
    bufferA.stopIndexPlusOne ← (start + count) * (IF bit8 THEN 1 ELSE 2);
    Stream.PutBlock[stream, LOOPHOLE[bufferA]];
    Stream.SendNow[stream];
    END;

 SendBuffer: PROC
    [stream: Stream.Handle, start, count: CARDINAL, buffer: Buffer] = BEGIN
    buffer.startIndex ← start * 2;
    buffer.stopIndexPlusOne ← (start + count) * 2;
    Stream.PutBlock[stream, LOOPHOLE[buffer]];
    Stream.SendNow[stream];
    buffer.blockPointer↑ ← ALL [0];
    END;
    
  SumScan: PROC [scanBuffer: Address, buffer: Buffer] = BEGIN
    FOR i: CARDINAL IN [0 .. EikonixProtocol.Width) DO
      buffer.blockPointer[i] ← buffer.blockPointer[i] +
        Inline.BITSHIFT[ReadEikonixBuffer[scanBuffer, i], -4];
      ENDLOOP;
    END;
  
  SumLongCard: PROC [scanBuffer: Address] = BEGIN
    FOR i: CARDINAL IN [0 .. EikonixProtocol.Width) DO
      longCardBuffer[i] ← longCardBuffer[i] + ReadEikonixBuffer[scanBuffer, i];
      ENDLOOP;
    END;
  
  DivideLongCardBuffer: PROC [buffer: Buffer] = BEGIN OPEN Inline;
    num: Environment.LongNumber;
    FOR i: CARDINAL IN [0 .. EikonixProtocol.Width) DO
      num ← LOOPHOLE[DBITAND[DBITSHIFT[longCardBuffer[i], - 10], 0FFFH]];
      buffer.blockPointer[i] ← LOOPHOLE[num.low];
      ENDLOOP;
    END;
    
  SetStagePosition: PROC [pos: CARDINAL] = BEGIN
    cmd: EikonixCommand;
    cmd.reg ← StagePosition;
    cmd.bits ← pos;
    MBWrite[deviceCommandRegister, LOOPHOLE [cmd]];
    END;
    
  StartScan: PROC [scanBuffer: Address, step: BOOL, bit8: BOOL] = BEGIN
    funct: EikonixFunctionControl;
    IF ~ step AND ~ bit8 THEN funct.funct ← Data12Bit;
    IF ~ step AND bit8 THEN funct.funct ← Data8Bit;
    IF step AND ~ bit8 THEN funct.funct ← Data12Step;
    IF step AND bit8 THEN funct.funct ← Data8Step;
    funct.direction ← 1;
    funct.buffer ← IF scanBuffer = dataBuffer0 THEN 0 ELSE 1;
    funct.go ← 1;
    MBWrite[functionControlRegister, LOOPHOLE [funct]];
    END;
    
  ReadEikonixBuffer: PROC
    [bufferAddress: Address, pixel: CARDINAL] RETURNS [CARDINAL] = INLINE BEGIN
    bufferAddress ← Inline.BITAND[pixel, 3777B] + bufferAddress;
    RETURN [MBRead[bufferAddress]];
    END;
    
  SendError: PROC [stream: Stream.Handle] =  BEGIN
    ERROR MyAbort;
    END;
  
  GetAddress: PROC [stream: Stream.Handle] RETURNS [EikonixProtocol.Address] = BEGIN
    md: MACHINE DEPENDENT RECORD [ a, b, c, d: CHAR ];
    md.a ← Stream.GetChar[stream];
    md.b ← Stream.GetChar[stream];
    md.c ← Stream.GetChar[stream];
    md.d ← Stream.GetChar[stream];
    RETURN [LOOPHOLE [md]];
    END;
  
  PutByteSwapWord: PROC [stream: Stream.Handle, word: WORD] = INLINE BEGIN
    Stream.PutWord[stream, ByteSwap[word]];
    RETURN;
    END;
    
  ByteSwap: PROC [in: CARDINAL] RETURNS [CARDINAL] = INLINE BEGIN
    RETURN [LOOPHOLE[Inline.BITROTATE[in, 8]]];
    END;
    
  MBWrite: PROC [reg: Address, data: WORD] = INLINE BEGIN
    data ← ByteSwap[data];
    DicentraInputOutput.RawWrite[data, LOOPHOLE [reg]];
    END;
  
  MBRead: PROC [reg: Address] RETURNS [WORD] =  INLINE BEGIN
    RETURN [ByteSwap[DicentraInputOutput.RawRead[LOOPHOLE [reg]]]];
    END;
    
  BusyWait: PROC [] =  BEGIN
    UNTIL (Inline.BITAND[MBRead[statusRegister], busyBit]) = 0 DO
--      Process.Yield[];
      ENDLOOP;
    END;
    
  goBit: WORD = 1B;
  DataWait: PROC [] = BEGIN
     UNTIL (Inline.BITAND[MBRead[functionControlRegister], goBit]) = 0 DO
--       Process.Yield[];
       ENDLOOP;
     END;

  EchoCommand: PROC [stream: Stream.Handle, cmd: EikonixProtocol.Command] = BEGIN
    stream.PutChar[LOOPHOLE[200B, CHAR]];
    stream.PutChar[LOOPHOLE[cmd, CHAR]];
    END;
  
  KillLights: PROC [] =  BEGIN
    DicentraInputOutput.Output[0, LOOPHOLE [MultibusAddresses.scc3 + 3B]];
    DicentraInputOutput.Output[0, LOOPHOLE [MultibusAddresses.scc3 + 5B]];
    END;
    
  New: PROCEDURE [words: LONG CARDINAL] RETURNS [p: LONG POINTER] =
    BEGIN
    pages: LONG CARDINAL ← Space.PagesFromWords[words];
    p ← Space.ScratchMap[pages];
    END;
  
  CheckScannerBusy: PROC [pupAddress: PupStream.PupAddress] = BEGIN
    IF active THEN ERROR PupStream.RejectThisRequest["Scanner busy"];
    END;
    
  Init[];
  
  END...