-- SA4000Impl.mesa (last edited by: Luniewski on: March 31, 1981 11:47 AM)
-- THINGS TO DO:
-- 1) Allocate ScheduleObject’s dynamically

DIRECTORY
DeviceTypes USING [sa1000, sa4000],
DiskChannel USING [
CompletionStatus, Cylinder, DiskPageNumber, DriveState, IORequestHandle],
DiskChannelBackend USING [
ChangeStateProc, DriveHandle, GetDrive, GetPAProc, GetPNProc,
GetStatusProc, NotifyIOComplete, RegisterDrive, RequestIOProc],
DiskDriverShared USING [
ScheduleHandle, ScheduleObject, GetNextPendingRequest, InsertRequest,
PeekNextPendingRequest],
Environment USING [Base, first64K],
Inline USING [LongDivMod, LowHalf],
Process USING [Detach, MsecToTicks, SetPriority, SetTimeout],
ProcessInternal USING [AllocateNakedCondition],
ProcessPriorities USING [diskInterruptPriority],
ResidentHeap USING [MakeNode],
RuntimeInternal USING [WorryCallDebugger],
SA4000Face USING [
Command, DeviceHandle, DiskAddress, GetDeviceAttributes, GetNextDevice,
GlobalStatePtr, globalStateSize, Initialize, InitializeCleanup, Initiate,
nullDeviceHandle, Operation, OperationPtr, operationSize, Poll,
Recalibrate, Status],
StoreDriverStartChain USING [Start],
Utilities USING [LongPointerFromPage],
Zone USING [Status];

SA4000Impl: MONITOR
IMPORTS
DiskChannelBackend, DiskDriverShared, Inline, Process, ProcessInternal,
ResidentHeap, RuntimeInternal, SA4000Face,
RemainingDrivers: StoreDriverStartChain, Utilities
EXPORTS StoreDriverStartChain
SHARES DiskChannel =
BEGIN

ErrorHalt: PROC =
{RuntimeInternal.WorryCallDebugger["Error in SA4000Impl"]};
ErrorHalt1: PROC RETURNS [SA4000Face.Command] = LOOPHOLE[ErrorHalt];

currentDrive: CARDINAL;
drives: ARRAY [0..3] OF DiskDriverShared.ScheduleObject;
nakedNotify: LONG POINTER TO CONDITION;
wakeupMask: WORD;
nDrives: CARDINAL ← 0;
maxRetries: CARDINAL = 24;
operationsPerDrive: CARDINAL = 2;
base: Environment.Base = Environment.first64K;

-- EXTERNAL PROCS

GetDiskAddress: PROC [
dH: DiskChannelBackend.DriveHandle, page: DiskChannel.DiskPageNumber]
RETURNS [addr: SA4000Face.DiskAddress] = INLINE
BEGIN OPEN addr;
temp: CARDINAL;
[quotient: temp, remainder: sector] ← Inline.LongDivMod[
num: page, den: dH.sectorsPerTrack];
head ← temp MOD dH.movingHeads;
cylinder ← temp/dH.movingHeads;
END;

GetPageAddress: DiskChannelBackend.GetPAProc =
{RETURN[LOOPHOLE[GetDiskAddress[dH, page]]]};

GetPageNumber: DiskChannelBackend.GetPNProc =
BEGIN OPEN LOOPHOLE[page, SA4000Face.DiskAddress];
RETURN[sector + dH.sectorsPerTrack*LONG[head + dH.movingHeads*cylinder]]
END;

GetStatus: DiskChannelBackend.GetStatusProc =
-- It is always ready since removable SA4000’s are not supported.
{RETURN[ready: TRUE, changeCount: dH.changeCount]};

ChangeState: DiskChannelBackend.ChangeStateProc = {--no-op for the SA4000--};

InterruptProcess: PROC =
BEGIN
Process.SetPriority[ProcessPriorities.diskInterruptPriority];
-- Main loop of interrupt process, which cannot be inside the monitor
-- since it calls NotifyIOComplete:
DO DiskChannelBackend.NotifyIOComplete[HandleInterrupt[]] ENDLOOP;
END;

-- ENTRY PROCS

