{File name: DiskDlionBT.mc
Description: Dandelion Disk Controller microcode, Version 2.0
Author: D. Davies
Created: October 30, 1979
Last Edited: November 19, 1979 4:19 PM
Last Edited: November 19, 1979 9:09 PM
Last Edited: November 21, 1979 2:55 PM, "minimum code" version completed
Last Edited: January 9, 1980 8:09 PM, create "transfer run of pages command"
Last Edited: February 1, 1980 10:48 AM, complies with standard Dandelion register assignments in Dandelion.df
Last Edited: March 14, 1980 11:14 AM, renamed to SA4DiskDlion from DiskDlion so a new version of the
microcode may be produced for the SA1000/SA4000 controller.
Last Edited: March 18, 1980 10:07 AM, update to Rev D CP board which cancels MDR← following an automatic
PgCrossBr. Extra code now inserted to make sure last word in page is stored anyway.
Last Edited: April 11, 1980 1:50 PM, to comply with the new standards for the composite SA1000/SA4000 controller which
has longer label fields (code overhead reduced).
Last Edited: April 18, 1980 3:07 PM, take out features that depend on IgnorePgCr.
Last Edited: April 30, 1980 11:06 AM, perform combined test on Header Field.
Last Edited: May 16, 1980 4:19 PM, add extra delay before Read or Verify on SA1000 drive to account for extra delay
after writing a field on that drive.
Last Edited: May 22, 1980 3:27 PM, add extra two pattern words for SA1000 drive to account for extra delay added above.
Last Edited: June 11, 1980 7:13 PM add extra word to sync pattern for SA4000 drive to try to decrease intermitent errors.
Decrease length of sync pattern on SA1000 byone word to make room for 10 word labels eventually.
Last Edited: June 18, 1980 8:19 PM, modify to work with SA4000HeadDLion.
Last Edited: June 25, 1980 12:48 PM, fix ancient Inr bug and delete absolute starting address.
Last Edited: June 27, 1980 2:29 PM, fix ancient bug in FinishIOCB.
Last Edited: June 30, 1980 4:40 PM, reduce code overhead in an attempt to allow 10 word labels.
Last Edited: June 30, 1980 10:29 PM, reduce code overhead further and ensure that read headers and labels are not incremented.
Last Edited: July 1, 1980 1:12 AM, show final Sector Count after a transfer command.
Last Edited: July 1, 1980 12:46 PM, detect memory error in midst of run of pages.
Last Edited: July 2, 1980 9:34 AM, complement KStatus for new HSIO board.
Last Edited: July 1, 1980 4:51 PM, split into DiskDlionA.mc and DiskDlionB.mc
Last Edited: July 2, 1980 5:30 PM, fix allocation bug in FinishIOCB that might go crazy if 2 IOCBs on chain.
Last Edited: July 8, 1980 10:41 PM, turn off interrupts as an experiment.
Last Edited: July 9, 1980 12:46 PM, turn them back on, had no effect.
Last Edited: July 15, 1980 5:08 PM, increase sync pattern len for SA1000 to try to correct errors with new (Shugart) clock.
Last Edited: July 28, 1980 2:47 PM, decrease sync pattern len to try and fit 12 word labels.
Last Edited: September 12, 1980 11:03 AM, fix ancient bug than caused "sector count" to be updated during non-transfer
commands.
Last Edited: October 7, 1980 5:17 PM, tighten code, discard code to detect page cross in an IOCB.
Last Edited: October 22, 1980 5:54 PM, take out MDR← not preceeded by MAR←.
Last Edited: November 4, 1980 10:57 AM, Trap illegal instructions.
Last edited by Jim August 20, 1981 2:20 PM: Fix for new assembler.
}
{
THIS CODE WILL NOT RUN PROPERLY UNLESS DiskDlionInit HAS INITIALIZED REGISTERS U0C00, UMaxSectTst, UStatusMsk and USyncAddrMk!!}

