-- ReadDisk.Mesa Edited by HGM on February 4, 1981 5:14 PM DIRECTORY Inline USING [BITAND, BITSHIFT], Process USING [Yield], Storage USING [Node, Free], AltoDefs USING [BYTE, BytesPerPage, PageNumber, PageSize], AltoFileDefs USING [eofDA, fillinDA, FP, vDA, vDC], DiskDefs USING [ CB, CBinit, CBptr, CBZ, CBZptr, DA, DC, DDC, DiskCheckError, DL, DS, DSfakeStatus, DSfreeStatus, DSgoodStatus, DSmaskStatus, FID, InvalidDA, RealDA, RetryCount, UnrecoverableDiskError, VirtualDA], MiscDefs USING [Zero], ProcessDefs USING [CV, DisableInterrupts, EnableInterrupts, InterruptLevel], SegmentDefs USING [ DataSegmentAddress, DataSegmentHandle, HardUp, MakeDataSegment], SystemDefs USING [FreePages], ReadDefs, StatsDefs USING [StatCounterIndex, StatsStringToIndex, StatIncr]; ReadDisk: MONITOR IMPORTS Inline, Process, Storage, DiskDefs, MiscDefs, ProcessDefs, SegmentDefs, SystemDefs, StatsDefs EXPORTS ReadDefs SHARES DiskDefs = BEGIN OPEN DiskDefs; pagesRead: StatsDefs.StatCounterIndex; diskErrors: StatsDefs.StatCounterIndex; checkErrors: StatsDefs.StatCounterIndex; overruns: StatsDefs.StatCounterIndex; realWaits: StatsDefs.StatCounterIndex; diskFinished: CONDITION; wakeupLevel: ProcessDefs.InterruptLevel = 13; PageNumber: TYPE = AltoDefs.PageNumber; vDA: TYPE = AltoFileDefs.vDA; vDC: TYPE = AltoFileDefs.vDC; ZoneExtension: TYPE = RECORD [ currentPage: PageNumber, cas: POINTER TO ARRAY [0..0) OF POINTER, eof: BOOLEAN, nCBs: CARDINAL, prevcb, nextcb: CBptr, ddc: DDC]; ZEptr: TYPE = POINTER TO ZoneExtension; 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 nil: POINTER = LOOPHOLE[0]; nextDiskCommand: POINTER TO CBptr = LOOPHOLE[521B]; lastDiskAddress: POINTER TO DA = LOOPHOLE[523B]; -- 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. --FreePageFID: FID = FID[-1,SN[1,1,1,17777B,-1]]; DoDiskCommand: PROCEDURE [zone: CBZptr, ddc: POINTER TO DiskDefs.DDC] = BEGIN ze: ZEptr = zone.info; cb: CBptr = ddc.cb; la: POINTER TO DL; cb.headerAddress ← @cb.header; IF (la ← cb.labelAddress) = nil THEN cb.labelAddress ← la ← @cb.label; IF ddc.ca = NIL AND ddc.action # SeekOnly THEN BEGIN AttemptToSmashPageZero: ERROR = CODE; ERROR AttemptToSmashPageZero; END; cb.dataAddress ← ddc.ca; IF cb.normalWakeups = 0 THEN cb.normalWakeups ← zone.normalWakeups; IF cb.errorWakeups = 0 THEN cb.errorWakeups ← zone.errorWakeups; la.fileID ← FID[1, ddc.fp.serial]; la.page ← cb.page ← ddc.page; IF ddc.da # AltoFileDefs.fillinDA THEN cb.header.diskAddress ← RealDA[ddc.da]; IF ddc.restore THEN cb.header.diskAddress.restore ← 1; cb.command ← DCs[ddc.action]; ProcessDefs.DisableInterrupts[]; BEGIN ptr, next: CBptr; IF (next ← nextDiskCommand↑) # nil THEN BEGIN trap: CARDINAL ← 0; LongDiskChain: SIGNAL = CODE; DO ptr ← next; next ← ptr.nextCB; IF next = nil THEN EXIT; IF (trap ← trap + 1) > 100 THEN SIGNAL LongDiskChain; 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↑ = nil THEN BEGIN prevcb: CBptr = ze.prevcb; IF prevcb = NIL THEN nextDiskCommand↑ ← cb ELSE SELECT MaskDS[prevcb.status, DSmaskStatus] FROM DSfreeStatus, DSgoodStatus => nextDiskCommand↑ ← cb; ENDCASE; END; END; ProcessDefs.EnableInterrupts[]; EnqueueActiveCB[zone, cb]; ze.prevcb ← cb; RETURN END; InitializeCBstorage: 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]) + SIZE[ZoneExtension]; cbVector: DESCRIPTOR FOR ARRAY OF CB ← DESCRIPTOR[ @zone.queueVec + SIZE[CBptr]*nq, nCBs]; ze: ZEptr; IF init = clear THEN BEGIN wakeupBit: WORD = Inline.BITSHIFT[1, wakeupLevel]; MiscDefs.Zero[zone, length]; zone.info ← zone + length - SIZE[ZoneExtension]; ze ← zone.info; zone.normalWakeups ← zone.errorWakeups ← wakeupBit; END ELSE ze ← zone.info; zone.currentPage ← page; zone.cbQueue ← DESCRIPTOR[@zone.queueVec, nq]; zone.qHead ← 0; zone.qTail ← nCBs; FOR i IN [0..nCBs) DO zone.cbQueue[i] ← cb ← @cbVector[i]; cb.zone ← zone; cb.status ← DSfreeStatus; ENDLOOP; zone.cbQueue[nCBs] ← NIL; RETURN END; ClearCB: PROCEDURE [cb: CBptr] = BEGIN zone: CBZptr = cb.zone; MiscDefs.Zero[cb, SIZE[CB]]; cb.zone ← zone; RETURN END; EnqueueActiveCB: 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; DequeueActiveCB: 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; -- 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. MaskDS: PROCEDURE [DS, DS] RETURNS [DS] = LOOPHOLE[Inline.BITAND]; GetNextPage: PUBLIC ENTRY PROCEDURE [zone: CBZptr] RETURNS [cb: CBptr] = BEGIN s: DS; ze: ZEptr = zone.info; DO -- loop for error recovery IF ze.eof THEN ERROR DiskCheckError[LAST[PageNumber]]; cb ← DequeueActiveCB[zone]; UNTIL cb.status.done # 0 DO -- not zero means done or fake or free IF nextDiskCommand↑ = nil AND cb.status.done = 0 THEN cb.status ← DSfakeStatus ELSE BEGIN StatsDefs.StatIncr[realWaits]; WAIT diskFinished; END; 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; StatsDefs.StatIncr[pagesRead]; RETURN END; FillThisBuffer[cb]; END; ENDCASE => BEGIN -- some error occurred first: CARDINAL; ec: CARDINAL; UNTIL (nextDiskCommand↑ = nil) DO Process.Yield[]; ENDLOOP; StatsDefs.StatIncr[diskErrors]; IF cb.status.finalStatus = CheckError THEN StatsDefs.StatIncr[checkErrors]; IF cb.status.dataLate = 1 THEN StatsDefs.StatIncr[overruns]; ec ← zone.errorCount ← zone.errorCount + 1; IF ec >= RetryCount THEN ERROR UnrecoverableDiskError[cb]; IF ec = 1 THEN zone.errorDA ← VirtualDA[cb.header.diskAddress]; ze.currentPage ← cb.page; ze.prevcb ← ze.nextcb ← NIL; ze.ddc.da ← zone.errorDA; InitializeCBstorage[zone, ze.nCBs, cb.page, dontClear]; IF ec > RetryCount/2 THEN BEGIN -- start a restore ddc: DDC; lastDiskAddress↑ ← InvalidDA; cb ← DequeueActiveCB[zone]; ClearCB[cb]; ddc ← DDC[cb, nil, [0], ze.currentPage, NIL, TRUE, SeekOnly]; DoDiskCommand[zone, @ddc]; first ← 1; END ELSE first ← 0; FillBuffers[zone, first]; END; ENDLOOP; END; ReleasePage: PUBLIC PROCEDURE [cb: CBptr] = BEGIN zone: CBZptr ← cb.zone; ze: ZEptr = zone.info; IF cb.labelAddress.bytes # AltoDefs.BytesPerPage THEN ze.eof ← TRUE; IF ze.eof THEN RETURN; FillThisBuffer[cb]; RETURN END; StopReading: PUBLIC PROCEDURE [zone: CBZptr] = BEGIN ze: ZEptr = zone.info; UNTIL (nextDiskCommand↑ = nil) DO Process.Yield[]; ENDLOOP; ProcessDefs.CV[wakeupLevel] ← NIL; SystemDefs.FreePages[ze.cas[0]]; Storage.Free[zone]; END; -- Page Reader -- 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. StartReading: PUBLIC PROCEDURE [fp: POINTER TO AltoFileDefs.FP, nCBs: CARDINAL] RETURNS [zone: CBZptr] = BEGIN buffer: POINTER; cas: POINTER TO ARRAY [0..0) OF POINTER; ze: ZEptr; zone ← Storage.Node[ SIZE[CBZ] + nCBs*(SIZE[CBptr] + SIZE[CB]) + SIZE[ZoneExtension] + nCBs]; InitializeCBstorage[zone, nCBs, 0, clear]; ze ← zone.info; ze.cas ← cas ← LOOPHOLE[ze + SIZE[ZoneExtension]]; buffer ← AllocateBuffers[nCBs]; FOR i: CARDINAL IN [0..nCBs) DO cas[i] ← buffer; buffer ← buffer + AltoDefs.PageSize; ENDLOOP; ze.ddc ← [, , fp.leaderDA, , fp, FALSE, ReadD]; ze.currentPage ← 0; ze.nCBs ← nCBs; ProcessDefs.CV[wakeupLevel] ← @diskFinished; FillBuffers[zone, 0]; RETURN END; -- Allocate our buffer from low memory in order to minimize sandbaring problems. -- The recipe for trouble (using AllocateResidentPages) is as follows: -- 1) Start booting (allocates 5 pages as high as possible) -- 2) Allocate another page of frames or heap, this becomes the sandbar -- 3) Finish booting (free up 5 pages) leaving a 5 page hole -- 4) Allocate another page of frames/heap inside the 5 page hole. -- This leaves a 4 page hole. If we start booting again, we will use 5 more pages. AllocateBuffers: PROCEDURE [pages: CARDINAL] RETURNS [POINTER] = BEGIN OPEN SegmentDefs; seg: DataSegmentHandle ← MakeDataSegment[pages: pages,info: HardUp]; RETURN[DataSegmentAddress[seg]]; END; FillBuffers: PROCEDURE [zone: CBZptr, first: CARDINAL] = BEGIN ze: ZEptr = zone.info; FOR i: CARDINAL IN [first..ze.nCBs) DO cb: CBptr ← DequeueActiveCB[zone]; FillThisBuffer[cb]; ENDLOOP; END; FillThisBuffer: PROCEDURE [nextcb: CBptr] = BEGIN zone: CBZptr = nextcb.zone; ze: ZEptr = zone.info; thiscb: CBptr; pageNumber: PageNumber ← ze.currentPage; IF ze.ddc.da = AltoFileDefs.eofDA THEN ERROR DiskCheckError[pageNumber]; ClearCB[nextcb]; thiscb ← ze.nextcb; ze.nextcb ← nextcb; IF thiscb = NIL THEN RETURN; thiscb.labelAddress ← LOOPHOLE[@nextcb.header.diskAddress]; ze.ddc.cb ← thiscb; ze.ddc.page ← pageNumber; ze.ddc.ca ← ze.cas[pageNumber MOD ze.nCBs]; IF pageNumber # zone.currentPage THEN ze.ddc.da ← AltoFileDefs.fillinDA; ze.currentPage ← pageNumber + 1; DoDiskCommand[zone, @ze.ddc]; END; pagesRead ← StatsDefs.StatsStringToIndex["Disk pages read by Booter"]; diskErrors ← StatsDefs.StatsStringToIndex["Disk errors encountered by Booter"]; checkErrors ← StatsDefs.StatsStringToIndex[ "Disk Check errors encountered by Booter"]; overruns ← StatsDefs.StatsStringToIndex["Disk Overruns encountered by Booter"]; realWaits ← StatsDefs.StatsStringToIndex["Waits for disk"]; END..