{
----------- Dandelion Processor Program - I/O Processor -----------
DESCRIPTION: Boot Program: IOP subroutines.
Last modification by Roy RXO : January 28, 1982 4:28 PM
File: BootSubs.asm
Stored: [Iris]<Workstation>Boot30>BootEPromRAM.dm
Written by Roy RXO .
Dennis DEG : 2-Sep-84 15:18:30, Add copyright notice.
}
{ Copyright (C) 1980, 1981, 1982 by Xerox Corporation. All rights reserved.}
; 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