:IF[WithRDC]; ************************************
INSERT[RdcDefs];
TITLE[RDC];*Rigid disk controller

%Edit by Ed Fiala 2 February 1982: more Output interlocks and save
4b mi.
Edit by Ev Neely March 23, 1981 2:42 PM Stepper settling in Recal.
Edit by Ev Neely November 4, 1980 4:32 PM. Fix Retry.
Edit by Ev Neely October 24, 1980 5:00 PM. Fix RecalSeekLimit.
Edit by Jim Frandeen September 22, 1980 3:51 PM Rearrange Output instructions to fix the Worst Gotcha Ever.
Edit by Jim Frandeen September 2, 1980 2:28 PM Change DoInt to NotifyInterrupt.
Edit by Ev Neely June 30, 1980 12:50. Changes in support of 48 bit processor-ID and performance improvement. Makes use of new PackedLabels prepared by head. Works with changed IOCB format.
Edit by Ev Neely June 19, 1980 11:56. Provides counting of servicelates for performance tests.
Edit by Jim Frandeen June 18, 1980 9:44 AM Update comment at StoreStatus.
Edit by Ev Neely June 13, 1980 5:29 PM. Fix for AR4320 provides for a maximum of 201 negative one track seeks during Recal so that device-hardware faults don’t hang the microcode.
Edit by Jim Frandeen March 17, 1980 8:07 PM Fix for new Rdc board. Fix for MP 733 caused by instruction sequence IOFetch20, Output, followed by task within four microinstructions; or Output, Output followed by task within four microinstructions.
Edit by Jim Frandeen February 19, 1980 7:39 PM Complete overhaul to help display jitter.
Edit by Jim Frandeen January 25, 1980 8:35 AM Get address of CSB from register zero.
Edit by Jim Frandeen January 21, 1980 8:29 AM Fix bug on label fixup for runs of pages. Make changes for new D0Lang.
Edit by Jim Frandeen December 26, 1979 2:13 AM for runs of pages.
This version does not attempt to lock out task zero.
%
SetTask[RdcTask];*currently 11b = 9d
ON PAGE[RdcPage];
RdIdle:
*Come here when we want to enter or continue the Idle state. We are just hanging around waiting for TheHead to chain a new IOCB onto the CSB. TheHead is our link to TheOutsideWorld. When TheOutsideWorld wants to execute a disk command, he calls TheHead. TheHead then constructs an Input Output Control Block (IOCB) and chains it onto the Controller Status Block (CSB). When we wake up, we look to see if there is anything to do.
Call[RdStrobe];*Terminate the wakeup.
*Come here when we get the next sector wakeup. We get a sector wakeup at the end of every data sector. 1383 sectors go by every second, so we get a wakeup every 723 microseconds. When we wake up, look and see if we have an IOCB chained onto the CSB. Fetch four words from the CSB: NextIOCB points to the next IOCB, if any. Deferring will be -1 if we must defer processing until TheHead has handled an error. The next word is not used. TransferMask will be used to call DoInt to schedule Mesa processes at the end of the command.
Input[RdcDiskStatus,RdcStatus],
Call[RdTask];*We are not in a hurry yet, so TASK to wait for memory.
Pfetch2[RdcCSBptr,RdcNextIOCB,RdcCSBnext!],
Call[RdUpdateCurrentSector];
T←RdcNextIOCB;*T points to next IOCB
RdNextIOCB:
*When we arrive here, IOCBptr points to the next IOCB and Deferring contains -1 if we must defer processing until Poll reports an error to the client.
LU←RdcDeferring,
GoTo[RdNewIOCB,ALU#0];*If new IOCB
GoTo[RdIdle];*If nothing to do
RdNewIOCB:
*Boy, Wow!! We have something to do! Time’s a wast’n! When the Controller turns on our wakeup latch, we are only 56 bytes from the sector mark. We only have 60 microseconds after sector wakeup to get ready and send a command to the Controller. No telling how much time has already gone by. We didn’t get our wakeup call until we were the highest priority task. And the evil DisplayController runs at a higher priority task level. If the header shows up before we send the command, we will get IOAtten and ServiceLate. And you know what that means! We will have to wait one more disk revolution. And TheOutsideWorld will not be pleased!
*A rule of the game is that we have to task to give up control of the CPU after every dozen or so microinstructions. And we don’t know when we will get scheduled again after a task. The evil DisplayController is a glutton for CPU cycles. If we don’t task often enough, the display will flicker. And the OutsideWorld will really be pissed! At the same time, the sector mark is less than 60 microseconds away. So we better hurry! Try to avoid branches that cause burps, and beat it down to DataTransfer.
*T points to the new IOCB. Fetch the new disk address from the IOCB’s OperationClientHeader into RdcCylinder and RdcHeadSector.
OddPfetch2[RdcZeroBase,RdcCylinder],
GoTo[RdIdle,ALU<0];*If deferring
*Continue here if Deferring is not negative. Set up base registers for accessing the IOCB.
RdcIOCBptr←T;*Set up base register for first 16 words of the IOCB.
RdcIOCB16Ptr←T;
RdcIOCB16Ptr←(RdcIOCB16Ptr) + (20C);*Set up base register for second 16 words of the IOCB.
RdRetryReEntry:
*Fetch the IOCB’s data pointer into RdcDataPtr and RdcDataPtr1. The operation-command (the original enumerated command issued by the user) into RdcIncrementDataPtrFlag (it is negative if the DataPtr is to be incremented after each page transferred). The number of pages to transfer into RdcPageCount.
Pfetch4[RdcIOCBPtr,RdcDataPtr,RdcIOCBdataPtr!],
Call[RdTask];
*Fetch the current cylinder address of the drive we are about to reference from CSB.diskAddress[drive]. This will be set to -1 if we must recalibrate. Currently, we always reference drive zero. Eventually, we will need to set DiskAddressPtr to point to CSB.diskAddress[of the drive we are about to reference].
Pfetch1[RdcDiskAddressPtr,RdcCurrentCylinder,0];
*Fetch four words from the PackedClientLabel that will be updated after each page transferred. A word containing the flag bits into RdcFileFlags (it must be zero for subsequent pages). A word containing FilePageLo into RdcFilePageLo which is incremented for subsequent pages. And two words which will not be changed but are fetched to allow use of a PStore4 (which is 3 times faster than a Ptore2) at RdGoodCompletion.
Pfetch4[RdcIOCB16Ptr,RdcFileFlags,AND[17,RdcIOCBFileFlags!]];
*Fetch RdcIOCBcontrollerCommand (a command formatted for the controller’s command register) from the IOCB into RdcCommand.
Pfetch1[RdcIOCBPtr,RdcCommand,AND[17,RdcIOCBcontrollerCommand!]],
Call[RdSetupDriveHead];
T←(RdcCurrentCylinder),
GoTo[RdRecal,R<0];*If recal required.
*Well, this is obstacle number one. The drive must be recalibrated before we can execute any commands. This means we must move the disk arm back to track 0. Seek errors cause this to happen. GoTo Recal and continue at TestForSeek when the drive has been recalibrated. If no seek is required after recalibration, continue at DiskArmPositioned after the heads have settled.
*The drive is ready, and we we do not need to recalibrate. See if we need to seek. We will not need to seek if the disk address is for one of the fixed heads or if CurrentCylinder is equal to Cylinder. Test for the same cylinder first to save time.
RdTestForSeek:
T←(RdcCylinder)-T;*T = Cylinder-CurrentCylinder
LU←(LDF[RdcHeadSector,4,1]),*Test for fixed heads.
GoTo[RdSeek,ALU#0];*If CurrentCylinder#Cylinder
RdDiskArmPositioned:
LU←LDF[RdcCommand,10,10];*Test for seek only
RdcSectorTimeOutCount←RdcSectorTimeOutWakeUps,
Skip[ALU#0];*If not seek only
RdcCompletionCode←RdcGoodCompletion,
GoTo[RdStoreStatus];*If seek only
T←LDF[RdcHeadSector,10,10],*T = sector specified by command.
Call[RdTask];
*Here we are ready to send a command to the Controller. Look to see if the sector coming up is the one we want to access. CurrentSector is what we think the next sector will be.
LU←(RdcCurrentSector) XOR T;
RdTestSector:
T←(RdcDataPtr1)+1,
GoTo[RdDataTransfer,ALU=0];*Go for it!!
RdcSectorTimeOutCount ← (RdcSectorTimeOutCount)-1;
T←LDF[RdcHeadSector,10,10],*T = sector specified by command.
GoTo[RdSectorWait, ALU#0];*If not sector time out
*Continue if the sector has timed out.
RdcCompletionCode←RdcSectorTimeOut,GoTo[RdReportError];
RdSectorWait:
*Can you believe it? We hurried all the way down here, and this isn’t even the sector we need to access. Clear the Error latch in case we got a ServiceLate on the last command. If so, the DevOp register will still have something in it, and this will cause another ServiceLate. We must interlock after the Output before a task switch. Go back to sleep until the next sector wakeup.
Output[RdcZeroBase,RdcErrorReset];
RdcZeroBase ← RdcZeroBase, IOStrobe, Call[RdTask];*Go back to sleep.
*Continue at the next sector wakeup.
Input[RdcDiskStatus,RdcStatus],
Call[RdTask];
Call[RdUpdateCurrentSector];
LU←(RdcCurrentSector) XOR T,
GoTo[RdTestSector];
RdDataTransfer:
*Get DataPtr ready to point to the data area in memory. If BP[0:23] is a base pointer, BP[0:7] is in bits 0-7, and BP[0:7]+1 is in bits 8-15.
RdcDataPtr1←(LSH[RdcDataPtr1,10]) OR T;
Output[RdcZeroBase, RdcMemBuffAdr];*Set MemBufAdr to zero.
LU←(RdcCommand) AND (RdcDataWriteOrVerify);
RdcNewCylinder←0C,*Assume we will not have to seek after this transfer.
GoTo[RdSendCommand,ALU=0];*If not write or verify.
*Continue if write or verify operation. Preload the Controller buffer with header and label data and 16D words of data.
IOFetch16[RdcIOCB16ptr,RdcOutput,0],*Transfer 20 words from the IOCB starting at RdcIOCBclientHeader which was 16word aligned at RdcIOCB16ptr so that we could send the header and label info with an IOFetch16.
Call[RdTask];
IOFetch16[RdcDataPtr,RdcOutput,0];*Transfer 20 data words to the Controller buffer.
*NOTE: We must be careful not to send the drive and head too soon. When we are writing, we get the last sector wake at the beginning of writing the ECC. ECC will not be completed until 4.5 microseconds after the wake-up. If we change the drive/head during this time, we will not be able to read the data back in.
Output[RdcDriveHead,RdcDrive/Head];*Send drive and head to Controller
Output[RdcCommand,RdcDevOp];*Send the Command.
LU←(RdcCurrentSector) XOR (RdcSectorsPerTrack),*Test for end of track.
GoTo[RdIncrementDiskAddress];
RdSendCommand:
*Come here if not write or verify. In this case, we send the command first and then load the header and label data into the Controller buffer.
Output[RdcDriveHead,RdcDrive/Head];*Send drive and head to Controller
Output[RdcCommand,RdcDevOp];*Send the Command.
Output[RdcZeroBase,RdcMemBuffAdr];*Set MemBufAdr to zero.
IOFetch16[RdcIOCB16ptr,RdcOutput,0], Call[RdTask];
*Now increment the disk address and the CurrentSector for the next wakeup. We must not store it into the IOCB until the command has completed. If we get an error, we need to be able to retry this IOCB.
LU←(RdcCurrentSector) XOR (RdcSectorsPerTrack);*Test for end of track.
RdIncrementDiskAddress:
RdcHeadSector←(RdcHeadSector)+1,*Increment sector.
GoTo[RdTestHeaderWrite,ALU#0];*If not new head
RdcCurrentSector←(zero)-1;*Next Sector is zero. We will increment by one below.
RdcHeadSector←LHMask[RdcHeadSector];*Set to read sector zero.
*Note: This will have to be modified for single platter drives
LU←(RdcHeadSector) XOR (RdcHead7);*Test for end of cylinder.
RdcHeadSector←(RdcHeadSector)+(400C),*Increment head.
GoTo[RdNewHead,ALU#0];*If not end of cylinder.
RdcHeadSector←(zero);*HeadSector←zero.
RdcCylinder←(RdcCylinder)+1;*Increment cylinder.
RdcNewCylinder←(zero)-1;*Set up to seek one cylinder in the positive direction next time.
RdNewHead:
Call[RdSetupDriveHead];*Set DriveHead for new head.
LU←(RdcCommand) AND (RdcWriteHeader),*Test for writing headers
GoTo[RdTestHeaderWrite2];
RdTestHeaderWrite:
LU←(RdcCommand) AND (RdcWriteHeader);*Test for writing headers
*If we are writing headers, we are done until the next sector wakeup. At that time, we will check to see if we got any errors. Terminate this wakeup and go to sleep.
RdTestHeaderWrite2:
RdcFilePageLo←(RdcFilePageLo)+1,*Increment filePageLo
GoTo[RdWaitForEndTransfer, ALU#0];*If writing headers
*We have just incremented the label file page. Now clear the label file flags(except for bootFile). We must not store them back into the client label of the IOCB until the command has terminated successfully.
RdcFileFlags←(RdcFileFlags) AND NOT (RdcMaskFileFlags);
*We have done all we can for now. Terminate this wakeup and go to sleep until the header field on the disk shows up. The header field contains the address of the next sector.
IOStrobe;*Terminate the wakeup
Call[RdTask];*Task switch.
*Continue here when the Controller has read the header field from the disk into its buffer. Transfer the header information from the Controller’s buffer into the IOCB. This is the exciting moment, ladies and gentlemen! We will soon know if we got our command ready in time.
RdcTemp←0C,*Set MemBuffAdr to zero.
Call[RdPrimeIdata];*Start the Controller.
RdcTemp←RdcTemp;*Interlock for PrimeIdata
*Now we perform one of those tricks of the microcoding business. We want to store the header information into two words of the IOCB beginning at RdcIOCBdiskHeader; but we must use a IOStore4 from and to a quadword boundary and the header info begins at RdcInput+2. So we store the four words beginning at RdcInput into RdcIOCBdeviceStatus. Since RdcIOCBdiskHeader = RdcIOCBdeviceStatus+2, the header data is stored into the right place. RdcIOCBdeviceStatus gets garbage but that’s ok because it will be overwritten at RdStoreErrorStatus time.
IOStore4[RdcIOCBptr, RdcInput,RdcIOCBdeviceStatus!],
*Well, we blew it! We got IOAtten from the Controller, and that means trouble! Our command was probably too late. Sigh!
GoTo[RdHeaderIOAtten, IOAtten];
*Well, we made it past the header. We win part one. But the game is not over. The bits are flying fast and furiously now. The SA4000 transfers seven million bits per second. Will we catch them all as the disk flies by? Or will the evil DisplayController steal our CPU cycles and cause ServiceLate or RateError? Tune in at EndTransfer for the exciting conclusion!!
LU←(RdcCommand) AND (RdcLabelReadOrVerify);*For test at EndLabel
Skip[ALU#0];*If label read or verify
LU←(RdcCommand) AND (RdcDataWriteOrVerify),
GoTo[RdTestDataAction];*If no label read or verify
*Come here to read or verify label. Issue IOStrobe to cancel the Header field wakeup and go to sleep.
Call[RdStrobe];*Terminate the wakeup.
*Continue at the first label field wakeup. (Note: if the Controller could not find the sync byte, we are at the next sector wakeup and IOAtten is set). Prepare the Controller to read the label from the Controller’s buffer. Issue IOStrobe to terminate the wakeup and go to sleep.
RdcTemp←6C,*Set MemBuffAdr to 6.
Call[RdPrimeIdata];*Start the Controller
RdcTemp←RdcTemp;*Interlock for PrimeIdata.
IOStore4[RdcIOCB16Ptr,RdcInput,AND[17,RdcIOCBdiskLabel!]],*Read the first four label words into the IOCB.
Call[RdStrobe];*Terminate the wakeup.
*Continue after the second label field wakeup. Read the second four label words into the IOCB.
T←(RdcIOCBPtr) + (40C);*Using this way of storing into the second half of the diskLabel saves two base registers.
OddIOStore4[RdcZeroBase,RdcInput];
LU←(RdcCommand) AND (RdcDataWriteOrVerify),
GoTo[RdTestDataAction,NoAtten];*If not IOAtten
*Come here if we have IOAtten due to a label error.
RdcCompletionCode←RdcLabelError,
GoTo[RdTestError];
RdTestDataAction:
*If write or verify data, GoTo WriteData. If data read, GoTo ReadData; otherwise skip data entirely.
LU←(RdcCommand) AND (RdcReadData),*Test for data read.
GoTo[RdWriteData,ALU#0];*If data write or verify.
GoTo[RdReadData,ALU#0];*If read data
GoTo[RdWaitForEndTransfer];*Skip data altogether.
RdWriteData:
*Come here to write or verify data. Issue IOStrobe to cancel the wakeup and go to sleep.
RdcTemp←40C,*to set MemBuffAdr
Call[RdStrobe];*Terminate the wakeup.
*Continue after the first data field wakeup. Set MemBufAdr to the address following the last address loaded with data in the sector wake (20 header and label + 20 data = 40). Here is Gotcha number 16. We must interlock before tasking.
Output[RdcTemp,RdcMemBuffAdr];
RdcTemp ← 15C;*Set loop counter to n-2.
RdcDataPtr←(RdcDataPtr)+(20C),
Call[RdUpdateDataPtr1];*If overflow.
RdWriteData2:
*Repeat the following loop 17B (15D) times: Transfer 20 (16D) words to the Controller buffer. Issue IOStrobe to terminate the wakeup. Go to sleep. Continue at the next data wakeup. Temp is a loop counter. It will be -1 the last time through the loop.
*We execute IOFetch4 four times instead of one IOFetch20. Again due to the famous Redell Syndrome.
IOFetch4[RdcDataPtr,RdcOutput,0];*Transfer 4 more data words to the Controller buffer.
Call[RdTask];*Must be on this instruction for placement constraint.
IOFetch4[RdcDataPtr,RdcOutput,4],*Transfer 4 more data words to the Controller buffer.
Call[RdDataPtrPlus10];*Increment DataPtr by 10.
IOFetch4[RdcDataPtr,RdcOutput,0],*Transfer 4 more data words to the Controller buffer.
Call[RdTask];
IOFetch4[RdcDataPtr,RdcOutput,4],*Transfer 4 more data words to the Controller buffer.
Call[RdDataPtrPlus10AndStrobe];*Increment DataPtr by 10 and terminate the wakeup.
*Continue here after the next data wakeup. Temp will be -1 the last time through the loop.
RdcTemp←(RdcTemp)-1,*Test for last transfer
GoTo[RdWriteData2,R>=0];*If not last data transfer
*Continue here on the 16th wakeup. Ignore by issuing IOStrobe and go to sleep. The next wakeup is equivalent to sector wakeup.
GoTo[RdWaitForEndTransfer];
RdReadData:
*Come here for data field read operation. Issue IOStrobe to terminate the header field or label field wakeup and go to sleep.
Call[RdStrobe];*Terminate the wakeup.
*Continue here on the first data field wakeup. Set MemBufAdr to 21B. Do PrimeIData to start the controller.
RdcTemp←21C,*Set MemBuffAdr to 21
Call[RdPrimeIdata];
RdcTemp←(16C);*Set loop counter to n-2 and Interlock.
RdReadData2:
*Repeat the following loop 16D times: Read 16D words into the data buffer. Issue IOStrobe to terminate the wakeup and go to sleep.
IOStore4[RdcDataPtr,RdcInput,0];*Transfer 4 bytes from Controller buffer to memory.
Call[RdTask];*Must be on this instruction for placement constraint.
IOStore4[RdcDataPtr,RdcInput,4],*Transfer 4 bytes from Controller buffer to memory.
Call[RdDataPtrPlus10];*Increment DataPtr by 10.
IOStore4[RdcDataPtr,RdcInput,0],*Transfer 4 bytes from Controller buffer to memory.
Call[RdTask];
IOStore4[RdcDataPtr,RdcInput,4],*Transfer last 4 bytes bytes from Controller buffer to memory.
Call[RdDataPtrPlus10AndStrobe];*Increment DataPtr by 10 and terminate the wakeup.
RdcTemp←(RdcTemp)-1,*Test for all bytes transferred.
GoTo[RdReadData2,R>=0];*If not last transfer
*Come here after the 17th wakeup. The last wakeup was the end of the read and the next sector wakeup.
RdcCurrentSector←(RdcCurrentSector)+1,*Increment the current sector. (It will be -1 if the current sector is zero).
GoTo[RdEndTransfer];
RdWaitForEndTransfer:
RdcCurrentSector←(RdcCurrentSector)+1,*Increment the current sector. (It will be -1 if the current sector is zero).
Call[RdStrobe];*Wait for next sector wakeup.
RdEndTransfer:
*Well, here we are boys and girls, at the wakeup after the data transfer. And the winner is.......
LU←RdcIncrementDataPtrFlag,
GoTo[RdRestoreDataPtr,R<0];*If incrementDataPtr
*Continue if DataPtr is not be to incremented. We must refetch DataPtr from the IOCB.
Pfetch2[RdcIOCBPtr,RdcDataPtr,RdcIOCBdataPtr!],
GoTo[RdTestCompletion];
RdRestoreDataPtr:
*DataPtr is in the format required by the hardware. If BP[0:23] is a base pointer, BP[0:7] is in bits 0-7, and BP[0:7]+1 is in bits 8-15. Put DataPtr back into the 24-bit address format.
RdcDataPtr1←RSH[RdcDataPtr1,10];
RdTestCompletion:
RdcCompletionCode←RdcGoodCompletion,*Assume good completion
GoTo[RdDataIOAtten,IOAtten];*Wait one instruction before testing IOAtten
*Wheww!!! We made it! Again the mighty RigidDiskController triumphs over the evil DisplayController!!!!!
RdGoodCompletion:
*Come here when the command has been completed. CompletionCode is GoodCompletion. We update all of the fields that have to do with runs of pages: clientHeader, filePageLo in clientLabel, pageCount, and DataPtr. We zero all of the flags in the clientLabel except for bootFile.
*Store the calculated next disk address(2 words) in IOCB’s RDC.clientHeader. The two words following RDC.clientHeaderare not used so that we may use a Pstore4 which is almost 3 times faster than a Pstore2.
Pstore4[RdcIOCB16ptr,RdcCylinder,0], Call[RdTask];
*Update FileFlags and FilePageLo in the IOCB’s PackedClientLabel. Four words of the PackedClientLabel were fetched so that this fast Pstore4 could be used.
Pstore4[RdcIOCB16ptr,RdcFileFlags,AND[17,RdcIOCBFileFlags!]];
*Decrement page count.
RdcPageCount←(RdcPageCount)-1;
FreezeResult;*Allow write of PageCount while preserving ALU for conditional Goto. Avoids Gotcha #5
*Update Data Pointer and pageCount in the IOCB.
Pstore4[RdcIOCBPtr,RdcDataPtr,RdcIOCBdataPtr!],
GoTo[RdStoreStatus,ALU=0];*Page count zero means end of run, go store status.
*Continue if the page count is not zero yet. See if we have to seek to a new cylinder. If so, we will seek one cylinder in the positive direction.
T←RdcNewCylinder, Skip[R>=0];*If not new cylinder
GoTo[RdSeekPos];
T←(RdcDataPtr1)+1,
GoTo[RdDataTransfer];*Go transfer next page.
RdStoreStatus:
Input[RdcDiskStatus,RdcStatus];
*Fetch the pointer to the next IOCB from the current IOCB. We must not do this unless the operation completed seccessfully. If an error occurred, we want CSB.next to be pointing to the IOCB that caused the error.
Pfetch1[RdcIOCBPtr,RdcNextIOCB,AND[17,RdcIOCBnext!]],
Call[RdTask];
*Now we must not TASK again until we have updated both Status in the IOCB and Next in the CSB. We could task if we did things in the following order: update status, update CSB.next, call DoInt. We do things in the order: update CSB.next, update status, call DoInt. Note that we can’t TASK within three microinstructions of LoadPage (D0Gotcha number 1).
Pstore1[RdcCSBptr,RdcNextIOCB,RdcCSBnext!];*Store pointer to next IOCB in CSB.
RdStoreErrorStatus:
Pfetch1[RdcCSBptr,RdcTemp,RdcCSBTransferMask!];
*First form the two word status needed for the IOCB. The CompletionCode goes in bits[0..3]. Controller status goes in the low order bits of the first word. And the second word is 0. Note that registers RdcDiskStatus and RdcCompletionCode are together and doubleword aligned.
T←LSH[RdcCompletionCode,14];
RdcDiskStatus←(RdcDiskStatus) OR T;
RdcCompletionCode←(Zero);
*Now store it in IOCBdeviceStatus.
Pstore2[RdcIOCBPtr,RdcDiskStatus,RdcIOCBdeviceStatus!];
*To avoid Gotcha #6 the above Pstore2 must be followed by a non memory instruction so that RdcCompletionCode will be written before the store.
*Now we call DoInt with T set to the TransferMask to schedule the Mesa processes that wants to know about end of command. We don’t test TransferMask for zero since Pilot always wants to know about end of command.
LoadPage[NotifyInterruptPage];
T←RdcTemp,*Bits to OR into NWW
CallP[NotifyInterrupt];*Set NWW and IntPending; uses registers 0,1
T←RdcNextIOCB,*T points to IOCB.
GoTo[RdNextIOCB];*Go process the next IOCB.
RdDriveChange:
*This section must be rewritten when multiple drives are added.
*Come here if the new command references a different disk drive than the previous command. We have to wait until the Controller has a new drive ready. When the drive is ready, go back and continue where we left off.
RdcSectorTimeOutCount←RdcDriveChangeTimeWakeUps,
GoTo[RdIdle];*Go back to sleep.
RdWaitForDriveReady:
*Continue here at the next sector wakeup. See if device is ready.
LU←(RdcDiskStatus) AND (RdcDevSelOK);*Check device selected
RdcSectorTimeOutCount←(RdcSectorTimeOutCount)-1,
Skip[ALU=0];*If drive is not yet ready.
GoTo[RdTestForSeek];*DevSelOK=1
*Come here if the disk is not yet operational (DevSelOK=0). Check for timeout.
Skip[ALU=0];*If not timeout
*Come here if the device is not yet operational and timeout has not occurred.
GoTo[RdIdle];
*Come here if disk has timed out. We can’t hang around forever waiting for the disk. TheHead will wonder what happened to us.
RdcCompletionCode←RdcNotReady,
GoTo[RdStoreStatus];
RdRecal:
%
Come here if CurrentCylinder is -1 indicating that we must recalibrate the disk. Send negative one track seek commands to the Controller until Track0 appears in the Status word. Recal is used in initializing and when the OutsideWorld is having trouble with the disk system. RdcRecalSeekCount is used to prevent the ucode from looping and banging the disk arm noisily against the stop when we can’t get to or can’t see track 0. We must wait for each seek to complete before sending another Command; otherwise we’ll be testing for track 0 before it’s available. RdcRecalSeekLimit is 300 decimal (454B) which is comfortably more than the maximum number of negative one track seeks.
%
RdcRecalSeekCount←RdcRecalSeekLimitHi;*Hi order byte of limit.
T←RdcRecalSeekLimitLo;*Lo order byte of limit.
RdcRecalSeekCount←(RdcRecalSeekCount)+T;
LU←(RdcDiskStatus) AND (RdcTrack0);
RdRecal2:
*Reentry point after each negative one track seek.
RdcRecalSeekCount←(RdcRecalSeekCount)-1,
GoTo[RdTrack0,ALU#0];*If we are at track 0.
*We are not at track 0 yet. RdcRecalSeekCount=0 implies a RecalError caused by hardware errors which prevent reaching or seeing track0.
Skip[ALU=0];*If RecalError.
*Not RecalError, send a negative one cyllinder seek command and wait for the seek to complete. Return to Recal2 above when it has completed.
T←1C,GoTo[RdSeekNeg];
*RecalError, set code and report error.
RdcCompletionCode←RdcRecalError,Goto[RdReportError];
RdTrack0:
*We are at track 0. We still have to seek to the desired cylinder unless it was 0.
LU←RdcCylinder;
RdcHeadSettleCount←RdcHeadSettlingTimeWakeUps,
Skip[ALU#0];*If desired cylinder was not 0, we must wait one revolution before seeking to it.
RdcCurrentCylinder←0C,
GoTo[RdHeadSettle];*If desired cylinder was 0, we are there but must wait one revolution for heads to settle.
RdcCurrentCylinder←0C;*Placement constraint.
RdStepperSettle:
*If desired cylinder was not 0, we must wait one revolution before seeking to it; because rapidly changing stepper directions drives it crazy. We don’t need to keep track of sectors because, after the seek, RdHeadSettle will reestablish the sector count.
Call[RdStrobe];*Wait for next sector wakeup.
RdcHeadSettleCount←(RdcHeadSettleCount)-1;
GoTo[RdStepperSettle,ALU#0];*If stepper not settled.
T←RdcCurrentCylinder,
GoTo[RdTestForSeek];
RdSeek:
*Come here if CurrentCylinder is not the same as Cylinder. We need to seek, unless the address is for one of the fixed heads. Head address is in bits [0..7] of HeadSector. Bits[0..3] are not used. Bit 4 will be nonzero if fixed heads. LU=(LDF[RdcHeadSector,4,1])
T←(zero)-T,*T = CurrentCylinder-Cylinder
GoTo[RdSeekNeg,ALU=0];*If not fixed heads
GoTo[RdDiskArmPositioned];*If fixed heads
RdSeekNeg:
RdcSeekCommand←RdcSeek-D,*Set seek command for negative seek.
GoTo[RdSeekNeg2,ALU>=0];
RdSeekPos:
RdcSeekCommand←RdcSeek+D,*Set for positive direction seek.
GoTo[RdSeekLoop];*If CurrentCylinder > Cylinder.
RdSeekNeg2:
T←(zero)-T;*Seek counts up instead of down.
RdSeekLoop:
*Send seek commands to Controller. T = number of tracks to seek in negative form. This crazy machine can add one to T, but it can’t subtract one from T. So we count up instead of down.
Output[RdcSeekCommand,RdcDevOp];*Send seek command, direction, and allow wake to Controller.
RdcSeekCommand ← RdcSeekCommand;*Delay for Worst Gotcha Ever.
*Now we need to delay at least one microsecond before issuing another Output command. If we send the commands too fast, the Controller gets confused. We delay by tasking. Initialize TimeOutCount. We better find our cylinder before it times out. Set up HeadSettlingCount. We will start to count down when the seek is complete.
Call[RdDelayAndTask];
RdcHeadSettleCount←RdcHeadSettlingTimeWakeUps,
Call[RdDelayAndTask];
T←(zero)+T+1;*Decrement # tracks to seek.
RdcSectorTimeOutCount←RdcSeekTimeOutWakeUps,
GoTo[RdSeekLoop,ALU#0];*If we need to send another seek command.
*Turn off the seek bit in the command, but leave AllowWake and Direction bits on. otherwise the Controller gets confused.
RdcSeekCommand←(RdcSeekCommand) AND (RdcAllowWakeAndSeekDirection);
Output[RdcSeekCommand, RdcDevOp];*Send command to Controller.
RdcSeekCommand ← RdcSeekCommand;*Delay for Worst Gotcha Ever.
RdSeekWait:
*Now we must wait for the seek to complete. Go to sleep and continue here at each sector wakeup. Check the Status word at each wakeup until SeekComplete appears.
Call[RdStrobe];
Input[RdcDiskStatus,RdcStatus],
Call[RdTask];
LU←(RdcDiskStatus) AND (RdcSeekComplete);*Check seek complete
RdcSectorTimeOutCount ← (RdcSectorTimeOutCount)-1,
GoTo[RdSeekComplete,ALU#0];
*Continue if seek has not yet completed. See if seek has timed out.
GoTo[RdSeekWait,ALU#0];*If seek has not timed out.
*A seek time out error has occurred.
RdcCompletionCode←RdcSeekTimeOut,GoTo[RdReportError];
RdSeekComplete:
*Seek has completed. See if this was a one step seek for recalibrating.
LU←RdcCurrentCylinder,
GoTo[RdHeadSettle,R>=0];*If not recalibrating
*The seek has completed, and we are recalibrating. Go see if we are at track 0 yet.
LU←(RdcDiskStatus) AND (RdcTrack0), GoTo[RdRecal2];
RdHeadSettle:
*Come here when the seek has completed. Now we must wait for the heads to settle at the new cylinder. All that seeking was a shock to their little sensors. Go to sleep and count sector wakeups until we think the heads have settled down. We have lost our place on the disk. However, we must wait for more than one disk revolution, so we are sure to see Track0 and find out where we are by the time the heads settle.
NOP;*Placement constraint
RdHeadSettle2:
T←RdcCylinder,*Prepare to update CurrentCylinder
Call[RdStrobe];*Wait for next sector wakeup.
Input[RdcDiskStatus,RdcStatus],
Call[RdTask];*Task and allow time for memory
Call[RdUpdateCurrentSector];
RdcHeadSettleCount←(RdcHeadSettleCount)-1;
GoTo[RdHeadSettle2,ALU#0];*If heads have not settled yet.
*Continue if the heads have settled.
Pstore1[RdcDiskAddressPtr,RdcCylinder,0];
*The seek has completed, and the heads have settled.
RdcCurrentCylinder←T,*Update CurrentCylinder
GoTo[RdDiskArmPositioned];
RdStrobe:
*This subroutine terminates the wakeup and goes to sleep until the next sector wakeup. Note: This does not work if IOStrobe and RETURN are on the same statement. We must delay before the RETURN.
IOStrobe, GoTo[RdTask];
RdDelayAndTask:
GoTo[RdTask];
RdDataPtrPlus10AndStrobe:
IOStrobe;
RdDataPtrPlus10:
RdcDataPtr←(RdcDataPtr)+(10C);
RdUpdateDataPtr1:
Skip[NoCarry];
RdcDataPtr1←(RdcDataPtr1)+(400C)+1;*Increment high order.
RdTask:
RETURN;
RdSetupDriveHead:
*Get DriveHead ready to select the drive and head we are about to access. Drive is in the two low order bits of DiskAddressPtr. DiskAddressPtr points to CSB.diskAddress[drive]. Head is in HeadSector in bits 14-17.
T←LDF[RdcHeadSector,0,10];*Head is in bits 14-17.
T←(LSH[RdcDiskAddressPtr,4]) OR T;*Drive is in bits 12-13.
RdcDriveHead←T,
RETURN;
RdPrimeIdata:
*This subroutine sets MemBuffAdr to the value in T and primes the Controller by Output to PrimeIdata. The instruction following the Call[RdPrimeIdata] must do an interlock of Temp to insure that the Output has been completed. We do not do the interlock before the return so as not to hold up the CPU unnecessarily.
Output[RdcTemp,RdcMemBuffAdr];*Set MemBuffAdr.
Output[RdcTemp,RdcPrimeIdata];*Start the Controller.
*The sequence Output, Output must not be followed within four microinstructions by a task switch, so do not task. See <WD0>D0Gotchas number 19.
UseCTask, GoTo[RdTask];*Return
*This subroutine updates CurrentSector so we know what sector is about to go by.
RdUpdateCurrentSector:
LU←(RdcDiskStatus) AND (RdcSector0);*Test for sector 0.
RdcCurrentSector←(RdcCurrentSector)+1,*Update current disk sector.
Skip[ALU=0];*Skip if not sector 0.
RdcCurrentSector←0C, RETURN;
RETURN;
*ERRORS COME THROUGH HERE.
RdHeaderIOAtten:
*Come here if we got IOAtten in state DataTransfer just after reading the Header. If the error was due to ServiceLate, we will retry if the retry error counter has not yet gone negative; otherwise we report the error.
RdcCompletionCode←RdcHeaderError;*Set error code
RdTestError:
*CompletionCode is HeaderError, DataError, or LabelError.
*This input instruction must not follow a memory instruction.
Input[RdcDiskStatus,RdcStatus],*Get Status from Controller.
Call[RdTask];
Output[RdcTemp,RdcErrorReset];
LU←(RdcDiskStatus) AND (RdcErrorBitsLow);
LU←(RdcDiskStatus) AND (RdcServiceLate),
Skip[ALU=0];
GoTo[RdReportError];*If BufErr, RdErr, WriteFault, or Ofault
*Continue if not BufErr, RdErr, WriteFault, or Ofault. Test for ServiceLate.
LU←(RdcDiskStatus) AND (RdcRateError),
GoTo[RdServiceLate,ALU#0];*If ServiceLate
*Continue if not ServiceLate. Test for RateError.
T←(RdcIOCBptr)+(RdcIOCBrateErrorRetryCount),*Set up to fetch RateError retry count.
GoTo[RdTestRetry,ALU#0];*If RateError.
*Continue here if no error bits are set in the Status word. This could be a label verify error or a header verify error. If this is a label error and no error bits are set, change the status to labelCheck.
LU←(RdcCompletionCode)-(RdcLabelError);
GoTo[RdReportError,ALU#0];*If not labelCheck
RdcCompletionCode←RdcLabelCheck;
RdReportError:
*Come here if an error is detected. Store -1 in CSB.deferring. This will cause us to stop processing IOCBs until the Poll procedure sets deferring back to zero.
RdcDeferring←(zero)-1;*Deferring = -1
PStore1[RdcCSBptr,RdcDeferring,RdcCSBdeferring!];
GoTo[RdStoreErrorStatus];*Note that this Goto can not be combined with the PStore1 because Gotcha #6 requires a non-memory-ref inst following the Pstore to allow RdcDeferring to be written and RdStoreErrorStatus starts with a memory-ref inst.
RdServiceLate:
Pfetch1[RdcCSBptr,RdcTemp,RdcCSBserviceLateCount!]; *To count servicelates during performance tests.
Call[RdTask];*Task and allow time for fetch
RdcTemp←(RdcTemp)+1;*Count ServiceLates.
Pstore1[RdcCSBptr,RdcTemp,RdcCSBserviceLateCount!]; *Save.
T←(RdcIOCBptr)+(RdcIOCBserviceLateRetryCount);*Set up to fetch ServiceLate retry count.
RdTestRetry:
*Come here if ServiceLate or RateError. Fetch the retry count from the IOCB. Retry count+1 times. This means if the count is zero, we will retry once. Decrement this count, update it in the IOCB, and test for negative. If negative, we give up; let TheHead figure out what to do. If the retry counter is not negative, we retry when the sector comes around again.
OddPfetch1[RdcZeroBase,RdcTemp],*Fetch retry count.
Call[RdTask];*Task and allow time for fetch
RdcTemp←(RdcTemp)-1,*Decrement retry count.
GoTo[RdRetry,R>=0];
*Come here if retry count has been exceeded. This is a breakpoint label.
RdErrorCountExceeded:
GoTo[RdReportError];
RdRetry:
*Retry the command the next time the sector comes around.
Nop;*Allow write of RdcTemp.
OddPstore1[RdcZeroBase,RdcTemp];*Update retry count.
*We can’t simply Goto[RdIdle] because this would fetch the OperationClientHeader which hasn’t been kept up during a run of pages.
PFetch2[RdcIOCB16ptr,RdcCylinder,0],*Get ClientHeader.
GoTo[RdRetryReEntry];*Retry
RdDataIOAtten:
*IOAtten in state RdcData, after the data transfer.
RdcCompletionCode←RdcDataError,*Error occurred in the data field.
GoTo[RdTestError];
ON PAGE[RdcInitPage];
*This is the RDC initialization code. This is throw-away code that lives in a separate page from the RDC microcode.
*Redefine temporary registers for initialization.
RV[RdcReg0, ADD[RdcRegBase,0]];
RV[RdcReg1, ADD[RdcRegBase,1]];
RV[RdcReg2, ADD[RdcRegBase,2]];
RV[RdcReg3, ADD[RdcRegBase,3]];
RdcInit:
*Store zero into CSBnext, CSBdeferring, CSBtail, and CSBtransferMask. On entry, R0 points to CSB.
T←RdcReg0, AT[RdcInitLoc];
RdcCSBptr←T;*Set Controller Status Block address.
RdcCSBzeroBase←0C;
RdcReg0←0C;
RdcReg1←0C;
RdcReg2←0C;
RdcReg3←0C;
Pstore1[RdcCSBptr,RdcReg0,RdcCSBserviceLateCount!]; *Supports counting servicelates during performance test.
RdcZeroBase←0C;
RdcIOCB16ZeroBase←0C;
T←(RdcCSBptr)+(RdcCSBnext);
OddPstore4[RdcZeroBase, RdcReg0];
RdcReg0←(zero)-1;
RdcReg1←(zero)-1;
RdcReg2←(zero)-1;
RdcReg3←(zero)-1;
T←(RdcCSBptr)+(RdcCSBdiskAddress);*T points to disk addresses in CSB.
RdcDiskAddressPtr←T;*Set RdcDiskAddressPtr to point to CSB.diskAddress[drive 0].
RdcDiskAddressZeroBase←0C;
OddPstore4[RdcZeroBase,RdcReg0];*Set all disks to recalibrate.
*Set CurrentSector to a value higher than any sector. This will cause us to start counting at sector 0.
RdcCurrentSector←1000C;
Output[RdcReg0, RdcGeneralReset];*Reset the Controller.
Output[RdcZeroBase, RdcDrive/Head];*Set Controller disk and head to zero.
RdcCommand←RdcAllowWake;
Output[RdcCommand, RdcDevOp];*Send Allow Wake to Controller.
LoadPage[RdcPage];
GoToP[RdIdle];

END[RigidDiskController];

:ELSE; *******************************************
TITLE[No.RDC.SA4000.microcode];
:ENDIF; ******************************************

%
LOG

October 19, 1979 6:07 PM: Fix label verify recalibration problem. We were recalibrating after any label verification error.

The first major revision to this code was to change all IOFetch16s to IOFetch4s. The IOFetch16 was causing a word to be duplicated on the disk and pushing every other word in the page down one word, pushing the last word on the page off the end. (This is the famous Redell Syndrome). The Controller must have a new word ready for the disk every 2.2 microseconds. During the IOFetch, the Controller’s buffer is locked out, and a new word cannot be transferred to the holding register. If enough branch burps cause the IOFetch16 to exceed the 2.2 microsecond time limit, a new word cannot be loaded into the holding register, and the previous word is duplicated.

October 14, 1979 4:34 PM: The new RDC bug has the following symptoms: Four word blocks of data were not getting preloaded into the Controller’s buffer. On all write operations, 32 words of data were being loaded into the Controller’s buffer after the Command had been sent to the Controller. Sometimes 8, 12, or 20 words of this preload data would not get loaded into the buffer, and the data from the previous write operation would get written to the disk page. The Controller did not report any error.

Chuck Thacker suggested the following solution to the problem: Preload the buffer
before sending the command to the Controller. I observed some interesting "features" about the Controller when I tried this:

1. It is not a good idea to preload the buffer with header and label data if the command is not write or verify. If the command is a read operation, this causes ServiceLate to occur about 20 percent of the time. Because of this, we only preload the buffer for write or verify operations.

2. If we preload the buffer with 32 words of data, we get spurious IOAtten from the Controller in the header field. No error bits are set in the Status word, and the header data is correct (i.e., it is not a verification error). If we only preload 16 words, we do not get this spurious IOAtten.

3. If the command is verify label, read data, we must set MemBuffAdr to zero right after sending the command to the Controller; Otherwise we get spurious IOAtten in the label field, i.e., no error bits are set and there is no verify error.

4. If we preload the buffer for a write operation, we must not set MemBuffAdr to zero after we execute the command. This causes IOAtten in the header field. In fact, this may be the cause for the whole problem we were having. Once in a while, maybe MemBuffAdr was getting set to zero before all of the data had been sent to the Controller.
%