-- File: PupBootServer.mesa,  Last Edit: HGM  February 4, 1981  11:29 PM

DIRECTORY
  InlineDefs USING [BcplLongNumber, MesaToBcplLongNumber],
  StringDefs USING [BcplSTRING],
  Process USING [Detach],
  BootServerDefs USING [
    pleaseStop, booting, slowBooting, statBootDir, statFile, statFileSent,
    statFileSentSlow, statMicrocodeBooted, statFileTroubles, statFileNeverStarted,
    statBusyDisk, statBusyBooting, statUnknown, BootFile, FastBooter, lock,
    SlowBooter, MicrocodeBooter, LookAtBootDir, bootStatsRequest, BootServerStats,
    lockBooterRequest, lockBooterReply, unlockBooterRequest, unlockBooterReply,
    microcodeRequest, microcodeVersionNumber],
  StatsDefs USING [StatIncr],
  PupDefs USING [
    GetFreePupBuffer, ReturnFreePupBuffer, Pair, PupAddress, PupBuffer,
    PupRouterBroadcastThis, PupRouterSendThis, ReturnPup, FastPath,
    SetPupContentsWords, SwapPupSourceAndDest],
  PupTypes USING [maxDataWordsPerGatewayPup, miscSrvSoc],
  BufferDefs;

