-- RavenSlotDriverImpl.mesa
-- Copyright (C) 1984, Xerox Corporation.  All rights reserved.
-- Mik Lamming, December 9, 1983 1: 25 pm
-- Michael Plass, September 14, 1984 11:01:25 am PDT
-- 
DIRECTORY
	DicentraInputOutput,
	Heap,
	Inline,
	Process,
	ProcessorFace,
	PDQueue,
	RavenSlotDriver,
	Watchdog;
-- 
RavenSlotDriverImpl: MONITOR 
	IMPORTS DicentraInputOutput, Inline, Heap, Process, ProcessorFace, PDQueue, Watchdog 
	EXPORTS RavenSlotDriver = BEGIN


-- ** Dicentra hyperspace configuration constants
hyperStart: DicentraInputOutput.IOAddress = MustBe64KWordBoundary[100000H];
hyperEndPlus1: DicentraInputOutput.IOAddress = LOOPHOLE[200000H];

-- ** Raven constants
bpi: CARDINAL = 384;
pageLengthInches: CARDINAL = 12; -- ample slop - 11 would probably do
pageWidthInches: CARDINAL = 8; 
white: CARDINAL = 0;

-- ** Slot paraphenalia
slot: Slot = LOOPHOLE[LONG[4000H]];  -- controller IO address
Slot: TYPE = LONG POINTER TO SlotRec;
SlotRec: TYPE = RECORD [
	printerCSR: CARDINAL,
	marginCount: CARDINAL,
	scanCount: CARDINAL,
	uARTModeOrCmd: CARDINAL,
	uARTData: CARDINAL];
slotBufferSize: CARDINAL = 2048;

-- ** Slot command codes
feedPageCommand: CARDINAL		= 40H;
txOffRxOnCommand: CARDINAL 	= 014H;
resetCommand: CARDINAL			= 40H;	-- internal reset to go into 'Mode' mode
configureUART: CARDINAL		= 03BH;	-- 1 stop bit, even parity, 7 bits, 64X clock
rxEnable: CARDINAL				= 015H;	-- Error reset, Recv enable
startSlot: CARDINAL					= 01H;
stopSlot: CARDINAL 					= 0;	
getStatus: CARDINAL				= 20H;

-- ** Status masks
txReady: CARDINAL = 1;
rxReady: CARDINAL = 2;
bufferEmpty: CARDINAL = 80H;

bufferSize: CARDINAL = 512; -- PowerOfTwoAtLeast[(bpi*pageLengthInches)/16];
Buffer: TYPE = LONG POINTER TO BufferRecord;
BufferRecord: TYPE = ARRAY [0..bufferSize) OF WORD;
dataBuffer: Buffer ← Heap.systemZone.NEW[BufferRecord];
whiteBuffer: Buffer ← Heap.systemZone.NEW[BufferRecord];


-- ** Maintenance panel codes
mpInitialising: NAT = 600;
	initUart: RavenSlotDriver.PrinterStatus = VAL[0];
	fillingHyper: RavenSlotDriver.PrinterStatus = VAL[1];
	patterningHyper: RavenSlotDriver.PrinterStatus = VAL[2];
	checkingHyper: RavenSlotDriver.PrinterStatus = VAL[3];
	hyperOK: RavenSlotDriver.PrinterStatus = VAL[4];
	errorHiDigits: RavenSlotDriver.PrinterStatus = VAL[13];
	errorLoDigits: RavenSlotDriver.PrinterStatus = VAL[14];
mpOK: NAT = 700;
mpWarning: NAT = 800;
mpFailure: NAT = 1300;
warningOrOKcode: NAT ← mpOK;

-- ** Miscellaneous dross
K64Words: LONG CARDINAL = LONG[64]*1024;