{ First commands and main body of the TransferRun command in DiskDlionA.mc. }

{--------------------------- Subroutine used to process a field of a sector -----------------------------------
This subroutine is used to read, write or verify a field in a sector. Its arguments are:
1) Word Count (~= length of field, definition may change depending on the operation) in UWdCount
2) Control word - the word sent to control the hardware when processing the CRC wd, in RAdr and
3) Buffer address - the location in physical memory of the first word of the buffer for the field to be transferred, in RHRCnt and RCnt.
The least significant two bits of the control word define the operation. These bits are WakeupControl.1 and WriteEnable for the controller, WriteEnable is the LSB. 00 => Read operation, 10 => Verify operation, 11 => Write operation. The initialization section of the code primes the data word with 0 in case this is a write op, saves the control word used to process the CRC residue and creates the transfer control word (the WriteCRC bit is off). After this, two branches are taken, one for write, the other for read and verify. The write code starts writing the PLL synchronization pattern (zeros) immediately, followed by the synchronization word (all ones) or address mark and the data. A sync word of all ones is used for an SA4000 drive. The address mark 50A1 is used for the SA1000 Header field, 50A3 is used for the Label and Data fields. The read and verify code waits until the PLL synchronization pattern is under the read head, then issues the transfer control word to start things up. The verify operation requires that the first word in the buffer be sent to the hardware before starting so this is done. The verfiy command uses the inner loop of the write code to write data to the controller to be verified. The read command has its own inner loop. When all data has been processed in all three commands, the wait command is sent to cause the CRC word to be processed, then the Freeze command is sent to stop the hardware and the next command is fetched from the IOCB.}
TransferField: RAdr ← RAdr or UFreezeCmd,
c1; {create the real command word}
UWaitCmd ← RAdr,c2; {save control word used for CRC residue}
RAdr ← RAdr and ~CWrCRC,c3; {create the transfer command}

UXferCmd ← RAdr, YDisp,c1; {save transfer command, is this a write op?}
KCtl ← UFreezeCmd, DISP4[SetupRdVer,0E],c2; {either start PLO sync pattern or prime for verify.
make sure microcode wakeup=FirmwareEnable
so we can clear the error flags (if Wakeup =
SectorFound, ClrKFlags wil abort the Wakeup!).
Clear the error flags so a delayed verify error
flag from the CRC word of a previously verified
field will not give a spurious error indication here}

{Setup the read and verify operations. The data separator on the SA4000 requires that the read head be enabled after the beginning of the synchronization pattern. The controller data separtor for the SA1000 does not care when it is enabled. The Read and Write microcode process in the same amount of time, so we may derive the position of the read head relative to the sector mark using the current location in the code. After the branch above, the write routine immediately begins writing the synchronization pattern and determining the address mark or sync word to be written. One might think delaying one click here would ensure the read head was over the synchronization pattern. Unfortunately, the position of the read head relative to the executing code is only known to within one click, so a delay of one click gives a real delay of zero to two clicks. By delaying two clicks here, we guarantee at least a one click delay. During the delay, the address of the data buffer and the word count are loaded. Since the verify operation needs to have the first data word ready as soon as the hardware finds the sync word, it is sent to the hardware here. This action has no effect if this is a read operation. Finally, the operation is chosen and starts. The read code adjusts the buffer pointer to point to the first word in the buffer so the first data word will be written properly. Note the value of word count depends of the operation performed. For the verify operation, it equals the number of words in the field minus one. For the Read operation, it equals the number of words in the field.}
SetupRdVer:
ClrKFlags,c3, at[0E,10,SetupWrt]; {clear error flags (see
above)}

{Wait for the read head to be in position while fetching the word count and adjusting the address so we can subtract in the MAR← below so Cin=0 so we can read a U register.}
Noop,c1;
RAdr ← UWdCount,c2; {get word count for read or verify}
RCnt ← RCnt+1,c3; {adjust the address now
so it will be set up after priming the
controller for the verify op,
yet allows us to subtract below.}

