-- Copyright (C) 1983  by Xerox Corporation. All rights reserved. 
-- BootChannelEFTP.mesa, HGM, 12-Dec-83  0:54:11, Use ReturnPacket, fixup Timeouts
-- BootChannelEFTP.mesa, HGM, 12-Nov-83 19:25:19
--     25-May-83  9:29:31 by DKnutsen

<<
This program implements a BootChannel which reads from a PUP network address using EFTP and the normal Pup booting protocols.  Pilot boot files must be encapsulated inside Alto boot files because the first page of an Alto boot file contains the propogation date.

A single, serially reusable, channel is supported.

NOTE: The module BootChannelSPP 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 (actually the implementation of SimplePUPIO) 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 [Location],
  BootChannel USING [Create, Handle, Operation, Result],
  Device USING [Ethernet, Type],
  Environment USING [bytesPerPage, LongPointerFromPage, PageCount, wordsPerPage],
  GermOps USING [GermWorldError],
  PilotMP USING [
    cGermERROR, cGermFunnyPacket, cGermNoServer, cGermTimeout, Code,
    cWaitingForBootServer],
  ProcessorFace USING [GetClockPulses, SetMP],
  PupTypes USING [PupAddress, PupSocketID, PupType, Pair, miscSrvSoc],
  ResidentMemory USING [Allocate, Free],
  System USING [Microseconds],
  
  SimplePUPIO USING [
    GetRawBufferSize, Initialize, cantHandleDeviceCode, Finalize, ReceivePacket,
    ReturnPacket, SendPacket, timedOutBytes];

