; InOutLd.asm - InLd, OutLd, BootFrom
; Copyright Xerox Corporation 1979
; last modified November 29, 1979  7:54 PM by Taft

	.titl InOutLd

.ent OutLd, InLd, BootFrom
.ent OutLdContu
.bext LastLdCB

	.srel

OutLd:		.OutLd
InLd:		.InLd
BootFrom:	.BootFrom
OutLdContu:	.OutLdContu
LastLdCB:	0

; OutLd and InLd are used to save Alto memory images.
; A complete save-restore cycle happens like this:
;
; OutLd(fp,ptr-to-OutLd-message-area)
;	writes a loader on file described by fp
;  	writes pages 2,3,... to 253.
;		writes pages 1,0
;		returns 0
; InLd(fp,ptr-to-InLd-message)
;	reads the fp file's loader into page 0,
;	copies InLd-message into page 0
;	and jumps to the loader.
;	the loader reads in pages 2,3,... to 253.
;		transfers to the restored outld
;	outld	copies the message into the OutLd-message-area
;		restores page 1, via page 0
;		restores page 0
;		returns 1 from OutLd with INTERRUPTS OFF
;
; BootFrom(fp) simulates a machine boot on the file fp.
; The loader that is written makes the file bootable directly.  It is just
; like the InLd call above, but the corresponding return from OutLd is
; 2, so that it can discover it was booted (no message).
;
; Immediately after a return from OutLd, LastLdCB (static) points to the
; disk command block for the last page of the file that was InLd'ed or
; Booted.  
;	Small print passage: Because OutLd and InLd are not reentrant
;	and because Swat uses them, you should copy the disk command
;	block quickly -- do not let Swat hit a breakpoint or whatever
;	before this is copied, or you will simply copy the corresponding
;	information for one of the Swat swapping files!
;
; If either ptr-to-xxx-message lies in page 0 or 1, the message will
; not be correctly delivered.  So if you don't want to receive a message,
; simply pass that argument as 0.
;
; Both routines preserve AC2, and return to call+2, so are BCPL-callable.
;
; These routines are tightly intertwined with the "disk" routines and
; the page 0 loader.
;
; Note that OutLd and InLd themselves are NOT reentrant.
;
; Unless you know what you are doing, you should turn off interrupts
; when calling OutLd.  If interrupts are popping off while state is being
; written on the file, strange things can happen (e.g., the "active" word
; can reflect the state inside an interrupt, with certain interrupt channels
; off!).
;
; structure fp:
;	[
;	SerialNumber word 2
;	Version word
;	blank word
;	Page1RealDA word	//**** differs from OS defn of FP ****
;	]
;
; OutLd/InLd file is:
;	loader (1 page)
;	images of pages 2 - 253.
;	image of page 1
;	image of page 0


InLdMessageSize = 23		; size of message area
MaxAddr = 176777		; highest real memory address in machine

	.NREL

; TLABL takes an FP pointer in ac0 and returns a label block
; pointer in ac1 -- Warning: destroys ac2.
; Therefore, returns ac2 = 0 as a handy side-effect.
TLABL:	STA 3 LblBlk+LblPageNum	; return address -- ok to use, CONTC resets it
	JSR TLABL2		; put ptr into ac3
LblBlk:	.BLK 10			; fabricated label goes here
TLABL2:	MOV 0 2			; 2 => fp
	LDA 0 0,2
	STA 0 LblSerial1,3
	LDA 0 1,2
	STA 0 LblSerial2,3
	LDA 0 2,2
	STA 0 LblVersion,3
	LDA 0 4,2		; get disk address
	STA 0 LblNext,3		; and put it in "next"
	MOV 3 1			; answer ... ac1 => label block
	MKZERO 2 2
	JMP @LblBlk+LblPageNum	; return


; Routines to copy parts of pages around:
; AC0: first source address, AC1: first destination address.
; Return with AC0 and AC1 updated to point to first words not touched.
;	JSR COPY
;	 negative of number of words to copy
;	returns here

