-- NXControl.mesa; edited by Gobbel; January 16, 1981  9:32 PM
-- Last modified December 14, 1983  2:29 PM by Taft
-- Last modified June 13, 1986  6:08 PM by Wyatt

DIRECTORY
  AltoDisplay USING [DCBchainHead, DCBHandle, DCBnil],
  BcplOps USING [BcplJSR],
  Boot: FROM "BootX" USING [bootPhysicalVolume, inLoad, Location, nullDiskFileID, Request,
    pRequest],
  BootDirDefs USING [cmd, CmdDirEntry, CmdDirEntryPtr, DirEntryPtr,
    DirEntryType, file, FileDirEntryPtr, germ, InsertDirEntry, killProber,
    ListPossibleMatches, Lookup, ProbeProcess, uCode],
  BootSwap USING [sPilotSwitches],
  ControlDefs USING [FieldDescriptor, FrameHandle],
  DeviceTypes USING [ethernet, sa4000],
  DiskDefs USING [
    CB, CBptr, DA, DL, DS, DSdone, DSgoodStatus, DSmaskStatus, InvalidDA],
  EFTPDefs USING [EFTPAbortReceiving, EFTPEndReceiving,
    EFTPFinishReceiving, EFTPGetBlock, EFTPOpenForReceiving,
    EFTPSetRecvTimeout, EFTPTimeOut],
  IODefs USING [BS, ControlA, ControlW, CR, DEL, ESC, NewLine, ReadChar, ReadLine,
    Rubout, SP, WriteChar, WriteLine, WriteDecimal, WriteOctal, WriteString],
  InlineDefs USING [BITAND, BITSHIFT, BITXOR, LongCOPY],
  KeyDefs USING [Keys],
  MiscDefs USING [CallDebugger, Zero],
  MMOps USING [BootViaNet, MakeBoot],
  Mopcodes USING [zLI4, zMISC, zRBL, zSHIFT, zSTARTIO, zWFS],
  NXDefs USING [Erase, Host, illPtr, Line, LineReset, NewLine, NoteUserAction,
    NXDisplay, ResetTo, Timer, SetTime, UserAbort, WatchDog],
  OsStaticDefs USING [OsStatics, AltoVersionNumber],
  ProcessDefs USING [
    Detach, DisableInterrupts, EnableInterrupts, MsecToTicks, SetTimeout],
  PupDefs USING [AppendPupAddress, GetFreePupBuffer, PupBuffer,
    PupPackageMake, PupRouterSendThis, SetPupContentsWords,
    UniqueLocalPupAddress],
  PupTypes USING [PupAddress],
  SDDefs USING [SD, sUncaughtSignal],
  StringDefs USING [AppendChar, AppendDecimal, AppendString,
    AppendSubString, EquivalentSubStrings, InvalidNumber, StringToDecimal,
    SubStringDescriptor],
  SystemDefs USING [AllocateHeapNode, AllocateResidentPages, FreePages],
  StartList,
  TemporaryBooting USING [defaultSwitches, Switches, UpDown],
  TimeDefs USING [AppendDayTime, PackedTime, UnpackDT];

NXControl: MONITOR
  IMPORTS BootDirDefs, EFTPDefs, InlineDefs, IODefs,
    MiscDefs, MMOps, BcplOps, NXDefs, ProcessDefs, PupDefs, StringDefs,
    SystemDefs, TimeDefs
  EXPORTS NXDefs
  SHARES DiskDefs =
BEGIN OPEN BootDirDefs, DiskDefs;

req: Boot.Request;
switches: TemporaryBooting.Switches ← TemporaryBooting.defaultSwitches;
switchString: STRING ← [30];
cursor: POINTER TO ARRAY [0..16) OF WORD = LOOPHOLE[431B];
doneCursor: ARRAY [0..16) OF WORD ← -- "Loaded"
  [160000B, 40000B, 41616B, 42122B,
  42116B, 46122B, 175637B, 0,
  30006B, 10002B, 10002B, 70616B,
  111122B, 111722B, 111022B, 74717B];
Question: SIGNAL [s: STRING] = CODE;
alto: BOOLEAN;
debug: PUBLIC BOOLEAN ← FALSE;

CmdRec: TYPE = RECORD [
  proc: PROC, name: STRING, validOnAlto: BOOLEAN ← TRUE];

