// LoadMB.bcpl -- loads MB file on Dorado

// Last modified June 25, 1982  6:18 PM by Taft

get "Streams.d"
get "AltoFileSys.d"

external
[
// incoming procedures
SetupReadParam; ReadParam; EvalParam
OpenFile; Closes; ReadBlock; WriteBlock; FilePos; SetFilePos; GetCompleteFa
Ws; Gets; Puts; Resets; ReadLeaderPage; TruncateDiskStream
PutTemplate; Enqueue
Zero; MoveBlock; Usc; CallSwat; ReadCalendar; Allocate

// incoming statics
keys; dsp; sysZone
]


structure MBName ↑0,0 byte  // terminates on null byte
structure String [ length byte; char ↑1,1 byte ]

//----------------------------------------------------------------------------
structure MB:		// block format in MB file
//----------------------------------------------------------------------------
[
word0 word =
   [
   blank byte		// used in Midas MB files, but not in normal ones
   type byte		// block type
   ]
data:
   [
   sourceLine word	// source line number
   value ↑0,0 word	// left-adjusted data
   ] =
memory:
   [
   memoryNum word	// memory number
   location word	// first location in memory
   ] =
fixup:
   [
   memoryNum word	// memory number
   location word	// location in memory
   firstBit byte	// field to be stored into
   lastBit byte
   value word		// value to be stored
   ] =
memoryName:
   [
   memoryNum word	// memory number
   width word		// memory width
   name @MBName		// memory name
   ] =
address:
   [
   memoryNum word	// memory number
   value word		// address value
   name @MBName		// address symbol name
   ] =
undefined:
   [
   memoryNum word	// memory number
   location word	// location in memory
   firstBit byte	// field to be stored into
   lastBit byte
   name @MBName		// external symbol name
   ]
]

manifest
[
// block types
mbEnd = 0		// end of MB file
mbData = 1		// store memory data and increment location
mbMemory = 2		// set memory number and location
mbFixup = 3		// forward reference fixup
mbMemoryName = 4	// correlate memory name and number
mbAddress = 5		// define address symbol
mbUndefined = 6		// external reference

// block lengths
lenMBData = offset MB.data.value/16  // + size of value
lenMBMemory = offset MB.memory.location/16 +1
lenMBFixup = offset MB.fixup.value/16 +1
lenMBMemoryName = offset MB.memoryName.name/16  // + size of name
lenMBAddress = offset MB.address.name/16  // + size of name
lenMBUndefined = offset MB.undefined.name/16  // + size of name

maxMemoryNum = 50
maxLenName = 127
]

//----------------------------------------------------------------------------
structure IM:		// IM word, in MB file only
//----------------------------------------------------------------------------
[
word0 word =
   [ RSTK bit 4; ALUF bit 4; BSEL bit 3; LC bit 3; ASEL01 bit 2 ] =
   [ RSTK0 bit 1; rest0 bit 15 ]
word1 word =
   [ ASEL2 bit 1; BLK bit 1; FF bit 8; JCN05 bit 6 ] =
   [ ASEL2BLK bit 2; rest1 bit 14 ]
word2 word =
   [ JCN67 bit 2; PE020 bit 1; PE2131 bit 1; blank bit 12 ]
word3 word =
   [ blank bit 4; absoluteAddr bit 12 ]
]

//----------------------------------------------------------------------------
structure IFUM:		// IFUM word, in MB file and in Item
//----------------------------------------------------------------------------
[
word0 word =
   [ blank bit 5; PA bit 1; IFAD bit 10 = [ IFAD01 bit 2; IFAD29 bit 8 ] ]
word1 word =
   [
   SGN bit 1; IPAR bit 3 = [ P0 bit; P1 bit; P2 bit ]; LEN bit 2;
   RBASEB bit 1; MEMB bit 3 = [ MEMB0 bit 1; MEMB12 bit 2 ];
   TYPE bit 2; N bit 4
   ]
]

