-- FloppyChannelImpl.mesa
-- last edited by: Luniewski on: February 5, 1981 6:49 PM
-- last edited by: Forrest on: March 23, 1981 7:23 PM

-- THINGS TO DO:
-- 1) Protect against multiple simultaneous calls
-- 2) Troy format

DIRECTORY
DeviceTypes USING [sa800],
DiskChannel USING [PVHandle],
DiskChannelBackend USING [
ChangeStateProc, DriveHandle, DriveObject, GetStatusProc, RegisterDrive],
DiskDriverShared USING [ScheduleHandle, ScheduleObject],
Environment USING [Base, first64K, wordsPerPage],
FloppyChannel USING [Attributes, Buffer, Context, DiskAddress, Status],
FloppyChannelInternal USING [maxDrives, OpBlock],
Inline USING [BITAND, LongCOPY],
PhysicalVolume USING [Error, ErrorType],
PilotFloppyFormat USING [
firstRealCylinder, logicalTrack0, FloppyPageCount, pilotContext],
Process USING [MsecToTicks, SetTimeout],
ProcessInternal USING [AllocateNakedCondition],
ResidentHeap USING [MakeNode],
SA800Face USING [
Attributes, Buffer, Context, DeviceHandle, DiskAddress, DiskChangeClear,
Function, GetContext, GetDeviceAttributes, GetNextDevice,
initialAllocationLength, Initialize, InitializeCleanup, Initiate,
nullDeviceHandle, operationBlockLength, OperationPtr, Poll, SetContext,
Status],
SimpleSpace USING [Create, LongPointer, Map, Unmap],
SwapperDriverStartChain USING [Start],
Space USING [defaultWindow, Handle];

FloppyChannelImpl: MONITOR
IMPORTS
DiskChannelBackend, Inline, PhysicalVolume, Process, ProcessInternal,
ResidentHeap, SA800Face, SimpleSpace,
RemainingDrivers: SwapperDriverStartChain
EXPORTS
DiskChannel, FloppyChannel, FloppyChannelInternal, PhysicalVolume,
SwapperDriverStartChain =
BEGIN OPEN FloppyChannel, FloppyChannelInternal;

-- Constants

idLength: CARDINAL = 3;
maxDrives: CARDINAL = FloppyChannelInternal.maxDrives;
maxRetries: CARDINAL = 30; -- should be 0 MOD 3
maxWordsPerTrack: CARDINAL = 4096;
trackPages: CARDINAL = maxWordsPerTrack/Environment.wordsPerPage;
nTasks: CARDINAL = 2;

-- Global Variables
clientDataSpace: Space.Handle;
drives: ARRAY [0..maxDrives) OF DiskDriverShared.ScheduleObject;
nakedNotify: LONG POINTER TO CONDITION;
nDrives: CARDINAL ← 0;
doRequestTask, recalRecoverTask: SA800Face.OperationPtr;
wakeupMask: WORD;

-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- Types and Loopholes
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

FaceToChannelContext: PROC [c: SA800Face.Context]
RETURNS [FloppyChannel.Context] =
INLINE {RETURN[LOOPHOLE[c]]};

ChannelToFaceContext: PROC [c: FloppyChannel.Context]
RETURNS [SA800Face.Context] =
INLINE {RETURN[LOOPHOLE[c]]};

FaceToChannelAttributes: PROC [a: SA800Face.Attributes]
RETURNS [FloppyChannel.Attributes] =
INLINE {RETURN[LOOPHOLE[a]]};

--DiskChannel.--DriveObject: PUBLIC TYPE = DiskChannelBackend.DriveObject;
--PhysicalVolume.-- Handle: PUBLIC TYPE = DiskChannel.PVHandle;

GetHandle: PROC [dH: DiskChannelBackend.DriveHandle] RETURNS [Handle] =
INLINE {RETURN[Handle[LOOPHOLE[dH], dH.changeCount]]};