;	JSR SKIPCOPY
;	 negative of number of words to skip over first
;	 negative of number of words to copy
;	returns here

SKIPCOPY: STA 3 T1,2
	ISZ T1 2		; -> - words to copy for COPY
	LDA 3 0,3		; - words to skip
	SUB 3 0
	SUB 3 1 SKP

COPY:	STA 3 T1,2
	MKONE 3 3
	SUB 3 0			; ac0← first source address -1
	LDA 3 @T1,2		; - words to copy
	ADC 3 1			; ac1← last destination address
	BLT			; updates ac0 to last source address +1
	INC 1 1			; ac1← last destination address +1
	LDA 3 T1,2
	JMP 1,3

; storage for outld and inld
WRITEa:		writeD
Booted:		.BLK 1
STACK:		.BLK 1
RETRN:		.BLK 1		; outld's ra
CVEC:		.BLK 1		; caller's message vector (InLd or OutLd)
TVEC:		MVEC-BootEntry+1	; page 0 address of mvec
TVECplus1:	MVEC-BootEntry+2	; 1+ page 0 address of mvec
LabelOffseta:	LABEL		; offset into KCB where label is
.LastLdCB:	LastLdCB	; will point to last CB used to InLd

;*********** OUTLD ***********
; AC0 = FP
; AC1 = an InLdMessageSize word vector to receive message upon resumption
.OutLd:	STA 3 RETRN
	STA 2 STACK		; Save it for returns!
	STA 1 CVEC		; output message vector

	JSR TLABL		; transform ac0 fid into ac1 -> label block!
	STA 2 @.BOOTFLAG	; 0 => Swatee was created by OutLd
	JSR GETFullDK		; get pointer to FullDK
	MOV 3 2
	LDA 0 WRITEa		; write and check command
	JSRII ISETU		; set up command blocks
	LDA 0 @LOADRP
	JSR PAGEC		; write out loader

	LDA 0 N400		; page 2 address-400
				; note CUR=KCB2 now
	STA 0 KCB2+CMADD,2	; BMPPAGE will increment it by 400.
	JSRII ITRANSFER		; write out bulk of mem. (pages 2 through end)
	LDA 0 N400		; write page 1
	JSR PAGEC
	JSR PAGEC0		; write page 0
	MKZERO 0 0		; outld finished, return 0
	JMP EXIT

.BOOTFLAG:	706

; Many moons later, the other guy calls inld on what was just written
;  above.  He yanks in and runs the page 0 loader, which comes here
;  when the core is transferred:

; The page 0 loader returns here when file is restored (all but page 0 & 1).
; interrupts are OFF.