{Now have waited two clicks, decide what this operation is (read or verify) and prime the controller with the first data word if it’s a verify. Note the start command is sent before the first data word. This is necessary because the the controller’s WriteData register is cleared as long as TransferEnable is lo.}
MAR ← [RHRCnt, RCnt-1], Xbus ← UXferCmd, XDisp,c1; {start pre-load and see whether this is a read
(X.14=0) or verify (X.14=1). Note the only
legal values for RCnt here are xx15, xx17 and
xx01. RCnt will not generate a PgCross for
any of these.}
KCtl ← UXferCmd, DISP4[StartRd, 0D],c2; {start the operation and chose the operation}

{Start the verify operation. Since the first word hs already been sent, send the control word and test to see if there is any thing to verify}
StartVer:
KOData ← MD, GOTO[WrtVerLp],c3, at[0F,10, StartRd]; {prime controller with first
word to be verified AFTER WriteData clear input
has been released above. Start verifying}

{Start the Read operation. We sent the control word in this click and will start the next click with a memory reference using the restored buffer pointer. The controller is frozen with the status a of the CRC word after the buffer is filled.}
StartRd:
RAdr ← RAdr-1,c3, at[0D,10,StartVer]; {do not incr word count
to account for sync word read}

{start the ref to bring in the first data word}
StartRdC1:
MAR ← [RHRCnt, RCnt-1], RCnt ← RCnt-1, GOTO[ReadLpC2],c1, at[1,2]; {start read into 1st loc in buffer
point to first loc so first data word is
written over the sync word or addr mk.}

{-------- Read Command Inner Loop -------------}
{During each click, a word is read from the disk and written into main memory. The word count is decremented and if greater than zero, reading continues. When it reaches zero, we jump to freeze the controller with the final status. The last MAR← is not used as it is begun in response to the CRC word being read in.}
ReadLp:
MAR ← [RHRCnt, RCnt], RCnt ← RCnt+1, BRANCH[ReadLpC2,FinRead], c1; {start mem write cycle}
ReadLpC2:
MDR ← KIData, WriteOK, CANCELBR[ReadLpC3, 2],c2, at[0,2,FinRead]; {write disk data to mem}
ReadLpC3:
RAdr ← RAdr-1, ZeroBr, GOTO[ReadLp],c3; {dcr word cnt, go start memory cycle, note
the WriteOK allows the MDR← to proceed
even though the assembler fears a PgCross
might be generated. In fact, this cannot happen
because we jump out of the loop before the
final increment that would cause the PgCross}

{Here the controller has the second to last word ready. The address now points to the last word, so there could not have been a
PageCross on the last MAR← so this MDR← will work. This is all incredibly ugly.}
FinRead:
MDR ← KIData, WriteOK, CANCELBR[FinReadC3, 2],c2, at[1,2,ReadLpC2]; {store the second to last
word in the field}
FinReadC3:
RAdr ← 1, ZeroBr,c3; {set up for branch below}

{Store the last word using a special MAR← that does not generate a page carry. This is done so the MDR← will not be cancelled. This is also used to freeze the controller after the last word.}
FinReadLp:
MAR ← [RHRCnt, RCnt+0], BRANCH[RdLastWd, FreezeRead],c1; {start store of last word}
RdLastWd:
MDR ← KIData,c2, at[0,2,FreezeRead]; {store field’s last word}
RAdr ← RAdr xor RAdr, ZeroBr, GOTO[FinReadLp],c3; {set up ZeroBr to write last word}

{Now the controller has just read and checked the CRCword. Freeze the controller now.}
FreezeRead:
KCtl ← UFreezeCmd, pRet4, GOTO[SndFreeze],c2, at[1,2,RdLastWd]; {stop the controller with the
proper status and return}