manifest
[
// masks defining contribution of each word to each parity bit
ifumW0P0 = 1400B; ifumW0P1 = 377B; ifumW0P2 = 2000B
ifumW1P0 = 317B; ifumW1P1 = 0B; ifumW1P2 = 107460B
]

//----------------------------------------------------------------------------
structure RM:		// RM word, in MB file and in Item
//----------------------------------------------------------------------------
[
word0 word
]

//----------------------------------------------------------------------------
structure Item:		// control store item
//----------------------------------------------------------------------------
[
extraIM bit 4 =		// extra bits for IM data
   [ PE020 bit; RSTK0 bit; PE2131 bit; BLK bit ]
zero bit 1		// must be zero
blank bit 8		// unused, leave zero for now
type bit 3		// Item type
data:  // IM, IFUM, or RM
   [
   addr word		// address
   word0 word		// left half (IM, IFUM) or entire value (RM)
   word1 word		// right half (IM, IFUM)
   ] =
end:
   [
   blank word
   checksum word	// checksum over entire item array
   startAddr word	// starting address
   ]
]

manifest
[
// Item types
itemIM = 0		// IM data
itemIFUM = 1		// IFUM data
itemEnd = 2		// End block
itemRM = 3		// RM data

lenItem = size Item/16
]

//----------------------------------------------------------------------------
structure CSArray:	// control store array
//----------------------------------------------------------------------------
[
length word		// length of array (Items)
maxLength word		// max length of array (Items)
item ↑0,0 @Item
]

//----------------------------------------------------------------------------
structure MesaString: [ length word; maxLength word; char↑0,1 byte ]
//----------------------------------------------------------------------------

//----------------------------------------------------------------------------
structure EBFile:	// Ether Bootable format
//----------------------------------------------------------------------------
[
overheadPage word 256 =
   [
   etherVersionNumber word
   mustBeZero word 2
   creationDate word 2
   name @MesaString	// name of this .eb file
   ]
item ↑0,0 @Item
]

//----------------------------------------------------------------------------
structure BRFile:	// Bcpl binary format
//----------------------------------------------------------------------------
[
bcplVersionNumber word
fileLength word
blank word		// must be zero
nameTableOffset word
blank word		// must be zero
labelTableOffset word
blank word		// must be zero
codeOffset word
blank word		// must be zero
chainTableOffset word
blank word		// must be zero
zchainTableOffset word
blank word 3		// must be zero
labelTable word 3 =
   [
   labelCount word	// must be 1
   labelNameNumber word	// must be 1
   labelPC word		// must be 1
   ]
chainTable word 1	// must be zero
zchainTable word 1	// must be zero
nameTable word 3 =
   [
   nameCount word	// must be 1
   nameDescriptor word	// must be 140b
   blank word
   nameString word 0	// actually @String
   ]
code word 2 =
   [
   codeLength word
   codeFirst word 1		// should be zero
   item ↑0,0 @Item
   ]
]

//----------------------------------------------------------------------------
structure InFile:	// Input file descriptor
//----------------------------------------------------------------------------
[
next word
name word		// -> complete file name string
cfa @CFA
startAddr word		// microcode starting address for this overlay
]
manifest lenInFile = size InFile/16

manifest
[
maxItems = 4096+1024+256+1  // IM+IFUM+RM+End, enough for one full image
lenCSArray = offset CSArray.item/16 + maxItems*lenItem
lenEBFile = offset EBFile.item/16  // + Item array
lenBRFile = offset BRFile.nameString/16  // + name string + Item array
etherVersionNumber = 0 lshift 8 + 1  // *** Change to 1 lshift 8 someday ***
bcplVersionNumber = 2 lshift 8
]