GetDrive: PROC [handle: Handle] RETURNS [DiskChannelBackend.DriveHandle] =
INLINE {RETURN[LOOPHOLE[handle.drive]]};

GetDeviceHandle: PROC [handle: Handle] RETURNS [SA800Face.DeviceHandle] =
INLINE {RETURN[GetDrive[handle].driveID.handle]};

GetBuffer: PROC [buf: FloppyChannel.Buffer]
RETURNS [buffer: SA800Face.Buffer] =
{RETURN[SA800Face.Buffer[address: buf.address, length: buf.length]]};

GetDA: PROC [a: FloppyChannel.DiskAddress]
RETURNS [address: SA800Face.DiskAddress] =
{RETURN[[cylinder: a.cylinder, head: a.head, sector: a.sector]]};

FilterStatus: PROC [s: SA800Face.Status]
RETURNS [status: FloppyChannel.Status] =
BEGIN
initialMask: SA800Face.Status = [ --Face status elements to keep--
diskChange: TRUE, na1: FALSE, twoSided: TRUE, na3: FALSE, error: TRUE,
inProgress: TRUE, recalibrateError: TRUE, dataLost: TRUE,
notReady: TRUE, writeProtect: TRUE, deletedData: TRUE,
recordNotFound: TRUE, crcError: TRUE, track00: TRUE,
index: FALSE, busy: FALSE];
status ← LOOPHOLE[Inline.BITAND[s, initialMask]];
SELECT TRUE FROM
s.inProgress, s.error => NULL;
s.na1, s.na3 => {status.hardwareError ← TRUE; status.error ← TRUE};
ENDCASE => status.goodCompletion ← TRUE;
END;

-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- MONITOR EXTERNAL Procs
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

BasicChangeState: DiskChannelBackend.ChangeStateProc = {};

GetContext: PUBLIC PROC [handle: Handle] RETURNS [context: Context] =
{RETURN[FaceToChannelContext[
SA800Face.GetContext[GetDeviceHandle[handle]]]]};

GetDeviceAttributes: PUBLIC PROC [handle: Handle]
RETURNS [attributes: Attributes] =
{RETURN[FaceToChannelAttributes[
SA800Face.GetDeviceAttributes[GetDeviceHandle[handle]]]]};

GetStatus: DiskChannelBackend.GetStatusProc = {
ready ← ~Nop[GetHandle[dH]].status.notReady;
RETURN[ready, dH.changeCount]};

Nop: PUBLIC PROC [handle: Handle] RETURNS [status: Status] =
{op: OpBlock ← [handle, nop]; RETURN[DoRequest[@op].status]};

ReadID: PUBLIC PROC [handle: Handle, address: DiskAddress, buffer: Buffer]
RETURNS [status: Status] =
BEGIN
op: OpBlock ← [
handle, readID, GetDA[address], GetBuffer[buffer], FALSE, 1];
RETURN[DoClientRequest[@op].status];
END;

ReadSectors: PUBLIC PROC [
handle: Handle, address: DiskAddress, buffer: Buffer, count: CARDINAL,
incrementDataPtr: BOOLEAN]
RETURNS [status: Status, countDone: CARDINAL] =
BEGIN
op: OpBlock ← [
handle, readSector, GetDA[address], GetBuffer[buffer],
incrementDataPtr, count];
[status, countDone] ← DoClientRequest[@op];
END;

SetContext: PUBLIC PROC [handle: Handle, context: Context]
RETURNS [ok: BOOLEAN] =
BEGIN
ok ← SA800Face.SetContext[
GetDeviceHandle[handle], ChannelToFaceContext[context]];
InitDisk[handle.drive, context];
END;

WriteSectors: PUBLIC PROC [
handle: Handle, address: DiskAddress, buffer: Buffer, count: CARDINAL,
incrementDataPtr: BOOLEAN]
RETURNS [status: Status, countDone: CARDINAL] =
BEGIN
op: OpBlock ← [
handle, writeSector, GetDA[address],
GetBuffer[buffer],incrementDataPtr, count];
[status, countDone] ← DoClientRequest[@op];
END;

