-- SA4000HeadDLion.mesa
-- Last edited by: Forrest on: February 14, 1981 2:25 PM

-- 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
DDC,
DeviceCleanup USING [Await, Item, Reason],
DLionInputOutput USING [GetRealPage, IOPage, Input, Output],
Environment USING [Base, first64K, Long, wordsPerPage],
HeadStartChain USING [Start],
Inline USING [BITAND, BITNOT, BITOR, LongMult, LowHalf],
PageMap USING [], -- implicit import from DLion InputOutput
PilotDisk USING [Handle, MatchLabels, NextLabel],
SA4000Face,
SA4000HeadDLionConstants,
Utilities USING [PageFromLongPointer];

SA4000HeadDLion: PROGRAM
IMPORTS
DLionInputOutput, DeviceCleanup, RemainingHeads: HeadStartChain, Inline,
PageMap, PilotDisk, Utilities
EXPORTS HeadStartChain, SA4000Face
SHARES SA4000Face =
BEGIN OPEN DDC, SA4000Face, SA4000HeadDLionConstants;

-- TYPE DEFINITIONS
-- Controller status block, only one per DLion.
-- Only state.next is known by microcode.
CSB: TYPE = MACHINE DEPENDENT RECORD [
cylinder(0): CARDINAL,
next(1): Environment.Base RELATIVE POINTER TO ChannelCommand,
tail(2): IOCBshortPtr, -- last iocb. Used by initiate
transferMask(3): WORD, -- naked notify mask.
needRecalibrate(4:0..15): BOOLEAN]; -- TRUE => recalibrate required.
csb: LONG POINTER TO CSB = LOOPHOLE[DLionInputOutput.IOPage+0B];

-- CONSTANTS
p: Environment.Base = Environment.first64K;

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;

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 CARDINAL ← SIZE[IOCB];

driveIsSA1000: BOOLEAN;
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: CARDINAL ← 0; -- total errors reported by Poll
pollRaceSpin: LONG CARDINAL ← 0;
recalSeekSpin: 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]};

-- 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] =
BEGIN
csb.next ← nil;
csb.tail ← nil;
csb.transferMask ← t;
csb.needRecalibrate ← TRUE;
-- don’t update cylinder
Reset[diskDeviceHandle]; -- reset the disk
END;

InitializeCleanup: PUBLIC PROC =
BEGIN 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←csb.cylinder; sCsb.needRecalibrate←TRUE; csb↑ ← sCsb};
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] =
BEGIN OPEN iocb: LOOPHOLE[o, IOCBlongPtr];
iocbShort: IOCBshortPtr = LOOPHOLE[Inline.LowHalf[@iocb]];
sectorsInPresentCyl: CARDINAL;
-- between start of run and end of current cylinder

--PageCrossCheck[iocbShort]; ++ delete after all debugging complete?
BEGIN -- touch (dirty if write) each page;
pageAddr: LONG POINTER ← o.dataPtr;
pageCount: CARDINAL = IF o.incrementDataPtr THEN o.pageCount ELSE 1;
SELECT DataOperation[o.command] FROM
read => THROUGH [0..pageCount) DO
pageAddr↑ ← 0; pageAddr ← pageAddr + Environment.wordsPerPage ENDLOOP;
write => THROUGH [0..pageCount) DO
d: CARDINAL = pageAddr↑; pageAddr ← pageAddr + Environment.wordsPerPage;
ENDLOOP;
ENDCASE;
END;
-- 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
BEGIN -- 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;
END;
csb.tail ← iocbShort;
END;

-- This procedure checks the status of an operation and returns the status
-- to the client
CantGetHere: ERROR = CODE;
Poll: PUBLIC PROC [o: OperationPtr] RETURNS [status: Status] =
BEGIN OPEN iocb: LOOPHOLE[o, IOCBlongPtr];
originalDeviceStatus: WORD ← 0;

