*-----------------------------------------------------------
Title[AltoDiabloDisk.mc...January 27, 1984  1:32 PM...Taft];
* Emulates an Alto Diablo (double model-44) disk on one surface
* (or "partition") of the Dorado's Trident disk 0.
*-----------------------------------------------------------

*-----------------------------------------------------------
% Data structures
*-----------------------------------------------------------

KBLK format:
	VM 521:	pointer to disk command block (KCB)
	VM 522:	status at beginning of current sector
	VM 523:	disk address of most recently started disk command
	VM 524:	sector interrupt bit mask

KCB format:
	KCB+0:	pointer to next KCB
	KCB+1:	ending status
	KCB+2:	command
	KCB+3:	pointer to header block
	KCB+4:	pointer to label block
	KCB+5:	pointer to data block
	KCB+6:	no-error completion interrupt bit mask
	KCB+7:	error completion interrupt bit mask
	KCB+10:	unused
	KCB+11:	disk address

Disk address format:
	0-3:	sector number
	4-12:	cylinder number
	13:	head number
	14:	drive number (XORed with command bit 15)
	15:	restore request

Disk command format:
	0-4:	seal (= 11B)
	5-7:	partition number (0 = default, 1-7 = specific partition;
		note that it's impossible to select partitions 8-19
		by this means)
	8-9:	header operation (0 = read, 1 = check, 2 or 3 = write)
	10-11:	label operation
	12-13:	data operation
	14:	seek only
	15:	complement drive number of disk address

Disk status format:
	0-3:	sector number
	4-7:	17B
	8:	seek failed
	9:	seek in progress
	10:	unit not ready
	11:	data late
	12:	no data transferred
	13:	data checksum error
	14-15:	completion code: 0 = normal, 1 = hardware error,
		2 = check error, 3 = illegal sector

Alto emulation is performed only on drive 0, which may be either a T-80 or
an AMS-315. A Trident disk is formatted as 815 cylinders, 5 heads, 29 sectors.
An AMS-315 disk is formatted as 815 cylinders, 19 heads, 29 sectors.
Diablo disk addresses are mapped onto the Dorado disk as follows:
	Dorado cylinder = 406*(Diablo drive) + Diablo cylinder +3
	Dorado head = (partition number)+1
	Dorado sector = nSectorsDiablo*(Diablo head) + Diablo sector
%

* Disk format parameters:
MC[nSectorsDiablo, 16];		* Emulated # of sectors (14B or 16B)
MC[offsetCylinderDiablo, 3];	* Cylinders reserved at beginning of disk
Set[interleaveSectors, 0];	* 1 to interleave sectors 3:1, 0 not
Set[staggerSectors, 1];		* Stagger sectors on adjacent cylinders

Set[XTask, IP[DSK]];
TopLevel;

*-----------------------------------------------------------
* Disk task initialization
*-----------------------------------------------------------
Subroutine;
DSKInitPC: T← DSK, CoReturn;
TopLevel;
	T← A0, RBase← RBase[DiskRegs];
	K400← 400C, Call[ClearDisk];	* Disable controller, clear wakeups
	TridentFlag← A0, MemBase← MDS,
		Call[InitRamDiablo];	* Init format Ram for Diablo

* Attempt to select a head that is legal on an AMS-315, illegal on a T-80.
* By looking at the error status, we can see what type of drive we have.
	T← KSelect, Call[SendDriveTag];	* Select the drive
	T← 5C;				* Head 5
	T← T OR (tagHead), Call[SendTag];
	KTemp0← muffHeadOvfl, Call[Read1Muff];
	T← tagDiskReset,
		KTemp0, Branch[.+2, R odd]; * Did we get a HeadOverflow error?
	 MaxPartition← 23C, Branch[.+2]; * No, this is an AMS-315 (19 heads)
	 MaxPartition← 5C;		* Yes, this is a T-80 (5 heads)
	T← T OR (tagControl), Call[SendTag]; * Reset error status

	KStatus← A0, Branch[AForgetCmmd];

*-----------------------------------------------------------
* SIO instruction
* Diablo disk does not use SIO, but Trident disk does.
* If TriDisk.mc is loaded, this label gets redefined.
*-----------------------------------------------------------

DiskSIO: Branch[ESIO];

