// BFSTestCmd.bcpl -- CreateFile, Erase, and Certify commands
// Copyright Xerox Corporation 1982
// Last modified May 6, 1982  5:55 PM by Boggs

get "Altofilesys.d"
get "AltoDefs.d"
get "Streams.d"
get "Disks.d"
get "Bfs.d"

external
[
// outgoing procedures
Certify; Erase; CreateFile
XferError

// incoming procedures
OpenFile; GetCurrentFa; PositionPage; DeleteFile
BFSInit; CloseDisk; BfsMakeFpFromLabel; BFSNewDisk
InitializeDiskCBZ; GetDiskCb; DoDiskCommand
AssignDiskPage; ReleaseDiskPage; VirtualDiskDA
Gets; Puts; Endofs; Closes; Resets
Zero; MoveBlock; SetBlock; Noop; Idle
Allocate; Free; DefaultArgs; MyFrame; GotoLabel
Ws; Confirm; Ding; GetNumber; GetString
PutTemplate; Random; MyIdle

// incoming statics
keys; dsp; sysZone
label; data; eng

// outgoing statics
sysDisk
]

static
[
maxVDA; sysDisk
nDisks; nTracks; nSectors; et
]

manifest
[
maxETEntries = 1000
errorThreshold = 3
]

structure ET:  //Error Table
[
nEntries word
entry↑0,maxETEntries-1 [ da word; nErrors word ]
]
manifest lenET = size ET/16

//----------------------------------------------------------------------------
let CreateFile() be
//----------------------------------------------------------------------------
[
sysDisk = BFSInit(sysZone, true, 0)
if sysDisk eq 0 then
   [
   Ws("*NCan't init the disk.  Is it on?  Is it formatted (use ERASE)?")
   return
   ]
let fileName = GetString(" named ")
if fileName eq 0 then [ CloseDisk(sysDisk); return ]
let nPages = GetNumber(", length in pages ")
Idle = MyIdle
DeleteFile(fileName)
if nPages ugr sysDisk>>DSK.diskKd>>KDH.freePages then
   [
   Ding(dsp)
   Ws("*NThere isn't enough space on your disk")
   nPages = 0
   ]

let stream = 0
maxVDA = sysDisk>>DSK.diskKd>>KDH.diskBTsize*16 -1
until nPages eq 0 do
   [
   // Scan the entire bit table and find the biggest hole
   ReleaseDiskPage(sysDisk, AssignDiskPage(sysDisk, 0))
   let vdaBiggestHole, sizeBiggestHole = 0, 0
   let vdaThisHole, sizeThisHole = 0, 0
   let vda = 0; while vda ule maxVDA do
      [
      test AssignDiskPage(sysDisk, vda, nil)
         ifso test vdaThisHole eq 0  //vda+1 is free
            ifso [ vdaThisHole, sizeThisHole = vda +1, 1 ]  //new Hole
            ifnot sizeThisHole = sizeThisHole +1
         ifnot if vdaThisHole ne 0 then  //vda+1 is allocated
            [
            if sizeThisHole ugr sizeBiggestHole then
               [ vdaBiggestHole, sizeBiggestHole = vdaThisHole, sizeThisHole ]
            vdaThisHole = 0
            ]
      vda = vda +1
      ]

   if vdaBiggestHole eq 0 then
      [
      if stream ne 0 then
         [ Closes(stream); stream = 0; DeleteFile(fileName) ]
      Ding(dsp)
      Ws("*NThere isn't enough space on your disk")
      break
      ]

   PutTemplate(dsp, "*NFound a group of $UOb pages starting at vda $UOb",
    sizeBiggestHole, vdaBiggestHole)
   ReleaseDiskPage(sysDisk, AssignDiskPage(sysDisk, vdaBiggestHole-1))
   if stream eq 0 then stream = OpenFile(fileName, ksTypeWriteOnly)
   let fa = vec lFA; GetCurrentFa(stream, fa)
   if nPages uls sizeBiggestHole then sizeBiggestHole = nPages
   PositionPage(stream, fa>>FA.pageNumber+sizeBiggestHole)
   nPages = nPages - sizeBiggestHole
   ]

if stream then Closes(stream)
Free(sysZone, fileName)
CloseDisk(sysDisk)
Idle = Noop
]

