-- SA4000HeadD0.mesa (last edited by: McJones on: July 24, 1980 11:23 AM)
-- Things to do:
-- 1) GetNextDevice should find all drives on a controller (does this need a microcode change?)
-- 2) The semantics of aborting should be independent of controllers; e.g. aborting could work on all drives, or on an individual drive. (Currently it works on all drives of a controller, but it is hard to see how this can be specified in a controller-independent way.)
DIRECTORY
DeviceCleanup USING [Await, Item, Reason],
D0InputOutput USING [ControllerNumber, GetNextController, IOPage, nullControllerNumber, rdc],
Environment USING [Base, first64K, Long],
HeadStartChain USING [Start],
Inline USING [BITAND, LowHalf],
PilotDisk USING [Handle, Label],
PilotFileTypes USING [PilotVFileType],
RDC,
SA4000Face,
System USING [],
SystemInternal USING [UniversalID];
SA4000HeadD0: PROGRAM
IMPORTS D0InputOutput, DeviceCleanup, RemainingHeads: HeadStartChain, Inline, RDC
EXPORTS HeadStartChain, SA4000Face, System SHARES SA4000Face =
BEGIN OPEN RDC, SA4000Face;
-- TYPE DEFINITIONS
CSB: TYPE = MACHINE DEPENDENT RECORD [
-- Controller status block. There is one CSB for each controller, with a maximum of four controllers. The CSB resides in a resident page of memory. D0InputOutput.IOPage is a LONG POINTER to an array of CSBs. The CSB is indexed by the controller number in the DiskHandle.
cylinder: ARRAY Drive OF CARDINAL, -- Contains the current cylinder address for each drive or -1 if the drive is to be recalibrated before the next command.
state: CSBState];
CSBState: TYPE = MACHINE DEPENDENT RECORD [
-- This part of the CSB is defined separately so that it can be saved and restored by InitializeCleanup.
next: IOCBshortPtr, -- Points to the next IOCB to be processed by the microcode. Points to the last IOCB if an error is reported by the microcode. Contains zero if the queue is empty. This is updated by Initiate and by the microcode. When the microcode finishes processing an IOCB, it replaces next with the next pointer from the IOCB.
deferring: INTEGER, -- This is set to -1 by the microcode when it reports an error completion to the Head. The microcode will not process any more IOCBs until this is set to zero again by Poll when the error is reported to the client. In the case of a labelCheck that requires a fixup, the error is not really an error at all, and the client will be unaware of the label fixup.
tail: IOCBshortPtr, -- Points to the last IOCB in the queue. Used only by Initiate.
transferMask: WORD]; -- This interupt mask is used by the microcode to schedule Mesa processes at the completion of each IOCB. It is initialized by StartB.
CSBPtr: TYPE = LONG POINTER TO CSB;
DiskHandle: TYPE = MACHINE DEPENDENT RECORD [ -- representation of DeviceHandle
zero: [0..16) ← 0,
controller: D0InputOutput.ControllerNumber,
drive: [0..256)];
Drive: TYPE = [0..drives);
-- CONSTANTS
drives: CARDINAL = 4; -- maximum number of drives per controller
rdcCommands: ARRAY Command OF WORD = [
-- These are the RDC operation codes that correspond to the LabelDataOperations. For each operation, header verify is assumed. Each command includes the allow wake bit (4000).
4110B, -- headerVerifyLabelVerify
4112B,
-- headerVerifyLabelVerifyDataRead
4114B,
-- headerVerifyLabelVerifyDataWrite
4111B,
-- headerVerifyLabelVerifyDataVerify
4140B,
-- headerVerifyLabelWrite
4144B,
-- headerVerifyLabelWriteDataWrite
4120B,
-- headerVerifyLabelRead
4122B,
-- headerVerifyLabelReadDataRead
4124B,
-- headerVerifyLabelReadDataWrite
4121B,
-- headerVerifyLabelReadDataVerify
4510B,
-- headerReadLabelVerify
4512B,
-- headerReadLabelVerifyDataRead
4514B,
-- headerReadLabelVerifyDataWrite
4511B,
-- headerReadLabelVerifyDataVerify
4540B,
-- headerReadLabelWrite
4544B,
-- headerReadLabelWriteDataWrite
4520B,
-- headerReadLabelRead
4522B,
-- headerReadLabelReadDataRead
4524B,
-- headerReadLabelReadDataWrite
4521B,
-- headerReadLabelReadDataVerify
4200B];
-- headerWrite
labelRead: WORD = 0020B; -- bit of RDC command signifying label read
p: Environment.Base = Environment.first64K;
nil: Environment.Base RELATIVE POINTER = LOOPHOLE[0];
nullCylinder: CARDINAL = 177777B; -- used for initialization and recalibration
nullDiskHandle: DiskHandle = [controller: D0InputOutput.nullControllerNumber, drive: 0]; -- GetNextDevice depends on this value
rdcInProgress: WORD = 0;
serviceLateRetries: CARDINAL = 1000; -- retries to be performed by the microcode
rateErrorRetries: CARDINAL = 1000; -- retries to be performed by the microcode
-- GLOBAL VARIABLES
globalStateSize: PUBLIC CARDINAL ← 0; -- an IOCB for label fixup is not required for the D0 implementation.
nullDeviceHandle: PUBLIC DeviceHandle ← Seal[nullDiskHandle];
operationSize: PUBLIC CARDINAL ← SIZE[IOCB];
totalErrors: CARDINAL ← 0; -- = total errors reported by Poll
uCodeServiceLateRetries: CARDINAL ← 0; -- total serviceLate retries performed by the microcode
uCodeRateErrorRetries: CARDINAL ← 0; -- total rateError retries performed by the microcode
-- PROCEDURES
GetDeviceAttributes: PUBLIC PROCEDURE [device: DeviceHandle] RETURNS [cylinders, movingHeads, fixedHeads, sectorsPerTrack: CARDINAL] =
-- MUST BE MODIFIED TO HANDLE FIXED HEADS, SINGLE-PLATTER DRIVES
BEGIN
RETURN[
cylinders: 202,
movingHeads: 8, -- actually depends on whether 4004 or 4008
fixedHeads: 0, -- actually depends on option
sectorsPerTrack: 28]
END;
GetNextDevice: PUBLIC PROCEDURE [device: DeviceHandle] RETURNS [DeviceHandle] =
-- MUST BE MODIFIED TO HANDLE MULTIPLE DRIVES/CONTROLLER
BEGIN OPEN D0InputOutput, disk: LOOPHOLE[device, DiskHandle];
controller: ControllerNumber;
SELECT disk FROM
nullDiskHandle =>
BEGIN
controller ← GetNextController[rdc, nullControllerNumber];
IF controller=nullControllerNumber THEN GOTO Null; -- no drives
RETURN[Seal[[controller: controller, drive: 0]]]
END;
ENDCASE => GOTO Null
EXITS Null => RETURN[nullDeviceHandle];
END;
Initialize: PUBLIC PROCEDURE[t: WORD, globalState: GlobalStatePtr] =
BEGIN OPEN D0InputOutput;
csbState: CSBState = [
next: nil,
deferring: 0,
tail: nil,
transferMask: t];
c: ControllerNumber ← nullControllerNumber;
WHILE (c ← GetNextController[rdc, c])~=nullControllerNumber DO OPEN csb: LOOPHOLE[@IOPage[c], CSBPtr];
csb.state ← csbState;
ENDLOOP;
END;
InitializeCleanup: PUBLIC PROCEDURE =
BEGIN OPEN D0InputOutput;
c: ControllerNumber ← nullControllerNumber;
WHILE (c ← GetNextController[rdc, c])~=nullControllerNumber DO OPEN csb: LOOPHOLE[@IOPage[c], CSBPtr];
InitializeCleanupForController[@csb];
ENDLOOP;
END;
Initiate: PUBLIC PROCEDURE [operationPtr: OperationPtr] =
BEGIN
-- This procedure initiates an operation. The client passes an IOCB with some of the parameters filled in. We fill in the parameters that are unique to this implementation of the Head and microcode. Then we chain the IOCB onto the CSB. The microcode wakes up at every sector to see if there is an IOCB waiting in the CSB queue.
iocb: IOCBlongPtr = LOOPHOLE[operationPtr];
iocbShort: IOCBshortPtr = LOOPHOLE[Inline.LowHalf[operationPtr]];
csb: CSBPtr = IGetCSB[iocb.operation.device];
-- Initialize clientHeader, clientLabel, status, RDC command, and next fields.
-- Design of RDC dictates position of clientHeader and (8 word) clientLabel fields in IOCB.
iocb.clientHeader ← iocb.operation.clientHeader;
PackLabel[to: @iocb.clientLabel, from: iocb.operation.labelPtr];
iocb.operation.deviceStatus.a ← rdcInProgress;
iocb.rdcCommand ← rdcCommands[iocb.operation.command];
iocb.next ← nil;
-- The microcode performs error retries automatically for serviceLate and rateError because they occur frequently.
iocb.serviceLateRetryCount ← serviceLateRetries;
iocb.rateErrorRetryCount ← rateErrorRetries;
-- Chain this IOCB onto CSB for processing by microcode.
IF csb.state.next = nil THEN -- microcode is currently not busy, so chain next IOCB onto head of list.
csb.state.next ← iocbShort
ELSE -- microcode is busy processing an IOCB, but it could report completion at any time. Chain new IOCB onto tail of queue. There is a possible race, which is handled in Poll.
p[csb.state.tail].next ← iocbShort;
csb.state.tail ← iocbShort;
END;
Poll: PUBLIC PROCEDURE [operationPtr: OperationPtr] RETURNS [status: Status] =
BEGIN
iocb: IOCBlongPtr = LOOPHOLE[operationPtr];
csb: CSBPtr = IGetCSB[iocb.operation.device];
controllerIdle: BOOLEAN = csb.state.next=nil OR csb.state.deferring<0; -- used to detect race noted in Initiate
-- Update the error counts performed by the microcode. The number of retries in the IOCB will be decremented by one for each retry. We keep track of these for debugging purposes.
uCodeServiceLateRetries ← uCodeServiceLateRetries + (serviceLateRetries-iocb.serviceLateRetryCount);
uCodeRateErrorRetries ← uCodeRateErrorRetries + (rateErrorRetries-iocb.rateErrorRetryCount);
status ← DecodeStatus[iocb.operation.deviceStatus.a]; -- check status of specified IOCB
IF status=inProgress THEN -- operation not completed (possibly not initiated)
-- If controller went idle before noticing this request, resubmit it (and following IOCBs).
BEGIN IF controllerIdle THEN csb.state.next ← LOOPHOLE[Inline.LowHalf[operationPtr]]; RETURN END;
IF status=goodCompletion THEN NULL -- operation successfully completed
ELSE -- operation unsuccessfully completed
BEGIN
-- For all of the following completion codes, the microcode has reported an error. csb.next still points to the IOCB that caused the error. csb.deferring has been set to -1 by the microcode to prevent it from processing any more IOCBs until we set it back to zero.
SELECT status FROM
labelCheck =>
BEGIN
-- The RDC microcode has read the actual label from the disk into the diskLabel of the IOCB. The RDC verifies all 8 words of the label, but we are only interested in verifying the "significant" ones.
IF MatchLabels[@iocb.clientLabel, @iocb.diskLabel] THEN -- not an error after all
BEGIN
-- If all the label words that we want to verify match the label words on the disk, we need only resubmit the IOCB again. First we update the clientHeader field in the operation to match the one in the IOCB which has been incremented by the microcode and we update the bootChainLink field in the operation label to match the corresponding field in the disk label.
iocb.operation.clientHeader ← iocb.clientHeader;
iocb.clientLabel.bootChainLink ← iocb.diskLabel.bootChainLink;
iocb.operation.deviceStatus.a ← rdcInProgress;
csb.state.deferring ← 0; -- restart the controller
RETURN[inProgress]
END;
END;
wrongSector =>
-- The code that was reported by the Controller is headerError. This means that the header read from the disk did not match the header sent by the client. It could be a wrongCylinder, wrongHeader, or wrongSector.
SELECT TRUE FROM
iocb.operation.clientHeader.cylinder~=iocb.operation.diskHeader.cylinder => status ← wrongCylinder;
iocb.operation.clientHeader.head~=iocb.operation.diskHeader.head => status ← wrongHead;
ENDCASE; -- wrongSector corresponds to headerError, so we don’t need to change status.
ENDCASE; -- other error
totalErrors ← totalErrors+1;
csb.state.next ← nil; -- zap all deferred requests
csb.state.deferring ← 0; -- restart the controller
END;
iocb.operation.clientHeader ← iocb.clientHeader;
UnpackLabel[to: iocb.operation.labelPtr,
from:
IF Inline.BITAND[iocb.rdcCommand, labelRead]~=0 THEN @iocb.diskLabel ELSE @iocb.clientLabel];
END;
Recalibrate: PUBLIC PROCEDURE [device: DeviceHandle] =
BEGIN
GetCSB[device].cylinder[LOOPHOLE[device, DiskHandle].drive] ← nullCylinder
END;
Reset: PUBLIC PROCEDURE [device: DeviceHandle] =
BEGIN
-- WHAT SHOULD THIS DO?
END;
Start: PUBLIC PROCEDURE = -- exported to HeadStartChain
BEGIN
RemainingHeads.Start[];
END;
-- Private procedures
DecodeStatus: PROCEDURE [r: UNSPECIFIED] RETURNS [s: Status] = -- decode RDC status word
INLINE BEGIN
RETURN[LOOPHOLE[LOOPHOLE[r, RDC.DeviceStatus].outcome]]; -- completion code in processor field matches Status codes up to wrongSector
END;
GetCSB: PROCEDURE[device: DeviceHandle] RETURNS [CSBPtr] =
BEGIN
RETURN[IGetCSB[device]]
END;
IGetCSB: PROCEDURE[device: DeviceHandle] RETURNS [CSBPtr] =
INLINE BEGIN
RETURN[LOOPHOLE[@D0InputOutput.IOPage[LOOPHOLE[device, DiskHandle].controller]]]
END;
InitializeCleanupForController: PROCEDURE [csb: CSBPtr] =
BEGIN OPEN DeviceCleanup;
item: Item;
reason: Reason;
csbState: CSBState;
DO
reason ← Await[@item];
SELECT reason FROM
turnOff, kill =>
BEGIN
UNTIL csb.state.next=nil OR csb.state.deferring<0 DO ENDLOOP;
csbState ← csb.state; -- save state of CSB
END;
turnOn => csb.state ← csbState; -- restore CSB state
ENDCASE
ENDLOOP
END;
MatchLabels: PROCEDURE [p1, p2: LONG POINTER TO RDC.PackedLabel] RETURNS [BOOLEAN] =
-- Like PilotDisk.MatchLabel, for packed labels.
-- All fields but bootChainLink are significant.
-- (Really should treat filePageHi as significant when type IN PilotVolumeFileType.)
BEGIN
MatchableLabel: TYPE = ARRAY [0..8) OF RECORD [UNSPECIFIED];
FOR i: CARDINAL IN [0..7) DO
IF LOOPHOLE[p1↑, MatchableLabel][i]~=LOOPHOLE[p2↑, MatchableLabel][i] THEN RETURN[FALSE];
ENDLOOP;
RETURN[TRUE]
END;
UniversalID: PUBLIC TYPE = SystemInternal.UniversalID;
PackLabel: PROCEDURE [to: LONG POINTER TO RDC.PackedLabel, from: LONG POINTER TO PilotDisk.Label] =
INLINE BEGIN OPEN
fileID: LOOPHOLE[from.fileID, SystemInternal.UniversalID], -- Mesa AR 4669
seq: LOOPHOLE[--from.--fileID.sequence, Environment.Long],
s: LOOPHOLE[seq.highbits, MACHINE DEPENDENT RECORD [
bits0Thru1 (0:0..1): CARDINAL[0..4),
bit2 (0:2..2): CARDINAL[0..2),
bits3Thru15 (0:3..15): CARDINAL[0..8192)]];
to↑ ← [
sequence0Bit2: s.bit2,
processorID0: --from.--fileID.processor.a,
processorID1: --from.--fileID.processor.b,
processorID2: --from.--fileID.processor.c,
sequence1: Inline.LowHalf[--from.--fileID.sequence],
sequence0Bits3Thru15: s.bits3Thru15,
immutable: from.immutable,
temporary: from.temporary,
zeroSize: from.zeroSize,
filePageLo: from.filePageLo,
type: from.type,
vp: NULL];
IF from.type IN PilotFileTypes.PilotVFileType THEN
to.vp ← volumeFile[filePageHi: from.filePageHi]
ELSE to.vp ← normalFile[PackDiskAddress[LOOPHOLE[from.bootChainLink]]];
END;
Seal: PROCEDURE [disk: DiskHandle] RETURNS [DeviceHandle] =
BEGIN RETURN[LOOPHOLE[disk]] END;
UnpackLabel: PROCEDURE [to: LONG POINTER TO PilotDisk.Label, from: LONG POINTER TO RDC.PackedLabel] =
INLINE BEGIN
to↑ ← [
fileID: [SystemInternal.UniversalID[
processor: [physical[a: from.processorID0, b: from.processorID1, c: from.processorID2]],
sequence: LOOPHOLE[Environment.Long[num[
lowbits: from.sequence1,
highbits: from.sequence0Bit2*8192+from.sequence0Bits3Thru15]]]]],
filePageLo: from.filePageLo,
filePageHi: 0,
immutable: from.immutable,
temporary: from.temporary,
zeroSize: from.zeroSize,
type: from.type,
bootChainLink: LOOPHOLE[LONG[0]]];
IF from.type IN PilotFileTypes.PilotVFileType THEN
to.filePageHi ← from.filePageHi
ELSE to.bootChainLink ← LOOPHOLE[UnpackDiskAddress[from.bootChainLink]];
END;
END.
LOG
Time: August 8, 1979 1:09 AMBy: RedellAction: Add log to file from Jarvis
Time: August 17, 1979 1:27 PMBy: GobbelAction: Pilot formatting
Time: August 22, 1979 12:40 PMBy: GobbelAction: Bring up to date with revised device face
Time: October 2, 1979 11:38 PMBy: RedellAction: Further cleanup, esp for bootchains (partial label verification)
Time: October 8, 1979 9:22 AMBy: McJonesAction: Restart controller bug, add hardwareState
Time: October 10, 1979 3:53 PMBy: McJonesAction: INLINE procedures for speed; labelCheck is cue to call StartLabelFix
Time: October 30, 1979 11:04 AMBy: McJonesAR2643: Label fix didn’t reset iocb.label from user’s label
Time: November 1, 1979 1:20 PMBy: FrandeenAction: Fix recalibrate after header error; bug in SynchronizeWithController
Time: November 7, 1979 1:17 PMBy: McJonesAR2739: Label fix up confused by spurious labelCheck status returned by read label command on sector with missing (label?) synch byte
Time: November 9, 1979 8:59 AMBy: FrandeenAction: Implement runs of pages. Change interface to Poll. Change LabelFix procedures. Change interface to StartB since we no longer need an extra IOCB for label fixup.
Time: December 13, 1979 11:29 AMBy: GobbelAction: Change name from SA4000D0Head to SA4000HeadD0
Time: January 30, 1980 12:48 PMBy: McJonesAction: Update to match new face; add StartChain logic
Time: June 25, 1980 10:34 AMBy: McJonesAction: 48-bit processor ids
Time: July 24, 1980 11:23 AMBy: McJonesAction: Label fixup must update iocb.operation.clientHeader