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.