IF ~LOOPHOLE[
o.deviceStatus.b ← GetDDCHardwareBits[], DDCHardwareBits].firmwareBusy
AND o.deviceStatus.a = 0 THEN status ← hardwareError
ELSE IF o.deviceStatus.a = 0 THEN RETURN[inProgress]
ELSE
BEGIN OPEN Inline;
IF BITAND[originalDeviceStatus ← o.deviceStatus.a, errorMask]=0 THEN
csb.cylinder ← iocb.presentCylinder; -- update cyl only if no error.
status ← NextIOCBState[@iocb, csb.cylinder];
END;
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 =>
BEGIN
-- 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
BEGIN
-- 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]]--];
END;
o.labelPtr↑ ← iocb.label;
-- copy NEXT[lastReadLabel], including boot chain links
o.deviceStatus.a ← originalDeviceStatus;
-- for debugging purposes (was zeroed)
END;
ENDCASE =>
BEGIN
-- 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
END;
END;

Recalibrate: PUBLIC PROC [device: DeviceHandle] =
{csb.needRecalibrate ← TRUE};

Reset: PUBLIC PROC [device: DeviceHandle] = {Output[resetDisk, NIL]};

Start: PUBLIC PROC =
BEGIN
IF (driveIsSA1000 ← GetDDCHardwareBits[].driveIsSA1000) THEN
BEGIN -- assumes SA1004 in constants defined above
pagesPerTrack ← sa1000PagesPerTrack;
sectorsPerCyl ← sa1000TracksPerCylinder * sa1000PagesPerTrack;
maxCylinderNum ← sa1000Cylinders - 1;
maxHeadNum ← sa1000TracksPerCylinder - 1;
resetDisk ← clearSA1000WriteFault;
END
ELSE
BEGIN -- assumes SA4008 in constants defined above
pagesPerTrack ← sa4000PagesPerTrack;
sectorsPerCyl ← sa4000PagesPerTrack * sa4000TracksPerCylinder;
maxCylinderNum ← sa4000Cylinders - 1;
maxHeadNum ← sa4000TracksPerCylinder - 1;
resetDisk ← clearSA4000WriteFault;
END;
RemainingHeads.Start[]
END;

-- DLion boards return status with negatvie logic
GetDDCHardwareBits: PROC RETURNS [DDCHardwareBits] = INLINE {
RETURN[LOOPHOLE[Inline.BITNOT[DLionInputOutput.Input[KStatus]]]]};