-- *** PUBLIC PROCEDURES AND VARIABLES ***
-- 
FallenOffTheEndOfHyperspace: PUBLIC ERROR = CODE;
BadParameters: PUBLIC ERROR = CODE;
-- 
outputTrayFullFlag: PUBLIC BOOLEAN ← FALSE;
lowTonerFlag: PUBLIC BOOLEAN ← FALSE;

HyperStore: PUBLIC PROC [ sourcePtr: LONG POINTER TO WORD, destOffset: LONG CARDINAL, wordCount: LONG CARDINAL] = BEGIN
	-- The destination area is referred to by an offset from the beginning of hyperspace.
	-- The microcode that implements WriteHyper has a feature that prevents it writing data across a 64K hyperspace boundary. Thats why some transfers have to be done in two parts.
	IF LOOPHOLE[hyperStart+destOffset+wordCount, LONG CARDINAL] > LOOPHOLE[hyperEndPlus1, LONG CARDINAL] THEN ERROR FallenOffTheEndOfHyperspace;
	WHILE wordCount>0 DO
		toNextBoundary: LONG CARDINAL ← K64Words - Inline.LowHalf[destOffset];
			-- use Inline.LowHalf above because MOD on LONG CARDINAL generates bad code
		xferSize: CARDINAL ← CARDINAL[MIN[wordCount, toNextBoundary, K64Words-1]];
		DicentraInputOutput.WriteHyper[from: sourcePtr, to: hyperStart+destOffset, words: xferSize];
		sourcePtr ← sourcePtr + xferSize;
		destOffset ← destOffset + xferSize;
		wordCount ← wordCount - xferSize;
		ENDLOOP;
	END;
	
GetStatus: PUBLIC PROC[] RETURNS[s: RavenSlotDriver.PrinterStatus] = BEGIN
	-- Returns the Raven status. Note the extra status code statusRQtimeout which gets returned if the Raven failed to answer the request for status.
	SendCommand[getStatus];
	RETURN[ReceiveStatus[]];
	END;
	
DisplayBadStatusInMP: PUBLIC PROC[s: RavenSlotDriver.PrinterStatus] = BEGIN
	-- show the Raven status with a 1300 code
	MPReport[s, mpFailure];
	END;
	
PrintPage: PUBLIC ENTRY PROC [slowMargin, fastMargin: NAT, numberOfLines, scanLineLength: NAT, paperSource: RavenSlotDriver.PaperSource, paperStacking: RavenSlotDriver.PaperStacking] RETURNS [RavenSlotDriver.SuccessCode] = BEGIN
	-- Returns when page is complete or an unrecoverable error has occurred.
	-- If error use LastStatus to find out why.
	-- If warning investigate outputTrayFullFlag and lowTonerFlag
	-- 
	deviceSlowMargin: CARDINAL = 100; 
	deviceFastMargin: CARDINAL = 400;
	s: RavenSlotDriver.PrinterStatus;
	scanLineWords: CARDINAL;
	hyperOffset: LONG CARDINAL ← 0;
	choppedLineLength: NAT ← (scanLineLength/16)*16; -- SLOT needs multiple of 16
	p: CARDINAL ← Process.GetPriority[]; -- don't get interrupted while writing
	
	FeedPage: PROC[] RETURNS[RavenSlotDriver.PrinterStatus]= BEGIN
		-- Feed a page and return useful status bytes: 
			-- warming, feederFault, registrationJam, fuserJam, noExit, interlockOpen, fuserCold, parityError, illegalCharacter, illegalSequence, noPaper, pageSync, goingOffLine, offLine, statusOverRun
		commandByte: CARDINAL ← feedPageCommand
			+ (IF paperSource=bottom THEN 0 ELSE 16)
			+ (IF paperStacking=aligned THEN 0 ELSE 1);
		SendCommand[commandByte];
		DO
			SELECT s ← ReceiveStatus[] FROM -- status bytes that we don't need to worry about 
				IN [key0..keyOffLine],  