BootChannelEFTP: PROGRAM
  IMPORTS
    remainingChannels: BootChannel, Environment, GermOps, ProcessorFace,
      ResidentMemory, SimplePUPIO
  EXPORTS BootChannel
  SHARES Device, GermOps =
  BEGIN

  -- PARAMETERS:

  maxBroadcastsForBootServer: CARDINAL = 15;

  timeoutBeforeConnected: System.Microseconds = 10000000;
  timeoutWhileConnected: System.Microseconds = 30000000;

  -- VARIABLES:

  bootFileNumber: CARDINAL;

  mySocket: PupTypes.PupSocketID = LOOPHOLE[ProcessorFace.GetClockPulses[]];
  him: PupTypes.PupAddress;
  anyBootServer: PupTypes.PupAddress = [[0], [0], PupTypes.miscSrvSoc];

  rawBuffer: LONG POINTER;
  recvStarted: BOOLEAN;
  receiveSeqNumber: CARDINAL;
  timeout: System.Microseconds;

  funnySequenceNumber: PilotMP.Code = PilotMP.cGermFunnyPacket;

  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 a PUP network address in pLocation↑.
      IF operation # read THEN  -- can't write cause no permanent buffer
        GermOps.GermWorldError[PilotMP.cGermERROR];
      rawBuffer ← ResidentMemory.Allocate[
        hyperspace,
        (SimplePUPIO.GetRawBufferSize[] + Environment.wordsPerPage -
          1)/Environment.wordsPerPage];
      result ← SimplePUPIO.Initialize[
        pLocation.deviceType, pLocation.deviceOrdinal, rawBuffer, avoidCleanup];
      WITH r: result SELECT FROM
        ok =>
          BEGIN  -- can handle device
          him ← [[0], [0], [0, 0]];
          recvStarted ← FALSE;
          receiveSeqNumber ← 0;
          bootFileNumber ← pLocation.bootFileNumber;  -- save in global.
	  timeout ← timeoutBeforeConnected;
          RETURN[[ok[]], Transfer];
          END;
        error => {
          FreeBuffer[];
          IF r.code # SimplePUPIO.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--
    -- not anything I implement.  Pass it on.
    RETURN remainingChannels.Create[pLocation, operation];
    END;

  Transfer: BootChannel.Handle --[page, count] RETURNS [result]--  =
    BEGIN
    -- Note: Modifies arguments.
    where: LONG POINTER ← Environment.LongPointerFromPage[page];
    bytes: CARDINAL;
    id: PupTypes.Pair;
    type: PupTypes.PupType;
    bootServerBroadcastsRemaining: CARDINAL;

    IF count = 0 THEN {
      ShutDownBootServer[bootFileNumber]; FreeBuffer[]; RETURN[[ok[]]]};
    bootServerBroadcastsRemaining ← maxBroadcastsForBootServer;
    WHILE count > 0 DO  -- until count sequential good packets have arrived
      DO  --until correct current sequential packetarrives--
        IF receiveSeqNumber = 0 THEN
          BEGIN
          ProcessorFace.SetMP[PilotMP.cWaitingForBootServer];
          result ← SimplePUPIO.SendPacket[
            anyBootServer, mySocket, bootFileSend, [0, bootFileNumber], NIL, 0];
          IF result # [ok[]] THEN GOTO ErrorExit;
          bootServerBroadcastsRemaining ← bootServerBroadcastsRemaining.PRED;
          IF bootServerBroadcastsRemaining = 0 THEN
            GermOps.GermWorldError[PilotMP.cGermNoServer];
          END;
        [result, bytes, id, type] ← SimplePUPIO.ReceivePacket[
          @him, mySocket, where, Environment.wordsPerPage, timeout];
        IF bytes = SimplePUPIO.timedOutBytes THEN {
          IF receiveSeqNumber = 0 THEN LOOP  -- prod boot server again
          ELSE GermOps.GermWorldError[PilotMP.cGermTimeout]};
        IF result # [ok[]] THEN GOTO ErrorExit;
        IF type # eData THEN LOOP;
        IF bytes # Environment.bytesPerPage THEN
          GermOps.GermWorldError[PilotMP.cGermFunnyPacket];
        timeout ← timeoutWhileConnected;
        SELECT id.b FROM
          receiveSeqNumber => EXIT;  -- that's the right packet.
          < receiveSeqNumber => {  -- Booter lost our ack
            receiveSeqNumber ← receiveSeqNumber.PRED;
            ProcessorFace.SetMP[PilotMP.cGermFunnyPacket];
            result ← SendAck[];
            receiveSeqNumber ← receiveSeqNumber.SUCC;
	    IF result # [ok[]] THEN GOTO ErrorExit};
          ENDCASE => GermOps.GermWorldError[PilotMP.cGermFunnyPacket];
        ENDLOOP;
      result ← SendAck[];
      IF result # [ok[]] THEN GOTO ErrorExit;
      receiveSeqNumber ← receiveSeqNumber.SUCC;
      IF receiveSeqNumber = 1 THEN LOOP;  -- First packet is Alto Disk loader.
      where ← where + Environment.wordsPerPage;
      count ← count.PRED;
      ENDLOOP;
    RETURN[[ok[]]];
    EXITS ErrorExit => {FreeBuffer[]; RETURN};
    END;  --Transfer--

  ShutDownBootServer: PROCEDURE [bootFileNumber: CARDINAL] =
    BEGIN
    myBufferInRawBuffer: LONG POINTER =  --
      -- my one-page buffer overlays the end of SimplePUPIO's buffer.
      rawBuffer + SimplePUPIO.GetRawBufferSize[] - Environment.wordsPerPage;
    bytes: CARDINAL;
    id: PupTypes.Pair;
    type: PupTypes.PupType;
    result: BootChannel.Result;

    BEGIN  --scope of Quit--
    DO
      -- Borrow tail of buffer to slurp any excess boot file pages.
      [result, bytes, id, type] ← SimplePUPIO.ReceivePacket[
        @him, mySocket, myBufferInRawBuffer, Environment.wordsPerPage, timeoutWhileConnected];
      IF bytes = SimplePUPIO.timedOutBytes THEN GOTO CleanUp;
      IF result # [ok[]] THEN GOTO Quit;
      SELECT TRUE FROM
        (type = eData AND id.b = receiveSeqNumber) => NULL;
          -- Discarding LoadState and BCD at end of file
        (type = eData AND id.b < receiveSeqNumber) =>
          receiveSeqNumber ← receiveSeqNumber - 1; -- Booter lost our ack.
        (type = eEnd AND id.b = receiveSeqNumber) =>
          BEGIN
          result ← SendAck[];
          IF result # [ok[]] THEN GOTO Quit;
          EXIT;
          END;
        ENDCASE => GermOps.GermWorldError[PilotMP.cGermFunnyPacket];
      result ← SendAck[];
      receiveSeqNumber ← receiveSeqNumber + 1;
      IF result # [ok[]] THEN GOTO Quit;
      ENDLOOP;
    GOTO CleanUp;
    EXITS CleanUp => SimplePUPIO.Finalize[avoidCleanup]; Quit => NULL;
    END;  --scope of Quit--
    END;

  FreeBuffer: PROCEDURE = {
    ResidentMemory.Free[
      hyperspace,
      (SimplePUPIO.GetRawBufferSize[] + Environment.wordsPerPage -
        1)/Environment.wordsPerPage, rawBuffer]};

  SendAck: PROCEDURE RETURNS [result: BootChannel.Result] = {
    RETURN SimplePUPIO.ReturnPacket[eAck, NIL, 0]};

  END.


LOG

February 7, 1980  2:28 PM   Knutsen/Murray
   Create file from BootChannelDisk of February 6, 1980  2:43 PM
February 8, 1980  12:12 AM   Knutsen/Murray   Buffer unscrambling
February 8, 1980  1:08 AM   Knutsen/Murray
   Discard trailing garbage on end of boot file (bcd for the loader?)
February 8, 1980  10:21 PM   Forrest   Put MP hack to show page 0
February 9, 1980  11:11 AM   Knutsen   Loop when page 0 seen second time
February 9, 1980  1:34 PM   Knutsen   Fudge in extra space before buffer
February 9, 1980  2:39 PM   Knutsen   display packet seq number in cursor
February 9, 1980  1:34 PM   Knutsen/HGM   Undo fudge
February 14, 1980  5:23 PM   McJones   New Boot.Location, faces, etc.
April 17, 1980  10:14 PM   Luniewski   AllocateMDSPages -> AllocateMDS
April 22, 1980  5:37 PM   McJones   Add avoidCleanup: TRUE to ActivateDriver call
June 23, 1980  2:37 PM   McJones   OISProcessorFace=>ProcessorFace
16-Aug-81 17:08:54   Forrest   7.0a build
 3-Nov-81 21:18:08   Knutsen
   Use Utilities.LongPointerFromPage instead of Boot.LP. Use ResidentHeap to allocate IOCB.
25-May-83  9:29:26   DKnutsen
   Get LongPointerFromPage from Environment. Renamed module BootChannelEther => BootChannelBSP. Make compatible with new BootChannel. Now will read from any attached ethernet. Clean up.Make compatible with new BootChannel. Now will read from any attached ethernet. Clean up.