-- Temporary logging for timing interrupt handling
-- start: INTEGER ← Inline.LowHalf[System.GetClockPulses[]];
-- end: INTEGER;
-- finished, started: INTEGER;
-- LogIndex: TYPE = [1..100];
-- log: ARRAY LogIndex OF LogEntry ← ALL[[0,0,0]];
-- LogEntry: TYPE = RECORD [duration, finished, started: INTEGER];
-- LogPtr: TYPE = LONG POINTER TO LogEntry;
-- first: LogPtr = @log[FIRST[LogIndex]];
-- last: LogPtr = @log[LAST[LogIndex]];
-- logEntry: LogPtr ← first;

--
NOTE !!!!
-- The following constant definitions of dataError and labelError should be deleted once the real
-- definitions are added to CompletionStatus in DiskChannel.mesa in the next build. (21-Jan-81)
-- Deleted (27-Feb-81)

statusTable: ARRAY SA4000Face.Status OF DiskChannel.CompletionStatus =
[ --impossible--, goodCompletion, dataError, labelError,
labelDoesNotMatch, seekFailed, hardwareError, notReady, hardwareError,
hardwareError, hardwareError, hardwareError];

HandleInterrupt: ENTRY PROC RETURNS [DiskChannel.IORequestHandle] =
--INLINE
-- This routine is called repeatedly until all completed operations have
-- been returned to main loop;
-- it then feeds the disk and goes to sleep waiting for the next interrupt.
BEGIN
RestartDisk: PROC = INLINE
BEGIN
qNode ← firstBusy; -- restart all active operations
DO
SA4000Face.Initiate[qNode.operation];
qNode ← qNode.next;
IF qNode = firstBusy OR qNode = firstFree THEN EXIT;
ENDLOOP;
END;
req: DiskChannel.IORequestHandle;
qNode: QNodePtr; -- Get next request to complete
-- Note that face aborts all operations initiated after erroneous one, until Poll
DO
IF ~QueueEmpty[] THEN
BEGIN
status: SA4000Face.Status = SA4000Face.Poll[firstBusy.operation];
IF status ~= inProgress THEN
BEGIN
-- got one
req ← firstBusy.req;
-- first pending operation has completed, for better or worse...
req.status ← statusTable[status];

IF req.count ~= req.countDone + firstBusy.operation.pageCount THEN
req.retryCount ← 0;
-- zero retry count if we have completed some pages successfully
req.countDone ← req.count - firstBusy.operation.pageCount;
-- add number of pages we finished this time through
IF status = goodCompletion THEN
BEGIN
IF firstBusy.operation.pageCount ~= 0 THEN ErrorHalt[];
FreeQNode[];
RETURN[req];
END;

IF (status=labelCheck AND req.countDone#0
-- AND req.expectLabelCheck)
-- temporary, until expectLabelCheckField is added to DiskChannel--
-- note this is coupled with FilerTransferImpl.VerifyLabel hack --
AND req.dontIncrement AND req.command=vvr)
OR (req.retryCount ← req.retryCount + 1) > maxRetries THEN
{FreeQNode[]; IF ~QueueEmpty[] THEN RestartDisk[]; RETURN[req]};
-- There has been a (soft) error
IF (req.retryCount MOD 4) = 0 THEN
SA4000Face.Recalibrate[firstBusy.operation.device];
RestartDisk[];
END;
END;
-- No successfully completed requests
FeedDisk[];
-- started ← FeedDisk[];
-- end ← Inline.LowHalf[System.GetClockPulses[]];
-- logEntry↑ ← [duration: end-start, finished: finished, started: started];
-- logEntry ← IF logEntry=last THEN first ELSE logEntry+SIZE[LogEntry];
WAIT nakedNotify; -- start ← Inline.LowHalf[System.GetClockPulses[]];
-- finished ← 0;
ENDLOOP;
END;

RequestIO: ENTRY DiskChannelBackend.RequestIOProc =
BEGIN
req.address ← GetPageAddress[DiskChannelBackend.GetDrive[req.channel], req.diskPage];
req.retryCount ← 0;
DiskDriverShared.InsertRequest[req];
-- add to queue of pending requests
--[] ←--FeedDisk[];
END;

-- INTERNAL PROCS