lowToner, -- ReceiveStatus[] has set lowTonerFlag
outputTrayFull, -- ReceiveStatus[] has set lowTonerFlag
displayAcknowledge,
pageAtOutputTray,
goingToSleep,
feeding,
readyToFeed => NULL;
				ENDCASE => EXIT;  -- everything else is serious
			ENDLOOP;
		RETURN [s];
		END;
	
	XFerBufferToSlot: PROC[b: Buffer] RETURNS[] = BEGIN
		-- Move scanLineWords into the Slot buffer in IO space using microcode loop
		start: CARDINAL ← slotBufferSize-scanLineWords;
		DicentraInputOutput.WriteBlock[from: b, to: slot+8000H+start, words: scanLineWords];
		END;
	
	SkipScanLines: PROCEDURE [lines: CARDINAL] RETURNS[BOOLEAN]= {
		-- Wait for Slot buffer to empty lines times - Returns FALSE if a timeout occured. 
		BufferEmpty: PROCEDURE RETURNS [BOOLEAN] = {
			x: CARDINAL ← DicentraInputOutput.Input[@slot.printerCSR];
			RETURN[Inline.BITAND[x, bufferEmpty] # 0];
			};
		FOR i: CARDINAL IN [0..lines) DO
			count: CARDINAL ← 65000;
			WHILE NOT BufferEmpty[] DO
				count ← count - 1;
				IF count=0 THEN RETURN[FALSE];
				ENDLOOP;
			ENDLOOP;
		RETURN[TRUE]; -- ok exit
		};
		
	
	-- *** CHECK FOR REASONABLE PARAMETERS ***
	IF slowMargin+numberOfLines > pageWidthInches*bpi THEN {
		PDQueue.LogMessage["Raven slowMargin/numberOfLines problem"];
		numberOfLines ← pageWidthInches*bpi - slowMargin;
		};
	IF fastMargin+scanLineLength > pageLengthInches*bpi THEN {
		IF fastMargin > pageLengthInches*bpi THEN {
			PDQueue.LogMessage["Raven fastMargin > pageLengthInches*bpi"];
			fastMargin ← scanLineLength ← 16;
			numberOfLines ← 0;
			}
		ELSE {
			PDQueue.LogMessage["Raven fastMargin+scanLineLength > pageLengthInches*bpi"];
			choppedLineLength ← ((pageLengthInches*bpi-fastMargin)/16)*16;
			};
		};
	scanLineWords ← scanLineLength/16;
	-- 
	-- *** SET UP MARGIN DIMENSIONS ***
	DicentraInputOutput.Output[-(fastMargin+deviceFastMargin), @slot.marginCount];
	DicentraInputOutput.Output[-(choppedLineLength), @slot.scanCount];
	slowMargin ← MIN[slowMargin, pageWidthInches*bpi] + deviceSlowMargin; -- add client margin to device margin
	
	-- *** START PAGE ROLLING ***
	IF (s ← FeedPage[]) # pageSync THEN { -- Feed a page (or tell client about problem)
		DisplayBadStatusInMP[s];
		SELECT s FROM
			registrationJam => PDQueue.LogMessage["Raven - Registration jam"];
			fuserJam => PDQueue.LogMessage["Raven - Fuser jam"];
			warming => PDQueue.LogMessage["Raven - Warming up"];
			feederFault => PDQueue.LogMessage["Raven - Out of paper"];
			interlockOpen => PDQueue.LogMessage["Raven - Door open"];
			fuserCold => PDQueue.LogMessage["Raven - Fuser cold"];
			parityError => PDQueue.LogMessage["Raven - Parity Error"];
			offLine => PDQueue.LogMessage["Raven - Offline"];
			ENDCASE => PDQueue.LogMessage["Raven - Can't feed page"];
		RETURN[error];
		};
	MPReport[s, warningOrOKcode]; 
	
	-- *** WRITE WHITE INTO MARGIN ***
	Process.SetPriority[7]; -- don't get interrupted
	XFerBufferToSlot[whiteBuffer]; -- prime slot buffer A with white
	DicentraInputOutput.Output[startSlot, @slot.printerCSR]; -- crank up slot
	IF NOT SkipScanLines[1] THEN { -- wait for first buffer to go out
		PDQueue.LogMessage["Raven - Timeout 1"];
		Process.SetPriority[p];  -- allow interrupts again
		RETURN[error]; 
		};
	XFerBufferToSlot[whiteBuffer]; -- prime slot buffer B with white
	IF NOT SkipScanLines[slowMargin-1] THEN { -- freewheel thru A and B till margin out
		PDQueue.LogMessage["Raven - Timeout 2"];
		Process.SetPriority[p];  -- allow interrupts again
		RETURN[error]; 
		};
	
	-- *** WRITE CLIENT DATA TILL EXHAUSTED ***
	IF numberOfLines=0 THEN {
		PDQueue.LogMessage["Raven - Empty page!"];
		}
	ELSE 
		FOR i: CARDINAL IN [0..numberOfLines) DO
			-- get a scan lines worth of data from hyper to my buffer
			HyperRead[destPtr: @dataBuffer[0], sourceOffset: hyperOffset, wordCount: scanLineWords];
			XFerBufferToSlot[dataBuffer]; -- copy data back to slot buffer
			hyperOffset ← hyperOffset + scanLineWords;
			IF NOT SkipScanLines[1] THEN {
				PDQueue.LogMessage["Raven - Timeout 3"];
				Process.SetPriority[p];  -- allow interrupts again
				RETURN[error];
				}
			ENDLOOP;
	
	-- *** FINISH OFF WITH SOME WHITE SPACE AGAIN ***
	XFerBufferToSlot[whiteBuffer];
	IF NOT SkipScanLines[1] THEN {
		PDQueue.LogMessage["Raven - Timeout 4"];
		Process.SetPriority[p];  -- allow interrupts again
		RETURN[error]; 
		};
	XFerBufferToSlot[whiteBuffer]; -- fill other buffer and freewheel to end
	DicentraInputOutput.Output[stopSlot, @slot.printerCSR]; -- stop slot
	
	Process.SetPriority[p];  -- allow interrupts again
	
	-- *** WAIT FOR PAGE TO LAND IN OUTPUT HOPPER ***
	DO
		s ← ReceiveStatus[];
		SELECT s FROM
			pageAtOutputTray => EXIT;
			IN [key0..keyOffLine],  
