FloppyHeadDLion.mesa
Copyright Ó 1982, 1985, 1987 by Xerox Corporation. All rights reserved.
Tim Diebert: May 7, 1987 9:18:53 am PDT
<< Comments: This is a revision of SA800HeadDLion.mesa to conform to changes made in floppyDiskFace.mesa to make the driver device independent. Cosmetic changes have been made such as renaming of variables, new definitions of certain variables ei. some status parameters will become device attribute parameters. Some procedure input and output parameters will change. Bigest change is in the polling routine, retries will be made instead of having them done at the drive level. >>
DIRECTORY
Basics USING [BITAND, BITOR, DivMod, LowHalf],
DeviceCleanup USING [Await, Item, Reason],
DeviceTypes USING [sa800, sa850],
DLionInputOutput USING [floppyCSBOffset, floppyCSBMax, floppyIOCBAddressOffset, floppyIOCBAddressMax, IOPage],
FloppyDiskFace,
PrincOps USING [Alignment, Base],
PrincOpsUtils USING [LongCopy];
FloppyHeadDLion:
PROGRAM
IMPORTS Basics, DeviceCleanup, DLionInputOutput, PrincOpsUtils
EXPORTS FloppyDiskFace
SHARES FloppyDiskFace =
BEGIN OPEN FloppyDiskFace;
********************************************************************
C S B
The Dandelion has at most one Shugart SA80x or SA85x floppy disk. The CSB resides in a resident page of memory (the IOPage).
********************************************************************
CSB: TYPE = RECORD [transferMask: WORD]; -- naked notify mask.
csb: LONG POINTER TO CSB = LOOPHOLE[DLionInputOutput.IOPage + DLionInputOutput.floppyCSBOffset];
compileCheckFloppyCSBSize:
BOOLEAN[
TRUE..
TRUE] =
(DLionInputOutput.floppyCSBMax >= SIZE[CSB]);
nextIOCBAddress: LONG POINTER TO IOPBlockPtr = LOOPHOLE[DLionInputOutput.IOPage + DLionInputOutput.floppyIOCBAddressOffset];
compileCheckNextIOCBAddressSize:
BOOLEAN[
TRUE..
TRUE] =
(DLionInputOutput.floppyIOCBAddressMax >= SIZE[IOPBlockPtr]);
********************************************************************
I O C B (and IOP command blocks)
********************************************************************
IOCBPtr: TYPE = LONG POINTER TO IOCB;
IOPBlockPtr: TYPE = PrincOps.Base RELATIVE POINTER TO IOPBlock;
nilIOP: IOPBlockPtr = LOOPHOLE[0];
IOCB:
TYPE =
MACHINE
DEPENDENT
RECORD [
operation(0): Operation, -- set by head client
iopBlk(8): IOPBlock, -- read/written by IOCB
started(24:0..0): BOOLEAN, -- => IOCB has been submitted to IOP
thisRun(24:1..15): [0..77777B], -- pages/tracks this trip
abortStatus(25): DetailedStatus, -- saved status. An operation may be aborted after it passes Initiate (new Disk Change while it is in the queue). This saves the status so the user finds out why it failed.
nextIOCB(26): IOCBPtr, -- points to next IOCB in chain
triesDone(28): CARDINAL, -- crc/dataLost/index retries Done
recNotFoundTries(29): CARDINAL, -- record not found retries count
totalRetries(30): CARDINAL, -- total of all retries done
writeProtection(31): BOOLEAN]; -- Context WriteProtection
IOPBlock:
TYPE =
MACHINE
DEPENDENT
RECORD [
bufferAddr(0:0..31): LONG POINTER, -- address disk buffer (unused if format)
unused(2): UNSPECIFIED ← 0,
sectorLength(3): CARDINAL, -- sector length(bytes)(unused if format)
sectorFormat(4): FormatWord, -- sector Format
diskAddress(5:0..31): DiskAddress, -- cylinder, head and sector
sectorCount(7): CARDINAL, -- sectors to transfer <= sectorPerTrack (if format, track count)
result(8): IOPResult, -- status posted by IOP
samePage(9:0..0): BOOLEAN, -- TRUE => use same memory page for all sectors (unused if format)
command(9:1..15): Command, -- IOP command word
subCommand(10): SubCommand, -- IOP sub-command word (IOP=esc Only)
formatInfo(11:0..31): FormattingInfo, -- used only in format
reserved(13:0..47): ARRAY [0..3) OF UNSPECIFIED];
FormattingInfo:
TYPE =
MACHINE
DEPENDENT
RECORD [
sectorLengthDiv4(0:0..7): CARDINAL [0..400B), -- sector length ((bytes)/4) MOD 256
encodedSectorLength(0:8..15): EncodedSectorLength, -- encoded sector length
sectorsPerTrack(1:0..7): CARDINAL [0..400B),
gap3FillCharsCount(1:8..15): CARDINAL [0..400B)]; -- interrecord gap count
order of these guys is significant to Head code as well as IOP
Command: TYPE = MACHINE DEPENDENT {nopC(0), readSectorC, writeSectorC, writeDeletedSectorC, readIDC, formatTrackC, recalibrateC, initializeC, escapeC, (77777B)};
SubCommand: TYPE = MACHINE DEPENDENT {nopSC(0), diskChangeClearSC, (177777B)};
EncodedSectorLength: TYPE = MACHINE DEPENDENT {b128(0), b256(1), b512(2), b1024(3), bUnknown};
FormatWord:
TYPE =
MACHINE
DEPENDENT
RECORD [
troyOrIBM(0:0..11): MACHINE DEPENDENT {IBM(0), Troy(1), (7777B)},
density(0:12..15): MACHINE DEPENDENT {single(0), double(10B), (17B)}];
IOPResult:
TYPE =
MACHINE
DEPENDENT
RECORD [
doorOpened(0:0..0): BOOLEAN, -- TRUE => operator opened disk door latched until DiskChangeClear[] AND ~notReady
unused1(0:1..1): BOOLEAN ← FALSE,
twoSided(0:2..2): BOOLEAN, -- true => two sided diskette installed
diskID(0:3..3): MACHINE DEPENDENT {SA800(0), SA850(1)},
error(0:4..4): BOOLEAN, -- IOP detected an error in the IOCB
unused2(0:5..5): BOOLEAN ← FALSE,
recalibrateError(0:6..6): BOOLEAN, -- IOP had error during recalibrate op
dataLost(0:7..7): BOOLEAN, -- data transfer not completed
notReady(0:8..8): BOOLEAN, -- drive has door open
writeProtect(0:9..9): BOOLEAN, -- write-protected diskette now in drive
deletedData(0:10..10): BOOLEAN, -- read a deleted data sector
recordNotFound(0:11..11): BOOLEAN, -- some sector in transfer not found
crcError(0:12..12): BOOLEAN, -- CRC error on read op
track00(0:13..13): BOOLEAN, -- read heads left on cylinder 0
unused3(0:14..14): BOOLEAN ← FALSE,
busy(0:15..15): BOOLEAN]; -- => chip busy, should always be FALSE
This type is used to describe the detailed status of the operation requested in contrast to Status found in the face which reports back a general status to the channel.
DetailedStatus:
TYPE =
MACHINE
DEPENDENT
RECORD [
diskChange: BOOLEAN, --Disk drive has apparently gone to a not ready (door open) state since the last operation was performed. Only DiskChangeClear will reset this status.
na1: BOOLEAN, --UNUSED
twoSided: BOOLEAN, --The diskette currently installed is two-sided.
na3: BOOLEAN, --UNUSED
error: BOOLEAN, --This status record indicates an error
inProgress: BOOLEAN, --The operation is not yet complete
recalibrateError: BOOL, --recalibrate subfunction failed to sense track00.
dataLost: BOOLEAN, -- data transfer requested was larger than expected.
notReady: BOOLEAN, -- The drive is not ready.
writeProtect: BOOLEAN, --Logical OR of the context setting of protect and the physical signal being returned from the drive.
deletedData: BOOLEAN, --The sector contained a deleted data address mark.
recordNotFound: BOOL, --The record defined by the disk address could not be found.
crcError: BOOLEAN, --A CRC error was encountered on either the record ID or data field.
track00: BOOLEAN, --Read/Write heads are currently positioned over track00, the outermost track.
index: BOOLEAN, --An index pulse signal was detected concurrent with the posting of this status.
busy: BOOLEAN]; --The drive is not idle.
********************************************************************
C O N S T A N T S
(canned stati can be found at the end of the module (so they won't lexically pollute code readers)
********************************************************************
commandMatrix:
ARRAY Function
OF Command = [
-- face => IOP command
nopC, readSectorC, writeSectorC, writeDeletedSectorC, readIDC, formatTrackC];
nullIOCB:
IOCB = [
operation: [device: LOOPHOLE[0], function: nop, incrementDataPointer: FALSE, tries: 1,
count: 0, address: [cylinder: 0, head: 0, sector: 0], dataPtr: NIL],
iopBlk: nullIOPBlk, started: FALSE, thisRun: 0,
abortStatus: initialStatus, nextIOCB: NIL, triesDone: 0,
recNotFoundTries: 0, totalRetries: 0, writeProtection: FALSE];
nullIOPBlk: IOPBlock =[
bufferAddr: NIL,
unused: 0,
sectorLength: 0,
sectorFormat: [troyOrIBM: IBM, density: single],
diskAddress: [cylinder: 0, head: 0, sector: 0],
sectorCount: 0,
result: nullIOPResult,
samePage: FALSE, command: nopC, subCommand: nopSC,
formatInfo: [0, b128, 0, 0],
reserved: [0, 0, 0]];
nullIOPResult: IOPResult = [
doorOpened: FALSE, twoSided: FALSE, diskID: SA800, error: FALSE,
recalibrateError: FALSE, dataLost: FALSE, notReady: FALSE,
writeProtect: FALSE, deletedData: FALSE, recordNotFound: FALSE,
crcError: FALSE, track00: FALSE, busy: FALSE];
nullDeviceHandle: PUBLIC DeviceHandle ← LOOPHOLE[0];
diskDeviceHandle: DeviceHandle = LOOPHOLE[1];
internalDeviceHandle: DeviceHandle = LOOPHOLE[2]; -- for internally generated IOCBs.
trackParameterLength: CARDINAL = 0; -- space needed for FormatTrack parameters
idLength: CARDINAL = 3; -- length of field ID in words
numOfCylinders: CARDINAL = 77; -- number of cylinders on SA800 and SA850 disks
initialAllocationLength: PUBLIC CARDINAL ← SIZE[IOCB];
operationBlockLength: PUBLIC CARDINAL ← SIZE[IOCB];
operationAlignment: PUBLIC PrincOps.Alignment ← a4;
Constants related to retry logic
maxRecNotFoundRetries: CARDINAL = 3;
maxRetries: CARDINAL = 30;
retryLimit: CARDINAL = 10;
standardRetries: Tries = 200B;
********************************************************************
G L O B A L V A R I A B L E S
********************************************************************
initializationIOCB: IOCBPtr;
state:
RECORD [
user: Context
--user accessable state
← [protect: FALSE, format: IBM, density: double, sectorLength: 256],
status: DetailedStatus ← diskChange, --most currently processed status
result: IOPResult ← nullIOPResult, --most currently processed IOP result
sectorsPerTrack: CARDINAL ← 15, --function[sectorLength, density]
encodedSectorLength: EncodedSectorLength ← b512]; --function[sectorLength]
tail: IOCBPtr;
iocbContext: Context ← [protect: FALSE, format: IBM, density: double, sectorLength: 256];
****************************
PUBLIC PROCEDURES
****************************
Do not submit an IOCB if there is nothing to do. This also eliminates the possiblilty of interrupting an existing IOCB. Since the occurence of a new diskChange error causes the IOCB queue to be cleared, the IOCB about to be submitted will arrive at the Head of the queue so it may be Polled immediately.
DiskChangeClear:
PUBLIC
PROCEDURE [DeviceHandle] =
BEGIN
ready: BOOLEAN;
IF ~state.status.diskChange THEN RETURN;
state.status.diskChange ← FALSE;
ready ← ~(Immediate[escapeC, diskChangeClearSC, TRUE] = notReady);
if there is a diskette inserted (notReady=FALSE) then recalibrate so the FDC and IOP are synchronized. When the heads are recalibrated they are loaded => door lock engaged. If there were no disk, the FDC would ever see index pulses and never ever unload the heads, leaving the door locked which is mildly embarrassing. This may also be the first recalibrate if there was no disk loaded when the system was initialized.
IF ready THEN [] ← Immediate[recalibrateC, nopSC, FALSE]
END;
Assumes an "Initialize" operation has been performed so "state.status" holds valid state bits
GetDeviceAttributes:
PUBLIC
PROC [DeviceHandle]
RETURNS [Attributes] =
BEGIN
RETURN[[
type: IF state.result.diskID = SA800 THEN DeviceTypes.sa800 ELSE DeviceTypes.sa850, --add later
numberOfHeads: IF ~state.status.twoSided THEN 1 ELSE 2,
numberOfCylinders: numOfCylinders,
maxSectorsPerTrack: LOOPHOLE[MaxSectorsPerTrack[state.user].maxSectorsPerTrack],
formatLength: trackParameterLength,
ready: IF state.status.notReady THEN FALSE ELSE TRUE,
diskChange: state.status.diskChange,
twoSided: state.status.twoSided,
busy: state.status.busy
]];
END;
ASSUMES EXACTLY ONE FLOPPY DRIVE IS CONNECTED TO THE DANDELION
GetNextDevice:
PUBLIC
PROC [device: DeviceHandle]
RETURNS [DeviceHandle] =
{RETURN[IF device = nullDeviceHandle THEN diskDeviceHandle ELSE nullDeviceHandle]};
GetContext: PUBLIC PROC [DeviceHandle] RETURNS [Context] = {RETURN[state.user]};
This code will only handle a single controller/single device configuration.
Initialize:
PUBLIC
PROC [notify:
WORD, initialAllocation:
LONG
POINTER] =
BEGIN
csb^ ← [transferMask: notify];
nextIOCBAddress^ ← nilIOP;
tail ← NIL;
initializationIOCB ← initialAllocation;
initializationIOCB^ ← nullIOCB;
initializationIOCB.operation.device ← internalDeviceHandle;
[] ← Immediate[initializeC, nopSC, TRUE];
state.status.diskChange ← TRUE;
END;
InitializeCleanup:
PUBLIC
PROC =
BEGIN
item: DeviceCleanup.Item; nextIOCBAddr: IOPBlockPtr; sCsb: CSB;
DO
reason: DeviceCleanup.Reason = DeviceCleanup.Await[@item];
SELECT reason
FROM
turnOff, kill => {
WHILE nextIOCBAddress^ # nilIOP DO ENDLOOP;
sCsb ← csb^;
nextIOCBAddr ← nextIOCBAddress^};
turnOn => {
csb^ ← sCsb;
nextIOCBAddress^ ← nextIOCBAddr};
ENDCASE
ENDLOOP
END;
This procedure initiates an operation. The client passes an IOCB with most 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 an wake up the microcode.
Initiate:
PUBLIC
PROC [o: OperationPtr]
RETURNS [status: Status] = {
OPEN iocb: LOOPHOLE[o, IOCBPtr];
IF o.device # diskDeviceHandle
AND o.device # internalDeviceHandle
THEN
RETURN[GetStatusForChannel[LOOPHOLE[Basics.BITOR[LOOPHOLE[statusError], Basics.BITAND[LOOPHOLE[infoMask], LOOPHOLE[state.status]]]]]];
IF o.device = diskDeviceHandle THEN iocb.iopBlk ← nullIOPBlk;
iocb.nextIOCB ← NIL;
iocb.started ← FALSE;
iocb.abortStatus ← inProgress;
iocb.triesDone ← 0;
iocb.recNotFoundTries ← 0;
iocb.totalRetries ← 0;
iocbContext ← state.user;
IF (status ← GetStatusForChannel[CheckIOCB[@iocb]]) = inProgress
THEN {
IF nextIOCBAddress^ = nilIOP
THEN RunIOCB[@iocb] --empty queue
ELSE {SetupIOCBContext[@iocb]; tail.nextIOCB ← @iocb}; --queue not empty
tail ← @iocb; --last iocb address set to this iocb
}
ELSE iocb.started ←
TRUE;
return (error) status if somebody polls this iocb.
};
Poll:
PUBLIC
PROC [o: OperationPtr]
RETURNS [status: Status, retriedCount:
CARDINAL] =
{ OPEN iocb: LOOPHOLE[o, IOCBPtr];
retriedCount ← 0;
IF ~iocb.abortStatus.inProgress THEN
RETURN[GetStatusForChannel[iocb.abortStatus], retriedCount];
IOCB still in process or was never started --
IF nextIOCBAddress^ # nilIOP OR ~iocb.started THEN
RETURN[GetStatusForChannel[LOOPHOLE[Basics.BITOR[LOOPHOLE[inProgress], Basics.BITAND[LOOPHOLE[state.status], LOOPHOLE[infoMask]]]]], retriedCount];
status ← GetStatusForChannel[GetStatus[@iocb]];
IF status = inProgress
THEN {
iocbContext ← [protect: iocb.writeProtection,
format: IF iocb.iopBlk.sectorFormat.troyOrIBM = IBM THEN IBM ELSE Troy,
density: IF iocb.iopBlk.sectorFormat.density = double THEN double ELSE single,
sectorLength: iocb.iopBlk.sectorLength];
RunIOCB[@iocb]
}
ELSE {
IF o.tries # noRetries
AND status # goodCompletion
THEN
-- Retry operation
{ status ← RetryOperation[@iocb];
RETURN[status, iocb.totalRetries]};
FOR newIOCB: IOCBPtr ← iocb.nextIOCB, newIOCB.nextIOCB
WHILE newIOCB #
NIL
DO
newIOCB.started ← TRUE;
IF CheckIOCB[newIOCB].inProgress
THEN {
iocbContext ← [protect: newIOCB.writeProtection,
format: IF newIOCB.iopBlk.sectorFormat.troyOrIBM = IBM THEN IBM ELSE Troy,
density: IF newIOCB.iopBlk.sectorFormat.density = double THEN double ELSE single,
sectorLength: newIOCB.iopBlk.sectorLength];
RunIOCB[newIOCB];
EXIT};
ENDLOOP;
}
};
Reset: PUBLIC PROC [DeviceHandle] = { nextIOCBAddress^ ← nilIOP; tail ← NIL };
This Routine determines the max number of tracks according to the current context setting.
MaxSectorsPerTrack:
PROC [context: Context]
RETURNS[maxSectorsPerTrack:
CARDINAL, byteIndex: EncodedSectorLength] =
{ sectorsPerTrack:
ARRAY EncodedSectorLength[b128..b1024]
OF
ARRAY Density
OF
CARDINAL
= [ -- Single density Double Density
-- 64w-- [26, 36],
-- 128w-- [15, 26],
-- 256w-- [ 8, 15],
-- 512w-- [ 4, 8]];
byteIndex ← SELECT context.sectorLength FROM
64 => b128, 128 => b256, 256 => b512, 512 => b1024, ENDCASE => bUnknown;
maxSectorsPerTrack ← sectorsPerTrack[byteIndex][IF context.density = single THEN single ELSE double];
};
This routine requires that the device be idle (i.e., the IOCB chain to be NIL) as well as all other context to be correct before anything is changed in the head's context.
SetContext:
PUBLIC
PROC [device: DeviceHandle, context: Context]
RETURNS [
BOOLEAN] =
BEGIN
byteIndex: EncodedSectorLength;
[state.sectorsPerTrack, byteIndex] ← MaxSectorsPerTrack[context];
IF (byteIndex=bUnknown) OR (nextIOCBAddress^ # nilIOP) OR (context.format#IBM)
THEN RETURN[FALSE];
state.user ← context;
state.sectorsPerTrack ← MaxSectorsPerTrack[context].maxSectorsPerTrack;
state.encodedSectorLength ← byteIndex;
RETURN[TRUE];
END;
Suggested Tries for standard recovery algorithm
SuggestedTries:
PUBLIC
PROC [device: DeviceHandle]
RETURNS[tries: Tries] =
{ -- use standard retry algorithm -- RETURN[standardRetries] };
Get detailed log status Gives more complete status info to be logged in case of error
GetLogStatus:
PUBLIC
PROC [operationPtr: OperationPtr]
RETURNS [logStatus: LogStatus] =
{ logStatus ←
DESCRIPTOR[@state.status, 1];
RETURN[logStatus] };
Largest track count that can be supplied for formatting in Words
MaxTracksPerFormatOperation:
PUBLIC
PROC [device: DeviceHandle]
RETURNS[maxTracks:
CARDINAL] =
{maxTracks ← numOfCylinders; IF state.status.twoSided THEN maxTracks ← maxTracks * 2};
************************ PRIVATE PROCEDURES ************************
BuildIOCB:
PROCEDURE [iocb: IOCBPtr] =
BEGIN
OPEN o: iocb.operation;
b: LONG POINTER TO IOPBlock = @iocb.iopBlk;
runLength: CARDINAL;
IF o.device = internalDeviceHandle THEN RETURN;
b.result ← nullIOPResult;
IF state.status.notReady THEN {b.command ← nopC; RETURN};
b.command ← commandMatrix[o.function];
iocb.writeProtection ← iocbContext.protect;
SELECT o.function
FROM
nop => NULL; -- allSetUp
readSector, writeSector, writeDeletedSector, readID =>
BEGIN
OPEN a: o.address;
runLength ← MIN[o.count, state.sectorsPerTrack-(a.sector-1)];
Touch[addr: o.dataPtr,
count: (IF o.function=readID THEN idLength ELSE iocbContext.sectorLength) * (IF o.incrementDataPointer THEN runLength ELSE 1),
dirty: (o.function=readSector OR o.function=readID)];
b.samePage ← ~o.incrementDataPointer;
b.bufferAddr ← o.dataPtr;
b.sectorLength ← iocbContext.sectorLength;
b.sectorFormat ← [
troyOrIBM: IF iocbContext.format = IBM THEN IBM ELSE Troy,
density: IF iocbContext.density = double THEN double ELSE single];
b.diskAddress ← o.address; -- set first disk address
b.sectorCount ← runLength; -- set number of sectors to transfer
iocb.thisRun ← runLength; -- and set run length for getStatus fixup.
END;
formatTrack =>
BEGIN
OPEN a: o.address;
gap3:
ARRAY EncodedSectorLength [b128..b1024]
OF
ARRAY Density
OF
CARDINAL
[512w][single] and [64w][double] are picked out of the air
= [ -- Single density Double Density
-- 64w-- [27, 26],
-- 128w-- [42, 54],
-- 256w-- [58, 84],
-- 512w-- [75, 116]];
b.sectorLength ← iocbContext.sectorLength;
b.formatInfo ← [
sectorLengthDiv4: iocbContext.sectorLength/2, -- (x * 2 bytes/wd)/4
encodedSectorLength: state.encodedSectorLength,
sectorsPerTrack: state.sectorsPerTrack,
gap3FillCharsCount: gap3[state.encodedSectorLength][IF iocbContext.density = double THEN double ELSE single]];
runLength ←
MIN[
o.count, IF state.status.twoSided THEN 1 ELSE (numOfCylinders - a.cylinder)];
b.sectorFormat ← [
troyOrIBM: IF iocbContext.format = IBM THEN IBM ELSE Troy,
density: IF iocbContext.density = double THEN double ELSE single];
b.diskAddress ← o.address;
b.sectorCount ← runLength;
iocb.thisRun ← runLength;
END
ENDCASE => NULL;
END;
A procedure to check an IOCB before it is submitted to the IOP. It attempts to screen out IOCBs which shouldn't even be attempted
CheckIOCB:
PROCEDURE [iocb: IOCBPtr]
RETURNS [status: DetailedStatus] =
BEGIN OPEN a: iocb.operation.address, o: iocb.operation, Basics;
IsWriteOp:
PROC [f: Function]
RETURNS [
BOOLEAN] =
INLINE {RETURN[f=writeSector OR f=writeDeletedSector OR f=formatTrack]};
IF ~iocb.abortStatus.inProgress THEN RETURN[iocb.abortStatus];
status ← inProgress;
IF o.function = formatTrack THEN a.sector ← 1;
SELECT
TRUE
FROM
o.device = internalDeviceHandle => NULL;
state.status.diskChange => status ← diskChange;
(IsWriteOp[o.function] AND (iocbContext.protect OR state.status.writeProtect)) =>
status ← writeProtect;
(IsTransferOp[o.function] AND (a.cylinder >= numOfCylinders
OR a.head > (IF state.status.twoSided THEN 1 ELSE 0)
OR a.sector NOT IN [1..state.sectorsPerTrack]
OR o.count = 0)) => status ← recordNotFound;
ENDCASE => NULL;
iocb.abortStatus ← status ← LOOPHOLE[BITOR[LOOPHOLE[status], BITAND[LOOPHOLE[infoMask], LOOPHOLE[state.status]]]];
END;
A procedure to produce the status of an IOCB after it has been completed by the IOP. In addition to deciding if the stage just performed had any errors, this procedure decides if there are any more stages to be performed. NOTE: there is no reason to detect abortStatus.inProgress=FALSE since this routine is only called from Poll which will detect this condition before trying to call GetStatus.
GetStatus:
PROCEDURE [iocb: IOCBPtr]
RETURNS [status: DetailedStatus] =
BEGIN OPEN b: iocb.iopBlk, o: iocb.operation;
command: Command = b.command;
result: IOPResult ← b.result; -- done merely for better code
pResult: POINTER TO READONLY IOPResult = @result;
update disk addr and client buffer parameters
BEGIN
OPEN a: o.address;
countDone: CARDINAL ← (iocb.thisRun - b.sectorCount);
SELECT command
FROM
formatTrackC =>
BEGIN
o.count ← o.count - countDone;
IF countDone#0
THEN
SELECT
TRUE
FROM
if twoSided, countDone can must <= 1
b.result.twoSided AND (a.head = 0) => a.head ← 1;
ENDCASE => {a.head ← 0; a.cylinder ← a.cylinder + countDone};
END;
readSectorC, readIDC, writeSectorC, writeDeletedSectorC =>
BEGIN
o.count ← o.count - countDone;
IF o.incrementDataPointer
THEN
o.dataPtr ← o.dataPtr
+ (IF command=readIDC THEN idLength ELSE iocbContext.sectorLength) * countDone;
[quotient: countDone, remainder: a.sector]
← Basics.DivMod[(a.sector-1) + countDone, state.sectorsPerTrack];
a.sector ← a.sector + 1;
since we break runs at tracks, countDone <= 1
IF countDone#0
THEN
SELECT
TRUE
FROM
b.result.twoSided AND (a.head = 0) => a.head ← 1;
ENDCASE => {a.head ← 0; a.cylinder ← a.cylinder + countDone};
END;
ENDCASE => NULL;
END;
status ← initialStatus; -- ← ALL[FALSE];
status.diskChange ← state.status.diskChange
OR (state.status.notReady AND ~pResult.notReady)
OR (~state.status.notReady AND (pResult.notReady OR pResult.doorOpened));
status.twoSided ← pResult.twoSided;
status.na3 ← FALSE;
(← TRUE) SA800 since Mokelumne pilot thinks this is a hardware error bit
status.notReady ← pResult.notReady;
status.writeProtect ← iocbContext.protect OR pResult.writeProtect;
status.na1 ← FALSE;
status.index ← FALSE; ++ IOP never sends an index pulse back
SELECT command
FROM
readSectorC =>
BEGIN
status.deletedData ← pResult.deletedData;
status.crcError ← pResult.crcError;
status.dataLost ← pResult.dataLost;
status.recordNotFound ← pResult.recordNotFound OR (o.address.cylinder >= numOfCylinders AND o.count > 0);
END;
IN [writeSectorC..readIDC] =>
BEGIN
status.crcError ← pResult.crcError;
status.dataLost ← pResult.dataLost;
status.recordNotFound ← pResult.recordNotFound OR (o.address.cylinder >= numOfCylinders AND o.count > 0);
END;
formatTrackC =>
BEGIN
status.dataLost ← pResult.dataLost;
status.recordNotFound ← pResult.recordNotFound OR (o.address.cylinder >= numOfCylinders AND o.count > 0);
END;
recalibrateC =>
BEGIN
status.recalibrateError ← pResult.recalibrateError;
status.track00 ← pResult.track00;
END;
initializeC, escapeC => status.track00 ← pResult.track00;
ENDCASE;
set error flag if there was an error or the run proceeds off the end of the disk.
status.error ← Basics.BITAND[LOOPHOLE[status], LOOPHOLE[statusError]] # 0;
status.inProgress ← ~status.error AND IsTransferOp[o.function] AND o.count > 0;
status.busy ← status.inProgress OR (iocb.nextIOCB # NIL);
state.status ← iocb.abortStatus ← status;
END; -- GetStatus
Immediate:
PROCEDURE [command: Command, subCommand: SubCommand, nopFunction:
BOOL]
RETURNS [initStatus: Status] =
BEGIN
initializationIOCB.iopBlk.command ← command;
initializationIOCB.iopBlk.subCommand ← subCommand;
IF nopFunction THEN initializationIOCB.operation.function ← nop;
initializationIOCB.operation.device ← internalDeviceHandle;
initializationIOCB.operation.tries ← 1;
initStatus ← Initiate[@initializationIOCB.operation];
WHILE initStatus = inProgress
DO
initStatus ← Poll[@initializationIOCB.operation].status
ENDLOOP
END;
IsTransferOp:
PROC [f: Function]
RETURNS [b:
BOOLEAN] =
BEGIN
b ←
SELECT f
FROM
readSector, readID, writeSector, writeDeletedSector, formatTrack => TRUE,
ENDCASE => FALSE;
END;
RunIOCB:
PROC [iocb: IOCBPtr] =
BEGIN
foo: LONG POINTER TO IOPBlock ← @iocb.iopBlk;
BuildIOCB[iocb];
iocb.started ← TRUE;
IF nextIOCBAddress^ = nilIOP
THEN
nextIOCBAddress^ ← LOOPHOLE[Basics.LowHalf[LOOPHOLE[foo]]];
END;
SetupIOCBContext:
PROC [iocb: IOCBPtr] =
BEGIN
b: LONG POINTER TO IOPBlock = @iocb.iopBlk;
iocb.writeProtection ← iocbContext.protect;
b.sectorLength ← iocbContext.sectorLength;
b.sectorFormat ← [troyOrIBM: IF iocbContext.format = IBM THEN IBM ELSE Troy, density: IF iocbContext.density = double THEN double ELSE single];
END;
change this to use checksum when implemented in microcode
Touch:
PROC [addr:
LONG
POINTER, count:
CARDINAL, dirty:
BOOLEAN] =
BEGIN
IF dirty
THEN {
next: LONG POINTER = addr + 1;
IF count # 0
THEN {
addr^ ← 0;
PrincOpsUtils.LongCopy[from:addr, to: next, nwords: count-1] };
}
ELSE
THROUGH [0..count)
DO dummy:
CARDINAL ← addr^;
addr ← addr + 1;
ENDLOOP;
END;
GetStatusForChannel converts the detailed status into the status expected back by the channel which is defineds in the face.
GetStatusForChannel:
PROC [inStatus: DetailedStatus]
RETURNS[outStatus: Status] = {
SELECT
TRUE
FROM
inStatus.inProgress => outStatus ← inProgress; -- operation is not yet complete
inStatus.diskChange => outStatus ← diskChange; -- drive has gone not ready since last successful operation (use DiskChangeClear to reset, then resubmit operation if desired)
inStatus.notReady => outStatus ← notReady; -- drive is not ready
Not Implemented => outStatus ← cylinderError; -- can't locate specified cylinder
inStatus.deletedData => outStatus ← deletedData; -- sector contained deleted data mark
inStatus.recordNotFound => outStatus ← recordNotFound; -- can't find record for specified disk address
inStatus.crcError => outStatus ← headerError; -- bad checksum in header
Not Implemented => outStatus ← dataError; -- bad checksum in data
inStatus.dataLost => outStatus ← dataLost; -- sector contained more data than expected (from context)
inStatus.writeProtect => outStatus ← writeFault; -- disk is write-protected (hardware or from context)
Not Implemented => outStatus ← memoryError; -- dataPtr does not point to valid memory (not resident, too small write-protected, etc.)
Not Implemented => outStatus ← invalidOperation; -- operation does not make sense
Not Implemented => outStatus ← aborted; -- Reset has been called
inStatus.error => outStatus ← otherError; -- unexpected software or hardware problem
ENDCASE => outStatus ← goodCompletion; -- operation has completed normally
};
RetryOperation:
PROC [iocb: IOCBPtr]
RETURNS [status: Status] ={
multipleOfTen: CARDINAL ← 0;
oneThird: CARDINAL = maxRetries / 3;
recover: BOOLEAN ← FALSE;
IF iocb.operation.tries = standardRetries
THEN {
iocb.totalRetries ← iocb.totalRetries + 1;
SELECT
TRUE
FROM
state.status.recordNotFound =>
{
-- Always recover/recalibrate after record not found
IF iocb.recNotFoundTries = 0 THEN recover ← TRUE;
status ← Recalibrate[recover]; -- recalibrate/recover
iocb.recNotFoundTries ← iocb.recNotFoundTries + 1;
IF status # goodCompletion -- recal failed
OR iocb.recNotFoundTries > maxRecNotFoundRetries
THEN
state.status holds results from recalibration so use
iocb.abortStatus as return value
RETURN[GetStatusForChannel[iocb.abortStatus]];
RETURN[ReInitiate[iocb]]; -- re Initiate IOCB
};
state.status.dataLost, state.status.crcError, state.status.index =>
{
-- Recalibrate every tenth fail and do an
extra recalibrate at end of a failing operation.
iocb.triesDone ← iocb.triesDone + 1;
multipleOfTen ← iocb.triesDone MOD oneThird;
IF multipleOfTen = 0
OR iocb.triesDone > maxRetries
THEN
{
IF iocb.triesDone = oneThird
THEN recover ←
TRUE;
status ← Recalibrate[recover];
IF status # goodCompletion
OR iocb.triesDone > maxRetries
THEN
state.status holds results from recalibration so use
iocb.abortStatus as return value
RETURN[GetStatusForChannel[iocb.abortStatus]];
};
RETURN[ReInitiate[iocb]]; -- re Initate IOCB
};
ENDCASE =>
-- other errors are fatal don't reInitiate return
RETURN[GetStatusForChannel[state.status]];
}
-- End Standard Retry Routine
Use state.status since don't didn't do a recalibrate
ELSE RETURN[GetStatusForChannel[state.status]];
};
Recalibrate:
PROC [recover:
BOOLEAN]
RETURNS[status: Status] =
{ command: Command ← recalibrateC;
IF recover THEN command ← nopC;
IF recalibrate fails after 10 trys return
FOR retry:
CARDINAL
IN [0..retryLimit)
DO
status ← Immediate[command, nopSC, recover];
IF status = goodCompletion THEN EXIT;
ENDLOOP;
ReInitiate:
PROC [iocb: IOCBPtr]
RETURNS [status: Status] =
{
OPEN o:
LOOPHOLE[iocb, OperationPtr];
iocbContext ← [protect: iocb.writeProtection,
format: IF iocb.iopBlk.sectorFormat.troyOrIBM = IBM THEN IBM ELSE Troy,
density: IF iocb.iopBlk.sectorFormat.density = double THEN double ELSE single,
sectorLength: iocb.iopBlk.sectorLength];
IF o.device = diskDeviceHandle THEN iocb.iopBlk ← nullIOPBlk;
iocb.nextIOCB ← NIL;
iocb.started ← FALSE;
iocb.abortStatus ← inProgress;
IF (status ← GetStatusForChannel[CheckIOCB[iocb]]) = inProgress
THEN RunIOCB[iocb]
return (error) status if somebody polls this iocb.
ELSE iocb.started ← TRUE;
};
********************************************************************
Constants representing Status bit settings
********************************************************************
recordNotFound: DetailedStatus = [diskChange:
FALSE, na1:
FALSE, twoSided:
FALSE,
na3:
FALSE, error:
TRUE, inProgress:
FALSE, recalibrateError:
FALSE, dataLost:
FALSE, notReady:
FALSE, writeProtect:
FALSE, deletedData:
FALSE, recordNotFound:
TRUE, crcError:
FALSE, track00:
FALSE, index:
FALSE, busy:
FALSE];
notReady: DetailedStatus = [diskChange:
FALSE, na1:
FALSE, twoSided:
FALSE, na3:
FALSE,
error:
TRUE, inProgress:
FALSE, recalibrateError:
FALSE, dataLost:
FALSE,
notReady:
TRUE, writeProtect:
FALSE, deletedData:
FALSE, recordNotFound:
FALSE, crcError:
FALSE, track00:
FALSE, index:
FALSE, busy:
FALSE];
diskChange: DetailedStatus = [diskChange:
TRUE, na1:
FALSE, twoSided:
FALSE, na3:
FALSE, error:
TRUE, inProgress:
FALSE, recalibrateError:
FALSE, dataLost:
FALSE,
notReady:
FALSE, writeProtect:
FALSE, deletedData:
FALSE, recordNotFound:
FALSE, crcError:
FALSE, track00:
FALSE, index:
FALSE, busy:
FALSE];
initialStatus: DetailedStatus = LOOPHOLE[0]; -- ALL[FALSE]
inProgress: DetailedStatus = [diskChange:
FALSE, na1:
FALSE, twoSided:
FALSE, na3:
FALSE,
error:
FALSE, inProgress:
TRUE, recalibrateError:
FALSE, dataLost:
FALSE,
notReady:
FALSE, writeProtect:
FALSE,deletedData:
FALSE, recordNotFound:
FALSE, crcError:
FALSE, track00:
FALSE, index:
FALSE, busy:
TRUE];
writeProtect: DetailedStatus = [diskChange:
FALSE, na1:
FALSE, twoSided:
FALSE, na3:
FALSE, error:
TRUE, inProgress:
FALSE, recalibrateError:
FALSE, dataLost:
FALSE,
notReady:
FALSE, writeProtect:
TRUE, deletedData:
FALSE, recordNotFound:
FALSE, crcError:
FALSE, track00:
FALSE, index:
FALSE, busy:
FALSE];
statusError: DetailedStatus = [diskChange:
TRUE, na1:
FALSE, twoSided:
FALSE, na3:
FALSE,
error:
TRUE, inProgress:
FALSE, recalibrateError:
TRUE, dataLost:
TRUE,
notReady:
TRUE, writeProtect:
FALSE, deletedData:
FALSE, recordNotFound:
TRUE, crcError:
TRUE, track00:
FALSE, index:
FALSE, busy:
FALSE];
infoMask: DetailedStatus = [diskChange: FALSE, na1: FALSE, twoSided: TRUE, na3: FALSE,
error: FALSE, inProgress: FALSE, recalibrateError: FALSE, dataLost: FALSE,
notReady: FALSE, writeProtect: TRUE, deletedData: FALSE, recordNotFound: FALSE, crcError: FALSE, track00: TRUE, index: FALSE, busy: FALSE];