//----------------------------------------------------------------------------
and Certify() be
//----------------------------------------------------------------------------
[
let nPasses = GetNumber(".  How many passes? ", 100)
unless GetDiskShape("CERTIFY") return
sysDisk = BFSInit(sysZone, false, 0, 0, true)
sysDisk>>BFSDSK.nDisks = nDisks
sysDisk>>BFSDSK.nTracks = nTracks
sysDisk>>BFSDSK.nSectors = nSectors
maxVDA = nDisks * nTracks * 2 * nSectors -1
et = Allocate(sysZone, lenET); Zero(et, lenET)
Idle = MyIdle

// Set up read and write cursors
let rCursor = table [ 0; 0; 0; 76000b; 41000b; 41000b; 41000b; 76000b
 44000b; 42000b; 42000b; 41000b; 41000b; 0; 0; 0 ]
let wCursor = table [ 0; 0; 0; 40400b; 44400b; 44400b; 44400b; 25000b
 25000b; 25000b; 12000b; 12000b; 12000b; 0; 0; 0 ]
let savedCursor = vec 16; MoveBlock(savedCursor, cursorBitMap, 16)

// suck out a random number of random numbers
let c = @realTimeClock & 7777b
while c ne 0 do [ Random(); c = c-1 ]

// certify the disk
for i = 1 to nPasses do
   [
   for w = 0 to 255 do data!w = Random()
   MoveBlock(cursorBitMap, wCursor, 16)
   sysDisk>>DSK.retryCount = 8
   if SweepDisk(i eq 1? DCwriteHLD, DCwriteLD) break
   MoveBlock(cursorBitMap, rCursor, 16)
   sysDisk>>DSK.retryCount = 1
   if SweepDisk(DCreadLD) break
   ]

// mark bad spots incorrigable
sysDisk>>DSK.retryCount = 8
let nBadPages = 0
SetBlock(lv label>>DL.fileId, -2, lFID)
for i = 0 to et>>ET.nEntries-1 do
   if et>>ET.entry↑i.nErrors uge errorThreshold then
      [
      let vda = VirtualDiskDA(sysDisk, lv et>>ET.entry↑i.da)
      XferPage(DCwriteHLD, vda, 0, label, 0, lv Noop)
      nBadPages = nBadPages +1
      ]
PutTemplate(dsp, "*N$D pages marked bad", nBadPages)

MoveBlock(cursorBitMap, savedCursor, 16)
Idle = Noop
Free(sysZone, et)
CloseDisk(sysDisk)
until Endofs(keys) do Gets(keys)
]

//----------------------------------------------------------------------------
and Erase() be
//----------------------------------------------------------------------------
[
Idle = MyIdle
if GetDiskShape("ERASE") then
   unless BFSNewDisk(sysZone, 0, nDisks, nTracks, 0, nSectors) do
      [ Ws("...failed"); Ding(dsp) ]
Idle = Noop
]

