// IfsBootServ.bcpl -- Boot Server
// Copyright Xerox Corporation 1979, 1980, 1981, 1982, 1983
// Adapted from PupBootServ.bcpl, the boot server used in gateways
// See <Pup>EtherBoot.press for protocol specification
// NOTE: EtherBoot.br and IfsBootA must be loaded in the same overlay as this.

// Last modified January 22, 1983  10:46 AM by Taft

get "Pup0.decl"
get "Pup1.decl"
get "Ifs.decl"
get "IfsRs.decl"
get "IfsBootServ.decl"
get DR, pathName from "IfsFiles.decl"
get FD, fs, dr from "IfsDirs.decl"

// outgoing procedures
BootServEvent; NoticeDirectoryChange; BootServ; SendBootDir

// incoming procedures
SendBootFile; SendMicrocodeFile; BootServUncommon; CheckBFT; BootNoop
GetPBI; ExchangePorts; CompletePup; ReleasePBI; SetPupDPort; SetPupID
CreateJob; DestroyJob; QueueEvent
ReadRecLE; MapTree; Lock; Unlock; LockCell; UnlockCell
ExpandTemplate; ExtractSubstring; ConcatenateStrings; StringCompare
Allocate; Free; MoveBlock; Zero; Usc; DoubleIncrement; Max

// outgoing statics

// incoming statics
ndbQ; maxPupDataBytes; socMiscellaneous
bootLoaderPacket; ifsCtxQ; sysZone; system; primaryIFS; CtxRunning

static @bs
let BootServEvent(ecb) be
// check boot file table now and then
bs>>BS.bftCheckTimer = Max(bs>>BS.bftCheckTimer-1, 0)
if bs>>BS.bftCheckTimer le 0 then
   bs>>BS.updateCtx = CreateJob(CheckBFT, jobTypeBootUpdate, lenBootCtx-3)

if bs>>BS.externalLock eq 0 then
   // Every five seconds, broadcast (to host 377) a Breath-of-life NON PUP
   // containing an Alto Ethernet boot loader.
   let ndb = ndbQ!0
   let pbi = GetPBI(socMiscellaneous, true)
   if pbi ne 0 then
      structure EtherPBI:
         blank word offset PBI.pup/16-2
         dest byte
         src byte
         type word
      pbi>>PBI.ndb = ndb
      pbi>>PBI.packetLength = 256
      // Call an assembly-language procedure in the same overlay,
      // thereby forcing the overlay to be swapped into bank 0 if it
      // is not there already.  This will ensure that the bootLoaderPacket
      // static is valid.
      MoveBlock(lv pbi>>EtherPBI.dest, bootLoaderPacket, 256)
      pbi>>EtherPBI.dest = 377b  // special for microcode
      pbi>>EtherPBI.src = ndb>>NDB.localHost
      // pbi>>EtherPBI.type = etBreathOfLife  // special for microcode

QueueEvent(ecb, breathInterval)

and NoticeDirectoryChange(fd) be
// Called from ModifyDirFD to notice every change to the directory.
// fd designates a file that is being created, deleted, or renamed.
// Directory is locked at the time of the call, so this procedure may not call
// file operations and must beware of deadlocks.
// *** Implement a more general notification mechanism if anyone else ever needs it ***
// For now, if the file name begins with "<System>Boot>" then initiate immediate
// rebuilding of the boot file directory.
if bs ne 0 &  // boot server initialized yet?
 CtxRunning ne bs>>BS.updateCtx &  // not the boot file update process itself?
 fd>>FD.fs eq primaryIFS &  // primary file system?
 StringCompare("<System>Boot>", lv fd>>FD.dr>>DR.pathName) eq -2 then  // initial substring
   // request complete BFT rebuild 30 seconds from now.  Reason for waiting is so that
   // if the directory change was caused by an FTP store, the store will have
   // completed before the rebuild begins.
   bs>>BS.bftCheckTimer = 3000/breathInterval
   bs>>BS.bftRebuildTimer = 0
