-- SA800HeadDLion.mesa -- last modified August 26, 1982 4:43 pm by Taft -- Things to do: -- 1. One and only one drive is handled by this Head. The Dandelion will -- have exactly one SA80x or SA85x type drive and exactly one floppy disk -- controller. -- 2. The operation.buffer.length is ignored. -- It should be removed from the face. DIRECTORY DeviceCleanup USING [Await, Item, Reason], DLionInputOutput USING [IOPage], Environment USING [Base], HeadStartChain USING [Start], Inline USING [BITAND, BITOR, DIVMOD, LongCOPY, LowHalf], SA800Face; SA800HeadDLion: PROGRAM IMPORTS DLionInputOutput, DeviceCleanup, RemainingHeads: HeadStartChain, Inline EXPORTS HeadStartChain, SA800Face SHARES SA800Face = BEGIN OPEN SA800Face; -- *********************************************************************** -- C S B -- The Dandelion has at most one Shugart SA80x or SA85x -- floppy disk. The CSB resides in a resident page of memory (the IOPage). -- Only state.next and .needReset are known by IOP code. -- *********************************************************************** CSB: TYPE = MACHINE DEPENDENT RECORD [ transferMask(0): WORD, -- naked notify mask. next(1): IOPBlockPtr, -- IOCB executed when Output is done. unused0(2): UNSPECIFIED, needReset(3): BOOLEAN, -- TRUE => reset required. tail(4:0..31): IOCBPtr]; -- Last IOCB in queue. Used only by Initiate. --unused1(6: 0..31): UNSPECIFIED]; csb: LONG POINTER TO CSB = LOOPHOLE[DLionInputOutput.IOPage + 111B]; -- 49'X -- *********************************************************************** -- I O C B (and IOP command blocks) -- *********************************************************************** IOCBPtr: TYPE = LONG POINTER TO IOCB; IOPBlockPtr: TYPE = Environment.Base RELATIVE POINTER TO IOPBlock; nilIOP: IOPBlockPtr = LOOPHOLE[0]; IOCB: TYPE = MACHINE DEPENDENT RECORD [ operation(0): Operation, -- set by head client iopBlk(8): IOPBlock, -- read/written by IOCB started(24:0..0): BOOLEAN, -- => IOCB has been submitted to IOP thisRun(24:1..15): [0..77777B], -- pages/tracks this trip abortStatus(25): Status, -- saved status. An operation may be aborted -- after it passes Initiate (new Disk Change while it is in the queue). This -- saves the status so the user finds out why it failed. nextIOCB(26): IOCBPtr]; -- points to next IOCB in chain IOPBlock: TYPE = MACHINE DEPENDENT RECORD [ bufferAddr(0:0..31): LONG POINTER, -- address disk buffer (unused if format) unused(2): UNSPECIFIED _ 0, sectorLength(3): CARDINAL, -- sector length(bytes)(unused if format) sectorFormat(4): FormatWord, -- sector Format diskAddress(5:0..31): DiskAddress, -- cylinder, head and sector sectorCount(7): CARDINAL, -- sectors to transfer <= sectorPerTrack -- (if format, track count) result(8): IOPResult, -- status posted by IOP samePage(9:0..0): BOOLEAN, -- TRUE => use same memory page for -- all sectors (unused if format) command(9:1..15): Command, -- IOP command word subCommand(10): SubCommand, -- IOP sub-command word (IOP=esc Only) formatInfo(11:0..31): FormattingInfo, -- used only in format reserved(13:0..47): ARRAY [0..3) OF UNSPECIFIED]; FormattingInfo: TYPE = MACHINE DEPENDENT RECORD [ sectorLengthDiv4(0:0..7): CARDINAL [0..400B), -- sector length -- ((bytes)/4) MOD 256 encodedSectorLength(0:8..15): EncodedSectorLength, -- encoded sector length sectorsPerTrack(1:0..7): CARDINAL [0..400B), gap3FillCharsCount(1:8..15): CARDINAL [0..400B)]; -- interrecord gap count -- order of these guys is significant to Head code as well as IOP Command: TYPE = MACHINE DEPENDENT { nopC(0), readSectorC, writeSectorC, writeDeletedSectorC, readIDC, formatTrackC, recalibrateC, initializeC, escapeC, (77777B)}; SubCommand: TYPE = MACHINE DEPENDENT {nopSC(0), diskChangeClearSC, (177777B)}; EncodedSectorLength: TYPE = MACHINE DEPENDENT { b128(0), b256(1), b512(2), b1024(3), bUnknown}; FormatWord: TYPE = MACHINE DEPENDENT RECORD [ troyOrIBM(0:0..11): MACHINE DEPENDENT {IBM(0), Troy(1), (7777B)}, density(0:12..15): MACHINE DEPENDENT {single(0), double(10B), (17B)}]; IOPResult: TYPE = MACHINE DEPENDENT RECORD [ doorOpened(0:0..0): BOOLEAN, -- TRUE => operator opened disk door -- latched until -- DiskChangeClear[] AND ~notReady unused1(0:1..1): BOOLEAN _ FALSE, twoSided(0:2..2): BOOLEAN, -- true => two sided diskette installed diskID(0:3..3): MACHINE DEPENDENT {SA800(0), SA850(1)}, error(0:4..4): BOOLEAN, -- IOP detected an error in the IOCB unused2(0:5..5): BOOLEAN _ FALSE, recalibrateError(0:6..6): BOOLEAN, -- IOP had error during recalibrate op dataLost(0:7..7): BOOLEAN, -- data transfer not completed notReady(0:8..8): BOOLEAN, -- drive has door open writeProtect(0:9..9): BOOLEAN, -- write-protected diskette now in drive deletedData(0:10..10): BOOLEAN, -- read a deleted data sector recordNotFound(0:11..11): BOOLEAN, -- some sector in transfer not found crcError(0:12..12): BOOLEAN, -- CRC error on read op track00(0:13..13): BOOLEAN, -- read heads left on cylinder 0 unused3(0:14..14): BOOLEAN _ FALSE, busy(0:15..15): BOOLEAN]; -- => chip busy, should always be FALSE -- *********************************************************************** -- C O N S T A N T S -- (canned stati can be found at the end of the module (so they won't lexically -- pollute code readers) -- *********************************************************************** commandMatrix: ARRAY Function OF Command = [ -- face => IOP command nopC, recalibrateC, escapeC, readSectorC, writeSectorC, writeDeletedSectorC, readIDC, formatTrackC]; nullIOCB: IOCB = [ operation: [ device: LOOPHOLE[0], function: nop, incrementDataPointer: FALSE, count: 0, address: [cylinder: 0, head: 0, sector: 0], buffer: [address: NIL, length: 0]], iopBlk: nullIOPBlk, started: FALSE, thisRun: 0, abortStatus: initialStatus, nextIOCB: NIL]; nullIOPBlk: IOPBlock =[ bufferAddr: NIL, unused: 0, sectorLength: 0, sectorFormat: [troyOrIBM: IBM, density: single], diskAddress: [cylinder: 0, head: 0, sector: 0], sectorCount: 0, result: nullIOPResult, samePage: FALSE, command: nopC, subCommand: nopSC, formatInfo: [0, b128, 0, 0], reserved: [0, 0, 0]]; nullIOPResult: IOPResult = [ doorOpened: FALSE, twoSided: FALSE, diskID: SA800, error: FALSE, recalibrateError: FALSE, dataLost: FALSE, notReady: FALSE, writeProtect: FALSE, deletedData: FALSE, recordNotFound: FALSE, crcError: FALSE, track00: FALSE, busy: FALSE]; nullDeviceHandle: PUBLIC DeviceHandle _ LOOPHOLE[0]; diskDeviceHandle: DeviceHandle = LOOPHOLE[1]; internalDeviceHandle: DeviceHandle = LOOPHOLE[2]; -- for internally generated IOCBs. trackParameterLength: CARDINAL = 0; -- space needed for FormatTrack parameters idLength: CARDINAL = 3; -- length of field ID in words numOfCylinders: CARDINAL = 77; -- number of cylinders on SA800 and SA850 disks initialAllocationLength: PUBLIC CARDINAL _ SIZE[IOCB]; operationBlockLength: PUBLIC CARDINAL _ SIZE[IOCB]; -- *********************************************************************** -- G L O B A L V A R I A B L E S -- *********************************************************************** initializationIOCB: IOCBPtr; state: RECORD [ user: Context --user accessable state _ [protect: FALSE, format: IBM, density: double, sectorLength: 256], status: Status _ diskChange, --most currently processed status result: IOPResult _ nullIOPResult, --most currently processed IOP result sectorsPerTrack: CARDINAL _ 15, --function[sectorLength, density] encodedSectorLength: EncodedSectorLength _ b512];--function[sectorLength] -- do not submit an IOCB if there is nothing to do. This also eliminates -- the possiblilty of interrupting an existing IOCB. Since the occurence of -- a new diskChange error causes the IOCB queue to be cleared, the IOCB about -- to be submitted will arrive at the Head of the queue so it may be Polled -- immediately. DiskChangeClear: PUBLIC PROCEDURE [DeviceHandle] = BEGIN ready: BOOLEAN; IF ~state.status.diskChange THEN RETURN; state.status.diskChange _ FALSE; ready _ ~Immediate[escapeC, diskChangeClearSC, nop].notReady; -- if there is a diskette inserted (notReady=FALSE) then recalibrate -- so the FDC and IOP are synchronized. When the heads are recalibrated they -- they are loaded => door lock engaged. If there were no disk, the FDC would -- never see index pulses and never ever unload the heads, leaving the door locked -- which is mildly embarrassing. This may also be the first recalibrate if there was no -- disk loaded when the system was initialized. IF ready THEN [] _ Immediate[recalibrateC, nopSC, recalibrate] END; -- Assumes an "Initialize" operation has been performed so "state.status" -- holds valid state bits GetDeviceAttributes: PUBLIC PROC [DeviceHandle] RETURNS [Attributes] = BEGIN RETURN[[ deviceType: IF state.result.diskID=SA800 THEN SA800 ELSE SA850, numberOfHeads: IF ~state.status.twoSided THEN 1 ELSE 2, numberOfCylinders: numOfCylinders, trackLength: trackParameterLength]]; END; -- ASSUMES EXACTLY ONE FLOPPY DRIVE IS CONNECTED TO THE DANDELION GetNextDevice: PUBLIC PROC [device: DeviceHandle] RETURNS [DeviceHandle] = { RETURN[IF device = nullDeviceHandle THEN diskDeviceHandle ELSE nullDeviceHandle]}; GetContext: PUBLIC PROC [DeviceHandle] RETURNS [Context] = {RETURN[state.user]}; -- This code will only handles a single controller/single device configuration. Initialize: PUBLIC PROC [notify: WORD, initialAllocation: LONG POINTER] = BEGIN csb^ _ [ transferMask: notify, next: nilIOP, unused0: NULL, needReset: FALSE, tail: NIL]; initializationIOCB _ initialAllocation; initializationIOCB^ _ nullIOCB; initializationIOCB.operation.device _ internalDeviceHandle; [] _ Immediate[initializeC, nopSC, nop]; state.status.diskChange _ TRUE; END; InitializeCleanup: PUBLIC PROC [DeviceHandle] = BEGIN item: DeviceCleanup.Item; sCsb: CSB; DO reason: DeviceCleanup.Reason = DeviceCleanup.Await[@item]; SELECT reason FROM turnOff, kill => {WHILE csb.next#nilIOP DO ENDLOOP; sCsb _ csb^}; turnOn => 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. The variable maxSecondsThisOperation may be used by the driver to timeout and rescue the operation. It is an upper bound on the number of seconds the operation will take after previous operations have been completed. Initiate: PUBLIC PROC [o: OperationPtr] RETURNS [status: Status, maxSecondsThisOperation: CARDINAL] = BEGIN OPEN iocb: LOOPHOLE[o, IOCBPtr]; maxSecondsThisOperation _ 0; IF o.device # diskDeviceHandle AND o.device # internalDeviceHandle THEN RETURN[Inline.BITOR[statusError, Inline.BITAND[infoMask, state.status]], 0]; IF o.device = diskDeviceHandle THEN iocb.iopBlk _ nullIOPBlk; iocb.nextIOCB _ NIL; iocb.started _ FALSE; iocb.abortStatus _ inProgress; IF (status _ CheckIOCB[@iocb]).inProgress THEN BEGIN IF csb.next = nilIOP THEN RunIOCB[@iocb] ELSE csb.tail.nextIOCB _ @iocb; csb.tail _ @iocb; maxSecondsThisOperation _ SELECT o.function FROM readSector, writeSector, writeDeletedSector, readID => 20 + (o.count + iocb.iopBlk.sectorCount)/5, formatTrack => 20 + (o.count + iocb.iopBlk.sectorCount), nop, recalibrate => 10, ENDCASE => 0; END ELSE iocb.started _ TRUE; -- return (error) status if somebody polls this guy END; Poll: PUBLIC PROC [o: OperationPtr] RETURNS [status: Status] = BEGIN OPEN iocb: LOOPHOLE[o, IOCBPtr]; IF ~iocb.abortStatus.inProgress THEN RETURN[iocb.abortStatus]; IF csb.next#nilIOP OR ~iocb.started THEN RETURN[Inline.BITOR[inProgress, Inline.BITAND[state.status, infoMask]]]; IF (status _ GetStatus[@iocb]).inProgress THEN RunIOCB[@iocb] ELSE FOR newIOCB: IOCBPtr _ iocb.nextIOCB, newIOCB.nextIOCB WHILE newIOCB # NIL DO newIOCB.started _ TRUE; IF CheckIOCB[newIOCB].inProgress THEN {RunIOCB[newIOCB]; EXIT}; ENDLOOP; END; Reset: PUBLIC PROC [DeviceHandle] = {csb.next _ nilIOP; csb.needReset _ TRUE; csb.tail _ NIL}; -- This routine requires that the device be idle (i.e., the IOCB chain to be NIL) -- as well as all other context to be correct before anything is changed in the -- head's context. SetContext: PUBLIC PROC [device: DeviceHandle, context: Context] RETURNS [BOOLEAN] = BEGIN Density: TYPE = {single, double}; sectorsPerTrack: ARRAY EncodedSectorLength[b128..b1024] OF ARRAY Density OF CARDINAL = [ -- Single density Double Density -- 64w-- [26, 36], -- 128w-- [15, 26], -- 256w-- [ 8, 15], -- 512w-- [ 4, 8]]; byteIndex: EncodedSectorLength = SELECT context.sectorLength FROM 64 => b128, 128 => b256, 256 => b512, 512 => b1024, ENDCASE => bUnknown; IF (byteIndex=bUnknown) OR (csb.next#nilIOP) OR (context.format#IBM) THEN RETURN[FALSE]; state.user _ context; state.sectorsPerTrack _ sectorsPerTrack[byteIndex][IF context.density=single THEN single ELSE double]; state.encodedSectorLength _ byteIndex; RETURN[TRUE]; END; -- exported to HeadStartChain Start: PUBLIC PROC = {RemainingHeads.Start[]}; --************************** PRIVATE PROCEDURES ************************** BuildIOCB: PROCEDURE [iocb: IOCBPtr] = BEGIN OPEN o: iocb.operation; b: LONG POINTER TO IOPBlock = @iocb.iopBlk; runLength: CARDINAL; IF o.device = internalDeviceHandle THEN RETURN; b.result _ nullIOPResult; IF state.status.notReady THEN {b.command _ nopC; RETURN}; b.command _ commandMatrix[o.function]; SELECT o.function FROM nop, recalibrate => NULL; -- allSetUp recovery => b.command _ nopC; -- for now, do nothing readSector, writeSector, writeDeletedSector, readID => BEGIN OPEN a: o.address; runLength _ MIN[o.count, state.sectorsPerTrack-(a.sector-1)]; Touch[ addr: o.buffer.address, count: (IF o.function=readID THEN idLength ELSE state.user.sectorLength) * (IF o.incrementDataPointer THEN runLength ELSE 1), dirty: (o.function=readSector OR o.function=readID)]; b.samePage _ ~o.incrementDataPointer; b.bufferAddr _ o.buffer.address; b.sectorLength _ state.user.sectorLength; b.sectorFormat _ [ troyOrIBM: IF state.user.format = Troy THEN Troy ELSE IBM, density: IF state.user.density = double THEN double ELSE single]; b.diskAddress _ o.address; -- set first disk address b.sectorCount _ runLength; -- set number of sectors to transfer iocb.thisRun _ runLength; -- and set run length for getStatus fixup. END; formatTrack => BEGIN OPEN a: o.address; Density: TYPE = {single, double}; gap3: ARRAY EncodedSectorLength [b128..b1024] OF ARRAY Density OF CARDINAL -- [512w][single] and [64w][double] are picked out of the air = [ -- Single density Double Density -- 64w-- [27, 26], -- 128w-- [42, 54], -- 256w-- [58, 84], -- 512w-- [75, 116]]; b.formatInfo _ [ sectorLengthDiv4: state.user.sectorLength/2, -- (x * 2 bytes/wd)/4 encodedSectorLength: state.encodedSectorLength, sectorsPerTrack: state.sectorsPerTrack, gap3FillCharsCount: gap3[state.encodedSectorLength] [IF state.user.density=single THEN single ELSE double]]; runLength _ MIN[ o.count, IF state.status.twoSided THEN 1 ELSE (numOfCylinders - a.cylinder)]; b.sectorFormat _ [ troyOrIBM: IF state.user.format = Troy THEN Troy ELSE IBM, density: IF state.user.density = double THEN double ELSE single]; b.diskAddress _ o.address; b.sectorCount _ runLength; iocb.thisRun _ runLength; END ENDCASE; END; -- a procedure to check an IOCB before it is submitted to the IOP. It attempts -- to screen out IOCBs which shouldn't even be attempted CheckIOCB: PROCEDURE [iocb: IOCBPtr] RETURNS [status: Status] = BEGIN OPEN a: iocb.operation.address, o: iocb.operation, Inline; IsWriteOp: PROC [f: Function] RETURNS [BOOLEAN] = INLINE {RETURN[f=writeSector OR f=writeDeletedSector OR f=formatTrack]}; IF ~iocb.abortStatus.inProgress THEN RETURN[iocb.abortStatus]; status _ inProgress; IF o.function = formatTrack THEN a.sector _ 1; SELECT TRUE FROM o.device = internalDeviceHandle => NULL; state.status.diskChange => status _ diskChange; (IsWriteOp[o.function] AND (state.user.protect OR state.status.writeProtect)) => status _ writeProtect; (IsTransferOp[o.function] AND (a.cylinder >= numOfCylinders OR a.head > (IF state.status.twoSided THEN 1 ELSE 0) OR a.sector NOT IN [1..state.sectorsPerTrack] OR o.count = 0)) => status _ recordNotFound; ENDCASE => NULL; iocb.abortStatus _ status _ BITOR[status, BITAND[infoMask, state.status]]; END; -- a procedure to produce the status of an IOCB after it has been completed by the IOP. -- In addition to deciding if the stage just performed had any errors, this procedure -- decides if there are any more stages to be performed. NOTE: there is no reason to -- detect abortStatus.inProgress=FALSE since this routine is only called from Poll -- which will detect this condition before trying to call GetStatus. GetStatus: PROCEDURE [iocb: IOCBPtr] RETURNS [status: Status] = BEGIN OPEN b: iocb.iopBlk, o: iocb.operation; command: Command = b.command; result: IOPResult _ b.result; -- done merely for better code pResult: POINTER TO READONLY IOPResult = @result; -- update disk addr and client buffer parameters BEGIN OPEN a: o.address; countDone: CARDINAL _ (iocb.thisRun - b.sectorCount); o.count _ o.count - countDone; SELECT command FROM formatTrackC => IF countDone#0 THEN SELECT TRUE FROM -- if twoSided, countDone can must <= 1 b.result.twoSided AND (a.head = 0) => a.head _ 1; ENDCASE => {a.head _ 0; a.cylinder _ a.cylinder + countDone}; readSectorC, readIDC, writeSectorC, writeDeletedSectorC => BEGIN IF o.incrementDataPointer THEN BEGIN length: CARDINAL = (IF command=readIDC THEN idLength ELSE state.user.sectorLength) * countDone; o.buffer.length _ o.buffer.length - length; o.buffer.address _ o.buffer.address + length; END; [quotient: countDone, remainder: a.sector] _ Inline.DIVMOD[(a.sector-1) + countDone, state.sectorsPerTrack]; a.sector _ a.sector + 1; -- since we break runs at tracks, countDone <= 1 IF countDone#0 THEN SELECT TRUE FROM b.result.twoSided AND (a.head = 0) => a.head _ 1; ENDCASE => {a.head _ 0; a.cylinder _ a.cylinder + countDone}; END; ENDCASE; END; status _ initialStatus; -- _ ALL[FALSE]; status.diskChange _ state.status.diskChange OR (state.status.notReady AND ~pResult.notReady) OR (~state.status.notReady AND (pResult.notReady OR pResult.doorOpened)); status.twoSided _ pResult.twoSided; -- status.na3 _ FALSE; -- (_ TRUE) SA800 since Mokelumne pilot thinks this is a hardware error bit status.notReady _ pResult.notReady; status.writeProtect _ state.user.protect OR pResult.writeProtect; -- status.na1 _ FALSE; -- status.index _ FALSE; ++ IOP never sends an index pulse back SELECT command FROM readSectorC => BEGIN status.deletedData _ pResult.deletedData; status.crcError _ pResult.crcError; status.dataLost _ pResult.dataLost; status.recordNotFound _ pResult.recordNotFound OR (o.address.cylinder >= numOfCylinders AND o.count > 0); END; IN [writeSectorC..readIDC] => BEGIN status.crcError _ pResult.crcError; status.dataLost _ pResult.dataLost; status.recordNotFound _ pResult.recordNotFound OR (o.address.cylinder >= numOfCylinders AND o.count > 0); END; formatTrackC => BEGIN status.dataLost _ pResult.dataLost; status.recordNotFound _ pResult.recordNotFound OR (o.address.cylinder >= numOfCylinders AND o.count > 0); END; recalibrateC => BEGIN status.recalibrateError _ pResult.recalibrateError; status.track00 _ pResult.track00; END; initializeC, escapeC => status.track00 _ pResult.track00; ENDCASE; -- set error flag if there was an error or the run proceeds off the end of the disk. status.error _ Inline.BITAND[status, statusError] # 0; status.inProgress _ ~status.error AND IsTransferOp[o.function] AND o.count > 0; status.busy _ status.inProgress OR (iocb.nextIOCB # NIL); state.status _ iocb.abortStatus _ status; END; Immediate: PROCEDURE [ command: Command, subCommand: SubCommand, function: Function] RETURNS [initStatus: Status] = BEGIN initializationIOCB.iopBlk.command _ command; initializationIOCB.iopBlk.subCommand _ subCommand; initializationIOCB.operation.function _ function; initializationIOCB.operation.device _ internalDeviceHandle; initStatus _ Initiate[@initializationIOCB.operation].status; WHILE initStatus.inProgress DO initStatus _ Poll[@initializationIOCB.operation] ENDLOOP END; IsTransferOp: PROC [f: Function] RETURNS [b: BOOLEAN] = --INLINE-- BEGIN b _ SELECT f FROM readSector, readID, writeSector, writeDeletedSector, formatTrack => TRUE, ENDCASE => FALSE; END; RunIOCB: PROC [iocb: IOCBPtr] = BEGIN BuildIOCB[iocb]; iocb.started _ TRUE; IF csb.next=nilIOP THEN csb.next _ LOOPHOLE[Inline.LowHalf[@iocb.iopBlk]]; END; -- change this to use checksum when implemented in microcode Touch: PROC [addr: LONG POINTER, count: CARDINAL, dirty: BOOLEAN] = BEGIN IF dirty THEN BEGIN next: LONG POINTER = addr+1; IF count#0 THEN {addr^ _ 0; Inline.LongCOPY[from:addr, to: next, nwords: count-1]} END ELSE THROUGH [0..count) DO dummy: CARDINAL _ addr^; addr _ addr+1 ENDLOOP; END; -- *********************************************************************** -- Various B O R I N G C O N S T A N T S -- *********************************************************************** recordNotFound: Status = [diskChange: FALSE, na1: FALSE, twoSided: FALSE, na3: FALSE, error: TRUE, inProgress: FALSE, recalibrateError: FALSE, dataLost: FALSE, notReady: FALSE, writeProtect: FALSE, deletedData: FALSE, recordNotFound: TRUE, crcError: FALSE, track00: FALSE, index: FALSE, busy: FALSE]; notReady: Status = [diskChange: FALSE, na1: FALSE, twoSided: FALSE, na3: FALSE, error: TRUE, inProgress: FALSE, recalibrateError: FALSE, dataLost: FALSE, notReady: TRUE, writeProtect: FALSE, deletedData: FALSE, recordNotFound: FALSE, crcError: FALSE, track00: FALSE, index: FALSE, busy: FALSE]; diskChange: Status = [diskChange: TRUE, na1: FALSE, twoSided: FALSE, na3: FALSE, error: TRUE, inProgress: FALSE, recalibrateError: FALSE, dataLost: FALSE, notReady: FALSE, writeProtect: FALSE, deletedData: FALSE, recordNotFound: FALSE, crcError: FALSE, track00: FALSE, index: FALSE, busy: FALSE]; initialStatus: Status = LOOPHOLE[0]; -- ALL[FALSE] inProgress: Status = [diskChange: FALSE, na1: FALSE, twoSided: FALSE, na3: FALSE, error: FALSE, inProgress: TRUE, recalibrateError: FALSE, dataLost: FALSE, notReady: FALSE, writeProtect: FALSE, deletedData: FALSE, recordNotFound: FALSE, crcError: FALSE, track00: FALSE, index: FALSE, busy: TRUE]; writeProtect: Status = [diskChange: FALSE, na1: FALSE, twoSided: FALSE, na3: FALSE, error: TRUE, inProgress: FALSE, recalibrateError: FALSE, dataLost: FALSE, notReady: FALSE, writeProtect: TRUE, deletedData: FALSE, recordNotFound: FALSE, crcError: FALSE, track00: FALSE, index: FALSE, busy: FALSE]; statusError: Status = [diskChange: TRUE, na1: FALSE, twoSided: FALSE, na3: FALSE, error: TRUE, inProgress: FALSE, recalibrateError: TRUE, dataLost: TRUE, notReady: TRUE, writeProtect: FALSE, deletedData: FALSE, recordNotFound: TRUE, crcError: TRUE, track00: FALSE, index: FALSE, busy: FALSE]; infoMask: Status = [diskChange: FALSE, na1: FALSE, twoSided: TRUE, na3: FALSE, error: FALSE, inProgress: FALSE, recalibrateError: FALSE, dataLost: FALSE, notReady: FALSE, writeProtect: TRUE, deletedData: FALSE, recordNotFound: FALSE, crcError: FALSE, track00: TRUE, index: FALSE, busy: FALSE]; END..... LOG Time: September 26, 1980 3:30 PM, by DDavies, start coding. Time: October 21, 1980 2:25 PM, add mapping of all pages for virt addresses Time: October 30, 1980 5:17 PM, add mapping of IOCB addresses Time: November 7, 1980 1:00 PM, add byte swapping for IDs read Time: November 12, 1980 11:13 AM, by DDavies, add bulletproofing against polling out of order. Time: December 1, 1980 1:35 PM, by DDavies, remove byte-swapping. Time: December 1, 1980 10:36 PM, by Forrest, run through reformatter. Time: January 22, 1981 11:10 AM, by Gobbel, fix bug that causes head to always die if function=formatTrack. Time: March 23, 1981 7:06 PM, by Forrest, fix bug causing recordNotFound when recalibrate/recover with count=0. (1792)\f8