{File name: DiskBootDlion.mc
Last Edited: by Dan Davies February 17, 1982 10:18 AM: Eliminate unnecessary instructions, put in track crossing code.
Last Edited: by Amy Fasnacht December 21, 1981 4:01 PM: Delete StartAddress
Last Edited: by Jim Frandeen August 21, 1981 10:45 AM: change for new assembler
Last Edited: by D. Davies February 10, 1981 3:52 PM
Last Edited: by Jarvis December 5, 1980 4:29 PM
Description: Booting code for Dandelion Disk Controller microcode, Version 2.0
Author: D. Davies
Created: September 2, 1980 2:00 PM
Dennis Grundler: 2-Sep-84 15:43:59, add copyright notice.
}

{
Copyright (C) 1980, 1981, 1982 by Xerox Corporation. All rights reserved.}

{

This code is used to read pages from the disk starting at sector 0 of some track. It assumes the necessary U registers are set up and the only operation needed is "Read".}

Set[L5.DiskStart, 7];
Set[L5.LabelWait,0];
Set[L5.DataWait,1];
Set[L5.SetupRd,2];
Set[L5.EndRead1,3];
Set[L5.EndRead2,4];

SetTask[4];

{----------------------------------- Start of Code ----------------------------------------}

{Initially, the microcode is in the dormant state. Upon awakening, it waits for SeekComplete, then finds the Index mark. Following this, it either waits for 27 sector marks to pass (SA4000) or starts reading immediately (SA1000).}

DiskStart:
KCtl ← dbUSeekCompleteWaitCmd, CANCELBR[$, 0F],c1, at[L5.DiskStart,10,WaitRets]; {Wait for
SeekComplete and DriveSelect}
RHRCnt ← 0,c2, at[0F,10]; {set hi addr of Header,
Label and Data buffers, Initialize status
flags.}
Xbus ← KStatus, XwdDisp,c3; {connected to SA1000 or SA4000?}

{get here when SeekComplete is true => Heads have settled and drive is selected. Wait for the Index mark and either proceed directly (SA1000) or wait until 27 sector marks have gone by (SA4000). The SA4000 wait is needed because the transfer of each sector on the SA4000 is proceeded by a wait for the next index mark. Thus in order to start on sector 0, we must count our way around the disk so when the transfer is started, the next sector mark belongs to sector 0. The next sector mark encountered after the index mark is found will be the one for sector 1.}
KCtl ← dbUFindIndexMkCmd, BRANCH[FindSA1Sect0, FindSA4Sect0,2],c1; {next click starts after index mark, do we
start read immediately or wait for sector
27?}
{SA4000 is connected, so wait for sector 27. This allows us to initialize registers etc then start reading right on sector 0}
FindSA4Sect0: RCnt ← dbUSectorsPerTrack,
c2, at[3,4,FindSA1Sect0]; {load number of sectors
in this track. Since this number is
decremented
after the index mark and each sector mark
thereafter, it reaches 0 during the sector just
before sector 0 arrives again.}
ClrKFlags,c3; {find a new index mark, not an old latched
one.}

{We have now found the Index mark. Wait for Sector 27 to come around.}
Sect27Lp:
RCnt ← RCnt-1, ZeroBr,c1; {Found sector 27 yet?}
ClrKFlags, BRANCH[AnotherSect, FirstSector],c2; {no=>wait some more for a new Sector
mark, yes => start reading}
AnotherSect: KCtl ← dbUFindSectMkCmd, GOTO[Sect27Lp],
c3, at[0,2,FirstSector]; {next wakeup on Sector
mark. It is not really necessary to send this
command more than once.}

{Found sector 27, start the sector zero}
FirstSector:
KCtl ← dbUFreezeCmd, GOTO[NewSector],c3, at[1,2,AnotherSect];

