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