// SwatDisk.bcpl - Disk stuff
// Copyright Xerox Corporation 1979, 1982
// Last modified March 21, 1982  11:25 PM by Boggs

get "Altodefs.d"
get "AltoFileSys.d"
get "Streams.d"
get "Disks.d"
get "Swat.decl"
get "SwatDisk.decl"

external
[
// outgoing procedures
CreateKVM

// incoming procedures
Disable; Enable
Fail; ReportFail; SetFailPt; UnSetFailPt

PutTemplate; Ws; Wss; Puts
Allocate; Free; Zero; SetBlock; MoveBlock
Enqueue; Dequeue; Unqueue; QueueLength
Idle; Noop; OutLd; InLd
OpenFile; Closes; WriteBlock
ActOnDiskPages; WriteDiskPages
DeleteDiskPages; RealDiskDA
ExtractSubstring

// incoming statics
sysDisk; sysZone; dsp; swattingOK; vm; fpSwat
]

//----------------------------------------------------------------------------
let CreateKVM(string, create; numargs na) = valof
//----------------------------------------------------------------------------
// FilePn:      0       1       2    ...    375b    376b   377b   400b
// Contents:  leader  loader  user2  ...  user375  user1  user0   empty
//              0       1       2    ...    hsp    hsp+1  hsp+2   hsp+3
[
if na ls 2 then create = false
let kvm = Allocate(sysZone, lenKVM); Zero(kvm, lenKVM)
kvm>>VM.name = string
SetBlock(lv kvm>>KVM.DAs, fillInDA, hsp+6)
SetFailPt(ckvm)
   [
   Zero(lv kvm>>KVM.fp, lFP)
   let s = OpenFile(string, (create? ksTypeWriteOnly, ksTypeReadOnly),
    wordItem, (create? verLatestCreate, verLatest), lv kvm>>KVM.fp)
   if s eq 0 then ReportFail("File not found")
   Closes(s)
   kvm>>KVM.DAs↑0 = kvm>>KVM.fp.leaderVirtualDa

   let fixedCA = vec 256
   let numChars = nil
   let DAs = lv kvm>>KVM.DAs↑0
   let lastPage = ActOnDiskPages(sysDisk, 0, DAs, lv kvm>>KVM.fp, 0,
    hsp+3, DCreadD, lv numChars, 0, fixedCA)
   if lastPage ls hsp+3 test create
      ifnot ReportFail("This file was not created by OutLd")
      ifso
         [
         DAs!(lastPage+1) = fillInDA
         WriteDiskPages(sysDisk, 0, DAs, lv kvm>>KVM.fp, lastPage,
          hsp+3, DCwriteD, lv numChars, 0, fixedCA)
         ]
   if create then
      [
      let leftoverDA = DAs!(hsp+4)
      if numChars ne 0 then
         WriteDiskPages(sysDisk, 0, DAs, lv kvm>>KVM.fp, hsp+3,
          hsp+3, DCwriteD, 0, 0, fixedCA)
      if leftoverDA ne eofDA then
         DeleteDiskPages(sysDisk, fixedCA, leftoverDA, lv kvm>>KVM.fp, hsp+4)
      ]
   RealDiskDA(sysDisk, DAs!1, lv kvm>>KVM.fp.leaderVirtualDa)
   RealDiskDA(sysDisk, DAs!(hsp+2), DAs)  // page 0
   RealDiskDA(sysDisk, DAs!(hsp+1), DAs+1)  // page 1
   for i = 2 to hsp do RealDiskDA(sysDisk, DAs!i, DAs+i)  // pages 2 to hsp

   kvm>>VM.fetch = KVMFetch
   kvm>>VM.store = KVMStore
   kvm>>VM.swap = KVMSwap
   kvm>>VM.cache = KVMCache
   kvm>>VM.destroy = KVMDestroy
   kvm>>VM.print = KVMPrint
   kvm>>VM.type = vmTypeDisk

   MoveBlock(cursorBitMap, table [ 100000b; 140000b; 160000b; 170000b;
    174000b; 176000b; 177000b; 170000b; 154000b; 114000b; 006000b;
    006000b; 003000b; 003000b; 001400b; 001400b ], 16)

   UnSetFailPt()
   resultis kvm
   ]

ckvm:
KVMDestroy(kvm)
Fail()
]

//----------------------------------------------------------------------------
and KVMDestroy(kvm) be
//----------------------------------------------------------------------------
[
KVMCache(kvm, vmFlushReset)
Free(sysZone, kvm>>VM.name)
Free(sysZone, kvm)
]

//----------------------------------------------------------------------------
and KVMSwap(kvm) be
//----------------------------------------------------------------------------
[
let scm = KVMFetch(kvm, 567b)
let fpSwatee = lv kvm>>KVM.fp
for i = 0 to lFP-1 do KVMStore(kvm, scm+swateeOffset+i, fpSwatee!i)
for i = 0 to lFP-1 do KVMStore(kvm, scm+swatOffset+i, fpSwat!i)
KVMCache(kvm, vmFlushReset)
Disable()
unless OutLd(fpSwat, 0) do InLd(fpSwatee, 0)
Enable()
KVMCache(kvm, vmReset)
]

//----------------------------------------------------------------------------
and KVMCache(kvm, action) be
//----------------------------------------------------------------------------
[
let kb = kvm>>KVM.kbQ.head; while kb ne 0 do
   [
   let nextKB = kb>>KB.link
   if (action & vmFlush) ne 0 & kb>>KB.dirty ne 0 then
      RWDiskPage(kvm, kb, kb>>KB.userPage, writeD, true)
   if (action & vmReset) ne 0 then
      [ Unqueue(lv kvm>>KVM.kbQ, kb); Free(sysZone, kb) ]
   kb = nextKB
   ]
]