{This code is used to find sector 0 on an SA1000 disk. It is done by verifying each field as it comes along. When the Header for sector 0 is found, we immediately start processing the Label for sector 0 below. Note the entry point for this code is at FindSA1Sect0, not at FindSA1Sect0Lp. Note also that the first word to be verified in the Header is the cylinder number, which is 0000. Since the hardware data register is cleared between commands, this value need not be pre-loaded.}
FindSA1Sect0Lp: KCtl ← dbUVerifyCmd,
c*{3}, at[0,2,Wait2]; {start looking for a
Header field}

VerifyLp:
RAdr ← RAdr-1, ZeroBr,c1; {have we sent all the words needed to
verify a Header yet?}
RCnt ← CVerifyErrMsk, BRANCH[MoreVerify, FinVerify],c2; {load mask for verify error flags and decide
if done yet}
MoreVerify:
KOData ← dbUHeadSect, GOTO[VerifyLp],c3, at[0,2,FinVerify]; {not done, send the Head-
Sector word. This is needed the first time we
execute this instruction and is adequate to
keep the board happy the rest of the time.}
FinVerify:
KCtl ← dbUFreezeCmd, L5 ← L5.LabelWait,c3, at[1,2,MoreVerify]; {Done with verify of
Header so freeze the hardware with the final
status.}

RCnt ← RCnt and ~KStatus, ZeroBr,c1; {Was that the Header for sector 0?}
FindSA1Sect0: RAdr ← 4, ClrKFlags, BRANCH[FindSA1Sect0Lp, Wait2],
c2, at[2,4,FindSA4Sect0]; {load loop count, turn
off old error flags and decide if we have
found sector 0 or not. The first time this
is executed, no tests were performed in
c1 so we always take the FindSA1Sect0Lp
branch. When sector 0 has been found,
wait two cycles and process the Label field.}

{This code is used to read the Header, Label and Data fields of a run of sectors on the disk. A run of sectors occupies consecutive sectors on consecutive tracks. It is assumed that the booting code has correctly loaded all necessary U registers. All sectors will be read regardless of whether an error is found or not. The Logical OR of all error flags will be left in dbUStatus. If no error is encountered, dbUStatus is left with zero. See the definitions file for the regular disk microcode (DiskDlion.df) for an definition of the error bits.
It is assumed that no run of pages may cross a cylinder boundry. In this version of the disk boot code, runs may cross track boundries. No check is made for cylinder boundries, the code will increment the track numbers indefinately.
Processing begins by loading the parameters for processing a header and finding the first sector mark. The Header field is then processed and the results tested using first the HeaderQuitMsk. Any status bits indicated by dbUHeaderQuitMask and ORed into dbUStatus. Note one of the bits tested when connected to an SA1000 drive should be the HeaderTag/SectorFound bit. This is set by a bit in the address mark of the Label and Data fields. It acts an error bit when looking for Header fields. Regardless of whether the status passes this test, parameters are loaded for the label field and it is processed. Its status is masked with dbULabelDataQuitMask and ORed into dbUStatus also. The process is repeated for the Data section. One difference between the Header and Label fields on the one hand and the Data field on the other is that the same two buffers are used to process the Header and Label fields of every page while each page requires a new data buffer. Real addresses of the string of data buffers are found by incrementing the page numbers in UDataPgNum. It is assumed that the contigous string of real memory pages is always in the first 64 K of real memory. After the status resulting from processing the Data field is masked by dbULabelDataQuitMask and ORed into dbUStatus, one sector has been processed. Before decrementing the number of pages left in the run, the number of pages left in the current track is decremented. If it becomes zero, the Head number is incremented in both command words where it appears (dbUFreezeCmd, dbUFindSectMkCmd). No test is made to see if the maximum head number has been exceeded as it is assumed this will never happen. After this, the number of pages left in the run is decremented and we either continue with the next page or stop.}

NewSector:
RCnt ← dbUHeaderAddr, ClrKFlags,c1; {start hardware looking
for a header, clear SectorFound flag}
RAdr ← CHeaderLen, pCall4,c2; {get control wd for Header field}
KCtl ← dbUFindSectMkCmd, CALL[TransferField],c3, at[0,10]; {start transferring as soon as the
sector mark is found.}