WriteDeletedSectors: PUBLIC PROC [
handle: Handle, address: DiskAddress, buffer: Buffer, count: CARDINAL,
incrementDataPtr: BOOLEAN]
RETURNS [status: Status, countDone: CARDINAL] =
BEGIN
op: OpBlock ← [
handle, writeDeletedSector, GetDA[address], GetBuffer[buffer],
incrementDataPtr, count];
[status, countDone] ← DoClientRequest[@op];
END;

-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- ENTRY then INTERNAL PROCS
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

DoRequest: PUBLIC ENTRY PROC [pOp: POINTER TO READONLY OpBlock]
RETURNS [
status: FloppyChannel.Status,
countDone: PilotFloppyFormat.FloppyPageCount] =
BEGIN
-- KLUDGE!!! Fix up the interface and/or DoRequestInternal to agree on
-- whether or not pOp is READONLY or not!!!!!
-- note: the reason for writing in the block is to pass back the
-- disk address returned by the head, which DoClientRequest
-- needs and does not want to caculate. Everything would work fine
-- if DoRequestInternal additionally returned this
-- guy vs. clobbering the OpBlock.
op: OpBlock ← pOp↑;
[status, countDone] ← DoRequestInternal[@op];
END;

-- should I be looking at the drive Handle???
DoClientRequest: PRIVATE ENTRY PROC [o: POINTER TO OpBlock]
RETURNS [status: FloppyChannel.Status, countDone: CARDINAL] =
BEGIN
Pages: PROC [c: CARDINAL] RETURNS [CARDINAL] = INLINE
{RETURN[(c+Environment.wordsPerPage-1)/Environment.wordsPerPage]};
pPinned: LONG POINTER
= SimpleSpace.LongPointer[clientDataSpace];
countLeft: CARDINAL ← o.count;
sectorLength: CARDINAL
= GetContext[o.device].context.sectorLength;
itemLength: CARDINAL
= IF o.function=readID THEN idLength ELSE sectorLength;
maxSectorsPerRun: CARDINAL = (maxWordsPerTrack / itemLength);
sectorsPerTrack: CARDINAL = GetDrive[o.device].sectorsPerTrack;
pUser: LONG POINTER ← o.buffer.address;
lengthUser: CARDINAL ← o.buffer.length;

IF ~IsChannelAccess[o.device] THEN GOTO notChannel;
countDone ← 0;
IF countLeft = 0 THEN {o.function ← nop; countLeft ← 1};
IF pUser=NIL THEN {pUser ← pPinned; o.incrementDataPtr ← FALSE};
SimpleSpace.Map[
clientDataSpace, Space.defaultWindow, TRUE,
-- Pin with a minimal amount of resident memory
-- BUG, changed before 6.0d
-- was MIN[trackPages, o.count], but sectors can be big
IF o.function=nop THEN 1
ELSE Pages[
itemLength
* (IF o.incrementDataPtr THEN
MIN[maxSectorsPerRun, countLeft] ELSE 1)]];
WHILE countLeft > 0 DO
countThisPass: CARDINAL ← MIN[countLeft, maxSectorsPerRun];
length: CARDINAL ← countThisPass*itemLength;
verify: OpBlock ← NULL;