{------- Set up Write Command --------------------}
{Each field in a sector begins with 6 words of zeros used by the disk’s data separator to lock its PLL, a word of ones or an address mark to define the word boundry, the field’s data and a CRC residue word. the first thing this routine does is begin writing the PLL sync pattern. The controller automatically begins writing a word of zeros. Because of the controller design, the second word of zeros will actually be being written when the first service request arrives. Thus to write 6 words of zeros, we send sync words 4 times. While sending the Sync words, we calculate the address mark or synchronization word to be sent to define the word boundry. The SA4000 requires a synchronization word of all ones. The Header field of an SA1000 disk requires an address mark of 50A1 hex. The Label and data fields of the SA1000 drive need an address mark of 50A3 hex. After writing the proper sync word or addres mark, we load the word count and start the inner loop to transfer data. At the end of the transfer, the Wait Command is sent out, causing the CRC word to be written, we wait one click for it to be written, then freeze the hardware. Finally, the IOCB pointer is reloaded and execution proceeds to fetch the next command.}
SetupWrt:
KCtl ← UXferCmd,c3, at[0F,10,SetupRdVer]; {send control wd}

ClrKFlags, RAdr ← 4, GOTO[SyncLpC2],c1; {Clear error flags left over from previous
fields (see comment just after TransferField).}

{loop to write initial word of synchronization pattern, all zeros. Note the branch cannot be in C1 as that would mean the test would be in C3, but that conflicts with the KOData← there.}
SyncLp:
Noop,c1, at[0,2,FinSync];
SyncLpC2:
RAdr ← RAdr-1, ZeroBr,c2; {dcr sync count}
KOData ← 0, BRANCH[SyncLp,FinSync],c3; {send sync word, done yet?}

{Now calculate the sync word or addres mark in the time remaining. A sync word is used for SA4000 drives and is composed of all 1s. An address mark is used on SA1000 drives. The Header field address mark is 51A1’X. An address mark for the Label or Data field is 50A3’X. USyncAdrMk contains either FFFF or 50A1, depending on the drive type. If the field is Label or Data, this is ORed with a "2". The result is sent to the controller at the right time, the Word Count is loaded and the inner loop entered.}
FinSync:
RAdr ← UField,c1, at[1,2,SyncLp]; {UField=40 => Header,
=80 => Label, =C0 => Data}
[] ← RAdr+RAdr, PgCarryBr, SuppressTimingWarning,c2; {carry => Label or Data field being written, so
turn on the tag bit}
KOData←0, BRANCH[SetupHd, SetupLabDat],c3; {continue sending sync pattern and decide
about tag bit}

{Sending Header Field, so don’t insert the "Label or Data" tag}
SetupHd:
RAdr ← 0, GOTO[MakeSyncAdrMk],c1, at[0,2,SetupLabDat];
{Sending Label or Data field, do insert the "Label or Data" tag bit}
SetupLabDat: RAdr ← CLabDatTag,
c1, at[1,2,SetupHd];
{Form the Sync word or Address mark by ORing the tag bit in. Obviously, this has no effect on the Sync word, only on the address mark}
MakeSyncAdrMk: RAdr ← RAdr or USyncAdrMk,
c2; {compose the sycn wd or addr mark}
KOData ← RAdr LRot0,c3; {Lo and behold, now its time to send it, so
do so}

{Start the first memory reference and load the word count before jumping into the inner loop}
MAR ← [RHRCnt, RCnt], RCnt ← RCnt+1,c1; {start read of first word from memory}
RAdr ← UWdCount, CANCELBR[InitWrtC3, 2],c2; {get word count}
InitWrtC3:
KOData ← MD, RAdr ← RAdr-1,c3; {decrement it for the word just sent to the
disk and enter the inner loop}

