-- File: BootChannelSPP.mesa - last edit: -- AOF 11-Feb-88 16:59:46 -- HGM 22-Nov-83 8:34:35 -- DKnutsen 13-Nov-83 16:43:38 -- Copyright (C) 1983, 1985, 1988 by Xerox Corporation. All rights reserved. << This program implements a BootChannel which reads from an NS network address using the Sequenced Packet Protocol (SPP). A single, serially reusable channel is supported. NOTE: The module BootChannelBSP is substantially similar to this one. If you make a change here, consider making a change to it too. TO DO: Improve performance. How about double buffering? WARNING: At present, this channel allocates a temporary buffer when BootChannel.Create is called, and frees it when the channel is closed. When network operations other than Inload[inloadeMode: load] are implemented, this buffer space will have to be allocated at germ initialization time and retained forever. >> DIRECTORY Boot USING [EthernetBootFileNumber, Location], BootChannel USING [ Create, Handle, Operation, Result, transferCleanup, transferWait], BootServerTypes USING [BootFileRequest, dataSST, dataWords], Device USING [Ethernet], Environment USING [ Byte, bytesPerWord, LongPointerFromPage, PageCount, PageNumber, wordsPerPage], Frame USING [GetReturnFrame, SetReturnFrame], GermOps USING [GermWorldError], Inline USING [LowHalf], NetworkStream USING [closeReplySST, closeSST], NSConstants USING [etherBootGermSocket], NSTypes USING [BufferBody, bytesPerSppHeader, ConnectionID, PacketType], PilotMP USING [ cGermDriver, cGermERROR, cGermFunnyPacket, cGermNoServer, cGermShortBootFile, cGermTimeout, Code, cWaitingForBootServer], PrincOps USING [ControlLink, Port], ProcessorFace USING [mp, SetMP], ResidentMemory USING [Allocate, Free], SimpleNSIO USING [ ByteCount, cantHandleDeviceCode, Finalize, fromAnyHost, fromAnySocket, GetRawBufferSize, Initialize, ReceivePacket, ReturnPacket, SendPacket, timedOutBytes], System USING [ broadcastHostNumber, GetClockPulses, HostNumber, Microseconds, NetworkAddress, SocketNumber]; BootChannelSPP: PROGRAM IMPORTS remainingChannels: BootChannel, Environment, Frame, GermOps, Inline, ProcessorFace, ResidentMemory, SimpleNSIO, System EXPORTS BootChannel SHARES Device, GermOps = BEGIN -- PARAMETERS: bootServerConnectionTries: CARDINAL = 10; timeoutBeforeConnected: System.Microseconds = 10000000; timeoutWhileConnected: System.Microseconds = 30000000; -- Variables and constants: rawBuffer: LONG POINTER; b: LONG POINTER TO NSTypes.BufferBody; bootLocation: LONG POINTER TO Boot.Location; -- (saved pointer) nullConnection: NSTypes.ConnectionID = [0]; -- (no official null). fullBodyDataBytes: CARDINAL = NSTypes.bytesPerSppHeader + BootServerTypes.dataWords*Environment.bytesPerWord; Page: TYPE = ARRAY [0..BootServerTypes.dataWords) OF WORD; PageDataPointer: TYPE = LONG POINTER TO Page; germsSocket: System.SocketNumber = NSConstants.etherBootGermSocket; PageNumber: TYPE = Environment.PageNumber; PageCount: TYPE = Environment.PageCount; earlyEndOfFile: PilotMP.Code = PilotMP.cGermShortBootFile; funnyPacketSequence: PilotMP.Code = PilotMP.cGermFunnyPacket; funnyPacketSize: PilotMP.Code = PilotMP.cGermFunnyPacket; funnyPacketType: PilotMP.Code = PilotMP.cGermFunnyPacket; funnyPacketSubtype: PilotMP.Code = PilotMP.cGermFunnyPacket; nextPacketDidntArrive: PilotMP.Code = PilotMP.cGermTimeout; normal: PilotMP.Code = PilotMP.cGermDriver; noBootServerResponded: PilotMP.Code = PilotMP.cGermNoServer; waitingForBootServer: PilotMP.Code = PilotMP.cWaitingForBootServer; -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- The Implementation -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Create: PUBLIC --BootChannel.-- PROCEDURE [ pLocation: LONG POINTER TO Boot.Location, operation: BootChannel.Operation, buffer: LONG POINTER ¬ NIL] RETURNS [result: BootChannel.Result, handle: BootChannel.Handle] = BEGIN IF pLocation.deviceType IN Device.Ethernet -- --OR phone lines OR ...-- THEN BEGIN --see if we can handle this device-- -- We MUST have been given an NS network address in pLocation­. IF operation # read THEN -- can't write cause no permanent buffer GermOps.GermWorldError[PilotMP.cGermERROR]; rawBuffer ¬ ResidentMemory.Allocate[ hyperspace, (SimpleNSIO.GetRawBufferSize[] + Environment.wordsPerPage - 1)/Environment.wordsPerPage]; [bufferBody: b, result: result] ¬ SimpleNSIO.Initialize[ pLocation.deviceType, pLocation.deviceOrdinal, rawBuffer, avoidCleanup]; WITH r: result SELECT FROM ok => BEGIN -- can handle device bootLocation ¬ pLocation; -- remember for later. [] ¬ InitializeReadPage[]; -- allocate frame, initialize PORT. RETURN[[ok[]], Transfer]; END; error => { FreeBuffer[]; IF r.code # SimpleNSIO.cantHandleDeviceCode THEN RETURN -- error. ELSE NULL}; -- fall through and pass it on. tryOtherLocation => {FreeBuffer[]; RETURN}; ENDCASE => GermOps.GermWorldError[PilotMP.cGermERROR]; END; --see if we can handle this device-- --ASSERT: Device is not an network, or not one -- that my implementation of SimpleNSIO can deal with. RETURN remainingChannels.Create[pLocation, operation]; END; Transfer: BootChannel.Handle --[page, count] RETURNS [result]-- = BEGIN dest: PageDataPointer ¬ Environment.LongPointerFromPage[page]; data: PageDataPointer; IF count = BootChannel.transferWait THEN RETURN[[ok[]]] -- only single buffering implemented for now ELSE IF count = BootChannel.transferCleanup THEN BEGIN -- stop receiving UNTIL ReadPage[100].data = NIL DO ENDLOOP; -- flush rest of file. RETURN[[ok[]]]; -- (we ignore errors on flush) END; IF count NOT IN CARDINAL THEN -- not supported yet. GermOps.GermWorldError[PilotMP.cGermERROR]; FOR countRemaining: CARDINAL DECREASING IN [1..CARDINAL[count]] DO [data, result] ¬ ReadPage[countRemaining]; IF result # [ok[]] THEN RETURN; -- (result already set) IF data = NIL THEN {FreeBuffer[]; RETURN[[error[earlyEndOfFile]]]}; dest­ ¬ data­; dest ¬ dest + BootServerTypes.dataWords; ENDLOOP; RETURN[[ok[]]]; END; GetUniqueConnectionID: PROC RETURNS [NSTypes.ConnectionID] = INLINE { RETURN Inline.LowHalf[System.GetClockPulses[]]}; -- Coroutine linkage to network input: ReadPage: PROC [allocation: CARDINAL] RETURNS [data: PageDataPointer, result: BootChannel.Result] = -- LOOPHOLE[LONG[@AwaitReadPageRequest]]; -- indirect control link to the PORT. -- allocation is the number of pages the caller is ready to process promptly. -- data is valid until the next call to ReadPage. -- Returns data = NIL if no more pages in boot file or error; the channel -- will have been closed. AwaitReadPageRequest: --RESPONDING-- PORT [ data: PageDataPointer, result: BootChannel.Result] RETURNS [allocation: CARDINAL]; -- args/results match ReadPage (but swapped). -- NOTE: See BootServerTypes for definition of protocol. InitializeReadPage: PROC RETURNS [ --matches PORT args-- data: PageDataPointer, result: BootChannel.Result] = BEGIN allocation: CARDINAL; -- "argument" myConnection: NSTypes.ConnectionID = GetUniqueConnectionID[]; -- NOTE: The SPEED of the processing loop below -- is important, and it is close to being too slow. -- Return from initialization; Await request for first page.. -- Set my PORT call to return to my caller on call below: LOOPHOLE[AwaitReadPageRequest, PrincOps.Port].dest ¬ PrincOps.ControlLink[frame[Frame.GetReturnFrame[]]]; allocation ¬ AwaitReadPageRequest[NIL, [ok[]]]; -- We now have a request for the first boot file page. THROUGH [0..bootServerConnectionTries) DO hisConnection: NSTypes.ConnectionID ¬ nullConnection; timeout: LONG CARDINAL ¬ timeoutBeforeConnected; sequenceWanted: CARDINAL; ReturnPacket: PROC RETURNS [BootChannel.Result] = -- (A proc to save code space. Speed not an issue -- since only used in exceptional cases.) -- Caller should have set systemPacket and subtype. BEGIN b.sendAck ¬ FALSE; b.attention ¬ FALSE; b.endOfMessage ¬ FALSE; b.unusedType ¬ 0; b.sourceConnectionID ¬ myConnection; b.destinationConnectionID ¬ hisConnection; b.sequenceNumber ¬ 0; b.acknowledgeNumber ¬ sequenceWanted; -- (was incremented) b.allocationNumber ¬ sequenceWanted + allocation - 1; RETURN SimpleNSIO.ReturnPacket[ dataBytes: NSTypes.bytesPerSppHeader, type: sequencedPacket]; END; -- Broadcast for boot server and hope he responds: ProcessorFace.SetMP[waitingForBootServer]; LOOPHOLE[@b.nsWords, LONG POINTER TO BootServerTypes.BootFileRequest]­ ¬ [ sppRequest[bootLocation.ethernetRequest.bfn, myConnection]]; -- We transmit to bootLocation.address, which is either a broadcast -- address or the specific address that the initial microcode used. result ¬ SimpleNSIO.SendPacket[ dataBytes: SIZE[sppRequest BootServerTypes.BootFileRequest]*Environment.bytesPerWord, type: bootServerPacket, sourceSocket: germsSocket, dest: bootLocation.ethernetRequest.address]; IF result # [ok[]] THEN GOTO Quit; BEGIN --scope of RebroadcastForBootServer-- remote: System.NetworkAddress ¬ bootLocation.ethernetRequest.address; bootFileFinished: BOOLEAN; IF bootLocation.ethernetRequest.address.host = System.broadcastHostNumber THEN remote.host ¬ SimpleNSIO.fromAnyHost; -- no host known. remote.socket ¬ SimpleNSIO.fromAnySocket; -- server will choose one. bootFileFinished ¬ FALSE; sequenceWanted ¬ 0; DO --until (boot server responds and) whole boot file read -- type: NSTypes.PacketType; dataBytes: SimpleNSIO.ByteCount; source: LONG POINTER TO System.NetworkAddress; -- (Typically, bootLocation contains server used by initial ucode.) [dataBytes: dataBytes, type: type, source: source, result: result] ¬ SimpleNSIO.ReceivePacket[ getFrom: remote, mySocket: germsSocket, timeout: timeout]; IF dataBytes = SimpleNSIO.timedOutBytes THEN GOTO RebroadcastForBootServer; IF result # [ok[]] THEN GOTO Quit; SELECT TRUE FROM type = sequencedPacket => BEGIN IF dataBytes < NSTypes.bytesPerSppHeader THEN LOOP; IF b.destinationConnectionID # myConnection THEN LOOP; IF hisConnection = nullConnection THEN { remote ¬ source­; -- latch onto responding server. hisConnection ¬ b.sourceConnectionID; timeout ¬ timeoutWhileConnected}; IF b.sourceConnectionID # hisConnection THEN LOOP; IF NOT b.systemPacket AND b.sequenceNumber = sequenceWanted THEN BEGIN --data packet-- IF b.sequenceNumber > sequenceWanted THEN { ProcessorFace.SetMP[funnyPacketSequence]; LOOP}; IF b.sequenceNumber = sequenceWanted THEN BEGIN --desired packet in sequence-- sequenceWanted ¬ sequenceWanted.SUCC; -- ASSUMES no non-fatal errors. SELECT b.subtype FROM BootServerTypes.dataSST => -- data packet. BEGIN IF dataBytes # fullBodyDataBytes THEN { result ¬ [error[funnyPacketSize]]; GOTO Quit}; IF ProcessorFace.mp # normal THEN ProcessorFace.SetMP[normal]; -- Return data to caller; Await request for next page.. allocation ¬ AwaitReadPageRequest[ LOOPHOLE[@b.sppWords], [ok[]]]; END; NetworkStream.closeSST => -- all boot file pages received. BEGIN b.systemPacket ¬ FALSE; b.subtype ¬ NetworkStream.closeReplySST; result ¬ ReturnPacket[]; IF result # [ok[]] THEN GOTO Quit; LOOP; -- don't test for send Ack END; NetworkStream.closeReplySST => bootFileFinished ¬ TRUE; ENDCASE => {result ¬ [error[funnyPacketSubtype]]; GOTO Quit}; END; --desired packet in sequence-- --ASSERT: sequenceNumber was <= sequenceWanted. -- Fall through and maybe ack.. END --data packet-- ELSE -- bogus seq number (or system packet) ProcessorFace.SetMP[funnyPacketSequence]; --ASSERT: System packet or sequenceNumber was <= sequenceWanted. -- Maybe ack.. IF b.sendAck THEN BEGIN b.systemPacket ¬ TRUE; b.subtype ¬ BootServerTypes.dataSST; result ¬ ReturnPacket[]; IF result # [ok[]] THEN GOTO Quit; END; --sendAck-- END; --type = sequencedPacket-- ENDCASE => {result ¬ [error[funnyPacketType]]; GOTO Quit}; IF bootFileFinished THEN {result ¬ [ok[]]; GOTO Quit}; ENDLOOP; -- reading next boot file page-- --ASSERT: Can't get here. EXITS RebroadcastForBootServer => NULL; -- (fall through) END; --scope of RebroadcastForBootServer-- IF sequenceWanted > 0 THEN { -- can't restart.. result ¬ [error[nextPacketDidntArrive]]; GOTO Quit}; ENDLOOP; -- trying to get boot server to respond-- result ¬ [error[noBootServerResponded]]; GOTO Quit; EXITS Quit --[result]-- => BEGIN -- NOTE: DO NOT come here before AwaitReadPageRequest has been called! -- (Just RETURN yourself instead.) Frame.SetReturnFrame[ -- Set RETURN to go to port caller. LOOPHOLE[AwaitReadPageRequest, PrincOps.Port].dest.frame]; SimpleNSIO.Finalize[avoidCleanup]; FreeBuffer[]; data ¬ NIL; -- ("result" already set by caller.) RETURN; END; END; FreeBuffer: PROCEDURE = { ResidentMemory.Free[ hyperspace, (SimpleNSIO.GetRawBufferSize[] + Environment.wordsPerPage - 1)/Environment.wordsPerPage, rawBuffer]}; END. LOG 25-Jan-82 14:30:48 FXH Created file from MStoreImpl and TestBootServer. 24-Feb-82 10:17:03 Forrest&Hal Twiddles to timeouts and drop errorPacket => irrecoverable error. 9-Jun-83 16:33:52 WDK Handle truncation warnings. Moved BootFileNumber, and EthernetRequest to Boot.mesa, BootServerBody to BootServerTypes. OISCP => NS. Use official type definitions rather than private copies. Get LongPointerFromPage from Environment. Renamed module BootChannelEthernet => BootChannelNS. Make compatible with new BootChannel. Improved documentation. Get stuff from BootServerTypes. Now will read from any network. Allocation bug fix. Put informative code in MP during unusual situations. Generate new connection id on boot server retries. Eliminate redundant variables. Eliminate replicated code. Improve control structure. Eliminate redundant tests. Pass buffer in to SimpleNSIO, so we can recover the storage! 19-Jul-83 11:22:05 AWL NSTypes.bytesPerLevel2SppHeader => NSTypes.bytesPerSppHeader. Port stuff changed for 32-bit procedure descriptors. 13-Nov-83 16:44:06 WDK Don't set mp on every good packet (helps Dicentra performance). Don't generate new connnectionID if have to rebroadcast for boot server. 28-Nov-83 17:28:52 WDK SPP allocation must always be 1 since we can't handle early packets. 8-Jun-84 10:01:05 AWL convert to multiple buffering BootChannel interface. This implementation still does single buffering. 14-Nov-84 15:20:53 KEK {nullConnection: NSTypes.ConnectionID = [ --arbitrary-- 53726];} ¬ [0] (AR 6087).