-- File: PrintSlot.mesa.
--edit history at bottom of file
-- Last Edited: October 28, 1981 11:34 AM By: GWilliams
--a 4K buffer load is dropped sporatically, added one line in inner loop to zero status on dequeing (@inline PrintBuffer code).

DIRECTORY
AltoDefs USING [PageSize],
FSPDefs USING[ZoneTooLarge],
InlineDefs USING[BITAND, BITNOT, BITOR, BITSHIFT, BITXOR],
IODefs USING[CR, ReadChar, WriteChar, WriteDecimal, WriteLine, WriteString],
MiscDefs USING [Zero],
PressDefs USING [abortFile, invertMode, PageG, PageGptr, SLOTScanLength, SLOTScanMarginAdjust, SLOTBitMarginAdjust, SLOTDouble, SLOTTimeOut],
RamDefs USING[StartIO],
SegmentDefs USING[DataSegmentAddress, DataSegmentHandle, DefaultBase, DeleteDataSegment, InsufficientVM, NewDataSegment],
StringDefs USING [UpperCase],
Storage USING[FreeWords, Words],
SystemDefs USING [Even],
TridentDefs USING[DA, diskCheck, diskRead, dlPtr, dstDone, dstZeroStatus, FP, kcbPtr, lKCB, Mstatus, PAGE, ptr, statusptr, Status, tfsdskPtr, TfsRealDA, TFSwordsPerPage, track];
PrintSlot: PROGRAM
IMPORTS InlineDefs, IODefs, MiscDefs, PressDefs, RamDefs, SegmentDefs, Storage, StringDefs, SystemDefs, TridentDefs, FSPDefs
EXPORTS PressDefs=
BEGIN OPEN AltoDefs, FSPDefs, InlineDefs, IODefs, MiscDefs, PressDefs, RamDefs, SegmentDefs, StringDefs, Storage, SystemDefs, TridentDefs;

-- Types
PError: TYPE = {noPaper, jam, firstFillFailed, firstFillTimeOut, pageTimeOut, overRan, readError, notSure};
SlotCommand: TYPE = {setDensity, beamOn, reset, stopPrint, beamOff};
SCB: TYPE = MACHINE DEPENDENT RECORD
[
blank:[0..37777B],--14 bits worth
com:[0..3],--Beam On =0, Status = 1, Reset = 2, Print = 3
blowup:[0..1),--True: make one bit into 4
lastPage:[0..1),--Marks this command as the last page if true
invert:[0..1),--invert this page
bitsPerLine:[0..17777B],--servo count for number of bits per line
bitMargin:CARDINAL,--bottom margin in bits
scanLineWc:CARDINAL,--double of the word count
scanMargin:CARDINAL,--left margin (bits before print in portrait)
scansPerPage:CARDINAL,--total number of scan lines per page
bufferPtr:POINTER,--print buffer base address (first loc.)
scanLineWcInc:CARDINAL,--print buffer scan line increment
scansPerBuff:CARDINAL,--print-mode buffer length
currentBuf:POINTER,--current buffer address
currentLine:CARDINAL,--current scan address
status:ReturnStatus,--return status
filler:CARDINAL];--in case SetSCB adds one to base address
ReturnStatus: TYPE = MACHINE DEPENDENT RECORD
[
blank:[0..377B],
spare2:[0..1],
wait:[0..1],
paperJam:[0..1],
addPaper:[0..1],
selectB:[0..1],
ready:[0..1],
lineSync:[0..1],
pageSync:[0..1]--complemented signal
];
normalizedStatus: ReturnStatus ← [0,0,1,1,1,0,0,0,1];
--161B

MetaStat: TYPE = POINTER TO AqMetaStat;
AqMetaStat: TYPE = MACHINE DEPENDENT RECORD
[wait:BOOLEAN,--from uCode
paperJam:BOOLEAN,--from uCode
paperOut:BOOLEAN,--from uCode
illegal:BOOLEAN,--from IllegalState
ready:BOOLEAN,--"OR" of various bits and variables
overRun:BOOLEAN,--from "bufferEmpty"
fill:[0..1777B],--filler
pageNotPrinted: CARDINAL,--from PrintFail
diskError:CARDINAL--from "readErrors"
];

