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