-- SA800HeadD0.mesa (last edited by: Forrest on: October 9, 1980 11:19 AM)

DIRECTORY
DeviceCleanup USING [Await, Item, Reason],
D0InputOutput USING [ControllerNumber, IOPage, GetNextController, fdc],
HeadStartChain USING [Start],
Inline USING [BITAND, BITOR, BITSHIFT],
SA800Face USING [Attributes, Context, DeviceHandle, Function, OperationPtr, Status],
SA800NeckD0,
System USING [GetClockPulses, Pulses];

SA800HeadD0: PROGRAM
IMPORTS
D0InputOutput, DeviceCleanup, RemainingHeads: HeadStartChain, Inline,
SA800NeckD0, System
EXPORTS SA800Face, HeadStartChain =
BEGIN OPEN SA800Face, SA800NeckD0;

-- EXPORTED VARIABLES
operationBlockLength: PUBLIC CARDINAL ← SIZE[IOCB] + 8;
initialAllocationLength: PUBLIC CARDINAL ← operationBlockLength;
nullDeviceHandle: PUBLIC DeviceHandle ← LOOPHOLE[-1];

-- LOCAL CONSTANTS & VARIABLES
task: UNSPECIFIED; --This variable has been shifted left to the proper postion.
csb: CSBPtr ← NIL; --If this stays NIL, the controller did not respond
loop: CARDINAL ← 0; --diagnostic counter (Re: QueueImmediate)--
resetIOCB: IOCBlongPtr;
formatTrackSize: CARDINAL = 80; -- value big enough for all format track operations
state: State ←
[user: [protect: FALSE, format: IBM, density: single, sectorLength: 256],
head: [status: diskChange, sectorIndex: 2]];

sectorMatrix: ARRAY SectorIndex OF SectorMatrix =
[[sectorsPerTrack: 26, gap3: 27], -- 128 byte sectors (index = 0)
[sectorsPerTrack: 15, gap3: 48], -- 256 byte sectors (index = 1)
[sectorsPerTrack: 8, gap3: 90], -- 512 byte sectors (index = 2)
[sectorsPerTrack: 4, gap3: 224]]; --1024 byte sectors (index = 3)

wordsPerSector: ARRAY SectorIndex OF CARDINAL = [64, 128, 256, 512];


commandMatrix: ARRAY Function OF CommandMatrix = [
--nop-- [nop, nopWakeAllow, NopStuff],
--recalibrate-- [seekTrack, seekWakeAllow, RecalibrateStuff],
--recovery => noop-- [nop, nopWakeAllow, NopStuff],
--readSector-- [readData, readWakeAllow, ReadSectorStuff],
--writeSector-- [writeData, writeWakeAllow, WriteSectorStuff],
--writeDeletedSector-- [writeDeletedData, writeWakeAllow, WriteDeletedSectorStuff],
--readID-- [readID, readWakeAllow, ReadIDStuff],
--FormatTrack-- [formatTrack, writeWakeAllow, FormatTrackStuff]];


-- PROCS

-- This procedure initiates an operation. The client passes an Operation block with most of the parameters included. We format the parameters that are unique to this implementation of the Head and microcode, add those not supplied by the client. The Operation block can then be called an IOCB and may then be chained to the CSB. If the device is currently idle, set cmdWakeAllow and the device will generate a wakeup withing ~1.5 usecs. If it is not idle the microcode should get to the new IOCB if we just tack it to the end of the list.

BuildStdFunction: PROC [op: OperationPtr, runContinuation: BOOLEAN ← FALSE]
RETURNS [status: Status] =
BEGIN OPEN Inline;
iocb: IOCBlongPtr = LOOPHOLE[op + 8];

