-- SA4000HeadDLion.mesa -- Last edited by: Forrest on: February 14, 1981 2:25 PM -- Things to do/dubious features: -- 1. If more than one drive, or if different kinds of Shugarts are used, -- Get attributes (at least) has to be fixed. -- 2. Some of the error flags returned will be different than those returned -- by the D0. For example, the Dandelion controller cannot simultaneously -- read and verify header fields. As a consequence, whenever an error is -- encountered during a header operation, the "wrongSector" status is -- returned. It is also not possible to issue any sort of timeout flag -- since the Head has no clock. -- 3. Runs of Header noops/reads are supported, they are done a page-at-a-time -- on the SA1000; more work probably has to be done here and for rr and -- rrr. -- 4. The calculation of the low part of the IOCB's read address depends upon -- knowledge that IOCB's live in the first 64K, as well as the page number -- being in the rightmost part of the flag word. -- 5. Rename (within mesa) some of the fields within the IOCB. For example, -- the incrementDataPtr bit is the high order bit of the DataPageNumber, -- and should be a seperate boolean rather than 100000B. Similarly, the -- head bits are now or'ed into freezeCmd, but should be seperatly defined. -- 6. Consider calculating the low half of the physical IOCB address once in -- initiate (and stashing it in the IOCB) rather then each time through the -- Next IOCB loop. -- 7. Does deviceCleanup do the right thing. -- 8. what should incrementDataPtr on ?,?,nop do?? -- 9. get rid of spin counters someday. --10. The increment field of the IOCB is the bit 0 of the page number. -- this conflicts with the fat memory boards and with large virtual -- address spaces and should be moved. DIRECTORY DDC, DeviceCleanup USING [Await, Item, Reason], DLionInputOutput USING [GetRealPage, IOPage, Input, Output], Environment USING [Base, first64K, Long, wordsPerPage], HeadStartChain USING [Start], Inline USING [BITAND, BITNOT, BITOR, LongMult, LowHalf], PageMap USING [], -- implicit import from DLion InputOutput PilotDisk USING [Handle, MatchLabels, NextLabel], SA4000Face, SA4000HeadDLionConstants, Utilities USING [PageFromLongPointer]; SA4000HeadDLion: PROGRAM IMPORTS DLionInputOutput, DeviceCleanup, RemainingHeads: HeadStartChain, Inline, PageMap, PilotDisk, Utilities EXPORTS HeadStartChain, SA4000Face SHARES SA4000Face = BEGIN OPEN DDC, SA4000Face, SA4000HeadDLionConstants; -- TYPE DEFINITIONS -- Controller status block, only one per DLion. -- Only state.next is known by microcode. CSB: TYPE = MACHINE DEPENDENT RECORD [ cylinder(0): CARDINAL, next(1): Environment.Base RELATIVE POINTER TO ChannelCommand, tail(2): IOCBshortPtr, -- last iocb. Used by initiate transferMask(3): WORD, -- naked notify mask. needRecalibrate(4:0..15): BOOLEAN]; -- TRUE => recalibrate required. csb: LONG POINTER TO CSB = LOOPHOLE[DLionInputOutput.IOPage+0B]; -- CONSTANTS p: Environment.Base = Environment.first64K; step: WORD = 200B; -- as part of Output causes heads to move directionInBit: WORD = 100B; -- on => heads move inwards (higher # cyls) driveSelect: WORD = 2000B; -- must be on for drive to listen clearSA1000WriteFault: WORD = 0B; -- reset disk to clear SA1000 fault sa1000PagesPerTrack: CARDINAL = 16; sa1000TracksPerCylinder: CARDINAL = 4; --Assumes SA1004 sa1000Cylinders: CARDINAL = 256; clearSA4000WriteFault: WORD = 3000B; -- reset disk to clear SA4000 fault sa4000PagesPerTrack: CARDINAL = 28; sa4000TracksPerCylinder: CARDINAL = 8; --Assumes SA4008 sa4000Cylinders: CARDINAL = 202; diskDeviceHandle: DeviceHandle = LOOPHOLE[1]; --#nulldeviceHandle errorMask: WORD = 77B; -- selects error bits in the device status KCtl: CARDINAL = 3; -- number of disk control register KStatus: CARDINAL = 3; -- number of disk status register startDiskCode: WORD = 2040B; -- control word sent to start the disk -- GLOBAL VARIABLES globalStateSize: PUBLIC CARDINAL _ 0; -- no fixup IOCB needed. nullDeviceHandle: PUBLIC DeviceHandle _ LOOPHOLE[177777B]; operationSize: PUBLIC CARDINAL _ SIZE[IOCB]; driveIsSA1000: BOOLEAN; pagesPerTrack: CARDINAL; sectorsPerCyl: CARDINAL; maxCylinderNum: CARDINAL; -- last cylinder maxHeadNum: CARDINAL; -- last head resetDisk: WORD; -- = clearSA?000WriteFault -- Counters to help track down apparent funnies in head totalErrors: CARDINAL _ 0; -- total errors reported by Poll pollRaceSpin: LONG CARDINAL _ 0; recalSeekSpin: LONG CARDINAL _ 0; -- Not used in current Pilot -- CurrentOperation: PUBLIC PROC [device: DeviceHandle] -- RETURNS [OperationPtr] = { RETURN[LOOPHOLE[@p[csb.next]]] }; -- THIS MUST BE MODIFIED TO HANDLE FIXED HEADS, SINGLE-PLATTER DRIVES AND -- OTHER MODELS OF SHUGART SA100X AND SA400X AND SA410X DRIVES GetDeviceAttributes: PUBLIC PROC [device: DeviceHandle] RETURNS [cylinders, movingHeads, fixedHeads, sectorsPerTrack: CARDINAL] = {RETURN[maxCylinderNum + 1, maxHeadNum + 1, 0, pagesPerTrack]}; -- ASSUMES EXACTLY ONE DRIVE IS CONNECTED TO THE DANDELION GetNextDevice: PUBLIC PROC [device: DeviceHandle] RETURNS [DeviceHandle] = { RETURN[IF device = nullDeviceHandle THEN diskDeviceHandle ELSE nullDeviceHandle]}; Initialize: PUBLIC PROC [t: WORD, globalState: GlobalStatePtr] = BEGIN csb.next _ nil; csb.tail _ nil; csb.transferMask _ t; csb.needRecalibrate _ TRUE; -- don't update cylinder Reset[diskDeviceHandle]; -- reset the disk END; InitializeCleanup: PUBLIC PROC = BEGIN OPEN DeviceCleanup; item: Item; sCsb: CSB; DO reason: Reason = Await[@item]; SELECT reason FROM turnOff, disconnect, kill => {WHILE GetDDCHardwareBits[].firmwareBusy DO ENDLOOP; sCsb _ csb^}; turnOn => -- I'm not sure what the right thing to do here is. -- Always recal seems safe. {sCsb.cylinder_csb.cylinder; sCsb.needRecalibrate_TRUE; 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. Initiate: PUBLIC PROC [o: OperationPtr] = BEGIN OPEN iocb: LOOPHOLE[o, IOCBlongPtr]; iocbShort: IOCBshortPtr = LOOPHOLE[Inline.LowHalf[@iocb]]; sectorsInPresentCyl: CARDINAL; -- between start of run and end of current cylinder --PageCrossCheck[iocbShort]; ++ delete after all debugging complete? BEGIN -- touch (dirty if write) each page; pageAddr: LONG POINTER _ o.dataPtr; pageCount: CARDINAL = IF o.incrementDataPtr THEN o.pageCount ELSE 1; SELECT DataOperation[o.command] FROM read => THROUGH [0..pageCount) DO pageAddr^ _ 0; pageAddr _ pageAddr + Environment.wordsPerPage ENDLOOP; write => THROUGH [0..pageCount) DO d: CARDINAL = pageAddr^; pageAddr _ pageAddr + Environment.wordsPerPage; ENDLOOP; ENDCASE; END; -- Schedule a recalibrate?? IF ~csb.needRecalibrate THEN iocb.iocbState _ startSeek ELSE {iocb.iocbState _ startRecal; csb.needRecalibrate _ FALSE}; sectorsInPresentCyl _ sectorsPerCyl - (o.clientHeader.head*pagesPerTrack+o.clientHeader.sector); iocb.destCylinder _ IF o.pageCount <= sectorsInPresentCyl THEN o.clientHeader.cylinder ELSE o.clientHeader.cylinder + ((o.pageCount - sectorsInPresentCyl - 1)/sectorsPerCyl) + 1; -- Set special value into device status to indicate new IOCB. -- Set SavedError so NextIOCBState won't find initial error. o.deviceStatus.a _ 177777B; iocb.savedError _ inProgress; iocb.nextIOCB _ nil; iocb.label _ o.labelPtr^; iocb.unusedLabel _ 0; -- use cylinder that the disk will be at when we start this operation. [] _ NextIOCBState[ @iocb, IF csb.next = nil THEN csb.cylinder ELSE p[csb.tail].destCylinder]; -- Arrange for execution. IF csb.next = nil THEN {csb.next _ LOOPHOLE[iocbShort+entryOffset]; Output[startDiskCode, @iocb]} ELSE BEGIN -- Disk busy; queue IOCB. A race is possible and is fixed in Poll. p[csb.tail].nextIOCB _ LOOPHOLE[iocbShort + entryOffset]; IF p[csb.tail].iocbState = transferFinish THEN -- microcode should finish so chain new one on p[csb.tail].transferCommand.finishTransfer.nextIOCB _ p[csb.tail].nextIOCB; END; csb.tail _ iocbShort; END; -- This procedure checks the status of an operation and returns the status -- to the client CantGetHere: ERROR = CODE; Poll: PUBLIC PROC [o: OperationPtr] RETURNS [status: Status] = BEGIN OPEN iocb: LOOPHOLE[o, IOCBlongPtr]; originalDeviceStatus: WORD _ 0; IF ~LOOPHOLE[ o.deviceStatus.b _ GetDDCHardwareBits[], DDCHardwareBits].firmwareBusy AND o.deviceStatus.a = 0 THEN status _ hardwareError ELSE IF o.deviceStatus.a = 0 THEN RETURN[inProgress] ELSE BEGIN OPEN Inline; IF BITAND[originalDeviceStatus _ o.deviceStatus.a, errorMask]=0 THEN csb.cylinder _ iocb.presentCylinder; -- update cyl only if no error. status _ NextIOCBState[@iocb, csb.cylinder]; END; SELECT status FROM inProgress => -- Present IOCB not yet complete. If the microcode thinks it is -- stopped, restart it; this will be the case when a disk request has -- to be done in several operations. Fall through and return. IF o.deviceStatus.a#0 THEN ERROR CantGetHere ELSE Output[startDiskCode, @iocb]; goodCompletion => BEGIN -- all the iocb fields have been corrected by NextIOCBState and the -- microcode should have already begun the next IOCB in the chain, if -- any, so set the CSB's next pointer, check to be sure that the -- microcode will in fact do that IOCB and return with this status IF (csb.next _ iocb.nextIOCB) # nil AND ~LOOPHOLE[originalDeviceStatus, DDCStatusBits].firmwareBusy THEN BEGIN -- we lost the race. Microcode should be stopped, but check. WHILE GetDDCHardwareBits[].firmwareBusy DO pollRaceSpin _ pollRaceSpin+1 ENDLOOP; Output[ startDiskCode --, @p[LOOPHOLE[csb.next-entryOffset, IOCBshortPtr]]--]; END; o.labelPtr^ _ iocb.label; -- copy NEXT[lastReadLabel], including boot chain links o.deviceStatus.a _ originalDeviceStatus; -- for debugging purposes (was zeroed) END; ENDCASE => BEGIN -- Trouble. Get things moving again IF GetDDCHardwareBits[].writeFault THEN Output[resetDisk, NIL]; -- for now, stop all processing on error csb.next _ nil; -- IF (csb.next _ iocb.nextIOCB) # nil THEN -- BEGIN -- next: IOCBlongPtr = -- @p[LOOPHOLE[csb.next - entryOffset, IOCBshortPtr]]; ------find next IOCB from chain. ------Recalculate the next IOCB considering the cylinder may be screwed up. ------BE CONSERVATIVE FOR NOW. -- IF TRUE OR next.iocbState = recalSeek THEN next.iocbState _ startRecal -- ELSE next.iocbState _ startSeek; -- next.operation.deviceStatus.a _ 177777B; ++ pretend this is a new IOCB -- next.savedError _ inProgress; -- [] _ NextIOCBState[next, csb.cylinder]; ++ think on it... -- Output[startDiskCode, next]; ++ start it. -- END; totalErrors _ totalErrors + 1; -- copy in NEXT[lastReadLabel]. This includes the boot chain link -- read in the last succussful operation,or initially supplied. -- This operation was done if status=labelCheck, and is not IOCB -- contains the label read from the disk, usefull for debugging IF status # labelCheck THEN o.labelPtr^ _ iocb.label; o.deviceStatus.a _ originalDeviceStatus; -- restore for diagnostics END; END; Recalibrate: PUBLIC PROC [device: DeviceHandle] = {csb.needRecalibrate _ TRUE}; Reset: PUBLIC PROC [device: DeviceHandle] = {Output[resetDisk, NIL]}; Start: PUBLIC PROC = BEGIN IF (driveIsSA1000 _ GetDDCHardwareBits[].driveIsSA1000) THEN BEGIN -- assumes SA1004 in constants defined above pagesPerTrack _ sa1000PagesPerTrack; sectorsPerCyl _ sa1000TracksPerCylinder * sa1000PagesPerTrack; maxCylinderNum _ sa1000Cylinders - 1; maxHeadNum _ sa1000TracksPerCylinder - 1; resetDisk _ clearSA1000WriteFault; END ELSE BEGIN -- assumes SA4008 in constants defined above pagesPerTrack _ sa4000PagesPerTrack; sectorsPerCyl _ sa4000PagesPerTrack * sa4000TracksPerCylinder; maxCylinderNum _ sa4000Cylinders - 1; maxHeadNum _ sa4000TracksPerCylinder - 1; resetDisk _ clearSA4000WriteFault; END; RemainingHeads.Start[] END; -- DLion boards return status with negatvie logic GetDDCHardwareBits: PROC RETURNS [DDCHardwareBits] = INLINE { RETURN[LOOPHOLE[Inline.BITNOT[DLionInputOutput.Input[KStatus]]]]}; -- Prepare the next stage of processing an IOCB. If nothing to do prepare a -- null IOCB. Never submits an IOCB, puts an IOCB on the queue, or starts the -- Disk uCode. Called only by Initiate on on a new request (with -- deviceStatus=-1) or Poll if the microcode has finished with the IOCB -- (deviceStatus=0). ImpossibleProcessingState: ERROR = CODE; NextIOCBState: PROC [iocb: IOCBlongPtr, curCyl: CARDINAL] RETURNS [iocbStatus: Status] = BEGIN OPEN o: iocb.operation; runLength: CARDINAL; -- number of pages to transfer physIOCBAddr: CARDINAL; -- low 16 bits of IOCB physical address IF o.deviceStatus.a = 177777B THEN -- First time for this IOCB. Check for illegal operation. Check header -- read/noop with a run of multiple pages, label write or data write. iocbStatus _ IF (SELECT HeaderOperation[o.command] FROM verify => FALSE, IN [noop..read] => (LabelOperation[o.command] = write OR DataOperation[o.command] = write OR o.pageCount > 1), ENDCASE => --write-- (driveIsSA1000 AND (o.pageCount#pagesPerTrack OR o.clientHeader.sector#0)) ) THEN hardwareError ELSE inProgress ELSE BEGIN -- something was done to IOCB. hwdStatus: DDCStatusBits = LOOPHOLE[o.deviceStatus.a]; -- decoded status bits from the controller hadError: BOOLEAN = (Inline.BITAND[hwdStatus, errorMask] # 0); -- set if last IOCB had an error -- fix up header, label if there no error. IF ~hadError AND iocb.iocbState IN [transfer..labelRead] THEN BEGIN OPEN cH: o.clientHeader, l: iocb.label; -- first fix Headers; if header read, simulate advance of client header. -- THIS WOULD BE WRONG FOR A RUN OF READ HEADERs IF HeaderOperation[o.command] = read THEN IF (cH.sector _ cH.sector + 1) = sectorsPerCyl THEN {cH.head _ cH.head + 1; cH.sector _ 0}; IF cH.head > maxHeadNum THEN {cH.head _ 0; cH.cylinder _ cH.cylinder + 1}; -- fix Labels; if label read, no updating occured IF LabelOperation[o.command] # read THEN BEGIN l.filePageLo _ l.filePageLo - 1; -- UnDo microcode increment PilotDisk.NextLabel[@l]; -- use pilot increment to properly set flags END; END; -- Fix up the dataPtr and pageCount. SectorCount will be non-zero -- if there was an error. The intended count is countThisStage. IF iocb.iocbState IN [transfer..transferFinish] THEN BEGIN runLength _ iocb.countThisStage-iocb.transferParm.sectorCount; o.pageCount _ o.pageCount - runLength; IF o.incrementDataPtr THEN o.dataPtr _ o.dataPtr + Words[runLength]; END; -- There are three main areas here, Recalibration, Seeking and DataTransfer. -- The recalibration algorithm proceeds as follows: -- 1. seek out by the current cylinder number, this should get us directly to -- cylinder zero. -- 2. If the Track00 signal is set, set the StartSeek and try to set the -- new state from there. If not on track 0, step in 16 tracks one at a -- time looking for track 0. This is done in case we overshot. -- 3. If track 00 is found at any step, set StartSeek and start a data -- transfer, if any. If all 16 steps are exhausted, step out -- maxCylinderNum+20 cylinders , one at a time. -- 4. If the Track00 is found at any step, stop and preceed as above. If -- not, post "SeekError" and return. If a Seek operation is needed to put -- the heads over the desired track, The next state _ "DoSeek", the -- distance and direction are computed and control is passed to the third -- section of NextIOCBState to build the Seek IOCB. The destination -- cylinder is also posted in the CSB. -- If a data transfer operation is needed, we first determine whether the -- first sector is to be found by verifying headers or by counting from the -- index mark. It is assumed that if the second method is to be employed -- (the Header Operation = read or noop), exactly one sector will be read and -- there will be no write operations involved. Next the maximum length of -- the transfer is computed. A run of pages may be broken before its natural -- finish for a number of reasons. First, page zero of any file is -- transferred independently of all other pages. This allows special -- treatment of its flags. Runs of pages are broken at 64K page boundries so -- the microcode need never increment two words of page labels. They are -- also broken at cylinder boundries so a seek may be inserted. The -- distance in pages to the closest boundry determines the length of the -- transfer. -- Finally, there may have been an error on the previous transfer. If so, it -- must be decoded. If it was a label check, we do not return to the client. -- Instead, a new IOCB is formulated to read the label that failed. The -- first six words of that label are compared to the first 6 of the -- clientLabel. If these do not match, there was a real error and the IOCB -- must be set to simulate an error on the original label verify (this -- involves setting the former pageCount and dataPtr fields). If the six -- words match, the last section of the label read is substituted into the -- clientLabel and the operation that included the label verify is restarted -- on the offending sector. If any other type of error occured, its identity -- is simply returned. -- Error decode; used to decide the next state. SELECT TRUE FROM -- first set the status for any of the disastrous faults iocb.savedError # inProgress => iocbStatus _ iocb.savedError; hwdStatus.memoryFault => -- this fails if the memory error occured WITHIN the iocb BEGIN IF LabelOperation[o.command] # read THEN iocb.label.filePageLo _ iocb.label.filePageLo - 1; iocbStatus _ hardwareError; END; hwdStatus.driveNotReady => iocbStatus _ notReady; hwdStatus.overrun, hwdStatus.writeFault => iocbStatus _ hardwareError; ENDCASE => iocbStatus _ inProgress; -- lastField is 0=>seek, 1=>header, 2=>label, 3=>data. SELECT hwdStatus.lastField FROM 0 => iocbStatus _ IF hadError THEN hardwareError ELSE inProgress; 1 => SELECT TRUE FROM hwdStatus.verifyError, hwdStatus.CRCError => iocbStatus _ wrongSector; iocbStatus = inProgress => iocbStatus _ hardwareError; -- shouldn't have stopped if no error ENDCASE; 2 => SELECT TRUE FROM hwdStatus.CRCError => iocbStatus _ labelError; -- bad label hwdStatus.verifyError => iocbStatus _ labelCheck; -- CRC ok, verify failed; go try read/label fixup. iocbStatus = inProgress => iocbStatus _ hardwareError; -- shouldn't have stopped if there was no error ENDCASE; 3 => SELECT TRUE FROM hwdStatus.verifyError, hwdStatus.CRCError => iocbStatus _ dataError ENDCASE; ENDCASE; -- If fall through, leave value set in first select END; -- Next State decision Loop -- Now define the next state. Note this may take several iterations. -- For example, The starting state could be "StartSeek" while the -- cylinder specified is the current cylinder. In this case the current -- state is set to "StartTransfer" and the code enters the next -- state selection procedure again. IF iocbStatus=inProgress OR iocbStatus=labelCheck THEN DO SELECT iocb.iocbState FROM startRecal => BEGIN iocb.presentCylinder _ IF curCyl=0 THEN 1 ELSE 0; -- force movement iocb.iocbState _ recalSeek; EXIT; END; recalSeek => BEGIN -- have just done seek to where cylinder 0 should be UNTIL GetDDCHardwareBits[].seekComplete DO --wait for hardware-- recalSeekSpin _ recalSeekSpin+1 ENDLOOP; IF GetDDCHardwareBits[].track00 THEN BEGIN curCyl _ iocb.presentCylinder _ csb.cylinder _ 0; iocb.iocbState _ startSeek; LOOP; END ELSE BEGIN -- not on cylinder 0 yet, try stepping in 16 cylinders csb.cylinder _ 0; -- just to make things consistent iocb.presentCylinder _ 1; iocb.iocbState _ recalStepIn; -- start process of stepping in EXIT; END; END; recalStepIn => -- tried stepping in one (more) cylinder in search of cylinder 0 SELECT TRUE FROM GetDDCHardwareBits[].track00 => -- made it to cylinder 0 BEGIN curCyl _ iocb.presentCylinder _ csb.cylinder _ 0; iocb.iocbState _ startSeek; LOOP; END; iocb.presentCylinder < 16 => BEGIN -- keep stepping-in in case we overshot cylinder 0 iocb.presentCylinder _ iocb.presentCylinder + 1; iocb.iocbState _ recalStepIn; -- continue stepping in EXIT; END; ENDCASE => -- have stepped in; cylinder 0 can't be that way so step out BEGIN csb.cylinder _ maxCylinderNum + 20; iocb.presentCylinder _ maxCylinderNum + 19; -- assume we are all the way in on the disk, past the last cyl iocb.iocbState _ recalStepOut; -- start process of stepping out EXIT; END; recalStepOut => -- tried stepping out one (more) cylinder in search of cylinder 0 SELECT TRUE FROM GetDDCHardwareBits[].track00 => BEGIN -- made it to cylinder 0 curCyl _ iocb.presentCylinder _ csb.cylinder _ 0; iocb.iocbState _ startSeek; LOOP; END; iocb.presentCylinder > 0 => BEGIN -- keep stepping out searching for cylinder 0 iocb.presentCylinder _ iocb.presentCylinder - 1; iocb.iocbState _ recalStepOut; -- continue process of stepping out EXIT; END; ENDCASE => BEGIN -- stepped in as far as possible, cyl 0 not be found => quit iocbStatus _ hardwareError; iocb.iocbState _ recalStepOut; -- loop here if get called again EXIT; END; startSeek => -- either just started or just finished recalibrating. See if a -- Seek is needed ( on destination cylinder now?). If so, set up -- state for Seek, else set state to startTransfer and go -- investigate need for transfer IF (iocb.presentCylinder _ curCyl) = o.clientHeader.cylinder THEN {iocb.iocbState _ startTransfer; LOOP} -- on cyl, start transfer ELSE IF o.clientHeader.cylinder > maxCylinderNum THEN {iocbStatus _ seekTimeout; -- can't get there; clientError-- EXIT} ELSE -- do the seek BEGIN iocb.presentCylinder _ o.clientHeader.cylinder; iocb.iocbState _ doSeek; EXIT; END; doSeek, -- => {iocb.iocbState _ startTransfer; LOOP}; startTransfer => SELECT TRUE FROM driveIsSA1000 AND HeaderOperation[o.command]=write => {iocb.iocbState _ eraseSA1000Track; runLength _ 1; EXIT}; (o.pageCount=0) => {iocb.iocbState _ transferFinish; LOOP}; ENDCASE => {iocb.iocbState _ transfer; LOOP}; eraseSA1000Track => BEGIN iocb.label.filePageLo _ iocb.label.filePageLo - 1; o.clientHeader.sector _ 0; iocb.iocbState _ transfer; LOOP; END; transfer => SELECT iocbStatus FROM inProgress => -- last run went ok BEGIN headerOp: FieldOperation = HeaderOperation[o.command]; sectorsLeftInCyl: CARDINAL = sectorsPerCyl - (o.clientHeader.head*pagesPerTrack + o.clientHeader.sector); runLength _ MIN[o.pageCount, sectorsLeftInCyl]; IF headerOp # write AND LabelOperation[o.command]#read THEN BEGIN OPEN l: iocb.label; -- stop run at 0/1 boundaries, cyl bounds, 65K page bounds IF l.filePageLo = 0 THEN { IF l.filePageHi = 0 THEN runLength _ 1} -- pg 0 ELSE runLength _ MIN[runLength, 65535 - l.filePageLo + 1]; IF driveIsSA1000 AND headerOp IN [noop..read] THEN runLength _ 1; END; IF o.pageCount = runLength THEN iocb.iocbState _ transferFinish ELSE BEGIN IF runLength = sectorsLeftInCyl THEN -- test for overrun, set poststep IF iocb.presentCylinder >= maxCylinderNum THEN iocbStatus _ seekTimeout -- should be client error ELSE iocb.presentCylinder _ curCyl + 1; iocb.iocbState _ transfer; END; EXIT; END; labelCheck => {iocbStatus _ inProgress; iocb.iocbState _ labelRead; EXIT}; ENDCASE => ImpossibleProcessingState; transferFinish => SELECT iocbStatus FROM inProgress => {iocbStatus _ goodCompletion; iocb.iocbState _ transferFinish; EXIT}; labelCheck => {iocbStatus _ inProgress; iocb.iocbState _ labelRead; EXIT}; ENDCASE => ImpossibleProcessingState; labelRead => BEGIN -- we have read a disk label to see if it matches the client's label. -- Note one cannot get a label check on a label read operation since -- the verifyError bit is masked off. If there was a CRC error, -- there would have been a labelError and we wouldn't be here -- now. Hence, the fact that this code is being executed implies -- the label read operation had no errors. -- iocbStatus already _ inProgress o.clientHeader _ o.diskHeader; -- restore Header saved when IOCB set up o.command _ iocb.savedIOCBCmd; -- restore command saved when IOCB set up IF ~PilotDisk.MatchLabels[o.labelPtr, @iocb.label] OR iocb.unusedLabel # 0 THEN {iocbStatus _ labelCheck; EXIT} ELSE {iocb.iocbState _ startTransfer; LOOP}; END; ENDCASE => ImpossibleProcessingState; ENDLOOP; -- build the IOCB from the information given above -- following block depends upon iocb being in first 64K, -- and layout of flag words with flags in rightmost part -- physIOCBAddr _ Inline.LowHalf[DLionInputOutput.GetRealAddress[iocb]]; BEGIN OPEN lp: LOOPHOLE[iocb, Environment.Long]; wPP: CARDINAL = Environment.wordsPerPage; physIOCBAddr _ (LOOPHOLE[DLionInputOutput.GetRealPage[lp.lowbits/wPP], CARDINAL]*wPP) + (lp.lowbits MOD wPP); END; IF iocbStatus # inProgress THEN BEGIN OPEN sC: iocb.seekCommand, fS: iocb.seekCommand.finishSeek; -- build a null IOCB. This will do nothing if run but cause and -- interrupt to the calling mesa process. This is needed if an IOCB -- specifying no action or an illegal action is given. When the iocb -- is finished or the next action specified is illegal, this type of null -- IOCB is also built. -- the present cylinder is already set -- the destination cylinder is already set -- as is the saved IOCB state, though this does not apply here -- the next IOCB field is also already set -- don't bother setting the transfer parameters, they won't be used sC _ seekCmdBlock[dummyIOCB]; -- set the dummy IOCB commands fS.nextIOCB _ iocb.nextIOCB; -- microcode chains to next IOCB fS.transferMask _ csb.transferMask; sC.negDistanceAddr _ sC.negDistanceAddr + physIOCBAddr; fS.statusAddr _ fS.statusAddr + physIOCBAddr; END ELSE SELECT iocb.iocbState FROM IN [recalStepIn..recalStepOut] => BEGIN OPEN sC: iocb.seekCommand, fS: iocb.seekCommand.finishSeek; -- build step iocb -- present cylinder is already set to where transfer starts -- destination cylinder is already set to where transfer ends -- transfer parameters not used sC _ seekCmdBlock[step]; -- set the Step IOCB commands IF iocb.iocbState = recalStepIn THEN -- or in the DirectionIn bit BEGIN sC.controlWd1 _ Inline.BITOR[sC.controlWd1, directionInBit]; sC.controlWd2 _ Inline.BITOR[sC.controlWd2, directionInBit]; sC.waitCmd _ Inline.BITOR[sC.waitCmd, directionInBit]; -- set in the wait command so it is set up before first step pulse END; sC.sendCtlAddr _ sC.sendCtlAddr + physIOCBAddr; fS.nextIOCB _ nil; -- stop after this IOCB completes fS.transferMask _ csb.transferMask; sC.negDistanceAddr _ sC.negDistanceAddr + physIOCBAddr; fS.statusAddr _ fS.statusAddr + physIOCBAddr; END; recalSeek, doSeek => BEGIN OPEN sC: iocb.seekCommand, fS: iocb.seekCommand.finishSeek; -- present cylinder is already set to where transfer starts -- destination cylinder is already set to where transfer ends -- transfer parameters not used sC _ seekCmdBlock[seek]; -- set the Seek IOCB commands IF iocb.presentCylinder > curCyl THEN BEGIN -- OR in DirectionInBit sC.controlWd1 _ Inline.BITOR[sC.controlWd1, directionInBit]; sC.controlWd2 _ Inline.BITOR[sC.controlWd2, directionInBit]; -- set in wait command so it is set up before first step pulse sC.waitCmd _ Inline.BITOR[sC.waitCmd, directionInBit]; -- negative of distance to move heads in cylinders sC.negDistance _ curCyl - iocb.presentCylinder; END ELSE -- out wants negative of distance to move heads in cylinders sC.negDistance _ iocb.presentCylinder - curCyl; sC.sendCtlAddr _ sC.sendCtlAddr + physIOCBAddr; fS.nextIOCB _ nil; -- stop after this IOCB completes fS.transferMask _ csb.transferMask; sC.negDistanceAddr _ sC.negDistanceAddr + physIOCBAddr; fS.statusAddr _ fS.statusAddr + physIOCBAddr; END; eraseSA1000Track, transfer, transferFinish => BEGIN OPEN tP: iocb.transferParm; headerOp: FieldOperation = HeaderOperation[o.command]; -- build transfer IOCB, run length in runLength (adjust if Header -- Read or Noop), command in o.command, add -- post-step of heads if iocb.present cylinder=currentCylinder+1, -- find sector 0 if header op # verify. Pre-increment the dataPtr -- and pageCount for run of pages if necessary. -- the present cylinder is already set -- the destination cylinder is already set -- as is the saved IOCB state, it doesn't apply here -- the next IOCB field is also already set -- fill in the transfer parameter block tP.headerOp _ headerOpBlock[headerOp]; tP.labelOp _ labelOpBlock[LabelOperation[o.command]]; tP.dataOp _ dataOpBlock[DataOperation[o.command]]; tP.dataOp.dataPgNum _ IF DataOperation[o.command] = noop THEN Utilities.PageFromLongPointer[DLionInputOutput.IOPage] ELSE (IF o.incrementDataPtr THEN 100000B ELSE 0) + Utilities.PageFromLongPointer[o.dataPtr]; BEGIN OPEN m: tP.miscCmds, cH: o.clientHeader; m _ miscCmdBlock[driveIsSA1000]; -- set findSectMk and Stop commands m.freezeCmd _ m.freezeCmd + cH.head*8*256; -- "or" head into bits 1..4 m.findSectMkCmd _ m.findSectMkCmd + cH.head*8*256; END; iocb.countThisStage _ tP.sectorCount _ runLength; -- does this transfer involve a verify of the Header fields or should -- the index mark be used to find the first sector? The vast majority -- of operations are done using header verifies to find the proper -- sector. The header is read or Nooped only during scavenging and -- diagnostic operations. It is written only when an entire track is -- being formatted. IF headerOp IN [noop..write] THEN -- this is not a verify so insert the commands used to find sector 0 -- via the index mark BEGIN OPEN sC: iocb.seekCommand, cH: o.clientHeader; IF driveIsSA1000 THEN BEGIN -- the SA1000 drive is connected sC _ seekCmdBlock[findSA1000Sector0]; sC.controlWd2 _ sC.controlWd2 + physIOCBAddr; -- insert phys address of start of transfer block SELECT headerOp FROM IN [noop..read] => BEGIN OPEN Inline; tP.sectorCount _ cH.sector + 1; -- For Trinity: if runLength=1 isn't this AND unnecessary???? -- or is it necessary because a header nop/read is -- really a bunch of reads on the SA1000 tP.dataOp.dataPgNum _ BITAND[tP.dataOp.dataPgNum, 77777B]; --runLength _ (already) 1; END; ENDCASE => --write, can't be verify here BEGIN OPEN Environment; -- to erase a SA1000Track, write one LARGE sector IF iocb.iocbState = eraseSA1000Track THEN tP.dataOp.dataCnt _ (sa1000PagesPerTrack+4) * wordsPerPage END; END ELSE BEGIN -- find sector on SA4000 by counting after the index mark sC _ seekCmdBlock[findSA4000Sector0]; sC.negDistance _ SA4000SectorDistance[cH.sector]; sC.negDistanceAddr _ sC.negDistanceAddr + physIOCBAddr; sC.sendCtlAddr _ sC.sendCtlAddr + physIOCBAddr; sC.initRegsParm _ sC.initRegsParm + physIOCBAddr; END; tP.failCount _ 1; -- quit immediately on Header Field errors END ELSE -- headerOp=verify BEGIN OPEN sC: iocb.seekCommand; -- Fill in commands to skip over most of the -- seekCommandBlock; the transfer starts as soon as SeekComplete -- is detected. sC _ seekCmdBlock[plainTransfer]; sC.controlWd1 _ sC.controlWd1 + physIOCBAddr; tP.failCount _ IF driveIsSA1000 THEN 3*2*sa1000PagesPerTrack --(3 field/sector) ELSE 2*sa4000PagesPerTrack --(1 field/sector) -- the 2 is for a cheap, quick retry END; -- set the transfer command block. It either specifies a post-transfer -- step or not, depending on -- whether iocb.presentCylinder=currentCylinder+1 BEGIN OPEN tC: iocb.transferCommand; IF iocb.presentCylinder = curCyl + 1 THEN tC _ transferCmdBlock[doStep] -- have the microcode do a post step before returning from the IOCB ELSE BEGIN -- no post step after transfer tC _ transferCmdBlock[noStep]; IF iocb.iocbState = transferFinish THEN -- continue with next IOCB in the chain -- the default value for nextIOCB is 0, which will stop -- the microcode after this stage, so unless set otherwise -- above, the microcode will stop. Note if this is the -- last IOCB in the chain, iocb.nextIOCB would have been -- equal to "nil", which was declared to be zero, so the -- microcode will stop in that case too. tC.finishTransfer.nextIOCB _ iocb.nextIOCB; tC.stepCmdWd1 _ tC.stepCmdWd1 + physIOCBAddr; END; tC.parmBlockAddr _ tC.parmBlockAddr + physIOCBAddr; tC.finishAddr _ tC.finishAddr + physIOCBAddr; tC.finishTransfer.statusAddr _ tC.finishTransfer.statusAddr + physIOCBAddr; tC.finishTransfer.transferMask _ csb.transferMask; END; tP.headerOp.headerAddr _ tP.headerOp.headerAddr + physIOCBAddr; tP.labelOp.labelAddr _ tP.labelOp.labelAddr + physIOCBAddr; END; -- of setting up a normal transfer IOCB labelRead => -- set up iocb for read of one label BEGIN OPEN tP: iocb.transferParm; o.labelPtr^ _ iocb.label; -- save expected label iocb.savedIOCBCmd _ o.command; -- save failed operation o.command _ vr; -- read the offending label o.diskHeader _ o.clientHeader; -- save correct header -- dest cylinder already set iocb.presentCylinder _ curCyl; -- we will be on this cylinder -- after the operation completes. This could have been set to -- curCyl+1 in the failing transfer operation. -- saved IOCB state was set when the next state was determined -- the next IOCB field is set -- fill in the transfer parameter block tP.sectorCount _ 1; -- read one label only tP.failCount _ IF driveIsSA1000 THEN 2*3*sa1000PagesPerTrack -- (3 fields/sector) ELSE 2*sa4000PagesPerTrack; -- (1 field /sector) -- the 2 is for a cheap, quick retry tP.headerOp _ headerOpBlock[verify]; -- verify header tP.labelOp _ labelOpBlock[read]; -- then read the label tP.dataOp _ dataOpBlock[noop]; -- forget the data tP.dataOp.dataPgNum _ Utilities.PageFromLongPointer[DLionInputOutput.IOPage]; BEGIN OPEN m: tP.miscCmds, cH: o.clientHeader; m _ miscCmdBlock[driveIsSA1000]; -- set findSectMk, Stop m.freezeCmd _ m.freezeCmd + cH.head*8*256; -- "or" head into bits 1..4 m.findSectMkCmd _ m.findSectMkCmd + cH.head*8*256; END; iocb.seekCommand _ seekCmdBlock[plainTransfer]; -- just looking iocb.seekCommand.controlWd1 _ iocb.seekCommand.controlWd1 + physIOCBAddr; BEGIN OPEN tC: iocb.transferCommand; tC _ transferCmdBlock[noStep]; -- no poststep tC.finishTransfer.nextIOCB _ nil; -- stop after label read tC.stepCmdWd1 _ tC.stepCmdWd1 + physIOCBAddr; tC.parmBlockAddr _ tC.parmBlockAddr + physIOCBAddr; tC.finishAddr _ tC.finishAddr + physIOCBAddr; tC.finishTransfer.statusAddr _ tC.finishTransfer.statusAddr + physIOCBAddr; tC.finishTransfer.transferMask _ csb.transferMask; END; tP.headerOp.headerAddr _ tP.headerOp.headerAddr + physIOCBAddr; tP.labelOp.labelAddr _ tP.labelOp.labelAddr + physIOCBAddr; END; ENDCASE; o.deviceStatus.a _ 0; -- mark iocb as not having been seen by uCode iocb.savedError _ iocbStatus; -- this will be used in no-op IOCB's RETURN[iocbStatus]; END; Output: PROC [cmd: UNSPECIFIED, i: IOCBlongPtr _ NIL] = INLINE { DLionInputOutput.Output[cmd, KCtl]}; IOCBCrossesPage: ERROR = CODE; PageCrossCheck: PROC [i: IOCBshortPtr] = INLINE BEGIN IF LOOPHOLE[i, CARDINAL] MOD Environment.wordsPerPage >= Environment.wordsPerPage - SIZE[IOCB] THEN ERROR IOCBCrossesPage; END; Words: PROC [sectors: CARDINAL] RETURNS [LONG CARDINAL] = INLINE { RETURN[Inline.LongMult[sectors, Environment.wordsPerPage]]}; END.... LOG Time: June 13, 1980 3:40 PM, By: DDavies Action: start change to Dandelion Head Time: June 16, 1980 5:11 PM, By: Davies Action: end first pass at Dandelion Head Time: June 17, 1980 5:12 PM, By: Davies Action: prepare for compilation Time: June 23, 1980 3:51 PM, By: Forrest Action: Fix bug, add IOCB check. Reformat/maulOver. Time: July 16, 1980 1:25 PM, By: Forrest Action: Take out code supporting initial debugging, add ability to format parts of SA4000 tracks. Time: September 9, 1980 2:33 PM, By: Forrest Action: Reformat (again), work on Label check bug. Time: September 9, 1980 2:33 PM, By: Sandman Action: AddressFault bug. Time: January 22, 1981 3:10 PM, By: Gobbel Action: Don't reset cylinder when first building IOCB. Reformat. Time: February 11, 1981 1:53 PM, By: Forrest Action: Add code (state) to format SA1000 track. Time: February 14, 1981 2:25 PM, By: Gobbel Action: Fix bug in NextIOCBState. Time: March 31, 1981 9:44 AM, By: Forrest Action: change the pre adjustment or pageCOunt and DataPointer in NextIOCBState to adjustment after the IOCB is cooked. (1792)\f8