-- Copyright (C) 1983, 1985 by Xerox Corporation. All rights reserved.
-- BootChannelSPP.mesa 12-Sep-85 14:09:32 by HGM, old allocate scheme for testing
-- BootChannelSPP.mesa 22-Nov-83 8:34:35 by HGM, send ack on future packets, window of 2
-- BootChannelSPP.mesa 13-Nov-83 16:43:38 by DKnutsen
<<
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]
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).