-- If we have a diskChange status, then nothing else can be done.
IF state.head.status.diskChange THEN {status ← diskChange; GOTO error};
SELECT op.function FROM
nop, readSector, readID, recalibrate, recovery => NULL;
writeSector, writeDeletedSector, formatTrack =>
IF state.user.protect OR state.head.status.writeProtect THEN
{status ← writeProtect; GOTO error};
ENDCASE;
IF runContinuation THEN
BEGIN -- don’t clobber next, softwareNext
softwareNextCopy: IOCBlongPtr = iocb.softwareNext;
nextCopy: IOCBlongPtr = iocb.next;
Clear16Words[iocb];
iocb.softwareNext ← softwareNextCopy;
iocb.next ← nextCopy;
iocb.wake ← commandMatrix[op.function].wake;
iocb.result ← clearResult;
iocb.function ← commandMatrix[op.function].function;
commandMatrix[op.function].stuffer[iocb, op];
RunThisIOCB[iocb] -- queues already OK
END
ELSE
BEGIN
Clear16Words[iocb];
iocb.wake ← commandMatrix[op.function].wake;
iocb.function ← commandMatrix[op.function].function;
iocb.result ← clearResult;
commandMatrix[op.function].stuffer[iocb, op];
IF op.count#0 THEN OnIOCBchain[iocb]; -- ELSE nothingToDo
END;
status ← BITAND[state.head.status, infoMask];
status.inProgress ← op.count#0; status.busy ← csb.state.next # NIL;
EXITS
error => status ← BITOR[status, BITAND[state.head.status, infoMask]];
END;

Clear16Words: PROC [pointer: LONG POINTER] =
INLINE {LOOPHOLE[pointer, LONG POINTER TO ARRAY [0..16) OF WORD]↑ ← ALL[0]};

DiskChangeClear: PUBLIC PROC [device: DeviceHandle] =
{state.head.status.diskChange ← FALSE; Reset[device]};

GetContext: PUBLIC PROC [DeviceHandle] RETURNS [Context] = {RETURN[state.user]};

GetDeviceAttributes: PUBLIC PROC [DeviceHandle] RETURNS [Attributes] =
BEGIN
RETURN[[
deviceType: SA800, numberOfCylinders: 77, numberOfHeads: 1,
trackLength: formatTrackSize]]
END;

GetNextDevice: PUBLIC PROC [device: DeviceHandle] RETURNS [DeviceHandle] =
BEGIN OPEN D0InputOutput, Inline;
RETURN[SELECT TRUE FROM
device = nullDeviceHandle =>
IF csb = NIL THEN nullDeviceHandle ELSE BITSHIFT[GetNextController[fdc, 0], 8],
-- IF controller ← GetNextController[fdc, disk.controller] # 0 THEN
-- the world has decided on multiple drives/controller.
-- This code is not intended to handle that situation.
ENDCASE => nullDeviceHandle];
END;

-- This code will only handles a single controller/single device configuration.
-- It does check for no controller.
Initialize: PUBLIC PROC [notify: WORD, initialAllocation: LONG POINTER] =
BEGIN
IF csb # NIL THEN
BEGIN
csb.state ← [
next:NIL, abortMicrocode: 0, notify:Inline.BITOR[notify, 100000B], tail:NIL];
-- The resetIOCB is used by SetChipParms. It acutally uses it three (3)
-- times during the SetChipParms, but that is of little consequence here.
resetIOCB ← initialAllocation + 8;
Reset[nullDeviceHandle]; --Pass initial parameters to FDC chip
END;
END;

--This routine will wait for the IOCB chain to empty before letting the debugger get control. For the time being (i.e., during debugging) it will also release after a ~15 second wait. The theory is that if the queue did not go empty in 15 seconds, it never will, and the debugger is going to aid in finding out why the hell we got here to start with.
InitializeCleanup: PUBLIC PROC [device: DeviceHandle] =
BEGIN
wait: System.Pulses = [15000000/LONG[64]];
item: DeviceCleanup.Item;
reason: DeviceCleanup.Reason;
csbState: CSBState;
DO
reason ← DeviceCleanup.Await[@item];
SELECT reason FROM
turnOff, kill =>
BEGIN
clock: System.Pulses = System.GetClockPulses[];
WHILE csb.state.next # NIL AND (System.GetClockPulses[] - clock) <= wait
DO ENDLOOP;
csbState ← csb.state;
END;
turnOn => csb.state ← csbState;
ENDCASE;
ENDLOOP;
END;

