*----------------------------------------------------------- Title[DiskSubrs.mc...February 12, 1983 2:10 PM...Taft]; * Disk subroutines shared by microcode for all disk formats. *----------------------------------------------------------- Set[XTask, IP[DSK]]; KnowRBase[DiskRegs]; TopLevel; :If[AltoMode]; ********** Alto version ********** *----------------------------------------------------------- * This label gets defined once here, and redefined in TriDisk.mc if loaded. * It is defined here because it gets bound too early if included * in ADiabloDisk.mc *----------------------------------------------------------- EndAltoLoop: KStatus_ A0, Block, Branch[AltoLoop]; :EndIf; ********************************** *----------------------------------------------------------- Read1Muff: * Read one muffler * Enter: KTemp0 = muffler number * Exit: KTemp0 = T = muffler value (0 or 1, right-justified); TIOA[DiskMuff] *----------------------------------------------------------- Subroutine; TIOA[DiskMuff], Global; Output_ KTemp0; Nop; * 1-cycle wait required by hardware KTemp0_ T_ Input, Return; *----------------------------------------------------------- Read20Muffs: * Read 20 consecutive mufflers * Enter: T = starting muffler number * Exit: KTemp0 = T = word full of muffler values, left-to-right * TIOA[DiskMuff] * Clobbers KTemp1 *----------------------------------------------------------- Subroutine; KTemp1_ T, TIOA[DiskMuff]; KTemp0_ T_ 1C; * Flag bit shifted out when done Read20MLoop: KTemp1_ (KTemp1)+1, Output_ KTemp1; KTemp0_ ((KTemp0) OR T) LSH 1; T_ Input, Branch[Read20MLoop, ALU>=0]; * Tests unshifted ALU output KTemp0_ T_ (KTemp0) OR T, Return; *----------------------------------------------------------- DoMuffOutput: * Do output to muffler register, for clearing TWs, etc. * Enter: T = muffler command * Exit: TIOA[DiskMuff] *----------------------------------------------------------- Subroutine; TIOA[DiskMuff], Global; Output_ T, Return; *----------------------------------------------------------- SetDriveAndSubSector: * Select new drive and set subsector counter * Enter: T = drive number * KTemp0 = subsector count -1 (the way the hardware wants it) * Exit: KSelect updated to new drive number * Clobbers T, KTemp0; invalidates Sector * Clears all TWs and sets BlockTilIndex. *----------------------------------------------------------- Subroutine; Sector_ Link; TopLevel; KSelect_ (KSelect) AND (Not[tagDriveNumber!]C); * Mask out old drive number KSelect_ T_ (KSelect) OR T; * Insert new drive number KTemp0_ LSH[KTemp0, 6]; * Combine subsector count with drive T_ (KTemp0) OR T, Call[SendDriveTag]; * Select the drive T_ T OR (tagLoadSubSector), Call[SendDriveTag]; * Load subsector count * There is a potential race here. We must first clear all TWs, then set * BlockTilIndex, then clear SectorTW which might have come up in the meantime. * Fortunately, IndexTW does a DC reset of BlockTilIndex if it comes up * in the meantime. T_ clearAllTWs, Call[DoMuffOutput]; TIOA[DiskControl]; * T_ blockTilIndex T_ blockTilIndex, Call[OutputGetsT]; T_ clearSectorTW; Sector_ (Sector)-(Link_ Sector)-1, * Sector_ -1 to invalidate it Branch[DoMuffOutput]; * Clear SectorTW and return *----------------------------------------------------------- SendDriveTag: * Select new drive * Enter: T = drive number (perhaps including subsector count) * Exit: T unchanged; TIOA[DiskTag]; * Clobbers KTemp0 *----------------------------------------------------------- Subroutine; KTemp0_ Link; TopLevel; TIOA[DiskTag], Call[OutputGetsT]; * Send just drive number T_ T OR (tagDrive), Call[OutputGetsT]; * Strobe the DriveTag T_ T AND (tagBus); Link_ KTemp0, Branch[OutputGetsT]; * Lower DriveTag and return *----------------------------------------------------------- SeekAndWaitForReady: * Issue seek command and wait until it is done * Enter: T = cylinder number * Exit: Seek completed * Clobbers T, KTemp0, KTemp1, KTemp2 * May leave a SectorTW or IndexTW dangling. *----------------------------------------------------------- Subroutine; KTemp2_ Link; TopLevel; T_ T OR (tagCylinder), Call[SendTag]; SeekWait: * Wait for seek to complete KTemp0_ muffNotReady, Call[Read1Muff]; KTemp0, Branch[SeekDone, R even]; * Branch if ready KStatus_ A0, Block, Call[UpdateSector]; * Not ready, await next sector Branch[SeekWait]; SeekDone: T_ A0, Link_ KTemp2, * Restore Link and return Branch[OutputGetsT]; * (Output_ 0 to DiskMuff does nothing) *----------------------------------------------------------- SendTag: * Send tag command and wait for tag sequence to complete * Enter: T = tag command * Exit: TIOA[DiskMuff] * Clobbers T, KTemp0, KTemp1 * Clears SeekTagTW, and counts sectors while waiting. *----------------------------------------------------------- Subroutine; KTemp1_ Link, Global; TopLevel; KTemp0_ T, TIOA[DiskMuff]; * First, clear SeekTagTW T_ clearSeekTagTW, Call[OutputGetsT]; T_ KTemp0, TIOA[DiskTag], Call[OutputGetsT]; * Issue the tag command * Wait until a SeekTagTW occurs, and handle sector wakeups while waiting. SendTagWait: Call[UpdateSector]; KTemp0_ muffSeekTagTW, Block, Call[Read1Muff]; T_ clearSeekTagTW, KTemp0, Branch[.+2, R odd]; Branch[SendTagWait]; * Wakeup wasn't SeekTagTW, wait more * Got a SeekTagTW. Clear it and return. Know TIOA[DiskMuff]. Link_ KTemp1, Branch[OutputGetsT]; *----------------------------------------------------------- WaitForSector: * Wait for desired sector to arrive * Enter: (Alto) KAddr = desired sector number * (Pilot) KTemp3 = desired sector number * Call: SCall[WaitForSector]; * Returns +1: failed to reach desired sector within 64 sector times * +2: succeeded: * TIOA[DiskControl] * Clobbers T, KTemp0, KTemp1, KTemp2 *----------------------------------------------------------- Subroutine; KTemp1_ Link; TopLevel; KTemp2_ -100C; SectorWaitLoop: T_ ClearSeekTagTW, Call[DoMuffOutput]; * Clear spurious SeekTagTWs Call[UpdateSector]; * Returns with T = Sector :If[AltoMode]; ********** Alto version ********** PD_ (KAddr) XOR T, TIOA[DiskControl]; :Else; ******** PrincOps version ******** PD_ (KTemp3) XOR T, TIOA[DiskControl]; :EndIf; ********************************** PD_ A0, Link_ KTemp1, Branch[SectorFound, ALU=0]; Subroutine; KTemp2_ (KTemp2)+1, Block, Branch[SectorWaitLoop, R<0]; Return; * Failure return SectorFound: Return[ALU=0]; * Normal +2 return *----------------------------------------------------------- UpdateSector: * Check for sector/index wakeups and update Sector register * Enter: Sector = current sector number (-1 => invalid) * MaxSectors = maximum number of sectors expected for this disk format * KSelect[4] = 1 iff sectors do not evenly divide the disk * Exit: Sector updated; T = Sector * TIOA[DiskMuff] * Clobbers KTemp0 * Clears any SectorTW or IndexTW that occurs, but leaves other wakeups alone. * Note that due to a design blunder, IndexTWs do not occur if the current drive * is deselected. When we see SectorTW, we compute Sector_ (Sector+1) mod MaxSectors. * Thus once we have seen one IndexTW we will stay synchronized. * Additionally, due to another design botch, the index pulse does not cause a SectorTW * if the number of subsectors/sector does not evenly divide the number of * subsectors/revolution; so when the drive is deselected we miss the index pulse entirely! * Therefore we must let the successor of MaxSectors-1 be 1 rather than 0; we can * reset to 0 only when we actually see an index pulse. * Note on sector numbering: * For an n-sector format, the sector immediately before Index is numbered n-1 * and the sector immediately after Index is numbered 0, for the purpose * of issuing commands. However, since commands are executed in the sector * after they are issued, the sector before Index is n-2 and the sector after * Index is n-1, as physically recorded on the disk. * This means that if the sectors do not evenly divide the disk, the unusable * short sector immediately before Index is the one numbered n-2, not n-1 * as you might expect. *----------------------------------------------------------- Subroutine; TIOA[DiskMuff], Global; T_ muffSectorTW; * Select SectorTW muffler PD_ (Sector)+1, Output_ T; * Carry_ 1 iff Sector = -1 T_ muffIndexTW; KTemp0_ Input; * Bit 15 _ SectorTW muffler KTemp0_ Output_ T, * Select IndexTW muffler Branch[NoSectorTW, R even]; * Branch if SectorTW not present Sector_ T_ (Sector)+1, XorSavedCarry; * Increment sector unless it is -1 PD_ (MaxSectors) XOR T; KTemp0_ (KTemp0) OR (clearSectorTW), Branch[.+2, ALU#0]; * Branch if did not wrap around T_ LDF[KSelect, 1, 13]; * Reset to 0 or 1, depending on whether or not * IndexTW causes a SectorTW Output_ KTemp0, Branch[.+2]; * Clear SectorTW, leave muffIndexTW selected NoSectorTW: T_ Sector; KTemp0_ Input; * Bit 15 _ IndexTW muffler KTemp0_ Or[clearSectorTW!, clearIndexTW!]C, Branch[.+2, R even]; * Branch if IndexTW not present * If we see IndexTW, zero Sector and clear BOTH SectorTW and IndexTW. T_ A0, Output_ KTemp0; Sector_ T, Return; *----------------------------------------------------------- ReadECC: * Read ECC following a block that was read (not checked). * A disk block read has: data, 2 garbage words, 2 ECC words. * Control gets here having read the first garbage word and executed at * least one additional microinstruction since the last Input. * Enter: TIOA[DiskData] * Exit: T = first ECC word * KTemp0 = second ECC word * ALU = (KTemp0) OR T *----------------------------------------------------------- Subroutine; * ReadFifoTW drops at T0 of the 4th cycle after Input, so we can Block * no earlier than the 5th cycle after Input. T_ A0; * 2nd cycle T_ T-1, Branch[., ALU=0]; * 3rd & 4th cycles Block; * 5th cycle * When we are awakened, we know we can input 3 words. * Note: cannot issue back-to-back Inputs, because OutReg is not reloaded * from the Fifo quickly enough. PD_ Input; * Garbage word Nop; T_ Input; * First ECC PD_ A0, Branch[CheckECCLoop]; * Read second ECC and return *----------------------------------------------------------- CheckECC: * Read ECC following a block that was checked * Control gets here having read neither of the garbage words and executed at * least 2 additional instructions since the last Input. * Enter: TIOA[DiskData] * Exit: T = first ECC word * KTemp0 = second ECC word * ALU = (KTemp0) OR T * Clobbers KTemp1 *----------------------------------------------------------- Subroutine; KTemp1_ 3C, Block; * ReadFifoTW drops at T0 of the 2nd cycle after Input, so we can Block * no earlier than the 3rd cycle after Input. CheckECCLoop: KTemp0_ Input, Branch[CheckECCEnd, ALU=0]; T_ KTemp0; Nop; KTemp1_ (KTemp1)-1, Block, Branch[CheckECCLoop]; CheckECCEnd: PD_ (KTemp0) OR T, Return; *----------------------------------------------------------- ClearDisk: * Reset disk controller and reset all TWs. * Enter: TIOA unknown * Exit: TIOA[DiskMuff]; KSelect initialized * Clobbers T, KTemp0, KTemp1 *----------------------------------------------------------- Subroutine; KTemp1_ Link; TopLevel; T_ DiskControl; * Need full TIOA for init TIOA_ T; * Initialization after power-on is somewhat hairy because IOReset does not * completely reset the controller; in particular, it does not reset the * control or tag registers. It DOES reset the drive select register, so no * drive is selected and the undefined state of the tag register is harmless. * However, we must be careful to reset the tag register sufficiently in * advance of issuing a DriveTag so that there won't be a race between changing * the Select and Tag lines to the drives. The drive select register is loaded * on the 1-to-0 transition of the DriveTag signal. T_ clearEnableRun; * Reset controller twice Output_ T, Call[OutputGetsT]; TIOA[DiskTag]; T_ tagDrive, Call[OutputGetsT]; * Ensure no 1-to-0 transition while * tag bus is changing T_ A0, Call[OutputGetsT]; * Reset drive select register and clear * all tag bus latches T_ clearAll, Call[DoMuffOutput]; * Clear all TWs * Initialize KSelect to select drive 0. Link_ KTemp1; Subroutine; KSelect_ tagSelectDrive, Return;