// 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) ]