HeaderRet:
RCnt ← dbUHeaderQuitMsk,c1, at[0,10,HeaderRet]; {get mask for Header
status bits.}
RCnt ← RCnt and ~KStatus, L5 ← L5.LabelWait,c2; {select meaningful status bits.}
dbUStatus ← RCnt or RAdr, CALL[Wait4],c3; {add them to current status (RAdr was
loaded with dbUStatus in TransferField).
Wait 4 cycles before starting the Label field.
This preserves the timing of the normal disk
code.}

{Here, the header was processed successfully, we proceed with the Label field. This is done by loading its parameters, setting the partial status to know the field being processed and calling TransferField}
DoLabel:
RAdr ← CLabelLen, pCall4,c2, at[L5.LabelWait, 10, WaitRets]; {get control
word for label field}
RCnt ← dbULabelAddr, CALL[TransferField],c3, at[1,10]; {go transfer the label field. }

{Return here after processing the label field}
LabelRet:
RCnt ← dbULabelDataQuitMsk,c1, at[1,10,HeaderRet]; {get Label status mask}
RCnt ← RCnt and ~KStatus, L5 ← L5.DataWait,c2; {Select pertinent status bits}
dbUStatus ← RCnt or RAdr, CALL[Wait4],c3; {record them and wait for start of data
field.}

{The Header and Label are done, set up to do the Data field. }
DoData:
RAdr ← dbUDataAddr,c2, at[L5.DataWait,10,WaitRets]; {get the
real addr of data buffer for this sector}
RCnt ← RAdr,c3; {RCnt ← real address.}

RAdr ← RAdr+0FF+1,c1; {increment the data pg number ptr}
dbUDataAddr ← RAdr, pCall4,c2; {update the the address of the data buffer}
RAdr ← CDataLen+1, CALL[TransferField],c3, at[2,10]; {go transfer the data}

{Return here after processing the data field. Test to see if there was a fatal error in processing it}
DataRet:
RCnt ← dbULabelDataQuitMsk,c1, at[2,10,HeaderRet]; {get msk specifying fatal
errors in the data field}
RCnt ← RCnt and ~KStatus,c2; {select the pertinent status bits}
dbUStatus ← RAdr or RCnt,c3; {record them and go see if done with track.}

{At least one sector has been successfully transferred and more may follow. The pointer to the next real memory page was incremented when it was used above.}
NextSector:
RCnt ← dbUSectorsLeftInTrack,c1; {load numberof sectors left in this track}
RCnt ← RCnt - 1, ZeroBr,c2; {decrement it, done with this track?}
dbUSectorsLeftInTrack ← RCnt, BRANCH[SectorTest, NewHead],c3; {update page count, either update Head num
in commands or check for done now.}

NewHead:
RCnt ← dbUHeadIncr,c1, at[1,2,SectorTest];
RAdr ← dbUFreezeCmd,c2; {get present freeze command}
RAdr ← RAdr + RCnt,c3; {set the new head number}

dbUFreezeCmd ← RAdr,c1; {update Freeze Command}
RAdr ← dbUFindSectMkCmd,c2; {get command used to find sector mk}
RCnt ← RAdr + RCnt, KCtl ← dbUFreezeCmd,c3; {set the new head number and send new head
number to hardware so it can settle.}

dbUFindSectMkCmd ← RCnt,c1; {update Find Sector Mark command}
RCnt ← dbUSectorsPerTrack,c2; {set up to reset the number of sectors per
track}
dbUSectorsLeftInTrack ← RCnt,c3;