Initiate: PUBLIC PROC [operationPtr: OperationPtr] RETURNS [Status, CARDINAL] =
BEGIN
SELECT operationPtr.function FROM
nop, recalibrate, recovery => operationPtr.count ← 1;
ENDCASE;
RETURN[
BuildStdFunction[operationPtr],
--800ms max seek time + tracks*(3ms step + 125ms/rotation)
3+(operationPtr.count/20)]
END;

--The iocb is ready to go on the CSB chain. If csb.state.next = state.head.tail = NIL then create an initial entry and start the microcode by setting cmdWakeAllow. The microcode should wake within 15 usecs.
-- There is a race condition regarding an IOCB completing while we are chaining.
-- This is is handled in Poll (and in reset)...
OnIOCBchain: PROC [iocb: IOCBlongPtr] =
BEGIN
iocb.next ← iocb.softwareNext ← NIL;
IF csb.state.next = NIL THEN {csb.state.tail ← iocb; RunThisIOCB[iocb]}
ELSE
BEGIN
IF LOOPHOLE[csb.state.tail-8, OperationPtr].count<=1 THEN
csb.state.tail.next ← iocb; -- place on hardware queue if tail op will complete
csb.state.tail.softwareNext ← iocb; -- always place on software queue
csb.state.tail ← iocb; -- update tail to point to new IOCB
END;
END;

Poll: PUBLIC PROC [operationPtr: OperationPtr] RETURNS [status: Status] =
BEGIN
iocb: IOCBlongPtr = LOOPHOLE[operationPtr + 8];
status ← ResultToStatus[iocb];
IF status.inProgress THEN RETURN
ELSE IF status.error THEN
BEGIN
IF iocb.next=NIL AND iocb.softwareNext#NIL THEN RunThisIOCB[iocb.softwareNext];
RETURN; -- an error occured in the middle of a run. Start the next IOCB
END
ELSE -- status=goodCompletion
BEGIN -- ASSUMES 1 HEAD, SINGLE DENSITY IBM FORMAT
SELECT operationPtr.function FROM
nop, recalibrate, recovery => NULL;
readSector, writeDeletedSector, writeSector =>
BEGIN OPEN da: operationPtr.address;
IF (da.sector ← da.sector+1)
> sectorMatrix[state.head.sectorIndex].sectorsPerTrack THEN
{da.sector ← 1; da.cylinder ← da.cylinder+1};
IF operationPtr.incrementDataPointer THEN
BEGIN OPEN b: operationPtr.buffer;
b.address ← b.address+wordsPerSector[state.head.sectorIndex];
b.length ← b.length-wordsPerSector[state.head.sectorIndex]
END;
END;
readID =>
BEGIN OPEN da: operationPtr.address;
IF (da.sector ← da.sector+1)
> sectorMatrix[state.head.sectorIndex].sectorsPerTrack THEN
{da.sector ← 1; da.cylinder ← da.cylinder+1};
IF operationPtr.incrementDataPointer THEN
BEGIN OPEN b: operationPtr.buffer;
b.address ← b.address+3; b.length ← b.length-3
END;
END;
formatTrack =>
BEGIN
operationPtr.address.cylinder ← operationPtr.address.cylinder+1;
operationPtr.address.sector ← 1;
IF operationPtr.incrementDataPointer THEN
BEGIN OPEN b: operationPtr.buffer;
b.address ← b.address+formatTrackSize; b.length ← b.length-formatTrackSize
END;
END;
ENDCASE;
-- get things moving
IF (operationPtr.count ← operationPtr.count-1) = 1 THEN
iocb.next ← iocb.softwareNext; -- will be last page of run
IF operationPtr.count#0 THEN
BEGIN
status ← BuildStdFunction[operationPtr, TRUE];
IF ~status.inProgress AND iocb.next#NIL THEN RunThisIOCB[iocb.next];
END;
END;
-- we are here if we are done with this IOCB, and we didn’t explicitly start another.
-- check for the race mentioned in OnICCBChain
IF iocb.next#NIL AND csb.state.next=NIL AND ResultToStatus[iocb.next].inProgress THEN
RunThisIOCB[iocb.next];
END;