*-----------------------------------------------------------
* Idle loop for Diablo emulation: awakened once per sector.
* Stores status and checks for newly-issued commands.
* If KStatus # 0, it is the ending status for a command that has just been
* finished; if KStatus = 0, the disk was idle during the last sector.
*-----------------------------------------------------------

AltoLoop:
	T← clearSeekTagTW, Call[DoMuffOutput]; * clear any spurious SeekTagTW
	Call[UpdateSector];

	T← (K400)+(124C);		* VM 524 = sector interrupt mask
	KTemp3← (Fetch← T)-(3C);
	RBase← RBase[NWW];
	NWW← (NWW) OR MD, RBase← RBase[DiskRegs];
* The NWW>0 test is not logically necessary, but is desirable because most
* existing disk software does NOT use interrupts.
	T← KStatus, Branch[.+2, ALU<=0]; * Disk active last sector?
	T← KStatus, Reschedule;		* NWW>0 now

	KTemp3← (Fetch← KTemp3)+1,	* VM 521 = KCB pointer
		Branch[AWasntIdle, ALU#0]; * Branch if already have status

* Controller was idle last sector.  Select drive, read status anew, and deselect
* drive (so as to leave drive deselected most of the time).  Trident spec is
* that selecting a drive takes effect within 200 ns, so it's unnecessary for
* the microcode to do any special timing or synchronization.
	T← KSelect, Call[SendDriveTag];	* Turn on select for current drive
	Call[AMapHdwStatus];		* Read status and map to Diablo format
	PD← MD,	Sector,			* MD#0 if about to start a new command
		Branch[SectorUnsync, R<0]; * Don't deselect if sector unsynchronized
	T← (KSelect) AND (Not[tagSelectDrive!]C),
		Branch[DontDeselect, ALU#0]; * Deselect drive if no command pending
	Call[SendDriveTag];		* Turn off select
	Nop;				* Placement

* The sector number we insert is the raw hardware sector number, not the
* emulated Diablo sector number.  I doubt anyone cares.
DontDeselect:
	T← LSH[Sector, 14];
SectorUnsync:
	T← KStatus← (KStatus) OR T;	* Insert sector number into software status

AWasntIdle:
	Store← KTemp3, DBuf← T, T← MD;	* VM 522 ← current status
	KPtr← PD← T;			* Test for nonzero KCB pointer
	T← (KPtr)+(2C), DblBranch[DoACmmd, EndAltoLoop, ALU#0];

* EndAltoLoop gets defined once in DiskSubrs.mc, and redefined in TriDisk.mc
* if the latter is loaded.
* EndAltoLoop:
* 	KStatus← A0, Block, Branch[AltoLoop];

*-----------------------------------------------------------
* Have a new Diablo command to execute.  KPtr = KCB pointer, T = KPtr+2.
*-----------------------------------------------------------
DoACmmd:
	T← (Fetch← T)+(7C);		* Fetch disk command (KCB+2)
	KCmmd← MD, Fetch← T;		* Fetch disk address (KCB+11)
	T← (KCmmd) AND (174000C);
	PD← T XOR (ACmmdSeal), KAddr← MD;
	PD← (KAddr)+(LShift[Sub[20, nSectorsDiablo!], 14]C),
		Branch[ACmmdBadSeal, ALU#0]; * Branch if invalid seal
	T← (K400)+(123C),		* VM 523
		Branch[ABadSector, Carry]; * Branch if invalid sector

	KStatus← A0, Fetch← T;		* Fetch diskAddr previous command
	Store← T, DBuf← KAddr;		* Store diskAddr this command
	T← DPF[KCmmd, 1, 1];		* Extract command bit 15 into bit 14
	KAddr← (KAddr) XOR T,		* A[14] XOR C[15] defines disk number
		Branch[ARestore, R odd];

	T← (KAddr) XOR MD;		* Compare this diskAddr with previous
	T← T AND (177772C);		* Just cylinder and disk numbers
	PD← T AND (7777C);		* (= ((KAddr) XOR MD) AND (7772C))
	PD← (KCmmd) AND (3400C), Branch[NoASeek, ALU=0];

*-----------------------------------------------------------
* Must do a seek.  KCmmd and KAddr are set up.
* Dorado cylinder = 406*(Diablo drive) + (Diablo cylinder)
*-----------------------------------------------------------
ASeek:
	T← (K400)+(226C);		* 626B = 406 = number of cylinders
	KTemp0← T;
	T← LDF[KAddr, 11, 3];		* Cylinder for command
	PD← (KTemp0)-T;			* Test for legal cylinder
	PD← (KAddr) AND (2C), Branch[ABadCylinder, Carry']; * Test drive bit
	T← T+(offsetCylinderDiablo), Branch[.+2, ALU=0];
	T← (KTemp0)+T;			* Drive=1, add 406 to cylinder
	Call[SeekAndWaitForReady];

*-----------------------------------------------------------
* Now compute the head number.
* Dorado head = (partition number)-1
* Partition number is in KCmmd[5:7] if nonzero, else DefaultPartition.
*-----------------------------------------------------------
	PD← (KCmmd) AND (3400C);
NoASeek:
	T← DefaultPartition, Branch[.+2, ALU=0]; * Skip if command partition = 0
	T← LDF[KCmmd, 3, 10];		* Get partition from command
	T← T OR (tagHead);
	T← T-1, Call[SendTag];		* Send the head tag command

:If[staggerSectors];	********** Stagger sectors on adjacent cylinders
* If cylinder is odd then flip the Diablo head bit.  This makes a given sector
* on adjacent cylinders be on opposite sides of the disk.  This means that
* during sequential reads, a seek to the next cylinder will lose
* only half a revolution rather than a whole revolution.
	T← (KAddr) AND (10C);		* Low bit of cylinder
	T← RSH[T, 1];			* Shift to head position
	KAddr← (KAddr) XOR T;
:EndIf;			************************************************

*-----------------------------------------------------------
* Now compute the sector number.
* Let s = nSectorsDiablo*(Diablo head) + (Diablo sector)
*-----------------------------------------------------------
	PD← (KCmmd) AND (2C);		* Seek-only command?
	PD← (KAddr) AND (4C), Branch[ACmmdSeekOnly, ALU#0];
	KAddr← LDF[KAddr, 4, 14], Branch[.+2, ALU=0]; * Extract sector
	KAddr← (KAddr)+(nSectorsDiablo); * Add sectors for head 1

:If[interleaveSectors];	********* Interleave sectors 3:1 **********
*-----------------------------------------------------------
* The sector number s is mapped into a hardware sector number [0..29]
* according to the following table:
*
* s = -- 19  0 10 20  1 11 21  2 12 22  3 13 23  4
* h =  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14
*
* s = 14 24  5 15 25  6 16 26  7 17 27  8 18 --  9
* h = 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
*
* Thus h ← if s = 19 then 1 else 3*(s mod 10) + s/10 + 2
* Note that h = 28 is unusable, and h = 29 is the sector after Index.
*-----------------------------------------------------------
	T← 1C;				* Compute s/10 and s mod 10
	KAddr← (KAddr)-(12C);		* Go around this loop at most 3 times
	T← T+1, Branch[.-1, ALU>=0];
	KAddr← (KAddr)+(12C);		* Now KAddr = s mod 10, T = s/10 + 2
	T← ((KAddr)+T) RCY 1;		* KAddr ← 3*KAddr + T
	KAddr← ((KAddr)+T) LCY 1;
	PD← (KAddr)-(36C);		* If result is 30 then change to 1
	KCmmd← (KCmmd) AND (374C), Branch[.+2, ALU<0];
	KAddr← 1C;
:Else;			********** Not interleaving sectors **********
	KCmmd← (KCmmd) AND (374C);
:EndIf;			**********************************************

*-----------------------------------------------------------
* Convert the Alto disk command into a Dorado disk command.
* Alto format: seal[0:7], headerCmmd[8:9], labelCmmd[10:11], dataCmmd[12:13],
*	seekOnly[14], exchangeDisks[15];
*	Commands: 0 = read, 1 = check, 2 or 3 = write
* Dorado format: headerCmmd[8:9], labelCmmd[10:11], dataCmmd[12:13];
*	Commands: 0 = none, 1 = write, 2 = check, 3 = read
*-----------------------------------------------------------
	T← (KCmmd) AND (250C);		* Turn any 3's into 2's
	T← (NOT T) RSH 1;
	KCmmd← (KCmmd) AND T;
	KCmmd← (KCmmd) XOR (374C);	* Complement to make Dorado commands

AWaitSector:
	SCall[WaitForSector];		* Returns with TIOA[DiskControl]
	 Branch[ABadSector];		* +1 return: failed to find the sector

*-----------------------------------------------------------
* Issue the command to the controller.
* Then check to see whether we issued it in time, and if not revoke it
* and wait for this sector to come around again.
*-----------------------------------------------------------
	Output← KCmmd, Call[UpdateSector]; * Returns with T = Sector
	PD← (KAddr) XOR T;		* Are we still at the same sector?
	T← A0, TIOA[DiskControl], Branch[ACmmdInTime, ALU=0];

	Output← T;			* Not in time.  This clears Active
	Output← T, Branch[AWaitSector];	* This reloads command register

*-----------------------------------------------------------
* Now do the data transfers.
* Each call to DoAltoCmmd executes the command in KCmmd[14:15]
* and left-cycles KCmmd 2 bits.
*-----------------------------------------------------------
ACmmdInTime:
	Block, Call[UpdateSector];	* Block til start of sector
	T← (KPtr)+(3C);
	KCmmd← RCY[KCmmd, KCmmd, 6];	* Header command to [14:15]

	DskMAddr← 1C;			* Header length -1
	KTemp3← (Fetch← T)+1, Call[DoAltoCmmd]; * Header pointer (KCB+3)

	DskMAddr← 7C;			* Label length -1
	KTemp3← (Fetch← KTemp3)+1, Call[DoAltoCmmd]; * Label pointer (KCB+4)

	DskMAddr← 377C;			* Data length -1
	Fetch← KTemp3, Call[DoAltoCmmd]; * Data pointer (KCB+5)
	Nop;

*-----------------------------------------------------------
* Command has completed.  Store ending status and initiate interrupts
* as appropriate.  KPtr still points to KCB.
* KStatus = 0 if all has gone well so far; # 0 if a check error or ECC error
* has been detected by the microcode.  Have not yet looked at hardware status.
* KTemp0 = muffReadError or muffWriteError, depending on the operation
* performed on the last block.
*-----------------------------------------------------------
ACmmdEnd:
	KPtr← (Fetch← KPtr)+1, Call[Read1Muff]; * Fetch successor KCB
	KTemp3← MD, PD← (KStatus) OR T;	* Software or hardware errors?
	T← (KPtr)+(5C), Branch[ACmmdEnd2, ALU=0]; * KCB+6: no-error interrupts

* Error occurred.
* Reset command register in case we have gotten out of sync with controller.
* Then disable further KCB processing and read complete status.
	KTemp3← T← A0, TIOA[DiskControl];
	KTemp0← T-T-1, Output← T, Call[OutputGetsT];
	T← (K400)+(123C);		* Forget disk address (set to -1)
	Store← T, DBuf← KTemp0, Call[AMapHdwStatus];
	T← clearErrors, Call[DoMuffOutput];
	T← (KPtr)+(6C);			* KCB+7: error interrupts

ACmmdEnd2:
	Fetch← T, T← clearSeekTagTW,	* Fetch interrupt word
		Call[DoMuffOutput];	* Clear TW caused by TagDone
	T← LSH[Sector, 14];		* Merge sector number into status
	KStatus← (KStatus) OR T, RBase← RBase[NWW];
	T← (R400)+(121C);
	NWW← (NWW) OR MD, RBase← RBase[DiskRegs];
	Store← T, DBuf← KTemp3,		* update VM 521 with successor pointer
		Branch[.+2, ALU<=0];	* Branch if no interrupt pending
	ReSchedule;
	T← KStatus← (KStatus) OR (7400C); * KStatus[4:7]←17B
	Store← KPtr, DBuf← T, Branch[AltoLoop]; * Store status in KCB+1

ACmmdSeekOnly:
	Branch[ACmmdAbort];

*-----------------------------------------------------------
* Restore command
*-----------------------------------------------------------
ARestore:
	T← tagControl;
	T← T OR (Or[tagDiskReset!, tagReZero!]C), Call[SendTag];
	T← clearAllTWs, Call[DoMuffOutput];
	T← K400, TIOA[DiskControl], Call[OutputGetsT]; * BlockTilIndex
	Sector← T-T-1, Block, Branch[ASeek]; * Now do a new seek always

*-----------------------------------------------------------
DoAltoCmmd:	* Do Alto command for one block
* Enter: KCmmd[14:15] = command for this block (Dorado format)
*	MD = pointer to start of block
*	DskMAddr = (length of block)-1
*	DiskBR.BRHi = 0
* Exit:	KCmmd left-cycled 2 bits
*	MemBase = MDS
*	KTemp0 = muffler address for reading ending status
* Clobbers T, KTemp0, KTemp1, KTemp2, DskMAddr, DiskBR.BRLo
* Note: if a check error occurs or improper TW occurs, does not return
* but rather aborts command and goes directly to ACmmdDone.
*-----------------------------------------------------------
Subroutine;

	KTemp2← Link;
TopLevel;
	T← MD, MemBase← DiskBR;
	BDispatch← KCmmd;		* Dispatch on KCmmd[14:15]

ACmmdTable: DispTable[4, 7, 4],
	BRLo← T, Branch[ACmmdTable];	* 0 can't happen
	T← 201C, Branch[ACmmdWrite];	* 1 write; T← sync pattern to write
	KTemp0← muffRdFifoTW, Branch[ACmmdCheck]; * 2 check
	KTemp0← muffRdFifoTW, Branch[ACmmdRead]; * 3 read

*-----------------------------------------------------------
* Write command.
* Controller gives a WriteFifoTW when there is room for at least 4 words
* in the Fifo.  Doing an Output that reduces the free space below 4 causes
* WriteFifoTW to be dropped at T0 of the 4th cycle after the Output,
* so a Block on the 5th cycle will take effect.
* I think it was originally intended that a 3-instruction, 2-word loop
* be possible:			Output; Output; Block;
* Unfortunately, this doesn't work if the second Output causes the wakeup
* to be dropped, because it is dropped so late that we will go around
* the loop twice more, outputting 4 words when there is room for only 3.
* Thus the minimal loop is:	{Output; Output; Nop}; Block;
* where the instructions inside { } may be permuted in any way.
* An equivalent loop is:	Output; Block;
* and it takes less microcode.
* Due to control section bugs, we must not Block on a memory reference
* if the task wakeup might be dropped at T0 of that instruction.
*-----------------------------------------------------------
ACmmdWrite:
	TIOA[DiskData];
	DskMAddr← (Fetch← DskMAddr)-1, Output← T; * Output sync pattern

	DskMAddr← (Fetch← DskMAddr)-1, Output← MD;
	Block, Branch[.-1, ALU>=0];

	Output← MD;			* Output last word

* Changing TIOA from DiskData to DiskControl disables WriteFifoTW.
* The wakeup is removed at T0 of the third instruction after the one that
* changes TIOA, so the earliest we can block is the fourth instruction.
* Hardware generates one more WriteFifoTW when it is done with this block.
	TIOA[DiskControl];
	KTemp0← muffWriteError;		* Select appropriate status bit
	KCmmd← LCY[KCmmd, KCmmd, 2];	* Shift command for next block
	Link← KTemp2;
Subroutine;
	MemBase← MDS, Block, Return;	* Wait until write really finished
TopLevel;

* DoAltoCmmd (cont'd)

*-----------------------------------------------------------
* Read command.
* Controller gives a ReadFifoTW when there are at least 3 words in the Fifo
* (actually, 2 in the Fifo and 1 in OutReg).  Doing an Input that reduces
* the count below 3 causes ReadFifoTW to be dropped at T0 of the 4th cycle
* after the Input, so a Block on the 5th cycle will take effect.
* Thus the minimal loop is:	Input; Block;
* Due to control section bugs, we must not Block on a memory reference
* if the task wakeup might be dropped at T0 of that instruction.
*-----------------------------------------------------------
ACmmdRead:
	DskMAddr← (DskMAddr)+1, Block, Call[Read1Muff];
	KTemp0, TIOA[DiskData], Branch[AReadBadTW, R even];
	PD← DskMAddr, T← Input, Branch[.+2]; * Can't do back-to-back Inputs

	PD← Store← DskMAddr, DBuf← T, T← Input;
	DskMAddr← (DskMAddr)-1, Block, Branch[.-1, ALU#0];

* A read block ends with 2 garbage words and 2 ECC words.  When we fall out
* of the main loop, we have already read the first garbage word.
	Call[ReadECC];			* Returns with ECC test hanging
	FreezeBC, Branch[ReadCheckEnd];	* Remainder same as check case

*-----------------------------------------------------------
* Check command.
* Controller gives a ReadFifoTW when there is at least 1 word in the Fifo
* (actually, OutReg full regardless of Fifo).  Doing an Input that empties
* the Fifo causes ReadFifoTW to be dropped at T0 of the 2nd cycle
* after the Input, so a Block on the 3rd cycle will take effect.
* Thus the minimal loop is:	Input; Nop; Nop; Block;
* Due to control section bugs, we must not Block on a memory reference
* if the task wakeup might be dropped at T0 of that instruction.
*-----------------------------------------------------------
ACmmdCheck:
	DskMAddr← (Fetch← DskMAddr)-1, Block, Call[Read1Muff];
	KTemp0← clearCompareErr, Branch[ACheckBadTW, R even];
	PD← (DskMAddr)+1, TIOA[DiskData];

* Main check loop -- 4 cycles per word.
* At the top of the loop, MD = the word fetched from memory during
* the previous cycle, and ALU=0 if that was the last word of the block.
ACheckLoop:
	PD← MD, T← MD, KTemp1← Input, Branch[ACheckLast, ALU=0];
	DskMAddr← (Fetch← DskMAddr)-1, Branch[ANoCheckWord, ALU=0];

* Memory word is nonzero: check it against disk word.
	PD← (KTemp1) XOR T;
	PD← (DskMAddr)+1, Block, Branch[ACheckLoop, ALU=0];
	KTemp0← A0, Branch[.-1];	* Not equal, cancel clearCompareErr

* Memory word is zero: store disk word on top of it.
* Note: the combination of memory reference and Block in the second
* instruction is OK, because if the wakeup is going to drop,
* it will drop by T0 of the first instruction.
ANoCheckWord:
	T← (DskMAddr)+(2C);		* We are behind count by 2 now
	PD← (Store← T)-1, DBuf← KTemp1, Block, Branch[ACheckLoop];

* Fell out of loop.
* T = ALU = last word from memory, and KTemp0 = last data word from disk.
ACheckLast:
	PD← (KTemp1) XOR T, Branch[.+2, ALU#0];
	PD← Store← T, DBuf← KTemp1;	* T = 0
	TIOA[DiskMuff], Branch[.+2, ALU=0];
	KTemp0← A0;			* Not equal, cancel clearCompareErr

* DoAltoCmmd (cont'd)

* If KTemp0 = clearCompareErr, check finished with no errors - it's OK
* to write the next block.  If KTemp0 = 0, a check error occurred.
* Hardware turns on CompareErr flipflop at the beginning of a checked block,
* which will inhibit writing of subsequent blocks if the microcode determines
* that there is a check error in this block or fails to clear it in time.
	PD← Output← KTemp0;		* Clear CompareErr iff no errors
	T← A0, TIOA[DiskData], Block, Branch[.+2, ALU=0];
	T← KCmmd;
	KCmmd← T, Call[CheckECC];	* returns with ECC test hanging

* At this point, ALU#0 if an ECC error occurred, and KCmmd=0 if
* a check error occurred.  This is the tail of both reading and checking.
ReadCheckEnd:
	KTemp0← muffReadError, Branch[.+2, ALU=0];
AChecksumError:
	KStatus← (KStatus) OR (4C);	* Alto ChecksumError
	KCmmd← LCY[KCmmd, KCmmd, 2];	* Shift command for next block
	Link← KTemp2, Branch[.+2, ALU=0];
Subroutine;
	MemBase← MDS, Return;
TopLevel;

* KCmmd=0 means a check error occurred.  Post CheckError completion code
* and abandon commands for remaining blocks.
	KStatus← (KStatus) OR (2C);	* Alto CheckError
ACmmdAbort:
	MemBase← MDS;
	KTemp0← muffReadError;
	Branch[ACmmdEnd];

* If a non-Fifo TW occurs at the beginning of reading or checking, most
* likely the data was so bad that the controller was unable to lock onto it
* before reaching the end of the sector.  Report this as a ChecksumError.
AReadBadTW:
	KCmmd← A0, Branch[AChecksumError];
ACheckBadTW:
	KCmmd← A0, Branch[AChecksumError];

* Command errors post bad status and abort the chain.
ABadCylinder:
	KStatus← 201C, Branch[.+2];	* Illegal cylinder, report SeekFail
ABadSector:
	Kstatus← 3C;			* Illegal sector
	Branch[ACmmdAbort];		* Duplicated for placement

* Incorrect seal abandons the command chain entirely and posts error status
* only in VM 522.
ACmmdBadSeal:
	T← (7S)-(K400);			* 177407 -- done + illegal command
	KStatus← T;

* Zero command chain and set disk address to -2 (any illegal addr will do)
AForgetCmmd:
	T← (K400)+(123C);
	T← (Store← T)+(DBuf← -2C);	* VM 523 ← -2
	Store← T, DBuf← 0C, Branch[AltoLoop]; * VM 521 ← 0

*-----------------------------------------------------------
AMapHdwStatus:	* Map hardware status to Diablo format
* Enter: KStatus = 0, except perhaps for software-detected errors
*	(ChecksumError, CheckError)
* Exit:	KStatus updated to reflect hardware status, including the done bits
*	but NOT including the sector number.
*	T = 0
* Clobbers T, KTemp0, KTemp1, KTemp2
*-----------------------------------------------------------
Subroutine;

	KTemp2← Link;
TopLevel;
	T← muffsStatus, Call[Read20Muffs];

	PD← (KTemp0) AND (16000C);	* NotSelected, NotOnLine, NotReady?
	T← (K400) OR (200C), Branch[.+2, ALU=0];
	KStatus← (KStatus) OR (40C);	* Report as NotReady

	PD← (KTemp0) AND T;		* FifoUnderflow, FifoOverflow?
	T← 200C, Branch[.+2, ALU=0];
	KStatus← (KStatus) OR (20C);	* Report as DataLate

	PD← (KTemp0) AND (5C), Branch[.+2, R>=0]; * SeekIncomplete?
	KStatus← (KStatus) OR T, FreezeBC; * Report as SeekFail

* Don't know whether to examine WriteError or ReadError.
* However, WriteError includes all ReadErrors, and additionally includes
* ReadOnly, CylOffset, and FifoParityError.  The first two cause DeviceCheck
* (included in ReadError) to be set if they occur during writing, and
* the third cannot occur during reading.  Therefore it suffices to examine
* FifoParityError and ReadError to decide whether any hardware error occurred.
	PD← (KStatus) AND (3C), Branch[.+2, ALU#0];
	T← A0, Link← KTemp2, Branch[.+3]; * No error
	T← A0, Link← KTemp2, Branch[.+2, ALU#0]; * CompletionCode already posted?
	KStatus← (KStatus) OR (1C);	* Post HardwareError
Subroutine;
	KStatus← (KStatus) OR (7400C), Return; * Insert done bits

*-----------------------------------------------------------
InitRamDiablo:		* Init format Ram for Diablo emulation.
* Also sets subsector count for drive 0 and issues a BlockTilIndex.
* Enter:
* Exit: TIOA[DiskMuff]
* Clobbers T, KTemp0, KTemp1, KTemp2
*-----------------------------------------------------------
Subroutine;

	KTemp2← Link;
TopLevel;
	MaxSectors← 36C;		* 30 sectors around (actually, 29 +
					* a fraction, though we use only 28)
	KTemp0← 3C;			* 4 subsectors per sector
	KSelect← (KSelect) OR (4000C);	* Sectors do not evenly divide the disk
	T← KTemp1← A0, Call[SetDriveAndSubSector]; * Drive 0
	T← (KTemp1)+1, TIOA[DiskRam],	* T← 1
		Call[OutputGetsT];	* [0] header count - 1 = 1
	T← 7C, Call[OutputGetsT];	* [1] label count - 1 = 7
	T← 377C;
	T← A0, Output← T,		* [2] data count - 1 = 377
		Call[OutputGetsT];	* [3] count for unused block = 0
	T← 104C, Call[OutputGetsT];	* [4] control tag for read
	T← 204C, Call[OutputGetsT];	* [5] control tag for write
	T← 4C;
	T← A0, Output← T,		* [6] control tag for head select
		Call[OutputGetsT];	* [7] control tag to zero the tag bus
	T← 33C, Call[OutputGetsT];	* [10] write delay first block
	T← 6C, Call[OutputGetsT];	* [11] write delay succeeding blocks
	T← 11C, Call[OutputGetsT];	* [12] read delay first block
	T← 2C, Call[OutputGetsT];	* [13] read delay succeeding blocks
	T← T-1, Output← T;		* [14] head select delay = 2
	T← A0, Output← T;		* [15] no. of ECC words - 1 = 1
	Output← T,			* [16] the constant 0
		Call[OutputGetsT];	* [17] unused word
	T← clearAll;			* Clear all TWs and errors
	Link← KTemp2, Branch[DoMuffOutput]; * Do it and return