-- File: DiskRequestor.mesa
-- Last edited by Levin: 30-Apr-81 16:21:09
DIRECTORY
AltoDefs USING [BytesPerPage, PageNumber],
ControlDefs USING [FrameHandle, NullFrame],
DiskIODefs USING [
CompletionProcedure, CompletionStatus, DiskRequest, eofvDA, FID,
fillInvDA, NormalCompletionProcedure, RequestError, RequestID, vDA,
VerboseCompletionProcedure],
DiskIOPrivate USING [
CB, CBPtr, DA, DCs, DL, DS, DSfakeStatus, DSgoodStatus, DSmaskStatus,
EnqueueCB, EnqueueForDisk, FreeCB, GetCBs,
InvalidDA, IOSynch, loggingEnabled, MaskDS, nSafetyDCBs, SimulateCompletion,
SpliceLists, SynchRecord, sysDisk, VirtualDA],
FrameOps USING [GetReturnLink, MyLocalFrame, SetReturnFrame],
Inline USING [DIVMOD],
ProcessDefs USING [Detach];
DiskRequestor: MONITOR LOCKS synch.LOCK USING synch: DiskIOPrivate.IOSynch
IMPORTS DiskIOPrivate, FrameOps, Inline, ProcessDefs
EXPORTS DiskIODefs, DiskIOPrivate =
BEGIN OPEN DiskIODefs, DiskIOPrivate;
-- Global Variables --
scratchPage: PUBLIC POINTER;
maxOps: PUBLIC CARDINAL;
-- Statistics Logging --
diskReads, diskWrites: PUBLIC LONG CARDINAL;
-- Miscellaneous Declarations --
BytesPerDiskPage: CARDINAL = AltoDefs.BytesPerPage;
notLastSkip: RequestID = 10101B;
lastSkip: RequestID = 50403B;
dummyID: RequestID = 112233B;
ImpossibleStatus: ERROR = CODE;
ImpossibleRequestID: ERROR = CODE;
-- Externally Visible Procedures --
InitiateDiskIO: PUBLIC PROCEDURE [operation: POINTER TO DiskRequest] =
-- initiates the requested disk transfers and returns to the caller. The
-- CompletionProcedure in the argument DiskRequest is invoked as each
-- transfer terminates.
BEGIN
ValidateRequest[operation];
WITH op: operation SELECT FROM
DoNothing => RETURN;
SeekOnly => -- not implemented yet --NULL;
ENDCASE =>
BEGIN
xferTail, skipTail: CBPtr;
[xferTail, skipTail] ← BuildXferChain[operation];
IF skipTail = NIL THEN {EnqueueForDisk[@xferTail]; RETURN};
EnqueueXferChain[operation, xferTail, skipTail];
END;
END;
BogusRequest: PUBLIC ERROR [reason: RequestError, operation: POINTER TO DiskRequest] =
CODE;
DiskError: PUBLIC ERROR [status: CompletionStatus] = CODE;
-- Internal Procedures --
ValidateRequest: PROCEDURE [operation: POINTER TO DiskRequest] =
-- checks that the argument DiskRequest is valid, and raises BogusRequest if
-- it isn't.
BEGIN
maxvda: vDA ← MaxLegalvDA[];
MaxLegalvDA: PROCEDURE RETURNS [vDA] =
-- returns the maximum legal vDA for the current disk structure.
BEGIN
RETURN[
VirtualDA[
DA[sysDisk.sectors - 1, sysDisk.tracks - 1, sysDisk.heads - 1,
sysDisk.disks - 1, 0]]]
END;
CheckvDA: PROCEDURE [vda: vDA] RETURNS [isFillIn: BOOLEAN] =
-- validates a virtual disk address. (This validation is not intended to
-- be complete, but rather serves only as a blunder check).
BEGIN
IF isFillIn ← (vda = fillInvDA) THEN RETURN;
IF vda = eofvDA OR vda > maxvda THEN ERROR BogusRequest[badvDA, operation];
END;
CheckStrictvDA: PROCEDURE [vda: vDA, allowEOF: BOOLEAN]
RETURNS [isEOF: BOOLEAN] =
-- checks that vda is a legitimate one (fillInvDA is not permitted).
BEGIN
IF vda = fillInvDA THEN ERROR BogusRequest[illegalvDASequence, operation];
IF isEOF ← (vda = eofvDA) THEN
IF allowEOF THEN RETURN ELSE ERROR BogusRequest[badvDA, operation];
END;
EnsureLegalvDASequence: PROCEDURE [noFillIns: BOOLEAN] =
-- ensures that fillInvDAs appear only where permitted.
BEGIN
first: CARDINAL ← 0;
nXfers: CARDINAL ← LENGTH[operation.xfers];
i: CARDINAL;
IF nXfers = 0 THEN ERROR BogusRequest[illegalTransfer, operation];
IF noFillIns THEN
BEGIN
IF operation.pagesToSkip ~= 0 THEN GO TO IllegalSequence;
FOR i IN [0..nXfers) DO
[] ← CheckStrictvDA[operation.xfers[i].diskAddress, FALSE];
ENDLOOP;
RETURN
END;
IF operation.pagesToSkip ~= 0 THEN
[] ← CheckStrictvDA[operation.firstPagevDA, FALSE]
ELSE
BEGIN
[] ← CheckStrictvDA[operation.xfers[0].diskAddress, FALSE];
FOR i IN [1..nXfers) DO
IF CheckvDA[operation.xfers[i].diskAddress] THEN {first ← i + 1; EXIT};
REPEAT FINISHED => RETURN;
ENDLOOP;
END;
FOR i IN [first..nXfers) DO
IF ~CheckvDA[operation.xfers[i].diskAddress] THEN GO TO IllegalSequence;
ENDLOOP;
EXITS IllegalSequence => ERROR BogusRequest[illegalvDASequence, operation];
END;
WITH op: operation SELECT FROM
DoNothing => RETURN;
SeekOnly =>
BEGIN
IF LENGTH[op.xfers] ~= 0 THEN ERROR BogusRequest[illegalTransfer, operation];
[] ← CheckStrictvDA[op.firstPagevDA, FALSE];
END;
ReadLD, ReadD, WriteD =>
BEGIN
EnsureLegalvDASequence[noFillIns: FALSE];
IF LENGTH[op.xfers] > maxOps - nSafetyDCBs THEN GO TO TooManyXfers;
END;
WriteHLD, WriteLD =>
BEGIN
EnsureLegalvDASequence[noFillIns: TRUE];
[] ← CheckStrictvDA[op.next, TRUE];
[] ← CheckStrictvDA[op.prev, TRUE];
IF LENGTH[op.xfers] > maxOps THEN GO TO TooManyXfers;
END;
ReadHLD =>
BEGIN
EnsureLegalvDASequence[noFillIns: TRUE];
IF LENGTH[op.xfers] > maxOps THEN GO TO TooManyXfers;
END;
ENDCASE;
EXITS TooManyXfers => ERROR BogusRequest[tooManyTransfers, operation];
END;
BuildXferChain: PROCEDURE [operation: POINTER TO DiskRequest]
RETURNS [xferTail, skipTail: CBPtr] =
-- (synchronously) constructs the chain of transfer CBs required by 'operation'.
BEGIN
thisCB: CBPtr;
page: AltoDefs.PageNumber ← operation.firstPage + operation.pagesToSkip;
label: POINTER TO DL;
thisvDA: vDA;
nXfers: CARDINAL = LENGTH[operation.xfers];
last: CARDINAL = nXfers - 1;
lastIsFillin: BOOLEAN = (operation.xfers[last].diskAddress = fillInvDA);
i: CARDINAL;
FillLabelAndCB: PROCEDURE =
-- prepares 'thisCB' for execution.
BEGIN
thisCB.headerAddress ← @thisCB.header;
thisCB.labelAddress ← label;
thisCB.dataAddress ← operation.xfers[i].buffer;
IF loggingEnabled THEN
SELECT operation.action FROM
ReadHLD, ReadLD, ReadD => diskReads ← diskReads + 1;
WriteHLD, WriteLD, WriteD => diskWrites ← diskWrites + 1;
ENDCASE;
thisCB.command ← DCs[operation.action];
thisCB.requestID ← operation.xfers[i].id;
thisCB.postProc ← operation.proc;
thisCB.omitRestore ← operation.noRestore;
label.page ← page;
page ← page + 1;
label.fileID ← LOOPHOLE[operation.fileID];
WITH op: operation SELECT FROM
WriteLD, WriteHLD =>
BEGIN
label.prev ← RealDA[IF i = 0 THEN op.prev ELSE op.xfers[i - 1].diskAddress];
label.next ← RealDA[IF i = last THEN op.next ELSE op.xfers[i + 1].diskAddress];
label.bytes ← IF i = last AND op.next = eofvDA THEN op.lastByteCount
ELSE BytesPerDiskPage;
END;
ENDCASE;
END;
IF operation.pagesToSkip = 0 THEN
BEGIN
xferTail ← GetCBs[n: (IF lastIsFillin THEN nXfers + 1 ELSE nXfers), wait: TRUE];
skipTail ← NIL;
END
ELSE
BEGIN
xferTail ← GetCBs[n: nXfers + nSafetyDCBs, wait: TRUE];
skipTail ← xferTail.nextOnQueue;
xferTail.nextOnQueue ← skipTail.nextOnQueue.nextOnQueue;
skipTail.nextOnQueue.nextOnQueue ← skipTail;
END;
thisCB ← xferTail.nextOnQueue;
thisvDA ← operation.xfers[0].diskAddress;
FOR i ← 0, i + 1 DO
nextvDA: vDA;
IF thisvDA ~= fillInvDA THEN thisCB.header.diskAddress ← RealDA[thisvDA];
label ← @thisCB.label;
IF i = last THEN EXIT;
nextvDA ← operation.xfers[i + 1].diskAddress;
IF nextvDA = fillInvDA THEN
label ← LOOPHOLE[@thisCB.nextOnQueue.header.diskAddress];
FillLabelAndCB[];
thisCB.nextCB ← thisCB.nextOnQueue; thisCB ← thisCB.nextOnQueue;
thisvDA ← nextvDA;
ENDLOOP;
IF lastIsFillin THEN
BEGIN
label ← LOOPHOLE[@thisCB.nextOnQueue.header];
FillLabelAndCB[];
thisCB.nextCB ← thisCB.nextOnQueue;
thisCB ← thisCB.nextOnQueue;
-- The extra CB allocated for the label of the last chained CB is now appended
-- to the command chain. It will initiate a seek to the predecessor of the
-- last transferred page, which, at worst, will cause the heads to move to a
-- new cylinder. This kludge makes it easy to recover the extra CB at the
-- proper time, at the possible expense of some potentially unnecessary arm motion.
thisCB.command ← DCs[SeekOnly];
thisCB.requestID ← dummyID;
thisCB.postProc ← [normal[DummyCompletionProc]];
END
ELSE FillLabelAndCB[];
END;
EnqueueXferChain: PROCEDURE [operation: POINTER TO DiskRequest, xTail, sTail: CBPtr] =
-- outer shell for DoXferEnqueuing.
BEGIN
frame: ControlDefs.FrameHandle;
DoXferEnqueuing: PROCEDURE RETURNS [frame: ControlDefs.FrameHandle] =
-- does the work of skipping pages from 'firstPage', whose vDA is known,
-- up to the page preceding the one specified in 'xfers[0]'. It then enqueues
-- the xfer chain and exits.
BEGIN
thisCB, labelCB: CBPtr;
page: AltoDefs.PageNumber ← operation.firstPage;
last: AltoDefs.PageNumber ← operation.firstPage + operation.pagesToSkip - 1;
label: POINTER TO DL;
fileID: FID ← operation.fileID;
nonXferID: RequestID ← operation.nonXferID;
userPostProc: CompletionProcedure ← operation.proc;
ourPostProc: CompletionProcedure ← userPostProc;
noRestore: BOOLEAN ← operation.noRestore;
synch: SynchRecord;
skipTail: CBPtr ← sTail;
xferTail: CBPtr ← xTail;
Returnee: PROCEDURE [ControlDefs.FrameHandle];
PrepareAndEnqueueThisCB: PROCEDURE =
-- sets up 'thisCB' as part of a skip chain.
BEGIN
thisCB.headerAddress ← @thisCB.header;
thisCB.labelAddress ← label;
thisCB.dataAddress ← scratchPage;
IF loggingEnabled THEN diskReads ← diskReads + 1;
thisCB.command ← DCs[ReadD];
SetPostProcAndID[thisCB];
thisCB.omitRestore ← noRestore;
label.page ← page;
page ← page + 1;
label.fileID ← LOOPHOLE[fileID];
IF skipTail ~= NIL THEN skipTail.nextCB ← thisCB;
EnqueueCB[@skipTail, thisCB];
END;
SetPostProcAndID: PROCEDURE [cb: CBPtr] =
-- fills in cb.postProc and cb.requestID.
BEGIN
cb.requestID ←
IF (cb.postProc ← ourPostProc) = userPostProc THEN nonXferID ELSE notLastSkip;
END;
ResetPostProcs: PROCEDURE =
-- recomputes the postProc and requestID fields for all cb's on skipTail.
BEGIN
cb: CBPtr ← skipTail;
IF cb = NIL THEN RETURN;
DO
SetPostProcAndID[cb];
IF (cb ← cb.nextOnQueue) = skipTail THEN EXIT;
ENDLOOP;
END;
SkipChecker: VerboseCompletionProcedure =
-- invoked when an asynchronously queued skip request has completed.
BEGIN
WITH userPostProc: userPostProc SELECT FROM
verbose => userPostProc.proc[nonXferID, status, header, label];
ENDCASE;
IF id = lastSkip THEN DoNotify[@synch, status];
END;
DoNotify: ENTRY PROCEDURE [synch: IOSynch, status: CompletionStatus] =
INLINE
-- wake up EnqueueAndWait, passing the status of the last CB that it enqueued.
{synch.status ← status; NOTIFY synch.lastDone};
EnqueueAndWait: ENTRY PROCEDURE [synch: IOSynch]
RETURNS [CompletionStatus] = INLINE
-- enqueues skipTail, waits until skips are complete, then returns the outcome.
BEGIN
synch.status ← noStatus; synch.lastDone.timeout ← 0;
skipTail.requestID ← lastSkip;
EnqueueForDisk[@skipTail];
skipTail ← NIL;
WHILE synch.status = noStatus DO WAIT synch.lastDone; ENDLOOP;
RETURN[synch.status]
END;
BEGIN
-- inner block so 'SpliceAndEnqueueXfers' can access local variables of this frame
frame ← ControlDefs.NullFrame;
thisCB ← skipTail.nextOnQueue;
labelCB ← skipTail;
skipTail ← NIL;
thisCB.header.diskAddress ← RealDA[operation.firstPagevDA];
WHILE page < last DO
label ← LOOPHOLE[@labelCB.header.diskAddress];
PrepareAndEnqueueThisCB[];
thisCB ← labelCB;
IF (labelCB ← GetCBs[n: 1, wait: FALSE]) = NIL THEN EXIT;
REPEAT FINISHED => {FreeCB[labelCB]; GO TO SpliceAndEnqueueXfers};
ENDLOOP;
-- If control comes here, we have insufficient CBs available to build the
-- entire chain now. We fork a process to wait for more. However, in order
-- to keep our frame around, we have to do some fancy shuffling of return
-- links. The dirty deed is done by the next three lines of code plus
-- 'ShuffleLinks'.
frame ← FrameOps.MyLocalFrame[];
Returnee ← LOOPHOLE[FrameOps.GetReturnLink[]];
Returnee[frame];
-- The code below is actually executed in a different process. When it
-- has completed, control will exit to the Mesa runtime system's process
-- destruction machinery.
ourPostProc ← CompletionProcedure[verbose[SkipChecker]];
ResetPostProcs[];
WHILE page < last DO
IF (labelCB ← GetCBs[n: 1, wait: FALSE]) = NIL THEN
BEGIN
IF skipTail ~= NIL AND EnqueueAndWait[@synch] ~= ok THEN
GO TO ErrorWhileSkipping;
labelCB ← GetCBs[n: 1, wait: TRUE];
END;
label ← LOOPHOLE[@labelCB.header.diskAddress];
PrepareAndEnqueueThisCB[];
thisCB ← labelCB;
REPEAT
FINISHED =>
{ourPostProc ← userPostProc; ResetPostProcs[]; GO TO SpliceAndEnqueueXfers};
ENDLOOP;
EXITS
SpliceAndEnqueueXfers =>
BEGIN
label ← LOOPHOLE[@xferTail.nextOnQueue.header.diskAddress];
PrepareAndEnqueueThisCB[];
skipTail.nextCB ← xferTail.nextOnQueue;
SpliceLists[@skipTail, @xferTail];
EnqueueForDisk[@skipTail];
END;
ErrorWhileSkipping => {FreeCB[thisCB]; SimulateCompletion[@xferTail]};
END; -- inner block for 'SpliceAndEnqueueXfers'
END;
ShuffleLinks: PROCEDURE [frame: ControlDefs.FrameHandle] =
-- invoked to patch up return links for forked process. The argument frame
-- belongs to DoXferEnqueuing, but the forking machinery has set the return
-- link of ShuffleLinks to be the process destruction code. We patch
-- ShuffleLinks to return to DoXferEnqueueing, and patch DoXferEnqueuing
-- to return to the process destroyer. Clear?
BEGIN
frame.returnlink ← FrameOps.GetReturnLink[];
FrameOps.SetReturnFrame[frame];
END;
frame ← DoXferEnqueuing[];
IF frame ~= ControlDefs.NullFrame THEN ProcessDefs.Detach[FORK ShuffleLinks[frame]];
END;
RealDA: PUBLIC PROCEDURE [v: vDA] RETURNS [da: DA] =
BEGIN
i: CARDINAL ← v;
da ← DA[0, 0, 0, 0, 0];
IF v # eofvDA THEN
BEGIN
[i, da.sector] ← Inline.DIVMOD[i, sysDisk.sectors];
[i, da.head] ← Inline.DIVMOD[i, sysDisk.heads];
[i, da.track] ← Inline.DIVMOD[i, sysDisk.tracks];
[i, da.disk] ← Inline.DIVMOD[i, sysDisk.disks];
IF i # 0 THEN da ← InvalidDA;
END;
END;
TransformStatus: PUBLIC PROCEDURE [status: DS] RETURNS [CompletionStatus] =
-- This procedure maps hardware status information to a simpler form for
-- completion procedures to handle.
BEGIN
SELECT MaskDS[status, DSmaskStatus] FROM
DSgoodStatus => RETURN[ok];
DSfakeStatus => RETURN[neverStarted];
ENDCASE;
SELECT status.finalStatus FROM
CheckError => RETURN[checkError];
IllegalSector => RETURN[badDiskAddress];
ENDCASE;
IF status.seekFailed = 1 THEN RETURN[seekFailure];
IF status.notReady = 1 THEN RETURN[diskOffline];
IF status.checksumError = 1 THEN RETURN[checksumError];
IF status.dataLate = 1 THEN RETURN[dataLate];
ERROR ImpossibleStatus
END;
DummyCompletionProc: NormalCompletionProcedure =
-- This procedure is invoked only when the relevant CB is the "extra" one
-- required to hold the label for the last legitimate CB in a linked-label chain.
{IF id ~= dummyID THEN ERROR ImpossibleRequestID};
END.