:IF[WithRDC]; ************************************
INSERT[RDCDefs];
*RDCDefs.Mc also used by SA4000Loader.Mc
TITLE[RDC];
*Rigid disk controller

%Ed Fiala 3 June 1983: Change branch at RdDataTransfer+3 so that command
setup is the same irrespective of whether or not the previous page had a
write-data operation (saves 5 mi). Moved 1 mi in RdcInit to the end of the
routine to increase the delay after the final Output. Reformatted whole
file according to my conventions. Bum 1 mi at RdSectorWait+2; 1 mi at
RdWriteData2+1; 1 mi at RdReadData2+1; 1 mi at RdHeadSettle2+2; 1 mi at
RdServiceLate+1; 1 mi at RdRecal+1. Reformat and absorb RDCDefs.Mc.
Save 2 cycles at RdIdle; Remove task at RdIdle+1.
Ed Fiala 3 November 1982: Bum 1 mi at RdTestHeaderWrite2+3.
Ed Fiala 2 February 1982: more Output interlocks and save 4b mi.
Ev Neely March 23, 1981: Stepper settling in Recal.
Ev Neely November 4, 1980: Fix Retry.
Ev Neely October 24, 1980: Fix RecalSeekLimit.
Jim Frandeen September 22, 1980: Rearrange Output instructions to fix the
Worst Gotcha Ever.
Jim Frandeen September 2, 1980: Change DoInt to NotifyInterrupt.
Ev Neely June 30, 1980: Changes in support of 48 bit processor-ID and
performance improvement. Makes use of new PackedLabels prepared by head.
Works with changed IOCB format.
Ev Neely June 19, 1980: Count servicelates for performance tests.
Jim Frandeen June 18, 1980: Update comment at StoreStatus.
Ev Neely June 13, 1980: 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.
Jim Frandeen March 17, 1980: Fix for new Rdc board. Fix for MP 733 caused
by instruction sequence IOFetch20, Output, followed by task within four
mi; or Output, Output followed by task within four mi.
Jim Frandeen February 19, 1980: Complete overhaul to help display jitter.
Jim Frandeen January 25, 1980: Get address of CSB from register zero.
Jim Frandeen January 21, 1980: Fix bug on label fixup for runs of pages.
Make changes for new D0Lang.
Jim Frandeen December 26, 1979: for runs of pages.
This version does not attempt to lock out task zero.
%

SetTask[RdcTask];
*currently 10b
OnPage[RdcPage];

%Enter or continue the Idle state. Wait for TheHead to chain a new IOCB onto
the CSB. TheHead is our link to TheOutsideWorld. When TheOutsideWorld
wants to execute a disk command, it calls TheHead, which then constructs an
Input Output Control Block (IOCB) and chains it onto the Controller Status
Block (CSB). At wakeup, count sectors and see if there is anything to do.
%
RdIdle:
IOStrobe, Call[RdTask];*Terminate the wakeup.

%Resume here on the next sector wakeup, which occurs at the end of every data
sector. 1383 sectors go by every second, so a wakeup occurs every 723
microseconds. See if an IOCB has been chained onto the CSB. Fetch four
CSB words:
NextIOCB points to the next IOCB, if any;
Deferring will be -1 if processing must be deferred until TheHead has
handled an error;
third word unused;
TransferMask will be OR’ed into NWW during the NotifyInterrupt call at the
end of the command ("NotifyInterrupt" is in Initialize.Mc and NWW is
interpreted at the label "NopInt" in MesaOP0.Mc and at the label
"Interrupt" in MesaP.Mc).

When the Controller turns on our wakeup latch, the sector mark is only 56
bytes away (~60 microseconds). If the header shows up before we send a
command, we will get IOAtten and ServiceLate and have to wait another disk
revolution. TheOutsideWorld will not be pleased!

