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