-- Copyright (C) 1984, 1985  by Xerox Corporation. All rights reserved. 
-- BootServersA.mesa, HGM, 25-Jun-85 15:24:48

DIRECTORY
  Inline USING [LongCOPY, LowHalf],
  Process USING [Detach],
  Space USING [Activate, Deactivate, Interval, nullInterval],
  Stream USING [Delete, Handle, PutBlock],
  System USING [
    GetClockPulses, NetworkAddress, nullSocketNumber,
    Pulses, PulsesToMicroseconds, SocketNumber],

  BootServerBasics USING [BootFileNumber],
  BootServer USING [BootFileType, Counters, MachineType],
  BootServerTypes USING [BootFileRequest, dataWords],
  BootServerFriends USING [
    ActivateFiles, AddBootFile, BootFile, DeactivateFiles,
    DeleteBootFileTable, FindBootFile, LockFileRead,
    pagesPerSwapUnit, SetupSpace, UnlockFile],
  Buffer USING [NSBuffer],
  NetworkStream USING [
    Close, ConnectionID, ConnectionFailed, ConnectionSuspended, CreateTransducer,
    GetUniqueConnectionID],
  NSConstants USING [bootServerSocket],
  NSTypes USING [ConnectionID],
  ServerHeap USING [Create, Destroy],
  Socket USING [
    AssignNetworkAddress, ChannelHandle, Create, Delete,
    GetDestination, GetPacket, GetSendBuffer, PutPacket,
    ReturnBuffer, SetDestination, SetPacketWords, SetWaitTime,
    SwapSourceAndDestination, TimeOut],
  Stats USING [StatCounterIndex, StatIncr, StatsStringToIndex];

