-- BootChannelEther.mesa
-- Last Edited by: Taft, February 25, 1983 4:16 pm

DIRECTORY
  Boot USING [Location, LP],
  BootChannel USING [Create, Operation, Handle, transferCleanup, transferWait],
  DeviceTypes USING [ethernet],
  Environment USING [bytesPerPage, PageCount, PageNumber, wordsPerPage],
  Inline USING [LongDiv, LongMult],
  MiniEthernetDefs USING [ActivateDriver, SendPacket, RecvPacket, KillDriver],
  ProcessorFace USING [GetClockPulses, microsecondsPerHundredPulses],
  PilotMP USING [
    cGermDeviceError, cGermFunnyPacket, cGermNoServer, cGermTimeout, Code],
  PupTypes USING [PupAddress, PupSocketID, PupType, Pair, miscSrvSoc],
  ResidentMemory USING [AllocateMDS];

BootChannelEther: PROGRAM
  IMPORTS
    Boot, RemainingChannels: BootChannel, Inline, MiniEthernetDefs, ProcessorFace,
    ResidentMemory
  EXPORTS BootChannel =
  -- Implementation of BootChannel for Ethernet 1.
  -- A single, serially reusable, channel is supported.
  BEGIN OPEN Boot, Environment;
  CantActivateDriver: PilotMP.Code = PilotMP.cGermDeviceError;
  NoBootServerResponded: PilotMP.Code = PilotMP.cGermNoServer;
  NextPacketDidntArrive: PilotMP.Code = PilotMP.cGermTimeout;
  FunnySizePacket: PilotMP.Code = PilotMP.cGermFunnyPacket;
  FunnySequenceNumber: PilotMP.Code = PilotMP.cGermFunnyPacket;
  pAllocateNext: LONG POINTER TO UNSPECIFIED; -- allocator for items in first 64K.
  bfn: CARDINAL; -- boot file number.
  iocb: LONG POINTER;
  bufferLength: CARDINAL = 300;
  fudge: CARDINAL = 25; -- must be bigger than encapsulation and pup overhead
  totalBufferLength: CARDINAL = bufferLength + fudge;
  buffer: LONG POINTER TO ARRAY (0..totalBufferLength] OF WORD;
  countTotalBuffer: PageCount =
    (totalBufferLength + wordsPerPage - 1)/wordsPerPage;

  mySocket: PupTypes.PupSocketID = LOOPHOLE[ProcessorFace.GetClockPulses[]];
  him: PupTypes.PupAddress;
  anyBootServer: PupTypes.PupAddress = [[0], [0], PupTypes.miscSrvSoc];
  pulsesPerSecond: CARDINAL =
    Inline.LongDiv[100*1000000, ProcessorFace.microsecondsPerHundredPulses];

  recvStarted: BOOLEAN;
  receiveSeqNumber: CARDINAL;
  Create: PUBLIC PROCEDURE [
    pLocation: POINTER TO Location, operation: BootChannel.Operation,
    dFirst64KStorage: LONG DESCRIPTOR FOR ARRAY OF WORD]
    RETURNS [handle: BootChannel.Handle] =
    BEGIN
    pAllocateNext ← BASE[dFirst64KStorage]; -- reset first 64K allocator
    IF pLocation.deviceType = DeviceTypes.ethernet THEN
      BEGIN
      bfn ← pLocation.bootFileNumber;
      SELECT operation FROM
	read => NULL;
	ENDCASE => Error[PilotMP.cGermDeviceError];
      StartRecving[bfn];
      handle ← EFTPGetClump;
      END
    ELSE -- not anything I implement.  Pass it on.
      handle ← RemainingChannels.Create[pLocation, operation, dFirst64KStorage];
    END;

  StartRecving: PROCEDURE [bfn: CARDINAL] =
    BEGIN
    bytes: INTEGER;
    id: PupTypes.Pair;
    type: PupTypes.PupType;
    timeout: LONG CARDINAL;
    Timer: PROCEDURE RETURNS [BOOLEAN] = {RETURN [TimeoutHasExpired[timeout]]};
    iocb ← Allocate[ --SIZE[MiniEthernetDefs.iocb]--16];
    buffer ← LONG[ResidentMemory.AllocateMDS[countTotalBuffer]];
    IF ~MiniEthernetDefs.ActivateDriver[buffer, bufferLength, iocb, TRUE] THEN
      Error[CantActivateDriver];
    him ← [[0], [0], [0, 0]];
    recvStarted ← FALSE;
    receiveSeqNumber ← 0;
    THROUGH [0..15) DO
      MiniEthernetDefs.SendPacket[
	anyBootServer, mySocket, bootFileSend, [0, bfn], NIL, 0];
      timeout ← SetTimeout[2];  -- will try 15 times at 2-second intervals
      -- Borrow tail of buffer to discard first packet
      [bytes, id, type] ← MiniEthernetDefs.RecvPacket[
	@him, mySocket, buffer + fudge, bufferLength, Timer];
      IF bytes < 0 THEN LOOP;
      IF type # eData THEN LOOP;
      IF bytes # Environment.bytesPerPage THEN Error[FunnySizePacket];
      -- Discard first packet, it's the Alto boot loader
      SendAck[];
      RETURN;
      REPEAT FINISHED => Error[NoBootServerResponded];
      ENDLOOP;
    END;

  EFTPGetClump: BootChannel.Handle
    --PROCEDURE [page: PageNumber, count: PageCount]-- =
    BEGIN
    where: LONG POINTER ← LPFromPage[page];
    bytes: INTEGER;
    id: PupTypes.Pair;
    type: PupTypes.PupType;
    timeout: LONG CARDINAL;
    Timer: PROCEDURE RETURNS [BOOLEAN] = {RETURN [TimeoutHasExpired[timeout]]};
    IF count = BootChannel.transferWait THEN RETURN;
    -- Ether BootChannel is synchronous; nothing to do
    IF count = BootChannel.transferCleanup THEN
      BEGIN StopRecving[bfn]; RETURN END; -- transfers done.  Shutdown the booter.
    THROUGH [0..count) DO
      DO
	-- Handy control structure
	timeout ← SetTimeout[30];  -- time out in 30 seconds
	[bytes, id, type] ← MiniEthernetDefs.RecvPacket[
	  @him, mySocket, where, wordsPerPage, Timer];
	IF bytes < 0 THEN Error[NextPacketDidntArrive];
	IF type # eData THEN LOOP;
	IF bytes # Environment.bytesPerPage THEN Error[FunnySizePacket];
	SELECT id.b FROM
	  receiveSeqNumber + 1 => EXIT;
	  receiveSeqNumber =>
	    BEGIN -- Booter lost our ack
	    SendAck[];
	    LOOP;
	    END;
	  ENDCASE => Error[FunnySequenceNumber];
	ENDLOOP;
      where ← where + wordsPerPage;
      receiveSeqNumber ← receiveSeqNumber + 1;
      SendAck[];
      ENDLOOP;
    END;

  StopRecving: PROCEDURE [bfn: CARDINAL] =
    BEGIN
    bytes: INTEGER;
    id: PupTypes.Pair;
    type: PupTypes.PupType;
    timeout: LONG CARDINAL;
    Timer: PROCEDURE RETURNS [BOOLEAN] = {RETURN [TimeoutHasExpired[timeout]]};
    DO
      timeout ← SetTimeout[30];  -- time out in 30 seconds
      -- Borrow tail of buffer to process tail
      [bytes, id, type] ← MiniEthernetDefs.RecvPacket[
	@him, mySocket, buffer + fudge, bufferLength, Timer];
      IF bytes < 0 THEN Error[NextPacketDidntArrive];
      SELECT TRUE FROM
	(type = eData AND id.b = receiveSeqNumber) =>
	  BEGIN -- Booter lost our ack
	  SendAck[];
	  LOOP;
	  END;
	(type = eData AND id.b = receiveSeqNumber + 1) =>
	  BEGIN -- Discard LoadState and BCD at end of file
	  receiveSeqNumber ← receiveSeqNumber + 1;
	  SendAck[];
	  LOOP;
	  END;
	(type = eEnd AND id.b = receiveSeqNumber + 1) =>
	  BEGIN receiveSeqNumber ← receiveSeqNumber + 1; SendAck[]; EXIT; END;
	ENDCASE => Error[FunnySequenceNumber];
      ENDLOOP;
    MiniEthernetDefs.KillDriver[];
    --FreeIocb[iocb];  ++ deallocate not implemented.
    --ResidentMemory.FreeMDSPages[base: Inline.LowHalf[buffer], pages: countTotalBuffer];  ++ deallocate not implemented.

    END;

  SendAck: PROCEDURE =
    BEGIN
    MiniEthernetDefs.SendPacket[
      him, mySocket, eAck, [0, receiveSeqNumber], NIL, 0];
    END;

  SetTimeout: PROCEDURE [timeout: CARDINAL --seconds--] RETURNS [timer: LONG CARDINAL] =
    BEGIN
    RETURN [ProcessorFace.GetClockPulses[] + Inline.LongMult[timeout, pulsesPerSecond]]; 
    END;

  TimeoutHasExpired: PROCEDURE [timer: LONG CARDINAL] RETURNS [BOOLEAN] =
    BEGIN
    RETURN [LOOPHOLE[ProcessorFace.GetClockPulses[]-timer, INT] >= 0];
    END;

  -- Allocator for 16-word aligned storage in first64K

  Allocate: PROCEDURE [size: CARDINAL] RETURNS [lp: LONG POINTER TO UNSPECIFIED] =
    BEGIN pAllocateNext ← (lp ← pAllocateNext) + LONG[((size + 15)/16)*16] END;

  Error: SIGNAL [PilotMP.Code] = CODE;

  LPFromPage: PROCEDURE [page: PageNumber] RETURNS [LONG POINTER] = INLINE
    BEGIN RETURN[LP[highbits: page/256, lowbits: page*256 --MOD 216--]] END;

  END.