; AC2 = pointer to FullDK (in REAL page 0)
; tvec = message vector in page 0
.OutLdContu:
	LDA 0 CUR,2 
	LDA 1 LabelOffseta
	ADD 0 1			; AC 1 points to label when xfer finished
	JSR GETFullDK		; get the FullDK NOT in page 0
	MOV 3 2
	LDA 0 READ
	JSRII ISETU		; setup to read page 1

	LDA 0 @TVEC
	STA 0 Booted
	SNZ 0 0			; message present?
	 JMP RP1		; no
	LDA 0 TVECplus1		; yes, ac0 ← @tvec+1
	LDA 1 CVEC		; pointer outload got eons ago
	JSR COPY		; copy message into user's area
	    -InLdMessageSize	; (if user's area in page 0 or 1, will be lost)

RP1:	JSR PAGEC0		; read in putative page 1 into page 0	
				; move it carefully into its rightful place.
	MKZERO 0 0		; start at loc 0
	LDA 1 N400		; ac1 runs through page 1
	JSR COPY
	    400-430		; restore 400 to 427 inclusive
	JSR SKIPCOPY
	    430-431		; skip real time clock
	    431-521		; restore 431 to 520 inclusive
	JSR SKIPCOPY
	    521-524		; skip disk command area
	    524-570		; restore 524 to 567 inclusive
	JSR SKIPCOPY
	    570-600		; skip clock storage area
	    600-1000		; restore 600 to 777 inclusive
	JSR PAGEC0		; page 1 restored. read in page 0.
	LDA 0 CUR,2		; get pointer to current KCB
	STA 0 @.LastLdCB	; save for user's scrutiny
	LDA 0 Booted		; see if we had a message
	SNZ 0 0
	 INCZL 0 0		; if it was zero, we booted. return 2
EXIT:	LDA 2 STACK
	LDA 3 RETRN
	JMP 1,3			; assume called from bcpl

; static pointers 

ISETU:		DiskIOSetup
ITRANSFER:	DiskIOTransfer
LOADRP: 	Page0Loader

;************  INLD  ************

; Many eons later inload is invoked by a running program, passing
;  the file id of the file we just wrote on ...
; AC0 = FP of file to boot
.BootFrom:
	MKMINUSONE 3 3 SKP

; AC0 = FP of saved memory image
; AC1 = message to be passed to it
.InLd:	MKZERO 3 3
	STA 3 Booted		; True if we are to use boot conventions
	STA 1 CVEC		; input message, no need to save ac3

	DIR			; no interrupts on

	JSR TLABL		; translates ac0 fid into ac1 label, ac2←0
	STA 2 @N420		; clear screen

	JSR GETFullDK
	MOV 3 2
	LDA 0 READ		; read and check command
	JSRII ISETU

	MKONE 0 0		; read loader into 1,2,...,400 (page 0)
	JSR PAGEC		; error will bomb and show lights

	LDA 0 CUR,2		; get at current label block
	LDA 1 LabelOffseta
	ADD 1 0			; ac0 => label after the read
	LDA 1 N402		; copy label block
	JSR COPY		; to 402,403,...
	    -10			; because that's where boot microcode puts it
	ISZ Booted		; see if we're supposed to boot
	 MKONE 3 3 SKP		; must be one (see below)
	 JMP 1			; yes!!!!
	STA 3 @TVEC		; signal message present
	LDA 0 CVEC		; copy message
	LDA 1 TVECplus1		; to page 0
	JSR COPY
	    -InLdMessageSize
	JMP 3			; jump to page 0 loader!

GETFullDK: JMP DKBlock		; can't reach DKBlock from where it is needed

; Simple one-page transfer using DiskIO routines below.
PAGEC0:	MKZERO 0 0		; enter here to transfer page 0

; ac0 = main mem address to save or restore process and check page
PAGEC:	STA 3 T1,2
	LDA 3 N400
	SUB 3 0			; compensate for BMPPAGE's work.
	LDA 3 CUR,2
	STA 0 CMADD,3		; and save in current block.
	JSR CONTC		; make up a command block
	JSR EXCHCB		; promote the actors
	STA 1 @KBLK		; start the disk
	JSR WAITC		; and wait for completion
	JMP @T1,2

; Disk I/O routines.  Used by InLd/OutLd swapper and can also be used
; by others.  The special organization for a boot loader is for
; InLd/OutLd purposes.
;
;The routines all require a storage area of size DKlen to work
; with, passed in Ac2.  For a template, see FullDK below.  Notice that it
; has actual code in it. 

;DiskIOSetup
;	ac0 = disk command [read (44120) or write (44130)]
;	ac1 => label block for PREVIOUS page of file.  The
;		LblNext entry of this label is the next DA to operate on.
;	ac2 => work area of length DKlen
;	returns with ac2 set up to go -- pass to DiskIOTransfer

;DiskIOTransfer
;	ac2 => work area, set up.
;	calls BMPPAGE (in work area; see template) to compute label and
;		disk command block for the next page to transfer.
;		(if you are doing a simple read or write, all you need
;		to compute is the core address, because CONTC has
;		computed the rest -- see code below)
;	if an error occurs, transfer to BAD (in work area; see template)
;	returns with ac2 full of left-over goodies (e.g., next disk address)
;	In particular, ac2!CUR => KCB for last page transferred
;	(perhaps in error).  That KCB!LABEL is, of course, the label.

	.srel
	.ent DiskIOSetup,DiskIOTransfer

DiskIOSetup:	.DiskIOSetup
DiskIOTransfer:	.DiskIOTransfer
Page0Loader:	BootEntry

	.nrel

; Definitions for a disk label block:

LblNext = 0		; DA for next page of file
LblBack = 1		; DA for previous page of file
LblSpare = 2		; Not currently used
LblNumChars = 3		; Number of chars in last page of file
LblPageNum = 4		; Page number in file
LblVersion = 5		; File version number
LblSerial1 = 6		; File serial number (high order)
LblSerial2 = 7		; File serial number (low order)

; Definitions for a disk control block (augmented for our use)
NXTCM = 0		; Next dcb pointer
STATU = 1		; Status
CMMD = 2		; Command
HDRPT = 3		; Header block pointer (points to HDR1)
LBLPT = 4		; Label block pointer (points to NEXTP)
CMADD = 5		; Data block pointer
OKINT = 6		; No-error interrupt bit mask
ERRIN = 7		; Error interrupt bit mask
HDR1 = 10		; Here is where header gets read in
CURRE = 11		; Disk address for the command

LABEL = 12		; A place to hold a label:
NEXTP = LblNext+LABEL
BACKP = LblBack+LABEL
UNUSE = LblSpare+LABEL
NUMCH = LblNumChars+LABEL
PN = LblPageNum+LABEL
VN = LblVersion+LABEL
SN1 = LblSerial1+LABEL
SN2 = LblSerial2+LABEL

KCBSize = SN2+1		; Length of KCB's we use

; Now for the layout of the temporary storage area (DK)
CUR = 0			; Points to "current" KCB
ALT = 1			; Points to the "alternate" KCB
KCB1 = 2		; First Disk Command block -- MUST be 2 (see SetupC)
KCB2 = KCB1+KCBSize	; Second command block
TRYCT = KCB2+KCBSize	; Temporaries (use with care)
T1 = TRYCT+1
T2 = T1+1
T3 = T2+1
BAD = T3+1		; Control transfers here when error encountered
BMPPAGE = BAD+1		; Control transfers here to "increment" page

readD = 44120		; check header, check label, read data
writeD = 44130		; check header, check label, write data

; The page 0 loader - See BuildBoot documentation for details
;
; This code (next 256 words) was written on the saved file
; and executes in page 0.  If it is booted or read in by Inld, location
; BootEntry will be 1.  It expects the label for the page on which it resides
; to be found at 402, 403,... (Boot does this; so does InLd)
;
; To Boot (following is EXACTLY what boot button does):
;	Turn off the display
;	Disable interrupts
;	Read in the first data page of the file to 1,2,... 400
;	Store the label for the page read at 402,403,...411
;	Jump to location 1.
;
; To Inld:
;	Turn off the display
;	Disable interrupts
;	Read in the first data page of the file to 1,2,....400
;	Store the label for the page read at 402,403,...411
;	Let MessageSignalAddress=@2
;	Set @MessageSignalAddress ←1
;	Copy message to MessageSignalAddress+1,"+2,..."+InLdMessageSize
;	Jump to location 3.
;
; Note: the remainder of this file must fit in 256 words.
; If it does, nothing is to be gained by further code-bumming, since
; there is a .BLK statement immediately prior to MVEC to force
; MVEC to appear at a fixed place.

Loader0 = .-1			; Relocation address

;Location 1:
BootEntry:
	JMP BootClr-Loader0	; Go clear some things.

;Location 2:
	MVEC-Loader0		; Address for messagesignal
				; (this word clobbered if booting)
;Location 3:
InldEntry: jmp DoIt		; Come here to really start the load

	.blk 2			; date file was built put here

DoIt:	JSR DKBlock
	MOV 3 2
	LDA 0 READ
	LDA 1 N402		; get label left by InLd or Boot
	JSR .DiskIOSetup	; format the command blocks
	LDA 0 N400		; init address for bumping command
	STA 0 KCB1+CMADD,2	; set CUR's address
	JSR .DiskIOTransfer-Loader0  ; read in bulk of file
				; CUR holds next page info.
	JSRII .+1		; go back -- ac2 => FullDK
	 OutLdContu		; address in restored code

READ:		readD
N400:		400
N402:		402

;**** FullDK -- template that corresponds to "work area" ****
;   It is set up with a BMPPAGE that will transfer all of a 64K machine
;   and a BAD that will display the offending KCB in the "lights"

DKBlock: JSR 0,3

FullDK:	.BLK 2			; CUR, ALT
	.BLK KCBSize
	.BLK KCBSize
	.BLK 4			; TRYCT,T1,T2,T3

	JMP GVUP		; BAD: Come here on error

; BMPPAGE: Here to compute next core address
	STA 3 T3,2		; save return address
	LDA 3 CUR,2		; get current block
	LDA 0 CMADD,3		; and its core address
	LDA 1 N400		; AC0← address of next page
	ADD 1 0
	LDA 1 MAXAD		; past end of real memory?
	SGEU 0 1
	 ISZ T3 2		; no, skip return
	JMP @T3 2

MAXAD:	MaxAddr
N420:	420

;**** End of template ****

EXCHCB:	LDA 0 CUR,2		; exchange CUR and ALT
	LDA 1 ALT,2
	STA 1 CUR,2
	STA 0 ALT,2
	JMP 0,3

WAITC:	; d.c. probably active, working on cur
	STA 3 T2,2
	MKZERO 3 3		; init retry count
	STA 3 TRYCT,2
	JMP RETRY		; here to avoid small address problems

; Sets up alternate command block -- d.c. may be active.
; WARNING: if you call this yourself, be careful that BMPPAGE
;  does not think you are transferring beyond the last page, because
;  you will return in a funny way.
; The disk address for this one will be the LblNext entry
;  from the current command block.
; This routine sets up the new label to check everything
;  except LblNext and LblPageNum
; Calls BMPPAGE to compute next core address.
CONTC:	STA 3 T2,2
	LDA 3 CUR,2
	LDA 0 NEXTP,3		; CUR's next is our current
	LDA 1 CURRE,3		; 0 the first time, so no checking
	LDA 3 ALT,2
	STA 0 CURRE,3		; new disk address
	STA 1 BACKP,3
	MKZERO 0 0
	STA 0 NXTCM,3		; no command after it yet
	STA 0 STATU,3		; zero status
	STA 0 NEXTP,3		; do not check this label entry
	STA 0 PN,3

; Note: LbLSpare and LblNumChars will be checked because there is
;  an old value left over from the last transfer that used this KCB.
; Version and Serial numbers will be checked if they were non-zero
;  in the label passed to DiskIOSetup

; Call the "BumpPage" routine to compute a core address for
;  reading or writing the next page.  Routine accepts:
;	CUR = pointer to KCB for current page
; Routine skip returns in ac0 the new core address.
; If no skip return, there are no more pages to transfer.

	JSR BMPPAGE,2		; AC0 ← new core address to use.
	 JMP LAST		; Warning: this returns from DiskIOTransfer.
	LDA 3 ALT,2		; Skip return => ok
	STA 0 CMADD,3		; Save address
	STA 3 @CUR,2		; put link in active command
	JMP @T2,2		; ALT ready before d.c. follows CUR.NXTCM.

LAST:	JSR WAITC		; come here from CONTC on last page
	JMP @T1,2		; return from DiskIOTransfer

KBLK:		521		; mail box for disk controller
MinusKCBSize:	-KCBSize
N377:		377

; Disk controller is dormant
; AC0 = disk (read or write) command
; AC1 = label block whose nextp is address to begin with
; Initializes command/label blocks so that nextp in cur
;  is the disk address to start with.
.DiskIOSetup:
	sta 3 T1 2		; save return address
	sta 1 T3 2		; save label ptr -1
	dsz T3 2
	sta 0 KCB1+CMMD 2	; store command in KCBs
	sta 0 KCB2+CMMD 2

	lda 3 N2
	sta 3 T2 2		; Init loop count (used below)
	add 2 3			; Know KCB1 = 2
	sta 3 CUR 2		; Establish current CB

; Don't need to initialize NEXTCM or STATU because they are set up by
; DiskIOTransfer.  CMADD is initialized by caller of DiskIOTransfer.
SETUPL:	mkzero 0 0
	sta 0 OKINT 3		; Zero out cells that have to be zero
	sta 0 ERRIN 3
	sta 0 HDR1 3
	sta 0 CURRE 3
	lda 1 M10		; Header pointer -- point to HDR1 word of KCB
	sub 1 3
	sta 3 HDRPT-HDR1 3
	inc 3 1			; Label pointer -- know LABEL = HDR1+2
	inc 1 1
	sta 1 LBLPT-HDR1 3
	lda 0 T3 2		; Source label block -1
	lda 3 M10		; - label size
	adc 3 1			; AC1← last word of destination label
	blt			; Move label into CB (AC1 unchanged by BLT)

	dsz T2 2		; Second iteration?
	 inc 1 3 skp		; No, make pointer to second CB
	 jmp @T1 2		; Yes, done
	sta 3 ALT 2		; Establish alternate CB
	jmp SETUPL

M10:	-10	; -Offset of HDR1 word in CB, and -size of label

; d.c. dormant -- cur and alt have been set up.
; This code is asynchronous and took several trys to get right!
; It's supposed to be timing independent.
.DiskIOTransfer:
	STA 3,T1,2		; save return address
	JSR CONTC		; set up ALT block

SWTCH:	JSR EXCHCB		; exchange CUR and ALT
;	STA 1 @KBLK		; &&&&&

; All pages before CUR are processed and checked.
; Link of CUR is 0.  One of the following is true:
; 	d.c. is active, working on CUR
; 	d.c. is dormant, never started CUR
; 	d.c. is dormant, finished CUR
WAITL:	LDA 3 CUR,2
	LDA 1 NEXTP,3
	LDA 0 @KBLK
	SZ 0 0
	 JMP WAITL1		; d.c. active, started CUR
	LDA 0 STATU,3
	SZ 0 0
	 JMP FUNNY		; d.c. finished, probably an error
	STA 3 @KBLK		; d.c. never started cur
	JMP WAITL

; Warning about this skip: if we are writing labels, chances are that NEXTP is
; full with something, so skip will happen immediately.  But never fear,
; WAITC will really wait.  This does mean that the write will never be
; initiated unless the caller does it (sta 1,@KBLK above, commented with &&&&,
; is risky because of possibility of interrupts).
WAITL1:	SZ 1 1			; is NextP zero?
	 JMP LBLIN		; d.c. started CUR, working or finished.
	JMP WAITL		; d.c. active but label hasn't arrived yet.

FUNNY:	JSR WAITC		; Have WAIT check for errors; it may retry.
				; Label as well as data are in.

; transfer of CUR streaming in.  Time to set up ALT for next page
LBLIN:	JSR CONTC
	JSR WAITC		; for CUR's data
	JMP SWTCH

; WAITC transfers here when all is lost because of disk errors.
; AC3 = disk control block causing error.
; Display "lights" showing offending disk control block.
; store DCB's etc. at 2,...,61
GVUP:	NEG 3 3			; store bad block at 16...37
	COM 3 0			; ac0 = offending block -1
	LDA 1 N37
	LDA 3 MinusKCBSize
	BLT

	JSR SDCB		; AC3← DCB1-1
MARKW:	111111			; this marks the beginning!

DCB1:	6			; this word goes at 2
	0
N15:	2+(4*3)-1		; bitmap address of this dcb is irrelevant
	200			; skip 200 scan lines

DCB2:	12			; this goes at word 6
	100000+KCBSize
	16			; ptr to command block
	1

DCB3:	0
	100000+KCBSize
	40			; ptr to mark words
	1

SDCB:	MOV 3 0
	LDA 1 N15		; store dcb's in 2...15
	LDA 3 M14
	BLT

	LDA 0 MARKW		; store marks in 40...61
	LDA 1 N61
	LDA 3 MinusKCBSize
	BLKS

	LDA 0 N2		; start display at 2
	STA 0 @N420
	JMP .

M14:	-(4*3)
; N37:	16+KCBSize-1
; N61:	40+KCBSize-1

; Following is remainder of WAITC.
; At this point we know that the transfer has been started, so it's
; safe to spin waiting for the cb's status to become nonzero.
RETRY:	LDA 3 CUR,2
	LDA 0 STATU,3
	SNZ 0 0
	 JMP RETRY		; status still zero -- transfer not done yet
	LDA 1 N377
	AND 0 1 SNR
	 JMP @T2,2		; return if no error
	ISZ TRYCT 2		; increment retry count
	LDA 1 TRYCT 2		; see if exceeded max retry count
	LDA 0 N37
	SLE 1 0
	 JMP BAD 2		; return to fail point in working block

	MKZERO 0 0		; prepare to retry the command
	STA 0 STATU,3 
	STA 0 HDR1,3		; clear header word not to be checked
	STA 0 NEXTP,3		; clear label words not to be checked
	STA 0 BACKP,3
	STA 0 UNUSE,3
	STA 0 NUMCH,3
	STA 0 PN,3

	LDA 0 N7		; do a restore every 8 tries
	AND 0 1 SNR
	 JMP DOREST

DORST1:	STA 3 @KBLK		; and try it again (or issue the restore)
	JMP RETRY

DOREST:	STA 3 RestCB+NEXTP	; retry this command after restore
	LDA 0 CURRE 3		; get disk address
	LDA 1 N2		; isolate just drive number
	AND 1 0			;  (in particular, set cylinder # to zero)
	INC 1 1			; set Restore bit
	STA 1 RestCB+CURRE	; store address in Restore command block
	JSR DORST1		; issue RestCB followed by original command

RestCB:	0		; NEXTP -- filled in with ptr to command that failed
	0		; STATU
	44002		; CMMD -- no transfer (seek only)
N7:	7		; HDRPT -- *** these words are irrelevant, so I use
N37:	37		; LBLPT -- *** them for constants
N2:	2		; CMADD -- ***
	0		; OKINT
	0		; ERRIN
N61:	61		; HDR1  -- ***
	1		; CURRE -- filled in with drive # and Restore (only)

; ***** Move the following code beyond MVEC if it doesn't fit here *****
; The following code is ONLY called on a boot.  Hence it can use
;  absolute addresses.

BootClr: mkminusone 0 0		; Correct parity
	lda 1 MAXAD-Loader0
	com 1 3
	blt			; copy memory to itself (AC3←0)
	sta 3 @N420-Loader0	; turn off display
	jmp InldEntry-Loader0	; and go...

; *****

; ***** If the following gives an error (negative block size),
; ***** move the BootClr routine beyond MVEC.
	.BLK (400-(InLdMessageSize+1))-(.-Loader0) ; spacer(must be ge 0)

MVEC:	0	; this will appear at 400-InLdMessageSize-1 when loaded
		; 0 signals no message unless inld revises

; Message vector of length InLdMessageSize is placed here.
; If BootClr routine is put here, it gets clobbered during an InLd,
; but that's ok because BootClr is called only during a boot.

; Fancy instruction to decide whether we overflowed allowed 256 words.
; ***** Needed only if BootClr routine put after MVEC.
; *****	jmp Loader0+200	; causes asm error if too big.
			; (Note: this word is itself not in page 0)

;******* End of Page 0 Loader ****************************

	.END