//----------------------------------------------------------------------------
let LoadMB() be
//----------------------------------------------------------------------------
[
Ws("*nLoadMB of June 25, 1982")

let csArray = vec lenCSArray; Zero(csArray, lenCSArray)
csArray>>CSArray.maxLength = maxItems

let stringVec, switchVec = vec 50, vec 50
SetupReadParam(stringVec, switchVec)

let WriteHeader = 0
let verify = false

for i = 1 to switchVec!0 do
   switchon switchVec!i into
      [
      case $B: case $b:
         WriteHeader = WriteBRHeader
         endcase
      case $E: case $e:
         WriteHeader = WriteEBHeader
         endcase
      case $V: case $v:
         verify = true
         endcase
      default:
         Fail("undefined global switch /$C", switchVec!i)
      ]

let streamOut = 0
let outName = vec 50
let inFileQ = vec 1; inFileQ!0 = 0
let labelName = vec 50
MoveBlock(labelName, "LoadRamTable", 12/2+1)

   [ // repeat
   if ReadParam(0, 0, 0, 0, true) eq -1 then break
   switchon switchVec!0 into
      [
      case 0:
         switchVec!1 = $I  // fall thru (treat as input file name)
      case 1:
         switchon switchVec!1 into
            [
            case $I: case $i:
               [
               let inName = vec 50
               let s = EvalParam(stringVec, "IW", "Input MB file name: ", inName)
               let inFile = Allocate(sysZone, lenInFile)
               let l = inName>>String.length rshift 1 +1
               inFile>>InFile.name = Allocate(sysZone, l)
               MoveBlock(inFile>>InFile.name, inName, l)
               GetCompleteFa(s, lv inFile>>InFile.cfa)
               Closes(s)
               inFile>>InFile.startAddr = -1  // haven't seen start address yet
               Enqueue(inFileQ, inFile)
               endcase
               ]
            case $L: case $l:
               unless WriteHeader eq WriteBRHeader do
                  Fail("/L only allowed with .BR output")
               EvalParam(stringVec, $P, "Label for LoadRam table: ", labelName)
               endcase
            case $O: case $o:
               streamOut = EvalParam(stringVec, "OW", "Output file name: ", outName)
               if WriteHeader eq 0 then WriteHeader = WriteEBHeader
               endcase
            case $S: case $s:
               if inFileQ!0 eq 0 then
                  Fail("start addr must follow the input file to which it applies")
               (inFileQ!1)>>InFile.startAddr =
                EvalParam(stringVec, $B, "Starting address: ")
               endcase
            default:
               Fail("undefined local switch /$C", switchVec!1)
               ]
         endcase
      default:
         Fail("multiple local switches")
      ]
   ] repeat

if inFileQ!0 eq 0 then Fail("no input file specified")

if WriteHeader ne 0 & streamOut eq 0 then
   [  // default output filename to match last input filename
   MoveBlock(outName, (inFileQ!1)>>InFile.name, 50)
   for i = 1 to outName>>String.length do
      if outName>>String.char↑i eq $. then outName>>String.length = i-1
   let i = outName>>String.length
   outName>>String.char↑(i+1) = $.
   outName>>String.char↑(i+2) = WriteHeader eq WriteBRHeader? $b, $e
   outName>>String.char↑(i+3) = WriteHeader eq WriteBRHeader? $r, $b
   outName>>String.length = i+3
   streamOut = OpenFile(outName, ksTypeWriteOnly, wordItem)
   ]

let defaultStartAddr = WriteHeader ne 0? 1076B, 1070B  // boot, resume start addrs

if WriteHeader ne 0 then
   WriteHeader(streamOut, csArray, labelName, outName)

let totalItems = 0
let inFile = inFileQ!0
while inFile ne 0 do
   [
   let streamMB = OpenFile(inFile>>InFile.name, ksTypeReadOnly, charItem, 0,
    lv inFile>>InFile.cfa.fp)
   if streamMB eq 0 then CallSwat("Failed to open existing file")
   let items = csArray>>CSArray.length
   ReadMBFile(streamMB, csArray)
   Closes(streamMB)
   if inFile>>InFile.startAddr eq -1 then inFile>>InFile.startAddr = defaultStartAddr
   AppendEndItem(csArray, inFile>>InFile.startAddr)
   items = csArray>>CSArray.length - items
   totalItems = totalItems + items
   PutTemplate(dsp, "*n$D items read from $S; starting address = $UOB.",
    items, inFile>>InFile.name, inFile>>InFile.startAddr)
   if WriteHeader ne 0 then
      [  // write csArray out onto file and re-use it
      WriteBlock(streamOut, lv csArray>>CSArray.item↑0, csArray>>CSArray.length*lenItem)
      Zero(csArray, lenCSArray)
      csArray>>CSArray.maxLength = maxItems
      ]
   inFile = inFile>>InFile.next
   ]

if WriteHeader ne 0 then
   [
   let pos = vec 1; FilePos(streamOut, pos)
   if WriteHeader eq WriteBRHeader then
      [
      TruncateDiskStream(streamOut)
      FixUpBRHeader(streamOut, totalItems*lenItem, labelName)
      ]
   Closes(streamOut)
   PutTemplate(dsp, "*n$ED bytes written on $S.", pos, outName)
   finish
   ]

if verify then
   [
   Ws("*nLoad and boot?")
      [ // repeat
      switchon Gets(keys) into
         [
         case $Y: case $y: case $*n:
            break
         case $N: case $n: case $*177:
            finish
         default:
            Ws("?")
         ]
      ] repeat
   ]

let LoadRamAndJump = table [ 61036B; 1401B ]
LoadRamAndJump(lv csArray>>CSArray.item↑0, true)
]