BootServersA: MONITOR
  IMPORTS
    Inline, Process, Space, Stream, System,
    BootServerFriends, NetworkStream, ServerHeap, Socket, Stats
  EXPORTS NetworkStream, BootServer =
  BEGIN
  
  ConnectionID: PUBLIC TYPE = NSTypes.ConnectionID;

  counters: BootServer.Counters ← [0, 0, 0, 0, 0, 0];

  pleaseStop: BOOLEAN;
  bootServerFork: PROCESS ← NIL;

  bootRequests: Stats.StatCounterIndex;

  CreateServer: PUBLIC ENTRY PROCEDURE = BEGIN ServerHeap.Create[]; END;

  AddBootFileToList: PUBLIC PROCEDURE [
    fileName: LONG STRING, bootFileNumber: BootServerBasics.BootFileNumber,
    fileType: BootServer.BootFileType, machineType: BootServer.MachineType,
    pup: BOOLEAN] =
    BEGIN
    BootServerFriends.AddBootFile[
      bootFileNumber, fileName, fileType, machineType, pup];
    END;

  ActivateServer: PUBLIC ENTRY PROCEDURE =
    BEGIN
    BootServerFriends.ActivateFiles[];
    pleaseStop ← FALSE;
    bootServerFork ← FORK Server[];
    END;

  DeactivateServer: PUBLIC ENTRY PROCEDURE =
    BEGIN
    pleaseStop ← TRUE;
    JOIN bootServerFork;
    bootServerFork ← NIL;
    BootServerFriends.DeactivateFiles[];
    END;

  ForgetBootFileList: PUBLIC PROCEDURE =
    BEGIN BootServerFriends.DeleteBootFileTable[]; END;

  DeleteServer: PUBLIC ENTRY PROCEDURE = BEGIN ServerHeap.Destroy[]; END;

  GetStatistics: PUBLIC PROCEDURE RETURNS [BootServer.Counters] =
    BEGIN RETURN[counters]; END;

  Server: PROCEDURE =
    BEGIN
    cH: Socket.ChannelHandle;
    b: Buffer.NSBuffer;
    cH ← Socket.Create[NSConstants.bootServerSocket, 0, 2];
    Socket.SetWaitTime[cH, 10000--ms--];
    UNTIL pleaseStop DO
      b ← NIL;
      b ← Socket.GetPacket[cH ! Socket.TimeOut => CONTINUE];
      IF b = NIL THEN LOOP;
      IF b.ns.packetType = bootServerPacket THEN
        BEGIN
        header: LONG POINTER TO BootServerTypes.BootFileRequest =
          LOOPHOLE[@b.ns.nsWords[0]];
        SELECT header.etherBootPacketType FROM
          simpleRequest =>
            BEGIN
            bootFileHeader: LONG POINTER TO simpleRequest
              BootServerTypes.BootFileRequest = LOOPHOLE[header];
            target: System.NetworkAddress;
            Socket.SwapSourceAndDestination[b];
            target ← Socket.GetDestination[b];
            Stats.StatIncr[bootRequests];
            counters.microcodeBootFilesRequested ←
              counters.microcodeBootFilesRequested + 1;
            IF ~booting THEN
              BEGIN
              bf: BootServerFriends.BootFile;
              bf ← BootServerFriends.FindBootFile[bootFileHeader.bootFileNumber];
              IF bf # NIL THEN
                BEGIN
                booting ← TRUE;
                Process.Detach[FORK MicrocodeBooter[bf, target]];
                END;
              END;
            END;  -- busy
          sppRequest =>
            BEGIN
            bootFileHeader: LONG POINTER TO sppRequest
              BootServerTypes.BootFileRequest = LOOPHOLE[header];
            target: System.NetworkAddress;
            connection: NetworkStream.ConnectionID;
            Socket.SwapSourceAndDestination[b];
            target ← Socket.GetDestination[b];
            connection ← bootFileHeader.connectionID;
            Stats.StatIncr[bootRequests];
            counters.bootFilesRequested ← counters.bootFilesRequested + 1;
            IF ~booting THEN
              BEGIN
              bf: BootServerFriends.BootFile;
              bf ← BootServerFriends.FindBootFile[bootFileHeader.bootFileNumber];
              IF bf # NIL THEN
                BEGIN
                booting ← TRUE;
                Process.Detach[FORK FastBooter[bf, target, connection]];
                END;
              END;
            END;  -- busy
          ENDCASE;
        END;
      IF b # NIL THEN Socket.ReturnBuffer[b];
      ENDLOOP;
    Socket.Delete[cH];
    END;

  booting: BOOLEAN ← FALSE;
  wordsPerSwapUnit: CARDINAL = BootServerFriends.pagesPerSwapUnit*BootServerTypes.dataWords;

  MicrocodeBooter: PUBLIC PROCEDURE [
    bf: BootServerFriends.BootFile, target: System.NetworkAddress] =
    BEGIN
    pulses: System.Pulses ← System.GetClockPulses[];
    b: Buffer.NSBuffer;
    packetNumber: CARDINAL ← 1;
    from: LONG POINTER;
    wordsLeft: LONG CARDINAL ← bf.bytes/2;
    pagesLeft: CARDINAL ← bf.pages;
    cH: Socket.ChannelHandle;
    bootData: LONG POINTER TO simpleData BootServerTypes.BootFileRequest;
    overhead: CARDINAL = SIZE[simpleData BootServerTypes.BootFileRequest];
    clumpSize: CARDINAL ← BootServerTypes.dataWords;
    this, next: Space.Interval ← Space.nullInterval;
    IF ~BootServerFriends.LockFileRead[bf] THEN
      BEGIN booting ← FALSE; RETURN; END;
    IF bf.space = Space.nullInterval THEN BootServerFriends.SetupSpace[bf];
    from ← bf.space.pointer;
    next ← [from, MIN[BootServerFriends.pagesPerSwapUnit, bf.pages]];
    Space.Activate[next];
    cH ← Socket.Create[System.nullSocketNumber];
    FOR page: CARDINAL ← 0, page + 1 UNTIL wordsLeft = 0 DO
      IF (page MOD BootServerFriends.pagesPerSwapUnit) = 0 THEN
        BEGIN
        IF this # Space.nullInterval THEN Space.Deactivate[this];
        this ← next;
        IF next # Space.nullInterval THEN
	  BEGIN
	  next.pointer ← this.pointer + wordsPerSwapUnit;
	  pagesLeft ← pagesLeft - BootServerFriends.pagesPerSwapUnit;
          next.count ← MIN[next.count, pagesLeft];
	  IF pagesLeft = 0 THEN next ← Space.nullInterval;
	  END;
        IF next # Space.nullInterval THEN Space.Activate[next];
        END;
      IF wordsLeft < BootServerTypes.dataWords THEN
        clumpSize ← Inline.LowHalf[wordsLeft];
      b ← Socket.GetSendBuffer[cH];
      Socket.SetDestination[b, target];
      b.ns.packetType ← bootServerPacket;
      bootData ← LOOPHOLE[@b.ns.nsWords];
      bootData↑ ← [simpleData[
        bootFileNumber: bf.code, packetNumber: packetNumber, data:]];
      Inline.LongCOPY[from: from, nwords: clumpSize, to: @bootData.data];
      Socket.SetPacketWords[b, overhead + clumpSize];
      Socket.PutPacket[cH, b];
      packetNumber ← packetNumber + 1;
      from ← from + clumpSize;
      wordsLeft ← wordsLeft - clumpSize;
      ENDLOOP;
    b ← Socket.GetSendBuffer[cH];
    Socket.SetDestination[b, target];
    b.ns.packetType ← bootServerPacket;
    bootData ← LOOPHOLE[@b.ns.nsWords];
    bootData↑ ← [simpleData[
      bootFileNumber: bf.code, packetNumber: packetNumber, data:]];
    Socket.SetPacketWords[b, overhead];
    Socket.PutPacket[cH, b];  -- End Marker for Initial
    Space.Deactivate[this];
    IF next # Space.nullInterval THEN Space.Deactivate[next];
    Socket.Delete[cH];
    pulses ← System.Pulses[System.GetClockPulses[] - pulses];
    bf.count ← bf.count + 1;
    bf.ms ← bf.ms + System.PulsesToMicroseconds[pulses]/1000;
    BootServerFriends.UnlockFile[bf];
    counters.microcodeBootFilesSent ← counters.microcodeBootFilesSent + 1;
    booting ← FALSE;
    END;

  FastBooter: PUBLIC PROCEDURE [
    bf: BootServerFriends.BootFile, target: System.NetworkAddress,
    connection: NetworkStream.ConnectionID] =
    BEGIN
    pulses: System.Pulses ← System.GetClockPulses[];
    from: LONG POINTER;
    wordsLeft: LONG CARDINAL ← bf.bytes/2;
    pagesLeft: CARDINAL ← bf.pages;
    clumpSize: CARDINAL ← BootServerTypes.dataWords;
    this, next: Space.Interval ← Space.nullInterval;
    stream: Stream.Handle ← NIL;
    good: BOOLEAN ← FALSE;
    IF ~BootServerFriends.LockFileRead[bf] THEN
      BEGIN booting ← FALSE; RETURN; END;
    IF bf.space = Space.nullInterval THEN BootServerFriends.SetupSpace[bf];
    from ← bf.space.pointer;
    stream ← NetworkStream.CreateTransducer[
      local: Socket.AssignNetworkAddress[],
      remote: target,
      localConnID: NetworkStream.GetUniqueConnectionID[],
      remoteConnID: connection,
      activelyEstablish: FALSE,
      classOfService: transactional !
        NetworkStream.ConnectionFailed => CONTINUE];
    IF stream = NIL THEN
      BEGIN  -- It happened once.  /HGM
      BootServerFriends.UnlockFile[bf];
      booting ← FALSE;
      RETURN;
      END;
    next ← [from, MIN[BootServerFriends.pagesPerSwapUnit, bf.pages]];
    Space.Activate[next];
    FOR page: CARDINAL ← 0, page + 1 UNTIL wordsLeft = 0 DO
      IF (page MOD BootServerFriends.pagesPerSwapUnit) = 0 THEN
        BEGIN
        IF this # Space.nullInterval THEN Space.Deactivate[this];
        this ← next;
        IF next # Space.nullInterval THEN
	  BEGIN
	  next.pointer ← this.pointer + wordsPerSwapUnit;
	  pagesLeft ← pagesLeft - BootServerFriends.pagesPerSwapUnit;
          next.count ← MIN[next.count, pagesLeft];
	  IF pagesLeft = 0 THEN next ← Space.nullInterval;
	  END;
        IF next # Space.nullInterval THEN Space.Activate[next];
        END;
      IF wordsLeft < BootServerTypes.dataWords THEN
        clumpSize ← Inline.LowHalf[wordsLeft];
      Stream.PutBlock[
        stream, [from, 0, 2*clumpSize], TRUE !
        NetworkStream.ConnectionSuspended => EXIT];
      from ← from + clumpSize;
      wordsLeft ← wordsLeft - clumpSize;
      REPEAT FINISHED => good ← NetworkStream.Close[stream] = good;
      ENDLOOP;
    Space.Deactivate[this];
    IF next # Space.nullInterval THEN Space.Deactivate[next];
    Stream.Delete[stream];
    pulses ← System.Pulses[System.GetClockPulses[] - pulses];
    IF good THEN
      BEGIN
      bf.count ← bf.count + 1;
      bf.ms ← bf.ms + System.PulsesToMicroseconds[pulses]/1000;
      END;
    BootServerFriends.UnlockFile[bf];
    counters.bootFilesSent ← counters.bootFilesSent + 1;
    booting ← FALSE;
    END;

  bootRequests ← Stats.StatsStringToIndex["Boot Server Requests"];
  END.