// NetExec.bcpl -- A one-day hack that worked
// Copyright Xerox Corporation 1979, 1980, 1982, 1983
// Last modified January 11, 1983  8:22 PM by Boggs

get "AltoDefs.d"
get "CmdScan.decl"

external
[
// outgoing procedures
LoadKT

// incoming procedures
PrintName; LoadMicrocode; Where
GetTime; GetDir; GetName; NetBoot

MyFrame; Usc; CallSwat; SysErr; MoveBlock; Zero
Enqueue; Dequeue; DoubleDifference; Junta
InitializeZone; Allocate; Free

InitializeContext; CallContextList; Block; Dismiss

CreateDisplayStream; ShowDisplayStream; EraseBits
GetBitPos; SetBitPos; CharWidth; GetLmarg; SetFont

SetTimer; TimerHasExpired
StartIO; FalsePredicate; ReadCalendar

UNPACKDT; WEEKDAY; MONTHNAME; WRITEUDT
Puts; Resets; Closes; Putbacks; Endofs
PutTemplate; Wss

CreateKeywordTable; InsertKeyword; LookupKeyword; DeleteKeyword
InitCmd; GetKeyword; GetNumber; CmdErrorCode; EnableCatch; EndCatch
BeginDefaultPhrase; EndDefaultPhrase; Confirm

InitPupLevel1; EtherBoot

// incoming statics
sysZone; sysFont; dsp; keys
timeRequest; dirRequest

// outgoing statics
ftpCtxQ; cmdDsp; cmdKT; ebKT; kbdCS; buf; eng; OpenFile
]

static
[
ftpCtxQ; cmdDsp; cmdKT; ebKT; kbdCS; buf
watchDog; numKTEs = 0; eng; OpenFile
cursorOn; cursorTimer
]

structure KTE:
[
date word 2
host word	//if host is zero then
bfn word	//bfn is really a local procedure (e.g. BfnToKeys)
]
manifest
[
lenKTE = size KTE/16
maxKTEs = 100

bufLen = 256 + 16384 + 4096 + 100  // header + IM + IFUM + slop
stkLim = 335b
]

//---------------------------------------------------------------------------
let NetExec() be Junta(nil, AfterJunta)
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
and AfterJunta() be
//---------------------------------------------------------------------------
[
let d = vec (lDCB+3); dsp = (d+1)&-2
let dcb = dsp+2; dsp!0, dsp!1 = dcb, dcb
Zero(dcb, lDCB); dcb>>DCB.height = 42
ShowDisplayStream(dsp, DSalone)
eng = (table [ 61014b; 1401b ])()<<VERS.eng

buf = @stkLim; @stkLim = buf + bufLen
let base = @stkLim; @stkLim = MyFrame() - 200
sysZone = InitializeZone(base, @stkLim - base, SysErr, SysErr)

let v = vec 1; ftpCtxQ = v; ftpCtxQ!0 = 0
InitPupLevel1(sysZone, ftpCtxQ, 10)
Enqueue(ftpCtxQ, InitializeContext(Allocate(sysZone, 500), 500, Title))
Enqueue(ftpCtxQ, InitializeContext(Allocate(sysZone, 500), 500, Command))
Enqueue(ftpCtxQ, InitializeContext(Allocate(sysZone, 300), 300, GetTime))
Enqueue(ftpCtxQ, InitializeContext(Allocate(sysZone, 300), 300, GetDir))
Enqueue(ftpCtxQ, InitializeContext(Allocate(sysZone, 300), 300, GetName))
Enqueue(ftpCtxQ, InitializeContext(Allocate(sysZone, 100), 100, WatchDog))

cmdKT = CreateKeywordTable(maxKTEs, lenKTE)
ebKT = CreateKeywordTable(maxKTEs, lenKTE)

CallContextList(ftpCtxQ!0) repeat
]