IF ~o.incrementDataPtr THEN {
countThisPass ← countLeft; length ← itemLength};
SELECT o.function FROM
writeDeletedSector, writeSector =>
BEGIN
IF o.incrementDataPtr
AND o.address.sector IN [1..sectorsPerTrack] THEN
BEGIN -- write runs to be track aligned for verify
countThisPass ← MIN[
countThisPass, 1+sectorsPerTrack-o.address.sector];
length ← countThisPass*itemLength
END;
Inline.LongCOPY[from: pUser, nwords: length, to: pPinned];
END
ENDCASE;
o.count ← countThisPass;
o.buffer ← [address: pPinned, length: length];
verify ← o↑;
[status, countThisPass] ← DoRequestInternal[o];
length ←
(IF o.incrementDataPtr OR countThisPass=0
THEN countThisPass ELSE 1)*itemLength;
SELECT o.function FROM
writeDeletedSector, writeSector =>
IF countThisPass>0 THEN
BEGIN
verifyStatus: Status; verifyCount: CARDINAL;
verify.function ← readSector; verify.count ← countThisPass;
[verifyStatus, verifyCount] ← DoRequestInternal[@verify];
IF verifyCount < countThisPass THEN status ← verifyStatus;
END;
readSector, readID =>
Inline.LongCOPY[from: pPinned, nwords: length, to: pUser]
ENDCASE;
countLeft ← countLeft - countThisPass;
countDone ← countDone + countThisPass;
-- o.address was updated by DoRequestInternal
IF o.incrementDataPtr THEN {
pUser ← pUser + length; lengthUser ← lengthUser - length};
IF status.error THEN EXIT;
ENDLOOP;
SimpleSpace.Unmap[clientDataSpace]; -- free up our resident buffer
IF o.function=nop THEN countDone ← 0;
EXITS notChannel =>
RETURN WITH ERROR PhysicalVolume.Error[invalidHandle];
END;

-- Set physical volume(diskette)-specific information when context changes
-- this is cyls, heads, sectors and (pilot only) npages
-- called from SetContext
InitDisk: ENTRY PROC [
drive: DiskChannelBackend.DriveHandle, context: FloppyChannel.Context] =
BEGIN
sectorTable: ARRAY [0..5] OF CARDINAL = [4, 8, 15, 26, 36, 2];
i: CARDINAL ← SELECT context.sectorLength FROM
512 => 1, -- 1024-byte sectors --
256 => 2, -- 512 (Pilot volume) --
128 => 3, -- 256 --
64 => 4, -- 128 --
ENDCASE => 5; -- Anything else must be Troy --
IF context.density = single THEN i ← i - 1;
drive.sectorsPerTrack ← sectorTable[i];
[numberOfHeads: drive.movingHeads, numberOfCylinders: drive.cylinders] ←
SA800Face.GetDeviceAttributes[drive.driveID.handle].attributes;
drive.nPages ←
drive.movingHeads * drive.sectorsPerTrack
*(drive.cylinders
- PilotFloppyFormat.logicalTrack0
- PilotFloppyFormat.firstRealCylinder);
END;

Recal: PUBLIC ENTRY PROC [handle: Handle]
RETURNS [status: FloppyChannel.Status] =
{RETURN[Recover[handle, recalibrate]]};

Recalibrate: PUBLIC ENTRY PROC [handle: Handle] RETURNS [status: Status] =
BEGIN
op: OpBlock ← [handle, recalibrate];
IF IsNonPilotAccess[handle] THEN RETURN[DoRequestInternal[@op].status]
ELSE RETURN WITH ERROR PhysicalVolume.Error[invalidHandle];
END;

-- as a side effect, this guy copies the final "head" next disk address
-- back to pOp.address
DoRequestInternal: INTERNAL PROC [pOp: POINTER TO OpBlock]
RETURNS [status: FloppyChannel.Status, countDone: CARDINAL] =
BEGIN
retryCount: CARDINAL ← 0;
recordNotFoundCount: CARDINAL ← 0;

FatalError: INTERNAL PROC RETURNS [fatal: BOOLEAN] =
INLINE BEGIN
oneThird: CARDINAL = maxRetries/3;
recordNotFoundRetrys: CARDINAL = 3;

