// PupBootServ.bcpl -- Boot Server // See AltoBoot.press for protocol specification // Last modified March 15, 1979 11:37 PM by Boggs get "Pup0.decl" get "Pup1.decl" get "Streams.d" get "PupBootServ.decl" get "PupServEftp.decl" external [ // outgoing procedures BootServ; BootServCtx LockBootServ; UnlockBootServ StringCompare; DoubleUsc // incoming procedures InitializeContext; Dismiss; Block MoveBlock; Zero; Enqueue; Unqueue; Dequeue CompletePup; GetPBI; ReleasePBI ExchangePorts; SetPupDPort; SetPupSPort; SetPupID CallSwat; DoubleIncrement; Usc; SetTimer; TimerHasExpired PupServSend; PupServReceive; GetTime OpenFile; FilePos; SetFilePos; ReadBlock; Resets; Closes Allocate; Free; DefaultArgs // outgoing statics @bs // incoming statics ndbQ; lenPup; maxPupDataBytes bootLoaderPacket; CtxRunning; @ms ] static @bs // BootServCtx is the main process for this module. // It lives forever and does the following tasks: // Broadcasts Boot loaders. // Probes for new versions of boot files. // Starts a Receiver process when it finds a newer boot file version. // Destroys transient processes when they are done. // There are two types of transient processes: // Receivers receive boot files from other boot servers // Senders send boot files to: // Other boot servers who need to update a boot file. // Altos that are requesting a boot. // There can be at most one receiver process. // There can be at most two sender processes: // At most one sending down an Ethernet (fast net). // At most one sending down a net other than an Ethernet (slow net). // BootServ is logically part of the miscellaneous services process, // handling the Boot protocol packet types. // There are two global locks which if set prevent any more // transient processes from being created. // Internal lock is set from within the gateway // External lock is set by commands from GateControl //---------------------------------------------------------------------------- let BootServCtx() be //---------------------------------------------------------------------------- [ Dismiss(breathInterval) // destroy any contexts which are done compiletest nova ifso [ while bs>>BS.doneQ.head ne 0 do ReleasePBI(Dequeue(lv bs>>BS.doneQ)-offset PBI.pup/16) ] ifnot [ while bs>>BS.doneQ.head ne 0 do Free(ms>>MS.zone, Dequeue(lv bs>>BS.doneQ)) ] // don't do anything but kill servers if a global lock is set if bs>>BS.globalLocks ne 0 loop // run down the list of boot files doing house keeping: // 1) if any need updating then try to spawn an update process // 2) if we know the time and any files have dates later than // now, set their dates to 0. let now = vec 1; let knowTime = GetTime(now) let bfe = bs>>BS.bfeQ.head; while bfe ne 0 do [ if knowTime & DoubleUsc(now, lv bfe>>BFE.date) ls 0 then Zero(lv bfe>>BFE.date, 2) if bfe>>BFE.update ne 0 then CreateBootCtx(ReceiveBootFile, bfe, rcvrLock, lv bfe>>BFE.port) bfe = bfe>>BFE.link ] // probe for new boot files now and then if bs>>BS.bcstTimer gr 0 then bs>>BS.bcstTimer = bs>>BS.bcstTimer -1 if bs>>BS.bcstTimer le 0 then SendBootDir(0, 0) // Every five seconds, broadcast (to host 377) a Breath-of-life NON PUP on // all directly connected Ethernets containing an Alto Ethernet boot loader. let ndb = ndbQ!0; while ndb ne 0 do [ if ndb>>NDB.netType eq netTypeEther then [ structure EtherPBI: [ blank word offset PBI.pup/16-2 dest byte src byte type word ] let pbi = GetPBI(ms>>MS.soc, true); if pbi eq 0 break pbi>>PBI.ndb = ndb pbi>>PBI.packetLength = 256 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 (ndb>>NDB.level0Transmit)(pbi) ] ndb = ndb!0 ] ] repeat //---------------------------------------------------------------------------- and BootServ(pbi) be //---------------------------------------------------------------------------- [ ExchangePorts(pbi) switchon pbi>>PBI.pup.type into [ case ptBootFileRequest: [ if bs>>BS.globalLocks ne 0 endcase let bfe = bs>>BS.bfeQ.head; while bfe ne 0 do [ if bfe>>BFE.bfn eq pbi>>PBI.pup.id^2 & bfe>>BFE.exists ne 0 then [ CreateBootCtx(SendBootFile, bfe, (pbi>>PBI.ndb>>NDB.netType eq netTypeEther? fastLock, slowLock), lv pbi>>PBI.pup.dPort) break ] bfe = bfe>>BFE.link ] endcase ] case ptBootDirRequest: [ if bs>>BS.globalLocks eq 0 then SendBootDir(lv pbi>>PBI.pup.dPort, lv pbi>>PBI.pup.id) endcase ] case ptBootDirReply: [ if bs>>BS.globalLocks ne 0 endcase let now = vec 1; let knowTime = GetTime(now) let p = 1; until p ge ((pbi>>PBI.pup.length-pupOvBytes)+1)/2 do [ let bfd = lv pbi>>PBI.pup.words^p p = p + lenBFD + bfd>>BFD.name.length rshift 1 +1 if (bfd>>BFD.bfn & 1b15) ne 0 % not knowTime % (knowTime & DoubleUsc(now, lv bfd>>BFD.date) ls 0) loop let bfe = bs>>BS.bfeQ.head; while bfe ne 0 do [ if bfe>>BFE.bfn eq bfd>>BFD.bfn then [ if StringCompare(lv bfd>>BFD.name, lv bfe>>BFE.name) ne 0 break let dateUsc = DoubleUsc(lv bfd>>BFD.date, lv bfe>>BFE.date) if dateUsc ls 0 then bs>>BS.bcstTimer = 0 if dateUsc gr 0 then [ MoveBlock(lv bfe>>BFE.port, lv pbi>>PBI.pup.dPort, lenPort) bfe>>BFE.update = true ] break ] bfe = bfe>>BFE.link ] ] endcase ] case ptBootStatsRequest: [ MoveBlock(lv pbi>>PBI.pup.words, lv bs>>BS.stats, size Stats/16) CompletePup(pbi, ptBootStatsReply, pupOvBytes+size Stats/8) return ] case ptBootLockRequest: [ if Authenticate(pbi) then [ bs>>BS.externalLock = true CompletePup(pbi, ptBootLockReply, pupOvBytes) return ] endcase ] case ptBootUnlockRequest: [ if Authenticate(pbi) then [ bs>>BS.externalLock = false bs>>BS.bcstTimer = 0 CompletePup(pbi, ptBootUnlockReply, pupOvBytes) return ] endcase ] ] ReleasePBI(pbi) ] //---------------------------------------------------------------------------- and Authenticate(pbi) = pbi>>PBI.pup.id^1 eq 27182 //---------------------------------------------------------------------------- //---------------------------------------------------------------------------- and SendBootDir(port, id) be //---------------------------------------------------------------------------- // Send the boot file directory to port. Set the pup IDs to id. // If port is 0, then broadcast it on all directly connected nets. [ let allNets = port eq 0 test port eq 0 ifso [ port = table [ 0; 0; psMiscServ ] id = table [ 0; 0 ] bs>>BS.bcstTimer = bs>>BS.bcstInterval ] ifnot DoubleIncrement(lv bs>>BS.stats.dirsSent) let pbi, p = 0, nil // p indexes pbi: pbi>>PBI.pup.words^p let bfe = bs>>BS.bfeQ.head; while bfe ne 0 do [ if bfe>>BFE.exists then [ if pbi eq 0 then [ pbi = GetPBI(ms>>MS.soc, true); if pbi eq 0 return SetPupDPort(pbi, port) SetPupID(pbi, id) pbi>>PBI.allNets = allNets p = 0 ] let lBFD = lenBFD + bfe>>BFE.name.length/2 +1 if (p+lBFD) gr maxPupDataBytes/2 then [ //ran out of space in this packet CompletePup(pbi, ptBootDirReply, pupOvBytes+p*2) pbi = 0 loop ] MoveBlock(lv pbi>>PBI.pup.words^(p+1), lv bfe>>BFE.bfd, lBFD) p = p + lBFD ] bfe = bfe>>BFE.link ] if pbi ne 0 then CompletePup(pbi, ptBootDirReply, pupOvBytes+p*2) ] //---------------------------------------------------------------------------- and ReceiveBootFile(ctx) be // a BCTX //---------------------------------------------------------------------------- [ let bfe = ctx>>BCTX.bfe let eftp = vec lenEFTP; Zero(eftp, lenEFTP) eftp>>EFTP.frnPort = lv ctx>>BCTX.port eftp>>EFTP.fp = lv bfe>>BFE.fp eftp>>EFTP.realName = lv bfe>>BFE.name eftp>>EFTP.tempName = "TEMP.BOOT" eftp>>EFTP.timeOut1 = 100 // 1 sec eftp>>EFTP.timeOut2 = 2000 // 20 sec eftp>>EFTP.proc1 = RBFStartProc eftp>>EFTP.proc2 = RBFEndProc if PupServReceive(eftp) then [ DoubleIncrement(lv bs>>BS.stats.filesRcvd) SendBootDir(0, 0) ] //even if we fail, wait for receipt of next boot dir bcst before trying again bfe>>BFE.update = false DestroyBootCtx(ctx) //returning from a context does an implicit Block() ] //---------------------------------------------------------------------------- and RBFStartProc(soc) be //---------------------------------------------------------------------------- [ let pbi = GetPBI(soc, true); if pbi eq 0 return pbi>>PBI.pup.id^2 = CtxRunning>>BCTX.bfe>>BFE.bfn CompletePup(pbi, ptBootFileRequest, pupOvBytes) ] //---------------------------------------------------------------------------- and RBFEndProc(stream) be //---------------------------------------------------------------------------- [ let fPos = vec 1; FilePos(stream, fPos) Resets(stream) let v = vec 5; ReadBlock(stream, v, 5) MoveBlock(lv CtxRunning>>BCTX.bfe>>BFE.date, v+3, 2) CtxRunning>>BCTX.bfe>>BFE.exists = true SetFilePos(stream, fPos) ] //---------------------------------------------------------------------------- and SendBootFile(ctx) be // a BCTX //---------------------------------------------------------------------------- [ let eftp = vec lenEFTP; Zero(eftp, lenEFTP) eftp>>EFTP.frnPort = lv ctx>>BCTX.port eftp>>EFTP.fp = lv ctx>>BCTX.bfe>>BFE.fp eftp>>EFTP.realName = lv ctx>>BCTX.bfe>>BFE.name let fast = ctx>>BCTX.lockMask eq fastLock eftp>>EFTP.timeOut1 = fast? 50, 3000 //1/2, 30 eftp>>EFTP.timeOut2 = fast? 500, 3000 //5, 30 if PupServSend(eftp) then DoubleIncrement(fast? lv bs>>BS.stats.fastSends, lv bs>>BS.stats.slowSends) DestroyBootCtx(ctx) //returning from a context does an implicit Block() ] //---------------------------------------------------------------------------- and CreateBootCtx(proc, bfe, lockMask, port) be //---------------------------------------------------------------------------- [ if bs>>BS.globalLocks ne 0 % (bs>>BS.localLocks & lockMask) ne 0 return bs>>BS.localLocks = bs>>BS.localLocks % lockMask let ctx = alto? Allocate(ms>>MS.zone, 1200, true), valof //nova case [ //locks set above because GetPBI can call Block. let pbi = GetPBI(ms>>MS.soc, true) resultis pbi eq 0? 0, lv pbi>>PBI.pup ] test ctx ne 0 ifso [ InitializeContext(ctx, (alto? 1200, lenPup), proc, BCTXextra) ctx>>BCTX.bfe = bfe ctx>>BCTX.lockMask = lockMask MoveBlock(lv ctx>>BCTX.port, port, lenPort) Enqueue(ms>>MS.ctxQ, ctx) ] ifnot bs>>BS.localLocks = bs>>BS.localLocks & not lockMask ] //---------------------------------------------------------------------------- and DestroyBootCtx(ctx) be //---------------------------------------------------------------------------- // This doesn't actually destroy it. It releases ctx's locks // and queues it for destruction by BootCtx. [ Unqueue(ms>>MS.ctxQ, ctx) Enqueue(lv bs>>BS.doneQ, ctx) bs>>BS.localLocks = bs>>BS.localLocks & not ctx>>BCTX.lockMask ] //---------------------------------------------------------------------------- and LockBootServ() be //---------------------------------------------------------------------------- // No further transient processes will be created. // Returns when none are active. [ bs>>BS.internalLock = true Block() repeatuntil bs>>BS.localLocks eq 0 ] //---------------------------------------------------------------------------- and UnlockBootServ(name; numargs na) be //---------------------------------------------------------------------------- [ if na gr 0 then [ let bfe = bs>>BS.bfeQ.head; while bfe ne 0 do [ if StringCompare(name, lv bfe>>BFE.name) eq 0 then [ Zero(lv bfe>>BFE.fp, 5) let stream = OpenFile(name, ksTypeReadOnly, charItem, 0, lv bfe>>BFE.fp) if stream ne 0 then [ let v = vec 5; ReadBlock(stream, v, 5) MoveBlock(lv bfe>>BFE.date, v+3, 2) Closes(stream) bfe>>BFE.exists = true bs>>BS.bcstTimer = 0 ] break ] bfe = bfe>>BFE.link ] ] bs>>BS.internalLock = false ] //---------------------------------------------------------------------------- and StringCompare(s1, s2, first1, last1, first2, last2; numargs na) = valof //---------------------------------------------------------------------------- //Compares the "first1" through "last1" characters of the string //"s1" with the "first2" through "last2" characters of "s2", //with lower-case alphabetics matching upper-case. Returns a code //describing the result: // -2 s1 is an initial substring of s2 // -1 s1 is "less than" s2 but not an initial substring // 0 s1 is "equal to" s2 // 1 s1 is "greater than" s2 //The arguments beyond "s2" are optional and default to the entire //respective string. [ DefaultArgs(lv na, -2, 1, s1>>String.length, 1, s2>>String.length) let firstDif = first2-first1 for i = first1 to last1 do [ if i gr last2-firstDif resultis 1 let c1 = s1>>String.char^i if c1 ge $a & c1 le $z then c1 = c1-($a-$A) let c2 = s2>>String.char^(firstDif+i) if c2 ge $a & c2 le $z then c2 = c2-($a-$A) if c1 ne c2 resultis Usc(c1, c2) ] resultis (last2-last1 eq firstDif? 0, -2) ] //---------------------------------------------------------------------------- and DoubleUsc(lvA, lvB) = //---------------------------------------------------------------------------- // lvA and lvB are the addresses of two 32-bit operands Returns: // -1 if A < B // 0 if A = B // 1 if A > B (table [ 41003b // sta 0 3 2 ; lvA 45002b // sta 1 2 2 ; lvB 23003b // lda 0 @3 2 ; A high part 27002b // lda 1 @2 2 ; B high part 106414b // se 0 1 ; A, B 405b // jmp dusc1 11003b // isz 3 2 ; lvA 11002b // isz 2 2 ; lvB 23003b // lda 0 @3 2 ; A low part 27002b // lda 1 @2 2 ; B low part 106433b // dusc1: sleu 0 1 ; A, B 405b // jmp gr ; A > B 106414b // se 0 1 ; A, B 102001b // mkminusone 0 0 skp ; A < B 102460b // mkzero 0 0 ; A = B 1401b // jmp 1 3 102520b // gr: mkone 0 0 1401b // jmp 1 3 ])(lvA, lvB)