//---------------------------------------------------------------------------
and Title() be
//---------------------------------------------------------------------------
[
Block()
let lineWords = lDCB+38*2*((sysFont!-2+1) rshift 1) + 1
let top = vec 1
top!0 = CreateDisplayStream(1, Allocate(sysZone, lineWords), lineWords)
top!1 = CreateDisplayStream(1, Allocate(sysZone, lineWords), lineWords)
ShowDisplayStream(top!0, DSbelow, dsp)
let bot = vec 1
bot!0 = CreateDisplayStream(1, Allocate(sysZone, lineWords), lineWords)
bot!1 = CreateDisplayStream(1, Allocate(sysZone, lineWords), lineWords)
ShowDisplayStream(bot!0, DSbelow, top!0)
let boldFont = vec 1; boldFont = boldFont +2
boldFont!-2 = -1; boldFont!-1 = sysFont
let machineType = selecton eng into
   [
   case 0: case 1: "Alto I"
   case 2: "Alto II"
   case 3: "Alto II XM"
   case 4: "Dolphin"
   case 5: "Dorado"
   ]

let lastTime, now = vec 1, vec 1
   [
   ReadCalendar(now)
   if DoubleDifference(now, lastTime) ne 0 then
      [
      let line = top!1
      Resets(line)
      PutTemplate(line, "-- $PXEROX$P BCPL Net Executive/12",
       SetFont, boldFont, SetFont, sysFont)
      FillWithDash(line, 330)
      WriteDate(line)
      FillWithDash(line, 605)
      ExchangeLines(top)

      line = bot!1
      Resets(line)
      FillWithDash(line, 125)
      Wss(line, machineType)
      FillWithDash(line, 250)
      PrintName(line)
      if eng gr 3 then
         [
         FillWithDash(line, 450)
         PutTemplate(line, "Partition $D", (table [ 61037b; 1401b ])(0))
         ]
      FillWithDash(line, 605)
      ExchangeLines(bot)

      MoveBlock(lastTime, now, 2)
      ]
   Dismiss(20)
   ] repeat
]

//---------------------------------------------------------------------------
and ExchangeLines(lineVec) be
//---------------------------------------------------------------------------
[
let dcb = @displayListHead
while dcb>>DCB.next ne lineVec!0>>DS.cdcb do dcb = dcb>>DCB.next
lineVec!1>>DS.cdcb>>DCB.next = lineVec!0>>DS.cdcb>>DCB.next
dcb>>DCB.next = lineVec!1>>DS.cdcb
let temp = lineVec!1; lineVec!1 = lineVec!0; lineVec!0 = temp
]

//---------------------------------------------------------------------------
and FillWithDash(stream, end) be
//---------------------------------------------------------------------------
[
if (end-GetBitPos(stream)) gr CharWidth(stream, $*S) &
 GetBitPos(stream) ne GetLmarg(stream) then Puts(stream, $*S)
for i = 1 to (end-CharWidth(stream, $*S)-GetBitPos(stream))/
 CharWidth(stream, $-) do Puts(stream, $-)
SetBitPos(stream, end)
]

//---------------------------------------------------------------------------
and WriteDate(stream) = valof
//---------------------------------------------------------------------------
// "Weekday Month Day - hour:minute:second am/pm"
[
structure UV:
   [
   year word
   month word
   day word
   hour word
   minute word
   second word
   dst word
   ]
manifest lenUV = size UV/16
let dv = vec 1; ReadCalendar(dv)
let uv = vec lenUV; UNPACKDT(dv, uv)
if uv>>UV.year le 1982 % uv>>UV.year gr 2000 then
   [
   Wss(stream, "Date and time unknown")
   resultis false
   ]
let day = selecton WEEKDAY(dv) into
   [
   case 0: "Monday"
   case 1: "Tuesday"
   case 2: "Wednesday"
   case 3: "Thursday"
   case 4: "Friday"
   case 5: "Saturday"
   case 6: "Sunday"
   ]
let month = selecton uv>>UV.month into
   [
   case 0: "Jan"
   case 1: "Feb"
   case 2: "Mar"
   case 3: "Apr"
   case 4: "May"
   case 5: "Jun"
   case 6: "Jul"
   case 7: "Aug"
   case 8: "Sep"
   case 9: "Oct"
   case 10: "Nov"
   case 11: "Dec"
   ]
PutTemplate(stream, "$S $S $UD - ", day, month, uv>>UV.day)
let am = uv>>UV.hour le 11
if uv>>UV.hour ge 12 then uv>>UV.hour = uv>>UV.hour - 12
if uv>>UV.hour ls 1 then uv>>UV.hour = 12
PutTemplate(stream, "$UD:$U2F0D:$U2F0D $S", uv>>UV.hour,
 uv>>UV.minute, uv>>UV.second, (am? "am", "pm"))
resultis true
]