-- Public Pack Variables EXPORTED to PressDefs
--EXPORTED to PressDefs
--Private Pack Variables
overRun, illegalState: BOOLEAN ← FALSE;
printFail, readErrors: CARDINAL;
slotCommandBlock: POINTER TO POINTER = LOOPHOLE[720B];
slotSIObit: CARDINAL = 4;
pageRetries: CARDINAL;
maxPageRetries: CARDINAL ← 3;

--debugging switches
useSLOT: BOOLEAN ← TRUE;
debug: BOOLEAN ← FALSE;
noDisplay: BOOLEAN ← TRUE;
diskRetries: CARDINAL ← 20B;
testDiskOnly: BOOLEAN ← FALSE;
buffersCheck: BOOLEAN ← TRUE;
--type message if didn’t get 32K for buffers in PSlot

da: PAGE;
--used by SLOTPrint and ReadBuffer
readNext, printNext: kcbPtr;
printCount, readCount: CARDINAL;
active: POINTER TO CARDINAL = LOOPHOLE[453B];
--for turning off memory parity interrrupts
wakeupsWaiting: POINTER TO CARDINAL = LOOPHOLE[452B];
RTC: POINTER TO CARDINAL = LOOPHOLE[430B];
Display: POINTER = LOOPHOLE [420B];
DisplayOn: BOOLEAN ← TRUE;
savedDisplay: WORD;

parityInterruptBit: CARDINAL = 1;

bandWidth: CARDINAL = 16;


beamOnCommand: CARDINAL = 0;
statusCommand: CARDINAL = 1;
resetCommand: CARDINAL = 2;
printCommand: CARDINAL = 3;


--Signals and Errors

--
Procs

SlotInit: PUBLIC PROC []=
{
SLOTCommand[reset];
SLOTCommand[beamOn];
SlotReady[];
};--
SlotInit

SLOTCommand: PROC[cmd: SlotCommand]=
BEGIN
i: CARDINAL;
sCB:SCB;
scb: POINTER TO SCB ← @sCB;

scb ← SetSCB[scb];--align to even word boundary and init some fields.
SELECT cmd FROM
setDensity, beamOn =>scb.com ← beamOnCommand;
reset =>{illegalState ← FALSE; printFail ← 0; overRun ← FALSE; readErrors ← 0;
scb.com ← resetCommand;};
stopPrint, beamOff =>scb.com ← resetCommand;
ENDCASE =>{illegalState ← TRUE; GOTO done};
slotCommandBlock↑ ← LOOPHOLE[scb];
IF useSLOT THEN
{[] ← StartIO[slotSIObit];
FOR i IN [0..30000]
DO
IF scb. status # ReturnStatus[0,0,0,0,0,0,0,0,0] THEN EXIT;
ENDLOOP;

IF scb.status = ReturnStatus[0,0,0,0,0,0,0,0,0] THEN illegalState ← TRUE;
IF cmd = beamOn THEN MsWait[1000];
};
EXITS
done => NULL;
END;
--SlotCommand

