-- 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).