//----------------------------------------------------------------------------
and KVMPrint(kvm, stream) be
//----------------------------------------------------------------------------
[
PutTemplate(stream, ", hits: $UD misses: $UD*N*N",
 kvm>>KVM.hits, kvm>>KVM.misses)
Wss(stream, "Core addr = Disk addr*N")
let kb = kvm>>KVM.kbQ.head; while kb ne 0 do
   [
   PutTemplate(stream, "   $U6O = $UO*N",
    kb>>KB.userPage lshift 8, kb>>KB.address)
   kb = kb>>KB.link
   ]
]

//----------------------------------------------------------------------------
and KVMFetch(kvm, addr) = @RealAddr(kvm, addr, false)
//----------------------------------------------------------------------------

//----------------------------------------------------------------------------
and KVMStore(kvm, addr, val) be @RealAddr(kvm, addr, true) = val
//----------------------------------------------------------------------------

//----------------------------------------------------------------------------
and RealAddr(kvm, addr, makeDirty) = valof
//----------------------------------------------------------------------------
[
let userPage = addr rshift 8
if userPage gr hsp resultis addr  // I/O region not swapped
let userWord = addr & 377b

let kbQ = lv kvm>>KVM.kbQ

// search cache
let kb = kbQ!0; while kb ne 0 do
   [
   if kb>>KB.userPage eq userPage break
   kb = kb>>KB.link
   ]

test kb ne 0
   ifso [ Unqueue(kbQ, kb); kvm>>KVM.hits = kvm>>KVM.hits +1 ]
   ifnot  //miss
      [
      kvm>>KVM.misses = kvm>>KVM.misses +1
      let lenKBQ = QueueLength(kbQ)
      if lenKBQ ls maxKBs then
         kb = Allocate(sysZone, lenKB, lenKBQ ne 0)
      if kb eq 0 then
         [
         kb = Dequeue(kbQ)
         if kb>>KB.dirty ne 0 then
            RWDiskPage(kvm, kb, kb>>KB.userPage, writeD, true)
         ]
      RWDiskPage(kvm, kb, userPage, readD, true)
      ]

Enqueue(kbQ, kb)
if makeDirty then kb>>KB.dirty = true
resultis lv kb>>KB.dataRec + userWord
]

//----------------------------------------------------------------------------
and FilePage(upn) = upn eq 0? hsp+2, upn eq 1? hsp+1, upn eq -1? 1, upn
//----------------------------------------------------------------------------

//----------------------------------------------------------------------------
and UserPage(fpn) = fpn eq hsp+2? 0, fpn eq hsp+1? 1, fpn eq 1? -1, fpn
//----------------------------------------------------------------------------

//----------------------------------------------------------------------------
and RWDiskPage(kvm, kb, userPage, command, retryErrors) be
//----------------------------------------------------------------------------
[
for trys = 1 to 10 do
   [
   if trys gr 4 then RWDiskPage(kvm, kb, userPage, restore, false)
   Zero(kb+1, offset KB.dataRec/16-1)
   kb>>KB.userPage = userPage
   kb>>KB.command = command
   if command eq restore then @diskAddress = -1
   kb>>KB.header = lv kb>>KB.headerRec
   kb>>KB.label = lv kb>>KB.labelRec
   kb>>KB.data = lv kb>>KB.dataRec
   kb>>KB.address = command eq restore? 1, kvm>>KVM.DAs↑userPage
   kb>>KB.numChars = 512
   kb>>KB.filePage = FilePage(userPage)
   kb>>KB.vn = kvm>>KVM.fp.version
   kb>>KB.sn1 = kvm>>KVM.fp.serialNumber.word1
   kb>>KB.sn2 = kvm>>KVM.fp.serialNumber.word2
   @diskCommand = lv kb>>KB.kcb
   until kb>>KB.status ne 0 do Idle()
   unless retryErrors return
   switchon kb>>KB.status & 377b into
      [
      case 0: return	// good status
      case 2: break	// check error
      default: loop	// other error
      ]
   ]

Ws("*N*NDisk error in Swatee*N")
PutTemplate(dsp, "Status: $UO, Command: $UO, Disk Address: $UO*N",
 kb>>KB.status, kb>>KB.command, kb>>KB.address)
PrintLabel("Label for Swatee:", kb)
let s = kb>>KB.status
test (s&3) eq 2
   ifso
      [
      RWDiskPage(kvm, kb, userPage, readHLD, false)
      PrintLabel("Label from disk: ", kb)
      Ws("Check error*N")
      ]
   ifnot
      [
      if (s & 200b) ne 0 then Ws("Seek failed ")
      if (s & 100b) ne 0 then Ws("Seek in progress ")
      if (s & 40b) ne 0 then Ws("Disk not ready (on?) ")
      if (s & 20b) ne 0 then Ws("Processing late ")
      if (s & 10b) ne 0 then Ws("No data transferred ")
      if (s & 4b) ne 0 then Ws("Checksum error ")
      Puts(dsp, $*N)
      ]

Free(sysZone, kb)
Fail()
]

//----------------------------------------------------------------------------
and PrintLabel(text, kb) be
//----------------------------------------------------------------------------
[
PutTemplate(dsp, "$S nextP  backP  numCh  page     serial    vers*N", text)
for i = 1 to text>>String.length-1 do Puts(dsp, $*S)
PutTemplate(dsp, " $U6F0O $U6F0O $U6F0O $U6F0O $EU11F0O $U6F0O*N",
 kb>>KB.backP, kb>>KB.nextP, kb>>KB.numChars, kb>>KB.filePage,
 lv kb>>KB.sn1, kb>>KB.vn)
]