//----------------------------------------------------------------------------
and Fail(string, par1, par2, par3) be
//----------------------------------------------------------------------------
[
Ws("*nCommand error: ")
PutTemplate(dsp, string, par1, par2, par3)
Ws("*n")
abort
]

//----------------------------------------------------------------------------
and ReadMBFile(stream, csArray) be
//----------------------------------------------------------------------------
[
manifest lenBuf = 1024
manifest minLenBuf = 128
let mbBuf = vec lenBuf
let mb = mbBuf
let endBuf = mbBuf
let item = lv csArray>>CSArray.item↑(csArray>>CSArray.length)
let endItem = lv csArray>>CSArray.item↑(csArray>>CSArray.maxLength)
let memoryNumIM = -2
let memoryNumIFUM = -2
let memoryNumRM = -2
let memoryNum = -1
let addr = nil
let memoryWidths = vec maxMemoryNum; Zero(memoryWidths, maxMemoryNum)
let name = vec maxLenName

   [ // repeat
   if (endBuf-mb) ls minLenBuf then
      [
      let wordsRemaining = endBuf-mb
      MoveBlock(mbBuf, mb, wordsRemaining)
      mb = mbBuf
      wordsRemaining = wordsRemaining +
       ReadBlock(stream, mb+wordsRemaining, lenBuf-wordsRemaining)
      if wordsRemaining le 0 then CallSwat("Missing End block in MB file")
      endBuf = mbBuf+wordsRemaining
      ]

   switchon mb>>MB.type into
      [
      case mbEnd:
         break

      case mbData:
         [
         let value = lv mb>>MB.data.value
         test memoryNum eq memoryNumIM ifso
            [
            item>>Item.type = itemIM
            item>>Item.data.addr = value>>IM.absoluteAddr
            item>>Item.data.word0 = value>>IM.rest0 lshift 1 + value>>IM.ASEL2
            item>>Item.data.word1 = value>>IM.rest1 lshift 2 + value>>IM.JCN67
            item>>Item.PE020 = value>>IM.PE020
            item>>Item.RSTK0 = value>>IM.RSTK0
            item>>Item.PE2131 = value>>IM.PE2131
            item>>Item.BLK = value>>IM.BLK
            item = item+lenItem
            if item ge endItem then CallSwat("Item array overflowed")
            ]
         ifnot test memoryNum eq memoryNumIFUM ifso
            [
            item>>Item.type = itemIFUM
            item>>Item.data.addr = addr
            // Despite what the manual says, the hardware really wants to
            // see even parity -- hence the "not" in the following statements
            value>>IFUM.P0 = not OddParity((value>>IFUM.word0 & ifumW0P0) xor
             (value>>IFUM.word1 & ifumW1P0))
            value>>IFUM.P1 = not OddParity((value>>IFUM.word0 & ifumW0P1) xor
             (value>>IFUM.word1 & ifumW1P1))
            value>>IFUM.P2 = not OddParity((value>>IFUM.word0 & ifumW0P2) xor
             (value>>IFUM.word1 & ifumW1P2))
            item>>Item.data.word0 = value>>IFUM.word0
            item>>Item.data.word1 = value>>IFUM.word1
            item = item+lenItem
            if item ge endItem then CallSwat("Item array overflowed")
            ]
         ifnot test memoryNum eq memoryNumRM ifso
            [
            item>>Item.type = itemRM
            item>>Item.data.addr = addr
            item>>Item.data.word0 = value>>RM.word0
            item = item+lenItem
            if item ge endItem then CallSwat("Item array overflowed")
            ]
         ifnot if memoryNum eq -1 then
            CallSwat("Data for unspecified memory")
         addr = addr+1
         mb = mb + lenMBData + memoryWidths!memoryNum
         endcase
         ]

      case mbMemory:
         memoryNum = mb>>MB.memory.memoryNum
         if memoryWidths!memoryNum eq 0 then
            CallSwat("Undefined memory")
         addr = mb>>MB.memory.location
         mb = mb + lenMBMemory
         endcase

      case mbFixup:
         CallSwat("Fixup block encountered in MB file")

      case mbMemoryName:
         [
         let newMemoryNum = mb>>MB.memoryName.memoryNum
         if Usc(newMemoryNum, maxMemoryNum) gr 0 then
            CallSwat("Memory number out of bounds")
         memoryWidths!newMemoryNum = (mb>>MB.memoryName.width +15) rshift 4
         let lenName = ReadName(lv mb>>MB.memoryName.name, name)
         test StringEq(name, "IM") ifso memoryNumIM = newMemoryNum
         ifnot test StringEq(name, "IFUM") ifso memoryNumIFUM = newMemoryNum
         ifnot if StringEq(name, "RM") then memoryNumRM = newMemoryNum
         mb = mb + lenMBMemoryName + lenName
         endcase
         ]

      case mbAddress:
         mb = mb + lenMBAddress + ReadName(lv mb>>MB.address.name, name)
         endcase

      case mbUndefined:
         CallSwat("Undefined symbol block encountered in MB file")

      default:
         CallSwat("Unknown block type in MB file")
      ]
   ] repeat

csArray>>CSArray.length = (item - lv csArray>>CSArray.item↑0)/lenItem
]