SlotStatus
: PROC[metaStat: MetaStat]=
{
sCB:SCB;
scb: POINTER TO SCB ← @sCB;

IF useSLOT THEN
{scb ← SetSCB[scb];
scb.com ← statusCommand;
scb.status ← normalizedStatus;--some dummy bits
slotCommandBlock↑ ← scb;
[] ← StartIO[slotSIObit];
MsWait[37];--give microcode time to see new command

IF scb.status = normalizedStatus THEN illegalState ← TRUE;
scb.status ← BITXOR[scb.status, normalizedStatus];

metaStat.wait ← scb.status.wait # 0;
metaStat.paperJam ← scb.status.paperJam # 0;
metaStat.paperOut ← scb.status.addPaper # 0;
metaStat.illegal ← illegalState;
metaStat.pageNotPrinted ← printFail;
metaStat.overRun ← overRun;
metaStat.diskError ← readErrors;
metaStat.ready ← scb.status.ready = 1 AND NOT(metaStat.wait OR metaStat.paperJam OR metaStat.paperOut OR metaStat.illegal OR metaStat.pageNotPrinted # 0 OR metaStat.overRun OR metaStat.diskError # 0);
}
ELSE metaStat.ready ← TRUE;
};--SlotStatus

PrintError
: PROC[err: PError, num: CARDINAL]=
--This routine should turn the display on if it was off and type appropriate message, then turn the display back off to continue.
--if the error is notSure, display the whole slotStats structure
{displayWasOn: BOOLEAN ← DisplayOn;
continue: BOOLEAN ← FALSE;
canContinue: BOOLEAN ← FALSE;

IF ~DisplayOn THEN
{Display↑ ← savedDisplay; DisplayOn ← TRUE};
SELECT err FROM
noPaper => {WriteLine["Reload printer with paper. Type any key to continue."L];
[]←ReadChar[];
continue ← TRUE;
IF ~displayWasOn THEN
{savedDisplay ← Display↑; Display↑ ←0; DisplayOn ← FALSE};
GOTO getOut;
};
jam => {WriteLine["Printer jammed. Fix, then type any key to continue."L];
[]←ReadChar[];
continue ← TRUE;
IF ~displayWasOn THEN
{savedDisplay ← Display↑; Display↑ ←0; DisplayOn ← FALSE};
GOTO getOut;
};
firstFillFailed, firstFillTimeOut=>
WriteLine["Trident didn’t get off the ground."L];
pageTimeOut=>WriteLine["Page took too long to print."L];
overRan=>{WriteString["Disk Overran "L]; WriteDecimal[num]; WriteLine[" times."L]; canContinue ← TRUE};
readError=>{WriteDecimal[num]; WriteLine[" readErrors."L];};
notSure =>{WriteLine["Unknown Error"L];};
ENDCASE;

IF canContinue THEN
{WriteString["Type Y to continue, anything else to exit: "L];
IF (UpperCase[ReadChar[]] = ’Y) THEN continue ← TRUE; WriteChar[CR]};
IF ~continue THEN abortFile;

EXITS
getOut=> NULL;

};
--PrintError

SlotReady: PROC [metaStat: MetaStat ← NIL]=
{
aqMetaStat: AqMetaStat;
mStat: MetaStat ← @aqMetaStat;

IF metaStat = NIL THEN
{metaStat ← mStat;
SlotStatus[metaStat];};
IF ~useSLOT THEN metaStat.ready ← TRUE;
IF ~metaStat.ready THEN
{SLOTCommand[reset];
SELECT TRUE FROM
metaStat.paperOut => PrintError[noPaper, 0];
metaStat.paperJam => PrintError[jam, 0];
metaStat.pageNotPrinted # 0 =>
SELECT metaStat.pageNotPrinted FROM
1 => PrintError[firstFillFailed, 0];
2 => PrintError[firstFillTimeOut, 0];
3 => PrintError[pageTimeOut, 0];--took too long to print entire page
ENDCASE;
metaStat.overRun => {IF pageRetries > maxPageRetries
THEN PrintError[overRan, pageRetries]
ELSE pageRetries ← pageRetries + 1};
metaStat.diskError # 0 => PrintError[readError, metaStat.diskError]
ENDCASE => PrintError[notSure, 0];
SLOTCommand[reset];
SLOTCommand[beamOn];
};
};
--SlotReady

MsWait
: PROC[ms: CARDINAL]=
{
timeOut: CARDINAL ← RTC↑ + ms/37;--RTC runs at ~38 uSec/click, but clicks are measured at a scale of ~1024 uSec/click
UNTIL RTC↑ >= timeOut DO ENDLOOP;
};
--MsWait

--Pslot grabs as much storage as it can for buffers of scan lines. Each buffer holds a multiple of TfsWordsPerPage
--The buffers are arranged in a ring, with @buffer-1 holding the address of the next buffer. The storage is grabbed from the system as
-- one large segment if it can get it, and then divided into the buffers.
--The display machinery is turned off here in the final product. It is turned back on if there is an eror.
--SLOTPrint is then called to print the page by filling the buffers and passing them to the microcode.
PSlot: PUBLIC PROC[disk: tfsdskPtr, pageG: PageGptr, lastPage: BOOLEAN, firstVDA: PAGE, filePtr: POINTER TO FP] =
BEGIN
i, nBufs, oldActive: CARDINAL;
zoneBase, firstBuf, buf: POINTER;
aqMetaStat: AqMetaStat;
BEGIN ENABLE UNWIND => IF zoneBase # NIL THEN FreeWords[zoneBase];

firstBitPageDA: PAGE ← pageG.BitPage + firstVDA;--assume a contiguous file for now
metaStat: MetaStat ← @ aqMetaStat;
buffLen: CARDINAL ← (1024*((pageG.BitWc*bandWidth+1023)/1024))+2;

pageRetries ← 0;
nBufs ← 10;
IF ~debug THEN
{oldActive ← active↑;
active↑ ← BITAND[oldActive, BITNOT[parityInterruptBit]]};

--get buffer space
zoneBase ← Words[nBufs * buffLen!
InsufficientVM, ZoneTooLarge =>
{nBufs ← nBufs - 1; RETRY}];
IF nBufs < 10 THEN WriteLine["Didn’t use 32K of buffer space"L];
IF nBufs < 2 THEN
{active↑ ← oldActive;
FreeWords[zoneBase];
WriteLine["Not Enough Memory. Type any char to continue"L];
[]←ReadChar[];
RETURN;};


--the microcode requires the bit array to begin on an even word boundary AND bit array[-1] must be available for linking the pointers.
--Since MakeNode has a bookkeeping word at ptr-1, we must round up. Hence the +2 in the original calc.
buf ← firstBuf ← (IF BITAND[zoneBase, 1] THEN zoneBase+1 ELSE zoneBase + 2);
FOR i IN [0..nBufs)
DO
(buf-1)↑ ← buf+buffLen;
buf ← buf+buffLen;
ENDLOOP;
(buf-(buffLen+1))↑ ← firstBuf;--backup to fix up last pointer & complete the ring.

DO
SLOTPrint[disk, lastPage, pageG.FirstBand*bandWidth, pageG.BitMargin, pageG.BitWc*16, (pageG.LastBand-pageG.FirstBand+1)*bandWidth, bandWidth, firstBuf, firstBitPageDA, metaStat];--print this page till it’s printed without errors.
IF (~metaStat.ready AND (metaStat.wait OR metaStat.paperJam OR metaStat.paperOut OR metaStat.illegal OR metaStat.pageNotPrinted#0))
THEN
{SLOTCommand[reset];--only clear Slot Errors (not disk errors)
SlotReady[metaStat];}
ELSE
EXIT;
ENDLOOP;

FreeWords[zoneBase];
zoneBase ← NIL;
IF ~debug THEN
{wakeupsWaiting↑ ← BITAND[wakeupsWaiting↑, BITNOT[parityInterruptBit]];
active↑ ← oldActive;};

END;
--of UNWIND
END;--PSlot
SLOTPrint: PROC[disk: tfsdskPtr, lastPage: BOOLEAN, scanMargin, bitMargin, scanLength, numScans, scansPerBuffer: CARDINAL, firstBuf: POINTER, FirstDA: PAGE, metaStat: MetaStat]=
{i, timeOut: CARDINAL;--i is used in two loops
dontUseTrident: BOOLEAN ← FALSE;
buf, currentBuf: POINTER;
dcb: kcbPtr;--used in first half of code and in inner loop
dcbTable: POINTER;
dcbSegment: DataSegmentHandle;
dcbTableSize: CARDINAL ← ((32*lKCB)/(PageSize))+1;--in alto pages
labelBuf: ARRAY [0..10) OF WORD;
tridentStart: CARDINAL = 40B;
tridentStop: CARDINAL = 20B;
sCB:SCB;
scb: POINTER TO SCB ← @sCB;
numWords: CARDINAL ← BITSHIFT[scanLength, -4];
sectorsPerBuffer: CARDINAL ← (scansPerBuffer * numWords+1023)/1024;
--
these vars were in PrintBuffer and ReadBuffer
status: Status;
DCBseal: CARDINAL = 122645B;
startNext: kcbPtr;
daPtr: POINTER TO DA;

--vars that are needed by TfsRealDA replacement

nSectors: CARDINAL ← disk.tfskd.kdh.nSectors;
nHeads: CARDINAL ← disk.tfskd.kdh.nHeads;
quotient: CARDINAL;

da ← FirstDA;
printCount ← readCount ← (numScans+scansPerBuffer-1)/scansPerBuffer;
scb ← SetSCB[scb];

--must init buf etc. here to avoid "warning: multiple initialization with a pointer"
scb.currentBuf ← scb.bufferPtr ← buf ← currentBuf ← firstBuf;
scb.com ← printCommand;
scb.lastPage ← IF lastPage THEN 1 ELSE 0;
scb.bitMargin ← bitMargin + SLOTBitMarginAdjust;
scb.scanLineWc ← BITSHIFT[scanLength, -5];--shift right 5 bits
scb.scanMargin ← scanMargin + SLOTScanMarginAdjust;
scb.scansPerPage ← numScans;
scb.scanLineWcInc ← numWords;
scb.scansPerBuff ← scansPerBuffer;

slotCommandBlock↑ ← scb;--give microcode the pointer, (uCode not running yet).

--get enough room for 64 buffers of 1024 words each
dcbSegment ← NewDataSegment[DefaultBase, ((64*lKCB)/(PageSize))+1];
dcbTable ← DataSegmentAddress[dcbSegment];
dcb ← LOOPHOLE[dcbTable, kcbPtr];

DO
readNext ← dcb;
FOR i IN [0..sectorsPerBuffer) DO
Zero[dcb, lKCB];
dcb.nextKCB ← dcb + lKCB;
dcb.blockH.comm ← diskCheck;
dcb.blockH.count ← 2;
dcb.blockH.addr ← LOOPHOLE[@dcb.diskAddress, dlPtr];--disk label pointer
dcb.blockL.comm ← diskRead;
dcb.blockL.addr ← LOOPHOLE[@labelBuf, dlPtr];
dcb.blockL.count ← 10;
dcb.blockD.comm ← diskRead;
dcb.blockD.count ← TFSwordsPerPage;
dcb.blockD.addr ← (buf+i*TFSwordsPerPage);
dcb ← dcb.nextKCB;
ENDLOOP;
ReadBuffer[FALSE, sectorsPerBuffer, disk];
buf ← (buf-1)↑;
IF buf = firstBuf THEN EXIT;
ENDLOOP;--buffers filled

(dcb-lKCB).nextKCB ← dcbTable;
readNext ← printNext ← dcbTable;

IF noDisplay THEN
{savedDisplay ← Display↑;
DisplayOn ← FALSE;
Display↑ ← 0;};

IF ~dontUseTrident THEN
{[] ← StartIO[tridentStart];
track↑ ← 177777B;
statusptr↑ ← Mstatus[1,1,1,1,1,1,1,1,1,1,1,1, 17B];
ptr↑ ← dcbTable;
};


timeOut ← RTC↑ + SLOTTimeOut*27;--give us SLOTTimeOut seconds to finish page

--PrintBuffer will return true as soon as the first buffer is filled (with or w/o errors)
UNTIL PrintBuffer[sectorsPerBuffer] DO
IF RTC↑ >= timeOut THEN printFail ← (IF printNext = firstBuf THEN 1 ELSE 2);
ENDLOOP;


overRun ← FALSE;

IF printFail = 0 THEN--start the SLOT!
{IF useSLOT THEN
[]←StartIO[slotSIObit];

DO
IF scb.currentBuf # currentBuf THEN--uCode started outputting next buf
{currentBuf ← scb.currentBuf;
--Put PrintBufer and ReadBuffer in line
--IF ~ PrintBuffer[sectorsPerBuffer] THEN EXIT;----"must be full" i.e., end of buffers to read
-- ReadBuffer[TRUE, sectorsPerBuffer, disk];
-------------- PrintBuffer code ------------------------------------------------
dcb ← printNext;

IF (printCount ← printCount - 1) # 0 THEN--go check result of another transfer
{FOR i IN [1..sectorsPerBuffer] DO
IF dcb.blockD.status = dstZeroStatus THEN {overRun ← TRUE; EXIT};--exits print loop
status ← BITOR[BITOR[dcb.blockH.status, dcb.blockD.status], dcb.blockL.status];
IF status # dstDone THEN
readErrors ← readErrors + 1;
dcb ← dcb.nextKCB;
dcb.blockD.status ← dstZeroStatus;--added for debugging!
ENDLOOP;

printNext ← dcb;
}; --IF (printCount ← printCount - 1) # 0
-------------- End PrintBuffer code --------------------------------------------
-------------- ReadBuffer code ------------------------------------------------

startNext ← readNext;
--readCount ← readCount - 1; No one references this.
FOR i IN [1..sectorsPerBuffer] DO
daPtr ← LOOPHOLE[readNext];
--daPtr↑ ← TfsRealDA[disk, da];----returns a DA record (2 words)
-------------- TfsRealDA code ------------------------------------------------
--This code could be replaced by keeping track of sector,,head,,track info with conditionals & eliminate
-- the MOD’s and /’s
daPtr↑.sector ← da MOD nSectors;
quotient ← da/nSectors;
daPtr↑.head ← quotient MOD nHeads;
daPtr↑.track ← quotient/nHeads;
-------------- End TfsRealDA code --------------------------------------------

da ← da + 1;
readNext.blockD.status ← dstZeroStatus;
readNext.id ← DCBseal;
readNext ← readNext.nextKCB;
ENDLOOP;
IF ptr↑ = NIL THEN ptr↑ ← startNext;
-------------- End ReadBuffer code --------------------------------------------

};
IF scb.status # ReturnStatus[0,0,0,0,0,0,0,0,0] THEN EXIT;--from inner loop
--IF ~ debug THEN
IF RTC↑ >= timeOut THEN {printFail ←3; EXIT};
ENDLOOP;--DO above IF scb.currentBuf #
};--IF printFail = 0 THEN

ptr↑ ← NIL;--quiet disk
DeleteDataSegment[dcbSegment];
IF ~DisplayOn THEN
{Display↑ ← savedDisplay;
DisplayOn ← TRUE;
};
SlotStatus[metaStat];--return final status
IF lastPage THEN {MsWait[6000]; SLOTCommand[reset];};
};
--SLOTPrint
PrintBuffer: PROC[sectorsPerBuffer: CARDINAL] RETURNS[ok: BOOLEAN ← TRUE]=
{
i: CARDINAL;
dcb: kcbPtr ← printNext;
status: Status;

IF printCount = 0 THEN RETURN;
printCount ← printCount - 1;
FOR i IN [1..sectorsPerBuffer] DO
IF dcb.blockD.status = dstZeroStatus THEN {overRun ← TRUE; RETURN [FALSE];};
status ← BITOR[BITOR[dcb.blockH.status, dcb.blockD.status], dcb.blockL.status];
IF status # dstDone THEN
readErrors ← readErrors + 1;
dcb ← dcb.nextKCB;
ENDLOOP;

printNext ← dcb;
};
--PrintBuffer

ReadBuffer
: PROC[startUp: BOOLEAN, sectorsPerBuffer: CARDINAL, disk: tfsdskPtr ]=
{
i: CARDINAL;
DCBseal: CARDINAL = 122645B;
startNext: kcbPtr ← readNext;
daPtr: POINTER TO DA;

readCount ← readCount - 1;
FOR i IN [1..sectorsPerBuffer] DO
daPtr ← LOOPHOLE[readNext];
daPtr↑ ← TfsRealDA[disk, da];--returns a DA record (2 words)
da ← da + 1;
readNext.blockD.status ← dstZeroStatus;
readNext.id ← DCBseal;
readNext ← readNext.nextKCB;
ENDLOOP;
IF startUp AND ptr↑ = NIL THEN ptr↑ ← startNext;
};
--ReadBuffer

SetSCB: PROC[scb: POINTER TO SCB] RETURNS[newSCBadr: POINTER TO SCB]=
{
Zero[scb, SIZE[SCB]];
newSCBadr ← Even[scb];
newSCBadr.invert ← IF invertMode THEN 1 ELSE 0;
newSCBadr.bitsPerLine ← SLOTScanLength;
newSCBadr.blowup ← IF SLOTDouble=0 THEN 0 ELSE 1;
};
--SetSCB
END.--PrintSlot
-- Last Edited: September 15, 1981 6:19 PM By: GWilliams
--noDisplay=FALSE (i.e., leave it on) and useSlot=FALSE, debug=FALSE(do timeouts)
--October 26, 1981 2:36 PM By: GWilliams
--cause PSlot to clear disk errors (& print a goofed-up page) a few times before erroring.

-- Last Edited: October 27, 1981 1:32 PM By: GWilliams
--SLOTPrint runs too slow dat 384 spi. Put PrintBuffer and ReadBuffer inline in inner loop