lowToner, -- ReceiveStatus[] has set lowTonerFlag
outputTrayFull, -- ReceiveStatus[] has set lowTonerFlag
displayAcknowledge,
goingToSleep,
feeding,
readyToFeed,
statusRQtimeout => NULL;
			feederFault => {
				PDQueue.LogMessage["Raven - Out of paper?"];
				};
			ENDCASE => {
				code: CARDINAL ← VAL[s];
				DisplayBadStatusInMP[s];
				-- OthelloDefs.WriteLongNumber[code];
				PDQueue.LogMessage["Raven - Page didn't complete satisfactorily"];
				RETURN[error];
				};
		Process.Pause[Process.MsecToTicks[200]];
		ENDLOOP;
	
	MPReport[GetStatus[], warningOrOKcode];
	IF warningOrOKcode=mpWarning THEN {
		IF lowTonerFlag THEN PDQueue.LogMessage["Raven - Page completed (Toner Low)"];
		IF outputTrayFullFlag THEN PDQueue.LogMessage["Raven - Page completed (Output tray full)"];
		RETURN[warning];
		}
	ELSE {
		code: CARDINAL ← VAL[s];
		-- OthelloDefs.WriteLongNumber[code];
		PDQueue.LogMessage["Raven - Page completed OK"];
		RETURN[ok];
		};
	END;
	