//----------------------------------------------------------------------------
and GetDiskShape(action) = valof
//----------------------------------------------------------------------------
[
if TryDisk(0, 0, 0, 0)<<DST.notReady then
   [ Ws(".  DP0 is not ready!"); Ding(dsp); resultis false ]
nDisks, nTracks, nSectors = 1, 203, 12
unless TryDisk(1, 0, 0, 0)<<DST.notReady do
   nDisks = Confirm("*NUse both disks? ")? 2, 1
unless TryDisk(0, 203, 0, 0)<<DST.seekFail do
   nTracks = Confirm("*NUse all 406 cylinders? ")? 406, 203
unless TryDisk(0, 0, 0, 13)<<DST.finalStatus eq badSector do
   nSectors = Confirm("*NUse all 14 sectors? ")? 14, 12
PutTemplate(dsp, "*NDisks: $D, Cylinders: $D, Heads: 2, Sectors: $D",
 nDisks, nTracks, nSectors)
let eng = (table [ 61014b; 1401b ])()<<VERS.eng
PutTemplate(dsp, "*NAbout to $S a $S model $S $Sfile system.", action,
 (nDisks eq 1? "single", "dual"), (nTracks eq 203? "31", "44"),
 (eng gr 3? (nSectors eq 12? "12 sector ", "14 sector "), ""))
Ws("*N*NWARNING: The old disk contents will be destroyed.")
let ok = Confirm("*NAre you sure this is what you want to do? ")
if ok then
   [
   let now = @realTimeClock
   while now + 5*27 ugr @realTimeClock loop
   Resets(keys)
   ok = Confirm("*N*NAre you still sure? ")
   ]
resultis ok
]

//----------------------------------------------------------------------------
and TryDisk(dsk, trk, hd, sect) = valof
//----------------------------------------------------------------------------
[
let kcb = vec lKCB; Zero(kcb, lKCB)
kcb>>KCB.command = seekOnly
kcb>>KCB.headerAddress = lv kcb>>KCB.header
kcb>>KCB.labelAddress = label
kcb>>KCB.dataAddress = data
kcb>>KCB.diskAddress.disk = dsk
kcb>>KCB.diskAddress.track = trk
kcb>>KCB.diskAddress.head = hd
kcb>>KCB.diskAddress.sector = sect
@diskAddress = -1
until @diskCommand eq 0 loop
for trys = 1 to 5 do
   [
   kcb>>KCB.status = 0
   @diskCommand = kcb
   while (kcb>>KCB.status & DSTdoneBits) eq 0 loop
   if (kcb>>KCB.status & DSTgoodStatusMask) eq DSTgoodStatus break
   ]
resultis kcb>>KCB.status
]

//----------------------------------------------------------------------------
and SweepDisk(action) = valof
//----------------------------------------------------------------------------
[
let zoneLength = sysDisk>>DSK.lengthCBZ + 2*nSectors*sysDisk>>DSK.lengthCB
let cbz = Allocate(sysZone, zoneLength)
InitializeDiskCBZ(sysDisk, cbz, 0, zoneLength, SweepRetry, lv SweepError)
cbz>>CBZ.cleanupRoutine = Noop
cbz>>CBZ.errorDA = 0
cbz>>CBZ.client = MyFrame()

SweepRetry: let sweepVDA = cbz>>CBZ.errorDA
while sweepVDA le maxVDA do
   [
   let cb = GetDiskCb(sysDisk, cbz)
   cb>>CB.labelAddress = data
   DoDiskCommand(sysDisk, cb, data, sweepVDA, data+5, data!4, action)
   sweepVDA = sweepVDA +1
   if (sweepVDA & 77b) eq 0 then
      [ unless Endofs(keys) break; if eng eq 4 then Idle() ]
   ]

while @cbz>>CBZ.queueHead ne 0 do GetDiskCb(sysDisk, cbz)
Free(sysZone, cbz)
resultis not Endofs(keys)
]

