; 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