ResultToStatus: PROC [iocb: IOCBlongPtr]
RETURNS [status: Status] =
BEGIN
lateDMA: Completion = [type: 1, code: 1];
sectorNotFound: Completion = [type: 3, code: 0];
idCRCerror: Completion = [type: 1, code: 2];
dataCRCerror: Completion = [type: 1, code: 3];
notReady: Completion = [type: 2, code: 0];
track00NotFound: Completion = [type: 2, code: 2];
clearStdResult: StandardResult =
[deleted: FALSE, completion: [type: 0B, code: 0B]];
controllerError: ControllerStatus =
[dmaWakeReq: TRUE, oDataParity: TRUE, oFault: TRUE, fdcIntF: FALSE];
result: Result ← iocb.result;
IF result # clearResult THEN
BEGIN
status ←
[diskChange: state.head.status.diskChange
-- OR (~state.head.status.notReady # result.driveStatus.ready0),
OR (state.head.status.notReady = result.driveStatus.ready0),
na1: FALSE,
twoSided: FALSE,
na3: FALSE,
error: (result.standardResult.completion.type # 0) OR
(Inline.BITAND[result.controllerStatus, controllerError] # 0),
inProgress: FALSE,
recalibrateError: result.standardResult.completion = track00NotFound,
dataLost: result.standardResult.completion = lateDMA OR iocb.length # 0,
notReady: result.standardResult.completion = notReady OR
~result.driveStatus.ready0,
writeProtect: result.driveStatus.writeProtect OR state.user.protect,
deletedData: result.standardResult.deleted,
recordNotFound: result.standardResult.completion = sectorNotFound,
crcError: result.standardResult.completion = idCRCerror OR
result.standardResult.completion = dataCRCerror,
track00: result.driveStatus.track00,
index: result.driveStatus.indexPulse, busy: FALSE];
status.error ← (Inline.BITAND[status, statusError] # 0) OR status.diskChange;
state.head.status ← status; --save most recent status for drive
-- status.diskChange ← FALSE; ++only returned on Initiate
END
ELSE {status ← inProgress; status.busy ← csb.state.next # NIL};
END;

Reset: PUBLIC PROC [device: DeviceHandle] =
BEGIN OPEN SA800NeckD0;
op: OperationPtr = LOOPHOLE[resetIOCB - 8];
revolutionTime: System.Pulses = [LONG[2000]]; --125 msecs+/-
reset: UNSPECIFIED = 120001B; --Command to reset chip
rp0: UNSPECIFIED = 120000B; --Parameter 0 for chip reset
current: IOCBlongPtr;
-- Constants unique to setting initial chip parameters
initialize: CARDINAL = 15B; --specify initialize subcommand
surface0: CARDINAL = 20B; --specify surface0
surface1: CARDINAL = 30B; --specify surface1
stepRate: CARDINAL = 10; --millisecond stepping rate
headSettle: CARDINAL = 15;
--millisecond time to let head settle after stepping
headUnload: CARDINAL = 4; --revolutions before head is unloaded
headLoad: CARDINAL = 36; --milliseconds allowed to load heads (MOD4)
specifyInit: IOCB =
[next: NIL, softwareNext: NIL, address: NIL, length: 0, result: clearResult,
wake: initWakeAllow, function: specify,
p0: [parameter, parm1, initialize],
p1: [parameter, parm2, stepRate],
p2: [parameter, parm3, headSettle],
p3: [parameter, 0, headUnload*16 + headLoad/4],
p4: LOOPHOLE[0], unused: 0];
specifybadTracks: IOCB =
[next: NIL, softwareNext: NIL, address: NIL, length: 0, result: clearResult,
wake: initWakeAllow, function: specify, p0: [parameter, parm1, surface0],
p1: [parameter, parm2, 377B],
p2: [parameter, parm3, 377B],
p3: [parameter, 0, 377B],
p4: LOOPHOLE[0], unused: 0];
dataWindow: CARDINAL ← 344B; --data window value for 250Kbit/sec rate

-- This is going to put the IOCB on a presumed empty chain and WAIT FOR IT TO
-- COMPLETE! If it doesn’t complete in "n" iterations through the loop,
-- well....sheeeeeeit! NOTE: The counter "loop" is the maximum number of times
-- we had to stay in this loop waiting for the microcode to finish an operation.
-- If it every hits ’n’, the magic number, we have failed. Since this is pure
-- microcode and no data transfer, that’s not too bad. For you floppy disk wizards,
-- the variable loop in the global frame is the maximum number of times we stayed in
-- this loop. Should this operation be in doubt, look there for hints. If one
-- feels confident, take out the maintenance of the counter.
QueueImmediate: PROC [iocb: IOCBlongPtr] =
BEGIN
index: CARDINAL;
iocb.next ← NIL;
RunThisIOCB[iocb];
FOR index IN [0..1000) DO IF csb.next=NIL THEN EXIT; loop ← loop ENDLOOP;
IF index > loop THEN loop ← index;
END;

--Reset the controller so we don’t get any more wakes.
OUTPUT[0, task + 0]; --write zero to reset register
OUTPUT[0, task + 1B]; --write zero to wake allow register
OUTPUT[1B, task + 0]; --request controller master reset
IF (current ← csb.state.next) # NIL THEN
BEGIN
--The microcode was "busy" by the definition that the IOCB chain is not empty.
-- Since the client is calling us, this operation must be taking longer than
-- it should, whatever that is. We want to kill the current operation, forcing
-- the microcode to proceed down the merry path of IOCBs. First clear the current
-- operation from the chip be issuing a reset to the chip.
clock: System.Pulses = System.GetClockPulses[];
-- Reset the FDC chip.
-- (This probably loses all initialization parameters. We will assume it does.)
OUTPUT[reset, task + 6];
OUTPUT[rp0, task + 6];
--In order for Reset to work, the chain must be empty. To do that, we need to
-- save the address of the next (logically) IOCB to be activated after the one
-- we blow away. Then we can restore it in the CSB when we finish.
current.next ← NIL;
-- ask the read loop to stop
csb.state.abortMicrocode ← -1;
--Start the microcode again with an index wake
-- (which includes IOATTEN so DMA requests will complete).
UNTIL csb.state.next = NIL DO
OUTPUT[3B, task + 1B];
--Wait for the microcode to finish off the current operation. This could take
-- a full revolution of the diskette!!!! Note that this wake generation is
-- coming from the controller, not the FDC chip. If we don’t get the wake,
-- ever, then we will hang inside the outer loop, FOREVER (give or take
-- a microsecond).
WHILE (clock - System.GetClockPulses[]) < revolutionTime DO --ARGH!!!
IF csb.state.next = NIL THEN EXIT; --inner loop only
ENDLOOP;
ENDLOOP;
csb.state.abortMicrocode ← 0;
END;
-- Well! We just wiped out all the parameters set up during initialization.
-- Guess we better set them back before anybody notices.
OUTPUT[dataWindow, task + 4B];
resetIOCB↑ ← specifyInit;
QueueImmediate[resetIOCB];
resetIOCB↑ ← specifybadTracks;
QueueImmediate[resetIOCB];
resetIOCB.p0 ← [parameter, parm1, surface1];
resetIOCB.result ← clearResult;
QueueImmediate[resetIOCB];
[] ← ResultToStatus[resetIOCB];
-- Request track00 since the chip no longer knows where it is. If this fails,
-- no one will ever know. If this fails, no one will ever know, but chances of
-- future success are dim.
op.function ← recalibrate; [] ← Initiate[op];
END;

RunThisIOCB: PROC [iocb: IOCBlongPtr] =
INLINE {csb.state.next ← iocb; OUTPUT[5B, task + 1B]};

SetContext: PUBLIC PROC [device: DeviceHandle, context: Context] RETURNS [ok: BOOLEAN] =
BEGIN
IF (ok ← (context.format # Troy) AND (context.density # double) AND
(SELECT context.sectorLength FROM 64, 128, 256, 512 => TRUE, ENDCASE => FALSE))
THEN state.user ← context;
state.head.sectorIndex ← SELECT state.user.sectorLength FROM
64 => 0, 128 => 1, 256 => 2, ENDCASE => 3;
END;

Start: PUBLIC PROC = {RemainingHeads.Start[]}; -- exported to StartChain

StartHead: PROC =
BEGIN OPEN D0InputOutput, Inline;
controller: ControllerNumber = GetNextController[fdc, 0];
IF controller # 0 THEN
{csb ← LOOPHOLE[@IOPage[controller]]; task ← BITSHIFT[controller, 4]}
END;

-- Procedures called by BuildStdFunction via Commandmatrix to stuff IOCB’s
FormatTrackStuff: PROC [iocb: IOCBlongPtr, operation: OperationPtr] =
BEGIN
RecordId: TYPE = MACHINE DEPENDENT RECORD [
cyl: [0..256), head: [0..256), sector: [0..256), sectorLength: [0..256)];
gap1: CARDINAL = 26;
gap5: CARDINAL = 40;
gap3: CARDINAL = sectorMatrix[state.head.sectorIndex].gap3;
numberOfSectors: CARDINAL = sectorMatrix[state.head.sectorIndex].sectorsPerTrack;
p1: ChipLoad = [parameter, parm2, gap3]; --intersector gap--
p2: ChipLoad = --sectors/track & length--
[parameter, parm3,
numberOfSectors + Inline.BITSHIFT[state.head.sectorIndex, 5]];
p3: ChipLoad = [parameter, parm4, gap5]; --last sector to index address mark--
p4: ChipLoad = [parameter, 0, gap1]; --index to first ID record address mark--
trackBuffer: LONG POINTER TO RecordId ← operation.buffer.address;
FOR index: CARDINAL IN (0..numberOfSectors] DO
trackBuffer↑ ← [
cyl: operation.address.cylinder, head: 0, sector: index,
sectorLength: state.head.sectorIndex];
trackBuffer ← trackBuffer + SIZE[RecordId];
ENDLOOP;
iocb.address ← operation.buffer.address;
iocb.length ← SIZE[RecordId]*numberOfSectors;
iocb.p0 ← [parameter, --track address --parm1, operation.address.cylinder];
iocb.p1 ← p1; iocb.p2 ← p2; iocb.p3 ← p3; iocb.p4 ← p4;
END;

NopStuff: PROC [iocb: IOCBlongPtr, operation: OperationPtr] = {};

ReadIDStuff: PROC [iocb: IOCBlongPtr, operation: OperationPtr] =
BEGIN
iocb.address ← operation.buffer.address;
iocb.length ← MIN[2, operation.buffer.length];
iocb.p0 ← [parameter, parm1, operation.address.cylinder];
iocb.p1 ← [parameter, parm2, 0];
iocb.p2 ← [parameter, 0, 1B];
END;

ReadSectorStuff, WriteSectorStuff, WriteDeletedSectorStuff: PROC [
iocb: IOCBlongPtr, operation: OperationPtr] =
BEGIN
iocb.address ← operation.buffer.address;
iocb.length ← MIN[operation.buffer.length, wordsPerSector[state.head.sectorIndex]];
iocb.p0 ← [parameter, parm1, operation.address.cylinder];
iocb.p1 ← [parameter, parm2, operation.address.sector];
iocb.p2 ← [parameter, 0, Inline.BITSHIFT[state.head.sectorIndex, 5] + 1B];
END;

RecalibrateStuff: PUBLIC PROC [iocb: IOCBlongPtr, operation: OperationPtr] =
{iocb.p0 ← [parameter, 0, 0] --track ← zero--};

--Mainline Code
StartHead[];
END.
LOG
Edited: June 17, 1980 9:53 AM by AOF: Added log to file.
Edited: June 18, 1980 9:09 AM by AOF: Added additional enumerated element (recovery) to functions allowed in Initiate.
Edited: June 27, 1980 10:59 AM by AOF: Changed definition of Reset and restructured code somewhat.
Edited: September 15, 1980 3:59 PM by Forrest: Runs of pages (software).
Edited: September 17, 1980 3:13 PM by Forrest: Redefine Disk Change.
Edited: October 2, 1980 1:31 PM by Luniewski: status.diskChange => status.error.
Edited: October 9, 1980 11:18 AM by Forrest: Debug Runs of Pages.