SA4000HeadDLion.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Forrest on: February 14, 1981 2:25 PM
Sturgis: July 22, 1983 9:50 am
Bob Hagmann: May 9, 1984 5:49:45 pm PDT
Willie-Sue, January 31, 1985 2:35:39 pm PST
Russ Atkinson (RRA) February 19, 1985 3:52:50 pm PST
Carl Hauser, November 15, 1985 10:26:58 am PST
Things to do/dubious features:
1. If more than one drive, or if different kinds of Shugarts are used, Get attributes (at least) has to be fixed.
2 Some of the error flags returned will be different than those returned by the D0. For example, the Dandelion controller cannot simultaneously read and verify header fields. As a consequence, whenever an error is encountered during a header operation, the "wrongSector" status is returned. It is also not possible to issue any sort of timeout flag since the Head has no clock.
3. Runs of Header noops/reads are supported, they are done a page-at-a-time on the SA1000; more work probably has to be done here and for rr and rrr.
4. The calculation of the low part of the IOCB's read address depends upon knowledge that IOCB's live in the first 64K, as well as the page number being in the rightmost part of the flag word.
5. Rename (within mesa) some of the fields within the IOCB. For example, the incrementDataPtr bit is the high order bit of the DataPageNumber, and should be a seperate boolean rather than 100000B. Similarly, the head bits are now or'ed into freezeCmd, but should be seperatly defined.
6. Consider calculating the low half of the physical IOCB address once in initiate (and stashing it in the IOCB) rather then each time through the Next IOCB loop.
7. Does deviceCleanup do the right thing.
8. what should incrementDataPtr on ?,?,nop do??
9. get rid of spin counters someday.
10. The increment field of the IOCB is the bit 0 of the page number. this conflicts with the fat memory boards and with large virtual address spaces and should be moved.
DIRECTORY
Basics USING[ BITAND, BITNOT, BITOR, LongNumber, LongMult],
DDC,
DeviceCleanup USING [Await, Item, Reason],
DiskFace USING [Label],
DLionInputOutput USING [GetRealPage, IOPage, Input, Output],
PrincOps USING [wordsPerPage],
PrincOpsUtils USING [LowHalf, PageNumberForAddress],
SA4000Face,
SA4000HeadDLionConstants;
SA4000HeadDLion: PROGRAM
IMPORTS Basics, DeviceCleanup, DLionInputOutput, PrincOpsUtils
EXPORTS SA4000Face
SHARES SA4000Face = { OPEN DDC, SA4000Face, SA4000HeadDLionConstants;
TYPE DEFINITIONS
Controller status block, only one per DLion.
Only state.next is known by microcode.
Base: TYPE = LONG BASE POINTER;
CSB: TYPE = MACHINE DEPENDENT RECORD [
cylinder(0): CARDINAL,
next(1): Base RELATIVE POINTER TO ChannelCommand,
tail(2): IOCBshortPtr, -- last iocb. Used by initiate
transferMask(3): WORD, -- naked notify mask.
needRecalibrate(4:0..15): BOOL]; -- TRUE => recalibrate required.
csb: LONG POINTER TO CSB = LOOPHOLE[DLionInputOutput.IOPage+0B];
Microcode implements Pilot file page number layout in labels, but Cedar Face expects INT's
FilePageRep: TYPE = MACHINE DEPENDENT RECORD[ n(0): SELECT OVERLAID * FROM
cedar =>
[cedar(0): INT],
pilot => [
filePageLo(0): CARDINAL,
pilotHi(1): RECORD[
filePageHi(0:0..6): [0..128), -- restricts to 23-bit page numbers (32-bit byte counts)
pad1 (0:7..12): [0..64) ← 0, -- always zero
immutable (0:13..13): BOOLFALSE, -- valid only in label of page 0
temporary (0:14..14): BOOLFALSE, -- valid only in label of page 0
zeroSize (0:15..15): BOOLFALSE
]
],
raw => [
lowHalf(0): CARDINAL,
highHalf(1): CARDINAL
]
ENDCASE
];
CONSTANTS
p : Base = LOOPHOLE[LONG[0]];
step: WORD = 200B; -- as part of Output causes heads to move
directionInBit: WORD = 100B; -- on => heads move inwards (higher # cyls)
driveSelect: WORD = 2000B; -- must be on for drive to listen
clearSA1000WriteFault: WORD = 0B; -- reset disk to clear SA1000 fault
sa1000PagesPerTrack: CARDINAL = 16;
sa1000TracksPerCylinder: CARDINAL = 4; --Assumes SA1004
sa1000Cylinders: CARDINAL = 256;
clearSA4000WriteFault: WORD = 3000B; -- reset disk to clear SA4000 fault
sa4000PagesPerTrack: CARDINAL = 28;
sa4000TracksPerCylinder: CARDINAL = 8; --Assumes SA4008
sa4000Cylinders: CARDINAL = 202;
q2040PagesPerTrack: CARDINAL = 16;
q2040TracksPerCylinder: CARDINAL = 8; -- assumes q2040
q2040Cylinders: CARDINAL = 512;
diskDeviceHandle: DeviceHandle = LOOPHOLE[1]; --#nulldeviceHandle
errorMask: WORD = 77B; -- selects error bits in the device status
KCtl: CARDINAL = 3; -- number of disk control register
KStatus: CARDINAL = 3; -- number of disk status register
startDiskCode: WORD = 2040B; -- control word sent to start the disk
GLOBAL VARIABLES
globalStateSize: PUBLIC CARDINAL ← 0; -- no fixup IOCB needed.
nullDeviceHandle: PUBLIC DeviceHandle ← LOOPHOLE[177777B];
operationSize: PUBLIC CARDINALSIZE[IOCB];
driveIsSA1000: BOOL;
pagesPerTrack: CARDINAL;
sectorsPerCyl: CARDINAL;
maxCylinderNum: CARDINAL; -- last cylinder
maxHeadNum: CARDINAL; -- last head
resetDisk: WORD; -- = clearSA?000WriteFault
Counters to help track down apparent funnies in head
totalErrors: PUBLIC CARDINAL ← 0; -- total errors reported by Poll
pollRaceSpin: LONG CARDINAL ← 0;
recalSeekSpin: LONG CARDINAL ← 0;
potentialKludgeErrors: LONG CARDINAL ← 0;
Not used in current Pilot
CurrentOperation: PUBLIC PROC [device: DeviceHandle]
RETURNS [OperationPtr] = { RETURN[LOOPHOLE[@p[csb.next]]] };
THIS MUST BE MODIFIED TO HANDLE FIXED HEADS, SINGLE-PLATTER DRIVES AND
OTHER MODELS OF SHUGART SA100X AND SA400X AND SA410X DRIVES
GetDeviceAttributes: PUBLIC PROC [device: DeviceHandle] RETURNS [cylinders, movingHeads, fixedHeads, sectorsPerTrack: CARDINAL] = {
RETURN[maxCylinderNum + 1, maxHeadNum + 1, 0, pagesPerTrack]};
GetTrueDeviceAttributes: PUBLIC PROC [device: DeviceHandle] RETURNS [cylinders, movingHeads, fixedHeads, sectorsPerTrack: CARDINAL] = {
[cylinders, movingHeads, fixedHeads, sectorsPerTrack] ← GetDeviceAttributes[device];
};
ASSUMES EXACTLY ONE DRIVE IS CONNECTED TO THE DANDELION
GetNextDevice: PUBLIC PROC [device: DeviceHandle] RETURNS [DeviceHandle] = {
RETURN[IF device = nullDeviceHandle THEN diskDeviceHandle ELSE nullDeviceHandle]};
Initialize: PUBLIC PROC [t: WORD, globalState: GlobalStatePtr] = {
csb.next ← nil;
csb.tail ← nil;
csb.transferMask ← t;
csb.needRecalibrate ← TRUE;
don't update cylinder
Reset[diskDeviceHandle]; -- reset the disk
};
InitializeCleanup: PUBLIC PROC = {
OPEN DeviceCleanup;
item: Item;
sCsb: CSB;
DO
reason: Reason = Await[@item];
SELECT reason FROM
turnOff, disconnect, kill =>
{WHILE GetDDCHardwareBits[].firmwareBusy DO ENDLOOP; sCsb ← csb^};
turnOn =>
I'm not sure what the right thing to do here is.
Always recal seems safe.
{sCsb.cylinder𡤌sb.cylinder; sCsb.needRecalibrate←TRUE; csb^ ← sCsb};
ENDCASE
ENDLOOP
};
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] = {
OPEN iocb: LOOPHOLE[o, IOCBlongPtr];
iocbShort: IOCBshortPtr = LOOPHOLE[PrincOpsUtils.LowHalf[@iocb]];
sectorsInPresentCyl: CARDINAL;
between start of run and end of current cylinder
{
touch (dirty if write) each page;
pageAddr: LONG POINTER ← o.dataPtr;
pageCount: CARDINAL = IF o.incrementDataPtr THEN o.pageCount ELSE 1;
PageCrossCheck[iocbShort];
SELECT DataOperation[o.command] FROM
read => THROUGH [0..pageCount) DO
pageAddr^ ← 0; pageAddr ← pageAddr + PrincOps.wordsPerPage ENDLOOP;
write => THROUGH [0..pageCount) DO
d: CARDINAL = pageAddr^; pageAddr ← pageAddr + PrincOps.wordsPerPage;
ENDLOOP;
ENDCASE;
};
Schedule a recalibrate??
IF ~csb.needRecalibrate
THEN iocb.iocbState ← startSeek
ELSE {iocb.iocbState ← startRecal; csb.needRecalibrate ← FALSE};
sectorsInPresentCyl ←
sectorsPerCyl - (o.clientHeader.head*pagesPerTrack+o.clientHeader.sector);
iocb.destCylinder ←
IF o.pageCount <= sectorsInPresentCyl
THEN o.clientHeader.cylinder
ELSE o.clientHeader.cylinder + ((o.pageCount - sectorsInPresentCyl - 1)/sectorsPerCyl) + 1;
Set special value into device status to indicate new IOCB.
Set SavedError so NextIOCBState won't find initial error.
o.deviceStatus.a ← 177777B; iocb.savedError ← inProgress;
iocb.nextIOCB ← nil; iocb.label ← o.labelPtr^; iocb.unusedLabel ← 0;
use cylinder that the disk will be at when we start this operation.
[] ← NextIOCBState[@iocb, IF csb.next = nil THEN csb.cylinder ELSE p[csb.tail].destCylinder];
Arrange for execution.
IF csb.next = nil
THEN {
csb.next ← LOOPHOLE[iocbShort+entryOffset];
Output[startDiskCode, @iocb]}
ELSE {
Disk busy; queue IOCB. A race is possible and is fixed in Poll.
p[csb.tail].nextIOCB ← LOOPHOLE[iocbShort + entryOffset];
IF p[csb.tail].iocbState = transferFinish THEN
microcode should finish so chain new one on
p[csb.tail].transferCommand.finishTransfer.nextIOCB ←
p[csb.tail].nextIOCB;
};
csb.tail ← iocbShort;
};
CantGetHere: ERROR = CODE;
Poll: PUBLIC PROC [o: OperationPtr] RETURNS [status: Status] = {
This procedure checks the status of an operation and returns the status to the client
OPEN iocb: LOOPHOLE[o, IOCBlongPtr];
originalDeviceStatus: WORD ← 0;
IF ~LOOPHOLE[
o.deviceStatus.b ← LOOPHOLE[GetDDCHardwareBits[]], DDCHardwareBits].firmwareBusy
AND o.deviceStatus.a = 0 THEN status ← hardwareError
ELSE IF o.deviceStatus.a = 0 THEN RETURN[inProgress]
ELSE {
OPEN Basics;
IF BITAND[originalDeviceStatus ← o.deviceStatus.a, errorMask]=0 THEN
csb.cylinder ← iocb.presentCylinder; -- update cyl only if no error.
status ← NextIOCBState[@iocb, csb.cylinder];
};
SELECT status FROM
inProgress =>
Present IOCB not yet complete. If the microcode thinks it is
stopped, restart it; this will be the case when a disk request has
to be done in several operations. Fall through and return.
IF o.deviceStatus.a#0 THEN ERROR CantGetHere
ELSE Output[startDiskCode, @iocb];
goodCompletion => {
all the iocb fields have been corrected by NextIOCBState and the
microcode should have already begun the next IOCB in the chain, if
any, so set the CSB's next pointer, check to be sure that the
microcode will in fact do that IOCB and return with this status
IF (csb.next ← iocb.nextIOCB) # nil AND ~LOOPHOLE[originalDeviceStatus, DDCStatusBits].firmwareBusy THEN {
we lost the race. Microcode should be stopped, but check.
WHILE GetDDCHardwareBits[].firmwareBusy
DO pollRaceSpin ← pollRaceSpin+1 ENDLOOP;
Output[
startDiskCode
--, @p[LOOPHOLE[csb.next-entryOffset, IOCBshortPtr]]--];
};
o.labelPtr^ ← iocb.label;
copy NEXT[lastReadLabel], including boot chain links
o.deviceStatus.a ← originalDeviceStatus;
for debugging purposes (was zeroed)
};
ENDCASE => {
Trouble. Get things moving again
IF GetDDCHardwareBits[].writeFault THEN Output[resetDisk, NIL];
for now, stop all processing on error
csb.next ← nil;
IF (csb.next ← iocb.nextIOCB) # nil THEN
BEGIN
next: IOCBlongPtr =
@p[LOOPHOLE[csb.next - entryOffset, IOCBshortPtr]];
----find next IOCB from chain.
----Recalculate the next IOCB considering the cylinder may be screwed up.
----BE CONSERVATIVE FOR NOW.
IF TRUE OR next.iocbState = recalSeek THEN next.iocbState ← startRecal
ELSE next.iocbState ← startSeek;
next.operation.deviceStatus.a ← 177777B; ++ pretend this is a new IOCB
next.savedError ← inProgress;
[] ← NextIOCBState[next, csb.cylinder]; ++ think on it...
Output[startDiskCode, next]; ++ start it.
END;
totalErrors ← totalErrors + 1;
copy in NEXT[lastReadLabel]. This includes the boot chain link
read in the last succussful operation,or initially supplied.
This operation was done if status=labelCheck, and is not IOCB
contains the label read from the disk, usefull for debugging
IF status # labelCheck THEN o.labelPtr^ ← iocb.label;
o.deviceStatus.a ← originalDeviceStatus; -- restore for diagnostics
};
};
Recalibrate: PUBLIC PROC [device: DeviceHandle] = {csb.needRecalibrate ← TRUE};
Reset: PUBLIC PROC [device: DeviceHandle] = {Output[resetDisk, NIL]};
Start: PROC = {
IF (driveIsSA1000 ← GetDDCHardwareBits[].driveIsSA1000)
THEN {
this test copied from Trinity version
IF GetDDCKTest[].notSector
THEN {
sa1000
pagesPerTrack ← sa1000PagesPerTrack;
sectorsPerCyl ← sa1000TracksPerCylinder * sa1000PagesPerTrack;
maxCylinderNum ← sa1000Cylinders - 1;
maxHeadNum ← sa1000TracksPerCylinder - 1;
resetDisk ← clearSA1000WriteFault;
}
ELSE {
quantum
pagesPerTrack ← q2040PagesPerTrack;
sectorsPerCyl ← q2040TracksPerCylinder * q2040PagesPerTrack;
maxCylinderNum ← q2040Cylinders - 1;
maxHeadNum ← q2040TracksPerCylinder - 1;
resetDisk ← clearSA1000WriteFault;
};
}
ELSE {
assumes SA4008 in constants defined above
pagesPerTrack ← sa4000PagesPerTrack;
sectorsPerCyl ← sa4000PagesPerTrack * sa4000TracksPerCylinder;
maxCylinderNum ← sa4000Cylinders - 1;
maxHeadNum ← sa4000TracksPerCylinder - 1;
resetDisk ← clearSA4000WriteFault;
};
};
DLion boards return status with negatvie logic
GetDDCHardwareBits: PROC RETURNS [DDCHardwareBits] = INLINE {
RETURN[LOOPHOLE[Basics.BITNOT[DLionInputOutput.Input[KStatus]]]]};
following added in analogy to trinity version Sturgis: July 22, 1983 9:41 am
DDCKTest copied from [idun]<apilot90>>Faces>Private>ddc.mesa
specification of KTest Register. Note this has to be inverted after being
obtained from DlionInput.Input[KTest] since negative drivers are used.
|rd dsk|rd dsk|dsk out|dsk wrt|~seek|~dir| b |~IW |TTL| ~ |~drv|~b |~ttl| ~ |~rd |~wrt|
|clock | data | clock | data |cmplt| in |horiz|rduc|vid|sctr|slct|vert|vidi|step|gate|gate|
| 8 4 2 1 | 8 4 2 1 | 8 4 2 1 | 8 4 2 1 |
| 1 | 4 2 1 | 4 2 1 | 4 2 1 | 4 2 1 | 4 2 1 |
|rd dsk|rd dsk|dsk out|dsk wrt|~seek|~dir| b |~IW |TTL| ~ |~drv|~b |~ttl| ~ |~rd |~wrt|
|clock | data | clock | data |cmplt| in |horiz|rduc|vid|sctr|slct|vert|vidi|step|gate|gate|
DDCKTest: TYPE = MACHINE DEPENDENT RECORD [
diskReadClk (0: 0.. 0): BOOL,
diskReadData (0: 1.. 1): BOOL,
diskOutputClk (0: 2.. 2): BOOL,
diskWriteData (0: 3.. 3): BOOL,
notSeekComplete (0: 4.. 4): BOOL,
notDirectionIn (0: 5.. 5): BOOL,
bHoriz (0: 6.. 6): BOOL,
notReduceIW (0: 7.. 7): BOOL,
ttlVideo (0: 8.. 8): BOOL,
notSector (0: 9.. 9): BOOL,
notDriveSelect (0:10..10): BOOL,
notBVert (0:11..11): BOOL,
notTtlvideo (0:12..12): BOOL,
notStep (0:13..13): BOOL,
notReadGate (0:14..14): BOOL,
notWriteGate (0:15..15): BOOL];
GetDDCKTest: PROC RETURNS[DDCKTest] = INLINE{
KTest: CARDINAL = 6;
RETURN[LOOPHOLE[Basics.BITNOT[DLionInputOutput.Input[KTest]]]]};
Prepare the next stage of processing an IOCB. If nothing to do prepare a
null IOCB. Never submits an IOCB, puts an IOCB on the queue, or starts the
Disk uCode. Called only by Initiate on on a new request (with
deviceStatus=-1) or Poll if the microcode has finished with the IOCB
(deviceStatus=0).
ImpossibleProcessingState: ERROR = CODE;
NextIOCBState: PROC [iocb: IOCBlongPtr, curCyl: CARDINAL] RETURNS [iocbStatus: Status] = {
OPEN o: iocb.operation;
runLength: CARDINAL; -- number of pages to transfer
physIOCBAddr: CARDINAL; -- low 16 bits of IOCB physical address
IF o.deviceStatus.a = 177777B
THEN
First time for this IOCB. Check for illegal operation. Check header
read/noop with a run of multiple pages, label write or data write.
iocbStatus ← IF (SELECT HeaderOperation[o.command] FROM
verify => FALSE,
IN [noop..read] =>
(LabelOperation[o.command] = write
OR DataOperation[o.command] = write OR o.pageCount > 1),
ENDCASE => --write--
(driveIsSA1000 AND
(o.pageCount#pagesPerTrack OR o.clientHeader.sector#0))
) THEN hardwareError ELSE inProgress
ELSE {
something was done to IOCB.
hwdStatus: DDCStatusBits = LOOPHOLE[o.deviceStatus.a];
decoded status bits from the controller
hadError: BOOL = (Basics.BITAND[LOOPHOLE[hwdStatus], errorMask] # 0);
set if last IOCB had an error
fix up header, label if there no error.
IF ~hadError AND iocb.iocbState IN [transfer..labelRead] THEN {
OPEN cH: o.clientHeader, l: iocb.label;
first fix Headers; if header read, simulate advance of client header.
THIS WOULD BE WRONG FOR A RUN OF READ HEADERs
IF HeaderOperation[o.command] = read THEN
IF (cH.sector ← cH.sector + 1) = sectorsPerCyl THEN
{cH.head ← cH.head + 1; cH.sector ← 0};
IF cH.head > maxHeadNum THEN
{cH.head ← 0; cH.cylinder ← cH.cylinder + 1};
fix Labels; if label read, no updating occured
IF LabelOperation[o.command] # read THEN {
Take care to clear Pilot's flags in the pageNumber field!
kludge: LONG POINTER TO FilePageRep = LOOPHOLE[@l.filePage];
kludge^ ← [pilot[pilotHi: [filePageHi: kludge.pilotHi.filePageHi],
filePageLo: kludge.filePageLo]];
};
};
Fix up the dataPtr and pageCount. SectorCount will be non-zero
if there was an error. The intended count is countThisStage.
IF iocb.iocbState IN [transfer..transferFinish] THEN {
runLength ← iocb.countThisStage-iocb.transferParm.sectorCount;
o.pageCount ← o.pageCount - runLength;
IF o.incrementDataPtr THEN o.dataPtr ← o.dataPtr + Words[runLength];
};
There are three main areas here, Recalibration, Seeking and DataTransfer.
The recalibration algorithm proceeds as follows:
1. seek out by the current cylinder number, this should get us directly to cylinder zero.
2. If the Track00 signal is set, set the StartSeek and try to set the new state from there. If not on track 0, step in 16 tracks one at a time looking for track 0. This is done in case we overshot.
3. If track 00 is found at any step, set StartSeek and start a data transfer, if any. If all 16 steps are exhausted, step out maxCylinderNum+20 cylinders , one at a time.
4. If the Track00 is found at any step, stop and preceed as above. If not, post "SeekError" and return. If a Seek operation is needed to put the heads over the desired track, The next state ← "DoSeek", the distance and direction are computed and control is passed to the third section of NextIOCBState to build the Seek IOCB. The destination cylinder is also posted in the CSB. If a data transfer operation is needed, we first determine whether the first sector is to be found by verifying headers or by counting from the index mark. It is assumed that if the second method is to be employed (the Header Operation = read or noop), exactly one sector will be read and there will be no write operations involved. Next the maximum length of the transfer is computed. A run of pages may be broken before its natural finish for a number of reasons. First, page zero of any file is transferred independently of all other pages. This allows special treatment of its flags. Runs of pages are broken at 64K page boundries so the microcode need never increment two words of page labels. They are also broken at cylinder boundries so a seek may be inserted. The distance in pages to the closest boundry determines the length of the transfer.
Finally, there may have been an error on the previous transfer. If so, it must be decoded. If it was a label check, we do not return to the client. Instead, a new IOCB is formulated to read the label that failed. The first six words of that label are compared to the first 6 of the clientLabel. If these do not match, there was a real error and the IOCB must be set to simulate an error on the original label verify (this involves setting the former pageCount and dataPtr fields). If the six words match, the last section of the label read is substituted into the clientLabel and the operation that included the label verify is restarted on the offending sector. If any other type of error occured, its identity is simply returned.
Error decode; used to decide the next state.
SELECT TRUE FROM -- first set the status for any of the disastrous faults
iocb.savedError # inProgress => iocbStatus ← iocb.savedError;
hwdStatus.memoryFault => {
this fails if the memory error occured WITHIN the iocb
IF LabelOperation[o.command] # read THEN {
kludge: LONG POINTER TO FilePageRep = LOOPHOLE[@iocb.label.filePage];
kludge.filePageLo ← kludge.filePageLo - 1;
};
iocbStatus ← hardwareError;
};
hwdStatus.driveNotReady => iocbStatus ← notReady;
hwdStatus.overrun, hwdStatus.writeFault =>
iocbStatus ← hardwareError;
ENDCASE => iocbStatus ← inProgress;
lastField is 0=>seek, 1=>header, 2=>label, 3=>data.
SELECT hwdStatus.lastField FROM
0 => iocbStatus ← IF hadError THEN hardwareError ELSE inProgress;
1 => SELECT TRUE FROM
hwdStatus.verifyError, hwdStatus.CRCError =>
iocbStatus ← wrongSector;
iocbStatus = inProgress => iocbStatus ← hardwareError;
shouldn't have stopped if no error
ENDCASE;
2 => SELECT TRUE FROM
hwdStatus.CRCError => iocbStatus ← labelError; -- bad label
hwdStatus.verifyError => iocbStatus ← labelCheck;
CRC ok, verify failed; go try read/label fixup.
iocbStatus = inProgress => iocbStatus ← hardwareError;
shouldn't have stopped if there was no error
ENDCASE;
3 => SELECT TRUE FROM
hwdStatus.verifyError, hwdStatus.CRCError =>
iocbStatus ← dataError
ENDCASE;
ENDCASE;
If fall through, leave value set in first select
};
Next State decision Loop. Note this may take several iterations. For example, the starting state could be "StartSeek" while the cylinder specified is the current cylinder. In this case the current state is set to "StartTransfer" and the code enters the next state selection procedure again.
statusCount[iocbStatus] ← statusCount[iocbStatus] + 1;
IF iocbStatus=inProgress OR iocbStatus=labelCheck THEN
DO
stateCount[iocb.iocbState] ← stateCount[iocb.iocbState] + 1;
SELECT iocb.iocbState FROM
startRecal => {
iocb.presentCylinder ← IF curCyl=0 THEN 1 ELSE 0; -- force movement
iocb.iocbState ← recalSeek;
EXIT;
};
recalSeek => {
have just done seek to where cylinder 0 should be
UNTIL GetDDCHardwareBits[].seekComplete DO --wait for hardware--
recalSeekSpin ← recalSeekSpin+1 ENDLOOP;
IF GetDDCHardwareBits[].track00 THEN
{
curCyl ← iocb.presentCylinder ← csb.cylinder ← 0;
iocb.iocbState ← startSeek;
LOOP;
}
ELSE
{ -- not on cylinder 0 yet, try stepping in 16 cylinders
csb.cylinder ← 0; -- just to make things consistent
iocb.presentCylinder ← 1;
iocb.iocbState ← recalStepIn; -- start process of stepping in
EXIT;
};
};
recalStepIn =>
tried stepping in one (more) cylinder in search of cylinder 0
SELECT TRUE FROM
GetDDCHardwareBits[].track00 => {
made it to cylinder 0
curCyl ← iocb.presentCylinder ← csb.cylinder ← 0;
iocb.iocbState ← startSeek;
LOOP;
};
iocb.presentCylinder < 16 => {
keep stepping-in in case we overshot cylinder 0
iocb.presentCylinder ← iocb.presentCylinder + 1;
iocb.iocbState ← recalStepIn; -- continue stepping in
EXIT;
};
ENDCASE => {
have stepped in; cylinder 0 can't be that way so step out
csb.cylinder ← maxCylinderNum + 20;
iocb.presentCylinder ← maxCylinderNum + 19;
assume we are all the way in on the disk, past the last cyl
iocb.iocbState ← recalStepOut; -- start process of stepping out
EXIT;
};
recalStepOut =>
tried stepping out one (more) cylinder in search of cylinder 0
SELECT TRUE FROM
GetDDCHardwareBits[].track00 => {
made it to cylinder 0
curCyl ← iocb.presentCylinder ← csb.cylinder ← 0;
iocb.iocbState ← startSeek;
LOOP;
};
iocb.presentCylinder > 0 => {
keep stepping out searching for cylinder 0
iocb.presentCylinder ← iocb.presentCylinder - 1;
iocb.iocbState ← recalStepOut; -- continue process of stepping out
EXIT;
};
ENDCASE => {
stepped in as far as possible, cyl 0 not be found => quit
iocbStatus ← hardwareError;
iocb.iocbState ← recalStepOut; -- loop here if get called again
EXIT;
};
startSeek =>
either just started or just finished recalibrating. See if a
Seek is needed ( on destination cylinder now?). If so, set up
state for Seek, else set state to startTransfer and go
investigate need for transfer
IF (iocb.presentCylinder ← curCyl) = o.clientHeader.cylinder THEN
{iocb.iocbState ← startTransfer; LOOP} -- on cyl, start transfer
ELSE IF o.clientHeader.cylinder > maxCylinderNum THEN
{iocbStatus ← seekTimeout; -- can't get there; clientError-- EXIT}
ELSE {
do the seek
iocb.presentCylinder ← o.clientHeader.cylinder;
iocb.iocbState ← doSeek;
EXIT;
};
doSeek, -- => {iocb.iocbState ← startTransfer; LOOP};
startTransfer =>
SELECT TRUE FROM
driveIsSA1000 AND HeaderOperation[o.command]=write =>
{iocb.iocbState ← eraseSA1000Track; runLength ← 1; EXIT};
(o.pageCount=0) => {iocb.iocbState ← transferFinish; LOOP};
ENDCASE => {iocb.iocbState ← transfer; LOOP};
eraseSA1000Track => {
kludge: LONG POINTER TO FilePageRep = LOOPHOLE[@iocb.label.filePage];
kludge.filePageLo ← kludge.filePageLo - 1;
o.clientHeader.sector ← 0;
iocb.iocbState ← transfer;
LOOP;
};
transfer => SELECT iocbStatus FROM
inProgress => {
last run went ok
headerOp: FieldOperation = HeaderOperation[o.command];
sectorsLeftInCyl: CARDINAL =
sectorsPerCyl - (o.clientHeader.head*pagesPerTrack
+ o.clientHeader.sector);
runLength ← MIN[o.pageCount, sectorsLeftInCyl];
IF headerOp # write AND LabelOperation[o.command]#read THEN {
OPEN l: iocb.label;
stop run at 0/1 boundaries, cyl bounds, 65K page bounds
kludge: LONG POINTER TO FilePageRep = LOOPHOLE[@iocb.label.filePage];
IF kludge.filePageLo = 0 THEN {
IF kludge.pilotHi.filePageHi = 0 THEN runLength ← 1} -- pg 0
ELSE runLength ← MIN[runLength, 65535 - kludge.filePageLo + 1];
IF driveIsSA1000 AND headerOp IN [noop..read] THEN runLength ← 1;
};
IF o.pageCount = runLength
THEN iocb.iocbState ← transferFinish
ELSE {
IF runLength = sectorsLeftInCyl THEN
test for overrun, set poststep
IF iocb.presentCylinder >= maxCylinderNum THEN
iocbStatus ← seekTimeout -- should be client error
ELSE iocb.presentCylinder ← curCyl + 1;
iocb.iocbState ← transfer;
};
EXIT;
};
labelCheck =>
{iocbStatus ← inProgress; iocb.iocbState ← labelRead; EXIT};
ENDCASE => ImpossibleProcessingState;
transferFinish => SELECT iocbStatus FROM
inProgress => {iocbStatus ← goodCompletion; iocb.iocbState ← transferFinish; EXIT};
labelCheck => {iocbStatus ← inProgress; iocb.iocbState ← labelRead; EXIT};
ENDCASE => ImpossibleProcessingState;
labelRead => {
we have read a disk label to see if it matches the client's label.
Note one cannot get a label check on a label read operation since the verifyError bit is masked off. If there was a CRC error, there would have been a labelError and we wouldn't be here now. Hence, the fact that this code is being executed implies the label read operation had no errors.
iocbStatus already ← inProgress
o.clientHeader ← o.diskHeader; -- restore Header saved when IOCB set up
o.command ← iocb.savedIOCBCmd; -- restore command saved when IOCB set up
IF ~MatchLabels[o.labelPtr, @iocb.label] OR iocb.unusedLabel # 0
THEN {
hardLabelChecks ← hardLabelChecks+1;
iocbStatus ← labelCheck;
EXIT}
ELSE
temp patch: complain about spurious label verify errors:
IF o.labelPtr.dontCare = iocb.label.dontCare
THEN {
iocbStatus ← labelError;
EXIT }
ELSE {
softLabelChecks ← softLabelChecks+1;
iocb.iocbState ← startTransfer;
LOOP};
};
ENDCASE => ImpossibleProcessingState;
ENDLOOP;
{
build the IOCB from the information given above; following block depends upon iocb being in first 64K, and layout of flag words with flags in rightmost part
physIOCBAddr ← Basics.LowHalf[DLionInputOutput.GetRealAddress[iocb]];
OPEN lp: LOOPHOLE[iocb, Basics.LongNumber];
wPP: CARDINAL = PrincOps.wordsPerPage;
physIOCBAddr ← (LOOPHOLE[DLionInputOutput.GetRealPage[lp.lowbits/wPP], CARDINAL]*wPP) + (lp.lowbits MOD wPP);
};
IF iocbStatus # inProgress THEN {
OPEN sC: iocb.seekCommand, fS: iocb.seekCommand.finishSeek;
build a null IOCB. This will do nothing if run but cause and interrupt to the calling mesa process. This is needed if an IOCB specifying no action or an illegal action is given. When the iocb is finished or the next action specified is illegal, this type of null IOCB is also built.
the present cylinder is already set
the destination cylinder is already set
as is the saved IOCB state, though this does not apply here
the next IOCB field is also already set
don't bother setting the transfer parameters, they won't be used
sC ← seekCmdBlock[dummyIOCB]; -- set the dummy IOCB commands
fS.nextIOCB ← iocb.nextIOCB; -- microcode chains to next IOCB
fS.transferMask ← csb.transferMask;
sC.negDistanceAddr ← sC.negDistanceAddr + physIOCBAddr;
fS.statusAddr ← fS.statusAddr + physIOCBAddr;
}
ELSE SELECT iocb.iocbState FROM
IN [recalStepIn..recalStepOut] => {
OPEN sC: iocb.seekCommand, fS: iocb.seekCommand.finishSeek;
build step iocb
present cylinder is already set to where transfer starts
destination cylinder is already set to where transfer ends
transfer parameters not used
sC ← seekCmdBlock[step]; -- set the Step IOCB commands
IF iocb.iocbState = recalStepIn THEN {
or in the DirectionIn bit
sC.controlWd1 ← Basics.BITOR[sC.controlWd1, directionInBit];
sC.controlWd2 ← Basics.BITOR[sC.controlWd2, directionInBit];
sC.waitCmd ← Basics.BITOR[sC.waitCmd, directionInBit];
set in the wait command so it is set up before first step pulse
};
sC.sendCtlAddr ← sC.sendCtlAddr + physIOCBAddr;
fS.nextIOCB ← nil; -- stop after this IOCB completes
fS.transferMask ← csb.transferMask;
sC.negDistanceAddr ← sC.negDistanceAddr + physIOCBAddr;
fS.statusAddr ← fS.statusAddr + physIOCBAddr;
};
recalSeek, doSeek =>
{ OPEN sC: iocb.seekCommand, fS: iocb.seekCommand.finishSeek;
present cylinder is already set to where transfer starts
destination cylinder is already set to where transfer ends
transfer parameters not used
sC ← seekCmdBlock[seek]; -- set the Seek IOCB commands
IF iocb.presentCylinder > curCyl THEN {
OR in DirectionInBit
sC.controlWd1 ← Basics.BITOR[sC.controlWd1, directionInBit];
sC.controlWd2 ← Basics.BITOR[sC.controlWd2, directionInBit];
set in wait command so it is set up before first step pulse
sC.waitCmd ← Basics.BITOR[sC.waitCmd, directionInBit];
negative of distance to move heads in cylinders
sC.negDistance ← curCyl - iocb.presentCylinder;
}
ELSE
out wants negative of distance to move heads in cylinders
sC.negDistance ← iocb.presentCylinder - curCyl;
sC.sendCtlAddr ← sC.sendCtlAddr + physIOCBAddr;
fS.nextIOCB ← nil; -- stop after this IOCB completes
fS.transferMask ← csb.transferMask;
sC.negDistanceAddr ← sC.negDistanceAddr + physIOCBAddr;
fS.statusAddr ← fS.statusAddr + physIOCBAddr;
};
eraseSA1000Track, transfer, transferFinish => {
OPEN tP: iocb.transferParm;
headerOp: FieldOperation = HeaderOperation[o.command];
build transfer IOCB, run length in runLength (adjust if Header Read or Noop), command in o.command, add post-step of heads if iocb.present cylinder=currentCylinder+1, find sector 0 if header op # verify. Pre-increment the dataPtr and pageCount for run of pages if necessary.
the present cylinder is already set
the destination cylinder is already set
as is the saved IOCB state, it doesn't apply here
the next IOCB field is also already set
fill in the transfer parameter block
tP.headerOp ← headerOpBlock[headerOp];
tP.labelOp ← labelOpBlock[LabelOperation[o.command]];
tP.dataOp ← dataOpBlock[DataOperation[o.command]];
tP.dataOp.dataPgNum ← IF DataOperation[o.command] = noop THEN
PrincOpsUtils.PageNumberForAddress[DLionInputOutput.IOPage]
ELSE (IF o.incrementDataPtr THEN 100000B ELSE 0) + PrincOpsUtils.PageNumberForAddress[o.dataPtr];
{ OPEN m: tP.miscCmds, cH: o.clientHeader;
m ← miscCmdBlock[driveIsSA1000]; -- set findSectMk and Stop commands
m.freezeCmd ← m.freezeCmd + cH.head*8*256; -- "or" head into bits 1..4
m.findSectMkCmd ← m.findSectMkCmd + cH.head*8*256;
};
iocb.countThisStage ← tP.sectorCount ← runLength;
does this transfer involve a verify of the Header fields or should
the index mark be used to find the first sector? The vast majority
of operations are done using header verifies to find the proper
sector. The header is read or Nooped only during scavenging and
diagnostic operations. It is written only when an entire track is
being formatted.
IF headerOp IN [noop..write] THEN {
this is not a verify so insert the commands used to find sector 0
via the index mark
OPEN sC: iocb.seekCommand, cH: o.clientHeader;
IF driveIsSA1000 THEN {
the SA1000 drive is connected
sC ← seekCmdBlock[findSA1000Sector0];
sC.controlWd2 ← sC.controlWd2 + physIOCBAddr;
insert phys address of start of transfer block
SELECT headerOp FROM
IN [noop..read] =>
{ OPEN Basics;
tP.sectorCount ← cH.sector + 1;
For Trinity: if runLength=1 isn't this AND unnecessary????
or is it necessary because a header nop/read is
really a bunch of reads on the SA1000
tP.dataOp.dataPgNum ← BITAND[tP.dataOp.dataPgNum, 77777B];
runLength ← (already) 1;
};
ENDCASE => --write, can't be verify here
{
to erase a SA1000Track, write one LARGE sector
IF iocb.iocbState = eraseSA1000Track THEN
tP.dataOp.dataCnt ← (sa1000PagesPerTrack+4) * PrincOps.wordsPerPage
};
}
ELSE {
find sector on SA4000 by counting after the index mark
sC ← seekCmdBlock[findSA4000Sector0];
sC.negDistance ← SA4000SectorDistance[cH.sector];
sC.negDistanceAddr ← sC.negDistanceAddr + physIOCBAddr;
sC.sendCtlAddr ← sC.sendCtlAddr + physIOCBAddr;
sC.initRegsParm ← sC.initRegsParm + physIOCBAddr;
};
tP.failCount ← 1; -- quit immediately on Header Field errors
}
ELSE -- headerOp=verify
{ OPEN sC: iocb.seekCommand;
Fill in commands to skip over most of the
seekCommandBlock; the transfer starts as soon as SeekComplete
is detected.
sC ← seekCmdBlock[plainTransfer];
sC.controlWd1 ← sC.controlWd1 + physIOCBAddr;
tP.failCount ←
IF driveIsSA1000 THEN 3*2*sa1000PagesPerTrack --(3 field/sector)
ELSE 2*sa4000PagesPerTrack --(1 field/sector)
the 2 is for a cheap, quick retry
};
{
set the transfer command block. It either specifies a post-transfer step or not, depending on whether iocb.presentCylinder=currentCylinder+1
OPEN tC: iocb.transferCommand;
IF iocb.presentCylinder = curCyl + 1 THEN
tC ← transferCmdBlock[doStep]
have the microcode do a post step before returning from the IOCB
ELSE {
no post step after transfer
tC ← transferCmdBlock[noStep];
IF iocb.iocbState = transferFinish THEN
continue with next IOCB in the chain; the default value for nextIOCB is 0, which will stop the microcode after this stage, so unless set otherwise above, the microcode will stop. Note if this is the last IOCB in the chain, iocb.nextIOCB would have been equal to "nil", which was declared to be zero, so the microcode will stop in that case too.
tC.finishTransfer.nextIOCB ← iocb.nextIOCB;
tC.stepCmdWd1 ← tC.stepCmdWd1 + physIOCBAddr;
};
tC.parmBlockAddr ← tC.parmBlockAddr + physIOCBAddr;
tC.finishAddr ← tC.finishAddr + physIOCBAddr;
tC.finishTransfer.statusAddr ← tC.finishTransfer.statusAddr + physIOCBAddr;
tC.finishTransfer.transferMask ← csb.transferMask;
};
tP.headerOp.headerAddr ← tP.headerOp.headerAddr + physIOCBAddr;
tP.labelOp.labelAddr ← tP.labelOp.labelAddr + physIOCBAddr;
}; -- of setting up a normal transfer IOCB
labelRead => {
OPEN tP: iocb.transferParm;
set up iocb for read of one label
o.labelPtr^ ← iocb.label; -- save expected label
iocb.savedIOCBCmd ← o.command; -- save failed operation
o.command ← vr; -- read the offending label
o.diskHeader ← o.clientHeader; -- save correct header
dest cylinder already set
iocb.presentCylinder ← curCyl; -- we will be on this cylinder
after the operation completes. This could have been set to
curCyl+1 in the failing transfer operation.
saved IOCB state was set when the next state was determined
the next IOCB field is set
fill in the transfer parameter block
tP.sectorCount ← 1; -- read one label only
tP.failCount ←
IF driveIsSA1000 THEN 2*3*sa1000PagesPerTrack -- (3 fields/sector)
ELSE 2*sa4000PagesPerTrack; -- (1 field /sector)
the 2 is for a cheap, quick retry
tP.headerOp ← headerOpBlock[verify]; -- verify header
tP.labelOp ← labelOpBlock[read]; -- then read the label
tP.dataOp ← dataOpBlock[noop]; -- forget the data
tP.dataOp.dataPgNum ←
PrincOpsUtils.PageNumberForAddress[DLionInputOutput.IOPage];
{ OPEN m: tP.miscCmds, cH: o.clientHeader;
m ← miscCmdBlock[driveIsSA1000]; -- set findSectMk, Stop
m.freezeCmd ← m.freezeCmd + cH.head*8*256; -- "or" head into bits 1..4
m.findSectMkCmd ← m.findSectMkCmd + cH.head*8*256;
};
iocb.seekCommand ← seekCmdBlock[plainTransfer]; -- just looking
iocb.seekCommand.controlWd1 ←
iocb.seekCommand.controlWd1 + physIOCBAddr;
{ OPEN tC: iocb.transferCommand;
tC ← transferCmdBlock[noStep]; -- no poststep
tC.finishTransfer.nextIOCB ← nil; -- stop after label read
tC.stepCmdWd1 ← tC.stepCmdWd1 + physIOCBAddr;
tC.parmBlockAddr ← tC.parmBlockAddr + physIOCBAddr;
tC.finishAddr ← tC.finishAddr + physIOCBAddr;
tC.finishTransfer.statusAddr ←
tC.finishTransfer.statusAddr + physIOCBAddr;
tC.finishTransfer.transferMask ← csb.transferMask;
};
tP.headerOp.headerAddr ← tP.headerOp.headerAddr + physIOCBAddr;
tP.labelOp.labelAddr ← tP.labelOp.labelAddr + physIOCBAddr;
};
ENDCASE;
o.deviceStatus.a ← 0; -- mark iocb as not having been seen by uCode
iocb.savedError ← iocbStatus; -- this will be used in no-op IOCB's
RETURN[iocbStatus];
};
MatchLabels: PROC[p1, p2: LONG POINTER TO DiskFace.Label] RETURNS[BOOL] = INLINE {
Matches labels ignoring bootChainLink field. Code stolen from PilotDisk interface
MatchableLabel: TYPE = MACHINE DEPENDENT RECORD [
a, b, c, d, dontCare: RECORD [UNSPECIFIED, UNSPECIFIED] ];
{
OPEN m1: LOOPHOLE[p1^, MatchableLabel], m2: LOOPHOLE[p2^, MatchableLabel];
RETURN[m1.a=m2.a AND m1.b=m2.b AND m1.c=m2.c AND m1.d=m2.d]
};
};
Output: PROC [cmd: UNSPECIFIED, i: IOCBlongPtr ← NIL] = INLINE {
DLionInputOutput.Output[cmd, KCtl]};
IOCBCrossesPage: ERROR = CODE;
PageCrossCheck: PROC [i: IOCBshortPtr] = INLINE {
IF LOOPHOLE[i, CARDINAL] MOD PrincOps.wordsPerPage >=
PrincOps.wordsPerPage - SIZE[IOCB] THEN ERROR IOCBCrossesPage;
};
Words: PROC [sectors: CARDINAL] RETURNS [LONG CARDINAL] = INLINE {
RETURN[Basics.LongMult[sectors, PrincOps.wordsPerPage]]};
Temporary statistics gathering (ADB 1/30/84)
stateCount: ARRAY DDC.ProcessingState OF INTALL[0];
statusCount: ARRAY SA4000Face.Status OF INTALL[0];
hardLabelChecks: INT ← 0;
softLabelChecks: INT ← 0;
Start[];
}....
LOG
Time: June 13, 1980 3:40 PM, By: DDavies
Action: start change to Dandelion Head
Time: June 16, 1980 5:11 PM, By: Davies
Action: end first pass at Dandelion Head
Time: June 17, 1980 5:12 PM, By: Davies
Action: prepare for compilation
Time: June 23, 1980 3:51 PM, By: Forrest
Action: Fix bug, add IOCB check. Reformat/maulOver.
Time: July 16, 1980 1:25 PM, By: Forrest
Action: Take out code supporting initial debugging, add ability to
format parts of SA4000 tracks.
Time: September 9, 1980 2:33 PM, By: Forrest
Action: Reformat (again), work on Label check bug.
Time: September 9, 1980 2:33 PM, By: Sandman
Action: AddressFault bug.
Time: January 22, 1981 3:10 PM, By: Gobbel
Action: Don't reset cylinder when first building IOCB. Reformat.
Time: February 11, 1981 1:53 PM, By: Forrest
Action: Add code (state) to format SA1000 track.
Time: February 14, 1981 2:25 PM, By: Gobbel
Action: Fix bug in NextIOCBState.
Time: March 31, 1981 9:44 AM, By: Forrest
Action: change the pre adjustment or pageCOunt and DataPointer in NextIOCBState to adjustment after the IOCB is cooked.
Remark: July 22, 1983 9:23 am: Sturgis: add test for quantum disk, assume only diff from SA1000 is shape of disk. (required adding q2040 constants, and modifying Start.) Added GetDDCKTest.
Time: January 20, 1984 4:42 pm, By: Andrew Birrell
Action: Convert to Cedar 5.1.
Time: January 20, 1984 4:42 pm, By: Bob Hagmann
Action: add GetTrueDeviceAttributes
Carl Hauser, November 14, 1985 10:35:36 am PST
fixed bug causing free pages with bad labels (wrong FilePage) to be written
changes to: Initiate the "Fix up label file page number representation" code must be idempotent: added "IF kludge.pilotHi.filePageHi = 0" test.