{One sector has been successfully processed. Was that the last sector? Decrement the sector count and find out.}
SectorTest:
RCnt ← dbUSectorCount,c1, at[0,2,NewHead]; {get old # remaining sects}
RCnt ← RCnt-1, ZeroBr,L5 ← L5.DiskStart, c2; {get new # sectors left, are we done?}
dbUSectorCount ← RCnt, BRANCH[NewSector, DoneQuit],c3; {update count, quit if all done}

{This is the exit taken if all pages were transferred sucessfully}
DoneQuit:
KCtl ← 0, CALL[Wait2],c1, at[1,2,NewSector]; {turn off disk and prepare
to start again though this isn’t necessary in
the current booting sequence.}

{--------------------------- Subroutine used to process a field of a sector -----------------------------------
This subroutine is used to read a field in a sector. Its arguments are:
1) Word Count (~= length of field) in RAdr
2) Control word - the word sent to control the hardware when processing the CRC wd, in dbUReadCmd 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 initialization section of the code inserts the Head number into the command andsaves it away. The read code waits until the PLL synchronization pattern is under the read head, then issues the transfer control word to start things up. When all data has been processed, the Freeze command is sent to stop the hardware and we return to the calling code. It seems silly to insert hte Head number into the field command for each field since the command is always the same. However, we must use the same amount of time as is used by the regular disk code in order to preserve the relationsship between the instruction being executed and the position of the read heads relative to the disk. Since this must be done anyway, we might just as well insert the had number here where it’s free than elsewhere.}
TransferField: dbUWdCount ← RAdr,
c1; {Save the word count while creating the
current command in RAdr}
RAdr ← dbUReadCmd,c2; {get the command used for all fields, all heads}
RAdr ← RAdr or dbUFreezeCmd, KCtl ← dbUFreezeCmd,c3; {insert the head number. make sure microcode
wakeup=FirmwareEnable so we can clear the
error flags (if Wakeup = SectorFound,
ClrKFlags will 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}

dbUFieldCmd ← RAdr, ClrKFlags, L5 ← L5.SetupRd,c1; {save transfer command, reset old flags}
RAdr ← dbUWdCount, CALL[Wait5],c2; {restore the word count and wait for start
of this field.}

{Setup the read operation. 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. The write routine in the regular code immediately begins writing the synchronization pattern and determining the address mark or sync word to be written. This code mimics the timing of the regular code so we may also derive the disk position from the current instruction being executed. 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. }
SetupRd:
KCtl ← dbUFieldCmd,c2, at[L5.SetupRd, 10, WaitRets]; {start the
read operation}

{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, L5 ← L5.EndRead1,c3; {magic decrement to make things come out
right. We go through the loop for WdCount-2
data words plus one synchronization word, or
WdCount-1 times in all.}

{start the ref to bring in the synchronization word}
StartRdC1:
MAR ← [RHRCnt, RCnt+0], GOTO[ReadLpC2],c1; {start read into 1st loc in buffer.
Point to first loc so first data word is
written over the sync word or addr mk that
will be written as a result of this MAR←.}

{-------- 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 read in the lasttwo words and freeze the controller with the final status. this awkward ending is made necessary by the fact that the MDR← is cancelled when a page cross is detected. This will happen on the last word of every data buffer.}
ReadLp:
MAR ← [RHRCnt, RCnt], RCnt ← RCnt+1, BRANCH[ReadLpC2,FinRead], c1; {start mem write cycle}
ReadLpC2:
MDR ← KIData, LOOPHOLE[wok], 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.}
FinRead:
MDR ← KIData, LOOPHOLE[wok], L5Disp, CANCELBR[Wait1, 2],c2, at[1,2,ReadLpC2]; {store the second to last
word in the field}

{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.}
MAR ← [RHRCnt, RCnt+0], L5 ← L5.EndRead2,c1, at[L5.EndRead1,10,WaitRets]; {start store
of last word}
MDR ← KIData, CALL[Wait2],c2; {store field’s last word}

{Now the controller has just read and checked the CRCword. Freeze the controller now.}
KCtl ← dbUFreezeCmd, pRet4,c2, at[L5.EndRead2,10,WaitRets]; {stop the
controller with the proper status}
RAdr ← dbUStatus, RET[HeaderRet],c3; {Prepare to record status and return}


{Here is the Wait ro
utine}
Wait5:Noop,c*;
Wait4:
Noop,c*;
Wait3:
Noop,c*;
Wait2:
L5Disp,c*, at[1,2,FindSA1Sect0Lp];
Wait1:
RET[WaitRets],c*;

{End}