; EtherBootLoader.asm -- Alto Ethernet boot loader
; Implemented (with much blood and sweat) by E. A. Taft, July 1976.
; Copyright Xerox Corporation 1979, 1983
; Last modified June 30, 1983  1:42 PM by Boggs

.ent bootLoaderPacket
.ent mayDayPacket

.srel

bootLoaderPacket: .bootLoaderPacket
mayDayPacket: .mayDayPacket

.nrel

; Format of an Ethernet-encapsulated Pup
etherAdr = 0		; Destination/source bytes
etherType = 1		; Ethernet type word
pupLength = 2		; Pup length
pupType = 3		; Transport control and type
pupID = 4		; Pup ID (2 words)
pupDNetHost = 6		; Pup destination net/host
pupDSocket = 7		; Pup destination socket (2 words)
pupSNetHost = 11	; Pup source net/host
pupSSocket = 12		; Pup source socket
pupContents = 14	; Pup contents start here

typeBreathOfLife = 602	; Ethernet type for a boot loader packet
typePup = 1000		; Ethernet type for a Pup
typeMayday = 244	; Pup type for boot file request
socketMiscServices = 4	; Socket to which request should be sent
socketEFTPReceiver = 20	; Our EFTP receiver socket

page0origin = 3		; Where page 0 portion of loader runs
page1origin = 624	; Where page 1 portion of loader will run

; Ethernet control word addresses
ePLoc = 600
eBLoc = 601
eELoc = 602
eLLoc = 603
eICLoc = 604
eIPLoc = 605
eOCLoc = 606
eOPLoc = 607
eHLoc = 610

; Convenient constant for accessing Ethernet control words by
; displacement addressing
eBase = 777

; The Ethernet Boot Loader.
; The first two words are reserved for the Ethernet header.
; The Alto jumps to the third word of the packet after receipt.

.bootLoaderPacket:	; Absolute address = 1 at runtime
	0		; Destination/source (filled in at runtime)
	typeBreathOfLife

; The following code runs only in page 0 and never migrates to page 1.
page0image:	; Absolute address = 3 at runtime

; Set the parameter that determines the desired boot file
	lda 0 @c177035		; Get keyboard bits
	com 0 0			; Turn them right side up
	sta 0 .mayDayPacket+pupID+1 ; Use as low 16 bits of Pup ID