//----------------------------------------------------------------------------
and AppendEndItem(csArray, startAddr) be
//----------------------------------------------------------------------------
[
let item = lv csArray>>CSArray.item↑(csArray>>CSArray.length)
csArray>>CSArray.length = csArray>>CSArray.length +1
item>>Item.type = itemEnd
item>>Item.end.startAddr = startAddr
let checksum = 0
let firstItem = lv csArray>>CSArray.item↑0
for i = 0 to (csArray>>CSArray.length * lenItem)-1 do
   checksum = checksum + firstItem!i
item>>Item.end.checksum = -checksum
]

//----------------------------------------------------------------------------
and ReadName(mbName, stringName) = valof
//----------------------------------------------------------------------------
// Converts from the MB name format to a normal string.
// Returns the number of words occupied by mbName.
[
let i = 0
   [ // repeat
   let char = mbName>>MBName↑i
   if char eq 0 then break
   stringName>>String.char↑(i+1) = char
   i = i+1
   ] repeat
stringName>>String.length = i
resultis i rshift 1 +1
]

//----------------------------------------------------------------------------
and StringEq(s1, s2) = valof
//----------------------------------------------------------------------------
[
let length = s1>>String.length
if length ne s2>>String.length resultis false
for i = 1 to length do
   [
   let c1, c2 = s1>>String.char↑i, s2>>String.char↑i
   if c1 ne c2 then
      unless (c1 xor 40B) eq c2 &
       Usc((c1 & 137B)-$A, $Z-$A) le 0 resultis false
   ]
resultis true
]