-- should only be called when one or more queue nodes are available
-- (i.e. QueueFull[] = FALSE)
DoRequest: INTERNAL PROC [req: DiskChannel.IORequestHandle] = -- INLINE
BEGIN
device: SA4000Face.DeviceHandle
= DiskChannelBackend.GetDrive[req.channel].driveID.handle;
qNode: QNodePtr = AllocateQNode[];
qNode.operation↑ ← SA4000Face.Operation[
clientHeader: LOOPHOLE[req.address],
diskHeader: SA4000Face.DiskAddress[0, 0, 0], labelPtr: req.label,
incrementDataPtr: ~req.dontIncrement,
command: SELECT req.command FROM
vvr => vvr, vvw => vvw, vrr => vrr, vww => vww, ENDCASE => ErrorHalt1[],
pageCount: Inline.LowHalf[req.count],
device: device, deviceStatus:,
dataPtr: Utilities.LongPointerFromPage[req.memoryPage]];
SA4000Face.Initiate[qNode.operation];
qNode.req ← req;
END;

FeedDisk: INTERNAL PROC -- RETURNS [i: INTEGER] -- =
-- keep the disk running, if possible
--INLINE-- BEGIN
req: DiskChannel.IORequestHandle; -- i ← 0;
UNTIL QueueFull[] OR (req ← GetNextPendingRequest[]) = NIL DO
DoRequest[req]; -- i ← i+1;
ENDLOOP;
END;

-- allows current drive to complete all requests on the current cylinder, then
-- round-robins through the drives (no provision is made for overlapped seek).
GetNextPendingRequest: INTERNAL PROC RETURNS [DiskChannel.IORequestHandle] =
--INLINE-- BEGIN
req: DiskChannel.IORequestHandle;
drive: DiskDriverShared.ScheduleHandle ← @drives[currentDrive];
-- always give priority to sectors on the current cylinder of the current drive
req ← DiskDriverShared.PeekNextPendingRequest[drive];
IF req ~= NIL AND drive.currentCylinder = req.address.cylinder THEN
RETURN[DiskDriverShared.GetNextPendingRequest[drive]];
-- whenever a cylinder change is required, go to next drive
-- (hoping that the cylinder change might be avoided)
THROUGH [0..nDrives) DO
currentDrive ← currentDrive + 1;
IF currentDrive >= nDrives THEN currentDrive ← 0;
drive ← @drives[currentDrive];
req ← DiskDriverShared.GetNextPendingRequest[drive];
IF req ~= NIL THEN RETURN[req];
ENDLOOP;
RETURN[NIL]
END;

-- Queue node machinery

QNodePtr: TYPE = LONG POINTER TO QNode;
QNode: TYPE = RECORD [
operation: SA4000Face.OperationPtr,
req: DiskChannel.IORequestHandle,
next: QNodePtr];
firstBusy, firstFree: QNodePtr;

AllocateQNode: INTERNAL PROC RETURNS [qNode: QNodePtr] = INLINE
BEGIN
qNode ← firstFree;
IF (firstFree ← firstFree.next) = firstBusy THEN firstFree ← NIL
END;

FreeQNode: INTERNAL PROC = INLINE
{IF QueueFull[] THEN firstFree ← firstBusy; firstBusy ← firstBusy.next};

QueueFull: INTERNAL PROC RETURNS [BOOLEAN] = INLINE {RETURN[firstFree = NIL]};

QueueEmpty: INTERNAL PROC RETURNS [BOOLEAN] = INLINE {RETURN[firstFree = firstBusy]};

-- INITIALIZATION PROCS