; Correct all memory parity by reading and writing all words in memory
; (note that interrupts are off, so the parity errors won't cause trouble).
; EtherBoot procedure enters here.
boot0:	mkminusone 0 0		; First source adr = 0 (-1)
	lda 3 k1000		; Count = -177000
	com 3 1			; Last dest adr = 176777
	blt

; Zero selected regions in page 1
	mkzero 0 0
	lda 1 c427		; Zero 401-427
	lda 3 m27
	blks
	lda 1 c567		; Zero 431-567
	lda 3 m137
	blks
	lda 1 c777		; Zero 600-777
	lda 3 m200
	blks

; Copy top part of boot loader up to page 1 (note ac1 still contains 777)
	lda 0 pPage1Code
	lda 3 mPage1Count
	blt

; Set up a "Mayday" packet to broadcast
	movzr 1 1		; Make mask = 377
	sta 1 @.errcnt		; Max allowable output errors = 377
	lda 0 c3		; Reset Ethernet
	sio
	and 1 0			; Mask local ethernet address
	lda 2 .eBase
	sta 0 eHLoc-eBase 2	; Store for microcode
	jsr boot1		; Make pointer to output packet

; The "Mayday" packet that we broadcast
.mayDayPacket: 0	; Destination/source (filled in)
k1000:	1000		; Ethernet packet type = typePup
	22.		; Pup length
	typeMayday	; Transport control/type
timeout: 0		; Pup ID
	0		; 
	0		; Destination net/host (broadcast or filled in)
	0		; Destination socket
	socketMiscServices
	0		; Source net/host (filled in at runtime)
	0		; Source socket (EFTP receiver)
	socketEFTPReceiver
	-1		; Nil checksum

boot1:	sta 3 eOPLoc-eBase 2	; Set pointer to packet
	lda 1 etherAdr 3
	add 0 1
	sta 1 etherAdr 3	; Set Ethernet source host = me
	lda 1 pupSNetHost 3
	add 0 1
	sta 1 pupSNetHost 3	; Set Pup source host = me
	lda 0 d13
	sta 0 eOCLoc-eBase 2	; Set size of packet

; Page 0 portion of boot loader (cont'd)

; Loop here for each retransmission of broadcast request
boot2:	lda 0 c3		; Reset Ethernet
	sio
	jsr @.DoEtherOutput	; Send packet off

; Set up to receive reply
boot3:	lda 3 .eBase		; For accessing Ethernet control words
	lda 2 c1000		; Where to put reply (page 2)
	sta 2 eIPLoc-eBase 3
	lda 0 d269		; Max length
	sta 0 eICLoc-eBase 3
	sta 0 @.maxLength
	mkzero 0 0		; Zero post location
	sta 0 ePLoc-eBase 3
	lda 0 c2		; etherInputCommand
	sio			; Fire up receiver

; Await reply
boot4:	lda 0 ePLoc-eBase 3	; Anything happened?
	sz 0 0
	 jmp boot6		; Yes
	mul			; No, waste time
	dsz timeout		; Timed out (about 1 second)?
	 jmp boot4		; No, keep waiting
	dsz retries		; Yes, too many retries?
	 jmp boot2		; No, send another request
	lda 0 c3		; Yes, reset interface and give up
	sio
	jmp @GiveUp		; Return to caller or loop

GiveUp:	Bomb-D			; Overwritten by EtherBoot if returnOnFail

; Here when have possible reply
boot6:	lda 3 c377		; Good input done status?
	se 3 0
	 jmp boot3		; No, ignore
	lda 0 etherType 2	; Yes, is it a Pup?
	lda 1 c1000
	se 0 1
	 jmp boot3		; No, ignore
	lda 0 pupType 2		; Yes, get Pup type
	and 3 0
	lda 3 pupID+1 2		; Get sequence number
	lda 1 c30		; EFTP data?
	sne 0 1
	sz 3 3			; Sequence number zero?
	 jmp boot3		; No, ignore

; Page 0 portion of boot loader (cont'd)

; We got a good packet, set up for transfer.
	lda 0 etherAdr 2	; Get dest/source word
	sta 0 @.goodDestSource	; Save it
	lda 3 .ackPacket	; Where we will build the Ack
	sta 3 @.eOPLoc		; Set pointer (count already ok)
	sta 3 426-ackPacket+D 3	; Cursor coordinates = (531,531)
	sta 3 427-ackPacket+D 3
	movs 0 0		; Swap source and destination
	sta 0 etherAdr 3	; Store in Ack
	lda 0 c1000		; Type = Pup
	sta 0 etherType 3
	lda 0 d22		; Pup length = 22
	sta 0 pupLength 3
	inc 1 0			; Pup type = Ack = 31
	sta 0 pupType 3
	lda 0 pupDNetHost 2	; Copy and exchange source and
	sta 0 pupSNetHost 3	;  destination ports
	lda 0 pupDSocket 2
	sta 0 pupSSocket 3
	lda 0 pupDSocket+1 2
	sta 0 pupSSocket+1 3
	lda 0 pupSNetHost 2
	sta 0 pupDNetHost 3
	lda 0 pupSSocket 2
	sta 0 pupDSocket 3
	lda 0 pupSSocket+1 2
	sta 0 pupDSocket+1 3
	dsz pupContents 3	; Pup checksum = nil (-1)

; Now read and discard packet 0 (which has the disk boot loader)
; and read packet 1, which contains the data destined for page 0.
	jsr @.DoEtherOutput	; Ack the packet we just got
	isz @.seqNum		; Advance to sequence number 1
	jsr @.ReceiveEFTPPacket	; Read packet 1

; Set up to transfer packet data into page 0 and jump to remaining
; boot code in page 1
	lda 0 pPage2Data	; First source adr -1
	lda 1 c377		; Last dest adr
	com 1 3			; - number of words (400)
	lda 2 pPage2Packet	; Where to start reading packet 2
	jmp @.ContinueBoot	; Jump to code in page 1


c177035: 177035
d22:	22.
d269:	269.
c427:	427
c567:	567
.eOPLoc: eOPLoc
.eBase:
c777:	777
m27:	-27
m137:	-137
m200:	-200
pPage1Code: page1image-page0image+page0origin-1
mPage1Count: page1origin-1000
pPage2Data: 1000+12.-1	; Where page 0 data sits when in page 2
pPage2Packet: 1000-12.	; Where to start reading packet 2
.ackPacket: ackPacket-D	; Where Ack packet is built
.DoEtherOutput: DoEtherOutput-D
.ReceiveEFTPPacket: ReceiveEFTPPacket-D
.ContinueBoot: ContinueBoot-D
.maxLength: maxLength-D
.goodDestSource: goodDestSource-D
.errcnt: errcnt-D
.seqNum: seqNum-D
retries: 30.		; Max number of broadcast retries

; Page 1 portion of boot program

page1image:


; Assemble variables and constants here so they can be reached
; from the page 0 code also.


; Constants
c2:	2
c3:	3
d13:	13.
c30:	30		; typeEFTPData
c377:	377
c1000:	1000
md12:	-12.

D = page1image-page1origin	; Load - runtime displacement
; Note -
; To generate a page 1 pc-relative address for absolute address "X"
;  write "X+D".
; To generate an absolute (runtime) page 1 address for label "X"
;  write "X-D".

; Ethernet control words addresses, for referencing relative
; to the page 1 portion of the boot loader.
rEPLoc = ePLoc+D
rEBLoc = eBLoc+D
rEELoc = eELoc+D
rELLoc = eLLoc+D
rEICLoc = eICLoc+D
rEIPLoc = eIPLoc+D
rEOCLoc = eOCLoc+D
rEOPLoc = eOPLoc+D
rEHLoc = eHLoc+D

; Random page 1 words we use as temporaries.
; All are assumed to be initialized to zero.

; 400-414 are used for scratch

ackPacket = 431+D	; 13. words in which Ack packet is built.
			; This is the cursor bitmap!
errcnt = 564+D		; Output error countdown
recret = 565+D		; Return from ReceiveEFTPPacket
seqNum = 566+D		; Current EFTP sequence number
goodDestSource = 567+D	; Ethernet address of me/partner
maxLength = 611+D	; Max input packet length (269 or 12)
dallyTimeout = 612+D	; Timeout for end dally if nonzero
endFlag = 613+D		; Zero if packet was Data, nonzero if End


.ackID1: ackPacket-D+pupID+1  ; Pointer to Ack's sequence number

; Page 1 portion of boot loader (cont'd)
; Subroutines called from both page 0 and page 1

; Receive EFTP packet
;	2/ where to put it in memory
; Ac2 is unchanged upon return.
; endFlag will be zero if a Data was received, nonzero if End.
; If dallyTimeout is nonzero, it will be counted down and the
; routine will return if it reaches zero.

ReceiveEFTPPacket:
	sta 3 recret		; Save return
	sta 2 rEIPLoc		; Set input pointer

; Await a new packet
rec1:	lda 0 maxLength		; Set count
	sta 0 rEICLoc
	mkzero 0 0		; Zero post location
	sta 0 rEPLoc
	lda 0 c2		; etherInputCommand = 2
	sio			; Fire up receiver
rec1a:	lda 0 rEPLoc		; Wait for something to happen
	movs 0 1 szr
	 jmp rec1b		; Something happened, check it
	lda 0 dallyTimeout	; No activity yet, check timeout
	sz 0 0
	 dsz dallyTimeout	; One is set, count it down
	  jmp rec1a		; Not set or not yet timed out
	jmp @recret		; Timed out, return now

; A packet has arrived.  Accept if good input or buffer overrun.
; This code is a bit mysterious because the way it works is to
; set ac1 = 1000 iff the status is acceptable.  This then gets
; compared with the incoming Ethernet type which should be 1000.
rec1b:	lda 3 c377
	ands 3 1		; Ac1 ← 1000 iff post code = 2
	sne 0 3			; Good normal input done status?
	 lda 1 c1000		; Yes, make it look like post 2

; Ensure that it is an EFTP Data or End from the correct partner
	lda 0 etherType 2	; Get ethernet type word
	se 0 1			; Is it a Pup? (1000)
	 jmp rec1		; No, ignore
	lda 0 etherAdr 2	; Get destination/source word
	lda 1 goodDestSource	; Correct partner?
	se 0 1
	 jmp rec1		; No, ignore (should really abort)
	lda 0 pupType 2		; Get Pup type
	and 3 0
	lda 1 c30		; Switch on it
	sub 0 1 snr
	 jmp rec3		; Got Data packet
	inc 1 0
	inc 0 0 szr		; Skip if End
	 jmp rec1		; Neither Data nor End, ignore
rec3:	sta 1 endFlag		; Set nonzero if this is an End

; Got an EFTP Data or end packet.
; Now check sequence number and send Ack
	lda 0 pupID+1 2		; Get packet sequence number
	sta 0 @.ackID1		; Set it for possible Ack
	lda 1 seqNum		; Get expected sequence number
	sub 1 0 snr		; Is it the right one?
	 jmp rec4		; Yes
	inc 0 0 snr		; No, retransmission of last one?
	 jsr DoEtherOutput	; Yes, ack it
	jmp rec1		; Ignore it and look for next

; Here when got a new packet
rec4:	isz seqNum		; Advance sequence number
	lda 3 recret		; Restore return pc
				; Fall into DoEtherOutput

; Page 1 portion of boot loader (cont'd)

; Do Ethernet output
; Assumes the packet and eOPLoc and eOCLoc are set up
; Does not clobber ac2

DoEtherOutput:
	lda 1 k777
	negl 1 0		; Delay about 2 ms to ensure that
	inc 0 0 szr		;  our ack is heard
k777:	 777			; (Otherwise known as "jmp .-1")
	sta 0 rEPLoc		; Zero post location
	sta 0 rELLoc		; Zero load location
	sta 0 rEICLoc		; Zero input count location
	mkone 0 0		; etherOutputCommand = 1
	sio			; Start output
	lda 0 rEPLoc		; Wait for something to happen
	snz 0 0
	 jmp .-2
	sne 0 1			; Good output done? ( = 777)
	 jmp 0 3		; Yes, done, return
	dsz errcnt		; No, check error counter
	 jmp DoEtherOutput	; Retry

; If we get too many errors, probably the Ethernet interface
; is broken, so we should reset and give up lest we pollute
; the Ethernet indefinitely
Bomb:	lda 0 c3		; etherResetCommand
	sio
c400:	400			; (Otherwise known as "jmp .")

; ** Main page 1 program **
; Control resumes here when the page 0 code is finished.
; When we get here, the second data packet of the transfer has
; been received (the one destined for page 0) and now resides
; in page 2, and the ac's are set up such that blt will move
; the data to page 0.  This code's responsibility is to receive
; the remaining pages of the transfer and store them starting at
; page 2.  The following variables are already set up:
;	goodDestSource	expected Ethernet dest/source word
;	seqNum		the next expected EFTP sequence number (2)
;	ac2		points to place to store next packet
;			(1000 minus the offset pupContents)
; Also, an Ack packet has already been built at ackPacket.

ContinueBoot:
	blt			; Copy data to page 0

; The main loop of the EFTP boot load sequence.
; First, save away the stuff at the end of the previous page
; that will get clobbered by the new Pup's header.
; Locations 400-413 are used for scratch.
bootlp:	mkminusone 0 0		; Compute first source adr -1
	add 2 0
	lda 3 md12		; -12. (number of words)
	lda 1 c377		; Compute pointer to last word of
	sub 3 1			;  save area
	blt			; Save the data

; Now receive the next Data (or End) packet
	jsr ReceiveEFTPPacket

; Now restore the clobbered region
	lda 0 c377		; Pointer to start of save area -1
	lda 3 md12		; -12. (number of words)
	com 3 1			; Compute pointer to last word of
	add 2 1			;  clobbered region
	blt			; Move the data back

; Unless memory is now full, advance the pointer and repeat.
; If we just read into the last page of memory, set the pointer to
; a scratch region at 400 and set the count so that only a minimum-
; length Pup (e.g., the End that we are expecting) will be
; accepted.
	lda 1 c400		; Amount to advance pointer by
	se 1 2			; Skip if already at scratch region
	 add 1 2		; Normally, advance pointer
	lda 0 endFlag
	lda 3 memOverflow
	snz 0 0			; Was an End received?
	sne 2 3			; Or beyond end of memory?
	 mov 1 2 skp		; Yes, set pointer to 400
	 jmp bootlp		; No, continue normally
	lda 1 d13		; Limit packet length to minimum
	sta 1 maxLength
	snz 0 0			; Received End?
	 jmp bootlp		; No, continue
	dsz dallyTimeout	; Yes, enable timeout (set to -1)
	jsr ReceiveEFTPPacket	; Await second End or timeout
	lda 0 c3		; Reset the Ethernet
	sio
	jmp @0			; Jump into the bootload!

memOverflow: 177000-12.  ; Where ac2 will point when memory is full


; ** Warning **
; The last 12 words of page 1 get clobbered during the act of
; receiving page 2.  This is ok since those 12 words are saved
; and restored.  However, the stuff in those words must not be
; executed or referenced while in the clobbered state!

	.end