-- 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 Jose Create file August 28, 1980 12:20 PM McJones Convert to ProcessInternal.AllocateNakedCondition September 18, 1980 4:23 PM Jose Separate startup- and new diskette-initialization; pin code and allocate driver memory only if drive present, incorporate new SA800Face October 10, 1980 4:21 PM Jose Rip out runs of pages code, substitute head runs, add DoClientRequest. October 21, 1980 2:32 PM Jose Return earliest failure status on Write(Deleted)Sectors, catch initiate error in DoRequest. December 17, 1980 4:34 PM Luniewski Delete 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 Luniewski Merge 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 Luniewski Merge Temporary fix to DoRequest to copy its argument until the "Kludge" comment there is resolved. (1792)\f8