RegisterDrives: PROC = -- Create and register drive objects.
BEGIN
device: SA4000Face.DeviceHandle ← SA4000Face.nullDeviceHandle;
d: DiskDriverShared.ScheduleHandle;
gsp: SA4000Face.GlobalStatePtr;
status: Zone.Status;
[gsp, status] ← ResidentHeap.MakeNode[
n: SA4000Face.globalStateSize, alignment: a16];
SA4000Face.Initialize[t: wakeupMask, globalState: gsp];
WHILE (device ← SA4000Face.GetNextDevice[device]) ~=
SA4000Face.nullDeviceHandle DO
d ← @drives[nDrives];
d.drive.driveID ← [type: DeviceTypes.sa4000, handle: device];
[cylinders: d.drive.cylinders, movingHeads: d.drive.movingHeads,
fixedHeads: d.drive.fixedHeads, sectorsPerTrack: d.drive.sectorsPerTrack]
← SA4000Face.GetDeviceAttributes[device];
IF d.drive.sectorsPerTrack = 16 THEN d.drive.driveID.type ← DeviceTypes.sa1000;
d.drive.nPages ←
(LONG[d.drive.cylinders]*d.drive.movingHeads +
d.drive.fixedHeads)*d.drive.sectorsPerTrack;
d.drive.requestIO ← RequestIO;
d.drive.getPageAddress ← GetPageAddress;
d.drive.getPageNumber ← GetPageNumber;
d.drive.state ← inactive;
d.drive.changeCount ← 1; -- removable SA4000’s are not supported
d.drive.getStatus ← GetStatus;
d.drive.changeState ← ChangeState;
d.currentSector ← 0;
d.currentCylinder ← 0;
d.first ← NIL;
DiskChannelBackend.RegisterDrive[@d.drive];
nDrives ← nDrives + 1;
ENDLOOP;
IF nDrives ~= 0 THEN SA4000Face.InitializeCleanup[];
END;

InitGlobals: PROC =
-- Set up ring of queue nodes and associated IOCB’s.
BEGIN
MakeQNode: PROC RETURNS [qNode: QNodePtr] =
BEGIN
rp: Environment.Base RELATIVE POINTER TO QNode;
status: Zone.Status;
[rp, status] ← ResidentHeap.MakeNode[SIZE[QNode]];
IF status ~= okay THEN ErrorHalt[];
qNode ← @base[rp];
END;
MakeOperation: PROC RETURNS [operation: SA4000Face.OperationPtr] =
BEGIN
rp: Environment.Base RELATIVE POINTER TO SA4000Face.Operation;
status: Zone.Status;
[rp, status] ← ResidentHeap.MakeNode[
n: SA4000Face.operationSize, alignment: a16];
IF status ~= okay THEN ErrorHalt[];
operation ← @base[rp];
END;
qNode, qNodeFirst: QNodePtr;
qNode ← qNodeFirst ← MakeQNode[];
qNode.operation ← MakeOperation[];
THROUGH (0..operationsPerDrive*nDrives) DO
qNode ← qNode.next ← MakeQNode[]; qNode.operation ← MakeOperation[];
ENDLOOP;
qNode.next ← qNodeFirst; -- close the ring of queue nodes
firstBusy ← firstFree ← qNode; -- initial state = queue empty
END;

Start: PUBLIC PROC = {RemainingDrivers.Start[]}; -- exported to StoreDriverStartChain

-- Initialization

[cv: nakedNotify, mask: wakeupMask] ← ProcessInternal.AllocateNakedCondition[];
Process.SetTimeout[condition: nakedNotify, ticks: Process.MsecToTicks[1000]];
RegisterDrives[];
InitGlobals[];
Process.Detach[FORK InterruptProcess];
currentDrive ← 0; -- starting drive
END.


LOG
(For earlier log entries see Pilot 4.0 archive version.)

April 13, 1980 10:35 PM
Forrest
Make IOCSImpl.InitializeNakedConditionVariable a local inline

June 11, 1980 10:08 AM
Luniewski
Initialize the state, changeCount and getStatus fields of
DriveObject’s correctly

June 25, 1980 9:46 AM
McJones
Changes to SA4000Face for 10-word labels

July 19, 1980 3:12 PM
Jose
Changes for addition of changeState field to DriveObject

July 24, 1980 2:52 PM
Luniewski
Initialize drive state in drive objects to inactive

August 28, 1980 11:56 AM
McJones
Convert to ProcessInternal.AllocateNakedCondition

October 11, 1980 9:01 PM
Forrest
hack for label check errors....

January 9, 1981 10:08 AM
Gobbel
Make operands for disk page count computation be LONG.

January 31, 1981 10:44 AM
Fay
Change statusTable to distinguish between dataError and labelError rather than report both as
checksumError; reduced operationsPerDrive from 4 to 2; changed RegisterDrives to
differentiate between SA1000 and SA4000.
February 27, 1981 9:04 AMYokota
LOOPHOLE for dataError and labelError are removed.
March 31, 1981 11:47 AMLuniewski
Do more retries and recalibrates on disk errors. Detect goodCompletion with run not
complete and complain if so.