//----------------------------------------------------------------------------
and OddParity(value) = valof
//----------------------------------------------------------------------------
// Returns the bit which, when combined with value, would make it have
// odd parity.
[
value = value xor (value rshift 8)
value = value xor (value rshift 4)
resultis (113151B rshift (value & 17B)) & 1
]

//----------------------------------------------------------------------------
and WriteEBHeader(stream, csArray, nil, fileName) be
//----------------------------------------------------------------------------
[
let buf = vec 256  // = lenEBFile

// Make the embedded creation date be the same as the file's
ReadLeaderPage(stream, buf)
let creationDate = vec 1; MoveBlock(creationDate, lv buf>>LD.created, 2)
Zero(buf, lenEBFile)
MoveBlock(lv buf>>EBFile.creationDate, creationDate, 2)
buf>>EBFile.etherVersionNumber = etherVersionNumber
MesaFromBCPLString(lv buf>>EBFile.name, fileName)
WriteBlock(stream, buf, lenEBFile)
]

//----------------------------------------------------------------------------
and WriteBRHeader(stream, csArray, labelName, nil) be
//----------------------------------------------------------------------------
[
let brFile = vec lenBRFile; Zero(brFile, lenBRFile)
let nameLength = labelName>>String.length/2+1
brFile>>BRFile.bcplVersionNumber = bcplVersionNumber
// will be fixed up later:
// brFile>>BRFile.fileLength = offset BRFile.item/16 + nameLength + csArrayLength
brFile>>BRFile.nameTableOffset = offset BRFile.nameTable/16
brFile>>BRFile.labelTableOffset = offset BRFile.labelTable/16
brFile>>BRFile.codeOffset = offset BRFile.code/16 + nameLength
brFile>>BRFile.chainTableOffset = offset BRFile.chainTable/16
brFile>>BRFile.zchainTableOffset = offset BRFile.zchainTable/16
brFile>>BRFile.labelCount = 1
brFile>>BRFile.labelNameNumber = 1
brFile>>BRFile.labelPC = 1
brFile>>BRFile.nameCount = 1
brFile>>BRFile.nameDescriptor = 140b
WriteBlock(stream, brFile, lenBRFile)
WriteBlock(stream, labelName, nameLength)
Puts(stream, 0)		// codeLength (csArrayLength+1), will be fixed up later
Puts(stream, 0)		// codeFirst
]

//----------------------------------------------------------------------------
and FixUpBRHeader(stream, csArrayLength, labelName) be
//----------------------------------------------------------------------------
[
let nameLength = labelName>>String.length/2+1
SetFilePos(stream, 0, offset BRFile.fileLength/8)
Puts(stream, offset BRFile.item/16 + nameLength + csArrayLength)
SetFilePos(stream, 0, 2*(lenBRFile+nameLength))
Puts(stream, csArrayLength+1)
Resets(stream)  // so OS won't truncate when we close!
]

//----------------------------------------------------------------------------
and MesaFromBCPLString(mesaString, bcplString) = valof
//----------------------------------------------------------------------------
// Returns the number of words occupied by the mesaString
[
let length = bcplString>>String.length
mesaString>>MesaString.length = length
mesaString>>MesaString.maxLength = (length+1) & -2
for i = 1 to length do mesaString>>MesaString.char↑(i-1) = bcplString>>String.char↑i
resultis (offset MesaString.char↑0 / 16) + (length+1) rshift 1
]