// 			P R I N T   -   DOVER   
// errors 1000
//

get "Spruce.d"
get "SpruceFiles.d"
get "Orbit.d"

// outgoing procedures
external
	[
	DrivePrinter
	]
// incoming procedures
external
	[
	SpruceError
	Max
	Min
	InitRam
//SprucePrint
	AddToBin
	Announce
	AwaitPageSync
	IncBinSerial
	PrintNext
	ReadBands
	ReadClock
	ReadFont
//CURSOR
	CursorChar
	CursorDigit
// SprucePrintRes
	DoFunc
	ROSStatus
// Timer (debug only)
	SetTimer
	TimerHasExpired
//OS
	Zero
	DisableInterrupts
	EnableInterrupts
	]
// incoming statics
external
	[
	scanTicks		// # of 38 usec. ticks in 4 printer scan lines.
	logBandRecordSize
	bandRecordSize
	nPagesPrinted
	Debug
	DebugSystem
	signalBand  
	Func
	debugTrail
	knockResult
	numPrinted
	numMustPrint
	InitMeasure // Dover engine control monitoring stuff
	CloseMeasure
	TickMeasure
	Measure
	measureTable
	xmFonts	// Font data will be read into Bank 1 via Bank 0, if set
	nRecs
	LowAdr
	FAvalue
	nBands
	stopsPrinting
	BinSerials
	]


// internal statics   
static
	[
	timeOut=15*27
	printHysteresis = 15 // print this many before allowing suspension, each time in
	  // ~~ above number may want to increase for large runs, or something?
	simLateDisk   
	// cStat ~~ Collect statistics on the appearance of Count-H (Dover only)
	// cStat ~~  Record minimum and maximum value of (nBands-current band) in
	// cStat ~~  count-H signal LAST appears during imaging of a page.
	// cStat ~~  Re-Initialize Sprint, or reset by hand, to reinitialize values
	cbMin = 32767 // cStat ~~ min value of (nBands-currentBand)
	cbMax = 0 // cStat ~~ max value -- nominal value is value of signalBand
	// delay ~~~~ debugging: delay per page to simulate printing
	debDelay = 0 // delay no delay std. 
	// delay ~~~~
	]

// File-wide structure and manifest declarations.

manifest [
	RTC=#430
	debugTrailSize = 10*3

	// Failure Code ranges -- see ROSCheck
	maxNotReady = 9 // not ready, but probably will be soon
	maxManual = 19 // Manual intervention required
	maxJam = 99	// Error during operation
	minDocumentError = 300 // Document inherently hard to print
		]

// ROSCheck values
structure CT:
 	[
 	invert		bit 1 // bit is on when things are good
 	malFnReq	bit 1 // Dover malfunction bit must also be on or no error
 	failureCode	bit 6
 	wordIndex	bit 4
 	bitIndex	bit 4
 	]

manifest // factors for creating table entries
	[
	Invert		= #100000
	MalFnReq	= #40000
	Code		= 256
	Word		= 16
	Bit		= 1
	]

// -----------------------------------------------------------
// DoverPrint		Initialization
// -----------------------------------------------------------

// Returns -1 if printing is successfully completed, else
// nPagesPrinted, so can pick up where we left off.