{-------------- inner loop for write and verify operations ------------------ }
{In each cycle, read a word fromthe buffer and write it to the disk. }
WrtVerLp:
MAR ← [RHRCnt, RCnt], RCnt ← RCnt+1,c1, at[0,2,FinWrtVer]; {start mem read}
WrtVerLpC2:
RAdr ← RAdr-1, ZeroBr, CANCELBR[WrtVerLpC3, 2],c2; {dcr word ct, done yet?}
WrtVerLpC3:
KOData ← MD, BRANCH[WrtVerLp, FinWrtVer],c3; {send data to controller, quit if done}

{Done writing or verifying data, send wait command. Either 2 or 3 repetitions of the Wait command are to be sent. Two repititions are sufficient if the SA4000 drive is connected or a verify operation is in progress. The controller has hardware to hold the write enable signal whilel the SA4000data separator grinds out the CRC word. It does not seem to be effective when using lthe SA1000 drive. Un fortuately, there is no time to wait the extra word when using the SA4000 drive, so thecases cannot be consistent. One should not wait when executing a verify operation as the CRC word would pass by and its status would be lost. Hence, we test to see if both this is a write operations and we are connected to the SA1000 disk before delaying an extra click.}
FinWrtVer:
Xbus ← UWaitCmd, XDisp,c1, at[1,2,WrtVerLp]; { Is this a write operation?}
RAdr ← RAdr+1, Xbus ← KStatus, XwdDisp, BRANCH[FinVerify, FinWrite, 0E], c2; {loop ctr ← 1, is the SA1000 connected?}

FinVerify:
KCtl ← UWaitCmd, CANCELBR[SndWait, 0F],c3, at[0, 2, FinWrite]; {Verify op, process the CRC
word and start waiting}
FinWrite:
KCtl ← UWaitCmd, BRANCH[SA1Wait, SA4Wait, 2],c3, at[1,2, FinVerify]; {Write op, process the CRC
word and decide whether to wait an extra click}

SA1Wait:
GOTO[SndWaitC2],c1, at[0,2, SA4Wait]; {connected to SA1000, wait
an extra clock by not decrementing loop ctr}
SA4Wait:
RAdr ← RAdr-1, NegBr, GOTO[SndWaitC2],c1, at[1,2, SA1Wait]; {connected to SA4000, loop
ctr ← 0 so executes SndWaitC3 only once.}

SndWait:
RAdr ← RAdr-1, NegBr,c1, at[0F,10]; {have enough clicks passed yet?}
SndWaitC2:
BRANCH[SndWaitC3, SndFreeze], pRet4,c2; {set up return in case and
decide whether to stop controller now}
SndWaitC3:
KCtl ← UWaitCmd, CANCELBR[SndWait, 0F],c3, at[2,4,SndFreeze]; {have controller verify
or write CRC word or wait until it is done.}

{Now stop the hardware and return. The Read routine joins the flow here with a CANCELBR[SndFreeze,2]}
SndFreeze:
KCtl ← UFreezeCmd, RET[HeaderRet],c3, at[3,4,SndWaitC3]; {stop the hardware and
return. Note that during a write operation the
last 4 bits of the CRC may still be in the SA4000’s
data separator. They will not be lost because
the WriteEnable seen by the disk is delayed
by 5 bit times.}


