-- 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..