//----------------------------------------------------------------------------
and SweepError(nil, cb, nil) be
//----------------------------------------------------------------------------
[
let dst = cb>>CB.status
let da = cb>>CB.diskAddress
if dst<<DST.checksumError ne 0 & dst<<DST.dataLate eq 0 then
   [
   PutTemplate(dsp, "*NChecksum error at Un $D, Cyl $D, Hd $D, Sec $D.",
    da<<DA.disk, da<<DA.track, da<<DA.head, da<<DA.sector)
   let i = 0; while i ls et>>ET.nEntries do
      [ if da eq et>>ET.entry↑i.da break; i = i +1 ]
   if i ls maxETEntries then
      [
      if et>>ET.entry↑i.nErrors eq 0 then
         [
         et>>ET.nEntries = et>>ET.nEntries +1
         et>>ET.entry↑i.da = da
         ]
      et>>ET.entry↑i.nErrors = et>>ET.entry↑i.nErrors +1
      PutTemplate(dsp, "  Errors here = $UD.", et>>ET.entry↑i.nErrors)
      ]
   if i eq maxETEntries then Ws(" Error table full!")
   ]

// treat it sort of like a soft error
let cbz = cb>>CB.cbz
cbz>>CBZ.errorDA = VirtualDiskDA(sysDisk, lv da) +1
InitializeDiskCBZ(sysDisk, cbz)
if sysDisk>>DSK.retryCount eq 1 & dst<<DST.seekFail then
   [  //retry count of one bypasses GetCB's recal for pos errors
   @diskAddress = -1
   DoDiskCommand(sysDisk, GetDiskCb(sysDisk, cbz), 0, 0, 0, 0, 525b)
   ]
GotoLabel(cbz>>CBZ.client, cbz>>CBZ.retry)
]

//----------------------------------------------------------------------------
and XferPage(action, vda, d, l, h, lvError; numargs na) = valof
//----------------------------------------------------------------------------
[
let header = vec 1
DefaultArgs(lv na, -2, data, label, header, lv XferError)
let cbz = vec CBzoneLength
InitializeDiskCBZ(sysDisk, cbz, 0, CBzoneLength, XferRetry, lvError)
cbz>>CBZ.cleanupRoutine = XferCleanup
cbz>>CBZ.client = h
XferRetry: let cb = GetDiskCb(sysDisk, cbz)
cb>>CB.labelAddress = l
let fp = vec lFP; BfsMakeFpFromLabel(fp, l)
DoDiskCommand(sysDisk, cb, d, vda, fp, l>>DL.pageNumber, action)
while @cbz>>CBZ.queueHead ne 0 do GetDiskCb(sysDisk, cbz)
resultis cbz>>CBZ.currentPage
]

//----------------------------------------------------------------------------
and XferCleanup(disk, cb, cbz) be
//----------------------------------------------------------------------------
[
MoveBlock(cbz>>CBZ.client, cb>>CB.headerAddress, 2)
cbz>>CBZ.currentPage = cb>>CB.status
]

//----------------------------------------------------------------------------
and XferError(nil, cb, nil) be
//----------------------------------------------------------------------------
[
let disk = cb>>CB.cbz>>CBZ.disk
let rda = cb>>CB.diskAddress
let vda = VirtualDiskDA(disk, lv rda)
PutTemplate(dsp, "*NHard error at VDA $UO = Unit $D Cylinder $D Head $D Sector $D",
 vda, rda<<DA.disk, rda<<DA.track, rda<<DA.head, rda<<DA.sector)
Ws("*NAttempted action was")
for i = 1 to 3 do
   [
   let action = selecton i into
      [
      case 1: cb>>CB.command.headerAction
      case 2: cb>>CB.command.labelAction
      case 3: cb>>CB.command.dataAction
      ]
   action = selecton action into
      [
      case 0: "read"
      case 1: "check"
      case 2: case 3: "write"
      ]
   let record = selecton i into
      [
      case 1: "header"
      case 2: "label"
      case 3: "data"
      ]
   PutTemplate(dsp, " $S $S", action, record)
   ]
Ws("*NResulting status was")
let dst = cb>>CB.status
if dst<<DST.seekFail then Ws(" seek failed")
if dst<<DST.seeking then Ws(" seeking")
if dst<<DST.notReady then Ws(" disk not ready (on?)")
if dst<<DST.dataLate then Ws(" data late")
if dst<<DST.noTransfer then Ws(" no transfer")
if dst<<DST.checksumError then Ws(" checksum error")
if dst<<DST.finalStatus then Ws(selecton dst<<DST.finalStatus into
   [
   case 1: (dst&360b)? "", " sector late"
   case 2: " check error"
   case 3: " illegal sector"
   ])
]