{
Finish IOCB
This command is used to finish up an IOCB. It posts the final status, interrupts a Mesa process and either goes to the dormant state or continues processing with the next IOCB. The command word is followed by the 16 bit virtual address link to the next IOCB in the chain (the link is 0 if this is the last IOCB), a 16 bit mask used to interrupt a Mesa process ,the command used to turn off the hardware if IOCB processing is to stop now and the address (lo 16 bits thereof) of the status location in the IOCB. The first action taken is to fetch and store the arguments to the command. Then we replace the HeadSelected field in the status received from the hardware with the Field number now in UField. This allows the Mesa processing to determine the number of the last field processed. The error bits from the hardware are then replaced with the error bits left from the last Test Status command or those fro the last Transfer Run of Pages command, whichever came last. The Sector Found bit is left from the original device status if the controller is connected to an SA4000. If connected to an SA1000, this bit is masked off as it would be set only if a Header field was sought but not found. It is assumed the SA4000HeadDLion head will have preceeded the Finish IOCB command with either a Test Status , Transfer or Initializer Registers command so as to eliminate irrelevant error flags. Next, three conditions are tested. If either there was a memory error during the IOCB, any errors were detected or the link to the next IOCB is nil, the FirmwareBusy flag is cleared in the final status. If none of these conditions are true, is is set there. This is used by the Device Head to decide whether the microcode will continue or stop. If a new IOCB was added to the chain, the Device Head can determine whether the microcode saw it and if not, the Head can restart the microcode. After the final IOCB status has been posted, the interrupt mask is used to set the interrupt flags, causing the Head to receive an interrupt from the emulator. Finally, based on whether the FirmwareBusy flag was set or not, the microcode either issues the stop control word found as the second to last argument to this command and goes to the dormant state or it goes to map the virtual IOCB address given to a real IOCB address and continue with the next IOCB.}
{fetch the Mesa interrupt mask from the argument list}
FinishIOCB:
MAR ← [RHRAdr, RAdr], RAdr ← RAdr+1,c1, at[0E,10,Inr]; {start fet and point to stop
command}
UNxtIOCBLnk ← RCnt, CANCELBR[$, 2],c2; {save link to next IOCB, was fetched as
part of command fetch.}
RCnt ← MD,c3; {get mesa interrupt mask}

{fetch the stop command from the argument list}
MAR ← [RHRAdr, RAdr], RAdr ← RAdr+1,c1; {Start fet and point to status addr}
UInterruptMsk ← RCnt, CANCELBR[$, 2],c2; {save Mesa interrupt mask}
RCnt ← MD,c3; {get stop command}

{finally, fetch the address of the IOCB status word}
MAR ← [RHRAdr, RAdr+0],c1; {start fet of last word in IOCB}
UFreezeCmd ← RCnt,c2; {Save stop command}
RAdr ← MD,c3; {get phys addr of status, hold in RAdr}

{Decide whether to store the final sector count. this should be done only if there was a transfer operation in the IOCB. If not, trust the Head to put an "Initialize" op in the IOCB. Hence, UField will be CIOCBDone <=> There was no transfer.}
RCnt ← CIOCBDone,c1; {get valuse used to test}
[] ← RCnt xor UField, ZeroBr,c2; {did a transfer? Zero => no}
RCnt ← USectorCntAddr, BRANCH[StoSectCnt, ComposeStat],c3; {get addr of sector count in case and
decide.}

{Store the final count of sectors left to process. This will be zero if there were no errors.}
StoSectCnt:
MAR ← [RHRAdr, RCnt+0],c1, at[0,2,ComposeStat]; {Start store of final
sector count, note it’s on same page as
final status since all in same IOCB.}
MDR ← USectorCount,c2; {store it}
Noop,c3;

{clear spurious error flags from the current status and combine it with the masked status. This will make up a major portion of the final status word.}
ComposeStat: RCnt ← ~UStatusMsk,
c1, at[1,2,StoSectCnt]; { get raw hardware status}
RCnt ← RCnt and ~KStatus,c2; {clear the HeadSelected field so the number of
the current field may be inserted there. also clear
the FirmwareBusy bit and the SA1000/SA4000
bit. If this is an SA1000, clear the HeaderTag bit.}
RCnt ← RCnt or UStatus,c3; {combine hardware and masked status to form
most of proper status word.}
{now fetch the current Field number so it can be OR’ed in. It contains the number of the current field being processed in the sector.}
RCnt ← RCnt LRot8,c1; {swap bytes to get at HeadSelected field}
RCnt ← RCnt or UField,c2; {insert field number in to status}
RCnt ← RCnt LRot8,c3; {Re-align the hardware status}

