{File name: EtherInitial.mc Description: Ethernet Initial microcode Author: Amy Fasnacht Created: September 24, 1981 Last edited by: Fiala 5-Jun-86 10:20:37: Cosmetic edits Sturgis: 15-Nov-83 14:55:58: adjust increment at end of loading germ to not bump the page if not necessary. (subtract 1 before doing the page adjustment.) This avoids an inconsistency with the germ as loaded by SAX000Initialq. Sturgis: 11-Nov-83 17:22:40: u3FFF gets clobbered, so repair it. Are there others lurking? What don't I understand? Sturgis: 3-Nov-83 14:34:31: defined GermPageHigh and Low incorrectly, used <0,1>, rather than <3E,2>. Also, forgot to set rBrh and rCrh to 0 before the move germ loop. Also, miscomputed the constant to be used in computing the bootFileNoRequest. Sturgis: 26-Oct-83 12:32:22: assembles without error. (after copying in some definitions from apilot100 Dandelion.df, since I want to use rubicon df files) Sturgis: 24-Oct-83 13:06:09: convert to Cedar rubicon world. Murage 19-Sep-83 18:49:21 - fixed Germ request initialization to get correct location also fixed socket bug. Murage - 28-Jul-83 13:57:57 - remove addressing bug caused by growth of germ request-data address offset beyond byte size. Murage - 22-Jul-83 11:07:00 - Accomodate changes for Germ as a result of going over to 32-bit procedure descriptors. Fasnacht, July 9, 1982 6:48 PM: Reinitialize dY when finished Fasnacht, January 29, 1982 11:01 AM: Change socket numbers, add diagnostic boot Fasnacht, January 28, 1982 10:05 AM: Implement 3-word file numbers, set up constants for the Germ Fasnacht, January 15, 1982 10:51 AM: Delete miscRet return point Fasnacht, January 13, 1982 11:17 AM: Change algorithm for file checksums to the standard rotate-and-add instead of simple addition. Fasnacht, January 11, 1982 10:27 AM: Delete setting of DCtl register Fasnacht, December 15, 1981 12:49 PM: Comment changes Fasnacht, December 2, 1981 1:41 PM: Modify to run on Trident machine Fasnacht, November 24, 1981 9:43 AM: Set up boot file type Fasnacht, November 20, 1981 1:47 PM: Remove checking packet checksums } Reserve[ProtectStart, ProtectFence], Reserve[0FE0, 0FFF]; {save room for boot kernel} { Phase0Protected (Protected.mc, IOPBoot.mc) resides in 0 - 00FF Phase0 (Phase0.mc, DiskBootDLion.mc, EtherBootDLion.mc) resides in 0100 - 0FDF The BootKernel resides in 0FE0 - 0FFF Part of the BootKernel that can be overlaid resides in 0FD8 - 0FDF } {Link register values used in 16-way dispatch InputReturn} Set[L6.Host1, 0]; Set[L6.Host0, 1]; Set[L6.Host2, 2]; Set[L6.Sequence, 3]; Set[L6.File1, 4]; Set[L6.File2, 5]; Set[L6.Source2, 6]; Set[L6.Source1, 7]; Set[L6.DontUseThisValue, 8]; Set[L6.Checksum, 0A]; Set[L6.Length, 0C]; Set[L6.PacketType, 9]; Set[L6.Source0, 0E]; {The Laws of the Ethernet Hardware: -- EICtl_ and EOCtl_ can occur in any cycle. They must occur in c1 or c2 when turning off wakeups. -- _EIData must occur in c2. When read in c3, it retrieves the previous input word. -- EOData_ can occur in any cycle. -- EStrobe for throwing out input packet must occur in c2. -- EStrobe for writing the data from EOData into the FIFO must occur in c1 or c3. } SetTask[0]; {This code is entered as a subroutine of CoreInitial for Ethernet booting. CoreInitial passes control to InitDLion, which in turn passes control here. This code runs concurrently with EtherInitial task 2 and Protected and they communicate through the uEtherBootDone, uEtherBootStatus, and uTimeout registers and EICtl. This module basically sets up some things for EtherInitial task 2, and then when task 2 has retrieved Mesa.db and the Germ, it moves the Germ into its proper location. Upon completion, we branch back to CoreInitial.} RegDef[uESubBoot, U, 0DE]; {this does not conflict with anything in apilot100dandelion.df} {Initialize registers for EtherInitial} DoneOnceOnlyInit: uEtherBootDone _ 0, c1; uEtherBootStatus _ 0, c2; rEtherBootRetries _ 0, uGotFirstCode _ 0, c3; rY _ 0FF+1, c1; uNextAddress _ rY, c2; rY _ 6, c3; rY _ rY LRot8, c1; uPacketType _ rY, {600H = 3000B} c2; {noop} c3; rG _ 0FF {clock high}, {so we don't timeout right away} c1; uTimeout _ 0, c2; Yrh _ 0, c3; {Wake up the Initial code} WakeUpEther: EICtl _ 2, CANCELBR[$, 1], c1; {Wait for the timeout flag to get set by Initial. Then if the uEtherBootDone flag has not been set, we go wake up the Initial code again.} TestTimeout: Ybus _ uTimeout, NZeroBr, CANCELBR[$, 1], c2; Ybus _ uEtherBootDone, ZeroBr, BRANCH[$, WakeUpEther], c3; Ybus _ uEtherBootStatus, NZeroBr, BRANCH[$, TestTimeout], c1; BRANCH[moveGerm, BootFailure], c2; {The uEtherBootDone flag has been set. If the uEtherBootStatus register indicates unsuccessful completion, then report an error.} BootFailure: acR _ bootDeviceError, GOTOABS[Maintenance1Loc], c3; {move the germ out of the visible display region} moveGerm: rCrh _ 0, c3; rBrh _ 0, c1; {noop} c2; rC_ nextPage, c3; rC_ rC+1, c1; {throw in an extra page} rB_ rC LRot8, c2; rB_ rB-1, c3; {last word of germ} acR_ germStart, c1; acR _ acR LRot8, c2; acR_ rC-acR, c3; transferCount_ acR, ZeroBr, c1; {number pages in germ} rC_ rC xor ~rC, BRANCH[moveGerm1, $], c2; acR_ bootNullGerm, GOTOABS[Maintenance1Loc], c3; moveGerm1: germStart_ rC, c3; acR_ acR LRot8, c1; {number words in germ} Noop, c2; []_ acR, ZeroBr, c3; moveGermLoop: MAR_ [rBrh, rB+0], BRANCH[$, setGermReq], c1; germStart_ rC, c2; {first word of germ} rE_ MD, c3; MAR_ [rCrh, rC+0], c1; MDR_ rE, c2; rB_ rB-1, c3; rC_ rC-1, c1; Noop, c2; acR_ acR-1, ZeroBr, GOTO[moveGermLoop], c3; {now set up an ether inload request in the germ} Set[germRequest, 0F0]; {offset in first page of germ, POINTER TO Request= LOOPHOLE[1360B]} Set[deviceKindRequest, 0F1]; Set[deviceOrdinalRequest, 0F2]; Set[bootFileNoRequest, 0F3]; Set[netNoRequest, 0F4]; Set[hostNoRequest, 0F5]; setGermReq: {first compute bottom 16 bits of boot file number from subBoot} {as follows: subBoot = 20, number = 05200'B subBoot = 21, number = 05201'B, ... subBoot = 27, number = 05207'B} rC _ 12'B, c2; rC _ rC LRot8, c3; rB _ 060'B, c1; rC _ rC + rB, c2; {rC = 05200'B-120'B = 05060'B} rB _ uESubBoot, c3; {rB = subBootNumber + 100'B for subBoot = 20'B, rB = 120'B} rB _ rB + rC, c1; {for subBoot = 20'B+i, rB = 05200'B+i} Noop, c2; rC _ germStart, c3; MAR _ [rCrh, germRequest+0], c1; MDR _ 0, c2; {0 = inload} {noop} c3; MAR _ [rCrh, deviceKindRequest+0], c1; MDR _ 5, c2; {device = ether} {noop} c3; MAR _ [rCrh, deviceOrdinalRequest+0], c1; MDR _ 0, c2; {device ordinal = 0} {noop} c3; MAR _ [rCrh, bootFileNoRequest+0], c1; MDR _ rB, c2; {bottom 16 bits of boot file #} {noop} c3; MAR _ [rCrh, netNoRequest+0], c1; MDR _ 0, c2; {net} {noop} c3; MAR _ [rCrh, hostNoRequest+0], c1; MDR _ 0, c2; {host} rErh _ 1, {needed by mapZap} c3; {now map the germ} {adapted from Sax000Initialq} mapGerm: rE_ germPageHigh, c1; rE_ rE LRot8, c2; rE_ rE or germPageLow, c3; acR_ transferCount, c1; {number pages in germ} rC_ germStart, L0_ 0, c2; rC_ rC and ~0FF, CALL[mapZap], c3; {This enterprise maps the page before the germ (which just happens to be in the display bank) to virtual page 0FE. The germ uses this page as a disk buffer. In order to avoid losing the page initially mapped to 0FE, we first make page 0FE vacant by swapping it with the first vacant page in the map.} {first swap entries for virtual page 0FE and page just past end (left in rE by mapZap)} rB_ 0FE, c1, at[0, 10, miscRet]; rE_ topPage, L0_ 0E, c2; rBrh_ 1, CALL[memSwap], c3; rE_ rE+1, {now the next available page in the map} c1, at[0E, 10, subrRet]; acR_ germStart, {page 0FE is now unmapped} c2; acR_ acR-0FF-1, c3; MAR_ [rBrh, rB+0], c1; {map in page before germ} MDR_ acR or present, c2; topPage_ rE c3; {unfortunately, the ether code has clobberd u3FFF (same as uTimeOut), so we have to fix it up } T _ u1FFF, c1; T _ T LShift1, SE_1, c2; u3FFF _ T, GOTO[exitToEmulator], c3; {Return to CoreInitial} MesaBoot: Noop, c2; Exit: dY _ 1, GOTO[exitToEmulator], c3; {special code to try to plant an error} HesError2: c2; HesError3: c3; HesError1: HesError: rB _ 0, c1; rBrh _ 0, c2; c3; MAR _ [rBrh, 0+0], c1; MDR _ 0FF, c2; c3; MAR _ [rBrh, 0+1], c1; MDR _ 4, CANCELBR[$, 0], c2; IOPCtl _ IOPInMode, GOTOABS[IdleLoc], c3; SetTask[2], StartAddress[Dispatch]; {This code runs concurrently with EtherInitial task 0 and Protected and is awakened by EtherInitial task 0. EtherInitial has been brought into memory by EtherBootDLion during the first stage of booting and set into execution by the IOP. This module implements the core of the second stage of booting, consisting of bringing the Mesa.db or MoonBoot.db code and the Germ into memory. The modules communicate through the uEtherBootDone, uEtherBootStatus, uTimeout and clock (dX, dY) registers and EICtl. This Dispatch is the center of control for the entire module. Based on the value of the TurnOff and RcvMode bits in EStatus, it branches to one of the large sections of code named Preparing to Transmit, Transmitting a Packet, or Receiving a Packet. The setting of either of these bits by another task causes an Attention, which we eventually notice and branch back here. This code gets wakeups only when the transmitter is on, the receiver is on and there is something to receive on the Ethernet, or the TurnOff bit has been set in EStatus. The transmitter is turned on here and is left on until we have finished booting successfully (GoodFinish) or have attempted to boot ten times and have given up (BadFinish).} Dispatch: rY _ 9, CANCELBR[$, 0F], c1; Dispatch2: Xbus _ EStatus, XLDisp, L6 _ L6.Host0, c2; rMM _ 0, DISP2[Operation], c3; GoToDispatch: CANCELBR[Dispatch, 0F], c3; { Preparing to Transmit } {The registers rJ (low) and rG (high) hold a 32-bit count which is decremented every 28.8 microseconds by the refresh task. We initialize the counter in this code so that it will reach zero in 10 seconds. If the count reaches zero before we have successfully received a packet, uTimeout gets set by refresh. This causes Task0 to set bit 15 of EStatus and causes an Attention (EtherDisp). When this happens, we end up here through Dispatch. Thus, this code gets executed before we transmit for the first time and whenever we timeout and have to transmit the request packet again. After 10 retries with no success, we give up attempting to boot.} {If we have already transmitted the packet 10 times without success, then give up.} Ybus _ rY - rEtherBootRetries, NegBr, GOTO[ResetClock], c1, at[1, 4, Operation]; Ybus _ rY - rEtherBootRetries, NegBr, GOTO[ResetClock], c1, at[3, 4, Operation]; {Initialize the counter to time out in approximately 10 seconds. Reset the timeout flag.} ResetClock: rJ _ 0, {clock low} uTimeout _ 0, BRANCH[$, TenRetries], c2; rG _ 5, {clock high} c3; {Turn the transmitter on.} EOCtl _ EnableTransmit, c1; {Turn the receiver on.} EICtl _ EnableReceive, c2; {Increment the retry count.} rEtherBootRetries _ rEtherBootRetries + 1, c3; {Set the sequence number expected to 1.} Q _ 1, c1; {Clear the R link register and the backoff mask and return to Dispatch.} rMM _ 0, uBackoffMask _ 0, GOTO[GoToDispatch], c2; {We have transmitted ten times with no success. Notify task0 that we have finished unsuccessfully} TenRetries: uEtherBootStatus _ rY, c3; BadFinish: EOCtl _ Off, c1; EICtl _ Off, c2; uEtherBootDone _ rY, GOTO[BadFinish], c3; { Transmitting a Packet } {We come here from Dispatch whenever the transmitter is turned on, we are not timed out, and there is nothing on the Ethernet to receive. We wish to first broadcast a packet requesting the Mesa.db code, then repeat all this code again to request the Germ. If this is the first time we are attempting to transmit a specific packet or if we have transmitted previously but have not had any collisions, we wait 1 ms (specified by InitialCountdown) before attempting to send the packet. With each collision on the Ethernet, we shift a 1 into the BackoffMask so that the length of time before retransmission becomes an exponentially increasing random number. When the mask becomes full due to too many collisions, we turn the transmitter off and wait to timeout and try again with a clear mask. The checksum includes all words in the packet except the Ethernet header and trailer and the checksum word itself. It has been precalculated as much as possible. In this code we add to the checksum the words source host address through boot file number. As no packet can be less than 30 decimal words long, we add 4 words of zeros to the end of the packet. These are not included in the length or the checksum. The form of the packet we transmit is: / | FFFF | destination | | FFFF | host Ethernet | | FFFF | address header | | ------ | source | | ------ | host | | ------ | address \ | 0600 | packet type | ------ | checksum | 0026 | length | 0009 | control | 0000 | destination | 0000 | network | FFFF | destination | FFFF | host | FFFF | address | 000A | socket number | 0000 | source | 0000 | network | ------ | source | ------ | host | ------ | address | 0041 | source socket | 0001 | boot packet type (request) | 0000 | boot | AA00 | file | ~~~ | number | 0000 | | 0000 | | 0000 | | 0000 | Ethernet / | ------ | hardware trailer \ | ------ | CRC --- varies with the particular machine ~~ determined by boot buttons } {Determine which file we are requesting} {the pilot microcode is at uESubBoot+10'B} {the germ is at uESubBoot+20'B} Ybus _ uGotFirstCode, NZeroBr, c1, at[0, 4, Operation]; rY _ uESubBoot, BRANCH[FirstTime, SecondTime], c2; FirstTime: rP _ 10'B, GOTO[SetFileNo], c3; SecondTime: rP _ 20'B, GOTO[SetFileNo], c3; SetFileNo: rY _ rY + rP, c1; uFileNumber2 _ rY, c2; rP _ 0AA, c3; {noop} c1; {Set up the precalculated checksum of AA50.} rY _ rP LRot8, c2; rP _ rY or 50, c3; {Add the 3-word host address to the checksum.} rWord _ uEHost0, CALL[AddChecksum], c1; rWord _ uEHost1, CALL[AddChecksum], c1, at[1, 10, ChksumReturn]; rWord _ uEHost2, CALL[AddChecksum], c1, at[2, 10, ChksumReturn]; {Add the source socket number to the checksum.} rWord _ 41, CALL[AddChecksum], c1, at[3, 10, ChksumReturn]; {Add the boot packet type to the checksum.} rWord _ 1, CALL[AddChecksum], c1, at[4, 10, ChksumReturn]; {Add the boot file number to the checksum.} rWord _ 0, CALL[AddChecksum], c1, at[5, 10, ChksumReturn]; rWord _ rY, CALL[AddChecksum], c1, at[6, 10, ChksumReturn]; rWord _ uFileNumber2, CALL[AddChecksum], c1, at[7, 10, ChksumReturn]; {If the calculated checksum is all 1s, then change it to 0.} ChksumTest: Ybus _ rP xor ~0, ZeroBr, c1, at[8, 10, ChksumReturn]; rWord _ 55, BRANCH[ChksumGood, ChksumZero], c2; ChksumZero: rP _ 0, GOTO[ChksumTest], c3; ChksumGood: rS _ 0, c3; rMM _ 0, L6 _ 0, c1; Srh _ 2, c2; {Set up the packet we are to transmit in memory at the beginning of bank 1. First set up the preamble.} rWord _ rWord LRot8 or rWord, CALL[MakePacket], c3; GoToPacket: CALL[MakePacket], c3, at[1, 10, PacketReturn]; CALL[MakePacket], c3, at[2, 10, PacketReturn]; rWord _ rWord or 0C0, CALL[MakePacket], c3, at[3, 10, PacketReturn]; {Set up the 3-word destination host address, all 1s for broadcast.} rWord _ ~rWord xor rWord, CALL[MakePacket], c3, at[4, 10, PacketReturn]; CALL[MakePacket], c3, at[5, 10, PacketReturn]; CALL[MakePacket], c3, at[6, 10, PacketReturn]; {Set up the 3-word source host address, provided by the IOP.} rWord _ uEHost0, CALL[MakePacket], c3, at[7, 10, PacketReturn]; rWord _ uEHost1, CALL[MakePacket], c3, at[8, 10, PacketReturn]; rWord _ uEHost2, CALL[MakePacket], c3, at[9, 10, PacketReturn]; {Set up the packet type, 600 hex.} rWord _ uPacketType, CALL[MakePacket], c3, at[0A, 10, PacketReturn]; {Set up the checksum.} rWord _ rP, CALL[MakePacket], c3, at[0B, 10, PacketReturn]; {Set up the length, 26 hex.} rWord _ 26, CALL[MakePacket], c3, at[0C, 10, PacketReturn]; {Set up the control word, 9.} rWord _ 9, CALL[MakePacket], c3, at[0D, 10, PacketReturn]; {Set up the destination network number, two words of all 0s.} rWord _ 0, CALL[MakePacket], c3, at[0E, 10, PacketReturn]; CALL[MakePacket], c3, at[0F, 10, PacketReturn]; {Set up the 3-word destination host address, all 1s for broadcast.} rWord _ ~rWord xor rWord, L6 _ 1, CALL[MakePacket], c3, at[0, 10, PacketReturn]; CALL[MakePacket], c3, at[1, 10, PacketReturn2]; CALL[MakePacket], c3, at[2, 10, PacketReturn2]; {Set up the socket number, A hex.} rWord _ 0A, CALL[MakePacket], c3, at[3, 10, PacketReturn2]; {Set up the source network number, two words of all 0s.} rWord _ 0, CALL[MakePacket], c3, at[4, 10, PacketReturn2]; rCount _ 4, CALL[MakePacket], c3, at[5, 10, PacketReturn2]; {Set up the 3-word source host address, provided by the IOP.} rWord _ uEHost0, CALL[MakePacket], c3, at[6, 10, PacketReturn2]; rWord _ uEHost1, CALL[MakePacket], c3, at[7, 10, PacketReturn2]; rWord _ uEHost2, CALL[MakePacket], c3, at[8, 10, PacketReturn2]; {Set up the source socket number. This number starts at 2500 decimal and is incremented in diskboot.ureg each time the initial microcode changes.} rWord _ 41, CALL[MakePacket], c3, at[9, 10, PacketReturn2]; {Set up the boot packet type, 1 for request.} rWord _ 1, CALL[MakePacket], c3, at[0A, 10, PacketReturn2]; {Set up the 3-word boot file number.} rWord _ 0, CALL[MakePacket], c3, at[0B, 10, PacketReturn2]; rWord _ rY, CALL[MakePacket], c3, at[0C, 10, PacketReturn2]; rWord _ uFileNumber2, CALL[MakePacket], c3, at[0D, 10, PacketReturn2]; rS _ rS - 1, c3, at[0E, 10, PacketReturn2]; {Add four words of zeros to the end of the packet. We must do this so that the total length of the packet in words including the Ethernet header is not less than 30 decimal words.} ZeroLoop: MAR _ rS _ [Srh, rS + 1], BRANCH[$, EndLoop], c1; MDR _ 0, CANCELBR[$, 0], c2; rCount _ rCount - 1, ZeroBr, GOTO[ZeroLoop], c3; {Determine the length of time we should wait before retransmitting.} EndLoop: rCount _ InitialCountdown, CANCELBR[$, 0], c2; Xbus _ rY _ uBackoffMask, XHDisp, c3; rY _ rY LShift1, SE _ 1, ZeroBr, uOldMask _ rY, BRANCH[$, FullMask, 1], c1; uBackoffMask _ rY, BRANCH[OldMask, NewMask], c2; OldMask: rCount _ rJ, {clock low} GOTO[Retransmit], c3; NewMask: rY _ rY xor ~rY, GOTO[Retransmit], c3; FullMask: EOCtl _ Off, CANCELBR[GoToDispatch, 1], c2; Retransmit: rCount _ rCount and rY, GOTO[WaitLoop2], c1; {Loop to count down the time to wait before transmitting.} WaitLoop: rCount _ rCount - 1, NegBr, BRANCH[$, GoToAttention, 0E], c1; WaitLoop2: EOCtl _ EnableTransmitDefer, BRANCH[$, StartTransmit], c2; rY _ uOldMask, EtherDisp, GOTO[WaitLoop], c3; GoToAttention: CANCELBR[AttentionSet, 0F], c2; {Transmit the packet we have just constructed in memory. If Attention is set, we quit transmitting and turn the transmitter off. If the Attemtion is not due to UnderRun or Collision, then restore the retransmission mask.} StartTransmit: rS _ 0, c3; EOCtl _ EnableTransmit, c1; Noop, c2; rCount _ 22, c3; MAR _ [Srh, rS + 0], EtherDisp, GOTO[OutputLoop2], c1; OutputLoop: MAR _ rS _ [Srh, rS + 1], EtherDisp, EStrobe, c1; OutputLoop2: rCount _ rCount - 1, ZeroBr, BRANCH[$, AttentionSet, 0E], c2; EOData _ MD, BRANCH[OutputLoop, LastWord], c3; AttentionSet: Xbus _ EStatus, XwdDisp, CANCELBR[$, 1], c3; EOCtl _ Off, DISP2[Attention], c1; EOCtl _ EnableTransmit, GOTO[PutBackMask], c2, at[Other, 4, Attention]; GOTO[GoToDispatch], c2, at[UnderRun, 4, Attention]; Collision: EOCtl _ EnableTransmit, GOTO[GoToDispatch], c2, at[Collision, 4, Attention]; GOTO[GoToDispatch], c2, at[CollisionUnderRun, 4, Attention]; PutBackMask: uBackoffMask _ rY, GOTO[Dispatch], c3; {The last word to transmit has been placed into the FIFO. EOCtl _ EnableTransmitLastWord tells the hardware that no more words will be placed into the FIFO, and must occur in c1 or c2. Wakeups will not occur again until the FIFO has been emptied onto the Ethernet.} LastWord: EStrobe, c1; EOCtl _ EnableTransmitLastWord, c2; EStrobe, c3; {Check the final status.} Xbus _ EStatus, XwdDisp, c1; DISP2[Status], c2; {The status is good. Turn the transmitter off and return to Dispatch.} StatusOK: rMM _ 0, MMrh _ 0, c3, at[0, 4, Status]; GoToFullMask: GOTO[FullMask], c1; {We have collided. Reset the transmitter and return to Dispatch.} Noop, c3, at[2, 4, Status]; EOCtl _ Off, GOTO[Collision], c1; {We have UnderRun. Turn the transmitter off and return to Dispatch. (Due to a hardware bug, this condition can never occur, but we insert this code in case the bug gets fixed.)} GOTO[GoToFullMask], c3, at[1, 4, Status]; GOTO[GoToFullMask], c3, at[3, 4, Status]; { Common Code for Transmitting } {This code places a word of the packet into the next memory location in the process of constructing a packet to transmit. On entry, Srh = 1, rS = the next available location in memory, and rWord = the next word to be added to the packet. rMM is the link register used in determining the return address.} MakePacket: MAR _ [Srh, rS + 0], c1; MDR _ rWord, c2; rS _ rS + 1, L6Disp, c3; Ybus _ rMM _ rMM + 1, YDisp, BRANCH[$, Ending2, 0E], c1; DISP4[PacketReturn], c2; Ending2: DISP4[PacketReturn2], c2; { Receiving a Packet } {We come here from Dispatch whenever TurnOff is not set and there is something on the Ethernet to be received. Thus receiving has precedence over transmitting. We receive all packets which appear on the Ethernet and must filter out the ones we do not want. The microcode we intend to receive comes in several packets with increasing sequence numbers, starting with 1. Whenever we receive one complete packet successfully, we reset the clock to timeout in two seconds. The form of each of the packets is: / | ------ | destination | | ------ | host Ethernet | | ------ | address header | | ------ | source | | ------ | host | | ------ | address \ | 0600 | packet type | ------ | checksum | ------ | length | 0009 | control | ------ | destination | ------ | network | ------ | destination | ------ | host | ------ | address | 0041 | socket number | ------ | source | ------ | network | ------ | source | ------ | host | ------ | address | 000A | source socket | 0002 | boot packet type (reply) | 0000 | boot | AA00 | file | ~~~ | number | ------ | sequence number (1, ...) | ------ | boot file data . . . . . . . . . . . . . . . . . . Ethernet / | ------ | hardware trailer \ | ------ | CRC --- varies ~~ determined by boot buttons Much of the packet we input and throw out without verifying its correctness. For the first packet of the sequence, if the host address matches our host number, the sequence number is 1, the boot file number is correct, and the packet type is correct, then we assume the packet is the correct packet. For subsequent packets, we check that the host address is correct, it has the next consecutive sequence number, the packet type and boot file number are correct, and that the source number matches the source number of packet 1 in the sequence. Due to space limitations, packet checksums are not verified. A packet having a length of 40 decimal indicates that there is no boot file data present and implies the last packet of the sequence. As no packet can be less than 30 decimal words long, there may be extra words added on the end which are not included in the length and which we throw out. Throughout the receive code, Q contains the sequence number which we expect the packet we are receiving to have. } {Input destination number and see if it matches our host number. If not, throw out the packet.} Receive: rY _ uEHost0, L6Disp, CALL[InputWord], c1, at[2, 4, Operation]; Ybus _ rY xor rWord, NZeroBr, L6 _ L6.Host1, c3, at[L6.Host0, 10, InputReturn]; rY _ uEHost1, L6Disp, BRANCH[InputWord, PurgePacket], c1; Ybus _ rY xor rWord, NZeroBr, L6 _ L6.Host2, c3, at[L6.Host1, 10, InputReturn]; rY _ uEHost2, L6Disp, BRANCH[InputWord, PurgePacket], c1; Ybus _ rY xor rWord, NZeroBr, L6 _ L6.Source0, c3, at[L6.Host2, 10, InputReturn]; {Input the 3-word source number. If this is the first packet of the sequence, then store the source number away in U registers 7E, 7F, and 70.} EtherDisp, BRANCH[$, Host2Wrong], c1; Ybus _ Q - 1, ZeroBr, L6 _ L6.Source0, BRANCH[$, HostAttn, 0E], c2; rCount _ 7C, L0 _ 0, BRANCH[NotFirstPacket, FirstPacket], c3; FirstPacket: rCount _ rCount + 1, AltUaddr, NibCarryBr, c1; rY _ EIData, uSourceBlock _ rY, BRANCH[$, PacketType], c2; rWord _ uPacketType, GOTO[FirstPacket], c3; {If this is not the first packet of the sequence, then see if the source number of this packet matches the source of packet 1. If not, throw out the packet.} NotFirstPacket: rY _ uSource0, L6Disp, CALL[InputWord], c1; Ybus _ rY xor rWord, NZeroBr, L6 _ L6.Source1, c3, at[L6.Source0, 10, InputReturn]; rY _ uSource1, L6Disp, BRANCH[InputWord, PurgePacket], c1; Ybus _ rY xor rWord, NZeroBr, L6 _ L6.Source2, c3, at[L6.Source1, 10, InputReturn]; rY _ uSource2, L6Disp, BRANCH[InputWord, PurgePacket], c1; Ybus _ rY xor rWord, NZeroBr, L6 _ L6.PacketType, c3, at[L6.Source2, 10, InputReturn]; rY _ uPacketType, L6Disp, BRANCH[InputWord, PurgePacket], c1; {Input packet type. If it is not 0600 hex, then throw out the packet.} PacketType: Ybus _ rY xor rWord, NZeroBr, L6 _ L6.Checksum, c3, at[L6.PacketType, 10, InputReturn]; {Input checksum and throw out.} rP _ 0, L6Disp, BRANCH[InputWord, PurgePacket], c1; {Input length and calculate the actual length of the data in the packet in words, which is the total length in bytes minus 28 bytes for the header, rounded up and divided by 2.} rMM _ 0AA, L6 _ L6.Length, c3, at[L6.Checksum, 10, InputReturn]; rY _ 0E, L6Disp, CALL[InputWord], c1; rWord _ rWord - 27, c3, at[L6.Length, 10, InputReturn]; {Input the next 14 words of the header and throw out} EatGarbage: rCount _ RShift1 rWord, SE _ 0, BRANCH[$, CheckFile], c1; rCount _ EIData, EtherDisp, c2; rY _ rY - 1, ZeroBr, BRANCH[EatGarbage, GarbageAttn, 0E], c3; {Input the 3-word boot file number and see if it matches the file number we requested in our request packet. If not, throw out the packet.} CheckFile: rWord _ EIData, c2; Ybus _ rWord, NZeroBr, L6 _ L6.File1, c3; rY _ rMM LRot8, L6Disp, BRANCH[InputWord, PurgePacket], c1; Ybus _ rWord xor rY, NZeroBr, L6 _ L6.File2, c3, at[L6.File1, 10, InputReturn]; rY _ uFileNumber2, L6Disp, BRANCH[InputWord, PurgePacket], c1; Ybus _ rWord xor rY, NZeroBr, L6 _ L6.Sequence, c3, at[L6.File2, 10, InputReturn]; {Input the sequence number. If this is not the sequence number expected, then throw out the packet.} L6Disp, BRANCH[InputWord, PurgePacket], c1; ChkSequence: Ybus _ Q xor rWord, NZeroBr, c3, at[L6.Sequence, 10, InputReturn]; {If the length of the data in this packet is 0, it is the last packet. Jump out of this loop.} Ybus _ rCount, ZeroBr, BRANCH[$, SequenceWrong], c1; rWord _ EIData, BRANCH[$, ZeroLength], c2; rY _ uNextAddress, c3; {Loop to input the next data word and store it into memory. If Attention is set, throw out the packet. We exit the loop normally when the length in words has counted down to zero.} FirstWord: MAR _ [Yrh, rY + 0], c1; MDR _ rWord, GOTO[Decrement], c2; InputLoop: MAR _ rY _ [Yrh, rY + 1], EtherDisp, BRANCH[$, ExtraWords], c1; InputLoop2: MDR _ rWord _ EIData, DISP4[InputState, 0C], c2; Decrement: rCount _ rCount - 1, ZeroBr, GOTO[InputLoop], c3, at[0C, 10, InputState]; GOTO[GoToPurge], c3, at[0D, 10, InputState]; rY _ rY + 0FF + 1, GOTO[FirstWord], c3, at[0E, 10, InputState]; GOTO[GoToPurge], c3, at[0F, 10, InputState]; {Throw out the rest of the packet, including the hardware CRC and any extraneous words which may be on the end of the packet to make the length at least 30 words.} ExtraWords: EStrobe, BRANCH[$, FixPageCross, 0D], c2; GOTO[NextAddress], c3; FixPageCross: rY _ rY + 0FF + 1, GOTO[NextAddress], c3; NextAddress: uNextAddress _ rY, c1; {We seem to have received this packet successfully. Increment the sequence number.} Q _ Q + 1, c2; {Set up the clock to timeout in two seconds and return to Dispatch to wait for the next packet.} rG _ 1, {clock high} c3; rJ _ 0 {clock low}, c1; GOTO[GoToDispatch], c2; { Common Code for Receiving } {As each word of the packet is inputted, we check the Attention bit. Attention in this case indicates timeout or the end of the packet has been received. Since the normal exit to the receive loop is for the length to count down to zero, an Attention is an abnormal occurrence. If it has been set, we throw out the rest of the packet by doing an EStrobe. This EStrobe must occur in c2.} InputWord: rWord _ EIData, DISP4[InputReturn], c2; HostAttn: CANCELBR[GoToPurge, 1], c3; Host2Wrong: EStrobe, CANCELBR[GoToDispatch, 1], c2; SequenceWrong: EStrobe, CANCELBR[GoToDispatch, 1], c2; GarbageAttn: CANCELBR[PurgePacket, 1], c1; GoToPurge: CANCELBR[PurgePacket, 1], c1; PurgePacket: EStrobe, CANCELBR[GoToDispatch, 0F], c2; { Verifying the File Checksum } {Read the code out of memory to check the file checksum, which is the last word in the file. This checksum is calculated in the same manner as the checksum which we transmitted in the request packet. When the checksum is computed on all words of the file including the checksum word itself, the result should be all 1s. On entry to this code, rP = 0.} ZeroLength: Noop, c3; Ybus _ uGotFirstCode, NZeroBr, c1; BRANCH[Mesa, Germ], c2; Mesa: rY _ 0FF + 1, GOTO[SetAddress], c3; Germ: rY _ germStart, GOTO[SetAddress], c3; SetAddress: rS _ uNextAddress, c1; rS _ rS - rY - 1, c2; Noop, c3; FileCheck: MAR _ [Yrh, rY + 0], c1; GOTO[NoPageCross], c2; FileChksum: MAR _ rY _ [Yrh, rY + 1], c1, at[9, 10, ChksumReturn]; rS _ rS - 1, NegBr, BRANCH[$, PageCross2, 1], c2; NoPageCross: rWord _ MD, BRANCH[AddWord, CheckChksum], c3; PageCross2: rY _ rY + 0FF + 1, BRANCH[FileCheck, CheckChksum2], c3; AddWord: rMM _ 8, CALL[AddChecksum], c1; {If the file checksum is wrong, then return to Dispatch and wait to timeout and retransmit.} CheckChksum: Ybus _ ~rP, NZeroBr, GOTO[CheckBranch], c1; CheckChksum2: Ybus _ ~rP, NZeroBr, c1; CheckBranch: Ybus _ uGotFirstCode, NZeroBr, BRANCH[ChksumOK, ChksumWrong], c2; ChksumWrong: CANCELBR[Dispatch, 1], c3; {The file checksum is good. If we have just received the first set of code, then set up the starting address for the Germ, turn the transmitter back on, and return to Dispatch.} ChksumOK: Q _ 1, BRANCH[$, GotGerm], c3; uGotFirstCode _ Q, c1; rY _ rY and ~0FF, c2; rY _ rY + 0FF + 1, c3; uNextAddress _ rY, c1; Noop, c2; germStart _ rY, c3; EICtl _ 2, {set Attention} c1; rEtherBootRetries _ 0, GOTO[GoToDispatch], c2; {If we have just received the Germ, then we are finished. Notify Task0 of our successful completion.} GotGerm: {first make sure we dont advance to a new page when not necessary} rMM _ rY - 1, c1; {noop} c2; {noop} c3; rMM _ rMM and ~0FF, c1; rMM _ rMM + 0FF + 1, c2; rMM _ rMM LRot8, c3; GoodFinish: nextPage _ rMM, c1; EICtl _ Off, c2; uEtherBootDone _ Q, GOTO[GoodFinish], {uEtherBootStatus is already zero} c3; { Checksum Calculation } {The checksum is initialized to 0. It is computed by rotating both the checksum and the data word left one bit and adding the data to the checksum using ones-complement addition. All words in the packet are used in calculating the checksum except the Ethernet header and the checksum word itself. A checksum of all 1s means that no checksum has been computed, so that if a checksum is computed to be all 1s, it is changed to 0 by convention.} GoToAdd: Noop, c1; AddChecksum: rWord _ LRot1 rWord, c2; rP _ LRot1 rP, c3; rP _ rP + rWord, CarryBr, c1; CarryBr: Ybus _ rMM _ rMM + 1, YDisp, BRANCH[$, Carry], c2; DISP4[ChksumReturn], c3; Carry: rP _ rP + 1, CANCELBR[$, 0F], c3; rMM _ rMM - 1, GOTO[CarryBr], c1;