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