{There are three tests that must be passed for FirmwareBusy to be set in the status word, there must have been no memory error, no processing errors and the Next IOCB link may not be null.}
Xbus←MStatus, XDisp,c1; {test for memory error}
Ybus ← UStatus , NZeroBr, DISP4[MemOK,7],c2; {Test status word, was there a memory error?}

MemError:
RCnt ← RCnt or CMemError, CANCELBR[LastIOCB, 1],c3, at[0F, 10, MemOK]; {mem failed, set flag,
make sure Branch at AnotherICOB goes to
ResetFirmwareBusy and quit}
MemOk:
Ybus ← UNxtIOCBLnk, ZeroBr,BRANCH[AnotherIOCB, LastIOCB],c3, at[7,10,MemError]; { test the next ICOB link
and decide whether ther were processing errors}

{There was either a memory or processing error so just start the memory cycle for the write of the status, leave FirmwareBusy cleared.}
LastIOCB:
MAR ← [RHRAdr, RAdr+0], CANCELBR[ResetFirmwareBusy,1],c1, at[1,2,AnotherIOCB]; {start store of
final Status, no more IOCBs to be executed.}
{Had no memory or processing errors, may not be another ICOB though. Start the memory write cycle to set the IOCB status decide whether to set FirmwareBusy or not..}
AnotherIOCB: MAR ← [RHRAdr, RAdr+0], BRANCH[SetFirmwareBusy, ResetFirmwareBusy], c1, at[0,2,LastIOCB];

ResetFirmwareBusy: MDR ← RCnt, RHRAdr ← 0, GOTO[GetIntrMask],
c2, at[1,2,SetFirmwareBusy]; {this is the Last
IOCB to be done now,
update final status andsave totest later}
SetFirmwareBusy: MDR ← RCnt +0FF+1, RHRAdr ← 0FF,
c2, at[0,2,ResetFirmwareBusy]; {Continue on after
this ICOB with the next one - set Firmware Busy
in the saved status and in a bit to be tested
below.}

{In either case, write the final status to the IOCB and save it so we can test it after sending the Mesa Interrupt}
GetIntrMask:
RCnt ← UInterruptMsk,c3; {get mesa interrupt mask}

{In one
atomic operation, set the interrupt mask}
RAdr← uWP,c1; {get the emulator’s mask}
RAdr ← RAdr or RCnt,c2; {or in the new interrupt flags}
uWP ← RAdr, MesaIntRq,c3; {Update the mask for the emulator and notify
emulator its been changed}

{Will another IOCB be processed or do we quit now? If FirmwareBusy (now in RHRAdr.7) is 0, quit. Otherwise proceed. Quitting is done by writing the control word following the Mesa interrupt mask to the controller and jumping to the Dormant state. Proceeding is done by loading the virtual link address into RAdr and going to map it to a real address.}
[] ← RHRAdr, XDisp,c1; {Test FirmwareBusy}
RHRAdr ← 0, DISP4[QuitNow,0E],c2; {IOCB virt addr has high bits=0}

{Quit after this IOCB}
QuitNow:
KCtl ← UFreezeCmd, GOTO[ShugartStart],c3, at[0E,10,QuitNow]; {stop hardware and go
dormant}

{Proceed with next IOCB in the chain}
GetNextIOCB: RAdr ← UNxtIOCBLnk, GOTO[NewIOCB],
c3, at[0F,10,QuitNow]; {get virtual addr of new IOCB
and go map it}

{this is a command used to set the UStatus register with the current status and the UField register to a known value (CIOCBDone). This command is used to ensure a valid, non-zero status when a tranfer operation was not performed.}
InitRegs:
RCnt ← RCnt and ~KStatus,c1, at[0F,10, Inr];
UStatus ← RCnt,c2;
RAdr ← CIOCBDone,c3;

UField ← RAdr, GOTO[FinLdReg],c1; {This also load CIOCBDone into
UHeaderQuitMsk, but that should be ok.}