//---------------------------------------------------------------------------
and Command() be  //a context
//---------------------------------------------------------------------------
[
manifest numLines = 20
cmdDsp = CreateDisplayStream(numLines, buf, bufLen)
for i = 1 to numLines-1 do Puts(cmdDsp, $*N)
ShowDisplayStream(cmdDsp, DSbelow, dsp)
SetTimer(lv cursorTimer, 0)
cmdDsp>>ST.putback = cmdDsp>>ST.puts; cmdDsp>>ST.puts = PutsWithCursor
keys>>ST.par1 = keys>>ST.gets; keys>>ST.gets = GetsWithCursor
keys>>ST.par2 = keys>>ST.endof; keys>>ST.endof = EndofWithCursor

let dummyDate = vec 1; Zero(dummyDate, 2)
LoadKT(cmdKT, "BootDP0", 0, dummyDate, DiskBoot)
LoadKT(cmdKT, "EtherBoot", 0, dummyDate, NetBoot)
LoadKT(cmdKT, "FileStat", 0, dummyDate, FileStat)
if eng gr 3 then
   [
   LoadKT(cmdKT, "LoadMicrocode", 0, dummyDate, LoadMicrocode)
   LoadKT(cmdKT, "Partition", 0, dummyDate, Partition)
   LoadKT(cmdKT, "PowerOff", 0, dummyDate, PowerOff)
   ]
LoadKT(cmdKT, "Probe", 0, dummyDate, Probe)
LoadKT(cmdKT, "Quit", 0, dummyDate, Quit)
LoadKT(cmdKT, "SetTime", 0, dummyDate, SetTime)
LoadKT(cmdKT, "Where", 0, dummyDate, Where)

   [
   kbdCS = InitCmd(256, 5, 0, 0, 0, keys, cmdDsp) repeatuntil kbdCS ne 0
   Wss(kbdCS,"*N>")
   if EnableCatch(kbdCS) then
      [
      if CmdErrorCode(kbdCS) eq ecKeyNotFound then Probe()
      EndCatch(kbdCS)
      ]
   SetTimer(lv watchDog, 30000)  //5 min
   let kte = GetKeyword(kbdCS, cmdKT)
   test kte>>KTE.host ne 0
      ifso EtherBoot(kte>>KTE.bfn, false, kte>>KTE.host)
      ifnot (kte>>KTE.bfn)()
   Closes(kbdCS)
   ] repeat
]