IF doRequestTask.address # pOp.address THEN {
retryCount ← 0; recordNotFoundCount ← 0;
pOp.address ← doRequestTask.address};
SELECT TRUE FROM
status.recordNotFound =>
BEGIN
-- always recover/recalibrate after recordNotFound
IF Recover[
pOp.device,
IF recordNotFoundCount = 0 THEN recovery ELSE recalibrate
].status.error THEN RETURN[TRUE];
IF (recordNotFoundCount ← recordNotFoundCount+1)
> recordNotFoundRetrys THEN RETURN[TRUE];
IF FilterStatus[
SA800Face.Initiate[doRequestTask].status].error THEN
RETURN[TRUE];
RETURN[FALSE];
END;
status.wrongSizeBuffer, status.crcError, status.hardwareError =>
BEGIN
-- throw in a free recalibrate at the end of a failing operation
IF (((retryCount ← retryCount + 1) MOD oneThird) = 0)
OR (retryCount > maxRetries) THEN
IF Recover[
pOp.device,
IF (retryCount = oneThird) THEN recovery ELSE recalibrate
].status.error THEN RETURN[TRUE];
IF retryCount > maxRetries THEN RETURN[TRUE];
IF FilterStatus[
SA800Face.Initiate[doRequestTask].status].error THEN
RETURN[TRUE];
RETURN[FALSE];
END;
ENDCASE => --fatal error--RETURN[TRUE];
END;

doRequestTask↑ ← [
device: GetDeviceHandle[pOp.device], function: pOp.function,
incrementDataPointer: pOp.incrementDataPtr, address: pOp.address,
buffer: pOp.buffer, count: pOp.count];
status ← FilterStatus[SA800Face.Initiate[doRequestTask].status];
IF status.inProgress THEN
DO -- wait for completion
status ← FilterStatus[SA800Face.Poll[doRequestTask]];
SELECT TRUE FROM
status.inProgress => WAIT nakedNotify;
status.goodCompletion => EXIT;
--error--ENDCASE => IF FatalError[] THEN EXIT;
ENDLOOP;
countDone ← pOp.count - doRequestTask.count;
pOp.address ← doRequestTask.address;
IF status.diskChanged THEN NoticeDiskChanged[pOp.device];
END;

IsChannelAccess: INTERNAL PROC [handle: Handle] RETURNS [BOOLEAN] =
INLINE BEGIN
drive: DiskChannelBackend.DriveHandle = GetDrive[handle];
RETURN[
(drive.changeCount=handle.changeCount) AND (drive.state = channel)];
END;