MustBe64KWordBoundary: PROC[i: LONG CARDINAL] RETURNS[DicentraInputOutput.IOAddress] = {
	-- Used to check configuration values at start up
	IF i MOD K64Words # 0 THEN ERROR;
	RETURN [LOOPHOLE[i]];
	};
	
HyperRead: PROC [destPtr: LONG POINTER TO WORD, sourceOffset: LONG CARDINAL, wordCount: LONG CARDINAL] = BEGIN
	-- The destination area is referred to by an offset from the beginning of hyperspace. Has to cope with microcode feature which will not transfer across 64K boundaries
	WHILE wordCount>0 DO
		toNextBoundary: LONG CARDINAL ← K64Words - Inline.LowHalf[sourceOffset];
		xferSize: CARDINAL ← CARDINAL[MIN[wordCount, toNextBoundary, K64Words-1]];
		IF Inline.HighHalf[hyperStart+sourceOffset] # Inline.HighHalf[hyperStart+sourceOffset+xferSize-1] THEN ERROR; 
		DicentraInputOutput.ReadHyper[from: hyperStart+sourceOffset, to: destPtr, words: xferSize];
		destPtr ← destPtr + xferSize;
		sourceOffset ← sourceOffset + xferSize;
		wordCount ← wordCount - xferSize;
		ENDLOOP;
	END;


Susp: PROC[n: CARDINAL] RETURNS[] = BEGIN
	-- Suspend for 'n' milliseconds
	Process.Pause[Process.MsecToTicks[n]]
	END;
		
MPReport: PROC[s: RavenSlotDriver.PrinterStatus, offset: CARDINAL] =  BEGIN
	-- Display status and status code in Dicentra MP
	ProcessorFace.SetMP[LOOPHOLE[s, CARDINAL]+offset];
	END;


MSecs1000WaitProcess: PROCESS ← NIL;