At the same time, the RDC must task often enough to satisfy the UTVFC
(display controller), which must wakeup four times per 28.8 microseconds.
Since the UTVFC task is higher priority than the RDC task, RDC tasking
requirements are similar to those of the emulator. Comments in Display.Mc
and Fault.Mc discuss timing issues. Essentially, tasking should
occur about every 2 to 3 microseconds (20 to 30 cycles), averaged over
several wakeups. If tasking is too infrequent, the display will flicker,
keystrokes will be missed or inserted, and the OutsideWorld will be pissed!
%
Input[RdcDiskStatus,RdcStatus];
PFetch2[RdcCSBptr,RdcNextIOCB,RdcCSBnext!], Call[RdUpdateCurrentSector];
T ← RdcNextIOCB;*T points to next IOCB

*IOCBptr points to the next IOCB and Deferring contains -1 if we must defer
*processing until Poll reports an error to the client.
RdNextIOCB:
LU ← RdcDeferring, Skip[ALU#0];
GoTo[RdIdle];*Nothing to do

*T points to the new IOCB. Fetch the new disk address from the IOCB’s
*OperationClientHeader into RdcCylinder and RdcHeadSector. Continue the
*idle loop if deferring.
OddPFetch2[RdcZeroBase,RdcCylinder], GoTo[RdIdle,ALU<0];

*Continue here if Deferring is not negative. Set up IOCB base registers.
RdcIOCBptr ← T;*1st 16d words
RdcIOCB16Ptr ← T;
RdcIOCB16Ptr ← (RdcIOCB16Ptr) + (20C);*2nd 16d words

*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.
RdRetryReEntry:
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];

*Well, this is obstacle number one. The drive must be recalibrated before
*doing any commands. 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.
T ← RdcCurrentCylinder, GoTo[RdRecal,R<0];*If recal required.

