Cedar Nucleus: disk channel
DiskImpl.mesa
Andrew Birrell July 8, 1983 4:24 pm
DIRECTORY
Disk,
DiskFace,
PrincOpsUtils USING[ AllocateNakedCondition, LongDivMod ],
Process USING[ Detach, SecondsToTicks, SetPriority, SetTimeout, priorityFaultHandlers ],
ProcessorFace USING[ GetClockPulses ],
VM USING[ lowCore ];
DiskImpl: MONITOR
IMPORTS DiskFace, PrincOpsUtils, Process, ProcessorFace, VM
EXPORTS Disk, DiskFace--DontCare-- =
BEGIN
Channel: TYPE = LONG POINTER TO ChannelObject;
ChannelObject: PUBLIC TYPE = MONITORED RECORD[
drive: DiskFace.DeviceHandle,
ordinal: CARDINAL,
-- above fields are immutable; remainder are protected by monitor lock --
cylinders, movingHeads, fixedHeads, sectorsPerTrack: CARDINAL ← 0,
freeOps: LONG POINTER TO Operation,
freed: CONDITION,
rest: Channel];
OperationPtr: TYPE = LONG POINTER TO Operation;
Operation: TYPE = MACHINE DEPENDENT RECORD[
rest(0): OperationPtr,
wait(2): CONDITION,
status(4): DiskFace.Status,
label(5): LONG POINTER TO DiskFace.Label,
fill(7): ARRAY [7..opFieldPosition) OF WORD,
faceOp(opFieldPosition): DiskFace.Operation,
extra(opFieldPosition+SIZE[DiskFace.Operation]): SEQUENCE COMPUTED CARDINAL OF WORD
];
opFieldPosition: CARDINAL = 16; -- N.B. This must be a multiple of 16 to align DiskFace.Operation
channelList: Channel ← NIL;
NextChannel: PUBLIC SAFE PROC[prev: Channel, wait: BOOL] RETURNS[next: Channel] =
TRUSTED BEGIN
DO next ← EntryNext[prev];
IF next # NIL OR NOT wait THEN EXIT;
[] ← AwaitChange[AwaitChange[0]];
ENDLOOP;
END;
change: CONDITION;
AwaitChange: ENTRY PROC[old: INT] RETURNS[new: INT] = TRUSTED
{ ENABLE UNWIND => NULL; DO WAIT change ENDLOOP--TEMP!!-- };
lastDrive: DiskFace.DeviceHandle ← DiskFace.nullDeviceHandle;
EntryNext: ENTRY PROC[prev: Channel] RETURNS[next: Channel] = TRUSTED
BEGIN
ENABLE UNWIND => NULL;
next ← IF prev = NIL THEN channelList ELSE prev.rest;
-- check whether next drive has changed state --
WHILE next # NIL
AND next.changeCount # DiskChannel.GetDriveAttributes[next.oldDrive].changeCount
DO Assign[prev, next.rest]; next ← next.rest; ENDLOOP;
IF next = NIL
THEN BEGIN
-- check whether there is a new drive --
new: DiskFace.DeviceHandle = DiskFace.GetNextDevice[lastDrive];
IF new # DiskFace.nullDeviceHandle
THEN { Assign[prev, NewDrive[new]]; lastDrive ← new };
next ← IF prev = NIL THEN channelList ELSE prev.rest;
END;
END;
Assign: INTERNAL PROC[tail: Channel, new: Channel] =
TRUSTED{ IF tail = NIL THEN channelList ← new ELSE tail.rest ← new };
lastOrdinal: CARDINAL ← 0;
opsPerDrive: INT ← 2;
NewDrive: INTERNAL PROC[new: DiskFace.DeviceHandle] RETURNS[channel: Channel] = TRUSTED
BEGIN
channel ← VM.lowCore.NEW[ChannelObject ← [
drive: new,
ordinal: lastOrdinal,
freeOps: NIL,
rest: NIL] ];
lastOrdinal ← lastOrdinal+1;
FOR i: INT IN [0..opsPerDrive)
DO
new: OperationPtr =
VM.lowCore.NEW[Operation[DiskFace.operationSize-SIZE[DiskFace.Operation]]];
new.label ← VM.lowCore.NEW[DiskFace.Label];
new.rest ← channel.freeOps;
channel.freeOps ← new;
ENDLOOP;
END;
InspectDiskShape: PUBLIC SAFE PROC[channel: Channel, mode: DiskFace.DeterminationMode]
RETURNS[nowKnown: BOOL] =
TRUSTED BEGIN
myOp: OperationPtr = AllocOp[channel];
nowKnown ← DiskFace.DetermineDiskShape[channel.drive, @myOp.faceOp, mode !
UNWIND => FreeOp[channel, myOp];];
FreeOp[channel, myOp];
IF nowKnown
THEN BEGIN
cylinders, movingHeads, fixedHeads, sectorsPerTrack: CARDINAL;
[cylinders, movingHeads, fixedHeads, sectorsPerTrack] ←
DiskFace.GetDeviceAttributes[channel.drive];
NoteShapeKnown[channel, cylinders, movingHeads, fixedHeads, sectorsPerTrack];
END;
END;
NoteShapeKnown: ENTRY PROC[channel: Channel, cylinders, movingHeads, fixedHeads, sectorsPerTrack: CARDINAL] =
BEGIN
ENABLE UNWIND => NULL;
channel.cylinders ← cylinders;
channel.movingHeads ← movingHeads;
channel.fixedHeads ← fixedHeads;
channel.sectorsPerTrack ← sectorsPerTrack;
END;
SameDrive: PUBLIC SAFE PROC[a,b: Channel] RETURNS[BOOL] =
TRUSTED { RETURN[ a.ordinal = b.ordinal ] };
Valid: PUBLIC SAFE PROC[channel: Channel] RETURNS[BOOL] =
TRUSTED { RETURN[TRUE] };
DriveAttributes: PUBLIC ENTRY SAFE PROC[channel: Channel] RETURNS[ type: DiskFace.Type, ordinal: INT, ready: BOOL, nPages: Disk.PageCount] = TRUSTED
BEGIN
ENABLE UNWIND => NULL;
type ← DiskFace.GetDeviceType[channel.drive];
ordinal ← channel.ordinal;
nPages ← (LONG[channel.cylinders] * channel.movingHeads) * channel.sectorsPerTrack;
END;
-- Performing operations! --
GetAddress: ENTRY PROC[channel: Channel, page: INT] RETURNS[addr: DiskFace.DiskAddress] =
BEGIN
ENABLE UNWIND => NULL;
cylinder: CARDINAL;
temp: CARDINAL;
[quotient: cylinder, remainder: temp] ← PrincOpsUtils.LongDivMod[
num: page, den: channel.sectorsPerTrack * channel.movingHeads];
RETURN[ [cylinder: cylinder,
sector: temp MOD channel.sectorsPerTrack, head: temp/channel.sectorsPerTrack ] ]
END;
AllocOp: ENTRY PROC[channel: Channel] RETURNS[op: OperationPtr] =
BEGIN
ENABLE UNWIND => NULL;
WHILE channel.freeOps = NIL DO WAIT channel.freed ENDLOOP;
op ← channel.freeOps; channel.freeOps ← op.rest; op.rest ← NIL;
END;
FreeOp: ENTRY PROC[channel: Channel, op: OperationPtr] =
BEGIN
ENABLE UNWIND => NULL;
op.rest ← channel.freeOps; channel.freeOps ← op;
NOTIFY channel.freed;
END;
largestRequestRun: INT = MIN[LAST[CARDINAL], 1000];
The largest number of pages in a single request handed to the DiskFace. DiskFace.Operation limits it to a CARDINAL, and we restrict it further to avoid hogging of the device by a single client.
DoIO: PUBLIC UNSAFE PROC[channel: Channel, label: LONG POINTER TO DiskFace.Label, request: LONG POINTER TO Disk.Request] RETURNS[ status: Disk.Status, countDone: Disk.PageCount] = TRUSTED
BEGIN
myOp: OperationPtr = AllocOp[channel];
countDone ← 0;
DO BEGIN -- Loop to break up large transfers.
ENABLE UNWIND => FreeOp[channel, myOp];
reqPages: INT = MIN[request.count, largestRequestRun];
doneThisTime: INT;
myOp.label^ ← label^;
myOp.faceOp ← [
clientHeader: GetAddress[channel, request.diskPage],
labelPtr: myOp.label,
dataPtr: request.data,
incrementDataPtr: request.incrementDataPtr,
unused: NULL,
command: request.command,
tries: request.tries,
pageCount: reqPages,
deviceStatus: NULL,
diskHeader: NULL,
device: channel.drive
];
myOp.status ← inProgress;
DoOperation[myOp];
doneThisTime ← reqPages - myOp.faceOp.pageCount;
countDone ← countDone + doneThisTime;
status ← [unchanged[myOp.status]];
request.diskPage ← [request.diskPage + doneThisTime];
label^ ← myOp.label^;
request.data ← myOp.faceOp.dataPtr;
request.count ← request.count - doneThisTime;
IF request.count = 0 OR myOp.status # goodCompletion THEN EXIT;
END;
ENDLOOP;
FreeOp[channel, myOp];
END;
currentOperations: CARDINAL ← 0;
Pulses: TYPE = LONG CARDINAL;
becameActive: Pulses; -- pulse reading when we changed from inactive to active
activeTotal: Pulses ← 0; -- total number of pulses while we have been active
totalReads, totalWrites, totalReadPages, totalWritePages: INT ← 0;
GetStatistics: PUBLIC ENTRY SAFE PROC RETURNS[
active, total: Pulses, reads, writes, readPages, writePages: INT ] = CHECKED
BEGIN
ENABLE UNWIND => NULL;
total ← ProcessorFace.GetClockPulses[];
active ← activeTotal + (IF currentOperations#0 THEN (total - becameActive) ELSE 0);
reads ← totalReads;
writes ← totalWrites;
readPages ← totalReadPages;
writePages ← totalWritePages;
END;
DoOperation: ENTRY PROC[myOp: OperationPtr] =
BEGIN
ENABLE UNWIND => NULL;
IF myOp.faceOp.command.data = write OR myOp.faceOp.command.label = write
THEN {totalWrites ← totalWrites+1; totalWritePages ← totalWritePages+myOp.faceOp.pageCount }
ELSE {totalReads ← totalReads+1; totalReadPages ← totalReadPages+myOp.faceOp.pageCount };
DiskFace.Initiate[@myOp.faceOp];
IF currentOperations = 0 THEN becameActive ← ProcessorFace.GetClockPulses[];
currentOperations ← currentOperations+1;
WHILE myOp.status = inProgress DO WAIT myOp.wait ENDLOOP;
END;
DiskInterrupt: ENTRY PROC[controller: DiskFace.ControllerHandle] =
BEGIN
cv: LONG POINTER TO CONDITION;
mask: WORD;
size: CARDINAL = DiskFace.GetControllerAttributes[controller];
GlobalState: TYPE = RECORD[SEQUENCE COMPUTED CARDINAL OF WORD];
globalState: LONG POINTER = VM.lowCore.NEW[GlobalState[size]];
[cv, mask] ← PrincOpsUtils.AllocateNakedCondition[];
-- Enable a timeout because disk world-swap can give our interrupt to the debugger!
Process.SetTimeout[cv, Process.SecondsToTicks[1]];
DiskFace.InitializeController[controller, globalState, mask];
DiskFace.InitializeCleanup[controller];
Process.SetPriority[Process.priorityFaultHandlers];
DO -- forever, once per naked wakeup
status: DiskFace.Status;
faceOp: DiskFace.OperationPtr;
retriedCount: CARDINAL;
myOp: OperationPtr;
WAIT cv; -- wakeup after one or more requests have completed
DO -- take all completed requests from controller
[status, faceOp, retriedCount] ← DiskFace.Poll[controller];
IF status = inProgress OR faceOp = NIL THEN EXIT;
myOp ← LOOPHOLE[faceOp-opFieldPosition];
myOp.status ← status;
currentOperations ← currentOperations-1;
IF currentOperations = 0
THEN activeTotal ← activeTotal + (ProcessorFace.GetClockPulses[] - becameActive);
NOTIFY myOp.wait;
ENDLOOP;
ENDLOOP;
END;
InitializeControllers: ENTRY PROC =
BEGIN
FOR this: DiskFace.ControllerHandle ← DiskFace.GetNextController[DiskFace.nullControllerHandle],
DiskFace.GetNextController[this]
UNTIL this = DiskFace.nullControllerHandle
DO Process.Detach[FORK DiskInterrupt[this]] ENDLOOP;
END;
--DiskFace.--DontCare: PUBLIC TYPE = DiskFace.DiskAddress;
GetBootChainLink: PUBLIC SAFE PROC[channel: Channel, diskPage: Disk.PageNumber] RETURNS [DiskFace.DontCare] = TRUSTED
{ RETURN[ GetAddress[channel, diskPage] ] };
GetPageNumber: PUBLIC ENTRY SAFE PROC[channel: Channel, link: DontCare] RETURNS[ Disk.PageNumber] = TRUSTED
{ RETURN[ [LONG[link.cylinder * channel.movingHeads + link.head] * channel.sectorsPerTrack
+ link.sector] ] };
InitializeControllers[];
END.