LOG
Time: February 7, 1980  2:28 PM	By: Knutsen and Murray	Action: Create file from BootChannelDisk of February 6, 1980  2:43 PM
Buffer unscrambling: February 8, 1980  12:12 AM
Discard Tail: February 8, 1980  1:08 AM
Time: February 8, 1980  12:12 AM	By: Knutsen and Murray	Action: Buffer unscrambling
Time: February 8, 1980  1:08 AM	By: Knutsen and Murray	Action: Discard trailing garbage on end of boot file (bcd for the loader?)
Time: February 8, 1980  10:21 PM	By: Forrest	Action: Put MP hack to show page 0
Time: February 9, 1980  11:11 AM	By: Knutsen	Action: Loop when page 0 seen second time
Time: February 9, 1980  1:34 PM	By: Knutsen	Action: Fudge in extra space before buffer
Time: February 9, 1980  2:39 PM	By: Knutsen	Action: display packet seq number in cursor
Time: February 9, 1980  1:34 PM	By: Knutsen/HGM	Action: Undo fudge
Time: February 14, 1980  5:23 PM	By: McJones	Action: New Boot.Location, faces, etc.
Time: April 17, 1980  10:14 PM	By: Luniewski	Action: AllocateMDSPages -> AllocateMDS
Time: April 22, 1980  5:37 PM	By: McJones	Action: Add avoidCleanup: TRUE to ActivateDriver call
Time: June 23, 1980  2:37 PM	By: McJones	Action: OISProcessorFace=>ProcessorFace
February 25, 1983 3:51 pm  Taft  Use real-time clock for timeout, not spin loop!