SendCommand: PROCEDURE [command: CARDINAL] = {
	XmitReady: PROCEDURE RETURNS [BOOLEAN] = {
		x: CARDINAL← DicentraInputOutput.Input[@slot.uARTModeOrCmd];
		RETURN[Inline.BITAND[x, txReady] # 0];
		};
	IF MSecs1000WaitProcess # NIL THEN [] ← JOIN MSecs1000WaitProcess;
	MSecs1000WaitProcess ← NIL;
	-- turn receiver off, transmitter on
	DicentraInputOutput.Output[01H, @slot.uARTModeOrCmd];
	WHILE NOT XmitReady[] DO ENDLOOP;
	DicentraInputOutput.Output[command, @slot.uARTData];
	WHILE NOT XmitReady[] DO ENDLOOP;
	-- turn transmitter off, receiver on
	DicentraInputOutput.Output[txOffRxOnCommand, @slot.uARTModeOrCmd];
	MSecs1000WaitProcess ← FORK Susp[1000];
	};
	
ReceiveStatus: PROCEDURE RETURNS [s: RavenSlotDriver.PrinterStatus] = {
	RecReady: PROCEDURE RETURNS [BOOLEAN] = {
		bits: CARDINAL ← DicentraInputOutput.Input[@slot.uARTModeOrCmd];
		RETURN[Inline.BITAND[bits, rxReady] # 0];
		};
	timeOut: LONG CARDINAL ← 100000;
	WHILE NOT RecReady[] DO
		timeOut ← timeOut - 1;
		IF timeOut=0 THEN RETURN[statusRQtimeout];
		ENDLOOP; 
	s ← DicentraInputOutput.Input[@slot.uARTData];
	SELECT s FROM
		lowToner => {
			lowTonerFlag ← TRUE;
			warningOrOKcode ← mpWarning;
			};
		outputTrayFull => {
			outputTrayFullFlag ← TRUE;
			warningOrOKcode ← mpWarning;
			};
		ENDCASE => {
			lowTonerFlag ← outputTrayFullFlag ← FALSE;
			warningOrOKcode ← mpOK;
			};
	RETURN[s];		
	};

Init: PROC[checkHyper: BOOLEAN←TRUE] RETURNS[] = BEGIN
	StripeHyper: PROC = BEGIN
		buffer: ARRAY [0..4) OF PACKED ARRAY [0..3840) OF BOOLEAN;
		p: POINTER ← @buffer;
		offset: LONG CARDINAL ← 0;
		FOR i: NAT IN [0..3840) DO
			buffer[0][i] ← ((i MOD 38) + i / 100) > 37;
			ENDLOOP;
		buffer[1] ← buffer[2] ← buffer[3] ← buffer[0];
		MPReport[fillingHyper, mpInitialising];
		WHILE offset < hyperEndPlus1-hyperStart DO
			wordCount: CARDINAL ← CARDINAL[MIN[(hyperEndPlus1-hyperStart)-offset, 3840/4]];
			HyperStore[sourcePtr: p, destOffset: offset, wordCount: wordCount];
			offset ← offset + wordCount;
			ENDLOOP;
		END;
	FillHyper: PROC [pattern: CARDINAL] = BEGIN
		buffer: ARRAY [0..256) OF CARDINAL ← ALL[pattern];
		offset: LONG CARDINAL ← 0;
		MPReport[fillingHyper, mpInitialising];
		PDQueue.LogMessage["Raven Initialising memory"];
		WHILE offset < hyperEndPlus1-hyperStart DO
			wordCount: CARDINAL ← CARDINAL[MIN[(hyperEndPlus1-hyperStart)-offset, 256]];
			HyperStore[sourcePtr: @buffer[0], destOffset: offset, wordCount: wordCount];
			offset ← offset + wordCount;
			ENDLOOP;
		END;
	PatternHyper: PROC [pattern: CARDINAL] = BEGIN
		buffer: ARRAY [0..99) OF CARDINAL;
		offset: LONG CARDINAL ← 0;
		mod: CARDINAL ← 0;
		MPReport[patterningHyper, mpInitialising];
		PDQueue.LogMessage["Raven Initialising memory"];
		WHILE offset < hyperEndPlus1-hyperStart DO
			wordCount: CARDINAL ← CARDINAL[MIN[(hyperEndPlus1-hyperStart)-offset, 99]];
			FOR j: CARDINAL IN [0..wordCount) DO
				mod ← mod + 1;
				IF mod = 10 THEN {pattern ← pattern * 13 + 5; mod ← 0};
				buffer[j] ← pattern;
				ENDLOOP;
			HyperStore[sourcePtr: @buffer[0], destOffset: offset, wordCount: wordCount];
			offset ← offset + wordCount;
			ENDLOOP;
		END;
	CheckHyper: PROC [pattern: CARDINAL] = BEGIN
		buffer: ARRAY [0..101) OF CARDINAL;
		fromHyper: ARRAY [0..101) OF CARDINAL;
		offset: LONG CARDINAL ← 0;
		mod: CARDINAL ← 0;
		MPReport[checkingHyper, mpInitialising];
		PDQueue.LogMessage["Raven Checking memory"];
		WHILE offset < hyperEndPlus1-hyperStart DO
			wordCount: CARDINAL ← CARDINAL[MIN[(hyperEndPlus1-hyperStart)-offset, 101]];
			FOR j: CARDINAL IN [0..wordCount) DO
				mod ← mod + 1;
				IF mod = 10 THEN {pattern ← pattern * 13 + 5; mod ← 0};
				buffer[j] ← pattern;
				ENDLOOP;
			HyperRead[destPtr: @fromHyper[0], sourceOffset: offset, wordCount: wordCount];
			FOR i: NAT IN [0..wordCount) DO
				IF fromHyper[i] # buffer[i] THEN DO
					p: CARDINAL ← Process.GetPriority[];
					Process.SetPriority[7];
					PDQueue.LogMessage["Raven Problem with memory"];
					MPReport[errorHiDigits, mpInitialising]; THROUGH [1..100000] DO ENDLOOP;
					ProcessorFace.SetMP[NAT[(offset+i)/1000]]; THROUGH [1..100000] DO ENDLOOP;
					MPReport[errorLoDigits, mpInitialising]; THROUGH [1..100000] DO ENDLOOP;
					ProcessorFace.SetMP[NAT[(offset+i) MOD 1000]]; THROUGH [1..100000] DO ENDLOOP;
					Process.SetPriority[p];
					ERROR;
					ENDLOOP;
				ENDLOOP;
			offset ← offset + wordCount;
			ENDLOOP;
		END;
		
	-- ***** Initialize the UART *****
	-- 
	MPReport[initUart, mpInitialising];
	PDQueue.LogMessage["Raven Initialising SLOT UART"];
	DO
		s: RavenSlotDriver.PrinterStatus;
		DicentraInputOutput.Output[0H, @slot.uARTModeOrCmd];
		DicentraInputOutput.Output[0H, @slot.uARTModeOrCmd];
		DicentraInputOutput.Output[0H, @slot.uARTModeOrCmd];
		DicentraInputOutput.Output[resetCommand, @slot.uARTModeOrCmd];
		DicentraInputOutput.Output[configureUART, @slot.uARTModeOrCmd];
		DicentraInputOutput.Output[rxEnable, @slot.uARTModeOrCmd];
		IF (s ← GetStatus[])#statusRQtimeout THEN EXIT;
		DisplayBadStatusInMP[s];
		PDQueue.LogMessage["Raven not answering"];
		ENDLOOP;
	-- 
	IF checkHyper THEN {
		DicentraInputOutput.SetNxmExpected[TRUE]; -- don't die on non-existant mem.
		-- PatternHyper[103];
		-- CheckHyper[103];
		StripeHyper[];
		DicentraInputOutput.SetNxmExpected[FALSE];
		MPReport[hyperOK, mpInitialising]; Susp[500];
		PDQueue.LogMessage["Raven Memory checks out OK"];
		FOR i: CARDINAL IN [0..bufferSize] DO
			whiteBuffer[i] ← white;
			ENDLOOP;

		};
	END;
	
testCopies: CARDINAL ← 0;
TestKeyPressed: ENTRY PROC[] RETURNS[yes: BOOLEAN ← FALSE] = BEGIN
	s: RavenSlotDriver.PrinterStatus;
	SELECT s ← ReceiveStatus[] FROM
		keyTest => yes←TRUE;
		IN [key0..key9] => {
			-- testCopies ← testCopies*10+CARDINAL[s-key0] MOD 100;
			};
		ENDCASE;
	END;
	
TestProcess: PROC[] RETURNS[] = BEGIN
	Process.SetPriority[Process.priorityBackground];
	DO 
		s: RavenSlotDriver.PrinterStatus ;
		successCode: RavenSlotDriver.SuccessCode;
		UNTIL TestKeyPressed[] DO ENDLOOP;
		PDQueue.LogMessage["Raven Printing test page"];
		successCode ← PrintPage[0, 0, 3072, 3840, top, aligned];
		SELECT successCode FROM
			warning => NULL;
			error => {
				p: CARDINAL ← Process.GetPriority[];
				Process.SetPriority[7];
				WHILE (s←GetStatus[])#standBy DO
					DisplayBadStatusInMP[s];
					ENDLOOP;
				Process.SetPriority[p];  -- allow interrupts again
				};
			ENDCASE => NULL; -- ok
		ENDLOOP;
	END;

-- tester: PROCESS;
Watchdog.Deactivate[];
Init[];
-- tester ← FORK TestProcess[];
PDQueue.LogMessage["Raven ready for service"];
END.