-- DiskIO.Mesa Edited by Sandman on June 11, 1980 3:28 PM -- Copyright Xerox Corporation 1979, 1980 DIRECTORY AltoDefs USING [BYTE, PageNumber, PageSize], AltoFileDefs USING [DISK, eofDA, fillinDA, SN, vDA, vDC], DiskDefs USING [ CB, CBinit, CBNil, CBptr, CBZ, CBZptr, DA, DC, DDC, DiskPageDesc, DiskRequest, DL, DS, DSfakeStatus, DSfreeStatus, DSgoodStatus, DSmaskStatus, FID, FreePageFID, InvalidDA, LastDiskAddress, lCBZ, nCB, NextDiskCommand, RetryCount, StandardDisk], InlineDefs USING [BITAND, COPY, DIVMOD], MiscDefs USING [], NucleusOps USING [], ProcessDefs USING [DisableInterrupts, EnableInterrupts], SegmentDefs USING [ DataSegmentAddress, DataSegmentHandle, DefaultMDSBase, DeleteDataSegment, MakeDataSegment]; DiskIO: PROGRAM IMPORTS InlineDefs, ProcessDefs, SegmentDefs EXPORTS DiskDefs, MiscDefs, NucleusOps SHARES DiskDefs = BEGIN OPEN DiskDefs; PageNumber: TYPE = AltoDefs.PageNumber; DISK: TYPE = AltoFileDefs.DISK; SN: TYPE = AltoFileDefs.SN; vDA: TYPE = AltoFileDefs.vDA; vDC: TYPE = AltoFileDefs.vDC; DC: TYPE = DiskDefs.DC; DS: TYPE = DiskDefs.DS; CBptr: TYPE = DiskDefs.CBptr; CBZptr: TYPE = DiskDefs.CBZptr; nil: POINTER = LOOPHOLE[0]; driveNumber: PUBLIC [0..1] _ 0; sysDisk: PUBLIC DISK _ StandardDisk; cbZone: ARRAY [0..lCBZ) OF UNSPECIFIED; zoneInUse: BOOLEAN _ FALSE; VirtualDA: PUBLIC PROCEDURE [da: DA] RETURNS [vDA] = BEGIN RETURN[ IF da = DA[0, 0, 0, 0, 0] THEN AltoFileDefs.eofDA ELSE vDA[ ((da.disk*sysDisk.tracks + da.track)*sysDisk.heads + da.head)*sysDisk.sectors + da.sector]]; END; RealDA: PUBLIC PROCEDURE [v: vDA] RETURNS [da: DA] = BEGIN i: CARDINAL _ v; da _ DA[0, 0, 0, 0, 0]; IF v # AltoFileDefs.eofDA THEN BEGIN [i, da.sector] _ InlineDefs.DIVMOD[i, sysDisk.sectors]; [i, da.head] _ InlineDefs.DIVMOD[i, sysDisk.heads]; [i, da.track] _ InlineDefs.DIVMOD[i, sysDisk.tracks]; [i, da.disk] _ InlineDefs.DIVMOD[i, sysDisk.disks]; IF i # 0 THEN da _ InvalidDA; END; RETURN END; -- Disk transfer "process" DCseal: AltoDefs.BYTE = 110B; DCs: ARRAY vDC OF DC = [DC[DCseal, DiskRead, DiskRead, DiskRead, 0, 0], -- ReadHLD DC[DCseal, DiskCheck, DiskRead, DiskRead, 0, 0], -- ReadLD DC[DCseal, DiskCheck, DiskCheck, DiskRead, 0, 0], -- ReadD DC[DCseal, DiskWrite, DiskWrite, DiskWrite, 0, 0], -- WriteHLD DC[DCseal, DiskCheck, DiskWrite, DiskWrite, 0, 0], -- WriteLD DC[DCseal, DiskCheck, DiskCheck, DiskWrite, 0, 0], -- WriteD DC[DCseal, DiskCheck, DiskCheck, DiskCheck, 1, 0], -- SeekOnly DC[DCseal, DiskCheck, DiskCheck, DiskCheck, 0, 0]]; -- DoNothing -- DoDiskCommand assumes that the version number in a FID (and -- in an FP) will never be used (is always one). It further -- assumes that if fp is nil (zero), a FreePageFID was meant; -- this allows the rest of the world to use short (3 word) FPs. NonZeroWaitCell: WORD _ 1; waitCell: POINTER TO WORD _ @NonZeroWaitCell; ResetWaitCell: PUBLIC PROCEDURE = BEGIN waitCell _ @NonZeroWaitCell; END; SetWaitCell: PUBLIC PROCEDURE [p: POINTER TO WORD] RETURNS [preval: POINTER TO WORD] = BEGIN ProcessDefs.DisableInterrupts[]; preval _ waitCell; waitCell _ p; ProcessDefs.EnableInterrupts[]; RETURN; END; DoDiskCommand: PUBLIC PROCEDURE [arg: POINTER TO DiskDefs.DDC] = BEGIN OPEN arg; ptr, next, prev: CBptr; la: POINTER TO DL; zone: CBZptr = cb.zone; cb.headerAddress _ @cb.header; IF (la _ cb.labelAddress) = nil THEN cb.labelAddress _ la _ @cb.label; cb.dataAddress _ ca; IF cb.normalWakeups = 0 THEN cb.normalWakeups _ zone.normalWakeups; IF cb.errorWakeups = 0 THEN cb.errorWakeups _ zone.errorWakeups; IF fp = nil THEN la.fileID _ FreePageFID ELSE la.fileID _ FID[1, fp.serial]; la.page _ cb.page _ page; IF da # AltoFileDefs.fillinDA THEN cb.header.diskAddress _ RealDA[da]; IF restore THEN cb.header.diskAddress.restore _ 1; cb.command _ DCs[action]; cb.command.exchange _ driveNumber; prev _ PrevCB[zone]; -- Put the command on the disk controller's queue UNTIL waitCell^ # 0 DO NULL ENDLOOP; -- Wait for Trident to finish ProcessDefs.DisableInterrupts[]; IF (next _ NextDiskCommand^) # CBNil THEN BEGIN DO ptr _ next; next _ ptr.nextCB; IF next = CBNil THEN EXIT; ENDLOOP; ptr.nextCB _ cb; END; -- Take care of a possible race with disk controller. The disk -- may have gone idle (perhaps due to an error) even as we were -- adding a command to the chain. To make sure there was no -- error, we check the status of the previous cb in this zone. IF NextDiskCommand^ = CBNil THEN SELECT MaskDS[prev.status, DSmaskStatus] FROM DSfreeStatus, DSgoodStatus => NextDiskCommand^ _ cb; ENDCASE; ProcessDefs.EnableInterrupts[]; EnqueueCB[zone, cb]; RETURN END; -- Disk command block queue InitializeCBstorage: PUBLIC PROCEDURE [ zone: CBZptr, nCBs: CARDINAL, page: PageNumber, init: CBinit] = BEGIN cb: CBptr; i: CARDINAL; nq: CARDINAL = nCBs + 1; length: CARDINAL = SIZE[CBZ] + nCBs*(SIZE[CB] + SIZE[CBptr]); queue: DESCRIPTOR FOR ARRAY OF CBptr _ DESCRIPTOR[@zone.queueVec, nq]; cbVector: DESCRIPTOR FOR ARRAY OF CB _ DESCRIPTOR[ @zone.queueVec + SIZE[CBptr]*nq, nCBs]; IF init = clear THEN Zero[zone, length]; zone.currentPage _ page; zone.cbQueue _ queue; zone.qTail _ 0; zone.qHead _ 1; queue[0] _ NIL; -- end of queue; FOR i IN [1..nCBs] DO queue[i] _ cb _ @cbVector[i - 1]; cb.zone _ zone; cb.status _ DSfreeStatus; ENDLOOP; RETURN END; NumCBs: PROCEDURE [zone: CBZptr] RETURNS [CARDINAL] = BEGIN RETURN[LENGTH[zone.cbQueue] - 1] END; ClearCB: PROCEDURE [cb: CBptr] = BEGIN zone: CBZptr = cb.zone; Zero[cb, SIZE[CB]]; cb.zone _ zone; RETURN END; EnqueueCB: PROCEDURE [zone: CBZptr, cb: CBptr] = BEGIN i: CARDINAL _ zone.qTail; IF zone.cbQueue[i] # NIL THEN ERROR; zone.cbQueue[i] _ cb; IF (i _ i + 1) = LENGTH[zone.cbQueue] THEN i _ 0; zone.qTail _ i; RETURN END; DequeueCB: PROCEDURE [zone: CBZptr] RETURNS [cb: CBptr] = BEGIN i: CARDINAL _ zone.qHead; IF (cb _ zone.cbQueue[i]) = NIL THEN ERROR; zone.cbQueue[i] _ NIL; IF (i _ i + 1) = LENGTH[zone.cbQueue] THEN i _ 0; zone.qHead _ i; RETURN END; PrevCB: PROCEDURE [zone: CBZptr] RETURNS [cb: CBptr] = BEGIN i: CARDINAL _ zone.qTail; i _ (IF i = 0 THEN LENGTH[zone.cbQueue] ELSE i) - 1; IF (cb _ zone.cbQueue[i]) = NIL THEN ERROR; RETURN END; CleanupCBqueue: PUBLIC PROCEDURE [zone: CBZptr] = BEGIN cb: CBptr; UNTIL zone.cbQueue[zone.qHead] = NIL DO cb _ GetCB[zone, dontClear]; ENDLOOP; RETURN END; -- Removing CBs from the queue. If for some reason the disk has -- gone idle without executing the command, we fake an error -- in it so that the entire zone of CBs will get retryed. RetryableDiskError: PUBLIC SIGNAL [cb: CBptr] = CODE; UnrecoverableDiskError: PUBLIC SIGNAL [cb: CBptr] = CODE; MaskDS: PROCEDURE [DS, DS] RETURNS [DS] = LOOPHOLE[InlineDefs.BITAND]; GetCB: PUBLIC PROCEDURE [zone: CBZptr, init: CBinit] RETURNS [cb: CBptr] = BEGIN s: DS; da: vDA; ddc: DDC; ec: CARDINAL; cb _ DequeueCB[zone]; UNTIL cb.status.done # 0 DO -- not zero means done or fake or free IF NextDiskCommand^ = CBNil AND cb.status.done = 0 THEN cb.status _ DSfakeStatus; ENDLOOP; cb.command.seal _ 0; -- remove command seal s _ MaskDS[cb.status, DSmaskStatus]; SELECT s FROM DSgoodStatus => BEGIN IF cb.header.diskAddress.restore = 0 THEN BEGIN zone.errorCount _ 0; zone.checkError _ FALSE; zone.currentBytes _ cb.labelAddress.bytes; IF zone.cleanup # LOOPHOLE[0] THEN zone.cleanup[cb]; END; IF init = clear THEN ClearCB[cb]; END; DSfreeStatus => ClearCB[cb]; -- really means DSneverBeenUsed ENDCASE => BEGIN -- some error occurred -- busy wait until disk controller is idle UNTIL NextDiskCommand^ = CBNil DO NULL ENDLOOP; IF s.dataLate = 0 THEN zone.errorCount _ zone.errorCount + 1; ec _ zone.errorCount; IF ec >= RetryCount THEN ERROR UnrecoverableDiskError[cb]; da _ zone.errorDA _ VirtualDA[cb.header.diskAddress]; IF cb.status.finalStatus = CheckError THEN zone.checkError _ TRUE; InitializeCBstorage[zone, NumCBs[zone], cb.page, dontClear]; IF ec > RetryCount/2 THEN BEGIN -- start a restore before signalling the error LastDiskAddress^ _ InvalidDA; ddc _ DDC[ cb: GetCB[zone, clear], ca: nil, da: [0], page: cb.page, fp: NIL, restore: TRUE, action: SeekOnly]; DoDiskCommand[@ddc]; END; ERROR RetryableDiskError[cb]; END; RETURN END; -- Don't all Cleanup procedures need to be locked? -- Segment swapper -- Note that each CB is used twice: first to hold the disk label -- for page i-1, and then to hold the DCB for page i. It isn't -- reused until the DCB for page i-1 is correctly done, which -- is guaranteed to be after the disk label for page i-1 is no -- longer needed, since things are done strictly sequentially by -- page number. -- Currently, DiskRequest.lastAction is not used by SwapPages. DiskCheckError: PUBLIC SIGNAL [page: PageNumber] = CODE; SwapPages: PUBLIC PROCEDURE [arg: POINTER TO swap DiskDefs.DiskRequest] RETURNS [PageNumber, CARDINAL] = BEGIN OPEN SegmentDefs, arg; i: PageNumber; cb, nextcb: CBptr; seg: DataSegmentHandle _ NIL; zone: CBZptr; ddc: DiskDefs.DDC _ DiskDefs.DDC[, ca, da^, , fp, FALSE, action]; IF zoneInUse THEN { seg _ MakeDataSegment[DefaultMDSBase, 1, [hard, topdown, table]]; zone _ DataSegmentAddress[seg]} ELSE {zoneInUse _ TRUE; zone _ @cbZone[0]}; InitializeCBstorage[zone, nCB, firstPage, clear]; IF desc # NIL THEN BEGIN zone.info _ desc; zone.cleanup _ GetDiskPageDesc; END; BEGIN ENABLE { RetryableDiskError --[cb]-- => { ddc.da _ zone.errorDA; ddc.ca _ cb.dataAddress; RETRY}; UNWIND => IF seg # NIL THEN DeleteDataSegment[seg] ELSE zoneInUse _ FALSE}; cb _ GetCB[zone, clear]; FOR i _ zone.currentPage, i + 1 UNTIL i = lastPage + 1 DO IF ddc.da = AltoFileDefs.eofDA THEN EXIT; IF signalCheckError AND zone.checkError AND zone.errorCount = RetryCount/2 THEN SIGNAL DiskCheckError[i]; nextcb _ GetCB[zone, clear]; cb.labelAddress _ LOOPHOLE[@nextcb.header.diskAddress]; ddc.cb _ cb; ddc.page _ i; IF i # zone.currentPage THEN ddc.da _ AltoFileDefs.fillinDA; DoDiskCommand[@ddc]; IF ~fixedCA THEN ddc.ca _ ddc.ca + AltoDefs.PageSize; cb _ nextcb; ENDLOOP; CleanupCBqueue[zone]; END; -- of enable block IF seg # NIL THEN DeleteDataSegment[seg] ELSE zoneInUse _ FALSE; RETURN[i - 1, zone.currentBytes] END; GetDiskPageDesc: PROCEDURE [cb: CBptr] = BEGIN la: POINTER TO DL = cb.labelAddress; desc: POINTER TO DiskPageDesc _ cb.zone.info; desc^ _ DiskPageDesc[ VirtualDA[la.prev], VirtualDA[cb.header.diskAddress], VirtualDA[la.next], la.page, la.bytes]; RETURN END; Zero: PUBLIC PROCEDURE [p: POINTER, l: CARDINAL] = BEGIN IF l = 0 THEN RETURN; p^ _ 0; InlineDefs.COPY[from: p, to: p + 1, nwords: l - 1]; RETURN END; END..