let DrivePrinter(pDoc, nPagesAlreadyPrinted, lvFailureCode) = valof
[
// ******** See SpruceMeasure -- don't move declarations of next five local variables ********
	let malfunction, proceedCode, nonF, coldStart, consecutiveErrors = nil, nil, nil, nil, nil
// ********
	let CommTab=vec 10
	CommTab=(CommTab+1)&(-2)

	// If monitoring dover performance, these are real
	InitializeHardware(false)
	InitMeasure(measureTable, 2000, (lv malfunction)-1)

	coldStart=true
	consecutiveErrors=0
	let knockKnock = false		// time to let somebody in if true

// Print (cont.)		Slow Loop

// Loop out to here when a new font set is required, or when
// needing to completely restart the printer.
[Slow
// Find out which page to print next:
	let copy = nil
	let page=PrintNext(pDoc, lv copy, nPagesPrinted)
	if page eq 0 then [ knockKnock = false; IncBinSerial(BinSerials+0); break ]
	if knockKnock then break // go let somebody in

// Read in font and relocate the pointers to characters:
	let font=page>>PageG.fontLoad*(size FontG/16)+pDoc>>DocG.Fonts
	// Next call returns location of ICC table,
	let FontTable = ReadFont(font, LowAdr, nRecs)
	unless xmFonts do for i=0 to pDoc>>DocG.ICCtotal-1 do // if rasters are in bank 1, they start at 0
		FontTable!i=FontTable!i + LowAdr
	FontTable=FontTable-100000b

// Figure out where buffers are.
	let nRecords= xmFonts? (pDoc>>DocG.ICCtotal+bandRecordSize-1) rshift logBandRecordSize,
				font>>FontG.nRecords
	let nfRecs=(nRecs-nRecords)/2 // max allowable band list size for normal alternation
	let LeftOver = LowAdr+(nRecords lshift logBandRecordSize)
	let LeftOverGuard = LeftOver+(loSize&(-4))-4
	let buf1 = (LeftOver+loSize+8)&(-2) // ~~ all the 4's, 8's and 10's in here are cowardly slack
	let buf2=buf1+nfRecs lshift logBandRecordSize
	let curbuf=coldStart? buf2, buf1 // first real data imaged from buf1

// Now read in the bands for this page
	test coldStart then Zero(curbuf, nBands*2)
	 or ReadBands(page, curbuf, nfRecs*2)
// Initialize the Orbit, Dover, etc.
	InitializeHardware(false)
	let np = nil
	let tim = @RTC
// Check for problems, wait for ready indication
  // wait for ready or some serious condition or for long enough
   unless Debug do
	[
	np = ROSCheck(lvFailureCode, nPagesAlreadyPrinted, false)
	if np eq -1 break // ready
	if @lvFailureCode > maxNotReady % (@RTC-tim) > timeOut resultis np
	] repeat

// Try to empty Orbit buffers to clear them.  This is to minimize
// toner dumping on first (blank) page.
	compileif loSize ls 256 then [ foo=nil ]
	for i=0 to 31 do DoFunc(fReadBlock, 256, LeftOver)

// Now feed first sheet:
	DoFunc(fControl, 21b)
// Turn off adCommandBeamOn, if on, revert to normal modulation
// ~~ causes glitch	AdCommand(AdapterScales)
	DoFunc(fROSCommand, adBufferReset)
// If Dover is already running, this StartPrint may not do the right
// thing, because it may be sent during the "dead" time near
// CS-5.
	FeedASheet()
	AwaitPageSync(0)	//Wait for sendVideo to go off.
// Print (cont.)		Fast Loop

[RunningLoop
// Calculate a ROS command table:
		CommTab!0=signalBand+2
		CommTab!1=adExternalCommand1	//No feed yet
		CommTab!2=signalBand+1
		CommTab!3=adExternalCommand1
		CommTab!4=10000b+signalBand // last entry nearing obsolescence 3-26-78 DCS
		CommTab!5=32	*256		//Status bits 0-3 of word 8
		CommTab!6=-1

// Start Orbit working on the present page.
	DoFunc(fControl, #21)		//Reset, clear behind
	DoFunc(fROSCommand, adBufferReset)
	LeftOver!0=0			//Initialize LeftOver table
	LeftOverGuard!1=-1
	tim=@RTC
// Start the microcode!!!!
	DoFunc(fSlot, CommTab, 20000, nBands-1, FAvalue, LeftOver,
			FontTable, curbuf-1, nil, nil, (copy lshift 5)+4)

if Debug then // ~~delay
	[ // ~~delay
	while (@RTC-tim) < debDelay loop	// ~~delay
	tim = @RTC // ~~delay don't invoke timeout code below
	] // ~~delay
	
// 	Prepare next page in parallel with imaging
	// Proceed code:
	// 0: Charge on ahead, full speed
	// 1: Pause because cannot fit bands or font for next page,
	//     or because printing is done.
	// 2: Reprint this page, due to misfeed (Dover only).
	// 4: No sheet in pipe (Dover only)

// Assume the page we are printing will be OK:
	unless coldStart then
		[
		nPagesPrinted=nPagesPrinted+1
		CursorDigit(nPagesPrinted)
		]
// See who's (maybe) knocking at my door
// This logic is copied from SpruceUtilsRes: if we are not planning to defer to spooler,
//  don't trouble to shut down here, just to find that out there. Would be too expensive
	if (DebugSystem&#30) eq 0 & numPrinted ge numMustPrint & knockResult &
	(nPagesPrinted-nPagesAlreadyPrinted) > printHysteresis 
	 then knockKnock = true

// Find next page to work on:
	proceedCode=1
	let nextbuf=curbuf xor buf1 xor buf2
	let nextPage=PrintNext(pDoc,lv copy, nPagesPrinted)

	if (not knockKnock) & nextPage then
		[
		if (nextPage>>PageG.fontLoad eq page>>PageG.fontLoad) &
		(nextPage>>PageG.nRecords le nfRecs) then
			[
			proceedCode = 0
// Read in the bands for the next page:
			ReadBands(nextPage, nextbuf, coldStart? nfRecs*2, nfRecs)
			if simLateDisk then
					[
					simLateDisk=simLateDisk xor 1
					if (simLateDisk&1) eq 0 then Func!3=-1
					]
			DisableInterrupts()
			test Func!3 gr signalBand+3 then
			CommTab!1=adExternalCommand1+1
			or	proceedCode=4
			EnableInterrupts()
			]
		]
// Print (cont.)		Wait for completion, analyze results

// Now see if we actually printed this page properly.  First,
// wait for Orbit to announce it is finished.
	let imageTimeout = 4*27
	while @#720 ne 0 do if (@RTC-tim) gr imageTimeout then
		[
		Blast()		// clear hardware, #720
		 // Machine dead or Orbit got behind and it wasn't detected
		 // Will generally be overriden by more specific analysis
		@lvFailureCode = 19 // Manual intervention required
		break
		]
// Check Orbit status
	nonF = 0
	let stat=Func!9		//Orbit printing status.
	let countBand = Func!8 // Band in which count-H last occurred (Dover only)
	// ~~ cStat
	cbMin = Min(cbMin, countBand) //~~ cStat
	if countBand<nBands then cbMax = Max(cbMax, countBand) //~~ cSTat
	// ~~ cSTat
	// Fatal error detected by microcode -- invalid Band entry -- possibly broken processor
	if stat<<STATUS.invalidBandEntry then
		[ InitializeHardware(true); 
		  if LeftOverGuard!1 eq -1 then SpruceError(1010, @1, @2)  //else, assume that
			//bad band entry is simply result of leftover overflow
		]
	test stat<<STATUS.prematurePageAbort then [ Announce(16); nonF = 301 ]
	  or if stat<<STATUS.behind then [ Announce(16); nonF = 300 ]
	if stat<<STATUS.timeout then [ Announce(17); @lvFailureCode = 18 ]
	if LeftOverGuard!1 ne -1 then
	  [ Announce(18); nonF = 310 // then check possible next page data enclobberment
	   if ((proceedCode&1) eq 0) & nextbuf eq buf1 then proceedCode = 1 ]
	if nonF then @lvFailureCode = nonF
	if (stopsPrinting eq binFull) & (@lvFailureCode eq nonF)  then @lvFailureCode = 130
	AddToBin(500)
// Now try for more specific engine status check

	np=ROSCheck(lvFailureCode, nPagesAlreadyPrinted, true)
	// Check COUNT-H signal, paper did not arrive -- else re-print page 
	 if coldStart eq 0 & 
		// ~~ microcode checks explicitly at end of each band, as well as at signalBand
		 (((CommTab!5)&1) eq 0 & countBand ge(nBands rshift 1))then proceedCode=proceedCode%
			(Func!3 le signalBand+3? 2, proceedCode eq 1? 0, 4)

// Now wait for page sync to go off
	unless AwaitPageSync(0) then @lvFailureCode = 16

//Now decide what to do:
	if proceedCode then Announce(proceedCode)
	malfunction = @lvFailureCode
	if malfunction ge minDocumentError then malfunction = 0 // will not stop

// First, if pipe is empty, fill it.  Will not be invoked if pausing. 
	if (proceedCode&4) ne 0 then
		[
		AwaitPageSync(1)
		FeedASheet()
		AwaitPageSync(0)
		proceedCode=proceedCode-4
		]

// Now react to the analysis: if at this point nothing is wrong and sheets are
// feeding, steam ahead. Otherwise, return to the slow loop if data is not available
// or if paper misfed. Return if there was a serious malfunction, or if things look
// odd several times consecutively (paper failed to feed is the only case, for now)

	Measure(#23,(@(#123))+1) // force random event
	unless malfunction do
	  [
	    if proceedCode eq 0 % proceedCode eq 2 & nonF ne 0 then
		  [
		  coldStart=false
		  curbuf=nextbuf
		  consecutiveErrors=0
		  loop
		  ]
	    if proceedCode eq 1 then [ coldStart = false; break ] //Out of RunningLoop
	    consecutiveErrors=consecutiveErrors+1 // misfeeds only, right now
	    if consecutiveErrors ge 4 then
  		    [
		    malfunction, @lvFailureCode = 17, 17
		    np = nPagesPrinted
		    ]
	  ]
	  
	Measure(#23,(@(#123))+1) // force random event
	if malfunction then
		[
		InitializeHardware(true)
		// if malfunction detected outside of ROSCheck, set np
 
		let backup =  2

		if np eq -1 then np = nPagesPrinted-backup
		// account for previous increment
		np = Max(np-1, nPagesAlreadyPrinted)
		CloseMeasure() // if measure code loaded, this is real, else dummy
		resultis np
		]
// If we get here, it is necessary to reprint this page. Paper is
// probably flowing. Probably a paper misfeed
// Exception: non-fatal malfunction (mf=0, but nonF isn't) -- give it up and go on
		unless nonF do nPagesPrinted=nPagesPrinted-1
		if (proceedCode&1) ne 0 then break	//Use pause code
		loop			//Reprint the page, or go on
]RunningLoop repeat

]Slow repeat

	InitializeHardware(true)
	CloseMeasure()
	resultis (knockKnock % @lvFailureCode)? nPagesPrinted, -1
]

// --------------------------------------------------------------------------
// nPagesPrinted = ROSCheck(lvFailureCode, nPagesAlreadyPrinted, running,)    
// --------------------------------------------------------------------------

// analyze ROS status
// nPagesPrinted = -1 if there are no malfunctions, ROS is ready to print
//			otherwise, the number of pages to report as having been printed
// @lvFailureCode will contain an analysis of the current state. The code values are
//			described below.
// nPagesAlreadyPrinted places a lower bound on the nPagesPrinted result. This 
//			prevents loops through this code from backing up too far.
// running		true if caller thinks main drive is allowed to be on for "ready"
//			result, false otherwise (waiting for ready).

// ROSCheck first screens the current status for any malfunction indications, and
// returns right away if there are none. Otherwise, it applies the current status
// against a  table of possible conditions, yielding a failure
// code and a new page value -- the last page certain to have been reliably
// printed. One possible condition is "no malfunction detected", which will only
// be issued for printers that give no positive indication when they are merely
// busy doing normal things.
// N.B. @lvFailureCode is not changed when no conditions are found

// The failure codes divide into ranges:
// 0-9		Not ready, but no indication of problems or will clear up automatically
//		(includes fuser cold, warmup mode, clearing revolutions, etc.)
// 10-19	Manual intervention required before machine can become ready
//		(paper low, power not there, etc.)
// 20-99	Jams and other operational malfunctions (many codes provided so that
//		each device can report machine-dependent jam explanations)
// 100-200	Inherent problems (e.g., insufficient bandwidth somewhere) that are
//		not curable by mere mortals. Attempts to improve the situation should be
//		abandoned.
// 200-300	Inherent problems encountered, but they did not terminate printing. One
//		should expect that one or more pages were improperly imaged.

// The machine-dependent control table is constructed using this pattern:

// structure CT:
// 	[
// 	invert		bit 1
// 	failureCode	bit 7
// 	wordIndex	bit 4
// 	bitIndex	bit 4
// 	]

// invert means that the bit is on when the condition is not a problem (e.g., "Ready")
// failureCode should be reported when the condition specified by the bitIndex'th
//		bit inROS status word wordIndex is a problem.
// A check, designed chiefly for Dover, verifies that the laser is on and the polygon is
// scanning (SOS/EOS are seeing things), by making sure that the low four bits of the line
// count (indicated in status word) are  changing. The code reads the line count, waits
// approximately the time needed for four scan lines (generous margin), then reads the
// count again, reporting a problem if the values are equal. This test happens only when
// the running parameter is false. The wait time (scanTicks) is computed in PrintInit, as:

// ResolutionS is scaled by 10 as stored, PaperSpeedInches by 100. The (fast 10 bits of)
// the RTC ticks at 38 usec. intervals. Thus the # ticks wanted for four lines is:

//   ( 4 lines x 10↑6 usec/sec x  1/38 tick/usec.) / ((PaperSpeedInches/100 x ResolutionS/10) lines/sec)

// or about (106 x 10↑3) / (PaperSpeedInches/100 x ResolutionS/10)   ticks -- about 30 for Dover.

// This is only approximate because ROSStatus time, about 10 ticks for Dover, is not included.
// Part of the reason for waiting four line times is to account for possible delays in obtaining
// the correct line count status. Later, this method can be used to check for correct polygon speed.
// The scanTicks static is shifted left by 6 to match the clock reading function's results

// ROSCheck (cont)

and ROSCheck(lvFailureCode, nPagesAlreadyPrinted, running) = valof
[
let ros8 = ROSStatus(8)
let ros9 = ROSStatus(9)
let localMode, scanning = nil, true
// ready if not in local mode and if printer-specific tests indicate ready
let ready = 	// Power on, no malfunction, paper tray OK, fuser warm 
	 (ros9&#2100) eq #2000  &  (ros8&#2004) eq 4

if ready then
	[
	localMode = (ROSStatus(0)&#20000) ne 0
	unless running do
	  [
	  let lineCount = ROSStatus(7) & #170000
	  let rtc = ReadClock()
	  until (ReadClock()-rtc) ge scanTicks loop
	  scanning = (ROSStatus(7)&#170000) ne lineCount
	  ]
	if localMode % not scanning then ready = false
	]

if ready % Debug resultis -1

// Analyze tables to identify the problem or condition preventing ready

let tab =  table [
	10*Code   +   8*Word +  5*Bit // PTDisorder-H Check paper tray
	20*Code   +   8*Word + 10*Bit // 27LS-H Jam -- paper feed
	21*Code   +   8*Word + 14*Bit + MalFnReq // LostPower-H Lost engine power
	22*Code   +   9*Word +  1*Bit // PhotoCellOut-H Jam--paper on drum
	23*Code   +   9*Word +  2*Bit // PreSeq-H Jam -- startup sequence
	24*Code   +   9*Word +  6*Bit // 38LS-H Jam--paper on fuser roll
	25*Code   +   9*Word + 10*Bit // 3LS-H Jam--paper on drum
	14*Code   +   8*Word + 13*Bit + Invert // ReadyTemp-H Fuser not warm
	12*Code   +   9*Word +  5*Bit + Invert // ACMonitor-H Engine not powered up
	26*Code   +   9*Word +  9*Bit // Malfunction-H unexplained malfunction
	0  ]
// Codes generated elsewhere:
//	7 -- no specific malfunction detected, but something is wrong
//	16 -- page sync never turned off
//	17 -- several consecutive unidentified errors
//	18 -- Orbit timed out -- page sync never arrived, or engine died or got behind
//	19 -- Alto code timed out -- same symptoms
//	300 -- Orbit got behind, but page terminated normally
//	301 -- Orbit got behind, had to quit early
//	310 -- Left over table got too large. Please report to fixer.

let bits = table [ #100000;
	  #40000;	  #20000;	  #10000
	   #4000;	   #2000;	   #1000
	    #400;	    #200;	    #100
	     #40;	     #20;	     #10
	      #4;	      #2;	      #1	]

// Condition interpreter

let code = 0
		while @tab do
			[
			let entry = @tab
			let isBit = (ROSStatus(entry<<CT.wordIndex) & bits!(entry<<CT.bitIndex)) ne 0? 1, 0
		   if isBit ne entry<<CT.invert  &  (entry<<CT.malFnReq eq 0 % (ros9&#100) ne 0) then
				[ code = entry<<CT.failureCode; break ]
			tab = tab + 1
			]
// No other explicit problems, report local mode, or laser off, or unknown problem
unless code do code = localMode? 11, scanning eq 0? 13, 7
@lvFailureCode = code
 let backup = code le maxManual? 0, code < 30? 2, 1  
resultis Max(nPagesAlreadyPrinted, nPagesPrinted-backup)
]
// -----------------------------------------
// Engine controls of various sorts.
// -----------------------------------------

and FeedASheet() be 
[
		Measure(#20,true)
		DoFunc(fROSCommand, adExternalCommand1+1)
		DoFunc(fROSCommand, adExternalCommand1)
		Measure(#20,false)
]
// -----------------------------------------
// Hardware setup for various devices.
// -----------------------------------------

and InitializeHardware(stop) be
[	if Debug return
	InitRam(0)			//Reset all RAM tasks, restart Trident if necessary

	DoFunc(fControl, 1)		//Reset Orbit

	 DoFunc(fROSCommand, adExternalCommand1)
]

and Blast() be InitializeHardware(false)

//BWBNovember 13, 1978  12:03 PM  derived from SprucePrint
// December 7, 1978  3:30 PM cleanup
// April 27, 1979  11:45 AM, add Dover Engine Control Monitoring code, see SpruceMeasure, by Swinehart
// 	 add ability to place fonts in bank 1 -- see all uses of xmFonts
// 	 repair bug in BankBlt interface for xmFonts stuff
// May 23, 1979  2:05 PM cleanup
// January 28, 1981 12:40 PM  don't swat if bad band entry probably due to leftover overflow
// February 5, 1981  3:41 PM,  add call on IncBinSerial