-- 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] =
BEGIN 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
BEGIN -- something was done to IOCB.
hwdStatus: DDCStatusBits = LOOPHOLE[o.deviceStatus.a];
-- decoded status bits from the controller
hadError: BOOLEAN = (Inline.BITAND[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
BEGIN 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
BEGIN
l.filePageLo ← l.filePageLo - 1; -- UnDo microcode increment
PilotDisk.NextLabel[@l]; -- use pilot increment to properly set flags
END;
END;

-- 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
BEGIN
runLength ← iocb.countThisStage-iocb.transferParm.sectorCount;
o.pageCount ← o.pageCount - runLength;
IF o.incrementDataPtr THEN o.dataPtr ← o.dataPtr + Words[runLength];
END;

-- 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
BEGIN
IF LabelOperation[o.command] # read THEN
iocb.label.filePageLo ← iocb.label.filePageLo - 1;
iocbStatus ← hardwareError;
END;
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
END;

-- Next State decision Loop
-- Now define the next state. 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.

IF iocbStatus=inProgress OR iocbStatus=labelCheck THEN
DO SELECT iocb.iocbState FROM
startRecal =>
BEGIN
iocb.presentCylinder ← IF curCyl=0 THEN 1 ELSE 0; -- force movement
iocb.iocbState ← recalSeek;
EXIT;
END;

recalSeek =>
BEGIN -- 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
BEGIN
curCyl ← iocb.presentCylinder ← csb.cylinder ← 0;
iocb.iocbState ← startSeek;
LOOP;
END
ELSE
BEGIN -- 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;
END;
END;

recalStepIn =>
-- tried stepping in one (more) cylinder in search of cylinder 0
SELECT TRUE FROM
GetDDCHardwareBits[].track00 => -- made it to cylinder 0
BEGIN
curCyl ← iocb.presentCylinder ← csb.cylinder ← 0;
iocb.iocbState ← startSeek;
LOOP;
END;
iocb.presentCylinder < 16 =>
BEGIN
-- keep stepping-in in case we overshot cylinder 0
iocb.presentCylinder ← iocb.presentCylinder + 1;
iocb.iocbState ← recalStepIn; -- continue stepping in
EXIT;
END;
ENDCASE =>
-- have stepped in; cylinder 0 can’t be that way so step out
BEGIN
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;
END;

recalStepOut =>
-- tried stepping out one (more) cylinder in search of cylinder 0
SELECT TRUE FROM
GetDDCHardwareBits[].track00 =>
BEGIN -- made it to cylinder 0
curCyl ← iocb.presentCylinder ← csb.cylinder ← 0;
iocb.iocbState ← startSeek;
LOOP;
END;
iocb.presentCylinder > 0 =>
BEGIN -- keep stepping out searching for cylinder 0
iocb.presentCylinder ← iocb.presentCylinder - 1;
iocb.iocbState ← recalStepOut; -- continue process of stepping out
EXIT;
END;
ENDCASE =>
BEGIN
-- stepped in as far as possible, cyl 0 not be found => quit
iocbStatus ← hardwareError;
iocb.iocbState ← recalStepOut; -- loop here if get called again
EXIT;
END;

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
BEGIN
iocb.presentCylinder ← o.clientHeader.cylinder;
iocb.iocbState ← doSeek;
EXIT;
END;

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 =>
BEGIN
iocb.label.filePageLo ← iocb.label.filePageLo - 1;
o.clientHeader.sector ← 0;
iocb.iocbState ← transfer;
LOOP;
END;

transfer => SELECT iocbStatus FROM
inProgress => -- last run went ok
BEGIN
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
BEGIN OPEN l: iocb.label;
-- stop run at 0/1 boundaries, cyl bounds, 65K page bounds
IF l.filePageLo = 0 THEN {
IF l.filePageHi = 0 THEN runLength ← 1} -- pg 0
ELSE runLength ← MIN[runLength, 65535 - l.filePageLo + 1];
IF driveIsSA1000 AND headerOp IN [noop..read] THEN runLength ← 1;
END;
IF o.pageCount = runLength THEN iocb.iocbState ← transferFinish
ELSE
BEGIN
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;
END;
EXIT;
END;
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 =>
BEGIN
-- 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 ~PilotDisk.MatchLabels[o.labelPtr, @iocb.label] OR
iocb.unusedLabel # 0 THEN {iocbStatus ← labelCheck; EXIT}
ELSE {iocb.iocbState ← startTransfer; LOOP};
END;
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 ← Inline.LowHalf[DLionInputOutput.GetRealAddress[iocb]];
BEGIN OPEN lp: LOOPHOLE[iocb, Environment.Long];
wPP: CARDINAL = Environment.wordsPerPage;
physIOCBAddr ←
(LOOPHOLE[DLionInputOutput.GetRealPage[lp.lowbits/wPP], CARDINAL]*wPP)
+ (lp.lowbits MOD wPP);
END;

IF iocbStatus # inProgress THEN
BEGIN 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;
END
ELSE SELECT iocb.iocbState FROM
IN [recalStepIn..recalStepOut] =>
BEGIN 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
BEGIN
sC.controlWd1 ← Inline.BITOR[sC.controlWd1, directionInBit];
sC.controlWd2 ← Inline.BITOR[sC.controlWd2, directionInBit];
sC.waitCmd ← Inline.BITOR[sC.waitCmd, directionInBit];
-- set in the wait command so it is set up before first step pulse
END;
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;
END;

recalSeek, doSeek =>
BEGIN 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
BEGIN
-- OR in DirectionInBit
sC.controlWd1 ← Inline.BITOR[sC.controlWd1, directionInBit];
sC.controlWd2 ← Inline.BITOR[sC.controlWd2, directionInBit];
-- set in wait command so it is set up before first step pulse
sC.waitCmd ← Inline.BITOR[sC.waitCmd, directionInBit];
-- negative of distance to move heads in cylinders
sC.negDistance ← curCyl - iocb.presentCylinder;
END
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;
END;

eraseSA1000Track, transfer, transferFinish =>
BEGIN 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
Utilities.PageFromLongPointer[DLionInputOutput.IOPage]
ELSE
(IF o.incrementDataPtr THEN 100000B ELSE 0)
+ Utilities.PageFromLongPointer[o.dataPtr];
BEGIN 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;
END;
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
BEGIN OPEN sC: iocb.seekCommand, cH: o.clientHeader;
IF driveIsSA1000 THEN
BEGIN -- 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] =>
BEGIN OPEN Inline;
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;
END;
ENDCASE => --write, can’t be verify here
BEGIN OPEN Environment;
-- to erase a SA1000Track, write one LARGE sector
IF iocb.iocbState = eraseSA1000Track THEN
tP.dataOp.dataCnt ← (sa1000PagesPerTrack+4) * wordsPerPage
END;
END
ELSE
BEGIN -- 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;
END;
tP.failCount ← 1; -- quit immediately on Header Field errors
END
ELSE -- headerOp=verify
BEGIN 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
END;
-- set the transfer command block. It either specifies a post-transfer
-- step or not, depending on
-- whether iocb.presentCylinder=currentCylinder+1
BEGIN 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
BEGIN -- 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;
END;
tC.parmBlockAddr ← tC.parmBlockAddr + physIOCBAddr;
tC.finishAddr ← tC.finishAddr + physIOCBAddr;
tC.finishTransfer.statusAddr ←
tC.finishTransfer.statusAddr + physIOCBAddr;
tC.finishTransfer.transferMask ← csb.transferMask;
END;
tP.headerOp.headerAddr ← tP.headerOp.headerAddr + physIOCBAddr;
tP.labelOp.labelAddr ← tP.labelOp.labelAddr + physIOCBAddr;
END; -- of setting up a normal transfer IOCB

labelRead => -- set up iocb for read of one label
BEGIN OPEN tP: iocb.transferParm;
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 ←
Utilities.PageFromLongPointer[DLionInputOutput.IOPage];
BEGIN 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;
END;
iocb.seekCommand ← seekCmdBlock[plainTransfer]; -- just looking
iocb.seekCommand.controlWd1 ←
iocb.seekCommand.controlWd1 + physIOCBAddr;
BEGIN 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;
END;
tP.headerOp.headerAddr ← tP.headerOp.headerAddr + physIOCBAddr;
tP.labelOp.labelAddr ← tP.labelOp.labelAddr + physIOCBAddr;
END;
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];
END;

Output: PROC [cmd: UNSPECIFIED, i: IOCBlongPtr ← NIL] = INLINE {
DLionInputOutput.Output[cmd, KCtl]};

IOCBCrossesPage: ERROR = CODE;
PageCrossCheck: PROC [i: IOCBshortPtr] = INLINE
BEGIN
IF LOOPHOLE[i, CARDINAL] MOD Environment.wordsPerPage >=
Environment.wordsPerPage - SIZE[IOCB] THEN ERROR IOCBCrossesPage;
END;

Words: PROC [sectors: CARDINAL] RETURNS [LONG CARDINAL] = INLINE {
RETURN[Inline.LongMult[sectors, Environment.wordsPerPage]]};

END....


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.