PupBootServer: MONITOR LOCKS BootServerDefs.lock
  IMPORTS InlineDefs, Process, BootServerDefs, StatsDefs, PupDefs
  EXPORTS BootServerDefs
  SHARES BufferDefs =
  -- network
  BEGIN OPEN StatsDefs, PupDefs, BootServerDefs;

  first: BootFile ← NIL;
  -- maybe we should allow several slow booters
  lock: BOOLEAN ← FALSE;

  -- This won't work with small buffers
  dwpb: CARDINAL = PupTypes.maxDataWordsPerGatewayPup;

  microcodeOffset: CARDINAL = 3000B;

  GetPointerToBootTable: PUBLIC PROCEDURE RETURNS [POINTER TO BootFile] =
    BEGIN RETURN[@first]; END;

  PupBootServer: PUBLIC PROCEDURE [b: PupBuffer] =
    BEGIN
    SELECT b.pupType FROM
      bootFileSend => SendBootFile[b];
      microcodeRequest => SendMicrocode[b];
      bootDirReq => SendBootDir[b];
      bootDirReply =>
	BEGIN
	IF lock OR pleaseStop THEN ReturnFreePupBuffer[b] ELSE LookAtBootDir[b];
	END;
      bootStatsRequest => BootServerStats[b];
      lockBooterRequest =>
	BEGIN lock ← TRUE; ReturnPup[b, lockBooterReply, 0]; END;
      unlockBooterRequest =>
	BEGIN lock ← FALSE; ReturnPup[b, unlockBooterReply, 0]; END;
      ENDCASE => ReturnFreePupBuffer[b];
    END;

  SendBootFile: PROCEDURE [b: PupBuffer] =
    BEGIN
    bf: BootFile;
    him: PupAddress;
    slow: BOOLEAN;
    code: WORD = b.pupID.b;
    SwapPupSourceAndDest[b]; -- fixup defaults
    him ← b.dest;
    ReturnFreePupBuffer[b];
    IF lock THEN RETURN;
    slow ← ~FastPath[him];
    IF (booting AND ~slow) OR (slowBooting AND slow) THEN
      BEGIN StatIncr[statBusyBooting]; RETURN; END;
    IF (bf ← FindEntry[code]) = NIL THEN RETURN;
    StatIncr[statFile];
    IF slow THEN slowBooting ← TRUE ELSE booting ← TRUE;
    Process.Detach[FORK Booter[bf, him, IF slow THEN slow ELSE fast]];
    END;

  SendMicrocode: PROCEDURE [b: PupBuffer] =
    BEGIN
    bf: BootFile;
    him: PupAddress;
    version: WORD ← b.pupID.a;
    code: WORD = microcodeOffset + b.pupID.b;
    SwapPupSourceAndDest[b]; -- fixup defaults
    him ← b.dest;
    ReturnFreePupBuffer[b];
    IF lock THEN RETURN;
    IF version # microcodeVersionNumber THEN RETURN;
    IF booting THEN BEGIN StatIncr[statBusyBooting]; RETURN; END;
    IF (bf ← FindEntry[code]) = NIL THEN RETURN;
    StatIncr[statFile];
    booting ← TRUE;
    Process.Detach[FORK Booter[bf, him, micro]];
    END;

  FindEntry: ENTRY PROCEDURE [code: WORD] RETURNS [bf: BootFile] =
    BEGIN
    previous: BootFile ← NIL;
    FOR bf ← first, bf.next UNTIL bf = NIL DO
      IF bf.code = code AND ~bf.unknown THEN EXIT;
      previous ← bf;
      REPEAT FINISHED => BEGIN StatIncr[statUnknown]; RETURN; END;
      ENDLOOP;
    IF previous # NIL THEN
      BEGIN previous.next ← bf.next; bf.next ← first; first ← bf; END;
    END;

  Booter: PROCEDURE [bf: BootFile, who: PupAddress, what: {fast, slow, micro}] =
    BEGIN
    StatIncr[
      SELECT
      (SELECT what FROM
	 fast => FastBooter[bf, who],
	 slow => SlowBooter[bf, who],
	 micro => MicrocodeBooter[bf, who],
	 ENDCASE => ERROR) FROM
	fast => statFileSent,
	slow => statFileSentSlow,
	micro => statMicrocodeBooted,
	diskBusy => statBusyDisk,
	neverStarted => statFileNeverStarted,
	troubles => statFileTroubles,
	ENDCASE => ERROR];
    IF what = slow THEN slowBooting ← FALSE ELSE booting ← FALSE;
    END;

  -- If b is NIL, then we broadcast the info.

  SendBootDir: PUBLIC PROCEDURE [b: PupBuffer] =
    BEGIN
    me, him: PupAddress;
    word, size: CARDINAL ← 0;
    bf: BootFile;
    id: Pair;
    broadcast: BOOLEAN ← b = NIL;
    IF lock THEN BEGIN IF b # NIL THEN ReturnFreePupBuffer[b]; RETURN; END;
    IF ~broadcast THEN
      BEGIN
      id ← b.pupID;
      SwapPupSourceAndDest[b]; -- fixup broadcast
      me ← b.source;
      him ← b.dest;
      END
    ELSE
      BEGIN
      id ← [0, 0];
      b ← GetFreePupBuffer[];
      me.socket ← him.socket ← PupTypes.miscSrvSoc;
      END;
    FOR bf ← first, bf.next UNTIL bf = NIL DO
      IF bf.unknown THEN LOOP;
      size ← ((1 + bf.fileName.length) + 1)/2;
      IF (word + size + 1 + 2) > dwpb THEN
	BEGIN -- this buffer is full, send it and get another one
	b.pupType ← bootDirReply;
	b.pupID ← id;
	SetPupContentsWords[b, word];
	b.dest ← him;
	b.source ← me;
	IF broadcast THEN PupRouterBroadcastThis[b] ELSE PupRouterSendThis[b];
	word ← 0;
	b ← GetFreePupBuffer[];
	END;
      b.pupWords[word] ← bf.code;
      LOOPHOLE[@b.pupWords[word + 1], LONG POINTER TO InlineDefs.BcplLongNumber]↑
	← InlineDefs.MesaToBcplLongNumber[bf.create];
      CopyString[bf.fileName, LOOPHOLE[@b.pupWords + word + 1 + 2]];
      word ← word + size + 1 + 2;
      ENDLOOP;
    b.pupType ← bootDirReply;
    b.pupID ← id;
    SetPupContentsWords[b, word];
    b.dest ← him;
    b.source ← me;
    IF broadcast THEN PupRouterBroadcastThis[b] ELSE PupRouterSendThis[b];
    StatIncr[statBootDir];
    END;

  CopyString: PROCEDURE [s: STRING, d: LONG POINTER TO StringDefs.BcplSTRING] =
    BEGIN
    d.length ← s.length;
    FOR i: CARDINAL IN [0..s.length) DO d.char[i] ← s[i]; ENDLOOP;
    END;

  END.