//---------------------------------------------------------------------------
and LoadKT(kt, name, host, date, bfn) be
//---------------------------------------------------------------------------
[
let kte = LookupKeyword(kt, name)
if kte ne 0 & DoubleUsc(date, lv kte>>KTE.date) gr 0 then
   [ DeleteKeyword(kt, name); kte = 0 ]
if kte eq 0 & numKTEs ne maxKTEs then
   [
   kte = InsertKeyword(kt, name)
   kte>>KTE.host = host
   kte>>KTE.bfn = bfn
   MoveBlock(lv kte>>KTE.date, date, 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)

//---------------------------------------------------------------------------
and Quit() be EtherBoot(0)
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
and SetTime() be timeRequest = true
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
and Probe() be dirRequest = true
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
and Partition() be
//---------------------------------------------------------------------------
[
Wss(kbdCS, " number ")
BeginDefaultPhrase(kbdCS)
PutTemplate(kbdCS, "$D", (table [ 61037b; 1401b ])(0))
EndDefaultPhrase(kbdCS)
(table [ 61037b; 1401b ])(GetNumber(kbdCS))
]

//---------------------------------------------------------------------------
and PowerOff() be if Confirm(kbdCS) then (table [ 61034b; 1401b ])()
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
and WatchDog() be  //a context
//---------------------------------------------------------------------------
[
SetTimer(lv watchDog, 30000)  //5 minutes
Block() repeatuntil TimerHasExpired(lv watchDog)
Quit()
]

//---------------------------------------------------------------------------
and DiskBoot() be
//---------------------------------------------------------------------------
// D0s don't boot when SIO 100000 is executed, so this Bcpl procedure
//  does what the microcode should do.
[
structure KCB:
   [
   link word
   status word
   command word
   headerAddress word
   labelAddress word
   dataAddress word
   normalWakeups word
   errorWakeups word
   header word
   diskAddress word
   ]
manifest lenKCB = size KCB/16

@displayListHead = 0  //turn off display
(table [ 61000b; 1401b ])()  //disable interrupts
StartIO(3)  //reset Ethernet

let kcb, data, label = vec lenKCB, vec 256, vec 8
for tries = 1 to 10 do
   [
   Zero(kcb, lenKCB)
   kcb>>KCB.command = 44100b  //check header, read label, read data
   kcb>>KCB.headerAddress = lv kcb>>KCB.header
   kcb>>KCB.labelAddress = label
   kcb>>KCB.dataAddress = data
   kcb>>KCB.diskAddress = kbdAd!0 xor -1
   @diskCommand = kcb  //spin the disk
   while (kcb>>KCB.status & 7400b) eq 0 loop  //wait for it to stop
   if (kcb>>KCB.status & 7667b) eq 7400b break  //good status
   if tries eq 10 then CallSwat("10 consecutive errors reading vda 0")
   ]

MoveBlock(402b, label, 8)  //402-411 ← label
MoveBlock(1, data, 256)  // 1-400 ← data
@2 = kcb>>KCB.status  //2 ← status
goto 1  //jump to bootloader start address
]

//---------------------------------------------------------------------------
and FileStat() be
//---------------------------------------------------------------------------
[
Wss(kbdCS, " for boot file ")
let kte = GetKeyword(kbdCS, cmdKT)
if kte>>KTE.host eq 0 then
   [
   Wss(kbdCS, "*NNetExec command - not a boot file")
   return
   ]
PutTemplate(kbdCS, "*NBoot file number $UOb, from host $UO#, created ",
 kte>>KTE.bfn, kte>>KTE.host)
let utv = vec 7; UNPACKDT(lv kte>>KTE.date, utv); WRITEUDT(kbdCS, utv)
let altoI = eng le 1
Wss(kbdCS, "*Nkeys <BS>")
for i = 0 to 15 do if ((kte>>KTE.bfn) & (1b15 rshift i)) ne 0 then
   Wss(kbdCS, selecton i into
      [
      case 0: " 3"
      case 1: " 2"
      case 2: " W"
      case 3: " Q"
      case 4: " S"
      case 5: " A"
      case 6: " 9"
      case 7: " I"
      case 8: " X"
      case 9: " O"
      case 10: " L"
      case 11: " <comma>"
      case 12: " <quote>"
      case 13: " ]"
      case 14: altoI? " <blank-middle>", " <FR4>"
      case 15: altoI? " <blank-top>", " <BW>"
      ])
]

//----------------------------------------------------------------------------
and GetsWithCursor(st) = valof
//----------------------------------------------------------------------------
[
Block() repeatwhile Endofs(st)
EraseCursor()
resultis st>>ST.par1(st)
]

//----------------------------------------------------------------------------
and PutsWithCursor(st, char) be
//----------------------------------------------------------------------------
[
EraseCursor()
Putbacks(st, char)
]

//----------------------------------------------------------------------------
and EndofWithCursor(st) = valof
//----------------------------------------------------------------------------
[
if TimerHasExpired(lv cursorTimer) then
   [
   SetTimer(lv cursorTimer, 50)
   test cursorOn
      ifso EraseCursor()
      ifnot [ Puts(cmdDsp, $|); cursorOn = true ]
   ]
resultis st>>ST.par2(st)
]

//----------------------------------------------------------------------------
and EraseCursor() be if cursorOn then
//----------------------------------------------------------------------------
[
EraseBits(cmdDsp, -CharWidth(cmdDsp,$|))
cursorOn = false
]