{ File name: EtherInitial.mc Description: Alternate Ethernet Initial microcode (6 boot) Last Edited: bj, 12-Apr-86 21:17:46 Fiala 25-Jul-86 12:35:02 } { Copyright (C) 1981, 1982, 1983 by Xerox Corporation. All rights reserved. } 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. +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ } { Initialize registers for EtherInitial } DoneOnceOnlyInit: uEtherBootDone ← 0 ,c1; uEtherBootStatus ← 0 ,c2; rEtherBootRetries ← 0, uGotFirstCode ← 0 ,c3; rG ← 0FF ,c1; {clock high} {so we don't timeout right away} uTimeout ← 0 ,c2; Yrh ← 0 ,c3; WakeUpEther: EICtl ← 2, CANCELBR[$, 1] ,c1; {Wake up the Initial code} { 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; rB ← germStart, 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; { +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ MoveGerm is *not* device specific and lives in CoreInitial (for simplicity) Entry is with rB=germStart, nextPage=the page after the germ The return is via GOTO[SetRequest] at c3. SetRequest is responsible for settting SD[request] to indicate the method that was used to Boot, then continue w/exitToEmulator +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ } { +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Here are the appropriate entries from the Cedar6.0 release: BootFile.Location: TYPE = MACHINE DEPENDENT RECORD[ deviceType(0): DiskFace.Type, deviceOrdinal(1): CARDINAL, vp(2): SELECT OVERLAID * FROM disk => [diskFileID(2): DiskFileID], ethernet => [bootFileNumber(2): CARDINAL, net(3), host(4): CARDINAL ← 0], any => [a(2), b(3), c(4), d(5), e(6), f(7), g(10B), h(11B): WORD], ENDCASE ]; DiskFace.Type: TYPE = MACHINE DEPENDENT { null(0), sa800(1), sa1000(2), sa4000(3), cdc9730(4), ethernet(5),(LAST[CARDINAL]) }; -- Identification of a boot file for loading it. BootFile.DiskFileID: TYPE = MACHINE DEPENDENT RECORD[ fID: DiskFace.FileID, -- for disk label firstPage: INT, -- for disk label firstLink: DiskFace.DontCare -- initial boot chain link ]; DiskFace.FileID: TYPE = MACHINE DEPENDENT RECORD[ id(0): SELECT OVERLAID * FROM rel => [relID(0): RelID, fill4(4): CARDINAL ← 0], abs => [absID(0): AbsID], ENDCASE ]; DiskFace.RelID: TYPE[4]; DiskFace.AbsID: TYPE[5]; -- Ignored in label verification. -- Typically used for boot chain links, in which case it's actually a DiskAddress. DiskFace.DontCare: TYPE[2]; Corresponding values from Pilot12.0: Device.Type: TYPE = PRIVATE RECORD [CARDINAL]; Ethernet: TYPE = CARDINAL [5..16); PilotDisk: TYPE = CARDINAL [64..1024); nullType: Type = [0]; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ } { Set up an Ethernet InLoad request for the Germ. rE has @SD[] - 1 relative to the MDS value (=1FF for PrincOps4.0, =23F for PrincOps3.0) rFrh has germPageHigh (or cedarGermPageHigh, which is the Germ's MDS!) } { +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ This uses Howard's Jackpot subBoot mechanism (SubCalc) Here we're constructing the bootfile request for the germ subBoot = 20, number = 05200'b subBoot = 21, number = 05201'b, ... subBoot = 27, number = 05207'b The communciation from PreEtherInitial is via uESubBoot which contains values in the range 0120 => 0127 because we're doing 20 sub-boots, the user selected the low order digit (octit?) What we want is to have #'s in the range 05200'b => 05207'b, we get these by noting that: 05060'b + 120'b = 05200'b, for TriDLion's the values are: 05070'b + 120'b = 05210'b then we create the interesting values appropriately rC ← 05000'b (12 LRot8) + 060'b (or 70'b) + uESubBoot (12x'b) +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ } SetRequest: CedarRequest: { offset in first page of germ, POINTER TO Request= LOOPHOLE[1360'b] remember POINTER = [3E,0000] and Germ is offset [00,0200] 1000'b } {Noop} ,c1; rE ← cedarGermPageLow ,c2; rE ← rE LRot8 ,c3; Map ← rF ← [rFrh, rE + 0] ,c1; {.mv cedarGerm} rC ← germRequest ,c2; rBrh ← rB ← MD ,c3; {.mr cedarGerm} MAR ← [rBrh, cedar.Request.Action+0] ,c1; MDR ← inLoad ,c2; {Noop} ,c3; MAR ← [rBrh, cedar.Request.deviceType+0] ,c1; MDR ← cedarGermEthernet ,c2; rD ← CPBootDevice ,c3; MAR ← [rBrh, cedar.Request.devOrd+0] ,c1; MDR ← ethernetDeviceOrdinal ,c2; Ybus ← rD, YDisp ,c3; rC ← 0A, BRANCH[ShugartNo, TridentNo, 7] ,c1; {12'b} ShugartNo: rD ← 30, GOTO[SubCalc] ,c2; {060'b} TridentNo: rD ← 38 ,c2; {070'b} SubCalc: rC ← rC LRot8 ,c3; rC ← rC + rD ,c1; rD ← uESubBoot ,c2; rC ← rC + rD ,c3; MAR ← [rBrh, cedar.Request.BootFileNumber+0] ,c1; MDR ← rC ,c2; {bottom 16 bits of boot file #} {Noop} ,c3; MAR ← [rBrh, cedar.Request.NetworkNumber+0] ,c1; MDR ← 0 ,c2; {pup broadcast net} {Noop} ,c3; MAR ← [rBrh, cedar.Request.HostNumber+0] ,c1; MDR ← 0, GOTO[Exit] ,c2; {pup broadcast host} {** MesaRequest: rC ← sFirstGermRequestHigh ,c1; rC ← rC LRot8 ,c2; {Constants use an 8-bit data path.} rE ← rE + rC + 1 ,c3; {correct page} Map ← rF ← [rFrh, rE + 0] ,c1; rC ← sFirstGermRequestLow ,c2; rBrh ← rB ← MD ,c3; ZeroReq: MAR ← [rBrh, rC+0] ,c1; MDR ← rD, rC ← rC + 1, PgCarryBr ,c2; {rD zero from above} BRANCH[ZeroReq, $] ,c3; MAR ← [rBrh, Request.action + 0] ,c1; MDR ← inLoad ,c2; rD ← RequestVersionHigh ,c3; MAR ← [rBrh, Request.location.deviceType + 0] ,c1; MDR ← germEthernet ,c2; rD ← rD LRot8 ,c3; MAR ← [rBrh, Request.location.devOrd + 0] ,c1; MDR ← ethernetDeviceOrdinal ,c2; rD ← rD or RequestVersionLow ,c3; MAR ← [rBrh, Request.version + 0] ,c1; MDR ← rD ,c2; rD ← ethernetBootFileNumberMiddle ,c3; MAR ← [rBrh, Request.location.ethernetBootFileNumber0 + 0] ,c1; MDR ← ethernetBootFileNumberHigh ,c2; rD ← rD LRot8 ,c3; MAR ← [rBrh, Request.location.ethernetBootFileNumber1 + 0] ,c1; MDR ← rD ,c2; rD ← CPBootDevice ,c3; MAR ← [rBrh, Request.location.ethernetNetworkNumber0 + 0] ,c1; MDR ← ethernetBootNetworkNumberHigh ,c2; Ybus ← rD, YDisp ,c3; rC ← 0A, BRANCH[ShugartNo, TridentNo, 7] ,c1; {12'b} ShugartNo: rD ← 30, GOTO[SubCalc] ,c2; {060'b} TridentNo: rD ← 38 ,c2; {070'b} SubCalc: rC ← rC LRot8 ,c3; rC ← rC + rD ,c1; rD ← uESubBoot ,c2; rC ← rC + rD ,c3; MAR ← [rBrh, Request.location.ethernetBootFileNumber2 + 0] ,c1; MDR ← rC ,c2; Noop ,c3; MAR ← [rBrh, Request.location.ethernetNetworkNumber1 + 0] ,c1; MDR ← ethernetBootNetworkNumberLow ,c2; Noop ,c3; MAR ← [rBrh, Request.location.ethernetHostNumber0 + 0] ,c1; MDR ← rD xor ~rD ,c2; rDrh ← 0 ,c3; MAR ← [rBrh, Request.location.ethernetHostNumber1 + 0] ,c1; MDR ← rD xor ~rD ,c2; Noop ,c3; MAR ← [rBrh, Request.location.ethernetHostNumber2 + 0] ,c1; MDR ← rD xor ~rD ,c2; Noop ,c3; MAR ← [rBrh, Request.location.ethernetSocket + 0] ,c1; MDR ← ethernetBootSocket ,c2; rC ← uDiagnostic, ZeroBr ,c3; rD ← 1, BRANCH[$, MesaBoot] ,c1; rD ← rD LRot8 ,c2; rC ← rC + 1 ,c3; MAR ← [rDrh, rC + 0] ,c1; Noop ,c2; rC ← MD ,c3; rC ← rC + rD ,c1; uBootStart ← rC, GOTO[Exit] ,c2; **} MesaBoot: Noop ,c2; Exit: dY ← 1, GOTO[exitToEmulator] ,c3; { Return to CoreInitial } 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, uTimeout ← 0, BRANCH[$, TenRetries] ,c2; {clock low} rG ← 5 ,c3; {clock high} EOCtl ← EnableTransmit ,c1; {Turn the transmitter on.} EICtl ← EnableReceive ,c2; {Turn the receiver on.} rEtherBootRetries ← rEtherBootRetries + 1 ,c3; {Increment the retry count.} Q ← 1 ,c1; {Set the sequence number expected to 1.} rMM ← 0, uBackoffMask ← 0, GOTO[GoToDispatch] ,c2; {Clear the R link register and the backoff mask and return to Dispatch.} { 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 number 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 | number header | | ---- | source | | ---- | host | | ---- | number \ | 0600 | packet type | ---- | checksum | 0026 | length | 0009 | control | 0000 | destination | 0000 | network | FFFF | destination | FFFF | host | FFFF | number | 000A | socket number | 0000 | source | 0000 | network | ---- | source | ---- | host | ---- | number | 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 This is Howard's Jackpot subBoot mechanism Here we're constructing the microcode/germ file number subBoot = 20, uCode = 0130'b, germ = 0140'b subBoot = 21, uCode = 0131'b, germ = 0141'b ... subBoot = 27, uCode = 0137'b, germ = 0147'b For TriDLion's the values are: subBoot = 20, uCode = 0230'b, germ = 0240'b subBoot = 21, uCode = 0231'b, germ = 0241'b ... subBoot = 27, uCode = 0237'b, germ = 0247'b So, what we end up with is uESubBoot + (10'b, 20'b, 110'b, 120'b) +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ } Ybus ← uGotFirstCode, NZeroBr ,c1, at[0, 4, Operation]; rY ← 0FF + 1, BRANCH[FirstTime, SecondTime] ,c2; FirstTime: Ybus ← uDiagnostic, NZeroBr ,c3; uNextAddress ← rY, BRANCH[NoDiagBoot, DiagBoot] ,c1; NoDiagBoot: rY ← 8, GOTO[WhichDisk] ,c2; {10'b} DiagBoot: rY ← 8, GOTO[WhichDisk] ,c2; {10'b} SecondTime: rY ← 10 ,c3; {20'b} Noop ,c1; Noop ,c2; WhichDisk: rWord ← CPBootDevice ,c3; Ybus ← rWord, YDisp ,c1; rP ← 0AA, BRANCH[SAx000Disk, TridentDisk, 7] ,c2; {252'b} SAx000Disk: GOTO[SetFileNo] ,c3; TridentDisk: rY ← rY + 40 ,c3; {100'b} { +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Set up the precalculated checksum of AA50 in rP: the running checksum Add to the checksum: (0-2) 3-word host number (3) source socket number #41 (4) boot packet type #1=request (5-7) boot file number 0000-AA00-xxxx = 25200000000B When the checksum calculation is complete, (8) launder the all 1's back to all 0's. +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ } SetFileNo: rWord ← uESubBoot ,c1; {0120 => 0127} rY ← rY + rWord ,c2; Noop ,c3; uFileNumber2 ← rY ,c1; rY ← rP LRot8 ,c2; {252000'b} rP ← rY or 50 ,c3; rWord ← uEHost0, CALL[AddChecksum] ,c1; rWord ← uEHost1, CALL[AddChecksum] ,c1, at[1, 10, ChksumReturn]; rWord ← uEHost2, CALL[AddChecksum] ,c1, at[2, 10, ChksumReturn]; rWord ← 41, CALL[AddChecksum] ,c1, at[3, 10, ChksumReturn]; rWord ← 1, CALL[AddChecksum] ,c1, at[4, 10, ChksumReturn]; 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]; ChksumTest: Ybus ← rP xor ~0, ZeroBr ,c1, at[8, 10, ChksumReturn]; rWord ← 55, BRANCH[ChksumGood, ChksumZero] ,c2; {preamble pattern} ChksumZero: rP ← 0, GOTO[ChksumTest] ,c3; ChksumGood: Srh ← 2, rS ← 2 ,c3; rMM ← 0, L6 ← 0 ,c1; rS ← rS LRot8 ,c2; { +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Assemble the packet we are to transmit in memory, at address 20200. This address is just an arbitrary pick. We could make the packet anywhere in memory except the first two banks of memory (display bank and memory map), and the first page of the third bank (IOPage). (0-3) preamble (4-6) 3-word destination host number, all 1s for broadcast (7-9) 3-word source host number, provided by the IOP (0A) packet type = 600'h (0B) checksum (0C) length = 26'h (0D) control word = 9 (0E-0F) destination network number, two words of all 0s +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ } 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 080, CALL[MakePacket] ,c3, at[3, 10, PacketReturn]; {last preamble bit on} 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]; 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]; rWord ← uPacketType, CALL[MakePacket] ,c3, at[0A, 10, PacketReturn]; rWord ← rP, CALL[MakePacket] ,c3, at[0B, 10, PacketReturn]; rWord ← 26, CALL[MakePacket] ,c3, at[0C, 10, PacketReturn]; rWord ← 9, CALL[MakePacket] ,c3, at[0D, 10, PacketReturn]; rWord ← 0, CALL[MakePacket] ,c3, at[0E, 10, PacketReturn]; CALL[MakePacket] ,c3, at[0F, 10, PacketReturn]; { +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Now we construck the *real* request (0-2) 3-word destination host number, all 1s for broadcast (3) socket number, 0A'h (4-5) source network number, two words of all 0s (6-8) 3-word source host number, provided by the IOP (9) source socket number. This number starts at 2500 decimal and is incremented in diskboot.ureg each time the initial microcode changes. (0A) boot packet type, 1 = request (0B-0D) 3-word boot file number (constructed above) +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ } 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]; rWord ← 0A, CALL[MakePacket] ,c3, at[3, 10, PacketReturn2]; rWord ← 0, CALL[MakePacket] ,c3, at[4, 10, PacketReturn2]; rCount ← 4, CALL[MakePacket] ,c3, at[5, 10, PacketReturn2]; 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]; rWord ← 41, CALL[MakePacket] ,c3, at[9, 10, PacketReturn2]; rWord ← 1, CALL[MakePacket] ,c3, at[0A, 10, PacketReturn2]; rWord ← 0, CALL[MakePacket] ,c3, at[0B, 10, PacketReturn2]; {0000'b} rWord ← rY, CALL[MakePacket] ,c3, at[0C, 10, PacketReturn2]; {0252'b} rWord ← uFileNumber2, CALL[MakePacket] ,c3, at[0D, 10, PacketReturn2]; {offset} 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 ← 2 ,c3; EOCtl ← EnableTransmit ,c1; rS ← rS LRot8 ,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| | ---- | number header | | ---- | source | | ---- | host | | ---- | number \ | 0600 | packet type | ---- | checksum | ---- | length | 0009 | control | ---- | destination | ---- | network | ---- | destination | ---- | host | ---- | number | 0041 | socket number | ---- | source | ---- | network | ---- | source | ---- | host | ---- | number | 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 number 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 number 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]; { +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ If we are fetching the Germ, then set the starting address to germStart. This catches the case where it is not our first try to receive the Germ successfully. 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 1, ZeroBr ,c3, at[L6.Sequence, 10, InputReturn]; Ybus ← uGotFirstCode, ZeroBr, BRANCH[NotPacket1, Packet1] ,c1; Packet1: Ybus ← Q xor rWord, NZeroBr, BRANCH[SetGermAddress, SetStartAddress] ,c2; NotPacket1: Ybus ← Q xor rWord, NZeroBr, CANCELBR[SetStartAddress, 1] ,c2; SetStartAddress: rY ← uNextAddress, BRANCH[SequenceOK, SequenceWrong] ,c3; SetGermAddress: rY ← germStart, BRANCH[SequenceOK, SequenceWrong] ,c3; { If the length of the data in this packet is 0, it is the last packet. Jump out of this loop. } SequenceOK: Ybus ← rCount, ZeroBr ,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 ,c3; {clock high} rJ ← 0 ,c1; {clock low} 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: CANCELBR[PurgePacket, 1] ,c1; 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 ,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, ZeroBr, 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 Mesa.db, then set up the starting address for the Germ, turn the transmitter back on, and return to Dispatch. If we have just received MoonBoot.db, then we are done. +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ } ChksumOK: Q ← 1, BRANCH[$, GotGerm] ,c3; uGotFirstCode ← Q ,c1; rY ← rY and ~0FF ,c2; Ybus ← uDiagnostic, NZeroBr ,c3; rY ← rY + 0FF + 1, BRANCH[$, GoodFinish2] ,c1; uNextAddress ← rY ,c2; germStart ← rY ,c3; EICtl ← 2 ,c1; {set Attention} rEtherBootRetries ← 0, GOTO[GoToDispatch] ,c2; { If we have just received the Germ, then we are finished. Notify Task0 of our successful completion. } GotGerm: rMM ← rY and ~0FF ,c1; rMM ← rMM + 0FF + 1 ,c2; rMM ← rMM LRot8 ,c3; GoodFinish: nextPage ← rMM ,c1; GoodFinish2: EICtl ← Off ,c2; uEtherBootDone ← Q, GOTO[GoodFinish] ,c3; {uEtherBootStatus already zero} { +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 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; {eof...}