// TfuCertify.bcpl
// Copyright Xerox Corporation 1979, 1980, 1984
// Last modified September 21, 1984 10:47 AM by Fiala
// Last modified June 14, 1980 2:59 PM by Taft
get "Altofilesys.d"
get "Disks.d"
get "Tfs.d"
external
[
//outgoing procedures
CertifyPack; ListBadSpots; ResetBadSpots
//incoming procedures
ConfirmWipe; BigDisplay; SmallDisplay
TFSInit; TFSClose; DataFix
TFSInitializeCbStorage; TFSGetCb; TFSDoDiskCommand; ActOnDiskPages
TFSDiskModel; VirtualDiskDA
Allocate; Free; Zero; MoveBlock
Ws; Gets; Puts; Endofs; PutTemplate; Random
//incoming statics
z; keys; dsp; TFSLeaveDisplay
]
static [ currentDA; et; saveDA ]
manifest maxETEntries = 100
structure ET:
[
nEntries word
entry↑0,maxETEntries-1 [ da @DA; nErrors word ]
]
manifest lenET = size ET/16
manifest
[
//The error count in et is incremented by 1 in the SweepDiskErr subroutine
//each time an irrecoverable error occurs. So errorThreshold is the number
//of passes that must experience irrecoverable errors at some disk address
//before it is declared to be a bad spot. I believe this parameter should
//be 1.
errorThreshold = 1
//retryCnt is the number of attempts made to reread data before declaring
//an irrecoverable error.
retryCnt = 1
nBufs = 3
cursor = #431
rtc = #430
]
//----------------------------------------------------------------------------
let CertifyPack(drive, passes) be
//----------------------------------------------------------------------------
[
unless ConfirmWipe(drive) return
let disk = TFSInit(z, false, drive, 0, true)
if disk eq 0 then
[ PutTemplate(dsp, "*nCan't access drive $O", drive); return ]
//retryCount is initialized to 14d by TFSSetDisk in TfsInit.bcpl,
//which is called by TFSInit(..). This is the total number of attempts
//made to read the data (?). Software will attempt to avoid an error by
//re-reading from the disk 3? times; if that succeeds, no indication
//that an error occurred will get back here. If the first 4? attempts to
//read data fail, then the MyDataFix subroutine (on the next page) will
//be called for the 4th? reread and all subsequent rereads until the data
//is declared irrecoverable. For these attempts, a reread either without
//error or with a correctable error will be considered success. Since the
//error correction procedure is time consuming, it is not desirable to
//invoke it until rereading has failed several times.
//For the purpose of diagnosing bad spots on the disk, retries are
//counter-productive. A read error should be retried 0 or 1 times before
//declaring a disk location to be a bad spot. The choice between 0 and 1 is
//affected by the likelihood of spurious read errors. If only 1 or 2 spurious
//read errors occur per Certify pass, it seems better to retry 0 times because
//an undetected bad spot is likely to eventually result in an irrecoverable
//read error during normal operation of IFS; if this happens, the
//IfsScavenger will have to be run, causing 2 to 3 hours of downtime, and a
//file may be lost or will have to be reloaded from backup. The value of
//a wasted disk page is ~$0.12.
let oldretryCount = disk>>DSK.retryCount
disk>>DSK.retryCount = retryCnt
et = Allocate(z, lenET)
Zero(et, lenET)
let bufVec = vec nBufs
for b = 0 to nBufs-1 do bufVec!b = Allocate(z, TFSwordsPerPage)
let buf = bufVec!0
//This code seems to leave the bad spot page alone, if it can be read
//and already has its seal = bplSeal. Otherwise, it zeroes the contents
//and writes bplSeal into the seal.
unless TransferPage0(disk, buf, DCreadD) & buf>>BPL.seal eq bplSeal do
[
Zero(buf, TFSwordsPerPage)
buf>>BPL.seal = bplSeal
TransferPage0(disk, buf, DCwriteHLD)
]
let rCursor = table [ 0; 0; 0; #76000; #41000; #41000; #41000; #76000; #44000; #42000; #42000; #41000; #41000; 0; 0; 0 ]
let wCursor = table [ 0; 0; 0; #40400; #44400; #44400; #44400; #25000; #25000; #25000; #12000; #12000; #12000; 0; 0; 0 ]
let savedCursor = vec 16
MoveBlock(savedCursor, cursor, 16)
MoveBlock(cursor, wCursor, 16)
// Suck out a random number of random numbers
let c = (@#430 & #7777)
while c ne 0 do [ Random(); c = c-1 ]
for i = 1 to passes do
[
PutTemplate(dsp, "*nPass $D", i)
for b = 0 to nBufs-1 do
for w = 0 to TFSwordsPerPage-1 do (bufVec!b)!w = Random()
MoveBlock(cursor, wCursor, 16)
unless (i eq 1? InitHeaders, WritePass)(disk, bufVec) break
MoveBlock(cursor, rCursor, 16)
unless ReadPass(disk, bufVec) break
]
disk>>DSK.retryCount = oldretryCount
MoveBlock(cursor, savedCursor, 16)
TransferPage0(disk, buf, DCreadD)
for i = 0 to (et>>ET.nEntries)-1 do
if et>>ET.entry↑i.nErrors ge errorThreshold then
AppendBadSpot(buf, lv et>>ET.entry↑i.da)
TransferPage0(disk, buf, DCwriteD)
for b = 0 to nBufs-1 do Free(z, bufVec!b)
Free(z, et)
TFSClose(disk)
]
//----------------------------------------------------------------------------
and AppendBadSpot(bpl, da) be
//----------------------------------------------------------------------------
[
for i = 0 to bpl>>BPL.nBadPages-1 do
if DAsEqual(da, lv bpl>>BPL.da↑i) return //duplicate
test bpl>>BPL.nBadPages ge (TFSwordsPerPage - offset BPL.da/16)/2
ifso Ws("*nBad Page List is full!")
ifnot
[
MoveBlock(lv bpl>>BPL.da↑(bpl>>BPL.nBadPages), da, 2)
bpl>>BPL.nBadPages = bpl>>BPL.nBadPages+1
]
]
//----------------------------------------------------------------------------
and InitHeaders(disk, bufVec) = SweepDisk(disk, bufVec, DCwriteHLD)
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
and WritePass(disk, bufVec) = SweepDisk(disk, bufVec, DCwriteLD)
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
and ReadPass(disk, bufVec) = valof
//----------------------------------------------------------------------------
[
static SysDataFix
//This code won't get called unless three attempts to read a disk block have
//failed. Since an irrecoverable read error occurs after only 1 reread, this
//means that this code won't get called. I have left in the code, however,
//in case different treatment is desired in the future.
let MyDataFix(block) = valof
[
DisplayOn()
let res = SysDataFix(block)
PutTemplate(dsp, "*n$S ECC error in $S at cyl $3D hd $2D sec $D",
(res eq -1? "Correctable", "Uncorrectable"),
selecton block>>KCBblock.Count into
[ case lDH: "Header"; case lDL: "Label"; case TFSwordsPerPage: "Data" ],
currentDA>>DA.track, currentDA>>DA.head, currentDA>>DA.sector)
RegisterError(currentDA)
Hesitate()
DisplayOff()
resultis res
]
SysDataFix = DataFix
DataFix = MyDataFix
let res = SweepDisk(disk, bufVec, DCreadLD)
DataFix = SysDataFix
resultis res
]
//----------------------------------------------------------------------------
and SweepDisk(disk, bufVec, action) = valof
//----------------------------------------------------------------------------
// bufVec is a pointer to nBufs data buffers. This code cycles through
// the buffers in such a way as to cause adjacent cylinders to transfer
// to or from different buffers. This maximizes data interference between
// adjacent tracks on the disk surface.
[
let savedTFSLeaveDisplay = TFSLeaveDisplay
TFSLeaveDisplay = true
DisplayOff()
let SweepDiskCleanup(disk, cb, cbz) be IncrementDA(disk, currentDA)
let v = vec 2; currentDA = v
Zero(currentDA, 2)
currentDA>>DA.sector = 1
let res = true
let cbz = vec CBzoneLength
TFSInitializeCbStorage(disk, cbz, 0, CBzoneLength, SDretry, lv SweepDiskErr)
cbz>>CBZ.cleanupRoutine = SweepDiskCleanup
SDretry:
[
let nextDA = vec 2
MoveBlock(nextDA, currentDA, 2)
if res then until nextDA!0 eq 0 & nextDA!1 eq 0 do
[ // Work done once per cylinder and before each retry
if UserAbort() then [ res = false; break ]
let buf = bufVec!(nextDA>>DA.track rem nBufs)
// This nonsense necessary to subvert TFSDoDiskCommand's label setup
let fileID = lv buf>>DL.fileId
disk>>TFSDSK.packID = buf>>DL.packID
let page = buf>>DL.pageNumber
[ // Work done once per page
let cb = TFSGetCb(disk, cbz)
MoveBlock(lv cb>>CB.diskAddress, nextDA, 2)
cb>>CB.AddrL = buf
TFSDoDiskCommand(disk, cb, buf, fillInDA, fileID, page, action)
IncrementDA(disk, nextDA)
] repeatuntil nextDA!1 eq 0
]
while cbz>>CBZ.head ne 0 do TFSGetCb(disk, cbz)
]
disk>>TFSDSK.packID = 0
DisplayOn()
TFSLeaveDisplay = savedTFSLeaveDisplay
resultis res
]
//----------------------------------------------------------------------------
and IncrementDA(disk, da) be
//----------------------------------------------------------------------------
[
da>>DA.sector = da>>DA.sector+1
if da>>DA.sector ge disk>>TFSDSK.nSectors then
[
da>>DA.sector = 0
da>>DA.head = da>>DA.head+1
if da>>DA.head ge disk>>TFSDSK.nHeads then
[
da>>DA.head = 0
da>>DA.track = da>>DA.track+1
if da>>DA.track ge disk>>TFSDSK.nTracks then
da>>DA.track = 0
]
]
]
//---------------------------------------------------------------------------
and SweepDiskErr(nil, cb, code) be
//---------------------------------------------------------------------------
[
DisplayOn()
let da = lv cb>>CB.diskAddress
PutTemplate(dsp, "*nUnrecoverable disk error at cyl $3D hd $2D sec $D",
da>>DA.track, da>>DA.head, da>>DA.sector)
RegisterError(da)
Hesitate()
DisplayOff()
]
//---------------------------------------------------------------------------
and TransferPage0(disk, buf, action) = valof
//---------------------------------------------------------------------------
// Transfers physical page 0 to or from the buffer, returning true
// if successful and false otherwise.
[
let DAs = vec 2
// Passing a DA of fillInDA causes TFSDoDiskCommand not to compute
// the real DA. Since the CB has been zeroed, a real DA of zero results.
DAs!0 = eofDA; DAs!1 = fillInDA; DAs!2 = eofDA
resultis ActOnDiskPages(disk, lv buf, DAs+1, table [ 0; 0; 0 ], 0, 0, action,
0, 0, 0, 0, 0, true) eq 0
]
//---------------------------------------------------------------------------
and RegisterError(da) be
//---------------------------------------------------------------------------
[
let i = 0
while i ls et>>ET.nEntries do
[
if DAsEqual(da, lv et>>ET.entry↑i.da) then
[ et>>ET.entry↑i.nErrors = et>>ET.entry↑i.nErrors+1; return ]
i = i+1
]
test i ls maxETEntries
ifso
[
MoveBlock(lv et>>ET.entry↑i.da, da, 2)
et>>ET.entry↑i.nErrors = 1
et>>ET.nEntries = et>>ET.nEntries+1
]
ifnot
Ws("*nError table full!")
]
//---------------------------------------------------------------------------
and DAsEqual(da1, da2) = da1!0 eq da2!0 & da1!1 eq da2!1
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
and DisplayOff() be [ saveDA = @DAstart; @DAstart = 0 ]
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
and DisplayOn() be @DAstart = saveDA
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
and Hesitate() be
//---------------------------------------------------------------------------
[
let t = @rtc+15
while t-@rtc ge 0 do loop
]
//---------------------------------------------------------------------------
and UserAbort() = valof
//---------------------------------------------------------------------------
[
unless Endofs(keys) do
[
DisplayOn()
Gets(keys)
Ws("[Command: ")
let c = Gets(keys)
Puts(dsp, c)
Ws("]")
DisplayOff()
resultis c eq $Q % c eq $q
]
resultis false
]
//---------------------------------------------------------------------------
and ListBadSpots(drive) be
//---------------------------------------------------------------------------
[
let disk = TFSInit(z, false, drive, 0, true)
if disk eq 0 then
[ PutTemplate(dsp, "Can't access drive $O", drive); return ]
BigDisplay()
let buf = vec TFSwordsPerPage
test TransferPage0(disk, buf, DCreadD) & buf>>BPL.seal eq bplSeal
ifso
test buf>>BPL.nBadPages eq 0
ifso Ws("*nThis pack has no known bad spots.")
ifnot for i = 0 to buf>>BPL.nBadPages-1 do
[
let da = lv buf>>BPL.da↑i
let fs = (da>>DA.track)/(disk>>TFSDSK.nVTracks)
disk>>TFSDSK.firstVTrack = (disk>>TFSDSK.nVTracks)*fs
let vda = VirtualDiskDA(disk, da)
PutTemplate(dsp, "*n Cyl $3D hd $2D sec $D = VDA $UD in FS $D",
da>>DA.track, da>>DA.head, da>>DA.sector, vda, fs)
]
ifnot
Ws("*nThis pack's bad spot table has not been initialized.")
TFSClose(disk)
SmallDisplay()
]
//---------------------------------------------------------------------------
and ResetBadSpots(drive) be
//---------------------------------------------------------------------------
[
let disk = TFSInit(z, false, drive, 0, true)
if disk eq 0 then
[ PutTemplate(dsp, "Can't access drive $O", drive); return ]
let buf = vec TFSwordsPerPage
Zero(buf, TFSwordsPerPage)
buf>>BPL.seal = bplSeal
TransferPage0(disk, buf, DCwriteD)
TFSClose(disk)
]