and BootServ(pbi) be
switchon pbi>>PBI.pup.type into
   case ptBootFileRequest:
   case ptBootNamedFileRequest:
      HandleBootFileRequest(pbi, pbi>>PBI.pup.id↑2, SendBootFile)

   case ptBootMicrocodeRequest:
      HandleBootFileRequest(pbi, pbi>>PBI.pup.id↑2 + uCodeOffsetBFN,

   case ptBootDirRequest:
      if bs>>BS.globalLocks eq 0 & Lock(lv bs>>BS.treeLock, false, true) then
         let pupHdr = vec offset Pup.words/16
         MoveBlock(pupHdr, lv pbi>>PBI.pup, offset Pup.words/16)
         ReleasePBI(pbi)  // give SendBootDir more PBIs to play with
         SendBootDir(lv pupHdr>>Pup.dPort, lv pupHdr>>Pup.id)
         Unlock(lv bs>>BS.treeLock)

and HandleBootFileRequest(pbi, bfn, Proc) be
let pupDataBytes = pbi>>PBI.pup.length - pupOvBytes
if pupDataBytes gr 100 then return
let bootingByName = pbi>>PBI.pup.type eq ptBootNamedFileRequest & pupDataBytes ne 0

// Not strictly necessary to lock the tree if booting by name, but it doesn't hurt.
if bs>>BS.globalLocks eq 0 & Lock(lv bs>>BS.treeLock, false, true) then
   let bfe = bootingByName? 0, FindBFE(bfn)

   // Fast booter if bootee is on directly-connected net; slow otherwise.
   // Permit at most one fast booter and one slow booter at a time.
   // Note that an ExchangePorts has been done, so dPort is the source port.
   let flags = pbi>>PBI.pup.dPort.net eq pbi>>PBI.ndb>>NDB.localNet?
    bsFastBooter, bsSlowBooter
   if (bootingByName % (bfe ne 0 & bfe>>BFE.exists)) & (bs>>BS.flags & flags) eq 0 then
      LockCell(lv bfe)  // no-op if there isn't one
      let ctx = CreateJob(Proc, jobTypeMiscellaneous, lenBootCtx-3, true)
      if ctx ne 0 then
         ctx>>BootCtx.userInfo = system

         ctx>>BootCtx.name = bootingByName?

          // Booting by name: Pup data is the boot file name, which we try to
          // match to a file in <System>Boot> (ignoring boot file number).
          // Trick: extract name by pretending entire PBI is a BCPL string.
           ExtractSubstring(pbi, (offset PBI.pup.bytes↑1)/8,
            ((offset PBI.pup.bytes↑1)/8-1)+pupDataBytes), false, true),

          // Booting by number: use file name given in BFE for that BFN.
          ExpandTemplate("Boot>$UO-$S", bfn, lv bfe>>BFE.name)

         MoveBlock(lv ctx>>BootCtx.port, lv pbi>>PBI.pup.dPort, lenPort)
         ctx>>BootCtx.booterFlags = flags
         bs>>BS.flags = bs>>BS.flags % flags
         // Sun boot request doesn't send first page of boot file
         ctx>>BootCtx.bytesToSkip = bootingByName? 512, 0
         Enqueue(ifsCtxQ, ctx)
      UnlockCell(lv bfe)
   Unlock(lv bs>>BS.treeLock)

// SendBootFile and SendMicrocodeFile are in a separate module, IfsBootSend.bcpl, for
// packaging reasons.
and SendBootDir(port, id) = valof
// Send the boot file directory to port.  Set the pup IDs to id.
// Returns true if boot file directory is non-empty and all of it was sent;
// false if directory is empty or ran out of PBIs while sending it.
// Caller is responsible for locking the BFT to maintain consistency.
// following 2 variables must immediately follow port and id in the frame.
let pbi, p = 0, 0  // p indexes pbi: pbi>>PBI.pup.words↑p

MapTree(bs>>BS.tree, 0, AppendBFD, lv port, 0, true)

if pbi ne 0 then CompletePup(pbi, ptBootDirReply, pupOvBytes+p*2)
DoubleIncrement(lv bs>>BS.stats.dirsSent)
resultis pbi ne 0

and AppendBFD(bfe, lvPort, nil) = valof
let pbi, p = lvPort!2, lvPort!3
if bfe>>BFE.exists then
   let lenBFD = offset BFD.name/16 + bfe>>BFE.name.length rshift 1 +1
   if p+lenBFD gr maxPupDataBytes rshift 1 then
      [  //ran out of space in this packet
      CompletePup(pbi, ptBootDirReply, pupOvBytes+p*2)
      pbi = 0; lvPort!2 = 0
   if pbi eq 0 then
      pbi = GetPBI(socMiscellaneous, true)
      if pbi eq 0 resultis false
      SetPupDPort(pbi, lvPort!0)
      SetPupID(pbi, lvPort!1)
      p = 0
   MoveBlock(lv pbi>>PBI.pup.words↑(p+1), lv bfe>>BFE.bfd, lenBFD)
   lvPort!2, lvPort!3 = pbi, p + lenBFD
resultis true