// TfsBase.Bcpl
// Copyright Xerox Corporation 1979, 1980, 1981
// Last modified July 9, 1981 1:23 PM by Taft
get "Altofilesys.d"
get "Disks.d"
get "Tfs.d"
compileif newname debug then [ manifest [ debug = false ]]
compileif newname saveregs then [ manifest [ saveregs = false ]]
external
[
// procedures defined here
TFSInitializeCbStorage
TFSDoDiskCommand
TFSGetCb
TFSActOnPages
DefaultTFSErrorRtn
DoRecovery
TFSWaitQuiet
TFSNonEx
DataFix
// procedures defined elsewhere
TFSIncrement
TFSModShift
VirtualDiskDA
RealDiskDA
Zero
MoveBlock
CallSwat
SysErr
DisableInterrupts
EnableInterrupts
ReturnTo
DefaultArgs
StartIO
Idle
Noop
SaveRegs // iff saveregs compile-time switch = true
// statics defined here
TFSLeaveDisplay
TFSSavedDisplay
TFSLock
TFSDebug
]
static // statics defined here
[
TFSLeaveDisplay = false // nonzero to leave display on during transfers
TFSSavedDisplay = -1 // saved display list head (-1 => none)
TFSLock = 0 // nonzero to lock out new commands
TFSDebug = debug
]
manifest RTC = #430
compileif debug then
[
static
[
TFSErrorRate // simulated error rate, measured in 64ths
TFSRestoreRate // simulated restore rate, measured in 64ths
]
//----------------------------------------------------------------------------
let RecordTFS(a,b,c) be
//----------------------------------------------------------------------------
// Kludge for recording errors. Expects to find #645 zero (for
// no error recording) or a pointer to a DebugData structure (see below).
// Records a,b in ring buffer (unless a=0, in which case b=disk, c=CB,
// and what is recorded is 0, VDA of CB).
// Then, if a=1, finds or creates status entry whose word 0 is equal to b
// and increments words 1 and 2 (double-precision counter).
// Unused entries are indicated by word 0 = -1.
// Current interpretations:
// a b
// 0 vda Command completed (TFSGetCb)
// 1 status Error occurred (DoRecovery)
// 2 command Command issued (DoRecovery)
// #1xx vda Command issued, xx = actNumber (TFSDoDiskCommand)
[
structure DebugData:
[
pNextRing word // -> next entry to use in ring buffer
pEndRing word // -> first word beyond end of ring buffer
pBeginRing word // -> first word of ring buffer
pBeginStatus word // -> beginning of block for 3-word status entries
pEndStatus word // -> first word beyond end of status entries
]
if @#645 ne 0 then
[
let p = @#645
if a eq 0 then b = VirtualDiskDA(b, lv c>>CB.diskAddress)
let s = p>>DebugData.pNextRing
s!0 = a; s!1 = b
s = s+2
if s eq p>>DebugData.pEndRing then s = p>>DebugData.pBeginRing
p>>DebugData.pNextRing = s
if a eq 1 then
[
s = p>>DebugData.pBeginStatus
until s eq p>>DebugData.pEndStatus do
[
if s!0 eq -1 then [ Zero(s+1, 2); s!0 = b ]
if b eq s!0 then [ TFSIncrement(s+1); break ]
s = s+3
]
]
]
]
] // compileif debug
// TFSRealDA, TFSVirtualDA, and TFSIncrement are defined in TfsA.asm
//----------------------------------------------------------------------------
let TFSInitializeCbStorage(disk, cbz, firstPage, length, retry, errorRtn;
numargs na) be
//----------------------------------------------------------------------------
// Init the cbz such that subsequently it can be used for
// TFS disk transfers.
[
if na ge 4 then
[
Zero(cbz, length)
cbz>>CBZ.length = length
cbz>>CBZ.errorRtn = na ge 6 & errorRtn? errorRtn, lv DefaultTFSErrorRtn
cbz>>CBZ.retry = retry
cbz>>CBZ.cleanupRoutine = Noop
]
cbz>>CBZ.disk = disk
cbz>>CBZ.currentPage = firstPage
cbz>>CBZ.queueHead = lv cbz>>CBZ.head // for backward compatibility
let cb = lv cbz>>TFSCBZ.CBs
cbz>>CBZ.head = cb
[
cbz>>CBZ.tail = cb
cb>>CB.cbz = cbz
cb>>CB.StatusH = dstFree
cb = cb+lCB
if cb+lCB gr cbz+cbz>>CBZ.length then cb = 0
cbz>>CBZ.tail>>CB.nextCB = cb
] repeatuntil cb eq 0
]
//----------------------------------------------------------------------------
and TFSDoDiskCommand(disk, cb, CA, DA, fp, pageNumber, action,
nextCb; numargs na) be
//----------------------------------------------------------------------------
// Expects command and label to both be zeroed on entry, or
// otherwise appropriately initialized
[
let cbz = cb>>CB.cbz
// Resurrect //* lines if interrupts ever implemented and used
//* if cb>>CB.normalWakeups eq 0 then
//* cb>>CB.normalWakeups = cbz>>CBZ.normalWakeups
//* if cb>>CB.errorWakeups eq 0 then
//* cb>>CB.normalWakeups = cbz>>CBZ.errorWakeups
// Setup header block part of sector transfer
cb>>CB.AddrH = lv (cb>>CB.diskAddress) // in front of this KCB
cb>>CB.CountH = lDH
// Setup label block part of sector transfer
if cb>>CB.AddrL eq 0 then // caller may want label to go elsewhere
cb>>CB.AddrL = na ge 8 & nextCb? lv nextCb>>CB.label+lDH, lv cb>>CB.label
cb>>CB.CountL = lDL
// Setup data block part of sector transfer
cb>>CB.AddrD = CA
cb>>CB.CountD = TFSwordsPerPage
// Setup for Label compare
MoveBlock(lv (cb>>CB.AddrL>>DL.fileId), fp, lFID) // FID part
cb>>CB.AddrL>>DL.packID = disk>>TFSDSK.packID
cb>>CB.AddrL>>DL.pageNumber = pageNumber
cb>>CB.truePageNumber = pageNumber
// Possibly put in the disk address for this command. DA eq fillinDA means
// it is already set up, or will be filled in from the previous label transfer
if DA ne fillInDA then RealDiskDA(disk, DA, lv cb>>CB.diskAddress)
cb>>CB.vDiskAddress = DA
// Fill in the actual disk action for each block of the sector
let actNumber = action-diskMagic
if actNumber ugr 10 then SysErr(action, ecBadAction)
cb>>CB.CommH = (table
[
diskRead ; diskCheck ; diskCheck ; diskWrite ; diskCheck ;
diskCheck ; diskNoop; diskNoop; diskCheck; diskCheck; diskCheck
])!actNumber
cb>>CB.CommL = (table
[
diskRead ; diskRead ; diskCheck ; diskWrite ; diskWrite ;
diskCheck ; diskNoop; diskNoop; diskRead; diskCheck; diskWrite
])!actNumber
cb>>CB.CommD = (table
[
diskRead ; diskRead ; diskRead ; diskWrite ; diskWrite ;
diskWrite ; 0; 0; 0; 0; 0
])!actNumber
// TFSDoDiskCommand (cont'd)
// Fill in the drive number
cb>>CB.drive = disk>>TFSDSK.driveNumber
// Wait for interlocked activity (DoRecovery, TFSTryDisk, etc.) to complete
while TFSLock ne 0 do Idle()
// Turn off the display, if not already off
if TFSSavedDisplay eq -1 then
[
TFSSavedDisplay = @DAstart
unless TFSLeaveDisplay do @DAstart = 0
]
// Fill in the command seal
cb>>CB.ID = dcbID
// Enqueue the command
compileif debug then [ RecordTFS(#100+actNumber, DA) ]
//* DisableInterrupts()
let p = KBLK>>KBLK.ptr // chase down chain
if p ne 0 then
[
until p>>KCB.nextKCB eq 0 do p = p>>KCB.nextKCB
p>>KCB.nextKCB = lv cb>>CB.diskAddress
]
if KBLK>>KBLK.ptr eq 0 then KBLK>>KBLK.ptr = lv cb>>CB.diskAddress
//* EnableInterrupts()
// Put this CB back on the available queue
cb>>CB.nextCB = 0
test cbz>>CBZ.head eq 0
ifso cbz>>CBZ.head = cb
ifnot cbz>>CBZ.tail>>CB.nextCB = cb
cbz>>CBZ.tail = cb
]
//----------------------------------------------------------------------------
and TFSGetCb(disk, cbz, dontClear, returnIfNoCb; numargs na) = valof
//----------------------------------------------------------------------------
[
// Dequeue next CB from CBZ
let cb = cbz>>CBZ.head
if cb eq 0 then
[
if na ge 4 & returnIfNoCb resultis 0
SysErr(cbz, ecTfsQueue) //there should be one
]
cbz>>CBZ.head = cb>>CB.nextCB
// If header status is dstFree, cb is usable
if cb>>CB.StatusH eq dstFree then [ Zero(cb, lVarCB); resultis cb ]
// Many ways to return a good cb
[ //repeat (gives us something to break out of)
// Following code depends on the fact that label is always transferred,
// although the data field may not be (DCreadLnD)
let lvStatus = cb>>CB.CommD eq 0? lv cb>>CB.StatusL, lv cb>>CB.StatusD
// Here is the main place to wait for command completion:
if @lvStatus eq 0 then
[
if na ge 4 & returnIfNoCb then // Put cb back on queue and return 0
[ cbz>>CBZ.head = cb; resultis 0 ]
let inTime = @RTC + 5*(1000/39) // Prepare a timer (5 seconds)
[ // Wait for command to complete or time out
Idle()
if @lvStatus ne 0 then break // command completed
if KBLK>>KBLK.ptr eq 0 & cb>>CB.ID eq dcbID then
// controller went idle without even starting this command
[ @lvStatus = dstForgotten % dstDone; break ]
if @RTC-inTime gr 0 then
// command timed out and controller seems to be hung up
[ @lvStatus = dstTimeout % dstDone; break ]
] repeat
]
compileif debug then
[
// If status is not set, it must mean that the command aborted,
// or the read task committed suicide, or sector pulses are not there.
if (@lvStatus & (dstForgotten % dstTimeout)) ne 0 then
[
let saveItNow = vec lVarCB
MoveBlock(saveItNow, cb, lVarCB)
compileif saveregs then [ SaveRegs() ]
if KBLK>>KBLK.aborted eq 0 then CallSwat("dead")
]
let ErrorRan = nil
if KBLK>>KBLK.aborted then @#22 = @#22+1
if TFSErrorRate then
[
let lb = (table [ #61003; #121000; #1401 ] )() //RCLK
ErrorRan = (lb rshift 6)M // make 6-bit quantity
if ErrorRan ls TFSErrorRate then cb>>CB.StatusH = dstTimeout
]
RecordTFS(0, disk, cb) //Record completion of command
]
// Merge together status for all blocks
let errorStatus = (cb>>CB.StatusD % cb>>CB.StatusL % cb>>CB.StatusH) &
dstErrors // mask out non-errors
// Most successful transfers exit here:
if errorStatus eq 0 then break
// TFSGetCb (cont'd)
// Must reset the disk after each error -- but don't tamper with zone.
// Note that other disk activity can be taking place (e.g., we might
// just have an ECC error and can fix it without flushing remainder
// of command block chain).
compileif debug then [ RecordTFS(1, errorStatus) ]
DoRecovery(disk, diskReset, cbz>>CBZ.errorRtn)
// Retry data-late and "forgotten" errors indefinitely, without any
// other recovery actions, and without counting them as errors.
if (errorStatus & dstRetryIndefinitely) eq 0 then
[
cbz>>CBZ.errorCount = cbz>>CBZ.errorCount+1
let errorCount = cbz>>CBZ.errorCount
// Disk is now quiet. Now do error reporting and recovery.
// Report a check error not accompanied by other errors only after
// it has been retried at least once -- because EOF is sometimes
// detected by slamming into page 0 and getting a check error.
unless errorCount eq 1 & errorStatus eq dstCompErr do
[
compileif debug then
[
if TFSErrorRate ne 0 & ErrorRan ls TFSRestoreRate then
errorCount = (disk>>DSK.retryCount rshift 1)+1
]
TFSIncrement(lv disk>>TFSDSK.nErrors)
let block = lv cb>>CB.CommH
errorStatus = 0
[ // repeat for each block
// Attempt ECC only if there were no other errors in this block.
// This includes check errors, since the first two words of a
// checked block are not stored in memory and therefore it is
// hopeless to correct an ECC error in a checked block.
// Also, do not invoke ECC until we have retried at least
// 4 times, to reduce the risk of false ECC correction
// on transient data errors.
let status = block>>KCBblock.Status & dstErrors
if status eq dstECCerror then
[
TFSIncrement(lv disk>>TFSDSK.nECCErrors)
if errorCount ge 4 & DataFix(block) eq -1 then
[ status = 0; TFSIncrement(lv disk>>TFSDSK.nECCFixes) ]
]
errorStatus = errorStatus % status
block = block+(size KCBblock/16)
] repeatwhile block ule (lv cb>>CB.CommD)
if errorStatus eq 0 then break //successful ECC fix
]
// Turn display back on now, since error routine might never return
if TFSSavedDisplay ne -1 then
[ @DAstart = TFSSavedDisplay; TFSSavedDisplay = -1 ]
// TFSGetCb (cont'd)
cbz>>CBZ.errorDA = cb>>CB.vDiskAddress
if errorCount ge disk>>DSK.retryCount then
[ // Non-recoverable error
TFSIncrement(lv disk>>TFSDSK.nUnRecov)
(@cbz>>CBZ.errorRtn)(cbz>>CBZ.errorRtn, cb, ecUnRecovDiskError)
break //Let remainder of transfers proceed
]
// If more than 8 errors, do restore before trying again.
if errorCount gr (disk>>DSK.retryCount rshift 1) then
[
// Check for read-only error. We do this after 8 retries
// (rather than immediately) because the hardware doesn't provide
// an unequivocal "tried to write when read-only" indication.
// Also, we must do a restore after the error routine returns in
// order to force the drive to notice the new state of the switch.
// Note that the ReadOnly bit has been masked out in errorStatus,
// but the status stored on top of the ID contains the true state
// of the ReadOnly switch.
if (cb>>CB.ID)<<DST.ReadOnly & errorStatus<<DST.DeviceCk then
(@cbz>>CBZ.errorRtn)(cbz>>CBZ.errorRtn, cb, ecReadOnly)
TFSIncrement(lv disk>>TFSDSK.nRestores)
DoRecovery(disk, diskRestore, cbz>>CBZ.errorRtn)
]
]
// Initialize things again
TFSInitializeCbStorage(disk, cbz, cb>>CB.truePageNumber)
ReturnTo(cbz>>CBZ.retry)
] repeat
// Turn display back on if disk now idle
if KBLK>>KBLK.ptr eq 0 & TFSSavedDisplay ne -1 then
[ @DAstart = TFSSavedDisplay; TFSSavedDisplay = -1 ]
// Good cb from previous transfer, ready to return
TFSIncrement(lv disk>>TFSDSK.nTransfers)
cbz>>CBZ.nextDA = VirtualDiskDA(disk, lv cb>>CB.AddrL>>DL.next)
cbz>>CBZ.currentNumChars = cb>>CB.AddrL>>DL.numChars
cbz>>CBZ.errorCount = 0
cbz>>CBZ.cleanupRoutine(disk, cb, cbz)
unless ((na ge 3) & dontClear) do Zero(cb, lVarCB)
resultis cb
]
//----------------------------------------------------------------------------
and DoRecovery(disk, command, errorRtn; numargs na) be
//----------------------------------------------------------------------------
// Recovery code for many purposes. Called from TFSGetCb and TFSInit
// The coaxing operation is performed if a command times out. It may
// be that we have inadvertently selected a non-existent drive.
// Also, we must handle the case in which the
// presently-selected drive has been taken off line, and no more
// sector pulses are arriving (Roger did not put the one-shot
// sector-pulse impersonator in his interface that McCreight did
// in his!). So be prepared to give some "fake" sector pulses via StartIO.
// Also, there is a bug in the controller such that if you issue a Read
// and the drive doesn't send you any data (e.g., because it's in select
// lock or the pack has been DC-erased), the controller gets hung up
// waiting for the sync bit. The only safe way to get out of this state is
// to reset the controller, turn it back on, and issue a diskReset.
[
if na ls 3 then errorRtn = lv DefaultTFSErrorRtn
let coax=false
let kcb = vec lKCB
while TFSLock ne 0 do Idle()
TFSLock = disk
TFSWaitQuiet(false)
let retryCount = 0
[ // repeat until we succeed in making all errors go away
if coax then StartIO(#20) // reset controller
retryCount = retryCount+1
if (retryCount & 17B) eq 0 then
[
compileif saveregs then [ SaveRegs() ]
(@errorRtn)(errorRtn, disk>>DSK.driveNumber, ecDriveHung)
]
Zero(kcb, lKCB)
kcb>>KCB.ID = dcbID
kcb>>KCB.track = -1
kcb>>KCB.drive = disk>>DSK.driveNumber
kcb>>KCB.CommH = command
KBLK>>KBLK.track = -1 // Force microcode to forget cylinder address
KBLK>>KBLK.drive = disk>>DSK.driveNumber % 100000B // Force drive select
KBLK>>KBLK.aborted = 0
compileif debug then [ RecordTFS(2, command) ]
KBLK>>KBLK.ptr = kcb
if coax then StartIO(#40) // start controller
TFSWaitQuiet(command eq diskRestore)
// If we timed out, perhaps sector pulses have gone away.
// Issue the command more forcefully next time around.
test kcb>>KCB.StatusH eq 0 % kcb>>KCB.ID eq dcbID
ifso coax = true
ifnot
[
// NotReady in KCB happens normally if command eq diskRestore.
manifest NotReady = 100000B rshift offset DST.NotReady
let status = KBLK>>KBLK.Status % (kcb>>KCB.StatusH & not NotReady)
// Consider recovery successful if command executed without
// error in non-coax mode.
if status<<DST.Errors eq 0 & not coax then break
coax = false
// Certain errors sometimes require a restore to reset.
// If we didn't succeed in resetting one, try a restore next time.
if (status & dstRestore) ne 0 then command = diskRestore
]
] repeat
TFSLock = 0
]
//----------------------------------------------------------------------------
and TFSWaitQuiet(awaitIndex) be
//----------------------------------------------------------------------------
// Wait until disk is thoroughly idle. Evidence for idle is:
// 1 - KBLK.ptr=0, i.e. no commands remain
// 2 - KBLK.Sector is counting, i.e. not in a write command,
// and read task has had time to finish.
// Also we should note that after a Restore is executed,
// it takes a while for sector pulses to start arriving again,
// and we must await an index mark because the sector count may have
// gotten out of sync.
[
let stage = 0
let lastPtr = -1
let timer = nil
let sector = nil
[ // repeat
switchon stage into
[
case 0:
// Wait for cb queue to empty, but time out if a single command
// stays stuck on the queue for more than 500 ms.
if KBLK>>KBLK.ptr ne lastPtr then
[ lastPtr = KBLK>>KBLK.ptr; timer = @RTC + 500/39 ]
if KBLK>>KBLK.ptr eq 0 then stage = 1
endcase
case 1:
// Wait for restore to complete
unless awaitIndex & KBLK>>KBLK.NotReady do
[ sector = KBLK>>KBLK.Sector; timer = @RTC+1; stage = 2 ]
endcase
case 2:
// Wait for sector number to advance, but time out after
// 39 ms (two rotations).
if KBLK>>KBLK.Sector ne sector then
[ unless awaitIndex break; stage = 3 ]
endcase
case 3:
// If did restore, wait for index mark (sector 0)
if KBLK>>KBLK.Sector eq 0 break
endcase
]
Idle()
] repeatuntil @RTC-timer gr 0
KBLK>>KBLK.ptr = 0 // in case microcode didn't do anything
]
//----------------------------------------------------------------------------
and DefaultTFSErrorRtn(lvErrorRtn, cb, ec) be SysErr(0, ec, cb)
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
and TFSNonEx(disk) be SysErr(disk, ecNoCreationAbility)
//----------------------------------------------------------------------------
// What happens if you try to create or delete files when initmode was 0
//----------------------------------------------------------------------------
and DataFix(block) = valof
//----------------------------------------------------------------------------
// ECC fixer. Argument is a "block" of the disk command.
// Returns: -1 if everything fixed correctly.
// ...otherwise... a number indicating where the ECC gave up.
[
manifest LCM = 21*2047
let sink = vec 1
let rem0 = block>>KCBblock.ECC0 & #37
let rem1 = block>>KCBblock.ECC1
let S0,S1 = 0,nil
let Dx,D,Dbits,p,data,mask = nil,nil,nil,nil,nil,nil
[ // repeat
if (rem1 & #1777) eq 0 then break
let msb = rem0 rshift 4
rem0 = ((rem0 lshift 1) & #37) + (rem1 rshift 15)
rem1 = (rem1 lshift 1) + msb
S0 = S0 + 1
if S0 ge 21 then resultis 1
] repeat
rem0 = rem0 lshift 6 + rem1 rshift 10
rem1 = block>>KCBblock.ECC0 rshift 5
if rem1 eq 0 % rem0 eq 0 then resultis 2
S1 = TFSModShift(rem1, rem0) + 11
if S1 gr 2047+11 then resultis 3
Dx = ((-19*S0 - 2*S1)+220*21) rem 21
//D = 2047*Dx - S1 + 2047 //I dont know why I must add 2047!!!
D = 2048*Dx - Dx - S1 + 2047 // D = 2047*Dx + 2047 - S1
//if D uge LCM then D = D - LCM // Fiala claims this can't happen
Dbits = D & #17
p = block>>KCBblock.Count - (D rshift 4)
data = block>>KCBblock.Addr
mask = rem0 rshift (16-Dbits)
for ptr = p to p+1 do
[
if mask ne 0 then
[
if ptr ls 0 resultis 4 //error outside of block
if ptr ls block>>KCBblock.Count then //error might be in ECC words
data!ptr = data!ptr xor mask
]
mask = rem0 lshift Dbits
]
resultis -1
]
// Microcode version shares S registers with the Read task.
// Therefore, call this only when disk is quiet.
//and TFSModShift(num, ref) = valof
// [
// let S1 = 0
// [
// if (#4000-S1 ls 0) % (num - ref eq 0) then break
// num = num lshift 1
// if (num & #4000) ne 0 then num = num xor #4005
// S1 = S1 + 1
// ] repeat
// resultis S1
// ]
//
//----------------------------------------------------------------------------
and TFSActOnPages(disk, CAs, DAs, fp, firstPage, lastPage, action,
lvNumChars, lastAction, fixedCA, cleanupRoutine, errorRtn,
returnOnCheckError, hintLastPage; numargs na) = valof
//----------------------------------------------------------------------------
// See ActOnDiskPages description in BFS section of O.S. manual.
// Returns the page number of the last page successfully acted on.
// CAs (core addresses) and DAs (disk addresses) are vectors
// indexed by page number (e.g. CAs!firstPage)
// the arguments following action are optional; if one of them is
// omitted or 0, the default action is taken
[
DefaultArgs(lv na, -7, lv na, action, 0, TFSDefaultCleanupRtn,
0, false, lastPage)
// Initialize for transfers
let result = nil
let cbz = vec CBzoneLength
TFSInitializeCbStorage(disk, cbz, firstPage, CBzoneLength, Aretry, errorRtn)
cbz>>CBZ.DAs = DAs
cbz>>CBZ.cleanupRoutine = cleanupRoutine
if hintLastPage-firstPage ugr lastPage-firstPage then
hintLastPage = lastPage // hintLastPage not in [firstPage..lastPage]
// Each cb is used twice:
// to hold the DL for page i-1, and
// to hold the KCB for page i.
// It isn't reused until the command for page i is done, and that is
// guaranteed to be after the DL for page i-1 is no longer needed,
// since everything is done strictly sequentially by page number.
// Note: if the hintLastPage looks reasonable and is less than lastPage,
// we transfer pages up to that point, then check to see whether the last
// page transferred really was the last page of the file. If so, we return
// without having caused the disk to seek to cylinder 0 as a result of
// chaining forward from the last page. If the hint was wrong, we have to
// queue up the remainder of the transfers; this costs an extra disk rotation.
// TFSActOnPages (cont'd)
Aretry:
[ // repeat
// Get a first cb
result = hintLastPage
let cb = TFSGetCb(disk, cbz)
let curFirstPage = cbz>>CBZ.currentPage
for pageNumber = curFirstPage to hintLastPage do
[
if DAs!pageNumber eq eofDA then // Last page has been fixed up
[ result = pageNumber-1; break ]
// Be very careful, if lastAction is different, to let first set of
// transfers, if any, finish and be retried if necessary. For example,
// if they are all writes, and lastAction is a read (into the same
// buffer), we must not queue the read until the write has completed
// and been checked. This is because the Trident (unlike the Diablo)
// does not stop executing commands when an error occurs, but rather
// continues racing down the command chain.
let thisCBaction = action
if pageNumber eq lastPage & thisCBaction ne lastAction then
[
if curFirstPage ne lastPage then
[ result = pageNumber-1; break ]
thisCBaction = lastAction
]
if thisCBaction eq DCdoNothing then loop
// Nonrecoverable error(s) check
if returnOnCheckError &
(cbz>>CBZ.errorCount eq disk>>DSK.retryCount rshift 1) then
resultis -(pageNumber+77B)
// If we are chaining, cause this command to fill in
// the disk address part of the next command
let nextCb = TFSGetCb(disk, cbz)
cb>>CB.AddrL = DAs!(pageNumber+1) eq fillInDA?
lv nextCb>>CB.label+lDH, lv nextCb>>CB.label
TFSDoDiskCommand(disk, cb, (fixedCA ne 0? fixedCA, CAs!pageNumber),
DAs!pageNumber, fp, pageNumber, thisCBaction)
cb = nextCb
]
while cbz>>CBZ.head ne 0 do TFSGetCb(disk, cbz)
if result eq lastPage % DAs!(result+1) eq eofDA then break
hintLastPage = lastPage // hint was wrong, restart transfer
TFSInitializeCbStorage(disk, cbz, result+1)
] repeat
// Cleanup
@lvNumChars = cbz>>CBZ.currentNumChars
resultis result
]
//----------------------------------------------------------------------------
and TFSDefaultCleanupRtn(disk, cb, cbz) be
//----------------------------------------------------------------------------
// The default cleanupRoutine substitutes the actual virtual DA for
// each instance of fillInDA in the DAs vector
[
let lvDA = lv ((cbz>>CBZ.DAs)!(cb>>CB.truePageNumber))
if lvDA!1 eq fillInDA then lvDA!1 = cbz>>CBZ.nextDA
if lvDA!(-1) eq fillInDA then
lvDA!(-1) = VirtualDiskDA(disk, lv (cb>>CB.AddrL>>DL.previous))
]