*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
*Test for fixed heads. Jump if CurrentCylinder#Cylinder
LU ← (LdF[RdcHeadSector,4,1]), GoTo[RdSeek,ALU#0];
RdDiskArmPositioned:
LU ← LdF[RdcCommand,10,10];*Test for seek only
*Skip if not seek only
RdcSectorTimeOutCount ← RdcSectorTimeOutWakeUps, Skip[ALU#0];
RdcCompletionCode ← RdcGoodCompletion, GoTo[RdStoreStatus];
*T = sector specified by command.
T ← LdF[RdcHeadSector,10,10], Call[RdTask];

*Here we are ready to send a command to the Controller. 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 = sector specified by command. Jump if not sector time out
T ← LdF[RdcHeadSector,10,10], GoTo[RdSectorWait,ALU#0];
*Continue if the sector has timed out.
RdcCompletionCode ← RdcSectorTimeOut, GoTo[RdReportError];

*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 cause another ServiceLate. We must interlock after the
*Output before a task switch. Sleep until the next sector wakeup.

RdSectorWait:
Output[RdcZeroBase,RdcErrorReset];
RdcZeroBase ← RdcZeroBase, IOStrobe, Call[RdTask];*Sleep.

*Continue at the next sector wakeup.
Input[RdcDiskStatus,RdcStatus], Call[RdUpdateCurrentSector];
LU ← (RdcCurrentSector) xor T, GoTo[RdTestSector];

*Loop back here on a multi-page transfer after 38 cycles + 1 run by another
*task. NOTE: The drive and head must not be sent too soon on a multi-page
*transfer with a data write. The sector wakeup occurs at the beginning of
*writing the ECC. ECC will not be completed until 4.5 microseconds after the
*wakeup. If the drive/head is changed during this time, the data on that
*sector will be unreadable.

*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.

RdDataTransfer:
RdcDataPtr1 ← (LSh[RdcDataPtr1,10]) or T;
Output[RdcZeroBase,RdcMemBuffAdr];*Zero MemBufAdr.
*Transfer 20 words from the IOCB starting at RdcIOCBclientHeader which was
*16-word aligned at RdcIOCB16ptr so that we could send the header and label
*info with an IOFetch16.
IOFetch16[RdcIOCB16ptr,RdcOutput,0], Call[RdTask];
LU ← (RdcCommand) and (RdcDataWriteOrVerify);
*Assume no seek after this transfer. Skip if not write or verify.
RdcNewCylinder ← 0C, Skip[ALU=0];
*Another IOFetch16 to preload the first 16d data words if data operation is
*write or verify.
IOFetch16[RdcDataPtr,RdcOutput,0];
Output[RdcDriveHead,RdcDrive/Head];*Send drive and head
Output[RdcCommand,RdcDevOp];*Send Command.
*Test for end of track.
LU ← (RdcCurrentSector) xor (RdcSectorsPerTrack);
*Increment sector and jump if not new head.
RdcHeadSector ← (RdcHeadSector) + 1, GoTo[RdTestHeaderWrite,ALU#0];
*Next Sector is zero. We will increment by one below.
RdcCurrentSector ← (Zero) - 1;
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.
*Increment head and jump if not end of cylinder.
RdcHeadSector ← (RdcHeadSector) + (400C), GoTo[RdNewHead,ALU#0];
RdcHeadSector ← Zero;*HeadSector←zero.
RdcCylinder ← (RdcCylinder) + 1;*Increment cylinder.
*Set up to seek one cylinder in the positive direction next time.
RdcNewCylinder ← (Zero) - 1;
RdNewHead:
Call[RdSetupDriveHead];*Set DriveHead for new head.
*Test for writing headers
LU ← (RdcCommand) and (RdcWriteHeader), GoTo[RdTestHeaderWrite2];

RdTestHeaderWrite:
LU ← (RdcCommand) and (RdcWriteHeader);*Test for writing headers
*If we are writing headers, we are done until the next sector wakeup. Then,
*we will check to see if we got any errors. Terminate this wakeup and sleep.
*Increment filePageLo and jump if writing headers
RdTestHeaderWrite2:
RdcFilePageLo ← (RdcFilePageLo) + 1, GoTo[RdWaitForEndTransfer,ALU#0];
*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 sleep until the
*header field on the disk shows up. The header field contains the address of
*the next sector.
IOStrobe, Call[RdTask];*Terminate wakeup & task

*Continue here when the header field is in the Controller’s buffer. Transfer
*the header into the IOCB. Zero MemBuffAdr and start the Controller.
RdcTemp ← 0C, Call[RdPrimeIdata];
RdcTemp ← RdcTemp;*Interlock for PrimeIdata
*We want to store the header information into two IOCB words at
*RdcIOCBdiskHeader; but we must use a IOStore4 to a quadword boundary and
*header info begins at RdcInput+2. So store 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.
*If IOAtten from the Controller, there’s trouble! Our command was possibly
*too late. Sigh!
IOStore4[RdcIOCBptr,RdcInput,RdcIOCBdeviceStatus!], GoTo[RdHeaderIOAtten,IOAtten];

*We made it past the header.
LU ← (RdcCommand) and (RdcLabelReadOrVerify);*For test at EndLabel
Skip[ALU#0];*Skip if label read or verify
LU ← (RdcCommand) and (RdcDataWriteOrVerify), GoTo[RdTestDataAction];
*Come here to read or verify label. Issue IOStrobe to cancel the Header
*field wakeup and 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 sleep.
*Set MemBuffAdr to 6 and start the Controller
RdcTemp ← 6C, Call[RdPrimeIdata];
RdcTemp ← RdcTemp;*Interlock for PrimeIdata.
*Read the first four label words into the IOCB and terminate the wakeup.
IOStore4[RdcIOCB16Ptr,RdcInput,AND[17,RdcIOCBdiskLabel!]], Call[RdStrobe];

*Continue after the second label field wakeup. Read the second four label
*words into the IOCB. This way os storing the second half of the diskLabel
*saves two base registers.
T ← (RdcIOCBPtr) + (40C);
OddIOStore4[RdcZeroBase,RdcInput];
LU ← (RdcCommand) and (RdcDataWriteOrVerify), Skip[IOAtten’];
RdcCompletionCode ← RdcLabelError, GoTo[RdTestError];
*If write or verify data, GoTo WriteData; on data read, GoTo ReadData;
*otherwise skip data entirely.
RdTestDataAction:
LU ← (RdcCommand) and (RdcReadData), GoTo[RdWriteData,ALU#0];
GoTo[RdReadData,ALU#0];*If read data
GoTo[RdWaitForEndTransfer];*Skip data altogether.

*Write or verify data. Terminate the wakeup.
RdWriteData:
RdcTemp ← 40C, Call[RdStrobe];*to set MemBuffAdr

*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).
Output[RdcTemp,RdcMemBuffAdr];
RdcTemp ← 15C;*Set loop counter to n-2.
RdcDataPtr ← (RdcDataPtr) + (20C), Call[RdUpdateDataPtr1];

*Repeat the following loop 17b (15d) times: Transfer 20b (16d) words to the
*Controller buffer; then issue IOStrobe to terminate the wakeup. Continue
*at the next data wakeup. Temp will be -1 the last time through the loop.
*Use four IOFetch4’s instead of one IOFetch16 to protect against a code
*sequence in which, due to too many branch burps, the 16d cycles of transport
*for the IOFetch16 extend longer than the 2.2 microseconds at which the disk
*falls behind; this would cause an undetected error. On a 100 nsec/cycle
*machine, this error would occur only if the 8 microinstructions concurrent
*with IOFetch16 transport include 4 or more true branch conditions.
*I am not aware of any sequence like that in any module, but it seems prudent
*to guard against it.

RdWriteData2:
IOFetch4[RdcDataPtr,RdcOutput,0];
IOFetch4[RdcDataPtr,RdcOutput,4], Call[RdDataPtrPlus10];
IOFetch4[RdcDataPtr,RdcOutput,0], Call[RdTask];
IOFetch4[RdcDataPtr,RdcOutput,4], Call[RdDataPtrPlus10AndStrobe];
*Continue here after the next data wakeup. Temp will be -1 the last time
*through the loop.
RdcTemp ← (RdcTemp) - 1, GoTo[RdWriteData2,R>=0];

*Continue here on the 16th wakeup. Ignore by issuing IOStrobe and sleep. The
*next wakeup is equivalent to sector wakeup.
GoTo[RdWaitForEndTransfer];

*Come here for data field read operation. Sleep.
RdReadData:
Call[RdStrobe];

*Continue here on the first data field wakeup. Set MemBufAdr to 21B. Do
*PrimeIData to start the controller.
RdcTemp ← 21C, Call[RdPrimeIdata];*Set MemBuffAdr to 21
RdcTemp ← 16C;*Set loop counter to n-2 and Interlock.
*Repeat the following loop 16d times: Read 16d words and sleep.
RdReadData2:
IOStore4[RdcDataPtr,RdcInput,0];
IOStore4[RdcDataPtr,RdcInput,4], Call[RdDataPtrPlus10];
IOStore4[RdcDataPtr,RdcInput,0], Call[RdTask];
IOStore4[RdcDataPtr,RdcInput,4], Call[RdDataPtrPlus10AndStrobe];
RdcTemp ← (RdcTemp) - 1, GoTo[RdReadData2,R>=0];

*Fall through after the 17th wakeup. The last wakeup was the end of the read
*and the next sector wakeup.
*Increment the current sector. (It will be -1 if the current sector is zero).
RdcCurrentSector ← (RdcCurrentSector) + 1, GoTo[RdEndTransfer];

*Wait for next sector wakeup.
RdWaitForEndTransfer:
RdcCurrentSector ← (RdcCurrentSector) + 1, Call[RdStrobe];

*Wakeup after the data transfer.
RdEndTransfer:
LU ← RdcIncrementDataPtrFlag, GoTo[RdRestoreDataPtr,R<0];
*Continue if DataPtr is not be to incremented. Refetch DataPtr from the IOCB.
PFetch2[RdcIOCBPtr,RdcDataPtr,RdcIOCBdataPtr!], GoTo[RdTestCompletion];

*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.
*Wait one instruction before testing IOAtten
RdRestoreDataPtr:
RdcDataPtr1 ← RSh[RdcDataPtr1,10];
RdTestCompletion:
*Assume good completion
RdcCompletionCode ← RdcGoodCompletion, GoTo[RdDataIOAtten,IOAtten];

*Come here at command completion. CompletionCode is GoodCompletion. Update
*all fields that have to do with runs of pages: clientHeader, filePageLo in
*clientLabel, pageCount, and DataPtr. Zero all flags in the clientLabel
*except for bootFile.

*Store the calculated next disk address(2 words) in IOCB’s RDC.clientHeader.
*The two unused words following RDC.clientHeader allow a PStore4, which is
*almost 3 times faster than a PStore2.
RdGoodCompletion:
PStore4[RdcIOCB16ptr,RdcCylinder,0], Call[RdTask];
*Update FileFlags and FilePageLo in the IOCB’s PackedClientLabel. Four words
*of PackedClientLabel were fetched so that this fast PStore4 could be used.
PStore4[RdcIOCB16ptr,RdcFileFlags,AND[17,RdcIOCBFileFlags!]];
*Decrement page count.
RdcPageCount ← (RdcPageCount) - 1;
*Allow write of PageCount while preserving ALU for conditional GoTo.
FreezeResult;
*Update Data Pointer and pageCount in the IOCB.
*Page count zero means end of run, go store status.
PStore4[RdcIOCBPtr,RdcDataPtr,RdcIOCBdataPtr!], GoTo[RdStoreStatus,ALU=0];
*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];*Skip if not new cylinder
GoTo[RdSeekPos];
T ← (RdcDataPtr1) + 1, GoTo[RdDataTransfer];*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 point at 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 mi of LoadPage (D0Gotcha number 1).
PStore1[RdcCSBptr,RdcNextIOCB,RdcCSBnext!];
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
*mi so that RdcCompletionCode will be written before the store.

*Now we call NotifyInterrupt 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. NotifyInterrupt smashes registers 0 and 1 of the task that calls.
LoadPage[NotifyInterruptPage];
T ← RdcTemp, CallP[NotifyInterrupt];*Bits to OR into NWW
T ← RdcNextIOCB, GoTo[RdNextIOCB];*Go process the next IOCB

*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.
RdDriveChange:
RdcSectorTimeOutCount ← RdcDriveChangeTimeWakeUps, GoTo[RdIdle];

*Continue here at the next sector wakeup. See if device is ready.
RdWaitForDriveReady:
LU ← (RdcDiskStatus) and (RdcDevSelOK);*Check device selected
*Skip if drive is not yet ready.
RdcSectorTimeOutCount ← (RdcSectorTimeOutCount) - 1, Skip[ALU=0];
GoTo[RdTestForSeek];*DevSelOK=1
*Here if the disk is not yet operational (DevSelOK=0). Check for timeout.
Skip[ALU=0];*Skip 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];

%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 prevents 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 300d (454b), comfortably more than the
maximum number of negative one track seeks.
%
RdRecal:
RdcRecalSeekCount ← HiA[RdcRecalSeekLimit];
RdcRecalSeekCount ← (RdcRecalSeekCount) or (LoA[RdcRecalSeekLimit]);
LU ← (RdcDiskStatus) and (RdcTrack0);
*Reentry point after each negative one track seek.
RdRecal2:
RdcRecalSeekCount ← (RdcRecalSeekCount) - 1, GoTo[RdTrack0,ALU#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];*Skip 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];

*We are at track 0; now seek to the desired cylinder unless it was 0.
RdTrack0:
LU ← RdcCylinder;
RdcHeadSettleCount ← RdcHeadSettlingTimeWakeUps, Skip[ALU#0];
*If desired cylinder = 0, wait one revolution for heads to settle.
RdcCurrentCylinder ← 0C, GoTo[RdHeadSettle];
RdcCurrentCylinder ← 0C;
*If desired cylinder was not 0, 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.
RdStepperSettle:
Call[RdStrobe];*Wait for next sector wakeup.

RdcHeadSettleCount ← (RdcHeadSettleCount) - 1;
GoTo[RdStepperSettle,ALU#0];*If stepper not settled.
T ← RdcCurrentCylinder, GoTo[RdTestForSeek];

*Come here if CurrentCylinder # Cylinder. Seek, unless the address is for
*one of the fixed heads. Head address is in bits [0..7] of HeadSector.
*Bits[0..3] are unused. Bit 4 will be nonzero if fixed heads.
*LU=(LdF[RdcHeadSector,4,1])
*T = CurrentCylinder-Cylinder. Jump if not fixed heads
RdSeek:
T ← (Zero) - T, GoTo[RdSeekNeg,ALU=0];
GoTo[RdDiskArmPositioned];*If fixed heads
RdSeekNeg:
*Set seek command for negative seek.
RdcSeekCommand ← RdcSeek-D, GoTo[RdSeekNeg2,ALU>=0];
RdSeekPos:
*Positive direction seek if CurrentCylinder > Cylinder.
RdcSeekCommand ← RdcSeek+D, GoTo[RdSeekLoop];

RdSeekNeg2:
T ← (Zero) - T;*Seek counts up instead of down.
*Send seek commands to Controller. T = minus number of tracks to seek.
*This crazy machine can add one to T, but it can’t subtract one from T, so we
*count up instead of down.
RdSeekLoop:
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.
*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.
*Jump if we need to send another seek command.
RdcSectorTimeOutCount ← RdcSeekTimeOutWakeUps, GoTo[RdSeekLoop,ALU#0];
*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.
*Now wait for the seek to complete. Sleep and continue here at each sector
*wakeup. Check the Status word at each wakeup until SeekComplete appears.
RdSeekWait:
Call[RdStrobe];

Input[RdcDiskStatus,RdcStatus], Call[RdTask];
LU ← (RdcDiskStatus) and (RdcSeekComplete);*Check seek complete
*Continue if seek has not yet completed. See if seek has timed out.
RdcSectorTimeOutCount ← (RdcSectorTimeOutCount) - 1, GoTo[RdSeekComplete,ALU#0];
GoTo[RdSeekWait,ALU#0];*Jump if seek has not timed out.
*A seek time out error has occurred.
RdcCompletionCode ← RdcSeekTimeOut, GoTo[RdReportError];

*Seek has completed. See if this was a one step seek for recalibrating.
*Jump if not recalibrating
RdSeekComplete:
LU ← RdcCurrentCylinder, GoTo[RdHeadSettle,R>=0];
*Seek complete and recalibrating. See if we are at track 0 yet.
LU ← (RdcDiskStatus) and (RdcTrack0), GoTo[RdRecal2];

*Come here when the seek has completed. Now wait for the heads to settle at
*the new cylinder. All that seeking was a shock to their little sensors.
*Sleep and count sector wakeups until we think the heads have settled down.
*We have lost our place on the disk. However, 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.
RdHeadSettle:
Nop;*Placement constraint
RdHeadSettle2:
*Prepare to update CurrentCylinder
T ← RdcCylinder, Call[RdStrobe];

*Wait for next sector wakeup.
Input[RdcDiskStatus,RdcStatus], 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. Update CurrentCylinder.
RdcCurrentCylinder ← T, GoTo[RdDiskArmPositioned];

*This subroutine terminates the wakeup and sleeps 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.
RdStrobe:
IOStrobe, GoTo[RdTask];

RdDelayAndTask:
GoTo[RdTask];

RdDataPtrPlus10AndStrobe:
IOStrobe;
RdDataPtrPlus10:
RdcDataPtr ← (RdcDataPtr) + (10C);
RdUpdateDataPtr1:
Skip[Carry’];
RdcDataPtr1 ← (RdcDataPtr1) + (400C) + 1;*Increment high order.
RdTask:
Return;

*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.
RdSetupDriveHead:
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;

*This subroutine sets MemBuffAdr to the value in T and primes the Controller
*by Output to PrimeIdata. The instruction following the Call[RdPrimeIdata]
*must interlock Temp to ensure that the Output has completed. We do not
*interlock before the return so as not to hold up the CPU unnecessarily.
RdPrimeIdata:
Output[RdcTemp,RdcMemBuffAdr];*Set MemBuffAdr.
Output[RdcTemp,RdcPrimeIdata];*Start the Controller.
*The sequence Output, Output must not be followed within four mi by a task
*switch, so do not task.
UseCTask, GoTo[RdTask];*Return

*Update CurrentSector so we know what sector is about to go by.
RdUpdateCurrentSector:
LU ← (RdcDiskStatus) and (RdcSector0);*Test for sector 0.
RdcCurrentSector ← (RdcCurrentSector) + 1, Skip[ALU=0];
RdcCurrentSector ← 0C, Return;
Return;

*ERRORS COME THROUGH HERE.

*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.
RdHeaderIOAtten:
RdcCompletionCode ← RdcHeaderError;*Set error code

*CompletionCode is HeaderError, DataError, or LabelError.
*This input instruction must not follow a memory instruction.
RdTestError:
Input[RdcDiskStatus,RdcStatus], 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];
*Continue if not ServiceLate. Test for RateError.
T ← (RdcIOCBptr) + (RdcIOCBrateErrorRetryCount), GoTo[RdTestRetry,ALU#0];

*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;
*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.
RdReportError:
RdcDeferring ← (Zero) - 1;*Deferring = -1
PStore1[RdcCSBptr,RdcDeferring,RdcCSBdeferring!];
*Note that this Goto cannot be combined with the PStore1 because a
*non-memory-ref must follow the PStore to allow RdcDeferring to be written,
*and RdStoreErrorStatus starts with a memory-ref mi.
GoTo[RdStoreErrorStatus];

RdServiceLate:
*Count servicelates during performance tests.
PFetch1[RdcCSBptr,RdcTemp,RdcCSBserviceLateCount!], Call[RdTask];
RdcTemp ← (RdcTemp) + 1;
PStore1[RdcCSBptr,RdcTemp,RdcCSBserviceLateCount!]; *Save.
*Set up to fetch ServiceLate retry count.
T ← (RdcIOCBptr) + (RdcIOCBserviceLateRetryCount);
*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.
RdTestRetry:
OddPFetch1[RdcZeroBase,RdcTemp], Call[RdTask];*Fetch retry count.
RdcTemp ← (RdcTemp) - 1, GoTo[RdRetry,R>=0];*Decrement retry count.
*Come here if retry count has been exceeded. This is a breakpoint label.
RdErrorCountExceeded:
GoTo[RdReportError];

*Retry the command the next time the sector comes around.
RdRetry:
Nop;*Allow write of RdcTemp.
OddPStore1[RdcZeroBase,RdcTemp];*Update retry count.
*We can’t simply Goto[RdIdle] which would fetch the OperationClientHeader
*which hasn’t been kept up during a run of pages.
*Get ClientHeader and retry.
PFetch2[RdcIOCB16ptr,RdcCylinder,0], GoTo[RdRetryReEntry];

*IOAtten in state RdcData, after the data transfer.
RdDataIOAtten:
RdcCompletionCode ← RdcDataError, GoTo[RdTestError];

*This is the throw-away RDC initialization code which lives in a separate
*page from the RDC microcode.

OnPage[RdcInitPage];

*Redefine temporary registers for initialization.
RV4[RdcReg0,RdcReg1,RdcReg2,RdcReg3,Add[RdcRegBase,0]];

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;
*Supports counting servicelates during performance test.
PStore1[RdcCSBptr,RdcReg0,RdcCSBserviceLateCount!];
RdcZeroBase ← 0C;
RdcIOCB16ZeroBase ← 0C;
T ← (RdcCSBptr) + (RdcCSBnext);
OddPStore4[RdcZeroBase,RdcReg0];
RdcReg0 ← (Zero) - 1;
RdcReg1 ← (Zero) - 1;
RdcReg2 ← (Zero) - 1;
RdcReg3 ← (Zero) - 1;
*T points to disk addresses in CSB.
T ← (RdcCSBptr) + (RdcCSBdiskAddress);
*Set RdcDiskAddressPtr to point to CSB.diskAddress[drive 0].
RdcDiskAddressPtr ← T;
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.
RdcDiskAddressZeroBase ← 0C;
LoadPage[RdcPage];
GoToP[RdIdle];

END[RDC];
: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.

2 June 1983: The ServiceLates referred to in (1) above no longer seem to
happen, so I restored the single treatment of header and label data
irrespective of the command.
%