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