<> <> <> <> DIRECTORY Basics USING [ LongDivMod ], Disk, DiskFace, PrincOpsUtils USING[ AllocateNakedCondition ], Process USING[ Detach, SecondsToTicks, SetPriority, SetTimeout, priorityFaultHandlers ], ProcessorFace USING[ GetClockPulses ], VM USING[ lowCore ]; DiskImpl: MONITOR IMPORTS Basics, 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, taken: CONDITION, waiters: INT, 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 -- <> <> <> 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, waiters: 0, 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] _ Basics.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; IF channel.freeOps = NIL THEN BEGIN channel.waiters _ channel.waiters+1; WHILE channel.freeOps = NIL DO WAIT channel.freed[ ! UNWIND => channel.waiters _ channel.waiters-1 ] ENDLOOP; channel.waiters _ channel.waiters-1; END; op _ channel.freeOps; channel.freeOps _ op.rest; op.rest _ NIL; BROADCAST channel.taken; END; FreeOp: ENTRY PROC[channel: Channel, op: OperationPtr] = BEGIN ENABLE UNWIND => NULL; op.rest _ channel.freeOps; channel.freeOps _ op; IF channel.waiters > 0 THEN BEGIN -- let any waiter have the processor, to enqueue the next request NOTIFY channel.freed; UNTIL channel.freeOps = NIL OR channel.waiters = 0 DO WAIT channel.taken ENDLOOP; END; END; largestRequestRun: INT = MIN[LAST[CARDINAL], 1000]; <> 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; status _ [unchanged[goodCompletion]]; -- in case request.count is already zero <> UNTIL request.count = 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 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.