;----------- Dandelion Processor Program - I/O Processor ----------- ; DESCRIPTION: Boot Program: IOP subroutines. ; Last modification by Roy Ogus: January 28, 1982 4:28 PM ; File: BootSubs.asm ; Stored: [Iris]<Workstation>Boot30>BootEPromRAM.dm ; Written by Roy Ogus. ; Modification History: ; - Created (November 13, 1980 9:37 AM) ; - Added TPC0 fix (November 20, 1980 4:37 PM) ; - Changed PhaseToMP, ErrorReport (December 22, 1980) ; - General CSImage size (December 22, 1980 5:14 PM) ; - Sector runs to end of track only (January 5, 1981 10:23 AM) ; - BootInit blanks MPanel (June 17, 1981 5:52 PM) ; - Changes for 2D, 2S boot floppies (June 17, 1981 6:12 PM) ; - Changes to CheckAltBootDevice (November 24, 1981 2:05 PM) ; - Changes to CheckAltBootDevice (December 10, 1981 6:12 PM) ; - UReg block uses GenericBootDevice (December 11, 1981 3:19 PM) ; - Ethernet host address reads 3 words, preset RS366 control (December 14, 1981 5:32 PM) ; - Added version number OR in BootDevice (January 13, 1982 4:22 PM) ; - Set interrupt state moved to BootInit (January 13, 1982 4:22 PM) ; - Add FloppyInitE entrypoint, check for no floppy (January 14, 1982 2:00 PM) ; - Took out di/ei in ReadSector, added blink-only to ErrorReport (January 22, 1982 4:48 PM) ; - Simplified double count check, other simplification (January 28, 1982 4:03 PM) ; DEFNITIONS: get "SysDefs" get "BootDefs" ; EXPORTS: EXP BootInit,CheckAltBootDevice EXP StartNextRead,GetNextWord EXP InitCSTPCImage,TransferCSImage,TransferTPCImage EXP WriteCS EXP SetupUregisters,InterpretUBlock,InformCPBootDevice,ReadMainMem EXP CheckCPStopped,StartCPKernel,StartCP EXP InitCPCmd,ReadCPWord,WriteCPword,ByteToWord EXP FloppyInit,FloppyInitE,DoSeekCmd EXP PhaseToMP,PutMP,ClearMPanel,IncrMP,DeltaMP,Delay,ErrorReport EXP SectorRunSize,MaxBufCnt,FloppyBuffer EXP IntMask,ORVersionNo ; For Burdock command file debugging ; IMPORTS: IMP StartIOPBoot ; From StartIOPBootYYY - Start of IOP boot file (Phase 0) IMP StartNextPhase ; From BootMain { This code contains the subroutines which are used by the Boot code in BootMain.asm. Notes: Boot file in Main memory should not cross 64K boundary Maximum of 16 loadU blocks in Phase 0 boot file. } ; ; SUBROUTINES. ; Subroutine: BootInit. ; Initialize the IOP, and the various data structures. ; Note: BootType is initialized at the start of execution. BootInit: ; Ensure that IOPWait = SwTAddr = 1 (should be after hard boot). mvi a,CPWaitSwT out CPControl mvi a,DisableFDC ; Disable floppy controller, Enable Waits out FDCState ; Set hardware interrupt mask. Interrupts are disabled at this point. SetIntMask: mvi a,BootIntrState ; Enable interrupts sim IntMask equ SetIntMask+1 ; Mask byte for modification by Burdock command file. ; Initialize and disable the Dma controller. in DmaStatus ; Clear any flags xra a out DmaMode ; Set miscellaneous registers. cma ; Set all Misc clock bits high out MiscClocks1 in CPIn ; Clear CPIn flags mvi a,BlankMPanel ; Clear MiscControl1, blank MPanel out MiscControl1 mvi a,CallReq+DigPr ; Set up RS366 control register out RS366Reg ; Initialize Boot flags. mvi a,BootMode+CPStopped ; BootMode, CPStopped True sta BootFlags ; Initialize data structures. lxi h,CSImage ; Initialize pointer to start of CSImage shld CSImageStart lxi h,CSImageSizeVal ; Initialize size of CSImage (in CSImageSize) shld CSImageSize xra a sta Phase ; Phase ← 0 sta uBlockCnt ; No. of uBlocks ← 0 sta BootSource ; BootSource ← 0 (IOP memory) sta DiagBoot ; DiagBoot ← 0 sta LastBlockFlags ; LastBlockFlags ← 0 cma sta BootDevice ; Boot device ← undefined (-1) lxi h,uBlockPtrArray ; Initialize uBlockPtr shld uBlockPtr ; uBlockPtr ← uBlockPtrArray lxi h,0 shld MPOffset ; MPOffset ← 0 ; Initialize default pointer for IOP execution after end of a Phase other than Phase 0. lxi h,StartNextPhase ; Pointer to IOP start address shld StartIOPAddress ; Initialize IOP boot file pointer for Phase 0. ; This value is imported from the file: StartIOPBootRAM or StartIOPBootProm, ; depending on whether it is the RAM or Prom configuration. lhld StartIOPBoot ; Start of Boot file in IOP memory shld BootAddrIOP ; Initialize PCB's. lxi h,StartCPBootFile ; Start of Boot file in CP memory shld BootAddrCP shld BootPCB+CPAddr1 ; High part of address = 0 lxi h,0 shld MemPCB+CPAddr1 ; High part of address = 0 lxi h,1 shld MemPCB+CPCnt ; Count = 1 ret ; Subroutine: ReadAltBoot (in PreBoot*.asm). ; Subroutine: CheckAltBootDevice. ; Check the BootType. If non-rigid disk booting is specified, set BootDevice appropriately. ; If rigid disk booting, set BootDevice according to what the value of CPDevice is. ; Note that the range of BootType was checked in ReadAltBoot. ; AltBoot codes: ; 0 - diagnostic rigid disk booting (default if no AltBoot) ; 1 - rigid disk booting ; 2 - floppy disk booting ; 3 - ethernet booting ; 4 - diagnostic ethernet booting ; 5 - diagnostic floppy disk booting ; 6 - alternante ether booting ; 7 - diagnostic Trident1 booting ; 8 - diagnostic Trident2 booting ; 9 - diagnostic Trident3 booting ; Table of BootDevice values. Indexed by BootType. ; An entry is the BootDevice value or -1 if rigid disk booting. BootDeviceTable: db -1 ; 0: diagnostic rigid db -1 ; 1: rigid db BootFloppy ; 2: floppy db BootEthernet ; 3: ethernet db BootEthernet ; 4: diagnostic ethernet db BootFloppy ; 5: diagnostic floppy db BootAltEthernet ; 6: alternate ethernet db -1 ; 7: diagnostic Trident1 db -1 ; 8: diagnostic Trident2 db -1 ; 9: diagnostic Trident3 ; ENTRY point: CheckAltBootDevice: lda BootType mov c,a ; Form table index in B,C xra a mov b,a lxi h,BootDeviceTable ; Point to start of table dad b ; H,L ← Start of Table + BootType mov a,m ; Read value for BootDevice ora a ; Check for -1 jm CheckRigidBoot ; m => -1, thus rigid booting ; Booting not from rigid. NonRigidBoot: SetGenericBootDevice: sta GenericBootDevice SetBootDevice: sta BootDevice ret ; Booting is to be from the rigid disk. In order to determine what the BootDevice value is, ; we have to look at both BootType and CPDevices. ; Check also if no disk or more than one disk is specified in CPDevice. ; The following table is used to determine BootDevice: ; CPDevice ; AltBoot 4000 1000 Trident ; -------------- ---------------------------------------------------------------------------- ; 0 BootSA4000 BootSA1000 BootTrident0 ; 1 BootSA4000 BootSA1000 BootTrident0 ; 7 error error BootTrident1 ; 8 error error BootTrident2 ; 9 error error BootTrident3 ; C has BootType. CheckRigidBoot: lda CPDevices ; Check for no rigid disk ani DiskBootMask ; Mask disk bits jz NoDiskFound ; z => No disk indicated mov b,a ; B ← CPDevice (modified) mov a,c ; A ← BootType cpi AltFloppyBoot ; Check if less than AltFloppyBoot jc AltBootRigid ; c => BootType < AltFloppyBoot ; The AltBoot specified Trident 1, 2, or 3. Check value in CPDevices. AltBootTrident: mov a,b ; Get CPDevices cpi BootSA1000Mask ; Is it the SA1000? jz InvalidBootType ; z => No SA1000 on system cpi BootSA4000Mask ; Is it the SA4000? jz InvalidBootType ; z => No SA4000 on system cpi BootTridentMask ; Is it the Trident? jnz MultiDisksFound ; nz => more than one disk bit set ; Set the Trident as BootDevice. ; ***Note: assumes that AltTrident1Boot (BootType)=BootTrident1 (BootDevice), etc. SetTrident: mvi a,BootTrident0 ; Set GenericBootDevice to Trident0 sta GenericBootDevice mov a,c ; BootDevice ← BootType jmp SetBootDevice ; Set the implemented rigid disk drive as the BootDevice. AltBootRigid: mov a,b ; Get CPDevices cpi BootSA1000Mask ; Is it the SA1000? jz SetSA1000 ; z => SA1000 cpi BootSA4000Mask ; Is it the SA4000? jz SetSA4000 ; z => SA4000 cpi BootTridentMask ; Is it the Trident? jz SetTrident0 ; z => Trident0 ; Supposed to be a disk boot, but more than one disk bits were set. MultiDisksFound: mvi c,ErrorMultiDisksFound ; ERROR: Multi disk bits specified in Mem 0 jmp ErrorReport ; Supposed to be a disk boot, but no disk bits were set. NoDiskFound: mvi c,ErrorNoDiskFound ; ERROR: No disk bits specified in Mem 0 jmp ErrorReport ; AltBoot specifies Trident disk, but no Trident on system. InvalidBootType: mvi c,ErrorInvalidBootType ; ERROR: No Trident disk on system jmp ErrorReport ; Set the SA1000 as the boot device. SetSA1000: mvi a,BootSA1000 ; Set the SA1000 jmp SetGenericBootDevice ; Set the SA1000 as the boot device. SetSA4000: mvi a,BootSA4000 ; Set the SA4000 jmp SetGenericBootDevice ; Set the Trident0 as the boot device. SetTrident0: mvi a,BootTrident0 ; Set the Trident0 jmp SetGenericBootDevice ; Subroutine: InitCSTPCImage. ; Initialize the CS image with the instruction: ; GOTO [K1Entry]; ; where K1Entry = 0F8F. ; Initialize the TPC array with all slots empty, i.e. high bit of word = 1. InitCSTPCImage: ; First do the TPC image. mvi c,8 ; Counter for 8 words mvi e,0 ; Initialize TPC slot to 8000H (empty) mvi d,80H lxi h,TPCBuffer ; Start of Buffer InitTPCImageLoop: mov m,e ; Store low byte inx h mov m,d ; Store high byte inx h dcr c ; More TPC's? jnz InitTPCImageLoop ; nz => More to do ; Initialize the CSImage. ; Set up the instruction counter. lhld CSImageSize ; Initialize count to value in CSImageSize xchg ; D,E has count lhld CSImageStart ; Initialize pointer to image InitCSImageLoop: mvi a,DefaultCS0 ; Byte 0 mov m,a inx h mvi a,DefaultCS1 ; Byte 1 mov m,a inx h mvi a,DefaultCS2 ; Byte 2 mov m,a inx h mvi a,DefaultCS3 ; Byte 3 mov m,a inx h mvi a,DefaultCS4 ; Byte 4 mov m,a inx h mvi a,DefaultCS5 ; Byte 5 mov m,a inx h dcx d ; End of loop? mov a,e ; Check for count = 0 ora d ; Low OR high jnz InitCSImageLoop ; nz => nonzero ret ; Subroutines to read the next word from the boot file. ; The boot file can be in EProm or in main memory or on the floppy. ; If the boot file is in main memory then the CP transfer needs to be initialized. ; StartNextRead is used to initialize the transfer in this case. ; The location of the boot file is determined by the value of BootSource: ; 0: IOP memory ; 1: CP memory ; 2: Floppy streaming ; Subroutine: StartNextRead [H,L: word count]. ; Start the next transfer from the boot file if in Main memory. ; BootSource=0 or 2 - do nothing ; BootSource=1 - initialize transfer through the CP port. ; On entry: H,L - number of words to be read in the next transaction. StartNextRead: lda BootSource ; Check boot source for main memory cpi BootSourceCP jz StartNextCP ; z => Boot file in main memory cpi BootSourceFloppy rz ; z => Boot file on Floppy, do nothing cpi BootSourceIOP rz ; z => IOP memory, do nothing jmp UnimplBootSource ; ERROR: undefined boot source ; Boot file is in main memory. Initialize the CP transfer. ; Format of initialize: ; Command, low address, middle address, high address, low [count], high [count]. ; Command is ReadCP memory. ; CP address in BootAddrCP (in BootPCB) ; CP count in CPCount. StartNextCP: shld BootPCB+CPCnt ; Save the count. mvi a,CPReadCmd ; Read command lxi h,BootPCB ; Point to the Boot PCB jmp InitCPCmd ; First byte is command ; Jump to InitCPCmd subroutine and Return. ; Subroutine: GetNextWord [H,L: Pointer to IOP buffer]. ; Get the next word from the boot file. ; The location of the boot file is determined by the value of BootSource: ; 0: IOP memory ; 1: CP memory ; 2: Floppy streaming ; IOP memory - Next boot file word pointer in BootAddrIOP. ; BootAddrIOP is incremented. ; CP memory - Next boot file word pointer in BootAddrCP. ; Read the word from the port, and increment the CP address. ; Note: assume no 64K crossing. ; Floppy Streaming - Next word in the floppy buffer, or take a disk fault. ; Check buffer count; if zero, start the next disk transfer, else return the next word in buffer. ; On entry: H,L - Address of the IOP buffer in which the word is to be placed. ; On exit: H,L - Address of the next word after the IOP buffer in which the word was placed. GetNextWord: lda BootSource ; Check boot source for main memory cpi BootSourceIOP jz GetNextIOP ; z => IOP memory cpi BootSourceCP jz GetNextCP ; z => CP memory cpi BootSourceFloppy jz GetNextFloppy ; z => Floppy ; Undefined boot source. UnimplBootSource: mvi c,ErrorUnimplBootSource ; ERROR: Unimplemented BootSource jmp ErrorReport ; Next word from the CP port. ; It is assumed that the transaction has been initiated. Read the data from the port ; and stores the data into the IOP buffer. ; H,L Points to the IOP buffer to which the word should be transferred. GetNextCP: call ReadCPbyte ; Get byte from port (returned in A) mov m,a ; Store in low byte inx h ; Point to the high byte call ReadCPbyte ; Get byte from port (returned in A) mov m,a ; Store in high byte inx h ; Point to the next byte push h ; Save H,L temporarily lhld BootAddrCP ; Increment the CP address inx h shld BootAddrCP ; Restore pointer in memory pop h ; Restore H,L ret ; Next word from the IOP memory. ; H,L Points to the IOP buffer to which the word should be transferred. ; BootAddrIOP points to the next location in the boot file. GetNextIOP: xchg ; D,E ← Pointer to IOP buffer lhld BootAddrIOP ; H,L ← Pointer to boot file mov a,m ; Low byte stax d ; Store in destination inx h ; Increment pointers inx d mov a,m ; High byte stax d ; Store in destination inx h ; Increment pointers inx d shld BootAddrIOP ; Update pointer in memory xchg ; H,L ← Pointer to next word after IOP buffer ret ; Next word from the Floppy buffer. ; H,L Points to the IOP buffer to which the word should be transferred. ; FloppyBufPtr points to the next location in the boot file (in floppy buffer). GetNextFloppy: xchg ; D,E ← pointer to buffer where word is to be returned lhld FloppyBufCnt ; Check number of words left in floppy buffer xra a cmp l ; Check low part jnz GetNextBuffer ; nz => buffer count is not zero, return word from buffer ; Low part is zero, check the high part. cmp h ; Check high part jnz GetNextBuffer ; nz => buffer count is not zero, return word from buffer ; The word count is zero. Fetch the next run of sectors from the disk. GetNextDisk: push d ; Save D,E (pointer to word buffer) call ReadSectorRun pop d ; Restore D,E (pointer to word buffer) ; The word is in the Floppy Buffer. D,E still points to the word buffer. GetNextBuffer: lhld FloppyBufPtr ; Store word in specified word buffer D1: inx d ; Point to high byte in buffer (Byte swap) ; nop ; (Non byte swap) mov a,m ; Low byte stax d ; Store in destination inx h ; Increment pointers D2: dcx d ; Point to low byte in buffer (Byte swap) ; inx d ; Point to high byte in buffer (Non Byte swap) mov a,m ; High byte stax d ; Store in destination inx h ; Increment pointers inx d D3: inx d ; Point to next byte in buffer (Byte swap) ; nop ; (Non byte swap) shld FloppyBufPtr ; Update pointer in memory lhld FloppyBufCnt ; Decrement the Floppy word count dcx h shld FloppyBufCnt xchg ; H,L ← Pointer to next word after word buffer ret ; Subroutine: WriteCS. ; Write the microinstruction in CSBuffer in either control store or a control store image ; in IOP memory. If the CS address is greater than or equal to the value in CSIMageSize, ; then write the microinstruction in the control store. Otherwise write the ; microinstruction in the image. ; Thus, CSAddress - CSImageSize < 0 => In overlay area ; and, CSAddress - CSImageSize >= 0 => Out of overlay area ; The current CS address is in CSAddress. ; If the write is directly into control store, and BootMode=0, ; then first stop the CP, and then restart it. WriteCS: lxi d,CSAddress ; D,E points to CSAddress lxi h,CSImageSize ; H,L points to CS address ldax d ; Subtract low bytes sub m ; We don't care about result, only the sign inx d ; Point to high bytes inx h ldax d ; Subtract high bytes sbb m ; Check sign of the difference: jm DoCSImage ; m => CSAddress - CSImageSize < 0 => In overlay area ; Write the microinstruction directly into control store. ; Use TPC [CSTask] for CS addressing. lda BootFlags ; Check whether BootMode=1 ani BootMode cz StopCP ; z => Not BootMode, Stop the CP lhld CSAddress ; H,L ← CS address xchg ; move to D,E mvi c,CSTask ; Use special TPC for control store addressing (to C) call DoWriteTPC ; Write the TPC ; Now write the location. lxi d,CSBuffer ; Point to the CS buffer call DoWriteCS ; Write the CS location lda BootFlags ; Check whether BootMode=1 ani BootMode cz StartCP ; Restart the CP ret ; Microinstruction is to be written into the image area in IOP memory. ; Write the instruction starting at IOP address: CSImage + 6*CSAddress. DoCSImage: lhld CSAddress ; H,L ← CSAddress dad h ; H,L ← 2*CSAddress mov e,l mov d,h ; D,E ← 2*CSAddress dad h ; H,L ← 4*CSAddress dad d ; H,L ← 6*CSAddress xchg ; D,E ← 6*CSAddress lhld CSImageStart ; H,L ← Pointer to start of CSImage dad d ; H,L ← CSImage + 6*CSAddress ; Now transfer the instruction into the image. ; H,L - pointer into the image. ; D,E - pointer to CSBuffer. ; C - byte counter lxi d,CSBuffer ; D,E ← Ptr to CSBuffer mvi c,6 ; 6 bytes WriteCSImageLoop: ldax d ; Get the byte from CSBuffer mov m,a ; Store in the image inx h ; Increment the pointers inx d dcr c ; Are we done? jnz WriteCSImageLoop ; nz => more bytes ret ; Subroutine: TransferCSImage. ; Transfer the control store image in IOP memory to the control store, ; starting at control store location 0. ; It is assumed that the CP is in the kernel. ; The control store image starts at location CSImage. ; Number of microinstructions in the image is given by CSImageSize. ; Current CS address in CSAddress. ; Counter for microinstructions in CSImageCnt. ; Pointer to next micronstructionin the image in CSImagePtr. TransferCSImage: lhld CSImageStart ; Initialize pointer to start of CSImage shld CSImagePtr lxi h,0 ; Initialize CS address to 0 shld CSAddress lhld CSImageSize ; Initialize count to value in CSImageSize shld CSImageCnt TransferCSLoop: ; Use TPC [CSTask] for CS addressing. lhld CSAddress ; H,L ← CS address xchg ; move to D,E mvi c,CSTask ; Use special TPC for control store addressing (to C) call DoWriteTPC ; Write the TPC ; Now write the location. lhld CSImagePtr ; Point to the next instruction in the image xchg ; D,E ← H,L (Pointer to next instruction) call DoWriteCS ; Write the CS location xchg ; H,L ← D,E (Pointer to next instruction) shld CSImagePtr ; Store back in memory lhld CSAddress ; Increment CS address inx h shld CSAddress lhld CSImageCnt ; Get count and check if zero dcx h shld CSImageCnt mov a,l ; Check for count = 0 ora h ; Low OR high jnz TransferCSLoop ; nz => nonzero ret ; Subroutine: TransferTPCImage. ; Transfer the TPC entries that were filled during this boot phase into the TPC's. ; There is a table of 8 TPC entries, starting at TPCBuffer. ; The table is initialized with the high bit = 1, indicating empty. ; If an entry is inserted, then the high bit will be 0. ; Pointer to TPC buffer area is in TPCBufPtr. ; TPC address is TPCAddress. ; It is assumed that the CP is in the kernel. ; NOTE: If the BootType is floppy booting AND Phase =2 then add 1 to TPC 0, ; top start the emulator at Go instead of Germ. ; THis will be removed when the germ is changed to know about Floppy booting. TransferTPCImage: ;*** Kludge : Fix TPC 0 if Phase=2 AND (BootType = AltFloppyBoot or AltDiagFloppyBoot). lda Phase cpi 2 ; Phase 2? jnz ContTPCTransfer ; nz => not Phase 2 ; Phase 2: Check BootType. lda BootType cpi AltFloppyBoot jz FixTPC0 ; z => It is a floppy boot cpi AltDiagFloppyBoot jz FixTPC0 ; z => It is a floppy boot ;*** End Kludge. ContTPCTransfer: lxi h,TPCBuffer ; Initialize TPCBufPtr to start of TPCBuffer shld TPCBufPtr xra a ; Initialize TPCAddress to 0 sta TPCAddress TransferTPCLoop: lhld TPCBufPtr ; H,L ← Ptr to next TPC slot mov e,m inx h mov d,m ; D,E ← TPC value inx h ; H,L ← Ptr to next TPC slot shld TPCBufPtr ; Store back mov a,d ; Check high nibble of slow for empty or full ora a jm NextTPCSlot ; m => Slot is still empty lda TPCAddress ; C ← TPC address mov c,a call DoWriteTPC ; Write the TPC NextTPCSlot: lda TPCAddress ; Increment TPC address and check for done inr a sta TPCAddress cpi 8 ; Is TPC address = 8 (done)? jnz TransferTPCLoop ; nz => Still more to do ret ; Increment TPC0. FixTPC0: lhld TPCBuffer+0 inx h shld TPCBuffer+0 jmp ContTPCTransfer ; Subroutine: SetupUregisters. ; Interpret the U register blocks that were found in the Boot file. ; Pointers to the 2nd word in each block were saved during phase 0 in uBlockPtrArray. ; uBlockCnt contains the number of blocks. SetupUregisters: lxi h,uBlockPtrArray ; Point to start of array shld uBlockPtr ; Store in pointer lda uBlockCnt ora a ; Set flags jmp CheckUCnt SetupULoop: call InterpretUBlock lda uBlockCnt dcr a sta uBlockCnt CheckUCnt: jnz SetupULoop ret ; Subroutine: InterpretUBlock. ; Interpret a particular U block. ; Note: This subroutine assumes Phase 0 activity, i.e. Boot file in IOP memory. InterpretUBlock: lhld uBlockPtr ; Get pointer to uBlock Ptr mov e,m ; Get the pointer inx h mov d,m ; D,E ← Pointer to uBlock inx h shld uBlockPtr ; Store back pointer to next pointer in array xchg ; H,L ← pointer to uBlock in IOP memory shld BootAddrIOP ; Fix boot file pointer ; Note: No StartNextRead called, since it is assumed to be Phase 0. lxi h,Header ; Get next word into Header call GetNextWord lda Header ani uBootDeviceMask ; Mask Boot device jz DoSetUBlock ; z => an unconditional u block ; The U block was not unconditional. Check if it matches the GenericBootDevice rrc ; Right align the BootDevice rrc rrc rrc lxi h,GenericBootDevice ; Compare with GenericBootDevice cmp m jz DoSetUBlock ; z => BootDevice matched ; Not appropriate boot device. ret ; Interpret the U block. ; First get the 16 words of U regsiter values. ; Format to CP is: command, uBlock number, 16 words of U register value. DoSetUBlock: mvi c,16 ; Set up a counter in C lxi h,uBlockBuffer ; Point to buffer area GetUValLoop: call GetNextWord ; Store in buffer (H,L updated) dcr c ; Done? jnz GetUValLoop ; nz => Not done yet ; We are now ready for the CP command. mvi a,CPLoadUCmd ; First byte is command call WriteCPbyte lda Header ; Send the block number ani uBlockMask call WriteCPbyte ; Now send the 16 words of data. mvi c,16 ; Set up a counter in C lxi h,uBlockBuffer ; Point to buffer area SetUValLoop: mov e,m ; Low part inx h mov d,m ; High part inx h call WriteCPword ; Send to port dcr c ; Done? jnz SetUValLoop ; nz => Not done yet ret ; Subroutine: InformCPBootDevice. ; Inform the CP that the U registers were loaded. ; Also provide the host address, value of DiagBoot, and BootDevice. ; Format of command: command, host address (6 bytes), DiagBoot, EPromVersion/BootDevice. InformCPBootDevice: mvi a,CPSetBootCmd ; Command call WriteCPbyte ; First byte is command ; Send the host address: lxi d,8000H+HostAddr ; Point to the HAddr prom (high word) call SendHostWord ; Send high host address word call SendHostWord ; Middle word call SendHostWord ; Low word ; Inform CP of DiagBoot. lda DiagBoot ; Send DiagBoot to CP as index call WriteCPbyte ; Inform CP of EPromVersion/BootDevice. lda BootDevice lxi h,EPromVersionLoc ; Point to shifted Version number ORVersionNo: {Change to "ana m" for debugging with version 2.4 EProms} ora m ; Merge with BootDevice jmp WriteCPbyte ; Jump to WriteCPbyte subroutine and Return. ; Subroutine: SendHostWord. ; Read a host address word from the host address prom and transmit to CP port. ; The prom has 12 nibbles containing the 48-bit host address, plus an 8-bit checksum. ; (Checksum is not checked.) ; ; Register usage: ; H,L - Word buffer ; D,E - Host address prom pointer ; C - byte counter ; ; On entry: D,E points to the first nibble of the Host word to be read. ; On exit: D,E points to the first nibble of the next Host word to be read. SendHostWord: lxi h,HeaderHi ; Use Header as a buffer, point to high byte mvi c,2 ; Count of 2 bytes ReadHAddrLoop: ldax d ; Get low nibble of byte inx d ; Point to next nibble ani 0FH ; Mask out high part mov b,a ; Store in B ldax d ; Get high nibble of byte inx d ; Point to next nibble ani 0FH ; Mask out high part rlc ; Move to high part of byte rlc rlc rlc ora b ; Form byte in A mov m,a ; Store in buffer dcx h ; Point to next byte up dcr c ; End of loop? jnz ReadHAddrLoop ; nz => still more to do ; Header has the word. Write to port. lhld Header xchg ; D,E ← address word, H,L ← Prom address call WriteCPword ; To CP xchg ; D,E ← Prom address ret ; Subroutine: ReadMainMem. ; Read the contents of a main memory location in the first 64K space. ; On entry: H,L = low 16 bits of address. ; On exit: low byte in A, high byte in B. ReadMainMem: shld MemPCB+CPAddr3 ; Store address in FlagPCB mvi a,CPReadCmd ; Read command lxi h,MemPCB ; Point to the FlagPCB call InitCPCmd ; First byte is command ; Now get the contents. call ReadCPbyte ; Get low byte from port (returned in A) mov c,a ; Save temporarily in C call ReadCPbyte ; Get high byte from port (returned in A) mov b,a ; Store in high byte mov a,c ; Return low byte in A ret ; Subroutine: CheckCPStopped. ; Check if CP is stopped, i.e. in CP kernel. ; If it is not, stop the CP. CheckCPStopped: lda BootFlags ; Check value in BootFlags ani CPStopped rnz ; nz => CP already stopped ; CP is not stopped. Send it back to the kernel. jmp StopCP ; Jump to StopCP subroutine and Return. ; Subroutine: DoWriteCS. ; Write the CS location. ; In BootMode the CS location is written. ; In non-BootMode, IOPWait is set, a CS byte is written, ; the CP re-enabled, and a Refresh is requested. Repeated for each byte. ; (Maximum time that IOPWait should be set is 80 340 ns IOP cycles, 92 333 ns cycles.) ; On entry: Assumes that the TPC[6] has been set up correctly. ; CS data is in a CSBuffer [0..5], (MSB..LSB). ; where D,E is pointing to the buffer. ; On exit: ; D,E points to the next byte after current CS buffer (useful for image transfer) ; All registers are used. ; Register usage: ; H,L - Points to CPControl (memory mapped I/O) ; B,C - Points to CSa-CSe (memory mapped I/O) ; D,E - Points to CSBuffer DoWriteCS: lxi h,8000H+CPControl ; Set H,L to point to CPControl (memory mapped I/O) lxi b,8000H+CSa ; Set B,C to point to CSa-CSe (memory mapped I/O) ; Check for BootMode. lda BootFlags ; Check whether the CP kernel is running (BootMode) ani BootMode ; jnz BootDoWriteCS ; BootMode (nz) => no CP control, Refresh ; Not BootMode. mvi a,6 ; Counter for 6 bytes DoWriteCSLoop: sta CSCount ; Use memory for CS byte counter mvi m,CPWait ; Set IOPWait in CPControl ; CP now in WAIT state. mvi m,CPWaitSwT ; [10] Set IOPWait, SwTAddr in CPControl ldax d ; [7] Get byte into A cma ; [4] Complement for CP LS240 stax b ; [7] Output CS byte mvi m,CPWait ; [10] Set IOPWait, clear SwTAddr in CPControl mvi m,CPEnable ; [10] Clear IOPWait, SwTAddr in CPControl ; CP now out of WAIT state. In wait state for 48 IOP cycles. mvi a,CPRefresh ; Request refresh call WriteCPbyte NextWriteCS: inx b ; Point to next CS I/O slot inx d ; Point to next CS buffer location lda CSCount ; Decrement CS byte counter dcr a jnz DoWriteCSLoop ; nz => still more ret ; In BootMode. IOPWait is true. BootDoWriteCS: mvi a,6 ; Counter for 6 bytes BootDoWriteCSLoop: sta CSCount ; Use memory for CS byte counter ldax d ; Get byte into A cma ; Complement for CP LS240 stax b ; Output CS byte inx b ; Point to next CS I/O slot inx d ; Point to next CS buffer location lda CSCount ; Decrement CS byte counter dcr a jnz BootDoWriteCSLoop ; nz => still more ret ; Subroutine: DoWriteTPC ; Write the TPC. ; In BootMode the TPC is written. ; In non-BootMode, IOPWait is set, TPC is written, ; the CP re-enabled, and a Refresh is requested. ; (Maximum time that IOPWait should be set is 80 340 ns IOP cycles, 92 333 ns cycles.) ; On entry: C contains the TPC address (3 bits right-justified) ; DE contains the TPC data (12 bits right-justified) ; Format of TPCHigh (write): TPCAddr[0:2],,TPCData[0:4]' ; Format of TPCLow (write): don't care,,TPCData[5:11]' ; Compute the values of TPCHigh, TPCLow beforehand so that the ; CP is kept with IOPWait high for a minimum of time. DoWriteTPC: call LeftAlignTPCAddr ; Left align 3 bits of address in C mov a,e ; Move TPC[4] into B for TPCHigh format ral ; TPC[4] into carry mov a,d ; get high part ral ; TPC[4] into B[7] cma ; complement for port ani 1FH ; Clear High 3 bits ora c ; OR in address ; Value in C not needed again. mov d,a ; Store back in D mov a,e ; Get low part (E[0] is don't care) cma ; complement for port mov e,a ; Store back in E lxi h,8000H+CPControl ; Set H,L to point to CPControl (memory mapped I/O) lxi b,8000H+TPCHigh ; Set B,C to point to CPControl (memory mapped I/O) ; Check for BootMode. lda BootFlags ; Check whether the CP kernel is running (BootMode) ani BootMode ; jnz BootWriteTPC ; BootMode (nz) => no CP control, Refresh mvi m,CPWait ; Set IOPWait in CPControl ; CP now in WAIT state. mvi m,CPWaitSwT ; [10] Set IOPWait, SwTAddr in CPControl mov a,d ; [4] Get high part stax b ; [7] Output TPCHigh (address, high data) inx b ; [6] point to TPCLow mov a,e ; [4] Get low part stax b ; [7] Output TPCLow (low data) mvi m,CPWait ; [10] Set IOPWait, clear SwTAddr in CPControl mvi m,CPEnable ; [10] Clear IOPWait, SwTAddr in CPControl ; CP now out of WAIT state. In wait state for 58 IOP cycles. mvi a,CPRefresh ; Request refresh jmp WriteCPbyte ; Jump to WriteCPbyte subroutine and Return. ; In BootMode. IOPWait is true. BootWriteTPC: mov a,d ; Get high part stax b ; Output TPCHigh (address, high data) inx b ; point to TPCLow mov a,e ; Get low part stax b ; Output TPCLow (low data) ret ; Subroutine to left align TPC address in C. LeftAlignTPCAddr: mov a,c ani 7H ; Clear top bits rrc rrc rrc mov c,a ret ; Subroutine: StartCPKernel and StopCP. ; Set IOPWait=0, SwTAddr=0, clear BootMode, set CPStopped in BootFlags. ; StartCPKernel: ; Start CP after CPKernel loading. ; On entry: IOPWait = SwTAddr = 1. ; StopCP: ; Stop CP execution. ; Toggle IOPWait in CPControl, in order to cause CP to enter the CPKernel. ; On entry: IOPWait = SwTAddr = 0. StopCP: StartCPKernel: mvi a,CPWait ; Clear SwTAddr (Set IOPWait) in CPControl out CPControl mvi a,CPEnable ; Clear IOPWait, SwTAddr in CPControl out CPControl mvi a,NoBootMode+CPStopped ; BootMode=0, CPStopped=1 sta BootFlags ret ; Subroutine: StartCP. ; Start CP execution, by issuing the ExitKernel command. ; Force the CP to exit the CP Kernel. StartCP: mvi a,CPExitKernel ; Command to CPKernel to exit CPKernel call WriteCPbyte ; Clear out flags in BootFlags. mvi a,NoBootMode ; BootMode=0, CPStopped=0 in BootFlags sta BootFlags ret ; Subroutine: InitCPCmd. ; Initialize CP port for reads or writes. ; On entry: ; A = command to microcode ; H,L = Pointer to CPport Control Block ; Format of initialize: ; Command, low address, middle address, high address, low [count], high [count]. InitCPCmd: call WriteCPbyte ; First byte is command lxi b,CPAddr3 ; Index in control block of CP buffer address dad b ; H,L points to Low 16 bits of CP buffer address mov e,m ; Address byte 3 inx h mov d,m ; Address byte 2 call WriteCPword ; Send to port inx h mov a,m ; High part address (address byte 1) ani CP64KMask ; Mask for address size call WriteCPbyte ; Send to port ; Note the high byte, which is always 0, is not transmitted to CP. inx h ; Ignore top address byte inx h ; Point to count mov e,m ; Low part count inx h mov d,m ; High part count dcx d ; Microcode wants count-1 jmp WriteCPword ; Send to port ; Jump to WriteCPword subroutine and Return. ; CPport data transfer subroutines. ; Subroutine: ReadCPbyte. ; Read CP byte. ; Byte returned in A register. ReadCPbyte: in CPStatus ; Read the port interrupt bits ani CPInIntMask ; CPIn requesting an interrupt? jz ReadCPbyte ; Zero means no interrupt in CPIn ; get data ret ; Subroutine: ReadCPword. ; Read CP word. ; Word returned in D,E. ReadCPword: call ReadCPbyte mov e,a call ReadCPbyte mov d,a ret ; Subroutine: WriteCPbyte. ; Write CP byte. ; Byte in A register written into port. WriteCPbyte: out CPOut ; Output data WaitCPOutAck: in CPStatus ; Read the port interrupt bits ani CPOutIntMask ; CPOut requesting an interrupt, i.e data read? jz WaitCPOutAck ; Zero means no interrupt ret ; Subroutine: WriteCPword. ; Write CP word. ; Word in D,E written into port. ; NOTE: Least significant byte written first through port. WriteCPword: mov a,e call WriteCPbyte mov a,d jmp WriteCPbyte ; Jump to WriteCPbyte subroutine and Return. ; Subroutine: ByteToWord [H,L: word] RETURNS [H,L: word]. ; Convert value in H,L (byte count) to word count. ; i.e. long shift right by 2. ByteToWord: ora a ; Clear carry mov a,h ; High part rar ; shift high part right, low bit into carry mov h,a mov a,l ; Low part rar ; shift low part right, shift in high bit mov l,a ret ; ; FLOPPY DISK subroutines. ; Constant data variables: SectorRunSize: db SectorNo ; Number of sectors read in a run SectorSize: dw SectorLen ; Sector size (bytes) MaxBufCnt: dw (SectorNo*SectorLen)/2 ; Size of Floppy Buffer (words) MaxSector: db MaxSectorNo+1 ; Maximum sector number+1 Density: db DDen ; Density: 0 = single, 1 = double FloppyBuffer: dw FloppyBuf ; Pointer to start of Floppy buffer ; Subroutine: ReadSectorRun. ; The floppy buffer was empty. Read the next run of sectors from the disk into the floppy buffer. ; If the end of a track is encountered before the end of the run, then stop the run. ; ; On entry: ; The disk address for the next access is in DSide, DCylinder (1 word), and Sector. ; The current disk head position is in Side, and Cylinder (1 word). ; The size of the sector run is in SectorRunSize. ; On exit: ; The disk address for the next access is in DSide, DCylinder (1 word), and Sector. ; The current disk head position is in Side, and Cylinder (1 word). ReadSectorRun: lda SectorRunSize ; Initialize: SectorCnt ← SectorRunSize sta SectorCnt lhld FloppyBuffer ; Initialize the buffer pointer of the ReadSector command shld FloppyBufPtr lxi h,0 ; Initialize: FloppyBufCnt ← 0 shld FloppyBufCnt ; Check if the Side should be switched. CheckSideSwitch: lxi h,Side ; Point to current Side lda DSide ; Get desired Side cmp m cnz DoSwitchSide ; nz => different, switch the side select ; Check if a seek is needed. CheckSeek: lxi h,Cylinder ; Point to current cylinder lda DCylinder ; Assumes high part of Cylinder=0 cmp m cnz DoSeekCmd ; nz => different, do a seek, update Cylinder ; Now do the read sector. ; Buffer pointer in FloppyBufPtr, sector size in SectorSize, sector number in Sector. ReadSectorLoop: lda Sector mov d,a ; Desired sector in D lda ReadSectorCom ; ReadSector command to E mov e,a mvi a,RetryNo ; Initialize retry counter sta RetryCount ReadAgain: call DoReadSector ; Do the read, soft error status returned in A ; (A) = 0 for no errors, (A) # 0 for soft errors (with errors = 1). ora a jz ReadSectorGood ; z => no errors ; We had a soft error. Try again. (Later: restore and try again) DecrReadRetry: lxi h,RetryCount dcr m jnz ReadAgain ; z => Too many retries ; Too many retries. Report error. ReadSectorFail: mvi c,ErrorReadSectorFail jmp ErrorReport ; ERROR: Too many read sector failures. ; The sector was read without error. ; Increment the Buffer count, by the sector size. ; Check if more sectors are too be read. ReadSectorGood: lhld SectorSize ; Update FloppyBufCnt call ByteToWord ; Convert to words xchg ; D,E ← Sector size (words) lhld FloppyBufCnt ; H,L ← Buffer count dad d ; H,L ← Buffer count + Sector size (words) shld FloppyBufCnt ; Store Buffer count lxi h,SectorCnt ; Decrement the sector count dcr m jz SectorRunDone ; z => Sector run completed ; Still more sectors to be read. Fix the next disk address. ; Increment the Buffer pointer, by the sector size. MoreSectors: lhld SectorSize ; Update BufferPtr for next transfer xchg ; D,E ← Sector size (bytes) lhld FloppyBufPtr ; H,L ← Buffer pointer dad d ; H,L ← Buffer pointer + Sector size shld FloppyBufPtr ; Store buffer pointer lxi h,Sector ; Increment sector number inr m lda MaxSector ; Get maximum sector number+1 cmp m jz TrackCross ; z => Sector number = MaxSector+1, i.e. end of track jmp ReadSectorLoop ; Do it again ; All the sectors in the run have been correctly read. ; Form the next disk address in DCylinder, Sector. ; Initialize the buffer pointer and count for GetNextWord. SectorRunDone: lhld FloppyBuffer ; Initialize the buffer pointer of the next GetNextWord shld FloppyBufPtr lxi h,Sector ; Increment sector number inr m lda MaxSector ; Get maximum sector number+1 cmp m jz FormNextDiskAddress ; z => end of cylinder ; If zero, jump to FormNextDiskAddress subroutine and Return. ; Sector did not cross track boundary. Set DCylinder ← Cylinder. lda Cylinder sta DCylinder ret ; The next sector crossed a track boundary. Terminate the run of sectors. ; Form the next disk address, and initialize the Buffer pointer for GetNextWord. TrackCross: lhld FloppyBuffer ; Initialize the buffer pointer for the next GetNextWord shld FloppyBufPtr ; Jump to FormNextDiskAddress subroutine (**below) and Return. ; Subroutine: FormNextDiskAddress. ; Form the disk address of the beginning of the next track. ; Single-sided disk: ; Sector ← 1; ; DCylinder ← Cylinder + 1; ; Double-sided disk: ; Sector ← 1; ; IF Side = 0 THEN DSide ← 1 ; ELSE {i.e. Side = 1} BEGIN DSide ← 0; ; DCylinder ← Cylinder + 1; ; END; FormNextDiskAddress: mvi a,1 ; Sector ← 1; sta Sector in FDCStatusReg ; Check in external status reg. whether single or double-sided ani FDCTwoSidedMask jz DoSingleSide ; z => single sided ; Double sided disk. lda Side xri Side1Mask sta DSide ; DSide ← Side xor Side1Mask rnz ; nz => DSide =1, i.e. was 0 ; DSide = 0, i.e. was 1. Increment the cylinder number. DoSingleSide: lda Cylinder inr a sta DCylinder ret ; Subroutine: DoSwitchSide. ; Switch the side select values in Side and FDCState. ; On entry: H,L points to Side ; A = DSide DoSwitchSide: mov m,a ; Side ← DSide ora a ; Check if desired side is 0 or 1 jnz SetSide1 ; nz => DSide is 1 SetSide0: mvi a,nFDCSide1Mask ; Set Side 0 (clear Side 1 bit) call ClearFDCStateBit lxi h,ReadSectorCom ; Fix up ReadSector Cmd mvi a,nType2SMask ; Clear S bit ana m EndSwitchSide: mov m,a ; Delay to wait for head settling time. lxi h,1 call Delay ret SetSide1: mvi a,FDCSide1Mask ; Set Side 1 call SetFDCStateBit lxi h,ReadSectorCom ; Fix up ReadSector Cmd mvi a,Type2SMask ; Set S bit ora m jmp EndSwitchSide ; Subroutine: DoSeekCmd. ; A seek is needed. Do the command, and retry if errors occur. ; On entry: ; Cylinder has current cylinder. ; DCylinder has desired cylinder. ; The sector number is greater than the maximum on a track. Move to the next track. DoSeekCmd: lda DCylinder ; Desired cylinder to D mov d,a lda SeekCom ; Seek command to E mov e,a mvi a,RetryNo ; Initialize retry counter sta RetryCount SeekAgain: call DoSeek ; Do the seek, soft error status returned in A ; (A) = 0 for no errors, (A) # 0 for soft errors (with errors = 1). ; Track location was updated if command was successful. ora a rz ; z => Seek was successful, return ; We had a soft error. Try again. (Later: restore and try again) DecrSeekRetry: lxi h,RetryCount dcr m jnz SeekAgain ; z => Too many retries ; Too many retries. Report error. SeekFail: mvi c,ErrorSeekFail jmp ErrorReport ; ERROR: Too many Seek failures. ; Subroutine: DoReadSector. ; Implement Read Sector command. ; Assumes Head at track and Track Register up to date. ; On entry: ; D = Desired sector ; E = Command ; On exit: ; A = Internal FDC completion status ; Call subroutine at DoReadAddressTrack, for Read Address or Read Track. DoReadSector: mov a,d ; Set up sector for ReadSector command out FDCSector ; Set desired sector in FDCSector Register ; Program channel 0 of Dma controller for memory writes. ; All interrupts are disabled: ; di ; Disable interrupts while programming Dma lhld FloppyBufPtr mov a,l ; Program low buffer address out DmaCh0Addr mov a,h ; Program high buffer address out DmaCh0Addr lhld SectorSize dcx h ; Decrement for Dma mov a,l ; Program low Count out DmaCh0Count mov a,h ; Program high Count ori DmaWriteBit ; OR in function (memory writes) out DmaCh0Count mvi a,EnFloppyChannel ; Enable channel 0: TCS, EW, EN0 out DmaMode ; ei ; Re-enable interrupts ; Dma channel is now programmed and enabled. DoReadContinue: mov a,e ; Get FDC command call SendCommand ; Issue command ; Command has ended. Check completion status. ; Check for error completion (internal status in A, external in B). ; (Not Ready, RNF, CRC error, Lost Data, Busy) ani ReadErrorMask sta DiskStatus ; Store away ani ReadHardMask ; (Not Ready, Busy) jz DoReadGood ReadHardError: mvi c,ErrorReadHardError jmp ErrorReport ; ERROR: Read Sector hard error ; Correct FDC termination. ; If we had an RNF or LostData error then we shouldn't check for DMA completion. DoReadGood: lda DiskStatus ; Restore status ani FDCRNFMask jnz DoReadDone ; Don't check Dma lda DiskStatus ; Restore status ani FDCLostData jnz DoReadDone ; Don't check Dma ; Check that DMA terminated. mov a,b ; B has external Status Reg value ani FDCEndCountMask jnz GoodDmaEndCount1 NoDmaEndCount1: mvi c,ErrorNoDmaEndCount1 jmp ErrorReport ; ERROR: External Dma End Count register not set ; Check internal Dma status to ensure completion. GoodDmaEndCount1: in DmaStatus ; Read internal Dma Status ani DmaCh0Mask jnz DoReadDone ; nz => channel completed NoDmaEndCount2: mvi c,ErrorNoDmaEndCount2 jmp ErrorReport ; ERROR: Internal Dma End Count not set. ; Read sector done without any hard errors. DoReadDone: xra a out DmaMode ; Disable Dma lda DiskStatus ; Return status ret ; Subroutine: DoSeek. ; Issue seek command. Assumes FDC Track register is up-to-date. ; Checks validity of track number on completion. ; Update DDen bit in FDCState for dsestination cylinder. ; Note that PreComp bit does not have to be changed since we do no writing. ; On entry: ; D = Desired Cylinder ; E = Command ; On exit: ; A = Internal FDC completion status DoSeek: mov a,d ; Check if desired Track OK cpi 77 jc SeekCylinderOK TrackToBig: mvi c,ErrorTrackToBig jmp ErrorReport ; ERROR: Track number is too large. ; Track number is less than 77. ; Output desired cylinder for Seek. SeekCylinderOK: out FDCData ; Set desired track in Data Register for Seek ; Cylinder number is OK. Check the value of the destination cylinder and update FDCState. DoType1Cmd: ; Check if Cylinder 0 and force to single density. mov a,d ; Get cylinder number ora a jz SetSD ; z => At cylinder 0, force single density ; The head is not at cylinder 0. Check the Density. lda Density ; Check for double density ora a jz SetSD ; z => single density, i.e. no DDen, no Precomp. ; Double density. Set DDen bit. mvi a,FDCDDenMask ; Set the DDen bit call SetFDCStateBit ; Now issue the command. IssueType1Cmd: mov a,e ; Get command call SendCommand ; Issue command ; Check for error completion (in A). ; (Not Ready, Seek error, CRC error, Busy) sta DiskStatus ; Store away ani Type1ErrorMask mov b,a ; save away ani Type1HardMask ; (Not Ready, Busy) jz CheckTrack ; z => no hard error Type1HardError: mvi c,ErrorType1HardError jmp ErrorReport ; ERROR: Type 1 hard error CheckTrack: in FDCTrack ; Check track register xra d jz UpdateCylinder ; z => Track register is correct CommandTrackError: mvi c,ErrorCommandTrackError jmp ErrorReport ; ERROR: Track register is not correct ; Track register is correct. Update Cylinder location. UpdateCylinder: mov l,d mvi h,0 shld Cylinder ExitType1Cmd: mov a,b ; Return status ret ; Set single density (clear the DDen bit). SetSD: mvi a,nFDCDDenMask ; Clear double density bit call ClearFDCStateBit jmp IssueType1Cmd ; Subroutine: SetFDCStateBit. ; Set a bit in FDCState and FDCStateVal. ; On entry: A = Mask of bit to be set. SetFDCStateBit: lxi h,FDCStateVal ; Point to FDCStateVal ora m ; Set the bit StoreNewBit: mov m,a ; Store in FDCStateVal out FDCState ; Store in FDCState ret ; Subroutine: ClearFDCStateBit. ; Clear a bit in FDCState and FDCStateVal. ; On entry: A = Complement of mask of bit to be cleared. ClearFDCStateBit: lxi h,FDCStateVal ; Point to FDCStateVal ana m ; Clear the bit jmp StoreNewBit ; EXTERNAL Subroutine: FloppyInit. ; Reset the floppy controller, and restore the the heads. FloppyInit: mvi a,RestoreCmd ; Initialize Restore command sta RestoreCom ; This entry point is the value of the Restore command is to be changed. ; First check if there is a floppy (based on switch setting). FloppyInitE: in FDCStatusReg ; Read SA800 bit (now no-floppy bit) ani FDCSA800Mask jnz NoFloppyFound ; nz => no Floppy mvi a,RetryNo ; Initialize retry counter sta RetryCount InitializeAgain: call FDCReset mvi c,5 ; Issue 5 Stepin commands mvi e,StepInCmdNoV ; Send StepIn commands (no verify) InitializeStepLoop: mov a,e call SendCommand ; Issue command ; Don't care what the status is. Errors will be caught by the Restore. ani Type1HardMask ; (Not Ready, Busy) jnz Type1HardError ; nz => hard error dcr c jnz InitializeStepLoop ; We should now be beyond (in) from Track 00. Issue Restore. lda RestoreCom mov e,a mvi d,0 ; D ← 0 RestoreAgain: call DoType1Cmd ; Issue Restore ; Check for error completion. ; (A) = 0 for no errors, (A) # 0 for soft errors (with errors = 1). ; Cylinder location was updated if command was successful. ora a jz CheckTrk0 ; We had a soft error. Try again. (Later: restore and try again) DecrRestoreRetry: lxi h,RetryCount dcr m jnz InitializeAgain ; z => Too many retries ; Too many retries. Report error. RestoreFail: mvi c,ErrorRestoreFail jmp ErrorReport ; ERROR: Too many Restore failures. ; Check the Track 0 bit in the Status. CheckTrk0: lda DiskStatus ani FDCTk0Mask ; Track 0 bit set? jz DecrRestoreRetry ; z => no track 0 bit ; Restore was successful. ; We are now sitting at track 0. Cylinder is initialized. ; Initialize Side and Sector, ReadSectorCom and SeekCom. RestoreGood: xra a sta Side ; Initialize to side 0 inr a ; Sector starts at 1 sta Sector mvi a,ReadSectorCmd sta ReadSectorCom ; Initialize ReadSector command for Side 0 mvi a,SeekCmd sta SeekCom ret ; Switch setting indicated that no floppy was installed. NoFloppyFound: mvi c,ErrorNoFloppy jmp ErrorReport ; ERROR: No floppy drive installed. ; Subroutine: FDCReset. ; Reset the FDC, abort the automatic Restore with ForceInterupt, ; and wait until Busy is 0. ; Sets the default FDCState value in FDCState. ; Returns status in A, after command completes. FDCReset: mvi a,DisableFDC ; clear state register, reset FDC, enable Waits out FDCState lxi h,4 ; Wait for more than 50 usec (MR pulse) call Delay mvi a,DefaultFDCStateVal ; Get control byte for FDC sta FDCStateVal out FDCState mvi a,ForceInt0Cmd ; Issue Force Interrupt command out FDCCommand lxi h,1 ; Busy status not available for 12 usec call Delay ; Wait for Busy to reset. Status returned in A and B. WaitFDCBusy: in FDCStatus ; Check for restore command completion mov b,a ani FDCBusyMask jnz WaitFDCBusy mov a,b ; Return status in A too ret ; Subroutine: SendCommand. ; Issue the command to the FDC. ; Command is issued, and the Int Req register is polled for command completion. ; Call subroutine at GetFDCStatus to get status only, after Busy resets. ; On entry: ; A = Command ; On exit: ; A = Internal status ; B = External status SendCommand: out FDCCommand lxi h,1 call Delay ; Check for FDC command in IntReq. GetFDCStatus: in IntReq ; Check for completion in IntReq Reg. xri IntReqMask ; complement signals which are active low ani FDCIntMask jz GetFDCStatus ; Command has completed. Get status. in FDCStatusReg ; Get external status register. mov b,a ; Save in B in FDCStatus ; Check for restore command completion sta DiskStatus ; Store away ret ; return status in A, external status in B ; ; MAINTENANCE PANEL subroutines. ; Subroutine: PhaseToMP. ; Put Phase*50 + 99 in maintenance panel ; Put Phase*50 + 100 in MPOffset ; Note: Won't work for Phase=0. PhaseToMP: lxi h,MPStartPhase0-1 ; MP = 99 call PutMP lxi b,MPStartPhase0 ; B,C = 100 lxi h,Phase ; Point to Phase number mov e,m ; E is counter for number of 50's PhaseToMPLoop: lxi h,50 ; Increment panel by 50 call DeltaMP lxi h,50 ; Increment Offset in B,C by 50 dad b mov c,l mov b,h dcr e ; Are we done? jnz PhaseToMPLoop ; nz => still more ; Store B,C in MPOffset. mov l,c mov h,b shld MPOffset ; MPOffset ← MP number ret ; Subroutine: DoMiscClock. ; Clocks a bit in the MiscClocks1 register. ; Width of clock pulse is 14 cycles (~5 usec). ; On entry: D contains a mask of the bit(s) to be toggled. DoMiscClock: mvi a,0FFH ; Set all high xra d ; Clear clock bit(s) out MiscClocks1 xra d ; Toggle bit again out MiscClocks1 ret ; Subroutine: PutMP [H,L: number]. ; Put a number in the maintenance panel. ; Number is put modulo 10,000D. ; On entry: H,L contains the number to be put in the panel. ; Note: Call at DeltaMP to increment the MP by the contents of H,L. PutMP: call ClearMPanel ; Clear the panel DeltaMP: inx h ; Bias so that a value of zero can be used mvi a,BlankMPanel ; Blank MP out MiscControl1 jmp CheckMPCount PutMPLoop: mvi d,IncMPanel ; Mask for IncMPanel clock call DoMiscClock CheckMPCount: dcx h ; Decrement the count mov a,l ; Check for count = 0 ora h ; Low OR high jnz PutMPLoop ; nz => nonzero ; Done. xra a ; Unblank MP out MiscControl1 ret ; Subroutine: ClearMPanel. ; Clear the maintenance panel, and disable blanking. ClearMPanel: mvi d,ClrMPanel ; Mask for ClrMPanel clock call DoMiscClock xra a ; Clear BlankMPanel out MiscControl1 ret ; Subroutine: IncrMP. ; Increment the maintenance panel. IncrMP: push d ; Save D,E mvi d,IncMPanel ; Mask for ClrMPanel clock call DoMiscClock pop d ret ; Subroutine: ErrorReport. ; Blink the maintenance panel with the error number. ; No return from this subroutine. ; On entry: C >= 0 => Blink (MPOffset + C) in MP ; C < 0 => Blink current number in MP ErrorReport: mov a,c ; Chek sign of C ora a jm ErrorBlink ; A < 0 => don't put new number in MP ; Put (MPOffset+C) in MP. lhld MPOffset ; Get offset value mvi b,0 ; Clear high part of B,C dad b ; Add offset: H,L ← actual number call PutMP ; Put the number in the panel ErrorBlink: mvi a,BlankMPanel ; Blank bit in A lxi h,BlinkDelay ; H,L ← Blink delay constant ErrorTrap: out MiscControl1 ; Blank/unblank the panel call Delay xri BlankMPanel ; Toggle Blank bit jmp ErrorTrap ; Subroutine: Delay. ; Wait the period of time specified in H,L. ; H,L restored, A restored. ; Delay is 74 + 24*const cycles (const in H,L) ; = 25 + 8* const usec ; Maximum delay: H,L = 0FFFFH = ~.5 sec Delay: push psw ; [12] Save A push h ; [12] Save H,L ; Inner loop approximately 24 cycles (=8 usec) DelayLoop: dcx h ; [6] xra a ; [4] cmp l ; [4] jnz DelayLoop ; [10] cmp h ; [4] jnz DelayLoop ; [10] pop h ; [10] Restore H,L pop a ; [10] Restore A ret ; [12] END BootSubs