nCommands: CARDINAL = 11;
cmdTable: ARRAY [0..nCommands) OF CmdRec = [
  [BootDP0, "BootDP0"],
  [Cedar, "Cedar", FALSE],
  [FileStat, "FileStat"],
  [Othello, "Othello", FALSE],
  [Partition, "Partition", FALSE],
  [PhysVolBoot, "PhysicalVolumeBoot", FALSE],
  [Probe, "Probe"],
  [Quit, "Quit"],
  [ReadSwitches, "Switches", FALSE],
  [SetTimeCmd, "SetTime"],
  [SetVersions, "SetVersions", FALSE]];

Catcher: PROC [msg, signal: UNSPECIFIED, frame: ControlDefs.FrameHandle] =
  {MiscDefs.CallDebugger["UNCAUGHT SIGNAL"L]};

Run: PROCEDURE =
  BEGIN OPEN IODefs;
  cmdPtr: DirEntryPtr;
  DO ENABLE
      BEGIN
      Rubout => BEGIN WriteString[" XXX"L]; LOOP END;
      Question =>
	BEGIN ENABLE NXDefs.UserAbort => GOTO Aborted;
	NXDefs.LineReset["Command, from:"L];
	ListPossibleMatches[s, cmd];
	NXDefs.NewLine[]; NXDefs.Line["or boot file, from:"L];
	ListPossibleMatches[s, file]; WriteChar[CR];
	RESUME;
	EXITS Aborted => RESUME;
	END;
      END;
    IF ~IODefs.NewLine[] THEN WriteChar[CR]; WriteChar['>];
    cmdPtr ← ReadCmd[">"L];
    WITH entry: cmdPtr SELECT FROM
      cmd => entry.proc[];
      file =>
	SELECT entry.sysType FROM
	  alto => MMOps.BootViaNet[entry.bfn, entry.host.host];
	  pilot => EtherBoot[@entry];
	  ENDCASE;
      ENDCASE;
    ENDLOOP;
  END;


ReadCmd: PROC [
  prompt: STRING, type: DirEntryType ← cmd+file,
  acceptDefault: BOOLEAN ← FALSE, default: STRING ← ""L]
  RETURNS [cmdPtr: DirEntryPtr] =
  BEGIN OPEN IODefs, NXDefs, StringDefs;
  s: STRING ← [45];
  startOver: BOOLEAN ← default.length>0;
  s.length ← 0; AppendString[s, default];
  DO
    SELECT ReadName[prompt, s, startOver] FROM
      CR, ESC, SP =>
	BEGIN
	IF s.length=0 THEN IF acceptDefault THEN RETURN[NIL] ELSE
	  {FlashDisplay[]; LOOP};
	cmdPtr ← Lookup[s, type];
	SELECT cmdPtr FROM
	  NIL =>
	    {FlashDisplay[]; WriteString[" ?"L]; startOver ← TRUE;
	    Probe[]; LOOP};
	  illPtr => FlashDisplay[];
	  ENDCASE => RETURN;
	END;
      ENDCASE;
    startOver ← FALSE;
    ENDLOOP;
  END;

ReadDecimal: PROC [
  prompt: STRING, acceptDefault: BOOLEAN ← FALSE, default: CARDINAL ← 0]
  RETURNS [CARDINAL] =
  BEGIN OPEN IODefs, NXDefs, StringDefs;
  s: STRING ← [45];
  startOver: BOOLEAN ← acceptDefault;
  s.length ← 0;
  IF acceptDefault THEN AppendDecimal[s, default];
  DO ENABLE Question =>
    BEGIN
    WriteChar['?]; WriteChar[CR];
    WriteLine["   Decimal number"L];
    WriteString[prompt]; WriteString[s]; LOOP
    END;
    SELECT ReadName[prompt, s, startOver] FROM
      CR, ESC, SP => RETURN[StringToDecimal[s ! InvalidNumber =>
      {WriteString[" ?"L]; CONTINUE}]];
      '? =>
	BEGIN
	WriteChar['?]; WriteChar[CR];
	WriteLine["   Decimal number"L];
	WriteString[prompt]; WriteString[s]; LOOP
	END;
      ENDCASE;
    startOver ← FALSE;
    ENDLOOP;
  END;

ReadName: PROC [prompt, s: STRING, new: BOOLEAN]
  RETURNS [c: CHARACTER] =
  BEGIN OPEN IODefs, NXDefs, StringDefs;
  DO
    c ← ReadChar[]; NoteUserAction[];
    ResetTo[prompt.length+s.length];
    SELECT c FROM
      BS, ControlA => Erase[1, s];
      SP, CR, ESC => RETURN;
      DEL => SIGNAL Rubout;
      ControlW => Erase[s.length, s];
      '? =>
	BEGIN
	WriteChar['?]; WriteChar[CR];
	SIGNAL Question[IF new THEN ""L ELSE s];
	WriteString[prompt]; WriteString[s]; LOOP
	END;
      >SP =>
	BEGIN
	IF new THEN {ResetTo[prompt.length]; s.length ← 0};
	IF s.length=s.maxlength THEN {FlashDisplay[]; LOOP};
	WriteChar[c]; AppendChar[s, c];
	END;
      ENDCASE => FlashDisplay[];
    new ← FALSE;
    ENDLOOP;
  END;

FlashDisplay: PROC =
  BEGIN
  dcb: AltoDisplay.DCBHandle;
  FOR dcb ← AltoDisplay.DCBchainHead↑, dcb.next UNTIL dcb=NIL DO
    dcb.background ← black ENDLOOP;
  THROUGH [0..10000] DO ENDLOOP;
  FOR dcb ← AltoDisplay.DCBchainHead↑, dcb.next UNTIL dcb=NIL DO
    dcb.background ← white ENDLOOP;
  END;

InstallCommands: PROC =
  BEGIN OPEN SystemDefs;
  entry: CmdDirEntryPtr;
  FOR i: CARDINAL IN [0..nCommands) DO
    IF alto AND ~cmdTable[i].validOnAlto THEN LOOP;
    entry ← AllocateHeapNode[SIZE[CmdDirEntry]];
    entry.name ← cmdTable[i].name;
    entry.vp ← cmd[cmdTable[i].proc];
    InsertDirEntry[entry];
    ENDLOOP;
  END;

Quit: PROC = {MMOps.BootViaNet[0]};

Probe: PROC = {ProcessDefs.Detach[FORK ProbeProcess[]]};

SetTimeCmd: PROC = {ProcessDefs.Detach[FORK NXDefs.SetTime[]]};

Partition: PROC =
  BEGIN OPEN IODefs, BcplOps;
  setPartition: RECORD [a, b: WORD] ← [61037B, 1400B];
  partition, status: CARDINAL;
  status ← BcplJSR[JSR, @setPartition, 0];
  WriteString[" number "L]; WriteDecimal[status];
  partition ← ReadDecimal[">Partition number "L, TRUE, status];
  [] ← BcplJSR[JSR, @setPartition, partition];
  END;

BootDP0: PROC =
  BEGIN OPEN AltoDisplay, DiskDefs, InlineDefs, IODefs, SystemDefs;
  StartIO: PROC [CARDINAL] = MACHINE CODE BEGIN Mopcodes.zSTARTIO END;
  i: CARDINAL;
  bltAndJump: ARRAY [0..5] OF UNSPECIFIED  ← [
    111000B,	-- mov 0 2
    21000B,	-- lda 0 0, 2
    25001B,	-- lda 1 1, 2
    35002B,	-- lda 3 2, 2
    61005B,	-- blt
    1];	-- jmp 1
  bltArgs: RECORD [a, b, c: UNSPECIFIED];
  cb: CBptr ← AllocateHeapNode[SIZE[CB]];
  oldDCBChainHead: DCBHandle;
  label: POINTER TO DL ← AllocateHeapNode[SIZE[DL]];
  data: POINTER TO ARRAY [0..256) OF WORD
    ← LOOPHOLE[AllocateResidentPages[1]];
  csb: POINTER TO CSB = LOOPHOLE[521B];
  keys: POINTER TO WORD = LOOPHOLE[177034B];
  bootLabel: POINTER TO DL = LOOPHOLE[402B];
  bootStatus: POINTER ← data+1;
  CSB: TYPE = RECORD [cb: CBptr, status: DS, addr: DA];

  oldDCBChainHead ← DCBchainHead↑;
  ProcessDefs.DisableInterrupts[];
  DCBchainHead↑ ← DCBnil;
  FOR i IN [0..2000] DO ENDLOOP;
  StartIO[3]; -- clear Ethernet
  csb.cb ← NIL; csb.addr ← InvalidDA;
  FOR i IN [1..10] DO
    MiscDefs.Zero[cb, SIZE[CB]];
    cb.command ← [110B, DiskCheck, DiskRead, DiskRead, 0, 0];
    cb.headerAddress ← @cb.header;
    cb.labelAddress ← label;
    cb.dataAddress ← data;
    cb.header.diskAddress ← LOOPHOLE[BITXOR[keys↑, -1]];
    csb.cb ← cb; -- start the disk
    WHILE cb.status.done#DSdone DO ENDLOOP;  -- wait for completion
    IF BITAND[cb.status, DSmaskStatus]=DSgoodStatus THEN EXIT;
    REPEAT
     FINISHED =>
      BEGIN
      ProcessDefs.EnableInterrupts[];
      DCBchainHead↑ ← oldDCBChainHead;
      WriteChar[CR];
      WriteString["10 consecutive errors trying to read vda 0"L];
      RETURN
      END;
    ENDLOOP;
  bootLabel↑ ← label↑; -- 402B ← label
  bootStatus↑ ← cb.status; -- 2B ← disk status
  bltArgs.a ← data-1; bltArgs.b ← 400B; bltArgs.c ← -256;
  [] ← BcplOps.BcplJSR[JSR, @bltAndJump, @bltArgs]; -- bye bye
  END;

FileStat: PROC =
  BEGIN OPEN InlineDefs, IODefs, PupDefs, PupTypes, StringDefs, TimeDefs;
  ENABLE Question =>
    BEGIN ENABLE NXDefs.UserAbort => GOTO Aborted;
    NXDefs.LineReset["File name, from:"L];
    ListPossibleMatches[s, IF alto THEN file ELSE file+uCode+germ];
    WriteChar[CR]; RESUME
    EXITS Aborted => RESUME;
    END;
  s: STRING ← [25];
  bfNum: CARDINAL;
  bfDate: PackedTime;
  hostAddr: PupAddress;
  uSwitchKbd: BOOLEAN =
    IF alto THEN OsStaticDefs.OsStatics.AltoVersion.engineeringnumber<2
    ELSE ProcessorType[]=CSL OR ProcessorType[]=Dorado;
  keys: ARRAY [0..15] OF STRING ←
    [IF uSwitchKbd THEN " <blank-top>"L ELSE " <BW>"L,
    IF uSwitchKbd THEN " <blank-middle>"L ELSE " <FR4>"L,
    " ]"L, " <quote>"L, " <comma>"L, " L"L,
    " O"L, " X"L, " I"L, " 9"L, " A"L, " S"L,
    " Q"L, " W"L, " 2"L, " 3"L];
  entry: DirEntryPtr;
  WriteString[" for file "L];
  entry ← ReadCmd[">FileStat for file "L, file+uCode+germ];
  WITH entry SELECT FROM
    file => {bfNum ← bfn; hostAddr ← host; bfDate ← date};
    uCode, germ => {bfNum ← bfn; hostAddr ← host; bfDate ← date};
    ENDCASE;
  WriteChar[CR];
  WriteString["File number "L];
  WriteOctal[bfNum];
  WriteString[", from host "L];
  s.length ← 0; AppendPupAddress[s, hostAddr];
  s.length ← s.length-1; WriteString[s];
  WriteString[", created "L]; s.length ← 0;
  AppendDayTime[s, UnpackDT[bfDate]];
  WriteLine[s];
  WriteString["Keys: <BS>"L];
  FOR i: INTEGER IN [0..16) DO
    IF BITAND[BITSHIFT[bfNum, -i], 1]=1 THEN WriteString[keys[i]];
    ENDLOOP;
  END;

SetVersions: PROC =
  BEGIN OPEN IODefs;
  entry: DirEntryPtr;
  WriteLine[" for germ and microcode"L];
  BEGIN ENABLE Question =>
    BEGIN ENABLE NXDefs.UserAbort => GOTO Aborted;
    NXDefs.LineReset["Available germ files:"L];
    ListPossibleMatches[s, germ];
    WriteChar[CR]; RESUME
    EXITS Aborted => RESUME;
    END;
  WriteString["Germ: "L];
  WriteString[IF germEntry=NIL THEN pilotGerm[ProcessorType[]]
    ELSE germEntry.name];
  entry ← ReadCmd[
    "Germ: "L, germ, TRUE,
    IF germEntry=NIL THEN pilotGerm[ProcessorType[]] ELSE germEntry.name];
  WriteChar[CR];
  IF entry#NIL THEN germEntry ← entry;
  END;
  BEGIN ENABLE Question =>
    BEGIN ENABLE NXDefs.UserAbort => GOTO Aborted;
    NXDefs.LineReset["Available microcode files:"L];
    ListPossibleMatches[s, uCode]; WriteChar[CR];
    RESUME
    EXITS Aborted => RESUME;
    END;
  WriteString["Microcode: "L];
  WriteString[
    IF uCodeEntry=NIL THEN
      microcodeFiles[ProcessorType[]] ELSE uCodeEntry.name];
  entry ← ReadCmd[
    "Microcode: "L, uCode, TRUE,
    IF uCodeEntry=NIL THEN
      microcodeFiles[ProcessorType[]] ELSE uCodeEntry.name];
  WriteChar[CR];
  IF entry#NIL THEN uCodeEntry ← entry;
  END;
  END;

ReadSwitches: PROC =
  {IODefs.WriteString[": "L]; IODefs.ReadLine[switchString]};

SetSwitches: PROC =
  BEGIN OPEN IODefs;
  FOR i: CARDINAL IN [0..switchString.length) DO
    SELECT switchString[i] FROM
      IN ['A..'Z] => Set[down, @switches, (switchString[i]-'A)+10];
      IN ['a..'z] => Set[down, @switches, (switchString[i]-'a)+10];
      IN ['0..'9] => Set[down, @switches, switchString[i]-'0];
      ENDCASE;
    ENDLOOP;
  switches.g ← down;
  END;

BitIndex: TYPE = CARDINAL [0..4096);
Bit: TYPE = CARDINAL [0..1];
BitDescriptor: TYPE = ControlDefs.FieldDescriptor;

Set: PROC [upDown: TemporaryBooting.UpDown, p: POINTER, index: BitIndex] =
  INLINE {SetBitWithDesc[LOOPHOLE[upDown], p, BD[index]]};

BD: PROC [CARDINAL] RETURNS [BitDescriptor] =
  MACHINE CODE BEGIN Mopcodes.zLI4; Mopcodes.zSHIFT END;

SetBitWithDesc: PROC [b: Bit, p: POINTER, bd: BitDescriptor] =
  MACHINE CODE BEGIN Mopcodes.zWFS END;

LoadRam: PROC [p: LONG POINTER, andJump: BOOLEAN] =
  MACHINE CODE BEGIN Mopcodes.zMISC, 3 END;

ClearDevices: PROC =
  MACHINE CODE BEGIN Mopcodes.zMISC, 4 END;

MapFlags: TYPE = MACHINE DEPENDENT RECORD [
  LogSE, W, D, Ref: BOOLEAN];

vacantFlags: MapFlags = [FALSE, TRUE, TRUE, FALSE];
cleanFlags: MapFlags = [FALSE, FALSE, FALSE, FALSE];

MapEntry: TYPE = MACHINE DEPENDENT RECORD [
  flags: MapFlags,
  realPage: [0..7777B]];

RealPage: TYPE = CARDINAL [0..10000B);
VirtualPage: TYPE = CARDINAL [0..40000B);

vacant: MapEntry = [vacantFlags,0];
clean: MapEntry = [cleanFlags,0];

ASSOC: PROC [CARDINAL, MapEntry] =
  MACHINE CODE {Mopcodes.zMISC, 0};

SETF: PROC [CARDINAL, MapEntry] RETURNS [MapEntry] =
  MACHINE CODE {Mopcodes.zMISC, 1};

LPFromPage: PROC [p: CARDINAL] RETURNS [LONG POINTER] =
  INLINE {RETURN[LOOPHOLE[LONG[p]*pageSize]]};

LongRead: PROC [LONG POINTER] RETURNS [CARDINAL] =
  MACHINE CODE {Mopcodes.zRBL, 0};

CountRealPages: PROC RETURNS [pageCount: CARDINAL] =
  BEGIN
  FOR pageCount ← 0, pageCount+1 DO
    m: MapEntry ← SETF[pageCount, clean];
    [] ← SETF[pageCount, m];
    IF m = vacant THEN EXIT;
    ENDLOOP;
  RETURN
  END;

MovePages: PROC [from, to, count: CARDINAL] =
  BEGIN
  FOR i: CARDINAL IN [0..count) DO
    m: MapEntry ← SETF[from+i, clean];
    ASSOC[from+i, vacant];
    ASSOC[to+i, m];
    ENDLOOP;
  RETURN
  END;

NullDA: DA = LOOPHOLE[0];

BootInfo: TYPE = ARRAY [0..8) OF UNSPECIFIED;
nullBootInfo: BootInfo ← ALL[0];

pageSize: CARDINAL = 256;
firstHyperPage: CARDINAL = 256;
germMDSIndex: CARDINAL = 76B;
germMDSPage: CARDINAL = germMDSIndex*pageSize;
germMDS: LONG POINTER = LOOPHOLE[LONG[germMDSIndex]*200000B];
firstGermPage: CARDINAL = germMDSPage + 2;
germStart: LONG POINTER = LPFromPage[firstGermPage];
germData: LONG POINTER = germMDS + LOOPHOLE[Boot.pRequest];
germSwitches: LONG POINTER =
  germMDS + LOOPHOLE[@SDDefs.SD[BootSwap.sPilotSwitches]];

pilotGerm: ARRAY Hardware OF STRING ←
  ["CedarD0.eg"L, "CedarD0.eg"L, "CedarD0.eg"L, "CedarDorado.eg"L];
microcodeFiles: ARRAY Hardware OF STRING ←
  ["CedarD0.eb"L, "CedarD0.eb"L, "CedarD0.eb"L, "CedarDorado.eb"L];
othelloFiles: ARRAY Hardware OF STRING ←
  ["CedarOthelloD0.pb"L, "CedarOthelloD0.pb"L, "CedarOthelloD0.pb"L, "CedarOthelloDorado.pb"L];
cedarFiles: ARRAY Hardware OF STRING ←
  ["BasicCedarD0.pb"L, "BasicCedarD0.pb"L, "BasicCedarD0.pb"L, "BasicCedarDorado.pb"L];
cedarGerms: ARRAY Hardware OF STRING ←
  ["CedarD0.eg"L, "CedarD0.eg"L, "CedarD0.eg"L, "CedarDorado.eg"L];

  Hardware: TYPE = {SDD, CSL, Tor, Dorado};
  uCodeEntry: DirEntryPtr ← NIL;
  germEntry: DirEntryPtr ← NIL;

  ProcessorType: PROC RETURNS [Hardware] =
    BEGIN
    Register: TYPE = MACHINE DEPENDENT RECORD [
      left: [0..377B], bitClock: [0..37B], fill: [0..7B]];
    Input: PROC [CARDINAL] RETURNS [Register] =
      MACHINE CODE BEGIN Mopcodes.zMISC, 5; END;
    reg: Register;
    IF OsStaticDefs.OsStatics.AltoVersion.engineeringnumber=5 THEN
      RETURN[Dorado];
    FOR i: CARDINAL IN [4..16] DO
      reg ← Input[i*16];
      IF reg.left=2 THEN EXIT
      ELSE IF reg.left=12B THEN RETURN[Tor];
      REPEAT FINISHED => ERROR;
    ENDLOOP;
    RETURN[IF reg.bitClock=5 THEN CSL ELSE SDD]; -- 3 for SDD Dolphin
    END;

GetFileName: PROC [entry: DirEntryPtr, type: DirEntryType, str: STRING]
  RETURNS [STRING] =
  BEGIN OPEN StringDefs;
  ssP: SubStringDescriptor ← ["P"L, 0, 1]; -- what a crock...
  ssA: SubStringDescriptor ← ["AlphaPilot"L, 0, 10];
  ss: SubStringDescriptor;
  root: STRING ← SELECT type FROM
    germ => pilotGerm[ProcessorType[]],
    uCode => microcodeFiles[ProcessorType[]],
    ENDCASE => ERROR;

  str.length ← 0;
  IF entry=NIL THEN {AppendString[str, root]; RETURN[str]};
  ss ← [entry.name, 0, ssP.length];
  IF EquivalentSubStrings[@ss, @ssP] THEN
    {AppendSubString[str, @ssP]; AppendString[str, root]; RETURN[str]};
  ss.length ← ssA.length; IF EquivalentSubStrings[@ss, @ssA] THEN
    {AppendSubString[str, @ssA]; AppendString[str, root]; RETURN[str]};
  AppendString[str, root];
  RETURN[str];
  END;

EtherBoot: PROC [entry: FileDirEntryPtr] =
  BEGIN
  req.action ← Boot.inLoad;
  req.location ←
    [deviceType: DeviceTypes.ethernet, deviceOrdinal: 0,
      vp: ethernet[entry.bfn, entry.host.net, entry.host.host]];
  SoftBoot[entry];
  END;

PhysVolBoot: PROC =
  BEGIN -- boot Shugart disk
  req.action ← Boot.bootPhysicalVolume;
  req.location ←
    [deviceType: DeviceTypes.sa4000, deviceOrdinal: 0,
     vp: disk[Boot.nullDiskFileID]];
  SoftBoot[];
  END;

FlipCursor: PROC =
  BEGIN
  FOR i: CARDINAL IN[0..16) DO
    cursor[i] ← InlineDefs.BITXOR[cursor[i], -1] ENDLOOP;
  END;

EtherGetModule: ENTRY PROC
  [entry: DirEntryPtr, pProc: PROCEDURE RETURNS [LONG POINTER]]
  RETURNS [pages: CARDINAL] =
  BEGIN OPEN EFTPDefs, IODefs, ProcessDefs, PupDefs, PupTypes;
  n, zero, lastn: CARDINAL ← 0;
  i, tP, firstP, lastP: LONG POINTER;
  bufP: POINTER ← NIL; -- must be initialized since NIL means not allocated
  me: PupAddress;
  oneSecond: CONDITION;
  host: PupAddress ← WITH entry SELECT FROM
    file => host, uCode, germ => host,
    ENDCASE => ERROR;
  bfn: CARDINAL ← WITH entry SELECT FROM
    file => bfn, uCode, germ => bfn,
    ENDCASE => ERROR;
  buffer: PupBuffer;
  squares: ARRAY [0..16) OF WORD ←
    [377B, 377B, 377B, 377B,
    377B, 377B, 377B, 377B,
    177400B, 177400B, 177400B, 177400B,
    177400B, 177400B, 177400B, 177400B];

    BEGIN ENABLE EFTPTimeOut =>
      {WriteString["receiver timed out"L]; GOTO ErrorExit};
    IF entry=NIL THEN RETURN[0];
    pages ← 0;
    SetTimeout[@oneSecond, MsecToTicks[1000]];
    cursor↑ ← squares;
    me ← UniqueLocalPupAddress[@host];
    bufP ← SystemDefs.AllocateResidentPages[1];
    EFTPSetRecvTimeout[100]; -- short timeout to get things rolling
    -- first set up receiver
    [] ← EFTPOpenForReceiving[me
      ! EFTPTimeOut => -- at least once, since we haven't asked for file
	BEGIN
	EFTPSetRecvTimeout[5000];
	IF (n←n+1)>10 THEN
	  {WriteString["no connection established"L]; GOTO ErrorExit};
	buffer ← GetFreePupBuffer[];
	buffer.source ← me;
	buffer.dest ← host;
	buffer.pupType ← bootFileSend;
	buffer.pupID.b ← bfn;
	SetPupContentsWords[buffer, 0];
	[] ← PupRouterSendThis[buffer];
	RESUME;
	END];
    [] ← EFTPGetBlock[bufP, 512];
      -- first block is alto bootloader, discard it
  
    FlipCursor[];
    IF entry.type=uCode THEN -- keep LoadRamAndJump happy
      BEGIN
      firstP ← tP ← pProc[] + pageSize - 1; pages ← pages+1;
      InlineDefs.LongCOPY[from: @zero, to: tP, nwords: 1];
      END;
  
    lastn ← 512; -- so check below succeeds first time
    DO -- main receive loop
      n ← EFTPGetBlock[bufP, 512 ! EFTPEndReceiving => EXIT];
      IF lastn<512 THEN 
	{WriteString["short EFTP block in middle of file"L]; GOTO ErrorExit};
      lastn ← n;
      tP ← pProc[]; pages ← pages+1;
      InlineDefs.LongCOPY[from: bufP, to: tP, nwords: pageSize];
      FlipCursor[];
      ENDLOOP;
    EFTPFinishReceiving[];
    SystemDefs.FreePages[bufP]; bufP ← NIL;
    IF entry.type=uCode THEN
      {lastP ← tP+n/2; n ← 0;
      FOR i ← firstP, i+1 UNTIL i=lastP DO n ← n+LongRead[i] ENDLOOP;
      IF n#0 THEN
	{WriteString["microcode checksum error"L]; GOTO ErrorExit}};
    WAIT oneSecond;
    EXITS ErrorExit =>
      {IF bufP#NIL THEN SystemDefs.FreePages[bufP];
      EFTPAbortReceiving[""L]; RETURN[0]};
    END;
  END;

SoftBoot: PROC [dirEntry: DirEntryPtr ← NIL] =
  BEGIN OPEN IODefs;
  germPages, rpCount, uCodePages: CARDINAL;
  nextGermTo, nextGermFrom: CARDINAL;
  nextHyperPage: CARDINAL ← firstHyperPage;
  entry: DirEntryPtr;
  fName: STRING ← [40];
  GetGermPage: PROCEDURE RETURNS [p: LONG POINTER] =
    BEGIN
    ProcessDefs.DisableInterrupts[];
    MovePages[from: nextGermFrom, to: nextGermTo, count: 1];
    ProcessDefs.EnableInterrupts[];
    p ← LPFromPage[nextGermTo];
    nextGermTo ← nextGermTo+1;  nextGermFrom ← nextGermFrom-1;
    END;
  GetHyperPage: PROCEDURE RETURNS [p: LONG POINTER] =
    BEGIN
    p ← LPFromPage[nextHyperPage];
    nextHyperPage ← nextHyperPage+1;
    END;

  BEGIN
  killProber ← TRUE;
  SetSwitches[];
  WriteString["...loading germ..."L];
  entry ← IF germEntry#NIL THEN germEntry
    ELSE Lookup[GetFileName[dirEntry, germ, fName], germ, FALSE];
  IF entry=NIL THEN
    {WriteChar[CR]; WriteString["Can't find file "L]; WriteLine[fName]; RETURN};
  rpCount ← CountRealPages[];
  nextGermFrom ← rpCount-1; -- start at end of real memory
  nextGermTo ← firstGermPage;
  germPages ← EtherGetModule[entry, GetGermPage];
  IF germPages=0 THEN
    {WriteChar[CR];
    WriteLine["EFTP problem - couldn't get germ"L]; GOTO Abort};
  WriteString["loading microcode..."L];
  entry ← IF uCodeEntry#NIL THEN uCodeEntry
    ELSE Lookup[GetFileName[dirEntry, uCode, fName], uCode, FALSE];
  IF entry=NIL THEN
    {WriteChar[CR];
    WriteString["Can't find file "L]; WriteLine[fName]; GOTO Abort};
  uCodePages ← EtherGetModule[entry, GetHyperPage];
  IF uCodePages=0 THEN
    {WriteChar[CR];
    WriteLine["EFTP problem - couldn't get microcode"L]; GOTO Abort};
  ProcessDefs.DisableInterrupts[];
  AltoDisplay.DCBchainHead↑ ← AltoDisplay.DCBnil;
  THROUGH [0..2000] DO ENDLOOP; -- let things quiet down
  InlineDefs.LongCOPY[from: @req, to: germData, nwords: SIZE[Boot.Request]];
  InlineDefs.LongCOPY[from: @switches, to: germSwitches,
    nwords: SIZE[TemporaryBooting.Switches]];
  cursor↑ ← doneCursor; -- since display is turned off
  LoadRam[LPFromPage[firstHyperPage]+pageSize-1, TRUE];
  EXITS Abort => -- put real pages back
    MovePages[from: firstGermPage, to: rpCount-germPages,
      count: nextGermTo-firstGermPage];
  END;
  END;

Othello: PROC = {BootGeneric[othelloFiles]};

Cedar: PROC =
  BEGIN
  IF germEntry=NIL THEN germEntry ← Lookup[cedarGerms[ProcessorType[]], germ, FALSE];
  BootGeneric[cedarFiles];
  END;

BootGeneric: PROC [fileNameTable: ARRAY Hardware OF STRING] =
  BEGIN OPEN IODefs, NXDefs;
  cmdPtr: DirEntryPtr ← Lookup[fileNameTable[ProcessorType[]], file, FALSE];
  SELECT cmdPtr FROM
    NIL =>
      {FlashDisplay[]; WriteString[" ?"L]; Probe[]; RETURN};
    illPtr => {FlashDisplay[]; RETURN};
    ENDCASE;
  WITH entry: cmdPtr SELECT FROM
    cmd => entry.proc[];
    file =>
      SELECT entry.sysType FROM
        alto => MMOps.BootViaNet[entry.bfn, entry.host.host];
        pilot => EtherBoot[@entry];
        ENDCASE;
    ENDCASE;
  END;

-- Initialization

BEGIN
vers: RECORD [WORD, WORD] ← [61014B, 1400B];
version: OsStaticDefs.AltoVersionNumber;

debug ← KeyDefs.Keys.Spare1=down OR debug;
IF ~debug THEN MMOps.MakeBoot[];
SDDefs.SD[SDDefs.sUncaughtSignal] ← Catcher;
version ← BcplOps.BcplJSR[JSR, @vers, 0];
OsStaticDefs.OsStatics.AltoVersion ← version;
alto ← version.engineeringnumber<4;
START NXDefs.NXDisplay;
InstallCommands[];
PupDefs.PupPackageMake[];
ProcessDefs.Detach[FORK NXDefs.Host];
ProcessDefs.Detach[FORK NXDefs.Timer];
ProcessDefs.Detach[FORK NXDefs.SetTime];
ProcessDefs.Detach[FORK ProbeProcess];
ProcessDefs.Detach[FORK NXDefs.WatchDog];
Run[];
END;

END...