{File name: PreEtherInitial.mc
Description: choses what intial ether microcode to actually load
Edited from EtherInitial.mc
Last edited by:
Sturgis: 3-Oct-83 13:27:57: rename as PreEtherInitial.mc, begin appropriate edits
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.
Last edited by: Amy Fasnacht, July 9, 1982 6:48 PM: Reinitialize dY when finished
Last edited by: Amy Fasnacht, January 29, 1982 11:01 AM: Change socket numbers, add diagnostic boot
Last edited by: Amy Fasnacht, January 28, 1982 10:05 AM: Implement 3-word file numbers, set up constants for the Germ
Last edited by: Amy Fasnacht, January 15, 1982 10:51 AM: Delete miscRet return point
Last edited by: Amy Fasnacht, January 13, 1982 11:17 AM: Change algorithm for file checksums to the standard rotate-and-add instead of simple addition.
Last edited by: Amy Fasnacht, January 11, 1982 10:27 AM: Delete setting of DCtl register
Last edited by: Amy Fasnacht, December 15, 1981 12:49 PM: Comment changes
Last edited by: Amy Fasnacht, December 2, 1981 1:41 PM: Modify to run on Trident machine
Last edited by: Amy Fasnacht, November 24, 1981 9:43 AM: Set up boot file type
Last edited by: Amy 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], StartAddress[go];
{we begin with code copied from phase0}
Reserve[ProtectStart, ProtectFence], Reserve[0FE0, 0FFF];
{The boot kernel resets the CP I/O registers and enables memory refresh}
go:IOPCtl ← 0, rD ← 0, rDrh ← 0, CANCELBR[$, 0F],c1; {disable IOP port}
dbUStatus ← 0, rE ← 0, c2; {clear disk status register}
rErh ← 1,c3;
DCtl ← 3,c1; {in case not done elsewhere, allows task 1 interrupts}
c2;
c3;
{clear the first 128K of memory, includes the IO page, takes 54 msec}
clear0:MAR ← [rDrh, rD+0],c1;
MDR ← rE, rD ← rD + 1, ZeroBr,c2;
passTraps ← rE, BRANCH[clear0, $],c3; {die on faults}
MAR ← [rErh, rE + 0], GOTO[clear2],c1;
clear1:MAR← [rErh, rE+0],c1;
clear2:MDR← rD, rE← rE+1, ZeroBr,c2;
bootDevice ← 0, BRANCH[clear1, $],c3;
{now we insert code to wake up the IOP task asnd wait for SubBoot to arive from the IOP}
RegDef[uESubBoot, U, 0DE]; {this does not conflict with anything in apilot100dandelion.df, and must agree with similar definitions in IOPPreEtherIntial and MultiEtherInitial.}
uESubBoot ← 0, c1;
IOPCtl ← IOPInMode,c2;
WaitForSubBoot:
rE ← uESubBoot,c3;
[] ← rE, ZeroBr,c1;
rE ← 6, BRANCH[$, WaitForSubBoot], c2;
rE ← rE LRot8, c3;
uPacketType ← rE, {600 hex} c1;
{noop} c2;
{noop} c3;
{subsequent code comes from EtherInitial}
uEtherBootDone ← 0,c1;
uEtherBootStatus ← 0,c2;
rEtherBootRetries ← 0, uGotFirstCode ← 0,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;
rB ← germStart, BRANCH[StartNextPhase, 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;
{The requested code has been retrieved successfully by EtherInitial. Inform the IOP that the file data starts at 100 H.}
StartNextPhase:rD ← 1,c3;
rDrh ← 0,c1;
acR ← 0FF+1,c2;
c3;
MAR ← [rDrh, rD + 0],c1;
MDR ← acR, c2;
ToldIOP: {for burdock breakpoint}
{noop}c3;
GOTOABS[IdleLoc],c1;
BurdockLoop1:
CANCELBR[$, 0F],c1;
BurdockLoop:
GOTO[BurdockLoop],c*;
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 last word of the file id equals uESubBoot}
rY ← uESubBoot,c1, at[0, 4, Operation];
{noop}c2;
{noop} c3;
SetFileNo:
uFileNumber2 ← rY,c1;
rP ← 0AA,c2;
rY ← 0FF+1,c3;
{first word will be placed at 100H}
uNextAddress ← rY,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:rY ← 0FF+1,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, CANCELBR[$],c3;
rMM ← rY 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;