IsNonPilotAccess: INTERNAL PROC [handle: Handle] RETURNS [BOOLEAN] =
INLINE BEGIN
drive: DiskChannelBackend.DriveHandle = GetDrive[handle];
RETURN[(drive.changeCount=handle.changeCount) AND (drive.state # pilot)];
END;

NoticeDiskChanged: INTERNAL PROC [h: Handle] =
BEGIN
drive: DiskChannelBackend.DriveHandle = GetDrive[h];
drive.changeCount ← drive.changeCount + 1;
SA800Face.DiskChangeClear[drive.driveID.handle];
END;

-- Perform recover/recalibrate outside normal request path. Polls until
-- complete; error recovery = simple retry.
Recover: INTERNAL PROC [
handle: Handle, func: SA800Face.Function[recalibrate..recovery]]
RETURNS [status: FloppyChannel.Status] =
BEGIN
retryLimit: CARDINAL = 10;
recalRecoverTask↑ ← [
device: GetDeviceHandle[handle], function: func,
incrementDataPointer: FALSE, address: [0, 0, 0], buffer: [NIL, 0], count: 0];
THROUGH [0..retryLimit) DO
status ← FilterStatus[SA800Face.Initiate[recalRecoverTask].status];
IF status.error THEN EXIT;
DO
status ← FilterStatus[SA800Face.Poll[recalRecoverTask]];
IF ~status.inProgress THEN EXIT;
WAIT nakedNotify;
ENDLOOP;
IF status.goodCompletion THEN RETURN;
ENDLOOP;
IF status.diskChanged THEN NoticeDiskChanged[handle]; -- here only on error
END;

-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- INITIALIZATION PROCS
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

InitializeCondition: PROC =
BEGIN
[cv: nakedNotify, mask: wakeupMask] ← ProcessInternal.AllocateNakedCondition[];
Process.SetTimeout[condition: nakedNotify, ticks: Process.MsecToTicks[1000]];
END;

-- should check for errors in allocating heap
InitializeDriverAlloc: PROC =
INLINE BEGIN
doRequestTask ← @Environment.first64K[
ResidentHeap.MakeNode[n: SA800Face.operationBlockLength, alignment: a4].node];
recalRecoverTask ← @Environment.first64K[
ResidentHeap.MakeNode[n: SA800Face.operationBlockLength, alignment: a4].node];
clientDataSpace ← SimpleSpace.Create[trackPages, hyperspace];
END;

RegisterDrives: PROC = -- Create and register drive objects.
BEGIN
device: SA800Face.DeviceHandle ← SA800Face.nullDeviceHandle;
gsp: Environment.Base RELATIVE POINTER = ResidentHeap.MakeNode[
n: SA800Face.initialAllocationLength, alignment: a4].node;
SA800Face.Initialize[
notify: wakeupMask, initialAllocation: @Environment.first64K[gsp]];
WHILE nDrives < maxDrives DO
d: DiskDriverShared.ScheduleHandle = @drives[nDrives];
IF (device ← SA800Face.GetNextDevice[device]) = SA800Face.nullDeviceHandle
THEN EXIT;
SA800Face.DiskChangeClear[device];
d.drive.driveID ← [type: DeviceTypes.sa800, handle: device];
d.drive.driverStorage ← NIL;
d.drive.state ← inactive;
d.drive.changeCount ← 0;
d.drive.requestIO ← NIL;
d.drive.getPageAddress ← NIL;
d.drive.getPageNumber ← NIL;
d.drive.getStatus ← GetStatus;
d.drive.changeState ← BasicChangeState;
d.currentSector ← 0;
d.currentCylinder ← 0;
d.first ← NIL;
DiskChannelBackend.RegisterDrive[@d.drive];
-- setContext calls InitDisk which sets sectors, movingHeads, cyl, npages
-- attr ← SA800Face.GetDeviceAttributes[device];
-- d.drive.cylinders ← attr.numberOfCylinders;
-- d.drive.movingHeads ← attr.numberOfHeads;
-- d.drive.sectorsPerTrack ← 0;
-- d.drive.nPages ← 0;
[] ← SetContext[[@d.drive, d.drive.changeCount], PilotFloppyFormat.pilotContext];
nDrives ← nDrives + 1;
ENDLOOP;
IF nDrives # 0 THEN
BEGIN
SA800Face.InitializeCleanup[device];
InitializeDriverAlloc[];
END;
END;

Start: PUBLIC PROC = {RemainingDrivers.Start[]};

InitializeCondition[];
RegisterDrives[];

END.

May 28, 1980 4:26 PM
JoseCreate file
August 28, 1980 12:20 PM
McJonesConvert to ProcessInternal.AllocateNakedCondition
September 18, 1980 4:23 PM
JoseSeparate startup- and new diskette-initialization; pin code and allocate driver memory only if drive present, incorporate new SA800Face
October 10, 1980 4:21 PM
JoseRip out runs of pages code, substitute head runs, add DoClientRequest.
October 21, 1980 2:32 PM
JoseReturn earliest failure status on Write(Deleted)Sectors, catch initiate error in DoRequest.
December 17, 1980 4:34 PM
LuniewskiDelete code to explicitly pin this code - let the packager do it. Bug fix to permit runs of pages to work from client side.
January 26, 1981 2:18 PM
LuniewskiMerge Version of Mokelumne maintenance release into this version. Upgrade to use SimpleSpace’s and not Space’s for the resident buffer.
February 5, 1981 6:49 PM
LuniewskiMerge Temporary fix to DoRequest to copy its argument until the "Kludge" comment there is resolved.